From 8e92dfc2a56c47579c77276cd2c4ce43053603b6 Mon Sep 17 00:00:00 2001 From: Cedric Brandily Date: Tue, 17 Mar 2015 23:44:53 +0100 Subject: [PATCH 0001/3095] Use cliff deferred help instead of homemade one This change removes openstackclient homemade hack to defer help printing in initialize_app and uses cliff (new) option to defer help printing. Change-Id: Ie3e94ec96254745bfef8c5ff5abc405facfe1bea Related-Bug: #1316622 --- openstackclient/shell.py | 35 +++-------------------------------- 1 file changed, 3 insertions(+), 32 deletions(-) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 3cfd731219..141dcd667b 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -76,7 +76,8 @@ def __init__(self): super(OpenStackShell, self).__init__( description=__doc__.strip(), version=openstackclient.__version__, - command_manager=commandmanager.CommandManager('openstack.cli')) + command_manager=commandmanager.CommandManager('openstack.cli'), + deferred_help=True) self.api_version = {} @@ -92,35 +93,6 @@ def __init__(self): self.client_manager = None - # NOTE(dtroyer): This hack changes the help action that Cliff - # automatically adds to the parser so we can defer - # its execution until after the api-versioned commands - # have been loaded. There doesn't seem to be a - # way to edit/remove anything from an existing parser. - - # Replace the cliff-added help.HelpAction to defer its execution - self.DeferredHelpAction = None - for a in self.parser._actions: - if type(a) == help.HelpAction: - # Found it, save and replace it - self.DeferredHelpAction = a - - # These steps are argparse-implementation-dependent - self.parser._actions.remove(a) - if self.parser._option_string_actions['-h']: - del self.parser._option_string_actions['-h'] - if self.parser._option_string_actions['--help']: - del self.parser._option_string_actions['--help'] - - # Make a new help option to just set a flag - self.parser.add_argument( - '-h', '--help', - action='store_true', - dest='deferred_help', - default=False, - help="Show this help message and exit", - ) - def configure_logging(self): """Configure logging for the app @@ -276,8 +248,7 @@ def initialize_app(self, argv): # set up additional clients to stuff in to client_manager?? # Handle deferred help and exit - if self.options.deferred_help: - self.DeferredHelpAction(self.parser, self.parser, None, None) + self.print_help_if_requested() # Set up common client session if self.options.os_cacert: From 621434451f561e7ef7c549a134f3bfadcf10520f Mon Sep 17 00:00:00 2001 From: Marek Aufart Date: Tue, 17 Mar 2015 15:38:24 +0100 Subject: [PATCH 0002/3095] Add the ability to set and unset flavor properties Added flavor set and unset command which allow manage flavor properties called extra_specs. Command flavor show output was extended with these properties. Closes-Bug: 1434137 Change-Id: Ie469bade802de18aab9d58eda3fff46064008163 --- doc/source/command-objects/flavor.rst | 40 ++++++++++ openstackclient/compute/v2/flavor.py | 78 ++++++++++++++++++- .../tests/compute/v2/test_flavor.py | 77 +++++++++++++++++- setup.cfg | 2 + 4 files changed, 193 insertions(+), 4 deletions(-) diff --git a/doc/source/command-objects/flavor.rst b/doc/source/command-objects/flavor.rst index fa9fd80583..5254b12cae 100644 --- a/doc/source/command-objects/flavor.rst +++ b/doc/source/command-objects/flavor.rst @@ -123,3 +123,43 @@ Display flavor details .. describe:: Flavor to display (name or ID) + +flavor set +---------- + +Set flavor properties + +.. program:: flavor set +.. code:: bash + + os flavor set + [--property [...] ] + + +.. option:: --property + + Property to add or modify for this flavor (repeat option to set multiple properties) + +.. describe:: + + Flavor to modify (name or ID) + +flavor unset +------------ + +Unset flavor properties + +.. program:: flavor unset +.. code:: bash + + os flavor unset + [--property [...] ] + + +.. option:: --property + + Property to remove from flavor (repeat option to remove multiple properties) + +.. describe:: + + Flavor to modify (name or ID) diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 195c9a0de3..eb18a43332 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -22,6 +22,7 @@ from cliff import lister from cliff import show +from openstackclient.common import parseractions from openstackclient.common import utils @@ -237,8 +238,79 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) compute_client = self.app.client_manager.compute - flavor = utils.find_resource(compute_client.flavors, - parsed_args.flavor)._info.copy() - flavor.pop("links") + resource_flavor = utils.find_resource(compute_client.flavors, + parsed_args.flavor) + flavor = resource_flavor._info.copy() + flavor.pop("links", None) + + flavor['properties'] = utils.format_dict(resource_flavor.get_keys()) + + return zip(*sorted(six.iteritems(flavor))) + + +class SetFlavor(show.ShowOne): + """Set flavor properties""" + + log = logging.getLogger(__name__ + ".SetFlavor") + + def get_parser(self, prog_name): + parser = super(SetFlavor, self).get_parser(prog_name) + parser.add_argument( + "--property", + metavar="", + action=parseractions.KeyValueAction, + help='Property to add or modify for this flavor ' + '(repeat option to set multiple properties)', + ) + parser.add_argument( + "flavor", + metavar="", + help="Flavor to modify (name or ID)", + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + compute_client = self.app.client_manager.compute + resource_flavor = compute_client.flavors.find(name=parsed_args.flavor) + + resource_flavor.set_keys(parsed_args.property) + + flavor = resource_flavor._info.copy() + flavor['properties'] = utils.format_dict(resource_flavor.get_keys()) + flavor.pop("links", None) + return zip(*sorted(six.iteritems(flavor))) + + +class UnsetFlavor(show.ShowOne): + """Unset flavor properties""" + + log = logging.getLogger(__name__ + ".UnsetFlavor") + + def get_parser(self, prog_name): + parser = super(UnsetFlavor, self).get_parser(prog_name) + parser.add_argument( + "--property", + metavar="", + action='append', + help='Property to remove from flavor ' + '(repeat option to unset multiple properties)', + ) + parser.add_argument( + "flavor", + metavar="", + help="Flavor to modify (name or ID)", + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + compute_client = self.app.client_manager.compute + resource_flavor = compute_client.flavors.find(name=parsed_args.flavor) + + resource_flavor.unset_keys(parsed_args.property) + flavor = resource_flavor._info.copy() + flavor['properties'] = utils.format_dict(resource_flavor.get_keys()) + flavor.pop("links", None) return zip(*sorted(six.iteritems(flavor))) diff --git a/openstackclient/tests/compute/v2/test_flavor.py b/openstackclient/tests/compute/v2/test_flavor.py index 8f33ccfe71..19be812403 100644 --- a/openstackclient/tests/compute/v2/test_flavor.py +++ b/openstackclient/tests/compute/v2/test_flavor.py @@ -22,8 +22,17 @@ class FakeFlavorResource(fakes.FakeResource): + _keys = {'property': 'value'} + + def set_keys(self, args): + self._keys.update(args) + + def unset_keys(self, keys): + for key in keys: + self._keys.pop(key, None) + def get_keys(self): - return {'property': 'value'} + return self._keys class TestFlavor(compute_fakes.TestComputev2): @@ -272,3 +281,69 @@ def test_flavor_list_long(self): 'property=\'value\'' ), ) self.assertEqual(datalist, tuple(data)) + + +class TestFlavorSet(TestFlavor): + + def setUp(self): + super(TestFlavorSet, self).setUp() + + self.flavors_mock.find.return_value = FakeFlavorResource( + None, + copy.deepcopy(compute_fakes.FLAVOR), + loaded=True, + ) + + self.cmd = flavor.SetFlavor(self.app, None) + + def test_flavor_set(self): + arglist = [ + '--property', 'FOO="B A R"', + 'baremetal' + ] + verifylist = [ + ('property', {'FOO': '"B A R"'}), + ('flavor', 'baremetal') + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.flavors_mock.find.assert_called_with(name='baremetal') + + self.assertEqual('properties', columns[2]) + self.assertIn('FOO=\'"B A R"\'', data[2]) + + +class TestFlavorUnset(TestFlavor): + + def setUp(self): + super(TestFlavorUnset, self).setUp() + + self.flavors_mock.find.return_value = FakeFlavorResource( + None, + copy.deepcopy(compute_fakes.FLAVOR), + loaded=True, + ) + + self.cmd = flavor.UnsetFlavor(self.app, None) + + def test_flavor_unset(self): + arglist = [ + '--property', 'property', + 'baremetal' + ] + verifylist = [ + ('property', ['property']), + ('flavor', 'baremetal'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.flavors_mock.find.assert_called_with(name='baremetal') + + self.assertEqual('properties', columns[2]) + self.assertNotIn('property', data[2]) diff --git a/setup.cfg b/setup.cfg index 1575066e33..f2c1a05754 100644 --- a/setup.cfg +++ b/setup.cfg @@ -75,6 +75,8 @@ openstack.compute.v2 = flavor_delete = openstackclient.compute.v2.flavor:DeleteFlavor flavor_list = openstackclient.compute.v2.flavor:ListFlavor flavor_show = openstackclient.compute.v2.flavor:ShowFlavor + flavor_set = openstackclient.compute.v2.flavor:SetFlavor + flavor_unset = openstackclient.compute.v2.flavor:UnsetFlavor host_list = openstackclient.compute.v2.host:ListHost host_show = openstackclient.compute.v2.host:ShowHost From 7628510182da5f4a7d1ca570a8fd06965881155a Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Thu, 12 Mar 2015 01:02:21 -0400 Subject: [PATCH 0003/3095] Add a doc about authenticating against v3 i've had to explain this too many times, and paste the env. vars i'm using to folks on irc. Change-Id: I87677c57b309a865c1bd8ea42dc44c00d3ec0489 --- doc/source/authentication.rst | 55 +++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/doc/source/authentication.rst b/doc/source/authentication.rst index 5acfe33947..fd48a0312a 100644 --- a/doc/source/authentication.rst +++ b/doc/source/authentication.rst @@ -84,3 +84,58 @@ by the ``ClientManager`` object. * if ``--os-auth-url`` is not supplied for any of the types except Token/Endpoint, exit with an error. + +Authenticating using Identity Server API v3 +------------------------------------------- + +To authenticate against an Identity Server API v3, the +``OS_IDENTITY_API_VERSION`` environment variable or +``--os-identity-api-version`` option must be changed to ``3``, instead of the +default ``2.0``. Similarly ``OS_AUTH_URL`` or ``os-auth-url`` should also be +updated. + +.. code-block:: bash + + $ export OS_IDENTITY_API_VERSION=3 (Defaults to 2.0) + $ export OS_AUTH_URL=http://localhost:5000/v3 + +Since Identity API v3 authentication is a bit more complex, there are additional +options that may be set, either as command line options or environment +variables. The most common case will be a user supplying both user name and +password, along with the project name; previously in v2.0 this would be +sufficient, but since the Identity API v3 has a ``Domain`` component, we need +to tell the client in which domain the user and project exists. + +If using a user name and password to authenticate, specify either it's owning +domain name or ID. + + * ``--os-user-domain-name`` or ``OS_USER_DOMAIN_NAME`` + + * ``--os-user-domain-id`` or ``OS_USER_DOMAIN_ID`` + +If using a project name as authorization scope, specify either it's owning +domain name or ID. + + * ``--os-project-domain-name`` or ``OS_PROJECT_DOMAIN_NAME`` + + * ``--os-project-domain-id`` or ``OS_PROJECT_DOMAIN_ID`` + +If using a domain as authorization scope, set either it's name or ID. + + * ``--os-domain-name`` or ``OS_DOMAIN_NAME`` + + * ``--os-domain-id`` or ``OS_DOMAIN_ID`` + +Note that if the user and project share the same domain, then simply setting +``os-default-domain`` or ``OS_DEFAULT_DOMAIN`` is sufficient. + +Thus, a minimal set of of environment variables would be: + +.. code-block:: bash + + $ export OS_IDENTITY_API_VERSION=3 + $ export OS_AUTH_URL=http://localhost:5000/v3 + $ export OS_DEFAULT_DOMAIN=default + $ export OS_USERNAME=admin + $ export OS_PASSWORD=secret + $ export OS_PROJECT_NAME=admin From 6c224f5acfeb2288f2f4be41aee112fd01cbf4d0 Mon Sep 17 00:00:00 2001 From: Colleen Murphy Date: Mon, 16 Feb 2015 23:21:00 -0800 Subject: [PATCH 0004/3095] Add project and domain params to network create Without this patch, openstackclient has no way to specify to which project a network belongs upon creation. Instead, it uses the project ID that the user is authenticating with to fill the tenant_id column. This is a problem because an admin user is unable to specify a project for a non-admin network. To fix this and to improve feature parity with the neutron client, this patch adds project and domain parameters to the network create command and uses the given project name to look up the project ID. Neutron does not allow the project to be changed after creation, so no such parameter has been added to the neutron set command. Neutron calls the field 'tenant_id', but this change exposes the parameter as '--project' to support the newer terminology. If no project is specified, the client defaults to the previous behavior of using the auth project. Change-Id: Ia33ff7d599542c5b88baf2a69b063a23089a3cc4 --- doc/source/command-objects/network.rst | 10 ++ openstackclient/network/v2/network.py | 21 ++++ openstackclient/tests/identity/v2_0/fakes.py | 7 ++ .../tests/network/v2/test_network.py | 100 ++++++++++++++++++ 4 files changed, 138 insertions(+) diff --git a/doc/source/command-objects/network.rst b/doc/source/command-objects/network.rst index 0edd298048..dcba9f82b8 100644 --- a/doc/source/command-objects/network.rst +++ b/doc/source/command-objects/network.rst @@ -13,10 +13,20 @@ Create new network .. code:: bash os network create + [--domain ] [--enable | --disable] + [--project ] [--share | --no-share] +.. option:: --domain + + Owner's domain (name or ID)" + +.. option:: --project + + Owner's project (name or ID) + .. option:: --enable Enable network (default) diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 1a79c80ab8..9b2466422a 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -22,6 +22,7 @@ from openstackclient.common import exceptions from openstackclient.common import utils +from openstackclient.identity import common as identity_common from openstackclient.network import common @@ -82,6 +83,14 @@ def get_parser(self, prog_name): action='store_false', help='Do not share the network between projects', ) + parser.add_argument( + '--project', + metavar='', + help="Owner's project (name or ID)") + parser.add_argument( + '--domain', + metavar='', + help="Owner's domain (name or ID)") return parser def take_action(self, parsed_args): @@ -101,6 +110,18 @@ def get_body(self, parsed_args): 'admin_state_up': parsed_args.admin_state} if parsed_args.shared is not None: body['shared'] = parsed_args.shared + if parsed_args.project is not None: + identity_client = self.app.client_manager.identity + if parsed_args.domain is not None: + domain = identity_common.find_domain(identity_client, + parsed_args.domain) + project_id = utils.find_resource(identity_client.projects, + parsed_args.project, + domain_id=domain.id).id + else: + project_id = utils.find_resource(identity_client.projects, + parsed_args.project).id + body['tenant_id'] = project_id return {'network': body} diff --git a/openstackclient/tests/identity/v2_0/fakes.py b/openstackclient/tests/identity/v2_0/fakes.py index b136f84165..6688606a42 100644 --- a/openstackclient/tests/identity/v2_0/fakes.py +++ b/openstackclient/tests/identity/v2_0/fakes.py @@ -142,6 +142,13 @@ def __init__(self, **kwargs): self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] + def __getattr__(self, name): + # Map v3 'projects' back to v2 'tenants' + if name == "projects": + return self.tenants + else: + raise AttributeError(name) + class TestIdentityv2(utils.TestCommand): def setUp(self): diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index e14dd88b1d..90085f287a 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -16,6 +16,9 @@ from openstackclient.common import exceptions from openstackclient.network.v2 import network +from openstackclient.tests import fakes +from openstackclient.tests.identity.v2_0 import fakes as identity_fakes_v2 +from openstackclient.tests.identity.v3 import fakes as identity_fakes_v3 from openstackclient.tests.network import common RESOURCE = 'network' @@ -65,6 +68,7 @@ def test_create_no_options(self): ('name', FAKE_NAME), ('admin_state', True), ('shared', None), + ('project', None), ] mocker = mock.Mock(return_value=copy.deepcopy(RESPONSE)) self.app.client_manager.network.create_network = mocker @@ -85,15 +89,36 @@ def test_create_all_options(self): arglist = [ "--disable", "--share", + "--project", identity_fakes_v3.project_name, + "--domain", identity_fakes_v3.domain_name, FAKE_NAME, ] + self.given_show_options verifylist = [ ('admin_state', False), ('shared', True), + ('project', identity_fakes_v3.project_name), + ('domain', identity_fakes_v3.domain_name), ('name', FAKE_NAME), ] + self.then_show_options mocker = mock.Mock(return_value=copy.deepcopy(RESPONSE)) self.app.client_manager.network.create_network = mocker + identity_client = identity_fakes_v3.FakeIdentityv3Client( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN, + ) + self.app.client_manager.identity = identity_client + self.projects_mock = self.app.client_manager.identity.projects + self.projects_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes_v3.PROJECT), + loaded=True, + ) + self.domains_mock = self.app.client_manager.identity.domains + self.domains_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes_v3.DOMAIN), + loaded=True, + ) cmd = network.CreateNetwork(self.app, self.namespace) parsed_args = self.check_parser(cmd, arglist, verifylist) @@ -104,6 +129,7 @@ def test_create_all_options(self): 'admin_state_up': False, 'name': FAKE_NAME, 'shared': True, + 'tenant_id': identity_fakes_v3.project_id, } }) self.assertEqual(FILTERED, result) @@ -135,6 +161,80 @@ def test_create_other_options(self): }) self.assertEqual(FILTERED, result) + def test_create_with_project_identityv2(self): + arglist = [ + "--project", identity_fakes_v2.project_name, + FAKE_NAME, + + ] + verifylist = [ + ('admin_state', True), + ('shared', None), + ('name', FAKE_NAME), + ('project', identity_fakes_v2.project_name), + ] + mocker = mock.Mock(return_value=copy.deepcopy(RESPONSE)) + self.app.client_manager.network.create_network = mocker + identity_client = identity_fakes_v2.FakeIdentityv2Client( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN, + ) + self.app.client_manager.identity = identity_client + self.projects_mock = self.app.client_manager.identity.tenants + self.projects_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes_v2.PROJECT), + loaded=True, + ) + cmd = network.CreateNetwork(self.app, self.namespace) + + parsed_args = self.check_parser(cmd, arglist, verifylist) + result = list(cmd.take_action(parsed_args)) + + mocker.assert_called_with({ + RESOURCE: { + 'admin_state_up': True, + 'name': FAKE_NAME, + 'tenant_id': identity_fakes_v2.project_id, + } + }) + self.assertEqual(FILTERED, result) + + def test_create_with_domain_identityv2(self): + arglist = [ + "--project", identity_fakes_v3.project_name, + "--domain", identity_fakes_v3.domain_name, + FAKE_NAME, + ] + verifylist = [ + ('admin_state', True), + ('shared', None), + ('project', identity_fakes_v3.project_name), + ('domain', identity_fakes_v3.domain_name), + ('name', FAKE_NAME), + ] + mocker = mock.Mock(return_value=copy.deepcopy(RESPONSE)) + self.app.client_manager.network.create_network = mocker + identity_client = identity_fakes_v2.FakeIdentityv2Client( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN, + ) + self.app.client_manager.identity = identity_client + self.projects_mock = self.app.client_manager.identity.tenants + self.projects_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes_v2.PROJECT), + loaded=True, + ) + cmd = network.CreateNetwork(self.app, self.namespace) + parsed_args = self.check_parser(cmd, arglist, verifylist) + + self.assertRaises( + AttributeError, + cmd.take_action, + parsed_args, + ) + class TestDeleteNetwork(common.TestNetworkBase): def test_delete(self): From 2ed0e220490da5c6ee8f34754d70540890b17921 Mon Sep 17 00:00:00 2001 From: Rodrigo Duarte Date: Mon, 22 Sep 2014 14:04:01 +0000 Subject: [PATCH 0005/3095] Add parent field to project creation Adding the possibility to create projects hierarchies by adding the parent field in the create project call. Co-Authored-By: Victor Silva Implements: bp hierarchical-multitenancy Change-Id: I4eac4f5bc067634cc38c305dacc59ab1da63c153 --- doc/source/command-objects/project.rst | 6 ++ openstackclient/identity/v3/project.py | 15 ++- openstackclient/tests/identity/v3/fakes.py | 10 ++ .../tests/identity/v3/test_project.py | 100 ++++++++++++++++++ 4 files changed, 129 insertions(+), 2 deletions(-) diff --git a/doc/source/command-objects/project.rst b/doc/source/command-objects/project.rst index 422e239c72..b8607f0b62 100644 --- a/doc/source/command-objects/project.rst +++ b/doc/source/command-objects/project.rst @@ -25,6 +25,12 @@ Create new project .. versionadded:: 3 +.. option:: --parent + + Parent of the project (name or ID) + + .. versionadded:: 3 + .. option:: --description Project description diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index 1c93ad5d6c..49979763c0 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -46,6 +46,11 @@ def get_parser(self, prog_name): metavar='', help='Domain owning the project (name or ID)', ) + parser.add_argument( + '--parent', + metavar='', + help='Parent of the project (name or ID)', + ) parser.add_argument( '--description', metavar='', @@ -86,6 +91,13 @@ def take_action(self, parsed_args): else: domain = None + parent = None + if parsed_args.parent: + parent = utils.find_resource( + identity_client.projects, + parsed_args.parent, + ).id + enabled = True if parsed_args.disable: enabled = False @@ -97,6 +109,7 @@ def take_action(self, parsed_args): project = identity_client.projects.create( name=parsed_args.name, domain=domain, + parent=parent, description=parsed_args.description, enabled=enabled, **kwargs @@ -111,8 +124,6 @@ def take_action(self, parsed_args): raise e project._info.pop('links') - # TODO(stevemar): Remove the line below when we support multitenancy - project._info.pop('parent_id', None) return zip(*sorted(six.iteritems(project._info))) diff --git a/openstackclient/tests/identity/v3/fakes.py b/openstackclient/tests/identity/v3/fakes.py index c868401aa1..4056db46cf 100644 --- a/openstackclient/tests/identity/v3/fakes.py +++ b/openstackclient/tests/identity/v3/fakes.py @@ -135,6 +135,16 @@ 'links': base_url + 'regions/' + region_id, } +PROJECT_WITH_PARENT = { + 'id': project_id + '-with-parent', + 'name': project_name + ' and their parents', + 'description': project_description + ' plus another four', + 'enabled': True, + 'domain_id': domain_id, + 'parent_id': project_id, + 'links': base_url + 'projects/' + (project_id + '-with-parent'), +} + role_id = 'r1' role_name = 'roller' diff --git a/openstackclient/tests/identity/v3/test_project.py b/openstackclient/tests/identity/v3/test_project.py index 874908daab..ec50da0c30 100644 --- a/openstackclient/tests/identity/v3/test_project.py +++ b/openstackclient/tests/identity/v3/test_project.py @@ -16,6 +16,7 @@ import copy import mock +from openstackclient.common import exceptions from openstackclient.identity.v3 import project from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes @@ -60,6 +61,7 @@ def test_project_create_no_options(self): identity_fakes.project_name, ] verifylist = [ + ('parent', None), ('enable', False), ('disable', False), ('name', identity_fakes.project_name), @@ -75,6 +77,7 @@ def test_project_create_no_options(self): 'domain': None, 'description': None, 'enabled': True, + 'parent': None, } # ProjectManager.create(name=, domain=, description=, # enabled=, **kwargs) @@ -103,6 +106,7 @@ def test_project_create_description(self): ('enable', False), ('disable', False), ('name', identity_fakes.project_name), + ('parent', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -115,6 +119,7 @@ def test_project_create_description(self): 'domain': None, 'description': 'new desc', 'enabled': True, + 'parent': None, } # ProjectManager.create(name=, domain=, description=, # enabled=, **kwargs) @@ -143,6 +148,7 @@ def test_project_create_domain(self): ('enable', False), ('disable', False), ('name', identity_fakes.project_name), + ('parent', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -155,6 +161,7 @@ def test_project_create_domain(self): 'domain': identity_fakes.domain_id, 'description': None, 'enabled': True, + 'parent': None, } # ProjectManager.create(name=, domain=, description=, # enabled=, **kwargs) @@ -183,6 +190,7 @@ def test_project_create_domain_no_perms(self): ('enable', False), ('disable', False), ('name', identity_fakes.project_name), + ('parent', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) mocker = mock.Mock() @@ -197,6 +205,7 @@ def test_project_create_domain_no_perms(self): 'domain': identity_fakes.domain_id, 'description': None, 'enabled': True, + 'parent': None, } self.projects_mock.create.assert_called_with( **kwargs @@ -221,6 +230,7 @@ def test_project_create_enable(self): ('enable', True), ('disable', False), ('name', identity_fakes.project_name), + ('parent', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -233,6 +243,7 @@ def test_project_create_enable(self): 'domain': None, 'description': None, 'enabled': True, + 'parent': None, } # ProjectManager.create(name=, domain=, description=, # enabled=, **kwargs) @@ -260,6 +271,7 @@ def test_project_create_disable(self): ('enable', False), ('disable', True), ('name', identity_fakes.project_name), + ('parent', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -272,6 +284,7 @@ def test_project_create_disable(self): 'domain': None, 'description': None, 'enabled': False, + 'parent': None, } # ProjectManager.create(name=, domain=, # description=, enabled=, **kwargs) @@ -311,6 +324,7 @@ def test_project_create_property(self): 'domain': None, 'description': None, 'enabled': True, + 'parent': None, 'fee': 'fi', 'fo': 'fum', } @@ -331,6 +345,92 @@ def test_project_create_property(self): ) self.assertEqual(datalist, data) + def test_project_create_parent(self): + self.projects_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.PROJECT), + loaded=True, + ) + self.projects_mock.create.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.PROJECT_WITH_PARENT), + loaded=True, + ) + + arglist = [ + '--domain', identity_fakes.PROJECT_WITH_PARENT['domain_id'], + '--parent', identity_fakes.PROJECT['name'], + identity_fakes.PROJECT_WITH_PARENT['name'], + ] + verifylist = [ + ('domain', identity_fakes.PROJECT_WITH_PARENT['domain_id']), + ('parent', identity_fakes.PROJECT['name']), + ('enable', False), + ('disable', False), + ('name', identity_fakes.PROJECT_WITH_PARENT['name']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + kwargs = { + 'name': identity_fakes.PROJECT_WITH_PARENT['name'], + 'domain': identity_fakes.PROJECT_WITH_PARENT['domain_id'], + 'parent': identity_fakes.PROJECT['id'], + 'description': None, + 'enabled': True, + } + + self.projects_mock.create.assert_called_with( + **kwargs + ) + + collist = ( + 'description', + 'domain_id', + 'enabled', + 'id', + 'name', + 'parent_id', + ) + self.assertEqual(columns, collist) + datalist = ( + identity_fakes.PROJECT_WITH_PARENT['description'], + identity_fakes.PROJECT_WITH_PARENT['domain_id'], + identity_fakes.PROJECT_WITH_PARENT['enabled'], + identity_fakes.PROJECT_WITH_PARENT['id'], + identity_fakes.PROJECT_WITH_PARENT['name'], + identity_fakes.PROJECT['id'], + ) + self.assertEqual(data, datalist) + + def test_project_create_invalid_parent(self): + self.projects_mock.resource_class.__name__ = 'Project' + self.projects_mock.get.side_effect = exceptions.NotFound( + 'Invalid parent') + self.projects_mock.find.side_effect = exceptions.NotFound( + 'Invalid parent') + + arglist = [ + '--domain', identity_fakes.domain_name, + '--parent', 'invalid', + identity_fakes.project_name, + ] + verifylist = [ + ('domain', identity_fakes.domain_name), + ('parent', 'invalid'), + ('enable', False), + ('disable', False), + ('name', identity_fakes.project_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args, + ) + class TestProjectDelete(TestProject): From 6a9d6af225097969eacd376c7af298d906e40d95 Mon Sep 17 00:00:00 2001 From: Marco Fargetta Date: Wed, 4 Mar 2015 17:24:07 +0100 Subject: [PATCH 0006/3095] Add support to remote_id The federation APIs for the identity providers introduce a new parameter for every identity provider, named remote_ids, which contains a list of entity ID associated with. This parameter can be provided during the creation of the identity provider and can be updated at any time. For more information look at the blueprint: https://blueprints.launchpad.net/keystone/+spec/idp-id-registration This patch add the support to this new parameter in the command line by inserting the option "--remote-id" in the following commands: - "identity provider create" - "identity provider set" Additionally, the values can be read from a file, specified by "--remote-id-file", containing an entity id per line. Change-Id: Ie93340ee57e54128daa70d8a7bd0a9975ff7eef4 Depends-On: I12a262c55b5f6b5cc7007865edf30f14269da537 Implements: blueprint idp-id-registration --- .../command-objects/identity-provider.rst | 22 ++ .../identity/v3/identity_provider.py | 68 ++++- openstackclient/tests/identity/v3/fakes.py | 2 + .../identity/v3/test_identity_provider.py | 236 +++++++++++++++++- 4 files changed, 314 insertions(+), 14 deletions(-) diff --git a/doc/source/command-objects/identity-provider.rst b/doc/source/command-objects/identity-provider.rst index 47e274dd56..90f0b4942c 100644 --- a/doc/source/command-objects/identity-provider.rst +++ b/doc/source/command-objects/identity-provider.rst @@ -15,10 +15,21 @@ Create new identity provider .. code:: bash os identity provider create + [--remote-id [...] | --remote-id-file ] [--description ] [--enable | --disable] +.. option:: --remote-id + + Remote IDs to associate with the Identity Provider (repeat to provide + multiple values) + +.. option:: --remote-id-file + + Name of a file that contains many remote IDs to associate with the identity + provider, one per line + .. option:: --description New identity provider description @@ -69,9 +80,20 @@ Set identity provider properties .. code:: bash os identity provider set + [--remote-id [...] | --remote-id-file ] [--enable | --disable] +.. option:: --remote-id + + Remote IDs to associate with the Identity Provider (repeat to provide + multiple values) + +.. option:: --remote-id-file + + Name of a file that contains many remote IDs to associate with the identity + provider, one per line + .. option:: --enable Enable the identity provider diff --git a/openstackclient/identity/v3/identity_provider.py b/openstackclient/identity/v3/identity_provider.py index 691446da4d..8096580016 100644 --- a/openstackclient/identity/v3/identity_provider.py +++ b/openstackclient/identity/v3/identity_provider.py @@ -35,6 +35,20 @@ def get_parser(self, prog_name): metavar='', help='New identity provider name (must be unique)' ) + identity_remote_id_provider = parser.add_mutually_exclusive_group() + identity_remote_id_provider.add_argument( + '--remote-id', + metavar='', + action='append', + help='Remote IDs to associate with the Identity Provider ' + '(repeat to provide multiple values)' + ) + identity_remote_id_provider.add_argument( + '--remote-id-file', + metavar='', + help='Name of a file that contains many remote IDs to associate ' + 'with the identity provider, one per line' + ) parser.add_argument( '--description', metavar='', @@ -59,8 +73,17 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity + if parsed_args.remote_id_file: + file_content = utils.read_blob_file_contents( + parsed_args.remote_id_file) + remote_ids = file_content.splitlines() + remote_ids = list(map(str.strip, remote_ids)) + else: + remote_ids = (parsed_args.remote_id + if parsed_args.remote_id else None) idp = identity_client.federation.identity_providers.create( id=parsed_args.identity_provider_id, + remote_ids=remote_ids, description=parsed_args.description, enabled=parsed_args.enabled) @@ -119,6 +142,20 @@ def get_parser(self, prog_name): metavar='', help='Identity provider to modify', ) + identity_remote_id_provider = parser.add_mutually_exclusive_group() + identity_remote_id_provider.add_argument( + '--remote-id', + metavar='', + action='append', + help='Remote IDs to associate with the Identity Provider ' + '(repeat to provide multiple values)' + ) + identity_remote_id_provider.add_argument( + '--remote-id-file', + metavar='', + help='Name of a file that contains many remote IDs to associate ' + 'with the identity provider, one per line' + ) enable_identity_provider = parser.add_mutually_exclusive_group() enable_identity_provider.add_argument( '--enable', @@ -136,16 +173,33 @@ def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) federation_client = self.app.client_manager.identity.federation - if parsed_args.enable is True: - enabled = True - elif parsed_args.disable is True: - enabled = False - else: - self.log.error("No changes requested") + # Basic argument checking + if (not parsed_args.enable and not parsed_args.disable and not + parsed_args.remote_id and not parsed_args.remote_id_file): + self.log.error('No changes requested') return (None, None) + # Always set remote_ids if either is passed in + if parsed_args.remote_id_file: + file_content = utils.read_blob_file_contents( + parsed_args.remote_id_file) + remote_ids = file_content.splitlines() + remote_ids = list(map(str.strip, remote_ids)) + elif parsed_args.remote_id: + remote_ids = parsed_args.remote_id + + # Setup keyword args for the client + kwargs = {} + if parsed_args.enable: + kwargs['enabled'] = True + if parsed_args.disable: + kwargs['enabled'] = False + if parsed_args.remote_id_file or parsed_args.remote_id: + kwargs['remote_ids'] = remote_ids + identity_provider = federation_client.identity_providers.update( - parsed_args.identity_provider, enabled=enabled) + parsed_args.identity_provider, **kwargs) + identity_provider._info.pop('links', None) return zip(*sorted(six.iteritems(identity_provider._info))) diff --git a/openstackclient/tests/identity/v3/fakes.py b/openstackclient/tests/identity/v3/fakes.py index c868401aa1..4c7ef6c16c 100644 --- a/openstackclient/tests/identity/v3/fakes.py +++ b/openstackclient/tests/identity/v3/fakes.py @@ -231,9 +231,11 @@ idp_id = 'test_idp' idp_description = 'super exciting IdP description' +idp_remote_ids = ['entity1', 'entity2'] IDENTITY_PROVIDER = { 'id': idp_id, + 'remote_ids': idp_remote_ids, 'enabled': True, 'description': idp_description } diff --git a/openstackclient/tests/identity/v3/test_identity_provider.py b/openstackclient/tests/identity/v3/test_identity_provider.py index 527f01b581..cd328c1d4c 100644 --- a/openstackclient/tests/identity/v3/test_identity_provider.py +++ b/openstackclient/tests/identity/v3/test_identity_provider.py @@ -14,6 +14,8 @@ import copy +import mock + from openstackclient.identity.v3 import identity_provider from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes @@ -51,6 +53,7 @@ def test_create_identity_provider_no_options(self): # Set expected values kwargs = { + 'remote_ids': None, 'enabled': True, 'description': None, } @@ -60,12 +63,13 @@ def test_create_identity_provider_no_options(self): **kwargs ) - collist = ('description', 'enabled', 'id') + collist = ('description', 'enabled', 'id', 'remote_ids') self.assertEqual(collist, columns) datalist = ( identity_fakes.idp_description, True, identity_fakes.idp_id, + identity_fakes.idp_remote_ids ) self.assertEqual(datalist, data) @@ -83,6 +87,7 @@ def test_create_identity_provider_description(self): # Set expected values kwargs = { + 'remote_ids': None, 'description': identity_fakes.idp_description, 'enabled': True, } @@ -92,12 +97,121 @@ def test_create_identity_provider_description(self): **kwargs ) - collist = ('description', 'enabled', 'id') + collist = ('description', 'enabled', 'id', 'remote_ids') + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.idp_description, + True, + identity_fakes.idp_id, + identity_fakes.idp_remote_ids + ) + self.assertEqual(datalist, data) + + def test_create_identity_provider_remote_id(self): + arglist = [ + identity_fakes.idp_id, + '--remote-id', identity_fakes.idp_remote_ids[0] + ] + verifylist = [ + ('identity_provider_id', identity_fakes.idp_id), + ('remote_id', identity_fakes.idp_remote_ids[:1]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'remote_ids': identity_fakes.idp_remote_ids[:1], + 'description': None, + 'enabled': True, + } + + self.identity_providers_mock.create.assert_called_with( + id=identity_fakes.idp_id, + **kwargs + ) + + collist = ('description', 'enabled', 'id', 'remote_ids') + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.idp_description, + True, + identity_fakes.idp_id, + identity_fakes.idp_remote_ids + ) + self.assertEqual(datalist, data) + + def test_create_identity_provider_remote_ids_multiple(self): + arglist = [ + '--remote-id', identity_fakes.idp_remote_ids[0], + '--remote-id', identity_fakes.idp_remote_ids[1], + identity_fakes.idp_id + ] + verifylist = [ + ('identity_provider_id', identity_fakes.idp_id), + ('remote_id', identity_fakes.idp_remote_ids), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'remote_ids': identity_fakes.idp_remote_ids, + 'description': None, + 'enabled': True, + } + + self.identity_providers_mock.create.assert_called_with( + id=identity_fakes.idp_id, + **kwargs + ) + + collist = ('description', 'enabled', 'id', 'remote_ids') self.assertEqual(collist, columns) datalist = ( identity_fakes.idp_description, True, identity_fakes.idp_id, + identity_fakes.idp_remote_ids + ) + self.assertEqual(datalist, data) + + def test_create_identity_provider_remote_ids_file(self): + arglist = [ + '--remote-id-file', '/tmp/file_name', + identity_fakes.idp_id, + ] + verifylist = [ + ('identity_provider_id', identity_fakes.idp_id), + ('remote_id_file', '/tmp/file_name'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + mocker = mock.Mock() + mocker.return_value = "\n".join(identity_fakes.idp_remote_ids) + with mock.patch("openstackclient.identity.v3.identity_provider." + "utils.read_blob_file_contents", mocker): + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'remote_ids': identity_fakes.idp_remote_ids, + 'description': None, + 'enabled': True, + } + + self.identity_providers_mock.create.assert_called_with( + id=identity_fakes.idp_id, + **kwargs + ) + + collist = ('description', 'enabled', 'id', 'remote_ids') + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.idp_description, + True, + identity_fakes.idp_id, + identity_fakes.idp_remote_ids ) self.assertEqual(datalist, data) @@ -123,6 +237,7 @@ def test_create_identity_provider_disabled(self): # Set expected values kwargs = { + 'remote_ids': None, 'enabled': False, 'description': None, } @@ -132,12 +247,13 @@ def test_create_identity_provider_disabled(self): **kwargs ) - collist = ('description', 'enabled', 'id') + collist = ('description', 'enabled', 'id', 'remote_ids') self.assertEqual(collist, columns) datalist = ( None, False, identity_fakes.idp_id, + identity_fakes.idp_remote_ids ) self.assertEqual(datalist, data) @@ -241,12 +357,13 @@ def test_identity_provider_show(self): identity_fakes.idp_id, ) - collist = ('description', 'enabled', 'id') + collist = ('description', 'enabled', 'id', 'remote_ids') self.assertEqual(collist, columns) datalist = ( identity_fakes.idp_description, True, identity_fakes.idp_id, + identity_fakes.idp_remote_ids ) self.assertEqual(datalist, data) @@ -276,11 +393,14 @@ def prepare(self): prepare(self) arglist = [ '--disable', identity_fakes.idp_id, + '--remote-id', identity_fakes.idp_remote_ids[0], + '--remote-id', identity_fakes.idp_remote_ids[1] ] verifylist = [ ('identity_provider', identity_fakes.idp_id), ('enable', False), ('disable', True), + ('remote_id', identity_fakes.idp_remote_ids) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -288,14 +408,16 @@ def prepare(self): self.identity_providers_mock.update.assert_called_with( identity_fakes.idp_id, enabled=False, + remote_ids=identity_fakes.idp_remote_ids ) - collist = ('description', 'enabled', 'id') + collist = ('description', 'enabled', 'id', 'remote_ids') self.assertEqual(collist, columns) datalist = ( identity_fakes.idp_description, False, identity_fakes.idp_id, + identity_fakes.idp_remote_ids ) self.assertEqual(datalist, data) @@ -316,23 +438,122 @@ def prepare(self): prepare(self) arglist = [ '--enable', identity_fakes.idp_id, + '--remote-id', identity_fakes.idp_remote_ids[0], + '--remote-id', identity_fakes.idp_remote_ids[1] ] verifylist = [ ('identity_provider', identity_fakes.idp_id), ('enable', True), ('disable', False), + ('remote_id', identity_fakes.idp_remote_ids) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.identity_providers_mock.update.assert_called_with( - identity_fakes.idp_id, enabled=True) - collist = ('description', 'enabled', 'id') + identity_fakes.idp_id, enabled=True, + remote_ids=identity_fakes.idp_remote_ids) + collist = ('description', 'enabled', 'id', 'remote_ids') + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.idp_description, + True, + identity_fakes.idp_id, + identity_fakes.idp_remote_ids + ) + self.assertEqual(datalist, data) + + def test_identity_provider_replace_remote_ids(self): + """Enable Identity Provider. + + Set Identity Provider's ``enabled`` attribute to True. + """ + def prepare(self): + """Prepare fake return objects before the test is executed""" + self.new_remote_id = 'new_entity' + + updated_idp = copy.deepcopy(identity_fakes.IDENTITY_PROVIDER) + updated_idp['remote_ids'] = [self.new_remote_id] + resources = fakes.FakeResource( + None, + updated_idp, + loaded=True + ) + self.identity_providers_mock.update.return_value = resources + + prepare(self) + arglist = [ + '--enable', identity_fakes.idp_id, + '--remote-id', self.new_remote_id + ] + verifylist = [ + ('identity_provider', identity_fakes.idp_id), + ('enable', True), + ('disable', False), + ('remote_id', [self.new_remote_id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.identity_providers_mock.update.assert_called_with( + identity_fakes.idp_id, enabled=True, + remote_ids=[self.new_remote_id]) + collist = ('description', 'enabled', 'id', 'remote_ids') + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.idp_description, + True, + identity_fakes.idp_id, + [self.new_remote_id] + ) + self.assertEqual(datalist, data) + + def test_identity_provider_replace_remote_ids_file(self): + """Enable Identity Provider. + + Set Identity Provider's ``enabled`` attribute to True. + """ + def prepare(self): + """Prepare fake return objects before the test is executed""" + self.new_remote_id = 'new_entity' + + updated_idp = copy.deepcopy(identity_fakes.IDENTITY_PROVIDER) + updated_idp['remote_ids'] = [self.new_remote_id] + resources = fakes.FakeResource( + None, + updated_idp, + loaded=True + ) + self.identity_providers_mock.update.return_value = resources + + prepare(self) + arglist = [ + '--enable', identity_fakes.idp_id, + '--remote-id-file', self.new_remote_id, + ] + verifylist = [ + ('identity_provider', identity_fakes.idp_id), + ('enable', True), + ('disable', False), + ('remote_id_file', self.new_remote_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + mocker = mock.Mock() + mocker.return_value = self.new_remote_id + with mock.patch("openstackclient.identity.v3.identity_provider." + "utils.read_blob_file_contents", mocker): + columns, data = self.cmd.take_action(parsed_args) + self.identity_providers_mock.update.assert_called_with( + identity_fakes.idp_id, enabled=True, + remote_ids=[self.new_remote_id]) + collist = ('description', 'enabled', 'id', 'remote_ids') self.assertEqual(collist, columns) datalist = ( identity_fakes.idp_description, True, identity_fakes.idp_id, + [self.new_remote_id] ) self.assertEqual(datalist, data) @@ -361,6 +582,7 @@ def prepare(self): ('identity_provider', identity_fakes.idp_id), ('enable', False), ('disable', False), + ('remote_id', None) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) From 894fe6caf69333a7bd2d6ec7e0cefb093bf00a77 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Tue, 31 Mar 2015 06:05:13 +0000 Subject: [PATCH 0007/3095] Imported Translations from Transifex For more information about this automatic import see: https://wiki.openstack.org/wiki/Translations/Infrastructure Change-Id: I24c3357026b230c335a5366035a2ff744cd79622 --- .../LC_MESSAGES/python-openstackclient.po | 748 ++++++++++++++++++ 1 file changed, 748 insertions(+) create mode 100644 python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po diff --git a/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po b/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po new file mode 100644 index 0000000000..662b27ed75 --- /dev/null +++ b/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po @@ -0,0 +1,748 @@ +# Chinese (Traditional, Taiwan) translations for python-openstackclient. +# Copyright (C) 2015 ORGANIZATION +# This file is distributed under the same license as the +# python-openstackclient project. +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: python-openstackclient\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2015-03-31 06:05+0000\n" +"PO-Revision-Date: 2015-03-31 05:45+0000\n" +"Last-Translator: Zhang Xiaowei \n" +"Language-Team: Chinese (Taiwan) (http://www.transifex.com/projects/p" +"/python-openstackclient/language/zh_TW/)\n" +"Plural-Forms: nplurals=1; plural=0\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 1.3\n" + +#: openstackclient/api/auth.py:130 +msgid "Set a username with --os-username or OS_USERNAME\n" +msgstr "以 --os-username 或 OS_USERNAME 設定名稱\n" + +#: openstackclient/api/auth.py:132 +msgid "Set an authentication URL, with --os-auth-url or OS_AUTH_URL\n" +msgstr "以 --os-auth-url 或 OS_AUTH_URL 設定認證網址\n" + +#: openstackclient/api/auth.py:136 +msgid "" +"Set a scope, such as a project or domain, with --os-project-name or " +"OS_PROJECT_NAME" +msgstr "以 --os-project-name 或 OS_PROJECT_NAME 設定範圍,如同專案或區域" + +#: openstackclient/compute/v2/availability_zone.py:72 +#: openstackclient/compute/v2/server.py:643 +#: openstackclient/identity/v2_0/endpoint.py:114 +#: openstackclient/identity/v2_0/project.py:145 +#: openstackclient/identity/v2_0/service.py:128 +#: openstackclient/identity/v2_0/user.py:174 +msgid "List additional fields in output" +msgstr "列出額外的欄位" + +#: openstackclient/compute/v2/server.py:121 +#: openstackclient/compute/v2/server.py:158 +#: openstackclient/compute/v2/server.py:502 +#: openstackclient/compute/v2/server.py:714 +#: openstackclient/compute/v2/server.py:748 +#: openstackclient/compute/v2/server.py:831 +#: openstackclient/compute/v2/server.py:855 +#: openstackclient/compute/v2/server.py:910 +#: openstackclient/compute/v2/server.py:1002 +#: openstackclient/compute/v2/server.py:1042 +#: openstackclient/compute/v2/server.py:1068 +#: openstackclient/compute/v2/server.py:1133 +#: openstackclient/compute/v2/server.py:1157 +#: openstackclient/compute/v2/server.py:1216 +#: openstackclient/compute/v2/server.py:1253 +#: openstackclient/compute/v2/server.py:1411 +#: openstackclient/compute/v2/server.py:1435 +#: openstackclient/compute/v2/server.py:1459 +#: openstackclient/compute/v2/server.py:1483 +#: openstackclient/compute/v2/server.py:1507 +msgid "Server (name or ID)" +msgstr "伺服器(名稱或識別號)" + +#: openstackclient/compute/v2/server.py:126 +msgid "Security group to add (name or ID)" +msgstr "要加入的安全性群組(名稱或識別號)" + +#: openstackclient/compute/v2/server.py:163 +msgid "Volume to add (name or ID)" +msgstr "要加入的雲硬碟(名稱或識別號)" + +#: openstackclient/compute/v2/server.py:168 +msgid "Server internal device name for volume" +msgstr "雲硬碟在雲實例內的裝置名稱" + +#: openstackclient/compute/v2/server.py:208 +#: openstackclient/compute/v2/server.py:1162 +msgid "New server name" +msgstr "新雲實例名稱" + +#: openstackclient/compute/v2/server.py:216 +msgid "Create server from this image" +msgstr "從此映像檔新增雲實例" + +#: openstackclient/compute/v2/server.py:221 +msgid "Create server from this volume" +msgstr "從此雲硬碟新增雲實例" + +#: openstackclient/compute/v2/server.py:227 +msgid "Create server with this flavor" +msgstr "以這個虛擬硬體樣板新增雲實例" + +#: openstackclient/compute/v2/server.py:234 +msgid "Security group to assign to this server (repeat for multiple groups)" +msgstr "要指定到此雲實例的安全性群組(為多個群組重復指定)" + +#: openstackclient/compute/v2/server.py:240 +msgid "Keypair to inject into this server (optional extension)" +msgstr "要注入到此伺服器的密鑰對(選用)" + +#: openstackclient/compute/v2/server.py:246 +msgid "Set a property on this server (repeat for multiple values)" +msgstr "為此伺服器設定屬性(為多個值重複設定)" + +#: openstackclient/compute/v2/server.py:254 +msgid "File to inject into image before boot (repeat for multiple files)" +msgstr "在開機前要注入映像檔的檔案(為多個檔案重復指定)" + +#: openstackclient/compute/v2/server.py:260 +msgid "User data file to serve from the metadata server" +msgstr "來自詮釋資料伺服器要服務的用戶資料檔案" + +#: openstackclient/compute/v2/server.py:265 +msgid "Select an availability zone for the server" +msgstr "為雲實例選擇可用的區域。" + +#: openstackclient/compute/v2/server.py:272 +msgid "" +"Map block devices; map is ::: " +"(optional extension)" +msgstr "映射區塊裝置;映射是 :::(選用)" + +#: openstackclient/compute/v2/server.py:281 +msgid "Specify NIC configuration (optional extension)" +msgstr "指定網路卡設置(選用)" + +#: openstackclient/compute/v2/server.py:288 +msgid "Hints for the scheduler (optional extension)" +msgstr "給排程器的提示(選用)" + +#: openstackclient/compute/v2/server.py:294 +msgid "" +"Use specified volume as the config drive, or 'True' to use an ephemeral " +"drive" +msgstr "使用指定的雲硬碟為設定檔硬碟,或「True」來使用暫時性硬碟" + +#: openstackclient/compute/v2/server.py:302 +msgid "Minimum number of servers to launch (default=1)" +msgstr "最少要發動的雲實例(預設為 1)" + +#: openstackclient/compute/v2/server.py:309 +msgid "Maximum number of servers to launch (default=1)" +msgstr "最多要發動的雲實例(預設為 1)" + +#: openstackclient/compute/v2/server.py:314 +msgid "Wait for build to complete" +msgstr "等待完成建立" + +#: openstackclient/compute/v2/server.py:354 +msgid "min instances should be <= max instances" +msgstr "雲實例發動的最少值不應大於最大值" + +#: openstackclient/compute/v2/server.py:357 +msgid "min instances should be > 0" +msgstr "雲實例發動的最少值要大於 0" + +#: openstackclient/compute/v2/server.py:360 +msgid "max instances should be > 0" +msgstr "雲實例發動的最大值要大於 0" + +#: openstackclient/compute/v2/server.py:396 +msgid "either net-id or port-id should be specified but not both" +msgstr "任選網路識別號或接口識別號,但不能兩者都指定" + +#: openstackclient/compute/v2/server.py:418 +msgid "can't create server with port specified since neutron not enabled" +msgstr "Neutron 未啟用時,不能以指定的接口來新增雲實例" + +#: openstackclient/compute/v2/server.py:483 +#, python-format +msgid "Error creating server: %s" +msgstr "新增雲實例時出錯:%s" + +#: openstackclient/compute/v2/server.py:485 +msgid "" +"\n" +"Error creating server" +msgstr "" +"\n" +"新增雲實例時出錯" + +#: openstackclient/compute/v2/server.py:507 +msgid "Name of new image (default is server name)" +msgstr "新映像檔的名稱(預設為雲實例名稱)" + +#: openstackclient/compute/v2/server.py:512 +msgid "Wait for image create to complete" +msgstr "等待映像檔新增完成" + +#: openstackclient/compute/v2/server.py:542 +#, python-format +msgid "Error creating server snapshot: %s" +msgstr "新增雲實例即時存檔時出錯:%s" + +#: openstackclient/compute/v2/server.py:544 +msgid "" +"\n" +"Error creating server snapshot" +msgstr "" +"\n" +"新增雲實例即時存檔時出錯" + +#: openstackclient/compute/v2/server.py:566 +msgid "Server(s) to delete (name or ID)" +msgstr "要刪除的雲實例(名稱或識別號)" + +#: openstackclient/compute/v2/server.py:590 +msgid "Only return instances that match the reservation" +msgstr "只回傳要保留的雲實例" + +#: openstackclient/compute/v2/server.py:595 +msgid "Regular expression to match IP addresses" +msgstr "以正規式來匹配 IP 位址" + +#: openstackclient/compute/v2/server.py:600 +msgid "Regular expression to match IPv6 addresses" +msgstr "以正規式來匹配 IPv6 位址" + +#: openstackclient/compute/v2/server.py:605 +msgid "Regular expression to match names" +msgstr "以正規式來匹配名稱" + +#: openstackclient/compute/v2/server.py:610 +msgid "Regular expression to match instance name (admin only)" +msgstr "以正規式來匹配雲實例名稱(管理員專用)" + +#: openstackclient/compute/v2/server.py:616 +msgid "Search by server status" +msgstr "以雲實例狀態來尋找" + +#: openstackclient/compute/v2/server.py:621 +msgid "Search by flavor" +msgstr "以虛擬硬體樣板來尋找" + +#: openstackclient/compute/v2/server.py:626 +msgid "Search by image" +msgstr "以映像檔來尋找" + +#: openstackclient/compute/v2/server.py:631 +msgid "Search by hostname" +msgstr "以主機名稱來尋找" + +#: openstackclient/compute/v2/server.py:637 +msgid "Include all projects (admin only)" +msgstr "包括所有的專案(管理員專用)" + +#: openstackclient/compute/v2/server.py:753 +msgid "Target hostname" +msgstr "目標主機名稱" + +#: openstackclient/compute/v2/server.py:761 +msgid "Perform a shared live migration (default)" +msgstr "覆行已分享的即時轉移(預設)" + +#: openstackclient/compute/v2/server.py:767 +msgid "Perform a block live migration" +msgstr "覆行區塊的即時轉移" + +#: openstackclient/compute/v2/server.py:774 +msgid "Allow disk over-commit on the destination host" +msgstr "允許目標主機過量使用" + +#: openstackclient/compute/v2/server.py:781 +msgid "Do not over-commit disk on the destination host (default)" +msgstr "不準目標主機過量使用(預設)" + +#: openstackclient/compute/v2/server.py:787 +#: openstackclient/compute/v2/server.py:1088 +msgid "Wait for resize to complete" +msgstr "等待調整容量完成" + +#: openstackclient/compute/v2/server.py:815 +#: openstackclient/compute/v2/server.py:1113 +msgid "Complete\n" +msgstr "完成\n" + +#: openstackclient/compute/v2/server.py:817 +msgid "" +"\n" +"Error migrating server" +msgstr "" +"\n" +"轉移雲實例出錯" + +#: openstackclient/compute/v2/server.py:864 +msgid "Perform a hard reboot" +msgstr "履行強制重開機" + +#: openstackclient/compute/v2/server.py:872 +msgid "Perform a soft reboot" +msgstr "履行重開機" + +#: openstackclient/compute/v2/server.py:877 +msgid "Wait for reboot to complete" +msgstr "等待重開機完成" + +#: openstackclient/compute/v2/server.py:894 +msgid "" +"\n" +"Reboot complete\n" +msgstr "" +"\n" +"重開機完成\n" + +#: openstackclient/compute/v2/server.py:896 +msgid "" +"\n" +"Error rebooting server\n" +msgstr "" +"\n" +"重開雲實例出錯\n" + +#: openstackclient/compute/v2/server.py:916 +msgid "Recreate server from this image" +msgstr "從此映像檔重建雲實例" + +#: openstackclient/compute/v2/server.py:926 +msgid "Wait for rebuild to complete" +msgstr "等待重建完成" + +#: openstackclient/compute/v2/server.py:947 +msgid "" +"\n" +"Complete\n" +msgstr "" +"\n" +"完成\n" + +#: openstackclient/compute/v2/server.py:949 +msgid "" +"\n" +"Error rebuilding server" +msgstr "" +"\n" +"重建雲實例出錯" + +#: openstackclient/compute/v2/server.py:966 +msgid "Name or ID of server to use" +msgstr "要用的雲實例名稱或識別號" + +#: openstackclient/compute/v2/server.py:971 +msgid "Name or ID of security group to remove from server" +msgstr "要從雲實例移除的安全性群組名稱或識別號" + +#: openstackclient/compute/v2/server.py:1007 +msgid "Volume to remove (name or ID)" +msgstr "要移除的雲硬碟(名稱或識別號)" + +#: openstackclient/compute/v2/server.py:1073 +msgid "Resize server to specified flavor" +msgstr "調整雲實例容量來符合指定的虛擬硬體樣板" + +#: openstackclient/compute/v2/server.py:1078 +msgid "Confirm server resize is complete" +msgstr "確認調整雲實例容量完成" + +#: openstackclient/compute/v2/server.py:1083 +msgid "Restore server state before resize" +msgstr "恢復雲實例狀態到未調整容量前" + +#: openstackclient/compute/v2/server.py:1115 +msgid "" +"\n" +"Error resizing server" +msgstr "" +"\n" +"調整雲實例容量時出錯" + +#: openstackclient/compute/v2/server.py:1167 +msgid "Set new root password (interactive only)" +msgstr "設定新密碼(只限互動)" + +#: openstackclient/compute/v2/server.py:1173 +msgid "" +"Property to add/change for this server (repeat option to set multiple " +"properties)" +msgstr "要加入這個雲實例的屬性(重復這選項來設定多個屬性)" + +#: openstackclient/compute/v2/server.py:1197 +msgid "New password: " +msgstr "新密碼:" + +#: openstackclient/compute/v2/server.py:1198 +msgid "Retype new password: " +msgstr "重新輸入新密碼:" + +#: openstackclient/compute/v2/server.py:1202 +msgid "Passwords do not match, password unchanged" +msgstr "密碼不一樣,未更換密碼" + +#: openstackclient/compute/v2/server.py:1222 +msgid "Display server diagnostics information" +msgstr "顯示雲實例診斷資訊" + +#: openstackclient/compute/v2/server.py:1235 +msgid "Error retrieving diagnostics data" +msgstr "獲得診斷資料時出錯" + +#: openstackclient/compute/v2/server.py:1258 +msgid "Login name (ssh -l option)" +msgstr "登入名稱(ssh -l 選項)" + +#: openstackclient/compute/v2/server.py:1269 +msgid "Destination port (ssh -p option)" +msgstr "目標埠口(ssh -p 選項)" + +#: openstackclient/compute/v2/server.py:1281 +msgid "Private key file (ssh -i option)" +msgstr "私鑰檔案(ssh -i 選項)" + +#: openstackclient/compute/v2/server.py:1292 +msgid "Options in ssh_config(5) format (ssh -o option)" +msgstr "ssh_config(5) 格式的選項(ssh -o 選項)" + +#: openstackclient/compute/v2/server.py:1306 +msgid "Use only IPv4 addresses" +msgstr "只使用 IPv4 位址" + +#: openstackclient/compute/v2/server.py:1313 +msgid "Use only IPv6 addresses" +msgstr "只使用 IPv6 位址" + +#: openstackclient/compute/v2/server.py:1322 +msgid "Use public IP address" +msgstr "使用公開 IP 位址" + +#: openstackclient/compute/v2/server.py:1330 +msgid "Use private IP address" +msgstr "使用私人 IP 位址" + +#: openstackclient/compute/v2/server.py:1337 +msgid "Use other IP address (public, private, etc)" +msgstr "使用其他 IP 位址(公開、私人等等)" + +#: openstackclient/compute/v2/server.py:1514 +msgid "Property key to remove from server (repeat to unset multiple values)" +msgstr "要從雲實例上移除的屬性鍵(重復來取消選擇多個值)" + +#: openstackclient/identity/v2_0/catalog.py:73 +#: openstackclient/identity/v3/catalog.py:72 +msgid "Service to display (type or name)" +msgstr "要顯示的伺服器(類型或名稱)" + +#: openstackclient/identity/v2_0/ec2creds.py:40 +msgid "Specify an alternate project (default: current authenticated project)" +msgstr "指定替代的專案(預設值:目前已認證的專案)" + +#: openstackclient/identity/v2_0/ec2creds.py:46 +msgid "Specify an alternate user (default: current authenticated user)" +msgstr "指定替代的用戶(預設值:目前已認證的用戶)" + +#: openstackclient/identity/v2_0/ec2creds.py:95 +#: openstackclient/identity/v2_0/ec2creds.py:168 +msgid "Credentials access key" +msgstr "憑鑰存取密鑰" + +#: openstackclient/identity/v2_0/ec2creds.py:100 +#: openstackclient/identity/v2_0/ec2creds.py:130 +#: openstackclient/identity/v2_0/ec2creds.py:173 +msgid "Specify a user" +msgstr "指定用戶" + +#: openstackclient/identity/v2_0/endpoint.py:40 +msgid "New endpoint service (name or ID)" +msgstr "新端點伺服器(名稱或識別號)" + +#: openstackclient/identity/v2_0/endpoint.py:46 +msgid "New endpoint public URL (required)" +msgstr "新端點公開網址(需要)" + +#: openstackclient/identity/v2_0/endpoint.py:51 +msgid "New endpoint admin URL" +msgstr "新端點管理員網址" + +#: openstackclient/identity/v2_0/endpoint.py:56 +msgid "New endpoint internal URL" +msgstr "新端點內部網址" + +#: openstackclient/identity/v2_0/endpoint.py:61 +msgid "New endpoint region ID" +msgstr "新端點地區識別號" + +#: openstackclient/identity/v2_0/endpoint.py:93 +msgid "Endpoint ID to delete" +msgstr "要刪除的端點識別號" + +#: openstackclient/identity/v2_0/endpoint.py:149 +msgid "Endpoint ID to display" +msgstr "要顯示的端點識別號" + +#: openstackclient/identity/v2_0/project.py:41 +msgid "New project name" +msgstr "新專案名稱" + +#: openstackclient/identity/v2_0/project.py:46 +msgid "Project description" +msgstr "專案描述" + +#: openstackclient/identity/v2_0/project.py:52 +msgid "Enable project (default)" +msgstr "啟用專案(預設)" + +#: openstackclient/identity/v2_0/project.py:57 +#: openstackclient/identity/v2_0/project.py:194 +msgid "Disable project" +msgstr "關閉專案" + +#: openstackclient/identity/v2_0/project.py:63 +msgid "Add a property to (repeat option to set multiple properties)" +msgstr "加入屬性到 (重復這選項來設定多個屬性)" + +#: openstackclient/identity/v2_0/project.py:69 +#: openstackclient/identity/v3/project.py:75 +msgid "Return existing project" +msgstr "回傳存在的專案" + +#: openstackclient/identity/v2_0/project.py:117 +msgid "Project(s) to delete (name or ID)" +msgstr "要刪除的專案(名稱或識別號)" + +#: openstackclient/identity/v2_0/project.py:173 +msgid "Project to modify (name or ID)" +msgstr "要更改的專案(名稱或識別號)" + +#: openstackclient/identity/v2_0/project.py:178 +msgid "Set project name" +msgstr "設定專案名稱" + +#: openstackclient/identity/v2_0/project.py:183 +msgid "Set project description" +msgstr "設定專案描述" + +#: openstackclient/identity/v2_0/project.py:189 +msgid "Enable project" +msgstr "啟用專案" + +#: openstackclient/identity/v2_0/project.py:200 +msgid "Set a project property (repeat option to set multiple properties)" +msgstr "設定專案屬性(重復這選項來設定多個屬性)" + +#: openstackclient/identity/v2_0/project.py:253 +msgid "Project to display (name or ID)" +msgstr "要顯示的專案(名稱或識別號)" + +#: openstackclient/identity/v2_0/role.py:41 +msgid "Role to add to : (name or ID)" +msgstr "加入角色到 :(名稱或識別號)" + +#: openstackclient/identity/v2_0/role.py:47 +#: openstackclient/identity/v2_0/role.py:309 +msgid "Include (name or ID)" +msgstr "包括 (名稱或識別號)" + +#: openstackclient/identity/v2_0/role.py:53 +#: openstackclient/identity/v2_0/role.py:315 +msgid "Include (name or ID)" +msgstr "包括 (名稱或識別號)" + +#: openstackclient/identity/v2_0/role.py:87 +msgid "New role name" +msgstr "新角色名稱" + +#: openstackclient/identity/v2_0/role.py:92 +#: openstackclient/identity/v3/role.py:157 +msgid "Return existing role" +msgstr "回傳存在的角色" + +#: openstackclient/identity/v2_0/role.py:127 +msgid "Role(s) to delete (name or ID)" +msgstr "要刪除的角色(名稱或識別號)" + +#: openstackclient/identity/v2_0/role.py:194 +#: openstackclient/identity/v2_0/role.py:257 +msgid "Project must be specified" +msgstr "必須指定專案" + +#: openstackclient/identity/v2_0/role.py:208 +#: openstackclient/identity/v2_0/role.py:263 +msgid "User must be specified" +msgstr "必須指定用戶" + +#: openstackclient/identity/v2_0/role.py:236 +msgid "User to list (name or ID)" +msgstr "要列出的用戶(名稱或識別號)" + +#: openstackclient/identity/v2_0/role.py:241 +msgid "Filter users by (name or ID)" +msgstr "以 來篩選用戶(名稱或識別號)" + +#: openstackclient/identity/v2_0/role.py:303 +msgid "Role to remove (name or ID)" +msgstr "要移除的角色(名稱或識別號)" + +#: openstackclient/identity/v2_0/role.py:344 +msgid "Role to display (name or ID)" +msgstr "要顯示的角色(名稱或識別號)" + +#: openstackclient/identity/v2_0/service.py:42 +msgid "New service type (compute, image, identity, volume, etc)" +msgstr "新伺服器類型(compute、image、identity 或 volume 等等)" + +#: openstackclient/identity/v2_0/service.py:53 +msgid "New service name" +msgstr "新伺服器名稱" + +#: openstackclient/identity/v2_0/service.py:58 +msgid "New service description" +msgstr "新伺服器描述" + +#: openstackclient/identity/v2_0/service.py:78 +msgid "" +"The argument --type is deprecated, use service create --name type instead." +msgstr "已經淘汰 --type 參數,請用 service create --name 來代替。" + +#: openstackclient/identity/v2_0/service.py:105 +msgid "Service to delete (name or ID)" +msgstr "要刪除的伺服器(名稱或識別號)" + +#: openstackclient/identity/v2_0/service.py:156 +msgid "Service to display (type, name or ID)" +msgstr "要顯示的伺服器(類型、名稱或識別號)" + +#: openstackclient/identity/v2_0/service.py:162 +msgid "Show service catalog information" +msgstr "顯示伺服器分類資訊" + +#: openstackclient/identity/v2_0/service.py:180 +#, python-format +msgid "No service catalog with a type, name or ID of '%s' exists." +msgstr "沒有相似「%s」類型、名稱或識別號的伺服器分類。" + +#: openstackclient/identity/v2_0/token.py:54 +msgid "Token to be deleted" +msgstr "要刪除的記號" + +#: openstackclient/identity/v2_0/user.py:40 +msgid "New user name" +msgstr "新用戶名稱" + +#: openstackclient/identity/v2_0/user.py:45 +msgid "Default project (name or ID)" +msgstr "預設專案(名稱或識別號)" + +#: openstackclient/identity/v2_0/user.py:50 +#: openstackclient/identity/v2_0/user.py:273 +msgid "Set user password" +msgstr "設定用戶密碼" + +#: openstackclient/identity/v2_0/user.py:56 +#: openstackclient/identity/v2_0/user.py:279 +msgid "Prompt interactively for password" +msgstr "為密碼互動提示" + +#: openstackclient/identity/v2_0/user.py:61 +#: openstackclient/identity/v2_0/user.py:284 +msgid "Set user email address" +msgstr "設定用戶電子信箱位址" + +#: openstackclient/identity/v2_0/user.py:67 +#: openstackclient/identity/v2_0/user.py:290 +msgid "Enable user (default)" +msgstr "啟用用戶(預設)" + +#: openstackclient/identity/v2_0/user.py:72 +#: openstackclient/identity/v2_0/user.py:295 +msgid "Disable user" +msgstr "關閉用戶" + +#: openstackclient/identity/v2_0/user.py:77 +#: openstackclient/identity/v3/user.py:89 +msgid "Return existing user" +msgstr "回傳存在的用戶" + +#: openstackclient/identity/v2_0/user.py:141 +msgid "User(s) to delete (name or ID)" +msgstr "要刪除的用戶(名稱或識別號)" + +#: openstackclient/identity/v2_0/user.py:168 +msgid "Filter users by project (name or ID)" +msgstr "以專案篩選用戶(名稱或識別號)" + +#: openstackclient/identity/v2_0/user.py:258 +msgid "User to change (name or ID)" +msgstr "要更換的用戶(名稱或識別號)" + +#: openstackclient/identity/v2_0/user.py:263 +msgid "Set user name" +msgstr "設定用戶名稱" + +#: openstackclient/identity/v2_0/user.py:268 +msgid "Set default project (name or ID)" +msgstr "設定預設專案(名稱或識別號)" + +#: openstackclient/identity/v2_0/user.py:361 +msgid "User to display (name or ID)" +msgstr "要顯示的用戶(名稱或識別號)" + +#: openstackclient/identity/v3/domain.py:62 +msgid "Return existing domain" +msgstr "回傳存在的地域" + +#: openstackclient/identity/v3/group.py:133 +msgid "Return existing group" +msgstr "回傳存在的群組" + +#: openstackclient/identity/v3/region.py:39 +msgid "New region ID" +msgstr "新地區識別號" + +#: openstackclient/identity/v3/region.py:44 +msgid "Parent region ID" +msgstr "父地區識別號" + +#: openstackclient/identity/v3/region.py:49 +#: openstackclient/identity/v3/region.py:151 +msgid "New region description" +msgstr "新地區描述" + +#: openstackclient/identity/v3/region.py:54 +#: openstackclient/identity/v3/region.py:156 +msgid "New region url" +msgstr "新地區網址" + +#: openstackclient/identity/v3/region.py:86 +msgid "Region ID to delete" +msgstr "要刪除的地區識別號" + +#: openstackclient/identity/v3/region.py:108 +msgid "Filter by parent region ID" +msgstr "以父地區識別號來篩選" + +#: openstackclient/identity/v3/region.py:141 +msgid "Region to modify" +msgstr "要更改的地區" + +#: openstackclient/identity/v3/region.py:146 +msgid "New parent region ID" +msgstr "新父地區識別號" + +#: openstackclient/identity/v3/region.py:191 +msgid "Region to display" +msgstr "要顯示的地區" + From f6bd2fa394e2878586f67b38fa64609a86c5d6df Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Tue, 31 Mar 2015 18:38:53 +0000 Subject: [PATCH 0008/3095] Updated from global requirements Change-Id: I5907d473b34799f9361ad2b611868ad22db3eaf6 --- requirements.txt | 14 +++++++------- test-requirements.txt | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/requirements.txt b/requirements.txt index 97d06c48f4..2e18b0b85f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,16 +5,16 @@ pbr>=0.6,!=0.7,<1.0 six>=1.9.0 Babel>=1.3 -cliff>=1.7.0 # Apache-2.0 +cliff>=1.10.0,<1.11.0 # Apache-2.0 cliff-tablib>=1.0 -oslo.config>=1.9.0 # Apache-2.0 -oslo.i18n>=1.3.0 # Apache-2.0 -oslo.utils>=1.2.0 # Apache-2.0 -oslo.serialization>=1.2.0 # Apache-2.0 +oslo.config>=1.9.3,<1.10.0 # Apache-2.0 +oslo.i18n>=1.5.0,<1.6.0 # Apache-2.0 +oslo.utils>=1.4.0,<1.5.0 # Apache-2.0 +oslo.serialization>=1.4.0,<1.5.0 # Apache-2.0 python-glanceclient>=0.15.0 python-keystoneclient>=1.1.0 -python-novaclient>=2.18.0,!=2.21.0 +python-novaclient>=2.22.0 python-cinderclient>=1.1.0 python-neutronclient>=2.3.11,<3 requests>=2.2.0,!=2.4.0 -stevedore>=1.1.0 # Apache-2.0 +stevedore>=1.3.0,<1.4.0 # Apache-2.0 diff --git a/test-requirements.txt b/test-requirements.txt index e897e60152..cbbac96bb2 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -7,9 +7,9 @@ coverage>=3.6 discover fixtures>=0.3.14 mock>=1.0 -oslosphinx>=2.2.0 # Apache-2.0 -oslotest>=1.2.0 # Apache-2.0 -requests-mock>=0.5.1 # Apache-2.0 +oslosphinx>=2.5.0,<2.6.0 # Apache-2.0 +oslotest>=1.5.1,<1.6.0 # Apache-2.0 +requests-mock>=0.6.0 # Apache-2.0 sphinx>=1.1.2,!=1.2.0,!=1.3b1,<1.3 testrepository>=0.0.18 testtools>=0.9.36,!=1.2.0 From e60bf28ae3bdb34b65316249f0e7615048aa1f95 Mon Sep 17 00:00:00 2001 From: Jamie Lennox Date: Thu, 2 Apr 2015 09:58:26 +1100 Subject: [PATCH 0009/3095] Use glanceclient's inbuilt images find Glanceclient image listing was special cased as it wasn't implemented in glanceclient directly. This is no longer the case and we should use glanceclient's functions. Change-Id: If8d1246f1bd97c07f9f10f5457aa32124efa0be3 --- openstackclient/image/client.py | 56 +-------------------------------- 1 file changed, 1 insertion(+), 55 deletions(-) diff --git a/openstackclient/image/client.py b/openstackclient/image/client.py index 3577966485..c78f442524 100644 --- a/openstackclient/image/client.py +++ b/openstackclient/image/client.py @@ -15,9 +15,6 @@ import logging -from glanceclient import exc as gc_exceptions -from glanceclient.v1 import client as gc_v1_client -from glanceclient.v1 import images as gc_v1_images from openstackclient.common import utils @@ -27,7 +24,7 @@ API_VERSION_OPTION = 'os_image_api_version' API_NAME = "image" API_VERSIONS = { - "1": "openstackclient.image.client.Client_v1", + "1": "glanceclient.v1.client.Client", "2": "glanceclient.v2.client.Client", } @@ -89,54 +86,3 @@ def build_option_parser(parser): DEFAULT_IMAGE_API_VERSION + ' (Env: OS_IMAGE_API_VERSION)') return parser - - -# NOTE(dtroyer): glanceclient.v1.image.ImageManager() doesn't have a find() -# method so add one here until the common client libs arrive -# A similar subclass will be required for v2 - -class Client_v1(gc_v1_client.Client): - """An image v1 client that uses ImageManager_v1""" - - def __init__(self, *args, **kwargs): - super(Client_v1, self).__init__(*args, **kwargs) - self.images = ImageManager_v1(getattr(self, 'http_client', self)) - - -class ImageManager_v1(gc_v1_images.ImageManager): - """Add find() and findall() to the ImageManager class""" - - def find(self, **kwargs): - """Find a single item with attributes matching ``**kwargs``. - - This isn't very efficient: it loads the entire list then filters on - the Python side. - """ - rl = self.findall(**kwargs) - num = len(rl) - - if num == 0: - raise gc_exceptions.NotFound - elif num > 1: - raise gc_exceptions.NoUniqueMatch - else: - return rl[0] - - def findall(self, **kwargs): - """Find all items with attributes matching ``**kwargs``. - - This isn't very efficient: it loads the entire list then filters on - the Python side. - """ - found = [] - searches = kwargs.items() - - for obj in self.list(): - try: - if all(getattr(obj, attr) == value - for (attr, value) in searches): - found.append(obj) - except AttributeError: - continue - - return found From ec4ef5f5ba0b5aada506fd979f10e36c2b35a0f6 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 18 Mar 2015 10:25:31 -0500 Subject: [PATCH 0010/3095] Suppress warnings user can't fix Requests/urllib3 started issuing warnings about certificates and SSL that our users are unable to do anything about. This is a very blunt way to suppress these warnings unless --verbose or --debug is supplied on the command line. Being more precise in the suppression requires importing the warning classes from urllib3 and dealing with the platforms where it has been unvendored from requests. Maybe in the future if there are concerns that this mutes too much otherwise. Change-Id: I50bb10a16222de12c5b95bfe042b92e43ea8ee7c --- openstackclient/shell.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 3cfd731219..172c03120e 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -1,4 +1,5 @@ # Copyright 2012-2013 OpenStack Foundation +# Copyright 2015 Dean Troyer # # 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 @@ -19,6 +20,7 @@ import logging import sys import traceback +import warnings from cliff import app from cliff import command @@ -139,12 +141,15 @@ def configure_logging(self): if self.options.verbose_level == 0: # --quiet root_logger.setLevel(logging.ERROR) + warnings.simplefilter("ignore") elif self.options.verbose_level == 1: # This is the default case, no --debug, --verbose or --quiet root_logger.setLevel(logging.WARNING) + warnings.simplefilter("ignore") elif self.options.verbose_level == 2: # One --verbose root_logger.setLevel(logging.INFO) + warnings.simplefilter("once") elif self.options.verbose_level >= 3: # Two or more --verbose root_logger.setLevel(logging.DEBUG) From 77e3fbae1a866e05219d272f9c3bdbaaa8030854 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Fri, 3 Apr 2015 02:26:22 -0400 Subject: [PATCH 0011/3095] Add support for showing limits of a specific project Looks like the option to show limits of a specific project was missing. This resulted in always using the authenticated project. Change-Id: I512a05df20860ffb52af34f3b64c9eb81dae8c61 Closes-Bug: 1438379 --- doc/source/command-objects/limits.rst | 10 +++++++++ openstackclient/common/limits.py | 29 ++++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/doc/source/command-objects/limits.rst b/doc/source/command-objects/limits.rst index 1eae4889f5..0d466af67f 100644 --- a/doc/source/command-objects/limits.rst +++ b/doc/source/command-objects/limits.rst @@ -16,6 +16,8 @@ Show compute and volume limits os limits show --absolute [--reserved] | --rate + [--project ] + [--domain ] .. option:: --absolute @@ -28,3 +30,11 @@ Show compute and volume limits .. option:: --reserved Include reservations count [only valid with :option:`--absolute`] + +.. option:: --project + + Show limits for a specific project (name or ID) [only valid with --absolute] + +.. option:: --domain + + Domain that owns --project (name or ID) [only valid with --absolute] diff --git a/openstackclient/common/limits.py b/openstackclient/common/limits.py index 9c9458ab91..4abcf169a3 100644 --- a/openstackclient/common/limits.py +++ b/openstackclient/common/limits.py @@ -21,6 +21,7 @@ from cliff import lister from openstackclient.common import utils +from openstackclient.identity import common as identity_common class ShowLimits(lister.Lister): @@ -49,6 +50,18 @@ def get_parser(self, prog_name): action="store_true", default=False, help="Include reservations count [only valid with --absolute]") + parser.add_argument( + '--project', + metavar='', + help='Show limits for a specific project (name or ID)' + ' [only valid with --absolute]', + ) + parser.add_argument( + '--domain', + metavar='', + help='Domain that owns --project (name or ID)' + ' [only valid with --absolute]', + ) return parser def take_action(self, parsed_args): @@ -57,7 +70,21 @@ def take_action(self, parsed_args): compute_client = self.app.client_manager.compute volume_client = self.app.client_manager.volume - compute_limits = compute_client.limits.get(parsed_args.is_reserved) + project_id = None + if parsed_args.project is not None: + identity_client = self.app.client_manager.identity + if parsed_args.domain is not None: + domain = identity_common.find_domain(identity_client, + parsed_args.domain) + project_id = utils.find_resource(identity_client.projects, + parsed_args.project, + domain_id=domain.id).id + else: + project_id = utils.find_resource(identity_client.projects, + parsed_args.project).id + + compute_limits = compute_client.limits.get(parsed_args.is_reserved, + tenant_id=project_id) volume_limits = volume_client.limits.get() if parsed_args.is_absolute: From 0d689871b4d8c060e1d48a9472449f3d670333bb Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 5 Sep 2014 02:00:36 -0500 Subject: [PATCH 0012/3095] Fix session timing Subclass keystoneclient.session.Session to add the timing hooks to record the elapsed time returned by requests.Response objects, including the redirection history. Redirects are included individually and not rolled into the total time for the original request. This works for all clients that use OSC's session. Closes-Bug: #1402577 Change-Id: I9360c90c151579b89a37edb8c11c17feb15b3cb9 --- openstackclient/common/clientmanager.py | 4 +- openstackclient/common/session.py | 50 +++++++++++++++++++++ openstackclient/common/timing.py | 10 +++-- openstackclient/shell.py | 9 ++-- openstackclient/tests/common/test_timing.py | 17 +++---- 5 files changed, 71 insertions(+), 19 deletions(-) create mode 100644 openstackclient/common/session.py diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index 10f38c25bc..6d2a9d76b7 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -19,10 +19,10 @@ import pkg_resources import sys -from keystoneclient import session import requests from openstackclient.api import auth +from openstackclient.common import session as osc_session from openstackclient.identity import client as identity_client @@ -157,7 +157,7 @@ def setup_auth(self): self.auth = auth_plugin.load_from_options(**self._auth_params) # needed by SAML authentication request_session = requests.session() - self.session = session.Session( + self.session = osc_session.TimingSession( auth=self.auth, session=request_session, verify=self._verify, diff --git a/openstackclient/common/session.py b/openstackclient/common/session.py new file mode 100644 index 0000000000..dda1c41709 --- /dev/null +++ b/openstackclient/common/session.py @@ -0,0 +1,50 @@ +# 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. +# + +"""Subclass of keystoneclient.session""" + +from keystoneclient import session + + +class TimingSession(session.Session): + """A Session that supports collection of timing data per Method URL""" + + def __init__( + self, + **kwargs + ): + """Pass through all arguments except timing""" + super(TimingSession, self).__init__(**kwargs) + + # times is a list of tuples: ("method url", elapsed_time) + self.times = [] + + def get_timings(self): + return self.times + + def reset_timings(self): + self.times = [] + + def request(self, url, method, **kwargs): + """Wrap the usual request() method with the timers""" + resp = super(TimingSession, self).request(url, method, **kwargs) + for h in resp.history: + self.times.append(( + "%s %s" % (h.request.method, h.request.url), + h.elapsed, + )) + self.times.append(( + "%s %s" % (resp.request.method, resp.request.url), + resp.elapsed, + )) + return resp diff --git a/openstackclient/common/timing.py b/openstackclient/common/timing.py index 1c94682c84..d13c86e73c 100644 --- a/openstackclient/common/timing.py +++ b/openstackclient/common/timing.py @@ -33,10 +33,12 @@ def take_action(self, parsed_args): results = [] total = 0.0 - for url, start, end in self.app.timing_data: - seconds = end - start - total += seconds - results.append((url, seconds)) + for url, td in self.app.timing_data: + # NOTE(dtroyer): Take the long way here because total_seconds() + # was added in py27. + sec = (td.microseconds + (td.seconds + td.days*86400) * 1e6) / 1e6 + total += sec + results.append((url, sec)) results.append(('Total', total)) return ( column_headers, diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 3cfd731219..058824887c 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -316,11 +316,10 @@ def clean_up(self, cmd, result, err): # Process collected timing data if self.options.timing: - # Loop through extensions - for mod in self.ext_modules: - client = getattr(self.client_manager, mod.API_NAME) - if hasattr(client, 'get_timings'): - self.timing_data.extend(client.get_timings()) + # Get session data + self.timing_data.extend( + self.client_manager.session.get_timings(), + ) # Use the Timing pseudo-command to generate the output tcmd = timing.Timing(self, self.options) diff --git a/openstackclient/tests/common/test_timing.py b/openstackclient/tests/common/test_timing.py index aa910b91e5..a7f93b55ca 100644 --- a/openstackclient/tests/common/test_timing.py +++ b/openstackclient/tests/common/test_timing.py @@ -13,14 +13,15 @@ """Test Timing pseudo-command""" +import datetime + from openstackclient.common import timing from openstackclient.tests import fakes from openstackclient.tests import utils timing_url = 'GET http://localhost:5000' -timing_start = 1404802774.872809 -timing_end = 1404802775.724802 +timing_elapsed = 0.872809 class FakeGenericClient(object): @@ -66,9 +67,10 @@ def test_timing_list_no_data(self): self.assertEqual(datalist, data) def test_timing_list(self): - self.app.timing_data = [ - (timing_url, timing_start, timing_end), - ] + self.app.timing_data = [( + timing_url, + datetime.timedelta(microseconds=timing_elapsed*1000000), + )] arglist = [] verifylist = [] @@ -79,9 +81,8 @@ def test_timing_list(self): collist = ('URL', 'Seconds') self.assertEqual(collist, columns) - timing_sec = timing_end - timing_start datalist = [ - (timing_url, timing_sec), - ('Total', timing_sec) + (timing_url, timing_elapsed), + ('Total', timing_elapsed), ] self.assertEqual(datalist, data) From a0fe37e189948b74ee8c2f8ec529ca175efc8449 Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Mon, 13 Apr 2015 16:21:50 -0600 Subject: [PATCH 0013/3095] Add warning message if unknown version supplied Print a warning message if an unknown api version is supplied. An attempt will be made to run the command anyway. Change-Id: Idec8e88fe9621f10ec4b7eecd90708fb3730f56f --- openstackclient/compute/client.py | 3 +++ openstackclient/shell.py | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/openstackclient/compute/client.py b/openstackclient/compute/client.py index 7ca08a4f23..bdba4dca3e 100644 --- a/openstackclient/compute/client.py +++ b/openstackclient/compute/client.py @@ -30,6 +30,9 @@ DEFAULT_COMPUTE_API_VERSION = '2' API_VERSION_OPTION = 'os_compute_api_version' API_NAME = 'compute' +API_VERSIONS = { + "2": "novaclient.client", +} def make_client(instance): diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 3cfd731219..ebc80c2a30 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -247,6 +247,11 @@ def initialize_app(self, argv): if version_opt: api = mod.API_NAME self.api_version[api] = version_opt + if version_opt not in mod.API_VERSIONS: + self.log.warning( + "The %s version <%s> is not in supported versions <%s>" + % (api, version_opt, + ', '.join(mod.API_VERSIONS.keys()))) # Command groups deal only with major versions version = '.v' + version_opt.replace('.', '_').split('_')[0] cmd_group = 'openstack.' + api.replace('-', '_') + version From ba7ad20942061eface8a0f11f302147744b59418 Mon Sep 17 00:00:00 2001 From: Marek Denis Date: Thu, 19 Mar 2015 10:46:28 +0100 Subject: [PATCH 0014/3095] Federation Service Providers CRUD operations Adds CRUD support for service providers as it's now available through keystoneclient Closes-Bug: 1435962 Depends-On: If802e8a47e45ae00112de3739334b4b5482d0500 Change-Id: Ic55101e50209070aa49ca2adc91c89ba754c8c68 --- .../identity/v3/service_provider.py | 216 +++++++++ openstackclient/tests/identity/v3/fakes.py | 16 + .../identity/v3/test_service_provider.py | 412 ++++++++++++++++++ setup.cfg | 6 + 4 files changed, 650 insertions(+) create mode 100644 openstackclient/identity/v3/service_provider.py create mode 100644 openstackclient/tests/identity/v3/test_service_provider.py diff --git a/openstackclient/identity/v3/service_provider.py b/openstackclient/identity/v3/service_provider.py new file mode 100644 index 0000000000..31e96a83a3 --- /dev/null +++ b/openstackclient/identity/v3/service_provider.py @@ -0,0 +1,216 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +"""Service Provider action implementations""" + +import logging +import six +import sys + +from cliff import command +from cliff import lister +from cliff import show + +from openstackclient.common import utils + + +class CreateServiceProvider(show.ShowOne): + """Create new service provider""" + + log = logging.getLogger(__name__ + '.CreateServiceProvider') + + def get_parser(self, prog_name): + parser = super(CreateServiceProvider, self).get_parser(prog_name) + parser.add_argument( + 'service_provider_id', + metavar='', + help='New service provider ID (must be unique)' + ) + parser.add_argument( + '--auth-url', + metavar='', + required=True, + help='Authentication URL of remote federated service provider', + ) + parser.add_argument( + '--description', + metavar='', + help='New service provider description', + ) + parser.add_argument( + '--service-provider-url', + metavar='', + required=True, + help='A service URL where SAML assertions are being sent', + ) + + enable_service_provider = parser.add_mutually_exclusive_group() + enable_service_provider.add_argument( + '--enable', + dest='enabled', + action='store_true', + default=True, + help='Enable service provider (default)', + ) + enable_service_provider.add_argument( + '--disable', + dest='enabled', + action='store_false', + help='Disable service provider', + ) + + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + service_client = self.app.client_manager.identity + sp = service_client.federation.service_providers.create( + id=parsed_args.service_provider_id, + auth_url=parsed_args.auth_url, + description=parsed_args.description, + enabled=parsed_args.enabled, + sp_url=parsed_args.service_provider_url) + + sp._info.pop('links', None) + return zip(*sorted(six.iteritems(sp._info))) + + +class DeleteServiceProvider(command.Command): + """Delete service provider""" + + log = logging.getLogger(__name__ + '.DeleteServiceProvider') + + def get_parser(self, prog_name): + parser = super(DeleteServiceProvider, self).get_parser(prog_name) + parser.add_argument( + 'service_provider', + metavar='', + help='Service provider ID to delete (ID)', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + service_client = self.app.client_manager.identity + service_client.federation.service_providers.delete( + parsed_args.service_provider) + return + + +class ListServiceProvider(lister.Lister): + """List service providers""" + + log = logging.getLogger(__name__ + '.ListServiceProvider') + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + service_client = self.app.client_manager.identity + data = service_client.federation.service_providers.list() + + column_headers = ('ID', 'Enabled', 'Description', 'Auth URL') + return (column_headers, + (utils.get_item_properties( + s, column_headers, + formatters={}, + ) for s in data)) + + +class SetServiceProvider(command.Command): + """Set service provider properties""" + + log = logging.getLogger(__name__ + '.SetServiceProvider') + + def get_parser(self, prog_name): + parser = super(SetServiceProvider, self).get_parser(prog_name) + parser.add_argument( + 'service_provider', + metavar='', + help='Service provider ID to change (ID)', + ) + parser.add_argument( + '--auth-url', + metavar='', + help='Authentication URL of remote federated Service Provider', + ) + + parser.add_argument( + '--description', + metavar='', + help='New service provider description', + ) + parser.add_argument( + '--service-provider-url', + metavar='', + help='A service URL where SAML assertions are being sent', + ) + enable_service_provider = parser.add_mutually_exclusive_group() + enable_service_provider.add_argument( + '--enable', + action='store_true', + help='Enable service provider', + ) + enable_service_provider.add_argument( + '--disable', + action='store_true', + help='Disable service provider', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + federation_client = self.app.client_manager.identity.federation + + enabled = None + if parsed_args.enable is True: + enabled = True + elif parsed_args.disable is True: + enabled = False + + if not any((enabled is not None, parsed_args.description, + parsed_args.service_provider_url, + parsed_args.auth_url)): + sys.stdout.write("Service Provider not updated, no arguments " + "present") + return (None, None) + + service_provider = federation_client.service_providers.update( + parsed_args.service_provider, enabled=enabled, + description=parsed_args.description, + auth_url=parsed_args.auth_url, + sp_url=parsed_args.service_provider_url) + return zip(*sorted(six.iteritems(service_provider._info))) + + +class ShowServiceProvider(show.ShowOne): + """Display service provider details""" + + log = logging.getLogger(__name__ + '.ShowServiceProvider') + + def get_parser(self, prog_name): + parser = super(ShowServiceProvider, self).get_parser(prog_name) + parser.add_argument( + 'service_provider', + metavar='', + help='Service provider ID to display (ID)', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + service_client = self.app.client_manager.identity + service_provider = utils.find_resource( + service_client.federation.service_providers, + parsed_args.service_provider) + + service_provider._info.pop('links', None) + return zip(*sorted(six.iteritems(service_provider._info))) diff --git a/openstackclient/tests/identity/v3/fakes.py b/openstackclient/tests/identity/v3/fakes.py index c868401aa1..eb8673ef42 100644 --- a/openstackclient/tests/identity/v3/fakes.py +++ b/openstackclient/tests/identity/v3/fakes.py @@ -243,6 +243,20 @@ mapping_id = 'test_mapping' mapping_id_updated = 'prod_mapping' +sp_id = 'BETA' +sp_description = 'Service Provider to burst into' +service_provider_url = 'https://beta.example.com/Shibboleth.sso/POST/SAML' +sp_auth_url = ('https://beta.example.com/v3/OS-FEDERATION/identity_providers/' + 'idp/protocol/saml2/auth') + +SERVICE_PROVIDER = { + 'id': sp_id, + 'enabled': True, + 'description': sp_description, + 'sp_url': service_provider_url, + 'auth_url': sp_auth_url +} + PROTOCOL_ID_MAPPING = { 'id': protocol_id, 'mapping': mapping_id @@ -380,6 +394,8 @@ def __init__(self, **kwargs): self.projects.resource_class = fakes.FakeResource(None, {}) self.domains = mock.Mock() self.domains.resource_class = fakes.FakeResource(None, {}) + self.service_providers = mock.Mock() + self.service_providers.resource_class = fakes.FakeResource(None, {}) class FakeFederatedClient(FakeIdentityv3Client): diff --git a/openstackclient/tests/identity/v3/test_service_provider.py b/openstackclient/tests/identity/v3/test_service_provider.py new file mode 100644 index 0000000000..e77870d696 --- /dev/null +++ b/openstackclient/tests/identity/v3/test_service_provider.py @@ -0,0 +1,412 @@ +# Copyright 2014 CERN. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import copy + +from openstackclient.identity.v3 import service_provider +from openstackclient.tests import fakes +from openstackclient.tests.identity.v3 import fakes as service_fakes + + +class TestServiceProvider(service_fakes.TestFederatedIdentity): + + def setUp(self): + super(TestServiceProvider, self).setUp() + + federation_lib = self.app.client_manager.identity.federation + self.service_providers_mock = federation_lib.service_providers + self.service_providers_mock.reset_mock() + + +class TestServiceProviderCreate(TestServiceProvider): + + def setUp(self): + super(TestServiceProviderCreate, self).setUp() + + copied_sp = copy.deepcopy(service_fakes.SERVICE_PROVIDER) + resource = fakes.FakeResource(None, copied_sp, loaded=True) + self.service_providers_mock.create.return_value = resource + self.cmd = service_provider.CreateServiceProvider(self.app, None) + + def test_create_service_provider_required_options_only(self): + arglist = [ + '--auth-url', service_fakes.sp_auth_url, + '--service-provider-url', service_fakes.service_provider_url, + service_fakes.sp_id, + ] + verifylist = [ + ('auth_url', service_fakes.sp_auth_url), + ('service_provider_url', service_fakes.service_provider_url), + ('service_provider_id', service_fakes.sp_id), + + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'enabled': True, + 'description': None, + 'auth_url': service_fakes.sp_auth_url, + 'sp_url': service_fakes.service_provider_url + } + + self.service_providers_mock.create.assert_called_with( + id=service_fakes.sp_id, + **kwargs + ) + + collist = ('auth_url', 'description', 'enabled', 'id', 'sp_url') + self.assertEqual(collist, columns) + datalist = ( + service_fakes.sp_auth_url, + service_fakes.sp_description, + True, + service_fakes.sp_id, + service_fakes.service_provider_url + ) + self.assertEqual(data, datalist) + + def test_create_service_provider_description(self): + + arglist = [ + '--description', service_fakes.sp_description, + '--auth-url', service_fakes.sp_auth_url, + '--service-provider-url', service_fakes.service_provider_url, + service_fakes.sp_id, + ] + verifylist = [ + ('description', service_fakes.sp_description), + ('auth_url', service_fakes.sp_auth_url), + ('service_provider_url', service_fakes.service_provider_url), + ('service_provider_id', service_fakes.sp_id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'description': service_fakes.sp_description, + 'auth_url': service_fakes.sp_auth_url, + 'sp_url': service_fakes.service_provider_url, + 'enabled': True, + } + + self.service_providers_mock.create.assert_called_with( + id=service_fakes.sp_id, + **kwargs + ) + + collist = ('auth_url', 'description', 'enabled', 'id', 'sp_url') + self.assertEqual(columns, collist) + datalist = ( + service_fakes.sp_auth_url, + service_fakes.sp_description, + True, + service_fakes.sp_id, + service_fakes.service_provider_url + ) + self.assertEqual(datalist, data) + + def test_create_service_provider_disabled(self): + + # Prepare FakeResource object + service_provider = copy.deepcopy(service_fakes.SERVICE_PROVIDER) + service_provider['enabled'] = False + service_provider['description'] = None + + resource = fakes.FakeResource(None, service_provider, loaded=True) + self.service_providers_mock.create.return_value = resource + + arglist = [ + '--auth-url', service_fakes.sp_auth_url, + '--service-provider-url', service_fakes.service_provider_url, + '--disable', + service_fakes.sp_id, + ] + verifylist = [ + ('auth_url', service_fakes.sp_auth_url), + ('service_provider_url', service_fakes.service_provider_url), + ('service_provider_id', service_fakes.sp_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + # Set expected values + kwargs = { + 'auth_url': service_fakes.sp_auth_url, + 'sp_url': service_fakes.service_provider_url, + 'enabled': False, + 'description': None, + } + + self.service_providers_mock.create.assert_called_with( + id=service_fakes.sp_id, + **kwargs + ) + + collist = ('auth_url', 'description', 'enabled', 'id', 'sp_url') + self.assertEqual(collist, collist) + datalist = ( + service_fakes.sp_auth_url, + None, + False, + service_fakes.sp_id, + service_fakes.service_provider_url + ) + self.assertEqual(datalist, data) + + +class TestServiceProviderDelete(TestServiceProvider): + + def setUp(self): + super(TestServiceProviderDelete, self).setUp() + + # This is the return value for utils.find_resource() + self.service_providers_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(service_fakes.SERVICE_PROVIDER), + loaded=True, + ) + + self.service_providers_mock.delete.return_value = None + self.cmd = service_provider.DeleteServiceProvider(self.app, None) + + def test_delete_service_provider(self): + arglist = [ + service_fakes.sp_id, + ] + verifylist = [ + ('service_provider', service_fakes.sp_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.service_providers_mock.delete.assert_called_with( + service_fakes.sp_id, + ) + + +class TestServiceProviderList(TestServiceProvider): + + def setUp(self): + super(TestServiceProviderList, self).setUp() + + self.service_providers_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(service_fakes.SERVICE_PROVIDER), + loaded=True, + ) + self.service_providers_mock.list.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy(service_fakes.SERVICE_PROVIDER), + loaded=True, + ), + ] + + # Get the command object to test + self.cmd = service_provider.ListServiceProvider(self.app, None) + + def test_service_provider_list_no_options(self): + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + self.service_providers_mock.list.assert_called_with() + + collist = ('ID', 'Enabled', 'Description', 'Auth URL') + self.assertEqual(collist, columns) + datalist = (( + service_fakes.sp_id, + True, + service_fakes.sp_description, + service_fakes.sp_auth_url + ), ) + self.assertEqual(tuple(data), datalist) + + +class TestServiceProviderShow(TestServiceProvider): + + def setUp(self): + super(TestServiceProviderShow, self).setUp() + + ret = fakes.FakeResource( + None, + copy.deepcopy(service_fakes.SERVICE_PROVIDER), + loaded=True, + ) + self.service_providers_mock.get.return_value = ret + # Get the command object to test + self.cmd = service_provider.ShowServiceProvider(self.app, None) + + def test_service_provider_show(self): + arglist = [ + service_fakes.sp_id, + ] + verifylist = [ + ('service_provider', service_fakes.sp_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.service_providers_mock.get.assert_called_with( + service_fakes.sp_id, + ) + + collist = ('auth_url', 'description', 'enabled', 'id', 'sp_url') + self.assertEqual(collist, columns) + datalist = ( + service_fakes.sp_auth_url, + service_fakes.sp_description, + True, + service_fakes.sp_id, + service_fakes.service_provider_url + ) + self.assertEqual(data, datalist) + + +class TestServiceProviderSet(TestServiceProvider): + + def setUp(self): + super(TestServiceProviderSet, self).setUp() + self.cmd = service_provider.SetServiceProvider(self.app, None) + + def test_service_provider_disable(self): + """Disable Service Provider + + Set Service Provider's ``enabled`` attribute to False. + """ + def prepare(self): + """Prepare fake return objects before the test is executed""" + updated_sp = copy.deepcopy(service_fakes.SERVICE_PROVIDER) + updated_sp['enabled'] = False + resources = fakes.FakeResource( + None, + updated_sp, + loaded=True + ) + self.service_providers_mock.update.return_value = resources + + prepare(self) + arglist = [ + '--disable', service_fakes.sp_id, + ] + verifylist = [ + ('service_provider', service_fakes.sp_id), + ('enable', False), + ('disable', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.service_providers_mock.update.assert_called_with( + service_fakes.sp_id, + enabled=False, + description=None, + auth_url=None, + sp_url=None + ) + + collist = ('auth_url', 'description', 'enabled', 'id', 'sp_url') + self.assertEqual(collist, columns) + datalist = ( + service_fakes.sp_auth_url, + service_fakes.sp_description, + False, + service_fakes.sp_id, + service_fakes.service_provider_url + ) + self.assertEqual(datalist, data) + + def test_service_provider_enable(self): + """Enable Service Provider. + + Set Service Provider's ``enabled`` attribute to True. + """ + def prepare(self): + """Prepare fake return objects before the test is executed""" + resources = fakes.FakeResource( + None, + copy.deepcopy(service_fakes.SERVICE_PROVIDER), + loaded=True + ) + self.service_providers_mock.update.return_value = resources + + prepare(self) + arglist = [ + '--enable', service_fakes.sp_id, + ] + verifylist = [ + ('service_provider', service_fakes.sp_id), + ('enable', True), + ('disable', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.service_providers_mock.update.assert_called_with( + service_fakes.sp_id, enabled=True, description=None, + auth_url=None, sp_url=None) + collist = ('auth_url', 'description', 'enabled', 'id', 'sp_url') + self.assertEqual(collist, columns) + datalist = ( + service_fakes.sp_auth_url, + service_fakes.sp_description, + True, + service_fakes.sp_id, + service_fakes.service_provider_url + ) + self.assertEqual(data, datalist) + + def test_service_provider_no_options(self): + def prepare(self): + """Prepare fake return objects before the test is executed""" + resources = fakes.FakeResource( + None, + copy.deepcopy(service_fakes.SERVICE_PROVIDER), + loaded=True + ) + self.service_providers_mock.get.return_value = resources + + resources = fakes.FakeResource( + None, + copy.deepcopy(service_fakes.SERVICE_PROVIDER), + loaded=True, + ) + self.service_providers_mock.update.return_value = resources + + prepare(self) + arglist = [ + service_fakes.sp_id, + ] + verifylist = [ + ('service_provider', service_fakes.sp_id), + ('description', None), + ('enable', False), + ('disable', False), + ('auth_url', None), + ('service_provider_url', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + # expect take_action() to return (None, None) as none of --disabled, + # --enabled, --description, --service-provider-url, --auth_url option + # was set. + self.assertEqual(columns, None) + self.assertEqual(data, None) diff --git a/setup.cfg b/setup.cfg index f2c1a05754..e2d7288488 100644 --- a/setup.cfg +++ b/setup.cfg @@ -280,6 +280,12 @@ openstack.identity.v3 = service_show = openstackclient.identity.v3.service:ShowService service_set = openstackclient.identity.v3.service:SetService + service_provider_create = openstackclient.identity.v3.service_provider:CreateServiceProvider + service_provider_delete = openstackclient.identity.v3.service_provider:DeleteServiceProvider + service_provider_list = openstackclient.identity.v3.service_provider:ListServiceProvider + service_provider_set = openstackclient.identity.v3.service_provider:SetServiceProvider + service_provider_show = openstackclient.identity.v3.service_provider:ShowServiceProvider + token_issue = openstackclient.identity.v3.token:IssueToken trust_create = openstackclient.identity.v3.trust:CreateTrust From caf91e69bace8f1f8201d6719b338ce3d78f42e4 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Sat, 28 Mar 2015 03:39:07 -0400 Subject: [PATCH 0015/3095] Add docs for service provider CRUD This patch adds service providers to command-objects, and makes a few changes to the help text, to align it more with the already established identity provider resource. Change-Id: Ibf3d2bc04bf5588d1fc9c37b8ca28c007496c021 --- .../command-objects/service-provider.rst | 125 ++++++++++++++++++ doc/source/commands.rst | 1 + .../identity/v3/service_provider.py | 34 ++--- 3 files changed, 144 insertions(+), 16 deletions(-) create mode 100644 doc/source/command-objects/service-provider.rst diff --git a/doc/source/command-objects/service-provider.rst b/doc/source/command-objects/service-provider.rst new file mode 100644 index 0000000000..963493b4a2 --- /dev/null +++ b/doc/source/command-objects/service-provider.rst @@ -0,0 +1,125 @@ +================ +service provider +================ + +Identity v3 + +`Requires: OS-FEDERATION extension` + +service provider create +----------------------- + +Create new service provider + +.. program:: service provider create +.. code:: bash + + os service provider create + [--description ] + [--enable | --disable] + --auth-url + --service-provider-url + + +.. option:: --auth-url + + Authentication URL of remote federated service provider (required) + +.. option:: --service-provider-url + + A service URL where SAML assertions are being sent (required) + +.. option:: --description + + New service provider description + +.. option:: --enable + + Enable the service provider (default) + +.. option:: --disable + + Disable the service provider + +.. describe:: + + New service provider name (must be unique) + +service provider delete +----------------------- + +Delete service provider + +.. program:: service provider delete +.. code:: bash + + os service provider delete + + +.. describe:: + + Service provider to delete + +service provider list +--------------------- + +List service providers + +.. program:: service provider list +.. code:: bash + + os service provider list + +service provider set +-------------------- + +Set service provider properties + +.. program:: service provider set +.. code:: bash + + os service provider set + [--enable | --disable] + [--description ] + [--auth-url ] + [--service-provider-url ] + + +.. option:: --service-provider-url + + New service provider URL, where SAML assertions are sent + +.. option:: --auth-url + + New Authentication URL of remote federated service provider + +.. option:: --description + + New service provider description + +.. option:: --enable + + Enable the service provider + +.. option:: --disable + + Disable the service provider + +.. describe:: + + Service provider to modify + +service provider show +--------------------- + +Display service provider details + +.. program:: service provider show +.. code:: bash + + os service provider show + + +.. describe:: + + Service provider to display diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 42d041afbd..b52d94590a 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -111,6 +111,7 @@ referring to both Compute and Volume quotas. * ``server``: (**Compute**) virtual machine instance * ``server image``: (**Compute**) saved server disk image * ``service``: (**Identity**) a cloud service +* ``service provider``: (**Identity**) a resource that consumes assertions from an ``identity provider`` * ``snapshot``: (**Volume**) a point-in-time copy of a volume * ``token``: (**Identity**) a bearer token managed by Identity service * ``usage``: (**Compute**) display host resources being consumed diff --git a/openstackclient/identity/v3/service_provider.py b/openstackclient/identity/v3/service_provider.py index 31e96a83a3..78f96b6c8e 100644 --- a/openstackclient/identity/v3/service_provider.py +++ b/openstackclient/identity/v3/service_provider.py @@ -33,14 +33,15 @@ def get_parser(self, prog_name): parser = super(CreateServiceProvider, self).get_parser(prog_name) parser.add_argument( 'service_provider_id', - metavar='', - help='New service provider ID (must be unique)' + metavar='', + help='New service provider name (must be unique)' ) parser.add_argument( '--auth-url', metavar='', required=True, - help='Authentication URL of remote federated service provider', + help='Authentication URL of remote federated service provider ' + '(required)', ) parser.add_argument( '--description', @@ -51,7 +52,8 @@ def get_parser(self, prog_name): '--service-provider-url', metavar='', required=True, - help='A service URL where SAML assertions are being sent', + help='A service URL where SAML assertions are being sent ' + '(required)', ) enable_service_provider = parser.add_mutually_exclusive_group() @@ -60,13 +62,13 @@ def get_parser(self, prog_name): dest='enabled', action='store_true', default=True, - help='Enable service provider (default)', + help='Enable the service provider (default)', ) enable_service_provider.add_argument( '--disable', dest='enabled', action='store_false', - help='Disable service provider', + help='Disable the service provider', ) return parser @@ -94,8 +96,8 @@ def get_parser(self, prog_name): parser = super(DeleteServiceProvider, self).get_parser(prog_name) parser.add_argument( 'service_provider', - metavar='', - help='Service provider ID to delete (ID)', + metavar='', + help='Service provider to delete', ) return parser @@ -134,13 +136,13 @@ def get_parser(self, prog_name): parser = super(SetServiceProvider, self).get_parser(prog_name) parser.add_argument( 'service_provider', - metavar='', - help='Service provider ID to change (ID)', + metavar='', + help='Service provider to modify', ) parser.add_argument( '--auth-url', metavar='', - help='Authentication URL of remote federated Service Provider', + help='New Authentication URL of remote federated service provider', ) parser.add_argument( @@ -151,18 +153,18 @@ def get_parser(self, prog_name): parser.add_argument( '--service-provider-url', metavar='', - help='A service URL where SAML assertions are being sent', + help='New service provider URL, where SAML assertions are sent', ) enable_service_provider = parser.add_mutually_exclusive_group() enable_service_provider.add_argument( '--enable', action='store_true', - help='Enable service provider', + help='Enable the service provider', ) enable_service_provider.add_argument( '--disable', action='store_true', - help='Disable service provider', + help='Disable the service provider', ) return parser @@ -200,8 +202,8 @@ def get_parser(self, prog_name): parser = super(ShowServiceProvider, self).get_parser(prog_name) parser.add_argument( 'service_provider', - metavar='', - help='Service provider ID to display (ID)', + metavar='', + help='Service provider to display', ) return parser From 8bd8a8dfd7d330573740f7610ea28ae291d1e4e2 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Fri, 3 Apr 2015 03:12:27 -0400 Subject: [PATCH 0016/3095] Add support to specify volume quotas per volume type Add a --volume-type option to quota set, this will allow users to set quotas for volume attributes on a per volume-type basis. for example: openstack quota set admin --volume-type myvol --volumes 12 Change-Id: I3ce9cf82a65d4f012b339f0e0dedb752cb132c33 Closes-Bug: 1438377 --- doc/source/command-objects/quota.rst | 5 +++++ openstackclient/common/quota.py | 12 ++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/doc/source/command-objects/quota.rst b/doc/source/command-objects/quota.rst index 053fb47acc..5ea49f8c52 100644 --- a/doc/source/command-objects/quota.rst +++ b/doc/source/command-objects/quota.rst @@ -30,6 +30,7 @@ Set quotas for project [--gigabytes ] [--snapshots ] [--volumes ] + [--volume-type ] @@ -121,6 +122,10 @@ Set quotas for class New value for the snapshots quota +.. option:: --volume-type + + Set quotas for a specific + quota show ---------- diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index dde4a9acb6..ea1dc38f58 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -82,6 +82,11 @@ def get_parser(self, prog_name): type=int, help='New value for the %s quota' % v, ) + parser.add_argument( + '--volume-type', + metavar='', + help='Set quotas for a specific ', + ) return parser def take_action(self, parsed_args): @@ -97,8 +102,11 @@ def take_action(self, parsed_args): volume_kwargs = {} for k, v in VOLUME_QUOTAS.items(): - if v in parsed_args: - volume_kwargs[k] = getattr(parsed_args, v, None) + value = getattr(parsed_args, v, None) + if value is not None: + if parsed_args.volume_type: + k = k + '_%s' % parsed_args.volume_type + volume_kwargs[k] = value if compute_kwargs == {} and volume_kwargs == {}: sys.stderr.write("No quotas updated") From 459526e25dc27de309abfbd8e584c7c756dd1245 Mon Sep 17 00:00:00 2001 From: Davanum Srinivas Date: Wed, 15 Apr 2015 16:32:10 -0400 Subject: [PATCH 0017/3095] Better help for --nic in create server Use the help string from python-novaclient for a better user experience. Closes-Bug: #1444685 Change-Id: If7b8e3f68a0c6ad82b9959f162670b5568d5d12d --- openstackclient/compute/v2/server.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 49ef18b2e3..e672819bb5 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -275,10 +275,17 @@ def get_parser(self, prog_name): ) parser.add_argument( '--nic', - metavar='', + metavar="", action='append', default=[], - help=_('Specify NIC configuration (optional extension)'), + help=_("Create a NIC on the server. " + "Specify option multiple times to create multiple NICs. " + "Either net-id or port-id must be provided, but not both. " + "net-id: attach NIC to network with this UUID, " + "port-id: attach NIC to port with this UUID, " + "v4-fixed-ip: IPv4 fixed address for NIC (optional), " + "v6-fixed-ip: IPv6 fixed address for NIC (optional), "), ) parser.add_argument( '--hint', From f43c1f76559ae8b5b738b7ae8b69b15c379f9145 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Mon, 13 Apr 2015 16:47:49 -0500 Subject: [PATCH 0018/3095] Defer client imports So we really weren't deferring the loading of client libs dadgummit, do that for real where possible. This shaves a couple of tenths off the static import times. Also defer as much import-time procesing as possible. This is a little ugly in api.auth but this also eliminates import of the auth plugins until they are needed. Change-Id: Ia11d4b9cf98231d37449103fc29101dc17afb009 --- openstackclient/api/auth.py | 64 ++++++++++++------- openstackclient/compute/client.py | 17 ++--- .../tests/common/test_clientmanager.py | 4 ++ .../tests/volume/test_find_resource.py | 7 ++ openstackclient/volume/client.py | 20 +++--- 5 files changed, 73 insertions(+), 39 deletions(-) diff --git a/openstackclient/api/auth.py b/openstackclient/api/auth.py index ba51bee1ff..ffbcb6fce0 100644 --- a/openstackclient/api/auth.py +++ b/openstackclient/api/auth.py @@ -27,29 +27,49 @@ LOG = logging.getLogger(__name__) - # Initialize the list of Authentication plugins early in order # to get the command-line options -PLUGIN_LIST = stevedore.ExtensionManager( - base.PLUGIN_NAMESPACE, - invoke_on_load=False, - propagate_map_exceptions=True, -) +PLUGIN_LIST = None -# Get the command line options so the help action has them available +# List of plugin command line options OPTIONS_LIST = {} -for plugin in PLUGIN_LIST: - for o in plugin.plugin.get_options(): - os_name = o.dest.lower().replace('_', '-') - os_env_name = 'OS_' + os_name.upper().replace('-', '_') - OPTIONS_LIST.setdefault(os_name, {'env': os_env_name, 'help': ''}) - # TODO(mhu) simplistic approach, would be better to only add - # help texts if they vary from one auth plugin to another - # also the text rendering is ugly in the CLI ... - OPTIONS_LIST[os_name]['help'] += 'With %s: %s\n' % ( - plugin.name, - o.help, + + +def get_plugin_list(): + """Gather plugin list and cache it""" + + global PLUGIN_LIST + + if PLUGIN_LIST is None: + PLUGIN_LIST = stevedore.ExtensionManager( + base.PLUGIN_NAMESPACE, + invoke_on_load=False, + propagate_map_exceptions=True, ) + return PLUGIN_LIST + + +def get_options_list(): + """Gather plugin options so the help action has them available""" + + global OPTIONS_LIST + + if not OPTIONS_LIST: + for plugin in get_plugin_list(): + for o in plugin.plugin.get_options(): + os_name = o.dest.lower().replace('_', '-') + os_env_name = 'OS_' + os_name.upper().replace('-', '_') + OPTIONS_LIST.setdefault( + os_name, {'env': os_env_name, 'help': ''}, + ) + # TODO(mhu) simplistic approach, would be better to only add + # help texts if they vary from one auth plugin to another + # also the text rendering is ugly in the CLI ... + OPTIONS_LIST[os_name]['help'] += 'With %s: %s\n' % ( + plugin.name, + o.help, + ) + return OPTIONS_LIST def select_auth_plugin(options): @@ -57,7 +77,7 @@ def select_auth_plugin(options): auth_plugin_name = None - if options.os_auth_type in [plugin.name for plugin in PLUGIN_LIST]: + if options.os_auth_type in [plugin.name for plugin in get_plugin_list()]: # A direct plugin name was given, use it return options.os_auth_type @@ -113,7 +133,7 @@ def build_auth_params(auth_plugin_name, cmd_options): else: LOG.debug('no auth_type') # delay the plugin choice, grab every option - plugin_options = set([o.replace('-', '_') for o in OPTIONS_LIST]) + plugin_options = set([o.replace('-', '_') for o in get_options_list()]) for option in plugin_options: option_name = 'os_' + option LOG.debug('fetching option %s' % option_name) @@ -147,7 +167,7 @@ def build_auth_plugins_option_parser(parser): authentication plugin. """ - available_plugins = [plugin.name for plugin in PLUGIN_LIST] + available_plugins = [plugin.name for plugin in get_plugin_list()] parser.add_argument( '--os-auth-type', metavar='', @@ -169,7 +189,7 @@ def build_auth_plugins_option_parser(parser): default=utils.env('OS_TENANT_ID') ), } - for o in OPTIONS_LIST: + for o in get_options_list(): # remove allusion to tenants from v2.0 API if 'tenant' not in o: parser.add_argument( diff --git a/openstackclient/compute/client.py b/openstackclient/compute/client.py index 7ca08a4f23..461c39267d 100644 --- a/openstackclient/compute/client.py +++ b/openstackclient/compute/client.py @@ -15,14 +15,6 @@ import logging -from novaclient import client as nova_client -from novaclient import extension - -try: - from novaclient.v2.contrib import list_extensions -except ImportError: - from novaclient.v1_1.contrib import list_extensions - from openstackclient.common import utils LOG = logging.getLogger(__name__) @@ -34,6 +26,15 @@ def make_client(instance): """Returns a compute service client.""" + + # Defer client imports until we actually need them + from novaclient import client as nova_client + from novaclient import extension + try: + from novaclient.v2.contrib import list_extensions + except ImportError: + from novaclient.v1_1.contrib import list_extensions + compute_client = nova_client.get_client_class( instance._api_version[API_NAME], ) diff --git a/openstackclient/tests/common/test_clientmanager.py b/openstackclient/tests/common/test_clientmanager.py index 3648bf5756..7a2f57bef6 100644 --- a/openstackclient/tests/common/test_clientmanager.py +++ b/openstackclient/tests/common/test_clientmanager.py @@ -34,6 +34,10 @@ SERVICE_CATALOG = service_catalog.ServiceCatalogV2(AUTH_REF) +# This is deferred in api.auth but we need it here... +auth.get_options_list() + + class Container(object): attr = clientmanager.ClientCache(lambda x: object()) diff --git a/openstackclient/tests/volume/test_find_resource.py b/openstackclient/tests/volume/test_find_resource.py index 56081966b7..00cc46a6ed 100644 --- a/openstackclient/tests/volume/test_find_resource.py +++ b/openstackclient/tests/volume/test_find_resource.py @@ -24,6 +24,13 @@ from openstackclient.volume import client # noqa +# Monkey patch for v1 cinderclient +# NOTE(dtroyer): Do here because openstackclient.volume.client +# doesn't do it until the client object is created now. +volumes.Volume.NAME_ATTR = 'display_name' +volume_snapshots.Snapshot.NAME_ATTR = 'display_name' + + ID = '1after909' NAME = 'PhilSpector' diff --git a/openstackclient/volume/client.py b/openstackclient/volume/client.py index 21072aeb01..a7b64def68 100644 --- a/openstackclient/volume/client.py +++ b/openstackclient/volume/client.py @@ -15,17 +15,8 @@ import logging -from cinderclient import extension -from cinderclient.v1.contrib import list_extensions -from cinderclient.v1 import volume_snapshots -from cinderclient.v1 import volumes - from openstackclient.common import utils -# Monkey patch for v1 cinderclient -volumes.Volume.NAME_ATTR = 'display_name' -volume_snapshots.Snapshot.NAME_ATTR = 'display_name' - LOG = logging.getLogger(__name__) DEFAULT_VOLUME_API_VERSION = '1' @@ -38,6 +29,17 @@ def make_client(instance): """Returns a volume service client.""" + + # Defer client imports until we actually need them + from cinderclient import extension + from cinderclient.v1.contrib import list_extensions + from cinderclient.v1 import volume_snapshots + from cinderclient.v1 import volumes + + # Monkey patch for v1 cinderclient + volumes.Volume.NAME_ATTR = 'display_name' + volume_snapshots.Snapshot.NAME_ATTR = 'display_name' + volume_client = utils.get_client_class( API_NAME, instance._api_version[API_NAME], From 57806064c090f9c5b3ea21d2cdec7193e1c93f62 Mon Sep 17 00:00:00 2001 From: Doug Hellmann Date: Thu, 16 Apr 2015 18:13:36 +0000 Subject: [PATCH 0019/3095] Uncap library requirements for liberty Change-Id: Ia2b0c00c5b1da19f2f6995aae3c2e0226a3fa2dc Depends-On: Ib948b756b8e6ca47a4c9c44c48031e54b7386a06 --- requirements.txt | 12 ++++++------ test-requirements.txt | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/requirements.txt b/requirements.txt index 2e18b0b85f..53d409eb27 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,16 +5,16 @@ pbr>=0.6,!=0.7,<1.0 six>=1.9.0 Babel>=1.3 -cliff>=1.10.0,<1.11.0 # Apache-2.0 +cliff>=1.10.0 # Apache-2.0 cliff-tablib>=1.0 -oslo.config>=1.9.3,<1.10.0 # Apache-2.0 -oslo.i18n>=1.5.0,<1.6.0 # Apache-2.0 -oslo.utils>=1.4.0,<1.5.0 # Apache-2.0 -oslo.serialization>=1.4.0,<1.5.0 # Apache-2.0 +oslo.config>=1.9.3 # Apache-2.0 +oslo.i18n>=1.5.0 # Apache-2.0 +oslo.utils>=1.4.0 # Apache-2.0 +oslo.serialization>=1.4.0 # Apache-2.0 python-glanceclient>=0.15.0 python-keystoneclient>=1.1.0 python-novaclient>=2.22.0 python-cinderclient>=1.1.0 python-neutronclient>=2.3.11,<3 requests>=2.2.0,!=2.4.0 -stevedore>=1.3.0,<1.4.0 # Apache-2.0 +stevedore>=1.3.0 # Apache-2.0 diff --git a/test-requirements.txt b/test-requirements.txt index cbbac96bb2..24f5c68ee9 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -7,8 +7,8 @@ coverage>=3.6 discover fixtures>=0.3.14 mock>=1.0 -oslosphinx>=2.5.0,<2.6.0 # Apache-2.0 -oslotest>=1.5.1,<1.6.0 # Apache-2.0 +oslosphinx>=2.5.0 # Apache-2.0 +oslotest>=1.5.1 # Apache-2.0 requests-mock>=0.6.0 # Apache-2.0 sphinx>=1.1.2,!=1.2.0,!=1.3b1,<1.3 testrepository>=0.0.18 From d363068617ea37cf17583c390df69f3bbe7ff45c Mon Sep 17 00:00:00 2001 From: Marek Aufart Date: Fri, 17 Apr 2015 14:20:37 +0200 Subject: [PATCH 0020/3095] Fix skipped image create attribute location attr Image create action accepts attribute called location, in method body is list of allowed attributes, which contain localtion, what is typo and this attribute was not passed to glance. Fixed. Change-Id: I357b06b63b8aa97f7a5f587ef3fcee7a4b360ed1 Closes-Bug: #1445460 --- openstackclient/image/v1/image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index 127a7735ec..a60afaf44e 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -190,7 +190,7 @@ def take_action(self, parsed_args): kwargs = {} copy_attrs = ('name', 'id', 'store', 'container_format', 'disk_format', 'owner', 'size', 'min_disk', 'min_ram', - 'localtion', 'copy_from', 'volume', 'force', + 'location', 'copy_from', 'volume', 'force', 'checksum', 'properties') for attr in copy_attrs: if attr in parsed_args: From 15bc2ccec9ca9df940a548da3cd810a107f45b39 Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Fri, 17 Apr 2015 07:54:05 -0600 Subject: [PATCH 0021/3095] Print warning on authentication error At least print a warning on authentication error. I have no idea why an exception is being ignored here because if there is no session, nothing is going to happen. This at least will print some useful warning: (.venv)terry@f350:~/python-openstackclient$ os flavor list --os-cloud pro WARNING: openstackclient.shell Possible error authenticating: __init__() got an unexpected keyword argument 'asdf' ERROR: openstack Authentication requires 'auth_url', which should be specified in 'HTTPClient' This error was caused by having a bogus value 'asdf' in the cloud.yaml for the cloud pro. Change-Id: Ie08432e0464cfa86b3b3f67ca29d3b7d23d2f46f --- openstackclient/shell.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index c118fbd290..be13d3580b 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -280,7 +280,8 @@ def prepare_to_run_command(self, cmd): try: # Trigger the Identity client to initialize self.client_manager.auth_ref - except Exception: + except Exception as e: + self.log.warning("Possible error authenticating: " + str(e)) pass return From 4c107e6f1b1913988e208b31206c84ab851b780c Mon Sep 17 00:00:00 2001 From: Nathan Kinder Date: Thu, 16 Apr 2015 19:12:45 -0700 Subject: [PATCH 0022/3095] Role operations should not require list object permission When using Keystone's policy.v3cloudsample.json policy file, a project admin is supposed to be able to manage role assignments. Unfortunately, a project admin isn't allowed to perform these operations using python-openstackclient, as we attempt to perform list operations for any of the object types specified (users, groups, projects). This is done in an attempt to lookup the id of the object by name, but we perform this list operation even when the user specifies everything by id. This causes 403 errors. This patch still attempts to look up the object id by name, but we catch the 403 and assume that the user specified an id if the list operation is not allowed. This is similar to what we do with the --domain option for other commands. Closes-bug: #1445528 Change-Id: Id95a8520e935c1092d5a22ecd8ea01f572334ac8 --- openstackclient/identity/common.py | 59 +++++++++++++- openstackclient/identity/v3/role.py | 81 ++++++++++--------- .../identity/v3/role_assignment.py | 17 ++-- 3 files changed, 108 insertions(+), 49 deletions(-) diff --git a/openstackclient/identity/common.py b/openstackclient/identity/common.py index 253729bddf..a1b46cb49f 100644 --- a/openstackclient/identity/common.py +++ b/openstackclient/identity/common.py @@ -17,6 +17,9 @@ from keystoneclient import exceptions as identity_exc from keystoneclient.v3 import domains +from keystoneclient.v3 import groups +from keystoneclient.v3 import projects +from keystoneclient.v3 import users from openstackclient.common import exceptions from openstackclient.common import utils @@ -56,4 +59,58 @@ def find_domain(identity_client, name_or_id): return dom except identity_exc.Forbidden: pass - return domains.Domain(None, {'id': name_or_id}) + return domains.Domain(None, {'id': name_or_id, 'name': name_or_id}) + + +def find_group(identity_client, name_or_id): + """Find a group. + + If the user does not have permissions to to perform a list groups call, + e.g., if the user is a project admin, assume that the group given is the + id rather than the name. This method is used by the role add command to + allow a role to be assigned to a group by a project admin who does not + have permission to list groups. + """ + try: + group = utils.find_resource(identity_client.groups, name_or_id) + if group is not None: + return group + except identity_exc.Forbidden: + pass + return groups.Group(None, {'id': name_or_id, 'name': name_or_id}) + + +def find_project(identity_client, name_or_id): + """Find a project. + + If the user does not have permissions to to perform a list projects + call, e.g., if the user is a project admin, assume that the project + given is the id rather than the name. This method is used by the role + add command to allow a role to be assigned to a user by a project admin + who does not have permission to list projects. + """ + try: + project = utils.find_resource(identity_client.projects, name_or_id) + if project is not None: + return project + except identity_exc.Forbidden: + pass + return projects.Project(None, {'id': name_or_id, 'name': name_or_id}) + + +def find_user(identity_client, name_or_id): + """Find a user. + + If the user does not have permissions to to perform a list users call, + e.g., if the user is a project admin, assume that the user given is the + id rather than the name. This method is used by the role add command to + allow a role to be assigned to a user by a project admin who does not + have permission to list users. + """ + try: + user = utils.find_resource(identity_client.users, name_or_id) + if user is not None: + return user + except identity_exc.Forbidden: + pass + return users.User(None, {'id': name_or_id, 'name': name_or_id}) diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index 0376070907..3dd998ba77 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -26,6 +26,7 @@ from openstackclient.common import utils from openstackclient.i18n import _ # noqa +from openstackclient.identity import common class AddRole(command.Command): @@ -78,12 +79,12 @@ def take_action(self, parsed_args): ) if parsed_args.user and parsed_args.domain: - user = utils.find_resource( - identity_client.users, + user = common.find_user( + identity_client, parsed_args.user, ) - domain = utils.find_resource( - identity_client.domains, + domain = common.find_domain( + identity_client, parsed_args.domain, ) identity_client.roles.grant( @@ -92,12 +93,12 @@ def take_action(self, parsed_args): domain=domain.id, ) elif parsed_args.user and parsed_args.project: - user = utils.find_resource( - identity_client.users, + user = common.find_user( + identity_client, parsed_args.user, ) - project = utils.find_resource( - identity_client.projects, + project = common.find_project( + identity_client, parsed_args.project, ) identity_client.roles.grant( @@ -106,12 +107,12 @@ def take_action(self, parsed_args): project=project.id, ) elif parsed_args.group and parsed_args.domain: - group = utils.find_resource( - identity_client.groups, + group = common.find_group( + identity_client, parsed_args.group, ) - domain = utils.find_resource( - identity_client.domains, + domain = common.find_domain( + identity_client, parsed_args.domain, ) identity_client.roles.grant( @@ -120,12 +121,12 @@ def take_action(self, parsed_args): domain=domain.id, ) elif parsed_args.group and parsed_args.project: - group = utils.find_resource( - identity_client.groups, + group = common.find_group( + identity_client, parsed_args.group, ) - project = utils.find_resource( - identity_client.projects, + project = common.find_project( + identity_client, parsed_args.project, ) identity_client.roles.grant( @@ -240,24 +241,24 @@ def take_action(self, parsed_args): identity_client = self.app.client_manager.identity if parsed_args.user: - user = utils.find_resource( - identity_client.users, + user = common.find_user( + identity_client, parsed_args.user, ) elif parsed_args.group: - group = utils.find_resource( - identity_client.groups, + group = common.find_group( + identity_client, parsed_args.group, ) if parsed_args.domain: - domain = utils.find_resource( - identity_client.domains, + domain = common.find_domain( + identity_client, parsed_args.domain, ) elif parsed_args.project: - project = utils.find_resource( - identity_client.projects, + project = common.find_project( + identity_client, parsed_args.project, ) @@ -370,12 +371,12 @@ def take_action(self, parsed_args): ) if parsed_args.user and parsed_args.domain: - user = utils.find_resource( - identity_client.users, + user = common.find_user( + identity_client, parsed_args.user, ) - domain = utils.find_resource( - identity_client.domains, + domain = common.find_domain( + identity_client, parsed_args.domain, ) identity_client.roles.revoke( @@ -384,12 +385,12 @@ def take_action(self, parsed_args): domain=domain.id, ) elif parsed_args.user and parsed_args.project: - user = utils.find_resource( - identity_client.users, + user = common.find_user( + identity_client, parsed_args.user, ) - project = utils.find_resource( - identity_client.projects, + project = common.find_project( + identity_client, parsed_args.project, ) identity_client.roles.revoke( @@ -398,12 +399,12 @@ def take_action(self, parsed_args): project=project.id, ) elif parsed_args.group and parsed_args.domain: - group = utils.find_resource( - identity_client.groups, + group = common.find_group( + identity_client, parsed_args.group, ) - domain = utils.find_resource( - identity_client.domains, + domain = common.find_domain( + identity_client, parsed_args.domain, ) identity_client.roles.revoke( @@ -412,12 +413,12 @@ def take_action(self, parsed_args): domain=domain.id, ) elif parsed_args.group and parsed_args.project: - group = utils.find_resource( - identity_client.groups, + group = common.find_group( + identity_client, parsed_args.group, ) - project = utils.find_resource( - identity_client.projects, + project = common.find_project( + identity_client, parsed_args.project, ) identity_client.roles.revoke( diff --git a/openstackclient/identity/v3/role_assignment.py b/openstackclient/identity/v3/role_assignment.py index f053b608c5..24e3a7f709 100644 --- a/openstackclient/identity/v3/role_assignment.py +++ b/openstackclient/identity/v3/role_assignment.py @@ -18,6 +18,7 @@ from cliff import lister from openstackclient.common import utils +from openstackclient.identity import common class ListRoleAssignment(lister.Lister): @@ -80,29 +81,29 @@ def take_action(self, parsed_args): user = None if parsed_args.user: - user = utils.find_resource( - identity_client.users, + user = common.find_user( + identity_client, parsed_args.user, ) domain = None if parsed_args.domain: - domain = utils.find_resource( - identity_client.domains, + domain = common.find_domain( + identity_client, parsed_args.domain, ) project = None if parsed_args.project: - project = utils.find_resource( - identity_client.projects, + project = common.find_project( + identity_client, parsed_args.project, ) group = None if parsed_args.group: - group = utils.find_resource( - identity_client.groups, + group = common.find_group( + identity_client, parsed_args.group, ) From 6c4f81516c6ad119d39c4952f3ab274f3d3f2efa Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Fri, 17 Apr 2015 13:37:44 -0400 Subject: [PATCH 0023/3095] Re-organize functional tests The tests should be further divded by project version, similar to the structure of the unit tests. Change-Id: Ied3a4204983cdd253c5602a60968c066038d88f2 --- functional/tests/examples/__init__.py | 0 .../tests/{ => examples}/test_examples.py | 0 functional/tests/identity/__init__.py | 0 functional/tests/identity/v2/__init__.py | 0 .../tests/{ => identity/v2}/test_identity.py | 79 --------------- functional/tests/identity/v3/__init__.py | 0 functional/tests/identity/v3/test_identity.py | 95 +++++++++++++++++++ functional/tests/object/__init__.py | 0 functional/tests/object/v1/__init__.py | 0 .../tests/{ => object/v1}/test_object.py | 0 10 files changed, 95 insertions(+), 79 deletions(-) create mode 100644 functional/tests/examples/__init__.py rename functional/tests/{ => examples}/test_examples.py (100%) create mode 100644 functional/tests/identity/__init__.py create mode 100644 functional/tests/identity/v2/__init__.py rename functional/tests/{ => identity/v2}/test_identity.py (60%) create mode 100644 functional/tests/identity/v3/__init__.py create mode 100644 functional/tests/identity/v3/test_identity.py create mode 100644 functional/tests/object/__init__.py create mode 100644 functional/tests/object/v1/__init__.py rename functional/tests/{ => object/v1}/test_object.py (100%) diff --git a/functional/tests/examples/__init__.py b/functional/tests/examples/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/functional/tests/test_examples.py b/functional/tests/examples/test_examples.py similarity index 100% rename from functional/tests/test_examples.py rename to functional/tests/examples/test_examples.py diff --git a/functional/tests/identity/__init__.py b/functional/tests/identity/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/functional/tests/identity/v2/__init__.py b/functional/tests/identity/v2/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/functional/tests/test_identity.py b/functional/tests/identity/v2/test_identity.py similarity index 60% rename from functional/tests/test_identity.py rename to functional/tests/identity/v2/test_identity.py index b328115446..37f29fc87e 100644 --- a/functional/tests/test_identity.py +++ b/functional/tests/identity/v2/test_identity.py @@ -10,9 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -import os -import uuid - from functional.common import exceptions from functional.common import test @@ -115,79 +112,3 @@ def test_ec2_credentials_show(self): 'ec2 credentials delete %s' % create_items[0]['access'], ) self.assert_show_fields(items, self.EC2_CREDENTIALS_FIELDS) - - -class IdentityV3Tests(test.TestCase): - """Functional tests for Identity V3 commands. """ - - DOMAIN_FIELDS = ['description', 'enabled', 'id', 'name', 'links'] - GROUP_FIELDS = ['description', 'domain_id', 'id', 'name', 'links'] - TOKEN_FIELDS = ['expires', 'id', 'project_id', 'user_id'] - - def _create_dummy_group(self): - name = uuid.uuid4().hex - self.openstack('group create ' + name) - return name - - def _create_dummy_domain(self): - name = uuid.uuid4().hex - self.openstack('domain create ' + name) - return name - - def setUp(self): - super(IdentityV3Tests, self).setUp() - auth_url = os.environ.get('OS_AUTH_URL') - auth_url = auth_url.replace('v2.0', 'v3') - os.environ['OS_AUTH_URL'] = auth_url - os.environ['OS_IDENTITY_API_VERSION'] = '3' - os.environ['OS_USER_DOMAIN_ID'] = 'default' - os.environ['OS_PROJECT_DOMAIN_ID'] = 'default' - - def test_group_create(self): - raw_output = self.openstack('group create ' + uuid.uuid4().hex) - items = self.parse_show(raw_output) - self.assert_show_fields(items, self.GROUP_FIELDS) - - def test_group_list(self): - self._create_dummy_group() - raw_output = self.openstack('group list') - items = self.parse_listing(raw_output) - self.assert_table_structure(items, BASIC_LIST_HEADERS) - - def test_group_delete(self): - name = self._create_dummy_group() - raw_output = self.openstack('group delete ' + name) - self.assertEqual(0, len(raw_output)) - - def test_group_show(self): - name = self._create_dummy_group() - raw_output = self.openstack('group show ' + name) - items = self.parse_show(raw_output) - self.assert_show_fields(items, self.GROUP_FIELDS) - - def test_domain_create(self): - raw_output = self.openstack('domain create ' + uuid.uuid4().hex) - items = self.parse_show(raw_output) - self.assert_show_fields(items, self.DOMAIN_FIELDS) - - def test_domain_list(self): - self._create_dummy_domain() - raw_output = self.openstack('domain list') - items = self.parse_listing(raw_output) - self.assert_table_structure(items, BASIC_LIST_HEADERS) - - def test_domain_delete(self): - name = self._create_dummy_domain() - self.assertRaises(exceptions.CommandFailed, - self.openstack, 'domain delete ' + name) - - def test_domain_show(self): - name = self._create_dummy_domain() - raw_output = self.openstack('domain show ' + name) - items = self.parse_show(raw_output) - self.assert_show_fields(items, self.DOMAIN_FIELDS) - - def test_token_issue(self): - raw_output = self.openstack('token issue') - items = self.parse_show(raw_output) - self.assert_show_fields(items, self.TOKEN_FIELDS) diff --git a/functional/tests/identity/v3/__init__.py b/functional/tests/identity/v3/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/functional/tests/identity/v3/test_identity.py b/functional/tests/identity/v3/test_identity.py new file mode 100644 index 0000000000..8231908594 --- /dev/null +++ b/functional/tests/identity/v3/test_identity.py @@ -0,0 +1,95 @@ +# 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 os +import uuid + +from functional.common import exceptions +from functional.common import test + +BASIC_LIST_HEADERS = ['ID', 'Name'] + + +class IdentityV3Tests(test.TestCase): + """Functional tests for Identity V3 commands. """ + + DOMAIN_FIELDS = ['description', 'enabled', 'id', 'name', 'links'] + GROUP_FIELDS = ['description', 'domain_id', 'id', 'name', 'links'] + TOKEN_FIELDS = ['expires', 'id', 'project_id', 'user_id'] + + def _create_dummy_group(self): + name = uuid.uuid4().hex + self.openstack('group create ' + name) + return name + + def _create_dummy_domain(self): + name = uuid.uuid4().hex + self.openstack('domain create ' + name) + return name + + def setUp(self): + super(IdentityV3Tests, self).setUp() + auth_url = os.environ.get('OS_AUTH_URL') + auth_url = auth_url.replace('v2.0', 'v3') + os.environ['OS_AUTH_URL'] = auth_url + os.environ['OS_IDENTITY_API_VERSION'] = '3' + os.environ['OS_USER_DOMAIN_ID'] = 'default' + os.environ['OS_PROJECT_DOMAIN_ID'] = 'default' + + def test_group_create(self): + raw_output = self.openstack('group create ' + uuid.uuid4().hex) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.GROUP_FIELDS) + + def test_group_list(self): + self._create_dummy_group() + raw_output = self.openstack('group list') + items = self.parse_listing(raw_output) + self.assert_table_structure(items, BASIC_LIST_HEADERS) + + def test_group_delete(self): + name = self._create_dummy_group() + raw_output = self.openstack('group delete ' + name) + self.assertEqual(0, len(raw_output)) + + def test_group_show(self): + name = self._create_dummy_group() + raw_output = self.openstack('group show ' + name) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.GROUP_FIELDS) + + def test_domain_create(self): + raw_output = self.openstack('domain create ' + uuid.uuid4().hex) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.DOMAIN_FIELDS) + + def test_domain_list(self): + self._create_dummy_domain() + raw_output = self.openstack('domain list') + items = self.parse_listing(raw_output) + self.assert_table_structure(items, BASIC_LIST_HEADERS) + + def test_domain_delete(self): + name = self._create_dummy_domain() + self.assertRaises(exceptions.CommandFailed, + self.openstack, 'domain delete ' + name) + + def test_domain_show(self): + name = self._create_dummy_domain() + raw_output = self.openstack('domain show ' + name) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.DOMAIN_FIELDS) + + def test_token_issue(self): + raw_output = self.openstack('token issue') + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.TOKEN_FIELDS) diff --git a/functional/tests/object/__init__.py b/functional/tests/object/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/functional/tests/object/v1/__init__.py b/functional/tests/object/v1/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/functional/tests/test_object.py b/functional/tests/object/v1/test_object.py similarity index 100% rename from functional/tests/test_object.py rename to functional/tests/object/v1/test_object.py From 0e61e5f43c68d98f6bf2278b4fa34d545032a6ce Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sat, 18 Apr 2015 06:04:12 +0000 Subject: [PATCH 0024/3095] Imported Translations from Transifex For more information about this automatic import see: https://wiki.openstack.org/wiki/Translations/Infrastructure Change-Id: I489da64d5c1b14506dfa6619df71afa763f4b3b6 --- .../de/LC_MESSAGES/python-openstackclient.po | 240 ++++++++++-------- .../locale/python-openstackclient.pot | 202 ++++++++------- .../LC_MESSAGES/python-openstackclient.po | 197 +++++++------- 3 files changed, 334 insertions(+), 305 deletions(-) diff --git a/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po b/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po index 6a00cc12bd..4db02a1ddc 100644 --- a/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po +++ b/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po @@ -9,9 +9,9 @@ msgid "" msgstr "" "Project-Id-Version: python-openstackclient\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-03-08 06:04+0000\n" -"PO-Revision-Date: 2015-03-05 20:28+0000\n" -"Last-Translator: openstackjenkins \n" +"POT-Creation-Date: 2015-04-18 06:04+0000\n" +"PO-Revision-Date: 2015-04-17 11:35+0000\n" +"Last-Translator: Ettore Atalan \n" "Language-Team: German (http://www.transifex.com/projects/p/python-" "openstackclient/language/de/)\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" @@ -20,49 +20,53 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 1.3\n" -#: openstackclient/api/auth.py:134 +#: openstackclient/api/auth.py:130 msgid "Set a username with --os-username or OS_USERNAME\n" -msgstr "" +msgstr "Legen Sie einen Benutzernamen mit --os-username oder OS_USERNAME fest\n" -#: openstackclient/api/auth.py:136 +#: openstackclient/api/auth.py:132 msgid "Set an authentication URL, with --os-auth-url or OS_AUTH_URL\n" msgstr "" +"Legen Sie eine Authentifizierungs-URL mit --os-auth-url oder OS_AUTH_URL " +"fest\n" -#: openstackclient/api/auth.py:140 +#: openstackclient/api/auth.py:136 msgid "" "Set a scope, such as a project or domain, with --os-project-name or " "OS_PROJECT_NAME" msgstr "" +"Legen Sie einen Gültigkeitsbereich, wie beispielsweise ein Projekt oder " +"eine Domäne, mit --os-project-name oder OS_PROJECT_NAME fest" #: openstackclient/compute/v2/availability_zone.py:72 -#: openstackclient/compute/v2/server.py:643 +#: openstackclient/compute/v2/server.py:650 #: openstackclient/identity/v2_0/endpoint.py:114 #: openstackclient/identity/v2_0/project.py:145 #: openstackclient/identity/v2_0/service.py:128 #: openstackclient/identity/v2_0/user.py:174 msgid "List additional fields in output" -msgstr "Zusätzliche Fehler in der Ausgabe auflisten" +msgstr "Zusätzliche Felder in der Ausgabe auflisten" #: openstackclient/compute/v2/server.py:121 #: openstackclient/compute/v2/server.py:158 -#: openstackclient/compute/v2/server.py:502 -#: openstackclient/compute/v2/server.py:714 -#: openstackclient/compute/v2/server.py:748 -#: openstackclient/compute/v2/server.py:831 -#: openstackclient/compute/v2/server.py:855 -#: openstackclient/compute/v2/server.py:910 -#: openstackclient/compute/v2/server.py:1002 -#: openstackclient/compute/v2/server.py:1042 -#: openstackclient/compute/v2/server.py:1068 -#: openstackclient/compute/v2/server.py:1133 -#: openstackclient/compute/v2/server.py:1157 -#: openstackclient/compute/v2/server.py:1216 -#: openstackclient/compute/v2/server.py:1253 -#: openstackclient/compute/v2/server.py:1411 -#: openstackclient/compute/v2/server.py:1435 -#: openstackclient/compute/v2/server.py:1459 -#: openstackclient/compute/v2/server.py:1483 -#: openstackclient/compute/v2/server.py:1507 +#: openstackclient/compute/v2/server.py:509 +#: openstackclient/compute/v2/server.py:721 +#: openstackclient/compute/v2/server.py:755 +#: openstackclient/compute/v2/server.py:838 +#: openstackclient/compute/v2/server.py:862 +#: openstackclient/compute/v2/server.py:917 +#: openstackclient/compute/v2/server.py:1009 +#: openstackclient/compute/v2/server.py:1049 +#: openstackclient/compute/v2/server.py:1075 +#: openstackclient/compute/v2/server.py:1140 +#: openstackclient/compute/v2/server.py:1164 +#: openstackclient/compute/v2/server.py:1223 +#: openstackclient/compute/v2/server.py:1260 +#: openstackclient/compute/v2/server.py:1418 +#: openstackclient/compute/v2/server.py:1442 +#: openstackclient/compute/v2/server.py:1466 +#: openstackclient/compute/v2/server.py:1490 +#: openstackclient/compute/v2/server.py:1514 msgid "Server (name or ID)" msgstr "Server (Name oder Kennung)" @@ -79,7 +83,7 @@ msgid "Server internal device name for volume" msgstr "Serverinterner Gerätename für Datenträger" #: openstackclient/compute/v2/server.py:208 -#: openstackclient/compute/v2/server.py:1162 +#: openstackclient/compute/v2/server.py:1169 msgid "New server name" msgstr "Neuer Servername" @@ -134,15 +138,26 @@ msgstr "" "::: (optionale " "Erweiterung)" -#: openstackclient/compute/v2/server.py:281 -msgid "Specify NIC configuration (optional extension)" -msgstr "Geben Sie eine NIC-Konfiguration an (optionale Erweiterung)" - -#: openstackclient/compute/v2/server.py:288 +#: openstackclient/compute/v2/server.py:282 +msgid "" +"Create a NIC on the server. Specify option multiple times to create " +"multiple NICs. Either net-id or port-id must be provided, but not both. " +"net-id: attach NIC to network with this UUID, port-id: attach NIC to port" +" with this UUID, v4-fixed-ip: IPv4 fixed address for NIC (optional), v6" +"-fixed-ip: IPv6 fixed address for NIC (optional), " +msgstr "" +"Erstellen Sie ein NIC auf dem Server. Geben Sie die Option mehrfach an, " +"um mehrere NICs zu erstellen. Entweder net-id oder port-id müssen " +"bereitgestellt werden, aber nicht beide. net-id: NIC an Netzwerk mit " +"dieser UUID binden, port-id: NIC an Port mit dieser UUID binden, v4" +"-fixed-ip: Feste IPv4-Adresse für NIC (optional), v6-fixed-ip: Feste " +"IPv6-Adresse für NIC (optional), " + +#: openstackclient/compute/v2/server.py:295 msgid "Hints for the scheduler (optional extension)" msgstr "Hinweise für den Planer (optionale Erweiterung)" -#: openstackclient/compute/v2/server.py:294 +#: openstackclient/compute/v2/server.py:301 msgid "" "Use specified volume as the config drive, or 'True' to use an ephemeral " "drive" @@ -150,44 +165,46 @@ msgstr "" "Benutzerdefinierter Datenträger als Konfigurationslaufwerk oder 'True', " "um ein flüchtiges Laufwerk zu verwenden" -#: openstackclient/compute/v2/server.py:302 +#: openstackclient/compute/v2/server.py:309 msgid "Minimum number of servers to launch (default=1)" msgstr "Minimale Anzahl der zu startenden Server (Standard=1)" -#: openstackclient/compute/v2/server.py:309 +#: openstackclient/compute/v2/server.py:316 msgid "Maximum number of servers to launch (default=1)" msgstr "Maximale Anzahl der zu startenden Server (Standard=1)" -#: openstackclient/compute/v2/server.py:314 +#: openstackclient/compute/v2/server.py:321 msgid "Wait for build to complete" msgstr "Warten Sie, bis die Herstellung abgeschlossen ist" -#: openstackclient/compute/v2/server.py:354 +#: openstackclient/compute/v2/server.py:361 msgid "min instances should be <= max instances" msgstr "min. Instanzen sollten <= max. Instanzen sein" -#: openstackclient/compute/v2/server.py:357 +#: openstackclient/compute/v2/server.py:364 msgid "min instances should be > 0" msgstr "min. Instanzen sollten > 0 sein" -#: openstackclient/compute/v2/server.py:360 +#: openstackclient/compute/v2/server.py:367 msgid "max instances should be > 0" msgstr "max. Instanzen sollten > 0 sein" -#: openstackclient/compute/v2/server.py:396 +#: openstackclient/compute/v2/server.py:403 msgid "either net-id or port-id should be specified but not both" -msgstr "" +msgstr "entweder net-id oder port-id sollten angegeben sein, aber nicht beide" -#: openstackclient/compute/v2/server.py:418 +#: openstackclient/compute/v2/server.py:425 msgid "can't create server with port specified since neutron not enabled" msgstr "" +"Server mit dem angegebenen Port kann nicht erstellt werden, da Neutron " +"nicht aktiviert ist" -#: openstackclient/compute/v2/server.py:483 +#: openstackclient/compute/v2/server.py:490 #, python-format msgid "Error creating server: %s" msgstr "Fehler beim Erstellen des Servers: %s" -#: openstackclient/compute/v2/server.py:485 +#: openstackclient/compute/v2/server.py:492 msgid "" "\n" "Error creating server" @@ -195,20 +212,20 @@ msgstr "" "\n" "Fehler beim Erstellen des Servers" -#: openstackclient/compute/v2/server.py:507 +#: openstackclient/compute/v2/server.py:514 msgid "Name of new image (default is server name)" msgstr "Name des neuen Abbilds (Servername ist Standard)" -#: openstackclient/compute/v2/server.py:512 +#: openstackclient/compute/v2/server.py:519 msgid "Wait for image create to complete" msgstr "Warten Sie, bis die Abbilderstellung abgeschlossen ist" -#: openstackclient/compute/v2/server.py:542 +#: openstackclient/compute/v2/server.py:549 #, python-format msgid "Error creating server snapshot: %s" msgstr "Fehler beim Erstellen der Server-Schattenkopie: %s" -#: openstackclient/compute/v2/server.py:544 +#: openstackclient/compute/v2/server.py:551 msgid "" "\n" "Error creating server snapshot" @@ -216,81 +233,81 @@ msgstr "" "\n" "Fehler beim Erstellen der Server-Schattenkopie" -#: openstackclient/compute/v2/server.py:566 +#: openstackclient/compute/v2/server.py:573 msgid "Server(s) to delete (name or ID)" msgstr "Zu löschende(r) Server (Name oder Kennung)" -#: openstackclient/compute/v2/server.py:590 +#: openstackclient/compute/v2/server.py:597 msgid "Only return instances that match the reservation" msgstr "Nur Instanzen zurückgeben, die der Einschränkung entsprechen" -#: openstackclient/compute/v2/server.py:595 +#: openstackclient/compute/v2/server.py:602 msgid "Regular expression to match IP addresses" msgstr "Regulärer Ausdruck zum Abgleichen mit IP-Adressen" -#: openstackclient/compute/v2/server.py:600 +#: openstackclient/compute/v2/server.py:607 msgid "Regular expression to match IPv6 addresses" msgstr "Regulärer Ausdruck zum Abgleichen mit IPv6-Adressen" -#: openstackclient/compute/v2/server.py:605 +#: openstackclient/compute/v2/server.py:612 msgid "Regular expression to match names" msgstr "Regulärer Ausdruck zum Abgleichen mit Namen" -#: openstackclient/compute/v2/server.py:610 +#: openstackclient/compute/v2/server.py:617 msgid "Regular expression to match instance name (admin only)" msgstr "Regulärer Ausdruck zum Abgleichen des Instanznamens (nur Administrator) " -#: openstackclient/compute/v2/server.py:616 +#: openstackclient/compute/v2/server.py:623 msgid "Search by server status" msgstr "Nach Serverstatus suchen" -#: openstackclient/compute/v2/server.py:621 +#: openstackclient/compute/v2/server.py:628 msgid "Search by flavor" msgstr "Nach Variante suchen" -#: openstackclient/compute/v2/server.py:626 +#: openstackclient/compute/v2/server.py:633 msgid "Search by image" msgstr "Nach Bild suchen" -#: openstackclient/compute/v2/server.py:631 +#: openstackclient/compute/v2/server.py:638 msgid "Search by hostname" msgstr "Nach Hostname suchen" -#: openstackclient/compute/v2/server.py:637 +#: openstackclient/compute/v2/server.py:644 msgid "Include all projects (admin only)" msgstr "Alle Projekte miteinbeziehen (nur Administrator)" -#: openstackclient/compute/v2/server.py:753 +#: openstackclient/compute/v2/server.py:760 msgid "Target hostname" msgstr "Zielhostname" -#: openstackclient/compute/v2/server.py:761 +#: openstackclient/compute/v2/server.py:768 msgid "Perform a shared live migration (default)" msgstr "Gemeinsame Live-Migration ausführen (Standard)" -#: openstackclient/compute/v2/server.py:767 +#: openstackclient/compute/v2/server.py:774 msgid "Perform a block live migration" msgstr "Blockorientierte Live-Migration ausführen" -#: openstackclient/compute/v2/server.py:774 +#: openstackclient/compute/v2/server.py:781 msgid "Allow disk over-commit on the destination host" msgstr "Festplattenüberladung auf dem Zielhost erlauben" -#: openstackclient/compute/v2/server.py:781 +#: openstackclient/compute/v2/server.py:788 msgid "Do not over-commit disk on the destination host (default)" msgstr "Festplatte auf dem Zielhost nicht überladen (Standard)" -#: openstackclient/compute/v2/server.py:787 -#: openstackclient/compute/v2/server.py:1088 +#: openstackclient/compute/v2/server.py:794 +#: openstackclient/compute/v2/server.py:1095 msgid "Wait for resize to complete" msgstr "Warten Sie, bis die Größenänderung abgeschlossen ist" -#: openstackclient/compute/v2/server.py:815 -#: openstackclient/compute/v2/server.py:1113 +#: openstackclient/compute/v2/server.py:822 +#: openstackclient/compute/v2/server.py:1120 msgid "Complete\n" msgstr "Fertig\n" -#: openstackclient/compute/v2/server.py:817 +#: openstackclient/compute/v2/server.py:824 msgid "" "\n" "Error migrating server" @@ -298,19 +315,19 @@ msgstr "" "\n" "Fehler beim Migrieren des Servers" -#: openstackclient/compute/v2/server.py:864 +#: openstackclient/compute/v2/server.py:871 msgid "Perform a hard reboot" msgstr "Harten Neustart ausführen" -#: openstackclient/compute/v2/server.py:872 +#: openstackclient/compute/v2/server.py:879 msgid "Perform a soft reboot" msgstr "Weichen Neustart ausführen" -#: openstackclient/compute/v2/server.py:877 +#: openstackclient/compute/v2/server.py:884 msgid "Wait for reboot to complete" msgstr "Warten Sie, bis der Neustart abgeschlossen ist" -#: openstackclient/compute/v2/server.py:894 +#: openstackclient/compute/v2/server.py:901 msgid "" "\n" "Reboot complete\n" @@ -318,7 +335,7 @@ msgstr "" "\n" "Neustart abgeschlossen\n" -#: openstackclient/compute/v2/server.py:896 +#: openstackclient/compute/v2/server.py:903 msgid "" "\n" "Error rebooting server\n" @@ -326,15 +343,15 @@ msgstr "" "\n" "Fehler beim Neustarten des Servers\n" -#: openstackclient/compute/v2/server.py:916 +#: openstackclient/compute/v2/server.py:923 msgid "Recreate server from this image" msgstr "Server aus diesem Abbild neu erstellen" -#: openstackclient/compute/v2/server.py:926 +#: openstackclient/compute/v2/server.py:933 msgid "Wait for rebuild to complete" msgstr "Warten Sie, bis die Wiederherstellung abgeschlossen ist" -#: openstackclient/compute/v2/server.py:947 +#: openstackclient/compute/v2/server.py:954 msgid "" "\n" "Complete\n" @@ -342,7 +359,7 @@ msgstr "" "\n" "Fertig\n" -#: openstackclient/compute/v2/server.py:949 +#: openstackclient/compute/v2/server.py:956 msgid "" "\n" "Error rebuilding server" @@ -350,31 +367,31 @@ msgstr "" "\n" "Fehler bei der Wiederherstellung des Servers" -#: openstackclient/compute/v2/server.py:966 +#: openstackclient/compute/v2/server.py:973 msgid "Name or ID of server to use" msgstr "Name oder Kennung des zu verwendenden Servers" -#: openstackclient/compute/v2/server.py:971 +#: openstackclient/compute/v2/server.py:978 msgid "Name or ID of security group to remove from server" msgstr "Name oder Kennung der vom Server zu entfernenden Sicherheitsgruppe" -#: openstackclient/compute/v2/server.py:1007 +#: openstackclient/compute/v2/server.py:1014 msgid "Volume to remove (name or ID)" msgstr "Zu entfernender Datenträger (Name oder Kennung)" -#: openstackclient/compute/v2/server.py:1073 +#: openstackclient/compute/v2/server.py:1080 msgid "Resize server to specified flavor" msgstr "Servergröße auf angegebene Variante ändern" -#: openstackclient/compute/v2/server.py:1078 +#: openstackclient/compute/v2/server.py:1085 msgid "Confirm server resize is complete" -msgstr "" +msgstr "Bestätigen Sie, ob die Größenänderung des Servers abgeschlossen ist" -#: openstackclient/compute/v2/server.py:1083 +#: openstackclient/compute/v2/server.py:1090 msgid "Restore server state before resize" msgstr "Serverstatus vor der Größenänderung wiederherstellen" -#: openstackclient/compute/v2/server.py:1115 +#: openstackclient/compute/v2/server.py:1122 msgid "" "\n" "Error resizing server" @@ -382,11 +399,11 @@ msgstr "" "\n" "Fehler bei der Größenänderung des Servers" -#: openstackclient/compute/v2/server.py:1167 +#: openstackclient/compute/v2/server.py:1174 msgid "Set new root password (interactive only)" msgstr "Neues root-Passwort festlegen (Nur interaktiv)" -#: openstackclient/compute/v2/server.py:1173 +#: openstackclient/compute/v2/server.py:1180 msgid "" "Property to add/change for this server (repeat option to set multiple " "properties)" @@ -394,71 +411,72 @@ msgstr "" "Zu hinzufügende/ändernde Eigenschaft für diesen Server (wiederholen Sie " "die Option, um mehrere Eigenschaften festzulegen)" -#: openstackclient/compute/v2/server.py:1197 +#: openstackclient/compute/v2/server.py:1204 msgid "New password: " msgstr "Neues Passwort:" -#: openstackclient/compute/v2/server.py:1198 +#: openstackclient/compute/v2/server.py:1205 msgid "Retype new password: " msgstr "Neues Passwort erneut eingeben:" -#: openstackclient/compute/v2/server.py:1202 +#: openstackclient/compute/v2/server.py:1209 msgid "Passwords do not match, password unchanged" msgstr "Passwörter stimmen nicht überein, Passwort unverändert" -#: openstackclient/compute/v2/server.py:1222 +#: openstackclient/compute/v2/server.py:1229 msgid "Display server diagnostics information" msgstr "Serverdiagnoseinformationen anzeigen" -#: openstackclient/compute/v2/server.py:1235 +#: openstackclient/compute/v2/server.py:1242 msgid "Error retrieving diagnostics data" msgstr "Fehler beim Abrufen der Diagnosedaten" -#: openstackclient/compute/v2/server.py:1258 +#: openstackclient/compute/v2/server.py:1265 msgid "Login name (ssh -l option)" msgstr "Anmeldename (ssh -l Option)" -#: openstackclient/compute/v2/server.py:1269 +#: openstackclient/compute/v2/server.py:1276 msgid "Destination port (ssh -p option)" msgstr "Zielport (ssh -p Option)" -#: openstackclient/compute/v2/server.py:1281 +#: openstackclient/compute/v2/server.py:1288 msgid "Private key file (ssh -i option)" msgstr "Private Schlüsseldatei (ssh -i Option)" -#: openstackclient/compute/v2/server.py:1292 +#: openstackclient/compute/v2/server.py:1299 msgid "Options in ssh_config(5) format (ssh -o option)" msgstr "Optionen im ssh_config(5)-Format (ssh -o Option)" -#: openstackclient/compute/v2/server.py:1306 +#: openstackclient/compute/v2/server.py:1313 msgid "Use only IPv4 addresses" msgstr "Nur IPv4-Adressen verwenden" -#: openstackclient/compute/v2/server.py:1313 +#: openstackclient/compute/v2/server.py:1320 msgid "Use only IPv6 addresses" msgstr "Nur IPv6-Adressen verwenden" -#: openstackclient/compute/v2/server.py:1322 +#: openstackclient/compute/v2/server.py:1329 msgid "Use public IP address" msgstr "Öffentliche IP-Adresse verwenden" -#: openstackclient/compute/v2/server.py:1330 +#: openstackclient/compute/v2/server.py:1337 msgid "Use private IP address" msgstr "Private IP-Adresse verwenden" -#: openstackclient/compute/v2/server.py:1337 +#: openstackclient/compute/v2/server.py:1344 msgid "Use other IP address (public, private, etc)" msgstr "Andere IP-Adresse verwenden (öffentlich, privat, usw.)" -#: openstackclient/compute/v2/server.py:1514 +#: openstackclient/compute/v2/server.py:1521 msgid "Property key to remove from server (repeat to unset multiple values)" msgstr "" "Vom Server zu entfernender Eigenschaftsschlüssel (zum Aufheben von " "mehreren Werten wiederholen)" -#: openstackclient/identity/v2_0/catalog.py:71 +#: openstackclient/identity/v2_0/catalog.py:73 +#: openstackclient/identity/v3/catalog.py:72 msgid "Service to display (type or name)" -msgstr "" +msgstr "Anzuzeigender Dienst (Typ oder Name)" #: openstackclient/identity/v2_0/ec2creds.py:40 msgid "Specify an alternate project (default: current authenticated project)" @@ -567,7 +585,7 @@ msgstr "" #: openstackclient/identity/v2_0/project.py:253 msgid "Project to display (name or ID)" -msgstr "" +msgstr "Anzuzeigendes Projekt (Name oder Kennung)" #: openstackclient/identity/v2_0/role.py:41 msgid "Role to add to : (name or ID)" @@ -616,11 +634,11 @@ msgstr "Benutzer nach filtern (Name oder Kennung)" #: openstackclient/identity/v2_0/role.py:303 msgid "Role to remove (name or ID)" -msgstr "" +msgstr "Zu entfernende Rolle (Name oder Kennung)" #: openstackclient/identity/v2_0/role.py:344 msgid "Role to display (name or ID)" -msgstr "" +msgstr "Anzuzeigende Rolle (Name oder Kennung)" #: openstackclient/identity/v2_0/service.py:42 msgid "New service type (compute, image, identity, volume, etc)" @@ -761,7 +779,7 @@ msgstr "Nach übergeordneter Regionskennung filtern" #: openstackclient/identity/v3/region.py:141 msgid "Region to modify" -msgstr "" +msgstr "Zu ändernde Region" #: openstackclient/identity/v3/region.py:146 msgid "New parent region ID" @@ -769,5 +787,5 @@ msgstr "Neue übergeordnete Regionskennung" #: openstackclient/identity/v3/region.py:191 msgid "Region to display" -msgstr "" +msgstr "Anzuzeigende Region" diff --git a/python-openstackclient/locale/python-openstackclient.pot b/python-openstackclient/locale/python-openstackclient.pot index 08accfb841..5031392888 100644 --- a/python-openstackclient/locale/python-openstackclient.pot +++ b/python-openstackclient/locale/python-openstackclient.pot @@ -7,9 +7,9 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: python-openstackclient 1.0.2.post63\n" +"Project-Id-Version: python-openstackclient 1.0.3.post31\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-03-08 06:04+0000\n" +"POT-Creation-Date: 2015-04-18 06:04+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -18,22 +18,22 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 1.3\n" -#: openstackclient/api/auth.py:134 +#: openstackclient/api/auth.py:130 msgid "Set a username with --os-username or OS_USERNAME\n" msgstr "" -#: openstackclient/api/auth.py:136 +#: openstackclient/api/auth.py:132 msgid "Set an authentication URL, with --os-auth-url or OS_AUTH_URL\n" msgstr "" -#: openstackclient/api/auth.py:140 +#: openstackclient/api/auth.py:136 msgid "" "Set a scope, such as a project or domain, with --os-project-name or " "OS_PROJECT_NAME" msgstr "" #: openstackclient/compute/v2/availability_zone.py:72 -#: openstackclient/compute/v2/server.py:643 +#: openstackclient/compute/v2/server.py:650 #: openstackclient/identity/v2_0/endpoint.py:114 #: openstackclient/identity/v2_0/project.py:145 #: openstackclient/identity/v2_0/service.py:128 @@ -43,24 +43,24 @@ msgstr "" #: openstackclient/compute/v2/server.py:121 #: openstackclient/compute/v2/server.py:158 -#: openstackclient/compute/v2/server.py:502 -#: openstackclient/compute/v2/server.py:714 -#: openstackclient/compute/v2/server.py:748 -#: openstackclient/compute/v2/server.py:831 -#: openstackclient/compute/v2/server.py:855 -#: openstackclient/compute/v2/server.py:910 -#: openstackclient/compute/v2/server.py:1002 -#: openstackclient/compute/v2/server.py:1042 -#: openstackclient/compute/v2/server.py:1068 -#: openstackclient/compute/v2/server.py:1133 -#: openstackclient/compute/v2/server.py:1157 -#: openstackclient/compute/v2/server.py:1216 -#: openstackclient/compute/v2/server.py:1253 -#: openstackclient/compute/v2/server.py:1411 -#: openstackclient/compute/v2/server.py:1435 -#: openstackclient/compute/v2/server.py:1459 -#: openstackclient/compute/v2/server.py:1483 -#: openstackclient/compute/v2/server.py:1507 +#: openstackclient/compute/v2/server.py:509 +#: openstackclient/compute/v2/server.py:721 +#: openstackclient/compute/v2/server.py:755 +#: openstackclient/compute/v2/server.py:838 +#: openstackclient/compute/v2/server.py:862 +#: openstackclient/compute/v2/server.py:917 +#: openstackclient/compute/v2/server.py:1009 +#: openstackclient/compute/v2/server.py:1049 +#: openstackclient/compute/v2/server.py:1075 +#: openstackclient/compute/v2/server.py:1140 +#: openstackclient/compute/v2/server.py:1164 +#: openstackclient/compute/v2/server.py:1223 +#: openstackclient/compute/v2/server.py:1260 +#: openstackclient/compute/v2/server.py:1418 +#: openstackclient/compute/v2/server.py:1442 +#: openstackclient/compute/v2/server.py:1466 +#: openstackclient/compute/v2/server.py:1490 +#: openstackclient/compute/v2/server.py:1514 msgid "Server (name or ID)" msgstr "" @@ -77,7 +77,7 @@ msgid "Server internal device name for volume" msgstr "" #: openstackclient/compute/v2/server.py:208 -#: openstackclient/compute/v2/server.py:1162 +#: openstackclient/compute/v2/server.py:1169 msgid "New server name" msgstr "" @@ -123,307 +123,313 @@ msgid "" "(optional extension)" msgstr "" -#: openstackclient/compute/v2/server.py:281 -msgid "Specify NIC configuration (optional extension)" +#: openstackclient/compute/v2/server.py:282 +msgid "" +"Create a NIC on the server. Specify option multiple times to create " +"multiple NICs. Either net-id or port-id must be provided, but not both. " +"net-id: attach NIC to network with this UUID, port-id: attach NIC to port" +" with this UUID, v4-fixed-ip: IPv4 fixed address for NIC (optional), v6" +"-fixed-ip: IPv6 fixed address for NIC (optional), " msgstr "" -#: openstackclient/compute/v2/server.py:288 +#: openstackclient/compute/v2/server.py:295 msgid "Hints for the scheduler (optional extension)" msgstr "" -#: openstackclient/compute/v2/server.py:294 +#: openstackclient/compute/v2/server.py:301 msgid "" "Use specified volume as the config drive, or 'True' to use an ephemeral " "drive" msgstr "" -#: openstackclient/compute/v2/server.py:302 +#: openstackclient/compute/v2/server.py:309 msgid "Minimum number of servers to launch (default=1)" msgstr "" -#: openstackclient/compute/v2/server.py:309 +#: openstackclient/compute/v2/server.py:316 msgid "Maximum number of servers to launch (default=1)" msgstr "" -#: openstackclient/compute/v2/server.py:314 +#: openstackclient/compute/v2/server.py:321 msgid "Wait for build to complete" msgstr "" -#: openstackclient/compute/v2/server.py:354 +#: openstackclient/compute/v2/server.py:361 msgid "min instances should be <= max instances" msgstr "" -#: openstackclient/compute/v2/server.py:357 +#: openstackclient/compute/v2/server.py:364 msgid "min instances should be > 0" msgstr "" -#: openstackclient/compute/v2/server.py:360 +#: openstackclient/compute/v2/server.py:367 msgid "max instances should be > 0" msgstr "" -#: openstackclient/compute/v2/server.py:396 +#: openstackclient/compute/v2/server.py:403 msgid "either net-id or port-id should be specified but not both" msgstr "" -#: openstackclient/compute/v2/server.py:418 +#: openstackclient/compute/v2/server.py:425 msgid "can't create server with port specified since neutron not enabled" msgstr "" -#: openstackclient/compute/v2/server.py:483 +#: openstackclient/compute/v2/server.py:490 #, python-format msgid "Error creating server: %s" msgstr "" -#: openstackclient/compute/v2/server.py:485 +#: openstackclient/compute/v2/server.py:492 msgid "" "\n" "Error creating server" msgstr "" -#: openstackclient/compute/v2/server.py:507 +#: openstackclient/compute/v2/server.py:514 msgid "Name of new image (default is server name)" msgstr "" -#: openstackclient/compute/v2/server.py:512 +#: openstackclient/compute/v2/server.py:519 msgid "Wait for image create to complete" msgstr "" -#: openstackclient/compute/v2/server.py:542 +#: openstackclient/compute/v2/server.py:549 #, python-format msgid "Error creating server snapshot: %s" msgstr "" -#: openstackclient/compute/v2/server.py:544 +#: openstackclient/compute/v2/server.py:551 msgid "" "\n" "Error creating server snapshot" msgstr "" -#: openstackclient/compute/v2/server.py:566 +#: openstackclient/compute/v2/server.py:573 msgid "Server(s) to delete (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:590 +#: openstackclient/compute/v2/server.py:597 msgid "Only return instances that match the reservation" msgstr "" -#: openstackclient/compute/v2/server.py:595 +#: openstackclient/compute/v2/server.py:602 msgid "Regular expression to match IP addresses" msgstr "" -#: openstackclient/compute/v2/server.py:600 +#: openstackclient/compute/v2/server.py:607 msgid "Regular expression to match IPv6 addresses" msgstr "" -#: openstackclient/compute/v2/server.py:605 +#: openstackclient/compute/v2/server.py:612 msgid "Regular expression to match names" msgstr "" -#: openstackclient/compute/v2/server.py:610 +#: openstackclient/compute/v2/server.py:617 msgid "Regular expression to match instance name (admin only)" msgstr "" -#: openstackclient/compute/v2/server.py:616 +#: openstackclient/compute/v2/server.py:623 msgid "Search by server status" msgstr "" -#: openstackclient/compute/v2/server.py:621 +#: openstackclient/compute/v2/server.py:628 msgid "Search by flavor" msgstr "" -#: openstackclient/compute/v2/server.py:626 +#: openstackclient/compute/v2/server.py:633 msgid "Search by image" msgstr "" -#: openstackclient/compute/v2/server.py:631 +#: openstackclient/compute/v2/server.py:638 msgid "Search by hostname" msgstr "" -#: openstackclient/compute/v2/server.py:637 +#: openstackclient/compute/v2/server.py:644 msgid "Include all projects (admin only)" msgstr "" -#: openstackclient/compute/v2/server.py:753 +#: openstackclient/compute/v2/server.py:760 msgid "Target hostname" msgstr "" -#: openstackclient/compute/v2/server.py:761 +#: openstackclient/compute/v2/server.py:768 msgid "Perform a shared live migration (default)" msgstr "" -#: openstackclient/compute/v2/server.py:767 +#: openstackclient/compute/v2/server.py:774 msgid "Perform a block live migration" msgstr "" -#: openstackclient/compute/v2/server.py:774 +#: openstackclient/compute/v2/server.py:781 msgid "Allow disk over-commit on the destination host" msgstr "" -#: openstackclient/compute/v2/server.py:781 +#: openstackclient/compute/v2/server.py:788 msgid "Do not over-commit disk on the destination host (default)" msgstr "" -#: openstackclient/compute/v2/server.py:787 -#: openstackclient/compute/v2/server.py:1088 +#: openstackclient/compute/v2/server.py:794 +#: openstackclient/compute/v2/server.py:1095 msgid "Wait for resize to complete" msgstr "" -#: openstackclient/compute/v2/server.py:815 -#: openstackclient/compute/v2/server.py:1113 +#: openstackclient/compute/v2/server.py:822 +#: openstackclient/compute/v2/server.py:1120 msgid "Complete\n" msgstr "" -#: openstackclient/compute/v2/server.py:817 +#: openstackclient/compute/v2/server.py:824 msgid "" "\n" "Error migrating server" msgstr "" -#: openstackclient/compute/v2/server.py:864 +#: openstackclient/compute/v2/server.py:871 msgid "Perform a hard reboot" msgstr "" -#: openstackclient/compute/v2/server.py:872 +#: openstackclient/compute/v2/server.py:879 msgid "Perform a soft reboot" msgstr "" -#: openstackclient/compute/v2/server.py:877 +#: openstackclient/compute/v2/server.py:884 msgid "Wait for reboot to complete" msgstr "" -#: openstackclient/compute/v2/server.py:894 +#: openstackclient/compute/v2/server.py:901 msgid "" "\n" "Reboot complete\n" msgstr "" -#: openstackclient/compute/v2/server.py:896 +#: openstackclient/compute/v2/server.py:903 msgid "" "\n" "Error rebooting server\n" msgstr "" -#: openstackclient/compute/v2/server.py:916 +#: openstackclient/compute/v2/server.py:923 msgid "Recreate server from this image" msgstr "" -#: openstackclient/compute/v2/server.py:926 +#: openstackclient/compute/v2/server.py:933 msgid "Wait for rebuild to complete" msgstr "" -#: openstackclient/compute/v2/server.py:947 +#: openstackclient/compute/v2/server.py:954 msgid "" "\n" "Complete\n" msgstr "" -#: openstackclient/compute/v2/server.py:949 +#: openstackclient/compute/v2/server.py:956 msgid "" "\n" "Error rebuilding server" msgstr "" -#: openstackclient/compute/v2/server.py:966 +#: openstackclient/compute/v2/server.py:973 msgid "Name or ID of server to use" msgstr "" -#: openstackclient/compute/v2/server.py:971 +#: openstackclient/compute/v2/server.py:978 msgid "Name or ID of security group to remove from server" msgstr "" -#: openstackclient/compute/v2/server.py:1007 +#: openstackclient/compute/v2/server.py:1014 msgid "Volume to remove (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:1073 +#: openstackclient/compute/v2/server.py:1080 msgid "Resize server to specified flavor" msgstr "" -#: openstackclient/compute/v2/server.py:1078 +#: openstackclient/compute/v2/server.py:1085 msgid "Confirm server resize is complete" msgstr "" -#: openstackclient/compute/v2/server.py:1083 +#: openstackclient/compute/v2/server.py:1090 msgid "Restore server state before resize" msgstr "" -#: openstackclient/compute/v2/server.py:1115 +#: openstackclient/compute/v2/server.py:1122 msgid "" "\n" "Error resizing server" msgstr "" -#: openstackclient/compute/v2/server.py:1167 +#: openstackclient/compute/v2/server.py:1174 msgid "Set new root password (interactive only)" msgstr "" -#: openstackclient/compute/v2/server.py:1173 +#: openstackclient/compute/v2/server.py:1180 msgid "" "Property to add/change for this server (repeat option to set multiple " "properties)" msgstr "" -#: openstackclient/compute/v2/server.py:1197 +#: openstackclient/compute/v2/server.py:1204 msgid "New password: " msgstr "" -#: openstackclient/compute/v2/server.py:1198 +#: openstackclient/compute/v2/server.py:1205 msgid "Retype new password: " msgstr "" -#: openstackclient/compute/v2/server.py:1202 +#: openstackclient/compute/v2/server.py:1209 msgid "Passwords do not match, password unchanged" msgstr "" -#: openstackclient/compute/v2/server.py:1222 +#: openstackclient/compute/v2/server.py:1229 msgid "Display server diagnostics information" msgstr "" -#: openstackclient/compute/v2/server.py:1235 +#: openstackclient/compute/v2/server.py:1242 msgid "Error retrieving diagnostics data" msgstr "" -#: openstackclient/compute/v2/server.py:1258 +#: openstackclient/compute/v2/server.py:1265 msgid "Login name (ssh -l option)" msgstr "" -#: openstackclient/compute/v2/server.py:1269 +#: openstackclient/compute/v2/server.py:1276 msgid "Destination port (ssh -p option)" msgstr "" -#: openstackclient/compute/v2/server.py:1281 +#: openstackclient/compute/v2/server.py:1288 msgid "Private key file (ssh -i option)" msgstr "" -#: openstackclient/compute/v2/server.py:1292 +#: openstackclient/compute/v2/server.py:1299 msgid "Options in ssh_config(5) format (ssh -o option)" msgstr "" -#: openstackclient/compute/v2/server.py:1306 +#: openstackclient/compute/v2/server.py:1313 msgid "Use only IPv4 addresses" msgstr "" -#: openstackclient/compute/v2/server.py:1313 +#: openstackclient/compute/v2/server.py:1320 msgid "Use only IPv6 addresses" msgstr "" -#: openstackclient/compute/v2/server.py:1322 +#: openstackclient/compute/v2/server.py:1329 msgid "Use public IP address" msgstr "" -#: openstackclient/compute/v2/server.py:1330 +#: openstackclient/compute/v2/server.py:1337 msgid "Use private IP address" msgstr "" -#: openstackclient/compute/v2/server.py:1337 +#: openstackclient/compute/v2/server.py:1344 msgid "Use other IP address (public, private, etc)" msgstr "" -#: openstackclient/compute/v2/server.py:1514 +#: openstackclient/compute/v2/server.py:1521 msgid "Property key to remove from server (repeat to unset multiple values)" msgstr "" -#: openstackclient/identity/v2_0/catalog.py:71 +#: openstackclient/identity/v2_0/catalog.py:73 +#: openstackclient/identity/v3/catalog.py:72 msgid "Service to display (type or name)" msgstr "" diff --git a/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po b/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po index 662b27ed75..415790458e 100644 --- a/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po +++ b/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po @@ -8,9 +8,9 @@ msgid "" msgstr "" "Project-Id-Version: python-openstackclient\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-03-31 06:05+0000\n" -"PO-Revision-Date: 2015-03-31 05:45+0000\n" -"Last-Translator: Zhang Xiaowei \n" +"POT-Creation-Date: 2015-04-18 06:04+0000\n" +"PO-Revision-Date: 2015-04-17 07:05+0000\n" +"Last-Translator: openstackjenkins \n" "Language-Team: Chinese (Taiwan) (http://www.transifex.com/projects/p" "/python-openstackclient/language/zh_TW/)\n" "Plural-Forms: nplurals=1; plural=0\n" @@ -34,7 +34,7 @@ msgid "" msgstr "以 --os-project-name 或 OS_PROJECT_NAME 設定範圍,如同專案或區域" #: openstackclient/compute/v2/availability_zone.py:72 -#: openstackclient/compute/v2/server.py:643 +#: openstackclient/compute/v2/server.py:650 #: openstackclient/identity/v2_0/endpoint.py:114 #: openstackclient/identity/v2_0/project.py:145 #: openstackclient/identity/v2_0/service.py:128 @@ -44,24 +44,24 @@ msgstr "列出額外的欄位" #: openstackclient/compute/v2/server.py:121 #: openstackclient/compute/v2/server.py:158 -#: openstackclient/compute/v2/server.py:502 -#: openstackclient/compute/v2/server.py:714 -#: openstackclient/compute/v2/server.py:748 -#: openstackclient/compute/v2/server.py:831 -#: openstackclient/compute/v2/server.py:855 -#: openstackclient/compute/v2/server.py:910 -#: openstackclient/compute/v2/server.py:1002 -#: openstackclient/compute/v2/server.py:1042 -#: openstackclient/compute/v2/server.py:1068 -#: openstackclient/compute/v2/server.py:1133 -#: openstackclient/compute/v2/server.py:1157 -#: openstackclient/compute/v2/server.py:1216 -#: openstackclient/compute/v2/server.py:1253 -#: openstackclient/compute/v2/server.py:1411 -#: openstackclient/compute/v2/server.py:1435 -#: openstackclient/compute/v2/server.py:1459 -#: openstackclient/compute/v2/server.py:1483 -#: openstackclient/compute/v2/server.py:1507 +#: openstackclient/compute/v2/server.py:509 +#: openstackclient/compute/v2/server.py:721 +#: openstackclient/compute/v2/server.py:755 +#: openstackclient/compute/v2/server.py:838 +#: openstackclient/compute/v2/server.py:862 +#: openstackclient/compute/v2/server.py:917 +#: openstackclient/compute/v2/server.py:1009 +#: openstackclient/compute/v2/server.py:1049 +#: openstackclient/compute/v2/server.py:1075 +#: openstackclient/compute/v2/server.py:1140 +#: openstackclient/compute/v2/server.py:1164 +#: openstackclient/compute/v2/server.py:1223 +#: openstackclient/compute/v2/server.py:1260 +#: openstackclient/compute/v2/server.py:1418 +#: openstackclient/compute/v2/server.py:1442 +#: openstackclient/compute/v2/server.py:1466 +#: openstackclient/compute/v2/server.py:1490 +#: openstackclient/compute/v2/server.py:1514 msgid "Server (name or ID)" msgstr "伺服器(名稱或識別號)" @@ -78,7 +78,7 @@ msgid "Server internal device name for volume" msgstr "雲硬碟在雲實例內的裝置名稱" #: openstackclient/compute/v2/server.py:208 -#: openstackclient/compute/v2/server.py:1162 +#: openstackclient/compute/v2/server.py:1169 msgid "New server name" msgstr "新雲實例名稱" @@ -124,58 +124,63 @@ msgid "" "(optional extension)" msgstr "映射區塊裝置;映射是 :::(選用)" -#: openstackclient/compute/v2/server.py:281 -msgid "Specify NIC configuration (optional extension)" -msgstr "指定網路卡設置(選用)" +#: openstackclient/compute/v2/server.py:282 +msgid "" +"Create a NIC on the server. Specify option multiple times to create " +"multiple NICs. Either net-id or port-id must be provided, but not both. " +"net-id: attach NIC to network with this UUID, port-id: attach NIC to port" +" with this UUID, v4-fixed-ip: IPv4 fixed address for NIC (optional), v6" +"-fixed-ip: IPv6 fixed address for NIC (optional), " +msgstr "" -#: openstackclient/compute/v2/server.py:288 +#: openstackclient/compute/v2/server.py:295 msgid "Hints for the scheduler (optional extension)" msgstr "給排程器的提示(選用)" -#: openstackclient/compute/v2/server.py:294 +#: openstackclient/compute/v2/server.py:301 msgid "" "Use specified volume as the config drive, or 'True' to use an ephemeral " "drive" msgstr "使用指定的雲硬碟為設定檔硬碟,或「True」來使用暫時性硬碟" -#: openstackclient/compute/v2/server.py:302 +#: openstackclient/compute/v2/server.py:309 msgid "Minimum number of servers to launch (default=1)" msgstr "最少要發動的雲實例(預設為 1)" -#: openstackclient/compute/v2/server.py:309 +#: openstackclient/compute/v2/server.py:316 msgid "Maximum number of servers to launch (default=1)" msgstr "最多要發動的雲實例(預設為 1)" -#: openstackclient/compute/v2/server.py:314 +#: openstackclient/compute/v2/server.py:321 msgid "Wait for build to complete" msgstr "等待完成建立" -#: openstackclient/compute/v2/server.py:354 +#: openstackclient/compute/v2/server.py:361 msgid "min instances should be <= max instances" msgstr "雲實例發動的最少值不應大於最大值" -#: openstackclient/compute/v2/server.py:357 +#: openstackclient/compute/v2/server.py:364 msgid "min instances should be > 0" msgstr "雲實例發動的最少值要大於 0" -#: openstackclient/compute/v2/server.py:360 +#: openstackclient/compute/v2/server.py:367 msgid "max instances should be > 0" msgstr "雲實例發動的最大值要大於 0" -#: openstackclient/compute/v2/server.py:396 +#: openstackclient/compute/v2/server.py:403 msgid "either net-id or port-id should be specified but not both" msgstr "任選網路識別號或接口識別號,但不能兩者都指定" -#: openstackclient/compute/v2/server.py:418 +#: openstackclient/compute/v2/server.py:425 msgid "can't create server with port specified since neutron not enabled" msgstr "Neutron 未啟用時,不能以指定的接口來新增雲實例" -#: openstackclient/compute/v2/server.py:483 +#: openstackclient/compute/v2/server.py:490 #, python-format msgid "Error creating server: %s" msgstr "新增雲實例時出錯:%s" -#: openstackclient/compute/v2/server.py:485 +#: openstackclient/compute/v2/server.py:492 msgid "" "\n" "Error creating server" @@ -183,20 +188,20 @@ msgstr "" "\n" "新增雲實例時出錯" -#: openstackclient/compute/v2/server.py:507 +#: openstackclient/compute/v2/server.py:514 msgid "Name of new image (default is server name)" msgstr "新映像檔的名稱(預設為雲實例名稱)" -#: openstackclient/compute/v2/server.py:512 +#: openstackclient/compute/v2/server.py:519 msgid "Wait for image create to complete" msgstr "等待映像檔新增完成" -#: openstackclient/compute/v2/server.py:542 +#: openstackclient/compute/v2/server.py:549 #, python-format msgid "Error creating server snapshot: %s" msgstr "新增雲實例即時存檔時出錯:%s" -#: openstackclient/compute/v2/server.py:544 +#: openstackclient/compute/v2/server.py:551 msgid "" "\n" "Error creating server snapshot" @@ -204,81 +209,81 @@ msgstr "" "\n" "新增雲實例即時存檔時出錯" -#: openstackclient/compute/v2/server.py:566 +#: openstackclient/compute/v2/server.py:573 msgid "Server(s) to delete (name or ID)" msgstr "要刪除的雲實例(名稱或識別號)" -#: openstackclient/compute/v2/server.py:590 +#: openstackclient/compute/v2/server.py:597 msgid "Only return instances that match the reservation" msgstr "只回傳要保留的雲實例" -#: openstackclient/compute/v2/server.py:595 +#: openstackclient/compute/v2/server.py:602 msgid "Regular expression to match IP addresses" msgstr "以正規式來匹配 IP 位址" -#: openstackclient/compute/v2/server.py:600 +#: openstackclient/compute/v2/server.py:607 msgid "Regular expression to match IPv6 addresses" msgstr "以正規式來匹配 IPv6 位址" -#: openstackclient/compute/v2/server.py:605 +#: openstackclient/compute/v2/server.py:612 msgid "Regular expression to match names" msgstr "以正規式來匹配名稱" -#: openstackclient/compute/v2/server.py:610 +#: openstackclient/compute/v2/server.py:617 msgid "Regular expression to match instance name (admin only)" msgstr "以正規式來匹配雲實例名稱(管理員專用)" -#: openstackclient/compute/v2/server.py:616 +#: openstackclient/compute/v2/server.py:623 msgid "Search by server status" msgstr "以雲實例狀態來尋找" -#: openstackclient/compute/v2/server.py:621 +#: openstackclient/compute/v2/server.py:628 msgid "Search by flavor" msgstr "以虛擬硬體樣板來尋找" -#: openstackclient/compute/v2/server.py:626 +#: openstackclient/compute/v2/server.py:633 msgid "Search by image" msgstr "以映像檔來尋找" -#: openstackclient/compute/v2/server.py:631 +#: openstackclient/compute/v2/server.py:638 msgid "Search by hostname" msgstr "以主機名稱來尋找" -#: openstackclient/compute/v2/server.py:637 +#: openstackclient/compute/v2/server.py:644 msgid "Include all projects (admin only)" msgstr "包括所有的專案(管理員專用)" -#: openstackclient/compute/v2/server.py:753 +#: openstackclient/compute/v2/server.py:760 msgid "Target hostname" msgstr "目標主機名稱" -#: openstackclient/compute/v2/server.py:761 +#: openstackclient/compute/v2/server.py:768 msgid "Perform a shared live migration (default)" msgstr "覆行已分享的即時轉移(預設)" -#: openstackclient/compute/v2/server.py:767 +#: openstackclient/compute/v2/server.py:774 msgid "Perform a block live migration" msgstr "覆行區塊的即時轉移" -#: openstackclient/compute/v2/server.py:774 +#: openstackclient/compute/v2/server.py:781 msgid "Allow disk over-commit on the destination host" msgstr "允許目標主機過量使用" -#: openstackclient/compute/v2/server.py:781 +#: openstackclient/compute/v2/server.py:788 msgid "Do not over-commit disk on the destination host (default)" msgstr "不準目標主機過量使用(預設)" -#: openstackclient/compute/v2/server.py:787 -#: openstackclient/compute/v2/server.py:1088 +#: openstackclient/compute/v2/server.py:794 +#: openstackclient/compute/v2/server.py:1095 msgid "Wait for resize to complete" msgstr "等待調整容量完成" -#: openstackclient/compute/v2/server.py:815 -#: openstackclient/compute/v2/server.py:1113 +#: openstackclient/compute/v2/server.py:822 +#: openstackclient/compute/v2/server.py:1120 msgid "Complete\n" msgstr "完成\n" -#: openstackclient/compute/v2/server.py:817 +#: openstackclient/compute/v2/server.py:824 msgid "" "\n" "Error migrating server" @@ -286,19 +291,19 @@ msgstr "" "\n" "轉移雲實例出錯" -#: openstackclient/compute/v2/server.py:864 +#: openstackclient/compute/v2/server.py:871 msgid "Perform a hard reboot" msgstr "履行強制重開機" -#: openstackclient/compute/v2/server.py:872 +#: openstackclient/compute/v2/server.py:879 msgid "Perform a soft reboot" msgstr "履行重開機" -#: openstackclient/compute/v2/server.py:877 +#: openstackclient/compute/v2/server.py:884 msgid "Wait for reboot to complete" msgstr "等待重開機完成" -#: openstackclient/compute/v2/server.py:894 +#: openstackclient/compute/v2/server.py:901 msgid "" "\n" "Reboot complete\n" @@ -306,7 +311,7 @@ msgstr "" "\n" "重開機完成\n" -#: openstackclient/compute/v2/server.py:896 +#: openstackclient/compute/v2/server.py:903 msgid "" "\n" "Error rebooting server\n" @@ -314,15 +319,15 @@ msgstr "" "\n" "重開雲實例出錯\n" -#: openstackclient/compute/v2/server.py:916 +#: openstackclient/compute/v2/server.py:923 msgid "Recreate server from this image" msgstr "從此映像檔重建雲實例" -#: openstackclient/compute/v2/server.py:926 +#: openstackclient/compute/v2/server.py:933 msgid "Wait for rebuild to complete" msgstr "等待重建完成" -#: openstackclient/compute/v2/server.py:947 +#: openstackclient/compute/v2/server.py:954 msgid "" "\n" "Complete\n" @@ -330,7 +335,7 @@ msgstr "" "\n" "完成\n" -#: openstackclient/compute/v2/server.py:949 +#: openstackclient/compute/v2/server.py:956 msgid "" "\n" "Error rebuilding server" @@ -338,31 +343,31 @@ msgstr "" "\n" "重建雲實例出錯" -#: openstackclient/compute/v2/server.py:966 +#: openstackclient/compute/v2/server.py:973 msgid "Name or ID of server to use" msgstr "要用的雲實例名稱或識別號" -#: openstackclient/compute/v2/server.py:971 +#: openstackclient/compute/v2/server.py:978 msgid "Name or ID of security group to remove from server" msgstr "要從雲實例移除的安全性群組名稱或識別號" -#: openstackclient/compute/v2/server.py:1007 +#: openstackclient/compute/v2/server.py:1014 msgid "Volume to remove (name or ID)" msgstr "要移除的雲硬碟(名稱或識別號)" -#: openstackclient/compute/v2/server.py:1073 +#: openstackclient/compute/v2/server.py:1080 msgid "Resize server to specified flavor" msgstr "調整雲實例容量來符合指定的虛擬硬體樣板" -#: openstackclient/compute/v2/server.py:1078 +#: openstackclient/compute/v2/server.py:1085 msgid "Confirm server resize is complete" msgstr "確認調整雲實例容量完成" -#: openstackclient/compute/v2/server.py:1083 +#: openstackclient/compute/v2/server.py:1090 msgid "Restore server state before resize" msgstr "恢復雲實例狀態到未調整容量前" -#: openstackclient/compute/v2/server.py:1115 +#: openstackclient/compute/v2/server.py:1122 msgid "" "\n" "Error resizing server" @@ -370,73 +375,73 @@ msgstr "" "\n" "調整雲實例容量時出錯" -#: openstackclient/compute/v2/server.py:1167 +#: openstackclient/compute/v2/server.py:1174 msgid "Set new root password (interactive only)" msgstr "設定新密碼(只限互動)" -#: openstackclient/compute/v2/server.py:1173 +#: openstackclient/compute/v2/server.py:1180 msgid "" "Property to add/change for this server (repeat option to set multiple " "properties)" msgstr "要加入這個雲實例的屬性(重復這選項來設定多個屬性)" -#: openstackclient/compute/v2/server.py:1197 +#: openstackclient/compute/v2/server.py:1204 msgid "New password: " msgstr "新密碼:" -#: openstackclient/compute/v2/server.py:1198 +#: openstackclient/compute/v2/server.py:1205 msgid "Retype new password: " msgstr "重新輸入新密碼:" -#: openstackclient/compute/v2/server.py:1202 +#: openstackclient/compute/v2/server.py:1209 msgid "Passwords do not match, password unchanged" msgstr "密碼不一樣,未更換密碼" -#: openstackclient/compute/v2/server.py:1222 +#: openstackclient/compute/v2/server.py:1229 msgid "Display server diagnostics information" msgstr "顯示雲實例診斷資訊" -#: openstackclient/compute/v2/server.py:1235 +#: openstackclient/compute/v2/server.py:1242 msgid "Error retrieving diagnostics data" msgstr "獲得診斷資料時出錯" -#: openstackclient/compute/v2/server.py:1258 +#: openstackclient/compute/v2/server.py:1265 msgid "Login name (ssh -l option)" msgstr "登入名稱(ssh -l 選項)" -#: openstackclient/compute/v2/server.py:1269 +#: openstackclient/compute/v2/server.py:1276 msgid "Destination port (ssh -p option)" msgstr "目標埠口(ssh -p 選項)" -#: openstackclient/compute/v2/server.py:1281 +#: openstackclient/compute/v2/server.py:1288 msgid "Private key file (ssh -i option)" msgstr "私鑰檔案(ssh -i 選項)" -#: openstackclient/compute/v2/server.py:1292 +#: openstackclient/compute/v2/server.py:1299 msgid "Options in ssh_config(5) format (ssh -o option)" msgstr "ssh_config(5) 格式的選項(ssh -o 選項)" -#: openstackclient/compute/v2/server.py:1306 +#: openstackclient/compute/v2/server.py:1313 msgid "Use only IPv4 addresses" msgstr "只使用 IPv4 位址" -#: openstackclient/compute/v2/server.py:1313 +#: openstackclient/compute/v2/server.py:1320 msgid "Use only IPv6 addresses" msgstr "只使用 IPv6 位址" -#: openstackclient/compute/v2/server.py:1322 +#: openstackclient/compute/v2/server.py:1329 msgid "Use public IP address" msgstr "使用公開 IP 位址" -#: openstackclient/compute/v2/server.py:1330 +#: openstackclient/compute/v2/server.py:1337 msgid "Use private IP address" msgstr "使用私人 IP 位址" -#: openstackclient/compute/v2/server.py:1337 +#: openstackclient/compute/v2/server.py:1344 msgid "Use other IP address (public, private, etc)" msgstr "使用其他 IP 位址(公開、私人等等)" -#: openstackclient/compute/v2/server.py:1514 +#: openstackclient/compute/v2/server.py:1521 msgid "Property key to remove from server (repeat to unset multiple values)" msgstr "要從雲實例上移除的屬性鍵(重復來取消選擇多個值)" From 5649695c658505b0217fb6d03cf199797b90ca4c Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Mon, 2 Mar 2015 17:05:35 -0600 Subject: [PATCH 0025/3095] Add --os-cloud support This adds a new option --os-cloud that allows the configuration values for multiple clouds to be stored in a local file and selected with a single option. Internal option names have had 'os_' removed to be comptible with the options returned from OpenStackConfig().get_one_cloud(). The config file is ~/.config/openstack/clouds.yaml: Sample ------ clouds: devstack: auth: auth_url: http://192.168.122.10:35357/ project_name: demo username: demo password: 0penstack region_name: RegionOne devstack: auth: auth_url: http://192.168.122.10:35357/ project_name: demo username: demo password: 0penstack region_name: RegionOne Co-Authored-By: Monty Taylor Change-Id: I4939acf8067e44ffe06a2e26fc28f1adf8985b7d Depends-On: I45e2550af58aee616ca168d20a557077beeab007 --- examples/common.py | 8 + examples/object_api.py | 17 +- examples/osc-lib.py | 14 +- openstackclient/api/auth.py | 104 +++-- openstackclient/common/clientmanager.py | 20 +- openstackclient/shell.py | 58 ++- .../tests/common/test_clientmanager.py | 83 ++-- openstackclient/tests/test_shell.py | 427 +++++++++--------- requirements.txt | 1 + 9 files changed, 411 insertions(+), 321 deletions(-) diff --git a/examples/common.py b/examples/common.py index 2e7985292b..3b31f582c6 100755 --- a/examples/common.py +++ b/examples/common.py @@ -84,9 +84,17 @@ def base_parser(parser): """ # Global arguments + parser.add_argument( + '--os-cloud', + metavar='', + dest='cloud', + default=env('OS_CLOUD'), + help='Cloud name in clouds.yaml (Env: OS_CLOUD)', + ) parser.add_argument( '--os-region-name', metavar='', + dest='region_name', default=env('OS_REGION_NAME'), help='Authentication region name (Env: OS_REGION_NAME)', ) diff --git a/examples/object_api.py b/examples/object_api.py index 5c6bd9f053..441013cad8 100755 --- a/examples/object_api.py +++ b/examples/object_api.py @@ -30,6 +30,8 @@ from openstackclient.api import object_store_v1 as object_store from openstackclient.identity import client as identity_client +from os_client_config import config as cloud_config + LOG = logging.getLogger('') @@ -37,6 +39,15 @@ def run(opts): """Run the examples""" + # Look for configuration file + # To support token-flow we have no required values + # print "options: %s" % self.options + cloud = cloud_config.OpenStackConfig().get_one_cloud( + cloud=opts.cloud, + argparse=opts, + ) + LOG.debug("cloud cfg: %s", cloud.config) + # Set up certificate verification and CA bundle # NOTE(dtroyer): This converts from the usual OpenStack way to the single # requests argument and is an app-specific thing because @@ -52,13 +63,13 @@ def run(opts): # The returned session will have a configured auth object # based on the selected plugin's available options. # So to do...oh, just go to api.auth.py and look at what it does. - session = common.make_session(opts, verify=verify) + session = common.make_session(cloud, verify=verify) # Extract an endpoint auth_ref = session.auth.get_auth_ref(session) - if opts.os_url: - endpoint = opts.os_url + if opts.url: + endpoint = opts.url else: endpoint = auth_ref.service_catalog.url_for( service_type='object-store', diff --git a/examples/osc-lib.py b/examples/osc-lib.py index 2960a2f74e..84501903c6 100755 --- a/examples/osc-lib.py +++ b/examples/osc-lib.py @@ -29,6 +29,8 @@ from openstackclient.common import clientmanager +from os_client_config import config as cloud_config + LOG = logging.getLogger('') @@ -36,6 +38,16 @@ def run(opts): """Run the examples""" + # Do configuration file handling + cc = cloud_config.OpenStackConfig() + LOG.debug("defaults: %s", cc.defaults) + + cloud = cc.get_one_cloud( + cloud=opts.cloud, + argparse=opts, + ) + LOG.debug("cloud cfg: %s", cloud.config) + # Loop through extensions to get API versions # Currently API versions are statically selected. Once discovery # is working this can go away... @@ -59,7 +71,7 @@ def run(opts): # Collect the auth and config options together and give them to # ClientManager and it will wrangle all of the goons into place. client_manager = clientmanager.ClientManager( - cli_options=opts, + cli_options=cloud, verify=verify, api_version=api_version, ) diff --git a/openstackclient/api/auth.py b/openstackclient/api/auth.py index ffbcb6fce0..9fb26e7100 100644 --- a/openstackclient/api/auth.py +++ b/openstackclient/api/auth.py @@ -77,25 +77,27 @@ def select_auth_plugin(options): auth_plugin_name = None - if options.os_auth_type in [plugin.name for plugin in get_plugin_list()]: - # A direct plugin name was given, use it - return options.os_auth_type - - if options.os_url and options.os_token: + # Do the token/url check first as this must override the default + # 'password' set by os-client-config + # Also, url and token are not copied into o-c-c's auth dict (yet?) + if options.auth.get('url', None) and options.auth.get('token', None): # service token authentication auth_plugin_name = 'token_endpoint' - elif options.os_username: - if options.os_identity_api_version == '3': + elif options.auth_type in [plugin.name for plugin in PLUGIN_LIST]: + # A direct plugin name was given, use it + auth_plugin_name = options.auth_type + elif options.auth.get('username', None): + if options.identity_api_version == '3': auth_plugin_name = 'v3password' - elif options.os_identity_api_version == '2.0': + elif options.identity_api_version.startswith('2'): auth_plugin_name = 'v2password' else: # let keystoneclient figure it out itself auth_plugin_name = 'osc_password' - elif options.os_token: - if options.os_identity_api_version == '3': + elif options.auth.get('token', None): + if options.identity_api_version == '3': auth_plugin_name = 'v3token' - elif options.os_identity_api_version == '2.0': + elif options.identity_api_version.startswith('2'): auth_plugin_name = 'v2token' else: # let keystoneclient figure it out itself @@ -109,35 +111,27 @@ def select_auth_plugin(options): def build_auth_params(auth_plugin_name, cmd_options): - auth_params = {} + + auth_params = dict(cmd_options.auth) if auth_plugin_name: LOG.debug('auth_type: %s', auth_plugin_name) auth_plugin_class = base.get_plugin_class(auth_plugin_name) - plugin_options = auth_plugin_class.get_options() - for option in plugin_options: - option_name = 'os_' + option.dest - LOG.debug('fetching option %s' % option_name) - auth_params[option.dest] = getattr(cmd_options, option_name, None) # grab tenant from project for v2.0 API compatibility if auth_plugin_name.startswith("v2"): - auth_params['tenant_id'] = getattr( - cmd_options, - 'os_project_id', - None, - ) - auth_params['tenant_name'] = getattr( - cmd_options, - 'os_project_name', - None, - ) + if 'project_id' in auth_params: + auth_params['tenant_id'] = auth_params['project_id'] + del auth_params['project_id'] + if 'project_name' in auth_params: + auth_params['tenant_name'] = auth_params['project_name'] + del auth_params['project_name'] else: LOG.debug('no auth_type') # delay the plugin choice, grab every option + auth_plugin_class = None plugin_options = set([o.replace('-', '_') for o in get_options_list()]) for option in plugin_options: - option_name = 'os_' + option - LOG.debug('fetching option %s' % option_name) - auth_params[option] = getattr(cmd_options, option_name, None) + LOG.debug('fetching option %s' % option) + auth_params[option] = getattr(cmd_options.auth, option, None) return (auth_plugin_class, auth_params) @@ -146,15 +140,29 @@ def check_valid_auth_options(options, auth_plugin_name): msg = '' if auth_plugin_name.endswith('password'): - if not options.os_username: - msg += _('Set a username with --os-username or OS_USERNAME\n') - if not options.os_auth_url: - msg += _('Set an authentication URL, with --os-auth-url or' - ' OS_AUTH_URL\n') - if (not options.os_project_id and not options.os_domain_id and not - options.os_domain_name and not options.os_project_name): + if not options.auth.get('username', None): + msg += _('Set a username with --os-username, OS_USERNAME,' + ' or auth.username\n') + if not options.auth.get('auth_url', None): + msg += _('Set an authentication URL, with --os-auth-url,' + ' OS_AUTH_URL or auth.auth_url\n') + if (not options.auth.get('project_id', None) and not + options.auth.get('domain_id', None) and not + options.auth.get('domain_name', None) and not + options.auth.get('project_name', None)): msg += _('Set a scope, such as a project or domain, with ' - '--os-project-name or OS_PROJECT_NAME') + '--os-project-name, OS_PROJECT_NAME or auth.project_name') + elif auth_plugin_name.endswith('token'): + if not options.auth.get('token', None): + msg += _('Set a token with --os-token, OS_TOKEN or auth.token\n') + if not options.auth.get('auth_url', None): + msg += _('Set a service AUTH_URL, with --os-auth-url, ' + 'OS_AUTH_URL or auth.auth_url\n') + elif auth_plugin_name == 'token_endpoint': + if not options.auth.get('token', None): + msg += _('Set a token with --os-token, OS_TOKEN or auth.token\n') + if not options.auth.get('url', None): + msg += _('Set a service URL, with --os-url, OS_URL or auth.url\n') if msg: raise exc.CommandError('Missing parameter(s): \n%s' % msg) @@ -171,6 +179,7 @@ def build_auth_plugins_option_parser(parser): parser.add_argument( '--os-auth-type', metavar='', + dest='auth_type', default=utils.env('OS_AUTH_TYPE'), help='Select an auhentication type. Available types: ' + ', '.join(available_plugins) + @@ -178,7 +187,7 @@ def build_auth_plugins_option_parser(parser): ' (Env: OS_AUTH_TYPE)', choices=available_plugins ) - # make sure we catch old v2.0 env values + # Maintain compatibility with old tenant env vars envs = { 'OS_PROJECT_NAME': utils.env( 'OS_PROJECT_NAME', @@ -190,15 +199,20 @@ def build_auth_plugins_option_parser(parser): ), } for o in get_options_list(): - # remove allusion to tenants from v2.0 API + # Remove tenant options from KSC plugins and replace them below if 'tenant' not in o: parser.add_argument( '--os-' + o, metavar='' % o, - default=envs.get(OPTIONS_LIST[o]['env'], - utils.env(OPTIONS_LIST[o]['env'])), - help='%s\n(Env: %s)' % (OPTIONS_LIST[o]['help'], - OPTIONS_LIST[o]['env']), + dest=o.replace('-', '_'), + default=envs.get( + OPTIONS_LIST[o]['env'], + utils.env(OPTIONS_LIST[o]['env']), + ), + help='%s\n(Env: %s)' % ( + OPTIONS_LIST[o]['help'], + OPTIONS_LIST[o]['env'], + ), ) # add tenant-related options for compatibility # this is deprecated but still used in some tempest tests... @@ -206,14 +220,12 @@ def build_auth_plugins_option_parser(parser): '--os-tenant-name', metavar='', dest='os_project_name', - default=utils.env('OS_TENANT_NAME'), help=argparse.SUPPRESS, ) parser.add_argument( '--os-tenant-id', metavar='', dest='os_project_id', - default=utils.env('OS_TENANT_ID'), help=argparse.SUPPRESS, ) return parser diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index 6d2a9d76b7..ca5ece0d75 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -58,7 +58,7 @@ def __getattr__(self, name): def __init__( self, - cli_options, + cli_options=None, api_version=None, verify=True, pw_func=None, @@ -82,8 +82,8 @@ def __init__( self._cli_options = cli_options self._api_version = api_version self._pw_callback = pw_func - self._url = self._cli_options.os_url - self._region_name = self._cli_options.os_region_name + self._url = self._cli_options.auth.get('url', None) + self._region_name = self._cli_options.region_name self.timing = self._cli_options.timing @@ -121,7 +121,7 @@ def setup_auth(self): # Horrible hack alert...must handle prompt for null password if # password auth is requested. if (self.auth_plugin_name.endswith('password') and - not self._cli_options.os_password): + not self._cli_options.auth.get('password', None)): self._cli_options.os_password = self._pw_callback() (auth_plugin, self._auth_params) = auth.build_auth_params( @@ -129,13 +129,15 @@ def setup_auth(self): self._cli_options, ) - default_domain = self._cli_options.os_default_domain + # TODO(mordred): This is a usability improvement that's broadly useful + # We should port it back up into os-client-config. + default_domain = self._cli_options.default_domain # NOTE(stevemar): If PROJECT_DOMAIN_ID or PROJECT_DOMAIN_NAME is # present, then do not change the behaviour. Otherwise, set the # PROJECT_DOMAIN_ID to 'OS_DEFAULT_DOMAIN' for better usability. if (self._api_version.get('identity') == '3' and - not self._auth_params.get('project_domain_id') and - not self._auth_params.get('project_domain_name')): + not self._auth_params.get('project_domain_id', None) and + not self._auth_params.get('project_domain_name', None)): self._auth_params['project_domain_id'] = default_domain # NOTE(stevemar): If USER_DOMAIN_ID or USER_DOMAIN_NAME is present, @@ -143,8 +145,8 @@ def setup_auth(self): # to 'OS_DEFAULT_DOMAIN' for better usability. if (self._api_version.get('identity') == '3' and self.auth_plugin_name.endswith('password') and - not self._auth_params.get('user_domain_id') and - not self._auth_params.get('user_domain_name')): + not self._auth_params.get('user_domain_id', None) and + not self._auth_params.get('user_domain_name', None)): self._auth_params['user_domain_id'] = default_domain # For compatibility until all clients can be updated diff --git a/openstackclient/shell.py b/openstackclient/shell.py index f4e3b7e51b..00f4a3c96f 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -33,6 +33,8 @@ from openstackclient.common import timing from openstackclient.common import utils +from os_client_config import config as cloud_config + DEFAULT_DOMAIN = 'default' @@ -86,10 +88,6 @@ def __init__(self): # Until we have command line arguments parsed, dump any stack traces self.dump_stack_trace = True - # This is instantiated in initialize_app() only when using - # password flow auth - self.auth_client = None - # Assume TLS host certificate verification is enabled self.verify = True @@ -165,10 +163,19 @@ def build_option_parser(self, description, version): description, version) + # service token auth argument + parser.add_argument( + '--os-cloud', + metavar='', + dest='cloud', + default=utils.env('OS_CLOUD'), + help='Cloud name in clouds.yaml (Env: OS_CLOUD)', + ) # Global arguments parser.add_argument( '--os-region-name', metavar='', + dest='region_name', default=utils.env('OS_REGION_NAME'), help='Authentication region name (Env: OS_REGION_NAME)') parser.add_argument( @@ -213,8 +220,43 @@ def initialize_app(self, argv): * authenticate against Identity if requested """ + # Parent __init__ parses argv into self.options super(OpenStackShell, self).initialize_app(argv) + # Resolve the verify/insecure exclusive pair here as cloud_config + # doesn't know about verify + self.options.insecure = ( + self.options.insecure and not self.options.verify + ) + + # Set the default plugin to token_endpoint if rl and token are given + if (self.options.url and self.options.token): + # Use service token authentication + cloud_config.set_default('auth_type', 'token_endpoint') + else: + cloud_config.set_default('auth_type', 'osc_password') + self.log.debug("options: %s", self.options) + + # Do configuration file handling + cc = cloud_config.OpenStackConfig() + self.log.debug("defaults: %s", cc.defaults) + + self.cloud = cc.get_one_cloud( + cloud=self.options.cloud, + argparse=self.options, + ) + self.log.debug("cloud cfg: %s", self.cloud.config) + + # Set up client TLS + cacert = self.cloud.cacert + if cacert: + self.verify = cacert + else: + self.verify = not getattr(self.cloud.config, 'insecure', False) + + # Neutralize verify option + self.options.verify = None + # Save default domain self.default_domain = self.options.os_default_domain @@ -260,14 +302,8 @@ def initialize_app(self, argv): # Handle deferred help and exit self.print_help_if_requested() - # Set up common client session - if self.options.os_cacert: - self.verify = self.options.os_cacert - else: - self.verify = not self.options.insecure - self.client_manager = clientmanager.ClientManager( - cli_options=self.options, + cli_options=self.cloud, verify=self.verify, api_version=self.api_version, pw_func=prompt_for_password, diff --git a/openstackclient/tests/common/test_clientmanager.py b/openstackclient/tests/common/test_clientmanager.py index 7a2f57bef6..26cf49679d 100644 --- a/openstackclient/tests/common/test_clientmanager.py +++ b/openstackclient/tests/common/test_clientmanager.py @@ -48,13 +48,14 @@ def __init__(self): class FakeOptions(object): def __init__(self, **kwargs): for option in auth.OPTIONS_LIST: - setattr(self, 'os_' + option.replace('-', '_'), None) - self.os_auth_type = None - self.os_identity_api_version = '2.0' + setattr(self, option.replace('-', '_'), None) + self.auth_type = None + self.identity_api_version = '2.0' self.timing = None - self.os_region_name = None - self.os_url = None - self.os_default_domain = 'default' + self.region_name = None + self.url = None + self.auth = {} + self.default_domain = 'default' self.__dict__.update(kwargs) @@ -86,9 +87,11 @@ def test_client_manager_token_endpoint(self): client_manager = clientmanager.ClientManager( cli_options=FakeOptions( - os_token=fakes.AUTH_TOKEN, - os_url=fakes.AUTH_URL, - os_auth_type='token_endpoint', + auth_type='token_endpoint', + auth=dict( + token=fakes.AUTH_TOKEN, + url=fakes.AUTH_URL, + ), ), api_version=API_VERSION, verify=True @@ -114,9 +117,11 @@ def test_client_manager_token(self): client_manager = clientmanager.ClientManager( cli_options=FakeOptions( - os_token=fakes.AUTH_TOKEN, - os_auth_url=fakes.AUTH_URL, - os_auth_type='v2token', + auth=dict( + token=fakes.AUTH_TOKEN, + auth_url=fakes.AUTH_URL, + ), + auth_type='v2token', ), api_version=API_VERSION, verify=True @@ -138,10 +143,12 @@ def test_client_manager_password(self): client_manager = clientmanager.ClientManager( cli_options=FakeOptions( - os_auth_url=fakes.AUTH_URL, - os_username=fakes.USERNAME, - os_password=fakes.PASSWORD, - os_project_name=fakes.PROJECT_NAME, + auth=dict( + auth_url=fakes.AUTH_URL, + username=fakes.USERNAME, + password=fakes.PASSWORD, + project_name=fakes.PROJECT_NAME, + ), ), api_version=API_VERSION, verify=False, @@ -198,11 +205,13 @@ def test_client_manager_password_verify_ca(self): client_manager = clientmanager.ClientManager( cli_options=FakeOptions( - os_auth_url=fakes.AUTH_URL, - os_username=fakes.USERNAME, - os_password=fakes.PASSWORD, - os_project_name=fakes.PROJECT_NAME, - os_auth_type='v2password', + auth=dict( + auth_url=fakes.AUTH_URL, + username=fakes.USERNAME, + password=fakes.PASSWORD, + project_name=fakes.PROJECT_NAME, + ), + auth_type='v2password', ), api_version=API_VERSION, verify='cafile', @@ -214,8 +223,8 @@ def test_client_manager_password_verify_ca(self): self.assertEqual('cafile', client_manager._cacert) def _select_auth_plugin(self, auth_params, api_version, auth_plugin_name): - auth_params['os_auth_type'] = auth_plugin_name - auth_params['os_identity_api_version'] = api_version + auth_params['auth_type'] = auth_plugin_name + auth_params['identity_api_version'] = api_version client_manager = clientmanager.ClientManager( cli_options=FakeOptions(**auth_params), api_version=API_VERSION, @@ -230,19 +239,33 @@ def _select_auth_plugin(self, auth_params, api_version, auth_plugin_name): def test_client_manager_select_auth_plugin(self): # test token auth - params = dict(os_token=fakes.AUTH_TOKEN, - os_auth_url=fakes.AUTH_URL) + params = dict( + auth=dict( + auth_url=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN, + ), + ) self._select_auth_plugin(params, '2.0', 'v2token') self._select_auth_plugin(params, '3', 'v3token') self._select_auth_plugin(params, 'XXX', 'token') # test token/endpoint auth - params = dict(os_token=fakes.AUTH_TOKEN, os_url='test') + params = dict( + auth_plugin='token_endpoint', + auth=dict( + url='test', + token=fakes.AUTH_TOKEN, + ), + ) self._select_auth_plugin(params, 'XXX', 'token_endpoint') # test password auth - params = dict(os_auth_url=fakes.AUTH_URL, - os_username=fakes.USERNAME, - os_password=fakes.PASSWORD, - os_project_name=fakes.PROJECT_NAME) + params = dict( + auth=dict( + auth_url=fakes.AUTH_URL, + username=fakes.USERNAME, + password=fakes.PASSWORD, + project_name=fakes.PROJECT_NAME, + ), + ) self._select_auth_plugin(params, '2.0', 'v2password') self._select_auth_plugin(params, '3', 'v3password') self._select_auth_plugin(params, 'XXX', 'password') diff --git a/openstackclient/tests/test_shell.py b/openstackclient/tests/test_shell.py index f1043072dc..a43be95413 100644 --- a/openstackclient/tests/test_shell.py +++ b/openstackclient/tests/test_shell.py @@ -82,34 +82,62 @@ def _assert_password_auth(self, cmd_options, default_args): fake_execute(_shell, _cmd) self.app.assert_called_with(["list", "project"]) - self.assertEqual(default_args["auth_url"], - _shell.options.os_auth_url) - self.assertEqual(default_args["project_id"], - _shell.options.os_project_id) - self.assertEqual(default_args["project_name"], - _shell.options.os_project_name) - self.assertEqual(default_args["domain_id"], - _shell.options.os_domain_id) - self.assertEqual(default_args["domain_name"], - _shell.options.os_domain_name) - self.assertEqual(default_args["user_domain_id"], - _shell.options.os_user_domain_id) - self.assertEqual(default_args["user_domain_name"], - _shell.options.os_user_domain_name) - self.assertEqual(default_args["project_domain_id"], - _shell.options.os_project_domain_id) - self.assertEqual(default_args["project_domain_name"], - _shell.options.os_project_domain_name) - self.assertEqual(default_args["username"], - _shell.options.os_username) - self.assertEqual(default_args["password"], - _shell.options.os_password) - self.assertEqual(default_args["region_name"], - _shell.options.os_region_name) - self.assertEqual(default_args["trust_id"], - _shell.options.os_trust_id) - self.assertEqual(default_args['auth_type'], - _shell.options.os_auth_type) + self.assertEqual( + default_args.get("auth_url", ''), + _shell.options.auth_url, + ) + self.assertEqual( + default_args.get("project_id", ''), + _shell.options.project_id, + ) + self.assertEqual( + default_args.get("project_name", ''), + _shell.options.project_name, + ) + self.assertEqual( + default_args.get("domain_id", ''), + _shell.options.domain_id, + ) + self.assertEqual( + default_args.get("domain_name", ''), + _shell.options.domain_name, + ) + self.assertEqual( + default_args.get("user_domain_id", ''), + _shell.options.user_domain_id, + ) + self.assertEqual( + default_args.get("user_domain_name", ''), + _shell.options.user_domain_name, + ) + self.assertEqual( + default_args.get("project_domain_id", ''), + _shell.options.project_domain_id, + ) + self.assertEqual( + default_args.get("project_domain_name", ''), + _shell.options.project_domain_name, + ) + self.assertEqual( + default_args.get("username", ''), + _shell.options.username, + ) + self.assertEqual( + default_args.get("password", ''), + _shell.options.password, + ) + self.assertEqual( + default_args.get("region_name", ''), + _shell.options.region_name, + ) + self.assertEqual( + default_args.get("trust_id", ''), + _shell.options.trust_id, + ) + self.assertEqual( + default_args.get('auth_type', ''), + _shell.options.auth_type, + ) def _assert_token_auth(self, cmd_options, default_args): with mock.patch("openstackclient.shell.OpenStackShell.initialize_app", @@ -118,9 +146,34 @@ def _assert_token_auth(self, cmd_options, default_args): fake_execute(_shell, _cmd) self.app.assert_called_with(["list", "role"]) - self.assertEqual(default_args["os_token"], _shell.options.os_token) - self.assertEqual(default_args["os_auth_url"], - _shell.options.os_auth_url) + self.assertEqual( + default_args.get("token", ''), + _shell.options.token, + "token" + ) + self.assertEqual( + default_args.get("auth_url", ''), + _shell.options.auth_url, + "auth_url" + ) + + def _assert_token_endpoint_auth(self, cmd_options, default_args): + with mock.patch("openstackclient.shell.OpenStackShell.initialize_app", + self.app): + _shell, _cmd = make_shell(), cmd_options + " list role" + fake_execute(_shell, _cmd) + + self.app.assert_called_with(["list", "role"]) + self.assertEqual( + default_args.get("token", ''), + _shell.options.token, + "token", + ) + self.assertEqual( + default_args.get("url", ''), + _shell.options.url, + "url", + ) def _assert_cli(self, cmd_options, default_args): with mock.patch("openstackclient.shell.OpenStackShell.initialize_app", @@ -178,301 +231,233 @@ def test_only_url_flow(self): flag = "--os-auth-url " + DEFAULT_AUTH_URL kwargs = { "auth_url": DEFAULT_AUTH_URL, - "project_id": "", - "project_name": "", - "user_domain_id": "", - "domain_id": "", - "domain_name": "", - "user_domain_name": "", - "project_domain_id": "", - "project_domain_name": "", - "username": "", - "password": "", - "region_name": "", - "trust_id": "", - "auth_type": "", } self._assert_password_auth(flag, kwargs) def test_only_project_id_flow(self): flag = "--os-project-id " + DEFAULT_PROJECT_ID kwargs = { - "auth_url": "", "project_id": DEFAULT_PROJECT_ID, - "project_name": "", - "domain_id": "", - "domain_name": "", - "user_domain_id": "", - "user_domain_name": "", - "project_domain_id": "", - "project_domain_name": "", - "username": "", - "password": "", - "region_name": "", - "trust_id": "", - "auth_type": "", } self._assert_password_auth(flag, kwargs) def test_only_project_name_flow(self): flag = "--os-project-name " + DEFAULT_PROJECT_NAME kwargs = { - "auth_url": "", - "project_id": "", "project_name": DEFAULT_PROJECT_NAME, - "domain_id": "", - "domain_name": "", - "user_domain_id": "", - "user_domain_name": "", - "project_domain_id": "", - "project_domain_name": "", - "username": "", - "password": "", - "region_name": "", - "trust_id": "", - "auth_type": "", } self._assert_password_auth(flag, kwargs) def test_only_domain_id_flow(self): flag = "--os-domain-id " + DEFAULT_DOMAIN_ID kwargs = { - "auth_url": "", - "project_id": "", - "project_name": "", "domain_id": DEFAULT_DOMAIN_ID, - "domain_name": "", - "user_domain_id": "", - "user_domain_name": "", - "project_domain_id": "", - "project_domain_name": "", - "username": "", - "password": "", - "region_name": "", - "trust_id": "", - "auth_type": "", } self._assert_password_auth(flag, kwargs) def test_only_domain_name_flow(self): flag = "--os-domain-name " + DEFAULT_DOMAIN_NAME kwargs = { - "auth_url": "", - "project_id": "", - "project_name": "", - "domain_id": "", "domain_name": DEFAULT_DOMAIN_NAME, - "user_domain_id": "", - "user_domain_name": "", - "project_domain_id": "", - "project_domain_name": "", - "username": "", - "password": "", - "region_name": "", - "trust_id": "", - "auth_type": "", } self._assert_password_auth(flag, kwargs) def test_only_user_domain_id_flow(self): flag = "--os-user-domain-id " + DEFAULT_USER_DOMAIN_ID kwargs = { - "auth_url": "", - "project_id": "", - "project_name": "", - "domain_id": "", - "domain_name": "", "user_domain_id": DEFAULT_USER_DOMAIN_ID, - "user_domain_name": "", - "project_domain_id": "", - "project_domain_name": "", - "username": "", - "password": "", - "region_name": "", - "trust_id": "", - "auth_type": "", } self._assert_password_auth(flag, kwargs) def test_only_user_domain_name_flow(self): flag = "--os-user-domain-name " + DEFAULT_USER_DOMAIN_NAME kwargs = { - "auth_url": "", - "project_id": "", - "project_name": "", - "domain_id": "", - "domain_name": "", - "user_domain_id": "", "user_domain_name": DEFAULT_USER_DOMAIN_NAME, - "project_domain_id": "", - "project_domain_name": "", - "username": "", - "password": "", - "region_name": "", - "trust_id": "", - "auth_type": "", } self._assert_password_auth(flag, kwargs) def test_only_project_domain_id_flow(self): flag = "--os-project-domain-id " + DEFAULT_PROJECT_DOMAIN_ID kwargs = { - "auth_url": "", - "project_id": "", - "project_name": "", - "domain_id": "", - "domain_name": "", - "user_domain_id": "", - "user_domain_name": "", "project_domain_id": DEFAULT_PROJECT_DOMAIN_ID, - "project_domain_name": "", - "username": "", - "password": "", - "region_name": "", - "trust_id": "", - "auth_type": "", } self._assert_password_auth(flag, kwargs) def test_only_project_domain_name_flow(self): flag = "--os-project-domain-name " + DEFAULT_PROJECT_DOMAIN_NAME kwargs = { - "auth_url": "", - "project_id": "", - "project_name": "", - "domain_id": "", - "domain_name": "", - "user_domain_id": "", - "user_domain_name": "", - "project_domain_id": "", "project_domain_name": DEFAULT_PROJECT_DOMAIN_NAME, - "username": "", - "password": "", - "region_name": "", - "trust_id": "", - "auth_type": "", } self._assert_password_auth(flag, kwargs) def test_only_username_flow(self): flag = "--os-username " + DEFAULT_USERNAME kwargs = { - "auth_url": "", - "project_id": "", - "project_name": "", - "domain_id": "", - "domain_name": "", - "user_domain_id": "", - "user_domain_name": "", - "project_domain_id": "", - "project_domain_name": "", "username": DEFAULT_USERNAME, - "password": "", - "region_name": "", - "trust_id": "", - "auth_type": "", } self._assert_password_auth(flag, kwargs) def test_only_password_flow(self): flag = "--os-password " + DEFAULT_PASSWORD kwargs = { - "auth_url": "", - "project_id": "", - "project_name": "", - "domain_id": "", - "domain_name": "", - "user_domain_id": "", - "user_domain_name": "", - "project_domain_id": "", - "project_domain_name": "", - "username": "", "password": DEFAULT_PASSWORD, - "region_name": "", - "trust_id": "", - "auth_type": "", } self._assert_password_auth(flag, kwargs) def test_only_region_name_flow(self): flag = "--os-region-name " + DEFAULT_REGION_NAME kwargs = { - "auth_url": "", - "project_id": "", - "project_name": "", - "domain_id": "", - "domain_name": "", - "user_domain_id": "", - "user_domain_name": "", - "project_domain_id": "", - "project_domain_name": "", - "username": "", - "password": "", "region_name": DEFAULT_REGION_NAME, - "trust_id": "", - "auth_type": "", } self._assert_password_auth(flag, kwargs) def test_only_trust_id_flow(self): flag = "--os-trust-id " + "1234" kwargs = { - "auth_url": "", - "project_id": "", - "project_name": "", - "domain_id": "", - "domain_name": "", - "user_domain_id": "", - "user_domain_name": "", - "project_domain_id": "", - "project_domain_name": "", - "username": "", - "password": "", - "region_name": "", "trust_id": "1234", - "auth_type": "", } self._assert_password_auth(flag, kwargs) def test_only_auth_type_flow(self): flag = "--os-auth-type " + "v2password" kwargs = { - "auth_url": "", - "project_id": "", - "project_name": "", - "domain_id": "", - "domain_name": "", - "user_domain_id": "", - "user_domain_name": "", - "project_domain_id": "", - "project_domain_name": "", - "username": "", - "password": "", - "region_name": "", - "trust_id": "", "auth_type": DEFAULT_AUTH_PLUGIN } self._assert_password_auth(flag, kwargs) class TestShellTokenAuth(TestShell): + def test_only_token(self): + flag = "--os-token " + DEFAULT_TOKEN + kwargs = { + "token": DEFAULT_TOKEN, + "auth_url": '', + } + self._assert_token_auth(flag, kwargs) + + def test_only_auth_url(self): + flag = "--os-auth-url " + DEFAULT_AUTH_URL + kwargs = { + "token": '', + "auth_url": DEFAULT_AUTH_URL, + } + self._assert_token_auth(flag, kwargs) + + def test_empty_auth(self): + os.environ = {} + flag = "" + kwargs = {} + self._assert_token_auth(flag, kwargs) + + +class TestShellTokenAuthEnv(TestShell): def setUp(self): - super(TestShellTokenAuth, self).setUp() + super(TestShellTokenAuthEnv, self).setUp() env = { "OS_TOKEN": DEFAULT_TOKEN, - "OS_AUTH_URL": DEFAULT_SERVICE_URL, + "OS_AUTH_URL": DEFAULT_AUTH_URL, } self.orig_env, os.environ = os.environ, env.copy() def tearDown(self): - super(TestShellTokenAuth, self).tearDown() + super(TestShellTokenAuthEnv, self).tearDown() os.environ = self.orig_env - def test_default_auth(self): + def test_env(self): flag = "" kwargs = { - "os_token": DEFAULT_TOKEN, - "os_auth_url": DEFAULT_SERVICE_URL + "token": DEFAULT_TOKEN, + "auth_url": DEFAULT_AUTH_URL, + } + self._assert_token_auth(flag, kwargs) + + def test_only_token(self): + flag = "--os-token xyzpdq" + kwargs = { + "token": "xyzpdq", + "auth_url": DEFAULT_AUTH_URL, + } + self._assert_token_auth(flag, kwargs) + + def test_only_auth_url(self): + flag = "--os-auth-url http://cloud.local:555" + kwargs = { + "token": DEFAULT_TOKEN, + "auth_url": "http://cloud.local:555", + } + self._assert_token_auth(flag, kwargs) + + def test_empty_auth(self): + os.environ = {} + flag = "" + kwargs = { + "token": '', + "auth_url": '', + } + self._assert_token_auth(flag, kwargs) + + +class TestShellTokenEndpointAuth(TestShell): + def test_only_token(self): + flag = "--os-token " + DEFAULT_TOKEN + kwargs = { + "token": DEFAULT_TOKEN, + "url": '', + } + self._assert_token_endpoint_auth(flag, kwargs) + + def test_only_url(self): + flag = "--os-url " + DEFAULT_SERVICE_URL + kwargs = { + "token": '', + "url": DEFAULT_SERVICE_URL, + } + self._assert_token_endpoint_auth(flag, kwargs) + + def test_empty_auth(self): + os.environ = {} + flag = "" + kwargs = { + "token": '', + "auth_url": '', + } + self._assert_token_endpoint_auth(flag, kwargs) + + +class TestShellTokenEndpointAuthEnv(TestShell): + def setUp(self): + super(TestShellTokenEndpointAuthEnv, self).setUp() + env = { + "OS_TOKEN": DEFAULT_TOKEN, + "OS_URL": DEFAULT_SERVICE_URL, + } + self.orig_env, os.environ = os.environ, env.copy() + + def tearDown(self): + super(TestShellTokenEndpointAuthEnv, self).tearDown() + os.environ = self.orig_env + + def test_env(self): + flag = "" + kwargs = { + "token": DEFAULT_TOKEN, + "url": DEFAULT_SERVICE_URL, + } + self._assert_token_auth(flag, kwargs) + + def test_only_token(self): + flag = "--os-token xyzpdq" + kwargs = { + "token": "xyzpdq", + "url": DEFAULT_SERVICE_URL, + } + self._assert_token_auth(flag, kwargs) + + def test_only_url(self): + flag = "--os-url http://cloud.local:555" + kwargs = { + "token": DEFAULT_TOKEN, + "url": "http://cloud.local:555", } self._assert_token_auth(flag, kwargs) @@ -480,8 +465,8 @@ def test_empty_auth(self): os.environ = {} flag = "" kwargs = { - "os_token": "", - "os_auth_url": "" + "token": '', + "url": '', } self._assert_token_auth(flag, kwargs) diff --git a/requirements.txt b/requirements.txt index 53d409eb27..b8712eb8da 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,6 +7,7 @@ six>=1.9.0 Babel>=1.3 cliff>=1.10.0 # Apache-2.0 cliff-tablib>=1.0 +os-client-config oslo.config>=1.9.3 # Apache-2.0 oslo.i18n>=1.5.0 # Apache-2.0 oslo.utils>=1.4.0 # Apache-2.0 From 6e7013954d4119c91345a5b83b9675c4be62a11d Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Tue, 3 Mar 2015 23:12:07 -0600 Subject: [PATCH 0026/3095] Begin documenting --os-cloud Change-Id: Id2e98ac5601840f6d380cabcd578f1a6d6d9b245 --- doc/source/authentication.rst | 7 +- doc/source/configuration.rst | 133 ++++++++++++++++++++++++++++++++++ doc/source/index.rst | 1 + doc/source/man/openstack.rst | 99 ++++++++++++++++++++++++- 4 files changed, 236 insertions(+), 4 deletions(-) create mode 100644 doc/source/configuration.rst diff --git a/doc/source/authentication.rst b/doc/source/authentication.rst index fd48a0312a..bf23b66a01 100644 --- a/doc/source/authentication.rst +++ b/doc/source/authentication.rst @@ -62,8 +62,11 @@ by the ``ClientManager`` object. plugins from the ``keystoneclient.auth.plugin`` entry point. * builds a list of authentication options from the plugins. +* The command line arguments are processed and a configuration is loaded from + :file:`clouds.yaml` if ``--os-cloud`` is provided. + * A new ``ClientManager`` is created and supplied with the set of options from the - command line and/or environment: + command line, environment and/or :file:`clouds.yaml`: * If ``--os-auth-type`` is provided and is a valid and available plugin it is used. @@ -71,7 +74,7 @@ by the ``ClientManager`` object. is selected based on the existing options. This is a short-circuit evaluation, the first match wins. - * If ``--os-endpoint`` and ``--os-token`` are both present ``token_endpoint`` + * If ``--os-url`` and ``--os-token`` are both present ``token_endpoint`` is selected * If ``--os-username`` is supplied ``password`` is selected * If ``--os-token`` is supplied ``token`` is selected diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst new file mode 100644 index 0000000000..e371e50448 --- /dev/null +++ b/doc/source/configuration.rst @@ -0,0 +1,133 @@ +============= +Configuration +============= + +OpenStackClient is primarily configured using command line options and environment +variables. Most of those settings can also be placed into a configuration file to +simplify managing multiple cloud configurations. + +There is a relationship between the global options, environment variables and +keywords used in the configuration files that should make translation between +these three areas simple. + +Most global options have a corresponding environment variable that may also be +used to set the value. If both are present, the command-line option takes priority. +The environment variable names are derived from the option name by dropping the +leading dashes (--), converting each embedded dash (-) to an underscore (_), and +converting to upper case. + +The keyword names in the configurations files are derived from the global option +names by dropping the ``--os-`` prefix if present. + +Global Options +-------------- + +The :doc:`openstack manpage ` lists all of the global +options recognized by OpenStackClient and the default authentication plugins. + +Environment Variables +--------------------- + +The :doc:`openstack manpage ` also lists all of the +environment variables recognized by OpenStackClient and the default +authentication plugins. + +Configuration Files +------------------- + +clouds.yaml +~~~~~~~~~~~ + +:file:`clouds.yaml` is a configuration file that contains everything needed +to connect to one or more clouds. It may contain private information and +is generally considered private to a user. + +OpenStackClient looks for a file called :file:`clouds.yaml` in the following +locations: + +* current directory +* :file:`~/.config/openstack` +* :file:`/etc/openstack` + +The first file found wins. + +The keys match the :program:`openstack` global options but without the +``--os-`` prefix. + +:: + + clouds: + devstack: + auth: + auth_url: http://192.168.122.10:35357/ + project_name: demo + username: demo + password: 0penstack + region_name: RegionOne + ds-admin: + auth: + auth_url: http://192.168.122.10:35357/ + project_name: admin + username: admin + password: 0penstack + region_name: RegionOne + infra: + cloud: rackspace + auth: + project_id: 275610 + username: openstack + password: xyzpdq!lazydog + region_name: DFW,ORD,IAD + +In the above example, the ``auth_url`` for the ``rackspace`` cloud is taken +from :file:`clouds-public.yaml` (see below). + +The first two entries are for two of the default users of the same DevStack +cloud. + +The third entry is for a Rackspace Cloud Servers account. It is equivalent +to the following options if the ``rackspace`` entry in :file:`clouds-public.yaml` +(below) is present: + +:: + + --os-auth-url https://identity.api.rackspacecloud.com/v2.0/ + --os-project-id 275610 + --os-username openstack + --os-password xyzpdq!lazydog + --os-region-name DFW + +and can be selected on the command line:: + + openstack --os-cloud infra server list + +Note that multiple regions are listed in the ``rackspace`` entry. An otherwise +identical configuration is created for each region. If ``-os-region-name`` is not +specified on the command line, the first region in the list is used by default. + +clouds-public.yaml +~~~~~~~~~~~~~~~~~~ + +:file:`clouds-public.yaml` is a configuration file that is intended to contain +public information about clouds that are common across a large number of users. +The idea is that :file:`clouds-public.yaml` could easily be shared among users +to simplify public could configuration. + +Similar to :file:`clouds.yaml`, OpenStackClient looks for +:file:`clouds-public.yaml` in the following locations: + +* current directory +* :file:`~/.config/openstack` +* :file:`/etc/openstack` + +The first file found wins. + +The keys here are referenced in :file:`clouds.yaml` ``cloud`` keys. Anything +that appears in :file:`clouds.yaml` + +:: + + public-clouds: + rackspace: + auth: + auth_url: 'https://identity.api.rackspacecloud.com/v2.0/' diff --git a/doc/source/index.rst b/doc/source/index.rst index 60a43d45be..7258a7b4cc 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -13,6 +13,7 @@ Contents: command-list commands + configuration plugins authentication interactive diff --git a/doc/source/man/openstack.rst b/doc/source/man/openstack.rst index a6de54db09..6d6dce4479 100644 --- a/doc/source/man/openstack.rst +++ b/doc/source/man/openstack.rst @@ -4,6 +4,7 @@ OpenStack Command Line + SYNOPSIS ======== @@ -57,8 +58,15 @@ OPTIONS :program:`openstack` recognizes the following global options: -:option:`--os-auth-plugin` - The authentication plugin to use when connecting to the Identity service. If this option is not set, :program:`openstack` will attempt to guess the authentication method to use based on the other options. +:option:`--os-cloud` + :program:`openstack` will look for a ``clouds.yaml`` file that contains + a cloud configuration to use for authentication. See CLOUD CONFIGURATION + below for more information. + +:option:`--os-auth-type` + The authentication plugin type to use when connecting to the Identity service. + If this option is not set, :program:`openstack` will attempt to guess the + authentication method to use based on the other options. If this option is set, its version must match :option:`--os-identity-api-version` :option:`--os-auth-url` @@ -156,6 +164,81 @@ Command Actions The actions used by OpenStackClient are defined with specific meaning to provide a consistent behavior for each object. Some actions have logical opposite actions, and those pairs will always match for any object that uses them. + +CLOUD CONFIGURATION +=================== + +Working with multiple clouds can be simplified by keeping the configuration +information for those clouds in a local file. :program:`openstack` supports +using a ``clouds.yaml`` configuration file. + +Config Files +------------ + +:program:`openstack` will look for a file called clouds.yaml in the following +locations: + +* Current Directory +* ~/.config/openstack +* /etc/openstack + +The first file found wins. + +The keys match the :program:`openstack` global options but without the +``--os-`` prefix: + +:: + + clouds: + devstack: + auth: + auth_url: http://192.168.122.10:35357/ + project_name: demo + username: demo + password: 0penstack + region_name: RegionOne + ds-admin: + auth: + auth_url: http://192.168.122.10:35357/ + project_name: admin + username: admin + password: 0penstack + region_name: RegionOne + infra: + cloud: rackspace + auth: + project_id: 275610 + username: openstack + password: xyzpdq!lazydog + region_name: DFW,ORD,IAD + +In the above example, the ``auth_url`` for the ``rackspace`` cloud is taken +from :file:`clouds-public.yaml`: + +:: + + public-clouds: + rackspace: + auth: + auth_url: 'https://identity.api.rackspacecloud.com/v2.0/' + +Authentication Settings +----------------------- + +OpenStackClient uses the Keystone authentication plugins so the required +auth settings are not always known until the authentication type is +selected. :program:`openstack` will attempt to detect a couple of common +auth types based on the arguments passed in or found in the configuration +file, but if those are incomplete it may be impossible to know which +auth type is intended. The :option:`--os-auth-type` option can always be +used to force a specific type. + +When :option:`--os-token` and :option:`--os-url` are both present the +``token_endpoint`` auth type is selected automatically. If +:option:`--os-auth-url` and :option:`--os-username` are present ``password`` +auth type is selected. + + NOTES ===== @@ -192,6 +275,15 @@ Create a new image:: FILES ===== +:file:`~/.config/openstack/clouds.yaml` + Configuration file used by the :option:`--os-cloud` global option. + +:file:`~/.config/openstack/clouds-public.yaml` + Configuration file containing public cloud provider information such as + authentication URLs and service definitions. The contents of this file + should be public and sharable. ``clouds.yaml`` may contain references + to clouds defined here as shortcuts. + :file:`~/.openstack` Placeholder for future local state directory. This directory is intended to be shared among multiple OpenStack-related applications; contents are namespaced with an identifier for the app that owns it. Shared contents (such as :file:`~/.openstack/cache`) have no prefix and the contents must be portable. @@ -201,6 +293,9 @@ ENVIRONMENT VARIABLES The following environment variables can be set to alter the behaviour of :program:`openstack`. Most of them have corresponding command-line options that take precedence if set. +:envvar:`OS_CLOUD` + The name of a cloud configuration in ``clouds.yaml``. + :envvar:`OS_AUTH_PLUGIN` The authentication plugin to use when connecting to the Identity service, its version must match the Identity API version From e85971b1a6bb533c6169161ead7b34c910f98e5f Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Sun, 19 Apr 2015 01:43:40 -0400 Subject: [PATCH 0027/3095] Update the docs for new nic options Change-Id: I4c8b93ede80f993eab8badfbba6bf1c530844d54 --- doc/source/command-objects/server.rst | 11 ++++++++--- openstackclient/compute/v2/server.py | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index 34555580ed..f9ea590828 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -67,7 +67,7 @@ Create a new server [--user-data ] [--availability-zone ] [--block-device-mapping [...] ] - [--nic [...] ] + [--nic [...] ] [--hint [...] ] [--config-drive |True ] [--min ] @@ -115,9 +115,14 @@ Create a new server Map block devices; map is ::: (optional extension) -.. option:: --nic +.. option:: --nic - Specify NIC configuration (optional extension) + Create a NIC on the server. Specify option multiple times to create + multiple NICs. Either net-id or port-id must be provided, but not both. + net-id: attach NIC to network with this UUID, + port-id: attach NIC to port with this UUID, + v4-fixed-ip: IPv4 fixed address for NIC (optional), + v6-fixed-ip: IPv6 fixed address for NIC (optional). .. option:: --hint diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index e672819bb5..e4e96ee7ff 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -285,7 +285,7 @@ def get_parser(self, prog_name): "net-id: attach NIC to network with this UUID, " "port-id: attach NIC to port with this UUID, " "v4-fixed-ip: IPv4 fixed address for NIC (optional), " - "v6-fixed-ip: IPv6 fixed address for NIC (optional), "), + "v6-fixed-ip: IPv6 fixed address for NIC (optional)."), ) parser.add_argument( '--hint', From 00eeb3593ca6cdd4a3c714ce49435ed74d5e0630 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Sun, 19 Apr 2015 02:41:04 -0400 Subject: [PATCH 0028/3095] remove unnecessary conditionals In several places we had else branches where a reasonable default would do the job. This makes the code a mean cleaer and easier to read. Change-Id: I231e09aab85fd32b8300bc33c48d0899b728b96e --- openstackclient/identity/v3/group.py | 7 +++---- openstackclient/identity/v3/project.py | 3 +-- openstackclient/identity/v3/trust.py | 9 +++------ openstackclient/identity/v3/user.py | 9 +++------ 4 files changed, 10 insertions(+), 18 deletions(-) diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py index a2afecb9a7..91acf3e545 100644 --- a/openstackclient/identity/v3/group.py +++ b/openstackclient/identity/v3/group.py @@ -137,11 +137,11 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity + + domain = None if parsed_args.domain: domain = common.find_domain(identity_client, parsed_args.domain).id - else: - domain = None try: group = identity_client.groups.create( @@ -228,11 +228,10 @@ def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity + domain = None if parsed_args.domain: domain = common.find_domain(identity_client, parsed_args.domain).id - else: - domain = None if parsed_args.user: user = utils.find_resource( diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index 1c93ad5d6c..c5560f5ebb 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -80,11 +80,10 @@ def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity + domain = None if parsed_args.domain: domain = common.find_domain(identity_client, parsed_args.domain).id - else: - domain = None enabled = True if parsed_args.disable: diff --git a/openstackclient/identity/v3/trust.py b/openstackclient/identity/v3/trust.py index e67b02e780..ab6673d2f8 100644 --- a/openstackclient/identity/v3/trust.py +++ b/openstackclient/identity/v3/trust.py @@ -92,23 +92,20 @@ def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) identity_client = self.app.client_manager.identity + project_domain = None if parsed_args.project_domain: project_domain = common.find_domain(identity_client, parsed_args.project_domain).id - else: - project_domain = None + trustor_domain = None if parsed_args.trustor_domain: trustor_domain = common.find_domain(identity_client, parsed_args.trustor_domain).id - else: - trustor_domain = None + trustee_domain = None if parsed_args.trustee_domain: trustee_domain = common.find_domain(identity_client, parsed_args.trustee_domain).id - else: - trustee_domain = None # NOTE(stevemar): Find the two users, project and roles that # are necessary for making a trust usable, the API dictates that diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index 0a154f6405..c1a0a43c6b 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -94,19 +94,17 @@ def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity + project_id = None if parsed_args.project: project_id = utils.find_resource( identity_client.projects, parsed_args.project, ).id - else: - project_id = None + domain_id = None if parsed_args.domain: domain_id = common.find_domain(identity_client, parsed_args.domain).id - else: - domain_id = None enabled = True if parsed_args.disable: @@ -211,11 +209,10 @@ def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity + domain = None if parsed_args.domain: domain = common.find_domain(identity_client, parsed_args.domain).id - else: - domain = None if parsed_args.group: group = utils.find_resource( From 32843844a3eed6b3fc78d6346baeb84a5d7068f4 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Mon, 20 Apr 2015 06:04:10 +0000 Subject: [PATCH 0029/3095] Imported Translations from Transifex For more information about this automatic import see: https://wiki.openstack.org/wiki/Translations/Infrastructure Change-Id: I0e7cdac01f3dc8b9368aa891e93125a5e838ae3d --- .../de/LC_MESSAGES/python-openstackclient.po | 50 +++++++++++++------ .../locale/python-openstackclient.pot | 36 +++++++++---- .../LC_MESSAGES/python-openstackclient.po | 42 ++++++++++------ 3 files changed, 87 insertions(+), 41 deletions(-) diff --git a/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po b/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po index 4db02a1ddc..a6ea95c6af 100644 --- a/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po +++ b/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po @@ -9,8 +9,8 @@ msgid "" msgstr "" "Project-Id-Version: python-openstackclient\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-04-18 06:04+0000\n" -"PO-Revision-Date: 2015-04-17 11:35+0000\n" +"POT-Creation-Date: 2015-04-20 06:04+0000\n" +"PO-Revision-Date: 2015-04-19 18:03+0000\n" "Last-Translator: Ettore Atalan \n" "Language-Team: German (http://www.transifex.com/projects/p/python-" "openstackclient/language/de/)\n" @@ -20,23 +20,41 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 1.3\n" -#: openstackclient/api/auth.py:130 -msgid "Set a username with --os-username or OS_USERNAME\n" -msgstr "Legen Sie einen Benutzernamen mit --os-username oder OS_USERNAME fest\n" +#: openstackclient/api/auth.py:144 +msgid "Set a username with --os-username, OS_USERNAME, or auth.username\n" +msgstr "" +"Legen Sie einen Benutzernamen mit --os-username, OS_USERNAME oder " +"auth.username fest\n" -#: openstackclient/api/auth.py:132 -msgid "Set an authentication URL, with --os-auth-url or OS_AUTH_URL\n" +#: openstackclient/api/auth.py:147 +msgid "" +"Set an authentication URL, with --os-auth-url, OS_AUTH_URL or " +"auth.auth_url\n" msgstr "" -"Legen Sie eine Authentifizierungs-URL mit --os-auth-url oder OS_AUTH_URL " -"fest\n" +"Legen Sie eine Authentifizierungs-URL mit --os-auth-url, OS_AUTH_URL oder" +" auth.auth_url fest\n" -#: openstackclient/api/auth.py:136 +#: openstackclient/api/auth.py:153 msgid "" -"Set a scope, such as a project or domain, with --os-project-name or " -"OS_PROJECT_NAME" +"Set a scope, such as a project or domain, with --os-project-name, " +"OS_PROJECT_NAME or auth.project_name" msgstr "" "Legen Sie einen Gültigkeitsbereich, wie beispielsweise ein Projekt oder " -"eine Domäne, mit --os-project-name oder OS_PROJECT_NAME fest" +"eine Domäne, mit OS_PROJECT_NAME oder auth.project_name fest" + +#: openstackclient/api/auth.py:157 openstackclient/api/auth.py:163 +msgid "Set a token with --os-token, OS_TOKEN or auth.token\n" +msgstr "Legen Sie einen Token mit --os-token, OS_TOKEN oder auth.token fest\n" + +#: openstackclient/api/auth.py:159 +msgid "Set a service AUTH_URL, with --os-auth-url, OS_AUTH_URL or auth.auth_url\n" +msgstr "" +"Legen Sie eine Dienst-AUTH_URL mit --os-auth-url, OS_AUTH_URL oder " +"auth.auth_url fest\n" + +#: openstackclient/api/auth.py:165 +msgid "Set a service URL, with --os-url, OS_URL or auth.url\n" +msgstr "Legen Sie eine Dienst-URL mit --os-url, OS_URL oder auth.url fest\n" #: openstackclient/compute/v2/availability_zone.py:72 #: openstackclient/compute/v2/server.py:650 @@ -144,14 +162,14 @@ msgid "" "multiple NICs. Either net-id or port-id must be provided, but not both. " "net-id: attach NIC to network with this UUID, port-id: attach NIC to port" " with this UUID, v4-fixed-ip: IPv4 fixed address for NIC (optional), v6" -"-fixed-ip: IPv6 fixed address for NIC (optional), " +"-fixed-ip: IPv6 fixed address for NIC (optional)." msgstr "" "Erstellen Sie ein NIC auf dem Server. Geben Sie die Option mehrfach an, " "um mehrere NICs zu erstellen. Entweder net-id oder port-id müssen " "bereitgestellt werden, aber nicht beide. net-id: NIC an Netzwerk mit " "dieser UUID binden, port-id: NIC an Port mit dieser UUID binden, v4" "-fixed-ip: Feste IPv4-Adresse für NIC (optional), v6-fixed-ip: Feste " -"IPv6-Adresse für NIC (optional), " +"IPv6-Adresse für NIC (optional)." #: openstackclient/compute/v2/server.py:295 msgid "Hints for the scheduler (optional extension)" @@ -606,7 +624,7 @@ msgid "New role name" msgstr "Name der neuen Rolle" #: openstackclient/identity/v2_0/role.py:92 -#: openstackclient/identity/v3/role.py:157 +#: openstackclient/identity/v3/role.py:158 msgid "Return existing role" msgstr "Vorhandene Rolle zurückgeben" diff --git a/python-openstackclient/locale/python-openstackclient.pot b/python-openstackclient/locale/python-openstackclient.pot index 5031392888..a7b64d5747 100644 --- a/python-openstackclient/locale/python-openstackclient.pot +++ b/python-openstackclient/locale/python-openstackclient.pot @@ -7,9 +7,9 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: python-openstackclient 1.0.3.post31\n" +"Project-Id-Version: python-openstackclient 1.0.3.post49\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-04-18 06:04+0000\n" +"POT-Creation-Date: 2015-04-20 06:04+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -18,18 +18,32 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 1.3\n" -#: openstackclient/api/auth.py:130 -msgid "Set a username with --os-username or OS_USERNAME\n" +#: openstackclient/api/auth.py:144 +msgid "Set a username with --os-username, OS_USERNAME, or auth.username\n" msgstr "" -#: openstackclient/api/auth.py:132 -msgid "Set an authentication URL, with --os-auth-url or OS_AUTH_URL\n" +#: openstackclient/api/auth.py:147 +msgid "" +"Set an authentication URL, with --os-auth-url, OS_AUTH_URL or " +"auth.auth_url\n" msgstr "" -#: openstackclient/api/auth.py:136 +#: openstackclient/api/auth.py:153 msgid "" -"Set a scope, such as a project or domain, with --os-project-name or " -"OS_PROJECT_NAME" +"Set a scope, such as a project or domain, with --os-project-name, " +"OS_PROJECT_NAME or auth.project_name" +msgstr "" + +#: openstackclient/api/auth.py:157 openstackclient/api/auth.py:163 +msgid "Set a token with --os-token, OS_TOKEN or auth.token\n" +msgstr "" + +#: openstackclient/api/auth.py:159 +msgid "Set a service AUTH_URL, with --os-auth-url, OS_AUTH_URL or auth.auth_url\n" +msgstr "" + +#: openstackclient/api/auth.py:165 +msgid "Set a service URL, with --os-url, OS_URL or auth.url\n" msgstr "" #: openstackclient/compute/v2/availability_zone.py:72 @@ -129,7 +143,7 @@ msgid "" "multiple NICs. Either net-id or port-id must be provided, but not both. " "net-id: attach NIC to network with this UUID, port-id: attach NIC to port" " with this UUID, v4-fixed-ip: IPv4 fixed address for NIC (optional), v6" -"-fixed-ip: IPv6 fixed address for NIC (optional), " +"-fixed-ip: IPv6 fixed address for NIC (optional)." msgstr "" #: openstackclient/compute/v2/server.py:295 @@ -553,7 +567,7 @@ msgid "New role name" msgstr "" #: openstackclient/identity/v2_0/role.py:92 -#: openstackclient/identity/v3/role.py:157 +#: openstackclient/identity/v3/role.py:158 msgid "Return existing role" msgstr "" diff --git a/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po b/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po index 415790458e..840eced0ad 100644 --- a/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po +++ b/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: python-openstackclient\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-04-18 06:04+0000\n" -"PO-Revision-Date: 2015-04-17 07:05+0000\n" +"POT-Creation-Date: 2015-04-20 06:04+0000\n" +"PO-Revision-Date: 2015-04-19 14:23+0000\n" "Last-Translator: openstackjenkins \n" "Language-Team: Chinese (Taiwan) (http://www.transifex.com/projects/p" "/python-openstackclient/language/zh_TW/)\n" @@ -19,19 +19,33 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 1.3\n" -#: openstackclient/api/auth.py:130 -msgid "Set a username with --os-username or OS_USERNAME\n" -msgstr "以 --os-username 或 OS_USERNAME 設定名稱\n" +#: openstackclient/api/auth.py:144 +msgid "Set a username with --os-username, OS_USERNAME, or auth.username\n" +msgstr "" -#: openstackclient/api/auth.py:132 -msgid "Set an authentication URL, with --os-auth-url or OS_AUTH_URL\n" -msgstr "以 --os-auth-url 或 OS_AUTH_URL 設定認證網址\n" +#: openstackclient/api/auth.py:147 +msgid "" +"Set an authentication URL, with --os-auth-url, OS_AUTH_URL or " +"auth.auth_url\n" +msgstr "" -#: openstackclient/api/auth.py:136 +#: openstackclient/api/auth.py:153 msgid "" -"Set a scope, such as a project or domain, with --os-project-name or " -"OS_PROJECT_NAME" -msgstr "以 --os-project-name 或 OS_PROJECT_NAME 設定範圍,如同專案或區域" +"Set a scope, such as a project or domain, with --os-project-name, " +"OS_PROJECT_NAME or auth.project_name" +msgstr "" + +#: openstackclient/api/auth.py:157 openstackclient/api/auth.py:163 +msgid "Set a token with --os-token, OS_TOKEN or auth.token\n" +msgstr "" + +#: openstackclient/api/auth.py:159 +msgid "Set a service AUTH_URL, with --os-auth-url, OS_AUTH_URL or auth.auth_url\n" +msgstr "" + +#: openstackclient/api/auth.py:165 +msgid "Set a service URL, with --os-url, OS_URL or auth.url\n" +msgstr "" #: openstackclient/compute/v2/availability_zone.py:72 #: openstackclient/compute/v2/server.py:650 @@ -130,7 +144,7 @@ msgid "" "multiple NICs. Either net-id or port-id must be provided, but not both. " "net-id: attach NIC to network with this UUID, port-id: attach NIC to port" " with this UUID, v4-fixed-ip: IPv4 fixed address for NIC (optional), v6" -"-fixed-ip: IPv6 fixed address for NIC (optional), " +"-fixed-ip: IPv6 fixed address for NIC (optional)." msgstr "" #: openstackclient/compute/v2/server.py:295 @@ -570,7 +584,7 @@ msgid "New role name" msgstr "新角色名稱" #: openstackclient/identity/v2_0/role.py:92 -#: openstackclient/identity/v3/role.py:157 +#: openstackclient/identity/v3/role.py:158 msgid "Return existing role" msgstr "回傳存在的角色" From d733e457e32ac52b0d26ed3de5a8971b02033a1f Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Sun, 19 Apr 2015 01:57:23 -0400 Subject: [PATCH 0030/3095] Refactor utility to find identity resources Based on the comments made in this patch: https://review.openstack.org/#/c/174908/2/ We should simplify and refactor the way we handle finding identity resources. Change-Id: I77db2e3564faa90a917082a6c6cb87269e93aebe --- openstackclient/identity/common.py | 97 +++++++++++++----------------- 1 file changed, 41 insertions(+), 56 deletions(-) diff --git a/openstackclient/identity/common.py b/openstackclient/identity/common.py index a1b46cb49f..2cc68c8dc2 100644 --- a/openstackclient/identity/common.py +++ b/openstackclient/identity/common.py @@ -20,6 +20,7 @@ from keystoneclient.v3 import groups from keystoneclient.v3 import projects from keystoneclient.v3 import users + from openstackclient.common import exceptions from openstackclient.common import utils @@ -43,74 +44,58 @@ def find_service(identity_client, name_type_or_id): def find_domain(identity_client, name_or_id): - """Find a domain. + return _find_identity_resource(identity_client.domains, name_or_id, + domains.Domain) - If the user does not have permissions to access the v3 domain API, e.g., - if the user is a project admin, assume that the domain given is the id - rather than the name. This method is used by the project list command, - so errors accessing the domain will be ignored and if the user has - access to the project API, everything will work fine. - Closes bugs #1317478 and #1317485. - """ - try: - dom = utils.find_resource(identity_client.domains, name_or_id) - if dom is not None: - return dom - except identity_exc.Forbidden: - pass - return domains.Domain(None, {'id': name_or_id, 'name': name_or_id}) +def find_group(identity_client, name_or_id): + return _find_identity_resource(identity_client.groups, name_or_id, + groups.Group) -def find_group(identity_client, name_or_id): - """Find a group. +def find_project(identity_client, name_or_id): + return _find_identity_resource(identity_client.projects, name_or_id, + projects.Project) - If the user does not have permissions to to perform a list groups call, - e.g., if the user is a project admin, assume that the group given is the - id rather than the name. This method is used by the role add command to - allow a role to be assigned to a group by a project admin who does not - have permission to list groups. - """ - try: - group = utils.find_resource(identity_client.groups, name_or_id) - if group is not None: - return group - except identity_exc.Forbidden: - pass - return groups.Group(None, {'id': name_or_id, 'name': name_or_id}) +def find_user(identity_client, name_or_id): + return _find_identity_resource(identity_client.users, name_or_id, + users.User) -def find_project(identity_client, name_or_id): - """Find a project. - If the user does not have permissions to to perform a list projects - call, e.g., if the user is a project admin, assume that the project - given is the id rather than the name. This method is used by the role - add command to allow a role to be assigned to a user by a project admin - who does not have permission to list projects. - """ - try: - project = utils.find_resource(identity_client.projects, name_or_id) - if project is not None: - return project - except identity_exc.Forbidden: - pass - return projects.Project(None, {'id': name_or_id, 'name': name_or_id}) +def _find_identity_resource(identity_client_manager, name_or_id, + resource_type): + """Find a specific identity resource. + Using keystoneclient's manager, attempt to find a specific resource by its + name or ID. If Forbidden to find the resource (a common case if the user + does not have permission), then return the resource by creating a local + instance of keystoneclient's Resource. -def find_user(identity_client, name_or_id): - """Find a user. + The parameter identity_client_manager is a keystoneclient manager, + for example: keystoneclient.v3.users or keystoneclient.v3.projects. + + The parameter resource_type is a keystoneclient resource, for example: + keystoneclient.v3.users.User or keystoneclient.v3.projects.Project. + + :param identity_client_manager: the manager that contains the resource + :type identity_client_manager: `keystoneclient.base.CrudManager` + :param name_or_id: the resources's name or ID + :type name_or_id: string + :param resource_type: class that represents the resource type + :type resource_type: `keystoneclient.base.Resource` + + :returns: the resource in question + :rtype: `keystoneclient.base.Resource` - If the user does not have permissions to to perform a list users call, - e.g., if the user is a project admin, assume that the user given is the - id rather than the name. This method is used by the role add command to - allow a role to be assigned to a user by a project admin who does not - have permission to list users. """ + try: - user = utils.find_resource(identity_client.users, name_or_id) - if user is not None: - return user + identity_resource = utils.find_resource(identity_client_manager, + name_or_id) + if identity_resource is not None: + return identity_resource except identity_exc.Forbidden: pass - return users.User(None, {'id': name_or_id, 'name': name_or_id}) + + return resource_type(None, {'id': name_or_id, 'name': name_or_id}) From 3c7b5185ca1eb89a00c54a57bb9ef028bcb290bb Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Tue, 14 Apr 2015 11:03:22 -0600 Subject: [PATCH 0031/3095] Handle the pagination for image list Handle the paginatiion for image list. We were sorting the data here, so nothing lost for the generator. Change-Id: I2d7d4b3d5c9f650953f309c971ac53b64f6f7f77 --- openstackclient/image/v1/image.py | 12 +++++++++- openstackclient/image/v2/image.py | 12 +++++++++- openstackclient/tests/image/v1/test_image.py | 20 +++++++++++------ openstackclient/tests/image/v2/test_image.py | 23 +++++++++++++++----- 4 files changed, 52 insertions(+), 15 deletions(-) diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index 127a7735ec..7f5f5af967 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -405,7 +405,17 @@ def take_action(self, parsed_args): columns = ("ID", "Name") column_headers = columns - data = image_client.api.image_list(**kwargs) + # List of image data received + data = [] + # No pages received yet, so start the page marker at None. + marker = None + while True: + page = image_client.api.image_list(marker=marker, **kwargs) + if not page: + break + data.extend(page) + # Set the marker to the id of the last item we received + marker = page[-1]['id'] if parsed_args.property: # NOTE(dtroyer): coerce to a list to subscript it in py3 diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index afc99e8578..eb82c9102c 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -156,7 +156,17 @@ def take_action(self, parsed_args): columns = ("ID", "Name") column_headers = columns - data = image_client.api.image_list(**kwargs) + # List of image data received + data = [] + # No pages received yet, so start the page marker at None. + marker = None + while True: + page = image_client.api.image_list(marker=marker, **kwargs) + if not page: + break + data.extend(page) + # Set the marker to the id of the last item we received + marker = page[-1]['id'] if parsed_args.property: # NOTE(dtroyer): coerce to a list to subscript it in py3 diff --git a/openstackclient/tests/image/v1/test_image.py b/openstackclient/tests/image/v1/test_image.py index 2776e7448d..2136f109de 100644 --- a/openstackclient/tests/image/v1/test_image.py +++ b/openstackclient/tests/image/v1/test_image.py @@ -306,8 +306,8 @@ def setUp(self): super(TestImageList, self).setUp() self.api_mock = mock.Mock() - self.api_mock.image_list.return_value = [ - copy.deepcopy(image_fakes.IMAGE), + self.api_mock.image_list.side_effect = [ + [copy.deepcopy(image_fakes.IMAGE)], [], ] self.app.client_manager.image.api = self.api_mock @@ -327,6 +327,7 @@ def test_image_list_no_options(self): columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( detailed=False, + marker=image_fakes.image_id, ) collist = ('ID', 'Name') @@ -354,6 +355,7 @@ def test_image_list_public_option(self): self.api_mock.image_list.assert_called_with( detailed=False, public=True, + marker=image_fakes.image_id, ) collist = ('ID', 'Name') @@ -381,6 +383,7 @@ def test_image_list_private_option(self): self.api_mock.image_list.assert_called_with( detailed=False, private=True, + marker=image_fakes.image_id, ) collist = ('ID', 'Name') @@ -405,6 +408,7 @@ def test_image_list_long_option(self): columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( detailed=True, + marker=image_fakes.image_id, ) collist = ( @@ -437,8 +441,8 @@ def test_image_list_long_option(self): @mock.patch('openstackclient.api.utils.simple_filter') def test_image_list_property_option(self, sf_mock): - sf_mock.return_value = [ - copy.deepcopy(image_fakes.IMAGE), + sf_mock.side_effect = [ + [copy.deepcopy(image_fakes.IMAGE)], [], ] arglist = [ @@ -453,6 +457,7 @@ def test_image_list_property_option(self, sf_mock): columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( detailed=True, + marker=image_fakes.image_id, ) sf_mock.assert_called_with( [image_fakes.IMAGE], @@ -472,8 +477,8 @@ def test_image_list_property_option(self, sf_mock): @mock.patch('openstackclient.common.utils.sort_items') def test_image_list_sort_option(self, si_mock): - si_mock.return_value = [ - copy.deepcopy(image_fakes.IMAGE) + si_mock.side_effect = [ + [copy.deepcopy(image_fakes.IMAGE)], [], ] arglist = ['--sort', 'name:asc'] @@ -483,7 +488,8 @@ def test_image_list_sort_option(self, si_mock): # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( - detailed=False + detailed=False, + marker=image_fakes.image_id, ) si_mock.assert_called_with( [image_fakes.IMAGE], diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index 6a28b1ec2d..7eb23769ec 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -70,8 +70,8 @@ def setUp(self): super(TestImageList, self).setUp() self.api_mock = mock.Mock() - self.api_mock.image_list.return_value = [ - copy.deepcopy(image_fakes.IMAGE), + self.api_mock.image_list.side_effect = [ + [copy.deepcopy(image_fakes.IMAGE)], [], ] self.app.client_manager.image.api = self.api_mock @@ -90,7 +90,9 @@ def test_image_list_no_options(self): # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) - self.api_mock.image_list.assert_called_with() + self.api_mock.image_list.assert_called_with( + marker=image_fakes.image_id, + ) collist = ('ID', 'Name') @@ -117,6 +119,7 @@ def test_image_list_public_option(self): columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( public=True, + marker=image_fakes.image_id, ) collist = ('ID', 'Name') @@ -144,6 +147,7 @@ def test_image_list_private_option(self): columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( private=True, + marker=image_fakes.image_id, ) collist = ('ID', 'Name') @@ -171,6 +175,7 @@ def test_image_list_shared_option(self): columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( shared=True, + marker=image_fakes.image_id, ) collist = ('ID', 'Name') @@ -193,7 +198,9 @@ def test_image_list_long_option(self): # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) - self.api_mock.image_list.assert_called_with() + self.api_mock.image_list.assert_called_with( + marker=image_fakes.image_id, + ) collist = ( 'ID', @@ -239,7 +246,9 @@ def test_image_list_property_option(self, sf_mock): # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) - self.api_mock.image_list.assert_called_with() + self.api_mock.image_list.assert_called_with( + marker=image_fakes.image_id, + ) sf_mock.assert_called_with( [image_fakes.IMAGE], attr='a', @@ -268,7 +277,9 @@ def test_image_list_sort_option(self, si_mock): # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) - self.api_mock.image_list.assert_called_with() + self.api_mock.image_list.assert_called_with( + marker=image_fakes.image_id, + ) si_mock.assert_called_with( [image_fakes.IMAGE], 'name:asc' From e4fdf6e4e5237d4276ec018d3e25ff39a84f6e8d Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Tue, 21 Apr 2015 11:10:48 -0500 Subject: [PATCH 0032/3095] Create 1.1.0 release notes Change-Id: Ib5dd052ed938a844d39a61fcbea0510a2a427ab7 --- doc/source/releases.rst | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/doc/source/releases.rst b/doc/source/releases.rst index 98aea3077c..cae5995dd8 100644 --- a/doc/source/releases.rst +++ b/doc/source/releases.rst @@ -2,6 +2,23 @@ Release Notes ============= +1.1.0 (21 Apr 2015) +=================== + +Primarily a procedural release for the OpenStack Kilo integrated release. +More details about the bugs fixed will be added here in 1.1.1 release +to follow shortly. + +* Add ``--os-cloud`` suport for cloud definitions in yaml files + +* Bug `1402577 `_: --timing option does not work. +* Bug `1434137 `_: osc needs to handle flavor properties +* Bug `1435640 `_: network create should allow for non-authenticated project +* Bug `1438377 `_: missing support for quota change per volume type +* Bug `1438379 `_: absolute limits for arbitrary tenants +* Bug `1444685 `_: server create does not explain network values + + 1.0.3 (10 Mar 2015) =================== From 2ee904cb2d18955930eaf0fea650222b2952cc6f Mon Sep 17 00:00:00 2001 From: Dag Stenstad Date: Tue, 21 Apr 2015 18:45:54 +0200 Subject: [PATCH 0033/3095] Adds support for container selection for backup Changed argument from parsed_args.volume to parsed_args.container as per what is expected in python-cinderclient. If not defined, defaults to "backup_swift_container" in cinder.conf. Stops there being a container for every volume you take backups off. Change-Id: I4a34894222f71b0f207d007c32427040589406aa Closes-Bug: 1446751 --- openstackclient/volume/v1/backup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/volume/v1/backup.py b/openstackclient/volume/v1/backup.py index 71c8ed3832..03c63a0545 100644 --- a/openstackclient/volume/v1/backup.py +++ b/openstackclient/volume/v1/backup.py @@ -64,7 +64,7 @@ def take_action(self, parsed_args): parsed_args.volume).id backup = volume_client.backups.create( volume_id, - parsed_args.volume, + parsed_args.container, parsed_args.name, parsed_args.description ) From 11c39530f5f97a14d534c8d5b7160a1e74f6cdf8 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 19 Mar 2015 11:46:05 -0500 Subject: [PATCH 0034/3095] Fix security group create description bug --description is optional in our CLI but the server requires it to be non-empty. Set a default value of the given name. Closes-Bug: #1434172 Change-Id: I81507a77ad8d815000ff411784ae71e229c77f78 --- openstackclient/compute/v2/security_group.py | 4 +- openstackclient/tests/compute/v2/fakes.py | 6 + .../tests/compute/v2/test_security_group.py | 197 ++++++++++++++++++ 3 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 openstackclient/tests/compute/v2/test_security_group.py diff --git a/openstackclient/compute/v2/security_group.py b/openstackclient/compute/v2/security_group.py index d4643438ff..21519add54 100644 --- a/openstackclient/compute/v2/security_group.py +++ b/openstackclient/compute/v2/security_group.py @@ -81,9 +81,11 @@ def take_action(self, parsed_args): compute_client = self.app.client_manager.compute + description = parsed_args.description or parsed_args.name + data = compute_client.security_groups.create( parsed_args.name, - parsed_args.description, + description, ) info = {} diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index a22c1ce027..c18dea7e19 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -16,6 +16,7 @@ import mock from openstackclient.tests import fakes +from openstackclient.tests.identity.v2_0 import fakes as identity_fakes from openstackclient.tests.image.v2 import fakes as image_fakes from openstackclient.tests.network.v2 import fakes as network_fakes from openstackclient.tests import utils @@ -85,6 +86,11 @@ def setUp(self): token=fakes.AUTH_TOKEN, ) + self.app.client_manager.identity = identity_fakes.FakeIdentityv2Client( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN, + ) + self.app.client_manager.image = image_fakes.FakeImagev2Client( endpoint=fakes.AUTH_URL, token=fakes.AUTH_TOKEN, diff --git a/openstackclient/tests/compute/v2/test_security_group.py b/openstackclient/tests/compute/v2/test_security_group.py new file mode 100644 index 0000000000..fdb659a86f --- /dev/null +++ b/openstackclient/tests/compute/v2/test_security_group.py @@ -0,0 +1,197 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +import copy +import mock + +from openstackclient.compute.v2 import security_group +from openstackclient.tests.compute.v2 import fakes as compute_fakes +from openstackclient.tests import fakes +from openstackclient.tests.identity.v2_0 import fakes as identity_fakes + + +security_group_id = '11' +security_group_name = 'wide-open' +security_group_description = 'nothing but net' + +SECURITY_GROUP = { + 'id': security_group_id, + 'name': security_group_name, + 'description': security_group_description, + 'tenant_id': identity_fakes.project_id, +} + + +class FakeSecurityGroupResource(fakes.FakeResource): + + def get_keys(self): + return {'property': 'value'} + + +class TestSecurityGroup(compute_fakes.TestComputev2): + + def setUp(self): + super(TestSecurityGroup, self).setUp() + + self.secgroups_mock = mock.Mock() + self.secgroups_mock.resource_class = fakes.FakeResource(None, {}) + self.app.client_manager.compute.security_groups = self.secgroups_mock + self.secgroups_mock.reset_mock() + + self.projects_mock = mock.Mock() + self.projects_mock.resource_class = fakes.FakeResource(None, {}) + self.app.client_manager.identity.projects = self.projects_mock + self.projects_mock.reset_mock() + + +class TestSecurityGroupCreate(TestSecurityGroup): + + def setUp(self): + super(TestSecurityGroupCreate, self).setUp() + + self.secgroups_mock.create.return_value = FakeSecurityGroupResource( + None, + copy.deepcopy(SECURITY_GROUP), + loaded=True, + ) + + # Get the command object to test + self.cmd = security_group.CreateSecurityGroup(self.app, None) + + def test_security_group_create_no_options(self): + arglist = [ + security_group_name, + ] + verifylist = [ + ('name', security_group_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # SecurityGroupManager.create(name, description) + self.secgroups_mock.create.assert_called_with( + security_group_name, + security_group_name, + ) + + collist = ( + 'description', + 'id', + 'name', + 'tenant_id', + ) + self.assertEqual(collist, columns) + datalist = ( + security_group_description, + security_group_id, + security_group_name, + identity_fakes.project_id, + ) + self.assertEqual(datalist, data) + + def test_security_group_create_description(self): + arglist = [ + security_group_name, + '--description', security_group_description, + ] + verifylist = [ + ('name', security_group_name), + ('description', security_group_description), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # SecurityGroupManager.create(name, description) + self.secgroups_mock.create.assert_called_with( + security_group_name, + security_group_description, + ) + + collist = ( + 'description', + 'id', + 'name', + 'tenant_id', + ) + self.assertEqual(collist, columns) + datalist = ( + security_group_description, + security_group_id, + security_group_name, + identity_fakes.project_id, + ) + self.assertEqual(datalist, data) + + +class TestSecurityGroupList(TestSecurityGroup): + + def setUp(self): + super(TestSecurityGroupList, self).setUp() + + self.secgroups_mock.list.return_value = [ + FakeSecurityGroupResource( + None, + copy.deepcopy(SECURITY_GROUP), + loaded=True, + ), + ] + + # Get the command object to test + self.cmd = security_group.ListSecurityGroup(self.app, None) + + def test_security_group_list_no_options(self): + self.projects_mock.list.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.PROJECT), + loaded=True, + ), + ] + + arglist = [] + verifylist = [ + ('all_projects', False), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'search_opts': { + 'all_tenants': False, + }, + } + + self.secgroups_mock.list.assert_called_with( + **kwargs + ) + + collist = ( + 'ID', + 'Name', + 'Description', + ) + self.assertEqual(collist, columns) + datalist = (( + security_group_id, + security_group_name, + security_group_description, + ), ) + self.assertEqual(datalist, tuple(data)) From 9b6dada8e371d9e77db129b15acb03a35115591a Mon Sep 17 00:00:00 2001 From: Ramaraja Ramachandran Date: Tue, 21 Apr 2015 16:40:40 +0530 Subject: [PATCH 0035/3095] Security group rule create fails By default the --dst-port value is set to None when no --dst-port argument is provided. By making the default value (0, 0), this allows novaclient to proceed without any error. Change-Id: Ibb58f5df5ed1890a8f499dd2467b12b0e79d547b Closes-Bug: #1443963 --- openstackclient/compute/v2/security_group.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openstackclient/compute/v2/security_group.py b/openstackclient/compute/v2/security_group.py index d4643438ff..d94973015d 100644 --- a/openstackclient/compute/v2/security_group.py +++ b/openstackclient/compute/v2/security_group.py @@ -290,6 +290,7 @@ def get_parser(self, prog_name): parser.add_argument( "--dst-port", metavar="", + default=(0, 0), action=parseractions.RangeAction, help="Destination port, may be a range: 137:139 (default: 0; " "only required for proto tcp and udp)", From a81332fad79be5f3ec615acf8eb9d6e99ed1f9fc Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Thu, 23 Apr 2015 23:08:17 -0400 Subject: [PATCH 0036/3095] Remove run_tests.sh Use tox instead. Change-Id: I70f96b301af129e58fd23e6a21c711bbc17940cd --- run_tests.sh | 49 ------------------------------------------------- 1 file changed, 49 deletions(-) delete mode 100755 run_tests.sh diff --git a/run_tests.sh b/run_tests.sh deleted file mode 100755 index 0b8cf67827..0000000000 --- a/run_tests.sh +++ /dev/null @@ -1,49 +0,0 @@ -#!/bin/bash - -function usage { - echo "Usage: $0 [OPTION]..." - echo "Run python-openstackclient's test suite(s)" - echo "" - echo " -p, --pep8 Just run pep8" - echo " -h, --help Print this usage message" - echo "" - echo "This script is deprecated and currently retained for compatibility." - echo 'You can run the full test suite for multiple environments by running "tox".' - echo 'You can run tests for only python 2.7 by running "tox -e py27", or run only' - echo 'the pep8 tests with "tox -e pep8".' - exit -} - -command -v tox > /dev/null 2>&1 -if [ $? -ne 0 ]; then - echo 'This script requires "tox" to run.' - echo 'You can install it with "pip install tox".' - exit 1; -fi - -just_pep8=0 - -function process_option { - case "$1" in - -h|--help) usage;; - -p|--pep8) let just_pep8=1;; - esac -} - -for arg in "$@"; do - process_option $arg -done - -if [ $just_pep8 -eq 1 ]; then - exec tox -e pep8 -fi - -tox -e py27 $toxargs 2>&1 | tee run_tests.err.log || exit 1 -tox_exit_code=${PIPESTATUS[0]} -if [ 0$tox_exit_code -ne 0 ]; then - exit $tox_exit_code -fi - -if [ -z "$toxargs" ]; then - tox -e pep8 -fi From f815b3fe27bd286c7cb9b23c41984eae2f12789f Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Thu, 23 Apr 2015 21:25:56 -0400 Subject: [PATCH 0037/3095] Add a doc that dictates backwards incompatible changes start tracking any changes in a standard format, so users may have a heads up about any impacts. Change-Id: Ibc06926a53592e927d11440362cd3598e0d4b2bf Closes-Bug: 1406470 --- doc/source/backwards-incompatible.rst | 47 +++++++++++++++++++++++++++ doc/source/index.rst | 1 + 2 files changed, 48 insertions(+) create mode 100644 doc/source/backwards-incompatible.rst diff --git a/doc/source/backwards-incompatible.rst b/doc/source/backwards-incompatible.rst new file mode 100644 index 0000000000..817f2a3097 --- /dev/null +++ b/doc/source/backwards-incompatible.rst @@ -0,0 +1,47 @@ +============================== +Backwards Incompatible Changes +============================== + +Despite our best efforts, sometimes the OpenStackClient team may introduce a +backwards incompatible change. For user convenience we are tracking any such +changes here (as of the 1.0.0 release). + +Should positional arguments for a command need to change, the OpenStackClient +team attempts to make the transition as painless as possible. Look for +deprecation warnings that indicate the new commands (or options) to use. + +List of Backwards Incompatible Changes +====================================== + +1. Rename command `openstack project usage list` + + The `project` part of the command was pointless. + + * In favor of: `openstack usage list` instead. + * As of: 1.0.2 + * Removed in: TBD + * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1406654 + * Commit: https://review.openstack.org/#/c/147379/ + +2. should not be optional for command `openstack service create` + + Previously, the command was `openstack service create --type `, + whereas now it is: `openstack service create --name ` + This bug also affected python-keystoneclient, and keystone. + + * In favor of: making a positional argument. + * As of: 1.0.2 + * Removed in: TBD + * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1404073 + * Commit: https://review.openstack.org/#/c/143242/ + +For Developers +============== + +If introducing a backwards incompatible change, then add the tag: +``BackwardsIncompatibleImpact`` to your git commit message, and if possible, +update this file. + +To review all changes that are affected, use the following query: + +https://review.openstack.org/#/q/project:openstack/python-openstackclient+AND+message:BackwardsIncompatibleImpact,n,z diff --git a/doc/source/index.rst b/doc/source/index.rst index 7258a7b4cc..7a5954799c 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -18,6 +18,7 @@ Contents: authentication interactive humaninterfaceguide + backwards-incompatible releases man/openstack From d6fa2428183e98c2f733cacb46b79e1e0757729f Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Fri, 24 Apr 2015 02:05:01 -0400 Subject: [PATCH 0038/3095] Remove references to venv Stick to using tox, remove the tools directory since it only contained references to using venv. Also update a few other locations that would have otherwise been incorrect. Change-Id: I7d3e9067256ac0935f07904abcf584d898ac1d6f --- .gitignore | 2 - openstack-common.conf | 2 +- tools/install_venv.py | 66 -------------- tools/install_venv_common.py | 172 ----------------------------------- tools/with_venv.sh | 4 - tox.ini | 2 +- 6 files changed, 2 insertions(+), 246 deletions(-) delete mode 100644 tools/install_venv.py delete mode 100644 tools/install_venv_common.py delete mode 100755 tools/with_venv.sh diff --git a/.gitignore b/.gitignore index 5dc75afa47..5e4f5a3c87 100644 --- a/.gitignore +++ b/.gitignore @@ -7,10 +7,8 @@ *.swp *~ .coverage -.openstackclient-venv .testrepository .tox -.venv AUTHORS build ChangeLog diff --git a/openstack-common.conf b/openstack-common.conf index 78b414c3f0..81a189f7ba 100644 --- a/openstack-common.conf +++ b/openstack-common.conf @@ -1,7 +1,7 @@ [DEFAULT] # The list of modules to copy from openstack-common -module=install_venv_common + # The base module to hold the copy of openstack.common base=openstackclient diff --git a/tools/install_venv.py b/tools/install_venv.py deleted file mode 100644 index 770ae73c5e..0000000000 --- a/tools/install_venv.py +++ /dev/null @@ -1,66 +0,0 @@ -# Copyright 2013 OpenStack, LLC. -# -# 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. -# - -""" -Installation script for python-openstackclient's development virtualenv -""" - -import os -import sys - -import install_venv_common as install_venv - - -def print_help(): - help = """ - python-openstackclient development environment setup is complete. - - python-openstackclient development uses virtualenv to track and manage Python - dependencies while in development and testing. - - To activate the python-openstackclient virtualenv for the extent of your current - shell session you can run: - - $ source .venv/bin/activate - - Or, if you prefer, you can run commands in the virtualenv on a case by case - basis by running: - - $ tools/with_venv.sh - - Also, make test will automatically use the virtualenv. - """ - print help - - -def main(argv): - root = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) - venv = os.path.join(root, ".venv") - pip_requires = os.path.join(root, "requirements.txt") - test_requires = os.path.join(root, "test-requirements.txt") - py_version = "python%s.%s" % (sys.version_info[0], sys.version_info[1]) - project = "python-openstackclient" - install = install_venv.InstallVenv(root, venv, pip_requires, test_requires, - py_version, project) - options = install.parse_args(argv) - install.check_python_version() - install.check_dependencies() - install.create_virtualenv(no_site_packages=options.no_site_packages) - install.install_dependencies() - print_help() - - -if __name__ == "__main__": - main(sys.argv) diff --git a/tools/install_venv_common.py b/tools/install_venv_common.py deleted file mode 100644 index e279159abb..0000000000 --- a/tools/install_venv_common.py +++ /dev/null @@ -1,172 +0,0 @@ -# Copyright 2013 OpenStack Foundation -# Copyright 2013 IBM Corp. -# -# 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. - -"""Provides methods needed by installation script for OpenStack development -virtual environments. - -Since this script is used to bootstrap a virtualenv from the system's Python -environment, it should be kept strictly compatible with Python 2.6. - -Synced in from openstack-common -""" - -from __future__ import print_function - -import optparse -import os -import subprocess -import sys - - -class InstallVenv(object): - - def __init__(self, root, venv, requirements, - test_requirements, py_version, - project): - self.root = root - self.venv = venv - self.requirements = requirements - self.test_requirements = test_requirements - self.py_version = py_version - self.project = project - - def die(self, message, *args): - print(message % args, file=sys.stderr) - sys.exit(1) - - def check_python_version(self): - if sys.version_info < (2, 6): - self.die("Need Python Version >= 2.6") - - def run_command_with_code(self, cmd, redirect_output=True, - check_exit_code=True): - """Runs a command in an out-of-process shell. - - Returns the output of that command. Working directory is self.root. - """ - if redirect_output: - stdout = subprocess.PIPE - else: - stdout = None - - proc = subprocess.Popen(cmd, cwd=self.root, stdout=stdout) - output = proc.communicate()[0] - if check_exit_code and proc.returncode != 0: - self.die('Command "%s" failed.\n%s', ' '.join(cmd), output) - return (output, proc.returncode) - - def run_command(self, cmd, redirect_output=True, check_exit_code=True): - return self.run_command_with_code(cmd, redirect_output, - check_exit_code)[0] - - def get_distro(self): - if (os.path.exists('/etc/fedora-release') or - os.path.exists('/etc/redhat-release')): - return Fedora( - self.root, self.venv, self.requirements, - self.test_requirements, self.py_version, self.project) - else: - return Distro( - self.root, self.venv, self.requirements, - self.test_requirements, self.py_version, self.project) - - def check_dependencies(self): - self.get_distro().install_virtualenv() - - def create_virtualenv(self, no_site_packages=True): - """Creates the virtual environment and installs PIP. - - Creates the virtual environment and installs PIP only into the - virtual environment. - """ - if not os.path.isdir(self.venv): - print('Creating venv...', end=' ') - if no_site_packages: - self.run_command(['virtualenv', '-q', '--no-site-packages', - self.venv]) - else: - self.run_command(['virtualenv', '-q', self.venv]) - print('done.') - else: - print("venv already exists...") - pass - - def pip_install(self, *args): - self.run_command(['tools/with_venv.sh', - 'pip', 'install', '--upgrade'] + list(args), - redirect_output=False) - - def install_dependencies(self): - print('Installing dependencies with pip (this can take a while)...') - - # First things first, make sure our venv has the latest pip and - # setuptools and pbr - self.pip_install('pip>=1.4') - self.pip_install('setuptools') - self.pip_install('pbr') - - self.pip_install('-r', self.requirements, '-r', self.test_requirements) - - def parse_args(self, argv): - """Parses command-line arguments.""" - parser = optparse.OptionParser() - parser.add_option('-n', '--no-site-packages', - action='store_true', - help="Do not inherit packages from global Python " - "install.") - return parser.parse_args(argv[1:])[0] - - -class Distro(InstallVenv): - - def check_cmd(self, cmd): - return bool(self.run_command(['which', cmd], - check_exit_code=False).strip()) - - def install_virtualenv(self): - if self.check_cmd('virtualenv'): - return - - if self.check_cmd('easy_install'): - print('Installing virtualenv via easy_install...', end=' ') - if self.run_command(['easy_install', 'virtualenv']): - print('Succeeded') - return - else: - print('Failed') - - self.die('ERROR: virtualenv not found.\n\n%s development' - ' requires virtualenv, please install it using your' - ' favorite package management tool' % self.project) - - -class Fedora(Distro): - """This covers all Fedora-based distributions. - - Includes: Fedora, RHEL, CentOS, Scientific Linux - """ - - def check_pkg(self, pkg): - return self.run_command_with_code(['rpm', '-q', pkg], - check_exit_code=False)[1] == 0 - - def install_virtualenv(self): - if self.check_cmd('virtualenv'): - return - - if not self.check_pkg('python-virtualenv'): - self.die("Please install 'python-virtualenv'.") - - super(Fedora, self).install_virtualenv() diff --git a/tools/with_venv.sh b/tools/with_venv.sh deleted file mode 100755 index c8d2940fc7..0000000000 --- a/tools/with_venv.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -TOOLS=`dirname $0` -VENV=$TOOLS/../.venv -source $VENV/bin/activate && $@ diff --git a/tox.ini b/tox.ini index 70076e8b76..70cca0341b 100644 --- a/tox.ini +++ b/tox.ini @@ -35,4 +35,4 @@ commands = python setup.py build_sphinx [flake8] show-source = True -exclude = .venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build,tools +exclude = ,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build,tools From aa7e58cefb67b28ec3c3fcc6f654b41d3ecc6c4a Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Thu, 16 Apr 2015 12:56:40 -0600 Subject: [PATCH 0039/3095] Functional tests run in many environments Have the functional tests running with less customization and less shell. This change will allow the functional tests to be run against any cloud set up in the environment. Change-Id: I24f621fbace62273e5a0be24e7af9078c0fc8550 --- .testr.conf | 2 +- functional/harpoon.sh | 37 ----------------------- functional/harpoonrc | 14 --------- functional/tests/object/v1/test_object.py | 34 +++++---------------- post_test_hook.sh | 9 ++++-- tox.ini | 2 +- 6 files changed, 17 insertions(+), 81 deletions(-) delete mode 100755 functional/harpoon.sh delete mode 100644 functional/harpoonrc diff --git a/.testr.conf b/.testr.conf index b8676b4580..7388cc3c45 100644 --- a/.testr.conf +++ b/.testr.conf @@ -2,7 +2,7 @@ test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \ OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \ OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \ - ${PYTHON:-python} -m subunit.run discover -t ./ ./openstackclient/tests $LISTOPT $IDOPTION + ${PYTHON:-python} -m subunit.run discover -t ./ ${OS_TEST_PATH:-./openstackclient/tests} $LISTOPT $IDOPTION test_id_option=--load-list $IDFILE test_list_option=--list diff --git a/functional/harpoon.sh b/functional/harpoon.sh deleted file mode 100755 index 4e16391794..0000000000 --- a/functional/harpoon.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/bash - -FUNCTIONAL_TEST_DIR=$(cd $(dirname "$0") && pwd) -source $FUNCTIONAL_TEST_DIR/harpoonrc - -OPENSTACKCLIENT_DIR=$FUNCTIONAL_TEST_DIR/.. - -if [[ -z $DEVSTACK_DIR ]]; then - DEVSTACK_DIR=$OPENSTACKCLIENT_DIR/../devstack - if [[ ! -d $DEVSTACK_DIR ]]; then - DEVSTACK_DIR=$HOME/devstack - if [[ ! -d $DEVSTACK_DIR ]]; then - echo "Where did you hide DevStack? Set DEVSTACK_DIR and try again" - exit 1 - fi - fi - echo "Using DevStack found at $DEVSTACK_DIR" -fi - -function setup_credentials { - RC_FILE=$DEVSTACK_DIR/accrc/$HARPOON_USER/$HARPOON_TENANT - source $RC_FILE - echo 'sourcing' $RC_FILE - echo 'running tests with' - env | grep OS -} - -function run_tests { - cd $FUNCTIONAL_TEST_DIR - python -m testtools.run discover - rvalue=$? - cd $OPENSTACKCLIENT_DIR - exit $rvalue -} - -setup_credentials -run_tests diff --git a/functional/harpoonrc b/functional/harpoonrc deleted file mode 100644 index ed9201ca1e..0000000000 --- a/functional/harpoonrc +++ /dev/null @@ -1,14 +0,0 @@ -# Global options -#RECLONE=yes - -# Devstack options -#ADMIN_PASSWORD=openstack -#MYSQL_PASSWORD=openstack -#RABBIT_PASSWORD=openstack -#SERVICE_TOKEN=openstack -#SERVICE_PASSWORD=openstack - -# Harpoon options -HARPOON_USER=admin -HARPOON_TENANT=admin -#DEVSTACK_DIR=/opt/stack/devstack diff --git a/functional/tests/object/v1/test_object.py b/functional/tests/object/v1/test_object.py index 4121524ace..5e9ac72c6d 100644 --- a/functional/tests/object/v1/test_object.py +++ b/functional/tests/object/v1/test_object.py @@ -32,63 +32,45 @@ def setUp(self): with open(self.OBJECT_NAME, 'w') as f: f.write('test content') - def test_container_create(self): + def test_object(self): raw_output = self.openstack('container create ' + self.CONTAINER_NAME) items = self.parse_listing(raw_output) self.assert_show_fields(items, CONTAINER_FIELDS) - def test_container_delete(self): - container_tmp = uuid.uuid4().hex - self.openstack('container create ' + container_tmp) - raw_output = self.openstack('container delete ' + container_tmp) - self.assertEqual(0, len(raw_output)) - - def test_container_list(self): raw_output = self.openstack('container list') items = self.parse_listing(raw_output) self.assert_table_structure(items, BASIC_LIST_HEADERS) - def test_container_show(self): self.openstack('container show ' + self.CONTAINER_NAME) # TODO(stevemar): Assert returned fields - def test_container_save(self): self.openstack('container save ' + self.CONTAINER_NAME) # TODO(stevemar): Assert returned fields - def test_object_create(self): raw_output = self.openstack('object create ' + self.CONTAINER_NAME + ' ' + self.OBJECT_NAME) items = self.parse_listing(raw_output) self.assert_show_fields(items, OBJECT_FIELDS) - def test_object_delete(self): - raw_output = self.openstack('object delete ' + self.CONTAINER_NAME - + ' ' + self.OBJECT_NAME) - self.assertEqual(0, len(raw_output)) - - def test_object_list(self): raw_output = self.openstack('object list ' + self.CONTAINER_NAME) items = self.parse_listing(raw_output) self.assert_table_structure(items, BASIC_LIST_HEADERS) - def test_object_save(self): - self.openstack('object create ' + self.CONTAINER_NAME - + ' ' + self.OBJECT_NAME) self.openstack('object save ' + self.CONTAINER_NAME + ' ' + self.OBJECT_NAME) # TODO(stevemar): Assert returned fields - def test_object_save_with_filename(self): - self.openstack('object create ' + self.CONTAINER_NAME - + ' ' + self.OBJECT_NAME) self.openstack('object save ' + self.CONTAINER_NAME + ' ' + self.OBJECT_NAME + ' --file tmp.txt') # TODO(stevemar): Assert returned fields - def test_object_show(self): - self.openstack('object create ' + self.CONTAINER_NAME - + ' ' + self.OBJECT_NAME) self.openstack('object show ' + self.CONTAINER_NAME + ' ' + self.OBJECT_NAME) # TODO(stevemar): Assert returned fields + + raw_output = self.openstack('object delete ' + self.CONTAINER_NAME + + ' ' + self.OBJECT_NAME) + self.assertEqual(0, len(raw_output)) + + raw_output = self.openstack('container delete ' + self.CONTAINER_NAME) + self.assertEqual(0, len(raw_output)) diff --git a/post_test_hook.sh b/post_test_hook.sh index b82c1e62a9..4c35b52099 100755 --- a/post_test_hook.sh +++ b/post_test_hook.sh @@ -10,6 +10,11 @@ set -xe OPENSTACKCLIENT_DIR=$(cd $(dirname "$0") && pwd) -cd $OPENSTACKCLIENT_DIR echo "Running openstackclient functional test suite" -sudo -H -u stack tox -e functional +sudo -H -u stack -i < Date: Sat, 25 Apr 2015 09:38:55 -0600 Subject: [PATCH 0040/3095] Reduce parameters to base class execute Simplify the parameters so we are just passing a command string to the execute command in the base class. The string is exactly the command we are going to run. This will make debugging easier and make it clearer what we are actually running. Change-Id: I0425007e1849f31d692420e38544c55b1acb86c4 --- functional/common/test.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/functional/common/test.py b/functional/common/test.py index 464844fad1..4a92def0d5 100644 --- a/functional/common/test.py +++ b/functional/common/test.py @@ -26,10 +26,8 @@ EXAMPLE_DIR = os.path.join(ROOT_DIR, 'examples') -def execute(cmd, action, flags='', params='', fail_ok=False, - merge_stderr=False): +def execute(cmd, fail_ok=False, merge_stderr=False): """Executes specified command for the given action.""" - cmd = ' '.join([cmd, flags, action, params]) cmd = shlex.split(cmd.encode('utf-8')) result = '' result_err = '' @@ -47,9 +45,10 @@ class TestCase(testtools.TestCase): delimiter_line = re.compile('^\+\-[\+\-]+\-\+$') - def openstack(self, action, flags='', params='', fail_ok=False): + @classmethod + def openstack(cls, cmd, fail_ok=False): """Executes openstackclient command for the given action.""" - return execute('openstack', action, flags, params, fail_ok) + return execute('openstack ' + cmd, fail_ok=fail_ok) def assert_table_structure(self, items, field_names): """Verify that all items have keys listed in field_names.""" From 48b52a0d40013d65e85ebeadcfb80a06c431b6db Mon Sep 17 00:00:00 2001 From: Juan Antonio Osorio Robles Date: Mon, 27 Apr 2015 08:39:21 +0300 Subject: [PATCH 0041/3095] Raise exception if no session is created If the clientmanager is unable to create a keystone session (due to insufficient auth parameters or something else) then the exception caused by this shouldn't be ignored, as was the case. On the other hand, we don't want this behaviour in the case of the 'complete' command, so this is now properly detected. Change-Id: If4f453d23cc87900cda752e9ffbcf41ded59e26f Closes-Bug: #1444640 --- openstackclient/shell.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 00f4a3c96f..90a6f32b70 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -24,6 +24,7 @@ from cliff import app from cliff import command +from cliff import complete from cliff import help import openstackclient @@ -76,6 +77,7 @@ def __init__(self): # Some commands do not need authentication help.HelpCommand.auth_required = False + complete.CompleteCommand.auth_required = False super(OpenStackShell, self).__init__( description=__doc__.strip(), @@ -318,12 +320,8 @@ def prepare_to_run_command(self, cmd): cmd.__class__.__name__, ) if cmd.auth_required: - try: - # Trigger the Identity client to initialize - self.client_manager.auth_ref - except Exception as e: - self.log.warning("Possible error authenticating: " + str(e)) - pass + # Trigger the Identity client to initialize + self.client_manager.auth_ref return def clean_up(self, cmd, result, err): From 75d3130ab248723d324dbcd2026d14f75185cf88 Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Mon, 27 Apr 2015 13:51:39 -0600 Subject: [PATCH 0042/3095] Remove unique class names because they are scoped Remove unique class names because they are module scoped. Also, add cleanup of the tmp file in the object test. Change-Id: I8107a02b13ff87793ba1e56e0f0ad26890f24369 --- functional/tests/identity/v2/test_identity.py | 4 ++-- functional/tests/identity/v3/test_identity.py | 6 +++--- functional/tests/object/v1/test_object.py | 10 ++++++---- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/functional/tests/identity/v2/test_identity.py b/functional/tests/identity/v2/test_identity.py index 37f29fc87e..6d28fa51b4 100644 --- a/functional/tests/identity/v2/test_identity.py +++ b/functional/tests/identity/v2/test_identity.py @@ -16,8 +16,8 @@ BASIC_LIST_HEADERS = ['ID', 'Name'] -class IdentityV2Tests(test.TestCase): - """Functional tests for Identity V2 commands. """ +class IdentityTests(test.TestCase): + """Functional tests for Identity commands. """ USER_FIELDS = ['email', 'enabled', 'id', 'name', 'project_id', 'username'] PROJECT_FIELDS = ['enabled', 'id', 'name', 'description'] diff --git a/functional/tests/identity/v3/test_identity.py b/functional/tests/identity/v3/test_identity.py index 8231908594..5d28f189d2 100644 --- a/functional/tests/identity/v3/test_identity.py +++ b/functional/tests/identity/v3/test_identity.py @@ -19,8 +19,8 @@ BASIC_LIST_HEADERS = ['ID', 'Name'] -class IdentityV3Tests(test.TestCase): - """Functional tests for Identity V3 commands. """ +class IdentityTests(test.TestCase): + """Functional tests for Identity commands. """ DOMAIN_FIELDS = ['description', 'enabled', 'id', 'name', 'links'] GROUP_FIELDS = ['description', 'domain_id', 'id', 'name', 'links'] @@ -37,7 +37,7 @@ def _create_dummy_domain(self): return name def setUp(self): - super(IdentityV3Tests, self).setUp() + super(IdentityTests, self).setUp() auth_url = os.environ.get('OS_AUTH_URL') auth_url = auth_url.replace('v2.0', 'v3') os.environ['OS_AUTH_URL'] = auth_url diff --git a/functional/tests/object/v1/test_object.py b/functional/tests/object/v1/test_object.py index 5e9ac72c6d..289e1ca77c 100644 --- a/functional/tests/object/v1/test_object.py +++ b/functional/tests/object/v1/test_object.py @@ -20,15 +20,17 @@ OBJECT_FIELDS = ['object', 'container', 'etag'] -class ObjectV1Tests(test.TestCase): - """Functional tests for Object V1 commands. """ +class ObjectTests(test.TestCase): + """Functional tests for Object commands. """ CONTAINER_NAME = uuid.uuid4().hex OBJECT_NAME = uuid.uuid4().hex + TMP_FILE = 'tmp.txt' def setUp(self): - super(ObjectV1Tests, self).setUp() + super(ObjectTests, self).setUp() self.addCleanup(os.remove, self.OBJECT_NAME) + self.addCleanup(os.remove, self.TMP_FILE) with open(self.OBJECT_NAME, 'w') as f: f.write('test content') @@ -61,7 +63,7 @@ def test_object(self): # TODO(stevemar): Assert returned fields self.openstack('object save ' + self.CONTAINER_NAME - + ' ' + self.OBJECT_NAME + ' --file tmp.txt') + + ' ' + self.OBJECT_NAME + ' --file ' + self.TMP_FILE) # TODO(stevemar): Assert returned fields self.openstack('object show ' + self.CONTAINER_NAME From b1ffbcff0b38e7e12b4764ced70c84faeaba35cf Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Mon, 27 Apr 2015 16:34:51 -0500 Subject: [PATCH 0043/3095] Redo 1.1.0 release notes The 1.1.0 release went out without the release notes commit, this fleshed out the text properly. Change-Id: I72811689c14a89c8468077d2cc3ab531acb7563b --- doc/source/releases.rst | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/doc/source/releases.rst b/doc/source/releases.rst index cae5995dd8..6018a73acd 100644 --- a/doc/source/releases.rst +++ b/doc/source/releases.rst @@ -5,18 +5,29 @@ Release Notes 1.1.0 (21 Apr 2015) =================== -Primarily a procedural release for the OpenStack Kilo integrated release. -More details about the bugs fixed will be added here in 1.1.1 release -to follow shortly. - -* Add ``--os-cloud`` suport for cloud definitions in yaml files - -* Bug `1402577 `_: --timing option does not work. -* Bug `1434137 `_: osc needs to handle flavor properties -* Bug `1435640 `_: network create should allow for non-authenticated project -* Bug `1438377 `_: missing support for quota change per volume type -* Bug `1438379 `_: absolute limits for arbitrary tenants -* Bug `1444685 `_: server create does not explain network values +* Add global ``--os-cloud`` option to select from a list of cloud configurations. + See :doc:`configuration` for more details. + +* Fix global ``--timing`` option operation. + Bug `1402577 `_ + +* Add ``flavor set`` and ``flavor unset`` commands. + Bug `1434137 `_ + +* Add ``--domain`` and ``--project`` options to ``network create`` command. + Bug `1435640 `_ + +* Add ``--volume-type`` option to ``quota set`` command. + Bug `1438377 `_ + +* Add ``--domain`` and ``--project`` options to ``limits show`` command. + Bug `1438379 `_ + +* Improve ``--nic`` option help for ``server create`` command. + Bug `1444685 `_ + +* Add ``--remote-id`` and ``--remote-id-file`` options to + ``identity provider create`` and ``identity provider set`` commands. 1.0.3 (10 Mar 2015) From cf52e722c0c37ce2f69b9ca0684890033c8c06c6 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Tue, 28 Apr 2015 18:15:20 -0500 Subject: [PATCH 0044/3095] Minor logging/debug cleanups This removed the rarely useful cliff command list from the debug output. Change-Id: I48f22086733acf90e79a6ddac8712734ee2d0b60 --- openstackclient/shell.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 90a6f32b70..5e2910215d 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -139,12 +139,11 @@ def configure_logging(self): # --debug forces traceback self.dump_stack_trace = True requests_log.setLevel(logging.DEBUG) - cliff_log.setLevel(logging.DEBUG) else: self.dump_stack_trace = False requests_log.setLevel(logging.ERROR) - cliff_log.setLevel(logging.ERROR) + cliff_log.setLevel(logging.ERROR) stevedore_log.setLevel(logging.ERROR) iso8601_log.setLevel(logging.ERROR) @@ -325,10 +324,7 @@ def prepare_to_run_command(self, cmd): return def clean_up(self, cmd, result, err): - self.log.debug('clean_up %s', cmd.__class__.__name__) - - if err: - self.log.debug('got an error: %s', err) + self.log.debug('clean_up %s: %s', cmd.__class__.__name__, err or '') # Process collected timing data if self.options.timing: From af2a665a637fb3cd11f67bc52ab01717c98e35c5 Mon Sep 17 00:00:00 2001 From: Lin Yang Date: Thu, 30 Apr 2015 17:21:45 +0800 Subject: [PATCH 0045/3095] Fix tiny typo in comment message compatability => compatibility Change-Id: I3181fb2b83df1e2cb60a9eedf319f2ad0a487dba Signed-off-by: Lin Yang --- openstackclient/image/v1/image.py | 2 +- openstackclient/image/v2/image.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index 4c07d360b6..830b99bab8 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -348,7 +348,7 @@ def get_parser(self, prog_name): help='List additional fields in output', ) - # --page-size has never worked, leave here for silent compatability + # --page-size has never worked, leave here for silent compatibility # We'll implement limit/marker differently later parser.add_argument( "--page-size", diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index eb82c9102c..99bf267414 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -98,7 +98,7 @@ def get_parser(self, prog_name): help='List additional fields in output', ) - # --page-size has never worked, leave here for silent compatability + # --page-size has never worked, leave here for silent compatibility # We'll implement limit/marker differently later parser.add_argument( "--page-size", From 5d5054b6f54982eea4f9f5b73429f342aa2c35cf Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Mon, 27 Apr 2015 16:30:41 -0500 Subject: [PATCH 0046/3095] Create 1.2.0 release notes Change-Id: I82dcd075960d9a4ed8b89471a31a951b274de4fa --- doc/source/releases.rst | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/doc/source/releases.rst b/doc/source/releases.rst index 6018a73acd..4def408e3c 100644 --- a/doc/source/releases.rst +++ b/doc/source/releases.rst @@ -2,6 +2,40 @@ Release Notes ============= +1.2.0 (30 Apr 2015) +=================== + +* Fix error in ``security group create`` command when ``--description`` is not + supplied. + Bug `1434172 `_ + +* Correct ``image list`` pagination handling, all images are now correctly returned. + Bug `1443089 `_ + +* Do not require ``--dst-port`` option with ``security group rule create`` when + ``--proto ICMP`` is selected. + Bug `1443963 `_ + +* Correctly pass ``--location`` arguemnt in ``image create`` command. + Bug `1445460 `_ + +* Correctly handle use of ``role`` commands for project admins. Using IDs will + work for project admins even when names will not due to non-admin contraints. + Bug `1445528 `_ + +* Correctly exit with an error when authentication can not be completed. + Bug `1444640 `_ + +* Fix ``backup create`` to correctly use the ``--container`` value if supplied. + Bug `1446751 `_ + +* Document the backward-compatibility-breaking changes in + :doc:`backwards-incompatibile`. + Bug `1406470 `_ + +* Add `--parent`` option to `projct create` command. + + 1.1.0 (21 Apr 2015) =================== From 1bb4bb3baf4a67c6bb2146746c1674fb7804cd4f Mon Sep 17 00:00:00 2001 From: Amey Bhide Date: Thu, 30 Apr 2015 16:38:19 -0700 Subject: [PATCH 0047/3095] Minor fix to openstack image show command image show using V2 api was failing. openstack --os-image-api-version 2 image show ERROR: openstack _info Closes-Bug: #1450829 Change-Id: Ic95db2f63d9f5f37e29f0d7e048397da311fbf8c --- openstackclient/image/v2/image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 99bf267414..0b2becb85a 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -248,5 +248,5 @@ def take_action(self, parsed_args): ) info = {} - info.update(image._info) + info.update(image) return zip(*sorted(six.iteritems(info))) From 47791a1639c9ab1da46e750ad11015d9ca868ab0 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 1 May 2015 09:38:04 -0500 Subject: [PATCH 0048/3095] Add image show tests Image v2 uses warlock objects rather than the usua Resource objects so we need to test for those. This adds a subset of the Image v2 schema that should be enough to test for proper warlock image handling. Depends-On: Ic95db2f63d9f5f37e29f0d7e048397da311fbf8c Change-Id: Ib89cce87f110a554f40e726718e31d39b500a6ae --- .../tests/compute/v2/test_server.py | 8 +- openstackclient/tests/image/v1/test_image.py | 33 +++++++ openstackclient/tests/image/v2/fakes.py | 93 ++++++++++++++++++- openstackclient/tests/image/v2/test_image.py | 40 +++++++- 4 files changed, 166 insertions(+), 8 deletions(-) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 079f301eac..baf53742e5 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -361,14 +361,14 @@ def test_server_image_create_no_options(self): compute_fakes.server_name, ) - collist = ('id', 'is_public', 'name', 'owner', 'protected') + collist = ('id', 'name', 'owner', 'protected', 'visibility') self.assertEqual(collist, columns) datalist = ( image_fakes.image_id, - image_fakes.image_public, image_fakes.image_name, image_fakes.image_owner, image_fakes.image_protected, + image_fakes.image_visibility, ) self.assertEqual(datalist, data) @@ -392,14 +392,14 @@ def test_server_image_create_name(self): 'img-nam', ) - collist = ('id', 'is_public', 'name', 'owner', 'protected') + collist = ('id', 'name', 'owner', 'protected', 'visibility') self.assertEqual(collist, columns) datalist = ( image_fakes.image_id, - image_fakes.image_public, image_fakes.image_name, image_fakes.image_owner, image_fakes.image_protected, + image_fakes.image_visibility, ) self.assertEqual(datalist, data) diff --git a/openstackclient/tests/image/v1/test_image.py b/openstackclient/tests/image/v1/test_image.py index 2136f109de..ef7ca9eaa7 100644 --- a/openstackclient/tests/image/v1/test_image.py +++ b/openstackclient/tests/image/v1/test_image.py @@ -659,3 +659,36 @@ def test_image_set_properties(self): image_fakes.image_id, **kwargs ) + + +class TestImageShow(TestImage): + + def setUp(self): + super(TestImageShow, self).setUp() + + self.images_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(image_fakes.IMAGE), + loaded=True, + ) + + # Get the command object to test + self.cmd = image.ShowImage(self.app, None) + + def test_image_show(self): + arglist = [ + image_fakes.image_id, + ] + verifylist = [ + ('image', image_fakes.image_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + self.images_mock.get.assert_called_with( + image_fakes.image_id, + ) + + self.assertEqual(image_fakes.IMAGE_columns, columns) + self.assertEqual(image_fakes.IMAGE_data, data) diff --git a/openstackclient/tests/image/v2/fakes.py b/openstackclient/tests/image/v2/fakes.py index 1b7edf08bf..678291bb86 100644 --- a/openstackclient/tests/image/v2/fakes.py +++ b/openstackclient/tests/image/v2/fakes.py @@ -19,18 +19,105 @@ from openstackclient.tests import utils -image_id = 'im1' +image_id = '0f41529e-7c12-4de8-be2d-181abb825b3c' image_name = 'graven' image_owner = 'baal' -image_public = False image_protected = False +image_visibility = 'public' IMAGE = { 'id': image_id, 'name': image_name, - 'is_public': image_public, 'owner': image_owner, 'protected': image_protected, + 'visibility': image_visibility, +} + +IMAGE_columns = tuple(sorted(IMAGE)) +IMAGE_data = tuple((IMAGE[x] for x in sorted(IMAGE))) + +# Just enough v2 schema to do some testing +IMAGE_schema = { + "additionalProperties": { + "type": "string" + }, + "name": "image", + "links": [ + { + "href": "{self}", + "rel": "self" + }, + { + "href": "{file}", + "rel": "enclosure" + }, + { + "href": "{schema}", + "rel": "describedby" + } + ], + "properties": { + "id": { + "pattern": "^([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}$", # noqa + "type": "string", + "description": "An identifier for the image" + }, + "name": { + "type": [ + "null", + "string" + ], + "description": "Descriptive name for the image", + "maxLength": 255 + }, + "owner": { + "type": [ + "null", + "string" + ], + "description": "Owner of the image", + "maxLength": 255 + }, + "protected": { + "type": "boolean", + "description": "If true, image will not be deletable." + }, + "self": { + "type": "string", + "description": "(READ-ONLY)" + }, + "schema": { + "type": "string", + "description": "(READ-ONLY)" + }, + "size": { + "type": [ + "null", + "integer" + ], + "description": "Size of image file in bytes (READ-ONLY)" + }, + "status": { + "enum": [ + "queued", + "saving", + "active", + "killed", + "deleted", + "pending_delete" + ], + "type": "string", + "description": "Status of the image (READ-ONLY)" + }, + "visibility": { + "enum": [ + "public", + "private" + ], + "type": "string", + "description": "Scope of image accessibility" + }, + } } diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index 7eb23769ec..73b5d39a4a 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -16,6 +16,9 @@ import copy import mock +import warlock + +from glanceclient.v2 import schemas from openstackclient.image.v2 import image from openstackclient.tests import fakes from openstackclient.tests.image.v2 import fakes as image_fakes @@ -223,7 +226,7 @@ def test_image_list_long_option(self): '', '', '', - '', + 'public', False, image_fakes.image_owner, '', @@ -293,3 +296,38 @@ def test_image_list_sort_option(self, si_mock): image_fakes.image_name ), ) self.assertEqual(datalist, tuple(data)) + + +class TestImageShow(TestImage): + + def setUp(self): + super(TestImageShow, self).setUp() + + # Set up the schema + self.model = warlock.model_factory( + image_fakes.IMAGE_schema, + schemas.SchemaBasedModel, + ) + + self.images_mock.get.return_value = self.model(**image_fakes.IMAGE) + + # Get the command object to test + self.cmd = image.ShowImage(self.app, None) + + def test_image_show(self): + arglist = [ + image_fakes.image_id, + ] + verifylist = [ + ('image', image_fakes.image_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + self.images_mock.get.assert_called_with( + image_fakes.image_id, + ) + + self.assertEqual(image_fakes.IMAGE_columns, columns) + self.assertEqual(image_fakes.IMAGE_data, data) From 9561420a4f02cc168b18e05a5ba6e0875a5cd652 Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Fri, 1 May 2015 12:40:46 -0600 Subject: [PATCH 0049/3095] minor syntax error in tox.ini Change-Id: I476ea8f2cf4370e62b6b6b15d72b97beabe6677e --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 25eb81bc49..fdac2b3302 100644 --- a/tox.ini +++ b/tox.ini @@ -35,4 +35,4 @@ commands = python setup.py build_sphinx [flake8] show-source = True -exclude = ,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build,tools +exclude = .git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build,tools From 90705f191e7f3831ecf9426679d5474ef5e5fecc Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 1 May 2015 17:37:38 -0500 Subject: [PATCH 0050/3095] Add os-client-config cli tests Add tests for --os-cloud handling and precedence between CLI, env vars and clouds.yaml. Change-Id: I91d96f483d395f19ffcf74ec0187718ba01a1c41 --- openstackclient/tests/test_shell.py | 256 +++++++++++++++++++++++++++- 1 file changed, 255 insertions(+), 1 deletion(-) diff --git a/openstackclient/tests/test_shell.py b/openstackclient/tests/test_shell.py index a43be95413..492b60de29 100644 --- a/openstackclient/tests/test_shell.py +++ b/openstackclient/tests/test_shell.py @@ -13,6 +13,7 @@ # under the License. # +import copy import mock import os @@ -36,7 +37,6 @@ DEFAULT_SERVICE_URL = "http://127.0.0.1:8771/v3.0/" DEFAULT_AUTH_PLUGIN = "v2password" - DEFAULT_COMPUTE_API_VERSION = "2" DEFAULT_IDENTITY_API_VERSION = "2" DEFAULT_IMAGE_API_VERSION = "2" @@ -49,6 +49,46 @@ LIB_VOLUME_API_VERSION = "1" LIB_NETWORK_API_VERSION = "2" +CLOUD_1 = { + 'clouds': { + 'scc': { + 'auth': { + 'auth_url': DEFAULT_AUTH_URL, + 'project_name': DEFAULT_PROJECT_NAME, + 'username': 'zaphod', + }, + 'region_name': 'occ-cloud', + 'donut': 'glazed', + } + } +} + +CLOUD_2 = { + 'clouds': { + 'megacloud': { + 'cloud': 'megadodo', + 'auth': { + 'project_name': 'heart-o-gold', + 'username': 'zaphod', + }, + 'region_name': 'occ-cloud', + } + } +} + +PUBLIC_1 = { + 'public-clouds': { + 'megadodo': { + 'auth': { + 'auth_url': DEFAULT_AUTH_URL, + 'project_name': DEFAULT_PROJECT_NAME, + }, + 'region_name': 'occ-public', + 'donut': 'cake', + } + } +} + def make_shell(): """Create a new command shell and mock out some bits.""" @@ -516,3 +556,217 @@ def test_empty_env(self): "network_api_version": LIB_NETWORK_API_VERSION } self._assert_cli(flag, kwargs) + + @mock.patch("os_client_config.config.OpenStackConfig._load_config_file") + def test_shell_args_cloud_no_vendor(self, config_mock): + config_mock.return_value = copy.deepcopy(CLOUD_1) + _shell = make_shell() + + fake_execute( + _shell, + "--os-cloud scc list user", + ) + self.assertEqual( + 'scc', + _shell.cloud.name, + ) + + # These come from clouds.yaml + self.assertEqual( + DEFAULT_AUTH_URL, + _shell.cloud.config['auth']['auth_url'], + ) + self.assertEqual( + DEFAULT_PROJECT_NAME, + _shell.cloud.config['auth']['project_name'], + ) + self.assertEqual( + 'zaphod', + _shell.cloud.config['auth']['username'], + ) + self.assertEqual( + 'occ-cloud', + _shell.cloud.config['region_name'], + ) + self.assertEqual( + 'glazed', + _shell.cloud.config['donut'], + ) + + @mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file") + @mock.patch("os_client_config.config.OpenStackConfig._load_config_file") + def test_shell_args_cloud_public(self, config_mock, public_mock): + config_mock.return_value = copy.deepcopy(CLOUD_2) + public_mock.return_value = copy.deepcopy(PUBLIC_1) + _shell = make_shell() + + fake_execute( + _shell, + "--os-cloud megacloud list user", + ) + self.assertEqual( + 'megacloud', + _shell.cloud.name, + ) + + # These come from clouds-public.yaml + self.assertEqual( + DEFAULT_AUTH_URL, + _shell.cloud.config['auth']['auth_url'], + ) + self.assertEqual( + 'cake', + _shell.cloud.config['donut'], + ) + + # These come from clouds.yaml + self.assertEqual( + 'heart-o-gold', + _shell.cloud.config['auth']['project_name'], + ) + self.assertEqual( + 'zaphod', + _shell.cloud.config['auth']['username'], + ) + self.assertEqual( + 'occ-cloud', + _shell.cloud.config['region_name'], + ) + + @mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file") + @mock.patch("os_client_config.config.OpenStackConfig._load_config_file") + def test_shell_args_precedence(self, config_mock, vendor_mock): + config_mock.return_value = copy.deepcopy(CLOUD_2) + vendor_mock.return_value = copy.deepcopy(PUBLIC_1) + _shell = make_shell() + + # Test command option overriding config file value + fake_execute( + _shell, + "--os-cloud megacloud --os-region-name krikkit list user", + ) + self.assertEqual( + 'megacloud', + _shell.cloud.name, + ) + + # These come from clouds-public.yaml + self.assertEqual( + DEFAULT_AUTH_URL, + _shell.cloud.config['auth']['auth_url'], + ) + self.assertEqual( + 'cake', + _shell.cloud.config['donut'], + ) + + # These come from clouds.yaml + self.assertEqual( + 'heart-o-gold', + _shell.cloud.config['auth']['project_name'], + ) + self.assertEqual( + 'zaphod', + _shell.cloud.config['auth']['username'], + ) + self.assertEqual( + 'krikkit', + _shell.cloud.config['region_name'], + ) + + +class TestShellCliEnv(TestShell): + def setUp(self): + super(TestShellCliEnv, self).setUp() + env = { + 'OS_REGION_NAME': 'occ-env', + } + self.orig_env, os.environ = os.environ, env.copy() + + def tearDown(self): + super(TestShellCliEnv, self).tearDown() + os.environ = self.orig_env + + @mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file") + @mock.patch("os_client_config.config.OpenStackConfig._load_config_file") + def test_shell_args_precedence_1(self, config_mock, vendor_mock): + config_mock.return_value = copy.deepcopy(CLOUD_2) + vendor_mock.return_value = copy.deepcopy(PUBLIC_1) + _shell = make_shell() + + # Test env var + fake_execute( + _shell, + "--os-cloud megacloud list user", + ) + self.assertEqual( + 'megacloud', + _shell.cloud.name, + ) + + # These come from clouds-public.yaml + self.assertEqual( + DEFAULT_AUTH_URL, + _shell.cloud.config['auth']['auth_url'], + ) + self.assertEqual( + 'cake', + _shell.cloud.config['donut'], + ) + + # These come from clouds.yaml + self.assertEqual( + 'heart-o-gold', + _shell.cloud.config['auth']['project_name'], + ) + self.assertEqual( + 'zaphod', + _shell.cloud.config['auth']['username'], + ) + self.assertEqual( + 'occ-env', + _shell.cloud.config['region_name'], + ) + + @mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file") + @mock.patch("os_client_config.config.OpenStackConfig._load_config_file") + def test_shell_args_precedence_2(self, config_mock, vendor_mock): + config_mock.return_value = copy.deepcopy(CLOUD_2) + vendor_mock.return_value = copy.deepcopy(PUBLIC_1) + _shell = make_shell() + + # Test command option overriding config file value + fake_execute( + _shell, + "--os-cloud megacloud --os-region-name krikkit list user", + ) + self.assertEqual( + 'megacloud', + _shell.cloud.name, + ) + + # These come from clouds-public.yaml + self.assertEqual( + DEFAULT_AUTH_URL, + _shell.cloud.config['auth']['auth_url'], + ) + self.assertEqual( + 'cake', + _shell.cloud.config['donut'], + ) + + # These come from clouds.yaml + self.assertEqual( + 'heart-o-gold', + _shell.cloud.config['auth']['project_name'], + ) + self.assertEqual( + 'zaphod', + _shell.cloud.config['auth']['username'], + ) + + # These come from the command line + self.assertEqual( + 'krikkit', + _shell.cloud.config['region_name'], + ) From 1f33a1eda926c8b5dce771883daef5881bab563a Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Mon, 4 May 2015 20:09:15 +0000 Subject: [PATCH 0051/3095] Updated from global requirements Change-Id: I1d339589a5ed94c726a6478318651b9db9765fdd --- requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index b8712eb8da..2a9e8ff310 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,14 +8,14 @@ Babel>=1.3 cliff>=1.10.0 # Apache-2.0 cliff-tablib>=1.0 os-client-config -oslo.config>=1.9.3 # Apache-2.0 +oslo.config>=1.11.0 # Apache-2.0 oslo.i18n>=1.5.0 # Apache-2.0 oslo.utils>=1.4.0 # Apache-2.0 oslo.serialization>=1.4.0 # Apache-2.0 python-glanceclient>=0.15.0 -python-keystoneclient>=1.1.0 +python-keystoneclient>=1.3.0 python-novaclient>=2.22.0 python-cinderclient>=1.1.0 python-neutronclient>=2.3.11,<3 -requests>=2.2.0,!=2.4.0 +requests>=2.5.2 stevedore>=1.3.0 # Apache-2.0 From 179ed18c30cea438e13ba92997d3f671e5f76a84 Mon Sep 17 00:00:00 2001 From: Jamie Lennox Date: Tue, 5 May 2015 12:43:12 +1000 Subject: [PATCH 0052/3095] Don't create empty quota set requests The way that getattr is called with the None default you will always create a compute_kwargs dictionary with key: None values. This means that we will always send these empty requests to the servers. Change so that only actually changed values end up in the quota set requests and get sent. Change-Id: I33bc3f4e1a8013ec672e995648d27513064baf26 Closes-Bug: #1451640 --- openstackclient/common/quota.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index ea1dc38f58..a40f6e4d84 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -97,8 +97,9 @@ def take_action(self, parsed_args): compute_kwargs = {} for k, v in COMPUTE_QUOTAS.items(): - if v in parsed_args: - compute_kwargs[k] = getattr(parsed_args, v, None) + value = getattr(parsed_args, v, None) + if value is not None: + compute_kwargs[k] = value volume_kwargs = {} for k, v in VOLUME_QUOTAS.items(): From c0bafb46a93fc1ecd074f312aca69dcd55fa2dae Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Tue, 5 May 2015 06:06:24 +0000 Subject: [PATCH 0053/3095] Imported Translations from Transifex For more information about this automatic import see: https://wiki.openstack.org/wiki/Translations/Infrastructure Change-Id: I0d706cdce7c4cb49c4bfa84d2d0ab92d591de71b --- .../de/LC_MESSAGES/python-openstackclient.po | 300 +++--------------- .../LC_MESSAGES/python-openstackclient.po | 256 +-------------- 2 files changed, 54 insertions(+), 502 deletions(-) diff --git a/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po b/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po index a6ea95c6af..079127c1c7 100644 --- a/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po +++ b/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: python-openstackclient\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-04-20 06:04+0000\n" +"POT-Creation-Date: 2015-05-05 06:06+0000\n" "PO-Revision-Date: 2015-04-19 18:03+0000\n" "Last-Translator: Ettore Atalan \n" "Language-Team: German (http://www.transifex.com/projects/p/python-" @@ -20,209 +20,147 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 1.3\n" -#: openstackclient/api/auth.py:144 msgid "Set a username with --os-username, OS_USERNAME, or auth.username\n" msgstr "" -"Legen Sie einen Benutzernamen mit --os-username, OS_USERNAME oder " -"auth.username fest\n" +"Legen Sie einen Benutzernamen mit --os-username, OS_USERNAME oder auth." +"username fest\n" -#: openstackclient/api/auth.py:147 msgid "" -"Set an authentication URL, with --os-auth-url, OS_AUTH_URL or " -"auth.auth_url\n" +"Set an authentication URL, with --os-auth-url, OS_AUTH_URL or auth.auth_url\n" msgstr "" -"Legen Sie eine Authentifizierungs-URL mit --os-auth-url, OS_AUTH_URL oder" -" auth.auth_url fest\n" +"Legen Sie eine Authentifizierungs-URL mit --os-auth-url, OS_AUTH_URL oder " +"auth.auth_url fest\n" -#: openstackclient/api/auth.py:153 msgid "" "Set a scope, such as a project or domain, with --os-project-name, " "OS_PROJECT_NAME or auth.project_name" msgstr "" -"Legen Sie einen Gültigkeitsbereich, wie beispielsweise ein Projekt oder " -"eine Domäne, mit OS_PROJECT_NAME oder auth.project_name fest" +"Legen Sie einen Gültigkeitsbereich, wie beispielsweise ein Projekt oder eine " +"Domäne, mit OS_PROJECT_NAME oder auth.project_name fest" -#: openstackclient/api/auth.py:157 openstackclient/api/auth.py:163 msgid "Set a token with --os-token, OS_TOKEN or auth.token\n" msgstr "Legen Sie einen Token mit --os-token, OS_TOKEN oder auth.token fest\n" -#: openstackclient/api/auth.py:159 -msgid "Set a service AUTH_URL, with --os-auth-url, OS_AUTH_URL or auth.auth_url\n" +msgid "" +"Set a service AUTH_URL, with --os-auth-url, OS_AUTH_URL or auth.auth_url\n" msgstr "" -"Legen Sie eine Dienst-AUTH_URL mit --os-auth-url, OS_AUTH_URL oder " -"auth.auth_url fest\n" +"Legen Sie eine Dienst-AUTH_URL mit --os-auth-url, OS_AUTH_URL oder auth." +"auth_url fest\n" -#: openstackclient/api/auth.py:165 msgid "Set a service URL, with --os-url, OS_URL or auth.url\n" msgstr "Legen Sie eine Dienst-URL mit --os-url, OS_URL oder auth.url fest\n" -#: openstackclient/compute/v2/availability_zone.py:72 -#: openstackclient/compute/v2/server.py:650 -#: openstackclient/identity/v2_0/endpoint.py:114 -#: openstackclient/identity/v2_0/project.py:145 -#: openstackclient/identity/v2_0/service.py:128 -#: openstackclient/identity/v2_0/user.py:174 msgid "List additional fields in output" msgstr "Zusätzliche Felder in der Ausgabe auflisten" -#: openstackclient/compute/v2/server.py:121 -#: openstackclient/compute/v2/server.py:158 -#: openstackclient/compute/v2/server.py:509 -#: openstackclient/compute/v2/server.py:721 -#: openstackclient/compute/v2/server.py:755 -#: openstackclient/compute/v2/server.py:838 -#: openstackclient/compute/v2/server.py:862 -#: openstackclient/compute/v2/server.py:917 -#: openstackclient/compute/v2/server.py:1009 -#: openstackclient/compute/v2/server.py:1049 -#: openstackclient/compute/v2/server.py:1075 -#: openstackclient/compute/v2/server.py:1140 -#: openstackclient/compute/v2/server.py:1164 -#: openstackclient/compute/v2/server.py:1223 -#: openstackclient/compute/v2/server.py:1260 -#: openstackclient/compute/v2/server.py:1418 -#: openstackclient/compute/v2/server.py:1442 -#: openstackclient/compute/v2/server.py:1466 -#: openstackclient/compute/v2/server.py:1490 -#: openstackclient/compute/v2/server.py:1514 msgid "Server (name or ID)" msgstr "Server (Name oder Kennung)" -#: openstackclient/compute/v2/server.py:126 msgid "Security group to add (name or ID)" msgstr "Zu hinzufügende Sicherheitsgruppe (Name oder Kennung)" -#: openstackclient/compute/v2/server.py:163 msgid "Volume to add (name or ID)" msgstr "Zu hinzufügender Datenträger (Name oder Kennung)" -#: openstackclient/compute/v2/server.py:168 msgid "Server internal device name for volume" msgstr "Serverinterner Gerätename für Datenträger" -#: openstackclient/compute/v2/server.py:208 -#: openstackclient/compute/v2/server.py:1169 msgid "New server name" msgstr "Neuer Servername" -#: openstackclient/compute/v2/server.py:216 msgid "Create server from this image" msgstr "Server aus diesem Abbild erstellen" -#: openstackclient/compute/v2/server.py:221 msgid "Create server from this volume" msgstr "Server aus diesem Datenträger erstellen" -#: openstackclient/compute/v2/server.py:227 msgid "Create server with this flavor" msgstr "Server mit dieser Variante erstellen" -#: openstackclient/compute/v2/server.py:234 msgid "Security group to assign to this server (repeat for multiple groups)" msgstr "" "Zu diesem Server zuweisende Sicherheitsgruppe (für mehrere Gruppen " "wiederholen)" -#: openstackclient/compute/v2/server.py:240 msgid "Keypair to inject into this server (optional extension)" msgstr "In diesem Server einzufügendes Schlüsselpaar (optionale Erweiterung)" -#: openstackclient/compute/v2/server.py:246 msgid "Set a property on this server (repeat for multiple values)" msgstr "" "Legen Sie eine Eigenschaft auf diesem Server fest (für mehrere Werte " "wiederholen)" -#: openstackclient/compute/v2/server.py:254 msgid "File to inject into image before boot (repeat for multiple files)" msgstr "" "Vor dem Start auf diesem Abbild einzufügende Datei (für mehrere Dateien " "wiederholen)" -#: openstackclient/compute/v2/server.py:260 msgid "User data file to serve from the metadata server" msgstr "Vom Metadatenserver anzubietende Benutzerdatendatei" -#: openstackclient/compute/v2/server.py:265 msgid "Select an availability zone for the server" msgstr "Wählen Sie eine Verfügbarkeitszone für den Server aus" -#: openstackclient/compute/v2/server.py:272 msgid "" "Map block devices; map is ::: " "(optional extension)" msgstr "" -"Blockorientierte Geräte abbilden; Abbildung ist " -"::: (optionale " -"Erweiterung)" +"Blockorientierte Geräte abbilden; Abbildung ist :::" +" (optionale Erweiterung)" -#: openstackclient/compute/v2/server.py:282 msgid "" -"Create a NIC on the server. Specify option multiple times to create " -"multiple NICs. Either net-id or port-id must be provided, but not both. " -"net-id: attach NIC to network with this UUID, port-id: attach NIC to port" -" with this UUID, v4-fixed-ip: IPv4 fixed address for NIC (optional), v6" -"-fixed-ip: IPv6 fixed address for NIC (optional)." +"Create a NIC on the server. Specify option multiple times to create multiple " +"NICs. Either net-id or port-id must be provided, but not both. net-id: " +"attach NIC to network with this UUID, port-id: attach NIC to port with this " +"UUID, v4-fixed-ip: IPv4 fixed address for NIC (optional), v6-fixed-ip: IPv6 " +"fixed address for NIC (optional)." msgstr "" -"Erstellen Sie ein NIC auf dem Server. Geben Sie die Option mehrfach an, " -"um mehrere NICs zu erstellen. Entweder net-id oder port-id müssen " -"bereitgestellt werden, aber nicht beide. net-id: NIC an Netzwerk mit " -"dieser UUID binden, port-id: NIC an Port mit dieser UUID binden, v4" -"-fixed-ip: Feste IPv4-Adresse für NIC (optional), v6-fixed-ip: Feste " -"IPv6-Adresse für NIC (optional)." - -#: openstackclient/compute/v2/server.py:295 +"Erstellen Sie ein NIC auf dem Server. Geben Sie die Option mehrfach an, um " +"mehrere NICs zu erstellen. Entweder net-id oder port-id müssen " +"bereitgestellt werden, aber nicht beide. net-id: NIC an Netzwerk mit dieser " +"UUID binden, port-id: NIC an Port mit dieser UUID binden, v4-fixed-ip: Feste " +"IPv4-Adresse für NIC (optional), v6-fixed-ip: Feste IPv6-Adresse für NIC " +"(optional)." + msgid "Hints for the scheduler (optional extension)" msgstr "Hinweise für den Planer (optionale Erweiterung)" -#: openstackclient/compute/v2/server.py:301 msgid "" -"Use specified volume as the config drive, or 'True' to use an ephemeral " -"drive" +"Use specified volume as the config drive, or 'True' to use an ephemeral drive" msgstr "" -"Benutzerdefinierter Datenträger als Konfigurationslaufwerk oder 'True', " -"um ein flüchtiges Laufwerk zu verwenden" +"Benutzerdefinierter Datenträger als Konfigurationslaufwerk oder 'True', um " +"ein flüchtiges Laufwerk zu verwenden" -#: openstackclient/compute/v2/server.py:309 msgid "Minimum number of servers to launch (default=1)" msgstr "Minimale Anzahl der zu startenden Server (Standard=1)" -#: openstackclient/compute/v2/server.py:316 msgid "Maximum number of servers to launch (default=1)" msgstr "Maximale Anzahl der zu startenden Server (Standard=1)" -#: openstackclient/compute/v2/server.py:321 msgid "Wait for build to complete" msgstr "Warten Sie, bis die Herstellung abgeschlossen ist" -#: openstackclient/compute/v2/server.py:361 msgid "min instances should be <= max instances" msgstr "min. Instanzen sollten <= max. Instanzen sein" -#: openstackclient/compute/v2/server.py:364 msgid "min instances should be > 0" msgstr "min. Instanzen sollten > 0 sein" -#: openstackclient/compute/v2/server.py:367 msgid "max instances should be > 0" msgstr "max. Instanzen sollten > 0 sein" -#: openstackclient/compute/v2/server.py:403 msgid "either net-id or port-id should be specified but not both" msgstr "entweder net-id oder port-id sollten angegeben sein, aber nicht beide" -#: openstackclient/compute/v2/server.py:425 msgid "can't create server with port specified since neutron not enabled" msgstr "" -"Server mit dem angegebenen Port kann nicht erstellt werden, da Neutron " -"nicht aktiviert ist" +"Server mit dem angegebenen Port kann nicht erstellt werden, da Neutron nicht " +"aktiviert ist" -#: openstackclient/compute/v2/server.py:490 #, python-format msgid "Error creating server: %s" msgstr "Fehler beim Erstellen des Servers: %s" -#: openstackclient/compute/v2/server.py:492 msgid "" "\n" "Error creating server" @@ -230,20 +168,16 @@ msgstr "" "\n" "Fehler beim Erstellen des Servers" -#: openstackclient/compute/v2/server.py:514 msgid "Name of new image (default is server name)" msgstr "Name des neuen Abbilds (Servername ist Standard)" -#: openstackclient/compute/v2/server.py:519 msgid "Wait for image create to complete" msgstr "Warten Sie, bis die Abbilderstellung abgeschlossen ist" -#: openstackclient/compute/v2/server.py:549 #, python-format msgid "Error creating server snapshot: %s" msgstr "Fehler beim Erstellen der Server-Schattenkopie: %s" -#: openstackclient/compute/v2/server.py:551 msgid "" "\n" "Error creating server snapshot" @@ -251,81 +185,61 @@ msgstr "" "\n" "Fehler beim Erstellen der Server-Schattenkopie" -#: openstackclient/compute/v2/server.py:573 msgid "Server(s) to delete (name or ID)" msgstr "Zu löschende(r) Server (Name oder Kennung)" -#: openstackclient/compute/v2/server.py:597 msgid "Only return instances that match the reservation" msgstr "Nur Instanzen zurückgeben, die der Einschränkung entsprechen" -#: openstackclient/compute/v2/server.py:602 msgid "Regular expression to match IP addresses" msgstr "Regulärer Ausdruck zum Abgleichen mit IP-Adressen" -#: openstackclient/compute/v2/server.py:607 msgid "Regular expression to match IPv6 addresses" msgstr "Regulärer Ausdruck zum Abgleichen mit IPv6-Adressen" -#: openstackclient/compute/v2/server.py:612 msgid "Regular expression to match names" msgstr "Regulärer Ausdruck zum Abgleichen mit Namen" -#: openstackclient/compute/v2/server.py:617 msgid "Regular expression to match instance name (admin only)" -msgstr "Regulärer Ausdruck zum Abgleichen des Instanznamens (nur Administrator) " +msgstr "" +"Regulärer Ausdruck zum Abgleichen des Instanznamens (nur Administrator) " -#: openstackclient/compute/v2/server.py:623 msgid "Search by server status" msgstr "Nach Serverstatus suchen" -#: openstackclient/compute/v2/server.py:628 msgid "Search by flavor" msgstr "Nach Variante suchen" -#: openstackclient/compute/v2/server.py:633 msgid "Search by image" msgstr "Nach Bild suchen" -#: openstackclient/compute/v2/server.py:638 msgid "Search by hostname" msgstr "Nach Hostname suchen" -#: openstackclient/compute/v2/server.py:644 msgid "Include all projects (admin only)" msgstr "Alle Projekte miteinbeziehen (nur Administrator)" -#: openstackclient/compute/v2/server.py:760 msgid "Target hostname" msgstr "Zielhostname" -#: openstackclient/compute/v2/server.py:768 msgid "Perform a shared live migration (default)" msgstr "Gemeinsame Live-Migration ausführen (Standard)" -#: openstackclient/compute/v2/server.py:774 msgid "Perform a block live migration" msgstr "Blockorientierte Live-Migration ausführen" -#: openstackclient/compute/v2/server.py:781 msgid "Allow disk over-commit on the destination host" msgstr "Festplattenüberladung auf dem Zielhost erlauben" -#: openstackclient/compute/v2/server.py:788 msgid "Do not over-commit disk on the destination host (default)" msgstr "Festplatte auf dem Zielhost nicht überladen (Standard)" -#: openstackclient/compute/v2/server.py:794 -#: openstackclient/compute/v2/server.py:1095 msgid "Wait for resize to complete" msgstr "Warten Sie, bis die Größenänderung abgeschlossen ist" -#: openstackclient/compute/v2/server.py:822 -#: openstackclient/compute/v2/server.py:1120 msgid "Complete\n" msgstr "Fertig\n" -#: openstackclient/compute/v2/server.py:824 msgid "" "\n" "Error migrating server" @@ -333,19 +247,15 @@ msgstr "" "\n" "Fehler beim Migrieren des Servers" -#: openstackclient/compute/v2/server.py:871 msgid "Perform a hard reboot" msgstr "Harten Neustart ausführen" -#: openstackclient/compute/v2/server.py:879 msgid "Perform a soft reboot" msgstr "Weichen Neustart ausführen" -#: openstackclient/compute/v2/server.py:884 msgid "Wait for reboot to complete" msgstr "Warten Sie, bis der Neustart abgeschlossen ist" -#: openstackclient/compute/v2/server.py:901 msgid "" "\n" "Reboot complete\n" @@ -353,7 +263,6 @@ msgstr "" "\n" "Neustart abgeschlossen\n" -#: openstackclient/compute/v2/server.py:903 msgid "" "\n" "Error rebooting server\n" @@ -361,15 +270,12 @@ msgstr "" "\n" "Fehler beim Neustarten des Servers\n" -#: openstackclient/compute/v2/server.py:923 msgid "Recreate server from this image" msgstr "Server aus diesem Abbild neu erstellen" -#: openstackclient/compute/v2/server.py:933 msgid "Wait for rebuild to complete" msgstr "Warten Sie, bis die Wiederherstellung abgeschlossen ist" -#: openstackclient/compute/v2/server.py:954 msgid "" "\n" "Complete\n" @@ -377,7 +283,6 @@ msgstr "" "\n" "Fertig\n" -#: openstackclient/compute/v2/server.py:956 msgid "" "\n" "Error rebuilding server" @@ -385,31 +290,24 @@ msgstr "" "\n" "Fehler bei der Wiederherstellung des Servers" -#: openstackclient/compute/v2/server.py:973 msgid "Name or ID of server to use" msgstr "Name oder Kennung des zu verwendenden Servers" -#: openstackclient/compute/v2/server.py:978 msgid "Name or ID of security group to remove from server" msgstr "Name oder Kennung der vom Server zu entfernenden Sicherheitsgruppe" -#: openstackclient/compute/v2/server.py:1014 msgid "Volume to remove (name or ID)" msgstr "Zu entfernender Datenträger (Name oder Kennung)" -#: openstackclient/compute/v2/server.py:1080 msgid "Resize server to specified flavor" msgstr "Servergröße auf angegebene Variante ändern" -#: openstackclient/compute/v2/server.py:1085 msgid "Confirm server resize is complete" msgstr "Bestätigen Sie, ob die Größenänderung des Servers abgeschlossen ist" -#: openstackclient/compute/v2/server.py:1090 msgid "Restore server state before resize" msgstr "Serverstatus vor der Größenänderung wiederherstellen" -#: openstackclient/compute/v2/server.py:1122 msgid "" "\n" "Error resizing server" @@ -417,393 +315,285 @@ msgstr "" "\n" "Fehler bei der Größenänderung des Servers" -#: openstackclient/compute/v2/server.py:1174 msgid "Set new root password (interactive only)" msgstr "Neues root-Passwort festlegen (Nur interaktiv)" -#: openstackclient/compute/v2/server.py:1180 msgid "" "Property to add/change for this server (repeat option to set multiple " "properties)" msgstr "" -"Zu hinzufügende/ändernde Eigenschaft für diesen Server (wiederholen Sie " -"die Option, um mehrere Eigenschaften festzulegen)" +"Zu hinzufügende/ändernde Eigenschaft für diesen Server (wiederholen Sie die " +"Option, um mehrere Eigenschaften festzulegen)" -#: openstackclient/compute/v2/server.py:1204 msgid "New password: " msgstr "Neues Passwort:" -#: openstackclient/compute/v2/server.py:1205 msgid "Retype new password: " msgstr "Neues Passwort erneut eingeben:" -#: openstackclient/compute/v2/server.py:1209 msgid "Passwords do not match, password unchanged" msgstr "Passwörter stimmen nicht überein, Passwort unverändert" -#: openstackclient/compute/v2/server.py:1229 msgid "Display server diagnostics information" msgstr "Serverdiagnoseinformationen anzeigen" -#: openstackclient/compute/v2/server.py:1242 msgid "Error retrieving diagnostics data" msgstr "Fehler beim Abrufen der Diagnosedaten" -#: openstackclient/compute/v2/server.py:1265 msgid "Login name (ssh -l option)" msgstr "Anmeldename (ssh -l Option)" -#: openstackclient/compute/v2/server.py:1276 msgid "Destination port (ssh -p option)" msgstr "Zielport (ssh -p Option)" -#: openstackclient/compute/v2/server.py:1288 msgid "Private key file (ssh -i option)" msgstr "Private Schlüsseldatei (ssh -i Option)" -#: openstackclient/compute/v2/server.py:1299 msgid "Options in ssh_config(5) format (ssh -o option)" msgstr "Optionen im ssh_config(5)-Format (ssh -o Option)" -#: openstackclient/compute/v2/server.py:1313 msgid "Use only IPv4 addresses" msgstr "Nur IPv4-Adressen verwenden" -#: openstackclient/compute/v2/server.py:1320 msgid "Use only IPv6 addresses" msgstr "Nur IPv6-Adressen verwenden" -#: openstackclient/compute/v2/server.py:1329 msgid "Use public IP address" msgstr "Öffentliche IP-Adresse verwenden" -#: openstackclient/compute/v2/server.py:1337 msgid "Use private IP address" msgstr "Private IP-Adresse verwenden" -#: openstackclient/compute/v2/server.py:1344 msgid "Use other IP address (public, private, etc)" msgstr "Andere IP-Adresse verwenden (öffentlich, privat, usw.)" -#: openstackclient/compute/v2/server.py:1521 msgid "Property key to remove from server (repeat to unset multiple values)" msgstr "" -"Vom Server zu entfernender Eigenschaftsschlüssel (zum Aufheben von " -"mehreren Werten wiederholen)" +"Vom Server zu entfernender Eigenschaftsschlüssel (zum Aufheben von mehreren " +"Werten wiederholen)" -#: openstackclient/identity/v2_0/catalog.py:73 -#: openstackclient/identity/v3/catalog.py:72 msgid "Service to display (type or name)" msgstr "Anzuzeigender Dienst (Typ oder Name)" -#: openstackclient/identity/v2_0/ec2creds.py:40 msgid "Specify an alternate project (default: current authenticated project)" msgstr "" -"Geben Sie ein alternatives Projekt an (Standard: aktuell " -"authentifiziertes Projekt)" +"Geben Sie ein alternatives Projekt an (Standard: aktuell authentifiziertes " +"Projekt)" -#: openstackclient/identity/v2_0/ec2creds.py:46 msgid "Specify an alternate user (default: current authenticated user)" msgstr "" "Geben Sie einen alternativen Benutzer an (Standard: aktuell " "authentifizierter Benutzer)" -#: openstackclient/identity/v2_0/ec2creds.py:95 -#: openstackclient/identity/v2_0/ec2creds.py:168 msgid "Credentials access key" msgstr "Anmeldedaten-Zugriffsschlüssel" -#: openstackclient/identity/v2_0/ec2creds.py:100 -#: openstackclient/identity/v2_0/ec2creds.py:130 -#: openstackclient/identity/v2_0/ec2creds.py:173 msgid "Specify a user" msgstr "Geben Sie einen Benutzer an" -#: openstackclient/identity/v2_0/endpoint.py:40 msgid "New endpoint service (name or ID)" msgstr "Neuer Endpunktdienst (Name oder Kennung)" -#: openstackclient/identity/v2_0/endpoint.py:46 msgid "New endpoint public URL (required)" msgstr "Öffentliche URL des neuen Endpunktes (erforderlich)" -#: openstackclient/identity/v2_0/endpoint.py:51 msgid "New endpoint admin URL" msgstr "Administrator-URL des neuen Endpunktes" -#: openstackclient/identity/v2_0/endpoint.py:56 msgid "New endpoint internal URL" msgstr "Interne URL des neuen Endpunktes" -#: openstackclient/identity/v2_0/endpoint.py:61 msgid "New endpoint region ID" msgstr "Neue Endpunkt-Regionskennung" -#: openstackclient/identity/v2_0/endpoint.py:93 msgid "Endpoint ID to delete" msgstr "Zu löschende Endpunktkennung" -#: openstackclient/identity/v2_0/endpoint.py:149 msgid "Endpoint ID to display" msgstr "Anzuzeigende Endpunktkennung" -#: openstackclient/identity/v2_0/project.py:41 msgid "New project name" msgstr "Name des neuen Projekts" -#: openstackclient/identity/v2_0/project.py:46 msgid "Project description" msgstr "Projektbeschreibung" -#: openstackclient/identity/v2_0/project.py:52 msgid "Enable project (default)" msgstr "Projekt aktivieren (Standardeinstellung)" -#: openstackclient/identity/v2_0/project.py:57 -#: openstackclient/identity/v2_0/project.py:194 msgid "Disable project" msgstr "Projekt deaktivieren" -#: openstackclient/identity/v2_0/project.py:63 msgid "Add a property to (repeat option to set multiple properties)" msgstr "" -"Fügen Sie eine Eigenschaft zu hinzu (wiederholen Sie die Option, " -"um mehrere Eigenschaften festzulegen)" +"Fügen Sie eine Eigenschaft zu hinzu (wiederholen Sie die Option, um " +"mehrere Eigenschaften festzulegen)" -#: openstackclient/identity/v2_0/project.py:69 -#: openstackclient/identity/v3/project.py:75 msgid "Return existing project" msgstr "Vorhandenes Projekt zurückgeben" -#: openstackclient/identity/v2_0/project.py:117 msgid "Project(s) to delete (name or ID)" msgstr "Zu löschende(s) Projekt(e) (Name oder Kennung)" -#: openstackclient/identity/v2_0/project.py:173 msgid "Project to modify (name or ID)" msgstr "Zu änderndes Projekt (Name oder Kennung)" -#: openstackclient/identity/v2_0/project.py:178 msgid "Set project name" msgstr "Projektname festlegen" -#: openstackclient/identity/v2_0/project.py:183 msgid "Set project description" msgstr "Projektbeschreibung festlegen" -#: openstackclient/identity/v2_0/project.py:189 msgid "Enable project" msgstr "Projekt aktivieren" -#: openstackclient/identity/v2_0/project.py:200 msgid "Set a project property (repeat option to set multiple properties)" msgstr "" "Legen Sie eine Projekteigenschaft fest (wiederholen Sie die Option, um " "mehrere Eigenschaften festzulegen)" -#: openstackclient/identity/v2_0/project.py:253 msgid "Project to display (name or ID)" msgstr "Anzuzeigendes Projekt (Name oder Kennung)" -#: openstackclient/identity/v2_0/role.py:41 msgid "Role to add to : (name or ID)" msgstr "Zu : hinzuzufügende Rolle (Name oder Kennung)" -#: openstackclient/identity/v2_0/role.py:47 -#: openstackclient/identity/v2_0/role.py:309 msgid "Include (name or ID)" msgstr " miteinbeziehen (Name oder Kennung)" -#: openstackclient/identity/v2_0/role.py:53 -#: openstackclient/identity/v2_0/role.py:315 msgid "Include (name or ID)" msgstr " miteinbeziehen (Name oder Kennung)" -#: openstackclient/identity/v2_0/role.py:87 msgid "New role name" msgstr "Name der neuen Rolle" -#: openstackclient/identity/v2_0/role.py:92 -#: openstackclient/identity/v3/role.py:158 msgid "Return existing role" msgstr "Vorhandene Rolle zurückgeben" -#: openstackclient/identity/v2_0/role.py:127 msgid "Role(s) to delete (name or ID)" msgstr "Zu löschende Rolle(n) (Name oder Kennung)" -#: openstackclient/identity/v2_0/role.py:194 -#: openstackclient/identity/v2_0/role.py:257 msgid "Project must be specified" msgstr "Projekt muss angegeben werden" -#: openstackclient/identity/v2_0/role.py:208 -#: openstackclient/identity/v2_0/role.py:263 msgid "User must be specified" msgstr "Benutzer muss angegeben werden" -#: openstackclient/identity/v2_0/role.py:236 msgid "User to list (name or ID)" msgstr "Aufzulistender Benutzer (Name oder Kennung)" -#: openstackclient/identity/v2_0/role.py:241 msgid "Filter users by (name or ID)" msgstr "Benutzer nach filtern (Name oder Kennung)" -#: openstackclient/identity/v2_0/role.py:303 msgid "Role to remove (name or ID)" msgstr "Zu entfernende Rolle (Name oder Kennung)" -#: openstackclient/identity/v2_0/role.py:344 msgid "Role to display (name or ID)" msgstr "Anzuzeigende Rolle (Name oder Kennung)" -#: openstackclient/identity/v2_0/service.py:42 msgid "New service type (compute, image, identity, volume, etc)" msgstr "Neuer Diensttyp (Berechnen, Abbild, Identität, Datenträger, usw.)" -#: openstackclient/identity/v2_0/service.py:53 msgid "New service name" msgstr "Name des neuen Dienstes" -#: openstackclient/identity/v2_0/service.py:58 msgid "New service description" msgstr "Beschreibung des neuen Dienstes" -#: openstackclient/identity/v2_0/service.py:78 msgid "" -"The argument --type is deprecated, use service create --name type instead." +"The argument --type is deprecated, use service create --name " +"type instead." msgstr "" -"Das Argument --type ist veraltet, verwenden Sie stattdessen service " -"create --name type." +"Das Argument --type ist veraltet, verwenden Sie stattdessen service create --" +"name type." -#: openstackclient/identity/v2_0/service.py:105 msgid "Service to delete (name or ID)" msgstr "Zu löschender Dienst (Name oder Kennung)" -#: openstackclient/identity/v2_0/service.py:156 msgid "Service to display (type, name or ID)" msgstr "Anzuzeigender Dienst (Typ, Name oder Kennung)" -#: openstackclient/identity/v2_0/service.py:162 msgid "Show service catalog information" msgstr "Dienstkataloginformation anzeigen" -#: openstackclient/identity/v2_0/service.py:180 #, python-format msgid "No service catalog with a type, name or ID of '%s' exists." msgstr "Kein Dienstkatalog mit Typ, Namen oder Kennung von '%s' ist vorhanden." -#: openstackclient/identity/v2_0/token.py:54 msgid "Token to be deleted" msgstr "Zu löschender Token" -#: openstackclient/identity/v2_0/user.py:40 msgid "New user name" msgstr "Neuer Benutzername" -#: openstackclient/identity/v2_0/user.py:45 msgid "Default project (name or ID)" msgstr "Standardprojekt (Name oder Kennung)" -#: openstackclient/identity/v2_0/user.py:50 -#: openstackclient/identity/v2_0/user.py:273 msgid "Set user password" msgstr "Benutzerpasswort festlegen" -#: openstackclient/identity/v2_0/user.py:56 -#: openstackclient/identity/v2_0/user.py:279 msgid "Prompt interactively for password" msgstr "Interaktiv nach dem Passwort abfragen" -#: openstackclient/identity/v2_0/user.py:61 -#: openstackclient/identity/v2_0/user.py:284 msgid "Set user email address" msgstr "E-Mail-Adresse des Benutzers festlegen" -#: openstackclient/identity/v2_0/user.py:67 -#: openstackclient/identity/v2_0/user.py:290 msgid "Enable user (default)" msgstr "Benutzer aktivieren (Standardeinstellung)" -#: openstackclient/identity/v2_0/user.py:72 -#: openstackclient/identity/v2_0/user.py:295 msgid "Disable user" msgstr "Benutzer deaktivieren" -#: openstackclient/identity/v2_0/user.py:77 -#: openstackclient/identity/v3/user.py:89 msgid "Return existing user" msgstr "Vorhandenen Benutzer zurückgeben" -#: openstackclient/identity/v2_0/user.py:141 msgid "User(s) to delete (name or ID)" msgstr "Zu löschende(r) Benutzer (Name oder Kennung)" -#: openstackclient/identity/v2_0/user.py:168 msgid "Filter users by project (name or ID)" msgstr "Benutzer nach Projekt filtern (Name oder Kennung)" -#: openstackclient/identity/v2_0/user.py:258 msgid "User to change (name or ID)" msgstr "Zu ändernder Benutzer (Name oder Kennung)" -#: openstackclient/identity/v2_0/user.py:263 msgid "Set user name" msgstr "Benutzername festlegen" -#: openstackclient/identity/v2_0/user.py:268 msgid "Set default project (name or ID)" msgstr "Standardprojekt festlegen (Name oder Kennung)" -#: openstackclient/identity/v2_0/user.py:361 msgid "User to display (name or ID)" msgstr "Anzuzeigender Benutzer (Name oder Kennung)" -#: openstackclient/identity/v3/domain.py:62 msgid "Return existing domain" msgstr "Vorhandene Domäne zurückgeben" -#: openstackclient/identity/v3/group.py:133 msgid "Return existing group" msgstr "Vorhandene Gruppe zurückgeben" -#: openstackclient/identity/v3/region.py:39 msgid "New region ID" msgstr "Neue Regionskennung" -#: openstackclient/identity/v3/region.py:44 msgid "Parent region ID" msgstr "Übergeordnete Regionskennung" -#: openstackclient/identity/v3/region.py:49 -#: openstackclient/identity/v3/region.py:151 msgid "New region description" msgstr "Beschreibung des neuen Bereichs" -#: openstackclient/identity/v3/region.py:54 -#: openstackclient/identity/v3/region.py:156 msgid "New region url" msgstr "URL des neuen Bereichs" -#: openstackclient/identity/v3/region.py:86 msgid "Region ID to delete" msgstr "Zu löschende Regionskennung" -#: openstackclient/identity/v3/region.py:108 msgid "Filter by parent region ID" msgstr "Nach übergeordneter Regionskennung filtern" -#: openstackclient/identity/v3/region.py:141 msgid "Region to modify" msgstr "Zu ändernde Region" -#: openstackclient/identity/v3/region.py:146 msgid "New parent region ID" msgstr "Neue übergeordnete Regionskennung" -#: openstackclient/identity/v3/region.py:191 msgid "Region to display" msgstr "Anzuzeigende Region" - diff --git a/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po b/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po index 840eced0ad..978471a5b4 100644 --- a/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po +++ b/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po @@ -8,193 +8,103 @@ msgid "" msgstr "" "Project-Id-Version: python-openstackclient\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-04-20 06:04+0000\n" +"POT-Creation-Date: 2015-05-05 06:06+0000\n" "PO-Revision-Date: 2015-04-19 14:23+0000\n" "Last-Translator: openstackjenkins \n" -"Language-Team: Chinese (Taiwan) (http://www.transifex.com/projects/p" -"/python-openstackclient/language/zh_TW/)\n" +"Language-Team: Chinese (Taiwan) (http://www.transifex.com/projects/p/python-" +"openstackclient/language/zh_TW/)\n" "Plural-Forms: nplurals=1; plural=0\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 1.3\n" -#: openstackclient/api/auth.py:144 -msgid "Set a username with --os-username, OS_USERNAME, or auth.username\n" -msgstr "" - -#: openstackclient/api/auth.py:147 -msgid "" -"Set an authentication URL, with --os-auth-url, OS_AUTH_URL or " -"auth.auth_url\n" -msgstr "" - -#: openstackclient/api/auth.py:153 -msgid "" -"Set a scope, such as a project or domain, with --os-project-name, " -"OS_PROJECT_NAME or auth.project_name" -msgstr "" - -#: openstackclient/api/auth.py:157 openstackclient/api/auth.py:163 -msgid "Set a token with --os-token, OS_TOKEN or auth.token\n" -msgstr "" - -#: openstackclient/api/auth.py:159 -msgid "Set a service AUTH_URL, with --os-auth-url, OS_AUTH_URL or auth.auth_url\n" -msgstr "" - -#: openstackclient/api/auth.py:165 -msgid "Set a service URL, with --os-url, OS_URL or auth.url\n" -msgstr "" - -#: openstackclient/compute/v2/availability_zone.py:72 -#: openstackclient/compute/v2/server.py:650 -#: openstackclient/identity/v2_0/endpoint.py:114 -#: openstackclient/identity/v2_0/project.py:145 -#: openstackclient/identity/v2_0/service.py:128 -#: openstackclient/identity/v2_0/user.py:174 msgid "List additional fields in output" msgstr "列出額外的欄位" -#: openstackclient/compute/v2/server.py:121 -#: openstackclient/compute/v2/server.py:158 -#: openstackclient/compute/v2/server.py:509 -#: openstackclient/compute/v2/server.py:721 -#: openstackclient/compute/v2/server.py:755 -#: openstackclient/compute/v2/server.py:838 -#: openstackclient/compute/v2/server.py:862 -#: openstackclient/compute/v2/server.py:917 -#: openstackclient/compute/v2/server.py:1009 -#: openstackclient/compute/v2/server.py:1049 -#: openstackclient/compute/v2/server.py:1075 -#: openstackclient/compute/v2/server.py:1140 -#: openstackclient/compute/v2/server.py:1164 -#: openstackclient/compute/v2/server.py:1223 -#: openstackclient/compute/v2/server.py:1260 -#: openstackclient/compute/v2/server.py:1418 -#: openstackclient/compute/v2/server.py:1442 -#: openstackclient/compute/v2/server.py:1466 -#: openstackclient/compute/v2/server.py:1490 -#: openstackclient/compute/v2/server.py:1514 msgid "Server (name or ID)" msgstr "伺服器(名稱或識別號)" -#: openstackclient/compute/v2/server.py:126 msgid "Security group to add (name or ID)" msgstr "要加入的安全性群組(名稱或識別號)" -#: openstackclient/compute/v2/server.py:163 msgid "Volume to add (name or ID)" msgstr "要加入的雲硬碟(名稱或識別號)" -#: openstackclient/compute/v2/server.py:168 msgid "Server internal device name for volume" msgstr "雲硬碟在雲實例內的裝置名稱" -#: openstackclient/compute/v2/server.py:208 -#: openstackclient/compute/v2/server.py:1169 msgid "New server name" msgstr "新雲實例名稱" -#: openstackclient/compute/v2/server.py:216 msgid "Create server from this image" msgstr "從此映像檔新增雲實例" -#: openstackclient/compute/v2/server.py:221 msgid "Create server from this volume" msgstr "從此雲硬碟新增雲實例" -#: openstackclient/compute/v2/server.py:227 msgid "Create server with this flavor" msgstr "以這個虛擬硬體樣板新增雲實例" -#: openstackclient/compute/v2/server.py:234 msgid "Security group to assign to this server (repeat for multiple groups)" msgstr "要指定到此雲實例的安全性群組(為多個群組重復指定)" -#: openstackclient/compute/v2/server.py:240 msgid "Keypair to inject into this server (optional extension)" msgstr "要注入到此伺服器的密鑰對(選用)" -#: openstackclient/compute/v2/server.py:246 msgid "Set a property on this server (repeat for multiple values)" msgstr "為此伺服器設定屬性(為多個值重複設定)" -#: openstackclient/compute/v2/server.py:254 msgid "File to inject into image before boot (repeat for multiple files)" msgstr "在開機前要注入映像檔的檔案(為多個檔案重復指定)" -#: openstackclient/compute/v2/server.py:260 msgid "User data file to serve from the metadata server" msgstr "來自詮釋資料伺服器要服務的用戶資料檔案" -#: openstackclient/compute/v2/server.py:265 msgid "Select an availability zone for the server" msgstr "為雲實例選擇可用的區域。" -#: openstackclient/compute/v2/server.py:272 msgid "" "Map block devices; map is ::: " "(optional extension)" -msgstr "映射區塊裝置;映射是 :::(選用)" - -#: openstackclient/compute/v2/server.py:282 -msgid "" -"Create a NIC on the server. Specify option multiple times to create " -"multiple NICs. Either net-id or port-id must be provided, but not both. " -"net-id: attach NIC to network with this UUID, port-id: attach NIC to port" -" with this UUID, v4-fixed-ip: IPv4 fixed address for NIC (optional), v6" -"-fixed-ip: IPv6 fixed address for NIC (optional)." msgstr "" +"映射區塊裝置;映射是 :::(選用)" -#: openstackclient/compute/v2/server.py:295 msgid "Hints for the scheduler (optional extension)" msgstr "給排程器的提示(選用)" -#: openstackclient/compute/v2/server.py:301 msgid "" -"Use specified volume as the config drive, or 'True' to use an ephemeral " -"drive" +"Use specified volume as the config drive, or 'True' to use an ephemeral drive" msgstr "使用指定的雲硬碟為設定檔硬碟,或「True」來使用暫時性硬碟" -#: openstackclient/compute/v2/server.py:309 msgid "Minimum number of servers to launch (default=1)" msgstr "最少要發動的雲實例(預設為 1)" -#: openstackclient/compute/v2/server.py:316 msgid "Maximum number of servers to launch (default=1)" msgstr "最多要發動的雲實例(預設為 1)" -#: openstackclient/compute/v2/server.py:321 msgid "Wait for build to complete" msgstr "等待完成建立" -#: openstackclient/compute/v2/server.py:361 msgid "min instances should be <= max instances" msgstr "雲實例發動的最少值不應大於最大值" -#: openstackclient/compute/v2/server.py:364 msgid "min instances should be > 0" msgstr "雲實例發動的最少值要大於 0" -#: openstackclient/compute/v2/server.py:367 msgid "max instances should be > 0" msgstr "雲實例發動的最大值要大於 0" -#: openstackclient/compute/v2/server.py:403 msgid "either net-id or port-id should be specified but not both" msgstr "任選網路識別號或接口識別號,但不能兩者都指定" -#: openstackclient/compute/v2/server.py:425 msgid "can't create server with port specified since neutron not enabled" msgstr "Neutron 未啟用時,不能以指定的接口來新增雲實例" -#: openstackclient/compute/v2/server.py:490 #, python-format msgid "Error creating server: %s" msgstr "新增雲實例時出錯:%s" -#: openstackclient/compute/v2/server.py:492 msgid "" "\n" "Error creating server" @@ -202,20 +112,16 @@ msgstr "" "\n" "新增雲實例時出錯" -#: openstackclient/compute/v2/server.py:514 msgid "Name of new image (default is server name)" msgstr "新映像檔的名稱(預設為雲實例名稱)" -#: openstackclient/compute/v2/server.py:519 msgid "Wait for image create to complete" msgstr "等待映像檔新增完成" -#: openstackclient/compute/v2/server.py:549 #, python-format msgid "Error creating server snapshot: %s" msgstr "新增雲實例即時存檔時出錯:%s" -#: openstackclient/compute/v2/server.py:551 msgid "" "\n" "Error creating server snapshot" @@ -223,81 +129,60 @@ msgstr "" "\n" "新增雲實例即時存檔時出錯" -#: openstackclient/compute/v2/server.py:573 msgid "Server(s) to delete (name or ID)" msgstr "要刪除的雲實例(名稱或識別號)" -#: openstackclient/compute/v2/server.py:597 msgid "Only return instances that match the reservation" msgstr "只回傳要保留的雲實例" -#: openstackclient/compute/v2/server.py:602 msgid "Regular expression to match IP addresses" msgstr "以正規式來匹配 IP 位址" -#: openstackclient/compute/v2/server.py:607 msgid "Regular expression to match IPv6 addresses" msgstr "以正規式來匹配 IPv6 位址" -#: openstackclient/compute/v2/server.py:612 msgid "Regular expression to match names" msgstr "以正規式來匹配名稱" -#: openstackclient/compute/v2/server.py:617 msgid "Regular expression to match instance name (admin only)" msgstr "以正規式來匹配雲實例名稱(管理員專用)" -#: openstackclient/compute/v2/server.py:623 msgid "Search by server status" msgstr "以雲實例狀態來尋找" -#: openstackclient/compute/v2/server.py:628 msgid "Search by flavor" msgstr "以虛擬硬體樣板來尋找" -#: openstackclient/compute/v2/server.py:633 msgid "Search by image" msgstr "以映像檔來尋找" -#: openstackclient/compute/v2/server.py:638 msgid "Search by hostname" msgstr "以主機名稱來尋找" -#: openstackclient/compute/v2/server.py:644 msgid "Include all projects (admin only)" msgstr "包括所有的專案(管理員專用)" -#: openstackclient/compute/v2/server.py:760 msgid "Target hostname" msgstr "目標主機名稱" -#: openstackclient/compute/v2/server.py:768 msgid "Perform a shared live migration (default)" msgstr "覆行已分享的即時轉移(預設)" -#: openstackclient/compute/v2/server.py:774 msgid "Perform a block live migration" msgstr "覆行區塊的即時轉移" -#: openstackclient/compute/v2/server.py:781 msgid "Allow disk over-commit on the destination host" msgstr "允許目標主機過量使用" -#: openstackclient/compute/v2/server.py:788 msgid "Do not over-commit disk on the destination host (default)" msgstr "不準目標主機過量使用(預設)" -#: openstackclient/compute/v2/server.py:794 -#: openstackclient/compute/v2/server.py:1095 msgid "Wait for resize to complete" msgstr "等待調整容量完成" -#: openstackclient/compute/v2/server.py:822 -#: openstackclient/compute/v2/server.py:1120 msgid "Complete\n" msgstr "完成\n" -#: openstackclient/compute/v2/server.py:824 msgid "" "\n" "Error migrating server" @@ -305,19 +190,15 @@ msgstr "" "\n" "轉移雲實例出錯" -#: openstackclient/compute/v2/server.py:871 msgid "Perform a hard reboot" msgstr "履行強制重開機" -#: openstackclient/compute/v2/server.py:879 msgid "Perform a soft reboot" msgstr "履行重開機" -#: openstackclient/compute/v2/server.py:884 msgid "Wait for reboot to complete" msgstr "等待重開機完成" -#: openstackclient/compute/v2/server.py:901 msgid "" "\n" "Reboot complete\n" @@ -325,7 +206,6 @@ msgstr "" "\n" "重開機完成\n" -#: openstackclient/compute/v2/server.py:903 msgid "" "\n" "Error rebooting server\n" @@ -333,15 +213,12 @@ msgstr "" "\n" "重開雲實例出錯\n" -#: openstackclient/compute/v2/server.py:923 msgid "Recreate server from this image" msgstr "從此映像檔重建雲實例" -#: openstackclient/compute/v2/server.py:933 msgid "Wait for rebuild to complete" msgstr "等待重建完成" -#: openstackclient/compute/v2/server.py:954 msgid "" "\n" "Complete\n" @@ -349,7 +226,6 @@ msgstr "" "\n" "完成\n" -#: openstackclient/compute/v2/server.py:956 msgid "" "\n" "Error rebuilding server" @@ -357,31 +233,24 @@ msgstr "" "\n" "重建雲實例出錯" -#: openstackclient/compute/v2/server.py:973 msgid "Name or ID of server to use" msgstr "要用的雲實例名稱或識別號" -#: openstackclient/compute/v2/server.py:978 msgid "Name or ID of security group to remove from server" msgstr "要從雲實例移除的安全性群組名稱或識別號" -#: openstackclient/compute/v2/server.py:1014 msgid "Volume to remove (name or ID)" msgstr "要移除的雲硬碟(名稱或識別號)" -#: openstackclient/compute/v2/server.py:1080 msgid "Resize server to specified flavor" msgstr "調整雲實例容量來符合指定的虛擬硬體樣板" -#: openstackclient/compute/v2/server.py:1085 msgid "Confirm server resize is complete" msgstr "確認調整雲實例容量完成" -#: openstackclient/compute/v2/server.py:1090 msgid "Restore server state before resize" msgstr "恢復雲實例狀態到未調整容量前" -#: openstackclient/compute/v2/server.py:1122 msgid "" "\n" "Error resizing server" @@ -389,379 +258,272 @@ msgstr "" "\n" "調整雲實例容量時出錯" -#: openstackclient/compute/v2/server.py:1174 msgid "Set new root password (interactive only)" msgstr "設定新密碼(只限互動)" -#: openstackclient/compute/v2/server.py:1180 msgid "" "Property to add/change for this server (repeat option to set multiple " "properties)" msgstr "要加入這個雲實例的屬性(重復這選項來設定多個屬性)" -#: openstackclient/compute/v2/server.py:1204 msgid "New password: " msgstr "新密碼:" -#: openstackclient/compute/v2/server.py:1205 msgid "Retype new password: " msgstr "重新輸入新密碼:" -#: openstackclient/compute/v2/server.py:1209 msgid "Passwords do not match, password unchanged" msgstr "密碼不一樣,未更換密碼" -#: openstackclient/compute/v2/server.py:1229 msgid "Display server diagnostics information" msgstr "顯示雲實例診斷資訊" -#: openstackclient/compute/v2/server.py:1242 msgid "Error retrieving diagnostics data" msgstr "獲得診斷資料時出錯" -#: openstackclient/compute/v2/server.py:1265 msgid "Login name (ssh -l option)" msgstr "登入名稱(ssh -l 選項)" -#: openstackclient/compute/v2/server.py:1276 msgid "Destination port (ssh -p option)" msgstr "目標埠口(ssh -p 選項)" -#: openstackclient/compute/v2/server.py:1288 msgid "Private key file (ssh -i option)" msgstr "私鑰檔案(ssh -i 選項)" -#: openstackclient/compute/v2/server.py:1299 msgid "Options in ssh_config(5) format (ssh -o option)" msgstr "ssh_config(5) 格式的選項(ssh -o 選項)" -#: openstackclient/compute/v2/server.py:1313 msgid "Use only IPv4 addresses" msgstr "只使用 IPv4 位址" -#: openstackclient/compute/v2/server.py:1320 msgid "Use only IPv6 addresses" msgstr "只使用 IPv6 位址" -#: openstackclient/compute/v2/server.py:1329 msgid "Use public IP address" msgstr "使用公開 IP 位址" -#: openstackclient/compute/v2/server.py:1337 msgid "Use private IP address" msgstr "使用私人 IP 位址" -#: openstackclient/compute/v2/server.py:1344 msgid "Use other IP address (public, private, etc)" msgstr "使用其他 IP 位址(公開、私人等等)" -#: openstackclient/compute/v2/server.py:1521 msgid "Property key to remove from server (repeat to unset multiple values)" msgstr "要從雲實例上移除的屬性鍵(重復來取消選擇多個值)" -#: openstackclient/identity/v2_0/catalog.py:73 -#: openstackclient/identity/v3/catalog.py:72 msgid "Service to display (type or name)" msgstr "要顯示的伺服器(類型或名稱)" -#: openstackclient/identity/v2_0/ec2creds.py:40 msgid "Specify an alternate project (default: current authenticated project)" msgstr "指定替代的專案(預設值:目前已認證的專案)" -#: openstackclient/identity/v2_0/ec2creds.py:46 msgid "Specify an alternate user (default: current authenticated user)" msgstr "指定替代的用戶(預設值:目前已認證的用戶)" -#: openstackclient/identity/v2_0/ec2creds.py:95 -#: openstackclient/identity/v2_0/ec2creds.py:168 msgid "Credentials access key" msgstr "憑鑰存取密鑰" -#: openstackclient/identity/v2_0/ec2creds.py:100 -#: openstackclient/identity/v2_0/ec2creds.py:130 -#: openstackclient/identity/v2_0/ec2creds.py:173 msgid "Specify a user" msgstr "指定用戶" -#: openstackclient/identity/v2_0/endpoint.py:40 msgid "New endpoint service (name or ID)" msgstr "新端點伺服器(名稱或識別號)" -#: openstackclient/identity/v2_0/endpoint.py:46 msgid "New endpoint public URL (required)" msgstr "新端點公開網址(需要)" -#: openstackclient/identity/v2_0/endpoint.py:51 msgid "New endpoint admin URL" msgstr "新端點管理員網址" -#: openstackclient/identity/v2_0/endpoint.py:56 msgid "New endpoint internal URL" msgstr "新端點內部網址" -#: openstackclient/identity/v2_0/endpoint.py:61 msgid "New endpoint region ID" msgstr "新端點地區識別號" -#: openstackclient/identity/v2_0/endpoint.py:93 msgid "Endpoint ID to delete" msgstr "要刪除的端點識別號" -#: openstackclient/identity/v2_0/endpoint.py:149 msgid "Endpoint ID to display" msgstr "要顯示的端點識別號" -#: openstackclient/identity/v2_0/project.py:41 msgid "New project name" msgstr "新專案名稱" -#: openstackclient/identity/v2_0/project.py:46 msgid "Project description" msgstr "專案描述" -#: openstackclient/identity/v2_0/project.py:52 msgid "Enable project (default)" msgstr "啟用專案(預設)" -#: openstackclient/identity/v2_0/project.py:57 -#: openstackclient/identity/v2_0/project.py:194 msgid "Disable project" msgstr "關閉專案" -#: openstackclient/identity/v2_0/project.py:63 msgid "Add a property to (repeat option to set multiple properties)" msgstr "加入屬性到 (重復這選項來設定多個屬性)" -#: openstackclient/identity/v2_0/project.py:69 -#: openstackclient/identity/v3/project.py:75 msgid "Return existing project" msgstr "回傳存在的專案" -#: openstackclient/identity/v2_0/project.py:117 msgid "Project(s) to delete (name or ID)" msgstr "要刪除的專案(名稱或識別號)" -#: openstackclient/identity/v2_0/project.py:173 msgid "Project to modify (name or ID)" msgstr "要更改的專案(名稱或識別號)" -#: openstackclient/identity/v2_0/project.py:178 msgid "Set project name" msgstr "設定專案名稱" -#: openstackclient/identity/v2_0/project.py:183 msgid "Set project description" msgstr "設定專案描述" -#: openstackclient/identity/v2_0/project.py:189 msgid "Enable project" msgstr "啟用專案" -#: openstackclient/identity/v2_0/project.py:200 msgid "Set a project property (repeat option to set multiple properties)" msgstr "設定專案屬性(重復這選項來設定多個屬性)" -#: openstackclient/identity/v2_0/project.py:253 msgid "Project to display (name or ID)" msgstr "要顯示的專案(名稱或識別號)" -#: openstackclient/identity/v2_0/role.py:41 msgid "Role to add to : (name or ID)" msgstr "加入角色到 :(名稱或識別號)" -#: openstackclient/identity/v2_0/role.py:47 -#: openstackclient/identity/v2_0/role.py:309 msgid "Include (name or ID)" msgstr "包括 (名稱或識別號)" -#: openstackclient/identity/v2_0/role.py:53 -#: openstackclient/identity/v2_0/role.py:315 msgid "Include (name or ID)" msgstr "包括 (名稱或識別號)" -#: openstackclient/identity/v2_0/role.py:87 msgid "New role name" msgstr "新角色名稱" -#: openstackclient/identity/v2_0/role.py:92 -#: openstackclient/identity/v3/role.py:158 msgid "Return existing role" msgstr "回傳存在的角色" -#: openstackclient/identity/v2_0/role.py:127 msgid "Role(s) to delete (name or ID)" msgstr "要刪除的角色(名稱或識別號)" -#: openstackclient/identity/v2_0/role.py:194 -#: openstackclient/identity/v2_0/role.py:257 msgid "Project must be specified" msgstr "必須指定專案" -#: openstackclient/identity/v2_0/role.py:208 -#: openstackclient/identity/v2_0/role.py:263 msgid "User must be specified" msgstr "必須指定用戶" -#: openstackclient/identity/v2_0/role.py:236 msgid "User to list (name or ID)" msgstr "要列出的用戶(名稱或識別號)" -#: openstackclient/identity/v2_0/role.py:241 msgid "Filter users by (name or ID)" msgstr "以 來篩選用戶(名稱或識別號)" -#: openstackclient/identity/v2_0/role.py:303 msgid "Role to remove (name or ID)" msgstr "要移除的角色(名稱或識別號)" -#: openstackclient/identity/v2_0/role.py:344 msgid "Role to display (name or ID)" msgstr "要顯示的角色(名稱或識別號)" -#: openstackclient/identity/v2_0/service.py:42 msgid "New service type (compute, image, identity, volume, etc)" msgstr "新伺服器類型(compute、image、identity 或 volume 等等)" -#: openstackclient/identity/v2_0/service.py:53 msgid "New service name" msgstr "新伺服器名稱" -#: openstackclient/identity/v2_0/service.py:58 msgid "New service description" msgstr "新伺服器描述" -#: openstackclient/identity/v2_0/service.py:78 msgid "" -"The argument --type is deprecated, use service create --name type instead." -msgstr "已經淘汰 --type 參數,請用 service create --name 來代替。" +"The argument --type is deprecated, use service create --name " +"type instead." +msgstr "" +"已經淘汰 --type 參數,請用 service create --name 來代替。" -#: openstackclient/identity/v2_0/service.py:105 msgid "Service to delete (name or ID)" msgstr "要刪除的伺服器(名稱或識別號)" -#: openstackclient/identity/v2_0/service.py:156 msgid "Service to display (type, name or ID)" msgstr "要顯示的伺服器(類型、名稱或識別號)" -#: openstackclient/identity/v2_0/service.py:162 msgid "Show service catalog information" msgstr "顯示伺服器分類資訊" -#: openstackclient/identity/v2_0/service.py:180 #, python-format msgid "No service catalog with a type, name or ID of '%s' exists." msgstr "沒有相似「%s」類型、名稱或識別號的伺服器分類。" -#: openstackclient/identity/v2_0/token.py:54 msgid "Token to be deleted" msgstr "要刪除的記號" -#: openstackclient/identity/v2_0/user.py:40 msgid "New user name" msgstr "新用戶名稱" -#: openstackclient/identity/v2_0/user.py:45 msgid "Default project (name or ID)" msgstr "預設專案(名稱或識別號)" -#: openstackclient/identity/v2_0/user.py:50 -#: openstackclient/identity/v2_0/user.py:273 msgid "Set user password" msgstr "設定用戶密碼" -#: openstackclient/identity/v2_0/user.py:56 -#: openstackclient/identity/v2_0/user.py:279 msgid "Prompt interactively for password" msgstr "為密碼互動提示" -#: openstackclient/identity/v2_0/user.py:61 -#: openstackclient/identity/v2_0/user.py:284 msgid "Set user email address" msgstr "設定用戶電子信箱位址" -#: openstackclient/identity/v2_0/user.py:67 -#: openstackclient/identity/v2_0/user.py:290 msgid "Enable user (default)" msgstr "啟用用戶(預設)" -#: openstackclient/identity/v2_0/user.py:72 -#: openstackclient/identity/v2_0/user.py:295 msgid "Disable user" msgstr "關閉用戶" -#: openstackclient/identity/v2_0/user.py:77 -#: openstackclient/identity/v3/user.py:89 msgid "Return existing user" msgstr "回傳存在的用戶" -#: openstackclient/identity/v2_0/user.py:141 msgid "User(s) to delete (name or ID)" msgstr "要刪除的用戶(名稱或識別號)" -#: openstackclient/identity/v2_0/user.py:168 msgid "Filter users by project (name or ID)" msgstr "以專案篩選用戶(名稱或識別號)" -#: openstackclient/identity/v2_0/user.py:258 msgid "User to change (name or ID)" msgstr "要更換的用戶(名稱或識別號)" -#: openstackclient/identity/v2_0/user.py:263 msgid "Set user name" msgstr "設定用戶名稱" -#: openstackclient/identity/v2_0/user.py:268 msgid "Set default project (name or ID)" msgstr "設定預設專案(名稱或識別號)" -#: openstackclient/identity/v2_0/user.py:361 msgid "User to display (name or ID)" msgstr "要顯示的用戶(名稱或識別號)" -#: openstackclient/identity/v3/domain.py:62 msgid "Return existing domain" msgstr "回傳存在的地域" -#: openstackclient/identity/v3/group.py:133 msgid "Return existing group" msgstr "回傳存在的群組" -#: openstackclient/identity/v3/region.py:39 msgid "New region ID" msgstr "新地區識別號" -#: openstackclient/identity/v3/region.py:44 msgid "Parent region ID" msgstr "父地區識別號" -#: openstackclient/identity/v3/region.py:49 -#: openstackclient/identity/v3/region.py:151 msgid "New region description" msgstr "新地區描述" -#: openstackclient/identity/v3/region.py:54 -#: openstackclient/identity/v3/region.py:156 msgid "New region url" msgstr "新地區網址" -#: openstackclient/identity/v3/region.py:86 msgid "Region ID to delete" msgstr "要刪除的地區識別號" -#: openstackclient/identity/v3/region.py:108 msgid "Filter by parent region ID" msgstr "以父地區識別號來篩選" -#: openstackclient/identity/v3/region.py:141 msgid "Region to modify" msgstr "要更改的地區" -#: openstackclient/identity/v3/region.py:146 msgid "New parent region ID" msgstr "新父地區識別號" -#: openstackclient/identity/v3/region.py:191 msgid "Region to display" msgstr "要顯示的地區" - From 4ce3d1dbad5fc0f854180c9f9973585f5d41dbcd Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 6 May 2015 16:09:34 +0000 Subject: [PATCH 0054/3095] Updated from global requirements Change-Id: Ica958638a86fe3f740aaa37b3d40d57680afd103 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 2a9e8ff310..7913c24c53 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,7 +15,7 @@ oslo.serialization>=1.4.0 # Apache-2.0 python-glanceclient>=0.15.0 python-keystoneclient>=1.3.0 python-novaclient>=2.22.0 -python-cinderclient>=1.1.0 +python-cinderclient>=1.2.0 python-neutronclient>=2.3.11,<3 requests>=2.5.2 stevedore>=1.3.0 # Apache-2.0 From 62bb88f621140670c1c2f26397f97cd87fd66b18 Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Fri, 1 May 2015 12:21:08 -0600 Subject: [PATCH 0055/3095] Security group rule delete broken Nova client was changed to take a rule id for security group rule delete. https://github.com/openstack/python-novaclient/blob/master/novaclient/v2/security_group_rules.py#L72 Change-Id: I0a69f3f196a36f267ee85a651b09aa8d3c328121 Closes-Bug: #1450872 --- doc/source/backwards-incompatible.rst | 12 ++++++ openstackclient/compute/v2/security_group.py | 39 ++------------------ 2 files changed, 16 insertions(+), 35 deletions(-) diff --git a/doc/source/backwards-incompatible.rst b/doc/source/backwards-incompatible.rst index 817f2a3097..437f9324bb 100644 --- a/doc/source/backwards-incompatible.rst +++ b/doc/source/backwards-incompatible.rst @@ -35,6 +35,18 @@ List of Backwards Incompatible Changes * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1404073 * Commit: https://review.openstack.org/#/c/143242/ +3. Command `openstack security group rule delete` now requires rule id + + Previously, the command was `openstack security group rule delete --proto + [--src-ip --dst-port ] `, + whereas now it is: `openstack security group rule delete `. + + * In favor of: Using `openstack security group rule delete `. + * As of: 1.2.1 + * Removed in: NA + * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1450872 + * Commit: https://review.openstack.org/#/c/179446/ + For Developers ============== diff --git a/openstackclient/compute/v2/security_group.py b/openstackclient/compute/v2/security_group.py index 55405810d2..d860bf808e 100644 --- a/openstackclient/compute/v2/security_group.py +++ b/openstackclient/compute/v2/security_group.py @@ -328,28 +328,9 @@ class DeleteSecurityGroupRule(command.Command): def get_parser(self, prog_name): parser = super(DeleteSecurityGroupRule, self).get_parser(prog_name) parser.add_argument( - 'group', - metavar='', - help='Security group rule to delete (name or ID)', - ) - parser.add_argument( - "--proto", - metavar="", - default="tcp", - help="IP protocol (icmp, tcp, udp; default: tcp)", - ) - parser.add_argument( - "--src-ip", - metavar="", - default="0.0.0.0/0", - help="Source IP (may use CIDR notation; default: 0.0.0.0/0)", - ) - parser.add_argument( - "--dst-port", - metavar="", - action=parseractions.RangeAction, - help="Destination port, may be a range: 137:139 (default: 0; " - "only required for proto tcp and udp)", + 'rule', + metavar='', + help='Security group rule ID to delete', ) return parser @@ -357,19 +338,7 @@ def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute - group = utils.find_resource( - compute_client.security_groups, - parsed_args.group, - ) - from_port, to_port = parsed_args.dst_port - # sigh...delete by ID? - compute_client.security_group_rules.delete( - group.id, - parsed_args.proto, - from_port, - to_port, - parsed_args.src_ip, - ) + compute_client.security_group_rules.delete(parsed_args.rule) return From 0816cd79d47f16f24555fd5812abdc6f5f41287d Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 7 May 2015 23:37:29 +0000 Subject: [PATCH 0056/3095] Updated from global requirements Change-Id: I94752657b7753d277950168f9efcea7074fb25ec --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 7913c24c53..e5a9637952 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ # The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -pbr>=0.6,!=0.7,<1.0 +pbr>=0.11,<2.0 six>=1.9.0 Babel>=1.3 From c126a2ae5665de05dbdd26eb5d6ed16a08d78781 Mon Sep 17 00:00:00 2001 From: Roxana Gherle Date: Mon, 11 May 2015 16:48:21 -0700 Subject: [PATCH 0057/3095] Send the correct user-agent to Keystone When we execute an Openstack CLI command, keystone should log in Keystone access log that the user-agent that made the request was 'python-openstackclient' instead of the default 'python-keystoneclient'. Therefore, when we create the authentication session we need to send the explicit user-agent. Closes-Bug: #1453995 Change-Id: I75087fd4bb1ff1e6f2a911bc70bf8008268276bb --- openstackclient/common/clientmanager.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index ca5ece0d75..85e367c41c 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -30,6 +30,8 @@ PLUGIN_MODULES = [] +USER_AGENT = 'python-openstackclient' + class ClientCache(object): """Descriptor class for caching created client handles.""" @@ -163,6 +165,7 @@ def setup_auth(self): auth=self.auth, session=request_session, verify=self._verify, + user_agent=USER_AGENT, ) return From 3ca96ef93cda1a3f186febc0e241e2d4adb682eb Mon Sep 17 00:00:00 2001 From: Juan Antonio Osorio Robles Date: Wed, 6 May 2015 22:46:32 +0300 Subject: [PATCH 0058/3095] Enable specifing domains in "role add" If users, projects or groups are provided by name, there is a possibility of the existence other users/projects/groups with the same name in other domain. Even though this is not a problem if the actual ID is given instead of a name; this is mostly a usability enhancement. So, three options were added, one for specifying the domain where the user belongs, another one to specify the project's domain, and finally one to specify the group's domain. Change-Id: Iab04b0e04fa75ea5aa3723b8ea42a45f58a6cdb2 Closes-Bug: #1421328 --- doc/source/command-objects/role.rst | 21 ++++++ openstackclient/identity/common.py | 16 ++--- openstackclient/identity/v3/role.py | 102 ++++++++++++++++++---------- 3 files changed, 95 insertions(+), 44 deletions(-) diff --git a/doc/source/command-objects/role.rst b/doc/source/command-objects/role.rst index 02766b0366..5fcbe82543 100644 --- a/doc/source/command-objects/role.rst +++ b/doc/source/command-objects/role.rst @@ -37,6 +37,27 @@ Add role to a user or group in a project or domain .. versionadded:: 3 +.. option:: --user-domain + + Domain the user belongs to (name or ID). + This can be used in case collisions between user names exist. + + .. versionadded:: 3 + +.. option:: --group-domain + + Domain the group belongs to (name or ID). + This can be used in case collisions between group names exist. + + .. versionadded:: 3 + +.. option:: --project-domain + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + + .. versionadded:: 3 + .. describe:: Role to add to ``:`` (name or ID) diff --git a/openstackclient/identity/common.py b/openstackclient/identity/common.py index 2cc68c8dc2..a6e674c030 100644 --- a/openstackclient/identity/common.py +++ b/openstackclient/identity/common.py @@ -48,23 +48,23 @@ def find_domain(identity_client, name_or_id): domains.Domain) -def find_group(identity_client, name_or_id): +def find_group(identity_client, name_or_id, domain_id=None): return _find_identity_resource(identity_client.groups, name_or_id, - groups.Group) + groups.Group, domain_id=domain_id) -def find_project(identity_client, name_or_id): +def find_project(identity_client, name_or_id, domain_id=None): return _find_identity_resource(identity_client.projects, name_or_id, - projects.Project) + projects.Project, domain_id=domain_id) -def find_user(identity_client, name_or_id): +def find_user(identity_client, name_or_id, domain_id=None): return _find_identity_resource(identity_client.users, name_or_id, - users.User) + users.User, domain_id=domain_id) def _find_identity_resource(identity_client_manager, name_or_id, - resource_type): + resource_type, **kwargs): """Find a specific identity resource. Using keystoneclient's manager, attempt to find a specific resource by its @@ -92,7 +92,7 @@ def _find_identity_resource(identity_client_manager, name_or_id, try: identity_resource = utils.find_resource(identity_client_manager, - name_or_id) + name_or_id, **kwargs) if identity_resource is not None: return identity_resource except identity_exc.Forbidden: diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index 3dd998ba77..bc64f7f8b0 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -63,6 +63,27 @@ def get_parser(self, prog_name): metavar='', help='Include (name or ID)', ) + parser.add_argument( + '--user-domain', + metavar='', + help=('Domain the user belongs to (name or ID). ' + 'This can be used in case collisions between user names ' + 'exist.') + ) + parser.add_argument( + '--group-domain', + metavar='', + help=('Domain the group belongs to (name or ID). ' + 'This can be used in case collisions between group names ' + 'exist.') + ) + parser.add_argument( + '--project-domain', + metavar='', + help=('Domain the project belongs to (name or ID). ' + 'This can be used in case collisions between project names ' + 'exist.') + ) return parser def take_action(self, parsed_args): @@ -78,67 +99,76 @@ def take_action(self, parsed_args): parsed_args.role, ) + kwargs = {} if parsed_args.user and parsed_args.domain: - user = common.find_user( + user_domain_id = self._get_domain_id_if_requested( + parsed_args.user_domain) + kwargs['user'] = common.find_user( identity_client, parsed_args.user, - ) - domain = common.find_domain( + user_domain_id, + ).id + kwargs['domain'] = common.find_domain( identity_client, parsed_args.domain, - ) - identity_client.roles.grant( - role.id, - user=user.id, - domain=domain.id, - ) + ).id elif parsed_args.user and parsed_args.project: - user = common.find_user( + user_domain_id = self._get_domain_id_if_requested( + parsed_args.user_domain) + kwargs['user'] = common.find_user( identity_client, parsed_args.user, - ) - project = common.find_project( + user_domain_id, + ).id + project_domain_id = self._get_domain_id_if_requested( + parsed_args.project_domain) + kwargs['project'] = common.find_project( identity_client, parsed_args.project, - ) - identity_client.roles.grant( - role.id, - user=user.id, - project=project.id, - ) + project_domain_id, + ).id elif parsed_args.group and parsed_args.domain: - group = common.find_group( + group_domain_id = self._get_domain_id_if_requested( + parsed_args.group_domain) + kwargs['group'] = common.find_group( identity_client, parsed_args.group, - ) - domain = common.find_domain( + group_domain_id, + ).id + kwargs['domain'] = common.find_domain( identity_client, parsed_args.domain, - ) - identity_client.roles.grant( - role.id, - group=group.id, - domain=domain.id, - ) + ).id elif parsed_args.group and parsed_args.project: - group = common.find_group( + group_domain_id = self._get_domain_id_if_requested( + parsed_args.group_domain) + kwargs['group'] = common.find_group( identity_client, parsed_args.group, - ) - project = common.find_project( + group_domain_id, + ).id + project_domain_id = self._get_domain_id_if_requested( + parsed_args.project_domain) + kwargs['project'] = common.find_project( identity_client, parsed_args.project, - ) - identity_client.roles.grant( - role.id, - group=group.id, - project=project.id, - ) + project_domain_id, + ).id else: sys.stderr.write("Role not added, incorrect set of arguments \ provided. See openstack --help for more details\n") + return + + identity_client.roles.grant(role.id, **kwargs) return + def _get_domain_id_if_requested(self, domain_name_or_id): + if domain_name_or_id is None: + return None + domain = common.find_domain(self.app.client_manager.identity, + domain_name_or_id) + return domain.id + class CreateRole(show.ShowOne): """Create new role""" From 0fc248c5f0d07c6451b47bbdb4044cfd3b850f29 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Tue, 12 May 2015 14:35:41 +0000 Subject: [PATCH 0059/3095] Updated from global requirements Change-Id: I432b3224f23616a5181412f01d31d6cedcc2ff84 --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index e5a9637952..2196fb8e18 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,10 +12,10 @@ oslo.config>=1.11.0 # Apache-2.0 oslo.i18n>=1.5.0 # Apache-2.0 oslo.utils>=1.4.0 # Apache-2.0 oslo.serialization>=1.4.0 # Apache-2.0 -python-glanceclient>=0.15.0 +python-glanceclient>=0.17.1 python-keystoneclient>=1.3.0 python-novaclient>=2.22.0 -python-cinderclient>=1.2.0 +python-cinderclient>=1.2.1 python-neutronclient>=2.3.11,<3 requests>=2.5.2 stevedore>=1.3.0 # Apache-2.0 From d39b9c91c4801753895fac8047aa2371a63bb96a Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Fri, 15 May 2015 08:39:39 -0600 Subject: [PATCH 0060/3095] Fix functional test gate The functional test gate seems to be broken. The environment variables are not being passed through. Change-Id: Ied1f56877e4793c5e88e59d2afb7f1a5b3868560 --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index fdac2b3302..2ee9cdbc18 100644 --- a/tox.ini +++ b/tox.ini @@ -17,6 +17,7 @@ commands = flake8 [testenv:functional] setenv = OS_TEST_PATH=./functional/tests +passenv = OS_* [testenv:venv] commands = {posargs} From 29f29e44d3ed894ebac5fe26d4410bd31149145a Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Wed, 29 Apr 2015 05:11:58 -0600 Subject: [PATCH 0061/3095] Use format options for functional tests Use the format options for functional tests so we can have more assertEquals and less assertIn. Change-Id: I34e6c76b42964f7b596ea35e6b0354a242611cb4 --- .testr.conf | 1 + functional/common/test.py | 31 ++++++++++++++- functional/tests/object/v1/test_container.py | 42 ++++++++++++++++++++ 3 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 functional/tests/object/v1/test_container.py diff --git a/.testr.conf b/.testr.conf index 7388cc3c45..90c80c1873 100644 --- a/.testr.conf +++ b/.testr.conf @@ -6,3 +6,4 @@ test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \ test_id_option=--load-list $IDFILE test_list_option=--list +group_regex=([^\.]+\.)+ diff --git a/functional/common/test.py b/functional/common/test.py index 4a92def0d5..7beaf39aa4 100644 --- a/functional/common/test.py +++ b/functional/common/test.py @@ -28,12 +28,12 @@ def execute(cmd, fail_ok=False, merge_stderr=False): """Executes specified command for the given action.""" - cmd = shlex.split(cmd.encode('utf-8')) + cmdlist = shlex.split(cmd.encode('utf-8')) result = '' result_err = '' stdout = subprocess.PIPE stderr = subprocess.STDOUT if merge_stderr else subprocess.PIPE - proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr) + proc = subprocess.Popen(cmdlist, stdout=stdout, stderr=stderr) result, result_err = proc.communicate() if not fail_ok and proc.returncode != 0: raise exceptions.CommandFailed(proc.returncode, cmd, result, @@ -50,6 +50,33 @@ def openstack(cls, cmd, fail_ok=False): """Executes openstackclient command for the given action.""" return execute('openstack ' + cmd, fail_ok=fail_ok) + @classmethod + def get_show_opts(cls, fields=[]): + return ' -f value ' + ' '.join(['-c ' + it for it in fields]) + + @classmethod + def get_list_opts(cls, headers=[]): + opts = ' -f csv --quote none ' + opts = opts + ' '.join(['-c ' + it for it in headers]) + return opts + + @classmethod + def assertOutput(cls, expected, actual): + if expected != actual: + raise Exception(expected + ' != ' + actual) + + @classmethod + def assertInOutput(cls, expected, actual): + if expected not in actual: + raise Exception(expected + ' not in ' + actual) + + @classmethod + def cleanup_tmpfile(cls, filename): + try: + os.remove(filename) + except OSError: + pass + def assert_table_structure(self, items, field_names): """Verify that all items have keys listed in field_names.""" for item in items: diff --git a/functional/tests/object/v1/test_container.py b/functional/tests/object/v1/test_container.py new file mode 100644 index 0000000000..9ea14cdec6 --- /dev/null +++ b/functional/tests/object/v1/test_container.py @@ -0,0 +1,42 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import uuid + +from functional.common import test + + +class ContainerTests(test.TestCase): + """Functional tests for object containers. """ + NAME = uuid.uuid4().hex + + @classmethod + def setUpClass(cls): + opts = cls.get_list_opts(['container']) + raw_output = cls.openstack('container create ' + cls.NAME + opts) + expected = 'container\n' + cls.NAME + '\n' + cls.assertOutput(expected, raw_output) + + @classmethod + def tearDownClass(cls): + raw_output = cls.openstack('container delete ' + cls.NAME) + cls.assertOutput('', raw_output) + + def test_container_list(self): + opts = self.get_list_opts(['Name']) + raw_output = self.openstack('container list' + opts) + self.assertIn(self.NAME, raw_output) + + def test_container_show(self): + opts = self.get_show_opts(['container']) + raw_output = self.openstack('container show ' + self.NAME + opts) + self.assertEqual(self.NAME + "\n", raw_output) From 12f1bdde2a501ed71d5f678c6b80371b0c3b6f4c Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Fri, 1 May 2015 06:53:59 -0600 Subject: [PATCH 0062/3095] Fix insecure/verify options The insecure and verify options are broken so that verify always gets set to True. One problem was that the parsed args not defaulted so os_cloud_config thinks there was always a command line specified. The other problem was getattr was called on cloud config instead of get. Closes-Bug: #1450855 Change-Id: Ib5f004f51a7453cc8f5a89759e2031ec42e04a30 --- openstackclient/shell.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 5e2910215d..da985cbc4b 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -187,11 +187,13 @@ def build_option_parser(self, description, version): verify_group = parser.add_mutually_exclusive_group() verify_group.add_argument( '--verify', - action='store_true', + default=None, + action='store_false', help='Verify server certificate (default)', ) verify_group.add_argument( '--insecure', + default=None, action='store_true', help='Disable server certificate verification', ) @@ -224,12 +226,6 @@ def initialize_app(self, argv): # Parent __init__ parses argv into self.options super(OpenStackShell, self).initialize_app(argv) - # Resolve the verify/insecure exclusive pair here as cloud_config - # doesn't know about verify - self.options.insecure = ( - self.options.insecure and not self.options.verify - ) - # Set the default plugin to token_endpoint if rl and token are given if (self.options.url and self.options.token): # Use service token authentication @@ -253,10 +249,8 @@ def initialize_app(self, argv): if cacert: self.verify = cacert else: - self.verify = not getattr(self.cloud.config, 'insecure', False) - - # Neutralize verify option - self.options.verify = None + self.verify = not self.cloud.config.get('insecure', False) + self.verify = self.cloud.config.get('verify', self.verify) # Save default domain self.default_domain = self.options.os_default_domain From f8bbbdce2402390e1b73590b0fb7fcb360974848 Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Wed, 29 Apr 2015 08:56:50 -0600 Subject: [PATCH 0063/3095] Add support for keypair functional tests Change-Id: I5d4730f8229b50d2b162864c74d8eabfef6c0991 --- functional/tests/compute/__init__.py | 0 functional/tests/compute/v2/__init__.py | 0 functional/tests/compute/v2/test_keypair.py | 44 +++++++++++++++++++++ 3 files changed, 44 insertions(+) create mode 100644 functional/tests/compute/__init__.py create mode 100644 functional/tests/compute/v2/__init__.py create mode 100644 functional/tests/compute/v2/test_keypair.py diff --git a/functional/tests/compute/__init__.py b/functional/tests/compute/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/functional/tests/compute/v2/__init__.py b/functional/tests/compute/v2/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/functional/tests/compute/v2/test_keypair.py b/functional/tests/compute/v2/test_keypair.py new file mode 100644 index 0000000000..0909c2d62f --- /dev/null +++ b/functional/tests/compute/v2/test_keypair.py @@ -0,0 +1,44 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import uuid + +from functional.common import test + + +class KeypairTests(test.TestCase): + """Functional tests for compute keypairs. """ + NAME = uuid.uuid4().hex + HEADERS = ['Name'] + FIELDS = ['deleted_at', 'name', 'updated_at'] + + @classmethod + def setUpClass(cls): + private_key = cls.openstack('keypair create ' + cls.NAME) + cls.assertInOutput('-----BEGIN RSA PRIVATE KEY-----', private_key) + cls.assertInOutput('-----END RSA PRIVATE KEY-----', private_key) + + @classmethod + def tearDownClass(cls): + raw_output = cls.openstack('keypair delete ' + cls.NAME) + cls.assertOutput('', raw_output) + + def test_keypair_list(self): + opts = self.get_list_opts(self.HEADERS) + raw_output = self.openstack('keypair list' + opts) + self.assertIn(self.NAME, raw_output) + + def test_keypair_show(self): + opts = self.get_show_opts(self.FIELDS) + raw_output = self.openstack('keypair show ' + self.NAME + opts) + expected = "None\n" + self.NAME + "\nNone\n" + self.assertEqual(expected, raw_output) From 7ef9f97b954678972eb8e15309a7f0280df283d5 Mon Sep 17 00:00:00 2001 From: Amey Bhide Date: Fri, 22 May 2015 10:04:19 -0700 Subject: [PATCH 0064/3095] Fix client error while rescuing an instance Typo server._info -> server Closes-Bug: #1457983 Change-Id: I606505d73b3aae90bac636e960275926284b4ea6 --- openstackclient/compute/v2/server.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index e4e96ee7ff..41c1b904f6 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1054,11 +1054,11 @@ def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute - server = utils.find_resource( + _, body = utils.find_resource( compute_client.servers, parsed_args.server, - ).rescue() - return zip(*sorted(six.iteritems(server._info))) + ).rescue() + return zip(*sorted(six.iteritems(body))) class ResizeServer(command.Command): From 9186885553ed8ad32c5a863c9efb63dd570a99bb Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Fri, 22 May 2015 13:00:03 -0700 Subject: [PATCH 0065/3095] Remove checks for None dates in keypair functional tests Steve made a comment about this and I agree, we should try and keep these tests and simple as possible and this kind of thing doesn't add much value. Change-Id: I1eb73e5f38904ee6c74f6c7e27fc66cfe198619d --- functional/tests/compute/v2/test_keypair.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/functional/tests/compute/v2/test_keypair.py b/functional/tests/compute/v2/test_keypair.py index 0909c2d62f..1c6e1b1685 100644 --- a/functional/tests/compute/v2/test_keypair.py +++ b/functional/tests/compute/v2/test_keypair.py @@ -19,7 +19,7 @@ class KeypairTests(test.TestCase): """Functional tests for compute keypairs. """ NAME = uuid.uuid4().hex HEADERS = ['Name'] - FIELDS = ['deleted_at', 'name', 'updated_at'] + FIELDS = ['name'] @classmethod def setUpClass(cls): @@ -40,5 +40,4 @@ def test_keypair_list(self): def test_keypair_show(self): opts = self.get_show_opts(self.FIELDS) raw_output = self.openstack('keypair show ' + self.NAME + opts) - expected = "None\n" + self.NAME + "\nNone\n" - self.assertEqual(expected, raw_output) + self.assertEqual(self.NAME + "\n", raw_output) From ab5a89493f376cb561e25d19da0a37617e09e6e0 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Fri, 22 May 2015 17:11:04 -0400 Subject: [PATCH 0066/3095] Add some comments about current plugin support Change-Id: Ida9ac9956c611fec783ff98a01628c3d38e7b58f --- doc/source/plugins.rst | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/doc/source/plugins.rst b/doc/source/plugins.rst index 0635f29ebe..57b9692741 100644 --- a/doc/source/plugins.rst +++ b/doc/source/plugins.rst @@ -7,8 +7,40 @@ properly installed for OSC to find and use it. It utilizes the ``setuptools`` entry points mechanism to advertise to OSC the plugin module and supported commands. +Adoption +======== + +OpenStackClient promises to provide first class support for the following +OpenStack services: Compute, Identity, Image, Storage, Volume and Network. +These services are considered essential to any OpenStack deployment. + +Other OpenStack services, such as Orchestration or Telemetry may create an +OpenStackClient plugin. The source code will not be hosted by +OpenStackClient. + +The following is a list of projects and their status as an OpenStackClient +plugin. + +============================= ====================================== + project notes +============================= ====================================== +python-barbicanclient n/a +python-ceilometerclient n/a +python-congressclient using OpenStackClient +python-designateclient n/a +python-heatclient n/a +python-ironicclient patch in progress (https://review.openstack.org/#/c/171672/) +python-magnumclient n/a +python-manilaclient n/a +python-mistralclient n/a +python-muranoclient n/a +python-saharaclient n/a +python-troveclient n/a +python-zaqarclient n/a +============================= ====================================== + Implementation --------------- +============== Plugins are discovered by enumerating the entry points found under :py:mod:`openstack.cli.extension` and initializing the specified From ba21d463de948697b1e884cae883a0e64a4d35f6 Mon Sep 17 00:00:00 2001 From: Amey Bhide Date: Fri, 22 May 2015 11:24:09 -0700 Subject: [PATCH 0067/3095] Add missing properties to image set command Enable user to update the following image properties from OSC: container-format, disk-format, size Closes-Bug: #1446362 Change-Id: Id9f40f15702e8f14f0327a37fcc7d7971338c258 --- doc/source/command-objects/image.rst | 18 ++++++++++++++ openstackclient/image/v1/image.py | 25 +++++++++++++++++++- openstackclient/tests/image/v1/test_image.py | 9 +++++++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/doc/source/command-objects/image.rst b/doc/source/command-objects/image.rst index 83036a64b7..18658651c3 100644 --- a/doc/source/command-objects/image.rst +++ b/doc/source/command-objects/image.rst @@ -205,6 +205,9 @@ Set image properties [--owner ] [--min-disk ] [--min-ram ] + [--container-format ] + [--disk-format ] + [--size ] [--protected | --unprotected] [--public | --private] [--property [...] ] @@ -226,6 +229,21 @@ Set image properties Minimum RAM size needed to boot image, in megabytes +.. option:: --container-format + + Container format of image. + Acceptable formats: ['ami', 'ari', 'aki', 'bare', 'ovf'] + +.. option:: --disk-format + + Disk format of image. + Acceptable formats: ['ami', 'ari', 'aki', 'vhd', 'vmdk', 'raw', 'qcow2', + 'vdi', 'iso'] + +.. option:: --size + + Size of image data (in bytes) + .. option:: --protected Prevent image from being deleted diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index 830b99bab8..d4d45fa287 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -508,6 +508,28 @@ def get_parser(self, prog_name): type=int, help="Minimum RAM size needed to boot image, in megabytes", ) + container_choices = ["ami", "ari", "aki", "bare", "ovf"] + parser.add_argument( + "--container-format", + metavar="", + help=("Container format of image. Acceptable formats: %s" % + container_choices), + choices=container_choices + ) + disk_choices = ["ami", "ari", "aki", "vhd", "vmdk", "raw", "qcow2", + "vdi", "iso"] + parser.add_argument( + "--disk-format", + metavar="", + help="Disk format of image. Acceptable formats: %s" % disk_choices, + choices=disk_choices + ) + parser.add_argument( + "--size", + metavar="", + type=int, + help="Size of image data (in bytes)" + ) protected_group = parser.add_mutually_exclusive_group() protected_group.add_argument( "--protected", @@ -545,7 +567,8 @@ def take_action(self, parsed_args): image_client = self.app.client_manager.image kwargs = {} - copy_attrs = ('name', 'owner', 'min_disk', 'min_ram', 'properties') + copy_attrs = ('name', 'owner', 'min_disk', 'min_ram', 'properties', + 'container_format', 'disk_format', 'size') for attr in copy_attrs: if attr in parsed_args: val = getattr(parsed_args, attr, None) diff --git a/openstackclient/tests/image/v1/test_image.py b/openstackclient/tests/image/v1/test_image.py index ef7ca9eaa7..eec8cfa5c3 100644 --- a/openstackclient/tests/image/v1/test_image.py +++ b/openstackclient/tests/image/v1/test_image.py @@ -547,6 +547,9 @@ def test_image_set_options(self): '--owner', 'new-owner', '--min-disk', '2', '--min-ram', '4', + '--container-format', 'ovf', + '--disk-format', 'vmdk', + '--size', '35165824', image_fakes.image_name, ] verifylist = [ @@ -554,6 +557,9 @@ def test_image_set_options(self): ('owner', 'new-owner'), ('min_disk', 2), ('min_ram', 4), + ('container_format', 'ovf'), + ('disk_format', 'vmdk'), + ('size', 35165824), ('image', image_fakes.image_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -566,6 +572,9 @@ def test_image_set_options(self): 'owner': 'new-owner', 'min_disk': 2, 'min_ram': 4, + 'container_format': 'ovf', + 'disk_format': 'vmdk', + 'size': 35165824 } # ImageManager.update(image, **kwargs) self.images_mock.update.assert_called_with( From cae03c6834baedd45d3962025a6168a70322df64 Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Tue, 26 May 2015 14:50:26 -0600 Subject: [PATCH 0068/3095] Remove oslo incubator config I don't think we are using oslo incubator right now, if it is needed, this file can be added back. This will make tab complete more pleasant as well. Change-Id: I41957c1449f8278f23ec07bce920524caea01280 --- openstack-common.conf | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 openstack-common.conf diff --git a/openstack-common.conf b/openstack-common.conf deleted file mode 100644 index 81a189f7ba..0000000000 --- a/openstack-common.conf +++ /dev/null @@ -1,7 +0,0 @@ -[DEFAULT] - -# The list of modules to copy from openstack-common - - -# The base module to hold the copy of openstack.common -base=openstackclient From 575dcdfc8ef6d24ecb75663eb94d549a8beb30fe Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Tue, 26 May 2015 17:40:01 -0400 Subject: [PATCH 0069/3095] Remove oslo serialization requirement Recently oslo serialization has started to also include python-msgpack. Since we were only using it for json support, we should just use python's json support. Especially since it's only used by our tests. Change-Id: I0f8d939d6fca7608eaa3eea7ea4ca93296aaab3a --- openstackclient/tests/common/test_clientmanager.py | 3 ++- requirements.txt | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openstackclient/tests/common/test_clientmanager.py b/openstackclient/tests/common/test_clientmanager.py index 26cf49679d..4e2f46b4ce 100644 --- a/openstackclient/tests/common/test_clientmanager.py +++ b/openstackclient/tests/common/test_clientmanager.py @@ -12,12 +12,13 @@ # License for the specific language governing permissions and limitations # under the License. # + +import json as jsonutils import mock from requests_mock.contrib import fixture from keystoneclient.auth.identity import v2 as auth_v2 from keystoneclient import service_catalog -from oslo_serialization import jsonutils from openstackclient.api import auth from openstackclient.api import auth_plugin diff --git a/requirements.txt b/requirements.txt index 2196fb8e18..415d27a098 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,6 @@ os-client-config oslo.config>=1.11.0 # Apache-2.0 oslo.i18n>=1.5.0 # Apache-2.0 oslo.utils>=1.4.0 # Apache-2.0 -oslo.serialization>=1.4.0 # Apache-2.0 python-glanceclient>=0.17.1 python-keystoneclient>=1.3.0 python-novaclient>=2.22.0 From af9275178ba994a81ad38e3446579c5d783c1d10 Mon Sep 17 00:00:00 2001 From: Petr Blaho Date: Wed, 27 May 2015 16:26:44 +0200 Subject: [PATCH 0070/3095] Adds python-tuskarclient to list of plugins Change-Id: Ie3468d14186f69ec9203f11302b8a07dc93dcc5a --- doc/source/plugins.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/plugins.rst b/doc/source/plugins.rst index 57b9692741..fdc39ea4c9 100644 --- a/doc/source/plugins.rst +++ b/doc/source/plugins.rst @@ -36,6 +36,7 @@ python-mistralclient n/a python-muranoclient n/a python-saharaclient n/a python-troveclient n/a +python-tuskarclient using OpenStackClient python-zaqarclient n/a ============================= ====================================== From ce05822a3a328bffa8ec62ec72da946c0d187c2c Mon Sep 17 00:00:00 2001 From: Amey Bhide Date: Tue, 12 May 2015 17:01:07 -0700 Subject: [PATCH 0071/3095] Add support for v2 image set command Partial-Bug: #1405562 Change-Id: Ie30802d720a247748c45099c38450cc6c76bbc2a --- openstackclient/image/v2/image.py | 137 +++++++++++++++++++ openstackclient/tests/image/v2/test_image.py | 50 +++++++ setup.cfg | 1 + 3 files changed, 188 insertions(+) diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 0b2becb85a..3dd9833849 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -250,3 +250,140 @@ def take_action(self, parsed_args): info = {} info.update(image) return zip(*sorted(six.iteritems(info))) + + +class SetImage(show.ShowOne): + """Set image properties""" + + log = logging.getLogger(__name__ + ".SetImage") + + def get_parser(self, prog_name): + parser = super(SetImage, self).get_parser(prog_name) + parser.add_argument( + "image", + metavar="", + help="Image to modify (name or ID)" + ) + parser.add_argument( + "--name", + metavar="", + help="New image name" + ) + parser.add_argument( + "--architecture", + metavar="", + help="Operating system Architecture" + ) + parser.add_argument( + "--protected", + dest="protected", + action="store_true", + help="Prevent image from being deleted" + ) + parser.add_argument( + "--instance-uuid", + metavar="", + help="ID of instance used to create this image" + ) + parser.add_argument( + "--min-disk", + type=int, + metavar="", + help="Minimum disk size needed to boot image, in gigabytes" + ) + visibility_choices = ["public", "private"] + parser.add_argument( + "--visibility", + metavar="", + choices=visibility_choices, + help="Scope of image accessibility. Valid values: %s" + % visibility_choices + ) + help_msg = ("ID of image in Glance that should be used as the kernel" + " when booting an AMI-style image") + parser.add_argument( + "--kernel-id", + metavar="", + help=help_msg + ) + parser.add_argument( + "--os-version", + metavar="", + help="Operating system version as specified by the distributor" + ) + disk_choices = ["None", "ami", "ari", "aki", "vhd", "vmdk", "raw", + "qcow2", "vdi", "iso"] + help_msg = ("Format of the disk. Valid values: %s" % disk_choices) + parser.add_argument( + "--disk-format", + metavar="", + choices=disk_choices, + help=help_msg + ) + parser.add_argument( + "--os-distro", + metavar="", + help="Common name of operating system distribution" + ) + parser.add_argument( + "--owner", + metavar="", + help="New Owner of the image" + ) + msg = ("ID of image stored in Glance that should be used as the " + "ramdisk when booting an AMI-style image") + parser.add_argument( + "--ramdisk-id", + metavar="", + help=msg + ) + parser.add_argument( + "--min-ram", + type=int, + metavar="", + help="Amount of RAM (in MB) required to boot image" + ) + container_choices = ["None", "ami", "ari", "aki", "bare", "ovf", "ova"] + help_msg = ("Format of the container. Valid values: %s" + % container_choices) + parser.add_argument( + "--container-format", + metavar="", + choices=container_choices, + help=help_msg + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + image_client = self.app.client_manager.image + + kwargs = {} + copy_attrs = ('architecture', 'container_format', 'disk_format', + 'file', 'kernel_id', 'locations', 'name', + 'min_disk', 'min_ram', 'name', 'os_distro', 'os_version', + 'owner', 'prefix', 'progress', 'ramdisk_id', + 'visibility') + for attr in copy_attrs: + if attr in parsed_args: + val = getattr(parsed_args, attr, None) + if val: + # Only include a value in kwargs for attributes that are + # actually present on the command line + kwargs[attr] = val + if parsed_args.protected: + kwargs['protected'] = True + else: + kwargs['protected'] = False + + if not kwargs: + self.log.warning("No arguments specified") + return {}, {} + + image = utils.find_resource( + image_client.images, parsed_args.image) + + image = image_client.images.update(image.id, **kwargs) + info = {} + info.update(image) + return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index 73b5d39a4a..7cfaf08315 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -331,3 +331,53 @@ def test_image_show(self): self.assertEqual(image_fakes.IMAGE_columns, columns) self.assertEqual(image_fakes.IMAGE_data, data) + + +class TestImageSet(TestImage): + + def setUp(self): + super(TestImageSet, self).setUp() + # Set up the schema + self.model = warlock.model_factory( + image_fakes.IMAGE_schema, + schemas.SchemaBasedModel, + ) + + self.images_mock.get.return_value = self.model(**image_fakes.IMAGE) + self.images_mock.update.return_value = self.model(**image_fakes.IMAGE) + # Get the command object to test + self.cmd = image.SetImage(self.app, None) + + def test_image_set_options(self): + arglist = [ + '--name', 'new-name', + '--owner', 'new-owner', + '--min-disk', '2', + '--min-ram', '4', + image_fakes.image_id, + ] + verifylist = [ + ('name', 'new-name'), + ('owner', 'new-owner'), + ('min_disk', 2), + ('min_ram', 4), + ('image', image_fakes.image_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + kwargs = { + 'name': 'new-name', + 'owner': 'new-owner', + 'min_disk': 2, + 'min_ram': 4, + 'protected': False + } + # ImageManager.update(image, **kwargs) + self.images_mock.update.assert_called_with( + image_fakes.image_id, **kwargs) + + self.assertEqual(image_fakes.IMAGE_columns, columns) + self.assertEqual(image_fakes.IMAGE_data, data) diff --git a/setup.cfg b/setup.cfg index e2d7288488..9abc216044 100644 --- a/setup.cfg +++ b/setup.cfg @@ -313,6 +313,7 @@ openstack.image.v2 = image_list = openstackclient.image.v2.image:ListImage image_save = openstackclient.image.v2.image:SaveImage image_show = openstackclient.image.v2.image:ShowImage + image_set = openstackclient.image.v2.image:SetImage openstack.network.v2 = network_create = openstackclient.network.v2.network:CreateNetwork From a15d8f681a19e8f247328c2748b06cf6a383c672 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 27 May 2015 14:29:24 -0500 Subject: [PATCH 0072/3095] Create 1.3.0 release notes Change-Id: I1bd91490487b4c5eb6de7ea2aa09848b063071f1 --- doc/source/releases.rst | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/doc/source/releases.rst b/doc/source/releases.rst index 4def408e3c..9048cb9551 100644 --- a/doc/source/releases.rst +++ b/doc/source/releases.rst @@ -2,6 +2,37 @@ Release Notes ============= +1.3.0 (27 May 2015) +=================== + +* Need to specify domain with role list + Bug `1421328 `_ + +* Add support for keystone service providers + Bug `1435962 `_ + +* Can't update disk_format and container_format of image + Bug `1446362 `_ + +* Openstack --os-image-api-version 2 image show fails + Bug `1450829 `_ + +* The insecure option is ignored for command line options and OCC + Bug `1450855 `_ + +* Delete security group rule broken + Bug `1450872 `_ + +* Quota set sends invalid messages + Bug `1451640 `_ + +* Keystone Access Log logs "python-keystoneclient" as User-Agent even when request is made by openstack client + Bug `1453995 `_ + +* Client error while rescuing an instance + Bug `1457983 `_ + + 1.2.0 (30 Apr 2015) =================== From 211c14c638b9bf393932be42d4f04a4dd12a84bc Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Thu, 28 May 2015 11:01:13 -0600 Subject: [PATCH 0073/3095] Fix shell tests Personally, I think these tests should be removed, they are testing OCC. An internal OCC change on a private method broke this test. Change-Id: I760bf90ef8bd97e30be7838874337be695d45285 --- openstackclient/tests/test_shell.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/openstackclient/tests/test_shell.py b/openstackclient/tests/test_shell.py index 492b60de29..a3250f1753 100644 --- a/openstackclient/tests/test_shell.py +++ b/openstackclient/tests/test_shell.py @@ -559,7 +559,7 @@ def test_empty_env(self): @mock.patch("os_client_config.config.OpenStackConfig._load_config_file") def test_shell_args_cloud_no_vendor(self, config_mock): - config_mock.return_value = copy.deepcopy(CLOUD_1) + config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_1)) _shell = make_shell() fake_execute( @@ -596,8 +596,8 @@ def test_shell_args_cloud_no_vendor(self, config_mock): @mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file") @mock.patch("os_client_config.config.OpenStackConfig._load_config_file") def test_shell_args_cloud_public(self, config_mock, public_mock): - config_mock.return_value = copy.deepcopy(CLOUD_2) - public_mock.return_value = copy.deepcopy(PUBLIC_1) + config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_2)) + public_mock.return_value = ('file.yaml', copy.deepcopy(PUBLIC_1)) _shell = make_shell() fake_execute( @@ -636,8 +636,8 @@ def test_shell_args_cloud_public(self, config_mock, public_mock): @mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file") @mock.patch("os_client_config.config.OpenStackConfig._load_config_file") def test_shell_args_precedence(self, config_mock, vendor_mock): - config_mock.return_value = copy.deepcopy(CLOUD_2) - vendor_mock.return_value = copy.deepcopy(PUBLIC_1) + config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_2)) + vendor_mock.return_value = ('file.yaml', copy.deepcopy(PUBLIC_1)) _shell = make_shell() # Test command option overriding config file value @@ -690,8 +690,8 @@ def tearDown(self): @mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file") @mock.patch("os_client_config.config.OpenStackConfig._load_config_file") def test_shell_args_precedence_1(self, config_mock, vendor_mock): - config_mock.return_value = copy.deepcopy(CLOUD_2) - vendor_mock.return_value = copy.deepcopy(PUBLIC_1) + config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_2)) + vendor_mock.return_value = ('file.yaml', copy.deepcopy(PUBLIC_1)) _shell = make_shell() # Test env var @@ -731,8 +731,8 @@ def test_shell_args_precedence_1(self, config_mock, vendor_mock): @mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file") @mock.patch("os_client_config.config.OpenStackConfig._load_config_file") def test_shell_args_precedence_2(self, config_mock, vendor_mock): - config_mock.return_value = copy.deepcopy(CLOUD_2) - vendor_mock.return_value = copy.deepcopy(PUBLIC_1) + config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_2)) + vendor_mock.return_value = ('file.yaml', copy.deepcopy(PUBLIC_1)) _shell = make_shell() # Test command option overriding config file value From da083d171d64a8d20553718d137960a1bfae8eeb Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Thu, 28 May 2015 15:58:42 -0400 Subject: [PATCH 0074/3095] Small tweaks to osc plugin docs Change-Id: Ifbc63871e60e8ee843fdfefd8026dc4224fe4e13 --- doc/source/plugins.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/source/plugins.rst b/doc/source/plugins.rst index fdc39ea4c9..d603e10217 100644 --- a/doc/source/plugins.rst +++ b/doc/source/plugins.rst @@ -26,18 +26,18 @@ plugin. ============================= ====================================== python-barbicanclient n/a python-ceilometerclient n/a -python-congressclient using OpenStackClient +python-congressclient using only OpenStackClient python-designateclient n/a -python-heatclient n/a +python-heatclient plans on creating plugins python-ironicclient patch in progress (https://review.openstack.org/#/c/171672/) -python-magnumclient n/a +python-magnumclient sent note on ML about creating a plugin python-manilaclient n/a python-mistralclient n/a python-muranoclient n/a python-saharaclient n/a python-troveclient n/a -python-tuskarclient using OpenStackClient -python-zaqarclient n/a +python-tuskarclient using OpenStackClient and their own shell +python-zaqarclient using only OpenStackClient ============================= ====================================== Implementation From 2c4b87869be7afb26d6190bb4fd3301eb4061604 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 1 May 2015 12:25:58 -0500 Subject: [PATCH 0075/3095] Add cli tests for --verify and friends The tests that will change after the verify-always-true bug is fixed are currently commented out. The commented asserts show where we want to go. Also fixes --verify parser value Change-Id: I891e3ead5fc3da3ed2ecba5d2befd9e910778655 --- openstackclient/shell.py | 4 +-- openstackclient/tests/test_shell.py | 54 ++++++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index da985cbc4b..86d3fe3a8f 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -187,14 +187,14 @@ def build_option_parser(self, description, version): verify_group = parser.add_mutually_exclusive_group() verify_group.add_argument( '--verify', + action='store_true', default=None, - action='store_false', help='Verify server certificate (default)', ) verify_group.add_argument( '--insecure', - default=None, action='store_true', + default=None, help='Disable server certificate verification', ) parser.add_argument( diff --git a/openstackclient/tests/test_shell.py b/openstackclient/tests/test_shell.py index a3250f1753..a37d2ac832 100644 --- a/openstackclient/tests/test_shell.py +++ b/openstackclient/tests/test_shell.py @@ -527,13 +527,65 @@ def tearDown(self): super(TestShellCli, self).tearDown() os.environ = self.orig_env - def test_shell_args(self): + def test_shell_args_no_options(self): _shell = make_shell() with mock.patch("openstackclient.shell.OpenStackShell.initialize_app", self.app): fake_execute(_shell, "list user") self.app.assert_called_with(["list", "user"]) + def test_shell_args_ca_options(self): + _shell = make_shell() + + # NOTE(dtroyer): The commented out asserts below are the desired + # behaviour and will be uncommented when the + # handling for --verify and --insecure is fixed. + + # Default + fake_execute(_shell, "list user") + self.assertIsNone(_shell.options.verify) + self.assertIsNone(_shell.options.insecure) + self.assertEqual('', _shell.options.os_cacert) + self.assertTrue(_shell.verify) + + # --verify + fake_execute(_shell, "--verify list user") + self.assertTrue(_shell.options.verify) + self.assertIsNone(_shell.options.insecure) + self.assertEqual('', _shell.options.os_cacert) + self.assertTrue(_shell.verify) + + # --insecure + fake_execute(_shell, "--insecure list user") + self.assertIsNone(_shell.options.verify) + self.assertTrue(_shell.options.insecure) + self.assertEqual('', _shell.options.os_cacert) + self.assertFalse(_shell.verify) + + # --os-cacert + fake_execute(_shell, "--os-cacert foo list user") + self.assertIsNone(_shell.options.verify) + self.assertIsNone(_shell.options.insecure) + self.assertEqual('foo', _shell.options.os_cacert) + self.assertTrue(_shell.verify) + + # --os-cacert and --verify + fake_execute(_shell, "--os-cacert foo --verify list user") + self.assertTrue(_shell.options.verify) + self.assertIsNone(_shell.options.insecure) + self.assertEqual('foo', _shell.options.os_cacert) + self.assertTrue(_shell.verify) + + # --os-cacert and --insecure + # NOTE(dtroyer): This really is a bogus combination, the default is + # to follow the requests.Session convention and let + # --os-cacert override --insecure + fake_execute(_shell, "--os-cacert foo --insecure list user") + self.assertIsNone(_shell.options.verify) + self.assertTrue(_shell.options.insecure) + self.assertEqual('foo', _shell.options.os_cacert) + self.assertTrue(_shell.verify) + def test_default_env(self): flag = "" kwargs = { From ae29f7f459afb567abc36bd0627157e7c84c7568 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Fri, 29 May 2015 11:27:16 -0400 Subject: [PATCH 0076/3095] Use ostestr for test runs This commit switches to use ostestr for running tests. ostestr integrates the subunit-trace output that other projects use for test output. (in addition to some other ui niceties) Change-Id: Ib5b5225b2a7bfb7897e1efe55181389d1ae095cb --- test-requirements.txt | 1 + tox.ini | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/test-requirements.txt b/test-requirements.txt index 24f5c68ee9..f82e48d74b 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -11,6 +11,7 @@ oslosphinx>=2.5.0 # Apache-2.0 oslotest>=1.5.1 # Apache-2.0 requests-mock>=0.6.0 # Apache-2.0 sphinx>=1.1.2,!=1.2.0,!=1.3b1,<1.3 +os-testr>=0.1.0 testrepository>=0.0.18 testtools>=0.9.36,!=1.2.0 WebOb>=1.2.3 diff --git a/tox.ini b/tox.ini index 2ee9cdbc18..ff91d890a2 100644 --- a/tox.ini +++ b/tox.ini @@ -9,8 +9,8 @@ install_command = pip install -U {opts} {packages} setenv = VIRTUAL_ENV={envdir} deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt -commands = python setup.py testr --testr-args='{posargs}' -whitelist_externals = bash +commands = ostestr {posargs} +whitelist_externals = ostestr [testenv:pep8] commands = flake8 From 224d375ef4120998dc51fbf55f1778d1ccf118a0 Mon Sep 17 00:00:00 2001 From: Matt Riedemann Date: Fri, 29 May 2015 11:55:09 -0700 Subject: [PATCH 0077/3095] Add --wait to server delete This allows the server delete command to wait for the server to be deleted (obviously). The wait method is the same model that Tempest uses, i.e. wait for a 404 on server GET (successful deletion), fail if the server went to ERROR status, or fail if a timeout is reached. The default timeout of 300 seconds is also what Tempest uses. Closes-Bug: #1460112 Change-Id: I0e66c400903e82832944d1cad61e7eb30177c3e8 --- doc/source/command-objects/server.rst | 6 ++- openstackclient/common/utils.py | 46 ++++++++++++++++++ openstackclient/compute/v2/server.py | 17 +++++++ openstackclient/tests/common/test_utils.py | 39 +++++++++++++++ .../tests/compute/v2/test_server.py | 47 +++++++++++++++++++ 5 files changed, 154 insertions(+), 1 deletion(-) diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index f9ea590828..10ab306502 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -157,7 +157,11 @@ Delete server(s) .. code:: bash os server delete - [ ...] + [ ...] [--wait] + +.. option:: --wait + + Wait for delete to complete .. describe:: diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index 4139770cd6..aad0519c1b 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -283,6 +283,52 @@ def wait_for_status(status_f, return retval +def wait_for_delete(manager, + res_id, + status_field='status', + sleep_time=5, + timeout=300, + callback=None): + """Wait for resource deletion + + :param res_id: the resource id to watch + :param status_field: the status attribute in the returned resource object, + this is used to check for error states while the resource is being + deleted + :param sleep_time: wait this long between checks (seconds) + :param timeout: check until this long (seconds) + :param callback: called per sleep cycle, useful to display progress; this + function is passed a progress value during each iteration of the wait + loop + :rtype: True on success, False if the resource has gone to error state or + the timeout has been reached + """ + total_time = 0 + while total_time < timeout: + try: + # might not be a bad idea to re-use find_resource here if it was + # a bit more friendly in the exceptions it raised so we could just + # handle a NotFound exception here without parsing the message + res = manager.get(res_id) + except Exception as ex: + if type(ex).__name__ == 'NotFound': + return True + raise + + status = getattr(res, status_field, '').lower() + if status == 'error': + return False + + if callback: + progress = getattr(res, 'progress', None) or 0 + callback(progress) + time.sleep(sleep_time) + total_time += sleep_time + + # if we got this far we've timed out + return False + + def get_effective_log_level(): """Returns the lowest logging level considered by logging handlers diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 41c1b904f6..5007b072ac 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -572,6 +572,11 @@ def get_parser(self, prog_name): nargs="+", help=_('Server(s) to delete (name or ID)'), ) + parser.add_argument( + '--wait', + action='store_true', + help=_('Wait for delete to complete'), + ) return parser def take_action(self, parsed_args): @@ -581,6 +586,18 @@ def take_action(self, parsed_args): server_obj = utils.find_resource( compute_client.servers, server) compute_client.servers.delete(server_obj.id) + if parsed_args.wait: + if utils.wait_for_delete( + compute_client.servers, + server_obj.id, + callback=_show_progress, + ): + sys.stdout.write('\n') + else: + self.log.error(_('Error deleting server: %s'), + server_obj.id) + sys.stdout.write(_('\nError deleting server')) + raise SystemExit return diff --git a/openstackclient/tests/common/test_utils.py b/openstackclient/tests/common/test_utils.py index cda0b1351d..d9f5b7a56e 100644 --- a/openstackclient/tests/common/test_utils.py +++ b/openstackclient/tests/common/test_utils.py @@ -13,6 +13,9 @@ # under the License. # +import time +import uuid + import mock from openstackclient.common import exceptions @@ -120,6 +123,42 @@ def test_sort_items_with_invalid_direction(self): utils.sort_items, items, sort_str) + @mock.patch.object(time, 'sleep') + def test_wait_for_delete_ok(self, mock_sleep): + # Tests the normal flow that the resource is deleted with a 404 coming + # back on the 2nd iteration of the wait loop. + resource = mock.MagicMock(status='ACTIVE', progress=None) + mock_get = mock.Mock(side_effect=[resource, + exceptions.NotFound(404)]) + manager = mock.MagicMock(get=mock_get) + res_id = str(uuid.uuid4()) + callback = mock.Mock() + self.assertTrue(utils.wait_for_delete(manager, res_id, + callback=callback)) + mock_sleep.assert_called_once_with(5) + callback.assert_called_once_with(0) + + @mock.patch.object(time, 'sleep') + def test_wait_for_delete_timeout(self, mock_sleep): + # Tests that we fail if the resource is not deleted before the timeout. + resource = mock.MagicMock(status='ACTIVE') + mock_get = mock.Mock(return_value=resource) + manager = mock.MagicMock(get=mock_get) + res_id = str(uuid.uuid4()) + self.assertFalse(utils.wait_for_delete(manager, res_id, sleep_time=1, + timeout=1)) + mock_sleep.assert_called_once_with(1) + + @mock.patch.object(time, 'sleep') + def test_wait_for_delete_error(self, mock_sleep): + # Tests that we fail if the resource goes to error state while waiting. + resource = mock.MagicMock(status='ERROR') + mock_get = mock.Mock(return_value=resource) + manager = mock.MagicMock(get=mock_get) + res_id = str(uuid.uuid4()) + self.assertFalse(utils.wait_for_delete(manager, res_id)) + self.assertFalse(mock_sleep.called) + class NoUniqueMatch(Exception): pass diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index baf53742e5..a8a1936d82 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -16,6 +16,7 @@ import copy import mock +from openstackclient.common import utils as common_utils from openstackclient.compute.v2 import server from openstackclient.tests.compute.v2 import fakes as compute_fakes from openstackclient.tests import fakes @@ -319,6 +320,52 @@ def test_server_delete_no_options(self): compute_fakes.server_id, ) + @mock.patch.object(common_utils, 'wait_for_delete', return_value=True) + def test_server_delete_wait_ok(self, mock_wait_for_delete): + arglist = [ + compute_fakes.server_id, '--wait' + ] + verifylist = [ + ('servers', [compute_fakes.server_id]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + self.cmd.take_action(parsed_args) + + self.servers_mock.delete.assert_called_with( + compute_fakes.server_id, + ) + + mock_wait_for_delete.assert_called_once_with( + self.servers_mock, + compute_fakes.server_id, + callback=server._show_progress + ) + + @mock.patch.object(common_utils, 'wait_for_delete', return_value=False) + def test_server_delete_wait_fails(self, mock_wait_for_delete): + arglist = [ + compute_fakes.server_id, '--wait' + ] + verifylist = [ + ('servers', [compute_fakes.server_id]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + self.assertRaises(SystemExit, self.cmd.take_action, parsed_args) + + self.servers_mock.delete.assert_called_with( + compute_fakes.server_id, + ) + + mock_wait_for_delete.assert_called_once_with( + self.servers_mock, + compute_fakes.server_id, + callback=server._show_progress + ) + class TestServerImageCreate(TestServer): From 542f5873648df3657e623643a19f723a298fc46f Mon Sep 17 00:00:00 2001 From: Guojian Shao Date: Fri, 29 May 2015 11:50:30 -0400 Subject: [PATCH 0078/3095] add --domain argument to v3 project set Currently argument 'domain' is not supported by command 'os project set', but it is required by keystone v3 update project API to match the domain id. Closes-Bug: #1460122 Change-Id: I1b32f67f78b369f6134a74cdf9a4811b7539d44b --- doc/source/command-objects/project.rst | 6 ++++++ openstackclient/identity/v3/project.py | 8 ++++++++ openstackclient/tests/identity/v3/test_project.py | 15 +++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/doc/source/command-objects/project.rst b/doc/source/command-objects/project.rst index b8607f0b62..cde4a9dd6c 100644 --- a/doc/source/command-objects/project.rst +++ b/doc/source/command-objects/project.rst @@ -124,6 +124,12 @@ Set project properties Set project name +.. option:: --domain + + Domain owning :ref:`\ ` (name or ID) + + .. versionadded:: 3 + .. option:: --description Set project description diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index 0cb3c45310..48f547f3ed 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -241,6 +241,11 @@ def get_parser(self, prog_name): metavar='', help='Set project name', ) + parser.add_argument( + '--domain', + metavar='', + help='Domain owning (name or ID)', + ) parser.add_argument( '--description', metavar='', @@ -271,6 +276,7 @@ def take_action(self, parsed_args): identity_client = self.app.client_manager.identity if (not parsed_args.name + and not parsed_args.domain and not parsed_args.description and not parsed_args.enable and not parsed_args.property @@ -285,6 +291,8 @@ def take_action(self, parsed_args): kwargs = {} if parsed_args.name: kwargs['name'] = parsed_args.name + if parsed_args.domain: + kwargs['domain'] = parsed_args.domain if parsed_args.description: kwargs['description'] = parsed_args.description if parsed_args.enable: diff --git a/openstackclient/tests/identity/v3/test_project.py b/openstackclient/tests/identity/v3/test_project.py index ec50da0c30..ebf612ccd3 100644 --- a/openstackclient/tests/identity/v3/test_project.py +++ b/openstackclient/tests/identity/v3/test_project.py @@ -618,10 +618,12 @@ def test_project_set_no_options(self): def test_project_set_name(self): arglist = [ '--name', 'qwerty', + '--domain', identity_fakes.domain_id, identity_fakes.project_name, ] verifylist = [ ('name', 'qwerty'), + ('domain', identity_fakes.domain_id), ('enable', False), ('disable', False), ('project', identity_fakes.project_name), @@ -634,6 +636,7 @@ def test_project_set_name(self): # Set expected values kwargs = { 'name': 'qwerty', + 'domain': identity_fakes.domain_id, } # ProjectManager.update(project, name=, domain=, description=, # enabled=, **kwargs) @@ -644,10 +647,12 @@ def test_project_set_name(self): def test_project_set_description(self): arglist = [ + '--domain', identity_fakes.domain_id, '--description', 'new desc', identity_fakes.project_name, ] verifylist = [ + ('domain', identity_fakes.domain_id), ('description', 'new desc'), ('enable', False), ('disable', False), @@ -660,6 +665,7 @@ def test_project_set_description(self): # Set expected values kwargs = { + 'domain': identity_fakes.domain_id, 'description': 'new desc', } self.projects_mock.update.assert_called_with( @@ -669,10 +675,12 @@ def test_project_set_description(self): def test_project_set_enable(self): arglist = [ + '--domain', identity_fakes.domain_id, '--enable', identity_fakes.project_name, ] verifylist = [ + ('domain', identity_fakes.domain_id), ('enable', True), ('disable', False), ('project', identity_fakes.project_name), @@ -684,6 +692,7 @@ def test_project_set_enable(self): # Set expected values kwargs = { + 'domain': identity_fakes.domain_id, 'enabled': True, } self.projects_mock.update.assert_called_with( @@ -693,10 +702,12 @@ def test_project_set_enable(self): def test_project_set_disable(self): arglist = [ + '--domain', identity_fakes.domain_id, '--disable', identity_fakes.project_name, ] verifylist = [ + ('domain', identity_fakes.domain_id), ('enable', False), ('disable', True), ('project', identity_fakes.project_name), @@ -708,6 +719,7 @@ def test_project_set_disable(self): # Set expected values kwargs = { + 'domain': identity_fakes.domain_id, 'enabled': False, } self.projects_mock.update.assert_called_with( @@ -717,11 +729,13 @@ def test_project_set_disable(self): def test_project_set_property(self): arglist = [ + '--domain', identity_fakes.domain_id, '--property', 'fee=fi', '--property', 'fo=fum', identity_fakes.project_name, ] verifylist = [ + ('domain', identity_fakes.domain_id), ('property', {'fee': 'fi', 'fo': 'fum'}), ('project', identity_fakes.project_name), ] @@ -732,6 +746,7 @@ def test_project_set_property(self): # Set expected values kwargs = { + 'domain': identity_fakes.domain_id, 'fee': 'fi', 'fo': 'fum', } From 01573be3f8ce8fbfa795a144cb5a7155cf83e3ef Mon Sep 17 00:00:00 2001 From: Guojian Shao Date: Sat, 30 May 2015 02:21:52 -0400 Subject: [PATCH 0079/3095] project create is missing --parent in doc 'project create' is missing '--parent ' argument in doc, actually it is supported. Change-Id: Id0edaab4e2b02a1f7d974d71a11c7c373e31806f Closes-Bug: #1460256 --- doc/source/command-objects/project.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/command-objects/project.rst b/doc/source/command-objects/project.rst index b8607f0b62..3a1df598ee 100644 --- a/doc/source/command-objects/project.rst +++ b/doc/source/command-objects/project.rst @@ -14,6 +14,7 @@ Create new project os project create [--domain ] + [--parent ] [--description ] [--enable | --disable] [--property ] From d14316a8322e58d6ff208ffd6fabdb4890b412f8 Mon Sep 17 00:00:00 2001 From: Guojian Shao Date: Sat, 30 May 2015 09:18:42 -0400 Subject: [PATCH 0080/3095] add domain scope arguments to v3 role add in doc There are optional domain scope arguments --user-domain, --group-domain and --project-domain to filter user, group and project for command 'os role add', however, the doc is missing them. Closes-Bug: #1460296 Change-Id: Ie7c7707d183da042c51e98b6cd4003c89efc4032 --- doc/source/command-objects/role.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/command-objects/role.rst b/doc/source/command-objects/role.rst index 5fcbe82543..3672cfa1fd 100644 --- a/doc/source/command-objects/role.rst +++ b/doc/source/command-objects/role.rst @@ -13,8 +13,8 @@ Add role to a user or group in a project or domain .. code:: bash os role add - --domain | --project - --user | --group + --domain | --project [--project-domain ] + --user [--user-domain ] | --group [--group-domain ] .. option:: --domain From 5361652d8f0a90b5a2ef296a9fb718ac3d397ea9 Mon Sep 17 00:00:00 2001 From: Amey Bhide Date: Wed, 27 May 2015 12:31:59 -0700 Subject: [PATCH 0081/3095] Add support for volume v2 API Added following commands for volume V2 API: volume show volume delete volume type show volume type delete snapshot show snapshot delete backup show backup delete Implements: blueprint volume-v2 Change-Id: I68bd303c194f304ad15f899d335b72a8bf3ebe10 --- openstackclient/tests/volume/v2/__init__.py | 0 openstackclient/tests/volume/v2/fakes.py | 136 ++++++++++++++++++ .../tests/volume/v2/test_backup.py | 82 +++++++++++ .../tests/volume/v2/test_snapshot.py | 82 +++++++++++ openstackclient/tests/volume/v2/test_type.py | 82 +++++++++++ .../tests/volume/v2/test_volume.py | 82 +++++++++++ openstackclient/volume/client.py | 3 +- openstackclient/volume/v2/__init__.py | 0 openstackclient/volume/v2/backup.py | 70 +++++++++ openstackclient/volume/v2/snapshot.py | 71 +++++++++ openstackclient/volume/v2/volume.py | 83 +++++++++++ openstackclient/volume/v2/volume_type.py | 68 +++++++++ setup.cfg | 13 ++ 13 files changed, 771 insertions(+), 1 deletion(-) create mode 100644 openstackclient/tests/volume/v2/__init__.py create mode 100644 openstackclient/tests/volume/v2/fakes.py create mode 100644 openstackclient/tests/volume/v2/test_backup.py create mode 100644 openstackclient/tests/volume/v2/test_snapshot.py create mode 100644 openstackclient/tests/volume/v2/test_type.py create mode 100644 openstackclient/tests/volume/v2/test_volume.py create mode 100644 openstackclient/volume/v2/__init__.py create mode 100644 openstackclient/volume/v2/backup.py create mode 100644 openstackclient/volume/v2/snapshot.py create mode 100644 openstackclient/volume/v2/volume.py create mode 100644 openstackclient/volume/v2/volume_type.py diff --git a/openstackclient/tests/volume/v2/__init__.py b/openstackclient/tests/volume/v2/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py new file mode 100644 index 0000000000..3eade3910f --- /dev/null +++ b/openstackclient/tests/volume/v2/fakes.py @@ -0,0 +1,136 @@ +# +# 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.tests import fakes +from openstackclient.tests.identity.v2_0 import fakes as identity_fakes +from openstackclient.tests import utils + +volume_id = "ce26708d-a7f8-4b4b-9861-4a80256615a6" +volume_name = "fake_volume" +volume_description = "fake description" +volume_status = "available" +volume_size = 20 +volume_type = "fake_lvmdriver-1" +volume_metadata = { + "foo": "bar" +} +volume_snapshot_id = 1 +volume_availability_zone = "nova" +volume_attachments = ["fake_attachments"] + +VOLUME = { + "id": volume_id, + "name": volume_name, + "description": volume_description, + "status": volume_status, + "size": volume_size, + "volume_type": volume_type, + "metadata": volume_metadata, + "snapshot_id": volume_snapshot_id, + "availability_zone": volume_availability_zone, + "attachments": volume_attachments +} + +VOLUME_columns = tuple(sorted(VOLUME)) +VOLUME_data = tuple((VOLUME[x] for x in sorted(VOLUME))) + + +snapshot_id = "cb2d364e-4d1c-451a-8c68-b5bbcb340fb2" +snapshot_name = "fake_snapshot" +snapshot_description = "fake description" +snapshot_size = 10 +snapshot_metadata = { + "foo": "bar" +} +snapshot_volume_id = "bdbae8dc-e6ca-43c0-8076-951cc1b093a4" + +SNAPSHOT = { + "id": snapshot_id, + "name": snapshot_name, + "description": snapshot_description, + "size": snapshot_size, + "metadata": snapshot_metadata +} + +SNAPSHOT_columns = tuple(sorted(SNAPSHOT)) +SNAPSHOT_data = tuple((SNAPSHOT[x] for x in sorted(SNAPSHOT))) + + +type_id = "5520dc9e-6f9b-4378-a719-729911c0f407" +type_description = "fake description" +type_name = "fake-lvmdriver-1" +type_extra_specs = { + "foo": "bar" +} + +TYPE = { + 'id': type_id, + 'name': type_name, + 'description': type_description, + 'extra_specs': type_extra_specs +} + +TYPE_columns = tuple(sorted(TYPE)) +TYPE_data = tuple((TYPE[x] for x in sorted(TYPE))) + +backup_id = "3c409fe6-4d03-4a06-aeab-18bdcdf3c8f4" +backup_volume_id = "bdbae8dc-e6ca-43c0-8076-951cc1b093a4" +backup_name = "fake_backup" +backup_description = "fake description" +backup_object_count = None +backup_container = None +backup_size = 10 + +BACKUP = { + "id": backup_id, + "name": backup_name, + "volume_id": backup_volume_id, + "description": backup_description, + "object_count": backup_object_count, + "container": backup_container, + "size": backup_size +} + +BACKUP_columns = tuple(sorted(BACKUP)) +BACKUP_data = tuple((BACKUP[x] for x in sorted(BACKUP))) + + +class FakeVolumeClient(object): + def __init__(self, **kwargs): + self.volumes = mock.Mock() + self.volumes.resource_class = fakes.FakeResource(None, {}) + self.volume_snapshots = mock.Mock() + self.volume_snapshots.resource_class = fakes.FakeResource(None, {}) + self.backups = mock.Mock() + self.backups.resource_class = fakes.FakeResource(None, {}) + self.volume_types = mock.Mock() + self.volume_types.resource_class = fakes.FakeResource(None, {}) + self.auth_token = kwargs['token'] + self.management_url = kwargs['endpoint'] + + +class TestVolume(utils.TestCommand): + def setUp(self): + super(TestVolume, self).setUp() + + self.app.client_manager.volume = FakeVolumeClient( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN + ) + self.app.client_manager.identity = identity_fakes.FakeIdentityv2Client( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN + ) diff --git a/openstackclient/tests/volume/v2/test_backup.py b/openstackclient/tests/volume/v2/test_backup.py new file mode 100644 index 0000000000..e24cac3cad --- /dev/null +++ b/openstackclient/tests/volume/v2/test_backup.py @@ -0,0 +1,82 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +import copy + +from openstackclient.tests import fakes +from openstackclient.tests.volume.v2 import fakes as volume_fakes +from openstackclient.volume.v2 import backup + + +class TestBackup(volume_fakes.TestVolume): + + def setUp(self): + super(TestBackup, self).setUp() + + self.backups_mock = self.app.client_manager.volume.backups + self.backups_mock.reset_mock() + + +class TestBackupShow(TestBackup): + def setUp(self): + super(TestBackupShow, self).setUp() + + self.backups_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.BACKUP), + loaded=True) + # Get the command object to test + self.cmd = backup.ShowBackup(self.app, None) + + def test_backup_show(self): + arglist = [ + volume_fakes.backup_id + ] + verifylist = [ + ("backup", volume_fakes.backup_id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.backups_mock.get.assert_called_with(volume_fakes.backup_id) + + self.assertEqual(volume_fakes.BACKUP_columns, columns) + self.assertEqual(volume_fakes.BACKUP_data, data) + + +class TestBackupDelete(TestBackup): + def setUp(self): + super(TestBackupDelete, self).setUp() + + self.backups_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.BACKUP), + loaded=True) + self.backups_mock.delete.return_value = None + + # Get the command object to mock + self.cmd = backup.DeleteBackup(self.app, None) + + def test_backup_delete(self): + arglist = [ + volume_fakes.backup_id + ] + verifylist = [ + ("backups", [volume_fakes.backup_id]) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + self.backups_mock.delete.assert_called_with(volume_fakes.backup_id) diff --git a/openstackclient/tests/volume/v2/test_snapshot.py b/openstackclient/tests/volume/v2/test_snapshot.py new file mode 100644 index 0000000000..9101541009 --- /dev/null +++ b/openstackclient/tests/volume/v2/test_snapshot.py @@ -0,0 +1,82 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +import copy + +from openstackclient.tests import fakes +from openstackclient.tests.volume.v2 import fakes as volume_fakes +from openstackclient.volume.v2 import snapshot + + +class TestSnapshot(volume_fakes.TestVolume): + + def setUp(self): + super(TestSnapshot, self).setUp() + + self.snapshots_mock = self.app.client_manager.volume.volume_snapshots + self.snapshots_mock.reset_mock() + + +class TestSnapshotShow(TestSnapshot): + def setUp(self): + super(TestSnapshotShow, self).setUp() + + self.snapshots_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.SNAPSHOT), + loaded=True) + # Get the command object to test + self.cmd = snapshot.ShowSnapshot(self.app, None) + + def test_snapshot_show(self): + arglist = [ + volume_fakes.snapshot_id + ] + verifylist = [ + ("snapshot", volume_fakes.snapshot_id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.snapshots_mock.get.assert_called_with(volume_fakes.snapshot_id) + + self.assertEqual(volume_fakes.SNAPSHOT_columns, columns) + self.assertEqual(volume_fakes.SNAPSHOT_data, data) + + +class TestSnapshotDelete(TestSnapshot): + def setUp(self): + super(TestSnapshotDelete, self).setUp() + + self.snapshots_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.SNAPSHOT), + loaded=True) + self.snapshots_mock.delete.return_value = None + + # Get the command object to mock + self.cmd = snapshot.DeleteSnapshot(self.app, None) + + def test_snapshot_delete(self): + arglist = [ + volume_fakes.snapshot_id + ] + verifylist = [ + ("snapshots", [volume_fakes.snapshot_id]) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + self.snapshots_mock.delete.assert_called_with(volume_fakes.snapshot_id) diff --git a/openstackclient/tests/volume/v2/test_type.py b/openstackclient/tests/volume/v2/test_type.py new file mode 100644 index 0000000000..6cc988b2dc --- /dev/null +++ b/openstackclient/tests/volume/v2/test_type.py @@ -0,0 +1,82 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +import copy + +from openstackclient.tests import fakes +from openstackclient.tests.volume.v2 import fakes as volume_fakes +from openstackclient.volume.v2 import volume_type + + +class TestType(volume_fakes.TestVolume): + + def setUp(self): + super(TestType, self).setUp() + + self.types_mock = self.app.client_manager.volume.volume_types + self.types_mock.reset_mock() + + +class TestTypeShow(TestType): + def setUp(self): + super(TestTypeShow, self).setUp() + + self.types_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.TYPE), + loaded=True) + # Get the command object to test + self.cmd = volume_type.ShowVolumeType(self.app, None) + + def test_type_show(self): + arglist = [ + volume_fakes.type_id + ] + verifylist = [ + ("volume_type", volume_fakes.type_id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.types_mock.get.assert_called_with(volume_fakes.type_id) + + self.assertEqual(volume_fakes.TYPE_columns, columns) + self.assertEqual(volume_fakes.TYPE_data, data) + + +class TestTypeDelete(TestType): + def setUp(self): + super(TestTypeDelete, self).setUp() + + self.types_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.TYPE), + loaded=True) + self.types_mock.delete.return_value = None + + # Get the command object to mock + self.cmd = volume_type.DeleteVolumeType(self.app, None) + + def test_type_delete(self): + arglist = [ + volume_fakes.type_id + ] + verifylist = [ + ("volume_type", volume_fakes.type_id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + self.types_mock.delete.assert_called_with(volume_fakes.type_id) diff --git a/openstackclient/tests/volume/v2/test_volume.py b/openstackclient/tests/volume/v2/test_volume.py new file mode 100644 index 0000000000..9e991b7278 --- /dev/null +++ b/openstackclient/tests/volume/v2/test_volume.py @@ -0,0 +1,82 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +import copy + +from openstackclient.tests import fakes +from openstackclient.tests.volume.v2 import fakes as volume_fakes +from openstackclient.volume.v2 import volume + + +class TestVolume(volume_fakes.TestVolume): + + def setUp(self): + super(TestVolume, self).setUp() + + self.volumes_mock = self.app.client_manager.volume.volumes + self.volumes_mock.reset_mock() + + +class TestVolumeShow(TestVolume): + def setUp(self): + super(TestVolumeShow, self).setUp() + + self.volumes_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.VOLUME), + loaded=True) + # Get the command object to test + self.cmd = volume.ShowVolume(self.app, None) + + def test_volume_show(self): + arglist = [ + volume_fakes.volume_id + ] + verifylist = [ + ("volume", volume_fakes.volume_id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.volumes_mock.get.assert_called_with(volume_fakes.volume_id) + + self.assertEqual(volume_fakes.VOLUME_columns, columns) + self.assertEqual(volume_fakes.VOLUME_data, data) + + +class TestVolumeDelete(TestVolume): + def setUp(self): + super(TestVolumeDelete, self).setUp() + + self.volumes_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.VOLUME), + loaded=True) + self.volumes_mock.delete.return_value = None + + # Get the command object to mock + self.cmd = volume.DeleteVolume(self.app, None) + + def test_volume_delete(self): + arglist = [ + volume_fakes.volume_id + ] + verifylist = [ + ("volumes", [volume_fakes.volume_id]) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + self.volumes_mock.delete.assert_called_with(volume_fakes.volume_id) diff --git a/openstackclient/volume/client.py b/openstackclient/volume/client.py index a7b64def68..1038c407e4 100644 --- a/openstackclient/volume/client.py +++ b/openstackclient/volume/client.py @@ -23,7 +23,8 @@ API_VERSION_OPTION = 'os_volume_api_version' API_NAME = "volume" API_VERSIONS = { - "1": "cinderclient.v1.client.Client" + "1": "cinderclient.v1.client.Client", + "2": "cinderclient.v2.client.Client" } diff --git a/openstackclient/volume/v2/__init__.py b/openstackclient/volume/v2/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openstackclient/volume/v2/backup.py b/openstackclient/volume/v2/backup.py new file mode 100644 index 0000000000..bf2ea3a620 --- /dev/null +++ b/openstackclient/volume/v2/backup.py @@ -0,0 +1,70 @@ +# +# 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 Backup action implementations""" + +import logging + +from cliff import command +from cliff import show +import six + +from openstackclient.common import utils + + +class DeleteBackup(command.Command): + """Delete backup(s)""" + + log = logging.getLogger(__name__ + ".DeleteBackup") + + def get_parser(self, prog_name): + parser = super(DeleteBackup, self).get_parser(prog_name) + parser.add_argument( + "backups", + metavar="", + nargs="+", + help="Backup(s) to delete (name or ID)" + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action: (%s)", parsed_args) + volume_client = self.app.client_manager.volume + for backup in parsed_args.backups: + backup_id = utils.find_resource( + volume_client.backups, backup).id + volume_client.backups.delete(backup_id) + return + + +class ShowBackup(show.ShowOne): + """Display backup details""" + + log = logging.getLogger(__name__ + ".ShowBackup") + + def get_parser(self, prog_name): + parser = super(ShowBackup, self).get_parser(prog_name) + parser.add_argument( + "backup", + metavar="", + help="Backup to display (name or ID)") + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action: (%s)", parsed_args) + volume_client = self.app.client_manager.volume + backup = utils.find_resource(volume_client.backups, + parsed_args.backup) + backup._info.pop("links", None) + return zip(*sorted(six.iteritems(backup._info))) diff --git a/openstackclient/volume/v2/snapshot.py b/openstackclient/volume/v2/snapshot.py new file mode 100644 index 0000000000..a6b02b63a0 --- /dev/null +++ b/openstackclient/volume/v2/snapshot.py @@ -0,0 +1,71 @@ +# +# 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 snapshot action implementations""" + +import logging + +from cliff import command +from cliff import show +import six + +from openstackclient.common import utils + + +class DeleteSnapshot(command.Command): + """Delete volume snapshot(s)""" + + log = logging.getLogger(__name__ + ".DeleteSnapshot") + + def get_parser(self, prog_name): + parser = super(DeleteSnapshot, self).get_parser(prog_name) + parser.add_argument( + "snapshots", + metavar="", + nargs="+", + help="Snapsho(s) to delete (name or ID)" + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action: (%s)", parsed_args) + volume_client = self.app.client_manager.volume + for snapshot in parsed_args.snapshots: + snapshot_id = utils.find_resource( + volume_client.volume_snapshots, snapshot).id + volume_client.volume_snapshots.delete(snapshot_id) + return + + +class ShowSnapshot(show.ShowOne): + """Display snapshot details""" + + log = logging.getLogger(__name__ + ".ShowSnapshot") + + def get_parser(self, prog_name): + parser = super(ShowSnapshot, self).get_parser(prog_name) + parser.add_argument( + "snapshot", + metavar="", + help="Snapshot to display (name or ID)" + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action: (%s)", parsed_args) + volume_client = self.app.client_manager.volume + snapshot = utils.find_resource( + volume_client.volume_snapshots, parsed_args.snapshot) + snapshot = volume_client.volume_snapshots.get(snapshot.id) + return zip(*sorted(six.iteritems(snapshot._info))) diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py new file mode 100644 index 0000000000..e50a6f0c9d --- /dev/null +++ b/openstackclient/volume/v2/volume.py @@ -0,0 +1,83 @@ +# +# 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 Volume action implementations""" + +import logging + +from cliff import command +from cliff import show +import six + +from openstackclient.common import utils + + +class DeleteVolume(command.Command): + """Delete volume(s)""" + + log = logging.getLogger(__name__ + ".DeleteVolume") + + def get_parser(self, prog_name): + parser = super(DeleteVolume, self).get_parser(prog_name) + parser.add_argument( + "volumes", + metavar="", + nargs="+", + help="Volume(s) to delete (name or ID)" + ) + parser.add_argument( + "--force", + dest="force", + action="store_true", + default=False, + help="Attempt forced removal of volume(s), regardless of state " + "(defaults to False" + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action: (%s)", parsed_args) + volume_client = self.app.client_manager.volume + for volume in parsed_args.volumes: + volume_obj = utils.find_resource( + volume_client.volumes, volume) + if parsed_args.force: + volume_client.volumes.force_delete(volume_obj.id) + else: + volume_client.volumes.delete(volume_obj.id) + return + + +class ShowVolume(show.ShowOne): + """Display volume details""" + + log = logging.getLogger(__name__ + '.ShowVolume') + + def get_parser(self, prog_name): + parser = super(ShowVolume, self).get_parser(prog_name) + parser.add_argument( + 'volume', + metavar="", + help="Volume to display (name or ID)" + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + volume_client = self.app.client_manager.volume + volume = utils.find_resource(volume_client.volumes, parsed_args.volume) + + # Remove key links from being displayed + volume._info.pop("links", None) + return zip(*sorted(six.iteritems(volume._info))) diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py new file mode 100644 index 0000000000..ae5cc8b892 --- /dev/null +++ b/openstackclient/volume/v2/volume_type.py @@ -0,0 +1,68 @@ +# +# 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 Type action implementations""" + +import logging + +from cliff import command +from cliff import show +import six + +from openstackclient.common import utils + + +class DeleteVolumeType(command.Command): + """Delete volume type""" + + log = logging.getLogger(__name__ + ".DeleteVolumeType") + + def get_parser(self, prog_name): + parser = super(DeleteVolumeType, self).get_parser(prog_name) + parser.add_argument( + "volume_type", + metavar="", + help="Volume type to delete (name or ID)" + ) + return parser + + def take_action(self, parsed_args): + self.log.info("take_action: (%s)", parsed_args) + volume_client = self.app.client_manager.volume + volume_type = utils.find_resource( + volume_client.volume_types, parsed_args.volume_type) + volume_client.volume_types.delete(volume_type.id) + return + + +class ShowVolumeType(show.ShowOne): + """Display volume type details""" + + log = logging.getLogger(__name__ + ".ShowVolumeType") + + def get_parser(self, prog_name): + parser = super(ShowVolumeType, self).get_parser(prog_name) + parser.add_argument( + "volume_type", + metavar="", + help="Volume type to display (name or ID)" + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action: (%s)", parsed_args) + volume_client = self.app.client_manager.volume + volume_type = utils.find_resource( + volume_client.volume_types, parsed_args.volume_type) + return zip(*sorted(six.iteritems(volume_type._info))) diff --git a/setup.cfg b/setup.cfg index 9abc216044..8db7c3eae4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -361,6 +361,19 @@ openstack.volume.v1 = volume_type_set = openstackclient.volume.v1.type:SetVolumeType volume_type_unset = openstackclient.volume.v1.type:UnsetVolumeType +openstack.volume.v2 = + backup_delete = openstackclient.volume.v2.backup:DeleteBackup + backup_show = openstackclient.volume.v2.backup:ShowBackup + + snapshot_delete = openstackclient.volume.v2.snapshot:DeleteSnapshot + snapshot_show = openstackclient.volume.v2.snapshot:ShowSnapshot + + volume_delete = openstackclient.volume.v2.volume:DeleteVolume + volume_show = openstackclient.volume.v2.volume:ShowVolume + + volume_type_delete = openstackclient.volume.v2.volume_type:DeleteVolumeType + volume_type_show = openstackclient.volume.v2.volume_type:ShowVolumeType + [build_sphinx] source-dir = doc/source build-dir = doc/build From 3ae247fdceac0e2c7bb6160f9ffefc4ad5d8da29 Mon Sep 17 00:00:00 2001 From: Jamie Lennox Date: Sun, 31 May 2015 13:37:50 +1000 Subject: [PATCH 0082/3095] Set tenant options on parsed namespace Because of the way OSC registers all plugins together we end up with os-tenant-X parameters being saved to the project-X attribute after parsing. If you are using the v2 plugins directly then they and os-client-config expect the tenant_X values and will assuming no scoping information if they are not present. Validating options for scope will also fail in this situation, not just because the resultant auth dictionary is missing the tenant-X attributes, but because OSC validates that either project or domain scope information is present. Fix this by just always setting the v2 parameters if the v3 parameters are present. This will have no effect on the generic or v3 case but fix the v2 case. Expand validation to include the tenant options so it knows that v2 plugins are scoped. Change-Id: I8cab3e423663f801cbf2d83106c671bddc58d7e6 Closes-Bug: #1460369 --- openstackclient/api/auth.py | 4 +++- openstackclient/common/clientmanager.py | 2 ++ openstackclient/shell.py | 17 +++++++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/openstackclient/api/auth.py b/openstackclient/api/auth.py index 9fb26e7100..1d50f92ca3 100644 --- a/openstackclient/api/auth.py +++ b/openstackclient/api/auth.py @@ -149,7 +149,9 @@ def check_valid_auth_options(options, auth_plugin_name): if (not options.auth.get('project_id', None) and not options.auth.get('domain_id', None) and not options.auth.get('domain_name', None) and not - options.auth.get('project_name', None)): + options.auth.get('project_name', None) and not + options.auth.get('tenant_id', None) and not + options.auth.get('tenant_name', None)): msg += _('Set a scope, such as a project or domain, with ' '--os-project-name, OS_PROJECT_NAME or auth.project_name') elif auth_plugin_name.endswith('token'): diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index 85e367c41c..6311c71a50 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -139,6 +139,7 @@ def setup_auth(self): # PROJECT_DOMAIN_ID to 'OS_DEFAULT_DOMAIN' for better usability. if (self._api_version.get('identity') == '3' and not self._auth_params.get('project_domain_id', None) and + not self.auth_plugin_name.startswith('v2') and not self._auth_params.get('project_domain_name', None)): self._auth_params['project_domain_id'] = default_domain @@ -147,6 +148,7 @@ def setup_auth(self): # to 'OS_DEFAULT_DOMAIN' for better usability. if (self._api_version.get('identity') == '3' and self.auth_plugin_name.endswith('password') and + not self.auth_plugin_name.startswith('v2') and not self._auth_params.get('user_domain_id', None) and not self._auth_params.get('user_domain_name', None)): self._auth_params['user_domain_id'] = default_domain diff --git a/openstackclient/shell.py b/openstackclient/shell.py index da985cbc4b..ab732dad4b 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -234,6 +234,23 @@ def initialize_app(self, argv): cloud_config.set_default('auth_type', 'osc_password') self.log.debug("options: %s", self.options) + project_id = getattr(self.options, 'project_id', None) + project_name = getattr(self.options, 'project_name', None) + tenant_id = getattr(self.options, 'tenant_id', None) + tenant_name = getattr(self.options, 'tenant_name', None) + + # handle some v2/v3 authentication inconsistencies by just acting like + # both the project and tenant information are both present. This can + # go away if we stop registering all the argparse options together. + if project_id and not tenant_id: + self.options.tenant_id = project_id + if project_name and not tenant_name: + self.options.tenant_name = project_name + if tenant_id and not project_id: + self.options.project_id = tenant_id + if tenant_name and not project_name: + self.options.project_name = tenant_name + # Do configuration file handling cc = cloud_config.OpenStackConfig() self.log.debug("defaults: %s", cc.defaults) From 0c9f5c25e4b72469c20522ed5a7ab1ae4679d34d Mon Sep 17 00:00:00 2001 From: Jamie Lennox Date: Mon, 1 Jun 2015 17:46:02 +1000 Subject: [PATCH 0083/3095] Ignore cover directory from git The cover directory holds autogenerated coverage reports that should not be checked into git and should therefore go in the gitignore file. Change-Id: I4f7225b5422493f99bc534d6ac622e0703781e6d --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 5e4f5a3c87..43bd5df814 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ doc/source/api/ # Development environment files .project .pydevproject +cover From f9fa307809788cd09e73f91fbcbc4be8019d178d Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Mon, 1 Jun 2015 13:32:37 -0600 Subject: [PATCH 0084/3095] Add volume functional tests Change-Id: I8e12837fb22cabeabf1cde341324927cc6ee0252 --- functional/tests/volume/__init__.py | 0 functional/tests/volume/v1/__init__.py | 0 functional/tests/volume/v1/test_volume.py | 45 +++++++++++++++++++++++ 3 files changed, 45 insertions(+) create mode 100644 functional/tests/volume/__init__.py create mode 100644 functional/tests/volume/v1/__init__.py create mode 100644 functional/tests/volume/v1/test_volume.py diff --git a/functional/tests/volume/__init__.py b/functional/tests/volume/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/functional/tests/volume/v1/__init__.py b/functional/tests/volume/v1/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/functional/tests/volume/v1/test_volume.py b/functional/tests/volume/v1/test_volume.py new file mode 100644 index 0000000000..9caf15630b --- /dev/null +++ b/functional/tests/volume/v1/test_volume.py @@ -0,0 +1,45 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import uuid + +from functional.common import test + + +class VolumeTests(test.TestCase): + """Functional tests for volume. """ + + NAME = uuid.uuid4().hex + HEADERS = ['"Display Name"'] + FIELDS = ['display_name'] + + @classmethod + def setUpClass(cls): + opts = cls.get_show_opts(cls.FIELDS) + raw_output = cls.openstack('volume create --size 1 ' + cls.NAME + opts) + expected = cls.NAME + '\n' + cls.assertOutput(expected, raw_output) + + @classmethod + def tearDownClass(cls): + raw_output = cls.openstack('volume delete ' + cls.NAME) + cls.assertOutput('', raw_output) + + def test_volume_list(self): + opts = self.get_list_opts(self.HEADERS) + raw_output = self.openstack('volume list' + opts) + self.assertIn(self.NAME, raw_output) + + def test_volume_show(self): + opts = self.get_show_opts(self.FIELDS) + raw_output = self.openstack('volume show ' + self.NAME + opts) + self.assertEqual(self.NAME + "\n", raw_output) From 746f6421d0490d41b1a3b7b0e014d0a7fe457afd Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Mon, 1 Jun 2015 14:20:39 -0600 Subject: [PATCH 0085/3095] Add image functional tests Change-Id: I30b219bc3393fcb197bda266125dcfb5c8780a3e --- functional/common/test.py | 2 +- functional/tests/image/__init__.py | 0 functional/tests/image/v1/__init__.py | 0 functional/tests/image/v1/test_image.py | 45 ++++++++++++++++++++ functional/tests/object/v1/test_container.py | 2 +- 5 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 functional/tests/image/__init__.py create mode 100644 functional/tests/image/v1/__init__.py create mode 100644 functional/tests/image/v1/test_image.py diff --git a/functional/common/test.py b/functional/common/test.py index 7beaf39aa4..ef034276e4 100644 --- a/functional/common/test.py +++ b/functional/common/test.py @@ -56,7 +56,7 @@ def get_show_opts(cls, fields=[]): @classmethod def get_list_opts(cls, headers=[]): - opts = ' -f csv --quote none ' + opts = ' -f csv ' opts = opts + ' '.join(['-c ' + it for it in headers]) return opts diff --git a/functional/tests/image/__init__.py b/functional/tests/image/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/functional/tests/image/v1/__init__.py b/functional/tests/image/v1/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/functional/tests/image/v1/test_image.py b/functional/tests/image/v1/test_image.py new file mode 100644 index 0000000000..e27ab24cd1 --- /dev/null +++ b/functional/tests/image/v1/test_image.py @@ -0,0 +1,45 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import uuid + +from functional.common import test + + +class ImageTests(test.TestCase): + """Functional tests for image. """ + + NAME = uuid.uuid4().hex + HEADERS = ['Name'] + FIELDS = ['name'] + + @classmethod + def setUpClass(cls): + opts = cls.get_show_opts(cls.FIELDS) + raw_output = cls.openstack('image create ' + cls.NAME + opts) + expected = cls.NAME + '\n' + cls.assertOutput(expected, raw_output) + + @classmethod + def tearDownClass(cls): + raw_output = cls.openstack('image delete ' + cls.NAME) + cls.assertOutput('', raw_output) + + def test_image_list(self): + opts = self.get_list_opts(self.HEADERS) + raw_output = self.openstack('image list' + opts) + self.assertIn(self.NAME, raw_output) + + def test_image_show(self): + opts = self.get_show_opts(self.FIELDS) + raw_output = self.openstack('image show ' + self.NAME + opts) + self.assertEqual(self.NAME + "\n", raw_output) diff --git a/functional/tests/object/v1/test_container.py b/functional/tests/object/v1/test_container.py index 9ea14cdec6..8721a4a79d 100644 --- a/functional/tests/object/v1/test_container.py +++ b/functional/tests/object/v1/test_container.py @@ -23,7 +23,7 @@ class ContainerTests(test.TestCase): def setUpClass(cls): opts = cls.get_list_opts(['container']) raw_output = cls.openstack('container create ' + cls.NAME + opts) - expected = 'container\n' + cls.NAME + '\n' + expected = '"container"\n"' + cls.NAME + '"\n' cls.assertOutput(expected, raw_output) @classmethod From a05cbf4c998678a3619d82dd49be2b7759373d60 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 29 Apr 2015 22:59:02 -0500 Subject: [PATCH 0086/3095] Rework shell tests This is the first step in reworking the shell argument handling, clean up and add tests to ensure functionality doesn't change. * Rework shell tests to break down global options and auth options. * Make tests table-driven * Remove 'os_' from 'cacert' and 'default_domain' internal option names Change-Id: Icf69c7e84f3f44b366fe64b6bbf4e3fe958eb302 --- examples/object_api.py | 4 +- examples/osc-lib.py | 4 +- openstackclient/shell.py | 4 +- openstackclient/tests/test_shell.py | 387 +++++++++++++--------------- 4 files changed, 182 insertions(+), 217 deletions(-) diff --git a/examples/object_api.py b/examples/object_api.py index 441013cad8..11b62da773 100755 --- a/examples/object_api.py +++ b/examples/object_api.py @@ -52,8 +52,8 @@ def run(opts): # NOTE(dtroyer): This converts from the usual OpenStack way to the single # requests argument and is an app-specific thing because # we want to be like OpenStackClient. - if opts.os_cacert: - verify = opts.os_cacert + if opts.cacert: + verify = opts.cacert else: verify = not opts.insecure diff --git a/examples/osc-lib.py b/examples/osc-lib.py index 84501903c6..abaa18711f 100755 --- a/examples/osc-lib.py +++ b/examples/osc-lib.py @@ -62,8 +62,8 @@ def run(opts): # NOTE(dtroyer): This converts from the usual OpenStack way to the single # requests argument and is an app-specific thing because # we want to be like OpenStackClient. - if opts.os_cacert: - verify = opts.os_cacert + if opts.cacert: + verify = opts.cacert else: verify = not opts.insecure diff --git a/openstackclient/shell.py b/openstackclient/shell.py index e618168387..136542dcd5 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -182,6 +182,7 @@ def build_option_parser(self, description, version): parser.add_argument( '--os-cacert', metavar='', + dest='cacert', default=utils.env('OS_CACERT'), help='CA certificate bundle file (Env: OS_CACERT)') verify_group = parser.add_mutually_exclusive_group() @@ -200,6 +201,7 @@ def build_option_parser(self, description, version): parser.add_argument( '--os-default-domain', metavar='', + dest='default_domain', default=utils.env( 'OS_DEFAULT_DOMAIN', default=DEFAULT_DOMAIN), @@ -270,7 +272,7 @@ def initialize_app(self, argv): self.verify = self.cloud.config.get('verify', self.verify) # Save default domain - self.default_domain = self.options.os_default_domain + self.default_domain = self.options.default_domain # Loop through extensions to get API versions for mod in clientmanager.PLUGIN_MODULES: diff --git a/openstackclient/tests/test_shell.py b/openstackclient/tests/test_shell.py index a37d2ac832..8850d8f97d 100644 --- a/openstackclient/tests/test_shell.py +++ b/openstackclient/tests/test_shell.py @@ -25,13 +25,15 @@ DEFAULT_PROJECT_ID = "xxxx-yyyy-zzzz" DEFAULT_PROJECT_NAME = "project" DEFAULT_DOMAIN_ID = "aaaa-bbbb-cccc" -DEFAULT_DOMAIN_NAME = "domain" +DEFAULT_DOMAIN_NAME = "default" DEFAULT_USER_DOMAIN_ID = "aaaa-bbbb-cccc" DEFAULT_USER_DOMAIN_NAME = "domain" DEFAULT_PROJECT_DOMAIN_ID = "aaaa-bbbb-cccc" DEFAULT_PROJECT_DOMAIN_NAME = "domain" DEFAULT_USERNAME = "username" DEFAULT_PASSWORD = "password" + +DEFAULT_CLOUD = "altocumulus" DEFAULT_REGION_NAME = "ZZ9_Plural_Z_Alpha" DEFAULT_TOKEN = "token" DEFAULT_SERVICE_URL = "http://127.0.0.1:8771/v3.0/" @@ -90,6 +92,54 @@ } +# The option table values is a tuple of (, , ) +# where is the test value to use, is True if this option +# should be tested as a CLI option and is True of this option +# should be tested as an environment variable. + +# Global options that should be parsed before shell.initialize_app() is called +global_options = { + '--os-cloud': (DEFAULT_CLOUD, True, True), + '--os-region-name': (DEFAULT_REGION_NAME, True, True), + '--os-default-domain': (DEFAULT_DOMAIN_NAME, True, True), + '--os-cacert': ('/dev/null', True, True), + '--timing': (True, True, False), +} + +auth_options = { + '--os-auth-url': (DEFAULT_AUTH_URL, True, True), + '--os-project-id': (DEFAULT_PROJECT_ID, True, True), + '--os-project-name': (DEFAULT_PROJECT_NAME, True, True), + '--os-domain-id': (DEFAULT_DOMAIN_ID, True, True), + '--os-domain-name': (DEFAULT_DOMAIN_NAME, True, True), + '--os-user-domain-id': (DEFAULT_USER_DOMAIN_ID, True, True), + '--os-user-domain-name': (DEFAULT_USER_DOMAIN_NAME, True, True), + '--os-project-domain-id': (DEFAULT_PROJECT_DOMAIN_ID, True, True), + '--os-project-domain-name': (DEFAULT_PROJECT_DOMAIN_NAME, True, True), + '--os-username': (DEFAULT_USERNAME, True, True), + '--os-password': (DEFAULT_PASSWORD, True, True), + '--os-region-name': (DEFAULT_REGION_NAME, True, True), + '--os-trust-id': ("1234", True, True), + '--os-auth-type': ("v2password", True, True), + '--os-token': (DEFAULT_TOKEN, True, True), + '--os-url': (DEFAULT_SERVICE_URL, True, True), +} + + +def opt2attr(opt): + if opt.startswith('--os-'): + attr = opt[5:] + elif opt.startswith('--'): + attr = opt[2:] + else: + attr = opt + return attr.lower().replace('-', '_') + + +def opt2env(opt): + return opt[2:].upper().replace('-', '_') + + def make_shell(): """Create a new command shell and mock out some bits.""" _shell = shell.OpenStackShell() @@ -115,69 +165,54 @@ def tearDown(self): super(TestShell, self).tearDown() self.cmd_patch.stop() - def _assert_password_auth(self, cmd_options, default_args): - with mock.patch("openstackclient.shell.OpenStackShell.initialize_app", - self.app): + def _assert_initialize_app_arg(self, cmd_options, default_args): + """Check the args passed to initialize_app() + + The argv argument to initialize_app() is the remainder from parsing + global options declared in both cliff.app and + openstackclient.OpenStackShell build_option_parser(). Any global + options passed on the commmad line should not be in argv but in + _shell.options. + """ + + with mock.patch( + "openstackclient.shell.OpenStackShell.initialize_app", + self.app, + ): _shell, _cmd = make_shell(), cmd_options + " list project" fake_execute(_shell, _cmd) self.app.assert_called_with(["list", "project"]) - self.assertEqual( - default_args.get("auth_url", ''), - _shell.options.auth_url, - ) - self.assertEqual( - default_args.get("project_id", ''), - _shell.options.project_id, - ) - self.assertEqual( - default_args.get("project_name", ''), - _shell.options.project_name, - ) - self.assertEqual( - default_args.get("domain_id", ''), - _shell.options.domain_id, - ) - self.assertEqual( - default_args.get("domain_name", ''), - _shell.options.domain_name, - ) - self.assertEqual( - default_args.get("user_domain_id", ''), - _shell.options.user_domain_id, - ) - self.assertEqual( - default_args.get("user_domain_name", ''), - _shell.options.user_domain_name, - ) - self.assertEqual( - default_args.get("project_domain_id", ''), - _shell.options.project_domain_id, - ) - self.assertEqual( - default_args.get("project_domain_name", ''), - _shell.options.project_domain_name, - ) - self.assertEqual( - default_args.get("username", ''), - _shell.options.username, - ) - self.assertEqual( - default_args.get("password", ''), - _shell.options.password, - ) - self.assertEqual( - default_args.get("region_name", ''), - _shell.options.region_name, - ) - self.assertEqual( - default_args.get("trust_id", ''), - _shell.options.trust_id, - ) - self.assertEqual( - default_args.get('auth_type', ''), - _shell.options.auth_type, - ) + for k in default_args.keys(): + self.assertEqual( + default_args[k], + vars(_shell.options)[k], + "%s does not match" % k, + ) + + def _assert_cloud_config_arg(self, cmd_options, default_args): + """Check the args passed to cloud_config.get_one_cloud() + + The argparse argument to get_one_cloud() is an argparse.Namespace + object that contains all of the options processed to this point in + initialize_app(). + """ + + self.occ_get_one = mock.Mock("Test Shell") + with mock.patch( + "os_client_config.config.OpenStackConfig.get_one_cloud", + self.occ_get_one, + ): + _shell, _cmd = make_shell(), cmd_options + " list project" + fake_execute(_shell, _cmd) + + opts = self.occ_get_one.call_args[1]['argparse'] + for k in default_args.keys(): + self.assertEqual( + default_args[k], + vars(opts)[k], + "%s does not match" % k, + ) def _assert_token_auth(self, cmd_options, default_args): with mock.patch("openstackclient.shell.OpenStackShell.initialize_app", @@ -258,136 +293,91 @@ def test_help_options(self): _shell.options.deferred_help) -class TestShellPasswordAuth(TestShell): +class TestShellOptions(TestShell): def setUp(self): - super(TestShellPasswordAuth, self).setUp() + super(TestShellOptions, self).setUp() self.orig_env, os.environ = os.environ, {} def tearDown(self): - super(TestShellPasswordAuth, self).tearDown() + super(TestShellOptions, self).tearDown() os.environ = self.orig_env - def test_only_url_flow(self): - flag = "--os-auth-url " + DEFAULT_AUTH_URL - kwargs = { - "auth_url": DEFAULT_AUTH_URL, - } - self._assert_password_auth(flag, kwargs) - - def test_only_project_id_flow(self): - flag = "--os-project-id " + DEFAULT_PROJECT_ID - kwargs = { - "project_id": DEFAULT_PROJECT_ID, - } - self._assert_password_auth(flag, kwargs) - - def test_only_project_name_flow(self): - flag = "--os-project-name " + DEFAULT_PROJECT_NAME - kwargs = { - "project_name": DEFAULT_PROJECT_NAME, - } - self._assert_password_auth(flag, kwargs) - - def test_only_domain_id_flow(self): - flag = "--os-domain-id " + DEFAULT_DOMAIN_ID - kwargs = { - "domain_id": DEFAULT_DOMAIN_ID, - } - self._assert_password_auth(flag, kwargs) - - def test_only_domain_name_flow(self): - flag = "--os-domain-name " + DEFAULT_DOMAIN_NAME - kwargs = { - "domain_name": DEFAULT_DOMAIN_NAME, - } - self._assert_password_auth(flag, kwargs) - - def test_only_user_domain_id_flow(self): - flag = "--os-user-domain-id " + DEFAULT_USER_DOMAIN_ID - kwargs = { - "user_domain_id": DEFAULT_USER_DOMAIN_ID, - } - self._assert_password_auth(flag, kwargs) - - def test_only_user_domain_name_flow(self): - flag = "--os-user-domain-name " + DEFAULT_USER_DOMAIN_NAME - kwargs = { - "user_domain_name": DEFAULT_USER_DOMAIN_NAME, - } - self._assert_password_auth(flag, kwargs) - - def test_only_project_domain_id_flow(self): - flag = "--os-project-domain-id " + DEFAULT_PROJECT_DOMAIN_ID - kwargs = { - "project_domain_id": DEFAULT_PROJECT_DOMAIN_ID, - } - self._assert_password_auth(flag, kwargs) - - def test_only_project_domain_name_flow(self): - flag = "--os-project-domain-name " + DEFAULT_PROJECT_DOMAIN_NAME - kwargs = { - "project_domain_name": DEFAULT_PROJECT_DOMAIN_NAME, - } - self._assert_password_auth(flag, kwargs) - - def test_only_username_flow(self): - flag = "--os-username " + DEFAULT_USERNAME - kwargs = { - "username": DEFAULT_USERNAME, - } - self._assert_password_auth(flag, kwargs) - - def test_only_password_flow(self): - flag = "--os-password " + DEFAULT_PASSWORD - kwargs = { - "password": DEFAULT_PASSWORD, - } - self._assert_password_auth(flag, kwargs) - - def test_only_region_name_flow(self): - flag = "--os-region-name " + DEFAULT_REGION_NAME - kwargs = { - "region_name": DEFAULT_REGION_NAME, - } - self._assert_password_auth(flag, kwargs) + def _test_options_init_app(self, test_opts): + for opt in test_opts.keys(): + if not test_opts[opt][1]: + continue + key = opt2attr(opt) + if type(test_opts[opt][0]) is str: + cmd = opt + " " + test_opts[opt][0] + else: + cmd = opt + kwargs = { + key: test_opts[opt][0], + } + self._assert_initialize_app_arg(cmd, kwargs) + + def _test_options_get_one_cloud(self, test_opts): + for opt in test_opts.keys(): + if not test_opts[opt][1]: + continue + key = opt2attr(opt) + if type(test_opts[opt][0]) is str: + cmd = opt + " " + test_opts[opt][0] + else: + cmd = opt + kwargs = { + key: test_opts[opt][0], + } + self._assert_cloud_config_arg(cmd, kwargs) + + def _test_env_init_app(self, test_opts): + for opt in test_opts.keys(): + if not test_opts[opt][2]: + continue + key = opt2attr(opt) + kwargs = { + key: test_opts[opt][0], + } + env = { + opt2env(opt): test_opts[opt][0], + } + os.environ = env.copy() + self._assert_initialize_app_arg("", kwargs) + + def _test_env_get_one_cloud(self, test_opts): + for opt in test_opts.keys(): + if not test_opts[opt][2]: + continue + key = opt2attr(opt) + kwargs = { + key: test_opts[opt][0], + } + env = { + opt2env(opt): test_opts[opt][0], + } + os.environ = env.copy() + self._assert_cloud_config_arg("", kwargs) - def test_only_trust_id_flow(self): - flag = "--os-trust-id " + "1234" - kwargs = { - "trust_id": "1234", - } - self._assert_password_auth(flag, kwargs) - - def test_only_auth_type_flow(self): - flag = "--os-auth-type " + "v2password" - kwargs = { - "auth_type": DEFAULT_AUTH_PLUGIN - } - self._assert_password_auth(flag, kwargs) + def test_empty_auth(self): + os.environ = {} + self._assert_initialize_app_arg("", {}) + self._assert_cloud_config_arg("", {}) + def test_global_options(self): + self._test_options_init_app(global_options) + self._test_options_get_one_cloud(global_options) -class TestShellTokenAuth(TestShell): - def test_only_token(self): - flag = "--os-token " + DEFAULT_TOKEN - kwargs = { - "token": DEFAULT_TOKEN, - "auth_url": '', - } - self._assert_token_auth(flag, kwargs) + def test_auth_options(self): + self._test_options_init_app(auth_options) + self._test_options_get_one_cloud(auth_options) - def test_only_auth_url(self): - flag = "--os-auth-url " + DEFAULT_AUTH_URL - kwargs = { - "token": '', - "auth_url": DEFAULT_AUTH_URL, - } - self._assert_token_auth(flag, kwargs) + def test_global_env(self): + self._test_env_init_app(global_options) + self._test_env_get_one_cloud(global_options) - def test_empty_auth(self): - os.environ = {} - flag = "" - kwargs = {} - self._assert_token_auth(flag, kwargs) + def test_auth_env(self): + self._test_env_init_app(auth_options) + self._test_env_get_one_cloud(auth_options) class TestShellTokenAuthEnv(TestShell): @@ -437,33 +427,6 @@ def test_empty_auth(self): self._assert_token_auth(flag, kwargs) -class TestShellTokenEndpointAuth(TestShell): - def test_only_token(self): - flag = "--os-token " + DEFAULT_TOKEN - kwargs = { - "token": DEFAULT_TOKEN, - "url": '', - } - self._assert_token_endpoint_auth(flag, kwargs) - - def test_only_url(self): - flag = "--os-url " + DEFAULT_SERVICE_URL - kwargs = { - "token": '', - "url": DEFAULT_SERVICE_URL, - } - self._assert_token_endpoint_auth(flag, kwargs) - - def test_empty_auth(self): - os.environ = {} - flag = "" - kwargs = { - "token": '', - "auth_url": '', - } - self._assert_token_endpoint_auth(flag, kwargs) - - class TestShellTokenEndpointAuthEnv(TestShell): def setUp(self): super(TestShellTokenEndpointAuthEnv, self).setUp() @@ -545,35 +508,35 @@ def test_shell_args_ca_options(self): fake_execute(_shell, "list user") self.assertIsNone(_shell.options.verify) self.assertIsNone(_shell.options.insecure) - self.assertEqual('', _shell.options.os_cacert) + self.assertEqual('', _shell.options.cacert) self.assertTrue(_shell.verify) # --verify fake_execute(_shell, "--verify list user") self.assertTrue(_shell.options.verify) self.assertIsNone(_shell.options.insecure) - self.assertEqual('', _shell.options.os_cacert) + self.assertEqual('', _shell.options.cacert) self.assertTrue(_shell.verify) # --insecure fake_execute(_shell, "--insecure list user") self.assertIsNone(_shell.options.verify) self.assertTrue(_shell.options.insecure) - self.assertEqual('', _shell.options.os_cacert) + self.assertEqual('', _shell.options.cacert) self.assertFalse(_shell.verify) # --os-cacert fake_execute(_shell, "--os-cacert foo list user") self.assertIsNone(_shell.options.verify) self.assertIsNone(_shell.options.insecure) - self.assertEqual('foo', _shell.options.os_cacert) + self.assertEqual('foo', _shell.options.cacert) self.assertTrue(_shell.verify) # --os-cacert and --verify fake_execute(_shell, "--os-cacert foo --verify list user") self.assertTrue(_shell.options.verify) self.assertIsNone(_shell.options.insecure) - self.assertEqual('foo', _shell.options.os_cacert) + self.assertEqual('foo', _shell.options.cacert) self.assertTrue(_shell.verify) # --os-cacert and --insecure @@ -583,7 +546,7 @@ def test_shell_args_ca_options(self): fake_execute(_shell, "--os-cacert foo --insecure list user") self.assertIsNone(_shell.options.verify) self.assertTrue(_shell.options.insecure) - self.assertEqual('foo', _shell.options.os_cacert) + self.assertEqual('foo', _shell.options.cacert) self.assertTrue(_shell.verify) def test_default_env(self): From b2cf651100b2a6dc1b934b86390eff94ef9f8fdc Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Thu, 28 May 2015 07:27:24 -0600 Subject: [PATCH 0087/3095] Fix security group list command Security group list command tries to get a project list and this may fail with a multitude of exceptions including but not limited to 401, 404, ConnectionRefused and EndpointNotFound. Rather than try to capture every possibility, this patch just catches the base class. Converting project ids to names is less important than having a working security group list command. Change-Id: I68214d2680bad907f9d04ad3ca2f62cf3feee028 Closes-Bug: #1459629 --- openstackclient/compute/v2/security_group.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/compute/v2/security_group.py b/openstackclient/compute/v2/security_group.py index d860bf808e..3dc9bae0b5 100644 --- a/openstackclient/compute/v2/security_group.py +++ b/openstackclient/compute/v2/security_group.py @@ -161,7 +161,7 @@ def _get_project(project_id): project_hash = {} try: projects = self.app.client_manager.identity.projects.list() - except ksc_exc.Forbidden: + except ksc_exc.ClientException: # This fails when the user is not an admin, just move along pass else: From f73716077772f44621a9f544388c4c32403734dc Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Tue, 2 Jun 2015 09:21:31 -0600 Subject: [PATCH 0088/3095] Get rid of oslo_i18n deprecation notice Change-Id: I12aa58b808c05d3eb6f5efcdc84df57f54a9782e --- openstackclient/i18n.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openstackclient/i18n.py b/openstackclient/i18n.py index 3611b315c9..3a11c1d0dc 100644 --- a/openstackclient/i18n.py +++ b/openstackclient/i18n.py @@ -13,9 +13,9 @@ # under the License. # -from oslo import i18n +import oslo_i18n -_translators = i18n.TranslatorFactory(domain='python-openstackclient') +_translators = oslo_i18n.TranslatorFactory(domain='python-openstackclient') # The primary translation function using the well-known name "_" _ = _translators.primary From 226fc6c80abdea266d15658d31e34baabc3be9ca Mon Sep 17 00:00:00 2001 From: Jamie Lennox Date: Mon, 1 Jun 2015 15:15:20 +1000 Subject: [PATCH 0089/3095] Change Credentials header to Blob from data The payload data of credentials is the unfortunately named blob. Currently when listing credentials the payload is excluded as OSC is looking for a column called data which does not exist. Change-Id: I6fa4579d7ec9ba393ede550191dbd8aa29767bf4 --- openstackclient/identity/v3/credential.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openstackclient/identity/v3/credential.py b/openstackclient/identity/v3/credential.py index 4d6889548b..dacbc9af51 100644 --- a/openstackclient/identity/v3/credential.py +++ b/openstackclient/identity/v3/credential.py @@ -105,9 +105,10 @@ class ListCredential(lister.Lister): def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) - columns = ('ID', 'Type', 'User ID', 'Data', 'Project ID') + columns = ('ID', 'Type', 'User ID', 'Blob', 'Project ID') + column_headers = ('ID', 'Type', 'User ID', 'Data', 'Project ID') data = self.app.client_manager.identity.credentials.list() - return (columns, + return (column_headers, (utils.get_item_properties( s, columns, formatters={}, From bf99218292addf025f56ad726967635928389a04 Mon Sep 17 00:00:00 2001 From: Jens Rosenboom Date: Wed, 3 Jun 2015 02:34:07 +0200 Subject: [PATCH 0090/3095] Add a reference to the IRC channels As the name of the associated IRC channel for this project is not completely obvious, add a reference to the doc index page so that it may be found easier. Change-Id: I6cb6e2571bff73db6d983cd96fe6d03319d6577c --- doc/source/index.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/source/index.rst b/doc/source/index.rst index 7a5954799c..a3bc58e42a 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -49,6 +49,7 @@ contributions. The code is hosted `on OpenStack's Git server`_. `Bug reports`_ and `blueprints`_ may be submitted to the :code:`python-openstackclient` project on `Launchpad`_. Code may be submitted to the :code:`openstack/python-openstackclient` project using `Gerrit`_. +Developers may also be found in the `IRC channel`_ ``#openstack-sdks``. .. _`on OpenStack's Git server`: https://git.openstack.org/cgit/openstack/python-openstackclient/tree .. _Launchpad: https://launchpad.net/python-openstackclient @@ -57,6 +58,7 @@ on `Launchpad`_. Code may be submitted to the .. _blueprints: https://blueprints.launchpad.net/python-openstackclient .. _PyPi: https://pypi.python.org/pypi/python-openstackclient .. _tarball: http://tarballs.openstack.org/python-openstackclient +.. _IRC channel: https://wiki.openstack.org/wiki/IRC Indices and Tables ================== From db7d4ebcfb6c071423b6623b1bad6526cca32235 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 3 Jun 2015 06:05:19 +0000 Subject: [PATCH 0091/3095] Imported Translations from Transifex For more information about this automatic import see: https://wiki.openstack.org/wiki/Translations/Infrastructure Change-Id: I1e445ea9300686f17d3289fb026fd62b6259e0a0 --- .../de/LC_MESSAGES/python-openstackclient.po | 778 +++++++++--------- .../locale/python-openstackclient.pot | 173 ++-- .../LC_MESSAGES/python-openstackclient.po | 694 ++++++++-------- 3 files changed, 837 insertions(+), 808 deletions(-) diff --git a/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po b/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po index 079127c1c7..0de5c743c7 100644 --- a/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po +++ b/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po @@ -9,8 +9,8 @@ msgid "" msgstr "" "Project-Id-Version: python-openstackclient\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-05-05 06:06+0000\n" -"PO-Revision-Date: 2015-04-19 18:03+0000\n" +"POT-Creation-Date: 2015-06-03 06:05+0000\n" +"PO-Revision-Date: 2015-06-01 21:54+0000\n" "Last-Translator: Ettore Atalan \n" "Language-Team: German (http://www.transifex.com/projects/p/python-" "openstackclient/language/de/)\n" @@ -20,93 +20,82 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 1.3\n" -msgid "Set a username with --os-username, OS_USERNAME, or auth.username\n" +msgid "" +"\n" +"Complete\n" msgstr "" -"Legen Sie einen Benutzernamen mit --os-username, OS_USERNAME oder auth." -"username fest\n" +"\n" +"Fertig\n" msgid "" -"Set an authentication URL, with --os-auth-url, OS_AUTH_URL or auth.auth_url\n" +"\n" +"Error creating server" msgstr "" -"Legen Sie eine Authentifizierungs-URL mit --os-auth-url, OS_AUTH_URL oder " -"auth.auth_url fest\n" +"\n" +"Fehler beim Erstellen des Servers" msgid "" -"Set a scope, such as a project or domain, with --os-project-name, " -"OS_PROJECT_NAME or auth.project_name" +"\n" +"Error creating server snapshot" msgstr "" -"Legen Sie einen Gültigkeitsbereich, wie beispielsweise ein Projekt oder eine " -"Domäne, mit OS_PROJECT_NAME oder auth.project_name fest" - -msgid "Set a token with --os-token, OS_TOKEN or auth.token\n" -msgstr "Legen Sie einen Token mit --os-token, OS_TOKEN oder auth.token fest\n" +"\n" +"Fehler beim Erstellen der Server-Schattenkopie" msgid "" -"Set a service AUTH_URL, with --os-auth-url, OS_AUTH_URL or auth.auth_url\n" +"\n" +"Error deleting server" msgstr "" -"Legen Sie eine Dienst-AUTH_URL mit --os-auth-url, OS_AUTH_URL oder auth." -"auth_url fest\n" - -msgid "Set a service URL, with --os-url, OS_URL or auth.url\n" -msgstr "Legen Sie eine Dienst-URL mit --os-url, OS_URL oder auth.url fest\n" - -msgid "List additional fields in output" -msgstr "Zusätzliche Felder in der Ausgabe auflisten" - -msgid "Server (name or ID)" -msgstr "Server (Name oder Kennung)" - -msgid "Security group to add (name or ID)" -msgstr "Zu hinzufügende Sicherheitsgruppe (Name oder Kennung)" - -msgid "Volume to add (name or ID)" -msgstr "Zu hinzufügender Datenträger (Name oder Kennung)" - -msgid "Server internal device name for volume" -msgstr "Serverinterner Gerätename für Datenträger" - -msgid "New server name" -msgstr "Neuer Servername" - -msgid "Create server from this image" -msgstr "Server aus diesem Abbild erstellen" +"\n" +"Fehler beim Löschen des Servers" -msgid "Create server from this volume" -msgstr "Server aus diesem Datenträger erstellen" +msgid "" +"\n" +"Error migrating server" +msgstr "" +"\n" +"Fehler beim Migrieren des Servers" -msgid "Create server with this flavor" -msgstr "Server mit dieser Variante erstellen" +msgid "" +"\n" +"Error rebooting server\n" +msgstr "" +"\n" +"Fehler beim Neustarten des Servers\n" -msgid "Security group to assign to this server (repeat for multiple groups)" +msgid "" +"\n" +"Error rebuilding server" msgstr "" -"Zu diesem Server zuweisende Sicherheitsgruppe (für mehrere Gruppen " -"wiederholen)" +"\n" +"Fehler bei der Wiederherstellung des Servers" -msgid "Keypair to inject into this server (optional extension)" -msgstr "In diesem Server einzufügendes Schlüsselpaar (optionale Erweiterung)" +msgid "" +"\n" +"Error resizing server" +msgstr "" +"\n" +"Fehler bei der Größenänderung des Servers" -msgid "Set a property on this server (repeat for multiple values)" +msgid "" +"\n" +"Reboot complete\n" msgstr "" -"Legen Sie eine Eigenschaft auf diesem Server fest (für mehrere Werte " -"wiederholen)" +"\n" +"Neustart abgeschlossen\n" -msgid "File to inject into image before boot (repeat for multiple files)" +msgid "Add a property to (repeat option to set multiple properties)" msgstr "" -"Vor dem Start auf diesem Abbild einzufügende Datei (für mehrere Dateien " -"wiederholen)" +"Fügen Sie eine Eigenschaft zu hinzu (wiederholen Sie die Option, um " +"mehrere Eigenschaften festzulegen)" -msgid "User data file to serve from the metadata server" -msgstr "Vom Metadatenserver anzubietende Benutzerdatendatei" +msgid "Allow disk over-commit on the destination host" +msgstr "Festplattenüberladung auf dem Zielhost erlauben" -msgid "Select an availability zone for the server" -msgstr "Wählen Sie eine Verfügbarkeitszone für den Server aus" +msgid "Complete\n" +msgstr "Fertig\n" -msgid "" -"Map block devices; map is ::: " -"(optional extension)" -msgstr "" -"Blockorientierte Geräte abbilden; Abbildung ist :::" -" (optionale Erweiterung)" +msgid "Confirm server resize is complete" +msgstr "Bestätigen Sie, ob die Größenänderung des Servers abgeschlossen ist" msgid "" "Create a NIC on the server. Specify option multiple times to create multiple " @@ -122,383 +111,416 @@ msgstr "" "IPv4-Adresse für NIC (optional), v6-fixed-ip: Feste IPv6-Adresse für NIC " "(optional)." -msgid "Hints for the scheduler (optional extension)" -msgstr "Hinweise für den Planer (optionale Erweiterung)" +msgid "Create server from this image" +msgstr "Server aus diesem Abbild erstellen" -msgid "" -"Use specified volume as the config drive, or 'True' to use an ephemeral drive" -msgstr "" -"Benutzerdefinierter Datenträger als Konfigurationslaufwerk oder 'True', um " -"ein flüchtiges Laufwerk zu verwenden" +msgid "Create server from this volume" +msgstr "Server aus diesem Datenträger erstellen" -msgid "Minimum number of servers to launch (default=1)" -msgstr "Minimale Anzahl der zu startenden Server (Standard=1)" +msgid "Create server with this flavor" +msgstr "Server mit dieser Variante erstellen" -msgid "Maximum number of servers to launch (default=1)" -msgstr "Maximale Anzahl der zu startenden Server (Standard=1)" +msgid "Credentials access key" +msgstr "Anmeldedaten-Zugriffsschlüssel" -msgid "Wait for build to complete" -msgstr "Warten Sie, bis die Herstellung abgeschlossen ist" +msgid "Default project (name or ID)" +msgstr "Standardprojekt (Name oder Kennung)" -msgid "min instances should be <= max instances" -msgstr "min. Instanzen sollten <= max. Instanzen sein" +msgid "Destination port (ssh -p option)" +msgstr "Zielport (ssh -p Option)" -msgid "min instances should be > 0" -msgstr "min. Instanzen sollten > 0 sein" +msgid "Disable project" +msgstr "Projekt deaktivieren" -msgid "max instances should be > 0" -msgstr "max. Instanzen sollten > 0 sein" +msgid "Disable user" +msgstr "Benutzer deaktivieren" -msgid "either net-id or port-id should be specified but not both" -msgstr "entweder net-id oder port-id sollten angegeben sein, aber nicht beide" +msgid "Display server diagnostics information" +msgstr "Serverdiagnoseinformationen anzeigen" -msgid "can't create server with port specified since neutron not enabled" -msgstr "" -"Server mit dem angegebenen Port kann nicht erstellt werden, da Neutron nicht " -"aktiviert ist" +msgid "Do not over-commit disk on the destination host (default)" +msgstr "Festplatte auf dem Zielhost nicht überladen (Standard)" -#, python-format -msgid "Error creating server: %s" -msgstr "Fehler beim Erstellen des Servers: %s" +msgid "Enable project" +msgstr "Projekt aktivieren" -msgid "" -"\n" -"Error creating server" -msgstr "" -"\n" -"Fehler beim Erstellen des Servers" +msgid "Enable project (default)" +msgstr "Projekt aktivieren (Standardeinstellung)" -msgid "Name of new image (default is server name)" -msgstr "Name des neuen Abbilds (Servername ist Standard)" +msgid "Enable user (default)" +msgstr "Benutzer aktivieren (Standardeinstellung)" -msgid "Wait for image create to complete" -msgstr "Warten Sie, bis die Abbilderstellung abgeschlossen ist" +msgid "Endpoint ID to delete" +msgstr "Zu löschende Endpunktkennung" + +msgid "Endpoint ID to display" +msgstr "Anzuzeigende Endpunktkennung" #, python-format msgid "Error creating server snapshot: %s" msgstr "Fehler beim Erstellen der Server-Schattenkopie: %s" -msgid "" -"\n" -"Error creating server snapshot" -msgstr "" -"\n" -"Fehler beim Erstellen der Server-Schattenkopie" - -msgid "Server(s) to delete (name or ID)" -msgstr "Zu löschende(r) Server (Name oder Kennung)" +#, python-format +msgid "Error creating server: %s" +msgstr "Fehler beim Erstellen des Servers: %s" -msgid "Only return instances that match the reservation" -msgstr "Nur Instanzen zurückgeben, die der Einschränkung entsprechen" +#, python-format +msgid "Error deleting server: %s" +msgstr "Fehler beim Löschen des Servers: %s" -msgid "Regular expression to match IP addresses" -msgstr "Regulärer Ausdruck zum Abgleichen mit IP-Adressen" +msgid "Error retrieving diagnostics data" +msgstr "Fehler beim Abrufen der Diagnosedaten" -msgid "Regular expression to match IPv6 addresses" -msgstr "Regulärer Ausdruck zum Abgleichen mit IPv6-Adressen" +msgid "File to inject into image before boot (repeat for multiple files)" +msgstr "" +"Vor dem Start auf diesem Abbild einzufügende Datei (für mehrere Dateien " +"wiederholen)" -msgid "Regular expression to match names" -msgstr "Regulärer Ausdruck zum Abgleichen mit Namen" +msgid "Filter by parent region ID" +msgstr "Nach übergeordneter Regionskennung filtern" -msgid "Regular expression to match instance name (admin only)" -msgstr "" -"Regulärer Ausdruck zum Abgleichen des Instanznamens (nur Administrator) " +msgid "Filter users by (name or ID)" +msgstr "Benutzer nach filtern (Name oder Kennung)" -msgid "Search by server status" -msgstr "Nach Serverstatus suchen" +msgid "Filter users by project (name or ID)" +msgstr "Benutzer nach Projekt filtern (Name oder Kennung)" -msgid "Search by flavor" -msgstr "Nach Variante suchen" +msgid "Hints for the scheduler (optional extension)" +msgstr "Hinweise für den Planer (optionale Erweiterung)" -msgid "Search by image" -msgstr "Nach Bild suchen" +msgid "Include (name or ID)" +msgstr " miteinbeziehen (Name oder Kennung)" -msgid "Search by hostname" -msgstr "Nach Hostname suchen" +msgid "Include (name or ID)" +msgstr " miteinbeziehen (Name oder Kennung)" msgid "Include all projects (admin only)" msgstr "Alle Projekte miteinbeziehen (nur Administrator)" -msgid "Target hostname" -msgstr "Zielhostname" +msgid "Keypair to inject into this server (optional extension)" +msgstr "In diesem Server einzufügendes Schlüsselpaar (optionale Erweiterung)" -msgid "Perform a shared live migration (default)" -msgstr "Gemeinsame Live-Migration ausführen (Standard)" +msgid "List additional fields in output" +msgstr "Zusätzliche Felder in der Ausgabe auflisten" -msgid "Perform a block live migration" -msgstr "Blockorientierte Live-Migration ausführen" +msgid "Login name (ssh -l option)" +msgstr "Anmeldename (ssh -l Option)" -msgid "Allow disk over-commit on the destination host" -msgstr "Festplattenüberladung auf dem Zielhost erlauben" +msgid "" +"Map block devices; map is ::: " +"(optional extension)" +msgstr "" +"Blockorientierte Geräte abbilden; Abbildung ist :::" +" (optionale Erweiterung)" -msgid "Do not over-commit disk on the destination host (default)" -msgstr "Festplatte auf dem Zielhost nicht überladen (Standard)" +msgid "Maximum number of servers to launch (default=1)" +msgstr "Maximale Anzahl der zu startenden Server (Standard=1)" -msgid "Wait for resize to complete" -msgstr "Warten Sie, bis die Größenänderung abgeschlossen ist" +msgid "Minimum number of servers to launch (default=1)" +msgstr "Minimale Anzahl der zu startenden Server (Standard=1)" -msgid "Complete\n" -msgstr "Fertig\n" +msgid "Name of new image (default is server name)" +msgstr "Name des neuen Abbilds (Servername ist Standard)" -msgid "" -"\n" -"Error migrating server" -msgstr "" -"\n" -"Fehler beim Migrieren des Servers" +msgid "Name or ID of security group to remove from server" +msgstr "Name oder Kennung der vom Server zu entfernenden Sicherheitsgruppe" -msgid "Perform a hard reboot" -msgstr "Harten Neustart ausführen" +msgid "Name or ID of server to use" +msgstr "Name oder Kennung des zu verwendenden Servers" -msgid "Perform a soft reboot" -msgstr "Weichen Neustart ausführen" +msgid "New endpoint admin URL" +msgstr "Administrator-URL des neuen Endpunktes" -msgid "Wait for reboot to complete" -msgstr "Warten Sie, bis der Neustart abgeschlossen ist" +msgid "New endpoint internal URL" +msgstr "Interne URL des neuen Endpunktes" -msgid "" -"\n" -"Reboot complete\n" -msgstr "" -"\n" -"Neustart abgeschlossen\n" +msgid "New endpoint public URL (required)" +msgstr "Öffentliche URL des neuen Endpunktes (erforderlich)" -msgid "" -"\n" -"Error rebooting server\n" -msgstr "" -"\n" -"Fehler beim Neustarten des Servers\n" +msgid "New endpoint region ID" +msgstr "Neue Endpunkt-Regionskennung" -msgid "Recreate server from this image" -msgstr "Server aus diesem Abbild neu erstellen" +msgid "New endpoint service (name or ID)" +msgstr "Neuer Endpunktdienst (Name oder Kennung)" -msgid "Wait for rebuild to complete" -msgstr "Warten Sie, bis die Wiederherstellung abgeschlossen ist" +msgid "New parent region ID" +msgstr "Neue übergeordnete Regionskennung" -msgid "" -"\n" -"Complete\n" -msgstr "" -"\n" -"Fertig\n" +msgid "New password: " +msgstr "Neues Passwort:" -msgid "" -"\n" -"Error rebuilding server" -msgstr "" -"\n" -"Fehler bei der Wiederherstellung des Servers" +msgid "New project name" +msgstr "Name des neuen Projekts" -msgid "Name or ID of server to use" -msgstr "Name oder Kennung des zu verwendenden Servers" +msgid "New region ID" +msgstr "Neue Regionskennung" -msgid "Name or ID of security group to remove from server" -msgstr "Name oder Kennung der vom Server zu entfernenden Sicherheitsgruppe" +msgid "New region description" +msgstr "Beschreibung des neuen Bereichs" -msgid "Volume to remove (name or ID)" -msgstr "Zu entfernender Datenträger (Name oder Kennung)" +msgid "New region url" +msgstr "URL des neuen Bereichs" -msgid "Resize server to specified flavor" -msgstr "Servergröße auf angegebene Variante ändern" +msgid "New role name" +msgstr "Name der neuen Rolle" -msgid "Confirm server resize is complete" -msgstr "Bestätigen Sie, ob die Größenänderung des Servers abgeschlossen ist" +msgid "New server name" +msgstr "Neuer Servername" -msgid "Restore server state before resize" -msgstr "Serverstatus vor der Größenänderung wiederherstellen" +msgid "New service description" +msgstr "Beschreibung des neuen Dienstes" -msgid "" -"\n" -"Error resizing server" -msgstr "" -"\n" -"Fehler bei der Größenänderung des Servers" +msgid "New service name" +msgstr "Name des neuen Dienstes" -msgid "Set new root password (interactive only)" -msgstr "Neues root-Passwort festlegen (Nur interaktiv)" +msgid "New service type (compute, image, identity, volume, etc)" +msgstr "Neuer Diensttyp (Berechnen, Abbild, Identität, Datenträger, usw.)" -msgid "" -"Property to add/change for this server (repeat option to set multiple " -"properties)" -msgstr "" -"Zu hinzufügende/ändernde Eigenschaft für diesen Server (wiederholen Sie die " -"Option, um mehrere Eigenschaften festzulegen)" +msgid "New user name" +msgstr "Neuer Benutzername" -msgid "New password: " -msgstr "Neues Passwort:" +#, python-format +msgid "No service catalog with a type, name or ID of '%s' exists." +msgstr "Kein Dienstkatalog mit Typ, Namen oder Kennung von '%s' ist vorhanden." -msgid "Retype new password: " -msgstr "Neues Passwort erneut eingeben:" +msgid "Only return instances that match the reservation" +msgstr "Nur Instanzen zurückgeben, die der Einschränkung entsprechen" + +msgid "Options in ssh_config(5) format (ssh -o option)" +msgstr "Optionen im ssh_config(5)-Format (ssh -o Option)" + +msgid "Parent region ID" +msgstr "Übergeordnete Regionskennung" msgid "Passwords do not match, password unchanged" msgstr "Passwörter stimmen nicht überein, Passwort unverändert" -msgid "Display server diagnostics information" -msgstr "Serverdiagnoseinformationen anzeigen" +msgid "Perform a block live migration" +msgstr "Blockorientierte Live-Migration ausführen" -msgid "Error retrieving diagnostics data" -msgstr "Fehler beim Abrufen der Diagnosedaten" +msgid "Perform a hard reboot" +msgstr "Harten Neustart ausführen" -msgid "Login name (ssh -l option)" -msgstr "Anmeldename (ssh -l Option)" +msgid "Perform a shared live migration (default)" +msgstr "Gemeinsame Live-Migration ausführen (Standard)" -msgid "Destination port (ssh -p option)" -msgstr "Zielport (ssh -p Option)" +msgid "Perform a soft reboot" +msgstr "Weichen Neustart ausführen" msgid "Private key file (ssh -i option)" msgstr "Private Schlüsseldatei (ssh -i Option)" -msgid "Options in ssh_config(5) format (ssh -o option)" -msgstr "Optionen im ssh_config(5)-Format (ssh -o Option)" +msgid "Project description" +msgstr "Projektbeschreibung" -msgid "Use only IPv4 addresses" -msgstr "Nur IPv4-Adressen verwenden" +msgid "Project must be specified" +msgstr "Projekt muss angegeben werden" -msgid "Use only IPv6 addresses" -msgstr "Nur IPv6-Adressen verwenden" +msgid "Project to display (name or ID)" +msgstr "Anzuzeigendes Projekt (Name oder Kennung)" -msgid "Use public IP address" -msgstr "Öffentliche IP-Adresse verwenden" +msgid "Project to modify (name or ID)" +msgstr "Zu änderndes Projekt (Name oder Kennung)" -msgid "Use private IP address" -msgstr "Private IP-Adresse verwenden" +msgid "Project(s) to delete (name or ID)" +msgstr "Zu löschende(s) Projekt(e) (Name oder Kennung)" -msgid "Use other IP address (public, private, etc)" -msgstr "Andere IP-Adresse verwenden (öffentlich, privat, usw.)" +msgid "Prompt interactively for password" +msgstr "Interaktiv nach dem Passwort abfragen" msgid "Property key to remove from server (repeat to unset multiple values)" msgstr "" "Vom Server zu entfernender Eigenschaftsschlüssel (zum Aufheben von mehreren " "Werten wiederholen)" -msgid "Service to display (type or name)" -msgstr "Anzuzeigender Dienst (Typ oder Name)" - -msgid "Specify an alternate project (default: current authenticated project)" +msgid "" +"Property to add/change for this server (repeat option to set multiple " +"properties)" msgstr "" -"Geben Sie ein alternatives Projekt an (Standard: aktuell authentifiziertes " -"Projekt)" +"Zu hinzufügende/ändernde Eigenschaft für diesen Server (wiederholen Sie die " +"Option, um mehrere Eigenschaften festzulegen)" -msgid "Specify an alternate user (default: current authenticated user)" +msgid "Recreate server from this image" +msgstr "Server aus diesem Abbild neu erstellen" + +msgid "Region ID to delete" +msgstr "Zu löschende Regionskennung" + +msgid "Region to display" +msgstr "Anzuzeigende Region" + +msgid "Region to modify" +msgstr "Zu ändernde Region" + +msgid "Regular expression to match IP addresses" +msgstr "Regulärer Ausdruck zum Abgleichen mit IP-Adressen" + +msgid "Regular expression to match IPv6 addresses" +msgstr "Regulärer Ausdruck zum Abgleichen mit IPv6-Adressen" + +msgid "Regular expression to match instance name (admin only)" msgstr "" -"Geben Sie einen alternativen Benutzer an (Standard: aktuell " -"authentifizierter Benutzer)" +"Regulärer Ausdruck zum Abgleichen des Instanznamens (nur Administrator) " -msgid "Credentials access key" -msgstr "Anmeldedaten-Zugriffsschlüssel" +msgid "Regular expression to match names" +msgstr "Regulärer Ausdruck zum Abgleichen mit Namen" -msgid "Specify a user" -msgstr "Geben Sie einen Benutzer an" +msgid "Resize server to specified flavor" +msgstr "Servergröße auf angegebene Variante ändern" -msgid "New endpoint service (name or ID)" -msgstr "Neuer Endpunktdienst (Name oder Kennung)" +msgid "Restore server state before resize" +msgstr "Serverstatus vor der Größenänderung wiederherstellen" -msgid "New endpoint public URL (required)" -msgstr "Öffentliche URL des neuen Endpunktes (erforderlich)" +msgid "Return existing domain" +msgstr "Vorhandene Domäne zurückgeben" -msgid "New endpoint admin URL" -msgstr "Administrator-URL des neuen Endpunktes" +msgid "Return existing group" +msgstr "Vorhandene Gruppe zurückgeben" -msgid "New endpoint internal URL" -msgstr "Interne URL des neuen Endpunktes" +msgid "Return existing project" +msgstr "Vorhandenes Projekt zurückgeben" -msgid "New endpoint region ID" -msgstr "Neue Endpunkt-Regionskennung" +msgid "Return existing role" +msgstr "Vorhandene Rolle zurückgeben" -msgid "Endpoint ID to delete" -msgstr "Zu löschende Endpunktkennung" +msgid "Return existing user" +msgstr "Vorhandenen Benutzer zurückgeben" -msgid "Endpoint ID to display" -msgstr "Anzuzeigende Endpunktkennung" +msgid "Retype new password: " +msgstr "Neues Passwort erneut eingeben:" -msgid "New project name" -msgstr "Name des neuen Projekts" +msgid "Role to add to : (name or ID)" +msgstr "Zu : hinzuzufügende Rolle (Name oder Kennung)" -msgid "Project description" -msgstr "Projektbeschreibung" +msgid "Role to display (name or ID)" +msgstr "Anzuzeigende Rolle (Name oder Kennung)" -msgid "Enable project (default)" -msgstr "Projekt aktivieren (Standardeinstellung)" +msgid "Role to remove (name or ID)" +msgstr "Zu entfernende Rolle (Name oder Kennung)" -msgid "Disable project" -msgstr "Projekt deaktivieren" +msgid "Role(s) to delete (name or ID)" +msgstr "Zu löschende Rolle(n) (Name oder Kennung)" -msgid "Add a property to (repeat option to set multiple properties)" +msgid "Search by flavor" +msgstr "Nach Variante suchen" + +msgid "Search by hostname" +msgstr "Nach Hostname suchen" + +msgid "Search by image" +msgstr "Nach Bild suchen" + +msgid "Search by server status" +msgstr "Nach Serverstatus suchen" + +msgid "Security group to add (name or ID)" +msgstr "Zu hinzufügende Sicherheitsgruppe (Name oder Kennung)" + +msgid "Security group to assign to this server (repeat for multiple groups)" msgstr "" -"Fügen Sie eine Eigenschaft zu hinzu (wiederholen Sie die Option, um " -"mehrere Eigenschaften festzulegen)" +"Zu diesem Server zuweisende Sicherheitsgruppe (für mehrere Gruppen " +"wiederholen)" -msgid "Return existing project" -msgstr "Vorhandenes Projekt zurückgeben" +msgid "Select an availability zone for the server" +msgstr "Wählen Sie eine Verfügbarkeitszone für den Server aus" -msgid "Project(s) to delete (name or ID)" -msgstr "Zu löschende(s) Projekt(e) (Name oder Kennung)" +msgid "Server (name or ID)" +msgstr "Server (Name oder Kennung)" -msgid "Project to modify (name or ID)" -msgstr "Zu änderndes Projekt (Name oder Kennung)" +msgid "Server internal device name for volume" +msgstr "Serverinterner Gerätename für Datenträger" -msgid "Set project name" -msgstr "Projektname festlegen" +msgid "Server(s) to delete (name or ID)" +msgstr "Zu löschende(r) Server (Name oder Kennung)" -msgid "Set project description" -msgstr "Projektbeschreibung festlegen" +msgid "Service to delete (name or ID)" +msgstr "Zu löschender Dienst (Name oder Kennung)" -msgid "Enable project" -msgstr "Projekt aktivieren" +msgid "Service to display (type or name)" +msgstr "Anzuzeigender Dienst (Typ oder Name)" + +msgid "Service to display (type, name or ID)" +msgstr "Anzuzeigender Dienst (Typ, Name oder Kennung)" msgid "Set a project property (repeat option to set multiple properties)" msgstr "" "Legen Sie eine Projekteigenschaft fest (wiederholen Sie die Option, um " "mehrere Eigenschaften festzulegen)" -msgid "Project to display (name or ID)" -msgstr "Anzuzeigendes Projekt (Name oder Kennung)" +msgid "Set a property on this server (repeat for multiple values)" +msgstr "" +"Legen Sie eine Eigenschaft auf diesem Server fest (für mehrere Werte " +"wiederholen)" -msgid "Role to add to : (name or ID)" -msgstr "Zu : hinzuzufügende Rolle (Name oder Kennung)" +msgid "" +"Set a scope, such as a project or domain, with --os-project-name, " +"OS_PROJECT_NAME or auth.project_name" +msgstr "" +"Legen Sie einen Gültigkeitsbereich, wie beispielsweise ein Projekt oder eine " +"Domäne, mit OS_PROJECT_NAME oder auth.project_name fest" -msgid "Include (name or ID)" -msgstr " miteinbeziehen (Name oder Kennung)" +msgid "" +"Set a service AUTH_URL, with --os-auth-url, OS_AUTH_URL or auth.auth_url\n" +msgstr "" +"Legen Sie eine Dienst-AUTH_URL mit --os-auth-url, OS_AUTH_URL oder auth." +"auth_url fest\n" -msgid "Include (name or ID)" -msgstr " miteinbeziehen (Name oder Kennung)" +msgid "Set a service URL, with --os-url, OS_URL or auth.url\n" +msgstr "Legen Sie eine Dienst-URL mit --os-url, OS_URL oder auth.url fest\n" -msgid "New role name" -msgstr "Name der neuen Rolle" +msgid "Set a token with --os-token, OS_TOKEN or auth.token\n" +msgstr "Legen Sie einen Token mit --os-token, OS_TOKEN oder auth.token fest\n" -msgid "Return existing role" -msgstr "Vorhandene Rolle zurückgeben" +msgid "Set a username with --os-username, OS_USERNAME, or auth.username\n" +msgstr "" +"Legen Sie einen Benutzernamen mit --os-username, OS_USERNAME oder auth." +"username fest\n" -msgid "Role(s) to delete (name or ID)" -msgstr "Zu löschende Rolle(n) (Name oder Kennung)" +msgid "" +"Set an authentication URL, with --os-auth-url, OS_AUTH_URL or auth.auth_url\n" +msgstr "" +"Legen Sie eine Authentifizierungs-URL mit --os-auth-url, OS_AUTH_URL oder " +"auth.auth_url fest\n" -msgid "Project must be specified" -msgstr "Projekt muss angegeben werden" +msgid "Set default project (name or ID)" +msgstr "Standardprojekt festlegen (Name oder Kennung)" -msgid "User must be specified" -msgstr "Benutzer muss angegeben werden" +msgid "Set new root password (interactive only)" +msgstr "Neues root-Passwort festlegen (Nur interaktiv)" -msgid "User to list (name or ID)" -msgstr "Aufzulistender Benutzer (Name oder Kennung)" +msgid "Set project description" +msgstr "Projektbeschreibung festlegen" -msgid "Filter users by (name or ID)" -msgstr "Benutzer nach filtern (Name oder Kennung)" +msgid "Set project name" +msgstr "Projektname festlegen" -msgid "Role to remove (name or ID)" -msgstr "Zu entfernende Rolle (Name oder Kennung)" +msgid "Set user email address" +msgstr "E-Mail-Adresse des Benutzers festlegen" -msgid "Role to display (name or ID)" -msgstr "Anzuzeigende Rolle (Name oder Kennung)" +msgid "Set user name" +msgstr "Benutzername festlegen" -msgid "New service type (compute, image, identity, volume, etc)" -msgstr "Neuer Diensttyp (Berechnen, Abbild, Identität, Datenträger, usw.)" +msgid "Set user password" +msgstr "Benutzerpasswort festlegen" -msgid "New service name" -msgstr "Name des neuen Dienstes" +msgid "Show service catalog information" +msgstr "Dienstkataloginformation anzeigen" -msgid "New service description" -msgstr "Beschreibung des neuen Dienstes" +msgid "Specify a user" +msgstr "Geben Sie einen Benutzer an" + +msgid "Specify an alternate project (default: current authenticated project)" +msgstr "" +"Geben Sie ein alternatives Projekt an (Standard: aktuell authentifiziertes " +"Projekt)" + +msgid "Specify an alternate user (default: current authenticated user)" +msgstr "" +"Geben Sie einen alternativen Benutzer an (Standard: aktuell " +"authentifizierter Benutzer)" + +msgid "Target hostname" +msgstr "Zielhostname" msgid "" "The argument --type is deprecated, use service create --name " @@ -507,93 +529,85 @@ msgstr "" "Das Argument --type ist veraltet, verwenden Sie stattdessen service create --" "name type." -msgid "Service to delete (name or ID)" -msgstr "Zu löschender Dienst (Name oder Kennung)" - -msgid "Service to display (type, name or ID)" -msgstr "Anzuzeigender Dienst (Typ, Name oder Kennung)" - -msgid "Show service catalog information" -msgstr "Dienstkataloginformation anzeigen" - -#, python-format -msgid "No service catalog with a type, name or ID of '%s' exists." -msgstr "Kein Dienstkatalog mit Typ, Namen oder Kennung von '%s' ist vorhanden." - msgid "Token to be deleted" msgstr "Zu löschender Token" -msgid "New user name" -msgstr "Neuer Benutzername" - -msgid "Default project (name or ID)" -msgstr "Standardprojekt (Name oder Kennung)" - -msgid "Set user password" -msgstr "Benutzerpasswort festlegen" +msgid "Use only IPv4 addresses" +msgstr "Nur IPv4-Adressen verwenden" -msgid "Prompt interactively for password" -msgstr "Interaktiv nach dem Passwort abfragen" +msgid "Use only IPv6 addresses" +msgstr "Nur IPv6-Adressen verwenden" -msgid "Set user email address" -msgstr "E-Mail-Adresse des Benutzers festlegen" +msgid "Use other IP address (public, private, etc)" +msgstr "Andere IP-Adresse verwenden (öffentlich, privat, usw.)" -msgid "Enable user (default)" -msgstr "Benutzer aktivieren (Standardeinstellung)" +msgid "Use private IP address" +msgstr "Private IP-Adresse verwenden" -msgid "Disable user" -msgstr "Benutzer deaktivieren" +msgid "Use public IP address" +msgstr "Öffentliche IP-Adresse verwenden" -msgid "Return existing user" -msgstr "Vorhandenen Benutzer zurückgeben" +msgid "" +"Use specified volume as the config drive, or 'True' to use an ephemeral drive" +msgstr "" +"Benutzerdefinierter Datenträger als Konfigurationslaufwerk oder 'True', um " +"ein flüchtiges Laufwerk zu verwenden" -msgid "User(s) to delete (name or ID)" -msgstr "Zu löschende(r) Benutzer (Name oder Kennung)" +msgid "User data file to serve from the metadata server" +msgstr "Vom Metadatenserver anzubietende Benutzerdatendatei" -msgid "Filter users by project (name or ID)" -msgstr "Benutzer nach Projekt filtern (Name oder Kennung)" +msgid "User must be specified" +msgstr "Benutzer muss angegeben werden" msgid "User to change (name or ID)" msgstr "Zu ändernder Benutzer (Name oder Kennung)" -msgid "Set user name" -msgstr "Benutzername festlegen" - -msgid "Set default project (name or ID)" -msgstr "Standardprojekt festlegen (Name oder Kennung)" - msgid "User to display (name or ID)" msgstr "Anzuzeigender Benutzer (Name oder Kennung)" -msgid "Return existing domain" -msgstr "Vorhandene Domäne zurückgeben" +msgid "User to list (name or ID)" +msgstr "Aufzulistender Benutzer (Name oder Kennung)" -msgid "Return existing group" -msgstr "Vorhandene Gruppe zurückgeben" +msgid "User(s) to delete (name or ID)" +msgstr "Zu löschende(r) Benutzer (Name oder Kennung)" -msgid "New region ID" -msgstr "Neue Regionskennung" +msgid "Volume to add (name or ID)" +msgstr "Zu hinzufügender Datenträger (Name oder Kennung)" -msgid "Parent region ID" -msgstr "Übergeordnete Regionskennung" +msgid "Volume to remove (name or ID)" +msgstr "Zu entfernender Datenträger (Name oder Kennung)" -msgid "New region description" -msgstr "Beschreibung des neuen Bereichs" +msgid "Wait for build to complete" +msgstr "Warten Sie, bis die Herstellung abgeschlossen ist" -msgid "New region url" -msgstr "URL des neuen Bereichs" +msgid "Wait for delete to complete" +msgstr "Warten Sie, bis das Löschen abgeschlossen ist" -msgid "Region ID to delete" -msgstr "Zu löschende Regionskennung" +msgid "Wait for image create to complete" +msgstr "Warten Sie, bis die Abbilderstellung abgeschlossen ist" -msgid "Filter by parent region ID" -msgstr "Nach übergeordneter Regionskennung filtern" +msgid "Wait for reboot to complete" +msgstr "Warten Sie, bis der Neustart abgeschlossen ist" -msgid "Region to modify" -msgstr "Zu ändernde Region" +msgid "Wait for rebuild to complete" +msgstr "Warten Sie, bis die Wiederherstellung abgeschlossen ist" -msgid "New parent region ID" -msgstr "Neue übergeordnete Regionskennung" +msgid "Wait for resize to complete" +msgstr "Warten Sie, bis die Größenänderung abgeschlossen ist" -msgid "Region to display" -msgstr "Anzuzeigende Region" +msgid "can't create server with port specified since neutron not enabled" +msgstr "" +"Server mit dem angegebenen Port kann nicht erstellt werden, da Neutron nicht " +"aktiviert ist" + +msgid "either net-id or port-id should be specified but not both" +msgstr "entweder net-id oder port-id sollten angegeben sein, aber nicht beide" + +msgid "max instances should be > 0" +msgstr "max. Instanzen sollten > 0 sein" + +msgid "min instances should be <= max instances" +msgstr "min. Instanzen sollten <= max. Instanzen sein" + +msgid "min instances should be > 0" +msgstr "min. Instanzen sollten > 0 sein" diff --git a/python-openstackclient/locale/python-openstackclient.pot b/python-openstackclient/locale/python-openstackclient.pot index a7b64d5747..5129fbf0fa 100644 --- a/python-openstackclient/locale/python-openstackclient.pot +++ b/python-openstackclient/locale/python-openstackclient.pot @@ -7,9 +7,9 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: python-openstackclient 1.0.3.post49\n" +"Project-Id-Version: python-openstackclient 1.3.1.dev26\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-04-20 06:04+0000\n" +"POT-Creation-Date: 2015-06-03 06:05+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -28,26 +28,26 @@ msgid "" "auth.auth_url\n" msgstr "" -#: openstackclient/api/auth.py:153 +#: openstackclient/api/auth.py:155 msgid "" "Set a scope, such as a project or domain, with --os-project-name, " "OS_PROJECT_NAME or auth.project_name" msgstr "" -#: openstackclient/api/auth.py:157 openstackclient/api/auth.py:163 +#: openstackclient/api/auth.py:159 openstackclient/api/auth.py:165 msgid "Set a token with --os-token, OS_TOKEN or auth.token\n" msgstr "" -#: openstackclient/api/auth.py:159 +#: openstackclient/api/auth.py:161 msgid "Set a service AUTH_URL, with --os-auth-url, OS_AUTH_URL or auth.auth_url\n" msgstr "" -#: openstackclient/api/auth.py:165 +#: openstackclient/api/auth.py:167 msgid "Set a service URL, with --os-url, OS_URL or auth.url\n" msgstr "" #: openstackclient/compute/v2/availability_zone.py:72 -#: openstackclient/compute/v2/server.py:650 +#: openstackclient/compute/v2/server.py:667 #: openstackclient/identity/v2_0/endpoint.py:114 #: openstackclient/identity/v2_0/project.py:145 #: openstackclient/identity/v2_0/service.py:128 @@ -58,23 +58,23 @@ msgstr "" #: openstackclient/compute/v2/server.py:121 #: openstackclient/compute/v2/server.py:158 #: openstackclient/compute/v2/server.py:509 -#: openstackclient/compute/v2/server.py:721 -#: openstackclient/compute/v2/server.py:755 -#: openstackclient/compute/v2/server.py:838 -#: openstackclient/compute/v2/server.py:862 -#: openstackclient/compute/v2/server.py:917 -#: openstackclient/compute/v2/server.py:1009 -#: openstackclient/compute/v2/server.py:1049 -#: openstackclient/compute/v2/server.py:1075 -#: openstackclient/compute/v2/server.py:1140 -#: openstackclient/compute/v2/server.py:1164 -#: openstackclient/compute/v2/server.py:1223 -#: openstackclient/compute/v2/server.py:1260 -#: openstackclient/compute/v2/server.py:1418 -#: openstackclient/compute/v2/server.py:1442 -#: openstackclient/compute/v2/server.py:1466 -#: openstackclient/compute/v2/server.py:1490 -#: openstackclient/compute/v2/server.py:1514 +#: openstackclient/compute/v2/server.py:738 +#: openstackclient/compute/v2/server.py:772 +#: openstackclient/compute/v2/server.py:855 +#: openstackclient/compute/v2/server.py:879 +#: openstackclient/compute/v2/server.py:934 +#: openstackclient/compute/v2/server.py:1026 +#: openstackclient/compute/v2/server.py:1066 +#: openstackclient/compute/v2/server.py:1092 +#: openstackclient/compute/v2/server.py:1157 +#: openstackclient/compute/v2/server.py:1181 +#: openstackclient/compute/v2/server.py:1240 +#: openstackclient/compute/v2/server.py:1277 +#: openstackclient/compute/v2/server.py:1435 +#: openstackclient/compute/v2/server.py:1459 +#: openstackclient/compute/v2/server.py:1483 +#: openstackclient/compute/v2/server.py:1507 +#: openstackclient/compute/v2/server.py:1531 msgid "Server (name or ID)" msgstr "" @@ -91,7 +91,7 @@ msgid "Server internal device name for volume" msgstr "" #: openstackclient/compute/v2/server.py:208 -#: openstackclient/compute/v2/server.py:1169 +#: openstackclient/compute/v2/server.py:1186 msgid "New server name" msgstr "" @@ -222,223 +222,238 @@ msgstr "" msgid "Server(s) to delete (name or ID)" msgstr "" +#: openstackclient/compute/v2/server.py:578 +msgid "Wait for delete to complete" +msgstr "" + #: openstackclient/compute/v2/server.py:597 +#, python-format +msgid "Error deleting server: %s" +msgstr "" + +#: openstackclient/compute/v2/server.py:599 +msgid "" +"\n" +"Error deleting server" +msgstr "" + +#: openstackclient/compute/v2/server.py:614 msgid "Only return instances that match the reservation" msgstr "" -#: openstackclient/compute/v2/server.py:602 +#: openstackclient/compute/v2/server.py:619 msgid "Regular expression to match IP addresses" msgstr "" -#: openstackclient/compute/v2/server.py:607 +#: openstackclient/compute/v2/server.py:624 msgid "Regular expression to match IPv6 addresses" msgstr "" -#: openstackclient/compute/v2/server.py:612 +#: openstackclient/compute/v2/server.py:629 msgid "Regular expression to match names" msgstr "" -#: openstackclient/compute/v2/server.py:617 +#: openstackclient/compute/v2/server.py:634 msgid "Regular expression to match instance name (admin only)" msgstr "" -#: openstackclient/compute/v2/server.py:623 +#: openstackclient/compute/v2/server.py:640 msgid "Search by server status" msgstr "" -#: openstackclient/compute/v2/server.py:628 +#: openstackclient/compute/v2/server.py:645 msgid "Search by flavor" msgstr "" -#: openstackclient/compute/v2/server.py:633 +#: openstackclient/compute/v2/server.py:650 msgid "Search by image" msgstr "" -#: openstackclient/compute/v2/server.py:638 +#: openstackclient/compute/v2/server.py:655 msgid "Search by hostname" msgstr "" -#: openstackclient/compute/v2/server.py:644 +#: openstackclient/compute/v2/server.py:661 msgid "Include all projects (admin only)" msgstr "" -#: openstackclient/compute/v2/server.py:760 +#: openstackclient/compute/v2/server.py:777 msgid "Target hostname" msgstr "" -#: openstackclient/compute/v2/server.py:768 +#: openstackclient/compute/v2/server.py:785 msgid "Perform a shared live migration (default)" msgstr "" -#: openstackclient/compute/v2/server.py:774 +#: openstackclient/compute/v2/server.py:791 msgid "Perform a block live migration" msgstr "" -#: openstackclient/compute/v2/server.py:781 +#: openstackclient/compute/v2/server.py:798 msgid "Allow disk over-commit on the destination host" msgstr "" -#: openstackclient/compute/v2/server.py:788 +#: openstackclient/compute/v2/server.py:805 msgid "Do not over-commit disk on the destination host (default)" msgstr "" -#: openstackclient/compute/v2/server.py:794 -#: openstackclient/compute/v2/server.py:1095 +#: openstackclient/compute/v2/server.py:811 +#: openstackclient/compute/v2/server.py:1112 msgid "Wait for resize to complete" msgstr "" -#: openstackclient/compute/v2/server.py:822 -#: openstackclient/compute/v2/server.py:1120 +#: openstackclient/compute/v2/server.py:839 +#: openstackclient/compute/v2/server.py:1137 msgid "Complete\n" msgstr "" -#: openstackclient/compute/v2/server.py:824 +#: openstackclient/compute/v2/server.py:841 msgid "" "\n" "Error migrating server" msgstr "" -#: openstackclient/compute/v2/server.py:871 +#: openstackclient/compute/v2/server.py:888 msgid "Perform a hard reboot" msgstr "" -#: openstackclient/compute/v2/server.py:879 +#: openstackclient/compute/v2/server.py:896 msgid "Perform a soft reboot" msgstr "" -#: openstackclient/compute/v2/server.py:884 +#: openstackclient/compute/v2/server.py:901 msgid "Wait for reboot to complete" msgstr "" -#: openstackclient/compute/v2/server.py:901 +#: openstackclient/compute/v2/server.py:918 msgid "" "\n" "Reboot complete\n" msgstr "" -#: openstackclient/compute/v2/server.py:903 +#: openstackclient/compute/v2/server.py:920 msgid "" "\n" "Error rebooting server\n" msgstr "" -#: openstackclient/compute/v2/server.py:923 +#: openstackclient/compute/v2/server.py:940 msgid "Recreate server from this image" msgstr "" -#: openstackclient/compute/v2/server.py:933 +#: openstackclient/compute/v2/server.py:950 msgid "Wait for rebuild to complete" msgstr "" -#: openstackclient/compute/v2/server.py:954 +#: openstackclient/compute/v2/server.py:971 msgid "" "\n" "Complete\n" msgstr "" -#: openstackclient/compute/v2/server.py:956 +#: openstackclient/compute/v2/server.py:973 msgid "" "\n" "Error rebuilding server" msgstr "" -#: openstackclient/compute/v2/server.py:973 +#: openstackclient/compute/v2/server.py:990 msgid "Name or ID of server to use" msgstr "" -#: openstackclient/compute/v2/server.py:978 +#: openstackclient/compute/v2/server.py:995 msgid "Name or ID of security group to remove from server" msgstr "" -#: openstackclient/compute/v2/server.py:1014 +#: openstackclient/compute/v2/server.py:1031 msgid "Volume to remove (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:1080 +#: openstackclient/compute/v2/server.py:1097 msgid "Resize server to specified flavor" msgstr "" -#: openstackclient/compute/v2/server.py:1085 +#: openstackclient/compute/v2/server.py:1102 msgid "Confirm server resize is complete" msgstr "" -#: openstackclient/compute/v2/server.py:1090 +#: openstackclient/compute/v2/server.py:1107 msgid "Restore server state before resize" msgstr "" -#: openstackclient/compute/v2/server.py:1122 +#: openstackclient/compute/v2/server.py:1139 msgid "" "\n" "Error resizing server" msgstr "" -#: openstackclient/compute/v2/server.py:1174 +#: openstackclient/compute/v2/server.py:1191 msgid "Set new root password (interactive only)" msgstr "" -#: openstackclient/compute/v2/server.py:1180 +#: openstackclient/compute/v2/server.py:1197 msgid "" "Property to add/change for this server (repeat option to set multiple " "properties)" msgstr "" -#: openstackclient/compute/v2/server.py:1204 +#: openstackclient/compute/v2/server.py:1221 msgid "New password: " msgstr "" -#: openstackclient/compute/v2/server.py:1205 +#: openstackclient/compute/v2/server.py:1222 msgid "Retype new password: " msgstr "" -#: openstackclient/compute/v2/server.py:1209 +#: openstackclient/compute/v2/server.py:1226 msgid "Passwords do not match, password unchanged" msgstr "" -#: openstackclient/compute/v2/server.py:1229 +#: openstackclient/compute/v2/server.py:1246 msgid "Display server diagnostics information" msgstr "" -#: openstackclient/compute/v2/server.py:1242 +#: openstackclient/compute/v2/server.py:1259 msgid "Error retrieving diagnostics data" msgstr "" -#: openstackclient/compute/v2/server.py:1265 +#: openstackclient/compute/v2/server.py:1282 msgid "Login name (ssh -l option)" msgstr "" -#: openstackclient/compute/v2/server.py:1276 +#: openstackclient/compute/v2/server.py:1293 msgid "Destination port (ssh -p option)" msgstr "" -#: openstackclient/compute/v2/server.py:1288 +#: openstackclient/compute/v2/server.py:1305 msgid "Private key file (ssh -i option)" msgstr "" -#: openstackclient/compute/v2/server.py:1299 +#: openstackclient/compute/v2/server.py:1316 msgid "Options in ssh_config(5) format (ssh -o option)" msgstr "" -#: openstackclient/compute/v2/server.py:1313 +#: openstackclient/compute/v2/server.py:1330 msgid "Use only IPv4 addresses" msgstr "" -#: openstackclient/compute/v2/server.py:1320 +#: openstackclient/compute/v2/server.py:1337 msgid "Use only IPv6 addresses" msgstr "" -#: openstackclient/compute/v2/server.py:1329 +#: openstackclient/compute/v2/server.py:1346 msgid "Use public IP address" msgstr "" -#: openstackclient/compute/v2/server.py:1337 +#: openstackclient/compute/v2/server.py:1354 msgid "Use private IP address" msgstr "" -#: openstackclient/compute/v2/server.py:1344 +#: openstackclient/compute/v2/server.py:1361 msgid "Use other IP address (public, private, etc)" msgstr "" -#: openstackclient/compute/v2/server.py:1521 +#: openstackclient/compute/v2/server.py:1538 msgid "Property key to remove from server (repeat to unset multiple values)" msgstr "" @@ -516,7 +531,7 @@ msgid "Add a property to (repeat option to set multiple properties)" msgstr "" #: openstackclient/identity/v2_0/project.py:69 -#: openstackclient/identity/v3/project.py:75 +#: openstackclient/identity/v3/project.py:80 msgid "Return existing project" msgstr "" @@ -567,7 +582,7 @@ msgid "New role name" msgstr "" #: openstackclient/identity/v2_0/role.py:92 -#: openstackclient/identity/v3/role.py:158 +#: openstackclient/identity/v3/role.py:188 msgid "Return existing role" msgstr "" diff --git a/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po b/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po index 978471a5b4..3954df9ac4 100644 --- a/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po +++ b/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: python-openstackclient\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-05-05 06:06+0000\n" -"PO-Revision-Date: 2015-04-19 14:23+0000\n" +"POT-Creation-Date: 2015-06-03 06:05+0000\n" +"PO-Revision-Date: 2015-06-01 20:40+0000\n" "Last-Translator: openstackjenkins \n" "Language-Team: Chinese (Taiwan) (http://www.transifex.com/projects/p/python-" "openstackclient/language/zh_TW/)\n" @@ -19,23 +19,73 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 1.3\n" -msgid "List additional fields in output" -msgstr "列出額外的欄位" +msgid "" +"\n" +"Complete\n" +msgstr "" +"\n" +"完成\n" -msgid "Server (name or ID)" -msgstr "伺服器(名稱或識別號)" +msgid "" +"\n" +"Error creating server" +msgstr "" +"\n" +"新增雲實例時出錯" -msgid "Security group to add (name or ID)" -msgstr "要加入的安全性群組(名稱或識別號)" +msgid "" +"\n" +"Error creating server snapshot" +msgstr "" +"\n" +"新增雲實例即時存檔時出錯" -msgid "Volume to add (name or ID)" -msgstr "要加入的雲硬碟(名稱或識別號)" +msgid "" +"\n" +"Error migrating server" +msgstr "" +"\n" +"轉移雲實例出錯" -msgid "Server internal device name for volume" -msgstr "雲硬碟在雲實例內的裝置名稱" +msgid "" +"\n" +"Error rebooting server\n" +msgstr "" +"\n" +"重開雲實例出錯\n" -msgid "New server name" -msgstr "新雲實例名稱" +msgid "" +"\n" +"Error rebuilding server" +msgstr "" +"\n" +"重建雲實例出錯" + +msgid "" +"\n" +"Error resizing server" +msgstr "" +"\n" +"調整雲實例容量時出錯" + +msgid "" +"\n" +"Reboot complete\n" +msgstr "" +"\n" +"重開機完成\n" + +msgid "Add a property to (repeat option to set multiple properties)" +msgstr "加入屬性到 (重復這選項來設定多個屬性)" + +msgid "Allow disk over-commit on the destination host" +msgstr "允許目標主機過量使用" + +msgid "Complete\n" +msgstr "完成\n" + +msgid "Confirm server resize is complete" +msgstr "確認調整雲實例容量完成" msgid "Create server from this image" msgstr "從此映像檔新增雲實例" @@ -46,390 +96,355 @@ msgstr "從此雲硬碟新增雲實例" msgid "Create server with this flavor" msgstr "以這個虛擬硬體樣板新增雲實例" -msgid "Security group to assign to this server (repeat for multiple groups)" -msgstr "要指定到此雲實例的安全性群組(為多個群組重復指定)" +msgid "Credentials access key" +msgstr "憑鑰存取密鑰" -msgid "Keypair to inject into this server (optional extension)" -msgstr "要注入到此伺服器的密鑰對(選用)" +msgid "Default project (name or ID)" +msgstr "預設專案(名稱或識別號)" -msgid "Set a property on this server (repeat for multiple values)" -msgstr "為此伺服器設定屬性(為多個值重複設定)" +msgid "Destination port (ssh -p option)" +msgstr "目標埠口(ssh -p 選項)" -msgid "File to inject into image before boot (repeat for multiple files)" -msgstr "在開機前要注入映像檔的檔案(為多個檔案重復指定)" +msgid "Disable project" +msgstr "關閉專案" -msgid "User data file to serve from the metadata server" -msgstr "來自詮釋資料伺服器要服務的用戶資料檔案" +msgid "Disable user" +msgstr "關閉用戶" -msgid "Select an availability zone for the server" -msgstr "為雲實例選擇可用的區域。" +msgid "Display server diagnostics information" +msgstr "顯示雲實例診斷資訊" -msgid "" -"Map block devices; map is ::: " -"(optional extension)" -msgstr "" -"映射區塊裝置;映射是 :::(選用)" +msgid "Do not over-commit disk on the destination host (default)" +msgstr "不準目標主機過量使用(預設)" -msgid "Hints for the scheduler (optional extension)" -msgstr "給排程器的提示(選用)" +msgid "Enable project" +msgstr "啟用專案" -msgid "" -"Use specified volume as the config drive, or 'True' to use an ephemeral drive" -msgstr "使用指定的雲硬碟為設定檔硬碟,或「True」來使用暫時性硬碟" +msgid "Enable project (default)" +msgstr "啟用專案(預設)" -msgid "Minimum number of servers to launch (default=1)" -msgstr "最少要發動的雲實例(預設為 1)" +msgid "Enable user (default)" +msgstr "啟用用戶(預設)" -msgid "Maximum number of servers to launch (default=1)" -msgstr "最多要發動的雲實例(預設為 1)" +msgid "Endpoint ID to delete" +msgstr "要刪除的端點識別號" -msgid "Wait for build to complete" -msgstr "等待完成建立" +msgid "Endpoint ID to display" +msgstr "要顯示的端點識別號" -msgid "min instances should be <= max instances" -msgstr "雲實例發動的最少值不應大於最大值" +#, python-format +msgid "Error creating server snapshot: %s" +msgstr "新增雲實例即時存檔時出錯:%s" -msgid "min instances should be > 0" -msgstr "雲實例發動的最少值要大於 0" +#, python-format +msgid "Error creating server: %s" +msgstr "新增雲實例時出錯:%s" -msgid "max instances should be > 0" -msgstr "雲實例發動的最大值要大於 0" +msgid "Error retrieving diagnostics data" +msgstr "獲得診斷資料時出錯" -msgid "either net-id or port-id should be specified but not both" -msgstr "任選網路識別號或接口識別號,但不能兩者都指定" +msgid "File to inject into image before boot (repeat for multiple files)" +msgstr "在開機前要注入映像檔的檔案(為多個檔案重復指定)" -msgid "can't create server with port specified since neutron not enabled" -msgstr "Neutron 未啟用時,不能以指定的接口來新增雲實例" +msgid "Filter by parent region ID" +msgstr "以父地區識別號來篩選" -#, python-format -msgid "Error creating server: %s" -msgstr "新增雲實例時出錯:%s" +msgid "Filter users by (name or ID)" +msgstr "以 來篩選用戶(名稱或識別號)" -msgid "" -"\n" -"Error creating server" -msgstr "" -"\n" -"新增雲實例時出錯" +msgid "Filter users by project (name or ID)" +msgstr "以專案篩選用戶(名稱或識別號)" -msgid "Name of new image (default is server name)" -msgstr "新映像檔的名稱(預設為雲實例名稱)" +msgid "Hints for the scheduler (optional extension)" +msgstr "給排程器的提示(選用)" -msgid "Wait for image create to complete" -msgstr "等待映像檔新增完成" +msgid "Include (name or ID)" +msgstr "包括 (名稱或識別號)" -#, python-format -msgid "Error creating server snapshot: %s" -msgstr "新增雲實例即時存檔時出錯:%s" +msgid "Include (name or ID)" +msgstr "包括 (名稱或識別號)" + +msgid "Include all projects (admin only)" +msgstr "包括所有的專案(管理員專用)" + +msgid "Keypair to inject into this server (optional extension)" +msgstr "要注入到此伺服器的密鑰對(選用)" + +msgid "List additional fields in output" +msgstr "列出額外的欄位" + +msgid "Login name (ssh -l option)" +msgstr "登入名稱(ssh -l 選項)" msgid "" -"\n" -"Error creating server snapshot" +"Map block devices; map is ::: " +"(optional extension)" msgstr "" -"\n" -"新增雲實例即時存檔時出錯" +"映射區塊裝置;映射是 :::(選用)" -msgid "Server(s) to delete (name or ID)" -msgstr "要刪除的雲實例(名稱或識別號)" +msgid "Maximum number of servers to launch (default=1)" +msgstr "最多要發動的雲實例(預設為 1)" -msgid "Only return instances that match the reservation" -msgstr "只回傳要保留的雲實例" +msgid "Minimum number of servers to launch (default=1)" +msgstr "最少要發動的雲實例(預設為 1)" -msgid "Regular expression to match IP addresses" -msgstr "以正規式來匹配 IP 位址" +msgid "Name of new image (default is server name)" +msgstr "新映像檔的名稱(預設為雲實例名稱)" -msgid "Regular expression to match IPv6 addresses" -msgstr "以正規式來匹配 IPv6 位址" +msgid "Name or ID of security group to remove from server" +msgstr "要從雲實例移除的安全性群組名稱或識別號" -msgid "Regular expression to match names" -msgstr "以正規式來匹配名稱" +msgid "Name or ID of server to use" +msgstr "要用的雲實例名稱或識別號" -msgid "Regular expression to match instance name (admin only)" -msgstr "以正規式來匹配雲實例名稱(管理員專用)" +msgid "New endpoint admin URL" +msgstr "新端點管理員網址" -msgid "Search by server status" -msgstr "以雲實例狀態來尋找" +msgid "New endpoint internal URL" +msgstr "新端點內部網址" -msgid "Search by flavor" -msgstr "以虛擬硬體樣板來尋找" +msgid "New endpoint public URL (required)" +msgstr "新端點公開網址(需要)" -msgid "Search by image" -msgstr "以映像檔來尋找" +msgid "New endpoint region ID" +msgstr "新端點地區識別號" -msgid "Search by hostname" -msgstr "以主機名稱來尋找" +msgid "New endpoint service (name or ID)" +msgstr "新端點伺服器(名稱或識別號)" -msgid "Include all projects (admin only)" -msgstr "包括所有的專案(管理員專用)" +msgid "New parent region ID" +msgstr "新父地區識別號" -msgid "Target hostname" -msgstr "目標主機名稱" +msgid "New password: " +msgstr "新密碼:" -msgid "Perform a shared live migration (default)" -msgstr "覆行已分享的即時轉移(預設)" +msgid "New project name" +msgstr "新專案名稱" -msgid "Perform a block live migration" -msgstr "覆行區塊的即時轉移" +msgid "New region ID" +msgstr "新地區識別號" -msgid "Allow disk over-commit on the destination host" -msgstr "允許目標主機過量使用" +msgid "New region description" +msgstr "新地區描述" -msgid "Do not over-commit disk on the destination host (default)" -msgstr "不準目標主機過量使用(預設)" +msgid "New region url" +msgstr "新地區網址" -msgid "Wait for resize to complete" -msgstr "等待調整容量完成" +msgid "New role name" +msgstr "新角色名稱" -msgid "Complete\n" -msgstr "完成\n" +msgid "New server name" +msgstr "新雲實例名稱" -msgid "" -"\n" -"Error migrating server" -msgstr "" -"\n" -"轉移雲實例出錯" +msgid "New service description" +msgstr "新伺服器描述" -msgid "Perform a hard reboot" -msgstr "履行強制重開機" +msgid "New service name" +msgstr "新伺服器名稱" -msgid "Perform a soft reboot" -msgstr "履行重開機" +msgid "New service type (compute, image, identity, volume, etc)" +msgstr "新伺服器類型(compute、image、identity 或 volume 等等)" -msgid "Wait for reboot to complete" -msgstr "等待重開機完成" +msgid "New user name" +msgstr "新用戶名稱" -msgid "" -"\n" -"Reboot complete\n" -msgstr "" -"\n" -"重開機完成\n" +#, python-format +msgid "No service catalog with a type, name or ID of '%s' exists." +msgstr "沒有相似「%s」類型、名稱或識別號的伺服器分類。" -msgid "" -"\n" -"Error rebooting server\n" -msgstr "" -"\n" -"重開雲實例出錯\n" +msgid "Only return instances that match the reservation" +msgstr "只回傳要保留的雲實例" -msgid "Recreate server from this image" -msgstr "從此映像檔重建雲實例" +msgid "Options in ssh_config(5) format (ssh -o option)" +msgstr "ssh_config(5) 格式的選項(ssh -o 選項)" -msgid "Wait for rebuild to complete" -msgstr "等待重建完成" +msgid "Parent region ID" +msgstr "父地區識別號" -msgid "" -"\n" -"Complete\n" -msgstr "" -"\n" -"完成\n" +msgid "Passwords do not match, password unchanged" +msgstr "密碼不一樣,未更換密碼" -msgid "" -"\n" -"Error rebuilding server" -msgstr "" -"\n" -"重建雲實例出錯" +msgid "Perform a block live migration" +msgstr "覆行區塊的即時轉移" -msgid "Name or ID of server to use" -msgstr "要用的雲實例名稱或識別號" +msgid "Perform a hard reboot" +msgstr "履行強制重開機" -msgid "Name or ID of security group to remove from server" -msgstr "要從雲實例移除的安全性群組名稱或識別號" +msgid "Perform a shared live migration (default)" +msgstr "覆行已分享的即時轉移(預設)" -msgid "Volume to remove (name or ID)" -msgstr "要移除的雲硬碟(名稱或識別號)" +msgid "Perform a soft reboot" +msgstr "履行重開機" -msgid "Resize server to specified flavor" -msgstr "調整雲實例容量來符合指定的虛擬硬體樣板" +msgid "Private key file (ssh -i option)" +msgstr "私鑰檔案(ssh -i 選項)" -msgid "Confirm server resize is complete" -msgstr "確認調整雲實例容量完成" +msgid "Project description" +msgstr "專案描述" -msgid "Restore server state before resize" -msgstr "恢復雲實例狀態到未調整容量前" +msgid "Project must be specified" +msgstr "必須指定專案" -msgid "" -"\n" -"Error resizing server" -msgstr "" -"\n" -"調整雲實例容量時出錯" +msgid "Project to display (name or ID)" +msgstr "要顯示的專案(名稱或識別號)" -msgid "Set new root password (interactive only)" -msgstr "設定新密碼(只限互動)" +msgid "Project to modify (name or ID)" +msgstr "要更改的專案(名稱或識別號)" + +msgid "Project(s) to delete (name or ID)" +msgstr "要刪除的專案(名稱或識別號)" + +msgid "Prompt interactively for password" +msgstr "為密碼互動提示" + +msgid "Property key to remove from server (repeat to unset multiple values)" +msgstr "要從雲實例上移除的屬性鍵(重復來取消選擇多個值)" msgid "" "Property to add/change for this server (repeat option to set multiple " "properties)" msgstr "要加入這個雲實例的屬性(重復這選項來設定多個屬性)" -msgid "New password: " -msgstr "新密碼:" - -msgid "Retype new password: " -msgstr "重新輸入新密碼:" - -msgid "Passwords do not match, password unchanged" -msgstr "密碼不一樣,未更換密碼" - -msgid "Display server diagnostics information" -msgstr "顯示雲實例診斷資訊" - -msgid "Error retrieving diagnostics data" -msgstr "獲得診斷資料時出錯" - -msgid "Login name (ssh -l option)" -msgstr "登入名稱(ssh -l 選項)" +msgid "Recreate server from this image" +msgstr "從此映像檔重建雲實例" -msgid "Destination port (ssh -p option)" -msgstr "目標埠口(ssh -p 選項)" +msgid "Region ID to delete" +msgstr "要刪除的地區識別號" -msgid "Private key file (ssh -i option)" -msgstr "私鑰檔案(ssh -i 選項)" +msgid "Region to display" +msgstr "要顯示的地區" -msgid "Options in ssh_config(5) format (ssh -o option)" -msgstr "ssh_config(5) 格式的選項(ssh -o 選項)" +msgid "Region to modify" +msgstr "要更改的地區" -msgid "Use only IPv4 addresses" -msgstr "只使用 IPv4 位址" +msgid "Regular expression to match IP addresses" +msgstr "以正規式來匹配 IP 位址" -msgid "Use only IPv6 addresses" -msgstr "只使用 IPv6 位址" +msgid "Regular expression to match IPv6 addresses" +msgstr "以正規式來匹配 IPv6 位址" -msgid "Use public IP address" -msgstr "使用公開 IP 位址" +msgid "Regular expression to match instance name (admin only)" +msgstr "以正規式來匹配雲實例名稱(管理員專用)" -msgid "Use private IP address" -msgstr "使用私人 IP 位址" +msgid "Regular expression to match names" +msgstr "以正規式來匹配名稱" -msgid "Use other IP address (public, private, etc)" -msgstr "使用其他 IP 位址(公開、私人等等)" +msgid "Resize server to specified flavor" +msgstr "調整雲實例容量來符合指定的虛擬硬體樣板" -msgid "Property key to remove from server (repeat to unset multiple values)" -msgstr "要從雲實例上移除的屬性鍵(重復來取消選擇多個值)" +msgid "Restore server state before resize" +msgstr "恢復雲實例狀態到未調整容量前" -msgid "Service to display (type or name)" -msgstr "要顯示的伺服器(類型或名稱)" +msgid "Return existing domain" +msgstr "回傳存在的地域" -msgid "Specify an alternate project (default: current authenticated project)" -msgstr "指定替代的專案(預設值:目前已認證的專案)" +msgid "Return existing group" +msgstr "回傳存在的群組" -msgid "Specify an alternate user (default: current authenticated user)" -msgstr "指定替代的用戶(預設值:目前已認證的用戶)" +msgid "Return existing project" +msgstr "回傳存在的專案" -msgid "Credentials access key" -msgstr "憑鑰存取密鑰" +msgid "Return existing role" +msgstr "回傳存在的角色" -msgid "Specify a user" -msgstr "指定用戶" +msgid "Return existing user" +msgstr "回傳存在的用戶" -msgid "New endpoint service (name or ID)" -msgstr "新端點伺服器(名稱或識別號)" +msgid "Retype new password: " +msgstr "重新輸入新密碼:" -msgid "New endpoint public URL (required)" -msgstr "新端點公開網址(需要)" +msgid "Role to add to : (name or ID)" +msgstr "加入角色到 :(名稱或識別號)" -msgid "New endpoint admin URL" -msgstr "新端點管理員網址" +msgid "Role to display (name or ID)" +msgstr "要顯示的角色(名稱或識別號)" -msgid "New endpoint internal URL" -msgstr "新端點內部網址" +msgid "Role to remove (name or ID)" +msgstr "要移除的角色(名稱或識別號)" -msgid "New endpoint region ID" -msgstr "新端點地區識別號" +msgid "Role(s) to delete (name or ID)" +msgstr "要刪除的角色(名稱或識別號)" -msgid "Endpoint ID to delete" -msgstr "要刪除的端點識別號" +msgid "Search by flavor" +msgstr "以虛擬硬體樣板來尋找" -msgid "Endpoint ID to display" -msgstr "要顯示的端點識別號" +msgid "Search by hostname" +msgstr "以主機名稱來尋找" -msgid "New project name" -msgstr "新專案名稱" +msgid "Search by image" +msgstr "以映像檔來尋找" -msgid "Project description" -msgstr "專案描述" +msgid "Search by server status" +msgstr "以雲實例狀態來尋找" -msgid "Enable project (default)" -msgstr "啟用專案(預設)" +msgid "Security group to add (name or ID)" +msgstr "要加入的安全性群組(名稱或識別號)" -msgid "Disable project" -msgstr "關閉專案" +msgid "Security group to assign to this server (repeat for multiple groups)" +msgstr "要指定到此雲實例的安全性群組(為多個群組重復指定)" -msgid "Add a property to (repeat option to set multiple properties)" -msgstr "加入屬性到 (重復這選項來設定多個屬性)" +msgid "Select an availability zone for the server" +msgstr "為雲實例選擇可用的區域。" -msgid "Return existing project" -msgstr "回傳存在的專案" +msgid "Server (name or ID)" +msgstr "伺服器(名稱或識別號)" -msgid "Project(s) to delete (name or ID)" -msgstr "要刪除的專案(名稱或識別號)" +msgid "Server internal device name for volume" +msgstr "雲硬碟在雲實例內的裝置名稱" -msgid "Project to modify (name or ID)" -msgstr "要更改的專案(名稱或識別號)" +msgid "Server(s) to delete (name or ID)" +msgstr "要刪除的雲實例(名稱或識別號)" -msgid "Set project name" -msgstr "設定專案名稱" +msgid "Service to delete (name or ID)" +msgstr "要刪除的伺服器(名稱或識別號)" -msgid "Set project description" -msgstr "設定專案描述" +msgid "Service to display (type or name)" +msgstr "要顯示的伺服器(類型或名稱)" -msgid "Enable project" -msgstr "啟用專案" +msgid "Service to display (type, name or ID)" +msgstr "要顯示的伺服器(類型、名稱或識別號)" msgid "Set a project property (repeat option to set multiple properties)" msgstr "設定專案屬性(重復這選項來設定多個屬性)" -msgid "Project to display (name or ID)" -msgstr "要顯示的專案(名稱或識別號)" - -msgid "Role to add to : (name or ID)" -msgstr "加入角色到 :(名稱或識別號)" - -msgid "Include (name or ID)" -msgstr "包括 (名稱或識別號)" - -msgid "Include (name or ID)" -msgstr "包括 (名稱或識別號)" +msgid "Set a property on this server (repeat for multiple values)" +msgstr "為此伺服器設定屬性(為多個值重複設定)" -msgid "New role name" -msgstr "新角色名稱" +msgid "Set default project (name or ID)" +msgstr "設定預設專案(名稱或識別號)" -msgid "Return existing role" -msgstr "回傳存在的角色" +msgid "Set new root password (interactive only)" +msgstr "設定新密碼(只限互動)" -msgid "Role(s) to delete (name or ID)" -msgstr "要刪除的角色(名稱或識別號)" +msgid "Set project description" +msgstr "設定專案描述" -msgid "Project must be specified" -msgstr "必須指定專案" +msgid "Set project name" +msgstr "設定專案名稱" -msgid "User must be specified" -msgstr "必須指定用戶" +msgid "Set user email address" +msgstr "設定用戶電子信箱位址" -msgid "User to list (name or ID)" -msgstr "要列出的用戶(名稱或識別號)" +msgid "Set user name" +msgstr "設定用戶名稱" -msgid "Filter users by (name or ID)" -msgstr "以 來篩選用戶(名稱或識別號)" +msgid "Set user password" +msgstr "設定用戶密碼" -msgid "Role to remove (name or ID)" -msgstr "要移除的角色(名稱或識別號)" +msgid "Show service catalog information" +msgstr "顯示伺服器分類資訊" -msgid "Role to display (name or ID)" -msgstr "要顯示的角色(名稱或識別號)" +msgid "Specify a user" +msgstr "指定用戶" -msgid "New service type (compute, image, identity, volume, etc)" -msgstr "新伺服器類型(compute、image、identity 或 volume 等等)" +msgid "Specify an alternate project (default: current authenticated project)" +msgstr "指定替代的專案(預設值:目前已認證的專案)" -msgid "New service name" -msgstr "新伺服器名稱" +msgid "Specify an alternate user (default: current authenticated user)" +msgstr "指定替代的用戶(預設值:目前已認證的用戶)" -msgid "New service description" -msgstr "新伺服器描述" +msgid "Target hostname" +msgstr "目標主機名稱" msgid "" "The argument --type is deprecated, use service create --name " @@ -437,93 +452,78 @@ msgid "" msgstr "" "已經淘汰 --type 參數,請用 service create --name 來代替。" -msgid "Service to delete (name or ID)" -msgstr "要刪除的伺服器(名稱或識別號)" - -msgid "Service to display (type, name or ID)" -msgstr "要顯示的伺服器(類型、名稱或識別號)" - -msgid "Show service catalog information" -msgstr "顯示伺服器分類資訊" - -#, python-format -msgid "No service catalog with a type, name or ID of '%s' exists." -msgstr "沒有相似「%s」類型、名稱或識別號的伺服器分類。" - msgid "Token to be deleted" msgstr "要刪除的記號" -msgid "New user name" -msgstr "新用戶名稱" - -msgid "Default project (name or ID)" -msgstr "預設專案(名稱或識別號)" - -msgid "Set user password" -msgstr "設定用戶密碼" +msgid "Use only IPv4 addresses" +msgstr "只使用 IPv4 位址" -msgid "Prompt interactively for password" -msgstr "為密碼互動提示" +msgid "Use only IPv6 addresses" +msgstr "只使用 IPv6 位址" -msgid "Set user email address" -msgstr "設定用戶電子信箱位址" +msgid "Use other IP address (public, private, etc)" +msgstr "使用其他 IP 位址(公開、私人等等)" -msgid "Enable user (default)" -msgstr "啟用用戶(預設)" +msgid "Use private IP address" +msgstr "使用私人 IP 位址" -msgid "Disable user" -msgstr "關閉用戶" +msgid "Use public IP address" +msgstr "使用公開 IP 位址" -msgid "Return existing user" -msgstr "回傳存在的用戶" +msgid "" +"Use specified volume as the config drive, or 'True' to use an ephemeral drive" +msgstr "使用指定的雲硬碟為設定檔硬碟,或「True」來使用暫時性硬碟" -msgid "User(s) to delete (name or ID)" -msgstr "要刪除的用戶(名稱或識別號)" +msgid "User data file to serve from the metadata server" +msgstr "來自詮釋資料伺服器要服務的用戶資料檔案" -msgid "Filter users by project (name or ID)" -msgstr "以專案篩選用戶(名稱或識別號)" +msgid "User must be specified" +msgstr "必須指定用戶" msgid "User to change (name or ID)" msgstr "要更換的用戶(名稱或識別號)" -msgid "Set user name" -msgstr "設定用戶名稱" - -msgid "Set default project (name or ID)" -msgstr "設定預設專案(名稱或識別號)" - msgid "User to display (name or ID)" msgstr "要顯示的用戶(名稱或識別號)" -msgid "Return existing domain" -msgstr "回傳存在的地域" +msgid "User to list (name or ID)" +msgstr "要列出的用戶(名稱或識別號)" -msgid "Return existing group" -msgstr "回傳存在的群組" +msgid "User(s) to delete (name or ID)" +msgstr "要刪除的用戶(名稱或識別號)" -msgid "New region ID" -msgstr "新地區識別號" +msgid "Volume to add (name or ID)" +msgstr "要加入的雲硬碟(名稱或識別號)" -msgid "Parent region ID" -msgstr "父地區識別號" +msgid "Volume to remove (name or ID)" +msgstr "要移除的雲硬碟(名稱或識別號)" -msgid "New region description" -msgstr "新地區描述" +msgid "Wait for build to complete" +msgstr "等待完成建立" -msgid "New region url" -msgstr "新地區網址" +msgid "Wait for image create to complete" +msgstr "等待映像檔新增完成" -msgid "Region ID to delete" -msgstr "要刪除的地區識別號" +msgid "Wait for reboot to complete" +msgstr "等待重開機完成" -msgid "Filter by parent region ID" -msgstr "以父地區識別號來篩選" +msgid "Wait for rebuild to complete" +msgstr "等待重建完成" -msgid "Region to modify" -msgstr "要更改的地區" +msgid "Wait for resize to complete" +msgstr "等待調整容量完成" -msgid "New parent region ID" -msgstr "新父地區識別號" +msgid "can't create server with port specified since neutron not enabled" +msgstr "Neutron 未啟用時,不能以指定的接口來新增雲實例" -msgid "Region to display" -msgstr "要顯示的地區" +msgid "either net-id or port-id should be specified but not both" +msgstr "任選網路識別號或接口識別號,但不能兩者都指定" + +msgid "max instances should be > 0" +msgstr "雲實例發動的最大值要大於 0" + +msgid "min instances should be <= max instances" +msgstr "雲實例發動的最少值不應大於最大值" + +msgid "min instances should be > 0" +msgstr "雲實例發動的最少值要大於 0" From 15d3717e733aec9e8b6526a1abffd62f2da1e32b Mon Sep 17 00:00:00 2001 From: Jamie Lennox Date: Mon, 1 Jun 2015 17:43:56 +1000 Subject: [PATCH 0092/3095] Add EC2 support for identity v3 API EC2 support is provided for the v2 identity API and is available in almost exactly the same format in the v3 API and enabled by default. Supporting EC2 in the v3 identity API in OSC will make it much easier to transition devstack to a v3 only state. Closes-Bug: 1236326 Change-Id: I52ff2020ef2fcbdc8a98280b73c6fd4a93bc8e0f --- openstackclient/identity/v3/ec2creds.py | 196 ++++++++++++++++++++++++ setup.cfg | 5 + 2 files changed, 201 insertions(+) create mode 100644 openstackclient/identity/v3/ec2creds.py diff --git a/openstackclient/identity/v3/ec2creds.py b/openstackclient/identity/v3/ec2creds.py new file mode 100644 index 0000000000..254cca78a5 --- /dev/null +++ b/openstackclient/identity/v3/ec2creds.py @@ -0,0 +1,196 @@ +# 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. + +"""Identity v3 EC2 Credentials action implementations""" + +import logging +import six + +from cliff import command +from cliff import lister +from cliff import show + +from openstackclient.common import utils +from openstackclient.i18n import _ # noqa + + +class CreateEC2Creds(show.ShowOne): + """Create EC2 credentials""" + + log = logging.getLogger(__name__ + ".CreateEC2Creds") + + def get_parser(self, prog_name): + parser = super(CreateEC2Creds, self).get_parser(prog_name) + parser.add_argument( + '--project', + metavar='', + help=_('Specify an alternate project' + ' (default: current authenticated project)'), + ) + parser.add_argument( + '--user', + metavar='', + help=_('Specify an alternate user' + ' (default: current authenticated user)'), + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + identity_client = self.app.client_manager.identity + + if parsed_args.project: + project = utils.find_resource( + identity_client.projects, + parsed_args.project, + ).id + else: + # Get the project from the current auth + project = self.app.client_manager.auth_ref.project_id + if parsed_args.user: + user = utils.find_resource( + identity_client.users, + parsed_args.user, + ).id + else: + # Get the user from the current auth + user = self.app.client_manager.auth_ref.user_id + + creds = identity_client.ec2.create(user, project) + + info = {} + info.update(creds._info) + + if 'tenant_id' in info: + info.update( + {'project_id': info.pop('tenant_id')} + ) + + return zip(*sorted(six.iteritems(info))) + + +class DeleteEC2Creds(command.Command): + """Delete EC2 credentials""" + + log = logging.getLogger(__name__ + '.DeleteEC2Creds') + + def get_parser(self, prog_name): + parser = super(DeleteEC2Creds, self).get_parser(prog_name) + parser.add_argument( + 'access_key', + metavar='', + help=_('Credentials access key'), + ) + parser.add_argument( + '--user', + metavar='', + help=_('Specify a user'), + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + identity_client = self.app.client_manager.identity + + if parsed_args.user: + user = utils.find_resource( + identity_client.users, + parsed_args.user, + ).id + else: + # Get the user from the current auth + user = self.app.client_manager.auth_ref.user_id + + identity_client.ec2.delete(user, parsed_args.access_key) + + +class ListEC2Creds(lister.Lister): + """List EC2 credentials""" + + log = logging.getLogger(__name__ + '.ListEC2Creds') + + def get_parser(self, prog_name): + parser = super(ListEC2Creds, self).get_parser(prog_name) + parser.add_argument( + '--user', + metavar='', + help=_('Specify a user'), + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + identity_client = self.app.client_manager.identity + + if parsed_args.user: + user = utils.find_resource( + identity_client.users, + parsed_args.user, + ).id + else: + # Get the user from the current auth + user = self.app.client_manager.auth_ref.user_id + + columns = ('access', 'secret', 'tenant_id', 'user_id') + column_headers = ('Access', 'Secret', 'Project ID', 'User ID') + data = identity_client.ec2.list(user) + + return (column_headers, + (utils.get_item_properties( + s, columns, + formatters={}, + ) for s in data)) + + +class ShowEC2Creds(show.ShowOne): + """Display EC2 credentials details""" + + log = logging.getLogger(__name__ + '.ShowEC2Creds') + + def get_parser(self, prog_name): + parser = super(ShowEC2Creds, self).get_parser(prog_name) + parser.add_argument( + 'access_key', + metavar='', + help=_('Credentials access key'), + ) + parser.add_argument( + '--user', + metavar='', + help=_('Specify a user'), + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + identity_client = self.app.client_manager.identity + + if parsed_args.user: + user = utils.find_resource( + identity_client.users, + parsed_args.user, + ).id + else: + # Get the user from the current auth + user = self.app.client_manager.auth_ref.user_id + + creds = identity_client.ec2.get(user, parsed_args.access_key) + + info = {} + info.update(creds._info) + + if 'tenant_id' in info: + info.update( + {'project_id': info.pop('tenant_id')} + ) + + return zip(*sorted(six.iteritems(info))) diff --git a/setup.cfg b/setup.cfg index 8db7c3eae4..ce6a0b9b00 100644 --- a/setup.cfg +++ b/setup.cfg @@ -208,6 +208,11 @@ openstack.identity.v3 = domain_set = openstackclient.identity.v3.domain:SetDomain domain_show = openstackclient.identity.v3.domain:ShowDomain + ec2_credentials_create = openstackclient.identity.v3.ec2creds:CreateEC2Creds + ec2_credentials_delete = openstackclient.identity.v3.ec2creds:DeleteEC2Creds + ec2_credentials_list = openstackclient.identity.v3.ec2creds:ListEC2Creds + ec2_credentials_show = openstackclient.identity.v3.ec2creds:ShowEC2Creds + endpoint_create = openstackclient.identity.v3.endpoint:CreateEndpoint endpoint_delete = openstackclient.identity.v3.endpoint:DeleteEndpoint endpoint_set = openstackclient.identity.v3.endpoint:SetEndpoint From 7665d52a0c0643aa6034aa8cf3ae1240c693ca5f Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Tue, 2 Jun 2015 23:38:02 -0400 Subject: [PATCH 0093/3095] Add domain support for ec2creds in v3 identity A follow up work item from I52ff2020ef2fcbdc8a98280b73c6fd4a93bc8e0f to support domain scoped users and projects for ec2creds in the v3 identity api. Related-Bug: 1236326 Change-Id: If4ac5356ade8cff347bb9eb9f88d1ace82bb7275 --- .../command-objects/ec2-credentials.rst | 41 ++++++ openstackclient/identity/v3/ec2creds.py | 135 ++++++++++++------ 2 files changed, 129 insertions(+), 47 deletions(-) diff --git a/doc/source/command-objects/ec2-credentials.rst b/doc/source/command-objects/ec2-credentials.rst index a5b6754947..d675dc8c12 100644 --- a/doc/source/command-objects/ec2-credentials.rst +++ b/doc/source/command-objects/ec2-credentials.rst @@ -15,6 +15,8 @@ Create EC2 credentials os ec2 credentials create [--project ] [--user ] + [--user-domain ] + [--project-domain ] .. option:: --project @@ -24,6 +26,21 @@ Create EC2 credentials Specify an alternate user (default: current authenticated user) +.. option:: --user-domain + + Domain the user belongs to (name or ID). + This can be used in case collisions between user names exist. + + .. versionadded:: 3 + +.. option:: --project-domain + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + + .. versionadded:: 3 + + The :option:`--project` and :option:`--user` options are typically only useful for admin users, but may be allowed for other users depending on the policy of the cloud and the roles granted to the user. @@ -38,12 +55,20 @@ Delete EC2 credentials os ec2 credentials delete [--user ] + [--user-domain ] .. option:: --user Specify a user +.. option:: --user-domain + + Domain the user belongs to (name or ID). + This can be used in case collisions between user names exist. + + .. versionadded:: 3 + .. _ec2_credentials_delete-access-key: .. describe:: access-key @@ -63,11 +88,19 @@ List EC2 credentials os ec2 credentials list [--user ] + [--user-domain ] .. option:: --user Filter list by +.. option:: --user-domain + + Domain the user belongs to (name or ID). + This can be used in case collisions between user names exist. + + .. versionadded:: 3 + The :option:`--user` option is typically only useful for admin users, but may be allowed for other users depending on the policy of the cloud and the roles granted to the user. @@ -82,12 +115,20 @@ Display EC2 credentials details os ec2 credentials show [--user ] + [--user-domain ] .. option:: --user Specify a user +.. option:: --user-domain + + Domain the user belongs to (name or ID). + This can be used in case collisions between user names exist. + + .. versionadded:: 3 + .. _ec2_credentials_show-access-key: .. describe:: access-key diff --git a/openstackclient/identity/v3/ec2creds.py b/openstackclient/identity/v3/ec2creds.py index 254cca78a5..c49502c68b 100644 --- a/openstackclient/identity/v3/ec2creds.py +++ b/openstackclient/identity/v3/ec2creds.py @@ -21,6 +21,35 @@ from openstackclient.common import utils from openstackclient.i18n import _ # noqa +from openstackclient.identity import common + + +def _determine_ec2_user(parsed_args, client_manager): + """Determine a user several different ways. + + Assumes parsed_args has user and user_domain arguments. Attempts to find + the user if domain scoping is provided, otherwise revert to a basic user + call. Lastly use the currently authenticated user. + + """ + + user_domain = None + if parsed_args.user_domain: + user_domain = common.find_domain(client_manager.identity, + parsed_args.user_domain) + if parsed_args.user: + if user_domain is not None: + user = utils.find_resource(client_manager.identity.users, + parsed_args.user, + domain_id=user_domain.id).id + else: + user = utils.find_resource( + client_manager.identity.users, + parsed_args.user).id + else: + # Get the user from the current auth + user = client_manager.auth_ref.user_id + return user class CreateEC2Creds(show.ShowOne): @@ -42,28 +71,45 @@ def get_parser(self, prog_name): help=_('Specify an alternate user' ' (default: current authenticated user)'), ) + parser.add_argument( + '--user-domain', + metavar='', + help=('Domain the user belongs to (name or ID). ' + 'This can be used in case collisions between user names ' + 'exist.') + ) + parser.add_argument( + '--project-domain', + metavar='', + help=('Domain the project belongs to (name or ID). ' + 'This can be used in case collisions between project names ' + 'exist.') + ) return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity + client_manager = self.app.client_manager + user = self.determine_ec2_user(parsed_args, client_manager) + + project_domain = None + if parsed_args.project_domain: + project_domain = common.find_domain(identity_client, + parsed_args.project_domain) if parsed_args.project: - project = utils.find_resource( - identity_client.projects, - parsed_args.project, - ).id + if project_domain is not None: + project = utils.find_resource(identity_client.projects, + parsed_args.project, + domain_id=project_domain.id).id + else: + project = utils.find_resource( + identity_client.projects, + parsed_args.project).id else: # Get the project from the current auth project = self.app.client_manager.auth_ref.project_id - if parsed_args.user: - user = utils.find_resource( - identity_client.users, - parsed_args.user, - ).id - else: - # Get the user from the current auth - user = self.app.client_manager.auth_ref.user_id creds = identity_client.ec2.create(user, project) @@ -95,22 +141,20 @@ def get_parser(self, prog_name): metavar='', help=_('Specify a user'), ) + parser.add_argument( + '--user-domain', + metavar='', + help=('Domain the user belongs to (name or ID). ' + 'This can be used in case collisions between user names ' + 'exist.') + ) return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) - identity_client = self.app.client_manager.identity - - if parsed_args.user: - user = utils.find_resource( - identity_client.users, - parsed_args.user, - ).id - else: - # Get the user from the current auth - user = self.app.client_manager.auth_ref.user_id - - identity_client.ec2.delete(user, parsed_args.access_key) + client_manager = self.app.client_manager + user = self.determine_ec2_user(parsed_args, client_manager) + client_manager.identity.ec2.delete(user, parsed_args.access_key) class ListEC2Creds(lister.Lister): @@ -125,24 +169,23 @@ def get_parser(self, prog_name): metavar='', help=_('Specify a user'), ) + parser.add_argument( + '--user-domain', + metavar='', + help=('Domain the user belongs to (name or ID). ' + 'This can be used in case collisions between user names ' + 'exist.') + ) return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) - identity_client = self.app.client_manager.identity - - if parsed_args.user: - user = utils.find_resource( - identity_client.users, - parsed_args.user, - ).id - else: - # Get the user from the current auth - user = self.app.client_manager.auth_ref.user_id + client_manager = self.app.client_manager + user = self.determine_ec2_user(parsed_args, client_manager) columns = ('access', 'secret', 'tenant_id', 'user_id') column_headers = ('Access', 'Secret', 'Project ID', 'User ID') - data = identity_client.ec2.list(user) + data = client_manager.identity.ec2.list(user) return (column_headers, (utils.get_item_properties( @@ -168,22 +211,20 @@ def get_parser(self, prog_name): metavar='', help=_('Specify a user'), ) + parser.add_argument( + '--user-domain', + metavar='', + help=('Domain the user belongs to (name or ID). ' + 'This can be used in case collisions between user names ' + 'exist.') + ) return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) - identity_client = self.app.client_manager.identity - - if parsed_args.user: - user = utils.find_resource( - identity_client.users, - parsed_args.user, - ).id - else: - # Get the user from the current auth - user = self.app.client_manager.auth_ref.user_id - - creds = identity_client.ec2.get(user, parsed_args.access_key) + client_manager = self.app.client_manager + user = self.determine_ec2_user(parsed_args, client_manager) + creds = client_manager.identity.ec2.get(user, parsed_args.access_key) info = {} info.update(creds._info) From 8d185a6a6f72ecdffc0fb90bd7e6625ad6c071a0 Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Wed, 3 Jun 2015 12:03:17 -0600 Subject: [PATCH 0094/3095] Add functional tests for volume set and unset Add functional tests for volume set and unset for metadata and resource values. Change-Id: Ief07fc5c480608bb900d55df935b89c503609c80 --- functional/tests/volume/v1/test_volume.py | 28 ++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/functional/tests/volume/v1/test_volume.py b/functional/tests/volume/v1/test_volume.py index 9caf15630b..4a70b77963 100644 --- a/functional/tests/volume/v1/test_volume.py +++ b/functional/tests/volume/v1/test_volume.py @@ -19,6 +19,7 @@ class VolumeTests(test.TestCase): """Functional tests for volume. """ NAME = uuid.uuid4().hex + OTHER_NAME = uuid.uuid4().hex HEADERS = ['"Display Name"'] FIELDS = ['display_name'] @@ -31,7 +32,12 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - raw_output = cls.openstack('volume delete ' + cls.NAME) + # Rename test + raw_output = cls.openstack( + 'volume set --name ' + cls.OTHER_NAME + ' ' + cls.NAME) + cls.assertOutput('', raw_output) + # Delete test + raw_output = cls.openstack('volume delete ' + cls.OTHER_NAME) cls.assertOutput('', raw_output) def test_volume_list(self): @@ -43,3 +49,23 @@ def test_volume_show(self): opts = self.get_show_opts(self.FIELDS) raw_output = self.openstack('volume show ' + self.NAME + opts) self.assertEqual(self.NAME + "\n", raw_output) + + def test_volume_properties(self): + raw_output = self.openstack( + 'volume set --property a=b --property c=d ' + self.NAME) + self.assertEqual("", raw_output) + opts = self.get_show_opts(["properties"]) + raw_output = self.openstack('volume show ' + self.NAME + opts) + self.assertEqual("a='b', c='d'\n", raw_output) + + raw_output = self.openstack('volume unset --property a ' + self.NAME) + self.assertEqual("", raw_output) + raw_output = self.openstack('volume show ' + self.NAME + opts) + self.assertEqual("c='d'\n", raw_output) + + def test_volume_set(self): + raw_output = self.openstack( + 'volume set --description RAMAC ' + self.NAME) + opts = self.get_show_opts(["display_description", "display_name"]) + raw_output = self.openstack('volume show ' + self.NAME + opts) + self.assertEqual("RAMAC\n" + self.NAME + "\n", raw_output) From 3fa0bbc7ee26766e7ba5d02cf43a940065fdf613 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 3 Jun 2015 10:56:03 -0500 Subject: [PATCH 0095/3095] Clean up ec2 credentials help text Re-sync the text in v2 and v3 help and the docs Depends-On: If4ac5356ade8cff347bb9eb9f88d1ace82bb7275 Change-Id: Iabef2f271fcf46748295c29713fea1811dcab29c --- .../command-objects/ec2-credentials.rst | 20 +++---- openstackclient/identity/v2_0/ec2creds.py | 18 +++--- openstackclient/identity/v3/ec2creds.py | 58 ++++++++++++------- 3 files changed, 57 insertions(+), 39 deletions(-) diff --git a/doc/source/command-objects/ec2-credentials.rst b/doc/source/command-objects/ec2-credentials.rst index d675dc8c12..f8e3856485 100644 --- a/doc/source/command-objects/ec2-credentials.rst +++ b/doc/source/command-objects/ec2-credentials.rst @@ -20,22 +20,22 @@ Create EC2 credentials .. option:: --project - Specify an alternate project (default: current authenticated project) + Create credentials in project (name or ID; default: current authenticated project) .. option:: --user - Specify an alternate user (default: current authenticated user) + Create credentials for user (name or ID; default: current authenticated user) .. option:: --user-domain - Domain the user belongs to (name or ID). + Select user from a specific domain (name or ID) This can be used in case collisions between user names exist. .. versionadded:: 3 .. option:: --project-domain - Domain the project belongs to (name or ID). + Select project from a specific domain (name or ID) This can be used in case collisions between project names exist. .. versionadded:: 3 @@ -60,11 +60,11 @@ Delete EC2 credentials .. option:: --user - Specify a user + Delete credentials for user (name or ID) .. option:: --user-domain - Domain the user belongs to (name or ID). + Select user from a specific domain (name or ID) This can be used in case collisions between user names exist. .. versionadded:: 3 @@ -92,11 +92,11 @@ List EC2 credentials .. option:: --user - Filter list by + Filter list by (name or ID) .. option:: --user-domain - Domain the user belongs to (name or ID). + Select user from a specific domain (name or ID) This can be used in case collisions between user names exist. .. versionadded:: 3 @@ -120,11 +120,11 @@ Display EC2 credentials details .. option:: --user - Specify a user + Show credentials for user (name or ID) .. option:: --user-domain - Domain the user belongs to (name or ID). + Select user from a specific domain (name or ID) This can be used in case collisions between user names exist. .. versionadded:: 3 diff --git a/openstackclient/identity/v2_0/ec2creds.py b/openstackclient/identity/v2_0/ec2creds.py index 90553eb1f9..348479ac33 100644 --- a/openstackclient/identity/v2_0/ec2creds.py +++ b/openstackclient/identity/v2_0/ec2creds.py @@ -37,14 +37,18 @@ def get_parser(self, prog_name): parser.add_argument( '--project', metavar='', - help=_('Specify an alternate project' - ' (default: current authenticated project)'), + help=_( + 'Create credentials in project ' + '(name or ID; default: current authenticated project)' + ), ) parser.add_argument( '--user', metavar='', - help=_('Specify an alternate user' - ' (default: current authenticated user)'), + help=_( + 'Create credentials for user ' + '(name or ID; default: current authenticated user)' + ), ) return parser @@ -97,7 +101,7 @@ def get_parser(self, prog_name): parser.add_argument( '--user', metavar='', - help=_('Specify a user'), + help=_('Delete credentials for user (name or ID)'), ) return parser @@ -127,7 +131,7 @@ def get_parser(self, prog_name): parser.add_argument( '--user', metavar='', - help=_('Specify a user'), + help=_('Filter list by user (name or ID)'), ) return parser @@ -170,7 +174,7 @@ def get_parser(self, prog_name): parser.add_argument( '--user', metavar='', - help=_('Specify a user'), + help=_('Show credentials for user (name or ID)'), ) return parser diff --git a/openstackclient/identity/v3/ec2creds.py b/openstackclient/identity/v3/ec2creds.py index c49502c68b..f995ae5553 100644 --- a/openstackclient/identity/v3/ec2creds.py +++ b/openstackclient/identity/v3/ec2creds.py @@ -62,28 +62,36 @@ def get_parser(self, prog_name): parser.add_argument( '--project', metavar='', - help=_('Specify an alternate project' - ' (default: current authenticated project)'), + help=_( + 'Create credentials in project ' + '(name or ID; default: current authenticated project)' + ), ) parser.add_argument( '--user', metavar='', - help=_('Specify an alternate user' - ' (default: current authenticated user)'), + help=_( + 'Create credentials for user ' + '(name or ID; default: current authenticated user)' + ), ) parser.add_argument( '--user-domain', metavar='', - help=('Domain the user belongs to (name or ID). ' - 'This can be used in case collisions between user names ' - 'exist.') + help=( + 'Select user from a specific domain (name or ID); ' + 'This can be used in case collisions between user names ' + 'exist.' + ), ) parser.add_argument( '--project-domain', metavar='', - help=('Domain the project belongs to (name or ID). ' - 'This can be used in case collisions between project names ' - 'exist.') + help=( + 'Select project from a specific domain (name or ID); ' + 'This can be used in case collisions between project names ' + 'exist.' + ), ) return parser @@ -139,14 +147,16 @@ def get_parser(self, prog_name): parser.add_argument( '--user', metavar='', - help=_('Specify a user'), + help=_('Delete credentials for user (name or ID)'), ) parser.add_argument( '--user-domain', metavar='', - help=('Domain the user belongs to (name or ID). ' - 'This can be used in case collisions between user names ' - 'exist.') + help=( + 'Select user from a specific domain (name or ID); ' + 'This can be used in case collisions between user names ' + 'exist.' + ), ) return parser @@ -167,14 +177,16 @@ def get_parser(self, prog_name): parser.add_argument( '--user', metavar='', - help=_('Specify a user'), + help=_('Filter list by user (name or ID)'), ) parser.add_argument( '--user-domain', metavar='', - help=('Domain the user belongs to (name or ID). ' - 'This can be used in case collisions between user names ' - 'exist.') + help=( + 'Select user from a specific domain (name or ID); ' + 'This can be used in case collisions between user names ' + 'exist.' + ), ) return parser @@ -209,14 +221,16 @@ def get_parser(self, prog_name): parser.add_argument( '--user', metavar='', - help=_('Specify a user'), + help=_('Show credentials for user (name or ID)'), ) parser.add_argument( '--user-domain', metavar='', - help=('Domain the user belongs to (name or ID). ' - 'This can be used in case collisions between user names ' - 'exist.') + help=( + 'Select user from a specific domain (name or ID); ' + 'This can be used in case collisions between user names ' + 'exist.' + ), ) return parser From 31d785ec6951a84f831ea3dfd49214c42ae4fd26 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 4 Jun 2015 09:20:29 -0500 Subject: [PATCH 0096/3095] Allow --insecure to override --os-cacert Change --insecure to ignore the --os-cacert setting. This is a change from before where OSC followed the requests pattern of cacert taking priority. This logic is also introduced in os-client-config 1.3.0; we do not require that release yet so it is duplicated here for now. That change will come with the upcoming global options refactor. Closes-Bug: #1447784 Change-Id: Iaa6d499ed0929c00a56dcd92a2017487c702774a --- openstackclient/shell.py | 21 +++++++++++++++------ openstackclient/tests/test_shell.py | 9 +++++---- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 136542dcd5..36483b3a7e 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -264,12 +264,21 @@ def initialize_app(self, argv): self.log.debug("cloud cfg: %s", self.cloud.config) # Set up client TLS - cacert = self.cloud.cacert - if cacert: - self.verify = cacert - else: - self.verify = not self.cloud.config.get('insecure', False) - self.verify = self.cloud.config.get('verify', self.verify) + # NOTE(dtroyer): --insecure is the non-default condition that + # overrides any verify setting in clouds.yaml + # so check it first, then fall back to any verify + # setting provided. + self.verify = not self.cloud.config.get( + 'insecure', + not self.cloud.config.get('verify', True), + ) + + # NOTE(dtroyer): Per bug https://bugs.launchpad.net/bugs/1447784 + # --insecure now overrides any --os-cacert setting, + # where before --insecure was ignored if --os-cacert + # was set. + if self.verify and self.cloud.cacert: + self.verify = self.cloud.cacert # Save default domain self.default_domain = self.options.default_domain diff --git a/openstackclient/tests/test_shell.py b/openstackclient/tests/test_shell.py index 8850d8f97d..b080ae9164 100644 --- a/openstackclient/tests/test_shell.py +++ b/openstackclient/tests/test_shell.py @@ -540,14 +540,15 @@ def test_shell_args_ca_options(self): self.assertTrue(_shell.verify) # --os-cacert and --insecure - # NOTE(dtroyer): This really is a bogus combination, the default is - # to follow the requests.Session convention and let - # --os-cacert override --insecure + # NOTE(dtroyer): Per bug https://bugs.launchpad.net/bugs/1447784 + # in this combination --insecure now overrides any + # --os-cacert setting, where before --insecure + # was ignored if --os-cacert was set. fake_execute(_shell, "--os-cacert foo --insecure list user") self.assertIsNone(_shell.options.verify) self.assertTrue(_shell.options.insecure) self.assertEqual('foo', _shell.options.cacert) - self.assertTrue(_shell.verify) + self.assertFalse(_shell.verify) def test_default_env(self): flag = "" From c88b433abbf6a05be7aca3ad34db7ad5842f266b Mon Sep 17 00:00:00 2001 From: Amey Bhide Date: Wed, 3 Jun 2015 10:40:42 -0700 Subject: [PATCH 0097/3095] Add support for volume snapshot v2 command openstack snapshot create openstack snapshot set openstack snapshot unset openstack snapshot list Implements: blueprint volume-v2 Change-Id: Ia1d9f4426baa0099281a9931f4eec99ebe1969b1 --- openstackclient/tests/volume/v2/fakes.py | 15 +- .../tests/volume/v2/test_snapshot.py | 183 ++++++++++++++++ openstackclient/volume/v2/snapshot.py | 205 +++++++++++++++++- setup.cfg | 4 + 4 files changed, 401 insertions(+), 6 deletions(-) diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py index 3eade3910f..155ccae805 100644 --- a/openstackclient/tests/volume/v2/fakes.py +++ b/openstackclient/tests/volume/v2/fakes.py @@ -12,6 +12,7 @@ # under the License. # +import copy import mock from openstackclient.tests import fakes @@ -62,11 +63,17 @@ "name": snapshot_name, "description": snapshot_description, "size": snapshot_size, - "metadata": snapshot_metadata + "status": "available", + "metadata": snapshot_metadata, + "created_at": "2015-06-03T18:49:19.000000", + "volume_id": volume_name } - -SNAPSHOT_columns = tuple(sorted(SNAPSHOT)) -SNAPSHOT_data = tuple((SNAPSHOT[x] for x in sorted(SNAPSHOT))) +EXPECTED_SNAPSHOT = copy.deepcopy(SNAPSHOT) +EXPECTED_SNAPSHOT.pop("metadata") +EXPECTED_SNAPSHOT['properties'] = "foo='bar'" +SNAPSHOT_columns = tuple(sorted(EXPECTED_SNAPSHOT)) +SNAPSHOT_data = tuple((EXPECTED_SNAPSHOT[x] + for x in sorted(EXPECTED_SNAPSHOT))) type_id = "5520dc9e-6f9b-4378-a719-729911c0f407" diff --git a/openstackclient/tests/volume/v2/test_snapshot.py b/openstackclient/tests/volume/v2/test_snapshot.py index 9101541009..3ceb57faf2 100644 --- a/openstackclient/tests/volume/v2/test_snapshot.py +++ b/openstackclient/tests/volume/v2/test_snapshot.py @@ -26,6 +26,53 @@ def setUp(self): self.snapshots_mock = self.app.client_manager.volume.volume_snapshots self.snapshots_mock.reset_mock() + self.volumes_mock = self.app.client_manager.volume.volumes + self.volumes_mock.reset_mock() + + +class TestSnapshotCreate(TestSnapshot): + def setUp(self): + super(TestSnapshotCreate, self).setUp() + + self.volumes_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.VOLUME), + loaded=True + ) + + self.snapshots_mock.create.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.SNAPSHOT), + loaded=True + ) + # Get the command object to test + self.cmd = snapshot.CreateSnapshot(self.app, None) + + def test_snapshot_create(self): + arglist = [ + volume_fakes.volume_id, + "--name", volume_fakes.snapshot_name, + "--description", volume_fakes.snapshot_description, + "--force" + ] + verifylist = [ + ("volume", volume_fakes.volume_id), + ("name", volume_fakes.snapshot_name), + ("description", volume_fakes.snapshot_description), + ("force", True) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.snapshots_mock.create.assert_called_with( + volume_fakes.volume_id, + force=True, + name=volume_fakes.snapshot_name, + description=volume_fakes.snapshot_description + ) + self.assertEqual(columns, volume_fakes.SNAPSHOT_columns) + self.assertEqual(data, volume_fakes.SNAPSHOT_data) class TestSnapshotShow(TestSnapshot): @@ -80,3 +127,139 @@ def test_snapshot_delete(self): self.cmd.take_action(parsed_args) self.snapshots_mock.delete.assert_called_with(volume_fakes.snapshot_id) + + +class TestSnapshotSet(TestSnapshot): + def setUp(self): + super(TestSnapshotSet, self).setUp() + + self.snapshots_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.SNAPSHOT), + loaded=True + ) + self.snapshots_mock.set_metadata.return_value = None + self.snapshots_mock.update.return_value = None + # Get the command object to mock + self.cmd = snapshot.SetSnapshot(self.app, None) + + def test_snapshot_set(self): + arglist = [ + volume_fakes.snapshot_id, + "--name", "new_snapshot", + "--property", "x=y", + "--property", "foo=foo" + ] + new_property = {"x": "y", "foo": "foo"} + verifylist = [ + ("snapshot", volume_fakes.snapshot_id), + ("name", "new_snapshot"), + ("property", new_property) + ] + + kwargs = { + "name": "new_snapshot", + } + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.snapshots_mock.update.assert_called_with( + volume_fakes.snapshot_id, **kwargs) + self.snapshots_mock.set_metadata.assert_called_with( + volume_fakes.snapshot_id, new_property + ) + + +class TestSnapshotUnset(TestSnapshot): + def setUp(self): + super(TestSnapshotUnset, self).setUp() + + self.snapshots_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.SNAPSHOT), + loaded=True + ) + self.snapshots_mock.delete_metadata.return_value = None + # Get the command object to mock + self.cmd = snapshot.UnsetSnapshot(self.app, None) + + def test_snapshot_unset(self): + arglist = [ + volume_fakes.snapshot_id, + "--property", "foo" + ] + verifylist = [ + ("snapshot", volume_fakes.snapshot_id), + ("property", ["foo"]) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.snapshots_mock.delete_metadata.assert_called_with( + volume_fakes.snapshot_id, ["foo"] + ) + + +class TestSnapshotList(TestSnapshot): + def setUp(self): + super(TestSnapshotList, self).setUp() + + self.volumes_mock.list.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.VOLUME), + loaded=True + ) + ] + self.snapshots_mock.list.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.SNAPSHOT), + loaded=True + ) + ] + # Get the command to test + self.cmd = snapshot.ListSnapshot(self.app, None) + + def test_snapshot_list_without_options(self): + arglist = [] + verifylist = [ + ("long", False) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + collist = ["ID", "Name", "Description", "Status", "Size"] + self.assertEqual(collist, columns) + datalist = (( + volume_fakes.snapshot_id, + volume_fakes.snapshot_name, + volume_fakes.snapshot_description, + "available", + volume_fakes.snapshot_size + ),) + self.assertEqual(datalist, tuple(data)) + + def test_snapshot_list_with_options(self): + arglist = ["--long"] + verifylist = [("long", True)] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = ["ID", "Name", "Description", "Status", "Size", "Created At", + "Volume", "Properties"] + self.assertEqual(collist, columns) + + datalist = (( + volume_fakes.snapshot_id, + volume_fakes.snapshot_name, + volume_fakes.snapshot_description, + "available", + volume_fakes.snapshot_size, + "2015-06-03T18:49:19.000000", + volume_fakes.volume_name, + volume_fakes.EXPECTED_SNAPSHOT.get("properties") + ),) + self.assertEqual(datalist, tuple(data)) diff --git a/openstackclient/volume/v2/snapshot.py b/openstackclient/volume/v2/snapshot.py index a6b02b63a0..4370cdeb17 100644 --- a/openstackclient/volume/v2/snapshot.py +++ b/openstackclient/volume/v2/snapshot.py @@ -14,15 +14,67 @@ """Volume v2 snapshot action implementations""" +import copy import logging from cliff import command +from cliff import lister from cliff import show import six +from openstackclient.common import parseractions from openstackclient.common import utils +class CreateSnapshot(show.ShowOne): + """Create new snapshot""" + + log = logging.getLogger(__name__ + ".CreateSnapshot") + + def get_parser(self, prog_name): + parser = super(CreateSnapshot, self).get_parser(prog_name) + parser.add_argument( + "volume", + metavar="", + help="Volume to snapshot (name or ID)" + ) + parser.add_argument( + "--name", + metavar="", + required=True, + help="Name of the snapshot" + ) + parser.add_argument( + "--description", + metavar="", + help="Description of the snapshot" + ) + parser.add_argument( + "--force", + dest="force", + action="store_true", + default=False, + help="Create a snapshot attached to an instance. Default is False" + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action: (%s)", parsed_args) + volume_client = self.app.client_manager.volume + volume_id = utils.find_resource( + volume_client.volumes, parsed_args.volume).id + snapshot = volume_client.volume_snapshots.create( + volume_id, + force=parsed_args.force, + name=parsed_args.name, + description=parsed_args.description + ) + snapshot._info.update( + {'properties': utils.format_dict(snapshot._info.pop('metadata'))} + ) + return zip(*sorted(six.iteritems(snapshot._info))) + + class DeleteSnapshot(command.Command): """Delete volume snapshot(s)""" @@ -34,7 +86,7 @@ def get_parser(self, prog_name): "snapshots", metavar="", nargs="+", - help="Snapsho(s) to delete (name or ID)" + help="Snapshot(s) to delete (name or ID)" ) return parser @@ -48,6 +100,115 @@ def take_action(self, parsed_args): return +class ListSnapshot(lister.Lister): + """List snapshots""" + + log = logging.getLogger(__name__ + ".ListSnapshot") + + def get_parser(self, prog_name): + parser = super(ListSnapshot, self).get_parser(prog_name) + parser.add_argument( + '--long', + action='store_true', + default=False, + help='List additional fields in output', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action: (%s)", parsed_args) + + def _format_volume_id(volume_id): + """Return a volume name if available + + :param volume_id: a volume ID + :rtype: either the volume ID or name + """ + + volume = volume_id + if volume_id in volume_cache.keys(): + volume = volume_cache[volume_id].name + return volume + + if parsed_args.long: + columns = ['ID', 'Name', 'Description', 'Status', + 'Size', 'Created At', 'Volume ID', 'Metadata'] + column_headers = copy.deepcopy(columns) + column_headers[6] = 'Volume' + column_headers[7] = 'Properties' + else: + columns = ['ID', 'Name', 'Description', 'Status', 'Size'] + column_headers = copy.deepcopy(columns) + + # Cache the volume list + volume_cache = {} + try: + for s in self.app.client_manager.volume.volumes.list(): + volume_cache[s.id] = s + except Exception: + # Just forget it if there's any trouble + pass + + data = self.app.client_manager.volume.volume_snapshots.list() + return (column_headers, + (utils.get_item_properties( + s, columns, + formatters={'Metadata': utils.format_dict, + 'Volume ID': _format_volume_id}, + ) for s in data)) + + +class SetSnapshot(command.Command): + """Set snapshot properties""" + + log = logging.getLogger(__name__ + '.SetSnapshot') + + def get_parser(self, prog_name): + parser = super(SetSnapshot, self).get_parser(prog_name) + parser.add_argument( + 'snapshot', + metavar='', + help='Snapshot to modify (name or ID)') + parser.add_argument( + '--name', + metavar='', + help='New snapshot name') + parser.add_argument( + '--description', + metavar='', + help='New snapshot description') + parser.add_argument( + '--property', + metavar='', + action=parseractions.KeyValueAction, + help='Property to add/change for this snapshot ' + '(repeat option to set multiple properties)', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + volume_client = self.app.client_manager.volume + snapshot = utils.find_resource(volume_client.volume_snapshots, + parsed_args.snapshot) + + kwargs = {} + if parsed_args.name: + kwargs['name'] = parsed_args.name + if parsed_args.description: + kwargs['description'] = parsed_args.description + + if not kwargs and not parsed_args.property: + self.app.log.error("No changes requested\n") + return + + if parsed_args.property: + volume_client.volume_snapshots.set_metadata(snapshot.id, + parsed_args.property) + volume_client.volume_snapshots.update(snapshot.id, **kwargs) + return + + class ShowSnapshot(show.ShowOne): """Display snapshot details""" @@ -67,5 +228,45 @@ def take_action(self, parsed_args): volume_client = self.app.client_manager.volume snapshot = utils.find_resource( volume_client.volume_snapshots, parsed_args.snapshot) - snapshot = volume_client.volume_snapshots.get(snapshot.id) + snapshot._info.update( + {'properties': utils.format_dict(snapshot._info.pop('metadata'))} + ) return zip(*sorted(six.iteritems(snapshot._info))) + + +class UnsetSnapshot(command.Command): + """Unset snapshot properties""" + + log = logging.getLogger(__name__ + '.UnsetSnapshot') + + def get_parser(self, prog_name): + parser = super(UnsetSnapshot, self).get_parser(prog_name) + parser.add_argument( + 'snapshot', + metavar='', + help='Snapshot to modify (name or ID)', + ) + parser.add_argument( + '--property', + metavar='', + action='append', + default=[], + help='Property to remove from snapshot ' + '(repeat to remove multiple values)', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + volume_client = self.app.client_manager.volume + snapshot = utils.find_resource( + volume_client.volume_snapshots, parsed_args.snapshot) + + if parsed_args.property: + volume_client.volume_snapshots.delete_metadata( + snapshot.id, + parsed_args.property, + ) + else: + self.app.log.error("No changes requested\n") + return diff --git a/setup.cfg b/setup.cfg index ce6a0b9b00..15b928bd4b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -370,8 +370,12 @@ openstack.volume.v2 = backup_delete = openstackclient.volume.v2.backup:DeleteBackup backup_show = openstackclient.volume.v2.backup:ShowBackup + snapshot_create = openstackclient.volume.v2.snapshot:CreateSnapshot snapshot_delete = openstackclient.volume.v2.snapshot:DeleteSnapshot + snapshot_list = openstackclient.volume.v2.snapshot:ListSnapshot + snapshot_set = openstackclient.volume.v2.snapshot:SetSnapshot snapshot_show = openstackclient.volume.v2.snapshot:ShowSnapshot + snapshot_unset = openstackclient.volume.v2.snapshot:UnsetSnapshot volume_delete = openstackclient.volume.v2.volume:DeleteVolume volume_show = openstackclient.volume.v2.volume:ShowVolume From 43d12db709805225f91608e3d1e1d43a71a5d26f Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 4 Jun 2015 20:14:18 +0000 Subject: [PATCH 0098/3095] Updated from global requirements Change-Id: I243598ee6778297b622c8e29c78b76d8eb5a4692 --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 415d27a098..ee82587b88 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,12 +7,12 @@ six>=1.9.0 Babel>=1.3 cliff>=1.10.0 # Apache-2.0 cliff-tablib>=1.0 -os-client-config +os-client-config>=1.2.0 oslo.config>=1.11.0 # Apache-2.0 oslo.i18n>=1.5.0 # Apache-2.0 oslo.utils>=1.4.0 # Apache-2.0 python-glanceclient>=0.17.1 -python-keystoneclient>=1.3.0 +python-keystoneclient>=1.6.0 python-novaclient>=2.22.0 python-cinderclient>=1.2.1 python-neutronclient>=2.3.11,<3 From b80856617f237b1045da2e9ab6ffd2512da0e353 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Tue, 2 Jun 2015 07:39:29 -0500 Subject: [PATCH 0099/3095] Create 1.4.0 release notes Change-Id: I2fa7d7875518ceebbdc3936eedfe72d17c81535f --- doc/source/releases.rst | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/doc/source/releases.rst b/doc/source/releases.rst index 9048cb9551..2d6224c618 100644 --- a/doc/source/releases.rst +++ b/doc/source/releases.rst @@ -2,6 +2,33 @@ Release Notes ============= +1.4.0 (04 Jun 2015) +=================== + +* AttributeError: 'Client' object has no attribute 'ec2' + Bug `1236326 `_ + +* ``--insecure`` is ignored if ``OS_CACERT`` env var is set + Bug `1447784 `_ + +* ``security group list`` always uses identity admin endpoint + Bug `1459629 `_ + +* Race failure to delete security group + Bug `1460112 `_ + +* v3 project set is missing ``--domain`` argument + Bug `1460122 `_ + +* Project create is missing ``--parent`` argument in doc + Bug `1460256 `_ + +* v3 ``role add`` is missing domain scope arguments in doc + Bug `1460296 `_ + +* Cannot force v2password auth plugin + Bug `1460369 `_ + 1.3.0 (27 May 2015) =================== From 2fce8634119af42cef580f52b6b11e9c13326532 Mon Sep 17 00:00:00 2001 From: Amey Bhide Date: Wed, 3 Jun 2015 15:05:18 -0700 Subject: [PATCH 0100/3095] Add support for volume backup v2 command openstack backup create openstack backup list openstack backup restore Implements: blueprint volume-v2 Change-Id: I77965730065dd44f256c46bcc43c1e6a03b63145 --- openstackclient/tests/volume/v2/fakes.py | 7 +- .../tests/volume/v2/test_backup.py | 147 ++++++++++++++++++ openstackclient/volume/v2/backup.py | 132 ++++++++++++++++ setup.cfg | 3 + 4 files changed, 288 insertions(+), 1 deletion(-) diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py index 3eade3910f..d6c07b9f88 100644 --- a/openstackclient/tests/volume/v2/fakes.py +++ b/openstackclient/tests/volume/v2/fakes.py @@ -93,6 +93,7 @@ backup_object_count = None backup_container = None backup_size = 10 +backup_status = "error" BACKUP = { "id": backup_id, @@ -101,7 +102,9 @@ "description": backup_description, "object_count": backup_object_count, "container": backup_container, - "size": backup_size + "size": backup_size, + "status": backup_status, + "availability_zone": volume_availability_zone, } BACKUP_columns = tuple(sorted(BACKUP)) @@ -118,6 +121,8 @@ def __init__(self, **kwargs): self.backups.resource_class = fakes.FakeResource(None, {}) self.volume_types = mock.Mock() self.volume_types.resource_class = fakes.FakeResource(None, {}) + self.restores = mock.Mock() + self.restores.resource_class = fakes.FakeResource(None, {}) self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] diff --git a/openstackclient/tests/volume/v2/test_backup.py b/openstackclient/tests/volume/v2/test_backup.py index e24cac3cad..7af22e8a45 100644 --- a/openstackclient/tests/volume/v2/test_backup.py +++ b/openstackclient/tests/volume/v2/test_backup.py @@ -26,6 +26,55 @@ def setUp(self): self.backups_mock = self.app.client_manager.volume.backups self.backups_mock.reset_mock() + self.volumes_mock = self.app.client_manager.volume.volumes + self.volumes_mock.reset_mock() + self.restores_mock = self.app.client_manager.volume.restores + self.restores_mock.reset_mock() + + +class TestBackupCreate(TestBackup): + def setUp(self): + super(TestBackupCreate, self).setUp() + + self.volumes_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.VOLUME), + loaded=True + ) + + self.backups_mock.create.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.BACKUP), + loaded=True + ) + # Get the command object to test + self.cmd = backup.CreateBackup(self.app, None) + + def test_backup_create(self): + arglist = [ + volume_fakes.volume_id, + "--name", volume_fakes.backup_name, + "--description", volume_fakes.backup_description, + "--container", volume_fakes.backup_name + ] + verifylist = [ + ("volume", volume_fakes.volume_id), + ("name", volume_fakes.backup_name), + ("description", volume_fakes.backup_description), + ("container", volume_fakes.backup_name) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.backups_mock.create.assert_called_with( + volume_fakes.volume_id, + container=volume_fakes.backup_name, + name=volume_fakes.backup_name, + description=volume_fakes.backup_description + ) + self.assertEqual(columns, volume_fakes.BACKUP_columns) + self.assertEqual(data, volume_fakes.BACKUP_data) class TestBackupShow(TestBackup): @@ -80,3 +129,101 @@ def test_backup_delete(self): self.cmd.take_action(parsed_args) self.backups_mock.delete.assert_called_with(volume_fakes.backup_id) + + +class TestBackupRestore(TestBackup): + def setUp(self): + super(TestBackupRestore, self).setUp() + + self.backups_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.BACKUP), + loaded=True + ) + self.volumes_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.VOLUME), + loaded=True + ) + self.restores_mock.restore.return_value = None + # Get the command object to mock + self.cmd = backup.RestoreBackup(self.app, None) + + def test_backup_restore(self): + arglist = [ + volume_fakes.backup_id, + volume_fakes.volume_id + ] + verifylist = [ + ("backup", volume_fakes.backup_id), + ("volume", volume_fakes.volume_id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + self.restores_mock.restore.assert_called_with(volume_fakes.backup_id, + volume_fakes.volume_id) + + +class TestBackupList(TestBackup): + def setUp(self): + super(TestBackupList, self).setUp() + + self.volumes_mock.list.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.VOLUME), + loaded=True + ) + ] + self.backups_mock.list.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.BACKUP), + loaded=True + ) + ] + # Get the command to test + self.cmd = backup.ListBackup(self.app, None) + + def test_backup_list_without_options(self): + arglist = [] + verifylist = [("long", False)] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + collist = ['ID', 'Name', 'Description', 'Status', 'Size'] + self.assertEqual(collist, columns) + + datalist = (( + volume_fakes.backup_id, + volume_fakes.backup_name, + volume_fakes.backup_description, + volume_fakes.backup_status, + volume_fakes.backup_size + ),) + self.assertEqual(datalist, tuple(data)) + + def test_backup_list_with_options(self): + arglist = ["--long"] + verifylist = [("long", True)] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + collist = ['ID', 'Name', 'Description', 'Status', 'Size', + 'Availability Zone', 'Volume', 'Container'] + self.assertEqual(collist, columns) + + datalist = (( + volume_fakes.backup_id, + volume_fakes.backup_name, + volume_fakes.backup_description, + volume_fakes.backup_status, + volume_fakes.backup_size, + volume_fakes.volume_availability_zone, + volume_fakes.backup_volume_id, + volume_fakes.backup_container + ),) + self.assertEqual(datalist, tuple(data)) diff --git a/openstackclient/volume/v2/backup.py b/openstackclient/volume/v2/backup.py index bf2ea3a620..3525e701fe 100644 --- a/openstackclient/volume/v2/backup.py +++ b/openstackclient/volume/v2/backup.py @@ -14,15 +14,62 @@ """Volume v2 Backup action implementations""" +import copy import logging from cliff import command +from cliff import lister from cliff import show import six from openstackclient.common import utils +class CreateBackup(show.ShowOne): + """Create new backup""" + + log = logging.getLogger(__name__ + ".CreateBackup") + + def get_parser(self, prog_name): + parser = super(CreateBackup, self).get_parser(prog_name) + parser.add_argument( + "volume", + metavar="", + help="Volume to backup (name or ID)" + ) + parser.add_argument( + "--name", + metavar="", + required=True, + help="Name of the backup" + ) + parser.add_argument( + "--description", + metavar="", + help="Description of the backup" + ) + parser.add_argument( + "--container", + metavar="", + help="Optional backup container name" + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action: (%s)", parsed_args) + volume_client = self.app.client_manager.volume + volume_id = utils.find_resource( + volume_client.volumes, parsed_args.volume).id + backup = volume_client.backups.create( + volume_id, + container=parsed_args.container, + name=parsed_args.name, + description=parsed_args.description + ) + backup._info.pop("links", None) + return zip(*sorted(six.iteritems(backup._info))) + + class DeleteBackup(command.Command): """Delete backup(s)""" @@ -48,6 +95,91 @@ def take_action(self, parsed_args): return +class ListBackup(lister.Lister): + """List backups""" + + log = logging.getLogger(__name__ + ".ListBackup") + + def get_parser(self, prog_name): + parser = super(ListBackup, self).get_parser(prog_name) + parser.add_argument( + "--long", + action="store_true", + default=False, + help="List additional fields in output" + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action: (%s)", parsed_args) + + def _format_volume_id(volume_id): + """Return a volume name if available + + :param volume_id: a volume ID + :rtype: either the volume ID or name + """ + + volume = volume_id + if volume_id in volume_cache.keys(): + volume = volume_cache[volume_id].name + return volume + + if parsed_args.long: + columns = ['ID', 'Name', 'Description', 'Status', 'Size', + 'Availability Zone', 'Volume ID', 'Container'] + column_headers = copy.deepcopy(columns) + column_headers[6] = 'Volume' + else: + columns = ['ID', 'Name', 'Description', 'Status', 'Size'] + column_headers = columns + + # Cache the volume list + volume_cache = {} + try: + for s in self.app.client_manager.volume.volumes.list(): + volume_cache[s.id] = s + except Exception: + # Just forget it if there's any trouble + pass + + data = self.app.client_manager.volume.backups.list() + + return (column_headers, + (utils.get_item_properties( + s, columns, + formatters={'Volume ID': _format_volume_id}, + ) for s in data)) + + +class RestoreBackup(show.ShowOne): + """Restore backup""" + + log = logging.getLogger(__name__ + ".RestoreBackup") + + def get_parser(self, prog_name): + parser = super(RestoreBackup, self).get_parser(prog_name) + parser.add_argument( + "backup", + metavar="", + help="Backup to restore (ID only)" + ) + parser.add_argument( + "volume", + metavar="", + help="Volume to restore to (name or ID)" + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action: (%s)", parsed_args) + volume_client = self.app.client_manager.volume + backup = utils.find_resource(volume_client.backups, parsed_args.backup) + destination_volume = utils.find_resource(volume_client.volumes, + parsed_args.volume) + return volume_client.restores.restore(backup.id, destination_volume.id) + + class ShowBackup(show.ShowOne): """Display backup details""" diff --git a/setup.cfg b/setup.cfg index ce6a0b9b00..838786ad18 100644 --- a/setup.cfg +++ b/setup.cfg @@ -367,7 +367,10 @@ openstack.volume.v1 = volume_type_unset = openstackclient.volume.v1.type:UnsetVolumeType openstack.volume.v2 = + backup_create = openstackclient.volume.v2.backup:CreateBackup backup_delete = openstackclient.volume.v2.backup:DeleteBackup + backup_list = openstackclient.volume.v2.backup:ListBackup + backup_restore = openstackclient.volume.v2.backup:RestoreBackup backup_show = openstackclient.volume.v2.backup:ShowBackup snapshot_delete = openstackclient.volume.v2.snapshot:DeleteSnapshot From 7cf779004e5e9eab9abee8d8a5bc2db3dfa8bd5d Mon Sep 17 00:00:00 2001 From: Dave Chen Date: Fri, 5 Jun 2015 22:43:08 +0800 Subject: [PATCH 0101/3095] Not use the deprecated argument `project` argument is deprecated in keystoneclient for V3 API, and use `default_project` instead, should use `default_project` as the argument name in the openstackclient accordingly. Change-Id: Ib9d70801c933a184afcdab75204393efa764fa87 Closes-Bug: #1462389 --- openstackclient/identity/v3/user.py | 2 +- openstackclient/tests/identity/v3/test_user.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index c1a0a43c6b..b72e0d1580 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -369,7 +369,7 @@ def take_action(self, parsed_args): if parsed_args.project: project_id = utils.find_resource( identity_client.projects, parsed_args.project).id - kwargs['project'] = project_id + kwargs['default_project'] = project_id kwargs['enabled'] = user.enabled if parsed_args.enable: kwargs['enabled'] = True diff --git a/openstackclient/tests/identity/v3/test_user.py b/openstackclient/tests/identity/v3/test_user.py index ab6e6bce2e..18fe9016d4 100644 --- a/openstackclient/tests/identity/v3/test_user.py +++ b/openstackclient/tests/identity/v3/test_user.py @@ -885,7 +885,7 @@ def test_user_set_project(self): # Set expected values kwargs = { 'enabled': True, - 'project': identity_fakes.project_id, + 'default_project': identity_fakes.project_id, } # UserManager.update(user, name=, domain=, project=, password=, # email=, description=, enabled=, default_project=) From f7feef7f8d1df7b6a28eb6d2e684bf8f1853d356 Mon Sep 17 00:00:00 2001 From: Juan Antonio Osorio Robles Date: Mon, 8 Jun 2015 16:30:06 +0300 Subject: [PATCH 0102/3095] Enable specifying domain for group and role commands Many of the commands for the group and role resources were lacking an option to specify the specific domain groups, projects or users belong to. This commit fixes that. Change-Id: I461d2bcfd01ad2dea970de38ec7ad6f4a631ceb1 Closes-bug: #1446546 --- doc/source/command-objects/group.rst | 62 +++++- doc/source/command-objects/role.rst | 50 ++++- openstackclient/identity/common.py | 16 +- openstackclient/identity/v3/group.py | 116 +++++++--- openstackclient/identity/v3/role.py | 321 ++++++++++----------------- 5 files changed, 315 insertions(+), 250 deletions(-) diff --git a/doc/source/command-objects/group.rst b/doc/source/command-objects/group.rst index 6c385058b0..0f2c5cd10b 100644 --- a/doc/source/command-objects/group.rst +++ b/doc/source/command-objects/group.rst @@ -13,9 +13,25 @@ Add user to group .. code:: bash os group add user + [--group-domain ] + [--user-domain ] +.. option:: --group-domain + + Domain the group belongs to (name or ID). This can be + used in case collisions between group names exist. + + .. versionadded:: 3 + +.. option:: --user-domain + + Domain the user belongs to (name or ID). This can be + used in case collisions between user names exist. + + .. versionadded:: 3 + .. describe:: Group to contain (name or ID) @@ -33,9 +49,25 @@ Check user membership in group .. code:: bash os group contains user + [--group-domain ] + [--user-domain ] +.. option:: --group-domain + + Domain the group belongs to (name or ID). This can be + used in case collisions between group names exist. + + .. versionadded:: 3 + +.. option:: --user-domain + + Domain the user belongs to (name or ID). This can be + used in case collisions between user names exist. + + .. versionadded:: 3 + .. describe:: Group to check (name or ID) @@ -106,7 +138,7 @@ List groups os group list [--domain ] - [--user ] + [--user [--user-domain ]] [--long] .. option:: --domain @@ -117,6 +149,13 @@ List groups Filter group list by (name or ID) +.. option:: --user-domain + + Domain the user belongs to (name or ID). This can be + used in case collisions between user names exist. + + .. versionadded:: 3 + .. option:: --long List additional fields in output @@ -130,9 +169,25 @@ Remove user from group .. code:: bash os group remove user + [--group-domain ] + [--user-domain ] +.. option:: --group-domain + + Domain the group belongs to (name or ID). This can be + used in case collisions between group names exist. + + .. versionadded:: 3 + +.. option:: --user-domain + + Domain the user belongs to (name or ID). This can be + used in case collisions between user names exist. + + .. versionadded:: 3 + .. describe:: Group containing (name or ID) @@ -150,10 +205,15 @@ Set group properties .. code:: bash os group set + [--domain ] [--name ] [--description ] +.. option:: --domain + + Domain containing (name or ID) + .. option:: --name New group name diff --git a/doc/source/command-objects/role.rst b/doc/source/command-objects/role.rst index 3672cfa1fd..dad5642dc0 100644 --- a/doc/source/command-objects/role.rst +++ b/doc/source/command-objects/role.rst @@ -101,8 +101,8 @@ List roles .. code:: bash os role list - [--domain | --project | --group ] + --domain | --project [--project-domain ] + --user [--user-domain ] | --group [--group-domain ] .. option:: --domain @@ -128,6 +128,27 @@ List roles .. versionadded:: 3 +.. option:: --user-domain + + Domain the user belongs to (name or ID). + This can be used in case collisions between user names exist. + + .. versionadded:: 3 + +.. option:: --group-domain + + Domain the group belongs to (name or ID). + This can be used in case collisions between group names exist. + + .. versionadded:: 3 + +.. option:: --project-domain + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + + .. versionadded:: 3 + role remove ----------- @@ -137,8 +158,8 @@ Remove role from domain/project : user/group .. code:: bash os role remove - [--domain | --project | --group ] + --domain | --project [--project-domain ] + --user [--user-domain ] | --group [--group-domain ] .. option:: --domain @@ -161,6 +182,27 @@ Remove role from domain/project : user/group .. versionadded:: 3 +.. option:: --user-domain + + Domain the user belongs to (name or ID). + This can be used in case collisions between user names exist. + + .. versionadded:: 3 + +.. option:: --group-domain + + Domain the group belongs to (name or ID). + This can be used in case collisions between group names exist. + + .. versionadded:: 3 + +.. option:: --project-domain + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + + .. versionadded:: 3 + .. describe:: Role to remove (name or ID) diff --git a/openstackclient/identity/common.py b/openstackclient/identity/common.py index a6e674c030..b97a17788a 100644 --- a/openstackclient/identity/common.py +++ b/openstackclient/identity/common.py @@ -43,22 +43,32 @@ def find_service(identity_client, name_type_or_id): raise exceptions.CommandError(msg) +def _get_domain_id_if_requested(identity_client, domain_name_or_id): + if not domain_name_or_id: + return None + domain = find_domain(identity_client, domain_name_or_id) + return domain.id + + def find_domain(identity_client, name_or_id): return _find_identity_resource(identity_client.domains, name_or_id, domains.Domain) -def find_group(identity_client, name_or_id, domain_id=None): +def find_group(identity_client, name_or_id, domain_name_or_id=None): + domain_id = _get_domain_id_if_requested(identity_client, domain_name_or_id) return _find_identity_resource(identity_client.groups, name_or_id, groups.Group, domain_id=domain_id) -def find_project(identity_client, name_or_id, domain_id=None): +def find_project(identity_client, name_or_id, domain_name_or_id=None): + domain_id = _get_domain_id_if_requested(identity_client, domain_name_or_id) return _find_identity_resource(identity_client.projects, name_or_id, projects.Project, domain_id=domain_id) -def find_user(identity_client, name_or_id, domain_id=None): +def find_user(identity_client, name_or_id, domain_name_or_id=None): + domain_id = _get_domain_id_if_requested(identity_client, domain_name_or_id) return _find_identity_resource(identity_client.users, name_or_id, users.User, domain_id=domain_id) diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py index 91acf3e545..b064eb777c 100644 --- a/openstackclient/identity/v3/group.py +++ b/openstackclient/identity/v3/group.py @@ -46,16 +46,32 @@ def get_parser(self, prog_name): metavar='', help='User to add to (name or ID)', ) + parser.add_argument( + '--group-domain', + metavar='', + help=('Domain the group belongs to (name or ID). ' + 'This can be used in case collisions between group names ' + 'exist.') + ) + parser.add_argument( + '--user-domain', + metavar='', + help=('Domain the user belongs to (name or ID). ' + 'This can be used in case collisions between user names ' + 'exist.') + ) return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity - user_id = utils.find_resource(identity_client.users, - parsed_args.user).id - group_id = utils.find_resource(identity_client.groups, - parsed_args.group).id + user_id = common.find_user(identity_client, + parsed_args.user, + parsed_args.user_domain).id + group_id = common.find_group(identity_client, + parsed_args.group, + parsed_args.group_domain).id try: identity_client.users.add_to_group(user_id, group_id) @@ -84,16 +100,32 @@ def get_parser(self, prog_name): metavar='', help='User to check (name or ID)', ) + parser.add_argument( + '--group-domain', + metavar='', + help=('Domain the group belongs to (name or ID). ' + 'This can be used in case collisions between group names ' + 'exist.') + ) + parser.add_argument( + '--user-domain', + metavar='', + help=('Domain the user belongs to (name or ID). ' + 'This can be used in case collisions between user names ' + 'exist.') + ) return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity - user_id = utils.find_resource(identity_client.users, - parsed_args.user).id - group_id = utils.find_resource(identity_client.groups, - parsed_args.group).id + user_id = common.find_user(identity_client, + parsed_args.user, + parsed_args.user_domain).id + group_id = common.find_group(identity_client, + parsed_args.group, + parsed_args.group_domain).id try: identity_client.users.check_in_group(user_id, group_id) @@ -184,17 +216,10 @@ def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity - domain = None - if parsed_args.domain: - domain = common.find_domain(identity_client, parsed_args.domain) for group in parsed_args.groups: - if domain is not None: - group_obj = utils.find_resource(identity_client.groups, - group, - domain_id=domain.id) - else: - group_obj = utils.find_resource(identity_client.groups, - group) + group_obj = common.find_group(identity_client, + group, + parsed_args.domain) identity_client.groups.delete(group_obj.id) return @@ -216,6 +241,13 @@ def get_parser(self, prog_name): metavar='', help='Filter group list by (name or ID)', ) + parser.add_argument( + '--user-domain', + metavar='', + help=('Domain the user belongs to (name or ID). ' + 'This can be used in case collisions between user names ' + 'exist.') + ) parser.add_argument( '--long', action='store_true', @@ -234,9 +266,10 @@ def take_action(self, parsed_args): parsed_args.domain).id if parsed_args.user: - user = utils.find_resource( - identity_client.users, + user = common.find_user( + identity_client, parsed_args.user, + parsed_args.user_domain, ).id else: user = None @@ -277,16 +310,32 @@ def get_parser(self, prog_name): metavar='', help='User to remove from (name or ID)', ) + parser.add_argument( + '--group-domain', + metavar='', + help=('Domain the group belongs to (name or ID). ' + 'This can be used in case collisions between group names ' + 'exist.') + ) + parser.add_argument( + '--user-domain', + metavar='', + help=('Domain the user belongs to (name or ID). ' + 'This can be used in case collisions between user names ' + 'exist.') + ) return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity - user_id = utils.find_resource(identity_client.users, - parsed_args.user).id - group_id = utils.find_resource(identity_client.groups, - parsed_args.group).id + user_id = common.find_user(identity_client, + parsed_args.user, + parsed_args.user_domain).id + group_id = common.find_group(identity_client, + parsed_args.group, + parsed_args.group_domain).id try: identity_client.users.remove_from_group(user_id, group_id) @@ -309,6 +358,11 @@ def get_parser(self, prog_name): 'group', metavar='', help='Group to modify (name or ID)') + parser.add_argument( + '--domain', + metavar='', + help='Domain containing (name or ID)', + ) parser.add_argument( '--name', metavar='', @@ -322,7 +376,8 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity - group = utils.find_resource(identity_client.groups, parsed_args.group) + group = common.find_group(identity_client, parsed_args.group, + parsed_args.domain) kwargs = {} if parsed_args.name: kwargs['name'] = parsed_args.name @@ -359,14 +414,9 @@ def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity - if parsed_args.domain: - domain = common.find_domain(identity_client, parsed_args.domain) - group = utils.find_resource(identity_client.groups, - parsed_args.group, - domain_id=domain.id) - else: - group = utils.find_resource(identity_client.groups, - parsed_args.group) + group = common.find_group(identity_client, + parsed_args.group, + domain_name_or_id=parsed_args.domain) group._info.pop('links') return zip(*sorted(six.iteritems(group._info))) diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index bc64f7f8b0..4f1c04d5f0 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -29,6 +29,100 @@ from openstackclient.identity import common +def _add_identity_and_resource_options_to_parser(parser): + domain_or_project = parser.add_mutually_exclusive_group() + domain_or_project.add_argument( + '--domain', + metavar='', + help='Include (name or ID)', + ) + domain_or_project.add_argument( + '--project', + metavar='', + help='Include `` (name or ID)', + ) + user_or_group = parser.add_mutually_exclusive_group() + user_or_group.add_argument( + '--user', + metavar='', + help='Include (name or ID)', + ) + user_or_group.add_argument( + '--group', + metavar='', + help='Include (name or ID)', + ) + parser.add_argument( + '--user-domain', + metavar='', + help=('Domain the user belongs to (name or ID). ' + 'This can be used in case collisions between user names ' + 'exist.') + ) + parser.add_argument( + '--group-domain', + metavar='', + help=('Domain the group belongs to (name or ID). ' + 'This can be used in case collisions between group names ' + 'exist.') + ) + parser.add_argument( + '--project-domain', + metavar='', + help=('Domain the project belongs to (name or ID). ' + 'This can be used in case collisions between project names ' + 'exist.') + ) + + +def _process_identity_and_resource_options(parsed_args, + identity_client_manager): + kwargs = {} + if parsed_args.user and parsed_args.domain: + kwargs['user'] = common.find_user( + identity_client_manager, + parsed_args.user, + parsed_args.user_domain, + ).id + kwargs['domain'] = common.find_domain( + identity_client_manager, + parsed_args.domain, + ).id + elif parsed_args.user and parsed_args.project: + kwargs['user'] = common.find_user( + identity_client_manager, + parsed_args.user, + parsed_args.user_domain, + ).id + kwargs['project'] = common.find_project( + identity_client_manager, + parsed_args.project, + parsed_args.project_domain, + ).id + elif parsed_args.group and parsed_args.domain: + kwargs['group'] = common.find_group( + identity_client_manager, + parsed_args.group, + parsed_args.group_domain, + ).id + kwargs['domain'] = common.find_domain( + identity_client_manager, + parsed_args.domain, + ).id + elif parsed_args.group and parsed_args.project: + kwargs['group'] = common.find_group( + identity_client_manager, + parsed_args.group, + parsed_args.group_domain, + ).id + kwargs['project'] = common.find_project( + identity_client_manager, + parsed_args.project, + parsed_args.group_domain, + ).id + return kwargs + + class AddRole(command.Command): """Adds a role to a user or group on a domain or project""" @@ -41,49 +135,7 @@ def get_parser(self, prog_name): metavar='', help='Role to add to (name or ID)', ) - domain_or_project = parser.add_mutually_exclusive_group() - domain_or_project.add_argument( - '--domain', - metavar='', - help='Include (name or ID)', - ) - domain_or_project.add_argument( - '--project', - metavar='', - help='Include `` (name or ID)', - ) - user_or_group = parser.add_mutually_exclusive_group() - user_or_group.add_argument( - '--user', - metavar='', - help='Include (name or ID)', - ) - user_or_group.add_argument( - '--group', - metavar='', - help='Include (name or ID)', - ) - parser.add_argument( - '--user-domain', - metavar='', - help=('Domain the user belongs to (name or ID). ' - 'This can be used in case collisions between user names ' - 'exist.') - ) - parser.add_argument( - '--group-domain', - metavar='', - help=('Domain the group belongs to (name or ID). ' - 'This can be used in case collisions between group names ' - 'exist.') - ) - parser.add_argument( - '--project-domain', - metavar='', - help=('Domain the project belongs to (name or ID). ' - 'This can be used in case collisions between project names ' - 'exist.') - ) + _add_identity_and_resource_options_to_parser(parser) return parser def take_action(self, parsed_args): @@ -99,76 +151,17 @@ def take_action(self, parsed_args): parsed_args.role, ) - kwargs = {} - if parsed_args.user and parsed_args.domain: - user_domain_id = self._get_domain_id_if_requested( - parsed_args.user_domain) - kwargs['user'] = common.find_user( - identity_client, - parsed_args.user, - user_domain_id, - ).id - kwargs['domain'] = common.find_domain( - identity_client, - parsed_args.domain, - ).id - elif parsed_args.user and parsed_args.project: - user_domain_id = self._get_domain_id_if_requested( - parsed_args.user_domain) - kwargs['user'] = common.find_user( - identity_client, - parsed_args.user, - user_domain_id, - ).id - project_domain_id = self._get_domain_id_if_requested( - parsed_args.project_domain) - kwargs['project'] = common.find_project( - identity_client, - parsed_args.project, - project_domain_id, - ).id - elif parsed_args.group and parsed_args.domain: - group_domain_id = self._get_domain_id_if_requested( - parsed_args.group_domain) - kwargs['group'] = common.find_group( - identity_client, - parsed_args.group, - group_domain_id, - ).id - kwargs['domain'] = common.find_domain( - identity_client, - parsed_args.domain, - ).id - elif parsed_args.group and parsed_args.project: - group_domain_id = self._get_domain_id_if_requested( - parsed_args.group_domain) - kwargs['group'] = common.find_group( - identity_client, - parsed_args.group, - group_domain_id, - ).id - project_domain_id = self._get_domain_id_if_requested( - parsed_args.project_domain) - kwargs['project'] = common.find_project( - identity_client, - parsed_args.project, - project_domain_id, - ).id - else: - sys.stderr.write("Role not added, incorrect set of arguments \ - provided. See openstack --help for more details\n") + kwargs = _process_identity_and_resource_options( + parsed_args, self.app.client_manager.identity) + if not kwargs: + sys.stderr.write("Role not added, incorrect set of arguments " + "provided. See openstack --help for more " + "details\n") return identity_client.roles.grant(role.id, **kwargs) return - def _get_domain_id_if_requested(self, domain_name_or_id): - if domain_name_or_id is None: - return None - domain = common.find_domain(self.app.client_manager.identity, - domain_name_or_id) - return domain.id - class CreateRole(show.ShowOne): """Create new role""" @@ -242,28 +235,7 @@ class ListRole(lister.Lister): def get_parser(self, prog_name): parser = super(ListRole, self).get_parser(prog_name) - domain_or_project = parser.add_mutually_exclusive_group() - domain_or_project.add_argument( - '--domain', - metavar='', - help='Filter roles by (name or ID)', - ) - domain_or_project.add_argument( - '--project', - metavar='', - help='Filter roles by (name or ID)', - ) - user_or_group = parser.add_mutually_exclusive_group() - user_or_group.add_argument( - '--user', - metavar='', - help='Filter roles by (name or ID)', - ) - user_or_group.add_argument( - '--group', - metavar='', - help='Filter roles by (name or ID)', - ) + _add_identity_and_resource_options_to_parser(parser) return parser def take_action(self, parsed_args): @@ -274,11 +246,13 @@ def take_action(self, parsed_args): user = common.find_user( identity_client, parsed_args.user, + parsed_args.user_domain, ) elif parsed_args.group: group = common.find_group( identity_client, parsed_args.group, + parsed_args.group_domain, ) if parsed_args.domain: @@ -290,6 +264,7 @@ def take_action(self, parsed_args): project = common.find_project( identity_client, parsed_args.project, + parsed_args.project_domain, ) # no user or group specified, list all roles in the system @@ -363,28 +338,7 @@ def get_parser(self, prog_name): metavar='', help='Role to remove (name or ID)', ) - domain_or_project = parser.add_mutually_exclusive_group() - domain_or_project.add_argument( - '--domain', - metavar='', - help='Include (name or ID)', - ) - domain_or_project.add_argument( - '--project', - metavar='', - help='Include (name or ID)', - ) - user_or_group = parser.add_mutually_exclusive_group() - user_or_group.add_argument( - '--user', - metavar='', - help='Include (name or ID)', - ) - user_or_group.add_argument( - '--group', - metavar='', - help='Include (name or ID)', - ) + _add_identity_and_resource_options_to_parser(parser) return parser def take_action(self, parsed_args): @@ -400,65 +354,14 @@ def take_action(self, parsed_args): parsed_args.role, ) - if parsed_args.user and parsed_args.domain: - user = common.find_user( - identity_client, - parsed_args.user, - ) - domain = common.find_domain( - identity_client, - parsed_args.domain, - ) - identity_client.roles.revoke( - role.id, - user=user.id, - domain=domain.id, - ) - elif parsed_args.user and parsed_args.project: - user = common.find_user( - identity_client, - parsed_args.user, - ) - project = common.find_project( - identity_client, - parsed_args.project, - ) - identity_client.roles.revoke( - role.id, - user=user.id, - project=project.id, - ) - elif parsed_args.group and parsed_args.domain: - group = common.find_group( - identity_client, - parsed_args.group, - ) - domain = common.find_domain( - identity_client, - parsed_args.domain, - ) - identity_client.roles.revoke( - role.id, - group=group.id, - domain=domain.id, - ) - elif parsed_args.group and parsed_args.project: - group = common.find_group( - identity_client, - parsed_args.group, - ) - project = common.find_project( - identity_client, - parsed_args.project, - ) - identity_client.roles.revoke( - role.id, - group=group.id, - project=project.id, - ) - else: + kwargs = _process_identity_and_resource_options( + parsed_args, self.app.client_manager.identity) + if not kwargs: sys.stderr.write("Role not removed, incorrect set of arguments \ provided. See openstack --help for more details\n") + return + + identity_client.roles.revoke(role.id, **kwargs) return From 4afd308a4f5f267cc13defbc14282ff124fdd1ee Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Mon, 8 Jun 2015 11:39:42 -0400 Subject: [PATCH 0103/3095] Include links to developer workflow documentation Newcomers aren't always sure how to get started with our tools or workflow. Lucky for them, we have documented these things. Unlucky for them, they have no idea the docs exist. Give them some links. Change-Id: I6fce6fbccb8a9fe16b48845790b4cac05317ebac --- README.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.rst b/README.rst index dce36e2dc2..1bfef94b20 100644 --- a/README.rst +++ b/README.rst @@ -15,6 +15,9 @@ language to describe operations in OpenStack. * `Blueprints`_ - feature specifications * `Bugs`_ - issue tracking * `Source`_ +* `Developer` - getting started as a developer +* `Contributing` - contributing code +* IRC: #openstack-sdks on Freenode (irc.freenode.net) * License: Apache 2.0 .. _PyPi: https://pypi.python.org/pypi/python-openstackclient @@ -23,6 +26,8 @@ language to describe operations in OpenStack. .. _Blueprints: https://blueprints.launchpad.net/python-openstackclient .. _Bugs: https://bugs.launchpad.net/python-openstackclient .. _Source: https://git.openstack.org/cgit/openstack/python-openstackclient +.. _Developer: http://docs.openstack.org/infra/manual/python.html +.. _Contributing: http://docs.openstack.org/infra/manual/developers.html Getting Started =============== From 1f1ed4cf6c3f9f6d68d1dcfaa8a46baead34e212 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Tue, 9 Jun 2015 06:01:46 +0000 Subject: [PATCH 0104/3095] Imported Translations from Transifex For more information about this automatic import see: https://wiki.openstack.org/wiki/Translations/Infrastructure Change-Id: Ib086dbeab7cf53a6b147d40594615e6c8bf1b6d1 --- .../de/LC_MESSAGES/python-openstackclient.po | 39 ++++++++++++------- .../locale/python-openstackclient.pot | 39 +++++++++++++------ .../LC_MESSAGES/python-openstackclient.po | 13 +------ 3 files changed, 54 insertions(+), 37 deletions(-) diff --git a/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po b/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po index 0de5c743c7..e836b6711c 100644 --- a/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po +++ b/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po @@ -9,8 +9,8 @@ msgid "" msgstr "" "Project-Id-Version: python-openstackclient\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-06-03 06:05+0000\n" -"PO-Revision-Date: 2015-06-01 21:54+0000\n" +"POT-Creation-Date: 2015-06-09 06:01+0000\n" +"PO-Revision-Date: 2015-06-04 16:33+0000\n" "Last-Translator: Ettore Atalan \n" "Language-Team: German (http://www.transifex.com/projects/p/python-" "openstackclient/language/de/)\n" @@ -111,6 +111,19 @@ msgstr "" "IPv4-Adresse für NIC (optional), v6-fixed-ip: Feste IPv6-Adresse für NIC " "(optional)." +msgid "" +"Create credentials for user (name or ID; default: current authenticated user)" +msgstr "" +"Anmeldedaten für Benutzer erstellen (Name oder Kennung; Standard: aktuell " +"authentifizierter Benutzer)" + +msgid "" +"Create credentials in project (name or ID; default: current authenticated " +"project)" +msgstr "" +"Anmeldedaten in Projekt erstellen (Name oder Kennung; Standard: aktuell " +"authentifiziertes Projekt)" + msgid "Create server from this image" msgstr "Server aus diesem Abbild erstellen" @@ -126,6 +139,9 @@ msgstr "Anmeldedaten-Zugriffsschlüssel" msgid "Default project (name or ID)" msgstr "Standardprojekt (Name oder Kennung)" +msgid "Delete credentials for user (name or ID)" +msgstr "Anmeldedaten für Benutzer löschen (name or ID)" + msgid "Destination port (ssh -p option)" msgstr "Zielport (ssh -p Option)" @@ -179,6 +195,9 @@ msgstr "" msgid "Filter by parent region ID" msgstr "Nach übergeordneter Regionskennung filtern" +msgid "Filter list by user (name or ID)" +msgstr "Liste nach Benutzer filtern (Name oder Kennung)" + msgid "Filter users by (name or ID)" msgstr "Benutzer nach filtern (Name oder Kennung)" @@ -503,22 +522,12 @@ msgstr "Benutzername festlegen" msgid "Set user password" msgstr "Benutzerpasswort festlegen" +msgid "Show credentials for user (name or ID)" +msgstr "Anmeldedaten für Benutzer anzeigen (name or ID)" + msgid "Show service catalog information" msgstr "Dienstkataloginformation anzeigen" -msgid "Specify a user" -msgstr "Geben Sie einen Benutzer an" - -msgid "Specify an alternate project (default: current authenticated project)" -msgstr "" -"Geben Sie ein alternatives Projekt an (Standard: aktuell authentifiziertes " -"Projekt)" - -msgid "Specify an alternate user (default: current authenticated user)" -msgstr "" -"Geben Sie einen alternativen Benutzer an (Standard: aktuell " -"authentifizierter Benutzer)" - msgid "Target hostname" msgstr "Zielhostname" diff --git a/python-openstackclient/locale/python-openstackclient.pot b/python-openstackclient/locale/python-openstackclient.pot index 5129fbf0fa..af372cd189 100644 --- a/python-openstackclient/locale/python-openstackclient.pot +++ b/python-openstackclient/locale/python-openstackclient.pot @@ -7,9 +7,9 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: python-openstackclient 1.3.1.dev26\n" +"Project-Id-Version: python-openstackclient 1.3.1.dev46\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-06-03 06:05+0000\n" +"POT-Creation-Date: 2015-06-09 06:01+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -463,22 +463,39 @@ msgid "Service to display (type or name)" msgstr "" #: openstackclient/identity/v2_0/ec2creds.py:40 -msgid "Specify an alternate project (default: current authenticated project)" +#: openstackclient/identity/v3/ec2creds.py:65 +msgid "" +"Create credentials in project (name or ID; default: current authenticated" +" project)" msgstr "" -#: openstackclient/identity/v2_0/ec2creds.py:46 -msgid "Specify an alternate user (default: current authenticated user)" +#: openstackclient/identity/v2_0/ec2creds.py:48 +#: openstackclient/identity/v3/ec2creds.py:73 +msgid "" +"Create credentials for user (name or ID; default: current authenticated " +"user)" msgstr "" -#: openstackclient/identity/v2_0/ec2creds.py:95 -#: openstackclient/identity/v2_0/ec2creds.py:168 +#: openstackclient/identity/v2_0/ec2creds.py:99 +#: openstackclient/identity/v2_0/ec2creds.py:172 +#: openstackclient/identity/v3/ec2creds.py:145 +#: openstackclient/identity/v3/ec2creds.py:219 msgid "Credentials access key" msgstr "" -#: openstackclient/identity/v2_0/ec2creds.py:100 -#: openstackclient/identity/v2_0/ec2creds.py:130 -#: openstackclient/identity/v2_0/ec2creds.py:173 -msgid "Specify a user" +#: openstackclient/identity/v2_0/ec2creds.py:104 +#: openstackclient/identity/v3/ec2creds.py:150 +msgid "Delete credentials for user (name or ID)" +msgstr "" + +#: openstackclient/identity/v2_0/ec2creds.py:134 +#: openstackclient/identity/v3/ec2creds.py:180 +msgid "Filter list by user (name or ID)" +msgstr "" + +#: openstackclient/identity/v2_0/ec2creds.py:177 +#: openstackclient/identity/v3/ec2creds.py:224 +msgid "Show credentials for user (name or ID)" msgstr "" #: openstackclient/identity/v2_0/endpoint.py:40 diff --git a/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po b/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po index 3954df9ac4..63f0b3ea98 100644 --- a/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po +++ b/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: python-openstackclient\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-06-03 06:05+0000\n" -"PO-Revision-Date: 2015-06-01 20:40+0000\n" +"POT-Creation-Date: 2015-06-09 06:01+0000\n" +"PO-Revision-Date: 2015-06-03 23:04+0000\n" "Last-Translator: openstackjenkins \n" "Language-Team: Chinese (Taiwan) (http://www.transifex.com/projects/p/python-" "openstackclient/language/zh_TW/)\n" @@ -434,15 +434,6 @@ msgstr "設定用戶密碼" msgid "Show service catalog information" msgstr "顯示伺服器分類資訊" -msgid "Specify a user" -msgstr "指定用戶" - -msgid "Specify an alternate project (default: current authenticated project)" -msgstr "指定替代的專案(預設值:目前已認證的專案)" - -msgid "Specify an alternate user (default: current authenticated user)" -msgstr "指定替代的用戶(預設值:目前已認證的用戶)" - msgid "Target hostname" msgstr "目標主機名稱" From 4fab60634983f9327295cc68edf89824557728f9 Mon Sep 17 00:00:00 2001 From: Rodrigo Duarte Sousa Date: Fri, 20 Mar 2015 17:51:02 -0300 Subject: [PATCH 0105/3095] Enables retrieval of project's parents and subtree Adds the possibility to retrieve a project and list its parents and subtree in the hierarchy. Co-Authored-By: Rodrigo Duarte Co-Authored-By: Samuel de Medeiros Queiroz Implements: bp hierarchical-multitenancy Change-Id: I874f6faffc8a2db9d99f12cbe0a69c0a30c0d9df --- doc/source/command-objects/project.rst | 12 ++ openstackclient/common/utils.py | 4 +- openstackclient/identity/v3/project.py | 37 ++++- openstackclient/tests/identity/v3/fakes.py | 19 +++ .../tests/identity/v3/test_project.py | 150 ++++++++++++++++++ 5 files changed, 213 insertions(+), 9 deletions(-) diff --git a/doc/source/command-objects/project.rst b/doc/source/command-objects/project.rst index 63796da84a..637a44986f 100644 --- a/doc/source/command-objects/project.rst +++ b/doc/source/command-objects/project.rst @@ -171,6 +171,18 @@ Display project details .. versionadded:: 3 +.. option:: --parents + + Show the project\'s parents as a list + + .. versionadded:: 3 + +.. option:: --children + + Show project\'s subtree (children) as a list + + .. versionadded:: 3 + .. _project_show-project: .. describe:: diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index aad0519c1b..c824678e3b 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -51,7 +51,7 @@ def find_resource(manager, name_or_id, **kwargs): # Try to get entity as integer id try: if isinstance(name_or_id, int) or name_or_id.isdigit(): - return manager.get(int(name_or_id)) + return manager.get(int(name_or_id), **kwargs) # FIXME(dtroyer): The exception to catch here is dependent on which # client library the manager passed in belongs to. # Eventually this should be pulled from a common set @@ -64,7 +64,7 @@ def find_resource(manager, name_or_id, **kwargs): # Try directly using the passed value try: - return manager.get(name_or_id) + return manager.get(name_or_id, **kwargs) except Exception: pass diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index 48f547f3ed..8185d65af8 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -323,6 +323,18 @@ def get_parser(self, prog_name): metavar='', help='Domain owning (name or ID)', ) + parser.add_argument( + '--parents', + action='store_true', + default=False, + help='Show the project\'s parents as a list', + ) + parser.add_argument( + '--children', + action='store_true', + default=False, + help='Show project\'s subtree (children) as a list', + ) return parser def take_action(self, parsed_args): @@ -331,14 +343,25 @@ def take_action(self, parsed_args): if parsed_args.domain: domain = common.find_domain(identity_client, parsed_args.domain) - project = utils.find_resource(identity_client.projects, - parsed_args.project, - domain_id=domain.id) + project = utils.find_resource( + identity_client.projects, + parsed_args.project, + domain_id=domain.id, + parents_as_list=parsed_args.parents, + subtree_as_list=parsed_args.children) else: - project = utils.find_resource(identity_client.projects, - parsed_args.project) + project = utils.find_resource( + identity_client.projects, + parsed_args.project, + parents_as_list=parsed_args.parents, + subtree_as_list=parsed_args.children) + + if project._info.get('parents'): + project._info['parents'] = [str(p['project']['id']) + for p in project._info['parents']] + if project._info.get('subtree'): + project._info['subtree'] = [str(p['project']['id']) + for p in project._info['subtree']] project._info.pop('links') - # TODO(stevemar): Remove the line below when we support multitenancy - project._info.pop('parent_id', None) return zip(*sorted(six.iteritems(project._info))) diff --git a/openstackclient/tests/identity/v3/fakes.py b/openstackclient/tests/identity/v3/fakes.py index dfbcf44f60..ae7a684cf0 100644 --- a/openstackclient/tests/identity/v3/fakes.py +++ b/openstackclient/tests/identity/v3/fakes.py @@ -145,6 +145,25 @@ 'links': base_url + 'projects/' + (project_id + '-with-parent'), } +PROJECT_WITH_GRANDPARENT = { + 'id': project_id + '-with-grandparent', + 'name': project_name + ', granny and grandpa', + 'description': project_description + ' plus another eight?', + 'enabled': True, + 'domain_id': domain_id, + 'parent_id': PROJECT_WITH_PARENT['id'], + 'links': base_url + 'projects/' + (project_id + '-with-grandparent'), +} + +parents = [{'project': PROJECT}] +grandparents = [{'project': PROJECT}, {'project': PROJECT_WITH_PARENT}] +ids_for_parents = [PROJECT['id']] +ids_for_parents_and_grandparents = [PROJECT['id'], PROJECT_WITH_PARENT['id']] + +children = [{'project': PROJECT_WITH_GRANDPARENT}] +ids_for_children = [PROJECT_WITH_GRANDPARENT['id']] + + role_id = 'r1' role_name = 'roller' diff --git a/openstackclient/tests/identity/v3/test_project.py b/openstackclient/tests/identity/v3/test_project.py index ebf612ccd3..946bbdcd89 100644 --- a/openstackclient/tests/identity/v3/test_project.py +++ b/openstackclient/tests/identity/v3/test_project.py @@ -783,6 +783,8 @@ def test_project_show(self): columns, data = self.cmd.take_action(parsed_args) self.projects_mock.get.assert_called_with( identity_fakes.project_id, + parents_as_list=False, + subtree_as_list=False, ) collist = ('description', 'domain_id', 'enabled', 'id', 'name') @@ -795,3 +797,151 @@ def test_project_show(self): identity_fakes.project_name, ) self.assertEqual(datalist, data) + + def test_project_show_parents(self): + project = copy.deepcopy(identity_fakes.PROJECT_WITH_GRANDPARENT) + project['parents'] = identity_fakes.grandparents + self.projects_mock.get.return_value = fakes.FakeResource( + None, + project, + loaded=True, + ) + + arglist = [ + identity_fakes.PROJECT_WITH_GRANDPARENT['id'], + '--parents', + ] + verifylist = [ + ('project', identity_fakes.PROJECT_WITH_GRANDPARENT['id']), + ('parents', True), + ('children', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.projects_mock.get.assert_called_with( + identity_fakes.PROJECT_WITH_GRANDPARENT['id'], + parents_as_list=True, + subtree_as_list=False, + ) + + collist = ( + 'description', + 'domain_id', + 'enabled', + 'id', + 'name', + 'parent_id', + 'parents', + ) + self.assertEqual(columns, collist) + datalist = ( + identity_fakes.PROJECT_WITH_GRANDPARENT['description'], + identity_fakes.PROJECT_WITH_GRANDPARENT['domain_id'], + identity_fakes.PROJECT_WITH_GRANDPARENT['enabled'], + identity_fakes.PROJECT_WITH_GRANDPARENT['id'], + identity_fakes.PROJECT_WITH_GRANDPARENT['name'], + identity_fakes.PROJECT_WITH_GRANDPARENT['parent_id'], + identity_fakes.ids_for_parents_and_grandparents, + ) + self.assertEqual(data, datalist) + + def test_project_show_subtree(self): + project = copy.deepcopy(identity_fakes.PROJECT_WITH_PARENT) + project['subtree'] = identity_fakes.children + self.projects_mock.get.return_value = fakes.FakeResource( + None, + project, + loaded=True, + ) + + arglist = [ + identity_fakes.PROJECT_WITH_PARENT['id'], + '--children', + ] + verifylist = [ + ('project', identity_fakes.PROJECT_WITH_PARENT['id']), + ('parents', False), + ('children', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.projects_mock.get.assert_called_with( + identity_fakes.PROJECT_WITH_PARENT['id'], + parents_as_list=False, + subtree_as_list=True, + ) + + collist = ( + 'description', + 'domain_id', + 'enabled', + 'id', + 'name', + 'parent_id', + 'subtree', + ) + self.assertEqual(columns, collist) + datalist = ( + identity_fakes.PROJECT_WITH_PARENT['description'], + identity_fakes.PROJECT_WITH_PARENT['domain_id'], + identity_fakes.PROJECT_WITH_PARENT['enabled'], + identity_fakes.PROJECT_WITH_PARENT['id'], + identity_fakes.PROJECT_WITH_PARENT['name'], + identity_fakes.PROJECT_WITH_PARENT['parent_id'], + identity_fakes.ids_for_children, + ) + self.assertEqual(data, datalist) + + def test_project_show_parents_and_children(self): + project = copy.deepcopy(identity_fakes.PROJECT_WITH_PARENT) + project['subtree'] = identity_fakes.children + project['parents'] = identity_fakes.parents + self.projects_mock.get.return_value = fakes.FakeResource( + None, + project, + loaded=True, + ) + + arglist = [ + identity_fakes.PROJECT_WITH_PARENT['id'], + '--parents', + '--children', + ] + verifylist = [ + ('project', identity_fakes.PROJECT_WITH_PARENT['id']), + ('parents', True), + ('children', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.projects_mock.get.assert_called_with( + identity_fakes.PROJECT_WITH_PARENT['id'], + parents_as_list=True, + subtree_as_list=True, + ) + + collist = ( + 'description', + 'domain_id', + 'enabled', + 'id', + 'name', + 'parent_id', + 'parents', + 'subtree', + ) + self.assertEqual(columns, collist) + datalist = ( + identity_fakes.PROJECT_WITH_PARENT['description'], + identity_fakes.PROJECT_WITH_PARENT['domain_id'], + identity_fakes.PROJECT_WITH_PARENT['enabled'], + identity_fakes.PROJECT_WITH_PARENT['id'], + identity_fakes.PROJECT_WITH_PARENT['name'], + identity_fakes.PROJECT_WITH_PARENT['parent_id'], + identity_fakes.ids_for_parents, + identity_fakes.ids_for_children, + ) + self.assertEqual(data, datalist) From 46f61c87b8afe586cacc12a91d3f94c89253365f Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Wed, 10 Jun 2015 11:52:52 -0600 Subject: [PATCH 0106/3095] Add functional tests for security group CRUD Change-Id: Ib5bbd46c0454d7dbb541354d515430922569c994 --- .../tests/compute/v2/test_security_group.py | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 functional/tests/compute/v2/test_security_group.py diff --git a/functional/tests/compute/v2/test_security_group.py b/functional/tests/compute/v2/test_security_group.py new file mode 100644 index 0000000000..2089b1d5dd --- /dev/null +++ b/functional/tests/compute/v2/test_security_group.py @@ -0,0 +1,57 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import uuid + +from functional.common import test + + +class SecurityGroupTests(test.TestCase): + """Functional tests for security group. """ + NAME = uuid.uuid4().hex + OTHER_NAME = uuid.uuid4().hex + HEADERS = ['Name'] + FIELDS = ['name'] + + @classmethod + def setUpClass(cls): + opts = cls.get_show_opts(cls.FIELDS) + raw_output = cls.openstack('security group create ' + cls.NAME + opts) + expected = cls.NAME + '\n' + cls.assertOutput(expected, raw_output) + + @classmethod + def tearDownClass(cls): + # Rename test + opts = cls.get_show_opts(cls.FIELDS) + raw_output = cls.openstack('security group set --name ' + + cls.OTHER_NAME + ' ' + cls.NAME + opts) + cls.assertOutput(cls.OTHER_NAME + "\n", raw_output) + # Delete test + raw_output = cls.openstack('security group delete ' + cls.OTHER_NAME) + cls.assertOutput('', raw_output) + + def test_security_group_list(self): + opts = self.get_list_opts(self.HEADERS) + raw_output = self.openstack('security group list' + opts) + self.assertIn(self.NAME, raw_output) + + def test_security_group_set(self): + opts = self.get_show_opts(['description', 'name']) + raw_output = self.openstack('security group set --description NSA ' + + self.NAME + opts) + self.assertEqual("NSA\n" + self.NAME + "\n", raw_output) + + def test_security_group_show(self): + opts = self.get_show_opts(self.FIELDS) + raw_output = self.openstack('security group show ' + self.NAME + opts) + self.assertEqual(self.NAME + "\n", raw_output) From 18991ab9a26546895f14508ffc282e807da90a0c Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 10 Jun 2015 21:27:27 +0000 Subject: [PATCH 0107/3095] Updated from global requirements Change-Id: I5b469d19ac58bcb31ebd276e1d62b3db8ccfb5a3 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ee82587b88..e11e24a133 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ os-client-config>=1.2.0 oslo.config>=1.11.0 # Apache-2.0 oslo.i18n>=1.5.0 # Apache-2.0 oslo.utils>=1.4.0 # Apache-2.0 -python-glanceclient>=0.17.1 +python-glanceclient>=0.18.0 python-keystoneclient>=1.6.0 python-novaclient>=2.22.0 python-cinderclient>=1.2.1 From f3725b4761fb4cb6765a75d6cd2c861f5af99f1a Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 11 Jun 2015 00:48:27 +0000 Subject: [PATCH 0108/3095] Updated from global requirements Change-Id: I4055698d0e4492a17623836e802ac56cd869ab0a --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e11e24a133..d420b1aa67 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,4 +17,4 @@ python-novaclient>=2.22.0 python-cinderclient>=1.2.1 python-neutronclient>=2.3.11,<3 requests>=2.5.2 -stevedore>=1.3.0 # Apache-2.0 +stevedore>=1.5.0 # Apache-2.0 From aac0d588bd83b51cd2f4b36b22741497fb39d79f Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Thu, 11 Jun 2015 02:43:57 -0400 Subject: [PATCH 0109/3095] Skip trying to set project_domain_id if not using password This is already fine for user_domain_id, and needs to be replicated for project_domain_id. Also added more logging. Change-Id: I3fa8f29edb3fc430d453bd0fc835312c0c8401f4 --- openstackclient/common/clientmanager.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index 6311c71a50..0159ad7d50 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -138,6 +138,7 @@ def setup_auth(self): # present, then do not change the behaviour. Otherwise, set the # PROJECT_DOMAIN_ID to 'OS_DEFAULT_DOMAIN' for better usability. if (self._api_version.get('identity') == '3' and + self.auth_plugin_name.endswith('password') and not self._auth_params.get('project_domain_id', None) and not self.auth_plugin_name.startswith('v2') and not self._auth_params.get('project_domain_name', None)): @@ -160,6 +161,7 @@ def setup_auth(self): self._project_name = self._auth_params['tenant_name'] LOG.info('Using auth plugin: %s' % self.auth_plugin_name) + LOG.debug('Using parameters %s' % self._auth_params) self.auth = auth_plugin.load_from_options(**self._auth_params) # needed by SAML authentication request_session = requests.session() From ec903a1f093325b9285ac028c4356fc3a5838ef3 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Thu, 11 Jun 2015 03:42:07 -0400 Subject: [PATCH 0110/3095] Add oidc plugin for listing federation projects the oidc plugin should be included in the list of valid federation protocols that can leverage `federation project list` Change-Id: I3f5c5ab262c7097273716a81618a2dcbb159dd6f --- openstackclient/identity/v3/unscoped_saml.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/identity/v3/unscoped_saml.py b/openstackclient/identity/v3/unscoped_saml.py index affbaf3a87..9b158b6752 100644 --- a/openstackclient/identity/v3/unscoped_saml.py +++ b/openstackclient/identity/v3/unscoped_saml.py @@ -25,7 +25,7 @@ from openstackclient.common import utils -UNSCOPED_AUTH_PLUGINS = ['v3unscopedsaml', 'v3unscopedadfs'] +UNSCOPED_AUTH_PLUGINS = ['v3unscopedsaml', 'v3unscopedadfs', 'v3oidc'] def auth_with_unscoped_saml(func): From b0ed8660b117878ed45d06a0f5eb47e5247f0711 Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Tue, 2 Jun 2015 08:42:11 -0600 Subject: [PATCH 0111/3095] Add flavor functional test Change-Id: I3166e1c3fb0f9b89cff8f083d30cb15e7196f59a --- functional/tests/compute/v2/test_flavor.py | 46 ++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 functional/tests/compute/v2/test_flavor.py diff --git a/functional/tests/compute/v2/test_flavor.py b/functional/tests/compute/v2/test_flavor.py new file mode 100644 index 0000000000..1ce2abd7ac --- /dev/null +++ b/functional/tests/compute/v2/test_flavor.py @@ -0,0 +1,46 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import uuid + +from functional.common import test + + +class FlavorTests(test.TestCase): + """Functional tests for flavor. """ + + NAME = uuid.uuid4().hex + HEADERS = ['Name'] + FIELDS = ['name'] + + @classmethod + def setUpClass(cls): + opts = cls.get_show_opts(cls.FIELDS) + raw_output = cls.openstack('flavor create ' + cls.NAME + opts) + expected = cls.NAME + '\n' + cls.assertOutput(expected, raw_output) + + @classmethod + def tearDownClass(cls): + raw_output = cls.openstack('flavor delete ' + cls.NAME) + cls.assertOutput('', raw_output) + + def test_flavor_list(self): + opts = self.get_list_opts(self.HEADERS) + raw_output = self.openstack('flavor list' + opts) + self.assertIn("small", raw_output) + self.assertIn(self.NAME, raw_output) + + def test_flavor_show(self): + opts = self.get_show_opts(self.FIELDS) + raw_output = self.openstack('flavor show ' + self.NAME + opts) + self.assertEqual(self.NAME + "\n", raw_output) From 83f5befc5e69700c36a5f7b032ea229d6b0fdf82 Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Thu, 11 Jun 2015 08:37:35 -0600 Subject: [PATCH 0112/3095] Add functional tests for flavor metadata Change-Id: Iae7a3f61c0c9777ee2511558d8942243066a8c60 --- functional/tests/compute/v2/test_flavor.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/functional/tests/compute/v2/test_flavor.py b/functional/tests/compute/v2/test_flavor.py index 1ce2abd7ac..becf217f6c 100644 --- a/functional/tests/compute/v2/test_flavor.py +++ b/functional/tests/compute/v2/test_flavor.py @@ -44,3 +44,13 @@ def test_flavor_show(self): opts = self.get_show_opts(self.FIELDS) raw_output = self.openstack('flavor show ' + self.NAME + opts) self.assertEqual(self.NAME + "\n", raw_output) + + def test_flavor_properties(self): + opts = self.get_show_opts(["properties"]) + raw_output = self.openstack( + 'flavor set --property a=b --property c=d ' + self.NAME + opts) + self.assertEqual("a='b', c='d'\n", raw_output) + + raw_output = self.openstack('flavor unset --property a ' + + self.NAME + opts) + self.assertEqual("c='d'\n", raw_output) From c7868451f5387f8c7d10303b268498633bbd4a2f Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Wed, 3 Jun 2015 13:40:24 -0600 Subject: [PATCH 0113/3095] Add functional tests for server CRUD Change-Id: I77f292d47a9bea6a5b486ce513c0c19ec8c845dd --- functional/tests/compute/v2/test_server.py | 55 ++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 functional/tests/compute/v2/test_server.py diff --git a/functional/tests/compute/v2/test_server.py b/functional/tests/compute/v2/test_server.py new file mode 100644 index 0000000000..bcef635f6f --- /dev/null +++ b/functional/tests/compute/v2/test_server.py @@ -0,0 +1,55 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import uuid + +from functional.common import test + + +class ServerTests(test.TestCase): + """Functional tests for server. """ + + NAME = uuid.uuid4().hex + HEADERS = ['"Name"'] + FIELDS = ['name'] + + @classmethod + def setUpClass(cls): + opts = cls.get_show_opts(cls.FIELDS) + # TODO(thowe): pull these values from clouds.yaml + flavor = '4' + image = 'cirros-0.3.4-x86_64-uec' + netid = '' + if netid: + nicargs = ' --nic net-id=' + netid + else: + nicargs = '' + raw_output = cls.openstack('server create --flavor ' + flavor + + ' --image ' + image + nicargs + ' ' + + cls.NAME + opts) + expected = cls.NAME + '\n' + cls.assertOutput(expected, raw_output) + + @classmethod + def tearDownClass(cls): + raw_output = cls.openstack('server delete ' + cls.NAME) + cls.assertOutput('', raw_output) + + def test_server_list(self): + opts = self.get_list_opts(self.HEADERS) + raw_output = self.openstack('server list' + opts) + self.assertIn(self.NAME, raw_output) + + def test_server_show(self): + opts = self.get_show_opts(self.FIELDS) + raw_output = self.openstack('server show ' + self.NAME + opts) + self.assertEqual(self.NAME + "\n", raw_output) From ebddb1005d3eda45f11eef08d83c271a657443b2 Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Thu, 11 Jun 2015 13:45:35 -0600 Subject: [PATCH 0114/3095] Add functional tests for volume set size Change-Id: Ie369c6366e1d0632ab1892fd019f5b12528c195b --- functional/tests/volume/v1/test_volume.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/functional/tests/volume/v1/test_volume.py b/functional/tests/volume/v1/test_volume.py index 4a70b77963..a0b77c7d07 100644 --- a/functional/tests/volume/v1/test_volume.py +++ b/functional/tests/volume/v1/test_volume.py @@ -69,3 +69,10 @@ def test_volume_set(self): opts = self.get_show_opts(["display_description", "display_name"]) raw_output = self.openstack('volume show ' + self.NAME + opts) self.assertEqual("RAMAC\n" + self.NAME + "\n", raw_output) + + def test_volume_set_size(self): + raw_output = self.openstack( + 'volume set --size 2 ' + self.NAME) + opts = self.get_show_opts(["display_name", "size"]) + raw_output = self.openstack('volume show ' + self.NAME + opts) + self.assertEqual(self.NAME + "\n2\n", raw_output) From b3289601c7123f412d043f16fc342ff9bfdf72b7 Mon Sep 17 00:00:00 2001 From: Dave Chen Date: Sun, 14 Jun 2015 14:59:38 +0800 Subject: [PATCH 0115/3095] Fix the typo in `openstackclient/shell.py` Change-Id: Ia101f6d50ecd4a195aa93b1f289def581b0c6f38 --- openstackclient/shell.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 36483b3a7e..4109b8bc26 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -43,7 +43,7 @@ def prompt_for_password(prompt=None): """Prompt user for a password - Propmpt for a password if stdin is a tty. + Prompt for a password if stdin is a tty. """ if not prompt: @@ -228,7 +228,7 @@ def initialize_app(self, argv): # Parent __init__ parses argv into self.options super(OpenStackShell, self).initialize_app(argv) - # Set the default plugin to token_endpoint if rl and token are given + # Set the default plugin to token_endpoint if url and token are given if (self.options.url and self.options.token): # Use service token authentication cloud_config.set_default('auth_type', 'token_endpoint') From 9f69b43f5aa7b001b3f2681209564d1152309bb2 Mon Sep 17 00:00:00 2001 From: Dave Chen Date: Sun, 14 Jun 2015 21:15:58 +0800 Subject: [PATCH 0116/3095] Improve the hint message Currently, we can get scoped token (domain scoped, project scoped) as well as unscoped token. When we use OSC to get a domain scoped token without explicitly set domain information, the hint message show us we need to set a scoped domain or project, but it miss that the parameters to be set in order to get project or domain scoped token is not the same. Thus, the hint message could be improved to make it more clear to end user. Change-Id: I94768c619b30be18737fec189ae6d81e81ba090d --- openstackclient/api/auth.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openstackclient/api/auth.py b/openstackclient/api/auth.py index 1d50f92ca3..820b4ecffa 100644 --- a/openstackclient/api/auth.py +++ b/openstackclient/api/auth.py @@ -152,8 +152,10 @@ def check_valid_auth_options(options, auth_plugin_name): options.auth.get('project_name', None) and not options.auth.get('tenant_id', None) and not options.auth.get('tenant_name', None)): - msg += _('Set a scope, such as a project or domain, with ' - '--os-project-name, OS_PROJECT_NAME or auth.project_name') + msg += _('Set a scope, such as a project or domain, set a ' + 'project scope with --os-project-name, OS_PROJECT_NAME ' + 'or auth.project_name, set a domain scope with ' + '--os-domain-name, OS_DOMAIN_NAME or auth.domain_name') elif auth_plugin_name.endswith('token'): if not options.auth.get('token', None): msg += _('Set a token with --os-token, OS_TOKEN or auth.token\n') From dca47a7bf29c1b0863e823bf058791f0fd8ed4cf Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Mon, 15 Jun 2015 03:47:16 +0000 Subject: [PATCH 0117/3095] Updated from global requirements Change-Id: I635a3ff9d1a90d0b1bb1e295a380eddd9d30a295 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index d420b1aa67..11d2a39f47 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,7 +14,7 @@ oslo.utils>=1.4.0 # Apache-2.0 python-glanceclient>=0.18.0 python-keystoneclient>=1.6.0 python-novaclient>=2.22.0 -python-cinderclient>=1.2.1 +python-cinderclient>=1.2.2 python-neutronclient>=2.3.11,<3 requests>=2.5.2 stevedore>=1.5.0 # Apache-2.0 From ce65164155dcf6dddf7695fbc5ca6352c121cc04 Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Wed, 3 Jun 2015 17:18:38 -0600 Subject: [PATCH 0118/3095] Add functional tests for image set This patch includes functional tests for image set and it includes a change to use the OSC utils.format_dict method to format the properties. This will give a more user friendly format to the image commands and it gives a more consistent testable format to the output. Instead of: {u'a': u'b', u'c': u'd'} The user will see: a=b, c=d Change-Id: Ib396316586ffc5dbab231064d5b6dc9425507934 --- functional/tests/image/v1/test_image.py | 23 ++++++++++++++++++++++- openstackclient/image/v1/image.py | 3 +++ openstackclient/tests/image/v1/fakes.py | 6 ++++-- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/functional/tests/image/v1/test_image.py b/functional/tests/image/v1/test_image.py index e27ab24cd1..9f6ddcc5df 100644 --- a/functional/tests/image/v1/test_image.py +++ b/functional/tests/image/v1/test_image.py @@ -19,6 +19,7 @@ class ImageTests(test.TestCase): """Functional tests for image. """ NAME = uuid.uuid4().hex + OTHER_NAME = uuid.uuid4().hex HEADERS = ['Name'] FIELDS = ['name'] @@ -31,7 +32,13 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - raw_output = cls.openstack('image delete ' + cls.NAME) + # Rename test + opts = cls.get_show_opts(cls.FIELDS) + raw_output = cls.openstack( + 'image set --name ' + cls.OTHER_NAME + ' ' + cls.NAME + opts) + cls.assertOutput(cls.OTHER_NAME + "\n", raw_output) + # Delete test + raw_output = cls.openstack('image delete ' + cls.OTHER_NAME) cls.assertOutput('', raw_output) def test_image_list(self): @@ -43,3 +50,17 @@ def test_image_show(self): opts = self.get_show_opts(self.FIELDS) raw_output = self.openstack('image show ' + self.NAME + opts) self.assertEqual(self.NAME + "\n", raw_output) + + def test_image_set(self): + opts = self.get_show_opts([ + "disk_format", "is_public", "min_disk", "min_ram", "name"]) + raw_output = self.openstack('image set --min-disk 4 --min-ram 5 ' + + '--disk-format qcow2 --public ' + + self.NAME + opts) + self.assertEqual("qcow2\nTrue\n4\n5\n" + self.NAME + '\n', raw_output) + + def test_image_metadata(self): + opts = self.get_show_opts(["name", "properties"]) + raw_output = self.openstack( + 'image set --property a=b --property c=d ' + self.NAME + opts) + self.assertEqual(self.NAME + "\na='b', c='d'\n", raw_output) diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index d4d45fa287..85a9e07683 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -275,6 +275,7 @@ def take_action(self, parsed_args): info = {} info.update(image._info) + info['properties'] = utils.format_dict(info.get('properties', {})) return zip(*sorted(six.iteritems(info))) @@ -608,6 +609,7 @@ def take_action(self, parsed_args): info = {} info.update(image._info) + info['properties'] = utils.format_dict(info.get('properties', {})) return zip(*sorted(six.iteritems(info))) @@ -636,4 +638,5 @@ def take_action(self, parsed_args): info = {} info.update(image._info) + info['properties'] = utils.format_dict(info.get('properties', {})) return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/tests/image/v1/fakes.py b/openstackclient/tests/image/v1/fakes.py index 972e641589..95a8a39ceb 100644 --- a/openstackclient/tests/image/v1/fakes.py +++ b/openstackclient/tests/image/v1/fakes.py @@ -30,7 +30,7 @@ 'Beta': 'b', 'Gamma': 'g', } -image_properties_str = "{'Alpha': 'a', 'Beta': 'b', 'Gamma': 'g'}" +image_properties_str = "Alpha='a', Beta='b', Gamma='g'" image_data = 'line 1\nline 2\n' IMAGE = { @@ -47,7 +47,9 @@ } IMAGE_columns = tuple(sorted(IMAGE)) -IMAGE_data = tuple((IMAGE[x] for x in sorted(IMAGE))) +IMAGE_output = dict(IMAGE) +IMAGE_output['properties'] = image_properties_str +IMAGE_data = tuple((IMAGE_output[x] for x in sorted(IMAGE_output))) class FakeImagev1Client(object): From 258f4ca6e6ca7fc67c746b77ab23a12065a0660a Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Tue, 16 Jun 2015 11:02:09 -0400 Subject: [PATCH 0119/3095] reference corect ec2 helper function ec2creds.py was referencing a function on self, but wasn't there. Correctly reference the right function. Change-Id: I62f09c497be9dbb394341914388d60634e8b80c2 Closes-Bug: 1465561 --- openstackclient/identity/v3/ec2creds.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openstackclient/identity/v3/ec2creds.py b/openstackclient/identity/v3/ec2creds.py index f995ae5553..3c41ba36d5 100644 --- a/openstackclient/identity/v3/ec2creds.py +++ b/openstackclient/identity/v3/ec2creds.py @@ -99,7 +99,7 @@ def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity client_manager = self.app.client_manager - user = self.determine_ec2_user(parsed_args, client_manager) + user = _determine_ec2_user(parsed_args, client_manager) project_domain = None if parsed_args.project_domain: @@ -163,7 +163,7 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) client_manager = self.app.client_manager - user = self.determine_ec2_user(parsed_args, client_manager) + user = _determine_ec2_user(parsed_args, client_manager) client_manager.identity.ec2.delete(user, parsed_args.access_key) @@ -193,7 +193,7 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) client_manager = self.app.client_manager - user = self.determine_ec2_user(parsed_args, client_manager) + user = _determine_ec2_user(parsed_args, client_manager) columns = ('access', 'secret', 'tenant_id', 'user_id') column_headers = ('Access', 'Secret', 'Project ID', 'User ID') @@ -237,7 +237,7 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) client_manager = self.app.client_manager - user = self.determine_ec2_user(parsed_args, client_manager) + user = _determine_ec2_user(parsed_args, client_manager) creds = client_manager.identity.ec2.get(user, parsed_args.access_key) info = {} From 291ba625f1efb8e06d4dccda2eca6621aa5de44d Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Tue, 16 Jun 2015 11:14:19 -0400 Subject: [PATCH 0120/3095] Add release notes for 1.5.0 Change-Id: Idb6941feb3c17694eb5d7dcf1189bb1a08336810 --- doc/source/releases.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/source/releases.rst b/doc/source/releases.rst index 2d6224c618..c2694c0498 100644 --- a/doc/source/releases.rst +++ b/doc/source/releases.rst @@ -2,6 +2,12 @@ Release Notes ============= +1.5.0 (16 Jun 2015) +=================== + +* openstack 'ListEC2Creds' object has no attribute 'determine_ec2_user' + Bug `1465561 `_ + 1.4.0 (04 Jun 2015) =================== From 383e12e5c54655947cf5615be910d8362b469312 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Tue, 16 Jun 2015 19:23:16 +0000 Subject: [PATCH 0121/3095] Updated from global requirements Change-Id: Ib17c91ef8395b1a4f490765896bffad166aa0ab6 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 11d2a39f47..35084ef608 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ cliff-tablib>=1.0 os-client-config>=1.2.0 oslo.config>=1.11.0 # Apache-2.0 oslo.i18n>=1.5.0 # Apache-2.0 -oslo.utils>=1.4.0 # Apache-2.0 +oslo.utils>=1.6.0 # Apache-2.0 python-glanceclient>=0.18.0 python-keystoneclient>=1.6.0 python-novaclient>=2.22.0 From 2b0013c5c1afe6d2fee5f93cf6928f6f910048c1 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Tue, 9 Jun 2015 17:25:12 -0400 Subject: [PATCH 0122/3095] Refactor option handling for user|group|project domain scoping put the common options in identity.common, this way the help is consistent Change-Id: I5b09cfb56fa0f8d16feb95150f216fccbe9f2b22 --- .../command-objects/ec2-credentials.rst | 9 ++- doc/source/command-objects/trust.rst | 9 +-- openstackclient/identity/common.py | 30 ++++++++++ openstackclient/identity/v3/ec2creds.py | 50 ++--------------- openstackclient/identity/v3/group.py | 56 +++---------------- openstackclient/identity/v3/role.py | 24 +------- openstackclient/identity/v3/trust.py | 10 +--- 7 files changed, 57 insertions(+), 131 deletions(-) diff --git a/doc/source/command-objects/ec2-credentials.rst b/doc/source/command-objects/ec2-credentials.rst index f8e3856485..6748422e14 100644 --- a/doc/source/command-objects/ec2-credentials.rst +++ b/doc/source/command-objects/ec2-credentials.rst @@ -28,19 +28,18 @@ Create EC2 credentials .. option:: --user-domain - Select user from a specific domain (name or ID) - This can be used in case collisions between user names exist. + Domain the user belongs to (name or ID). This can be + used in case collisions between user names exist. .. versionadded:: 3 .. option:: --project-domain - Select project from a specific domain (name or ID) - This can be used in case collisions between project names exist. + Domain the project belongs to (name or ID). This can be + used in case collisions between user names exist. .. versionadded:: 3 - The :option:`--project` and :option:`--user` options are typically only useful for admin users, but may be allowed for other users depending on the policy of the cloud and the roles granted to the user. diff --git a/doc/source/command-objects/trust.rst b/doc/source/command-objects/trust.rst index c5e16b464c..556edc54d4 100644 --- a/doc/source/command-objects/trust.rst +++ b/doc/source/command-objects/trust.rst @@ -39,15 +39,16 @@ Create new trust Sets an expiration date for the trust (format of YYYY-mm-ddTHH:MM:SS) -.. option:: --project-domain +.. option:: --project-domain - Domain that contains (name or ID) + Domain the project belongs to (name or ID). This can be + used in case collisions between user names exist. -.. option:: --trustor-domain +.. option:: --trustor-domain Domain that contains (name or ID) -.. option:: --trustee-domain +.. option:: --trustee-domain Domain that contains (name or ID) diff --git a/openstackclient/identity/common.py b/openstackclient/identity/common.py index b97a17788a..6eca02bad8 100644 --- a/openstackclient/identity/common.py +++ b/openstackclient/identity/common.py @@ -109,3 +109,33 @@ def _find_identity_resource(identity_client_manager, name_or_id, pass return resource_type(None, {'id': name_or_id, 'name': name_or_id}) + + +def add_user_domain_option_to_parser(parser): + parser.add_argument( + '--user-domain', + metavar='', + help=('Domain the user belongs to (name or ID). ' + 'This can be used in case collisions between user names ' + 'exist.') + ) + + +def add_group_domain_option_to_parser(parser): + parser.add_argument( + '--group-domain', + metavar='', + help=('Domain the group belongs to (name or ID). ' + 'This can be used in case collisions between group names ' + 'exist.') + ) + + +def add_project_domain_option_to_parser(parser): + parser.add_argument( + '--project-domain', + metavar='', + help=('Domain the project belongs to (name or ID). ' + 'This can be used in case collisions between project names ' + 'exist.') + ) diff --git a/openstackclient/identity/v3/ec2creds.py b/openstackclient/identity/v3/ec2creds.py index f995ae5553..b518b37078 100644 --- a/openstackclient/identity/v3/ec2creds.py +++ b/openstackclient/identity/v3/ec2creds.py @@ -75,24 +75,8 @@ def get_parser(self, prog_name): '(name or ID; default: current authenticated user)' ), ) - parser.add_argument( - '--user-domain', - metavar='', - help=( - 'Select user from a specific domain (name or ID); ' - 'This can be used in case collisions between user names ' - 'exist.' - ), - ) - parser.add_argument( - '--project-domain', - metavar='', - help=( - 'Select project from a specific domain (name or ID); ' - 'This can be used in case collisions between project names ' - 'exist.' - ), - ) + common.add_user_domain_option_to_parser(parser) + common.add_project_domain_option_to_parser(parser) return parser def take_action(self, parsed_args): @@ -149,15 +133,7 @@ def get_parser(self, prog_name): metavar='', help=_('Delete credentials for user (name or ID)'), ) - parser.add_argument( - '--user-domain', - metavar='', - help=( - 'Select user from a specific domain (name or ID); ' - 'This can be used in case collisions between user names ' - 'exist.' - ), - ) + common.add_user_domain_option_to_parser(parser) return parser def take_action(self, parsed_args): @@ -179,15 +155,7 @@ def get_parser(self, prog_name): metavar='', help=_('Filter list by user (name or ID)'), ) - parser.add_argument( - '--user-domain', - metavar='', - help=( - 'Select user from a specific domain (name or ID); ' - 'This can be used in case collisions between user names ' - 'exist.' - ), - ) + common.add_user_domain_option_to_parser(parser) return parser def take_action(self, parsed_args): @@ -223,15 +191,7 @@ def get_parser(self, prog_name): metavar='', help=_('Show credentials for user (name or ID)'), ) - parser.add_argument( - '--user-domain', - metavar='', - help=( - 'Select user from a specific domain (name or ID); ' - 'This can be used in case collisions between user names ' - 'exist.' - ), - ) + common.add_user_domain_option_to_parser(parser) return parser def take_action(self, parsed_args): diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py index b064eb777c..d659f71e4a 100644 --- a/openstackclient/identity/v3/group.py +++ b/openstackclient/identity/v3/group.py @@ -46,20 +46,8 @@ def get_parser(self, prog_name): metavar='', help='User to add to (name or ID)', ) - parser.add_argument( - '--group-domain', - metavar='', - help=('Domain the group belongs to (name or ID). ' - 'This can be used in case collisions between group names ' - 'exist.') - ) - parser.add_argument( - '--user-domain', - metavar='', - help=('Domain the user belongs to (name or ID). ' - 'This can be used in case collisions between user names ' - 'exist.') - ) + common.add_group_domain_option_to_parser(parser) + common.add_user_domain_option_to_parser(parser) return parser def take_action(self, parsed_args): @@ -100,20 +88,8 @@ def get_parser(self, prog_name): metavar='', help='User to check (name or ID)', ) - parser.add_argument( - '--group-domain', - metavar='', - help=('Domain the group belongs to (name or ID). ' - 'This can be used in case collisions between group names ' - 'exist.') - ) - parser.add_argument( - '--user-domain', - metavar='', - help=('Domain the user belongs to (name or ID). ' - 'This can be used in case collisions between user names ' - 'exist.') - ) + common.add_group_domain_option_to_parser(parser) + common.add_user_domain_option_to_parser(parser) return parser def take_action(self, parsed_args): @@ -241,13 +217,7 @@ def get_parser(self, prog_name): metavar='', help='Filter group list by (name or ID)', ) - parser.add_argument( - '--user-domain', - metavar='', - help=('Domain the user belongs to (name or ID). ' - 'This can be used in case collisions between user names ' - 'exist.') - ) + common.add_user_domain_option_to_parser(parser) parser.add_argument( '--long', action='store_true', @@ -310,20 +280,8 @@ def get_parser(self, prog_name): metavar='', help='User to remove from (name or ID)', ) - parser.add_argument( - '--group-domain', - metavar='', - help=('Domain the group belongs to (name or ID). ' - 'This can be used in case collisions between group names ' - 'exist.') - ) - parser.add_argument( - '--user-domain', - metavar='', - help=('Domain the user belongs to (name or ID). ' - 'This can be used in case collisions between user names ' - 'exist.') - ) + common.add_group_domain_option_to_parser(parser) + common.add_user_domain_option_to_parser(parser) return parser def take_action(self, parsed_args): diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index 4f1c04d5f0..17f47ffd63 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -52,27 +52,9 @@ def _add_identity_and_resource_options_to_parser(parser): metavar='', help='Include (name or ID)', ) - parser.add_argument( - '--user-domain', - metavar='', - help=('Domain the user belongs to (name or ID). ' - 'This can be used in case collisions between user names ' - 'exist.') - ) - parser.add_argument( - '--group-domain', - metavar='', - help=('Domain the group belongs to (name or ID). ' - 'This can be used in case collisions between group names ' - 'exist.') - ) - parser.add_argument( - '--project-domain', - metavar='', - help=('Domain the project belongs to (name or ID). ' - 'This can be used in case collisions between project names ' - 'exist.') - ) + common.add_group_domain_option_to_parser(parser) + common.add_project_domain_option_to_parser(parser) + common.add_user_domain_option_to_parser(parser) def _process_identity_and_resource_options(parsed_args, diff --git a/openstackclient/identity/v3/trust.py b/openstackclient/identity/v3/trust.py index ab6673d2f8..c8e5c4c729 100644 --- a/openstackclient/identity/v3/trust.py +++ b/openstackclient/identity/v3/trust.py @@ -71,19 +71,15 @@ def get_parser(self, prog_name): help='Sets an expiration date for the trust' ' (format of YYYY-mm-ddTHH:MM:SS)', ) - parser.add_argument( - '--project-domain', - metavar='', - help='Domain that contains (name or ID)', - ) + common.add_project_domain_option_to_parser(parser) parser.add_argument( '--trustor-domain', - metavar='', + metavar='', help='Domain that contains (name or ID)', ) parser.add_argument( '--trustee-domain', - metavar='', + metavar='', help='Domain that contains (name or ID)', ) return parser From 7354d600e269b540ad49cffc0b17090d6ac994ae Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Tue, 9 Jun 2015 14:34:18 -0500 Subject: [PATCH 0123/3095] Update 1.4.0 release notes Depends-On: I5b469d19ac58bcb31ebd276e1d62b3db8ccfb5a3 Change-Id: I67daab9045852a5d23d69f9b4290e857f5137121 --- doc/source/releases.rst | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/doc/source/releases.rst b/doc/source/releases.rst index c2694c0498..b8402cc932 100644 --- a/doc/source/releases.rst +++ b/doc/source/releases.rst @@ -8,12 +8,15 @@ Release Notes * openstack 'ListEC2Creds' object has no attribute 'determine_ec2_user' Bug `1465561 `_ -1.4.0 (04 Jun 2015) +1.4.0 (11 Jun 2015) =================== * AttributeError: 'Client' object has no attribute 'ec2' Bug `1236326 `_ +* Group/role identity v3 commands have no option for domain to operate on + Bug `1446546 `_ + * ``--insecure`` is ignored if ``OS_CACERT`` env var is set Bug `1447784 `_ @@ -35,6 +38,9 @@ Release Notes * Cannot force v2password auth plugin Bug `1460369 `_ +* Let's not use the deprecated argument + Bug `1462389 `_ + 1.3.0 (27 May 2015) =================== From ce67c3afa506d002c26df72f99b008bdc9bd5787 Mon Sep 17 00:00:00 2001 From: Amey Bhide Date: Thu, 18 Jun 2015 23:46:41 -0700 Subject: [PATCH 0124/3095] Fix typo in user.rst Change name -> user-name Closes-Bug: #1466738 Change-Id: I63d6b4f5ac3cacff624c2e951873aa044a699234 --- doc/source/command-objects/user.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/command-objects/user.rst b/doc/source/command-objects/user.rst index 79807b23bf..8aec76b40d 100644 --- a/doc/source/command-objects/user.rst +++ b/doc/source/command-objects/user.rst @@ -65,7 +65,7 @@ Create new user If the username already exist return the existing user data and do not fail. -.. describe:: +.. describe:: New user name From 012e6a762897864dc54a9d1a0270b46dde64951f Mon Sep 17 00:00:00 2001 From: Guojian Shao Date: Fri, 19 Jun 2015 15:11:12 +0800 Subject: [PATCH 0125/3095] fix typo for server create in server.rst we have in server create option, while in the explanation, they both should be . Closes-Bug: #1466742 Change-Id: I26a25f57e57d8f9e19ec9c9ccb3c2b8d5396b78d --- doc/source/command-objects/server.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index 10ab306502..eb61bab549 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -60,7 +60,7 @@ Create a new server os server create --image | --volume --flavor - [--security-group [...] ] + [--security-group [...] ] [--key-name ] [--property [...] ] [--file ] [...] ] From 2d6bc8f4c38dbf997e3e71119f13f0328b4a8669 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Mon, 22 Jun 2015 08:28:06 +0000 Subject: [PATCH 0126/3095] Updated from global requirements Change-Id: Ie4f93534ec504e7672c88ab02efc8747df91318c --- requirements.txt | 14 +++++++------- setup.py | 1 - test-requirements.txt | 12 ++++++------ 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/requirements.txt b/requirements.txt index 35084ef608..8b241e0db8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,20 +1,20 @@ # The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -pbr>=0.11,<2.0 +pbr<2.0,>=0.11 six>=1.9.0 Babel>=1.3 -cliff>=1.10.0 # Apache-2.0 +cliff>=1.10.0 # Apache-2.0 cliff-tablib>=1.0 os-client-config>=1.2.0 -oslo.config>=1.11.0 # Apache-2.0 -oslo.i18n>=1.5.0 # Apache-2.0 -oslo.utils>=1.6.0 # Apache-2.0 +oslo.config>=1.11.0 # Apache-2.0 +oslo.i18n>=1.5.0 # Apache-2.0 +oslo.utils>=1.6.0 # Apache-2.0 python-glanceclient>=0.18.0 python-keystoneclient>=1.6.0 python-novaclient>=2.22.0 python-cinderclient>=1.2.2 -python-neutronclient>=2.3.11,<3 +python-neutronclient<3,>=2.3.11 requests>=2.5.2 -stevedore>=1.5.0 # Apache-2.0 +stevedore>=1.5.0 # Apache-2.0 diff --git a/setup.py b/setup.py index 736375744d..056c16c2b8 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test-requirements.txt b/test-requirements.txt index f82e48d74b..ce3fcd73aa 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,17 +1,17 @@ # The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -hacking>=0.10.0,<0.11 +hacking<0.11,>=0.10.0 coverage>=3.6 discover fixtures>=0.3.14 mock>=1.0 -oslosphinx>=2.5.0 # Apache-2.0 -oslotest>=1.5.1 # Apache-2.0 -requests-mock>=0.6.0 # Apache-2.0 -sphinx>=1.1.2,!=1.2.0,!=1.3b1,<1.3 +oslosphinx>=2.5.0 # Apache-2.0 +oslotest>=1.5.1 # Apache-2.0 +requests-mock>=0.6.0 # Apache-2.0 +sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 os-testr>=0.1.0 testrepository>=0.0.18 -testtools>=0.9.36,!=1.2.0 +testtools>=1.4.0 WebOb>=1.2.3 From ed241ef9bc4e36137ecf835c545fdd44dddc426f Mon Sep 17 00:00:00 2001 From: Samuel de Medeiros Queiroz Date: Fri, 19 Sep 2014 12:29:39 -0300 Subject: [PATCH 0127/3095] Add support to inherited project role grant calls Once inherited project role grant calls are implemented on python-keystoneclient, python-openstackclient also should support such calls. This patch add such support as well as its related tests. Co-Authored-By: Raildo Mascena Change-Id: Id72670be8640e5c6e2490a6ef849e9ec3493b1a9 Implements: blueprint hierarchical-multitenancy --- .../command-objects/role_assignment.rst | 5 ++ openstackclient/identity/common.py | 9 ++++ openstackclient/identity/v3/role.py | 2 + .../tests/identity/v3/test_role.py | 46 +++++++++++++++++++ 4 files changed, 62 insertions(+) diff --git a/doc/source/command-objects/role_assignment.rst b/doc/source/command-objects/role_assignment.rst index 749d883e54..cfb1079cf3 100644 --- a/doc/source/command-objects/role_assignment.rst +++ b/doc/source/command-objects/role_assignment.rst @@ -19,6 +19,7 @@ List role assignments [--domain ] [--project ] [--effective] + [--inherited] .. option:: --role @@ -43,3 +44,7 @@ List role assignments .. option:: --effective Returns only effective role assignments (defaults to False) + +.. option:: --inherited + + Specifies if the role grant is inheritable to the sub projects diff --git a/openstackclient/identity/common.py b/openstackclient/identity/common.py index 6eca02bad8..2638b7976e 100644 --- a/openstackclient/identity/common.py +++ b/openstackclient/identity/common.py @@ -139,3 +139,12 @@ def add_project_domain_option_to_parser(parser): 'This can be used in case collisions between project names ' 'exist.') ) + + +def add_inherited_option_to_parser(parser): + parser.add_argument( + '--inherited', + action='store_true', + default=False, + help=('Specifies if the role grant is inheritable to the sub projects') + ) diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index 17f47ffd63..199b7dcaa1 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -55,6 +55,7 @@ def _add_identity_and_resource_options_to_parser(parser): common.add_group_domain_option_to_parser(parser) common.add_project_domain_option_to_parser(parser) common.add_user_domain_option_to_parser(parser) + common.add_inherited_option_to_parser(parser) def _process_identity_and_resource_options(parsed_args, @@ -102,6 +103,7 @@ def _process_identity_and_resource_options(parsed_args, parsed_args.project, parsed_args.group_domain, ).id + kwargs['inherited'] = parsed_args.inherited return kwargs diff --git a/openstackclient/tests/identity/v3/test_role.py b/openstackclient/tests/identity/v3/test_role.py index 84cf07a185..4ff3b95f63 100644 --- a/openstackclient/tests/identity/v3/test_role.py +++ b/openstackclient/tests/identity/v3/test_role.py @@ -45,6 +45,15 @@ def setUp(self): self.roles_mock = self.app.client_manager.identity.roles self.roles_mock.reset_mock() + def _is_inheritance_testcase(self): + return False + + +class TestRoleInherited(TestRole): + + def _is_inheritance_testcase(self): + return True + class TestRoleAdd(TestRole): @@ -95,12 +104,15 @@ def test_role_add_user_domain(self): '--domain', identity_fakes.domain_name, identity_fakes.role_name, ] + if self._is_inheritance_testcase(): + arglist.append('--inherited') verifylist = [ ('user', identity_fakes.user_name), ('group', None), ('domain', identity_fakes.domain_name), ('project', None), ('role', identity_fakes.role_name), + ('inherited', self._is_inheritance_testcase()), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -111,6 +123,7 @@ def test_role_add_user_domain(self): kwargs = { 'user': identity_fakes.user_id, 'domain': identity_fakes.domain_id, + 'inherited': self._is_inheritance_testcase(), } # RoleManager.grant(role, user=, group=, domain=, project=) self.roles_mock.grant.assert_called_with( @@ -124,12 +137,15 @@ def test_role_add_user_project(self): '--project', identity_fakes.project_name, identity_fakes.role_name, ] + if self._is_inheritance_testcase(): + arglist.append('--inherited') verifylist = [ ('user', identity_fakes.user_name), ('group', None), ('domain', None), ('project', identity_fakes.project_name), ('role', identity_fakes.role_name), + ('inherited', self._is_inheritance_testcase()), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -140,6 +156,7 @@ def test_role_add_user_project(self): kwargs = { 'user': identity_fakes.user_id, 'project': identity_fakes.project_id, + 'inherited': self._is_inheritance_testcase(), } # RoleManager.grant(role, user=, group=, domain=, project=) self.roles_mock.grant.assert_called_with( @@ -153,12 +170,15 @@ def test_role_add_group_domain(self): '--domain', identity_fakes.domain_name, identity_fakes.role_name, ] + if self._is_inheritance_testcase(): + arglist.append('--inherited') verifylist = [ ('user', None), ('group', identity_fakes.group_name), ('domain', identity_fakes.domain_name), ('project', None), ('role', identity_fakes.role_name), + ('inherited', self._is_inheritance_testcase()), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -169,6 +189,7 @@ def test_role_add_group_domain(self): kwargs = { 'group': identity_fakes.group_id, 'domain': identity_fakes.domain_id, + 'inherited': self._is_inheritance_testcase(), } # RoleManager.grant(role, user=, group=, domain=, project=) self.roles_mock.grant.assert_called_with( @@ -182,12 +203,15 @@ def test_role_add_group_project(self): '--project', identity_fakes.project_name, identity_fakes.role_name, ] + if self._is_inheritance_testcase(): + arglist.append('--inherited') verifylist = [ ('user', None), ('group', identity_fakes.group_name), ('domain', None), ('project', identity_fakes.project_name), ('role', identity_fakes.role_name), + ('inherited', self._is_inheritance_testcase()), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -198,6 +222,7 @@ def test_role_add_group_project(self): kwargs = { 'group': identity_fakes.group_id, 'project': identity_fakes.project_id, + 'inherited': self._is_inheritance_testcase(), } # RoleManager.grant(role, user=, group=, domain=, project=) self.roles_mock.grant.assert_called_with( @@ -206,6 +231,10 @@ def test_role_add_group_project(self): ) +class TestRoleAddInherited(TestRoleAdd, TestRoleInherited): + pass + + class TestRoleCreate(TestRole): def setUp(self): @@ -550,12 +579,15 @@ def test_role_remove_user_domain(self): '--domain', identity_fakes.domain_name, identity_fakes.role_name, ] + if self._is_inheritance_testcase(): + arglist.append('--inherited') verifylist = [ ('user', identity_fakes.user_name), ('group', None), ('domain', identity_fakes.domain_name), ('project', None), ('role', identity_fakes.role_name), + ('inherited', self._is_inheritance_testcase()), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -566,6 +598,7 @@ def test_role_remove_user_domain(self): kwargs = { 'user': identity_fakes.user_id, 'domain': identity_fakes.domain_id, + 'inherited': self._is_inheritance_testcase(), } # RoleManager.revoke(role, user=, group=, domain=, project=) self.roles_mock.revoke.assert_called_with( @@ -579,12 +612,15 @@ def test_role_remove_user_project(self): '--project', identity_fakes.project_name, identity_fakes.role_name, ] + if self._is_inheritance_testcase(): + arglist.append('--inherited') verifylist = [ ('user', identity_fakes.user_name), ('group', None), ('domain', None), ('project', identity_fakes.project_name), ('role', identity_fakes.role_name), + ('inherited', self._is_inheritance_testcase()), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -595,6 +631,7 @@ def test_role_remove_user_project(self): kwargs = { 'user': identity_fakes.user_id, 'project': identity_fakes.project_id, + 'inherited': self._is_inheritance_testcase(), } # RoleManager.revoke(role, user=, group=, domain=, project=) self.roles_mock.revoke.assert_called_with( @@ -608,12 +645,16 @@ def test_role_remove_group_domain(self): '--domain', identity_fakes.domain_name, identity_fakes.role_name, ] + if self._is_inheritance_testcase(): + arglist.append('--inherited') verifylist = [ ('user', None), ('group', identity_fakes.group_name), ('domain', identity_fakes.domain_name), ('project', None), ('role', identity_fakes.role_name), + ('role', identity_fakes.role_name), + ('inherited', self._is_inheritance_testcase()), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -624,6 +665,7 @@ def test_role_remove_group_domain(self): kwargs = { 'group': identity_fakes.group_id, 'domain': identity_fakes.domain_id, + 'inherited': self._is_inheritance_testcase(), } # RoleManager.revoke(role, user=, group=, domain=, project=) self.roles_mock.revoke.assert_called_with( @@ -637,12 +679,15 @@ def test_role_remove_group_project(self): '--project', identity_fakes.project_name, identity_fakes.role_name, ] + if self._is_inheritance_testcase(): + arglist.append('--inherited') verifylist = [ ('user', None), ('group', identity_fakes.group_name), ('domain', None), ('project', identity_fakes.project_name), ('role', identity_fakes.role_name), + ('inherited', self._is_inheritance_testcase()), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -653,6 +698,7 @@ def test_role_remove_group_project(self): kwargs = { 'group': identity_fakes.group_id, 'project': identity_fakes.project_id, + 'inherited': self._is_inheritance_testcase(), } # RoleManager.revoke(role, user=, group=, domain=, project=) self.roles_mock.revoke.assert_called_with( From af7f64eae5c89ca5faf380ec03d1d9f31707b2a9 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Mon, 22 Jun 2015 21:43:54 +0000 Subject: [PATCH 0128/3095] Updated from global requirements Change-Id: Ib550688f8420e8d29b594d90705ef8a89eb03018 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 8b241e0db8..dcba6fd13b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ pbr<2.0,>=0.11 six>=1.9.0 Babel>=1.3 -cliff>=1.10.0 # Apache-2.0 +cliff>=1.13.0 # Apache-2.0 cliff-tablib>=1.0 os-client-config>=1.2.0 oslo.config>=1.11.0 # Apache-2.0 From 4f12a826798312cbd0c287f8241611a42825687d Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 24 Jun 2015 00:21:41 +0000 Subject: [PATCH 0129/3095] Updated from global requirements Change-Id: I4c5304a276b1c2b2ea98c98518217b0201a2c993 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index dcba6fd13b..7c6f8aa62c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ six>=1.9.0 Babel>=1.3 cliff>=1.13.0 # Apache-2.0 cliff-tablib>=1.0 -os-client-config>=1.2.0 +os-client-config>=1.4.0 oslo.config>=1.11.0 # Apache-2.0 oslo.i18n>=1.5.0 # Apache-2.0 oslo.utils>=1.6.0 # Apache-2.0 From 253ba3c425a8077fc1d3f4504e219765b004f813 Mon Sep 17 00:00:00 2001 From: Guojian Shao Date: Wed, 24 Jun 2015 18:05:49 +0800 Subject: [PATCH 0130/3095] fix typo in network.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There is a typo for --domain argument in network create: Owner’s domain (name or ID)", the last character " should be removed. Closes-Bug: #1468282 Change-Id: I81d55841e633a52f3913cf5f4a3e6626ebc1f919 --- doc/source/command-objects/network.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/command-objects/network.rst b/doc/source/command-objects/network.rst index dcba9f82b8..e6a028b778 100644 --- a/doc/source/command-objects/network.rst +++ b/doc/source/command-objects/network.rst @@ -21,7 +21,7 @@ Create new network .. option:: --domain - Owner's domain (name or ID)" + Owner's domain (name or ID) .. option:: --project From 8899bc4162df0c9f235f6c3bf84c601ef06bafb9 Mon Sep 17 00:00:00 2001 From: Guojian Shao Date: Fri, 26 Jun 2015 12:07:58 +0800 Subject: [PATCH 0131/3095] fix confused domain argument for network create v2 we have used domain scope arguments --project-domain, --user-domain and --group-domain in identity commands, for example, role add v3, to prevent resources conflict from same resource name existence. To keep with the style of identity commands, it's better to rename --domain to --project-domain. Closes-Bug: #1468988 Change-Id: Ic6ccb895cf9be4a3d5f0001525e3b80cd340da8b --- doc/source/command-objects/network.rst | 12 ++++++------ openstackclient/network/v2/network.py | 19 ++++++------------- .../tests/network/v2/test_network.py | 9 ++++----- 3 files changed, 16 insertions(+), 24 deletions(-) diff --git a/doc/source/command-objects/network.rst b/doc/source/command-objects/network.rst index e6a028b778..6b63c35da8 100644 --- a/doc/source/command-objects/network.rst +++ b/doc/source/command-objects/network.rst @@ -13,20 +13,20 @@ Create new network .. code:: bash os network create - [--domain ] + [--project [--project-domain ]] [--enable | --disable] - [--project ] [--share | --no-share] -.. option:: --domain - - Owner's domain (name or ID) - .. option:: --project Owner's project (name or ID) +.. option:: --project-domain + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + .. option:: --enable Enable network (default) diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 9b2466422a..1fa05462f0 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -87,10 +87,7 @@ def get_parser(self, prog_name): '--project', metavar='', help="Owner's project (name or ID)") - parser.add_argument( - '--domain', - metavar='', - help="Owner's domain (name or ID)") + identity_common.add_project_domain_option_to_parser(parser) return parser def take_action(self, parsed_args): @@ -112,15 +109,11 @@ def get_body(self, parsed_args): body['shared'] = parsed_args.shared if parsed_args.project is not None: identity_client = self.app.client_manager.identity - if parsed_args.domain is not None: - domain = identity_common.find_domain(identity_client, - parsed_args.domain) - project_id = utils.find_resource(identity_client.projects, - parsed_args.project, - domain_id=domain.id).id - else: - project_id = utils.find_resource(identity_client.projects, - parsed_args.project).id + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id body['tenant_id'] = project_id return {'network': body} diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index 90085f287a..36133a3bb8 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -90,14 +90,14 @@ def test_create_all_options(self): "--disable", "--share", "--project", identity_fakes_v3.project_name, - "--domain", identity_fakes_v3.domain_name, + "--project-domain", identity_fakes_v3.domain_name, FAKE_NAME, ] + self.given_show_options verifylist = [ ('admin_state', False), ('shared', True), ('project', identity_fakes_v3.project_name), - ('domain', identity_fakes_v3.domain_name), + ('project_domain', identity_fakes_v3.domain_name), ('name', FAKE_NAME), ] + self.then_show_options mocker = mock.Mock(return_value=copy.deepcopy(RESPONSE)) @@ -165,7 +165,6 @@ def test_create_with_project_identityv2(self): arglist = [ "--project", identity_fakes_v2.project_name, FAKE_NAME, - ] verifylist = [ ('admin_state', True), @@ -203,14 +202,14 @@ def test_create_with_project_identityv2(self): def test_create_with_domain_identityv2(self): arglist = [ "--project", identity_fakes_v3.project_name, - "--domain", identity_fakes_v3.domain_name, + "--project-domain", identity_fakes_v3.domain_name, FAKE_NAME, ] verifylist = [ ('admin_state', True), ('shared', None), ('project', identity_fakes_v3.project_name), - ('domain', identity_fakes_v3.domain_name), + ('project_domain', identity_fakes_v3.domain_name), ('name', FAKE_NAME), ] mocker = mock.Mock(return_value=copy.deepcopy(RESPONSE)) From 7bb038c4a77c396d1831a4e48dfe1f3a3202c279 Mon Sep 17 00:00:00 2001 From: David Moreau Simard Date: Tue, 23 Jun 2015 23:16:31 -0400 Subject: [PATCH 0132/3095] Add support for volume API v1 QoS commands This commit adds the following commands: volume qos associate volume qos create volume qos delete volume qos disassociate volume qos list volume qos set volume qos show volume qos unset Change-Id: I72ea1b9a4d0bd0e35eda03071ea438b75439fce9 Partial-Bug: #1467967 --- openstackclient/tests/volume/v1/fakes.py | 40 ++ .../tests/volume/v1/test_qos_specs.py | 348 ++++++++++++++++++ openstackclient/volume/v1/qos_specs.py | 307 +++++++++++++++ setup.cfg | 9 + 4 files changed, 704 insertions(+) create mode 100644 openstackclient/tests/volume/v1/test_qos_specs.py create mode 100644 openstackclient/volume/v1/qos_specs.py diff --git a/openstackclient/tests/volume/v1/fakes.py b/openstackclient/tests/volume/v1/fakes.py index 339fb2d482..84cfaf31e8 100644 --- a/openstackclient/tests/volume/v1/fakes.py +++ b/openstackclient/tests/volume/v1/fakes.py @@ -79,6 +79,42 @@ 'name': image_name, } +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 = { + '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 +} + class FakeImagev1Client(object): def __init__(self, **kwargs): @@ -93,6 +129,10 @@ def __init__(self, **kwargs): self.services.resource_class = fakes.FakeResource(None, {}) self.extensions = mock.Mock() self.extensions.resource_class = fakes.FakeResource(None, {}) + self.qos_specs = mock.Mock() + self.qos_specs.resource_class = fakes.FakeResource(None, {}) + self.volume_types = mock.Mock() + self.volume_types.resource_class = fakes.FakeResource(None, {}) self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] diff --git a/openstackclient/tests/volume/v1/test_qos_specs.py b/openstackclient/tests/volume/v1/test_qos_specs.py new file mode 100644 index 0000000000..fd03c6cc1f --- /dev/null +++ b/openstackclient/tests/volume/v1/test_qos_specs.py @@ -0,0 +1,348 @@ +# Copyright 2015 iWeb Technologies Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +import copy + +from openstackclient.tests import fakes +from openstackclient.tests.volume.v1 import fakes as volume_fakes +from openstackclient.volume.v1 import qos_specs + + +class TestQos(volume_fakes.TestVolumev1): + + def setUp(self): + super(TestQos, self).setUp() + + self.qos_mock = self.app.client_manager.volume.qos_specs + self.qos_mock.reset_mock() + + self.types_mock = self.app.client_manager.volume.volume_types + self.types_mock.reset_mock() + + +class TestQosCreate(TestQos): + def setUp(self): + super(TestQosCreate, self).setUp() + + # Get the command object to test + self.cmd = qos_specs.CreateQos(self.app, None) + + def test_qos_create_without_properties(self): + self.qos_mock.create.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.QOS_DEFAULT_CONSUMER), + loaded=True + ) + + arglist = [ + volume_fakes.qos_name, + ] + verifylist = [ + ('name', volume_fakes.qos_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.qos_mock.create.assert_called_with( + volume_fakes.qos_name, + {'consumer': volume_fakes.qos_default_consumer} + ) + + collist = ( + 'consumer', + 'id', + 'name' + ) + self.assertEqual(collist, columns) + datalist = ( + volume_fakes.qos_default_consumer, + volume_fakes.qos_id, + volume_fakes.qos_name + ) + self.assertEqual(datalist, data) + + def test_qos_create_with_consumer(self): + self.qos_mock.create.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.QOS), + loaded=True + ) + + arglist = [ + volume_fakes.qos_name, + '--consumer', volume_fakes.qos_consumer + ] + verifylist = [ + ('name', volume_fakes.qos_name), + ('consumer', volume_fakes.qos_consumer) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.qos_mock.create.assert_called_with( + volume_fakes.qos_name, + {'consumer': volume_fakes.qos_consumer} + ) + + collist = ( + 'consumer', + 'id', + 'name' + ) + self.assertEqual(collist, columns) + datalist = ( + volume_fakes.qos_consumer, + volume_fakes.qos_id, + volume_fakes.qos_name + ) + self.assertEqual(datalist, data) + + def test_qos_create_with_properties(self): + self.qos_mock.create.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.QOS_WITH_SPECS), + loaded=True + ) + + arglist = [ + volume_fakes.qos_name, + '--consumer', volume_fakes.qos_consumer, + '--property', 'foo=bar', + '--property', 'iops=9001' + ] + verifylist = [ + ('name', volume_fakes.qos_name), + ('consumer', volume_fakes.qos_consumer), + ('property', volume_fakes.qos_specs) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + specs = volume_fakes.qos_specs.copy() + specs.update({'consumer': volume_fakes.qos_consumer}) + self.qos_mock.create.assert_called_with( + volume_fakes.qos_name, + specs + ) + + collist = ( + 'consumer', + 'id', + 'name', + 'specs', + ) + self.assertEqual(collist, columns) + datalist = ( + volume_fakes.qos_consumer, + volume_fakes.qos_id, + volume_fakes.qos_name, + volume_fakes.qos_specs, + ) + self.assertEqual(datalist, data) + + +class TestQosDelete(TestQos): + def setUp(self): + super(TestQosDelete, self).setUp() + + self.qos_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.QOS), + loaded=True, + ) + + # Get the command object to test + self.cmd = qos_specs.DeleteQos(self.app, None) + + def test_qos_delete_with_id(self): + arglist = [ + volume_fakes.qos_id + ] + verifylist = [ + ('qos_specs', volume_fakes.qos_id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + self.qos_mock.delete.assert_called_with(volume_fakes.qos_id) + + def test_qos_delete_with_name(self): + arglist = [ + volume_fakes.qos_name + ] + verifylist = [ + ('qos_specs', volume_fakes.qos_name) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + self.qos_mock.delete.assert_called_with(volume_fakes.qos_id) + + +class TestQosSet(TestQos): + def setUp(self): + super(TestQosSet, self).setUp() + + # Get the command object to test + self.cmd = qos_specs.SetQos(self.app, None) + + def test_qos_set_with_properties_with_id(self): + self.qos_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.QOS_WITH_SPECS), + loaded=True + ) + arglist = [ + volume_fakes.qos_id, + '--property', 'foo=bar', + '--property', 'iops=9001' + ] + verifylist = [ + ('qos_specs', volume_fakes.qos_id), + ('property', volume_fakes.qos_specs) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + self.qos_mock.set_keys.assert_called_with( + volume_fakes.qos_id, + volume_fakes.qos_specs + ) + + +class TestQosUnset(TestQos): + def setUp(self): + super(TestQosUnset, self).setUp() + + # Get the command object to test + self.cmd = qos_specs.UnsetQos(self.app, None) + + def test_qos_unset_with_properties(self): + self.qos_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.QOS), + loaded=True + ) + arglist = [ + volume_fakes.qos_id, + '--property', 'iops', + '--property', 'foo' + ] + + verifylist = [ + ('qos_specs', volume_fakes.qos_id), + ('property', ['iops', 'foo']) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + self.qos_mock.unset_keys.assert_called_with( + volume_fakes.qos_id, + ['iops', 'foo'] + ) + + +class TestQosAssociate(TestQos): + def setUp(self): + super(TestQosAssociate, self).setUp() + + # Get the command object to test + self.cmd = qos_specs.AssociateQos(self.app, None) + + def test_qos_associate(self): + self.qos_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.QOS), + loaded=True + ) + self.types_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.TYPE), + loaded=True + ) + arglist = [ + volume_fakes.qos_id, + volume_fakes.type_id + ] + verifylist = [ + ('qos_specs', volume_fakes.qos_id), + ('volume_type', volume_fakes.type_id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + self.qos_mock.associate.assert_called_with( + volume_fakes.qos_id, + volume_fakes.type_id + ) + + +class TestQosDisassociate(TestQos): + def setUp(self): + super(TestQosDisassociate, self).setUp() + + # Get the command object to test + self.cmd = qos_specs.DisassociateQos(self.app, None) + + def test_qos_disassociate_with_volume_type(self): + self.qos_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.QOS), + loaded=True + ) + self.types_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.TYPE), + loaded=True + ) + arglist = [ + volume_fakes.qos_id, + '--volume-type', volume_fakes.type_id + ] + verifylist = [ + ('qos_specs', volume_fakes.qos_id), + ('volume_type', volume_fakes.type_id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + self.qos_mock.disassociate.assert_called_with( + volume_fakes.qos_id, + volume_fakes.type_id + ) + + def test_qos_disassociate_with_all_volume_types(self): + self.qos_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.QOS), + loaded=True + ) + + arglist = [ + volume_fakes.qos_id, + '--all' + ] + verifylist = [ + ('qos_specs', volume_fakes.qos_id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + self.qos_mock.disassociate_all.assert_called_with(volume_fakes.qos_id) diff --git a/openstackclient/volume/v1/qos_specs.py b/openstackclient/volume/v1/qos_specs.py new file mode 100644 index 0000000000..634b62979c --- /dev/null +++ b/openstackclient/volume/v1/qos_specs.py @@ -0,0 +1,307 @@ +# Copyright 2015 iWeb Technologies Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +"""Volume v1 QoS action implementations""" + +import logging +import six + +from cliff import command +from cliff import lister +from cliff import show + +from openstackclient.common import parseractions +from openstackclient.common import utils + + +class CreateQos(show.ShowOne): + """Create new QoS specification""" + + log = logging.getLogger(__name__ + '.CreateQos') + + def get_parser(self, prog_name): + parser = super(CreateQos, self).get_parser(prog_name) + parser.add_argument( + 'name', + metavar='', + help='New QoS specification name', + ) + consumer_choices = ['front-end', 'back-end', 'both'] + parser.add_argument( + '--consumer', + metavar='', + choices=consumer_choices, + default='both', + help='Consumer of the QoS. Valid consumers: %s ' + "(defaults to 'both')" % utils.format_list(consumer_choices) + ) + parser.add_argument( + '--property', + metavar='', + action=parseractions.KeyValueAction, + help='Set a QoS specification property ' + '(repeat option to set multiple properties)', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + volume_client = self.app.client_manager.volume + specs = {} + specs.update({'consumer': parsed_args.consumer}) + + if parsed_args.property: + specs.update(parsed_args.property) + + qos_specs = volume_client.qos_specs.create(parsed_args.name, specs) + + return zip(*sorted(six.iteritems(qos_specs._info))) + + +class DeleteQos(command.Command): + """Delete QoS specification""" + + log = logging.getLogger(__name__ + '.DeleteQos') + + def get_parser(self, prog_name): + parser = super(DeleteQos, self).get_parser(prog_name) + parser.add_argument( + 'qos_specs', + metavar='', + help='QoS specification to delete (name or ID)', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + volume_client = self.app.client_manager.volume + qos_specs = utils.find_resource(volume_client.qos_specs, + parsed_args.qos_specs) + + volume_client.qos_specs.delete(qos_specs.id) + + return + + +class ListQos(lister.Lister): + """List QoS specifications""" + + log = logging.getLogger(__name__ + '.ListQos') + + def get_parser(self, prog_name): + parser = super(ListQos, self).get_parser(prog_name) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + volume_client = self.app.client_manager.volume + qos_specs_list = volume_client.qos_specs.list() + + for qos in qos_specs_list: + qos_associations = volume_client.qos_specs.get_associations(qos) + if qos_associations: + associations = [association.name + for association in qos_associations] + qos._info.update({'associations': associations}) + + columns = ('ID', 'Name', 'Consumer', 'Associations', 'Specs') + return (columns, + (utils.get_dict_properties( + s._info, columns, + formatters={ + 'Specs': utils.format_dict, + 'Associations': utils.format_list + }, + ) for s in qos_specs_list)) + + +class ShowQos(show.ShowOne): + """Display QoS specification details""" + + log = logging.getLogger(__name__ + '.ShowQos') + + def get_parser(self, prog_name): + parser = super(ShowQos, self).get_parser(prog_name) + parser.add_argument( + 'qos_specs', + metavar='', + help='QoS specification to display (name or ID)', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + volume_client = self.app.client_manager.volume + qos_specs = utils.find_resource(volume_client.qos_specs, + parsed_args.qos_specs) + + qos_associations = volume_client.qos_specs.get_associations(qos_specs) + if qos_associations: + associations = [association.name + for association in qos_associations] + qos_specs._info.update({ + 'associations': utils.format_list(associations) + }) + qos_specs._info.update({'specs': utils.format_dict(qos_specs.specs)}) + + return zip(*sorted(six.iteritems(qos_specs._info))) + + +class SetQos(command.Command): + """Set QoS specification properties""" + + log = logging.getLogger(__name__ + '.SetQos') + + def get_parser(self, prog_name): + parser = super(SetQos, self).get_parser(prog_name) + parser.add_argument( + 'qos_specs', + metavar='', + help='QoS specification to modify (name or ID)', + ) + parser.add_argument( + '--property', + metavar='', + action=parseractions.KeyValueAction, + help='Property to add or modify for this QoS specification ' + '(repeat option to set multiple properties)', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + volume_client = self.app.client_manager.volume + qos_specs = utils.find_resource(volume_client.qos_specs, + parsed_args.qos_specs) + + if parsed_args.property: + volume_client.qos_specs.set_keys(qos_specs.id, + parsed_args.property) + else: + self.app.log.error("No changes requested\n") + + return + + +class UnsetQos(command.Command): + """Unset QoS specification properties""" + + log = logging.getLogger(__name__ + '.SetQos') + + def get_parser(self, prog_name): + parser = super(UnsetQos, self).get_parser(prog_name) + parser.add_argument( + 'qos_specs', + metavar='', + help='QoS specification to modify (name or ID)', + ) + parser.add_argument( + '--property', + metavar='', + action='append', + default=[], + help='Property to remove from the QoS specification. ' + '(repeat option to unset multiple properties)', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + volume_client = self.app.client_manager.volume + qos_specs = utils.find_resource(volume_client.qos_specs, + parsed_args.qos_specs) + + if parsed_args.property: + volume_client.qos_specs.unset_keys(qos_specs.id, + parsed_args.property) + else: + self.app.log.error("No changes requested\n") + + return + + +class AssociateQos(command.Command): + """Associate a QoS specification to a volume type""" + + log = logging.getLogger(__name__ + '.AssociateQos') + + def get_parser(self, prog_name): + parser = super(AssociateQos, self).get_parser(prog_name) + parser.add_argument( + 'qos_specs', + metavar='', + help='QoS specification to modify (name or ID)', + ) + parser.add_argument( + 'volume_type', + metavar='', + help='Volume type to associate the QoS (name or ID)', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + volume_client = self.app.client_manager.volume + qos_specs = utils.find_resource(volume_client.qos_specs, + parsed_args.qos_specs) + volume_type = utils.find_resource(volume_client.volume_types, + parsed_args.volume_type) + + volume_client.qos_specs.associate(qos_specs.id, volume_type.id) + + return + + +class DisassociateQos(command.Command): + """Disassociate a QoS specification from a volume type""" + + log = logging.getLogger(__name__ + '.DisassociateQos') + + def get_parser(self, prog_name): + parser = super(DisassociateQos, self).get_parser(prog_name) + parser.add_argument( + 'qos_specs', + metavar='', + help='QoS specification to modify (name or ID)', + ) + volume_type_group = parser.add_mutually_exclusive_group() + volume_type_group.add_argument( + '--volume-type', + metavar='', + help='Volume type to disassociate the QoS from (name or ID)', + ) + volume_type_group.add_argument( + '--all', + action='store_true', + default=False, + help='Disassociate the QoS from every volume type', + ) + + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + volume_client = self.app.client_manager.volume + qos_specs = utils.find_resource(volume_client.qos_specs, + parsed_args.qos_specs) + + if parsed_args.volume_type: + volume_type = utils.find_resource(volume_client.volume_types, + parsed_args.volume_type) + volume_client.qos_specs.disassociate(qos_specs.id, volume_type.id) + elif parsed_args.all: + volume_client.qos_specs.disassociate_all(qos_specs.id) + + return diff --git a/setup.cfg b/setup.cfg index 8ba172be78..4ed8fb07f2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -366,6 +366,15 @@ openstack.volume.v1 = volume_type_set = openstackclient.volume.v1.type:SetVolumeType volume_type_unset = openstackclient.volume.v1.type:UnsetVolumeType + volume_qos_create = openstackclient.volume.v1.qos_specs:CreateQos + volume_qos_delete = openstackclient.volume.v1.qos_specs:DeleteQos + volume_qos_list = openstackclient.volume.v1.qos_specs:ListQos + volume_qos_show = openstackclient.volume.v1.qos_specs:ShowQos + volume_qos_set = openstackclient.volume.v1.qos_specs:SetQos + volume_qos_unset = openstackclient.volume.v1.qos_specs:UnsetQos + volume_qos_associate = openstackclient.volume.v1.qos_specs:AssociateQos + volume_qos_disassociate = openstackclient.volume.v1.qos_specs:DisassociateQos + openstack.volume.v2 = backup_create = openstackclient.volume.v2.backup:CreateBackup backup_delete = openstackclient.volume.v2.backup:DeleteBackup From 1d51eb82d05bce9b78ad289378e7d656511933d9 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Sun, 28 Jun 2015 23:40:34 -0400 Subject: [PATCH 0133/3095] Alphabetize QoS specs setup.cfg and the implementation had some functions that were not in alphabetical order. Since the rest of OSC is alphabetized, let's stick to that. Change-Id: Ief5d4694c7b6bc20a0898437b96305885104d45c --- openstackclient/volume/v1/qos_specs.py | 208 ++++++++++++------------- setup.cfg | 6 +- 2 files changed, 107 insertions(+), 107 deletions(-) diff --git a/openstackclient/volume/v1/qos_specs.py b/openstackclient/volume/v1/qos_specs.py index 634b62979c..74429481df 100644 --- a/openstackclient/volume/v1/qos_specs.py +++ b/openstackclient/volume/v1/qos_specs.py @@ -26,6 +26,38 @@ from openstackclient.common import utils +class AssociateQos(command.Command): + """Associate a QoS specification to a volume type""" + + log = logging.getLogger(__name__ + '.AssociateQos') + + def get_parser(self, prog_name): + parser = super(AssociateQos, self).get_parser(prog_name) + parser.add_argument( + 'qos_specs', + metavar='', + help='QoS specification to modify (name or ID)', + ) + parser.add_argument( + 'volume_type', + metavar='', + help='Volume type to associate the QoS (name or ID)', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + volume_client = self.app.client_manager.volume + qos_specs = utils.find_resource(volume_client.qos_specs, + parsed_args.qos_specs) + volume_type = utils.find_resource(volume_client.volume_types, + parsed_args.volume_type) + + volume_client.qos_specs.associate(qos_specs.id, volume_type.id) + + return + + class CreateQos(show.ShowOne): """Create new QoS specification""" @@ -95,6 +127,49 @@ def take_action(self, parsed_args): return +class DisassociateQos(command.Command): + """Disassociate a QoS specification from a volume type""" + + log = logging.getLogger(__name__ + '.DisassociateQos') + + def get_parser(self, prog_name): + parser = super(DisassociateQos, self).get_parser(prog_name) + parser.add_argument( + 'qos_specs', + metavar='', + help='QoS specification to modify (name or ID)', + ) + volume_type_group = parser.add_mutually_exclusive_group() + volume_type_group.add_argument( + '--volume-type', + metavar='', + help='Volume type to disassociate the QoS from (name or ID)', + ) + volume_type_group.add_argument( + '--all', + action='store_true', + default=False, + help='Disassociate the QoS from every volume type', + ) + + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + volume_client = self.app.client_manager.volume + qos_specs = utils.find_resource(volume_client.qos_specs, + parsed_args.qos_specs) + + if parsed_args.volume_type: + volume_type = utils.find_resource(volume_client.volume_types, + parsed_args.volume_type) + volume_client.qos_specs.disassociate(qos_specs.id, volume_type.id) + elif parsed_args.all: + volume_client.qos_specs.disassociate_all(qos_specs.id) + + return + + class ListQos(lister.Lister): """List QoS specifications""" @@ -127,38 +202,6 @@ def take_action(self, parsed_args): ) for s in qos_specs_list)) -class ShowQos(show.ShowOne): - """Display QoS specification details""" - - log = logging.getLogger(__name__ + '.ShowQos') - - def get_parser(self, prog_name): - parser = super(ShowQos, self).get_parser(prog_name) - parser.add_argument( - 'qos_specs', - metavar='', - help='QoS specification to display (name or ID)', - ) - return parser - - def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) - volume_client = self.app.client_manager.volume - qos_specs = utils.find_resource(volume_client.qos_specs, - parsed_args.qos_specs) - - qos_associations = volume_client.qos_specs.get_associations(qos_specs) - if qos_associations: - associations = [association.name - for association in qos_associations] - qos_specs._info.update({ - 'associations': utils.format_list(associations) - }) - qos_specs._info.update({'specs': utils.format_dict(qos_specs.specs)}) - - return zip(*sorted(six.iteritems(qos_specs._info))) - - class SetQos(command.Command): """Set QoS specification properties""" @@ -195,25 +238,17 @@ def take_action(self, parsed_args): return -class UnsetQos(command.Command): - """Unset QoS specification properties""" +class ShowQos(show.ShowOne): + """Display QoS specification details""" - log = logging.getLogger(__name__ + '.SetQos') + log = logging.getLogger(__name__ + '.ShowQos') def get_parser(self, prog_name): - parser = super(UnsetQos, self).get_parser(prog_name) + parser = super(ShowQos, self).get_parser(prog_name) parser.add_argument( 'qos_specs', metavar='', - help='QoS specification to modify (name or ID)', - ) - parser.add_argument( - '--property', - metavar='', - action='append', - default=[], - help='Property to remove from the QoS specification. ' - '(repeat option to unset multiple properties)', + help='QoS specification to display (name or ID)', ) return parser @@ -223,72 +258,38 @@ def take_action(self, parsed_args): qos_specs = utils.find_resource(volume_client.qos_specs, parsed_args.qos_specs) - if parsed_args.property: - volume_client.qos_specs.unset_keys(qos_specs.id, - parsed_args.property) - else: - self.app.log.error("No changes requested\n") + qos_associations = volume_client.qos_specs.get_associations(qos_specs) + if qos_associations: + associations = [association.name + for association in qos_associations] + qos_specs._info.update({ + 'associations': utils.format_list(associations) + }) + qos_specs._info.update({'specs': utils.format_dict(qos_specs.specs)}) - return + return zip(*sorted(six.iteritems(qos_specs._info))) -class AssociateQos(command.Command): - """Associate a QoS specification to a volume type""" +class UnsetQos(command.Command): + """Unset QoS specification properties""" - log = logging.getLogger(__name__ + '.AssociateQos') + log = logging.getLogger(__name__ + '.SetQos') def get_parser(self, prog_name): - parser = super(AssociateQos, self).get_parser(prog_name) + parser = super(UnsetQos, self).get_parser(prog_name) parser.add_argument( 'qos_specs', metavar='', help='QoS specification to modify (name or ID)', ) parser.add_argument( - 'volume_type', - metavar='', - help='Volume type to associate the QoS (name or ID)', - ) - return parser - - def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) - volume_client = self.app.client_manager.volume - qos_specs = utils.find_resource(volume_client.qos_specs, - parsed_args.qos_specs) - volume_type = utils.find_resource(volume_client.volume_types, - parsed_args.volume_type) - - volume_client.qos_specs.associate(qos_specs.id, volume_type.id) - - return - - -class DisassociateQos(command.Command): - """Disassociate a QoS specification from a volume type""" - - log = logging.getLogger(__name__ + '.DisassociateQos') - - def get_parser(self, prog_name): - parser = super(DisassociateQos, self).get_parser(prog_name) - parser.add_argument( - 'qos_specs', - metavar='', - help='QoS specification to modify (name or ID)', - ) - volume_type_group = parser.add_mutually_exclusive_group() - volume_type_group.add_argument( - '--volume-type', - metavar='', - help='Volume type to disassociate the QoS from (name or ID)', - ) - volume_type_group.add_argument( - '--all', - action='store_true', - default=False, - help='Disassociate the QoS from every volume type', + '--property', + metavar='', + action='append', + default=[], + help='Property to remove from the QoS specification. ' + '(repeat option to unset multiple properties)', ) - return parser def take_action(self, parsed_args): @@ -297,11 +298,10 @@ def take_action(self, parsed_args): qos_specs = utils.find_resource(volume_client.qos_specs, parsed_args.qos_specs) - if parsed_args.volume_type: - volume_type = utils.find_resource(volume_client.volume_types, - parsed_args.volume_type) - volume_client.qos_specs.disassociate(qos_specs.id, volume_type.id) - elif parsed_args.all: - volume_client.qos_specs.disassociate_all(qos_specs.id) + if parsed_args.property: + volume_client.qos_specs.unset_keys(qos_specs.id, + parsed_args.property) + else: + self.app.log.error("No changes requested\n") return diff --git a/setup.cfg b/setup.cfg index 4ed8fb07f2..7fb227e67b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -366,14 +366,14 @@ openstack.volume.v1 = volume_type_set = openstackclient.volume.v1.type:SetVolumeType volume_type_unset = openstackclient.volume.v1.type:UnsetVolumeType + volume_qos_associate = openstackclient.volume.v1.qos_specs:AssociateQos volume_qos_create = openstackclient.volume.v1.qos_specs:CreateQos volume_qos_delete = openstackclient.volume.v1.qos_specs:DeleteQos + volume_qos_disassociate = openstackclient.volume.v1.qos_specs:DisassociateQos volume_qos_list = openstackclient.volume.v1.qos_specs:ListQos - volume_qos_show = openstackclient.volume.v1.qos_specs:ShowQos volume_qos_set = openstackclient.volume.v1.qos_specs:SetQos + volume_qos_show = openstackclient.volume.v1.qos_specs:ShowQos volume_qos_unset = openstackclient.volume.v1.qos_specs:UnsetQos - volume_qos_associate = openstackclient.volume.v1.qos_specs:AssociateQos - volume_qos_disassociate = openstackclient.volume.v1.qos_specs:DisassociateQos openstack.volume.v2 = backup_create = openstackclient.volume.v2.backup:CreateBackup From 36613f9f53435ac9f3fb8badfa8ecb82e71b9fad Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Sun, 28 Jun 2015 23:43:39 -0400 Subject: [PATCH 0134/3095] No need for get_parser on QoS list There are no arguments so there's no need to define the get_parser function. Change-Id: Icfa8accf6dbb7f8d1a0472926403b405da3cc611 --- openstackclient/volume/v1/qos_specs.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/openstackclient/volume/v1/qos_specs.py b/openstackclient/volume/v1/qos_specs.py index 74429481df..eacb970687 100644 --- a/openstackclient/volume/v1/qos_specs.py +++ b/openstackclient/volume/v1/qos_specs.py @@ -175,10 +175,6 @@ class ListQos(lister.Lister): log = logging.getLogger(__name__ + '.ListQos') - def get_parser(self, prog_name): - parser = super(ListQos, self).get_parser(prog_name) - return parser - def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume From 862afd1e7b674350880de0faf6c9392bc2894977 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Mon, 29 Jun 2015 03:28:19 +0000 Subject: [PATCH 0135/3095] Add docs for QoS specs QoS v1 was recently included in openstackclient. We should include command object docs. Change-Id: I891231be095324bf55eb7ee4bb86debdf7a26f05 --- doc/source/command-objects/volume_qos.rst | 154 ++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 doc/source/command-objects/volume_qos.rst diff --git a/doc/source/command-objects/volume_qos.rst b/doc/source/command-objects/volume_qos.rst new file mode 100644 index 0000000000..287059f1cc --- /dev/null +++ b/doc/source/command-objects/volume_qos.rst @@ -0,0 +1,154 @@ +========== +volume qos +========== + +volume v1, v2 + +volume qos associate +-------------------- + +Associate a QoS specification to a volume type + +.. program:: volume qos associate +.. code:: bash + + os volume qos associate + + + +.. describe:: + + QoS specification to modify (name or ID) + +.. describe:: + + Volume type to associate the QoS (name or ID) + +volume qos create +----------------- + +Create new QoS Specification + +.. program:: volume qos create +.. code:: bash + + os volume qos create + [--consumer ] + [--property [...] ] + + +.. option:: --consumer + + Consumer of the QoS. Valid consumers: 'front-end', 'back-end', 'both' (defaults to 'both') + +.. option:: --property + + Set a property on this QoS specification (repeat option to set multiple properties) + +.. describe:: + + New QoS specification name + +volume qos delete +----------------- + +Delete QoS specification + +.. program:: volume qos delete +.. code:: bash + + os volume qos delete + + +.. describe:: + + QoS specification to delete (name or ID) + +volume qos disassociate +----------------------- + +Disassociate a QoS specification from a volume type + +.. program:: volume qos disassoiate +.. code:: bash + + os volume qos disassociate + --volume-type | --all + + +.. option:: --volume-type + + Volume type to disassociate the QoS from (name or ID) + +.. option:: --all + + Disassociate the QoS from every volume type + +.. describe:: + + QoS specification to modify (name or ID) + +volume qos list +--------------- + +List QoS specifications + +.. program:: volume qos list +.. code:: bash + + os volume qos list + +volume qos set +-------------- + +Set QoS specification properties + +.. program:: volume qos set +.. code:: bash + + os volume qos set + [--property [...] ] + + +.. option:: --property + + Property to add or modify for this QoS specification (repeat option to set multiple properties) + +.. describe:: + + QoS specification to modify (name or ID) + +volume qos show +--------------- + +Display QoS specification details + +.. program:: volume qos show +.. code:: bash + + os volume qos show + + +.. describe:: + + QoS specification to display (name or ID) + +volume qos unset +---------------- + +Unset QoS specification properties + +.. program:: volume qos unset +.. code:: bash + + os volume qos unset + [--property ] + + +.. option:: --property + + Property to remove from QoS specification (repeat option to remove multiple properties) + +.. describe:: + + QoS specification to modify (name or ID) From 1051a469440b3caeb94a01375c1d5e553acedf4b Mon Sep 17 00:00:00 2001 From: David Moreau Simard Date: Mon, 29 Jun 2015 12:25:30 -0400 Subject: [PATCH 0136/3095] Alphabetize tests for v1 qos_specs As a follow up of sorting the methods for volume v1 qos_specs. Change-Id: I428167297e7110e586d139bf38fd22d321836e80 --- .../tests/volume/v1/test_qos_specs.py | 148 +++++++++--------- 1 file changed, 74 insertions(+), 74 deletions(-) diff --git a/openstackclient/tests/volume/v1/test_qos_specs.py b/openstackclient/tests/volume/v1/test_qos_specs.py index fd03c6cc1f..da7adc32e5 100644 --- a/openstackclient/tests/volume/v1/test_qos_specs.py +++ b/openstackclient/tests/volume/v1/test_qos_specs.py @@ -32,6 +32,41 @@ def setUp(self): self.types_mock.reset_mock() +class TestQosAssociate(TestQos): + def setUp(self): + super(TestQosAssociate, self).setUp() + + # Get the command object to test + self.cmd = qos_specs.AssociateQos(self.app, None) + + def test_qos_associate(self): + self.qos_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.QOS), + loaded=True + ) + self.types_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.TYPE), + loaded=True + ) + arglist = [ + volume_fakes.qos_id, + volume_fakes.type_id + ] + verifylist = [ + ('qos_specs', volume_fakes.qos_id), + ('volume_type', volume_fakes.type_id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + self.qos_mock.associate.assert_called_with( + volume_fakes.qos_id, + volume_fakes.type_id + ) + + class TestQosCreate(TestQos): def setUp(self): super(TestQosCreate, self).setUp() @@ -196,153 +231,118 @@ def test_qos_delete_with_name(self): self.qos_mock.delete.assert_called_with(volume_fakes.qos_id) -class TestQosSet(TestQos): +class TestQosDisassociate(TestQos): def setUp(self): - super(TestQosSet, self).setUp() + super(TestQosDisassociate, self).setUp() # Get the command object to test - self.cmd = qos_specs.SetQos(self.app, None) + self.cmd = qos_specs.DisassociateQos(self.app, None) - def test_qos_set_with_properties_with_id(self): + def test_qos_disassociate_with_volume_type(self): self.qos_mock.get.return_value = fakes.FakeResource( None, - copy.deepcopy(volume_fakes.QOS_WITH_SPECS), + copy.deepcopy(volume_fakes.QOS), + loaded=True + ) + self.types_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.TYPE), loaded=True ) arglist = [ volume_fakes.qos_id, - '--property', 'foo=bar', - '--property', 'iops=9001' + '--volume-type', volume_fakes.type_id ] verifylist = [ ('qos_specs', volume_fakes.qos_id), - ('property', volume_fakes.qos_specs) + ('volume_type', volume_fakes.type_id) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) - self.qos_mock.set_keys.assert_called_with( + self.qos_mock.disassociate.assert_called_with( volume_fakes.qos_id, - volume_fakes.qos_specs + volume_fakes.type_id ) - -class TestQosUnset(TestQos): - def setUp(self): - super(TestQosUnset, self).setUp() - - # Get the command object to test - self.cmd = qos_specs.UnsetQos(self.app, None) - - def test_qos_unset_with_properties(self): + def test_qos_disassociate_with_all_volume_types(self): self.qos_mock.get.return_value = fakes.FakeResource( None, copy.deepcopy(volume_fakes.QOS), loaded=True ) + arglist = [ volume_fakes.qos_id, - '--property', 'iops', - '--property', 'foo' + '--all' ] - verifylist = [ - ('qos_specs', volume_fakes.qos_id), - ('property', ['iops', 'foo']) + ('qos_specs', volume_fakes.qos_id) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) - self.qos_mock.unset_keys.assert_called_with( - volume_fakes.qos_id, - ['iops', 'foo'] - ) + self.qos_mock.disassociate_all.assert_called_with(volume_fakes.qos_id) -class TestQosAssociate(TestQos): +class TestQosSet(TestQos): def setUp(self): - super(TestQosAssociate, self).setUp() + super(TestQosSet, self).setUp() # Get the command object to test - self.cmd = qos_specs.AssociateQos(self.app, None) + self.cmd = qos_specs.SetQos(self.app, None) - def test_qos_associate(self): + def test_qos_set_with_properties_with_id(self): self.qos_mock.get.return_value = fakes.FakeResource( None, - copy.deepcopy(volume_fakes.QOS), - loaded=True - ) - self.types_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.TYPE), + copy.deepcopy(volume_fakes.QOS_WITH_SPECS), loaded=True ) arglist = [ volume_fakes.qos_id, - volume_fakes.type_id + '--property', 'foo=bar', + '--property', 'iops=9001' ] verifylist = [ ('qos_specs', volume_fakes.qos_id), - ('volume_type', volume_fakes.type_id) + ('property', volume_fakes.qos_specs) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) - self.qos_mock.associate.assert_called_with( + self.qos_mock.set_keys.assert_called_with( volume_fakes.qos_id, - volume_fakes.type_id + volume_fakes.qos_specs ) -class TestQosDisassociate(TestQos): +class TestQosUnset(TestQos): def setUp(self): - super(TestQosDisassociate, self).setUp() + super(TestQosUnset, self).setUp() # Get the command object to test - self.cmd = qos_specs.DisassociateQos(self.app, None) + self.cmd = qos_specs.UnsetQos(self.app, None) - def test_qos_disassociate_with_volume_type(self): + def test_qos_unset_with_properties(self): self.qos_mock.get.return_value = fakes.FakeResource( None, copy.deepcopy(volume_fakes.QOS), loaded=True ) - self.types_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.TYPE), - loaded=True - ) arglist = [ volume_fakes.qos_id, - '--volume-type', volume_fakes.type_id + '--property', 'iops', + '--property', 'foo' ] + verifylist = [ ('qos_specs', volume_fakes.qos_id), - ('volume_type', volume_fakes.type_id) + ('property', ['iops', 'foo']) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) - self.qos_mock.disassociate.assert_called_with( + self.qos_mock.unset_keys.assert_called_with( volume_fakes.qos_id, - volume_fakes.type_id - ) - - def test_qos_disassociate_with_all_volume_types(self): - self.qos_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.QOS), - loaded=True + ['iops', 'foo'] ) - - arglist = [ - volume_fakes.qos_id, - '--all' - ] - verifylist = [ - ('qos_specs', volume_fakes.qos_id) - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - self.cmd.take_action(parsed_args) - self.qos_mock.disassociate_all.assert_called_with(volume_fakes.qos_id) From 4d832e7beb9860419eb00fb6f5a2a454dcd86494 Mon Sep 17 00:00:00 2001 From: David Moreau Simard Date: Mon, 29 Jun 2015 17:15:31 -0400 Subject: [PATCH 0137/3095] Add tests for 'list' and 'show' for volume qos v1 Change-Id: I1b4d998f3ec1bd5cb8dd4c9e0d04fd3b3049e9d7 --- openstackclient/tests/volume/v1/fakes.py | 13 +++ .../tests/volume/v1/test_qos_specs.py | 98 +++++++++++++++++++ 2 files changed, 111 insertions(+) diff --git a/openstackclient/tests/volume/v1/fakes.py b/openstackclient/tests/volume/v1/fakes.py index 84cfaf31e8..c6b4f0b858 100644 --- a/openstackclient/tests/volume/v1/fakes.py +++ b/openstackclient/tests/volume/v1/fakes.py @@ -95,6 +95,11 @@ 'foo': 'bar', 'iops': '9001' } +qos_association = { + 'association_type': 'volume_type', + 'name': type_name, + 'id': type_id +} QOS = { 'id': qos_id, @@ -115,6 +120,14 @@ 'specs': qos_specs } +QOS_WITH_ASSOCIATIONS = { + 'id': qos_id, + 'consumer': qos_consumer, + 'name': qos_name, + 'specs': qos_specs, + 'associations': [qos_association] +} + class FakeImagev1Client(object): def __init__(self, **kwargs): diff --git a/openstackclient/tests/volume/v1/test_qos_specs.py b/openstackclient/tests/volume/v1/test_qos_specs.py index da7adc32e5..0a5d14e60d 100644 --- a/openstackclient/tests/volume/v1/test_qos_specs.py +++ b/openstackclient/tests/volume/v1/test_qos_specs.py @@ -15,6 +15,7 @@ import copy +from openstackclient.common import utils from openstackclient.tests import fakes from openstackclient.tests.volume.v1 import fakes as volume_fakes from openstackclient.volume.v1 import qos_specs @@ -285,6 +286,52 @@ def test_qos_disassociate_with_all_volume_types(self): self.qos_mock.disassociate_all.assert_called_with(volume_fakes.qos_id) +class TestQosList(TestQos): + def setUp(self): + super(TestQosList, self).setUp() + + self.qos_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.QOS_WITH_ASSOCIATIONS), + loaded=True, + ) + self.qos_mock.list.return_value = [self.qos_mock.get.return_value] + self.qos_mock.get_associations.return_value = [fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.qos_association), + loaded=True, + )] + + # Get the command object to test + self.cmd = qos_specs.ListQos(self.app, None) + + def test_qos_list(self): + arglist = [] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.qos_mock.list.assert_called() + + collist = ( + 'ID', + 'Name', + 'Consumer', + 'Associations', + 'Specs', + ) + self.assertEqual(collist, columns) + datalist = (( + volume_fakes.qos_id, + volume_fakes.qos_name, + volume_fakes.qos_consumer, + volume_fakes.type_name, + utils.format_dict(volume_fakes.qos_specs), + ), ) + self.assertEqual(datalist, tuple(data)) + + class TestQosSet(TestQos): def setUp(self): super(TestQosSet, self).setUp() @@ -316,6 +363,57 @@ def test_qos_set_with_properties_with_id(self): ) +class TestQosShow(TestQos): + def setUp(self): + super(TestQosShow, self).setUp() + + self.qos_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.QOS_WITH_ASSOCIATIONS), + loaded=True, + ) + self.qos_mock.get_associations.return_value = [fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.qos_association), + loaded=True, + )] + + # Get the command object to test + self.cmd = qos_specs.ShowQos(self.app, None) + + def test_qos_show(self): + arglist = [ + volume_fakes.qos_id + ] + verifylist = [ + ('qos_specs', volume_fakes.qos_id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.qos_mock.get.assert_called_with( + volume_fakes.qos_id + ) + + collist = ( + 'associations', + 'consumer', + 'id', + 'name', + 'specs' + ) + self.assertEqual(collist, columns) + datalist = ( + volume_fakes.type_name, + volume_fakes.qos_consumer, + volume_fakes.qos_id, + volume_fakes.qos_name, + utils.format_dict(volume_fakes.qos_specs), + ) + self.assertEqual(datalist, tuple(data)) + + class TestQosUnset(TestQos): def setUp(self): super(TestQosUnset, self).setUp() From 974c9d5793bb00cbcfac799ea18c3b73c0b2455e Mon Sep 17 00:00:00 2001 From: David Moreau Simard Date: Sat, 27 Jun 2015 09:31:19 -0400 Subject: [PATCH 0138/3095] Add support for volume API v2 QoS commands This commit adds the following commands: volume qos associate volume qos create volume qos delete volume qos disassociate volume qos list volume qos set volume qos show volume qos unset Change-Id: If3c679557ac9abb0dfc75d290b96fb9c8d46c7b7 Partial-Bug: #1467967 --- openstackclient/tests/volume/v2/fakes.py | 43 ++ .../tests/volume/v2/test_qos_specs.py | 446 ++++++++++++++++++ openstackclient/volume/v2/qos_specs.py | 303 ++++++++++++ setup.cfg | 9 + 4 files changed, 801 insertions(+) create mode 100644 openstackclient/tests/volume/v2/test_qos_specs.py create mode 100644 openstackclient/volume/v2/qos_specs.py diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py index f28a3e25f3..4f5f9cfdcb 100644 --- a/openstackclient/tests/volume/v2/fakes.py +++ b/openstackclient/tests/volume/v2/fakes.py @@ -117,6 +117,47 @@ BACKUP_columns = tuple(sorted(BACKUP)) BACKUP_data = tuple((BACKUP[x] for x in sorted(BACKUP))) +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 FakeVolumeClient(object): def __init__(self, **kwargs): @@ -130,6 +171,8 @@ def __init__(self, **kwargs): self.volume_types.resource_class = fakes.FakeResource(None, {}) self.restores = mock.Mock() self.restores.resource_class = fakes.FakeResource(None, {}) + self.qos_specs = mock.Mock() + self.qos_specs.resource_class = fakes.FakeResource(None, {}) self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] diff --git a/openstackclient/tests/volume/v2/test_qos_specs.py b/openstackclient/tests/volume/v2/test_qos_specs.py new file mode 100644 index 0000000000..92b3f1793f --- /dev/null +++ b/openstackclient/tests/volume/v2/test_qos_specs.py @@ -0,0 +1,446 @@ +# Copyright 2015 iWeb Technologies Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +import copy + +from openstackclient.common import utils +from openstackclient.tests import fakes +from openstackclient.tests.volume.v2 import fakes as volume_fakes +from openstackclient.volume.v2 import qos_specs + + +class TestQos(volume_fakes.TestVolume): + + def setUp(self): + super(TestQos, self).setUp() + + self.qos_mock = self.app.client_manager.volume.qos_specs + self.qos_mock.reset_mock() + + self.types_mock = self.app.client_manager.volume.volume_types + self.types_mock.reset_mock() + + +class TestQosAssociate(TestQos): + def setUp(self): + super(TestQosAssociate, self).setUp() + + # Get the command object to test + self.cmd = qos_specs.AssociateQos(self.app, None) + + def test_qos_associate(self): + self.qos_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.QOS), + loaded=True + ) + self.types_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.TYPE), + loaded=True + ) + arglist = [ + volume_fakes.qos_id, + volume_fakes.type_id + ] + verifylist = [ + ('qos_specs', volume_fakes.qos_id), + ('volume_type', volume_fakes.type_id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + self.qos_mock.associate.assert_called_with( + volume_fakes.qos_id, + volume_fakes.type_id + ) + + +class TestQosCreate(TestQos): + def setUp(self): + super(TestQosCreate, self).setUp() + + # Get the command object to test + self.cmd = qos_specs.CreateQos(self.app, None) + + def test_qos_create_without_properties(self): + self.qos_mock.create.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.QOS_DEFAULT_CONSUMER), + loaded=True + ) + + arglist = [ + volume_fakes.qos_name, + ] + verifylist = [ + ('name', volume_fakes.qos_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.qos_mock.create.assert_called_with( + volume_fakes.qos_name, + {'consumer': volume_fakes.qos_default_consumer} + ) + + collist = ( + 'consumer', + 'id', + 'name' + ) + self.assertEqual(collist, columns) + datalist = ( + volume_fakes.qos_default_consumer, + volume_fakes.qos_id, + volume_fakes.qos_name + ) + self.assertEqual(datalist, data) + + def test_qos_create_with_consumer(self): + self.qos_mock.create.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.QOS), + loaded=True + ) + + arglist = [ + volume_fakes.qos_name, + '--consumer', volume_fakes.qos_consumer + ] + verifylist = [ + ('name', volume_fakes.qos_name), + ('consumer', volume_fakes.qos_consumer) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.qos_mock.create.assert_called_with( + volume_fakes.qos_name, + {'consumer': volume_fakes.qos_consumer} + ) + + collist = ( + 'consumer', + 'id', + 'name' + ) + self.assertEqual(collist, columns) + datalist = ( + volume_fakes.qos_consumer, + volume_fakes.qos_id, + volume_fakes.qos_name + ) + self.assertEqual(datalist, data) + + def test_qos_create_with_properties(self): + self.qos_mock.create.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.QOS_WITH_SPECS), + loaded=True + ) + + arglist = [ + volume_fakes.qos_name, + '--consumer', volume_fakes.qos_consumer, + '--property', 'foo=bar', + '--property', 'iops=9001' + ] + verifylist = [ + ('name', volume_fakes.qos_name), + ('consumer', volume_fakes.qos_consumer), + ('property', volume_fakes.qos_specs) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + specs = volume_fakes.qos_specs.copy() + specs.update({'consumer': volume_fakes.qos_consumer}) + self.qos_mock.create.assert_called_with( + volume_fakes.qos_name, + specs + ) + + collist = ( + 'consumer', + 'id', + 'name', + 'specs', + ) + self.assertEqual(collist, columns) + datalist = ( + volume_fakes.qos_consumer, + volume_fakes.qos_id, + volume_fakes.qos_name, + volume_fakes.qos_specs, + ) + self.assertEqual(datalist, data) + + +class TestQosDelete(TestQos): + def setUp(self): + super(TestQosDelete, self).setUp() + + self.qos_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.QOS), + loaded=True, + ) + + # Get the command object to test + self.cmd = qos_specs.DeleteQos(self.app, None) + + def test_qos_delete_with_id(self): + arglist = [ + volume_fakes.qos_id + ] + verifylist = [ + ('qos_specs', volume_fakes.qos_id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + self.qos_mock.delete.assert_called_with(volume_fakes.qos_id) + + def test_qos_delete_with_name(self): + arglist = [ + volume_fakes.qos_name + ] + verifylist = [ + ('qos_specs', volume_fakes.qos_name) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + self.qos_mock.delete.assert_called_with(volume_fakes.qos_id) + + +class TestQosDisassociate(TestQos): + def setUp(self): + super(TestQosDisassociate, self).setUp() + + # Get the command object to test + self.cmd = qos_specs.DisassociateQos(self.app, None) + + def test_qos_disassociate_with_volume_type(self): + self.qos_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.QOS), + loaded=True + ) + self.types_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.TYPE), + loaded=True + ) + arglist = [ + volume_fakes.qos_id, + '--volume-type', volume_fakes.type_id + ] + verifylist = [ + ('qos_specs', volume_fakes.qos_id), + ('volume_type', volume_fakes.type_id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + self.qos_mock.disassociate.assert_called_with( + volume_fakes.qos_id, + volume_fakes.type_id + ) + + def test_qos_disassociate_with_all_volume_types(self): + self.qos_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.QOS), + loaded=True + ) + + arglist = [ + volume_fakes.qos_id, + '--all' + ] + verifylist = [ + ('qos_specs', volume_fakes.qos_id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + self.qos_mock.disassociate_all.assert_called_with(volume_fakes.qos_id) + + +class TestQosList(TestQos): + def setUp(self): + super(TestQosList, self).setUp() + + self.qos_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.QOS_WITH_ASSOCIATIONS), + loaded=True, + ) + self.qos_mock.list.return_value = [self.qos_mock.get.return_value] + self.qos_mock.get_associations.return_value = [fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.qos_association), + loaded=True, + )] + + # Get the command object to test + self.cmd = qos_specs.ListQos(self.app, None) + + def test_qos_list(self): + arglist = [] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.qos_mock.list.assert_called() + + collist = ( + 'ID', + 'Name', + 'Consumer', + 'Associations', + 'Specs', + ) + self.assertEqual(collist, columns) + datalist = (( + volume_fakes.qos_id, + volume_fakes.qos_name, + volume_fakes.qos_consumer, + volume_fakes.type_name, + utils.format_dict(volume_fakes.qos_specs), + ), ) + self.assertEqual(datalist, tuple(data)) + + +class TestQosSet(TestQos): + def setUp(self): + super(TestQosSet, self).setUp() + + # Get the command object to test + self.cmd = qos_specs.SetQos(self.app, None) + + def test_qos_set_with_properties_with_id(self): + self.qos_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.QOS_WITH_SPECS), + loaded=True + ) + arglist = [ + volume_fakes.qos_id, + '--property', 'foo=bar', + '--property', 'iops=9001' + ] + verifylist = [ + ('qos_specs', volume_fakes.qos_id), + ('property', volume_fakes.qos_specs) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + self.qos_mock.set_keys.assert_called_with( + volume_fakes.qos_id, + volume_fakes.qos_specs + ) + + +class TestQosShow(TestQos): + def setUp(self): + super(TestQosShow, self).setUp() + + self.qos_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.QOS_WITH_ASSOCIATIONS), + loaded=True, + ) + self.qos_mock.get_associations.return_value = [fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.qos_association), + loaded=True, + )] + + # Get the command object to test + self.cmd = qos_specs.ShowQos(self.app, None) + + def test_qos_show(self): + arglist = [ + volume_fakes.qos_id + ] + verifylist = [ + ('qos_specs', volume_fakes.qos_id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.qos_mock.get.assert_called_with( + volume_fakes.qos_id + ) + + collist = ( + 'associations', + 'consumer', + 'id', + 'name', + 'specs' + ) + self.assertEqual(collist, columns) + datalist = ( + volume_fakes.type_name, + volume_fakes.qos_consumer, + volume_fakes.qos_id, + volume_fakes.qos_name, + utils.format_dict(volume_fakes.qos_specs), + ) + self.assertEqual(datalist, tuple(data)) + + +class TestQosUnset(TestQos): + def setUp(self): + super(TestQosUnset, self).setUp() + + # Get the command object to test + self.cmd = qos_specs.UnsetQos(self.app, None) + + def test_qos_unset_with_properties(self): + self.qos_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.QOS), + loaded=True + ) + arglist = [ + volume_fakes.qos_id, + '--property', 'iops', + '--property', 'foo' + ] + + verifylist = [ + ('qos_specs', volume_fakes.qos_id), + ('property', ['iops', 'foo']) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + self.qos_mock.unset_keys.assert_called_with( + volume_fakes.qos_id, + ['iops', 'foo'] + ) diff --git a/openstackclient/volume/v2/qos_specs.py b/openstackclient/volume/v2/qos_specs.py new file mode 100644 index 0000000000..7f02fa4a2d --- /dev/null +++ b/openstackclient/volume/v2/qos_specs.py @@ -0,0 +1,303 @@ +# Copyright 2015 iWeb Technologies Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +"""Volume v2 QoS action implementations""" + +import logging +import six + +from cliff import command +from cliff import lister +from cliff import show + +from openstackclient.common import parseractions +from openstackclient.common import utils + + +class AssociateQos(command.Command): + """Associate a QoS specification to a volume type""" + + log = logging.getLogger(__name__ + '.AssociateQos') + + def get_parser(self, prog_name): + parser = super(AssociateQos, self).get_parser(prog_name) + parser.add_argument( + 'qos_specs', + metavar='', + help='QoS specification to modify (name or ID)', + ) + parser.add_argument( + 'volume_type', + metavar='', + help='Volume type to associate the QoS (name or ID)', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + volume_client = self.app.client_manager.volume + qos_specs = utils.find_resource(volume_client.qos_specs, + parsed_args.qos_specs) + volume_type = utils.find_resource(volume_client.volume_types, + parsed_args.volume_type) + + volume_client.qos_specs.associate(qos_specs.id, volume_type.id) + + return + + +class CreateQos(show.ShowOne): + """Create new QoS specification""" + + log = logging.getLogger(__name__ + '.CreateQos') + + def get_parser(self, prog_name): + parser = super(CreateQos, self).get_parser(prog_name) + parser.add_argument( + 'name', + metavar='', + help='New QoS specification name', + ) + consumer_choices = ['front-end', 'back-end', 'both'] + parser.add_argument( + '--consumer', + metavar='', + choices=consumer_choices, + default='both', + help='Consumer of the QoS. Valid consumers: %s ' + "(defaults to 'both')" % utils.format_list(consumer_choices) + ) + parser.add_argument( + '--property', + metavar='', + action=parseractions.KeyValueAction, + help='Set a QoS specification property ' + '(repeat option to set multiple properties)', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + volume_client = self.app.client_manager.volume + specs = {} + specs.update({'consumer': parsed_args.consumer}) + + if parsed_args.property: + specs.update(parsed_args.property) + + qos_specs = volume_client.qos_specs.create(parsed_args.name, specs) + + return zip(*sorted(six.iteritems(qos_specs._info))) + + +class DeleteQos(command.Command): + """Delete QoS specification""" + + log = logging.getLogger(__name__ + '.DeleteQos') + + def get_parser(self, prog_name): + parser = super(DeleteQos, self).get_parser(prog_name) + parser.add_argument( + 'qos_specs', + metavar='', + help='QoS specification to delete (name or ID)', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + volume_client = self.app.client_manager.volume + qos_specs = utils.find_resource(volume_client.qos_specs, + parsed_args.qos_specs) + + volume_client.qos_specs.delete(qos_specs.id) + + return + + +class DisassociateQos(command.Command): + """Disassociate a QoS specification from a volume type""" + + log = logging.getLogger(__name__ + '.DisassociateQos') + + def get_parser(self, prog_name): + parser = super(DisassociateQos, self).get_parser(prog_name) + parser.add_argument( + 'qos_specs', + metavar='', + help='QoS specification to modify (name or ID)', + ) + volume_type_group = parser.add_mutually_exclusive_group() + volume_type_group.add_argument( + '--volume-type', + metavar='', + help='Volume type to disassociate the QoS from (name or ID)', + ) + volume_type_group.add_argument( + '--all', + action='store_true', + default=False, + help='Disassociate the QoS from every volume type', + ) + + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + volume_client = self.app.client_manager.volume + qos_specs = utils.find_resource(volume_client.qos_specs, + parsed_args.qos_specs) + + if parsed_args.volume_type: + volume_type = utils.find_resource(volume_client.volume_types, + parsed_args.volume_type) + volume_client.qos_specs.disassociate(qos_specs.id, volume_type.id) + elif parsed_args.all: + volume_client.qos_specs.disassociate_all(qos_specs.id) + + return + + +class ListQos(lister.Lister): + """List QoS specifications""" + + log = logging.getLogger(__name__ + '.ListQos') + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + volume_client = self.app.client_manager.volume + qos_specs_list = volume_client.qos_specs.list() + + for qos in qos_specs_list: + qos_associations = volume_client.qos_specs.get_associations(qos) + if qos_associations: + associations = [association.name + for association in qos_associations] + qos._info.update({'associations': associations}) + + columns = ('ID', 'Name', 'Consumer', 'Associations', 'Specs') + return (columns, + (utils.get_dict_properties( + s._info, columns, + formatters={ + 'Specs': utils.format_dict, + 'Associations': utils.format_list + }, + ) for s in qos_specs_list)) + + +class SetQos(command.Command): + """Set QoS specification properties""" + + log = logging.getLogger(__name__ + '.SetQos') + + def get_parser(self, prog_name): + parser = super(SetQos, self).get_parser(prog_name) + parser.add_argument( + 'qos_specs', + metavar='', + help='QoS specification to modify (name or ID)', + ) + parser.add_argument( + '--property', + metavar='', + action=parseractions.KeyValueAction, + help='Property to add or modify for this QoS specification ' + '(repeat option to set multiple properties)', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + volume_client = self.app.client_manager.volume + qos_specs = utils.find_resource(volume_client.qos_specs, + parsed_args.qos_specs) + + if parsed_args.property: + volume_client.qos_specs.set_keys(qos_specs.id, + parsed_args.property) + else: + self.app.log.error("No changes requested\n") + + return + + +class ShowQos(show.ShowOne): + """Display QoS specification details""" + + log = logging.getLogger(__name__ + '.ShowQos') + + def get_parser(self, prog_name): + parser = super(ShowQos, self).get_parser(prog_name) + parser.add_argument( + 'qos_specs', + metavar='', + help='QoS specification to display (name or ID)', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + volume_client = self.app.client_manager.volume + qos_specs = utils.find_resource(volume_client.qos_specs, + parsed_args.qos_specs) + + qos_associations = volume_client.qos_specs.get_associations(qos_specs) + if qos_associations: + associations = [association.name + for association in qos_associations] + qos_specs._info.update({ + 'associations': utils.format_list(associations) + }) + qos_specs._info.update({'specs': utils.format_dict(qos_specs.specs)}) + + return zip(*sorted(six.iteritems(qos_specs._info))) + + +class UnsetQos(command.Command): + """Unset QoS specification properties""" + + log = logging.getLogger(__name__ + '.SetQos') + + def get_parser(self, prog_name): + parser = super(UnsetQos, self).get_parser(prog_name) + parser.add_argument( + 'qos_specs', + metavar='', + help='QoS specification to modify (name or ID)', + ) + parser.add_argument( + '--property', + metavar='', + action='append', + default=[], + help='Property to remove from the QoS specification. ' + '(repeat option to unset multiple properties)', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + volume_client = self.app.client_manager.volume + qos_specs = utils.find_resource(volume_client.qos_specs, + parsed_args.qos_specs) + + if parsed_args.property: + volume_client.qos_specs.unset_keys(qos_specs.id, + parsed_args.property) + else: + self.app.log.error("No changes requested\n") + + return diff --git a/setup.cfg b/setup.cfg index 8ba172be78..c3c178cdbb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -386,6 +386,15 @@ openstack.volume.v2 = volume_type_delete = openstackclient.volume.v2.volume_type:DeleteVolumeType volume_type_show = openstackclient.volume.v2.volume_type:ShowVolumeType + volume_qos_associate = openstackclient.volume.v2.qos_specs:AssociateQos + volume_qos_create = openstackclient.volume.v2.qos_specs:CreateQos + volume_qos_delete = openstackclient.volume.v2.qos_specs:DeleteQos + volume_qos_disassociate = openstackclient.volume.v2.qos_specs:DisassociateQos + volume_qos_list = openstackclient.volume.v2.qos_specs:ListQos + volume_qos_set = openstackclient.volume.v2.qos_specs:SetQos + volume_qos_show = openstackclient.volume.v2.qos_specs:ShowQos + volume_qos_unset = openstackclient.volume.v2.qos_specs:UnsetQos + [build_sphinx] source-dir = doc/source build-dir = doc/build From a7eb8b5b3f2a8b7ee53e0787f49952d63d2660bb Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Tue, 30 Jun 2015 22:45:47 +0000 Subject: [PATCH 0139/3095] Updated from global requirements Change-Id: Icf1e3f1292baafe7746fd8804c61ccaab592db65 --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index ce3fcd73aa..4b7ca5b61a 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -5,7 +5,7 @@ hacking<0.11,>=0.10.0 coverage>=3.6 discover -fixtures>=0.3.14 +fixtures>=1.3.1 mock>=1.0 oslosphinx>=2.5.0 # Apache-2.0 oslotest>=1.5.1 # Apache-2.0 From 700048a1e0ba9aec03192d46303085c8e4d19ea2 Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Wed, 1 Jul 2015 05:11:45 -0600 Subject: [PATCH 0140/3095] Fix examples with cacert Change-Id: I2a4f758ef11caf51d0c47cb5632e59245d631d3d Closes-Bug: #1470272 --- examples/common.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/common.py b/examples/common.py index 3b31f582c6..840e715e16 100755 --- a/examples/common.py +++ b/examples/common.py @@ -101,6 +101,7 @@ def base_parser(parser): parser.add_argument( '--os-cacert', metavar='', + dest='cacert', default=env('OS_CACERT'), help='CA certificate bundle file (Env: OS_CACERT)', ) From bd589778c287f1f1b2f7c2dcce7c917e49f956c3 Mon Sep 17 00:00:00 2001 From: Marek Aufart Date: Tue, 23 Jun 2015 15:11:06 +0200 Subject: [PATCH 0141/3095] Move update code from image create command Openstack image create command updates existing image (with same name) by default. That might be confusing since glance allows create multiple images with same names and may lead to unwanted image update by image create command. Image update code was moved from image create action to image set action. BackwardsIncompatibleImpact Change-Id: I1686c6544c366262efab9e33c066d5f8a667f707 Closes-Bug: #1461817 --- doc/source/backwards-incompatible.rst | 14 ++ doc/source/command-objects/image.rst | 40 ++++++ openstackclient/image/v1/image.py | 142 ++++++++++++++----- openstackclient/tests/image/v1/test_image.py | 132 +++++++++-------- 4 files changed, 223 insertions(+), 105 deletions(-) diff --git a/doc/source/backwards-incompatible.rst b/doc/source/backwards-incompatible.rst index 437f9324bb..ae77164b65 100644 --- a/doc/source/backwards-incompatible.rst +++ b/doc/source/backwards-incompatible.rst @@ -47,6 +47,20 @@ List of Backwards Incompatible Changes * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1450872 * Commit: https://review.openstack.org/#/c/179446/ +4. Command `openstack image create` does not update already existing image + + Previously, the image create command updated already existing image if it had + same name. It disabled possibility to create multiple images with same name + and lead to potentially unwanted update of existing images by image create + command. + Now, update code was moved from create action to set action. + + * In favor of: Create multiple images with same name (as glance does). + * As of: 1.5.0 + * Removed in: NA + * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1461817 + * Commit: https://review.openstack.org/#/c/194654/ + For Developers ============== diff --git a/doc/source/command-objects/image.rst b/doc/source/command-objects/image.rst index 18658651c3..824d4930e5 100644 --- a/doc/source/command-objects/image.rst +++ b/doc/source/command-objects/image.rst @@ -210,6 +210,14 @@ Set image properties [--size ] [--protected | --unprotected] [--public | --private] + [--store ] + [--location ] + [--copy-from ] + [--file ] + [--volume ] + [--force] + [--checksum ] + [--stdin] [--property [...] ] @@ -260,6 +268,38 @@ Set image properties Image is inaccessible to the public (default) +.. option:: --store + + Upload image to this store + +.. option:: --location + + Download image from an existing URL + +.. option:: --copy-from + + Copy image from the data store (similar to --location) + +.. option:: --file + + Upload image from local file + +.. option:: --volume + + Update image with a volume + +.. option:: --force + + Force image update if volume is in use (only meaningful with --volume) + +.. option:: --checksum + + Image hash used for verification + +.. option:: --stdin + + Allow to read image data from standard input + .. option:: --property Set a property on this image (repeat for multiple values) diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index d4d45fa287..2fa42a0414 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -33,7 +33,6 @@ from glanceclient.common import utils as gc_utils from openstackclient.api import utils as api_utils -from openstackclient.common import exceptions from openstackclient.common import parseractions from openstackclient.common import utils @@ -244,29 +243,7 @@ def take_action(self, parsed_args): # Wrap the call to catch exceptions in order to close files try: - try: - image = utils.find_resource( - image_client.images, - parsed_args.name, - ) - - # Preserve previous properties if any are being set now - if image.properties: - if parsed_args.properties: - image.properties.update(kwargs['properties']) - kwargs['properties'] = image.properties - - except exceptions.CommandError: - if not parsed_args.volume: - # This is normal for a create or reserve (create w/o - # an image), but skip for create from volume - image = image_client.images.create(**kwargs) - else: - # Update an existing reservation - - # If an image is specified via --file, --location or - # --copy-from let the API handle it - image = image_client.images.update(image.id, **kwargs) + image = image_client.images.create(**kwargs) finally: # Clean up open files - make sure data isn't a string if ('data' in kwargs and hasattr(kwargs['data'], 'close') and @@ -560,6 +537,51 @@ def get_parser(self, prog_name): help="Set a property on this image " "(repeat option to set multiple properties)", ) + parser.add_argument( + "--store", + metavar="", + help="Upload image to this store", + ) + parser.add_argument( + "--location", + metavar="", + help="Download image from an existing URL", + ) + parser.add_argument( + "--copy-from", + metavar="", + help="Copy image from the data store (similar to --location)", + ) + parser.add_argument( + "--file", + metavar="", + help="Upload image from local file", + ) + parser.add_argument( + "--volume", + metavar="", + help="Create image from a volume", + ) + parser.add_argument( + "--force", + dest='force', + action='store_true', + default=False, + help="Force image change if volume is in use " + "(only meaningful with --volume)", + ) + parser.add_argument( + "--stdin", + dest='stdin', + action='store_true', + default=False, + help="Read image data from standard input", + ) + parser.add_argument( + "--checksum", + metavar="", + help="Image hash used for verification", + ) return parser def take_action(self, parsed_args): @@ -568,7 +590,8 @@ def take_action(self, parsed_args): kwargs = {} copy_attrs = ('name', 'owner', 'min_disk', 'min_ram', 'properties', - 'container_format', 'disk_format', 'size') + 'container_format', 'disk_format', 'size', 'store', + 'location', 'copy_from', 'volume', 'force', 'checksum') for attr in copy_attrs: if attr in parsed_args: val = getattr(parsed_args, attr, None) @@ -591,20 +614,63 @@ def take_action(self, parsed_args): if parsed_args.private: kwargs['is_public'] = False - if not kwargs: - self.log.warning('no arguments specified') - return {}, {} - - image = utils.find_resource( - image_client.images, - parsed_args.image, - ) - - if image.properties and parsed_args.properties: - image.properties.update(kwargs['properties']) - kwargs['properties'] = image.properties + # Wrap the call to catch exceptions in order to close files + try: + image = utils.find_resource( + image_client.images, + parsed_args.image, + ) - image = image_client.images.update(image.id, **kwargs) + if not parsed_args.location and not parsed_args.copy_from: + if parsed_args.volume: + volume_client = self.app.client_manager.volume + source_volume = utils.find_resource( + volume_client.volumes, + parsed_args.volume, + ) + response, body = volume_client.volumes.upload_to_image( + source_volume.id, + parsed_args.force, + parsed_args.image, + (parsed_args.container_format + if parsed_args.container_format + else image.container_format), + (parsed_args.disk_format + if parsed_args.disk_format + else image.disk_format), + ) + info = body['os-volume_upload_image'] + elif parsed_args.file: + # Send an open file handle to glanceclient so it will + # do a chunked transfer + kwargs["data"] = io.open(parsed_args.file, "rb") + else: + # Read file from stdin + if sys.stdin.isatty() is not True: + if parsed_args.stdin: + if msvcrt: + msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY) + # Send an open file handle to glanceclient so it + # will do a chunked transfer + kwargs["data"] = sys.stdin + else: + self.log.warning('Use --stdin to enable read image' + ' data from standard input') + + if image.properties and parsed_args.properties: + image.properties.update(kwargs['properties']) + kwargs['properties'] = image.properties + + if not kwargs: + self.log.warning('no arguments specified') + return {}, {} + + image = image_client.images.update(image.id, **kwargs) + finally: + # Clean up open files - make sure data isn't a string + if ('data' in kwargs and hasattr(kwargs['data'], 'close') and + kwargs['data'] != sys.stdin): + kwargs['data'].close() info = {} info.update(image._info) diff --git a/openstackclient/tests/image/v1/test_image.py b/openstackclient/tests/image/v1/test_image.py index eec8cfa5c3..a79df8b4a1 100644 --- a/openstackclient/tests/image/v1/test_image.py +++ b/openstackclient/tests/image/v1/test_image.py @@ -178,8 +178,8 @@ def test_image_create_file(self, mock_open): # Ensure the input file is closed mock_file.close.assert_called_with() - # ImageManager.get(name) - self.images_mock.get.assert_called_with(image_fakes.image_name) + # ImageManager.get(name) not to be called since update action exists + self.images_mock.get.assert_not_called() # ImageManager.create(name=, **) self.images_mock.create.assert_called_with( @@ -201,71 +201,6 @@ def test_image_create_file(self, mock_open): self.assertEqual(image_fakes.IMAGE_columns, columns) self.assertEqual(image_fakes.IMAGE_data, data) - def test_image_create_volume(self): - # Set up VolumeManager Mock - volumes_mock = self.app.client_manager.volume.volumes - volumes_mock.reset_mock() - volumes_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy({'id': 'vol1', 'name': 'volly'}), - loaded=True, - ) - response = { - "id": 'volume_id', - "updated_at": 'updated_at', - "status": 'uploading', - "display_description": 'desc', - "size": 'size', - "volume_type": 'volume_type', - "image_id": 'image1', - "container_format": image.DEFAULT_CONTAINER_FORMAT, - "disk_format": image.DEFAULT_DISK_FORMAT, - "image_name": image_fakes.image_name, - } - full_response = {"os-volume_upload_image": response} - volumes_mock.upload_to_image.return_value = (201, full_response) - - arglist = [ - '--volume', 'volly', - image_fakes.image_name, - ] - verifylist = [ - ('private', False), - ('protected', False), - ('public', False), - ('unprotected', False), - ('volume', 'volly'), - ('force', False), - ('name', image_fakes.image_name), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # DisplayCommandBase.take_action() returns two tuples - columns, data = self.cmd.take_action(parsed_args) - - # VolumeManager.upload_to_image(volume, force, image_name, - # container_format, disk_format) - volumes_mock.upload_to_image.assert_called_with( - 'vol1', - False, - image_fakes.image_name, - 'bare', - 'raw', - ) - - # ImageManager.update(image_id, remove_props=, **) - self.images_mock.update.assert_called_with( - image_fakes.image_id, - name=image_fakes.image_name, - container_format=image.DEFAULT_CONTAINER_FORMAT, - disk_format=image.DEFAULT_DISK_FORMAT, - properties=image_fakes.image_properties, - volume='volly', - ) - - self.assertEqual(image_fakes.IMAGE_columns, columns) - self.assertEqual(image_fakes.IMAGE_data, data) - class TestImageDelete(TestImage): @@ -669,6 +604,69 @@ def test_image_set_properties(self): **kwargs ) + def test_image_update_volume(self): + # Set up VolumeManager Mock + volumes_mock = self.app.client_manager.volume.volumes + volumes_mock.reset_mock() + volumes_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy({'id': 'vol1', 'name': 'volly'}), + loaded=True, + ) + response = { + "id": 'volume_id', + "updated_at": 'updated_at', + "status": 'uploading', + "display_description": 'desc', + "size": 'size', + "volume_type": 'volume_type', + "container_format": image.DEFAULT_CONTAINER_FORMAT, + "disk_format": image.DEFAULT_DISK_FORMAT, + "image": image_fakes.image_name, + } + full_response = {"os-volume_upload_image": response} + volumes_mock.upload_to_image.return_value = (201, full_response) + + arglist = [ + '--volume', 'volly', + '--name', 'updated_image', + image_fakes.image_name, + ] + verifylist = [ + ('private', False), + ('protected', False), + ('public', False), + ('unprotected', False), + ('volume', 'volly'), + ('force', False), + ('name', 'updated_image'), + ('image', image_fakes.image_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # VolumeManager.upload_to_image(volume, force, image_name, + # container_format, disk_format) + volumes_mock.upload_to_image.assert_called_with( + 'vol1', + False, + image_fakes.image_name, + '', + '', + ) + + # ImageManager.update(image_id, remove_props=, **) + self.images_mock.update.assert_called_with( + image_fakes.image_id, + name='updated_image', + volume='volly', + ) + + self.assertEqual(image_fakes.IMAGE_columns, columns) + self.assertEqual(image_fakes.IMAGE_data, data) + class TestImageShow(TestImage): From 3bfaf79732a5c4493ae700722c48a20dab8690a2 Mon Sep 17 00:00:00 2001 From: David Moreau Simard Date: Tue, 30 Jun 2015 14:36:51 -0400 Subject: [PATCH 0142/3095] Show which aggregate a hypervisor is member of This adds support for showing which host aggregates a hypervisor is member of, if any. It supports hypervisors with or without nova cells. Closes-bug: #1470875 Change-Id: I0cfe4f15fa8f8ba0be3295b79cd438998893114c --- openstackclient/compute/v2/hypervisor.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/openstackclient/compute/v2/hypervisor.py b/openstackclient/compute/v2/hypervisor.py index 65035d0431..f33beb0892 100644 --- a/openstackclient/compute/v2/hypervisor.py +++ b/openstackclient/compute/v2/hypervisor.py @@ -77,6 +77,29 @@ def take_action(self, parsed_args): hypervisor = utils.find_resource(compute_client.hypervisors, parsed_args.hypervisor)._info.copy() + aggregates = compute_client.aggregates.list() + hypervisor["aggregates"] = list() + if aggregates: + # Hypervisors in nova cells are prefixed by "@" + if "@" in hypervisor['service']['host']: + cell, service_host = hypervisor['service']['host'].split('@', + 1) + else: + cell = None + service_host = hypervisor['service']['host'] + + if cell: + # The host aggregates are also prefixed by "@" + member_of = [aggregate.name + for aggregate in aggregates + if cell in aggregate.name and + service_host in aggregate.hosts] + else: + member_of = [aggregate.name + for aggregate in aggregates + if service_host in aggregate.hosts] + hypervisor["aggregates"] = member_of + uptime = compute_client.hypervisors.uptime(hypervisor['id'])._info # Extract data from uptime value # format: 0 up 0, 0 users, load average: 0, 0, 0 From 5521e4c504c6a3a06f17a9e4f80444743aa293c7 Mon Sep 17 00:00:00 2001 From: Roxana Gherle Date: Fri, 22 May 2015 16:22:35 -0700 Subject: [PATCH 0143/3095] Add --os-endpoint-type cli optional argument User should be able to specify the endpoint type through a CLI optional argument/ENV variable setting. We will name this new optional argument: --os-endpoint-type (Env: OS_ENDPOINT_TYPE) and based on the value given, the service API will use that specific endpoint type. Possible values: public, admin, internal. DocImpact Closes-Bug: #1454392 Change-Id: Ife3d4e46b44c0ddcd712b1130e27e362545a9a29 --- doc/source/configuration.rst | 8 +++++++- doc/source/man/openstack.rst | 5 +++++ openstackclient/common/clientmanager.py | 10 ++++++++-- openstackclient/common/utils.py | 8 ++++++++ openstackclient/compute/client.py | 5 +++++ openstackclient/identity/client.py | 7 ++++++- openstackclient/image/client.py | 2 ++ openstackclient/network/client.py | 6 ++++++ openstackclient/object/client.py | 1 + openstackclient/shell.py | 14 +++++++++++++- openstackclient/tests/common/test_clientmanager.py | 11 +++++++++++ openstackclient/tests/common/test_utils.py | 10 ++++++++++ openstackclient/tests/fakes.py | 2 ++ openstackclient/tests/test_shell.py | 8 ++++++++ openstackclient/volume/client.py | 5 +++++ 15 files changed, 97 insertions(+), 5 deletions(-) diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst index e371e50448..b603eee425 100644 --- a/doc/source/configuration.rst +++ b/doc/source/configuration.rst @@ -78,6 +78,7 @@ The keys match the :program:`openstack` global options but without the username: openstack password: xyzpdq!lazydog region_name: DFW,ORD,IAD + endpoint_type: internal In the above example, the ``auth_url`` for the ``rackspace`` cloud is taken from :file:`clouds-public.yaml` (see below). @@ -96,6 +97,7 @@ to the following options if the ``rackspace`` entry in :file:`clouds-public.yaml --os-username openstack --os-password xyzpdq!lazydog --os-region-name DFW + --os-endpoint-type internal and can be selected on the command line:: @@ -105,13 +107,17 @@ Note that multiple regions are listed in the ``rackspace`` entry. An otherwise identical configuration is created for each region. If ``-os-region-name`` is not specified on the command line, the first region in the list is used by default. +The selection of ``endpoint_type`` (as seen above in the ``rackspace`` entry) +is optional. For this configuration to work, every service for this cloud +instance must already be configured to support this type of endpoint. + clouds-public.yaml ~~~~~~~~~~~~~~~~~~ :file:`clouds-public.yaml` is a configuration file that is intended to contain public information about clouds that are common across a large number of users. The idea is that :file:`clouds-public.yaml` could easily be shared among users -to simplify public could configuration. +to simplify public cloud configuration. Similar to :file:`clouds.yaml`, OpenStackClient looks for :file:`clouds-public.yaml` in the following locations: diff --git a/doc/source/man/openstack.rst b/doc/source/man/openstack.rst index 6d6dce4479..9d7115275c 100644 --- a/doc/source/man/openstack.rst +++ b/doc/source/man/openstack.rst @@ -120,6 +120,8 @@ OPTIONS :option:`--os-XXXX-api-version` Additional API version options will be available depending on the installed API libraries. +:option:`--os-endpoint-type` + Endpoint type. Valid options are `public`, `admin` and `internal`. COMMANDS ======== @@ -344,6 +346,9 @@ The following environment variables can be set to alter the behaviour of :progra :envvar:`OS_XXXX_API_VERSION` Additional API version options will be available depending on the installed API libraries. +:envvar:`OS_ENDPOINT_TYPE` + Endpoint type. Valid options are `public`, `admin` and `internal`. + BUGS ==== diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index 6311c71a50..2a57b8ff72 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -86,6 +86,7 @@ def __init__( self._pw_callback = pw_func self._url = self._cli_options.auth.get('url', None) self._region_name = self._cli_options.region_name + self._endpoint_type = self._cli_options.endpoint_type self.timing = self._cli_options.timing @@ -181,18 +182,23 @@ def auth_ref(self): self._auth_ref = self.auth.get_auth_ref(self.session) return self._auth_ref - def get_endpoint_for_service_type(self, service_type, region_name=None): + def get_endpoint_for_service_type(self, service_type, region_name=None, + endpoint_type='public'): """Return the endpoint URL for the service type.""" + if not endpoint_type: + endpoint_type = 'public' # See if we are using password flow auth, i.e. we have a # service catalog to select endpoints from if self.auth_ref: endpoint = self.auth_ref.service_catalog.url_for( service_type=service_type, region_name=region_name, + endpoint_type=endpoint_type, ) else: # Get the passed endpoint directly from the auth plugin - endpoint = self.auth.get_endpoint(self.session) + endpoint = self.auth.get_endpoint(self.session, + interface=endpoint_type) return endpoint diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index aad0519c1b..caafa83732 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -368,3 +368,11 @@ def read_blob_file_contents(blob_file): except IOError: msg = "Error occurred trying to read from file %s" raise exceptions.CommandError(msg % blob_file) + + +def build_kwargs_dict(arg_name, value): + """Return a dictionary containing `arg_name` if `value` is set.""" + kwargs = {} + if value: + kwargs[arg_name] = value + return kwargs diff --git a/openstackclient/compute/client.py b/openstackclient/compute/client.py index 93a7b71537..27d63a9575 100644 --- a/openstackclient/compute/client.py +++ b/openstackclient/compute/client.py @@ -48,12 +48,17 @@ def make_client(instance): extensions = [extension.Extension('list_extensions', list_extensions)] + # Remember endpoint_type only if it is set + kwargs = utils.build_kwargs_dict('endpoint_type', + instance._endpoint_type) + client = compute_client( session=instance.session, extensions=extensions, http_log_debug=http_log_debug, timings=instance.timing, region_name=instance._region_name, + **kwargs ) return client diff --git a/openstackclient/identity/client.py b/openstackclient/identity/client.py index 4127a451e5..cc803511d6 100644 --- a/openstackclient/identity/client.py +++ b/openstackclient/identity/client.py @@ -46,10 +46,15 @@ def make_client(instance): API_VERSIONS) LOG.debug('Instantiating identity client: %s', identity_client) + # Remember interface only if endpoint_type is set + kwargs = utils.build_kwargs_dict('interface', + instance._endpoint_type) + client = identity_client( session=instance.session, region_name=instance._region_name, - ) + **kwargs + ) return client diff --git a/openstackclient/image/client.py b/openstackclient/image/client.py index c78f442524..8e2d6cd979 100644 --- a/openstackclient/image/client.py +++ b/openstackclient/image/client.py @@ -46,6 +46,7 @@ def make_client(instance): endpoint = instance.get_endpoint_for_service_type( API_NAME, region_name=instance._region_name, + endpoint_type=instance._endpoint_type, ) client = image_client( @@ -68,6 +69,7 @@ def make_client(instance): endpoint=instance.get_endpoint_for_service_type( IMAGE_API_TYPE, region_name=instance._region_name, + endpoint_type=instance._endpoint_type, ) ) diff --git a/openstackclient/network/client.py b/openstackclient/network/client.py index 870566aaa4..de08e5e2fe 100644 --- a/openstackclient/network/client.py +++ b/openstackclient/network/client.py @@ -47,11 +47,17 @@ def make_client(instance): endpoint = instance.get_endpoint_for_service_type( API_NAME, region_name=instance._region_name, + endpoint_type=instance._endpoint_type, ) + # Remember endpoint_type only if it is set + kwargs = utils.build_kwargs_dict('endpoint_type', + instance._endpoint_type) + client = network_client( session=instance.session, region_name=instance._region_name, + **kwargs ) network_api = utils.get_client_class( diff --git a/openstackclient/object/client.py b/openstackclient/object/client.py index beb7c04fc1..676f664217 100644 --- a/openstackclient/object/client.py +++ b/openstackclient/object/client.py @@ -36,6 +36,7 @@ def make_client(instance): endpoint = instance.get_endpoint_for_service_type( 'object-store', region_name=instance._region_name, + endpoint_type=instance._endpoint_type, ) client = object_store_v1.APIv1( diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 36483b3a7e..6c9095868b 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -208,6 +208,15 @@ def build_option_parser(self, description, version): help='Default domain ID, default=' + DEFAULT_DOMAIN + ' (Env: OS_DEFAULT_DOMAIN)') + parser.add_argument( + '--os-endpoint-type', + metavar='', + dest='endpoint_type', + choices=['admin', 'public', 'internal'], + default=utils.env('OS_ENDPOINT_TYPE'), + help='Select an endpoint type.' + ' Valid endpoint types: [admin, public, internal].' + ' (Env: OS_ENDPOINT_TYPE)') parser.add_argument( '--timing', default=False, @@ -254,7 +263,10 @@ def initialize_app(self, argv): self.options.project_name = tenant_name # Do configuration file handling - cc = cloud_config.OpenStackConfig() + # Ignore the default value of endpoint_type. Only if it is set later + # will it be used. + cc = cloud_config.OpenStackConfig( + override_defaults={'endpoint_type': None, }) self.log.debug("defaults: %s", cc.defaults) self.cloud = cc.get_one_cloud( diff --git a/openstackclient/tests/common/test_clientmanager.py b/openstackclient/tests/common/test_clientmanager.py index 4e2f46b4ce..e86ef50997 100644 --- a/openstackclient/tests/common/test_clientmanager.py +++ b/openstackclient/tests/common/test_clientmanager.py @@ -54,6 +54,7 @@ def __init__(self, **kwargs): self.identity_api_version = '2.0' self.timing = None self.region_name = None + self.endpoint_type = None self.url = None self.auth = {} self.default_domain = 'default' @@ -123,6 +124,8 @@ def test_client_manager_token(self): auth_url=fakes.AUTH_URL, ), auth_type='v2token', + endpoint_type=fakes.ENDPOINT_TYPE, + region_name=fakes.REGION_NAME, ), api_version=API_VERSION, verify=True @@ -137,6 +140,14 @@ def test_client_manager_token(self): client_manager.auth, auth_v2.Token, ) + self.assertEqual( + fakes.ENDPOINT_TYPE, + client_manager._endpoint_type, + ) + self.assertEqual( + fakes.REGION_NAME, + client_manager._region_name, + ) self.assertFalse(client_manager._insecure) self.assertTrue(client_manager._verify) diff --git a/openstackclient/tests/common/test_utils.py b/openstackclient/tests/common/test_utils.py index d9f5b7a56e..a25a5ba510 100644 --- a/openstackclient/tests/common/test_utils.py +++ b/openstackclient/tests/common/test_utils.py @@ -159,6 +159,16 @@ def test_wait_for_delete_error(self, mock_sleep): self.assertFalse(utils.wait_for_delete(manager, res_id)) self.assertFalse(mock_sleep.called) + def test_build_kwargs_dict_value_set(self): + self.assertEqual({'arg_bla': 'bla'}, + utils.build_kwargs_dict('arg_bla', 'bla')) + + def test_build_kwargs_dict_value_None(self): + self.assertEqual({}, utils.build_kwargs_dict('arg_bla', None)) + + def test_build_kwargs_dict_value_empty_str(self): + self.assertEqual({}, utils.build_kwargs_dict('arg_bla', '')) + class NoUniqueMatch(Exception): pass diff --git a/openstackclient/tests/fakes.py b/openstackclient/tests/fakes.py index 323f954306..a9322ec3ed 100644 --- a/openstackclient/tests/fakes.py +++ b/openstackclient/tests/fakes.py @@ -26,6 +26,8 @@ USERNAME = "itchy" PASSWORD = "scratchy" PROJECT_NAME = "poochie" +REGION_NAME = "richie" +ENDPOINT_TYPE = "catchy" TEST_RESPONSE_DICT = fixture.V2Token(token_id=AUTH_TOKEN, user_name=USERNAME) diff --git a/openstackclient/tests/test_shell.py b/openstackclient/tests/test_shell.py index b080ae9164..674d83452d 100644 --- a/openstackclient/tests/test_shell.py +++ b/openstackclient/tests/test_shell.py @@ -38,6 +38,7 @@ DEFAULT_TOKEN = "token" DEFAULT_SERVICE_URL = "http://127.0.0.1:8771/v3.0/" DEFAULT_AUTH_PLUGIN = "v2password" +DEFAULT_ENDPOINT_TYPE = "internal" DEFAULT_COMPUTE_API_VERSION = "2" DEFAULT_IDENTITY_API_VERSION = "2" @@ -61,6 +62,7 @@ }, 'region_name': 'occ-cloud', 'donut': 'glazed', + 'endpoint_type': 'public', } } } @@ -104,6 +106,7 @@ '--os-default-domain': (DEFAULT_DOMAIN_NAME, True, True), '--os-cacert': ('/dev/null', True, True), '--timing': (True, True, False), + '--os-endpoint-type': (DEFAULT_ENDPOINT_TYPE, True, True) } auth_options = { @@ -123,6 +126,7 @@ '--os-auth-type': ("v2password", True, True), '--os-token': (DEFAULT_TOKEN, True, True), '--os-url': (DEFAULT_SERVICE_URL, True, True), + '--os-endpoint-type': (DEFAULT_ENDPOINT_TYPE, True, True), } @@ -608,6 +612,10 @@ def test_shell_args_cloud_no_vendor(self, config_mock): 'glazed', _shell.cloud.config['donut'], ) + self.assertEqual( + 'public', + _shell.cloud.config['endpoint_type'], + ) @mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file") @mock.patch("os_client_config.config.OpenStackConfig._load_config_file") diff --git a/openstackclient/volume/client.py b/openstackclient/volume/client.py index 1038c407e4..965c42ec2d 100644 --- a/openstackclient/volume/client.py +++ b/openstackclient/volume/client.py @@ -53,11 +53,16 @@ def make_client(instance): extensions = [extension.Extension('list_extensions', list_extensions)] + # Remember endpoint_type only if it is set + kwargs = utils.build_kwargs_dict('endpoint_type', + instance._endpoint_type) + client = volume_client( session=instance.session, extensions=extensions, http_log_debug=http_log_debug, region_name=instance._region_name, + **kwargs ) return client From aa3b3c1f0f39e05c242ddfc840774d2d23f91b46 Mon Sep 17 00:00:00 2001 From: Guojian Shao Date: Sat, 4 Jul 2015 21:40:31 +0800 Subject: [PATCH 0144/3095] add functional tests for identity v3 To make test cases more clearly, split test_identity.py into test_user.py, test_role, etc. Add more test cases for user, role, etc. Furthermore, to make functional tests run repeatedly without raising duplicated error, clean up resources before exiting each test case. Change-Id: I1541943ad0b8d4d8d1e72822c159fda243b3d1d7 Implements: blueprint identity-functional-tests --- functional/common/test.py | 20 ++ functional/tests/identity/v3/test_catalog.py | 42 ++++ functional/tests/identity/v3/test_domain.py | 57 +++++ functional/tests/identity/v3/test_group.py | 178 ++++++++++++++++ functional/tests/identity/v3/test_identity.py | 201 ++++++++++++------ functional/tests/identity/v3/test_project.py | 113 ++++++++++ functional/tests/identity/v3/test_role.py | 145 +++++++++++++ functional/tests/identity/v3/test_token.py | 21 ++ functional/tests/identity/v3/test_user.py | 67 ++++++ test-requirements.txt | 1 + 10 files changed, 785 insertions(+), 60 deletions(-) create mode 100644 functional/tests/identity/v3/test_catalog.py create mode 100644 functional/tests/identity/v3/test_domain.py create mode 100644 functional/tests/identity/v3/test_group.py create mode 100644 functional/tests/identity/v3/test_project.py create mode 100644 functional/tests/identity/v3/test_role.py create mode 100644 functional/tests/identity/v3/test_token.py create mode 100644 functional/tests/identity/v3/test_user.py diff --git a/functional/common/test.py b/functional/common/test.py index ef034276e4..50d59fd19f 100644 --- a/functional/common/test.py +++ b/functional/common/test.py @@ -89,6 +89,26 @@ def assert_show_fields(self, items, field_names): for key in six.iterkeys(item): self.assertIn(key, field_names) + def assert_show_structure(self, items, field_names): + """Verify that all field_names listed in keys of all items.""" + if isinstance(items, list): + o = {} + for d in items: + o.update(d) + else: + o = items + item_keys = o.keys() + for field in field_names: + self.assertIn(field, item_keys) + + def parse_show_as_object(self, raw_output): + """Return a dict with values parsed from cli output.""" + items = self.parse_show(raw_output) + o = {} + for item in items: + o.update(item) + return o + def parse_show(self, raw_output): """Return list of dicts with item values parsed from cli output.""" diff --git a/functional/tests/identity/v3/test_catalog.py b/functional/tests/identity/v3/test_catalog.py new file mode 100644 index 0000000000..ec4d035ba6 --- /dev/null +++ b/functional/tests/identity/v3/test_catalog.py @@ -0,0 +1,42 @@ +# 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 functional.tests.identity.v3 import test_identity + + +class CatalogTests(test_identity.IdentityTests): + + def test_catalog_list(self): + raw_output = self.openstack('catalog list') + items = self.parse_listing(raw_output) + self.assert_table_structure(items, ['Name', 'Type', 'Endpoints']) + + def test_catalog_show(self): + """test catalog show command + + The output example: + +-----------+-------------------------------------------+ + | Field | Value | + +-----------+-------------------------------------------+ + | endpoints | test1 | + | | publicURL: http://localhost:5000/v2.0 | + | | internalURL: http://localhost:5000/v2.0 | + | | adminURL: http://localhost:5000/v2.0 | + | | | + | name | keystone | + | type | identity | + +-----------+-------------------------------------------+ + """ + raw_output = self.openstack('catalog show %s' % 'identity') + items = self.parse_show(raw_output) + # items may have multiple endpoint urls with empty key + self.assert_show_fields(items, ['endpoints', 'name', 'type', '', 'id']) diff --git a/functional/tests/identity/v3/test_domain.py b/functional/tests/identity/v3/test_domain.py new file mode 100644 index 0000000000..f3ae4e890b --- /dev/null +++ b/functional/tests/identity/v3/test_domain.py @@ -0,0 +1,57 @@ +# 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 tempest_lib.common.utils import data_utils + +from functional.common import exceptions +from functional.tests.identity.v3 import test_identity + + +class DomainTests(test_identity.IdentityTests): + + def test_domain_create(self): + domain_name = data_utils.rand_name('TestDomain') + raw_output = self.openstack('domain create %s' % domain_name) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.DOMAIN_FIELDS) + # disable domain first before deleting it + self.addCleanup(self.openstack, + 'domain delete %s' % domain_name) + self.addCleanup(self.openstack, + 'domain set --disable %s' % domain_name) + + def test_domain_list(self): + self._create_dummy_domain() + raw_output = self.openstack('domain list') + items = self.parse_listing(raw_output) + self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS) + + def test_domain_delete(self): + domain_name = self._create_dummy_domain(add_clean_up=False) + # cannot delete enabled domain, disable it first + raw_output = self.openstack('domain set --disable %s' % domain_name) + self.assertEqual(0, len(raw_output)) + raw_output = self.openstack('domain delete %s' % domain_name) + self.assertEqual(0, len(raw_output)) + + def test_domain_delete_failure(self): + domain_name = self._create_dummy_domain() + # cannot delete enabled domain + self.assertRaises(exceptions.CommandFailed, + self.openstack, + 'domain delete %s' % domain_name) + + def test_domain_show(self): + domain_name = self._create_dummy_domain() + raw_output = self.openstack('domain show %s' % domain_name) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.DOMAIN_FIELDS) diff --git a/functional/tests/identity/v3/test_group.py b/functional/tests/identity/v3/test_group.py new file mode 100644 index 0000000000..2d7f8f383f --- /dev/null +++ b/functional/tests/identity/v3/test_group.py @@ -0,0 +1,178 @@ +# 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 tempest_lib.common.utils import data_utils + +from functional.tests.identity.v3 import test_identity + + +class GroupTests(test_identity.IdentityTests): + + def test_group_create(self): + self._create_dummy_group() + + def test_group_list(self): + group_name = self._create_dummy_group() + raw_output = self.openstack('group list') + items = self.parse_listing(raw_output) + self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS) + self.assertInOutput(group_name, raw_output) + + def test_group_list_with_domain(self): + group_name = self._create_dummy_group() + raw_output = self.openstack( + 'group list --domain %s' % self.domain_name) + items = self.parse_listing(raw_output) + self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS) + self.assertInOutput(group_name, raw_output) + + def test_group_delete(self): + group_name = self._create_dummy_group(add_clean_up=False) + raw_output = self.openstack( + 'group delete ' + '--domain %(domain)s ' + '%(name)s' % {'domain': self.domain_name, + 'name': group_name}) + self.assertEqual(0, len(raw_output)) + + def test_group_show(self): + group_name = self._create_dummy_group() + raw_output = self.openstack( + 'group show ' + '--domain %(domain)s ' + '%(name)s' % {'domain': self.domain_name, + 'name': group_name}) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.GROUP_FIELDS) + + def test_group_set(self): + group_name = self._create_dummy_group() + new_group_name = data_utils.rand_name('NewTestGroup') + raw_output = self.openstack( + 'group set ' + '--domain %(domain)s ' + '--name %(new_group)s ' + '%(group)s' % {'domain': self.domain_name, + 'new_group': new_group_name, + 'group': group_name}) + self.assertEqual(0, len(raw_output)) + raw_output = self.openstack( + 'group show ' + '--domain %(domain)s ' + '%(group)s' % {'domain': self.domain_name, + 'group': new_group_name}) + group = self.parse_show_as_object(raw_output) + self.assertEqual(new_group_name, group['name']) + # reset group name to make sure it will be cleaned up + raw_output = self.openstack( + 'group set ' + '--domain %(domain)s ' + '--name %(new_group)s ' + '%(group)s' % {'domain': self.domain_name, + 'new_group': group_name, + 'group': new_group_name}) + self.assertEqual(0, len(raw_output)) + + def test_group_add_user(self): + group_name = self._create_dummy_group() + username = self._create_dummy_user() + raw_output = self.openstack( + 'group add user ' + '--group-domain %(group_domain)s ' + '--user-domain %(user_domain)s ' + '%(group)s %(user)s' % {'group_domain': self.domain_name, + 'user_domain': self.domain_name, + 'group': group_name, + 'user': username}) + self.assertOutput( + '%(user)s added to group %(group)s\n' % {'user': username, + 'group': group_name}, + raw_output + ) + self.addCleanup( + self.openstack, + 'group remove user ' + '--group-domain %(group_domain)s ' + '--user-domain %(user_domain)s ' + '%(group)s %(user)s' % {'group_domain': self.domain_name, + 'user_domain': self.domain_name, + 'group': group_name, + 'user': username}) + + def test_group_contains_user(self): + group_name = self._create_dummy_group() + username = self._create_dummy_user() + raw_output = self.openstack( + 'group add user ' + '--group-domain %(group_domain)s ' + '--user-domain %(user_domain)s ' + '%(group)s %(user)s' % {'group_domain': self.domain_name, + 'user_domain': self.domain_name, + 'group': group_name, + 'user': username}) + self.assertOutput( + '%(user)s added to group %(group)s\n' % {'user': username, + 'group': group_name}, + raw_output + ) + raw_output = self.openstack( + 'group contains user ' + '--group-domain %(group_domain)s ' + '--user-domain %(user_domain)s ' + '%(group)s %(user)s' % {'group_domain': self.domain_name, + 'user_domain': self.domain_name, + 'group': group_name, + 'user': username}) + self.assertOutput( + '%(user)s in group %(group)s\n' % {'user': username, + 'group': group_name}, + raw_output) + self.addCleanup( + self.openstack, + 'group remove user ' + '--group-domain %(group_domain)s ' + '--user-domain %(user_domain)s ' + '%(group)s %(user)s' % {'group_domain': self.domain_name, + 'user_domain': self.domain_name, + 'group': group_name, + 'user': username}) + + def test_group_remove_user(self): + group_name = self._create_dummy_group() + username = self._create_dummy_user() + raw_output = self.openstack( + 'group add user ' + '--group-domain %(group_domain)s ' + '--user-domain %(user_domain)s ' + '%(group)s %(user)s' % {'group_domain': self.domain_name, + 'user_domain': self.domain_name, + 'group': group_name, + 'user': username}) + self.assertOutput( + '%(user)s added to group %(group)s\n' % {'user': username, + 'group': group_name}, + raw_output + ) + raw_output = self.openstack( + 'group remove user ' + '--group-domain %(group_domain)s ' + '--user-domain %(user_domain)s ' + '%(group)s %(user)s' % {'group_domain': self.domain_name, + 'user_domain': self.domain_name, + 'group': group_name, + 'user': username}) + self.assertOutput( + '%(user)s removed from ' + 'group %(group)s\n' % {'user': username, + 'group': group_name}, + raw_output + ) diff --git a/functional/tests/identity/v3/test_identity.py b/functional/tests/identity/v3/test_identity.py index 5d28f189d2..c72dc9a05f 100644 --- a/functional/tests/identity/v3/test_identity.py +++ b/functional/tests/identity/v3/test_identity.py @@ -11,11 +11,12 @@ # under the License. import os -import uuid -from functional.common import exceptions +from tempest_lib.common.utils import data_utils + from functional.common import test + BASIC_LIST_HEADERS = ['ID', 'Name'] @@ -25,19 +26,18 @@ class IdentityTests(test.TestCase): DOMAIN_FIELDS = ['description', 'enabled', 'id', 'name', 'links'] GROUP_FIELDS = ['description', 'domain_id', 'id', 'name', 'links'] TOKEN_FIELDS = ['expires', 'id', 'project_id', 'user_id'] - - def _create_dummy_group(self): - name = uuid.uuid4().hex - self.openstack('group create ' + name) - return name - - def _create_dummy_domain(self): - name = uuid.uuid4().hex - self.openstack('domain create ' + name) - return name - - def setUp(self): - super(IdentityTests, self).setUp() + USER_FIELDS = ['email', 'enabled', 'id', 'name', 'name', + 'domain_id', 'default_project_id', 'description'] + PROJECT_FIELDS = ['description', 'id', 'domain_id', + 'enabled', 'name', 'parent_id', 'links'] + ROLE_FIELDS = ['id', 'name', 'links'] + + @classmethod + def setUpClass(cls): + if hasattr(super(IdentityTests, cls), 'setUpClass'): + super(IdentityTests, cls).setUpClass() + + # prepare v3 env auth_url = os.environ.get('OS_AUTH_URL') auth_url = auth_url.replace('v2.0', 'v3') os.environ['OS_AUTH_URL'] = auth_url @@ -45,51 +45,132 @@ def setUp(self): os.environ['OS_USER_DOMAIN_ID'] = 'default' os.environ['OS_PROJECT_DOMAIN_ID'] = 'default' - def test_group_create(self): - raw_output = self.openstack('group create ' + uuid.uuid4().hex) + # create dummy domain + cls.domain_name = data_utils.rand_name('TestDomain') + cls.domain_description = data_utils.rand_name('description') + cls.openstack( + 'domain create ' + '--description %(description)s ' + '--enable ' + '%(name)s' % {'description': cls.domain_description, + 'name': cls.domain_name}) + + # create dummy project + cls.project_name = data_utils.rand_name('TestProject') + cls.project_description = data_utils.rand_name('description') + cls.openstack( + 'project create ' + '--domain %(domain)s ' + '--description %(description)s ' + '--enable ' + '%(name)s' % {'domain': cls.domain_name, + 'description': cls.project_description, + 'name': cls.project_name}) + + @classmethod + def tearDownClass(cls): + cls.openstack('project delete %s' % cls.project_name) + cls.openstack('domain set --disable %s' % cls.domain_name) + cls.openstack('domain delete %s' % cls.domain_name) + + if hasattr(super(IdentityTests, cls), 'tearDownClass'): + super(IdentityTests, cls).tearDownClass() + + def _create_dummy_user(self, add_clean_up=True): + username = data_utils.rand_name('TestUser') + password = data_utils.rand_name('password') + email = data_utils.rand_name() + '@example.com' + description = data_utils.rand_name('description') + raw_output = self.openstack( + 'user create ' + '--domain %(domain)s ' + '--project %(project)s ' + '--password %(password)s ' + '--email %(email)s ' + '--description %(description)s ' + '--enable ' + '%(name)s' % {'domain': self.domain_name, + 'project': self.project_name, + 'email': email, + 'password': password, + 'description': description, + 'name': username}) items = self.parse_show(raw_output) - self.assert_show_fields(items, self.GROUP_FIELDS) - - def test_group_list(self): - self._create_dummy_group() - raw_output = self.openstack('group list') - items = self.parse_listing(raw_output) - self.assert_table_structure(items, BASIC_LIST_HEADERS) - - def test_group_delete(self): - name = self._create_dummy_group() - raw_output = self.openstack('group delete ' + name) - self.assertEqual(0, len(raw_output)) - - def test_group_show(self): - name = self._create_dummy_group() - raw_output = self.openstack('group show ' + name) - items = self.parse_show(raw_output) - self.assert_show_fields(items, self.GROUP_FIELDS) - - def test_domain_create(self): - raw_output = self.openstack('domain create ' + uuid.uuid4().hex) + self.assert_show_fields(items, self.USER_FIELDS) + if add_clean_up: + self.addCleanup( + self.openstack, + 'user delete %s' % self.parse_show_as_object(raw_output)['id']) + return username + + def _create_dummy_role(self, add_clean_up=True): + role_name = data_utils.rand_name('TestRole') + raw_output = self.openstack('role create %s' % role_name) items = self.parse_show(raw_output) - self.assert_show_fields(items, self.DOMAIN_FIELDS) - - def test_domain_list(self): - self._create_dummy_domain() - raw_output = self.openstack('domain list') - items = self.parse_listing(raw_output) - self.assert_table_structure(items, BASIC_LIST_HEADERS) - - def test_domain_delete(self): - name = self._create_dummy_domain() - self.assertRaises(exceptions.CommandFailed, - self.openstack, 'domain delete ' + name) - - def test_domain_show(self): - name = self._create_dummy_domain() - raw_output = self.openstack('domain show ' + name) + self.assert_show_fields(items, self.ROLE_FIELDS) + role = self.parse_show_as_object(raw_output) + self.assertEqual(role_name, role['name']) + if add_clean_up: + self.addCleanup( + self.openstack, + 'role delete %s' % role['id']) + return role_name + + def _create_dummy_group(self, add_clean_up=True): + group_name = data_utils.rand_name('TestGroup') + description = data_utils.rand_name('description') + raw_output = self.openstack( + 'group create ' + '--domain %(domain)s ' + '--description %(description)s ' + '%(name)s' % {'domain': self.domain_name, + 'description': description, + 'name': group_name}) items = self.parse_show(raw_output) - self.assert_show_fields(items, self.DOMAIN_FIELDS) - - def test_token_issue(self): - raw_output = self.openstack('token issue') - items = self.parse_show(raw_output) - self.assert_show_fields(items, self.TOKEN_FIELDS) + self.assert_show_fields(items, self.GROUP_FIELDS) + if add_clean_up: + self.addCleanup( + self.openstack, + 'group delete ' + '--domain %(domain)s ' + '%(name)s' % {'domain': self.domain_name, + 'name': group_name}) + return group_name + + def _create_dummy_domain(self, add_clean_up=True): + domain_name = data_utils.rand_name('TestDomain') + domain_description = data_utils.rand_name('description') + self.openstack( + 'domain create ' + '--description %(description)s ' + '--enable %(name)s' % {'description': domain_description, + 'name': domain_name}) + if add_clean_up: + self.addCleanup( + self.openstack, + 'domain delete %s' % domain_name + ) + self.addCleanup( + self.openstack, + 'domain set --disable %s' % domain_name + ) + return domain_name + + def _create_dummy_project(self, add_clean_up=True): + project_name = data_utils.rand_name('TestProject') + project_description = data_utils.rand_name('description') + self.openstack( + 'project create ' + '--domain %(domain)s ' + '--description %(description)s ' + '--enable %(name)s' % {'domain': self.domain_name, + 'description': project_description, + 'name': project_name}) + if add_clean_up: + self.addCleanup( + self.openstack, + 'project delete ' + '--domain %(domain)s ' + '%(name)s' % {'domain': self.domain_name, + 'name': project_name}) + return project_name diff --git a/functional/tests/identity/v3/test_project.py b/functional/tests/identity/v3/test_project.py new file mode 100644 index 0000000000..204a8d14e4 --- /dev/null +++ b/functional/tests/identity/v3/test_project.py @@ -0,0 +1,113 @@ +# 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 tempest_lib.common.utils import data_utils + +from functional.tests.identity.v3 import test_identity + + +class ProjectTests(test_identity.IdentityTests): + + def test_project_create(self): + project_name = data_utils.rand_name('TestProject') + description = data_utils.rand_name('description') + raw_output = self.openstack( + 'project create ' + '--domain %(domain)s ' + '--description %(description)s ' + '--enable ' + '--property k1=v1 ' + '--property k2=v2 ' + '%(name)s' % {'domain': self.domain_name, + 'description': description, + 'name': project_name}) + self.addCleanup( + self.openstack, + 'project delete ' + '--domain %(domain)s ' + '%(name)s' % {'domain': self.domain_name, + 'name': project_name} + ) + items = self.parse_show(raw_output) + show_fields = list(self.PROJECT_FIELDS) + show_fields.extend(['k1', 'k2']) + self.assert_show_fields(items, show_fields) + project = self.parse_show_as_object(raw_output) + self.assertEqual('v1', project['k1']) + self.assertEqual('v2', project['k2']) + + def test_project_delete(self): + project_name = self._create_dummy_project(add_clean_up=False) + raw_output = self.openstack( + 'project delete ' + '--domain %(domain)s ' + '%(name)s' % {'domain': self.domain_name, + 'name': project_name}) + self.assertEqual(0, len(raw_output)) + + def test_project_list(self): + raw_output = self.openstack('project list') + items = self.parse_listing(raw_output) + self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS) + + def test_project_list_with_domain(self): + project_name = self._create_dummy_project() + raw_output = self.openstack( + 'project list --domain %s' % self.domain_name) + items = self.parse_listing(raw_output) + self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS) + self.assertInOutput(project_name, raw_output) + self.assertTrue(len(items) > 0) + + def test_project_set(self): + project_name = self._create_dummy_project() + new_project_name = data_utils.rand_name('NewTestProject') + raw_output = self.openstack( + 'project set ' + '--name %(new_name)s ' + '--disable ' + '--property k0=v0 ' + '%(name)s' % {'new_name': new_project_name, + 'domain': self.domain_name, + 'name': project_name}) + self.assertEqual(0, len(raw_output)) + # check project details + raw_output = self.openstack( + 'project show ' + '--domain %(domain)s ' + '%(name)s' % {'domain': self.domain_name, + 'name': new_project_name} + ) + items = self.parse_show(raw_output) + fields = list(self.PROJECT_FIELDS) + fields.extend(['k0']) + self.assert_show_fields(items, fields) + project = self.parse_show_as_object(raw_output) + self.assertEqual(new_project_name, project['name']) + self.assertEqual('False', project['enabled']) + self.assertEqual('v0', project['k0']) + # reset project to make sure it will be cleaned up + self.openstack( + 'project set ' + '--name %(new_name)s ' + '--enable ' + '%(name)s' % {'new_name': project_name, + 'name': new_project_name}) + + def test_project_show(self): + raw_output = self.openstack( + 'project show ' + '--domain %(domain)s ' + '%(name)s' % {'domain': self.domain_name, + 'name': self.project_name}) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.PROJECT_FIELDS) diff --git a/functional/tests/identity/v3/test_role.py b/functional/tests/identity/v3/test_role.py new file mode 100644 index 0000000000..7e0cf76e95 --- /dev/null +++ b/functional/tests/identity/v3/test_role.py @@ -0,0 +1,145 @@ +# 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 tempest_lib.common.utils import data_utils + +from functional.tests.identity.v3 import test_identity + + +class RoleTests(test_identity.IdentityTests): + + def test_role_create(self): + self._create_dummy_role() + + def test_role_delete(self): + role_name = self._create_dummy_role(add_clean_up=False) + raw_output = self.openstack('role delete %s' % role_name) + self.assertEqual(0, len(raw_output)) + + def test_role_list(self): + self._create_dummy_role() + raw_output = self.openstack('role list') + items = self.parse_listing(raw_output) + self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS) + + def test_role_list_with_user_project(self): + role_name = self._create_dummy_role() + username = self._create_dummy_user() + raw_output = self.openstack( + 'role add ' + '--project %(project)s ' + '--project-domain %(project_domain)s ' + '--user %(user)s ' + '--user-domain %(user_domain)s ' + '%(role)s' % {'project': self.project_name, + 'project_domain': self.domain_name, + 'user': username, + 'user_domain': self.domain_name, + 'role': role_name}) + self.assertEqual(0, len(raw_output)) + raw_output = self.openstack( + 'role list ' + '--project %(project)s ' + '--project-domain %(project_domain)s ' + '--user %(user)s ' + '--user-domain %(user_domain)s ' + '' % {'project': self.project_name, + 'project_domain': self.domain_name, + 'user': username, + 'user_domain': self.domain_name}) + items = self.parse_listing(raw_output) + self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS) + self.assertEqual(1, len(items)) + self.addCleanup( + self.openstack, + 'role remove ' + '--project %(project)s ' + '--project-domain %(project_domain)s ' + '--user %(user)s ' + '--user-domain %(user_domain)s ' + '%(role)s' % {'project': self.project_name, + 'project_domain': self.domain_name, + 'user': username, + 'user_domain': self.domain_name, + 'role': role_name}) + + def test_role_show(self): + role_name = self._create_dummy_role() + raw_output = self.openstack('role show %s' % role_name) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.ROLE_FIELDS) + + def test_role_set(self): + role_name = self._create_dummy_role() + new_role_name = data_utils.rand_name('NewTestRole') + raw_output = self.openstack( + 'role set --name %s %s' % (new_role_name, role_name)) + self.assertEqual(0, len(raw_output)) + raw_output = self.openstack('role show %s' % new_role_name) + role = self.parse_show_as_object(raw_output) + self.assertEqual(new_role_name, role['name']) + + def test_role_add(self): + role_name = self._create_dummy_role() + username = self._create_dummy_user() + raw_output = self.openstack( + 'role add ' + '--project %(project)s ' + '--project-domain %(project_domain)s ' + '--user %(user)s ' + '--user-domain %(user_domain)s ' + '%(role)s' % {'project': self.project_name, + 'project_domain': self.domain_name, + 'user': username, + 'user_domain': self.domain_name, + 'role': role_name}) + self.assertEqual(0, len(raw_output)) + self.addCleanup( + self.openstack, + 'role remove ' + '--project %(project)s ' + '--project-domain %(project_domain)s ' + '--user %(user)s ' + '--user-domain %(user_domain)s ' + '%(role)s' % {'project': self.project_name, + 'project_domain': self.domain_name, + 'user': username, + 'user_domain': self.domain_name, + 'role': role_name}) + + def test_role_remove(self): + role_name = self._create_dummy_role() + username = self._create_dummy_user() + raw_output = self.openstack( + 'role add ' + '--project %(project)s ' + '--project-domain %(project_domain)s ' + '--user %(user)s ' + '--user-domain %(user_domain)s ' + '%(role)s' % {'project': self.project_name, + 'project_domain': self.domain_name, + 'user': username, + 'user_domain': self.domain_name, + 'role': role_name}) + self.assertEqual(0, len(raw_output)) + raw_output = self.openstack( + 'role remove ' + '--project %(project)s ' + '--project-domain %(project_domain)s ' + '--user %(user)s ' + '--user-domain %(user_domain)s ' + '%(role)s' % {'project': self.project_name, + 'project_domain': self.domain_name, + 'user': username, + 'user_domain': self.domain_name, + 'role': role_name}) + self.assertEqual(0, len(raw_output)) diff --git a/functional/tests/identity/v3/test_token.py b/functional/tests/identity/v3/test_token.py new file mode 100644 index 0000000000..67fddccf68 --- /dev/null +++ b/functional/tests/identity/v3/test_token.py @@ -0,0 +1,21 @@ +# 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 functional.tests.identity.v3 import test_identity + + +class TokenTests(test_identity.IdentityTests): + + def test_token_issue(self): + raw_output = self.openstack('token issue') + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.TOKEN_FIELDS) diff --git a/functional/tests/identity/v3/test_user.py b/functional/tests/identity/v3/test_user.py new file mode 100644 index 0000000000..69420b96f2 --- /dev/null +++ b/functional/tests/identity/v3/test_user.py @@ -0,0 +1,67 @@ +# 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 tempest_lib.common.utils import data_utils + +from functional.tests.identity.v3 import test_identity + + +class UserTests(test_identity.IdentityTests): + + def test_user_create(self): + self._create_dummy_user() + + def test_user_delete(self): + username = self._create_dummy_user(add_clean_up=False) + raw_output = self.openstack('user delete ' + '--domain %(domain)s ' + '%(name)s' % {'domain': self.domain_name, + 'name': username}) + self.assertEqual(0, len(raw_output)) + + def test_user_list(self): + raw_output = self.openstack('user list') + items = self.parse_listing(raw_output) + self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS) + + def test_user_set(self): + username = self._create_dummy_user() + raw_output = self.openstack('user show ' + '--domain %(domain)s ' + '%(name)s' % {'domain': self.domain_name, + 'name': username}) + user = self.parse_show_as_object(raw_output) + new_username = data_utils.rand_name('NewTestUser') + new_email = data_utils.rand_name() + '@example.com' + raw_output = self.openstack('user set ' + '--email %(email)s ' + '--name %(new_name)s ' + '%(id)s' % {'email': new_email, + 'new_name': new_username, + 'id': user['id']}) + self.assertEqual(0, len(raw_output)) + raw_output = self.openstack('user show ' + '--domain %(domain)s ' + '%(name)s' % {'domain': self.domain_name, + 'name': new_username}) + new_user = self.parse_show_as_object(raw_output) + self.assertEqual(user['id'], new_user['id']) + self.assertEqual(new_email, new_user['email']) + + def test_user_show(self): + username = self._create_dummy_user() + raw_output = self.openstack('user show ' + '--domain %(domain)s ' + '%(name)s' % {'domain': self.domain_name, + 'name': username}) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.USER_FIELDS) diff --git a/test-requirements.txt b/test-requirements.txt index 4b7ca5b61a..950ae37306 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -15,3 +15,4 @@ os-testr>=0.1.0 testrepository>=0.0.18 testtools>=1.4.0 WebOb>=1.2.3 +tempest-lib>=0.6.1 From 8c7920ddf022a8f7588a8de520ffb71b66fa49b0 Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Wed, 8 Jul 2015 10:38:35 -0600 Subject: [PATCH 0145/3095] Remove testing of cliff command line options These options are part of cliff, let cliff test them. Change-Id: I802c25ba80048607eef6909a21709dcda63231cc --- openstackclient/tests/network/common.py | 27 ------------------- .../tests/network/v2/test_network.py | 8 +++--- 2 files changed, 4 insertions(+), 31 deletions(-) diff --git a/openstackclient/tests/network/common.py b/openstackclient/tests/network/common.py index 7162f97bd3..31fde257a1 100644 --- a/openstackclient/tests/network/common.py +++ b/openstackclient/tests/network/common.py @@ -33,30 +33,3 @@ def setUp(self): service_type="network", ) self.api = self.app.client_manager.network.api - - given_show_options = [ - '-f', - 'shell', - '-c', - 'id', - '--prefix', - 'TST', - ] - then_show_options = [ - ('formatter', 'shell'), - ('columns', ['id']), - ('prefix', 'TST'), - ] - given_list_options = [ - '-f', - 'csv', - '-c', - 'id', - '--quote', - 'all', - ] - then_list_options = [ - ('formatter', 'csv'), - ('columns', ['id']), - ('quote_mode', 'all'), - ] diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index 36133a3bb8..87a4406662 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -92,14 +92,14 @@ def test_create_all_options(self): "--project", identity_fakes_v3.project_name, "--project-domain", identity_fakes_v3.domain_name, FAKE_NAME, - ] + self.given_show_options + ] verifylist = [ ('admin_state', False), ('shared', True), ('project', identity_fakes_v3.project_name), ('project_domain', identity_fakes_v3.domain_name), ('name', FAKE_NAME), - ] + self.then_show_options + ] mocker = mock.Mock(return_value=copy.deepcopy(RESPONSE)) self.app.client_manager.network.create_network = mocker identity_client = identity_fakes_v3.FakeIdentityv3Client( @@ -519,8 +519,8 @@ def test_show_no_options(self, n_mock): self.assertEqual(FILTERED, result) def test_show_all_options(self, n_mock): - arglist = [FAKE_NAME] + self.given_show_options - verifylist = [('identifier', FAKE_NAME)] + self.then_show_options + arglist = [FAKE_NAME] + verifylist = [('identifier', FAKE_NAME)] n_mock.return_value = copy.deepcopy(RECORD) self.cmd = network.ShowNetwork(self.app, self.namespace) From 2d4a7371601290abca1878f07b952dc8d9deff02 Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Wed, 8 Jul 2015 11:21:41 -0600 Subject: [PATCH 0146/3095] Remove the --dhcp option to network list The --dhcp option lists agents, not networks. This does not make a lot of sense. Another command should be created to list agents. BackwardsIncompatibleImpact Closes-Bug: #1472613 Change-Id: I5ecfe3fc046a07eb64a4dabd41dbd99de7c2215f --- doc/source/backwards-incompatible.rst | 14 ++++ openstackclient/network/v2/network.py | 64 ++++++++----------- .../tests/network/v2/test_network.py | 48 -------------- 3 files changed, 41 insertions(+), 85 deletions(-) diff --git a/doc/source/backwards-incompatible.rst b/doc/source/backwards-incompatible.rst index ae77164b65..c511852f39 100644 --- a/doc/source/backwards-incompatible.rst +++ b/doc/source/backwards-incompatible.rst @@ -61,6 +61,20 @@ List of Backwards Incompatible Changes * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1461817 * Commit: https://review.openstack.org/#/c/194654/ +5. Command `openstack network list --dhcp` has been removed + + The --dhcp option to network list is not a logical use case of listing + networks, it lists agents. Another command should be added in the future + to provide this functionality. It is highly unlikely anyone uses this + feature as we don't support any other agent commands. Use neutron + dhcp-agent-list-hosting-net command instead. + + * In favor of: Create network agent list command in the future + * As of: 1.6.0 + * Removed in: NA + * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/472613 + * Commit: https://review.openstack.org/#/c/194654/ + For Developers ============== diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 1fa05462f0..336b3086e2 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -156,10 +156,6 @@ def get_parser(self, prog_name): default=False, help='List external networks', ) - parser.add_argument( - '--dhcp', - metavar='', - help='DHCP agent ID') parser.add_argument( '--long', action='store_true', @@ -172,40 +168,34 @@ def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) client = self.app.client_manager.network - if parsed_args.dhcp: - data = client.api.dhcp_agent_list(dhcp_id=parsed_args.dhcp) - - columns = ('ID',) - column_headers = columns + data = client.api.network_list(external=parsed_args.external) + + if parsed_args.long: + columns = ( + 'ID', + 'Name', + 'Status', + 'project_id', + 'state', + 'Shared', + 'Subnets', + 'provider:network_type', + 'router_type', + ) + column_headers = ( + 'ID', + 'Name', + 'Status', + 'Project', + 'State', + 'Shared', + 'Subnets', + 'Network Type', + 'Router Type', + ) else: - data = client.api.network_list(external=parsed_args.external) - - if parsed_args.long: - columns = ( - 'ID', - 'Name', - 'Status', - 'project_id', - 'state', - 'Shared', - 'Subnets', - 'provider:network_type', - 'router_type', - ) - column_headers = ( - 'ID', - 'Name', - 'Status', - 'Project', - 'State', - 'Shared', - 'Subnets', - 'Network Type', - 'Router Type', - ) - else: - columns = ('ID', 'Name', 'Subnets') - column_headers = columns + columns = ('ID', 'Name', 'Subnets') + column_headers = columns for d in data: d = _prep_network_detail(d) diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index 36133a3bb8..61541823e5 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -278,7 +278,6 @@ def test_network_list_no_options(self, n_mock): arglist = [] verifylist = [ ('external', False), - ('dhcp', None), ('long', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -306,7 +305,6 @@ def test_list_external(self, n_mock): ] verifylist = [ ('external', True), - ('dhcp', None), ('long', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -334,7 +332,6 @@ def test_network_list_long(self, n_mock): ] verifylist = [ ('long', True), - ('dhcp', None), ('external', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -377,51 +374,6 @@ def test_network_list_long(self, n_mock): self.assertEqual(list(data), datalist) -@mock.patch( - 'openstackclient.api.network_v2.APIv2.dhcp_agent_list' -) -class TestListDhcpAgent(common.TestNetworkBase): - - def setUp(self): - super(TestListDhcpAgent, self).setUp() - - # Get the command object to test - self.cmd = network.ListNetwork(self.app, self.namespace) - - self.DHCP_LIST = [ - {'id': '1'}, - {'id': '2'}, - ] - - def test_list_dhcp(self, n_mock): - n_mock.return_value = self.DHCP_LIST - - arglist = [ - '--dhcp', 'dhcpid', - ] - verifylist = [ - ('external', False), - ('dhcp', 'dhcpid'), - ('long', False), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # DisplayCommandBase.take_action() returns two tuples - columns, data = self.cmd.take_action(parsed_args) - - # Set expected values - n_mock.assert_called_with( - dhcp_id='dhcpid', - ) - - self.assertEqual(('ID',), columns) - datalist = [ - ('1',), - ('2',), - ] - self.assertEqual(datalist, list(data)) - - class TestSetNetwork(common.TestNetworkBase): def test_set_this(self): arglist = [ From 7af00f833f83bf375a4270bff609f62902a6c887 Mon Sep 17 00:00:00 2001 From: Martin Schuppert Date: Wed, 8 Jul 2015 16:15:54 +0200 Subject: [PATCH 0147/3095] openstack catalog list always returns publicURL for internalURL and adminURL With this change 'openstack catalog list' returns the correspoding URL for publicURL, internalURL and adminURL in _format_endpoints . Change-Id: I5d946c9d70a2d3c22a7cc77067fec8e2e9aa4940 Closes-Bug: 1472629 --- openstackclient/identity/v2_0/catalog.py | 2 +- .../tests/identity/v2_0/test_catalog.py | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/openstackclient/identity/v2_0/catalog.py b/openstackclient/identity/v2_0/catalog.py index 7d17fbf586..c10001d0df 100644 --- a/openstackclient/identity/v2_0/catalog.py +++ b/openstackclient/identity/v2_0/catalog.py @@ -31,7 +31,7 @@ def _format_endpoints(eps=None): region = eps[index].get('region', '') ret += region + '\n' for url in ['publicURL', 'internalURL', 'adminURL']: - ret += " %s: %s\n" % (url, eps[index]['publicURL']) + ret += " %s: %s\n" % (url, eps[index][url]) return ret diff --git a/openstackclient/tests/identity/v2_0/test_catalog.py b/openstackclient/tests/identity/v2_0/test_catalog.py index 5044595434..fe13d78d02 100644 --- a/openstackclient/tests/identity/v2_0/test_catalog.py +++ b/openstackclient/tests/identity/v2_0/test_catalog.py @@ -27,11 +27,13 @@ class TestCatalog(utils.TestCommand): { 'region': 'one', 'publicURL': 'https://public.one.example.com', + 'internalURL': 'https://internal.one.example.com', 'adminURL': 'https://admin.one.example.com', }, { 'region': 'two', 'publicURL': 'https://public.two.example.com', + 'internalURL': 'https://internal.two.example.com', 'adminURL': 'https://admin.two.example.com', }, ], @@ -74,11 +76,11 @@ def test_catalog_list(self): 'supernova', 'compute', 'one\n publicURL: https://public.one.example.com\n ' - 'internalURL: https://public.one.example.com\n ' - 'adminURL: https://public.one.example.com\n' + 'internalURL: https://internal.one.example.com\n ' + 'adminURL: https://admin.one.example.com\n' 'two\n publicURL: https://public.two.example.com\n ' - 'internalURL: https://public.two.example.com\n ' - 'adminURL: https://public.two.example.com\n', + 'internalURL: https://internal.two.example.com\n ' + 'adminURL: https://admin.two.example.com\n', ), ) self.assertEqual(datalist, tuple(data)) @@ -108,11 +110,11 @@ def test_catalog_show(self): self.assertEqual(collist, columns) datalist = ( 'one\n publicURL: https://public.one.example.com\n ' - 'internalURL: https://public.one.example.com\n ' - 'adminURL: https://public.one.example.com\n' + 'internalURL: https://internal.one.example.com\n ' + 'adminURL: https://admin.one.example.com\n' 'two\n publicURL: https://public.two.example.com\n ' - 'internalURL: https://public.two.example.com\n ' - 'adminURL: https://public.two.example.com\n', + 'internalURL: https://internal.two.example.com\n ' + 'adminURL: https://admin.two.example.com\n', 'qwertyuiop', 'supernova', 'compute', From f89fc1ef3288fb432fe6f76268a92f6f1111a1eb Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Wed, 8 Jul 2015 13:52:45 -0600 Subject: [PATCH 0148/3095] Fix address parsing for server ssh command There seem to be three formats for the server address field and the old code only supported the old format. This code adds a parser for all three formats. Change-Id: I7f12d2c69ff70556907ea6f31a0e0bba91b68b49 Closes-Bug: #1469843 --- openstackclient/compute/v2/server.py | 52 ++++++++++++------ .../tests/compute/v2/test_server.py | 54 +++++++++++++++++++ 2 files changed, 91 insertions(+), 15 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 5007b072ac..4efef975bb 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -55,6 +55,39 @@ def _format_servers_list_networks(networks): return '; '.join(output) +def _get_ip_address(addresses, address_type, ip_address_family): + # Old style addresses + if address_type in addresses: + for addy in addresses[address_type]: + if int(addy['version']) in ip_address_family: + return addy['addr'] + + # New style addresses + new_address_type = address_type + if address_type == 'public': + new_address_type = 'floating' + if address_type == 'private': + new_address_type = 'fixed' + for network in addresses: + for addy in addresses[network]: + # Case where it is list of strings + if isinstance(addy, six.string_types): + if new_address_type == 'fixed': + return addresses[network][0] + else: + return addresses[network][-1] + # Case where it is a dict + if 'OS-EXT-IPS:type' not in addy: + continue + if addy['OS-EXT-IPS:type'] == new_address_type: + if int(addy['version']) in ip_address_family: + return addy['addr'] + raise exceptions.CommandError( + "ERROR: No %s IP version %s address found" % + (address_type, ip_address_family) + ) + + def _prep_server_detail(compute_client, server): """Prepare the detailed server dict for printing @@ -1283,6 +1316,7 @@ def get_parser(self, prog_name): ) parser.add_argument( '-l', + dest='login', metavar='', help=argparse.SUPPRESS, ) @@ -1381,13 +1415,6 @@ def take_action(self, parsed_args): # Build the command cmd = "ssh" - # Look for address type - if parsed_args.address_type: - address_type = parsed_args.address_type - if address_type not in server.addresses: - raise SystemExit("ERROR: No %s IP address found" % address_type) - - # Set up desired address family ip_address_family = [4, 6] if parsed_args.ipv4: ip_address_family = [4] @@ -1396,14 +1423,6 @@ def take_action(self, parsed_args): ip_address_family = [6] cmd += " -6" - # Grab the first matching IP address - ip_address = None - for addr in server.addresses[address_type]: - if int(addr['version']) in ip_address_family: - ip_address = addr['addr'] - if not ip_address: - raise SystemExit("ERROR: No IP address found") - if parsed_args.port: cmd += " -p %d" % parsed_args.port if parsed_args.identity: @@ -1418,6 +1437,9 @@ def take_action(self, parsed_args): cmd += " -v" cmd += " %s@%s" + ip_address = _get_ip_address(server.addresses, + parsed_args.address_type, + ip_address_family) self.log.debug("ssh command: %s", (cmd % (login, ip_address))) os.system(cmd % (login, ip_address)) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index a8a1936d82..eed148a0cf 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -15,7 +15,9 @@ import copy import mock +import testtools +from openstackclient.common import exceptions from openstackclient.common import utils as common_utils from openstackclient.compute.v2 import server from openstackclient.tests.compute.v2 import fakes as compute_fakes @@ -580,3 +582,55 @@ def test_server_resize_revert(self): self.servers_mock.revert_resize.assert_called_with( self.servers_get_return_value, ) + + +class TestServerGeneral(testtools.TestCase): + OLD = { + 'private': [ + { + 'addr': '192.168.0.3', + 'version': 4, + }, + ] + } + NEW = { + 'foo': [ + { + 'OS-EXT-IPS-MAC:mac_addr': 'fa:16:3e:93:b3:01', + 'version': 4, + 'addr': '10.10.1.2', + 'OS-EXT-IPS:type': 'fixed', + }, + { + 'OS-EXT-IPS-MAC:mac_addr': 'fa:16:3e:93:b3:02', + 'version': 6, + 'addr': '0:0:0:0:0:ffff:a0a:103', + 'OS-EXT-IPS:type': 'floating', + }, + ] + } + ODD = {'jenkins': ['10.3.3.18', '124.12.125.4']} + + def test_get_ip_address(self): + self.assertEqual("192.168.0.3", + server._get_ip_address(self.OLD, 'private', [4, 6])) + self.assertEqual("10.10.1.2", + server._get_ip_address(self.NEW, 'fixed', [4, 6])) + self.assertEqual("10.10.1.2", + server._get_ip_address(self.NEW, 'private', [4, 6])) + self.assertEqual("0:0:0:0:0:ffff:a0a:103", + server._get_ip_address(self.NEW, 'public', [6])) + self.assertEqual("0:0:0:0:0:ffff:a0a:103", + server._get_ip_address(self.NEW, 'floating', [6])) + self.assertEqual("124.12.125.4", + server._get_ip_address(self.ODD, 'public', [4, 6])) + self.assertEqual("10.3.3.18", + server._get_ip_address(self.ODD, 'private', [4, 6])) + self.assertRaises(exceptions.CommandError, + server._get_ip_address, self.NEW, 'public', [4]) + self.assertRaises(exceptions.CommandError, + server._get_ip_address, self.NEW, 'admin', [4]) + self.assertRaises(exceptions.CommandError, + server._get_ip_address, self.OLD, 'public', [4, 6]) + self.assertRaises(exceptions.CommandError, + server._get_ip_address, self.OLD, 'private', [6]) From a3f50eafa58b27c576b398ebcbb1bebddf1d56c9 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 9 Jul 2015 06:11:22 +0000 Subject: [PATCH 0149/3095] Imported Translations from Transifex For more information about this automatic import see: https://wiki.openstack.org/wiki/Translations/Infrastructure Change-Id: I053c9518ef01e008e63833494d2b51c44d3a64a7 --- .../de/LC_MESSAGES/python-openstackclient.po | 17 ++++------- .../locale/python-openstackclient.pot | 29 ++++++++++--------- .../LC_MESSAGES/python-openstackclient.po | 6 ++-- 3 files changed, 23 insertions(+), 29 deletions(-) diff --git a/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po b/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po index e836b6711c..a6c5a98037 100644 --- a/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po +++ b/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po @@ -9,11 +9,11 @@ msgid "" msgstr "" "Project-Id-Version: python-openstackclient\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-06-09 06:01+0000\n" -"PO-Revision-Date: 2015-06-04 16:33+0000\n" -"Last-Translator: Ettore Atalan \n" -"Language-Team: German (http://www.transifex.com/projects/p/python-" -"openstackclient/language/de/)\n" +"POT-Creation-Date: 2015-07-09 06:11+0000\n" +"PO-Revision-Date: 2015-06-14 18:41+0000\n" +"Last-Translator: openstackjenkins \n" +"Language-Team: German (http://www.transifex.com/p/python-openstackclient/" +"language/de/)\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -471,13 +471,6 @@ msgstr "" "Legen Sie eine Eigenschaft auf diesem Server fest (für mehrere Werte " "wiederholen)" -msgid "" -"Set a scope, such as a project or domain, with --os-project-name, " -"OS_PROJECT_NAME or auth.project_name" -msgstr "" -"Legen Sie einen Gültigkeitsbereich, wie beispielsweise ein Projekt oder eine " -"Domäne, mit OS_PROJECT_NAME oder auth.project_name fest" - msgid "" "Set a service AUTH_URL, with --os-auth-url, OS_AUTH_URL or auth.auth_url\n" msgstr "" diff --git a/python-openstackclient/locale/python-openstackclient.pot b/python-openstackclient/locale/python-openstackclient.pot index af372cd189..7c99dbf833 100644 --- a/python-openstackclient/locale/python-openstackclient.pot +++ b/python-openstackclient/locale/python-openstackclient.pot @@ -7,9 +7,9 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: python-openstackclient 1.3.1.dev46\n" +"Project-Id-Version: python-openstackclient 1.5.1.dev45\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-06-09 06:01+0000\n" +"POT-Creation-Date: 2015-07-09 06:11+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -30,19 +30,20 @@ msgstr "" #: openstackclient/api/auth.py:155 msgid "" -"Set a scope, such as a project or domain, with --os-project-name, " -"OS_PROJECT_NAME or auth.project_name" +"Set a scope, such as a project or domain, set a project scope with --os-" +"project-name, OS_PROJECT_NAME or auth.project_name, set a domain scope " +"with --os-domain-name, OS_DOMAIN_NAME or auth.domain_name" msgstr "" -#: openstackclient/api/auth.py:159 openstackclient/api/auth.py:165 +#: openstackclient/api/auth.py:161 openstackclient/api/auth.py:167 msgid "Set a token with --os-token, OS_TOKEN or auth.token\n" msgstr "" -#: openstackclient/api/auth.py:161 +#: openstackclient/api/auth.py:163 msgid "Set a service AUTH_URL, with --os-auth-url, OS_AUTH_URL or auth.auth_url\n" msgstr "" -#: openstackclient/api/auth.py:167 +#: openstackclient/api/auth.py:169 msgid "Set a service URL, with --os-url, OS_URL or auth.url\n" msgstr "" @@ -478,23 +479,23 @@ msgstr "" #: openstackclient/identity/v2_0/ec2creds.py:99 #: openstackclient/identity/v2_0/ec2creds.py:172 -#: openstackclient/identity/v3/ec2creds.py:145 -#: openstackclient/identity/v3/ec2creds.py:219 +#: openstackclient/identity/v3/ec2creds.py:129 +#: openstackclient/identity/v3/ec2creds.py:187 msgid "Credentials access key" msgstr "" #: openstackclient/identity/v2_0/ec2creds.py:104 -#: openstackclient/identity/v3/ec2creds.py:150 +#: openstackclient/identity/v3/ec2creds.py:134 msgid "Delete credentials for user (name or ID)" msgstr "" #: openstackclient/identity/v2_0/ec2creds.py:134 -#: openstackclient/identity/v3/ec2creds.py:180 +#: openstackclient/identity/v3/ec2creds.py:156 msgid "Filter list by user (name or ID)" msgstr "" #: openstackclient/identity/v2_0/ec2creds.py:177 -#: openstackclient/identity/v3/ec2creds.py:224 +#: openstackclient/identity/v3/ec2creds.py:192 msgid "Show credentials for user (name or ID)" msgstr "" @@ -599,7 +600,7 @@ msgid "New role name" msgstr "" #: openstackclient/identity/v2_0/role.py:92 -#: openstackclient/identity/v3/role.py:188 +#: openstackclient/identity/v3/role.py:165 msgid "Return existing role" msgstr "" @@ -738,7 +739,7 @@ msgstr "" msgid "Return existing domain" msgstr "" -#: openstackclient/identity/v3/group.py:133 +#: openstackclient/identity/v3/group.py:141 msgid "Return existing group" msgstr "" diff --git a/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po b/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po index 63f0b3ea98..87ddd9ac81 100644 --- a/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po +++ b/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po @@ -8,10 +8,10 @@ msgid "" msgstr "" "Project-Id-Version: python-openstackclient\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-06-09 06:01+0000\n" -"PO-Revision-Date: 2015-06-03 23:04+0000\n" +"POT-Creation-Date: 2015-07-09 06:11+0000\n" +"PO-Revision-Date: 2015-06-14 18:41+0000\n" "Last-Translator: openstackjenkins \n" -"Language-Team: Chinese (Taiwan) (http://www.transifex.com/projects/p/python-" +"Language-Team: Chinese (Taiwan) (http://www.transifex.com/p/python-" "openstackclient/language/zh_TW/)\n" "Plural-Forms: nplurals=1; plural=0\n" "MIME-Version: 1.0\n" From 89cf9f61b18dff5b073fae74191fbc8447448374 Mon Sep 17 00:00:00 2001 From: lin-hua-cheng Date: Thu, 9 Jul 2015 21:40:31 -0700 Subject: [PATCH 0150/3095] Fixes modules index generated by Sphinx Sphinx was always using (o)penstackclient for the prefix so the index wasn't very useful. Change-Id: Ie9f5d7fe428142bdb8027b422e3023418b48c428 --- doc/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index e805a98767..f4434ec154 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -100,7 +100,7 @@ pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +modindex_common_prefix = ['openstackclient.'] # -- Options for HTML output -------------------------------------------------- From e76de2c204b3a861355159953a777bf1dc83c38d Mon Sep 17 00:00:00 2001 From: Guojian Shao Date: Sat, 4 Jul 2015 22:03:05 +0800 Subject: [PATCH 0151/3095] add functional tests for identity v2 split test_identity.py into test_user.py, test_project, etc. To make functional tests run repeatedly without raising duplicated error, clean up resources before exiting each test case. Change-Id: I8f31ccbd70f1cccdab8b3720aac179e2e399486d Implements: blueprint identity-functional-tests --- functional/tests/identity/v2/test_catalog.py | 42 ++++ .../tests/identity/v2/test_ec2_credentials.py | 40 ++++ functional/tests/identity/v2/test_identity.py | 191 ++++++++++-------- functional/tests/identity/v2/test_project.py | 84 ++++++++ functional/tests/identity/v2/test_role.py | 109 ++++++++++ functional/tests/identity/v2/test_token.py | 24 +++ functional/tests/identity/v2/test_user.py | 60 ++++++ 7 files changed, 462 insertions(+), 88 deletions(-) create mode 100644 functional/tests/identity/v2/test_catalog.py create mode 100644 functional/tests/identity/v2/test_ec2_credentials.py create mode 100644 functional/tests/identity/v2/test_project.py create mode 100644 functional/tests/identity/v2/test_role.py create mode 100644 functional/tests/identity/v2/test_token.py create mode 100644 functional/tests/identity/v2/test_user.py diff --git a/functional/tests/identity/v2/test_catalog.py b/functional/tests/identity/v2/test_catalog.py new file mode 100644 index 0000000000..3a1f7e112a --- /dev/null +++ b/functional/tests/identity/v2/test_catalog.py @@ -0,0 +1,42 @@ +# 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 functional.tests.identity.v2 import test_identity + + +class CatalogTests(test_identity.IdentityTests): + + def test_catalog_list(self): + raw_output = self.openstack('catalog list') + items = self.parse_listing(raw_output) + self.assert_table_structure(items, self.CATALOG_LIST_HEADERS) + + def test_catalog_show(self): + """test catalog show command + + The output example: + +-----------+-------------------------------------------+ + | Field | Value | + +-----------+-------------------------------------------+ + | endpoints | test1 | + | | publicURL: http://localhost:5000/v2.0 | + | | internalURL: http://localhost:5000/v2.0 | + | | adminURL: http://localhost:5000/v2.0 | + | | | + | name | keystone | + | type | identity | + +-----------+-------------------------------------------+ + """ + raw_output = self.openstack('catalog show %s' % 'identity') + items = self.parse_show(raw_output) + # items may have multiple endpoint urls with empty key + self.assert_show_fields(items, ['endpoints', 'name', 'type', '']) diff --git a/functional/tests/identity/v2/test_ec2_credentials.py b/functional/tests/identity/v2/test_ec2_credentials.py new file mode 100644 index 0000000000..86702c0c6f --- /dev/null +++ b/functional/tests/identity/v2/test_ec2_credentials.py @@ -0,0 +1,40 @@ +# 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 functional.tests.identity.v2 import test_identity + + +class EC2CredentialsTests(test_identity.IdentityTests): + + def test_ec2_credentials_create(self): + self._create_dummy_ec2_credentials() + + def test_ec2_credentials_delete(self): + access_key = self._create_dummy_ec2_credentials(add_clean_up=False) + raw_output = self.openstack( + 'ec2 credentials delete %s' % access_key, + ) + self.assertEqual(0, len(raw_output)) + + def test_ec2_credentials_list(self): + self._create_dummy_ec2_credentials() + raw_output = self.openstack('ec2 credentials list') + items = self.parse_listing(raw_output) + self.assert_table_structure(items, self.EC2_CREDENTIALS_LIST_HEADERS) + + def test_ec2_credentials_show(self): + access_key = self._create_dummy_ec2_credentials() + show_output = self.openstack( + 'ec2 credentials show %s' % access_key, + ) + items = self.parse_show(show_output) + self.assert_show_fields(items, self.EC2_CREDENTIALS_FIELDS) diff --git a/functional/tests/identity/v2/test_identity.py b/functional/tests/identity/v2/test_identity.py index 6d28fa51b4..07fc354e45 100644 --- a/functional/tests/identity/v2/test_identity.py +++ b/functional/tests/identity/v2/test_identity.py @@ -10,7 +10,8 @@ # License for the specific language governing permissions and limitations # under the License. -from functional.common import exceptions +from tempest_lib.common.utils import data_utils + from functional.common import test BASIC_LIST_HEADERS = ['ID', 'Name'] @@ -19,96 +20,110 @@ class IdentityTests(test.TestCase): """Functional tests for Identity commands. """ - USER_FIELDS = ['email', 'enabled', 'id', 'name', 'project_id', 'username'] - PROJECT_FIELDS = ['enabled', 'id', 'name', 'description'] - EC2_CREDENTIALS_FIELDS = [ - 'access', - 'project_id', - 'secret', - 'trust_id', - 'user_id', - ] - EC2_CREDENTIALS_LIST_HEADERS = [ - 'Access', - 'Secret', - 'Project ID', - 'User ID', - ] - - def test_user_list(self): - raw_output = self.openstack('user list') - items = self.parse_listing(raw_output) - self.assert_table_structure(items, BASIC_LIST_HEADERS) - - def test_user_show(self): - raw_output = self.openstack('user show admin') + USER_FIELDS = ['email', 'enabled', 'id', 'name', 'project_id', + 'username', 'domain_id', 'default_project_id'] + PROJECT_FIELDS = ['enabled', 'id', 'name', 'description', 'domain_id'] + TOKEN_FIELDS = ['expires', 'id', 'project_id', 'user_id'] + ROLE_FIELDS = ['id', 'name', 'links'] + + EC2_CREDENTIALS_FIELDS = ['access', 'project_id', 'secret', + 'trust_id', 'user_id'] + EC2_CREDENTIALS_LIST_HEADERS = ['Access', 'Secret', + 'Project ID', 'User ID'] + CATALOG_LIST_HEADERS = ['Name', 'Type', 'Endpoints'] + + @classmethod + def setUpClass(cls): + if hasattr(super(IdentityTests, cls), 'setUpClass'): + super(IdentityTests, cls).setUpClass() + + # create dummy project + cls.project_name = data_utils.rand_name('TestProject') + cls.project_description = data_utils.rand_name('description') + cls.openstack( + 'project create ' + '--description %(description)s ' + '--enable ' + '%(name)s' % {'description': cls.project_description, + 'name': cls.project_name}) + + @classmethod + def tearDownClass(cls): + cls.openstack('project delete %s' % cls.project_name) + + if hasattr(super(IdentityTests, cls), 'tearDownClass'): + super(IdentityTests, cls).tearDownClass() + + def _create_dummy_project(self, add_clean_up=True): + project_name = data_utils.rand_name('TestProject') + project_description = data_utils.rand_name('description') + raw_output = self.openstack( + 'project create ' + '--description %(description)s ' + '--enable %(name)s' % {'description': project_description, + 'name': project_name}) items = self.parse_show(raw_output) - self.assert_show_fields(items, self.USER_FIELDS) - - def test_user_create(self): - raw_output = self.openstack('user create mjordan --password bulls' - ' --email hoops@example.com') + self.assert_show_fields(items, self.PROJECT_FIELDS) + project = self.parse_show_as_object(raw_output) + if add_clean_up: + self.addCleanup( + self.openstack, + 'project delete %s' % project['id']) + return project_name + + def _create_dummy_user(self, add_clean_up=True): + username = data_utils.rand_name('TestUser') + password = data_utils.rand_name('password') + email = data_utils.rand_name() + '@example.com' + raw_output = self.openstack( + 'user create ' + '--project %(project)s ' + '--password %(password)s ' + '--email %(email)s ' + '--enable ' + '%(name)s' % {'project': self.project_name, + 'email': email, + 'password': password, + 'name': username}) items = self.parse_show(raw_output) self.assert_show_fields(items, self.USER_FIELDS) - - def test_user_delete(self): - self.openstack('user create dummy') - raw_output = self.openstack('user delete dummy') - self.assertEqual(0, len(raw_output)) - - def test_bad_user_command(self): - self.assertRaises(exceptions.CommandFailed, - self.openstack, 'user unlist') - - def test_project_list(self): - raw_output = self.openstack('project list') - items = self.parse_listing(raw_output) - self.assert_table_structure(items, BASIC_LIST_HEADERS) - - def test_project_show(self): - raw_output = self.openstack('project show admin') + if add_clean_up: + self.addCleanup( + self.openstack, + 'user delete %s' % self.parse_show_as_object(raw_output)['id']) + return username + + def _create_dummy_role(self, add_clean_up=True): + role_name = data_utils.rand_name('TestRole') + raw_output = self.openstack('role create %s' % role_name) items = self.parse_show(raw_output) - self.assert_show_fields(items, self.PROJECT_FIELDS) - - def test_project_create(self): - raw_output = self.openstack('project create test-project') + self.assert_show_fields(items, self.ROLE_FIELDS) + role = self.parse_show_as_object(raw_output) + self.assertEqual(role_name, role['name']) + if add_clean_up: + self.addCleanup( + self.openstack, + 'role delete %s' % role['id']) + return role_name + + def _create_dummy_ec2_credentials(self, add_clean_up=True): + raw_output = self.openstack('ec2 credentials create') items = self.parse_show(raw_output) - self.assert_show_fields(items, self.PROJECT_FIELDS) - - def test_project_delete(self): - self.openstack('project create dummy-project') - raw_output = self.openstack('project delete dummy-project') - self.assertEqual(0, len(raw_output)) - - def test_ec2_credentials_create(self): - create_output = self.openstack('ec2 credentials create') - create_items = self.parse_show(create_output) - self.openstack( - 'ec2 credentials delete %s' % create_items[0]['access'], - ) - self.assert_show_fields(create_items, self.EC2_CREDENTIALS_FIELDS) - - def test_ec2_credentials_delete(self): - create_output = self.openstack('ec2 credentials create') - create_items = self.parse_show(create_output) - raw_output = self.openstack( - 'ec2 credentials delete %s' % create_items[0]['access'], - ) - self.assertEqual(0, len(raw_output)) - - def test_ec2_credentials_list(self): - raw_output = self.openstack('ec2 credentials list') - items = self.parse_listing(raw_output) - self.assert_table_structure(items, self.EC2_CREDENTIALS_LIST_HEADERS) - - def test_ec2_credentials_show(self): - create_output = self.openstack('ec2 credentials create') - create_items = self.parse_show(create_output) - show_output = self.openstack( - 'ec2 credentials show %s' % create_items[0]['access'], - ) - items = self.parse_show(show_output) - self.openstack( - 'ec2 credentials delete %s' % create_items[0]['access'], - ) self.assert_show_fields(items, self.EC2_CREDENTIALS_FIELDS) + ec2_credentials = self.parse_show_as_object(raw_output) + access_key = ec2_credentials['access'] + if add_clean_up: + self.addCleanup( + self.openstack, + 'ec2 credentials delete %s' % access_key) + return access_key + + def _create_dummy_token(self, add_clean_up=True): + raw_output = self.openstack('token issue') + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.TOKEN_FIELDS) + token = self.parse_show_as_object(raw_output) + if add_clean_up: + self.addCleanup(self.openstack, + 'token revoke %s' % token['id']) + return token['id'] diff --git a/functional/tests/identity/v2/test_project.py b/functional/tests/identity/v2/test_project.py new file mode 100644 index 0000000000..88b282ef3e --- /dev/null +++ b/functional/tests/identity/v2/test_project.py @@ -0,0 +1,84 @@ +# 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 tempest_lib.common.utils import data_utils + +from functional.tests.identity.v2 import test_identity + + +class ProjectTests(test_identity.IdentityTests): + + def test_project_create(self): + project_name = data_utils.rand_name('TestProject') + description = data_utils.rand_name('description') + raw_output = self.openstack( + 'project create ' + '--description %(description)s ' + '--enable ' + '--property k1=v1 ' + '--property k2=v2 ' + '%(name)s' % {'description': description, + 'name': project_name}) + self.addCleanup( + self.openstack, + 'project delete %s' % project_name + ) + items = self.parse_show(raw_output) + show_fields = list(self.PROJECT_FIELDS) + show_fields.extend(['k1', 'k2']) + self.assert_show_fields(items, show_fields) + project = self.parse_show_as_object(raw_output) + self.assertEqual('v1', project['k1']) + self.assertEqual('v2', project['k2']) + + def test_project_delete(self): + project_name = self._create_dummy_project(add_clean_up=False) + raw_output = self.openstack( + 'project delete %s' % project_name) + self.assertEqual(0, len(raw_output)) + + def test_project_list(self): + raw_output = self.openstack('project list') + items = self.parse_listing(raw_output) + self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS) + + def test_project_set(self): + project_name = self._create_dummy_project() + new_project_name = data_utils.rand_name('NewTestProject') + raw_output = self.openstack( + 'project set ' + '--name %(new_name)s ' + '--disable ' + '--property k0=v0 ' + '%(name)s' % {'new_name': new_project_name, + 'name': project_name}) + self.assertEqual(0, len(raw_output)) + # check project details + raw_output = self.openstack( + 'project show %s' % new_project_name + ) + items = self.parse_show(raw_output) + fields = list(self.PROJECT_FIELDS) + fields.extend(['k0']) + self.assert_show_fields(items, fields) + project = self.parse_show_as_object(raw_output) + self.assertEqual(new_project_name, project['name']) + self.assertEqual('False', project['enabled']) + self.assertEqual('v0', project['k0']) + + def test_project_show(self): + project_name = self._create_dummy_project() + raw_output = self.openstack( + 'project show %s' % project_name + ) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.PROJECT_FIELDS) diff --git a/functional/tests/identity/v2/test_role.py b/functional/tests/identity/v2/test_role.py new file mode 100644 index 0000000000..e542a5fbdd --- /dev/null +++ b/functional/tests/identity/v2/test_role.py @@ -0,0 +1,109 @@ +# 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 functional.tests.identity.v2 import test_identity + + +class RoleTests(test_identity.IdentityTests): + + def test_role_create(self): + self._create_dummy_role() + + def test_role_delete(self): + role_name = self._create_dummy_role(add_clean_up=False) + raw_output = self.openstack('role delete %s' % role_name) + self.assertEqual(0, len(raw_output)) + + def test_role_list(self): + self._create_dummy_role() + raw_output = self.openstack('role list') + items = self.parse_listing(raw_output) + self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS) + + def test_role_list_with_user_project(self): + project_name = self._create_dummy_project() + role_name = self._create_dummy_role() + username = self._create_dummy_user() + raw_output = self.openstack( + 'role add ' + '--project %(project)s ' + '--user %(user)s ' + '%(role)s' % {'project': project_name, + 'user': username, + 'role': role_name}) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.ROLE_FIELDS) + raw_output = self.openstack( + 'role list ' + '--project %(project)s ' + '--user %(user)s ' + '' % {'project': project_name, + 'user': username}) + items = self.parse_listing(raw_output) + self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS) + self.assertEqual(1, len(items)) + self.addCleanup( + self.openstack, + 'role remove ' + '--project %(project)s ' + '--user %(user)s ' + '%(role)s' % {'project': project_name, + 'user': username, + 'role': role_name}) + + def test_role_show(self): + role_name = self._create_dummy_role() + raw_output = self.openstack('role show %s' % role_name) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.ROLE_FIELDS) + + def test_role_add(self): + role_name = self._create_dummy_role() + username = self._create_dummy_user() + raw_output = self.openstack( + 'role add ' + '--project %(project)s ' + '--user %(user)s ' + '%(role)s' % {'project': self.project_name, + 'user': username, + 'role': role_name}) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.ROLE_FIELDS) + self.addCleanup( + self.openstack, + 'role remove ' + '--project %(project)s ' + '--user %(user)s ' + '%(role)s' % {'project': self.project_name, + 'user': username, + 'role': role_name}) + + def test_role_remove(self): + role_name = self._create_dummy_role() + username = self._create_dummy_user() + raw_output = self.openstack( + 'role add ' + '--project %(project)s ' + '--user %(user)s ' + '%(role)s' % {'project': self.project_name, + 'user': username, + 'role': role_name}) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.ROLE_FIELDS) + raw_output = self.openstack( + 'role remove ' + '--project %(project)s ' + '--user %(user)s ' + '%(role)s' % {'project': self.project_name, + 'user': username, + 'role': role_name}) + self.assertEqual(0, len(raw_output)) diff --git a/functional/tests/identity/v2/test_token.py b/functional/tests/identity/v2/test_token.py new file mode 100644 index 0000000000..bac2b0ac9c --- /dev/null +++ b/functional/tests/identity/v2/test_token.py @@ -0,0 +1,24 @@ +# 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 functional.tests.identity.v2 import test_identity + + +class TokenTests(test_identity.IdentityTests): + + def test_token_issue(self): + self._create_dummy_token() + + def test_token_revoke(self): + token_id = self._create_dummy_token(add_clean_up=False) + raw_output = self.openstack('token revoke %s' % token_id) + self.assertEqual(0, len(raw_output)) diff --git a/functional/tests/identity/v2/test_user.py b/functional/tests/identity/v2/test_user.py new file mode 100644 index 0000000000..41895e7ecc --- /dev/null +++ b/functional/tests/identity/v2/test_user.py @@ -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. + +from tempest_lib.common.utils import data_utils + +from functional.common import exceptions +from functional.tests.identity.v2 import test_identity + + +class UserTests(test_identity.IdentityTests): + + def test_user_create(self): + self._create_dummy_user() + + def test_user_delete(self): + username = self._create_dummy_user(add_clean_up=False) + raw_output = self.openstack('user delete %s' % username) + self.assertEqual(0, len(raw_output)) + + def test_user_list(self): + raw_output = self.openstack('user list') + items = self.parse_listing(raw_output) + self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS) + + def test_user_set(self): + username = self._create_dummy_user() + raw_output = self.openstack('user show %s' % username) + user = self.parse_show_as_object(raw_output) + new_username = data_utils.rand_name('NewTestUser') + new_email = data_utils.rand_name() + '@example.com' + raw_output = self.openstack('user set ' + '--email %(email)s ' + '--name %(new_name)s ' + '%(id)s' % {'email': new_email, + 'new_name': new_username, + 'id': user['id']}) + self.assertEqual(0, len(raw_output)) + raw_output = self.openstack('user show %s' % new_username) + new_user = self.parse_show_as_object(raw_output) + self.assertEqual(user['id'], new_user['id']) + self.assertEqual(new_email, new_user['email']) + + def test_user_show(self): + username = self._create_dummy_user() + raw_output = self.openstack('user show %s' % username) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.USER_FIELDS) + + def test_bad_user_command(self): + self.assertRaises(exceptions.CommandFailed, + self.openstack, 'user unlist') From f807f0a66e2d53b6711937a3687d44f11dc1d6fd Mon Sep 17 00:00:00 2001 From: lin-hua-cheng Date: Fri, 10 Jul 2015 00:11:18 -0700 Subject: [PATCH 0152/3095] Fix wrong mock method call There is no assert_called() method in mock, replace it with assert_called_with() method. The old method used to work with mock 1.0.1 because it was a noop in magicmock. Needs https://review.openstack.org/#/c/200583 to pass the requirements check. https://review.openstack.org/#/c/193935/ changed the OS_* vars we source by forcing v2password as the auth method. change our identity v3 test setup by setting v3password Co-Authored-By: Steve Martinelli Closes-Bug: 1473454 Depends-on: I0cfab6d13e5d9e744cb302c86a2c21269923e75d Change-Id: Id22765c7e044797e03d19ad1b103fadec2726aa2 --- functional/tests/identity/v3/test_identity.py | 3 +-- openstackclient/tests/compute/v2/test_server.py | 2 +- openstackclient/tests/volume/v1/test_qos_specs.py | 2 +- openstackclient/tests/volume/v2/test_qos_specs.py | 2 +- test-requirements.txt | 3 ++- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/functional/tests/identity/v3/test_identity.py b/functional/tests/identity/v3/test_identity.py index c72dc9a05f..cf434559f9 100644 --- a/functional/tests/identity/v3/test_identity.py +++ b/functional/tests/identity/v3/test_identity.py @@ -42,8 +42,7 @@ def setUpClass(cls): auth_url = auth_url.replace('v2.0', 'v3') os.environ['OS_AUTH_URL'] = auth_url os.environ['OS_IDENTITY_API_VERSION'] = '3' - os.environ['OS_USER_DOMAIN_ID'] = 'default' - os.environ['OS_PROJECT_DOMAIN_ID'] = 'default' + os.environ['OS_AUTH_TYPE'] = 'v3password' # create dummy domain cls.domain_name = data_utils.rand_name('TestDomain') diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index a8a1936d82..c80b008423 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -250,7 +250,7 @@ def test_server_create_userdata(self, mock_open): mock_open.assert_called_with('userdata.sh') # Ensure the userdata file is closed - mock_file.close.assert_called() + mock_file.close.assert_called_with() # Set expected values kwargs = dict( diff --git a/openstackclient/tests/volume/v1/test_qos_specs.py b/openstackclient/tests/volume/v1/test_qos_specs.py index 0a5d14e60d..226fe673e5 100644 --- a/openstackclient/tests/volume/v1/test_qos_specs.py +++ b/openstackclient/tests/volume/v1/test_qos_specs.py @@ -312,7 +312,7 @@ def test_qos_list(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.qos_mock.list.assert_called() + self.qos_mock.list.assert_called_with() collist = ( 'ID', diff --git a/openstackclient/tests/volume/v2/test_qos_specs.py b/openstackclient/tests/volume/v2/test_qos_specs.py index 92b3f1793f..6a5509887a 100644 --- a/openstackclient/tests/volume/v2/test_qos_specs.py +++ b/openstackclient/tests/volume/v2/test_qos_specs.py @@ -312,7 +312,7 @@ def test_qos_list(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.qos_mock.list.assert_called() + self.qos_mock.list.assert_called_with() collist = ( 'ID', diff --git a/test-requirements.txt b/test-requirements.txt index 950ae37306..cf92f70248 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -6,7 +6,8 @@ hacking<0.11,>=0.10.0 coverage>=3.6 discover fixtures>=1.3.1 -mock>=1.0 +mock>=1.0;python_version!='2.6' +mock==1.0.1;python_version=='2.6' oslosphinx>=2.5.0 # Apache-2.0 oslotest>=1.5.1 # Apache-2.0 requests-mock>=0.6.0 # Apache-2.0 From 1462fb7f49d1ebd4868fee0c997ceb85c45582dc Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sun, 12 Jul 2015 15:22:26 +0000 Subject: [PATCH 0153/3095] Updated from global requirements Change-Id: Id0d62a51c35a4f7db5144dcbcde1888703fae378 --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index cf92f70248..009316e10c 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -6,7 +6,7 @@ hacking<0.11,>=0.10.0 coverage>=3.6 discover fixtures>=1.3.1 -mock>=1.0;python_version!='2.6' +mock>=1.1;python_version!='2.6' mock==1.0.1;python_version=='2.6' oslosphinx>=2.5.0 # Apache-2.0 oslotest>=1.5.1 # Apache-2.0 From ef0cf00b3367b3656c94b4972bc757ee0026876d Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Mon, 13 Jul 2015 07:44:24 -0600 Subject: [PATCH 0154/3095] Fix interactive password prompt Change-Id: Ie0e7a9cd6016b5c646a111a76e8372e10602a25c Closes-Bug: #1473862 --- openstackclient/common/clientmanager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index fae95630dc..c77a78485c 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -125,7 +125,7 @@ def setup_auth(self): # password auth is requested. if (self.auth_plugin_name.endswith('password') and not self._cli_options.auth.get('password', None)): - self._cli_options.os_password = self._pw_callback() + self._cli_options.auth['password'] = self._pw_callback() (auth_plugin, self._auth_params) = auth.build_auth_params( self.auth_plugin_name, From 922074b2d9cbaf527b57ea70df6c7c7b8f33054f Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Mon, 13 Jul 2015 14:50:47 -0600 Subject: [PATCH 0155/3095] Drop py33 support for Liberty Less is more: https://wiki.openstack.org/wiki/Python3 Change-Id: I8a98b72f85bb29a3663c654613ac0d70fddbc1b1 --- setup.cfg | 2 +- tox.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index b0343e0987..496529c2d8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -17,7 +17,7 @@ classifier = Programming Language :: Python :: 2.7 Programming Language :: Python :: 2.6 Programming Language :: Python :: 3 - Programming Language :: Python :: 3.3 + Programming Language :: Python :: 3.4 [files] packages = diff --git a/tox.ini b/tox.ini index ff91d890a2..34b881f80e 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] minversion = 1.6 -envlist = py33,py34,py26,py27,pep8 +envlist = py34,py26,py27,pep8 skipdist = True [testenv] From 79c69e1e82d8d974309326e6f25ad5b6dcb4b838 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Mon, 13 Jul 2015 22:59:15 -0400 Subject: [PATCH 0156/3095] temporarily skip help tests We're trying to change cliff for the better, by improving the UX of the help command. But cliff tests against the tip of OSC. Temporarily skip the tests that are failing and then update them once the fix for cliff has been merged. The patch in question: https://review.openstack.org/#/c/201258/ Change-Id: Ie1e7ddd66477ddf4a3bafedad12934e900ace48f --- openstackclient/tests/test_shell.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openstackclient/tests/test_shell.py b/openstackclient/tests/test_shell.py index 674d83452d..0b4ff6857f 100644 --- a/openstackclient/tests/test_shell.py +++ b/openstackclient/tests/test_shell.py @@ -16,6 +16,7 @@ import copy import mock import os +import testtools from openstackclient import shell from openstackclient.tests import utils @@ -283,6 +284,7 @@ def tearDown(self): super(TestShellHelp, self).tearDown() os.environ = self.orig_env + @testtools.skip("skip until bug 1444983 is resolved") def test_help_options(self): flag = "-h list server" kwargs = { From 1af89f757c1edf44067de964cb6ca8dffbb1969e Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 15 Jul 2015 01:37:44 +0000 Subject: [PATCH 0157/3095] Updated from global requirements Change-Id: I0703a48d8c95617e687eea9eea7990d778d760b5 --- requirements.txt | 4 ++-- setup.py | 2 +- test-requirements.txt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index 7c6f8aa62c..9ea39916fa 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ # The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -pbr<2.0,>=0.11 +pbr<2.0,>=1.3 six>=1.9.0 Babel>=1.3 @@ -10,7 +10,7 @@ cliff-tablib>=1.0 os-client-config>=1.4.0 oslo.config>=1.11.0 # Apache-2.0 oslo.i18n>=1.5.0 # Apache-2.0 -oslo.utils>=1.6.0 # Apache-2.0 +oslo.utils>=1.9.0 # Apache-2.0 python-glanceclient>=0.18.0 python-keystoneclient>=1.6.0 python-novaclient>=2.22.0 diff --git a/setup.py b/setup.py index 056c16c2b8..d8080d05c8 100644 --- a/setup.py +++ b/setup.py @@ -25,5 +25,5 @@ pass setuptools.setup( - setup_requires=['pbr'], + setup_requires=['pbr>=1.3'], pbr=True) diff --git a/test-requirements.txt b/test-requirements.txt index 009316e10c..24ba0f434a 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -9,7 +9,7 @@ fixtures>=1.3.1 mock>=1.1;python_version!='2.6' mock==1.0.1;python_version=='2.6' oslosphinx>=2.5.0 # Apache-2.0 -oslotest>=1.5.1 # Apache-2.0 +oslotest>=1.7.0 # Apache-2.0 requests-mock>=0.6.0 # Apache-2.0 sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 os-testr>=0.1.0 From 36391a81a3415d24c55d6bbc318157dc119da8a7 Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Sat, 4 Jul 2015 09:32:16 -0600 Subject: [PATCH 0158/3095] Rename endpoint type to interface Change-Id: I4e21d09bc747e8210f4f79a1d6c4c7ccf2f25d1c Closes-Bug: #1454392 --- doc/source/configuration.rst | 8 ++++---- doc/source/man/openstack.rst | 12 ++++++------ openstackclient/common/clientmanager.py | 12 ++++++------ openstackclient/compute/client.py | 5 ++--- openstackclient/identity/client.py | 5 ++--- openstackclient/image/client.py | 4 ++-- openstackclient/network/client.py | 5 ++--- openstackclient/object/client.py | 2 +- openstackclient/shell.py | 18 +++++++++--------- .../tests/common/test_clientmanager.py | 8 ++++---- openstackclient/tests/fakes.py | 2 +- openstackclient/tests/test_shell.py | 10 +++++----- openstackclient/volume/client.py | 5 ++--- 13 files changed, 46 insertions(+), 50 deletions(-) diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst index b603eee425..de3f84ee8e 100644 --- a/doc/source/configuration.rst +++ b/doc/source/configuration.rst @@ -78,7 +78,7 @@ The keys match the :program:`openstack` global options but without the username: openstack password: xyzpdq!lazydog region_name: DFW,ORD,IAD - endpoint_type: internal + interface: internal In the above example, the ``auth_url`` for the ``rackspace`` cloud is taken from :file:`clouds-public.yaml` (see below). @@ -97,7 +97,7 @@ to the following options if the ``rackspace`` entry in :file:`clouds-public.yaml --os-username openstack --os-password xyzpdq!lazydog --os-region-name DFW - --os-endpoint-type internal + --os-interface internal and can be selected on the command line:: @@ -107,9 +107,9 @@ Note that multiple regions are listed in the ``rackspace`` entry. An otherwise identical configuration is created for each region. If ``-os-region-name`` is not specified on the command line, the first region in the list is used by default. -The selection of ``endpoint_type`` (as seen above in the ``rackspace`` entry) +The selection of ``interface`` (as seen above in the ``rackspace`` entry) is optional. For this configuration to work, every service for this cloud -instance must already be configured to support this type of endpoint. +instance must already be configured to support this type of interface. clouds-public.yaml ~~~~~~~~~~~~~~~~~~ diff --git a/doc/source/man/openstack.rst b/doc/source/man/openstack.rst index 9d7115275c..53bf36299b 100644 --- a/doc/source/man/openstack.rst +++ b/doc/source/man/openstack.rst @@ -67,7 +67,7 @@ OPTIONS The authentication plugin type to use when connecting to the Identity service. If this option is not set, :program:`openstack` will attempt to guess the authentication method to use based on the other options. - If this option is set, its version must match :option:`--os-identity-api-version` + If this option is set, its version must match :option:`--os-identity-api-version` :option:`--os-auth-url` Authentication URL @@ -120,8 +120,8 @@ OPTIONS :option:`--os-XXXX-api-version` Additional API version options will be available depending on the installed API libraries. -:option:`--os-endpoint-type` - Endpoint type. Valid options are `public`, `admin` and `internal`. +:option:`--os-interface` + Interface type. Valid options are `public`, `admin` and `internal`. COMMANDS ======== @@ -299,7 +299,7 @@ The following environment variables can be set to alter the behaviour of :progra The name of a cloud configuration in ``clouds.yaml``. :envvar:`OS_AUTH_PLUGIN` - The authentication plugin to use when connecting to the Identity service, its version must match the Identity API version + The authentication plugin to use when connecting to the Identity service, its version must match the Identity API version :envvar:`OS_AUTH_URL` Authentication URL @@ -346,8 +346,8 @@ The following environment variables can be set to alter the behaviour of :progra :envvar:`OS_XXXX_API_VERSION` Additional API version options will be available depending on the installed API libraries. -:envvar:`OS_ENDPOINT_TYPE` - Endpoint type. Valid options are `public`, `admin` and `internal`. +:envvar:`OS_INTERFACE` + Interface type. Valid options are `public`, `admin` and `internal`. BUGS diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index fae95630dc..8612a614e9 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -86,7 +86,7 @@ def __init__( self._pw_callback = pw_func self._url = self._cli_options.auth.get('url', None) self._region_name = self._cli_options.region_name - self._endpoint_type = self._cli_options.endpoint_type + self._interface = self._cli_options.interface self.timing = self._cli_options.timing @@ -185,22 +185,22 @@ def auth_ref(self): return self._auth_ref def get_endpoint_for_service_type(self, service_type, region_name=None, - endpoint_type='public'): + interface='public'): """Return the endpoint URL for the service type.""" - if not endpoint_type: - endpoint_type = 'public' + if not interface: + interface = 'public' # See if we are using password flow auth, i.e. we have a # service catalog to select endpoints from if self.auth_ref: endpoint = self.auth_ref.service_catalog.url_for( service_type=service_type, region_name=region_name, - endpoint_type=endpoint_type, + endpoint_type=interface, ) else: # Get the passed endpoint directly from the auth plugin endpoint = self.auth.get_endpoint(self.session, - interface=endpoint_type) + interface=interface) return endpoint diff --git a/openstackclient/compute/client.py b/openstackclient/compute/client.py index 27d63a9575..6ae87b7982 100644 --- a/openstackclient/compute/client.py +++ b/openstackclient/compute/client.py @@ -48,9 +48,8 @@ def make_client(instance): extensions = [extension.Extension('list_extensions', list_extensions)] - # Remember endpoint_type only if it is set - kwargs = utils.build_kwargs_dict('endpoint_type', - instance._endpoint_type) + # Remember interface only if it is set + kwargs = utils.build_kwargs_dict('endpoint_type', instance._interface) client = compute_client( session=instance.session, diff --git a/openstackclient/identity/client.py b/openstackclient/identity/client.py index cc803511d6..d7b663ddaf 100644 --- a/openstackclient/identity/client.py +++ b/openstackclient/identity/client.py @@ -46,9 +46,8 @@ def make_client(instance): API_VERSIONS) LOG.debug('Instantiating identity client: %s', identity_client) - # Remember interface only if endpoint_type is set - kwargs = utils.build_kwargs_dict('interface', - instance._endpoint_type) + # Remember interface only if interface is set + kwargs = utils.build_kwargs_dict('interface', instance._interface) client = identity_client( session=instance.session, diff --git a/openstackclient/image/client.py b/openstackclient/image/client.py index 8e2d6cd979..8fbf8c0f25 100644 --- a/openstackclient/image/client.py +++ b/openstackclient/image/client.py @@ -46,7 +46,7 @@ def make_client(instance): endpoint = instance.get_endpoint_for_service_type( API_NAME, region_name=instance._region_name, - endpoint_type=instance._endpoint_type, + interface=instance._interface, ) client = image_client( @@ -69,7 +69,7 @@ def make_client(instance): endpoint=instance.get_endpoint_for_service_type( IMAGE_API_TYPE, region_name=instance._region_name, - endpoint_type=instance._endpoint_type, + interface=instance._interface, ) ) diff --git a/openstackclient/network/client.py b/openstackclient/network/client.py index de08e5e2fe..0ef6885287 100644 --- a/openstackclient/network/client.py +++ b/openstackclient/network/client.py @@ -47,12 +47,11 @@ def make_client(instance): endpoint = instance.get_endpoint_for_service_type( API_NAME, region_name=instance._region_name, - endpoint_type=instance._endpoint_type, + interface=instance._interface, ) # Remember endpoint_type only if it is set - kwargs = utils.build_kwargs_dict('endpoint_type', - instance._endpoint_type) + kwargs = utils.build_kwargs_dict('endpoint_type', instance._interface) client = network_client( session=instance.session, diff --git a/openstackclient/object/client.py b/openstackclient/object/client.py index 676f664217..0359940d82 100644 --- a/openstackclient/object/client.py +++ b/openstackclient/object/client.py @@ -36,7 +36,7 @@ def make_client(instance): endpoint = instance.get_endpoint_for_service_type( 'object-store', region_name=instance._region_name, - endpoint_type=instance._endpoint_type, + interface=instance._interface, ) client = object_store_v1.APIv1( diff --git a/openstackclient/shell.py b/openstackclient/shell.py index b4e5904c2a..edeffdfb54 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -209,14 +209,14 @@ def build_option_parser(self, description, version): DEFAULT_DOMAIN + ' (Env: OS_DEFAULT_DOMAIN)') parser.add_argument( - '--os-endpoint-type', - metavar='', - dest='endpoint_type', + '--os-interface', + metavar='', + dest='interface', choices=['admin', 'public', 'internal'], - default=utils.env('OS_ENDPOINT_TYPE'), - help='Select an endpoint type.' - ' Valid endpoint types: [admin, public, internal].' - ' (Env: OS_ENDPOINT_TYPE)') + default=utils.env('OS_INTERFACE'), + help='Select an interface type.' + ' Valid interface types: [admin, public, internal].' + ' (Env: OS_INTERFACE)') parser.add_argument( '--timing', default=False, @@ -263,10 +263,10 @@ def initialize_app(self, argv): self.options.project_name = tenant_name # Do configuration file handling - # Ignore the default value of endpoint_type. Only if it is set later + # Ignore the default value of interface. Only if it is set later # will it be used. cc = cloud_config.OpenStackConfig( - override_defaults={'endpoint_type': None, }) + override_defaults={'interface': None, }) self.log.debug("defaults: %s", cc.defaults) self.cloud = cc.get_one_cloud( diff --git a/openstackclient/tests/common/test_clientmanager.py b/openstackclient/tests/common/test_clientmanager.py index e86ef50997..29cc59ed26 100644 --- a/openstackclient/tests/common/test_clientmanager.py +++ b/openstackclient/tests/common/test_clientmanager.py @@ -54,7 +54,7 @@ def __init__(self, **kwargs): self.identity_api_version = '2.0' self.timing = None self.region_name = None - self.endpoint_type = None + self.interface = None self.url = None self.auth = {} self.default_domain = 'default' @@ -124,7 +124,7 @@ def test_client_manager_token(self): auth_url=fakes.AUTH_URL, ), auth_type='v2token', - endpoint_type=fakes.ENDPOINT_TYPE, + interface=fakes.INTERFACE, region_name=fakes.REGION_NAME, ), api_version=API_VERSION, @@ -141,8 +141,8 @@ def test_client_manager_token(self): auth_v2.Token, ) self.assertEqual( - fakes.ENDPOINT_TYPE, - client_manager._endpoint_type, + fakes.INTERFACE, + client_manager._interface, ) self.assertEqual( fakes.REGION_NAME, diff --git a/openstackclient/tests/fakes.py b/openstackclient/tests/fakes.py index a9322ec3ed..ff69c190df 100644 --- a/openstackclient/tests/fakes.py +++ b/openstackclient/tests/fakes.py @@ -27,7 +27,7 @@ PASSWORD = "scratchy" PROJECT_NAME = "poochie" REGION_NAME = "richie" -ENDPOINT_TYPE = "catchy" +INTERFACE = "catchy" TEST_RESPONSE_DICT = fixture.V2Token(token_id=AUTH_TOKEN, user_name=USERNAME) diff --git a/openstackclient/tests/test_shell.py b/openstackclient/tests/test_shell.py index 674d83452d..4e1b0ed846 100644 --- a/openstackclient/tests/test_shell.py +++ b/openstackclient/tests/test_shell.py @@ -38,7 +38,7 @@ DEFAULT_TOKEN = "token" DEFAULT_SERVICE_URL = "http://127.0.0.1:8771/v3.0/" DEFAULT_AUTH_PLUGIN = "v2password" -DEFAULT_ENDPOINT_TYPE = "internal" +DEFAULT_INTERFACE = "internal" DEFAULT_COMPUTE_API_VERSION = "2" DEFAULT_IDENTITY_API_VERSION = "2" @@ -62,7 +62,7 @@ }, 'region_name': 'occ-cloud', 'donut': 'glazed', - 'endpoint_type': 'public', + 'interface': 'public', } } } @@ -106,7 +106,7 @@ '--os-default-domain': (DEFAULT_DOMAIN_NAME, True, True), '--os-cacert': ('/dev/null', True, True), '--timing': (True, True, False), - '--os-endpoint-type': (DEFAULT_ENDPOINT_TYPE, True, True) + '--os-interface': (DEFAULT_INTERFACE, True, True) } auth_options = { @@ -126,7 +126,7 @@ '--os-auth-type': ("v2password", True, True), '--os-token': (DEFAULT_TOKEN, True, True), '--os-url': (DEFAULT_SERVICE_URL, True, True), - '--os-endpoint-type': (DEFAULT_ENDPOINT_TYPE, True, True), + '--os-interface': (DEFAULT_INTERFACE, True, True), } @@ -614,7 +614,7 @@ def test_shell_args_cloud_no_vendor(self, config_mock): ) self.assertEqual( 'public', - _shell.cloud.config['endpoint_type'], + _shell.cloud.config['interface'], ) @mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file") diff --git a/openstackclient/volume/client.py b/openstackclient/volume/client.py index 965c42ec2d..093178e305 100644 --- a/openstackclient/volume/client.py +++ b/openstackclient/volume/client.py @@ -53,9 +53,8 @@ def make_client(instance): extensions = [extension.Extension('list_extensions', list_extensions)] - # Remember endpoint_type only if it is set - kwargs = utils.build_kwargs_dict('endpoint_type', - instance._endpoint_type) + # Remember interface only if it is set + kwargs = utils.build_kwargs_dict('endpoint_type', instance._interface) client = volume_client( session=instance.session, From c830d96e4f3af5a8ce7e903fae02e01409ffdfdd Mon Sep 17 00:00:00 2001 From: David Moreau Simard Date: Wed, 15 Jul 2015 15:40:51 -0400 Subject: [PATCH 0159/3095] Fix image save with API v2 Glanceclient v2 no longer expects the whole image object, just the image id. Change-Id: I8f34acfa50ca2d50eb7c9eb1dd5114c4621ad158 Closes-bug: #1475001 --- openstackclient/image/v2/image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 3dd9833849..3808f6cf28 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -219,7 +219,7 @@ def take_action(self, parsed_args): image_client.images, parsed_args.image, ) - data = image_client.images.data(image) + data = image_client.images.data(image.id) gc_utils.save_image(data, parsed_args.file) From 37c83e6231dcfb2f0f4d28f13f78ac2dc170b768 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Thu, 16 Jul 2015 01:59:08 -0400 Subject: [PATCH 0160/3095] Fix the way we call find_resource when only using ID Change-Id: I6fb08edd5499767863e0e67f363bcd9fff3aea60 Closes-Bug: 1475127 --- openstackclient/identity/common.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/openstackclient/identity/common.py b/openstackclient/identity/common.py index 2638b7976e..d0edb0bd5a 100644 --- a/openstackclient/identity/common.py +++ b/openstackclient/identity/common.py @@ -57,20 +57,32 @@ def find_domain(identity_client, name_or_id): def find_group(identity_client, name_or_id, domain_name_or_id=None): domain_id = _get_domain_id_if_requested(identity_client, domain_name_or_id) - return _find_identity_resource(identity_client.groups, name_or_id, - groups.Group, domain_id=domain_id) + if not domain_id: + return _find_identity_resource(identity_client.groups, name_or_id, + groups.Group) + else: + return _find_identity_resource(identity_client.groups, name_or_id, + groups.Group, domain_id=domain_id) def find_project(identity_client, name_or_id, domain_name_or_id=None): domain_id = _get_domain_id_if_requested(identity_client, domain_name_or_id) - return _find_identity_resource(identity_client.projects, name_or_id, - projects.Project, domain_id=domain_id) + if not domain_id: + return _find_identity_resource(identity_client.projects, name_or_id, + projects.Project) + else: + return _find_identity_resource(identity_client.projects, name_or_id, + projects.Project, domain_id=domain_id) def find_user(identity_client, name_or_id, domain_name_or_id=None): domain_id = _get_domain_id_if_requested(identity_client, domain_name_or_id) - return _find_identity_resource(identity_client.users, name_or_id, - users.User, domain_id=domain_id) + if not domain_id: + return _find_identity_resource(identity_client.users, name_or_id, + users.User) + else: + return _find_identity_resource(identity_client.users, name_or_id, + users.User, domain_id=domain_id) def _find_identity_resource(identity_client_manager, name_or_id, From 7829aca70459753d7df0e197f4bf6090542f9b7b Mon Sep 17 00:00:00 2001 From: Guojian Shao Date: Wed, 15 Jul 2015 12:37:30 +0800 Subject: [PATCH 0161/3095] only return endpoints that have url Change-Id: I97a502252c0c377fce573e92b83c0122812f6f80 Closes-Bug: #1474656 --- openstackclient/identity/v2_0/catalog.py | 6 ++- .../tests/identity/v2_0/test_catalog.py | 40 +++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/openstackclient/identity/v2_0/catalog.py b/openstackclient/identity/v2_0/catalog.py index c10001d0df..e166c855c4 100644 --- a/openstackclient/identity/v2_0/catalog.py +++ b/openstackclient/identity/v2_0/catalog.py @@ -30,8 +30,10 @@ def _format_endpoints(eps=None): for index, ep in enumerate(eps): region = eps[index].get('region', '') ret += region + '\n' - for url in ['publicURL', 'internalURL', 'adminURL']: - ret += " %s: %s\n" % (url, eps[index][url]) + for endpoint_type in ['publicURL', 'internalURL', 'adminURL']: + url = eps[index].get(endpoint_type) + if url: + ret += " %s: %s\n" % (endpoint_type, url) return ret diff --git a/openstackclient/tests/identity/v2_0/test_catalog.py b/openstackclient/tests/identity/v2_0/test_catalog.py index fe13d78d02..7f1835d674 100644 --- a/openstackclient/tests/identity/v2_0/test_catalog.py +++ b/openstackclient/tests/identity/v2_0/test_catalog.py @@ -84,6 +84,46 @@ def test_catalog_list(self): ), ) self.assertEqual(datalist, tuple(data)) + def test_catalog_list_with_endpoint_url(self): + fake_service = { + 'id': 'qwertyuiop', + 'type': 'compute', + 'name': 'supernova', + 'endpoints': [ + { + 'region': 'one', + 'publicURL': 'https://public.one.example.com', + }, + { + 'region': 'two', + 'publicURL': 'https://public.two.example.com', + 'internalURL': 'https://internal.two.example.com', + }, + ], + } + self.sc_mock.service_catalog.get_data.return_value = [ + fake_service, + ] + + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + self.sc_mock.service_catalog.get_data.assert_called_with() + + collist = ('Name', 'Type', 'Endpoints') + self.assertEqual(collist, columns) + datalist = (( + 'supernova', + 'compute', + 'one\n publicURL: https://public.one.example.com\n' + 'two\n publicURL: https://public.two.example.com\n ' + 'internalURL: https://internal.two.example.com\n' + ), ) + self.assertEqual(datalist, tuple(data)) + class TestCatalogShow(TestCatalog): From 7b32ec003babec2c6d052146baf2951e465454f5 Mon Sep 17 00:00:00 2001 From: Guojian Shao Date: Thu, 16 Jul 2015 23:23:45 +0800 Subject: [PATCH 0162/3095] add --project-domain option for user v3 user v3 create/set only support --project option, we need --project-domain to prevent collisions between project names exist. Change-Id: I2d62e5b9bb6df4c5c5a9542514faf2e4365bb18b Closes-Bug: #1475357 --- doc/source/command-objects/user.rst | 14 ++++- openstackclient/identity/v3/user.py | 14 +++-- .../tests/identity/v3/test_user.py | 62 +++++++++++++++++++ 3 files changed, 82 insertions(+), 8 deletions(-) diff --git a/doc/source/command-objects/user.rst b/doc/source/command-objects/user.rst index 8aec76b40d..2e297bb360 100644 --- a/doc/source/command-objects/user.rst +++ b/doc/source/command-objects/user.rst @@ -14,7 +14,7 @@ Create new user os user create [--domain ] - [--project ] + [--project [--project-domain ]] [--password ] [--password-prompt] [--email ] @@ -33,6 +33,11 @@ Create new user Default project (name or ID) +.. option:: --project-domain + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + .. option:: --password Set user password @@ -136,7 +141,7 @@ Set user properties os user set [--name ] - [--project ] + [--project [--project-domain ]] [--password ] [--email ] [--description ] @@ -151,6 +156,11 @@ Set user properties Set default project (name or ID) +.. option:: --project-domain + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + .. option:: --password Set user password diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index b72e0d1580..459707d2af 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -51,6 +51,7 @@ def get_parser(self, prog_name): metavar='', help='Default project (name or ID)', ) + common.add_project_domain_option_to_parser(parser) parser.add_argument( '--password', metavar='', @@ -96,10 +97,9 @@ def take_action(self, parsed_args): project_id = None if parsed_args.project: - project_id = utils.find_resource( - identity_client.projects, - parsed_args.project, - ).id + project_id = common.find_project(identity_client, + parsed_args.project, + parsed_args.project_domain).id domain_id = None if parsed_args.domain: @@ -301,6 +301,7 @@ def get_parser(self, prog_name): metavar='', help='Set default project (name or ID)', ) + common.add_project_domain_option_to_parser(parser) parser.add_argument( '--password', metavar='', @@ -367,8 +368,9 @@ def take_action(self, parsed_args): if parsed_args.description: kwargs['description'] = parsed_args.description if parsed_args.project: - project_id = utils.find_resource( - identity_client.projects, parsed_args.project).id + project_id = common.find_project(identity_client, + parsed_args.project, + parsed_args.project_domain).id kwargs['default_project'] = project_id kwargs['enabled'] = user.enabled if parsed_args.enable: diff --git a/openstackclient/tests/identity/v3/test_user.py b/openstackclient/tests/identity/v3/test_user.py index 18fe9016d4..bdde25a2ce 100644 --- a/openstackclient/tests/identity/v3/test_user.py +++ b/openstackclient/tests/identity/v3/test_user.py @@ -320,6 +320,68 @@ def test_user_create_project(self): ) self.assertEqual(datalist, data) + def test_user_create_project_domain(self): + # Return the new project + self.projects_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.PROJECT_2), + loaded=True, + ) + # Set up to return an updated user + USER_2 = copy.deepcopy(identity_fakes.USER) + USER_2['default_project_id'] = identity_fakes.PROJECT_2['id'] + self.users_mock.create.return_value = fakes.FakeResource( + None, + USER_2, + loaded=True, + ) + + arglist = [ + '--project', identity_fakes.PROJECT_2['name'], + '--project-domain', identity_fakes.PROJECT_2['domain_id'], + identity_fakes.user_name, + ] + verifylist = [ + ('project', identity_fakes.PROJECT_2['name']), + ('project_domain', identity_fakes.PROJECT_2['domain_id']), + ('enable', False), + ('disable', False), + ('name', identity_fakes.user_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'name': identity_fakes.user_name, + 'default_project': identity_fakes.PROJECT_2['id'], + 'description': None, + 'domain': None, + 'email': None, + 'enabled': True, + 'password': None, + } + # UserManager.create(name=, domain=, project=, password=, email=, + # description=, enabled=, default_project=) + self.users_mock.create.assert_called_with( + **kwargs + ) + + collist = ('default_project_id', 'domain_id', 'email', + 'enabled', 'id', 'name') + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.PROJECT_2['id'], + identity_fakes.domain_id, + identity_fakes.user_email, + True, + identity_fakes.user_id, + identity_fakes.user_name, + ) + self.assertEqual(datalist, data) + def test_user_create_domain(self): arglist = [ '--domain', identity_fakes.domain_name, From cc522821f6aef901c71b141a832ea6f7808092b9 Mon Sep 17 00:00:00 2001 From: Guojian Shao Date: Wed, 15 Jul 2015 11:44:49 +0800 Subject: [PATCH 0163/3095] add functional tests for identity v2 add tests for endpoint and service. Change-Id: Iec80106d6e4c310cea1c5af262d145ac1f56525e Implements: blueprint identity-functional-tests --- functional/tests/identity/v2/test_endpoint.py | 38 ++++++++++++++ functional/tests/identity/v2/test_identity.py | 50 +++++++++++++++++++ functional/tests/identity/v2/test_service.py | 37 ++++++++++++++ 3 files changed, 125 insertions(+) create mode 100644 functional/tests/identity/v2/test_endpoint.py create mode 100644 functional/tests/identity/v2/test_service.py diff --git a/functional/tests/identity/v2/test_endpoint.py b/functional/tests/identity/v2/test_endpoint.py new file mode 100644 index 0000000000..0aed3220c8 --- /dev/null +++ b/functional/tests/identity/v2/test_endpoint.py @@ -0,0 +1,38 @@ +# 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 functional.tests.identity.v2 import test_identity + + +class EndpointTests(test_identity.IdentityTests): + + def test_endpoint_create(self): + self._create_dummy_endpoint() + + def test_endpoint_delete(self): + endpoint_id = self._create_dummy_endpoint(add_clean_up=False) + raw_output = self.openstack( + 'endpoint delete %s' % endpoint_id) + self.assertEqual(0, len(raw_output)) + + def test_endpoint_list(self): + endpoint_id = self._create_dummy_endpoint() + raw_output = self.openstack('endpoint list') + self.assertInOutput(endpoint_id, raw_output) + items = self.parse_listing(raw_output) + self.assert_table_structure(items, self.ENDPOINT_LIST_HEADERS) + + def test_endpoint_show(self): + endpoint_id = self._create_dummy_endpoint() + raw_output = self.openstack('endpoint show %s' % endpoint_id) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.ENDPOINT_FIELDS) diff --git a/functional/tests/identity/v2/test_identity.py b/functional/tests/identity/v2/test_identity.py index 07fc354e45..1badf2fdcc 100644 --- a/functional/tests/identity/v2/test_identity.py +++ b/functional/tests/identity/v2/test_identity.py @@ -25,12 +25,17 @@ class IdentityTests(test.TestCase): PROJECT_FIELDS = ['enabled', 'id', 'name', 'description', 'domain_id'] TOKEN_FIELDS = ['expires', 'id', 'project_id', 'user_id'] ROLE_FIELDS = ['id', 'name', 'links'] + SERVICE_FIELDS = ['id', 'enabled', 'name', 'type', 'description'] + ENDPOINT_FIELDS = ['id', 'region', 'service_id', 'service_name', + 'service_type', 'enabled', 'publicurl', + 'adminurl', 'internalurl'] EC2_CREDENTIALS_FIELDS = ['access', 'project_id', 'secret', 'trust_id', 'user_id'] EC2_CREDENTIALS_LIST_HEADERS = ['Access', 'Secret', 'Project ID', 'User ID'] CATALOG_LIST_HEADERS = ['Name', 'Type', 'Endpoints'] + ENDPOINT_LIST_HEADERS = ['ID', 'Region', 'Service Name', 'Service Type'] @classmethod def setUpClass(cls): @@ -127,3 +132,48 @@ def _create_dummy_token(self, add_clean_up=True): self.addCleanup(self.openstack, 'token revoke %s' % token['id']) return token['id'] + + def _create_dummy_service(self, add_clean_up=True): + service_name = data_utils.rand_name('TestService') + description = data_utils.rand_name('description') + type_name = data_utils.rand_name('TestType') + raw_output = self.openstack( + 'service create ' + '--name %(name)s ' + '--description %(description)s ' + '%(type)s' % {'name': service_name, + 'description': description, + 'type': type_name}) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.SERVICE_FIELDS) + if add_clean_up: + service = self.parse_show_as_object(raw_output) + self.addCleanup(self.openstack, + 'service delete %s' % service['id']) + return service_name + + def _create_dummy_endpoint(self, add_clean_up=True): + region_id = data_utils.rand_name('TestRegion') + service_name = self._create_dummy_service() + public_url = data_utils.rand_url() + admin_url = data_utils.rand_url() + internal_url = data_utils.rand_url() + raw_output = self.openstack( + 'endpoint create ' + '--publicurl %(publicurl)s ' + '--adminurl %(adminurl)s ' + '--internalurl %(internalurl)s ' + '--region %(region)s ' + '%(service)s' % {'publicurl': public_url, + 'adminurl': admin_url, + 'internalurl': internal_url, + 'region': region_id, + 'service': service_name}) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.ENDPOINT_FIELDS) + endpoint = self.parse_show_as_object(raw_output) + if add_clean_up: + self.addCleanup( + self.openstack, + 'endpoint delete %s' % endpoint['id']) + return endpoint['id'] diff --git a/functional/tests/identity/v2/test_service.py b/functional/tests/identity/v2/test_service.py new file mode 100644 index 0000000000..bd982be1bd --- /dev/null +++ b/functional/tests/identity/v2/test_service.py @@ -0,0 +1,37 @@ +# 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 functional.tests.identity.v2 import test_identity + + +class ServiceTests(test_identity.IdentityTests): + + def test_service_create(self): + self._create_dummy_service() + + def test_service_delete(self): + service_name = self._create_dummy_service(add_clean_up=False) + raw_output = self.openstack('service delete %s' % service_name) + self.assertEqual(0, len(raw_output)) + + def test_service_list(self): + self._create_dummy_service() + raw_output = self.openstack('service list') + items = self.parse_listing(raw_output) + self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS) + + def test_service_show(self): + service_name = self._create_dummy_service() + raw_output = self.openstack( + 'service show %s' % service_name) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.SERVICE_FIELDS) From abfb0115277471dd584daed18e09eeecf768b527 Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Mon, 6 Jul 2015 11:22:05 -0600 Subject: [PATCH 0164/3095] Support multiple volume qos delete Also make sure that we are using the singular qos_spec when we should and the plural qos_specs otherwise. Change-Id: If4bbeb6fc245d7d80dc3d7dccfe9f949e802653c --- doc/source/command-objects/volume_qos.rst | 26 +++---- .../tests/volume/v1/test_qos_specs.py | 16 ++-- .../tests/volume/v2/test_qos_specs.py | 16 ++-- openstackclient/volume/v1/qos_specs.py | 75 +++++++++---------- openstackclient/volume/v2/qos_specs.py | 75 +++++++++---------- 5 files changed, 103 insertions(+), 105 deletions(-) diff --git a/doc/source/command-objects/volume_qos.rst b/doc/source/command-objects/volume_qos.rst index 287059f1cc..d0fe36f940 100644 --- a/doc/source/command-objects/volume_qos.rst +++ b/doc/source/command-objects/volume_qos.rst @@ -13,10 +13,10 @@ Associate a QoS specification to a volume type .. code:: bash os volume qos associate - + -.. describe:: +.. describe:: QoS specification to modify (name or ID) @@ -58,11 +58,11 @@ Delete QoS specification .. code:: bash os volume qos delete - + [ ...] -.. describe:: +.. describe:: - QoS specification to delete (name or ID) + QoS specification(s) to delete (name or ID) volume qos disassociate ----------------------- @@ -74,7 +74,7 @@ Disassociate a QoS specification from a volume type os volume qos disassociate --volume-type | --all - + .. option:: --volume-type @@ -84,7 +84,7 @@ Disassociate a QoS specification from a volume type Disassociate the QoS from every volume type -.. describe:: +.. describe:: QoS specification to modify (name or ID) @@ -108,13 +108,13 @@ Set QoS specification properties os volume qos set [--property [...] ] - + .. option:: --property Property to add or modify for this QoS specification (repeat option to set multiple properties) -.. describe:: +.. describe:: QoS specification to modify (name or ID) @@ -127,9 +127,9 @@ Display QoS specification details .. code:: bash os volume qos show - + -.. describe:: +.. describe:: QoS specification to display (name or ID) @@ -143,12 +143,12 @@ Unset QoS specification properties os volume qos unset [--property ] - + .. option:: --property Property to remove from QoS specification (repeat option to remove multiple properties) -.. describe:: +.. describe:: QoS specification to modify (name or ID) diff --git a/openstackclient/tests/volume/v1/test_qos_specs.py b/openstackclient/tests/volume/v1/test_qos_specs.py index 226fe673e5..c2e6c0afc8 100644 --- a/openstackclient/tests/volume/v1/test_qos_specs.py +++ b/openstackclient/tests/volume/v1/test_qos_specs.py @@ -56,7 +56,7 @@ def test_qos_associate(self): volume_fakes.type_id ] verifylist = [ - ('qos_specs', volume_fakes.qos_id), + ('qos_spec', volume_fakes.qos_id), ('volume_type', volume_fakes.type_id) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -210,7 +210,7 @@ def test_qos_delete_with_id(self): volume_fakes.qos_id ] verifylist = [ - ('qos_specs', volume_fakes.qos_id) + ('qos_specs', [volume_fakes.qos_id]) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -223,7 +223,7 @@ def test_qos_delete_with_name(self): volume_fakes.qos_name ] verifylist = [ - ('qos_specs', volume_fakes.qos_name) + ('qos_specs', [volume_fakes.qos_name]) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -255,7 +255,7 @@ def test_qos_disassociate_with_volume_type(self): '--volume-type', volume_fakes.type_id ] verifylist = [ - ('qos_specs', volume_fakes.qos_id), + ('qos_spec', volume_fakes.qos_id), ('volume_type', volume_fakes.type_id) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -278,7 +278,7 @@ def test_qos_disassociate_with_all_volume_types(self): '--all' ] verifylist = [ - ('qos_specs', volume_fakes.qos_id) + ('qos_spec', volume_fakes.qos_id) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -351,7 +351,7 @@ def test_qos_set_with_properties_with_id(self): '--property', 'iops=9001' ] verifylist = [ - ('qos_specs', volume_fakes.qos_id), + ('qos_spec', volume_fakes.qos_id), ('property', volume_fakes.qos_specs) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -386,7 +386,7 @@ def test_qos_show(self): volume_fakes.qos_id ] verifylist = [ - ('qos_specs', volume_fakes.qos_id) + ('qos_spec', volume_fakes.qos_id) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -434,7 +434,7 @@ def test_qos_unset_with_properties(self): ] verifylist = [ - ('qos_specs', volume_fakes.qos_id), + ('qos_spec', volume_fakes.qos_id), ('property', ['iops', 'foo']) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/volume/v2/test_qos_specs.py b/openstackclient/tests/volume/v2/test_qos_specs.py index 6a5509887a..4222ed0772 100644 --- a/openstackclient/tests/volume/v2/test_qos_specs.py +++ b/openstackclient/tests/volume/v2/test_qos_specs.py @@ -56,7 +56,7 @@ def test_qos_associate(self): volume_fakes.type_id ] verifylist = [ - ('qos_specs', volume_fakes.qos_id), + ('qos_spec', volume_fakes.qos_id), ('volume_type', volume_fakes.type_id) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -210,7 +210,7 @@ def test_qos_delete_with_id(self): volume_fakes.qos_id ] verifylist = [ - ('qos_specs', volume_fakes.qos_id) + ('qos_specs', [volume_fakes.qos_id]) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -223,7 +223,7 @@ def test_qos_delete_with_name(self): volume_fakes.qos_name ] verifylist = [ - ('qos_specs', volume_fakes.qos_name) + ('qos_specs', [volume_fakes.qos_name]) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -255,7 +255,7 @@ def test_qos_disassociate_with_volume_type(self): '--volume-type', volume_fakes.type_id ] verifylist = [ - ('qos_specs', volume_fakes.qos_id), + ('qos_spec', volume_fakes.qos_id), ('volume_type', volume_fakes.type_id) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -278,7 +278,7 @@ def test_qos_disassociate_with_all_volume_types(self): '--all' ] verifylist = [ - ('qos_specs', volume_fakes.qos_id) + ('qos_spec', volume_fakes.qos_id) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -351,7 +351,7 @@ def test_qos_set_with_properties_with_id(self): '--property', 'iops=9001' ] verifylist = [ - ('qos_specs', volume_fakes.qos_id), + ('qos_spec', volume_fakes.qos_id), ('property', volume_fakes.qos_specs) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -386,7 +386,7 @@ def test_qos_show(self): volume_fakes.qos_id ] verifylist = [ - ('qos_specs', volume_fakes.qos_id) + ('qos_spec', volume_fakes.qos_id) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -434,7 +434,7 @@ def test_qos_unset_with_properties(self): ] verifylist = [ - ('qos_specs', volume_fakes.qos_id), + ('qos_spec', volume_fakes.qos_id), ('property', ['iops', 'foo']) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/openstackclient/volume/v1/qos_specs.py b/openstackclient/volume/v1/qos_specs.py index eacb970687..8e909e3d3b 100644 --- a/openstackclient/volume/v1/qos_specs.py +++ b/openstackclient/volume/v1/qos_specs.py @@ -34,8 +34,8 @@ class AssociateQos(command.Command): def get_parser(self, prog_name): parser = super(AssociateQos, self).get_parser(prog_name) parser.add_argument( - 'qos_specs', - metavar='', + 'qos_spec', + metavar='', help='QoS specification to modify (name or ID)', ) parser.add_argument( @@ -48,12 +48,12 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume - qos_specs = utils.find_resource(volume_client.qos_specs, - parsed_args.qos_specs) + qos_spec = utils.find_resource(volume_client.qos_specs, + parsed_args.qos_spec) volume_type = utils.find_resource(volume_client.volume_types, parsed_args.volume_type) - volume_client.qos_specs.associate(qos_specs.id, volume_type.id) + volume_client.qos_specs.associate(qos_spec.id, volume_type.id) return @@ -97,9 +97,9 @@ def take_action(self, parsed_args): if parsed_args.property: specs.update(parsed_args.property) - qos_specs = volume_client.qos_specs.create(parsed_args.name, specs) + qos_spec = volume_client.qos_specs.create(parsed_args.name, specs) - return zip(*sorted(six.iteritems(qos_specs._info))) + return zip(*sorted(six.iteritems(qos_spec._info))) class DeleteQos(command.Command): @@ -111,19 +111,18 @@ def get_parser(self, prog_name): parser = super(DeleteQos, self).get_parser(prog_name) parser.add_argument( 'qos_specs', - metavar='', - help='QoS specification to delete (name or ID)', + metavar='', + nargs="+", + help='QoS specification(s) to delete (name or ID)', ) return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume - qos_specs = utils.find_resource(volume_client.qos_specs, - parsed_args.qos_specs) - - volume_client.qos_specs.delete(qos_specs.id) - + for qos in parsed_args.qos_specs: + qos_spec = utils.find_resource(volume_client.qos_specs, qos) + volume_client.qos_specs.delete(qos_spec.id) return @@ -135,8 +134,8 @@ class DisassociateQos(command.Command): def get_parser(self, prog_name): parser = super(DisassociateQos, self).get_parser(prog_name) parser.add_argument( - 'qos_specs', - metavar='', + 'qos_spec', + metavar='', help='QoS specification to modify (name or ID)', ) volume_type_group = parser.add_mutually_exclusive_group() @@ -157,15 +156,15 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume - qos_specs = utils.find_resource(volume_client.qos_specs, - parsed_args.qos_specs) + qos_spec = utils.find_resource(volume_client.qos_specs, + parsed_args.qos_spec) if parsed_args.volume_type: volume_type = utils.find_resource(volume_client.volume_types, parsed_args.volume_type) - volume_client.qos_specs.disassociate(qos_specs.id, volume_type.id) + volume_client.qos_specs.disassociate(qos_spec.id, volume_type.id) elif parsed_args.all: - volume_client.qos_specs.disassociate_all(qos_specs.id) + volume_client.qos_specs.disassociate_all(qos_spec.id) return @@ -206,8 +205,8 @@ class SetQos(command.Command): def get_parser(self, prog_name): parser = super(SetQos, self).get_parser(prog_name) parser.add_argument( - 'qos_specs', - metavar='', + 'qos_spec', + metavar='', help='QoS specification to modify (name or ID)', ) parser.add_argument( @@ -222,11 +221,11 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume - qos_specs = utils.find_resource(volume_client.qos_specs, - parsed_args.qos_specs) + qos_spec = utils.find_resource(volume_client.qos_specs, + parsed_args.qos_spec) if parsed_args.property: - volume_client.qos_specs.set_keys(qos_specs.id, + volume_client.qos_specs.set_keys(qos_spec.id, parsed_args.property) else: self.app.log.error("No changes requested\n") @@ -242,8 +241,8 @@ class ShowQos(show.ShowOne): def get_parser(self, prog_name): parser = super(ShowQos, self).get_parser(prog_name) parser.add_argument( - 'qos_specs', - metavar='', + 'qos_spec', + metavar='', help='QoS specification to display (name or ID)', ) return parser @@ -251,19 +250,19 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume - qos_specs = utils.find_resource(volume_client.qos_specs, - parsed_args.qos_specs) + qos_spec = utils.find_resource(volume_client.qos_specs, + parsed_args.qos_spec) - qos_associations = volume_client.qos_specs.get_associations(qos_specs) + qos_associations = volume_client.qos_specs.get_associations(qos_spec) if qos_associations: associations = [association.name for association in qos_associations] - qos_specs._info.update({ + qos_spec._info.update({ 'associations': utils.format_list(associations) }) - qos_specs._info.update({'specs': utils.format_dict(qos_specs.specs)}) + qos_spec._info.update({'specs': utils.format_dict(qos_spec.specs)}) - return zip(*sorted(six.iteritems(qos_specs._info))) + return zip(*sorted(six.iteritems(qos_spec._info))) class UnsetQos(command.Command): @@ -274,8 +273,8 @@ class UnsetQos(command.Command): def get_parser(self, prog_name): parser = super(UnsetQos, self).get_parser(prog_name) parser.add_argument( - 'qos_specs', - metavar='', + 'qos_spec', + metavar='', help='QoS specification to modify (name or ID)', ) parser.add_argument( @@ -291,11 +290,11 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume - qos_specs = utils.find_resource(volume_client.qos_specs, - parsed_args.qos_specs) + qos_spec = utils.find_resource(volume_client.qos_specs, + parsed_args.qos_spec) if parsed_args.property: - volume_client.qos_specs.unset_keys(qos_specs.id, + volume_client.qos_specs.unset_keys(qos_spec.id, parsed_args.property) else: self.app.log.error("No changes requested\n") diff --git a/openstackclient/volume/v2/qos_specs.py b/openstackclient/volume/v2/qos_specs.py index 7f02fa4a2d..ac78ca1575 100644 --- a/openstackclient/volume/v2/qos_specs.py +++ b/openstackclient/volume/v2/qos_specs.py @@ -34,8 +34,8 @@ class AssociateQos(command.Command): def get_parser(self, prog_name): parser = super(AssociateQos, self).get_parser(prog_name) parser.add_argument( - 'qos_specs', - metavar='', + 'qos_spec', + metavar='', help='QoS specification to modify (name or ID)', ) parser.add_argument( @@ -48,12 +48,12 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume - qos_specs = utils.find_resource(volume_client.qos_specs, - parsed_args.qos_specs) + qos_spec = utils.find_resource(volume_client.qos_specs, + parsed_args.qos_spec) volume_type = utils.find_resource(volume_client.volume_types, parsed_args.volume_type) - volume_client.qos_specs.associate(qos_specs.id, volume_type.id) + volume_client.qos_specs.associate(qos_spec.id, volume_type.id) return @@ -97,9 +97,9 @@ def take_action(self, parsed_args): if parsed_args.property: specs.update(parsed_args.property) - qos_specs = volume_client.qos_specs.create(parsed_args.name, specs) + qos_spec = volume_client.qos_specs.create(parsed_args.name, specs) - return zip(*sorted(six.iteritems(qos_specs._info))) + return zip(*sorted(six.iteritems(qos_spec._info))) class DeleteQos(command.Command): @@ -111,19 +111,18 @@ def get_parser(self, prog_name): parser = super(DeleteQos, self).get_parser(prog_name) parser.add_argument( 'qos_specs', - metavar='', - help='QoS specification to delete (name or ID)', + metavar='', + nargs="+", + help='QoS specification(s) to delete (name or ID)', ) return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume - qos_specs = utils.find_resource(volume_client.qos_specs, - parsed_args.qos_specs) - - volume_client.qos_specs.delete(qos_specs.id) - + for qos in parsed_args.qos_specs: + qos_spec = utils.find_resource(volume_client.qos_specs, qos) + volume_client.qos_specs.delete(qos_spec.id) return @@ -135,8 +134,8 @@ class DisassociateQos(command.Command): def get_parser(self, prog_name): parser = super(DisassociateQos, self).get_parser(prog_name) parser.add_argument( - 'qos_specs', - metavar='', + 'qos_spec', + metavar='', help='QoS specification to modify (name or ID)', ) volume_type_group = parser.add_mutually_exclusive_group() @@ -157,15 +156,15 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume - qos_specs = utils.find_resource(volume_client.qos_specs, - parsed_args.qos_specs) + qos_spec = utils.find_resource(volume_client.qos_specs, + parsed_args.qos_spec) if parsed_args.volume_type: volume_type = utils.find_resource(volume_client.volume_types, parsed_args.volume_type) - volume_client.qos_specs.disassociate(qos_specs.id, volume_type.id) + volume_client.qos_specs.disassociate(qos_spec.id, volume_type.id) elif parsed_args.all: - volume_client.qos_specs.disassociate_all(qos_specs.id) + volume_client.qos_specs.disassociate_all(qos_spec.id) return @@ -206,8 +205,8 @@ class SetQos(command.Command): def get_parser(self, prog_name): parser = super(SetQos, self).get_parser(prog_name) parser.add_argument( - 'qos_specs', - metavar='', + 'qos_spec', + metavar='', help='QoS specification to modify (name or ID)', ) parser.add_argument( @@ -222,11 +221,11 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume - qos_specs = utils.find_resource(volume_client.qos_specs, - parsed_args.qos_specs) + qos_spec = utils.find_resource(volume_client.qos_specs, + parsed_args.qos_spec) if parsed_args.property: - volume_client.qos_specs.set_keys(qos_specs.id, + volume_client.qos_specs.set_keys(qos_spec.id, parsed_args.property) else: self.app.log.error("No changes requested\n") @@ -242,8 +241,8 @@ class ShowQos(show.ShowOne): def get_parser(self, prog_name): parser = super(ShowQos, self).get_parser(prog_name) parser.add_argument( - 'qos_specs', - metavar='', + 'qos_spec', + metavar='', help='QoS specification to display (name or ID)', ) return parser @@ -251,19 +250,19 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume - qos_specs = utils.find_resource(volume_client.qos_specs, - parsed_args.qos_specs) + qos_spec = utils.find_resource(volume_client.qos_specs, + parsed_args.qos_spec) - qos_associations = volume_client.qos_specs.get_associations(qos_specs) + qos_associations = volume_client.qos_specs.get_associations(qos_spec) if qos_associations: associations = [association.name for association in qos_associations] - qos_specs._info.update({ + qos_spec._info.update({ 'associations': utils.format_list(associations) }) - qos_specs._info.update({'specs': utils.format_dict(qos_specs.specs)}) + qos_spec._info.update({'specs': utils.format_dict(qos_spec.specs)}) - return zip(*sorted(six.iteritems(qos_specs._info))) + return zip(*sorted(six.iteritems(qos_spec._info))) class UnsetQos(command.Command): @@ -274,8 +273,8 @@ class UnsetQos(command.Command): def get_parser(self, prog_name): parser = super(UnsetQos, self).get_parser(prog_name) parser.add_argument( - 'qos_specs', - metavar='', + 'qos_spec', + metavar='', help='QoS specification to modify (name or ID)', ) parser.add_argument( @@ -291,11 +290,11 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume - qos_specs = utils.find_resource(volume_client.qos_specs, - parsed_args.qos_specs) + qos_spec = utils.find_resource(volume_client.qos_specs, + parsed_args.qos_spec) if parsed_args.property: - volume_client.qos_specs.unset_keys(qos_specs.id, + volume_client.qos_specs.unset_keys(qos_spec.id, parsed_args.property) else: self.app.log.error("No changes requested\n") From d82b1f9d7334c1cd976624788da785a87cd5db8a Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Mon, 6 Jul 2015 11:22:33 -0600 Subject: [PATCH 0165/3095] Add functional tests for volume qos Change-Id: I80010b56b399bc027ac864304be60a3ee53bda00 --- functional/tests/volume/v1/test_qos.py | 54 ++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 functional/tests/volume/v1/test_qos.py diff --git a/functional/tests/volume/v1/test_qos.py b/functional/tests/volume/v1/test_qos.py new file mode 100644 index 0000000000..122a4538ce --- /dev/null +++ b/functional/tests/volume/v1/test_qos.py @@ -0,0 +1,54 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import uuid + +from functional.common import test + + +class VolumeQosTests(test.TestCase): + """Functional tests for volume qos. """ + + NAME = uuid.uuid4().hex + HEADERS = ['Name'] + FIELDS = ['id', 'name'] + ID = None + + @classmethod + def setUpClass(cls): + opts = cls.get_show_opts(cls.FIELDS) + raw_output = cls.openstack('volume qos create ' + cls.NAME + opts) + cls.ID, name, rol = raw_output.split('\n') + cls.assertOutput(cls.NAME, name) + + @classmethod + def tearDownClass(cls): + raw_output = cls.openstack('volume qos delete ' + cls.ID) + cls.assertOutput('', raw_output) + + def test_volume_qos_list(self): + opts = self.get_list_opts(self.HEADERS) + raw_output = self.openstack('volume qos list' + opts) + self.assertIn(self.NAME, raw_output) + + def test_volume_qos_show(self): + opts = self.get_show_opts(self.FIELDS) + raw_output = self.openstack('volume qos show ' + self.ID + opts) + self.assertEqual(self.ID + "\n" + self.NAME + "\n", raw_output) + + def test_volume_qos_metadata(self): + raw_output = self.openstack( + 'volume qos set --property a=b --property c=d ' + self.ID) + self.assertEqual("", raw_output) + opts = self.get_show_opts(['name', 'specs']) + raw_output = self.openstack('volume qos show ' + self.ID + opts) + self.assertEqual(self.NAME + "\na='b', c='d'\n", raw_output) From 566987ecf1e437454173c4655d40b9f3ec19d678 Mon Sep 17 00:00:00 2001 From: Guojian Shao Date: Tue, 14 Jul 2015 21:41:07 +0800 Subject: [PATCH 0166/3095] add functional tests for identity v3 add functional tests for endpoint, region and service. Change-Id: I504878811dc8f9fcb2295cbf7419885959d9c1f6 Implements: blueprint identity-functional-tests --- functional/tests/identity/v3/test_catalog.py | 25 +++--- functional/tests/identity/v3/test_endpoint.py | 60 ++++++++++++++ functional/tests/identity/v3/test_identity.py | 78 +++++++++++++++++++ functional/tests/identity/v3/test_region.py | 63 +++++++++++++++ functional/tests/identity/v3/test_service.py | 64 +++++++++++++++ 5 files changed, 279 insertions(+), 11 deletions(-) create mode 100644 functional/tests/identity/v3/test_endpoint.py create mode 100644 functional/tests/identity/v3/test_region.py create mode 100644 functional/tests/identity/v3/test_service.py diff --git a/functional/tests/identity/v3/test_catalog.py b/functional/tests/identity/v3/test_catalog.py index ec4d035ba6..6836452954 100644 --- a/functional/tests/identity/v3/test_catalog.py +++ b/functional/tests/identity/v3/test_catalog.py @@ -24,17 +24,20 @@ def test_catalog_show(self): """test catalog show command The output example: - +-----------+-------------------------------------------+ - | Field | Value | - +-----------+-------------------------------------------+ - | endpoints | test1 | - | | publicURL: http://localhost:5000/v2.0 | - | | internalURL: http://localhost:5000/v2.0 | - | | adminURL: http://localhost:5000/v2.0 | - | | | - | name | keystone | - | type | identity | - +-----------+-------------------------------------------+ + +-----------+----------------------------------------+ + | Field | Value | + +-----------+----------------------------------------+ + | endpoints | test1 | + | | public: http://localhost:5000/v2.0 | + | | test1 | + | | internal: http://localhost:5000/v2.0 | + | | test1 | + | | admin: http://localhost:35357/v2.0 | + | | | + | id | e1e68b5ba21a43a39ff1cf58e736c3aa | + | name | keystone | + | type | identity | + +-----------+----------------------------------------+ """ raw_output = self.openstack('catalog show %s' % 'identity') items = self.parse_show(raw_output) diff --git a/functional/tests/identity/v3/test_endpoint.py b/functional/tests/identity/v3/test_endpoint.py new file mode 100644 index 0000000000..a7590787d9 --- /dev/null +++ b/functional/tests/identity/v3/test_endpoint.py @@ -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. + +from tempest_lib.common.utils import data_utils + +from functional.tests.identity.v3 import test_identity + + +class EndpointTests(test_identity.IdentityTests): + + def test_endpoint_create(self): + self._create_dummy_endpoint(interface='public') + self._create_dummy_endpoint(interface='admin') + self._create_dummy_endpoint(interface='internal') + + def test_endpoint_delete(self): + endpoint_id = self._create_dummy_endpoint(add_clean_up=False) + raw_output = self.openstack( + 'endpoint delete %s' % endpoint_id) + self.assertEqual(0, len(raw_output)) + + def test_endpoint_list(self): + endpoint_id = self._create_dummy_endpoint() + raw_output = self.openstack('endpoint list') + self.assertInOutput(endpoint_id, raw_output) + items = self.parse_listing(raw_output) + self.assert_table_structure(items, self.ENDPOINT_LIST_HEADERS) + + def test_endpoint_set(self): + endpoint_id = self._create_dummy_endpoint() + new_endpoint_url = data_utils.rand_url() + raw_output = self.openstack( + 'endpoint set ' + '--interface %(interface)s ' + '--url %(url)s ' + '--disable ' + '%(endpoint_id)s' % {'interface': 'admin', + 'url': new_endpoint_url, + 'endpoint_id': endpoint_id}) + self.assertEqual(0, len(raw_output)) + raw_output = self.openstack('endpoint show %s' % endpoint_id) + endpoint = self.parse_show_as_object(raw_output) + self.assertEqual('admin', endpoint['interface']) + self.assertEqual(new_endpoint_url, endpoint['url']) + self.assertEqual('False', endpoint['enabled']) + + def test_endpoint_show(self): + endpoint_id = self._create_dummy_endpoint() + raw_output = self.openstack('endpoint show %s' % endpoint_id) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.ENDPOINT_FIELDS) diff --git a/functional/tests/identity/v3/test_identity.py b/functional/tests/identity/v3/test_identity.py index cf434559f9..d7e72b02a7 100644 --- a/functional/tests/identity/v3/test_identity.py +++ b/functional/tests/identity/v3/test_identity.py @@ -31,6 +31,16 @@ class IdentityTests(test.TestCase): PROJECT_FIELDS = ['description', 'id', 'domain_id', 'enabled', 'name', 'parent_id', 'links'] ROLE_FIELDS = ['id', 'name', 'links'] + SERVICE_FIELDS = ['id', 'enabled', 'name', 'type', 'description'] + REGION_FIELDS = ['description', 'enabled', 'parent_region', + 'region', 'url'] + ENDPOINT_FIELDS = ['id', 'region', 'region_id', 'service_id', + 'service_name', 'service_type', 'enabled', + 'interface', 'url'] + + REGION_LIST_HEADERS = ['Region', 'Parent Region', 'Description', 'URL'] + ENDPOINT_LIST_HEADERS = ['ID', 'Region', 'Service Name', 'Service Type', + 'Enabled', 'Interface', 'URL'] @classmethod def setUpClass(cls): @@ -68,7 +78,9 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): + # delete dummy project cls.openstack('project delete %s' % cls.project_name) + # disable and delete dummy domain cls.openstack('domain set --disable %s' % cls.domain_name) cls.openstack('domain delete %s' % cls.domain_name) @@ -173,3 +185,69 @@ def _create_dummy_project(self, add_clean_up=True): '%(name)s' % {'domain': self.domain_name, 'name': project_name}) return project_name + + def _create_dummy_region(self, parent_region=None, add_clean_up=True): + region_id = data_utils.rand_name('TestRegion') + description = data_utils.rand_name('description') + url = data_utils.rand_url() + parent_region_arg = '' + if parent_region is not None: + parent_region_arg = '--parent-region %s' % parent_region + raw_output = self.openstack( + 'region create ' + '%(parent_region_arg)s ' + '--description %(description)s ' + '--url %(url)s ' + '%(id)s' % {'parent_region_arg': parent_region_arg, + 'description': description, + 'url': url, + 'id': region_id}) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.REGION_FIELDS) + if add_clean_up: + self.addCleanup(self.openstack, + 'region delete %s' % region_id) + return region_id + + def _create_dummy_service(self, add_clean_up=True): + service_name = data_utils.rand_name('TestService') + description = data_utils.rand_name('description') + type_name = data_utils.rand_name('TestType') + raw_output = self.openstack( + 'service create ' + '--name %(name)s ' + '--description %(description)s ' + '--enable ' + '%(type)s' % {'name': service_name, + 'description': description, + 'type': type_name}) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.SERVICE_FIELDS) + if add_clean_up: + service = self.parse_show_as_object(raw_output) + self.addCleanup(self.openstack, + 'service delete %s' % service['id']) + return service_name + + def _create_dummy_endpoint(self, interface='public', add_clean_up=True): + region_id = self._create_dummy_region() + service_name = self._create_dummy_service() + endpoint_url = data_utils.rand_url() + raw_output = self.openstack( + 'endpoint create ' + '--region %(region)s ' + '--enable ' + '%(service)s ' + '%(interface)s ' + '%(url)s' % {'region': region_id, + 'service': service_name, + 'interface': interface, + 'url': endpoint_url}) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.ENDPOINT_FIELDS) + endpoint = self.parse_show_as_object(raw_output) + if add_clean_up: + self.addCleanup( + self.openstack, + 'endpoint delete %s' % endpoint['id']) + return endpoint['id'] diff --git a/functional/tests/identity/v3/test_region.py b/functional/tests/identity/v3/test_region.py new file mode 100644 index 0000000000..be6ef1cb68 --- /dev/null +++ b/functional/tests/identity/v3/test_region.py @@ -0,0 +1,63 @@ +# 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 functional.tests.identity.v3 import test_identity + + +class RegionTests(test_identity.IdentityTests): + + def test_region_create(self): + self._create_dummy_region() + + def test_region_create_with_parent_region(self): + parent_region_id = self._create_dummy_region() + self._create_dummy_region(parent_region=parent_region_id) + + def test_region_delete(self): + region_id = self._create_dummy_region(add_clean_up=False) + raw_output = self.openstack('region delete %s' % region_id) + self.assertEqual(0, len(raw_output)) + + def test_region_list(self): + raw_output = self.openstack('region list') + items = self.parse_listing(raw_output) + self.assert_table_structure(items, self.REGION_LIST_HEADERS) + + def test_region_set(self): + # prepare region with parent-region + parent_region_id = self._create_dummy_region() + new_parent_region_id = self._create_dummy_region() + region_id = self._create_dummy_region(parent_region_id) + # check region details + raw_output = self.openstack('region show %s' % region_id) + region = self.parse_show_as_object(raw_output) + self.assertEqual(parent_region_id, region['parent_region']) + self.assertEqual(region_id, region['region']) + # update parent-region + raw_output = self.openstack( + 'region set ' + '--parent-region %(parent_region)s ' + '%(region)s' % {'parent_region': new_parent_region_id, + 'region': region_id}) + self.assertEqual(0, len(raw_output)) + # check updated region details + raw_output = self.openstack('region show %s' % region_id) + region = self.parse_show_as_object(raw_output) + self.assertEqual(new_parent_region_id, region['parent_region']) + self.assertEqual(region_id, region['region']) + + def test_region_show(self): + region_id = self._create_dummy_region() + raw_output = self.openstack('region show %s' % region_id) + region = self.parse_show_as_object(raw_output) + self.assertEqual(region_id, region['region']) + self.assertEqual('None', region['parent_region']) diff --git a/functional/tests/identity/v3/test_service.py b/functional/tests/identity/v3/test_service.py new file mode 100644 index 0000000000..147208a2bc --- /dev/null +++ b/functional/tests/identity/v3/test_service.py @@ -0,0 +1,64 @@ +# 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 tempest_lib.common.utils import data_utils + +from functional.tests.identity.v3 import test_identity + + +class ServiceTests(test_identity.IdentityTests): + + def test_service_create(self): + self._create_dummy_service() + + def test_service_delete(self): + service_name = self._create_dummy_service(add_clean_up=False) + raw_output = self.openstack('service delete %s' % service_name) + self.assertEqual(0, len(raw_output)) + + def test_service_list(self): + self._create_dummy_service() + raw_output = self.openstack('service list') + items = self.parse_listing(raw_output) + self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS) + + def test_service_set(self): + service_name = self._create_dummy_service() + # set service + new_service_name = data_utils.rand_name('NewTestService') + new_service_description = data_utils.rand_name('description') + new_service_type = data_utils.rand_name('NewTestType') + raw_output = self.openstack( + 'service set ' + '--type %(type)s ' + '--name %(name)s ' + '--description %(description)s ' + '--disable ' + '%(service)s' % {'type': new_service_type, + 'name': new_service_name, + 'description': new_service_description, + 'service': service_name}) + self.assertEqual(0, len(raw_output)) + # get service details + raw_output = self.openstack('service show %s' % new_service_name) + # assert service details + service = self.parse_show_as_object(raw_output) + self.assertEqual(new_service_type, service['type']) + self.assertEqual(new_service_name, service['name']) + self.assertEqual(new_service_description, service['description']) + + def test_service_show(self): + service_name = self._create_dummy_service() + raw_output = self.openstack( + 'service show %s' % service_name) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.SERVICE_FIELDS) From bc28adc62d463e5ba7e3fb9dad4f0b53898beb9a Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Fri, 17 Jul 2015 11:21:02 -0600 Subject: [PATCH 0167/3095] Remove requirements.txt from tox.ini From lifeless: pbr reflects the package dependencies from requirements.txt into the sdist that tox builds. Change-Id: I63548ec321b9c59cc935ba8179b1da5b9c90e09b --- tox.ini | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 34b881f80e..23104b820f 100644 --- a/tox.ini +++ b/tox.ini @@ -7,8 +7,7 @@ skipdist = True usedevelop = True install_command = pip install -U {opts} {packages} setenv = VIRTUAL_ENV={envdir} -deps = -r{toxinidir}/requirements.txt - -r{toxinidir}/test-requirements.txt +deps = -r{toxinidir}/test-requirements.txt commands = ostestr {posargs} whitelist_externals = ostestr From e7e8760fa6d8af1217bca55efef4bfa2c4eba396 Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Wed, 8 Jul 2015 09:49:39 -0600 Subject: [PATCH 0168/3095] Remove unnecessary test extension mock The network extension test does not need this. Change-Id: I8dd2cad759a813d7df0476afa6c56f455ddff616 --- openstackclient/tests/common/test_extension.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/openstackclient/tests/common/test_extension.py b/openstackclient/tests/common/test_extension.py index 8327a414b3..6d34bdd8d8 100644 --- a/openstackclient/tests/common/test_extension.py +++ b/openstackclient/tests/common/test_extension.py @@ -52,13 +52,6 @@ def setUp(self): loaded=True, ), ] - self.network_extensions_mock.list.return_value = [ - fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.EXTENSION), - loaded=True, - ), - ] # Get the command object to test self.cmd = extension.ListExtension(self.app, None) From ff3dbddf599e1153f0961e112edbf0d35d65cf89 Mon Sep 17 00:00:00 2001 From: Guojian Shao Date: Fri, 17 Jul 2015 00:32:22 +0800 Subject: [PATCH 0169/3095] enhance tests for user v3 Change-Id: Ib17ba0cd71068ba8d7e7665160daf3ae0735971b Related-Bug: #1475357 --- functional/tests/identity/v3/test_identity.py | 2 + functional/tests/identity/v3/test_user.py | 40 +++++++++++++++++-- .../tests/identity/v3/test_user.py | 33 +++++++++++++++ 3 files changed, 72 insertions(+), 3 deletions(-) diff --git a/functional/tests/identity/v3/test_identity.py b/functional/tests/identity/v3/test_identity.py index d7e72b02a7..cd11e200af 100644 --- a/functional/tests/identity/v3/test_identity.py +++ b/functional/tests/identity/v3/test_identity.py @@ -96,12 +96,14 @@ def _create_dummy_user(self, add_clean_up=True): 'user create ' '--domain %(domain)s ' '--project %(project)s ' + '--project-domain %(project_domain)s ' '--password %(password)s ' '--email %(email)s ' '--description %(description)s ' '--enable ' '%(name)s' % {'domain': self.domain_name, 'project': self.project_name, + 'project_domain': self.domain_name, 'email': email, 'password': password, 'description': description, diff --git a/functional/tests/identity/v3/test_user.py b/functional/tests/identity/v3/test_user.py index 69420b96f2..00b9bdc2fc 100644 --- a/functional/tests/identity/v3/test_user.py +++ b/functional/tests/identity/v3/test_user.py @@ -53,9 +53,43 @@ def test_user_set(self): '--domain %(domain)s ' '%(name)s' % {'domain': self.domain_name, 'name': new_username}) - new_user = self.parse_show_as_object(raw_output) - self.assertEqual(user['id'], new_user['id']) - self.assertEqual(new_email, new_user['email']) + updated_user = self.parse_show_as_object(raw_output) + self.assertEqual(user['id'], updated_user['id']) + self.assertEqual(new_email, updated_user['email']) + + def test_user_set_default_project_id(self): + username = self._create_dummy_user() + project_name = self._create_dummy_project() + # get original user details + raw_output = self.openstack('user show ' + '--domain %(domain)s ' + '%(name)s' % {'domain': self.domain_name, + 'name': username}) + user = self.parse_show_as_object(raw_output) + # update user + raw_output = self.openstack('user set ' + '--project %(project)s ' + '--project-domain %(project_domain)s ' + '%(id)s' % {'project': project_name, + 'project_domain': + self.domain_name, + 'id': user['id']}) + self.assertEqual(0, len(raw_output)) + # get updated user details + raw_output = self.openstack('user show ' + '--domain %(domain)s ' + '%(name)s' % {'domain': self.domain_name, + 'name': username}) + updated_user = self.parse_show_as_object(raw_output) + # get project details + raw_output = self.openstack('project show ' + '--domain %(domain)s ' + '%(name)s' % {'domain': self.domain_name, + 'name': project_name}) + project = self.parse_show_as_object(raw_output) + # check updated user details + self.assertEqual(user['id'], updated_user['id']) + self.assertEqual(project['id'], updated_user['default_project_id']) def test_user_show(self): username = self._create_dummy_user() diff --git a/openstackclient/tests/identity/v3/test_user.py b/openstackclient/tests/identity/v3/test_user.py index bdde25a2ce..76d5f83487 100644 --- a/openstackclient/tests/identity/v3/test_user.py +++ b/openstackclient/tests/identity/v3/test_user.py @@ -956,6 +956,39 @@ def test_user_set_project(self): **kwargs ) + def test_user_set_project_domain(self): + arglist = [ + '--project', identity_fakes.project_id, + '--project-domain', identity_fakes.domain_id, + identity_fakes.user_name, + ] + verifylist = [ + ('name', None), + ('password', None), + ('email', None), + ('project', identity_fakes.project_id), + ('project_domain', identity_fakes.domain_id), + ('enable', False), + ('disable', False), + ('user', identity_fakes.user_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'enabled': True, + 'default_project': identity_fakes.project_id, + } + # UserManager.update(user, name=, domain=, project=, password=, + # email=, description=, enabled=, default_project=) + self.users_mock.update.assert_called_with( + identity_fakes.user_id, + **kwargs + ) + def test_user_set_enable(self): arglist = [ '--enable', From ab2c1f2b820d7fc04bf2979209198adf3417403f Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sat, 18 Jul 2015 01:57:02 +0000 Subject: [PATCH 0170/3095] Updated from global requirements Change-Id: Id60bd6f5114276ca2267e71de34afa18d5c0a78f --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 24ba0f434a..f9c257680e 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -6,7 +6,7 @@ hacking<0.11,>=0.10.0 coverage>=3.6 discover fixtures>=1.3.1 -mock>=1.1;python_version!='2.6' +mock!=1.1.4,>=1.1;python_version!='2.6' mock==1.0.1;python_version=='2.6' oslosphinx>=2.5.0 # Apache-2.0 oslotest>=1.7.0 # Apache-2.0 From d79900a075a78da0cc8a504630fd5a2df41c4ae2 Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Wed, 10 Jun 2015 15:03:15 -0600 Subject: [PATCH 0171/3095] Add functional tests for servers that require wait Change-Id: I4c85b1e303ecb99458594e7743950b0668b3bdfc --- functional/tests/compute/v2/test_server.py | 69 +++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/functional/tests/compute/v2/test_server.py b/functional/tests/compute/v2/test_server.py index bcef635f6f..2bf01341e7 100644 --- a/functional/tests/compute/v2/test_server.py +++ b/functional/tests/compute/v2/test_server.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import time import uuid from functional.common import test @@ -19,6 +20,7 @@ class ServerTests(test.TestCase): """Functional tests for server. """ NAME = uuid.uuid4().hex + OTHER_NAME = uuid.uuid4().hex HEADERS = ['"Name"'] FIELDS = ['name'] @@ -41,7 +43,12 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - raw_output = cls.openstack('server delete ' + cls.NAME) + # Rename test + raw_output = cls.openstack('server set --name ' + cls.OTHER_NAME + + ' ' + cls.NAME) + cls.assertOutput("", raw_output) + # Delete test + raw_output = cls.openstack('server delete ' + cls.OTHER_NAME) cls.assertOutput('', raw_output) def test_server_list(self): @@ -53,3 +60,63 @@ def test_server_show(self): opts = self.get_show_opts(self.FIELDS) raw_output = self.openstack('server show ' + self.NAME + opts) self.assertEqual(self.NAME + "\n", raw_output) + + def wait_for(self, desired, wait=120, interval=5, failures=['ERROR']): + # TODO(thowe): Add a server wait command to osc + status = "notset" + wait = 120 + interval = 5 + total_sleep = 0 + opts = self.get_show_opts(['status']) + while total_sleep < wait: + status = self.openstack('server show ' + self.NAME + opts) + status = status.rstrip() + print('Waiting for {} current status: {}'.format(desired, status)) + if status == desired: + break + self.assertNotIn(status, failures) + time.sleep(interval) + total_sleep += interval + self.assertEqual(desired, status) + + def test_server_up_test(self): + self.wait_for("ACTIVE") + # give it a little bit more time + time.sleep(5) + # metadata + raw_output = self.openstack( + 'server set --property a=b --property c=d ' + self.NAME) + opts = self.get_show_opts(["name", "properties"]) + raw_output = self.openstack('server show ' + self.NAME + opts) + self.assertEqual(self.NAME + "\na='b', c='d'\n", raw_output) + # suspend + raw_output = self.openstack('server suspend ' + self.NAME) + self.assertEqual("", raw_output) + self.wait_for("SUSPENDED") + # resume + raw_output = self.openstack('server resume ' + self.NAME) + self.assertEqual("", raw_output) + self.wait_for("ACTIVE") + # lock + raw_output = self.openstack('server lock ' + self.NAME) + self.assertEqual("", raw_output) + # unlock + raw_output = self.openstack('server unlock ' + self.NAME) + self.assertEqual("", raw_output) + # pause + raw_output = self.openstack('server pause ' + self.NAME) + self.assertEqual("", raw_output) + self.wait_for("PAUSED") + # unpause + raw_output = self.openstack('server unpause ' + self.NAME) + self.assertEqual("", raw_output) + self.wait_for("ACTIVE") + # rescue + opts = self.get_show_opts(["adminPass"]) + raw_output = self.openstack('server rescue ' + self.NAME + opts) + self.assertNotEqual("", raw_output) + self.wait_for("RESCUE") + # unrescue + raw_output = self.openstack('server unrescue ' + self.NAME) + self.assertEqual("", raw_output) + self.wait_for("ACTIVE") From b3335b3474e559d8afb0b7f86796fbb68a0687a8 Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Fri, 8 May 2015 13:14:15 -0600 Subject: [PATCH 0172/3095] Do not set default versions in parsed args Setting default versions in parsed args makes it so OCC cannot tell if the argument was parsed, an environment variable was set or it is just defaulted. In order to set api versions from OCC, it will have to be defaulted after processing OCC. Closes-Bug: #1453229 Change-Id: I4d065919397b783f3bdd4022c986c0234a7a16e6 --- doc/source/backwards-incompatible.rst | 15 +++++++++++++++ doc/source/plugins.rst | 7 ++----- openstackclient/compute/client.py | 8 +++----- openstackclient/identity/client.py | 8 +++----- openstackclient/image/client.py | 8 +++----- openstackclient/network/client.py | 8 +++----- openstackclient/object/client.py | 8 +++----- openstackclient/shell.py | 4 +++- openstackclient/tests/test_shell.py | 26 ++++++++++++++------------ openstackclient/volume/client.py | 8 +++----- 10 files changed, 52 insertions(+), 48 deletions(-) diff --git a/doc/source/backwards-incompatible.rst b/doc/source/backwards-incompatible.rst index c511852f39..e89cc3a680 100644 --- a/doc/source/backwards-incompatible.rst +++ b/doc/source/backwards-incompatible.rst @@ -75,6 +75,21 @@ List of Backwards Incompatible Changes * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/472613 * Commit: https://review.openstack.org/#/c/194654/ +6. Plugin interface change for default API versions + + Previously, the default version was set in the parsed arguments, + but this makes it impossible to tell what has been passed in at the + command line, set in an environment variable or is just the default. + Now, the module should have a DEFAULT_API_VERSION that contains the + value and it will be set after command line argument, environment + and OCC file processing. + + * In favor of: DEFAULT_API_VERSION + * As of: 1.2.1 + * Removed in: NA + * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1453229 + * Commit: https://review.openstack.org/#/c/181514/ + For Developers ============== diff --git a/doc/source/plugins.rst b/doc/source/plugins.rst index d603e10217..17a508fed7 100644 --- a/doc/source/plugins.rst +++ b/doc/source/plugins.rst @@ -83,7 +83,7 @@ so the version should not contain the leading 'v' character. .. code-block:: python - DEFAULT_OSCPLUGIN_API_VERSION = '1' + DEFAULT_API_VERSION = '1' # Required by the OSC plugin interface API_NAME = 'oscplugin' @@ -123,10 +123,7 @@ so the version should not contain the leading 'v' character. parser.add_argument( '--os-oscplugin-api-version', metavar='', - default=utils.env( - 'OS_OSCPLUGIN_API_VERSION', - default=DEFAULT_OSCPLUGIN_API_VERSION), help='OSC Plugin API version, default=' + - DEFAULT_OSCPLUGIN_API_VERSION + + DEFAULT_API_VERSION + ' (Env: OS_OSCPLUGIN_API_VERSION)') return parser diff --git a/openstackclient/compute/client.py b/openstackclient/compute/client.py index 6ae87b7982..9dda32d660 100644 --- a/openstackclient/compute/client.py +++ b/openstackclient/compute/client.py @@ -19,7 +19,7 @@ LOG = logging.getLogger(__name__) -DEFAULT_COMPUTE_API_VERSION = '2' +DEFAULT_API_VERSION = '2' API_VERSION_OPTION = 'os_compute_api_version' API_NAME = 'compute' API_VERSIONS = { @@ -68,10 +68,8 @@ def build_option_parser(parser): parser.add_argument( '--os-compute-api-version', metavar='', - default=utils.env( - 'OS_COMPUTE_API_VERSION', - default=DEFAULT_COMPUTE_API_VERSION), + default=utils.env('OS_COMPUTE_API_VERSION'), help='Compute API version, default=' + - DEFAULT_COMPUTE_API_VERSION + + DEFAULT_API_VERSION + ' (Env: OS_COMPUTE_API_VERSION)') return parser diff --git a/openstackclient/identity/client.py b/openstackclient/identity/client.py index d7b663ddaf..b8bb33f43f 100644 --- a/openstackclient/identity/client.py +++ b/openstackclient/identity/client.py @@ -21,7 +21,7 @@ LOG = logging.getLogger(__name__) -DEFAULT_IDENTITY_API_VERSION = '2' +DEFAULT_API_VERSION = '2' API_VERSION_OPTION = 'os_identity_api_version' API_NAME = 'identity' API_VERSIONS = { @@ -63,11 +63,9 @@ def build_option_parser(parser): parser.add_argument( '--os-identity-api-version', metavar='', - default=utils.env( - 'OS_IDENTITY_API_VERSION', - default=DEFAULT_IDENTITY_API_VERSION), + default=utils.env('OS_IDENTITY_API_VERSION'), help='Identity API version, default=' + - DEFAULT_IDENTITY_API_VERSION + + DEFAULT_API_VERSION + ' (Env: OS_IDENTITY_API_VERSION)') return auth.build_auth_plugins_option_parser(parser) diff --git a/openstackclient/image/client.py b/openstackclient/image/client.py index 8fbf8c0f25..8dd146e9f2 100644 --- a/openstackclient/image/client.py +++ b/openstackclient/image/client.py @@ -20,7 +20,7 @@ LOG = logging.getLogger(__name__) -DEFAULT_IMAGE_API_VERSION = '1' +DEFAULT_API_VERSION = '1' API_VERSION_OPTION = 'os_image_api_version' API_NAME = "image" API_VERSIONS = { @@ -81,10 +81,8 @@ def build_option_parser(parser): parser.add_argument( '--os-image-api-version', metavar='', - default=utils.env( - 'OS_IMAGE_API_VERSION', - default=DEFAULT_IMAGE_API_VERSION), + default=utils.env('OS_IMAGE_API_VERSION'), help='Image API version, default=' + - DEFAULT_IMAGE_API_VERSION + + DEFAULT_API_VERSION + ' (Env: OS_IMAGE_API_VERSION)') return parser diff --git a/openstackclient/network/client.py b/openstackclient/network/client.py index 0ef6885287..5f72782bb1 100644 --- a/openstackclient/network/client.py +++ b/openstackclient/network/client.py @@ -18,7 +18,7 @@ LOG = logging.getLogger(__name__) -DEFAULT_NETWORK_API_VERSION = '2' +DEFAULT_API_VERSION = '2' API_VERSION_OPTION = 'os_network_api_version' API_NAME = "network" API_VERSIONS = { @@ -83,10 +83,8 @@ def build_option_parser(parser): parser.add_argument( '--os-network-api-version', metavar='', - default=utils.env( - 'OS_NETWORK_API_VERSION', - default=DEFAULT_NETWORK_API_VERSION), + default=utils.env('OS_NETWORK_API_VERSION'), help='Network API version, default=' + - DEFAULT_NETWORK_API_VERSION + + DEFAULT_API_VERSION + ' (Env: OS_NETWORK_API_VERSION)') return parser diff --git a/openstackclient/object/client.py b/openstackclient/object/client.py index 0359940d82..e75878026f 100644 --- a/openstackclient/object/client.py +++ b/openstackclient/object/client.py @@ -22,7 +22,7 @@ LOG = logging.getLogger(__name__) -DEFAULT_OBJECT_API_VERSION = '1' +DEFAULT_API_VERSION = '1' API_VERSION_OPTION = 'os_object_api_version' API_NAME = 'object_store' API_VERSIONS = { @@ -52,10 +52,8 @@ def build_option_parser(parser): parser.add_argument( '--os-object-api-version', metavar='', - default=utils.env( - 'OS_OBJECT_API_VERSION', - default=DEFAULT_OBJECT_API_VERSION), + default=utils.env('OS_OBJECT_API_VERSION'), help='Object API version, default=' + - DEFAULT_OBJECT_API_VERSION + + DEFAULT_API_VERSION + ' (Env: OS_OBJECT_API_VERSION)') return parser diff --git a/openstackclient/shell.py b/openstackclient/shell.py index edeffdfb54..55b93b7a46 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -297,7 +297,9 @@ def initialize_app(self, argv): # Loop through extensions to get API versions for mod in clientmanager.PLUGIN_MODULES: - version_opt = getattr(self.options, mod.API_VERSION_OPTION, None) + default_version = getattr(mod, 'DEFAULT_API_VERSION', None) + option = mod.API_VERSION_OPTION.replace('os_', '') + version_opt = self.cloud.config.get(option, default_version) if version_opt: api = mod.API_NAME self.api_version[api] = version_opt diff --git a/openstackclient/tests/test_shell.py b/openstackclient/tests/test_shell.py index e2f0580b2c..0e0cc50b54 100644 --- a/openstackclient/tests/test_shell.py +++ b/openstackclient/tests/test_shell.py @@ -41,17 +41,17 @@ DEFAULT_AUTH_PLUGIN = "v2password" DEFAULT_INTERFACE = "internal" -DEFAULT_COMPUTE_API_VERSION = "2" -DEFAULT_IDENTITY_API_VERSION = "2" -DEFAULT_IMAGE_API_VERSION = "2" -DEFAULT_VOLUME_API_VERSION = "1" -DEFAULT_NETWORK_API_VERSION = "2" - -LIB_COMPUTE_API_VERSION = "2" -LIB_IDENTITY_API_VERSION = "2" -LIB_IMAGE_API_VERSION = "1" -LIB_VOLUME_API_VERSION = "1" -LIB_NETWORK_API_VERSION = "2" +DEFAULT_COMPUTE_API_VERSION = "" +DEFAULT_IDENTITY_API_VERSION = "" +DEFAULT_IMAGE_API_VERSION = "" +DEFAULT_VOLUME_API_VERSION = "" +DEFAULT_NETWORK_API_VERSION = "" + +LIB_COMPUTE_API_VERSION = "" +LIB_IDENTITY_API_VERSION = "" +LIB_IMAGE_API_VERSION = "" +LIB_VOLUME_API_VERSION = "" +LIB_NETWORK_API_VERSION = "" CLOUD_1 = { 'clouds': { @@ -203,7 +203,9 @@ def _assert_cloud_config_arg(self, cmd_options, default_args): initialize_app(). """ - self.occ_get_one = mock.Mock("Test Shell") + cloud = mock.Mock(name="cloudy") + cloud.config = {} + self.occ_get_one = mock.Mock(return_value=cloud) with mock.patch( "os_client_config.config.OpenStackConfig.get_one_cloud", self.occ_get_one, diff --git a/openstackclient/volume/client.py b/openstackclient/volume/client.py index 093178e305..d4800b8d5c 100644 --- a/openstackclient/volume/client.py +++ b/openstackclient/volume/client.py @@ -19,7 +19,7 @@ LOG = logging.getLogger(__name__) -DEFAULT_VOLUME_API_VERSION = '1' +DEFAULT_API_VERSION = '1' API_VERSION_OPTION = 'os_volume_api_version' API_NAME = "volume" API_VERSIONS = { @@ -72,10 +72,8 @@ def build_option_parser(parser): parser.add_argument( '--os-volume-api-version', metavar='', - default=utils.env( - 'OS_VOLUME_API_VERSION', - default=DEFAULT_VOLUME_API_VERSION), + default=utils.env('OS_VOLUME_API_VERSION'), help='Volume API version, default=' + - DEFAULT_VOLUME_API_VERSION + + DEFAULT_API_VERSION + ' (Env: OS_VOLUME_API_VERSION)') return parser From 2fc800aace36211f576fd447375854218c29cfd1 Mon Sep 17 00:00:00 2001 From: jiaxi Date: Sat, 18 Jul 2015 03:33:03 -0400 Subject: [PATCH 0173/3095] Making --property as required when openstack flavor unset When using openstack flavor unset, the --property is needed, so the --property should be a required parameter. Close-Bug: #1474237 Change-Id: Id6913e1b263eddeb5dd44c3edb957206b68f816c --- openstackclient/compute/v2/flavor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index eb18a43332..3458cf7969 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -295,6 +295,7 @@ def get_parser(self, prog_name): action='append', help='Property to remove from flavor ' '(repeat option to unset multiple properties)', + required=True, ) parser.add_argument( "flavor", From 8bae13835495e078be13b91ecc0d688037e91572 Mon Sep 17 00:00:00 2001 From: heha Date: Sat, 18 Jul 2015 18:19:01 +0800 Subject: [PATCH 0174/3095] Add details to the documentation. Add "or-show" to "project create" and "role create" in the documentation. Closes-Bug: #1475485 Change-Id: I2d98766c4ddd8b2022012c4f6c5e1d4fcebfc42a --- doc/source/command-objects/project.rst | 7 +++++++ doc/source/command-objects/role.rst | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/doc/source/command-objects/project.rst b/doc/source/command-objects/project.rst index 637a44986f..54c69a6ca6 100644 --- a/doc/source/command-objects/project.rst +++ b/doc/source/command-objects/project.rst @@ -18,6 +18,7 @@ Create new project [--description ] [--enable | --disable] [--property ] + [--or-show] .. option:: --domain @@ -49,6 +50,12 @@ Create new project Add a property to :ref:`\ ` (repeat option to set multiple properties) +.. option:: --or-show + + Return existing project + + If the project already exist return the existing project data and do not fail. + .. _project_create-name: .. describe:: diff --git a/doc/source/command-objects/role.rst b/doc/source/command-objects/role.rst index dad5642dc0..5020a91c53 100644 --- a/doc/source/command-objects/role.rst +++ b/doc/source/command-objects/role.rst @@ -71,8 +71,15 @@ Create new role .. code:: bash os role create + [--or-show] +.. option:: --or-show + + Return existing role + + If the role already exist return the existing role data and do not fail. + .. describe:: New role name From a9f85736eff6ef83289aca072511c2aca0938617 Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Fri, 12 Jun 2015 10:03:44 -0600 Subject: [PATCH 0175/3095] Add functional tests server IP attach and detach Change-Id: Id87a5474e2df80c9ef84a6c554f12116302a38b3 --- functional/tests/compute/v2/test_server.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/functional/tests/compute/v2/test_server.py b/functional/tests/compute/v2/test_server.py index 2bf01341e7..dd2d923b19 100644 --- a/functional/tests/compute/v2/test_server.py +++ b/functional/tests/compute/v2/test_server.py @@ -23,6 +23,7 @@ class ServerTests(test.TestCase): OTHER_NAME = uuid.uuid4().hex HEADERS = ['"Name"'] FIELDS = ['name'] + IP_POOL = 'public' @classmethod def setUpClass(cls): @@ -120,3 +121,23 @@ def test_server_up_test(self): raw_output = self.openstack('server unrescue ' + self.NAME) self.assertEqual("", raw_output) self.wait_for("ACTIVE") + # attach ip + opts = self.get_show_opts(["id", "ip"]) + raw_output = self.openstack('ip floating create ' + self.IP_POOL + + opts) + ipid, ip, rol = tuple(raw_output.split('\n')) + self.assertNotEqual("", ipid) + self.assertNotEqual("", ip) + raw_output = self.openstack('ip floating add ' + ip + ' ' + self.NAME) + self.assertEqual("", raw_output) + opts = self.get_show_opts(["addresses"]) + raw_output = self.openstack('server show ' + self.NAME) + self.assertIn(ip, raw_output) + # detach ip + raw_output = self.openstack('ip floating remove ' + ip + ' ' + + self.NAME) + self.assertEqual("", raw_output) + raw_output = self.openstack('server show ' + self.NAME) + self.assertNotIn(ip, raw_output) + raw_output = self.openstack('ip floating delete ' + ipid) + self.assertEqual("", raw_output) From ea103cab1d632419a92940104209735c9d7f029f Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Fri, 12 Jun 2015 10:49:03 -0600 Subject: [PATCH 0176/3095] Add functional tests server reboot Change-Id: I1699ac826519ccc177159423c7b6727ffaf4abff --- functional/tests/compute/v2/test_server.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/functional/tests/compute/v2/test_server.py b/functional/tests/compute/v2/test_server.py index dd2d923b19..f38115800a 100644 --- a/functional/tests/compute/v2/test_server.py +++ b/functional/tests/compute/v2/test_server.py @@ -130,7 +130,6 @@ def test_server_up_test(self): self.assertNotEqual("", ip) raw_output = self.openstack('ip floating add ' + ip + ' ' + self.NAME) self.assertEqual("", raw_output) - opts = self.get_show_opts(["addresses"]) raw_output = self.openstack('server show ' + self.NAME) self.assertIn(ip, raw_output) # detach ip @@ -141,3 +140,7 @@ def test_server_up_test(self): self.assertNotIn(ip, raw_output) raw_output = self.openstack('ip floating delete ' + ipid) self.assertEqual("", raw_output) + # reboot + raw_output = self.openstack('server reboot ' + self.NAME) + self.assertEqual("", raw_output) + self.wait_for("ACTIVE") From 88c39ef48e2296590ed9dbf532f059ca06655b9e Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Sun, 19 Jul 2015 06:06:19 -0600 Subject: [PATCH 0177/3095] Minor identity documentation change Change-Id: Ib139f2376e70ebb7b5621cdad4da61c64c1f9246 --- doc/source/command-objects/project.rst | 2 +- doc/source/command-objects/role.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/command-objects/project.rst b/doc/source/command-objects/project.rst index 54c69a6ca6..a8a6e81230 100644 --- a/doc/source/command-objects/project.rst +++ b/doc/source/command-objects/project.rst @@ -54,7 +54,7 @@ Create new project Return existing project - If the project already exist return the existing project data and do not fail. + If the project already exists return the existing project data and do not fail. .. _project_create-name: .. describe:: diff --git a/doc/source/command-objects/role.rst b/doc/source/command-objects/role.rst index 5020a91c53..d26932b1db 100644 --- a/doc/source/command-objects/role.rst +++ b/doc/source/command-objects/role.rst @@ -78,7 +78,7 @@ Create new role Return existing role - If the role already exist return the existing role data and do not fail. + If the role already exists return the existing role data and do not fail. .. describe:: From 643d1d90fe31b1aceb986f668e1661f96203db57 Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Sun, 19 Jul 2015 07:03:57 -0600 Subject: [PATCH 0178/3095] More minor docs fixes Change-Id: Ia74b8e14bacb562d9bac29221f511acbab5296df --- openstackclient/common/utils.py | 6 +++--- openstackclient/object/v1/lib/__init__.py | 0 2 files changed, 3 insertions(+), 3 deletions(-) delete mode 100644 openstackclient/object/v1/lib/__init__.py diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index 6cd35c05d1..2f8419f42a 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -186,9 +186,9 @@ def sort_items(items, sort_str): :param items: a list or generator object of items :param sort_str: a string defining the sort rules, the format is - ':[direction1],:[direction2]...', direction can be 'asc' - for ascending or 'desc' for descending, if direction is not given, - it's ascending by default + ':[direction1],:[direction2]...', direction can be 'asc' + for ascending or 'desc' for descending, if direction is not given, + it's ascending by default :return: sorted items """ if not sort_str: diff --git a/openstackclient/object/v1/lib/__init__.py b/openstackclient/object/v1/lib/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 From 9c95b378323fdc37d403c3757064349c9aaa509b Mon Sep 17 00:00:00 2001 From: jiaxi Date: Sun, 19 Jul 2015 02:39:06 -0400 Subject: [PATCH 0179/3095] --property should be required in `os unset` commands The three commands below openstack volume unset openstack snapshot unset openstack volume type unset Should have --property as a required argument, not optional. The reason is the command will not work without --property. Closes-Bug: #1475872 Change-Id: Ib9e29392472db38982cc2817af2dd5055f5a01ca --- openstackclient/volume/v1/snapshot.py | 1 + openstackclient/volume/v1/type.py | 1 + openstackclient/volume/v1/volume.py | 1 + 3 files changed, 3 insertions(+) diff --git a/openstackclient/volume/v1/snapshot.py b/openstackclient/volume/v1/snapshot.py index 5ec2b3c5ca..e81efb5aaf 100644 --- a/openstackclient/volume/v1/snapshot.py +++ b/openstackclient/volume/v1/snapshot.py @@ -263,6 +263,7 @@ def get_parser(self, prog_name): default=[], help='Property to remove from snapshot ' '(repeat to remove multiple values)', + required=True, ) return parser diff --git a/openstackclient/volume/v1/type.py b/openstackclient/volume/v1/type.py index 46d1828b1e..fced73b9c5 100644 --- a/openstackclient/volume/v1/type.py +++ b/openstackclient/volume/v1/type.py @@ -166,6 +166,7 @@ def get_parser(self, prog_name): default=[], help='Property to remove from volume type ' '(repeat option to remove multiple properties)', + required=True, ) return parser diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index ad9671e35f..884611eccb 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -437,6 +437,7 @@ def get_parser(self, prog_name): default=[], help='Property to remove from volume ' '(repeat option to remove multiple properties)', + required=True, ) return parser From e30ebfeb17edaa154357bca222a92f0fbc8f4404 Mon Sep 17 00:00:00 2001 From: jiaxi Date: Sun, 19 Jul 2015 03:07:02 -0400 Subject: [PATCH 0180/3095] Add volume type show for volume v1 volume type show is missing for volume V1 API. Closes-Bug: #1475879 Change-Id: Ic8a0845ecec04146d536412463175f57ef6511ae --- openstackclient/volume/v1/type.py | 24 ++++++++++++++++++++++++ setup.cfg | 1 + 2 files changed, 25 insertions(+) diff --git a/openstackclient/volume/v1/type.py b/openstackclient/volume/v1/type.py index 46d1828b1e..87bf96533d 100644 --- a/openstackclient/volume/v1/type.py +++ b/openstackclient/volume/v1/type.py @@ -182,3 +182,27 @@ def take_action(self, parsed_args): else: self.app.log.error("No changes requested\n") return + + +class ShowVolumeType(show.ShowOne): + """Display volume type details""" + + log = logging.getLogger(__name__ + ".ShowVolumeType") + + def get_parser(self, prog_name): + parser = super(ShowVolumeType, self).get_parser(prog_name) + parser.add_argument( + "volume_type", + metavar="", + help="Volume type to display (name or ID)" + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action: (%s)", parsed_args) + volume_client = self.app.client_manager.volume + volume_type = utils.find_resource( + volume_client.volume_types, parsed_args.volume_type) + properties = utils.format_dict(volume_type._info.pop('extra_specs')) + volume_type._info.update({'properties': properties}) + return zip(*sorted(six.iteritems(volume_type._info))) diff --git a/setup.cfg b/setup.cfg index 496529c2d8..a7593f8bee 100644 --- a/setup.cfg +++ b/setup.cfg @@ -365,6 +365,7 @@ openstack.volume.v1 = volume_type_list = openstackclient.volume.v1.type:ListVolumeType volume_type_set = openstackclient.volume.v1.type:SetVolumeType volume_type_unset = openstackclient.volume.v1.type:UnsetVolumeType + volume_type_show = openstackclient.volume.v1.type:ShowVolumeType volume_qos_associate = openstackclient.volume.v1.qos_specs:AssociateQos volume_qos_create = openstackclient.volume.v1.qos_specs:CreateQos From 00b4e38d3c3acf01ca5d7ff3cae287b4f8c9df2a Mon Sep 17 00:00:00 2001 From: Gilles Dubreuil Date: Wed, 15 Jul 2015 17:19:30 +1000 Subject: [PATCH 0181/3095] Removes trailing blank in trust show Previously a blank character at the end of the roles value would remain Change-Id: I0961a5f9fb4b270a6055ee69898eadee315e416a Closes-Bug: 1474707 --- openstackclient/identity/v3/trust.py | 4 ++-- openstackclient/tests/identity/v3/test_trust.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openstackclient/identity/v3/trust.py b/openstackclient/identity/v3/trust.py index c8e5c4c729..5104864c79 100644 --- a/openstackclient/identity/v3/trust.py +++ b/openstackclient/identity/v3/trust.py @@ -144,7 +144,7 @@ def take_action(self, parsed_args): # Format roles into something sensible roles = trust._info.pop('roles') - msg = ''.join([r['name'] + ' ' for r in roles]) + msg = ' '.join(r['name'] for r in roles) trust._info['roles'] = msg return zip(*sorted(six.iteritems(trust._info))) @@ -215,7 +215,7 @@ def take_action(self, parsed_args): # Format roles into something sensible roles = trust._info.pop('roles') - msg = ''.join([r['name'] + ' ' for r in roles]) + msg = ' '.join(r['name'] for r in roles) trust._info['roles'] = msg return zip(*sorted(six.iteritems(trust._info))) diff --git a/openstackclient/tests/identity/v3/test_trust.py b/openstackclient/tests/identity/v3/test_trust.py index b3fbe7f000..b90e78159b 100644 --- a/openstackclient/tests/identity/v3/test_trust.py +++ b/openstackclient/tests/identity/v3/test_trust.py @@ -107,7 +107,7 @@ def test_trust_create_basic(self): identity_fakes.trust_id, identity_fakes.trust_impersonation, identity_fakes.project_id, - identity_fakes.role_name + ' ', + identity_fakes.role_name, identity_fakes.user_id, identity_fakes.user_id ) @@ -222,7 +222,7 @@ def test_trust_show(self): identity_fakes.trust_id, identity_fakes.trust_impersonation, identity_fakes.project_id, - identity_fakes.role_name + ' ', + identity_fakes.role_name, identity_fakes.user_id, identity_fakes.user_id ) From 11c9695e5ef127d0c99e2af610e462f5b1af4933 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Sun, 19 Jul 2015 08:54:50 -0700 Subject: [PATCH 0182/3095] Rename type.py to volume_type.py For Volume V1, we have a type.py file which should be renamed to volume_type.py (as it's named for V2). Change-Id: If860bbafe4a801d8b4fa06938eef20658c4fcc2c Closes-Bug: 1475958 --- .../volume/v1/{type.py => volume_type.py} | 0 setup.cfg | 12 ++++++------ 2 files changed, 6 insertions(+), 6 deletions(-) rename openstackclient/volume/v1/{type.py => volume_type.py} (100%) diff --git a/openstackclient/volume/v1/type.py b/openstackclient/volume/v1/volume_type.py similarity index 100% rename from openstackclient/volume/v1/type.py rename to openstackclient/volume/v1/volume_type.py diff --git a/setup.cfg b/setup.cfg index a7593f8bee..826605738b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -360,12 +360,12 @@ openstack.volume.v1 = volume_show = openstackclient.volume.v1.volume:ShowVolume volume_unset = openstackclient.volume.v1.volume:UnsetVolume - volume_type_create = openstackclient.volume.v1.type:CreateVolumeType - volume_type_delete = openstackclient.volume.v1.type:DeleteVolumeType - volume_type_list = openstackclient.volume.v1.type:ListVolumeType - volume_type_set = openstackclient.volume.v1.type:SetVolumeType - volume_type_unset = openstackclient.volume.v1.type:UnsetVolumeType - volume_type_show = openstackclient.volume.v1.type:ShowVolumeType + volume_type_create = openstackclient.volume.v1.volume_type:CreateVolumeType + volume_type_delete = openstackclient.volume.v1.volume_type:DeleteVolumeType + volume_type_list = openstackclient.volume.v1.volume_type:ListVolumeType + volume_type_set = openstackclient.volume.v1.volume_type:SetVolumeType + volume_type_show = openstackclient.volume.v1.volume_type:ShowVolumeType + volume_type_unset = openstackclient.volume.v1.volume_type:UnsetVolumeType volume_qos_associate = openstackclient.volume.v1.qos_specs:AssociateQos volume_qos_create = openstackclient.volume.v1.qos_specs:CreateQos From a6d5f3f94cb5d709c593c9638f44b3519222b4ca Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Sun, 19 Jul 2015 17:36:34 -0700 Subject: [PATCH 0183/3095] Format volume type properties when showing Currently, the properties of a volume type are unformatted. Use the formatter to keep things consistent with the way properties are represented in OSC. Change-Id: I81c6bd3fdbc30568c269e501c740473b2a1ffb4e --- openstackclient/tests/volume/v2/fakes.py | 11 +++++++++++ openstackclient/tests/volume/v2/test_type.py | 4 ++-- openstackclient/volume/v2/volume_type.py | 2 ++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py index 4f5f9cfdcb..c896ed6dd1 100644 --- a/openstackclient/tests/volume/v2/fakes.py +++ b/openstackclient/tests/volume/v2/fakes.py @@ -93,6 +93,17 @@ TYPE_columns = tuple(sorted(TYPE)) TYPE_data = tuple((TYPE[x] for x in sorted(TYPE))) +formatted_type_properties = "foo='bar'" +TYPE_FORMATTED = { + 'id': type_id, + 'name': type_name, + 'description': type_description, + 'properties': formatted_type_properties +} +TYPE_FORMATTED_columns = tuple(sorted(TYPE_FORMATTED)) +TYPE_FORMATTED_data = tuple((TYPE_FORMATTED[x] for x in + sorted(TYPE_FORMATTED))) + backup_id = "3c409fe6-4d03-4a06-aeab-18bdcdf3c8f4" backup_volume_id = "bdbae8dc-e6ca-43c0-8076-951cc1b093a4" backup_name = "fake_backup" diff --git a/openstackclient/tests/volume/v2/test_type.py b/openstackclient/tests/volume/v2/test_type.py index 6cc988b2dc..a6e91e8d69 100644 --- a/openstackclient/tests/volume/v2/test_type.py +++ b/openstackclient/tests/volume/v2/test_type.py @@ -51,8 +51,8 @@ def test_type_show(self): columns, data = self.cmd.take_action(parsed_args) self.types_mock.get.assert_called_with(volume_fakes.type_id) - self.assertEqual(volume_fakes.TYPE_columns, columns) - self.assertEqual(volume_fakes.TYPE_data, data) + self.assertEqual(volume_fakes.TYPE_FORMATTED_columns, columns) + self.assertEqual(volume_fakes.TYPE_FORMATTED_data, data) class TestTypeDelete(TestType): diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index ae5cc8b892..c724d8671c 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -65,4 +65,6 @@ def take_action(self, parsed_args): volume_client = self.app.client_manager.volume volume_type = utils.find_resource( volume_client.volume_types, parsed_args.volume_type) + properties = utils.format_dict(volume_type._info.pop('extra_specs')) + volume_type._info.update({'properties': properties}) return zip(*sorted(six.iteritems(volume_type._info))) From c5b43831606e0fd813e1f06b40950f8c85555953 Mon Sep 17 00:00:00 2001 From: Javier Pena Date: Mon, 6 Jul 2015 18:04:18 +0200 Subject: [PATCH 0184/3095] Fix the way auth_type default value is overriden Commit 50f05448982b5fafd9d9a7783b639dd145090a0d to os-client-config removed the default values in the _defaults dict. This makes any call to cloud_config.set_default() before initializing the dict fail. The fix changes the way the auth_type default is overriden, by doing it when cloud_config.OpenStackConfig() is executed. Change-Id: If37d3ba303f01d4c77fd7c15a3cde9634534b64a Closes-bug: #1473921 --- openstackclient/shell.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index edeffdfb54..319f10dec5 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -240,9 +240,9 @@ def initialize_app(self, argv): # Set the default plugin to token_endpoint if url and token are given if (self.options.url and self.options.token): # Use service token authentication - cloud_config.set_default('auth_type', 'token_endpoint') + auth_type = 'token_endpoint' else: - cloud_config.set_default('auth_type', 'osc_password') + auth_type = 'osc_password' self.log.debug("options: %s", self.options) project_id = getattr(self.options, 'project_id', None) @@ -266,7 +266,8 @@ def initialize_app(self, argv): # Ignore the default value of interface. Only if it is set later # will it be used. cc = cloud_config.OpenStackConfig( - override_defaults={'interface': None, }) + override_defaults={'interface': None, + 'auth_type': auth_type, }) self.log.debug("defaults: %s", cc.defaults) self.cloud = cc.get_one_cloud( From 7bb459837bf8023cbc71cbf41007f8aa4c4725fb Mon Sep 17 00:00:00 2001 From: chengkunye Date: Thu, 16 Jul 2015 17:32:42 +0800 Subject: [PATCH 0185/3095] add image member commands for image API This commit adds the following commands: image project add image project remove Closes-Bug: 1402420 Change-Id: I07954e9fa43a3ad6078dd939ecedf9f038299e7b --- doc/source/command-objects/image.rst | 54 +++++++ openstackclient/image/v2/image.py | 80 ++++++++++ openstackclient/tests/image/v2/fakes.py | 15 ++ openstackclient/tests/image/v2/test_image.py | 155 +++++++++++++++++++ setup.cfg | 2 + 5 files changed, 306 insertions(+) diff --git a/doc/source/command-objects/image.rst b/doc/source/command-objects/image.rst index 824d4930e5..c4b24233ee 100644 --- a/doc/source/command-objects/image.rst +++ b/doc/source/command-objects/image.rst @@ -322,3 +322,57 @@ Display image details .. describe:: Image to display (name or ID) + +image add project +----------------- + +*Only supported for Image v2* + +Associate project with image + +.. progran:: image add project +.. code:: bash + + os image add project + [--project-domain ] + + +.. option:: --project-domain + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + +.. describe:: + + Image to share (name or ID). + +.. describe:: + + Project to associate with image (name or ID) + +image remove project +-------------------- + +*Only supported for Image v2* + +Disassociate project with image + +.. progran:: image remove project +.. code:: bash + + os image remove remove + [--project-domain ] + + +.. option:: --project-domain + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + +.. describe:: + + Image to unshare (name or ID). + +.. describe:: + + Project to disassociate with image (name or ID) diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 3dd9833849..3048520289 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -27,6 +27,49 @@ from openstackclient.api import utils as api_utils from openstackclient.common import parseractions from openstackclient.common import utils +from openstackclient.identity import common + + +class AddProjectToImage(show.ShowOne): + """Associate project with image""" + + log = logging.getLogger(__name__ + ".AddProjectToImage") + + def get_parser(self, prog_name): + parser = super(AddProjectToImage, self).get_parser(prog_name) + parser.add_argument( + "image", + metavar="", + help="Image to share (name or ID)", + ) + parser.add_argument( + "project", + metavar="", + help="Project to associate with image (name or ID)", + ) + common.add_project_domain_option_to_parser(parser) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + + image_client = self.app.client_manager.image + identity_client = self.app.client_manager.identity + + project_id = common.find_project(identity_client, + parsed_args.project, + parsed_args.project_domain).id + + image_id = utils.find_resource( + image_client.images, + parsed_args.image).id + + image_member = image_client.image_members.create( + image_id, + project_id, + ) + + return zip(*sorted(six.iteritems(image_member._info))) class DeleteImage(command.Command): @@ -192,6 +235,43 @@ def take_action(self, parsed_args): ) +class RemoveProjectImage(command.Command): + """Disassociate project with image""" + + log = logging.getLogger(__name__ + ".RemoveProjectImage") + + def get_parser(self, prog_name): + parser = super(RemoveProjectImage, self).get_parser(prog_name) + parser.add_argument( + "image", + metavar="", + help="Image to unshare (name or ID)", + ) + parser.add_argument( + "project", + metavar="", + help="Project to disassociate with image (name or ID)", + ) + common.add_project_domain_option_to_parser(parser) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + + image_client = self.app.client_manager.image + identity_client = self.app.client_manager.identity + + project_id = common.find_project(identity_client, + parsed_args.project, + parsed_args.project_domain).id + + image_id = utils.find_resource( + image_client.images, + parsed_args.image).id + + image_client.image_members.delete(image_id, project_id) + + class SaveImage(command.Command): """Save an image locally""" diff --git a/openstackclient/tests/image/v2/fakes.py b/openstackclient/tests/image/v2/fakes.py index 678291bb86..1a9e301a01 100644 --- a/openstackclient/tests/image/v2/fakes.py +++ b/openstackclient/tests/image/v2/fakes.py @@ -18,6 +18,7 @@ from openstackclient.tests import fakes from openstackclient.tests import utils +from openstackclient.tests.identity.v3 import fakes as identity_fakes image_id = '0f41529e-7c12-4de8-be2d-181abb825b3c' image_name = 'graven' @@ -36,6 +37,13 @@ IMAGE_columns = tuple(sorted(IMAGE)) IMAGE_data = tuple((IMAGE[x] for x in sorted(IMAGE))) +member_status = 'pending' +MEMBER = { + 'member_id': identity_fakes.project_id, + 'image_id': image_id, + 'status': member_status, +} + # Just enough v2 schema to do some testing IMAGE_schema = { "additionalProperties": { @@ -125,6 +133,8 @@ class FakeImagev2Client(object): def __init__(self, **kwargs): self.images = mock.Mock() self.images.resource_class = fakes.FakeResource(None, {}) + self.image_members = mock.Mock() + self.image_members.resource_class = fakes.FakeResource(None, {}) self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] @@ -137,3 +147,8 @@ def setUp(self): endpoint=fakes.AUTH_URL, token=fakes.AUTH_TOKEN, ) + + self.app.client_manager.identity = identity_fakes.FakeIdentityv3Client( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN, + ) diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index 7cfaf08315..bfb9476518 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -21,6 +21,7 @@ from glanceclient.v2 import schemas from openstackclient.image.v2 import image from openstackclient.tests import fakes +from openstackclient.tests.identity.v3 import fakes as identity_fakes from openstackclient.tests.image.v2 import fakes as image_fakes @@ -32,6 +33,96 @@ def setUp(self): # Get a shortcut to the ServerManager Mock self.images_mock = self.app.client_manager.image.images self.images_mock.reset_mock() + self.image_members_mock = self.app.client_manager.image.image_members + self.image_members_mock.reset_mock() + self.project_mock = self.app.client_manager.identity.projects + self.project_mock.reset_mock() + self.domain_mock = self.app.client_manager.identity.domains + self.domain_mock.reset_mock() + + +class TestAddProjectToImage(TestImage): + + def setUp(self): + super(TestAddProjectToImage, self).setUp() + + # This is the return value for utils.find_resource() + self.images_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(image_fakes.IMAGE), + loaded=True, + ) + self.image_members_mock.create.return_value = fakes.FakeResource( + None, + copy.deepcopy(image_fakes.MEMBER), + loaded=True, + ) + self.project_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.PROJECT), + loaded=True, + ) + self.domain_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.DOMAIN), + loaded=True, + ) + # Get the command object to test + self.cmd = image.AddProjectToImage(self.app, None) + + def test_add_project_to_image_no_option(self): + arglist = [ + image_fakes.image_id, + identity_fakes.project_id, + ] + verifylist = [ + ('image', image_fakes.image_id), + ('project', identity_fakes.project_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + self.image_members_mock.create.assert_called_with( + image_fakes.image_id, + identity_fakes.project_id + ) + collist = ('image_id', 'member_id', 'status') + self.assertEqual(collist, columns) + datalist = ( + image_fakes.image_id, + identity_fakes.project_id, + image_fakes.member_status + ) + self.assertEqual(datalist, data) + + def test_add_project_to_image_with_option(self): + arglist = [ + image_fakes.image_id, + identity_fakes.project_id, + '--project-domain', identity_fakes.domain_id, + ] + verifylist = [ + ('image', image_fakes.image_id), + ('project', identity_fakes.project_id), + ('project_domain', identity_fakes.domain_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + self.image_members_mock.create.assert_called_with( + image_fakes.image_id, + identity_fakes.project_id + ) + collist = ('image_id', 'member_id', 'status') + self.assertEqual(collist, columns) + datalist = ( + image_fakes.image_id, + identity_fakes.project_id, + image_fakes.member_status + ) + self.assertEqual(datalist, data) class TestImageDelete(TestImage): @@ -298,6 +389,70 @@ def test_image_list_sort_option(self, si_mock): self.assertEqual(datalist, tuple(data)) +class TestRemoveProjectImage(TestImage): + + def setUp(self): + super(TestRemoveProjectImage, self).setUp() + + # This is the return value for utils.find_resource() + self.images_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(image_fakes.IMAGE), + loaded=True, + ) + self.project_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.PROJECT), + loaded=True, + ) + self.domain_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.DOMAIN), + loaded=True, + ) + self.image_members_mock.delete.return_value = None + # Get the command object to test + self.cmd = image.RemoveProjectImage(self.app, None) + + def test_remove_project_image_no_options(self): + arglist = [ + image_fakes.image_id, + identity_fakes.project_id, + ] + verifylist = [ + ('image', image_fakes.image_id), + ('project', identity_fakes.project_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + self.cmd.take_action(parsed_args) + self.image_members_mock.delete.assert_called_with( + image_fakes.image_id, + identity_fakes.project_id, + ) + + def test_remove_project_image_with_options(self): + arglist = [ + image_fakes.image_id, + identity_fakes.project_id, + '--project-domain', identity_fakes.domain_id, + ] + verifylist = [ + ('image', image_fakes.image_id), + ('project', identity_fakes.project_id), + ('project_domain', identity_fakes.domain_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + self.cmd.take_action(parsed_args) + self.image_members_mock.delete.assert_called_with( + image_fakes.image_id, + identity_fakes.project_id, + ) + + class TestImageShow(TestImage): def setUp(self): diff --git a/setup.cfg b/setup.cfg index b0343e0987..1139f09ce6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -314,8 +314,10 @@ openstack.image.v1 = image_show = openstackclient.image.v1.image:ShowImage openstack.image.v2 = + image_add_project = openstackclient.image.v2.image:AddProjectToImage image_delete = openstackclient.image.v2.image:DeleteImage image_list = openstackclient.image.v2.image:ListImage + image_remove_project = openstackclient.image.v2.image:RemoveProjectImage image_save = openstackclient.image.v2.image:SaveImage image_show = openstackclient.image.v2.image:ShowImage image_set = openstackclient.image.v2.image:SetImage From 659abf492825b71b99e6f4be58447d6f393e33ef Mon Sep 17 00:00:00 2001 From: chengkunye Date: Fri, 17 Jul 2015 17:05:23 +0800 Subject: [PATCH 0186/3095] Add create and list for volume type v2 Volume API v2 is missing create and list features. implements bp: volume-v2 Change-Id: I34a1ae440e9620b1c65546f4f43b369c8661250d --- doc/source/command-objects/volume-type.rst | 26 +++- openstackclient/tests/volume/v2/test_type.py | 129 +++++++++++++++++++ openstackclient/volume/v2/volume_type.py | 97 ++++++++++++++ setup.cfg | 2 + 4 files changed, 253 insertions(+), 1 deletion(-) diff --git a/doc/source/command-objects/volume-type.rst b/doc/source/command-objects/volume-type.rst index 0898df528b..4d5651ac90 100644 --- a/doc/source/command-objects/volume-type.rst +++ b/doc/source/command-objects/volume-type.rst @@ -2,7 +2,7 @@ volume type =========== -Volume v1 +Volume v1, v2 volume type create ------------------ @@ -13,9 +13,29 @@ Create new volume type .. code:: bash os volume type create + [--description ] + [--public | --private] [--property [...] ] +.. option:: --description + + New volume type description + + .. versionadded:: 2 + +.. option:: --public + + Volume type is accessible to the public + + .. versionadded:: 2 + +.. option:: --private + + Volume type is not accessible to the public + + .. versionadded:: 2 + .. option:: --property Set a property on this volume type (repeat option to set multiple properties) @@ -57,6 +77,8 @@ List volume types volume type set --------------- +*Only supported for Volume API v1* + Set volume type properties .. program:: volume type set @@ -77,6 +99,8 @@ Set volume type properties volume type unset ----------------- +*Only supported for Volume API v1* + Unset volume type properties .. program:: volume type unset diff --git a/openstackclient/tests/volume/v2/test_type.py b/openstackclient/tests/volume/v2/test_type.py index 6cc988b2dc..3963496634 100644 --- a/openstackclient/tests/volume/v2/test_type.py +++ b/openstackclient/tests/volume/v2/test_type.py @@ -28,6 +28,135 @@ def setUp(self): self.types_mock.reset_mock() +class TestTypeCreate(TestType): + + def setUp(self): + super(TestTypeCreate, self).setUp() + + self.types_mock.create.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.TYPE), + loaded=True, + ) + # Get the command object to test + self.cmd = volume_type.CreateVolumeType(self.app, None) + + def test_type_create_public(self): + arglist = [ + volume_fakes.type_name, + "--description", volume_fakes.type_description, + "--public" + ] + verifylist = [ + ("name", volume_fakes.type_name), + ("description", volume_fakes.type_description), + ("public", True), + ("private", False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.types_mock.create.assert_called_with( + volume_fakes.type_name, + description=volume_fakes.type_description, + public=True, + ) + + collist = ( + 'description', + 'id', + 'name', + ) + self.assertEqual(collist, columns) + datalist = ( + volume_fakes.type_description, + volume_fakes.type_id, + volume_fakes.type_name, + ) + self.assertEqual(datalist, data) + + def test_type_create_private(self): + arglist = [ + volume_fakes.type_name, + "--description", volume_fakes.type_description, + "--private" + ] + verifylist = [ + ("name", volume_fakes.type_name), + ("description", volume_fakes.type_description), + ("public", False), + ("private", True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.types_mock.create.assert_called_with( + volume_fakes.type_name, + description=volume_fakes.type_description, + private=True, + ) + + collist = ( + 'description', + 'id', + 'name', + ) + self.assertEqual(collist, columns) + datalist = ( + volume_fakes.type_description, + volume_fakes.type_id, + volume_fakes.type_name, + ) + self.assertEqual(datalist, data) + + +class TestTypeList(TestType): + def setUp(self): + super(TestTypeList, self).setUp() + + self.types_mock.list.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.TYPE), + loaded=True + ) + ] + # get the command to test + self.cmd = volume_type.ListVolumeType(self.app, None) + + def test_type_list_without_options(self): + arglist = [] + verifylist = [ + ("long", False) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + collist = ["ID", "Name"] + self.assertEqual(collist, columns) + datalist = (( + volume_fakes.type_id, + volume_fakes.type_name, + ),) + self.assertEqual(datalist, tuple(data)) + + def test_type_list_with_options(self): + arglist = ["--long"] + verifylist = [("long", True)] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + collist = ["ID", "Name", "Description", "Properties"] + self.assertEqual(collist, columns) + datalist = (( + volume_fakes.type_id, + volume_fakes.type_name, + volume_fakes.type_description, + "foo='bar'" + ),) + self.assertEqual(datalist, tuple(data)) + + class TestTypeShow(TestType): def setUp(self): super(TestTypeShow, self).setUp() diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index ae5cc8b892..c785021f50 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -17,12 +17,79 @@ import logging from cliff import command +from cliff import lister from cliff import show import six +from openstackclient.common import parseractions from openstackclient.common import utils +class CreateVolumeType(show.ShowOne): + """Create new volume type""" + + log = logging.getLogger(__name__ + ".CreateVolumeType") + + def get_parser(self, prog_name): + parser = super(CreateVolumeType, self).get_parser(prog_name) + parser.add_argument( + "name", + metavar="", + help="New volume type name" + ) + parser.add_argument( + "--description", + metavar="", + help="New volume type description", + ) + public_group = parser.add_mutually_exclusive_group() + public_group.add_argument( + "--public", + dest="public", + action="store_true", + default=False, + help="Volume type is accessible to the public", + ) + public_group.add_argument( + "--private", + dest="private", + action="store_true", + default=False, + help="Volume type is not accessible to the public", + ) + parser.add_argument( + '--property', + metavar='', + action=parseractions.KeyValueAction, + help='Property to add for this volume type' + '(repeat option to set multiple properties)', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + + volume_client = self.app.client_manager.volume + + kwargs = {} + if parsed_args.public: + kwargs['public'] = True + if parsed_args.private: + kwargs['private'] = True + + volume_type = volume_client.volume_types.create( + parsed_args.name, + description=parsed_args.description, + **kwargs + ) + volume_type._info.pop('extra_specs') + if parsed_args.property: + result = volume_type.set_keys(parsed_args.property) + volume_type._info.update({'properties': utils.format_dict(result)}) + + return zip(*sorted(six.iteritems(volume_type._info))) + + class DeleteVolumeType(command.Command): """Delete volume type""" @@ -46,6 +113,36 @@ def take_action(self, parsed_args): return +class ListVolumeType(lister.Lister): + """List volume types""" + + log = logging.getLogger(__name__ + '.ListVolumeType') + + def get_parser(self, prog_name): + parser = super(ListVolumeType, self).get_parser(prog_name) + parser.add_argument( + '--long', + action='store_true', + default=False, + help='List additional fields in output') + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + if parsed_args.long: + columns = ['ID', 'Name', 'Description', 'Extra Specs'] + column_headers = ['ID', 'Name', 'Description', 'Properties'] + else: + columns = ['ID', 'Name'] + column_headers = columns + data = self.app.client_manager.volume.volume_types.list() + return (column_headers, + (utils.get_item_properties( + s, columns, + formatters={'Extra Specs': utils.format_dict}, + ) for s in data)) + + class ShowVolumeType(show.ShowOne): """Display volume type details""" diff --git a/setup.cfg b/setup.cfg index 826605738b..d73a09ec9b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -393,7 +393,9 @@ openstack.volume.v2 = volume_delete = openstackclient.volume.v2.volume:DeleteVolume volume_show = openstackclient.volume.v2.volume:ShowVolume + volume_type_create = openstackclient.volume.v2.volume_type:CreateVolumeType volume_type_delete = openstackclient.volume.v2.volume_type:DeleteVolumeType + volume_type_list = openstackclient.volume.v2.volume_type:ListVolumeType volume_type_show = openstackclient.volume.v2.volume_type:ShowVolumeType volume_qos_associate = openstackclient.volume.v2.qos_specs:AssociateQos From 921361b3ae538b8f4a9a4725623ccc5bb7a0d48d Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Fri, 10 Jul 2015 18:01:55 +0000 Subject: [PATCH 0187/3095] Make trustee/trustor/project searchable by ID In the previous implementation, we were always including the domain argument, which caused a lookup by name for trustee/trustor and project. By excluding it when not necessary, we do a search by ID in find_resources. Change-Id: Id756aeab522b5dccb2dc6b31d137a28514b0fdf6 Closes-Bug: 1473298 --- openstackclient/identity/v3/trust.py | 31 +++++++--------------------- 1 file changed, 8 insertions(+), 23 deletions(-) diff --git a/openstackclient/identity/v3/trust.py b/openstackclient/identity/v3/trust.py index c8e5c4c729..2f0f804a6a 100644 --- a/openstackclient/identity/v3/trust.py +++ b/openstackclient/identity/v3/trust.py @@ -88,35 +88,20 @@ def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) identity_client = self.app.client_manager.identity - project_domain = None - if parsed_args.project_domain: - project_domain = common.find_domain(identity_client, - parsed_args.project_domain).id - - trustor_domain = None - if parsed_args.trustor_domain: - trustor_domain = common.find_domain(identity_client, - parsed_args.trustor_domain).id - - trustee_domain = None - if parsed_args.trustee_domain: - trustee_domain = common.find_domain(identity_client, - parsed_args.trustee_domain).id - # NOTE(stevemar): Find the two users, project and roles that # are necessary for making a trust usable, the API dictates that # trustee, project and role are optional, but that makes the trust # pointless, and trusts are immutable, so let's enforce it at the # client level. - trustor_id = utils.find_resource(identity_client.users, - parsed_args.trustor, - domain_id=trustor_domain).id - trustee_id = utils.find_resource(identity_client.users, - parsed_args.trustee, - domain_id=trustee_domain).id - project_id = utils.find_resource(identity_client.projects, + trustor_id = common.find_user(identity_client, + parsed_args.trustor, + parsed_args.trustor_domain).id + trustee_id = common.find_user(identity_client, + parsed_args.trustee, + parsed_args.trustee_domain).id + project_id = common.find_project(identity_client, parsed_args.project, - domain_id=project_domain).id + parsed_args.project_domain).id role_names = [] for role in parsed_args.role: From d70df6a4a2bdd99d22051cb999e7c4b36e4e5868 Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Tue, 21 Jul 2015 14:07:18 -0600 Subject: [PATCH 0188/3095] Fix yet more documentation warnings Change-Id: I87683039601ae90531f27ebebbc4d0b1d252e846 --- doc/source/command-objects/endpoint.rst | 2 +- doc/source/command-objects/image.rst | 6 +++--- doc/source/command-objects/keypair.rst | 8 ++++---- doc/source/command-objects/project.rst | 3 ++- doc/source/command-objects/user.rst | 4 ++-- doc/source/releases.rst | 4 ++-- 6 files changed, 14 insertions(+), 13 deletions(-) diff --git a/doc/source/command-objects/endpoint.rst b/doc/source/command-objects/endpoint.rst index 7846a1b1f5..074f20a0b4 100644 --- a/doc/source/command-objects/endpoint.rst +++ b/doc/source/command-objects/endpoint.rst @@ -48,7 +48,7 @@ Create new endpoint .. code:: bash os endpoint create - [--region + [--region ] [--enable | --disable] diff --git a/doc/source/command-objects/image.rst b/doc/source/command-objects/image.rst index c4b24233ee..257414242d 100644 --- a/doc/source/command-objects/image.rst +++ b/doc/source/command-objects/image.rst @@ -246,7 +246,7 @@ Set image properties Disk format of image. Acceptable formats: ['ami', 'ari', 'aki', 'vhd', 'vmdk', 'raw', 'qcow2', - 'vdi', 'iso'] + 'vdi', 'iso'] .. option:: --size @@ -330,7 +330,7 @@ image add project Associate project with image -.. progran:: image add project +.. program:: image add project .. code:: bash os image add project @@ -357,7 +357,7 @@ image remove project Disassociate project with image -.. progran:: image remove project +.. program:: image remove project .. code:: bash os image remove remove diff --git a/doc/source/command-objects/keypair.rst b/doc/source/command-objects/keypair.rst index 04c5721f91..e49acace3d 100644 --- a/doc/source/command-objects/keypair.rst +++ b/doc/source/command-objects/keypair.rst @@ -12,7 +12,7 @@ keypair create Create new public key -.. program keypair create +.. program:: keypair create .. code:: bash os keypair create @@ -32,7 +32,7 @@ keypair delete Delete public key -.. program keypair delete +.. program:: keypair delete .. code:: bash os keypair delete @@ -47,7 +47,7 @@ keypair list List public key fingerprints -.. program keypair list +.. program:: keypair list .. code:: bash os keypair list @@ -57,7 +57,7 @@ keypair show Display public key details -.. program keypair show +.. program:: keypair show .. code:: bash os keypair show diff --git a/doc/source/command-objects/project.rst b/doc/source/command-objects/project.rst index a8a6e81230..a342115d38 100644 --- a/doc/source/command-objects/project.rst +++ b/doc/source/command-objects/project.rst @@ -70,11 +70,12 @@ Delete project(s) .. code:: bash os project delete + [--domain ] [ ...] .. option:: --domain - Domain owning :ref:`\ <_project_delete-project>` (name or ID) + Domain owning :ref:`\ ` (name or ID) .. versionadded:: 3 diff --git a/doc/source/command-objects/user.rst b/doc/source/command-objects/user.rst index 2e297bb360..fbc0b6de75 100644 --- a/doc/source/command-objects/user.rst +++ b/doc/source/command-objects/user.rst @@ -83,16 +83,16 @@ Delete user(s) .. code:: bash os user delete + [--domain ] [ ...] .. option:: --domain - Domain owning :ref:`\ <_user_delete-user>` (name or ID) + Domain owning :ref:`\ ` (name or ID) .. versionadded:: 3 .. _user_delete-user: - .. describe:: User to delete (name or ID) diff --git a/doc/source/releases.rst b/doc/source/releases.rst index b8402cc932..4af9761b99 100644 --- a/doc/source/releases.rst +++ b/doc/source/releases.rst @@ -100,7 +100,7 @@ Release Notes Bug `1446751 `_ * Document the backward-compatibility-breaking changes in - :doc:`backwards-incompatibile`. + :doc:`backwards-incompatible`. Bug `1406470 `_ * Add `--parent`` option to `projct create` command. @@ -203,7 +203,7 @@ Release Notes * Set a default domain ID when both ``OS_USER_DOMAIN_ID`` and ``OS_USER_DOMAIN_NAME`` are not set. This is also done for - ``OS_PROJECT_DOMAIN_ID`` and ``OS_PROJECT_DOMAIN_NAME`. + ``OS_PROJECT_DOMAIN_ID`` and ``OS_PROJECT_DOMAIN_NAME``. (*Identity API v3 only*). Bug `1385338 `_: Improve domain related defaults when using v3 identity From 54ff5d8758afa6e101ab6e9e67daa8f0711a25c7 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 22 Jul 2015 04:59:50 +0000 Subject: [PATCH 0189/3095] Updated from global requirements Change-Id: I7fc281b80342a14e11f87afb41c3abc515d8487c --- requirements.txt | 2 +- test-requirements.txt | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 9ea39916fa..e06a04ad27 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,7 +14,7 @@ oslo.utils>=1.9.0 # Apache-2.0 python-glanceclient>=0.18.0 python-keystoneclient>=1.6.0 python-novaclient>=2.22.0 -python-cinderclient>=1.2.2 +python-cinderclient>=1.3.1 python-neutronclient<3,>=2.3.11 requests>=2.5.2 stevedore>=1.5.0 # Apache-2.0 diff --git a/test-requirements.txt b/test-requirements.txt index f9c257680e..7cf9d4555e 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -6,8 +6,7 @@ hacking<0.11,>=0.10.0 coverage>=3.6 discover fixtures>=1.3.1 -mock!=1.1.4,>=1.1;python_version!='2.6' -mock==1.0.1;python_version=='2.6' +mock>=1.2 oslosphinx>=2.5.0 # Apache-2.0 oslotest>=1.7.0 # Apache-2.0 requests-mock>=0.6.0 # Apache-2.0 From a5b0d9ab356f9d94f88feaae82bf80e46e81bc47 Mon Sep 17 00:00:00 2001 From: chengkunye Date: Wed, 22 Jul 2015 05:08:36 -0400 Subject: [PATCH 0190/3095] add doc for floatingip Change-Id: Ibdee50066452ed33f210e93de29eba3c0ec2e155 --- doc/source/command-objects/floatingip.rst | 85 +++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 doc/source/command-objects/floatingip.rst diff --git a/doc/source/command-objects/floatingip.rst b/doc/source/command-objects/floatingip.rst new file mode 100644 index 0000000000..1ae3041e42 --- /dev/null +++ b/doc/source/command-objects/floatingip.rst @@ -0,0 +1,85 @@ +========== +floatingip +========== + +Compute v2 + +ip floating add +--------------- + +Add floating-ip to server + +.. program:: ip floating add +.. code:: bash + + os ip floating add + + + +.. describe:: + + IP address to add to server + +.. describe:: + + Server to receive the IP address (name or ID) + +ip floating create +------------------ + +Create new floating-ip + +.. program:: ip floating create +.. code:: bash + + os ip floating create + + +.. describe:: + + Pool to fetch floating IP from + +ip floating delete +------------------ + +Delete a floating-ip + +.. program:: ip floating delete +.. code:: bash + + os ip floating delete + + +.. describe:: + + IP address to delete + +ip floating list +---------------- + +List floating-ips + +.. program:: ip floating list +.. code:: bash + + os ip floating list + +ip floating remove +------------------ + +Remove floating-ip from server + +.. program:: ip floating remove +.. code:: bash + + os ip floating remove + + + +.. describe:: + + IP address to remove from server + +.. describe:: + + Server to remove the IP address from (name or ID) From 874c9212929204a6eb3c0dc70afd5ccea9794178 Mon Sep 17 00:00:00 2001 From: Hugh Saunders Date: Wed, 22 Jul 2015 11:49:58 +0100 Subject: [PATCH 0191/3095] Fix --os-auth-plugin in auth_with_unscoped_saml The error message refers to --os-auth-plugin which is not a valid option. This patch changes that to --os-auth-type. Change-Id: I02ec0b7855131180bb8c674051930ebb51cb7303 Closes-Bug: #1477083 --- openstackclient/identity/v3/unscoped_saml.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/identity/v3/unscoped_saml.py b/openstackclient/identity/v3/unscoped_saml.py index 9b158b6752..fddac68fae 100644 --- a/openstackclient/identity/v3/unscoped_saml.py +++ b/openstackclient/identity/v3/unscoped_saml.py @@ -37,7 +37,7 @@ def _decorated(self, parsed_args): else: msg = ('This command requires the use of an unscoped SAML ' 'authentication plugin. Please use argument ' - '--os-auth-plugin with one of the following ' + '--os-auth-type with one of the following ' 'plugins: ' + ', '.join(UNSCOPED_AUTH_PLUGINS)) raise exceptions.CommandError(msg) return _decorated From 15fe0fae179b8b1f8c83144b22542d9b379d39ad Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 22 Jul 2015 10:51:07 -0500 Subject: [PATCH 0192/3095] Add plugin interface version External plugins need to know which plugin interface is being used by the host OSC. Releases <1.6 (?) will not have a version defined. Plugins can add the following to their make_client() to discover the interface version: _plugin_interface_version = getattr( instance, "PLUGIN_INTERFACE_VERSION", None, ) Change-Id: Ifc0f40fec5bc27f6425139984936b7f6e032a580 --- openstackclient/common/clientmanager.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index 742509e4fd..6abe16db51 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -48,6 +48,10 @@ def __get__(self, instance, owner): class ClientManager(object): """Manages access to API clients, including authentication.""" + + # A simple incrementing version for the plugin to know what is available + PLUGIN_INTERFACE_VERSION = "2" + identity = ClientCache(identity_client.make_client) def __getattr__(self, name): From 025d38040c8eb427e6c908374b9c6fb93b056036 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 23 Jul 2015 06:08:14 +0000 Subject: [PATCH 0193/3095] Imported Translations from Transifex For more information about this automatic import see: https://wiki.openstack.org/wiki/Translations/Infrastructure Change-Id: Ic6d4612806a946a4f8047b501bea8ae08557fad7 --- .../locale/de/LC_MESSAGES/python-openstackclient.po | 6 +++--- .../locale/zh_TW/LC_MESSAGES/python-openstackclient.po | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po b/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po index a6c5a98037..bf15a9072d 100644 --- a/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po +++ b/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po @@ -9,11 +9,11 @@ msgid "" msgstr "" "Project-Id-Version: python-openstackclient\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-07-09 06:11+0000\n" +"POT-Creation-Date: 2015-07-23 06:08+0000\n" "PO-Revision-Date: 2015-06-14 18:41+0000\n" "Last-Translator: openstackjenkins \n" -"Language-Team: German (http://www.transifex.com/p/python-openstackclient/" -"language/de/)\n" +"Language-Team: German (http://www.transifex.com/projects/p/python-" +"openstackclient/language/de/)\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" diff --git a/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po b/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po index 87ddd9ac81..c17f8dab3d 100644 --- a/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po +++ b/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po @@ -8,10 +8,10 @@ msgid "" msgstr "" "Project-Id-Version: python-openstackclient\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-07-09 06:11+0000\n" +"POT-Creation-Date: 2015-07-23 06:08+0000\n" "PO-Revision-Date: 2015-06-14 18:41+0000\n" "Last-Translator: openstackjenkins \n" -"Language-Team: Chinese (Taiwan) (http://www.transifex.com/p/python-" +"Language-Team: Chinese (Taiwan) (http://www.transifex.com/projects/p/python-" "openstackclient/language/zh_TW/)\n" "Plural-Forms: nplurals=1; plural=0\n" "MIME-Version: 1.0\n" From 43942871a9d1515b6ed261e5093001850c2232be Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Sun, 19 Jul 2015 12:15:04 -0600 Subject: [PATCH 0194/3095] Add configuration show command Create a `configuration show` command that displays the current configuration of the CLI. Different configurations can be displayed using options such as --os-cloud. Passwords and tokens are redacted by default unless the --unmask option is specified. Closes-Bug: #1476729 Change-Id: I0792365d0c5fa526cd09c0ed88c6bb1e2cb813a7 --- doc/source/command-objects/configuration.rst | 18 +++++ doc/source/configuration.rst | 6 ++ openstackclient/common/clientmanager.py | 4 + openstackclient/common/configuration.py | 58 ++++++++++++++ .../tests/common/test_configuration.py | 79 +++++++++++++++++++ openstackclient/tests/fakes.py | 12 +++ setup.cfg | 1 + 7 files changed, 178 insertions(+) create mode 100644 doc/source/command-objects/configuration.rst create mode 100644 openstackclient/common/configuration.py create mode 100644 openstackclient/tests/common/test_configuration.py diff --git a/doc/source/command-objects/configuration.rst b/doc/source/command-objects/configuration.rst new file mode 100644 index 0000000000..0ee8bd63e5 --- /dev/null +++ b/doc/source/command-objects/configuration.rst @@ -0,0 +1,18 @@ +============= +configuration +============= + +Available for all services + +configuration show +------------------ + +Show the current openstack client configuration. This command is a little +different from other show commands because it does not take a resource name +or id to show. The command line options, such as --os-cloud, can be used to +show different configurations. + +.. program:: configuration show +.. code:: bash + + os configuration show diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst index de3f84ee8e..563a7193ea 100644 --- a/doc/source/configuration.rst +++ b/doc/source/configuration.rst @@ -137,3 +137,9 @@ that appears in :file:`clouds.yaml` rackspace: auth: auth_url: 'https://identity.api.rackspacecloud.com/v2.0/' + +Debugging +~~~~~~~~~ +You may find the :doc:`config show ` +helpful to debug configuration issues. It will display your current +configuration. diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index 742509e4fd..d8e7e1a9fa 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -15,6 +15,7 @@ """Manage access to the clients, including authenticating when needed.""" +import copy import logging import pkg_resources import sys @@ -203,6 +204,9 @@ def get_endpoint_for_service_type(self, service_type, region_name=None, interface=interface) return endpoint + def get_configuration(self): + return copy.deepcopy(self._cli_options.config) + # Plugin Support diff --git a/openstackclient/common/configuration.py b/openstackclient/common/configuration.py new file mode 100644 index 0000000000..83df73e23e --- /dev/null +++ b/openstackclient/common/configuration.py @@ -0,0 +1,58 @@ +# 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. +# + +"""Configuration action implementations""" + +import logging + +from cliff import show +import six + +REDACTED = "" + + +class ShowConfiguration(show.ShowOne): + """Display configuration details""" + + log = logging.getLogger(__name__ + '.ShowConfiguration') + + def get_parser(self, prog_name): + parser = super(ShowConfiguration, self).get_parser(prog_name) + mask_group = parser.add_mutually_exclusive_group() + mask_group.add_argument( + "--mask", + dest="mask", + action="store_true", + default=True, + help="Attempt to mask passwords (default)", + ) + mask_group.add_argument( + "--unmask", + dest="mask", + action="store_false", + help="Show password in clear text", + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + + info = self.app.client_manager.get_configuration() + for key, value in six.iteritems(info.pop('auth', {})): + if parsed_args.mask: + if 'password' in key.lower(): + value = REDACTED + if 'token' in key.lower(): + value = REDACTED + info['auth.' + key] = value + return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/tests/common/test_configuration.py b/openstackclient/tests/common/test_configuration.py new file mode 100644 index 0000000000..3b942533a1 --- /dev/null +++ b/openstackclient/tests/common/test_configuration.py @@ -0,0 +1,79 @@ +# 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.common import configuration +from openstackclient.tests import fakes +from openstackclient.tests import utils + + +class TestConfiguration(utils.TestCommand): + + def test_show(self): + arglist = [] + verifylist = [('mask', True)] + cmd = configuration.ShowConfiguration(self.app, None) + parsed_args = self.check_parser(cmd, arglist, verifylist) + + columns, data = cmd.take_action(parsed_args) + + collist = ('auth.password', 'auth.token', 'auth.username', + 'identity_api_version', 'region') + self.assertEqual(collist, columns) + datalist = ( + configuration.REDACTED, + configuration.REDACTED, + fakes.USERNAME, + fakes.VERSION, + fakes.REGION_NAME, + ) + self.assertEqual(datalist, tuple(data)) + + def test_show_unmask(self): + arglist = ['--unmask'] + verifylist = [('mask', False)] + cmd = configuration.ShowConfiguration(self.app, None) + parsed_args = self.check_parser(cmd, arglist, verifylist) + + columns, data = cmd.take_action(parsed_args) + + collist = ('auth.password', 'auth.token', 'auth.username', + 'identity_api_version', 'region') + self.assertEqual(collist, columns) + datalist = ( + fakes.PASSWORD, + fakes.AUTH_TOKEN, + fakes.USERNAME, + fakes.VERSION, + fakes.REGION_NAME, + ) + self.assertEqual(datalist, tuple(data)) + + def test_show_mask(self): + arglist = ['--mask'] + verifylist = [('mask', True)] + cmd = configuration.ShowConfiguration(self.app, None) + parsed_args = self.check_parser(cmd, arglist, verifylist) + + columns, data = cmd.take_action(parsed_args) + + collist = ('auth.password', 'auth.token', 'auth.username', + 'identity_api_version', 'region') + self.assertEqual(collist, columns) + datalist = ( + configuration.REDACTED, + configuration.REDACTED, + fakes.USERNAME, + fakes.VERSION, + fakes.REGION_NAME, + ) + self.assertEqual(datalist, tuple(data)) diff --git a/openstackclient/tests/fakes.py b/openstackclient/tests/fakes.py index ff69c190df..979f9481dc 100644 --- a/openstackclient/tests/fakes.py +++ b/openstackclient/tests/fakes.py @@ -28,6 +28,7 @@ PROJECT_NAME = "poochie" REGION_NAME = "richie" INTERFACE = "catchy" +VERSION = "3" TEST_RESPONSE_DICT = fixture.V2Token(token_id=AUTH_TOKEN, user_name=USERNAME) @@ -102,6 +103,17 @@ def __init__(self): self.auth_ref = None self.auth_plugin_name = None + def get_configuration(self): + return { + 'auth': { + 'username': USERNAME, + 'password': PASSWORD, + 'token': AUTH_TOKEN, + }, + 'region': REGION_NAME, + 'identity_api_version': VERSION, + } + class FakeModule(object): def __init__(self, name, version): diff --git a/setup.cfg b/setup.cfg index 496529c2d8..2500fdf1eb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -44,6 +44,7 @@ openstack.cli.base = volume = openstackclient.volume.client openstack.common = + configuration_show = openstackclient.common.configuration:ShowConfiguration extension_list = openstackclient.common.extension:ListExtension limits_show = openstackclient.common.limits:ShowLimits quota_set = openstackclient.common.quota:SetQuota From 96afb8b1b7b59a8a53b6614457fbbf36fc9882dc Mon Sep 17 00:00:00 2001 From: Amey Bhide Date: Mon, 1 Jun 2015 23:40:48 -0700 Subject: [PATCH 0195/3095] Add support for volume v2 commands Adds the following commands: openstack volume create openstack volume set openstack volume unset Implements: blueprint volume-v2 Change-Id: Icb7404815763aa88550112fb42f5200ce05c2486 --- openstackclient/tests/volume/v2/fakes.py | 17 +- .../tests/volume/v2/test_volume.py | 469 +++++++++++++++++- openstackclient/volume/v2/volume.py | 235 +++++++++ setup.cfg | 3 + 4 files changed, 722 insertions(+), 2 deletions(-) diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py index c896ed6dd1..a95bc94b14 100644 --- a/openstackclient/tests/volume/v2/fakes.py +++ b/openstackclient/tests/volume/v2/fakes.py @@ -17,6 +17,7 @@ from openstackclient.tests import fakes from openstackclient.tests.identity.v2_0 import fakes as identity_fakes +from openstackclient.tests.image.v2 import fakes as image_fakes from openstackclient.tests import utils volume_id = "ce26708d-a7f8-4b4b-9861-4a80256615a6" @@ -26,8 +27,11 @@ volume_size = 20 volume_type = "fake_lvmdriver-1" volume_metadata = { - "foo": "bar" + 'Alpha': 'a', + 'Beta': 'b', + 'Gamma': 'g', } +volume_metadata_str = "Alpha='a', Beta='b', Gamma='g'" volume_snapshot_id = 1 volume_availability_zone = "nova" volume_attachments = ["fake_attachments"] @@ -169,6 +173,13 @@ 'associations': [qos_association] } +image_id = 'im1' +image_name = 'graven' +IMAGE = { + 'id': image_id, + 'name': image_name +} + class FakeVolumeClient(object): def __init__(self, **kwargs): @@ -200,3 +211,7 @@ def setUp(self): endpoint=fakes.AUTH_URL, token=fakes.AUTH_TOKEN ) + self.app.client_manager.image = image_fakes.FakeImagev2Client( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN + ) diff --git a/openstackclient/tests/volume/v2/test_volume.py b/openstackclient/tests/volume/v2/test_volume.py index 9e991b7278..4fffefa4d7 100644 --- a/openstackclient/tests/volume/v2/test_volume.py +++ b/openstackclient/tests/volume/v2/test_volume.py @@ -15,18 +15,485 @@ import copy from openstackclient.tests import fakes +from openstackclient.tests.identity.v2_0 import fakes as identity_fakes from openstackclient.tests.volume.v2 import fakes as volume_fakes from openstackclient.volume.v2 import volume class TestVolume(volume_fakes.TestVolume): - def setUp(self): super(TestVolume, self).setUp() self.volumes_mock = self.app.client_manager.volume.volumes self.volumes_mock.reset_mock() + self.projects_mock = self.app.client_manager.identity.tenants + self.projects_mock.reset_mock() + + self.users_mock = self.app.client_manager.identity.users + self.users_mock.reset_mock() + + self.images_mock = self.app.client_manager.image.images + self.images_mock.reset_mock() + + +class TestVolumeCreate(TestVolume): + def setUp(self): + super(TestVolumeCreate, self).setUp() + + self.volumes_mock.create.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.VOLUME), + loaded=True, + ) + + # Get the command object to test + self.cmd = volume.CreateVolume(self.app, None) + + def test_volume_create_min_options(self): + arglist = [ + '--size', str(volume_fakes.volume_size), + volume_fakes.volume_name, + ] + verifylist = [ + ('size', volume_fakes.volume_size), + ('name', volume_fakes.volume_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + self.volumes_mock.create.assert_called_with( + size=volume_fakes.volume_size, + snapshot_id=None, + name=volume_fakes.volume_name, + description=None, + volume_type=None, + user_id=None, + project_id=None, + availability_zone=None, + metadata=None, + imageRef=None, + source_volid=None + ) + + collist = ( + 'attachments', + 'availability_zone', + 'description', + 'id', + 'name', + 'properties', + 'size', + 'snapshot_id', + 'status', + 'type', + ) + self.assertEqual(collist, columns) + datalist = ( + volume_fakes.volume_attachments, + volume_fakes.volume_availability_zone, + volume_fakes.volume_description, + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_metadata_str, + volume_fakes.volume_size, + volume_fakes.volume_snapshot_id, + volume_fakes.volume_status, + volume_fakes.volume_type, + ) + self.assertEqual(datalist, data) + + def test_volume_create_options(self): + arglist = [ + '--size', str(volume_fakes.volume_size), + '--description', volume_fakes.volume_description, + '--type', volume_fakes.volume_type, + '--availability-zone', volume_fakes.volume_availability_zone, + volume_fakes.volume_name, + ] + verifylist = [ + ('size', volume_fakes.volume_size), + ('description', volume_fakes.volume_description), + ('type', volume_fakes.volume_type), + ('availability_zone', volume_fakes.volume_availability_zone), + ('name', volume_fakes.volume_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + self.volumes_mock.create.assert_called_with( + size=volume_fakes.volume_size, + snapshot_id=None, + name=volume_fakes.volume_name, + description=volume_fakes.volume_description, + volume_type=volume_fakes.volume_type, + user_id=None, + project_id=None, + availability_zone=volume_fakes.volume_availability_zone, + metadata=None, + imageRef=None, + source_volid=None + ) + + collist = ( + 'attachments', + 'availability_zone', + 'description', + 'id', + 'name', + 'properties', + 'size', + 'snapshot_id', + 'status', + 'type', + ) + self.assertEqual(collist, columns) + datalist = ( + volume_fakes.volume_attachments, + volume_fakes.volume_availability_zone, + volume_fakes.volume_description, + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_metadata_str, + volume_fakes.volume_size, + volume_fakes.volume_snapshot_id, + volume_fakes.volume_status, + volume_fakes.volume_type, + ) + self.assertEqual(datalist, data) + + def test_volume_create_user_project_id(self): + # Return a project + self.projects_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.PROJECT), + loaded=True, + ) + # Return a user + self.users_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.USER), + loaded=True, + ) + + arglist = [ + '--size', str(volume_fakes.volume_size), + '--project', identity_fakes.project_id, + '--user', identity_fakes.user_id, + volume_fakes.volume_name, + ] + verifylist = [ + ('size', volume_fakes.volume_size), + ('project', identity_fakes.project_id), + ('user', identity_fakes.user_id), + ('name', volume_fakes.volume_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + self.volumes_mock.create.assert_called_with( + size=volume_fakes.volume_size, + snapshot_id=None, + name=volume_fakes.volume_name, + description=None, + volume_type=None, + user_id=identity_fakes.user_id, + project_id=identity_fakes.project_id, + availability_zone=None, + metadata=None, + imageRef=None, + source_volid=None + ) + + collist = ( + 'attachments', + 'availability_zone', + 'description', + 'id', + 'name', + 'properties', + 'size', + 'snapshot_id', + 'status', + 'type', + ) + self.assertEqual(collist, columns) + datalist = ( + volume_fakes.volume_attachments, + volume_fakes.volume_availability_zone, + volume_fakes.volume_description, + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_metadata_str, + volume_fakes.volume_size, + volume_fakes.volume_snapshot_id, + volume_fakes.volume_status, + volume_fakes.volume_type, + ) + self.assertEqual(datalist, data) + + def test_volume_create_user_project_name(self): + # Return a project + self.projects_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.PROJECT), + loaded=True, + ) + # Return a user + self.users_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.USER), + loaded=True, + ) + + arglist = [ + '--size', str(volume_fakes.volume_size), + '--project', identity_fakes.project_name, + '--user', identity_fakes.user_name, + volume_fakes.volume_name, + ] + verifylist = [ + ('size', volume_fakes.volume_size), + ('project', identity_fakes.project_name), + ('user', identity_fakes.user_name), + ('name', volume_fakes.volume_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + self.volumes_mock.create.assert_called_with( + size=volume_fakes.volume_size, + snapshot_id=None, + name=volume_fakes.volume_name, + description=None, + volume_type=None, + user_id=identity_fakes.user_id, + project_id=identity_fakes.project_id, + availability_zone=None, + metadata=None, + imageRef=None, + source_volid=None + ) + + collist = ( + 'attachments', + 'availability_zone', + 'description', + 'id', + 'name', + 'properties', + 'size', + 'snapshot_id', + 'status', + 'type', + ) + self.assertEqual(collist, columns) + datalist = ( + volume_fakes.volume_attachments, + volume_fakes.volume_availability_zone, + volume_fakes.volume_description, + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_metadata_str, + volume_fakes.volume_size, + volume_fakes.volume_snapshot_id, + volume_fakes.volume_status, + volume_fakes.volume_type, + ) + self.assertEqual(datalist, data) + + def test_volume_create_properties(self): + arglist = [ + '--property', 'Alpha=a', + '--property', 'Beta=b', + '--size', str(volume_fakes.volume_size), + volume_fakes.volume_name, + ] + verifylist = [ + ('property', {'Alpha': 'a', 'Beta': 'b'}), + ('size', volume_fakes.volume_size), + ('name', volume_fakes.volume_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + self.volumes_mock.create.assert_called_with( + size=volume_fakes.volume_size, + snapshot_id=None, + name=volume_fakes.volume_name, + description=None, + volume_type=None, + user_id=None, + project_id=None, + availability_zone=None, + metadata={'Alpha': 'a', 'Beta': 'b'}, + imageRef=None, + source_volid=None + ) + + collist = ( + 'attachments', + 'availability_zone', + 'description', + 'id', + 'name', + 'properties', + 'size', + 'snapshot_id', + 'status', + 'type', + ) + self.assertEqual(collist, columns) + datalist = ( + volume_fakes.volume_attachments, + volume_fakes.volume_availability_zone, + volume_fakes.volume_description, + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_metadata_str, + volume_fakes.volume_size, + volume_fakes.volume_snapshot_id, + volume_fakes.volume_status, + volume_fakes.volume_type, + ) + self.assertEqual(datalist, data) + + def test_volume_create_image_id(self): + self.images_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.IMAGE), + loaded=True, + ) + + arglist = [ + '--image', volume_fakes.image_id, + '--size', str(volume_fakes.volume_size), + volume_fakes.volume_name, + ] + verifylist = [ + ('image', volume_fakes.image_id), + ('size', volume_fakes.volume_size), + ('name', volume_fakes.volume_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + self.volumes_mock.create.assert_called_with( + size=volume_fakes.volume_size, + snapshot_id=None, + name=volume_fakes.volume_name, + description=None, + volume_type=None, + user_id=None, + project_id=None, + availability_zone=None, + metadata=None, + imageRef=volume_fakes.image_id, + source_volid=None + ) + + collist = ( + 'attachments', + 'availability_zone', + 'description', + 'id', + 'name', + 'properties', + 'size', + 'snapshot_id', + 'status', + 'type', + ) + self.assertEqual(collist, columns) + datalist = ( + volume_fakes.volume_attachments, + volume_fakes.volume_availability_zone, + volume_fakes.volume_description, + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_metadata_str, + volume_fakes.volume_size, + volume_fakes.volume_snapshot_id, + volume_fakes.volume_status, + volume_fakes.volume_type, + ) + self.assertEqual(datalist, data) + + def test_volume_create_image_name(self): + self.images_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.IMAGE), + loaded=True, + ) + + arglist = [ + '--image', volume_fakes.image_name, + '--size', str(volume_fakes.volume_size), + volume_fakes.volume_name, + ] + verifylist = [ + ('image', volume_fakes.image_name), + ('size', volume_fakes.volume_size), + ('name', volume_fakes.volume_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + self.volumes_mock.create.assert_called_with( + size=volume_fakes.volume_size, + snapshot_id=None, + name=volume_fakes.volume_name, + description=None, + volume_type=None, + user_id=None, + project_id=None, + availability_zone=None, + metadata=None, + imageRef=volume_fakes.image_id, + source_volid=None + ) + + collist = ( + 'attachments', + 'availability_zone', + 'description', + 'id', + 'name', + 'properties', + 'size', + 'snapshot_id', + 'status', + 'type', + ) + self.assertEqual(collist, columns) + datalist = ( + volume_fakes.volume_attachments, + volume_fakes.volume_availability_zone, + volume_fakes.volume_description, + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_metadata_str, + volume_fakes.volume_size, + volume_fakes.volume_snapshot_id, + volume_fakes.volume_status, + volume_fakes.volume_type, + ) + self.assertEqual(datalist, data) + class TestVolumeShow(TestVolume): def setUp(self): diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index e50a6f0c9d..d4536f5149 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -20,9 +20,139 @@ from cliff import show import six +from openstackclient.common import parseractions from openstackclient.common import utils +class CreateVolume(show.ShowOne): + """Create new volume""" + + log = logging.getLogger(__name__ + ".CreateVolume") + + def get_parser(self, prog_name): + parser = super(CreateVolume, self).get_parser(prog_name) + parser.add_argument( + "name", + metavar="", + help="New volume name" + ) + parser.add_argument( + "--size", + metavar="", + type=int, + required=True, + help="New volume size in GB" + ) + parser.add_argument( + "--snapshot", + metavar="", + help="Use as source of new volume (name or ID)" + ) + parser.add_argument( + "--description", + metavar="", + help="New volume description" + ) + parser.add_argument( + "--type", + metavar="", + help="Use as the new volume type", + ) + parser.add_argument( + '--user', + metavar='', + help='Specify an alternate user (name or ID)', + ) + parser.add_argument( + '--project', + metavar='', + help='Specify an alternate project (name or ID)', + ) + parser.add_argument( + "--availability-zone", + metavar="", + help="Create new volume in " + ) + parser.add_argument( + "--image", + metavar="", + help="Use as source of new volume (name or ID)" + ) + parser.add_argument( + "--source", + metavar="", + help="Volume to clone (name or ID)" + ) + parser.add_argument( + "--property", + metavar="", + action=parseractions.KeyValueAction, + help="Set a property to this volume " + "(repeat option to set multiple properties)" + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action: (%s)", parsed_args) + + identity_client = self.app.client_manager.identity + volume_client = self.app.client_manager.volume + image_client = self.app.client_manager.image + + source_volume = None + if parsed_args.source: + source_volume = utils.find_resource( + volume_client.volumes, + parsed_args.source).id + + image = None + if parsed_args.image: + image = utils.find_resource( + image_client.images, + parsed_args.image).id + + snapshot = None + if parsed_args.snapshot: + snapshot = utils.find_resource( + volume_client.snapshots, + parsed_args.snapshot).id + + project = None + if parsed_args.project: + project = utils.find_resource( + identity_client.projects, + parsed_args.project).id + + user = None + if parsed_args.user: + user = utils.find_resource( + identity_client.users, + parsed_args.user).id + + volume = volume_client.volumes.create( + size=parsed_args.size, + snapshot_id=snapshot, + name=parsed_args.name, + description=parsed_args.description, + volume_type=parsed_args.type, + user_id=user, + project_id=project, + availability_zone=parsed_args.availability_zone, + metadata=parsed_args.property, + imageRef=image, + source_volid=source_volume + ) + # Remove key links from being displayed + volume._info.update( + { + 'properties': utils.format_dict(volume._info.pop('metadata')), + 'type': volume._info.pop('volume_type') + } + ) + volume._info.pop("links", None) + return zip(*sorted(six.iteritems(volume._info))) + + class DeleteVolume(command.Command): """Delete volume(s)""" @@ -59,6 +189,77 @@ def take_action(self, parsed_args): return +class SetVolume(show.ShowOne): + """Set volume properties""" + + log = logging.getLogger(__name__ + '.SetVolume') + + def get_parser(self, prog_name): + parser = super(SetVolume, self).get_parser(prog_name) + parser.add_argument( + 'volume', + metavar='', + help='Volume to change (name or ID)', + ) + parser.add_argument( + '--name', + metavar='', + help='New volume name', + ) + parser.add_argument( + '--description', + metavar='', + help='New volume description', + ) + parser.add_argument( + '--size', + metavar='', + type=int, + help='Extend volume size in GB', + ) + parser.add_argument( + '--property', + metavar='', + action=parseractions.KeyValueAction, + help='Property to add or modify for this volume ' + '(repeat option to set multiple properties)', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + volume_client = self.app.client_manager.volume + volume = utils.find_resource(volume_client.volumes, parsed_args.volume) + + if parsed_args.size: + if volume.status != 'available': + self.app.log.error("Volume is in %s state, it must be " + "available before size can be extended" % + volume.status) + return + if parsed_args.size <= volume.size: + self.app.log.error("New size must be greater than %s GB" % + volume.size) + return + volume_client.volumes.extend(volume.id, parsed_args.size) + + if parsed_args.property: + volume_client.volumes.set_metadata(volume.id, parsed_args.property) + + kwargs = {} + if parsed_args.name: + kwargs['display_name'] = parsed_args.name + if parsed_args.description: + kwargs['display_description'] = parsed_args.description + if kwargs: + volume_client.volumes.update(volume.id, **kwargs) + + if not kwargs and not parsed_args.property and not parsed_args.size: + self.app.log.error("No changes requested\n") + + return + + class ShowVolume(show.ShowOne): """Display volume details""" @@ -81,3 +282,37 @@ def take_action(self, parsed_args): # Remove key links from being displayed volume._info.pop("links", None) return zip(*sorted(six.iteritems(volume._info))) + + +class UnsetVolume(command.Command): + """Unset volume properties""" + + log = logging.getLogger(__name__ + '.UnsetVolume') + + def get_parser(self, prog_name): + parser = super(UnsetVolume, self).get_parser(prog_name) + parser.add_argument( + 'volume', + metavar='', + help='Volume to modify (name or ID)', + ) + parser.add_argument( + '--property', + metavar='', + required=True, + action='append', + default=[], + help='Property to remove from volume ' + '(repeat option to remove multiple properties)', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + volume_client = self.app.client_manager.volume + volume = utils.find_resource( + volume_client.volumes, parsed_args.volume) + + volume_client.volumes.delete_metadata( + volume.id, parsed_args.property) + return diff --git a/setup.cfg b/setup.cfg index 706ea98033..6f7cad61f5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -393,8 +393,11 @@ openstack.volume.v2 = snapshot_show = openstackclient.volume.v2.snapshot:ShowSnapshot snapshot_unset = openstackclient.volume.v2.snapshot:UnsetSnapshot + volume_create = openstackclient.volume.v2.volume:CreateVolume volume_delete = openstackclient.volume.v2.volume:DeleteVolume + volume_set = openstackclient.volume.v2.volume:SetVolume volume_show = openstackclient.volume.v2.volume:ShowVolume + volume_unset = openstackclient.volume.v2.volume:UnsetVolume volume_type_create = openstackclient.volume.v2.volume_type:CreateVolumeType volume_type_delete = openstackclient.volume.v2.volume_type:DeleteVolumeType From 77214c56e78dff60cdaf8fa2a446fd9658261cbf Mon Sep 17 00:00:00 2001 From: jiaxi Date: Fri, 17 Jul 2015 23:29:50 -0400 Subject: [PATCH 0196/3095] Fix quota set failed problem When using the command: openstack quota set, the compute quota below can't be set successfully,the value of compute quota stay unchanged, 'fixed-ips', 'floating-ips', 'injected-files', 'key-pairs'. What's more,I add a TODO comment in the code for two reason. 1. volume quota set works fine for the moment. 2. To indicate that this issue about volume needs discuss and report another bug, if it's confirmed. This bug is only about compute quota. Change-Id: Ic1028d561f5a0030cf65ac18fc117bf01e945478 Partial-Bug: #1420104 --- openstackclient/common/quota.py | 3 +- openstackclient/tests/common/test_quota.py | 89 ++++++++++++++++++++++ openstackclient/tests/compute/v2/fakes.py | 18 +++++ 3 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 openstackclient/tests/common/test_quota.py diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index a40f6e4d84..be6c36eb7e 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -97,12 +97,13 @@ def take_action(self, parsed_args): compute_kwargs = {} for k, v in COMPUTE_QUOTAS.items(): - value = getattr(parsed_args, v, None) + value = getattr(parsed_args, k, None) if value is not None: compute_kwargs[k] = value volume_kwargs = {} for k, v in VOLUME_QUOTAS.items(): + # TODO(jiaxi): Should use k or v needs discuss value = getattr(parsed_args, v, None) if value is not None: if parsed_args.volume_type: diff --git a/openstackclient/tests/common/test_quota.py b/openstackclient/tests/common/test_quota.py new file mode 100644 index 0000000000..f0013e4863 --- /dev/null +++ b/openstackclient/tests/common/test_quota.py @@ -0,0 +1,89 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import copy + +from openstackclient.common import quota +from openstackclient.tests.compute.v2 import fakes as compute_fakes +from openstackclient.tests import fakes + + +class FakeQuotaResource(fakes.FakeResource): + + _keys = {'property': 'value'} + + def set_keys(self, args): + self._keys.update(args) + + def unset_keys(self, keys): + for key in keys: + self._keys.pop(key, None) + + def get_keys(self): + return self._keys + + +class TestQuota(compute_fakes.TestComputev2): + + def setUp(self): + super(TestQuota, self).setUp() + self.quotas_mock = self.app.client_manager.compute.quotas + self.quotas_mock.reset_mock() + + +class TestQuotaSet(TestQuota): + + def setUp(self): + super(TestQuotaSet, self).setUp() + + self.quotas_mock.find.return_value = FakeQuotaResource( + None, + copy.deepcopy(compute_fakes.QUOTA), + loaded=True, + ) + + self.quotas_mock.update.return_value = FakeQuotaResource( + None, + copy.deepcopy(compute_fakes.QUOTA), + loaded=True, + ) + + self.cmd = quota.SetQuota(self.app, None) + + def test_quota_set(self): + arglist = [ + '--floating-ips', str(compute_fakes.floating_ip_num), + '--fixed-ips', str(compute_fakes.fix_ip_num), + '--injected-files', str(compute_fakes.injected_file_num), + '--key-pairs', str(compute_fakes.key_pair_num), + compute_fakes.project_name, + ] + verifylist = [ + ('floating_ips', compute_fakes.floating_ip_num), + ('fixed_ips', compute_fakes.fix_ip_num), + ('injected_files', compute_fakes.injected_file_num), + ('key_pairs', compute_fakes.key_pair_num), + ('project', compute_fakes.project_name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + kwargs = { + 'floating_ips': compute_fakes.floating_ip_num, + 'fixed_ips': compute_fakes.fix_ip_num, + 'injected_files': compute_fakes.injected_file_num, + 'key_pairs': compute_fakes.key_pair_num, + } + + self.quotas_mock.update.assert_called_with('project_test', **kwargs) diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index c18dea7e19..e798bd4036 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -62,6 +62,22 @@ 'vcpus': flavor_vcpus, } +floating_ip_num = 100 +fix_ip_num = 100 +injected_file_num = 100 +key_pair_num = 100 +project_name = 'project_test' +QUOTA = { + 'project': project_name, + 'floating-ips': floating_ip_num, + 'fix-ips': fix_ip_num, + 'injected-files': injected_file_num, + 'key-pairs': key_pair_num, +} + +QUOTA_columns = tuple(sorted(QUOTA)) +QUOTA_data = tuple(QUOTA[x] for x in sorted(QUOTA)) + class FakeComputev2Client(object): def __init__(self, **kwargs): @@ -73,6 +89,8 @@ def __init__(self, **kwargs): self.extensions.resource_class = fakes.FakeResource(None, {}) self.flavors = mock.Mock() self.flavors.resource_class = fakes.FakeResource(None, {}) + self.quotas = mock.Mock() + self.quotas.resource_class = fakes.FakeResource(None, {}) self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] From ead9a40e0297f317eeff5da6a0d7658bf79b92eb Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 29 Jul 2015 03:50:47 +0000 Subject: [PATCH 0197/3095] Updated from global requirements Change-Id: Ic047055ffc7b94032e615815b162f4e830126e6e --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 7cf9d4555e..8abe2cb720 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -8,7 +8,7 @@ discover fixtures>=1.3.1 mock>=1.2 oslosphinx>=2.5.0 # Apache-2.0 -oslotest>=1.7.0 # Apache-2.0 +oslotest>=1.9.0 # Apache-2.0 requests-mock>=0.6.0 # Apache-2.0 sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 os-testr>=0.1.0 From 69093cab614c39a0b00acc87b904acfd01125343 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 29 Jul 2015 06:32:36 +0000 Subject: [PATCH 0198/3095] Imported Translations from Transifex For more information about this automatic import see: https://wiki.openstack.org/wiki/Translations/Infrastructure Change-Id: I4b1050a38ed62872ef809ef9145f0e7cd653be0d --- .../de/LC_MESSAGES/python-openstackclient.po | 4 +- .../locale/python-openstackclient.pot | 230 +++++++++--------- .../LC_MESSAGES/python-openstackclient.po | 4 +- 3 files changed, 119 insertions(+), 119 deletions(-) diff --git a/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po b/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po index bf15a9072d..fd2ad2fbaa 100644 --- a/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po +++ b/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po @@ -9,7 +9,7 @@ msgid "" msgstr "" "Project-Id-Version: python-openstackclient\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-07-23 06:08+0000\n" +"POT-Creation-Date: 2015-07-29 06:32+0000\n" "PO-Revision-Date: 2015-06-14 18:41+0000\n" "Last-Translator: openstackjenkins \n" "Language-Team: German (http://www.transifex.com/projects/p/python-" @@ -18,7 +18,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 1.3\n" +"Generated-By: Babel 2.0\n" msgid "" "\n" diff --git a/python-openstackclient/locale/python-openstackclient.pot b/python-openstackclient/locale/python-openstackclient.pot index 7c99dbf833..e53b8249fb 100644 --- a/python-openstackclient/locale/python-openstackclient.pot +++ b/python-openstackclient/locale/python-openstackclient.pot @@ -7,16 +7,16 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: python-openstackclient 1.5.1.dev45\n" +"Project-Id-Version: python-openstackclient 1.5.1.dev136\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-07-09 06:11+0000\n" +"POT-Creation-Date: 2015-07-29 06:32+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 1.3\n" +"Generated-By: Babel 2.0\n" #: openstackclient/api/auth.py:144 msgid "Set a username with --os-username, OS_USERNAME, or auth.username\n" @@ -48,7 +48,7 @@ msgid "Set a service URL, with --os-url, OS_URL or auth.url\n" msgstr "" #: openstackclient/compute/v2/availability_zone.py:72 -#: openstackclient/compute/v2/server.py:667 +#: openstackclient/compute/v2/server.py:700 #: openstackclient/identity/v2_0/endpoint.py:114 #: openstackclient/identity/v2_0/project.py:145 #: openstackclient/identity/v2_0/service.py:128 @@ -56,89 +56,89 @@ msgstr "" msgid "List additional fields in output" msgstr "" -#: openstackclient/compute/v2/server.py:121 -#: openstackclient/compute/v2/server.py:158 -#: openstackclient/compute/v2/server.py:509 -#: openstackclient/compute/v2/server.py:738 -#: openstackclient/compute/v2/server.py:772 -#: openstackclient/compute/v2/server.py:855 -#: openstackclient/compute/v2/server.py:879 -#: openstackclient/compute/v2/server.py:934 -#: openstackclient/compute/v2/server.py:1026 -#: openstackclient/compute/v2/server.py:1066 -#: openstackclient/compute/v2/server.py:1092 -#: openstackclient/compute/v2/server.py:1157 -#: openstackclient/compute/v2/server.py:1181 -#: openstackclient/compute/v2/server.py:1240 -#: openstackclient/compute/v2/server.py:1277 -#: openstackclient/compute/v2/server.py:1435 -#: openstackclient/compute/v2/server.py:1459 -#: openstackclient/compute/v2/server.py:1483 -#: openstackclient/compute/v2/server.py:1507 -#: openstackclient/compute/v2/server.py:1531 +#: openstackclient/compute/v2/server.py:154 +#: openstackclient/compute/v2/server.py:191 +#: openstackclient/compute/v2/server.py:542 +#: openstackclient/compute/v2/server.py:771 +#: openstackclient/compute/v2/server.py:805 +#: openstackclient/compute/v2/server.py:888 +#: openstackclient/compute/v2/server.py:912 +#: openstackclient/compute/v2/server.py:967 +#: openstackclient/compute/v2/server.py:1059 +#: openstackclient/compute/v2/server.py:1099 +#: openstackclient/compute/v2/server.py:1125 +#: openstackclient/compute/v2/server.py:1190 +#: openstackclient/compute/v2/server.py:1214 +#: openstackclient/compute/v2/server.py:1273 +#: openstackclient/compute/v2/server.py:1310 +#: openstackclient/compute/v2/server.py:1457 +#: openstackclient/compute/v2/server.py:1481 +#: openstackclient/compute/v2/server.py:1505 +#: openstackclient/compute/v2/server.py:1529 +#: openstackclient/compute/v2/server.py:1553 msgid "Server (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:126 +#: openstackclient/compute/v2/server.py:159 msgid "Security group to add (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:163 +#: openstackclient/compute/v2/server.py:196 msgid "Volume to add (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:168 +#: openstackclient/compute/v2/server.py:201 msgid "Server internal device name for volume" msgstr "" -#: openstackclient/compute/v2/server.py:208 -#: openstackclient/compute/v2/server.py:1186 +#: openstackclient/compute/v2/server.py:241 +#: openstackclient/compute/v2/server.py:1219 msgid "New server name" msgstr "" -#: openstackclient/compute/v2/server.py:216 +#: openstackclient/compute/v2/server.py:249 msgid "Create server from this image" msgstr "" -#: openstackclient/compute/v2/server.py:221 +#: openstackclient/compute/v2/server.py:254 msgid "Create server from this volume" msgstr "" -#: openstackclient/compute/v2/server.py:227 +#: openstackclient/compute/v2/server.py:260 msgid "Create server with this flavor" msgstr "" -#: openstackclient/compute/v2/server.py:234 +#: openstackclient/compute/v2/server.py:267 msgid "Security group to assign to this server (repeat for multiple groups)" msgstr "" -#: openstackclient/compute/v2/server.py:240 +#: openstackclient/compute/v2/server.py:273 msgid "Keypair to inject into this server (optional extension)" msgstr "" -#: openstackclient/compute/v2/server.py:246 +#: openstackclient/compute/v2/server.py:279 msgid "Set a property on this server (repeat for multiple values)" msgstr "" -#: openstackclient/compute/v2/server.py:254 +#: openstackclient/compute/v2/server.py:287 msgid "File to inject into image before boot (repeat for multiple files)" msgstr "" -#: openstackclient/compute/v2/server.py:260 +#: openstackclient/compute/v2/server.py:293 msgid "User data file to serve from the metadata server" msgstr "" -#: openstackclient/compute/v2/server.py:265 +#: openstackclient/compute/v2/server.py:298 msgid "Select an availability zone for the server" msgstr "" -#: openstackclient/compute/v2/server.py:272 +#: openstackclient/compute/v2/server.py:305 msgid "" "Map block devices; map is ::: " "(optional extension)" msgstr "" -#: openstackclient/compute/v2/server.py:282 +#: openstackclient/compute/v2/server.py:315 msgid "" "Create a NIC on the server. Specify option multiple times to create " "multiple NICs. Either net-id or port-id must be provided, but not both. " @@ -147,318 +147,318 @@ msgid "" "-fixed-ip: IPv6 fixed address for NIC (optional)." msgstr "" -#: openstackclient/compute/v2/server.py:295 +#: openstackclient/compute/v2/server.py:328 msgid "Hints for the scheduler (optional extension)" msgstr "" -#: openstackclient/compute/v2/server.py:301 +#: openstackclient/compute/v2/server.py:334 msgid "" "Use specified volume as the config drive, or 'True' to use an ephemeral " "drive" msgstr "" -#: openstackclient/compute/v2/server.py:309 +#: openstackclient/compute/v2/server.py:342 msgid "Minimum number of servers to launch (default=1)" msgstr "" -#: openstackclient/compute/v2/server.py:316 +#: openstackclient/compute/v2/server.py:349 msgid "Maximum number of servers to launch (default=1)" msgstr "" -#: openstackclient/compute/v2/server.py:321 +#: openstackclient/compute/v2/server.py:354 msgid "Wait for build to complete" msgstr "" -#: openstackclient/compute/v2/server.py:361 +#: openstackclient/compute/v2/server.py:394 msgid "min instances should be <= max instances" msgstr "" -#: openstackclient/compute/v2/server.py:364 +#: openstackclient/compute/v2/server.py:397 msgid "min instances should be > 0" msgstr "" -#: openstackclient/compute/v2/server.py:367 +#: openstackclient/compute/v2/server.py:400 msgid "max instances should be > 0" msgstr "" -#: openstackclient/compute/v2/server.py:403 +#: openstackclient/compute/v2/server.py:436 msgid "either net-id or port-id should be specified but not both" msgstr "" -#: openstackclient/compute/v2/server.py:425 +#: openstackclient/compute/v2/server.py:458 msgid "can't create server with port specified since neutron not enabled" msgstr "" -#: openstackclient/compute/v2/server.py:490 +#: openstackclient/compute/v2/server.py:523 #, python-format msgid "Error creating server: %s" msgstr "" -#: openstackclient/compute/v2/server.py:492 +#: openstackclient/compute/v2/server.py:525 msgid "" "\n" "Error creating server" msgstr "" -#: openstackclient/compute/v2/server.py:514 +#: openstackclient/compute/v2/server.py:547 msgid "Name of new image (default is server name)" msgstr "" -#: openstackclient/compute/v2/server.py:519 +#: openstackclient/compute/v2/server.py:552 msgid "Wait for image create to complete" msgstr "" -#: openstackclient/compute/v2/server.py:549 +#: openstackclient/compute/v2/server.py:582 #, python-format msgid "Error creating server snapshot: %s" msgstr "" -#: openstackclient/compute/v2/server.py:551 +#: openstackclient/compute/v2/server.py:584 msgid "" "\n" "Error creating server snapshot" msgstr "" -#: openstackclient/compute/v2/server.py:573 +#: openstackclient/compute/v2/server.py:606 msgid "Server(s) to delete (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:578 +#: openstackclient/compute/v2/server.py:611 msgid "Wait for delete to complete" msgstr "" -#: openstackclient/compute/v2/server.py:597 +#: openstackclient/compute/v2/server.py:630 #, python-format msgid "Error deleting server: %s" msgstr "" -#: openstackclient/compute/v2/server.py:599 +#: openstackclient/compute/v2/server.py:632 msgid "" "\n" "Error deleting server" msgstr "" -#: openstackclient/compute/v2/server.py:614 +#: openstackclient/compute/v2/server.py:647 msgid "Only return instances that match the reservation" msgstr "" -#: openstackclient/compute/v2/server.py:619 +#: openstackclient/compute/v2/server.py:652 msgid "Regular expression to match IP addresses" msgstr "" -#: openstackclient/compute/v2/server.py:624 +#: openstackclient/compute/v2/server.py:657 msgid "Regular expression to match IPv6 addresses" msgstr "" -#: openstackclient/compute/v2/server.py:629 +#: openstackclient/compute/v2/server.py:662 msgid "Regular expression to match names" msgstr "" -#: openstackclient/compute/v2/server.py:634 +#: openstackclient/compute/v2/server.py:667 msgid "Regular expression to match instance name (admin only)" msgstr "" -#: openstackclient/compute/v2/server.py:640 +#: openstackclient/compute/v2/server.py:673 msgid "Search by server status" msgstr "" -#: openstackclient/compute/v2/server.py:645 +#: openstackclient/compute/v2/server.py:678 msgid "Search by flavor" msgstr "" -#: openstackclient/compute/v2/server.py:650 +#: openstackclient/compute/v2/server.py:683 msgid "Search by image" msgstr "" -#: openstackclient/compute/v2/server.py:655 +#: openstackclient/compute/v2/server.py:688 msgid "Search by hostname" msgstr "" -#: openstackclient/compute/v2/server.py:661 +#: openstackclient/compute/v2/server.py:694 msgid "Include all projects (admin only)" msgstr "" -#: openstackclient/compute/v2/server.py:777 +#: openstackclient/compute/v2/server.py:810 msgid "Target hostname" msgstr "" -#: openstackclient/compute/v2/server.py:785 +#: openstackclient/compute/v2/server.py:818 msgid "Perform a shared live migration (default)" msgstr "" -#: openstackclient/compute/v2/server.py:791 +#: openstackclient/compute/v2/server.py:824 msgid "Perform a block live migration" msgstr "" -#: openstackclient/compute/v2/server.py:798 +#: openstackclient/compute/v2/server.py:831 msgid "Allow disk over-commit on the destination host" msgstr "" -#: openstackclient/compute/v2/server.py:805 +#: openstackclient/compute/v2/server.py:838 msgid "Do not over-commit disk on the destination host (default)" msgstr "" -#: openstackclient/compute/v2/server.py:811 -#: openstackclient/compute/v2/server.py:1112 +#: openstackclient/compute/v2/server.py:844 +#: openstackclient/compute/v2/server.py:1145 msgid "Wait for resize to complete" msgstr "" -#: openstackclient/compute/v2/server.py:839 -#: openstackclient/compute/v2/server.py:1137 +#: openstackclient/compute/v2/server.py:872 +#: openstackclient/compute/v2/server.py:1170 msgid "Complete\n" msgstr "" -#: openstackclient/compute/v2/server.py:841 +#: openstackclient/compute/v2/server.py:874 msgid "" "\n" "Error migrating server" msgstr "" -#: openstackclient/compute/v2/server.py:888 +#: openstackclient/compute/v2/server.py:921 msgid "Perform a hard reboot" msgstr "" -#: openstackclient/compute/v2/server.py:896 +#: openstackclient/compute/v2/server.py:929 msgid "Perform a soft reboot" msgstr "" -#: openstackclient/compute/v2/server.py:901 +#: openstackclient/compute/v2/server.py:934 msgid "Wait for reboot to complete" msgstr "" -#: openstackclient/compute/v2/server.py:918 +#: openstackclient/compute/v2/server.py:951 msgid "" "\n" "Reboot complete\n" msgstr "" -#: openstackclient/compute/v2/server.py:920 +#: openstackclient/compute/v2/server.py:953 msgid "" "\n" "Error rebooting server\n" msgstr "" -#: openstackclient/compute/v2/server.py:940 +#: openstackclient/compute/v2/server.py:973 msgid "Recreate server from this image" msgstr "" -#: openstackclient/compute/v2/server.py:950 +#: openstackclient/compute/v2/server.py:983 msgid "Wait for rebuild to complete" msgstr "" -#: openstackclient/compute/v2/server.py:971 +#: openstackclient/compute/v2/server.py:1004 msgid "" "\n" "Complete\n" msgstr "" -#: openstackclient/compute/v2/server.py:973 +#: openstackclient/compute/v2/server.py:1006 msgid "" "\n" "Error rebuilding server" msgstr "" -#: openstackclient/compute/v2/server.py:990 +#: openstackclient/compute/v2/server.py:1023 msgid "Name or ID of server to use" msgstr "" -#: openstackclient/compute/v2/server.py:995 +#: openstackclient/compute/v2/server.py:1028 msgid "Name or ID of security group to remove from server" msgstr "" -#: openstackclient/compute/v2/server.py:1031 +#: openstackclient/compute/v2/server.py:1064 msgid "Volume to remove (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:1097 +#: openstackclient/compute/v2/server.py:1130 msgid "Resize server to specified flavor" msgstr "" -#: openstackclient/compute/v2/server.py:1102 +#: openstackclient/compute/v2/server.py:1135 msgid "Confirm server resize is complete" msgstr "" -#: openstackclient/compute/v2/server.py:1107 +#: openstackclient/compute/v2/server.py:1140 msgid "Restore server state before resize" msgstr "" -#: openstackclient/compute/v2/server.py:1139 +#: openstackclient/compute/v2/server.py:1172 msgid "" "\n" "Error resizing server" msgstr "" -#: openstackclient/compute/v2/server.py:1191 +#: openstackclient/compute/v2/server.py:1224 msgid "Set new root password (interactive only)" msgstr "" -#: openstackclient/compute/v2/server.py:1197 +#: openstackclient/compute/v2/server.py:1230 msgid "" "Property to add/change for this server (repeat option to set multiple " "properties)" msgstr "" -#: openstackclient/compute/v2/server.py:1221 +#: openstackclient/compute/v2/server.py:1254 msgid "New password: " msgstr "" -#: openstackclient/compute/v2/server.py:1222 +#: openstackclient/compute/v2/server.py:1255 msgid "Retype new password: " msgstr "" -#: openstackclient/compute/v2/server.py:1226 +#: openstackclient/compute/v2/server.py:1259 msgid "Passwords do not match, password unchanged" msgstr "" -#: openstackclient/compute/v2/server.py:1246 +#: openstackclient/compute/v2/server.py:1279 msgid "Display server diagnostics information" msgstr "" -#: openstackclient/compute/v2/server.py:1259 +#: openstackclient/compute/v2/server.py:1292 msgid "Error retrieving diagnostics data" msgstr "" -#: openstackclient/compute/v2/server.py:1282 +#: openstackclient/compute/v2/server.py:1315 msgid "Login name (ssh -l option)" msgstr "" -#: openstackclient/compute/v2/server.py:1293 +#: openstackclient/compute/v2/server.py:1327 msgid "Destination port (ssh -p option)" msgstr "" -#: openstackclient/compute/v2/server.py:1305 +#: openstackclient/compute/v2/server.py:1339 msgid "Private key file (ssh -i option)" msgstr "" -#: openstackclient/compute/v2/server.py:1316 +#: openstackclient/compute/v2/server.py:1350 msgid "Options in ssh_config(5) format (ssh -o option)" msgstr "" -#: openstackclient/compute/v2/server.py:1330 +#: openstackclient/compute/v2/server.py:1364 msgid "Use only IPv4 addresses" msgstr "" -#: openstackclient/compute/v2/server.py:1337 +#: openstackclient/compute/v2/server.py:1371 msgid "Use only IPv6 addresses" msgstr "" -#: openstackclient/compute/v2/server.py:1346 +#: openstackclient/compute/v2/server.py:1380 msgid "Use public IP address" msgstr "" -#: openstackclient/compute/v2/server.py:1354 +#: openstackclient/compute/v2/server.py:1388 msgid "Use private IP address" msgstr "" -#: openstackclient/compute/v2/server.py:1361 +#: openstackclient/compute/v2/server.py:1395 msgid "Use other IP address (public, private, etc)" msgstr "" -#: openstackclient/compute/v2/server.py:1538 +#: openstackclient/compute/v2/server.py:1560 msgid "Property key to remove from server (repeat to unset multiple values)" msgstr "" -#: openstackclient/identity/v2_0/catalog.py:73 +#: openstackclient/identity/v2_0/catalog.py:75 #: openstackclient/identity/v3/catalog.py:72 msgid "Service to display (type or name)" msgstr "" @@ -707,7 +707,7 @@ msgid "Disable user" msgstr "" #: openstackclient/identity/v2_0/user.py:77 -#: openstackclient/identity/v3/user.py:89 +#: openstackclient/identity/v3/user.py:90 msgid "Return existing user" msgstr "" diff --git a/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po b/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po index c17f8dab3d..bd8e72c6c8 100644 --- a/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po +++ b/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: python-openstackclient\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-07-23 06:08+0000\n" +"POT-Creation-Date: 2015-07-29 06:32+0000\n" "PO-Revision-Date: 2015-06-14 18:41+0000\n" "Last-Translator: openstackjenkins \n" "Language-Team: Chinese (Taiwan) (http://www.transifex.com/projects/p/python-" @@ -17,7 +17,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 1.3\n" +"Generated-By: Babel 2.0\n" msgid "" "\n" From 4b86324ee2f7c9a441e4ed2c7bac833641e85b47 Mon Sep 17 00:00:00 2001 From: Manuel Silveyra Date: Wed, 29 Jul 2015 12:57:13 -0700 Subject: [PATCH 0199/3095] New test for configuration show Creates a new common file for configuration tests. Change-Id: Id4c26759dfec5d508b762d0b54386f258a362971 --- functional/tests/common/__init__.py | 0 functional/tests/common/test_configuration.py | 23 +++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 functional/tests/common/__init__.py create mode 100644 functional/tests/common/test_configuration.py diff --git a/functional/tests/common/__init__.py b/functional/tests/common/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/functional/tests/common/test_configuration.py b/functional/tests/common/test_configuration.py new file mode 100644 index 0000000000..400fd0c4e1 --- /dev/null +++ b/functional/tests/common/test_configuration.py @@ -0,0 +1,23 @@ +# 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 functional.common import test + +BASIC_CONFIG_HEADERS = ['Field', 'Value'] + + +class ConfigurationTests(test.TestCase): + + def test_configuration_show(self): + raw_output = self.openstack('configuration show') + items = self.parse_listing(raw_output) + self.assert_table_structure(items, BASIC_CONFIG_HEADERS) From 149ce526143265d1d265761a9fb57bb66ee7a75a Mon Sep 17 00:00:00 2001 From: Radu Mateescu Date: Wed, 29 Jul 2015 14:50:50 -0500 Subject: [PATCH 0200/3095] Add functional tests for volume type list add tests for `os volume type list` Change-Id: Icd874b9cfac9376cc410041806fac64f1ff0c59d --- .../tests/volume/v1/test_volume_type.py | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 functional/tests/volume/v1/test_volume_type.py diff --git a/functional/tests/volume/v1/test_volume_type.py b/functional/tests/volume/v1/test_volume_type.py new file mode 100644 index 0000000000..8b4a5da86e --- /dev/null +++ b/functional/tests/volume/v1/test_volume_type.py @@ -0,0 +1,40 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import uuid + +from functional.common import test + + +class VolumeTypeTests(test.TestCase): + """Functional tests for volume type. """ + + NAME = uuid.uuid4().hex + HEADERS = ['"Name"'] + FIELDS = ['name'] + + @classmethod + def setUpClass(cls): + opts = cls.get_show_opts(cls.FIELDS) + raw_output = cls.openstack('volume type create ' + cls.NAME + opts) + expected = cls.NAME + '\n' + cls.assertOutput(expected, raw_output) + + @classmethod + def tearDownClass(cls): + raw_output = cls.openstack('volume type delete ' + cls.NAME) + cls.assertOutput('', raw_output) + + def test_volume_type_list(self): + opts = self.get_list_opts(self.HEADERS) + raw_output = self.openstack('volume type list' + opts) + self.assertIn(self.NAME, raw_output) From d7d2b92cfff1e2bcd836d4a52e99fe44321c5a4a Mon Sep 17 00:00:00 2001 From: Kenneth Chu Date: Thu, 30 Jul 2015 13:35:51 -0400 Subject: [PATCH 0201/3095] Added test for `volume type show` Change-Id: I05bbbdd8389f57d567b02391cbbc52448a9a2e7a --- functional/tests/volume/v1/test_volume_type.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/functional/tests/volume/v1/test_volume_type.py b/functional/tests/volume/v1/test_volume_type.py index 8b4a5da86e..c3568694ca 100644 --- a/functional/tests/volume/v1/test_volume_type.py +++ b/functional/tests/volume/v1/test_volume_type.py @@ -38,3 +38,8 @@ def test_volume_type_list(self): opts = self.get_list_opts(self.HEADERS) raw_output = self.openstack('volume type list' + opts) self.assertIn(self.NAME, raw_output) + + def test_volume_type_show(self): + opts = self.get_show_opts(self.FIELDS) + raw_output = self.openstack('volume type show ' + self.NAME + opts) + self.assertEqual(self.NAME + "\n", raw_output) From acb1c94750188abb2b039362e478a1f49f18baef Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Thu, 30 Jul 2015 11:27:55 -0700 Subject: [PATCH 0202/3095] Set OS_VOLUME_API_VERSION before running functional tests For v1 functional tests, set the env. var. Change-Id: I6c554932bdb8f99438d4f2ae855eb16c5bb3a357 --- functional/tests/volume/v1/common.py | 23 +++++++++++++++++++++++ functional/tests/volume/v1/test_qos.py | 4 ++-- functional/tests/volume/v1/test_volume.py | 4 ++-- 3 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 functional/tests/volume/v1/common.py diff --git a/functional/tests/volume/v1/common.py b/functional/tests/volume/v1/common.py new file mode 100644 index 0000000000..7d35ed5e6d --- /dev/null +++ b/functional/tests/volume/v1/common.py @@ -0,0 +1,23 @@ +# 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 os + +from functional.common import test + + +class BaseVolumeTests(test.TestCase): + """Base class for Volume functional tests. """ + + @classmethod + def setUpClass(cls): + os.environ['OS_VOLUME_API_VERSION'] = '1' diff --git a/functional/tests/volume/v1/test_qos.py b/functional/tests/volume/v1/test_qos.py index 122a4538ce..f4b2fec3a6 100644 --- a/functional/tests/volume/v1/test_qos.py +++ b/functional/tests/volume/v1/test_qos.py @@ -12,10 +12,10 @@ import uuid -from functional.common import test +from functional.tests.volume.v1 import common -class VolumeQosTests(test.TestCase): +class VolumeTests(common.BaseVolumeTests): """Functional tests for volume qos. """ NAME = uuid.uuid4().hex diff --git a/functional/tests/volume/v1/test_volume.py b/functional/tests/volume/v1/test_volume.py index a0b77c7d07..a5a6dca480 100644 --- a/functional/tests/volume/v1/test_volume.py +++ b/functional/tests/volume/v1/test_volume.py @@ -12,10 +12,10 @@ import uuid -from functional.common import test +from functional.tests.volume.v1 import common -class VolumeTests(test.TestCase): +class VolumeTests(common.BaseVolumeTests): """Functional tests for volume. """ NAME = uuid.uuid4().hex From 521b2cc5dc2575d41b1e3d4509c3d8bb35290e00 Mon Sep 17 00:00:00 2001 From: Jerry George Date: Thu, 30 Jul 2015 14:41:41 -0400 Subject: [PATCH 0203/3095] Minor Documentation changes for code samples Change-Id: I2d13fe2884d9eaebd112d342041e2e219bf6a240 --- HACKING.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/HACKING.rst b/HACKING.rst index 27c5e4851b..b7cc831cbb 100644 --- a/HACKING.rst +++ b/HACKING.rst @@ -83,14 +83,16 @@ commandline arguments, etc.) should be presumed to be encoded as utf-8. WRONG: + infile = open('testfile', 'r') mystring = infile.readline() myreturnstring = do_some_magic_with(mystring) outfile.write(myreturnstring) RIGHT: + infile = open('testfile', 'r') mystring = infile.readline() - mytext = s.decode('utf-8') + mytext = mystring.decode('utf-8') returntext = do_some_magic_with(mytext) returnstring = returntext.encode('utf-8') outfile.write(returnstring) From 4ebeb25b237b40028bd22b7a66ed3b8e2f17efe7 Mon Sep 17 00:00:00 2001 From: John Keenleyside Date: Thu, 30 Jul 2015 15:07:48 -0400 Subject: [PATCH 0204/3095] add new test for configuration show unmask Change-Id: Ib85bfa627f4ddee9e017f148c86a6d7d640bfba4 --- functional/tests/common/test_configuration.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/functional/tests/common/test_configuration.py b/functional/tests/common/test_configuration.py index 400fd0c4e1..94a624d2eb 100644 --- a/functional/tests/common/test_configuration.py +++ b/functional/tests/common/test_configuration.py @@ -10,6 +10,8 @@ # License for the specific language governing permissions and limitations # under the License. +import os + from functional.common import test BASIC_CONFIG_HEADERS = ['Field', 'Value'] @@ -21,3 +23,9 @@ def test_configuration_show(self): raw_output = self.openstack('configuration show') items = self.parse_listing(raw_output) self.assert_table_structure(items, BASIC_CONFIG_HEADERS) + + def test_configuration_show_unmask(self): + opts = "-f value -c auth.password" + raw_output = self.openstack('configuration show --unmask ' + opts) + passwd = os.environ['OS_PASSWORD'] + self.assertOutput(passwd + '\n', raw_output) From 235cd227bd3d7e47f4e0b5743c5cee4f8f70de08 Mon Sep 17 00:00:00 2001 From: Yunpeng Li Date: Thu, 30 Jul 2015 15:07:36 -0400 Subject: [PATCH 0205/3095] New test for configuration show --mask Change-Id: I56bb110d25b9f05cb1a706ecc5bbf5b6b154b240 --- functional/tests/common/test_configuration.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/functional/tests/common/test_configuration.py b/functional/tests/common/test_configuration.py index 94a624d2eb..f4a0209973 100644 --- a/functional/tests/common/test_configuration.py +++ b/functional/tests/common/test_configuration.py @@ -13,19 +13,26 @@ import os from functional.common import test +from openstackclient.common import configuration + BASIC_CONFIG_HEADERS = ['Field', 'Value'] class ConfigurationTests(test.TestCase): + opts = "-f value -c auth.password" + def test_configuration_show(self): raw_output = self.openstack('configuration show') items = self.parse_listing(raw_output) self.assert_table_structure(items, BASIC_CONFIG_HEADERS) def test_configuration_show_unmask(self): - opts = "-f value -c auth.password" - raw_output = self.openstack('configuration show --unmask ' + opts) + raw_output = self.openstack('configuration show --unmask ' + self.opts) passwd = os.environ['OS_PASSWORD'] self.assertOutput(passwd + '\n', raw_output) + + def test_configuration_show_mask(self): + raw_output = self.openstack('configuration show --mask ' + self.opts) + self.assertOutput(configuration.REDACTED + '\n', raw_output) From cfb99cb9b112140025b8d28beed55638774213aa Mon Sep 17 00:00:00 2001 From: Joe Wigglesworth Date: Thu, 30 Jul 2015 17:18:37 -0400 Subject: [PATCH 0206/3095] Removed unnecessary assignment of function result Joined lines together Change-Id: Iffe19c309369301137cfd52c8ea1aa988ce39f7e --- functional/tests/volume/v1/test_volume.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/functional/tests/volume/v1/test_volume.py b/functional/tests/volume/v1/test_volume.py index a0b77c7d07..ae89f00ca7 100644 --- a/functional/tests/volume/v1/test_volume.py +++ b/functional/tests/volume/v1/test_volume.py @@ -64,15 +64,13 @@ def test_volume_properties(self): self.assertEqual("c='d'\n", raw_output) def test_volume_set(self): - raw_output = self.openstack( - 'volume set --description RAMAC ' + self.NAME) + self.openstack('volume set --description RAMAC ' + self.NAME) opts = self.get_show_opts(["display_description", "display_name"]) raw_output = self.openstack('volume show ' + self.NAME + opts) self.assertEqual("RAMAC\n" + self.NAME + "\n", raw_output) def test_volume_set_size(self): - raw_output = self.openstack( - 'volume set --size 2 ' + self.NAME) + self.openstack('volume set --size 2 ' + self.NAME) opts = self.get_show_opts(["display_name", "size"]) raw_output = self.openstack('volume show ' + self.NAME + opts) self.assertEqual(self.NAME + "\n2\n", raw_output) From 701b59f651fe1ec88400f014165efa6b176f375c Mon Sep 17 00:00:00 2001 From: lin-hua-cheng Date: Fri, 31 Jul 2015 18:56:34 -0700 Subject: [PATCH 0207/3095] Add domain scoping in 'role assignment list' Add ability to set which domain the user, group or project belong to. Change-Id: Idf6c7f23ab96261a73682226fb10ce5f0133924e Closes-Bug: #1472909 --- doc/source/command-objects/role_assignment.rst | 18 ++++++++++++++++++ openstackclient/identity/v3/role_assignment.py | 6 ++++++ 2 files changed, 24 insertions(+) diff --git a/doc/source/command-objects/role_assignment.rst b/doc/source/command-objects/role_assignment.rst index cfb1079cf3..6bb24cb34c 100644 --- a/doc/source/command-objects/role_assignment.rst +++ b/doc/source/command-objects/role_assignment.rst @@ -15,9 +15,12 @@ List role assignments os role assignment list [--role ] [--user ] + [--user-domain ] [--group ] + [--group-domain ] [--domain ] [--project ] + [--project-domain ] [--effective] [--inherited] @@ -29,10 +32,20 @@ List role assignments User to filter (name or ID) +.. option:: --user-domain + + Domain the user belongs to (name or ID). + This can be used in case collisions between user names exist. + .. option:: --group Group to filter (name or ID) +.. option:: --group-domain + + Domain the group belongs to (name or ID). + This can be used in case collisions between group names exist. + .. option:: --domain Domain to filter (name or ID) @@ -41,6 +54,11 @@ List role assignments Project to filter (name or ID) +.. option:: --project-domain + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + .. option:: --effective Returns only effective role assignments (defaults to False) diff --git a/openstackclient/identity/v3/role_assignment.py b/openstackclient/identity/v3/role_assignment.py index 24e3a7f709..92168498f7 100644 --- a/openstackclient/identity/v3/role_assignment.py +++ b/openstackclient/identity/v3/role_assignment.py @@ -45,11 +45,13 @@ def get_parser(self, prog_name): metavar='', help='User to filter (name or ID)', ) + common.add_user_domain_option_to_parser(parser) user_or_group.add_argument( '--group', metavar='', help='Group to filter (name or ID)', ) + common.add_group_domain_option_to_parser(parser) domain_or_project = parser.add_mutually_exclusive_group() domain_or_project.add_argument( '--domain', @@ -61,6 +63,7 @@ def get_parser(self, prog_name): metavar='', help='Project to filter (name or ID)', ) + common.add_project_domain_option_to_parser(parser) return parser @@ -84,6 +87,7 @@ def take_action(self, parsed_args): user = common.find_user( identity_client, parsed_args.user, + parsed_args.user_domain, ) domain = None @@ -98,6 +102,7 @@ def take_action(self, parsed_args): project = common.find_project( identity_client, parsed_args.project, + parsed_args.project_domain, ) group = None @@ -105,6 +110,7 @@ def take_action(self, parsed_args): group = common.find_group( identity_client, parsed_args.group, + parsed_args.group_domain, ) effective = True if parsed_args.effective else False From cd98e063eb3bbf5e2e574f61f1062f611707321d Mon Sep 17 00:00:00 2001 From: Kelvin Lui Date: Thu, 30 Jul 2015 14:04:31 -0400 Subject: [PATCH 0208/3095] Introduce functional test for Identity Provider Identity Provider currently doesn't have test coverage. Change-Id: Iea2e705f9d2303f58516f08a7526135988032025 --- functional/tests/identity/v3/test_identity.py | 19 ++++++++++++++++++ functional/tests/identity/v3/test_idp.py | 20 +++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 functional/tests/identity/v3/test_idp.py diff --git a/functional/tests/identity/v3/test_identity.py b/functional/tests/identity/v3/test_identity.py index cd11e200af..bf3da167fa 100644 --- a/functional/tests/identity/v3/test_identity.py +++ b/functional/tests/identity/v3/test_identity.py @@ -42,6 +42,8 @@ class IdentityTests(test.TestCase): ENDPOINT_LIST_HEADERS = ['ID', 'Region', 'Service Name', 'Service Type', 'Enabled', 'Interface', 'URL'] + IDENTITY_PROVIDER_FIELDS = ['description', 'enabled', 'id', 'remote_ids'] + @classmethod def setUpClass(cls): if hasattr(super(IdentityTests, cls), 'setUpClass'): @@ -253,3 +255,20 @@ def _create_dummy_endpoint(self, interface='public', add_clean_up=True): self.openstack, 'endpoint delete %s' % endpoint['id']) return endpoint['id'] + + def _create_dummy_idp(self, add_clean_up=True): + identity_provider = data_utils.rand_name('IdentityProvider') + description = data_utils.rand_name('description') + raw_output = self.openstack( + 'identity provider create ' + ' %(name)s ' + '--description %(description)s ' + '--enable ' % {'name': identity_provider, + 'description': description}) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.IDENTITY_PROVIDER_FIELDS) + if add_clean_up: + self.addCleanup( + self.openstack, + 'identity provider delete %s' % identity_provider) + return identity_provider diff --git a/functional/tests/identity/v3/test_idp.py b/functional/tests/identity/v3/test_idp.py new file mode 100644 index 0000000000..6a07f158d0 --- /dev/null +++ b/functional/tests/identity/v3/test_idp.py @@ -0,0 +1,20 @@ +# 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 functional.tests.identity.v3 import test_identity + + +class IdentityProviderTests(test_identity.IdentityTests): + # Introduce functional test case for command 'Identity Provider' + + def test_idp_create(self): + self._create_dummy_idp() From d52ecfc05840e1c02f7cd4e8f4b64b5cbcdac51d Mon Sep 17 00:00:00 2001 From: JP Parkin Date: Thu, 30 Jul 2015 17:31:00 -0400 Subject: [PATCH 0209/3095] Added a new function test for volume type set This is a new test to validate the setting of one property for an existing volume type Change-Id: Ia9a7d86ce9b0d8df9d64ddc1df2d443843bba5ef --- functional/tests/volume/v1/test_volume_type.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/functional/tests/volume/v1/test_volume_type.py b/functional/tests/volume/v1/test_volume_type.py index c3568694ca..afdf10025f 100644 --- a/functional/tests/volume/v1/test_volume_type.py +++ b/functional/tests/volume/v1/test_volume_type.py @@ -12,10 +12,10 @@ import uuid -from functional.common import test +from functional.tests.volume.v1 import common -class VolumeTypeTests(test.TestCase): +class VolumeTypeTests(common.BaseVolumeTests): """Functional tests for volume type. """ NAME = uuid.uuid4().hex @@ -43,3 +43,14 @@ def test_volume_type_show(self): opts = self.get_show_opts(self.FIELDS) raw_output = self.openstack('volume type show ' + self.NAME + opts) self.assertEqual(self.NAME + "\n", raw_output) + + def test_volume_set_one_property(self): + props = "foo='bar'" + + raw_output = self.openstack('volume type set ' + self.NAME + + ' --property ' + props) + self.assertEqual('', raw_output) + + raw_output = self.openstack('volume type show -f value -c properties ' + + self.NAME) + self.assertEqual(props + '\n', raw_output) From eb4246fc94dfa895797d0de2ab5b7a2ce1d0c93c Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Tue, 4 Aug 2015 00:49:08 +0000 Subject: [PATCH 0210/3095] Updated from global requirements Change-Id: I4f5e05c8207e91f0da87b4bbd25f711d42016cbe --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 8abe2cb720..23edc37250 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -8,7 +8,7 @@ discover fixtures>=1.3.1 mock>=1.2 oslosphinx>=2.5.0 # Apache-2.0 -oslotest>=1.9.0 # Apache-2.0 +oslotest>=1.10.0 # Apache-2.0 requests-mock>=0.6.0 # Apache-2.0 sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 os-testr>=0.1.0 From f44590a59981a472089aef345b20a8d17d65737c Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Tue, 4 Aug 2015 06:26:16 +0000 Subject: [PATCH 0211/3095] Imported Translations from Transifex For more information about this automatic import see: https://wiki.openstack.org/wiki/Translations/Infrastructure Change-Id: If738066b8f940bdc99a98352dfaadbd5ee548794 --- .../locale/de/LC_MESSAGES/python-openstackclient.po | 4 ++-- .../locale/zh_TW/LC_MESSAGES/python-openstackclient.po | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po b/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po index fd2ad2fbaa..6c838a4a28 100644 --- a/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po +++ b/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po @@ -9,10 +9,10 @@ msgid "" msgstr "" "Project-Id-Version: python-openstackclient\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-07-29 06:32+0000\n" +"POT-Creation-Date: 2015-08-04 06:26+0000\n" "PO-Revision-Date: 2015-06-14 18:41+0000\n" "Last-Translator: openstackjenkins \n" -"Language-Team: German (http://www.transifex.com/projects/p/python-" +"Language-Team: German (http://www.transifex.com/openstack/python-" "openstackclient/language/de/)\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" "MIME-Version: 1.0\n" diff --git a/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po b/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po index bd8e72c6c8..0680a23ec9 100644 --- a/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po +++ b/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po @@ -8,10 +8,10 @@ msgid "" msgstr "" "Project-Id-Version: python-openstackclient\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-07-29 06:32+0000\n" +"POT-Creation-Date: 2015-08-04 06:26+0000\n" "PO-Revision-Date: 2015-06-14 18:41+0000\n" "Last-Translator: openstackjenkins \n" -"Language-Team: Chinese (Taiwan) (http://www.transifex.com/projects/p/python-" +"Language-Team: Chinese (Taiwan) (http://www.transifex.com/openstack/python-" "openstackclient/language/zh_TW/)\n" "Plural-Forms: nplurals=1; plural=0\n" "MIME-Version: 1.0\n" From 4850728b990536ad49775e67c849536d6320e111 Mon Sep 17 00:00:00 2001 From: Robert Francis Date: Thu, 30 Jul 2015 15:11:33 -0400 Subject: [PATCH 0212/3095] Add functional test for volume type create --property Combine test_volume_set_one_propety and test_volume_type_set_unset_properties. Change-Id: Ia8862a59161f6a15880b87ea305fb5bb15c31300 --- functional/tests/volume/v1/test_volume_type.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/functional/tests/volume/v1/test_volume_type.py b/functional/tests/volume/v1/test_volume_type.py index afdf10025f..dc8b1f4580 100644 --- a/functional/tests/volume/v1/test_volume_type.py +++ b/functional/tests/volume/v1/test_volume_type.py @@ -44,13 +44,17 @@ def test_volume_type_show(self): raw_output = self.openstack('volume type show ' + self.NAME + opts) self.assertEqual(self.NAME + "\n", raw_output) - def test_volume_set_one_property(self): - props = "foo='bar'" + def test_volume_type_set_unset_properties(self): + raw_output = self.openstack( + 'volume type set --property a=b --property c=d ' + self.NAME) + self.assertEqual("", raw_output) - raw_output = self.openstack('volume type set ' + self.NAME + - ' --property ' + props) - self.assertEqual('', raw_output) + opts = self.get_show_opts(["properties"]) + raw_output = self.openstack('volume type show ' + self.NAME + opts) + self.assertEqual("a='b', c='d'\n", raw_output) - raw_output = self.openstack('volume type show -f value -c properties ' + raw_output = self.openstack('volume type unset --property a ' + self.NAME) - self.assertEqual(props + '\n', raw_output) + self.assertEqual("", raw_output) + raw_output = self.openstack('volume type show ' + self.NAME + opts) + self.assertEqual("c='d'\n", raw_output) From fd617115857a5c666befbc05d21309de0af742fc Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Wed, 5 Aug 2015 09:55:29 -0600 Subject: [PATCH 0213/3095] Fix quota show when there is no project id If no id is in the response, quota show fails. Change-Id: I9905431b006404c9ba8453eba016cec9ebe19402 Closes-Bug: #1481803 --- openstackclient/common/quota.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index a40f6e4d84..4963eeac89 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -222,8 +222,7 @@ def take_action(self, parsed_args): info.pop(k) # Handle project ID special as it only appears in output - if info['id']: - info['project'] = info['id'] - info.pop('id') + if 'id' in info: + info['project'] = info.pop('id') return zip(*sorted(six.iteritems(info))) From 4097ec1d24f2735f9f2e2d084979d68261beb366 Mon Sep 17 00:00:00 2001 From: Samuel de Medeiros Queiroz Date: Thu, 6 Aug 2015 12:03:02 -0300 Subject: [PATCH 0214/3095] Fixes inherited role assignments CRUD calls The paremeter to Keystone Client was passed as 'inherited', when it should be 'os_inherit_extension_inherited'. Closes-Bug: #1482254 Change-Id: I1cb46add532223ef0b9620763b1047cc80e19ec0 --- openstackclient/identity/v3/role.py | 2 +- openstackclient/tests/identity/v3/test_role.py | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index 199b7dcaa1..2ca58a2734 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -103,7 +103,7 @@ def _process_identity_and_resource_options(parsed_args, parsed_args.project, parsed_args.group_domain, ).id - kwargs['inherited'] = parsed_args.inherited + kwargs['os_inherit_extension_inherited'] = parsed_args.inherited return kwargs diff --git a/openstackclient/tests/identity/v3/test_role.py b/openstackclient/tests/identity/v3/test_role.py index 4ff3b95f63..4a0ba0664e 100644 --- a/openstackclient/tests/identity/v3/test_role.py +++ b/openstackclient/tests/identity/v3/test_role.py @@ -123,7 +123,7 @@ def test_role_add_user_domain(self): kwargs = { 'user': identity_fakes.user_id, 'domain': identity_fakes.domain_id, - 'inherited': self._is_inheritance_testcase(), + 'os_inherit_extension_inherited': self._is_inheritance_testcase(), } # RoleManager.grant(role, user=, group=, domain=, project=) self.roles_mock.grant.assert_called_with( @@ -156,7 +156,7 @@ def test_role_add_user_project(self): kwargs = { 'user': identity_fakes.user_id, 'project': identity_fakes.project_id, - 'inherited': self._is_inheritance_testcase(), + 'os_inherit_extension_inherited': self._is_inheritance_testcase(), } # RoleManager.grant(role, user=, group=, domain=, project=) self.roles_mock.grant.assert_called_with( @@ -189,7 +189,7 @@ def test_role_add_group_domain(self): kwargs = { 'group': identity_fakes.group_id, 'domain': identity_fakes.domain_id, - 'inherited': self._is_inheritance_testcase(), + 'os_inherit_extension_inherited': self._is_inheritance_testcase(), } # RoleManager.grant(role, user=, group=, domain=, project=) self.roles_mock.grant.assert_called_with( @@ -222,7 +222,7 @@ def test_role_add_group_project(self): kwargs = { 'group': identity_fakes.group_id, 'project': identity_fakes.project_id, - 'inherited': self._is_inheritance_testcase(), + 'os_inherit_extension_inherited': self._is_inheritance_testcase(), } # RoleManager.grant(role, user=, group=, domain=, project=) self.roles_mock.grant.assert_called_with( @@ -598,7 +598,7 @@ def test_role_remove_user_domain(self): kwargs = { 'user': identity_fakes.user_id, 'domain': identity_fakes.domain_id, - 'inherited': self._is_inheritance_testcase(), + 'os_inherit_extension_inherited': self._is_inheritance_testcase(), } # RoleManager.revoke(role, user=, group=, domain=, project=) self.roles_mock.revoke.assert_called_with( @@ -631,7 +631,7 @@ def test_role_remove_user_project(self): kwargs = { 'user': identity_fakes.user_id, 'project': identity_fakes.project_id, - 'inherited': self._is_inheritance_testcase(), + 'os_inherit_extension_inherited': self._is_inheritance_testcase(), } # RoleManager.revoke(role, user=, group=, domain=, project=) self.roles_mock.revoke.assert_called_with( @@ -665,7 +665,7 @@ def test_role_remove_group_domain(self): kwargs = { 'group': identity_fakes.group_id, 'domain': identity_fakes.domain_id, - 'inherited': self._is_inheritance_testcase(), + 'os_inherit_extension_inherited': self._is_inheritance_testcase(), } # RoleManager.revoke(role, user=, group=, domain=, project=) self.roles_mock.revoke.assert_called_with( @@ -698,7 +698,7 @@ def test_role_remove_group_project(self): kwargs = { 'group': identity_fakes.group_id, 'project': identity_fakes.project_id, - 'inherited': self._is_inheritance_testcase(), + 'os_inherit_extension_inherited': self._is_inheritance_testcase(), } # RoleManager.revoke(role, user=, group=, domain=, project=) self.roles_mock.revoke.assert_called_with( From dc6fe04895287647f6bf5a977b398659edfda822 Mon Sep 17 00:00:00 2001 From: heha Date: Fri, 31 Jul 2015 18:26:33 +0800 Subject: [PATCH 0215/3095] Add list feature to volume v2 "volume list" is not in the v2. Co-Authored-By: Lin Hua Cheng implements bp: volume-v2 Change-Id: I9f4585202f5f9ec5f4c091278fc6c4036efb1290 --- doc/source/command-objects/volume.rst | 2 +- openstackclient/tests/volume/v2/fakes.py | 6 +- .../tests/volume/v2/test_volume.py | 214 ++++++++++++++++++ openstackclient/volume/v2/volume.py | 109 +++++++++ setup.cfg | 1 + 5 files changed, 330 insertions(+), 2 deletions(-) diff --git a/doc/source/command-objects/volume.rst b/doc/source/command-objects/volume.rst index 3baae5d853..556d1645f3 100644 --- a/doc/source/command-objects/volume.rst +++ b/doc/source/command-objects/volume.rst @@ -2,7 +2,7 @@ volume ====== -Volume v1 +Volume v1, v2 volume create ------------- diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py index a95bc94b14..0d8c2024c7 100644 --- a/openstackclient/tests/volume/v2/fakes.py +++ b/openstackclient/tests/volume/v2/fakes.py @@ -15,11 +15,15 @@ import copy import mock +from openstackclient.tests.compute.v2 import fakes as compute_fakes from openstackclient.tests import fakes from openstackclient.tests.identity.v2_0 import fakes as identity_fakes from openstackclient.tests.image.v2 import fakes as image_fakes from openstackclient.tests import utils +volume_attachment_server = copy.deepcopy(compute_fakes.SERVER) +volume_attachment_server['device'] = 'device' + volume_id = "ce26708d-a7f8-4b4b-9861-4a80256615a6" volume_name = "fake_volume" volume_description = "fake description" @@ -34,7 +38,7 @@ volume_metadata_str = "Alpha='a', Beta='b', Gamma='g'" volume_snapshot_id = 1 volume_availability_zone = "nova" -volume_attachments = ["fake_attachments"] +volume_attachments = [volume_attachment_server] VOLUME = { "id": volume_id, diff --git a/openstackclient/tests/volume/v2/test_volume.py b/openstackclient/tests/volume/v2/test_volume.py index 4fffefa4d7..348ae377de 100644 --- a/openstackclient/tests/volume/v2/test_volume.py +++ b/openstackclient/tests/volume/v2/test_volume.py @@ -495,6 +495,220 @@ def test_volume_create_image_name(self): self.assertEqual(datalist, data) +class TestVolumeList(TestVolume): + + def setUp(self): + super(TestVolumeList, self).setUp() + + self.volumes_mock.list.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.VOLUME), + loaded=True, + ), + ] + + self.users_mock.get.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.USER), + loaded=True, + ), + ] + + self.projects_mock.get.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.PROJECT), + loaded=True, + ), + ] + + # Get the command object to test + self.cmd = volume.ListVolume(self.app, None) + + def test_volume_list_no_options(self): + arglist = [] + verifylist = [ + ('long', False), + ('all_projects', False), + ('name', None), + ('status', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = [ + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Attached to', + ] + self.assertEqual(collist, columns) + + server = volume_fakes.volume_attachment_server['id'] + device = volume_fakes.volume_attachment_server['device'] + msg = 'Attached to %s on %s ' % (server, device) + datalist = (( + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_status, + volume_fakes.volume_size, + msg, + ), ) + self.assertEqual(datalist, tuple(data)) + + def test_volume_list_all_projects_option(self): + arglist = [ + '--all-projects', + ] + verifylist = [ + ('long', False), + ('all_projects', True), + ('name', None), + ('status', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = [ + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Attached to', + ] + self.assertEqual(collist, columns) + + server = volume_fakes.volume_attachment_server['id'] + device = volume_fakes.volume_attachment_server['device'] + msg = 'Attached to %s on %s ' % (server, device) + datalist = (( + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_status, + volume_fakes.volume_size, + msg, + ), ) + self.assertEqual(datalist, tuple(data)) + + def test_volume_list_name(self): + arglist = [ + '--name', volume_fakes.volume_name, + ] + verifylist = [ + ('long', False), + ('all_projects', False), + ('name', volume_fakes.volume_name), + ('status', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = ( + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Attached to', + ) + self.assertEqual(collist, tuple(columns)) + + server = volume_fakes.volume_attachment_server['id'] + device = volume_fakes.volume_attachment_server['device'] + msg = 'Attached to %s on %s ' % (server, device) + + datalist = (( + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_status, + volume_fakes.volume_size, + msg, + ), ) + self.assertEqual(datalist, tuple(data)) + + def test_volume_list_status(self): + arglist = [ + '--status', volume_fakes.volume_status, + ] + verifylist = [ + ('long', False), + ('all_projects', False), + ('name', None), + ('status', volume_fakes.volume_status), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = ( + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Attached to', + ) + self.assertEqual(collist, tuple(columns)) + + server = volume_fakes.volume_attachment_server['id'] + device = volume_fakes.volume_attachment_server['device'] + msg = 'Attached to %s on %s ' % (server, device) + datalist = (( + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_status, + volume_fakes.volume_size, + msg, + ), ) + self.assertEqual(datalist, tuple(data)) + + def test_volume_list_long(self): + arglist = [ + '--long', + ] + verifylist = [ + ('long', True), + ('all_projects', False), + ('name', None), + ('status', None), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = [ + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Type', + 'Bootable', + 'Attached to', + 'Properties', + ] + self.assertEqual(collist, columns) + + server = volume_fakes.volume_attachment_server['id'] + device = volume_fakes.volume_attachment_server['device'] + msg = 'Attached to %s on %s ' % (server, device) + datalist = (( + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_status, + volume_fakes.volume_size, + volume_fakes.volume_type, + '', + msg, + "Alpha='a', Beta='b', Gamma='g'", + ), ) + self.assertEqual(datalist, tuple(data)) + + class TestVolumeShow(TestVolume): def setUp(self): super(TestVolumeShow, self).setUp() diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index d4536f5149..fe4a3ff63f 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -14,9 +14,12 @@ """Volume V2 Volume action implementations""" +import copy import logging +import os from cliff import command +from cliff import lister from cliff import show import six @@ -189,6 +192,112 @@ def take_action(self, parsed_args): return +class ListVolume(lister.Lister): + """List volumes""" + + log = logging.getLogger(__name__ + '.ListVolume') + + def get_parser(self, prog_name): + parser = super(ListVolume, self).get_parser(prog_name) + parser.add_argument( + '--all-projects', + action='store_true', + default=bool(int(os.environ.get("ALL_PROJECTS", 0))), + help='Include all projects (admin only)', + ) + parser.add_argument( + '--long', + action='store_true', + default=False, + help='List additional fields in output', + ) + parser.add_argument( + '--name', + metavar='', + help='Filter results by name', + ) + parser.add_argument( + '--status', + metavar='', + help='Filter results by status', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + + volume_client = self.app.client_manager.volume + compute_client = self.app.client_manager.compute + + def _format_attach(attachments): + """Return a formatted string of a volume's attached instances + + :param volume: a volume.attachments field + :rtype: a string of formatted instances + """ + + msg = '' + for attachment in attachments: + server = attachment['id'] + if server in server_cache: + server = server_cache[server].name + device = attachment['device'] + msg += 'Attached to %s on %s ' % (server, device) + return msg + + if parsed_args.long: + columns = [ + 'ID', + 'Name', + 'Status', + 'Size', + 'Volume Type', + 'Bootable', + 'Attachments', + 'Metadata', + ] + column_headers = copy.deepcopy(columns) + column_headers[1] = 'Display Name' + column_headers[4] = 'Type' + column_headers[6] = 'Attached to' + column_headers[7] = 'Properties' + else: + columns = [ + 'ID', + 'Name', + 'Status', + 'Size', + 'Attachments', + ] + column_headers = copy.deepcopy(columns) + column_headers[1] = 'Display Name' + column_headers[4] = 'Attached to' + + # Cache the server list + server_cache = {} + try: + for s in compute_client.servers.list(): + server_cache[s.id] = s + except Exception: + # Just forget it if there's any trouble + pass + + search_opts = { + 'all_projects': parsed_args.all_projects, + 'display_name': parsed_args.name, + 'status': parsed_args.status, + } + + data = volume_client.volumes.list(search_opts=search_opts) + + return (column_headers, + (utils.get_item_properties( + s, columns, + formatters={'Metadata': utils.format_dict, + 'Attachments': _format_attach}, + ) for s in data)) + + class SetVolume(show.ShowOne): """Set volume properties""" diff --git a/setup.cfg b/setup.cfg index 6f7cad61f5..daa2b0df87 100644 --- a/setup.cfg +++ b/setup.cfg @@ -393,6 +393,7 @@ openstack.volume.v2 = snapshot_show = openstackclient.volume.v2.snapshot:ShowSnapshot snapshot_unset = openstackclient.volume.v2.snapshot:UnsetSnapshot + volume_list = openstackclient.volume.v2.volume:ListVolume volume_create = openstackclient.volume.v2.volume:CreateVolume volume_delete = openstackclient.volume.v2.volume:DeleteVolume volume_set = openstackclient.volume.v2.volume:SetVolume From 429ceef0c6c9deff521905c149e7600ffa5334ba Mon Sep 17 00:00:00 2001 From: heha Date: Fri, 24 Jul 2015 10:03:06 +0800 Subject: [PATCH 0216/3095] Add set feature to volume type v2 "volume type set" and "volume type unset" is not in the v2. Co-Authored-By: Lin Hua Cheng implements bp: volume-v2 Change-Id: Ia804787d76d2029726c030b43c61eac3b411f66a --- doc/source/command-objects/volume-type.rst | 18 ++- openstackclient/tests/volume/v2/test_type.py | 130 +++++++++++++++++++ openstackclient/volume/v2/volume_type.py | 94 ++++++++++++++ setup.cfg | 2 + 4 files changed, 240 insertions(+), 4 deletions(-) diff --git a/doc/source/command-objects/volume-type.rst b/doc/source/command-objects/volume-type.rst index 4d5651ac90..0edd742aee 100644 --- a/doc/source/command-objects/volume-type.rst +++ b/doc/source/command-objects/volume-type.rst @@ -77,17 +77,29 @@ List volume types volume type set --------------- -*Only supported for Volume API v1* - Set volume type properties .. program:: volume type set .. code:: bash os volume type set + [--name ] + [--description ] [--property [...] ] +.. option:: --name + + Set volume type name + + .. versionadded:: 2 + +.. option:: --description + + Set volume type description + + .. versionadded:: 2 + .. option:: --property Property to add or modify for this volume type (repeat option to set multiple properties) @@ -99,8 +111,6 @@ Set volume type properties volume type unset ----------------- -*Only supported for Volume API v1* - Unset volume type properties .. program:: volume type unset diff --git a/openstackclient/tests/volume/v2/test_type.py b/openstackclient/tests/volume/v2/test_type.py index c5b27fa512..9a07263b5f 100644 --- a/openstackclient/tests/volume/v2/test_type.py +++ b/openstackclient/tests/volume/v2/test_type.py @@ -19,6 +19,20 @@ from openstackclient.volume.v2 import volume_type +class FakeTypeResource(fakes.FakeResource): + + _keys = {'property': 'value'} + + def set_keys(self, args): + self._keys.update(args) + + def unset_keys(self, key): + self._keys.pop(key, None) + + def get_keys(self): + return self._keys + + class TestType(volume_fakes.TestVolume): def setUp(self): @@ -184,6 +198,122 @@ def test_type_show(self): self.assertEqual(volume_fakes.TYPE_FORMATTED_data, data) +class TestTypeSet(TestType): + + def setUp(self): + super(TestTypeSet, self).setUp() + + self.types_mock.get.return_value = FakeTypeResource( + None, + copy.deepcopy(volume_fakes.TYPE), + loaded=True, + ) + + # Get the command object to test + self.cmd = volume_type.SetVolumeType(self.app, None) + + def test_type_set_name(self): + new_name = 'new_name' + arglist = [ + '--name', new_name, + volume_fakes.type_id, + ] + verifylist = [ + ('name', new_name), + ('description', None), + ('property', None), + ('volume_type', volume_fakes.type_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'name': new_name, + } + self.types_mock.update.assert_called_with( + volume_fakes.type_id, + **kwargs + ) + + def test_type_set_description(self): + new_desc = 'new_desc' + arglist = [ + '--description', new_desc, + volume_fakes.type_id, + ] + verifylist = [ + ('name', None), + ('description', new_desc), + ('property', None), + ('volume_type', volume_fakes.type_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'description': new_desc, + } + self.types_mock.update.assert_called_with( + volume_fakes.type_id, + **kwargs + ) + + def test_type_set_property(self): + arglist = [ + '--property', 'myprop=myvalue', + volume_fakes.type_id, + ] + verifylist = [ + ('name', None), + ('description', None), + ('property', {'myprop': 'myvalue'}), + ('volume_type', volume_fakes.type_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + result = self.types_mock.get.return_value._keys + self.assertIn('myprop', result) + self.assertEqual('myvalue', result['myprop']) + + +class TestTypeUnset(TestType): + + def setUp(self): + super(TestTypeUnset, self).setUp() + + self.types_mock.get.return_value = FakeTypeResource( + None, + copy.deepcopy(volume_fakes.TYPE), + loaded=True, + ) + + self.cmd = volume_type.UnsetVolumeType(self.app, None) + + def test_type_unset(self): + arglist = [ + '--property', 'property', + volume_fakes.type_id, + ] + verifylist = [ + ('property', 'property'), + ('volume_type', volume_fakes.type_id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + result = self.types_mock.get.return_value._keys + + self.assertNotIn('property', result) + + class TestTypeDelete(TestType): def setUp(self): super(TestTypeDelete, self).setUp() diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index 7f9a1c4b6c..fb0342c555 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -143,6 +143,67 @@ def take_action(self, parsed_args): ) for s in data)) +class SetVolumeType(command.Command): + """Set volume type properties""" + + log = logging.getLogger(__name__ + '.SetVolumeType') + + def get_parser(self, prog_name): + parser = super(SetVolumeType, self).get_parser(prog_name) + parser.add_argument( + 'volume_type', + metavar='', + help='Volume type to modify (name or ID)', + ) + parser.add_argument( + '--name', + metavar='', + help='Set volume type name', + ) + parser.add_argument( + '--description', + metavar='', + help='Set volume type description', + ) + parser.add_argument( + '--property', + metavar='', + action=parseractions.KeyValueAction, + help='Property to add or modify for this volume type ' + '(repeat option to set multiple properties)', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + volume_client = self.app.client_manager.volume + volume_type = utils.find_resource( + volume_client.volume_types, parsed_args.volume_type) + + if (not parsed_args.name + and not parsed_args.description + and not parsed_args.property): + self.app.log.error("No changes requested\n") + return + + kwargs = {} + if parsed_args.name: + kwargs['name'] = parsed_args.name + if parsed_args.description: + kwargs['description'] = parsed_args.description + + if kwargs: + volume_client.volume_types.update( + volume_type.id, + **kwargs + ) + + if parsed_args.property: + volume_type.set_keys(parsed_args.property) + + return + + class ShowVolumeType(show.ShowOne): """Display volume type details""" @@ -165,3 +226,36 @@ def take_action(self, parsed_args): properties = utils.format_dict(volume_type._info.pop('extra_specs')) volume_type._info.update({'properties': properties}) return zip(*sorted(six.iteritems(volume_type._info))) + + +class UnsetVolumeType(command.Command): + """Unset volume type properties""" + + log = logging.getLogger(__name__ + '.UnsetVolumeType') + + def get_parser(self, prog_name): + parser = super(UnsetVolumeType, self).get_parser(prog_name) + parser.add_argument( + 'volume_type', + metavar='', + help='Volume type to modify (name or ID)', + ) + parser.add_argument( + '--property', + metavar='', + default=[], + required=True, + help='Property to remove from volume type ' + '(repeat option to remove multiple properties)', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + volume_client = self.app.client_manager.volume + volume_type = utils.find_resource( + volume_client.volume_types, + parsed_args.volume_type, + ) + volume_type.unset_keys(parsed_args.property) + return diff --git a/setup.cfg b/setup.cfg index c954073242..fd62407c0b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -398,7 +398,9 @@ openstack.volume.v2 = volume_type_create = openstackclient.volume.v2.volume_type:CreateVolumeType volume_type_delete = openstackclient.volume.v2.volume_type:DeleteVolumeType volume_type_list = openstackclient.volume.v2.volume_type:ListVolumeType + volume_type_set = openstackclient.volume.v2.volume_type:SetVolumeType volume_type_show = openstackclient.volume.v2.volume_type:ShowVolumeType + volume_type_unset = openstackclient.volume.v2.volume_type:UnsetVolumeType volume_qos_associate = openstackclient.volume.v2.qos_specs:AssociateQos volume_qos_create = openstackclient.volume.v2.qos_specs:CreateQos From c54d1241a08be79e3006558cab7b2f44c36e7dda Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Thu, 6 Aug 2015 22:01:55 -0700 Subject: [PATCH 0217/3095] Alphabetize setup.cfg noticed that volume type list was out of order. Change-Id: I965a201bc9c0fac67d01d5ee368149cac89095c9 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 6441a77f45..f2f4833b2f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -393,9 +393,9 @@ openstack.volume.v2 = snapshot_show = openstackclient.volume.v2.snapshot:ShowSnapshot snapshot_unset = openstackclient.volume.v2.snapshot:UnsetSnapshot - volume_list = openstackclient.volume.v2.volume:ListVolume volume_create = openstackclient.volume.v2.volume:CreateVolume volume_delete = openstackclient.volume.v2.volume:DeleteVolume + volume_list = openstackclient.volume.v2.volume:ListVolume volume_set = openstackclient.volume.v2.volume:SetVolume volume_show = openstackclient.volume.v2.volume:ShowVolume volume_unset = openstackclient.volume.v2.volume:UnsetVolume From 94a8805a9a96daa2ada2b38db1fac86e06525620 Mon Sep 17 00:00:00 2001 From: Samuel de Medeiros Queiroz Date: Wed, 17 Sep 2014 12:03:19 -0300 Subject: [PATCH 0218/3095] Inherited info/option when listing role assignment Adds inherited information when listing role assignments. In addition, it makes possible to list only inherited ones by adding --inherited option. Change-Id: Idf889603d584716da95e2c7b4880142fbd8291c4 Closes-Bug: 1370546 --- .../identity/v3/role_assignment.py | 14 +- openstackclient/tests/identity/v3/fakes.py | 14 ++ .../tests/identity/v3/test_role_assignment.py | 142 ++++++++++++++---- 3 files changed, 137 insertions(+), 33 deletions(-) diff --git a/openstackclient/identity/v3/role_assignment.py b/openstackclient/identity/v3/role_assignment.py index 92168498f7..169c6cb970 100644 --- a/openstackclient/identity/v3/role_assignment.py +++ b/openstackclient/identity/v3/role_assignment.py @@ -64,12 +64,12 @@ def get_parser(self, prog_name): help='Project to filter (name or ID)', ) common.add_project_domain_option_to_parser(parser) - + common.add_inherited_option_to_parser(parser) return parser def _as_tuple(self, assignment): return (assignment.role, assignment.user, assignment.group, - assignment.project, assignment.domain) + assignment.project, assignment.domain, assignment.inherited) def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) @@ -115,14 +115,17 @@ def take_action(self, parsed_args): effective = True if parsed_args.effective else False self.log.debug('take_action(%s)' % parsed_args) - columns = ('Role', 'User', 'Group', 'Project', 'Domain') + columns = ('Role', 'User', 'Group', 'Project', 'Domain', 'Inherited') + + inherited_to = 'projects' if parsed_args.inherited else None data = identity_client.role_assignments.list( domain=domain, user=user, group=group, project=project, role=role, - effective=effective) + effective=effective, + os_inherit_extension_inherited_to=inherited_to) data_parsed = [] for assignment in data: @@ -139,6 +142,9 @@ def take_action(self, parsed_args): assignment.domain = '' assignment.project = '' + inherited = scope.get('OS-INHERIT:inherited_to') == 'projects' + assignment.inherited = inherited + del assignment.scope if hasattr(assignment, 'user'): diff --git a/openstackclient/tests/identity/v3/fakes.py b/openstackclient/tests/identity/v3/fakes.py index ae7a684cf0..9c4de9cc0d 100644 --- a/openstackclient/tests/identity/v3/fakes.py +++ b/openstackclient/tests/identity/v3/fakes.py @@ -313,6 +313,13 @@ 'role': {'id': role_id}, } +ASSIGNMENT_WITH_PROJECT_ID_AND_USER_ID_INHERITED = { + 'scope': {'project': {'id': project_id}, + 'OS-INHERIT:inherited_to': 'projects'}, + 'user': {'id': user_id}, + 'role': {'id': role_id}, +} + ASSIGNMENT_WITH_PROJECT_ID_AND_GROUP_ID = { 'scope': {'project': {'id': project_id}}, 'group': {'id': group_id}, @@ -325,6 +332,13 @@ 'role': {'id': role_id}, } +ASSIGNMENT_WITH_DOMAIN_ID_AND_USER_ID_INHERITED = { + 'scope': {'domain': {'id': domain_id}, + 'OS-INHERIT:inherited_to': 'projects'}, + 'user': {'id': user_id}, + 'role': {'id': role_id}, +} + ASSIGNMENT_WITH_DOMAIN_ID_AND_GROUP_ID = { 'scope': {'domain': {'id': domain_id}}, 'group': {'id': group_id}, diff --git a/openstackclient/tests/identity/v3/test_role_assignment.py b/openstackclient/tests/identity/v3/test_role_assignment.py index b1ce8b297f..9817f53a4d 100644 --- a/openstackclient/tests/identity/v3/test_role_assignment.py +++ b/openstackclient/tests/identity/v3/test_role_assignment.py @@ -86,21 +86,24 @@ def test_role_assignment_list_no_filters(self): effective=False, role=None, user=None, - project=None) + project=None, + os_inherit_extension_inherited_to=None) - collist = ('Role', 'User', 'Group', 'Project', 'Domain') - self.assertEqual(collist, columns) + collist = ('Role', 'User', 'Group', 'Project', 'Domain', 'Inherited') + self.assertEqual(columns, collist) datalist = (( identity_fakes.role_id, identity_fakes.user_id, '', identity_fakes.project_id, - '' + '', + False ), (identity_fakes.role_id, '', identity_fakes.group_id, identity_fakes.project_id, - '' + '', + False ),) self.assertEqual(datalist, tuple(data)) @@ -131,6 +134,7 @@ def test_role_assignment_list_user(self): ('project', None), ('role', None), ('effective', False), + ('inherited', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -143,21 +147,24 @@ def test_role_assignment_list_user(self): group=None, project=None, role=None, - effective=False) + effective=False, + os_inherit_extension_inherited_to=None) - collist = ('Role', 'User', 'Group', 'Project', 'Domain') - self.assertEqual(collist, columns) + collist = ('Role', 'User', 'Group', 'Project', 'Domain', 'Inherited') + self.assertEqual(columns, collist) datalist = (( identity_fakes.role_id, identity_fakes.user_id, '', '', - identity_fakes.domain_id + identity_fakes.domain_id, + False ), (identity_fakes.role_id, identity_fakes.user_id, '', identity_fakes.project_id, - '' + '', + False ),) self.assertEqual(datalist, tuple(data)) @@ -188,6 +195,7 @@ def test_role_assignment_list_group(self): ('project', None), ('role', None), ('effective', False), + ('inherited', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -200,21 +208,24 @@ def test_role_assignment_list_group(self): effective=False, project=None, role=None, - user=None) + user=None, + os_inherit_extension_inherited_to=None) - collist = ('Role', 'User', 'Group', 'Project', 'Domain') - self.assertEqual(collist, columns) + collist = ('Role', 'User', 'Group', 'Project', 'Domain', 'Inherited') + self.assertEqual(columns, collist) datalist = (( identity_fakes.role_id, '', identity_fakes.group_id, '', - identity_fakes.domain_id + identity_fakes.domain_id, + False ), (identity_fakes.role_id, '', identity_fakes.group_id, identity_fakes.project_id, - '' + '', + False ),) self.assertEqual(datalist, tuple(data)) @@ -245,6 +256,7 @@ def test_role_assignment_list_domain(self): ('project', None), ('role', None), ('effective', False), + ('inherited', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -257,21 +269,24 @@ def test_role_assignment_list_domain(self): effective=False, project=None, role=None, - user=None) + user=None, + os_inherit_extension_inherited_to=None) - collist = ('Role', 'User', 'Group', 'Project', 'Domain') - self.assertEqual(collist, columns) + collist = ('Role', 'User', 'Group', 'Project', 'Domain', 'Inherited') + self.assertEqual(columns, collist) datalist = (( identity_fakes.role_id, identity_fakes.user_id, '', '', - identity_fakes.domain_id + identity_fakes.domain_id, + False ), (identity_fakes.role_id, '', identity_fakes.group_id, '', - identity_fakes.domain_id + identity_fakes.domain_id, + False ),) self.assertEqual(datalist, tuple(data)) @@ -302,6 +317,7 @@ def test_role_assignment_list_project(self): ('project', identity_fakes.project_name), ('role', None), ('effective', False), + ('inherited', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -314,21 +330,24 @@ def test_role_assignment_list_project(self): effective=False, project=self.projects_mock.get(), role=None, - user=None) + user=None, + os_inherit_extension_inherited_to=None) - collist = ('Role', 'User', 'Group', 'Project', 'Domain') - self.assertEqual(collist, columns) + collist = ('Role', 'User', 'Group', 'Project', 'Domain', 'Inherited') + self.assertEqual(columns, collist) datalist = (( identity_fakes.role_id, identity_fakes.user_id, '', identity_fakes.project_id, - '' + '', + False ), (identity_fakes.role_id, '', identity_fakes.group_id, identity_fakes.project_id, - '' + '', + False ),) self.assertEqual(datalist, tuple(data)) @@ -357,6 +376,7 @@ def test_role_assignment_list_effective(self): ('project', None), ('role', None), ('effective', True), + ('inherited', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -369,20 +389,84 @@ def test_role_assignment_list_effective(self): effective=True, project=None, role=None, - user=None) + user=None, + os_inherit_extension_inherited_to=None) - collist = ('Role', 'User', 'Group', 'Project', 'Domain') - self.assertEqual(collist, columns) + collist = ('Role', 'User', 'Group', 'Project', 'Domain', 'Inherited') + self.assertEqual(columns, collist) datalist = (( identity_fakes.role_id, identity_fakes.user_id, '', identity_fakes.project_id, - '' + '', + False + ), (identity_fakes.role_id, + identity_fakes.user_id, + '', + '', + identity_fakes.domain_id, + False + ),) + self.assertEqual(tuple(data), datalist) + + def test_role_assignment_list_inherited(self): + + self.role_assignments_mock.list.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy( + (identity_fakes. + ASSIGNMENT_WITH_PROJECT_ID_AND_USER_ID_INHERITED)), + loaded=True, + ), + fakes.FakeResource( + None, + copy.deepcopy( + (identity_fakes. + ASSIGNMENT_WITH_DOMAIN_ID_AND_USER_ID_INHERITED)), + loaded=True, + ), + ] + + arglist = ['--inherited'] + verifylist = [ + ('user', None), + ('group', None), + ('domain', None), + ('project', None), + ('role', None), + ('effective', False), + ('inherited', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + self.role_assignments_mock.list.assert_called_with( + domain=None, + group=None, + effective=False, + project=None, + role=None, + user=None, + os_inherit_extension_inherited_to='projects') + + collist = ('Role', 'User', 'Group', 'Project', 'Domain', 'Inherited') + self.assertEqual(columns, collist) + datalist = (( + identity_fakes.role_id, + identity_fakes.user_id, + '', + identity_fakes.project_id, + '', + True ), (identity_fakes.role_id, identity_fakes.user_id, '', '', identity_fakes.domain_id, + True ),) self.assertEqual(datalist, tuple(data)) From cb8590ffb3bf79a2754e913d3cc05b2b8318b8bc Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Thu, 6 Aug 2015 22:24:22 -0700 Subject: [PATCH 0219/3095] Rename command docs to remove underscores For consistency, use dashes instead of underscores. Change-Id: I2da19b56952a8a9d172793ae211bdc58ddff4146 --- .../{availability_zone.rst => availability-zone.rst} | 0 .../command-objects/{role_assignment.rst => role-assignment.rst} | 0 doc/source/command-objects/{volume_qos.rst => volume-qos.rst} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename doc/source/command-objects/{availability_zone.rst => availability-zone.rst} (100%) rename doc/source/command-objects/{role_assignment.rst => role-assignment.rst} (100%) rename doc/source/command-objects/{volume_qos.rst => volume-qos.rst} (100%) diff --git a/doc/source/command-objects/availability_zone.rst b/doc/source/command-objects/availability-zone.rst similarity index 100% rename from doc/source/command-objects/availability_zone.rst rename to doc/source/command-objects/availability-zone.rst diff --git a/doc/source/command-objects/role_assignment.rst b/doc/source/command-objects/role-assignment.rst similarity index 100% rename from doc/source/command-objects/role_assignment.rst rename to doc/source/command-objects/role-assignment.rst diff --git a/doc/source/command-objects/volume_qos.rst b/doc/source/command-objects/volume-qos.rst similarity index 100% rename from doc/source/command-objects/volume_qos.rst rename to doc/source/command-objects/volume-qos.rst From 51ae8c78b913a72826f8409e84155148986b7b09 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Thu, 6 Aug 2015 22:15:42 -0700 Subject: [PATCH 0220/3095] Add --inherited to the role docs commit Id72670be8640e5c6e2490a6ef849e9ec3493b1a9 forgot to update the docs for role.rst. Change-Id: I97c426ea9b290fc266b34cb0bf97de56cfd098de --- doc/source/command-objects/role.rst | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/doc/source/command-objects/role.rst b/doc/source/command-objects/role.rst index d26932b1db..9ba149ecbc 100644 --- a/doc/source/command-objects/role.rst +++ b/doc/source/command-objects/role.rst @@ -15,6 +15,7 @@ Add role to a user or group in a project or domain os role add --domain | --project [--project-domain ] --user [--user-domain ] | --group [--group-domain ] + --inherited .. option:: --domain @@ -58,6 +59,12 @@ Add role to a user or group in a project or domain .. versionadded:: 3 +.. option:: --inherited + + Specifies if the role grant is inheritable to the sub projects. + + .. versionadded:: 3 + .. describe:: Role to add to ``:`` (name or ID) @@ -110,6 +117,7 @@ List roles os role list --domain | --project [--project-domain ] --user [--user-domain ] | --group [--group-domain ] + --inherited .. option:: --domain @@ -156,6 +164,12 @@ List roles .. versionadded:: 3 +.. option:: --inherited + + Specifies if the role grant is inheritable to the sub projects. + + .. versionadded:: 3 + role remove ----------- @@ -167,6 +181,7 @@ Remove role from domain/project : user/group os role remove --domain | --project [--project-domain ] --user [--user-domain ] | --group [--group-domain ] + --inherited .. option:: --domain @@ -210,6 +225,12 @@ Remove role from domain/project : user/group .. versionadded:: 3 +.. option:: --inherited + + Specifies if the role grant is inheritable to the sub projects. + + .. versionadded:: 3 + .. describe:: Role to remove (name or ID) From d833d377cbd86b1f3cddb284c4ede24cbece3263 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Sat, 8 Aug 2015 01:07:07 -0700 Subject: [PATCH 0221/3095] Add developer documentation OSC was missing developer docs, these are critically helpful for new developers. Add sections related to running tests (tox, functional and with the debugger), and generating docs locally. implements bp: developer-documentation Change-Id: I428d89c3e5fc335864bc4f7843506043ec332fe5 --- doc/source/developing.rst | 89 +++++++++++++++++++++++++++++++++++++++ doc/source/index.rst | 10 ++++- 2 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 doc/source/developing.rst diff --git a/doc/source/developing.rst b/doc/source/developing.rst new file mode 100644 index 0000000000..f13241f24e --- /dev/null +++ b/doc/source/developing.rst @@ -0,0 +1,89 @@ +=============================== +Developing with OpenStackClient +=============================== + +Testing +------- + +Using ``tox`` +============= + +Before running tests, you should have ``tox`` installed and available in your +environment: + +.. code-block:: bash + + $ pip install tox + +To execute the full suite of tests maintained within OpenStackClient, run: + +.. code-block:: bash + + $ tox + +.. NOTE:: + + The first time you run ``tox``, it will take additional time to build + virtualenvs. You can later use the ``-r`` option with ``tox`` to rebuild + your virtualenv in a similar manner. + +To run tests for one or more specific test environments (for example, the most +common configuration of Python 2.7 and PEP-8), list the environments with the +``-e`` option, separated by spaces: + +.. code-block:: bash + + $ tox -e py27,pep8 + +See ``tox.ini`` for the full list of available test environments. + +Running functional tests +======================== + +OpenStackClient also maintains a set of functional tests that are optimally +designed to be run against OpenStack's gate. Optionally, a developer may +choose to run these tests against any OpenStack deployment, however depending +on the services available, results will vary. + +To run the entire suite of functional tests: + +.. code-block:: bash + + $ tox -e functional + +To run a specific functional test: + +.. code-block:: bash + + $ tox -e functional -- --regex functional.tests.compute.v2.test_server + +Running with PDB +================ + +Using PDB breakpoints with ``tox`` and ``testr`` normally doesn't work since +the tests fail with a `BdbQuit` exception rather than stopping at the +breakpoint. + +To run with PDB breakpoints during testing, use the `debug` ``tox`` environment +rather than ``py27``. Here's an example, passing the name of a test since +you'll normally only want to run the test that hits your breakpoint: + +.. code-block:: bash + + $ tox -e debug opentackclient.tests.identity.v3.test_group + +For reference, the `debug` ``tox`` environment implements the instructions +here: https://wiki.openstack.org/wiki/Testr#Debugging_.28pdb.29_Tests + + +Building the Documentation +-------------------------- + +The documentation is generated with Sphinx using the ``tox`` command. To +create HTML docs, run the following: + +.. code-block:: bash + + $ tox -e docs + +The resultant HTML will be the ``doc/build/html`` directory. diff --git a/doc/source/index.rst b/doc/source/index.rst index a3bc58e42a..c90b8e5296 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -6,7 +6,8 @@ OpenStackClient (aka OSC) is a command-line client for OpenStack that brings the command set for Compute, Identity, Image, Object Store and Volume APIs together in a single shell with a uniform command structure. -Contents: +User Documentation +------------------ .. toctree:: :maxdepth: 1 @@ -29,6 +30,13 @@ Getting Started * Read the source `on OpenStack's Git server`_ * Install OpenStackClient from `PyPi`_ or a `tarball`_ +Developer Documentation +----------------------- + +.. toctree:: + :maxdepth: 1 + + developing Project Goals ------------- From b8b8383867e1800192f51f248d4132e276792f8e Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Sat, 8 Aug 2015 01:47:09 -0700 Subject: [PATCH 0222/3095] Add release notes for 1.6.0 Change-Id: Ia96f9c31879215cb98096de327a1add308fe1a36 --- doc/source/releases.rst | 128 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/doc/source/releases.rst b/doc/source/releases.rst index 4af9761b99..5dd6771932 100644 --- a/doc/source/releases.rst +++ b/doc/source/releases.rst @@ -2,6 +2,134 @@ Release Notes ============= +1.6.0 (10 Aug 2015) +=================== + +* Added support for Volume v2 APIs + Blueprint `volume-v2 `_ + + * Backup + * Snapshot + * Volume + * Volume Type + * Volume QoS + +* Updated ``python-cliff`` provides improved UX + + * -h and --help revert to top level help when used in a subcommand + Bug `1444983 `_ + + * suggest commands if command is not recognized + Bug `1462192 `_ + +* Bad argument passed to inherited role assignments CRUD + Bug `1482254 `_ + +* quota show for a project blows up + Bug `1481803 `_ + +* Unscoped saml auth error message refers to invalid option os-auth-type + Bug `1477083 `_ + +* Need a command to show current configuration + Bug `1476729 `_ + +* type should be volume_type + Bug `1475958 `_ + +* openstack volume(v1) type missing show + Bug `1475879 `_ + +* property should be required argument in unset cmd + Bug `1475872 `_ + +* --or-show missing from some command docs + Bug `1475485 `_ + +* missing project-domain to prevent project name collisions for user v3 + Bug `1475357 `_ + +* Cannot manipulate group member by ID + Bug `1475127 `_ + +* glance client expects image ID only, should use base resource + Bug `1475001 `_ + +* trust roles display a trailing character upon show and create + Bug `1474707 `_ + +* catalog list fails in identity v2 + Bug `1474656 `_ + +* openstack flavor unset NoneType error when used without --proprty + Bug `1474237 `_ + +* TypeError: 'NoneType' object does not support item assignment with latest os-client-config + Bug `1473921 `_ + +* authentication fails when openstackclient prompts for a password + Bug `1473862 `_ + +* New mock release(1.1.0) broke unit/function tests + Bug `1473454 `_ + +* Cannot create keystone trust with python-openstackclient using trustor/trustee id + Bug `1473298 `_ + +* "role assignment list" fails if two users in different domains have the same name + Bug `1472909 `_ + +* openstack catalog list always returns publicURL + Bug `1472629 `_ + +* The network list --dhcp option is inconsistent + Bug `1472613 `_ + +* Add support for showing aggregates in an hypervisor's properties + Bug `1470875 `_ + +* Can't seem to be able to get Openstackclient/examples to work + Bug `1470272 `_ + +* openstack server ssh fails to see floating IP address + Bug `1469843 `_ + +* confused domain argument for network create v2 + Bug `1468988 `_ + +* small typo in network.rst + Bug `1468282 `_ + +* Add support for Cinder volume qos commands + Bug `1467967 `_ + +* mismatch option in server.rst + Bug `1466742 `_ + +* user create mismatch object name in doc + Bug `1466738 `_ + +* Existing image is updated when call image create + Bug `1461817 `_ + +* ERROR: openstack 'ArgumentParser' object has no attribute 'debug' + Bug `1459519 `_ + +* Add an --os-endpoint-type cli optional argument to be able to select endpoint interface type + Bug `1454392 `_ + +* API versions are ignored from OCC + Bug `1453229 `_ + +* Issues with OpenStackClient / Locale / OSX + Bug `1436898 `_ + +* Image sharing does not seem to be supported + Bug `1402420 `_ + +* rename requires files to standard names + Bug `1179008 `_ + 1.5.0 (16 Jun 2015) =================== From e23dd6de5854fcc8ff76fe1b51eb46162770d9cc Mon Sep 17 00:00:00 2001 From: Daisuke Fujita Date: Fri, 29 May 2015 20:39:24 +0900 Subject: [PATCH 0223/3095] Set up every time record log in file This will allow users to record logs of all their commands into a predefined log file, in clouds.yaml. The log should have a format similar to that of oslo.log. Change-Id: I1b334bf429d575fc25809c9706fc0b11116be3f1 Implements: blueprint every-time-record-log-in-file --- doc/source/configuration.rst | 71 +++++++++ doc/source/man/openstack.rst | 21 +++ openstackclient/common/context.py | 149 +++++++++++++++++++ openstackclient/shell.py | 55 +++++-- openstackclient/tests/common/test_context.py | 102 +++++++++++++ openstackclient/tests/test_shell.py | 96 +++++++++++- 6 files changed, 479 insertions(+), 15 deletions(-) create mode 100644 openstackclient/common/context.py create mode 100644 openstackclient/tests/common/test_context.py diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst index 563a7193ea..c770014091 100644 --- a/doc/source/configuration.rst +++ b/doc/source/configuration.rst @@ -143,3 +143,74 @@ Debugging You may find the :doc:`config show ` helpful to debug configuration issues. It will display your current configuration. + +Logging Settings +---------------- + +By setting `log_level` or `log_file` in the configuration +:file:`clouds.yaml`, a user may enable additional logging:: + + clouds: + devstack: + auth: + auth_url: http://192.168.122.10:35357/ + project_name: demo + username: demo + password: 0penstack + region_name: RegionOne + operation_log: + logging: TRUE + file: /tmp/openstackclient_demo.log + level: info + ds-admin: + auth: + auth_url: http://192.168.122.10:35357/ + project_name: admin + username: admin + password: 0penstack + region_name: RegionOne + log_file: /tmp/openstackclient_admin.log + log_level: debug + +:dfn:`log_file`: ```` + Full path to logging file. +:dfn:`log_level`: ``error`` | ``info`` | ``debug`` + If log level is not set, ``warning`` will be used. + +If log level is ``info``, the following information is recorded: + +* cloud name +* user name +* project name +* CLI start time (logging start time) +* CLI end time +* CLI arguments +* CLI return value +* and any ``info`` messages. + +If log level is ``debug``, the following information is recorded: + +* cloud name +* user name +* project name +* CLI start time (logging start time) +* CLI end time +* CLI arguments +* CLI return value +* API request header/body +* API response header/body +* and any ``debug`` messages. + +When a command is executed, these logs are saved every time. Recording the user +operations can help to identify resource changes and provide useful information +for troubleshooting. + +If saving the output of a single command use the `--log-file` option instead. + +* `--log-file ` + +The logging level for `--log-file` can be set by using following options. + +* `-v, --verbose` +* `-q, --quiet` +* `--debug` diff --git a/doc/source/man/openstack.rst b/doc/source/man/openstack.rst index 53bf36299b..3e47635e0f 100644 --- a/doc/source/man/openstack.rst +++ b/doc/source/man/openstack.rst @@ -123,6 +123,18 @@ OPTIONS :option:`--os-interface` Interface type. Valid options are `public`, `admin` and `internal`. +:option:`--log-file` + Specify a file to log output. Disabled by default. + +:option:`-v, --verbose` + Increase verbosity of output. Can be repeated. + +:option:`-q, --quiet` + suppress output except warnings and errors + +:option:`--debug` + show tracebacks on errors and set verbosity to debug + COMMANDS ======== @@ -240,6 +252,15 @@ When :option:`--os-token` and :option:`--os-url` are both present the :option:`--os-auth-url` and :option:`--os-username` are present ``password`` auth type is selected. +Logging Settings +---------------- + +:program:`openstack` can record the operation history by logging settings +in configuration file. Recording the user operation, it can identify the +change of the resource and it becomes useful information for troubleshooting. + +See :doc:`../configuration` about Logging Settings for more details. + NOTES ===== diff --git a/openstackclient/common/context.py b/openstackclient/common/context.py new file mode 100644 index 0000000000..b7b16e9441 --- /dev/null +++ b/openstackclient/common/context.py @@ -0,0 +1,149 @@ +# 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. +# + +"""Context and Formatter""" + +import logging + +_LOG_MESSAGE_FORMAT = ('%(asctime)s.%(msecs)03d %(process)d ' + '%(levelname)s %(name)s [%(clouds_name)s ' + '%(username)s %(project_name)s] %(message)s') +_LOG_DATE_FORMAT = '%Y-%m-%d %H:%M:%S' + + +def setup_handler_logging_level(handler_type, level): + """Setup of the handler for set the logging level + + :param handler_type: type of logging handler + :param level: logging level + :return: None + """ + # Set the handler logging level of FileHandler(--log-file) + # and StreamHandler + for h in logging.getLogger('').handlers: + if type(h) is handler_type: + h.setLevel(level) + + +def setup_logging(shell, cloud_config): + """Get one cloud configuration from configuration file and setup logging + + :param shell: instance of openstackclient shell + :param cloud_config: + instance of the cloud specified by --os-cloud + in the configuration file + :return: None + """ + + log_level = logging.WARNING + log_file = cloud_config.config.get('log_file', None) + if log_file: + # setup the logging level + get_log_level = cloud_config.config.get('log_level') + if get_log_level: + log_level = { + 'error': logging.ERROR, + 'info': logging.INFO, + 'debug': logging.DEBUG, + }.get(get_log_level, logging.WARNING) + + # setup the logging context + log_cont = _LogContext( + clouds_name=cloud_config.config.get('cloud'), + project_name=cloud_config.auth.get('project_name'), + username=cloud_config.auth.get('username'), + ) + # setup the logging handler + log_handler = _setup_handler_for_logging( + logging.FileHandler, + log_level, + file_name=log_file, + context=log_cont, + ) + if log_level == logging.DEBUG: + # DEBUG only. + # setup the operation_log + shell.enable_operation_logging = True + shell.operation_log.setLevel(logging.DEBUG) + shell.operation_log.addHandler(log_handler) + + +def _setup_handler_for_logging(handler_type, level, file_name, context): + """Setup of the handler + + Setup of the handler for addition of the logging handler, + changes of the logging format, change of the logging level, + + :param handler_type: type of logging handler + :param level: logging level + :param file_name: name of log-file + :param context: instance of _LogContext() + :return: logging handler + """ + + root_logger = logging.getLogger('') + handler = None + # Setup handler for FileHandler(--os-cloud) + handler = logging.FileHandler( + filename=file_name, + ) + formatter = _LogContextFormatter( + context=context, + fmt=_LOG_MESSAGE_FORMAT, + datefmt=_LOG_DATE_FORMAT, + ) + handler.setFormatter(formatter) + handler.setLevel(level) + + # If both `--log-file` and `--os-cloud` are specified, + # the log is output to each file. + root_logger.addHandler(handler) + + return handler + + +class _LogContext(object): + """Helper class to represent useful information about a logging context""" + + def __init__(self, clouds_name=None, project_name=None, username=None): + """Initialize _LogContext instance + + :param clouds_name: one of the cloud name in configuration file + :param project_name: the project name in cloud(clouds_name) + :param username: the user name in cloud(clouds_name) + """ + + self.clouds_name = clouds_name + self.project_name = project_name + self.username = username + + def to_dict(self): + return { + 'clouds_name': self.clouds_name, + 'project_name': self.project_name, + 'username': self.username + } + + +class _LogContextFormatter(logging.Formatter): + """Customize the logging format for logging handler""" + + def __init__(self, *args, **kwargs): + self.context = kwargs.pop('context', None) + logging.Formatter.__init__(self, *args, **kwargs) + + def format(self, record): + d = self.context.to_dict() + for k, v in d.items(): + setattr(record, k, v) + return logging.Formatter.format(self, record) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 319f10dec5..7cef51baea 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -30,6 +30,7 @@ import openstackclient from openstackclient.common import clientmanager from openstackclient.common import commandmanager +from openstackclient.common import context from openstackclient.common import exceptions as exc from openstackclient.common import timing from openstackclient.common import utils @@ -95,6 +96,10 @@ def __init__(self): self.client_manager = None + # Operation log + self.enable_operation_logging = False + self.command_options = None + def configure_logging(self): """Configure logging for the app @@ -107,24 +112,30 @@ def configure_logging(self): self.options.verbose_level = 3 super(OpenStackShell, self).configure_logging() - root_logger = logging.getLogger('') # Set logging to the requested level if self.options.verbose_level == 0: # --quiet - root_logger.setLevel(logging.ERROR) + log_level = logging.ERROR warnings.simplefilter("ignore") elif self.options.verbose_level == 1: # This is the default case, no --debug, --verbose or --quiet - root_logger.setLevel(logging.WARNING) + log_level = logging.WARNING warnings.simplefilter("ignore") elif self.options.verbose_level == 2: # One --verbose - root_logger.setLevel(logging.INFO) + log_level = logging.INFO warnings.simplefilter("once") elif self.options.verbose_level >= 3: # Two or more --verbose - root_logger.setLevel(logging.DEBUG) + log_level = logging.DEBUG + + # Set the handler logging level of FileHandler(--log-file) + # and StreamHandler + if self.options.log_file: + context.setup_handler_logging_level(logging.FileHandler, log_level) + + context.setup_handler_logging_level(logging.StreamHandler, log_level) # Requests logs some stuff at INFO that we don't want # unless we have DEBUG @@ -147,9 +158,17 @@ def configure_logging(self): stevedore_log.setLevel(logging.ERROR) iso8601_log.setLevel(logging.ERROR) + # Operation logging + self.operation_log = logging.getLogger("operation_log") + self.operation_log.setLevel(logging.ERROR) + self.operation_log.propagate = False + def run(self, argv): + ret_val = 1 + self.command_options = argv try: - return super(OpenStackShell, self).run(argv) + ret_val = super(OpenStackShell, self).run(argv) + return ret_val except Exception as e: if not logging.getLogger('').handlers: logging.basicConfig() @@ -157,7 +176,13 @@ def run(self, argv): self.log.error(traceback.format_exc(e)) else: self.log.error('Exception raised: ' + str(e)) - return 1 + if self.enable_operation_logging: + self.operation_log.error(traceback.format_exc(e)) + + return ret_val + + finally: + self.log.info("END return value: %s", ret_val) def build_option_parser(self, description, version): parser = super(OpenStackShell, self).build_option_parser( @@ -243,7 +268,6 @@ def initialize_app(self, argv): auth_type = 'token_endpoint' else: auth_type = 'osc_password' - self.log.debug("options: %s", self.options) project_id = getattr(self.options, 'project_id', None) project_name = getattr(self.options, 'project_name', None) @@ -266,14 +290,23 @@ def initialize_app(self, argv): # Ignore the default value of interface. Only if it is set later # will it be used. cc = cloud_config.OpenStackConfig( - override_defaults={'interface': None, - 'auth_type': auth_type, }) - self.log.debug("defaults: %s", cc.defaults) + override_defaults={ + 'interface': None, + 'auth_type': auth_type, + }, + ) self.cloud = cc.get_one_cloud( cloud=self.options.cloud, argparse=self.options, ) + + # Set up every time record log in file and logging start + context.setup_logging(self, self.cloud) + + self.log.info("START with options: %s", self.command_options) + self.log.debug("options: %s", self.options) + self.log.debug("defaults: %s", cc.defaults) self.log.debug("cloud cfg: %s", self.cloud.config) # Set up client TLS diff --git a/openstackclient/tests/common/test_context.py b/openstackclient/tests/common/test_context.py new file mode 100644 index 0000000000..145546a3c3 --- /dev/null +++ b/openstackclient/tests/common/test_context.py @@ -0,0 +1,102 @@ +# 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 logging +import mock +import os + +from openstackclient.common import context +from openstackclient.tests import utils + + +class TestContext(utils.TestCase): + + TEST_LOG_FILE = "/tmp/test_log_file" + + def setUp(self): + super(TestContext, self).setUp() + + def tearDown(self): + super(TestContext, self).tearDown() + if os.path.exists(self.TEST_LOG_FILE): + os.remove(self.TEST_LOG_FILE) + + def setup_handler_logging_level(self): + handler_type = logging.FileHandler + handler = logging.FileHandler(filename=self.TEST_LOG_FILE) + handler.setLevel(logging.ERROR) + logging.getLogger('').addHandler(handler) + context.setup_handler_logging_level(handler_type, logging.INFO) + self.log.info("test log") + ld = open(self.TEST_LOG_FILE) + line = ld.readlines() + ld.close() + if os.path.exists(self.TEST_LOG_FILE): + os.remove(self.TEST_LOG_FILE) + self.assertGreaterEqual(line.find("test log"), 0) + + @mock.patch("openstackclient.common.context._setup_handler_for_logging") + def test_setup_logging(self, setuph): + setuph.return_value = mock.MagicMock() + shell = mock.MagicMock() + cloud_config = mock.MagicMock() + cloud_config.auth = { + 'project_name': 'heart-o-gold', + 'username': 'zaphod' + } + cloud_config.config = { + 'log_level': 'debug', + 'log_file': self.TEST_LOG_FILE, + 'cloud': 'megadodo' + } + context.setup_logging(shell, cloud_config) + self.assertEqual(True, shell.enable_operation_logging) + + +class Test_LogContext(utils.TestCase): + def setUp(self): + super(Test_LogContext, self).setUp() + + def test_context(self): + ctx = context._LogContext() + self.assertTrue(ctx) + + def test_context_to_dict(self): + ctx = context._LogContext('cloudsName', 'projectName', 'userNmae') + ctx_dict = ctx.to_dict() + self.assertEqual('cloudsName', ctx_dict['clouds_name']) + self.assertEqual('projectName', ctx_dict['project_name']) + self.assertEqual('userNmae', ctx_dict['username']) + + +class Test_LogContextFormatter(utils.TestCase): + def setUp(self): + super(Test_LogContextFormatter, self).setUp() + self.ctx = context._LogContext('cloudsName', 'projectName', 'userNmae') + self.addfmt = "%(clouds_name)s %(project_name)s %(username)s" + + def test_contextrrormatter(self): + ctxfmt = context._LogContextFormatter() + self.assertTrue(ctxfmt) + + def test_context_format(self): + record = mock.MagicMock() + logging.Formatter.format = mock.MagicMock() + logging.Formatter.format.return_value = record + + ctxfmt = context._LogContextFormatter(context=self.ctx, + fmt=self.addfmt) + addctx = ctxfmt.format(record) + self.assertEqual('cloudsName', addctx.clouds_name) + self.assertEqual('projectName', addctx.project_name) + self.assertEqual('userNmae', addctx.username) diff --git a/openstackclient/tests/test_shell.py b/openstackclient/tests/test_shell.py index e2f0580b2c..5b84475329 100644 --- a/openstackclient/tests/test_shell.py +++ b/openstackclient/tests/test_shell.py @@ -77,6 +77,8 @@ 'username': 'zaphod', }, 'region_name': 'occ-cloud', + 'log_file': '/tmp/test_log_file', + 'log_level': 'debug', } } } @@ -621,9 +623,12 @@ def test_shell_args_cloud_no_vendor(self, config_mock): @mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file") @mock.patch("os_client_config.config.OpenStackConfig._load_config_file") - def test_shell_args_cloud_public(self, config_mock, public_mock): + @mock.patch("openstackclient.common.context._setup_handler_for_logging") + def test_shell_args_cloud_public(self, setup_handler, config_mock, + public_mock): config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_2)) public_mock.return_value = ('file.yaml', copy.deepcopy(PUBLIC_1)) + setup_handler.return_value = mock.MagicMock() _shell = make_shell() fake_execute( @@ -661,9 +666,12 @@ def test_shell_args_cloud_public(self, config_mock, public_mock): @mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file") @mock.patch("os_client_config.config.OpenStackConfig._load_config_file") - def test_shell_args_precedence(self, config_mock, vendor_mock): + @mock.patch("openstackclient.common.context._setup_handler_for_logging") + def test_shell_args_precedence(self, setup_handler, config_mock, + vendor_mock): config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_2)) vendor_mock.return_value = ('file.yaml', copy.deepcopy(PUBLIC_1)) + setup_handler.return_value = mock.MagicMock() _shell = make_shell() # Test command option overriding config file value @@ -715,9 +723,12 @@ def tearDown(self): @mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file") @mock.patch("os_client_config.config.OpenStackConfig._load_config_file") - def test_shell_args_precedence_1(self, config_mock, vendor_mock): + @mock.patch("openstackclient.common.context._setup_handler_for_logging") + def test_shell_args_precedence_1(self, setup_handler, config_mock, + vendor_mock): config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_2)) vendor_mock.return_value = ('file.yaml', copy.deepcopy(PUBLIC_1)) + setup_handler.return_value = mock.MagicMock() _shell = make_shell() # Test env var @@ -756,9 +767,12 @@ def test_shell_args_precedence_1(self, config_mock, vendor_mock): @mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file") @mock.patch("os_client_config.config.OpenStackConfig._load_config_file") - def test_shell_args_precedence_2(self, config_mock, vendor_mock): + @mock.patch("openstackclient.common.context._setup_handler_for_logging") + def test_shell_args_precedence_2(self, setup_handler, config_mock, + vendor_mock): config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_2)) vendor_mock.return_value = ('file.yaml', copy.deepcopy(PUBLIC_1)) + setup_handler.return_value = mock.MagicMock() _shell = make_shell() # Test command option overriding config file value @@ -796,3 +810,77 @@ def test_shell_args_precedence_2(self, config_mock, vendor_mock): 'krikkit', _shell.cloud.config['region_name'], ) + + +class TestShellCliLogging(TestShell): + def setUp(self): + super(TestShellCliLogging, self).setUp() + + def tearDown(self): + super(TestShellCliLogging, self).tearDown() + + @mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file") + @mock.patch("os_client_config.config.OpenStackConfig._load_config_file") + @mock.patch("openstackclient.common.context._setup_handler_for_logging") + def test_shell_args_precedence_1(self, setup_handler, config_mock, + vendor_mock): + config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_2)) + vendor_mock.return_value = ('file.yaml', copy.deepcopy(PUBLIC_1)) + setup_handler.return_value = mock.MagicMock() + _shell = make_shell() + + # These come from clouds.yaml + fake_execute( + _shell, + "--os-cloud megacloud list user", + ) + self.assertEqual( + 'megacloud', + _shell.cloud.name, + ) + + self.assertEqual( + '/tmp/test_log_file', + _shell.cloud.config['log_file'], + ) + self.assertEqual( + 'debug', + _shell.cloud.config['log_level'], + ) + + @mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file") + @mock.patch("os_client_config.config.OpenStackConfig._load_config_file") + def test_shell_args_precedence_2(self, config_mock, vendor_mock): + config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_1)) + vendor_mock.return_value = ('file.yaml', copy.deepcopy(PUBLIC_1)) + _shell = make_shell() + + # Test operation_log_file not set + fake_execute( + _shell, + "--os-cloud scc list user", + ) + self.assertEqual( + False, + _shell.enable_operation_logging, + ) + + @mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file") + @mock.patch("os_client_config.config.OpenStackConfig._load_config_file") + @mock.patch("openstackclient.common.context._setup_handler_for_logging") + def test_shell_args_precedence_3(self, setup_handler, config_mock, + vendor_mock): + config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_2)) + vendor_mock.return_value = ('file.yaml', copy.deepcopy(PUBLIC_1)) + setup_handler.return_value = mock.MagicMock() + _shell = make_shell() + + # Test enable_operation_logging set + fake_execute( + _shell, + "--os-cloud megacloud list user", + ) + self.assertEqual( + True, + _shell.enable_operation_logging, + ) From a82a418a0e385373ce87eafd487e5da258176f71 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Sat, 8 Aug 2015 16:20:58 -0700 Subject: [PATCH 0224/3095] Update plugin documentation Several projects have begun to make OSC plugins; we should list the status of the major projects, so users can know if they can expect support for that feature/project. Change-Id: Ib3c11c8f2b90663e37578a2714d438944eb1c6f6 --- doc/source/plugins.rst | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/doc/source/plugins.rst b/doc/source/plugins.rst index 17a508fed7..8102c979be 100644 --- a/doc/source/plugins.rst +++ b/doc/source/plugins.rst @@ -24,20 +24,21 @@ plugin. ============================= ====================================== project notes ============================= ====================================== -python-barbicanclient n/a -python-ceilometerclient n/a -python-congressclient using only OpenStackClient -python-designateclient n/a -python-heatclient plans on creating plugins +python-barbicanclient patch in progress (https://review.openstack.org/#/c/198732/) +python-ceilometerclient using argparse +python-congressclient using OpenStackClient +python-cueclient using OpenStackClient +python-designateclient uses cliff, but not OpenStackClient +python-heatclient patch in progress (https://review.openstack.org/#/c/195867/) python-ironicclient patch in progress (https://review.openstack.org/#/c/171672/) -python-magnumclient sent note on ML about creating a plugin -python-manilaclient n/a -python-mistralclient n/a -python-muranoclient n/a -python-saharaclient n/a -python-troveclient n/a +python-magnumclient using argparse +python-manilaclient using argparse +python-mistralclient using cliff, but not OpenStackClient +python-muranoclient using argparse +python-saharaclient using argparse +python-troveclient using argparse python-tuskarclient using OpenStackClient and their own shell -python-zaqarclient using only OpenStackClient +python-zaqarclient using OpenStackClient ============================= ====================================== Implementation From bc4e6c2bf0eb1e5f7ac949e4c00f1ddb5e5de685 Mon Sep 17 00:00:00 2001 From: Eric Brown Date: Sun, 9 Aug 2015 13:20:59 -0700 Subject: [PATCH 0225/3095] Remove non-existing hacking deviations from doc Commit Id38a1497019c7fe2d4ad8567f1c0c8d229951751 removed all of the hacking rules from the ignore list. However, the hacking doc still refers to those previouly ignored rules. Change-Id: I370be096d83cd5121d24bb96d8d5dc08a1c3c216 --- HACKING.rst | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/HACKING.rst b/HACKING.rst index b7cc831cbb..e6c8d07827 100644 --- a/HACKING.rst +++ b/HACKING.rst @@ -5,17 +5,6 @@ OpenStack Style Commandments http://docs.openstack.org/developer/hacking/ - Step 2: Read on -Deviations from Global Hacking ------------------------------- - -The following checks are specifically skipped in OpenStackClient:: - -* H305 - py2/py3 compatibility problems -* H307 - py2/py3 compatibility problems -* H402 - one line docstring ends in period -* H904 - backslash continuation line - - General ------- - thou shalt not violate causality in our time cone, or else From 043fc51c51a33dcc0b994a4753e2652f02cc6e69 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Mon, 10 Aug 2015 01:10:26 +0000 Subject: [PATCH 0226/3095] Updated from global requirements Change-Id: I9232c1a3069cdb29a174eb7d93f55069d2f68e54 --- requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index e06a04ad27..fe8b9b4a1a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ # The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -pbr<2.0,>=1.3 +pbr<2.0,>=1.4 six>=1.9.0 Babel>=1.3 @@ -13,8 +13,8 @@ oslo.i18n>=1.5.0 # Apache-2.0 oslo.utils>=1.9.0 # Apache-2.0 python-glanceclient>=0.18.0 python-keystoneclient>=1.6.0 -python-novaclient>=2.22.0 +python-novaclient>=2.26.0 python-cinderclient>=1.3.1 -python-neutronclient<3,>=2.3.11 +python-neutronclient<3,>=2.6.0 requests>=2.5.2 stevedore>=1.5.0 # Apache-2.0 From d0cb0d1192fe721314768f1b8e12d1d541d79320 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Mon, 10 Aug 2015 15:09:10 -0700 Subject: [PATCH 0227/3095] Skip functional test: test_server_up seems like test_server_up is causing intermittent issues in our CI, skipping for now, so we can proceed with merging code. Change-Id: Id41a78e703d7b416dfdc9a7bc95b19a8999192e3 Related-Bug: 1483422 --- functional/tests/compute/v2/test_server.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/functional/tests/compute/v2/test_server.py b/functional/tests/compute/v2/test_server.py index f38115800a..aa1c1201df 100644 --- a/functional/tests/compute/v2/test_server.py +++ b/functional/tests/compute/v2/test_server.py @@ -13,6 +13,8 @@ import time import uuid +import testtools + from functional.common import test @@ -80,6 +82,7 @@ def wait_for(self, desired, wait=120, interval=5, failures=['ERROR']): total_sleep += interval self.assertEqual(desired, status) + @testtools.skip('skipping due to bug 1483422') def test_server_up_test(self): self.wait_for("ACTIVE") # give it a little bit more time From 14bda1762b93975239c0db4669d94dd2be3d04c5 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Tue, 11 Aug 2015 01:38:56 +0000 Subject: [PATCH 0228/3095] Updated from global requirements Change-Id: I8fbb5213701f995bad7de243fd3a54fd147eb074 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index fe8b9b4a1a..b575125229 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ pbr<2.0,>=1.4 six>=1.9.0 Babel>=1.3 -cliff>=1.13.0 # Apache-2.0 +cliff>=1.14.0 # Apache-2.0 cliff-tablib>=1.0 os-client-config>=1.4.0 oslo.config>=1.11.0 # Apache-2.0 From 0cc1e5aa2b7ff7fa55e6083a397c07fc1cc744dd Mon Sep 17 00:00:00 2001 From: Jamie Lennox Date: Tue, 11 Aug 2015 15:34:15 +1000 Subject: [PATCH 0229/3095] Use correct domain to find project When adding a role to a group and project OSC is mistakenly using the group_domain to find the project which will fail if the group_domain != project_domain. Change-Id: I4c1bec9b3b183c755be121b91f40e026d707192b Closes-Bug: #1483520 --- openstackclient/identity/v3/role.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index 2ca58a2734..9243639e56 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -101,7 +101,7 @@ def _process_identity_and_resource_options(parsed_args, kwargs['project'] = common.find_project( identity_client_manager, parsed_args.project, - parsed_args.group_domain, + parsed_args.project_domain, ).id kwargs['os_inherit_extension_inherited'] = parsed_args.inherited return kwargs From b1ce0356f2e6fc4e36471394d0f871a3d1e6d2e5 Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Tue, 11 Aug 2015 14:40:53 -0600 Subject: [PATCH 0230/3095] Add tests for volume quota set Add some tests for volume quota set and get rid of TODO about using the value instead of the key to get the attribute. Change-Id: I57aa57951aeea65965966e63af922cda532d759d --- openstackclient/common/quota.py | 3 +- openstackclient/tests/common/test_quota.py | 45 ++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index e79fd7ed1d..c1ff3792d9 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -103,8 +103,7 @@ def take_action(self, parsed_args): volume_kwargs = {} for k, v in VOLUME_QUOTAS.items(): - # TODO(jiaxi): Should use k or v needs discuss - value = getattr(parsed_args, v, None) + value = getattr(parsed_args, k, None) if value is not None: if parsed_args.volume_type: k = k + '_%s' % parsed_args.volume_type diff --git a/openstackclient/tests/common/test_quota.py b/openstackclient/tests/common/test_quota.py index f0013e4863..b6ad1566c2 100644 --- a/openstackclient/tests/common/test_quota.py +++ b/openstackclient/tests/common/test_quota.py @@ -12,6 +12,8 @@ import copy +import mock + from openstackclient.common import quota from openstackclient.tests.compute.v2 import fakes as compute_fakes from openstackclient.tests import fakes @@ -38,6 +40,11 @@ def setUp(self): super(TestQuota, self).setUp() self.quotas_mock = self.app.client_manager.compute.quotas self.quotas_mock.reset_mock() + volume_mock = mock.Mock() + volume_mock.quotas = mock.Mock() + self.app.client_manager.volume = volume_mock + self.volume_quotas_mock = volume_mock.quotas + self.volume_quotas_mock.reset_mock() class TestQuotaSet(TestQuota): @@ -57,6 +64,18 @@ def setUp(self): loaded=True, ) + self.volume_quotas_mock.find.return_value = FakeQuotaResource( + None, + copy.deepcopy(compute_fakes.QUOTA), + loaded=True, + ) + + self.volume_quotas_mock.update.return_value = FakeQuotaResource( + None, + copy.deepcopy(compute_fakes.QUOTA), + loaded=True, + ) + self.cmd = quota.SetQuota(self.app, None) def test_quota_set(self): @@ -87,3 +106,29 @@ def test_quota_set(self): } self.quotas_mock.update.assert_called_with('project_test', **kwargs) + + def test_quota_set_volume(self): + arglist = [ + '--gigabytes', str(compute_fakes.floating_ip_num), + '--snapshots', str(compute_fakes.fix_ip_num), + '--volumes', str(compute_fakes.injected_file_num), + compute_fakes.project_name, + ] + verifylist = [ + ('gigabytes', compute_fakes.floating_ip_num), + ('snapshots', compute_fakes.fix_ip_num), + ('volumes', compute_fakes.injected_file_num), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + kwargs = { + 'gigabytes': compute_fakes.floating_ip_num, + 'snapshots': compute_fakes.fix_ip_num, + 'volumes': compute_fakes.injected_file_num, + } + + self.volume_quotas_mock.update.assert_called_with('project_test', + **kwargs) From e90849238cbf91e2308420a382606ec1d2821a8b Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 13 Aug 2015 02:19:22 +0000 Subject: [PATCH 0231/3095] Updated from global requirements Change-Id: Ie503f1ed6f6f91adfcf40f71053ba4fec02179f3 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index b575125229..a70f07d161 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ Babel>=1.3 cliff>=1.14.0 # Apache-2.0 cliff-tablib>=1.0 os-client-config>=1.4.0 -oslo.config>=1.11.0 # Apache-2.0 +oslo.config>=2.1.0 # Apache-2.0 oslo.i18n>=1.5.0 # Apache-2.0 oslo.utils>=1.9.0 # Apache-2.0 python-glanceclient>=0.18.0 From a6c8c8f7af1e99326c59780e8ea3259885c0e054 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 13 Aug 2015 09:31:11 +0000 Subject: [PATCH 0232/3095] Updated from global requirements Change-Id: Ie9049726dd4ac60238cb8b2658fa8510f4b11fde --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a70f07d161..ee72054c81 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ six>=1.9.0 Babel>=1.3 cliff>=1.14.0 # Apache-2.0 cliff-tablib>=1.0 -os-client-config>=1.4.0 +os-client-config!=1.6.2,>=1.4.0 oslo.config>=2.1.0 # Apache-2.0 oslo.i18n>=1.5.0 # Apache-2.0 oslo.utils>=1.9.0 # Apache-2.0 From ac5e289476911d8db13fce8411a062814a069d4b Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 13 Aug 2015 20:21:57 +0000 Subject: [PATCH 0233/3095] Updated from global requirements Change-Id: I302808700fe98add83069e7ed32ea329eb32cea1 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ee72054c81..20a22fea73 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ cliff-tablib>=1.0 os-client-config!=1.6.2,>=1.4.0 oslo.config>=2.1.0 # Apache-2.0 oslo.i18n>=1.5.0 # Apache-2.0 -oslo.utils>=1.9.0 # Apache-2.0 +oslo.utils>=2.0.0 # Apache-2.0 python-glanceclient>=0.18.0 python-keystoneclient>=1.6.0 python-novaclient>=2.26.0 From 9c3c33639139ab319ca6d00925b34f9293ca81af Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Sun, 9 Aug 2015 06:26:56 -0600 Subject: [PATCH 0234/3095] Move set warnings filters to logging module This is the first step in moving logging out of shell.py Change-Id: I3dcb4e17bb4687988ddf9b793ad1a308ef89b242 Implements: blueprint logging-migration --- openstackclient/common/context.py | 10 ++++++++++ openstackclient/shell.py | 5 +---- openstackclient/tests/common/test_context.py | 9 +++++++++ 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/openstackclient/common/context.py b/openstackclient/common/context.py index b7b16e9441..348c5509bd 100644 --- a/openstackclient/common/context.py +++ b/openstackclient/common/context.py @@ -14,6 +14,7 @@ """Context and Formatter""" import logging +import warnings _LOG_MESSAGE_FORMAT = ('%(asctime)s.%(msecs)03d %(process)d ' '%(levelname)s %(name)s [%(clouds_name)s ' @@ -21,6 +22,15 @@ _LOG_DATE_FORMAT = '%Y-%m-%d %H:%M:%S' +def set_warning_filter(log_level): + if log_level == logging.ERROR: + warnings.simplefilter("ignore") + elif log_level == logging.WARNING: + warnings.simplefilter("ignore") + elif log_level == logging.INFO: + warnings.simplefilter("once") + + def setup_handler_logging_level(handler_type, level): """Setup of the handler for set the logging level diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 7cef51baea..58eb9f5468 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -20,7 +20,6 @@ import logging import sys import traceback -import warnings from cliff import app from cliff import command @@ -117,18 +116,16 @@ def configure_logging(self): if self.options.verbose_level == 0: # --quiet log_level = logging.ERROR - warnings.simplefilter("ignore") elif self.options.verbose_level == 1: # This is the default case, no --debug, --verbose or --quiet log_level = logging.WARNING - warnings.simplefilter("ignore") elif self.options.verbose_level == 2: # One --verbose log_level = logging.INFO - warnings.simplefilter("once") elif self.options.verbose_level >= 3: # Two or more --verbose log_level = logging.DEBUG + context.set_warning_filter(log_level) # Set the handler logging level of FileHandler(--log-file) # and StreamHandler diff --git a/openstackclient/tests/common/test_context.py b/openstackclient/tests/common/test_context.py index 145546a3c3..8e1bcf8381 100644 --- a/openstackclient/tests/common/test_context.py +++ b/openstackclient/tests/common/test_context.py @@ -62,6 +62,15 @@ def test_setup_logging(self, setuph): context.setup_logging(shell, cloud_config) self.assertEqual(True, shell.enable_operation_logging) + @mock.patch('warnings.simplefilter') + def test_set_warning_filter(self, simplefilter): + context.set_warning_filter(logging.ERROR) + simplefilter.assert_called_with("ignore") + context.set_warning_filter(logging.WARNING) + simplefilter.assert_called_with("ignore") + context.set_warning_filter(logging.INFO) + simplefilter.assert_called_with("once") + class Test_LogContext(utils.TestCase): def setUp(self): From ca9965c3282d028da52ec465d5024f16fb54ba04 Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Sun, 9 Aug 2015 07:01:18 -0600 Subject: [PATCH 0235/3095] Move options to log level out of shell.py Move the conversion of command line options to log level out of shell.py. Change-Id: I86cb45a85cd63927aa1c87c1eed27542981df659 Implements: blueprint logging-migration --- openstackclient/common/context.py | 16 ++++++++++++++++ openstackclient/shell.py | 13 +------------ openstackclient/tests/common/test_context.py | 11 +++++++++++ 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/openstackclient/common/context.py b/openstackclient/common/context.py index 348c5509bd..70c8594398 100644 --- a/openstackclient/common/context.py +++ b/openstackclient/common/context.py @@ -22,6 +22,22 @@ _LOG_DATE_FORMAT = '%Y-%m-%d %H:%M:%S' +def log_level_from_options(options): + # if --debug, --quiet or --verbose is not specified, + # the default logging level is warning + log_level = logging.WARNING + if options.verbose_level == 0: + # --quiet + log_level = logging.ERROR + elif options.verbose_level == 2: + # One --verbose + log_level = logging.INFO + elif options.verbose_level >= 3: + # Two or more --verbose + log_level = logging.DEBUG + return log_level + + def set_warning_filter(log_level): if log_level == logging.ERROR: warnings.simplefilter("ignore") diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 58eb9f5468..6ba19d194f 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -113,18 +113,7 @@ def configure_logging(self): super(OpenStackShell, self).configure_logging() # Set logging to the requested level - if self.options.verbose_level == 0: - # --quiet - log_level = logging.ERROR - elif self.options.verbose_level == 1: - # This is the default case, no --debug, --verbose or --quiet - log_level = logging.WARNING - elif self.options.verbose_level == 2: - # One --verbose - log_level = logging.INFO - elif self.options.verbose_level >= 3: - # Two or more --verbose - log_level = logging.DEBUG + log_level = context.log_level_from_options(self.options) context.set_warning_filter(log_level) # Set the handler logging level of FileHandler(--log-file) diff --git a/openstackclient/tests/common/test_context.py b/openstackclient/tests/common/test_context.py index 8e1bcf8381..63c2420516 100644 --- a/openstackclient/tests/common/test_context.py +++ b/openstackclient/tests/common/test_context.py @@ -62,6 +62,17 @@ def test_setup_logging(self, setuph): context.setup_logging(shell, cloud_config) self.assertEqual(True, shell.enable_operation_logging) + def test_log_level_from_options(self): + opts = mock.Mock() + opts.verbose_level = 0 + self.assertEqual(logging.ERROR, context.log_level_from_options(opts)) + opts.verbose_level = 1 + self.assertEqual(logging.WARNING, context.log_level_from_options(opts)) + opts.verbose_level = 2 + self.assertEqual(logging.INFO, context.log_level_from_options(opts)) + opts.verbose_level = 3 + self.assertEqual(logging.DEBUG, context.log_level_from_options(opts)) + @mock.patch('warnings.simplefilter') def test_set_warning_filter(self, simplefilter): context.set_warning_filter(logging.ERROR) From d828429d6aeb73976d3a2e422477bee2f4b13b64 Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Sun, 9 Aug 2015 07:11:18 -0600 Subject: [PATCH 0236/3095] Extract log level from configuration file Extract log_level from configuration file if the level was not overridden by the command line option. The default command line option is 1 and there is no command line option to set the verbose_level to 1, so if it is 1, it has not be set. Change-Id: I1be04367c72f83c1181f92ca4c2c83165b66995c Implements: blueprint logging-migration --- openstackclient/common/context.py | 39 +++++++++++++++----- openstackclient/tests/common/test_context.py | 24 ++++++++++++ 2 files changed, 53 insertions(+), 10 deletions(-) diff --git a/openstackclient/common/context.py b/openstackclient/common/context.py index 70c8594398..4c04e661ff 100644 --- a/openstackclient/common/context.py +++ b/openstackclient/common/context.py @@ -38,6 +38,32 @@ def log_level_from_options(options): return log_level +def log_level_from_config(config): + # Check the command line option + verbose_level = config.get('verbose_level') + if config.get('debug', False): + verbose_level = 3 + if verbose_level == 0: + verbose_level = 'error' + elif verbose_level == 1: + # If a command line option has not been specified, check the + # configuration file + verbose_level = config.get('log_level', 'warning') + elif verbose_level == 2: + verbose_level = 'info' + else: + verbose_level = 'debug' + + log_level = { + 'critical': logging.CRITICAL, + 'error': logging.ERROR, + 'warning': logging.WARNING, + 'info': logging.INFO, + 'debug': logging.DEBUG, + }.get(verbose_level, logging.WARNING) + return log_level + + def set_warning_filter(log_level): if log_level == logging.ERROR: warnings.simplefilter("ignore") @@ -71,18 +97,11 @@ def setup_logging(shell, cloud_config): :return: None """ - log_level = logging.WARNING + log_level = log_level_from_config(cloud_config.config) + set_warning_filter(log_level) + log_file = cloud_config.config.get('log_file', None) if log_file: - # setup the logging level - get_log_level = cloud_config.config.get('log_level') - if get_log_level: - log_level = { - 'error': logging.ERROR, - 'info': logging.INFO, - 'debug': logging.DEBUG, - }.get(get_log_level, logging.WARNING) - # setup the logging context log_cont = _LogContext( clouds_name=cloud_config.config.get('cloud'), diff --git a/openstackclient/tests/common/test_context.py b/openstackclient/tests/common/test_context.py index 63c2420516..38a4d833bc 100644 --- a/openstackclient/tests/common/test_context.py +++ b/openstackclient/tests/common/test_context.py @@ -73,6 +73,30 @@ def test_log_level_from_options(self): opts.verbose_level = 3 self.assertEqual(logging.DEBUG, context.log_level_from_options(opts)) + def test_log_level_from_config(self): + cfg = {'verbose_level': 0} + self.assertEqual(logging.ERROR, context.log_level_from_config(cfg)) + cfg = {'verbose_level': 1} + self.assertEqual(logging.WARNING, context.log_level_from_config(cfg)) + cfg = {'verbose_level': 2} + self.assertEqual(logging.INFO, context.log_level_from_config(cfg)) + cfg = {'verbose_level': 3} + self.assertEqual(logging.DEBUG, context.log_level_from_config(cfg)) + cfg = {'verbose_level': 1, 'log_level': 'critical'} + self.assertEqual(logging.CRITICAL, context.log_level_from_config(cfg)) + cfg = {'verbose_level': 1, 'log_level': 'error'} + self.assertEqual(logging.ERROR, context.log_level_from_config(cfg)) + cfg = {'verbose_level': 1, 'log_level': 'warning'} + self.assertEqual(logging.WARNING, context.log_level_from_config(cfg)) + cfg = {'verbose_level': 1, 'log_level': 'info'} + self.assertEqual(logging.INFO, context.log_level_from_config(cfg)) + cfg = {'verbose_level': 1, 'log_level': 'debug'} + self.assertEqual(logging.DEBUG, context.log_level_from_config(cfg)) + cfg = {'verbose_level': 1, 'log_level': 'bogus'} + self.assertEqual(logging.WARNING, context.log_level_from_config(cfg)) + cfg = {'verbose_level': 1, 'log_level': 'info', 'debug': True} + self.assertEqual(logging.DEBUG, context.log_level_from_config(cfg)) + @mock.patch('warnings.simplefilter') def test_set_warning_filter(self, simplefilter): context.set_warning_filter(logging.ERROR) From 6c46355734f7a7278b92645e6a465b6e38096daf Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Sun, 9 Aug 2015 07:31:34 -0600 Subject: [PATCH 0237/3095] Optimize log formatting There is no way to change the configuration variables we want printed in log messages, so format them in the constructor. This will save us from overridding the format method and a couple cpu cycles every log message. This change also moves the _LOG* variables into the formatter since they aren't really globally needed. Change-Id: I706e9db7da3daec20332f9d1533fe665f2739dea Implements: blueprint logging-migration --- openstackclient/common/context.py | 90 ++++++++------------ openstackclient/tests/common/test_context.py | 62 ++++++-------- 2 files changed, 60 insertions(+), 92 deletions(-) diff --git a/openstackclient/common/context.py b/openstackclient/common/context.py index 4c04e661ff..570919392c 100644 --- a/openstackclient/common/context.py +++ b/openstackclient/common/context.py @@ -16,11 +16,6 @@ import logging import warnings -_LOG_MESSAGE_FORMAT = ('%(asctime)s.%(msecs)03d %(process)d ' - '%(levelname)s %(name)s [%(clouds_name)s ' - '%(username)s %(project_name)s] %(message)s') -_LOG_DATE_FORMAT = '%Y-%m-%d %H:%M:%S' - def log_level_from_options(options): # if --debug, --quiet or --verbose is not specified, @@ -73,6 +68,37 @@ def set_warning_filter(log_level): warnings.simplefilter("once") +class _FileFormatter(logging.Formatter): + """Customize the logging format for logging handler""" + _LOG_MESSAGE_BEGIN = ( + '%(asctime)s.%(msecs)03d %(process)d %(levelname)s %(name)s ') + _LOG_MESSAGE_CONTEXT = '[%(cloud)s %(username)s %(project)s] ' + _LOG_MESSAGE_END = '%(message)s' + _LOG_DATE_FORMAT = '%Y-%m-%d %H:%M:%S' + + def __init__(self, options=None, config=None, **kwargs): + context = {} + if options: + context = { + 'cloud': getattr(options, 'cloud', ''), + 'project': getattr(options, 'os_project_name', ''), + 'username': getattr(options, 'username', ''), + } + elif config: + context = { + 'cloud': config.config.get('cloud', ''), + 'project': config.auth.get('project_name', ''), + 'username': config.auth.get('username', ''), + } + if context: + self.fmt = (self._LOG_MESSAGE_BEGIN + + (self._LOG_MESSAGE_CONTEXT % context) + + self._LOG_MESSAGE_END) + else: + self.fmt = self._LOG_MESSAGE_BEGIN + self._LOG_MESSAGE_END + logging.Formatter.__init__(self, self.fmt, self._LOG_DATE_FORMAT) + + def setup_handler_logging_level(handler_type, level): """Setup of the handler for set the logging level @@ -103,17 +129,13 @@ def setup_logging(shell, cloud_config): log_file = cloud_config.config.get('log_file', None) if log_file: # setup the logging context - log_cont = _LogContext( - clouds_name=cloud_config.config.get('cloud'), - project_name=cloud_config.auth.get('project_name'), - username=cloud_config.auth.get('username'), - ) + formatter = _FileFormatter(config=cloud_config) # setup the logging handler log_handler = _setup_handler_for_logging( logging.FileHandler, log_level, file_name=log_file, - context=log_cont, + formatter=formatter, ) if log_level == logging.DEBUG: # DEBUG only. @@ -123,7 +145,7 @@ def setup_logging(shell, cloud_config): shell.operation_log.addHandler(log_handler) -def _setup_handler_for_logging(handler_type, level, file_name, context): +def _setup_handler_for_logging(handler_type, level, file_name, formatter): """Setup of the handler Setup of the handler for addition of the logging handler, @@ -132,7 +154,7 @@ def _setup_handler_for_logging(handler_type, level, file_name, context): :param handler_type: type of logging handler :param level: logging level :param file_name: name of log-file - :param context: instance of _LogContext() + :param formatter: instance of logging.Formatter :return: logging handler """ @@ -142,11 +164,6 @@ def _setup_handler_for_logging(handler_type, level, file_name, context): handler = logging.FileHandler( filename=file_name, ) - formatter = _LogContextFormatter( - context=context, - fmt=_LOG_MESSAGE_FORMAT, - datefmt=_LOG_DATE_FORMAT, - ) handler.setFormatter(formatter) handler.setLevel(level) @@ -155,40 +172,3 @@ def _setup_handler_for_logging(handler_type, level, file_name, context): root_logger.addHandler(handler) return handler - - -class _LogContext(object): - """Helper class to represent useful information about a logging context""" - - def __init__(self, clouds_name=None, project_name=None, username=None): - """Initialize _LogContext instance - - :param clouds_name: one of the cloud name in configuration file - :param project_name: the project name in cloud(clouds_name) - :param username: the user name in cloud(clouds_name) - """ - - self.clouds_name = clouds_name - self.project_name = project_name - self.username = username - - def to_dict(self): - return { - 'clouds_name': self.clouds_name, - 'project_name': self.project_name, - 'username': self.username - } - - -class _LogContextFormatter(logging.Formatter): - """Customize the logging format for logging handler""" - - def __init__(self, *args, **kwargs): - self.context = kwargs.pop('context', None) - logging.Formatter.__init__(self, *args, **kwargs) - - def format(self, record): - d = self.context.to_dict() - for k, v in d.items(): - setattr(record, k, v) - return logging.Formatter.format(self, record) diff --git a/openstackclient/tests/common/test_context.py b/openstackclient/tests/common/test_context.py index 38a4d833bc..cc213b13f4 100644 --- a/openstackclient/tests/common/test_context.py +++ b/openstackclient/tests/common/test_context.py @@ -107,40 +107,28 @@ def test_set_warning_filter(self, simplefilter): simplefilter.assert_called_with("once") -class Test_LogContext(utils.TestCase): - def setUp(self): - super(Test_LogContext, self).setUp() - - def test_context(self): - ctx = context._LogContext() - self.assertTrue(ctx) - - def test_context_to_dict(self): - ctx = context._LogContext('cloudsName', 'projectName', 'userNmae') - ctx_dict = ctx.to_dict() - self.assertEqual('cloudsName', ctx_dict['clouds_name']) - self.assertEqual('projectName', ctx_dict['project_name']) - self.assertEqual('userNmae', ctx_dict['username']) - - -class Test_LogContextFormatter(utils.TestCase): - def setUp(self): - super(Test_LogContextFormatter, self).setUp() - self.ctx = context._LogContext('cloudsName', 'projectName', 'userNmae') - self.addfmt = "%(clouds_name)s %(project_name)s %(username)s" - - def test_contextrrormatter(self): - ctxfmt = context._LogContextFormatter() - self.assertTrue(ctxfmt) - - def test_context_format(self): - record = mock.MagicMock() - logging.Formatter.format = mock.MagicMock() - logging.Formatter.format.return_value = record - - ctxfmt = context._LogContextFormatter(context=self.ctx, - fmt=self.addfmt) - addctx = ctxfmt.format(record) - self.assertEqual('cloudsName', addctx.clouds_name) - self.assertEqual('projectName', addctx.project_name) - self.assertEqual('userNmae', addctx.username) +class TestFileFormatter(utils.TestCase): + def test_nothing(self): + formatter = context._FileFormatter() + self.assertEqual(('%(asctime)s.%(msecs)03d %(process)d %(levelname)s ' + '%(name)s %(message)s'), formatter.fmt) + + def test_options(self): + class Opts(object): + cloud = 'cloudy' + os_project_name = 'projecty' + username = 'usernamey' + options = Opts() + formatter = context._FileFormatter(options=options) + self.assertEqual(('%(asctime)s.%(msecs)03d %(process)d %(levelname)s ' + '%(name)s [cloudy usernamey projecty] %(message)s'), + formatter.fmt) + + def test_config(self): + config = mock.Mock() + config.config = {'cloud': 'cloudy'} + config.auth = {'project_name': 'projecty', 'username': 'usernamey'} + formatter = context._FileFormatter(config=config) + self.assertEqual(('%(asctime)s.%(msecs)03d %(process)d %(levelname)s ' + '%(name)s [cloudy usernamey projecty] %(message)s'), + formatter.fmt) From 0f0d66f3f101cbc1108af579aa0f375b0d383375 Mon Sep 17 00:00:00 2001 From: Major Hayden Date: Wed, 12 Aug 2015 16:37:03 -0400 Subject: [PATCH 0238/3095] Running 'limits show' returns nothing Running limits show without --absolute or --rate returns nothing and the user is left to figure out what they need to provide to get the correct data back. This patch prints an error and help output by making at least one of the arguments required. Change-Id: I576cf8ec0e05524ee67d46c48b56da8d44258667 --- openstackclient/common/limits.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/common/limits.py b/openstackclient/common/limits.py index 4abcf169a3..c738f15092 100644 --- a/openstackclient/common/limits.py +++ b/openstackclient/common/limits.py @@ -31,7 +31,7 @@ class ShowLimits(lister.Lister): def get_parser(self, prog_name): parser = super(ShowLimits, self).get_parser(prog_name) - type_group = parser.add_mutually_exclusive_group() + type_group = parser.add_mutually_exclusive_group(required=True) type_group.add_argument( "--absolute", dest="is_absolute", From 00eebaf5bc5ff7a383a8ed085098abc24c8a2e74 Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Tue, 18 Aug 2015 10:09:46 -0600 Subject: [PATCH 0239/3095] Override the debug default and help text Cliff sets the default debug value to False and this makes it impossible to override debug with OCC. If we set the default to None, we can override debug in clouds.yaml. Also, OSC changes the meaning of --debug, so modify the help text. Change-Id: I5e6680b2286cd7f55afe4b083fae5f8a4a9567a2 Closes-Bug: #1483378 --- openstackclient/shell.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index a8b5ac4c88..566a7cd9f0 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -79,6 +79,10 @@ def __init__(self): help.HelpCommand.auth_required = False complete.CompleteCommand.auth_required = False + # Slight change to the meaning of --debug + self.DEFAULT_DEBUG_VALUE = None + self.DEFAULT_DEBUG_HELP = 'Set debug logging and traceback on errors.' + super(OpenStackShell, self).__init__( description=__doc__.strip(), version=openstackclient.__version__, From 0f837df839ee91e822bc2d8e8c76e3fb013933e2 Mon Sep 17 00:00:00 2001 From: Asha Saravanamohan Date: Tue, 18 Aug 2015 15:03:02 -0400 Subject: [PATCH 0240/3095] Added note to install openstackclient Change-Id: Idcd9ef4e7a10ebbd8b68e7320680f503dfc166a9 Closes-Bug: #1483384 --- doc/source/developing.rst | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/doc/source/developing.rst b/doc/source/developing.rst index f13241f24e..cd15f9fbaf 100644 --- a/doc/source/developing.rst +++ b/doc/source/developing.rst @@ -87,3 +87,20 @@ create HTML docs, run the following: $ tox -e docs The resultant HTML will be the ``doc/build/html`` directory. + +Testing new code +---------------- + +If a developer wants to test new code (feature, command or option) that +they have written, OpenStackClient may be installed from source by running +the following commands in the base directory of the project: + +.. code-block:: bash + + $ python setup.py develop + +or + +.. code-block:: bash + + $ pip install -e From 1004e06cee75829f9db6bb496e6ef49984d827fe Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Wed, 19 Aug 2015 11:01:26 -0600 Subject: [PATCH 0241/3095] Update the plugin docs for designate Change-Id: I43cee0670728ec15de461be55ffb0504a216de77 --- doc/source/plugins.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/plugins.rst b/doc/source/plugins.rst index 8102c979be..469742dc88 100644 --- a/doc/source/plugins.rst +++ b/doc/source/plugins.rst @@ -28,7 +28,7 @@ python-barbicanclient patch in progress (https://review.openstack.org/# python-ceilometerclient using argparse python-congressclient using OpenStackClient python-cueclient using OpenStackClient -python-designateclient uses cliff, but not OpenStackClient +python-designateclient patch in progress (https://review.openstack.org/#/c/133676/) python-heatclient patch in progress (https://review.openstack.org/#/c/195867/) python-ironicclient patch in progress (https://review.openstack.org/#/c/171672/) python-magnumclient using argparse From 1966663cedf02ddd41b3c213987f21ce0a73f401 Mon Sep 17 00:00:00 2001 From: Kelvin Lui Date: Wed, 19 Aug 2015 16:45:13 -0400 Subject: [PATCH 0242/3095] Adds documentation on weekly meeting Change-Id: Ia51f76323800aa5397a0d8a307c1cfbdaf90ab24 --- doc/source/developing.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/doc/source/developing.rst b/doc/source/developing.rst index f13241f24e..5810ddeb8c 100644 --- a/doc/source/developing.rst +++ b/doc/source/developing.rst @@ -2,6 +2,16 @@ Developing with OpenStackClient =============================== +Communication +------------- + +Meetings +========= +The OpenStackClient team meets regularly on every Thursday. For details +please refer to the `wiki`_. + +.. _`wiki`: https://wiki.openstack.org/wiki/Meetings/OpenStackClient + Testing ------- From 8fb19bc2a93c6cf6fa71c809dd47c6123a523b04 Mon Sep 17 00:00:00 2001 From: Kelvin Lui Date: Wed, 5 Aug 2015 15:47:08 -0400 Subject: [PATCH 0243/3095] additional functional tests for identity providers add tests for: * delete * set * list * show Change-Id: Ibe34f28d7ae77d139a6e0edf4fe04215c371c9a8 --- functional/tests/identity/v3/test_identity.py | 1 + functional/tests/identity/v3/test_idp.py | 34 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/functional/tests/identity/v3/test_identity.py b/functional/tests/identity/v3/test_identity.py index bf3da167fa..4c3ff7dab7 100644 --- a/functional/tests/identity/v3/test_identity.py +++ b/functional/tests/identity/v3/test_identity.py @@ -43,6 +43,7 @@ class IdentityTests(test.TestCase): 'Enabled', 'Interface', 'URL'] IDENTITY_PROVIDER_FIELDS = ['description', 'enabled', 'id', 'remote_ids'] + IDENTITY_PROVIDER_LIST_HEADERS = ['ID', 'Enabled', 'Description'] @classmethod def setUpClass(cls): diff --git a/functional/tests/identity/v3/test_idp.py b/functional/tests/identity/v3/test_idp.py index 6a07f158d0..3d6739d7f4 100644 --- a/functional/tests/identity/v3/test_idp.py +++ b/functional/tests/identity/v3/test_idp.py @@ -11,6 +11,7 @@ # under the License. from functional.tests.identity.v3 import test_identity +from tempest_lib.common.utils import data_utils class IdentityProviderTests(test_identity.IdentityTests): @@ -18,3 +19,36 @@ class IdentityProviderTests(test_identity.IdentityTests): def test_idp_create(self): self._create_dummy_idp() + + def test_idp_delete(self): + identity_provider = self._create_dummy_idp(add_clean_up=False) + raw_output = self.openstack('identity provider delete %s' + % identity_provider) + self.assertEqual(0, len(raw_output)) + + def test_idp_show(self): + identity_provider = self._create_dummy_idp(add_clean_up=True) + raw_output = self.openstack('identity provider show %s' + % identity_provider) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.IDENTITY_PROVIDER_FIELDS) + + def test_idp_list(self): + self._create_dummy_idp(add_clean_up=True) + raw_output = self.openstack('identity provider list') + items = self.parse_listing(raw_output) + self.assert_table_structure(items, self.IDENTITY_PROVIDER_LIST_HEADERS) + + def test_idp_set(self): + identity_provider = self._create_dummy_idp(add_clean_up=True) + new_remoteid = data_utils.rand_name('newRemoteId') + raw_output = self.openstack('identity provider set ' + '%(identity-provider)s ' + '--remote-id %(remote-id)s ' + % {'identity-provider': identity_provider, + 'remote-id': new_remoteid}) + self.assertEqual(0, len(raw_output)) + raw_output = self.openstack('identity provider show %s' + % identity_provider) + updated_value = self.parse_show_as_object(raw_output) + self.assertIn(new_remoteid, updated_value['remote_ids']) From 59d12a63b4c9eb71294931de1eb8957465cae6fd Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Fri, 21 Aug 2015 15:29:08 -0500 Subject: [PATCH 0244/3095] unwedge the osc gate keystone added a new property to projects, called is_domain. our functional tests fail because we are not expecting that in the project's 'show' command. Change-Id: Idf05118155847e3a6002818c44b99825801ea9f4 Related-Bug: #1487600 --- functional/tests/identity/v3/test_identity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/functional/tests/identity/v3/test_identity.py b/functional/tests/identity/v3/test_identity.py index bf3da167fa..29eba19a1d 100644 --- a/functional/tests/identity/v3/test_identity.py +++ b/functional/tests/identity/v3/test_identity.py @@ -28,7 +28,7 @@ class IdentityTests(test.TestCase): TOKEN_FIELDS = ['expires', 'id', 'project_id', 'user_id'] USER_FIELDS = ['email', 'enabled', 'id', 'name', 'name', 'domain_id', 'default_project_id', 'description'] - PROJECT_FIELDS = ['description', 'id', 'domain_id', + PROJECT_FIELDS = ['description', 'id', 'domain_id', 'is_domain', 'enabled', 'name', 'parent_id', 'links'] ROLE_FIELDS = ['id', 'name', 'links'] SERVICE_FIELDS = ['id', 'enabled', 'name', 'type', 'description'] From 48f7f0f1bcb0d43d18e89f8ff54971f450f2f58b Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Fri, 21 Aug 2015 12:06:54 -0600 Subject: [PATCH 0245/3095] Automate flavors, networks, and image get Functional tests rely on some sort of configuration and rather than configure something right now, how about it pulls something from the middle of the available list. Change-Id: I8147e40e5ee7393d8a8dcf1b0beb48856f28af7e --- functional/tests/compute/v2/test_server.py | 38 +++++++++++++++++----- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/functional/tests/compute/v2/test_server.py b/functional/tests/compute/v2/test_server.py index aa1c1201df..a6cc98e621 100644 --- a/functional/tests/compute/v2/test_server.py +++ b/functional/tests/compute/v2/test_server.py @@ -15,6 +15,7 @@ import testtools +from functional.common import exceptions from functional.common import test @@ -27,19 +28,38 @@ class ServerTests(test.TestCase): FIELDS = ['name'] IP_POOL = 'public' + @classmethod + def get_flavor(cls): + raw_output = cls.openstack('flavor list -f value -c ID') + ray = raw_output.split('\n') + idx = len(ray)/2 + return ray[idx] + + @classmethod + def get_image(cls): + raw_output = cls.openstack('image list -f value -c ID') + ray = raw_output.split('\n') + idx = len(ray)/2 + return ray[idx] + + @classmethod + def get_network(cls): + try: + raw_output = cls.openstack('network list -f value -c ID') + except exceptions.CommandFailed: + return '' + ray = raw_output.split('\n') + idx = len(ray)/2 + return ' --nic net-id=' + ray[idx] + @classmethod def setUpClass(cls): opts = cls.get_show_opts(cls.FIELDS) - # TODO(thowe): pull these values from clouds.yaml - flavor = '4' - image = 'cirros-0.3.4-x86_64-uec' - netid = '' - if netid: - nicargs = ' --nic net-id=' + netid - else: - nicargs = '' + flavor = cls.get_flavor() + image = cls.get_image() + network = cls.get_network() raw_output = cls.openstack('server create --flavor ' + flavor + - ' --image ' + image + nicargs + ' ' + + ' --image ' + image + network + ' ' + cls.NAME + opts) expected = cls.NAME + '\n' cls.assertOutput(expected, raw_output) From f14251669f96d6010581702417828f4380144aa2 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Mon, 24 Aug 2015 10:38:43 -0500 Subject: [PATCH 0246/3095] default OS_VOLUME_API_VERSION to v2 Cinder is trying to deprecate/remove support for v1, so we should, as a client library default to v2 and keep support for v1. Related-Bug: 1467589 Change-Id: I732448a57fc3fd06a8d82ec0f0d2ace671036ca2 --- openstackclient/volume/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/volume/client.py b/openstackclient/volume/client.py index d4800b8d5c..0973868b86 100644 --- a/openstackclient/volume/client.py +++ b/openstackclient/volume/client.py @@ -19,7 +19,7 @@ LOG = logging.getLogger(__name__) -DEFAULT_API_VERSION = '1' +DEFAULT_API_VERSION = '2' API_VERSION_OPTION = 'os_volume_api_version' API_NAME = "volume" API_VERSIONS = { From 85a03945f0b5da0ec5778a929e08a641f427513a Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Mon, 10 Aug 2015 12:33:28 -0600 Subject: [PATCH 0247/3095] Create log configuration class Configuration of logging gets triggered twice. The first time it uses the CLI options when the application is started and second it uses the configuration file after that is read. The state of the logging needs to be saved from the first to the second time, so I created a class. Implements: blueprint logging-migration Change-Id: I7b8d1a3b6fd128e98cafd7c16009c7b694a52146 --- openstackclient/common/context.py | 144 +++++++++--------- openstackclient/shell.py | 61 +------- openstackclient/tests/common/test_context.py | 152 ++++++++++++++----- openstackclient/tests/test_shell.py | 94 +----------- 4 files changed, 189 insertions(+), 262 deletions(-) diff --git a/openstackclient/common/context.py b/openstackclient/common/context.py index 570919392c..6d1aec13b7 100644 --- a/openstackclient/common/context.py +++ b/openstackclient/common/context.py @@ -11,9 +11,10 @@ # under the License. # -"""Context and Formatter""" +"""Application logging""" import logging +import sys import warnings @@ -99,76 +100,71 @@ def __init__(self, options=None, config=None, **kwargs): logging.Formatter.__init__(self, self.fmt, self._LOG_DATE_FORMAT) -def setup_handler_logging_level(handler_type, level): - """Setup of the handler for set the logging level - - :param handler_type: type of logging handler - :param level: logging level - :return: None - """ - # Set the handler logging level of FileHandler(--log-file) - # and StreamHandler - for h in logging.getLogger('').handlers: - if type(h) is handler_type: - h.setLevel(level) - - -def setup_logging(shell, cloud_config): - """Get one cloud configuration from configuration file and setup logging - - :param shell: instance of openstackclient shell - :param cloud_config: - instance of the cloud specified by --os-cloud - in the configuration file - :return: None - """ - - log_level = log_level_from_config(cloud_config.config) - set_warning_filter(log_level) - - log_file = cloud_config.config.get('log_file', None) - if log_file: - # setup the logging context - formatter = _FileFormatter(config=cloud_config) - # setup the logging handler - log_handler = _setup_handler_for_logging( - logging.FileHandler, - log_level, - file_name=log_file, - formatter=formatter, - ) - if log_level == logging.DEBUG: - # DEBUG only. - # setup the operation_log - shell.enable_operation_logging = True - shell.operation_log.setLevel(logging.DEBUG) - shell.operation_log.addHandler(log_handler) - - -def _setup_handler_for_logging(handler_type, level, file_name, formatter): - """Setup of the handler - - Setup of the handler for addition of the logging handler, - changes of the logging format, change of the logging level, - - :param handler_type: type of logging handler - :param level: logging level - :param file_name: name of log-file - :param formatter: instance of logging.Formatter - :return: logging handler - """ - - root_logger = logging.getLogger('') - handler = None - # Setup handler for FileHandler(--os-cloud) - handler = logging.FileHandler( - filename=file_name, - ) - handler.setFormatter(formatter) - handler.setLevel(level) - - # If both `--log-file` and `--os-cloud` are specified, - # the log is output to each file. - root_logger.addHandler(handler) - - return handler +class LogConfigurator(object): + + _CONSOLE_MESSAGE_FORMAT = '%(message)s' + + def __init__(self, options): + self.root_logger = logging.getLogger('') + self.root_logger.setLevel(logging.DEBUG) + + # Force verbose_level 3 on --debug + self.dump_trace = False + if options.debug: + options.verbose_level = 3 + self.dump_trace = True + + # Always send higher-level messages to the console via stderr + self.console_logger = logging.StreamHandler(sys.stderr) + log_level = log_level_from_options(options) + self.console_logger.setLevel(log_level) + formatter = logging.Formatter(self._CONSOLE_MESSAGE_FORMAT) + self.console_logger.setFormatter(formatter) + self.root_logger.addHandler(self.console_logger) + + # Set the warning filter now + set_warning_filter(log_level) + + # Set up logging to a file + self.file_logger = None + log_file = options.log_file + if log_file: + self.file_logger = logging.FileHandler(filename=log_file) + self.file_logger.setFormatter(_FileFormatter(options=options)) + self.file_logger.setLevel(log_level) + self.root_logger.addHandler(self.file_logger) + + # Requests logs some stuff at INFO that we don't want + # unless we have DEBUG + requests_log = logging.getLogger("requests") + + # Other modules we don't want DEBUG output for + cliff_log = logging.getLogger('cliff') + stevedore_log = logging.getLogger('stevedore') + iso8601_log = logging.getLogger("iso8601") + + if options.debug: + # --debug forces traceback + requests_log.setLevel(logging.DEBUG) + else: + requests_log.setLevel(logging.ERROR) + + cliff_log.setLevel(logging.ERROR) + stevedore_log.setLevel(logging.ERROR) + iso8601_log.setLevel(logging.ERROR) + + def configure(self, cloud_config): + log_level = log_level_from_config(cloud_config.config) + set_warning_filter(log_level) + self.dump_trace = cloud_config.config.get('debug', self.dump_trace) + self.console_logger.setLevel(log_level) + + log_file = cloud_config.config.get('log_file', None) + if log_file: + if not self.file_logger: + self.file_logger = logging.FileHandler(filename=log_file) + formatter = _FileFormatter(cloud_config=cloud_config) + self.file_logger.setFormatter(formatter) + self.file_logger.setFormatter(_FileFormatter(config=cloud_config)) + self.file_logger.setLevel(log_level) + self.root_logger.addHandler(self.file_logger) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 6ba19d194f..72f663a093 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -94,60 +94,12 @@ def __init__(self): self.verify = True self.client_manager = None - - # Operation log - self.enable_operation_logging = False self.command_options = None def configure_logging(self): - """Configure logging for the app - - Cliff sets some defaults we don't want so re-work it a bit - """ - - if self.options.debug: - # --debug forces verbose_level 3 - # Set this here so cliff.app.configure_logging() can work - self.options.verbose_level = 3 - - super(OpenStackShell, self).configure_logging() - - # Set logging to the requested level - log_level = context.log_level_from_options(self.options) - context.set_warning_filter(log_level) - - # Set the handler logging level of FileHandler(--log-file) - # and StreamHandler - if self.options.log_file: - context.setup_handler_logging_level(logging.FileHandler, log_level) - - context.setup_handler_logging_level(logging.StreamHandler, log_level) - - # Requests logs some stuff at INFO that we don't want - # unless we have DEBUG - requests_log = logging.getLogger("requests") - - # Other modules we don't want DEBUG output for - cliff_log = logging.getLogger('cliff') - stevedore_log = logging.getLogger('stevedore') - iso8601_log = logging.getLogger("iso8601") - - if self.options.debug: - # --debug forces traceback - self.dump_stack_trace = True - requests_log.setLevel(logging.DEBUG) - else: - self.dump_stack_trace = False - requests_log.setLevel(logging.ERROR) - - cliff_log.setLevel(logging.ERROR) - stevedore_log.setLevel(logging.ERROR) - iso8601_log.setLevel(logging.ERROR) - - # Operation logging - self.operation_log = logging.getLogger("operation_log") - self.operation_log.setLevel(logging.ERROR) - self.operation_log.propagate = False + """Configure logging for the app.""" + self.log_configurator = context.LogConfigurator(self.options) + self.dump_stack_trace = self.log_configurator.dump_trace def run(self, argv): ret_val = 1 @@ -162,8 +114,6 @@ def run(self, argv): self.log.error(traceback.format_exc(e)) else: self.log.error('Exception raised: ' + str(e)) - if self.enable_operation_logging: - self.operation_log.error(traceback.format_exc(e)) return ret_val @@ -287,9 +237,8 @@ def initialize_app(self, argv): argparse=self.options, ) - # Set up every time record log in file and logging start - context.setup_logging(self, self.cloud) - + self.log_configurator.configure(self.cloud) + self.dump_stack_trace = self.log_configurator.dump_trace self.log.info("START with options: %s", self.command_options) self.log.debug("options: %s", self.options) self.log.debug("defaults: %s", cc.defaults) diff --git a/openstackclient/tests/common/test_context.py b/openstackclient/tests/common/test_context.py index cc213b13f4..55e42851b3 100644 --- a/openstackclient/tests/common/test_context.py +++ b/openstackclient/tests/common/test_context.py @@ -13,7 +13,6 @@ import logging import mock -import os from openstackclient.common import context from openstackclient.tests import utils @@ -21,47 +20,6 @@ class TestContext(utils.TestCase): - TEST_LOG_FILE = "/tmp/test_log_file" - - def setUp(self): - super(TestContext, self).setUp() - - def tearDown(self): - super(TestContext, self).tearDown() - if os.path.exists(self.TEST_LOG_FILE): - os.remove(self.TEST_LOG_FILE) - - def setup_handler_logging_level(self): - handler_type = logging.FileHandler - handler = logging.FileHandler(filename=self.TEST_LOG_FILE) - handler.setLevel(logging.ERROR) - logging.getLogger('').addHandler(handler) - context.setup_handler_logging_level(handler_type, logging.INFO) - self.log.info("test log") - ld = open(self.TEST_LOG_FILE) - line = ld.readlines() - ld.close() - if os.path.exists(self.TEST_LOG_FILE): - os.remove(self.TEST_LOG_FILE) - self.assertGreaterEqual(line.find("test log"), 0) - - @mock.patch("openstackclient.common.context._setup_handler_for_logging") - def test_setup_logging(self, setuph): - setuph.return_value = mock.MagicMock() - shell = mock.MagicMock() - cloud_config = mock.MagicMock() - cloud_config.auth = { - 'project_name': 'heart-o-gold', - 'username': 'zaphod' - } - cloud_config.config = { - 'log_level': 'debug', - 'log_file': self.TEST_LOG_FILE, - 'cloud': 'megadodo' - } - context.setup_logging(shell, cloud_config) - self.assertEqual(True, shell.enable_operation_logging) - def test_log_level_from_options(self): opts = mock.Mock() opts.verbose_level = 0 @@ -132,3 +90,113 @@ def test_config(self): self.assertEqual(('%(asctime)s.%(msecs)03d %(process)d %(levelname)s ' '%(name)s [cloudy usernamey projecty] %(message)s'), formatter.fmt) + + +class TestLogConfigurator(utils.TestCase): + def setUp(self): + super(TestLogConfigurator, self).setUp() + self.options = mock.Mock() + self.options.verbose_level = 1 + self.options.log_file = None + self.options.debug = False + self.root_logger = mock.Mock() + self.root_logger.setLevel = mock.Mock() + self.root_logger.addHandler = mock.Mock() + self.requests_log = mock.Mock() + self.requests_log.setLevel = mock.Mock() + self.cliff_log = mock.Mock() + self.cliff_log.setLevel = mock.Mock() + self.stevedore_log = mock.Mock() + self.stevedore_log.setLevel = mock.Mock() + self.iso8601_log = mock.Mock() + self.iso8601_log.setLevel = mock.Mock() + self.loggers = [ + self.root_logger, + self.requests_log, + self.cliff_log, + self.stevedore_log, + self.iso8601_log] + + @mock.patch('logging.StreamHandler') + @mock.patch('logging.getLogger') + @mock.patch('openstackclient.common.context.set_warning_filter') + def test_init(self, warning_filter, getLogger, handle): + getLogger.side_effect = self.loggers + console_logger = mock.Mock() + console_logger.setFormatter = mock.Mock() + console_logger.setLevel = mock.Mock() + handle.return_value = console_logger + + configurator = context.LogConfigurator(self.options) + + getLogger.assert_called_with('iso8601') # last call + warning_filter.assert_called_with(logging.WARNING) + self.root_logger.setLevel.assert_called_with(logging.DEBUG) + self.root_logger.addHandler.assert_called_with(console_logger) + self.requests_log.setLevel.assert_called_with(logging.ERROR) + self.cliff_log.setLevel.assert_called_with(logging.ERROR) + self.stevedore_log.setLevel.assert_called_with(logging.ERROR) + self.iso8601_log.setLevel.assert_called_with(logging.ERROR) + self.assertEqual(False, configurator.dump_trace) + + @mock.patch('logging.getLogger') + @mock.patch('openstackclient.common.context.set_warning_filter') + def test_init_no_debug(self, warning_filter, getLogger): + getLogger.side_effect = self.loggers + self.options.debug = True + + configurator = context.LogConfigurator(self.options) + + warning_filter.assert_called_with(logging.DEBUG) + self.requests_log.setLevel.assert_called_with(logging.DEBUG) + self.assertEqual(True, configurator.dump_trace) + + @mock.patch('logging.FileHandler') + @mock.patch('logging.getLogger') + @mock.patch('openstackclient.common.context.set_warning_filter') + @mock.patch('openstackclient.common.context._FileFormatter') + def test_init_log_file(self, formatter, warning_filter, getLogger, handle): + getLogger.side_effect = self.loggers + self.options.log_file = '/tmp/log_file' + file_logger = mock.Mock() + file_logger.setFormatter = mock.Mock() + file_logger.setLevel = mock.Mock() + handle.return_value = file_logger + mock_formatter = mock.Mock() + formatter.return_value = mock_formatter + + context.LogConfigurator(self.options) + + handle.assert_called_with(filename=self.options.log_file) + self.root_logger.addHandler.assert_called_with(file_logger) + file_logger.setFormatter.assert_called_with(mock_formatter) + file_logger.setLevel.assert_called_with(logging.WARNING) + + @mock.patch('logging.FileHandler') + @mock.patch('logging.getLogger') + @mock.patch('openstackclient.common.context.set_warning_filter') + @mock.patch('openstackclient.common.context._FileFormatter') + def test_configure(self, formatter, warning_filter, getLogger, handle): + getLogger.side_effect = self.loggers + configurator = context.LogConfigurator(self.options) + cloud_config = mock.Mock() + config_log = '/tmp/config_log' + cloud_config.config = { + 'log_file': config_log, + 'verbose_level': 1, + 'log_level': 'info'} + file_logger = mock.Mock() + file_logger.setFormatter = mock.Mock() + file_logger.setLevel = mock.Mock() + handle.return_value = file_logger + mock_formatter = mock.Mock() + formatter.return_value = mock_formatter + + configurator.configure(cloud_config) + + warning_filter.assert_called_with(logging.INFO) + handle.assert_called_with(filename=config_log) + self.root_logger.addHandler.assert_called_with(file_logger) + file_logger.setFormatter.assert_called_with(mock_formatter) + file_logger.setLevel.assert_called_with(logging.INFO) + self.assertEqual(False, configurator.dump_trace) diff --git a/openstackclient/tests/test_shell.py b/openstackclient/tests/test_shell.py index 5b84475329..cacd2fb763 100644 --- a/openstackclient/tests/test_shell.py +++ b/openstackclient/tests/test_shell.py @@ -623,12 +623,9 @@ def test_shell_args_cloud_no_vendor(self, config_mock): @mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file") @mock.patch("os_client_config.config.OpenStackConfig._load_config_file") - @mock.patch("openstackclient.common.context._setup_handler_for_logging") - def test_shell_args_cloud_public(self, setup_handler, config_mock, - public_mock): + def test_shell_args_cloud_public(self, config_mock, public_mock): config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_2)) public_mock.return_value = ('file.yaml', copy.deepcopy(PUBLIC_1)) - setup_handler.return_value = mock.MagicMock() _shell = make_shell() fake_execute( @@ -666,12 +663,9 @@ def test_shell_args_cloud_public(self, setup_handler, config_mock, @mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file") @mock.patch("os_client_config.config.OpenStackConfig._load_config_file") - @mock.patch("openstackclient.common.context._setup_handler_for_logging") - def test_shell_args_precedence(self, setup_handler, config_mock, - vendor_mock): + def test_shell_args_precedence(self, config_mock, vendor_mock): config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_2)) vendor_mock.return_value = ('file.yaml', copy.deepcopy(PUBLIC_1)) - setup_handler.return_value = mock.MagicMock() _shell = make_shell() # Test command option overriding config file value @@ -723,12 +717,9 @@ def tearDown(self): @mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file") @mock.patch("os_client_config.config.OpenStackConfig._load_config_file") - @mock.patch("openstackclient.common.context._setup_handler_for_logging") - def test_shell_args_precedence_1(self, setup_handler, config_mock, - vendor_mock): + def test_shell_args_precedence_1(self, config_mock, vendor_mock): config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_2)) vendor_mock.return_value = ('file.yaml', copy.deepcopy(PUBLIC_1)) - setup_handler.return_value = mock.MagicMock() _shell = make_shell() # Test env var @@ -767,12 +758,9 @@ def test_shell_args_precedence_1(self, setup_handler, config_mock, @mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file") @mock.patch("os_client_config.config.OpenStackConfig._load_config_file") - @mock.patch("openstackclient.common.context._setup_handler_for_logging") - def test_shell_args_precedence_2(self, setup_handler, config_mock, - vendor_mock): + def test_shell_args_precedence_2(self, config_mock, vendor_mock): config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_2)) vendor_mock.return_value = ('file.yaml', copy.deepcopy(PUBLIC_1)) - setup_handler.return_value = mock.MagicMock() _shell = make_shell() # Test command option overriding config file value @@ -810,77 +798,3 @@ def test_shell_args_precedence_2(self, setup_handler, config_mock, 'krikkit', _shell.cloud.config['region_name'], ) - - -class TestShellCliLogging(TestShell): - def setUp(self): - super(TestShellCliLogging, self).setUp() - - def tearDown(self): - super(TestShellCliLogging, self).tearDown() - - @mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file") - @mock.patch("os_client_config.config.OpenStackConfig._load_config_file") - @mock.patch("openstackclient.common.context._setup_handler_for_logging") - def test_shell_args_precedence_1(self, setup_handler, config_mock, - vendor_mock): - config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_2)) - vendor_mock.return_value = ('file.yaml', copy.deepcopy(PUBLIC_1)) - setup_handler.return_value = mock.MagicMock() - _shell = make_shell() - - # These come from clouds.yaml - fake_execute( - _shell, - "--os-cloud megacloud list user", - ) - self.assertEqual( - 'megacloud', - _shell.cloud.name, - ) - - self.assertEqual( - '/tmp/test_log_file', - _shell.cloud.config['log_file'], - ) - self.assertEqual( - 'debug', - _shell.cloud.config['log_level'], - ) - - @mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file") - @mock.patch("os_client_config.config.OpenStackConfig._load_config_file") - def test_shell_args_precedence_2(self, config_mock, vendor_mock): - config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_1)) - vendor_mock.return_value = ('file.yaml', copy.deepcopy(PUBLIC_1)) - _shell = make_shell() - - # Test operation_log_file not set - fake_execute( - _shell, - "--os-cloud scc list user", - ) - self.assertEqual( - False, - _shell.enable_operation_logging, - ) - - @mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file") - @mock.patch("os_client_config.config.OpenStackConfig._load_config_file") - @mock.patch("openstackclient.common.context._setup_handler_for_logging") - def test_shell_args_precedence_3(self, setup_handler, config_mock, - vendor_mock): - config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_2)) - vendor_mock.return_value = ('file.yaml', copy.deepcopy(PUBLIC_1)) - setup_handler.return_value = mock.MagicMock() - _shell = make_shell() - - # Test enable_operation_logging set - fake_execute( - _shell, - "--os-cloud megacloud list user", - ) - self.assertEqual( - True, - _shell.enable_operation_logging, - ) From 5171a427ac81605182cee55efdcd48da07cb5633 Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Fri, 28 Aug 2015 09:32:05 -0600 Subject: [PATCH 0248/3095] Ignore flavor and image find errors on server show If there is an error finding an image or a flavor during image show, ignore it and just print the id of the flavor or image. This code is also used during server create and server rebuild, but only to display the results. Change-Id: I5362158ab8ffc3e5a0800904d6ea15420c3a8627 Closes-bug: #1489901 --- openstackclient/compute/v2/server.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 4efef975bb..6d837d9cd5 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -107,14 +107,20 @@ def _prep_server_detail(compute_client, server): image_info = info.get('image', {}) if image_info: image_id = image_info.get('id', '') - image = utils.find_resource(compute_client.images, image_id) - info['image'] = "%s (%s)" % (image.name, image_id) + try: + image = utils.find_resource(compute_client.images, image_id) + info['image'] = "%s (%s)" % (image.name, image_id) + except Exception: + info['image'] = image_id # Convert the flavor blob to a name flavor_info = info.get('flavor', {}) flavor_id = flavor_info.get('id', '') - flavor = utils.find_resource(compute_client.flavors, flavor_id) - info['flavor'] = "%s (%s)" % (flavor.name, flavor_id) + try: + flavor = utils.find_resource(compute_client.flavors, flavor_id) + info['flavor'] = "%s (%s)" % (flavor.name, flavor_id) + except Exception: + info['flavor'] = flavor_id # NOTE(dtroyer): novaclient splits these into separate entries... # Format addresses in a useful way From 14a714f2a28d2f0ae232556c1f8ccd6d4a5b2043 Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Fri, 28 Aug 2015 10:34:50 -0600 Subject: [PATCH 0249/3095] Volume v2 list does not show server name The volume v2 list was using the volume id rather than the server id. Change-Id: Ibe03d34b5b503af2d00202dabd640f796449cf9a Closes-Bug: #1489954 --- openstackclient/tests/volume/v2/fakes.py | 7 ++++--- openstackclient/tests/volume/v2/test_volume.py | 10 +++++----- openstackclient/volume/v2/volume.py | 2 +- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py index 0d8c2024c7..7b7758a3a8 100644 --- a/openstackclient/tests/volume/v2/fakes.py +++ b/openstackclient/tests/volume/v2/fakes.py @@ -15,14 +15,15 @@ import copy import mock -from openstackclient.tests.compute.v2 import fakes as compute_fakes from openstackclient.tests import fakes from openstackclient.tests.identity.v2_0 import fakes as identity_fakes from openstackclient.tests.image.v2 import fakes as image_fakes from openstackclient.tests import utils -volume_attachment_server = copy.deepcopy(compute_fakes.SERVER) -volume_attachment_server['device'] = 'device' +volume_attachment_server = { + 'device': '/dev/ice', + 'server_id': '1233', +} volume_id = "ce26708d-a7f8-4b4b-9861-4a80256615a6" volume_name = "fake_volume" diff --git a/openstackclient/tests/volume/v2/test_volume.py b/openstackclient/tests/volume/v2/test_volume.py index 348ae377de..b15fd02fc1 100644 --- a/openstackclient/tests/volume/v2/test_volume.py +++ b/openstackclient/tests/volume/v2/test_volume.py @@ -548,7 +548,7 @@ def test_volume_list_no_options(self): ] self.assertEqual(collist, columns) - server = volume_fakes.volume_attachment_server['id'] + server = volume_fakes.volume_attachment_server['server_id'] device = volume_fakes.volume_attachment_server['device'] msg = 'Attached to %s on %s ' % (server, device) datalist = (( @@ -583,7 +583,7 @@ def test_volume_list_all_projects_option(self): ] self.assertEqual(collist, columns) - server = volume_fakes.volume_attachment_server['id'] + server = volume_fakes.volume_attachment_server['server_id'] device = volume_fakes.volume_attachment_server['device'] msg = 'Attached to %s on %s ' % (server, device) datalist = (( @@ -618,7 +618,7 @@ def test_volume_list_name(self): ) self.assertEqual(collist, tuple(columns)) - server = volume_fakes.volume_attachment_server['id'] + server = volume_fakes.volume_attachment_server['server_id'] device = volume_fakes.volume_attachment_server['device'] msg = 'Attached to %s on %s ' % (server, device) @@ -654,7 +654,7 @@ def test_volume_list_status(self): ) self.assertEqual(collist, tuple(columns)) - server = volume_fakes.volume_attachment_server['id'] + server = volume_fakes.volume_attachment_server['server_id'] device = volume_fakes.volume_attachment_server['device'] msg = 'Attached to %s on %s ' % (server, device) datalist = (( @@ -693,7 +693,7 @@ def test_volume_list_long(self): ] self.assertEqual(collist, columns) - server = volume_fakes.volume_attachment_server['id'] + server = volume_fakes.volume_attachment_server['server_id'] device = volume_fakes.volume_attachment_server['device'] msg = 'Attached to %s on %s ' % (server, device) datalist = (( diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index fe4a3ff63f..1d298f46d3 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -238,7 +238,7 @@ def _format_attach(attachments): msg = '' for attachment in attachments: - server = attachment['id'] + server = attachment['server_id'] if server in server_cache: server = server_cache[server].name device = attachment['device'] From d751a21d2ce84e9e9fc64658a28ad479f2b6593e Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Tue, 1 Sep 2015 15:51:58 -0700 Subject: [PATCH 0250/3095] Fix 'auhentication' spelling error/mistake Change-Id: Iba58c188d2ae44170539534eea1415cf8eb65ac4 --- openstackclient/api/auth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/api/auth.py b/openstackclient/api/auth.py index 820b4ecffa..66272e4247 100644 --- a/openstackclient/api/auth.py +++ b/openstackclient/api/auth.py @@ -185,7 +185,7 @@ def build_auth_plugins_option_parser(parser): metavar='', dest='auth_type', default=utils.env('OS_AUTH_TYPE'), - help='Select an auhentication type. Available types: ' + + help='Select an authentication type. Available types: ' + ', '.join(available_plugins) + '. Default: selected based on --os-username/--os-token' + ' (Env: OS_AUTH_TYPE)', From e3c46ece4a496584a54b9d39b55921990db4a7b3 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Tue, 1 Sep 2015 16:43:07 -0700 Subject: [PATCH 0251/3095] Use a common decorator to log 'take_action' activation Instead of duplicating the same log statement throughout the code, the same logic can be provided by a shared decorator that abstracts away the logging capability and unifies it behind a common function instead. Change-Id: Icc63bced7347c8bbf0299a4c5821425a10892a79 --- openstackclient/common/configuration.py | 4 +- openstackclient/common/limits.py | 2 +- openstackclient/common/module.py | 6 ++- openstackclient/common/quota.py | 6 ++- openstackclient/common/utils.py | 23 +++++++++++ openstackclient/compute/v2/aggregate.py | 6 +-- .../compute/v2/availability_zone.py | 2 +- openstackclient/compute/v2/console.py | 4 +- openstackclient/compute/v2/floatingip.py | 6 +-- openstackclient/compute/v2/floatingippool.py | 2 +- openstackclient/compute/v2/keypair.py | 6 +-- openstackclient/compute/v2/security_group.py | 8 ++-- openstackclient/compute/v2/server.py | 40 +++++++++---------- openstackclient/identity/v2_0/catalog.py | 4 +- openstackclient/identity/v2_0/ec2creds.py | 8 ++-- openstackclient/identity/v2_0/endpoint.py | 8 ++-- openstackclient/identity/v2_0/project.py | 10 ++--- openstackclient/identity/v2_0/role.py | 14 +++---- openstackclient/identity/v2_0/service.py | 8 ++-- openstackclient/identity/v2_0/token.py | 3 +- openstackclient/identity/v2_0/user.py | 10 ++--- openstackclient/identity/v3/catalog.py | 4 +- openstackclient/identity/v3/consumer.py | 10 ++--- openstackclient/identity/v3/credential.py | 10 ++--- openstackclient/identity/v3/domain.py | 10 ++--- openstackclient/identity/v3/ec2creds.py | 8 ++-- openstackclient/identity/v3/endpoint.py | 10 ++--- .../identity/v3/federation_protocol.py | 4 +- openstackclient/identity/v3/group.py | 16 ++++---- .../identity/v3/identity_provider.py | 10 ++--- openstackclient/identity/v3/policy.py | 10 ++--- openstackclient/identity/v3/project.py | 10 ++--- openstackclient/identity/v3/region.py | 10 ++--- openstackclient/identity/v3/role.py | 12 +++--- openstackclient/identity/v3/service.py | 10 ++--- .../identity/v3/service_provider.py | 10 ++--- openstackclient/identity/v3/token.py | 2 +- openstackclient/identity/v3/unscoped_saml.py | 4 +- openstackclient/identity/v3/user.py | 12 +++--- openstackclient/object/v1/container.py | 8 ++-- openstackclient/object/v1/object.py | 8 ++-- openstackclient/volume/v1/backup.py | 10 ++--- openstackclient/volume/v1/qos_specs.py | 16 ++++---- openstackclient/volume/v1/snapshot.py | 12 +++--- openstackclient/volume/v1/volume.py | 12 +++--- openstackclient/volume/v1/volume_type.py | 10 ++--- openstackclient/volume/v2/qos_specs.py | 16 ++++---- openstackclient/volume/v2/snapshot.py | 4 +- openstackclient/volume/v2/volume.py | 8 ++-- openstackclient/volume/v2/volume_type.py | 8 ++-- 50 files changed, 242 insertions(+), 212 deletions(-) diff --git a/openstackclient/common/configuration.py b/openstackclient/common/configuration.py index 83df73e23e..ac2792dd43 100644 --- a/openstackclient/common/configuration.py +++ b/openstackclient/common/configuration.py @@ -18,6 +18,8 @@ from cliff import show import six +from openstackclient.common import utils + REDACTED = "" @@ -44,8 +46,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) info = self.app.client_manager.get_configuration() for key, value in six.iteritems(info.pop('auth', {})): diff --git a/openstackclient/common/limits.py b/openstackclient/common/limits.py index 4abcf169a3..348fd35aab 100644 --- a/openstackclient/common/limits.py +++ b/openstackclient/common/limits.py @@ -64,8 +64,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute volume_client = self.app.client_manager.volume diff --git a/openstackclient/common/module.py b/openstackclient/common/module.py index 356cdca3b0..f0ed23b2b1 100644 --- a/openstackclient/common/module.py +++ b/openstackclient/common/module.py @@ -22,6 +22,8 @@ from cliff import lister from cliff import show +from openstackclient.common import utils + class ListCommand(lister.Lister): """List recognized commands by group""" @@ -29,8 +31,8 @@ class ListCommand(lister.Lister): auth_required = False log = logging.getLogger(__name__ + '.ListCommand') + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) cm = self.app.command_manager groups = cm.get_command_groups() @@ -54,8 +56,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) data = {} # Get module versions diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index e79fd7ed1d..65367e03cc 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -23,6 +23,8 @@ from cliff import command from cliff import show +from openstackclient.common import utils + # List the quota items, map the internal argument name to the option # name that the user sees. @@ -89,8 +91,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute volume_client = self.app.client_manager.volume @@ -188,8 +190,8 @@ def get_network_quota(self, parsed_args): else: return {} + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute volume_client = self.app.client_manager.volume diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index 2f8419f42a..b6726bfa84 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -26,6 +26,29 @@ from openstackclient.common import exceptions +def log_method(log, level=logging.DEBUG): + """Logs a method and its arguments when entered.""" + + def decorator(func): + func_name = func.__name__ + + @six.wraps(func) + def wrapper(self, *args, **kwargs): + if log.isEnabledFor(level): + pretty_args = [] + if args: + pretty_args.extend(str(a) for a in args) + if kwargs: + pretty_args.extend( + "%s=%s" % (k, v) for k, v in six.iteritems(kwargs)) + log.log(level, "%s(%s)", func_name, ", ".join(pretty_args)) + return func(self, *args, **kwargs) + + return wrapper + + return decorator + + def find_resource(manager, name_or_id, **kwargs): """Helper for the _find_* methods. diff --git a/openstackclient/compute/v2/aggregate.py b/openstackclient/compute/v2/aggregate.py index 84ed5c7d27..a1ba618fda 100644 --- a/openstackclient/compute/v2/aggregate.py +++ b/openstackclient/compute/v2/aggregate.py @@ -123,8 +123,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute data = utils.find_resource( @@ -256,8 +256,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute aggregate = utils.find_resource( @@ -303,8 +303,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute data = utils.find_resource( diff --git a/openstackclient/compute/v2/availability_zone.py b/openstackclient/compute/v2/availability_zone.py index 648c0ee4ce..0fe6c73acb 100644 --- a/openstackclient/compute/v2/availability_zone.py +++ b/openstackclient/compute/v2/availability_zone.py @@ -73,8 +73,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) if parsed_args.long: columns = ('Zone Name', 'Zone Status', diff --git a/openstackclient/compute/v2/console.py b/openstackclient/compute/v2/console.py index 082a3a0c0f..bb0747b1cb 100644 --- a/openstackclient/compute/v2/console.py +++ b/openstackclient/compute/v2/console.py @@ -47,8 +47,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute server = utils.find_resource( @@ -103,8 +103,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute server = utils.find_resource( compute_client.servers, diff --git a/openstackclient/compute/v2/floatingip.py b/openstackclient/compute/v2/floatingip.py index 658f0d5abb..c557c24b6a 100644 --- a/openstackclient/compute/v2/floatingip.py +++ b/openstackclient/compute/v2/floatingip.py @@ -69,8 +69,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute floating_ip = compute_client.floating_ips.create(parsed_args.pool) @@ -93,8 +93,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute floating_ip = utils.find_resource( @@ -111,8 +111,8 @@ class ListFloatingIP(lister.Lister): log = logging.getLogger(__name__ + '.ListFloatingIP') + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute columns = ('ID', 'Pool', 'IP', 'Fixed IP', 'Instance ID') diff --git a/openstackclient/compute/v2/floatingippool.py b/openstackclient/compute/v2/floatingippool.py index db1c9f0f79..cc4855527b 100644 --- a/openstackclient/compute/v2/floatingippool.py +++ b/openstackclient/compute/v2/floatingippool.py @@ -27,8 +27,8 @@ class ListFloatingIPPool(lister.Lister): log = logging.getLogger(__name__ + '.ListFloatingIPPool') + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute columns = ('Name',) diff --git a/openstackclient/compute/v2/keypair.py b/openstackclient/compute/v2/keypair.py index edf25f8358..5c627c5082 100644 --- a/openstackclient/compute/v2/keypair.py +++ b/openstackclient/compute/v2/keypair.py @@ -47,8 +47,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute public_key = parsed_args.public_key @@ -93,8 +93,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute compute_client.keypairs.delete(parsed_args.name) return @@ -140,8 +140,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute keypair = utils.find_resource(compute_client.keypairs, parsed_args.name) diff --git a/openstackclient/compute/v2/security_group.py b/openstackclient/compute/v2/security_group.py index 3dc9bae0b5..0ed7c5925a 100644 --- a/openstackclient/compute/v2/security_group.py +++ b/openstackclient/compute/v2/security_group.py @@ -107,8 +107,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute data = utils.find_resource( @@ -199,8 +199,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute data = utils.find_resource( @@ -240,8 +240,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute info = {} @@ -334,8 +334,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute compute_client.security_group_rules.delete(parsed_args.rule) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 4efef975bb..95406f135e 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -355,8 +355,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute volume_client = self.app.client_manager.volume @@ -553,8 +553,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute image_client = self.app.client_manager.image server = utils.find_resource( @@ -612,8 +612,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute for server in parsed_args.servers: server_obj = utils.find_resource( @@ -701,8 +701,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute search_opts = { 'reservation_id': parsed_args.reservation_id, @@ -772,8 +772,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute utils.find_resource( @@ -845,8 +845,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute @@ -889,8 +889,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute utils.find_resource( @@ -935,8 +935,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute server = utils.find_resource( compute_client.servers, parsed_args.server) @@ -984,8 +984,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute # Lookup parsed_args.image @@ -1100,8 +1100,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute _, body = utils.find_resource( @@ -1146,8 +1146,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute server = utils.find_resource( @@ -1191,8 +1191,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute utils.find_resource( @@ -1232,8 +1232,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute server = utils.find_resource( @@ -1280,8 +1280,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute server = utils.find_resource(compute_client.servers, parsed_args.server) @@ -1403,8 +1403,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute server = utils.find_resource( @@ -1458,8 +1458,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute utils.find_resource( @@ -1482,8 +1482,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute utils.find_resource( @@ -1506,8 +1506,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute utils.find_resource( @@ -1530,8 +1530,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute utils.find_resource( @@ -1562,8 +1562,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute server = utils.find_resource( compute_client.servers, diff --git a/openstackclient/identity/v2_0/catalog.py b/openstackclient/identity/v2_0/catalog.py index e166c855c4..9bc2755aa2 100644 --- a/openstackclient/identity/v2_0/catalog.py +++ b/openstackclient/identity/v2_0/catalog.py @@ -42,8 +42,8 @@ class ListCatalog(lister.Lister): log = logging.getLogger(__name__ + '.ListCatalog') + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) # This is ugly because if auth hasn't happened yet we need # to trigger it here. @@ -76,8 +76,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) # This is ugly because if auth hasn't happened yet we need # to trigger it here. diff --git a/openstackclient/identity/v2_0/ec2creds.py b/openstackclient/identity/v2_0/ec2creds.py index 348479ac33..a7730ce252 100644 --- a/openstackclient/identity/v2_0/ec2creds.py +++ b/openstackclient/identity/v2_0/ec2creds.py @@ -52,8 +52,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity if parsed_args.project: @@ -105,8 +105,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity if parsed_args.user: @@ -135,8 +135,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity if parsed_args.user: @@ -178,8 +178,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity if parsed_args.user: diff --git a/openstackclient/identity/v2_0/endpoint.py b/openstackclient/identity/v2_0/endpoint.py index 370a931d40..1744cc72ac 100644 --- a/openstackclient/identity/v2_0/endpoint.py +++ b/openstackclient/identity/v2_0/endpoint.py @@ -62,8 +62,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity service = common.find_service(identity_client, parsed_args.service) endpoint = identity_client.endpoints.create( @@ -93,8 +93,8 @@ def get_parser(self, prog_name): help=_('Endpoint ID to delete')) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity identity_client.endpoints.delete(parsed_args.endpoint) return @@ -115,8 +115,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity if parsed_args.long: columns = ('ID', 'Region', 'Service Name', 'Service Type', @@ -150,8 +150,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity data = identity_client.endpoints.list() match = None diff --git a/openstackclient/identity/v2_0/project.py b/openstackclient/identity/v2_0/project.py index d01807a600..97a95f2881 100644 --- a/openstackclient/identity/v2_0/project.py +++ b/openstackclient/identity/v2_0/project.py @@ -70,8 +70,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity enabled = True @@ -118,8 +118,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity for project in parsed_args.projects: @@ -146,8 +146,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) if parsed_args.long: columns = ('ID', 'Name', 'Description', 'Enabled') else: @@ -202,8 +202,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity if (not parsed_args.name @@ -253,8 +253,8 @@ def get_parser(self, prog_name): help=_('Project to display (name or ID)')) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity info = {} diff --git a/openstackclient/identity/v2_0/role.py b/openstackclient/identity/v2_0/role.py index 3167f50f07..e98f8cb347 100644 --- a/openstackclient/identity/v2_0/role.py +++ b/openstackclient/identity/v2_0/role.py @@ -54,8 +54,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity role = utils.find_resource(identity_client.roles, parsed_args.role) project = utils.find_resource( @@ -93,8 +93,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity try: role = identity_client.roles.create(parsed_args.role_name) @@ -128,8 +128,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity for role in parsed_args.roles: @@ -160,8 +160,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity auth_ref = self.app.client_manager.auth_ref @@ -242,8 +242,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity auth_ref = self.app.client_manager.auth_ref @@ -316,8 +316,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity role = utils.find_resource(identity_client.roles, parsed_args.role) project = utils.find_resource( @@ -345,8 +345,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity role = utils.find_resource(identity_client.roles, parsed_args.role) diff --git a/openstackclient/identity/v2_0/service.py b/openstackclient/identity/v2_0/service.py index f8630238b6..c9d4844126 100644 --- a/openstackclient/identity/v2_0/service.py +++ b/openstackclient/identity/v2_0/service.py @@ -59,8 +59,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity type_or_name = parsed_args.type_or_name @@ -106,8 +106,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity service = common.find_service(identity_client, parsed_args.service) identity_client.services.delete(service.id) @@ -129,8 +129,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) if parsed_args.long: columns = ('ID', 'Name', 'Type', 'Description') @@ -163,8 +163,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity auth_ref = self.app.client_manager.auth_ref diff --git a/openstackclient/identity/v2_0/token.py b/openstackclient/identity/v2_0/token.py index c8b003ee02..5fed58e591 100644 --- a/openstackclient/identity/v2_0/token.py +++ b/openstackclient/identity/v2_0/token.py @@ -21,6 +21,7 @@ from cliff import command from cliff import show +from openstackclient.common import utils from openstackclient.i18n import _ # noqa @@ -33,8 +34,8 @@ def get_parser(self, prog_name): parser = super(IssueToken, self).get_parser(prog_name) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) token = self.app.client_manager.auth_ref.service_catalog.get_token() token['project_id'] = token.pop('tenant_id') diff --git a/openstackclient/identity/v2_0/user.py b/openstackclient/identity/v2_0/user.py index 500aeec32e..76902e6925 100644 --- a/openstackclient/identity/v2_0/user.py +++ b/openstackclient/identity/v2_0/user.py @@ -78,8 +78,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity if parsed_args.project: @@ -142,8 +142,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity for user in parsed_args.users: @@ -174,8 +174,8 @@ def get_parser(self, prog_name): help=_('List additional fields in output')) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity def _format_project(project): @@ -296,8 +296,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity if parsed_args.password_prompt: @@ -362,8 +362,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity info = {} diff --git a/openstackclient/identity/v3/catalog.py b/openstackclient/identity/v3/catalog.py index 1899f25e63..76f7da51b3 100644 --- a/openstackclient/identity/v3/catalog.py +++ b/openstackclient/identity/v3/catalog.py @@ -39,8 +39,8 @@ class ListCatalog(lister.Lister): log = logging.getLogger(__name__ + '.ListCatalog') + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) # This is ugly because if auth hasn't happened yet we need # to trigger it here. @@ -73,8 +73,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) # This is ugly because if auth hasn't happened yet we need # to trigger it here. diff --git a/openstackclient/identity/v3/consumer.py b/openstackclient/identity/v3/consumer.py index ffbd5104cf..0a6ade67f8 100644 --- a/openstackclient/identity/v3/consumer.py +++ b/openstackclient/identity/v3/consumer.py @@ -40,8 +40,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity consumer = identity_client.oauth1.consumers.create( parsed_args.description @@ -64,8 +64,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity consumer = utils.find_resource( identity_client.oauth1.consumers, parsed_args.consumer) @@ -78,8 +78,8 @@ class ListConsumer(lister.Lister): log = logging.getLogger(__name__ + '.ListConsumer') + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) columns = ('ID', 'Description') data = self.app.client_manager.identity.oauth1.consumers.list() return (columns, @@ -108,8 +108,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity consumer = utils.find_resource( identity_client.oauth1.consumers, parsed_args.consumer) @@ -140,8 +140,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity consumer = utils.find_resource( identity_client.oauth1.consumers, parsed_args.consumer) diff --git a/openstackclient/identity/v3/credential.py b/openstackclient/identity/v3/credential.py index dacbc9af51..dbd73e2e38 100644 --- a/openstackclient/identity/v3/credential.py +++ b/openstackclient/identity/v3/credential.py @@ -57,8 +57,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity user_id = utils.find_resource(identity_client.users, parsed_args.user).id @@ -91,8 +91,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity identity_client.credentials.delete(parsed_args.credential) return @@ -103,8 +103,8 @@ class ListCredential(lister.Lister): log = logging.getLogger(__name__ + '.ListCredential') + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) columns = ('ID', 'Type', 'User ID', 'Blob', 'Project ID') column_headers = ('ID', 'Type', 'User ID', 'Data', 'Project ID') data = self.app.client_manager.identity.credentials.list() @@ -150,8 +150,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity kwargs = {} if parsed_args.user: @@ -189,8 +189,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity credential = utils.find_resource(identity_client.credentials, parsed_args.credential) diff --git a/openstackclient/identity/v3/domain.py b/openstackclient/identity/v3/domain.py index 38f99a9772..aec530a883 100644 --- a/openstackclient/identity/v3/domain.py +++ b/openstackclient/identity/v3/domain.py @@ -63,8 +63,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity enabled = True @@ -103,8 +103,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity domain = utils.find_resource(identity_client.domains, parsed_args.domain) @@ -117,8 +117,8 @@ class ListDomain(lister.Lister): log = logging.getLogger(__name__ + '.ListDomain') + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) columns = ('ID', 'Name', 'Enabled', 'Description') data = self.app.client_manager.identity.domains.list() return (columns, @@ -163,8 +163,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity domain = utils.find_resource(identity_client.domains, parsed_args.domain) @@ -200,8 +200,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity domain = utils.find_resource(identity_client.domains, parsed_args.domain) diff --git a/openstackclient/identity/v3/ec2creds.py b/openstackclient/identity/v3/ec2creds.py index 03314634a7..a12ee25e2b 100644 --- a/openstackclient/identity/v3/ec2creds.py +++ b/openstackclient/identity/v3/ec2creds.py @@ -79,8 +79,8 @@ def get_parser(self, prog_name): common.add_project_domain_option_to_parser(parser) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity client_manager = self.app.client_manager user = _determine_ec2_user(parsed_args, client_manager) @@ -136,8 +136,8 @@ def get_parser(self, prog_name): common.add_user_domain_option_to_parser(parser) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) client_manager = self.app.client_manager user = _determine_ec2_user(parsed_args, client_manager) client_manager.identity.ec2.delete(user, parsed_args.access_key) @@ -158,8 +158,8 @@ def get_parser(self, prog_name): common.add_user_domain_option_to_parser(parser) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) client_manager = self.app.client_manager user = _determine_ec2_user(parsed_args, client_manager) @@ -194,8 +194,8 @@ def get_parser(self, prog_name): common.add_user_domain_option_to_parser(parser) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) client_manager = self.app.client_manager user = _determine_ec2_user(parsed_args, client_manager) creds = client_manager.identity.ec2.get(user, parsed_args.access_key) diff --git a/openstackclient/identity/v3/endpoint.py b/openstackclient/identity/v3/endpoint.py index 52db5ace59..3d1c6f5416 100644 --- a/openstackclient/identity/v3/endpoint.py +++ b/openstackclient/identity/v3/endpoint.py @@ -78,8 +78,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity service = common.find_service(identity_client, parsed_args.service) @@ -113,8 +113,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity endpoint_id = utils.find_resource(identity_client.endpoints, parsed_args.endpoint).id @@ -147,8 +147,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity columns = ('ID', 'Region', 'Service Name', 'Service Type', 'Enabled', 'Interface', 'URL') @@ -221,8 +221,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity endpoint = utils.find_resource(identity_client.endpoints, parsed_args.endpoint) @@ -270,8 +270,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity endpoint = utils.find_resource(identity_client.endpoints, parsed_args.endpoint) diff --git a/openstackclient/identity/v3/federation_protocol.py b/openstackclient/identity/v3/federation_protocol.py index 57e8255e9c..20877fcd4d 100644 --- a/openstackclient/identity/v3/federation_protocol.py +++ b/openstackclient/identity/v3/federation_protocol.py @@ -50,8 +50,8 @@ def get_parser(self, prog_name): return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity protocol = identity_client.federation.protocols.create( protocol_id=parsed_args.federation_protocol, @@ -88,8 +88,8 @@ def get_parser(self, prog_name): return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity identity_client.federation.protocols.delete( parsed_args.identity_provider, parsed_args.federation_protocol) diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py index d659f71e4a..82975065ac 100644 --- a/openstackclient/identity/v3/group.py +++ b/openstackclient/identity/v3/group.py @@ -50,8 +50,8 @@ def get_parser(self, prog_name): common.add_user_domain_option_to_parser(parser) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity user_id = common.find_user(identity_client, @@ -92,8 +92,8 @@ def get_parser(self, prog_name): common.add_user_domain_option_to_parser(parser) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity user_id = common.find_user(identity_client, @@ -142,8 +142,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity domain = None @@ -188,8 +188,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity for group in parsed_args.groups: @@ -226,8 +226,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity domain = None @@ -284,8 +284,8 @@ def get_parser(self, prog_name): common.add_user_domain_option_to_parser(parser) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity user_id = common.find_user(identity_client, @@ -331,8 +331,8 @@ def get_parser(self, prog_name): help='New group description') return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity group = common.find_group(identity_client, parsed_args.group, parsed_args.domain) @@ -368,8 +368,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity group = common.find_group(identity_client, diff --git a/openstackclient/identity/v3/identity_provider.py b/openstackclient/identity/v3/identity_provider.py index 8096580016..27982a9d5c 100644 --- a/openstackclient/identity/v3/identity_provider.py +++ b/openstackclient/identity/v3/identity_provider.py @@ -70,8 +70,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity if parsed_args.remote_id_file: file_content = utils.read_blob_file_contents( @@ -105,8 +105,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity identity_client.federation.identity_providers.delete( parsed_args.identity_provider) @@ -118,8 +118,8 @@ class ListIdentityProvider(lister.Lister): log = logging.getLogger(__name__ + '.ListIdentityProvider') + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) columns = ('ID', 'Enabled', 'Description') identity_client = self.app.client_manager.identity data = identity_client.federation.identity_providers.list() @@ -169,8 +169,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) federation_client = self.app.client_manager.identity.federation # Basic argument checking @@ -218,8 +218,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity identity_provider = utils.find_resource( identity_client.federation.identity_providers, diff --git a/openstackclient/identity/v3/policy.py b/openstackclient/identity/v3/policy.py index 8293542317..9da94863f4 100644 --- a/openstackclient/identity/v3/policy.py +++ b/openstackclient/identity/v3/policy.py @@ -47,8 +47,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) blob = utils.read_blob_file_contents(parsed_args.rules) identity_client = self.app.client_manager.identity @@ -75,8 +75,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity identity_client.policies.delete(parsed_args.policy) return @@ -97,8 +97,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) if parsed_args.long: columns = ('ID', 'Type', 'Blob') column_headers = ('ID', 'Type', 'Rules') @@ -137,8 +137,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity blob = None @@ -172,8 +172,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity policy = utils.find_resource(identity_client.policies, parsed_args.policy) diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index 8185d65af8..96d7f97b20 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -81,8 +81,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity domain = None @@ -146,8 +146,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity domain = None @@ -190,8 +190,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity if parsed_args.long: columns = ('ID', 'Name', 'Domain ID', 'Description', 'Enabled') @@ -271,8 +271,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity if (not parsed_args.name @@ -337,8 +337,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity if parsed_args.domain: diff --git a/openstackclient/identity/v3/region.py b/openstackclient/identity/v3/region.py index 5cb51fc5ec..eb4c084ca4 100644 --- a/openstackclient/identity/v3/region.py +++ b/openstackclient/identity/v3/region.py @@ -56,8 +56,8 @@ def get_parser(self, prog_name): return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity region = identity_client.regions.create( @@ -87,8 +87,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity identity_client.regions.delete(parsed_args.region) @@ -109,8 +109,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity kwargs = {} @@ -157,8 +157,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity if (not parsed_args.url @@ -192,8 +192,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity region = utils.find_resource(identity_client.regions, diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index 9243639e56..46e2e440aa 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -122,8 +122,8 @@ def get_parser(self, prog_name): _add_identity_and_resource_options_to_parser(parser) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity if (not parsed_args.user and not parsed_args.domain @@ -166,8 +166,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity try: @@ -199,8 +199,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity for role in parsed_args.roles: @@ -325,8 +325,8 @@ def get_parser(self, prog_name): _add_identity_and_resource_options_to_parser(parser) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity if (not parsed_args.user and not parsed_args.domain @@ -368,8 +368,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity if not parsed_args.name: @@ -398,8 +398,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity role = utils.find_resource( diff --git a/openstackclient/identity/v3/service.py b/openstackclient/identity/v3/service.py index d63a953789..85081aa764 100644 --- a/openstackclient/identity/v3/service.py +++ b/openstackclient/identity/v3/service.py @@ -61,8 +61,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity enabled = True @@ -94,8 +94,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity service = common.find_service(identity_client, parsed_args.service) @@ -119,8 +119,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) if parsed_args.long: columns = ('ID', 'Name', 'Type', 'Description', 'Enabled') @@ -173,8 +173,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity if (not parsed_args.name @@ -219,8 +219,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity service = common.find_service(identity_client, parsed_args.service) diff --git a/openstackclient/identity/v3/service_provider.py b/openstackclient/identity/v3/service_provider.py index 78f96b6c8e..838ad4a29e 100644 --- a/openstackclient/identity/v3/service_provider.py +++ b/openstackclient/identity/v3/service_provider.py @@ -73,8 +73,8 @@ def get_parser(self, prog_name): return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) service_client = self.app.client_manager.identity sp = service_client.federation.service_providers.create( id=parsed_args.service_provider_id, @@ -101,8 +101,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) service_client = self.app.client_manager.identity service_client.federation.service_providers.delete( parsed_args.service_provider) @@ -114,8 +114,8 @@ class ListServiceProvider(lister.Lister): log = logging.getLogger(__name__ + '.ListServiceProvider') + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) service_client = self.app.client_manager.identity data = service_client.federation.service_providers.list() @@ -168,8 +168,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) federation_client = self.app.client_manager.identity.federation enabled = None @@ -207,8 +207,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) service_client = self.app.client_manager.identity service_provider = utils.find_resource( service_client.federation.service_providers, diff --git a/openstackclient/identity/v3/token.py b/openstackclient/identity/v3/token.py index 7000b62ce0..cd3dc798fd 100644 --- a/openstackclient/identity/v3/token.py +++ b/openstackclient/identity/v3/token.py @@ -182,8 +182,8 @@ def get_parser(self, prog_name): parser = super(IssueToken, self).get_parser(prog_name) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) token = self.app.client_manager.auth_ref.service_catalog.get_token() if 'tenant_id' in token: token['project_id'] = token.pop('tenant_id') diff --git a/openstackclient/identity/v3/unscoped_saml.py b/openstackclient/identity/v3/unscoped_saml.py index fddac68fae..e659e75e82 100644 --- a/openstackclient/identity/v3/unscoped_saml.py +++ b/openstackclient/identity/v3/unscoped_saml.py @@ -49,8 +49,8 @@ class ListAccessibleDomains(lister.Lister): log = logging.getLogger(__name__ + '.ListAccessibleDomains') @auth_with_unscoped_saml + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) columns = ('ID', 'Enabled', 'Name', 'Description') identity_client = self.app.client_manager.identity data = identity_client.federation.domains.list() @@ -67,8 +67,8 @@ class ListAccessibleProjects(lister.Lister): log = logging.getLogger(__name__ + '.ListAccessibleProjects') @auth_with_unscoped_saml + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) columns = ('ID', 'Domain ID', 'Enabled', 'Name') identity_client = self.app.client_manager.identity data = identity_client.federation.projects.list() diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index 459707d2af..f23a90f710 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -91,8 +91,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity project_id = None @@ -155,8 +155,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity domain = None @@ -205,8 +205,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity domain = None @@ -336,8 +336,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity if parsed_args.password_prompt: @@ -396,8 +396,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity current_password = utils.get_password( @@ -430,8 +430,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity if parsed_args.domain: diff --git a/openstackclient/object/v1/container.py b/openstackclient/object/v1/container.py index bc4fdec81b..49173debbc 100644 --- a/openstackclient/object/v1/container.py +++ b/openstackclient/object/v1/container.py @@ -41,8 +41,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) results = [] for container in parsed_args.containers: @@ -74,8 +74,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) for container in parsed_args.containers: self.app.client_manager.object_store.container_delete( @@ -125,8 +125,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) if parsed_args.long: columns = ('Name', 'Bytes', 'Count') @@ -192,8 +192,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) data = self.app.client_manager.object_store.container_show( container=parsed_args.container, diff --git a/openstackclient/object/v1/object.py b/openstackclient/object/v1/object.py index 752d78423e..c90f031918 100644 --- a/openstackclient/object/v1/object.py +++ b/openstackclient/object/v1/object.py @@ -46,8 +46,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) results = [] for obj in parsed_args.objects: @@ -85,8 +85,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) for obj in parsed_args.objects: self.app.client_manager.object_store.object_delete( @@ -147,8 +147,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) if parsed_args.long: columns = ( @@ -240,8 +240,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) data = self.app.client_manager.object_store.object_show( container=parsed_args.container, diff --git a/openstackclient/volume/v1/backup.py b/openstackclient/volume/v1/backup.py index 03c63a0545..c668e36699 100644 --- a/openstackclient/volume/v1/backup.py +++ b/openstackclient/volume/v1/backup.py @@ -57,8 +57,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume volume_id = utils.find_resource(volume_client.volumes, parsed_args.volume).id @@ -88,8 +88,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume for backup in parsed_args.backups: backup_id = utils.find_resource(volume_client.backups, @@ -113,8 +113,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) def _format_volume_id(volume_id): """Return a volume name if available @@ -172,8 +172,8 @@ def get_parser(self, prog_name): help='Volume to restore to (name or ID)') return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume backup = utils.find_resource(volume_client.backups, parsed_args.backup) @@ -196,8 +196,8 @@ def get_parser(self, prog_name): help='Backup to display (ID only)') return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume backup = utils.find_resource(volume_client.backups, parsed_args.backup) diff --git a/openstackclient/volume/v1/qos_specs.py b/openstackclient/volume/v1/qos_specs.py index 8e909e3d3b..d1c70113cc 100644 --- a/openstackclient/volume/v1/qos_specs.py +++ b/openstackclient/volume/v1/qos_specs.py @@ -45,8 +45,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume qos_spec = utils.find_resource(volume_client.qos_specs, parsed_args.qos_spec) @@ -88,8 +88,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume specs = {} specs.update({'consumer': parsed_args.consumer}) @@ -117,8 +117,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume for qos in parsed_args.qos_specs: qos_spec = utils.find_resource(volume_client.qos_specs, qos) @@ -153,8 +153,8 @@ def get_parser(self, prog_name): return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume qos_spec = utils.find_resource(volume_client.qos_specs, parsed_args.qos_spec) @@ -174,8 +174,8 @@ class ListQos(lister.Lister): log = logging.getLogger(__name__ + '.ListQos') + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume qos_specs_list = volume_client.qos_specs.list() @@ -218,8 +218,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume qos_spec = utils.find_resource(volume_client.qos_specs, parsed_args.qos_spec) @@ -247,8 +247,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume qos_spec = utils.find_resource(volume_client.qos_specs, parsed_args.qos_spec) @@ -287,8 +287,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume qos_spec = utils.find_resource(volume_client.qos_specs, parsed_args.qos_spec) diff --git a/openstackclient/volume/v1/snapshot.py b/openstackclient/volume/v1/snapshot.py index e81efb5aaf..de7bb5b9ac 100644 --- a/openstackclient/volume/v1/snapshot.py +++ b/openstackclient/volume/v1/snapshot.py @@ -59,8 +59,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume volume_id = utils.find_resource(volume_client.volumes, parsed_args.volume).id @@ -93,8 +93,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume for snapshot in parsed_args.snapshots: snapshot_id = utils.find_resource(volume_client.volume_snapshots, @@ -118,8 +118,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) def _format_volume_id(volume_id): """Return a volume name if available @@ -194,8 +194,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume snapshot = utils.find_resource(volume_client.volume_snapshots, parsed_args.snapshot) @@ -231,8 +231,8 @@ def get_parser(self, prog_name): help='Snapshot to display (name or ID)') return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume snapshot = utils.find_resource(volume_client.volume_snapshots, parsed_args.snapshot) @@ -267,8 +267,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume snapshot = utils.find_resource( volume_client.volume_snapshots, parsed_args.snapshot) diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index 884611eccb..52b0eb2eb9 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -102,8 +102,8 @@ def get_parser(self, prog_name): return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity image_client = self.app.client_manager.image @@ -186,8 +186,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume for volume in parsed_args.volumes: volume_obj = utils.find_resource( @@ -230,8 +230,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume compute_client = self.app.client_manager.compute @@ -351,8 +351,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume volume = utils.find_resource(volume_client.volumes, parsed_args.volume) @@ -399,8 +399,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume volume = utils.find_resource(volume_client.volumes, parsed_args.volume) # Map 'metadata' column to 'properties' @@ -441,8 +441,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume volume = utils.find_resource( volume_client.volumes, parsed_args.volume) diff --git a/openstackclient/volume/v1/volume_type.py b/openstackclient/volume/v1/volume_type.py index d5c617b2f7..d7765c7942 100644 --- a/openstackclient/volume/v1/volume_type.py +++ b/openstackclient/volume/v1/volume_type.py @@ -47,8 +47,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume volume_type = volume_client.volume_types.create(parsed_args.name) volume_type._info.pop('extra_specs') @@ -75,8 +75,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume volume_type_id = utils.find_resource( volume_client.volume_types, parsed_args.volume_type).id @@ -98,8 +98,8 @@ def get_parser(self, prog_name): help='List additional fields in output') return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) if parsed_args.long: columns = ('ID', 'Name', 'Extra Specs') column_headers = ('ID', 'Name', 'Properties') @@ -135,8 +135,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume volume_type = utils.find_resource( volume_client.volume_types, parsed_args.volume_type) @@ -170,8 +170,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume volume_type = utils.find_resource( volume_client.volume_types, diff --git a/openstackclient/volume/v2/qos_specs.py b/openstackclient/volume/v2/qos_specs.py index ac78ca1575..b3a34cac18 100644 --- a/openstackclient/volume/v2/qos_specs.py +++ b/openstackclient/volume/v2/qos_specs.py @@ -45,8 +45,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume qos_spec = utils.find_resource(volume_client.qos_specs, parsed_args.qos_spec) @@ -88,8 +88,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume specs = {} specs.update({'consumer': parsed_args.consumer}) @@ -117,8 +117,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume for qos in parsed_args.qos_specs: qos_spec = utils.find_resource(volume_client.qos_specs, qos) @@ -153,8 +153,8 @@ def get_parser(self, prog_name): return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume qos_spec = utils.find_resource(volume_client.qos_specs, parsed_args.qos_spec) @@ -174,8 +174,8 @@ class ListQos(lister.Lister): log = logging.getLogger(__name__ + '.ListQos') + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume qos_specs_list = volume_client.qos_specs.list() @@ -218,8 +218,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume qos_spec = utils.find_resource(volume_client.qos_specs, parsed_args.qos_spec) @@ -247,8 +247,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume qos_spec = utils.find_resource(volume_client.qos_specs, parsed_args.qos_spec) @@ -287,8 +287,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume qos_spec = utils.find_resource(volume_client.qos_specs, parsed_args.qos_spec) diff --git a/openstackclient/volume/v2/snapshot.py b/openstackclient/volume/v2/snapshot.py index 4370cdeb17..bbc92c4888 100644 --- a/openstackclient/volume/v2/snapshot.py +++ b/openstackclient/volume/v2/snapshot.py @@ -186,8 +186,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume snapshot = utils.find_resource(volume_client.volume_snapshots, parsed_args.snapshot) @@ -256,8 +256,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume snapshot = utils.find_resource( volume_client.volume_snapshots, parsed_args.snapshot) diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index fe4a3ff63f..151d3b7d4b 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -223,8 +223,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume compute_client = self.app.client_manager.compute @@ -335,8 +335,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume volume = utils.find_resource(volume_client.volumes, parsed_args.volume) @@ -383,8 +383,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume volume = utils.find_resource(volume_client.volumes, parsed_args.volume) @@ -416,8 +416,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume volume = utils.find_resource( volume_client.volumes, parsed_args.volume) diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index fb0342c555..8cca86f9a3 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -66,8 +66,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume @@ -127,8 +127,8 @@ def get_parser(self, prog_name): help='List additional fields in output') return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) if parsed_args.long: columns = ['ID', 'Name', 'Description', 'Extra Specs'] column_headers = ['ID', 'Name', 'Description', 'Properties'] @@ -174,8 +174,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume volume_type = utils.find_resource( volume_client.volume_types, parsed_args.volume_type) @@ -250,8 +250,8 @@ def get_parser(self, prog_name): ) return parser + @utils.log_method(log) def take_action(self, parsed_args): - self.log.debug('take_action(%s)', parsed_args) volume_client = self.app.client_manager.volume volume_type = utils.find_resource( volume_client.volume_types, From e6706f252642e52dd9de556b92edb769afa57868 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 23 Jul 2015 15:08:52 -0500 Subject: [PATCH 0252/3095] Properly handle port arguments for ICMP The Compute API requires 'from_port' and 'to_port' to be -1 for ICMP security group rules. It happily accepts them empty or None but the resulting rules do not work. So we force the values for ICMP rules. Closes-bug: #1477629 Change-Id: Iba57211014caca16be7c9a28d15d4db2a6c51b8d --- openstackclient/compute/v2/security_group.py | 11 +- .../compute/v2/test_security_group_rule.py | 338 ++++++++++++++++++ 2 files changed, 345 insertions(+), 4 deletions(-) create mode 100644 openstackclient/tests/compute/v2/test_security_group_rule.py diff --git a/openstackclient/compute/v2/security_group.py b/openstackclient/compute/v2/security_group.py index 3dc9bae0b5..25c2ed3fc8 100644 --- a/openstackclient/compute/v2/security_group.py +++ b/openstackclient/compute/v2/security_group.py @@ -50,10 +50,10 @@ def _xform_security_group_rule(sgroup): info['ip_range'] = info['ip_range']['cidr'] else: info['ip_range'] = '' - if info['ip_protocol'] == 'icmp': - info['port_range'] = '' - elif info['ip_protocol'] is None: + if info['ip_protocol'] is None: info['ip_protocol'] = '' + elif info['ip_protocol'].lower() == 'icmp': + info['port_range'] = '' return info @@ -307,7 +307,10 @@ def take_action(self, parsed_args): compute_client.security_groups, parsed_args.group, ) - from_port, to_port = parsed_args.dst_port + if parsed_args.proto.lower() == 'icmp': + from_port, to_port = -1, -1 + else: + from_port, to_port = parsed_args.dst_port data = compute_client.security_group_rules.create( group.id, parsed_args.proto, diff --git a/openstackclient/tests/compute/v2/test_security_group_rule.py b/openstackclient/tests/compute/v2/test_security_group_rule.py new file mode 100644 index 0000000000..9516f8ddb6 --- /dev/null +++ b/openstackclient/tests/compute/v2/test_security_group_rule.py @@ -0,0 +1,338 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +import copy +import mock + +from openstackclient.compute.v2 import security_group +from openstackclient.tests.compute.v2 import fakes as compute_fakes +from openstackclient.tests import fakes +from openstackclient.tests.identity.v2_0 import fakes as identity_fakes + + +security_group_id = '11' +security_group_name = 'wide-open' +security_group_description = 'nothing but net' + +security_group_rule_id = '1' + +SECURITY_GROUP = { + 'id': security_group_id, + 'name': security_group_name, + 'description': security_group_description, + 'tenant_id': identity_fakes.project_id, +} + +SECURITY_GROUP_RULE = { + 'id': security_group_rule_id, + 'group': {}, + 'ip_protocol': 'tcp', + 'ip_range': '0.0.0.0/0', + 'parent_group_id': security_group_id, + 'from_port': 0, + 'to_port': 0, +} + +SECURITY_GROUP_RULE_ICMP = { + 'id': security_group_rule_id, + 'group': {}, + 'ip_protocol': 'icmp', + 'ip_range': '0.0.0.0/0', + 'parent_group_id': security_group_id, + 'from_port': -1, + 'to_port': -1, +} + + +class FakeSecurityGroupRuleResource(fakes.FakeResource): + + def get_keys(self): + return {'property': 'value'} + + +class TestSecurityGroupRule(compute_fakes.TestComputev2): + + def setUp(self): + super(TestSecurityGroupRule, self).setUp() + + self.secgroups_mock = mock.Mock() + self.secgroups_mock.resource_class = fakes.FakeResource(None, {}) + self.app.client_manager.compute.security_groups = self.secgroups_mock + self.secgroups_mock.reset_mock() + + self.sg_rules_mock = mock.Mock() + self.sg_rules_mock.resource_class = fakes.FakeResource(None, {}) + self.app.client_manager.compute.security_group_rules = \ + self.sg_rules_mock + self.sg_rules_mock.reset_mock() + + +class TestSecurityGroupRuleCreate(TestSecurityGroupRule): + + def setUp(self): + super(TestSecurityGroupRuleCreate, self).setUp() + + self.secgroups_mock.get.return_value = FakeSecurityGroupRuleResource( + None, + copy.deepcopy(SECURITY_GROUP), + loaded=True, + ) + + # Get the command object to test + self.cmd = security_group.CreateSecurityGroupRule(self.app, None) + + def test_security_group_rule_create_no_options(self): + self.sg_rules_mock.create.return_value = FakeSecurityGroupRuleResource( + None, + copy.deepcopy(SECURITY_GROUP_RULE), + loaded=True, + ) + + arglist = [ + security_group_name, + ] + verifylist = [ + ('group', security_group_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # SecurityGroupManager.create(name, description) + self.sg_rules_mock.create.assert_called_with( + security_group_id, + 'tcp', + 0, + 0, + '0.0.0.0/0', + ) + + collist = ( + 'group', + 'id', + 'ip_protocol', + 'ip_range', + 'parent_group_id', + 'port_range', + ) + self.assertEqual(collist, columns) + datalist = ( + {}, + security_group_rule_id, + 'tcp', + '', + security_group_id, + '0:0', + ) + self.assertEqual(datalist, data) + + def test_security_group_rule_create_ftp(self): + sg_rule = copy.deepcopy(SECURITY_GROUP_RULE) + sg_rule['from_port'] = 20 + sg_rule['to_port'] = 21 + self.sg_rules_mock.create.return_value = FakeSecurityGroupRuleResource( + None, + sg_rule, + loaded=True, + ) + + arglist = [ + security_group_name, + '--dst-port', '20:21', + ] + verifylist = [ + ('group', security_group_name), + ('dst_port', (20, 21)), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # SecurityGroupManager.create(name, description) + self.sg_rules_mock.create.assert_called_with( + security_group_id, + 'tcp', + 20, + 21, + '0.0.0.0/0', + ) + + collist = ( + 'group', + 'id', + 'ip_protocol', + 'ip_range', + 'parent_group_id', + 'port_range', + ) + self.assertEqual(collist, columns) + datalist = ( + {}, + security_group_rule_id, + 'tcp', + '', + security_group_id, + '20:21', + ) + self.assertEqual(datalist, data) + + def test_security_group_rule_create_ssh(self): + sg_rule = copy.deepcopy(SECURITY_GROUP_RULE) + sg_rule['from_port'] = 22 + sg_rule['to_port'] = 22 + self.sg_rules_mock.create.return_value = FakeSecurityGroupRuleResource( + None, + sg_rule, + loaded=True, + ) + + arglist = [ + security_group_name, + '--dst-port', '22', + ] + verifylist = [ + ('group', security_group_name), + ('dst_port', (22, 22)), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # SecurityGroupManager.create(name, description) + self.sg_rules_mock.create.assert_called_with( + security_group_id, + 'tcp', + 22, + 22, + '0.0.0.0/0', + ) + + collist = ( + 'group', + 'id', + 'ip_protocol', + 'ip_range', + 'parent_group_id', + 'port_range', + ) + self.assertEqual(collist, columns) + datalist = ( + {}, + security_group_rule_id, + 'tcp', + '', + security_group_id, + '22:22', + ) + self.assertEqual(datalist, data) + + def test_security_group_rule_create_udp(self): + sg_rule = copy.deepcopy(SECURITY_GROUP_RULE) + sg_rule['ip_protocol'] = 'udp' + self.sg_rules_mock.create.return_value = FakeSecurityGroupRuleResource( + None, + sg_rule, + loaded=True, + ) + + arglist = [ + security_group_name, + '--proto', 'udp', + ] + verifylist = [ + ('group', security_group_name), + ('proto', 'udp'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # SecurityGroupManager.create(name, description) + self.sg_rules_mock.create.assert_called_with( + security_group_id, + 'udp', + 0, + 0, + '0.0.0.0/0', + ) + + collist = ( + 'group', + 'id', + 'ip_protocol', + 'ip_range', + 'parent_group_id', + 'port_range', + ) + self.assertEqual(collist, columns) + datalist = ( + {}, + security_group_rule_id, + 'udp', + '', + security_group_id, + '0:0', + ) + self.assertEqual(datalist, data) + + def test_security_group_rule_create_icmp(self): + self.sg_rules_mock.create.return_value = FakeSecurityGroupRuleResource( + None, + copy.deepcopy(SECURITY_GROUP_RULE_ICMP), + loaded=True, + ) + + arglist = [ + security_group_name, + '--proto', 'ICMP', + ] + verifylist = [ + ('group', security_group_name), + ('proto', 'ICMP'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # SecurityGroupManager.create(name, description) + self.sg_rules_mock.create.assert_called_with( + security_group_id, + 'ICMP', + -1, + -1, + '0.0.0.0/0', + ) + + collist = ( + 'group', + 'id', + 'ip_protocol', + 'ip_range', + 'parent_group_id', + 'port_range', + ) + self.assertEqual(collist, columns) + datalist = ( + {}, + security_group_rule_id, + 'icmp', + '', + security_group_id, + '', + ) + self.assertEqual(datalist, data) From 3abfea083a13c9aa69bedd10ea0286353d2f5887 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 4 Sep 2015 16:22:33 -0500 Subject: [PATCH 0253/3095] Fix compute API version snafu novaclient 2.27.0 introduced the API microversion discovery and client.Client now wants an api_version argument to properly work out the correct API version in use. OSC needs to provide this when required. Letting the compute client plugin do the version validity checking makes more sense than encoding it into shell.py, so I've added a new OSC plugin interface function check_api_version() that is called from shell.py if it exists. If it either does not exist or it returns False the previous version checking using API_VERSIONS is still performed. compute.client.check_api_version() conditionally imports the new novaclient.api_versions module and uses it if successful. Otherwise check_api_version() returns False and the previous code path is resumed. One side-effect of this is that it is now valid to use --os-compute-api-version with any valid microversion supported by the installed python-novaclient. Closes-Bug: #1492467 Change-Id: I4535b38a5639a03a9597bf83f6394f9bb45c2b9e --- openstackclient/compute/client.py | 49 +++++++++++++++++++++++++++++++ openstackclient/shell.py | 22 ++++++++++---- 2 files changed, 66 insertions(+), 5 deletions(-) diff --git a/openstackclient/compute/client.py b/openstackclient/compute/client.py index 9dda32d660..dd40b9a994 100644 --- a/openstackclient/compute/client.py +++ b/openstackclient/compute/client.py @@ -15,6 +15,7 @@ import logging +from openstackclient.common import exceptions from openstackclient.common import utils LOG = logging.getLogger(__name__) @@ -26,6 +27,9 @@ "2": "novaclient.client", } +# Save the microversion if in use +_compute_api_version = None + def make_client(instance): """Returns a compute service client.""" @@ -51,6 +55,9 @@ def make_client(instance): # Remember interface only if it is set kwargs = utils.build_kwargs_dict('endpoint_type', instance._interface) + if _compute_api_version is not None: + kwargs.update({'api_version': _compute_api_version}) + client = compute_client( session=instance.session, extensions=extensions, @@ -73,3 +80,45 @@ def build_option_parser(parser): DEFAULT_API_VERSION + ' (Env: OS_COMPUTE_API_VERSION)') return parser + + +def check_api_version(check_version): + """Validate version supplied by user + + Returns: + * True if version is OK + * False if the version has not been checked and the previous plugin + check should be performed + * throws an exception if the version is no good + + TODO(dtroyer): make the exception thrown a version-related one + """ + + # Defer client imports until we actually need them + try: + from novaclient import api_versions + except ImportError: + # Retain previous behaviour + return False + + import novaclient + + global _compute_api_version + + # Copy some logic from novaclient 2.27.0 for basic version detection + # NOTE(dtroyer): This is only enough to resume operations using API + # version 2.0 or any valid version supplied by the user. + _compute_api_version = api_versions.get_api_version(check_version) + + if _compute_api_version > api_versions.APIVersion("2.0"): + if not _compute_api_version.matches( + novaclient.API_MIN_VERSION, + novaclient.API_MAX_VERSION, + ): + raise exceptions.CommandError( + "versions supported by client: %s - %s" % ( + novaclient.API_MIN_VERSION.get_string(), + novaclient.API_MAX_VERSION.get_string(), + ), + ) + return True diff --git a/openstackclient/shell.py b/openstackclient/shell.py index a8b5ac4c88..27bcba4891 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -304,11 +304,23 @@ def initialize_app(self, argv): if version_opt: api = mod.API_NAME self.api_version[api] = version_opt - if version_opt not in mod.API_VERSIONS: - self.log.warning( - "The %s version <%s> is not in supported versions <%s>" - % (api, version_opt, - ', '.join(mod.API_VERSIONS.keys()))) + + # Add a plugin interface to let the module validate the version + # requested by the user + skip_old_check = False + mod_check_api_version = getattr(mod, 'check_api_version', None) + if mod_check_api_version: + # this throws an exception if invalid + skip_old_check = mod_check_api_version(version_opt) + + mod_versions = getattr(mod, 'API_VERSIONS', None) + if not skip_old_check and mod_versions: + if version_opt not in mod_versions: + self.log.warning( + "%s version %s is not in supported versions %s" + % (api, version_opt, + ', '.join(mod.API_VERSIONS.keys()))) + # Command groups deal only with major versions version = '.v' + version_opt.replace('.', '_').split('_')[0] cmd_group = 'openstack.' + api.replace('-', '_') + version From 36a9703a192af16d2bad8cc7fec1eba25de8d01b Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Thu, 13 Aug 2015 21:57:53 -0700 Subject: [PATCH 0254/3095] Support listing users by group name Listing users within a group is not presently domain scoped. We do not use the domain info at all when the group option is present. A new --group-domain option is not needed since we cannot list users by --project and --group, they are mutually exclusive (as per the identity API). Closes-Bug: 1492916 Change-Id: I50f995ee4a03c2bdb21f2b5722546ab8fe786eb6 --- openstackclient/identity/v3/user.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index 459707d2af..737ebc652a 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -214,13 +214,11 @@ def take_action(self, parsed_args): domain = common.find_domain(identity_client, parsed_args.domain).id + group = None if parsed_args.group: - group = utils.find_resource( - identity_client.groups, - parsed_args.group, - ).id - else: - group = None + group = common.find_group(identity_client, + parsed_args.group, + parsed_args.domain).id if parsed_args.project: if domain is not None: From 66010b41f128d415aff6aedde3f21981117600a6 Mon Sep 17 00:00:00 2001 From: Ashish Singh Date: Thu, 27 Aug 2015 17:07:09 +0530 Subject: [PATCH 0255/3095] Add support for listing servers of other projects Added project and project-domain option to server list command for listing servers based on project name or id Co-Authored-By: Steve Martinelli Change-Id: Iaadfffe734ad8a72fa4b1eeb2222748c66f7fae0 Closes-Bug: #1488486 --- doc/source/command-objects/server.rst | 10 ++++++++++ openstackclient/compute/v2/server.py | 18 ++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index eb61bab549..b00b94479d 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -185,6 +185,7 @@ List servers [--image ] [--host ] [--all-projects] + [--project [--project-domain ]] [--long] .. option:: --reservation-id @@ -227,6 +228,15 @@ List servers Include all projects (admin only) +.. option:: --project + + Search by project (admin only) (name or ID) + +.. option:: --project-domain + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + .. option:: --long List additional fields in output diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 4efef975bb..a246a04d2a 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -36,6 +36,7 @@ from openstackclient.common import parseractions from openstackclient.common import utils from openstackclient.i18n import _ # noqa +from openstackclient.identity import common as identity_common from openstackclient.network import common @@ -693,6 +694,11 @@ def get_parser(self, prog_name): default=bool(int(os.environ.get("ALL_PROJECTS", 0))), help=_('Include all projects (admin only)'), ) + parser.add_argument( + '--project', + metavar='', + help="Search by project (admin only) (name or ID)") + identity_common.add_project_domain_option_to_parser(parser) parser.add_argument( '--long', action='store_true', @@ -704,6 +710,17 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute + + project_id = None + if parsed_args.project: + identity_client = self.app.client_manager.identity + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + parsed_args.all_projects = True + search_opts = { 'reservation_id': parsed_args.reservation_id, 'ip': parsed_args.ip, @@ -714,6 +731,7 @@ def take_action(self, parsed_args): 'flavor': parsed_args.flavor, 'image': parsed_args.image, 'host': parsed_args.host, + 'tenant_id': project_id, 'all_tenants': parsed_args.all_projects, } self.log.debug('search options: %s', search_opts) From 10db0df857f8982110b67094f237384acda2fa3a Mon Sep 17 00:00:00 2001 From: kafka Date: Wed, 12 Aug 2015 12:07:34 +0800 Subject: [PATCH 0256/3095] Add filtering by user for 'openstack server list' Add a new option to search by user when listing servers, include support for domain scoped users, also update docs Co-Authored-By: Steve Martinelli Closes-Bug: #1483974 Change-Id: Ifdade6dc9ca8400fbd85f6b55793ab15ed17b97d --- doc/source/command-objects/server.rst | 9 +++++++++ openstackclient/compute/v2/server.py | 17 ++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index b00b94479d..9ff4843727 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -237,6 +237,15 @@ List servers Domain the project belongs to (name or ID). This can be used in case collisions between project names exist. +.. option:: --user + + Search by user (admin only) (name or ID) + +.. option:: --user-domain + + Domain the user belongs to (name or ID). + This can be used in case collisions between user names exist. + .. option:: --long List additional fields in output diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index a246a04d2a..30a1b0636d 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -699,6 +699,12 @@ def get_parser(self, prog_name): metavar='', help="Search by project (admin only) (name or ID)") identity_common.add_project_domain_option_to_parser(parser) + parser.add_argument( + '--user', + metavar='', + help=_('Search by user (admin only) (name or ID)'), + ) + identity_common.add_user_domain_option_to_parser(parser) parser.add_argument( '--long', action='store_true', @@ -710,10 +716,10 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) compute_client = self.app.client_manager.compute + identity_client = self.app.client_manager.identity project_id = None if parsed_args.project: - identity_client = self.app.client_manager.identity project_id = identity_common.find_project( identity_client, parsed_args.project, @@ -721,6 +727,14 @@ def take_action(self, parsed_args): ).id parsed_args.all_projects = True + user_id = None + if parsed_args.user: + user_id = identity_common.find_project( + identity_client, + parsed_args.user, + parsed_args.user_domain, + ).id + search_opts = { 'reservation_id': parsed_args.reservation_id, 'ip': parsed_args.ip, @@ -733,6 +747,7 @@ def take_action(self, parsed_args): 'host': parsed_args.host, 'tenant_id': project_id, 'all_tenants': parsed_args.all_projects, + 'user_id': user_id, } self.log.debug('search options: %s', search_opts) From c513f05ce994a81044f787bd8460227f785ad8c1 Mon Sep 17 00:00:00 2001 From: Andrey Kurilin Date: Wed, 9 Sep 2015 18:00:50 +0300 Subject: [PATCH 0257/3095] Use novaclient.client.Client for initialization Nova client `novaclient.client.Client` is a recommended entry point for novaclient. It supports backward-compatibility and allows Nova-folks to change interfaces of inner versioned clients classes. Change-Id: Iaf20714f63c307f88a451759f041ca509fbcf6f8 --- openstackclient/compute/client.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/openstackclient/compute/client.py b/openstackclient/compute/client.py index dd40b9a994..8ac5f32416 100644 --- a/openstackclient/compute/client.py +++ b/openstackclient/compute/client.py @@ -42,10 +42,12 @@ def make_client(instance): except ImportError: from novaclient.v1_1.contrib import list_extensions - compute_client = nova_client.get_client_class( - instance._api_version[API_NAME], - ) - LOG.debug('Instantiating compute client: %s', compute_client) + if _compute_api_version is not None: + version = _compute_api_version + else: + version = instance._api_version[API_NAME] + + LOG.debug('Instantiating compute client for V%s' % version) # Set client http_log_debug to True if verbosity level is high enough http_log_debug = utils.get_effective_log_level() <= logging.DEBUG @@ -55,10 +57,8 @@ def make_client(instance): # Remember interface only if it is set kwargs = utils.build_kwargs_dict('endpoint_type', instance._interface) - if _compute_api_version is not None: - kwargs.update({'api_version': _compute_api_version}) - - client = compute_client( + client = nova_client.Client( + version, session=instance.session, extensions=extensions, http_log_debug=http_log_debug, From a936c308936a0d24c3c023481104df82cada1dff Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Fri, 11 Sep 2015 06:10:25 +0000 Subject: [PATCH 0258/3095] Imported Translations from Transifex For more information about this automatic import see: https://wiki.openstack.org/wiki/Translations/Infrastructure Change-Id: I000bbddd938d7f83a8a85ccb7a62eb3a23f3dd46 --- .../de/LC_MESSAGES/python-openstackclient.po | 9 +- .../locale/python-openstackclient.pot | 230 +++++++++--------- 2 files changed, 123 insertions(+), 116 deletions(-) diff --git a/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po b/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po index 6c838a4a28..6e9acd7b33 100644 --- a/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po +++ b/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po @@ -9,9 +9,9 @@ msgid "" msgstr "" "Project-Id-Version: python-openstackclient\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-08-04 06:26+0000\n" -"PO-Revision-Date: 2015-06-14 18:41+0000\n" -"Last-Translator: openstackjenkins \n" +"POT-Creation-Date: 2015-09-11 06:10+0000\n" +"PO-Revision-Date: 2015-09-10 23:17+0000\n" +"Last-Translator: Ettore Atalan \n" "Language-Team: German (http://www.transifex.com/openstack/python-" "openstackclient/language/de/)\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" @@ -432,6 +432,9 @@ msgstr "Nach Bild suchen" msgid "Search by server status" msgstr "Nach Serverstatus suchen" +msgid "Search by user (admin only) (name or ID)" +msgstr "Nach Benutzer suchen (Nur Administrator) (Name oder Kennung)" + msgid "Security group to add (name or ID)" msgstr "Zu hinzufügende Sicherheitsgruppe (Name oder Kennung)" diff --git a/python-openstackclient/locale/python-openstackclient.pot b/python-openstackclient/locale/python-openstackclient.pot index e53b8249fb..f613a5a982 100644 --- a/python-openstackclient/locale/python-openstackclient.pot +++ b/python-openstackclient/locale/python-openstackclient.pot @@ -7,9 +7,9 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: python-openstackclient 1.5.1.dev136\n" +"Project-Id-Version: python-openstackclient 1.6.1.dev43\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-07-29 06:32+0000\n" +"POT-Creation-Date: 2015-09-11 06:10+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -48,7 +48,7 @@ msgid "Set a service URL, with --os-url, OS_URL or auth.url\n" msgstr "" #: openstackclient/compute/v2/availability_zone.py:72 -#: openstackclient/compute/v2/server.py:700 +#: openstackclient/compute/v2/server.py:718 #: openstackclient/identity/v2_0/endpoint.py:114 #: openstackclient/identity/v2_0/project.py:145 #: openstackclient/identity/v2_0/service.py:128 @@ -56,89 +56,89 @@ msgstr "" msgid "List additional fields in output" msgstr "" -#: openstackclient/compute/v2/server.py:154 -#: openstackclient/compute/v2/server.py:191 -#: openstackclient/compute/v2/server.py:542 -#: openstackclient/compute/v2/server.py:771 -#: openstackclient/compute/v2/server.py:805 -#: openstackclient/compute/v2/server.py:888 -#: openstackclient/compute/v2/server.py:912 -#: openstackclient/compute/v2/server.py:967 -#: openstackclient/compute/v2/server.py:1059 -#: openstackclient/compute/v2/server.py:1099 -#: openstackclient/compute/v2/server.py:1125 -#: openstackclient/compute/v2/server.py:1190 -#: openstackclient/compute/v2/server.py:1214 -#: openstackclient/compute/v2/server.py:1273 -#: openstackclient/compute/v2/server.py:1310 -#: openstackclient/compute/v2/server.py:1457 -#: openstackclient/compute/v2/server.py:1481 -#: openstackclient/compute/v2/server.py:1505 -#: openstackclient/compute/v2/server.py:1529 -#: openstackclient/compute/v2/server.py:1553 +#: openstackclient/compute/v2/server.py:161 +#: openstackclient/compute/v2/server.py:198 +#: openstackclient/compute/v2/server.py:549 +#: openstackclient/compute/v2/server.py:810 +#: openstackclient/compute/v2/server.py:844 +#: openstackclient/compute/v2/server.py:927 +#: openstackclient/compute/v2/server.py:951 +#: openstackclient/compute/v2/server.py:1006 +#: openstackclient/compute/v2/server.py:1098 +#: openstackclient/compute/v2/server.py:1138 +#: openstackclient/compute/v2/server.py:1164 +#: openstackclient/compute/v2/server.py:1229 +#: openstackclient/compute/v2/server.py:1253 +#: openstackclient/compute/v2/server.py:1312 +#: openstackclient/compute/v2/server.py:1349 +#: openstackclient/compute/v2/server.py:1496 +#: openstackclient/compute/v2/server.py:1520 +#: openstackclient/compute/v2/server.py:1544 +#: openstackclient/compute/v2/server.py:1568 +#: openstackclient/compute/v2/server.py:1592 msgid "Server (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:159 +#: openstackclient/compute/v2/server.py:166 msgid "Security group to add (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:196 +#: openstackclient/compute/v2/server.py:203 msgid "Volume to add (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:201 +#: openstackclient/compute/v2/server.py:208 msgid "Server internal device name for volume" msgstr "" -#: openstackclient/compute/v2/server.py:241 -#: openstackclient/compute/v2/server.py:1219 +#: openstackclient/compute/v2/server.py:248 +#: openstackclient/compute/v2/server.py:1258 msgid "New server name" msgstr "" -#: openstackclient/compute/v2/server.py:249 +#: openstackclient/compute/v2/server.py:256 msgid "Create server from this image" msgstr "" -#: openstackclient/compute/v2/server.py:254 +#: openstackclient/compute/v2/server.py:261 msgid "Create server from this volume" msgstr "" -#: openstackclient/compute/v2/server.py:260 +#: openstackclient/compute/v2/server.py:267 msgid "Create server with this flavor" msgstr "" -#: openstackclient/compute/v2/server.py:267 +#: openstackclient/compute/v2/server.py:274 msgid "Security group to assign to this server (repeat for multiple groups)" msgstr "" -#: openstackclient/compute/v2/server.py:273 +#: openstackclient/compute/v2/server.py:280 msgid "Keypair to inject into this server (optional extension)" msgstr "" -#: openstackclient/compute/v2/server.py:279 +#: openstackclient/compute/v2/server.py:286 msgid "Set a property on this server (repeat for multiple values)" msgstr "" -#: openstackclient/compute/v2/server.py:287 +#: openstackclient/compute/v2/server.py:294 msgid "File to inject into image before boot (repeat for multiple files)" msgstr "" -#: openstackclient/compute/v2/server.py:293 +#: openstackclient/compute/v2/server.py:300 msgid "User data file to serve from the metadata server" msgstr "" -#: openstackclient/compute/v2/server.py:298 +#: openstackclient/compute/v2/server.py:305 msgid "Select an availability zone for the server" msgstr "" -#: openstackclient/compute/v2/server.py:305 +#: openstackclient/compute/v2/server.py:312 msgid "" "Map block devices; map is ::: " "(optional extension)" msgstr "" -#: openstackclient/compute/v2/server.py:315 +#: openstackclient/compute/v2/server.py:322 msgid "" "Create a NIC on the server. Specify option multiple times to create " "multiple NICs. Either net-id or port-id must be provided, but not both. " @@ -147,314 +147,318 @@ msgid "" "-fixed-ip: IPv6 fixed address for NIC (optional)." msgstr "" -#: openstackclient/compute/v2/server.py:328 +#: openstackclient/compute/v2/server.py:335 msgid "Hints for the scheduler (optional extension)" msgstr "" -#: openstackclient/compute/v2/server.py:334 +#: openstackclient/compute/v2/server.py:341 msgid "" "Use specified volume as the config drive, or 'True' to use an ephemeral " "drive" msgstr "" -#: openstackclient/compute/v2/server.py:342 +#: openstackclient/compute/v2/server.py:349 msgid "Minimum number of servers to launch (default=1)" msgstr "" -#: openstackclient/compute/v2/server.py:349 +#: openstackclient/compute/v2/server.py:356 msgid "Maximum number of servers to launch (default=1)" msgstr "" -#: openstackclient/compute/v2/server.py:354 +#: openstackclient/compute/v2/server.py:361 msgid "Wait for build to complete" msgstr "" -#: openstackclient/compute/v2/server.py:394 +#: openstackclient/compute/v2/server.py:401 msgid "min instances should be <= max instances" msgstr "" -#: openstackclient/compute/v2/server.py:397 +#: openstackclient/compute/v2/server.py:404 msgid "min instances should be > 0" msgstr "" -#: openstackclient/compute/v2/server.py:400 +#: openstackclient/compute/v2/server.py:407 msgid "max instances should be > 0" msgstr "" -#: openstackclient/compute/v2/server.py:436 +#: openstackclient/compute/v2/server.py:443 msgid "either net-id or port-id should be specified but not both" msgstr "" -#: openstackclient/compute/v2/server.py:458 +#: openstackclient/compute/v2/server.py:465 msgid "can't create server with port specified since neutron not enabled" msgstr "" -#: openstackclient/compute/v2/server.py:523 +#: openstackclient/compute/v2/server.py:530 #, python-format msgid "Error creating server: %s" msgstr "" -#: openstackclient/compute/v2/server.py:525 +#: openstackclient/compute/v2/server.py:532 msgid "" "\n" "Error creating server" msgstr "" -#: openstackclient/compute/v2/server.py:547 +#: openstackclient/compute/v2/server.py:554 msgid "Name of new image (default is server name)" msgstr "" -#: openstackclient/compute/v2/server.py:552 +#: openstackclient/compute/v2/server.py:559 msgid "Wait for image create to complete" msgstr "" -#: openstackclient/compute/v2/server.py:582 +#: openstackclient/compute/v2/server.py:589 #, python-format msgid "Error creating server snapshot: %s" msgstr "" -#: openstackclient/compute/v2/server.py:584 +#: openstackclient/compute/v2/server.py:591 msgid "" "\n" "Error creating server snapshot" msgstr "" -#: openstackclient/compute/v2/server.py:606 +#: openstackclient/compute/v2/server.py:613 msgid "Server(s) to delete (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:611 +#: openstackclient/compute/v2/server.py:618 msgid "Wait for delete to complete" msgstr "" -#: openstackclient/compute/v2/server.py:630 +#: openstackclient/compute/v2/server.py:637 #, python-format msgid "Error deleting server: %s" msgstr "" -#: openstackclient/compute/v2/server.py:632 +#: openstackclient/compute/v2/server.py:639 msgid "" "\n" "Error deleting server" msgstr "" -#: openstackclient/compute/v2/server.py:647 +#: openstackclient/compute/v2/server.py:654 msgid "Only return instances that match the reservation" msgstr "" -#: openstackclient/compute/v2/server.py:652 +#: openstackclient/compute/v2/server.py:659 msgid "Regular expression to match IP addresses" msgstr "" -#: openstackclient/compute/v2/server.py:657 +#: openstackclient/compute/v2/server.py:664 msgid "Regular expression to match IPv6 addresses" msgstr "" -#: openstackclient/compute/v2/server.py:662 +#: openstackclient/compute/v2/server.py:669 msgid "Regular expression to match names" msgstr "" -#: openstackclient/compute/v2/server.py:667 +#: openstackclient/compute/v2/server.py:674 msgid "Regular expression to match instance name (admin only)" msgstr "" -#: openstackclient/compute/v2/server.py:673 +#: openstackclient/compute/v2/server.py:680 msgid "Search by server status" msgstr "" -#: openstackclient/compute/v2/server.py:678 +#: openstackclient/compute/v2/server.py:685 msgid "Search by flavor" msgstr "" -#: openstackclient/compute/v2/server.py:683 +#: openstackclient/compute/v2/server.py:690 msgid "Search by image" msgstr "" -#: openstackclient/compute/v2/server.py:688 +#: openstackclient/compute/v2/server.py:695 msgid "Search by hostname" msgstr "" -#: openstackclient/compute/v2/server.py:694 +#: openstackclient/compute/v2/server.py:701 msgid "Include all projects (admin only)" msgstr "" -#: openstackclient/compute/v2/server.py:810 +#: openstackclient/compute/v2/server.py:711 +msgid "Search by user (admin only) (name or ID)" +msgstr "" + +#: openstackclient/compute/v2/server.py:849 msgid "Target hostname" msgstr "" -#: openstackclient/compute/v2/server.py:818 +#: openstackclient/compute/v2/server.py:857 msgid "Perform a shared live migration (default)" msgstr "" -#: openstackclient/compute/v2/server.py:824 +#: openstackclient/compute/v2/server.py:863 msgid "Perform a block live migration" msgstr "" -#: openstackclient/compute/v2/server.py:831 +#: openstackclient/compute/v2/server.py:870 msgid "Allow disk over-commit on the destination host" msgstr "" -#: openstackclient/compute/v2/server.py:838 +#: openstackclient/compute/v2/server.py:877 msgid "Do not over-commit disk on the destination host (default)" msgstr "" -#: openstackclient/compute/v2/server.py:844 -#: openstackclient/compute/v2/server.py:1145 +#: openstackclient/compute/v2/server.py:883 +#: openstackclient/compute/v2/server.py:1184 msgid "Wait for resize to complete" msgstr "" -#: openstackclient/compute/v2/server.py:872 -#: openstackclient/compute/v2/server.py:1170 +#: openstackclient/compute/v2/server.py:911 +#: openstackclient/compute/v2/server.py:1209 msgid "Complete\n" msgstr "" -#: openstackclient/compute/v2/server.py:874 +#: openstackclient/compute/v2/server.py:913 msgid "" "\n" "Error migrating server" msgstr "" -#: openstackclient/compute/v2/server.py:921 +#: openstackclient/compute/v2/server.py:960 msgid "Perform a hard reboot" msgstr "" -#: openstackclient/compute/v2/server.py:929 +#: openstackclient/compute/v2/server.py:968 msgid "Perform a soft reboot" msgstr "" -#: openstackclient/compute/v2/server.py:934 +#: openstackclient/compute/v2/server.py:973 msgid "Wait for reboot to complete" msgstr "" -#: openstackclient/compute/v2/server.py:951 +#: openstackclient/compute/v2/server.py:990 msgid "" "\n" "Reboot complete\n" msgstr "" -#: openstackclient/compute/v2/server.py:953 +#: openstackclient/compute/v2/server.py:992 msgid "" "\n" "Error rebooting server\n" msgstr "" -#: openstackclient/compute/v2/server.py:973 +#: openstackclient/compute/v2/server.py:1012 msgid "Recreate server from this image" msgstr "" -#: openstackclient/compute/v2/server.py:983 +#: openstackclient/compute/v2/server.py:1022 msgid "Wait for rebuild to complete" msgstr "" -#: openstackclient/compute/v2/server.py:1004 +#: openstackclient/compute/v2/server.py:1043 msgid "" "\n" "Complete\n" msgstr "" -#: openstackclient/compute/v2/server.py:1006 +#: openstackclient/compute/v2/server.py:1045 msgid "" "\n" "Error rebuilding server" msgstr "" -#: openstackclient/compute/v2/server.py:1023 +#: openstackclient/compute/v2/server.py:1062 msgid "Name or ID of server to use" msgstr "" -#: openstackclient/compute/v2/server.py:1028 +#: openstackclient/compute/v2/server.py:1067 msgid "Name or ID of security group to remove from server" msgstr "" -#: openstackclient/compute/v2/server.py:1064 +#: openstackclient/compute/v2/server.py:1103 msgid "Volume to remove (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:1130 +#: openstackclient/compute/v2/server.py:1169 msgid "Resize server to specified flavor" msgstr "" -#: openstackclient/compute/v2/server.py:1135 +#: openstackclient/compute/v2/server.py:1174 msgid "Confirm server resize is complete" msgstr "" -#: openstackclient/compute/v2/server.py:1140 +#: openstackclient/compute/v2/server.py:1179 msgid "Restore server state before resize" msgstr "" -#: openstackclient/compute/v2/server.py:1172 +#: openstackclient/compute/v2/server.py:1211 msgid "" "\n" "Error resizing server" msgstr "" -#: openstackclient/compute/v2/server.py:1224 +#: openstackclient/compute/v2/server.py:1263 msgid "Set new root password (interactive only)" msgstr "" -#: openstackclient/compute/v2/server.py:1230 +#: openstackclient/compute/v2/server.py:1269 msgid "" "Property to add/change for this server (repeat option to set multiple " "properties)" msgstr "" -#: openstackclient/compute/v2/server.py:1254 +#: openstackclient/compute/v2/server.py:1293 msgid "New password: " msgstr "" -#: openstackclient/compute/v2/server.py:1255 +#: openstackclient/compute/v2/server.py:1294 msgid "Retype new password: " msgstr "" -#: openstackclient/compute/v2/server.py:1259 +#: openstackclient/compute/v2/server.py:1298 msgid "Passwords do not match, password unchanged" msgstr "" -#: openstackclient/compute/v2/server.py:1279 +#: openstackclient/compute/v2/server.py:1318 msgid "Display server diagnostics information" msgstr "" -#: openstackclient/compute/v2/server.py:1292 +#: openstackclient/compute/v2/server.py:1331 msgid "Error retrieving diagnostics data" msgstr "" -#: openstackclient/compute/v2/server.py:1315 +#: openstackclient/compute/v2/server.py:1354 msgid "Login name (ssh -l option)" msgstr "" -#: openstackclient/compute/v2/server.py:1327 +#: openstackclient/compute/v2/server.py:1366 msgid "Destination port (ssh -p option)" msgstr "" -#: openstackclient/compute/v2/server.py:1339 +#: openstackclient/compute/v2/server.py:1378 msgid "Private key file (ssh -i option)" msgstr "" -#: openstackclient/compute/v2/server.py:1350 +#: openstackclient/compute/v2/server.py:1389 msgid "Options in ssh_config(5) format (ssh -o option)" msgstr "" -#: openstackclient/compute/v2/server.py:1364 +#: openstackclient/compute/v2/server.py:1403 msgid "Use only IPv4 addresses" msgstr "" -#: openstackclient/compute/v2/server.py:1371 +#: openstackclient/compute/v2/server.py:1410 msgid "Use only IPv6 addresses" msgstr "" -#: openstackclient/compute/v2/server.py:1380 +#: openstackclient/compute/v2/server.py:1419 msgid "Use public IP address" msgstr "" -#: openstackclient/compute/v2/server.py:1388 +#: openstackclient/compute/v2/server.py:1427 msgid "Use private IP address" msgstr "" -#: openstackclient/compute/v2/server.py:1395 +#: openstackclient/compute/v2/server.py:1434 msgid "Use other IP address (public, private, etc)" msgstr "" -#: openstackclient/compute/v2/server.py:1560 +#: openstackclient/compute/v2/server.py:1599 msgid "Property key to remove from server (repeat to unset multiple values)" msgstr "" @@ -669,7 +673,7 @@ msgstr "" msgid "No service catalog with a type, name or ID of '%s' exists." msgstr "" -#: openstackclient/identity/v2_0/token.py:54 +#: openstackclient/identity/v2_0/token.py:55 msgid "Token to be deleted" msgstr "" From 9e6f99e2ab4763c5288e5e876dee2708d0ddc273 Mon Sep 17 00:00:00 2001 From: Andrey Kurilin Date: Fri, 11 Sep 2015 16:23:54 +0300 Subject: [PATCH 0259/3095] Use `discover_extensions` for novaclient novaclien v2.26.0 includes `discover_extensions` method, which returns list of all nova extensions based of version. Such method allows us to reduce imports of novaclient's modules and construct novaclient instance simpler. Change-Id: Idbe3ed275fb4a7e3918b11669dcfad47b8de4fb9 --- openstackclient/compute/client.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/openstackclient/compute/client.py b/openstackclient/compute/client.py index 8ac5f32416..23a4decaab 100644 --- a/openstackclient/compute/client.py +++ b/openstackclient/compute/client.py @@ -34,13 +34,8 @@ def make_client(instance): """Returns a compute service client.""" - # Defer client imports until we actually need them + # Defer client import until we actually need them from novaclient import client as nova_client - from novaclient import extension - try: - from novaclient.v2.contrib import list_extensions - except ImportError: - from novaclient.v1_1.contrib import list_extensions if _compute_api_version is not None: version = _compute_api_version @@ -52,7 +47,8 @@ def make_client(instance): # Set client http_log_debug to True if verbosity level is high enough http_log_debug = utils.get_effective_log_level() <= logging.DEBUG - extensions = [extension.Extension('list_extensions', list_extensions)] + extensions = [ext for ext in nova_client.discover_extensions(version) + if ext.name == "list_extensions"] # Remember interface only if it is set kwargs = utils.build_kwargs_dict('endpoint_type', instance._interface) From 1461c0eb179e5bf2bdb2c0527fae8b9785397b56 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sat, 12 Sep 2015 01:15:28 +0000 Subject: [PATCH 0260/3095] Updated from global requirements Change-Id: I63d1c3bdf6bdd5ff5e1c16af3ee4f095a7d659f1 --- requirements.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index 20a22fea73..88e449f8ba 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,20 +1,20 @@ # The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -pbr<2.0,>=1.4 +pbr<2.0,>=1.6 six>=1.9.0 Babel>=1.3 cliff>=1.14.0 # Apache-2.0 cliff-tablib>=1.0 os-client-config!=1.6.2,>=1.4.0 -oslo.config>=2.1.0 # Apache-2.0 +oslo.config>=2.3.0 # Apache-2.0 oslo.i18n>=1.5.0 # Apache-2.0 oslo.utils>=2.0.0 # Apache-2.0 python-glanceclient>=0.18.0 python-keystoneclient>=1.6.0 -python-novaclient>=2.26.0 +python-novaclient>=2.28.1 python-cinderclient>=1.3.1 -python-neutronclient<3,>=2.6.0 +python-neutronclient>=2.6.0 requests>=2.5.2 stevedore>=1.5.0 # Apache-2.0 From 817ab3ec0ea5a74aeda12850f39a56f332e9558b Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Wed, 16 Sep 2015 15:31:01 -0400 Subject: [PATCH 0261/3095] set image api to 1 for functional tests devstack now defaults to image api v2, but osc does not support v2 image create. set the functional tests to use v1 for now to unwedge the gate. Closes-Bug: #1496337 Change-Id: Ia02ed761446b8de52c932a424b9c423691ebcceb --- functional/tests/image/v1/test_image.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/functional/tests/image/v1/test_image.py b/functional/tests/image/v1/test_image.py index 9f6ddcc5df..17c0c3dd8f 100644 --- a/functional/tests/image/v1/test_image.py +++ b/functional/tests/image/v1/test_image.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import os import uuid from functional.common import test @@ -25,6 +26,7 @@ class ImageTests(test.TestCase): @classmethod def setUpClass(cls): + os.environ['OS_IMAGE_API_VERSION'] = '1' opts = cls.get_show_opts(cls.FIELDS) raw_output = cls.openstack('image create ' + cls.NAME + opts) expected = cls.NAME + '\n' From 1afd8f62cd5b59c9bd3042af6dbc4ba94717c023 Mon Sep 17 00:00:00 2001 From: NiallBunting Date: Fri, 18 Sep 2015 15:21:59 +0000 Subject: [PATCH 0262/3095] Image fix bug with --volume Currently after calling the cinderclient to create an image from a volume, it also then tries to create another image. This fails as the keyword volume is unexpected. This add checks so the image is not created in this case. Allowing --volume to not throw an error when it has worked. Change-Id: I67e650eb0b8c331d86515e3e326c39a5d6dad5e1 Closes-Bug: 1497221 --- openstackclient/image/v1/image.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index 68c81cd58d..81d384f71f 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -213,6 +213,8 @@ def take_action(self, parsed_args): if parsed_args.private: kwargs['is_public'] = False + info = {} + if not parsed_args.location and not parsed_args.copy_from: if parsed_args.volume: volume_client = self.app.client_manager.volume @@ -241,18 +243,18 @@ def take_action(self, parsed_args): # do a chunked transfer kwargs["data"] = sys.stdin - # Wrap the call to catch exceptions in order to close files - try: - image = image_client.images.create(**kwargs) - finally: - # Clean up open files - make sure data isn't a string - if ('data' in kwargs and hasattr(kwargs['data'], 'close') and - kwargs['data'] != sys.stdin): - kwargs['data'].close() - - info = {} - info.update(image._info) - info['properties'] = utils.format_dict(info.get('properties', {})) + if not parsed_args.volume: + # Wrap the call to catch exceptions in order to close files + try: + image = image_client.images.create(**kwargs) + finally: + # Clean up open files - make sure data isn't a string + if ('data' in kwargs and hasattr(kwargs['data'], 'close') and + kwargs['data'] != sys.stdin): + kwargs['data'].close() + + info.update(image._info) + info['properties'] = utils.format_dict(info.get('properties', {})) return zip(*sorted(six.iteritems(info))) From 9987a958415744fdd748058e3f92fbba7455d036 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Fri, 18 Sep 2015 16:42:31 +0000 Subject: [PATCH 0263/3095] Updated from global requirements Change-Id: Id0791447748e9430c995cbab87260e843e8a2819 --- requirements.txt | 2 +- setup.py | 2 +- test-requirements.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 88e449f8ba..c638ed1875 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ # The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -pbr<2.0,>=1.6 +pbr>=1.6 six>=1.9.0 Babel>=1.3 diff --git a/setup.py b/setup.py index d8080d05c8..782bb21f06 100644 --- a/setup.py +++ b/setup.py @@ -25,5 +25,5 @@ pass setuptools.setup( - setup_requires=['pbr>=1.3'], + setup_requires=['pbr>=1.8'], pbr=True) diff --git a/test-requirements.txt b/test-requirements.txt index 23edc37250..25d062898c 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -15,4 +15,4 @@ os-testr>=0.1.0 testrepository>=0.0.18 testtools>=1.4.0 WebOb>=1.2.3 -tempest-lib>=0.6.1 +tempest-lib>=0.8.0 From b1972fb5610ac22c256334f4de25b0278e1c1daa Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 18 Sep 2015 14:17:01 -0500 Subject: [PATCH 0264/3095] Set default auth plugin back to 'password' This was a hack that should be less needed now... Change-Id: Id8cba87ad05b106aa36e356c0d70a568316fd327 --- openstackclient/shell.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index c08d619d44..c7ce41b2a2 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -203,7 +203,7 @@ def initialize_app(self, argv): # Use service token authentication auth_type = 'token_endpoint' else: - auth_type = 'osc_password' + auth_type = 'password' project_id = getattr(self.options, 'project_id', None) project_name = getattr(self.options, 'project_name', None) From d6788f7e7594bdcfa7d5c6cd980a9cb54918f355 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 18 Sep 2015 14:28:55 -0500 Subject: [PATCH 0265/3095] Move option logging back to start if initialize_app() The log for the options should be printed early enough to see what is being passed in to occ. Change-Id: I97b09bc28abcd485b6793d0223b9f8602237fd80 --- openstackclient/shell.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index c08d619d44..335890407f 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -197,6 +197,8 @@ def initialize_app(self, argv): # Parent __init__ parses argv into self.options super(OpenStackShell, self).initialize_app(argv) + self.log.info("START with options: %s", self.command_options) + self.log.debug("options: %s", self.options) # Set the default plugin to token_endpoint if url and token are given if (self.options.url and self.options.token): @@ -239,8 +241,6 @@ def initialize_app(self, argv): self.log_configurator.configure(self.cloud) self.dump_stack_trace = self.log_configurator.dump_trace - self.log.info("START with options: %s", self.command_options) - self.log.debug("options: %s", self.options) self.log.debug("defaults: %s", cc.defaults) self.log.debug("cloud cfg: %s", self.cloud.config) From 3f532a2b69aa8d98e2fbf58c7f8c9f6aedbd0656 Mon Sep 17 00:00:00 2001 From: Hidekazu Nakamura Date: Thu, 17 Sep 2015 00:29:53 +0900 Subject: [PATCH 0266/3095] Remove backticks from help in role commands the docs and code had inconsistencies with how it references other arguments, lets just remove the backticks from around them. Change-Id: I43d17b07364e45387c6b9d86c2aca26eeea8ed93 --- doc/source/command-objects/role.rst | 18 +++++++++--------- openstackclient/identity/v3/role.py | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/doc/source/command-objects/role.rst b/doc/source/command-objects/role.rst index 9ba149ecbc..48751ed7bb 100644 --- a/doc/source/command-objects/role.rst +++ b/doc/source/command-objects/role.rst @@ -20,21 +20,21 @@ Add role to a user or group in a project or domain .. option:: --domain - Include `` (name or ID) + Include (name or ID) .. versionadded:: 3 .. option:: --project - Include `` (name or ID) + Include (name or ID) .. option:: --user - Include `` (name or ID) + Include (name or ID) .. option:: --group - Include `` (name or ID) + Include (name or ID) .. versionadded:: 3 @@ -67,7 +67,7 @@ Add role to a user or group in a project or domain .. describe:: - Role to add to ``:`` (name or ID) + Role to add to : (name or ID) role create ----------- @@ -186,21 +186,21 @@ Remove role from domain/project : user/group .. option:: --domain - Include `` (name or ID) + Include (name or ID) .. versionadded:: 3 .. option:: --project - Include `` (name or ID) + Include (name or ID) .. option:: --user - Include `` (name or ID) + Include (name or ID) .. option:: --group - Include `` (name or ID) + Include (name or ID) .. versionadded:: 3 diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index 46e2e440aa..c72de477dd 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -39,7 +39,7 @@ def _add_identity_and_resource_options_to_parser(parser): domain_or_project.add_argument( '--project', metavar='', - help='Include `` (name or ID)', + help='Include (name or ID)', ) user_or_group = parser.add_mutually_exclusive_group() user_or_group.add_argument( From 80ae715a09a53022514254866bac4604704eca04 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Mon, 21 Sep 2015 06:09:48 +0000 Subject: [PATCH 0267/3095] Imported Translations from Zanata For more information about this automatic import see: https://wiki.openstack.org/wiki/Translations/Infrastructure Change-Id: I2eadaa91b2a089bc54ab167a1d6e5f7fd7acb228 --- .../de/LC_MESSAGES/python-openstackclient.po | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po b/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po index 6e9acd7b33..675894307e 100644 --- a/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po +++ b/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po @@ -5,13 +5,15 @@ # # Translators: # Ettore Atalan , 2014-2015 +# Andreas Jaeger , 2015. #zanata +# OpenStack Infra , 2015. #zanata msgid "" msgstr "" -"Project-Id-Version: python-openstackclient\n" +"Project-Id-Version: python-openstackclient 1.6.1.dev56\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-09-11 06:10+0000\n" -"PO-Revision-Date: 2015-09-10 23:17+0000\n" -"Last-Translator: Ettore Atalan \n" +"POT-Creation-Date: 2015-09-21 06:09+0000\n" +"PO-Revision-Date: 2015-09-19 07:10+0000\n" +"Last-Translator: Andreas Jaeger \n" "Language-Team: German (http://www.transifex.com/openstack/python-" "openstackclient/language/de/)\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" @@ -433,7 +435,7 @@ msgid "Search by server status" msgstr "Nach Serverstatus suchen" msgid "Search by user (admin only) (name or ID)" -msgstr "Nach Benutzer suchen (Nur Administrator) (Name oder Kennung)" +msgstr "Nach Benutzer suchen (nur Administrator) (Name oder Kennung)" msgid "Security group to add (name or ID)" msgstr "Zu hinzufügende Sicherheitsgruppe (Name oder Kennung)" @@ -474,6 +476,16 @@ msgstr "" "Legen Sie eine Eigenschaft auf diesem Server fest (für mehrere Werte " "wiederholen)" +msgid "" +"Set a scope, such as a project or domain, set a project scope with --os-" +"project-name, OS_PROJECT_NAME or auth.project_name, set a domain scope with " +"--os-domain-name, OS_DOMAIN_NAME or auth.domain_name" +msgstr "" +"Setzen Sie eine Eigenschaft, wie Projekt oder Domäne, setzen Sie eine " +"Projekteigenschaft mit --os-project-name, OS_PROJECT_NAME oder auth." +"project_name, setzen Sie eine Dömaneneigenschaft mit --os-domain-name, " +"OS_DOMAIN_NAME oder auth.domain_name." + msgid "" "Set a service AUTH_URL, with --os-auth-url, OS_AUTH_URL or auth.auth_url\n" msgstr "" From 0857da76d9bfc30430f7de58a8c508c169a35843 Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Mon, 21 Sep 2015 14:54:16 +0000 Subject: [PATCH 0268/3095] Change ignore-errors to ignore_errors Needed for coverage 4.0 Change-Id: I38a9361df07f290ea3588996462bbbc1a6d7b8d8 --- .coveragerc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.coveragerc b/.coveragerc index 9c8f046281..2651b2ffef 100644 --- a/.coveragerc +++ b/.coveragerc @@ -4,4 +4,4 @@ source = openstackclient omit = openstackclient/openstack/* [report] -ignore-errors = True +ignore_errors = True From f5b50df8ea6de7e763f1c2e7079429d9c783f963 Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Sat, 19 Sep 2015 13:04:13 -0400 Subject: [PATCH 0269/3095] Add image create support for image v2 We have it for v1, but v2 is the future. There are two differences, things in v2 do not go into a properties dict, and the actual image data needs to get uploaded as a second step. Closes-Bug: 1405562 Co-Authored-By: Niall Bunting Co-Authored-By: Sean Perry Change-Id: If7b81c4a6746c8a1eb0302c96e045fb0f457d67b --- doc/source/command-objects/image.rst | 21 ++- openstackclient/image/v2/image.py | 187 ++++++++++++++++++- openstackclient/tests/image/v2/test_image.py | 186 ++++++++++++++++++ setup.cfg | 1 + 4 files changed, 393 insertions(+), 2 deletions(-) diff --git a/doc/source/command-objects/image.rst b/doc/source/command-objects/image.rst index 257414242d..8e44f517b5 100644 --- a/doc/source/command-objects/image.rst +++ b/doc/source/command-objects/image.rst @@ -7,7 +7,7 @@ Image v1, v2 image create ------------ -*Only supported for Image v1* +*Image v1, v2* Create/upload an image @@ -32,6 +32,7 @@ Create/upload an image [--protected | --unprotected] [--public | --private] [--property [...] ] + [--tag [...] ] .. option:: --id @@ -42,6 +43,8 @@ Create/upload an image Upload image to this store + *Image version 1 only.* + .. option:: --container-format Image container format (default: bare) @@ -54,10 +57,14 @@ Create/upload an image Image owner project name or ID + *Image version 1 only.* + .. option:: --size Image size, in bytes (only used with --location and --copy-from) + *Image version 1 only.* + .. option:: --min-disk Minimum disk size needed to boot image, in gigabytes @@ -70,10 +77,14 @@ Create/upload an image Download image from an existing URL + *Image version 1 only.* + .. option:: --copy-from Copy image from the data store (similar to --location) + *Image version 1 only.* + .. option:: --file Upload image from local file @@ -90,6 +101,8 @@ Create/upload an image Image hash used for verification + *Image version 1 only.* + .. option:: --protected Prevent image from being deleted @@ -110,6 +123,12 @@ Create/upload an image Set a property on this image (repeat for multiple values) +.. option:: --tag + + Set a tag on this image (repeat for multiple values) + + .. versionadded:: 2 + .. describe:: New image name diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 4c019db64a..67390118e7 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -22,14 +22,19 @@ from cliff import command from cliff import lister from cliff import show - from glanceclient.common import utils as gc_utils + from openstackclient.api import utils as api_utils +from openstackclient.common import exceptions from openstackclient.common import parseractions from openstackclient.common import utils from openstackclient.identity import common +DEFAULT_CONTAINER_FORMAT = 'bare' +DEFAULT_DISK_FORMAT = 'raw' + + class AddProjectToImage(show.ShowOne): """Associate project with image""" @@ -72,6 +77,186 @@ def take_action(self, parsed_args): return zip(*sorted(six.iteritems(image_member._info))) +class CreateImage(show.ShowOne): + """Create/upload an image""" + + log = logging.getLogger(__name__ + ".CreateImage") + deadopts = ('owner', 'size', 'location', 'copy-from', 'checksum', 'store') + + def get_parser(self, prog_name): + parser = super(CreateImage, self).get_parser(prog_name) + # TODO(mordred): add --volume and --force parameters and support + # TODO(bunting): There are additional arguments that v1 supported + # that v2 either doesn't support or supports weirdly. + # --checksum - could be faked clientside perhaps? + # --owner - could be set as an update after the put? + # --location - maybe location add? + # --size - passing image size is actually broken in python-glanceclient + # --copy-from - does not exist in v2 + # --store - does not exits in v2 + parser.add_argument( + "name", + metavar="", + help="New image name", + ) + parser.add_argument( + "--id", + metavar="", + help="Image ID to reserve", + ) + parser.add_argument( + "--container-format", + default=DEFAULT_CONTAINER_FORMAT, + metavar="", + help="Image container format " + "(default: %s)" % DEFAULT_CONTAINER_FORMAT, + ) + parser.add_argument( + "--disk-format", + default=DEFAULT_DISK_FORMAT, + metavar="", + help="Image disk format " + "(default: %s)" % DEFAULT_DISK_FORMAT, + ) + parser.add_argument( + "--min-disk", + metavar="", + type=int, + help="Minimum disk size needed to boot image, in gigabytes", + ) + parser.add_argument( + "--min-ram", + metavar="", + type=int, + help="Minimum RAM size needed to boot image, in megabytes", + ) + parser.add_argument( + "--file", + metavar="", + help="Upload image from local file", + ) + protected_group = parser.add_mutually_exclusive_group() + protected_group.add_argument( + "--protected", + action="store_true", + help="Prevent image from being deleted", + ) + protected_group.add_argument( + "--unprotected", + action="store_true", + help="Allow image to be deleted (default)", + ) + public_group = parser.add_mutually_exclusive_group() + public_group.add_argument( + "--public", + action="store_true", + help="Image is accessible to the public", + ) + public_group.add_argument( + "--private", + action="store_true", + help="Image is inaccessible to the public (default)", + ) + parser.add_argument( + "--property", + dest="properties", + metavar="", + action=parseractions.KeyValueAction, + help="Set a property on this image " + "(repeat option to set multiple properties)", + ) + parser.add_argument( + "--tag", + dest="tags", + metavar="", + action='append', + help="Set a tag on this image " + "(repeat option to set multiple tags)", + ) + for deadopt in self.deadopts: + parser.add_argument( + "--%s" % deadopt, + metavar="<%s>" % deadopt, + dest=deadopt.replace('-', '_'), + help=argparse.SUPPRESS + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + image_client = self.app.client_manager.image + + for deadopt in self.deadopts: + if getattr(parsed_args, deadopt.replace('-', '_'), None): + raise exceptions.CommandError( + "ERROR: --%s was given, which is an Image v1 option" + " that is no longer supported in Image v2" % deadopt) + + # Build an attribute dict from the parsed args, only include + # attributes that were actually set on the command line + kwargs = {} + copy_attrs = ('name', 'id', + 'container_format', 'disk_format', + 'min_disk', 'min_ram', + 'tags') + for attr in copy_attrs: + if attr in parsed_args: + val = getattr(parsed_args, attr, None) + if val: + # Only include a value in kwargs for attributes that + # are actually present on the command line + kwargs[attr] = val + # properties should get flattened into the general kwargs + if getattr(parsed_args, 'properties', None): + for k, v in six.iteritems(parsed_args.properties): + kwargs[k] = str(v) + # Handle exclusive booleans with care + # Avoid including attributes in kwargs if an option is not + # present on the command line. These exclusive booleans are not + # a single value for the pair of options because the default must be + # to do nothing when no options are present as opposed to always + # setting a default. + if parsed_args.protected: + kwargs['protected'] = True + if parsed_args.unprotected: + kwargs['protected'] = False + if parsed_args.public: + kwargs['visibility'] = 'public' + if parsed_args.private: + kwargs['visibility'] = 'private' + + # open the file first to ensure any failures are handled before the + # image is created + fp = gc_utils.get_data_file(parsed_args) + + if fp is None and parsed_args.file: + self.log.warning("Failed to get an image file.") + return {}, {} + + image = image_client.images.create(**kwargs) + + if fp is not None: + with fp: + try: + image_client.images.upload(image.id, fp) + except Exception as e: + # If the upload fails for some reason attempt to remove the + # dangling queued image made by the create() call above but + # only if the user did not specify an id which indicates + # the Image already exists and should be left alone. + try: + if 'id' not in kwargs: + image_client.images.delete(image.id) + except Exception: + pass # we don't care about this one + raise e # now, throw the upload exception again + + # update the image after the data has been uploaded + image = image_client.images.get(image.id) + + return zip(*sorted(six.iteritems(image))) + + class DeleteImage(command.Command): """Delete image(s)""" diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index bfb9476518..bb720d79c8 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -19,6 +19,7 @@ import warlock from glanceclient.v2 import schemas +from openstackclient.common import exceptions from openstackclient.image.v2 import image from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes @@ -41,6 +42,191 @@ def setUp(self): self.domain_mock.reset_mock() +class TestImageCreate(TestImage): + + def setUp(self): + super(TestImageCreate, self).setUp() + + self.images_mock.create.return_value = fakes.FakeResource( + None, + copy.deepcopy(image_fakes.IMAGE), + loaded=True, + ) + # This is the return value for utils.find_resource() + self.images_mock.get.return_value = copy.deepcopy(image_fakes.IMAGE) + self.images_mock.update.return_value = fakes.FakeResource( + None, + copy.deepcopy(image_fakes.IMAGE), + loaded=True, + ) + + # Get the command object to test + self.cmd = image.CreateImage(self.app, None) + + def test_image_reserve_no_options(self): + mock_exception = { + 'find.side_effect': exceptions.CommandError('x'), + } + self.images_mock.configure_mock(**mock_exception) + arglist = [ + image_fakes.image_name, + ] + verifylist = [ + ('container_format', image.DEFAULT_CONTAINER_FORMAT), + ('disk_format', image.DEFAULT_DISK_FORMAT), + ('name', image_fakes.image_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # ImageManager.create(name=, **) + self.images_mock.create.assert_called_with( + name=image_fakes.image_name, + container_format=image.DEFAULT_CONTAINER_FORMAT, + disk_format=image.DEFAULT_DISK_FORMAT, + ) + + # Verify update() was not called, if it was show the args + self.assertEqual(self.images_mock.update.call_args_list, []) + + self.images_mock.upload.assert_called_with( + mock.ANY, mock.ANY, + ) + + self.assertEqual(image_fakes.IMAGE_columns, columns) + self.assertEqual(image_fakes.IMAGE_data, data) + + @mock.patch('glanceclient.common.utils.get_data_file', name='Open') + def test_image_reserve_options(self, mock_open): + mock_file = mock.MagicMock(name='File') + mock_open.return_value = mock_file + mock_open.read.return_value = None + mock_exception = { + 'find.side_effect': exceptions.CommandError('x'), + } + self.images_mock.configure_mock(**mock_exception) + arglist = [ + '--container-format', 'ovf', + '--disk-format', 'fs', + '--min-disk', '10', + '--min-ram', '4', + '--protected', + '--private', + image_fakes.image_name, + ] + verifylist = [ + ('container_format', 'ovf'), + ('disk_format', 'fs'), + ('min_disk', 10), + ('min_ram', 4), + ('protected', True), + ('unprotected', False), + ('public', False), + ('private', True), + ('name', image_fakes.image_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # ImageManager.create(name=, **) + self.images_mock.create.assert_called_with( + name=image_fakes.image_name, + container_format='ovf', + disk_format='fs', + min_disk=10, + min_ram=4, + protected=True, + visibility='private', + ) + + # Verify update() was not called, if it was show the args + self.assertEqual(self.images_mock.update.call_args_list, []) + + self.images_mock.upload.assert_called_with( + mock.ANY, mock.ANY, + ) + + self.assertEqual(image_fakes.IMAGE_columns, columns) + self.assertEqual(image_fakes.IMAGE_data, data) + + @mock.patch('glanceclient.common.utils.get_data_file', name='Open') + def test_image_create_file(self, mock_open): + mock_file = mock.MagicMock(name='File') + mock_open.return_value = mock_file + mock_open.read.return_value = image_fakes.IMAGE_data + mock_exception = { + 'find.side_effect': exceptions.CommandError('x'), + } + self.images_mock.configure_mock(**mock_exception) + + arglist = [ + '--file', 'filer', + '--unprotected', + '--public', + '--property', 'Alpha=1', + '--property', 'Beta=2', + '--tag', 'awesome', + '--tag', 'better', + image_fakes.image_name, + ] + verifylist = [ + ('file', 'filer'), + ('protected', False), + ('unprotected', True), + ('public', True), + ('private', False), + ('properties', {'Alpha': '1', 'Beta': '2'}), + ('tags', ['awesome', 'better']), + ('name', image_fakes.image_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # ImageManager.create(name=, **) + self.images_mock.create.assert_called_with( + name=image_fakes.image_name, + container_format=image.DEFAULT_CONTAINER_FORMAT, + disk_format=image.DEFAULT_DISK_FORMAT, + protected=False, + visibility='public', + Alpha='1', + Beta='2', + tags=['awesome', 'better'], + ) + + # Verify update() was not called, if it was show the args + self.assertEqual(self.images_mock.update.call_args_list, []) + + self.images_mock.upload.assert_called_with( + mock.ANY, mock.ANY, + ) + + self.assertEqual(image_fakes.IMAGE_columns, columns) + self.assertEqual(image_fakes.IMAGE_data, data) + + def test_image_dead_options(self): + + arglist = [ + '--owner', 'nobody', + image_fakes.image_name, + ] + verifylist = [ + ('owner', 'nobody'), + ('name', image_fakes.image_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, parsed_args) + + class TestAddProjectToImage(TestImage): def setUp(self): diff --git a/setup.cfg b/setup.cfg index f2f4833b2f..0b9368254b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -316,6 +316,7 @@ openstack.image.v1 = openstack.image.v2 = image_add_project = openstackclient.image.v2.image:AddProjectToImage + image_create = openstackclient.image.v2.image:CreateImage image_delete = openstackclient.image.v2.image:DeleteImage image_list = openstackclient.image.v2.image:ListImage image_remove_project = openstackclient.image.v2.image:RemoveProjectImage From d8f7527ff2231c4755b0bce28d1ad38fe11a9370 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Sun, 20 Sep 2015 15:44:00 -0400 Subject: [PATCH 0270/3095] Format an images properties and tags Currently, these properties are each top level keys, they should all be under a single 'properties' field. Secondly, the tags are kept as an array, but can be shown as a comma separated string. Change-Id: Ic769c657a86e768fee38acc40434c377de70a7bc --- openstackclient/image/v2/image.py | 38 ++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 67390118e7..01468c4d11 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -35,6 +35,38 @@ DEFAULT_DISK_FORMAT = 'raw' +def _format_image(image): + """Format an image to make it more consistent with OSC operations. """ + + info = {} + properties = {} + + # the only fields we're not including is "links", "tags" and the properties + fields_to_show = ['status', 'name', 'container_format', 'created_at', + 'size', 'disk_format', 'updated_at', 'visibility', + 'min_disk', 'protected', 'id', 'file', 'checksum', + 'owner', 'virtual_size', 'min_ram', 'schema'] + + # split out the usual key and the properties which are top-level + for key in six.iterkeys(image): + if key in fields_to_show: + info[key] = image.get(key) + elif key == 'tags': + continue # handle this later + else: + properties[key] = image.get(key) + + # format the tags if they are there + if image.get('tags'): + info['tags'] = utils.format_list(image.get('tags')) + + # add properties back into the dictionary as a top-level key + if properties: + info['properties'] = utils.format_dict(properties) + + return info + + class AddProjectToImage(show.ShowOne): """Associate project with image""" @@ -254,7 +286,8 @@ def take_action(self, parsed_args): # update the image after the data has been uploaded image = image_client.images.get(image.id) - return zip(*sorted(six.iteritems(image))) + info = _format_image(image) + return zip(*sorted(six.iteritems(info))) class DeleteImage(command.Command): @@ -512,8 +545,7 @@ def take_action(self, parsed_args): parsed_args.image, ) - info = {} - info.update(image) + info = _format_image(image) return zip(*sorted(six.iteritems(info))) From e0e9b2bfaee72a8b7c0619faefc86f1bbcdf7414 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Mon, 21 Sep 2015 15:13:48 -0400 Subject: [PATCH 0271/3095] Use format_list instead of format_dict when listing images This currently breaks listing images with --long. Tags are an array and shouldn't be formatted as a dictionary. Change-Id: I6d1d85351b58ae4824498774673ebdc8eaa7e420 Closes-Bug: #1498150 --- openstackclient/image/v2/image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 4c019db64a..058f7f2d53 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -229,7 +229,7 @@ def take_action(self, parsed_args): s, columns, formatters={ - 'tags': utils.format_dict, + 'tags': utils.format_list, }, ) for s in data) ) From b8faa8ae8702d2739ee353fa09933dbe99123b25 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Tue, 22 Sep 2015 09:13:15 -0500 Subject: [PATCH 0272/3095] Add release notes for 1.7.0 Change-Id: I4b4d229f7c3292923c8a29d1e1182a8352688b5d --- doc/source/releases.rst | 42 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/doc/source/releases.rst b/doc/source/releases.rst index 5dd6771932..526b1635c7 100644 --- a/doc/source/releases.rst +++ b/doc/source/releases.rst @@ -2,6 +2,48 @@ Release Notes ============= +1.7.0 (22 Sep 2015) +=================== + +* Add support for v2 image create/update commands + Bug `1405562 `_ + +* ICMP secgroup rule must have ``--dst-port -1`` to actually allow ICMP + Bug `1477629 `_ + +* Add a blurb about ``pip install -e .`` to the developer docs + Bug `1483384 `_ + +* Add filtering by user for command 'openstack server list' + Bug `1483974 `_ + +* No support for listing servers of other project with openstack server list + Bug `1488486 `_ + +* Missing image causes confusing 'server show' response + Bug `1489901 `_ + +* Volume Attached to is incorrect + Bug `1489954 `_ + +* novaclient 2.27.0 breaks version handling + Bug `1492467 `_ + +* Support listing users in a non-default domain scoped group + Bug `1492916 `_ + +* ERROR: InvocationError: + '/opt/stack/new/python-openstackclient/.tox/functional/bin/ostestr' + in gate-osc-dsvm-functional + Bug `1496337 `_ + +* image list --long is broken with v2 + Bug `1498150 `_ + +* Add ``--log-file`` option support + +* Set default Volume API version to ``2`` + 1.6.0 (10 Aug 2015) =================== From 8faabb3bbaa199cce8a52d6e6ed40b15e4a3a000 Mon Sep 17 00:00:00 2001 From: NiallBunting Date: Tue, 22 Sep 2015 09:59:30 +0000 Subject: [PATCH 0273/3095] Glance `image set` Resolve Fracturing Currently `image set` uses the new api, where other parts of osc the old api is used. This deprecates the v2 api in favour of the v1 to maintain the same commands across osc. However the functionality now remains there as people could now be using this functionality. This also adds the --unprotected argument, as in the previous version if --protected was not supplied it would just make the argument --unprotected without the users explicit consent. The patch also fixes the documentation for image set as it was outdated. Change-Id: I990d20332c80165102badef7ac94ddbeb7824950 Closes-Bug: 1498092 --- doc/source/command-objects/image.rst | 60 +++++++++++++++++- openstackclient/image/v2/image.py | 65 ++++++++++++++++++-- openstackclient/tests/image/v2/test_image.py | 3 +- 3 files changed, 119 insertions(+), 9 deletions(-) diff --git a/doc/source/command-objects/image.rst b/doc/source/command-objects/image.rst index 257414242d..b62f6e0b01 100644 --- a/doc/source/command-objects/image.rst +++ b/doc/source/command-objects/image.rst @@ -193,7 +193,7 @@ Save an image locally image set --------- -*Only supported for Image v1* +*Image v1, v2* Set image properties @@ -252,6 +252,8 @@ Set image properties Size of image data (in bytes) + *Image version 1 only.* + .. option:: --protected Prevent image from being deleted @@ -272,38 +274,94 @@ Set image properties Upload image to this store + *Image version 1 only.* + .. option:: --location Download image from an existing URL + *Image version 1 only.* + .. option:: --copy-from Copy image from the data store (similar to --location) + *Image version 1 only.* + .. option:: --file Upload image from local file + *Image version 1 only.* + .. option:: --volume Update image with a volume + *Image version 1 only.* + .. option:: --force Force image update if volume is in use (only meaningful with --volume) + *Image version 1 only.* + .. option:: --checksum Image hash used for verification + *Image version 1 only.* + .. option:: --stdin Allow to read image data from standard input + *Image version 1 only.* + .. option:: --property Set a property on this image (repeat for multiple values) + *Image version 1 only.* + +.. option:: --architecture + + Operating system Architecture + + .. versionadded:: 2 + +.. option:: --ramdisk-id + + ID of image stored in Glance that should be used as + the ramdisk when booting an AMI-style image + + .. versionadded:: 2 + +.. option:: --os-distro + + Common name of operating system distribution + + .. versionadded:: 2 + +.. option:: --os-version + + Operating system version as specified by the distributor + + .. versionadded:: 2 + +.. option:: --kernel-id + + ID of image in Glance that should be used as the + kernel when booting an AMI-style image + + .. versionadded:: 2 + +.. option:: --instance-uuid + + ID of instance used to create this image + + .. versionadded:: 2 + .. describe:: Image to modify (name or ID) diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 4c019db64a..a8a0c1090b 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -25,6 +25,7 @@ from glanceclient.common import utils as gc_utils from openstackclient.api import utils as api_utils +from openstackclient.common import exceptions from openstackclient.common import parseractions from openstackclient.common import utils from openstackclient.identity import common @@ -336,9 +337,22 @@ class SetImage(show.ShowOne): """Set image properties""" log = logging.getLogger(__name__ + ".SetImage") + deadopts = ('size', 'store', 'location', 'copy-from', 'checksum') def get_parser(self, prog_name): parser = super(SetImage, self).get_parser(prog_name) + # TODO(bunting): There are additional arguments that v1 supported + # --size - does not exist in v2 + # --store - does not exist in v2 + # --location - maybe location add? + # --copy-from - does not exist in v2 + # --file - should be able to upload file + # --volume - needs adding + # --force - needs adding + # --checksum - maybe could be done client side + # --stdin - could be implemented + # --property - needs adding + # --tags - needs adding parser.add_argument( "image", metavar="", @@ -354,12 +368,28 @@ def get_parser(self, prog_name): metavar="", help="Operating system Architecture" ) - parser.add_argument( + protected_group = parser.add_mutually_exclusive_group() + protected_group.add_argument( "--protected", - dest="protected", action="store_true", help="Prevent image from being deleted" ) + protected_group.add_argument( + "--unprotected", + action="store_true", + help="Allow image to be deleted (default)" + ) + public_group = parser.add_mutually_exclusive_group() + public_group.add_argument( + "--public", + action="store_true", + help="Image is accessible to the public", + ) + public_group.add_argument( + "--private", + action="store_true", + help="Image is inaccessible to the public (default)", + ) parser.add_argument( "--instance-uuid", metavar="", @@ -372,12 +402,11 @@ def get_parser(self, prog_name): help="Minimum disk size needed to boot image, in gigabytes" ) visibility_choices = ["public", "private"] - parser.add_argument( + public_group.add_argument( "--visibility", metavar="", choices=visibility_choices, - help="Scope of image accessibility. Valid values: %s" - % visibility_choices + help=argparse.SUPPRESS ) help_msg = ("ID of image in Glance that should be used as the kernel" " when booting an AMI-style image") @@ -432,12 +461,25 @@ def get_parser(self, prog_name): choices=container_choices, help=help_msg ) + for deadopt in self.deadopts: + parser.add_argument( + "--%s" % deadopt, + metavar="<%s>" % deadopt, + dest=deadopt.replace('-', '_'), + help=argparse.SUPPRESS + ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) image_client = self.app.client_manager.image + for deadopt in self.deadopts: + if getattr(parsed_args, deadopt.replace('-', '_'), None): + raise exceptions.CommandError( + "ERROR: --%s was given, which is an Image v1 option" + " that is no longer supported in Image v2" % deadopt) + kwargs = {} copy_attrs = ('architecture', 'container_format', 'disk_format', 'file', 'kernel_id', 'locations', 'name', @@ -451,10 +493,21 @@ def take_action(self, parsed_args): # Only include a value in kwargs for attributes that are # actually present on the command line kwargs[attr] = val + + # Handle exclusive booleans with care + # Avoid including attributes in kwargs if an option is not + # present on the command line. These exclusive booleans are not + # a single value for the pair of options because the default must be + # to do nothing when no options are present as opposed to always + # setting a default. if parsed_args.protected: kwargs['protected'] = True - else: + if parsed_args.unprotected: kwargs['protected'] = False + if parsed_args.public: + kwargs['visibility'] = 'public' + if parsed_args.private: + kwargs['visibility'] = 'private' if not kwargs: self.log.warning("No arguments specified") diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index bfb9476518..0c4aad2768 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -527,8 +527,7 @@ def test_image_set_options(self): 'name': 'new-name', 'owner': 'new-owner', 'min_disk': 2, - 'min_ram': 4, - 'protected': False + 'min_ram': 4 } # ImageManager.update(image, **kwargs) self.images_mock.update.assert_called_with( From 0e89d084343bcda55db737c3286a17ec695befaf Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Thu, 24 Sep 2015 11:50:46 -0400 Subject: [PATCH 0274/3095] docs: pip install -e needs an argument The arguments to install the development branch is incorrect, since the -e option needs a directory to look for setup.py in. Change-Id: Icfe402e7b79a50ddc885c7eadb6c323c27e36ef3 --- doc/source/developing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/developing.rst b/doc/source/developing.rst index 90f0900ab0..c41fbf3472 100644 --- a/doc/source/developing.rst +++ b/doc/source/developing.rst @@ -113,4 +113,4 @@ or .. code-block:: bash - $ pip install -e + $ pip install -e . From e52dfce7bd12590a2fb4383eb0a57d941353019c Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Fri, 25 Sep 2015 00:01:49 -0400 Subject: [PATCH 0275/3095] Add shields.io version/downloads links/badges into README.rst it's handy to have appear in the pypi page Change-Id: I142da7d194d719f3b8218ad84e32e19bf0071c63 --- README.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.rst b/README.rst index 1bfef94b20..7a92b1c997 100644 --- a/README.rst +++ b/README.rst @@ -2,6 +2,14 @@ OpenStackClient =============== +.. image:: https://img.shields.io/pypi/v/python-openstackclient.svg + :target: https://pypi.python.org/pypi/python-openstackclient/ + :alt: Latest Version + +.. image:: https://img.shields.io/pypi/dm/python-openstackclient.svg + :target: https://pypi.python.org/pypi/python-openstackclient/ + :alt: Downloads + OpenStackClient (aka OSC) is a command-line client for OpenStack that brings the command set for Compute, Identity, Image, Object Store and Volume APIs together in a single shell with a uniform command structure. From 05f5e043d8cc536c21acb51c5a9e85fac9563f47 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Thu, 24 Sep 2015 23:24:44 -0400 Subject: [PATCH 0276/3095] Additional exception handling for find_resource A few things here: 1) we need to check if the client class even has a 'resource_class', in the case of glanceclient, it does not. 2) If everything fails we should print a better error message, rather than a "find" failed, since some clients don't support find. Change-Id: I6277322639e75b1635f9f3d159753efadbce1031 --- openstackclient/common/utils.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index b6726bfa84..7a5d33e3b0 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -94,12 +94,15 @@ def find_resource(manager, name_or_id, **kwargs): if len(kwargs) == 0: kwargs = {} - # Prepare the kwargs for calling find - if 'NAME_ATTR' in manager.resource_class.__dict__: - # novaclient does this for oddball resources - kwargs[manager.resource_class.NAME_ATTR] = name_or_id - else: - kwargs['name'] = name_or_id + try: + # Prepare the kwargs for calling find + if 'NAME_ATTR' in manager.resource_class.__dict__: + # novaclient does this for oddball resources + kwargs[manager.resource_class.NAME_ATTR] = name_or_id + else: + kwargs['name'] = name_or_id + except Exception: + pass # finally try to find entity by name try: @@ -118,7 +121,8 @@ def find_resource(manager, name_or_id, **kwargs): (manager.resource_class.__name__.lower(), name_or_id) raise exceptions.CommandError(msg) else: - raise + msg = "Could not find resource %s" % name_or_id + raise exceptions.CommandError(msg) def format_dict(data): From 83282bc5e133d8cd1718df524c1fa06add130b8a Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Thu, 24 Sep 2015 23:39:37 -0400 Subject: [PATCH 0277/3095] attempt to find resource by listing add a last-ditch effort to find the resource in question by listing all the resources and doing a simply match for name and id. if no match is found then raise an error, if the list call is unsuccessful, raise the same error. we have failed this city. Closes-Bug: #1501362 Change-Id: I0d3d7002e9ac47b17b1ef1a5534406c85b1fc753 --- openstackclient/common/utils.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index 7a5d33e3b0..51e2a2f9ca 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -121,8 +121,24 @@ def find_resource(manager, name_or_id, **kwargs): (manager.resource_class.__name__.lower(), name_or_id) raise exceptions.CommandError(msg) else: - msg = "Could not find resource %s" % name_or_id - raise exceptions.CommandError(msg) + pass + + try: + for resource in manager.list(): + # short circuit and return the first match + if (resource.get('id') == name_or_id or + resource.get('name') == name_or_id): + return resource + else: + # we found no match, keep going to bomb out + pass + except Exception: + # in case the list fails for some reason + pass + + # if we hit here, we've failed, report back this error: + msg = "Could not find resource %s" % name_or_id + raise exceptions.CommandError(msg) def format_dict(data): From b33cdec92ab3707886c12f49e9db27981114b35d Mon Sep 17 00:00:00 2001 From: Sean Perry Date: Wed, 23 Sep 2015 10:39:13 -0700 Subject: [PATCH 0278/3095] Mark arguments for 'credential' commands as required According to the [1], 'user_id', 'type', and 'blob' are all required arguments for 'credential set' but the code treats them as optional. Set the 'required' flag and remove logic supporting missing arguments. [1]: https://github.com/openstack/keystone-specs/blob/master/api/v3/identity-api-v3.rst#credentials-v3credentials "spec" Change-Id: I597c9616ad744385fc6dd92379feb03daec54458 Closes-Bug: #1418837 --- openstackclient/identity/v3/credential.py | 31 +++-- openstackclient/tests/identity/v3/fakes.py | 4 + .../tests/identity/v3/test_credential.py | 112 ++++++++++++++++++ 3 files changed, 131 insertions(+), 16 deletions(-) create mode 100644 openstackclient/tests/identity/v3/test_credential.py diff --git a/openstackclient/identity/v3/credential.py b/openstackclient/identity/v3/credential.py index dbd73e2e38..f22092d4db 100644 --- a/openstackclient/identity/v3/credential.py +++ b/openstackclient/identity/v3/credential.py @@ -17,7 +17,6 @@ import logging import six -import sys from cliff import command from cliff import lister @@ -130,17 +129,20 @@ def get_parser(self, prog_name): parser.add_argument( '--user', metavar='', + required=True, help='Name or ID of user that owns the credential', ) parser.add_argument( '--type', metavar='', choices=['ec2', 'cert'], + required=True, help='New credential type', ) parser.add_argument( '--data', metavar='', + required=True, help='New credential data', ) parser.add_argument( @@ -153,25 +155,22 @@ def get_parser(self, prog_name): @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - kwargs = {} - if parsed_args.user: - user_id = utils.find_resource(identity_client.users, - parsed_args.user).id - if user_id: - kwargs['user'] = user_id - if parsed_args.type: - kwargs['type'] = parsed_args.type - if parsed_args.data: - kwargs['data'] = parsed_args.data + + user_id = utils.find_resource(identity_client.users, + parsed_args.user).id + if parsed_args.project: project = utils.find_resource(identity_client.projects, parsed_args.project).id - kwargs['project'] = project + else: + project = None + + identity_client.credentials.update(parsed_args.credential, + user=user_id, + type=parsed_args.type, + blob=parsed_args.data, + project=project) - if not kwargs: - sys.stdout.write("Credential not updated, no arguments present") - return - identity_client.credentials.update(parsed_args.credential, **kwargs) return diff --git a/openstackclient/tests/identity/v3/fakes.py b/openstackclient/tests/identity/v3/fakes.py index 9c4de9cc0d..e1793ca16f 100644 --- a/openstackclient/tests/identity/v3/fakes.py +++ b/openstackclient/tests/identity/v3/fakes.py @@ -195,6 +195,8 @@ 'links': base_url + 'services/' + service_id, } +credential_id = 'c-123' + endpoint_id = 'e-123' endpoint_url = 'http://127.0.0.1:35357' endpoint_region = 'RegionOne' @@ -400,6 +402,8 @@ class FakeIdentityv3Client(object): def __init__(self, **kwargs): self.domains = mock.Mock() self.domains.resource_class = fakes.FakeResource(None, {}) + self.credentials = mock.Mock() + self.credentials.resource_class = fakes.FakeResource(None, {}) self.endpoints = mock.Mock() self.endpoints.resource_class = fakes.FakeResource(None, {}) self.groups = mock.Mock() diff --git a/openstackclient/tests/identity/v3/test_credential.py b/openstackclient/tests/identity/v3/test_credential.py new file mode 100644 index 0000000000..5adcf7fa46 --- /dev/null +++ b/openstackclient/tests/identity/v3/test_credential.py @@ -0,0 +1,112 @@ +# 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 json + +from openstackclient.identity.v3 import credential +from openstackclient.tests.identity.v3 import fakes as identity_fakes +from openstackclient.tests.utils import ParserException + + +class TestCredential(identity_fakes.TestIdentityv3): + data = { + "access": "abc123", + "secret": "hidden-message", + "trust_id": None + } + + def __init__(self, *args): + super(TestCredential, self).__init__(*args) + + self.json_data = json.dumps(self.data) + + def setUp(self): + super(TestCredential, self).setUp() + + # Get a shortcut to the CredentialManager Mock + self.credentials_mock = self.app.client_manager.identity.credentials + self.credentials_mock.reset_mock() + + # Get a shortcut to the UserManager Mock + self.users_mock = self.app.client_manager.identity.users + self.users_mock.reset_mock() + + # Get a shortcut to the ProjectManager Mock + self.projects_mock = self.app.client_manager.identity.projects + self.projects_mock.reset_mock() + + +class TestCredentialSet(TestCredential): + def setUp(self): + super(TestCredentialSet, self).setUp() + self.cmd = credential.SetCredential(self.app, None) + + def test_credential_set_no_options(self): + arglist = [ + identity_fakes.credential_id, + ] + + self.assertRaises(ParserException, + self.check_parser, self.cmd, arglist, []) + + def test_credential_set_missing_user(self): + arglist = [ + '--type', 'ec2', + '--data', self.json_data, + identity_fakes.credential_id, + ] + + self.assertRaises(ParserException, + self.check_parser, self.cmd, arglist, []) + + def test_credential_set_missing_type(self): + arglist = [ + '--user', identity_fakes.user_name, + '--data', self.json_data, + identity_fakes.credential_id, + ] + + self.assertRaises(ParserException, + self.check_parser, self.cmd, arglist, []) + + def test_credential_set_missing_data(self): + arglist = [ + '--user', identity_fakes.user_name, + '--type', 'ec2', + identity_fakes.credential_id, + ] + + self.assertRaises(ParserException, + self.check_parser, self.cmd, arglist, []) + + def test_credential_set_valid(self): + arglist = [ + '--user', identity_fakes.user_name, + '--type', 'ec2', + '--data', self.json_data, + identity_fakes.credential_id, + ] + + parsed_args = self.check_parser(self.cmd, arglist, []) + self.cmd.take_action(parsed_args) + + def test_credential_set_valid_with_project(self): + arglist = [ + '--user', identity_fakes.user_name, + '--type', 'ec2', + '--data', self.json_data, + '--project', identity_fakes.project_name, + identity_fakes.credential_id, + ] + + parsed_args = self.check_parser(self.cmd, arglist, []) + self.cmd.take_action(parsed_args) From df85380ffb3d2b7df90be7905a7013fc33d1ea3b Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 1 Oct 2015 06:12:57 +0000 Subject: [PATCH 0279/3095] Imported Translations from Zanata For more information about this automatic import see: https://wiki.openstack.org/wiki/Translations/Infrastructure Change-Id: Ida5289f0229020953ec715c4a547ab030fbf75e7 --- .../locale/de/LC_MESSAGES/python-openstackclient.po | 7 ++++--- .../locale/zh_TW/LC_MESSAGES/python-openstackclient.po | 10 ++++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po b/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po index 675894307e..c95391fe6b 100644 --- a/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po +++ b/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po @@ -9,18 +9,19 @@ # OpenStack Infra , 2015. #zanata msgid "" msgstr "" -"Project-Id-Version: python-openstackclient 1.6.1.dev56\n" +"Project-Id-Version: python-openstackclient 1.7.1.dev7\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-09-21 06:09+0000\n" +"POT-Creation-Date: 2015-10-01 06:12+0000\n" "PO-Revision-Date: 2015-09-19 07:10+0000\n" "Last-Translator: Andreas Jaeger \n" +"Language: de\n" "Language-Team: German (http://www.transifex.com/openstack/python-" "openstackclient/language/de/)\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.0\n" +"Generated-By: Babel 2.1.1\n" msgid "" "\n" diff --git a/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po b/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po index 0680a23ec9..d8d070d2ff 100644 --- a/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po +++ b/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po @@ -4,20 +4,22 @@ # python-openstackclient project. # # Translators: +# OpenStack Infra , 2015. #zanata msgid "" msgstr "" -"Project-Id-Version: python-openstackclient\n" +"Project-Id-Version: python-openstackclient 1.7.1.dev7\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-08-04 06:26+0000\n" -"PO-Revision-Date: 2015-06-14 18:41+0000\n" +"POT-Creation-Date: 2015-10-01 06:12+0000\n" +"PO-Revision-Date: 2015-06-14 06:41+0000\n" "Last-Translator: openstackjenkins \n" +"Language: zh_Hant_TW\n" "Language-Team: Chinese (Taiwan) (http://www.transifex.com/openstack/python-" "openstackclient/language/zh_TW/)\n" "Plural-Forms: nplurals=1; plural=0\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.0\n" +"Generated-By: Babel 2.1.1\n" msgid "" "\n" From 97659adf53c28ae77e47573492636d55a549947f Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 1 Oct 2015 11:26:02 -0500 Subject: [PATCH 0280/3095] Add tests for find_resource() Add a couple of tests for utils.find_resource() for the odd resources and managers without resource_class. Change-Id: I2ed9b491d1361b5259b3a5f80b4fac787a7087c1 --- openstackclient/tests/common/test_utils.py | 54 ++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/openstackclient/tests/common/test_utils.py b/openstackclient/tests/common/test_utils.py index a25a5ba510..373c0de4d8 100644 --- a/openstackclient/tests/common/test_utils.py +++ b/openstackclient/tests/common/test_utils.py @@ -20,6 +20,7 @@ from openstackclient.common import exceptions from openstackclient.common import utils +from openstackclient.tests import fakes from openstackclient.tests import utils as test_utils PASSWORD = "Pa$$w0rd" @@ -27,6 +28,18 @@ DROWSSAP = "dr0w$$aP" +class FakeOddballResource(fakes.FakeResource): + + def get(self, attr): + """get() is needed for utils.find_resource()""" + if attr == 'id': + return self.id + elif attr == 'name': + return self.name + else: + return None + + class TestUtils(test_utils.TestCase): def test_get_password_good(self): @@ -242,6 +255,47 @@ def test_find_resource_find_no_unique(self): self.manager.get.assert_called_with(self.name) self.manager.find.assert_called_with(name=self.name) + def test_find_resource_silly_resource(self): + # We need a resource with no resource_class for this test, start fresh + self.manager = mock.Mock() + self.manager.get = mock.Mock(side_effect=Exception('Boom!')) + self.manager.find = mock.Mock( + side_effect=AttributeError( + "'Controller' object has no attribute 'find'", + ) + ) + silly_resource = FakeOddballResource( + None, + {'id': '12345', 'name': self.name}, + loaded=True, + ) + self.manager.list = mock.Mock( + return_value=[silly_resource, ], + ) + result = utils.find_resource(self.manager, self.name) + self.assertEqual(silly_resource, result) + self.manager.get.assert_called_with(self.name) + self.manager.find.assert_called_with(name=self.name) + + def test_find_resource_silly_resource_not_found(self): + # We need a resource with no resource_class for this test, start fresh + self.manager = mock.Mock() + self.manager.get = mock.Mock(side_effect=Exception('Boom!')) + self.manager.find = mock.Mock( + side_effect=AttributeError( + "'Controller' object has no attribute 'find'", + ) + ) + self.manager.list = mock.Mock(return_value=[]) + result = self.assertRaises(exceptions.CommandError, + utils.find_resource, + self.manager, + self.name) + self.assertEqual("Could not find resource legos", + str(result)) + self.manager.get.assert_called_with(self.name) + self.manager.find.assert_called_with(name=self.name) + def test_format_dict(self): expected = "a='b', c='d', e='f'" self.assertEqual(expected, From a4483a05137bbd3ed416b6a61687315095ccced7 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Thu, 10 Sep 2015 01:45:33 -0500 Subject: [PATCH 0281/3095] Add support for updating swift account properties this patch adds support for creating/updating and removing properties (nee: metadata) for object store accounts. Partial-Bug: #1501943 Change-Id: I3ed70a5d8bd8920fedb79adc60cdc602261d5eef --- doc/source/command-objects/account.rst | 35 +++++++++++++ openstackclient/api/object_store_v1.py | 47 +++++++++++++++++ openstackclient/object/v1/account.py | 71 ++++++++++++++++++++++++++ setup.cfg | 2 + 4 files changed, 155 insertions(+) create mode 100644 doc/source/command-objects/account.rst create mode 100644 openstackclient/object/v1/account.py diff --git a/doc/source/command-objects/account.rst b/doc/source/command-objects/account.rst new file mode 100644 index 0000000000..6783fd6be3 --- /dev/null +++ b/doc/source/command-objects/account.rst @@ -0,0 +1,35 @@ +======= +account +======= + +Object Store v1 + +account set +----------- + +Set account properties + +.. program:: account set +.. code:: bash + + os account set + [--property [...] ] + +.. option:: --property + + Set a property on this account (repeat option to set multiple properties) + +account unset +------------- + +Unset account properties + +.. program:: account unset +.. code:: bash + + os account unset + [--property ] + +.. option:: --property + + Property to remove from account (repeat option to remove multiple properties) diff --git a/openstackclient/api/object_store_v1.py b/openstackclient/api/object_store_v1.py index c52eeb3ac9..c817b65070 100644 --- a/openstackclient/api/object_store_v1.py +++ b/openstackclient/api/object_store_v1.py @@ -386,3 +386,50 @@ def object_show( data[key.lower()] = value return data + + def account_set( + self, + properties, + ): + """Set account properties + + :param dict properties: + properties to add or update for the account + """ + + # NOTE(stevemar): As per the API, the headers have to be in the form + # of "X-Account-Meta-Book: MobyDick" + + headers = {} + for k, v in properties.iteritems(): + header_name = 'X-Account-Meta-%s' % k + headers[header_name] = v + + if headers: + # NOTE(stevemar): The URL (first argument) in this case is already + # set to the swift account endpoint, because that's how it's + # registered in the catalog + self.create("", headers=headers) + + def account_unset( + self, + properties, + ): + """Unset account properties + + :param dict properties: + properties to remove from the account + """ + + # NOTE(stevemar): As per the API, the headers have to be in the form + # of "X-Remove-Account-Meta-Book: x". In the case where metadata is + # removed, we can set the value of the header to anything, so it's + # set to 'x' + + headers = {} + for k in properties: + header_name = 'X-Remove-Account-Meta-%s' % k + headers[header_name] = "x" + + if headers: + self.create("", headers=headers) diff --git a/openstackclient/object/v1/account.py b/openstackclient/object/v1/account.py new file mode 100644 index 0000000000..1f38b96a82 --- /dev/null +++ b/openstackclient/object/v1/account.py @@ -0,0 +1,71 @@ +# 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. +# + +"""Account v1 action implementations""" + + +import logging + +from cliff import command + +from openstackclient.common import parseractions +from openstackclient.common import utils + + +class SetAccount(command.Command): + """Set account properties""" + + log = logging.getLogger(__name__ + '.SetAccount') + + def get_parser(self, prog_name): + parser = super(SetAccount, self).get_parser(prog_name) + parser.add_argument( + "--property", + metavar="", + required=True, + action=parseractions.KeyValueAction, + help="Set a property on this account " + "(repeat option to set multiple properties)" + ) + return parser + + @utils.log_method(log) + def take_action(self, parsed_args): + self.app.client_manager.object_store.account_set( + properties=parsed_args.property, + ) + + +class UnsetAccount(command.Command): + """Unset account properties""" + + log = logging.getLogger(__name__ + '.UnsetAccount') + + def get_parser(self, prog_name): + parser = super(UnsetAccount, self).get_parser(prog_name) + parser.add_argument( + '--property', + metavar='', + required=True, + action='append', + default=[], + help='Property to remove from account ' + '(repeat option to remove multiple properties)', + ) + return parser + + @utils.log_method(log) + def take_action(self, parsed_args): + self.app.client_manager.object_store.account_unset( + properties=parsed_args.property, + ) diff --git a/setup.cfg b/setup.cfg index f2f4833b2f..f33e7c6d67 100644 --- a/setup.cfg +++ b/setup.cfg @@ -331,6 +331,8 @@ openstack.network.v2 = network_show = openstackclient.network.v2.network:ShowNetwork openstack.object_store.v1 = + account_set = openstackclient.object.v1.account:SetAccount + account_unset = openstackclient.object.v1.account:UnsetAccount container_create = openstackclient.object.v1.container:CreateContainer container_delete = openstackclient.object.v1.container:DeleteContainer container_list = openstackclient.object.v1.container:ListContainer From 4733fd0d3cd328a8abbd62cbfabd973b0986c58c Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Thu, 10 Sep 2015 16:06:32 -0500 Subject: [PATCH 0282/3095] Add support for showing account details add the command `openstack account show` that lists details about the object store account that the user authenticated against. Partial-Bug: #1501943 Change-Id: I1246dafee812b63a41d43be4e3598224364a2c11 --- doc/source/command-objects/account.rst | 10 ++++++++++ openstackclient/api/object_store_v1.py | 21 +++++++++++++++++++++ openstackclient/object/v1/account.py | 14 +++++++++++++- setup.cfg | 1 + 4 files changed, 45 insertions(+), 1 deletion(-) diff --git a/doc/source/command-objects/account.rst b/doc/source/command-objects/account.rst index 6783fd6be3..db4ad9b16e 100644 --- a/doc/source/command-objects/account.rst +++ b/doc/source/command-objects/account.rst @@ -19,6 +19,16 @@ Set account properties Set a property on this account (repeat option to set multiple properties) +account show +------------ + +Display account details + +.. program:: account show +.. code:: bash + + os account show + account unset ------------- diff --git a/openstackclient/api/object_store_v1.py b/openstackclient/api/object_store_v1.py index c817b65070..afc5b4c163 100644 --- a/openstackclient/api/object_store_v1.py +++ b/openstackclient/api/object_store_v1.py @@ -411,6 +411,23 @@ def account_set( # registered in the catalog self.create("", headers=headers) + def account_show(self): + """Show account details""" + + # NOTE(stevemar): Just a HEAD request to the endpoint already in the + # catalog should be enough. + response = self._request("HEAD", "") + data = {} + for k, v in response.headers.iteritems(): + data[k] = v + # Map containers, bytes and objects a bit nicer + data['Containers'] = data.pop('x-account-container-count', None) + data['Objects'] = data.pop('x-account-object-count', None) + data['Bytes'] = data.pop('x-account-bytes-used', None) + # Add in Account info too + data['Account'] = self._find_account_id() + return data + def account_unset( self, properties, @@ -433,3 +450,7 @@ def account_unset( if headers: self.create("", headers=headers) + + def _find_account_id(self): + url_parts = urlparse(self.endpoint) + return url_parts.path.split('/')[-1] diff --git a/openstackclient/object/v1/account.py b/openstackclient/object/v1/account.py index 1f38b96a82..4ff890ce10 100644 --- a/openstackclient/object/v1/account.py +++ b/openstackclient/object/v1/account.py @@ -13,10 +13,11 @@ """Account v1 action implementations""" - import logging from cliff import command +from cliff import show +import six from openstackclient.common import parseractions from openstackclient.common import utils @@ -46,6 +47,17 @@ def take_action(self, parsed_args): ) +class ShowAccount(show.ShowOne): + """Display account details""" + + log = logging.getLogger(__name__ + '.ShowAccount') + + @utils.log_method(log) + def take_action(self, parsed_args): + data = self.app.client_manager.object_store.account_show() + return zip(*sorted(six.iteritems(data))) + + class UnsetAccount(command.Command): """Unset account properties""" diff --git a/setup.cfg b/setup.cfg index f33e7c6d67..c84327073b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -332,6 +332,7 @@ openstack.network.v2 = openstack.object_store.v1 = account_set = openstackclient.object.v1.account:SetAccount + account_show = openstackclient.object.v1.account:ShowAccount account_unset = openstackclient.object.v1.account:UnsetAccount container_create = openstackclient.object.v1.container:CreateContainer container_delete = openstackclient.object.v1.container:DeleteContainer From faece91756fd37c02e1d16e2a7f1176621d7e896 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Thu, 10 Sep 2015 16:10:55 -0500 Subject: [PATCH 0283/3095] cleanup account ids from container commands use a common function to determine account ID instead of different ways - depending on the response and command Change-Id: I95adc5dc7d5a82a2cffc570d1ded24d1fc754a11 --- openstackclient/api/object_store_v1.py | 12 ++++++------ openstackclient/tests/api/test_object_store_v1.py | 2 ++ .../tests/object/v1/test_container_all.py | 2 ++ openstackclient/tests/object/v1/test_object_all.py | 2 ++ 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/openstackclient/api/object_store_v1.py b/openstackclient/api/object_store_v1.py index afc5b4c163..fab470e5ab 100644 --- a/openstackclient/api/object_store_v1.py +++ b/openstackclient/api/object_store_v1.py @@ -44,9 +44,8 @@ def container_create( """ response = self.create(container, method='PUT') - url_parts = urlparse(self.endpoint) data = { - 'account': url_parts.path.split('/')[-1], + 'account': self._find_account_id(), 'container': container, 'x-trans-id': response.headers.get('x-trans-id', None), } @@ -154,12 +153,13 @@ def container_show( response = self._request('HEAD', container) data = { - 'account': response.headers.get('x-container-meta-owner', None), + 'account': self._find_account_id(), 'container': container, 'object_count': response.headers.get( 'x-container-object-count', None, ), + 'meta-owner': response.headers.get('x-container-meta-owner', None), 'bytes_used': response.headers.get('x-container-bytes-used', None), 'read_acl': response.headers.get('x-container-read', None), 'write_acl': response.headers.get('x-container-write', None), @@ -194,9 +194,8 @@ def object_create( method='PUT', data=f, ) - url_parts = urlparse(self.endpoint) data = { - 'account': url_parts.path.split('/')[-1], + 'account': self._find_account_id(), 'container': container, 'object': object, 'x-trans-id': response.headers.get('X-Trans-Id', None), @@ -352,10 +351,11 @@ def object_show( response = self._request('HEAD', "%s/%s" % (container, object)) data = { - 'account': response.headers.get('x-container-meta-owner', None), + 'account': self._find_account_id(), 'container': container, 'object': object, 'content-type': response.headers.get('content-type', None), + 'meta-owner': response.headers.get('x-container-meta-owner', None), } if 'content-length' in response.headers: data['content-length'] = response.headers.get( diff --git a/openstackclient/tests/api/test_object_store_v1.py b/openstackclient/tests/api/test_object_store_v1.py index b18a003db5..323bb8e0b5 100644 --- a/openstackclient/tests/api/test_object_store_v1.py +++ b/openstackclient/tests/api/test_object_store_v1.py @@ -157,6 +157,7 @@ def test_container_show(self): 'container': 'qaz', 'object_count': '1', 'bytes_used': '577', + 'meta-owner': FAKE_ACCOUNT, 'read_acl': None, 'write_acl': None, 'sync_to': None, @@ -322,6 +323,7 @@ def test_object_show(self): 'content-type': 'text/alpha', 'content-length': '577', 'last-modified': '20130101', + 'meta-owner': FAKE_ACCOUNT, 'etag': 'qaz', 'wife': 'Wilma', 'x-tra-header': 'yabba-dabba-do', diff --git a/openstackclient/tests/object/v1/test_container_all.py b/openstackclient/tests/object/v1/test_container_all.py index 8b200e09e3..4477f2e03b 100644 --- a/openstackclient/tests/object/v1/test_container_all.py +++ b/openstackclient/tests/object/v1/test_container_all.py @@ -316,6 +316,7 @@ def test_object_show_container(self): 'account', 'bytes_used', 'container', + 'meta-owner', 'object_count', 'read_acl', 'sync_key', @@ -327,6 +328,7 @@ def test_object_show_container(self): object_fakes.ACCOUNT_ID, '123', 'ernie', + object_fakes.ACCOUNT_ID, '42', 'qaz', 'rfv', diff --git a/openstackclient/tests/object/v1/test_object_all.py b/openstackclient/tests/object/v1/test_object_all.py index 7a76ab76ad..41fe6324cd 100644 --- a/openstackclient/tests/object/v1/test_object_all.py +++ b/openstackclient/tests/object/v1/test_object_all.py @@ -160,6 +160,7 @@ def test_object_show(self): 'content-type', 'etag', 'last-modified', + 'meta-owner', 'object', 'x-object-manifest', ) @@ -171,6 +172,7 @@ def test_object_show(self): 'text/plain', '4c4e39a763d58392724bccf76a58783a', 'yesterday', + object_fakes.ACCOUNT_ID, object_fakes.object_name_1, 'manifest', ) From a2786fa88bf06ea0c1852d3538cb50b2b5197f0a Mon Sep 17 00:00:00 2001 From: Atsushi SAKAI Date: Thu, 24 Sep 2015 20:39:12 +0900 Subject: [PATCH 0284/3095] Add one parenthesis In the following help message, last parenthesis is missing. $ openstack --os-volume-api-version 2 help volume delete usage: openstack volume delete [-h] [--force] [ ...] Delete volume(s) positional arguments: Volume(s) to delete (name or ID) optional arguments: -h, --help show this help message and exit --force Attempt forced removal of volume(s), regardless of state (defaults to False Change-Id: I45c4030abf076cba14450019c379d333eb6530d6 --- openstackclient/volume/v2/volume.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index ad6215e4e9..758f312b4b 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -175,7 +175,7 @@ def get_parser(self, prog_name): action="store_true", default=False, help="Attempt forced removal of volume(s), regardless of state " - "(defaults to False" + "(defaults to False)" ) return parser From ecb69a47711a0023edaa608cda41b8e908f80985 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Thu, 1 Oct 2015 18:43:04 -0400 Subject: [PATCH 0285/3095] Rename swift account commands rename `os account` to `os object store account` Co-Authored-By: Lin Hua Cheng Closes-Bug: #1501943 Change-Id: I54fdcea7a48df16f20e17605110f8d33a20f713c --- .../{account.rst => object-store-account.rst} | 30 +++++++++---------- setup.cfg | 6 ++-- 2 files changed, 18 insertions(+), 18 deletions(-) rename doc/source/command-objects/{account.rst => object-store-account.rst} (50%) diff --git a/doc/source/command-objects/account.rst b/doc/source/command-objects/object-store-account.rst similarity index 50% rename from doc/source/command-objects/account.rst rename to doc/source/command-objects/object-store-account.rst index db4ad9b16e..bdc9393eaa 100644 --- a/doc/source/command-objects/account.rst +++ b/doc/source/command-objects/object-store-account.rst @@ -1,43 +1,43 @@ -======= -account -======= +==================== +object store account +==================== Object Store v1 -account set ------------ +object store account set +------------------------ Set account properties -.. program:: account set +.. program:: object store account set .. code:: bash - os account set + os object store account set [--property [...] ] .. option:: --property Set a property on this account (repeat option to set multiple properties) -account show ------------- +object store account show +------------------------- Display account details -.. program:: account show +.. program:: object store account show .. code:: bash - os account show + os object store account show -account unset -------------- +object store account unset +-------------------------- Unset account properties -.. program:: account unset +.. program:: object store account unset .. code:: bash - os account unset + os object store account unset [--property ] .. option:: --property diff --git a/setup.cfg b/setup.cfg index c84327073b..f40270c205 100644 --- a/setup.cfg +++ b/setup.cfg @@ -331,9 +331,9 @@ openstack.network.v2 = network_show = openstackclient.network.v2.network:ShowNetwork openstack.object_store.v1 = - account_set = openstackclient.object.v1.account:SetAccount - account_show = openstackclient.object.v1.account:ShowAccount - account_unset = openstackclient.object.v1.account:UnsetAccount + object_store_account_set = openstackclient.object.v1.account:SetAccount + object_store_account_show = openstackclient.object.v1.account:ShowAccount + object_store_account_unset = openstackclient.object.v1.account:UnsetAccount container_create = openstackclient.object.v1.container:CreateContainer container_delete = openstackclient.object.v1.container:DeleteContainer container_list = openstackclient.object.v1.container:ListContainer From 45f1509cd387acc8b9a14895817116dae3c47bcb Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sun, 4 Oct 2015 22:45:07 +0000 Subject: [PATCH 0286/3095] Updated from global requirements Change-Id: I5b898fc5444688bde538fbccda6e3c189a66f701 --- test-requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test-requirements.txt b/test-requirements.txt index 25d062898c..111d70bc10 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -11,8 +11,8 @@ oslosphinx>=2.5.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 requests-mock>=0.6.0 # Apache-2.0 sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 -os-testr>=0.1.0 +os-testr>=0.4.1 testrepository>=0.0.18 testtools>=1.4.0 WebOb>=1.2.3 -tempest-lib>=0.8.0 +tempest-lib>=0.9.0 From abaf711e249c36b5fe75439691609c09fb9ef141 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Fri, 11 Sep 2015 00:00:00 -0500 Subject: [PATCH 0287/3095] add support for set/unset of container properties include docs and commands to set and unset container properties Partial-Bug: #1501945 Change-Id: I8d7e8cf356a2321a37ed940c4e10cae411b94dfd --- doc/source/command-objects/container.rst | 40 ++++++++++++ openstackclient/api/object_store_v1.py | 78 +++++++++++++++++++----- openstackclient/object/v1/container.py | 62 +++++++++++++++++++ setup.cfg | 2 + 4 files changed, 166 insertions(+), 16 deletions(-) diff --git a/doc/source/command-objects/container.rst b/doc/source/command-objects/container.rst index 3372f4d9bd..3e52d56b05 100644 --- a/doc/source/command-objects/container.rst +++ b/doc/source/command-objects/container.rst @@ -89,6 +89,26 @@ Save container contents locally Container to save +container set +------------- + +Set container properties + +.. program:: container set +.. code:: bash + + os container set + [] + [--property [...] ] + +.. describe:: + + Container to modify + +.. option:: --property + + Set a property on this container (repeat option to set multiple properties) + container show -------------- @@ -103,3 +123,23 @@ Display container details .. describe:: Container to display + +container unset +--------------- + +Unset container properties + +.. program:: container unset +.. code:: bash + + os container unset + [] + [--property ] + +.. describe:: + + Container to modify + +.. option:: --property + + Property to remove from container (repeat option to remove multiple properties) diff --git a/openstackclient/api/object_store_v1.py b/openstackclient/api/object_store_v1.py index fab470e5ab..b47f556b09 100644 --- a/openstackclient/api/object_store_v1.py +++ b/openstackclient/api/object_store_v1.py @@ -139,6 +139,23 @@ def container_save( for object in objects: self.object_save(container=container, object=object['name']) + def container_set( + self, + container, + properties, + ): + """Set container properties + + :param string container: + name of container to modify + :param dict properties: + properties to add or update for the container + """ + + headers = self._set_properties(properties, 'X-Container-Meta-%s') + if headers: + self.create(container, headers=headers) + def container_show( self, container=None, @@ -168,6 +185,24 @@ def container_show( } return data + def container_unset( + self, + container, + properties, + ): + """Unset container properties + + :param string container: + name of container to modify + :param dict properties: + properties to remove from the container + """ + + headers = self._unset_properties(properties, + 'X-Remove-Container-Meta-%s') + if headers: + self.create(container, headers=headers) + def object_create( self, container=None, @@ -397,14 +432,7 @@ def account_set( properties to add or update for the account """ - # NOTE(stevemar): As per the API, the headers have to be in the form - # of "X-Account-Meta-Book: MobyDick" - - headers = {} - for k, v in properties.iteritems(): - header_name = 'X-Account-Meta-%s' % k - headers[header_name] = v - + headers = self._set_properties(properties, 'X-Account-Meta-%s') if headers: # NOTE(stevemar): The URL (first argument) in this case is already # set to the swift account endpoint, because that's how it's @@ -438,19 +466,37 @@ def account_unset( properties to remove from the account """ + headers = self._unset_properties(properties, + 'X-Remove-Account-Meta-%s') + if headers: + self.create("", headers=headers) + + def _find_account_id(self): + url_parts = urlparse(self.endpoint) + return url_parts.path.split('/')[-1] + + def _unset_properties(self, properties, header_tag): # NOTE(stevemar): As per the API, the headers have to be in the form # of "X-Remove-Account-Meta-Book: x". In the case where metadata is # removed, we can set the value of the header to anything, so it's - # set to 'x' + # set to 'x'. In the case of a Container property we use: + # "X-Remove-Container-Meta-Book: x", and the same logic applies for + # Object properties headers = {} for k in properties: - header_name = 'X-Remove-Account-Meta-%s' % k - headers[header_name] = "x" + header_name = header_tag % k + headers[header_name] = 'x' + return headers - if headers: - self.create("", headers=headers) + def _set_properties(self, properties, header_tag): + # NOTE(stevemar): As per the API, the headers have to be in the form + # of "X-Account-Meta-Book: MobyDick". In the case of a Container + # property we use: "X-Add-Container-Meta-Book: MobyDick", and the same + # logic applies for Object properties - def _find_account_id(self): - url_parts = urlparse(self.endpoint) - return url_parts.path.split('/')[-1] + headers = {} + for k, v in properties.iteritems(): + header_name = header_tag % k + headers[header_name] = v + return headers diff --git a/openstackclient/object/v1/container.py b/openstackclient/object/v1/container.py index 49173debbc..b8eb4c254e 100644 --- a/openstackclient/object/v1/container.py +++ b/openstackclient/object/v1/container.py @@ -23,6 +23,7 @@ from cliff import lister from cliff import show +from openstackclient.common import parseractions from openstackclient.common import utils @@ -178,6 +179,36 @@ def take_action(self, parsed_args): ) +class SetContainer(command.Command): + """Set container properties""" + + log = logging.getLogger(__name__ + '.SetContainer') + + def get_parser(self, prog_name): + parser = super(SetContainer, self).get_parser(prog_name) + parser.add_argument( + 'container', + metavar='', + help='Container to modify', + ) + parser.add_argument( + "--property", + metavar="", + required=True, + action=parseractions.KeyValueAction, + help="Set a property on this container " + "(repeat option to set multiple properties)" + ) + return parser + + @utils.log_method(log) + def take_action(self, parsed_args): + self.app.client_manager.object_store.container_set( + parsed_args.container, + properties=parsed_args.property, + ) + + class ShowContainer(show.ShowOne): """Display container details""" @@ -200,3 +231,34 @@ def take_action(self, parsed_args): ) return zip(*sorted(six.iteritems(data))) + + +class UnsetContainer(command.Command): + """Unset container properties""" + + log = logging.getLogger(__name__ + '.UnsetContainer') + + def get_parser(self, prog_name): + parser = super(UnsetContainer, self).get_parser(prog_name) + parser.add_argument( + 'container', + metavar='', + help='Container to modify', + ) + parser.add_argument( + '--property', + metavar='', + required=True, + action='append', + default=[], + help='Property to remove from container ' + '(repeat option to remove multiple properties)', + ) + return parser + + @utils.log_method(log) + def take_action(self, parsed_args): + self.app.client_manager.object_store.container_unset( + parsed_args.container, + properties=parsed_args.property, + ) diff --git a/setup.cfg b/setup.cfg index 2498a3d355..248c9c6604 100644 --- a/setup.cfg +++ b/setup.cfg @@ -339,7 +339,9 @@ openstack.object_store.v1 = container_delete = openstackclient.object.v1.container:DeleteContainer container_list = openstackclient.object.v1.container:ListContainer container_save = openstackclient.object.v1.container:SaveContainer + container_set = openstackclient.object.v1.container:SetContainer container_show = openstackclient.object.v1.container:ShowContainer + container_unset = openstackclient.object.v1.container:UnsetContainer object_create = openstackclient.object.v1.object:CreateObject object_delete = openstackclient.object.v1.object:DeleteObject object_list = openstackclient.object.v1.object:ListObject From e48c7afee4d92f2dd37bb537d25f0f671cef1568 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Fri, 11 Sep 2015 00:38:56 -0500 Subject: [PATCH 0288/3095] add set/unset support for objects in object store add docs and command support to set and unset metadata of objects that are stored in an object store (swift). Closes-Bug: #1501945 Change-Id: If838a4b3343b6ddb97cd4bd1cb63f0ba1c1a00a1 --- doc/source/command-objects/object.rst | 50 +++++++++++++++++ openstackclient/api/object_store_v1.py | 40 ++++++++++++++ openstackclient/object/v1/object.py | 74 ++++++++++++++++++++++++++ setup.cfg | 2 + 4 files changed, 166 insertions(+) diff --git a/doc/source/command-objects/object.rst b/doc/source/command-objects/object.rst index 90bcfa1c91..a9c4049b42 100644 --- a/doc/source/command-objects/object.rst +++ b/doc/source/command-objects/object.rst @@ -119,6 +119,31 @@ Save object locally Object to save +object set +---------- + +Set object properties + +.. program:: object set +.. code:: bash + + os object set + + [] + [--property [...] ] + +.. describe:: + + Modify from + +.. describe:: + + Object to modify + +.. option:: --property + + Set a property on this object (repeat option to set multiple properties) + object show ----------- @@ -138,3 +163,28 @@ Display object details .. describe:: Object to display + +object unset +------------ + +Unset object properties + +.. program:: object unset +.. code:: bash + + os object unset + + [] + [--property ] + +.. describe:: + + Modify from + +.. describe:: + + Object to modify + +.. option:: --property + + Property to remove from object (repeat option to remove multiple properties) diff --git a/openstackclient/api/object_store_v1.py b/openstackclient/api/object_store_v1.py index b47f556b09..c870332aec 100644 --- a/openstackclient/api/object_store_v1.py +++ b/openstackclient/api/object_store_v1.py @@ -366,6 +366,46 @@ def object_save( for chunk in response.iter_content(): f.write(chunk) + def object_set( + self, + container, + object, + properties, + ): + """Set object properties + + :param string container: + container name for object to modify + :param string object: + name of object to modify + :param dict properties: + properties to add or update for the container + """ + + headers = self._set_properties(properties, 'X-Object-Meta-%s') + if headers: + self.create("%s/%s" % (container, object), headers=headers) + + def object_unset( + self, + container, + object, + properties, + ): + """Unset object properties + + :param string container: + container name for object to modify + :param string object: + name of object to modify + :param dict properties: + properties to remove from the object + """ + + headers = self._unset_properties(properties, 'X-Remove-Object-Meta-%s') + if headers: + self.create("%s/%s" % (container, object), headers=headers) + def object_show( self, container=None, diff --git a/openstackclient/object/v1/object.py b/openstackclient/object/v1/object.py index c90f031918..a023e3a00c 100644 --- a/openstackclient/object/v1/object.py +++ b/openstackclient/object/v1/object.py @@ -23,6 +23,7 @@ from cliff import lister from cliff import show +from openstackclient.common import parseractions from openstackclient.common import utils @@ -221,6 +222,42 @@ def take_action(self, parsed_args): ) +class SetObject(command.Command): + """Set object properties""" + + log = logging.getLogger(__name__ + '.SetObject') + + def get_parser(self, prog_name): + parser = super(SetObject, self).get_parser(prog_name) + parser.add_argument( + 'container', + metavar='', + help='Modify from ', + ) + parser.add_argument( + 'object', + metavar='', + help='Object to modify', + ) + parser.add_argument( + "--property", + metavar="", + required=True, + action=parseractions.KeyValueAction, + help="Set a property on this object " + "(repeat option to set multiple properties)" + ) + return parser + + @utils.log_method(log) + def take_action(self, parsed_args): + self.app.client_manager.object_store.object_set( + parsed_args.container, + parsed_args.object, + properties=parsed_args.property, + ) + + class ShowObject(show.ShowOne): """Display object details""" @@ -249,3 +286,40 @@ def take_action(self, parsed_args): ) return zip(*sorted(six.iteritems(data))) + + +class UnsetObject(command.Command): + """Unset object properties""" + + log = logging.getLogger(__name__ + '.UnsetObject') + + def get_parser(self, prog_name): + parser = super(UnsetObject, self).get_parser(prog_name) + parser.add_argument( + 'container', + metavar='', + help='Modify from ', + ) + parser.add_argument( + 'object', + metavar='', + help='Object to modify', + ) + parser.add_argument( + '--property', + metavar='', + required=True, + action='append', + default=[], + help='Property to remove from object ' + '(repeat option to remove multiple properties)', + ) + return parser + + @utils.log_method(log) + def take_action(self, parsed_args): + self.app.client_manager.object_store.object_unset( + parsed_args.container, + parsed_args.object, + properties=parsed_args.property, + ) diff --git a/setup.cfg b/setup.cfg index 248c9c6604..7265eb883e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -346,7 +346,9 @@ openstack.object_store.v1 = object_delete = openstackclient.object.v1.object:DeleteObject object_list = openstackclient.object.v1.object:ListObject object_save = openstackclient.object.v1.object:SaveObject + object_set = openstackclient.object.v1.object:SetObject object_show = openstackclient.object.v1.object:ShowObject + object_unset = openstackclient.object.v1.object:UnsetObject openstack.volume.v1 = backup_create = openstackclient.volume.v1.backup:CreateBackup From 5bd536608963898c7e670fcc1d15120c51424431 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Tue, 6 Oct 2015 02:07:45 -0400 Subject: [PATCH 0289/3095] Update the plugin docs There have been some changes as to the status of OSC plugins, highlight these changes in the table. Change-Id: Iff6520d35b34ae5d7bf88a128ebdf05f3681d643 --- doc/source/plugins.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/source/plugins.rst b/doc/source/plugins.rst index 469742dc88..5f82cc6879 100644 --- a/doc/source/plugins.rst +++ b/doc/source/plugins.rst @@ -24,20 +24,20 @@ plugin. ============================= ====================================== project notes ============================= ====================================== -python-barbicanclient patch in progress (https://review.openstack.org/#/c/198732/) +python-barbicanclient using OpenStackClient python-ceilometerclient using argparse python-congressclient using OpenStackClient python-cueclient using OpenStackClient -python-designateclient patch in progress (https://review.openstack.org/#/c/133676/) +python-designateclient using OpenStackClient python-heatclient patch in progress (https://review.openstack.org/#/c/195867/) -python-ironicclient patch in progress (https://review.openstack.org/#/c/171672/) +python-ironicclient Using OpenStackClient python-magnumclient using argparse python-manilaclient using argparse -python-mistralclient using cliff, but not OpenStackClient +python-mistralclient using cliff python-muranoclient using argparse -python-saharaclient using argparse +python-saharaclient using OpenStackClient python-troveclient using argparse -python-tuskarclient using OpenStackClient and their own shell +python-tuskarclient using OpenStackClient python-zaqarclient using OpenStackClient ============================= ====================================== From 578a57dcc349cf0c71403e8e56f4f3bf01842513 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Tue, 6 Oct 2015 08:46:28 -0500 Subject: [PATCH 0290/3095] Set object store arg order in docs argparse displays positional args last in the help output, our docs should match. Change-Id: I01e8d62cb8c1b537ea0441b8bdf8880a4c856b32 --- doc/source/command-objects/container.rst | 20 ++++++++++---------- doc/source/command-objects/object.rst | 20 ++++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/doc/source/command-objects/container.rst b/doc/source/command-objects/container.rst index 3e52d56b05..251e3f314a 100644 --- a/doc/source/command-objects/container.rst +++ b/doc/source/command-objects/container.rst @@ -98,17 +98,17 @@ Set container properties .. code:: bash os container set - [] [--property [...] ] - -.. describe:: - - Container to modify + [] .. option:: --property Set a property on this container (repeat option to set multiple properties) +.. describe:: + + Container to modify + container show -------------- @@ -133,13 +133,13 @@ Unset container properties .. code:: bash os container unset - [] [--property ] - -.. describe:: - - Container to modify + [] .. option:: --property Property to remove from container (repeat option to remove multiple properties) + +.. describe:: + + Container to modify diff --git a/doc/source/command-objects/object.rst b/doc/source/command-objects/object.rst index a9c4049b42..ac81441702 100644 --- a/doc/source/command-objects/object.rst +++ b/doc/source/command-objects/object.rst @@ -128,9 +128,13 @@ Set object properties .. code:: bash os object set + [--property [...] ] [] - [--property [...] ] + +.. option:: --property + + Set a property on this object (repeat option to set multiple properties) .. describe:: @@ -140,10 +144,6 @@ Set object properties Object to modify -.. option:: --property - - Set a property on this object (repeat option to set multiple properties) - object show ----------- @@ -173,9 +173,13 @@ Unset object properties .. code:: bash os object unset + [--property ] [] - [--property ] + +.. option:: --property + + Property to remove from object (repeat option to remove multiple properties) .. describe:: @@ -184,7 +188,3 @@ Unset object properties .. describe:: Object to modify - -.. option:: --property - - Property to remove from object (repeat option to remove multiple properties) From 7075c90053806d585ffb0042b858c482a831263c Mon Sep 17 00:00:00 2001 From: Rudolf Vriend Date: Tue, 6 Oct 2015 17:14:25 +0200 Subject: [PATCH 0291/3095] Evaluate --inherited in role list the --inherited option was not being passed into keystoneclient Closes-Bug: #1502822 Change-Id: I48170dc67b23cc9b0665b1e0f38118eea952f131 --- openstackclient/identity/v3/role.py | 5 +++++ openstackclient/tests/identity/v3/test_role.py | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index c72de477dd..0e8c51ca8d 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -260,6 +260,7 @@ def take_action(self, parsed_args): data = identity_client.roles.list( user=user, domain=domain, + os_inherit_extension_inherited=parsed_args.inherited ) for user_role in data: user_role.user = user.name @@ -269,6 +270,7 @@ def take_action(self, parsed_args): data = identity_client.roles.list( user=user, project=project, + os_inherit_extension_inherited=parsed_args.inherited ) for user_role in data: user_role.user = user.name @@ -278,12 +280,14 @@ def take_action(self, parsed_args): data = identity_client.roles.list( user=user, domain='default', + os_inherit_extension_inherited=parsed_args.inherited ) elif parsed_args.group and parsed_args.domain: columns = ('ID', 'Name', 'Domain', 'Group') data = identity_client.roles.list( group=group, domain=domain, + os_inherit_extension_inherited=parsed_args.inherited ) for group_role in data: group_role.group = group.name @@ -293,6 +297,7 @@ def take_action(self, parsed_args): data = identity_client.roles.list( group=group, project=project, + os_inherit_extension_inherited=parsed_args.inherited ) for group_role in data: group_role.group = group.name diff --git a/openstackclient/tests/identity/v3/test_role.py b/openstackclient/tests/identity/v3/test_role.py index 4a0ba0664e..ee3dc946a1 100644 --- a/openstackclient/tests/identity/v3/test_role.py +++ b/openstackclient/tests/identity/v3/test_role.py @@ -383,6 +383,7 @@ def test_user_list_user(self): kwargs = { 'domain': 'default', 'user': self.users_mock.get(), + 'os_inherit_extension_inherited': False } # RoleManager.list(user=, group=, domain=, project=, **kwargs) self.roles_mock.list.assert_called_with( @@ -415,6 +416,7 @@ def test_role_list_domain_user(self): kwargs = { 'domain': self.domains_mock.get(), 'user': self.users_mock.get(), + 'os_inherit_extension_inherited': False } # RoleManager.list(user=, group=, domain=, project=, **kwargs) self.roles_mock.list.assert_called_with( @@ -449,6 +451,7 @@ def test_role_list_domain_group(self): kwargs = { 'domain': self.domains_mock.get(), 'group': self.groups_mock.get(), + 'os_inherit_extension_inherited': False } # RoleManager.list(user=, group=, domain=, project=, **kwargs) self.roles_mock.list.assert_called_with( @@ -483,6 +486,7 @@ def test_role_list_project_user(self): kwargs = { 'project': self.projects_mock.get(), 'user': self.users_mock.get(), + 'os_inherit_extension_inherited': False } # RoleManager.list(user=, group=, domain=, project=, **kwargs) self.roles_mock.list.assert_called_with( @@ -517,6 +521,7 @@ def test_role_list_project_group(self): kwargs = { 'project': self.projects_mock.get(), 'group': self.groups_mock.get(), + 'os_inherit_extension_inherited': False } # RoleManager.list(user=, group=, domain=, project=, **kwargs) self.roles_mock.list.assert_called_with( From 201b1cee86a4df8ede6c97d962ac331ad0378140 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Tue, 22 Sep 2015 17:17:37 -0500 Subject: [PATCH 0292/3095] Clean up Image v2 image set command Make the Image v2 image set command meet at the intersection of the v1 image set command and the v2 image create command: * Add visibility to the deadopts list and remove the option * Put the options in the same order as v1 image set * Make the help text match * Add --properties * Move the additional options that do not appear in either v1 image set or v2 image create after --property as they are really pre-defined properties * Add tests for v2 image set to match v1 and then some * Put the SetImage class in v2/image.py in alphabetical order Change-Id: I102b914e8ad09a014f6fdd846c5766b6c2eaadb8 --- doc/source/command-objects/image.rst | 41 ++-- openstackclient/image/v2/image.py | 180 ++++++++-------- openstackclient/tests/image/v2/test_image.py | 207 ++++++++++++++++--- 3 files changed, 284 insertions(+), 144 deletions(-) diff --git a/doc/source/command-objects/image.rst b/doc/source/command-objects/image.rst index d2698b0b97..8dce3662f3 100644 --- a/doc/source/command-objects/image.rst +++ b/doc/source/command-objects/image.rst @@ -238,6 +238,12 @@ Set image properties [--checksum ] [--stdin] [--property [...] ] + [--architecture ] + [--instance-id ] + [--kernel-id ] + [--os-distro ] + [--os-version ] + [--ramdisk-id ] .. option:: --name @@ -258,14 +264,11 @@ Set image properties .. option:: --container-format - Container format of image. - Acceptable formats: ['ami', 'ari', 'aki', 'bare', 'ovf'] + Image container format (default: bare) .. option:: --disk-format - Disk format of image. - Acceptable formats: ['ami', 'ari', 'aki', 'vhd', 'vmdk', 'raw', 'qcow2', - 'vdi', 'iso'] + Image disk format (default: raw) .. option:: --size @@ -339,45 +342,41 @@ Set image properties .. option:: --property - Set a property on this image (repeat for multiple values) - - *Image version 1 only.* + Set a property on this image (repeat option to set multiple properties) .. option:: --architecture - Operating system Architecture + Operating system architecture .. versionadded:: 2 -.. option:: --ramdisk-id +.. option:: --instance-id - ID of image stored in Glance that should be used as - the ramdisk when booting an AMI-style image + ID of server instance used to create this image .. versionadded:: 2 -.. option:: --os-distro +.. option:: --kernel-id - Common name of operating system distribution + ID of kernel image used to boot this disk image .. versionadded:: 2 -.. option:: --os-version +.. option:: --os-distro - Operating system version as specified by the distributor + Operating system distribution name .. versionadded:: 2 -.. option:: --kernel-id +.. option:: --os-version - ID of image in Glance that should be used as the - kernel when booting an AMI-style image + Operating system distribution version .. versionadded:: 2 -.. option:: --instance-uuid +.. option:: --ramdisk-id - ID of instance used to create this image + ID of ramdisk image used to boot this disk image .. versionadded:: 2 diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index fff26c0249..11c7483bdb 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -210,7 +210,7 @@ def get_parser(self, prog_name): "--%s" % deadopt, metavar="<%s>" % deadopt, dest=deadopt.replace('-', '_'), - help=argparse.SUPPRESS + help=argparse.SUPPRESS, ) return parser @@ -522,38 +522,11 @@ def take_action(self, parsed_args): gc_utils.save_image(data, parsed_args.file) -class ShowImage(show.ShowOne): - """Display image details""" - - log = logging.getLogger(__name__ + ".ShowImage") - - def get_parser(self, prog_name): - parser = super(ShowImage, self).get_parser(prog_name) - parser.add_argument( - "image", - metavar="", - help="Image to display (name or ID)", - ) - return parser - - def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) - - image_client = self.app.client_manager.image - image = utils.find_resource( - image_client.images, - parsed_args.image, - ) - - info = _format_image(image) - return zip(*sorted(six.iteritems(info))) - - class SetImage(show.ShowOne): """Set image properties""" log = logging.getLogger(__name__ + ".SetImage") - deadopts = ('size', 'store', 'location', 'copy-from', 'checksum') + deadopts = ('visibility',) def get_parser(self, prog_name): parser = super(SetImage, self).get_parser(prog_name) @@ -567,7 +540,6 @@ def get_parser(self, prog_name): # --force - needs adding # --checksum - maybe could be done client side # --stdin - could be implemented - # --property - needs adding # --tags - needs adding parser.add_argument( "image", @@ -580,20 +552,44 @@ def get_parser(self, prog_name): help="New image name" ) parser.add_argument( - "--architecture", - metavar="", - help="Operating system Architecture" + "--owner", + metavar="", + help="New image owner project (name or ID)", + ) + parser.add_argument( + "--min-disk", + type=int, + metavar="", + help="Minimum disk size needed to boot image, in gigabytes" + ) + parser.add_argument( + "--min-ram", + type=int, + metavar="", + help="Minimum RAM size needed to boot image, in megabytes", + ) + parser.add_argument( + "--container-format", + metavar="", + help="Image container format " + "(default: %s)" % DEFAULT_CONTAINER_FORMAT, + ) + parser.add_argument( + "--disk-format", + metavar="", + help="Image disk format " + "(default: %s)" % DEFAULT_DISK_FORMAT, ) protected_group = parser.add_mutually_exclusive_group() protected_group.add_argument( "--protected", action="store_true", - help="Prevent image from being deleted" + help="Prevent image from being deleted", ) protected_group.add_argument( "--unprotected", action="store_true", - help="Allow image to be deleted (default)" + help="Allow image to be deleted (default)", ) public_group = parser.add_mutually_exclusive_group() public_group.add_argument( @@ -607,82 +603,55 @@ def get_parser(self, prog_name): help="Image is inaccessible to the public (default)", ) parser.add_argument( - "--instance-uuid", - metavar="", - help="ID of instance used to create this image" + "--property", + dest="properties", + metavar="", + action=parseractions.KeyValueAction, + help="Set a property on this image " + "(repeat option to set multiple properties)", ) parser.add_argument( - "--min-disk", - type=int, - metavar="", - help="Minimum disk size needed to boot image, in gigabytes" - ) - visibility_choices = ["public", "private"] - public_group.add_argument( - "--visibility", - metavar="", - choices=visibility_choices, - help=argparse.SUPPRESS + "--architecture", + metavar="", + help="Operating system architecture", ) - help_msg = ("ID of image in Glance that should be used as the kernel" - " when booting an AMI-style image") parser.add_argument( - "--kernel-id", - metavar="", - help=help_msg + "--instance-id", + metavar="", + help="ID of server instance used to create this image", ) parser.add_argument( - "--os-version", - metavar="", - help="Operating system version as specified by the distributor" + "--instance-uuid", + metavar="", + dest="instance_id", + help=argparse.SUPPRESS, ) - disk_choices = ["None", "ami", "ari", "aki", "vhd", "vmdk", "raw", - "qcow2", "vdi", "iso"] - help_msg = ("Format of the disk. Valid values: %s" % disk_choices) parser.add_argument( - "--disk-format", - metavar="", - choices=disk_choices, - help=help_msg + "--kernel-id", + metavar="", + help="ID of kernel image used to boot this disk image", ) parser.add_argument( "--os-distro", metavar="", - help="Common name of operating system distribution" + help="Operating system distribution name", ) parser.add_argument( - "--owner", - metavar="", - help="New Owner of the image" + "--os-version", + metavar="", + help="Operating system distribution version", ) - msg = ("ID of image stored in Glance that should be used as the " - "ramdisk when booting an AMI-style image") parser.add_argument( "--ramdisk-id", metavar="", - help=msg - ) - parser.add_argument( - "--min-ram", - type=int, - metavar="", - help="Amount of RAM (in MB) required to boot image" - ) - container_choices = ["None", "ami", "ari", "aki", "bare", "ovf", "ova"] - help_msg = ("Format of the container. Valid values: %s" - % container_choices) - parser.add_argument( - "--container-format", - metavar="", - choices=container_choices, - help=help_msg + help="ID of ramdisk image used to boot this disk image", ) for deadopt in self.deadopts: parser.add_argument( "--%s" % deadopt, metavar="<%s>" % deadopt, dest=deadopt.replace('-', '_'), - help=argparse.SUPPRESS + help=argparse.SUPPRESS, ) return parser @@ -698,10 +667,9 @@ def take_action(self, parsed_args): kwargs = {} copy_attrs = ('architecture', 'container_format', 'disk_format', - 'file', 'kernel_id', 'locations', 'name', + 'file', 'instance_id', 'kernel_id', 'locations', 'min_disk', 'min_ram', 'name', 'os_distro', 'os_version', - 'owner', 'prefix', 'progress', 'ramdisk_id', - 'visibility') + 'owner', 'prefix', 'progress', 'ramdisk_id') for attr in copy_attrs: if attr in parsed_args: val = getattr(parsed_args, attr, None) @@ -710,6 +678,11 @@ def take_action(self, parsed_args): # actually present on the command line kwargs[attr] = val + # Properties should get flattened into the general kwargs + if getattr(parsed_args, 'properties', None): + for k, v in six.iteritems(parsed_args.properties): + kwargs[k] = str(v) + # Handle exclusive booleans with care # Avoid including attributes in kwargs if an option is not # present on the command line. These exclusive booleans are not @@ -736,3 +709,30 @@ def take_action(self, parsed_args): info = {} info.update(image) return zip(*sorted(six.iteritems(info))) + + +class ShowImage(show.ShowOne): + """Display image details""" + + log = logging.getLogger(__name__ + ".ShowImage") + + def get_parser(self, prog_name): + parser = super(ShowImage, self).get_parser(prog_name) + parser.add_argument( + "image", + metavar="", + help="Image to display (name or ID)", + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + + image_client = self.app.client_manager.image + image = utils.find_resource( + image_client.images, + parsed_args.image, + ) + + info = _format_image(image) + return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index 65d5e555f6..ce2974d692 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -210,7 +210,7 @@ def test_image_create_file(self, mock_open): self.assertEqual(image_fakes.IMAGE_columns, columns) self.assertEqual(image_fakes.IMAGE_data, data) - def test_image_dead_options(self): + def test_image_create_dead_options(self): arglist = [ '--owner', 'nobody', @@ -639,11 +639,10 @@ def test_remove_project_image_with_options(self): ) -class TestImageShow(TestImage): +class TestImageSet(TestImage): def setUp(self): - super(TestImageShow, self).setUp() - + super(TestImageSet, self).setUp() # Set up the schema self.model = warlock.model_factory( image_fakes.IMAGE_schema, @@ -651,33 +650,190 @@ def setUp(self): ) self.images_mock.get.return_value = self.model(**image_fakes.IMAGE) - + self.images_mock.update.return_value = self.model(**image_fakes.IMAGE) # Get the command object to test - self.cmd = image.ShowImage(self.app, None) + self.cmd = image.SetImage(self.app, None) - def test_image_show(self): + def test_image_set_options(self): arglist = [ + '--name', 'new-name', + '--owner', 'new-owner', + '--min-disk', '2', + '--min-ram', '4', + '--container-format', 'ovf', + '--disk-format', 'vmdk', image_fakes.image_id, ] verifylist = [ + ('name', 'new-name'), + ('owner', 'new-owner'), + ('min_disk', 2), + ('min_ram', 4), + ('container_format', 'ovf'), + ('disk_format', 'vmdk'), ('image', image_fakes.image_id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) - self.images_mock.get.assert_called_with( - image_fakes.image_id, - ) + + kwargs = { + 'name': 'new-name', + 'owner': 'new-owner', + 'min_disk': 2, + 'min_ram': 4, + 'container_format': 'ovf', + 'disk_format': 'vmdk', + } + # ImageManager.update(image, **kwargs) + self.images_mock.update.assert_called_with( + image_fakes.image_id, **kwargs) self.assertEqual(image_fakes.IMAGE_columns, columns) self.assertEqual(image_fakes.IMAGE_data, data) + def test_image_set_bools1(self): + arglist = [ + '--protected', + '--private', + image_fakes.image_name, + ] + verifylist = [ + ('protected', True), + ('unprotected', False), + ('public', False), + ('private', True), + ('image', image_fakes.image_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + self.cmd.take_action(parsed_args) -class TestImageSet(TestImage): + kwargs = { + 'protected': True, + 'visibility': 'private', + } + # ImageManager.update(image, **kwargs) + self.images_mock.update.assert_called_with( + image_fakes.image_id, + **kwargs + ) + + def test_image_set_bools2(self): + arglist = [ + '--unprotected', + '--public', + image_fakes.image_name, + ] + verifylist = [ + ('protected', False), + ('unprotected', True), + ('public', True), + ('private', False), + ('image', image_fakes.image_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + self.cmd.take_action(parsed_args) + + kwargs = { + 'protected': False, + 'visibility': 'public', + } + # ImageManager.update(image, **kwargs) + self.images_mock.update.assert_called_with( + image_fakes.image_id, + **kwargs + ) + + def test_image_set_properties(self): + arglist = [ + '--property', 'Alpha=1', + '--property', 'Beta=2', + image_fakes.image_name, + ] + verifylist = [ + ('properties', {'Alpha': '1', 'Beta': '2'}), + ('image', image_fakes.image_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + self.cmd.take_action(parsed_args) + + kwargs = { + 'Alpha': '1', + 'Beta': '2', + } + # ImageManager.update(image, **kwargs) + self.images_mock.update.assert_called_with( + image_fakes.image_id, + **kwargs + ) + + def test_image_set_fake_properties(self): + arglist = [ + '--architecture', 'z80', + '--instance-id', '12345', + '--kernel-id', '67890', + '--os-distro', 'cpm', + '--os-version', '2.2H', + '--ramdisk-id', 'xyzpdq', + image_fakes.image_name, + ] + verifylist = [ + ('architecture', 'z80'), + ('instance_id', '12345'), + ('kernel_id', '67890'), + ('os_distro', 'cpm'), + ('os_version', '2.2H'), + ('ramdisk_id', 'xyzpdq'), + ('image', image_fakes.image_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + self.cmd.take_action(parsed_args) + + kwargs = { + 'architecture': 'z80', + 'instance_id': '12345', + 'kernel_id': '67890', + 'os_distro': 'cpm', + 'os_version': '2.2H', + 'ramdisk_id': 'xyzpdq', + } + # ImageManager.update(image, **kwargs) + self.images_mock.update.assert_called_with( + image_fakes.image_id, + **kwargs + ) + + def test_image_set_dead_options(self): + + arglist = [ + '--visibility', '1-mile', + image_fakes.image_name, + ] + verifylist = [ + ('visibility', '1-mile'), + ('image', image_fakes.image_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, parsed_args) + + +class TestImageShow(TestImage): def setUp(self): - super(TestImageSet, self).setUp() + super(TestImageShow, self).setUp() + # Set up the schema self.model = warlock.model_factory( image_fakes.IMAGE_schema, @@ -685,39 +841,24 @@ def setUp(self): ) self.images_mock.get.return_value = self.model(**image_fakes.IMAGE) - self.images_mock.update.return_value = self.model(**image_fakes.IMAGE) + # Get the command object to test - self.cmd = image.SetImage(self.app, None) + self.cmd = image.ShowImage(self.app, None) - def test_image_set_options(self): + def test_image_show(self): arglist = [ - '--name', 'new-name', - '--owner', 'new-owner', - '--min-disk', '2', - '--min-ram', '4', image_fakes.image_id, ] verifylist = [ - ('name', 'new-name'), - ('owner', 'new-owner'), - ('min_disk', 2), - ('min_ram', 4), ('image', image_fakes.image_id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) - - kwargs = { - 'name': 'new-name', - 'owner': 'new-owner', - 'min_disk': 2, - 'min_ram': 4 - } - # ImageManager.update(image, **kwargs) - self.images_mock.update.assert_called_with( - image_fakes.image_id, **kwargs) + self.images_mock.get.assert_called_with( + image_fakes.image_id, + ) self.assertEqual(image_fakes.IMAGE_columns, columns) self.assertEqual(image_fakes.IMAGE_data, data) From 1afb57453387f9f81d755f23d75d583b732e2d12 Mon Sep 17 00:00:00 2001 From: NiallBunting Date: Wed, 23 Sep 2015 14:54:26 +0000 Subject: [PATCH 0293/3095] Add tags to `image set` This adds --tag to the v2 version of `image set`. This is another step to compatability between the osc image api. Added merge of tags into existing tags and handling duplicates, and tests for same. Co-Authored-By: Steve Martinelli Change-Id: Ie800fcbf8bbc0978c54ace3278750a18023e8ce4 --- doc/source/command-objects/image.rst | 9 ++ openstackclient/image/v2/image.py | 19 ++++- .../tests/compute/v2/test_server.py | 6 +- openstackclient/tests/image/v2/fakes.py | 15 ++++ openstackclient/tests/image/v2/test_image.py | 83 ++++++++++++++++++- 5 files changed, 122 insertions(+), 10 deletions(-) diff --git a/doc/source/command-objects/image.rst b/doc/source/command-objects/image.rst index 8dce3662f3..c3fe77a10b 100644 --- a/doc/source/command-objects/image.rst +++ b/doc/source/command-objects/image.rst @@ -238,6 +238,7 @@ Set image properties [--checksum ] [--stdin] [--property [...] ] + [--tag [...] ] [--architecture ] [--instance-id ] [--kernel-id ] @@ -344,6 +345,14 @@ Set image properties Set a property on this image (repeat option to set multiple properties) + .. versionadded:: 2 + +.. option:: --tag + + Set a tag on this image (repeat for multiple values) + + .. versionadded:: 2 + .. option:: --architecture Operating system architecture diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 11c7483bdb..7ef1f7806a 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -57,8 +57,7 @@ def _format_image(image): properties[key] = image.get(key) # format the tags if they are there - if image.get('tags'): - info['tags'] = utils.format_list(image.get('tags')) + info['tags'] = utils.format_list(image.get('tags')) # add properties back into the dictionary as a top-level key if properties: @@ -540,7 +539,6 @@ def get_parser(self, prog_name): # --force - needs adding # --checksum - maybe could be done client side # --stdin - could be implemented - # --tags - needs adding parser.add_argument( "image", metavar="", @@ -610,6 +608,15 @@ def get_parser(self, prog_name): help="Set a property on this image " "(repeat option to set multiple properties)", ) + parser.add_argument( + "--tag", + dest="tags", + metavar="", + default=[], + action='append', + help="Set a tag on this image " + "(repeat option to set multiple tags)", + ) parser.add_argument( "--architecture", metavar="", @@ -669,7 +676,7 @@ def take_action(self, parsed_args): copy_attrs = ('architecture', 'container_format', 'disk_format', 'file', 'instance_id', 'kernel_id', 'locations', 'min_disk', 'min_ram', 'name', 'os_distro', 'os_version', - 'owner', 'prefix', 'progress', 'ramdisk_id') + 'owner', 'prefix', 'progress', 'ramdisk_id', 'tags') for attr in copy_attrs: if attr in parsed_args: val = getattr(parsed_args, attr, None) @@ -705,6 +712,10 @@ def take_action(self, parsed_args): image = utils.find_resource( image_client.images, parsed_args.image) + if parsed_args.tags: + # Tags should be extended, but duplicates removed + kwargs['tags'] = list(set(image.tags).union(set(parsed_args.tags))) + image = image_client.images.update(image.id, **kwargs) info = {} info.update(image) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 4df18f0523..1e99bcd0ba 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -410,13 +410,14 @@ def test_server_image_create_no_options(self): compute_fakes.server_name, ) - collist = ('id', 'name', 'owner', 'protected', 'visibility') + collist = ('id', 'name', 'owner', 'protected', 'tags', 'visibility') self.assertEqual(collist, columns) datalist = ( image_fakes.image_id, image_fakes.image_name, image_fakes.image_owner, image_fakes.image_protected, + image_fakes.image_tags, image_fakes.image_visibility, ) self.assertEqual(datalist, data) @@ -441,13 +442,14 @@ def test_server_image_create_name(self): 'img-nam', ) - collist = ('id', 'name', 'owner', 'protected', 'visibility') + collist = ('id', 'name', 'owner', 'protected', 'tags', 'visibility') self.assertEqual(collist, columns) datalist = ( image_fakes.image_id, image_fakes.image_name, image_fakes.image_owner, image_fakes.image_protected, + image_fakes.image_tags, image_fakes.image_visibility, ) self.assertEqual(datalist, data) diff --git a/openstackclient/tests/image/v2/fakes.py b/openstackclient/tests/image/v2/fakes.py index 1a9e301a01..11ad455df2 100644 --- a/openstackclient/tests/image/v2/fakes.py +++ b/openstackclient/tests/image/v2/fakes.py @@ -13,6 +13,7 @@ # under the License. # +import copy import mock from openstackclient.tests import fakes @@ -25,6 +26,7 @@ image_owner = 'baal' image_protected = False image_visibility = 'public' +image_tags = [] IMAGE = { 'id': image_id, @@ -32,11 +34,16 @@ 'owner': image_owner, 'protected': image_protected, 'visibility': image_visibility, + 'tags': image_tags } IMAGE_columns = tuple(sorted(IMAGE)) IMAGE_data = tuple((IMAGE[x] for x in sorted(IMAGE))) +IMAGE_SHOW = copy.copy(IMAGE) +IMAGE_SHOW['tags'] = '' +IMAGE_SHOW_data = tuple((IMAGE_SHOW[x] for x in sorted(IMAGE_SHOW))) + member_status = 'pending' MEMBER = { 'member_id': identity_fakes.project_id, @@ -117,6 +124,14 @@ "type": "string", "description": "Status of the image (READ-ONLY)" }, + "tags": { + "items": { + "type": "string", + "maxLength": 255 + }, + "type": "array", + "description": "List of strings related to the image" + }, "visibility": { "enum": [ "public", diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index ce2974d692..46da9c6893 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -96,7 +96,7 @@ def test_image_reserve_no_options(self): ) self.assertEqual(image_fakes.IMAGE_columns, columns) - self.assertEqual(image_fakes.IMAGE_data, data) + self.assertEqual(image_fakes.IMAGE_SHOW_data, data) @mock.patch('glanceclient.common.utils.get_data_file', name='Open') def test_image_reserve_options(self, mock_open): @@ -151,7 +151,7 @@ def test_image_reserve_options(self, mock_open): ) self.assertEqual(image_fakes.IMAGE_columns, columns) - self.assertEqual(image_fakes.IMAGE_data, data) + self.assertEqual(image_fakes.IMAGE_SHOW_data, data) @mock.patch('glanceclient.common.utils.get_data_file', name='Open') def test_image_create_file(self, mock_open): @@ -208,7 +208,7 @@ def test_image_create_file(self, mock_open): ) self.assertEqual(image_fakes.IMAGE_columns, columns) - self.assertEqual(image_fakes.IMAGE_data, data) + self.assertEqual(image_fakes.IMAGE_SHOW_data, data) def test_image_create_dead_options(self): @@ -812,6 +812,81 @@ def test_image_set_fake_properties(self): **kwargs ) + def test_image_set_tag(self): + arglist = [ + '--tag', 'test-tag', + image_fakes.image_name, + ] + verifylist = [ + ('tags', ['test-tag']), + ('image', image_fakes.image_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + self.cmd.take_action(parsed_args) + + kwargs = { + 'tags': ['test-tag'], + } + # ImageManager.update(image, **kwargs) + self.images_mock.update.assert_called_with( + image_fakes.image_id, + **kwargs + ) + + def test_image_set_tag_merge(self): + old_image = copy.copy(image_fakes.IMAGE) + old_image['tags'] = ['old1', 'new2'] + self.images_mock.get.return_value = self.model(**old_image) + arglist = [ + '--tag', 'test-tag', + image_fakes.image_name, + ] + verifylist = [ + ('tags', ['test-tag']), + ('image', image_fakes.image_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + self.cmd.take_action(parsed_args) + + kwargs = { + 'tags': ['old1', 'new2', 'test-tag'], + } + # ImageManager.update(image, **kwargs) + a, k = self.images_mock.update.call_args + self.assertEqual(image_fakes.image_id, a[0]) + self.assertTrue('tags' in k) + self.assertEqual(set(kwargs['tags']), set(k['tags'])) + + def test_image_set_tag_merge_dupe(self): + old_image = copy.copy(image_fakes.IMAGE) + old_image['tags'] = ['old1', 'new2'] + self.images_mock.get.return_value = self.model(**old_image) + arglist = [ + '--tag', 'old1', + image_fakes.image_name, + ] + verifylist = [ + ('tags', ['old1']), + ('image', image_fakes.image_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + self.cmd.take_action(parsed_args) + + kwargs = { + 'tags': ['new2', 'old1'], + } + # ImageManager.update(image, **kwargs) + a, k = self.images_mock.update.call_args + self.assertEqual(image_fakes.image_id, a[0]) + self.assertTrue('tags' in k) + self.assertEqual(set(kwargs['tags']), set(k['tags'])) + def test_image_set_dead_options(self): arglist = [ @@ -861,4 +936,4 @@ def test_image_show(self): ) self.assertEqual(image_fakes.IMAGE_columns, columns) - self.assertEqual(image_fakes.IMAGE_data, data) + self.assertEqual(image_fakes.IMAGE_SHOW_data, data) From 2bd82ab89258110b44135ae4cf2c4678938673d2 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Thu, 24 Sep 2015 12:00:39 -0400 Subject: [PATCH 0294/3095] image set should not show the resource the rest of OSC set commands do not show the resource after it has been updated. unless the update fails then we report back a failure, otherwise the user should assume everything went fine. Change-Id: I2bd4188450c3853b4a1bc25f80fc9450cda32bdd --- doc/source/backwards-incompatible.rst | 12 ++++++++++++ functional/tests/image/v1/test_image.py | 17 ++++++++--------- openstackclient/image/v1/image.py | 10 +++------- openstackclient/image/v2/image.py | 5 +---- openstackclient/tests/image/v1/test_image.py | 12 ++---------- openstackclient/tests/image/v2/test_image.py | 5 +---- 6 files changed, 27 insertions(+), 34 deletions(-) diff --git a/doc/source/backwards-incompatible.rst b/doc/source/backwards-incompatible.rst index e89cc3a680..f9f2ed4485 100644 --- a/doc/source/backwards-incompatible.rst +++ b/doc/source/backwards-incompatible.rst @@ -90,6 +90,18 @@ List of Backwards Incompatible Changes * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1453229 * Commit: https://review.openstack.org/#/c/181514/ +7. `image set` commands will no longer return the modified resource + + Previously, modifying an image would result in the new image being displayed + to the user. To keep things consistent with other `set` commands, we will + no longer be showing the modified resource. + + * In favor of: Use `set` then `show` + * As of: NA + * Removed in: NA + * Bug: NA + * Commit: NA + For Developers ============== diff --git a/functional/tests/image/v1/test_image.py b/functional/tests/image/v1/test_image.py index 17c0c3dd8f..fe61f83036 100644 --- a/functional/tests/image/v1/test_image.py +++ b/functional/tests/image/v1/test_image.py @@ -35,10 +35,9 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): # Rename test - opts = cls.get_show_opts(cls.FIELDS) - raw_output = cls.openstack( - 'image set --name ' + cls.OTHER_NAME + ' ' + cls.NAME + opts) - cls.assertOutput(cls.OTHER_NAME + "\n", raw_output) + raw_output = cls.openstack('image set --name ' + cls.OTHER_NAME + ' ' + + cls.NAME) + cls.assertOutput('', raw_output) # Delete test raw_output = cls.openstack('image delete ' + cls.OTHER_NAME) cls.assertOutput('', raw_output) @@ -56,13 +55,13 @@ def test_image_show(self): def test_image_set(self): opts = self.get_show_opts([ "disk_format", "is_public", "min_disk", "min_ram", "name"]) - raw_output = self.openstack('image set --min-disk 4 --min-ram 5 ' + - '--disk-format qcow2 --public ' + - self.NAME + opts) + self.openstack('image set --min-disk 4 --min-ram 5 ' + + '--disk-format qcow2 --public ' + self.NAME) + raw_output = self.openstack('image show ' + self.NAME + opts) self.assertEqual("qcow2\nTrue\n4\n5\n" + self.NAME + '\n', raw_output) def test_image_metadata(self): opts = self.get_show_opts(["name", "properties"]) - raw_output = self.openstack( - 'image set --property a=b --property c=d ' + self.NAME + opts) + self.openstack('image set --property a=b --property c=d ' + self.NAME) + raw_output = self.openstack('image show ' + self.NAME + opts) self.assertEqual(self.NAME + "\na='b', c='d'\n", raw_output) diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index 68c81cd58d..23f5713621 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -452,7 +452,7 @@ def take_action(self, parsed_args): gc_utils.save_image(data, parsed_args.file) -class SetImage(show.ShowOne): +class SetImage(command.Command): """Set image properties""" log = logging.getLogger(__name__ + ".SetImage") @@ -629,7 +629,7 @@ def take_action(self, parsed_args): volume_client.volumes, parsed_args.volume, ) - response, body = volume_client.volumes.upload_to_image( + volume_client.volumes.upload_to_image( source_volume.id, parsed_args.force, parsed_args.image, @@ -640,7 +640,6 @@ def take_action(self, parsed_args): if parsed_args.disk_format else image.disk_format), ) - info = body['os-volume_upload_image'] elif parsed_args.file: # Send an open file handle to glanceclient so it will # do a chunked transfer @@ -673,10 +672,7 @@ def take_action(self, parsed_args): kwargs['data'] != sys.stdin): kwargs['data'].close() - info = {} - info.update(image._info) - info['properties'] = utils.format_dict(info.get('properties', {})) - return zip(*sorted(six.iteritems(info))) + return class ShowImage(show.ShowOne): diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 7ef1f7806a..7d8b14124a 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -521,7 +521,7 @@ def take_action(self, parsed_args): gc_utils.save_image(data, parsed_args.file) -class SetImage(show.ShowOne): +class SetImage(command.Command): """Set image properties""" log = logging.getLogger(__name__ + ".SetImage") @@ -717,9 +717,6 @@ def take_action(self, parsed_args): kwargs['tags'] = list(set(image.tags).union(set(parsed_args.tags))) image = image_client.images.update(image.id, **kwargs) - info = {} - info.update(image) - return zip(*sorted(six.iteritems(info))) class ShowImage(show.ShowOne): diff --git a/openstackclient/tests/image/v1/test_image.py b/openstackclient/tests/image/v1/test_image.py index a79df8b4a1..d10d3b1577 100644 --- a/openstackclient/tests/image/v1/test_image.py +++ b/openstackclient/tests/image/v1/test_image.py @@ -499,8 +499,7 @@ def test_image_set_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples - columns, data = self.cmd.take_action(parsed_args) + self.cmd.take_action(parsed_args) kwargs = { 'name': 'new-name', @@ -517,9 +516,6 @@ def test_image_set_options(self): **kwargs ) - self.assertEqual(image_fakes.IMAGE_columns, columns) - self.assertEqual(image_fakes.IMAGE_data, data) - def test_image_set_bools1(self): arglist = [ '--protected', @@ -644,8 +640,7 @@ def test_image_update_volume(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples - columns, data = self.cmd.take_action(parsed_args) + self.cmd.take_action(parsed_args) # VolumeManager.upload_to_image(volume, force, image_name, # container_format, disk_format) @@ -664,9 +659,6 @@ def test_image_update_volume(self): volume='volly', ) - self.assertEqual(image_fakes.IMAGE_columns, columns) - self.assertEqual(image_fakes.IMAGE_data, data) - class TestImageShow(TestImage): diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index 46da9c6893..4ce854759b 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -676,7 +676,7 @@ def test_image_set_options(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) # DisplayCommandBase.take_action() returns two tuples - columns, data = self.cmd.take_action(parsed_args) + self.cmd.take_action(parsed_args) kwargs = { 'name': 'new-name', @@ -690,9 +690,6 @@ def test_image_set_options(self): self.images_mock.update.assert_called_with( image_fakes.image_id, **kwargs) - self.assertEqual(image_fakes.IMAGE_columns, columns) - self.assertEqual(image_fakes.IMAGE_data, data) - def test_image_set_bools1(self): arglist = [ '--protected', From 80e3a2dedbaccd582b5761b3419ec5ea033d7fe8 Mon Sep 17 00:00:00 2001 From: Alex Schultz Date: Tue, 6 Oct 2015 15:59:50 -0500 Subject: [PATCH 0295/3095] Add ID column to compute service list This change adds the ID to the compute service list command so that the ID can be leveraged by scripts trying to remove or update a specific service. Change-Id: I446b4c0071988133195eb2382313b3918b7ffa72 Closes-Bug: #1503430 --- openstackclient/compute/v2/service.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index 4b2ebac6ba..3a031bff25 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.py @@ -43,6 +43,7 @@ def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) compute_client = self.app.client_manager.compute columns = ( + "Id", "Binary", "Host", "Zone", From 985b2cdd2c07442f4446e6164117cb8011269c8c Mon Sep 17 00:00:00 2001 From: lin-hua-cheng Date: Tue, 6 Oct 2015 23:26:08 -0700 Subject: [PATCH 0296/3095] Fix non-ascii issue with object commands Escape the container and object name whenever it is used as URL. Change-Id: I2343c1e67843ab53773b3fca6e258dc329cd9573 Closes-Bug: #1503508 --- openstackclient/api/object_store_v1.py | 35 ++++++++++++++++---------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/openstackclient/api/object_store_v1.py b/openstackclient/api/object_store_v1.py index c870332aec..ae03ab7dd2 100644 --- a/openstackclient/api/object_store_v1.py +++ b/openstackclient/api/object_store_v1.py @@ -16,6 +16,7 @@ import io import os import six +from six.moves import urllib try: from urllib.parse import urlparse # noqa @@ -42,8 +43,7 @@ def container_create( :returns: dict of returned headers """ - - response = self.create(container, method='PUT') + response = self.create(urllib.parse.quote(container), method='PUT') data = { 'account': self._find_account_id(), 'container': container, @@ -63,7 +63,7 @@ def container_delete( """ if container: - self.delete(container) + self.delete(urllib.parse.quote(container)) def container_list( self, @@ -154,7 +154,7 @@ def container_set( headers = self._set_properties(properties, 'X-Container-Meta-%s') if headers: - self.create(container, headers=headers) + self.create(urllib.parse.quote(container), headers=headers) def container_show( self, @@ -168,7 +168,7 @@ def container_show( dict of returned headers """ - response = self._request('HEAD', container) + response = self._request('HEAD', urllib.parse.quote(container)) data = { 'account': self._find_account_id(), 'container': container, @@ -201,7 +201,7 @@ def container_unset( headers = self._unset_properties(properties, 'X-Remove-Container-Meta-%s') if headers: - self.create(container, headers=headers) + self.create(urllib.parse.quote(container), headers=headers) def object_create( self, @@ -222,7 +222,8 @@ def object_create( # TODO(dtroyer): What exception to raise here? return {} - full_url = "%s/%s" % (container, object) + full_url = "%s/%s" % (urllib.parse.quote(container), + urllib.parse.quote(object)) with io.open(object, 'rb') as f: response = self.create( full_url, @@ -255,7 +256,8 @@ def object_delete( if container is None or object is None: return - self.delete("%s/%s" % (container, object)) + self.delete("%s/%s" % (urllib.parse.quote(container), + urllib.parse.quote(object))) def object_list( self, @@ -332,7 +334,7 @@ def object_list( if delimiter: params['delimiter'] = delimiter - return self.list(container, **params) + return self.list(urllib.parse.quote(container), **params) def object_save( self, @@ -355,7 +357,8 @@ def object_save( response = self._request( 'GET', - "%s/%s" % (container, object), + "%s/%s" % (urllib.parse.quote(container), + urllib.parse.quote(object)), stream=True, ) if response.status_code == 200: @@ -384,7 +387,9 @@ def object_set( headers = self._set_properties(properties, 'X-Object-Meta-%s') if headers: - self.create("%s/%s" % (container, object), headers=headers) + self.create("%s/%s" % (urllib.parse.quote(container), + urllib.parse.quote(object)), + headers=headers) def object_unset( self, @@ -404,7 +409,9 @@ def object_unset( headers = self._unset_properties(properties, 'X-Remove-Object-Meta-%s') if headers: - self.create("%s/%s" % (container, object), headers=headers) + self.create("%s/%s" % (urllib.parse.quote(container), + urllib.parse.quote(object)), + headers=headers) def object_show( self, @@ -424,7 +431,9 @@ def object_show( if container is None or object is None: return {} - response = self._request('HEAD', "%s/%s" % (container, object)) + response = self._request('HEAD', "%s/%s" % + (urllib.parse.quote(container), + urllib.parse.quote(object))) data = { 'account': self._find_account_id(), 'container': container, From e523c699a29b87200b15a99b2b159fa3583f5dc1 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 7 Oct 2015 17:37:20 -0500 Subject: [PATCH 0297/3095] Add test for role list --inherited Change-Id: I216ab6c8ac903720ec67870a5171ae57a8f293aa --- .../tests/identity/v3/test_role.py | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/openstackclient/tests/identity/v3/test_role.py b/openstackclient/tests/identity/v3/test_role.py index ee3dc946a1..8ad4b099b4 100644 --- a/openstackclient/tests/identity/v3/test_role.py +++ b/openstackclient/tests/identity/v3/test_role.py @@ -367,6 +367,39 @@ def test_role_list_no_options(self): ), ) self.assertEqual(datalist, tuple(data)) + def test_user_list_inherited(self): + arglist = [ + '--user', identity_fakes.user_id, + '--inherited', + ] + verifylist = [ + ('user', identity_fakes.user_id), + ('inherited', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'domain': 'default', + 'user': self.users_mock.get(), + 'os_inherit_extension_inherited': True, + } + # RoleManager.list(user=, group=, domain=, project=, **kwargs) + self.roles_mock.list.assert_called_with( + **kwargs + ) + + collist = ('ID', 'Name') + self.assertEqual(collist, columns) + datalist = (( + identity_fakes.role_id, + identity_fakes.role_name, + ), ) + self.assertEqual(datalist, tuple(data)) + def test_user_list_user(self): arglist = [ '--user', identity_fakes.user_id, From 27465f193d60250339dfcc4579753996c9a2c4e5 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 8 Oct 2015 00:09:09 +0000 Subject: [PATCH 0298/3095] Updated from global requirements Change-Id: I30b638f4f04d0fbf4ffc2fac965306ce99faacf1 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c638ed1875..2556c135f7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,7 +13,7 @@ oslo.i18n>=1.5.0 # Apache-2.0 oslo.utils>=2.0.0 # Apache-2.0 python-glanceclient>=0.18.0 python-keystoneclient>=1.6.0 -python-novaclient>=2.28.1 +python-novaclient>=2.29.0 python-cinderclient>=1.3.1 python-neutronclient>=2.6.0 requests>=2.5.2 From 851393eb0bf60edf92b91e7b40b5e46cfae6353c Mon Sep 17 00:00:00 2001 From: Daisuke Fujita Date: Thu, 8 Oct 2015 10:16:47 +0900 Subject: [PATCH 0299/3095] Fix typos in authentication.rst 1. "inital" to "initial" 2. "set of of environment variables" to "set of environment variables" Change-Id: I7040792c1a03a9a3ac873f9a0428bfa1178fd550 --- doc/source/authentication.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/authentication.rst b/doc/source/authentication.rst index bf23b66a01..a3986ee414 100644 --- a/doc/source/authentication.rst +++ b/doc/source/authentication.rst @@ -83,7 +83,7 @@ by the ``ClientManager`` object. * Load the selected plugin class. * When an operation that requires authentication is attempted ``ClientManager`` - makes the actual inital request to the Identity service. + makes the actual initial request to the Identity service. * if ``--os-auth-url`` is not supplied for any of the types except Token/Endpoint, exit with an error. @@ -132,7 +132,7 @@ If using a domain as authorization scope, set either it's name or ID. Note that if the user and project share the same domain, then simply setting ``os-default-domain`` or ``OS_DEFAULT_DOMAIN`` is sufficient. -Thus, a minimal set of of environment variables would be: +Thus, a minimal set of environment variables would be: .. code-block:: bash From 2eaaf37a0d0fdfc68df2daec2ba8b7c2bd40f69b Mon Sep 17 00:00:00 2001 From: Daisuke Fujita Date: Thu, 8 Oct 2015 10:56:23 +0900 Subject: [PATCH 0300/3095] Fix a typo in commands.rst 1. "ec2 cedentials" to "ec2 credentials" Change-Id: I9a17c0d592f9a4d6e53e6dd786cba405468a3e09 --- doc/source/commands.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/commands.rst b/doc/source/commands.rst index b52d94590a..ccd70be25d 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -80,7 +80,7 @@ referring to both Compute and Volume quotas. * ``container``: (**Object Store**) a grouping of objects * ``credentials``: (**Identity**) specific to identity providers * ``domain``: (**Identity**) a grouping of projects -* ``ec2 cedentials``: (**Identity**) AWS EC2-compatible credentials +* ``ec2 credentials``: (**Identity**) AWS EC2-compatible credentials * ``endpoint``: (**Identity**) the base URL used to contact a specific service * ``extension``: (**Compute**, **Identity**, **Volume**) OpenStack server API extensions * ``federation protocol``: (**Identity**) the underlying protocol used while federating identities From bd14d078098eb2055c0dae784de892b0b1346ed5 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Thu, 8 Oct 2015 02:10:39 -0400 Subject: [PATCH 0301/3095] unwedge the gate devstack defaults to v3 and makes some janky assumptions in the generated rc files Change-Id: I4b0b3eb6ab2aa9be2ac4c2404b2f9655e3ed564e --- functional/tests/volume/v1/test_volume.py | 2 ++ post_test_hook.sh | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/functional/tests/volume/v1/test_volume.py b/functional/tests/volume/v1/test_volume.py index 596b8217f3..874be6e115 100644 --- a/functional/tests/volume/v1/test_volume.py +++ b/functional/tests/volume/v1/test_volume.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import os import uuid from functional.tests.volume.v1 import common @@ -25,6 +26,7 @@ class VolumeTests(common.BaseVolumeTests): @classmethod def setUpClass(cls): + os.environ['OS_VOLUME_API_VERSION'] = '1' opts = cls.get_show_opts(cls.FIELDS) raw_output = cls.openstack('volume create --size 1 ' + cls.NAME + opts) expected = cls.NAME + '\n' diff --git a/post_test_hook.sh b/post_test_hook.sh index 4c35b52099..7bb036f9cf 100755 --- a/post_test_hook.sh +++ b/post_test_hook.sh @@ -12,7 +12,7 @@ OPENSTACKCLIENT_DIR=$(cd $(dirname "$0") && pwd) echo "Running openstackclient functional test suite" sudo -H -u stack -i < Date: Tue, 6 Oct 2015 17:47:38 -0400 Subject: [PATCH 0302/3095] Add a table showing all the openstack plugin objects We need a spot that highlights the `objects` used by OpenStack based `plugins`. The sooner we have this information out, the sooner we reduce the chance of a name collision. Change-Id: If9f07e19adea4aaa813eecdd0bce1fbfe616e306 --- doc/source/commands.rst | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/doc/source/commands.rst b/doc/source/commands.rst index b52d94590a..168950c97c 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -120,6 +120,40 @@ referring to both Compute and Volume quotas. * ``volume``: (**Volume**) block volumes * ``volume type``: (**Volume**) deployment-specific types of volumes available + +Plugin Objects +-------------- + +The following are known `Objects` used by OpenStack :doc:`plugins`. These are +listed here to avoid name conflicts when creating new plugins. + +* ``baremetal``: (**Baremetal (Ironic)**) +* ``congress datasource``: (**Policy (Congress)**) +* ``congress driver``: (**Policy (Congress)**) +* ``congress policy``: (**Policy (Congress)**) +* ``congress policy rule``: (**Policy (Congress)**) +* ``dataprocessing data source``: (**Data Processing (Sahara)**) +* ``dataprocessing image``: (**Data Processing (Sahara)**) +* ``dataprocessing image tags``: (**Data Processing (Sahara)**) +* ``dataprocessing plugin``: (**Data Processing (Sahara)**) +* ``management plan``: (**Management (Tuskar)**) +* ``management role``: (**Management (Tuskar)**) +* ``message-broker cluster``: (**Message Broker (Cue)**) +* ``message flavor``: (**Messaging (Zaqar)**) +* ``pool``: (**Messaging (Zaqar)**) +* ``ptr record``: (**DNS (Designate)**) +* ``queue``: (**Messaging (Zaqar)**) +* ``recordset``: (**DNS (Designate)**) +* ``secret``: (**Key Manager (Barbican)**) +* ``secret container``: (**Key Manager (Barbican)**) +* ``secret order``: (**Key Manager (Barbican)**) +* ``stack``: (**Orchestration (Heat)**) +* ``tld``: (**DNS (Designate)**) +* ``zone``: (**DNS (Designate)**) +* ``zone blacklist``: (**DNS (Designate)**) +* ``zone transfer``: (**DNS (Designate)**) + + Actions ------- From 17f794ca08ab7b74a73667d82e375b3c01adca1d Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Thu, 3 Sep 2015 13:52:44 -0500 Subject: [PATCH 0303/3095] Change Identity API default version to 3 Devstack now issues all v3 OSC commands for our CI, and since v3 has everything v2 has, we should be able to switch to v3. Furthermore, most OSC users were initially using OSC since they were looking to exploit v3 keystone capabilities. Change-Id: If7de86946f6dd0f148aa5f526155cccf90adfcc3 --- openstackclient/identity/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/identity/client.py b/openstackclient/identity/client.py index b8bb33f43f..bd882ce80b 100644 --- a/openstackclient/identity/client.py +++ b/openstackclient/identity/client.py @@ -21,7 +21,7 @@ LOG = logging.getLogger(__name__) -DEFAULT_API_VERSION = '2' +DEFAULT_API_VERSION = '3' API_VERSION_OPTION = 'os_identity_api_version' API_NAME = 'identity' API_VERSIONS = { From 1f8b81462826284068b96ffce2648344c9e7c644 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Fri, 11 Sep 2015 00:46:03 -0500 Subject: [PATCH 0304/3095] Fix up object-store show commands 1) Change metadata to appear under a common 'properties' key, and use the utility to format them, this applied to object, account and container. 2) Clean up container and object output, which were setting the x-container-meta-owner property, but this is metadata only for the container, so it's pointless to have, removed it. 3) Container show was showing read/write ACLs and sync stuff, but these are not being returned by my swift by default, so I moved these to be checks, so we don't clutter the output. Change-Id: Ife7521fe9c2724035b06963c118bd6016ba2f5b5 --- openstackclient/api/object_store_v1.py | 66 ++++++++++++------- openstackclient/object/v1/account.py | 2 + openstackclient/object/v1/container.py | 2 + openstackclient/object/v1/object.py | 2 + .../tests/api/test_object_store_v1.py | 9 +-- .../tests/object/v1/test_container_all.py | 3 - .../tests/object/v1/test_object_all.py | 2 - 7 files changed, 48 insertions(+), 38 deletions(-) diff --git a/openstackclient/api/object_store_v1.py b/openstackclient/api/object_store_v1.py index ae03ab7dd2..b1c78d990a 100644 --- a/openstackclient/api/object_store_v1.py +++ b/openstackclient/api/object_store_v1.py @@ -15,6 +15,7 @@ import io import os + import six from six.moves import urllib @@ -176,13 +177,24 @@ def container_show( 'x-container-object-count', None, ), - 'meta-owner': response.headers.get('x-container-meta-owner', None), - 'bytes_used': response.headers.get('x-container-bytes-used', None), - 'read_acl': response.headers.get('x-container-read', None), - 'write_acl': response.headers.get('x-container-write', None), - 'sync_to': response.headers.get('x-container-sync-to', None), - 'sync_key': response.headers.get('x-container-sync-key', None), + 'bytes_used': response.headers.get('x-container-bytes-used', None) } + + if 'x-container-read' in response.headers: + data['read_acl'] = response.headers.get('x-container-read', None) + if 'x-container-write' in response.headers: + data['write_acl'] = response.headers.get('x-container-write', None) + if 'x-container-sync-to' in response.headers: + data['sync_to'] = response.headers.get('x-container-sync-to', None) + if 'x-container-sync-key' in response.headers: + data['sync_key'] = response.headers.get('x-container-sync-key', + None) + + properties = self._get_properties(response.headers, + 'x-container-meta-') + if properties: + data['properties'] = properties + return data def container_unset( @@ -434,12 +446,12 @@ def object_show( response = self._request('HEAD', "%s/%s" % (urllib.parse.quote(container), urllib.parse.quote(object))) + data = { 'account': self._find_account_id(), 'container': container, 'object': object, 'content-type': response.headers.get('content-type', None), - 'meta-owner': response.headers.get('x-container-meta-owner', None), } if 'content-length' in response.headers: data['content-length'] = response.headers.get( @@ -455,19 +467,10 @@ def object_show( 'x-object-manifest', None, ) - for key, value in six.iteritems(response.headers): - if key.startswith('x-object-meta-'): - data[key[len('x-object-meta-'):].lower()] = value - elif key not in ( - 'content-type', - 'content-length', - 'last-modified', - 'etag', - 'date', - 'x-object-manifest', - 'x-container-meta-owner', - ): - data[key.lower()] = value + + properties = self._get_properties(response.headers, 'x-object-meta-') + if properties: + data['properties'] = properties return data @@ -495,12 +498,16 @@ def account_show(self): # catalog should be enough. response = self._request("HEAD", "") data = {} - for k, v in response.headers.iteritems(): - data[k] = v + + properties = self._get_properties(response.headers, 'x-account-meta-') + if properties: + data['properties'] = properties + # Map containers, bytes and objects a bit nicer - data['Containers'] = data.pop('x-account-container-count', None) - data['Objects'] = data.pop('x-account-object-count', None) - data['Bytes'] = data.pop('x-account-bytes-used', None) + data['Containers'] = response.headers.get('x-account-container-count', + None) + data['Objects'] = response.headers.get('x-account-object-count', None) + data['Bytes'] = response.headers.get('x-account-bytes-used', None) # Add in Account info too data['Account'] = self._find_account_id() return data @@ -549,3 +556,12 @@ def _set_properties(self, properties, header_tag): header_name = header_tag % k headers[header_name] = v return headers + + def _get_properties(self, headers, header_tag): + # Add in properties as a top level key, this is consistent with other + # OSC commands + properties = {} + for k, v in six.iteritems(headers): + if k.startswith(header_tag): + properties[k[len(header_tag):]] = v + return properties diff --git a/openstackclient/object/v1/account.py b/openstackclient/object/v1/account.py index 4ff890ce10..aa94ff5c2c 100644 --- a/openstackclient/object/v1/account.py +++ b/openstackclient/object/v1/account.py @@ -55,6 +55,8 @@ class ShowAccount(show.ShowOne): @utils.log_method(log) def take_action(self, parsed_args): data = self.app.client_manager.object_store.account_show() + if 'properties' in data: + data['properties'] = utils.format_dict(data.pop('properties')) return zip(*sorted(six.iteritems(data))) diff --git a/openstackclient/object/v1/container.py b/openstackclient/object/v1/container.py index b8eb4c254e..8c8844e2b9 100644 --- a/openstackclient/object/v1/container.py +++ b/openstackclient/object/v1/container.py @@ -229,6 +229,8 @@ def take_action(self, parsed_args): data = self.app.client_manager.object_store.container_show( container=parsed_args.container, ) + if 'properties' in data: + data['properties'] = utils.format_dict(data.pop('properties')) return zip(*sorted(six.iteritems(data))) diff --git a/openstackclient/object/v1/object.py b/openstackclient/object/v1/object.py index a023e3a00c..4bd06124d6 100644 --- a/openstackclient/object/v1/object.py +++ b/openstackclient/object/v1/object.py @@ -284,6 +284,8 @@ def take_action(self, parsed_args): container=parsed_args.container, object=parsed_args.object, ) + if 'properties' in data: + data['properties'] = utils.format_dict(data.pop('properties')) return zip(*sorted(six.iteritems(data))) diff --git a/openstackclient/tests/api/test_object_store_v1.py b/openstackclient/tests/api/test_object_store_v1.py index 323bb8e0b5..0f7c45599f 100644 --- a/openstackclient/tests/api/test_object_store_v1.py +++ b/openstackclient/tests/api/test_object_store_v1.py @@ -157,11 +157,6 @@ def test_container_show(self): 'container': 'qaz', 'object_count': '1', 'bytes_used': '577', - 'meta-owner': FAKE_ACCOUNT, - 'read_acl': None, - 'write_acl': None, - 'sync_to': None, - 'sync_key': None, } self.requests_mock.register_uri( 'HEAD', @@ -323,10 +318,8 @@ def test_object_show(self): 'content-type': 'text/alpha', 'content-length': '577', 'last-modified': '20130101', - 'meta-owner': FAKE_ACCOUNT, 'etag': 'qaz', - 'wife': 'Wilma', - 'x-tra-header': 'yabba-dabba-do', + 'properties': {'wife': 'Wilma'}, } self.requests_mock.register_uri( 'HEAD', diff --git a/openstackclient/tests/object/v1/test_container_all.py b/openstackclient/tests/object/v1/test_container_all.py index 4477f2e03b..69fc0f3962 100644 --- a/openstackclient/tests/object/v1/test_container_all.py +++ b/openstackclient/tests/object/v1/test_container_all.py @@ -286,7 +286,6 @@ def setUp(self): def test_object_show_container(self): headers = { - 'x-container-meta-owner': object_fakes.ACCOUNT_ID, 'x-container-object-count': '42', 'x-container-bytes-used': '123', 'x-container-read': 'qaz', @@ -316,7 +315,6 @@ def test_object_show_container(self): 'account', 'bytes_used', 'container', - 'meta-owner', 'object_count', 'read_acl', 'sync_key', @@ -328,7 +326,6 @@ def test_object_show_container(self): object_fakes.ACCOUNT_ID, '123', 'ernie', - object_fakes.ACCOUNT_ID, '42', 'qaz', 'rfv', diff --git a/openstackclient/tests/object/v1/test_object_all.py b/openstackclient/tests/object/v1/test_object_all.py index 41fe6324cd..7a76ab76ad 100644 --- a/openstackclient/tests/object/v1/test_object_all.py +++ b/openstackclient/tests/object/v1/test_object_all.py @@ -160,7 +160,6 @@ def test_object_show(self): 'content-type', 'etag', 'last-modified', - 'meta-owner', 'object', 'x-object-manifest', ) @@ -172,7 +171,6 @@ def test_object_show(self): 'text/plain', '4c4e39a763d58392724bccf76a58783a', 'yesterday', - object_fakes.ACCOUNT_ID, object_fakes.object_name_1, 'manifest', ) From b12d8502226f56421bbe90747cf1727222c6e6fa Mon Sep 17 00:00:00 2001 From: Sirushti Murugesan Date: Mon, 12 Oct 2015 17:23:06 +0530 Subject: [PATCH 0305/3095] Fix functional tests for Python 3.4 * shlex.split() expects a string. Not bytes. * decode the bytestring result of subprocess's communicate() to a string. Change-Id: I209f67a91dc609b1e30cb9e683d3d6ee63d00069 --- functional/common/test.py | 3 ++- functional/tests/compute/v2/test_server.py | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/functional/common/test.py b/functional/common/test.py index 50d59fd19f..2fc355f82f 100644 --- a/functional/common/test.py +++ b/functional/common/test.py @@ -28,13 +28,14 @@ def execute(cmd, fail_ok=False, merge_stderr=False): """Executes specified command for the given action.""" - cmdlist = shlex.split(cmd.encode('utf-8')) + cmdlist = shlex.split(cmd) result = '' result_err = '' stdout = subprocess.PIPE stderr = subprocess.STDOUT if merge_stderr else subprocess.PIPE proc = subprocess.Popen(cmdlist, stdout=stdout, stderr=stderr) result, result_err = proc.communicate() + result = result.decode('utf-8') if not fail_ok and proc.returncode != 0: raise exceptions.CommandFailed(proc.returncode, cmd, result, result_err) diff --git a/functional/tests/compute/v2/test_server.py b/functional/tests/compute/v2/test_server.py index a6cc98e621..bd1b2a876b 100644 --- a/functional/tests/compute/v2/test_server.py +++ b/functional/tests/compute/v2/test_server.py @@ -32,14 +32,14 @@ class ServerTests(test.TestCase): def get_flavor(cls): raw_output = cls.openstack('flavor list -f value -c ID') ray = raw_output.split('\n') - idx = len(ray)/2 + idx = int(len(ray)/2) return ray[idx] @classmethod def get_image(cls): raw_output = cls.openstack('image list -f value -c ID') ray = raw_output.split('\n') - idx = len(ray)/2 + idx = int(len(ray)/2) return ray[idx] @classmethod @@ -49,7 +49,7 @@ def get_network(cls): except exceptions.CommandFailed: return '' ray = raw_output.split('\n') - idx = len(ray)/2 + idx = int(len(ray)/2) return ' --nic net-id=' + ray[idx] @classmethod From f0a81c284d2f533e0fe8adc747c5bd0532a7684f Mon Sep 17 00:00:00 2001 From: lin-hua-cheng Date: Tue, 6 Oct 2015 20:42:40 -0700 Subject: [PATCH 0306/3095] Mask the sensitive values in debug log Change-Id: I0eb11a648c3be21749690f079229c8e63a678e6c Closes-Bug: #1501598 --- openstackclient/common/clientmanager.py | 4 +++- openstackclient/shell.py | 10 +++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index 55c6fe53f0..edabf65e57 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -20,6 +20,7 @@ import pkg_resources import sys +from oslo_utils import strutils import requests from openstackclient.api import auth @@ -167,7 +168,8 @@ def setup_auth(self): self._project_name = self._auth_params['tenant_name'] LOG.info('Using auth plugin: %s' % self.auth_plugin_name) - LOG.debug('Using parameters %s' % self._auth_params) + LOG.debug('Using parameters %s' % + strutils.mask_password(self._auth_params)) self.auth = auth_plugin.load_from_options(**self._auth_params) # needed by SAML authentication request_session = requests.session() diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 5b36b8b2fa..1a5055bd42 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -25,6 +25,7 @@ from cliff import command from cliff import complete from cliff import help +from oslo_utils import strutils import openstackclient from openstackclient.common import clientmanager @@ -201,8 +202,10 @@ def initialize_app(self, argv): # Parent __init__ parses argv into self.options super(OpenStackShell, self).initialize_app(argv) - self.log.info("START with options: %s", self.command_options) - self.log.debug("options: %s", self.options) + self.log.info("START with options: %s", + strutils.mask_password(self.command_options)) + self.log.debug("options: %s", + strutils.mask_password(self.options)) # Set the default plugin to token_endpoint if url and token are given if (self.options.url and self.options.token): @@ -246,7 +249,8 @@ def initialize_app(self, argv): self.log_configurator.configure(self.cloud) self.dump_stack_trace = self.log_configurator.dump_trace self.log.debug("defaults: %s", cc.defaults) - self.log.debug("cloud cfg: %s", self.cloud.config) + self.log.debug("cloud cfg: %s", + strutils.mask_password(self.cloud.config)) # Set up client TLS # NOTE(dtroyer): --insecure is the non-default condition that From 10ecd678021a7590b420907f73a7ef6d84c616ff Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Tue, 13 Oct 2015 01:02:45 +0000 Subject: [PATCH 0307/3095] Updated from global requirements Change-Id: I60a7bd4cd9527cf3b54ab5d2968dff5134711bff --- requirements.txt | 2 +- test-requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 2556c135f7..3c13cca868 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,5 +16,5 @@ python-keystoneclient>=1.6.0 python-novaclient>=2.29.0 python-cinderclient>=1.3.1 python-neutronclient>=2.6.0 -requests>=2.5.2 +requests!=2.8.0,>=2.5.2 stevedore>=1.5.0 # Apache-2.0 diff --git a/test-requirements.txt b/test-requirements.txt index 111d70bc10..a75c37b793 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -14,5 +14,5 @@ sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 os-testr>=0.4.1 testrepository>=0.0.18 testtools>=1.4.0 -WebOb>=1.2.3 +WebOb<1.5.0,>=1.2.3 tempest-lib>=0.9.0 From 2ca76810a26fbd8ffd4389c965ca2bab7053e28f Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Tue, 13 Oct 2015 11:04:52 +0000 Subject: [PATCH 0308/3095] Updated from global requirements Change-Id: I400cce4dfd840bba2d4f368a5d7f057f28b435ab --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index a75c37b793..9298fb3b48 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -15,4 +15,4 @@ os-testr>=0.4.1 testrepository>=0.0.18 testtools>=1.4.0 WebOb<1.5.0,>=1.2.3 -tempest-lib>=0.9.0 +tempest-lib>=0.10.0 From b60a9492a31bb1130338176e4b90a7572c10edc3 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Tue, 13 Oct 2015 14:38:02 -0500 Subject: [PATCH 0309/3095] Remove cliff-tablib from requirements.txt No longer needed Change-Id: I07c72fd34e9a7429d6fb910e254f91e888070009 --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3c13cca868..e6472eb969 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,6 @@ six>=1.9.0 Babel>=1.3 cliff>=1.14.0 # Apache-2.0 -cliff-tablib>=1.0 os-client-config!=1.6.2,>=1.4.0 oslo.config>=2.3.0 # Apache-2.0 oslo.i18n>=1.5.0 # Apache-2.0 From cbc1897b7d87946e5f9a84727310b59af4c7d624 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Sat, 10 Oct 2015 02:36:15 -0400 Subject: [PATCH 0310/3095] Move session and fixtures to keystoneauth1 in an effort to start consuming keystoneauth1, we can move our sesssion and fixture code over to use keystoneauth1 instead of keystoneclient. Change-Id: Ibcbd588ce2e3d864f87ff1eb6e1c3c071a1e06f6 --- openstackclient/tests/api/fakes.py | 2 +- openstackclient/tests/api/test_image_v1.py | 2 +- openstackclient/tests/api/test_image_v2.py | 2 +- openstackclient/tests/api/test_network_v2.py | 2 +- openstackclient/tests/api/test_object_store_v1.py | 2 +- openstackclient/tests/fakes.py | 2 +- openstackclient/tests/object/v1/fakes.py | 2 +- requirements.txt | 1 + 8 files changed, 8 insertions(+), 7 deletions(-) diff --git a/openstackclient/tests/api/fakes.py b/openstackclient/tests/api/fakes.py index 85617ab7d7..e285a61c3c 100644 --- a/openstackclient/tests/api/fakes.py +++ b/openstackclient/tests/api/fakes.py @@ -15,7 +15,7 @@ from requests_mock.contrib import fixture -from keystoneclient import session +from keystoneauth1 import session from openstackclient.tests import utils diff --git a/openstackclient/tests/api/test_image_v1.py b/openstackclient/tests/api/test_image_v1.py index 34fcfca443..f347975637 100644 --- a/openstackclient/tests/api/test_image_v1.py +++ b/openstackclient/tests/api/test_image_v1.py @@ -15,7 +15,7 @@ from requests_mock.contrib import fixture -from keystoneclient import session +from keystoneauth1 import session from openstackclient.api import image_v1 from openstackclient.tests import utils diff --git a/openstackclient/tests/api/test_image_v2.py b/openstackclient/tests/api/test_image_v2.py index ddb160eea5..77063997e2 100644 --- a/openstackclient/tests/api/test_image_v2.py +++ b/openstackclient/tests/api/test_image_v2.py @@ -15,7 +15,7 @@ from requests_mock.contrib import fixture -from keystoneclient import session +from keystoneauth1 import session from openstackclient.api import image_v2 from openstackclient.tests import utils diff --git a/openstackclient/tests/api/test_network_v2.py b/openstackclient/tests/api/test_network_v2.py index 13c5d6ea17..80f1d9dec0 100644 --- a/openstackclient/tests/api/test_network_v2.py +++ b/openstackclient/tests/api/test_network_v2.py @@ -15,7 +15,7 @@ from requests_mock.contrib import fixture -from keystoneclient import session +from keystoneauth1 import session from openstackclient.api import network_v2 as network from openstackclient.tests import utils diff --git a/openstackclient/tests/api/test_object_store_v1.py b/openstackclient/tests/api/test_object_store_v1.py index 323bb8e0b5..0176057309 100644 --- a/openstackclient/tests/api/test_object_store_v1.py +++ b/openstackclient/tests/api/test_object_store_v1.py @@ -17,7 +17,7 @@ from requests_mock.contrib import fixture -from keystoneclient import session +from keystoneauth1 import session from openstackclient.api import object_store_v1 as object_store from openstackclient.tests import utils diff --git a/openstackclient/tests/fakes.py b/openstackclient/tests/fakes.py index 979f9481dc..357c470f66 100644 --- a/openstackclient/tests/fakes.py +++ b/openstackclient/tests/fakes.py @@ -17,7 +17,7 @@ import six import sys -from keystoneclient import fixture +from keystoneauth1 import fixture import requests diff --git a/openstackclient/tests/object/v1/fakes.py b/openstackclient/tests/object/v1/fakes.py index 6aef05b1e8..986ab2f30b 100644 --- a/openstackclient/tests/object/v1/fakes.py +++ b/openstackclient/tests/object/v1/fakes.py @@ -13,7 +13,7 @@ # under the License. # -from keystoneclient import session +from keystoneauth1 import session from openstackclient.api import object_store_v1 as object_store from openstackclient.tests import utils diff --git a/requirements.txt b/requirements.txt index e6472eb969..3cea907740 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,6 +6,7 @@ six>=1.9.0 Babel>=1.3 cliff>=1.14.0 # Apache-2.0 +keystoneauth1>=1.0.0 os-client-config!=1.6.2,>=1.4.0 oslo.config>=2.3.0 # Apache-2.0 oslo.i18n>=1.5.0 # Apache-2.0 From a29df98ef98acae6b7009deb3843db403bcf8074 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 14 Oct 2015 09:33:25 +0000 Subject: [PATCH 0311/3095] Updated from global requirements Change-Id: Ie32abbbe6bb5ace5f1178fb266b9f1c60f625d72 --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 9298fb3b48..27f72bbed3 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -14,5 +14,5 @@ sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 os-testr>=0.4.1 testrepository>=0.0.18 testtools>=1.4.0 -WebOb<1.5.0,>=1.2.3 +WebOb>=1.2.3 tempest-lib>=0.10.0 From 9f51ccdf8c2b2e1afceeb9e6570ed629191a71c8 Mon Sep 17 00:00:00 2001 From: kafka Date: Wed, 12 Aug 2015 12:22:41 +0800 Subject: [PATCH 0312/3095] Add filtering by project/user for 'openstack volume list' added project, user and domain options for filtering results, also cleaned up the order to match the docs. Co-Authored-By: Steve Martinelli Closed-bug: #1483976 Change-Id: I9d955094d31d4a28e215d24f7521a11c62bee8db --- doc/source/command-objects/volume.rst | 30 ++++++++++++++++----- openstackclient/volume/v2/volume.py | 39 +++++++++++++++++++++++---- 2 files changed, 58 insertions(+), 11 deletions(-) diff --git a/doc/source/command-objects/volume.rst b/doc/source/command-objects/volume.rst index 556d1645f3..c94cbd23ad 100644 --- a/doc/source/command-objects/volume.rst +++ b/doc/source/command-objects/volume.rst @@ -102,22 +102,40 @@ List volumes .. code:: bash os volume list - [--status ] - [--name ] [--all-projects] + [--project [--project-domain ]] + [--user [--user-domain ]] + [--name ] + [--status ] [--long] -.. option:: --status +.. option:: --all-projects - Filter results by status +.. option:: --project + + Filter results by project (name or ID) (admin only) + +.. option:: --project-domain + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + +.. option:: --user + + Filter results by user (name or ID) (admin only) + +.. option:: --user-domain + + Domain the user belongs to (name or ID). + This can be used in case collisions between user names exist. .. option:: --name Filter results by name -.. option:: --all-projects +.. option:: --status - Include all projects (admin only) + Filter results by status .. option:: --long diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index 758f312b4b..5df656354c 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -25,6 +25,7 @@ from openstackclient.common import parseractions from openstackclient.common import utils +from openstackclient.identity import common as identity_common class CreateVolume(show.ShowOne): @@ -206,11 +207,17 @@ def get_parser(self, prog_name): help='Include all projects (admin only)', ) parser.add_argument( - '--long', - action='store_true', - default=False, - help='List additional fields in output', + '--project', + metavar='', + help='Filter results by project (name or ID) (admin only)' ) + identity_common.add_project_domain_option_to_parser(parser) + parser.add_argument( + '--user', + metavar='', + help='Filter results by user (name or ID) (admin only)' + ) + identity_common.add_user_domain_option_to_parser(parser) parser.add_argument( '--name', metavar='', @@ -221,6 +228,12 @@ def get_parser(self, prog_name): metavar='', help='Filter results by status', ) + parser.add_argument( + '--long', + action='store_true', + default=False, + help='List additional fields in output', + ) return parser @utils.log_method(log) @@ -228,6 +241,7 @@ def take_action(self, parsed_args): volume_client = self.app.client_manager.volume compute_client = self.app.client_manager.compute + identity_client = self.app.client_manager.identity def _format_attach(attachments): """Return a formatted string of a volume's attached instances @@ -282,8 +296,23 @@ def _format_attach(attachments): # Just forget it if there's any trouble pass + project_id = None + if parsed_args.project: + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain) + + user_id = None + if parsed_args.user: + user_id = identity_common.find_project(identity_client, + parsed_args.user, + parsed_args.user_domain) + search_opts = { - 'all_projects': parsed_args.all_projects, + 'all_tenants': parsed_args.all_projects, + 'project_id': project_id, + 'user_id': user_id, 'display_name': parsed_args.name, 'status': parsed_args.status, } From d1bc15f498da1a4d5f1c865d5eb6a151f472be9c Mon Sep 17 00:00:00 2001 From: Alex Schultz Date: Tue, 6 Oct 2015 22:12:44 -0500 Subject: [PATCH 0313/3095] Add compute service delete This change adds 'compute service delete ' to the openstack client. This is the equivalent of 'nova service-delete ' Change-Id: I69ef1cac72cbe125c2114f8e958e22350a70f367 Closes-Bug: #1503510 --- doc/source/command-objects/compute-agent.rst | 100 ++++++++++++++++++ .../command-objects/compute-service.rst | 73 +++++++++++++ openstackclient/compute/v2/service.py | 22 ++++ openstackclient/tests/compute/v2/fakes.py | 4 + .../tests/compute/v2/test_service.py | 54 ++++++++++ setup.cfg | 1 + 6 files changed, 254 insertions(+) create mode 100644 doc/source/command-objects/compute-agent.rst create mode 100644 doc/source/command-objects/compute-service.rst create mode 100644 openstackclient/tests/compute/v2/test_service.py diff --git a/doc/source/command-objects/compute-agent.rst b/doc/source/command-objects/compute-agent.rst new file mode 100644 index 0000000000..395ee4f372 --- /dev/null +++ b/doc/source/command-objects/compute-agent.rst @@ -0,0 +1,100 @@ +============= +compute agent +============= + +Compute v2 + +compute agent create +-------------------- + +Create compute agent + +.. program:: compute agent create +.. code:: bash + + os compute agent create + + + +.. _compute_agent-create: +.. describe:: + + Type of OS + +.. describe:: + + Type of architecture + +.. describe:: + + Version + +.. describe:: + + URL + +.. describe:: + + MD5 hash + +.. describe:: + + Type of hypervisor + +compute agent delete +-------------------- + +Delete compute agent command + +.. program:: compute agent delete +.. code:: bash + + os compute agent delete + +.. _compute_agent-delete: +.. describe:: + + ID of agent to delete + +compute agent list +------------------ + +List compute agent command + +.. program:: compute agent list +.. code:: bash + + os compute agent list [--hypervisor ] + +.. _compute_agent-list: +.. describe:: --hypervisor + + Optional type of hypervisor + +compute agent set +----------------- + +Set compute agent command + +.. program:: agent set +.. code:: bash + + os compute agent set + + +.. _compute_agent-set: +.. describe:: + + ID of the agent + +.. describe:: + + Version of the agent + +.. describe:: + + URL + +.. describe:: + + MD5 hash diff --git a/doc/source/command-objects/compute-service.rst b/doc/source/command-objects/compute-service.rst new file mode 100644 index 0000000000..95a77bdf29 --- /dev/null +++ b/doc/source/command-objects/compute-service.rst @@ -0,0 +1,73 @@ +=============== +compute service +=============== + +Compute v2 + +compute service delete +---------------------- + +Delete service command + +.. program:: compute service delete +.. code:: bash + + os compute service delete + + +.. _compute-service-delete: +.. describe:: + + Compute service to delete (ID only) + +compute service list +-------------------- + +List service command + +.. program:: compute service list +.. code:: bash + + os compute service list + [--host ] + [--service ] + +.. _compute-service-list: +.. describe:: --host + + Name of host + +.. describe:: --service + + Name of service + + +compute service set +------------------- + +Set service command + +.. program:: compute service set +.. code:: bash + + os compute service list + [--enable | --disable] + + +.. _compute-service-set: +.. describe:: --enable + + Enable service + +.. describe:: --disable + + Disable service + +.. describe:: + + Name of host + +.. describe:: + + Name of service + diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index 3a031bff25..c2d51c2aa3 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.py @@ -17,11 +17,33 @@ import logging +from cliff import command from cliff import lister from openstackclient.common import utils +class DeleteService(command.Command): + """Delete service command""" + + log = logging.getLogger(__name__ + ".DeleteService") + + def get_parser(self, prog_name): + parser = super(DeleteService, self).get_parser(prog_name) + parser.add_argument( + "service", + metavar="", + help="Compute service to delete (ID only)") + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + compute_client = self.app.client_manager.compute + + compute_client.services.delete(parsed_args.service) + return + + class ListService(lister.Lister): """List service command""" diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index e798bd4036..08eb5afa8e 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -25,6 +25,8 @@ server_id = 'serv1' server_name = 'waiter' +service_id = '1' + SERVER = { 'id': server_id, 'name': server_name, @@ -85,6 +87,8 @@ def __init__(self, **kwargs): self.images.resource_class = fakes.FakeResource(None, {}) self.servers = mock.Mock() self.servers.resource_class = fakes.FakeResource(None, {}) + self.services = mock.Mock() + self.services.resource_class = fakes.FakeResource(None, {}) self.extensions = mock.Mock() self.extensions.resource_class = fakes.FakeResource(None, {}) self.flavors = mock.Mock() diff --git a/openstackclient/tests/compute/v2/test_service.py b/openstackclient/tests/compute/v2/test_service.py new file mode 100644 index 0000000000..c6db30ac27 --- /dev/null +++ b/openstackclient/tests/compute/v2/test_service.py @@ -0,0 +1,54 @@ +# Copyright 2015 Mirantis, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +from openstackclient.compute.v2 import service +from openstackclient.tests.compute.v2 import fakes as compute_fakes + + +class TestService(compute_fakes.TestComputev2): + + def setUp(self): + super(TestService, self).setUp() + + # Get a shortcut to the ServiceManager Mock + self.service_mock = self.app.client_manager.compute.services + self.service_mock.reset_mock() + + +class TestServiceDelete(TestService): + + def setUp(self): + super(TestServiceDelete, self).setUp() + + self.service_mock.delete.return_value = None + + # Get the command object to test + self.cmd = service.DeleteService(self.app, None) + + def test_service_delete_no_options(self): + arglist = [ + compute_fakes.service_id, + ] + verifylist = [ + ('service', compute_fakes.service_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + self.cmd.take_action(parsed_args) + + self.service_mock.delete.assert_called_with( + compute_fakes.service_id, + ) diff --git a/setup.cfg b/setup.cfg index 7265eb883e..75ec0e4c47 100644 --- a/setup.cfg +++ b/setup.cfg @@ -66,6 +66,7 @@ openstack.compute.v2 = aggregate_set = openstackclient.compute.v2.aggregate:SetAggregate aggregate_show = openstackclient.compute.v2.aggregate:ShowAggregate + compute_service_delete = openstackclient.compute.v2.service:DeleteService compute_service_list = openstackclient.compute.v2.service:ListService compute_service_set = openstackclient.compute.v2.service:SetService From 195a0edeb77607cd95899834e5813ef34ada7c0c Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 16 Oct 2015 16:02:07 -0500 Subject: [PATCH 0314/3095] Follow-on for volume list - add tests, clean help * Add volume list tests for v1 (a copy of the v2 tests) * Converts volume v2 tests to use Identity v3 so domains can be tested * Add volume list (v2) tests for new options * Re-orders volume list options (both v1 and v2) to match * MArks the new volume list (v2) options as v2-only in doc Change-Id: I2181b2c48cfde2147d7d0ef135322df8a81e7ce8 --- doc/source/command-objects/volume.rst | 16 +- .../tests/volume/v1/test_volume.py | 182 ++++++++++++++++++ openstackclient/tests/volume/v2/fakes.py | 4 +- .../tests/volume/v2/test_volume.py | 164 +++++++++++++++- openstackclient/volume/v1/volume.py | 10 +- openstackclient/volume/v2/volume.py | 15 +- 6 files changed, 365 insertions(+), 26 deletions(-) diff --git a/doc/source/command-objects/volume.rst b/doc/source/command-objects/volume.rst index c94cbd23ad..cb52c560ec 100644 --- a/doc/source/command-objects/volume.rst +++ b/doc/source/command-objects/volume.rst @@ -109,34 +109,44 @@ List volumes [--status ] [--long] -.. option:: --all-projects - .. option:: --project Filter results by project (name or ID) (admin only) + *Volume version 2 only* + .. option:: --project-domain Domain the project belongs to (name or ID). This can be used in case collisions between project names exist. + *Volume version 2 only* + .. option:: --user Filter results by user (name or ID) (admin only) + *Volume version 2 only* + .. option:: --user-domain Domain the user belongs to (name or ID). This can be used in case collisions between user names exist. + *Volume version 2 only* + .. option:: --name - Filter results by name + Filter results by volume name .. option:: --status Filter results by status +.. option:: --all-projects + + Include all projects (admin only) + .. option:: --long List additional fields in output diff --git a/openstackclient/tests/volume/v1/test_volume.py b/openstackclient/tests/volume/v1/test_volume.py index f73260e91d..70ff50de6d 100644 --- a/openstackclient/tests/volume/v1/test_volume.py +++ b/openstackclient/tests/volume/v1/test_volume.py @@ -527,6 +527,188 @@ def test_volume_create_image_name(self): self.assertEqual(datalist, data) +class TestVolumeList(TestVolume): + + def setUp(self): + super(TestVolumeList, self).setUp() + + self.volumes_mock.list.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.VOLUME), + loaded=True, + ), + ] + + # Get the command object to test + self.cmd = volume.ListVolume(self.app, None) + + def test_volume_list_no_options(self): + arglist = [] + verifylist = [ + ('long', False), + ('all_projects', False), + ('name', None), + ('status', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = ( + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Attached to', + ) + self.assertEqual(collist, columns) + + datalist = (( + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_status, + volume_fakes.volume_size, + '', + ), ) + self.assertEqual(datalist, tuple(data)) + + def test_volume_list_name(self): + arglist = [ + '--name', volume_fakes.volume_name, + ] + verifylist = [ + ('long', False), + ('all_projects', False), + ('name', volume_fakes.volume_name), + ('status', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = ( + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Attached to', + ) + self.assertEqual(collist, tuple(columns)) + + datalist = (( + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_status, + volume_fakes.volume_size, + '', + ), ) + self.assertEqual(datalist, tuple(data)) + + def test_volume_list_status(self): + arglist = [ + '--status', volume_fakes.volume_status, + ] + verifylist = [ + ('long', False), + ('all_projects', False), + ('name', None), + ('status', volume_fakes.volume_status), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = ( + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Attached to', + ) + self.assertEqual(collist, tuple(columns)) + + datalist = (( + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_status, + volume_fakes.volume_size, + '', + ), ) + self.assertEqual(datalist, tuple(data)) + + def test_volume_list_all_projects(self): + arglist = [ + '--all-projects', + ] + verifylist = [ + ('long', False), + ('all_projects', True), + ('name', None), + ('status', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = ( + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Attached to', + ) + self.assertEqual(collist, columns) + + datalist = (( + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_status, + volume_fakes.volume_size, + '', + ), ) + self.assertEqual(datalist, tuple(data)) + + def test_volume_list_long(self): + arglist = [ + '--long', + ] + verifylist = [ + ('long', True), + ('all_projects', False), + ('name', None), + ('status', None), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = ( + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Type', + 'Bootable', + 'Attached to', + 'Properties', + ) + self.assertEqual(collist, columns) + + datalist = (( + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_status, + volume_fakes.volume_size, + volume_fakes.volume_type, + '', + '', + "Alpha='a', Beta='b', Gamma='g'", + ), ) + self.assertEqual(datalist, tuple(data)) + + class TestVolumeSet(TestVolume): def setUp(self): diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py index 7b7758a3a8..b9b2ae87e9 100644 --- a/openstackclient/tests/volume/v2/fakes.py +++ b/openstackclient/tests/volume/v2/fakes.py @@ -16,7 +16,7 @@ import mock from openstackclient.tests import fakes -from openstackclient.tests.identity.v2_0 import fakes as identity_fakes +from openstackclient.tests.identity.v3 import fakes as identity_fakes from openstackclient.tests.image.v2 import fakes as image_fakes from openstackclient.tests import utils @@ -212,7 +212,7 @@ def setUp(self): endpoint=fakes.AUTH_URL, token=fakes.AUTH_TOKEN ) - self.app.client_manager.identity = identity_fakes.FakeIdentityv2Client( + self.app.client_manager.identity = identity_fakes.FakeIdentityv3Client( endpoint=fakes.AUTH_URL, token=fakes.AUTH_TOKEN ) diff --git a/openstackclient/tests/volume/v2/test_volume.py b/openstackclient/tests/volume/v2/test_volume.py index b15fd02fc1..70324b6e35 100644 --- a/openstackclient/tests/volume/v2/test_volume.py +++ b/openstackclient/tests/volume/v2/test_volume.py @@ -15,7 +15,7 @@ import copy from openstackclient.tests import fakes -from openstackclient.tests.identity.v2_0 import fakes as identity_fakes +from openstackclient.tests.identity.v3 import fakes as identity_fakes from openstackclient.tests.volume.v2 import fakes as volume_fakes from openstackclient.volume.v2 import volume @@ -27,7 +27,7 @@ def setUp(self): self.volumes_mock = self.app.client_manager.volume.volumes self.volumes_mock.reset_mock() - self.projects_mock = self.app.client_manager.identity.tenants + self.projects_mock = self.app.client_manager.identity.projects self.projects_mock.reset_mock() self.users_mock = self.app.client_manager.identity.users @@ -560,32 +560,145 @@ def test_volume_list_no_options(self): ), ) self.assertEqual(datalist, tuple(data)) - def test_volume_list_all_projects_option(self): + def test_volume_list_project(self): arglist = [ - '--all-projects', + '--project', identity_fakes.project_name, ] verifylist = [ + ('project', identity_fakes.project_name), ('long', False), - ('all_projects', True), - ('name', None), + ('all_projects', False), ('status', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - collist = [ + collist = ( 'ID', 'Display Name', 'Status', 'Size', 'Attached to', + ) + self.assertEqual(collist, tuple(columns)) + + server = volume_fakes.volume_attachment_server['server_id'] + device = volume_fakes.volume_attachment_server['device'] + msg = 'Attached to %s on %s ' % (server, device) + + datalist = (( + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_status, + volume_fakes.volume_size, + msg, + ), ) + self.assertEqual(datalist, tuple(data)) + + def test_volume_list_project_domain(self): + arglist = [ + '--project', identity_fakes.project_name, + '--project-domain', identity_fakes.domain_name, ] - self.assertEqual(collist, columns) + verifylist = [ + ('project', identity_fakes.project_name), + ('project_domain', identity_fakes.domain_name), + ('long', False), + ('all_projects', False), + ('status', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = ( + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Attached to', + ) + self.assertEqual(collist, tuple(columns)) + + server = volume_fakes.volume_attachment_server['server_id'] + device = volume_fakes.volume_attachment_server['device'] + msg = 'Attached to %s on %s ' % (server, device) + + datalist = (( + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_status, + volume_fakes.volume_size, + msg, + ), ) + self.assertEqual(datalist, tuple(data)) + + def test_volume_list_user(self): + arglist = [ + '--user', identity_fakes.user_name, + ] + verifylist = [ + ('user', identity_fakes.user_name), + ('long', False), + ('all_projects', False), + ('status', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = ( + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Attached to', + ) + self.assertEqual(collist, tuple(columns)) server = volume_fakes.volume_attachment_server['server_id'] device = volume_fakes.volume_attachment_server['device'] msg = 'Attached to %s on %s ' % (server, device) + + datalist = (( + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_status, + volume_fakes.volume_size, + msg, + ), ) + self.assertEqual(datalist, tuple(data)) + + def test_volume_list_user_domain(self): + arglist = [ + '--user', identity_fakes.user_name, + '--user-domain', identity_fakes.domain_name, + ] + verifylist = [ + ('user', identity_fakes.user_name), + ('user_domain', identity_fakes.domain_name), + ('long', False), + ('all_projects', False), + ('status', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = ( + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Attached to', + ) + self.assertEqual(collist, tuple(columns)) + + server = volume_fakes.volume_attachment_server['server_id'] + device = volume_fakes.volume_attachment_server['device'] + msg = 'Attached to %s on %s ' % (server, device) + datalist = (( volume_fakes.volume_id, volume_fakes.volume_name, @@ -666,6 +779,41 @@ def test_volume_list_status(self): ), ) self.assertEqual(datalist, tuple(data)) + def test_volume_list_all_projects(self): + arglist = [ + '--all-projects', + ] + verifylist = [ + ('long', False), + ('all_projects', True), + ('name', None), + ('status', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = [ + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Attached to', + ] + self.assertEqual(collist, columns) + + server = volume_fakes.volume_attachment_server['server_id'] + device = volume_fakes.volume_attachment_server['device'] + msg = 'Attached to %s on %s ' % (server, device) + datalist = (( + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_status, + volume_fakes.volume_size, + msg, + ), ) + self.assertEqual(datalist, tuple(data)) + def test_volume_list_long(self): arglist = [ '--long', diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index 52b0eb2eb9..92afe8b0c7 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -206,16 +206,16 @@ class ListVolume(lister.Lister): def get_parser(self, prog_name): parser = super(ListVolume, self).get_parser(prog_name) + parser.add_argument( + '--name', + metavar='', + help='Filter results by volume name', + ) parser.add_argument( '--status', metavar='', help='Filter results by status', ) - parser.add_argument( - '--name', - metavar='', - help='Filter results by name', - ) parser.add_argument( '--all-projects', action='store_true', diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index 5df656354c..f59567cc1c 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -16,7 +16,6 @@ import copy import logging -import os from cliff import command from cliff import lister @@ -200,12 +199,6 @@ class ListVolume(lister.Lister): def get_parser(self, prog_name): parser = super(ListVolume, self).get_parser(prog_name) - parser.add_argument( - '--all-projects', - action='store_true', - default=bool(int(os.environ.get("ALL_PROJECTS", 0))), - help='Include all projects (admin only)', - ) parser.add_argument( '--project', metavar='', @@ -221,13 +214,19 @@ def get_parser(self, prog_name): parser.add_argument( '--name', metavar='', - help='Filter results by name', + help='Filter results by volume name', ) parser.add_argument( '--status', metavar='', help='Filter results by status', ) + parser.add_argument( + '--all-projects', + action='store_true', + default=False, + help='Include all projects (admin only)', + ) parser.add_argument( '--long', action='store_true', From d3e2e35f1dd2f1a46da58e09b36025392fc62653 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sat, 17 Oct 2015 02:59:01 +0000 Subject: [PATCH 0315/3095] Updated from global requirements Change-Id: Ic779c64e11691b48638ebd973723da2af28d5b96 --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index e6472eb969..ba7a9e3e8f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,9 +9,9 @@ cliff>=1.14.0 # Apache-2.0 os-client-config!=1.6.2,>=1.4.0 oslo.config>=2.3.0 # Apache-2.0 oslo.i18n>=1.5.0 # Apache-2.0 -oslo.utils>=2.0.0 # Apache-2.0 +oslo.utils>=2.4.0 # Apache-2.0 python-glanceclient>=0.18.0 -python-keystoneclient>=1.6.0 +python-keystoneclient!=1.8.0,>=1.6.0 python-novaclient>=2.29.0 python-cinderclient>=1.3.1 python-neutronclient>=2.6.0 From db2dfa311fcad0649383f725ffa62d6509315aaf Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 15 Oct 2015 11:58:53 -0500 Subject: [PATCH 0316/3095] Add release notes for 1.8.0 Depends-On: I2181b2c48cfde2147d7d0ef135322df8a81e7ce8 Change-Id: I9bdee1b932bcefd3c802461f6f7ac6d9266110ce --- doc/source/releases.rst | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/doc/source/releases.rst b/doc/source/releases.rst index 526b1635c7..2e9f6fff85 100644 --- a/doc/source/releases.rst +++ b/doc/source/releases.rst @@ -2,6 +2,39 @@ Release Notes ============= +1.8.0 (18 Oct 2015) +=================== + +* `image create --volume` command (v1) will attempt to create two images + Bug `1497221 `_ + +* Add filtering by project/uesr for command `volume list` + Bug `1483976 `_ + +* Password used by plugin shows up in debug mode + Bug `1501598 `_ + +* Add support for `object store account` + Bug `1501943 `_ + +* Add support for setting properties on objects and containers + Bug `1501945 `_ + +* `role list` ignores `--inherited` option + Bug `1502822 `_ + +* `compute service list` does not return service ID number + Bug `1503430 `_ + +* Containers and objects with non-ascii characters fails + Bug `1503508 `_ + +1.7.1 (30 Sep 2015) +=================== + +* Image v2 lookup issues + Bug `1501362 `_ + 1.7.0 (22 Sep 2015) =================== From 9471115a9a516049684755d7582338c83b3ca1f5 Mon Sep 17 00:00:00 2001 From: liyingjun Date: Wed, 14 Oct 2015 11:05:02 +0800 Subject: [PATCH 0317/3095] Support pagination params for flavor list Missing 'marker' and 'limit' params for `openstack flavor list` shell command. It would be nice to have this when there are many flavors. Closes-bug: #1505874 Change-Id: I088ac5d24f0d7595f5cbce14f063e296a449eb26 --- doc/source/command-objects/flavor.rst | 10 ++++++++++ openstackclient/compute/v2/flavor.py | 13 +++++++++++- .../tests/compute/v2/test_flavor.py | 20 ++++++++++++++----- 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/doc/source/command-objects/flavor.rst b/doc/source/command-objects/flavor.rst index 5254b12cae..2389f7dc94 100644 --- a/doc/source/command-objects/flavor.rst +++ b/doc/source/command-objects/flavor.rst @@ -91,6 +91,8 @@ List flavors os flavor list [--public | --private | --all] [--long] + [--marker ] + [--limit ] .. option:: --public @@ -108,6 +110,14 @@ List flavors List additional fields in output +.. option:: --marker + + The last flavor ID of the previous page + +.. option:: --limit + + Maximum number of flavors to display + flavor show ----------- diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 3458cf7969..7474580b60 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -181,6 +181,15 @@ def get_parser(self, prog_name): action='store_true', default=False, help='List additional fields in output') + parser.add_argument( + '--marker', + metavar="", + help='The last flavor ID of the previous page') + parser.add_argument( + '--limit', + type=int, + metavar="", + help='Maximum number of flavors to display') return parser def take_action(self, parsed_args): @@ -202,7 +211,9 @@ def take_action(self, parsed_args): # and flavors from their own projects only. is_public = None if parsed_args.all else parsed_args.public - data = compute_client.flavors.list(is_public=is_public) + data = compute_client.flavors.list(is_public=is_public, + marker=parsed_args.marker, + limit=parsed_args.limit) if parsed_args.long: columns = columns + ( diff --git a/openstackclient/tests/compute/v2/test_flavor.py b/openstackclient/tests/compute/v2/test_flavor.py index 19be812403..523104f091 100644 --- a/openstackclient/tests/compute/v2/test_flavor.py +++ b/openstackclient/tests/compute/v2/test_flavor.py @@ -76,7 +76,9 @@ def test_flavor_list_no_options(self): # Set expected values kwargs = { - 'is_public': True + 'is_public': True, + 'limit': None, + 'marker': None } self.flavors_mock.list.assert_called_with( @@ -119,7 +121,9 @@ def test_flavor_list_all_flavors(self): # Set expected values kwargs = { - 'is_public': None + 'is_public': None, + 'limit': None, + 'marker': None } self.flavors_mock.list.assert_called_with( @@ -162,7 +166,9 @@ def test_flavor_list_private_flavors(self): # Set expected values kwargs = { - 'is_public': False + 'is_public': False, + 'limit': None, + 'marker': None } self.flavors_mock.list.assert_called_with( @@ -205,7 +211,9 @@ def test_flavor_list_public_flavors(self): # Set expected values kwargs = { - 'is_public': True + 'is_public': True, + 'limit': None, + 'marker': None } self.flavors_mock.list.assert_called_with( @@ -248,7 +256,9 @@ def test_flavor_list_long(self): # Set expected values kwargs = { - 'is_public': True + 'is_public': True, + 'limit': None, + 'marker': None } self.flavors_mock.list.assert_called_with( From 539c39bfafa06e794b1855325a29f7ee4a9ac3c2 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Sun, 18 Oct 2015 15:32:45 -0400 Subject: [PATCH 0318/3095] remove url from v3 regions the parameter "url" was removed from Keystone, it was only added for one release as part of an experimental support for adding service providers. BackwardsIncompatibleImpact Closes-Bug: 1506841 Change-Id: I7a62fbf1d9bfa8e6dd8d619e98c32b9860348d2e --- doc/source/backwards-incompatible.rst | 12 +++ doc/source/command-objects/region.rst | 10 --- functional/tests/identity/v3/test_identity.py | 8 +- openstackclient/identity/v3/region.py | 22 +----- openstackclient/tests/identity/v3/fakes.py | 2 - .../tests/identity/v3/test_region.py | 79 ++----------------- 6 files changed, 23 insertions(+), 110 deletions(-) diff --git a/doc/source/backwards-incompatible.rst b/doc/source/backwards-incompatible.rst index f9f2ed4485..94873c149f 100644 --- a/doc/source/backwards-incompatible.rst +++ b/doc/source/backwards-incompatible.rst @@ -102,6 +102,18 @@ List of Backwards Incompatible Changes * Bug: NA * Commit: NA +8. `region` commands no longer support `url` + + The Keystone team removed support for thr `url` attribute from the client + and server side. Changes to the `create`, `set` and `list` commands for + regions have been affected. + + * In favor of: NA + * As of 1.9.0 + * Removed in: NA + * Bug: https://launchpad.net/bugs/1506841 + * Commit: https://review.openstack.org/#/c/236736/ + For Developers ============== diff --git a/doc/source/command-objects/region.rst b/doc/source/command-objects/region.rst index cb4a059eab..1892fc244f 100644 --- a/doc/source/command-objects/region.rst +++ b/doc/source/command-objects/region.rst @@ -15,7 +15,6 @@ Create new region os region create [--parent-region ] [--description ] - [--url ] .. option:: --parent-region @@ -26,10 +25,6 @@ Create new region New region description -.. option:: --url - - New region URL - .. _region_create-region-id: .. describe:: @@ -77,7 +72,6 @@ Set region properties os region set [--parent-region ] [--description ] - [--url ] .. option:: --parent-region @@ -88,10 +82,6 @@ Set region properties New region description -.. option:: --url - - New region URL - .. _region_set-region-id: .. describe:: diff --git a/functional/tests/identity/v3/test_identity.py b/functional/tests/identity/v3/test_identity.py index dd5f2f4ec3..3164e8fbb5 100644 --- a/functional/tests/identity/v3/test_identity.py +++ b/functional/tests/identity/v3/test_identity.py @@ -32,13 +32,12 @@ class IdentityTests(test.TestCase): 'enabled', 'name', 'parent_id', 'links'] ROLE_FIELDS = ['id', 'name', 'links'] SERVICE_FIELDS = ['id', 'enabled', 'name', 'type', 'description'] - REGION_FIELDS = ['description', 'enabled', 'parent_region', - 'region', 'url'] + REGION_FIELDS = ['description', 'enabled', 'parent_region', 'region'] ENDPOINT_FIELDS = ['id', 'region', 'region_id', 'service_id', 'service_name', 'service_type', 'enabled', 'interface', 'url'] - REGION_LIST_HEADERS = ['Region', 'Parent Region', 'Description', 'URL'] + REGION_LIST_HEADERS = ['Region', 'Parent Region', 'Description'] ENDPOINT_LIST_HEADERS = ['ID', 'Region', 'Service Name', 'Service Type', 'Enabled', 'Interface', 'URL'] @@ -194,7 +193,6 @@ def _create_dummy_project(self, add_clean_up=True): def _create_dummy_region(self, parent_region=None, add_clean_up=True): region_id = data_utils.rand_name('TestRegion') description = data_utils.rand_name('description') - url = data_utils.rand_url() parent_region_arg = '' if parent_region is not None: parent_region_arg = '--parent-region %s' % parent_region @@ -202,10 +200,8 @@ def _create_dummy_region(self, parent_region=None, add_clean_up=True): 'region create ' '%(parent_region_arg)s ' '--description %(description)s ' - '--url %(url)s ' '%(id)s' % {'parent_region_arg': parent_region_arg, 'description': description, - 'url': url, 'id': region_id}) items = self.parse_show(raw_output) self.assert_show_fields(items, self.REGION_FIELDS) diff --git a/openstackclient/identity/v3/region.py b/openstackclient/identity/v3/region.py index eb4c084ca4..1ff0b8c069 100644 --- a/openstackclient/identity/v3/region.py +++ b/openstackclient/identity/v3/region.py @@ -48,12 +48,6 @@ def get_parser(self, prog_name): metavar='', help=_('New region description'), ) - parser.add_argument( - '--url', - metavar='', - help=_('New region url'), - ) - return parser @utils.log_method(log) @@ -62,7 +56,6 @@ def take_action(self, parsed_args): region = identity_client.regions.create( id=parsed_args.region, - url=parsed_args.url, parent_region=parsed_args.parent_region, description=parsed_args.description, ) @@ -117,8 +110,8 @@ def take_action(self, parsed_args): if parsed_args.parent_region: kwargs['parent_region_id'] = parsed_args.parent_region - columns_headers = ('Region', 'Parent Region', 'Description', 'URL') - columns = ('ID', 'Parent Region Id', 'Description', 'URL') + columns_headers = ('Region', 'Parent Region', 'Description') + columns = ('ID', 'Parent Region Id', 'Description') data = identity_client.regions.list(**kwargs) return (columns_headers, @@ -150,25 +143,16 @@ def get_parser(self, prog_name): metavar='', help=_('New region description'), ) - parser.add_argument( - '--url', - metavar='', - help=_('New region url'), - ) return parser @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - if (not parsed_args.url - and not parsed_args.parent_region - and not parsed_args.description): + if not parsed_args.parent_region and not parsed_args.description: return kwargs = {} - if parsed_args.url: - kwargs['url'] = parsed_args.url if parsed_args.description: kwargs['description'] = parsed_args.description if parsed_args.parent_region: diff --git a/openstackclient/tests/identity/v3/fakes.py b/openstackclient/tests/identity/v3/fakes.py index 9c4de9cc0d..ff267478db 100644 --- a/openstackclient/tests/identity/v3/fakes.py +++ b/openstackclient/tests/identity/v3/fakes.py @@ -123,13 +123,11 @@ } region_id = 'region_one' -region_url = 'http://localhost:1111' region_parent_region_id = 'region_two' region_description = 'region one' REGION = { 'id': region_id, - 'url': region_url, 'description': region_description, 'parent_region_id': region_parent_region_id, 'links': base_url + 'regions/' + region_id, diff --git a/openstackclient/tests/identity/v3/test_region.py b/openstackclient/tests/identity/v3/test_region.py index 7f6ced9f2d..0ebbbecf44 100644 --- a/openstackclient/tests/identity/v3/test_region.py +++ b/openstackclient/tests/identity/v3/test_region.py @@ -61,19 +61,17 @@ def test_region_create_description(self): 'description': identity_fakes.region_description, 'id': identity_fakes.region_id, 'parent_region': None, - 'url': None, } self.regions_mock.create.assert_called_with( **kwargs ) - collist = ('description', 'parent_region', 'region', 'url') + collist = ('description', 'parent_region', 'region') self.assertEqual(collist, columns) datalist = ( identity_fakes.region_description, identity_fakes.region_parent_region_id, identity_fakes.region_id, - identity_fakes.region_url, ) self.assertEqual(datalist, data) @@ -94,19 +92,17 @@ def test_region_create_no_options(self): 'description': None, 'id': identity_fakes.region_id, 'parent_region': None, - 'url': None, } self.regions_mock.create.assert_called_with( **kwargs ) - collist = ('description', 'parent_region', 'region', 'url') + collist = ('description', 'parent_region', 'region') self.assertEqual(collist, columns) datalist = ( identity_fakes.region_description, identity_fakes.region_parent_region_id, identity_fakes.region_id, - identity_fakes.region_url, ) self.assertEqual(datalist, data) @@ -129,54 +125,17 @@ def test_region_create_parent_region_id(self): 'description': None, 'id': identity_fakes.region_id, 'parent_region': identity_fakes.region_parent_region_id, - 'url': None, } self.regions_mock.create.assert_called_with( **kwargs ) - collist = ('description', 'parent_region', 'region', 'url') + collist = ('description', 'parent_region', 'region') self.assertEqual(collist, columns) datalist = ( identity_fakes.region_description, identity_fakes.region_parent_region_id, identity_fakes.region_id, - identity_fakes.region_url, - ) - self.assertEqual(datalist, data) - - def test_region_create_url(self): - arglist = [ - identity_fakes.region_id, - '--url', identity_fakes.region_url, - ] - verifylist = [ - ('region', identity_fakes.region_id), - ('url', identity_fakes.region_url), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # DisplayCommandBase.take_action() returns two tuples - columns, data = self.cmd.take_action(parsed_args) - - # Set expected values - kwargs = { - 'description': None, - 'id': identity_fakes.region_id, - 'parent_region': None, - 'url': identity_fakes.region_url, - } - self.regions_mock.create.assert_called_with( - **kwargs - ) - - collist = ('description', 'parent_region', 'region', 'url') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.region_description, - identity_fakes.region_parent_region_id, - identity_fakes.region_id, - identity_fakes.region_url, ) self.assertEqual(datalist, data) @@ -233,13 +192,12 @@ def test_region_list_no_options(self): columns, data = self.cmd.take_action(parsed_args) self.regions_mock.list.assert_called_with() - collist = ('Region', 'Parent Region', 'Description', 'URL') + collist = ('Region', 'Parent Region', 'Description') self.assertEqual(collist, columns) datalist = (( identity_fakes.region_id, identity_fakes.region_parent_region_id, identity_fakes.region_description, - identity_fakes.region_url, ), ) self.assertEqual(datalist, tuple(data)) @@ -257,13 +215,12 @@ def test_region_list_parent_region_id(self): self.regions_mock.list.assert_called_with( parent_region_id=identity_fakes.region_parent_region_id) - collist = ('Region', 'Parent Region', 'Description', 'URL') + collist = ('Region', 'Parent Region', 'Description') self.assertEqual(collist, columns) datalist = (( identity_fakes.region_id, identity_fakes.region_parent_region_id, identity_fakes.region_description, - identity_fakes.region_url, ), ) self.assertEqual(datalist, tuple(data)) @@ -319,29 +276,6 @@ def test_region_set_description(self): **kwargs ) - def test_region_set_url(self): - arglist = [ - '--url', 'new url', - identity_fakes.region_id, - ] - verifylist = [ - ('url', 'new url'), - ('region', identity_fakes.region_id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) - - # Set expected values - kwargs = { - 'url': 'new url', - } - self.regions_mock.update.assert_called_with( - identity_fakes.region_id, - **kwargs - ) - def test_region_set_parent_region_id(self): arglist = [ '--parent-region', 'new_parent', @@ -395,12 +329,11 @@ def test_region_show(self): identity_fakes.region_id, ) - collist = ('description', 'parent_region', 'region', 'url') + collist = ('description', 'parent_region', 'region') self.assertEqual(collist, columns) datalist = ( identity_fakes.region_description, identity_fakes.region_parent_region_id, identity_fakes.region_id, - identity_fakes.region_url, ) self.assertEqual(datalist, data) From 46fdaaba6b20ce548f2a5adc51c5954c55fc402c Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Tue, 20 Oct 2015 15:44:38 -0500 Subject: [PATCH 0319/3095] Add Command Options guideline doc Add a developer guideline for command options to define the options used across multiple commands and make them behave consistently. Change-Id: I1dbbafe8061e10b271cd55cac056731508c52204 --- doc/source/command-options.rst | 135 +++++++++++++++++++++++++++++++++ doc/source/index.rst | 1 + 2 files changed, 136 insertions(+) create mode 100644 doc/source/command-options.rst diff --git a/doc/source/command-options.rst b/doc/source/command-options.rst new file mode 100644 index 0000000000..edafb15322 --- /dev/null +++ b/doc/source/command-options.rst @@ -0,0 +1,135 @@ +=============== +Command Options +=============== + +OpenStackClient commands all have a set of zero or more options unique to +the command, however there are of course ways in which these options are +common and consistent across all of the commands that include them. + +These are the set of guidelines for OSC developers that help keep the +interface and commands consistent. + +In some cases (like the boolean variables below) we use the same pattern +for defining and using options in all situations. The alternative of only +using it when necessary leads to errors when copy-n-paste is used for a +new command without understanding why or why not that instance is correct. + +Boolean Options +=============== + +Boolean options for any command that sets a resource state, such as 'enabled' +or 'public', shall always have both positive and negative options defined. +The names of those options shall either be a naturally occuring pair of +words (in English) or a positive option and a negative option with `no-` +prepended (such as in the traditional GNU option usage) like `--share` and +`--no-share`. + +In order to handle those APIs that behave differently when a field is set to +`None` and when the field is not present in a passed argument list or dict, +each of the boolean options shall set its own variable to `True` as part of +a mutiually exclusive group, rather than the more common configuration of setting a +single destination variable `True` or `False` directly. This allows us to +detect the situation when neither option is present (both variables will be +`False`) and act accordingly for those APIs where this matters. + +This also requires that each of the boolean values be tested in the +`take_action()` method to correctly set (or not) the underlying API field +values. + +.. option:: --enable + + Enable (default) + +.. option:: --disable + + Disable + +Implementation +~~~~~~~~~~~~~~ + +The parser declaration should look like this:: + +.. code-block: python + + enable_group = parser.add_mutually_exclusive_group() + enable_group.add_argument( + '--enable', + action='store_true', + help=_('Enable (default)'), + ) + enable_group.add_argument( + '--disable', + action='store_true', + help=_('Disable '), + ) + +An example handler in `take_action()`:: + + # This leaves 'enabled' undefined if neither option is present + if parsed_args.enable: + kwargs['enabled'] = True + if parsed_args.disable: + kwargs['enabled'] = False + +List Command Options +==================== + +Additional Fields +----------------- + +Most list commands only return a subset of the available fields by default. +Additional fields are available with the `--long` option. All list +commands should allow `--long` even if they return all fields by default. + +.. option:: --long + + List additional fields in output + +Implementation +~~~~~~~~~~~~~~ + +The parser declaration should look like this:: + +.. code-block: python + + parser.add_argument( + '--long', + action='store_true', + default=False, + help='List additional fields in output', + ) + +Pagination +---------- + +There are many ways to do pagination, some OpenStack APIs support it, some don't. +OpenStackClient attempts to define a single common way to specify pagination on +the command line. + +.. option:: --marker + + Anchor for paging + +.. option:: --limit + + Limit number of returned (*integer*) + +Implementation +~~~~~~~~~~~~~~ + +The parser declaration should look like this:: + +.. code-block: python + + parser.add_argument( + "--marker", + metavar="", + help="Anchor for paging", + ) + + parser.add_argument( + "--limit", + metavar="", + type=int, + help="Limit the number of returned", + ) diff --git a/doc/source/index.rst b/doc/source/index.rst index c90b8e5296..6b7d63f36e 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -37,6 +37,7 @@ Developer Documentation :maxdepth: 1 developing + command-options Project Goals ------------- From 12668b3dababed103c3ad74fee4b7e81d4be1de3 Mon Sep 17 00:00:00 2001 From: lin-hua-cheng Date: Thu, 22 Oct 2015 17:32:45 -0700 Subject: [PATCH 0320/3095] Fix issue when displaying image_member image_member doesn't have a _info attribute, glanceclient returns warlock object instead of a Resource object. Change-Id: If6e7c4bd404454bd6cbe8c111879c1afa1380211 Closes-Bug: #1509054 --- openstackclient/image/v2/image.py | 2 +- openstackclient/tests/fakes.py | 8 ++++++++ openstackclient/tests/image/v2/test_image.py | 4 +--- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 7d8b14124a..a846f4b328 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -105,7 +105,7 @@ def take_action(self, parsed_args): project_id, ) - return zip(*sorted(six.iteritems(image_member._info))) + return zip(*sorted(six.iteritems(image_member))) class CreateImage(show.ShowOne): diff --git a/openstackclient/tests/fakes.py b/openstackclient/tests/fakes.py index 357c470f66..0f5ef74aa0 100644 --- a/openstackclient/tests/fakes.py +++ b/openstackclient/tests/fakes.py @@ -150,3 +150,11 @@ def __init__(self, headers={}, status_code=200, data=None, encoding=None): self._content = json.dumps(data) if not isinstance(self._content, six.binary_type): self._content = self._content.encode() + + +class FakeModel(dict): + def __getattr__(self, key): + try: + return self[key] + except KeyError: + raise AttributeError(key) diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index 4ce854759b..72ba0567b5 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -238,10 +238,8 @@ def setUp(self): copy.deepcopy(image_fakes.IMAGE), loaded=True, ) - self.image_members_mock.create.return_value = fakes.FakeResource( - None, + self.image_members_mock.create.return_value = fakes.FakeModel( copy.deepcopy(image_fakes.MEMBER), - loaded=True, ) self.project_mock.get.return_value = fakes.FakeResource( None, From 0ee5527fa52b219b49082d61d95d84f4b948c02f Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 24 Oct 2015 11:44:40 +0800 Subject: [PATCH 0321/3095] Trivial clean up: do not use plural form in command arguments. It is a general rule that we don't use plural form for the name of command arguments. But class DeleteServer() is still using "servers". So use "server instead". Change-Id: I2d76de14ec34b88547b9f728b41e9bd93b2a22c5 --- openstackclient/compute/v2/server.py | 4 ++-- openstackclient/tests/compute/v2/test_server.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 661ce84783..ac71740570 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -607,7 +607,7 @@ class DeleteServer(command.Command): def get_parser(self, prog_name): parser = super(DeleteServer, self).get_parser(prog_name) parser.add_argument( - 'servers', + 'server', metavar='', nargs="+", help=_('Server(s) to delete (name or ID)'), @@ -622,7 +622,7 @@ def get_parser(self, prog_name): @utils.log_method(log) def take_action(self, parsed_args): compute_client = self.app.client_manager.compute - for server in parsed_args.servers: + for server in parsed_args.server: server_obj = utils.find_resource( compute_client.servers, server) compute_client.servers.delete(server_obj.id) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 1e99bcd0ba..4f5bdd5b6a 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -311,7 +311,7 @@ def test_server_delete_no_options(self): compute_fakes.server_id, ] verifylist = [ - ('servers', [compute_fakes.server_id]), + ('server', [compute_fakes.server_id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -328,7 +328,7 @@ def test_server_delete_wait_ok(self, mock_wait_for_delete): compute_fakes.server_id, '--wait' ] verifylist = [ - ('servers', [compute_fakes.server_id]), + ('server', [compute_fakes.server_id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -351,7 +351,7 @@ def test_server_delete_wait_fails(self, mock_wait_for_delete): compute_fakes.server_id, '--wait' ] verifylist = [ - ('servers', [compute_fakes.server_id]), + ('server', [compute_fakes.server_id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) From eac40beb6b5e0e1a74be2dd8fabc916aeea22b46 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Wed, 28 Oct 2015 19:19:47 +0800 Subject: [PATCH 0322/3095] Trivial clean up: Add doc for "osc server lock/unlock". There is no doc for "server lock/unlock" in doc/source/commands.rst. Change-Id: Ibabc260f6269a452c3de0d032839f63938bd348e --- doc/source/commands.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 8515bacff6..80ce824425 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -169,7 +169,7 @@ Those actions with an opposite action are noted in parens if applicable. * ``delete`` (``create``) - delete a specific occurrence of the specified object * ``issue`` (``revoke``) - issue a token * ``list`` - display summary information about multiple objects -* ``lock`` (``unlock``) +* ``lock`` (``unlock``) - lock a server so that non-admin user won't be able to execute actions * ``migrate`` - move a server to a different host; ``--live`` performs a live migration if possible * ``pause`` (``unpause``) - stop a server and leave it in memory @@ -184,7 +184,7 @@ Those actions with an opposite action are noted in parens if applicable. * ``set`` (``unset``) - set a property on the object, formerly called metadata * ``show`` - display detailed information about the specific object * ``suspend`` (``resume``) - stop a server and save to disk freeing memory -* ``unlock`` (``lock``) +* ``unlock`` (``lock``) - unlock a server * ``unpause`` (``pause``) - return a paused server to running state * ``unrescue`` (``rescue``) - return a server to normal boot mode * ``unset`` (``set``) - remove an attribute of the object From 53099ea2c287add65aa12edbfedb5394306c14f9 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 28 Oct 2015 22:10:14 +0000 Subject: [PATCH 0323/3095] Updated from global requirements Change-Id: Iee71a3906069245cee8ec9edd68b4f22a8e006bc --- requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index ca6a30c56a..a710d6fb8b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,12 +8,12 @@ Babel>=1.3 cliff>=1.14.0 # Apache-2.0 keystoneauth1>=1.0.0 os-client-config!=1.6.2,>=1.4.0 -oslo.config>=2.3.0 # Apache-2.0 +oslo.config>=2.6.0 # Apache-2.0 oslo.i18n>=1.5.0 # Apache-2.0 -oslo.utils>=2.4.0 # Apache-2.0 +oslo.utils!=2.6.0,>=2.4.0 # Apache-2.0 python-glanceclient>=0.18.0 python-keystoneclient!=1.8.0,>=1.6.0 -python-novaclient>=2.29.0 +python-novaclient!=2.33.0,>=2.29.0 python-cinderclient>=1.3.1 python-neutronclient>=2.6.0 requests!=2.8.0,>=2.5.2 From bfebac82825a7242ad4e2923e3b71d38b391f08e Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Mon, 10 Aug 2015 12:45:35 -0600 Subject: [PATCH 0324/3095] Allow debug to be set in configuration file The current default value for debug in cliff is False. Cloud config assumes that it was set that way on the command line and does not overlay it with the value from the configuration file. Cliff bug: https://bugs.launchpad.net/python-cliff/+bug/1483378 Change-Id: I66d82b489b2241dbcd1e1350e94259a54ce09de7 --- openstackclient/shell.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 72f663a093..f7704efcb0 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -232,10 +232,16 @@ def initialize_app(self, argv): }, ) + # TODO(thowe): Change cliff so the default value for debug + # can be set to None. + if not self.options.debug: + self.options.debug = None self.cloud = cc.get_one_cloud( cloud=self.options.cloud, argparse=self.options, ) + if self.options.debug is not None: + self.options.debug = False self.log_configurator.configure(self.cloud) self.dump_stack_trace = self.log_configurator.dump_trace From 05800c47227ce7c6918296c33fe0b9a774d14cda Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Fri, 30 Oct 2015 17:38:42 +0900 Subject: [PATCH 0325/3095] Rename context.py to logs.py At one point this file contained the context for logging, but the reason for its existence is now for logging. Implements: blueprint logging-migration Change-Id: I4ba42bbef97b09d31236ac8c01b6fb23827b8bee --- .../common/{context.py => logs.py} | 0 openstackclient/shell.py | 6 +- .../common/{test_context.py => test_logs.py} | 64 +++++++++---------- 3 files changed, 34 insertions(+), 36 deletions(-) rename openstackclient/common/{context.py => logs.py} (100%) rename openstackclient/tests/common/{test_context.py => test_logs.py} (76%) diff --git a/openstackclient/common/context.py b/openstackclient/common/logs.py similarity index 100% rename from openstackclient/common/context.py rename to openstackclient/common/logs.py diff --git a/openstackclient/shell.py b/openstackclient/shell.py index f7704efcb0..deb35b3c0f 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -29,8 +29,8 @@ import openstackclient from openstackclient.common import clientmanager from openstackclient.common import commandmanager -from openstackclient.common import context from openstackclient.common import exceptions as exc +from openstackclient.common import logs from openstackclient.common import timing from openstackclient.common import utils @@ -98,7 +98,7 @@ def __init__(self): def configure_logging(self): """Configure logging for the app.""" - self.log_configurator = context.LogConfigurator(self.options) + self.log_configurator = logs.LogConfigurator(self.options) self.dump_stack_trace = self.log_configurator.dump_trace def run(self, argv): @@ -240,8 +240,6 @@ def initialize_app(self, argv): cloud=self.options.cloud, argparse=self.options, ) - if self.options.debug is not None: - self.options.debug = False self.log_configurator.configure(self.cloud) self.dump_stack_trace = self.log_configurator.dump_trace diff --git a/openstackclient/tests/common/test_context.py b/openstackclient/tests/common/test_logs.py similarity index 76% rename from openstackclient/tests/common/test_context.py rename to openstackclient/tests/common/test_logs.py index 55e42851b3..fe054a3ba7 100644 --- a/openstackclient/tests/common/test_context.py +++ b/openstackclient/tests/common/test_logs.py @@ -14,7 +14,7 @@ import logging import mock -from openstackclient.common import context +from openstackclient.common import logs from openstackclient.tests import utils @@ -23,51 +23,51 @@ class TestContext(utils.TestCase): def test_log_level_from_options(self): opts = mock.Mock() opts.verbose_level = 0 - self.assertEqual(logging.ERROR, context.log_level_from_options(opts)) + self.assertEqual(logging.ERROR, logs.log_level_from_options(opts)) opts.verbose_level = 1 - self.assertEqual(logging.WARNING, context.log_level_from_options(opts)) + self.assertEqual(logging.WARNING, logs.log_level_from_options(opts)) opts.verbose_level = 2 - self.assertEqual(logging.INFO, context.log_level_from_options(opts)) + self.assertEqual(logging.INFO, logs.log_level_from_options(opts)) opts.verbose_level = 3 - self.assertEqual(logging.DEBUG, context.log_level_from_options(opts)) + self.assertEqual(logging.DEBUG, logs.log_level_from_options(opts)) def test_log_level_from_config(self): cfg = {'verbose_level': 0} - self.assertEqual(logging.ERROR, context.log_level_from_config(cfg)) + self.assertEqual(logging.ERROR, logs.log_level_from_config(cfg)) cfg = {'verbose_level': 1} - self.assertEqual(logging.WARNING, context.log_level_from_config(cfg)) + self.assertEqual(logging.WARNING, logs.log_level_from_config(cfg)) cfg = {'verbose_level': 2} - self.assertEqual(logging.INFO, context.log_level_from_config(cfg)) + self.assertEqual(logging.INFO, logs.log_level_from_config(cfg)) cfg = {'verbose_level': 3} - self.assertEqual(logging.DEBUG, context.log_level_from_config(cfg)) + self.assertEqual(logging.DEBUG, logs.log_level_from_config(cfg)) cfg = {'verbose_level': 1, 'log_level': 'critical'} - self.assertEqual(logging.CRITICAL, context.log_level_from_config(cfg)) + self.assertEqual(logging.CRITICAL, logs.log_level_from_config(cfg)) cfg = {'verbose_level': 1, 'log_level': 'error'} - self.assertEqual(logging.ERROR, context.log_level_from_config(cfg)) + self.assertEqual(logging.ERROR, logs.log_level_from_config(cfg)) cfg = {'verbose_level': 1, 'log_level': 'warning'} - self.assertEqual(logging.WARNING, context.log_level_from_config(cfg)) + self.assertEqual(logging.WARNING, logs.log_level_from_config(cfg)) cfg = {'verbose_level': 1, 'log_level': 'info'} - self.assertEqual(logging.INFO, context.log_level_from_config(cfg)) + self.assertEqual(logging.INFO, logs.log_level_from_config(cfg)) cfg = {'verbose_level': 1, 'log_level': 'debug'} - self.assertEqual(logging.DEBUG, context.log_level_from_config(cfg)) + self.assertEqual(logging.DEBUG, logs.log_level_from_config(cfg)) cfg = {'verbose_level': 1, 'log_level': 'bogus'} - self.assertEqual(logging.WARNING, context.log_level_from_config(cfg)) + self.assertEqual(logging.WARNING, logs.log_level_from_config(cfg)) cfg = {'verbose_level': 1, 'log_level': 'info', 'debug': True} - self.assertEqual(logging.DEBUG, context.log_level_from_config(cfg)) + self.assertEqual(logging.DEBUG, logs.log_level_from_config(cfg)) @mock.patch('warnings.simplefilter') def test_set_warning_filter(self, simplefilter): - context.set_warning_filter(logging.ERROR) + logs.set_warning_filter(logging.ERROR) simplefilter.assert_called_with("ignore") - context.set_warning_filter(logging.WARNING) + logs.set_warning_filter(logging.WARNING) simplefilter.assert_called_with("ignore") - context.set_warning_filter(logging.INFO) + logs.set_warning_filter(logging.INFO) simplefilter.assert_called_with("once") class TestFileFormatter(utils.TestCase): def test_nothing(self): - formatter = context._FileFormatter() + formatter = logs._FileFormatter() self.assertEqual(('%(asctime)s.%(msecs)03d %(process)d %(levelname)s ' '%(name)s %(message)s'), formatter.fmt) @@ -77,7 +77,7 @@ class Opts(object): os_project_name = 'projecty' username = 'usernamey' options = Opts() - formatter = context._FileFormatter(options=options) + formatter = logs._FileFormatter(options=options) self.assertEqual(('%(asctime)s.%(msecs)03d %(process)d %(levelname)s ' '%(name)s [cloudy usernamey projecty] %(message)s'), formatter.fmt) @@ -86,7 +86,7 @@ def test_config(self): config = mock.Mock() config.config = {'cloud': 'cloudy'} config.auth = {'project_name': 'projecty', 'username': 'usernamey'} - formatter = context._FileFormatter(config=config) + formatter = logs._FileFormatter(config=config) self.assertEqual(('%(asctime)s.%(msecs)03d %(process)d %(levelname)s ' '%(name)s [cloudy usernamey projecty] %(message)s'), formatter.fmt) @@ -119,7 +119,7 @@ def setUp(self): @mock.patch('logging.StreamHandler') @mock.patch('logging.getLogger') - @mock.patch('openstackclient.common.context.set_warning_filter') + @mock.patch('openstackclient.common.logs.set_warning_filter') def test_init(self, warning_filter, getLogger, handle): getLogger.side_effect = self.loggers console_logger = mock.Mock() @@ -127,7 +127,7 @@ def test_init(self, warning_filter, getLogger, handle): console_logger.setLevel = mock.Mock() handle.return_value = console_logger - configurator = context.LogConfigurator(self.options) + configurator = logs.LogConfigurator(self.options) getLogger.assert_called_with('iso8601') # last call warning_filter.assert_called_with(logging.WARNING) @@ -140,12 +140,12 @@ def test_init(self, warning_filter, getLogger, handle): self.assertEqual(False, configurator.dump_trace) @mock.patch('logging.getLogger') - @mock.patch('openstackclient.common.context.set_warning_filter') + @mock.patch('openstackclient.common.logs.set_warning_filter') def test_init_no_debug(self, warning_filter, getLogger): getLogger.side_effect = self.loggers self.options.debug = True - configurator = context.LogConfigurator(self.options) + configurator = logs.LogConfigurator(self.options) warning_filter.assert_called_with(logging.DEBUG) self.requests_log.setLevel.assert_called_with(logging.DEBUG) @@ -153,8 +153,8 @@ def test_init_no_debug(self, warning_filter, getLogger): @mock.patch('logging.FileHandler') @mock.patch('logging.getLogger') - @mock.patch('openstackclient.common.context.set_warning_filter') - @mock.patch('openstackclient.common.context._FileFormatter') + @mock.patch('openstackclient.common.logs.set_warning_filter') + @mock.patch('openstackclient.common.logs._FileFormatter') def test_init_log_file(self, formatter, warning_filter, getLogger, handle): getLogger.side_effect = self.loggers self.options.log_file = '/tmp/log_file' @@ -165,7 +165,7 @@ def test_init_log_file(self, formatter, warning_filter, getLogger, handle): mock_formatter = mock.Mock() formatter.return_value = mock_formatter - context.LogConfigurator(self.options) + logs.LogConfigurator(self.options) handle.assert_called_with(filename=self.options.log_file) self.root_logger.addHandler.assert_called_with(file_logger) @@ -174,11 +174,11 @@ def test_init_log_file(self, formatter, warning_filter, getLogger, handle): @mock.patch('logging.FileHandler') @mock.patch('logging.getLogger') - @mock.patch('openstackclient.common.context.set_warning_filter') - @mock.patch('openstackclient.common.context._FileFormatter') + @mock.patch('openstackclient.common.logs.set_warning_filter') + @mock.patch('openstackclient.common.logs._FileFormatter') def test_configure(self, formatter, warning_filter, getLogger, handle): getLogger.side_effect = self.loggers - configurator = context.LogConfigurator(self.options) + configurator = logs.LogConfigurator(self.options) cloud_config = mock.Mock() config_log = '/tmp/config_log' cloud_config.config = { From 9e507523215bb38502832ceccea0a9b94b6f4cbd Mon Sep 17 00:00:00 2001 From: xiexs Date: Mon, 2 Nov 2015 04:28:08 -0500 Subject: [PATCH 0326/3095] Fix the bug of "openstack usage show" When there is no resouce usage associated with the project, an odd output will be displayed. This patch tried to fix this issue. Change-Id: I6f254c6ba37fbb760ada08e640c4938668d560dc Closes-Bug: #1512220 --- openstackclient/compute/v2/usage.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/openstackclient/compute/v2/usage.py b/openstackclient/compute/v2/usage.py index 31b90c229d..6d5d678fc9 100644 --- a/openstackclient/compute/v2/usage.py +++ b/openstackclient/compute/v2/usage.py @@ -183,10 +183,18 @@ def take_action(self, parsed_args): )) info = {} - info['Servers'] = len(usage.server_usages) - info['RAM MB-Hours'] = float("%.2f" % usage.total_memory_mb_usage) - info['CPU Hours'] = float("%.2f" % usage.total_vcpus_usage) - info['Disk GB-Hours'] = float("%.2f" % usage.total_local_gb_usage) + info['Servers'] = ( + len(usage.server_usages) + if hasattr(usage, "server_usages") else None) + info['RAM MB-Hours'] = ( + float("%.2f" % usage.total_memory_mb_usage) + if hasattr(usage, "total_memory_mb_usage") else None) + info['CPU Hours'] = ( + float("%.2f" % usage.total_vcpus_usage) + if hasattr(usage, "total_vcpus_usage") else None) + info['Disk GB-Hours'] = ( + float("%.2f" % usage.total_local_gb_usage) + if hasattr(usage, "total_local_gb_usage") else None) return zip(*sorted(six.iteritems(info))) From 332ec43b5f61e84c319eff1215cb5ab8c4221522 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 4 Nov 2015 06:04:23 +0000 Subject: [PATCH 0327/3095] Imported Translations from Zanata For more information about this automatic import see: https://wiki.openstack.org/wiki/Translations/Infrastructure Change-Id: Ibaa8ba0e2fd013b0cc48e2c4fe8737299b64ea48 --- .../de/LC_MESSAGES/python-openstackclient.po | 10 +++----- .../locale/python-openstackclient.pot | 23 ++++++++----------- .../LC_MESSAGES/python-openstackclient.po | 10 +++----- 3 files changed, 15 insertions(+), 28 deletions(-) diff --git a/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po b/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po index c95391fe6b..983abdb561 100644 --- a/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po +++ b/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po @@ -9,14 +9,13 @@ # OpenStack Infra , 2015. #zanata msgid "" msgstr "" -"Project-Id-Version: python-openstackclient 1.7.1.dev7\n" +"Project-Id-Version: python-openstackclient 1.8.1.dev15\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-10-01 06:12+0000\n" +"POT-Creation-Date: 2015-11-04 06:04+0000\n" "PO-Revision-Date: 2015-09-19 07:10+0000\n" "Last-Translator: Andreas Jaeger \n" "Language: de\n" -"Language-Team: German (http://www.transifex.com/openstack/python-" -"openstackclient/language/de/)\n" +"Language-Team: German\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -280,9 +279,6 @@ msgstr "Neue Regionskennung" msgid "New region description" msgstr "Beschreibung des neuen Bereichs" -msgid "New region url" -msgstr "URL des neuen Bereichs" - msgid "New role name" msgstr "Name der neuen Rolle" diff --git a/python-openstackclient/locale/python-openstackclient.pot b/python-openstackclient/locale/python-openstackclient.pot index f613a5a982..cc4f1a1aae 100644 --- a/python-openstackclient/locale/python-openstackclient.pot +++ b/python-openstackclient/locale/python-openstackclient.pot @@ -7,16 +7,16 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: python-openstackclient 1.6.1.dev43\n" +"Project-Id-Version: python-openstackclient 1.8.1.dev15\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-09-11 06:10+0000\n" +"POT-Creation-Date: 2015-11-04 06:04+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.0\n" +"Generated-By: Babel 2.1.1\n" #: openstackclient/api/auth.py:144 msgid "Set a username with --os-username, OS_USERNAME, or auth.username\n" @@ -756,32 +756,27 @@ msgid "Parent region ID" msgstr "" #: openstackclient/identity/v3/region.py:49 -#: openstackclient/identity/v3/region.py:151 +#: openstackclient/identity/v3/region.py:144 msgid "New region description" msgstr "" -#: openstackclient/identity/v3/region.py:54 -#: openstackclient/identity/v3/region.py:156 -msgid "New region url" -msgstr "" - -#: openstackclient/identity/v3/region.py:86 +#: openstackclient/identity/v3/region.py:79 msgid "Region ID to delete" msgstr "" -#: openstackclient/identity/v3/region.py:108 +#: openstackclient/identity/v3/region.py:101 msgid "Filter by parent region ID" msgstr "" -#: openstackclient/identity/v3/region.py:141 +#: openstackclient/identity/v3/region.py:134 msgid "Region to modify" msgstr "" -#: openstackclient/identity/v3/region.py:146 +#: openstackclient/identity/v3/region.py:139 msgid "New parent region ID" msgstr "" -#: openstackclient/identity/v3/region.py:191 +#: openstackclient/identity/v3/region.py:175 msgid "Region to display" msgstr "" diff --git a/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po b/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po index d8d070d2ff..301912c748 100644 --- a/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po +++ b/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po @@ -7,14 +7,13 @@ # OpenStack Infra , 2015. #zanata msgid "" msgstr "" -"Project-Id-Version: python-openstackclient 1.7.1.dev7\n" +"Project-Id-Version: python-openstackclient 1.8.1.dev15\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-10-01 06:12+0000\n" +"POT-Creation-Date: 2015-11-04 06:04+0000\n" "PO-Revision-Date: 2015-06-14 06:41+0000\n" "Last-Translator: openstackjenkins \n" "Language: zh_Hant_TW\n" -"Language-Team: Chinese (Taiwan) (http://www.transifex.com/openstack/python-" -"openstackclient/language/zh_TW/)\n" +"Language-Team: Chinese (Taiwan)\n" "Plural-Forms: nplurals=1; plural=0\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -229,9 +228,6 @@ msgstr "新地區識別號" msgid "New region description" msgstr "新地區描述" -msgid "New region url" -msgstr "新地區網址" - msgid "New role name" msgstr "新角色名稱" From 7d8bb331a0ab516aaa3721b9b200a388214a22cb Mon Sep 17 00:00:00 2001 From: xiexs Date: Wed, 4 Nov 2015 10:22:39 -0500 Subject: [PATCH 0328/3095] Add project-name/-id validation for the OSC "openstack quota set" The quota info would be set into DB, even though the project actually does not exist. This patch tried to add a validation to forbid this undesirable behavior. Change-Id: Ia2d8c96527820e25b074e6486d3f39c5ad7eae60 Closes-Bug: #1512638 --- openstackclient/common/quota.py | 15 +++++++++---- openstackclient/tests/common/test_quota.py | 26 +++++++++++++++++----- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index e092fefff5..b5d4eb8772 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -94,6 +94,7 @@ def get_parser(self, prog_name): @utils.log_method(log) def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity compute_client = self.app.client_manager.compute volume_client = self.app.client_manager.volume @@ -115,23 +116,29 @@ def take_action(self, parsed_args): sys.stderr.write("No quotas updated") return + if parsed_args.project: + project = utils.find_resource( + identity_client.projects, + parsed_args.project, + ) + if parsed_args.quota_class: if compute_kwargs: compute_client.quota_classes.update( - parsed_args.project, + project.id, **compute_kwargs) if volume_kwargs: volume_client.quota_classes.update( - parsed_args.project, + project.id, **volume_kwargs) else: if compute_kwargs: compute_client.quotas.update( - parsed_args.project, + project.id, **compute_kwargs) if volume_kwargs: volume_client.quotas.update( - parsed_args.project, + project.id, **volume_kwargs) diff --git a/openstackclient/tests/common/test_quota.py b/openstackclient/tests/common/test_quota.py index b6ad1566c2..047ef3433f 100644 --- a/openstackclient/tests/common/test_quota.py +++ b/openstackclient/tests/common/test_quota.py @@ -17,6 +17,7 @@ from openstackclient.common import quota from openstackclient.tests.compute.v2 import fakes as compute_fakes from openstackclient.tests import fakes +from openstackclient.tests.identity.v2_0 import fakes as identity_fakes class FakeQuotaResource(fakes.FakeResource): @@ -45,6 +46,8 @@ def setUp(self): self.app.client_manager.volume = volume_mock self.volume_quotas_mock = volume_mock.quotas self.volume_quotas_mock.reset_mock() + self.projects_mock = self.app.client_manager.identity.projects + self.projects_mock.reset_mock() class TestQuotaSet(TestQuota): @@ -76,6 +79,12 @@ def setUp(self): loaded=True, ) + self.projects_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.PROJECT), + loaded=True, + ) + self.cmd = quota.SetQuota(self.app, None) def test_quota_set(self): @@ -84,14 +93,14 @@ def test_quota_set(self): '--fixed-ips', str(compute_fakes.fix_ip_num), '--injected-files', str(compute_fakes.injected_file_num), '--key-pairs', str(compute_fakes.key_pair_num), - compute_fakes.project_name, + identity_fakes.project_name, ] verifylist = [ ('floating_ips', compute_fakes.floating_ip_num), ('fixed_ips', compute_fakes.fix_ip_num), ('injected_files', compute_fakes.injected_file_num), ('key_pairs', compute_fakes.key_pair_num), - ('project', compute_fakes.project_name), + ('project', identity_fakes.project_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -105,14 +114,17 @@ def test_quota_set(self): 'key_pairs': compute_fakes.key_pair_num, } - self.quotas_mock.update.assert_called_with('project_test', **kwargs) + self.quotas_mock.update.assert_called_with( + identity_fakes.project_id, + **kwargs + ) def test_quota_set_volume(self): arglist = [ '--gigabytes', str(compute_fakes.floating_ip_num), '--snapshots', str(compute_fakes.fix_ip_num), '--volumes', str(compute_fakes.injected_file_num), - compute_fakes.project_name, + identity_fakes.project_name, ] verifylist = [ ('gigabytes', compute_fakes.floating_ip_num), @@ -130,5 +142,7 @@ def test_quota_set_volume(self): 'volumes': compute_fakes.injected_file_num, } - self.volume_quotas_mock.update.assert_called_with('project_test', - **kwargs) + self.volume_quotas_mock.update.assert_called_with( + identity_fakes.project_id, + **kwargs + ) From c49c9df10590dce70e5384357ca1249f209cb509 Mon Sep 17 00:00:00 2001 From: Sean Perry Date: Wed, 4 Nov 2015 09:50:37 -0800 Subject: [PATCH 0329/3095] Import the module not the class As requested during review 226922. Change-Id: Ic5222141e247ce33cf5dbee66667cee3040e1cc3 --- openstackclient/tests/identity/v3/test_credential.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/openstackclient/tests/identity/v3/test_credential.py b/openstackclient/tests/identity/v3/test_credential.py index 5adcf7fa46..e2e690c3b4 100644 --- a/openstackclient/tests/identity/v3/test_credential.py +++ b/openstackclient/tests/identity/v3/test_credential.py @@ -14,7 +14,7 @@ from openstackclient.identity.v3 import credential from openstackclient.tests.identity.v3 import fakes as identity_fakes -from openstackclient.tests.utils import ParserException +from openstackclient.tests import utils class TestCredential(identity_fakes.TestIdentityv3): @@ -55,7 +55,7 @@ def test_credential_set_no_options(self): identity_fakes.credential_id, ] - self.assertRaises(ParserException, + self.assertRaises(utils.ParserException, self.check_parser, self.cmd, arglist, []) def test_credential_set_missing_user(self): @@ -65,7 +65,7 @@ def test_credential_set_missing_user(self): identity_fakes.credential_id, ] - self.assertRaises(ParserException, + self.assertRaises(utils.ParserException, self.check_parser, self.cmd, arglist, []) def test_credential_set_missing_type(self): @@ -75,7 +75,7 @@ def test_credential_set_missing_type(self): identity_fakes.credential_id, ] - self.assertRaises(ParserException, + self.assertRaises(utils.ParserException, self.check_parser, self.cmd, arglist, []) def test_credential_set_missing_data(self): @@ -85,7 +85,7 @@ def test_credential_set_missing_data(self): identity_fakes.credential_id, ] - self.assertRaises(ParserException, + self.assertRaises(utils.ParserException, self.check_parser, self.cmd, arglist, []) def test_credential_set_valid(self): From 2f00fcda77af8b87d009cd92facd6a5a6ea4acd8 Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Wed, 4 Nov 2015 11:20:43 -0700 Subject: [PATCH 0330/3095] Allow int version numbers in the clouds.yaml OSC blows up if you try to use for example identity_api_version: 2 in the clouds.yaml. It will only work with a string '2'. This fixes that. Change-Id: I785d37a288126a1582464e907c7f9c9947bac27c --- openstackclient/shell.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 72521cb275..5b4939a217 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -280,7 +280,7 @@ def initialize_app(self, argv): for mod in clientmanager.PLUGIN_MODULES: default_version = getattr(mod, 'DEFAULT_API_VERSION', None) option = mod.API_VERSION_OPTION.replace('os_', '') - version_opt = self.cloud.config.get(option, default_version) + version_opt = str(self.cloud.config.get(option, default_version)) if version_opt: api = mod.API_NAME self.api_version[api] = version_opt From 1809faaf1fbffea497cfbe2f1c7adde6bf449234 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 24 Oct 2015 12:05:50 +0800 Subject: [PATCH 0331/3095] Add "server start" command to osc. There is no start command in osc. Add it. Change-Id: Ic50f83413ab17c53396065aabb3f5a1506b52959 Implements: blueprint cmd-with-multi-servers --- doc/source/command-objects/server.rst | 15 +++++++++++++++ doc/source/commands.rst | 1 + openstackclient/compute/v2/server.py | 25 +++++++++++++++++++++++++ setup.cfg | 1 + 4 files changed, 42 insertions(+) diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index 9ff4843727..4f0b836d58 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -595,6 +595,21 @@ Ssh to server Server (name or ID) +server start +------------ + +Start server(s) + +.. program:: server start +.. code:: bash + + os server start + [ ...] + +.. describe:: + + Server(s) to start (name or ID) + server suspend -------------- diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 80ce824425..d89e71935d 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -183,6 +183,7 @@ Those actions with an opposite action are noted in parens if applicable. * ``save`` - download an object locally * ``set`` (``unset``) - set a property on the object, formerly called metadata * ``show`` - display detailed information about the specific object +* ``start`` - start one or more servers * ``suspend`` (``resume``) - stop a server and save to disk freeing memory * ``unlock`` (``lock``) - unlock a server * ``unpause`` (``pause``) - return a paused server to running state diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index ac71740570..2768951de6 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1483,6 +1483,31 @@ def take_action(self, parsed_args): os.system(cmd % (login, ip_address)) +class StartServer(command.Command): + """Start server(s).""" + + log = logging.getLogger(__name__ + '.StartServer') + + def get_parser(self, prog_name): + parser = super(StartServer, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + nargs="+", + help=_('Server(s) to start (name or ID)'), + ) + return parser + + @utils.log_method(log) + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + for server in parsed_args.server: + utils.find_resource( + compute_client.servers, + server, + ).start() + + class SuspendServer(command.Command): """Suspend server""" diff --git a/setup.cfg b/setup.cfg index 7265eb883e..cfbc7c294b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -133,6 +133,7 @@ openstack.compute.v2 = server_set = openstackclient.compute.v2.server:SetServer server_show = openstackclient.compute.v2.server:ShowServer server_ssh = openstackclient.compute.v2.server:SshServer + server_start = openstackclient.compute.v2.server:StartServer server_suspend = openstackclient.compute.v2.server:SuspendServer server_unlock = openstackclient.compute.v2.server:UnlockServer server_unpause = openstackclient.compute.v2.server:UnpauseServer From 7107b5536e9ece347014ec88c5c5bb3e68050882 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 24 Oct 2015 12:08:35 +0800 Subject: [PATCH 0332/3095] Add "server stop" command to osc. There is no stop command in osc. Add it. Change-Id: Ia11a8f3b1245c884f7da442292009342d2ffde1e Implements: blueprint cmd-with-multi-servers --- doc/source/command-objects/server.rst | 15 +++++++++++++++ doc/source/commands.rst | 3 ++- openstackclient/compute/v2/server.py | 25 +++++++++++++++++++++++++ setup.cfg | 1 + 4 files changed, 43 insertions(+), 1 deletion(-) diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index 4f0b836d58..3cb6598da4 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -610,6 +610,21 @@ Start server(s) Server(s) to start (name or ID) +server stop +----------- + +Stop server(s) + +.. program:: server stop +.. code:: bash + + os server stop + [ ...] + +.. describe:: + + Server(s) to stop (name or ID) + server suspend -------------- diff --git a/doc/source/commands.rst b/doc/source/commands.rst index d89e71935d..34a30c4e44 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -183,7 +183,8 @@ Those actions with an opposite action are noted in parens if applicable. * ``save`` - download an object locally * ``set`` (``unset``) - set a property on the object, formerly called metadata * ``show`` - display detailed information about the specific object -* ``start`` - start one or more servers +* ``start`` (``stop``) - start one or more servers +* ``stop`` (``start``) - stop one or more servers * ``suspend`` (``resume``) - stop a server and save to disk freeing memory * ``unlock`` (``lock``) - unlock a server * ``unpause`` (``pause``) - return a paused server to running state diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 2768951de6..e7dfbbef2a 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1508,6 +1508,31 @@ def take_action(self, parsed_args): ).start() +class StopServer(command.Command): + """Stop server(s).""" + + log = logging.getLogger(__name__ + '.StopServer') + + def get_parser(self, prog_name): + parser = super(StopServer, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + nargs="+", + help=_('Server(s) to stop (name or ID)'), + ) + return parser + + @utils.log_method(log) + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + for server in parsed_args.server: + utils.find_resource( + compute_client.servers, + server, + ).stop() + + class SuspendServer(command.Command): """Suspend server""" diff --git a/setup.cfg b/setup.cfg index cfbc7c294b..d917f04210 100644 --- a/setup.cfg +++ b/setup.cfg @@ -134,6 +134,7 @@ openstack.compute.v2 = server_show = openstackclient.compute.v2.server:ShowServer server_ssh = openstackclient.compute.v2.server:SshServer server_start = openstackclient.compute.v2.server:StartServer + server_stop = openstackclient.compute.v2.server:StopServer server_suspend = openstackclient.compute.v2.server:SuspendServer server_unlock = openstackclient.compute.v2.server:UnlockServer server_unpause = openstackclient.compute.v2.server:UnpauseServer From 45a07afd54767e5e6951b5834efab296cf52e9b5 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Fri, 6 Nov 2015 19:02:36 +0800 Subject: [PATCH 0333/3095] Trivial: Fix wrong comment of _format_servers_list_networks(). The parameter name should be 'networks'. Change-Id: I140c3d61dccbbec40c14bd7a8f2aeac0eb24384d --- openstackclient/compute/v2/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index ac71740570..085aa498c8 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -43,7 +43,7 @@ def _format_servers_list_networks(networks): """Return a formatted string of a server's networks - :param server: a Server.networks field + :param networks: a Server.networks field :rtype: a string of formatted network addresses """ output = [] From 5ad59968ac4257c76aec9bd62c417fe8b5403608 Mon Sep 17 00:00:00 2001 From: NiallBunting Date: Fri, 25 Sep 2015 13:21:01 +0000 Subject: [PATCH 0334/3095] Add --owner to `image create` This adds --owner to `image create`. This is backwards compatable with v1. Change-Id: I9e79cf880c91a1386419db729818d23dfe632179 Depends-On: I8d572a070bbb04dccdd051b8e0ad199c5754746e --- doc/source/command-objects/image.rst | 2 -- openstackclient/image/v2/image.py | 10 +++++++--- openstackclient/tests/image/v2/test_image.py | 6 ++++-- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/doc/source/command-objects/image.rst b/doc/source/command-objects/image.rst index c3fe77a10b..d4b9916221 100644 --- a/doc/source/command-objects/image.rst +++ b/doc/source/command-objects/image.rst @@ -57,8 +57,6 @@ Create/upload an image Image owner project name or ID - *Image version 1 only.* - .. option:: --size Image size, in bytes (only used with --location and --copy-from) diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 7d8b14124a..6fd6c74e30 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -112,7 +112,7 @@ class CreateImage(show.ShowOne): """Create/upload an image""" log = logging.getLogger(__name__ + ".CreateImage") - deadopts = ('owner', 'size', 'location', 'copy-from', 'checksum', 'store') + deadopts = ('size', 'location', 'copy-from', 'checksum', 'store') def get_parser(self, prog_name): parser = super(CreateImage, self).get_parser(prog_name) @@ -120,7 +120,6 @@ def get_parser(self, prog_name): # TODO(bunting): There are additional arguments that v1 supported # that v2 either doesn't support or supports weirdly. # --checksum - could be faked clientside perhaps? - # --owner - could be set as an update after the put? # --location - maybe location add? # --size - passing image size is actually broken in python-glanceclient # --copy-from - does not exist in v2 @@ -149,6 +148,11 @@ def get_parser(self, prog_name): help="Image disk format " "(default: %s)" % DEFAULT_DISK_FORMAT, ) + parser.add_argument( + "--owner", + metavar="", + help="Image owner project name or ID", + ) parser.add_argument( "--min-disk", metavar="", @@ -229,7 +233,7 @@ def take_action(self, parsed_args): copy_attrs = ('name', 'id', 'container_format', 'disk_format', 'min_disk', 'min_ram', - 'tags') + 'tags', 'owner') for attr in copy_attrs: if attr in parsed_args: val = getattr(parsed_args, attr, None) diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index 4ce854759b..b35fda798e 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -112,6 +112,7 @@ def test_image_reserve_options(self, mock_open): '--disk-format', 'fs', '--min-disk', '10', '--min-ram', '4', + '--owner', '123456', '--protected', '--private', image_fakes.image_name, @@ -121,6 +122,7 @@ def test_image_reserve_options(self, mock_open): ('disk_format', 'fs'), ('min_disk', 10), ('min_ram', 4), + ('owner', '123456'), ('protected', True), ('unprotected', False), ('public', False), @@ -139,6 +141,7 @@ def test_image_reserve_options(self, mock_open): disk_format='fs', min_disk=10, min_ram=4, + owner='123456', protected=True, visibility='private', ) @@ -213,11 +216,10 @@ def test_image_create_file(self, mock_open): def test_image_create_dead_options(self): arglist = [ - '--owner', 'nobody', + '--store', 'somewhere', image_fakes.image_name, ] verifylist = [ - ('owner', 'nobody'), ('name', image_fakes.image_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) From db46666589d7a0c8097a86735d8821f313c54412 Mon Sep 17 00:00:00 2001 From: xiexs Date: Fri, 6 Nov 2015 09:16:43 -0500 Subject: [PATCH 0335/3095] Fix a bug about "openstack server list --user" Fix the bug to get the right user id. Change-Id: Ie60a719a40654802772884ff94271b37aa061ac3 Closes-Bug: #1513701 --- openstackclient/compute/v2/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index ee66c66596..9fb91dcaab 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -735,7 +735,7 @@ def take_action(self, parsed_args): user_id = None if parsed_args.user: - user_id = identity_common.find_project( + user_id = identity_common.find_user( identity_client, parsed_args.user, parsed_args.user_domain, From f8f2f4dcd3013a76d350242507fc75ee7309c534 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 7 Nov 2015 14:39:53 +0800 Subject: [PATCH 0336/3095] Use fake server name instead of id when testing "server_name" param. "server create" command takes a server name parameter. The server id is generated by openstack. When we intended to pass a server name to the unit tests of "server create" command, we passed server id. It won't be any problem because the fake server id is also a string, but we should pass a fake server name because we have a string to fake one. Change-Id: I9944f0ea2a6c457e4fad8215a54778bca08965ab --- .../tests/compute/v2/test_server.py | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 4f5bdd5b6a..f420ff0ada 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -85,10 +85,10 @@ def setUp(self): def test_server_create_no_options(self): arglist = [ - compute_fakes.server_id, + compute_fakes.server_name, ] verifylist = [ - ('server_name', compute_fakes.server_id), + ('server_name', compute_fakes.server_name), ] try: # Missing required args should bail here @@ -100,13 +100,13 @@ def test_server_create_minimal(self): arglist = [ '--image', 'image1', '--flavor', 'flavor1', - compute_fakes.server_id, + compute_fakes.server_name, ] verifylist = [ ('image', 'image1'), ('flavor', 'flavor1'), ('config_drive', False), - ('server_name', compute_fakes.server_id), + ('server_name', compute_fakes.server_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -131,7 +131,7 @@ def test_server_create_minimal(self): ) # ServerManager.create(name, image, flavor, **kwargs) self.servers_mock.create.assert_called_with( - compute_fakes.server_id, + compute_fakes.server_name, self.image, self.flavor, **kwargs @@ -154,14 +154,14 @@ def test_server_create_with_network(self): '--flavor', 'flavor1', '--nic', 'net-id=net1', '--nic', 'port-id=port1', - compute_fakes.server_id, + compute_fakes.server_name, ] verifylist = [ ('image', 'image1'), ('flavor', 'flavor1'), ('nic', ['net-id=net1', 'port-id=port1']), ('config_drive', False), - ('server_name', compute_fakes.server_id), + ('server_name', compute_fakes.server_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -207,7 +207,7 @@ def test_server_create_with_network(self): ) # ServerManager.create(name, image, flavor, **kwargs) self.servers_mock.create.assert_called_with( - compute_fakes.server_id, + compute_fakes.server_name, self.image, self.flavor, **kwargs @@ -234,14 +234,14 @@ def test_server_create_userdata(self, mock_open): '--image', 'image1', '--flavor', 'flavor1', '--user-data', 'userdata.sh', - compute_fakes.server_id, + compute_fakes.server_name, ] verifylist = [ ('image', 'image1'), ('flavor', 'flavor1'), ('user_data', 'userdata.sh'), ('config_drive', False), - ('server_name', compute_fakes.server_id), + ('server_name', compute_fakes.server_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -272,7 +272,7 @@ def test_server_create_userdata(self, mock_open): ) # ServerManager.create(name, image, flavor, **kwargs) self.servers_mock.create.assert_called_with( - compute_fakes.server_id, + compute_fakes.server_name, self.image, self.flavor, **kwargs From 176735f4aa8d9b0e9ab3bdb75ac2df8d62a22c8b Mon Sep 17 00:00:00 2001 From: xiexs Date: Fri, 6 Nov 2015 09:57:48 -0500 Subject: [PATCH 0337/3095] Change method to get the user_id Instead of "find_project", using "find_user" to get the user_id while the option --user is specified for "openstack volume list" Change-Id: Iea8472b7b8e709a8792a56575e00003a9cbdaa39 Closes-Bug: #1514145 --- openstackclient/volume/v2/volume.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index f59567cc1c..f34198dcf3 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -304,9 +304,9 @@ def _format_attach(attachments): user_id = None if parsed_args.user: - user_id = identity_common.find_project(identity_client, - parsed_args.user, - parsed_args.user_domain) + user_id = identity_common.find_user(identity_client, + parsed_args.user, + parsed_args.user_domain) search_opts = { 'all_tenants': parsed_args.all_projects, From a337f664d23cea334be4618a0049a9f124df777c Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Fri, 23 Oct 2015 16:59:03 +0800 Subject: [PATCH 0338/3095] Enable "openstack server pause" command to take multiple servers. Current "openstack server pause" command could only pause one server. Improve it to be able to handle more than one servers. Also improve the doc to reflect the new feature. Change-Id: I809f77f0720457c9cdc1028a70b391c75885984c Implements: blueprint cmd-with-multi-servers --- doc/source/command-objects/server.rst | 6 +++--- doc/source/commands.rst | 2 +- openstackclient/compute/v2/server.py | 15 ++++++++------- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index 3cb6598da4..155b76fea4 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -311,17 +311,17 @@ Migrate server to different host server pause ------------ -Pause server +Pause server(s) .. program:: server pause .. code:: bash os server pause - + [ ...] .. describe:: - Server (name or ID) + Server(s) to pause (name or ID) server reboot ------------- diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 34a30c4e44..e9a8b87e71 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -172,7 +172,7 @@ Those actions with an opposite action are noted in parens if applicable. * ``lock`` (``unlock``) - lock a server so that non-admin user won't be able to execute actions * ``migrate`` - move a server to a different host; ``--live`` performs a live migration if possible -* ``pause`` (``unpause``) - stop a server and leave it in memory +* ``pause`` (``unpause``) - stop one or more servers and leave them in memory * ``reboot`` - forcibly reboot a server * ``rebuild`` - rebuild a server using (most of) the same arguments as in the original create * ``remove`` (``add``) - remove an object from a group of objects diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 9fb91dcaab..b5e7f004b8 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -915,7 +915,7 @@ def take_action(self, parsed_args): class PauseServer(command.Command): - """Pause server""" + """Pause server(s)""" log = logging.getLogger(__name__ + '.PauseServer') @@ -924,18 +924,19 @@ def get_parser(self, prog_name): parser.add_argument( 'server', metavar='', - help=_('Server (name or ID)'), + nargs='+', + help=_('Server(s) to pause (name or ID)'), ) return parser @utils.log_method(log) def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - utils.find_resource( - compute_client.servers, - parsed_args.server, - ).pause() + for server in parsed_args.server: + utils.find_resource( + compute_client.servers, + server + ).pause() class RebootServer(command.Command): From ea63553925bce0432958ae4a15536f7f83b1f28d Mon Sep 17 00:00:00 2001 From: xiexs Date: Mon, 9 Nov 2015 04:22:00 -0500 Subject: [PATCH 0339/3095] Fix the bug of "openstack console log show" The behaviors are inconsistent while different negative line numbers specified. Change-Id: I2573f3e789f5603c896758971830ffc0b94c5e2b Closes-Bug: #1512263 --- openstackclient/common/parseractions.py | 15 +++++ openstackclient/compute/v2/console.py | 2 + .../tests/common/test_parseractions.py | 55 +++++++++++++++++++ 3 files changed, 72 insertions(+) diff --git a/openstackclient/common/parseractions.py b/openstackclient/common/parseractions.py index 8f6008e26b..fd90369a7f 100644 --- a/openstackclient/common/parseractions.py +++ b/openstackclient/common/parseractions.py @@ -65,3 +65,18 @@ def __call__(self, parser, namespace, values, option_string=None): # Too many values msg = "Invalid range, too many values" raise argparse.ArgumentError(self, msg) + + +class NonNegativeAction(argparse.Action): + """A custom action to check whether the value is non-negative or not + + Ensures the value is >= 0. + """ + + def __call__(self, parser, namespace, values, option_string=None): + try: + assert(int(values) >= 0) + setattr(namespace, self.dest, values) + except Exception: + msg = "%s expected a non-negative integer" % (str(option_string)) + raise argparse.ArgumentTypeError(self, msg) diff --git a/openstackclient/compute/v2/console.py b/openstackclient/compute/v2/console.py index bb0747b1cb..aafa5d441c 100644 --- a/openstackclient/compute/v2/console.py +++ b/openstackclient/compute/v2/console.py @@ -22,6 +22,7 @@ from cliff import command from cliff import show +from openstackclient.common import parseractions from openstackclient.common import utils @@ -42,6 +43,7 @@ def get_parser(self, prog_name): metavar='', type=int, default=None, + action=parseractions.NonNegativeAction, help='Number of lines to display from the end of the log ' '(default=all)', ) diff --git a/openstackclient/tests/common/test_parseractions.py b/openstackclient/tests/common/test_parseractions.py index 8afcb63260..b75c48140b 100644 --- a/openstackclient/tests/common/test_parseractions.py +++ b/openstackclient/tests/common/test_parseractions.py @@ -102,3 +102,58 @@ def test_error_values(self): expect = {'green': '100%'} self.assertDictEqual(expect, actual) self.assertEqual(None, failhere) + + +class TestNonNegativeAction(utils.TestCase): + def test_negative_values(self): + parser = argparse.ArgumentParser() + + # Set up our typical usage + parser.add_argument( + '--foo', + metavar='', + type=int, + action=parseractions.NonNegativeAction, + ) + + self.assertRaises( + argparse.ArgumentTypeError, + parser.parse_args, + "--foo -1".split() + ) + + def test_zero_values(self): + parser = argparse.ArgumentParser() + + # Set up our typical usage + parser.add_argument( + '--foo', + metavar='', + type=int, + action=parseractions.NonNegativeAction, + ) + + results = parser.parse_args( + '--foo 0'.split() + ) + + actual = getattr(results, 'foo', None) + self.assertEqual(actual, 0) + + def test_positive_values(self): + parser = argparse.ArgumentParser() + + # Set up our typical usage + parser.add_argument( + '--foo', + metavar='', + type=int, + action=parseractions.NonNegativeAction, + ) + + results = parser.parse_args( + '--foo 1'.split() + ) + + actual = getattr(results, 'foo', None) + self.assertEqual(actual, 1) From 441543d67fbff068c1993a39c72c8dd62df34cfb Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Mon, 9 Nov 2015 09:29:38 -0500 Subject: [PATCH 0340/3095] Set default network api to 2.0 instead of 2 neutronclient expects 2.0 as the version if you go through the discovery constructor. For that reason, 2.0 is the 'correct' version to set in config files or environment variables for if you're using things that are not OSC. However, if you do that, OSC prints a warning that 2.0 is not in the supported network version list. Let's support both so that users don't get a confuse. Change-Id: I7412519693f75fcd29f5621ce9e5a2df2da92684 --- openstackclient/network/client.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openstackclient/network/client.py b/openstackclient/network/client.py index 5f72782bb1..69ed11feb8 100644 --- a/openstackclient/network/client.py +++ b/openstackclient/network/client.py @@ -18,10 +18,11 @@ LOG = logging.getLogger(__name__) -DEFAULT_API_VERSION = '2' +DEFAULT_API_VERSION = '2.0' API_VERSION_OPTION = 'os_network_api_version' API_NAME = "network" API_VERSIONS = { + "2.0": "neutronclient.v2_0.client.Client", "2": "neutronclient.v2_0.client.Client", } # Translate our API version to auth plugin version prefix @@ -32,6 +33,7 @@ NETWORK_API_TYPE = 'network' NETWORK_API_VERSIONS = { + '2.0': 'openstackclient.api.network_v2.APIv2', '2': 'openstackclient.api.network_v2.APIv2', } From c9756667ce7d9ac735174f00af746ef7bf0d80c2 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 10 Nov 2015 16:28:25 +0800 Subject: [PATCH 0341/3095] Trivial cleanup: Use plural format for "server delete" doc. Change-Id: I4dca25f1933d5925db443eca75f602a39a833413 --- doc/source/command-objects/server.rst | 2 +- doc/source/commands.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index 3cb6598da4..706ebf3e85 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -165,7 +165,7 @@ Delete server(s) .. describe:: - Server to delete (name or ID) + Server(s) to delete (name or ID) server list ----------- diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 34a30c4e44..f6c90cdded 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -166,7 +166,7 @@ Those actions with an opposite action are noted in parens if applicable. is built in the order of ``container add object ``, the positional arguments appear in the same order * ``create`` (``delete``) - create a new occurrence of the specified object -* ``delete`` (``create``) - delete a specific occurrence of the specified object +* ``delete`` (``create``) - delete specific occurrences of the specified objects * ``issue`` (``revoke``) - issue a token * ``list`` - display summary information about multiple objects * ``lock`` (``unlock``) - lock a server so that non-admin user won't be able to execute actions From c396b69d3722576a62b411a3a19360cb6f1eef0b Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Thu, 12 Nov 2015 17:02:24 +0800 Subject: [PATCH 0342/3095] Trivial: Fix wrong param name in comment. "volume" should be "attachments". Change-Id: I41c797f0c7cccb2727e9e6cd6424ea2f740624b7 --- openstackclient/volume/v1/volume.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index 92afe8b0c7..0691d884a5 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -239,7 +239,7 @@ def take_action(self, parsed_args): def _format_attach(attachments): """Return a formatted string of a volume's attached instances - :param volume: a volume.attachments field + :param attachments: a volume.attachments field :rtype: a string of formatted instances """ From c079e137748d4914c0da56e2b148eb4495b41db4 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Sun, 18 Oct 2015 16:59:01 -0400 Subject: [PATCH 0343/3095] better format remote IDs for identity providers remote-ids are a list, and we should format these values as such, rather than python representations of lists/arrays. Closes-Bug: 1478995 Change-Id: Ia6ced0fab2435b8cb486822c676c0dee32613abe --- openstackclient/identity/v3/identity_provider.py | 10 +++++++--- openstackclient/tests/identity/v3/fakes.py | 1 + .../tests/identity/v3/test_identity_provider.py | 14 +++++++------- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/openstackclient/identity/v3/identity_provider.py b/openstackclient/identity/v3/identity_provider.py index 27982a9d5c..50bed1f6f0 100644 --- a/openstackclient/identity/v3/identity_provider.py +++ b/openstackclient/identity/v3/identity_provider.py @@ -88,6 +88,8 @@ def take_action(self, parsed_args): enabled=parsed_args.enabled) idp._info.pop('links', None) + remote_ids = utils.format_list(idp._info.pop('remote_ids', [])) + idp._info['remote_ids'] = remote_ids return zip(*sorted(six.iteritems(idp._info))) @@ -221,9 +223,11 @@ def get_parser(self, prog_name): @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - identity_provider = utils.find_resource( + idp = utils.find_resource( identity_client.federation.identity_providers, parsed_args.identity_provider) - identity_provider._info.pop('links', None) - return zip(*sorted(six.iteritems(identity_provider._info))) + idp._info.pop('links', None) + remote_ids = utils.format_list(idp._info.pop('remote_ids', [])) + idp._info['remote_ids'] = remote_ids + return zip(*sorted(six.iteritems(idp._info))) diff --git a/openstackclient/tests/identity/v3/fakes.py b/openstackclient/tests/identity/v3/fakes.py index 61e74f9c86..9fe341ed6a 100644 --- a/openstackclient/tests/identity/v3/fakes.py +++ b/openstackclient/tests/identity/v3/fakes.py @@ -261,6 +261,7 @@ idp_id = 'test_idp' idp_description = 'super exciting IdP description' idp_remote_ids = ['entity1', 'entity2'] +formatted_idp_remote_ids = 'entity1, entity2' IDENTITY_PROVIDER = { 'id': idp_id, diff --git a/openstackclient/tests/identity/v3/test_identity_provider.py b/openstackclient/tests/identity/v3/test_identity_provider.py index cd328c1d4c..9e9e921ea0 100644 --- a/openstackclient/tests/identity/v3/test_identity_provider.py +++ b/openstackclient/tests/identity/v3/test_identity_provider.py @@ -69,7 +69,7 @@ def test_create_identity_provider_no_options(self): identity_fakes.idp_description, True, identity_fakes.idp_id, - identity_fakes.idp_remote_ids + identity_fakes.formatted_idp_remote_ids ) self.assertEqual(datalist, data) @@ -103,7 +103,7 @@ def test_create_identity_provider_description(self): identity_fakes.idp_description, True, identity_fakes.idp_id, - identity_fakes.idp_remote_ids + identity_fakes.formatted_idp_remote_ids ) self.assertEqual(datalist, data) @@ -137,7 +137,7 @@ def test_create_identity_provider_remote_id(self): identity_fakes.idp_description, True, identity_fakes.idp_id, - identity_fakes.idp_remote_ids + identity_fakes.formatted_idp_remote_ids ) self.assertEqual(datalist, data) @@ -172,7 +172,7 @@ def test_create_identity_provider_remote_ids_multiple(self): identity_fakes.idp_description, True, identity_fakes.idp_id, - identity_fakes.idp_remote_ids + identity_fakes.formatted_idp_remote_ids ) self.assertEqual(datalist, data) @@ -211,7 +211,7 @@ def test_create_identity_provider_remote_ids_file(self): identity_fakes.idp_description, True, identity_fakes.idp_id, - identity_fakes.idp_remote_ids + identity_fakes.formatted_idp_remote_ids ) self.assertEqual(datalist, data) @@ -253,7 +253,7 @@ def test_create_identity_provider_disabled(self): None, False, identity_fakes.idp_id, - identity_fakes.idp_remote_ids + identity_fakes.formatted_idp_remote_ids ) self.assertEqual(datalist, data) @@ -363,7 +363,7 @@ def test_identity_provider_show(self): identity_fakes.idp_description, True, identity_fakes.idp_id, - identity_fakes.idp_remote_ids + identity_fakes.formatted_idp_remote_ids ) self.assertEqual(datalist, data) From 8b23c2690c90a6cafc58f8a72f04c8e33234ce37 Mon Sep 17 00:00:00 2001 From: xiexs Date: Tue, 10 Nov 2015 02:39:28 -0500 Subject: [PATCH 0344/3095] Split the vol_id from a dev mapping Add a split into the dev mapping to get the right vol_id. Change-Id: I1a7bf6351491b1321c5ca0fa4a27f29825400eaf Closes-Bug: #1514394 --- openstackclient/compute/v2/server.py | 15 +++- openstackclient/tests/compute/v2/fakes.py | 10 +++ .../tests/compute/v2/test_server.py | 73 +++++++++++++++++++ 3 files changed, 94 insertions(+), 4 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index b5e7f004b8..ef9d24e72c 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -425,10 +425,17 @@ def take_action(self, parsed_args): dev_key, dev_vol = dev_map.split('=', 1) block_volume = None if dev_vol: - block_volume = utils.find_resource( - volume_client.volumes, - dev_vol, - ).id + vol = dev_vol.split(':', 1)[0] + if vol: + vol_id = utils.find_resource( + volume_client.volumes, + vol, + ).id + block_volume = dev_vol.replace(vol, vol_id) + else: + msg = _("Volume name or ID must be specified if " + "--block-device-mapping is specified") + raise exceptions.CommandError(msg) block_device_mapping.update({dev_key: block_volume}) nics = [] diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 08eb5afa8e..13db0c01b0 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -20,6 +20,7 @@ from openstackclient.tests.image.v2 import fakes as image_fakes from openstackclient.tests.network.v2 import fakes as network_fakes from openstackclient.tests import utils +from openstackclient.tests.volume.v2 import fakes as volume_fakes server_id = 'serv1' @@ -80,6 +81,8 @@ QUOTA_columns = tuple(sorted(QUOTA)) QUOTA_data = tuple(QUOTA[x] for x in sorted(QUOTA)) +block_device_mapping = 'vda=' + volume_fakes.volume_name + ':::0' + class FakeComputev2Client(object): def __init__(self, **kwargs): @@ -95,6 +98,8 @@ def __init__(self, **kwargs): self.flavors.resource_class = fakes.FakeResource(None, {}) self.quotas = mock.Mock() self.quotas.resource_class = fakes.FakeResource(None, {}) + self.volumes = mock.Mock() + self.volumes.resource_class = fakes.FakeResource(None, {}) self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] @@ -122,3 +127,8 @@ def setUp(self): endpoint=fakes.AUTH_URL, token=fakes.AUTH_TOKEN, ) + + self.app.client_manager.volume = volume_fakes.FakeVolumeClient( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN, + ) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index f420ff0ada..f6f9797acf 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -24,6 +24,7 @@ from openstackclient.tests import fakes from openstackclient.tests.image.v2 import fakes as image_fakes from openstackclient.tests import utils +from openstackclient.tests.volume.v2 import fakes as volume_fakes class TestServer(compute_fakes.TestComputev2): @@ -47,6 +48,10 @@ def setUp(self): self.images_mock = self.app.client_manager.image.images self.images_mock.reset_mock() + # Get a shortcut to the VolumeManager Mock + self.volumes_mock = self.app.client_manager.volume.volumes + self.volumes_mock.reset_mock() + class TestServerCreate(TestServer): @@ -80,6 +85,13 @@ def setUp(self): ) self.flavors_mock.get.return_value = self.flavor + self.volume = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.VOLUME), + loaded=True, + ) + self.volumes_mock.get.return_value = self.volume + # Get the command object to test self.cmd = server.CreateServer(self.app, None) @@ -289,6 +301,67 @@ def test_server_create_userdata(self, mock_open): ) self.assertEqual(datalist, data) + def test_server_create_with_block_device_mapping(self): + arglist = [ + '--image', 'image1', + '--flavor', compute_fakes.flavor_id, + '--block-device-mapping', compute_fakes.block_device_mapping, + compute_fakes.server_name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', compute_fakes.flavor_id), + ('block_device_mapping', [compute_fakes.block_device_mapping]), + ('config_drive', False), + ('server_name', compute_fakes.server_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # CreateServer.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + real_volume_mapping = ( + (compute_fakes.block_device_mapping.split('=', 1)[1]).replace( + volume_fakes.volume_name, + volume_fakes.volume_id)) + + # Set expected values + kwargs = dict( + meta=None, + files={}, + reservation_id=None, + min_count=1, + max_count=1, + security_groups=[], + userdata=None, + key_name=None, + availability_zone=None, + block_device_mapping={ + 'vda': real_volume_mapping + }, + nics=[], + scheduler_hints={}, + config_drive=None, + ) + # ServerManager.create(name, image, flavor, **kwargs) + self.servers_mock.create.assert_called_with( + compute_fakes.server_name, + self.image, + self.flavor, + **kwargs + ) + + collist = ('addresses', 'flavor', 'id', 'name', 'properties') + self.assertEqual(collist, columns) + datalist = ( + '', + 'Large ()', + compute_fakes.server_id, + compute_fakes.server_name, + '', + ) + self.assertEqual(datalist, data) + class TestServerDelete(TestServer): From ed82312bcb831f1ab30e90e50c5708b00fdd23af Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Thu, 12 Nov 2015 17:07:03 +0800 Subject: [PATCH 0345/3095] Trivial: Fix wrong param name in comment. "volume" should be "attachments". Change-Id: Id1e9a733e18db595d8981b5b3a7735313a346787 --- openstackclient/volume/v2/volume.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index f34198dcf3..430d12285a 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -245,7 +245,7 @@ def take_action(self, parsed_args): def _format_attach(attachments): """Return a formatted string of a volume's attached instances - :param volume: a volume.attachments field + :param attachments: a volume.attachments field :rtype: a string of formatted instances """ From cfd2bf5882b72d6dbd09a1839b63ed41f3834365 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Sun, 18 Oct 2015 16:03:08 -0400 Subject: [PATCH 0346/3095] validate non-ascii values for swift properties skip properties that are non-ascii values, but proceed with properties that work. log these failed values back to the user. Change-Id: Iaca8909f4465a01c8aebfd290b1a322823702359 Closes-Bug: 1503898 --- openstackclient/api/object_store_v1.py | 8 ++++++++ openstackclient/common/utils.py | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/openstackclient/api/object_store_v1.py b/openstackclient/api/object_store_v1.py index b1c78d990a..ab75a78c29 100644 --- a/openstackclient/api/object_store_v1.py +++ b/openstackclient/api/object_store_v1.py @@ -14,6 +14,7 @@ """Object Store v1 API Library""" import io +import logging import os import six @@ -25,6 +26,7 @@ from urlparse import urlparse # noqa from openstackclient.api import api +from openstackclient.common import utils class APIv1(api.BaseAPI): @@ -551,8 +553,14 @@ def _set_properties(self, properties, header_tag): # property we use: "X-Add-Container-Meta-Book: MobyDick", and the same # logic applies for Object properties + log = logging.getLogger(__name__ + '._set_properties') + headers = {} for k, v in properties.iteritems(): + if not utils.is_ascii(k) or not utils.is_ascii(v): + log.error('Cannot set property %s to non-ascii value', k) + continue + header_name = header_tag % k headers[header_name] = v return headers diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index 51e2a2f9ca..8db4f35bc8 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -419,3 +419,11 @@ def build_kwargs_dict(arg_name, value): if value: kwargs[arg_name] = value return kwargs + + +def is_ascii(string): + try: + string.decode('ascii') + return True + except UnicodeDecodeError: + return False From 51f2fda0417b391051743038de9f979ed4190127 Mon Sep 17 00:00:00 2001 From: lin-hua-cheng Date: Thu, 12 Nov 2015 16:49:45 -0800 Subject: [PATCH 0347/3095] Add capability to update description of an IdP Change-Id: I854067642bbfde6fdf84b22b9cc1de8afc7767c0 Closes-Bug: #1515815 --- .../command-objects/identity-provider.rst | 5 ++ .../identity/v3/identity_provider.py | 13 ++++- .../identity/v3/test_identity_provider.py | 48 +++++++++++++++++++ 3 files changed, 64 insertions(+), 2 deletions(-) diff --git a/doc/source/command-objects/identity-provider.rst b/doc/source/command-objects/identity-provider.rst index 90f0b4942c..ac46273638 100644 --- a/doc/source/command-objects/identity-provider.rst +++ b/doc/source/command-objects/identity-provider.rst @@ -81,6 +81,7 @@ Set identity provider properties os identity provider set [--remote-id [...] | --remote-id-file ] + [--description ] [--enable | --disable] @@ -94,6 +95,10 @@ Set identity provider properties Name of a file that contains many remote IDs to associate with the identity provider, one per line +.. option:: --description + + Set identity provider description + .. option:: --enable Enable the identity provider diff --git a/openstackclient/identity/v3/identity_provider.py b/openstackclient/identity/v3/identity_provider.py index 27982a9d5c..b6c358f2c5 100644 --- a/openstackclient/identity/v3/identity_provider.py +++ b/openstackclient/identity/v3/identity_provider.py @@ -142,6 +142,11 @@ def get_parser(self, prog_name): metavar='', help='Identity provider to modify', ) + parser.add_argument( + '--description', + metavar='', + help='Set identity provider description', + ) identity_remote_id_provider = parser.add_mutually_exclusive_group() identity_remote_id_provider.add_argument( '--remote-id', @@ -174,8 +179,10 @@ def take_action(self, parsed_args): federation_client = self.app.client_manager.identity.federation # Basic argument checking - if (not parsed_args.enable and not parsed_args.disable and not - parsed_args.remote_id and not parsed_args.remote_id_file): + if (not parsed_args.enable and not parsed_args.disable and + not parsed_args.remote_id and + not parsed_args.remote_id_file and + not parsed_args.description): self.log.error('No changes requested') return (None, None) @@ -190,6 +197,8 @@ def take_action(self, parsed_args): # Setup keyword args for the client kwargs = {} + if parsed_args.description: + kwargs['description'] = parsed_args.description if parsed_args.enable: kwargs['enabled'] = True if parsed_args.disable: diff --git a/openstackclient/tests/identity/v3/test_identity_provider.py b/openstackclient/tests/identity/v3/test_identity_provider.py index cd328c1d4c..bd094ac4ba 100644 --- a/openstackclient/tests/identity/v3/test_identity_provider.py +++ b/openstackclient/tests/identity/v3/test_identity_provider.py @@ -374,6 +374,50 @@ def setUp(self): super(TestIdentityProviderSet, self).setUp() self.cmd = identity_provider.SetIdentityProvider(self.app, None) + def test_identity_provider_set_description(self): + """Set Identity Provider's description. """ + def prepare(self): + """Prepare fake return objects before the test is executed""" + updated_idp = copy.deepcopy(identity_fakes.IDENTITY_PROVIDER) + updated_idp['enabled'] = False + resources = fakes.FakeResource( + None, + updated_idp, + loaded=True + ) + self.identity_providers_mock.update.return_value = resources + + prepare(self) + new_description = 'new desc' + arglist = [ + '--description', new_description, + identity_fakes.idp_id + ] + verifylist = [ + ('identity_provider', identity_fakes.idp_id), + ('description', new_description), + ('enable', False), + ('disable', False), + ('remote_id', None) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.identity_providers_mock.update.assert_called_with( + identity_fakes.idp_id, + description=new_description + ) + + collist = ('description', 'enabled', 'id', 'remote_ids') + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.idp_description, + False, + identity_fakes.idp_id, + identity_fakes.idp_remote_ids + ) + self.assertEqual(datalist, data) + def test_identity_provider_disable(self): """Disable Identity Provider @@ -398,6 +442,7 @@ def prepare(self): ] verifylist = [ ('identity_provider', identity_fakes.idp_id), + ('description', None), ('enable', False), ('disable', True), ('remote_id', identity_fakes.idp_remote_ids) @@ -443,6 +488,7 @@ def prepare(self): ] verifylist = [ ('identity_provider', identity_fakes.idp_id), + ('description', None), ('enable', True), ('disable', False), ('remote_id', identity_fakes.idp_remote_ids) @@ -488,6 +534,7 @@ def prepare(self): ] verifylist = [ ('identity_provider', identity_fakes.idp_id), + ('description', None), ('enable', True), ('disable', False), ('remote_id', [self.new_remote_id]) @@ -533,6 +580,7 @@ def prepare(self): ] verifylist = [ ('identity_provider', identity_fakes.idp_id), + ('description', None), ('enable', True), ('disable', False), ('remote_id_file', self.new_remote_id), From 90d86ef01cb6ce93c3341562c0e0e79da0a6d4ad Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Thu, 12 Nov 2015 23:50:36 +0800 Subject: [PATCH 0348/3095] Enable "openstack server unpause" command to take multiple servers. Current "openstack server unpause" command could only unpause one server. Improve it to be able to handle more than one servers. Also improve the doc to reflect the new feature. Change-Id: I069ebdd6dcd121f6e55c2bf40d42197f93830e0c Implements: blueprint cmd-with-multi-servers --- doc/source/command-objects/server.rst | 6 +++--- doc/source/commands.rst | 2 +- openstackclient/compute/v2/server.py | 14 ++++++++------ 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index 83f83ddc49..211963f3eb 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -658,17 +658,17 @@ Unlock server server unpause -------------- -Unpause server +Unpause server(s) .. program:: server unpause .. code:: bash os server unpause - + [ ...] .. describe:: - Server (name or ID) + Server(s) to unpause (name or ID) server unrescue --------------- diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 9a87709ce9..62ed7652cc 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -187,7 +187,7 @@ Those actions with an opposite action are noted in parens if applicable. * ``stop`` (``start``) - stop one or more servers * ``suspend`` (``resume``) - stop a server and save to disk freeing memory * ``unlock`` (``lock``) - unlock a server -* ``unpause`` (``pause``) - return a paused server to running state +* ``unpause`` (``pause``) - return one or more paused servers to running state * ``unrescue`` (``rescue``) - return a server to normal boot mode * ``unset`` (``set``) - remove an attribute of the object diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index b5e7f004b8..a94857b45c 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1583,7 +1583,7 @@ def take_action(self, parsed_args): class UnpauseServer(command.Command): - """Unpause server""" + """Unpause server(s)""" log = logging.getLogger(__name__ + '.UnpauseServer') @@ -1592,7 +1592,8 @@ def get_parser(self, prog_name): parser.add_argument( 'server', metavar='', - help=_('Server (name or ID)'), + nargs='+', + help=_('Server(s) to unpause (name or ID)'), ) return parser @@ -1600,10 +1601,11 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): compute_client = self.app.client_manager.compute - utils.find_resource( - compute_client.servers, - parsed_args.server, - ).unpause() + for server in parsed_args.server: + utils.find_resource( + compute_client.servers, + server, + ).unpause() class UnrescueServer(command.Command): From c1f0ad6d714b1cb9cb9f9ba25c02243a0c5b7d66 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Fri, 13 Nov 2015 10:25:27 +0800 Subject: [PATCH 0349/3095] Enable "openstack server lock" command to take multiple servers. Current "openstack server lock" command could only lock one server. Improve it to be able to handle more than one servers. Also improve the doc to reflect the new feature. Change-Id: Ifcf103b1c32e6c547ac09f688b887b1c03f92b09 Implements: blueprint cmd-with-multi-servers --- doc/source/command-objects/server.rst | 6 +++--- doc/source/commands.rst | 2 +- openstackclient/compute/v2/server.py | 14 ++++++++------ 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index 211963f3eb..d8b3dbcbaf 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -253,17 +253,17 @@ List servers server lock ----------- -Lock a server. A non-admin user will not be able to execute actions +Lock server(s). A non-admin user will not be able to execute actions .. program:: server lock .. code:: bash os server lock - + [ ...] .. describe:: - Server (name or ID) + Server(s) to lock (name or ID) server migrate -------------- diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 62ed7652cc..ca60ab8e76 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -169,7 +169,7 @@ Those actions with an opposite action are noted in parens if applicable. * ``delete`` (``create``) - delete specific occurrences of the specified objects * ``issue`` (``revoke``) - issue a token * ``list`` - display summary information about multiple objects -* ``lock`` (``unlock``) - lock a server so that non-admin user won't be able to execute actions +* ``lock`` (``unlock``) - lock one or more servers so that non-admin user won't be able to execute actions * ``migrate`` - move a server to a different host; ``--live`` performs a live migration if possible * ``pause`` (``unpause``) - stop one or more servers and leave them in memory diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index a94857b45c..15aff77493 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -798,7 +798,7 @@ def take_action(self, parsed_args): class LockServer(command.Command): - """Lock a server. A non-admin user will not be able to execute actions""" + """Lock server(s). A non-admin user will not be able to execute actions""" log = logging.getLogger(__name__ + '.LockServer') @@ -807,7 +807,8 @@ def get_parser(self, prog_name): parser.add_argument( 'server', metavar='', - help=_('Server (name or ID)'), + nargs='+', + help=_('Server(s) to lock (name or ID)'), ) return parser @@ -815,10 +816,11 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): compute_client = self.app.client_manager.compute - utils.find_resource( - compute_client.servers, - parsed_args.server, - ).lock() + for server in parsed_args.server: + utils.find_resource( + compute_client.servers, + server, + ).lock() # FIXME(dtroyer): Here is what I want, how with argparse/cliff? From 91fbb0e1361fe24273ace0e5f5e7d338aefbe168 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Fri, 13 Nov 2015 11:02:01 +0800 Subject: [PATCH 0350/3095] Enable "openstack server unlock" command to take multiple servers. Current "openstack server unlock" command could only unlock one server. Improve it to be able to handle more than one servers. Also improve the doc to reflect the new feature. Change-Id: Ibf57b2021a504da950a491d63139a438087aed0b Implements: blueprint cmd-with-multi-servers --- doc/source/command-objects/server.rst | 6 +++--- doc/source/commands.rst | 2 +- openstackclient/compute/v2/server.py | 14 ++++++++------ 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index d8b3dbcbaf..4aeef4d056 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -643,17 +643,17 @@ Suspend server server unlock ------------- -Unlock server +Unlock server(s) .. program:: server unlock .. code:: bash os server unlock - + [ ...] .. describe:: - Server (name or ID) + Server(s) to unlock (name or ID) server unpause -------------- diff --git a/doc/source/commands.rst b/doc/source/commands.rst index ca60ab8e76..e69699b173 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -186,7 +186,7 @@ Those actions with an opposite action are noted in parens if applicable. * ``start`` (``stop``) - start one or more servers * ``stop`` (``start``) - stop one or more servers * ``suspend`` (``resume``) - stop a server and save to disk freeing memory -* ``unlock`` (``lock``) - unlock a server +* ``unlock`` (``lock``) - unlock one or more servers * ``unpause`` (``pause``) - return one or more paused servers to running state * ``unrescue`` (``rescue``) - return a server to normal boot mode * ``unset`` (``set``) - remove an attribute of the object diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 15aff77493..22303838b4 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1561,7 +1561,7 @@ def take_action(self, parsed_args): class UnlockServer(command.Command): - """Unlock server""" + """Unlock server(s)""" log = logging.getLogger(__name__ + '.UnlockServer') @@ -1570,7 +1570,8 @@ def get_parser(self, prog_name): parser.add_argument( 'server', metavar='', - help=_('Server (name or ID)'), + nargs='+', + help=_('Server(s) to unlock (name or ID)'), ) return parser @@ -1578,10 +1579,11 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): compute_client = self.app.client_manager.compute - utils.find_resource( - compute_client.servers, - parsed_args.server, - ).unlock() + for server in parsed_args.server: + utils.find_resource( + compute_client.servers, + server, + ).unlock() class UnpauseServer(command.Command): From fc32b0d76bf07cb5bd43f2f9dc0078438a9ae351 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Fri, 6 Nov 2015 17:28:03 +0800 Subject: [PATCH 0351/3095] Improve "server list" command to have the same output as "nova list". "nova list" will also output "Task State" and "Power State" by default. This patch improves "server list" command to have the same columns, but not by default. These two columns will be output if --long is added. The power state is an int, so also adds a formatter helper function to translate it to human readable string, just as "Networks" does. Change-Id: I0530a910bec03835839a5ba7687c66d5643338f3 --- openstackclient/compute/v2/server.py | 45 ++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index b5e7f004b8..1112393141 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -56,6 +56,29 @@ def _format_servers_list_networks(networks): return '; '.join(output) +def _format_servers_list_power_state(state): + """Return a formatted string of a server's power state + + :param state: the power state number of a server + :rtype: a string mapped to the power state number + """ + power_states = [ + 'NOSTATE', # 0x00 + 'Running', # 0x01 + '', # 0x02 + 'Paused', # 0x03 + 'Shutdown', # 0x04 + '', # 0x05 + 'Crashed', # 0x06 + 'Suspended' # 0x07 + ] + + try: + return power_states[state] + except Exception: + return 'N/A' + + def _get_ip_address(addresses, address_type, ip_address_family): # Old style addresses if address_type in addresses: @@ -762,6 +785,8 @@ def take_action(self, parsed_args): 'ID', 'Name', 'Status', + 'OS-EXT-STS:task_state', + 'OS-EXT-STS:power_state', 'Networks', 'OS-EXT-AZ:availability_zone', 'OS-EXT-SRV-ATTR:host', @@ -771,18 +796,32 @@ def take_action(self, parsed_args): 'ID', 'Name', 'Status', + 'Task State', + 'Power State', 'Networks', 'Availability Zone', 'Host', 'Properties', ) mixed_case_fields = [ + 'OS-EXT-STS:task_state', + 'OS-EXT-STS:power_state', 'OS-EXT-AZ:availability_zone', 'OS-EXT-SRV-ATTR:host', ] else: - columns = ('ID', 'Name', 'Status', 'Networks') - column_headers = columns + columns = ( + 'ID', + 'Name', + 'Status', + 'Networks', + ) + column_headers = ( + 'ID', + 'Name', + 'Status', + 'Networks', + ) mixed_case_fields = [] data = compute_client.servers.list(search_opts=search_opts) return (column_headers, @@ -790,6 +829,8 @@ def take_action(self, parsed_args): s, columns, mixed_case_fields=mixed_case_fields, formatters={ + 'OS-EXT-STS:power_state': + _format_servers_list_power_state, 'Networks': _format_servers_list_networks, 'Metadata': utils.format_dict, }, From 471881f85eeb8ea4d251b6816fcf3f5892f60689 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Mon, 16 Nov 2015 19:09:14 -0500 Subject: [PATCH 0352/3095] Add release notes for 1.9.0 Sum up the latest bug fixes for the latest release. Change-Id: I4f6e9e9d5062e5ccb94f088abdc270fc40373d16 --- doc/source/releases.rst | 58 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/doc/source/releases.rst b/doc/source/releases.rst index 2e9f6fff85..148402bbde 100644 --- a/doc/source/releases.rst +++ b/doc/source/releases.rst @@ -2,6 +2,64 @@ Release Notes ============= +1.9.0 (17 Nov 2015) +=================== + +* Several updates to `openstack server` + Blueprint `servers `_ + + * `openstack server start` + * `openstack server stop` + * `openstack server pause` + * `openstack server unpause` + * `openstack server lock` + * `openstack server unlock` + +* Obfuscate passwords when used in debug mode + Bug `1501598 `_ + +* Clean up `identity provider show` + Bug `1478995 `_ + +* Add `description` to `identity provider set` + Bug `1515815 `_ + +* Add `compute service delete` + Bug `1503510 `_ + +* Log a warning when setting non-ascii object store properties + Bug `1503898 `_ + +* Add 'marker' and 'limit' to `openstack flavor list` + Bug `1505874 `_ + +* Remove `url` from `region create` and `region set` + Bug `1506841 `_ + +* `openstack image add project` fails with AttributeError for image v2 + Bug `1509054 `_ + +* Inconsistent output with `openstack usage show` + Bug `1512220 `_ + +* Validate --lines with `openstack console log show` + Bug `1512263 `_ + +* Validate --project does not exist with `openstack quota set` + Bug `1512638 `_ + +* Cannot list servers while --user specified for `openstack server list` + Bug `1513701 `_ + +* Cannot list volumes while --user specified for `openstack volume list` + Bug `1514145 `_ + +* Cannot find volume with --block-device-mapping with `openstack server create` + Bug `1514394 `_ + +* Fix documentation for `credential set` + Bug `1418837 `_ + 1.8.0 (18 Oct 2015) =================== From 63c9ee7dde952e6a22888925ad633f354d31c561 Mon Sep 17 00:00:00 2001 From: Atsushi SAKAI Date: Wed, 18 Nov 2015 13:21:25 +0900 Subject: [PATCH 0353/3095] Remove LICENSE APPENDIX From seeing other OpenStack modules, APPENDIX:How to apply the Apache License to your work. is not written. Change-Id: I6fe0968e2281ef4f60e7c41f5621f552efc8a349 --- LICENSE | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/LICENSE b/LICENSE index d645695673..68c771a099 100644 --- a/LICENSE +++ b/LICENSE @@ -174,29 +174,3 @@ incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - 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 1a8020cc1b7a47ad5c910673ce3d279687bbaa14 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 18 Nov 2015 15:32:36 -0600 Subject: [PATCH 0354/3095] Remove deprecated 'project usage list' command Remove the 'project usage list' command that was deprecated in version 1.0.2 in review Ie08d4f88d71a660fca1862405351109cd0aa86b6. Note that the removed command class is a good example of how to wrap a command and show a deprecation message. Change-Id: I6c750730963615895f5d9953487d2d5a905885a8 --- openstackclient/compute/v2/usage.py | 17 ----------------- setup.cfg | 2 -- 2 files changed, 19 deletions(-) diff --git a/openstackclient/compute/v2/usage.py b/openstackclient/compute/v2/usage.py index 6d5d678fc9..4e7cf10064 100644 --- a/openstackclient/compute/v2/usage.py +++ b/openstackclient/compute/v2/usage.py @@ -196,20 +196,3 @@ def take_action(self, parsed_args): float("%.2f" % usage.total_local_gb_usage) if hasattr(usage, "total_local_gb_usage") else None) return zip(*sorted(six.iteritems(info))) - - -# This is out of order due to the subclass, will eventually be removed - -class ListProjectUsage(ListUsage): - """List resource usage per project""" - - deprecated = True - - log = logging.getLogger('DEPRECATED:') - - def take_action(self, parsed_args): - self.log.warning( - "%s is deprecated, use 'usage list'", - getattr(self, 'cmd_name', 'this command'), - ) - return super(ListProjectUsage, self).take_action(parsed_args) diff --git a/setup.cfg b/setup.cfg index b59b8474f6..d79df17b84 100644 --- a/setup.cfg +++ b/setup.cfg @@ -104,8 +104,6 @@ openstack.compute.v2 = keypair_list = openstackclient.compute.v2.keypair:ListKeypair keypair_show = openstackclient.compute.v2.keypair:ShowKeypair - project_usage_list = openstackclient.compute.v2.usage:ListProjectUsage - security_group_create = openstackclient.compute.v2.security_group:CreateSecurityGroup security_group_delete = openstackclient.compute.v2.security_group:DeleteSecurityGroup security_group_list = openstackclient.compute.v2.security_group:ListSecurityGroup From 71e1adeaf07ec1bf7f2e9bdd0c93ec2c90da85f7 Mon Sep 17 00:00:00 2001 From: Mark Vanderwiel Date: Wed, 18 Nov 2015 16:21:33 -0600 Subject: [PATCH 0355/3095] Allow error status to be specified For some apis, heat, the error status is "failed". This patch changes the wait_for_status method to allow for the error status to be passed in the same way as the success status. Change-Id: I20db4051d3f5611a4b13fe23ea8798b82a40da81 --- openstackclient/common/utils.py | 3 +- openstackclient/tests/common/test_utils.py | 40 ++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index 8db4f35bc8..0a1f8e0e79 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -298,6 +298,7 @@ def wait_for_status(status_f, res_id, status_field='status', success_status=['active'], + error_status=['error'], sleep_time=5, callback=None): """Wait for status change on a resource during a long-running operation @@ -316,7 +317,7 @@ def wait_for_status(status_f, if status in success_status: retval = True break - elif status == 'error': + elif status in error_status: retval = False break if callback: diff --git a/openstackclient/tests/common/test_utils.py b/openstackclient/tests/common/test_utils.py index 373c0de4d8..b564ffab48 100644 --- a/openstackclient/tests/common/test_utils.py +++ b/openstackclient/tests/common/test_utils.py @@ -136,6 +136,46 @@ def test_sort_items_with_invalid_direction(self): utils.sort_items, items, sort_str) + @mock.patch.object(time, 'sleep') + def test_wait_for_status_ok(self, mock_sleep): + # Tests the normal flow that the resource is status=active + resource = mock.MagicMock(status='ACTIVE') + status_f = mock.Mock(return_value=resource) + res_id = str(uuid.uuid4()) + self.assertTrue(utils.wait_for_status(status_f, res_id,)) + self.assertFalse(mock_sleep.called) + + @mock.patch.object(time, 'sleep') + def test_wait_for_status_ok__with_overrides(self, mock_sleep): + # Tests the normal flow that the resource is status=complete + resource = mock.MagicMock(my_status='COMPLETE') + status_f = mock.Mock(return_value=resource) + res_id = str(uuid.uuid4()) + self.assertTrue(utils.wait_for_status(status_f, res_id, + status_field='my_status', + success_status=['complete'])) + self.assertFalse(mock_sleep.called) + + @mock.patch.object(time, 'sleep') + def test_wait_for_status_error(self, mock_sleep): + # Tests that we fail if the resource is status=error + resource = mock.MagicMock(status='ERROR') + status_f = mock.Mock(return_value=resource) + res_id = str(uuid.uuid4()) + self.assertFalse(utils.wait_for_status(status_f, res_id)) + self.assertFalse(mock_sleep.called) + + @mock.patch.object(time, 'sleep') + def test_wait_for_status_error_with_overrides(self, mock_sleep): + # Tests that we fail if the resource is my_status=failed + resource = mock.MagicMock(my_status='FAILED') + status_f = mock.Mock(return_value=resource) + res_id = str(uuid.uuid4()) + self.assertFalse(utils.wait_for_status(status_f, res_id, + status_field='my_status', + error_status=['failed'])) + self.assertFalse(mock_sleep.called) + @mock.patch.object(time, 'sleep') def test_wait_for_delete_ok(self, mock_sleep): # Tests the normal flow that the resource is deleted with a 404 coming From 20bf1ef6757258619678e5ee00b8a2c6d742e1f1 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 14 Nov 2015 14:38:12 +0800 Subject: [PATCH 0356/3095] Enable FakeResource to fake methods. Use MagicMock to fake a method in FakeResource. A new function: add_method(name, return_value) is added to FakeResource. The caller specifies method @name and @return_value, the function will add an attribute with @name, which is a callable MagicMock object whose return value is @return_value. When user access the attribute with a (), @return_value will be returned by MagicMock, which looks like a function call. Change-Id: I12eb876cbebab064773df7b5dd612de69bbf3f01 Implements: blueprint osc-unit-test-framework-improvement --- openstackclient/tests/fakes.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/openstackclient/tests/fakes.py b/openstackclient/tests/fakes.py index 0f5ef74aa0..9f4dcc50b5 100644 --- a/openstackclient/tests/fakes.py +++ b/openstackclient/tests/fakes.py @@ -14,6 +14,7 @@ # import json +import mock import six import sys @@ -122,17 +123,41 @@ def __init__(self, name, version): class FakeResource(object): - def __init__(self, manager, info, loaded=False): + def __init__(self, manager=None, info={}, loaded=False, methods={}): + """Set attributes and methods for a resource. + + :param manager: + The resource manager + :param Dictionary info: + A dictionary with all attributes + :param bool loaded: + True if the resource is loaded in memory + :param Dictionary methods: + A dictionary with all methods + """ self.__name__ = type(self).__name__ self.manager = manager self._info = info self._add_details(info) + self._add_methods(methods) self._loaded = loaded def _add_details(self, info): for (k, v) in six.iteritems(info): setattr(self, k, v) + def _add_methods(self, methods): + """Fake methods with MagicMock objects. + + For each <@key, @value> pairs in methods, add an callable MagicMock + object named @key as an attribute, and set the mock's return_value to + @value. When users access the attribute with (), @value will be + returned, which looks like a function call. + """ + for (name, ret) in six.iteritems(methods): + method = mock.MagicMock(return_value=ret) + setattr(self, name, method) + def __repr__(self): reprkeys = sorted(k for k in self.__dict__.keys() if k[0] != '_' and k != 'manager') From b1cc7fb4f6b6fd1844ead784978d9f5dae0e81f5 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Thu, 12 Nov 2015 11:25:59 +0800 Subject: [PATCH 0357/3095] Introduce random server faking mechanism. This patch introduces a new server faking mechanism to support multiple servers faking. Server names and ids can be generated randomly, and use APIs in class FakeServer to get one or more servers. Change-Id: Ic54f3bf7c77294dc7dfb9acdbf4a721eb5eef6af Implements: blueprint osc-unit-test-framework-improvement --- openstackclient/tests/fakes.py | 70 ++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/openstackclient/tests/fakes.py b/openstackclient/tests/fakes.py index 9f4dcc50b5..85e65fb198 100644 --- a/openstackclient/tests/fakes.py +++ b/openstackclient/tests/fakes.py @@ -13,10 +13,12 @@ # under the License. # +import copy import json import mock import six import sys +import uuid from keystoneauth1 import fixture import requests @@ -183,3 +185,71 @@ def __getattr__(self, key): return self[key] except KeyError: raise AttributeError(key) + + +class FakeServer(object): + """Fake one or more compute servers.""" + + @staticmethod + def create_one_server(attrs={}, methods={}): + """Create a fake server. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :return: + A FakeResource object, with id, name, metadata + """ + # Set default attributes. + server_info = { + 'id': 'server-id-' + uuid.uuid4().hex, + 'name': 'server-name-' + uuid.uuid4().hex, + 'metadata': {}, + } + + # Overwrite default attributes. + server_info.update(attrs) + + server = FakeResource(info=copy.deepcopy(server_info), + methods=methods, + loaded=True) + return server + + @staticmethod + def create_servers(attrs={}, methods={}, count=2): + """Create multiple fake servers. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :param int count: + The number of servers to fake + :return: + A list of FakeResource objects faking the servers + """ + servers = [] + for i in range(0, count): + servers.append(FakeServer.create_one_server(attrs, methods)) + + return servers + + @staticmethod + def get_servers(servers=None, count=2): + """Get an iterable MagicMock object with a list of faked servers. + + If servers list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List servers: + A list of FakeResource objects faking servers + :param int count: + The number of servers to fake + :return: + An iterable Mock object with side_effect set to a list of faked + servers + """ + if servers is None: + servers = FakeServer.create_servers(count) + return mock.MagicMock(side_effect=servers) From 5c0959c4fedf541fa40d543f17b9e97dedabab9d Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Thu, 12 Nov 2015 13:22:06 +0800 Subject: [PATCH 0358/3095] Add unit tests for "server pause" command. This patch adds unit tests for "server pause" command, including one and multiple servers. Change-Id: If5551e77d7dd4f7f48c6ee4a7f80f8313817f492 Implements: blueprint osc-unit-test-framework-improvement --- .../tests/compute/v2/test_server.py | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index f6f9797acf..791a90abd1 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -528,6 +528,63 @@ def test_server_image_create_name(self): self.assertEqual(datalist, data) +class TestServerPause(TestServer): + + def setUp(self): + super(TestServerPause, self).setUp() + + # Get the command object to test + self.cmd = server.PauseServer(self.app, None) + + # Set methods to be tested. + self.methods = { + 'pause': None, + } + + def setup_servers_mock(self, count=1): + servers = fakes.FakeServer.create_servers(methods=self.methods, + count=count) + + # This is the return value for utils.find_resource() + self.servers_mock.get = fakes.FakeServer.get_servers(servers, 1) + + return servers + + def test_server_pause_one_server(self): + servers = self.setup_servers_mock(1) + + arglist = [ + servers[0].id, + ] + verifylist = [ + ('server', [servers[0].id]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + self.cmd.take_action(parsed_args) + + servers[0].pause.assert_called_with() + + def test_server_pause_multi_servers(self): + servers = self.setup_servers_mock(3) + arglist = [] + verifylist = [] + + for i in range(0, len(servers)): + arglist.append(servers[i].id) + verifylist = [ + ('server', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + self.cmd.take_action(parsed_args) + + for i in range(0, len(servers)): + servers[i].pause.assert_called_with() + + class TestServerResize(TestServer): def setUp(self): From c0cc53807754b5957e84d159f70fe42407d77f29 Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Fri, 21 Aug 2015 11:05:36 -0600 Subject: [PATCH 0359/3095] Have configuration tests support OCC Change-Id: Ia8b0e5672e2e6cf6a37582bf231385aafda8836d --- functional/tests/common/test_configuration.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/functional/tests/common/test_configuration.py b/functional/tests/common/test_configuration.py index f4a0209973..801ee10dfc 100644 --- a/functional/tests/common/test_configuration.py +++ b/functional/tests/common/test_configuration.py @@ -30,9 +30,15 @@ def test_configuration_show(self): def test_configuration_show_unmask(self): raw_output = self.openstack('configuration show --unmask ' + self.opts) - passwd = os.environ['OS_PASSWORD'] - self.assertOutput(passwd + '\n', raw_output) + # If we are using os-client-config, this will not be set. Rather than + # parse clouds.yaml to get the right value, just make sure + # we are not getting redacted. + passwd = os.environ.get('OS_PASSWORD') + if passwd: + self.assertEqual(passwd + '\n', raw_output) + else: + self.assertNotEqual(configuration.REDACTED + '\n', raw_output) def test_configuration_show_mask(self): raw_output = self.openstack('configuration show --mask ' + self.opts) - self.assertOutput(configuration.REDACTED + '\n', raw_output) + self.assertEqual(configuration.REDACTED + '\n', raw_output) From a7ecec2a50532d29f48a2f24c56e72043d1c204e Mon Sep 17 00:00:00 2001 From: NiallBunting Date: Mon, 28 Sep 2015 15:52:12 +0000 Subject: [PATCH 0360/3095] Add --volume to Image `create` This was previously part of the command, so added it in for backwards compatibility. This adds the --volume command and the complimentary --force command allowing users to create images from volumes. It seems it may not be possible to add it to image set v2. Change-Id: Ica36e70989f75d80959af3227f66708758fae68d --- openstackclient/image/v2/image.py | 46 +++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 7d8b14124a..2b3f3bfd18 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -116,7 +116,6 @@ class CreateImage(show.ShowOne): def get_parser(self, prog_name): parser = super(CreateImage, self).get_parser(prog_name) - # TODO(mordred): add --volume and --force parameters and support # TODO(bunting): There are additional arguments that v1 supported # that v2 either doesn't support or supports weirdly. # --checksum - could be faked clientside perhaps? @@ -166,6 +165,19 @@ def get_parser(self, prog_name): metavar="", help="Upload image from local file", ) + parser.add_argument( + "--volume", + metavar="", + help="Create image from a volume", + ) + parser.add_argument( + "--force", + dest='force', + action='store_true', + default=False, + help="Force image creation if volume is in use " + "(only meaningful with --volume)", + ) protected_group = parser.add_mutually_exclusive_group() protected_group.add_argument( "--protected", @@ -241,6 +253,7 @@ def take_action(self, parsed_args): if getattr(parsed_args, 'properties', None): for k, v in six.iteritems(parsed_args.properties): kwargs[k] = str(v) + # Handle exclusive booleans with care # Avoid including attributes in kwargs if an option is not # present on the command line. These exclusive booleans are not @@ -259,12 +272,33 @@ def take_action(self, parsed_args): # open the file first to ensure any failures are handled before the # image is created fp = gc_utils.get_data_file(parsed_args) + info = {} + if fp is not None and parsed_args.volume: + raise exceptions.CommandError("Uploading data and using container " + "are not allowed at the same time") if fp is None and parsed_args.file: self.log.warning("Failed to get an image file.") return {}, {} - image = image_client.images.create(**kwargs) + # If a volume is specified. + if parsed_args.volume: + volume_client = self.app.client_manager.volume + source_volume = utils.find_resource( + volume_client.volumes, + parsed_args.volume, + ) + response, body = volume_client.volumes.upload_to_image( + source_volume.id, + parsed_args.force, + parsed_args.name, + parsed_args.container_format, + parsed_args.disk_format, + ) + info = body['os-volume_upload_image'] + info['volume_type'] = info['volume_type']['name'] + else: + image = image_client.images.create(**kwargs) if fp is not None: with fp: @@ -285,7 +319,9 @@ def take_action(self, parsed_args): # update the image after the data has been uploaded image = image_client.images.get(image.id) - info = _format_image(image) + if not info: + info = _format_image(image) + return zip(*sorted(six.iteritems(info))) @@ -535,8 +571,8 @@ def get_parser(self, prog_name): # --location - maybe location add? # --copy-from - does not exist in v2 # --file - should be able to upload file - # --volume - needs adding - # --force - needs adding + # --volume - not possible with v2 as can't change id + # --force - see `--volume` # --checksum - maybe could be done client side # --stdin - could be implemented parser.add_argument( From 99498b0ab3f8a131fca3663f170d746d8bb090ae Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Fri, 20 Nov 2015 12:49:17 -0600 Subject: [PATCH 0361/3095] Unable to set some compute quotas The OpenStackClient mapping of 'openstack quota set' arguments isn't correct for compute quota items that have to different names. For example, the --injected-file-size argument is mapped to injected_file_size, but the compute quotas item is actually injected_file_content_bytes. This incorrect mapping prevented the impacted compute quota items from being set. The problem impacts the following 'openstack quota set' arguments: --injected-file-size --injected-path-size --properties --secgroup-rules --secgroups This patch set also expands the compute quota unit tests to verify all compute quota items that can be set. Change-Id: I0a2f241e425f4811e4ae55be183ac0c8b0805c2a Closes-Bug: #1475831 --- openstackclient/common/quota.py | 1 + openstackclient/tests/common/test_quota.py | 26 ++++++++++++++++++++++ openstackclient/tests/compute/v2/fakes.py | 16 +++++++++++++ 3 files changed, 43 insertions(+) diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index b5d4eb8772..c5404f0734 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -81,6 +81,7 @@ def get_parser(self, prog_name): parser.add_argument( '--%s' % v, metavar='<%s>' % v, + dest=k, type=int, help='New value for the %s quota' % v, ) diff --git a/openstackclient/tests/common/test_quota.py b/openstackclient/tests/common/test_quota.py index 047ef3433f..ff711a7577 100644 --- a/openstackclient/tests/common/test_quota.py +++ b/openstackclient/tests/common/test_quota.py @@ -92,14 +92,31 @@ def test_quota_set(self): '--floating-ips', str(compute_fakes.floating_ip_num), '--fixed-ips', str(compute_fakes.fix_ip_num), '--injected-files', str(compute_fakes.injected_file_num), + '--injected-file-size', str(compute_fakes.injected_file_size_num), + '--injected-path-size', str(compute_fakes.injected_path_size_num), '--key-pairs', str(compute_fakes.key_pair_num), + '--cores', str(compute_fakes.core_num), + '--ram', str(compute_fakes.ram_num), + '--instances', str(compute_fakes.instance_num), + '--properties', str(compute_fakes.property_num), + '--secgroup-rules', str(compute_fakes.secgroup_rule_num), + '--secgroups', str(compute_fakes.secgroup_num), identity_fakes.project_name, ] verifylist = [ ('floating_ips', compute_fakes.floating_ip_num), ('fixed_ips', compute_fakes.fix_ip_num), ('injected_files', compute_fakes.injected_file_num), + ('injected_file_content_bytes', + compute_fakes.injected_file_size_num), + ('injected_file_path_bytes', compute_fakes.injected_path_size_num), ('key_pairs', compute_fakes.key_pair_num), + ('cores', compute_fakes.core_num), + ('ram', compute_fakes.ram_num), + ('instances', compute_fakes.instance_num), + ('metadata_items', compute_fakes.property_num), + ('security_group_rules', compute_fakes.secgroup_rule_num), + ('security_groups', compute_fakes.secgroup_num), ('project', identity_fakes.project_name), ] @@ -111,7 +128,16 @@ def test_quota_set(self): 'floating_ips': compute_fakes.floating_ip_num, 'fixed_ips': compute_fakes.fix_ip_num, 'injected_files': compute_fakes.injected_file_num, + 'injected_file_content_bytes': + compute_fakes.injected_file_size_num, + 'injected_file_path_bytes': compute_fakes.injected_path_size_num, 'key_pairs': compute_fakes.key_pair_num, + 'cores': compute_fakes.core_num, + 'ram': compute_fakes.ram_num, + 'instances': compute_fakes.instance_num, + 'metadata_items': compute_fakes.property_num, + 'security_group_rules': compute_fakes.secgroup_rule_num, + 'security_groups': compute_fakes.secgroup_num, } self.quotas_mock.update.assert_called_with( diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 13db0c01b0..94d1f6aa07 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -68,14 +68,30 @@ floating_ip_num = 100 fix_ip_num = 100 injected_file_num = 100 +injected_file_size_num = 10240 +injected_path_size_num = 255 key_pair_num = 100 +core_num = 20 +ram_num = 51200 +instance_num = 10 +property_num = 128 +secgroup_rule_num = 20 +secgroup_num = 10 project_name = 'project_test' QUOTA = { 'project': project_name, 'floating-ips': floating_ip_num, 'fix-ips': fix_ip_num, 'injected-files': injected_file_num, + 'injected-file-size': injected_file_size_num, + 'injected-path-size': injected_path_size_num, 'key-pairs': key_pair_num, + 'cores': core_num, + 'ram': ram_num, + 'instances': instance_num, + 'properties': property_num, + 'secgroup_rules': secgroup_rule_num, + 'secgroups': secgroup_num, } QUOTA_columns = tuple(sorted(QUOTA)) From 319fc09430d3e0ba7a3d7895d1d3a483edfcdc1b Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 21 Nov 2015 19:02:32 +0800 Subject: [PATCH 0362/3095] Trivial: Remove doc for non-existing param in format_dict(). There is no parameter named format. Change-Id: I286006430efb2850b978b6f2abaed87216156d12 --- openstackclient/common/utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index 0a1f8e0e79..91b1e05aa1 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -145,7 +145,6 @@ def format_dict(data): """Return a formatted string of key value pairs :param data: a dict - :param format: optional formatting hints :rtype: a string formatted to key='value' """ From 5e461765404d3225a6c1c9c81b7e68e24299cc87 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 20 Nov 2015 10:36:40 -0600 Subject: [PATCH 0363/3095] Add command wrapper doc This is a follow-up to I6c750730963615895f5d9953487d2d5a905885a8 that removed a command deprecation warning wrapper. This documents the technique for later use. Change-Id: Ieaa1e6b7eed4e5b037b4bfb6cf488e1290fc69f7 --- doc/source/command-wrappers.rst | 52 +++++++++++++++++++++++++++++++++ doc/source/index.rst | 1 + 2 files changed, 53 insertions(+) create mode 100644 doc/source/command-wrappers.rst diff --git a/doc/source/command-wrappers.rst b/doc/source/command-wrappers.rst new file mode 100644 index 0000000000..b14eccdde8 --- /dev/null +++ b/doc/source/command-wrappers.rst @@ -0,0 +1,52 @@ +====================== +Command Class Wrappers +====================== + +When we want to deprecate a command, policy says we need to alert the user. +We do this with a message logged at WARNING level before any command output +is emitted. + +OpenStackClient command classes are derived from the ``cliff`` classes. +Cliff uses ``setuptools`` entry points for dispatching the parsed command +to the respective handler classes. This lends itself to modifying the +command execution at run-time. + +The obvious approach to adding the deprecation message would be to just add +the message to the command class ``take_action()`` method directly. But then +the various deprecations are scattered throughout the code base. If we +instead wrap the deprecated command class with a new class we can put all of +the wrappers into a separate, dedicated module. This also lets us leave the +original class unmodified and puts all of the deprecation bits in one place. + +This is an example of a minimal wrapper around a command class that logs a +deprecation message as a warning to the user then calls the original class. + +* Subclass the deprecated command. + +* Set class attribute ``deprecated`` to ``True`` to signal cliff to not + emit help text for this command. + +* Log the deprecation message at WARNING level and refer to the replacement + for the deprecated command in the log warning message. + +* Change the entry point class in ``setup.cfg`` to point to the new class. + +Example Deprecation Class +------------------------- + +.. code-block: python + + class ListFooOld(ListFoo): + """List resources""" + + # This notifies cliff to not display the help for this command + deprecated = True + + log = logging.getLogger('deprecated') + + def take_action(self, parsed_args): + self.log.warning( + "%s is deprecated, use 'foobar list'", + getattr(self, 'cmd_name', 'this command'), + ) + return super(ListFooOld, self).take_action(parsed_args) diff --git a/doc/source/index.rst b/doc/source/index.rst index 6b7d63f36e..255474721e 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -38,6 +38,7 @@ Developer Documentation developing command-options + command-wrappers Project Goals ------------- From 815cd8a1991b88511e6a6afca926a3c104e9e7a8 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 21 Nov 2015 13:33:20 +0800 Subject: [PATCH 0364/3095] Use class FakeServer in TestServerCreate. In the current TestServerCreate, there are several problems: 1. The fake create() returns a server with no 'networks' field. The new_server is used to fake the created server which is supposed to be returned by create(), but it has a 'networks' field. They have the same name and id, but they are actually not the same server. As a result, when checking the return value from create(), 'networks' is not checked. 2. The fake server is not accessable in the test functions. So each time a test function wants to get the server name or id, it has to use the constants defined in compute_fakes. This is not good. We should make the fake server accessable in all test functions to ensure they actually get the same server. This patch fix them both by using the new class FakeServer to fake a server. Change-Id: I8ffc8e233f8710034329ed33fccb2c734898ec2d Implements: blueprint osc-unit-test-framework-improvement --- .../tests/compute/v2/test_server.py | 106 +++++++++++------- 1 file changed, 68 insertions(+), 38 deletions(-) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 791a90abd1..b61d11b001 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -58,18 +58,16 @@ class TestServerCreate(TestServer): def setUp(self): super(TestServerCreate, self).setUp() - self.servers_mock.create.return_value = fakes.FakeResource( - None, - copy.deepcopy(compute_fakes.SERVER), - loaded=True, - ) - new_server = fakes.FakeResource( - None, - copy.deepcopy(compute_fakes.SERVER), - loaded=True, - ) - new_server.__dict__['networks'] = {} - self.servers_mock.get.return_value = new_server + attrs = { + 'networks': {}, + } + self.new_server = fakes.FakeServer.create_one_server(attrs=attrs) + + # This is the return value for utils.find_resource(). + # This is for testing --wait option. + self.servers_mock.get.return_value = self.new_server + + self.servers_mock.create.return_value = self.new_server self.image = fakes.FakeResource( None, @@ -97,10 +95,10 @@ def setUp(self): def test_server_create_no_options(self): arglist = [ - compute_fakes.server_name, + self.new_server.name, ] verifylist = [ - ('server_name', compute_fakes.server_name), + ('server_name', self.new_server.name), ] try: # Missing required args should bail here @@ -112,13 +110,13 @@ def test_server_create_minimal(self): arglist = [ '--image', 'image1', '--flavor', 'flavor1', - compute_fakes.server_name, + self.new_server.name, ] verifylist = [ ('image', 'image1'), ('flavor', 'flavor1'), ('config_drive', False), - ('server_name', compute_fakes.server_name), + ('server_name', self.new_server.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -143,19 +141,27 @@ def test_server_create_minimal(self): ) # ServerManager.create(name, image, flavor, **kwargs) self.servers_mock.create.assert_called_with( - compute_fakes.server_name, + self.new_server.name, self.image, self.flavor, **kwargs ) - collist = ('addresses', 'flavor', 'id', 'name', 'properties') + collist = ( + 'addresses', + 'flavor', + 'id', + 'name', + 'networks', + 'properties', + ) self.assertEqual(collist, columns) datalist = ( '', 'Large ()', - compute_fakes.server_id, - compute_fakes.server_name, + self.new_server.id, + self.new_server.name, + self.new_server.networks, '', ) self.assertEqual(datalist, data) @@ -166,14 +172,14 @@ def test_server_create_with_network(self): '--flavor', 'flavor1', '--nic', 'net-id=net1', '--nic', 'port-id=port1', - compute_fakes.server_name, + self.new_server.name, ] verifylist = [ ('image', 'image1'), ('flavor', 'flavor1'), ('nic', ['net-id=net1', 'port-id=port1']), ('config_drive', False), - ('server_name', compute_fakes.server_name), + ('server_name', self.new_server.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -219,19 +225,27 @@ def test_server_create_with_network(self): ) # ServerManager.create(name, image, flavor, **kwargs) self.servers_mock.create.assert_called_with( - compute_fakes.server_name, + self.new_server.name, self.image, self.flavor, **kwargs ) - collist = ('addresses', 'flavor', 'id', 'name', 'properties') + collist = ( + 'addresses', + 'flavor', + 'id', + 'name', + 'networks', + 'properties', + ) self.assertEqual(collist, columns) datalist = ( '', 'Large ()', - compute_fakes.server_id, - compute_fakes.server_name, + self.new_server.id, + self.new_server.name, + self.new_server.networks, '', ) self.assertEqual(datalist, data) @@ -246,14 +260,14 @@ def test_server_create_userdata(self, mock_open): '--image', 'image1', '--flavor', 'flavor1', '--user-data', 'userdata.sh', - compute_fakes.server_name, + self.new_server.name, ] verifylist = [ ('image', 'image1'), ('flavor', 'flavor1'), ('user_data', 'userdata.sh'), ('config_drive', False), - ('server_name', compute_fakes.server_name), + ('server_name', self.new_server.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -284,19 +298,27 @@ def test_server_create_userdata(self, mock_open): ) # ServerManager.create(name, image, flavor, **kwargs) self.servers_mock.create.assert_called_with( - compute_fakes.server_name, + self.new_server.name, self.image, self.flavor, **kwargs ) - collist = ('addresses', 'flavor', 'id', 'name', 'properties') + collist = ( + 'addresses', + 'flavor', + 'id', + 'name', + 'networks', + 'properties', + ) self.assertEqual(collist, columns) datalist = ( '', 'Large ()', - compute_fakes.server_id, - compute_fakes.server_name, + self.new_server.id, + self.new_server.name, + self.new_server.networks, '', ) self.assertEqual(datalist, data) @@ -306,14 +328,14 @@ def test_server_create_with_block_device_mapping(self): '--image', 'image1', '--flavor', compute_fakes.flavor_id, '--block-device-mapping', compute_fakes.block_device_mapping, - compute_fakes.server_name, + self.new_server.name, ] verifylist = [ ('image', 'image1'), ('flavor', compute_fakes.flavor_id), ('block_device_mapping', [compute_fakes.block_device_mapping]), ('config_drive', False), - ('server_name', compute_fakes.server_name), + ('server_name', self.new_server.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -345,19 +367,27 @@ def test_server_create_with_block_device_mapping(self): ) # ServerManager.create(name, image, flavor, **kwargs) self.servers_mock.create.assert_called_with( - compute_fakes.server_name, + self.new_server.name, self.image, self.flavor, **kwargs ) - collist = ('addresses', 'flavor', 'id', 'name', 'properties') + collist = ( + 'addresses', + 'flavor', + 'id', + 'name', + 'networks', + 'properties', + ) self.assertEqual(collist, columns) datalist = ( '', 'Large ()', - compute_fakes.server_id, - compute_fakes.server_name, + self.new_server.id, + self.new_server.name, + self.new_server.networks, '', ) self.assertEqual(datalist, data) From 1cf5c5525b2402cfa8ab84103f1849dde6d5ce1a Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 21 Nov 2015 16:10:48 +0800 Subject: [PATCH 0365/3095] Use class FakeServer in TestServerDelete. There are the same problems with TestServerCreate in TestServerDelete. Use the new class FakeServer to fix them. Change-Id: Icdcc90cc93ed1080187fb0edca885b0db56ab35d Implements: blueprint osc-unit-test-framework-improvement --- .../tests/compute/v2/test_server.py | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index b61d11b001..b5fa8a8c46 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -398,12 +398,11 @@ class TestServerDelete(TestServer): def setUp(self): super(TestServerDelete, self).setUp() + self.server = fakes.FakeServer.create_one_server() + # This is the return value for utils.find_resource() - self.servers_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(compute_fakes.SERVER), - loaded=True, - ) + self.servers_mock.get.return_value = self.server + self.servers_mock.delete.return_value = None # Get the command object to test @@ -411,10 +410,10 @@ def setUp(self): def test_server_delete_no_options(self): arglist = [ - compute_fakes.server_id, + self.server.id, ] verifylist = [ - ('server', [compute_fakes.server_id]), + ('server', [self.server.id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -422,16 +421,16 @@ def test_server_delete_no_options(self): self.cmd.take_action(parsed_args) self.servers_mock.delete.assert_called_with( - compute_fakes.server_id, + self.server.id, ) @mock.patch.object(common_utils, 'wait_for_delete', return_value=True) def test_server_delete_wait_ok(self, mock_wait_for_delete): arglist = [ - compute_fakes.server_id, '--wait' + self.server.id, '--wait' ] verifylist = [ - ('server', [compute_fakes.server_id]), + ('server', [self.server.id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -439,22 +438,22 @@ def test_server_delete_wait_ok(self, mock_wait_for_delete): self.cmd.take_action(parsed_args) self.servers_mock.delete.assert_called_with( - compute_fakes.server_id, + self.server.id, ) mock_wait_for_delete.assert_called_once_with( self.servers_mock, - compute_fakes.server_id, + self.server.id, callback=server._show_progress ) @mock.patch.object(common_utils, 'wait_for_delete', return_value=False) def test_server_delete_wait_fails(self, mock_wait_for_delete): arglist = [ - compute_fakes.server_id, '--wait' + self.server.id, '--wait' ] verifylist = [ - ('server', [compute_fakes.server_id]), + ('server', [self.server.id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -462,12 +461,12 @@ def test_server_delete_wait_fails(self, mock_wait_for_delete): self.assertRaises(SystemExit, self.cmd.take_action, parsed_args) self.servers_mock.delete.assert_called_with( - compute_fakes.server_id, + self.server.id, ) mock_wait_for_delete.assert_called_once_with( self.servers_mock, - compute_fakes.server_id, + self.server.id, callback=server._show_progress ) From 588d73461e9c9a78fd89781df4468d6d88f6ce7f Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 21 Nov 2015 16:19:45 +0800 Subject: [PATCH 0366/3095] Use class FakeServer in TestServerImageCreate. There are the same problems with TestServerCreate in TestServerImageCreate. Use the new class FakeServer to fix them. Change-Id: Ie723fa95620549f09a81ef72953f46877ef9252a Implements: blueprint osc-unit-test-framework-improvement --- .../tests/compute/v2/test_server.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index b5fa8a8c46..8d66d27f9d 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -476,12 +476,10 @@ class TestServerImageCreate(TestServer): def setUp(self): super(TestServerImageCreate, self).setUp() + self.server = fakes.FakeServer.create_one_server() + # This is the return value for utils.find_resource() - self.servers_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(compute_fakes.SERVER), - loaded=True, - ) + self.servers_mock.get.return_value = self.server self.servers_mock.create_image.return_value = image_fakes.image_id @@ -496,10 +494,10 @@ def setUp(self): def test_server_image_create_no_options(self): arglist = [ - compute_fakes.server_id, + self.server.id, ] verifylist = [ - ('server', compute_fakes.server_id), + ('server', self.server.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -509,7 +507,7 @@ def test_server_image_create_no_options(self): # ServerManager.create_image(server, image_name, metadata=) self.servers_mock.create_image.assert_called_with( self.servers_mock.get.return_value, - compute_fakes.server_name, + self.server.name, ) collist = ('id', 'name', 'owner', 'protected', 'tags', 'visibility') @@ -527,11 +525,11 @@ def test_server_image_create_no_options(self): def test_server_image_create_name(self): arglist = [ '--name', 'img-nam', - compute_fakes.server_id, + self.server.id, ] verifylist = [ ('name', 'img-nam'), - ('server', compute_fakes.server_id), + ('server', self.server.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) From c9041622ef387b5710648b58b8ff530b353d88da Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 21 Nov 2015 16:27:25 +0800 Subject: [PATCH 0367/3095] Use class FakeServer in TestServerResize. There are the same problems with TestServerCreate in TestServerResize. Use the new class FakeServer to fix them. Change-Id: Ibde3e68a7bc55bbbf8357ba98be2559a6d0d41b6 Implements: blueprint osc-unit-test-framework-improvement --- .../tests/compute/v2/test_server.py | 39 +++++++++---------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 8d66d27f9d..940a4f1207 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -617,13 +617,10 @@ class TestServerResize(TestServer): def setUp(self): super(TestServerResize, self).setUp() + self.server = fakes.FakeServer.create_one_server() + # This is the return value for utils.find_resource() - self.servers_get_return_value = fakes.FakeResource( - None, - copy.deepcopy(compute_fakes.SERVER), - loaded=True, - ) - self.servers_mock.get.return_value = self.servers_get_return_value + self.servers_mock.get.return_value = self.server self.servers_mock.resize.return_value = None self.servers_mock.confirm_resize.return_value = None @@ -642,12 +639,12 @@ def setUp(self): def test_server_resize_no_options(self): arglist = [ - compute_fakes.server_id, + self.server.id, ] verifylist = [ ('confirm', False), ('revert', False), - ('server', compute_fakes.server_id), + ('server', self.server.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -655,7 +652,7 @@ def test_server_resize_no_options(self): self.cmd.take_action(parsed_args) self.servers_mock.get.assert_called_with( - compute_fakes.server_id, + self.server.id, ) self.assertNotCalled(self.servers_mock.resize) @@ -665,13 +662,13 @@ def test_server_resize_no_options(self): def test_server_resize(self): arglist = [ '--flavor', compute_fakes.flavor_id, - compute_fakes.server_id, + self.server.id, ] verifylist = [ ('flavor', compute_fakes.flavor_id), ('confirm', False), ('revert', False), - ('server', compute_fakes.server_id), + ('server', self.server.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -679,14 +676,14 @@ def test_server_resize(self): self.cmd.take_action(parsed_args) self.servers_mock.get.assert_called_with( - compute_fakes.server_id, + self.server.id, ) self.flavors_mock.get.assert_called_with( compute_fakes.flavor_id, ) self.servers_mock.resize.assert_called_with( - self.servers_get_return_value, + self.server, self.flavors_get_return_value, ) self.assertNotCalled(self.servers_mock.confirm_resize) @@ -695,12 +692,12 @@ def test_server_resize(self): def test_server_resize_confirm(self): arglist = [ '--confirm', - compute_fakes.server_id, + self.server.id, ] verifylist = [ ('confirm', True), ('revert', False), - ('server', compute_fakes.server_id), + ('server', self.server.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -708,24 +705,24 @@ def test_server_resize_confirm(self): self.cmd.take_action(parsed_args) self.servers_mock.get.assert_called_with( - compute_fakes.server_id, + self.server.id, ) self.assertNotCalled(self.servers_mock.resize) self.servers_mock.confirm_resize.assert_called_with( - self.servers_get_return_value, + self.server, ) self.assertNotCalled(self.servers_mock.revert_resize) def test_server_resize_revert(self): arglist = [ '--revert', - compute_fakes.server_id, + self.server.id, ] verifylist = [ ('confirm', False), ('revert', True), - ('server', compute_fakes.server_id), + ('server', self.server.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -733,13 +730,13 @@ def test_server_resize_revert(self): self.cmd.take_action(parsed_args) self.servers_mock.get.assert_called_with( - compute_fakes.server_id, + self.server.id, ) self.assertNotCalled(self.servers_mock.resize) self.assertNotCalled(self.servers_mock.confirm_resize) self.servers_mock.revert_resize.assert_called_with( - self.servers_get_return_value, + self.server, ) From a6291663ffd316094275f7d3f166e01b7af27565 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 21 Nov 2015 16:31:15 +0800 Subject: [PATCH 0368/3095] Remove the old fake server data. The old fake server framework has be replaced with the new class FakeServer, which is stronger in: 1. faking multiple servers with random names and ids 2. faking methods in a server 3. easier to use So remove the old fake server data, and use class FakeServer from now on. Change-Id: Ife8ee37a7ce14d9a3201104bce8075a918a97613 Implements: blueprint osc-unit-test-framework-improvement --- openstackclient/tests/compute/v2/fakes.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 13db0c01b0..fcbea6fd45 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -23,17 +23,8 @@ from openstackclient.tests.volume.v2 import fakes as volume_fakes -server_id = 'serv1' -server_name = 'waiter' - service_id = '1' -SERVER = { - 'id': server_id, - 'name': server_name, - 'metadata': {}, -} - extension_name = 'Multinic' extension_namespace = 'http://docs.openstack.org/compute/ext/'\ 'multinic/api/v1.1' From 46e061785f06c64161f3a87b3cc4f9071700d5de Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sat, 21 Nov 2015 16:23:01 +0000 Subject: [PATCH 0369/3095] Updated from global requirements Change-Id: If8a37fdf90f3e97712a5d223d8e166840d036ab2 --- requirements.txt | 4 ++-- test-requirements.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index a710d6fb8b..215874cfa3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,9 +8,9 @@ Babel>=1.3 cliff>=1.14.0 # Apache-2.0 keystoneauth1>=1.0.0 os-client-config!=1.6.2,>=1.4.0 -oslo.config>=2.6.0 # Apache-2.0 +oslo.config>=2.7.0 # Apache-2.0 oslo.i18n>=1.5.0 # Apache-2.0 -oslo.utils!=2.6.0,>=2.4.0 # Apache-2.0 +oslo.utils>=2.8.0 # Apache-2.0 python-glanceclient>=0.18.0 python-keystoneclient!=1.8.0,>=1.6.0 python-novaclient!=2.33.0,>=2.29.0 diff --git a/test-requirements.txt b/test-requirements.txt index 27f72bbed3..c02ebf0ea4 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -7,7 +7,7 @@ coverage>=3.6 discover fixtures>=1.3.1 mock>=1.2 -oslosphinx>=2.5.0 # Apache-2.0 +oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 requests-mock>=0.6.0 # Apache-2.0 sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 From 6957fb3ee7128bcabb5324bfe6f2b02a7f0dd534 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 21 Nov 2015 19:33:14 +0800 Subject: [PATCH 0370/3095] Trivial: Fix wrong doc for wait_for_status(). Two trivial fixes: 1. docs for parameters are not sorted correctly 2. missing doc for a parameter Change-Id: I0cfb65e0f897c391b9b6e7225251e88855b07a56 --- openstackclient/common/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index 0a1f8e0e79..8822cd1d86 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -305,8 +305,9 @@ def wait_for_status(status_f, :param status_f: a status function that takes a single id argument :param res_id: the resource id to watch - :param success_status: a list of status strings for successful completion :param status_field: the status attribute in the returned resource object + :param success_status: a list of status strings for successful completion + :param error_status: a list of status strings for error :param sleep_time: wait this long (seconds) :param callback: called per sleep cycle, useful to display progress :rtype: True on success From a41a8c42af47ea51782f90221e987287484a26d7 Mon Sep 17 00:00:00 2001 From: xiexs Date: Tue, 24 Nov 2015 00:40:20 -0500 Subject: [PATCH 0371/3095] Add "openstack server shelve" into OSC Currently, the shelve operation is not supported by OSC. So, this patch attempts to add it into OSC. Change-Id: I92545300bef006a069338168d2de800e8a58af69 Implements: blueprint introduce-shelve-into-osc --- doc/source/command-objects/server.rst | 15 ++++++ doc/source/commands.rst | 1 + openstackclient/compute/v2/server.py | 25 +++++++++ .../tests/compute/v2/test_server.py | 54 +++++++++++++++++++ setup.cfg | 1 + 5 files changed, 96 insertions(+) diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index 4aeef4d056..f55e4139da 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -495,6 +495,21 @@ Resume server Server (name or ID) +server shelve +------------- + +Shelve server(s) + +.. program:: server shelve +.. code:: bash + + os server shelve + [ ...] + +.. describe:: + + Server(s) to shelve (name or ID) + server set ---------- diff --git a/doc/source/commands.rst b/doc/source/commands.rst index e69699b173..ade351155f 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -182,6 +182,7 @@ Those actions with an opposite action are noted in parens if applicable. * ``revoke`` (``issue``) - revoke a token * ``save`` - download an object locally * ``set`` (``unset``) - set a property on the object, formerly called metadata +* ``shelve`` (``unshelve``) - shelve one or more server * ``show`` - display detailed information about the specific object * ``start`` (``stop``) - start one or more servers * ``stop`` (``start``) - stop one or more servers diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index aa4569c3bb..bb74ae4321 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1387,6 +1387,31 @@ def take_action(self, parsed_args): return zip(*sorted(six.iteritems(data))) +class ShelveServer(command.Command): + """Shelve server(s)""" + + log = logging.getLogger(__name__ + '.ShelveServer') + + def get_parser(self, prog_name): + parser = super(ShelveServer, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + nargs='+', + help=_('Server(s) to shelve (name or ID)'), + ) + return parser + + @utils.log_method(log) + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + for server in parsed_args.server: + utils.find_resource( + compute_client.servers, + server, + ).shelve() + + class SshServer(command.Command): """Ssh to server""" diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 791a90abd1..61e098a827 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -766,3 +766,57 @@ def test_get_ip_address(self): server._get_ip_address, self.OLD, 'public', [4, 6]) self.assertRaises(exceptions.CommandError, server._get_ip_address, self.OLD, 'private', [6]) + + +class TestShelveServer(TestServer): + + def setUp(self): + super(TestShelveServer, self).setUp() + + # Get the command object to test + self.cmd = server.ShelveServer(self.app, None) + + # Set shelve method to be tested. + self.methods = { + 'shelve': None, + } + + def setup_servers_mock(self, count=1): + servers = fakes.FakeServer.create_servers(methods=self.methods, + count=count) + + self.servers_mock.get = fakes.FakeServer.get_servers(servers, 1) + + return servers + + def test_shelve_one_server(self): + server = self.setup_servers_mock(1)[0] + + arglist = [ + server.id, + ] + verifylist = [ + ('server', [server.id]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + server.shelve.assert_called_with() + + def test_shelve_multi_servers(self): + servers = self.setup_servers_mock(3) + arglist = [] + verifylist = [] + + for i in range(0, len(servers)): + arglist.append(servers[i].id) + verifylist = [ + ('server', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + for i in range(0, len(servers)): + servers[i].shelve.assert_called_with() diff --git a/setup.cfg b/setup.cfg index d79df17b84..6760e84d40 100644 --- a/setup.cfg +++ b/setup.cfg @@ -130,6 +130,7 @@ openstack.compute.v2 = server_resize = openstackclient.compute.v2.server:ResizeServer server_resume = openstackclient.compute.v2.server:ResumeServer server_set = openstackclient.compute.v2.server:SetServer + server_shelve = openstackclient.compute.v2.server:ShelveServer server_show = openstackclient.compute.v2.server:ShowServer server_ssh = openstackclient.compute.v2.server:SshServer server_start = openstackclient.compute.v2.server:StartServer From 4955117dff6ebebefcbe7bfafef0d8295e147d58 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 24 Nov 2015 18:47:26 +0800 Subject: [PATCH 0372/3095] Enable "openstack server suspend" command to take multiple servers. Current "openstack server suspend" command could only suspend one server. Improve it to be able to handle more than one servers. Also improve the doc to reflect the new feature. Change-Id: Ic0417ee28f46c9198a35744c0180342e61966b26 Implements: blueprint cmd-with-multi-servers --- doc/source/command-objects/server.rst | 6 +++--- doc/source/commands.rst | 2 +- openstackclient/compute/v2/server.py | 14 ++++++++------ 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index 4aeef4d056..eb75ef87eb 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -628,17 +628,17 @@ Stop server(s) server suspend -------------- -Suspend server +Suspend server(s) .. program:: server suspend .. code:: bash os server suspend - + [ ...] .. describe:: - Server (name or ID) + Server(s) to suspend (name or ID) server unlock ------------- diff --git a/doc/source/commands.rst b/doc/source/commands.rst index e69699b173..07dcd18d3f 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -185,7 +185,7 @@ Those actions with an opposite action are noted in parens if applicable. * ``show`` - display detailed information about the specific object * ``start`` (``stop``) - start one or more servers * ``stop`` (``start``) - stop one or more servers -* ``suspend`` (``resume``) - stop a server and save to disk freeing memory +* ``suspend`` (``resume``) - stop one or more servers and save to disk freeing memory * ``unlock`` (``lock``) - unlock one or more servers * ``unpause`` (``pause``) - return one or more paused servers to running state * ``unrescue`` (``rescue``) - return a server to normal boot mode diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index aa4569c3bb..db62f000a6 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1585,7 +1585,7 @@ def take_action(self, parsed_args): class SuspendServer(command.Command): - """Suspend server""" + """Suspend server(s)""" log = logging.getLogger(__name__ + '.SuspendServer') @@ -1594,7 +1594,8 @@ def get_parser(self, prog_name): parser.add_argument( 'server', metavar='', - help=_('Server (name or ID)'), + nargs='+', + help=_('Server(s) to suspend (name or ID)'), ) return parser @@ -1602,10 +1603,11 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): compute_client = self.app.client_manager.compute - utils.find_resource( - compute_client.servers, - parsed_args.server, - ).suspend() + for server in parsed_args.server: + utils.find_resource( + compute_client.servers, + server, + ).suspend() class UnlockServer(command.Command): From bfa223f61591cf7515265c0ddcacc02797cee055 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 24 Nov 2015 18:54:03 +0800 Subject: [PATCH 0373/3095] Enable "openstack server resume" command to take multiple servers. Current "openstack server resume" command could only resume one server. Improve it to be able to handle more than one servers. Also improve the doc to reflect the new feature. Change-Id: I726eb86bfa3df3a9911f45770e6641264dbc1e0b Implements: blueprint cmd-with-multi-servers --- doc/source/command-objects/server.rst | 6 +++--- doc/source/commands.rst | 2 +- openstackclient/compute/v2/server.py | 14 ++++++++------ 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index eb75ef87eb..ed96286b3f 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -483,17 +483,17 @@ a revert to release the new server and restart the old one. server resume ------------- -Resume server +Resume server(s) .. program:: server resume .. code:: bash os server resume - + [ ...] .. describe:: - Server (name or ID) + Server(s) to resume (name or ID) server set ---------- diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 07dcd18d3f..abeda09801 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -178,7 +178,7 @@ Those actions with an opposite action are noted in parens if applicable. * ``remove`` (``add``) - remove an object from a group of objects * ``rescue`` (``unrescue``) - reboot a server in a special rescue mode allowing access to the original disks * ``resize`` - change a server's flavor -* ``resume`` (``suspend``) - return a suspended server to running state +* ``resume`` (``suspend``) - return one or more suspended servers to running state * ``revoke`` (``issue``) - revoke a token * ``save`` - download an object locally * ``set`` (``unset``) - set a property on the object, formerly called metadata diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index db62f000a6..7c58b598fb 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1268,7 +1268,7 @@ def take_action(self, parsed_args): class ResumeServer(command.Command): - """Resume server""" + """Resume server(s)""" log = logging.getLogger(__name__ + '.ResumeServer') @@ -1277,7 +1277,8 @@ def get_parser(self, prog_name): parser.add_argument( 'server', metavar='', - help=_('Server (name or ID)'), + nargs='+', + help=_('Server(s) to resume (name or ID)'), ) return parser @@ -1285,10 +1286,11 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): compute_client = self.app.client_manager.compute - utils.find_resource( - compute_client.servers, - parsed_args.server, - ) .resume() + for server in parsed_args.server: + utils.find_resource( + compute_client.servers, + server, + ).resume() class SetServer(command.Command): From 41133fb82ebbadd565b38886883f8bba44295f8c Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Tue, 24 Nov 2015 07:52:43 -0600 Subject: [PATCH 0374/3095] Doc: Add security group and security group rule Add missing command list documentation for the 'security group' and 'security group rule' commands. In addition, update the command description and argument help to fix minor issues and use consistent terminology. Change-Id: I9f4a3fbac5637289f19511874e16391d3fe27132 --- .../command-objects/security-group-rule.rst | 65 +++++++++++++ doc/source/command-objects/security-group.rst | 95 +++++++++++++++++++ openstackclient/compute/v2/security_group.py | 20 ++-- 3 files changed, 170 insertions(+), 10 deletions(-) create mode 100644 doc/source/command-objects/security-group-rule.rst create mode 100644 doc/source/command-objects/security-group.rst diff --git a/doc/source/command-objects/security-group-rule.rst b/doc/source/command-objects/security-group-rule.rst new file mode 100644 index 0000000000..8bd0d0615c --- /dev/null +++ b/doc/source/command-objects/security-group-rule.rst @@ -0,0 +1,65 @@ +=================== +security group rule +=================== + +Compute v2 + +security group rule create +-------------------------- + +Create a new security group rule + +.. program:: security group rule create +.. code:: bash + + os security group rule create + [--proto ] + [--src-ip ] + [--dst-port ] + + +.. option:: --proto + + IP protocol (icmp, tcp, udp; default: tcp) + +.. option:: --src-ip + + Source IP (may use CIDR notation; default: 0.0.0.0/0) + +.. option:: --dst-port + + Destination port, may be a range: 137:139 (default: 0; only required for proto tcp and udp) + +.. describe:: + + Create rule in this security group (name or ID) + +security group rule delete +-------------------------- + +Delete a security group rule + +.. program:: security group rule delete +.. code:: bash + + os security group rule delete + + +.. describe:: + + Security group rule to delete (ID only) + +security group rule list +------------------------ + +List security group rules + +.. program:: security group rule list +.. code:: bash + + os security group rule list + + +.. describe:: + + List all rules in this security group (name or ID) diff --git a/doc/source/command-objects/security-group.rst b/doc/source/command-objects/security-group.rst new file mode 100644 index 0000000000..60de41d824 --- /dev/null +++ b/doc/source/command-objects/security-group.rst @@ -0,0 +1,95 @@ +============== +security group +============== + +Compute v2 + +security group create +--------------------- + +Create a new security group + +.. program:: security group create +.. code:: bash + + os security group create + [--description ] + + +.. option:: --description + + Security group description + +.. describe:: + + New security group name + +security group delete +--------------------- + +Delete a security group + +.. program:: security group delete +.. code:: bash + + os security group delete + + +.. describe:: + + Security group to delete (name or ID) + +security group list +------------------- + +List security groups + +.. program:: security group list +.. code:: bash + + os security group list + [--all-projects] + +.. option:: --all-projects + + Display information from all projects (admin only) + +security group set +------------------ + +Set security group properties + +.. program:: security group set +.. code:: bash + + os security group set + [--name ] + [--description ] + + +.. option:: --name + + New security group name + +.. option:: --description + + New security group description + +.. describe:: + + Security group to modify (name or ID) + +security group show +------------------- + +Display security group details + +.. program:: security group show +.. code:: bash + + os security group show + + +.. describe:: + + Security group to display (name or ID) diff --git a/openstackclient/compute/v2/security_group.py b/openstackclient/compute/v2/security_group.py index 3dd0c49b95..0ba51d685d 100644 --- a/openstackclient/compute/v2/security_group.py +++ b/openstackclient/compute/v2/security_group.py @@ -103,7 +103,7 @@ def get_parser(self, prog_name): parser.add_argument( 'group', metavar='', - help='Name or ID of security group to delete', + help='Security group to delete (name or ID)', ) return parser @@ -120,7 +120,7 @@ def take_action(self, parsed_args): class ListSecurityGroup(lister.Lister): - """List all security groups""" + """List security groups""" log = logging.getLogger(__name__ + ".ListSecurityGroup") @@ -185,7 +185,7 @@ def get_parser(self, prog_name): parser.add_argument( 'group', metavar='', - help='Name or ID of security group to change', + help='Security group to modify (name or ID)', ) parser.add_argument( '--name', @@ -195,7 +195,7 @@ def get_parser(self, prog_name): parser.add_argument( "--description", metavar="", - help="New security group name", + help="New security group description", ) return parser @@ -227,7 +227,7 @@ def take_action(self, parsed_args): class ShowSecurityGroup(show.ShowOne): - """Show a specific security group""" + """Display security group details""" log = logging.getLogger(__name__ + '.ShowSecurityGroup') @@ -236,7 +236,7 @@ def get_parser(self, prog_name): parser.add_argument( 'group', metavar='', - help='Name or ID of security group to change', + help='Security group to display (name or ID)', ) return parser @@ -275,7 +275,7 @@ def get_parser(self, prog_name): parser.add_argument( 'group', metavar='', - help='Create rule in this security group', + help='Create rule in this security group (name or ID)', ) parser.add_argument( "--proto", @@ -333,7 +333,7 @@ def get_parser(self, prog_name): parser.add_argument( 'rule', metavar='', - help='Security group rule ID to delete', + help='Security group rule to delete (ID only)', ) return parser @@ -346,7 +346,7 @@ def take_action(self, parsed_args): class ListSecurityGroupRule(lister.Lister): - """List all security group rules""" + """List security group rules""" log = logging.getLogger(__name__ + ".ListSecurityGroupRule") @@ -355,7 +355,7 @@ def get_parser(self, prog_name): parser.add_argument( 'group', metavar='', - help='List all rules in this security group', + help='List all rules in this security group (name or ID)', ) return parser From 325420f6d166aef0b58242918fac464642395b5d Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Tue, 24 Nov 2015 10:31:07 -0600 Subject: [PATCH 0375/3095] Doc: Update and add IP address Fix the 'ip floating' command documentation file name. Add missing command list documentation for the 'ip fixed' and 'ip floating pool' commands. In addition, update the command description and argument help to fix minor issues and use consistent terminology. And finally, cleanup the Network command object formatting. Change-Id: I0168891bc674c9f0d4df4062f7e93f497dc8fe6f --- doc/source/command-objects/ip-fixed.rst | 45 +++++++++++++++++++ .../command-objects/ip-floating-pool.rst | 15 +++++++ .../{floatingip.rst => ip-floating.rst} | 36 +++++++-------- doc/source/commands.rst | 11 ++--- openstackclient/compute/v2/fixedip.py | 12 ++--- openstackclient/compute/v2/floatingip.py | 18 ++++---- openstackclient/compute/v2/floatingippool.py | 2 +- 7 files changed, 100 insertions(+), 39 deletions(-) create mode 100644 doc/source/command-objects/ip-fixed.rst create mode 100644 doc/source/command-objects/ip-floating-pool.rst rename doc/source/command-objects/{floatingip.rst => ip-floating.rst} (60%) diff --git a/doc/source/command-objects/ip-fixed.rst b/doc/source/command-objects/ip-fixed.rst new file mode 100644 index 0000000000..3a55b9953c --- /dev/null +++ b/doc/source/command-objects/ip-fixed.rst @@ -0,0 +1,45 @@ +======== +ip fixed +======== + +Compute v2 + +ip fixed add +------------ + +Add fixed IP address to server + +.. program:: ip fixed add +.. code:: bash + + os ip fixed add + + + +.. describe:: + + Network to fetch an IP address from (name or ID) + +.. describe:: + + Server to receive the IP address (name or ID) + +ip fixed remove +--------------- + +Remove fixed IP address from server + +.. program:: ip fixed remove +.. code:: bash + + os ip fixed remove + + + +.. describe:: + + IP address to remove from server (name only) + +.. describe:: + + Server to remove the IP address from (name or ID) diff --git a/doc/source/command-objects/ip-floating-pool.rst b/doc/source/command-objects/ip-floating-pool.rst new file mode 100644 index 0000000000..63a450eb40 --- /dev/null +++ b/doc/source/command-objects/ip-floating-pool.rst @@ -0,0 +1,15 @@ +================ +ip floating pool +================ + +Compute v2 + +ip floating pool list +--------------------- + +List pools of floating IP addresses + +.. program:: ip floating pool list +.. code:: bash + + os ip floating pool list diff --git a/doc/source/command-objects/floatingip.rst b/doc/source/command-objects/ip-floating.rst similarity index 60% rename from doc/source/command-objects/floatingip.rst rename to doc/source/command-objects/ip-floating.rst index 1ae3041e42..6bfd7f4498 100644 --- a/doc/source/command-objects/floatingip.rst +++ b/doc/source/command-objects/ip-floating.rst @@ -1,24 +1,24 @@ -========== -floatingip -========== +=========== +ip floating +=========== Compute v2 ip floating add --------------- -Add floating-ip to server +Add floating IP address to server .. program:: ip floating add .. code:: bash os ip floating add - + -.. describe:: +.. describe:: - IP address to add to server + IP address to add to server (name only) .. describe:: @@ -27,7 +27,7 @@ Add floating-ip to server ip floating create ------------------ -Create new floating-ip +Create new floating IP address .. program:: ip floating create .. code:: bash @@ -37,27 +37,27 @@ Create new floating-ip .. describe:: - Pool to fetch floating IP from + Pool to fetch IP address from (name or ID) ip floating delete ------------------ -Delete a floating-ip +Delete a floating IP address .. program:: ip floating delete .. code:: bash os ip floating delete - + -.. describe:: +.. describe:: - IP address to delete + IP address to delete (ID only) ip floating list ---------------- -List floating-ips +List floating IP addresses .. program:: ip floating list .. code:: bash @@ -67,18 +67,18 @@ List floating-ips ip floating remove ------------------ -Remove floating-ip from server +Remove floating IP address from server .. program:: ip floating remove .. code:: bash os ip floating remove - + -.. describe:: +.. describe:: - IP address to remove from server + IP address to remove from server (name only) .. describe:: diff --git a/doc/source/commands.rst b/doc/source/commands.rst index abeda09801..44be8bc7c3 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -91,13 +91,14 @@ referring to both Compute and Volume quotas. * ``hypervisor stats``: (**Compute**) hypervisor statistics over all compute nodes * ``identity provider``: (**Identity**) a source of users and authentication * ``image``: (**Image**) a disk image -* ``ip fixed``: Compute, Network - an internal IP address assigned to a server -* ``ip floating``: Compute, Network - a public IP address that can be mapped to a server +* ``ip fixed``: (**Compute**, **Network**) - an internal IP address assigned to a server +* ``ip floating``: (**Compute**, **Network**) - a public IP address that can be mapped to a server +* ``ip floating pool``: (**Compute**, **Network**) - a pool of public IP addresses * ``keypair``: (**Compute**) an SSH public key * ``limits``: (**Compute**, **Volume**) resource usage limits * ``mapping``: (**Identity**) a definition to translate identity provider attributes to Identity concepts * ``module``: internal - installed Python modules in the OSC process -* ``network``: Network - a virtual network for connecting servers and other resources +* ``network``: (**Network**) - a virtual network for connecting servers and other resources * ``object``: (**Object Store**) a single file in the Object Store * ``policy``: (**Identity**) determines authorization * ``project``: (**Identity**) owns a group of resources @@ -106,8 +107,8 @@ referring to both Compute and Volume quotas. * ``request token``: (**Identity**) temporary OAuth-based token * ``role``: (**Identity**) a policy object used to determine authorization * ``role assignment``: (**Identity**) a relationship between roles, users or groups, and domains or projects -* ``security group``: Compute, Network - groups of network access rules -* ``security group rule``: Compute, Network - the individual rules that define protocol/IP/port access +* ``security group``: (**Compute**, **Network**) - groups of network access rules +* ``security group rule``: (**Compute**, **Network**) - the individual rules that define protocol/IP/port access * ``server``: (**Compute**) virtual machine instance * ``server image``: (**Compute**) saved server disk image * ``service``: (**Identity**) a cloud service diff --git a/openstackclient/compute/v2/fixedip.py b/openstackclient/compute/v2/fixedip.py index d105e3915f..da9d85c331 100644 --- a/openstackclient/compute/v2/fixedip.py +++ b/openstackclient/compute/v2/fixedip.py @@ -23,7 +23,7 @@ class AddFixedIP(command.Command): - """Add fixed-ip command""" + """Add fixed IP address to server""" log = logging.getLogger(__name__ + ".AddFixedIP") @@ -32,12 +32,12 @@ def get_parser(self, prog_name): parser.add_argument( "network", metavar="", - help="Name of the network to fetch an IP address from", + help="Network to fetch an IP address from (name or ID)", ) parser.add_argument( "server", metavar="", - help="Name of the server to receive the IP address", + help="Server to receive the IP address (name or ID)", ) return parser @@ -56,7 +56,7 @@ def take_action(self, parsed_args): class RemoveFixedIP(command.Command): - """Remove fixed-ip command""" + """Remove fixed IP address from server""" log = logging.getLogger(__name__ + ".RemoveFixedIP") @@ -65,12 +65,12 @@ def get_parser(self, prog_name): parser.add_argument( "ip_address", metavar="", - help="IP address to remove from server", + help="IP address to remove from server (name only)", ) parser.add_argument( "server", metavar="", - help="Name of the server to remove the IP address from", + help="Server to remove the IP address from (name or ID)", ) return parser diff --git a/openstackclient/compute/v2/floatingip.py b/openstackclient/compute/v2/floatingip.py index c557c24b6a..65fe5910ba 100644 --- a/openstackclient/compute/v2/floatingip.py +++ b/openstackclient/compute/v2/floatingip.py @@ -26,7 +26,7 @@ class AddFloatingIP(command.Command): - """Add floating-ip to server""" + """Add floating IP address to server""" log = logging.getLogger(__name__ + ".AddFloatingIP") @@ -35,7 +35,7 @@ def get_parser(self, prog_name): parser.add_argument( "ip_address", metavar="", - help="IP address to add to server", + help="IP address to add to server (name only)", ) parser.add_argument( "server", @@ -56,7 +56,7 @@ def take_action(self, parsed_args): class CreateFloatingIP(show.ShowOne): - """Create new floating-ip""" + """Create new floating IP address""" log = logging.getLogger(__name__ + '.CreateFloatingIP') @@ -65,7 +65,7 @@ def get_parser(self, prog_name): parser.add_argument( 'pool', metavar='', - help='Pool to fetch floating IP from', + help='Pool to fetch IP address from (name or ID)', ) return parser @@ -80,7 +80,7 @@ def take_action(self, parsed_args): class DeleteFloatingIP(command.Command): - """Delete a floating-ip""" + """Delete a floating IP address""" log = logging.getLogger(__name__ + '.DeleteFloatingIP') @@ -89,7 +89,7 @@ def get_parser(self, prog_name): parser.add_argument( "ip_address", metavar="", - help="IP address to delete", + help="IP address to delete (ID only)", ) return parser @@ -107,7 +107,7 @@ def take_action(self, parsed_args): class ListFloatingIP(lister.Lister): - """List floating-ips""" + """List floating IP addresses""" log = logging.getLogger(__name__ + '.ListFloatingIP') @@ -127,7 +127,7 @@ def take_action(self, parsed_args): class RemoveFloatingIP(command.Command): - """Remove floating-ip from server""" + """Remove floating IP address from server""" log = logging.getLogger(__name__ + ".RemoveFloatingIP") @@ -136,7 +136,7 @@ def get_parser(self, prog_name): parser.add_argument( "ip_address", metavar="", - help="IP address to remove from server", + help="IP address to remove from server (name only)", ) parser.add_argument( "server", diff --git a/openstackclient/compute/v2/floatingippool.py b/openstackclient/compute/v2/floatingippool.py index cc4855527b..39a2d8fed4 100644 --- a/openstackclient/compute/v2/floatingippool.py +++ b/openstackclient/compute/v2/floatingippool.py @@ -23,7 +23,7 @@ class ListFloatingIPPool(lister.Lister): - """List floating-ip-pools""" + """List pools of floating IP addresses""" log = logging.getLogger(__name__ + '.ListFloatingIPPool') From 510a4d9674a61b4bc994138790452fad45616c10 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Tue, 24 Nov 2015 17:08:04 -0500 Subject: [PATCH 0376/3095] Remove py26 support as of mitaka, the infra team won't have the resources available to reasonably test py26, also the oslo team is dropping py26 support from their libraries. sine we rely on oslo for a lot of our work, and depend on infra for our CI, we should drop py26 support too. Change-Id: I66d168ecc755a3ea0e01d5b6344d06d9e0d1378b Closes-Bug: 1519510 --- setup.cfg | 1 - tox.ini | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index d79df17b84..3521b598da 100644 --- a/setup.cfg +++ b/setup.cfg @@ -15,7 +15,6 @@ classifier = Programming Language :: Python Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 - Programming Language :: Python :: 2.6 Programming Language :: Python :: 3 Programming Language :: Python :: 3.4 diff --git a/tox.ini b/tox.ini index 23104b820f..240427d9f3 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] minversion = 1.6 -envlist = py34,py26,py27,pep8 +envlist = py34,py27,pep8 skipdist = True [testenv] From 3dc97c0d3f2127c2af66fc41d9a4a32adda24545 Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Wed, 10 Jun 2015 12:52:40 -0600 Subject: [PATCH 0377/3095] Add functional tests for network crud Change-Id: If965a7389ffa5b7ad44f53eebc2e8b918c6d2ace --- functional/tests/network/__init__.py | 0 functional/tests/network/v2/__init__.py | 0 functional/tests/network/v2/test_network.py | 50 +++++++++++++++++++++ 3 files changed, 50 insertions(+) create mode 100644 functional/tests/network/__init__.py create mode 100644 functional/tests/network/v2/__init__.py create mode 100644 functional/tests/network/v2/test_network.py diff --git a/functional/tests/network/__init__.py b/functional/tests/network/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/functional/tests/network/v2/__init__.py b/functional/tests/network/v2/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/functional/tests/network/v2/test_network.py b/functional/tests/network/v2/test_network.py new file mode 100644 index 0000000000..0b2f35ea47 --- /dev/null +++ b/functional/tests/network/v2/test_network.py @@ -0,0 +1,50 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import uuid + +from functional.common import test + + +class NetworkTests(test.TestCase): + """Functional tests for network. """ + NAME = uuid.uuid4().hex + HEADERS = ['Name'] + FIELDS = ['name'] + + @classmethod + def setUpClass(cls): + opts = cls.get_show_opts(cls.FIELDS) + raw_output = cls.openstack('network create ' + cls.NAME + opts) + expected = cls.NAME + '\n' + cls.assertOutput(expected, raw_output) + + @classmethod + def tearDownClass(cls): + raw_output = cls.openstack('network delete ' + cls.NAME) + cls.assertOutput('', raw_output) + + def test_network_list(self): + opts = self.get_list_opts(self.HEADERS) + raw_output = self.openstack('network list' + opts) + self.assertIn(self.NAME, raw_output) + + def test_network_set(self): + raw_output = self.openstack('network set --disable ' + self.NAME) + opts = self.get_show_opts(['name', 'state']) + raw_output = self.openstack('network show ' + self.NAME + opts) + self.assertEqual(self.NAME + "\nDOWN\n", raw_output) + + def test_network_show(self): + opts = self.get_show_opts(self.FIELDS) + raw_output = self.openstack('network show ' + self.NAME + opts) + self.assertEqual(self.NAME + "\n", raw_output) From f3c69ceeb50bad5f07cc65693aed04f1a4921345 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 21 Nov 2015 19:49:47 +0800 Subject: [PATCH 0378/3095] Trivial: Add missing doc for parameter in wait_for_delete(). The doc of parameter manager is missing. Change-Id: I4e99c06ab713532d73615670ada0a61462285d76 --- openstackclient/common/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index 4da8e32054..04c7fe5bb8 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -335,6 +335,7 @@ def wait_for_delete(manager, callback=None): """Wait for resource deletion + :param manager: the manager from which we can get the resource :param res_id: the resource id to watch :param status_field: the status attribute in the returned resource object, this is used to check for error states while the resource is being From b3c2668c341da42ce7f193b1035f7ab352538d95 Mon Sep 17 00:00:00 2001 From: lin-hua-cheng Date: Wed, 25 Nov 2015 00:17:49 -0800 Subject: [PATCH 0379/3095] Move FakeServer to tests.common.v2.compute.fakes FakeServer should not be in tests.fakes since this should be just for generic re-usable classes. Change-Id: I19209952de69626dfa3caadc5d1cc69b7feadeba --- openstackclient/tests/compute/v2/fakes.py | 70 +++++++++++++++++++ .../tests/compute/v2/test_server.py | 27 ++++--- openstackclient/tests/fakes.py | 70 ------------------- 3 files changed, 87 insertions(+), 80 deletions(-) diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 4ac6a20ea9..32161ddc9f 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -13,7 +13,9 @@ # under the License. # +import copy import mock +import uuid from openstackclient.tests import fakes from openstackclient.tests.identity.v2_0 import fakes as identity_fakes @@ -139,3 +141,71 @@ def setUp(self): endpoint=fakes.AUTH_URL, token=fakes.AUTH_TOKEN, ) + + +class FakeServer(object): + """Fake one or more compute servers.""" + + @staticmethod + def create_one_server(attrs={}, methods={}): + """Create a fake server. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :return: + A FakeResource object, with id, name, metadata + """ + # Set default attributes. + server_info = { + 'id': 'server-id-' + uuid.uuid4().hex, + 'name': 'server-name-' + uuid.uuid4().hex, + 'metadata': {}, + } + + # Overwrite default attributes. + server_info.update(attrs) + + server = fakes.FakeResource(info=copy.deepcopy(server_info), + methods=methods, + loaded=True) + return server + + @staticmethod + def create_servers(attrs={}, methods={}, count=2): + """Create multiple fake servers. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :param int count: + The number of servers to fake + :return: + A list of FakeResource objects faking the servers + """ + servers = [] + for i in range(0, count): + servers.append(FakeServer.create_one_server(attrs, methods)) + + return servers + + @staticmethod + def get_servers(servers=None, count=2): + """Get an iterable MagicMock object with a list of faked servers. + + If servers list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List servers: + A list of FakeResource objects faking servers + :param int count: + The number of servers to fake + :return: + An iterable Mock object with side_effect set to a list of faked + servers + """ + if servers is None: + servers = FakeServer.create_servers(count) + return mock.MagicMock(side_effect=servers) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 710ee01bf7..a975a5372a 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -61,7 +61,8 @@ def setUp(self): attrs = { 'networks': {}, } - self.new_server = fakes.FakeServer.create_one_server(attrs=attrs) + self.new_server = compute_fakes.FakeServer.create_one_server( + attrs=attrs) # This is the return value for utils.find_resource(). # This is for testing --wait option. @@ -398,7 +399,7 @@ class TestServerDelete(TestServer): def setUp(self): super(TestServerDelete, self).setUp() - self.server = fakes.FakeServer.create_one_server() + self.server = compute_fakes.FakeServer.create_one_server() # This is the return value for utils.find_resource() self.servers_mock.get.return_value = self.server @@ -476,7 +477,7 @@ class TestServerImageCreate(TestServer): def setUp(self): super(TestServerImageCreate, self).setUp() - self.server = fakes.FakeServer.create_one_server() + self.server = compute_fakes.FakeServer.create_one_server() # This is the return value for utils.find_resource() self.servers_mock.get.return_value = self.server @@ -569,11 +570,14 @@ def setUp(self): } def setup_servers_mock(self, count=1): - servers = fakes.FakeServer.create_servers(methods=self.methods, - count=count) + servers = compute_fakes.FakeServer.create_servers( + methods=self.methods, + count=count) # This is the return value for utils.find_resource() - self.servers_mock.get = fakes.FakeServer.get_servers(servers, 1) + self.servers_mock.get = compute_fakes.FakeServer.get_servers( + servers, + 1) return servers @@ -617,7 +621,7 @@ class TestServerResize(TestServer): def setUp(self): super(TestServerResize, self).setUp() - self.server = fakes.FakeServer.create_one_server() + self.server = compute_fakes.FakeServer.create_one_server() # This is the return value for utils.find_resource() self.servers_mock.get.return_value = self.server @@ -806,10 +810,13 @@ def setUp(self): } def setup_servers_mock(self, count=1): - servers = fakes.FakeServer.create_servers(methods=self.methods, - count=count) + servers = compute_fakes.FakeServer.create_servers( + methods=self.methods, + count=count) - self.servers_mock.get = fakes.FakeServer.get_servers(servers, 1) + self.servers_mock.get = compute_fakes.FakeServer.get_servers( + servers, + 1) return servers diff --git a/openstackclient/tests/fakes.py b/openstackclient/tests/fakes.py index 85e65fb198..9f4dcc50b5 100644 --- a/openstackclient/tests/fakes.py +++ b/openstackclient/tests/fakes.py @@ -13,12 +13,10 @@ # under the License. # -import copy import json import mock import six import sys -import uuid from keystoneauth1 import fixture import requests @@ -185,71 +183,3 @@ def __getattr__(self, key): return self[key] except KeyError: raise AttributeError(key) - - -class FakeServer(object): - """Fake one or more compute servers.""" - - @staticmethod - def create_one_server(attrs={}, methods={}): - """Create a fake server. - - :param Dictionary attrs: - A dictionary with all attributes - :param Dictionary methods: - A dictionary with all methods - :return: - A FakeResource object, with id, name, metadata - """ - # Set default attributes. - server_info = { - 'id': 'server-id-' + uuid.uuid4().hex, - 'name': 'server-name-' + uuid.uuid4().hex, - 'metadata': {}, - } - - # Overwrite default attributes. - server_info.update(attrs) - - server = FakeResource(info=copy.deepcopy(server_info), - methods=methods, - loaded=True) - return server - - @staticmethod - def create_servers(attrs={}, methods={}, count=2): - """Create multiple fake servers. - - :param Dictionary attrs: - A dictionary with all attributes - :param Dictionary methods: - A dictionary with all methods - :param int count: - The number of servers to fake - :return: - A list of FakeResource objects faking the servers - """ - servers = [] - for i in range(0, count): - servers.append(FakeServer.create_one_server(attrs, methods)) - - return servers - - @staticmethod - def get_servers(servers=None, count=2): - """Get an iterable MagicMock object with a list of faked servers. - - If servers list is provided, then initialize the Mock object with the - list. Otherwise create one. - - :param List servers: - A list of FakeResource objects faking servers - :param int count: - The number of servers to fake - :return: - An iterable Mock object with side_effect set to a list of faked - servers - """ - if servers is None: - servers = FakeServer.create_servers(count) - return mock.MagicMock(side_effect=servers) From 74f84f32f50526fd389e3a3714ca0eda26c8f074 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Wed, 25 Nov 2015 08:54:52 -0600 Subject: [PATCH 0380/3095] Refactor: Order of security group class names Per comment in [1], refactor the security group class names to be in alphabetical order. [1] https://review.openstack.org/#/c/249223 Change-Id: If28a153cdab57c0659ff5c78b276766d4043467f --- openstackclient/compute/v2/security_group.py | 246 +++++++++---------- 1 file changed, 123 insertions(+), 123 deletions(-) diff --git a/openstackclient/compute/v2/security_group.py b/openstackclient/compute/v2/security_group.py index 0ba51d685d..68f086bc80 100644 --- a/openstackclient/compute/v2/security_group.py +++ b/openstackclient/compute/v2/security_group.py @@ -93,6 +93,64 @@ def take_action(self, parsed_args): return zip(*sorted(six.iteritems(info))) +class CreateSecurityGroupRule(show.ShowOne): + """Create a new security group rule""" + + log = logging.getLogger(__name__ + ".CreateSecurityGroupRule") + + def get_parser(self, prog_name): + parser = super(CreateSecurityGroupRule, self).get_parser(prog_name) + parser.add_argument( + 'group', + metavar='', + help='Create rule in this security group (name or ID)', + ) + parser.add_argument( + "--proto", + metavar="", + default="tcp", + help="IP protocol (icmp, tcp, udp; default: tcp)", + ) + parser.add_argument( + "--src-ip", + metavar="", + default="0.0.0.0/0", + help="Source IP (may use CIDR notation; default: 0.0.0.0/0)", + ) + parser.add_argument( + "--dst-port", + metavar="", + default=(0, 0), + action=parseractions.RangeAction, + help="Destination port, may be a range: 137:139 (default: 0; " + "only required for proto tcp and udp)", + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + + compute_client = self.app.client_manager.compute + group = utils.find_resource( + compute_client.security_groups, + parsed_args.group, + ) + if parsed_args.proto.lower() == 'icmp': + from_port, to_port = -1, -1 + else: + from_port, to_port = parsed_args.dst_port + data = compute_client.security_group_rules.create( + group.id, + parsed_args.proto, + from_port, + to_port, + parsed_args.src_ip, + ) + + info = _xform_security_group_rule(data._info) + return zip(*sorted(six.iteritems(info))) + + class DeleteSecurityGroup(command.Command): """Delete a security group""" @@ -119,6 +177,28 @@ def take_action(self, parsed_args): return +class DeleteSecurityGroupRule(command.Command): + """Delete a security group rule""" + + log = logging.getLogger(__name__ + '.DeleteSecurityGroupRule') + + def get_parser(self, prog_name): + parser = super(DeleteSecurityGroupRule, self).get_parser(prog_name) + parser.add_argument( + 'rule', + metavar='', + help='Security group rule to delete (ID only)', + ) + return parser + + @utils.log_method(log) + def take_action(self, parsed_args): + + compute_client = self.app.client_manager.compute + compute_client.security_group_rules.delete(parsed_args.rule) + return + + class ListSecurityGroup(lister.Lister): """List security groups""" @@ -175,6 +255,49 @@ def _get_project(project_id): ) for s in data)) +class ListSecurityGroupRule(lister.Lister): + """List security group rules""" + + log = logging.getLogger(__name__ + ".ListSecurityGroupRule") + + def get_parser(self, prog_name): + parser = super(ListSecurityGroupRule, self).get_parser(prog_name) + parser.add_argument( + 'group', + metavar='', + help='List all rules in this security group (name or ID)', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + + compute_client = self.app.client_manager.compute + group = utils.find_resource( + compute_client.security_groups, + parsed_args.group, + ) + + # Argh, the rules are not Resources... + rules = [] + for rule in group.rules: + rules.append(security_group_rules.SecurityGroupRule( + compute_client.security_group_rules, + _xform_security_group_rule(rule), + )) + + columns = column_headers = ( + "ID", + "IP Protocol", + "IP Range", + "Port Range", + ) + return (column_headers, + (utils.get_item_properties( + s, columns, + ) for s in rules)) + + class SetSecurityGroup(show.ShowOne): """Set security group properties""" @@ -263,126 +386,3 @@ def take_action(self, parsed_args): ) return zip(*sorted(six.iteritems(info))) - - -class CreateSecurityGroupRule(show.ShowOne): - """Create a new security group rule""" - - log = logging.getLogger(__name__ + ".CreateSecurityGroupRule") - - def get_parser(self, prog_name): - parser = super(CreateSecurityGroupRule, self).get_parser(prog_name) - parser.add_argument( - 'group', - metavar='', - help='Create rule in this security group (name or ID)', - ) - parser.add_argument( - "--proto", - metavar="", - default="tcp", - help="IP protocol (icmp, tcp, udp; default: tcp)", - ) - parser.add_argument( - "--src-ip", - metavar="", - default="0.0.0.0/0", - help="Source IP (may use CIDR notation; default: 0.0.0.0/0)", - ) - parser.add_argument( - "--dst-port", - metavar="", - default=(0, 0), - action=parseractions.RangeAction, - help="Destination port, may be a range: 137:139 (default: 0; " - "only required for proto tcp and udp)", - ) - return parser - - def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) - - compute_client = self.app.client_manager.compute - group = utils.find_resource( - compute_client.security_groups, - parsed_args.group, - ) - if parsed_args.proto.lower() == 'icmp': - from_port, to_port = -1, -1 - else: - from_port, to_port = parsed_args.dst_port - data = compute_client.security_group_rules.create( - group.id, - parsed_args.proto, - from_port, - to_port, - parsed_args.src_ip, - ) - - info = _xform_security_group_rule(data._info) - return zip(*sorted(six.iteritems(info))) - - -class DeleteSecurityGroupRule(command.Command): - """Delete a security group rule""" - - log = logging.getLogger(__name__ + '.DeleteSecurityGroupRule') - - def get_parser(self, prog_name): - parser = super(DeleteSecurityGroupRule, self).get_parser(prog_name) - parser.add_argument( - 'rule', - metavar='', - help='Security group rule to delete (ID only)', - ) - return parser - - @utils.log_method(log) - def take_action(self, parsed_args): - - compute_client = self.app.client_manager.compute - compute_client.security_group_rules.delete(parsed_args.rule) - return - - -class ListSecurityGroupRule(lister.Lister): - """List security group rules""" - - log = logging.getLogger(__name__ + ".ListSecurityGroupRule") - - def get_parser(self, prog_name): - parser = super(ListSecurityGroupRule, self).get_parser(prog_name) - parser.add_argument( - 'group', - metavar='', - help='List all rules in this security group (name or ID)', - ) - return parser - - def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) - - compute_client = self.app.client_manager.compute - group = utils.find_resource( - compute_client.security_groups, - parsed_args.group, - ) - - # Argh, the rules are not Resources... - rules = [] - for rule in group.rules: - rules.append(security_group_rules.SecurityGroupRule( - compute_client.security_group_rules, - _xform_security_group_rule(rule), - )) - - columns = column_headers = ( - "ID", - "IP Protocol", - "IP Range", - "Port Range", - ) - return (column_headers, - (utils.get_item_properties( - s, columns, - ) for s in rules)) From d1a58653ab8c3a98f2315ebbffd5e86be0beb966 Mon Sep 17 00:00:00 2001 From: Xi Yang Date: Sun, 8 Nov 2015 23:22:51 +0800 Subject: [PATCH 0381/3095] Use is_public to set access of volume type Currently the 'public' and 'private' keys does not work when creating volume type, 'is_public' should be used. Change-Id: If34a66053ea6c192882a1b9d8bbb1d3666be3f83 Closes-bug: 1520115 --- openstackclient/tests/volume/v2/test_type.py | 6 +++--- openstackclient/volume/v2/volume_type.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/openstackclient/tests/volume/v2/test_type.py b/openstackclient/tests/volume/v2/test_type.py index 9a07263b5f..c63cd1fa53 100644 --- a/openstackclient/tests/volume/v2/test_type.py +++ b/openstackclient/tests/volume/v2/test_type.py @@ -73,7 +73,7 @@ def test_type_create_public(self): self.types_mock.create.assert_called_with( volume_fakes.type_name, description=volume_fakes.type_description, - public=True, + is_public=True, ) collist = ( @@ -93,7 +93,7 @@ def test_type_create_private(self): arglist = [ volume_fakes.type_name, "--description", volume_fakes.type_description, - "--private" + "--private", ] verifylist = [ ("name", volume_fakes.type_name), @@ -107,7 +107,7 @@ def test_type_create_private(self): self.types_mock.create.assert_called_with( volume_fakes.type_name, description=volume_fakes.type_description, - private=True, + is_public=False, ) collist = ( diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index 8cca86f9a3..583e6ed9c6 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -73,9 +73,9 @@ def take_action(self, parsed_args): kwargs = {} if parsed_args.public: - kwargs['public'] = True + kwargs['is_public'] = True if parsed_args.private: - kwargs['private'] = True + kwargs['is_public'] = False volume_type = volume_client.volume_types.create( parsed_args.name, From 32e0ed6980abab17c2027063b97784d092b44cee Mon Sep 17 00:00:00 2001 From: Xi Yang Date: Fri, 13 Nov 2015 10:42:46 +0800 Subject: [PATCH 0382/3095] Change 'Object Store' to 'Object Storage' Because the official documents uses 'Object Storage', so change it in this project. Change-Id: I87c0996b48c452b3ff619b8214676ae377e6d0af --- doc/source/command-objects/container.rst | 2 +- doc/source/command-objects/object-store-account.rst | 2 +- doc/source/command-objects/object.rst | 2 +- doc/source/commands.rst | 4 ++-- doc/source/index.rst | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/source/command-objects/container.rst b/doc/source/command-objects/container.rst index 251e3f314a..e6517e9bb8 100644 --- a/doc/source/command-objects/container.rst +++ b/doc/source/command-objects/container.rst @@ -2,7 +2,7 @@ container ========= -Object Store v1 +Object Storage v1 container create ---------------- diff --git a/doc/source/command-objects/object-store-account.rst b/doc/source/command-objects/object-store-account.rst index bdc9393eaa..ba37078ed6 100644 --- a/doc/source/command-objects/object-store-account.rst +++ b/doc/source/command-objects/object-store-account.rst @@ -2,7 +2,7 @@ object store account ==================== -Object Store v1 +Object Storage v1 object store account set ------------------------ diff --git a/doc/source/command-objects/object.rst b/doc/source/command-objects/object.rst index ac81441702..5aaad8a526 100644 --- a/doc/source/command-objects/object.rst +++ b/doc/source/command-objects/object.rst @@ -2,7 +2,7 @@ object ====== -Object Store v1 +Object Storage v1 object create ------------- diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 971739de20..90aa481e94 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -77,7 +77,7 @@ referring to both Compute and Volume quotas. * ``console log``: (**Compute**) server console text dump * ``console url``: (**Compute**) server remote console URL * ``consumer``: (**Identity**) OAuth-based delegatee -* ``container``: (**Object Store**) a grouping of objects +* ``container``: (**Object Storage**) a grouping of objects * ``credentials``: (**Identity**) specific to identity providers * ``domain``: (**Identity**) a grouping of projects * ``ec2 credentials``: (**Identity**) AWS EC2-compatible credentials @@ -99,7 +99,7 @@ referring to both Compute and Volume quotas. * ``mapping``: (**Identity**) a definition to translate identity provider attributes to Identity concepts * ``module``: internal - installed Python modules in the OSC process * ``network``: (**Network**) - a virtual network for connecting servers and other resources -* ``object``: (**Object Store**) a single file in the Object Store +* ``object``: (**Object Storage**) a single file in the Object Storage * ``policy``: (**Identity**) determines authorization * ``project``: (**Identity**) owns a group of resources * ``quota``: (**Compute**, **Volume**) resource usage restrictions diff --git a/doc/source/index.rst b/doc/source/index.rst index 6b7d63f36e..50b1fd24a6 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -3,7 +3,7 @@ OpenStackClient =============== OpenStackClient (aka OSC) is a command-line client for OpenStack that -brings the command set for Compute, Identity, Image, Object Store and Volume +brings the command set for Compute, Identity, Image, Object Storage and Volume APIs together in a single shell with a uniform command structure. User Documentation From c48afe6032ad53fcdc12c749adfe2d0f006fef14 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 21 Nov 2015 19:47:30 +0800 Subject: [PATCH 0383/3095] Trivial: Fix a typo. Change-Id: I236b4f53ee23cc97900e6244ab709404cc44a4ca --- openstackclient/common/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index 04c7fe5bb8..42630d913a 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -377,7 +377,7 @@ def wait_for_delete(manager, def get_effective_log_level(): """Returns the lowest logging level considered by logging handlers - Retrieve an return the smallest log level set among the root + Retrieve and return the smallest log level set among the root logger's handlers (in case of multiple handlers). """ root_log = logging.getLogger() From a2047d3f9fb36d67d4897d1bf976b5a7eb3f610f Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Fri, 27 Nov 2015 16:52:56 +0800 Subject: [PATCH 0384/3095] Trivial: Fix typo in find() in network. Change-Id: Ic54fbb9160adefe3d025d537125e125128f75ee6 --- openstackclient/network/common.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openstackclient/network/common.py b/openstackclient/network/common.py index bd6203bd03..31faef25f3 100644 --- a/openstackclient/network/common.py +++ b/openstackclient/network/common.py @@ -27,7 +27,8 @@ def find(client, resource, resources, name_or_id, name_attr='name'): n = find(netclient, 'network', 'networks', 'matrix') """ list_method = getattr(client, "list_%s" % resources) - # Search for by name + + # Search by name kwargs = {name_attr: name_or_id, 'fields': 'id'} data = list_method(**kwargs) info = data[resources] @@ -36,7 +37,8 @@ def find(client, resource, resources, name_or_id, name_attr='name'): if len(info) > 1: msg = "More than one %s exists with the name '%s'." raise exceptions.CommandError(msg % (resource, name_or_id)) - # Search for by id + + # Search by id data = list_method(id=name_or_id, fields='id') info = data[resources] if len(info) == 1: From bf657ef28664e01822562f669137abdd94635f7d Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Thu, 26 Nov 2015 13:25:16 +0800 Subject: [PATCH 0385/3095] Move setup_servers_mock() to class TestServer. This function will be used in almost every test case. So move it to the base class to avoid define it each time a test case is added. Change-Id: I060b54f0935b42a85042ad217d851fea649ec8d9 Implements: blueprint osc-unit-test-framework-improvement --- .../tests/compute/v2/test_server.py | 36 +++++++------------ 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index a975a5372a..b8c38f0cc0 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -52,6 +52,19 @@ def setUp(self): self.volumes_mock = self.app.client_manager.volume.volumes self.volumes_mock.reset_mock() + # Set object methods to be tested. Could be overwriten in subclass. + self.methods = {} + + def setup_servers_mock(self, count): + servers = compute_fakes.FakeServer.create_servers(methods=self.methods, + count=count) + + # This is the return value for utils.find_resource() + self.servers_mock.get = compute_fakes.FakeServer.get_servers(servers, + 0) + + return servers + class TestServerCreate(TestServer): @@ -569,18 +582,6 @@ def setUp(self): 'pause': None, } - def setup_servers_mock(self, count=1): - servers = compute_fakes.FakeServer.create_servers( - methods=self.methods, - count=count) - - # This is the return value for utils.find_resource() - self.servers_mock.get = compute_fakes.FakeServer.get_servers( - servers, - 1) - - return servers - def test_server_pause_one_server(self): servers = self.setup_servers_mock(1) @@ -809,17 +810,6 @@ def setUp(self): 'shelve': None, } - def setup_servers_mock(self, count=1): - servers = compute_fakes.FakeServer.create_servers( - methods=self.methods, - count=count) - - self.servers_mock.get = compute_fakes.FakeServer.get_servers( - servers, - 1) - - return servers - def test_shelve_one_server(self): server = self.setup_servers_mock(1)[0] From f9a41788cf275793449d27ab0c7fb0a0657006d0 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Thu, 26 Nov 2015 13:45:51 +0800 Subject: [PATCH 0386/3095] Use setup_servers_mock() in the base class in TestServerDelete. Use setup_servers_mock() in class TestServerDelete to coordinate the test class format. Change-Id: I1901d6b781d97820667984241f4d68764d045854 Implements: blueprint osc-unit-test-framework-improvement --- .../tests/compute/v2/test_server.py | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index b8c38f0cc0..672d3dde08 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -412,22 +412,19 @@ class TestServerDelete(TestServer): def setUp(self): super(TestServerDelete, self).setUp() - self.server = compute_fakes.FakeServer.create_one_server() - - # This is the return value for utils.find_resource() - self.servers_mock.get.return_value = self.server - self.servers_mock.delete.return_value = None # Get the command object to test self.cmd = server.DeleteServer(self.app, None) def test_server_delete_no_options(self): + servers = self.setup_servers_mock(count=1) + arglist = [ - self.server.id, + servers[0].id, ] verifylist = [ - ('server', [self.server.id]), + ('server', [servers[0].id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -435,16 +432,18 @@ def test_server_delete_no_options(self): self.cmd.take_action(parsed_args) self.servers_mock.delete.assert_called_with( - self.server.id, + servers[0].id, ) @mock.patch.object(common_utils, 'wait_for_delete', return_value=True) def test_server_delete_wait_ok(self, mock_wait_for_delete): + servers = self.setup_servers_mock(count=1) + arglist = [ - self.server.id, '--wait' + servers[0].id, '--wait' ] verifylist = [ - ('server', [self.server.id]), + ('server', [servers[0].id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -452,22 +451,24 @@ def test_server_delete_wait_ok(self, mock_wait_for_delete): self.cmd.take_action(parsed_args) self.servers_mock.delete.assert_called_with( - self.server.id, + servers[0].id, ) mock_wait_for_delete.assert_called_once_with( self.servers_mock, - self.server.id, + servers[0].id, callback=server._show_progress ) @mock.patch.object(common_utils, 'wait_for_delete', return_value=False) def test_server_delete_wait_fails(self, mock_wait_for_delete): + servers = self.setup_servers_mock(count=1) + arglist = [ - self.server.id, '--wait' + servers[0].id, '--wait' ] verifylist = [ - ('server', [self.server.id]), + ('server', [servers[0].id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -475,12 +476,12 @@ def test_server_delete_wait_fails(self, mock_wait_for_delete): self.assertRaises(SystemExit, self.cmd.take_action, parsed_args) self.servers_mock.delete.assert_called_with( - self.server.id, + servers[0].id, ) mock_wait_for_delete.assert_called_once_with( self.servers_mock, - self.server.id, + servers[0].id, callback=server._show_progress ) From 96331689f0bd26ee6d87b42dec303a0ca2835257 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Thu, 26 Nov 2015 13:59:28 +0800 Subject: [PATCH 0387/3095] Add multiple servers test case to TestServerDelete. This patch adds a multiple servers test case to class TestServerDelete. Change-Id: I8b0c37ec2a8e7b23889e25c9004f867f2907f630 Implements: blueprint osc-unit-test-framework-improvement --- .../tests/compute/v2/test_server.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 672d3dde08..5f6880ed0d 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -17,6 +17,7 @@ import mock import testtools +from mock import call from openstackclient.common import exceptions from openstackclient.common import utils as common_utils from openstackclient.compute.v2 import server @@ -435,6 +436,27 @@ def test_server_delete_no_options(self): servers[0].id, ) + def test_server_delete_multi_servers(self): + servers = self.setup_servers_mock(count=3) + + arglist = [] + verifylist = [] + + for s in servers: + arglist.append(s.id) + verifylist = [ + ('server', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + self.cmd.take_action(parsed_args) + + calls = [] + for s in servers: + calls.append(call(s.id)) + self.servers_mock.delete.assert_has_calls(calls) + @mock.patch.object(common_utils, 'wait_for_delete', return_value=True) def test_server_delete_wait_ok(self, mock_wait_for_delete): servers = self.setup_servers_mock(count=1) From 569ff3dcd88b5e18501a8d5356f3bf4693bde17c Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Fri, 27 Nov 2015 19:01:46 +0800 Subject: [PATCH 0388/3095] Abstract a helper function for server.xxx() tests. The test cases for server.xxx() are all the same, with one or more faked servers. So use a helper function to reduce code duplicate. Change-Id: I660c7731e2de8bf4d815b414a621d8d9ca6d5a8b Implements: blueprint osc-unit-test-framework-improvement --- .../tests/compute/v2/test_server.py | 83 ++++++------------- 1 file changed, 24 insertions(+), 59 deletions(-) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 5f6880ed0d..32b405e26b 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -63,9 +63,28 @@ def setup_servers_mock(self, count): # This is the return value for utils.find_resource() self.servers_mock.get = compute_fakes.FakeServer.get_servers(servers, 0) - return servers + def run_method_with_servers(self, method_name, server_count): + servers = self.setup_servers_mock(server_count) + + arglist = [] + verifylist = [] + + for s in servers: + arglist.append(s.id) + verifylist = [ + ('server', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + self.cmd.take_action(parsed_args) + + for s in servers: + method = getattr(s, method_name) + method.assert_called_with() + class TestServerCreate(TestServer): @@ -606,38 +625,10 @@ def setUp(self): } def test_server_pause_one_server(self): - servers = self.setup_servers_mock(1) - - arglist = [ - servers[0].id, - ] - verifylist = [ - ('server', [servers[0].id]), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # DisplayCommandBase.take_action() returns two tuples - self.cmd.take_action(parsed_args) - - servers[0].pause.assert_called_with() + self.run_method_with_servers('pause', 1) def test_server_pause_multi_servers(self): - servers = self.setup_servers_mock(3) - arglist = [] - verifylist = [] - - for i in range(0, len(servers)): - arglist.append(servers[i].id) - verifylist = [ - ('server', arglist), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # DisplayCommandBase.take_action() returns two tuples - self.cmd.take_action(parsed_args) - - for i in range(0, len(servers)): - servers[i].pause.assert_called_with() + self.run_method_with_servers('pause', 3) class TestServerResize(TestServer): @@ -834,33 +825,7 @@ def setUp(self): } def test_shelve_one_server(self): - server = self.setup_servers_mock(1)[0] - - arglist = [ - server.id, - ] - verifylist = [ - ('server', [server.id]), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - self.cmd.take_action(parsed_args) - - server.shelve.assert_called_with() + self.run_method_with_servers('shelve', 1) def test_shelve_multi_servers(self): - servers = self.setup_servers_mock(3) - arglist = [] - verifylist = [] - - for i in range(0, len(servers)): - arglist.append(servers[i].id) - verifylist = [ - ('server', arglist), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - self.cmd.take_action(parsed_args) - - for i in range(0, len(servers)): - servers[i].shelve.assert_called_with() + self.run_method_with_servers('shelve', 3) From 7886fdd3dea15579058007e89588e1da393b57b5 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Fri, 27 Nov 2015 13:55:07 +0800 Subject: [PATCH 0389/3095] Add unit tests for "server unpause" command. Change-Id: Ia74d9875b8aa413b84d0077d22adb75b866a5701 Implements: blueprint osc-unit-test-framework-improvement --- .../tests/compute/v2/test_server.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 32b405e26b..ea190fd0ef 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -759,6 +759,26 @@ def test_server_resize_revert(self): ) +class TestServerUnpause(TestServer): + + def setUp(self): + super(TestServerUnpause, self).setUp() + + # Get the command object to test + self.cmd = server.UnpauseServer(self.app, None) + + # Set methods to be tested. + self.methods = { + 'unpause': None, + } + + def test_server_unpause_one_server(self): + self.run_method_with_servers('unpause', 1) + + def test_server_unpause_multi_servers(self): + self.run_method_with_servers('unpause', 3) + + class TestServerGeneral(testtools.TestCase): OLD = { 'private': [ From cb16599f10e6134e61761b8a02c05dadd0574187 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Fri, 27 Nov 2015 14:11:45 +0800 Subject: [PATCH 0390/3095] Add unit tests for "server lock" command. Change-Id: I0396fc4836ee918298bbe3860b7c1f42d3b97e33 Implements: blueprint osc-unit-test-framework-improvement --- .../tests/compute/v2/test_server.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index ea190fd0ef..981d9b2a62 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -611,6 +611,26 @@ def test_server_image_create_name(self): self.assertEqual(datalist, data) +class TestServerLock(TestServer): + + def setUp(self): + super(TestServerLock, self).setUp() + + # Get the command object to test + self.cmd = server.LockServer(self.app, None) + + # Set methods to be tested. + self.methods = { + 'lock': None, + } + + def test_server_lock_one_server(self): + self.run_method_with_servers('lock', 1) + + def test_server_lock_multi_servers(self): + self.run_method_with_servers('lock', 3) + + class TestServerPause(TestServer): def setUp(self): From 4062d599651d3be03b9ac8d451146cd5345d149d Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Fri, 27 Nov 2015 14:14:41 +0800 Subject: [PATCH 0391/3095] Add unit tests for "server unlock" command. Change-Id: I4fbf115f925a6ffdeaec3d856d22224fa7730729 Implements: blueprint osc-unit-test-framework-improvement --- .../tests/compute/v2/test_server.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 981d9b2a62..1e891c2629 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -779,6 +779,26 @@ def test_server_resize_revert(self): ) +class TestServerUnlock(TestServer): + + def setUp(self): + super(TestServerUnlock, self).setUp() + + # Get the command object to test + self.cmd = server.UnlockServer(self.app, None) + + # Set methods to be tested. + self.methods = { + 'unlock': None, + } + + def test_server_unlock_one_server(self): + self.run_method_with_servers('unlock', 1) + + def test_server_unlock_multi_servers(self): + self.run_method_with_servers('unlock', 3) + + class TestServerUnpause(TestServer): def setUp(self): From c17698706373d10d3ad022333a366076f83a957d Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Fri, 27 Nov 2015 14:21:32 +0800 Subject: [PATCH 0392/3095] Add unit tests for "server suspend" command. Change-Id: Idfdd98fd27d97e1216abe9b14d3dea7c8f2a5a68 Implements: blueprint osc-unit-test-framework-improvement --- .../tests/compute/v2/test_server.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 1e891c2629..9891a29cec 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -779,6 +779,26 @@ def test_server_resize_revert(self): ) +class TestServerSuspend(TestServer): + + def setUp(self): + super(TestServerSuspend, self).setUp() + + # Get the command object to test + self.cmd = server.SuspendServer(self.app, None) + + # Set methods to be tested. + self.methods = { + 'suspend': None, + } + + def test_server_suspend_one_server(self): + self.run_method_with_servers('suspend', 1) + + def test_server_suspend_multi_servers(self): + self.run_method_with_servers('suspend', 3) + + class TestServerUnlock(TestServer): def setUp(self): From 9f38df920889c09faf7af4a6211eb6f9e31f8a39 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Fri, 27 Nov 2015 14:25:06 +0800 Subject: [PATCH 0393/3095] Add unit tests for "server resume" command. Change-Id: I8961e72b6901a52612cf62f52c02b6c2ff3dcd94 Implements: blueprint osc-unit-test-framework-improvement --- .../tests/compute/v2/test_server.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 9891a29cec..bad3cec838 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -779,6 +779,26 @@ def test_server_resize_revert(self): ) +class TestServerResume(TestServer): + + def setUp(self): + super(TestServerResume, self).setUp() + + # Get the command object to test + self.cmd = server.ResumeServer(self.app, None) + + # Set methods to be tested. + self.methods = { + 'resume': None, + } + + def test_server_resume_one_server(self): + self.run_method_with_servers('resume', 1) + + def test_server_resume_multi_servers(self): + self.run_method_with_servers('resume', 3) + + class TestServerSuspend(TestServer): def setUp(self): From cfff32aed68949fd70e2c5212efd52ac550d0785 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Fri, 27 Nov 2015 14:32:08 +0800 Subject: [PATCH 0394/3095] Add unit tests for "server start" command. Change-Id: I19060419a17b8e081e8f51c08959f24b84e7e570 Implements: blueprint osc-unit-test-framework-improvement --- .../tests/compute/v2/test_server.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index bad3cec838..709d13060b 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -799,6 +799,26 @@ def test_server_resume_multi_servers(self): self.run_method_with_servers('resume', 3) +class TestServerStart(TestServer): + + def setUp(self): + super(TestServerStart, self).setUp() + + # Get the command object to test + self.cmd = server.StartServer(self.app, None) + + # Set methods to be tested. + self.methods = { + 'start': None, + } + + def test_server_start_one_server(self): + self.run_method_with_servers('start', 1) + + def test_server_start_multi_servers(self): + self.run_method_with_servers('start', 3) + + class TestServerSuspend(TestServer): def setUp(self): From dacd5dce28d8dea7340c6b40f58393104ebc8f22 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Fri, 27 Nov 2015 14:34:16 +0800 Subject: [PATCH 0395/3095] Add unit tests for "server stop" command. Change-Id: Idfa82f7f3aee3824aedf8b551be7942659530457 Implements: blueprint osc-unit-test-framework-improvement --- .../tests/compute/v2/test_server.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 709d13060b..b3bf5e015b 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -819,6 +819,26 @@ def test_server_start_multi_servers(self): self.run_method_with_servers('start', 3) +class TestServerStop(TestServer): + + def setUp(self): + super(TestServerStop, self).setUp() + + # Get the command object to test + self.cmd = server.StopServer(self.app, None) + + # Set methods to be tested. + self.methods = { + 'stop': None, + } + + def test_server_stop_one_server(self): + self.run_method_with_servers('stop', 1) + + def test_server_stop_multi_servers(self): + self.run_method_with_servers('stop', 3) + + class TestServerSuspend(TestServer): def setUp(self): From 54b0ef3358b25b0c164ee5f4eba07ee474513d3a Mon Sep 17 00:00:00 2001 From: Xi Yang Date: Sun, 8 Nov 2015 22:05:15 +0800 Subject: [PATCH 0396/3095] Use Block Storage instead of Volume Volume is better to be replaced by Block Storage in the doc. Change-Id: I736669ee01c7385b6e701cb20f4334eff1c49286 --- README.rst | 4 ++-- doc/source/command-objects/backup.rst | 2 +- doc/source/command-objects/extension.rst | 2 +- doc/source/command-objects/limits.rst | 6 +++--- doc/source/command-objects/quota.rst | 8 ++++---- doc/source/command-objects/snapshot.rst | 2 +- doc/source/command-objects/volume-qos.rst | 2 +- doc/source/command-objects/volume-type.rst | 2 +- doc/source/command-objects/volume.rst | 2 +- doc/source/commands.rst | 2 +- doc/source/index.rst | 5 +++-- doc/source/man/openstack.rst | 2 +- doc/source/plugins.rst | 5 +++-- doc/source/releases.rst | 4 ++-- openstackclient/common/extension.py | 4 ++-- openstackclient/common/limits.py | 2 +- 16 files changed, 28 insertions(+), 26 deletions(-) diff --git a/README.rst b/README.rst index 7a92b1c997..53b42a7d84 100644 --- a/README.rst +++ b/README.rst @@ -11,8 +11,8 @@ OpenStackClient :alt: Downloads OpenStackClient (aka OSC) is a command-line client for OpenStack that brings -the command set for Compute, Identity, Image, Object Store and Volume APIs -together in a single shell with a uniform command structure. +the command set for Compute, Identity, Image, Object Store and Block Storage +APIs together in a single shell with a uniform command structure. The primary goal is to provide a unified shell command structure and a common language to describe operations in OpenStack. diff --git a/doc/source/command-objects/backup.rst b/doc/source/command-objects/backup.rst index ec201aa3d5..9d7fb95d68 100644 --- a/doc/source/command-objects/backup.rst +++ b/doc/source/command-objects/backup.rst @@ -2,7 +2,7 @@ backup ====== -Volume v1 +Block Storage v1 backup create ------------- diff --git a/doc/source/command-objects/extension.rst b/doc/source/command-objects/extension.rst index 8f39a62529..dff30fa137 100644 --- a/doc/source/command-objects/extension.rst +++ b/doc/source/command-objects/extension.rst @@ -34,7 +34,7 @@ List API extensions .. option:: --volume - List extensions for the Volume API + List extensions for the Block Storage API .. option:: --long diff --git a/doc/source/command-objects/limits.rst b/doc/source/command-objects/limits.rst index 0d466af67f..6a7509f204 100644 --- a/doc/source/command-objects/limits.rst +++ b/doc/source/command-objects/limits.rst @@ -2,14 +2,14 @@ limits ====== -The Compute and Volume APIs have resource usage limits. +The Compute and Block Storage APIs have resource usage limits. -Compute v2, Volume v1 +Compute v2, Block Storage v1 limits show ----------- -Show compute and volume limits +Show compute and block storage limits .. program:: limits show .. code:: bash diff --git a/doc/source/command-objects/quota.rst b/doc/source/command-objects/quota.rst index 5ea49f8c52..98e6df3317 100644 --- a/doc/source/command-objects/quota.rst +++ b/doc/source/command-objects/quota.rst @@ -4,7 +4,7 @@ quota Resource quotas appear in multiple APIs, OpenStackClient presents them as a single object with multiple properties. -Compute v2, Volume v1 +Compute v2, Block Storage v1 quota set --------- @@ -26,7 +26,7 @@ Set quotas for project [--properties ] [--ram ] - # Volume settings + # Block Storage settings [--gigabytes ] [--snapshots ] [--volumes ] @@ -51,7 +51,7 @@ Set quotas for class [--properties ] [--ram ] - # Volume settings + # Block Storage settings [--gigabytes ] [--snapshots ] [--volumes ] @@ -161,4 +161,4 @@ Show quotas for project .. _quota_show-class: .. describe:: - Class to show \ No newline at end of file + Class to show diff --git a/doc/source/command-objects/snapshot.rst b/doc/source/command-objects/snapshot.rst index 7bfd1d9203..307db2e1a1 100644 --- a/doc/source/command-objects/snapshot.rst +++ b/doc/source/command-objects/snapshot.rst @@ -2,7 +2,7 @@ snapshot ======== -Volume v1 +Block Storage v1 snapshot create --------------- diff --git a/doc/source/command-objects/volume-qos.rst b/doc/source/command-objects/volume-qos.rst index d0fe36f940..2d9d14a4f0 100644 --- a/doc/source/command-objects/volume-qos.rst +++ b/doc/source/command-objects/volume-qos.rst @@ -2,7 +2,7 @@ volume qos ========== -volume v1, v2 +Block Storage v1, v2 volume qos associate -------------------- diff --git a/doc/source/command-objects/volume-type.rst b/doc/source/command-objects/volume-type.rst index 0edd742aee..8a11384790 100644 --- a/doc/source/command-objects/volume-type.rst +++ b/doc/source/command-objects/volume-type.rst @@ -2,7 +2,7 @@ volume type =========== -Volume v1, v2 +Block Storage v1, v2 volume type create ------------------ diff --git a/doc/source/command-objects/volume.rst b/doc/source/command-objects/volume.rst index cb52c560ec..bf89dc44e0 100644 --- a/doc/source/command-objects/volume.rst +++ b/doc/source/command-objects/volume.rst @@ -2,7 +2,7 @@ volume ====== -Volume v1, v2 +Block Storage v1, v2 volume create ------------- diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 90aa481e94..51b42aba5e 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -70,7 +70,7 @@ the API resources will be merged, as in the ``quota`` object that has options referring to both Compute and Volume quotas. * ``access token``: (**Identity**) long-lived OAuth-based token -* ``availability zone``: (**Compute**) a logical partition of hosts or volume services +* ``availability zone``: (**Compute**) a logical partition of hosts or block storage services * ``aggregate``: (**Compute**) a grouping of servers * ``backup``: (**Volume**) a volume copy * ``catalog``: (**Identity**) service catalog diff --git a/doc/source/index.rst b/doc/source/index.rst index 50b1fd24a6..4c8170ea5e 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -3,8 +3,9 @@ OpenStackClient =============== OpenStackClient (aka OSC) is a command-line client for OpenStack that -brings the command set for Compute, Identity, Image, Object Storage and Volume -APIs together in a single shell with a uniform command structure. +brings the command set for Compute, Identity, Image, Object Storage and +Block Storage APIs together in a single shell with a uniform command +structure. User Documentation ------------------ diff --git a/doc/source/man/openstack.rst b/doc/source/man/openstack.rst index 3e47635e0f..a33b1891b4 100644 --- a/doc/source/man/openstack.rst +++ b/doc/source/man/openstack.rst @@ -171,7 +171,7 @@ project support. The object names may consist of multiple words to compose a unique name. Occasionally when multiple APIs have a common name with common overlapping purposes there will be options to select which object to use, or the API resources will be merged, as in the ``quota`` object that has options -referring to both Compute and Volume quotas. +referring to both Compute and Block Storage quotas. Command Actions --------------- diff --git a/doc/source/plugins.rst b/doc/source/plugins.rst index 5f82cc6879..38884a2342 100644 --- a/doc/source/plugins.rst +++ b/doc/source/plugins.rst @@ -11,8 +11,9 @@ Adoption ======== OpenStackClient promises to provide first class support for the following -OpenStack services: Compute, Identity, Image, Storage, Volume and Network. -These services are considered essential to any OpenStack deployment. +OpenStack services: Compute, Identity, Image, Object Storage, Block Storage +and Network. These services are considered essential to any OpenStack +deployment. Other OpenStack services, such as Orchestration or Telemetry may create an OpenStackClient plugin. The source code will not be hosted by diff --git a/doc/source/releases.rst b/doc/source/releases.rst index 148402bbde..b6e64fa29e 100644 --- a/doc/source/releases.rst +++ b/doc/source/releases.rst @@ -133,12 +133,12 @@ Release Notes * Add ``--log-file`` option support -* Set default Volume API version to ``2`` +* Set default Block Storage API version to ``2`` 1.6.0 (10 Aug 2015) =================== -* Added support for Volume v2 APIs +* Added support for Block Storage v2 APIs Blueprint `volume-v2 `_ * Backup diff --git a/openstackclient/common/extension.py b/openstackclient/common/extension.py index dad7ed6285..4bca4ba146 100644 --- a/openstackclient/common/extension.py +++ b/openstackclient/common/extension.py @@ -49,7 +49,7 @@ def get_parser(self, prog_name): '--volume', action='store_true', default=False, - help='List extensions for the Volume API') + help='List extensions for the Block Storage API') parser.add_argument( '--long', action='store_true', @@ -95,7 +95,7 @@ def take_action(self, parsed_args): try: data += volume_client.list_extensions.show_all() except Exception: - message = "Extensions list not supported by Volume API" + message = "Extensions list not supported by Block Storage API" self.log.warning(message) # Resource classes for the above diff --git a/openstackclient/common/limits.py b/openstackclient/common/limits.py index 582f70c580..577036702d 100644 --- a/openstackclient/common/limits.py +++ b/openstackclient/common/limits.py @@ -25,7 +25,7 @@ class ShowLimits(lister.Lister): - """Show compute and volume limits""" + """Show compute and block storage limits""" log = logging.getLogger(__name__ + '.ShowLimits') From c27f9e3c6c8a781230cc5d19420b29a0c47e9bc6 Mon Sep 17 00:00:00 2001 From: xiexs Date: Sat, 28 Nov 2015 19:07:20 +0800 Subject: [PATCH 0397/3095] Fix a bug of "openstack volume delete" While multi volumes specified for the osc "openstack volume delete", only the last volume is deleted. This patch tries to fix it. Change-Id: I171b2869f85b29c88fda16eaf5bf163c55795df0 Closes-Bug: #1520541 --- openstackclient/volume/v2/volume.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index 430d12285a..925b01d435 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -185,10 +185,10 @@ def take_action(self, parsed_args): for volume in parsed_args.volumes: volume_obj = utils.find_resource( volume_client.volumes, volume) - if parsed_args.force: - volume_client.volumes.force_delete(volume_obj.id) - else: - volume_client.volumes.delete(volume_obj.id) + if parsed_args.force: + volume_client.volumes.force_delete(volume_obj.id) + else: + volume_client.volumes.delete(volume_obj.id) return From af5dbf72c4bf5cd0c8b59bf1a914706eff88e21a Mon Sep 17 00:00:00 2001 From: xiexs Date: Thu, 26 Nov 2015 21:00:04 -0500 Subject: [PATCH 0398/3095] Add "openstack server unshelve" into OSC The unshelve operation is not supported by OSC, and this patch tries to add it. Change-Id: Ic60a4616cb63ad21c1a3c8e02611da8bad3a8bd0 Implements: blueprint introduce-shelve-into-osc --- doc/source/command-objects/server.rst | 15 ++++ doc/source/commands.rst | 3 +- openstackclient/compute/v2/server.py | 75 ++++++++++++------- .../tests/compute/v2/test_server.py | 60 ++++++++++----- setup.cfg | 1 + 5 files changed, 108 insertions(+), 46 deletions(-) diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index 6ae4d2543d..657cc5641f 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -720,3 +720,18 @@ Unset server properties .. describe:: Server (name or ID) + +server unshelve +--------------- + +Unshelve server(s) + +.. program:: server unshelve +.. code:: bash + + os server unshelve + [ ...] + +.. describe:: + + Server(s) to unshelve (name or ID) diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 90aa481e94..e0742ab441 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -183,7 +183,7 @@ Those actions with an opposite action are noted in parens if applicable. * ``revoke`` (``issue``) - revoke a token * ``save`` - download an object locally * ``set`` (``unset``) - set a property on the object, formerly called metadata -* ``shelve`` (``unshelve``) - shelve one or more server +* ``shelve`` (``unshelve``) - shelve one or more servers * ``show`` - display detailed information about the specific object * ``start`` (``stop``) - start one or more servers * ``stop`` (``start``) - stop one or more servers @@ -192,6 +192,7 @@ Those actions with an opposite action are noted in parens if applicable. * ``unpause`` (``pause``) - return one or more paused servers to running state * ``unrescue`` (``rescue``) - return a server to normal boot mode * ``unset`` (``set``) - remove an attribute of the object +* ``unshelve`` (``shelve``) - unshelve one or more servers Implementation diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 875b9a137d..3f39210f16 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1352,6 +1352,31 @@ def take_action(self, parsed_args): raise exceptions.CommandError(msg) +class ShelveServer(command.Command): + """Shelve server(s)""" + + log = logging.getLogger(__name__ + '.ShelveServer') + + def get_parser(self, prog_name): + parser = super(ShelveServer, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + nargs='+', + help=_('Server(s) to shelve (name or ID)'), + ) + return parser + + @utils.log_method(log) + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + for server in parsed_args.server: + utils.find_resource( + compute_client.servers, + server, + ).shelve() + + class ShowServer(show.ShowOne): """Show server details""" @@ -1389,31 +1414,6 @@ def take_action(self, parsed_args): return zip(*sorted(six.iteritems(data))) -class ShelveServer(command.Command): - """Shelve server(s)""" - - log = logging.getLogger(__name__ + '.ShelveServer') - - def get_parser(self, prog_name): - parser = super(ShelveServer, self).get_parser(prog_name) - parser.add_argument( - 'server', - metavar='', - nargs='+', - help=_('Server(s) to shelve (name or ID)'), - ) - return parser - - @utils.log_method(log) - def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - for server in parsed_args.server: - utils.find_resource( - compute_client.servers, - server, - ).shelve() - - class SshServer(command.Command): """Ssh to server""" @@ -1748,3 +1748,28 @@ def take_action(self, parsed_args): server, parsed_args.property, ) + + +class UnshelveServer(command.Command): + """Unshelve server(s)""" + + log = logging.getLogger(__name__ + '.UnshelveServer') + + def get_parser(self, prog_name): + parser = super(UnshelveServer, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + nargs='+', + help=_('Server(s) to unshelve (name or ID)'), + ) + return parser + + @utils.log_method(log) + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + for server in parsed_args.server: + utils.find_resource( + compute_client.servers, + server, + ).unshelve() diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index b3bf5e015b..c22e59a713 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -799,6 +799,26 @@ def test_server_resume_multi_servers(self): self.run_method_with_servers('resume', 3) +class TestServerShelve(TestServer): + + def setUp(self): + super(TestServerShelve, self).setUp() + + # Get the command object to test + self.cmd = server.ShelveServer(self.app, None) + + # Set shelve method to be tested. + self.methods = { + 'shelve': None, + } + + def test_shelve_one_server(self): + self.run_method_with_servers('shelve', 1) + + def test_shelve_multi_servers(self): + self.run_method_with_servers('shelve', 3) + + class TestServerStart(TestServer): def setUp(self): @@ -899,6 +919,26 @@ def test_server_unpause_multi_servers(self): self.run_method_with_servers('unpause', 3) +class TestServerUnshelve(TestServer): + + def setUp(self): + super(TestServerUnshelve, self).setUp() + + # Get the command object to test + self.cmd = server.UnshelveServer(self.app, None) + + # Set unshelve method to be tested. + self.methods = { + 'unshelve': None, + } + + def test_unshelve_one_server(self): + self.run_method_with_servers('unshelve', 1) + + def test_unshelve_multi_servers(self): + self.run_method_with_servers('unshelve', 3) + + class TestServerGeneral(testtools.TestCase): OLD = { 'private': [ @@ -949,23 +989,3 @@ def test_get_ip_address(self): server._get_ip_address, self.OLD, 'public', [4, 6]) self.assertRaises(exceptions.CommandError, server._get_ip_address, self.OLD, 'private', [6]) - - -class TestShelveServer(TestServer): - - def setUp(self): - super(TestShelveServer, self).setUp() - - # Get the command object to test - self.cmd = server.ShelveServer(self.app, None) - - # Set shelve method to be tested. - self.methods = { - 'shelve': None, - } - - def test_shelve_one_server(self): - self.run_method_with_servers('shelve', 1) - - def test_shelve_multi_servers(self): - self.run_method_with_servers('shelve', 3) diff --git a/setup.cfg b/setup.cfg index 6760e84d40..a971446874 100644 --- a/setup.cfg +++ b/setup.cfg @@ -140,6 +140,7 @@ openstack.compute.v2 = server_unpause = openstackclient.compute.v2.server:UnpauseServer server_unrescue = openstackclient.compute.v2.server:UnrescueServer server_unset = openstackclient.compute.v2.server:UnsetServer + server_unshelve = openstackclient.compute.v2.server:UnshelveServer usage_list = openstackclient.compute.v2.usage:ListUsage usage_show = openstackclient.compute.v2.usage:ShowUsage From f9ca752177dbce93dc2bc4944905b9057e50e814 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 28 Nov 2015 14:05:20 +0800 Subject: [PATCH 0399/3095] Refactor: Abstract columns out in TestFlavorList to avoid redundant code. The columns has been set in each test case of TestFlavorList, which is not necessary. This patch abstract it out and remove all redundant code. Change-Id: Ideb2872f073755ac251bd603fd906410ea86c4ef Implements: blueprint improve-flavor-unit-tes --- .../tests/compute/v2/test_flavor.py | 73 +++++-------------- 1 file changed, 20 insertions(+), 53 deletions(-) diff --git a/openstackclient/tests/compute/v2/test_flavor.py b/openstackclient/tests/compute/v2/test_flavor.py index 523104f091..3028a31352 100644 --- a/openstackclient/tests/compute/v2/test_flavor.py +++ b/openstackclient/tests/compute/v2/test_flavor.py @@ -47,6 +47,21 @@ def setUp(self): class TestFlavorList(TestFlavor): + columns = ( + 'ID', + 'Name', + 'RAM', + 'Disk', + 'Ephemeral', + 'VCPUs', + 'Is Public', + ) + columns_long = columns + ( + 'Swap', + 'RXTX Factor', + 'Properties' + ) + def setUp(self): super(TestFlavorList, self).setUp() @@ -85,16 +100,7 @@ def test_flavor_list_no_options(self): **kwargs ) - collist = ( - 'ID', - 'Name', - 'RAM', - 'Disk', - 'Ephemeral', - 'VCPUs', - 'Is Public', - ) - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = (( compute_fakes.flavor_id, compute_fakes.flavor_name, @@ -130,16 +136,7 @@ def test_flavor_list_all_flavors(self): **kwargs ) - collist = ( - 'ID', - 'Name', - 'RAM', - 'Disk', - 'Ephemeral', - 'VCPUs', - 'Is Public', - ) - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = (( compute_fakes.flavor_id, compute_fakes.flavor_name, @@ -175,16 +172,7 @@ def test_flavor_list_private_flavors(self): **kwargs ) - collist = ( - 'ID', - 'Name', - 'RAM', - 'Disk', - 'Ephemeral', - 'VCPUs', - 'Is Public', - ) - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = (( compute_fakes.flavor_id, compute_fakes.flavor_name, @@ -220,16 +208,7 @@ def test_flavor_list_public_flavors(self): **kwargs ) - collist = ( - 'ID', - 'Name', - 'RAM', - 'Disk', - 'Ephemeral', - 'VCPUs', - 'Is Public', - ) - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = (( compute_fakes.flavor_id, compute_fakes.flavor_name, @@ -265,19 +244,7 @@ def test_flavor_list_long(self): **kwargs ) - collist = ( - 'ID', - 'Name', - 'RAM', - 'Disk', - 'Ephemeral', - 'VCPUs', - 'Is Public', - 'Swap', - 'RXTX Factor', - 'Properties' - ) - self.assertEqual(collist, columns) + self.assertEqual(self.columns_long, columns) datalist = (( compute_fakes.flavor_id, compute_fakes.flavor_name, From f19ff68e59e0fde9f46379dd225bf4d715d2d413 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 28 Nov 2015 14:26:09 +0800 Subject: [PATCH 0400/3095] Refactor: Abstract datalist out in TestFlavorList to avoid redundant code. datalist has been set in each test case in TestFlavorList, which is not necessary. This patch abstract it out and remove all redundant code. Change-Id: I6f735f7d9fa29a8ab435adaef17ca559df1fdcee Implements: blueprint improve-flavor-unit-test --- .../tests/compute/v2/test_flavor.py | 73 +++++-------------- 1 file changed, 20 insertions(+), 53 deletions(-) diff --git a/openstackclient/tests/compute/v2/test_flavor.py b/openstackclient/tests/compute/v2/test_flavor.py index 3028a31352..6cb754f784 100644 --- a/openstackclient/tests/compute/v2/test_flavor.py +++ b/openstackclient/tests/compute/v2/test_flavor.py @@ -62,6 +62,21 @@ class TestFlavorList(TestFlavor): 'Properties' ) + data = (( + compute_fakes.flavor_id, + compute_fakes.flavor_name, + compute_fakes.flavor_ram, + '', + '', + compute_fakes.flavor_vcpus, + '' + ), ) + data_long = (data[0] + ( + '', + '', + 'property=\'value\'' + ), ) + def setUp(self): super(TestFlavorList, self).setUp() @@ -101,16 +116,7 @@ def test_flavor_list_no_options(self): ) self.assertEqual(self.columns, columns) - datalist = (( - compute_fakes.flavor_id, - compute_fakes.flavor_name, - compute_fakes.flavor_ram, - '', - '', - compute_fakes.flavor_vcpus, - '' - ), ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(tuple(self.data), tuple(data)) def test_flavor_list_all_flavors(self): arglist = [ @@ -137,16 +143,7 @@ def test_flavor_list_all_flavors(self): ) self.assertEqual(self.columns, columns) - datalist = (( - compute_fakes.flavor_id, - compute_fakes.flavor_name, - compute_fakes.flavor_ram, - '', - '', - compute_fakes.flavor_vcpus, - '' - ), ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(tuple(self.data), tuple(data)) def test_flavor_list_private_flavors(self): arglist = [ @@ -173,16 +170,7 @@ def test_flavor_list_private_flavors(self): ) self.assertEqual(self.columns, columns) - datalist = (( - compute_fakes.flavor_id, - compute_fakes.flavor_name, - compute_fakes.flavor_ram, - '', - '', - compute_fakes.flavor_vcpus, - '' - ), ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(tuple(self.data), tuple(data)) def test_flavor_list_public_flavors(self): arglist = [ @@ -209,16 +197,7 @@ def test_flavor_list_public_flavors(self): ) self.assertEqual(self.columns, columns) - datalist = (( - compute_fakes.flavor_id, - compute_fakes.flavor_name, - compute_fakes.flavor_ram, - '', - '', - compute_fakes.flavor_vcpus, - '' - ), ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(tuple(self.data), tuple(data)) def test_flavor_list_long(self): arglist = [ @@ -245,19 +224,7 @@ def test_flavor_list_long(self): ) self.assertEqual(self.columns_long, columns) - datalist = (( - compute_fakes.flavor_id, - compute_fakes.flavor_name, - compute_fakes.flavor_ram, - '', - '', - compute_fakes.flavor_vcpus, - '', - '', - '', - 'property=\'value\'' - ), ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(tuple(self.data_long), tuple(data)) class TestFlavorSet(TestFlavor): From 109672fecb72e5cef4bc9163d6b93fd27ad98c32 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 28 Nov 2015 15:25:39 +0800 Subject: [PATCH 0401/3095] Refactor: Move FakeFlavorResource to compute_fakes.py. Just like FakeServer and FakeResource, FakeFlavorResource should be in compute_fakes.py. Change-Id: I8315256fb90377605fa2190e24c9674f6aaf6efd Implements: blueprint improve-flavor-unit-test --- openstackclient/tests/compute/v2/fakes.py | 21 ++++++++++++++++++ .../tests/compute/v2/test_flavor.py | 22 +++---------------- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 32161ddc9f..5513f1b1ea 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -209,3 +209,24 @@ def get_servers(servers=None, count=2): if servers is None: servers = FakeServer.create_servers(count) return mock.MagicMock(side_effect=servers) + + +class FakeFlavorResource(fakes.FakeResource): + """Fake flavor object's methods to help test. + + The flavor object has three methods to get, set, unset its properties. + Need to fake them, otherwise the functions to be tested won't run properly. + """ + + # Fake properties. + _keys = {'property': 'value'} + + def set_keys(self, args): + self._keys.update(args) + + def unset_keys(self, keys): + for key in keys: + self._keys.pop(key, None) + + def get_keys(self): + return self._keys diff --git a/openstackclient/tests/compute/v2/test_flavor.py b/openstackclient/tests/compute/v2/test_flavor.py index 6cb754f784..196723ead6 100644 --- a/openstackclient/tests/compute/v2/test_flavor.py +++ b/openstackclient/tests/compute/v2/test_flavor.py @@ -17,22 +17,6 @@ from openstackclient.compute.v2 import flavor from openstackclient.tests.compute.v2 import fakes as compute_fakes -from openstackclient.tests import fakes - - -class FakeFlavorResource(fakes.FakeResource): - - _keys = {'property': 'value'} - - def set_keys(self, args): - self._keys.update(args) - - def unset_keys(self, keys): - for key in keys: - self._keys.pop(key, None) - - def get_keys(self): - return self._keys class TestFlavor(compute_fakes.TestComputev2): @@ -81,7 +65,7 @@ def setUp(self): super(TestFlavorList, self).setUp() self.flavors_mock.list.return_value = [ - FakeFlavorResource( + compute_fakes.FakeFlavorResource( None, copy.deepcopy(compute_fakes.FLAVOR), loaded=True, @@ -232,7 +216,7 @@ class TestFlavorSet(TestFlavor): def setUp(self): super(TestFlavorSet, self).setUp() - self.flavors_mock.find.return_value = FakeFlavorResource( + self.flavors_mock.find.return_value = compute_fakes.FakeFlavorResource( None, copy.deepcopy(compute_fakes.FLAVOR), loaded=True, @@ -265,7 +249,7 @@ class TestFlavorUnset(TestFlavor): def setUp(self): super(TestFlavorUnset, self).setUp() - self.flavors_mock.find.return_value = FakeFlavorResource( + self.flavors_mock.find.return_value = compute_fakes.FakeFlavorResource( None, copy.deepcopy(compute_fakes.FLAVOR), loaded=True, From 0de260e8be0db504de375c5f77928931fbb0cb3b Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 28 Nov 2015 11:15:54 +0800 Subject: [PATCH 0402/3095] Introduce class FakeFlavor to fake one or more flavors. Change-Id: I1b20e7d50e478ce8114ca08aa455b7acad4ea7f5 Implements: blueprint improve-flavor-unit-test --- openstackclient/tests/compute/v2/fakes.py | 64 +++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 5513f1b1ea..ffa2f95e7a 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -230,3 +230,67 @@ def unset_keys(self, keys): def get_keys(self): return self._keys + + +class FakeFlavor(object): + """Fake one or more flavors.""" + + @staticmethod + def create_one_flavor(attrs={}): + """Create a fake flavor. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeFlavorResource object, with id, name, ram, vcpus, properties + """ + # Set default attributes. + flavor_info = { + 'id': 'flavor-id-' + uuid.uuid4().hex, + 'name': 'flavor-name-' + uuid.uuid4().hex, + 'ram': 8192, + 'vcpus': 4, + } + + # Overwrite default attributes. + flavor_info.update(attrs) + + flavor = FakeFlavorResource(info=copy.deepcopy(flavor_info), + loaded=True) + return flavor + + @staticmethod + def create_flavors(attrs={}, count=2): + """Create multiple fake flavors. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of flavors to fake + :return: + A list of FakeFlavorResource objects faking the flavors + """ + flavors = [] + for i in range(0, count): + flavors.append(FakeFlavor.create_one_flavor(attrs)) + + return flavors + + @staticmethod + def get_flavors(flavors=None, count=2): + """Get an iterable MagicMock object with a list of faked flavors. + + If flavors list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List flavors: + A list of FakeFlavorResource objects faking flavors + :param int count: + The number of flavors to fake + :return: + An iterable Mock object with side_effect set to a list of faked + flavors + """ + if flavors is None: + flavors = FakeServer.create_flavors(count) + return mock.MagicMock(side_effect=flavors) From 8d42da518664ae60ebf602be8849fa37d661f95d Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 28 Nov 2015 15:17:50 +0800 Subject: [PATCH 0403/3095] Use FakeFlavor in TestFlavorList. Change-Id: I3595877bed41bc476934ca924f1f9c8c0ad79176 Implements: blueprint improve-flavor-unit-test --- .../tests/compute/v2/test_flavor.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/openstackclient/tests/compute/v2/test_flavor.py b/openstackclient/tests/compute/v2/test_flavor.py index 196723ead6..bafa06c304 100644 --- a/openstackclient/tests/compute/v2/test_flavor.py +++ b/openstackclient/tests/compute/v2/test_flavor.py @@ -31,6 +31,9 @@ def setUp(self): class TestFlavorList(TestFlavor): + # Return value of self.flavors_mock.list(). + flavors = compute_fakes.FakeFlavor.create_flavors(count=1) + columns = ( 'ID', 'Name', @@ -47,12 +50,12 @@ class TestFlavorList(TestFlavor): ) data = (( - compute_fakes.flavor_id, - compute_fakes.flavor_name, - compute_fakes.flavor_ram, + flavors[0].id, + flavors[0].name, + flavors[0].ram, '', '', - compute_fakes.flavor_vcpus, + flavors[0].vcpus, '' ), ) data_long = (data[0] + ( @@ -64,13 +67,7 @@ class TestFlavorList(TestFlavor): def setUp(self): super(TestFlavorList, self).setUp() - self.flavors_mock.list.return_value = [ - compute_fakes.FakeFlavorResource( - None, - copy.deepcopy(compute_fakes.FLAVOR), - loaded=True, - ), - ] + self.flavors_mock.list.return_value = self.flavors # Get the command object to test self.cmd = flavor.ListFlavor(self.app, None) From dfbf41c55fb759184e60663ee667a1e2ad6e19fa Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 28 Nov 2015 16:05:14 +0800 Subject: [PATCH 0404/3095] Use FakeFlavor in TestFlavorSet. Change-Id: I335298b07afb3f969c76748527dda06cb5393fa8 Implements: blueprint improve-flavor-unit-test --- openstackclient/tests/compute/v2/test_flavor.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/openstackclient/tests/compute/v2/test_flavor.py b/openstackclient/tests/compute/v2/test_flavor.py index bafa06c304..8ad1f85e20 100644 --- a/openstackclient/tests/compute/v2/test_flavor.py +++ b/openstackclient/tests/compute/v2/test_flavor.py @@ -210,14 +210,13 @@ def test_flavor_list_long(self): class TestFlavorSet(TestFlavor): + # Return value of self.flavors_mock.find(). + flavor = compute_fakes.FakeFlavor.create_one_flavor() + def setUp(self): super(TestFlavorSet, self).setUp() - self.flavors_mock.find.return_value = compute_fakes.FakeFlavorResource( - None, - copy.deepcopy(compute_fakes.FLAVOR), - loaded=True, - ) + self.flavors_mock.find.return_value = self.flavor self.cmd = flavor.SetFlavor(self.app, None) From db516b2c96dd449daad5ff6c9479d87f1457f410 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 28 Nov 2015 16:09:01 +0800 Subject: [PATCH 0405/3095] Use FakeFlavor in TestFlavorUnset. Change-Id: Ifbd360db39d380efd9632300367b13283ac75f54 Implements: blueprint improve-flavor-unit-test --- openstackclient/tests/compute/v2/test_flavor.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/openstackclient/tests/compute/v2/test_flavor.py b/openstackclient/tests/compute/v2/test_flavor.py index 8ad1f85e20..095b5f2812 100644 --- a/openstackclient/tests/compute/v2/test_flavor.py +++ b/openstackclient/tests/compute/v2/test_flavor.py @@ -13,8 +13,6 @@ # under the License. # -import copy - from openstackclient.compute.v2 import flavor from openstackclient.tests.compute.v2 import fakes as compute_fakes @@ -242,14 +240,13 @@ def test_flavor_set(self): class TestFlavorUnset(TestFlavor): + # Return value of self.flavors_mock.find(). + flavor = compute_fakes.FakeFlavor.create_one_flavor() + def setUp(self): super(TestFlavorUnset, self).setUp() - self.flavors_mock.find.return_value = compute_fakes.FakeFlavorResource( - None, - copy.deepcopy(compute_fakes.FLAVOR), - loaded=True, - ) + self.flavors_mock.find.return_value = self.flavor self.cmd = flavor.UnsetFlavor(self.app, None) From c5a55d1370d92ffc6eb0f142633e4d4883453b1b Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 28 Nov 2015 16:24:41 +0800 Subject: [PATCH 0406/3095] User FakeFlavor in TestServerResize. Change-Id: Iac9b4583befaa4eb79ec59c39b97c613884ef2f7 Implements: blueprint improve-flavor-unit-test --- openstackclient/tests/compute/v2/test_server.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index b3bf5e015b..726d75e052 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -666,11 +666,8 @@ def setUp(self): self.servers_mock.revert_resize.return_value = None # This is the return value for utils.find_resource() - self.flavors_get_return_value = fakes.FakeResource( - None, - copy.deepcopy(compute_fakes.FLAVOR), - loaded=True, - ) + self.flavors_get_return_value = \ + compute_fakes.FakeFlavor.create_one_flavor() self.flavors_mock.get.return_value = self.flavors_get_return_value # Get the command object to test @@ -700,11 +697,11 @@ def test_server_resize_no_options(self): def test_server_resize(self): arglist = [ - '--flavor', compute_fakes.flavor_id, + '--flavor', self.flavors_get_return_value.id, self.server.id, ] verifylist = [ - ('flavor', compute_fakes.flavor_id), + ('flavor', self.flavors_get_return_value.id), ('confirm', False), ('revert', False), ('server', self.server.id), @@ -718,7 +715,7 @@ def test_server_resize(self): self.server.id, ) self.flavors_mock.get.assert_called_with( - compute_fakes.flavor_id, + self.flavors_get_return_value.id, ) self.servers_mock.resize.assert_called_with( From b7f62058ad19ccba6574c5f791d87620e62be6ea Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 28 Nov 2015 16:29:55 +0800 Subject: [PATCH 0407/3095] Use FakeFlavor in TestServerCreate. Change-Id: Ib82f845258b1ad78cbc8b23d28fa42e7ccc8097a Implements: blueprint improve-flavor-unit-test --- .../tests/compute/v2/test_server.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 726d75e052..feb6054766 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -110,11 +110,7 @@ def setUp(self): ) self.cimages_mock.get.return_value = self.image - self.flavor = fakes.FakeResource( - None, - copy.deepcopy(compute_fakes.FLAVOR), - loaded=True, - ) + self.flavor = compute_fakes.FakeFlavor.create_one_flavor() self.flavors_mock.get.return_value = self.flavor self.volume = fakes.FakeResource( @@ -192,7 +188,7 @@ def test_server_create_minimal(self): self.assertEqual(collist, columns) datalist = ( '', - 'Large ()', + self.flavor.name + ' ()', self.new_server.id, self.new_server.name, self.new_server.networks, @@ -276,7 +272,7 @@ def test_server_create_with_network(self): self.assertEqual(collist, columns) datalist = ( '', - 'Large ()', + self.flavor.name + ' ()', self.new_server.id, self.new_server.name, self.new_server.networks, @@ -349,7 +345,7 @@ def test_server_create_userdata(self, mock_open): self.assertEqual(collist, columns) datalist = ( '', - 'Large ()', + self.flavor.name + ' ()', self.new_server.id, self.new_server.name, self.new_server.networks, @@ -360,13 +356,13 @@ def test_server_create_userdata(self, mock_open): def test_server_create_with_block_device_mapping(self): arglist = [ '--image', 'image1', - '--flavor', compute_fakes.flavor_id, + '--flavor', self.flavor.id, '--block-device-mapping', compute_fakes.block_device_mapping, self.new_server.name, ] verifylist = [ ('image', 'image1'), - ('flavor', compute_fakes.flavor_id), + ('flavor', self.flavor.id), ('block_device_mapping', [compute_fakes.block_device_mapping]), ('config_drive', False), ('server_name', self.new_server.name), @@ -418,7 +414,7 @@ def test_server_create_with_block_device_mapping(self): self.assertEqual(collist, columns) datalist = ( '', - 'Large ()', + self.flavor.name + ' ()', self.new_server.id, self.new_server.name, self.new_server.networks, From d236a783d250ccbe51da2914e438ee37976886a2 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 28 Nov 2015 16:33:11 +0800 Subject: [PATCH 0408/3095] Remove old fake flavor data. The new class FakeFlavor has been introduced to fake one or more flavors. So use it and remove the old fake flavor data. Change-Id: Ie3a33b36ae6e597c6a0b1d17ad13c73cf4b73bc9 Implements: blueprint improve-flavor-unit-test --- openstackclient/tests/compute/v2/fakes.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index ffa2f95e7a..0d6cd43058 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -46,18 +46,6 @@ 'links': extension_links, } -flavor_id = 'm1.large' -flavor_name = 'Large' -flavor_ram = 8192 -flavor_vcpus = 4 - -FLAVOR = { - 'id': flavor_id, - 'name': flavor_name, - 'ram': flavor_ram, - 'vcpus': flavor_vcpus, -} - floating_ip_num = 100 fix_ip_num = 100 injected_file_num = 100 From e2d0684876038b68885487176365b776ade6ca67 Mon Sep 17 00:00:00 2001 From: venkatamahesh Date: Wed, 16 Sep 2015 23:24:13 +0530 Subject: [PATCH 0409/3095] Change the home-page value in setup.cfg Change-Id: Ib0ea7b2b6271f3fb3d0818c7fa52a51682658810 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 6760e84d40..c8b85cf123 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,7 +5,7 @@ description-file = README.rst author = OpenStack author-email = openstack-dev@lists.openstack.org -home-page = http://wiki.openstack.org/OpenStackClient +home-page = http://docs.openstack.org/developer/python-openstackclient classifier = Environment :: OpenStack Intended Audience :: Information Technology From 342fd158e9a0744ff75e9234c996b6b5ef1907ff Mon Sep 17 00:00:00 2001 From: xiexs Date: Mon, 23 Nov 2015 08:19:00 -0500 Subject: [PATCH 0410/3095] Add status column for "openstack image list" Actually, the status column is useful for the user. So, it`s better to output this info by default (or, user had to specify the extra option, i.e. --long). Change-Id: Id2a9f86f0de5310f8f5ff9a46bf1b7411094b519 Closes-Bug: #1519181 --- openstackclient/image/v1/image.py | 9 +++++-- openstackclient/image/v2/image.py | 2 +- openstackclient/tests/image/v1/test_image.py | 25 ++++++++++++-------- openstackclient/tests/image/v2/test_image.py | 20 ++++++++++------ 4 files changed, 36 insertions(+), 20 deletions(-) diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index 35e9ef43ea..4ebc8f93e4 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -354,7 +354,12 @@ def take_action(self, parsed_args): kwargs['public'] = True if parsed_args.private: kwargs['private'] = True - kwargs['detailed'] = bool(parsed_args.property or parsed_args.long) + # Note: We specifically need to do that below to get the 'status' + # column. + # + # Always set kwargs['detailed'] to True, and then filter the columns + # according to whether the --long option is specified or not. + kwargs['detailed'] = True if parsed_args.long: columns = ( @@ -382,7 +387,7 @@ def take_action(self, parsed_args): 'Properties', ) else: - columns = ("ID", "Name") + columns = ("ID", "Name", "Status") column_headers = columns # List of image data received diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 5552f033c4..c0fb5b589d 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -417,7 +417,7 @@ def take_action(self, parsed_args): 'Tags', ) else: - columns = ("ID", "Name") + columns = ("ID", "Name", "Status") column_headers = columns # List of image data received diff --git a/openstackclient/tests/image/v1/test_image.py b/openstackclient/tests/image/v1/test_image.py index d10d3b1577..4d964bdb56 100644 --- a/openstackclient/tests/image/v1/test_image.py +++ b/openstackclient/tests/image/v1/test_image.py @@ -261,16 +261,17 @@ def test_image_list_no_options(self): # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( - detailed=False, + detailed=True, marker=image_fakes.image_id, ) - collist = ('ID', 'Name') + collist = ('ID', 'Name', 'Status') self.assertEqual(collist, columns) datalist = (( image_fakes.image_id, image_fakes.image_name, + '', ), ) self.assertEqual(datalist, tuple(data)) @@ -288,17 +289,18 @@ def test_image_list_public_option(self): # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( - detailed=False, + detailed=True, public=True, marker=image_fakes.image_id, ) - collist = ('ID', 'Name') + collist = ('ID', 'Name', 'Status') self.assertEqual(collist, columns) datalist = (( image_fakes.image_id, image_fakes.image_name, + '', ), ) self.assertEqual(datalist, tuple(data)) @@ -316,17 +318,18 @@ def test_image_list_private_option(self): # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( - detailed=False, + detailed=True, private=True, marker=image_fakes.image_id, ) - collist = ('ID', 'Name') + collist = ('ID', 'Name', 'Status') self.assertEqual(collist, columns) datalist = (( image_fakes.image_id, image_fakes.image_name, + '', ), ) self.assertEqual(datalist, tuple(data)) @@ -401,12 +404,13 @@ def test_image_list_property_option(self, sf_mock): property_field='properties', ) - collist = ('ID', 'Name') + collist = ('ID', 'Name', 'Status') self.assertEqual(columns, collist) datalist = (( image_fakes.image_id, image_fakes.image_name, + '', ), ) self.assertEqual(datalist, tuple(data)) @@ -423,7 +427,7 @@ def test_image_list_sort_option(self, si_mock): # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( - detailed=False, + detailed=True, marker=image_fakes.image_id, ) si_mock.assert_called_with( @@ -431,12 +435,13 @@ def test_image_list_sort_option(self, si_mock): 'name:asc' ) - collist = ('ID', 'Name') + collist = ('ID', 'Name', 'Status') self.assertEqual(collist, columns) datalist = (( image_fakes.image_id, - image_fakes.image_name + image_fakes.image_name, + '', ), ) self.assertEqual(datalist, tuple(data)) diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index f71407a68f..be73c4ca9c 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -374,12 +374,13 @@ def test_image_list_no_options(self): marker=image_fakes.image_id, ) - collist = ('ID', 'Name') + collist = ('ID', 'Name', 'Status') self.assertEqual(collist, columns) datalist = (( image_fakes.image_id, image_fakes.image_name, + '', ), ) self.assertEqual(datalist, tuple(data)) @@ -402,12 +403,13 @@ def test_image_list_public_option(self): marker=image_fakes.image_id, ) - collist = ('ID', 'Name') + collist = ('ID', 'Name', 'Status') self.assertEqual(collist, columns) datalist = (( image_fakes.image_id, image_fakes.image_name, + '', ), ) self.assertEqual(datalist, tuple(data)) @@ -430,12 +432,13 @@ def test_image_list_private_option(self): marker=image_fakes.image_id, ) - collist = ('ID', 'Name') + collist = ('ID', 'Name', 'Status') self.assertEqual(collist, columns) datalist = (( image_fakes.image_id, image_fakes.image_name, + '', ), ) self.assertEqual(datalist, tuple(data)) @@ -458,12 +461,13 @@ def test_image_list_shared_option(self): marker=image_fakes.image_id, ) - collist = ('ID', 'Name') + collist = ('ID', 'Name', 'Status') self.assertEqual(columns, collist) datalist = (( image_fakes.image_id, image_fakes.image_name, + '', ), ) self.assertEqual(datalist, tuple(data)) @@ -536,12 +540,13 @@ def test_image_list_property_option(self, sf_mock): property_field='properties', ) - collist = ('ID', 'Name') + collist = ('ID', 'Name', 'Status') self.assertEqual(columns, collist) datalist = (( image_fakes.image_id, image_fakes.image_name, + '', ), ) self.assertEqual(datalist, tuple(data)) @@ -565,12 +570,13 @@ def test_image_list_sort_option(self, si_mock): 'name:asc' ) - collist = ('ID', 'Name') + collist = ('ID', 'Name', 'Status') self.assertEqual(collist, columns) datalist = (( image_fakes.image_id, - image_fakes.image_name + image_fakes.image_name, + '', ), ) self.assertEqual(datalist, tuple(data)) From 0ccd2a8108d71fffa1fda13022d52c3a38371ce6 Mon Sep 17 00:00:00 2001 From: xiexs Date: Sun, 29 Nov 2015 22:29:57 -0500 Subject: [PATCH 0411/3095] Add project name/ID validation for "openstack quota show" A validation is necessary to check the existence of project. This patch is similar to Ia2d8c96527820e25b074e6486d3f39c5ad7eae60. Change-Id: Id8895ba7a21ecad05942619a82a87c0dc68eae53 --- openstackclient/common/quota.py | 12 +- openstackclient/tests/common/test_quota.py | 121 +++++++++++++++++++++ openstackclient/tests/compute/v2/fakes.py | 2 + 3 files changed, 132 insertions(+), 3 deletions(-) diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index c5404f0734..8a9b910f2f 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -173,13 +173,19 @@ def get_parser(self, prog_name): return parser def get_compute_volume_quota(self, client, parsed_args): + identity_client = self.app.client_manager.identity + project = utils.find_resource( + identity_client.projects, + parsed_args.project, + ).id + try: if parsed_args.quota_class: - quota = client.quota_classes.get(parsed_args.project) + quota = client.quota_classes.get(project) elif parsed_args.default: - quota = client.quotas.defaults(parsed_args.project) + quota = client.quotas.defaults(project) else: - quota = client.quotas.get(parsed_args.project) + quota = client.quotas.get(project) except Exception as e: if type(e).__name__ == 'EndpointNotFound': return {} diff --git a/openstackclient/tests/common/test_quota.py b/openstackclient/tests/common/test_quota.py index ff711a7577..485b8a8b97 100644 --- a/openstackclient/tests/common/test_quota.py +++ b/openstackclient/tests/common/test_quota.py @@ -18,6 +18,7 @@ from openstackclient.tests.compute.v2 import fakes as compute_fakes from openstackclient.tests import fakes from openstackclient.tests.identity.v2_0 import fakes as identity_fakes +from openstackclient.tests.network.v2 import fakes as network_fakes class FakeQuotaResource(fakes.FakeResource): @@ -41,13 +42,23 @@ def setUp(self): super(TestQuota, self).setUp() self.quotas_mock = self.app.client_manager.compute.quotas self.quotas_mock.reset_mock() + self.quotas_class_mock = self.app.client_manager.compute.quota_classes + self.quotas_class_mock.reset_mock() volume_mock = mock.Mock() volume_mock.quotas = mock.Mock() self.app.client_manager.volume = volume_mock self.volume_quotas_mock = volume_mock.quotas self.volume_quotas_mock.reset_mock() + self.volume_quotas_class_mock = \ + self.app.client_manager.volume.quota_classes + self.volume_quotas_class_mock.reset_mock() self.projects_mock = self.app.client_manager.identity.projects self.projects_mock.reset_mock() + self.app.client_manager.auth_ref = mock.Mock() + self.app.client_manager.auth_ref.service_catalog = mock.Mock() + self.service_catalog_mock = \ + self.app.client_manager.auth_ref.service_catalog + self.service_catalog_mock.reset_mock() class TestQuotaSet(TestQuota): @@ -172,3 +183,113 @@ def test_quota_set_volume(self): identity_fakes.project_id, **kwargs ) + + +class TestQuotaShow(TestQuota): + + def setUp(self): + super(TestQuotaShow, self).setUp() + + self.quotas_mock.get.return_value = FakeQuotaResource( + None, + copy.deepcopy(compute_fakes.QUOTA), + loaded=True, + ) + + self.quotas_mock.defaults.return_value = FakeQuotaResource( + None, + copy.deepcopy(compute_fakes.QUOTA), + loaded=True, + ) + + self.volume_quotas_mock.get.return_value = FakeQuotaResource( + None, + copy.deepcopy(compute_fakes.QUOTA), + loaded=True, + ) + + self.volume_quotas_mock.defaults.return_value = FakeQuotaResource( + None, + copy.deepcopy(compute_fakes.QUOTA), + loaded=True, + ) + + self.service_catalog_mock.get_endpoints.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.ENDPOINT), + loaded=True, + ) + ] + + self.quotas_class_mock.get.return_value = FakeQuotaResource( + None, + copy.deepcopy(compute_fakes.QUOTA), + loaded=True, + ) + + self.volume_quotas_class_mock.get.return_value = FakeQuotaResource( + None, + copy.deepcopy(compute_fakes.QUOTA), + loaded=True, + ) + + self.projects_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.PROJECT), + loaded=True, + ) + + self.app.client_manager.network = network_fakes.FakeNetworkV2Client( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN, + ) + + self.cmd = quota.ShowQuota(self.app, None) + + def test_quota_show(self): + arglist = [ + identity_fakes.project_name, + ] + verifylist = [ + ('project', identity_fakes.project_name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.quotas_mock.get.assert_called_with(identity_fakes.project_id) + + def test_quota_show_with_default(self): + arglist = [ + '--default', + identity_fakes.project_name, + ] + verifylist = [ + ('default', True), + ('project', identity_fakes.project_name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.quotas_mock.defaults.assert_called_with(identity_fakes.project_id) + + def test_quota_show_with_class(self): + arglist = [ + '--class', + identity_fakes.project_name, + ] + verifylist = [ + ('quota_class', True), + ('project', identity_fakes.project_name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.quotas_class_mock.get.assert_called_with( + identity_fakes.project_id) diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 0d6cd43058..e744f30542 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -95,6 +95,8 @@ def __init__(self, **kwargs): self.flavors.resource_class = fakes.FakeResource(None, {}) self.quotas = mock.Mock() self.quotas.resource_class = fakes.FakeResource(None, {}) + self.quota_classes = mock.Mock() + self.quota_classes.resource_class = fakes.FakeResource(None, {}) self.volumes = mock.Mock() self.volumes.resource_class = fakes.FakeResource(None, {}) self.auth_token = kwargs['token'] From 0eecedc0b69d61e457be54a18fe75b6255f77def Mon Sep 17 00:00:00 2001 From: xiexs Date: Mon, 30 Nov 2015 00:18:17 -0500 Subject: [PATCH 0412/3095] Add unit testcases for "openstack flavor delete" Change-Id: If23a71c678193e5c9c91300fddd17e79f674bf82 --- .../tests/compute/v2/test_flavor.py | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/openstackclient/tests/compute/v2/test_flavor.py b/openstackclient/tests/compute/v2/test_flavor.py index 095b5f2812..60356efa8b 100644 --- a/openstackclient/tests/compute/v2/test_flavor.py +++ b/openstackclient/tests/compute/v2/test_flavor.py @@ -13,6 +13,7 @@ # under the License. # +from openstackclient.common import exceptions from openstackclient.compute.v2 import flavor from openstackclient.tests.compute.v2 import fakes as compute_fakes @@ -27,6 +28,51 @@ def setUp(self): self.flavors_mock.reset_mock() +class TestFlavorDelete(TestFlavor): + + flavor = compute_fakes.FakeFlavor.create_one_flavor() + + def setUp(self): + super(TestFlavorDelete, self).setUp() + + self.flavors_mock.get.return_value = self.flavor + self.flavors_mock.delete.return_value = None + + self.cmd = flavor.DeleteFlavor(self.app, None) + + def test_flavor_delete(self): + arglist = [ + self.flavor.id + ] + verifylist = [ + ('flavor', self.flavor.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.flavors_mock.delete.assert_called_with(self.flavor.id) + + def test_flavor_delete_with_unexist_flavor(self): + self.flavors_mock.get.side_effect = exceptions.NotFound(None) + self.flavors_mock.find.side_effect = exceptions.NotFound(None) + + arglist = [ + 'unexist_flavor' + ] + verifylist = [ + ('flavor', 'unexist_flavor'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + class TestFlavorList(TestFlavor): # Return value of self.flavors_mock.list(). From 31c6957362b9ab5a787c49475c30ef70177918e3 Mon Sep 17 00:00:00 2001 From: xiexs Date: Fri, 27 Nov 2015 14:58:46 +0800 Subject: [PATCH 0413/3095] Introduce FakeVolume class Introduce FakeVolume to improve the current volume unittest framework with following two advantages: 1. generate more than one faking volumes 2. all faking volumes generated by random Change-Id: I9d56efa4fd4f03c82cd4e29622b6312566dbc453 Implements: blueprint improve-volume-unittest-framework --- openstackclient/tests/volume/v2/fakes.py | 84 ++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py index b9b2ae87e9..60cec33558 100644 --- a/openstackclient/tests/volume/v2/fakes.py +++ b/openstackclient/tests/volume/v2/fakes.py @@ -14,6 +14,8 @@ import copy import mock +import random +import uuid from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes @@ -220,3 +222,85 @@ def setUp(self): endpoint=fakes.AUTH_URL, token=fakes.AUTH_TOKEN ) + + +class FakeVolume(object): + """Fake one or more volumes. + + TODO(xiexs): Currently, only volume API v2 is supported by this class. + """ + + @staticmethod + def create_one_volume(attrs={}): + """Create a fake volume. + + :param Dictionary attrs: + A dictionary with all attributes of volume + :retrun: + A FakeResource object with id, name, status, etc. + """ + # Set default attribute + volume_info = { + 'id': 'volume-id' + uuid.uuid4().hex, + 'name': 'volume-name' + uuid.uuid4().hex, + 'description': 'description' + uuid.uuid4().hex, + 'status': random.choice(['available', 'in_use']), + 'size': random.randint(1, 20), + 'volume_type': + random.choice(['fake_lvmdriver-1', 'fake_lvmdriver-2']), + 'metadata': { + 'key' + uuid.uuid4().hex: 'val' + uuid.uuid4().hex, + 'key' + uuid.uuid4().hex: 'val' + uuid.uuid4().hex, + 'key' + uuid.uuid4().hex: 'val' + uuid.uuid4().hex}, + 'snapshot_id': random.randint(1, 5), + 'availability_zone': 'zone' + uuid.uuid4().hex, + 'attachments': { + 'device': '/dev/' + uuid.uuid4().hex, + 'server_id': uuid.uuid4().hex}, + } + + # Overwrite default attributes if there are some attributes set + volume_info.update(attrs) + + volume = fakes.FakeResource( + None, + volume_info, + loaded=True) + return volume + + @staticmethod + def create_volumes(attrs={}, count=2): + """Create multiple fake volumes. + + :param Dictionary attrs: + A dictionary with all attributes of volume + :param Integer count: + The number of volumes to be faked + :return: + A list of FakeResource objects + """ + volumes = [] + for n in range(0, count): + volumes.append(FakeVolume.create_one_volume(attrs)) + + return volumes + + @staticmethod + def get_volumes(volumes=None, count=2): + """Get an iterable MagicMock object with a list of faked volumes. + + If volumes list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List volumes: + A list of FakeResource objects faking volumes + :param Integer count: + The number of volumes to be faked + :return + An iterable Mock object with side_effect set to a list of faked + volumes + """ + if volumes is None: + volumes = FakeVolume.create_volumes(count) + + return mock.MagicMock(side_effect=volumes) From 9168373b3d7502f0ebe7bca9097ee474bb8d6c5b Mon Sep 17 00:00:00 2001 From: Jean-Philippe Evrard Date: Wed, 18 Nov 2015 10:50:34 +0100 Subject: [PATCH 0414/3095] Consistency of the --all argument for snapshots This change is inspired by the volume.py. It allow the user to use openstack snapshot list --all. Closes-Bug: #1517386 Change-Id: I72a53fcd0c5c5af539cd88b37e71b4331fa67473 --- doc/source/command-objects/snapshot.rst | 5 +++ .../tests/volume/v2/test_snapshot.py | 33 ++++++++++++++++++- openstackclient/volume/v1/snapshot.py | 13 +++++++- openstackclient/volume/v2/snapshot.py | 13 +++++++- 4 files changed, 61 insertions(+), 3 deletions(-) diff --git a/doc/source/command-objects/snapshot.rst b/doc/source/command-objects/snapshot.rst index 7bfd1d9203..e05673b82e 100644 --- a/doc/source/command-objects/snapshot.rst +++ b/doc/source/command-objects/snapshot.rst @@ -60,6 +60,11 @@ List snapshots .. code:: bash os snapshot list + [--all-projects] + +.. option:: --all-projects + + Include all projects (admin only) .. option:: --long diff --git a/openstackclient/tests/volume/v2/test_snapshot.py b/openstackclient/tests/volume/v2/test_snapshot.py index 3ceb57faf2..3b30d4ef20 100644 --- a/openstackclient/tests/volume/v2/test_snapshot.py +++ b/openstackclient/tests/volume/v2/test_snapshot.py @@ -225,6 +225,7 @@ def setUp(self): def test_snapshot_list_without_options(self): arglist = [] verifylist = [ + ('all_projects', False), ("long", False) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -243,7 +244,7 @@ def test_snapshot_list_without_options(self): def test_snapshot_list_with_options(self): arglist = ["--long"] - verifylist = [("long", True)] + verifylist = [("long", True), ('all_projects', False)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) @@ -263,3 +264,33 @@ def test_snapshot_list_with_options(self): volume_fakes.EXPECTED_SNAPSHOT.get("properties") ),) self.assertEqual(datalist, tuple(data)) + + def test_snapshot_list_all_projects(self): + arglist = [ + '--all-projects', + ] + verifylist = [ + ('long', False), + ('all_projects', True) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = [ + "ID", + "Name", + "Description", + "Status", + "Size" + ] + self.assertEqual(collist, columns) + + datalist = (( + volume_fakes.snapshot_id, + volume_fakes.snapshot_name, + volume_fakes.snapshot_description, + "available", + volume_fakes.snapshot_size + ), ) + self.assertEqual(datalist, tuple(data)) diff --git a/openstackclient/volume/v1/snapshot.py b/openstackclient/volume/v1/snapshot.py index de7bb5b9ac..93e17eb8a6 100644 --- a/openstackclient/volume/v1/snapshot.py +++ b/openstackclient/volume/v1/snapshot.py @@ -110,6 +110,12 @@ class ListSnapshot(lister.Lister): def get_parser(self, prog_name): parser = super(ListSnapshot, self).get_parser(prog_name) + parser.add_argument( + '--all-projects', + action='store_true', + default=False, + help='Include all projects (admin only)', + ) parser.add_argument( '--long', action='store_true', @@ -157,7 +163,12 @@ def _format_volume_id(volume_id): # Just forget it if there's any trouble pass - data = self.app.client_manager.volume.volume_snapshots.list() + search_opts = { + 'all_tenants': parsed_args.all_projects, + } + + data = self.app.client_manager.volume.volume_snapshots.list( + search_opts=search_opts) return (column_headers, (utils.get_item_properties( s, columns, diff --git a/openstackclient/volume/v2/snapshot.py b/openstackclient/volume/v2/snapshot.py index bbc92c4888..aa7630ae6e 100644 --- a/openstackclient/volume/v2/snapshot.py +++ b/openstackclient/volume/v2/snapshot.py @@ -107,6 +107,12 @@ class ListSnapshot(lister.Lister): def get_parser(self, prog_name): parser = super(ListSnapshot, self).get_parser(prog_name) + parser.add_argument( + '--all-projects', + action='store_true', + default=False, + help='Include all projects (admin only)', + ) parser.add_argument( '--long', action='store_true', @@ -149,7 +155,12 @@ def _format_volume_id(volume_id): # Just forget it if there's any trouble pass - data = self.app.client_manager.volume.volume_snapshots.list() + search_opts = { + 'all_tenants': parsed_args.all_projects, + } + + data = self.app.client_manager.volume.volume_snapshots.list( + search_opts=search_opts) return (column_headers, (utils.get_item_properties( s, columns, From eb8672978c84e5756d368d03658f16177cb8f126 Mon Sep 17 00:00:00 2001 From: xiexs Date: Thu, 19 Nov 2015 04:07:17 -0500 Subject: [PATCH 0415/3095] Add testcases for compute.v2.service Add a set of testcases to test the classes of ListService and SetService in the compute.v2.service. And to be consistent with cinder term, use service_binary to represent the service binary. Change-Id: I9fe740f07c9ce3afdba7b7cca152d614170abb96 --- openstackclient/tests/compute/v2/fakes.py | 11 +- .../tests/compute/v2/test_service.py | 103 +++++++++++++++++- 2 files changed, 109 insertions(+), 5 deletions(-) diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 0d6cd43058..64485e9c66 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -25,8 +25,6 @@ from openstackclient.tests.volume.v2 import fakes as volume_fakes -service_id = '1' - extension_name = 'Multinic' extension_namespace = 'http://docs.openstack.org/compute/ext/'\ 'multinic/api/v1.1' @@ -80,6 +78,15 @@ block_device_mapping = 'vda=' + volume_fakes.volume_name + ':::0' +service_host = 'host_test' +service_binary = 'compute_test' +service_status = 'enabled' +SERVICE = { + 'host': service_host, + 'binary': service_binary, + 'status': service_status, +} + class FakeComputev2Client(object): def __init__(self, **kwargs): diff --git a/openstackclient/tests/compute/v2/test_service.py b/openstackclient/tests/compute/v2/test_service.py index c6db30ac27..380fbc4f3b 100644 --- a/openstackclient/tests/compute/v2/test_service.py +++ b/openstackclient/tests/compute/v2/test_service.py @@ -13,8 +13,11 @@ # under the License. # +import copy + from openstackclient.compute.v2 import service from openstackclient.tests.compute.v2 import fakes as compute_fakes +from openstackclient.tests import fakes class TestService(compute_fakes.TestComputev2): @@ -39,10 +42,10 @@ def setUp(self): def test_service_delete_no_options(self): arglist = [ - compute_fakes.service_id, + compute_fakes.service_binary, ] verifylist = [ - ('service', compute_fakes.service_id), + ('service', compute_fakes.service_binary), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -50,5 +53,99 @@ def test_service_delete_no_options(self): self.cmd.take_action(parsed_args) self.service_mock.delete.assert_called_with( - compute_fakes.service_id, + compute_fakes.service_binary, + ) + + +class TestServiceList(TestService): + + def setUp(self): + super(TestServiceList, self).setUp() + + self.service_mock.list.return_value = [fakes.FakeResource( + None, + copy.deepcopy(compute_fakes.SERVICE), + loaded=True, + )] + + # Get the command object to test + self.cmd = service.ListService(self.app, None) + + def test_service_list(self): + arglist = [ + '--host', compute_fakes.service_host, + '--service', compute_fakes.service_binary, + ] + verifylist = [ + ('host', compute_fakes.service_host), + ('service', compute_fakes.service_binary), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + self.cmd.take_action(parsed_args) + + self.service_mock.list.assert_called_with( + compute_fakes.service_host, + compute_fakes.service_binary, + ) + + +class TestServiceSet(TestService): + + def setUp(self): + super(TestServiceSet, self).setUp() + + self.service_mock.enable.return_value = [fakes.FakeResource( + None, + copy.deepcopy(compute_fakes.SERVICE), + loaded=True, + )] + + self.service_mock.disable.return_value = [fakes.FakeResource( + None, + copy.deepcopy(compute_fakes.SERVICE), + loaded=True, + )] + + self.cmd = service.SetService(self.app, None) + + def test_service_set_enable(self): + arglist = [ + compute_fakes.service_host, + compute_fakes.service_binary, + '--enable', + ] + verifylist = [ + ('host', compute_fakes.service_host), + ('service', compute_fakes.service_binary), + ('enabled', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.service_mock.enable.assert_called_with( + compute_fakes.service_host, + compute_fakes.service_binary, + ) + + def test_service_set_disable(self): + arglist = [ + compute_fakes.service_host, + compute_fakes.service_binary, + '--disable', + ] + verifylist = [ + ('host', compute_fakes.service_host), + ('service', compute_fakes.service_binary), + ('enabled', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.service_mock.disable.assert_called_with( + compute_fakes.service_host, + compute_fakes.service_binary, ) From aca279a98325fc6ba74df7f5b78c8d07ef349a5b Mon Sep 17 00:00:00 2001 From: Lingxian Kong Date: Mon, 30 Nov 2015 23:48:21 +0800 Subject: [PATCH 0416/3095] Integrating mistralclient with openstackclient Change-Id: Ie54d7c15366a8272eefa94c0d8d2430428bdc590 Depends-On: Ic099aaec88377a76a17700c33fed944e52ec5633 --- doc/source/commands.rst | 7 +++++++ doc/source/plugins.rst | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 9a87709ce9..b2cd4b80f4 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -127,11 +127,14 @@ Plugin Objects The following are known `Objects` used by OpenStack :doc:`plugins`. These are listed here to avoid name conflicts when creating new plugins. +* ``action definition``: (**Workflow Engine (Mistral)**) +* ``action execution``: (**Workflow Engine (Mistral)**) * ``baremetal``: (**Baremetal (Ironic)**) * ``congress datasource``: (**Policy (Congress)**) * ``congress driver``: (**Policy (Congress)**) * ``congress policy``: (**Policy (Congress)**) * ``congress policy rule``: (**Policy (Congress)**) +* ``cron trigger``: (**Workflow Engine (Mistral)**) * ``dataprocessing data source``: (**Data Processing (Sahara)**) * ``dataprocessing image``: (**Data Processing (Sahara)**) * ``dataprocessing image tags``: (**Data Processing (Sahara)**) @@ -148,7 +151,11 @@ listed here to avoid name conflicts when creating new plugins. * ``secret container``: (**Key Manager (Barbican)**) * ``secret order``: (**Key Manager (Barbican)**) * ``stack``: (**Orchestration (Heat)**) +* ``task exeuction``: (**Workflow Engine (Mistral)**) * ``tld``: (**DNS (Designate)**) +* ``workbook``: (**Workflow Engine (Mistral)**) +* ``workflow``: (**Workflow Engine (Mistral)**) +* ``workflow execution``: (**Workflow Engine (Mistral)**) * ``zone``: (**DNS (Designate)**) * ``zone blacklist``: (**DNS (Designate)**) * ``zone transfer``: (**DNS (Designate)**) diff --git a/doc/source/plugins.rst b/doc/source/plugins.rst index 5f82cc6879..193545e180 100644 --- a/doc/source/plugins.rst +++ b/doc/source/plugins.rst @@ -33,7 +33,7 @@ python-heatclient patch in progress (https://review.openstack.org/# python-ironicclient Using OpenStackClient python-magnumclient using argparse python-manilaclient using argparse -python-mistralclient using cliff +python-mistralclient using OpenStackClient python-muranoclient using argparse python-saharaclient using OpenStackClient python-troveclient using argparse From 43fbe569a5d1affa994d8c1be096e873d4abfa17 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Mon, 30 Nov 2015 16:30:42 -0500 Subject: [PATCH 0417/3095] Add a changelog to see all changes into tagged releases in addition to release notes, it'll be nice to also see the changelog that happened between releases. Change-Id: I4548be22c377ec12f0398a248391384126506715 --- doc/source/history.rst | 1 + doc/source/index.rst | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 doc/source/history.rst diff --git a/doc/source/history.rst b/doc/source/history.rst new file mode 100644 index 0000000000..69ed4fe6c2 --- /dev/null +++ b/doc/source/history.rst @@ -0,0 +1 @@ +.. include:: ../../ChangeLog diff --git a/doc/source/index.rst b/doc/source/index.rst index 66afc2b5d0..c1fbbe39fd 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -20,7 +20,6 @@ User Documentation interactive humaninterfaceguide backwards-incompatible - releases man/openstack Getting Started @@ -30,6 +29,15 @@ Getting Started * Read the source `on OpenStack's Git server`_ * Install OpenStackClient from `PyPi`_ or a `tarball`_ +Release Notes +------------- + +.. toctree:: + :maxdepth: 1 + + releases + history + Developer Documentation ----------------------- From cb31eb836340547d1b8a1ee86a6f6c63f7c02ecb Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sun, 29 Nov 2015 02:37:54 +0000 Subject: [PATCH 0418/3095] Updated from global requirements Change-Id: Ia957c7f6023e1ac976a291fd081538c90870c802 --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 215874cfa3..fbe3b8d656 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ pbr>=1.6 six>=1.9.0 Babel>=1.3 -cliff>=1.14.0 # Apache-2.0 +cliff>=1.15.0 # Apache-2.0 keystoneauth1>=1.0.0 os-client-config!=1.6.2,>=1.4.0 oslo.config>=2.7.0 # Apache-2.0 @@ -16,5 +16,5 @@ python-keystoneclient!=1.8.0,>=1.6.0 python-novaclient!=2.33.0,>=2.29.0 python-cinderclient>=1.3.1 python-neutronclient>=2.6.0 -requests!=2.8.0,>=2.5.2 +requests>=2.8.1 stevedore>=1.5.0 # Apache-2.0 From b36b477e8106978669f653e4721ff023006772c6 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 17 Nov 2015 21:41:47 +0800 Subject: [PATCH 0419/3095] Add --marker option to "server list" command. This option will give user a chance to display the server list from wherever they want. Change-Id: I92cca5e98cd473f1113a9106eb9d1f490694b1fe --- doc/source/command-objects/server.rst | 6 ++++++ openstackclient/compute/v2/server.py | 17 ++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index 6ae4d2543d..41d484ee97 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -187,6 +187,7 @@ List servers [--all-projects] [--project [--project-domain ]] [--long] + [--marker ] .. option:: --reservation-id @@ -250,6 +251,11 @@ List servers List additional fields in output +.. option:: --marker + + The last server (name or ID) of the previous page. Display list of servers + after marker. Display all servers if not specified. + server lock ----------- diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 875b9a137d..2e5cd99ce2 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -747,6 +747,14 @@ def get_parser(self, prog_name): default=False, help=_('List additional fields in output'), ) + parser.add_argument( + '--marker', + metavar='', + default=None, + help=('The last server (name or ID) of the previous page. Display' + ' list of servers after marker. Display all servers if not' + ' specified.') + ) return parser @utils.log_method(log) @@ -830,7 +838,14 @@ def take_action(self, parsed_args): 'Networks', ) mixed_case_fields = [] - data = compute_client.servers.list(search_opts=search_opts) + + marker_id = None + if parsed_args.marker: + marker_id = utils.find_resource(compute_client.servers, + parsed_args.marker).id + + data = compute_client.servers.list(search_opts=search_opts, + marker=marker_id) return (column_headers, (utils.get_item_properties( s, columns, From 43c11c356b92e11a27179a02d2aab1b56fe3bc07 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 17 Nov 2015 21:44:02 +0800 Subject: [PATCH 0420/3095] Add --limit option to "server list" command. This option will limit the total amount of items the command will list up. Change-Id: I46af0d479d795ebb6a74585d0f76629dd940b117 --- doc/source/command-objects/server.rst | 7 +++++++ openstackclient/compute/v2/server.py | 13 ++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index 41d484ee97..afa328ba36 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -188,6 +188,7 @@ List servers [--project [--project-domain ]] [--long] [--marker ] + [--limit ] .. option:: --reservation-id @@ -256,6 +257,12 @@ List servers The last server (name or ID) of the previous page. Display list of servers after marker. Display all servers if not specified. +.. option:: --limit + + Maximum number of servers to display. If limit equals -1, all servers will + be displayed. If limit is greater than 'osapi_max_limit' option of Nova + API, 'osapi_max_limit' will be used instead. + server lock ----------- diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 2e5cd99ce2..1d0de27e17 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -755,6 +755,16 @@ def get_parser(self, prog_name): ' list of servers after marker. Display all servers if not' ' specified.') ) + parser.add_argument( + '--limit', + metavar='', + type=int, + default=None, + help=("Maximum number of servers to display. If limit equals -1," + " all servers will be displayed. If limit is greater than" + " 'osapi_max_limit' option of Nova API," + " 'osapi_max_limit' will be used instead."), + ) return parser @utils.log_method(log) @@ -845,7 +855,8 @@ def take_action(self, parsed_args): parsed_args.marker).id data = compute_client.servers.list(search_opts=search_opts, - marker=marker_id) + marker=marker_id, + limit=parsed_args.limit) return (column_headers, (utils.get_item_properties( s, columns, From fad7126e7929621e37a021ee474b6aa771a92912 Mon Sep 17 00:00:00 2001 From: xiexs Date: Wed, 2 Dec 2015 13:45:58 +0800 Subject: [PATCH 0421/3095] Remove list output from "compute service set" Replace the super class from lister.Lister to command.Command, as no need to display the status for the "set" command. Change-Id: Ibaf3c1e349633223fca19937bbd9060d4f9ecbda Closes-Bug: #1517804 --- openstackclient/compute/v2/service.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index c2d51c2aa3..bdb98b3924 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.py @@ -81,7 +81,7 @@ def take_action(self, parsed_args): ) for s in data)) -class SetService(lister.Lister): +class SetService(command.Command): """Set service command""" log = logging.getLogger(__name__ + ".SetService") @@ -113,19 +113,10 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) compute_client = self.app.client_manager.compute - columns = ( - "Host", - "Service", - "Disabled" - ) if parsed_args.enabled: action = compute_client.services.enable else: action = compute_client.services.disable - data = action(parsed_args.host, parsed_args.service) - return (columns, - (utils.get_item_properties( - s, columns, - ) for s in data)) + action(parsed_args.host, parsed_args.service) From 727792da17ac5edcfc2df7dd820dbccf318554fe Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Fri, 16 Oct 2015 02:08:05 -0400 Subject: [PATCH 0422/3095] autodocument commands from plugins using stevedore.sphinxext conflicting command names are a painpoint, so is manually updating a list. let's autodocument the commands that are being use by existing osc supporters. Change-Id: If37d81dfd57cc79803668b64be1ccd776e319572 --- doc/source/commands.rst | 3 ++- doc/source/conf.py | 1 + doc/source/index.rst | 1 + doc/source/plugin-commands.rst | 32 ++++++++++++++++++++++++++++++++ test-requirements.txt | 11 +++++++++++ 5 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 doc/source/plugin-commands.rst diff --git a/doc/source/commands.rst b/doc/source/commands.rst index e0742ab441..606989504b 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -126,7 +126,8 @@ Plugin Objects -------------- The following are known `Objects` used by OpenStack :doc:`plugins`. These are -listed here to avoid name conflicts when creating new plugins. +listed here to avoid name conflicts when creating new plugins. For a complete +list check out :doc:`plugin-commands`. * ``baremetal``: (**Baremetal (Ironic)**) * ``congress datasource``: (**Policy (Congress)**) diff --git a/doc/source/conf.py b/doc/source/conf.py index f4434ec154..792ba40a41 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -38,6 +38,7 @@ 'sphinx.ext.todo', 'oslosphinx', 'ext.apidoc', + 'stevedore.sphinxext', ] # Add any paths that contain templates here, relative to this directory. diff --git a/doc/source/index.rst b/doc/source/index.rst index c1fbbe39fd..49929972c3 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -16,6 +16,7 @@ User Documentation commands configuration plugins + plugin-commands authentication interactive humaninterfaceguide diff --git a/doc/source/plugin-commands.rst b/doc/source/plugin-commands.rst new file mode 100644 index 0000000000..893c4c4968 --- /dev/null +++ b/doc/source/plugin-commands.rst @@ -0,0 +1,32 @@ +================= + Plugin Commands +================= + +.. list-plugins:: openstack.cli.extension + +.. list-plugins:: openstack.key_manager.v1 + :detailed: + +.. list-plugins:: openstack.baremetal.v1 + :detailed: + +.. list-plugins:: openstack.congressclient.v1 + :detailed: + +.. list-plugins:: openstack.workflow_engine.v2 + :detailed: + +.. list-plugins:: openstack.data_processing.v1 + :detailed: + +.. list-plugins:: openstack.dns.v1 + :detailed: + +.. list-plugins:: openstack.management.v1 + :detailed: + +.. list-plugins:: openstack.messaging.v1 + :detailed: + +.. list-plugins:: openstack.orchestration.v1 + :detailed: diff --git a/test-requirements.txt b/test-requirements.txt index c02ebf0ea4..05b95f758a 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -16,3 +16,14 @@ testrepository>=0.0.18 testtools>=1.4.0 WebOb>=1.2.3 tempest-lib>=0.10.0 + +# Install these to generate sphinx autodocs +python-barbicanclient>=3.3.0 +python-congressclient>=1.0.0 +python-designateclient>=1.5.0 +python-heatclient>=0.6.0 +python-ironicclient>=0.8.0 +python-mistralclient>=1.0.0 +python-saharaclient>=0.10.0 +python-tuskarclient>=0.1.17 +python-zaqarclient>=0.3.0 From 0069a0196717eb7ba60bcda5b89bf87608cc01dd Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Wed, 2 Dec 2015 13:47:15 +0800 Subject: [PATCH 0423/3095] Support "server list" searching by both flavor name and ID. Nova API only supports list servers searching by flavor ID. In OSC, we can support both flavor name and ID by mapping the name to ID. This patch also fix the inconsistent doc in .py and .rst files. Partial-Bug: 1521492 Change-Id: I1d1a6aa91aef4e2846745babe8382481185fa96e --- doc/source/command-objects/server.rst | 2 +- openstackclient/compute/v2/server.py | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index 70b4748251..59c047ad6d 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -216,7 +216,7 @@ List servers .. option:: --flavor - Search by flavor ID + Search by flavor (name or ID) .. option:: --image diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 9dca784487..42699f8d2a 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -712,7 +712,7 @@ def get_parser(self, prog_name): parser.add_argument( '--flavor', metavar='', - help=_('Search by flavor'), + help=_('Search by flavor (name or ID)'), ) parser.add_argument( '--image', @@ -789,6 +789,13 @@ def take_action(self, parsed_args): parsed_args.user_domain, ).id + # Nova only supports list servers searching by flavor ID. So if a + # flavor name is given, map it to ID. + flavor_id = None + if parsed_args.flavor: + flavor_id = utils.find_resource(compute_client.flavors, + parsed_args.flavor).id + search_opts = { 'reservation_id': parsed_args.reservation_id, 'ip': parsed_args.ip, @@ -796,7 +803,7 @@ def take_action(self, parsed_args): 'name': parsed_args.name, 'instance_name': parsed_args.instance_name, 'status': parsed_args.status, - 'flavor': parsed_args.flavor, + 'flavor': flavor_id, 'image': parsed_args.image, 'host': parsed_args.host, 'tenant_id': project_id, From 106f928cb66fbfb3fb99f32b9d3e8ffdbda04d75 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Wed, 2 Dec 2015 14:02:12 +0800 Subject: [PATCH 0424/3095] Support "server list" searching by both image name and ID. Nova API only supports list servers searching by image ID. In OSC, we can support both image name and ID by mapping the name to ID. This patch also fix the inconsistent doc in .py and .rst files. Closes-Bug: 1521492 Change-Id: I70613843f82d74732bd32a457cd4a31aba57825f --- doc/source/command-objects/server.rst | 2 +- openstackclient/compute/v2/server.py | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index 59c047ad6d..627b673325 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -220,7 +220,7 @@ List servers .. option:: --image - Search by image ID + Search by image (name or ID) .. option:: --host diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 42699f8d2a..d58ebacd92 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -717,7 +717,7 @@ def get_parser(self, prog_name): parser.add_argument( '--image', metavar='', - help=_('Search by image'), + help=_('Search by image (name or ID)'), ) parser.add_argument( '--host', @@ -796,6 +796,13 @@ def take_action(self, parsed_args): flavor_id = utils.find_resource(compute_client.flavors, parsed_args.flavor).id + # Nova only supports list servers searching by image ID. So if a + # image name is given, map it to ID. + image_id = None + if parsed_args.image: + image_id = utils.find_resource(compute_client.images, + parsed_args.image).id + search_opts = { 'reservation_id': parsed_args.reservation_id, 'ip': parsed_args.ip, @@ -804,7 +811,7 @@ def take_action(self, parsed_args): 'instance_name': parsed_args.instance_name, 'status': parsed_args.status, 'flavor': flavor_id, - 'image': parsed_args.image, + 'image': image_id, 'host': parsed_args.host, 'tenant_id': project_id, 'all_tenants': parsed_args.all_projects, From 51e31054686dc4b7d6deb1aa182dd581b0758a20 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Mon, 30 Nov 2015 21:54:19 -0500 Subject: [PATCH 0425/3095] Add release notes for 2.0.0 Change-Id: I878609870701abaef60d49813b3184f473d936bf --- doc/source/releases.rst | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/doc/source/releases.rst b/doc/source/releases.rst index 148402bbde..857db97eaa 100644 --- a/doc/source/releases.rst +++ b/doc/source/releases.rst @@ -2,6 +2,41 @@ Release Notes ============= +2.0.0 (1 Dec 2015) +================== + +* Dropping python 2.6 support + Bug `1519510 `_ + +* Removed the deprecated command `project usage list` in favor of `usage list` + +* Several updates to `openstack server` + + * Added `--marker` to `openstack server list` + * Added `--limit` to `openstack server list` + * Added `openstack server shelve` + * Added `openstack server unshelve` + * `openstack server resume` now takes multiple servers + * `openstack server suspend` now takes multiple servers + +* Some compute quotas were not being set + Bug `1475831 `_ + +* Add `--all` for `snapshot list` + Bug `1517386 `_ + +* Add `status` to `image list` (v1 and v2) + Bug `1519181 `_ + +* Fix `volume type create`, passes incorrect privacy argument + Bug `1520115 `_ + +* `volume delete` only deletes last volume specified + Bug `1520541 `_ + +* Add dependency on keystoneauth1 module to perform authentication in place + of python-keystoneclient. + 1.9.0 (17 Nov 2015) =================== From bf090c69c2e055285fc4fe45af0a5f66d6dc7759 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 21 Oct 2015 12:01:56 -0500 Subject: [PATCH 0426/3095] Switch to ksa Session * Change session imports to keystoneauth1 * Change keystoneclient.exception imports to keystoneauth1 * Change exceptions raised from internal API from keystoneclient to openstack.common Change-Id: I046d89f561d6fe04baae53726f9749d2e7fe2056 --- examples/common.py | 4 ++-- openstackclient/api/api.py | 19 ++++++++++--------- openstackclient/common/session.py | 4 ++-- openstackclient/compute/v2/security_group.py | 4 ++-- openstackclient/identity/v2_0/project.py | 6 +++--- openstackclient/identity/v2_0/role.py | 4 ++-- openstackclient/identity/v2_0/user.py | 6 +++--- openstackclient/identity/v3/domain.py | 4 ++-- openstackclient/identity/v3/group.py | 4 ++-- openstackclient/identity/v3/project.py | 4 ++-- openstackclient/identity/v3/role.py | 4 ++-- openstackclient/identity/v3/user.py | 4 ++-- .../tests/identity/v2_0/test_project.py | 4 ++-- .../tests/identity/v2_0/test_role.py | 4 ++-- .../tests/identity/v2_0/test_user.py | 5 +++-- 15 files changed, 41 insertions(+), 39 deletions(-) diff --git a/examples/common.py b/examples/common.py index 840e715e16..6d48a8cb27 100755 --- a/examples/common.py +++ b/examples/common.py @@ -37,7 +37,7 @@ import sys import traceback -from keystoneclient import session as ksc_session +from keystoneauth1 import session as ks_session from openstackclient.api import auth @@ -226,7 +226,7 @@ def make_session(opts, **kwargs): ) auth_p = auth_plugin.load_from_options(**auth_params) - session = ksc_session.Session( + session = ks_session.Session( auth=auth_p, **kwargs ) diff --git a/openstackclient/api/api.py b/openstackclient/api/api.py index ba83ce4d52..97eb7e4a75 100644 --- a/openstackclient/api/api.py +++ b/openstackclient/api/api.py @@ -15,8 +15,9 @@ import simplejson as json -from keystoneclient import exceptions as ksc_exceptions -from keystoneclient import session as ksc_session +from keystoneauth1 import exceptions as ks_exceptions +from keystoneauth1 import session as ks_session + from openstackclient.common import exceptions @@ -24,7 +25,7 @@ class KeystoneSession(object): """Wrapper for the Keystone Session Restore some requests.session.Session compatibility; - keystoneclient.session.Session.request() has the method and url + keystoneauth1.session.Session.request() has the method and url arguments swapped from the rest of the requests-using world. """ @@ -70,7 +71,7 @@ def _request(self, method, url, session=None, **kwargs): if not session: session = self.session if not session: - session = ksc_session.Session() + session = ks_session.Session() if self.endpoint: if url: @@ -255,7 +256,7 @@ def getlist(kw): return data[0] if len(data) > 1: msg = "Multiple %s exist with %s='%s'" - raise ksc_exceptions.CommandError( + raise exceptions.CommandError( msg % (resource, attr, value), ) @@ -314,7 +315,7 @@ def find_one( num_bulk = len(bulk_list) if num_bulk == 0: msg = "none found" - raise ksc_exceptions.NotFound(msg) + raise exceptions.NotFound(msg) elif num_bulk > 1: msg = "many found" raise RuntimeError(msg) @@ -338,12 +339,12 @@ def find( try: ret = self._request('GET', "/%s/%s" % (path, value)).json() - except ksc_exceptions.NotFound: + except ks_exceptions.NotFound: kwargs = {attr: value} try: ret = self.find_one("/%s/detail" % (path), **kwargs) - except ksc_exceptions.NotFound: + except ks_exceptions.NotFound: msg = "%s not found" % value - raise ksc_exceptions.NotFound(msg) + raise exceptions.NotFound(msg) return ret diff --git a/openstackclient/common/session.py b/openstackclient/common/session.py index dda1c41709..9b19fd46e9 100644 --- a/openstackclient/common/session.py +++ b/openstackclient/common/session.py @@ -11,9 +11,9 @@ # under the License. # -"""Subclass of keystoneclient.session""" +"""Subclass of keystoneauth1.session""" -from keystoneclient import session +from keystoneauth1 import session class TimingSession(session.Session): diff --git a/openstackclient/compute/v2/security_group.py b/openstackclient/compute/v2/security_group.py index 68f086bc80..6d38195cf5 100644 --- a/openstackclient/compute/v2/security_group.py +++ b/openstackclient/compute/v2/security_group.py @@ -23,7 +23,7 @@ from cliff import lister from cliff import show -from keystoneclient import exceptions as ksc_exc +from keystoneauth1 import exceptions as ks_exc try: from novaclient.v2 import security_group_rules @@ -241,7 +241,7 @@ def _get_project(project_id): project_hash = {} try: projects = self.app.client_manager.identity.projects.list() - except ksc_exc.ClientException: + except ks_exc.ClientException: # This fails when the user is not an admin, just move along pass else: diff --git a/openstackclient/identity/v2_0/project.py b/openstackclient/identity/v2_0/project.py index 97a95f2881..065f0adfbb 100644 --- a/openstackclient/identity/v2_0/project.py +++ b/openstackclient/identity/v2_0/project.py @@ -21,7 +21,7 @@ from cliff import command from cliff import lister from cliff import show -from keystoneclient import exceptions as ksc_exc +from keystoneauth1 import exceptions as ks_exc from openstackclient.common import parseractions from openstackclient.common import utils @@ -88,7 +88,7 @@ def take_action(self, parsed_args): enabled=enabled, **kwargs ) - except ksc_exc.Conflict as e: + except ks_exc.Conflict as e: if parsed_args.or_show: project = utils.find_resource( identity_client.tenants, @@ -264,7 +264,7 @@ def take_action(self, parsed_args): parsed_args.project, ) info.update(project._info) - except ksc_exc.Forbidden as e: + except ks_exc.Forbidden as e: auth_ref = self.app.client_manager.auth_ref if ( parsed_args.project == auth_ref.project_id or diff --git a/openstackclient/identity/v2_0/role.py b/openstackclient/identity/v2_0/role.py index e98f8cb347..cab6b4a540 100644 --- a/openstackclient/identity/v2_0/role.py +++ b/openstackclient/identity/v2_0/role.py @@ -21,7 +21,7 @@ from cliff import command from cliff import lister from cliff import show -from keystoneclient import exceptions as ksc_exc +from keystoneauth1 import exceptions as ks_exc from openstackclient.common import exceptions from openstackclient.common import utils @@ -98,7 +98,7 @@ def take_action(self, parsed_args): identity_client = self.app.client_manager.identity try: role = identity_client.roles.create(parsed_args.role_name) - except ksc_exc.Conflict as e: + except ks_exc.Conflict as e: if parsed_args.or_show: role = utils.find_resource( identity_client.roles, diff --git a/openstackclient/identity/v2_0/user.py b/openstackclient/identity/v2_0/user.py index 76902e6925..e2b285bd9d 100644 --- a/openstackclient/identity/v2_0/user.py +++ b/openstackclient/identity/v2_0/user.py @@ -21,7 +21,7 @@ from cliff import command from cliff import lister from cliff import show -from keystoneclient import exceptions as ksc_exc +from keystoneauth1 import exceptions as ks_exc from openstackclient.common import utils from openstackclient.i18n import _ # noqa @@ -104,7 +104,7 @@ def take_action(self, parsed_args): tenant_id=project_id, enabled=enabled, ) - except ksc_exc.Conflict as e: + except ks_exc.Conflict as e: if parsed_args.or_show: user = utils.find_resource( identity_client.users, @@ -373,7 +373,7 @@ def take_action(self, parsed_args): parsed_args.user, ) info.update(user._info) - except ksc_exc.Forbidden as e: + except ks_exc.Forbidden as e: auth_ref = self.app.client_manager.auth_ref if ( parsed_args.user == auth_ref.user_id or diff --git a/openstackclient/identity/v3/domain.py b/openstackclient/identity/v3/domain.py index aec530a883..8278a300ef 100644 --- a/openstackclient/identity/v3/domain.py +++ b/openstackclient/identity/v3/domain.py @@ -22,7 +22,7 @@ from cliff import command from cliff import lister from cliff import show -from keystoneclient import exceptions as ksc_exc +from keystoneauth1 import exceptions as ks_exc from openstackclient.common import utils from openstackclient.i18n import _ # noqa @@ -77,7 +77,7 @@ def take_action(self, parsed_args): description=parsed_args.description, enabled=enabled, ) - except ksc_exc.Conflict as e: + except ks_exc.Conflict as e: if parsed_args.or_show: domain = utils.find_resource(identity_client.domains, parsed_args.name) diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py index 82975065ac..d503a6b869 100644 --- a/openstackclient/identity/v3/group.py +++ b/openstackclient/identity/v3/group.py @@ -22,7 +22,7 @@ from cliff import command from cliff import lister from cliff import show -from keystoneclient import exceptions as ksc_exc +from keystoneauth1 import exceptions as ks_exc from openstackclient.common import utils from openstackclient.i18n import _ # noqa @@ -156,7 +156,7 @@ def take_action(self, parsed_args): name=parsed_args.name, domain=domain, description=parsed_args.description) - except ksc_exc.Conflict as e: + except ks_exc.Conflict as e: if parsed_args.or_show: group = utils.find_resource(identity_client.groups, parsed_args.name, diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index 96d7f97b20..f87105dd23 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -21,7 +21,7 @@ from cliff import command from cliff import lister from cliff import show -from keystoneclient import exceptions as ksc_exc +from keystoneauth1 import exceptions as ks_exc from openstackclient.common import parseractions from openstackclient.common import utils @@ -113,7 +113,7 @@ def take_action(self, parsed_args): enabled=enabled, **kwargs ) - except ksc_exc.Conflict as e: + except ks_exc.Conflict as e: if parsed_args.or_show: project = utils.find_resource(identity_client.projects, parsed_args.name, diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index 0e8c51ca8d..66f189649b 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -22,7 +22,7 @@ from cliff import command from cliff import lister from cliff import show -from keystoneclient import exceptions as ksc_exc +from keystoneauth1 import exceptions as ks_exc from openstackclient.common import utils from openstackclient.i18n import _ # noqa @@ -172,7 +172,7 @@ def take_action(self, parsed_args): try: role = identity_client.roles.create(name=parsed_args.name) - except ksc_exc.Conflict as e: + except ks_exc.Conflict as e: if parsed_args.or_show: role = utils.find_resource(identity_client.roles, parsed_args.name) diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index 0e894544f0..eaef8f0523 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -22,7 +22,7 @@ from cliff import command from cliff import lister from cliff import show -from keystoneclient import exceptions as ksc_exc +from keystoneauth1 import exceptions as ks_exc from openstackclient.common import utils from openstackclient.i18n import _ # noqa @@ -122,7 +122,7 @@ def take_action(self, parsed_args): description=parsed_args.description, enabled=enabled ) - except ksc_exc.Conflict as e: + except ks_exc.Conflict as e: if parsed_args.or_show: user = utils.find_resource(identity_client.users, parsed_args.name, diff --git a/openstackclient/tests/identity/v2_0/test_project.py b/openstackclient/tests/identity/v2_0/test_project.py index 18e862eb13..16ab195736 100644 --- a/openstackclient/tests/identity/v2_0/test_project.py +++ b/openstackclient/tests/identity/v2_0/test_project.py @@ -15,7 +15,7 @@ import copy -from keystoneclient import exceptions as ksc_exc +from keystoneauth1 import exceptions as ks_exc from openstackclient.identity.v2_0 import project from openstackclient.tests import fakes @@ -223,7 +223,7 @@ def test_project_create_property(self): def test_project_create_or_show_exists(self): def _raise_conflict(*args, **kwargs): - raise ksc_exc.Conflict(None) + raise ks_exc.Conflict(None) # need to make this throw an exception... self.projects_mock.create.side_effect = _raise_conflict diff --git a/openstackclient/tests/identity/v2_0/test_role.py b/openstackclient/tests/identity/v2_0/test_role.py index 478a4ff210..ec484333c1 100644 --- a/openstackclient/tests/identity/v2_0/test_role.py +++ b/openstackclient/tests/identity/v2_0/test_role.py @@ -16,7 +16,7 @@ import copy import mock -from keystoneclient import exceptions as ksc_exc +from keystoneauth1 import exceptions as ks_exc from openstackclient.common import exceptions from openstackclient.identity.v2_0 import role @@ -146,7 +146,7 @@ def test_role_create_no_options(self): def test_role_create_or_show_exists(self): def _raise_conflict(*args, **kwargs): - raise ksc_exc.Conflict(None) + raise ks_exc.Conflict(None) # need to make this throw an exception... self.roles_mock.create.side_effect = _raise_conflict diff --git a/openstackclient/tests/identity/v2_0/test_user.py b/openstackclient/tests/identity/v2_0/test_user.py index 6fe82f933d..bf25681e6f 100644 --- a/openstackclient/tests/identity/v2_0/test_user.py +++ b/openstackclient/tests/identity/v2_0/test_user.py @@ -16,7 +16,8 @@ import copy import mock -from keystoneclient import exceptions as ksc_exc +from keystoneauth1 import exceptions as ks_exc + from openstackclient.identity.v2_0 import user from openstackclient.tests import fakes from openstackclient.tests.identity.v2_0 import fakes as identity_fakes @@ -345,7 +346,7 @@ def test_user_create_disable(self): def test_user_create_or_show_exists(self): def _raise_conflict(*args, **kwargs): - raise ksc_exc.Conflict(None) + raise ks_exc.Conflict(None) # need to make this throw an exception... self.users_mock.create.side_effect = _raise_conflict From 3f7c01cae5180d5b03e73fc7033d507fdfb72f2d Mon Sep 17 00:00:00 2001 From: xiexs Date: Fri, 27 Nov 2015 13:58:40 +0800 Subject: [PATCH 0427/3095] Introduce FakeImage class Introduce FakeImage to improve the current image unittest framework with following two advantages: 1. generate more than one faking images 2. all faking images generated by random Change-Id: Ide326fa2a047ddeea478bef97000083617a0b744 Implements: blueprint improve-image-unittest-framework --- openstackclient/tests/image/v2/fakes.py | 133 ++++++++++++++++++++++++ 1 file changed, 133 insertions(+) diff --git a/openstackclient/tests/image/v2/fakes.py b/openstackclient/tests/image/v2/fakes.py index 11ad455df2..692ef104de 100644 --- a/openstackclient/tests/image/v2/fakes.py +++ b/openstackclient/tests/image/v2/fakes.py @@ -15,7 +15,10 @@ import copy import mock +import random +import uuid +from openstackclient.common import utils as common_utils from openstackclient.tests import fakes from openstackclient.tests import utils @@ -167,3 +170,133 @@ def setUp(self): endpoint=fakes.AUTH_URL, token=fakes.AUTH_TOKEN, ) + + +class FakeImage(object): + """Fake one or more images. + + TODO(xiexs): Currently, only image API v2 is supported by this class. + """ + + @staticmethod + def create_one_image(attrs={}): + """Create a fake image. + + :param Dictionary attrs: + A dictionary with all attrbutes of image + :retrun: + A FakeResource object with id, name, owner, protected, + visibility and tags attrs + """ + # Set default attribute + image_info = { + 'id': 'image-id' + uuid.uuid4().hex, + 'name': 'image-name' + uuid.uuid4().hex, + 'owner': 'image-owner' + uuid.uuid4().hex, + 'protected': bool(random.choice([0, 1])), + 'visibility': random.choice(['public', 'private']), + 'tags': [uuid.uuid4().hex for r in range(random.randint(1, 5))], + } + + # Overwrite default attributes if there are some attributes set + image_info.update(attrs) + + image = fakes.FakeResource( + None, + image_info, + loaded=True) + return image + + @staticmethod + def create_images(attrs={}, count=2): + """Create multiple fake images. + + :param Dictionary attrs: + A dictionary with all attributes of image + :param Integer count: + The number of images to be faked + :return: + A list of FakeResource objects + """ + images = [] + for n in range(0, count): + images.append(FakeImage.create_one_image(attrs)) + + return images + + @staticmethod + def get_images(images=None, count=2): + """Get an iterable MagicMock object with a list of faked images. + + If images list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List images: + A list of FakeResource objects faking images + :param Integer count: + The number of images to be faked + :return + An iterable Mock object with side_effect set to a list of faked + images + """ + if images is None: + images = FakeImage.create_images(count) + + return mock.MagicMock(side_effect=images) + + @staticmethod + def get_image_info(image=None): + """Get the image info from a faked image object. + + :param image: + A FakeResource objects faking image + :return + A dictionary which includes the faked image info as follows: + { + 'id': image_id, + 'name': image_name, + 'owner': image_owner, + 'protected': image_protected, + 'visibility': image_visibility, + 'tags': image_tags + } + """ + if image is not None: + return image._info + return {} + + @staticmethod + def get_image_columns(image=None): + """Get the image columns from a faked image object. + + :param image: + A FakeResource objects faking image + :return + A tuple which may include the following keys: + ('id', 'name', 'owner', 'protected', 'visibility', 'tags') + """ + if image is not None: + return tuple(k for k in sorted( + FakeImage.get_image_info(image).keys())) + return tuple([]) + + @staticmethod + def get_image_data(image=None): + """Get the image data from a faked image object. + + :param image: + A FakeResource objects faking image + :return + A tuple which may include the following values: + ('image-123', 'image-foo', 'admin', False, 'public', 'bar, baz') + """ + data_list = [] + if image is not None: + for x in sorted(FakeImage.get_image_info(image).keys()): + if x == 'tags': + # The 'tags' should be format_list + data_list.append( + common_utils.format_list(getattr(image, x))) + else: + data_list.append(getattr(image, x)) + return tuple(data_list) From d37d27b2d6fd09b4b8ca111057f894a55725c579 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Wed, 2 Dec 2015 19:27:06 +0800 Subject: [PATCH 0428/3095] Enable setup_servers_mock() to take attributes param. setup_servers_mock() is now able to set methods to the fake servers. But it cannot set attributes. This patch enable it to do so. This will be useful in "server list" test cases. Change-Id: Ic30d750ebe4650244707a368cdd5d622a8f1b8ed Implements: blueprint osc-unit-test-framework-improvement --- openstackclient/tests/compute/v2/test_server.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index a750ed5dde..6cc314e89c 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -53,11 +53,15 @@ def setUp(self): self.volumes_mock = self.app.client_manager.volume.volumes self.volumes_mock.reset_mock() + # Set object attributes to be tested. Could be overwriten in subclass. + self.attrs = {} + # Set object methods to be tested. Could be overwriten in subclass. self.methods = {} def setup_servers_mock(self, count): - servers = compute_fakes.FakeServer.create_servers(methods=self.methods, + servers = compute_fakes.FakeServer.create_servers(attrs=self.attrs, + methods=self.methods, count=count) # This is the return value for utils.find_resource() From d7c3048f568ca59edf4db18c294d8d5d0573c178 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Wed, 2 Dec 2015 17:24:41 +0800 Subject: [PATCH 0429/3095] Add class TestServerList to provide basic unit test for "server list" command. This patch provide a class to test "server list" command. Only one simplest case in this patch. Some of the options in "server list" are complicated. And the server object contains lots of attributes need to be handled in specific ways. So other test cases will be added in other patches. Change-Id: Id9fdba8f149bd74187aa42516067dacebc6962b5 Implements: blueprint osc-unit-test-framework-improvement --- .../tests/compute/v2/test_server.py | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 6cc314e89c..359920f230 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -611,6 +611,87 @@ def test_server_image_create_name(self): self.assertEqual(datalist, data) +class TestServerList(TestServer): + + # Columns to be listed up. + columns = ( + 'ID', + 'Name', + 'Status', + 'Networks', + ) + + # Data returned by corresponding Nova API. The elements in this list are + # tuples filled with server attributes. + data = [] + + # Default search options, in the case of no commandline option specified. + search_opts = { + 'reservation_id': None, + 'ip': None, + 'ip6': None, + 'name': None, + 'instance_name': None, + 'status': None, + 'flavor': None, + 'image': None, + 'host': None, + 'tenant_id': None, + 'all_tenants': False, + 'user_id': None, + } + + # Default params of the core function of the command in the case of no + # commandline option specified. + kwargs = { + 'search_opts': search_opts, + 'marker': None, + 'limit': None, + } + + def setUp(self): + super(TestServerList, self).setUp() + + # The fake servers' attributes. + self.attrs = { + 'status': 'ACTIVE', + 'networks': { + u'public': [u'10.20.30.40', u'2001:db8::5'] + }, + } + + # The servers to be listed. + self.servers = self.setup_servers_mock(3) + + self.servers_mock.list.return_value = self.servers + + # Get the command object to test + self.cmd = server.ListServer(self.app, None) + + # Prepare data returned by fake Nova API. + for s in self.servers: + self.data.append(( + s.id, + s.name, + s.status, + u'public=10.20.30.40, 2001:db8::5', + )) + + def test_server_list_no_option(self): + arglist = [] + verifylist = [ + ('all_projects', False), + ('long', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.servers_mock.list.assert_called_with(**self.kwargs) + self.assertEqual(self.columns, columns) + self.assertEqual(tuple(self.data), tuple(data)) + + class TestServerLock(TestServer): def setUp(self): From 6e747629ad2eab980671ebabb5b66f4acdfc0e8d Mon Sep 17 00:00:00 2001 From: xiexs Date: Fri, 27 Nov 2015 11:44:38 -0500 Subject: [PATCH 0430/3095] Add multi deletion testcase for openstack volume delete Change-Id: Id8e6e8311e46e4c8644d41d773aeb27416ca6a7e --- .../tests/volume/v2/test_volume.py | 40 +++++++++++++++---- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/openstackclient/tests/volume/v2/test_volume.py b/openstackclient/tests/volume/v2/test_volume.py index 70324b6e35..50826c31aa 100644 --- a/openstackclient/tests/volume/v2/test_volume.py +++ b/openstackclient/tests/volume/v2/test_volume.py @@ -14,6 +14,8 @@ import copy +from mock import call + from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes from openstackclient.tests.volume.v2 import fakes as volume_fakes @@ -36,6 +38,14 @@ def setUp(self): self.images_mock = self.app.client_manager.image.images self.images_mock.reset_mock() + def setup_volumes_mock(self, count): + volumes = volume_fakes.FakeVolume.create_volumes(count=count) + + self.volumes_mock.get = volume_fakes.FakeVolume.get_volumes( + volumes, + 0) + return volumes + class TestVolumeCreate(TestVolume): def setUp(self): @@ -888,24 +898,38 @@ class TestVolumeDelete(TestVolume): def setUp(self): super(TestVolumeDelete, self).setUp() - self.volumes_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.VOLUME), - loaded=True) self.volumes_mock.delete.return_value = None # Get the command object to mock self.cmd = volume.DeleteVolume(self.app, None) - def test_volume_delete(self): + def test_volume_delete_one_volume(self): + volumes = self.setup_volumes_mock(count=1) + arglist = [ - volume_fakes.volume_id + volumes[0].id ] verifylist = [ - ("volumes", [volume_fakes.volume_id]) + ("volumes", [volumes[0].id]) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) - self.volumes_mock.delete.assert_called_with(volume_fakes.volume_id) + self.volumes_mock.delete.assert_called_with(volumes[0].id) + + def test_volume_delete_multi_volumes(self): + volumes = self.setup_volumes_mock(count=3) + + arglist = [v.id for v in volumes] + verifylist = [ + ('volumes', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + calls = [call(v.id) for v in volumes] + + self.volumes_mock.delete.assert_has_calls(calls) From afd1b489ff2137ef1ee0eef1ec1c202f2e1ac899 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 2 Dec 2015 10:04:39 -0600 Subject: [PATCH 0431/3095] Add reno for release notes management Change-Id: Iaaf3f9ca7cb431e41adfb2a052e01e2240a63a07 --- .gitignore | 2 + doc/source/developing.rst | 35 +++ releasenotes/notes/.placeholder | 0 releasenotes/source/_static/.placeholder | 0 releasenotes/source/_templates/.placeholder | 0 releasenotes/source/conf.py | 282 ++++++++++++++++++++ releasenotes/source/index.rst | 8 + releasenotes/source/unreleased.rst | 5 + test-requirements.txt | 1 + tox.ini | 3 + 10 files changed, 336 insertions(+) create mode 100644 releasenotes/notes/.placeholder create mode 100644 releasenotes/source/_static/.placeholder create mode 100644 releasenotes/source/_templates/.placeholder create mode 100644 releasenotes/source/conf.py create mode 100644 releasenotes/source/index.rst create mode 100644 releasenotes/source/unreleased.rst diff --git a/.gitignore b/.gitignore index 43bd5df814..34cd3a23e9 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,5 @@ doc/source/api/ .project .pydevproject cover +# Files created by releasenotes build +releasenotes/build diff --git a/doc/source/developing.rst b/doc/source/developing.rst index c41fbf3472..9f8e55ca2a 100644 --- a/doc/source/developing.rst +++ b/doc/source/developing.rst @@ -98,6 +98,41 @@ create HTML docs, run the following: The resultant HTML will be the ``doc/build/html`` directory. +Release Notes +------------- + +The release notes for a patch should be included in the patch. See the +`Project Team Guide`_ for more information on using reno in OpenStack. + +.. _`Project Team Guide`: http://docs.openstack.org/project-team-guide/release-management.html#managing-release-notes + +If any of the following applies to the patch, a release note is required: + +* The deployer needs to take an action when upgrading +* The plugin interface changes +* A new feature is implemented +* A command or option is removed +* Current behavior is changed +* A security bug is fixed + +Reno is used to generate release notes. Please read the docs for details. In summary, use + +.. code-block:: bash + + $ tox -e venv -- reno new + +Then edit the sample file that was created and push it with your change. + +To see the results: + +.. code-block:: bash + + $ git commit # Commit the change because reno scans git log. + + $ tox -e releasenotes + +Then look at the generated release notes files in releasenotes/build/html in your favorite browser. + Testing new code ---------------- diff --git a/releasenotes/notes/.placeholder b/releasenotes/notes/.placeholder new file mode 100644 index 0000000000..e69de29bb2 diff --git a/releasenotes/source/_static/.placeholder b/releasenotes/source/_static/.placeholder new file mode 100644 index 0000000000..e69de29bb2 diff --git a/releasenotes/source/_templates/.placeholder b/releasenotes/source/_templates/.placeholder new file mode 100644 index 0000000000..e69de29bb2 diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py new file mode 100644 index 0000000000..1b6929e228 --- /dev/null +++ b/releasenotes/source/conf.py @@ -0,0 +1,282 @@ +# -*- coding: utf-8 -*- +# 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. + +# OpenStackClient Release Notes documentation build configuration file, created +# by sphinx-quickstart on Tue Nov 3 17:40:50 2015. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'oslosphinx', + 'reno.sphinxext', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +# source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'OpenStackClient Release Notes' +copyright = u'2015, OpenStackClient Developers' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +from openstackclient import version_info as openstackclient_version +# The full version, including alpha/beta/rc tags. +release = openstackclient_version.version_string_with_vcs() +# The short X.Y version. +version = openstackclient_version.canonical_version_string() + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +# today = '' +# Else, today_fmt is used as the format for a strftime call. +# today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = [] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +# default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +# add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +# add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +# show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +# modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +# keep_warnings = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +# html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +# html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +# html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +# html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +# html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +# html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +# html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +# html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +# html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +# html_additional_pages = {} + +# If false, no module index is generated. +# html_domain_indices = True + +# If false, no index is generated. +# html_use_index = True + +# If true, the index is split into individual pages for each letter. +# html_split_index = False + +# If true, links to the reST sources are added to the pages. +# html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +# html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +# html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +# html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +# html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'OpenStackClientReleaseNotesdoc' + + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # 'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [( + 'index', + 'OpenStackClientReleaseNotes.tex', + u'OpenStackClient Release Notes Documentation', + u'OpenStackClient Developers', + 'manual', +)] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +# latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +# latex_use_parts = False + +# If true, show page references after internal links. +# latex_show_pagerefs = False + +# If true, show URL addresses after external links. +# latex_show_urls = False + +# Documents to append as an appendix to all manuals. +# latex_appendices = [] + +# If false, no module index is generated. +# latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [( + 'index', + 'openstackclientreleasenotes', + u'OpenStackClient Release Notes Documentation', + [u'OpenStackClient Developers'], + 1, +)] + +# If true, show URL addresses after external links. +# man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [( + 'index', + 'OpenStackClientReleaseNotes', + u'OpenStackclient Release Notes Documentation', + u'OpenStackclient Developers', + 'OpenStackClientReleaseNotes', + 'One line description of project.', + 'Miscellaneous', +)] + +# Documents to append as an appendix to all manuals. +# texinfo_appendices = [] + +# If false, no module index is generated. +# texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +# texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +# texinfo_no_detailmenu = False diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst new file mode 100644 index 0000000000..c89ed747ee --- /dev/null +++ b/releasenotes/source/index.rst @@ -0,0 +1,8 @@ +============================= +OpenStackClient Release Notes +============================= + +.. toctree:: + :maxdepth: 1 + + unreleased diff --git a/releasenotes/source/unreleased.rst b/releasenotes/source/unreleased.rst new file mode 100644 index 0000000000..cb3cccf2c6 --- /dev/null +++ b/releasenotes/source/unreleased.rst @@ -0,0 +1,5 @@ +===================== +Current Release Notes +===================== + +.. release-notes:: diff --git a/test-requirements.txt b/test-requirements.txt index 05b95f758a..7322c84aa8 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -9,6 +9,7 @@ fixtures>=1.3.1 mock>=1.2 oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 +reno>=0.1.1 # Apache2 requests-mock>=0.6.0 # Apache-2.0 sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 os-testr>=0.4.1 diff --git a/tox.ini b/tox.ini index 240427d9f3..dd2b7b5412 100644 --- a/tox.ini +++ b/tox.ini @@ -33,6 +33,9 @@ downloadcache = ~/cache/pip [testenv:docs] commands = python setup.py build_sphinx +[testenv:releasenotes] +commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html + [flake8] show-source = True exclude = .git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build,tools From e604a726b2608941dc2b60cf4f071e7777ba2b17 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 2 Dec 2015 13:21:52 -0600 Subject: [PATCH 0432/3095] Convert 2.0 release notes to reno format Change-Id: I3706f78152c77837ba17218d68b57ac10563bfa7 --- doc/source/releases.rst | 35 ++----------------- .../notes/add-ksa-7f0795157d93a898.yaml | 5 +++ .../notes/bug-1475831-3caafd724d4ed644.yaml | 5 +++ .../notes/bug-1517386-1c0aad8e3203b02b.yaml | 5 +++ .../notes/bug-1519181-932c1ff07ef16666.yaml | 6 ++++ .../notes/bug-1519510-3cde4643b33ebb7a.yaml | 5 +++ .../notes/bug-1520115-0367e1c8917dc912.yaml | 5 +++ .../notes/bug-1520541-44d45e4693089c03.yaml | 5 +++ ...o-project-usage-list-e88eb49aa2e96cf7.yaml | 3 ++ .../server-changes-3962541b6ebdbbd8.yaml | 10 ++++++ 10 files changed, 51 insertions(+), 33 deletions(-) create mode 100644 releasenotes/notes/add-ksa-7f0795157d93a898.yaml create mode 100644 releasenotes/notes/bug-1475831-3caafd724d4ed644.yaml create mode 100644 releasenotes/notes/bug-1517386-1c0aad8e3203b02b.yaml create mode 100644 releasenotes/notes/bug-1519181-932c1ff07ef16666.yaml create mode 100644 releasenotes/notes/bug-1519510-3cde4643b33ebb7a.yaml create mode 100644 releasenotes/notes/bug-1520115-0367e1c8917dc912.yaml create mode 100644 releasenotes/notes/bug-1520541-44d45e4693089c03.yaml create mode 100644 releasenotes/notes/no-project-usage-list-e88eb49aa2e96cf7.yaml create mode 100644 releasenotes/notes/server-changes-3962541b6ebdbbd8.yaml diff --git a/doc/source/releases.rst b/doc/source/releases.rst index 857db97eaa..853ef9551a 100644 --- a/doc/source/releases.rst +++ b/doc/source/releases.rst @@ -2,40 +2,9 @@ Release Notes ============= -2.0.0 (1 Dec 2015) -================== +As of release 2.0 the release notes can be found on the OpenStack `Release Notes site`_. -* Dropping python 2.6 support - Bug `1519510 `_ - -* Removed the deprecated command `project usage list` in favor of `usage list` - -* Several updates to `openstack server` - - * Added `--marker` to `openstack server list` - * Added `--limit` to `openstack server list` - * Added `openstack server shelve` - * Added `openstack server unshelve` - * `openstack server resume` now takes multiple servers - * `openstack server suspend` now takes multiple servers - -* Some compute quotas were not being set - Bug `1475831 `_ - -* Add `--all` for `snapshot list` - Bug `1517386 `_ - -* Add `status` to `image list` (v1 and v2) - Bug `1519181 `_ - -* Fix `volume type create`, passes incorrect privacy argument - Bug `1520115 `_ - -* `volume delete` only deletes last volume specified - Bug `1520541 `_ - -* Add dependency on keystoneauth1 module to perform authentication in place - of python-keystoneclient. +.. _`Release Notes site`: http://docs.openstack.org/releasenotes/python-openstackclient 1.9.0 (17 Nov 2015) =================== diff --git a/releasenotes/notes/add-ksa-7f0795157d93a898.yaml b/releasenotes/notes/add-ksa-7f0795157d93a898.yaml new file mode 100644 index 0000000000..c777469e26 --- /dev/null +++ b/releasenotes/notes/add-ksa-7f0795157d93a898.yaml @@ -0,0 +1,5 @@ +--- +other: + - | + Add dependency on keystoneauth1 module to perform authentication in place + of python-keystoneclient. diff --git a/releasenotes/notes/bug-1475831-3caafd724d4ed644.yaml b/releasenotes/notes/bug-1475831-3caafd724d4ed644.yaml new file mode 100644 index 0000000000..dbe5285cba --- /dev/null +++ b/releasenotes/notes/bug-1475831-3caafd724d4ed644.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Some compute quotas were not being set + [Bug `1475831 `_] diff --git a/releasenotes/notes/bug-1517386-1c0aad8e3203b02b.yaml b/releasenotes/notes/bug-1517386-1c0aad8e3203b02b.yaml new file mode 100644 index 0000000000..5353a2516c --- /dev/null +++ b/releasenotes/notes/bug-1517386-1c0aad8e3203b02b.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Add `--all` for `snapshot list` + [Bug `1517386 `_] diff --git a/releasenotes/notes/bug-1519181-932c1ff07ef16666.yaml b/releasenotes/notes/bug-1519181-932c1ff07ef16666.yaml new file mode 100644 index 0000000000..919b0ec803 --- /dev/null +++ b/releasenotes/notes/bug-1519181-932c1ff07ef16666.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Add Status column to default `image list` output (v1 and v2) + [Bug `1519181 `_] + diff --git a/releasenotes/notes/bug-1519510-3cde4643b33ebb7a.yaml b/releasenotes/notes/bug-1519510-3cde4643b33ebb7a.yaml new file mode 100644 index 0000000000..7476b4f814 --- /dev/null +++ b/releasenotes/notes/bug-1519510-3cde4643b33ebb7a.yaml @@ -0,0 +1,5 @@ +--- +other: + - | + Drop Python 2.6 support + [Bug `1519510 `_] diff --git a/releasenotes/notes/bug-1520115-0367e1c8917dc912.yaml b/releasenotes/notes/bug-1520115-0367e1c8917dc912.yaml new file mode 100644 index 0000000000..98829c7595 --- /dev/null +++ b/releasenotes/notes/bug-1520115-0367e1c8917dc912.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Fix `volume type create`, passes incorrect privacy argument + [Bug `1520115 `_] diff --git a/releasenotes/notes/bug-1520541-44d45e4693089c03.yaml b/releasenotes/notes/bug-1520541-44d45e4693089c03.yaml new file mode 100644 index 0000000000..d3e445574c --- /dev/null +++ b/releasenotes/notes/bug-1520541-44d45e4693089c03.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + `volume delete` only deletes last volume specified + [Bug `1520541 `_] diff --git a/releasenotes/notes/no-project-usage-list-e88eb49aa2e96cf7.yaml b/releasenotes/notes/no-project-usage-list-e88eb49aa2e96cf7.yaml new file mode 100644 index 0000000000..67c58700f0 --- /dev/null +++ b/releasenotes/notes/no-project-usage-list-e88eb49aa2e96cf7.yaml @@ -0,0 +1,3 @@ +--- +upgrade: + - Removed the deprecated command `project usage list` in favor of `usage list` diff --git a/releasenotes/notes/server-changes-3962541b6ebdbbd8.yaml b/releasenotes/notes/server-changes-3962541b6ebdbbd8.yaml new file mode 100644 index 0000000000..ae3739bd2b --- /dev/null +++ b/releasenotes/notes/server-changes-3962541b6ebdbbd8.yaml @@ -0,0 +1,10 @@ +--- +other: + - | + `server` resource changes: + + * Added `--limit` and `--marker` to `server list` + * Added `server shelve` + * Added `server unshelve` + * `server resume` now takes multiple servers + * `server suspend` now takes multiple servers From 6cea1e0463632cd73f569376ed05d880c214d4c5 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Fri, 4 Dec 2015 00:21:32 +0800 Subject: [PATCH 0433/3095] Trivial: Reorder doc of "server shelve" command to keep alphabetic order. Change-Id: I616154a8c958b6980233014c7ae1c4544283d72d --- doc/source/command-objects/server.rst | 30 +++++++++++++-------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index 70b4748251..506a82d8e2 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -508,21 +508,6 @@ Resume server(s) Server(s) to resume (name or ID) -server shelve -------------- - -Shelve server(s) - -.. program:: server shelve -.. code:: bash - - os server shelve - [ ...] - -.. describe:: - - Server(s) to shelve (name or ID) - server set ---------- @@ -555,6 +540,21 @@ Set server properties Server (name or ID) +server shelve +------------- + +Shelve server(s) + +.. program:: server shelve +.. code:: bash + + os server shelve + [ ...] + +.. describe:: + + Server(s) to shelve (name or ID) + server show ----------- From 197d86dffa4ba7eefbf6f18381ff1ced9dc7f23f Mon Sep 17 00:00:00 2001 From: Xi Yang Date: Tue, 1 Dec 2015 18:42:54 +0800 Subject: [PATCH 0434/3095] Fix exception when doing volume set operation The v2 SetVolume extends from show.ShowOne and returns None after setting volume operation. It will raise an exception. This patch is going to fix the issue by changing the parent class of SetVolume to command.Command. Change-Id: Iefa453fe4adad06f2a0601a052c01e74004be5b7 Closes-bug: 1521896 --- openstackclient/volume/v2/volume.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index 925b01d435..bbcceca6bf 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -326,7 +326,7 @@ def _format_attach(attachments): ) for s in data)) -class SetVolume(show.ShowOne): +class SetVolume(command.Command): """Set volume properties""" log = logging.getLogger(__name__ + '.SetVolume') From ad17d847f84d2b3912a813e4c17ef3dd5257d182 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 3 Dec 2015 15:07:55 +0000 Subject: [PATCH 0435/3095] Updated from global requirements Change-Id: I3ca62d29df56ec45bf1fe10d920c1f9ed5f5b4f1 --- test-requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test-requirements.txt b/test-requirements.txt index 7322c84aa8..b013dc3932 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -9,8 +9,8 @@ fixtures>=1.3.1 mock>=1.2 oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 -reno>=0.1.1 # Apache2 -requests-mock>=0.6.0 # Apache-2.0 +reno>=0.1.1 # Apache2 +requests-mock>=0.7.0 # Apache-2.0 sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 os-testr>=0.4.1 testrepository>=0.0.18 From ca76260bf803895960cea3d6f735ba98ffb85463 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Fri, 4 Dec 2015 11:51:15 +0800 Subject: [PATCH 0436/3095] Trivial: Improve doc for "server create" command The following options of "server create" command support searching by both name and ID. So add this info into doc. --image, --volume, --flavor, --security. Change-Id: I93b167da58144e5de6c9996009b7ea2449fb4cd8 --- doc/source/command-objects/server.rst | 9 +++++---- openstackclient/compute/v2/server.py | 8 ++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index 70b4748251..ddd1d5cd53 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -77,19 +77,20 @@ Create a new server .. option:: --image - Create server from this image + Create server from this image (name or ID) .. option:: --volume - Create server from this volume + Create server from this volume (name or ID) .. option:: --flavor - Create server with this flavor + Create server with this flavor (name or ID) .. option:: --security-group - Security group to assign to this server (repeat for multiple groups) + Security group to assign to this server (name or ID) + (repeat for multiple groups) .. option:: --key-name diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 9dca784487..e7b18c43a4 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -276,25 +276,25 @@ def get_parser(self, prog_name): disk_group.add_argument( '--image', metavar='', - help=_('Create server from this image'), + help=_('Create server from this image (name or ID)'), ) disk_group.add_argument( '--volume', metavar='', - help=_('Create server from this volume'), + help=_('Create server from this volume (name or ID)'), ) parser.add_argument( '--flavor', metavar='', required=True, - help=_('Create server with this flavor'), + help=_('Create server with this flavor (name or ID)'), ) parser.add_argument( '--security-group', metavar='', action='append', default=[], - help=_('Security group to assign to this server ' + help=_('Security group to assign to this server (name or ID) ' '(repeat for multiple groups)'), ) parser.add_argument( From b4e88aa3cecdb6d3d8c883b9bbca3e755c3485dc Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 5 Dec 2015 10:43:06 +0800 Subject: [PATCH 0437/3095] Trivial: Remove unuseful doc of "network list" command. There is actually no "--dhcp" option in "network list" command. So remove the doc from .rst file. Change-Id: I481b260f99be635be4de8e6780206af47cdaa7ce --- doc/source/command-objects/network.rst | 5 ----- 1 file changed, 5 deletions(-) diff --git a/doc/source/command-objects/network.rst b/doc/source/command-objects/network.rst index 6b63c35da8..6199d0248b 100644 --- a/doc/source/command-objects/network.rst +++ b/doc/source/command-objects/network.rst @@ -74,17 +74,12 @@ List networks os network list [--external] - [--dhcp ] [--long] .. option:: --external List external networks -.. option:: --dhcp - - DHCP agent ID - .. option:: --long List additional fields in output From 29b994567787a47048f1a27a55efb88d68645490 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 1 Dec 2015 18:05:52 +0800 Subject: [PATCH 0438/3095] Refactor network test: Introduce TestNetworkv2 and TestNetwork to improve unit test of network The class inherit architecture in network unit test is different from other test classes, which leads to lots of redundant code. This patch will make it the same as the other test classes. And it will be more convenience for the coming up refactor. Change-Id: I6f239dd54b9401ff2bbcf7ffdeb18769a450f573 Implements: blueprint osc-network-unit-test-refactor Related-to: blueprint neutron-client --- openstackclient/tests/network/v2/fakes.py | 22 +++++++++++++++++++ .../tests/network/v2/test_network.py | 13 +++++++++++ 2 files changed, 35 insertions(+) diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index ea191c8e8c..a7176ce2e0 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -11,8 +11,13 @@ # under the License. # +import argparse import mock +from openstackclient.api import network_v2 +from openstackclient.tests import fakes +from openstackclient.tests import utils + extension_name = 'Matrix' extension_namespace = 'http://docs.openstack.org/network/' extension_description = 'Simulated reality' @@ -33,3 +38,20 @@ class FakeNetworkV2Client(object): def __init__(self, **kwargs): self.list_extensions = mock.Mock(return_value={'extensions': [NETEXT]}) + + +class TestNetworkV2(utils.TestCommand): + def setUp(self): + super(TestNetworkV2, self).setUp() + + self.namespace = argparse.Namespace() + + self.app.client_manager.network = FakeNetworkV2Client( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN, + ) + + self.app.client_manager.network.api = network_v2.APIv2( + session=mock.Mock(), + service_type="network", + ) diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index 55062594f0..9a85826e75 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -20,6 +20,7 @@ from openstackclient.tests.identity.v2_0 import fakes as identity_fakes_v2 from openstackclient.tests.identity.v3 import fakes as identity_fakes_v3 from openstackclient.tests.network import common +from openstackclient.tests.network.v2 import fakes as network_fakes RESOURCE = 'network' RESOURCES = 'networks' @@ -59,6 +60,18 @@ ] +class TestNetwork(network_fakes.TestNetworkV2): + + def setUp(self): + super(TestNetwork, self).setUp() + + # Get a shortcut to the network client + self.network = self.app.client_manager.network + + # Get a shortcut to the APIManager + self.api = self.app.client_manager.network.api + + class TestCreateNetwork(common.TestNetworkBase): def test_create_no_options(self): arglist = [ From 038334ff986e76498ca4e02aafe43bb6e1891b11 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 1 Dec 2015 19:47:19 +0800 Subject: [PATCH 0439/3095] Refactor TestCreateNetwork: Split TestCreateNetwork into two classes for identity v2 and v3 In TestCreateNetwork, both Identity v2 and v3 clients are tested. As a result, we should initialize the identity client again and again in each test function. To reduce redundant code, this patch split TestCreateNetwork to TestCreateNetworkIdentityV2 and V3. And then initialize the identity client only once in each of them. Change-Id: I349fe7f827524beb541efe7dd9460c534254b80c Implements: blueprint osc-network-unit-test-refactor Related-to: blueprint neutron-client --- openstackclient/tests/network/v2/test_network.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index 9a85826e75..510e06b90a 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -72,7 +72,11 @@ def setUp(self): self.api = self.app.client_manager.network.api -class TestCreateNetwork(common.TestNetworkBase): +class TestCreateNetworkIdentityV3(TestNetwork): + + def setUp(self): + super(TestCreateNetworkIdentityV3, self).setUp() + def test_create_no_options(self): arglist = [ FAKE_NAME, @@ -174,6 +178,12 @@ def test_create_other_options(self): }) self.assertEqual(FILTERED, result) + +class TestCreateNetworkIdentityV2(TestNetwork): + + def setUp(self): + super(TestCreateNetworkIdentityV2, self).setUp() + def test_create_with_project_identityv2(self): arglist = [ "--project", identity_fakes_v2.project_name, From af6269d3107adddec4eff2bb2497a387172437ec Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 1 Dec 2015 19:56:20 +0800 Subject: [PATCH 0440/3095] Refactor TestCreateNetwork: Setup cmd in setUp() in TestCreateNetworkIdentityV3 This patch setup the tested command only once in setUp() for TestCreateNetworkIdentityV3. Change-Id: Iff5119fa24c9cc0caa72ff0e8f63e8dcac72470a Implements: blueprint osc-network-unit-test-refactor Related-to: blueprint neutron-client --- .../tests/network/v2/test_network.py | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index 510e06b90a..ce0a0de706 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -77,6 +77,12 @@ class TestCreateNetworkIdentityV3(TestNetwork): def setUp(self): super(TestCreateNetworkIdentityV3, self).setUp() + self.new_network = mock.Mock(return_value=copy.deepcopy(RESPONSE)) + self.network.create_network = self.new_network + + # Get the command object to test + self.cmd = network.CreateNetwork(self.app, self.namespace) + def test_create_no_options(self): arglist = [ FAKE_NAME, @@ -87,14 +93,11 @@ def test_create_no_options(self): ('shared', None), ('project', None), ] - mocker = mock.Mock(return_value=copy.deepcopy(RESPONSE)) - self.app.client_manager.network.create_network = mocker - cmd = network.CreateNetwork(self.app, self.namespace) - parsed_args = self.check_parser(cmd, arglist, verifylist) - result = list(cmd.take_action(parsed_args)) + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = list(self.cmd.take_action(parsed_args)) - mocker.assert_called_with({ + self.network.create_network.assert_called_with({ RESOURCE: { 'admin_state_up': True, 'name': FAKE_NAME, @@ -117,8 +120,6 @@ def test_create_all_options(self): ('project_domain', identity_fakes_v3.domain_name), ('name', FAKE_NAME), ] - mocker = mock.Mock(return_value=copy.deepcopy(RESPONSE)) - self.app.client_manager.network.create_network = mocker identity_client = identity_fakes_v3.FakeIdentityv3Client( endpoint=fakes.AUTH_URL, token=fakes.AUTH_TOKEN, @@ -136,12 +137,11 @@ def test_create_all_options(self): copy.deepcopy(identity_fakes_v3.DOMAIN), loaded=True, ) - cmd = network.CreateNetwork(self.app, self.namespace) - parsed_args = self.check_parser(cmd, arglist, verifylist) - result = list(cmd.take_action(parsed_args)) + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = list(self.cmd.take_action(parsed_args)) - mocker.assert_called_with({ + self.network.create_network.assert_called_with({ RESOURCE: { 'admin_state_up': False, 'name': FAKE_NAME, @@ -162,14 +162,11 @@ def test_create_other_options(self): ('shared', False), ('name', FAKE_NAME), ] - mocker = mock.Mock(return_value=copy.deepcopy(RESPONSE)) - self.app.client_manager.network.create_network = mocker - cmd = network.CreateNetwork(self.app, self.namespace) - parsed_args = self.check_parser(cmd, arglist, verifylist) - result = list(cmd.take_action(parsed_args)) + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = list(self.cmd.take_action(parsed_args)) - mocker.assert_called_with({ + self.network.create_network.assert_called_with({ RESOURCE: { 'admin_state_up': True, 'name': FAKE_NAME, From 3cc38d2844408ac2ce0b29d7d8f07ef8dea22417 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 1 Dec 2015 19:59:27 +0800 Subject: [PATCH 0441/3095] Refactor TestCreateNetwork: Setup cmd in setUp() in TestCreateNetworkIdentityV2 This patch setup the tested command only once in setUp() for TestCreateNetworkIdentityV2. Change-Id: I689980674c9500c0c040d27164ec61f5fcf40ee5 Implements: blueprint osc-network-unit-test-refactor Related-to: blueprint neutron-client --- .../tests/network/v2/test_network.py | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index ce0a0de706..c45d624bf8 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -181,6 +181,12 @@ class TestCreateNetworkIdentityV2(TestNetwork): def setUp(self): super(TestCreateNetworkIdentityV2, self).setUp() + self.new_network = mock.Mock(return_value=copy.deepcopy(RESPONSE)) + self.network.create_network = self.new_network + + # Get the command object to test + self.cmd = network.CreateNetwork(self.app, self.namespace) + def test_create_with_project_identityv2(self): arglist = [ "--project", identity_fakes_v2.project_name, @@ -192,8 +198,6 @@ def test_create_with_project_identityv2(self): ('name', FAKE_NAME), ('project', identity_fakes_v2.project_name), ] - mocker = mock.Mock(return_value=copy.deepcopy(RESPONSE)) - self.app.client_manager.network.create_network = mocker identity_client = identity_fakes_v2.FakeIdentityv2Client( endpoint=fakes.AUTH_URL, token=fakes.AUTH_TOKEN, @@ -205,12 +209,11 @@ def test_create_with_project_identityv2(self): copy.deepcopy(identity_fakes_v2.PROJECT), loaded=True, ) - cmd = network.CreateNetwork(self.app, self.namespace) - parsed_args = self.check_parser(cmd, arglist, verifylist) - result = list(cmd.take_action(parsed_args)) + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = list(self.cmd.take_action(parsed_args)) - mocker.assert_called_with({ + self.network.create_network.assert_called_with({ RESOURCE: { 'admin_state_up': True, 'name': FAKE_NAME, @@ -232,8 +235,6 @@ def test_create_with_domain_identityv2(self): ('project_domain', identity_fakes_v3.domain_name), ('name', FAKE_NAME), ] - mocker = mock.Mock(return_value=copy.deepcopy(RESPONSE)) - self.app.client_manager.network.create_network = mocker identity_client = identity_fakes_v2.FakeIdentityv2Client( endpoint=fakes.AUTH_URL, token=fakes.AUTH_TOKEN, @@ -245,12 +246,12 @@ def test_create_with_domain_identityv2(self): copy.deepcopy(identity_fakes_v2.PROJECT), loaded=True, ) - cmd = network.CreateNetwork(self.app, self.namespace) - parsed_args = self.check_parser(cmd, arglist, verifylist) + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.assertRaises( AttributeError, - cmd.take_action, + self.cmd.take_action, parsed_args, ) From 16d07e71015e4a1696a891804b2ed78d51ff6add Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 1 Dec 2015 20:05:36 +0800 Subject: [PATCH 0442/3095] Refactor TestCreateNetwork: Setup identity client in setUp() in TestCreateNetworkIdentityV3 This patch setup the v3 identity client only once in setUp() for TestCreateNetworkIdentityV3. Change-Id: Ia20db01af85f868a88bf905174b8556979b0a39a Implements: blueprint osc-network-unit-test-refactor Related-to: blueprint neutron-client --- .../tests/network/v2/test_network.py | 41 +++++++++++-------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index c45d624bf8..b00ae6608e 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -83,6 +83,30 @@ def setUp(self): # Get the command object to test self.cmd = network.CreateNetwork(self.app, self.namespace) + # Set identity client v3. And get a shortcut to Identity client. + identity_client = identity_fakes_v3.FakeIdentityv3Client( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN, + ) + self.app.client_manager.identity = identity_client + self.identity = self.app.client_manager.identity + + # Get a shortcut to the ProjectManager Mock + self.projects_mock = self.identity.projects + self.projects_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes_v3.PROJECT), + loaded=True, + ) + + # Get a shortcut to the DomainManager Mock + self.domains_mock = self.identity.domains + self.domains_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes_v3.DOMAIN), + loaded=True, + ) + def test_create_no_options(self): arglist = [ FAKE_NAME, @@ -120,23 +144,6 @@ def test_create_all_options(self): ('project_domain', identity_fakes_v3.domain_name), ('name', FAKE_NAME), ] - identity_client = identity_fakes_v3.FakeIdentityv3Client( - endpoint=fakes.AUTH_URL, - token=fakes.AUTH_TOKEN, - ) - self.app.client_manager.identity = identity_client - self.projects_mock = self.app.client_manager.identity.projects - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes_v3.PROJECT), - loaded=True, - ) - self.domains_mock = self.app.client_manager.identity.domains - self.domains_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes_v3.DOMAIN), - loaded=True, - ) parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = list(self.cmd.take_action(parsed_args)) From ee6855858fe2c0bec00f554c56a2d697c26f9b7f Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 1 Dec 2015 20:14:47 +0800 Subject: [PATCH 0443/3095] Refactor TestCreateNetwork: Setup identity client in setUp() in TestCreateNetworkIdentityV2 This patch setup the v2 identity client only once in setUp() for TestCreateNetworkIdentityV2. Change-Id: I9d7a83e5e67e810a5c5f760fb9c40ce450f7dc99 Implements: blueprint osc-network-unit-test-refactor Related-to: blueprint neutron-client --- .../tests/network/v2/test_network.py | 40 +++++++++---------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index b00ae6608e..63bae3e32f 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -194,6 +194,24 @@ def setUp(self): # Get the command object to test self.cmd = network.CreateNetwork(self.app, self.namespace) + # Set identity client v2. And get a shortcut to Identity client. + identity_client = identity_fakes_v2.FakeIdentityv2Client( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN, + ) + self.app.client_manager.identity = identity_client + self.identity = self.app.client_manager.identity + + # Get a shortcut to the ProjectManager Mock + self.projects_mock = self.identity.tenants + self.projects_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes_v2.PROJECT), + loaded=True, + ) + + # There is no DomainManager Mock in fake identity v2. + def test_create_with_project_identityv2(self): arglist = [ "--project", identity_fakes_v2.project_name, @@ -205,17 +223,6 @@ def test_create_with_project_identityv2(self): ('name', FAKE_NAME), ('project', identity_fakes_v2.project_name), ] - identity_client = identity_fakes_v2.FakeIdentityv2Client( - endpoint=fakes.AUTH_URL, - token=fakes.AUTH_TOKEN, - ) - self.app.client_manager.identity = identity_client - self.projects_mock = self.app.client_manager.identity.tenants - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes_v2.PROJECT), - loaded=True, - ) parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = list(self.cmd.take_action(parsed_args)) @@ -242,17 +249,6 @@ def test_create_with_domain_identityv2(self): ('project_domain', identity_fakes_v3.domain_name), ('name', FAKE_NAME), ] - identity_client = identity_fakes_v2.FakeIdentityv2Client( - endpoint=fakes.AUTH_URL, - token=fakes.AUTH_TOKEN, - ) - self.app.client_manager.identity = identity_client - self.projects_mock = self.app.client_manager.identity.tenants - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes_v2.PROJECT), - loaded=True, - ) parsed_args = self.check_parser(self.cmd, arglist, verifylist) From d1fffb25436944cf4d163968273a46d18c81924f Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 1 Dec 2015 20:28:36 +0800 Subject: [PATCH 0444/3095] Refactor TestDeleteNetwork: Use TestNetwork in TestDeleteNetwork Make TestDeleteNetwork inherit from TestNetwork, and have the same class architecture as the other test classes. Change-Id: I37047d7d13931b2a8f25665d6427efc05fc4f989 Implements: blueprint osc-network-unit-test-refactor Related-to: blueprint neutron-client --- .../tests/network/v2/test_network.py | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index 63bae3e32f..e747d3c72a 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -259,7 +259,22 @@ def test_create_with_domain_identityv2(self): ) -class TestDeleteNetwork(common.TestNetworkBase): +class TestDeleteNetwork(TestNetwork): + + def setUp(self): + super(TestDeleteNetwork, self).setUp() + + self.network.delete_network = mock.Mock( + return_value=None + ) + + self.network.list_networks = mock.Mock( + return_value={RESOURCES: [copy.deepcopy(RECORD)]} + ) + + # Get the command object to test + self.cmd = network.DeleteNetwork(self.app, self.namespace) + def test_delete(self): arglist = [ FAKE_NAME, @@ -267,16 +282,11 @@ def test_delete(self): verifylist = [ ('networks', [FAKE_NAME]), ] - lister = mock.Mock(return_value={RESOURCES: [copy.deepcopy(RECORD)]}) - self.app.client_manager.network.list_networks = lister - mocker = mock.Mock(return_value=None) - self.app.client_manager.network.delete_network = mocker - cmd = network.DeleteNetwork(self.app, self.namespace) - parsed_args = self.check_parser(cmd, arglist, verifylist) - result = cmd.take_action(parsed_args) + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) - mocker.assert_called_with(FAKE_ID) + self.network.delete_network.assert_called_with(FAKE_ID) self.assertEqual(None, result) From 697df67611e85116e67e65d6c28b938c172e89ef Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Wed, 2 Dec 2015 09:37:15 +0800 Subject: [PATCH 0445/3095] Refactor TestListNetwork: Use TestNetwork in TestListNetwork Abstract cloumns and data out in TestListNetwork so that they can be reused by each case. Also rename n_mock to network_list because it is used to fake function network_list(). Change-Id: I3f65f7bd6c587c167dc1cf8b6d34e003c6454e57 Implements: blueprint osc-network-unit-test-refactor Related-to: blueprint neutron-client --- .../tests/network/v2/test_network.py | 95 +++++++++---------- 1 file changed, 44 insertions(+), 51 deletions(-) diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index e747d3c72a..4861d7df79 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -36,7 +36,6 @@ 'subnets': ['a', 'b'], 'tenant_id': FAKE_PROJECT, } -COLUMNS = ['ID', 'Name', 'Subnets'] RESPONSE = {RESOURCE: copy.deepcopy(RECORD)} FILTERED = [ ( @@ -293,7 +292,35 @@ def test_delete(self): @mock.patch( 'openstackclient.api.network_v2.APIv2.network_list' ) -class TestListNetwork(common.TestNetworkBase): +class TestListNetwork(TestNetwork): + + columns = [ + 'ID', + 'Name', + 'Subnets' + ] + columns_long = [ + 'ID', + 'Name', + 'Status', + 'Project', + 'State', + 'Shared', + 'Subnets', + 'Network Type', + 'Router Type', + ] + + data = [ + (FAKE_ID, FAKE_NAME, 'a, b'), + (FAKE_ID, FAKE_NAME, 'a, b'), + ] + data_long = [ + (FAKE_ID, FAKE_NAME, 'ACTIVE', FAKE_PROJECT, + 'UP', '', 'a, b', '', 'External'), + (FAKE_ID, FAKE_NAME, 'ACTIVE', FAKE_PROJECT, + 'UP', '', 'a, b', '', 'External'), + ] def setUp(self): super(TestListNetwork, self).setUp() @@ -306,8 +333,8 @@ def setUp(self): copy.deepcopy(RECORD), ] - def test_network_list_no_options(self, n_mock): - n_mock.return_value = self.NETWORK_LIST + def test_network_list_no_options(self, network_list): + network_list.return_value = self.NETWORK_LIST arglist = [] verifylist = [ @@ -320,19 +347,15 @@ def test_network_list_no_options(self, n_mock): columns, data = self.cmd.take_action(parsed_args) # Set expected values - n_mock.assert_called_with( + network_list.assert_called_with( external=False, ) - self.assertEqual(tuple(COLUMNS), columns) - datalist = [ - (FAKE_ID, FAKE_NAME, 'a, b'), - (FAKE_ID, FAKE_NAME, 'a, b'), - ] - self.assertEqual(datalist, list(data)) + self.assertEqual(tuple(self.columns), columns) + self.assertEqual(self.data, list(data)) - def test_list_external(self, n_mock): - n_mock.return_value = self.NETWORK_LIST + def test_list_external(self, network_list): + network_list.return_value = self.NETWORK_LIST arglist = [ '--external', @@ -347,19 +370,15 @@ def test_list_external(self, n_mock): columns, data = self.cmd.take_action(parsed_args) # Set expected values - n_mock.assert_called_with( + network_list.assert_called_with( external=True, ) - self.assertEqual(tuple(COLUMNS), columns) - datalist = [ - (FAKE_ID, FAKE_NAME, 'a, b'), - (FAKE_ID, FAKE_NAME, 'a, b'), - ] - self.assertEqual(datalist, list(data)) + self.assertEqual(tuple(self.columns), columns) + self.assertEqual(self.data, list(data)) - def test_network_list_long(self, n_mock): - n_mock.return_value = self.NETWORK_LIST + def test_network_list_long(self, network_list): + network_list.return_value = self.NETWORK_LIST arglist = [ '--long', @@ -374,38 +393,12 @@ def test_network_list_long(self, n_mock): columns, data = self.cmd.take_action(parsed_args) # Set expected values - n_mock.assert_called_with( + network_list.assert_called_with( external=False, ) - collist = ( - 'ID', - 'Name', - 'Status', - 'Project', - 'State', - 'Shared', - 'Subnets', - 'Network Type', - 'Router Type', - ) - self.assertEqual(columns, collist) - dataitem = ( - FAKE_ID, - FAKE_NAME, - 'ACTIVE', - FAKE_PROJECT, - 'UP', - '', - 'a, b', - '', - 'External', - ) - datalist = [ - dataitem, - dataitem, - ] - self.assertEqual(list(data), datalist) + self.assertEqual(columns, tuple(self.columns_long)) + self.assertEqual(self.data_long, list(data)) class TestSetNetwork(common.TestNetworkBase): From 84cf168d1c13f4654b04bbca00b3bbc1e6a9f6a1 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Wed, 2 Dec 2015 09:51:48 +0800 Subject: [PATCH 0446/3095] Refactor TestSetNetwork: Use TestNetwork in TestSetNetwork Use TestNetwork in TestSetNetwork, and also setup test command in setUp(). Change-Id: I4e72332fcf253407bb3d5f20aaf4be047046e146 Related-to: blueprint neutron-client --- .../tests/network/v2/test_network.py | 49 +++++++++---------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index 4861d7df79..18bcdb7716 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -401,7 +401,21 @@ def test_network_list_long(self, network_list): self.assertEqual(self.data_long, list(data)) -class TestSetNetwork(common.TestNetworkBase): +class TestSetNetwork(TestNetwork): + + def setUp(self): + super(TestSetNetwork, self).setUp() + + self.network.update_network = mock.Mock( + return_value=None + ) + + self.network.list_networks = mock.Mock( + return_value={RESOURCES: [copy.deepcopy(RECORD)]} + ) + + self.cmd = network.SetNetwork(self.app, self.namespace) + def test_set_this(self): arglist = [ FAKE_NAME, @@ -415,18 +429,13 @@ def test_set_this(self): ('name', 'noob'), ('shared', True), ] - lister = mock.Mock(return_value={RESOURCES: [copy.deepcopy(RECORD)]}) - self.app.client_manager.network.list_networks = lister - mocker = mock.Mock(return_value=None) - self.app.client_manager.network.update_network = mocker - cmd = network.SetNetwork(self.app, self.namespace) - parsed_args = self.check_parser(cmd, arglist, verifylist) - result = cmd.take_action(parsed_args) + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) exp = {'admin_state_up': True, 'name': 'noob', 'shared': True} exp_record = {RESOURCE: exp} - mocker.assert_called_with(FAKE_ID, exp_record) + self.network.update_network.assert_called_with(FAKE_ID, exp_record) self.assertEqual(None, result) def test_set_that(self): @@ -440,31 +449,21 @@ def test_set_that(self): ('admin_state', False), ('shared', False), ] - lister = mock.Mock(return_value={RESOURCES: [copy.deepcopy(RECORD)]}) - self.app.client_manager.network.list_networks = lister - mocker = mock.Mock(return_value=None) - self.app.client_manager.network.update_network = mocker - cmd = network.SetNetwork(self.app, self.namespace) - parsed_args = self.check_parser(cmd, arglist, verifylist) - result = cmd.take_action(parsed_args) + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) exp = {'admin_state_up': False, 'shared': False} exp_record = {RESOURCE: exp} - mocker.assert_called_with(FAKE_ID, exp_record) + self.network.update_network.assert_called_with(FAKE_ID, exp_record) self.assertEqual(None, result) def test_set_nothing(self): arglist = [FAKE_NAME, ] verifylist = [('identifier', FAKE_NAME), ] - lister = mock.Mock(return_value={RESOURCES: [copy.deepcopy(RECORD)]}) - self.app.client_manager.network.list_networks = lister - mocker = mock.Mock(return_value=None) - self.app.client_manager.network.update_network = mocker - cmd = network.SetNetwork(self.app, self.namespace) - - parsed_args = self.check_parser(cmd, arglist, verifylist) - self.assertRaises(exceptions.CommandError, cmd.take_action, + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) From 0cc1492ccdf44312ea50630d92ac121ea8e579a8 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Wed, 2 Dec 2015 10:23:52 +0800 Subject: [PATCH 0447/3095] Refactor TestShowNetwork: Use TestNetwork in TestShowNetwork There are severail problems in TestShowNetwork: 1. NETWORK_ITEM is not used. 2. Some redundant code. 3. The param n_mock is used to fake find_attr(), but the name is confusing. So rename it to find_attr(). This patch fixes them. Change-Id: I7bc909057125013fb9d215e92c61f847300f93e2 Implements: blueprint osc-network-unit-test-refactor Related-to: blueprint neutron-client --- .../tests/network/v2/test_network.py | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index 18bcdb7716..fab4a4d6b7 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -19,7 +19,6 @@ from openstackclient.tests import fakes from openstackclient.tests.identity.v2_0 import fakes as identity_fakes_v2 from openstackclient.tests.identity.v3 import fakes as identity_fakes_v3 -from openstackclient.tests.network import common from openstackclient.tests.network.v2 import fakes as network_fakes RESOURCE = 'network' @@ -414,6 +413,7 @@ def setUp(self): return_value={RESOURCES: [copy.deepcopy(RECORD)]} ) + # Get the command object to test self.cmd = network.SetNetwork(self.app, self.namespace) def test_set_this(self): @@ -470,7 +470,7 @@ def test_set_nothing(self): @mock.patch( 'openstackclient.api.network_v2.APIv2.find_attr' ) -class TestShowNetwork(common.TestNetworkBase): +class TestShowNetwork(TestNetwork): def setUp(self): super(TestShowNetwork, self).setUp() @@ -478,32 +478,28 @@ def setUp(self): # Get the command object to test self.cmd = network.ShowNetwork(self.app, self.namespace) - self.NETWORK_ITEM = copy.deepcopy(RECORD) - - def test_show_no_options(self, n_mock): + def test_show_no_options(self, find_attr): arglist = [ FAKE_NAME, ] verifylist = [ ('identifier', FAKE_NAME), ] - n_mock.return_value = copy.deepcopy(RECORD) - self.cmd = network.ShowNetwork(self.app, self.namespace) + find_attr.return_value = copy.deepcopy(RECORD) parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = list(self.cmd.take_action(parsed_args)) - n_mock.assert_called_with('networks', FAKE_NAME) + find_attr.assert_called_with('networks', FAKE_NAME) self.assertEqual(FILTERED, result) - def test_show_all_options(self, n_mock): + def test_show_all_options(self, find_attr): arglist = [FAKE_NAME] verifylist = [('identifier', FAKE_NAME)] - n_mock.return_value = copy.deepcopy(RECORD) - self.cmd = network.ShowNetwork(self.app, self.namespace) + find_attr.return_value = copy.deepcopy(RECORD) parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = list(self.cmd.take_action(parsed_args)) - n_mock.assert_called_with('networks', FAKE_NAME) + find_attr.assert_called_with('networks', FAKE_NAME) self.assertEqual(FILTERED, result) From 0bb645cb99ad6a93c18fdce462154446907f1d8c Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Wed, 2 Dec 2015 11:03:49 +0800 Subject: [PATCH 0448/3095] Refactor network test: Remove unusful test code. openstackclient/tests/network/common.py won't be used anymore. Remove it. Change-Id: I641f6d44852931e31ecda22261bfbc9f451446de Implements: blueprint osc-network-unit-test-refactor Related-to: blueprint neutron-client --- openstackclient/tests/network/common.py | 35 ------------------------- 1 file changed, 35 deletions(-) delete mode 100644 openstackclient/tests/network/common.py diff --git a/openstackclient/tests/network/common.py b/openstackclient/tests/network/common.py deleted file mode 100644 index 31fde257a1..0000000000 --- a/openstackclient/tests/network/common.py +++ /dev/null @@ -1,35 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - -import argparse -import mock - -from openstackclient.api import network_v2 -from openstackclient.tests import utils - - -class FakeNetworkClient(object): - pass - - -class TestNetworkBase(utils.TestCommand): - def setUp(self): - super(TestNetworkBase, self).setUp() - self.namespace = argparse.Namespace() - - self.app.client_manager.network = FakeNetworkClient() - self.app.client_manager.network.api = network_v2.APIv2( - session=mock.Mock(), - service_type="network", - ) - self.api = self.app.client_manager.network.api From dd1ca68f56b0a3631ca4bb682c0c0ae8cc4215ae Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sun, 6 Dec 2015 01:18:13 +0800 Subject: [PATCH 0449/3095] Trivial: Import network.common as network_common in server.py In server.py, identity.common is imported as identity_common. But network.common is imported as common, which is confuseing. This patch imports network.common as network_common. Change-Id: I74295bc88b22de398ab64fe556aedaca2453d17d --- openstackclient/compute/v2/server.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index d58ebacd92..ee65070098 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -37,7 +37,7 @@ from openstackclient.common import utils from openstackclient.i18n import _ # noqa from openstackclient.identity import common as identity_common -from openstackclient.network import common +from openstackclient.network import common as network_common def _format_servers_list_networks(networks): @@ -476,15 +476,19 @@ def take_action(self, parsed_args): if neutron_enabled: network_client = self.app.client_manager.network if nic_info["net-id"]: - nic_info["net-id"] = common.find(network_client, - 'network', - 'networks', - nic_info["net-id"]) + nic_info["net-id"] = network_common.find( + network_client, + 'network', + 'networks', + nic_info["net-id"] + ) if nic_info["port-id"]: - nic_info["port-id"] = common.find(network_client, - 'port', - 'ports', - nic_info["port-id"]) + nic_info["port-id"] = network_common.find( + network_client, + 'port', + 'ports', + nic_info["port-id"] + ) else: if nic_info["net-id"]: nic_info["net-id"] = utils.find_resource( From 7d01a44822b6fb16f900a9c375ac1cefa49cb148 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sun, 6 Dec 2015 20:47:37 +0000 Subject: [PATCH 0450/3095] Updated from global requirements Change-Id: I7910586e32ec248e95f7f54fe776b3603f667c49 --- requirements.txt | 2 +- test-requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 8b1a999d2c..0bce74a2f7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ six>=1.9.0 Babel>=1.3 cliff>=1.15.0 # Apache-2.0 -keystoneauth1>=1.0.0 +keystoneauth1>=2.1.0 openstacksdk os-client-config!=1.6.2,>=1.4.0 oslo.config>=2.7.0 # Apache-2.0 diff --git a/test-requirements.txt b/test-requirements.txt index b013dc3932..dbfeb4324c 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -16,7 +16,7 @@ os-testr>=0.4.1 testrepository>=0.0.18 testtools>=1.4.0 WebOb>=1.2.3 -tempest-lib>=0.10.0 +tempest-lib>=0.11.0 # Install these to generate sphinx autodocs python-barbicanclient>=3.3.0 From 0d7be6fbcb3423fb0fe9c483c2980260bddf2ba9 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Mon, 7 Dec 2015 22:16:04 +0800 Subject: [PATCH 0451/3095] Use formatter in server.py for "server list" tests. We should use the formatter in the original code to set the expected data in each test case. The same problem exists in other test cases, like "server create". But data structure in TestServerCreate is different from TestServerList, so will fix the problem in another patch. Change-Id: I233f7a91cd4cc1e996941e26ea85490fa3290572 --- openstackclient/tests/compute/v2/test_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 359920f230..ce2dcdf7e8 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -674,7 +674,7 @@ def setUp(self): s.id, s.name, s.status, - u'public=10.20.30.40, 2001:db8::5', + server._format_servers_list_networks(s.networks), )) def test_server_list_no_option(self): From 6a3bc765f4fd23f1592e5e4e8fc6bc77634e8dc2 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 1 Dec 2015 16:27:40 +0800 Subject: [PATCH 0452/3095] Introduce class FakeNetwork to fake one or more networks. Introduce a new class FakeNetwork to fake one or more Network objects so that we don't need to initialize objects once and once again in each test case. Change-Id: I519e5368025946a737002a2e97bc218b65a78fa2 Implements: blueprint osc-network-unit-test-refactor Related-to: blueprint neutron-client --- openstackclient/tests/network/v2/fakes.py | 89 +++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index a7176ce2e0..10814f96fb 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -12,7 +12,9 @@ # import argparse +import copy import mock +import uuid from openstackclient.api import network_v2 from openstackclient.tests import fakes @@ -46,6 +48,8 @@ def setUp(self): self.namespace = argparse.Namespace() + self.app.client_manager.session = mock.Mock() + self.app.client_manager.network = FakeNetworkV2Client( endpoint=fakes.AUTH_URL, token=fakes.AUTH_TOKEN, @@ -55,3 +59,88 @@ def setUp(self): session=mock.Mock(), service_type="network", ) + + +class FakeNetwork(object): + """Fake one or more networks.""" + + @staticmethod + def create_one_network(attrs={}, methods={}): + """Create a fake network. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :return: + A FakeResource object, with id, name, admin_state_up, + router_external, status, subnets, tenant_id + """ + # Set default attributes. + network_attrs = { + 'id': 'network-id-' + uuid.uuid4().hex, + 'name': 'network-name-' + uuid.uuid4().hex, + 'status': 'ACTIVE', + 'tenant_id': 'project-id-' + uuid.uuid4().hex, + 'admin_state_up': True, + 'shared': False, + 'subnets': ['a', 'b'], + 'provider_network_type': 'vlan', + 'router_external': True, + 'is_dirty': True, + } + + # Overwrite default attributes. + network_attrs.update(attrs) + + # Set default methods. + network_methods = { + 'keys': ['id', 'name', 'admin_state_up', 'router_external', + 'status', 'subnets', 'tenant_id'], + } + + # Overwrite default methods. + network_methods.update(methods) + + network = fakes.FakeResource(info=copy.deepcopy(network_attrs), + methods=copy.deepcopy(network_methods), + loaded=True) + return network + + @staticmethod + def create_networks(attrs={}, methods={}, count=2): + """Create multiple fake networks. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :param int count: + The number of networks to fake + :return: + A list of FakeResource objects faking the networks + """ + networks = [] + for i in range(0, count): + networks.append(FakeNetwork.create_one_network(attrs, methods)) + + return networks + + @staticmethod + def get_networks(networks=None, count=2): + """Get an iterable MagicMock object with a list of faked networks. + + If networks list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List networks: + A list of FakeResource objects faking networks + :param int count: + The number of networks to fake + :return: + An iterable Mock object with side_effect set to a list of faked + networks + """ + if networks is None: + networks = FakeNetwork.create_networks(count) + return mock.MagicMock(side_effect=networks) From 481b711faefe21738da20187cef80fc5fa63fce4 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 1 Dec 2015 16:00:28 +0800 Subject: [PATCH 0453/3095] SDK integration: Add a temporary method to create network client using sdk. This patch adds a temporary method to create a network client using sdk. This method will help to migrate network commands from neutronclient to sdk one by one. The command which is being migrated will use this temporary method to create the sdk client, and the rest ones will use the old client. The temporary method will finally be removed and implement the same thing in make_client(). This patch will also add sdk to requirements file. And adds some formatter helper functions, which will be used in class CreateNetwork, ListNetwork and ShowNetwork. This patch is splited from TerryHowe 's original patch. Change-Id: Ie9b35747680afeb66cf6922e2c654fbca7e03569 Implements: blueprint neutron-client Co-Authored-By: TerryHowe Co-Authored-By: Tang Chen --- openstackclient/network/v2/network.py | 24 +++++++++++++++++++++++- requirements.txt | 1 + 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 336b3086e2..cd14592df4 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -20,15 +20,37 @@ from cliff import lister from cliff import show +from openstack import connection + from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.identity import common as identity_common from openstackclient.network import common +def _format_admin_state(item): + return 'UP' if item else 'DOWN' + + +def _format_router_external(item): + return 'External' if item else 'Internal' + + +_formatters = { + 'subnets': utils.format_list, + 'admin_state_up': _format_admin_state, + 'router_external': _format_router_external, +} + + +def _make_client_sdk(instance): + """Return a network proxy""" + conn = connection.Connection(authenticator=instance.session.auth) + return conn.network + + def _prep_network_detail(net): """Prepare network object for output""" - if 'subnets' in net: net['subnets'] = utils.format_list(net['subnets']) if 'admin_state_up' in net: diff --git a/requirements.txt b/requirements.txt index fbe3b8d656..8b1a999d2c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,6 +7,7 @@ six>=1.9.0 Babel>=1.3 cliff>=1.15.0 # Apache-2.0 keystoneauth1>=1.0.0 +openstacksdk os-client-config!=1.6.2,>=1.4.0 oslo.config>=2.7.0 # Apache-2.0 oslo.i18n>=1.5.0 # Apache-2.0 From 566388ab1eddd339b054c2046d41e2b01476f4e2 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Fri, 4 Dec 2015 16:37:40 -0600 Subject: [PATCH 0454/3095] Add source security group support to create rule The 'security group rule create' command was updated to support a source security group. Now either a source IP address block or source security group can be specified when creating a rule. The default remains the same. Change-Id: If57de2871810caddeeaee96482eb34146968e173 Closes-Bug: #1522969 --- .../command-objects/security-group-rule.rst | 8 ++- openstackclient/compute/v2/security_group.py | 12 ++++- .../compute/v2/test_security_group_rule.py | 54 ++++++++++++++----- 3 files changed, 56 insertions(+), 18 deletions(-) diff --git a/doc/source/command-objects/security-group-rule.rst b/doc/source/command-objects/security-group-rule.rst index 8bd0d0615c..ce2e4d346f 100644 --- a/doc/source/command-objects/security-group-rule.rst +++ b/doc/source/command-objects/security-group-rule.rst @@ -14,7 +14,7 @@ Create a new security group rule os security group rule create [--proto ] - [--src-ip ] + [--src-ip | --src-group ] [--dst-port ] @@ -24,7 +24,11 @@ Create a new security group rule .. option:: --src-ip - Source IP (may use CIDR notation; default: 0.0.0.0/0) + Source IP address block (may use CIDR notation; default: 0.0.0.0/0) + +.. option:: --src-group + + Source security group (ID only) .. option:: --dst-port diff --git a/openstackclient/compute/v2/security_group.py b/openstackclient/compute/v2/security_group.py index 6d38195cf5..8844f5cce8 100644 --- a/openstackclient/compute/v2/security_group.py +++ b/openstackclient/compute/v2/security_group.py @@ -111,11 +111,18 @@ def get_parser(self, prog_name): default="tcp", help="IP protocol (icmp, tcp, udp; default: tcp)", ) - parser.add_argument( + source_group = parser.add_mutually_exclusive_group() + source_group.add_argument( "--src-ip", metavar="", default="0.0.0.0/0", - help="Source IP (may use CIDR notation; default: 0.0.0.0/0)", + help="Source IP address block (may use CIDR notation; default: " + "0.0.0.0/0)", + ) + source_group.add_argument( + "--src-group", + metavar="", + help="Source security group (ID only)", ) parser.add_argument( "--dst-port", @@ -145,6 +152,7 @@ def take_action(self, parsed_args): from_port, to_port, parsed_args.src_ip, + parsed_args.src_group, ) info = _xform_security_group_rule(data._info) diff --git a/openstackclient/tests/compute/v2/test_security_group_rule.py b/openstackclient/tests/compute/v2/test_security_group_rule.py index 9516f8ddb6..cf540e021f 100644 --- a/openstackclient/tests/compute/v2/test_security_group_rule.py +++ b/openstackclient/tests/compute/v2/test_security_group_rule.py @@ -18,6 +18,7 @@ from openstackclient.tests.compute.v2 import fakes as compute_fakes from openstackclient.tests import fakes from openstackclient.tests.identity.v2_0 import fakes as identity_fakes +from openstackclient.tests import utils security_group_id = '11' @@ -25,6 +26,7 @@ security_group_description = 'nothing but net' security_group_rule_id = '1' +security_group_rule_cidr = '0.0.0.0/0' SECURITY_GROUP = { 'id': security_group_id, @@ -37,7 +39,7 @@ 'id': security_group_rule_id, 'group': {}, 'ip_protocol': 'tcp', - 'ip_range': '0.0.0.0/0', + 'ip_range': {'cidr': security_group_rule_cidr}, 'parent_group_id': security_group_id, 'from_port': 0, 'to_port': 0, @@ -47,7 +49,7 @@ 'id': security_group_rule_id, 'group': {}, 'ip_protocol': 'icmp', - 'ip_range': '0.0.0.0/0', + 'ip_range': {'cidr': security_group_rule_cidr}, 'parent_group_id': security_group_id, 'from_port': -1, 'to_port': -1, @@ -115,7 +117,8 @@ def test_security_group_rule_create_no_options(self): 'tcp', 0, 0, - '0.0.0.0/0', + security_group_rule_cidr, + None, ) collist = ( @@ -131,7 +134,7 @@ def test_security_group_rule_create_no_options(self): {}, security_group_rule_id, 'tcp', - '', + security_group_rule_cidr, security_group_id, '0:0', ) @@ -166,7 +169,8 @@ def test_security_group_rule_create_ftp(self): 'tcp', 20, 21, - '0.0.0.0/0', + security_group_rule_cidr, + None, ) collist = ( @@ -182,7 +186,7 @@ def test_security_group_rule_create_ftp(self): {}, security_group_rule_id, 'tcp', - '', + security_group_rule_cidr, security_group_id, '20:21', ) @@ -192,6 +196,7 @@ def test_security_group_rule_create_ssh(self): sg_rule = copy.deepcopy(SECURITY_GROUP_RULE) sg_rule['from_port'] = 22 sg_rule['to_port'] = 22 + sg_rule['group'] = {'name': security_group_name} self.sg_rules_mock.create.return_value = FakeSecurityGroupRuleResource( None, sg_rule, @@ -201,10 +206,12 @@ def test_security_group_rule_create_ssh(self): arglist = [ security_group_name, '--dst-port', '22', + '--src-group', security_group_id, ] verifylist = [ ('group', security_group_name), ('dst_port', (22, 22)), + ('src_group', security_group_id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -217,7 +224,8 @@ def test_security_group_rule_create_ssh(self): 'tcp', 22, 22, - '0.0.0.0/0', + security_group_rule_cidr, + security_group_id, ) collist = ( @@ -230,10 +238,10 @@ def test_security_group_rule_create_ssh(self): ) self.assertEqual(collist, columns) datalist = ( - {}, + {'name': security_group_name}, security_group_rule_id, 'tcp', - '', + security_group_rule_cidr, security_group_id, '22:22', ) @@ -267,7 +275,8 @@ def test_security_group_rule_create_udp(self): 'udp', 0, 0, - '0.0.0.0/0', + security_group_rule_cidr, + None, ) collist = ( @@ -283,26 +292,31 @@ def test_security_group_rule_create_udp(self): {}, security_group_rule_id, 'udp', - '', + security_group_rule_cidr, security_group_id, '0:0', ) self.assertEqual(datalist, data) def test_security_group_rule_create_icmp(self): + sg_rule_cidr = '10.0.2.0/24' + sg_rule = copy.deepcopy(SECURITY_GROUP_RULE_ICMP) + sg_rule['ip_range'] = {'cidr': sg_rule_cidr} self.sg_rules_mock.create.return_value = FakeSecurityGroupRuleResource( None, - copy.deepcopy(SECURITY_GROUP_RULE_ICMP), + sg_rule, loaded=True, ) arglist = [ security_group_name, '--proto', 'ICMP', + '--src-ip', sg_rule_cidr, ] verifylist = [ ('group', security_group_name), ('proto', 'ICMP'), + ('src_ip', sg_rule_cidr) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -315,7 +329,8 @@ def test_security_group_rule_create_icmp(self): 'ICMP', -1, -1, - '0.0.0.0/0', + sg_rule_cidr, + None, ) collist = ( @@ -331,8 +346,19 @@ def test_security_group_rule_create_icmp(self): {}, security_group_rule_id, 'icmp', - '', + sg_rule_cidr, security_group_id, '', ) self.assertEqual(datalist, data) + + def test_security_group_rule_create_src_invalid(self): + arglist = [ + security_group_name, + '--proto', 'ICMP', + '--src-ip', security_group_rule_cidr, + '--src-group', security_group_id, + ] + + self.assertRaises(utils.ParserException, + self.check_parser, self.cmd, arglist, []) From a4696dce1e0c15e5f6a8bef4efe8c878437c4c05 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 8 Dec 2015 10:05:36 +0800 Subject: [PATCH 0455/3095] Use FakeImage in server test cases. We have a class FakeImage to fake one or more images. So use it in test_server.py. Change-Id: I276e4ade5aecefbe66b9722c1dfbac10b3bd14b6 Implements: blueprint improve-image-unittest-framework --- .../tests/compute/v2/test_server.py | 40 ++++++++----------- 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 359920f230..2241563026 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -107,11 +107,7 @@ def setUp(self): self.servers_mock.create.return_value = self.new_server - self.image = fakes.FakeResource( - None, - copy.deepcopy(image_fakes.IMAGE), - loaded=True, - ) + self.image = image_fakes.FakeImage.create_one_image() self.cimages_mock.get.return_value = self.image self.flavor = compute_fakes.FakeFlavor.create_one_flavor() @@ -537,13 +533,9 @@ def setUp(self): # This is the return value for utils.find_resource() self.servers_mock.get.return_value = self.server - self.servers_mock.create_image.return_value = image_fakes.image_id - - self.images_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(image_fakes.IMAGE), - loaded=True, - ) + self.image = image_fakes.FakeImage.create_one_image() + self.images_mock.get.return_value = self.image + self.servers_mock.create_image.return_value = self.image.id # Get the command object to test self.cmd = server.CreateServerImage(self.app, None) @@ -569,12 +561,12 @@ def test_server_image_create_no_options(self): collist = ('id', 'name', 'owner', 'protected', 'tags', 'visibility') self.assertEqual(collist, columns) datalist = ( - image_fakes.image_id, - image_fakes.image_name, - image_fakes.image_owner, - image_fakes.image_protected, - image_fakes.image_tags, - image_fakes.image_visibility, + self.image.id, + self.image.name, + self.image.owner, + self.image.protected, + self.image.tags, + self.image.visibility, ) self.assertEqual(datalist, data) @@ -601,12 +593,12 @@ def test_server_image_create_name(self): collist = ('id', 'name', 'owner', 'protected', 'tags', 'visibility') self.assertEqual(collist, columns) datalist = ( - image_fakes.image_id, - image_fakes.image_name, - image_fakes.image_owner, - image_fakes.image_protected, - image_fakes.image_tags, - image_fakes.image_visibility, + self.image.id, + self.image.name, + self.image.owner, + self.image.protected, + self.image.tags, + self.image.visibility, ) self.assertEqual(datalist, data) From ec79d338f7388f779095162d838dd29e33a69829 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 8 Dec 2015 10:15:59 +0800 Subject: [PATCH 0456/3095] Use FakeVolume in server test cases. We have a class FakeVolume to fake one or more volumes. So use it in test_server.py. Change-Id: I735ae7f678a6799e0ae4c7c25c8083d9ebf47b09 Implements: blueprint improve-volume-unittest-framework --- openstackclient/tests/compute/v2/fakes.py | 2 -- .../tests/compute/v2/test_server.py | 19 +++++++------------ 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 91cc2bd274..ecf7f599ba 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -76,8 +76,6 @@ QUOTA_columns = tuple(sorted(QUOTA)) QUOTA_data = tuple(QUOTA[x] for x in sorted(QUOTA)) -block_device_mapping = 'vda=' + volume_fakes.volume_name + ':::0' - service_host = 'host_test' service_binary = 'compute_test' service_status = 'enabled' diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 2241563026..389b605f72 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -13,7 +13,6 @@ # under the License. # -import copy import mock import testtools @@ -22,7 +21,6 @@ from openstackclient.common import utils as common_utils from openstackclient.compute.v2 import server from openstackclient.tests.compute.v2 import fakes as compute_fakes -from openstackclient.tests import fakes from openstackclient.tests.image.v2 import fakes as image_fakes from openstackclient.tests import utils from openstackclient.tests.volume.v2 import fakes as volume_fakes @@ -113,12 +111,9 @@ def setUp(self): self.flavor = compute_fakes.FakeFlavor.create_one_flavor() self.flavors_mock.get.return_value = self.flavor - self.volume = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.VOLUME), - loaded=True, - ) + self.volume = volume_fakes.FakeVolume.create_one_volume() self.volumes_mock.get.return_value = self.volume + self.block_device_mapping = 'vda=' + self.volume.name + ':::0' # Get the command object to test self.cmd = server.CreateServer(self.app, None) @@ -357,13 +352,13 @@ def test_server_create_with_block_device_mapping(self): arglist = [ '--image', 'image1', '--flavor', self.flavor.id, - '--block-device-mapping', compute_fakes.block_device_mapping, + '--block-device-mapping', self.block_device_mapping, self.new_server.name, ] verifylist = [ ('image', 'image1'), ('flavor', self.flavor.id), - ('block_device_mapping', [compute_fakes.block_device_mapping]), + ('block_device_mapping', [self.block_device_mapping]), ('config_drive', False), ('server_name', self.new_server.name), ] @@ -373,9 +368,9 @@ def test_server_create_with_block_device_mapping(self): columns, data = self.cmd.take_action(parsed_args) real_volume_mapping = ( - (compute_fakes.block_device_mapping.split('=', 1)[1]).replace( - volume_fakes.volume_name, - volume_fakes.volume_id)) + (self.block_device_mapping.split('=', 1)[1]).replace( + self.volume.name, + self.volume.id)) # Set expected values kwargs = dict( From 85d6aeea32e1d208b4af1247012fe63e68922a7d Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 8 Dec 2015 13:35:51 +0800 Subject: [PATCH 0457/3095] Trivial: Coding style fix in test_flavor.py Change-Id: I3dcb3c4fbd9ff9c351426ae2ad9da009208a6485 --- openstackclient/tests/compute/v2/test_flavor.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openstackclient/tests/compute/v2/test_flavor.py b/openstackclient/tests/compute/v2/test_flavor.py index 60356efa8b..3a59020c49 100644 --- a/openstackclient/tests/compute/v2/test_flavor.py +++ b/openstackclient/tests/compute/v2/test_flavor.py @@ -173,10 +173,10 @@ def test_flavor_list_all_flavors(self): def test_flavor_list_private_flavors(self): arglist = [ '--private', - ] + ] verifylist = [ ('public', False), - ] + ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -200,10 +200,10 @@ def test_flavor_list_private_flavors(self): def test_flavor_list_public_flavors(self): arglist = [ '--public', - ] + ] verifylist = [ ('public', True), - ] + ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) From 56150de2243c8b9d81aa2dc87ffc764b893a75e1 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 1 Dec 2015 15:40:52 +0800 Subject: [PATCH 0458/3095] Migrate "network list" command to new version using SDK. This patch makes "network list" command use sdk. Since we have to keep the other commands runnable with the old network client, we use a temporary method to create sdk network client. And as a result, the tests need to patch a method to fake the temporary method, which will be removed at last. Change-Id: I0882501cd7bb2c17917e10a6da4298f1452c9765 Implements: blueprint neutron-client Co-Authored-By: Terry Howe Co-Authored-By: Tang Chen --- openstackclient/network/v2/network.py | 46 ++++++---- .../tests/network/v2/test_network.py | 92 +++++++++---------- 2 files changed, 74 insertions(+), 64 deletions(-) diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index cd14592df4..48b5b5a017 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -188,21 +188,21 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) + self.app.client_manager.network = \ + _make_client_sdk(self.app.client_manager) client = self.app.client_manager.network - data = client.api.network_list(external=parsed_args.external) - if parsed_args.long: columns = ( - 'ID', - 'Name', - 'Status', - 'project_id', - 'state', - 'Shared', - 'Subnets', - 'provider:network_type', - 'router_type', + 'id', + 'name', + 'status', + 'tenant_id', + 'admin_state_up', + 'shared', + 'subnets', + 'provider_network_type', + 'router_external', ) column_headers = ( 'ID', @@ -216,16 +216,26 @@ def take_action(self, parsed_args): 'Router Type', ) else: - columns = ('ID', 'Name', 'Subnets') - column_headers = columns - - for d in data: - d = _prep_network_detail(d) + columns = ( + 'id', + 'name', + 'subnets' + ) + column_headers = ( + 'ID', + 'Name', + 'Subnets', + ) + if parsed_args.external: + args = {'router:external': True} + else: + args = {} + data = client.networks(**args) return (column_headers, - (utils.get_dict_properties( + (utils.get_item_properties( s, columns, - formatters={'subnets': utils.format_list}, + formatters=_formatters, ) for s in data)) diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index fab4a4d6b7..ccf4fcd030 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -15,6 +15,7 @@ import mock from openstackclient.common import exceptions +from openstackclient.common import utils from openstackclient.network.v2 import network from openstackclient.tests import fakes from openstackclient.tests.identity.v2_0 import fakes as identity_fakes_v2 @@ -288,17 +289,18 @@ def test_delete(self): self.assertEqual(None, result) -@mock.patch( - 'openstackclient.api.network_v2.APIv2.network_list' -) +@mock.patch('openstackclient.network.v2.network._make_client_sdk') class TestListNetwork(TestNetwork): - columns = [ + # The networks going to be listed up. + _network = network_fakes.FakeNetwork.create_networks(count=3) + + columns = ( 'ID', 'Name', - 'Subnets' - ] - columns_long = [ + 'Subnets', + ) + columns_long = ( 'ID', 'Name', 'Status', @@ -308,18 +310,29 @@ class TestListNetwork(TestNetwork): 'Subnets', 'Network Type', 'Router Type', - ] - - data = [ - (FAKE_ID, FAKE_NAME, 'a, b'), - (FAKE_ID, FAKE_NAME, 'a, b'), - ] - data_long = [ - (FAKE_ID, FAKE_NAME, 'ACTIVE', FAKE_PROJECT, - 'UP', '', 'a, b', '', 'External'), - (FAKE_ID, FAKE_NAME, 'ACTIVE', FAKE_PROJECT, - 'UP', '', 'a, b', '', 'External'), - ] + ) + + data = [] + for net in _network: + data.append(( + net.id, + net.name, + utils.format_list(net.subnets), + )) + + data_long = [] + for net in _network: + data_long.append(( + net.id, + net.name, + net.status, + net.tenant_id, + network._format_admin_state(net.admin_state_up), + net.shared, + utils.format_list(net.subnets), + net.provider_network_type, + network._format_router_external(net.router_external), + )) def setUp(self): super(TestListNetwork, self).setUp() @@ -327,13 +340,10 @@ def setUp(self): # Get the command object to test self.cmd = network.ListNetwork(self.app, self.namespace) - self.NETWORK_LIST = [ - copy.deepcopy(RECORD), - copy.deepcopy(RECORD), - ] + self.network.networks = mock.Mock(return_value=self._network) - def test_network_list_no_options(self, network_list): - network_list.return_value = self.NETWORK_LIST + def test_network_list_no_options(self, _make_client_sdk): + _make_client_sdk.return_value = self.app.client_manager.network arglist = [] verifylist = [ @@ -345,16 +355,12 @@ def test_network_list_no_options(self, network_list): # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) - # Set expected values - network_list.assert_called_with( - external=False, - ) - - self.assertEqual(tuple(self.columns), columns) + self.network.networks.assert_called_with() + self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) - def test_list_external(self, network_list): - network_list.return_value = self.NETWORK_LIST + def test_list_external(self, _make_client_sdk): + _make_client_sdk.return_value = self.app.client_manager.network arglist = [ '--external', @@ -368,16 +374,14 @@ def test_list_external(self, network_list): # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) - # Set expected values - network_list.assert_called_with( - external=True, + self.network.networks.assert_called_with( + **{'router:external': True} ) - - self.assertEqual(tuple(self.columns), columns) + self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) - def test_network_list_long(self, network_list): - network_list.return_value = self.NETWORK_LIST + def test_network_list_long(self, _make_client_sdk): + _make_client_sdk.return_value = self.app.client_manager.network arglist = [ '--long', @@ -391,12 +395,8 @@ def test_network_list_long(self, network_list): # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) - # Set expected values - network_list.assert_called_with( - external=False, - ) - - self.assertEqual(columns, tuple(self.columns_long)) + self.network.networks.assert_called_with() + self.assertEqual(self.columns_long, columns) self.assertEqual(self.data_long, list(data)) From 23486176063fb97c811af92c8af63ef833508d40 Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Tue, 8 Dec 2015 09:23:47 -0700 Subject: [PATCH 0459/3095] The format_exc method does not take an exception For py35, this call blows up. Seems to be ignored for py27, but even in py27, it doesn't take an exception. https://docs.python.org/2.7/library/traceback.html https://docs.python.org/3/library/traceback.html Change-Id: I2602426b966045b15b96e5e41d0df6524ed05119 --- openstackclient/shell.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 5b4939a217..c84e2b1d40 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -116,7 +116,7 @@ def run(self, argv): if not logging.getLogger('').handlers: logging.basicConfig() if self.dump_stack_trace: - self.log.error(traceback.format_exc(e)) + self.log.error(traceback.format_exc()) else: self.log.error('Exception raised: ' + str(e)) From b98aee57c10917d5f57f2ed3cfae3ce67e33496c Mon Sep 17 00:00:00 2001 From: Min Min Ren Date: Wed, 9 Dec 2015 05:42:18 +0800 Subject: [PATCH 0460/3095] Fix "sevice show" cannot catch NoUniqueMatch Exception Fix a bug for "service show" subcommand cannot cache NoUniqueMatch Exception Change-Id: I393c5417de0fef424618b08119ddbc8fea27e114 Closes-Bug: #1524305 --- openstackclient/identity/common.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openstackclient/identity/common.py b/openstackclient/identity/common.py index d0edb0bd5a..2afa41fb35 100644 --- a/openstackclient/identity/common.py +++ b/openstackclient/identity/common.py @@ -41,6 +41,11 @@ def find_service(identity_client, name_type_or_id): msg = ("No service with a type, name or ID of '%s' exists." % name_type_or_id) raise exceptions.CommandError(msg) + except identity_exc.NoUniqueMatch: + msg = ("Multiple service matches found for '%s', " + "use an ID to be more specific." + % name_type_or_id) + raise exceptions.CommandError(msg) def _get_domain_id_if_requested(identity_client, domain_name_or_id): From 0b4fb0bb662d71a85aba251e13d403cf692d8d54 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Fri, 4 Dec 2015 19:48:22 +0800 Subject: [PATCH 0461/3095] Migrate "network create" command to use SDK. This patch makes "network create" command use sdk. Since we have to keep the other commands runnable with the old network client, we use a temporary method to create sdk network client. And as a result, the tests need to patch a method to fake the temporary method, which will be removed at last. Change-Id: I06559c675be1188747257f72f18d6b4d420d0285 Implements: blueprint neutron-client Co-Authored-By: Terry Howe Co-Authored-By: Tang Chen --- openstackclient/network/v2/network.py | 15 +- .../tests/network/v2/test_network.py | 158 ++++++++++++------ 2 files changed, 114 insertions(+), 59 deletions(-) diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 48b5b5a017..3b7ae7375e 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -114,15 +114,14 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) + self.app.client_manager.network = \ + _make_client_sdk(self.app.client_manager) client = self.app.client_manager.network body = self.get_body(parsed_args) - create_method = getattr(client, "create_network") - data = create_method(body)['network'] - if data: - data = _prep_network_detail(data) - else: - data = {'': ''} - return zip(*sorted(six.iteritems(data))) + obj = client.create_network(**body) + columns = sorted(obj.keys()) + data = utils.get_item_properties(obj, columns, formatters=_formatters) + return (tuple(columns), data) def get_body(self, parsed_args): body = {'name': str(parsed_args.name), @@ -137,7 +136,7 @@ def get_body(self, parsed_args): parsed_args.project_domain, ).id body['tenant_id'] = project_id - return {'network': body} + return body class DeleteNetwork(command.Command): diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index ccf4fcd030..8648a11fb6 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -71,13 +71,38 @@ def setUp(self): self.api = self.app.client_manager.network.api +@mock.patch('openstackclient.network.v2.network._make_client_sdk') class TestCreateNetworkIdentityV3(TestNetwork): + # The new network created. + _network = network_fakes.FakeNetwork.create_one_network( + attrs={'tenant_id': identity_fakes_v3.project_id} + ) + + columns = ( + 'admin_state_up', + 'id', + 'name', + 'router_external', + 'status', + 'subnets', + 'tenant_id', + ) + + data = ( + network._format_admin_state(_network.admin_state_up), + _network.id, + _network.name, + network._format_router_external(_network.router_external), + _network.status, + utils.format_list(_network.subnets), + _network.tenant_id, + ) + def setUp(self): super(TestCreateNetworkIdentityV3, self).setUp() - self.new_network = mock.Mock(return_value=copy.deepcopy(RESPONSE)) - self.network.create_network = self.new_network + self.network.create_network = mock.Mock(return_value=self._network) # Get the command object to test self.cmd = network.CreateNetwork(self.app, self.namespace) @@ -106,89 +131,117 @@ def setUp(self): loaded=True, ) - def test_create_no_options(self): + def test_create_no_options(self, _make_client_sdk): + _make_client_sdk.return_value = self.app.client_manager.network + arglist = [ - FAKE_NAME, + self._network.name, ] verifylist = [ - ('name', FAKE_NAME), + ('name', self._network.name), ('admin_state', True), ('shared', None), ('project', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = list(self.cmd.take_action(parsed_args)) + columns, data = self.cmd.take_action(parsed_args) - self.network.create_network.assert_called_with({ - RESOURCE: { - 'admin_state_up': True, - 'name': FAKE_NAME, - } + self.network.create_network.assert_called_with(**{ + 'admin_state_up': True, + 'name': self._network.name, }) - self.assertEqual(FILTERED, result) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_all_options(self, _make_client_sdk): + _make_client_sdk.return_value = self.app.client_manager.network - def test_create_all_options(self): arglist = [ "--disable", "--share", "--project", identity_fakes_v3.project_name, "--project-domain", identity_fakes_v3.domain_name, - FAKE_NAME, + self._network.name, ] verifylist = [ ('admin_state', False), ('shared', True), ('project', identity_fakes_v3.project_name), ('project_domain', identity_fakes_v3.domain_name), - ('name', FAKE_NAME), + ('name', self._network.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = list(self.cmd.take_action(parsed_args)) + columns, data = (self.cmd.take_action(parsed_args)) - self.network.create_network.assert_called_with({ - RESOURCE: { - 'admin_state_up': False, - 'name': FAKE_NAME, - 'shared': True, - 'tenant_id': identity_fakes_v3.project_id, - } + self.network.create_network.assert_called_with(**{ + 'admin_state_up': False, + 'name': self._network.name, + 'shared': True, + 'tenant_id': identity_fakes_v3.project_id, }) - self.assertEqual(FILTERED, result) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_other_options(self, _make_client_sdk): + _make_client_sdk.return_value = self.app.client_manager.network - def test_create_other_options(self): arglist = [ "--enable", "--no-share", - FAKE_NAME, + self._network.name, ] verifylist = [ ('admin_state', True), ('shared', False), - ('name', FAKE_NAME), + ('name', self._network.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = list(self.cmd.take_action(parsed_args)) + columns, data = self.cmd.take_action(parsed_args) - self.network.create_network.assert_called_with({ - RESOURCE: { - 'admin_state_up': True, - 'name': FAKE_NAME, - 'shared': False, - } + self.network.create_network.assert_called_with(**{ + 'admin_state_up': True, + 'name': self._network.name, + 'shared': False, }) - self.assertEqual(FILTERED, result) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) +@mock.patch('openstackclient.network.v2.network._make_client_sdk') class TestCreateNetworkIdentityV2(TestNetwork): + # The new network created. + _network = network_fakes.FakeNetwork.create_one_network( + attrs={'tenant_id': identity_fakes_v2.project_id} + ) + + columns = ( + 'admin_state_up', + 'id', + 'name', + 'router_external', + 'status', + 'subnets', + 'tenant_id', + ) + + data = ( + network._format_admin_state(_network.admin_state_up), + _network.id, + _network.name, + network._format_router_external(_network.router_external), + _network.status, + utils.format_list(_network.subnets), + _network.tenant_id, + ) + def setUp(self): super(TestCreateNetworkIdentityV2, self).setUp() - self.new_network = mock.Mock(return_value=copy.deepcopy(RESPONSE)) - self.network.create_network = self.new_network + self.network.create_network = mock.Mock(return_value=self._network) # Get the command object to test self.cmd = network.CreateNetwork(self.app, self.namespace) @@ -211,42 +264,45 @@ def setUp(self): # There is no DomainManager Mock in fake identity v2. - def test_create_with_project_identityv2(self): + def test_create_with_project_identityv2(self, _make_client_sdk): + _make_client_sdk.return_value = self.app.client_manager.network + arglist = [ "--project", identity_fakes_v2.project_name, - FAKE_NAME, + self._network.name, ] verifylist = [ ('admin_state', True), ('shared', None), - ('name', FAKE_NAME), + ('name', self._network.name), ('project', identity_fakes_v2.project_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = list(self.cmd.take_action(parsed_args)) + columns, data = self.cmd.take_action(parsed_args) - self.network.create_network.assert_called_with({ - RESOURCE: { - 'admin_state_up': True, - 'name': FAKE_NAME, - 'tenant_id': identity_fakes_v2.project_id, - } + self.network.create_network.assert_called_with(**{ + 'admin_state_up': True, + 'name': self._network.name, + 'tenant_id': identity_fakes_v2.project_id, }) - self.assertEqual(FILTERED, result) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_with_domain_identityv2(self, _make_client_sdk): + _make_client_sdk.return_value = self.app.client_manager.network - def test_create_with_domain_identityv2(self): arglist = [ "--project", identity_fakes_v3.project_name, "--project-domain", identity_fakes_v3.domain_name, - FAKE_NAME, + self._network.name, ] verifylist = [ ('admin_state', True), ('shared', None), ('project', identity_fakes_v3.project_name), ('project_domain', identity_fakes_v3.domain_name), - ('name', FAKE_NAME), + ('name', self._network.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) From 511e8622944bc64822af07b1bd11681b0c5d45b3 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 5 Dec 2015 10:31:54 +0800 Subject: [PATCH 0462/3095] Migrate "network delete" command to use SDK. This patch makes "network delete" command use sdk. Since we have to keep the other commands runnable with the old network client, we use a temporary method to create sdk network client. And as a result, the tests need to patch a method to fake the temporary method, which will be removed at last. Change-Id: I1f2c404e4b0ff6727e4c535ce543aa406f2290ce Implements: blueprint neutron-client Co-Authored-By: Terry Howe Co-Authored-By: Tang Chen --- openstackclient/network/v2/network.py | 7 +++--- .../tests/network/v2/test_network.py | 22 ++++++++++--------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 3b7ae7375e..9139757bde 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -156,11 +156,12 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) + self.app.client_manager.network = \ + _make_client_sdk(self.app.client_manager) client = self.app.client_manager.network - delete_method = getattr(client, "delete_network") for network in parsed_args.networks: - _id = common.find(client, 'network', 'networks', network) - delete_method(_id) + obj = client.find_network(network) + client.delete_network(obj) return diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index 8648a11fb6..41be5933e0 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -314,34 +314,36 @@ def test_create_with_domain_identityv2(self, _make_client_sdk): ) +@mock.patch('openstackclient.network.v2.network._make_client_sdk') class TestDeleteNetwork(TestNetwork): + # The network to delete. + _network = network_fakes.FakeNetwork.create_one_network() + def setUp(self): super(TestDeleteNetwork, self).setUp() - self.network.delete_network = mock.Mock( - return_value=None - ) + self.network.delete_network = mock.Mock(return_value=None) - self.network.list_networks = mock.Mock( - return_value={RESOURCES: [copy.deepcopy(RECORD)]} - ) + self.network.find_network = mock.Mock(return_value=self._network) # Get the command object to test self.cmd = network.DeleteNetwork(self.app, self.namespace) - def test_delete(self): + def test_delete(self, _make_client_sdk): + _make_client_sdk.return_value = self.app.client_manager.network + arglist = [ - FAKE_NAME, + self._network.name, ] verifylist = [ - ('networks', [FAKE_NAME]), + ('networks', [self._network.name]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.network.delete_network.assert_called_with(FAKE_ID) + self.network.delete_network.assert_called_with(self._network) self.assertEqual(None, result) From 45c644d428581592f9a17585e019bd6d565133e3 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 5 Dec 2015 11:16:44 +0800 Subject: [PATCH 0463/3095] Migrate "network set" command to use SDK. This patch makes "network set" command use sdk. Since we have to keep the other commands runnable with the old network client, we use a temporary method to create sdk network client. And as a result, the tests need to patch a method to fake the temporary method, which will be removed at last. Change-Id: I794ac4b82d9200747298f1a9ee44611140e9b6d0 Implements: blueprint neutron-client Co-Authored-By: Terry Howe Co-Authored-By: Tang Chen --- openstackclient/network/v2/network.py | 21 ++++---- .../tests/network/v2/test_network.py | 50 +++++++++++-------- 2 files changed, 40 insertions(+), 31 deletions(-) diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 9139757bde..184c86299e 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -25,7 +25,6 @@ from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.identity import common as identity_common -from openstackclient.network import common def _format_admin_state(item): @@ -288,21 +287,23 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) + self.app.client_manager.network = \ + _make_client_sdk(self.app.client_manager) client = self.app.client_manager.network - _id = common.find(client, 'network', 'networks', - parsed_args.identifier) - body = {} + obj = client.find_network(parsed_args.identifier, ignore_missing=False) + if parsed_args.name is not None: - body['name'] = str(parsed_args.name) + obj.name = str(parsed_args.name) if parsed_args.admin_state is not None: - body['admin_state_up'] = parsed_args.admin_state + obj.admin_state_up = parsed_args.admin_state if parsed_args.shared is not None: - body['shared'] = parsed_args.shared - if body == {}: + obj.shared = parsed_args.shared + + if not obj.is_dirty: msg = "Nothing specified to be set" raise exceptions.CommandError(msg) - update_method = getattr(client, "update_network") - update_method(_id, {'network': body}) + + client.update_network(obj) return diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index 41be5933e0..6fd8bfcdcd 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -458,31 +458,35 @@ def test_network_list_long(self, _make_client_sdk): self.assertEqual(self.data_long, list(data)) +@mock.patch('openstackclient.network.v2.network._make_client_sdk') class TestSetNetwork(TestNetwork): + # The network to set. + _network = network_fakes.FakeNetwork.create_one_network() + def setUp(self): super(TestSetNetwork, self).setUp() - self.network.update_network = mock.Mock( - return_value=None - ) + self.network.update_network = mock.Mock(return_value=None) - self.network.list_networks = mock.Mock( - return_value={RESOURCES: [copy.deepcopy(RECORD)]} - ) + self.network.find_network = mock.Mock(return_value=self._network) # Get the command object to test self.cmd = network.SetNetwork(self.app, self.namespace) - def test_set_this(self): + def test_set_this(self, _make_client_sdk): + _make_client_sdk.return_value = self.app.client_manager.network + + self._network.is_dirty = True + arglist = [ - FAKE_NAME, + self._network.name, '--enable', '--name', 'noob', '--share', ] verifylist = [ - ('identifier', FAKE_NAME), + ('identifier', self._network.name), ('admin_state', True), ('name', 'noob'), ('shared', True), @@ -491,19 +495,21 @@ def test_set_this(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - exp = {'admin_state_up': True, 'name': 'noob', 'shared': True} - exp_record = {RESOURCE: exp} - self.network.update_network.assert_called_with(FAKE_ID, exp_record) + self.network.update_network.assert_called_with(self._network) self.assertEqual(None, result) - def test_set_that(self): + def test_set_that(self, _make_client_sdk): + _make_client_sdk.return_value = self.app.client_manager.network + + self._network.is_dirty = True + arglist = [ - FAKE_NAME, + self._network.name, '--disable', '--no-share', ] verifylist = [ - ('identifier', FAKE_NAME), + ('identifier', self._network.name), ('admin_state', False), ('shared', False), ] @@ -511,14 +517,16 @@ def test_set_that(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - exp = {'admin_state_up': False, 'shared': False} - exp_record = {RESOURCE: exp} - self.network.update_network.assert_called_with(FAKE_ID, exp_record) + self.network.update_network.assert_called_with(self._network) self.assertEqual(None, result) - def test_set_nothing(self): - arglist = [FAKE_NAME, ] - verifylist = [('identifier', FAKE_NAME), ] + def test_set_nothing(self, _make_client_sdk): + _make_client_sdk.return_value = self.app.client_manager.network + + self._network.is_dirty = False + + arglist = [self._network.name, ] + verifylist = [('identifier', self._network.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.assertRaises(exceptions.CommandError, self.cmd.take_action, From 2a2cb4f75d4b83ac821df0d3da0046d24ca5eee0 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 5 Dec 2015 12:34:54 +0800 Subject: [PATCH 0464/3095] Migrate "network show" command to use SDK. This patch makes "network show" command use sdk. Since we have to keep the other commands runnable with the old network client, we use a temporary method to create sdk network client. And as a result, the tests need to patch a method to fake the temporary method, which will be removed at last. There are two same test cases in the unit tests. This patch will remove one. And since the output has changed, we also need to fix function test cases. Change-Id: I4c06b4efad2db430767bbaa882b0876df3ab483a Implements: blueprint neutron-client Co-Authored-By: Terry Howe Co-Authored-By: Tang Chen --- functional/tests/network/v2/test_network.py | 4 +- openstackclient/network/v2/network.py | 29 ++------ .../tests/network/v2/test_network.py | 69 +++++++++++++------ 3 files changed, 57 insertions(+), 45 deletions(-) diff --git a/functional/tests/network/v2/test_network.py b/functional/tests/network/v2/test_network.py index 0b2f35ea47..5cc461b96c 100644 --- a/functional/tests/network/v2/test_network.py +++ b/functional/tests/network/v2/test_network.py @@ -40,9 +40,9 @@ def test_network_list(self): def test_network_set(self): raw_output = self.openstack('network set --disable ' + self.NAME) - opts = self.get_show_opts(['name', 'state']) + opts = self.get_show_opts(['name', 'admin_state_up']) raw_output = self.openstack('network show ' + self.NAME + opts) - self.assertEqual(self.NAME + "\nDOWN\n", raw_output) + self.assertEqual("DOWN\n" + self.NAME + "\n", raw_output) def test_network_show(self): opts = self.get_show_opts(self.FIELDS) diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 184c86299e..4c94dc650e 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -14,7 +14,6 @@ """Network action implementations""" import logging -import six from cliff import command from cliff import lister @@ -48,22 +47,6 @@ def _make_client_sdk(instance): return conn.network -def _prep_network_detail(net): - """Prepare network object for output""" - if 'subnets' in net: - net['subnets'] = utils.format_list(net['subnets']) - if 'admin_state_up' in net: - net['state'] = 'UP' if net['admin_state_up'] else 'DOWN' - net.pop('admin_state_up') - if 'router:external' in net: - net['router_type'] = 'External' if net['router:external'] \ - else 'Internal' - net.pop('router:external') - if 'tenant_id' in net: - net['project_id'] = net.pop('tenant_id') - return net - - class CreateNetwork(show.ShowOne): """Create new network""" @@ -323,10 +306,10 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) + self.app.client_manager.network = \ + _make_client_sdk(self.app.client_manager) client = self.app.client_manager.network - net = client.api.find_attr( - 'networks', - parsed_args.identifier, - ) - data = _prep_network_detail(net) - return zip(*sorted(six.iteritems(data))) + obj = client.find_network(parsed_args.identifier, ignore_missing=False) + columns = sorted(obj.keys()) + data = utils.get_item_properties(obj, columns, formatters=_formatters) + return (tuple(columns), data) diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index 6fd8bfcdcd..67f446505b 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -21,6 +21,7 @@ from openstackclient.tests.identity.v2_0 import fakes as identity_fakes_v2 from openstackclient.tests.identity.v3 import fakes as identity_fakes_v3 from openstackclient.tests.network.v2 import fakes as network_fakes +from openstackclient.tests import utils as tests_utils RESOURCE = 'network' RESOURCES = 'networks' @@ -533,39 +534,67 @@ def test_set_nothing(self, _make_client_sdk): parsed_args) -@mock.patch( - 'openstackclient.api.network_v2.APIv2.find_attr' -) +@mock.patch('openstackclient.network.v2.network._make_client_sdk') class TestShowNetwork(TestNetwork): + # The network to set. + _network = network_fakes.FakeNetwork.create_one_network() + + columns = ( + 'admin_state_up', + 'id', + 'name', + 'router_external', + 'status', + 'subnets', + 'tenant_id', + ) + + data = ( + network._format_admin_state(_network.admin_state_up), + _network.id, + _network.name, + network._format_router_external(_network.router_external), + _network.status, + utils.format_list(_network.subnets), + _network.tenant_id, + ) + def setUp(self): super(TestShowNetwork, self).setUp() + self.network.find_network = mock.Mock(return_value=self._network) + # Get the command object to test self.cmd = network.ShowNetwork(self.app, self.namespace) - def test_show_no_options(self, find_attr): + def test_show_no_options(self, _make_client_sdk): + _make_client_sdk.return_value = self.app.client_manager.network + + arglist = [] + verifylist = [] + + try: + # Missing required args should bail here + self.check_parser(self.cmd, arglist, verifylist) + except tests_utils.ParserException: + pass + + def test_show_all_options(self, _make_client_sdk): + _make_client_sdk.return_value = self.app.client_manager.network + arglist = [ - FAKE_NAME, + self._network.name, ] verifylist = [ - ('identifier', FAKE_NAME), + ('identifier', self._network.name), ] - find_attr.return_value = copy.deepcopy(RECORD) parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = list(self.cmd.take_action(parsed_args)) - - find_attr.assert_called_with('networks', FAKE_NAME) - self.assertEqual(FILTERED, result) - - def test_show_all_options(self, find_attr): - arglist = [FAKE_NAME] - verifylist = [('identifier', FAKE_NAME)] - find_attr.return_value = copy.deepcopy(RECORD) + columns, data = self.cmd.take_action(parsed_args) - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = list(self.cmd.take_action(parsed_args)) + self.network.find_network.assert_called_with(self._network.name, + ignore_missing=False) - find_attr.assert_called_with('networks', FAKE_NAME) - self.assertEqual(FILTERED, result) + self.assertEqual(tuple(self.columns), columns) + self.assertEqual(list(self.data), list(data)) From 4be716eb27752d715ea1140b76e4a03907edd87f Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Fri, 4 Dec 2015 16:30:10 +0800 Subject: [PATCH 0465/3095] Migrate network client to SDK. The previous patches have migrate all network commands to the new version using sdk. This patch will remove the temporary method, and implement a new make_client() to create sdk network client. And also, find() in openstackclient/network/common.py must support sdk. The logic of this function will become much easier than before, so this patch also removes two useless test cases of find(). This patch will also remove the patched methods in tests. Change-Id: Ic2f7bca073beb9757172d16f95d9b82c48cbbc12 Implements: blueprint neutron-client Co-Authored-By: Terry Howe Co-Authored-By: Tang Chen --- openstackclient/network/client.py | 62 +++---------------- openstackclient/network/common.py | 24 +------ openstackclient/network/v2/network.py | 18 ------ .../tests/compute/v2/test_server.py | 12 ++++ openstackclient/tests/network/test_common.py | 33 +++------- .../tests/network/v2/test_network.py | 62 +++++-------------- 6 files changed, 48 insertions(+), 163 deletions(-) diff --git a/openstackclient/network/client.py b/openstackclient/network/client.py index 69ed11feb8..7714c52504 100644 --- a/openstackclient/network/client.py +++ b/openstackclient/network/client.py @@ -13,6 +13,8 @@ import logging +from openstack import connection + from openstackclient.common import utils @@ -22,62 +24,18 @@ API_VERSION_OPTION = 'os_network_api_version' API_NAME = "network" API_VERSIONS = { - "2.0": "neutronclient.v2_0.client.Client", - "2": "neutronclient.v2_0.client.Client", -} -# Translate our API version to auth plugin version prefix -API_VERSION_MAP = { - '2.0': 'v2.0', - '2': 'v2.0', -} - -NETWORK_API_TYPE = 'network' -NETWORK_API_VERSIONS = { - '2.0': 'openstackclient.api.network_v2.APIv2', - '2': 'openstackclient.api.network_v2.APIv2', + "2.0": "openstack.connection.Connection", + "2": "openstack.connection.Connection", } def make_client(instance): - """Returns an network service client""" - network_client = utils.get_client_class( - API_NAME, - instance._api_version[API_NAME], - API_VERSIONS) - LOG.debug('Instantiating network client: %s', network_client) - - endpoint = instance.get_endpoint_for_service_type( - API_NAME, - region_name=instance._region_name, - interface=instance._interface, - ) - - # Remember endpoint_type only if it is set - kwargs = utils.build_kwargs_dict('endpoint_type', instance._interface) - - client = network_client( - session=instance.session, - region_name=instance._region_name, - **kwargs - ) - - network_api = utils.get_client_class( - API_NAME, - instance._api_version[API_NAME], - NETWORK_API_VERSIONS) - LOG.debug('Instantiating network api: %s', network_client) - - # v2 is hard-coded until discovery is completed, neutron only has one atm - client.api = network_api( - session=instance.session, - service_type=NETWORK_API_TYPE, - endpoint='/'.join([ - endpoint, - API_VERSION_MAP[instance._api_version[API_NAME]], - ]) - ) - - return client + """Returns a network proxy""" + conn = connection.Connection(authenticator=instance.session.auth) + LOG.debug('Connection: %s', conn) + LOG.debug('Network client initialized using OpenStack SDK: %s', + conn.network) + return conn.network def build_option_parser(parser): diff --git a/openstackclient/network/common.py b/openstackclient/network/common.py index 31faef25f3..7b3f8a62e9 100644 --- a/openstackclient/network/common.py +++ b/openstackclient/network/common.py @@ -11,8 +11,6 @@ # under the License. # -from openstackclient.common import exceptions - def find(client, resource, resources, name_or_id, name_attr='name'): """Find a network resource @@ -26,22 +24,6 @@ def find(client, resource, resources, name_or_id, name_attr='name'): For example: n = find(netclient, 'network', 'networks', 'matrix') """ - list_method = getattr(client, "list_%s" % resources) - - # Search by name - kwargs = {name_attr: name_or_id, 'fields': 'id'} - data = list_method(**kwargs) - info = data[resources] - if len(info) == 1: - return info[0]['id'] - if len(info) > 1: - msg = "More than one %s exists with the name '%s'." - raise exceptions.CommandError(msg % (resource, name_or_id)) - - # Search by id - data = list_method(id=name_or_id, fields='id') - info = data[resources] - if len(info) == 1: - return info[0]['id'] - msg = "No %s with a name or ID of '%s' exists." % (resource, name_or_id) - raise exceptions.CommandError(msg) + list_method = getattr(client, "find_%s" % resource) + data = list_method(name_or_id, ignore_missing=False) + return data.id diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 4c94dc650e..64b98f5d85 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -19,8 +19,6 @@ from cliff import lister from cliff import show -from openstack import connection - from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.identity import common as identity_common @@ -41,12 +39,6 @@ def _format_router_external(item): } -def _make_client_sdk(instance): - """Return a network proxy""" - conn = connection.Connection(authenticator=instance.session.auth) - return conn.network - - class CreateNetwork(show.ShowOne): """Create new network""" @@ -96,8 +88,6 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) - self.app.client_manager.network = \ - _make_client_sdk(self.app.client_manager) client = self.app.client_manager.network body = self.get_body(parsed_args) obj = client.create_network(**body) @@ -138,8 +128,6 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) - self.app.client_manager.network = \ - _make_client_sdk(self.app.client_manager) client = self.app.client_manager.network for network in parsed_args.networks: obj = client.find_network(network) @@ -170,8 +158,6 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) - self.app.client_manager.network = \ - _make_client_sdk(self.app.client_manager) client = self.app.client_manager.network if parsed_args.long: @@ -270,8 +256,6 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) - self.app.client_manager.network = \ - _make_client_sdk(self.app.client_manager) client = self.app.client_manager.network obj = client.find_network(parsed_args.identifier, ignore_missing=False) @@ -306,8 +290,6 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) - self.app.client_manager.network = \ - _make_client_sdk(self.app.client_manager) client = self.app.client_manager.network obj = client.find_network(parsed_args.identifier, ignore_missing=False) columns = sorted(obj.keys()) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index ce2dcdf7e8..01c945eecc 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -231,6 +231,18 @@ def test_server_create_with_network(self): list_networks.return_value = {'networks': [{'id': 'net1_uuid'}]} list_ports.return_value = {'ports': [{'id': 'port1_uuid'}]} + # Mock sdk APIs. + _network = mock.Mock() + _network.id = 'net1_uuid' + _port = mock.Mock() + _port.id = 'port1_uuid' + find_network = mock.Mock() + find_port = mock.Mock() + find_network.return_value = _network + find_port.return_value = _port + self.app.client_manager.network.find_network = find_network + self.app.client_manager.network.find_port = find_port + # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) diff --git a/openstackclient/tests/network/test_common.py b/openstackclient/tests/network/test_common.py index b30fdfcb35..58586ac066 100644 --- a/openstackclient/tests/network/test_common.py +++ b/openstackclient/tests/network/test_common.py @@ -13,7 +13,6 @@ import mock -from openstackclient.common import exceptions from openstackclient.network import common from openstackclient.tests import utils @@ -28,45 +27,31 @@ def setUp(self): super(TestFind, self).setUp() self.mock_client = mock.Mock() self.list_resources = mock.Mock() - self.mock_client.list_resources = self.list_resources - self.matrix = {'id': ID} + self.mock_client.find_resource = self.list_resources + self.resource = mock.Mock() + self.resource.id = ID def test_name(self): - self.list_resources.return_value = {RESOURCES: [self.matrix]} + self.list_resources.return_value = self.resource result = common.find(self.mock_client, RESOURCE, RESOURCES, NAME) self.assertEqual(ID, result) - self.list_resources.assert_called_with(fields='id', name=NAME) + self.list_resources.assert_called_with(NAME, ignore_missing=False) def test_id(self): - self.list_resources.side_effect = [{RESOURCES: []}, - {RESOURCES: [self.matrix]}] + self.list_resources.return_value = self.resource result = common.find(self.mock_client, RESOURCE, RESOURCES, NAME) self.assertEqual(ID, result) - self.list_resources.assert_called_with(fields='id', id=NAME) + self.list_resources.assert_called_with(NAME, ignore_missing=False) def test_nameo(self): - self.list_resources.return_value = {RESOURCES: [self.matrix]} + self.list_resources.return_value = self.resource result = common.find(self.mock_client, RESOURCE, RESOURCES, NAME, name_attr='nameo') self.assertEqual(ID, result) - self.list_resources.assert_called_with(fields='id', nameo=NAME) - - def test_dups(self): - dup = {'id': 'Larry'} - self.list_resources.return_value = {RESOURCES: [self.matrix, dup]} - - self.assertRaises(exceptions.CommandError, common.find, - self.mock_client, RESOURCE, RESOURCES, NAME) - - def test_nada(self): - self.list_resources.side_effect = [{RESOURCES: []}, - {RESOURCES: []}] - - self.assertRaises(exceptions.CommandError, common.find, - self.mock_client, RESOURCE, RESOURCES, NAME) + self.list_resources.assert_called_with(NAME, ignore_missing=False) diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index 67f446505b..3dcf381151 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -72,7 +72,6 @@ def setUp(self): self.api = self.app.client_manager.network.api -@mock.patch('openstackclient.network.v2.network._make_client_sdk') class TestCreateNetworkIdentityV3(TestNetwork): # The new network created. @@ -132,9 +131,7 @@ def setUp(self): loaded=True, ) - def test_create_no_options(self, _make_client_sdk): - _make_client_sdk.return_value = self.app.client_manager.network - + def test_create_no_options(self): arglist = [ self._network.name, ] @@ -155,9 +152,7 @@ def test_create_no_options(self, _make_client_sdk): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) - def test_create_all_options(self, _make_client_sdk): - _make_client_sdk.return_value = self.app.client_manager.network - + def test_create_all_options(self): arglist = [ "--disable", "--share", @@ -185,9 +180,7 @@ def test_create_all_options(self, _make_client_sdk): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) - def test_create_other_options(self, _make_client_sdk): - _make_client_sdk.return_value = self.app.client_manager.network - + def test_create_other_options(self): arglist = [ "--enable", "--no-share", @@ -211,7 +204,6 @@ def test_create_other_options(self, _make_client_sdk): self.assertEqual(self.data, data) -@mock.patch('openstackclient.network.v2.network._make_client_sdk') class TestCreateNetworkIdentityV2(TestNetwork): # The new network created. @@ -265,9 +257,7 @@ def setUp(self): # There is no DomainManager Mock in fake identity v2. - def test_create_with_project_identityv2(self, _make_client_sdk): - _make_client_sdk.return_value = self.app.client_manager.network - + def test_create_with_project_identityv2(self): arglist = [ "--project", identity_fakes_v2.project_name, self._network.name, @@ -290,9 +280,7 @@ def test_create_with_project_identityv2(self, _make_client_sdk): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) - def test_create_with_domain_identityv2(self, _make_client_sdk): - _make_client_sdk.return_value = self.app.client_manager.network - + def test_create_with_domain_identityv2(self): arglist = [ "--project", identity_fakes_v3.project_name, "--project-domain", identity_fakes_v3.domain_name, @@ -315,7 +303,6 @@ def test_create_with_domain_identityv2(self, _make_client_sdk): ) -@mock.patch('openstackclient.network.v2.network._make_client_sdk') class TestDeleteNetwork(TestNetwork): # The network to delete. @@ -331,9 +318,7 @@ def setUp(self): # Get the command object to test self.cmd = network.DeleteNetwork(self.app, self.namespace) - def test_delete(self, _make_client_sdk): - _make_client_sdk.return_value = self.app.client_manager.network - + def test_delete(self): arglist = [ self._network.name, ] @@ -348,7 +333,6 @@ def test_delete(self, _make_client_sdk): self.assertEqual(None, result) -@mock.patch('openstackclient.network.v2.network._make_client_sdk') class TestListNetwork(TestNetwork): # The networks going to be listed up. @@ -401,9 +385,7 @@ def setUp(self): self.network.networks = mock.Mock(return_value=self._network) - def test_network_list_no_options(self, _make_client_sdk): - _make_client_sdk.return_value = self.app.client_manager.network - + def test_network_list_no_options(self): arglist = [] verifylist = [ ('external', False), @@ -418,9 +400,7 @@ def test_network_list_no_options(self, _make_client_sdk): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) - def test_list_external(self, _make_client_sdk): - _make_client_sdk.return_value = self.app.client_manager.network - + def test_list_external(self): arglist = [ '--external', ] @@ -439,9 +419,7 @@ def test_list_external(self, _make_client_sdk): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) - def test_network_list_long(self, _make_client_sdk): - _make_client_sdk.return_value = self.app.client_manager.network - + def test_network_list_long(self): arglist = [ '--long', ] @@ -459,7 +437,6 @@ def test_network_list_long(self, _make_client_sdk): self.assertEqual(self.data_long, list(data)) -@mock.patch('openstackclient.network.v2.network._make_client_sdk') class TestSetNetwork(TestNetwork): # The network to set. @@ -475,9 +452,7 @@ def setUp(self): # Get the command object to test self.cmd = network.SetNetwork(self.app, self.namespace) - def test_set_this(self, _make_client_sdk): - _make_client_sdk.return_value = self.app.client_manager.network - + def test_set_this(self): self._network.is_dirty = True arglist = [ @@ -499,9 +474,7 @@ def test_set_this(self, _make_client_sdk): self.network.update_network.assert_called_with(self._network) self.assertEqual(None, result) - def test_set_that(self, _make_client_sdk): - _make_client_sdk.return_value = self.app.client_manager.network - + def test_set_that(self): self._network.is_dirty = True arglist = [ @@ -521,9 +494,7 @@ def test_set_that(self, _make_client_sdk): self.network.update_network.assert_called_with(self._network) self.assertEqual(None, result) - def test_set_nothing(self, _make_client_sdk): - _make_client_sdk.return_value = self.app.client_manager.network - + def test_set_nothing(self): self._network.is_dirty = False arglist = [self._network.name, ] @@ -534,7 +505,6 @@ def test_set_nothing(self, _make_client_sdk): parsed_args) -@mock.patch('openstackclient.network.v2.network._make_client_sdk') class TestShowNetwork(TestNetwork): # The network to set. @@ -568,9 +538,7 @@ def setUp(self): # Get the command object to test self.cmd = network.ShowNetwork(self.app, self.namespace) - def test_show_no_options(self, _make_client_sdk): - _make_client_sdk.return_value = self.app.client_manager.network - + def test_show_no_options(self): arglist = [] verifylist = [] @@ -580,9 +548,7 @@ def test_show_no_options(self, _make_client_sdk): except tests_utils.ParserException: pass - def test_show_all_options(self, _make_client_sdk): - _make_client_sdk.return_value = self.app.client_manager.network - + def test_show_all_options(self): arglist = [ self._network.name, ] From 55480d014c22a4184c4e97c693b7368401bcf837 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 5 Dec 2015 14:25:37 +0800 Subject: [PATCH 0466/3095] Remove unuseful test data in test_netwrok.py Change-Id: Ibd4f8bb602acdcc3421205d9dafc8dcafb9645df Implements: blueprint osc-network-unit-test-refactor Related-to: blueprint neutron-client --- .../tests/network/v2/test_network.py | 36 ------------------- 1 file changed, 36 deletions(-) diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index 3dcf381151..f08736569f 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -23,42 +23,6 @@ from openstackclient.tests.network.v2 import fakes as network_fakes from openstackclient.tests import utils as tests_utils -RESOURCE = 'network' -RESOURCES = 'networks' -FAKE_ID = 'iditty' -FAKE_NAME = 'noo' -FAKE_PROJECT = 'yaa' -RECORD = { - 'id': FAKE_ID, - 'name': FAKE_NAME, - 'admin_state_up': True, - 'router:external': True, - 'status': 'ACTIVE', - 'subnets': ['a', 'b'], - 'tenant_id': FAKE_PROJECT, -} -RESPONSE = {RESOURCE: copy.deepcopy(RECORD)} -FILTERED = [ - ( - 'id', - 'name', - 'project_id', - 'router_type', - 'state', - 'status', - 'subnets', - ), - ( - FAKE_ID, - FAKE_NAME, - FAKE_PROJECT, - 'External', - 'UP', - 'ACTIVE', - 'a, b', - ), -] - class TestNetwork(network_fakes.TestNetworkV2): From d377756a6208c2e3569b96a9d434f79c508616d6 Mon Sep 17 00:00:00 2001 From: xiexs Date: Wed, 9 Dec 2015 17:55:34 +0800 Subject: [PATCH 0467/3095] Refactor TestImageDelete with FakeImage Change-Id: I052a0220ca5d974824fc46ad403234e65e8173aa Implements: blueprint improve-image-unittest-framework --- openstackclient/tests/image/v2/test_image.py | 22 ++++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index be73c4ca9c..bdce146e27 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -41,6 +41,14 @@ def setUp(self): self.domain_mock = self.app.client_manager.identity.domains self.domain_mock.reset_mock() + def setup_images_mock(self, count): + images = image_fakes.FakeImage.create_images(count=count) + + self.images_mock.get = image_fakes.FakeImage.get_images( + images, + 0) + return images + class TestImageCreate(TestImage): @@ -316,23 +324,19 @@ class TestImageDelete(TestImage): def setUp(self): super(TestImageDelete, self).setUp() - # This is the return value for utils.find_resource() - self.images_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(image_fakes.IMAGE), - loaded=True, - ) self.images_mock.delete.return_value = None # Get the command object to test self.cmd = image.DeleteImage(self.app, None) def test_image_delete_no_options(self): + images = self.setup_images_mock(count=1) + arglist = [ - image_fakes.image_id, + images[0].id, ] verifylist = [ - ('images', [image_fakes.image_id]), + ('images', [images[0].id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -340,7 +344,7 @@ def test_image_delete_no_options(self): self.cmd.take_action(parsed_args) self.images_mock.delete.assert_called_with( - image_fakes.image_id, + images[0].id, ) From 50e52f355f8cf5c7cad8682923283bf61f244573 Mon Sep 17 00:00:00 2001 From: xiexs Date: Wed, 9 Dec 2015 17:58:07 +0800 Subject: [PATCH 0468/3095] Add multi deletion testcase for "openstack image delete" Change-Id: I5442128a290a9ad3b9ff9919431a1ecc0c697dad Implements: blueprint improve-image-unittest-framework --- openstackclient/tests/image/v2/test_image.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index bdce146e27..10266f0223 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -347,6 +347,22 @@ def test_image_delete_no_options(self): images[0].id, ) + def test_image_delete_multi_images(self): + images = self.setup_images_mock(count=3) + + arglist = [i.id for i in images] + verifylist = [ + ('images', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + calls = [mock.call(i.id) for i in images] + + self.images_mock.delete.assert_has_calls(calls) + class TestImageList(TestImage): From 6a5be8c4c9abf25d7fa6a952ae466aedd056d353 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Wed, 9 Dec 2015 19:42:52 +0800 Subject: [PATCH 0469/3095] Add unit test for TestServerList to test --long option. In two steps: 1. Setup all necessary attributes of a server in setUp(), including the ones that are not faked in FaseServer by default. 2. Run a similar process with no option test case. The future plan is to move all these attributes to FakeServer. But it will cause some other changes which has nothing to do with this patch. So leave this job to do later. Change-Id: I1134812a0ea146ef737b0f0ffbef8ca23684accd Implements: blueprint osc-unit-test-framework-improvement --- .../tests/compute/v2/test_server.py | 55 +++++++++++++++++-- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index ce2dcdf7e8..ec916b9fdc 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -620,10 +620,17 @@ class TestServerList(TestServer): 'Status', 'Networks', ) - - # Data returned by corresponding Nova API. The elements in this list are - # tuples filled with server attributes. - data = [] + columns_long = ( + 'ID', + 'Name', + 'Status', + 'Task State', + 'Power State', + 'Networks', + 'Availability Zone', + 'Host', + 'Properties', + ) # Default search options, in the case of no commandline option specified. search_opts = { @@ -652,12 +659,18 @@ class TestServerList(TestServer): def setUp(self): super(TestServerList, self).setUp() - # The fake servers' attributes. + # The fake servers' attributes. Use the original attributes names in + # nova, not the ones printed by "server list" command. self.attrs = { 'status': 'ACTIVE', + 'OS-EXT-STS:task_state': 'None', + 'OS-EXT-STS:power_state': 0x01, # Running 'networks': { u'public': [u'10.20.30.40', u'2001:db8::5'] }, + 'OS-EXT-AZ:availability_zone': 'availability-zone-xxx', + 'OS-EXT-SRV-ATTR:host': 'host-name-xxx', + 'Metadata': '', } # The servers to be listed. @@ -669,6 +682,9 @@ def setUp(self): self.cmd = server.ListServer(self.app, None) # Prepare data returned by fake Nova API. + self.data = [] + self.data_long = [] + for s in self.servers: self.data.append(( s.id, @@ -676,6 +692,19 @@ def setUp(self): s.status, server._format_servers_list_networks(s.networks), )) + self.data_long.append(( + s.id, + s.name, + s.status, + getattr(s, 'OS-EXT-STS:task_state'), + server._format_servers_list_power_state( + getattr(s, 'OS-EXT-STS:power_state') + ), + server._format_servers_list_networks(s.networks), + getattr(s, 'OS-EXT-AZ:availability_zone'), + getattr(s, 'OS-EXT-SRV-ATTR:host'), + s.Metadata, + )) def test_server_list_no_option(self): arglist = [] @@ -691,6 +720,22 @@ def test_server_list_no_option(self): self.assertEqual(self.columns, columns) self.assertEqual(tuple(self.data), tuple(data)) + def test_server_list_long_option(self): + arglist = [ + '--long', + ] + verifylist = [ + ('all_projects', False), + ('long', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.servers_mock.list.assert_called_with(**self.kwargs) + self.assertEqual(self.columns_long, columns) + self.assertEqual(tuple(self.data_long), tuple(data)) + class TestServerLock(TestServer): From 7aa6e5e36c2aee4bbffda0a1ed56f7e6e508e52d Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Wed, 9 Dec 2015 11:38:31 -0700 Subject: [PATCH 0470/3095] SDK integration extensions and server create networks Finish up the SDK integration with server create network and port find and extension list. Change-Id: I18dbada784d8aa92a45a937f251023ddf899c53e --- openstackclient/common/extension.py | 4 +- openstackclient/compute/v2/server.py | 19 ++----- openstackclient/network/common.py | 29 ---------- .../tests/common/test_extension.py | 6 +- .../tests/compute/v2/test_server.py | 17 ++++-- openstackclient/tests/network/test_common.py | 57 ------------------- openstackclient/tests/network/v2/fakes.py | 20 ++++--- 7 files changed, 33 insertions(+), 119 deletions(-) delete mode 100644 openstackclient/network/common.py delete mode 100644 openstackclient/tests/network/test_common.py diff --git a/openstackclient/common/extension.py b/openstackclient/common/extension.py index dad7ed6285..8825b4918b 100644 --- a/openstackclient/common/extension.py +++ b/openstackclient/common/extension.py @@ -111,9 +111,9 @@ def take_action(self, parsed_args): if parsed_args.network or show_all: network_client = self.app.client_manager.network try: - data = network_client.list_extensions()['extensions'] + data = network_client.extensions() dict_tuples = ( - utils.get_dict_properties( + utils.get_item_properties( s, columns, formatters={}, diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 67da63b118..9e2721fb67 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -37,7 +37,6 @@ from openstackclient.common import utils from openstackclient.i18n import _ # noqa from openstackclient.identity import common as identity_common -from openstackclient.network import common as network_common def _format_servers_list_networks(networks): @@ -476,19 +475,13 @@ def take_action(self, parsed_args): if neutron_enabled: network_client = self.app.client_manager.network if nic_info["net-id"]: - nic_info["net-id"] = network_common.find( - network_client, - 'network', - 'networks', - nic_info["net-id"] - ) + net = network_client.find_network( + nic_info["net-id"], ignore_missing=False) + nic_info["net-id"] = net.id if nic_info["port-id"]: - nic_info["port-id"] = network_common.find( - network_client, - 'port', - 'ports', - nic_info["port-id"] - ) + port = network_client.find_port( + nic_info["port-id"], ignore_missing=False) + nic_info["port-id"] = port.id else: if nic_info["net-id"]: nic_info["net-id"] = utils.find_resource( diff --git a/openstackclient/network/common.py b/openstackclient/network/common.py deleted file mode 100644 index 7b3f8a62e9..0000000000 --- a/openstackclient/network/common.py +++ /dev/null @@ -1,29 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - - -def find(client, resource, resources, name_or_id, name_attr='name'): - """Find a network resource - - :param client: network client - :param resource: name of the resource - :param resources: plural name of resource - :param name_or_id: name or id of resource user is looking for - :param name_attr: key to the name attribute for the resource - - For example: - n = find(netclient, 'network', 'networks', 'matrix') - """ - list_method = getattr(client, "find_%s" % resource) - data = list_method(name_or_id, ignore_missing=False) - return data.id diff --git a/openstackclient/tests/common/test_extension.py b/openstackclient/tests/common/test_extension.py index 6d34bdd8d8..5f5588a7d3 100644 --- a/openstackclient/tests/common/test_extension.py +++ b/openstackclient/tests/common/test_extension.py @@ -34,9 +34,9 @@ def setUp(self): self.app.client_manager.identity.extensions) self.identity_extensions_mock.reset_mock() - network = network_fakes.FakeNetworkV2Client() - self.app.client_manager.network = network - self.network_extensions_mock = network.list_extensions + network_client = network_fakes.FakeNetworkV2Client() + self.app.client_manager.network = network_client + self.network_extensions_mock = network_client.extensions self.network_extensions_mock.reset_mock() diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index d6dcde1af4..82f32051be 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -215,12 +215,17 @@ def test_server_create_with_network(self): self.app.client_manager.auth_ref.service_catalog.get_endpoints = ( get_endpoints) - list_networks = mock.Mock() - list_ports = mock.Mock() - self.app.client_manager.network.list_networks = list_networks - self.app.client_manager.network.list_ports = list_ports - list_networks.return_value = {'networks': [{'id': 'net1_uuid'}]} - list_ports.return_value = {'ports': [{'id': 'port1_uuid'}]} + find_network = mock.Mock() + find_port = mock.Mock() + network_client = self.app.client_manager.network + network_client.find_network = find_network + network_client.find_port = find_port + netty = mock.Mock() + netty.id = 'net1_uuid' + porty = mock.Mock() + porty.id = 'port1_uuid' + find_network.return_value = netty + find_port.return_value = porty # Mock sdk APIs. _network = mock.Mock() diff --git a/openstackclient/tests/network/test_common.py b/openstackclient/tests/network/test_common.py deleted file mode 100644 index 58586ac066..0000000000 --- a/openstackclient/tests/network/test_common.py +++ /dev/null @@ -1,57 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - -import mock - -from openstackclient.network import common -from openstackclient.tests import utils - -RESOURCE = 'resource' -RESOURCES = 'resources' -NAME = 'matrix' -ID = 'Fishburne' - - -class TestFind(utils.TestCase): - def setUp(self): - super(TestFind, self).setUp() - self.mock_client = mock.Mock() - self.list_resources = mock.Mock() - self.mock_client.find_resource = self.list_resources - self.resource = mock.Mock() - self.resource.id = ID - - def test_name(self): - self.list_resources.return_value = self.resource - - result = common.find(self.mock_client, RESOURCE, RESOURCES, NAME) - - self.assertEqual(ID, result) - self.list_resources.assert_called_with(NAME, ignore_missing=False) - - def test_id(self): - self.list_resources.return_value = self.resource - - result = common.find(self.mock_client, RESOURCE, RESOURCES, NAME) - - self.assertEqual(ID, result) - self.list_resources.assert_called_with(NAME, ignore_missing=False) - - def test_nameo(self): - self.list_resources.return_value = self.resource - - result = common.find(self.mock_client, RESOURCE, RESOURCES, NAME, - name_attr='nameo') - - self.assertEqual(ID, result) - self.list_resources.assert_called_with(NAME, ignore_missing=False) diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 10814f96fb..9e99911430 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -27,19 +27,21 @@ extension_alias = 'Dystopian' extension_links = '[{"href":''"https://github.com/os/network", "type"}]' -NETEXT = { - 'name': extension_name, - 'namespace': extension_namespace, - 'description': extension_description, - 'updated': extension_updated, - 'alias': extension_alias, - 'links': extension_links, -} + +def create_extension(): + extension = mock.Mock() + extension.name = extension_name + extension.namespace = extension_namespace + extension.description = extension_description + extension.updated = extension_updated + extension.alias = extension_alias + extension.links = extension_links + return extension class FakeNetworkV2Client(object): def __init__(self, **kwargs): - self.list_extensions = mock.Mock(return_value={'extensions': [NETEXT]}) + self.extensions = mock.Mock(return_value=[create_extension()]) class TestNetworkV2(utils.TestCommand): From 7a42174c4b30fd31113da906cf46d158fe9e18d4 Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Wed, 9 Dec 2015 11:45:59 -0700 Subject: [PATCH 0471/3095] Remove old code after sdk integration We won't need this anymore. Change-Id: Ib10be1dedb8db81f0cba6e45b8a9b0aade2ab473 --- openstackclient/api/network_v2.py | 59 ------------------- openstackclient/tests/api/test_network_v2.py | 52 ---------------- openstackclient/tests/network/v2/fakes.py | 6 -- .../tests/network/v2/test_network.py | 3 - 4 files changed, 120 deletions(-) delete mode 100644 openstackclient/api/network_v2.py delete mode 100644 openstackclient/tests/api/test_network_v2.py diff --git a/openstackclient/api/network_v2.py b/openstackclient/api/network_v2.py deleted file mode 100644 index 90be52377a..0000000000 --- a/openstackclient/api/network_v2.py +++ /dev/null @@ -1,59 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - -"""Network v2 API Library""" - -from openstackclient.api import api - - -class APIv2(api.BaseAPI): - """Network v2 API""" - - def __init__(self, **kwargs): - super(APIv2, self).__init__(**kwargs) - - def dhcp_agent_list( - self, - dhcp_id=None, - **filter - ): - """List DHCP agents - - :param string dhcp_id: - DHCP Agent ID - :param filter: - used to create the query string filters - http://docs.openstack.org/api/openstack-network/2.0/content/filtering.html - """ - - return self.list('dhcp-networks', **filter)['dhcp-networks'] - - def network_list( - self, - external=False, - **filter - ): - """List external networks - - :param string dhcp_id: - DHCP agent ID - :param bool external: - Return external networks if True - :param filter: - used to create the query string filters - http://docs.openstack.org/api/openstack-network/2.0/content/filtering.html - """ - - if external: - filter = {'router:external': True} - return self.list('networks', **filter)['networks'] diff --git a/openstackclient/tests/api/test_network_v2.py b/openstackclient/tests/api/test_network_v2.py deleted file mode 100644 index 80f1d9dec0..0000000000 --- a/openstackclient/tests/api/test_network_v2.py +++ /dev/null @@ -1,52 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - -"""Network v2 API Library Tests""" - -from requests_mock.contrib import fixture - -from keystoneauth1 import session -from openstackclient.api import network_v2 as network -from openstackclient.tests import utils - - -FAKE_PROJECT = 'xyzpdq' -FAKE_URL = 'http://gopher.com/v2/' + FAKE_PROJECT - - -class TestNetworkAPIv2(utils.TestCase): - - def setUp(self): - super(TestNetworkAPIv2, self).setUp() - sess = session.Session() - self.api = network.APIv2(session=sess, endpoint=FAKE_URL) - self.requests_mock = self.useFixture(fixture.Fixture()) - - -class TestNetwork(TestNetworkAPIv2): - - LIST_NETWORK_RESP = [ - {'id': '1', 'name': 'p1', 'description': 'none', 'enabled': True}, - {'id': '2', 'name': 'p2', 'description': 'none', 'enabled': False}, - {'id': '3', 'name': 'p3', 'description': 'none', 'enabled': True}, - ] - - def test_network_list_no_options(self): - self.requests_mock.register_uri( - 'GET', - FAKE_URL + '/networks', - json={'networks': self.LIST_NETWORK_RESP}, - status_code=200, - ) - ret = self.api.network_list() - self.assertEqual(self.LIST_NETWORK_RESP, ret) diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 9e99911430..abb88ee478 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -16,7 +16,6 @@ import mock import uuid -from openstackclient.api import network_v2 from openstackclient.tests import fakes from openstackclient.tests import utils @@ -57,11 +56,6 @@ def setUp(self): token=fakes.AUTH_TOKEN, ) - self.app.client_manager.network.api = network_v2.APIv2( - session=mock.Mock(), - service_type="network", - ) - class FakeNetwork(object): """Fake one or more networks.""" diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index f08736569f..4c897bcb7f 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -32,9 +32,6 @@ def setUp(self): # Get a shortcut to the network client self.network = self.app.client_manager.network - # Get a shortcut to the APIManager - self.api = self.app.client_manager.network.api - class TestCreateNetworkIdentityV3(TestNetwork): From f65f82e4350757fcae93891c42ed8b01bb8577f2 Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Wed, 9 Dec 2015 11:59:30 -0700 Subject: [PATCH 0472/3095] Fix poorly named test mocks Change-Id: I6e2911e88fc458b39d5024a5714ed8af3f519946 --- openstackclient/tests/compute/v2/test_server.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 82f32051be..73d3cc6193 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -220,12 +220,12 @@ def test_server_create_with_network(self): network_client = self.app.client_manager.network network_client.find_network = find_network network_client.find_port = find_port - netty = mock.Mock() - netty.id = 'net1_uuid' - porty = mock.Mock() - porty.id = 'port1_uuid' - find_network.return_value = netty - find_port.return_value = porty + network_resource = mock.Mock() + network_resource.id = 'net1_uuid' + port_resource = mock.Mock() + port_resource.id = 'port1_uuid' + find_network.return_value = network_resource + find_port.return_value = port_resource # Mock sdk APIs. _network = mock.Mock() From 8485a52b99d895a79dca3d7d384543e847b9f381 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Wed, 9 Dec 2015 15:50:17 -0600 Subject: [PATCH 0473/3095] Remove python-neutronclient requirement Now that the neutron support is using the openstacksdk requirement, the python-neutronclient requirement can be removed. Change-Id: Ieefac297e136f2f2997ec41cef2673e814c75b55 Related-to: blueprint neutron-client --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 0bce74a2f7..f7113547c9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,6 +16,5 @@ python-glanceclient>=0.18.0 python-keystoneclient!=1.8.0,>=1.6.0 python-novaclient!=2.33.0,>=2.29.0 python-cinderclient>=1.3.1 -python-neutronclient>=2.6.0 requests>=2.8.1 stevedore>=1.5.0 # Apache-2.0 From a8ba54562b4e31c5a02c9aef19e1c016129e30f4 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 8 Dec 2015 19:16:59 +0800 Subject: [PATCH 0474/3095] TestServerGeneral: Add test for _format_servers_list_power_state() If we have tests for each helper function, then we can call them in other tests without worrying about error. Change-Id: I1e4273aef201fe9d8936e5c365ebb46068039892 Implements: blueprint osc-unit-test-framework-improvement --- .../tests/compute/v2/test_server.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index d6dcde1af4..805aa7396c 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -1066,3 +1066,23 @@ def test_get_ip_address(self): server._get_ip_address, self.OLD, 'public', [4, 6]) self.assertRaises(exceptions.CommandError, server._get_ip_address, self.OLD, 'private', [6]) + + def test_format_servers_list_power_state(self): + self.assertEqual("NOSTATE", + server._format_servers_list_power_state(0x00)) + self.assertEqual("Running", + server._format_servers_list_power_state(0x01)) + self.assertEqual("", + server._format_servers_list_power_state(0x02)) + self.assertEqual("Paused", + server._format_servers_list_power_state(0x03)) + self.assertEqual("Shutdown", + server._format_servers_list_power_state(0x04)) + self.assertEqual("", + server._format_servers_list_power_state(0x05)) + self.assertEqual("Crashed", + server._format_servers_list_power_state(0x06)) + self.assertEqual("Suspended", + server._format_servers_list_power_state(0x07)) + self.assertEqual("N/A", + server._format_servers_list_power_state(0x08)) From 1cf320302bee2e406ed7189d3cf3d08542770637 Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Thu, 10 Dec 2015 14:58:16 -0700 Subject: [PATCH 0475/3095] Map some of the SDK field names The keys() method returns the keys returned from Neutron, but the SDK maps some things like tenant_id to project_id. This makes the output a little prettier. Change-Id: Ibd8c890b61ffc94021f93fc1051fcf5dabd1e9ea --- openstackclient/network/v2/network.py | 19 +++++++++++++++---- openstackclient/tests/network/v2/fakes.py | 5 ++++- .../tests/network/v2/test_network.py | 14 +++++++------- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 519356b4f7..6b2062dd10 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -39,6 +39,17 @@ def _format_router_external(item): } +def _get_columns(item): + columns = item.keys() + if 'tenant_id' in columns: + columns.remove('tenant_id') + columns.append('project_id') + if 'router:external' in columns: + columns.remove('router:external') + columns.append('router_external') + return tuple(sorted(columns)) + + class CreateNetwork(show.ShowOne): """Create new network""" @@ -91,9 +102,9 @@ def take_action(self, parsed_args): client = self.app.client_manager.network body = self.get_body(parsed_args) obj = client.create_network(**body) - columns = sorted(obj.keys()) + columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) - return (tuple(columns), data) + return (columns, data) def get_body(self, parsed_args): body = {'name': str(parsed_args.name), @@ -292,6 +303,6 @@ def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) client = self.app.client_manager.network obj = client.find_network(parsed_args.identifier, ignore_missing=False) - columns = sorted(obj.keys()) + columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) - return (tuple(columns), data) + return (columns, data) diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index abb88ee478..0e601a72ac 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -73,11 +73,12 @@ def create_one_network(attrs={}, methods={}): router_external, status, subnets, tenant_id """ # Set default attributes. + project_id = 'project-id-' + uuid.uuid4().hex network_attrs = { 'id': 'network-id-' + uuid.uuid4().hex, 'name': 'network-name-' + uuid.uuid4().hex, 'status': 'ACTIVE', - 'tenant_id': 'project-id-' + uuid.uuid4().hex, + 'tenant_id': project_id, 'admin_state_up': True, 'shared': False, 'subnets': ['a', 'b'], @@ -101,6 +102,8 @@ def create_one_network(attrs={}, methods={}): network = fakes.FakeResource(info=copy.deepcopy(network_attrs), methods=copy.deepcopy(network_methods), loaded=True) + network.project_id = project_id + return network @staticmethod diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index e86514927a..df61b0db28 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -44,20 +44,20 @@ class TestCreateNetworkIdentityV3(TestNetwork): 'admin_state_up', 'id', 'name', + 'project_id', 'router_external', 'status', 'subnets', - 'tenant_id', ) data = ( network._format_admin_state(_network.admin_state_up), _network.id, _network.name, + _network.project_id, network._format_router_external(_network.router_external), _network.status, utils.format_list(_network.subnets), - _network.tenant_id, ) def setUp(self): @@ -176,20 +176,20 @@ class TestCreateNetworkIdentityV2(TestNetwork): 'admin_state_up', 'id', 'name', + 'project_id', 'router_external', 'status', 'subnets', - 'tenant_id', ) data = ( network._format_admin_state(_network.admin_state_up), _network.id, _network.name, + _network.project_id, network._format_router_external(_network.router_external), _network.status, utils.format_list(_network.subnets), - _network.tenant_id, ) def setUp(self): @@ -330,7 +330,7 @@ class TestListNetwork(TestNetwork): net.id, net.name, net.status, - net.tenant_id, + net.project_id, network._format_admin_state(net.admin_state_up), net.shared, utils.format_list(net.subnets), @@ -475,20 +475,20 @@ class TestShowNetwork(TestNetwork): 'admin_state_up', 'id', 'name', + 'project_id', 'router_external', 'status', 'subnets', - 'tenant_id', ) data = ( network._format_admin_state(_network.admin_state_up), _network.id, _network.name, + _network.project_id, network._format_router_external(_network.router_external), _network.status, utils.format_list(_network.subnets), - _network.tenant_id, ) def setUp(self): From 2caf7b19e207f232530534ad2d7407af5e3355d9 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 5 Dec 2015 10:40:09 +0800 Subject: [PATCH 0476/3095] Trivial: Do not use plural format in command parameter in "network delete" Since "network delete" could delete more than one network, add a (s) to the doc. And also, rename the parameter "networks" to "network". The naming style is not using plural format in parameter. Change-Id: Id434ea905af34457f84ea6bcb18addef5800429a --- doc/source/command-objects/network.rst | 2 +- openstackclient/network/v2/network.py | 6 +++--- openstackclient/tests/network/v2/test_network.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/source/command-objects/network.rst b/doc/source/command-objects/network.rst index 6199d0248b..c77bea1f50 100644 --- a/doc/source/command-objects/network.rst +++ b/doc/source/command-objects/network.rst @@ -62,7 +62,7 @@ Delete network(s) .. _network_delete-project: .. describe:: - Network to delete (name or ID) + Network(s) to delete (name or ID) network list ------------ diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 64b98f5d85..519356b4f7 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -119,17 +119,17 @@ class DeleteNetwork(command.Command): def get_parser(self, prog_name): parser = super(DeleteNetwork, self).get_parser(prog_name) parser.add_argument( - 'networks', + 'network', metavar="", nargs="+", - help=("Network to delete (name or ID)") + help=("Network(s) to delete (name or ID)") ) return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) client = self.app.client_manager.network - for network in parsed_args.networks: + for network in parsed_args.network: obj = client.find_network(network) client.delete_network(obj) return diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index 4c897bcb7f..e86514927a 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -284,7 +284,7 @@ def test_delete(self): self._network.name, ] verifylist = [ - ('networks', [self._network.name]), + ('network', [self._network.name]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) From 4221bc282d80b99694f7cc57c90d4bbb9f310120 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 12 Dec 2015 01:19:20 +0800 Subject: [PATCH 0477/3095] Trivial: Fix parameter name typo in network.rst 1. "network delete" takes network name or ID, not project 2. "network set/show" takes network name or ID, not only name. So use network, not name. Change-Id: I13835fea1d0151ea0cd93e250b022c9daf74b537 --- doc/source/command-objects/network.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/source/command-objects/network.rst b/doc/source/command-objects/network.rst index c77bea1f50..7c791840fc 100644 --- a/doc/source/command-objects/network.rst +++ b/doc/source/command-objects/network.rst @@ -59,7 +59,7 @@ Delete network(s) os network delete [ ...] -.. _network_delete-project: +.. _network_delete-network: .. describe:: Network(s) to delete (name or ID) @@ -118,7 +118,7 @@ Set network properties Do not share the network between projects -.. _network_set-name: +.. _network_set-network: .. describe:: Network to modify (name or ID) @@ -134,7 +134,7 @@ Display network details os network show -.. _network_show-name: +.. _network_show-network: .. describe:: Network to display (name or ID) From a5267772186add4e37c454c5a97d0ea3a1c88a81 Mon Sep 17 00:00:00 2001 From: David Moreau Simard Date: Wed, 9 Dec 2015 16:18:54 -0500 Subject: [PATCH 0478/3095] Make --image parameter optional in "server rebuild" The command will now default to the image currently in-use by the server, effectively making the --image parameter optional. This commit also adds basic tests for ServerRebuild since there wasn't any. Will add more full tests for it. Change-Id: I733fd3ad5a825f06563c72aa430122e1a0e3b3b0 Closes-bug: #1524406 Co-Authored-By: David Moreau Simard Co-Authored-By: Tang Chen --- doc/source/command-objects/server.rst | 5 +- openstackclient/compute/v2/server.py | 11 +-- .../tests/compute/v2/test_server.py | 70 +++++++++++++++++++ 3 files changed, 79 insertions(+), 7 deletions(-) diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index fd487b8ea4..fc27597125 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -375,14 +375,15 @@ Rebuild server .. code:: bash os server rebuild - --image + [--image ] [--password ] [--wait] .. option:: --image - Recreate server from this image + Recreate server from the specified image (name or ID). Defaults to the + currently used one. .. option:: --password diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 9e2721fb67..6dce8ed546 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1096,8 +1096,8 @@ def get_parser(self, prog_name): parser.add_argument( '--image', metavar='', - required=True, - help=_('Recreate server from this image'), + help=_('Recreate server from the specified image (name or ID).' + ' Defaults to the currently used one.'), ) parser.add_argument( '--password', @@ -1115,12 +1115,13 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): compute_client = self.app.client_manager.compute - # Lookup parsed_args.image - image = utils.find_resource(compute_client.images, parsed_args.image) - server = utils.find_resource( compute_client.servers, parsed_args.server) + # If parsed_args.image is not set, default to the currently used one. + image_id = parsed_args.image or server._info.get('image', {}).get('id') + image = utils.find_resource(compute_client.images, image_id) + server = server.rebuild(image, parsed_args.password) if parsed_args.wait: if utils.wait_for_status( diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 73d3cc6193..02eae4241e 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -736,6 +736,76 @@ def test_server_pause_multi_servers(self): self.run_method_with_servers('pause', 3) +class TestServerRebuild(TestServer): + + def setUp(self): + super(TestServerRebuild, self).setUp() + + # Return value for utils.find_resource for image + self.image = image_fakes.FakeImage.create_one_image() + self.cimages_mock.get.return_value = self.image + + # Fake the rebuilt new server. + new_server = compute_fakes.FakeServer.create_one_server() + + # Fake the server to be rebuilt. The IDs of them should be the same. + attrs = { + 'id': new_server.id, + 'image': { + 'id': self.image.id + }, + 'networks': {}, + 'adminPass': 'passw0rd', + } + methods = { + 'rebuild': new_server, + } + self.server = compute_fakes.FakeServer.create_one_server( + attrs=attrs, + methods=methods + ) + + # Return value for utils.find_resource for server. + self.servers_mock.get.return_value = self.server + + self.cmd = server.RebuildServer(self.app, None) + + def test_rebuild_with_current_image(self): + arglist = [ + self.server.id, + ] + verifylist = [ + ('server', self.server.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Get the command object to test. + self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.cimages_mock.get.assert_called_with(self.image.id) + self.server.rebuild.assert_called_with(self.image, None) + + def test_rebuild_with_current_image_and_password(self): + password = 'password-xxx' + arglist = [ + self.server.id, + '--password', password + ] + verifylist = [ + ('server', self.server.id), + ('password', password) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Get the command object to test + self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.cimages_mock.get.assert_called_with(self.image.id) + self.server.rebuild.assert_called_with(self.image, password) + + class TestServerResize(TestServer): def setUp(self): From 6f7c705d4a4c9b4f7d618619cecef4c6e36c2b74 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Fri, 11 Dec 2015 17:18:28 +0800 Subject: [PATCH 0479/3095] Trivial: Improve unclear comments in test_server.py In test_server.py, there are two ImageManagers are faked: 1. the one in compute client 2. the one in image client But the comments are the same. And so is volume. This patch makes the comments more clear. Change-Id: I2c52f48a7b3c005c185a4ac64abbb3e18d5fb3de --- openstackclient/tests/compute/v2/test_server.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 73d3cc6193..7af83195cb 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -31,23 +31,23 @@ class TestServer(compute_fakes.TestComputev2): def setUp(self): super(TestServer, self).setUp() - # Get a shortcut to the ServerManager Mock + # Get a shortcut to the compute client ServerManager Mock self.servers_mock = self.app.client_manager.compute.servers self.servers_mock.reset_mock() - # Get a shortcut to the ImageManager Mock + # Get a shortcut to the compute client ImageManager Mock self.cimages_mock = self.app.client_manager.compute.images self.cimages_mock.reset_mock() - # Get a shortcut to the FlavorManager Mock + # Get a shortcut to the compute client FlavorManager Mock self.flavors_mock = self.app.client_manager.compute.flavors self.flavors_mock.reset_mock() - # Get a shortcut to the ImageManager Mock + # Get a shortcut to the image client ImageManager Mock self.images_mock = self.app.client_manager.image.images self.images_mock.reset_mock() - # Get a shortcut to the VolumeManager Mock + # Get a shortcut to the volume client VolumeManager Mock self.volumes_mock = self.app.client_manager.volume.volumes self.volumes_mock.reset_mock() From 185412f28c6eea825760617548e4256ac35003bb Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 12 Dec 2015 17:19:10 +0800 Subject: [PATCH 0480/3095] Router: Add class FakeRouter to test "router xxx" command A unit test class similar to FakeServer, which is able to fake one or more routers. It will be used by the router CRUD patches. Change-Id: I9b87c6c95282902c3a829da51229a35d4265a1e4 Implements: blueprint neutron-client Partial-bug: #1519503 --- openstackclient/tests/network/v2/fakes.py | 81 +++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index abb88ee478..284a40b7a2 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -140,3 +140,84 @@ def get_networks(networks=None, count=2): if networks is None: networks = FakeNetwork.create_networks(count) return mock.MagicMock(side_effect=networks) + + +class FakeRouter(object): + """Fake one or more routers.""" + + @staticmethod + def create_one_router(attrs={}, methods={}): + """Create a fake router. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :return: + A FakeResource object, with id, name, admin_state_up, + status, tenant_id + """ + # Set default attributes. + router_attrs = { + 'id': 'router-id-' + uuid.uuid4().hex, + 'name': 'router-name-' + uuid.uuid4().hex, + 'status': 'ACTIVE', + 'admin_state_up': True, + 'distributed': False, + 'ha': False, + 'tenant_id': 'project-id-' + uuid.uuid4().hex, + 'routes': [], + 'external_gateway_info': {}, + } + + # Overwrite default attributes. + router_attrs.update(attrs) + + # Set default methods. + router_methods = {} + + # Overwrite default methods. + router_methods.update(methods) + + router = fakes.FakeResource(info=copy.deepcopy(router_attrs), + methods=copy.deepcopy(router_methods), + loaded=True) + return router + + @staticmethod + def create_routers(attrs={}, methods={}, count=2): + """Create multiple fake routers. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :param int count: + The number of routers to fake + :return: + A list of FakeResource objects faking the routers + """ + routers = [] + for i in range(0, count): + routers.append(FakeRouter.create_one_router(attrs, methods)) + + return routers + + @staticmethod + def get_routers(routers=None, count=2): + """Get an iterable MagicMock object with a list of faked routers. + + If routers list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List routers: + A list of FakeResource objects faking routers + :param int count: + The number of routers to fake + :return: + An iterable Mock object with side_effect set to a list of faked + routers + """ + if routers is None: + routers = FakeRouter.create_routers(count) + return mock.MagicMock(side_effect=routers) From 3278b3a022c34b1abe28e1ed7b16ed60a059a441 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 12 Dec 2015 10:28:48 +0800 Subject: [PATCH 0481/3095] Router: Add "router list" command using SDK Add "router list" command. It takes one "--long" option. By default, the command will print router id, name, status, admin state up, distributed, ha and project id. With "--long" option, it will also print routes and external gateway info. Change-Id: I9d21904c41c11ee1fa107f985744878a1dc2f970 Implements: blueprint neutron-client Partial-bug: #1519503 --- doc/source/command-objects/router.rst | 20 ++++ doc/source/commands.rst | 1 + openstackclient/network/v2/router.py | 93 ++++++++++++++++ .../tests/network/v2/test_router.py | 105 ++++++++++++++++++ setup.cfg | 1 + 5 files changed, 220 insertions(+) create mode 100644 doc/source/command-objects/router.rst create mode 100644 openstackclient/network/v2/router.py create mode 100644 openstackclient/tests/network/v2/test_router.py diff --git a/doc/source/command-objects/router.rst b/doc/source/command-objects/router.rst new file mode 100644 index 0000000000..f7b5a67e07 --- /dev/null +++ b/doc/source/command-objects/router.rst @@ -0,0 +1,20 @@ +====== +router +====== + +Network v2 + +router list +----------- + +List routers + +.. program:: router list +.. code:: bash + + os router list + [--long] + +.. option:: --long + + List additional fields in output diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 1c6c2d8ab3..0ec4f71bcb 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -107,6 +107,7 @@ referring to both Compute and Volume quotas. * ``request token``: (**Identity**) temporary OAuth-based token * ``role``: (**Identity**) a policy object used to determine authorization * ``role assignment``: (**Identity**) a relationship between roles, users or groups, and domains or projects +* ``router``: (**Network**) - a virtual router * ``security group``: (**Compute**, **Network**) - groups of network access rules * ``security group rule``: (**Compute**, **Network**) - the individual rules that define protocol/IP/port access * ``server``: (**Compute**) virtual machine instance diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py new file mode 100644 index 0000000000..cf5dae59f1 --- /dev/null +++ b/openstackclient/network/v2/router.py @@ -0,0 +1,93 @@ +# 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. +# + +"""Router action implementations""" + +import json +import logging + +from cliff import lister + +from openstackclient.common import utils + + +def _format_admin_state(state): + return 'UP' if state else 'DOWN' + + +def _format_external_gateway_info(info): + try: + return json.dumps(info) + except (TypeError, KeyError): + return '' + + +_formatters = { + 'admin_state_up': _format_admin_state, + 'external_gateway_info': _format_external_gateway_info, +} + + +class ListRouter(lister.Lister): + """List routers""" + + log = logging.getLogger(__name__ + '.ListRouter') + + def get_parser(self, prog_name): + parser = super(ListRouter, self).get_parser(prog_name) + parser.add_argument( + '--long', + action='store_true', + default=False, + help='List additional fields in output', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + client = self.app.client_manager.network + + columns = ( + 'id', + 'name', + 'status', + 'admin_state_up', + 'distributed', + 'ha', + 'tenant_id', + ) + column_headers = ( + 'ID', + 'Name', + 'Status', + 'State', + 'Distributed', + 'HA', + 'Project', + ) + if parsed_args.long: + columns = columns + ( + 'routes', + 'external_gateway_info', + ) + column_headers = column_headers + ( + 'Routes', + 'External gateway info', + ) + + data = client.routers() + return (column_headers, + (utils.get_item_properties( + s, columns, + formatters=_formatters, + ) for s in data)) diff --git a/openstackclient/tests/network/v2/test_router.py b/openstackclient/tests/network/v2/test_router.py new file mode 100644 index 0000000000..d91daceb8a --- /dev/null +++ b/openstackclient/tests/network/v2/test_router.py @@ -0,0 +1,105 @@ +# 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 router +from openstackclient.tests.network.v2 import fakes as network_fakes + + +class TestRouter(network_fakes.TestNetworkV2): + + def setUp(self): + super(TestRouter, self).setUp() + + # Get a shortcut to the network client + self.network = self.app.client_manager.network + + +class TestListRouter(TestRouter): + + # The routers going to be listed up. + routers = network_fakes.FakeRouter.create_routers(count=3) + + columns = ( + 'ID', + 'Name', + 'Status', + 'State', + 'Distributed', + 'HA', + 'Project', + ) + columns_long = columns + ( + 'Routes', + 'External gateway info', + ) + + data = [] + for r in routers: + data.append(( + r.id, + r.name, + r.status, + router._format_admin_state(r.admin_state_up), + r.distributed, + r.ha, + r.tenant_id, + )) + data_long = [] + for i in range(0, len(routers)): + r = routers[i] + data_long.append( + data[i] + ( + r.routes, + router._format_external_gateway_info(r.external_gateway_info), + ) + ) + + def setUp(self): + super(TestListRouter, self).setUp() + + # Get the command object to test + self.cmd = router.ListRouter(self.app, self.namespace) + + self.network.routers = mock.Mock(return_value=self.routers) + + def test_router_list_no_options(self): + arglist = [] + verifylist = [ + ('long', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + self.network.routers.assert_called_with() + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_router_list_long(self): + arglist = [ + '--long', + ] + verifylist = [ + ('long', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + self.network.routers.assert_called_with() + self.assertEqual(self.columns_long, columns) + self.assertEqual(self.data_long, list(data)) diff --git a/setup.cfg b/setup.cfg index 27b00d45b1..01615a3380 100644 --- a/setup.cfg +++ b/setup.cfg @@ -332,6 +332,7 @@ openstack.network.v2 = network_list = openstackclient.network.v2.network:ListNetwork network_set = openstackclient.network.v2.network:SetNetwork network_show = openstackclient.network.v2.network:ShowNetwork + router_list = openstackclient.network.v2.router:ListRouter openstack.object_store.v1 = object_store_account_set = openstackclient.object.v1.account:SetAccount From 0a132d3256fd6a44c504efc19e8a5fba038c09be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Nov=C3=BD?= Date: Fri, 11 Dec 2015 23:11:58 +0100 Subject: [PATCH 0482/3095] Deprecated tox -downloadcache option removed Caching is enabled by default from pip version 6.0 More info: https://testrun.org/tox/latest/config.html#confval-downloadcache=path https://pip.pypa.io/en/stable/reference/pip_install/#caching Change-Id: I521b7cb11374f0600d1f6d4c6529e95aa29654b0 --- tox.ini | 3 --- 1 file changed, 3 deletions(-) diff --git a/tox.ini b/tox.ini index dd2b7b5412..cc1407f45c 100644 --- a/tox.ini +++ b/tox.ini @@ -27,9 +27,6 @@ commands = python setup.py test --coverage --testr-args='{posargs}' [testenv:debug] commands = oslo_debug_helper -t openstackclient/tests {posargs} -[tox:jenkins] -downloadcache = ~/cache/pip - [testenv:docs] commands = python setup.py build_sphinx From cb812322540db78a2412d32e94fc82ec09eb48c7 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Fri, 11 Dec 2015 22:53:43 +0000 Subject: [PATCH 0483/3095] Updated from global requirements Change-Id: Ie88d50a9c1539ad24e0f8dae8ee5155ad467a0c0 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f7113547c9..52b9420327 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ openstacksdk os-client-config!=1.6.2,>=1.4.0 oslo.config>=2.7.0 # Apache-2.0 oslo.i18n>=1.5.0 # Apache-2.0 -oslo.utils>=2.8.0 # Apache-2.0 +oslo.utils!=3.1.0,>=2.8.0 # Apache-2.0 python-glanceclient>=0.18.0 python-keystoneclient!=1.8.0,>=1.6.0 python-novaclient!=2.33.0,>=2.29.0 From 01ef42c8b4fbed1dfd54bf9ebed866e8d585ea75 Mon Sep 17 00:00:00 2001 From: "sonu.kumar" Date: Mon, 14 Dec 2015 13:04:02 +0530 Subject: [PATCH 0484/3095] Removes MANIFEST.in as it is not needed explicitely by PBR This patch removes `MANIFEST.in` file as pbr generates a sensible manifest from git files and some standard files and it removes the need for an explicit `MANIFEST.in` file. Change-Id: I78b28bf2fe60dc64cf6c3894da36f683a2e0bb30 --- MANIFEST.in | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 MANIFEST.in diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index aeabc0d31a..0000000000 --- a/MANIFEST.in +++ /dev/null @@ -1,14 +0,0 @@ -include AUTHORS -include babel.cfg -include ChangeLog -include LICENSE -include README.rst - -recursive-include doc * -recursive-include tests * -recursive-include python-openstackclient *.po *.pot - -exclude .gitignore -exclude .gitreview - -global-exclude *.pyc From 7b110511d2553e4096950032315e34085e4f311f Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Thu, 10 Dec 2015 13:30:21 -0600 Subject: [PATCH 0485/3095] Doc: Add optional command specs process The developer documentation has been updated to include an optional command specs process. This process may be used to work out the specifications for new commands, objects and actions before their implementation. This new process could assist in the implementation of additional neutron support in OSC. Change-Id: I62f7472435a9caacee0d1b4c8d35417c123b5a44 --- doc/source/index.rst | 1 + doc/source/specs/command-objects/example.rst | 86 ++++++++++++++++++++ doc/source/specs/commands.rst | 43 ++++++++++ 3 files changed, 130 insertions(+) create mode 100644 doc/source/specs/command-objects/example.rst create mode 100644 doc/source/specs/commands.rst diff --git a/doc/source/index.rst b/doc/source/index.rst index 49929972c3..bb0f0854a7 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -48,6 +48,7 @@ Developer Documentation developing command-options command-wrappers + specs/commands Project Goals ------------- diff --git a/doc/source/specs/command-objects/example.rst b/doc/source/specs/command-objects/example.rst new file mode 100644 index 0000000000..6f8b058844 --- /dev/null +++ b/doc/source/specs/command-objects/example.rst @@ -0,0 +1,86 @@ +======= +example +======= + +This is a specification for the ``example`` command object. It is not intended +to be a complete template for new commands since other actions, options +and/or arguments may be used. You can include general specification information +before the commands below. This information could include links to related material +or descriptions of similar commands. + +[example API name] [example API version] + +example create +-------------- + +Create new example + +.. program:: example create +.. code:: bash + + os example create + + +.. describe:: + + New example name + +example delete +-------------- + +Delete example(s) + +.. program:: example delete +.. code:: bash + + os example delete + [ ...] + +.. describe:: + + Example to delete (name or ID) + +example list +------------ + +List examples + +.. program:: example list +.. code:: bash + + os example list + +example set +----------- + +Set example properties + +.. program:: example set +.. code:: bash + + os example set + [--name ] + + +.. option:: --name + + New example name + +.. describe:: + + Example to modify (name or ID) + +example show +------------ + +Display example details + +.. program:: example show +.. code:: bash + + os example show + + +.. describe:: + + Example to display (name or ID) diff --git a/doc/source/specs/commands.rst b/doc/source/specs/commands.rst new file mode 100644 index 0000000000..55bf947651 --- /dev/null +++ b/doc/source/specs/commands.rst @@ -0,0 +1,43 @@ +============= +Command Specs +============= + +Specifications for new commands, objects and actions are listed below. +These specifications have not been implemented. See +:doc:`Command List <../command-list>` for implemented commands and +:doc:`Command Structure <../commands>` for implemented objects and actions. + +It is optional to propose a specifications patch for new commands, +objects and actions here before submitting the implementation. Once your +specifications patch merges then you may proceed with the implementation. +Your implementation patches should move applicable portions of the +specifications patch to the official :doc:`Command List <../command-list>` +and :doc:`Command Structure <../commands>` documentation. + +Objects Specs +------------- + +Add specifications for new objects based on the ``example`` object. + +* ``example``: (**example API name**) example object description + +Actions Specs +------------- + +Add specifications for new actions based on the ``example`` action. + +* ``example`` - example action description + +Commands Specs +-------------- + +Add specifications for new commands based on the commands for the +``example`` object. The ``example`` commands are not intended to +be a complete template for new commands since other actions, options +and/or arguments may be used. + +.. toctree:: + :glob: + :maxdepth: 2 + + command-objects/* From 74a6a81ae9d2c92575f4be531aa88fbd5dae9819 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Mon, 14 Dec 2015 03:30:00 -0500 Subject: [PATCH 0486/3095] when fetching object store properties use lower() sometimes properties within object store concepts are stored with mixed case depending on the client used to store said properties. when retrieving properties to 'show' the user, always call lower() on the property in question when comparing it to the reserved values of the swift API. Change-Id: I97ffc715788ca3cd021413124b6945a399465c99 Closes-Bug: 1525805 --- openstackclient/api/object_store_v1.py | 2 +- openstackclient/tests/api/test_object_store_v1.py | 5 ++++- releasenotes/notes/bug_1525805-122e6ce0c3cd4945.yaml | 5 +++++ 3 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/bug_1525805-122e6ce0c3cd4945.yaml diff --git a/openstackclient/api/object_store_v1.py b/openstackclient/api/object_store_v1.py index ab75a78c29..d9f130bcec 100644 --- a/openstackclient/api/object_store_v1.py +++ b/openstackclient/api/object_store_v1.py @@ -570,6 +570,6 @@ def _get_properties(self, headers, header_tag): # OSC commands properties = {} for k, v in six.iteritems(headers): - if k.startswith(header_tag): + if k.lower().startswith(header_tag): properties[k[len(header_tag):]] = v return properties diff --git a/openstackclient/tests/api/test_object_store_v1.py b/openstackclient/tests/api/test_object_store_v1.py index 992bf2617e..8cc3a92717 100644 --- a/openstackclient/tests/api/test_object_store_v1.py +++ b/openstackclient/tests/api/test_object_store_v1.py @@ -157,6 +157,7 @@ def test_container_show(self): 'container': 'qaz', 'object_count': '1', 'bytes_used': '577', + 'properties': {'Owner': FAKE_ACCOUNT}, } self.requests_mock.register_uri( 'HEAD', @@ -309,6 +310,7 @@ def test_object_show(self): 'etag': 'qaz', 'x-container-meta-owner': FAKE_ACCOUNT, 'x-object-meta-wife': 'Wilma', + 'x-object-meta-Husband': 'fred', 'x-tra-header': 'yabba-dabba-do', } resp = { @@ -319,7 +321,8 @@ def test_object_show(self): 'content-length': '577', 'last-modified': '20130101', 'etag': 'qaz', - 'properties': {'wife': 'Wilma'}, + 'properties': {'wife': 'Wilma', + 'Husband': 'fred'}, } self.requests_mock.register_uri( 'HEAD', diff --git a/releasenotes/notes/bug_1525805-122e6ce0c3cd4945.yaml b/releasenotes/notes/bug_1525805-122e6ce0c3cd4945.yaml new file mode 100644 index 0000000000..444b63781b --- /dev/null +++ b/releasenotes/notes/bug_1525805-122e6ce0c3cd4945.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Fix case sensitivity when showing object-store properties. + [Bug `1525805 `_] From 079123bb0b29fba19fa1b12d545493cbbd2d7024 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Thu, 3 Dec 2015 11:20:07 -0600 Subject: [PATCH 0487/3095] Remote security group name not displayed for rule The 'security group rule list' command was updated to display the remote security group name for a security group rule. This was done via a new 'Remote Security Group' column. The output of the 'security group rule create' and 'security group show' commands was also updated to include 'remote_security_group' information instead of the raw 'group' information returned from the API layer. Change-Id: I5f9600338c8331966d2c658109a24b502c538106 Closes-Bug: #1520003 --- openstackclient/compute/v2/security_group.py | 6 + .../compute/v2/test_security_group_rule.py | 109 +++++++++++++++--- .../notes/bug-1520003-505af921c8afffc9.yaml | 5 + 3 files changed, 102 insertions(+), 18 deletions(-) create mode 100644 releasenotes/notes/bug-1520003-505af921c8afffc9.yaml diff --git a/openstackclient/compute/v2/security_group.py b/openstackclient/compute/v2/security_group.py index 8844f5cce8..a514085b08 100644 --- a/openstackclient/compute/v2/security_group.py +++ b/openstackclient/compute/v2/security_group.py @@ -54,6 +54,11 @@ def _xform_security_group_rule(sgroup): info['ip_protocol'] = '' elif info['ip_protocol'].lower() == 'icmp': info['port_range'] = '' + group = info.pop('group') + if 'name' in group: + info['remote_security_group'] = group['name'] + else: + info['remote_security_group'] = '' return info @@ -299,6 +304,7 @@ def take_action(self, parsed_args): "IP Protocol", "IP Range", "Port Range", + "Remote Security Group", ) return (column_headers, (utils.get_item_properties( diff --git a/openstackclient/tests/compute/v2/test_security_group_rule.py b/openstackclient/tests/compute/v2/test_security_group_rule.py index cf540e021f..cfe3d46e59 100644 --- a/openstackclient/tests/compute/v2/test_security_group_rule.py +++ b/openstackclient/tests/compute/v2/test_security_group_rule.py @@ -28,13 +28,6 @@ security_group_rule_id = '1' security_group_rule_cidr = '0.0.0.0/0' -SECURITY_GROUP = { - 'id': security_group_id, - 'name': security_group_name, - 'description': security_group_description, - 'tenant_id': identity_fakes.project_id, -} - SECURITY_GROUP_RULE = { 'id': security_group_rule_id, 'group': {}, @@ -55,6 +48,26 @@ 'to_port': -1, } +SECURITY_GROUP_RULE_REMOTE_GROUP = { + 'id': security_group_rule_id, + 'group': {"tenant_id": "14", "name": "default"}, + 'ip_protocol': 'tcp', + 'ip_range': {}, + 'parent_group_id': security_group_id, + 'from_port': 80, + 'to_port': 80, +} + +SECURITY_GROUP = { + 'id': security_group_id, + 'name': security_group_name, + 'description': security_group_description, + 'tenant_id': identity_fakes.project_id, + 'rules': [SECURITY_GROUP_RULE, + SECURITY_GROUP_RULE_ICMP, + SECURITY_GROUP_RULE_REMOTE_GROUP], +} + class FakeSecurityGroupRuleResource(fakes.FakeResource): @@ -122,21 +135,21 @@ def test_security_group_rule_create_no_options(self): ) collist = ( - 'group', 'id', 'ip_protocol', 'ip_range', 'parent_group_id', 'port_range', + 'remote_security_group', ) self.assertEqual(collist, columns) datalist = ( - {}, security_group_rule_id, 'tcp', security_group_rule_cidr, security_group_id, '0:0', + '', ) self.assertEqual(datalist, data) @@ -174,21 +187,21 @@ def test_security_group_rule_create_ftp(self): ) collist = ( - 'group', 'id', 'ip_protocol', 'ip_range', 'parent_group_id', 'port_range', + 'remote_security_group', ) self.assertEqual(collist, columns) datalist = ( - {}, security_group_rule_id, 'tcp', security_group_rule_cidr, security_group_id, '20:21', + '', ) self.assertEqual(datalist, data) @@ -196,6 +209,7 @@ def test_security_group_rule_create_ssh(self): sg_rule = copy.deepcopy(SECURITY_GROUP_RULE) sg_rule['from_port'] = 22 sg_rule['to_port'] = 22 + sg_rule['ip_range'] = {} sg_rule['group'] = {'name': security_group_name} self.sg_rules_mock.create.return_value = FakeSecurityGroupRuleResource( None, @@ -229,21 +243,21 @@ def test_security_group_rule_create_ssh(self): ) collist = ( - 'group', 'id', 'ip_protocol', 'ip_range', 'parent_group_id', 'port_range', + 'remote_security_group', ) self.assertEqual(collist, columns) datalist = ( - {'name': security_group_name}, security_group_rule_id, 'tcp', - security_group_rule_cidr, + '', security_group_id, '22:22', + security_group_name, ) self.assertEqual(datalist, data) @@ -280,21 +294,21 @@ def test_security_group_rule_create_udp(self): ) collist = ( - 'group', 'id', 'ip_protocol', 'ip_range', 'parent_group_id', 'port_range', + 'remote_security_group', ) self.assertEqual(collist, columns) datalist = ( - {}, security_group_rule_id, 'udp', security_group_rule_cidr, security_group_id, '0:0', + '', ) self.assertEqual(datalist, data) @@ -334,21 +348,21 @@ def test_security_group_rule_create_icmp(self): ) collist = ( - 'group', 'id', 'ip_protocol', 'ip_range', 'parent_group_id', 'port_range', + 'remote_security_group', ) self.assertEqual(collist, columns) datalist = ( - {}, security_group_rule_id, 'icmp', sg_rule_cidr, security_group_id, '', + '', ) self.assertEqual(datalist, data) @@ -362,3 +376,62 @@ def test_security_group_rule_create_src_invalid(self): self.assertRaises(utils.ParserException, self.check_parser, self.cmd, arglist, []) + + +class TestSecurityGroupRuleList(TestSecurityGroupRule): + + def setUp(self): + super(TestSecurityGroupRuleList, self).setUp() + + self.secgroups_mock.get.return_value = FakeSecurityGroupRuleResource( + None, + copy.deepcopy(SECURITY_GROUP), + loaded=True, + ) + + # Get the command object to test + self.cmd = security_group.ListSecurityGroupRule(self.app, None) + + def test_security_group_rule_list(self): + + arglist = [ + security_group_name, + ] + verifylist = [ + ('group', security_group_name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + collist = ( + 'ID', + 'IP Protocol', + 'IP Range', + 'Port Range', + 'Remote Security Group', + ) + self.assertEqual(collist, columns) + datalist = (( + security_group_rule_id, + 'tcp', + security_group_rule_cidr, + '0:0', + '', + ), ( + security_group_rule_id, + 'icmp', + security_group_rule_cidr, + '', + '', + ), ( + security_group_rule_id, + 'tcp', + '', + '80:80', + 'default', + ), + ) + self.assertEqual(datalist, tuple(data)) diff --git a/releasenotes/notes/bug-1520003-505af921c8afffc9.yaml b/releasenotes/notes/bug-1520003-505af921c8afffc9.yaml new file mode 100644 index 0000000000..ca94b958f0 --- /dev/null +++ b/releasenotes/notes/bug-1520003-505af921c8afffc9.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Add remote security group to `os security group rule list` + [Bug `1520003 `_] From 6158ebb0e02ca2b796df973e71c6a7d5e829c959 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Mon, 14 Dec 2015 16:33:49 +0800 Subject: [PATCH 0488/3095] Router: Add "router create" command using SDK This patch adds "router create" command to osc using sdk. NOTE: Test for --project needs support for fake identity client v2 and v3. These tests will be added in other patches. NOTE: external_gateway_info and routes are not supported to be passed to create command now. They will be supported in another tow patches. NOTE: Creating a ha router is not supported for now. Will support it in another patch. Change-Id: I7642295d27c27dd498331ae1da1c293706d8f6af Implements: blueprint neutron-client Partial-bug: #1519503 --- doc/source/command-objects/router.rst | 40 ++++++++++ openstackclient/network/v2/router.py | 79 +++++++++++++++++++ openstackclient/tests/network/v2/fakes.py | 5 +- .../tests/network/v2/test_router.py | 62 +++++++++++++++ setup.cfg | 1 + 5 files changed, 186 insertions(+), 1 deletion(-) diff --git a/doc/source/command-objects/router.rst b/doc/source/command-objects/router.rst index f7b5a67e07..9e1e72911a 100644 --- a/doc/source/command-objects/router.rst +++ b/doc/source/command-objects/router.rst @@ -4,6 +4,46 @@ router Network v2 +router create +-------------- + +Create new router + +.. program:: router create +.. code:: bash + + os router create + [--project [--project-domain ]] + [--enable | --disable] + [--distributed] + + +.. option:: --project + + Owner's project (name or ID) + +.. option:: --project-domain + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + +.. option:: --enable + + Enable router (default) + +.. option:: --disable + + Disable router + +.. option:: --distributed + + Create a distributed router + +.. _router_create-name: +.. describe:: + + New router name + router list ----------- diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index cf5dae59f1..755bf10059 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -17,8 +17,10 @@ import logging from cliff import lister +from cliff import show from openstackclient.common import utils +from openstackclient.identity import common as identity_common def _format_admin_state(state): @@ -38,6 +40,83 @@ def _format_external_gateway_info(info): } +def _get_attrs(client_manager, parsed_args): + attrs = {} + if parsed_args.name is not None: + attrs['name'] = str(parsed_args.name) + if parsed_args.admin_state_up is not None: + attrs['admin_state_up'] = parsed_args.admin_state_up + if parsed_args.distributed is not None: + attrs['distributed'] = parsed_args.distributed + if 'project' in parsed_args and parsed_args.project is not None: + identity_client = client_manager.identity + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + attrs['tenant_id'] = project_id + return attrs + + +class CreateRouter(show.ShowOne): + """Create a new router""" + + log = logging.getLogger(__name__ + '.CreateRouter') + + def get_parser(self, prog_name): + parser = super(CreateRouter, self).get_parser(prog_name) + parser.add_argument( + 'name', + metavar='', + help="New router name", + ) + admin_group = parser.add_mutually_exclusive_group() + admin_group.add_argument( + '--enable', + dest='admin_state_up', + action='store_true', + default=True, + help="Enable router (default)", + ) + admin_group.add_argument( + '--disable', + dest='admin_state_up', + action='store_false', + help="Disable router", + ) + parser.add_argument( + '--distributed', + dest='distributed', + action='store_true', + default=False, + help="Create a distributed router", + ) + parser.add_argument( + '--project', + metavar='', + help="Owner's project (name or ID)", + ) + identity_common.add_project_domain_option_to_parser(parser) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + client = self.app.client_manager.network + + attrs = _get_attrs(self.app.client_manager, parsed_args) + obj = client.create_router(**attrs) + + columns = sorted(obj.keys()) + data = utils.get_item_properties(obj, columns, formatters=_formatters) + + if 'tenant_id' in columns: + # Rename "tenant_id" to "project_id". + index = columns.index('tenant_id') + columns[index] = 'project_id' + return (tuple(columns), data) + + class ListRouter(lister.Lister): """List routers""" diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 284a40b7a2..b45c5412f9 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -174,7 +174,10 @@ def create_one_router(attrs={}, methods={}): router_attrs.update(attrs) # Set default methods. - router_methods = {} + router_methods = { + 'keys': ['id', 'name', 'admin_state_up', 'distributed', 'ha', + 'tenant_id'], + } # Overwrite default methods. router_methods.update(methods) diff --git a/openstackclient/tests/network/v2/test_router.py b/openstackclient/tests/network/v2/test_router.py index d91daceb8a..5170826cbd 100644 --- a/openstackclient/tests/network/v2/test_router.py +++ b/openstackclient/tests/network/v2/test_router.py @@ -15,6 +15,7 @@ from openstackclient.network.v2 import router from openstackclient.tests.network.v2 import fakes as network_fakes +from openstackclient.tests import utils as tests_utils class TestRouter(network_fakes.TestNetworkV2): @@ -26,6 +27,67 @@ def setUp(self): self.network = self.app.client_manager.network +class TestCreateRouter(TestRouter): + + # The new router created. + new_router = network_fakes.FakeRouter.create_one_router() + + columns = ( + 'admin_state_up', + 'distributed', + 'ha', + 'id', + 'name', + 'project_id', + ) + data = ( + router._format_admin_state(new_router.admin_state_up), + new_router.distributed, + new_router.ha, + new_router.id, + new_router.name, + new_router.tenant_id, + ) + + def setUp(self): + super(TestCreateRouter, self).setUp() + + self.network.create_router = mock.Mock(return_value=self.new_router) + + # Get the command object to test + self.cmd = router.CreateRouter(self.app, self.namespace) + + def test_create_no_options(self): + arglist = [] + verifylist = [] + + try: + self.check_parser(self.cmd, arglist, verifylist) + except tests_utils.ParserException: + pass + + def test_create_default_options(self): + arglist = [ + self.new_router.name, + ] + verifylist = [ + ('name', self.new_router.name), + ('admin_state_up', True), + ('distributed', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_router.assert_called_with(**{ + 'admin_state_up': True, + 'name': self.new_router.name, + 'distributed': False, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + class TestListRouter(TestRouter): # The routers going to be listed up. diff --git a/setup.cfg b/setup.cfg index 01615a3380..535e4fdf95 100644 --- a/setup.cfg +++ b/setup.cfg @@ -332,6 +332,7 @@ openstack.network.v2 = network_list = openstackclient.network.v2.network:ListNetwork network_set = openstackclient.network.v2.network:SetNetwork network_show = openstackclient.network.v2.network:ShowNetwork + router_create = openstackclient.network.v2.router:CreateRouter router_list = openstackclient.network.v2.router:ListRouter openstack.object_store.v1 = From bd0bed746707c1b59eb659767d44d94666b15433 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Wed, 16 Dec 2015 14:32:20 +0800 Subject: [PATCH 0489/3095] Network: Improve no option test for "network create". Currently, test_create_no_options() case actually takes a 'name' arg. Rename it to test_create_default_options, and add a test_create_no_options case taking nothing. Change-Id: I4f9d2e8cbfa843faea641d3cc959f96894c9cd5d --- openstackclient/tests/network/v2/test_network.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index e86514927a..5a718686cf 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -93,6 +93,16 @@ def setUp(self): ) def test_create_no_options(self): + arglist = [] + verifylist = [] + + try: + # Missing required args should bail here + self.check_parser(self.cmd, arglist, verifylist) + except tests_utils.ParserException: + pass + + def test_create_default_options(self): arglist = [ self._network.name, ] From 3364855a2283b57ccd09ea6b98ca45aba5dbcccd Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Mon, 14 Dec 2015 20:54:03 +0800 Subject: [PATCH 0490/3095] Router: Add "router delete" command using SDK This patch adds "router delete" command to osc using sdk. Change-Id: I47d0ca7f7984942ffceaeb1c9ac69efd09145f40 Implements: blueprint neutron-client Partial-bug: #1519503 --- doc/source/command-objects/router.rst | 16 ++++++++++ openstackclient/network/v2/router.py | 25 ++++++++++++++++ .../tests/network/v2/test_router.py | 29 +++++++++++++++++++ setup.cfg | 1 + 4 files changed, 71 insertions(+) diff --git a/doc/source/command-objects/router.rst b/doc/source/command-objects/router.rst index 9e1e72911a..c3a952e9dd 100644 --- a/doc/source/command-objects/router.rst +++ b/doc/source/command-objects/router.rst @@ -44,6 +44,22 @@ Create new router New router name +router delete +-------------- + +Delete router(s) + +.. program:: router delete +.. code:: bash + + os router delete + [ ...] + +.. _router_delete-router: +.. describe:: + + Router(s) to delete (name or ID) + router list ----------- diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 755bf10059..0042e93f73 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -16,6 +16,7 @@ import json import logging +from cliff import command from cliff import lister from cliff import show @@ -117,6 +118,30 @@ def take_action(self, parsed_args): return (tuple(columns), data) +class DeleteRouter(command.Command): + """Delete router(s)""" + + log = logging.getLogger(__name__ + '.DeleteRouter') + + def get_parser(self, prog_name): + parser = super(DeleteRouter, self).get_parser(prog_name) + parser.add_argument( + 'router', + metavar="", + nargs="+", + help=("Router(s) to delete (name or ID)") + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + client = self.app.client_manager.network + for router in parsed_args.router: + obj = client.find_router(router) + client.delete_router(obj) + return + + class ListRouter(lister.Lister): """List routers""" diff --git a/openstackclient/tests/network/v2/test_router.py b/openstackclient/tests/network/v2/test_router.py index 5170826cbd..ccda9d2dc2 100644 --- a/openstackclient/tests/network/v2/test_router.py +++ b/openstackclient/tests/network/v2/test_router.py @@ -88,6 +88,35 @@ def test_create_default_options(self): self.assertEqual(self.data, data) +class TestDeleteRouter(TestRouter): + + # The router to delete. + _router = network_fakes.FakeRouter.create_one_router() + + def setUp(self): + super(TestDeleteRouter, self).setUp() + + self.network.delete_router = mock.Mock(return_value=None) + + self.network.find_router = mock.Mock(return_value=self._router) + + # Get the command object to test + self.cmd = router.DeleteRouter(self.app, self.namespace) + + def test_delete(self): + arglist = [ + self._router.name, + ] + verifylist = [ + ('router', [self._router.name]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.network.delete_router.assert_called_with(self._router) + self.assertEqual(None, result) + + class TestListRouter(TestRouter): # The routers going to be listed up. diff --git a/setup.cfg b/setup.cfg index 535e4fdf95..ee6192a72b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -333,6 +333,7 @@ openstack.network.v2 = network_set = openstackclient.network.v2.network:SetNetwork network_show = openstackclient.network.v2.network:ShowNetwork router_create = openstackclient.network.v2.router:CreateRouter + router_delete = openstackclient.network.v2.router:DeleteRouter router_list = openstackclient.network.v2.router:ListRouter openstack.object_store.v1 = From 1a3068d72997634a6370f3a907b83eddda71aa1c Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Tue, 15 Dec 2015 19:00:09 +0000 Subject: [PATCH 0491/3095] Updated from global requirements Change-Id: Ibef84b93179904c124fc493a51e4855512ddf755 --- requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 52b9420327..7451deff1f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,10 +11,10 @@ openstacksdk os-client-config!=1.6.2,>=1.4.0 oslo.config>=2.7.0 # Apache-2.0 oslo.i18n>=1.5.0 # Apache-2.0 -oslo.utils!=3.1.0,>=2.8.0 # Apache-2.0 -python-glanceclient>=0.18.0 +oslo.utils>=3.2.0 # Apache-2.0 +python-glanceclient>=1.2.0 python-keystoneclient!=1.8.0,>=1.6.0 python-novaclient!=2.33.0,>=2.29.0 python-cinderclient>=1.3.1 -requests>=2.8.1 +requests!=2.9.0,>=2.8.1 stevedore>=1.5.0 # Apache-2.0 From 556397aae721b240b6bd8eb971a18e669ad50c1d Mon Sep 17 00:00:00 2001 From: xiexs Date: Wed, 16 Dec 2015 09:25:38 +0800 Subject: [PATCH 0492/3095] Refactor TestImageCreate with FakeImage class Change-Id: I0044df36bb4d761c7998dfc8aa9a86d21d81da83 Implements: blueprint improve-image-unittest-framework --- openstackclient/tests/image/v2/fakes.py | 2 +- openstackclient/tests/image/v2/test_image.py | 115 ++++++++++--------- 2 files changed, 64 insertions(+), 53 deletions(-) diff --git a/openstackclient/tests/image/v2/fakes.py b/openstackclient/tests/image/v2/fakes.py index 692ef104de..5441a3e2ab 100644 --- a/openstackclient/tests/image/v2/fakes.py +++ b/openstackclient/tests/image/v2/fakes.py @@ -195,7 +195,7 @@ def create_one_image(attrs={}): 'owner': 'image-owner' + uuid.uuid4().hex, 'protected': bool(random.choice([0, 1])), 'visibility': random.choice(['public', 'private']), - 'tags': [uuid.uuid4().hex for r in range(random.randint(1, 5))], + 'tags': [uuid.uuid4().hex for r in range(2)], } # Overwrite default attributes if there are some attributes set diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index 10266f0223..cb92d495ab 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -55,18 +55,12 @@ class TestImageCreate(TestImage): def setUp(self): super(TestImageCreate, self).setUp() - self.images_mock.create.return_value = fakes.FakeResource( - None, - copy.deepcopy(image_fakes.IMAGE), - loaded=True, - ) + self.new_image = image_fakes.FakeImage.create_one_image() + self.images_mock.create.return_value = self.new_image # This is the return value for utils.find_resource() - self.images_mock.get.return_value = copy.deepcopy(image_fakes.IMAGE) - self.images_mock.update.return_value = fakes.FakeResource( - None, - copy.deepcopy(image_fakes.IMAGE), - loaded=True, - ) + self.images_mock.get.return_value = copy.deepcopy( + image_fakes.FakeImage.get_image_info(self.new_image)) + self.images_mock.update.return_value = self.new_image # Get the command object to test self.cmd = image.CreateImage(self.app, None) @@ -77,12 +71,12 @@ def test_image_reserve_no_options(self): } self.images_mock.configure_mock(**mock_exception) arglist = [ - image_fakes.image_name, + self.new_image.name ] verifylist = [ ('container_format', image.DEFAULT_CONTAINER_FORMAT), ('disk_format', image.DEFAULT_DISK_FORMAT), - ('name', image_fakes.image_name), + ('name', self.new_image.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -91,7 +85,7 @@ def test_image_reserve_no_options(self): # ImageManager.create(name=, **) self.images_mock.create.assert_called_with( - name=image_fakes.image_name, + name=self.new_image.name, container_format=image.DEFAULT_CONTAINER_FORMAT, disk_format=image.DEFAULT_DISK_FORMAT, ) @@ -103,8 +97,12 @@ def test_image_reserve_no_options(self): mock.ANY, mock.ANY, ) - self.assertEqual(image_fakes.IMAGE_columns, columns) - self.assertEqual(image_fakes.IMAGE_SHOW_data, data) + self.assertEqual( + image_fakes.FakeImage.get_image_columns(self.new_image), + columns) + self.assertEqual( + image_fakes.FakeImage.get_image_data(self.new_image), + data) @mock.patch('glanceclient.common.utils.get_data_file', name='Open') def test_image_reserve_options(self, mock_open): @@ -120,22 +118,24 @@ def test_image_reserve_options(self, mock_open): '--disk-format', 'fs', '--min-disk', '10', '--min-ram', '4', - '--owner', '123456', - '--protected', - '--private', - image_fakes.image_name, + '--owner', self.new_image.owner, + ('--protected' + if self.new_image.protected else '--unprotected'), + ('--private' + if self.new_image.visibility == 'private' else '--public'), + self.new_image.name, ] verifylist = [ ('container_format', 'ovf'), ('disk_format', 'fs'), ('min_disk', 10), ('min_ram', 4), - ('owner', '123456'), - ('protected', True), - ('unprotected', False), - ('public', False), - ('private', True), - ('name', image_fakes.image_name), + ('owner', self.new_image.owner), + ('protected', self.new_image.protected), + ('unprotected', not self.new_image.protected), + ('public', self.new_image.visibility == 'public'), + ('private', self.new_image.visibility == 'private'), + ('name', self.new_image.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -144,14 +144,14 @@ def test_image_reserve_options(self, mock_open): # ImageManager.create(name=, **) self.images_mock.create.assert_called_with( - name=image_fakes.image_name, + name=self.new_image.name, container_format='ovf', disk_format='fs', min_disk=10, min_ram=4, - owner='123456', - protected=True, - visibility='private', + owner=self.new_image.owner, + protected=self.new_image.protected, + visibility=self.new_image.visibility, ) # Verify update() was not called, if it was show the args @@ -161,14 +161,19 @@ def test_image_reserve_options(self, mock_open): mock.ANY, mock.ANY, ) - self.assertEqual(image_fakes.IMAGE_columns, columns) - self.assertEqual(image_fakes.IMAGE_SHOW_data, data) + self.assertEqual( + image_fakes.FakeImage.get_image_columns(self.new_image), + columns) + self.assertEqual( + image_fakes.FakeImage.get_image_data(self.new_image), + data) @mock.patch('glanceclient.common.utils.get_data_file', name='Open') def test_image_create_file(self, mock_open): mock_file = mock.MagicMock(name='File') mock_open.return_value = mock_file - mock_open.read.return_value = image_fakes.IMAGE_data + mock_open.read.return_value = ( + image_fakes.FakeImage.get_image_data(self.new_image)) mock_exception = { 'find.side_effect': exceptions.CommandError('x'), } @@ -176,23 +181,25 @@ def test_image_create_file(self, mock_open): arglist = [ '--file', 'filer', - '--unprotected', - '--public', + ('--unprotected' + if not self.new_image.protected else '--protected'), + ('--public' + if self.new_image.visibility == 'public' else '--private'), '--property', 'Alpha=1', '--property', 'Beta=2', - '--tag', 'awesome', - '--tag', 'better', - image_fakes.image_name, + '--tag', self.new_image.tags[0], + '--tag', self.new_image.tags[1], + self.new_image.name, ] verifylist = [ ('file', 'filer'), - ('protected', False), - ('unprotected', True), - ('public', True), - ('private', False), + ('protected', self.new_image.protected), + ('unprotected', not self.new_image.protected), + ('public', self.new_image.visibility == 'public'), + ('private', self.new_image.visibility == 'private'), ('properties', {'Alpha': '1', 'Beta': '2'}), - ('tags', ['awesome', 'better']), - ('name', image_fakes.image_name), + ('tags', self.new_image.tags), + ('name', self.new_image.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -201,14 +208,14 @@ def test_image_create_file(self, mock_open): # ImageManager.create(name=, **) self.images_mock.create.assert_called_with( - name=image_fakes.image_name, + name=self.new_image.name, container_format=image.DEFAULT_CONTAINER_FORMAT, disk_format=image.DEFAULT_DISK_FORMAT, - protected=False, - visibility='public', + protected=self.new_image.protected, + visibility=self.new_image.visibility, Alpha='1', Beta='2', - tags=['awesome', 'better'], + tags=self.new_image.tags, ) # Verify update() was not called, if it was show the args @@ -218,17 +225,21 @@ def test_image_create_file(self, mock_open): mock.ANY, mock.ANY, ) - self.assertEqual(image_fakes.IMAGE_columns, columns) - self.assertEqual(image_fakes.IMAGE_SHOW_data, data) + self.assertEqual( + image_fakes.FakeImage.get_image_columns(self.new_image), + columns) + self.assertEqual( + image_fakes.FakeImage.get_image_data(self.new_image), + data) def test_image_create_dead_options(self): arglist = [ '--store', 'somewhere', - image_fakes.image_name, + self.new_image.name, ] verifylist = [ - ('name', image_fakes.image_name), + ('name', self.new_image.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) From 8a805dae0cc0ac3391c970a84b540a85cce24274 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 15 Dec 2015 10:04:07 +0800 Subject: [PATCH 0493/3095] Router: Add "router set" command using SDK This patch adds "router set" command to osc using sdk. NOTE: Setting ha property of a router is not supported for now. Will suppport it in another patch. Change-Id: I9c15249ae61a87291f0728ad1c8f0a98aa8119bf Implements: blueprint neutron-client Partial-bug: #1519503 --- doc/source/command-objects/router.rst | 39 ++++++++ openstackclient/network/v2/router.py | 78 ++++++++++++++++ .../tests/network/v2/test_router.py | 90 +++++++++++++++++++ setup.cfg | 1 + 4 files changed, 208 insertions(+) diff --git a/doc/source/command-objects/router.rst b/doc/source/command-objects/router.rst index c3a952e9dd..952415a8ac 100644 --- a/doc/source/command-objects/router.rst +++ b/doc/source/command-objects/router.rst @@ -74,3 +74,42 @@ List routers .. option:: --long List additional fields in output + +router set +---------- + +Set router properties + +.. program:: router set +.. code:: bash + + os router set + [--name ] + [--enable | --disable] + [--distributed | --centralized] + + +.. option:: --name + + Set router name + +.. option:: --enable + + Enable router + +.. option:: --disable + + Disable router + +.. option:: --distributed + + Set router to distributed mode (disabled router only) + +.. option:: --centralized + + Set router to centralized mode (disabled router only) + +.. _router_set-router: +.. describe:: + + Router to modify (name or ID) diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 0042e93f73..d084db1f75 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -20,6 +20,7 @@ from cliff import lister from cliff import show +from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.identity import common as identity_common @@ -49,6 +50,7 @@ def _get_attrs(client_manager, parsed_args): attrs['admin_state_up'] = parsed_args.admin_state_up if parsed_args.distributed is not None: attrs['distributed'] = parsed_args.distributed + # "router set" command doesn't support setting project. if 'project' in parsed_args and parsed_args.project is not None: identity_client = client_manager.identity project_id = identity_common.find_project( @@ -57,6 +59,11 @@ def _get_attrs(client_manager, parsed_args): parsed_args.project_domain, ).id attrs['tenant_id'] = project_id + + # TODO(tangchen): Support getting 'ha' property. + # TODO(tangchen): Support getting 'external_gateway_info' property. + # TODO(tangchen): Support getting 'routes' property. + return attrs @@ -195,3 +202,74 @@ def take_action(self, parsed_args): s, columns, formatters=_formatters, ) for s in data)) + + +class SetRouter(command.Command): + """Set router properties""" + + log = logging.getLogger(__name__ + '.SetRouter') + + def get_parser(self, prog_name): + parser = super(SetRouter, self).get_parser(prog_name) + parser.add_argument( + 'router', + metavar="", + help=("Router to modify (name or ID)") + ) + parser.add_argument( + '--name', + metavar='', + help='Set router name', + ) + admin_group = parser.add_mutually_exclusive_group() + admin_group.add_argument( + '--enable', + dest='admin_state_up', + action='store_true', + default=None, + help='Enable router', + ) + admin_group.add_argument( + '--disable', + dest='admin_state_up', + action='store_false', + help='Disable router', + ) + distribute_group = parser.add_mutually_exclusive_group() + distribute_group.add_argument( + '--distributed', + dest='distributed', + action='store_true', + default=None, + help="Set router to distributed mode (disabled router only)", + ) + distribute_group.add_argument( + '--centralized', + dest='distributed', + action='store_false', + help="Set router to centralized mode (disabled router only)", + ) + + # TODO(tangchen): Support setting 'ha' property in 'router set' + # command. It appears that changing the ha state is supported by + # neutron under certain conditions. + + # TODO(tangchen): Support setting 'external_gateway_info' property in + # 'router set' command. + + # TODO(tangchen): Support setting 'routes' property in 'router set' + # command. + + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + client = self.app.client_manager.network + obj = client.find_router(parsed_args.router, ignore_missing=False) + + attrs = _get_attrs(self.app.client_manager, parsed_args) + if attrs == {}: + msg = "Nothing specified to be set" + raise exceptions.CommandError(msg) + + client.update_router(obj, **attrs) diff --git a/openstackclient/tests/network/v2/test_router.py b/openstackclient/tests/network/v2/test_router.py index ccda9d2dc2..e40d2d7105 100644 --- a/openstackclient/tests/network/v2/test_router.py +++ b/openstackclient/tests/network/v2/test_router.py @@ -13,6 +13,7 @@ import mock +from openstackclient.common import exceptions from openstackclient.network.v2 import router from openstackclient.tests.network.v2 import fakes as network_fakes from openstackclient.tests import utils as tests_utils @@ -194,3 +195,92 @@ def test_router_list_long(self): self.network.routers.assert_called_with() self.assertEqual(self.columns_long, columns) self.assertEqual(self.data_long, list(data)) + + +class TestSetRouter(TestRouter): + + # The router to set. + _router = network_fakes.FakeRouter.create_one_router() + + def setUp(self): + super(TestSetRouter, self).setUp() + + self.network.update_router = mock.Mock(return_value=None) + + self.network.find_router = mock.Mock(return_value=self._router) + + # Get the command object to test + self.cmd = router.SetRouter(self.app, self.namespace) + + def test_set_this(self): + arglist = [ + self._router.name, + '--enable', + '--distributed', + '--name', 'noob', + ] + verifylist = [ + ('router', self._router.name), + ('admin_state_up', True), + ('distributed', True), + ('name', 'noob'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = { + 'admin_state_up': True, + 'distributed': True, + 'name': 'noob', + } + self.network.update_router.assert_called_with(self._router, **attrs) + self.assertEqual(None, result) + + def test_set_that(self): + arglist = [ + self._router.name, + '--disable', + '--centralized', + ] + verifylist = [ + ('router', self._router.name), + ('admin_state_up', False), + ('distributed', False), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = { + 'admin_state_up': False, + 'distributed': False, + } + self.network.update_router.assert_called_with(self._router, **attrs) + self.assertEqual(None, result) + + def test_set_distributed_centralized(self): + arglist = [ + self._router.name, + '--distributed', + '--centralized', + ] + verifylist = [ + ('router', self._router.name), + ('distributed', True), + ('distributed', False), + ] + + try: + # Argument parse failing should bail here + self.check_parser(self.cmd, arglist, verifylist) + except tests_utils.ParserException: + pass + + def test_set_nothing(self): + arglist = [self._router.name, ] + verifylist = [('router', self._router.name), ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) diff --git a/setup.cfg b/setup.cfg index ee6192a72b..d55dd1c989 100644 --- a/setup.cfg +++ b/setup.cfg @@ -335,6 +335,7 @@ openstack.network.v2 = router_create = openstackclient.network.v2.router:CreateRouter router_delete = openstackclient.network.v2.router:DeleteRouter router_list = openstackclient.network.v2.router:ListRouter + router_set = openstackclient.network.v2.router:SetRouter openstack.object_store.v1 = object_store_account_set = openstackclient.object.v1.account:SetAccount From 408dc7b2904f5d60061da07c57cbaeaf1b96c719 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 15 Dec 2015 17:28:06 +0800 Subject: [PATCH 0494/3095] Router: Add "router show" command using SDK This patch adds "router show" command to osc using sdk. Change-Id: Idb0f7f0376926e97f9f70a52ef21511e7ffa9d92 Implements: blueprint neutron-client Partial-bug: #1519503 --- doc/source/command-objects/router.rst | 20 ++++++- openstackclient/network/v2/router.py | 23 ++++++++ .../tests/network/v2/test_router.py | 58 +++++++++++++++++++ setup.cfg | 1 + 4 files changed, 100 insertions(+), 2 deletions(-) diff --git a/doc/source/command-objects/router.rst b/doc/source/command-objects/router.rst index 952415a8ac..e881065dd4 100644 --- a/doc/source/command-objects/router.rst +++ b/doc/source/command-objects/router.rst @@ -5,7 +5,7 @@ router Network v2 router create --------------- +------------- Create new router @@ -45,7 +45,7 @@ Create new router New router name router delete --------------- +------------- Delete router(s) @@ -113,3 +113,19 @@ Set router properties .. describe:: Router to modify (name or ID) + +router show +----------- + +Display router details + +.. program:: router show +.. code:: bash + + os router show + + +.. _router_show-router: +.. describe:: + + Router to display (name or ID) diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index d084db1f75..5ad7ccd284 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -273,3 +273,26 @@ def take_action(self, parsed_args): raise exceptions.CommandError(msg) client.update_router(obj, **attrs) + + +class ShowRouter(show.ShowOne): + """Display router details""" + + log = logging.getLogger(__name__ + '.ShowRouter') + + def get_parser(self, prog_name): + parser = super(ShowRouter, self).get_parser(prog_name) + parser.add_argument( + 'router', + metavar="", + help="Router to display (name or ID)" + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + client = self.app.client_manager.network + obj = client.find_router(parsed_args.router, ignore_missing=False) + columns = sorted(obj.keys()) + data = utils.get_item_properties(obj, columns, formatters=_formatters) + return (tuple(columns), data) diff --git a/openstackclient/tests/network/v2/test_router.py b/openstackclient/tests/network/v2/test_router.py index e40d2d7105..d483be85dd 100644 --- a/openstackclient/tests/network/v2/test_router.py +++ b/openstackclient/tests/network/v2/test_router.py @@ -284,3 +284,61 @@ def test_set_nothing(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) + + +class TestShowRouter(TestRouter): + + # The router to set. + _router = network_fakes.FakeRouter.create_one_router() + + columns = ( + 'admin_state_up', + 'distributed', + 'ha', + 'id', + 'name', + 'tenant_id', + ) + + data = ( + router._format_admin_state(_router.admin_state_up), + _router.distributed, + _router.ha, + _router.id, + _router.name, + _router.tenant_id, + ) + + def setUp(self): + super(TestShowRouter, self).setUp() + + self.network.find_router = mock.Mock(return_value=self._router) + + # Get the command object to test + self.cmd = router.ShowRouter(self.app, self.namespace) + + def test_show_no_options(self): + arglist = [] + verifylist = [] + + try: + # Missing required args should bail here + self.check_parser(self.cmd, arglist, verifylist) + except tests_utils.ParserException: + pass + + def test_show_all_options(self): + arglist = [ + self._router.name, + ] + verifylist = [ + ('router', self._router.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.find_router.assert_called_with(self._router.name, + ignore_missing=False) + self.assertEqual(tuple(self.columns), columns) + self.assertEqual(self.data, data) diff --git a/setup.cfg b/setup.cfg index d55dd1c989..7f54fb1fb0 100644 --- a/setup.cfg +++ b/setup.cfg @@ -336,6 +336,7 @@ openstack.network.v2 = router_delete = openstackclient.network.v2.router:DeleteRouter router_list = openstackclient.network.v2.router:ListRouter router_set = openstackclient.network.v2.router:SetRouter + router_show = openstackclient.network.v2.router:ShowRouter openstack.object_store.v1 = object_store_account_set = openstackclient.object.v1.account:SetAccount From b3943d714275b276a973de9c52307e82c90be8bc Mon Sep 17 00:00:00 2001 From: NiallBunting Date: Mon, 14 Dec 2015 14:28:45 +0000 Subject: [PATCH 0495/3095] Add image re/deactivate commands This change allows admins to deactivate and reactivate their images. Currently this has to be done with the REST api or the glanceclient. This change introduces `--deactivate` and `--activate` for the `image set` command. This requires glanceclient 1.2.0. Which got bumped here: https://review.openstack.org/#/c/257512/ Change-Id: I476c44a0343cdc92d58ddc93fb06470242de2345 Depends-On: I2c370c6bf6ff664d94d756cc76aaa983fbdb8869 Closes-Bug: 1516661 --- doc/source/command-objects/image.rst | 13 +++++ openstackclient/image/v2/image.py | 32 ++++++++++- openstackclient/tests/image/v2/test_image.py | 58 ++++++++++++++++++++ 3 files changed, 101 insertions(+), 2 deletions(-) diff --git a/doc/source/command-objects/image.rst b/doc/source/command-objects/image.rst index d4b9916221..6a4782ea5e 100644 --- a/doc/source/command-objects/image.rst +++ b/doc/source/command-objects/image.rst @@ -243,6 +243,7 @@ Set image properties [--os-distro ] [--os-version ] [--ramdisk-id ] + [--activate|--deactivate] .. option:: --name @@ -387,6 +388,18 @@ Set image properties .. versionadded:: 2 +.. option:: --activate + + Activate the image. + + .. versionadded:: 2 + +.. option:: --deactivate + + Deactivate the image. + + .. versionadded:: 2 + .. describe:: Image to modify (name or ID) diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index a3c1a99d17..1fcb92d9a5 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -693,6 +693,17 @@ def get_parser(self, prog_name): metavar="", help="ID of ramdisk image used to boot this disk image", ) + deactivate_group = parser.add_mutually_exclusive_group() + deactivate_group.add_argument( + "--deactivate", + action="store_true", + help="Deactivate the image", + ) + deactivate_group.add_argument( + "--activate", + action="store_true", + help="Activate the image", + ) for deadopt in self.deadopts: parser.add_argument( "--%s" % deadopt, @@ -745,18 +756,35 @@ def take_action(self, parsed_args): if parsed_args.private: kwargs['visibility'] = 'private' - if not kwargs: + # Checks if anything that requires getting the image + if not (kwargs or parsed_args.deactivate or parsed_args.activate): self.log.warning("No arguments specified") return {}, {} image = utils.find_resource( image_client.images, parsed_args.image) + if parsed_args.deactivate: + image_client.images.deactivate(image.id) + activation_status = "deactivated" + if parsed_args.activate: + image_client.images.reactivate(image.id) + activation_status = "activated" + + # Check if need to do the actual update + if not kwargs: + return {}, {} + if parsed_args.tags: # Tags should be extended, but duplicates removed kwargs['tags'] = list(set(image.tags).union(set(parsed_args.tags))) - image = image_client.images.update(image.id, **kwargs) + try: + image = image_client.images.update(image.id, **kwargs) + except Exception as e: + if activation_status is not None: + print("Image %s was %s." % (image.id, activation_status)) + raise e class ShowImage(show.ShowOne): diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index be73c4ca9c..71e1a77b0a 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -838,6 +838,64 @@ def test_image_set_tag(self): **kwargs ) + def test_image_set_activate(self): + arglist = [ + '--tag', 'test-tag', + '--activate', + image_fakes.image_name, + ] + verifylist = [ + ('tags', ['test-tag']), + ('image', image_fakes.image_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + self.cmd.take_action(parsed_args) + + kwargs = { + 'tags': ['test-tag'], + } + + self.images_mock.reactivate.assert_called_with( + image_fakes.image_id, + ) + + # ImageManager.update(image, **kwargs) + self.images_mock.update.assert_called_with( + image_fakes.image_id, + **kwargs + ) + + def test_image_set_deactivate(self): + arglist = [ + '--tag', 'test-tag', + '--deactivate', + image_fakes.image_name, + ] + verifylist = [ + ('tags', ['test-tag']), + ('image', image_fakes.image_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + self.cmd.take_action(parsed_args) + + kwargs = { + 'tags': ['test-tag'], + } + + self.images_mock.deactivate.assert_called_with( + image_fakes.image_id, + ) + + # ImageManager.update(image, **kwargs) + self.images_mock.update.assert_called_with( + image_fakes.image_id, + **kwargs + ) + def test_image_set_tag_merge(self): old_image = copy.copy(image_fakes.IMAGE) old_image['tags'] = ['old1', 'new2'] From 494659fbe432795c846a4e4e59e7faf4a25b6dca Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Thu, 17 Dec 2015 09:34:12 +0800 Subject: [PATCH 0496/3095] Trivial: Remove useless return from files in compute. Change-Id: I9dc6749256fcd53d292d7f658912c032e9ce9df5 --- openstackclient/compute/v2/agent.py | 1 - openstackclient/compute/v2/aggregate.py | 1 - openstackclient/compute/v2/console.py | 1 - openstackclient/compute/v2/fixedip.py | 2 -- openstackclient/compute/v2/flavor.py | 1 - openstackclient/compute/v2/floatingip.py | 3 --- openstackclient/compute/v2/keypair.py | 1 - openstackclient/compute/v2/security_group.py | 2 -- openstackclient/compute/v2/server.py | 2 -- openstackclient/compute/v2/service.py | 1 - 10 files changed, 15 deletions(-) diff --git a/openstackclient/compute/v2/agent.py b/openstackclient/compute/v2/agent.py index 14c4b2c7f5..75c67f3115 100644 --- a/openstackclient/compute/v2/agent.py +++ b/openstackclient/compute/v2/agent.py @@ -91,7 +91,6 @@ def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) compute_client = self.app.client_manager.compute compute_client.agents.delete(parsed_args.id) - return class ListAgent(lister.Lister): diff --git a/openstackclient/compute/v2/aggregate.py b/openstackclient/compute/v2/aggregate.py index a1ba618fda..97ad127cda 100644 --- a/openstackclient/compute/v2/aggregate.py +++ b/openstackclient/compute/v2/aggregate.py @@ -132,7 +132,6 @@ def take_action(self, parsed_args): parsed_args.aggregate, ) compute_client.aggregates.delete(data.id) - return class ListAggregate(lister.Lister): diff --git a/openstackclient/compute/v2/console.py b/openstackclient/compute/v2/console.py index aafa5d441c..f086478622 100644 --- a/openstackclient/compute/v2/console.py +++ b/openstackclient/compute/v2/console.py @@ -65,7 +65,6 @@ def take_action(self, parsed_args): data = server.get_console_output(length=length) sys.stdout.write(data) - return class ShowConsoleURL(show.ShowOne): diff --git a/openstackclient/compute/v2/fixedip.py b/openstackclient/compute/v2/fixedip.py index da9d85c331..1ab8a18c49 100644 --- a/openstackclient/compute/v2/fixedip.py +++ b/openstackclient/compute/v2/fixedip.py @@ -52,7 +52,6 @@ def take_action(self, parsed_args): compute_client.servers, parsed_args.server) server.add_fixed_ip(network.id) - return class RemoveFixedIP(command.Command): @@ -82,4 +81,3 @@ def take_action(self, parsed_args): compute_client.servers, parsed_args.server) server.remove_fixed_ip(parsed_args.ip_address) - return diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 7474580b60..b34197e049 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -145,7 +145,6 @@ def take_action(self, parsed_args): flavor = utils.find_resource(compute_client.flavors, parsed_args.flavor) compute_client.flavors.delete(flavor.id) - return class ListFlavor(lister.Lister): diff --git a/openstackclient/compute/v2/floatingip.py b/openstackclient/compute/v2/floatingip.py index 65fe5910ba..6f2e360cf8 100644 --- a/openstackclient/compute/v2/floatingip.py +++ b/openstackclient/compute/v2/floatingip.py @@ -52,7 +52,6 @@ def take_action(self, parsed_args): compute_client.servers, parsed_args.server) server.add_floating_ip(parsed_args.ip_address) - return class CreateFloatingIP(show.ShowOne): @@ -103,7 +102,6 @@ def take_action(self, parsed_args): ) compute_client.floating_ips.delete(floating_ip) - return class ListFloatingIP(lister.Lister): @@ -153,4 +151,3 @@ def take_action(self, parsed_args): compute_client.servers, parsed_args.server) server.remove_floating_ip(parsed_args.ip_address) - return diff --git a/openstackclient/compute/v2/keypair.py b/openstackclient/compute/v2/keypair.py index 5c627c5082..965629858a 100644 --- a/openstackclient/compute/v2/keypair.py +++ b/openstackclient/compute/v2/keypair.py @@ -97,7 +97,6 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): compute_client = self.app.client_manager.compute compute_client.keypairs.delete(parsed_args.name) - return class ListKeypair(lister.Lister): diff --git a/openstackclient/compute/v2/security_group.py b/openstackclient/compute/v2/security_group.py index 6d38195cf5..374b2fbd89 100644 --- a/openstackclient/compute/v2/security_group.py +++ b/openstackclient/compute/v2/security_group.py @@ -174,7 +174,6 @@ def take_action(self, parsed_args): parsed_args.group, ) compute_client.security_groups.delete(data.id) - return class DeleteSecurityGroupRule(command.Command): @@ -196,7 +195,6 @@ def take_action(self, parsed_args): compute_client = self.app.client_manager.compute compute_client.security_group_rules.delete(parsed_args.rule) - return class ListSecurityGroup(lister.Lister): diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 9e2721fb67..be0ad8cb5a 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -204,7 +204,6 @@ def take_action(self, parsed_args): ) server.add_security_group(security_group.name) - return class AddServerVolume(command.Command): @@ -665,7 +664,6 @@ def take_action(self, parsed_args): server_obj.id) sys.stdout.write(_('\nError deleting server')) raise SystemExit - return class ListServer(lister.Lister): diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index c2d51c2aa3..dd32eb1289 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.py @@ -41,7 +41,6 @@ def take_action(self, parsed_args): compute_client = self.app.client_manager.compute compute_client.services.delete(parsed_args.service) - return class ListService(lister.Lister): From fe7d2d12c0f2e5d9bbd089b09977a841b126deab Mon Sep 17 00:00:00 2001 From: SaiKiran Date: Thu, 17 Dec 2015 16:37:00 +0530 Subject: [PATCH 0497/3095] Replace assertEqual(None, *) with assertIsNone in tests In python-openstackclient some test cases using asserEqual(None, *) instead of assertIsNone(). assertIsNone method provides clear error message. Change-Id: I3069a6436d11efa513ae94f21ceab46c498d6e25 Closes-Bug: #1527054 --- openstackclient/tests/identity/v3/test_identity_provider.py | 4 ++-- openstackclient/tests/network/v2/test_network.py | 6 +++--- openstackclient/tests/network/v2/test_router.py | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/openstackclient/tests/identity/v3/test_identity_provider.py b/openstackclient/tests/identity/v3/test_identity_provider.py index db097d5df9..36358be315 100644 --- a/openstackclient/tests/identity/v3/test_identity_provider.py +++ b/openstackclient/tests/identity/v3/test_identity_provider.py @@ -638,5 +638,5 @@ def prepare(self): # expect take_action() to return (None, None) as # neither --enable nor --disable was specified - self.assertEqual(None, columns) - self.assertEqual(None, data) + self.assertIsNone(columns) + self.assertIsNone(data) diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index 5a718686cf..f22224b650 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -301,7 +301,7 @@ def test_delete(self): result = self.cmd.take_action(parsed_args) self.network.delete_network.assert_called_with(self._network) - self.assertEqual(None, result) + self.assertIsNone(result) class TestListNetwork(TestNetwork): @@ -443,7 +443,7 @@ def test_set_this(self): result = self.cmd.take_action(parsed_args) self.network.update_network.assert_called_with(self._network) - self.assertEqual(None, result) + self.assertIsNone(result) def test_set_that(self): self._network.is_dirty = True @@ -463,7 +463,7 @@ def test_set_that(self): result = self.cmd.take_action(parsed_args) self.network.update_network.assert_called_with(self._network) - self.assertEqual(None, result) + self.assertIsNone(result) def test_set_nothing(self): self._network.is_dirty = False diff --git a/openstackclient/tests/network/v2/test_router.py b/openstackclient/tests/network/v2/test_router.py index d483be85dd..fba6e192c5 100644 --- a/openstackclient/tests/network/v2/test_router.py +++ b/openstackclient/tests/network/v2/test_router.py @@ -115,7 +115,7 @@ def test_delete(self): result = self.cmd.take_action(parsed_args) self.network.delete_router.assert_called_with(self._router) - self.assertEqual(None, result) + self.assertIsNone(result) class TestListRouter(TestRouter): @@ -235,7 +235,7 @@ def test_set_this(self): 'name': 'noob', } self.network.update_router.assert_called_with(self._router, **attrs) - self.assertEqual(None, result) + self.assertIsNone(result) def test_set_that(self): arglist = [ @@ -257,7 +257,7 @@ def test_set_that(self): 'distributed': False, } self.network.update_router.assert_called_with(self._router, **attrs) - self.assertEqual(None, result) + self.assertIsNone(result) def test_set_distributed_centralized(self): arglist = [ From 96cc5eb3540e4753a8862020b2f6e78a465be4e2 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Wed, 16 Dec 2015 16:01:40 -0600 Subject: [PATCH 0498/3095] Add support to list all security group rules Both nova and neutron allow security group rules to be listed without specifying the owning security group. This patch set makes the group argument on 'os security group rule list' optional. Behavior is unchanged when the argument is specified. When the argument is not specified then all accessible security group rules will be listed. The listing will include the owning security group for each rule. Change-Id: I6914baecf70a65354e1e82dad92c6afbd32b4973 Related-Bug: #1519512 --- .../command-objects/security-group-rule.rst | 2 +- openstackclient/compute/v2/security_group.py | 32 ++++--- .../compute/v2/test_security_group_rule.py | 86 ++++++++++++++++++- 3 files changed, 104 insertions(+), 16 deletions(-) diff --git a/doc/source/command-objects/security-group-rule.rst b/doc/source/command-objects/security-group-rule.rst index ce2e4d346f..ec03644e9f 100644 --- a/doc/source/command-objects/security-group-rule.rst +++ b/doc/source/command-objects/security-group-rule.rst @@ -62,7 +62,7 @@ List security group rules .. code:: bash os security group rule list - + [] .. describe:: diff --git a/openstackclient/compute/v2/security_group.py b/openstackclient/compute/v2/security_group.py index a514085b08..42581d5578 100644 --- a/openstackclient/compute/v2/security_group.py +++ b/openstackclient/compute/v2/security_group.py @@ -278,6 +278,7 @@ def get_parser(self, prog_name): parser.add_argument( 'group', metavar='', + nargs='?', help='List all rules in this security group (name or ID)', ) return parser @@ -286,26 +287,35 @@ def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) compute_client = self.app.client_manager.compute - group = utils.find_resource( - compute_client.security_groups, - parsed_args.group, + columns = column_headers = ( + "ID", + "IP Protocol", + "IP Range", + "Port Range", + "Remote Security Group", ) + rules_to_list = [] + if parsed_args.group: + group = utils.find_resource( + compute_client.security_groups, + parsed_args.group, + ) + rules_to_list = group.rules + else: + columns = columns + ('parent_group_id',) + column_headers = column_headers + ('Security Group',) + for group in compute_client.security_groups.list(): + rules_to_list.extend(group.rules) + # Argh, the rules are not Resources... rules = [] - for rule in group.rules: + for rule in rules_to_list: rules.append(security_group_rules.SecurityGroupRule( compute_client.security_group_rules, _xform_security_group_rule(rule), )) - columns = column_headers = ( - "ID", - "IP Protocol", - "IP Range", - "Port Range", - "Remote Security Group", - ) return (column_headers, (utils.get_item_properties( s, columns, diff --git a/openstackclient/tests/compute/v2/test_security_group_rule.py b/openstackclient/tests/compute/v2/test_security_group_rule.py index cfe3d46e59..0e7ee05d87 100644 --- a/openstackclient/tests/compute/v2/test_security_group_rule.py +++ b/openstackclient/tests/compute/v2/test_security_group_rule.py @@ -68,6 +68,28 @@ SECURITY_GROUP_RULE_REMOTE_GROUP], } +security_group_2_id = '12' +security_group_2_name = 'he-shoots' +security_group_2_description = 'he scores' + +SECURITY_GROUP_2_RULE = { + 'id': '2', + 'group': {}, + 'ip_protocol': 'tcp', + 'ip_range': {}, + 'parent_group_id': security_group_2_id, + 'from_port': 80, + 'to_port': 80, +} + +SECURITY_GROUP_2 = { + 'id': security_group_2_id, + 'name': security_group_2_name, + 'description': security_group_2_description, + 'tenant_id': identity_fakes.project_id, + 'rules': [SECURITY_GROUP_2_RULE], +} + class FakeSecurityGroupRuleResource(fakes.FakeResource): @@ -383,12 +405,22 @@ class TestSecurityGroupRuleList(TestSecurityGroupRule): def setUp(self): super(TestSecurityGroupRuleList, self).setUp() - self.secgroups_mock.get.return_value = FakeSecurityGroupRuleResource( + security_group_mock = FakeSecurityGroupRuleResource( None, copy.deepcopy(SECURITY_GROUP), loaded=True, ) + security_group_2_mock = FakeSecurityGroupRuleResource( + None, + copy.deepcopy(SECURITY_GROUP_2), + loaded=True, + ) + + self.secgroups_mock.get.return_value = security_group_mock + self.secgroups_mock.list.return_value = [security_group_mock, + security_group_2_mock] + # Get the command object to test self.cmd = security_group.ListSecurityGroupRule(self.app, None) @@ -420,18 +452,64 @@ def test_security_group_rule_list(self): security_group_rule_cidr, '0:0', '', - ), ( + ), ( security_group_rule_id, 'icmp', security_group_rule_cidr, '', '', - ), ( + ), ( security_group_rule_id, 'tcp', '', '80:80', 'default', - ), + ),) + self.assertEqual(datalist, tuple(data)) + + def test_security_group_rule_list_no_group(self): + + parsed_args = self.check_parser(self.cmd, [], []) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + collist = ( + 'ID', + 'IP Protocol', + 'IP Range', + 'Port Range', + 'Remote Security Group', + 'Security Group', ) + self.assertEqual(collist, columns) + datalist = (( + security_group_rule_id, + 'tcp', + security_group_rule_cidr, + '0:0', + '', + security_group_id, + ), ( + security_group_rule_id, + 'icmp', + security_group_rule_cidr, + '', + '', + security_group_id, + ), ( + security_group_rule_id, + 'tcp', + '', + '80:80', + 'default', + security_group_id, + ), ( + '2', + 'tcp', + '', + '80:80', + '', + security_group_2_id, + ),) self.assertEqual(datalist, tuple(data)) From e590597871e1b28ef4a8f2595265c1da2745276e Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Fri, 18 Dec 2015 18:05:41 +0800 Subject: [PATCH 0499/3095] Trivial: Remove useless return from files in network Change-Id: Ib871fbde7c7140eca875403332ad5ab65a7e940d --- openstackclient/network/v2/network.py | 2 -- openstackclient/network/v2/router.py | 1 - 2 files changed, 3 deletions(-) diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 519356b4f7..15f4592b51 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -132,7 +132,6 @@ def take_action(self, parsed_args): for network in parsed_args.network: obj = client.find_network(network) client.delete_network(obj) - return class ListNetwork(lister.Lister): @@ -271,7 +270,6 @@ def take_action(self, parsed_args): raise exceptions.CommandError(msg) client.update_network(obj) - return class ShowNetwork(show.ShowOne): diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 5ad7ccd284..09e0fe4c84 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -146,7 +146,6 @@ def take_action(self, parsed_args): for router in parsed_args.router: obj = client.find_router(router) client.delete_router(obj) - return class ListRouter(lister.Lister): From f552302b614e612892714173b45b651caa10371a Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Fri, 18 Dec 2015 23:25:05 +0800 Subject: [PATCH 0500/3095] Trivial: Remove useless return from files in image and volume Change-Id: I3526ecd202d0908d91305a066ad72d03cee794b5 --- openstackclient/image/v1/image.py | 2 -- openstackclient/volume/v1/backup.py | 1 - openstackclient/volume/v1/qos_specs.py | 9 --------- openstackclient/volume/v1/snapshot.py | 3 --- openstackclient/volume/v1/volume.py | 4 ---- openstackclient/volume/v1/volume_type.py | 4 ---- openstackclient/volume/v2/backup.py | 1 - openstackclient/volume/v2/qos_specs.py | 9 --------- openstackclient/volume/v2/snapshot.py | 3 --- openstackclient/volume/v2/volume.py | 4 ---- openstackclient/volume/v2/volume_type.py | 4 ---- 11 files changed, 44 deletions(-) diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index 4ebc8f93e4..0382501ef6 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -679,8 +679,6 @@ def take_action(self, parsed_args): kwargs['data'] != sys.stdin): kwargs['data'].close() - return - class ShowImage(show.ShowOne): """Display image details""" diff --git a/openstackclient/volume/v1/backup.py b/openstackclient/volume/v1/backup.py index c668e36699..4f2ff8bb16 100644 --- a/openstackclient/volume/v1/backup.py +++ b/openstackclient/volume/v1/backup.py @@ -95,7 +95,6 @@ def take_action(self, parsed_args): backup_id = utils.find_resource(volume_client.backups, backup).id volume_client.backups.delete(backup_id) - return class ListBackup(lister.Lister): diff --git a/openstackclient/volume/v1/qos_specs.py b/openstackclient/volume/v1/qos_specs.py index d1c70113cc..73e70a21fb 100644 --- a/openstackclient/volume/v1/qos_specs.py +++ b/openstackclient/volume/v1/qos_specs.py @@ -55,8 +55,6 @@ def take_action(self, parsed_args): volume_client.qos_specs.associate(qos_spec.id, volume_type.id) - return - class CreateQos(show.ShowOne): """Create new QoS specification""" @@ -123,7 +121,6 @@ def take_action(self, parsed_args): for qos in parsed_args.qos_specs: qos_spec = utils.find_resource(volume_client.qos_specs, qos) volume_client.qos_specs.delete(qos_spec.id) - return class DisassociateQos(command.Command): @@ -166,8 +163,6 @@ def take_action(self, parsed_args): elif parsed_args.all: volume_client.qos_specs.disassociate_all(qos_spec.id) - return - class ListQos(lister.Lister): """List QoS specifications""" @@ -230,8 +225,6 @@ def take_action(self, parsed_args): else: self.app.log.error("No changes requested\n") - return - class ShowQos(show.ShowOne): """Display QoS specification details""" @@ -298,5 +291,3 @@ def take_action(self, parsed_args): parsed_args.property) else: self.app.log.error("No changes requested\n") - - return diff --git a/openstackclient/volume/v1/snapshot.py b/openstackclient/volume/v1/snapshot.py index 93e17eb8a6..24379a9a93 100644 --- a/openstackclient/volume/v1/snapshot.py +++ b/openstackclient/volume/v1/snapshot.py @@ -100,7 +100,6 @@ def take_action(self, parsed_args): snapshot_id = utils.find_resource(volume_client.volume_snapshots, snapshot).id volume_client.volume_snapshots.delete(snapshot_id) - return class ListSnapshot(lister.Lister): @@ -226,7 +225,6 @@ def take_action(self, parsed_args): return snapshot.update(**kwargs) - return class ShowSnapshot(show.ShowOne): @@ -291,4 +289,3 @@ def take_action(self, parsed_args): ) else: self.app.log.error("No changes requested\n") - return diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index 0691d884a5..17b6c9c856 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -196,7 +196,6 @@ def take_action(self, parsed_args): volume_client.volumes.force_delete(volume_obj.id) else: volume_client.volumes.delete(volume_obj.id) - return class ListVolume(lister.Lister): @@ -382,8 +381,6 @@ def take_action(self, parsed_args): if not kwargs and not parsed_args.property and not parsed_args.size: self.app.log.error("No changes requested\n") - return - class ShowVolume(show.ShowOne): """Show volume details""" @@ -454,4 +451,3 @@ def take_action(self, parsed_args): ) else: self.app.log.error("No changes requested\n") - return diff --git a/openstackclient/volume/v1/volume_type.py b/openstackclient/volume/v1/volume_type.py index d7765c7942..b664adfba6 100644 --- a/openstackclient/volume/v1/volume_type.py +++ b/openstackclient/volume/v1/volume_type.py @@ -81,7 +81,6 @@ def take_action(self, parsed_args): volume_type_id = utils.find_resource( volume_client.volume_types, parsed_args.volume_type).id volume_client.volume_types.delete(volume_type_id) - return class ListVolumeType(lister.Lister): @@ -144,8 +143,6 @@ def take_action(self, parsed_args): if parsed_args.property: volume_type.set_keys(parsed_args.property) - return - class UnsetVolumeType(command.Command): """Unset volume type properties""" @@ -182,7 +179,6 @@ def take_action(self, parsed_args): volume_type.unset_keys(parsed_args.property) else: self.app.log.error("No changes requested\n") - return class ShowVolumeType(show.ShowOne): diff --git a/openstackclient/volume/v2/backup.py b/openstackclient/volume/v2/backup.py index 3525e701fe..bc919d0bc5 100644 --- a/openstackclient/volume/v2/backup.py +++ b/openstackclient/volume/v2/backup.py @@ -92,7 +92,6 @@ def take_action(self, parsed_args): backup_id = utils.find_resource( volume_client.backups, backup).id volume_client.backups.delete(backup_id) - return class ListBackup(lister.Lister): diff --git a/openstackclient/volume/v2/qos_specs.py b/openstackclient/volume/v2/qos_specs.py index b3a34cac18..678fde4f3c 100644 --- a/openstackclient/volume/v2/qos_specs.py +++ b/openstackclient/volume/v2/qos_specs.py @@ -55,8 +55,6 @@ def take_action(self, parsed_args): volume_client.qos_specs.associate(qos_spec.id, volume_type.id) - return - class CreateQos(show.ShowOne): """Create new QoS specification""" @@ -123,7 +121,6 @@ def take_action(self, parsed_args): for qos in parsed_args.qos_specs: qos_spec = utils.find_resource(volume_client.qos_specs, qos) volume_client.qos_specs.delete(qos_spec.id) - return class DisassociateQos(command.Command): @@ -166,8 +163,6 @@ def take_action(self, parsed_args): elif parsed_args.all: volume_client.qos_specs.disassociate_all(qos_spec.id) - return - class ListQos(lister.Lister): """List QoS specifications""" @@ -230,8 +225,6 @@ def take_action(self, parsed_args): else: self.app.log.error("No changes requested\n") - return - class ShowQos(show.ShowOne): """Display QoS specification details""" @@ -298,5 +291,3 @@ def take_action(self, parsed_args): parsed_args.property) else: self.app.log.error("No changes requested\n") - - return diff --git a/openstackclient/volume/v2/snapshot.py b/openstackclient/volume/v2/snapshot.py index aa7630ae6e..f939a553f3 100644 --- a/openstackclient/volume/v2/snapshot.py +++ b/openstackclient/volume/v2/snapshot.py @@ -97,7 +97,6 @@ def take_action(self, parsed_args): snapshot_id = utils.find_resource( volume_client.volume_snapshots, snapshot).id volume_client.volume_snapshots.delete(snapshot_id) - return class ListSnapshot(lister.Lister): @@ -217,7 +216,6 @@ def take_action(self, parsed_args): volume_client.volume_snapshots.set_metadata(snapshot.id, parsed_args.property) volume_client.volume_snapshots.update(snapshot.id, **kwargs) - return class ShowSnapshot(show.ShowOne): @@ -280,4 +278,3 @@ def take_action(self, parsed_args): ) else: self.app.log.error("No changes requested\n") - return diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index bbcceca6bf..c636cf2fa1 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -189,7 +189,6 @@ def take_action(self, parsed_args): volume_client.volumes.force_delete(volume_obj.id) else: volume_client.volumes.delete(volume_obj.id) - return class ListVolume(lister.Lister): @@ -394,8 +393,6 @@ def take_action(self, parsed_args): if not kwargs and not parsed_args.property and not parsed_args.size: self.app.log.error("No changes requested\n") - return - class ShowVolume(show.ShowOne): """Display volume details""" @@ -452,4 +449,3 @@ def take_action(self, parsed_args): volume_client.volumes.delete_metadata( volume.id, parsed_args.property) - return diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index 583e6ed9c6..06ab8f82c3 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -110,7 +110,6 @@ def take_action(self, parsed_args): volume_type = utils.find_resource( volume_client.volume_types, parsed_args.volume_type) volume_client.volume_types.delete(volume_type.id) - return class ListVolumeType(lister.Lister): @@ -201,8 +200,6 @@ def take_action(self, parsed_args): if parsed_args.property: volume_type.set_keys(parsed_args.property) - return - class ShowVolumeType(show.ShowOne): """Display volume type details""" @@ -258,4 +255,3 @@ def take_action(self, parsed_args): parsed_args.volume_type, ) volume_type.unset_keys(parsed_args.property) - return From b4660fec7dc1e1df53268e45e905d63f558cc383 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 8 Dec 2015 19:10:46 +0800 Subject: [PATCH 0501/3095] TestServerGeneral: Add test for _format_servers_list_networks() The items in a dict are in random order. So if a server is in two networks, there may be two results after formatted. Change-Id: I2e15d202639e3fff427935f46650a405b0e51bcc Implements: blueprint osc-unit-test-framework-improvement --- .../tests/compute/v2/test_server.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 00ced0e326..fb2fc23d98 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -1136,3 +1136,27 @@ def test_format_servers_list_power_state(self): server._format_servers_list_power_state(0x07)) self.assertEqual("N/A", server._format_servers_list_power_state(0x08)) + + def test_format_servers_list_networks(self): + # Setup network info to test. + networks = { + u'public': [u'10.20.30.40', u'2001:db8::f'], + u'private': [u'2001:db8::f', u'10.20.30.40'], + } + + # Prepare expected data. + # Since networks is a dict, whose items are in random order, there + # could be two results after formatted. + data_1 = (u'private=2001:db8::f, 10.20.30.40; ' + u'public=10.20.30.40, 2001:db8::f') + data_2 = (u'public=10.20.30.40, 2001:db8::f; ' + u'private=2001:db8::f, 10.20.30.40') + + # Call _format_servers_list_networks(). + networks_format = server._format_servers_list_networks(networks) + + msg = ('Network string is not formatted correctly.\n' + 'reference = %s or %s\n' + 'actual = %s\n' % + (data_1, data_2, networks_format)) + self.assertIn(networks_format, (data_1, data_2), msg) From 17f3685a83d29f33f79c0626eceefae029c2aaf5 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Wed, 9 Dec 2015 11:20:34 +0800 Subject: [PATCH 0502/3095] TestServerGeneral: Add test for _prep_server_detail() 1. Make TestServerGeneral inherit from TestServer because we need to use servers_mock, image_mock and flavor_mock in compute_client. 2. Create a dict containing all info of a server in the original format, and pass it to _prep_server_detail(). 3. Compare the original and formatted dict. Change-Id: Ie0b83c42a5c3bbba630a064d28374d07e2ce9caf Implements: blueprint osc-unit-test-framework-improvement --- .../tests/compute/v2/test_server.py | 46 ++++++++++++++++++- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index fb2fc23d98..dba44293ad 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -14,7 +14,6 @@ # import mock -import testtools from mock import call from openstackclient.common import exceptions @@ -1066,7 +1065,7 @@ def test_unshelve_multi_servers(self): self.run_method_with_servers('unshelve', 3) -class TestServerGeneral(testtools.TestCase): +class TestServerGeneral(TestServer): OLD = { 'private': [ { @@ -1160,3 +1159,46 @@ def test_format_servers_list_networks(self): 'actual = %s\n' % (data_1, data_2, networks_format)) self.assertIn(networks_format, (data_1, data_2), msg) + + @mock.patch('openstackclient.common.utils.find_resource') + def test_prep_server_detail(self, find_resource): + # Setup mock method return value. utils.find_resource() will be called + # twice in _prep_server_detail(): + # - The first time, return image info. + # - The second time, return flavor info. + _image = image_fakes.FakeImage.create_one_image() + _flavor = compute_fakes.FakeFlavor.create_one_flavor() + find_resource.side_effect = [_image, _flavor] + + # compute_client.servers.get() will be called once, return server info. + server_info = { + 'image': {u'id': _image.id}, + 'flavor': {u'id': _flavor.id}, + 'tenant_id': u'tenant-id-xxx', + 'networks': {u'public': [u'10.20.30.40', u'2001:db8::f']}, + 'links': u'http://xxx.yyy.com', + } + _server = compute_fakes.FakeServer.create_one_server(attrs=server_info) + self.servers_mock.get.return_value = _server + + # Prepare result data. + info = { + 'id': _server.id, + 'name': _server.name, + 'addresses': u'public=10.20.30.40, 2001:db8::f', + 'flavor': u'%s (%s)' % (_flavor.name, _flavor.id), + 'image': u'%s (%s)' % (_image.name, _image.id), + 'project_id': u'tenant-id-xxx', + 'properties': '', + } + + # Call _prep_server_detail(). + server_detail = server._prep_server_detail( + self.app.client_manager.compute, + _server + ) + # 'networks' is used to create _server. Remove it. + server_detail.pop('networks') + + # Check the results. + self.assertDictEqual(info, server_detail) From 0a444fc949584c9ac2a555bfa9ad221913ad4779 Mon Sep 17 00:00:00 2001 From: xiexs Date: Sat, 5 Dec 2015 19:25:30 +0800 Subject: [PATCH 0503/3095] Add owner validation for "openstack image create/set" Owner validation is necessary if a new image owner will be created/set. Change-Id: I621774e02866bfa98a31b613deff5d7b6a962737 Closes-Bug: #1517134 --- doc/source/command-objects/image.rst | 12 +++ openstackclient/image/v2/image.py | 18 ++++ openstackclient/tests/image/v2/test_image.py | 90 +++++++++++++++++++- 3 files changed, 116 insertions(+), 4 deletions(-) diff --git a/doc/source/command-objects/image.rst b/doc/source/command-objects/image.rst index 6a4782ea5e..f1893efaec 100644 --- a/doc/source/command-objects/image.rst +++ b/doc/source/command-objects/image.rst @@ -33,6 +33,7 @@ Create/upload an image [--public | --private] [--property [...] ] [--tag [...] ] + [--project-domain ] .. option:: --id @@ -127,6 +128,11 @@ Create/upload an image .. versionadded:: 2 +.. option:: --project-domain + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + .. describe:: New image name @@ -244,6 +250,7 @@ Set image properties [--os-version ] [--ramdisk-id ] [--activate|--deactivate] + [--project-domain ] .. option:: --name @@ -400,6 +407,11 @@ Set image properties .. versionadded:: 2 +.. option:: --project-domain + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + .. describe:: Image to modify (name or ID) diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 1fcb92d9a5..ad536ba26b 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -220,6 +220,7 @@ def get_parser(self, prog_name): help="Set a tag on this image " "(repeat option to set multiple tags)", ) + common.add_project_domain_option_to_parser(parser) for deadopt in self.deadopts: parser.add_argument( "--%s" % deadopt, @@ -231,6 +232,7 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) + identity_client = self.app.client_manager.identity image_client = self.app.client_manager.image for deadopt in self.deadopts: @@ -285,6 +287,13 @@ def take_action(self, parsed_args): self.log.warning("Failed to get an image file.") return {}, {} + if parsed_args.owner: + kwargs['owner'] = common.find_project( + identity_client, + parsed_args.owner, + parsed_args.project_domain, + ).id + # If a volume is specified. if parsed_args.volume: volume_client = self.app.client_manager.volume @@ -704,6 +713,7 @@ def get_parser(self, prog_name): action="store_true", help="Activate the image", ) + common.add_project_domain_option_to_parser(parser) for deadopt in self.deadopts: parser.add_argument( "--%s" % deadopt, @@ -715,6 +725,7 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) + identity_client = self.app.client_manager.identity image_client = self.app.client_manager.image for deadopt in self.deadopts: @@ -779,6 +790,13 @@ def take_action(self, parsed_args): # Tags should be extended, but duplicates removed kwargs['tags'] = list(set(image.tags).union(set(parsed_args.tags))) + if parsed_args.owner: + kwargs['owner'] = common.find_project( + identity_client, + parsed_args.owner, + parsed_args.project_domain, + ).id + try: image = image_client.images.update(image.id, **kwargs) except Exception as e: diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index 118a119fa7..0218241397 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -57,6 +57,19 @@ def setUp(self): self.new_image = image_fakes.FakeImage.create_one_image() self.images_mock.create.return_value = self.new_image + + self.project_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.PROJECT), + loaded=True, + ) + + self.domain_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.DOMAIN), + loaded=True, + ) + # This is the return value for utils.find_resource() self.images_mock.get.return_value = copy.deepcopy( image_fakes.FakeImage.get_image_info(self.new_image)) @@ -123,6 +136,7 @@ def test_image_reserve_options(self, mock_open): if self.new_image.protected else '--unprotected'), ('--private' if self.new_image.visibility == 'private' else '--public'), + '--project-domain', identity_fakes.domain_id, self.new_image.name, ] verifylist = [ @@ -135,6 +149,7 @@ def test_image_reserve_options(self, mock_open): ('unprotected', not self.new_image.protected), ('public', self.new_image.visibility == 'public'), ('private', self.new_image.visibility == 'private'), + ('project_domain', identity_fakes.domain_id), ('name', self.new_image.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -149,7 +164,7 @@ def test_image_reserve_options(self, mock_open): disk_format='fs', min_disk=10, min_ram=4, - owner=self.new_image.owner, + owner=identity_fakes.project_id, protected=self.new_image.protected, visibility=self.new_image.visibility, ) @@ -168,6 +183,40 @@ def test_image_reserve_options(self, mock_open): image_fakes.FakeImage.get_image_data(self.new_image), data) + def test_image_create_with_unexist_owner(self): + self.project_mock.get.side_effect = exceptions.NotFound(None) + self.project_mock.find.side_effect = exceptions.NotFound(None) + + arglist = [ + '--container-format', 'ovf', + '--disk-format', 'fs', + '--min-disk', '10', + '--min-ram', '4', + '--owner', 'unexist_owner', + '--protected', + '--private', + image_fakes.image_name, + ] + verifylist = [ + ('container_format', 'ovf'), + ('disk_format', 'fs'), + ('min_disk', 10), + ('min_ram', 4), + ('owner', 'unexist_owner'), + ('protected', True), + ('unprotected', False), + ('public', False), + ('private', True), + ('name', image_fakes.image_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args, + ) + @mock.patch('glanceclient.common.utils.get_data_file', name='Open') def test_image_create_file(self, mock_open): mock_file = mock.MagicMock(name='File') @@ -686,6 +735,18 @@ def setUp(self): schemas.SchemaBasedModel, ) + self.project_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.PROJECT), + loaded=True, + ) + + self.domain_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.DOMAIN), + loaded=True, + ) + self.images_mock.get.return_value = self.model(**image_fakes.IMAGE) self.images_mock.update.return_value = self.model(**image_fakes.IMAGE) # Get the command object to test @@ -694,20 +755,22 @@ def setUp(self): def test_image_set_options(self): arglist = [ '--name', 'new-name', - '--owner', 'new-owner', + '--owner', identity_fakes.project_name, '--min-disk', '2', '--min-ram', '4', '--container-format', 'ovf', '--disk-format', 'vmdk', + '--project-domain', identity_fakes.domain_id, image_fakes.image_id, ] verifylist = [ ('name', 'new-name'), - ('owner', 'new-owner'), + ('owner', identity_fakes.project_name), ('min_disk', 2), ('min_ram', 4), ('container_format', 'ovf'), ('disk_format', 'vmdk'), + ('project_domain', identity_fakes.domain_id), ('image', image_fakes.image_id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -717,7 +780,7 @@ def test_image_set_options(self): kwargs = { 'name': 'new-name', - 'owner': 'new-owner', + 'owner': identity_fakes.project_id, 'min_disk': 2, 'min_ram': 4, 'container_format': 'ovf', @@ -727,6 +790,25 @@ def test_image_set_options(self): self.images_mock.update.assert_called_with( image_fakes.image_id, **kwargs) + def test_image_set_with_unexist_owner(self): + self.project_mock.get.side_effect = exceptions.NotFound(None) + self.project_mock.find.side_effect = exceptions.NotFound(None) + + arglist = [ + '--owner', 'unexist_owner', + image_fakes.image_id, + ] + verifylist = [ + ('owner', 'unexist_owner'), + ('image', image_fakes.image_id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, parsed_args) + def test_image_set_bools1(self): arglist = [ '--protected', From 0ea5c0351b928ac340f7c11f1ca2115dd95ee654 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Mon, 21 Dec 2015 23:45:24 +0000 Subject: [PATCH 0504/3095] Updated from global requirements Change-Id: I589f0463cb9696586fa3ed7ed0bb756d155cc0e5 --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index dbfeb4324c..9193301372 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -16,7 +16,7 @@ os-testr>=0.4.1 testrepository>=0.0.18 testtools>=1.4.0 WebOb>=1.2.3 -tempest-lib>=0.11.0 +tempest-lib>=0.12.0 # Install these to generate sphinx autodocs python-barbicanclient>=3.3.0 From d1311f9742ad93198b9ca72f47d51710e17ced38 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Wed, 23 Dec 2015 12:48:45 -0600 Subject: [PATCH 0505/3095] Add all regions to cloud configuration OCC now requires all regions to be listed in the cloud configuration. Update the shell unit tests to list all of the regions being tested. Change-Id: Ic7300d1f708339701b5daadbf2c4769b239a2adb Closes-Bug: #1528926 --- openstackclient/tests/test_shell.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/tests/test_shell.py b/openstackclient/tests/test_shell.py index c548d8905d..c4546d89f2 100644 --- a/openstackclient/tests/test_shell.py +++ b/openstackclient/tests/test_shell.py @@ -76,7 +76,7 @@ 'project_name': 'heart-o-gold', 'username': 'zaphod', }, - 'region_name': 'occ-cloud', + 'region_name': 'occ-cloud,krikkit,occ-env', 'log_file': '/tmp/test_log_file', 'log_level': 'debug', } From 0e38ef84844ba406cb18fe3893dc6bebd205ef51 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Wed, 23 Dec 2015 13:44:22 -0600 Subject: [PATCH 0506/3095] Improve output for "os security group show" Improve the security group rules output when running the "os security group show" command. Each security group rule is now displayed on a separate line. Current output example: $ openstack security group show default +-------------+------------------------- ... ---+ | Field | Value ... | +-------------+------------------------- ... ---+ | description | Default security group ... | | id | 048a5fc3-3be1-407d-ae47-9... | | name | default ... | | project_id | 3b96bb2020c1459da76963f9e... | | rules | [u"id='5d812367-9829-4340...t"] | +-------------+------------------------- ... ---+ New output example: +-------------+------------------------- ... ---+ | Field | Value ... | +-------------+------------------------- ... ---+ | description | Default security group ... | | id | 048a5fc3-3be1-407d-ae47-9... | | name | default ... | | project_id | 3b96bb2020c1459da76963f9e... | | rules | id='5d812367-9829-4340-95...lt' | | | id='ee451d1c-ade3-4975-8e...lt' | +-------------+------------------------- ... ---+ Change-Id: I1386075310896c58a2b776e2bbec3603bd00eff1 Partial-Bug: #1519511 Related-To: blueprint neutron-client --- openstackclient/common/utils.py | 7 ++++--- openstackclient/compute/v2/security_group.py | 2 +- openstackclient/tests/common/test_utils.py | 7 +++++++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index 42630d913a..783ca8c050 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -154,14 +154,15 @@ def format_dict(data): return output[:-2] -def format_list(data): +def format_list(data, separator=', '): """Return a formatted strings :param data: a list of strings - :rtype: a string formatted to a,b,c + :param separator: the separator to use between strings (default: ', ') + :rtype: a string formatted based on separator """ - return ', '.join(sorted(data)) + return separator.join(sorted(data)) def get_field(item, field): diff --git a/openstackclient/compute/v2/security_group.py b/openstackclient/compute/v2/security_group.py index e3f542b564..a6f060f60c 100644 --- a/openstackclient/compute/v2/security_group.py +++ b/openstackclient/compute/v2/security_group.py @@ -390,7 +390,7 @@ def take_action(self, parsed_args): # Format rules into a list of strings info.update( - {'rules': rules} + {'rules': utils.format_list(rules, separator='\n')} ) # Map 'tenant_id' column to 'project_id' info.update( diff --git a/openstackclient/tests/common/test_utils.py b/openstackclient/tests/common/test_utils.py index b564ffab48..064ad417e6 100644 --- a/openstackclient/tests/common/test_utils.py +++ b/openstackclient/tests/common/test_utils.py @@ -347,3 +347,10 @@ def test_format_list(self): expected = 'a, b, c' self.assertEqual(expected, utils.format_list(['a', 'b', 'c'])) self.assertEqual(expected, utils.format_list(['c', 'b', 'a'])) + + def test_format_list_separator(self): + expected = 'a\nb\nc' + actual_pre_sorted = utils.format_list(['a', 'b', 'c'], separator='\n') + actual_unsorted = utils.format_list(['c', 'b', 'a'], separator='\n') + self.assertEqual(expected, actual_pre_sorted) + self.assertEqual(expected, actual_unsorted) From 7a1a59e5bb5a64dbeb91abdcac31dd0c8803c27d Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Wed, 23 Dec 2015 15:15:06 -0600 Subject: [PATCH 0507/3095] Functional tests for security group rule Add functional tests for the "security group rule" commands. Change-Id: Ia03886e92632f37a3d2625df1c3fa7c2a536c564 Partial-Bug: #1519512 Related-to: blueprint neutron-client --- .../compute/v2/test_security_group_rule.py | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 functional/tests/compute/v2/test_security_group_rule.py diff --git a/functional/tests/compute/v2/test_security_group_rule.py b/functional/tests/compute/v2/test_security_group_rule.py new file mode 100644 index 0000000000..e864b08f6c --- /dev/null +++ b/functional/tests/compute/v2/test_security_group_rule.py @@ -0,0 +1,59 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import uuid + +from functional.common import test + + +class SecurityGroupRuleTests(test.TestCase): + """Functional tests for security group rule. """ + SECURITY_GROUP_NAME = uuid.uuid4().hex + SECURITY_GROUP_RULE_ID = None + NAME_FIELD = ['name'] + ID_FIELD = ['id'] + ID_HEADER = ['ID'] + + @classmethod + def setUpClass(cls): + # Create the security group to hold the rule. + opts = cls.get_show_opts(cls.NAME_FIELD) + raw_output = cls.openstack('security group create ' + + cls.SECURITY_GROUP_NAME + + opts) + expected = cls.SECURITY_GROUP_NAME + '\n' + cls.assertOutput(expected, raw_output) + + # Create the security group rule. + opts = cls.get_show_opts(cls.ID_FIELD) + raw_output = cls.openstack('security group rule create ' + + cls.SECURITY_GROUP_NAME + + ' --proto tcp --dst-port 80:80' + + opts) + cls.SECURITY_GROUP_RULE_ID = raw_output.strip('\n') + + @classmethod + def tearDownClass(cls): + raw_output = cls.openstack('security group rule delete ' + + cls.SECURITY_GROUP_RULE_ID) + cls.assertOutput('', raw_output) + + raw_output = cls.openstack('security group delete ' + + cls.SECURITY_GROUP_NAME) + cls.assertOutput('', raw_output) + + def test_security_group_rule_list(self): + opts = self.get_list_opts(self.ID_HEADER) + raw_output = self.openstack('security group rule list ' + + self.SECURITY_GROUP_NAME + + opts) + self.assertIn(self.SECURITY_GROUP_RULE_ID, raw_output) From 8210ba7a557a44c322ff3adddcbc98072a978e11 Mon Sep 17 00:00:00 2001 From: Xi Yang Date: Fri, 25 Dec 2015 13:14:33 +0800 Subject: [PATCH 0508/3095] Replace assertEqual(None, *) with assertIsNone(*) This patch is going to replace assertEqual(None, *) with assertIsNone(*) in unit test code to have more clear messages in case of failure. Change-Id: I6f85498347e8fc7cad5ea7afb832b9acda7daafc --- openstackclient/tests/common/test_parseractions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/tests/common/test_parseractions.py b/openstackclient/tests/common/test_parseractions.py index b75c48140b..0afbdb7af7 100644 --- a/openstackclient/tests/common/test_parseractions.py +++ b/openstackclient/tests/common/test_parseractions.py @@ -101,7 +101,7 @@ def test_error_values(self): # There should be no red or blue expect = {'green': '100%'} self.assertDictEqual(expect, actual) - self.assertEqual(None, failhere) + self.assertIsNone(failhere) class TestNonNegativeAction(utils.TestCase): From f0a3b175a1a7a8d537f6f8023dd8ff8155375f60 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Mon, 28 Dec 2015 17:28:04 +0800 Subject: [PATCH 0509/3095] Refactor: Initialize parser in setUp() in TestKeyValueAction No need to initialize parser in each test case. Do it in setUp(). Also remove the test_default_values case because it could be tested in the test_good_values case. Change-Id: Ia2ed7c9e46bf6baabbd62b9d50511c5e8103e5e2 --- .../tests/common/test_parseractions.py | 61 +++---------------- 1 file changed, 9 insertions(+), 52 deletions(-) diff --git a/openstackclient/tests/common/test_parseractions.py b/openstackclient/tests/common/test_parseractions.py index 0afbdb7af7..0d9961bcb1 100644 --- a/openstackclient/tests/common/test_parseractions.py +++ b/openstackclient/tests/common/test_parseractions.py @@ -20,34 +20,14 @@ class TestKeyValueAction(utils.TestCase): - def test_good_values(self): - parser = argparse.ArgumentParser() - # Set up our typical usage - parser.add_argument( - '--property', - metavar='', - action=parseractions.KeyValueAction, - help='Property to store for this volume ' - '(repeat option to set multiple properties)', - ) - - results = parser.parse_args([ - '--property', 'red=', - '--property', 'green=100%', - '--property', 'blue=50%', - ]) - - actual = getattr(results, 'property', {}) - # All should pass through unmolested - expect = {'red': '', 'green': '100%', 'blue': '50%'} - self.assertDictEqual(expect, actual) + def setUp(self): + super(TestKeyValueAction, self).setUp() - def test_default_values(self): - parser = argparse.ArgumentParser() + self.parser = argparse.ArgumentParser() # Set up our typical usage - parser.add_argument( + self.parser.add_argument( '--property', metavar='', action=parseractions.KeyValueAction, @@ -56,52 +36,29 @@ def test_default_values(self): '(repeat option to set multiple properties)', ) - results = parser.parse_args([ + def test_good_values(self): + results = self.parser.parse_args([ '--property', 'red=', '--property', 'green=100%', '--property', 'blue=50%', ]) actual = getattr(results, 'property', {}) - # Verify green default is changed, format default is unchanged + # All should pass through unmolested expect = {'red': '', 'green': '100%', 'blue': '50%', 'format': '#rgb'} self.assertDictEqual(expect, actual) def test_error_values(self): - parser = argparse.ArgumentParser() - - # Set up our typical usage - parser.add_argument( - '--property', - metavar='', - action=parseractions.KeyValueAction, - default={'green': '20%', 'blue': '40%'}, - help='Property to store for this volume ' - '(repeat option to set multiple properties)', - ) - - results = parser.parse_args([ + results = self.parser.parse_args([ '--property', 'red', '--property', 'green=100%', '--property', 'blue', ]) - failhere = None actual = getattr(results, 'property', {}) - # Verify non-existent red key - try: - failhere = actual['red'] - except Exception as e: - self.assertTrue(type(e) == KeyError) - # Verify removal of blue key - try: - failhere = actual['blue'] - except Exception as e: - self.assertTrue(type(e) == KeyError) # There should be no red or blue - expect = {'green': '100%'} + expect = {'green': '100%', 'format': '#rgb'} self.assertDictEqual(expect, actual) - self.assertIsNone(failhere) class TestNonNegativeAction(utils.TestCase): From 8d718e9d6796b0ff115698a19f330113a80d09ef Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Mon, 28 Dec 2015 17:34:28 +0800 Subject: [PATCH 0510/3095] Refactor: Initialize parser in setUp() in TestNonNegativeAction Change-Id: I12846acc4450d31d19897bbdfc6846bde8c8f2ce --- .../tests/common/test_parseractions.py | 36 ++++++------------- 1 file changed, 10 insertions(+), 26 deletions(-) diff --git a/openstackclient/tests/common/test_parseractions.py b/openstackclient/tests/common/test_parseractions.py index 0d9961bcb1..0109a3f3d9 100644 --- a/openstackclient/tests/common/test_parseractions.py +++ b/openstackclient/tests/common/test_parseractions.py @@ -62,35 +62,29 @@ def test_error_values(self): class TestNonNegativeAction(utils.TestCase): - def test_negative_values(self): - parser = argparse.ArgumentParser() + + def setUp(self): + super(TestNonNegativeAction, self).setUp() + + self.parser = argparse.ArgumentParser() # Set up our typical usage - parser.add_argument( + self.parser.add_argument( '--foo', metavar='', type=int, action=parseractions.NonNegativeAction, ) + def test_negative_values(self): self.assertRaises( argparse.ArgumentTypeError, - parser.parse_args, + self.parser.parse_args, "--foo -1".split() ) def test_zero_values(self): - parser = argparse.ArgumentParser() - - # Set up our typical usage - parser.add_argument( - '--foo', - metavar='', - type=int, - action=parseractions.NonNegativeAction, - ) - - results = parser.parse_args( + results = self.parser.parse_args( '--foo 0'.split() ) @@ -98,17 +92,7 @@ def test_zero_values(self): self.assertEqual(actual, 0) def test_positive_values(self): - parser = argparse.ArgumentParser() - - # Set up our typical usage - parser.add_argument( - '--foo', - metavar='', - type=int, - action=parseractions.NonNegativeAction, - ) - - results = parser.parse_args( + results = self.parser.parse_args( '--foo 1'.split() ) From 5f0147ad12b83c01b2a098a99239b574729b99f0 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 29 Dec 2015 10:50:44 +0800 Subject: [PATCH 0511/3095] Trivial: Remove useless string_to_bool() string_to_bool() is not used by anyone. Furthermore, it is not well designed. It tries to convirt 't', '1' to True, which could be confused. So remove it. If we need something similar, let's make a better one. Change-Id: Ic1f63480c806bf7bcc9f541fc806eed297ddf718 --- openstackclient/common/utils.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index 42630d913a..deab3a0725 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -258,10 +258,6 @@ def sort_items(items, sort_str): return items -def string_to_bool(arg): - return arg.strip().lower() in ('t', 'true', 'yes', '1') - - def env(*vars, **kwargs): """Search for the first defined of possibly many env vars From eeeb9f729f1482d89203e5124b78eab29113c1cf Mon Sep 17 00:00:00 2001 From: Rushi Agrawal Date: Tue, 29 Dec 2015 23:17:19 +0530 Subject: [PATCH 0512/3095] Docstring should say 'default' if option is default Change-Id: I59d83e7a0a590fa537f509f7f19940640f555679 --- doc/source/command-objects/compute-service.rst | 2 +- openstackclient/compute/v2/service.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/command-objects/compute-service.rst b/doc/source/command-objects/compute-service.rst index 95a77bdf29..7bcab4d692 100644 --- a/doc/source/command-objects/compute-service.rst +++ b/doc/source/command-objects/compute-service.rst @@ -57,7 +57,7 @@ Set service command .. _compute-service-set: .. describe:: --enable - Enable service + Enable service (default) .. describe:: --disable diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index 0a3a5fe424..af3e940a36 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.py @@ -100,7 +100,7 @@ def get_parser(self, prog_name): "--enable", dest="enabled", default=True, - help="Enable a service", + help="Enable a service (default)", action="store_true") enabled_group.add_argument( "--disable", From 66931c6931ee39cc58159d3823b46ad225f39ec8 Mon Sep 17 00:00:00 2001 From: Jude Job Date: Tue, 29 Dec 2015 17:23:42 +0530 Subject: [PATCH 0513/3095] Enabling domain lookup for project set v3 command Currently the domain option for `project set` attempts to set a new domain that owns the project. This is actually an action that is denied by keystone server. Instead, the domain option should be used as a lookup, to find projects that exist in the non-default domain. Co-Authored-By: Steve Martinelli Closes-Bug: #1524456 Change-Id: I30a3812184fe262667e09baa106d2275c2cbb981 --- openstackclient/identity/v3/project.py | 8 ++------ openstackclient/tests/identity/v3/test_project.py | 5 ----- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index f87105dd23..22745aa409 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -283,16 +283,12 @@ def take_action(self, parsed_args): and not parsed_args.disable): return - project = utils.find_resource( - identity_client.projects, - parsed_args.project, - ) + project = common.find_project(identity_client, parsed_args.project, + parsed_args.domain) kwargs = {} if parsed_args.name: kwargs['name'] = parsed_args.name - if parsed_args.domain: - kwargs['domain'] = parsed_args.domain if parsed_args.description: kwargs['description'] = parsed_args.description if parsed_args.enable: diff --git a/openstackclient/tests/identity/v3/test_project.py b/openstackclient/tests/identity/v3/test_project.py index 946bbdcd89..991bae8bf7 100644 --- a/openstackclient/tests/identity/v3/test_project.py +++ b/openstackclient/tests/identity/v3/test_project.py @@ -636,7 +636,6 @@ def test_project_set_name(self): # Set expected values kwargs = { 'name': 'qwerty', - 'domain': identity_fakes.domain_id, } # ProjectManager.update(project, name=, domain=, description=, # enabled=, **kwargs) @@ -665,7 +664,6 @@ def test_project_set_description(self): # Set expected values kwargs = { - 'domain': identity_fakes.domain_id, 'description': 'new desc', } self.projects_mock.update.assert_called_with( @@ -692,7 +690,6 @@ def test_project_set_enable(self): # Set expected values kwargs = { - 'domain': identity_fakes.domain_id, 'enabled': True, } self.projects_mock.update.assert_called_with( @@ -719,7 +716,6 @@ def test_project_set_disable(self): # Set expected values kwargs = { - 'domain': identity_fakes.domain_id, 'enabled': False, } self.projects_mock.update.assert_called_with( @@ -746,7 +742,6 @@ def test_project_set_property(self): # Set expected values kwargs = { - 'domain': identity_fakes.domain_id, 'fee': 'fi', 'fo': 'fum', } From a2a63f19bf1d21c437f1b980aa209277fc947a89 Mon Sep 17 00:00:00 2001 From: Jude Job Date: Wed, 16 Dec 2015 14:38:15 +0530 Subject: [PATCH 0514/3095] Implementation for project unset cmd for python-openstackclient. This patch introduces a unit test class TestProjectUnset for testing unset cmd. Co-Authored-By: Steve Martinelli Change-Id: Ib4a414d2313e3d37e48d1cb3639f064231aec508 Closes-Bug: #1486597 --- doc/source/command-objects/project.rst | 24 ++++++++ functional/tests/identity/v2/test_project.py | 8 ++- openstackclient/identity/v2_0/project.py | 56 +++++++++++++++++++ .../tests/identity/v2_0/test_project.py | 48 +++++++++++++++- setup.cfg | 1 + 5 files changed, 133 insertions(+), 4 deletions(-) diff --git a/doc/source/command-objects/project.rst b/doc/source/command-objects/project.rst index a342115d38..f76f79ff53 100644 --- a/doc/source/command-objects/project.rst +++ b/doc/source/command-objects/project.rst @@ -156,6 +156,8 @@ Set project properties Set a property on :ref:`\ ` (repeat option to set multiple properties) + *Identity version 2 only* + .. _project_set-project: .. describe:: @@ -195,3 +197,25 @@ Display project details .. describe:: Project to display (name or ID) + +project unset +------------- + +Unset project properties + +*Identity version 2 only* + +.. program:: project unset +.. code:: bash + + os project unset + --property [--property ...] + + +.. option:: --property + + Property key to remove from project (repeat option to remove multiple properties) + +.. describe:: + + Project to modify (name or ID) diff --git a/functional/tests/identity/v2/test_project.py b/functional/tests/identity/v2/test_project.py index 88b282ef3e..3a5e8e81a9 100644 --- a/functional/tests/identity/v2/test_project.py +++ b/functional/tests/identity/v2/test_project.py @@ -68,12 +68,12 @@ def test_project_set(self): ) items = self.parse_show(raw_output) fields = list(self.PROJECT_FIELDS) - fields.extend(['k0']) + fields.extend(['properties']) self.assert_show_fields(items, fields) project = self.parse_show_as_object(raw_output) self.assertEqual(new_project_name, project['name']) self.assertEqual('False', project['enabled']) - self.assertEqual('v0', project['k0']) + self.assertEqual("k0='v0'", project['properties']) def test_project_show(self): project_name = self._create_dummy_project() @@ -81,4 +81,6 @@ def test_project_show(self): 'project show %s' % project_name ) items = self.parse_show(raw_output) - self.assert_show_fields(items, self.PROJECT_FIELDS) + fields = list(self.PROJECT_FIELDS) + fields.extend(['properties']) + self.assert_show_fields(items, fields) diff --git a/openstackclient/identity/v2_0/project.py b/openstackclient/identity/v2_0/project.py index 065f0adfbb..4330c79ca0 100644 --- a/openstackclient/identity/v2_0/project.py +++ b/openstackclient/identity/v2_0/project.py @@ -282,4 +282,60 @@ def take_action(self, parsed_args): # TODO(stevemar): Remove the line below when we support multitenancy info.pop('parent_id', None) + + # NOTE(stevemar): Property handling isn't really supported in Keystone + # and needs a lot of extra handling. Let's reserve the properties that + # the API has and handle the extra top level properties. + reserved = ('name', 'id', 'enabled', 'description') + properties = {} + for k, v in info.items(): + if k not in reserved: + # If a key is not in `reserved` it's a property, pop it + info.pop(k) + # If a property has been "unset" it's `None`, so don't show it + if v is not None: + properties[k] = v + + info['properties'] = utils.format_dict(properties) return zip(*sorted(six.iteritems(info))) + + +class UnsetProject(command.Command): + """Unset project properties""" + + log = logging.getLogger(__name__ + '.UnsetProject') + + def get_parser(self, prog_name): + parser = super(UnsetProject, self).get_parser(prog_name) + parser.add_argument( + 'project', + metavar='', + help=_('Project to modify (name or ID)'), + ) + parser.add_argument( + '--property', + metavar='', + action='append', + default=[], + help=_('Unset a project property ' + '(repeat option to unset multiple properties)'), + required=True, + ) + return parser + + @utils.log_method(log) + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + project = utils.find_resource( + identity_client.tenants, + parsed_args.project, + ) + if not parsed_args.property: + self.app.log.error("No changes requested\n") + else: + kwargs = project._info + for key in parsed_args.property: + if key in kwargs: + kwargs[key] = None + identity_client.tenants.update(project.id, **kwargs) + return diff --git a/openstackclient/tests/identity/v2_0/test_project.py b/openstackclient/tests/identity/v2_0/test_project.py index 16ab195736..e2100cd2d1 100644 --- a/openstackclient/tests/identity/v2_0/test_project.py +++ b/openstackclient/tests/identity/v2_0/test_project.py @@ -592,12 +592,58 @@ def test_project_show(self): identity_fakes.project_id, ) - collist = ('description', 'enabled', 'id', 'name') + collist = ('description', 'enabled', 'id', 'name', 'properties') self.assertEqual(collist, columns) datalist = ( identity_fakes.project_description, True, identity_fakes.project_id, identity_fakes.project_name, + '', ) self.assertEqual(datalist, data) + + +class TestProjectUnset(TestProject): + + def setUp(self): + super(TestProjectUnset, self).setUp() + + project_dict = {'fee': 'fi', 'fo': 'fum'} + project_dict.update(identity_fakes.PROJECT) + self.projects_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(project_dict), + loaded=True, + ) + + # Get the command object to test + self.cmd = project.UnsetProject(self.app, None) + + def test_project_unset_key(self): + arglist = [ + '--property', 'fee', + '--property', 'fo', + identity_fakes.project_name, + ] + verifylist = [ + ('property', ['fee', 'fo']), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.run(parsed_args) + # Set expected values + kwargs = { + 'description': identity_fakes.project_description, + 'enabled': True, + 'fee': None, + 'fo': None, + 'id': identity_fakes.project_id, + 'name': identity_fakes.project_name, + } + + self.projects_mock.update.assert_called_with( + identity_fakes.project_id, + **kwargs + ) diff --git a/setup.cfg b/setup.cfg index 01615a3380..b1e634a38d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -163,6 +163,7 @@ openstack.identity.v2 = project_list = openstackclient.identity.v2_0.project:ListProject project_set = openstackclient.identity.v2_0.project:SetProject project_show = openstackclient.identity.v2_0.project:ShowProject + project_unset = openstackclient.identity.v2_0.project:UnsetProject role_add = openstackclient.identity.v2_0.role:AddRole role_create = openstackclient.identity.v2_0.role:CreateRole From 57dac0bc3add71d6c491e2cecc60ef756b75ac32 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Wed, 2 Dec 2015 14:43:01 -0600 Subject: [PATCH 0515/3095] Refactor network endpoint enablement checking Move the network endpoint enablement checking from the 'server create' command to the common client manager. This allows future network commands to use either nova or neutron networking based on the cloud environment. This patch set also includes related unit test enhancements to the common client manager to trigger authentication on the tests. Change-Id: Ia37e81d4fb05a1e2fceb3e5d367bda769ab8e64b Related-Bug: #1519511 Related-to: blueprint neutron-client --- openstackclient/common/clientmanager.py | 20 +++++++++++ openstackclient/compute/v2/server.py | 10 ++---- .../tests/common/test_clientmanager.py | 33 +++++++++++++++++++ openstackclient/tests/fakes.py | 14 +++++++- 4 files changed, 68 insertions(+), 9 deletions(-) diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index edabf65e57..dce1972515 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -191,6 +191,26 @@ def auth_ref(self): self._auth_ref = self.auth.get_auth_ref(self.session) return self._auth_ref + def is_network_endpoint_enabled(self): + """Check if the network endpoint is enabled""" + # Trigger authentication necessary to determine if the network + # endpoint is enabled. + if self.auth_ref: + service_catalog = self.auth_ref.service_catalog + else: + service_catalog = None + # Assume that the network endpoint is enabled. + network_endpoint_enabled = True + if service_catalog: + if 'network' in service_catalog.get_endpoints(): + LOG.debug("Network endpoint in service catalog") + else: + LOG.debug("No network endpoint in service catalog") + network_endpoint_enabled = False + else: + LOG.debug("No service catalog, assuming network endpoint enabled") + return network_endpoint_enabled + def get_endpoint_for_service_type(self, service_type, region_name=None, interface='public'): """Return the endpoint URL for the service type.""" diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index be0ad8cb5a..7afd18f25b 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -257,10 +257,6 @@ class CreateServer(show.ShowOne): log = logging.getLogger(__name__ + '.CreateServer') - def _is_neutron_enabled(self): - service_catalog = self.app.client_manager.auth_ref.service_catalog - return 'network' in service_catalog.get_endpoints() - def get_parser(self, prog_name): parser = super(CreateServer, self).get_parser(prog_name) parser.add_argument( @@ -460,8 +456,6 @@ def take_action(self, parsed_args): block_device_mapping.update({dev_key: block_volume}) nics = [] - if parsed_args.nic: - neutron_enabled = self._is_neutron_enabled() for nic_str in parsed_args.nic: nic_info = {"net-id": "", "v4-fixed-ip": "", "v6-fixed-ip": "", "port-id": ""} @@ -471,7 +465,7 @@ def take_action(self, parsed_args): msg = _("either net-id or port-id should be specified " "but not both") raise exceptions.CommandError(msg) - if neutron_enabled: + if self.app.client_manager.is_network_endpoint_enabled(): network_client = self.app.client_manager.network if nic_info["net-id"]: net = network_client.find_network( @@ -489,7 +483,7 @@ def take_action(self, parsed_args): ).id if nic_info["port-id"]: msg = _("can't create server with port specified " - "since neutron not enabled") + "since network endpoint not enabled") raise exceptions.CommandError(msg) nics.append(nic_info) diff --git a/openstackclient/tests/common/test_clientmanager.py b/openstackclient/tests/common/test_clientmanager.py index 29cc59ed26..523f79a32a 100644 --- a/openstackclient/tests/common/test_clientmanager.py +++ b/openstackclient/tests/common/test_clientmanager.py @@ -77,6 +77,9 @@ def setUp(self): self.requests = self.useFixture(fixture.Fixture()) # fake v2password token retrieval self.stub_auth(json=fakes.TEST_RESPONSE_DICT) + # fake token and token_endpoint retrieval + self.stub_auth(json=fakes.TEST_RESPONSE_DICT, + url='/'.join([fakes.AUTH_URL, 'v2.0/tokens'])) # fake v3password token retrieval self.stub_auth(json=fakes.TEST_RESPONSE_DICT_V3, url='/'.join([fakes.AUTH_URL, 'auth/tokens'])) @@ -99,6 +102,7 @@ def test_client_manager_token_endpoint(self): verify=True ) client_manager.setup_auth() + client_manager.auth_ref self.assertEqual( fakes.AUTH_URL, @@ -114,6 +118,7 @@ def test_client_manager_token_endpoint(self): ) self.assertFalse(client_manager._insecure) self.assertTrue(client_manager._verify) + self.assertTrue(client_manager.is_network_endpoint_enabled()) def test_client_manager_token(self): @@ -131,6 +136,7 @@ def test_client_manager_token(self): verify=True ) client_manager.setup_auth() + client_manager.auth_ref self.assertEqual( fakes.AUTH_URL, @@ -150,6 +156,7 @@ def test_client_manager_token(self): ) self.assertFalse(client_manager._insecure) self.assertTrue(client_manager._verify) + self.assertTrue(client_manager.is_network_endpoint_enabled()) def test_client_manager_password(self): @@ -166,6 +173,7 @@ def test_client_manager_password(self): verify=False, ) client_manager.setup_auth() + client_manager.auth_ref self.assertEqual( fakes.AUTH_URL, @@ -195,6 +203,28 @@ def test_client_manager_password(self): dir(SERVICE_CATALOG), dir(client_manager.auth_ref.service_catalog), ) + self.assertTrue(client_manager.is_network_endpoint_enabled()) + + def test_client_manager_network_endpoint_disabled(self): + + client_manager = clientmanager.ClientManager( + cli_options=FakeOptions( + auth=dict( + auth_url=fakes.AUTH_URL, + username=fakes.USERNAME, + password=fakes.PASSWORD, + project_name=fakes.PROJECT_NAME, + ), + auth_type='v3password', + ), + api_version={"identity": "3"}, + verify=False, + ) + client_manager.setup_auth() + client_manager.auth_ref + + # v3 fake doesn't have network endpoint. + self.assertFalse(client_manager.is_network_endpoint_enabled()) def stub_auth(self, json=None, url=None, verb=None, **kwargs): subject_token = fakes.AUTH_TOKEN @@ -229,10 +259,12 @@ def test_client_manager_password_verify_ca(self): verify='cafile', ) client_manager.setup_auth() + client_manager.auth_ref self.assertFalse(client_manager._insecure) self.assertTrue(client_manager._verify) self.assertEqual('cafile', client_manager._cacert) + self.assertTrue(client_manager.is_network_endpoint_enabled()) def _select_auth_plugin(self, auth_params, api_version, auth_plugin_name): auth_params['auth_type'] = auth_plugin_name @@ -243,6 +275,7 @@ def _select_auth_plugin(self, auth_params, api_version, auth_plugin_name): verify=True ) client_manager.setup_auth() + client_manager.auth_ref self.assertEqual( auth_plugin_name, diff --git a/openstackclient/tests/fakes.py b/openstackclient/tests/fakes.py index 9f4dcc50b5..718dff694b 100644 --- a/openstackclient/tests/fakes.py +++ b/openstackclient/tests/fakes.py @@ -34,7 +34,15 @@ TEST_RESPONSE_DICT = fixture.V2Token(token_id=AUTH_TOKEN, user_name=USERNAME) _s = TEST_RESPONSE_DICT.add_service('identity', name='keystone') -_s.add_endpoint(AUTH_URL + '/v2.0') +_s.add_endpoint(AUTH_URL + ':5000/v2.0') +_s = TEST_RESPONSE_DICT.add_service('network', name='neutron') +_s.add_endpoint(AUTH_URL + ':9696') +_s = TEST_RESPONSE_DICT.add_service('compute', name='nova') +_s.add_endpoint(AUTH_URL + ':8774/v2') +_s = TEST_RESPONSE_DICT.add_service('image', name='glance') +_s.add_endpoint(AUTH_URL + ':9292') +_s = TEST_RESPONSE_DICT.add_service('object', name='swift') +_s.add_endpoint(AUTH_URL + ':8080/v1') TEST_RESPONSE_DICT_V3 = fixture.V3Token(user_name=USERNAME) TEST_RESPONSE_DICT_V3.set_project_scope() @@ -103,6 +111,7 @@ def __init__(self): self.session = None self.auth_ref = None self.auth_plugin_name = None + self.network_endpoint_enabled = True def get_configuration(self): return { @@ -115,6 +124,9 @@ def get_configuration(self): 'identity_api_version': VERSION, } + def is_network_endpoint_enabled(self): + return self.network_endpoint_enabled + class FakeModule(object): def __init__(self, name, version): From b29947449ada77795be2f2bd0aae263f5c765a56 Mon Sep 17 00:00:00 2001 From: zhurong Date: Tue, 5 Jan 2016 02:45:45 -0500 Subject: [PATCH 0516/3095] Delete the unused LOG configure code Delete the unused LOG configure code and import code Change-Id: I1fb0cacfe44b6a2fd4e4b3f504b6d1dec055c5c4 --- openstackclient/common/commandmanager.py | 4 ---- openstackclient/object/client.py | 4 ---- 2 files changed, 8 deletions(-) diff --git a/openstackclient/common/commandmanager.py b/openstackclient/common/commandmanager.py index b34bf7d6f3..b809d63aea 100644 --- a/openstackclient/common/commandmanager.py +++ b/openstackclient/common/commandmanager.py @@ -15,15 +15,11 @@ """Modify cliff.CommandManager""" -import logging import pkg_resources import cliff.commandmanager -LOG = logging.getLogger(__name__) - - class CommandManager(cliff.commandmanager.CommandManager): """Add additional functionality to cliff.CommandManager diff --git a/openstackclient/object/client.py b/openstackclient/object/client.py index e75878026f..3af6f8a0b7 100644 --- a/openstackclient/object/client.py +++ b/openstackclient/object/client.py @@ -15,13 +15,9 @@ """Object client""" -import logging - from openstackclient.api import object_store_v1 from openstackclient.common import utils -LOG = logging.getLogger(__name__) - DEFAULT_API_VERSION = '1' API_VERSION_OPTION = 'os_object_api_version' API_NAME = 'object_store' From 5ff660f71854bc4e11d7e37b6ed54c6170bedc10 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Tue, 5 Jan 2016 12:33:11 -0600 Subject: [PATCH 0517/3095] Further improve output for "os security group show" Improve the security group rules output when running the "os security group show" command. Empty and duplicate information for each security group rule is now removed. This will ensure that the rules remain readable when direction and ethertype information is returned as part of the transition to neutron networking. Change-Id: Ib49c27a9d7f4d5d38ceb2b0d785ddf94d88b2d89 Partial-Bug: #1519511 Related-To: blueprint neutron-client --- openstackclient/compute/v2/security_group.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/openstackclient/compute/v2/security_group.py b/openstackclient/compute/v2/security_group.py index 95e3a1c168..6395e102b9 100644 --- a/openstackclient/compute/v2/security_group.py +++ b/openstackclient/compute/v2/security_group.py @@ -62,6 +62,23 @@ def _xform_security_group_rule(sgroup): return info +def _xform_and_trim_security_group_rule(sgroup): + info = _xform_security_group_rule(sgroup) + # Trim parent security group ID since caller has this information. + info.pop('parent_group_id', None) + # Trim keys with empty string values. + keys_to_trim = [ + 'ip_protocol', + 'ip_range', + 'port_range', + 'remote_security_group', + ] + for key in keys_to_trim: + if key in info and not info[key]: + info.pop(key) + return info + + class CreateSecurityGroup(show.ShowOne): """Create a new security group""" @@ -396,7 +413,8 @@ def take_action(self, parsed_args): )._info) rules = [] for r in info['rules']: - rules.append(utils.format_dict(_xform_security_group_rule(r))) + formatted_rule = _xform_and_trim_security_group_rule(r) + rules.append(utils.format_dict(formatted_rule)) # Format rules into a list of strings info.update( From 591d74945c463314768053bb76eb0e04bdf95394 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 6 Jan 2016 23:19:32 +0000 Subject: [PATCH 0518/3095] Updated from global requirements Change-Id: I3c50646aa9cb13eec6676452f8e3302741cf6445 --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 7451deff1f..a4e2948134 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,8 +8,8 @@ Babel>=1.3 cliff>=1.15.0 # Apache-2.0 keystoneauth1>=2.1.0 openstacksdk -os-client-config!=1.6.2,>=1.4.0 -oslo.config>=2.7.0 # Apache-2.0 +os-client-config>=1.13.1 +oslo.config>=3.2.0 # Apache-2.0 oslo.i18n>=1.5.0 # Apache-2.0 oslo.utils>=3.2.0 # Apache-2.0 python-glanceclient>=1.2.0 From ee0fb4a3baf084462ed4b9a82efa1507fb81397f Mon Sep 17 00:00:00 2001 From: Einst Crazy Date: Thu, 7 Jan 2016 14:35:47 +0800 Subject: [PATCH 0519/3095] Replace assertEqual(*, None) with assertIsNone in tests Replace assertEqual(*, None) with assertIsNone in tests to have more clear messages in case of failure. Change-Id: I8964a10ae3529e978bfab1d8140f95da4b56615c --- openstackclient/tests/identity/v3/test_service_provider.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openstackclient/tests/identity/v3/test_service_provider.py b/openstackclient/tests/identity/v3/test_service_provider.py index e77870d696..0bf7019963 100644 --- a/openstackclient/tests/identity/v3/test_service_provider.py +++ b/openstackclient/tests/identity/v3/test_service_provider.py @@ -408,5 +408,5 @@ def prepare(self): # expect take_action() to return (None, None) as none of --disabled, # --enabled, --description, --service-provider-url, --auth_url option # was set. - self.assertEqual(columns, None) - self.assertEqual(data, None) + self.assertIsNone(columns) + self.assertIsNone(data) From a56fc3403909daa6710bf7e2bd641051d1419579 Mon Sep 17 00:00:00 2001 From: "Swapnil Kulkarni (coolsvap)" Date: Thu, 7 Jan 2016 12:40:09 +0530 Subject: [PATCH 0520/3095] Use assertTrue/False instead of assertEqual(T/F) The usage of assertEqual(True/False, ***) should be changed to a meaningful format of assertTrue/False(***). Change-Id: I3437634329fc8ecef25082b43b5fc0e1030cdbda Closes-Bug:#1512207 --- openstackclient/tests/common/test_logs.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openstackclient/tests/common/test_logs.py b/openstackclient/tests/common/test_logs.py index fe054a3ba7..a319533ace 100644 --- a/openstackclient/tests/common/test_logs.py +++ b/openstackclient/tests/common/test_logs.py @@ -137,7 +137,7 @@ def test_init(self, warning_filter, getLogger, handle): self.cliff_log.setLevel.assert_called_with(logging.ERROR) self.stevedore_log.setLevel.assert_called_with(logging.ERROR) self.iso8601_log.setLevel.assert_called_with(logging.ERROR) - self.assertEqual(False, configurator.dump_trace) + self.assertFalse(configurator.dump_trace) @mock.patch('logging.getLogger') @mock.patch('openstackclient.common.logs.set_warning_filter') @@ -149,7 +149,7 @@ def test_init_no_debug(self, warning_filter, getLogger): warning_filter.assert_called_with(logging.DEBUG) self.requests_log.setLevel.assert_called_with(logging.DEBUG) - self.assertEqual(True, configurator.dump_trace) + self.assertTrue(configurator.dump_trace) @mock.patch('logging.FileHandler') @mock.patch('logging.getLogger') @@ -199,4 +199,4 @@ def test_configure(self, formatter, warning_filter, getLogger, handle): self.root_logger.addHandler.assert_called_with(file_logger) file_logger.setFormatter.assert_called_with(mock_formatter) file_logger.setLevel.assert_called_with(logging.INFO) - self.assertEqual(False, configurator.dump_trace) + self.assertFalse(configurator.dump_trace) From 5cbecc130ef2aacd5d9bd265b814e6f8140374f9 Mon Sep 17 00:00:00 2001 From: Guang Yee Date: Thu, 7 Jan 2016 18:56:44 -0800 Subject: [PATCH 0521/3095] Support non-interactive user password update Currently user password update require interactive prompting of user's original password. This is problematic because we can't support non-interactive applications and therefore hinders automation. This patch make it possible by optionally accepting an '--original-password' argument. If specified, we would use it instead of prompting. DocImpact Change-Id: I2d994e8c2be949f7ae616ac1d1594fb94e1a27cd Closes-Bug: 1531360 --- openstackclient/identity/v3/user.py | 31 +++++++++++++++++-- .../tests/identity/v3/test_user.py | 19 ++++++++++++ .../notes/bug-1531360-0f5c62d18088e5b5.yaml | 5 +++ 3 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/bug-1531360-0f5c62d18088e5b5.yaml diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index eaef8f0523..43a116cb36 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -392,14 +392,41 @@ def get_parser(self, prog_name): metavar='', help='New user password' ) + parser.add_argument( + '--original-password', + metavar='', + help='Original user password' + ) return parser @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - current_password = utils.get_password( - self.app.stdin, prompt="Current Password:", confirm=False) + # FIXME(gyee): there are two scenarios: + # + # 1. user update password for himself + # 2. admin update password on behalf of the user. This is an unlikely + # scenario because that will require admin knowing the user's + # original password which is forbidden under most security + # policies. + # + # Of the two scenarios above, user either authenticate using its + # original password or an authentication token. For scenario #1, + # if user is authenticating with its original password (i.e. passing + # --os-password argument), we can just make use of it instead of using + # --original-password or prompting. For scenario #2, admin will need + # to specify --original-password option or this won't work because + # --os-password is the admin's own password. In the future if we stop + # supporting scenario #2 then we can just do this. + # + # current_password = (parsed_args.original_password or + # self.app.cloud.password) + # + current_password = parsed_args.original_password + if current_password is None: + current_password = utils.get_password( + self.app.stdin, prompt="Current Password:", confirm=False) password = parsed_args.password if password is None: diff --git a/openstackclient/tests/identity/v3/test_user.py b/openstackclient/tests/identity/v3/test_user.py index 76d5f83487..41fab60ee7 100644 --- a/openstackclient/tests/identity/v3/test_user.py +++ b/openstackclient/tests/identity/v3/test_user.py @@ -1095,6 +1095,25 @@ def test_user_create_password_prompt(self): current_pass, new_pass ) + def test_user_password_change_no_prompt(self): + current_pass = 'old_pass' + new_pass = 'new_pass' + arglist = [ + '--password', new_pass, + '--original-password', current_pass, + ] + verifylist = [ + ('password', new_pass), + ('original_password', current_pass), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.users_mock.update_password.assert_called_with( + current_pass, new_pass + ) + class TestUserShow(TestUser): diff --git a/releasenotes/notes/bug-1531360-0f5c62d18088e5b5.yaml b/releasenotes/notes/bug-1531360-0f5c62d18088e5b5.yaml new file mode 100644 index 0000000000..992f2656e2 --- /dev/null +++ b/releasenotes/notes/bug-1531360-0f5c62d18088e5b5.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Support non-interactive user password update + [Bug `1531360 `_] From bd1adaf003a805a1b480b7b48db2a9fe6c4a5ee9 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Tue, 12 Jan 2016 05:05:59 +0000 Subject: [PATCH 0522/3095] Updated from global requirements Change-Id: I3430eea5b97057caad6d211885e1402bec7c1a01 --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 9193301372..39a8f71c42 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -16,7 +16,7 @@ os-testr>=0.4.1 testrepository>=0.0.18 testtools>=1.4.0 WebOb>=1.2.3 -tempest-lib>=0.12.0 +tempest-lib>=0.13.0 # Install these to generate sphinx autodocs python-barbicanclient>=3.3.0 From 030aee6fbcca6be97dd898e8c1b4c05021832bbd Mon Sep 17 00:00:00 2001 From: SaiKiran Date: Sat, 9 Jan 2016 11:39:01 +0530 Subject: [PATCH 0523/3095] Changed the abstract columns and datalists from test cases of common and Identity columns and datalist has been set in each test case in compute, which is not necessary. This patch abstract it out and remove all redundant code. Change-Id: I45a3582088161f12e60e7c933da8e7dcc0ae8e7b Closes-Bug: #1532384 --- .../tests/common/test_configuration.py | 47 ++--- .../tests/common/test_extension.py | 11 +- openstackclient/tests/common/test_timing.py | 12 +- .../tests/identity/v2_0/test_catalog.py | 12 +- .../tests/identity/v2_0/test_project.py | 91 +++------ .../tests/identity/v2_0/test_role.py | 46 ++--- .../tests/identity/v2_0/test_service.py | 57 ++---- .../tests/identity/v2_0/test_user.py | 145 +++++-------- .../tests/identity/v3/test_domain.py | 57 ++---- .../tests/identity/v3/test_endpoint.py | 145 +++++++------ .../tests/identity/v3/test_group.py | 44 ++-- .../identity/v3/test_identity_provider.py | 111 ++++------ .../tests/identity/v3/test_project.py | 125 ++++-------- .../tests/identity/v3/test_region.py | 75 ++++--- .../tests/identity/v3/test_role.py | 38 ++-- .../tests/identity/v3/test_role_assignment.py | 30 +-- .../tests/identity/v3/test_service.py | 63 +++--- .../identity/v3/test_service_provider.py | 79 +++---- .../tests/identity/v3/test_user.py | 192 ++++++------------ 19 files changed, 564 insertions(+), 816 deletions(-) diff --git a/openstackclient/tests/common/test_configuration.py b/openstackclient/tests/common/test_configuration.py index 3b942533a1..e81550ed0a 100644 --- a/openstackclient/tests/common/test_configuration.py +++ b/openstackclient/tests/common/test_configuration.py @@ -18,6 +18,21 @@ class TestConfiguration(utils.TestCommand): + columns = ( + 'auth.password', + 'auth.token', + 'auth.username', + 'identity_api_version', + 'region', + ) + datalist = ( + configuration.REDACTED, + configuration.REDACTED, + fakes.USERNAME, + fakes.VERSION, + fakes.REGION_NAME, + ) + def test_show(self): arglist = [] verifylist = [('mask', True)] @@ -26,17 +41,8 @@ def test_show(self): columns, data = cmd.take_action(parsed_args) - collist = ('auth.password', 'auth.token', 'auth.username', - 'identity_api_version', 'region') - self.assertEqual(collist, columns) - datalist = ( - configuration.REDACTED, - configuration.REDACTED, - fakes.USERNAME, - fakes.VERSION, - fakes.REGION_NAME, - ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_show_unmask(self): arglist = ['--unmask'] @@ -46,9 +52,7 @@ def test_show_unmask(self): columns, data = cmd.take_action(parsed_args) - collist = ('auth.password', 'auth.token', 'auth.username', - 'identity_api_version', 'region') - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = ( fakes.PASSWORD, fakes.AUTH_TOKEN, @@ -56,7 +60,7 @@ def test_show_unmask(self): fakes.VERSION, fakes.REGION_NAME, ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(datalist, data) def test_show_mask(self): arglist = ['--mask'] @@ -66,14 +70,5 @@ def test_show_mask(self): columns, data = cmd.take_action(parsed_args) - collist = ('auth.password', 'auth.token', 'auth.username', - 'identity_api_version', 'region') - self.assertEqual(collist, columns) - datalist = ( - configuration.REDACTED, - configuration.REDACTED, - fakes.USERNAME, - fakes.VERSION, - fakes.REGION_NAME, - ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) diff --git a/openstackclient/tests/common/test_extension.py b/openstackclient/tests/common/test_extension.py index 5f5588a7d3..21c2cc24f1 100644 --- a/openstackclient/tests/common/test_extension.py +++ b/openstackclient/tests/common/test_extension.py @@ -42,6 +42,8 @@ def setUp(self): class TestExtensionList(TestExtension): + columns = ('Name', 'Alias', 'Description') + def setUp(self): super(TestExtensionList, self).setUp() @@ -67,8 +69,7 @@ def test_extension_list_no_options(self): # no args should output from all services self.identity_extensions_mock.list.assert_called_with() - collist = ('Name', 'Alias', 'Description') - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = ( ( identity_fakes.extension_name, @@ -135,8 +136,7 @@ def test_extension_list_identity(self): self.identity_extensions_mock.list.assert_called_with() - collist = ('Name', 'Alias', 'Description') - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = (( identity_fakes.extension_name, identity_fakes.extension_alias, @@ -157,8 +157,7 @@ def test_extension_list_network(self): self.network_extensions_mock.assert_called_with() - collist = ('Name', 'Alias', 'Description') - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = ( ( network_fakes.extension_name, diff --git a/openstackclient/tests/common/test_timing.py b/openstackclient/tests/common/test_timing.py index a7f93b55ca..e7b9a04070 100644 --- a/openstackclient/tests/common/test_timing.py +++ b/openstackclient/tests/common/test_timing.py @@ -33,6 +33,11 @@ def __init__(self, **kwargs): class TestTiming(utils.TestCommand): + columns = ( + 'URL', + 'Seconds', + ) + def setUp(self): super(TestTiming, self).setUp() @@ -59,8 +64,7 @@ def test_timing_list_no_data(self): # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) - collist = ('URL', 'Seconds') - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = [ ('Total', 0.0,) ] @@ -78,9 +82,7 @@ def test_timing_list(self): # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) - - collist = ('URL', 'Seconds') - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = [ (timing_url, timing_elapsed), ('Total', timing_elapsed), diff --git a/openstackclient/tests/identity/v2_0/test_catalog.py b/openstackclient/tests/identity/v2_0/test_catalog.py index 7f1835d674..ff1d993e98 100644 --- a/openstackclient/tests/identity/v2_0/test_catalog.py +++ b/openstackclient/tests/identity/v2_0/test_catalog.py @@ -55,6 +55,12 @@ def setUp(self): class TestCatalogList(TestCatalog): + columns = ( + 'Name', + 'Type', + 'Endpoints', + ) + def setUp(self): super(TestCatalogList, self).setUp() @@ -70,8 +76,7 @@ def test_catalog_list(self): columns, data = self.cmd.take_action(parsed_args) self.sc_mock.service_catalog.get_data.assert_called_with() - collist = ('Name', 'Type', 'Endpoints') - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = (( 'supernova', 'compute', @@ -113,8 +118,7 @@ def test_catalog_list_with_endpoint_url(self): columns, data = self.cmd.take_action(parsed_args) self.sc_mock.service_catalog.get_data.assert_called_with() - collist = ('Name', 'Type', 'Endpoints') - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = (( 'supernova', 'compute', diff --git a/openstackclient/tests/identity/v2_0/test_project.py b/openstackclient/tests/identity/v2_0/test_project.py index 16ab195736..78e5dff7d0 100644 --- a/openstackclient/tests/identity/v2_0/test_project.py +++ b/openstackclient/tests/identity/v2_0/test_project.py @@ -34,6 +34,19 @@ def setUp(self): class TestProjectCreate(TestProject): + columns = ( + 'description', + 'enabled', + 'id', + 'name', + ) + datalist = ( + identity_fakes.project_description, + True, + identity_fakes.project_id, + identity_fakes.project_name, + ) + def setUp(self): super(TestProjectCreate, self).setUp() @@ -69,16 +82,8 @@ def test_project_create_no_options(self): identity_fakes.project_name, **kwargs ) - - collist = ('description', 'enabled', 'id', 'name') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.project_description, - True, - identity_fakes.project_id, - identity_fakes.project_name, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_project_create_description(self): arglist = [ @@ -104,15 +109,8 @@ def test_project_create_description(self): **kwargs ) - collist = ('description', 'enabled', 'id', 'name') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.project_description, - True, - identity_fakes.project_id, - identity_fakes.project_name, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_project_create_enable(self): arglist = [ @@ -139,15 +137,8 @@ def test_project_create_enable(self): **kwargs ) - collist = ('description', 'enabled', 'id', 'name') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.project_description, - True, - identity_fakes.project_id, - identity_fakes.project_name, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_project_create_disable(self): arglist = [ @@ -174,15 +165,8 @@ def test_project_create_disable(self): **kwargs ) - collist = ('description', 'enabled', 'id', 'name') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.project_description, - True, - identity_fakes.project_id, - identity_fakes.project_name, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_project_create_property(self): arglist = [ @@ -211,15 +195,8 @@ def test_project_create_property(self): **kwargs ) - collist = ('description', 'enabled', 'id', 'name') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.project_description, - True, - identity_fakes.project_id, - identity_fakes.project_name, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_project_create_or_show_exists(self): def _raise_conflict(*args, **kwargs): @@ -260,15 +237,8 @@ def _raise_conflict(*args, **kwargs): **kwargs ) - collist = ('description', 'enabled', 'id', 'name') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.project_description, - True, - identity_fakes.project_id, - identity_fakes.project_name, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_project_create_or_show_not_exists(self): arglist = [ @@ -294,15 +264,8 @@ def test_project_create_or_show_not_exists(self): **kwargs ) - collist = ('description', 'enabled', 'id', 'name') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.project_description, - True, - identity_fakes.project_id, - identity_fakes.project_name, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) class TestProjectDelete(TestProject): diff --git a/openstackclient/tests/identity/v2_0/test_role.py b/openstackclient/tests/identity/v2_0/test_role.py index ec484333c1..c2bacc5207 100644 --- a/openstackclient/tests/identity/v2_0/test_role.py +++ b/openstackclient/tests/identity/v2_0/test_role.py @@ -107,6 +107,15 @@ def test_role_add(self): class TestRoleCreate(TestRole): + columns = ( + 'id', + 'name' + ) + datalist = ( + identity_fakes.role_id, + identity_fakes.role_name, + ) + def setUp(self): super(TestRoleCreate, self).setUp() @@ -136,13 +145,8 @@ def test_role_create_no_options(self): identity_fakes.role_name, ) - collist = ('id', 'name') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.role_id, - identity_fakes.role_name, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_role_create_or_show_exists(self): def _raise_conflict(*args, **kwargs): @@ -178,13 +182,8 @@ def _raise_conflict(*args, **kwargs): identity_fakes.role_name, ) - collist = ('id', 'name') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.role_id, - identity_fakes.role_name, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_role_create_or_show_not_exists(self): arglist = [ @@ -205,13 +204,8 @@ def test_role_create_or_show_not_exists(self): identity_fakes.role_name, ) - collist = ('id', 'name') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.role_id, - identity_fakes.role_name, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) class TestRoleDelete(TestRole): @@ -283,6 +277,13 @@ def test_role_list_no_options(self): class TestUserRoleList(TestRole): + columns = ( + 'ID', + 'Name', + 'Project', + 'User' + ) + def setUp(self): super(TestUserRoleList, self).setUp() @@ -395,8 +396,7 @@ def test_user_role_list_project_def_creds(self): identity_fakes.PROJECT_2['id'], ) - collist = ('ID', 'Name', 'Project', 'User') - self.assertEqual(collist, columns) + self.assertEqual(columns, columns) datalist = (( identity_fakes.role_id, identity_fakes.role_name, diff --git a/openstackclient/tests/identity/v2_0/test_service.py b/openstackclient/tests/identity/v2_0/test_service.py index 26a9716f6d..b97786b420 100644 --- a/openstackclient/tests/identity/v2_0/test_service.py +++ b/openstackclient/tests/identity/v2_0/test_service.py @@ -32,6 +32,19 @@ def setUp(self): class TestServiceCreate(TestService): + columns = ( + 'description', + 'id', + 'name', + 'type', + ) + datalist = ( + identity_fakes.service_description, + identity_fakes.service_id, + identity_fakes.service_name, + identity_fakes.service_type, + ) + def setUp(self): super(TestServiceCreate, self).setUp() @@ -66,15 +79,8 @@ def test_service_create_with_type_positional(self): None, ) - collist = ('description', 'id', 'name', 'type') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.service_description, - identity_fakes.service_id, - identity_fakes.service_name, - identity_fakes.service_type, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_service_create_with_type_option(self): arglist = [ @@ -99,15 +105,8 @@ def test_service_create_with_type_option(self): None, ) - collist = ('description', 'id', 'name', 'type') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.service_description, - identity_fakes.service_id, - identity_fakes.service_name, - identity_fakes.service_type, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_service_create_with_name_option(self): arglist = [ @@ -132,15 +131,8 @@ def test_service_create_with_name_option(self): None, ) - collist = ('description', 'id', 'name', 'type') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.service_description, - identity_fakes.service_id, - identity_fakes.service_name, - identity_fakes.service_type, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_service_create_description(self): arglist = [ @@ -166,15 +158,8 @@ def test_service_create_description(self): identity_fakes.service_description, ) - collist = ('description', 'id', 'name', 'type') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.service_description, - identity_fakes.service_id, - identity_fakes.service_name, - identity_fakes.service_type, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) class TestServiceDelete(TestService): diff --git a/openstackclient/tests/identity/v2_0/test_user.py b/openstackclient/tests/identity/v2_0/test_user.py index bf25681e6f..a25def8786 100644 --- a/openstackclient/tests/identity/v2_0/test_user.py +++ b/openstackclient/tests/identity/v2_0/test_user.py @@ -39,6 +39,21 @@ def setUp(self): class TestUserCreate(TestUser): + columns = ( + 'email', + 'enabled', + 'id', + 'name', + 'project_id', + ) + datalist = ( + identity_fakes.user_email, + True, + identity_fakes.user_id, + identity_fakes.user_name, + identity_fakes.project_id, + ) + def setUp(self): super(TestUserCreate, self).setUp() @@ -84,16 +99,8 @@ def test_user_create_no_options(self): **kwargs ) - collist = ('email', 'enabled', 'id', 'name', 'project_id') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.user_email, - True, - identity_fakes.user_id, - identity_fakes.user_name, - identity_fakes.project_id, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_user_create_password(self): arglist = [ @@ -122,17 +129,8 @@ def test_user_create_password(self): None, **kwargs ) - - collist = ('email', 'enabled', 'id', 'name', 'project_id') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.user_email, - True, - identity_fakes.user_id, - identity_fakes.user_name, - identity_fakes.project_id, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_user_create_password_prompt(self): arglist = [ @@ -164,16 +162,8 @@ def test_user_create_password_prompt(self): **kwargs ) - collist = ('email', 'enabled', 'id', 'name', 'project_id') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.user_email, - True, - identity_fakes.user_id, - identity_fakes.user_name, - identity_fakes.project_id, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_user_create_email(self): arglist = [ @@ -202,16 +192,8 @@ def test_user_create_email(self): **kwargs ) - collist = ('email', 'enabled', 'id', 'name', 'project_id') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.user_email, - True, - identity_fakes.user_id, - identity_fakes.user_name, - identity_fakes.project_id, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_user_create_project(self): # Return the new project @@ -255,8 +237,7 @@ def test_user_create_project(self): **kwargs ) - collist = ('email', 'enabled', 'id', 'name', 'project_id') - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = ( identity_fakes.user_email, True, @@ -294,16 +275,8 @@ def test_user_create_enable(self): **kwargs ) - collist = ('email', 'enabled', 'id', 'name', 'project_id') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.user_email, - True, - identity_fakes.user_id, - identity_fakes.user_name, - identity_fakes.project_id, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_user_create_disable(self): arglist = [ @@ -333,16 +306,8 @@ def test_user_create_disable(self): **kwargs ) - collist = ('email', 'enabled', 'id', 'name', 'project_id') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.user_email, - True, - identity_fakes.user_id, - identity_fakes.user_name, - identity_fakes.project_id, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_user_create_or_show_exists(self): def _raise_conflict(*args, **kwargs): @@ -373,16 +338,8 @@ def _raise_conflict(*args, **kwargs): # UserManager.create(name, password, email, tenant_id=, enabled=) self.users_mock.get.assert_called_with(identity_fakes.user_name) - collist = ('email', 'enabled', 'id', 'name', 'project_id') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.user_email, - True, - identity_fakes.user_id, - identity_fakes.user_name, - identity_fakes.project_id, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_user_create_or_show_not_exists(self): arglist = [ @@ -410,17 +367,8 @@ def test_user_create_or_show_not_exists(self): None, **kwargs ) - - collist = ('email', 'enabled', 'id', 'name', 'project_id') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.user_email, - True, - identity_fakes.user_id, - identity_fakes.user_name, - identity_fakes.project_id, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) class TestUserDelete(TestUser): @@ -458,6 +406,17 @@ def test_user_delete_no_options(self): class TestUserList(TestUser): + columns = ( + 'ID', + 'Name', + ) + datalist = ( + ( + identity_fakes.user_id, + identity_fakes.user_name, + ), + ) + def setUp(self): super(TestUserList, self).setUp() @@ -495,13 +454,8 @@ def test_user_list_no_options(self): self.users_mock.list.assert_called_with(tenant_id=None) - collist = ('ID', 'Name') - self.assertEqual(collist, columns) - datalist = (( - identity_fakes.user_id, - identity_fakes.user_name, - ), ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) def test_user_list_project(self): arglist = [ @@ -518,13 +472,8 @@ def test_user_list_project(self): self.users_mock.list.assert_called_with(tenant_id=project_id) - collist = ('ID', 'Name') - self.assertEqual(collist, columns) - datalist = (( - identity_fakes.user_id, - identity_fakes.user_name, - ), ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) def test_user_list_long(self): arglist = [ diff --git a/openstackclient/tests/identity/v3/test_domain.py b/openstackclient/tests/identity/v3/test_domain.py index cfec10e7c3..969c2df7ef 100644 --- a/openstackclient/tests/identity/v3/test_domain.py +++ b/openstackclient/tests/identity/v3/test_domain.py @@ -29,6 +29,19 @@ def setUp(self): class TestDomainCreate(TestDomain): + columns = ( + 'description', + 'enabled', + 'id', + 'name', + ) + datalist = ( + identity_fakes.domain_description, + True, + identity_fakes.domain_id, + identity_fakes.domain_name, + ) + def setUp(self): super(TestDomainCreate, self).setUp() @@ -63,15 +76,8 @@ def test_domain_create_no_options(self): **kwargs ) - collist = ('description', 'enabled', 'id', 'name') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.domain_description, - True, - identity_fakes.domain_id, - identity_fakes.domain_name, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_domain_create_description(self): arglist = [ @@ -97,15 +103,8 @@ def test_domain_create_description(self): **kwargs ) - collist = ('description', 'enabled', 'id', 'name') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.domain_description, - True, - identity_fakes.domain_id, - identity_fakes.domain_name, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_domain_create_enable(self): arglist = [ @@ -131,15 +130,8 @@ def test_domain_create_enable(self): **kwargs ) - collist = ('description', 'enabled', 'id', 'name') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.domain_description, - True, - identity_fakes.domain_id, - identity_fakes.domain_name, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_domain_create_disable(self): arglist = [ @@ -165,15 +157,8 @@ def test_domain_create_disable(self): **kwargs ) - collist = ('description', 'enabled', 'id', 'name') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.domain_description, - True, - identity_fakes.domain_id, - identity_fakes.domain_name, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) class TestDomainDelete(TestDomain): diff --git a/openstackclient/tests/identity/v3/test_endpoint.py b/openstackclient/tests/identity/v3/test_endpoint.py index ecfa71ab3c..fb88e004cd 100644 --- a/openstackclient/tests/identity/v3/test_endpoint.py +++ b/openstackclient/tests/identity/v3/test_endpoint.py @@ -37,6 +37,17 @@ def get_fake_service_name(self): class TestEndpointCreate(TestEndpoint): + columns = ( + 'enabled', + 'id', + 'interface', + 'region', + 'service_id', + 'service_name', + 'service_type', + 'url', + ) + def setUp(self): super(TestEndpointCreate, self).setUp() @@ -86,9 +97,7 @@ def test_endpoint_create_no_options(self): **kwargs ) - collist = ('enabled', 'id', 'interface', 'region', 'service_id', - 'service_name', 'service_type', 'url') - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = ( True, identity_fakes.endpoint_id, @@ -133,9 +142,7 @@ def test_endpoint_create_region(self): **kwargs ) - collist = ('enabled', 'id', 'interface', 'region', 'service_id', - 'service_name', 'service_type', 'url') - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = ( True, identity_fakes.endpoint_id, @@ -179,9 +186,7 @@ def test_endpoint_create_enable(self): **kwargs ) - collist = ('enabled', 'id', 'interface', 'region', 'service_id', - 'service_name', 'service_type', 'url') - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = ( True, identity_fakes.endpoint_id, @@ -225,9 +230,7 @@ def test_endpoint_create_disable(self): **kwargs ) - collist = ('enabled', 'id', 'interface', 'region', 'service_id', - 'service_name', 'service_type', 'url') - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = ( True, identity_fakes.endpoint_id, @@ -276,6 +279,16 @@ def test_endpoint_delete(self): class TestEndpointList(TestEndpoint): + columns = ( + 'ID', + 'Region', + 'Service Name', + 'Service Type', + 'Enabled', + 'Interface', + 'URL', + ) + def setUp(self): super(TestEndpointList, self).setUp() @@ -306,18 +319,18 @@ def test_endpoint_list_no_options(self): columns, data = self.cmd.take_action(parsed_args) self.endpoints_mock.list.assert_called_with() - collist = ('ID', 'Region', 'Service Name', 'Service Type', - 'Enabled', 'Interface', 'URL') - self.assertEqual(collist, columns) - datalist = (( - identity_fakes.endpoint_id, - identity_fakes.endpoint_region, - self.get_fake_service_name(), - identity_fakes.service_type, - True, - identity_fakes.endpoint_interface, - identity_fakes.endpoint_url, - ),) + self.assertEqual(self.columns, columns) + datalist = ( + ( + identity_fakes.endpoint_id, + identity_fakes.endpoint_region, + self.get_fake_service_name(), + identity_fakes.service_type, + True, + identity_fakes.endpoint_interface, + identity_fakes.endpoint_url, + ), + ) self.assertEqual(datalist, tuple(data)) def test_endpoint_list_service(self): @@ -338,18 +351,18 @@ def test_endpoint_list_service(self): } self.endpoints_mock.list.assert_called_with(**kwargs) - collist = ('ID', 'Region', 'Service Name', 'Service Type', - 'Enabled', 'Interface', 'URL') - self.assertEqual(collist, columns) - datalist = (( - identity_fakes.endpoint_id, - identity_fakes.endpoint_region, - self.get_fake_service_name(), - identity_fakes.service_type, - True, - identity_fakes.endpoint_interface, - identity_fakes.endpoint_url, - ),) + self.assertEqual(self.columns, columns) + datalist = ( + ( + identity_fakes.endpoint_id, + identity_fakes.endpoint_region, + self.get_fake_service_name(), + identity_fakes.service_type, + True, + identity_fakes.endpoint_interface, + identity_fakes.endpoint_url, + ), + ) self.assertEqual(datalist, tuple(data)) def test_endpoint_list_interface(self): @@ -370,18 +383,18 @@ def test_endpoint_list_interface(self): } self.endpoints_mock.list.assert_called_with(**kwargs) - collist = ('ID', 'Region', 'Service Name', 'Service Type', - 'Enabled', 'Interface', 'URL') - self.assertEqual(collist, columns) - datalist = (( - identity_fakes.endpoint_id, - identity_fakes.endpoint_region, - self.get_fake_service_name(), - identity_fakes.service_type, - True, - identity_fakes.endpoint_interface, - identity_fakes.endpoint_url, - ),) + self.assertEqual(self.columns, columns) + datalist = ( + ( + identity_fakes.endpoint_id, + identity_fakes.endpoint_region, + self.get_fake_service_name(), + identity_fakes.service_type, + True, + identity_fakes.endpoint_interface, + identity_fakes.endpoint_url, + ), + ) self.assertEqual(datalist, tuple(data)) def test_endpoint_list_region(self): @@ -402,18 +415,18 @@ def test_endpoint_list_region(self): } self.endpoints_mock.list.assert_called_with(**kwargs) - collist = ('ID', 'Region', 'Service Name', 'Service Type', - 'Enabled', 'Interface', 'URL') - self.assertEqual(collist, columns) - datalist = (( - identity_fakes.endpoint_id, - identity_fakes.endpoint_region, - self.get_fake_service_name(), - identity_fakes.service_type, - True, - identity_fakes.endpoint_interface, - identity_fakes.endpoint_url, - ),) + self.assertEqual(self.columns, columns) + datalist = ( + ( + identity_fakes.endpoint_id, + identity_fakes.endpoint_region, + self.get_fake_service_name(), + identity_fakes.service_type, + True, + identity_fakes.endpoint_interface, + identity_fakes.endpoint_url, + ), + ) self.assertEqual(datalist, tuple(data)) @@ -658,8 +671,16 @@ def test_endpoint_show(self): identity_fakes.endpoint_id, ) - collist = ('enabled', 'id', 'interface', 'region', 'service_id', - 'service_name', 'service_type', 'url') + collist = ( + 'enabled', + 'id', + 'interface', + 'region', + 'service_id', + 'service_name', + 'service_type', + 'url', + ) self.assertEqual(collist, columns) datalist = ( True, diff --git a/openstackclient/tests/identity/v3/test_group.py b/openstackclient/tests/identity/v3/test_group.py index 6766a0819b..59a3681971 100644 --- a/openstackclient/tests/identity/v3/test_group.py +++ b/openstackclient/tests/identity/v3/test_group.py @@ -38,6 +38,17 @@ def setUp(self): class TestGroupList(TestGroup): + columns = ( + 'ID', + 'Name', + ) + datalist = ( + ( + identity_fakes.group_id, + identity_fakes.group_name, + ), + ) + def setUp(self): super(TestGroupList, self).setUp() @@ -87,13 +98,8 @@ def test_group_list_no_options(self): **kwargs ) - collist = ('ID', 'Name') - self.assertEqual(collist, columns) - datalist = (( - identity_fakes.group_id, - identity_fakes.group_name, - ), ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) def test_group_list_domain(self): arglist = [ @@ -117,13 +123,8 @@ def test_group_list_domain(self): **kwargs ) - collist = ('ID', 'Name') - self.assertEqual(collist, columns) - datalist = (( - identity_fakes.group_id, - identity_fakes.group_name, - ), ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) def test_group_list_user(self): arglist = [ @@ -147,13 +148,8 @@ def test_group_list_user(self): **kwargs ) - collist = ('ID', 'Name') - self.assertEqual(collist, columns) - datalist = (( - identity_fakes.group_id, - identity_fakes.group_name, - ), ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) def test_group_list_long(self): arglist = [ @@ -177,17 +173,15 @@ def test_group_list_long(self): **kwargs ) - collist = ( - 'ID', - 'Name', + columns = self.columns + ( 'Domain ID', 'Description', ) - self.assertEqual(collist, columns) datalist = (( identity_fakes.group_id, identity_fakes.group_name, '', '', ), ) + self.assertEqual(columns, columns) self.assertEqual(datalist, tuple(data)) diff --git a/openstackclient/tests/identity/v3/test_identity_provider.py b/openstackclient/tests/identity/v3/test_identity_provider.py index 36358be315..50a922b89e 100644 --- a/openstackclient/tests/identity/v3/test_identity_provider.py +++ b/openstackclient/tests/identity/v3/test_identity_provider.py @@ -33,6 +33,19 @@ def setUp(self): class TestIdentityProviderCreate(TestIdentityProvider): + columns = ( + 'description', + 'enabled', + 'id', + 'remote_ids', + ) + datalist = ( + identity_fakes.idp_description, + True, + identity_fakes.idp_id, + identity_fakes.formatted_idp_remote_ids, + ) + def setUp(self): super(TestIdentityProviderCreate, self).setUp() @@ -63,15 +76,8 @@ def test_create_identity_provider_no_options(self): **kwargs ) - collist = ('description', 'enabled', 'id', 'remote_ids') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.idp_description, - True, - identity_fakes.idp_id, - identity_fakes.formatted_idp_remote_ids - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_create_identity_provider_description(self): arglist = [ @@ -97,15 +103,8 @@ def test_create_identity_provider_description(self): **kwargs ) - collist = ('description', 'enabled', 'id', 'remote_ids') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.idp_description, - True, - identity_fakes.idp_id, - identity_fakes.formatted_idp_remote_ids - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_create_identity_provider_remote_id(self): arglist = [ @@ -131,15 +130,8 @@ def test_create_identity_provider_remote_id(self): **kwargs ) - collist = ('description', 'enabled', 'id', 'remote_ids') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.idp_description, - True, - identity_fakes.idp_id, - identity_fakes.formatted_idp_remote_ids - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_create_identity_provider_remote_ids_multiple(self): arglist = [ @@ -166,15 +158,8 @@ def test_create_identity_provider_remote_ids_multiple(self): **kwargs ) - collist = ('description', 'enabled', 'id', 'remote_ids') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.idp_description, - True, - identity_fakes.idp_id, - identity_fakes.formatted_idp_remote_ids - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_create_identity_provider_remote_ids_file(self): arglist = [ @@ -205,15 +190,8 @@ def test_create_identity_provider_remote_ids_file(self): **kwargs ) - collist = ('description', 'enabled', 'id', 'remote_ids') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.idp_description, - True, - identity_fakes.idp_id, - identity_fakes.formatted_idp_remote_ids - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_create_identity_provider_disabled(self): @@ -247,8 +225,7 @@ def test_create_identity_provider_disabled(self): **kwargs ) - collist = ('description', 'enabled', 'id', 'remote_ids') - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = ( None, False, @@ -370,6 +347,19 @@ def test_identity_provider_show(self): class TestIdentityProviderSet(TestIdentityProvider): + columns = ( + 'description', + 'enabled', + 'id', + 'remote_ids', + ) + datalist = ( + identity_fakes.idp_description, + True, + identity_fakes.idp_id, + identity_fakes.idp_remote_ids, + ) + def setUp(self): super(TestIdentityProviderSet, self).setUp() self.cmd = identity_provider.SetIdentityProvider(self.app, None) @@ -401,15 +391,12 @@ def prepare(self): ('remote_id', None) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) self.identity_providers_mock.update.assert_called_with( identity_fakes.idp_id, - description=new_description + description=new_description, ) - - collist = ('description', 'enabled', 'id', 'remote_ids') - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = ( identity_fakes.idp_description, False, @@ -456,8 +443,7 @@ def prepare(self): remote_ids=identity_fakes.idp_remote_ids ) - collist = ('description', 'enabled', 'id', 'remote_ids') - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = ( identity_fakes.idp_description, False, @@ -499,15 +485,8 @@ def prepare(self): self.identity_providers_mock.update.assert_called_with( identity_fakes.idp_id, enabled=True, remote_ids=identity_fakes.idp_remote_ids) - collist = ('description', 'enabled', 'id', 'remote_ids') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.idp_description, - True, - identity_fakes.idp_id, - identity_fakes.idp_remote_ids - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_identity_provider_replace_remote_ids(self): """Enable Identity Provider. @@ -545,8 +524,7 @@ def prepare(self): self.identity_providers_mock.update.assert_called_with( identity_fakes.idp_id, enabled=True, remote_ids=[self.new_remote_id]) - collist = ('description', 'enabled', 'id', 'remote_ids') - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = ( identity_fakes.idp_description, True, @@ -595,8 +573,7 @@ def prepare(self): self.identity_providers_mock.update.assert_called_with( identity_fakes.idp_id, enabled=True, remote_ids=[self.new_remote_id]) - collist = ('description', 'enabled', 'id', 'remote_ids') - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = ( identity_fakes.idp_description, True, diff --git a/openstackclient/tests/identity/v3/test_project.py b/openstackclient/tests/identity/v3/test_project.py index 946bbdcd89..0453fd8f8b 100644 --- a/openstackclient/tests/identity/v3/test_project.py +++ b/openstackclient/tests/identity/v3/test_project.py @@ -38,6 +38,21 @@ def setUp(self): class TestProjectCreate(TestProject): + columns = ( + 'description', + 'domain_id', + 'enabled', + 'id', + 'name' + ) + datalist = ( + identity_fakes.project_description, + identity_fakes.domain_id, + True, + identity_fakes.project_id, + identity_fakes.project_name, + ) + def setUp(self): super(TestProjectCreate, self).setUp() @@ -127,16 +142,8 @@ def test_project_create_description(self): **kwargs ) - collist = ('description', 'domain_id', 'enabled', 'id', 'name') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.project_description, - identity_fakes.domain_id, - True, - identity_fakes.project_id, - identity_fakes.project_name, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_project_create_domain(self): arglist = [ @@ -169,16 +176,8 @@ def test_project_create_domain(self): **kwargs ) - collist = ('description', 'domain_id', 'enabled', 'id', 'name') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.project_description, - identity_fakes.domain_id, - True, - identity_fakes.project_id, - identity_fakes.project_name, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_project_create_domain_no_perms(self): arglist = [ @@ -210,16 +209,8 @@ def test_project_create_domain_no_perms(self): self.projects_mock.create.assert_called_with( **kwargs ) - collist = ('description', 'domain_id', 'enabled', 'id', 'name') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.project_description, - identity_fakes.domain_id, - True, - identity_fakes.project_id, - identity_fakes.project_name, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_project_create_enable(self): arglist = [ @@ -251,16 +242,8 @@ def test_project_create_enable(self): **kwargs ) - collist = ('description', 'domain_id', 'enabled', 'id', 'name') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.project_description, - identity_fakes.domain_id, - True, - identity_fakes.project_id, - identity_fakes.project_name, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_project_create_disable(self): arglist = [ @@ -292,16 +275,8 @@ def test_project_create_disable(self): **kwargs ) - collist = ('description', 'domain_id', 'enabled', 'id', 'name') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.project_description, - identity_fakes.domain_id, - True, - identity_fakes.project_id, - identity_fakes.project_name, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_project_create_property(self): arglist = [ @@ -334,16 +309,8 @@ def test_project_create_property(self): **kwargs ) - collist = ('description', 'domain_id', 'enabled', 'id', 'name') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.project_description, - identity_fakes.domain_id, - True, - identity_fakes.project_id, - identity_fakes.project_name, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_project_create_parent(self): self.projects_mock.get.return_value = fakes.FakeResource( @@ -467,6 +434,17 @@ def test_project_delete_no_options(self): class TestProjectList(TestProject): + columns = ( + 'ID', + 'Name', + ) + datalist = ( + ( + identity_fakes.project_id, + identity_fakes.project_name, + ), + ) + def setUp(self): super(TestProjectList, self).setUp() @@ -490,13 +468,8 @@ def test_project_list_no_options(self): columns, data = self.cmd.take_action(parsed_args) self.projects_mock.list.assert_called_with() - collist = ('ID', 'Name') - self.assertEqual(collist, columns) - datalist = (( - identity_fakes.project_id, - identity_fakes.project_name, - ), ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) def test_project_list_long(self): arglist = [ @@ -543,13 +516,8 @@ def test_project_list_domain(self): self.projects_mock.list.assert_called_with( domain=identity_fakes.domain_id) - collist = ('ID', 'Name') - self.assertEqual(collist, columns) - datalist = (( - identity_fakes.project_id, - identity_fakes.project_name, - ), ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) def test_project_list_domain_no_perms(self): arglist = [ @@ -567,13 +535,8 @@ def test_project_list_domain_no_perms(self): self.projects_mock.list.assert_called_with( domain=identity_fakes.domain_id) - collist = ('ID', 'Name') - self.assertEqual(collist, columns) - datalist = (( - identity_fakes.project_id, - identity_fakes.project_name, - ), ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) class TestProjectSet(TestProject): diff --git a/openstackclient/tests/identity/v3/test_region.py b/openstackclient/tests/identity/v3/test_region.py index 0ebbbecf44..9ac9ddf8ed 100644 --- a/openstackclient/tests/identity/v3/test_region.py +++ b/openstackclient/tests/identity/v3/test_region.py @@ -30,6 +30,17 @@ def setUp(self): class TestRegionCreate(TestRegion): + columns = ( + 'description', + 'parent_region', + 'region', + ) + datalist = ( + identity_fakes.region_description, + identity_fakes.region_parent_region_id, + identity_fakes.region_id, + ) + def setUp(self): super(TestRegionCreate, self).setUp() @@ -66,14 +77,8 @@ def test_region_create_description(self): **kwargs ) - collist = ('description', 'parent_region', 'region') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.region_description, - identity_fakes.region_parent_region_id, - identity_fakes.region_id, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_region_create_no_options(self): arglist = [ @@ -97,14 +102,8 @@ def test_region_create_no_options(self): **kwargs ) - collist = ('description', 'parent_region', 'region') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.region_description, - identity_fakes.region_parent_region_id, - identity_fakes.region_id, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_region_create_parent_region_id(self): arglist = [ @@ -129,15 +128,8 @@ def test_region_create_parent_region_id(self): self.regions_mock.create.assert_called_with( **kwargs ) - - collist = ('description', 'parent_region', 'region') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.region_description, - identity_fakes.region_parent_region_id, - identity_fakes.region_id, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) class TestRegionDelete(TestRegion): @@ -169,6 +161,19 @@ def test_region_delete_no_options(self): class TestRegionList(TestRegion): + columns = ( + 'Region', + 'Parent Region', + 'Description', + ) + datalist = ( + ( + identity_fakes.region_id, + identity_fakes.region_parent_region_id, + identity_fakes.region_description, + ), + ) + def setUp(self): super(TestRegionList, self).setUp() @@ -192,14 +197,8 @@ def test_region_list_no_options(self): columns, data = self.cmd.take_action(parsed_args) self.regions_mock.list.assert_called_with() - collist = ('Region', 'Parent Region', 'Description') - self.assertEqual(collist, columns) - datalist = (( - identity_fakes.region_id, - identity_fakes.region_parent_region_id, - identity_fakes.region_description, - ), ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) def test_region_list_parent_region_id(self): arglist = [ @@ -215,14 +214,8 @@ def test_region_list_parent_region_id(self): self.regions_mock.list.assert_called_with( parent_region_id=identity_fakes.region_parent_region_id) - collist = ('Region', 'Parent Region', 'Description') - self.assertEqual(collist, columns) - datalist = (( - identity_fakes.region_id, - identity_fakes.region_parent_region_id, - identity_fakes.region_description, - ), ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) class TestRegionSet(TestRegion): diff --git a/openstackclient/tests/identity/v3/test_role.py b/openstackclient/tests/identity/v3/test_role.py index 8ad4b099b4..1910c8742b 100644 --- a/openstackclient/tests/identity/v3/test_role.py +++ b/openstackclient/tests/identity/v3/test_role.py @@ -314,6 +314,17 @@ def test_role_delete_no_options(self): class TestRoleList(TestRole): + columns = ( + 'ID', + 'Name', + ) + datalist = ( + ( + identity_fakes.role_id, + identity_fakes.role_name, + ), + ) + def setUp(self): super(TestRoleList, self).setUp() @@ -359,13 +370,8 @@ def test_role_list_no_options(self): self.roles_mock.list.assert_called_with() - collist = ('ID', 'Name') - self.assertEqual(collist, columns) - datalist = (( - identity_fakes.role_id, - identity_fakes.role_name, - ), ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) def test_user_list_inherited(self): arglist = [ @@ -392,13 +398,8 @@ def test_user_list_inherited(self): **kwargs ) - collist = ('ID', 'Name') - self.assertEqual(collist, columns) - datalist = (( - identity_fakes.role_id, - identity_fakes.role_name, - ), ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) def test_user_list_user(self): arglist = [ @@ -423,13 +424,8 @@ def test_user_list_user(self): **kwargs ) - collist = ('ID', 'Name') - self.assertEqual(collist, columns) - datalist = (( - identity_fakes.role_id, - identity_fakes.role_name, - ), ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) def test_role_list_domain_user(self): arglist = [ diff --git a/openstackclient/tests/identity/v3/test_role_assignment.py b/openstackclient/tests/identity/v3/test_role_assignment.py index 9817f53a4d..5723a33477 100644 --- a/openstackclient/tests/identity/v3/test_role_assignment.py +++ b/openstackclient/tests/identity/v3/test_role_assignment.py @@ -26,6 +26,15 @@ def setUp(self): class TestRoleAssignmentList(TestRoleAssignment): + columns = ( + 'Role', + 'User', + 'Group', + 'Project', + 'Domain', + 'Inherited', + ) + def setUp(self): super(TestRoleAssignment, self).setUp() @@ -89,8 +98,7 @@ def test_role_assignment_list_no_filters(self): project=None, os_inherit_extension_inherited_to=None) - collist = ('Role', 'User', 'Group', 'Project', 'Domain', 'Inherited') - self.assertEqual(columns, collist) + self.assertEqual(self.columns, columns) datalist = (( identity_fakes.role_id, identity_fakes.user_id, @@ -150,8 +158,7 @@ def test_role_assignment_list_user(self): effective=False, os_inherit_extension_inherited_to=None) - collist = ('Role', 'User', 'Group', 'Project', 'Domain', 'Inherited') - self.assertEqual(columns, collist) + self.assertEqual(self.columns, columns) datalist = (( identity_fakes.role_id, identity_fakes.user_id, @@ -211,8 +218,7 @@ def test_role_assignment_list_group(self): user=None, os_inherit_extension_inherited_to=None) - collist = ('Role', 'User', 'Group', 'Project', 'Domain', 'Inherited') - self.assertEqual(columns, collist) + self.assertEqual(self.columns, columns) datalist = (( identity_fakes.role_id, '', @@ -272,8 +278,7 @@ def test_role_assignment_list_domain(self): user=None, os_inherit_extension_inherited_to=None) - collist = ('Role', 'User', 'Group', 'Project', 'Domain', 'Inherited') - self.assertEqual(columns, collist) + self.assertEqual(self.columns, columns) datalist = (( identity_fakes.role_id, identity_fakes.user_id, @@ -333,8 +338,7 @@ def test_role_assignment_list_project(self): user=None, os_inherit_extension_inherited_to=None) - collist = ('Role', 'User', 'Group', 'Project', 'Domain', 'Inherited') - self.assertEqual(columns, collist) + self.assertEqual(self.columns, columns) datalist = (( identity_fakes.role_id, identity_fakes.user_id, @@ -392,8 +396,7 @@ def test_role_assignment_list_effective(self): user=None, os_inherit_extension_inherited_to=None) - collist = ('Role', 'User', 'Group', 'Project', 'Domain', 'Inherited') - self.assertEqual(columns, collist) + self.assertEqual(self.columns, columns) datalist = (( identity_fakes.role_id, identity_fakes.user_id, @@ -453,8 +456,7 @@ def test_role_assignment_list_inherited(self): user=None, os_inherit_extension_inherited_to='projects') - collist = ('Role', 'User', 'Group', 'Project', 'Domain', 'Inherited') - self.assertEqual(columns, collist) + self.assertEqual(self.columns, columns) datalist = (( identity_fakes.role_id, identity_fakes.user_id, diff --git a/openstackclient/tests/identity/v3/test_service.py b/openstackclient/tests/identity/v3/test_service.py index d2b54c7f6e..c609142cd7 100644 --- a/openstackclient/tests/identity/v3/test_service.py +++ b/openstackclient/tests/identity/v3/test_service.py @@ -32,6 +32,21 @@ def setUp(self): class TestServiceCreate(TestService): + columns = ( + 'description', + 'enabled', + 'id', + 'name', + 'type', + ) + datalist = ( + identity_fakes.service_description, + True, + identity_fakes.service_id, + identity_fakes.service_name, + identity_fakes.service_type, + ) + def setUp(self): super(TestServiceCreate, self).setUp() @@ -69,16 +84,8 @@ def test_service_create_name(self): enabled=True, ) - collist = ('description', 'enabled', 'id', 'name', 'type') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.service_description, - True, - identity_fakes.service_id, - identity_fakes.service_name, - identity_fakes.service_type, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_service_create_description(self): arglist = [ @@ -105,16 +112,8 @@ def test_service_create_description(self): enabled=True, ) - collist = ('description', 'enabled', 'id', 'name', 'type') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.service_description, - True, - identity_fakes.service_id, - identity_fakes.service_name, - identity_fakes.service_type, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_service_create_enable(self): arglist = [ @@ -141,16 +140,8 @@ def test_service_create_enable(self): enabled=True, ) - collist = ('description', 'enabled', 'id', 'name', 'type') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.service_description, - True, - identity_fakes.service_id, - identity_fakes.service_name, - identity_fakes.service_type, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_service_create_disable(self): arglist = [ @@ -177,16 +168,8 @@ def test_service_create_disable(self): enabled=False, ) - collist = ('description', 'enabled', 'id', 'name', 'type') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.service_description, - True, - identity_fakes.service_id, - identity_fakes.service_name, - identity_fakes.service_type, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) class TestServiceDelete(TestService): diff --git a/openstackclient/tests/identity/v3/test_service_provider.py b/openstackclient/tests/identity/v3/test_service_provider.py index e77870d696..24fa7c7b64 100644 --- a/openstackclient/tests/identity/v3/test_service_provider.py +++ b/openstackclient/tests/identity/v3/test_service_provider.py @@ -31,6 +31,21 @@ def setUp(self): class TestServiceProviderCreate(TestServiceProvider): + columns = ( + 'auth_url', + 'description', + 'enabled', + 'id', + 'sp_url', + ) + datalist = ( + service_fakes.sp_auth_url, + service_fakes.sp_description, + True, + service_fakes.sp_id, + service_fakes.service_provider_url + ) + def setUp(self): super(TestServiceProviderCreate, self).setUp() @@ -67,16 +82,8 @@ def test_create_service_provider_required_options_only(self): **kwargs ) - collist = ('auth_url', 'description', 'enabled', 'id', 'sp_url') - self.assertEqual(collist, columns) - datalist = ( - service_fakes.sp_auth_url, - service_fakes.sp_description, - True, - service_fakes.sp_id, - service_fakes.service_provider_url - ) - self.assertEqual(data, datalist) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_create_service_provider_description(self): @@ -109,16 +116,8 @@ def test_create_service_provider_description(self): **kwargs ) - collist = ('auth_url', 'description', 'enabled', 'id', 'sp_url') - self.assertEqual(columns, collist) - datalist = ( - service_fakes.sp_auth_url, - service_fakes.sp_description, - True, - service_fakes.sp_id, - service_fakes.service_provider_url - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_create_service_provider_disabled(self): @@ -155,9 +154,7 @@ def test_create_service_provider_disabled(self): id=service_fakes.sp_id, **kwargs ) - - collist = ('auth_url', 'description', 'enabled', 'id', 'sp_url') - self.assertEqual(collist, collist) + self.assertEqual(self.columns, columns) datalist = ( service_fakes.sp_auth_url, None, @@ -282,6 +279,21 @@ def test_service_provider_show(self): class TestServiceProviderSet(TestServiceProvider): + columns = ( + 'auth_url', + 'description', + 'enabled', + 'id', + 'sp_url', + ) + datalist = ( + service_fakes.sp_auth_url, + service_fakes.sp_description, + False, + service_fakes.sp_id, + service_fakes.service_provider_url, + ) + def setUp(self): super(TestServiceProviderSet, self).setUp() self.cmd = service_provider.SetServiceProvider(self.app, None) @@ -321,16 +333,8 @@ def prepare(self): sp_url=None ) - collist = ('auth_url', 'description', 'enabled', 'id', 'sp_url') - self.assertEqual(collist, columns) - datalist = ( - service_fakes.sp_auth_url, - service_fakes.sp_description, - False, - service_fakes.sp_id, - service_fakes.service_provider_url - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_service_provider_enable(self): """Enable Service Provider. @@ -361,8 +365,7 @@ def prepare(self): self.service_providers_mock.update.assert_called_with( service_fakes.sp_id, enabled=True, description=None, auth_url=None, sp_url=None) - collist = ('auth_url', 'description', 'enabled', 'id', 'sp_url') - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = ( service_fakes.sp_auth_url, service_fakes.sp_description, @@ -370,7 +373,7 @@ def prepare(self): service_fakes.sp_id, service_fakes.service_provider_url ) - self.assertEqual(data, datalist) + self.assertEqual(datalist, data) def test_service_provider_no_options(self): def prepare(self): @@ -408,5 +411,5 @@ def prepare(self): # expect take_action() to return (None, None) as none of --disabled, # --enabled, --description, --service-provider-url, --auth_url option # was set. - self.assertEqual(columns, None) - self.assertEqual(data, None) + self.assertIsNone(columns) + self.assertIsNone(data) diff --git a/openstackclient/tests/identity/v3/test_user.py b/openstackclient/tests/identity/v3/test_user.py index 76d5f83487..6dd3a71d00 100644 --- a/openstackclient/tests/identity/v3/test_user.py +++ b/openstackclient/tests/identity/v3/test_user.py @@ -52,6 +52,23 @@ def setUp(self): class TestUserCreate(TestUser): + columns = ( + 'default_project_id', + 'domain_id', + 'email', + 'enabled', + 'id', + 'name', + ) + datalist = ( + identity_fakes.project_id, + identity_fakes.domain_id, + identity_fakes.user_email, + True, + identity_fakes.user_id, + identity_fakes.user_name, + ) + def setUp(self): super(TestUserCreate, self).setUp() @@ -107,18 +124,8 @@ def test_user_create_no_options(self): **kwargs ) - collist = ('default_project_id', 'domain_id', 'email', - 'enabled', 'id', 'name') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.project_id, - identity_fakes.domain_id, - identity_fakes.user_email, - True, - identity_fakes.user_id, - identity_fakes.user_name, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_user_create_password(self): arglist = [ @@ -152,19 +159,8 @@ def test_user_create_password(self): self.users_mock.create.assert_called_with( **kwargs ) - - collist = ('default_project_id', 'domain_id', 'email', - 'enabled', 'id', 'name') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.project_id, - identity_fakes.domain_id, - identity_fakes.user_email, - True, - identity_fakes.user_id, - identity_fakes.user_name, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_user_create_password_prompt(self): arglist = [ @@ -202,18 +198,8 @@ def test_user_create_password_prompt(self): **kwargs ) - collist = ('default_project_id', 'domain_id', 'email', - 'enabled', 'id', 'name') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.project_id, - identity_fakes.domain_id, - identity_fakes.user_email, - True, - identity_fakes.user_id, - identity_fakes.user_name, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_user_create_email(self): arglist = [ @@ -247,18 +233,8 @@ def test_user_create_email(self): **kwargs ) - collist = ('default_project_id', 'domain_id', 'email', - 'enabled', 'id', 'name') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.project_id, - identity_fakes.domain_id, - identity_fakes.user_email, - True, - identity_fakes.user_id, - identity_fakes.user_name, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_user_create_project(self): # Return the new project @@ -307,9 +283,7 @@ def test_user_create_project(self): **kwargs ) - collist = ('default_project_id', 'domain_id', 'email', - 'enabled', 'id', 'name') - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = ( identity_fakes.PROJECT_2['id'], identity_fakes.domain_id, @@ -369,9 +343,7 @@ def test_user_create_project_domain(self): **kwargs ) - collist = ('default_project_id', 'domain_id', 'email', - 'enabled', 'id', 'name') - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = ( identity_fakes.PROJECT_2['id'], identity_fakes.domain_id, @@ -414,18 +386,8 @@ def test_user_create_domain(self): **kwargs ) - collist = ('default_project_id', 'domain_id', 'email', - 'enabled', 'id', 'name') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.project_id, - identity_fakes.domain_id, - identity_fakes.user_email, - True, - identity_fakes.user_id, - identity_fakes.user_name, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_user_create_enable(self): arglist = [ @@ -458,18 +420,8 @@ def test_user_create_enable(self): **kwargs ) - collist = ('default_project_id', 'domain_id', 'email', - 'enabled', 'id', 'name') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.project_id, - identity_fakes.domain_id, - identity_fakes.user_email, - True, - identity_fakes.user_id, - identity_fakes.user_name, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_user_create_disable(self): arglist = [ @@ -500,19 +452,8 @@ def test_user_create_disable(self): self.users_mock.create.assert_called_with( **kwargs ) - - collist = ('default_project_id', 'domain_id', 'email', - 'enabled', 'id', 'name') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.project_id, - identity_fakes.domain_id, - identity_fakes.user_email, - True, - identity_fakes.user_id, - identity_fakes.user_name, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) class TestUserDelete(TestUser): @@ -550,6 +491,17 @@ def test_user_delete_no_options(self): class TestUserList(TestUser): + columns = [ + 'ID', + 'Name' + ] + datalist = ( + ( + identity_fakes.user_id, + identity_fakes.user_name, + ), + ) + def setUp(self): super(TestUserList, self).setUp() @@ -614,13 +566,8 @@ def test_user_list_no_options(self): **kwargs ) - collist = ['ID', 'Name'] - self.assertEqual(collist, columns) - datalist = (( - identity_fakes.user_id, - identity_fakes.user_name, - ), ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) def test_user_list_domain(self): arglist = [ @@ -644,13 +591,8 @@ def test_user_list_domain(self): **kwargs ) - collist = ['ID', 'Name'] - self.assertEqual(collist, columns) - datalist = (( - identity_fakes.user_id, - identity_fakes.user_name, - ), ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) def test_user_list_group(self): arglist = [ @@ -674,13 +616,8 @@ def test_user_list_group(self): **kwargs ) - collist = ['ID', 'Name'] - self.assertEqual(collist, columns) - datalist = (( - identity_fakes.user_id, - identity_fakes.user_name, - ), ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) def test_user_list_long(self): arglist = [ @@ -714,15 +651,17 @@ def test_user_list_long(self): 'Enabled', ] self.assertEqual(collist, columns) - datalist = (( - identity_fakes.user_id, - identity_fakes.user_name, - identity_fakes.project_id, - identity_fakes.domain_id, - '', - identity_fakes.user_email, - True, - ), ) + datalist = ( + ( + identity_fakes.user_id, + identity_fakes.user_name, + identity_fakes.project_id, + identity_fakes.domain_id, + '', + identity_fakes.user_email, + True, + ), + ) self.assertEqual(datalist, tuple(data)) def test_user_list_project(self): @@ -744,13 +683,8 @@ def test_user_list_project(self): self.role_assignments_mock.list.assert_called_with(**kwargs) self.users_mock.get.assert_called_with(identity_fakes.user_id) - collist = ['ID', 'Name'] - self.assertEqual(collist, columns) - datalist = (( - identity_fakes.user_id, - identity_fakes.user_name, - ), ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) class TestUserSet(TestUser): From 84174440fc9fd4679f3a72d24ed50f0b1927b91d Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Wed, 13 Jan 2016 11:36:49 -0600 Subject: [PATCH 0524/3095] Refactor "os availability zone list" Refactor the "os availability zone list" command to make it a common command instead of a compute-only command. Since availability zones are common to compute, volume and network (new), this refactoring allows availability zone support to be added for volume and network. In addition to the refactor, unit and functional tests were added. Change-Id: I63e9d41d229b21cd38e5a083493042c096d65e05 Partial-Bug: #1532945 --- .../tests/common/test_availability_zone.py | 25 ++++ .../v2 => common}/availability_zone.py | 0 .../tests/common/test_availability_zone.py | 113 ++++++++++++++++++ openstackclient/tests/compute/v2/fakes.py | 62 ++++++++++ setup.cfg | 3 +- 5 files changed, 201 insertions(+), 2 deletions(-) create mode 100644 functional/tests/common/test_availability_zone.py rename openstackclient/{compute/v2 => common}/availability_zone.py (100%) create mode 100644 openstackclient/tests/common/test_availability_zone.py diff --git a/functional/tests/common/test_availability_zone.py b/functional/tests/common/test_availability_zone.py new file mode 100644 index 0000000000..9296db23d7 --- /dev/null +++ b/functional/tests/common/test_availability_zone.py @@ -0,0 +1,25 @@ +# 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 functional.common import test + + +class AvailabilityZoneTests(test.TestCase): + """Functional tests for availability zone. """ + HEADERS = ["'Zone Name'"] + # So far, all components have the same default availability zone name. + DEFAULT_AZ_NAME = 'nova' + + def test_availability_zone_list(self): + opts = self.get_list_opts(self.HEADERS) + raw_output = self.openstack('availability zone list' + opts) + self.assertIn(self.DEFAULT_AZ_NAME, raw_output) diff --git a/openstackclient/compute/v2/availability_zone.py b/openstackclient/common/availability_zone.py similarity index 100% rename from openstackclient/compute/v2/availability_zone.py rename to openstackclient/common/availability_zone.py diff --git a/openstackclient/tests/common/test_availability_zone.py b/openstackclient/tests/common/test_availability_zone.py new file mode 100644 index 0000000000..35089d0623 --- /dev/null +++ b/openstackclient/tests/common/test_availability_zone.py @@ -0,0 +1,113 @@ +# 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 six + +from openstackclient.common import availability_zone +from openstackclient.tests.compute.v2 import fakes as compute_fakes +from openstackclient.tests import fakes +from openstackclient.tests import utils + + +def _build_compute_az_datalist(compute_az, long_datalist=False): + datalist = () + if not long_datalist: + datalist = ( + compute_az.zoneName, + 'available', + ) + else: + for host, services in six.iteritems(compute_az.hosts): + for service, state in six.iteritems(services): + datalist += ( + compute_az.zoneName, + 'available', + host, + service, + 'enabled :-) ' + state['updated_at'], + ) + return (datalist,) + + +class TestAvailabilityZone(utils.TestCommand): + + def setUp(self): + super(TestAvailabilityZone, self).setUp() + + compute_client = compute_fakes.FakeComputev2Client( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN, + ) + self.app.client_manager.compute = compute_client + + self.compute_azs_mock = compute_client.availability_zones + self.compute_azs_mock.reset_mock() + + +class TestAvailabilityZoneList(TestAvailabilityZone): + + compute_azs = \ + compute_fakes.FakeAvailabilityZone.create_availability_zones() + + def setUp(self): + super(TestAvailabilityZoneList, self).setUp() + + self.compute_azs_mock.list.return_value = self.compute_azs + + # Get the command object to test + self.cmd = availability_zone.ListAvailabilityZone(self.app, None) + + def test_availability_zone_list_no_options(self): + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + self.compute_azs_mock.list.assert_called_with() + + columnslist = ('Zone Name', 'Zone Status') + self.assertEqual(columnslist, columns) + datalist = () + for compute_az in self.compute_azs: + datalist += _build_compute_az_datalist(compute_az) + self.assertEqual(datalist, tuple(data)) + + def test_availability_zone_list_long(self): + arglist = [ + '--long', + ] + verifylist = [ + ('long', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + self.compute_azs_mock.list.assert_called_with() + + columnslist = ( + 'Zone Name', + 'Zone Status', + 'Host Name', + 'Service Name', + 'Service Status', + ) + self.assertEqual(columnslist, columns) + datalist = () + for compute_az in self.compute_azs: + datalist += _build_compute_az_datalist(compute_az, + long_datalist=True) + self.assertEqual(datalist, tuple(data)) diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index ecf7f599ba..a90c9ee71a 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -88,6 +88,8 @@ class FakeComputev2Client(object): def __init__(self, **kwargs): + self.availability_zones = mock.Mock() + self.availability_zones.resource_class = fakes.FakeResource(None, {}) self.images = mock.Mock() self.images.resource_class = fakes.FakeResource(None, {}) self.servers = mock.Mock() @@ -289,3 +291,63 @@ def get_flavors(flavors=None, count=2): if flavors is None: flavors = FakeServer.create_flavors(count) return mock.MagicMock(side_effect=flavors) + + +class FakeAvailabilityZone(object): + """Fake one or more compute availability zones (AZs).""" + + @staticmethod + def create_one_availability_zone(attrs={}, methods={}): + """Create a fake AZ. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :return: + A FakeResource object with zoneName, zoneState, etc. + """ + # Set default attributes. + host_name = uuid.uuid4().hex + service_name = uuid.uuid4().hex + service_updated_at = uuid.uuid4().hex + availability_zone = { + 'zoneName': uuid.uuid4().hex, + 'zoneState': {'available': True}, + 'hosts': {host_name: {service_name: { + 'available': True, + 'active': True, + 'updated_at': service_updated_at, + }}}, + } + + # Overwrite default attributes. + availability_zone.update(attrs) + + availability_zone = fakes.FakeResource( + info=copy.deepcopy(availability_zone), + methods=methods, + loaded=True) + return availability_zone + + @staticmethod + def create_availability_zones(attrs={}, methods={}, count=2): + """Create multiple fake AZs. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :param int count: + The number of AZs to fake + :return: + A list of FakeResource objects faking the AZs + """ + availability_zones = [] + for i in range(0, count): + availability_zone = \ + FakeAvailabilityZone.create_one_availability_zone( + attrs, methods) + availability_zones.append(availability_zone) + + return availability_zones diff --git a/setup.cfg b/setup.cfg index 986a077151..4a0c302781 100644 --- a/setup.cfg +++ b/setup.cfg @@ -43,6 +43,7 @@ openstack.cli.base = volume = openstackclient.volume.client openstack.common = + availability_zone_list = openstackclient.common.availability_zone:ListAvailabilityZone configuration_show = openstackclient.common.configuration:ShowConfiguration extension_list = openstackclient.common.extension:ListExtension limits_show = openstackclient.common.limits:ShowLimits @@ -50,8 +51,6 @@ openstack.common = quota_show = openstackclient.common.quota:ShowQuota openstack.compute.v2 = - availability_zone_list = openstackclient.compute.v2.availability_zone:ListAvailabilityZone - compute_agent_create = openstackclient.compute.v2.agent:CreateAgent compute_agent_delete = openstackclient.compute.v2.agent:DeleteAgent compute_agent_list = openstackclient.compute.v2.agent:ListAgent From a8ec2ac49475c60c8e72a0fc3db6df778918bb49 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Thu, 14 Jan 2016 08:22:09 -0600 Subject: [PATCH 0525/3095] Support listing volume availability zones Update the "os availability zone list" command to support listing volume availability zones along with the currently listed compute availability zones. This adds a --compute and --volume option to the command in order to select the availability zones to list. By default, all availability zones are listed. If the Block Storage API does not support listing availability zones then an warning message will be issued. Change-Id: I8159509a41bd1fb1b4e77fdbb512cf64a5ac11a9 Closes-Bug: #1532945 --- .../command-objects/availability-zone.rst | 12 ++- doc/source/commands.rst | 2 +- openstackclient/common/availability_zone.py | 75 +++++++++++--- .../tests/common/test_availability_zone.py | 99 +++++++++++++++++-- openstackclient/tests/volume/v2/fakes.py | 54 ++++++++++ .../notes/bug-1532945-1a5485b8d0ebddb8.yaml | 8 ++ 6 files changed, 224 insertions(+), 26 deletions(-) create mode 100644 releasenotes/notes/bug-1532945-1a5485b8d0ebddb8.yaml diff --git a/doc/source/command-objects/availability-zone.rst b/doc/source/command-objects/availability-zone.rst index 3743523088..8c021529c8 100644 --- a/doc/source/command-objects/availability-zone.rst +++ b/doc/source/command-objects/availability-zone.rst @@ -2,7 +2,7 @@ availability zone ================= -Compute v2 +Compute v2, Block Storage v2 availability zone list ---------------------- @@ -13,8 +13,18 @@ List availability zones and their status .. code:: bash os availability zone list + [--compute] + [--volume] [--long] +.. option:: --compute + + List compute availability zones + +.. option:: --volume + + List volume availability zones + .. option:: --long List additional fields in output diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 621a0a9050..5427b27ebb 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -70,7 +70,7 @@ the API resources will be merged, as in the ``quota`` object that has options referring to both Compute and Volume quotas. * ``access token``: (**Identity**) long-lived OAuth-based token -* ``availability zone``: (**Compute**) a logical partition of hosts or block storage services +* ``availability zone``: (**Compute**, **Volume**) a logical partition of hosts or block storage services * ``aggregate``: (**Compute**) a grouping of servers * ``backup``: (**Volume**) a volume copy * ``catalog``: (**Identity**) service catalog diff --git a/openstackclient/common/availability_zone.py b/openstackclient/common/availability_zone.py index 0fe6c73acb..e72732e722 100644 --- a/openstackclient/common/availability_zone.py +++ b/openstackclient/common/availability_zone.py @@ -11,7 +11,7 @@ # under the License. # -"""Compute v2 Availability Zone action implementations""" +"""Availability Zone action implementations""" import copy import logging @@ -24,15 +24,19 @@ from openstackclient.i18n import _ # noqa -def _xform_availability_zone(az, include_extra): - result = [] - zone_info = {} +def _xform_common_availability_zone(az, zone_info): if hasattr(az, 'zoneState'): zone_info['zone_status'] = ('available' if az.zoneState['available'] else 'not available') if hasattr(az, 'zoneName'): zone_info['zone_name'] = az.zoneName + +def _xform_compute_availability_zone(az, include_extra): + result = [] + zone_info = {} + _xform_common_availability_zone(az, zone_info) + if not include_extra: result.append(zone_info) return result @@ -58,6 +62,14 @@ def _xform_availability_zone(az, include_extra): return result +def _xform_volume_availability_zone(az): + result = [] + zone_info = {} + _xform_common_availability_zone(az, zone_info) + result.append(zone_info) + return result + + class ListAvailabilityZone(lister.Lister): """List availability zones and their status""" @@ -65,6 +77,16 @@ class ListAvailabilityZone(lister.Lister): def get_parser(self, prog_name): parser = super(ListAvailabilityZone, self).get_parser(prog_name) + parser.add_argument( + '--compute', + action='store_true', + default=False, + help='List compute availability zones') + parser.add_argument( + '--volume', + action='store_true', + default=False, + help='List volume availability zones') parser.add_argument( '--long', action='store_true', @@ -73,15 +95,7 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) - def take_action(self, parsed_args): - - if parsed_args.long: - columns = ('Zone Name', 'Zone Status', - 'Host Name', 'Service Name', 'Service Status') - else: - columns = ('Zone Name', 'Zone Status') - + def get_compute_availability_zones(self, parsed_args): compute_client = self.app.client_manager.compute try: data = compute_client.availability_zones.list() @@ -94,7 +108,40 @@ def take_action(self, parsed_args): # Argh, the availability zones are not iterable... result = [] for zone in data: - result += _xform_availability_zone(zone, parsed_args.long) + result += _xform_compute_availability_zone(zone, parsed_args.long) + return result + + def get_volume_availability_zones(self, parsed_args): + volume_client = self.app.client_manager.volume + try: + data = volume_client.availability_zones.list() + except Exception: + message = "Availability zones list not supported by " \ + "Block Storage API" + self.log.warning(message) + + result = [] + for zone in data: + result += _xform_volume_availability_zone(zone) + return result + + @utils.log_method(log) + def take_action(self, parsed_args): + + if parsed_args.long: + columns = ('Zone Name', 'Zone Status', + 'Host Name', 'Service Name', 'Service Status') + else: + columns = ('Zone Name', 'Zone Status') + + # Show everything by default. + show_all = (not parsed_args.compute and not parsed_args.volume) + + result = [] + if parsed_args.compute or show_all: + result += self.get_compute_availability_zones(parsed_args) + if parsed_args.volume or show_all: + result += self.get_volume_availability_zones(parsed_args) return (columns, (utils.get_dict_properties( diff --git a/openstackclient/tests/common/test_availability_zone.py b/openstackclient/tests/common/test_availability_zone.py index 35089d0623..232b56c99c 100644 --- a/openstackclient/tests/common/test_availability_zone.py +++ b/openstackclient/tests/common/test_availability_zone.py @@ -17,6 +17,7 @@ from openstackclient.tests.compute.v2 import fakes as compute_fakes from openstackclient.tests import fakes from openstackclient.tests import utils +from openstackclient.tests.volume.v2 import fakes as volume_fakes def _build_compute_az_datalist(compute_az, long_datalist=False): @@ -39,6 +40,22 @@ def _build_compute_az_datalist(compute_az, long_datalist=False): return (datalist,) +def _build_volume_az_datalist(volume_az, long_datalist=False): + datalist = () + if not long_datalist: + datalist = ( + volume_az.zoneName, + 'available', + ) + else: + datalist = ( + volume_az.zoneName, + 'available', + '', '', '', + ) + return (datalist,) + + class TestAvailabilityZone(utils.TestCommand): def setUp(self): @@ -53,16 +70,37 @@ def setUp(self): self.compute_azs_mock = compute_client.availability_zones self.compute_azs_mock.reset_mock() + volume_client = volume_fakes.FakeVolumeClient( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN, + ) + self.app.client_manager.volume = volume_client + + self.volume_azs_mock = volume_client.availability_zones + self.volume_azs_mock.reset_mock() + class TestAvailabilityZoneList(TestAvailabilityZone): compute_azs = \ compute_fakes.FakeAvailabilityZone.create_availability_zones() + volume_azs = \ + volume_fakes.FakeAvailabilityZone.create_availability_zones(count=1) + + short_columnslist = ('Zone Name', 'Zone Status') + long_columnslist = ( + 'Zone Name', + 'Zone Status', + 'Host Name', + 'Service Name', + 'Service Status', + ) def setUp(self): super(TestAvailabilityZoneList, self).setUp() self.compute_azs_mock.list.return_value = self.compute_azs + self.volume_azs_mock.list.return_value = self.volume_azs # Get the command object to test self.cmd = availability_zone.ListAvailabilityZone(self.app, None) @@ -76,12 +114,14 @@ def test_availability_zone_list_no_options(self): columns, data = self.cmd.take_action(parsed_args) self.compute_azs_mock.list.assert_called_with() + self.volume_azs_mock.list.assert_called_with() - columnslist = ('Zone Name', 'Zone Status') - self.assertEqual(columnslist, columns) + self.assertEqual(self.short_columnslist, columns) datalist = () for compute_az in self.compute_azs: datalist += _build_compute_az_datalist(compute_az) + for volume_az in self.volume_azs: + datalist += _build_volume_az_datalist(volume_az) self.assertEqual(datalist, tuple(data)) def test_availability_zone_list_long(self): @@ -97,17 +137,56 @@ def test_availability_zone_list_long(self): columns, data = self.cmd.take_action(parsed_args) self.compute_azs_mock.list.assert_called_with() + self.volume_azs_mock.list.assert_called_with() - columnslist = ( - 'Zone Name', - 'Zone Status', - 'Host Name', - 'Service Name', - 'Service Status', - ) - self.assertEqual(columnslist, columns) + self.assertEqual(self.long_columnslist, columns) datalist = () for compute_az in self.compute_azs: datalist += _build_compute_az_datalist(compute_az, long_datalist=True) + for volume_az in self.volume_azs: + datalist += _build_volume_az_datalist(volume_az, + long_datalist=True) + self.assertEqual(datalist, tuple(data)) + + def test_availability_zone_list_compute(self): + arglist = [ + '--compute', + ] + verifylist = [ + ('compute', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + self.compute_azs_mock.list.assert_called_with() + self.volume_azs_mock.list.assert_not_called() + + self.assertEqual(self.short_columnslist, columns) + datalist = () + for compute_az in self.compute_azs: + datalist += _build_compute_az_datalist(compute_az) + self.assertEqual(datalist, tuple(data)) + + def test_availability_zone_list_volume(self): + arglist = [ + '--volume', + ] + verifylist = [ + ('volume', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + self.compute_azs_mock.list.assert_not_called() + self.volume_azs_mock.list.assert_called_with() + + self.assertEqual(self.short_columnslist, columns) + datalist = () + for volume_az in self.volume_azs: + datalist += _build_volume_az_datalist(volume_az) self.assertEqual(datalist, tuple(data)) diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py index 60cec33558..2e58e58df9 100644 --- a/openstackclient/tests/volume/v2/fakes.py +++ b/openstackclient/tests/volume/v2/fakes.py @@ -202,6 +202,8 @@ def __init__(self, **kwargs): self.restores.resource_class = fakes.FakeResource(None, {}) self.qos_specs = mock.Mock() self.qos_specs.resource_class = fakes.FakeResource(None, {}) + self.availability_zones = mock.Mock() + self.availability_zones.resource_class = fakes.FakeResource(None, {}) self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] @@ -304,3 +306,55 @@ def get_volumes(volumes=None, count=2): volumes = FakeVolume.create_volumes(count) return mock.MagicMock(side_effect=volumes) + + +class FakeAvailabilityZone(object): + """Fake one or more volume availability zones (AZs).""" + + @staticmethod + def create_one_availability_zone(attrs={}, methods={}): + """Create a fake AZ. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :return: + A FakeResource object with zoneName, zoneState, etc. + """ + # Set default attributes. + availability_zone = { + 'zoneName': uuid.uuid4().hex, + 'zoneState': {'available': True}, + } + + # Overwrite default attributes. + availability_zone.update(attrs) + + availability_zone = fakes.FakeResource( + info=copy.deepcopy(availability_zone), + methods=methods, + loaded=True) + return availability_zone + + @staticmethod + def create_availability_zones(attrs={}, methods={}, count=2): + """Create multiple fake AZs. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :param int count: + The number of AZs to fake + :return: + A list of FakeResource objects faking the AZs + """ + availability_zones = [] + for i in range(0, count): + availability_zone = \ + FakeAvailabilityZone.create_one_availability_zone( + attrs, methods) + availability_zones.append(availability_zone) + + return availability_zones diff --git a/releasenotes/notes/bug-1532945-1a5485b8d0ebddb8.yaml b/releasenotes/notes/bug-1532945-1a5485b8d0ebddb8.yaml new file mode 100644 index 0000000000..bee8102bcb --- /dev/null +++ b/releasenotes/notes/bug-1532945-1a5485b8d0ebddb8.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Add volume support to `os availability zone list` + [Bug `1532945 `_] + + * New `--compute` option to only list compute availability zones. + * New `--volume` option to only list volume availability zones. From cf2de9af79cedd51ca080f5a6521997c05647418 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 18 Dec 2015 14:16:45 -0600 Subject: [PATCH 0526/3095] Change --owner to --project in image commands * image create and image set now use --project to specify an alternate project to own the image * --owner is still silently accepted but deprecated, add warning messages * --project and --owner are mutually exclusive to prevent precedence issues Closes Bug: 1527833 Change-Id: Iccb1a1d9175ef9b5edcd79d294607db12641c1f0 --- doc/source/command-objects/image.rst | 28 ++++--- openstackclient/image/v1/image.py | 65 +++++++++++--- openstackclient/image/v2/image.py | 84 ++++++++++++++----- openstackclient/tests/image/v1/test_image.py | 9 +- openstackclient/tests/image/v2/test_image.py | 63 ++++++++++++-- .../notes/bug-1527833-42cde11d28b09ac3.yaml | 7 ++ 6 files changed, 204 insertions(+), 52 deletions(-) create mode 100644 releasenotes/notes/bug-1527833-42cde11d28b09ac3.yaml diff --git a/doc/source/command-objects/image.rst b/doc/source/command-objects/image.rst index f1893efaec..1528676091 100644 --- a/doc/source/command-objects/image.rst +++ b/doc/source/command-objects/image.rst @@ -19,7 +19,6 @@ Create/upload an image [--store ] [--container-format ] [--disk-format ] - [--owner ] [--size ] [--min-disk ] [--min-ram ] @@ -33,7 +32,7 @@ Create/upload an image [--public | --private] [--property [...] ] [--tag [...] ] - [--project-domain ] + [--project [--project-domain ]] .. option:: --id @@ -54,10 +53,6 @@ Create/upload an image Image disk format (default: raw) -.. option:: --owner - - Image owner project name or ID - .. option:: --size Image size, in bytes (only used with --location and --copy-from) @@ -128,11 +123,18 @@ Create/upload an image .. versionadded:: 2 +.. option:: --project + + Set an alternate project on this image (name or ID). + Previously known as `--owner`. + .. option:: --project-domain Domain the project belongs to (name or ID). This can be used in case collisions between project names exist. + .. versionadded:: 2 + .. describe:: New image name @@ -225,7 +227,6 @@ Set image properties os image set [--name ] - [--owner ] [--min-disk ] [--min-ram ] [--container-format ] @@ -250,17 +251,13 @@ Set image properties [--os-version ] [--ramdisk-id ] [--activate|--deactivate] - [--project-domain ] + [--project [--project-domain ]] .. option:: --name New image name -.. option:: --owner - - New image owner project (name or ID) - .. option:: --min-disk Minimum disk size needed to boot image, in gigabytes @@ -407,11 +404,18 @@ Set image properties .. versionadded:: 2 +.. option:: --project + + Set an alternate project on this image (name or ID). + Previously known as `--owner`. + .. option:: --project-domain Domain the project belongs to (name or ID). This can be used in case collisions between project names exist. + .. versionadded:: 2 + .. describe:: Image to modify (name or ID) diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index 0382501ef6..c18f3fc79d 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -35,6 +35,7 @@ from openstackclient.api import utils as api_utils from openstackclient.common import parseractions from openstackclient.common import utils +from openstackclient.i18n import _ # noqa DEFAULT_CONTAINER_FORMAT = 'bare' @@ -92,11 +93,6 @@ def get_parser(self, prog_name): help="Image disk format " "(default: %s)" % DEFAULT_DISK_FORMAT, ) - parser.add_argument( - "--owner", - metavar="", - help="Image owner project name or ID", - ) parser.add_argument( "--size", metavar="", @@ -178,12 +174,32 @@ def get_parser(self, prog_name): help="Set a property on this image " "(repeat option to set multiple properties)", ) + # NOTE(dtroyer): --owner is deprecated in Jan 2016 in an early + # 2.x release. Do not remove before Jan 2017 + # and a 3.x release. + project_group = parser.add_mutually_exclusive_group() + project_group.add_argument( + "--project", + metavar="", + help="Set an alternate project on this image (name or ID)", + ) + project_group.add_argument( + "--owner", + metavar="", + help=argparse.SUPPRESS, + ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) image_client = self.app.client_manager.image + if getattr(parsed_args, 'owner', None) is not None: + self.log.warning(_( + 'The --owner option is deprecated, ' + 'please use --project instead.' + )) + # Build an attribute dict from the parsed args, only include # attributes that were actually set on the command line kwargs = {} @@ -198,6 +214,12 @@ def take_action(self, parsed_args): # Only include a value in kwargs for attributes that are # actually present on the command line kwargs[attr] = val + + # Special case project option back to API attribute name 'owner' + val = getattr(parsed_args, 'project', None) + if val: + kwargs['owner'] = val + # Handle exclusive booleans with care # Avoid including attributes in kwargs if an option is not # present on the command line. These exclusive booleans are not @@ -383,7 +405,7 @@ def take_action(self, parsed_args): 'Status', 'Visibility', 'Protected', - 'Owner', + 'Project', 'Properties', ) else: @@ -476,11 +498,6 @@ def get_parser(self, prog_name): metavar="", help="New image name", ) - parser.add_argument( - "--owner", - metavar="", - help="New image owner project (name or ID)", - ) parser.add_argument( "--min-disk", metavar="", @@ -590,12 +607,32 @@ def get_parser(self, prog_name): metavar="", help="Image hash used for verification", ) + # NOTE(dtroyer): --owner is deprecated in Jan 2016 in an early + # 2.x release. Do not remove before Jan 2017 + # and a 3.x release. + project_group = parser.add_mutually_exclusive_group() + project_group.add_argument( + "--project", + metavar="", + help="Set an alternate project on this image (name or ID)", + ) + project_group.add_argument( + "--owner", + metavar="", + help=argparse.SUPPRESS, + ) return parser def take_action(self, parsed_args): self.log.debug("take_action(%s)", parsed_args) image_client = self.app.client_manager.image + if getattr(parsed_args, 'owner', None) is not None: + self.log.warning(_( + 'The --owner option is deprecated, ' + 'please use --project instead.' + )) + kwargs = {} copy_attrs = ('name', 'owner', 'min_disk', 'min_ram', 'properties', 'container_format', 'disk_format', 'size', 'store', @@ -607,6 +644,12 @@ def take_action(self, parsed_args): # Only include a value in kwargs for attributes that are # actually present on the command line kwargs[attr] = val + + # Special case project option back to API attribute name 'owner' + val = getattr(parsed_args, 'project', None) + if val: + kwargs['owner'] = val + # Handle exclusive booleans with care # Avoid including attributes in kwargs if an option is not # present on the command line. These exclusive booleans are not diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index ad536ba26b..3c1faf594c 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -28,6 +28,7 @@ from openstackclient.common import exceptions from openstackclient.common import parseractions from openstackclient.common import utils +from openstackclient.i18n import _ # noqa from openstackclient.identity import common @@ -147,11 +148,6 @@ def get_parser(self, prog_name): help="Image disk format " "(default: %s)" % DEFAULT_DISK_FORMAT, ) - parser.add_argument( - "--owner", - metavar="", - help="Image owner project name or ID", - ) parser.add_argument( "--min-disk", metavar="", @@ -220,6 +216,20 @@ def get_parser(self, prog_name): help="Set a tag on this image " "(repeat option to set multiple tags)", ) + # NOTE(dtroyer): --owner is deprecated in Jan 2016 in an early + # 2.x release. Do not remove before Jan 2017 + # and a 3.x release. + project_group = parser.add_mutually_exclusive_group() + project_group.add_argument( + "--project", + metavar="", + help="Set an alternate project on this image (name or ID)", + ) + project_group.add_argument( + "--owner", + metavar="", + help=argparse.SUPPRESS, + ) common.add_project_domain_option_to_parser(parser) for deadopt in self.deadopts: parser.add_argument( @@ -246,8 +256,7 @@ def take_action(self, parsed_args): kwargs = {} copy_attrs = ('name', 'id', 'container_format', 'disk_format', - 'min_disk', 'min_ram', - 'tags', 'owner') + 'min_disk', 'min_ram', 'tags') for attr in copy_attrs: if attr in parsed_args: val = getattr(parsed_args, attr, None) @@ -255,6 +264,7 @@ def take_action(self, parsed_args): # Only include a value in kwargs for attributes that # are actually present on the command line kwargs[attr] = val + # properties should get flattened into the general kwargs if getattr(parsed_args, 'properties', None): for k, v in six.iteritems(parsed_args.properties): @@ -275,6 +285,21 @@ def take_action(self, parsed_args): if parsed_args.private: kwargs['visibility'] = 'private' + # Handle deprecated --owner option + project_arg = parsed_args.project + if parsed_args.owner: + project_arg = parsed_args.owner + self.log.warning(_( + 'The --owner option is deprecated, ' + 'please use --project instead.' + )) + if project_arg: + kwargs['owner'] = common.find_project( + identity_client, + project_arg, + parsed_args.project_domain, + ).id + # open the file first to ensure any failures are handled before the # image is created fp = gc_utils.get_data_file(parsed_args) @@ -458,7 +483,7 @@ def take_action(self, parsed_args): 'Status', 'Visibility', 'Protected', - 'Owner', + 'Project', 'Tags', ) else: @@ -598,11 +623,6 @@ def get_parser(self, prog_name): metavar="", help="New image name" ) - parser.add_argument( - "--owner", - metavar="", - help="New image owner project (name or ID)", - ) parser.add_argument( "--min-disk", type=int, @@ -713,6 +733,20 @@ def get_parser(self, prog_name): action="store_true", help="Activate the image", ) + # NOTE(dtroyer): --owner is deprecated in Jan 2016 in an early + # 2.x release. Do not remove before Jan 2017 + # and a 3.x release. + project_group = parser.add_mutually_exclusive_group() + project_group.add_argument( + "--project", + metavar="", + help="Set an alternate project on this image (name or ID)", + ) + project_group.add_argument( + "--owner", + metavar="", + help=argparse.SUPPRESS, + ) common.add_project_domain_option_to_parser(parser) for deadopt in self.deadopts: parser.add_argument( @@ -738,7 +772,7 @@ def take_action(self, parsed_args): copy_attrs = ('architecture', 'container_format', 'disk_format', 'file', 'instance_id', 'kernel_id', 'locations', 'min_disk', 'min_ram', 'name', 'os_distro', 'os_version', - 'owner', 'prefix', 'progress', 'ramdisk_id', 'tags') + 'prefix', 'progress', 'ramdisk_id', 'tags') for attr in copy_attrs: if attr in parsed_args: val = getattr(parsed_args, attr, None) @@ -767,6 +801,21 @@ def take_action(self, parsed_args): if parsed_args.private: kwargs['visibility'] = 'private' + # Handle deprecated --owner option + project_arg = parsed_args.project + if parsed_args.owner: + project_arg = parsed_args.owner + self.log.warning(_( + 'The --owner option is deprecated, ' + 'please use --project instead.' + )) + if project_arg: + kwargs['owner'] = common.find_project( + identity_client, + project_arg, + parsed_args.project_domain, + ).id + # Checks if anything that requires getting the image if not (kwargs or parsed_args.deactivate or parsed_args.activate): self.log.warning("No arguments specified") @@ -790,13 +839,6 @@ def take_action(self, parsed_args): # Tags should be extended, but duplicates removed kwargs['tags'] = list(set(image.tags).union(set(parsed_args.tags))) - if parsed_args.owner: - kwargs['owner'] = common.find_project( - identity_client, - parsed_args.owner, - parsed_args.project_domain, - ).id - try: image = image_client.images.update(image.id, **kwargs) except Exception as e: diff --git a/openstackclient/tests/image/v1/test_image.py b/openstackclient/tests/image/v1/test_image.py index 4d964bdb56..60b7f3093d 100644 --- a/openstackclient/tests/image/v1/test_image.py +++ b/openstackclient/tests/image/v1/test_image.py @@ -103,6 +103,7 @@ def test_image_reserve_options(self): '--min-ram', '4', '--protected', '--private', + '--project', 'q', image_fakes.image_name, ] verifylist = [ @@ -114,6 +115,7 @@ def test_image_reserve_options(self): ('unprotected', False), ('public', False), ('private', True), + ('project', 'q'), ('name', image_fakes.image_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -130,6 +132,7 @@ def test_image_reserve_options(self): min_ram=4, protected=True, is_public=False, + owner='q', data=mock.ANY, ) @@ -358,7 +361,7 @@ def test_image_list_long_option(self): 'Status', 'Visibility', 'Protected', - 'Owner', + 'Project', 'Properties', ) @@ -484,22 +487,22 @@ def test_image_set_no_options(self): def test_image_set_options(self): arglist = [ '--name', 'new-name', - '--owner', 'new-owner', '--min-disk', '2', '--min-ram', '4', '--container-format', 'ovf', '--disk-format', 'vmdk', '--size', '35165824', + '--project', 'new-owner', image_fakes.image_name, ] verifylist = [ ('name', 'new-name'), - ('owner', 'new-owner'), ('min_disk', 2), ('min_ram', 4), ('container_format', 'ovf'), ('disk_format', 'vmdk'), ('size', 35165824), + ('project', 'new-owner'), ('image', image_fakes.image_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index 0218241397..3534a3a441 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -131,11 +131,11 @@ def test_image_reserve_options(self, mock_open): '--disk-format', 'fs', '--min-disk', '10', '--min-ram', '4', - '--owner', self.new_image.owner, ('--protected' if self.new_image.protected else '--unprotected'), ('--private' if self.new_image.visibility == 'private' else '--public'), + '--project', self.new_image.owner, '--project-domain', identity_fakes.domain_id, self.new_image.name, ] @@ -144,11 +144,11 @@ def test_image_reserve_options(self, mock_open): ('disk_format', 'fs'), ('min_disk', 10), ('min_ram', 4), - ('owner', self.new_image.owner), ('protected', self.new_image.protected), ('unprotected', not self.new_image.protected), ('public', self.new_image.visibility == 'public'), ('private', self.new_image.visibility == 'private'), + ('project', self.new_image.owner), ('project_domain', identity_fakes.domain_id), ('name', self.new_image.name), ] @@ -217,6 +217,40 @@ def test_image_create_with_unexist_owner(self): parsed_args, ) + def test_image_create_with_unexist_project(self): + self.project_mock.get.side_effect = exceptions.NotFound(None) + self.project_mock.find.side_effect = exceptions.NotFound(None) + + arglist = [ + '--container-format', 'ovf', + '--disk-format', 'fs', + '--min-disk', '10', + '--min-ram', '4', + '--protected', + '--private', + '--project', 'unexist_owner', + image_fakes.image_name, + ] + verifylist = [ + ('container_format', 'ovf'), + ('disk_format', 'fs'), + ('min_disk', 10), + ('min_ram', 4), + ('protected', True), + ('unprotected', False), + ('public', False), + ('private', True), + ('project', 'unexist_owner'), + ('name', image_fakes.image_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args, + ) + @mock.patch('glanceclient.common.utils.get_data_file', name='Open') def test_image_create_file(self, mock_open): mock_file = mock.MagicMock(name='File') @@ -575,7 +609,7 @@ def test_image_list_long_option(self): 'Status', 'Visibility', 'Protected', - 'Owner', + 'Project', 'Tags', ) @@ -755,21 +789,21 @@ def setUp(self): def test_image_set_options(self): arglist = [ '--name', 'new-name', - '--owner', identity_fakes.project_name, '--min-disk', '2', '--min-ram', '4', '--container-format', 'ovf', '--disk-format', 'vmdk', + '--project', identity_fakes.project_name, '--project-domain', identity_fakes.domain_id, image_fakes.image_id, ] verifylist = [ ('name', 'new-name'), - ('owner', identity_fakes.project_name), ('min_disk', 2), ('min_ram', 4), ('container_format', 'ovf'), ('disk_format', 'vmdk'), + ('project', identity_fakes.project_name), ('project_domain', identity_fakes.domain_id), ('image', image_fakes.image_id), ] @@ -809,6 +843,25 @@ def test_image_set_with_unexist_owner(self): exceptions.CommandError, self.cmd.take_action, parsed_args) + def test_image_set_with_unexist_project(self): + self.project_mock.get.side_effect = exceptions.NotFound(None) + self.project_mock.find.side_effect = exceptions.NotFound(None) + + arglist = [ + '--project', 'unexist_owner', + image_fakes.image_id, + ] + verifylist = [ + ('project', 'unexist_owner'), + ('image', image_fakes.image_id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, parsed_args) + def test_image_set_bools1(self): arglist = [ '--protected', diff --git a/releasenotes/notes/bug-1527833-42cde11d28b09ac3.yaml b/releasenotes/notes/bug-1527833-42cde11d28b09ac3.yaml new file mode 100644 index 0000000000..21f748bbe0 --- /dev/null +++ b/releasenotes/notes/bug-1527833-42cde11d28b09ac3.yaml @@ -0,0 +1,7 @@ +--- +other: + - Change the `--owner` option to `--project` in `image create` + and `image set` commands. `--owner` is deprecated and no longer + documented but is still accepted; a warning message will be shown + if it is used. + [Bug `1527833 `_] From a3c617172657b559000e6565eafde07648422a96 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Fri, 15 Jan 2016 10:25:41 -0600 Subject: [PATCH 0527/3095] Doc: Network is supported for extension object Update extension object documentation to note that network is also supported. Change-Id: Ifcebdb4dc6bf56482700887c09d89ac64eab321c --- doc/source/commands.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 621a0a9050..fc54c2edf7 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -82,7 +82,7 @@ referring to both Compute and Volume quotas. * ``domain``: (**Identity**) a grouping of projects * ``ec2 credentials``: (**Identity**) AWS EC2-compatible credentials * ``endpoint``: (**Identity**) the base URL used to contact a specific service -* ``extension``: (**Compute**, **Identity**, **Volume**) OpenStack server API extensions +* ``extension``: (**Compute**, **Identity**, **Network**, **Volume**) OpenStack server API extensions * ``federation protocol``: (**Identity**) the underlying protocol used while federating identities * ``flavor``: (**Compute**) predefined server configurations: ram, root disk, etc * ``group``: (**Identity**) a grouping of users From 86fcd67468708a1edd23f3ff6eb852b43bc618bd Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Mon, 18 Jan 2016 22:45:59 +0000 Subject: [PATCH 0528/3095] Updated from global requirements Change-Id: I68ad139345040dee6e5b0e8a7477acb84aa1a06b --- requirements.txt | 24 ++++++++++++------------ test-requirements.txt | 38 +++++++++++++++++++------------------- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/requirements.txt b/requirements.txt index a4e2948134..120505ef9a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,20 +1,20 @@ # The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -pbr>=1.6 -six>=1.9.0 +pbr>=1.6 # Apache-2.0 +six>=1.9.0 # MIT -Babel>=1.3 +Babel>=1.3 # BSD cliff>=1.15.0 # Apache-2.0 -keystoneauth1>=2.1.0 -openstacksdk -os-client-config>=1.13.1 +keystoneauth1>=2.1.0 # Apache-2.0 +openstacksdk # Apache-2.0 +os-client-config>=1.13.1 # Apache-2.0 oslo.config>=3.2.0 # Apache-2.0 oslo.i18n>=1.5.0 # Apache-2.0 -oslo.utils>=3.2.0 # Apache-2.0 -python-glanceclient>=1.2.0 -python-keystoneclient!=1.8.0,>=1.6.0 -python-novaclient!=2.33.0,>=2.29.0 -python-cinderclient>=1.3.1 -requests!=2.9.0,>=2.8.1 +oslo.utils>=3.4.0 # Apache-2.0 +python-glanceclient>=1.2.0 # Apache-2.0 +python-keystoneclient!=1.8.0,>=1.6.0 # Apache-2.0 +python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0 +python-cinderclient>=1.3.1 # Apache-2.0 +requests!=2.9.0,>=2.8.1 # Apache-2.0 stevedore>=1.5.0 # Apache-2.0 diff --git a/test-requirements.txt b/test-requirements.txt index 39a8f71c42..1c495a4f40 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -3,28 +3,28 @@ # process, which may cause wedges in the gate later. hacking<0.11,>=0.10.0 -coverage>=3.6 -discover -fixtures>=1.3.1 -mock>=1.2 +coverage>=3.6 # Apache-2.0 +discover # BSD +fixtures>=1.3.1 # Apache-2.0/BSD +mock>=1.2 # BSD oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 reno>=0.1.1 # Apache2 requests-mock>=0.7.0 # Apache-2.0 -sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 -os-testr>=0.4.1 -testrepository>=0.0.18 -testtools>=1.4.0 -WebOb>=1.2.3 -tempest-lib>=0.13.0 +sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 # BSD +os-testr>=0.4.1 # Apache-2.0 +testrepository>=0.0.18 # Apache-2.0/BSD +testtools>=1.4.0 # MIT +WebOb>=1.2.3 # MIT +tempest-lib>=0.13.0 # Apache-2.0 # Install these to generate sphinx autodocs -python-barbicanclient>=3.3.0 -python-congressclient>=1.0.0 -python-designateclient>=1.5.0 -python-heatclient>=0.6.0 -python-ironicclient>=0.8.0 -python-mistralclient>=1.0.0 -python-saharaclient>=0.10.0 -python-tuskarclient>=0.1.17 -python-zaqarclient>=0.3.0 +python-barbicanclient>=3.3.0 # Apache-2.0 +python-congressclient>=1.0.0 # Apache-2.0 +python-designateclient>=1.5.0 # Apache-2.0 +python-heatclient>=0.6.0 # Apache-2.0 +python-ironicclient>=0.8.0 # Apache-2.0 +python-mistralclient>=1.0.0 # Apache-2.0 +python-saharaclient>=0.10.0 # Apache-2.0 +python-tuskarclient>=0.1.17 # Apache-2.0 +python-zaqarclient>=0.3.0 # Apache-2.0 From 5dbca5f56a4c6f94fa3fee0430a67dc1d79be380 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Tue, 19 Jan 2016 06:03:29 +0000 Subject: [PATCH 0529/3095] Imported Translations from Zanata For more information about this automatic import see: https://wiki.openstack.org/wiki/Translations/Infrastructure Change-Id: I1270bace355443e5980d897f82745dd5a28628a2 --- .../de/LC_MESSAGES/python-openstackclient.po | 45 +-- .../locale/python-openstackclient.pot | 295 ++++++++++-------- .../LC_MESSAGES/python-openstackclient.po | 43 +-- 3 files changed, 190 insertions(+), 193 deletions(-) diff --git a/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po b/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po index 983abdb561..1c94b9d4b2 100644 --- a/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po +++ b/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po @@ -1,4 +1,4 @@ -# German translations for python-openstackclient. +# Translations template for python-openstackclient. # Copyright (C) 2015 ORGANIZATION # This file is distributed under the same license as the # python-openstackclient project. @@ -9,18 +9,19 @@ # OpenStack Infra , 2015. #zanata msgid "" msgstr "" -"Project-Id-Version: python-openstackclient 1.8.1.dev15\n" +"Project-Id-Version: python-openstackclient 2.0.1.dev168\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-11-04 06:04+0000\n" +"POT-Creation-Date: 2016-01-19 02:20+0000\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2015-09-19 07:10+0000\n" "Last-Translator: Andreas Jaeger \n" "Language: de\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Generated-By: Babel 2.0\n" +"X-Generator: Zanata 3.7.3\n" "Language-Team: German\n" -"Plural-Forms: nplurals=2; plural=(n != 1)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.1.1\n" msgid "" "\n" @@ -126,15 +127,6 @@ msgstr "" "Anmeldedaten in Projekt erstellen (Name oder Kennung; Standard: aktuell " "authentifiziertes Projekt)" -msgid "Create server from this image" -msgstr "Server aus diesem Abbild erstellen" - -msgid "Create server from this volume" -msgstr "Server aus diesem Datenträger erstellen" - -msgid "Create server with this flavor" -msgstr "Server mit dieser Variante erstellen" - msgid "Credentials access key" msgstr "Anmeldedaten-Zugriffsschlüssel" @@ -358,9 +350,6 @@ msgstr "" "Zu hinzufügende/ändernde Eigenschaft für diesen Server (wiederholen Sie die " "Option, um mehrere Eigenschaften festzulegen)" -msgid "Recreate server from this image" -msgstr "Server aus diesem Abbild neu erstellen" - msgid "Region ID to delete" msgstr "Zu löschende Regionskennung" @@ -419,15 +408,9 @@ msgstr "Zu entfernende Rolle (Name oder Kennung)" msgid "Role(s) to delete (name or ID)" msgstr "Zu löschende Rolle(n) (Name oder Kennung)" -msgid "Search by flavor" -msgstr "Nach Variante suchen" - msgid "Search by hostname" msgstr "Nach Hostname suchen" -msgid "Search by image" -msgstr "Nach Bild suchen" - msgid "Search by server status" msgstr "Nach Serverstatus suchen" @@ -437,11 +420,6 @@ msgstr "Nach Benutzer suchen (nur Administrator) (Name oder Kennung)" msgid "Security group to add (name or ID)" msgstr "Zu hinzufügende Sicherheitsgruppe (Name oder Kennung)" -msgid "Security group to assign to this server (repeat for multiple groups)" -msgstr "" -"Zu diesem Server zuweisende Sicherheitsgruppe (für mehrere Gruppen " -"wiederholen)" - msgid "Select an availability zone for the server" msgstr "Wählen Sie eine Verfügbarkeitszone für den Server aus" @@ -609,11 +587,6 @@ msgstr "Warten Sie, bis die Wiederherstellung abgeschlossen ist" msgid "Wait for resize to complete" msgstr "Warten Sie, bis die Größenänderung abgeschlossen ist" -msgid "can't create server with port specified since neutron not enabled" -msgstr "" -"Server mit dem angegebenen Port kann nicht erstellt werden, da Neutron nicht " -"aktiviert ist" - msgid "either net-id or port-id should be specified but not both" msgstr "entweder net-id oder port-id sollten angegeben sein, aber nicht beide" diff --git a/python-openstackclient/locale/python-openstackclient.pot b/python-openstackclient/locale/python-openstackclient.pot index cc4f1a1aae..afc892669d 100644 --- a/python-openstackclient/locale/python-openstackclient.pot +++ b/python-openstackclient/locale/python-openstackclient.pot @@ -1,22 +1,22 @@ # Translations template for python-openstackclient. -# Copyright (C) 2015 ORGANIZATION +# Copyright (C) 2016 ORGANIZATION # This file is distributed under the same license as the # python-openstackclient project. -# FIRST AUTHOR , 2015. +# FIRST AUTHOR , 2016. # #, fuzzy msgid "" msgstr "" -"Project-Id-Version: python-openstackclient 1.8.1.dev15\n" +"Project-Id-Version: python-openstackclient 2.0.1.dev168\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-11-04 06:04+0000\n" +"POT-Creation-Date: 2016-01-19 06:03+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.1.1\n" +"Generated-By: Babel 2.2.0\n" #: openstackclient/api/auth.py:144 msgid "Set a username with --os-username, OS_USERNAME, or auth.username\n" @@ -48,7 +48,7 @@ msgid "Set a service URL, with --os-url, OS_URL or auth.url\n" msgstr "" #: openstackclient/compute/v2/availability_zone.py:72 -#: openstackclient/compute/v2/server.py:718 +#: openstackclient/compute/v2/server.py:737 #: openstackclient/identity/v2_0/endpoint.py:114 #: openstackclient/identity/v2_0/project.py:145 #: openstackclient/identity/v2_0/service.py:128 @@ -56,89 +56,85 @@ msgstr "" msgid "List additional fields in output" msgstr "" -#: openstackclient/compute/v2/server.py:161 -#: openstackclient/compute/v2/server.py:198 -#: openstackclient/compute/v2/server.py:549 -#: openstackclient/compute/v2/server.py:810 -#: openstackclient/compute/v2/server.py:844 -#: openstackclient/compute/v2/server.py:927 -#: openstackclient/compute/v2/server.py:951 -#: openstackclient/compute/v2/server.py:1006 -#: openstackclient/compute/v2/server.py:1098 -#: openstackclient/compute/v2/server.py:1138 -#: openstackclient/compute/v2/server.py:1164 -#: openstackclient/compute/v2/server.py:1229 -#: openstackclient/compute/v2/server.py:1253 -#: openstackclient/compute/v2/server.py:1312 -#: openstackclient/compute/v2/server.py:1349 -#: openstackclient/compute/v2/server.py:1496 -#: openstackclient/compute/v2/server.py:1520 -#: openstackclient/compute/v2/server.py:1544 -#: openstackclient/compute/v2/server.py:1568 -#: openstackclient/compute/v2/server.py:1592 +#: openstackclient/compute/v2/server.py:183 +#: openstackclient/compute/v2/server.py:219 +#: openstackclient/compute/v2/server.py:569 +#: openstackclient/compute/v2/server.py:923 +#: openstackclient/compute/v2/server.py:1031 +#: openstackclient/compute/v2/server.py:1086 +#: openstackclient/compute/v2/server.py:1179 +#: openstackclient/compute/v2/server.py:1219 +#: openstackclient/compute/v2/server.py:1245 +#: openstackclient/compute/v2/server.py:1336 +#: openstackclient/compute/v2/server.py:1420 +#: openstackclient/compute/v2/server.py:1457 +#: openstackclient/compute/v2/server.py:1732 +#: openstackclient/compute/v2/server.py:1756 msgid "Server (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:166 +#: openstackclient/compute/v2/server.py:188 msgid "Security group to add (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:203 +#: openstackclient/compute/v2/server.py:224 msgid "Volume to add (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:208 +#: openstackclient/compute/v2/server.py:229 msgid "Server internal device name for volume" msgstr "" -#: openstackclient/compute/v2/server.py:248 -#: openstackclient/compute/v2/server.py:1258 +#: openstackclient/compute/v2/server.py:265 +#: openstackclient/compute/v2/server.py:1341 msgid "New server name" msgstr "" -#: openstackclient/compute/v2/server.py:256 -msgid "Create server from this image" +#: openstackclient/compute/v2/server.py:273 +msgid "Create server from this image (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:261 -msgid "Create server from this volume" +#: openstackclient/compute/v2/server.py:278 +msgid "Create server from this volume (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:267 -msgid "Create server with this flavor" +#: openstackclient/compute/v2/server.py:284 +msgid "Create server with this flavor (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:274 -msgid "Security group to assign to this server (repeat for multiple groups)" +#: openstackclient/compute/v2/server.py:291 +msgid "" +"Security group to assign to this server (name or ID) (repeat for multiple" +" groups)" msgstr "" -#: openstackclient/compute/v2/server.py:280 +#: openstackclient/compute/v2/server.py:297 msgid "Keypair to inject into this server (optional extension)" msgstr "" -#: openstackclient/compute/v2/server.py:286 +#: openstackclient/compute/v2/server.py:303 msgid "Set a property on this server (repeat for multiple values)" msgstr "" -#: openstackclient/compute/v2/server.py:294 +#: openstackclient/compute/v2/server.py:311 msgid "File to inject into image before boot (repeat for multiple files)" msgstr "" -#: openstackclient/compute/v2/server.py:300 +#: openstackclient/compute/v2/server.py:317 msgid "User data file to serve from the metadata server" msgstr "" -#: openstackclient/compute/v2/server.py:305 +#: openstackclient/compute/v2/server.py:322 msgid "Select an availability zone for the server" msgstr "" -#: openstackclient/compute/v2/server.py:312 +#: openstackclient/compute/v2/server.py:329 msgid "" "Map block devices; map is ::: " "(optional extension)" msgstr "" -#: openstackclient/compute/v2/server.py:322 +#: openstackclient/compute/v2/server.py:339 msgid "" "Create a NIC on the server. Specify option multiple times to create " "multiple NICs. Either net-id or port-id must be provided, but not both. " @@ -147,321 +143,367 @@ msgid "" "-fixed-ip: IPv6 fixed address for NIC (optional)." msgstr "" -#: openstackclient/compute/v2/server.py:335 +#: openstackclient/compute/v2/server.py:352 msgid "Hints for the scheduler (optional extension)" msgstr "" -#: openstackclient/compute/v2/server.py:341 +#: openstackclient/compute/v2/server.py:358 msgid "" "Use specified volume as the config drive, or 'True' to use an ephemeral " "drive" msgstr "" -#: openstackclient/compute/v2/server.py:349 +#: openstackclient/compute/v2/server.py:366 msgid "Minimum number of servers to launch (default=1)" msgstr "" -#: openstackclient/compute/v2/server.py:356 +#: openstackclient/compute/v2/server.py:373 msgid "Maximum number of servers to launch (default=1)" msgstr "" -#: openstackclient/compute/v2/server.py:361 +#: openstackclient/compute/v2/server.py:378 msgid "Wait for build to complete" msgstr "" -#: openstackclient/compute/v2/server.py:401 +#: openstackclient/compute/v2/server.py:418 msgid "min instances should be <= max instances" msgstr "" -#: openstackclient/compute/v2/server.py:404 +#: openstackclient/compute/v2/server.py:421 msgid "min instances should be > 0" msgstr "" -#: openstackclient/compute/v2/server.py:407 +#: openstackclient/compute/v2/server.py:424 msgid "max instances should be > 0" msgstr "" -#: openstackclient/compute/v2/server.py:443 -msgid "either net-id or port-id should be specified but not both" +#: openstackclient/compute/v2/server.py:453 +msgid "Volume name or ID must be specified if --block-device-mapping is specified" msgstr "" #: openstackclient/compute/v2/server.py:465 -msgid "can't create server with port specified since neutron not enabled" +msgid "either net-id or port-id should be specified but not both" +msgstr "" + +#: openstackclient/compute/v2/server.py:485 +msgid "can't create server with port specified since network endpoint not enabled" msgstr "" -#: openstackclient/compute/v2/server.py:530 +#: openstackclient/compute/v2/server.py:550 #, python-format msgid "Error creating server: %s" msgstr "" -#: openstackclient/compute/v2/server.py:532 +#: openstackclient/compute/v2/server.py:552 msgid "" "\n" "Error creating server" msgstr "" -#: openstackclient/compute/v2/server.py:554 +#: openstackclient/compute/v2/server.py:574 msgid "Name of new image (default is server name)" msgstr "" -#: openstackclient/compute/v2/server.py:559 +#: openstackclient/compute/v2/server.py:579 msgid "Wait for image create to complete" msgstr "" -#: openstackclient/compute/v2/server.py:589 +#: openstackclient/compute/v2/server.py:609 #, python-format msgid "Error creating server snapshot: %s" msgstr "" -#: openstackclient/compute/v2/server.py:591 +#: openstackclient/compute/v2/server.py:611 msgid "" "\n" "Error creating server snapshot" msgstr "" -#: openstackclient/compute/v2/server.py:613 +#: openstackclient/compute/v2/server.py:633 msgid "Server(s) to delete (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:618 +#: openstackclient/compute/v2/server.py:638 msgid "Wait for delete to complete" msgstr "" -#: openstackclient/compute/v2/server.py:637 +#: openstackclient/compute/v2/server.py:657 #, python-format msgid "Error deleting server: %s" msgstr "" -#: openstackclient/compute/v2/server.py:639 +#: openstackclient/compute/v2/server.py:659 msgid "" "\n" "Error deleting server" msgstr "" -#: openstackclient/compute/v2/server.py:654 +#: openstackclient/compute/v2/server.py:673 msgid "Only return instances that match the reservation" msgstr "" -#: openstackclient/compute/v2/server.py:659 +#: openstackclient/compute/v2/server.py:678 msgid "Regular expression to match IP addresses" msgstr "" -#: openstackclient/compute/v2/server.py:664 +#: openstackclient/compute/v2/server.py:683 msgid "Regular expression to match IPv6 addresses" msgstr "" -#: openstackclient/compute/v2/server.py:669 +#: openstackclient/compute/v2/server.py:688 msgid "Regular expression to match names" msgstr "" -#: openstackclient/compute/v2/server.py:674 +#: openstackclient/compute/v2/server.py:693 msgid "Regular expression to match instance name (admin only)" msgstr "" -#: openstackclient/compute/v2/server.py:680 +#: openstackclient/compute/v2/server.py:699 msgid "Search by server status" msgstr "" -#: openstackclient/compute/v2/server.py:685 -msgid "Search by flavor" +#: openstackclient/compute/v2/server.py:704 +msgid "Search by flavor (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:690 -msgid "Search by image" +#: openstackclient/compute/v2/server.py:709 +msgid "Search by image (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:695 +#: openstackclient/compute/v2/server.py:714 msgid "Search by hostname" msgstr "" -#: openstackclient/compute/v2/server.py:701 +#: openstackclient/compute/v2/server.py:720 msgid "Include all projects (admin only)" msgstr "" -#: openstackclient/compute/v2/server.py:711 +#: openstackclient/compute/v2/server.py:730 msgid "Search by user (admin only) (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:849 +#: openstackclient/compute/v2/server.py:888 +msgid "Server(s) to lock (name or ID)" +msgstr "" + +#: openstackclient/compute/v2/server.py:928 msgid "Target hostname" msgstr "" -#: openstackclient/compute/v2/server.py:857 +#: openstackclient/compute/v2/server.py:936 msgid "Perform a shared live migration (default)" msgstr "" -#: openstackclient/compute/v2/server.py:863 +#: openstackclient/compute/v2/server.py:942 msgid "Perform a block live migration" msgstr "" -#: openstackclient/compute/v2/server.py:870 +#: openstackclient/compute/v2/server.py:949 msgid "Allow disk over-commit on the destination host" msgstr "" -#: openstackclient/compute/v2/server.py:877 +#: openstackclient/compute/v2/server.py:956 msgid "Do not over-commit disk on the destination host (default)" msgstr "" -#: openstackclient/compute/v2/server.py:883 -#: openstackclient/compute/v2/server.py:1184 +#: openstackclient/compute/v2/server.py:962 +#: openstackclient/compute/v2/server.py:1265 msgid "Wait for resize to complete" msgstr "" -#: openstackclient/compute/v2/server.py:911 -#: openstackclient/compute/v2/server.py:1209 +#: openstackclient/compute/v2/server.py:990 +#: openstackclient/compute/v2/server.py:1290 msgid "Complete\n" msgstr "" -#: openstackclient/compute/v2/server.py:913 +#: openstackclient/compute/v2/server.py:992 msgid "" "\n" "Error migrating server" msgstr "" -#: openstackclient/compute/v2/server.py:960 +#: openstackclient/compute/v2/server.py:1007 +msgid "Server(s) to pause (name or ID)" +msgstr "" + +#: openstackclient/compute/v2/server.py:1040 msgid "Perform a hard reboot" msgstr "" -#: openstackclient/compute/v2/server.py:968 +#: openstackclient/compute/v2/server.py:1048 msgid "Perform a soft reboot" msgstr "" -#: openstackclient/compute/v2/server.py:973 +#: openstackclient/compute/v2/server.py:1053 msgid "Wait for reboot to complete" msgstr "" -#: openstackclient/compute/v2/server.py:990 +#: openstackclient/compute/v2/server.py:1070 msgid "" "\n" "Reboot complete\n" msgstr "" -#: openstackclient/compute/v2/server.py:992 +#: openstackclient/compute/v2/server.py:1072 msgid "" "\n" "Error rebooting server\n" msgstr "" -#: openstackclient/compute/v2/server.py:1012 -msgid "Recreate server from this image" +#: openstackclient/compute/v2/server.py:1091 +msgid "" +"Recreate server from the specified image (name or ID). Defaults to the " +"currently used one." msgstr "" -#: openstackclient/compute/v2/server.py:1022 +#: openstackclient/compute/v2/server.py:1102 msgid "Wait for rebuild to complete" msgstr "" -#: openstackclient/compute/v2/server.py:1043 +#: openstackclient/compute/v2/server.py:1124 msgid "" "\n" "Complete\n" msgstr "" -#: openstackclient/compute/v2/server.py:1045 +#: openstackclient/compute/v2/server.py:1126 msgid "" "\n" "Error rebuilding server" msgstr "" -#: openstackclient/compute/v2/server.py:1062 +#: openstackclient/compute/v2/server.py:1143 msgid "Name or ID of server to use" msgstr "" -#: openstackclient/compute/v2/server.py:1067 +#: openstackclient/compute/v2/server.py:1148 msgid "Name or ID of security group to remove from server" msgstr "" -#: openstackclient/compute/v2/server.py:1103 +#: openstackclient/compute/v2/server.py:1184 msgid "Volume to remove (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:1169 +#: openstackclient/compute/v2/server.py:1250 msgid "Resize server to specified flavor" msgstr "" -#: openstackclient/compute/v2/server.py:1174 +#: openstackclient/compute/v2/server.py:1255 msgid "Confirm server resize is complete" msgstr "" -#: openstackclient/compute/v2/server.py:1179 +#: openstackclient/compute/v2/server.py:1260 msgid "Restore server state before resize" msgstr "" -#: openstackclient/compute/v2/server.py:1211 +#: openstackclient/compute/v2/server.py:1292 msgid "" "\n" "Error resizing server" msgstr "" -#: openstackclient/compute/v2/server.py:1263 +#: openstackclient/compute/v2/server.py:1311 +msgid "Server(s) to resume (name or ID)" +msgstr "" + +#: openstackclient/compute/v2/server.py:1346 msgid "Set new root password (interactive only)" msgstr "" -#: openstackclient/compute/v2/server.py:1269 +#: openstackclient/compute/v2/server.py:1352 msgid "" "Property to add/change for this server (repeat option to set multiple " "properties)" msgstr "" -#: openstackclient/compute/v2/server.py:1293 +#: openstackclient/compute/v2/server.py:1376 msgid "New password: " msgstr "" -#: openstackclient/compute/v2/server.py:1294 +#: openstackclient/compute/v2/server.py:1377 msgid "Retype new password: " msgstr "" -#: openstackclient/compute/v2/server.py:1298 +#: openstackclient/compute/v2/server.py:1381 msgid "Passwords do not match, password unchanged" msgstr "" -#: openstackclient/compute/v2/server.py:1318 +#: openstackclient/compute/v2/server.py:1396 +msgid "Server(s) to shelve (name or ID)" +msgstr "" + +#: openstackclient/compute/v2/server.py:1426 msgid "Display server diagnostics information" msgstr "" -#: openstackclient/compute/v2/server.py:1331 +#: openstackclient/compute/v2/server.py:1439 msgid "Error retrieving diagnostics data" msgstr "" -#: openstackclient/compute/v2/server.py:1354 +#: openstackclient/compute/v2/server.py:1462 msgid "Login name (ssh -l option)" msgstr "" -#: openstackclient/compute/v2/server.py:1366 +#: openstackclient/compute/v2/server.py:1474 msgid "Destination port (ssh -p option)" msgstr "" -#: openstackclient/compute/v2/server.py:1378 +#: openstackclient/compute/v2/server.py:1486 msgid "Private key file (ssh -i option)" msgstr "" -#: openstackclient/compute/v2/server.py:1389 +#: openstackclient/compute/v2/server.py:1497 msgid "Options in ssh_config(5) format (ssh -o option)" msgstr "" -#: openstackclient/compute/v2/server.py:1403 +#: openstackclient/compute/v2/server.py:1511 msgid "Use only IPv4 addresses" msgstr "" -#: openstackclient/compute/v2/server.py:1410 +#: openstackclient/compute/v2/server.py:1518 msgid "Use only IPv6 addresses" msgstr "" -#: openstackclient/compute/v2/server.py:1419 +#: openstackclient/compute/v2/server.py:1527 msgid "Use public IP address" msgstr "" -#: openstackclient/compute/v2/server.py:1427 +#: openstackclient/compute/v2/server.py:1535 msgid "Use private IP address" msgstr "" -#: openstackclient/compute/v2/server.py:1434 +#: openstackclient/compute/v2/server.py:1542 msgid "Use other IP address (public, private, etc)" msgstr "" -#: openstackclient/compute/v2/server.py:1599 +#: openstackclient/compute/v2/server.py:1605 +msgid "Server(s) to start (name or ID)" +msgstr "" + +#: openstackclient/compute/v2/server.py:1630 +msgid "Server(s) to stop (name or ID)" +msgstr "" + +#: openstackclient/compute/v2/server.py:1655 +msgid "Server(s) to suspend (name or ID)" +msgstr "" + +#: openstackclient/compute/v2/server.py:1681 +msgid "Server(s) to unlock (name or ID)" +msgstr "" + +#: openstackclient/compute/v2/server.py:1707 +msgid "Server(s) to unpause (name or ID)" +msgstr "" + +#: openstackclient/compute/v2/server.py:1763 msgid "Property key to remove from server (repeat to unset multiple values)" msgstr "" +#: openstackclient/compute/v2/server.py:1794 +msgid "Server(s) to unshelve (name or ID)" +msgstr "" + #: openstackclient/identity/v2_0/catalog.py:75 #: openstackclient/identity/v3/catalog.py:72 msgid "Service to display (type or name)" @@ -562,6 +604,7 @@ msgid "Project(s) to delete (name or ID)" msgstr "" #: openstackclient/identity/v2_0/project.py:173 +#: openstackclient/identity/v2_0/project.py:313 msgid "Project to modify (name or ID)" msgstr "" @@ -585,6 +628,10 @@ msgstr "" msgid "Project to display (name or ID)" msgstr "" +#: openstackclient/identity/v2_0/project.py:320 +msgid "Unset a project property (repeat option to unset multiple properties)" +msgstr "" + #: openstackclient/identity/v2_0/role.py:41 msgid "Role to add to : (name or ID)" msgstr "" diff --git a/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po b/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po index 301912c748..2603266684 100644 --- a/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po +++ b/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po @@ -1,4 +1,4 @@ -# Chinese (Traditional, Taiwan) translations for python-openstackclient. +# Translations template for python-openstackclient. # Copyright (C) 2015 ORGANIZATION # This file is distributed under the same license as the # python-openstackclient project. @@ -7,18 +7,19 @@ # OpenStack Infra , 2015. #zanata msgid "" msgstr "" -"Project-Id-Version: python-openstackclient 1.8.1.dev15\n" +"Project-Id-Version: python-openstackclient 2.0.1.dev168\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2015-11-04 06:04+0000\n" +"POT-Creation-Date: 2016-01-19 02:20+0000\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2015-06-14 06:41+0000\n" "Last-Translator: openstackjenkins \n" -"Language: zh_Hant_TW\n" +"Language: zh-TW\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"Generated-By: Babel 2.0\n" +"X-Generator: Zanata 3.7.3\n" "Language-Team: Chinese (Taiwan)\n" -"Plural-Forms: nplurals=1; plural=0\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.1.1\n" msgid "" "\n" @@ -88,15 +89,6 @@ msgstr "完成\n" msgid "Confirm server resize is complete" msgstr "確認調整雲實例容量完成" -msgid "Create server from this image" -msgstr "從此映像檔新增雲實例" - -msgid "Create server from this volume" -msgstr "從此雲硬碟新增雲實例" - -msgid "Create server with this flavor" -msgstr "以這個虛擬硬體樣板新增雲實例" - msgid "Credentials access key" msgstr "憑鑰存取密鑰" @@ -303,9 +295,6 @@ msgid "" "properties)" msgstr "要加入這個雲實例的屬性(重復這選項來設定多個屬性)" -msgid "Recreate server from this image" -msgstr "從此映像檔重建雲實例" - msgid "Region ID to delete" msgstr "要刪除的地區識別號" @@ -363,24 +352,15 @@ msgstr "要移除的角色(名稱或識別號)" msgid "Role(s) to delete (name or ID)" msgstr "要刪除的角色(名稱或識別號)" -msgid "Search by flavor" -msgstr "以虛擬硬體樣板來尋找" - msgid "Search by hostname" msgstr "以主機名稱來尋找" -msgid "Search by image" -msgstr "以映像檔來尋找" - msgid "Search by server status" msgstr "以雲實例狀態來尋找" msgid "Security group to add (name or ID)" msgstr "要加入的安全性群組(名稱或識別號)" -msgid "Security group to assign to this server (repeat for multiple groups)" -msgstr "要指定到此雲實例的安全性群組(為多個群組重復指定)" - msgid "Select an availability zone for the server" msgstr "為雲實例選擇可用的區域。" @@ -502,9 +482,6 @@ msgstr "等待重建完成" msgid "Wait for resize to complete" msgstr "等待調整容量完成" -msgid "can't create server with port specified since neutron not enabled" -msgstr "Neutron 未啟用時,不能以指定的接口來新增雲實例" - msgid "either net-id or port-id should be specified but not both" msgstr "任選網路識別號或接口識別號,但不能兩者都指定" From 4d4368bb266c5fc8f4b13593a7f519ab60547e39 Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Tue, 19 Jan 2016 16:15:59 -0700 Subject: [PATCH 0530/3095] Initialize activation status activation_status throws an exception if the image set fails because it is not initialized. Change-Id: Iff6d5a8844eed954fb1c0bcea96118b99ac6bcf4 --- openstackclient/image/v2/image.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index ad536ba26b..106da9ef10 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -775,6 +775,7 @@ def take_action(self, parsed_args): image = utils.find_resource( image_client.images, parsed_args.image) + activation_status = None if parsed_args.deactivate: image_client.images.deactivate(image.id) activation_status = "deactivated" From 3168e2297d0b48c240b22fb6d8c1b7d05def1e6b Mon Sep 17 00:00:00 2001 From: SaiKiran Date: Tue, 22 Dec 2015 19:41:15 +0530 Subject: [PATCH 0531/3095] Add support to delete the ports This patch adds "port delete" command to osc. Change-Id: I5c92b2f573249df4e6551506584ccafb4ff290b2 Implements: blueprint neutron-client Partial-Bug: #1519909 --- doc/source/command-objects/port.rst | 21 +++++ doc/source/commands.rst | 1 + openstackclient/network/v2/port.py | 42 ++++++++++ openstackclient/tests/network/v2/fakes.py | 77 +++++++++++++++++++ openstackclient/tests/network/v2/test_port.py | 53 +++++++++++++ ...-port-delete-command-4789d3881b186cfc.yaml | 5 ++ setup.cfg | 1 + 7 files changed, 200 insertions(+) create mode 100644 doc/source/command-objects/port.rst create mode 100644 openstackclient/network/v2/port.py create mode 100644 openstackclient/tests/network/v2/test_port.py create mode 100644 releasenotes/notes/add-port-delete-command-4789d3881b186cfc.yaml diff --git a/doc/source/command-objects/port.rst b/doc/source/command-objects/port.rst new file mode 100644 index 0000000000..78677332be --- /dev/null +++ b/doc/source/command-objects/port.rst @@ -0,0 +1,21 @@ +==== +port +==== + +Network v2 + +port delete +----------- + +Delete port(s) + +.. program:: port delete +.. code:: bash + + os port delete + [ ...] + +.. _port_delete-port: +.. describe:: + + Port(s) to delete (name or ID) diff --git a/doc/source/commands.rst b/doc/source/commands.rst index fc54c2edf7..f7e8b444d4 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -101,6 +101,7 @@ referring to both Compute and Volume quotas. * ``network``: (**Network**) - a virtual network for connecting servers and other resources * ``object``: (**Object Storage**) a single file in the Object Storage * ``policy``: (**Identity**) determines authorization +* ``port``: (**Network**) - a virtual port for connecting servers and other resources to a network * ``project``: (**Identity**) owns a group of resources * ``quota``: (**Compute**, **Volume**) resource usage restrictions * ``region``: (**Identity**) a subset of an OpenStack deployment diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py new file mode 100644 index 0000000000..ad906a287a --- /dev/null +++ b/openstackclient/network/v2/port.py @@ -0,0 +1,42 @@ +# 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. +# + +"""Port action implementations""" + +import logging + +from cliff import command + + +class DeletePort(command.Command): + """Delete port(s)""" + + log = logging.getLogger(__name__ + '.DeletePort') + + def get_parser(self, prog_name): + parser = super(DeletePort, self).get_parser(prog_name) + parser.add_argument( + 'port', + metavar="", + nargs="+", + help=("Port(s) to delete (name or ID)") + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + client = self.app.client_manager.network + + for port in parsed_args.port: + res = client.find_port(port) + client.delete_port(res) diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 80760a7792..de885c62ab 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -145,6 +145,83 @@ def get_networks(networks=None, count=2): return mock.MagicMock(side_effect=networks) +class FakePort(object): + """Fake one or more ports.""" + + @staticmethod + def create_one_port(attrs={}, methods={}): + """Create a fake port. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :return: + A FakeResource object, with id, name, admin_state_up, + status, tenant_id + """ + # Set default attributes. + port_attrs = { + 'id': 'port-id-' + uuid.uuid4().hex, + 'name': 'port-name-' + uuid.uuid4().hex, + 'status': 'ACTIVE', + 'admin_state_up': True, + 'tenant_id': 'project-id-' + uuid.uuid4().hex, + } + + # Overwrite default attributes. + port_attrs.update(attrs) + + # Set default methods. + port_methods = {} + + # Overwrite default methods. + port_methods.update(methods) + + port = fakes.FakeResource(info=copy.deepcopy(port_attrs), + methods=copy.deepcopy(port_methods), + loaded=True) + return port + + @staticmethod + def create_ports(attrs={}, methods={}, count=2): + """Create multiple fake ports. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :param int count: + The number of ports to fake + :return: + A list of FakeResource objects faking the ports + """ + ports = [] + for i in range(0, count): + ports.append(FakePort.create_one_port(attrs, methods)) + + return ports + + @staticmethod + def get_ports(ports=None, count=2): + """Get an iterable MagicMock object with a list of faked ports. + + If ports list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List ports: + A list of FakeResource objects faking ports + :param int count: + The number of ports to fake + :return: + An iterable Mock object with side_effect set to a list of faked + ports + """ + if ports is None: + ports = FakePort.create_ports(count) + return mock.MagicMock(side_effect=ports) + + class FakeRouter(object): """Fake one or more routers.""" diff --git a/openstackclient/tests/network/v2/test_port.py b/openstackclient/tests/network/v2/test_port.py new file mode 100644 index 0000000000..a1ddefa1de --- /dev/null +++ b/openstackclient/tests/network/v2/test_port.py @@ -0,0 +1,53 @@ +# 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 port +from openstackclient.tests.network.v2 import fakes as network_fakes + + +class TestPort(network_fakes.TestNetworkV2): + + def setUp(self): + super(TestPort, self).setUp() + + # Get a shortcut to the network client + self.network = self.app.client_manager.network + + +class TestDeletePort(TestPort): + + # The port to delete. + _port = network_fakes.FakePort.create_one_port() + + def setUp(self): + super(TestDeletePort, self).setUp() + + self.network.delete_port = mock.Mock(return_value=None) + self.network.find_port = mock.Mock(return_value=self._port) + # Get the command object to test + self.cmd = port.DeletePort(self.app, self.namespace) + + def test_delete(self): + arglist = [ + self._port.name, + ] + verifylist = [ + ('port', [self._port.name]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.network.delete_port.assert_called_with(self._port) + self.assertIsNone(result) diff --git a/releasenotes/notes/add-port-delete-command-4789d3881b186cfc.yaml b/releasenotes/notes/add-port-delete-command-4789d3881b186cfc.yaml new file mode 100644 index 0000000000..dd1e703d6a --- /dev/null +++ b/releasenotes/notes/add-port-delete-command-4789d3881b186cfc.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add support for the ``port delete`` command. + [Bug `1519909 `_] diff --git a/setup.cfg b/setup.cfg index 986a077151..7e0f0b4c8e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -333,6 +333,7 @@ openstack.network.v2 = network_list = openstackclient.network.v2.network:ListNetwork network_set = openstackclient.network.v2.network:SetNetwork network_show = openstackclient.network.v2.network:ShowNetwork + port_delete = openstackclient.network.v2.port:DeletePort router_create = openstackclient.network.v2.router:CreateRouter router_delete = openstackclient.network.v2.router:DeleteRouter router_list = openstackclient.network.v2.router:ListRouter From 604d8589ee0634af6f3f44c8579ff7ddaceb407d Mon Sep 17 00:00:00 2001 From: SaiKiran Date: Tue, 12 Jan 2016 20:39:02 +0530 Subject: [PATCH 0532/3095] Refactor: Abstract columns and datalist out in volume test cases columns and datalist has been set in each test case in volume, which is not necessary. This patch abstract it out and remove all redundant code. Change-Id: I3a09d5d2db86da986bdcfbf2310978ced181017d --- .../tests/volume/v1/test_qos_specs.py | 46 +-- .../tests/volume/v1/test_volume.py | 302 ++++------------- .../tests/volume/v2/test_backup.py | 41 ++- .../tests/volume/v2/test_qos_specs.py | 45 +-- .../tests/volume/v2/test_snapshot.py | 30 +- openstackclient/tests/volume/v2/test_type.py | 55 ++-- .../tests/volume/v2/test_volume.py | 310 ++++-------------- 7 files changed, 226 insertions(+), 603 deletions(-) diff --git a/openstackclient/tests/volume/v1/test_qos_specs.py b/openstackclient/tests/volume/v1/test_qos_specs.py index c2e6c0afc8..7ecc8ee829 100644 --- a/openstackclient/tests/volume/v1/test_qos_specs.py +++ b/openstackclient/tests/volume/v1/test_qos_specs.py @@ -69,6 +69,18 @@ def test_qos_associate(self): class TestQosCreate(TestQos): + + columns = ( + 'consumer', + 'id', + 'name', + ) + datalist = ( + volume_fakes.qos_consumer, + volume_fakes.qos_id, + volume_fakes.qos_name + ) + def setUp(self): super(TestQosCreate, self).setUp() @@ -97,12 +109,7 @@ def test_qos_create_without_properties(self): {'consumer': volume_fakes.qos_default_consumer} ) - collist = ( - 'consumer', - 'id', - 'name' - ) - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = ( volume_fakes.qos_default_consumer, volume_fakes.qos_id, @@ -133,19 +140,8 @@ def test_qos_create_with_consumer(self): volume_fakes.qos_name, {'consumer': volume_fakes.qos_consumer} ) - - collist = ( - 'consumer', - 'id', - 'name' - ) - self.assertEqual(collist, columns) - datalist = ( - volume_fakes.qos_consumer, - volume_fakes.qos_id, - volume_fakes.qos_name - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_qos_create_with_properties(self): self.qos_mock.create.return_value = fakes.FakeResource( @@ -176,17 +172,11 @@ def test_qos_create_with_properties(self): specs ) - collist = ( - 'consumer', - 'id', - 'name', + columns = self.columns + ( 'specs', ) - self.assertEqual(collist, columns) - datalist = ( - volume_fakes.qos_consumer, - volume_fakes.qos_id, - volume_fakes.qos_name, + self.assertEqual(columns, columns) + datalist = self.datalist + ( volume_fakes.qos_specs, ) self.assertEqual(datalist, data) diff --git a/openstackclient/tests/volume/v1/test_volume.py b/openstackclient/tests/volume/v1/test_volume.py index 70ff50de6d..33255aacc1 100644 --- a/openstackclient/tests/volume/v1/test_volume.py +++ b/openstackclient/tests/volume/v1/test_volume.py @@ -49,6 +49,29 @@ def setUp(self): class TestVolumeCreate(TestVolume): + columns = ( + 'attach_status', + 'availability_zone', + 'display_description', + 'display_name', + 'id', + 'properties', + 'size', + 'status', + 'type', + ) + datalist = ( + 'detached', + volume_fakes.volume_zone, + volume_fakes.volume_description, + volume_fakes.volume_name, + volume_fakes.volume_id, + volume_fakes.volume_metadata_str, + volume_fakes.volume_size, + volume_fakes.volume_status, + volume_fakes.volume_type, + ) + def setUp(self): super(TestVolumeCreate, self).setUp() @@ -93,31 +116,8 @@ def test_volume_create_min_options(self): None, None, ) - - collist = ( - 'attach_status', - 'availability_zone', - 'display_description', - 'display_name', - 'id', - 'properties', - 'size', - 'status', - 'type', - ) - self.assertEqual(collist, columns) - datalist = ( - 'detached', - volume_fakes.volume_zone, - volume_fakes.volume_description, - volume_fakes.volume_name, - volume_fakes.volume_id, - volume_fakes.volume_metadata_str, - volume_fakes.volume_size, - volume_fakes.volume_status, - volume_fakes.volume_type, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_volume_create_options(self): arglist = [ @@ -158,30 +158,8 @@ def test_volume_create_options(self): None, ) - collist = ( - 'attach_status', - 'availability_zone', - 'display_description', - 'display_name', - 'id', - 'properties', - 'size', - 'status', - 'type', - ) - self.assertEqual(collist, columns) - datalist = ( - 'detached', - volume_fakes.volume_zone, - volume_fakes.volume_description, - volume_fakes.volume_name, - volume_fakes.volume_id, - volume_fakes.volume_metadata_str, - volume_fakes.volume_size, - volume_fakes.volume_status, - volume_fakes.volume_type, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_volume_create_user_project_id(self): # Return a project @@ -233,30 +211,8 @@ def test_volume_create_user_project_id(self): None, ) - collist = ( - 'attach_status', - 'availability_zone', - 'display_description', - 'display_name', - 'id', - 'properties', - 'size', - 'status', - 'type', - ) - self.assertEqual(collist, columns) - datalist = ( - 'detached', - volume_fakes.volume_zone, - volume_fakes.volume_description, - volume_fakes.volume_name, - volume_fakes.volume_id, - volume_fakes.volume_metadata_str, - volume_fakes.volume_size, - volume_fakes.volume_status, - volume_fakes.volume_type, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_volume_create_user_project_name(self): # Return a project @@ -308,30 +264,8 @@ def test_volume_create_user_project_name(self): None, ) - collist = ( - 'attach_status', - 'availability_zone', - 'display_description', - 'display_name', - 'id', - 'properties', - 'size', - 'status', - 'type', - ) - self.assertEqual(collist, columns) - datalist = ( - 'detached', - volume_fakes.volume_zone, - volume_fakes.volume_description, - volume_fakes.volume_name, - volume_fakes.volume_id, - volume_fakes.volume_metadata_str, - volume_fakes.volume_size, - volume_fakes.volume_status, - volume_fakes.volume_type, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_volume_create_properties(self): arglist = [ @@ -369,30 +303,8 @@ def test_volume_create_properties(self): None, ) - collist = ( - 'attach_status', - 'availability_zone', - 'display_description', - 'display_name', - 'id', - 'properties', - 'size', - 'status', - 'type', - ) - self.assertEqual(collist, columns) - datalist = ( - 'detached', - volume_fakes.volume_zone, - volume_fakes.volume_description, - volume_fakes.volume_name, - volume_fakes.volume_id, - volume_fakes.volume_metadata_str, - volume_fakes.volume_size, - volume_fakes.volume_status, - volume_fakes.volume_type, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_volume_create_image_id(self): self.images_mock.get.return_value = fakes.FakeResource( @@ -435,30 +347,8 @@ def test_volume_create_image_id(self): volume_fakes.image_id, ) - collist = ( - 'attach_status', - 'availability_zone', - 'display_description', - 'display_name', - 'id', - 'properties', - 'size', - 'status', - 'type', - ) - self.assertEqual(collist, columns) - datalist = ( - 'detached', - volume_fakes.volume_zone, - volume_fakes.volume_description, - volume_fakes.volume_name, - volume_fakes.volume_id, - volume_fakes.volume_metadata_str, - volume_fakes.volume_size, - volume_fakes.volume_status, - volume_fakes.volume_type, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_volume_create_image_name(self): self.images_mock.get.return_value = fakes.FakeResource( @@ -501,34 +391,29 @@ def test_volume_create_image_name(self): volume_fakes.image_id, ) - collist = ( - 'attach_status', - 'availability_zone', - 'display_description', - 'display_name', - 'id', - 'properties', - 'size', - 'status', - 'type', - ) - self.assertEqual(collist, columns) - datalist = ( - 'detached', - volume_fakes.volume_zone, - volume_fakes.volume_description, - volume_fakes.volume_name, - volume_fakes.volume_id, - volume_fakes.volume_metadata_str, - volume_fakes.volume_size, - volume_fakes.volume_status, - volume_fakes.volume_type, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) class TestVolumeList(TestVolume): + columns = ( + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Attached to', + ) + datalist = ( + ( + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_status, + volume_fakes.volume_size, + '', + ), + ) + def setUp(self): super(TestVolumeList, self).setUp() @@ -555,23 +440,8 @@ def test_volume_list_no_options(self): columns, data = self.cmd.take_action(parsed_args) - collist = ( - 'ID', - 'Display Name', - 'Status', - 'Size', - 'Attached to', - ) - self.assertEqual(collist, columns) - - datalist = (( - volume_fakes.volume_id, - volume_fakes.volume_name, - volume_fakes.volume_status, - volume_fakes.volume_size, - '', - ), ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) def test_volume_list_name(self): arglist = [ @@ -586,24 +456,8 @@ def test_volume_list_name(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - - collist = ( - 'ID', - 'Display Name', - 'Status', - 'Size', - 'Attached to', - ) - self.assertEqual(collist, tuple(columns)) - - datalist = (( - volume_fakes.volume_id, - volume_fakes.volume_name, - volume_fakes.volume_status, - volume_fakes.volume_size, - '', - ), ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, tuple(columns)) + self.assertEqual(self.datalist, tuple(data)) def test_volume_list_status(self): arglist = [ @@ -618,24 +472,8 @@ def test_volume_list_status(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - - collist = ( - 'ID', - 'Display Name', - 'Status', - 'Size', - 'Attached to', - ) - self.assertEqual(collist, tuple(columns)) - - datalist = (( - volume_fakes.volume_id, - volume_fakes.volume_name, - volume_fakes.volume_status, - volume_fakes.volume_size, - '', - ), ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, tuple(columns)) + self.assertEqual(self.datalist, tuple(data)) def test_volume_list_all_projects(self): arglist = [ @@ -650,24 +488,8 @@ def test_volume_list_all_projects(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - - collist = ( - 'ID', - 'Display Name', - 'Status', - 'Size', - 'Attached to', - ) - self.assertEqual(collist, columns) - - datalist = (( - volume_fakes.volume_id, - volume_fakes.volume_name, - volume_fakes.volume_status, - volume_fakes.volume_size, - '', - ), ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, tuple(columns)) + self.assertEqual(self.datalist, tuple(data)) def test_volume_list_long(self): arglist = [ diff --git a/openstackclient/tests/volume/v2/test_backup.py b/openstackclient/tests/volume/v2/test_backup.py index 7af22e8a45..dc1d78776f 100644 --- a/openstackclient/tests/volume/v2/test_backup.py +++ b/openstackclient/tests/volume/v2/test_backup.py @@ -166,6 +166,24 @@ def test_backup_restore(self): class TestBackupList(TestBackup): + + columns = [ + 'ID', + 'Name', + 'Description', + 'Status', + 'Size', + ] + datalist = ( + ( + volume_fakes.backup_id, + volume_fakes.backup_name, + volume_fakes.backup_description, + volume_fakes.backup_status, + volume_fakes.backup_size + ), + ) + def setUp(self): super(TestBackupList, self).setUp() @@ -193,17 +211,8 @@ def test_backup_list_without_options(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - collist = ['ID', 'Name', 'Description', 'Status', 'Size'] - self.assertEqual(collist, columns) - - datalist = (( - volume_fakes.backup_id, - volume_fakes.backup_name, - volume_fakes.backup_description, - volume_fakes.backup_status, - volume_fakes.backup_size - ),) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) def test_backup_list_with_options(self): arglist = ["--long"] @@ -212,9 +221,13 @@ def test_backup_list_with_options(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - collist = ['ID', 'Name', 'Description', 'Status', 'Size', - 'Availability Zone', 'Volume', 'Container'] - self.assertEqual(collist, columns) + columns = self.columns + [ + 'Availability Zone', + 'Volume', + 'Container', + ] + + self.assertEqual(columns, columns) datalist = (( volume_fakes.backup_id, diff --git a/openstackclient/tests/volume/v2/test_qos_specs.py b/openstackclient/tests/volume/v2/test_qos_specs.py index 4222ed0772..403634a33f 100644 --- a/openstackclient/tests/volume/v2/test_qos_specs.py +++ b/openstackclient/tests/volume/v2/test_qos_specs.py @@ -69,6 +69,18 @@ def test_qos_associate(self): class TestQosCreate(TestQos): + + columns = ( + 'consumer', + 'id', + 'name' + ) + datalist = ( + volume_fakes.qos_consumer, + volume_fakes.qos_id, + volume_fakes.qos_name + ) + def setUp(self): super(TestQosCreate, self).setUp() @@ -97,12 +109,7 @@ def test_qos_create_without_properties(self): {'consumer': volume_fakes.qos_default_consumer} ) - collist = ( - 'consumer', - 'id', - 'name' - ) - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = ( volume_fakes.qos_default_consumer, volume_fakes.qos_id, @@ -134,18 +141,8 @@ def test_qos_create_with_consumer(self): {'consumer': volume_fakes.qos_consumer} ) - collist = ( - 'consumer', - 'id', - 'name' - ) - self.assertEqual(collist, columns) - datalist = ( - volume_fakes.qos_consumer, - volume_fakes.qos_id, - volume_fakes.qos_name - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_qos_create_with_properties(self): self.qos_mock.create.return_value = fakes.FakeResource( @@ -176,17 +173,11 @@ def test_qos_create_with_properties(self): specs ) - collist = ( - 'consumer', - 'id', - 'name', + columns = self.columns + ( 'specs', ) - self.assertEqual(collist, columns) - datalist = ( - volume_fakes.qos_consumer, - volume_fakes.qos_id, - volume_fakes.qos_name, + self.assertEqual(columns, columns) + datalist = self.datalist + ( volume_fakes.qos_specs, ) self.assertEqual(datalist, data) diff --git a/openstackclient/tests/volume/v2/test_snapshot.py b/openstackclient/tests/volume/v2/test_snapshot.py index 3b30d4ef20..1c1dd43789 100644 --- a/openstackclient/tests/volume/v2/test_snapshot.py +++ b/openstackclient/tests/volume/v2/test_snapshot.py @@ -202,6 +202,15 @@ def test_snapshot_unset(self): class TestSnapshotList(TestSnapshot): + + columns = [ + "ID", + "Name", + "Description", + "Status", + "Size" + ] + def setUp(self): super(TestSnapshotList, self).setUp() @@ -231,8 +240,7 @@ def test_snapshot_list_without_options(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - collist = ["ID", "Name", "Description", "Status", "Size"] - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = (( volume_fakes.snapshot_id, volume_fakes.snapshot_name, @@ -249,9 +257,12 @@ def test_snapshot_list_with_options(self): columns, data = self.cmd.take_action(parsed_args) - collist = ["ID", "Name", "Description", "Status", "Size", "Created At", - "Volume", "Properties"] - self.assertEqual(collist, columns) + columns = self.columns + [ + "Created At", + "Volume", + "Properties" + ] + self.assertEqual(columns, columns) datalist = (( volume_fakes.snapshot_id, @@ -277,14 +288,7 @@ def test_snapshot_list_all_projects(self): columns, data = self.cmd.take_action(parsed_args) - collist = [ - "ID", - "Name", - "Description", - "Status", - "Size" - ] - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = (( volume_fakes.snapshot_id, diff --git a/openstackclient/tests/volume/v2/test_type.py b/openstackclient/tests/volume/v2/test_type.py index c63cd1fa53..9bf6036399 100644 --- a/openstackclient/tests/volume/v2/test_type.py +++ b/openstackclient/tests/volume/v2/test_type.py @@ -44,6 +44,17 @@ def setUp(self): class TestTypeCreate(TestType): + columns = ( + 'description', + 'id', + 'name', + ) + datalist = ( + volume_fakes.type_description, + volume_fakes.type_id, + volume_fakes.type_name, + ) + def setUp(self): super(TestTypeCreate, self).setUp() @@ -76,18 +87,8 @@ def test_type_create_public(self): is_public=True, ) - collist = ( - 'description', - 'id', - 'name', - ) - self.assertEqual(collist, columns) - datalist = ( - volume_fakes.type_description, - volume_fakes.type_id, - volume_fakes.type_name, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_type_create_private(self): arglist = [ @@ -110,21 +111,17 @@ def test_type_create_private(self): is_public=False, ) - collist = ( - 'description', - 'id', - 'name', - ) - self.assertEqual(collist, columns) - datalist = ( - volume_fakes.type_description, - volume_fakes.type_id, - volume_fakes.type_name, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) class TestTypeList(TestType): + + columns = [ + "ID", + "Name" + ] + def setUp(self): super(TestTypeList, self).setUp() @@ -146,8 +143,7 @@ def test_type_list_without_options(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - collist = ["ID", "Name"] - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = (( volume_fakes.type_id, volume_fakes.type_name, @@ -160,8 +156,11 @@ def test_type_list_with_options(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - collist = ["ID", "Name", "Description", "Properties"] - self.assertEqual(collist, columns) + columns = self.columns + [ + "Description", + "Properties" + ] + self.assertEqual(columns, columns) datalist = (( volume_fakes.type_id, volume_fakes.type_name, diff --git a/openstackclient/tests/volume/v2/test_volume.py b/openstackclient/tests/volume/v2/test_volume.py index 50826c31aa..d61a6c1540 100644 --- a/openstackclient/tests/volume/v2/test_volume.py +++ b/openstackclient/tests/volume/v2/test_volume.py @@ -48,6 +48,32 @@ def setup_volumes_mock(self, count): class TestVolumeCreate(TestVolume): + + columns = ( + 'attachments', + 'availability_zone', + 'description', + 'id', + 'name', + 'properties', + 'size', + 'snapshot_id', + 'status', + 'type', + ) + datalist = ( + volume_fakes.volume_attachments, + volume_fakes.volume_availability_zone, + volume_fakes.volume_description, + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_metadata_str, + volume_fakes.volume_size, + volume_fakes.volume_snapshot_id, + volume_fakes.volume_status, + volume_fakes.volume_type, + ) + def setUp(self): super(TestVolumeCreate, self).setUp() @@ -88,32 +114,8 @@ def test_volume_create_min_options(self): source_volid=None ) - collist = ( - 'attachments', - 'availability_zone', - 'description', - 'id', - 'name', - 'properties', - 'size', - 'snapshot_id', - 'status', - 'type', - ) - self.assertEqual(collist, columns) - datalist = ( - volume_fakes.volume_attachments, - volume_fakes.volume_availability_zone, - volume_fakes.volume_description, - volume_fakes.volume_id, - volume_fakes.volume_name, - volume_fakes.volume_metadata_str, - volume_fakes.volume_size, - volume_fakes.volume_snapshot_id, - volume_fakes.volume_status, - volume_fakes.volume_type, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_volume_create_options(self): arglist = [ @@ -149,32 +151,8 @@ def test_volume_create_options(self): source_volid=None ) - collist = ( - 'attachments', - 'availability_zone', - 'description', - 'id', - 'name', - 'properties', - 'size', - 'snapshot_id', - 'status', - 'type', - ) - self.assertEqual(collist, columns) - datalist = ( - volume_fakes.volume_attachments, - volume_fakes.volume_availability_zone, - volume_fakes.volume_description, - volume_fakes.volume_id, - volume_fakes.volume_name, - volume_fakes.volume_metadata_str, - volume_fakes.volume_size, - volume_fakes.volume_snapshot_id, - volume_fakes.volume_status, - volume_fakes.volume_type, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_volume_create_user_project_id(self): # Return a project @@ -221,32 +199,8 @@ def test_volume_create_user_project_id(self): source_volid=None ) - collist = ( - 'attachments', - 'availability_zone', - 'description', - 'id', - 'name', - 'properties', - 'size', - 'snapshot_id', - 'status', - 'type', - ) - self.assertEqual(collist, columns) - datalist = ( - volume_fakes.volume_attachments, - volume_fakes.volume_availability_zone, - volume_fakes.volume_description, - volume_fakes.volume_id, - volume_fakes.volume_name, - volume_fakes.volume_metadata_str, - volume_fakes.volume_size, - volume_fakes.volume_snapshot_id, - volume_fakes.volume_status, - volume_fakes.volume_type, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_volume_create_user_project_name(self): # Return a project @@ -293,32 +247,8 @@ def test_volume_create_user_project_name(self): source_volid=None ) - collist = ( - 'attachments', - 'availability_zone', - 'description', - 'id', - 'name', - 'properties', - 'size', - 'snapshot_id', - 'status', - 'type', - ) - self.assertEqual(collist, columns) - datalist = ( - volume_fakes.volume_attachments, - volume_fakes.volume_availability_zone, - volume_fakes.volume_description, - volume_fakes.volume_id, - volume_fakes.volume_name, - volume_fakes.volume_metadata_str, - volume_fakes.volume_size, - volume_fakes.volume_snapshot_id, - volume_fakes.volume_status, - volume_fakes.volume_type, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_volume_create_properties(self): arglist = [ @@ -351,32 +281,8 @@ def test_volume_create_properties(self): source_volid=None ) - collist = ( - 'attachments', - 'availability_zone', - 'description', - 'id', - 'name', - 'properties', - 'size', - 'snapshot_id', - 'status', - 'type', - ) - self.assertEqual(collist, columns) - datalist = ( - volume_fakes.volume_attachments, - volume_fakes.volume_availability_zone, - volume_fakes.volume_description, - volume_fakes.volume_id, - volume_fakes.volume_name, - volume_fakes.volume_metadata_str, - volume_fakes.volume_size, - volume_fakes.volume_snapshot_id, - volume_fakes.volume_status, - volume_fakes.volume_type, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_volume_create_image_id(self): self.images_mock.get.return_value = fakes.FakeResource( @@ -411,35 +317,11 @@ def test_volume_create_image_id(self): availability_zone=None, metadata=None, imageRef=volume_fakes.image_id, - source_volid=None + source_volid=None, ) - collist = ( - 'attachments', - 'availability_zone', - 'description', - 'id', - 'name', - 'properties', - 'size', - 'snapshot_id', - 'status', - 'type', - ) - self.assertEqual(collist, columns) - datalist = ( - volume_fakes.volume_attachments, - volume_fakes.volume_availability_zone, - volume_fakes.volume_description, - volume_fakes.volume_id, - volume_fakes.volume_name, - volume_fakes.volume_metadata_str, - volume_fakes.volume_size, - volume_fakes.volume_snapshot_id, - volume_fakes.volume_status, - volume_fakes.volume_type, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_volume_create_image_name(self): self.images_mock.get.return_value = fakes.FakeResource( @@ -477,36 +359,20 @@ def test_volume_create_image_name(self): source_volid=None ) - collist = ( - 'attachments', - 'availability_zone', - 'description', - 'id', - 'name', - 'properties', - 'size', - 'snapshot_id', - 'status', - 'type', - ) - self.assertEqual(collist, columns) - datalist = ( - volume_fakes.volume_attachments, - volume_fakes.volume_availability_zone, - volume_fakes.volume_description, - volume_fakes.volume_id, - volume_fakes.volume_name, - volume_fakes.volume_metadata_str, - volume_fakes.volume_size, - volume_fakes.volume_snapshot_id, - volume_fakes.volume_status, - volume_fakes.volume_type, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) class TestVolumeList(TestVolume): + columns = [ + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Attached to', + ] + def setUp(self): super(TestVolumeList, self).setUp() @@ -549,14 +415,7 @@ def test_volume_list_no_options(self): columns, data = self.cmd.take_action(parsed_args) - collist = [ - 'ID', - 'Display Name', - 'Status', - 'Size', - 'Attached to', - ] - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) server = volume_fakes.volume_attachment_server['server_id'] device = volume_fakes.volume_attachment_server['device'] @@ -584,19 +443,11 @@ def test_volume_list_project(self): columns, data = self.cmd.take_action(parsed_args) - collist = ( - 'ID', - 'Display Name', - 'Status', - 'Size', - 'Attached to', - ) - self.assertEqual(collist, tuple(columns)) + self.assertEqual(self.columns, columns) server = volume_fakes.volume_attachment_server['server_id'] device = volume_fakes.volume_attachment_server['device'] msg = 'Attached to %s on %s ' % (server, device) - datalist = (( volume_fakes.volume_id, volume_fakes.volume_name, @@ -622,19 +473,11 @@ def test_volume_list_project_domain(self): columns, data = self.cmd.take_action(parsed_args) - collist = ( - 'ID', - 'Display Name', - 'Status', - 'Size', - 'Attached to', - ) - self.assertEqual(collist, tuple(columns)) + self.assertEqual(self.columns, columns) server = volume_fakes.volume_attachment_server['server_id'] device = volume_fakes.volume_attachment_server['device'] msg = 'Attached to %s on %s ' % (server, device) - datalist = (( volume_fakes.volume_id, volume_fakes.volume_name, @@ -658,19 +501,10 @@ def test_volume_list_user(self): columns, data = self.cmd.take_action(parsed_args) - collist = ( - 'ID', - 'Display Name', - 'Status', - 'Size', - 'Attached to', - ) - self.assertEqual(collist, tuple(columns)) - + self.assertEqual(self.columns, columns) server = volume_fakes.volume_attachment_server['server_id'] device = volume_fakes.volume_attachment_server['device'] msg = 'Attached to %s on %s ' % (server, device) - datalist = (( volume_fakes.volume_id, volume_fakes.volume_name, @@ -696,19 +530,11 @@ def test_volume_list_user_domain(self): columns, data = self.cmd.take_action(parsed_args) - collist = ( - 'ID', - 'Display Name', - 'Status', - 'Size', - 'Attached to', - ) - self.assertEqual(collist, tuple(columns)) + self.assertEqual(self.columns, columns) server = volume_fakes.volume_attachment_server['server_id'] device = volume_fakes.volume_attachment_server['device'] msg = 'Attached to %s on %s ' % (server, device) - datalist = (( volume_fakes.volume_id, volume_fakes.volume_name, @@ -732,19 +558,11 @@ def test_volume_list_name(self): columns, data = self.cmd.take_action(parsed_args) - collist = ( - 'ID', - 'Display Name', - 'Status', - 'Size', - 'Attached to', - ) - self.assertEqual(collist, tuple(columns)) + self.assertEqual(self.columns, columns) server = volume_fakes.volume_attachment_server['server_id'] device = volume_fakes.volume_attachment_server['device'] msg = 'Attached to %s on %s ' % (server, device) - datalist = (( volume_fakes.volume_id, volume_fakes.volume_name, @@ -768,14 +586,7 @@ def test_volume_list_status(self): columns, data = self.cmd.take_action(parsed_args) - collist = ( - 'ID', - 'Display Name', - 'Status', - 'Size', - 'Attached to', - ) - self.assertEqual(collist, tuple(columns)) + self.assertEqual(self.columns, columns) server = volume_fakes.volume_attachment_server['server_id'] device = volume_fakes.volume_attachment_server['device'] @@ -803,14 +614,7 @@ def test_volume_list_all_projects(self): columns, data = self.cmd.take_action(parsed_args) - collist = [ - 'ID', - 'Display Name', - 'Status', - 'Size', - 'Attached to', - ] - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) server = volume_fakes.volume_attachment_server['server_id'] device = volume_fakes.volume_attachment_server['device'] From 0e6b86ad94819fcb7bf20e3368b86a504d6c6702 Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Sat, 9 Jan 2016 13:35:30 +0900 Subject: [PATCH 0533/3095] Set up logger of each command by metaclass compute.v2.flavor is changed in this commit as an initial example. Partial-Bug: #1532294 Change-Id: I262af6ade0ae03fbe1cd2ad198faf4ebb4ecf7ce --- openstackclient/common/command.py | 42 ++++++++++++++++++++ openstackclient/compute/v2/flavor.py | 28 +++---------- openstackclient/tests/common/test_command.py | 32 +++++++++++++++ 3 files changed, 80 insertions(+), 22 deletions(-) create mode 100644 openstackclient/common/command.py create mode 100644 openstackclient/tests/common/test_command.py diff --git a/openstackclient/common/command.py b/openstackclient/common/command.py new file mode 100644 index 0000000000..b8d9fc6f1b --- /dev/null +++ b/openstackclient/common/command.py @@ -0,0 +1,42 @@ +# Copyright 2016 NEC Corporation +# +# 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 abc +import logging + +from cliff import command +from cliff import lister +from cliff import show +import six + + +class CommandMeta(abc.ABCMeta): + def __new__(mcs, name, bases, cls_dict): + if 'log' not in cls_dict: + cls_dict['log'] = logging.getLogger( + cls_dict['__module__'] + '.' + name) + return super(CommandMeta, mcs).__new__(mcs, name, bases, cls_dict) + + +@six.add_metaclass(CommandMeta) +class Command(command.Command): + pass + + +class Lister(Command, lister.Lister): + pass + + +class ShowOne(Command, show.ShowOne): + pass diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index b34197e049..1af5fe7002 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -15,22 +15,16 @@ """Flavor action implementations""" -import logging import six -from cliff import command -from cliff import lister -from cliff import show - +from openstackclient.common import command from openstackclient.common import parseractions from openstackclient.common import utils -class CreateFlavor(show.ShowOne): +class CreateFlavor(command.ShowOne): """Create new flavor""" - log = logging.getLogger(__name__ + ".CreateFlavor") - def get_parser(self, prog_name): parser = super(CreateFlavor, self).get_parser(prog_name) parser.add_argument( @@ -128,8 +122,6 @@ def take_action(self, parsed_args): class DeleteFlavor(command.Command): """Delete flavor""" - log = logging.getLogger(__name__ + ".DeleteFlavor") - def get_parser(self, prog_name): parser = super(DeleteFlavor, self).get_parser(prog_name) parser.add_argument( @@ -147,11 +139,9 @@ def take_action(self, parsed_args): compute_client.flavors.delete(flavor.id) -class ListFlavor(lister.Lister): +class ListFlavor(command.Lister): """List flavors""" - log = logging.getLogger(__name__ + ".ListFlavor") - def get_parser(self, prog_name): parser = super(ListFlavor, self).get_parser(prog_name) public_group = parser.add_mutually_exclusive_group() @@ -231,11 +221,9 @@ def take_action(self, parsed_args): ) for s in data)) -class ShowFlavor(show.ShowOne): +class ShowFlavor(command.ShowOne): """Display flavor details""" - log = logging.getLogger(__name__ + ".ShowFlavor") - def get_parser(self, prog_name): parser = super(ShowFlavor, self).get_parser(prog_name) parser.add_argument( @@ -258,11 +246,9 @@ def take_action(self, parsed_args): return zip(*sorted(six.iteritems(flavor))) -class SetFlavor(show.ShowOne): +class SetFlavor(command.ShowOne): """Set flavor properties""" - log = logging.getLogger(__name__ + ".SetFlavor") - def get_parser(self, prog_name): parser = super(SetFlavor, self).get_parser(prog_name) parser.add_argument( @@ -292,11 +278,9 @@ def take_action(self, parsed_args): return zip(*sorted(six.iteritems(flavor))) -class UnsetFlavor(show.ShowOne): +class UnsetFlavor(command.ShowOne): """Unset flavor properties""" - log = logging.getLogger(__name__ + ".UnsetFlavor") - def get_parser(self, prog_name): parser = super(UnsetFlavor, self).get_parser(prog_name) parser.add_argument( diff --git a/openstackclient/tests/common/test_command.py b/openstackclient/tests/common/test_command.py new file mode 100644 index 0000000000..1b2584bdde --- /dev/null +++ b/openstackclient/tests/common/test_command.py @@ -0,0 +1,32 @@ +# Copyright 2016 NEC Corporation +# +# 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.common import command +from openstackclient.tests import utils as test_utils + + +class FakeCommand(command.Command): + def take_action(self, parsed_args): + pass + + +class TestCommand(test_utils.TestCase): + + def test_command_has_logger(self): + cmd = FakeCommand(mock.Mock(), mock.Mock()) + self.assertTrue(hasattr(cmd, 'log')) + self.assertEqual('openstackclient.tests.common.test_command.' + 'FakeCommand', cmd.log.name) From 50d54bb007c2531013e9b7ae358737dcc19c519b Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Sat, 9 Jan 2016 13:43:36 +0900 Subject: [PATCH 0534/3095] log_method: get logger from decorated method if unspecified This commit makes 'log' optional. 'log' attribute of each command class does not exist when the class is defined because 'log' is now setup dynamically when a class is instantiated. Instead log_method looks for a logger from a decorating method. compute.v2.server is changed in this commit as an example. Change-Id: Ic4d128f8e027d3b8e6f884f31369e9085c0f0871 Partial-Bug: #1532294 --- openstackclient/common/utils.py | 22 +++-- openstackclient/compute/v2/server.py | 121 +++++++-------------------- 2 files changed, 44 insertions(+), 99 deletions(-) diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index 096c995baa..3ae30c8f71 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -26,28 +26,32 @@ from openstackclient.common import exceptions -def log_method(log, level=logging.DEBUG): - """Logs a method and its arguments when entered.""" +class log_method(object): - def decorator(func): + def __init__(self, log=None, level=logging.DEBUG): + self._log = log + self._level = level + + def __call__(self, func): func_name = func.__name__ + if not self._log: + self._log = logging.getLogger(func.__class__.__name__) @six.wraps(func) - def wrapper(self, *args, **kwargs): - if log.isEnabledFor(level): + def wrapper(*args, **kwargs): + if self._log.isEnabledFor(self._level): pretty_args = [] if args: pretty_args.extend(str(a) for a in args) if kwargs: pretty_args.extend( "%s=%s" % (k, v) for k, v in six.iteritems(kwargs)) - log.log(level, "%s(%s)", func_name, ", ".join(pretty_args)) - return func(self, *args, **kwargs) + self._log.log(self._level, "%s(%s)", + func_name, ", ".join(pretty_args)) + return func(*args, **kwargs) return wrapper - return decorator - def find_resource(manager, name_or_id, **kwargs): """Helper for the _find_* methods. diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index f5876d4fd7..b4db621600 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -18,14 +18,11 @@ import argparse import getpass import io -import logging import os import six import sys -from cliff import command -from cliff import lister -from cliff import show +from openstackclient.common import command try: from novaclient.v2 import servers @@ -173,8 +170,6 @@ def _show_progress(progress): class AddServerSecurityGroup(command.Command): """Add security group to server""" - log = logging.getLogger(__name__ + '.AddServerSecurityGroup') - def get_parser(self, prog_name): parser = super(AddServerSecurityGroup, self).get_parser(prog_name) parser.add_argument( @@ -209,8 +204,6 @@ def take_action(self, parsed_args): class AddServerVolume(command.Command): """Add volume to server""" - log = logging.getLogger(__name__ + '.AddServerVolume') - def get_parser(self, prog_name): parser = super(AddServerVolume, self).get_parser(prog_name) parser.add_argument( @@ -252,11 +245,9 @@ def take_action(self, parsed_args): ) -class CreateServer(show.ShowOne): +class CreateServer(command.ShowOne): """Create a new server""" - log = logging.getLogger(__name__ + '.CreateServer') - def get_parser(self, prog_name): parser = super(CreateServer, self).get_parser(prog_name) parser.add_argument( @@ -379,7 +370,7 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) + @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute volume_client = self.app.client_manager.volume @@ -556,11 +547,9 @@ def take_action(self, parsed_args): return zip(*sorted(six.iteritems(details))) -class CreateServerImage(show.ShowOne): +class CreateServerImage(command.ShowOne): """Create a new disk image from a running server""" - log = logging.getLogger(__name__ + '.CreateServerImage') - def get_parser(self, prog_name): parser = super(CreateServerImage, self).get_parser(prog_name) parser.add_argument( @@ -580,7 +569,7 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) + @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute image_client = self.app.client_manager.image @@ -622,8 +611,6 @@ def take_action(self, parsed_args): class DeleteServer(command.Command): """Delete server(s)""" - log = logging.getLogger(__name__ + '.DeleteServer') - def get_parser(self, prog_name): parser = super(DeleteServer, self).get_parser(prog_name) parser.add_argument( @@ -639,7 +626,7 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) + @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute for server in parsed_args.server: @@ -660,11 +647,9 @@ def take_action(self, parsed_args): raise SystemExit -class ListServer(lister.Lister): +class ListServer(command.Lister): """List servers""" - log = logging.getLogger(__name__ + '.ListServer') - def get_parser(self, prog_name): parser = super(ListServer, self).get_parser(prog_name) parser.add_argument( @@ -756,7 +741,7 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) + @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute identity_client = self.app.client_manager.identity @@ -877,8 +862,6 @@ class LockServer(command.Command): """Lock server(s). A non-admin user will not be able to execute actions""" - log = logging.getLogger(__name__ + '.LockServer') - def get_parser(self, prog_name): parser = super(LockServer, self).get_parser(prog_name) parser.add_argument( @@ -889,7 +872,7 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) + @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute @@ -913,8 +896,6 @@ def take_action(self, parsed_args): class MigrateServer(command.Command): """Migrate server to different host""" - log = logging.getLogger(__name__ + '.MigrateServer') - def get_parser(self, prog_name): parser = super(MigrateServer, self).get_parser(prog_name) parser.add_argument( @@ -963,7 +944,7 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) + @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute @@ -996,8 +977,6 @@ def take_action(self, parsed_args): class PauseServer(command.Command): """Pause server(s)""" - log = logging.getLogger(__name__ + '.PauseServer') - def get_parser(self, prog_name): parser = super(PauseServer, self).get_parser(prog_name) parser.add_argument( @@ -1008,7 +987,7 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) + @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute for server in parsed_args.server: @@ -1021,8 +1000,6 @@ def take_action(self, parsed_args): class RebootServer(command.Command): """Perform a hard or soft server reboot""" - log = logging.getLogger(__name__ + '.RebootServer') - def get_parser(self, prog_name): parser = super(RebootServer, self).get_parser(prog_name) parser.add_argument( @@ -1054,7 +1031,7 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) + @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute server = utils.find_resource( @@ -1073,11 +1050,9 @@ def take_action(self, parsed_args): raise SystemExit -class RebuildServer(show.ShowOne): +class RebuildServer(command.ShowOne): """Rebuild server""" - log = logging.getLogger(__name__ + '.RebuildServer') - def get_parser(self, prog_name): parser = super(RebuildServer, self).get_parser(prog_name) parser.add_argument( @@ -1103,7 +1078,7 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) + @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute @@ -1133,8 +1108,6 @@ def take_action(self, parsed_args): class RemoveServerSecurityGroup(command.Command): """Remove security group from server""" - log = logging.getLogger(__name__ + '.RemoveServerSecurityGroup') - def get_parser(self, prog_name): parser = super(RemoveServerSecurityGroup, self).get_parser(prog_name) parser.add_argument( @@ -1169,8 +1142,6 @@ def take_action(self, parsed_args): class RemoveServerVolume(command.Command): """Remove volume from server""" - log = logging.getLogger(__name__ + '.RemoveServerVolume') - def get_parser(self, prog_name): parser = super(RemoveServerVolume, self).get_parser(prog_name) parser.add_argument( @@ -1206,11 +1177,9 @@ def take_action(self, parsed_args): ) -class RescueServer(show.ShowOne): +class RescueServer(command.ShowOne): """Put server in rescue mode""" - log = logging.getLogger(__name__ + '.RescueServer') - def get_parser(self, prog_name): parser = super(RescueServer, self).get_parser(prog_name) parser.add_argument( @@ -1220,7 +1189,7 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) + @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute @@ -1234,8 +1203,6 @@ def take_action(self, parsed_args): class ResizeServer(command.Command): """Scale server to a new flavor""" - log = logging.getLogger(__name__ + '.ResizeServer') - def get_parser(self, prog_name): parser = super(ResizeServer, self).get_parser(prog_name) phase_group = parser.add_mutually_exclusive_group() @@ -1266,7 +1233,7 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) + @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute @@ -1300,8 +1267,6 @@ def take_action(self, parsed_args): class ResumeServer(command.Command): """Resume server(s)""" - log = logging.getLogger(__name__ + '.ResumeServer') - def get_parser(self, prog_name): parser = super(ResumeServer, self).get_parser(prog_name) parser.add_argument( @@ -1312,7 +1277,7 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) + @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute @@ -1326,8 +1291,6 @@ def take_action(self, parsed_args): class SetServer(command.Command): """Set server properties""" - log = logging.getLogger(__name__ + '.SetServer') - def get_parser(self, prog_name): parser = super(SetServer, self).get_parser(prog_name) parser.add_argument( @@ -1354,7 +1317,7 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) + @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute @@ -1385,8 +1348,6 @@ def take_action(self, parsed_args): class ShelveServer(command.Command): """Shelve server(s)""" - log = logging.getLogger(__name__ + '.ShelveServer') - def get_parser(self, prog_name): parser = super(ShelveServer, self).get_parser(prog_name) parser.add_argument( @@ -1397,7 +1358,7 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) + @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute for server in parsed_args.server: @@ -1407,11 +1368,9 @@ def take_action(self, parsed_args): ).shelve() -class ShowServer(show.ShowOne): +class ShowServer(command.ShowOne): """Show server details""" - log = logging.getLogger(__name__ + '.ShowServer') - def get_parser(self, prog_name): parser = super(ShowServer, self).get_parser(prog_name) parser.add_argument( @@ -1427,7 +1386,7 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) + @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute server = utils.find_resource(compute_client.servers, @@ -1447,8 +1406,6 @@ def take_action(self, parsed_args): class SshServer(command.Command): """Ssh to server""" - log = logging.getLogger(__name__ + '.SshServer') - def get_parser(self, prog_name): parser = super(SshServer, self).get_parser(prog_name) parser.add_argument( @@ -1550,7 +1507,7 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) + @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute @@ -1594,8 +1551,6 @@ def take_action(self, parsed_args): class StartServer(command.Command): """Start server(s).""" - log = logging.getLogger(__name__ + '.StartServer') - def get_parser(self, prog_name): parser = super(StartServer, self).get_parser(prog_name) parser.add_argument( @@ -1606,7 +1561,7 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) + @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute for server in parsed_args.server: @@ -1619,8 +1574,6 @@ def take_action(self, parsed_args): class StopServer(command.Command): """Stop server(s).""" - log = logging.getLogger(__name__ + '.StopServer') - def get_parser(self, prog_name): parser = super(StopServer, self).get_parser(prog_name) parser.add_argument( @@ -1631,7 +1584,7 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) + @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute for server in parsed_args.server: @@ -1644,8 +1597,6 @@ def take_action(self, parsed_args): class SuspendServer(command.Command): """Suspend server(s)""" - log = logging.getLogger(__name__ + '.SuspendServer') - def get_parser(self, prog_name): parser = super(SuspendServer, self).get_parser(prog_name) parser.add_argument( @@ -1656,7 +1607,7 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) + @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute @@ -1670,8 +1621,6 @@ def take_action(self, parsed_args): class UnlockServer(command.Command): """Unlock server(s)""" - log = logging.getLogger(__name__ + '.UnlockServer') - def get_parser(self, prog_name): parser = super(UnlockServer, self).get_parser(prog_name) parser.add_argument( @@ -1682,7 +1631,7 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) + @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute @@ -1696,8 +1645,6 @@ def take_action(self, parsed_args): class UnpauseServer(command.Command): """Unpause server(s)""" - log = logging.getLogger(__name__ + '.UnpauseServer') - def get_parser(self, prog_name): parser = super(UnpauseServer, self).get_parser(prog_name) parser.add_argument( @@ -1708,7 +1655,7 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) + @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute @@ -1722,8 +1669,6 @@ def take_action(self, parsed_args): class UnrescueServer(command.Command): """Restore server from rescue mode""" - log = logging.getLogger(__name__ + '.UnrescueServer') - def get_parser(self, prog_name): parser = super(UnrescueServer, self).get_parser(prog_name) parser.add_argument( @@ -1733,7 +1678,7 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) + @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute @@ -1746,8 +1691,6 @@ def take_action(self, parsed_args): class UnsetServer(command.Command): """Unset server properties""" - log = logging.getLogger(__name__ + '.UnsetServer') - def get_parser(self, prog_name): parser = super(UnsetServer, self).get_parser(prog_name) parser.add_argument( @@ -1765,7 +1708,7 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) + @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute server = utils.find_resource( @@ -1783,8 +1726,6 @@ def take_action(self, parsed_args): class UnshelveServer(command.Command): """Unshelve server(s)""" - log = logging.getLogger(__name__ + '.UnshelveServer') - def get_parser(self, prog_name): parser = super(UnshelveServer, self).get_parser(prog_name) parser.add_argument( @@ -1795,7 +1736,7 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) + @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute for server in parsed_args.server: From 2b670afa33a8d8677f534e37412882110ea60ee3 Mon Sep 17 00:00:00 2001 From: SaiKiran Date: Thu, 7 Jan 2016 16:59:48 +0530 Subject: [PATCH 0535/3095] Refactor abstract columns and datalist out in compute test cases columns and datalist has been set in each test case in compute, which is not necessary. This patch abstract it out and remove all redundant code. Change-Id: I5e8423722416ea31fdced4c932ed141de90028ab Closes-Bug: #1531816 --- .../tests/compute/v2/test_security_group.py | 45 ++---- .../compute/v2/test_security_group_rule.py | 59 ++----- .../tests/compute/v2/test_server.py | 146 +++++++----------- 3 files changed, 83 insertions(+), 167 deletions(-) diff --git a/openstackclient/tests/compute/v2/test_security_group.py b/openstackclient/tests/compute/v2/test_security_group.py index fdb659a86f..87cc4870d8 100644 --- a/openstackclient/tests/compute/v2/test_security_group.py +++ b/openstackclient/tests/compute/v2/test_security_group.py @@ -56,6 +56,19 @@ def setUp(self): class TestSecurityGroupCreate(TestSecurityGroup): + columns = ( + 'description', + 'id', + 'name', + 'tenant_id', + ) + data = ( + security_group_description, + security_group_id, + security_group_name, + identity_fakes.project_id, + ) + def setUp(self): super(TestSecurityGroupCreate, self).setUp() @@ -86,20 +99,8 @@ def test_security_group_create_no_options(self): security_group_name, ) - collist = ( - 'description', - 'id', - 'name', - 'tenant_id', - ) - self.assertEqual(collist, columns) - datalist = ( - security_group_description, - security_group_id, - security_group_name, - identity_fakes.project_id, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) def test_security_group_create_description(self): arglist = [ @@ -121,20 +122,8 @@ def test_security_group_create_description(self): security_group_description, ) - collist = ( - 'description', - 'id', - 'name', - 'tenant_id', - ) - self.assertEqual(collist, columns) - datalist = ( - security_group_description, - security_group_id, - security_group_name, - identity_fakes.project_id, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) class TestSecurityGroupList(TestSecurityGroup): diff --git a/openstackclient/tests/compute/v2/test_security_group_rule.py b/openstackclient/tests/compute/v2/test_security_group_rule.py index 0e7ee05d87..a2f9b10825 100644 --- a/openstackclient/tests/compute/v2/test_security_group_rule.py +++ b/openstackclient/tests/compute/v2/test_security_group_rule.py @@ -116,6 +116,15 @@ def setUp(self): class TestSecurityGroupRuleCreate(TestSecurityGroupRule): + columns = ( + 'id', + 'ip_protocol', + 'ip_range', + 'parent_group_id', + 'port_range', + 'remote_security_group', + ) + def setUp(self): super(TestSecurityGroupRuleCreate, self).setUp() @@ -156,15 +165,7 @@ def test_security_group_rule_create_no_options(self): None, ) - collist = ( - 'id', - 'ip_protocol', - 'ip_range', - 'parent_group_id', - 'port_range', - 'remote_security_group', - ) - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = ( security_group_rule_id, 'tcp', @@ -208,15 +209,7 @@ def test_security_group_rule_create_ftp(self): None, ) - collist = ( - 'id', - 'ip_protocol', - 'ip_range', - 'parent_group_id', - 'port_range', - 'remote_security_group', - ) - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = ( security_group_rule_id, 'tcp', @@ -264,15 +257,7 @@ def test_security_group_rule_create_ssh(self): security_group_id, ) - collist = ( - 'id', - 'ip_protocol', - 'ip_range', - 'parent_group_id', - 'port_range', - 'remote_security_group', - ) - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = ( security_group_rule_id, 'tcp', @@ -315,15 +300,7 @@ def test_security_group_rule_create_udp(self): None, ) - collist = ( - 'id', - 'ip_protocol', - 'ip_range', - 'parent_group_id', - 'port_range', - 'remote_security_group', - ) - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = ( security_group_rule_id, 'udp', @@ -369,15 +346,7 @@ def test_security_group_rule_create_icmp(self): None, ) - collist = ( - 'id', - 'ip_protocol', - 'ip_range', - 'parent_group_id', - 'port_range', - 'remote_security_group', - ) - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = ( security_group_rule_id, 'icmp', diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 72fdbafc85..f6b622916b 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -89,6 +89,26 @@ def run_method_with_servers(self, method_name, server_count): class TestServerCreate(TestServer): + columns = ( + 'addresses', + 'flavor', + 'id', + 'name', + 'networks', + 'properties', + ) + + def datalist(self): + datalist = ( + '', + self.flavor.name + ' ()', + self.new_server.id, + self.new_server.name, + self.new_server.networks, + '', + ) + return datalist + def setUp(self): super(TestServerCreate, self).setUp() @@ -171,24 +191,8 @@ def test_server_create_minimal(self): **kwargs ) - collist = ( - 'addresses', - 'flavor', - 'id', - 'name', - 'networks', - 'properties', - ) - self.assertEqual(collist, columns) - datalist = ( - '', - self.flavor.name + ' ()', - self.new_server.id, - self.new_server.name, - self.new_server.networks, - '', - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) def test_server_create_with_network(self): arglist = [ @@ -272,24 +276,8 @@ def test_server_create_with_network(self): **kwargs ) - collist = ( - 'addresses', - 'flavor', - 'id', - 'name', - 'networks', - 'properties', - ) - self.assertEqual(collist, columns) - datalist = ( - '', - self.flavor.name + ' ()', - self.new_server.id, - self.new_server.name, - self.new_server.networks, - '', - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) @mock.patch('openstackclient.compute.v2.server.io.open') def test_server_create_userdata(self, mock_open): @@ -345,24 +333,8 @@ def test_server_create_userdata(self, mock_open): **kwargs ) - collist = ( - 'addresses', - 'flavor', - 'id', - 'name', - 'networks', - 'properties', - ) - self.assertEqual(collist, columns) - datalist = ( - '', - self.flavor.name + ' ()', - self.new_server.id, - self.new_server.name, - self.new_server.networks, - '', - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) def test_server_create_with_block_device_mapping(self): arglist = [ @@ -414,24 +386,8 @@ def test_server_create_with_block_device_mapping(self): **kwargs ) - collist = ( - 'addresses', - 'flavor', - 'id', - 'name', - 'networks', - 'properties', - ) - self.assertEqual(collist, columns) - datalist = ( - '', - self.flavor.name + ' ()', - self.new_server.id, - self.new_server.name, - self.new_server.networks, - '', - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) class TestServerDelete(TestServer): @@ -536,6 +492,26 @@ def test_server_delete_wait_fails(self, mock_wait_for_delete): class TestServerImageCreate(TestServer): + columns = ( + 'id', + 'name', + 'owner', + 'protected', + 'tags', + 'visibility', + ) + + def datalist(self): + datalist = ( + self.image.id, + self.image.name, + self.image.owner, + self.image.protected, + self.image.tags, + self.image.visibility, + ) + return datalist + def setUp(self): super(TestServerImageCreate, self).setUp() @@ -569,17 +545,8 @@ def test_server_image_create_no_options(self): self.server.name, ) - collist = ('id', 'name', 'owner', 'protected', 'tags', 'visibility') - self.assertEqual(collist, columns) - datalist = ( - self.image.id, - self.image.name, - self.image.owner, - self.image.protected, - self.image.tags, - self.image.visibility, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) def test_server_image_create_name(self): arglist = [ @@ -601,17 +568,8 @@ def test_server_image_create_name(self): 'img-nam', ) - collist = ('id', 'name', 'owner', 'protected', 'tags', 'visibility') - self.assertEqual(collist, columns) - datalist = ( - self.image.id, - self.image.name, - self.image.owner, - self.image.protected, - self.image.tags, - self.image.visibility, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) class TestServerList(TestServer): From 4b70e607a034a265820e1e1e962027cc23bfcea9 Mon Sep 17 00:00:00 2001 From: Travis Tripp Date: Thu, 21 Jan 2016 12:05:27 -0700 Subject: [PATCH 0536/3095] Add python-searchlightclient to list of adopters. This adds python-searchlightclient to the list of adopters. Change-Id: Ifb6f1931c00fc735a0b0fcc830979a4d83c0527d --- doc/source/plugins.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/plugins.rst b/doc/source/plugins.rst index 4013ae318a..7513d47ae4 100644 --- a/doc/source/plugins.rst +++ b/doc/source/plugins.rst @@ -37,6 +37,7 @@ python-manilaclient using argparse python-mistralclient using OpenStackClient python-muranoclient using argparse python-saharaclient using OpenStackClient +python-searchlightclient using OpenStackClient python-troveclient using argparse python-tuskarclient using OpenStackClient python-zaqarclient using OpenStackClient From 59b1bb10d676eddfdd9e9f9e8075fd312bd9f941 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Fri, 22 Jan 2016 04:09:05 +0000 Subject: [PATCH 0537/3095] Updated from global requirements Change-Id: I89081053923a81260111dbfefd307b80e3858caf --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 120505ef9a..87bb3ab1f1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,7 +13,7 @@ oslo.config>=3.2.0 # Apache-2.0 oslo.i18n>=1.5.0 # Apache-2.0 oslo.utils>=3.4.0 # Apache-2.0 python-glanceclient>=1.2.0 # Apache-2.0 -python-keystoneclient!=1.8.0,>=1.6.0 # Apache-2.0 +python-keystoneclient!=1.8.0,!=2.1.0,>=1.6.0 # Apache-2.0 python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0 python-cinderclient>=1.3.1 # Apache-2.0 requests!=2.9.0,>=2.8.1 # Apache-2.0 From 69b8cde5f12c1d11036d99ef69421f103ebed87d Mon Sep 17 00:00:00 2001 From: SaiKiran Date: Sat, 9 Jan 2016 20:51:10 +0530 Subject: [PATCH 0538/3095] Refactor abstract columns and datalist out in image and object test cases columns and datalist has been set in each test case in image and object which is not necessary. This patch abstract it out and remove all redundant code. Change-Id: Ie6aa3fa27ab2a468c67da31209107517259631c2 Related-Bug: 1532384 --- openstackclient/tests/image/v1/test_image.py | 68 ++++------- openstackclient/tests/image/v2/test_image.py | 110 +++++++----------- .../tests/object/v1/test_container.py | 18 ++- .../tests/object/v1/test_container_all.py | 20 ++-- .../tests/object/v1/test_object.py | 53 +++------ .../tests/object/v1/test_object_all.py | 8 +- 6 files changed, 106 insertions(+), 171 deletions(-) diff --git a/openstackclient/tests/image/v1/test_image.py b/openstackclient/tests/image/v1/test_image.py index 60b7f3093d..1e0b29aa33 100644 --- a/openstackclient/tests/image/v1/test_image.py +++ b/openstackclient/tests/image/v1/test_image.py @@ -240,6 +240,19 @@ def test_image_delete_no_options(self): class TestImageList(TestImage): + columns = ( + 'ID', + 'Name', + 'Status', + ) + datalist = ( + ( + image_fakes.image_id, + image_fakes.image_name, + '', + ), + ) + def setUp(self): super(TestImageList, self).setUp() @@ -268,15 +281,8 @@ def test_image_list_no_options(self): marker=image_fakes.image_id, ) - collist = ('ID', 'Name', 'Status') - - self.assertEqual(collist, columns) - datalist = (( - image_fakes.image_id, - image_fakes.image_name, - '', - ), ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) def test_image_list_public_option(self): arglist = [ @@ -297,15 +303,8 @@ def test_image_list_public_option(self): marker=image_fakes.image_id, ) - collist = ('ID', 'Name', 'Status') - - self.assertEqual(collist, columns) - datalist = (( - image_fakes.image_id, - image_fakes.image_name, - '', - ), ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) def test_image_list_private_option(self): arglist = [ @@ -326,15 +325,8 @@ def test_image_list_private_option(self): marker=image_fakes.image_id, ) - collist = ('ID', 'Name', 'Status') - - self.assertEqual(collist, columns) - datalist = (( - image_fakes.image_id, - image_fakes.image_name, - '', - ), ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) def test_image_list_long_option(self): arglist = [ @@ -407,15 +399,8 @@ def test_image_list_property_option(self, sf_mock): property_field='properties', ) - collist = ('ID', 'Name', 'Status') - - self.assertEqual(columns, collist) - datalist = (( - image_fakes.image_id, - image_fakes.image_name, - '', - ), ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) @mock.patch('openstackclient.common.utils.sort_items') def test_image_list_sort_option(self, si_mock): @@ -438,15 +423,8 @@ def test_image_list_sort_option(self, si_mock): 'name:asc' ) - collist = ('ID', 'Name', 'Status') - - self.assertEqual(collist, columns) - datalist = (( - image_fakes.image_id, - image_fakes.image_name, - '', - ), ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) class TestImageSet(TestImage): diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index 3534a3a441..488eab20c9 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -333,6 +333,17 @@ def test_image_create_dead_options(self): class TestAddProjectToImage(TestImage): + columns = ( + 'image_id', + 'member_id', + 'status', + ) + datalist = ( + image_fakes.image_id, + identity_fakes.project_id, + image_fakes.member_status, + ) + def setUp(self): super(TestAddProjectToImage, self).setUp() @@ -375,14 +386,8 @@ def test_add_project_to_image_no_option(self): image_fakes.image_id, identity_fakes.project_id ) - collist = ('image_id', 'member_id', 'status') - self.assertEqual(collist, columns) - datalist = ( - image_fakes.image_id, - identity_fakes.project_id, - image_fakes.member_status - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) def test_add_project_to_image_with_option(self): arglist = [ @@ -403,14 +408,8 @@ def test_add_project_to_image_with_option(self): image_fakes.image_id, identity_fakes.project_id ) - collist = ('image_id', 'member_id', 'status') - self.assertEqual(collist, columns) - datalist = ( - image_fakes.image_id, - identity_fakes.project_id, - image_fakes.member_status - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) class TestImageDelete(TestImage): @@ -460,6 +459,19 @@ def test_image_delete_multi_images(self): class TestImageList(TestImage): + columns = ( + 'ID', + 'Name', + 'Status', + ) + datalist = ( + ( + image_fakes.image_id, + image_fakes.image_name, + '', + ), + ) + def setUp(self): super(TestImageList, self).setUp() @@ -488,15 +500,8 @@ def test_image_list_no_options(self): marker=image_fakes.image_id, ) - collist = ('ID', 'Name', 'Status') - - self.assertEqual(collist, columns) - datalist = (( - image_fakes.image_id, - image_fakes.image_name, - '', - ), ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) def test_image_list_public_option(self): arglist = [ @@ -517,15 +522,8 @@ def test_image_list_public_option(self): marker=image_fakes.image_id, ) - collist = ('ID', 'Name', 'Status') - - self.assertEqual(collist, columns) - datalist = (( - image_fakes.image_id, - image_fakes.image_name, - '', - ), ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) def test_image_list_private_option(self): arglist = [ @@ -546,15 +544,8 @@ def test_image_list_private_option(self): marker=image_fakes.image_id, ) - collist = ('ID', 'Name', 'Status') - - self.assertEqual(collist, columns) - datalist = (( - image_fakes.image_id, - image_fakes.image_name, - '', - ), ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) def test_image_list_shared_option(self): arglist = [ @@ -575,15 +566,8 @@ def test_image_list_shared_option(self): marker=image_fakes.image_id, ) - collist = ('ID', 'Name', 'Status') - - self.assertEqual(columns, collist) - datalist = (( - image_fakes.image_id, - image_fakes.image_name, - '', - ), ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) def test_image_list_long_option(self): arglist = [ @@ -654,15 +638,8 @@ def test_image_list_property_option(self, sf_mock): property_field='properties', ) - collist = ('ID', 'Name', 'Status') - - self.assertEqual(columns, collist) - datalist = (( - image_fakes.image_id, - image_fakes.image_name, - '', - ), ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) @mock.patch('openstackclient.common.utils.sort_items') def test_image_list_sort_option(self, si_mock): @@ -684,15 +661,8 @@ def test_image_list_sort_option(self, si_mock): 'name:asc' ) - collist = ('ID', 'Name', 'Status') - - self.assertEqual(collist, columns) - datalist = (( - image_fakes.image_id, - image_fakes.image_name, - '', - ), ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) class TestRemoveProjectImage(TestImage): diff --git a/openstackclient/tests/object/v1/test_container.py b/openstackclient/tests/object/v1/test_container.py index 2ef2f7b52a..afcb3386b4 100644 --- a/openstackclient/tests/object/v1/test_container.py +++ b/openstackclient/tests/object/v1/test_container.py @@ -32,6 +32,9 @@ def __init__(self, endpoint=None, **kwargs): class TestContainer(object_fakes.TestObjectv1): + + columns = ('Name',) + def setUp(self): super(TestContainer, self).setUp() self.app.client_manager.object_store = object_store.APIv1( @@ -73,8 +76,7 @@ def test_object_list_containers_no_options(self, c_mock): **kwargs ) - collist = ('Name',) - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = ( (object_fakes.container_name, ), (object_fakes.container_name_3, ), @@ -107,8 +109,7 @@ def test_object_list_containers_prefix(self, c_mock): **kwargs ) - collist = ('Name',) - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = ( (object_fakes.container_name, ), (object_fakes.container_name_3, ), @@ -143,8 +144,7 @@ def test_object_list_containers_marker(self, c_mock): **kwargs ) - collist = ('Name',) - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = ( (object_fakes.container_name, ), (object_fakes.container_name_3, ), @@ -176,8 +176,7 @@ def test_object_list_containers_limit(self, c_mock): **kwargs ) - collist = ('Name',) - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = ( (object_fakes.container_name, ), (object_fakes.container_name_3, ), @@ -250,8 +249,7 @@ def test_object_list_containers_all(self, c_mock): **kwargs ) - collist = ('Name',) - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = ( (object_fakes.container_name, ), (object_fakes.container_name_2, ), diff --git a/openstackclient/tests/object/v1/test_container_all.py b/openstackclient/tests/object/v1/test_container_all.py index 69fc0f3962..c2dd02a59a 100644 --- a/openstackclient/tests/object/v1/test_container_all.py +++ b/openstackclient/tests/object/v1/test_container_all.py @@ -29,6 +29,12 @@ def setUp(self): class TestContainerCreate(TestContainerAll): + columns = ( + 'account', + 'container', + 'x-trans-id', + ) + def setUp(self): super(TestContainerCreate, self).setUp() @@ -54,8 +60,7 @@ def test_object_create_container_single(self): # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) - collist = ('account', 'container', 'x-trans-id') - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = [( object_fakes.ACCOUNT_ID, 'ernie', @@ -89,8 +94,7 @@ def test_object_create_container_more(self): # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) - collist = ('account', 'container', 'x-trans-id') - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = [ ( object_fakes.ACCOUNT_ID, @@ -161,6 +165,8 @@ def test_object_delete_container_more(self): class TestContainerList(TestContainerAll): + columns = ('Name',) + def setUp(self): super(TestContainerList, self).setUp() @@ -187,8 +193,7 @@ def test_object_list_containers_no_options(self): # Lister.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) - collist = ('Name',) - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = [ (object_fakes.container_name, ), (object_fakes.container_name_3, ), @@ -219,8 +224,7 @@ def test_object_list_containers_prefix(self): # Lister.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) - collist = ('Name',) - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = [ (object_fakes.container_name, ), (object_fakes.container_name_3, ), diff --git a/openstackclient/tests/object/v1/test_object.py b/openstackclient/tests/object/v1/test_object.py index 305fe8f83b..f0d62f6e16 100644 --- a/openstackclient/tests/object/v1/test_object.py +++ b/openstackclient/tests/object/v1/test_object.py @@ -40,6 +40,13 @@ def setUp(self): ) class TestObjectList(TestObject): + columns = ('Name',) + datalist = ( + ( + object_fakes.object_name_2, + ), + ) + def setUp(self): super(TestObjectList, self).setUp() @@ -67,8 +74,7 @@ def test_object_list_objects_no_options(self, o_mock): container=object_fakes.container_name, ) - collist = ('Name',) - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = ( (object_fakes.object_name_1, ), (object_fakes.object_name_2, ), @@ -102,12 +108,8 @@ def test_object_list_objects_prefix(self, o_mock): **kwargs ) - collist = ('Name',) - self.assertEqual(collist, columns) - datalist = ( - (object_fakes.object_name_2, ), - ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) def test_object_list_objects_delimiter(self, o_mock): o_mock.return_value = [ @@ -136,12 +138,8 @@ def test_object_list_objects_delimiter(self, o_mock): **kwargs ) - collist = ('Name',) - self.assertEqual(collist, columns) - datalist = ( - (object_fakes.object_name_2, ), - ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) def test_object_list_objects_marker(self, o_mock): o_mock.return_value = [ @@ -170,12 +168,8 @@ def test_object_list_objects_marker(self, o_mock): **kwargs ) - collist = ('Name',) - self.assertEqual(collist, columns) - datalist = ( - (object_fakes.object_name_2, ), - ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) def test_object_list_objects_end_marker(self, o_mock): o_mock.return_value = [ @@ -204,12 +198,8 @@ def test_object_list_objects_end_marker(self, o_mock): **kwargs ) - collist = ('Name',) - self.assertEqual(collist, columns) - datalist = ( - (object_fakes.object_name_2, ), - ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) def test_object_list_objects_limit(self, o_mock): o_mock.return_value = [ @@ -238,12 +228,8 @@ def test_object_list_objects_limit(self, o_mock): **kwargs ) - collist = ('Name',) - self.assertEqual(collist, columns) - datalist = ( - (object_fakes.object_name_2, ), - ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) def test_object_list_objects_long(self, o_mock): o_mock.return_value = [ @@ -320,8 +306,7 @@ def test_object_list_objects_all(self, o_mock): **kwargs ) - collist = ('Name',) - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = ( (object_fakes.object_name_1, ), (object_fakes.object_name_2, ), diff --git a/openstackclient/tests/object/v1/test_object_all.py b/openstackclient/tests/object/v1/test_object_all.py index 7a76ab76ad..a90e5b6587 100644 --- a/openstackclient/tests/object/v1/test_object_all.py +++ b/openstackclient/tests/object/v1/test_object_all.py @@ -37,6 +37,8 @@ def setUp(self): class TestObjectList(TestObjectAll): + columns = ('Name',) + def setUp(self): super(TestObjectList, self).setUp() @@ -69,8 +71,7 @@ def test_object_list_objects_no_options(self): # Lister.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) - collist = ('Name',) - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = [ (object_fakes.object_name_1, ), (object_fakes.object_name_2, ), @@ -104,8 +105,7 @@ def test_object_list_objects_prefix(self): # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) - collist = ('Name',) - self.assertEqual(collist, columns) + self.assertEqual(self.columns, columns) datalist = ( (object_fakes.object_name_2, ), ) From c20f9a86ee3f0b453f5e79d94a567abda7947a70 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sat, 23 Jan 2016 10:35:55 +0000 Subject: [PATCH 0539/3095] Updated from global requirements Change-Id: Iafdb2a4841ff9a9f643182f13ff1bcec47e3adb2 --- requirements.txt | 2 +- test-requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 87bb3ab1f1..6b2e351ec0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ keystoneauth1>=2.1.0 # Apache-2.0 openstacksdk # Apache-2.0 os-client-config>=1.13.1 # Apache-2.0 oslo.config>=3.2.0 # Apache-2.0 -oslo.i18n>=1.5.0 # Apache-2.0 +oslo.i18n>=2.1.0 # Apache-2.0 oslo.utils>=3.4.0 # Apache-2.0 python-glanceclient>=1.2.0 # Apache-2.0 python-keystoneclient!=1.8.0,!=2.1.0,>=1.6.0 # Apache-2.0 diff --git a/test-requirements.txt b/test-requirements.txt index 1c495a4f40..a0b05bf1b6 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -23,7 +23,7 @@ python-barbicanclient>=3.3.0 # Apache-2.0 python-congressclient>=1.0.0 # Apache-2.0 python-designateclient>=1.5.0 # Apache-2.0 python-heatclient>=0.6.0 # Apache-2.0 -python-ironicclient>=0.8.0 # Apache-2.0 +python-ironicclient>=0.9.0 # Apache-2.0 python-mistralclient>=1.0.0 # Apache-2.0 python-saharaclient>=0.10.0 # Apache-2.0 python-tuskarclient>=0.1.17 # Apache-2.0 From ffcfff6f3eda2cc9022b7924aa1d08739d63aaa1 Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Wed, 13 Aug 2014 13:13:51 -0600 Subject: [PATCH 0540/3095] Subnet List Subnet list command Partially implements: blueprint neutron-client Partial-Bug: #1523258 Change-Id: I3c0748074a6511ff92500516b3129886d2476eed --- doc/source/command-objects/subnet.rst | 20 ++++ doc/source/commands.rst | 1 + openstackclient/network/v2/subnet.py | 70 ++++++++++++ openstackclient/tests/network/v2/fakes.py | 68 +++++++++++ .../tests/network/v2/test_subnet.py | 108 ++++++++++++++++++ setup.cfg | 1 + 6 files changed, 268 insertions(+) create mode 100644 doc/source/command-objects/subnet.rst create mode 100644 openstackclient/network/v2/subnet.py create mode 100644 openstackclient/tests/network/v2/test_subnet.py diff --git a/doc/source/command-objects/subnet.rst b/doc/source/command-objects/subnet.rst new file mode 100644 index 0000000000..70a0eedfa1 --- /dev/null +++ b/doc/source/command-objects/subnet.rst @@ -0,0 +1,20 @@ +====== +subnet +====== + +Network v2 + +subnet list +----------- + +List subnets + +.. program:: subnet list +.. code:: bash + + os subnet list + [--long] + +.. option:: --long + + List additional fields in output diff --git a/doc/source/commands.rst b/doc/source/commands.rst index f7e8b444d4..5c8ea1b2c8 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -116,6 +116,7 @@ referring to both Compute and Volume quotas. * ``service``: (**Identity**) a cloud service * ``service provider``: (**Identity**) a resource that consumes assertions from an ``identity provider`` * ``snapshot``: (**Volume**) a point-in-time copy of a volume +* ``subnet``: (**Network**) - a pool of private IP addresses that can be assigned to instances or other resources * ``token``: (**Identity**) a bearer token managed by Identity service * ``usage``: (**Compute**) display host resources being consumed * ``user``: (**Identity**) individual cloud resources users diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py new file mode 100644 index 0000000000..cd0e52ffcb --- /dev/null +++ b/openstackclient/network/v2/subnet.py @@ -0,0 +1,70 @@ +# 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. +# + +"""Subnet action implementations""" + +import logging + +from cliff import lister + +from openstackclient.common import utils + + +def _format_allocation_pools(data): + pool_formatted = ['%s-%s' % (pool.get('start', ''), pool.get('end', '')) + for pool in data] + return ','.join(pool_formatted) + + +_formatters = { + 'allocation_pools': _format_allocation_pools, + 'dns_nameservers': utils.format_list, + 'host_routes': utils.format_list, +} + + +class ListSubnet(lister.Lister): + """List subnets""" + + log = logging.getLogger(__name__ + '.ListSubnet') + + def get_parser(self, prog_name): + parser = super(ListSubnet, self).get_parser(prog_name) + parser.add_argument( + '--long', + action='store_true', + default=False, + help='List additional fields in output', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + + data = self.app.client_manager.network.subnets() + + headers = ('ID', 'Name', 'Network', 'CIDR') + columns = ('id', 'name', 'network_id', 'cidr') + if parsed_args.long: + headers += ('Project', 'DHCP', 'DNS Nameservers', + 'Allocation Pools', 'Host Routes', 'IP Version', + 'Gateway') + columns += ('tenant_id', 'enable_dhcp', 'dns_nameservers', + 'allocation_pools', 'host_routes', 'ip_version', + 'gateway_ip') + + return (headers, + (utils.get_item_properties( + s, columns, + formatters=_formatters, + ) for s in data)) diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index de885c62ab..6085bfbd50 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -304,3 +304,71 @@ def get_routers(routers=None, count=2): if routers is None: routers = FakeRouter.create_routers(count) return mock.MagicMock(side_effect=routers) + + +class FakeSubnet(object): + """Fake one or more subnets.""" + + @staticmethod + def create_one_subnet(attrs={}, methods={}): + """Create a fake subnet. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :return: + A FakeResource object faking the subnet + """ + # Set default attributes. + subnet_attrs = { + 'id': 'subnet-id-' + uuid.uuid4().hex, + 'name': 'subnet-name-' + uuid.uuid4().hex, + 'network_id': 'network-id-' + uuid.uuid4().hex, + 'cidr': '10.10.10.0/24', + 'tenant_id': 'project-id-' + uuid.uuid4().hex, + 'enable_dhcp': True, + 'dns_nameservers': [], + 'allocation_pools': [], + 'host_routes': [], + 'ip_version': '4', + 'gateway_ip': '10.10.10.1', + } + + # Overwrite default attributes. + subnet_attrs.update(attrs) + + # Set default methods. + subnet_methods = { + 'keys': ['id', 'name', 'network_id', 'cidr', 'enable_dhcp', + 'allocation_pools', 'dns_nameservers', 'gateway_ip', + 'host_routes', 'ip_version', 'tenant_id'] + } + + # Overwrite default methods. + subnet_methods.update(methods) + + subnet = fakes.FakeResource(info=copy.deepcopy(subnet_attrs), + methods=copy.deepcopy(subnet_methods), + loaded=True) + + return subnet + + @staticmethod + def create_subnets(attrs={}, methods={}, count=2): + """Create multiple fake subnets. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :param int count: + The number of subnets to fake + :return: + A list of FakeResource objects faking the subnets + """ + subnets = [] + for i in range(0, count): + subnets.append(FakeSubnet.create_one_subnet(attrs, methods)) + + return subnets diff --git a/openstackclient/tests/network/v2/test_subnet.py b/openstackclient/tests/network/v2/test_subnet.py new file mode 100644 index 0000000000..74b4d33283 --- /dev/null +++ b/openstackclient/tests/network/v2/test_subnet.py @@ -0,0 +1,108 @@ +# 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.common import utils +from openstackclient.network.v2 import subnet as subnet_v2 +from openstackclient.tests.network.v2 import fakes as network_fakes + + +class TestSubnet(network_fakes.TestNetworkV2): + def setUp(self): + super(TestSubnet, self).setUp() + + # Get a shortcut to the network client + self.network = self.app.client_manager.network + + +class TestListSubnet(TestSubnet): + # The subnets going to be listed up. + _subnet = network_fakes.FakeSubnet.create_subnets(count=3) + + columns = ( + 'ID', + 'Name', + 'Network', + 'CIDR' + ) + columns_long = columns + ( + 'Project', + 'DHCP', + 'DNS Nameservers', + 'Allocation Pools', + 'Host Routes', + 'IP Version', + 'Gateway' + ) + + data = [] + for subnet in _subnet: + data.append(( + subnet.id, + subnet.name, + subnet.network_id, + subnet.cidr, + )) + + data_long = [] + for subnet in _subnet: + data_long.append(( + subnet.id, + subnet.name, + subnet.network_id, + subnet.cidr, + subnet.tenant_id, + subnet.enable_dhcp, + utils.format_list(subnet.dns_nameservers), + subnet_v2._format_allocation_pools(subnet.allocation_pools), + utils.format_list(subnet.host_routes), + subnet.ip_version, + subnet.gateway_ip + )) + + def setUp(self): + super(TestListSubnet, self).setUp() + + # Get the command object to test + self.cmd = subnet_v2.ListSubnet(self.app, self.namespace) + + self.network.subnets = mock.Mock(return_value=self._subnet) + + def test_subnet_list_no_options(self): + arglist = [] + verifylist = [ + ('long', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.subnets.assert_called_with() + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_subnet_list_long(self): + arglist = [ + '--long', + ] + verifylist = [ + ('long', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.subnets.assert_called_with() + self.assertEqual(self.columns_long, columns) + self.assertEqual(self.data_long, list(data)) diff --git a/setup.cfg b/setup.cfg index 7e0f0b4c8e..98baeee4d1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -339,6 +339,7 @@ openstack.network.v2 = router_list = openstackclient.network.v2.router:ListRouter router_set = openstackclient.network.v2.router:SetRouter router_show = openstackclient.network.v2.router:ShowRouter + subnet_list = openstackclient.network.v2.subnet:ListSubnet openstack.object_store.v1 = object_store_account_set = openstackclient.object.v1.account:SetAccount From 1db40e85632b8979f72c3f38b16eb5408d2d6a6e Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Mon, 25 Jan 2016 10:28:59 -0600 Subject: [PATCH 0541/3095] Add router functional tests Add functional tests for the "os router" commands. Change-Id: I99045e6e2f548ac4206afcdb61940180e609a6bc Partial-bug: #1519503 Partially-implements: blueprint neutron-client --- functional/tests/network/v2/test_router.py | 50 ++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 functional/tests/network/v2/test_router.py diff --git a/functional/tests/network/v2/test_router.py b/functional/tests/network/v2/test_router.py new file mode 100644 index 0000000000..ceb76255ed --- /dev/null +++ b/functional/tests/network/v2/test_router.py @@ -0,0 +1,50 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import uuid + +from functional.common import test + + +class RouterTests(test.TestCase): + """Functional tests for router. """ + NAME = uuid.uuid4().hex + HEADERS = ['Name'] + FIELDS = ['name'] + + @classmethod + def setUpClass(cls): + opts = cls.get_show_opts(cls.FIELDS) + raw_output = cls.openstack('router create ' + cls.NAME + opts) + expected = cls.NAME + '\n' + cls.assertOutput(expected, raw_output) + + @classmethod + def tearDownClass(cls): + raw_output = cls.openstack('router delete ' + cls.NAME) + cls.assertOutput('', raw_output) + + def test_router_list(self): + opts = self.get_list_opts(self.HEADERS) + raw_output = self.openstack('router list' + opts) + self.assertIn(self.NAME, raw_output) + + def test_router_set(self): + self.openstack('router set --disable ' + self.NAME) + opts = self.get_show_opts(['name', 'admin_state_up']) + raw_output = self.openstack('router show ' + self.NAME + opts) + self.assertEqual("DOWN\n" + self.NAME + "\n", raw_output) + + def test_router_show(self): + opts = self.get_show_opts(self.FIELDS) + raw_output = self.openstack('router show ' + self.NAME + opts) + self.assertEqual(self.NAME + "\n", raw_output) From 299c5710c518d9502a23f5240360047baa81ac8d Mon Sep 17 00:00:00 2001 From: Lingxian Kong Date: Tue, 26 Jan 2016 10:09:16 +1300 Subject: [PATCH 0542/3095] Add releasenote for 'subnet list' command support Change-Id: I415dfe4b752dbf9dd4d5b052e29001a7a1d0ef99 Depends-On: I3c0748074a6511ff92500516b3129886d2476eed --- .../notes/add-subnet-list-command-970f4b397469bdc6.yaml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 releasenotes/notes/add-subnet-list-command-970f4b397469bdc6.yaml diff --git a/releasenotes/notes/add-subnet-list-command-970f4b397469bdc6.yaml b/releasenotes/notes/add-subnet-list-command-970f4b397469bdc6.yaml new file mode 100644 index 0000000000..ec1b7a2e80 --- /dev/null +++ b/releasenotes/notes/add-subnet-list-command-970f4b397469bdc6.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add support for the ``subnet list`` command. + [Bug `1523258 `_] From 291e3a7397979acfd035f8f97a14aa94c1a9f22a Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Tue, 26 Jan 2016 23:29:00 +0000 Subject: [PATCH 0543/3095] Updated from global requirements Change-Id: I0bd39534777eb919810b6a1b9c8117cdcd61ff2c --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 6b2e351ec0..a096e92a05 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ cliff>=1.15.0 # Apache-2.0 keystoneauth1>=2.1.0 # Apache-2.0 openstacksdk # Apache-2.0 os-client-config>=1.13.1 # Apache-2.0 -oslo.config>=3.2.0 # Apache-2.0 +oslo.config>=3.4.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 oslo.utils>=3.4.0 # Apache-2.0 python-glanceclient>=1.2.0 # Apache-2.0 From db458d7dec6b2cf712a14e54a76305258aedb700 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Tue, 26 Jan 2016 17:42:41 -0600 Subject: [PATCH 0544/3095] Use correct terminology for subnets OpenStack uses 'CIDR' incorrectly in many places. We are not going to perpetuate that usage. The correct name here is simply 'subnet' as the data is the network address for the subnet, in CIDR notation. Also, some additional cleanups as suggested in comments to https://review.openstack.org/#/c/84782 Depends-on: I3c0748074a6511ff92500516b3129886d2476eed Change-Id: Ib44c49dc1739ce7d881432e482dd16f8928eef49 --- doc/source/commands.rst | 2 +- openstackclient/network/v2/subnet.py | 4 ++-- openstackclient/tests/network/v2/test_subnet.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 5c8ea1b2c8..fae1fa6681 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -116,7 +116,7 @@ referring to both Compute and Volume quotas. * ``service``: (**Identity**) a cloud service * ``service provider``: (**Identity**) a resource that consumes assertions from an ``identity provider`` * ``snapshot``: (**Volume**) a point-in-time copy of a volume -* ``subnet``: (**Network**) - a pool of private IP addresses that can be assigned to instances or other resources +* ``subnet``: (**Network**) - a contiguous range of IP addresses assigned to a network * ``token``: (**Identity**) a bearer token managed by Identity service * ``usage``: (**Compute**) display host resources being consumed * ``user``: (**Identity**) individual cloud resources users diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index cd0e52ffcb..627471252a 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -53,10 +53,10 @@ def take_action(self, parsed_args): data = self.app.client_manager.network.subnets() - headers = ('ID', 'Name', 'Network', 'CIDR') + headers = ('ID', 'Name', 'Network', 'Subnet') columns = ('id', 'name', 'network_id', 'cidr') if parsed_args.long: - headers += ('Project', 'DHCP', 'DNS Nameservers', + headers += ('Project', 'DHCP', 'Name Servers', 'Allocation Pools', 'Host Routes', 'IP Version', 'Gateway') columns += ('tenant_id', 'enable_dhcp', 'dns_nameservers', diff --git a/openstackclient/tests/network/v2/test_subnet.py b/openstackclient/tests/network/v2/test_subnet.py index 74b4d33283..5fca5edd99 100644 --- a/openstackclient/tests/network/v2/test_subnet.py +++ b/openstackclient/tests/network/v2/test_subnet.py @@ -34,12 +34,12 @@ class TestListSubnet(TestSubnet): 'ID', 'Name', 'Network', - 'CIDR' + 'Subnet' ) columns_long = columns + ( 'Project', 'DHCP', - 'DNS Nameservers', + 'Name Servers', 'Allocation Pools', 'Host Routes', 'IP Version', From a7e5faf22c6d3bb8f5700f37519b1aa8fa2887c5 Mon Sep 17 00:00:00 2001 From: Dougal Matthews Date: Wed, 27 Jan 2016 14:38:22 +0000 Subject: [PATCH 0545/3095] Remove the Tuskar client The Tuskar project is now inactive and has been archived. Change-Id: I4c7cb50560b40ba1bc130be5bdc9446a46b09c24 --- doc/source/commands.rst | 2 -- doc/source/plugins.rst | 1 - test-requirements.txt | 1 - 3 files changed, 4 deletions(-) diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 92b89637ad..51b7d14b4b 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -144,8 +144,6 @@ list check out :doc:`plugin-commands`. * ``dataprocessing image``: (**Data Processing (Sahara)**) * ``dataprocessing image tags``: (**Data Processing (Sahara)**) * ``dataprocessing plugin``: (**Data Processing (Sahara)**) -* ``management plan``: (**Management (Tuskar)**) -* ``management role``: (**Management (Tuskar)**) * ``message-broker cluster``: (**Message Broker (Cue)**) * ``message flavor``: (**Messaging (Zaqar)**) * ``pool``: (**Messaging (Zaqar)**) diff --git a/doc/source/plugins.rst b/doc/source/plugins.rst index 4013ae318a..0c838ec5e6 100644 --- a/doc/source/plugins.rst +++ b/doc/source/plugins.rst @@ -38,7 +38,6 @@ python-mistralclient using OpenStackClient python-muranoclient using argparse python-saharaclient using OpenStackClient python-troveclient using argparse -python-tuskarclient using OpenStackClient python-zaqarclient using OpenStackClient ============================= ====================================== diff --git a/test-requirements.txt b/test-requirements.txt index a0b05bf1b6..d0d6829563 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -26,5 +26,4 @@ python-heatclient>=0.6.0 # Apache-2.0 python-ironicclient>=0.9.0 # Apache-2.0 python-mistralclient>=1.0.0 # Apache-2.0 python-saharaclient>=0.10.0 # Apache-2.0 -python-tuskarclient>=0.1.17 # Apache-2.0 python-zaqarclient>=0.3.0 # Apache-2.0 From 3a48989eb02187f384cfbf7bb7cd55502741fc68 Mon Sep 17 00:00:00 2001 From: Tom Cocozzello Date: Wed, 9 Dec 2015 10:08:16 -0600 Subject: [PATCH 0546/3095] Return names in list role assignments Utilize the new include names functionality added to list role assignments (GET /role_assignments?include_names=True). Which will return the names of the entities instead of their IDs. Change-Id: I6dc03baf61ef9354a8a259a9f17ff47ce1665ce7 Depends-On: I4aa77c08660a0cbd021502155938a46121ca76ef Closes-Bug: #1479569 Implements: blueprint list-assignment-with-names --- .../command-objects/role-assignment.rst | 4 + .../identity/v3/role_assignment.py | 40 ++++++-- openstackclient/tests/identity/v3/fakes.py | 29 ++++++ .../tests/identity/v3/test_role_assignment.py | 96 +++++++++++++++++-- ...ole_assignment_names-0db89f50259d4be2.yaml | 6 ++ 5 files changed, 162 insertions(+), 13 deletions(-) create mode 100644 releasenotes/notes/list_role_assignment_names-0db89f50259d4be2.yaml diff --git a/doc/source/command-objects/role-assignment.rst b/doc/source/command-objects/role-assignment.rst index 6bb24cb34c..893ebdc4e9 100644 --- a/doc/source/command-objects/role-assignment.rst +++ b/doc/source/command-objects/role-assignment.rst @@ -66,3 +66,7 @@ List role assignments .. option:: --inherited Specifies if the role grant is inheritable to the sub projects + +.. option:: --names + + Returns role assignments with names instead of IDs diff --git a/openstackclient/identity/v3/role_assignment.py b/openstackclient/identity/v3/role_assignment.py index 169c6cb970..771afeb2f5 100644 --- a/openstackclient/identity/v3/role_assignment.py +++ b/openstackclient/identity/v3/role_assignment.py @@ -39,6 +39,11 @@ def get_parser(self, prog_name): metavar='', help='Role to filter (name or ID)', ) + parser.add_argument( + '--names', + action="store_true", + help='Display names instead of IDs', + ) user_or_group = parser.add_mutually_exclusive_group() user_or_group.add_argument( '--user', @@ -113,6 +118,7 @@ def take_action(self, parsed_args): parsed_args.group_domain, ) + include_names = True if parsed_args.names else False effective = True if parsed_args.effective else False self.log.debug('take_action(%s)' % parsed_args) columns = ('Role', 'User', 'Group', 'Project', 'Domain', 'Inherited') @@ -125,17 +131,26 @@ def take_action(self, parsed_args): project=project, role=role, effective=effective, - os_inherit_extension_inherited_to=inherited_to) + os_inherit_extension_inherited_to=inherited_to, + include_names=include_names) data_parsed = [] for assignment in data: # Removing the extra "scope" layer in the assignment json scope = assignment.scope if 'project' in scope: - setattr(assignment, 'project', scope['project']['id']) + if include_names: + prj = '@'.join([scope['project']['name'], + scope['project']['domain']['name']]) + setattr(assignment, 'project', prj) + else: + setattr(assignment, 'project', scope['project']['id']) assignment.domain = '' elif 'domain' in scope: - setattr(assignment, 'domain', scope['domain']['id']) + if include_names: + setattr(assignment, 'domain', scope['domain']['name']) + else: + setattr(assignment, 'domain', scope['domain']['id']) assignment.project = '' else: @@ -148,17 +163,30 @@ def take_action(self, parsed_args): del assignment.scope if hasattr(assignment, 'user'): - setattr(assignment, 'user', assignment.user['id']) + if include_names: + usr = '@'.join([assignment.user['name'], + assignment.user['domain']['name']]) + setattr(assignment, 'user', usr) + else: + setattr(assignment, 'user', assignment.user['id']) assignment.group = '' elif hasattr(assignment, 'group'): - setattr(assignment, 'group', assignment.group['id']) + if include_names: + grp = '@'.join([assignment.group['name'], + assignment.group['domain']['name']]) + setattr(assignment, 'group', grp) + else: + setattr(assignment, 'group', assignment.group['id']) assignment.user = '' else: assignment.user = '' assignment.group = '' if hasattr(assignment, 'role'): - setattr(assignment, 'role', assignment.role['id']) + if include_names: + setattr(assignment, 'role', assignment.role['name']) + else: + setattr(assignment, 'role', assignment.role['id']) else: assignment.role = '' diff --git a/openstackclient/tests/identity/v3/fakes.py b/openstackclient/tests/identity/v3/fakes.py index 9fe341ed6a..59b08973b1 100644 --- a/openstackclient/tests/identity/v3/fakes.py +++ b/openstackclient/tests/identity/v3/fakes.py @@ -314,6 +314,22 @@ 'role': {'id': role_id}, } +ASSIGNMENT_WITH_PROJECT_ID_AND_USER_ID_INCLUDE_NAMES = { + 'scope': { + 'project': { + 'domain': {'id': domain_id, + 'name': domain_name}, + 'id': project_id, + 'name': project_name}}, + 'user': { + 'domain': {'id': domain_id, + 'name': domain_name}, + 'id': user_id, + 'name': user_name}, + 'role': {'id': role_id, + 'name': role_name}, +} + ASSIGNMENT_WITH_PROJECT_ID_AND_USER_ID_INHERITED = { 'scope': {'project': {'id': project_id}, 'OS-INHERIT:inherited_to': 'projects'}, @@ -333,6 +349,19 @@ 'role': {'id': role_id}, } +ASSIGNMENT_WITH_DOMAIN_ID_AND_USER_ID_INCLUDE_NAMES = { + 'scope': { + 'domain': {'id': domain_id, + 'name': domain_name}}, + 'user': { + 'domain': {'id': domain_id, + 'name': domain_name}, + 'id': user_id, + 'name': user_name}, + 'role': {'id': role_id, + 'name': role_name}, +} + ASSIGNMENT_WITH_DOMAIN_ID_AND_USER_ID_INHERITED = { 'scope': {'domain': {'id': domain_id}, 'OS-INHERIT:inherited_to': 'projects'}, diff --git a/openstackclient/tests/identity/v3/test_role_assignment.py b/openstackclient/tests/identity/v3/test_role_assignment.py index 5723a33477..067a9537e6 100644 --- a/openstackclient/tests/identity/v3/test_role_assignment.py +++ b/openstackclient/tests/identity/v3/test_role_assignment.py @@ -96,7 +96,8 @@ def test_role_assignment_list_no_filters(self): role=None, user=None, project=None, - os_inherit_extension_inherited_to=None) + os_inherit_extension_inherited_to=None, + include_names=False) self.assertEqual(self.columns, columns) datalist = (( @@ -143,6 +144,7 @@ def test_role_assignment_list_user(self): ('role', None), ('effective', False), ('inherited', False), + ('names', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -156,7 +158,8 @@ def test_role_assignment_list_user(self): project=None, role=None, effective=False, - os_inherit_extension_inherited_to=None) + os_inherit_extension_inherited_to=None, + include_names=False) self.assertEqual(self.columns, columns) datalist = (( @@ -203,6 +206,7 @@ def test_role_assignment_list_group(self): ('role', None), ('effective', False), ('inherited', False), + ('names', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -216,7 +220,8 @@ def test_role_assignment_list_group(self): project=None, role=None, user=None, - os_inherit_extension_inherited_to=None) + os_inherit_extension_inherited_to=None, + include_names=False) self.assertEqual(self.columns, columns) datalist = (( @@ -263,6 +268,7 @@ def test_role_assignment_list_domain(self): ('role', None), ('effective', False), ('inherited', False), + ('names', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -276,7 +282,8 @@ def test_role_assignment_list_domain(self): project=None, role=None, user=None, - os_inherit_extension_inherited_to=None) + os_inherit_extension_inherited_to=None, + include_names=False) self.assertEqual(self.columns, columns) datalist = (( @@ -323,6 +330,7 @@ def test_role_assignment_list_project(self): ('role', None), ('effective', False), ('inherited', False), + ('names', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -336,7 +344,8 @@ def test_role_assignment_list_project(self): project=self.projects_mock.get(), role=None, user=None, - os_inherit_extension_inherited_to=None) + os_inherit_extension_inherited_to=None, + include_names=False) self.assertEqual(self.columns, columns) datalist = (( @@ -381,6 +390,7 @@ def test_role_assignment_list_effective(self): ('role', None), ('effective', True), ('inherited', False), + ('names', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -394,7 +404,8 @@ def test_role_assignment_list_effective(self): project=None, role=None, user=None, - os_inherit_extension_inherited_to=None) + os_inherit_extension_inherited_to=None, + include_names=False) self.assertEqual(self.columns, columns) datalist = (( @@ -441,6 +452,7 @@ def test_role_assignment_list_inherited(self): ('role', None), ('effective', False), ('inherited', True), + ('names', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -454,7 +466,8 @@ def test_role_assignment_list_inherited(self): project=None, role=None, user=None, - os_inherit_extension_inherited_to='projects') + os_inherit_extension_inherited_to='projects', + include_names=False) self.assertEqual(self.columns, columns) datalist = (( @@ -472,3 +485,72 @@ def test_role_assignment_list_inherited(self): True ),) self.assertEqual(datalist, tuple(data)) + + def test_role_assignment_list_include_names(self): + + self.role_assignments_mock.list.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy( + identity_fakes + .ASSIGNMENT_WITH_PROJECT_ID_AND_USER_ID_INCLUDE_NAMES), + loaded=True, + ), + fakes.FakeResource( + None, + copy.deepcopy( + identity_fakes + .ASSIGNMENT_WITH_DOMAIN_ID_AND_USER_ID_INCLUDE_NAMES), + loaded=True, + ), + ] + + arglist = ['--names'] + verifylist = [ + ('user', None), + ('group', None), + ('domain', None), + ('project', None), + ('role', None), + ('effective', False), + ('inherited', False), + ('names', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + + # This test will not run correctly until the patch in the python + # client is merged. Once that is done 'data' should return the + # correct information + columns, data = self.cmd.take_action(parsed_args) + + self.role_assignments_mock.list.assert_called_with( + domain=None, + group=None, + effective=False, + project=None, + role=None, + user=None, + os_inherit_extension_inherited_to=None, + include_names=True) + + collist = ('Role', 'User', 'Group', 'Project', 'Domain', 'Inherited') + self.assertEqual(columns, collist) + + datalist1 = (( + identity_fakes.role_name, + '@'.join([identity_fakes.user_name, identity_fakes.domain_name]), + '', + '@'.join([identity_fakes.project_name, + identity_fakes.domain_name]), + '', + False + ), (identity_fakes.role_name, + '@'.join([identity_fakes.user_name, identity_fakes.domain_name]), + '', + '', + identity_fakes.domain_name, + False + ),) + self.assertEqual(tuple(data), datalist1) diff --git a/releasenotes/notes/list_role_assignment_names-0db89f50259d4be2.yaml b/releasenotes/notes/list_role_assignment_names-0db89f50259d4be2.yaml new file mode 100644 index 0000000000..6f5df8d89c --- /dev/null +++ b/releasenotes/notes/list_role_assignment_names-0db89f50259d4be2.yaml @@ -0,0 +1,6 @@ +--- +features: + - > + [`bug 1479569 `_] + Add an optional ``--names`` argument to the `role assignment list`` command. This + will output names instead of IDs for users, groups, roles, projects, and domains. \ No newline at end of file From c43bdc1441a2149562264a82437bf3d99f97939f Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 28 Jan 2016 13:41:16 +0000 Subject: [PATCH 0547/3095] Updated from global requirements Change-Id: If070122805497e0943918900c6d1f6a54faef37a --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a096e92a05..cc189ba7ba 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ six>=1.9.0 # MIT Babel>=1.3 # BSD cliff>=1.15.0 # Apache-2.0 keystoneauth1>=2.1.0 # Apache-2.0 -openstacksdk # Apache-2.0 +openstacksdk>=0.7.4 # Apache-2.0 os-client-config>=1.13.1 # Apache-2.0 oslo.config>=3.4.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 From 5903ffff8a56481e124aa4fe8144c16348f6f8b9 Mon Sep 17 00:00:00 2001 From: Mark Vanderwiel Date: Thu, 28 Jan 2016 10:52:45 -0600 Subject: [PATCH 0548/3095] Allow wait_for_delete to work for all clients Allow the exception and error status strings to be passed in such that other plugins can make use of this function. There is a comment in find_resource: The exception to catch here is dependent on which client library the manager passed in belongs to. Eventually this should be pulled from a common set of client exceptions. Since I think that is a long ways off, this change will work now and also work when a common exception is defined and used. Change-Id: Iab56cd1166028caed4f1e657e0b1ee81af3f48d8 --- openstackclient/common/utils.py | 8 ++++++-- openstackclient/tests/common/test_utils.py | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index 3ae30c8f71..2860608d2d 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -331,6 +331,8 @@ def wait_for_status(status_f, def wait_for_delete(manager, res_id, status_field='status', + error_status=['error'], + exception_name=['NotFound'], sleep_time=5, timeout=300, callback=None): @@ -341,6 +343,8 @@ def wait_for_delete(manager, :param status_field: the status attribute in the returned resource object, this is used to check for error states while the resource is being deleted + :param error_status: a list of status strings for error + :param exception_name: a list of exception strings for deleted case :param sleep_time: wait this long between checks (seconds) :param timeout: check until this long (seconds) :param callback: called per sleep cycle, useful to display progress; this @@ -357,12 +361,12 @@ def wait_for_delete(manager, # handle a NotFound exception here without parsing the message res = manager.get(res_id) except Exception as ex: - if type(ex).__name__ == 'NotFound': + if type(ex).__name__ in exception_name: return True raise status = getattr(res, status_field, '').lower() - if status == 'error': + if status in error_status: return False if callback: diff --git a/openstackclient/tests/common/test_utils.py b/openstackclient/tests/common/test_utils.py index 064ad417e6..9c02df317f 100644 --- a/openstackclient/tests/common/test_utils.py +++ b/openstackclient/tests/common/test_utils.py @@ -212,6 +212,28 @@ def test_wait_for_delete_error(self, mock_sleep): self.assertFalse(utils.wait_for_delete(manager, res_id)) self.assertFalse(mock_sleep.called) + @mock.patch.object(time, 'sleep') + def test_wait_for_delete_error_with_overrides(self, mock_sleep): + # Tests that we fail if the resource is my_status=failed + resource = mock.MagicMock(my_status='FAILED') + mock_get = mock.Mock(return_value=resource) + manager = mock.MagicMock(get=mock_get) + res_id = str(uuid.uuid4()) + self.assertFalse(utils.wait_for_delete(manager, res_id, + status_field='my_status', + error_status=['failed'])) + self.assertFalse(mock_sleep.called) + + @mock.patch.object(time, 'sleep') + def test_wait_for_delete_error_with_overrides_exception(self, mock_sleep): + # Tests that we succeed if the resource is specific exception + mock_get = mock.Mock(side_effect=Exception) + manager = mock.MagicMock(get=mock_get) + res_id = str(uuid.uuid4()) + self.assertTrue(utils.wait_for_delete(manager, res_id, + exception_name=['Exception'])) + self.assertFalse(mock_sleep.called) + def test_build_kwargs_dict_value_set(self): self.assertEqual({'arg_bla': 'bla'}, utils.build_kwargs_dict('arg_bla', 'bla')) From 67ecf4ef436f3f70427658225a34c342ba8ea482 Mon Sep 17 00:00:00 2001 From: Brad Behle Date: Wed, 27 Jan 2016 20:50:22 -0600 Subject: [PATCH 0549/3095] Add availability zone support for network commands Add --availability-zone-hint parm to network create. Also add availability_zones and availability_zone_hints to the network list and network show commands Change-Id: Ib4dc2e3e7897939be7bef6b25a095c8222b885bc Partially-implements: blueprint neutron-client --- doc/source/command-objects/network.rst | 6 +++++ openstackclient/network/v2/network.py | 21 +++++++++++++++++- openstackclient/tests/network/v2/fakes.py | 5 ++++- .../tests/network/v2/test_network.py | 22 ++++++++++++++++++- 4 files changed, 51 insertions(+), 3 deletions(-) diff --git a/doc/source/command-objects/network.rst b/doc/source/command-objects/network.rst index 7c791840fc..bb36667253 100644 --- a/doc/source/command-objects/network.rst +++ b/doc/source/command-objects/network.rst @@ -16,6 +16,7 @@ Create new network [--project [--project-domain ]] [--enable | --disable] [--share | --no-share] + [--availability-zone-hint ] .. option:: --project @@ -43,6 +44,11 @@ Create new network Do not share the network between projects +.. option:: --availability-zone-hint + + Availability Zone in which to create this network (requires the Network + Availability Zone extension, this option can be repeated). + .. _network_create-name: .. describe:: diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 38dff8d92a..7d9324f085 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -36,6 +36,8 @@ def _format_router_external(item): 'subnets': utils.format_list, 'admin_state_up': _format_admin_state, 'router_external': _format_router_external, + 'availability_zones': utils.format_list, + 'availability_zone_hints': utils.format_list, } @@ -93,8 +95,19 @@ def get_parser(self, prog_name): parser.add_argument( '--project', metavar='', - help="Owner's project (name or ID)") + help="Owner's project (name or ID)" + ) identity_common.add_project_domain_option_to_parser(parser) + + parser.add_argument( + '--availability-zone-hint', + action='append', + dest='availability_zone_hints', + metavar='', + help='Availability Zone in which to create this network ' + '(requires the Network Availability Zone extension, ' + 'this option can be repeated).', + ) return parser def take_action(self, parsed_args): @@ -119,6 +132,10 @@ def get_body(self, parsed_args): parsed_args.project_domain, ).id body['tenant_id'] = project_id + if parsed_args.availability_zone_hints is not None: + body['availability_zone_hints'] = \ + parsed_args.availability_zone_hints + return body @@ -181,6 +198,7 @@ def take_action(self, parsed_args): 'subnets', 'provider_network_type', 'router_external', + 'availability_zones', ) column_headers = ( 'ID', @@ -192,6 +210,7 @@ def take_action(self, parsed_args): 'Subnets', 'Network Type', 'Router Type', + 'Availability Zones', ) else: columns = ( diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index de885c62ab..146ba61649 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -85,6 +85,8 @@ def create_one_network(attrs={}, methods={}): 'provider_network_type': 'vlan', 'router_external': True, 'is_dirty': True, + 'availability_zones': [], + 'availability_zone_hints': [], } # Overwrite default attributes. @@ -93,7 +95,8 @@ def create_one_network(attrs={}, methods={}): # Set default methods. network_methods = { 'keys': ['id', 'name', 'admin_state_up', 'router_external', - 'status', 'subnets', 'tenant_id'], + 'status', 'subnets', 'tenant_id', 'availability_zones', + 'availability_zone_hints'], } # Overwrite default methods. diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index 12ac802c82..37cc66742a 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -37,11 +37,16 @@ class TestCreateNetworkIdentityV3(TestNetwork): # The new network created. _network = network_fakes.FakeNetwork.create_one_network( - attrs={'tenant_id': identity_fakes_v3.project_id} + attrs={ + 'tenant_id': identity_fakes_v3.project_id, + 'availability_zone_hints': ["nova"], + } ) columns = ( 'admin_state_up', + 'availability_zone_hints', + 'availability_zones', 'id', 'name', 'project_id', @@ -52,6 +57,8 @@ class TestCreateNetworkIdentityV3(TestNetwork): data = ( network._format_admin_state(_network.admin_state_up), + utils.format_list(_network.availability_zone_hints), + utils.format_list(_network.availability_zones), _network.id, _network.name, _network.project_id, @@ -129,6 +136,7 @@ def test_create_all_options(self): "--share", "--project", identity_fakes_v3.project_name, "--project-domain", identity_fakes_v3.domain_name, + "--availability-zone-hint", "nova", self._network.name, ] verifylist = [ @@ -136,6 +144,7 @@ def test_create_all_options(self): ('shared', True), ('project', identity_fakes_v3.project_name), ('project_domain', identity_fakes_v3.domain_name), + ('availability_zone_hints', ["nova"]), ('name', self._network.name), ] @@ -144,6 +153,7 @@ def test_create_all_options(self): self.network.create_network.assert_called_with(**{ 'admin_state_up': False, + 'availability_zone_hints': ["nova"], 'name': self._network.name, 'shared': True, 'tenant_id': identity_fakes_v3.project_id, @@ -184,6 +194,8 @@ class TestCreateNetworkIdentityV2(TestNetwork): columns = ( 'admin_state_up', + 'availability_zone_hints', + 'availability_zones', 'id', 'name', 'project_id', @@ -194,6 +206,8 @@ class TestCreateNetworkIdentityV2(TestNetwork): data = ( network._format_admin_state(_network.admin_state_up), + utils.format_list(_network.availability_zone_hints), + utils.format_list(_network.availability_zones), _network.id, _network.name, _network.project_id, @@ -324,6 +338,7 @@ class TestListNetwork(TestNetwork): 'Subnets', 'Network Type', 'Router Type', + 'Availability Zones', ) data = [] @@ -346,6 +361,7 @@ class TestListNetwork(TestNetwork): utils.format_list(net.subnets), net.provider_network_type, network._format_router_external(net.router_external), + utils.format_list(net.availability_zones), )) def setUp(self): @@ -483,6 +499,8 @@ class TestShowNetwork(TestNetwork): columns = ( 'admin_state_up', + 'availability_zone_hints', + 'availability_zones', 'id', 'name', 'project_id', @@ -493,6 +511,8 @@ class TestShowNetwork(TestNetwork): data = ( network._format_admin_state(_network.admin_state_up), + utils.format_list(_network.availability_zone_hints), + utils.format_list(_network.availability_zones), _network.id, _network.name, _network.project_id, From a6cd2dc13680c818c7053ef51526962639441b31 Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Thu, 28 Jan 2016 23:46:21 +0900 Subject: [PATCH 0550/3095] Update translation setup Follow new infra setup for translations, see spec http://specs.openstack.org/openstack-infra/infra-specs/specs/translation_setup.html for full details. This basically renames python-openstackclient/locale/python-openstackclient.pot to openstackclient/locale/openstackclient.pot. For this we need to update setup.cfg. Update also domain name in i18n.py. Change-Id: I89fad12f20775c8b7cd228348ff82a77488e6ab2 --- openstackclient/i18n.py | 2 +- .../locale/de/LC_MESSAGES/openstackclient.po | 0 .../locale/openstackclient.pot | 0 .../locale/zh_TW/LC_MESSAGES/openstackclient.po | 0 setup.cfg | 12 ++++++------ 5 files changed, 7 insertions(+), 7 deletions(-) rename python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po => openstackclient/locale/de/LC_MESSAGES/openstackclient.po (100%) rename python-openstackclient/locale/python-openstackclient.pot => openstackclient/locale/openstackclient.pot (100%) rename python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po => openstackclient/locale/zh_TW/LC_MESSAGES/openstackclient.po (100%) diff --git a/openstackclient/i18n.py b/openstackclient/i18n.py index 3a11c1d0dc..1d09772cab 100644 --- a/openstackclient/i18n.py +++ b/openstackclient/i18n.py @@ -15,7 +15,7 @@ import oslo_i18n -_translators = oslo_i18n.TranslatorFactory(domain='python-openstackclient') +_translators = oslo_i18n.TranslatorFactory(domain='openstackclient') # The primary translation function using the well-known name "_" _ = _translators.primary diff --git a/python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po b/openstackclient/locale/de/LC_MESSAGES/openstackclient.po similarity index 100% rename from python-openstackclient/locale/de/LC_MESSAGES/python-openstackclient.po rename to openstackclient/locale/de/LC_MESSAGES/openstackclient.po diff --git a/python-openstackclient/locale/python-openstackclient.pot b/openstackclient/locale/openstackclient.pot similarity index 100% rename from python-openstackclient/locale/python-openstackclient.pot rename to openstackclient/locale/openstackclient.pot diff --git a/python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po b/openstackclient/locale/zh_TW/LC_MESSAGES/openstackclient.po similarity index 100% rename from python-openstackclient/locale/zh_TW/LC_MESSAGES/python-openstackclient.po rename to openstackclient/locale/zh_TW/LC_MESSAGES/openstackclient.po diff --git a/setup.cfg b/setup.cfg index 8a09735c30..2751eb3178 100644 --- a/setup.cfg +++ b/setup.cfg @@ -447,13 +447,13 @@ universal = 1 [extract_messages] keywords = _ gettext ngettext l_ lazy_gettext mapping_file = babel.cfg -output_file = python-openstackclient/locale/python-openstackclient.pot +output_file = openstackclient/locale/openstackclient.pot [update_catalog] -domain = python-openstackclient -output_dir = python-openstackclient/locale -input_file = python-openstackclient/locale/python-openstackclient.pot +domain = openstackclient +output_dir = openstackclient/locale +input_file = openstackclient/locale/openstackclient.pot [compile_catalog] -directory = python-openstackclient/locale -domain = python-openstackclient +directory = openstackclient/locale +domain = openstackclient From c0d2120883080ba1a4326dc97e078d95de170a51 Mon Sep 17 00:00:00 2001 From: Jas Date: Thu, 21 Jan 2016 04:19:48 -0600 Subject: [PATCH 0551/3095] Add availability zone support for router commands This patch allows the adding of availability_zone_hints during router create. Also allows for the display of availability_zones during list and and show commands. Change-Id: Ifbc5c218bc7103d28076d726212ce25321bcf7f1 Partial-bug: #1519503 Partially-implements: blueprint neutron-client --- doc/source/command-objects/router.rst | 6 ++++ openstackclient/network/v2/router.py | 17 +++++++++++ openstackclient/tests/network/v2/fakes.py | 2 ++ .../tests/network/v2/test_router.py | 28 +++++++++++++++++++ 4 files changed, 53 insertions(+) diff --git a/doc/source/command-objects/router.rst b/doc/source/command-objects/router.rst index e881065dd4..2a5bf0539c 100644 --- a/doc/source/command-objects/router.rst +++ b/doc/source/command-objects/router.rst @@ -16,6 +16,7 @@ Create new router [--project [--project-domain ]] [--enable | --disable] [--distributed] + [--availability-zone-hint ] .. option:: --project @@ -39,6 +40,11 @@ Create new router Create a distributed router +.. option:: --availability-zone-hint + + Availability Zone in which to create this router (requires the Router + Availability Zone extension, this option can be repeated). + .. _router_create-name: .. describe:: diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 09e0fe4c84..06e086db00 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -39,6 +39,8 @@ def _format_external_gateway_info(info): _formatters = { 'admin_state_up': _format_admin_state, 'external_gateway_info': _format_external_gateway_info, + 'availability_zones': utils.format_list, + 'availability_zone_hints': utils.format_list, } @@ -50,6 +52,9 @@ def _get_attrs(client_manager, parsed_args): attrs['admin_state_up'] = parsed_args.admin_state_up if parsed_args.distributed is not None: attrs['distributed'] = parsed_args.distributed + if ('availability_zone_hints' in parsed_args + and parsed_args.availability_zone_hints is not None): + attrs['availability_zone_hints'] = parsed_args.availability_zone_hints # "router set" command doesn't support setting project. if 'project' in parsed_args and parsed_args.project is not None: identity_client = client_manager.identity @@ -105,6 +110,16 @@ def get_parser(self, prog_name): metavar='', help="Owner's project (name or ID)", ) + parser.add_argument( + '--availability-zone-hint', + metavar='', + action='append', + dest='availability_zone_hints', + help='Availability Zone in which to create this router ' + '(requires the Router Availability Zone extension, ' + 'this option can be repeated).', + ) + identity_common.add_project_domain_option_to_parser(parser) return parser @@ -189,10 +204,12 @@ def take_action(self, parsed_args): columns = columns + ( 'routes', 'external_gateway_info', + 'availability_zones' ) column_headers = column_headers + ( 'Routes', 'External gateway info', + 'Availability zones' ) data = client.routers() diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 80760a7792..2bddf17e58 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -171,6 +171,8 @@ def create_one_router(attrs={}, methods={}): 'tenant_id': 'project-id-' + uuid.uuid4().hex, 'routes': [], 'external_gateway_info': {}, + 'availability_zone_hints': [], + 'availability_zones': [], } # Overwrite default attributes. diff --git a/openstackclient/tests/network/v2/test_router.py b/openstackclient/tests/network/v2/test_router.py index fba6e192c5..98e9f17a8f 100644 --- a/openstackclient/tests/network/v2/test_router.py +++ b/openstackclient/tests/network/v2/test_router.py @@ -14,6 +14,7 @@ import mock from openstackclient.common import exceptions +from openstackclient.common import utils as osc_utils from openstackclient.network.v2 import router from openstackclient.tests.network.v2 import fakes as network_fakes from openstackclient.tests import utils as tests_utils @@ -88,6 +89,31 @@ def test_create_default_options(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_create_with_AZ_hints(self): + arglist = [ + self.new_router.name, + '--availability-zone-hint', 'fake-az', + '--availability-zone-hint', 'fake-az2', + ] + verifylist = [ + ('name', self.new_router.name), + ('availability_zone_hints', ['fake-az', 'fake-az2']), + ('admin_state_up', True), + ('distributed', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = (self.cmd.take_action(parsed_args)) + self.network.create_router.assert_called_with(**{ + 'admin_state_up': True, + 'name': self.new_router.name, + 'distributed': False, + 'availability_zone_hints': ['fake-az', 'fake-az2'], + }) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + class TestDeleteRouter(TestRouter): @@ -135,6 +161,7 @@ class TestListRouter(TestRouter): columns_long = columns + ( 'Routes', 'External gateway info', + 'Availability zones' ) data = [] @@ -155,6 +182,7 @@ class TestListRouter(TestRouter): data[i] + ( r.routes, router._format_external_gateway_info(r.external_gateway_info), + osc_utils.format_list(r.availability_zones), ) ) From 5032dbc8074d5133c6b71610cd57d3c8da07c9b9 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Fri, 29 Jan 2016 15:45:07 -0600 Subject: [PATCH 0552/3095] Skip identity v2 functional tests DevStack now uses identity v3 by default thus causing OSC to only load openstack.identity.v3 commands. This prevents running functional tests on openstack.identity.v2 commands. As a result, this patch set skips all identity v2 functional tests to unblock the gate. Change-Id: I066187318be71792a966fa21226fab0d406c3758 Partial-Bug: #1539780 --- functional/tests/identity/v2/test_catalog.py | 3 +++ functional/tests/identity/v2/test_ec2_credentials.py | 3 +++ functional/tests/identity/v2/test_endpoint.py | 3 +++ functional/tests/identity/v2/test_project.py | 3 +++ functional/tests/identity/v2/test_role.py | 3 +++ functional/tests/identity/v2/test_service.py | 3 +++ functional/tests/identity/v2/test_token.py | 3 +++ functional/tests/identity/v2/test_user.py | 3 +++ functional/tests/identity/v3/test_identity.py | 9 --------- 9 files changed, 24 insertions(+), 9 deletions(-) diff --git a/functional/tests/identity/v2/test_catalog.py b/functional/tests/identity/v2/test_catalog.py index 3a1f7e112a..7b5c0f971c 100644 --- a/functional/tests/identity/v2/test_catalog.py +++ b/functional/tests/identity/v2/test_catalog.py @@ -10,9 +10,12 @@ # License for the specific language governing permissions and limitations # under the License. +import testtools + from functional.tests.identity.v2 import test_identity +@testtools.skip('bug/1539780') class CatalogTests(test_identity.IdentityTests): def test_catalog_list(self): diff --git a/functional/tests/identity/v2/test_ec2_credentials.py b/functional/tests/identity/v2/test_ec2_credentials.py index 86702c0c6f..910229dbe7 100644 --- a/functional/tests/identity/v2/test_ec2_credentials.py +++ b/functional/tests/identity/v2/test_ec2_credentials.py @@ -10,9 +10,12 @@ # License for the specific language governing permissions and limitations # under the License. +import testtools + from functional.tests.identity.v2 import test_identity +@testtools.skip('bug/1539780') class EC2CredentialsTests(test_identity.IdentityTests): def test_ec2_credentials_create(self): diff --git a/functional/tests/identity/v2/test_endpoint.py b/functional/tests/identity/v2/test_endpoint.py index 0aed3220c8..aac61263c2 100644 --- a/functional/tests/identity/v2/test_endpoint.py +++ b/functional/tests/identity/v2/test_endpoint.py @@ -10,9 +10,12 @@ # License for the specific language governing permissions and limitations # under the License. +import testtools + from functional.tests.identity.v2 import test_identity +@testtools.skip('bug/1539780') class EndpointTests(test_identity.IdentityTests): def test_endpoint_create(self): diff --git a/functional/tests/identity/v2/test_project.py b/functional/tests/identity/v2/test_project.py index 3a5e8e81a9..52c4639e54 100644 --- a/functional/tests/identity/v2/test_project.py +++ b/functional/tests/identity/v2/test_project.py @@ -10,11 +10,14 @@ # License for the specific language governing permissions and limitations # under the License. +import testtools + from tempest_lib.common.utils import data_utils from functional.tests.identity.v2 import test_identity +@testtools.skip('bug/1539780') class ProjectTests(test_identity.IdentityTests): def test_project_create(self): diff --git a/functional/tests/identity/v2/test_role.py b/functional/tests/identity/v2/test_role.py index e542a5fbdd..c0eb5fc7d5 100644 --- a/functional/tests/identity/v2/test_role.py +++ b/functional/tests/identity/v2/test_role.py @@ -10,9 +10,12 @@ # License for the specific language governing permissions and limitations # under the License. +import testtools + from functional.tests.identity.v2 import test_identity +@testtools.skip('bug/1539780') class RoleTests(test_identity.IdentityTests): def test_role_create(self): diff --git a/functional/tests/identity/v2/test_service.py b/functional/tests/identity/v2/test_service.py index bd982be1bd..716966fc47 100644 --- a/functional/tests/identity/v2/test_service.py +++ b/functional/tests/identity/v2/test_service.py @@ -10,9 +10,12 @@ # License for the specific language governing permissions and limitations # under the License. +import testtools + from functional.tests.identity.v2 import test_identity +@testtools.skip('bug/1539780') class ServiceTests(test_identity.IdentityTests): def test_service_create(self): diff --git a/functional/tests/identity/v2/test_token.py b/functional/tests/identity/v2/test_token.py index bac2b0ac9c..1bd7baba4f 100644 --- a/functional/tests/identity/v2/test_token.py +++ b/functional/tests/identity/v2/test_token.py @@ -10,9 +10,12 @@ # License for the specific language governing permissions and limitations # under the License. +import testtools + from functional.tests.identity.v2 import test_identity +@testtools.skip('bug/1539780') class TokenTests(test_identity.IdentityTests): def test_token_issue(self): diff --git a/functional/tests/identity/v2/test_user.py b/functional/tests/identity/v2/test_user.py index 41895e7ecc..5cd16dbebd 100644 --- a/functional/tests/identity/v2/test_user.py +++ b/functional/tests/identity/v2/test_user.py @@ -10,12 +10,15 @@ # License for the specific language governing permissions and limitations # under the License. +import testtools + from tempest_lib.common.utils import data_utils from functional.common import exceptions from functional.tests.identity.v2 import test_identity +@testtools.skip('bug/1539780') class UserTests(test_identity.IdentityTests): def test_user_create(self): diff --git a/functional/tests/identity/v3/test_identity.py b/functional/tests/identity/v3/test_identity.py index 3164e8fbb5..6aeddccc03 100644 --- a/functional/tests/identity/v3/test_identity.py +++ b/functional/tests/identity/v3/test_identity.py @@ -10,8 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -import os - from tempest_lib.common.utils import data_utils from functional.common import test @@ -49,13 +47,6 @@ def setUpClass(cls): if hasattr(super(IdentityTests, cls), 'setUpClass'): super(IdentityTests, cls).setUpClass() - # prepare v3 env - auth_url = os.environ.get('OS_AUTH_URL') - auth_url = auth_url.replace('v2.0', 'v3') - os.environ['OS_AUTH_URL'] = auth_url - os.environ['OS_IDENTITY_API_VERSION'] = '3' - os.environ['OS_AUTH_TYPE'] = 'v3password' - # create dummy domain cls.domain_name = data_utils.rand_name('TestDomain') cls.domain_description = data_utils.rand_name('description') From bca1a930f49b731001c1a06f78b0ff516af9959a Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Mon, 1 Feb 2016 20:56:43 +0000 Subject: [PATCH 0553/3095] Revert "Skip identity v2 functional tests" This reverts commit 5032dbc8074d5133c6b71610cd57d3c8da07c9b9 based on [1]. [1] https://review.openstack.org/#/c/274703/ Change-Id: Ic10ef7c37d71e452fbc4bd36e28be79d669b4e3f --- functional/tests/identity/v2/test_catalog.py | 3 --- functional/tests/identity/v2/test_ec2_credentials.py | 3 --- functional/tests/identity/v2/test_endpoint.py | 3 --- functional/tests/identity/v2/test_project.py | 3 --- functional/tests/identity/v2/test_role.py | 3 --- functional/tests/identity/v2/test_service.py | 3 --- functional/tests/identity/v2/test_token.py | 3 --- functional/tests/identity/v2/test_user.py | 3 --- functional/tests/identity/v3/test_identity.py | 9 +++++++++ 9 files changed, 9 insertions(+), 24 deletions(-) diff --git a/functional/tests/identity/v2/test_catalog.py b/functional/tests/identity/v2/test_catalog.py index 7b5c0f971c..3a1f7e112a 100644 --- a/functional/tests/identity/v2/test_catalog.py +++ b/functional/tests/identity/v2/test_catalog.py @@ -10,12 +10,9 @@ # License for the specific language governing permissions and limitations # under the License. -import testtools - from functional.tests.identity.v2 import test_identity -@testtools.skip('bug/1539780') class CatalogTests(test_identity.IdentityTests): def test_catalog_list(self): diff --git a/functional/tests/identity/v2/test_ec2_credentials.py b/functional/tests/identity/v2/test_ec2_credentials.py index 910229dbe7..86702c0c6f 100644 --- a/functional/tests/identity/v2/test_ec2_credentials.py +++ b/functional/tests/identity/v2/test_ec2_credentials.py @@ -10,12 +10,9 @@ # License for the specific language governing permissions and limitations # under the License. -import testtools - from functional.tests.identity.v2 import test_identity -@testtools.skip('bug/1539780') class EC2CredentialsTests(test_identity.IdentityTests): def test_ec2_credentials_create(self): diff --git a/functional/tests/identity/v2/test_endpoint.py b/functional/tests/identity/v2/test_endpoint.py index aac61263c2..0aed3220c8 100644 --- a/functional/tests/identity/v2/test_endpoint.py +++ b/functional/tests/identity/v2/test_endpoint.py @@ -10,12 +10,9 @@ # License for the specific language governing permissions and limitations # under the License. -import testtools - from functional.tests.identity.v2 import test_identity -@testtools.skip('bug/1539780') class EndpointTests(test_identity.IdentityTests): def test_endpoint_create(self): diff --git a/functional/tests/identity/v2/test_project.py b/functional/tests/identity/v2/test_project.py index 52c4639e54..3a5e8e81a9 100644 --- a/functional/tests/identity/v2/test_project.py +++ b/functional/tests/identity/v2/test_project.py @@ -10,14 +10,11 @@ # License for the specific language governing permissions and limitations # under the License. -import testtools - from tempest_lib.common.utils import data_utils from functional.tests.identity.v2 import test_identity -@testtools.skip('bug/1539780') class ProjectTests(test_identity.IdentityTests): def test_project_create(self): diff --git a/functional/tests/identity/v2/test_role.py b/functional/tests/identity/v2/test_role.py index c0eb5fc7d5..e542a5fbdd 100644 --- a/functional/tests/identity/v2/test_role.py +++ b/functional/tests/identity/v2/test_role.py @@ -10,12 +10,9 @@ # License for the specific language governing permissions and limitations # under the License. -import testtools - from functional.tests.identity.v2 import test_identity -@testtools.skip('bug/1539780') class RoleTests(test_identity.IdentityTests): def test_role_create(self): diff --git a/functional/tests/identity/v2/test_service.py b/functional/tests/identity/v2/test_service.py index 716966fc47..bd982be1bd 100644 --- a/functional/tests/identity/v2/test_service.py +++ b/functional/tests/identity/v2/test_service.py @@ -10,12 +10,9 @@ # License for the specific language governing permissions and limitations # under the License. -import testtools - from functional.tests.identity.v2 import test_identity -@testtools.skip('bug/1539780') class ServiceTests(test_identity.IdentityTests): def test_service_create(self): diff --git a/functional/tests/identity/v2/test_token.py b/functional/tests/identity/v2/test_token.py index 1bd7baba4f..bac2b0ac9c 100644 --- a/functional/tests/identity/v2/test_token.py +++ b/functional/tests/identity/v2/test_token.py @@ -10,12 +10,9 @@ # License for the specific language governing permissions and limitations # under the License. -import testtools - from functional.tests.identity.v2 import test_identity -@testtools.skip('bug/1539780') class TokenTests(test_identity.IdentityTests): def test_token_issue(self): diff --git a/functional/tests/identity/v2/test_user.py b/functional/tests/identity/v2/test_user.py index 5cd16dbebd..41895e7ecc 100644 --- a/functional/tests/identity/v2/test_user.py +++ b/functional/tests/identity/v2/test_user.py @@ -10,15 +10,12 @@ # License for the specific language governing permissions and limitations # under the License. -import testtools - from tempest_lib.common.utils import data_utils from functional.common import exceptions from functional.tests.identity.v2 import test_identity -@testtools.skip('bug/1539780') class UserTests(test_identity.IdentityTests): def test_user_create(self): diff --git a/functional/tests/identity/v3/test_identity.py b/functional/tests/identity/v3/test_identity.py index 6aeddccc03..3164e8fbb5 100644 --- a/functional/tests/identity/v3/test_identity.py +++ b/functional/tests/identity/v3/test_identity.py @@ -10,6 +10,8 @@ # License for the specific language governing permissions and limitations # under the License. +import os + from tempest_lib.common.utils import data_utils from functional.common import test @@ -47,6 +49,13 @@ def setUpClass(cls): if hasattr(super(IdentityTests, cls), 'setUpClass'): super(IdentityTests, cls).setUpClass() + # prepare v3 env + auth_url = os.environ.get('OS_AUTH_URL') + auth_url = auth_url.replace('v2.0', 'v3') + os.environ['OS_AUTH_URL'] = auth_url + os.environ['OS_IDENTITY_API_VERSION'] = '3' + os.environ['OS_AUTH_TYPE'] = 'v3password' + # create dummy domain cls.domain_name = data_utils.rand_name('TestDomain') cls.domain_description = data_utils.rand_name('description') From 258c1102cc6b93a860bcd7cc083d4e14ae0025ce Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Sun, 10 Jan 2016 21:54:53 +0900 Subject: [PATCH 0554/3095] log take_action parameters in a single place Previously each command logs take_action parameters explicitly by using @utils.log_method decorator or log.debug(). Some commands have no logging. This commit calls a logger in the base class and drops all logging definition from individual commands. Closes-Bug: #1532294 Change-Id: I43cd0290a4353c68c075bade9571c940733da1be --- openstackclient/common/availability_zone.py | 8 +--- openstackclient/common/command.py | 5 +- openstackclient/common/configuration.py | 10 +--- openstackclient/common/extension.py | 10 +--- openstackclient/common/limits.py | 9 +--- openstackclient/common/module.py | 14 ++---- openstackclient/common/quota.py | 13 +----- openstackclient/common/timing.py | 10 +--- openstackclient/compute/v2/agent.py | 24 ++-------- openstackclient/compute/v2/aggregate.py | 43 +++-------------- openstackclient/compute/v2/console.py | 13 +----- openstackclient/compute/v2/fixedip.py | 11 +---- openstackclient/compute/v2/flavor.py | 6 --- openstackclient/compute/v2/floatingip.py | 25 ++-------- openstackclient/compute/v2/floatingippool.py | 10 +--- openstackclient/compute/v2/host.py | 15 ++---- openstackclient/compute/v2/hypervisor.py | 15 ++---- .../compute/v2/hypervisor_stats.py | 8 +--- openstackclient/compute/v2/keypair.py | 24 ++-------- openstackclient/compute/v2/security_group.py | 46 +++---------------- openstackclient/compute/v2/server.py | 32 ------------- openstackclient/compute/v2/service.py | 17 +------ openstackclient/compute/v2/usage.py | 15 ++---- openstackclient/identity/v2_0/catalog.py | 15 ++---- openstackclient/identity/v2_0/ec2creds.py | 24 ++-------- openstackclient/identity/v2_0/endpoint.py | 24 ++-------- openstackclient/identity/v2_0/project.py | 29 ++---------- openstackclient/identity/v2_0/role.py | 36 +++------------ openstackclient/identity/v2_0/service.py | 24 ++-------- openstackclient/identity/v2_0/token.py | 14 +----- openstackclient/identity/v2_0/user.py | 26 ++--------- openstackclient/identity/v3/catalog.py | 15 ++---- openstackclient/identity/v3/consumer.py | 27 ++--------- openstackclient/identity/v3/credential.py | 27 ++--------- openstackclient/identity/v3/domain.py | 26 ++--------- openstackclient/identity/v3/ec2creds.py | 24 ++-------- openstackclient/identity/v3/endpoint.py | 27 ++--------- .../identity/v3/federation_protocol.py | 24 ++-------- openstackclient/identity/v3/group.py | 35 ++------------ .../identity/v3/identity_provider.py | 27 ++--------- openstackclient/identity/v3/mapping.py | 25 ++-------- openstackclient/identity/v3/policy.py | 27 ++--------- openstackclient/identity/v3/project.py | 26 ++--------- openstackclient/identity/v3/region.py | 27 ++--------- openstackclient/identity/v3/role.py | 32 ++----------- .../identity/v3/role_assignment.py | 11 +---- openstackclient/identity/v3/service.py | 27 ++--------- .../identity/v3/service_provider.py | 27 ++--------- openstackclient/identity/v3/token.py | 25 ++-------- openstackclient/identity/v3/trust.py | 24 ++-------- openstackclient/identity/v3/unscoped_saml.py | 15 ++---- openstackclient/identity/v3/user.py | 29 ++---------- openstackclient/image/v1/image.py | 34 ++------------ openstackclient/image/v2/image.py | 41 ++--------------- openstackclient/network/v2/network.py | 28 ++--------- openstackclient/network/v2/port.py | 5 -- openstackclient/network/v2/router.py | 27 ++--------- openstackclient/network/v2/subnet.py | 6 --- openstackclient/object/v1/account.py | 16 +------ openstackclient/object/v1/container.py | 34 ++------------ openstackclient/object/v1/object.py | 34 ++------------ openstackclient/volume/v1/backup.py | 27 ++--------- openstackclient/volume/v1/qos_specs.py | 36 ++------------- openstackclient/volume/v1/snapshot.py | 30 ++---------- openstackclient/volume/v1/volume.py | 30 ++---------- openstackclient/volume/v1/volume_type.py | 30 ++---------- openstackclient/volume/v2/backup.py | 28 ++--------- openstackclient/volume/v2/qos_specs.py | 36 ++------------- openstackclient/volume/v2/snapshot.py | 29 ++---------- openstackclient/volume/v2/volume.py | 30 ++---------- openstackclient/volume/v2/volume_type.py | 30 ++---------- 71 files changed, 242 insertions(+), 1391 deletions(-) diff --git a/openstackclient/common/availability_zone.py b/openstackclient/common/availability_zone.py index e72732e722..fa5aee4726 100644 --- a/openstackclient/common/availability_zone.py +++ b/openstackclient/common/availability_zone.py @@ -14,12 +14,11 @@ """Availability Zone action implementations""" import copy -import logging -from cliff import lister from novaclient import exceptions as nova_exceptions import six +from openstackclient.common import command from openstackclient.common import utils from openstackclient.i18n import _ # noqa @@ -70,11 +69,9 @@ def _xform_volume_availability_zone(az): return result -class ListAvailabilityZone(lister.Lister): +class ListAvailabilityZone(command.Lister): """List availability zones and their status""" - log = logging.getLogger(__name__ + '.ListAvailabilityZone') - def get_parser(self, prog_name): parser = super(ListAvailabilityZone, self).get_parser(prog_name) parser.add_argument( @@ -125,7 +122,6 @@ def get_volume_availability_zones(self, parsed_args): result += _xform_volume_availability_zone(zone) return result - @utils.log_method(log) def take_action(self, parsed_args): if parsed_args.long: diff --git a/openstackclient/common/command.py b/openstackclient/common/command.py index b8d9fc6f1b..13b0bcc2c4 100644 --- a/openstackclient/common/command.py +++ b/openstackclient/common/command.py @@ -31,7 +31,10 @@ def __new__(mcs, name, bases, cls_dict): @six.add_metaclass(CommandMeta) class Command(command.Command): - pass + + def run(self, parsed_args): + self.log.debug('run(%s)', parsed_args) + return super(Command, self).run(parsed_args) class Lister(Command, lister.Lister): diff --git a/openstackclient/common/configuration.py b/openstackclient/common/configuration.py index ac2792dd43..a70e4d1436 100644 --- a/openstackclient/common/configuration.py +++ b/openstackclient/common/configuration.py @@ -13,21 +13,16 @@ """Configuration action implementations""" -import logging - -from cliff import show import six -from openstackclient.common import utils +from openstackclient.common import command REDACTED = "" -class ShowConfiguration(show.ShowOne): +class ShowConfiguration(command.ShowOne): """Display configuration details""" - log = logging.getLogger(__name__ + '.ShowConfiguration') - def get_parser(self, prog_name): parser = super(ShowConfiguration, self).get_parser(prog_name) mask_group = parser.add_mutually_exclusive_group() @@ -46,7 +41,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): info = self.app.client_manager.get_configuration() diff --git a/openstackclient/common/extension.py b/openstackclient/common/extension.py index d1ae208de7..8b556b4cb2 100644 --- a/openstackclient/common/extension.py +++ b/openstackclient/common/extension.py @@ -16,18 +16,14 @@ """Extension action implementations""" import itertools -import logging - -from cliff import lister +from openstackclient.common import command from openstackclient.common import utils -class ListExtension(lister.Lister): +class ListExtension(command.Lister): """List API extensions""" - log = logging.getLogger(__name__ + '.ListExtension') - def get_parser(self, prog_name): parser = super(ListExtension, self).get_parser(prog_name) parser.add_argument( @@ -58,8 +54,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) - if parsed_args.long: columns = ('Name', 'Namespace', 'Description', 'Alias', 'Updated', 'Links') diff --git a/openstackclient/common/limits.py b/openstackclient/common/limits.py index 577036702d..bd546c01ee 100644 --- a/openstackclient/common/limits.py +++ b/openstackclient/common/limits.py @@ -16,19 +16,15 @@ """Limits Action Implementation""" import itertools -import logging - -from cliff import lister +from openstackclient.common import command from openstackclient.common import utils from openstackclient.identity import common as identity_common -class ShowLimits(lister.Lister): +class ShowLimits(command.Lister): """Show compute and block storage limits""" - log = logging.getLogger(__name__ + '.ShowLimits') - def get_parser(self, prog_name): parser = super(ShowLimits, self).get_parser(prog_name) type_group = parser.add_mutually_exclusive_group(required=True) @@ -64,7 +60,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): compute_client = self.app.client_manager.compute diff --git a/openstackclient/common/module.py b/openstackclient/common/module.py index f0ed23b2b1..a3dea5da50 100644 --- a/openstackclient/common/module.py +++ b/openstackclient/common/module.py @@ -15,23 +15,17 @@ """Module action implementation""" -import logging import six import sys -from cliff import lister -from cliff import show +from openstackclient.common import command -from openstackclient.common import utils - -class ListCommand(lister.Lister): +class ListCommand(command.Lister): """List recognized commands by group""" auth_required = False - log = logging.getLogger(__name__ + '.ListCommand') - @utils.log_method(log) def take_action(self, parsed_args): cm = self.app.command_manager groups = cm.get_command_groups() @@ -40,11 +34,10 @@ def take_action(self, parsed_args): return (columns, ((c, cm.get_command_names(group=c)) for c in groups)) -class ListModule(show.ShowOne): +class ListModule(command.ShowOne): """List module versions""" auth_required = False - log = logging.getLogger(__name__ + '.ListModule') def get_parser(self, prog_name): parser = super(ListModule, self).get_parser(prog_name) @@ -56,7 +49,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): data = {} diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index 8a9b910f2f..480abbd943 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -16,13 +16,10 @@ """Quota action implementations""" import itertools -import logging import six import sys -from cliff import command -from cliff import show - +from openstackclient.common import command from openstackclient.common import utils @@ -60,8 +57,6 @@ class SetQuota(command.Command): """Set quotas for project or class""" - log = logging.getLogger(__name__ + '.SetQuota') - def get_parser(self, prog_name): parser = super(SetQuota, self).get_parser(prog_name) parser.add_argument( @@ -92,7 +87,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity @@ -143,11 +137,9 @@ def take_action(self, parsed_args): **volume_kwargs) -class ShowQuota(show.ShowOne): +class ShowQuota(command.ShowOne): """Show quotas for project or class""" - log = logging.getLogger(__name__ + '.ShowQuota') - def get_parser(self, prog_name): parser = super(ShowQuota, self).get_parser(prog_name) parser.add_argument( @@ -203,7 +195,6 @@ def get_network_quota(self, parsed_args): else: return {} - @utils.log_method(log) def take_action(self, parsed_args): compute_client = self.app.client_manager.compute diff --git a/openstackclient/common/timing.py b/openstackclient/common/timing.py index d13c86e73c..5f62875947 100644 --- a/openstackclient/common/timing.py +++ b/openstackclient/common/timing.py @@ -13,19 +13,13 @@ """Timing Implementation""" -import logging +from openstackclient.common import command -from cliff import lister - -class Timing(lister.Lister): +class Timing(command.Lister): """Show timing data""" - log = logging.getLogger(__name__ + '.Timing') - def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) - column_headers = ( 'URL', 'Seconds', diff --git a/openstackclient/compute/v2/agent.py b/openstackclient/compute/v2/agent.py index 75c67f3115..59d7dc66e3 100644 --- a/openstackclient/compute/v2/agent.py +++ b/openstackclient/compute/v2/agent.py @@ -15,21 +15,15 @@ """Agent action implementations""" -import logging import six -from cliff import command -from cliff import lister -from cliff import show - +from openstackclient.common import command from openstackclient.common import utils -class CreateAgent(show.ShowOne): +class CreateAgent(command.ShowOne): """Create compute agent command""" - log = logging.getLogger(__name__ + ".CreateAgent") - def get_parser(self, prog_name): parser = super(CreateAgent, self).get_parser(prog_name) parser.add_argument( @@ -60,7 +54,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) compute_client = self.app.client_manager.compute args = ( parsed_args.os, @@ -77,8 +70,6 @@ def take_action(self, parsed_args): class DeleteAgent(command.Command): """Delete compute agent command""" - log = logging.getLogger(__name__ + ".DeleteAgent") - def get_parser(self, prog_name): parser = super(DeleteAgent, self).get_parser(prog_name) parser.add_argument( @@ -88,16 +79,13 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) compute_client = self.app.client_manager.compute compute_client.agents.delete(parsed_args.id) -class ListAgent(lister.Lister): +class ListAgent(command.Lister): """List compute agent command""" - log = logging.getLogger(__name__ + ".ListAgent") - def get_parser(self, prog_name): parser = super(ListAgent, self).get_parser(prog_name) parser.add_argument( @@ -107,7 +95,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) compute_client = self.app.client_manager.compute columns = ( "Agent ID", @@ -125,11 +112,9 @@ def take_action(self, parsed_args): ) for s in data)) -class SetAgent(show.ShowOne): +class SetAgent(command.ShowOne): """Set compute agent command""" - log = logging.getLogger(__name__ + ".SetAgent") - def get_parser(self, prog_name): parser = super(SetAgent, self).get_parser(prog_name) parser.add_argument( @@ -151,7 +136,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) compute_client = self.app.client_manager.compute args = ( parsed_args.id, diff --git a/openstackclient/compute/v2/aggregate.py b/openstackclient/compute/v2/aggregate.py index 97ad127cda..5f297257dd 100644 --- a/openstackclient/compute/v2/aggregate.py +++ b/openstackclient/compute/v2/aggregate.py @@ -16,22 +16,16 @@ """Compute v2 Aggregate action implementations""" -import logging import six -from cliff import command -from cliff import lister -from cliff import show - +from openstackclient.common import command from openstackclient.common import parseractions from openstackclient.common import utils -class AddAggregateHost(show.ShowOne): +class AddAggregateHost(command.ShowOne): """Add host to aggregate""" - log = logging.getLogger(__name__ + '.AddAggregateHost') - def get_parser(self, prog_name): parser = super(AddAggregateHost, self).get_parser(prog_name) parser.add_argument( @@ -47,8 +41,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) - compute_client = self.app.client_manager.compute aggregate = utils.find_resource( @@ -62,11 +54,9 @@ def take_action(self, parsed_args): return zip(*sorted(six.iteritems(info))) -class CreateAggregate(show.ShowOne): +class CreateAggregate(command.ShowOne): """Create a new aggregate""" - log = logging.getLogger(__name__ + ".CreateAggregate") - def get_parser(self, prog_name): parser = super(CreateAggregate, self).get_parser(prog_name) parser.add_argument( @@ -89,8 +79,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) - compute_client = self.app.client_manager.compute info = {} @@ -112,8 +100,6 @@ def take_action(self, parsed_args): class DeleteAggregate(command.Command): """Delete an existing aggregate""" - log = logging.getLogger(__name__ + '.DeleteAggregate') - def get_parser(self, prog_name): parser = super(DeleteAggregate, self).get_parser(prog_name) parser.add_argument( @@ -123,7 +109,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): compute_client = self.app.client_manager.compute @@ -134,11 +119,9 @@ def take_action(self, parsed_args): compute_client.aggregates.delete(data.id) -class ListAggregate(lister.Lister): +class ListAggregate(command.Lister): """List all aggregates""" - log = logging.getLogger(__name__ + ".ListAggregate") - def get_parser(self, prog_name): parser = super(ListAggregate, self).get_parser(prog_name) parser.add_argument( @@ -149,8 +132,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) - compute_client = self.app.client_manager.compute data = compute_client.aggregates.list() @@ -186,11 +167,9 @@ def take_action(self, parsed_args): ) for s in data)) -class RemoveAggregateHost(show.ShowOne): +class RemoveAggregateHost(command.ShowOne): """Remove host from aggregate""" - log = logging.getLogger(__name__ + '.RemoveAggregateHost') - def get_parser(self, prog_name): parser = super(RemoveAggregateHost, self).get_parser(prog_name) parser.add_argument( @@ -206,8 +185,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) - compute_client = self.app.client_manager.compute aggregate = utils.find_resource( @@ -224,11 +201,9 @@ def take_action(self, parsed_args): return zip(*sorted(six.iteritems(info))) -class SetAggregate(show.ShowOne): +class SetAggregate(command.ShowOne): """Set aggregate properties""" - log = logging.getLogger(__name__ + '.SetAggregate') - def get_parser(self, prog_name): parser = super(SetAggregate, self).get_parser(prog_name) parser.add_argument( @@ -255,7 +230,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): compute_client = self.app.client_manager.compute @@ -288,11 +262,9 @@ def take_action(self, parsed_args): return ({}, {}) -class ShowAggregate(show.ShowOne): +class ShowAggregate(command.ShowOne): """Display aggregate details""" - log = logging.getLogger(__name__ + '.ShowAggregate') - def get_parser(self, prog_name): parser = super(ShowAggregate, self).get_parser(prog_name) parser.add_argument( @@ -302,7 +274,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): compute_client = self.app.client_manager.compute diff --git a/openstackclient/compute/v2/console.py b/openstackclient/compute/v2/console.py index f086478622..6c0ec31a56 100644 --- a/openstackclient/compute/v2/console.py +++ b/openstackclient/compute/v2/console.py @@ -15,13 +15,10 @@ """Compute v2 Console action implementations""" -import logging import six import sys -from cliff import command -from cliff import show - +from openstackclient.common import command from openstackclient.common import parseractions from openstackclient.common import utils @@ -29,8 +26,6 @@ class ShowConsoleLog(command.Command): """Show server's console output""" - log = logging.getLogger(__name__ + '.ShowConsoleLog') - def get_parser(self, prog_name): parser = super(ShowConsoleLog, self).get_parser(prog_name) parser.add_argument( @@ -49,7 +44,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): compute_client = self.app.client_manager.compute @@ -67,11 +61,9 @@ def take_action(self, parsed_args): sys.stdout.write(data) -class ShowConsoleURL(show.ShowOne): +class ShowConsoleURL(command.ShowOne): """Show server's remote console URL""" - log = logging.getLogger(__name__ + '.ShowConsoleURL') - def get_parser(self, prog_name): parser = super(ShowConsoleURL, self).get_parser(prog_name) parser.add_argument( @@ -104,7 +96,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): compute_client = self.app.client_manager.compute server = utils.find_resource( diff --git a/openstackclient/compute/v2/fixedip.py b/openstackclient/compute/v2/fixedip.py index 1ab8a18c49..daac97d1be 100644 --- a/openstackclient/compute/v2/fixedip.py +++ b/openstackclient/compute/v2/fixedip.py @@ -15,18 +15,13 @@ """Fixed IP action implementations""" -import logging - -from cliff import command - +from openstackclient.common import command from openstackclient.common import utils class AddFixedIP(command.Command): """Add fixed IP address to server""" - log = logging.getLogger(__name__ + ".AddFixedIP") - def get_parser(self, prog_name): parser = super(AddFixedIP, self).get_parser(prog_name) parser.add_argument( @@ -42,7 +37,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) compute_client = self.app.client_manager.compute network = utils.find_resource( @@ -57,8 +51,6 @@ def take_action(self, parsed_args): class RemoveFixedIP(command.Command): """Remove fixed IP address from server""" - log = logging.getLogger(__name__ + ".RemoveFixedIP") - def get_parser(self, prog_name): parser = super(RemoveFixedIP, self).get_parser(prog_name) parser.add_argument( @@ -74,7 +66,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) compute_client = self.app.client_manager.compute server = utils.find_resource( diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 1af5fe7002..093592cd82 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -98,7 +98,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) compute_client = self.app.client_manager.compute args = ( @@ -132,7 +131,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) compute_client = self.app.client_manager.compute flavor = utils.find_resource(compute_client.flavors, parsed_args.flavor) @@ -182,7 +180,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) compute_client = self.app.client_manager.compute columns = ( "ID", @@ -234,7 +231,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) compute_client = self.app.client_manager.compute resource_flavor = utils.find_resource(compute_client.flavors, parsed_args.flavor) @@ -266,7 +262,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) compute_client = self.app.client_manager.compute resource_flavor = compute_client.flavors.find(name=parsed_args.flavor) @@ -299,7 +294,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) compute_client = self.app.client_manager.compute resource_flavor = compute_client.flavors.find(name=parsed_args.flavor) diff --git a/openstackclient/compute/v2/floatingip.py b/openstackclient/compute/v2/floatingip.py index 6f2e360cf8..29ecbc9057 100644 --- a/openstackclient/compute/v2/floatingip.py +++ b/openstackclient/compute/v2/floatingip.py @@ -15,21 +15,15 @@ """Floating IP action implementations""" -import logging import six -from cliff import command -from cliff import lister -from cliff import show - +from openstackclient.common import command from openstackclient.common import utils class AddFloatingIP(command.Command): """Add floating IP address to server""" - log = logging.getLogger(__name__ + ".AddFloatingIP") - def get_parser(self, prog_name): parser = super(AddFloatingIP, self).get_parser(prog_name) parser.add_argument( @@ -45,7 +39,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) compute_client = self.app.client_manager.compute server = utils.find_resource( @@ -54,11 +47,9 @@ def take_action(self, parsed_args): server.add_floating_ip(parsed_args.ip_address) -class CreateFloatingIP(show.ShowOne): +class CreateFloatingIP(command.ShowOne): """Create new floating IP address""" - log = logging.getLogger(__name__ + '.CreateFloatingIP') - def get_parser(self, prog_name): parser = super(CreateFloatingIP, self).get_parser(prog_name) parser.add_argument( @@ -68,7 +59,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): compute_client = self.app.client_manager.compute floating_ip = compute_client.floating_ips.create(parsed_args.pool) @@ -81,8 +71,6 @@ def take_action(self, parsed_args): class DeleteFloatingIP(command.Command): """Delete a floating IP address""" - log = logging.getLogger(__name__ + '.DeleteFloatingIP') - def get_parser(self, prog_name): parser = super(DeleteFloatingIP, self).get_parser(prog_name) parser.add_argument( @@ -92,7 +80,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): compute_client = self.app.client_manager.compute @@ -104,12 +91,9 @@ def take_action(self, parsed_args): compute_client.floating_ips.delete(floating_ip) -class ListFloatingIP(lister.Lister): +class ListFloatingIP(command.Lister): """List floating IP addresses""" - log = logging.getLogger(__name__ + '.ListFloatingIP') - - @utils.log_method(log) def take_action(self, parsed_args): compute_client = self.app.client_manager.compute @@ -127,8 +111,6 @@ def take_action(self, parsed_args): class RemoveFloatingIP(command.Command): """Remove floating IP address from server""" - log = logging.getLogger(__name__ + ".RemoveFloatingIP") - def get_parser(self, prog_name): parser = super(RemoveFloatingIP, self).get_parser(prog_name) parser.add_argument( @@ -144,7 +126,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) compute_client = self.app.client_manager.compute server = utils.find_resource( diff --git a/openstackclient/compute/v2/floatingippool.py b/openstackclient/compute/v2/floatingippool.py index 39a2d8fed4..997e03247a 100644 --- a/openstackclient/compute/v2/floatingippool.py +++ b/openstackclient/compute/v2/floatingippool.py @@ -15,19 +15,13 @@ """Floating IP Pool action implementations""" -import logging - -from cliff import lister - +from openstackclient.common import command from openstackclient.common import utils -class ListFloatingIPPool(lister.Lister): +class ListFloatingIPPool(command.Lister): """List pools of floating IP addresses""" - log = logging.getLogger(__name__ + '.ListFloatingIPPool') - - @utils.log_method(log) def take_action(self, parsed_args): compute_client = self.app.client_manager.compute diff --git a/openstackclient/compute/v2/host.py b/openstackclient/compute/v2/host.py index 4f7273883f..f2257d1219 100644 --- a/openstackclient/compute/v2/host.py +++ b/openstackclient/compute/v2/host.py @@ -15,18 +15,13 @@ """Host action implementations""" -import logging - -from cliff import lister - +from openstackclient.common import command from openstackclient.common import utils -class ListHost(lister.Lister): +class ListHost(command.Lister): """List host command""" - log = logging.getLogger(__name__ + ".ListHost") - def get_parser(self, prog_name): parser = super(ListHost, self).get_parser(prog_name) parser.add_argument( @@ -36,7 +31,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) compute_client = self.app.client_manager.compute columns = ( "Host Name", @@ -50,11 +44,9 @@ def take_action(self, parsed_args): ) for s in data)) -class ShowHost(lister.Lister): +class ShowHost(command.Lister): """Show host command""" - log = logging.getLogger(__name__ + ".ShowHost") - def get_parser(self, prog_name): parser = super(ShowHost, self).get_parser(prog_name) parser.add_argument( @@ -64,7 +56,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) compute_client = self.app.client_manager.compute columns = ( "Host", diff --git a/openstackclient/compute/v2/hypervisor.py b/openstackclient/compute/v2/hypervisor.py index f33beb0892..f5288a35a5 100644 --- a/openstackclient/compute/v2/hypervisor.py +++ b/openstackclient/compute/v2/hypervisor.py @@ -15,21 +15,16 @@ """Hypervisor action implementations""" -import logging import re import six -from cliff import lister -from cliff import show - +from openstackclient.common import command from openstackclient.common import utils -class ListHypervisor(lister.Lister): +class ListHypervisor(command.Lister): """List hypervisors""" - log = logging.getLogger(__name__ + ".ListHypervisor") - def get_parser(self, prog_name): parser = super(ListHypervisor, self).get_parser(prog_name) parser.add_argument( @@ -40,7 +35,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) compute_client = self.app.client_manager.compute columns = ( "ID", @@ -58,11 +52,9 @@ def take_action(self, parsed_args): ) for s in data)) -class ShowHypervisor(show.ShowOne): +class ShowHypervisor(command.ShowOne): """Display hypervisor details""" - log = logging.getLogger(__name__ + ".ShowHypervisor") - def get_parser(self, prog_name): parser = super(ShowHypervisor, self).get_parser(prog_name) parser.add_argument( @@ -72,7 +64,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) compute_client = self.app.client_manager.compute hypervisor = utils.find_resource(compute_client.hypervisors, parsed_args.hypervisor)._info.copy() diff --git a/openstackclient/compute/v2/hypervisor_stats.py b/openstackclient/compute/v2/hypervisor_stats.py index 43ba9fc8ed..290f54185b 100644 --- a/openstackclient/compute/v2/hypervisor_stats.py +++ b/openstackclient/compute/v2/hypervisor_stats.py @@ -14,19 +14,15 @@ """Hypervisor Stats action implementations""" -import logging import six -from cliff import show +from openstackclient.common import command -class ShowHypervisorStats(show.ShowOne): +class ShowHypervisorStats(command.ShowOne): """Display hypervisor stats details""" - log = logging.getLogger(__name__ + ".ShowHypervisorStats") - def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) compute_client = self.app.client_manager.compute hypervisor_stats = compute_client.hypervisors.statistics().to_dict() diff --git a/openstackclient/compute/v2/keypair.py b/openstackclient/compute/v2/keypair.py index 965629858a..71c9d6747b 100644 --- a/openstackclient/compute/v2/keypair.py +++ b/openstackclient/compute/v2/keypair.py @@ -15,24 +15,18 @@ """Keypair action implementations""" -import logging import os import six import sys -from cliff import command -from cliff import lister -from cliff import show - +from openstackclient.common import command from openstackclient.common import exceptions from openstackclient.common import utils -class CreateKeypair(show.ShowOne): +class CreateKeypair(command.ShowOne): """Create new public key""" - log = logging.getLogger(__name__ + '.CreateKeypair') - def get_parser(self, prog_name): parser = super(CreateKeypair, self).get_parser(prog_name) parser.add_argument( @@ -47,7 +41,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): compute_client = self.app.client_manager.compute @@ -82,8 +75,6 @@ def take_action(self, parsed_args): class DeleteKeypair(command.Command): """Delete public key""" - log = logging.getLogger(__name__ + '.DeleteKeypair') - def get_parser(self, prog_name): parser = super(DeleteKeypair, self).get_parser(prog_name) parser.add_argument( @@ -93,19 +84,15 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): compute_client = self.app.client_manager.compute compute_client.keypairs.delete(parsed_args.name) -class ListKeypair(lister.Lister): +class ListKeypair(command.Lister): """List public key fingerprints""" - log = logging.getLogger(__name__ + ".ListKeypair") - def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) compute_client = self.app.client_manager.compute columns = ( "Name", @@ -119,11 +106,9 @@ def take_action(self, parsed_args): ) for s in data)) -class ShowKeypair(show.ShowOne): +class ShowKeypair(command.ShowOne): """Display public key details""" - log = logging.getLogger(__name__ + '.ShowKeypair') - def get_parser(self, prog_name): parser = super(ShowKeypair, self).get_parser(prog_name) parser.add_argument( @@ -139,7 +124,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): compute_client = self.app.client_manager.compute keypair = utils.find_resource(compute_client.keypairs, diff --git a/openstackclient/compute/v2/security_group.py b/openstackclient/compute/v2/security_group.py index 6395e102b9..b975a50533 100644 --- a/openstackclient/compute/v2/security_group.py +++ b/openstackclient/compute/v2/security_group.py @@ -16,13 +16,8 @@ """Compute v2 Security Group action implementations""" -import logging import six -from cliff import command -from cliff import lister -from cliff import show - from keystoneauth1 import exceptions as ks_exc try: @@ -30,6 +25,7 @@ except ImportError: from novaclient.v1_1 import security_group_rules +from openstackclient.common import command from openstackclient.common import parseractions from openstackclient.common import utils @@ -79,11 +75,9 @@ def _xform_and_trim_security_group_rule(sgroup): return info -class CreateSecurityGroup(show.ShowOne): +class CreateSecurityGroup(command.ShowOne): """Create a new security group""" - log = logging.getLogger(__name__ + ".CreateSecurityGroup") - def get_parser(self, prog_name): parser = super(CreateSecurityGroup, self).get_parser(prog_name) parser.add_argument( @@ -99,8 +93,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) - compute_client = self.app.client_manager.compute description = parsed_args.description or parsed_args.name @@ -115,11 +107,9 @@ def take_action(self, parsed_args): return zip(*sorted(six.iteritems(info))) -class CreateSecurityGroupRule(show.ShowOne): +class CreateSecurityGroupRule(command.ShowOne): """Create a new security group rule""" - log = logging.getLogger(__name__ + ".CreateSecurityGroupRule") - def get_parser(self, prog_name): parser = super(CreateSecurityGroupRule, self).get_parser(prog_name) parser.add_argument( @@ -157,8 +147,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) - compute_client = self.app.client_manager.compute group = utils.find_resource( compute_client.security_groups, @@ -184,8 +172,6 @@ def take_action(self, parsed_args): class DeleteSecurityGroup(command.Command): """Delete a security group""" - log = logging.getLogger(__name__ + '.DeleteSecurityGroup') - def get_parser(self, prog_name): parser = super(DeleteSecurityGroup, self).get_parser(prog_name) parser.add_argument( @@ -195,7 +181,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): compute_client = self.app.client_manager.compute @@ -209,8 +194,6 @@ def take_action(self, parsed_args): class DeleteSecurityGroupRule(command.Command): """Delete a security group rule""" - log = logging.getLogger(__name__ + '.DeleteSecurityGroupRule') - def get_parser(self, prog_name): parser = super(DeleteSecurityGroupRule, self).get_parser(prog_name) parser.add_argument( @@ -220,18 +203,15 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): compute_client = self.app.client_manager.compute compute_client.security_group_rules.delete(parsed_args.rule) -class ListSecurityGroup(lister.Lister): +class ListSecurityGroup(command.Lister): """List security groups""" - log = logging.getLogger(__name__ + ".ListSecurityGroup") - def get_parser(self, prog_name): parser = super(ListSecurityGroup, self).get_parser(prog_name) parser.add_argument( @@ -250,8 +230,6 @@ def _get_project(project_id): except KeyError: return project_id - self.log.debug("take_action(%s)", parsed_args) - compute_client = self.app.client_manager.compute columns = ( "ID", @@ -283,11 +261,9 @@ def _get_project(project_id): ) for s in data)) -class ListSecurityGroupRule(lister.Lister): +class ListSecurityGroupRule(command.Lister): """List security group rules""" - log = logging.getLogger(__name__ + ".ListSecurityGroupRule") - def get_parser(self, prog_name): parser = super(ListSecurityGroupRule, self).get_parser(prog_name) parser.add_argument( @@ -299,8 +275,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) - compute_client = self.app.client_manager.compute columns = column_headers = ( "ID", @@ -337,11 +311,9 @@ def take_action(self, parsed_args): ) for s in rules)) -class SetSecurityGroup(show.ShowOne): +class SetSecurityGroup(command.ShowOne): """Set security group properties""" - log = logging.getLogger(__name__ + '.SetSecurityGroup') - def get_parser(self, prog_name): parser = super(SetSecurityGroup, self).get_parser(prog_name) parser.add_argument( @@ -361,7 +333,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): compute_client = self.app.client_manager.compute @@ -388,11 +359,9 @@ def take_action(self, parsed_args): return ({}, {}) -class ShowSecurityGroup(show.ShowOne): +class ShowSecurityGroup(command.ShowOne): """Display security group details""" - log = logging.getLogger(__name__ + '.ShowSecurityGroup') - def get_parser(self, prog_name): parser = super(ShowSecurityGroup, self).get_parser(prog_name) parser.add_argument( @@ -402,7 +371,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): compute_client = self.app.client_manager.compute diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index b4db621600..dd7bc470ba 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -185,8 +185,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) - compute_client = self.app.client_manager.compute server = utils.find_resource( @@ -224,8 +222,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) - compute_client = self.app.client_manager.compute volume_client = self.app.client_manager.volume @@ -370,7 +366,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute volume_client = self.app.client_manager.volume @@ -569,7 +564,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute image_client = self.app.client_manager.image @@ -626,7 +620,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute for server in parsed_args.server: @@ -741,7 +734,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute identity_client = self.app.client_manager.identity @@ -872,7 +864,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute @@ -944,7 +935,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute @@ -987,7 +977,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute for server in parsed_args.server: @@ -1031,7 +1020,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute server = utils.find_resource( @@ -1078,7 +1066,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute @@ -1123,8 +1110,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) - compute_client = self.app.client_manager.compute server = utils.find_resource( @@ -1157,8 +1142,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) - compute_client = self.app.client_manager.compute volume_client = self.app.client_manager.volume @@ -1189,7 +1172,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute @@ -1233,7 +1215,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute @@ -1277,7 +1258,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute @@ -1317,7 +1297,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute @@ -1358,7 +1337,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute for server in parsed_args.server: @@ -1386,7 +1364,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute server = utils.find_resource(compute_client.servers, @@ -1507,7 +1484,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute @@ -1561,7 +1537,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute for server in parsed_args.server: @@ -1584,7 +1559,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute for server in parsed_args.server: @@ -1607,7 +1581,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute @@ -1631,7 +1604,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute @@ -1655,7 +1627,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute @@ -1678,7 +1649,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute @@ -1708,7 +1678,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute server = utils.find_resource( @@ -1736,7 +1705,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method() def take_action(self, parsed_args): compute_client = self.app.client_manager.compute for server in parsed_args.server: diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index af3e940a36..3c06272490 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.py @@ -15,19 +15,13 @@ """Service action implementations""" -import logging - -from cliff import command -from cliff import lister - +from openstackclient.common import command from openstackclient.common import utils class DeleteService(command.Command): """Delete service command""" - log = logging.getLogger(__name__ + ".DeleteService") - def get_parser(self, prog_name): parser = super(DeleteService, self).get_parser(prog_name) parser.add_argument( @@ -37,17 +31,14 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) compute_client = self.app.client_manager.compute compute_client.services.delete(parsed_args.service) -class ListService(lister.Lister): +class ListService(command.Lister): """List service command""" - log = logging.getLogger(__name__ + ".ListService") - def get_parser(self, prog_name): parser = super(ListService, self).get_parser(prog_name) parser.add_argument( @@ -61,7 +52,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) compute_client = self.app.client_manager.compute columns = ( "Id", @@ -83,8 +73,6 @@ def take_action(self, parsed_args): class SetService(command.Command): """Set service command""" - log = logging.getLogger(__name__ + ".SetService") - def get_parser(self, prog_name): parser = super(SetService, self).get_parser(prog_name) parser.add_argument( @@ -110,7 +98,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) compute_client = self.app.client_manager.compute if parsed_args.enabled: diff --git a/openstackclient/compute/v2/usage.py b/openstackclient/compute/v2/usage.py index 4e7cf10064..baa501706a 100644 --- a/openstackclient/compute/v2/usage.py +++ b/openstackclient/compute/v2/usage.py @@ -16,21 +16,17 @@ """Usage action implementations""" import datetime -import logging import sys -from cliff import lister -from cliff import show import six +from openstackclient.common import command from openstackclient.common import utils -class ListUsage(lister.Lister): +class ListUsage(command.Lister): """List resource usage per project""" - log = logging.getLogger(__name__ + ".ListUsage") - def get_parser(self, prog_name): parser = super(ListUsage, self).get_parser(prog_name) parser.add_argument( @@ -49,7 +45,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) def _format_project(project): if not project: @@ -118,11 +113,9 @@ def _format_project(project): ) for s in usage_list)) -class ShowUsage(show.ShowOne): +class ShowUsage(command.ShowOne): """Show resource usage for a single project""" - log = logging.getLogger(__name__ + ".ShowUsage") - def get_parser(self, prog_name): parser = super(ShowUsage, self).get_parser(prog_name) parser.add_argument( @@ -147,8 +140,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) - identity_client = self.app.client_manager.identity compute_client = self.app.client_manager.compute dateformat = "%Y-%m-%d" diff --git a/openstackclient/identity/v2_0/catalog.py b/openstackclient/identity/v2_0/catalog.py index 9bc2755aa2..c927943f85 100644 --- a/openstackclient/identity/v2_0/catalog.py +++ b/openstackclient/identity/v2_0/catalog.py @@ -13,12 +13,9 @@ """Identity v2 Service Catalog action implementations""" -import logging import six -from cliff import lister -from cliff import show - +from openstackclient.common import command from openstackclient.common import utils from openstackclient.i18n import _ # noqa @@ -37,12 +34,9 @@ def _format_endpoints(eps=None): return ret -class ListCatalog(lister.Lister): +class ListCatalog(command.Lister): """List services in the service catalog""" - log = logging.getLogger(__name__ + '.ListCatalog') - - @utils.log_method(log) def take_action(self, parsed_args): # This is ugly because if auth hasn't happened yet we need @@ -62,11 +56,9 @@ def take_action(self, parsed_args): ) for s in data)) -class ShowCatalog(show.ShowOne): +class ShowCatalog(command.ShowOne): """Display service catalog details""" - log = logging.getLogger(__name__ + '.ShowCatalog') - def get_parser(self, prog_name): parser = super(ShowCatalog, self).get_parser(prog_name) parser.add_argument( @@ -76,7 +68,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): # This is ugly because if auth hasn't happened yet we need diff --git a/openstackclient/identity/v2_0/ec2creds.py b/openstackclient/identity/v2_0/ec2creds.py index a7730ce252..a16b3d9eff 100644 --- a/openstackclient/identity/v2_0/ec2creds.py +++ b/openstackclient/identity/v2_0/ec2creds.py @@ -16,22 +16,16 @@ """Identity v2 EC2 Credentials action implementations""" -import logging import six -from cliff import command -from cliff import lister -from cliff import show - +from openstackclient.common import command from openstackclient.common import utils from openstackclient.i18n import _ # noqa -class CreateEC2Creds(show.ShowOne): +class CreateEC2Creds(command.ShowOne): """Create EC2 credentials""" - log = logging.getLogger(__name__ + ".CreateEC2Creds") - def get_parser(self, prog_name): parser = super(CreateEC2Creds, self).get_parser(prog_name) parser.add_argument( @@ -52,7 +46,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity @@ -89,8 +82,6 @@ def take_action(self, parsed_args): class DeleteEC2Creds(command.Command): """Delete EC2 credentials""" - log = logging.getLogger(__name__ + '.DeleteEC2Creds') - def get_parser(self, prog_name): parser = super(DeleteEC2Creds, self).get_parser(prog_name) parser.add_argument( @@ -105,7 +96,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity @@ -121,11 +111,9 @@ def take_action(self, parsed_args): identity_client.ec2.delete(user, parsed_args.access_key) -class ListEC2Creds(lister.Lister): +class ListEC2Creds(command.Lister): """List EC2 credentials""" - log = logging.getLogger(__name__ + '.ListEC2Creds') - def get_parser(self, prog_name): parser = super(ListEC2Creds, self).get_parser(prog_name) parser.add_argument( @@ -135,7 +123,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity @@ -159,11 +146,9 @@ def take_action(self, parsed_args): ) for s in data)) -class ShowEC2Creds(show.ShowOne): +class ShowEC2Creds(command.ShowOne): """Display EC2 credentials details""" - log = logging.getLogger(__name__ + '.ShowEC2Creds') - def get_parser(self, prog_name): parser = super(ShowEC2Creds, self).get_parser(prog_name) parser.add_argument( @@ -178,7 +163,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity diff --git a/openstackclient/identity/v2_0/endpoint.py b/openstackclient/identity/v2_0/endpoint.py index 1744cc72ac..08c09565dd 100644 --- a/openstackclient/identity/v2_0/endpoint.py +++ b/openstackclient/identity/v2_0/endpoint.py @@ -15,23 +15,17 @@ """Endpoint action implementations""" -import logging import six -from cliff import command -from cliff import lister -from cliff import show - +from openstackclient.common import command from openstackclient.common import utils from openstackclient.i18n import _ # noqa from openstackclient.identity import common -class CreateEndpoint(show.ShowOne): +class CreateEndpoint(command.ShowOne): """Create new endpoint""" - log = logging.getLogger(__name__ + '.CreateEndpoint') - def get_parser(self, prog_name): parser = super(CreateEndpoint, self).get_parser(prog_name) parser.add_argument( @@ -62,7 +56,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity service = common.find_service(identity_client, parsed_args.service) @@ -83,8 +76,6 @@ def take_action(self, parsed_args): class DeleteEndpoint(command.Command): """Delete endpoint""" - log = logging.getLogger(__name__ + '.DeleteEndpoint') - def get_parser(self, prog_name): parser = super(DeleteEndpoint, self).get_parser(prog_name) parser.add_argument( @@ -93,18 +84,15 @@ def get_parser(self, prog_name): help=_('Endpoint ID to delete')) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity identity_client.endpoints.delete(parsed_args.endpoint) return -class ListEndpoint(lister.Lister): +class ListEndpoint(command.Lister): """List endpoints""" - log = logging.getLogger(__name__ + '.ListEndpoint') - def get_parser(self, prog_name): parser = super(ListEndpoint, self).get_parser(prog_name) parser.add_argument( @@ -115,7 +103,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity if parsed_args.long: @@ -136,11 +123,9 @@ def take_action(self, parsed_args): ) for s in data)) -class ShowEndpoint(show.ShowOne): +class ShowEndpoint(command.ShowOne): """Display endpoint details""" - log = logging.getLogger(__name__ + '.ShowEndpoint') - def get_parser(self, prog_name): parser = super(ShowEndpoint, self).get_parser(prog_name) parser.add_argument( @@ -150,7 +135,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity data = identity_client.endpoints.list() diff --git a/openstackclient/identity/v2_0/project.py b/openstackclient/identity/v2_0/project.py index 4330c79ca0..9e26c308b4 100644 --- a/openstackclient/identity/v2_0/project.py +++ b/openstackclient/identity/v2_0/project.py @@ -15,24 +15,19 @@ """Identity v2 Project action implementations""" -import logging import six -from cliff import command -from cliff import lister -from cliff import show from keystoneauth1 import exceptions as ks_exc +from openstackclient.common import command from openstackclient.common import parseractions from openstackclient.common import utils from openstackclient.i18n import _ # noqa -class CreateProject(show.ShowOne): +class CreateProject(command.ShowOne): """Create new project""" - log = logging.getLogger(__name__ + '.CreateProject') - def get_parser(self, prog_name): parser = super(CreateProject, self).get_parser(prog_name) parser.add_argument( @@ -70,7 +65,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity @@ -106,8 +100,6 @@ def take_action(self, parsed_args): class DeleteProject(command.Command): """Delete project(s)""" - log = logging.getLogger(__name__ + '.DeleteProject') - def get_parser(self, prog_name): parser = super(DeleteProject, self).get_parser(prog_name) parser.add_argument( @@ -118,7 +110,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity @@ -131,11 +122,9 @@ def take_action(self, parsed_args): return -class ListProject(lister.Lister): +class ListProject(command.Lister): """List projects""" - log = logging.getLogger(__name__ + '.ListProject') - def get_parser(self, prog_name): parser = super(ListProject, self).get_parser(prog_name) parser.add_argument( @@ -146,7 +135,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): if parsed_args.long: columns = ('ID', 'Name', 'Description', 'Enabled') @@ -163,8 +151,6 @@ def take_action(self, parsed_args): class SetProject(command.Command): """Set project properties""" - log = logging.getLogger(__name__ + '.SetProject') - def get_parser(self, prog_name): parser = super(SetProject, self).get_parser(prog_name) parser.add_argument( @@ -202,7 +188,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity @@ -240,11 +225,9 @@ def take_action(self, parsed_args): return -class ShowProject(show.ShowOne): +class ShowProject(command.ShowOne): """Display project details""" - log = logging.getLogger(__name__ + '.ShowProject') - def get_parser(self, prog_name): parser = super(ShowProject, self).get_parser(prog_name) parser.add_argument( @@ -253,7 +236,6 @@ def get_parser(self, prog_name): help=_('Project to display (name or ID)')) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity @@ -303,8 +285,6 @@ def take_action(self, parsed_args): class UnsetProject(command.Command): """Unset project properties""" - log = logging.getLogger(__name__ + '.UnsetProject') - def get_parser(self, prog_name): parser = super(UnsetProject, self).get_parser(prog_name) parser.add_argument( @@ -323,7 +303,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity project = utils.find_resource( diff --git a/openstackclient/identity/v2_0/role.py b/openstackclient/identity/v2_0/role.py index cab6b4a540..892ce006e5 100644 --- a/openstackclient/identity/v2_0/role.py +++ b/openstackclient/identity/v2_0/role.py @@ -15,24 +15,19 @@ """Identity v2 Role action implementations""" -import logging import six -from cliff import command -from cliff import lister -from cliff import show from keystoneauth1 import exceptions as ks_exc +from openstackclient.common import command from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.i18n import _ # noqa -class AddRole(show.ShowOne): +class AddRole(command.ShowOne): """Add role to project:user""" - log = logging.getLogger(__name__ + '.AddRole') - def get_parser(self, prog_name): parser = super(AddRole, self).get_parser(prog_name) parser.add_argument( @@ -54,7 +49,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity role = utils.find_resource(identity_client.roles, parsed_args.role) @@ -74,11 +68,9 @@ def take_action(self, parsed_args): return zip(*sorted(six.iteritems(info))) -class CreateRole(show.ShowOne): +class CreateRole(command.ShowOne): """Create new role""" - log = logging.getLogger(__name__ + '.CreateRole') - def get_parser(self, prog_name): parser = super(CreateRole, self).get_parser(prog_name) parser.add_argument( @@ -93,7 +85,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity try: @@ -116,8 +107,6 @@ def take_action(self, parsed_args): class DeleteRole(command.Command): """Delete role(s)""" - log = logging.getLogger(__name__ + '.DeleteRole') - def get_parser(self, prog_name): parser = super(DeleteRole, self).get_parser(prog_name) parser.add_argument( @@ -128,7 +117,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity @@ -141,11 +129,9 @@ def take_action(self, parsed_args): return -class ListRole(lister.Lister): +class ListRole(command.Lister): """List roles""" - log = logging.getLogger(__name__ + '.ListRole') - def get_parser(self, prog_name): parser = super(ListRole, self).get_parser(prog_name) parser.add_argument( @@ -160,7 +146,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity auth_ref = self.app.client_manager.auth_ref @@ -222,11 +207,9 @@ def take_action(self, parsed_args): ) for s in data)) -class ListUserRole(lister.Lister): +class ListUserRole(command.Lister): """List user-role assignments""" - log = logging.getLogger(__name__ + '.ListUserRole') - def get_parser(self, prog_name): parser = super(ListUserRole, self).get_parser(prog_name) parser.add_argument( @@ -242,7 +225,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity auth_ref = self.app.client_manager.auth_ref @@ -293,8 +275,6 @@ def take_action(self, parsed_args): class RemoveRole(command.Command): """Remove role from project : user""" - log = logging.getLogger(__name__ + '.RemoveRole') - def get_parser(self, prog_name): parser = super(RemoveRole, self).get_parser(prog_name) parser.add_argument( @@ -316,7 +296,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity role = utils.find_resource(identity_client.roles, parsed_args.role) @@ -331,11 +310,9 @@ def take_action(self, parsed_args): project.id) -class ShowRole(show.ShowOne): +class ShowRole(command.ShowOne): """Display role details""" - log = logging.getLogger(__name__ + '.ShowRole') - def get_parser(self, prog_name): parser = super(ShowRole, self).get_parser(prog_name) parser.add_argument( @@ -345,7 +322,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity role = utils.find_resource(identity_client.roles, parsed_args.role) diff --git a/openstackclient/identity/v2_0/service.py b/openstackclient/identity/v2_0/service.py index c9d4844126..3af85d7db0 100644 --- a/openstackclient/identity/v2_0/service.py +++ b/openstackclient/identity/v2_0/service.py @@ -16,24 +16,18 @@ """Service action implementations""" import argparse -import logging import six -from cliff import command -from cliff import lister -from cliff import show - +from openstackclient.common import command from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.i18n import _ # noqa from openstackclient.identity import common -class CreateService(show.ShowOne): +class CreateService(command.ShowOne): """Create new service""" - log = logging.getLogger(__name__ + '.CreateService') - def get_parser(self, prog_name): parser = super(CreateService, self).get_parser(prog_name) parser.add_argument( @@ -59,7 +53,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity @@ -95,8 +88,6 @@ def take_action(self, parsed_args): class DeleteService(command.Command): """Delete service""" - log = logging.getLogger(__name__ + '.DeleteService') - def get_parser(self, prog_name): parser = super(DeleteService, self).get_parser(prog_name) parser.add_argument( @@ -106,7 +97,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity service = common.find_service(identity_client, parsed_args.service) @@ -114,11 +104,9 @@ def take_action(self, parsed_args): return -class ListService(lister.Lister): +class ListService(command.Lister): """List services""" - log = logging.getLogger(__name__ + '.ListService') - def get_parser(self, prog_name): parser = super(ListService, self).get_parser(prog_name) parser.add_argument( @@ -129,7 +117,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): if parsed_args.long: @@ -143,11 +130,9 @@ def take_action(self, parsed_args): ) -class ShowService(show.ShowOne): +class ShowService(command.ShowOne): """Display service details""" - log = logging.getLogger(__name__ + '.ShowService') - def get_parser(self, prog_name): parser = super(ShowService, self).get_parser(prog_name) parser.add_argument( @@ -163,7 +148,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity auth_ref = self.app.client_manager.auth_ref diff --git a/openstackclient/identity/v2_0/token.py b/openstackclient/identity/v2_0/token.py index 5fed58e591..db38fae8e3 100644 --- a/openstackclient/identity/v2_0/token.py +++ b/openstackclient/identity/v2_0/token.py @@ -15,26 +15,19 @@ """Identity v2 Token action implementations""" -import logging import six -from cliff import command -from cliff import show - -from openstackclient.common import utils +from openstackclient.common import command from openstackclient.i18n import _ # noqa -class IssueToken(show.ShowOne): +class IssueToken(command.ShowOne): """Issue new token""" - log = logging.getLogger(__name__ + '.IssueToken') - def get_parser(self, prog_name): parser = super(IssueToken, self).get_parser(prog_name) return parser - @utils.log_method(log) def take_action(self, parsed_args): token = self.app.client_manager.auth_ref.service_catalog.get_token() @@ -45,8 +38,6 @@ def take_action(self, parsed_args): class RevokeToken(command.Command): """Revoke existing token""" - log = logging.getLogger(__name__ + '.RevokeToken') - def get_parser(self, prog_name): parser = super(RevokeToken, self).get_parser(prog_name) parser.add_argument( @@ -57,7 +48,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) identity_client = self.app.client_manager.identity identity_client.tokens.delete(parsed_args.token) diff --git a/openstackclient/identity/v2_0/user.py b/openstackclient/identity/v2_0/user.py index e2b285bd9d..3d848737e5 100644 --- a/openstackclient/identity/v2_0/user.py +++ b/openstackclient/identity/v2_0/user.py @@ -15,23 +15,18 @@ """Identity v2.0 User action implementations""" -import logging import six -from cliff import command -from cliff import lister -from cliff import show from keystoneauth1 import exceptions as ks_exc +from openstackclient.common import command from openstackclient.common import utils from openstackclient.i18n import _ # noqa -class CreateUser(show.ShowOne): +class CreateUser(command.ShowOne): """Create new user""" - log = logging.getLogger(__name__ + '.CreateUser') - def get_parser(self, prog_name): parser = super(CreateUser, self).get_parser(prog_name) parser.add_argument( @@ -78,7 +73,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity @@ -130,8 +124,6 @@ def take_action(self, parsed_args): class DeleteUser(command.Command): """Delete user(s)""" - log = logging.getLogger(__name__ + '.DeleteUser') - def get_parser(self, prog_name): parser = super(DeleteUser, self).get_parser(prog_name) parser.add_argument( @@ -142,7 +134,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity @@ -155,11 +146,9 @@ def take_action(self, parsed_args): return -class ListUser(lister.Lister): +class ListUser(command.Lister): """List users""" - log = logging.getLogger(__name__ + '.ListUser') - def get_parser(self, prog_name): parser = super(ListUser, self).get_parser(prog_name) parser.add_argument( @@ -174,7 +163,6 @@ def get_parser(self, prog_name): help=_('List additional fields in output')) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity @@ -248,8 +236,6 @@ def _format_project(project): class SetUser(command.Command): """Set user properties""" - log = logging.getLogger(__name__ + '.SetUser') - def get_parser(self, prog_name): parser = super(SetUser, self).get_parser(prog_name) parser.add_argument( @@ -296,7 +282,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity @@ -348,11 +333,9 @@ def take_action(self, parsed_args): return -class ShowUser(show.ShowOne): +class ShowUser(command.ShowOne): """Display user details""" - log = logging.getLogger(__name__ + '.ShowUser') - def get_parser(self, prog_name): parser = super(ShowUser, self).get_parser(prog_name) parser.add_argument( @@ -362,7 +345,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity diff --git a/openstackclient/identity/v3/catalog.py b/openstackclient/identity/v3/catalog.py index 76f7da51b3..795f5d5c6d 100644 --- a/openstackclient/identity/v3/catalog.py +++ b/openstackclient/identity/v3/catalog.py @@ -13,12 +13,9 @@ """Identity v3 Service Catalog action implementations""" -import logging - -from cliff import lister -from cliff import show import six +from openstackclient.common import command from openstackclient.common import utils from openstackclient.i18n import _ # noqa @@ -34,12 +31,9 @@ def _format_endpoints(eps=None): return ret -class ListCatalog(lister.Lister): +class ListCatalog(command.Lister): """List services in the service catalog""" - log = logging.getLogger(__name__ + '.ListCatalog') - - @utils.log_method(log) def take_action(self, parsed_args): # This is ugly because if auth hasn't happened yet we need @@ -59,11 +53,9 @@ def take_action(self, parsed_args): ) for s in data)) -class ShowCatalog(show.ShowOne): +class ShowCatalog(command.ShowOne): """Display service catalog details""" - log = logging.getLogger(__name__ + '.ShowCatalog') - def get_parser(self, prog_name): parser = super(ShowCatalog, self).get_parser(prog_name) parser.add_argument( @@ -73,7 +65,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): # This is ugly because if auth hasn't happened yet we need diff --git a/openstackclient/identity/v3/consumer.py b/openstackclient/identity/v3/consumer.py index 0a6ade67f8..729839882a 100644 --- a/openstackclient/identity/v3/consumer.py +++ b/openstackclient/identity/v3/consumer.py @@ -15,22 +15,16 @@ """Identity v3 Consumer action implementations""" -import logging import six import sys -from cliff import command -from cliff import lister -from cliff import show - +from openstackclient.common import command from openstackclient.common import utils -class CreateConsumer(show.ShowOne): +class CreateConsumer(command.ShowOne): """Create new consumer""" - log = logging.getLogger(__name__ + '.CreateConsumer') - def get_parser(self, prog_name): parser = super(CreateConsumer, self).get_parser(prog_name) parser.add_argument( @@ -40,7 +34,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity consumer = identity_client.oauth1.consumers.create( @@ -53,8 +46,6 @@ def take_action(self, parsed_args): class DeleteConsumer(command.Command): """Delete consumer""" - log = logging.getLogger(__name__ + '.DeleteConsumer') - def get_parser(self, prog_name): parser = super(DeleteConsumer, self).get_parser(prog_name) parser.add_argument( @@ -64,7 +55,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity consumer = utils.find_resource( @@ -73,12 +63,9 @@ def take_action(self, parsed_args): return -class ListConsumer(lister.Lister): +class ListConsumer(command.Lister): """List consumers""" - log = logging.getLogger(__name__ + '.ListConsumer') - - @utils.log_method(log) def take_action(self, parsed_args): columns = ('ID', 'Description') data = self.app.client_manager.identity.oauth1.consumers.list() @@ -92,8 +79,6 @@ def take_action(self, parsed_args): class SetConsumer(command.Command): """Set consumer properties""" - log = logging.getLogger(__name__ + '.SetConsumer') - def get_parser(self, prog_name): parser = super(SetConsumer, self).get_parser(prog_name) parser.add_argument( @@ -108,7 +93,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity consumer = utils.find_resource( @@ -126,11 +110,9 @@ def take_action(self, parsed_args): return -class ShowConsumer(show.ShowOne): +class ShowConsumer(command.ShowOne): """Display consumer details""" - log = logging.getLogger(__name__ + '.ShowConsumer') - def get_parser(self, prog_name): parser = super(ShowConsumer, self).get_parser(prog_name) parser.add_argument( @@ -140,7 +122,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity consumer = utils.find_resource( diff --git a/openstackclient/identity/v3/credential.py b/openstackclient/identity/v3/credential.py index f22092d4db..6208b32004 100644 --- a/openstackclient/identity/v3/credential.py +++ b/openstackclient/identity/v3/credential.py @@ -15,21 +15,15 @@ """Identity v3 Credential action implementations""" -import logging import six -from cliff import command -from cliff import lister -from cliff import show - +from openstackclient.common import command from openstackclient.common import utils -class CreateCredential(show.ShowOne): +class CreateCredential(command.ShowOne): """Create credential command""" - log = logging.getLogger(__name__ + '.CreateCredential') - def get_parser(self, prog_name): parser = super(CreateCredential, self).get_parser(prog_name) parser.add_argument( @@ -56,7 +50,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity user_id = utils.find_resource(identity_client.users, @@ -79,8 +72,6 @@ def take_action(self, parsed_args): class DeleteCredential(command.Command): """Delete credential command""" - log = logging.getLogger(__name__ + '.DeleteCredential') - def get_parser(self, prog_name): parser = super(DeleteCredential, self).get_parser(prog_name) parser.add_argument( @@ -90,19 +81,15 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity identity_client.credentials.delete(parsed_args.credential) return -class ListCredential(lister.Lister): +class ListCredential(command.Lister): """List credential command""" - log = logging.getLogger(__name__ + '.ListCredential') - - @utils.log_method(log) def take_action(self, parsed_args): columns = ('ID', 'Type', 'User ID', 'Blob', 'Project ID') column_headers = ('ID', 'Type', 'User ID', 'Data', 'Project ID') @@ -117,8 +104,6 @@ def take_action(self, parsed_args): class SetCredential(command.Command): """Set credential command""" - log = logging.getLogger(__name__ + '.SetCredential') - def get_parser(self, prog_name): parser = super(SetCredential, self).get_parser(prog_name) parser.add_argument( @@ -152,7 +137,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity @@ -174,11 +158,9 @@ def take_action(self, parsed_args): return -class ShowCredential(show.ShowOne): +class ShowCredential(command.ShowOne): """Show credential command""" - log = logging.getLogger(__name__ + '.ShowCredential') - def get_parser(self, prog_name): parser = super(ShowCredential, self).get_parser(prog_name) parser.add_argument( @@ -188,7 +170,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity credential = utils.find_resource(identity_client.credentials, diff --git a/openstackclient/identity/v3/domain.py b/openstackclient/identity/v3/domain.py index 8278a300ef..cec967b897 100644 --- a/openstackclient/identity/v3/domain.py +++ b/openstackclient/identity/v3/domain.py @@ -15,24 +15,19 @@ """Identity v3 Domain action implementations""" -import logging import six import sys -from cliff import command -from cliff import lister -from cliff import show from keystoneauth1 import exceptions as ks_exc +from openstackclient.common import command from openstackclient.common import utils from openstackclient.i18n import _ # noqa -class CreateDomain(show.ShowOne): +class CreateDomain(command.ShowOne): """Create new domain""" - log = logging.getLogger(__name__ + '.CreateDomain') - def get_parser(self, prog_name): parser = super(CreateDomain, self).get_parser(prog_name) parser.add_argument( @@ -63,7 +58,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity @@ -92,8 +86,6 @@ def take_action(self, parsed_args): class DeleteDomain(command.Command): """Delete domain""" - log = logging.getLogger(__name__ + '.DeleteDomain') - def get_parser(self, prog_name): parser = super(DeleteDomain, self).get_parser(prog_name) parser.add_argument( @@ -103,7 +95,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity domain = utils.find_resource(identity_client.domains, @@ -112,12 +103,9 @@ def take_action(self, parsed_args): return -class ListDomain(lister.Lister): +class ListDomain(command.Lister): """List domains""" - log = logging.getLogger(__name__ + '.ListDomain') - - @utils.log_method(log) def take_action(self, parsed_args): columns = ('ID', 'Name', 'Enabled', 'Description') data = self.app.client_manager.identity.domains.list() @@ -131,8 +119,6 @@ def take_action(self, parsed_args): class SetDomain(command.Command): """Set domain properties""" - log = logging.getLogger(__name__ + '.SetDomain') - def get_parser(self, prog_name): parser = super(SetDomain, self).get_parser(prog_name) parser.add_argument( @@ -163,7 +149,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity domain = utils.find_resource(identity_client.domains, @@ -186,11 +171,9 @@ def take_action(self, parsed_args): return -class ShowDomain(show.ShowOne): +class ShowDomain(command.ShowOne): """Display domain details""" - log = logging.getLogger(__name__ + '.ShowDomain') - def get_parser(self, prog_name): parser = super(ShowDomain, self).get_parser(prog_name) parser.add_argument( @@ -200,7 +183,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity domain = utils.find_resource(identity_client.domains, diff --git a/openstackclient/identity/v3/ec2creds.py b/openstackclient/identity/v3/ec2creds.py index a12ee25e2b..777c0430e7 100644 --- a/openstackclient/identity/v3/ec2creds.py +++ b/openstackclient/identity/v3/ec2creds.py @@ -12,13 +12,9 @@ """Identity v3 EC2 Credentials action implementations""" -import logging import six -from cliff import command -from cliff import lister -from cliff import show - +from openstackclient.common import command from openstackclient.common import utils from openstackclient.i18n import _ # noqa from openstackclient.identity import common @@ -52,11 +48,9 @@ def _determine_ec2_user(parsed_args, client_manager): return user -class CreateEC2Creds(show.ShowOne): +class CreateEC2Creds(command.ShowOne): """Create EC2 credentials""" - log = logging.getLogger(__name__ + ".CreateEC2Creds") - def get_parser(self, prog_name): parser = super(CreateEC2Creds, self).get_parser(prog_name) parser.add_argument( @@ -79,7 +73,6 @@ def get_parser(self, prog_name): common.add_project_domain_option_to_parser(parser) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity client_manager = self.app.client_manager @@ -119,8 +112,6 @@ def take_action(self, parsed_args): class DeleteEC2Creds(command.Command): """Delete EC2 credentials""" - log = logging.getLogger(__name__ + '.DeleteEC2Creds') - def get_parser(self, prog_name): parser = super(DeleteEC2Creds, self).get_parser(prog_name) parser.add_argument( @@ -136,18 +127,15 @@ def get_parser(self, prog_name): common.add_user_domain_option_to_parser(parser) return parser - @utils.log_method(log) def take_action(self, parsed_args): client_manager = self.app.client_manager user = _determine_ec2_user(parsed_args, client_manager) client_manager.identity.ec2.delete(user, parsed_args.access_key) -class ListEC2Creds(lister.Lister): +class ListEC2Creds(command.Lister): """List EC2 credentials""" - log = logging.getLogger(__name__ + '.ListEC2Creds') - def get_parser(self, prog_name): parser = super(ListEC2Creds, self).get_parser(prog_name) parser.add_argument( @@ -158,7 +146,6 @@ def get_parser(self, prog_name): common.add_user_domain_option_to_parser(parser) return parser - @utils.log_method(log) def take_action(self, parsed_args): client_manager = self.app.client_manager user = _determine_ec2_user(parsed_args, client_manager) @@ -174,11 +161,9 @@ def take_action(self, parsed_args): ) for s in data)) -class ShowEC2Creds(show.ShowOne): +class ShowEC2Creds(command.ShowOne): """Display EC2 credentials details""" - log = logging.getLogger(__name__ + '.ShowEC2Creds') - def get_parser(self, prog_name): parser = super(ShowEC2Creds, self).get_parser(prog_name) parser.add_argument( @@ -194,7 +179,6 @@ def get_parser(self, prog_name): common.add_user_domain_option_to_parser(parser) return parser - @utils.log_method(log) def take_action(self, parsed_args): client_manager = self.app.client_manager user = _determine_ec2_user(parsed_args, client_manager) diff --git a/openstackclient/identity/v3/endpoint.py b/openstackclient/identity/v3/endpoint.py index 3d1c6f5416..1eff3b3b14 100644 --- a/openstackclient/identity/v3/endpoint.py +++ b/openstackclient/identity/v3/endpoint.py @@ -15,14 +15,10 @@ """Identity v3 Endpoint action implementations""" -import logging import six import sys -from cliff import command -from cliff import lister -from cliff import show - +from openstackclient.common import command from openstackclient.common import utils from openstackclient.identity import common @@ -34,11 +30,9 @@ def get_service_name(service): return '' -class CreateEndpoint(show.ShowOne): +class CreateEndpoint(command.ShowOne): """Create new endpoint""" - log = logging.getLogger(__name__ + '.CreateEndpoint') - def get_parser(self, prog_name): parser = super(CreateEndpoint, self).get_parser(prog_name) parser.add_argument( @@ -78,7 +72,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity service = common.find_service(identity_client, parsed_args.service) @@ -102,8 +95,6 @@ def take_action(self, parsed_args): class DeleteEndpoint(command.Command): """Delete endpoint""" - log = logging.getLogger(__name__ + '.DeleteEndpoint') - def get_parser(self, prog_name): parser = super(DeleteEndpoint, self).get_parser(prog_name) parser.add_argument( @@ -113,7 +104,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity endpoint_id = utils.find_resource(identity_client.endpoints, @@ -122,11 +112,9 @@ def take_action(self, parsed_args): return -class ListEndpoint(lister.Lister): +class ListEndpoint(command.Lister): """List endpoints""" - log = logging.getLogger(__name__ + '.ListEndpoint') - def get_parser(self, prog_name): parser = super(ListEndpoint, self).get_parser(prog_name) parser.add_argument( @@ -147,7 +135,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity columns = ('ID', 'Region', 'Service Name', 'Service Type', @@ -176,8 +163,6 @@ def take_action(self, parsed_args): class SetEndpoint(command.Command): """Set endpoint properties""" - log = logging.getLogger(__name__ + '.SetEndpoint') - def get_parser(self, prog_name): parser = super(SetEndpoint, self).get_parser(prog_name) parser.add_argument( @@ -221,7 +206,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity endpoint = utils.find_resource(identity_client.endpoints, @@ -256,11 +240,9 @@ def take_action(self, parsed_args): return -class ShowEndpoint(show.ShowOne): +class ShowEndpoint(command.ShowOne): """Display endpoint details""" - log = logging.getLogger(__name__ + '.ShowEndpoint') - def get_parser(self, prog_name): parser = super(ShowEndpoint, self).get_parser(prog_name) parser.add_argument( @@ -270,7 +252,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity endpoint = utils.find_resource(identity_client.endpoints, diff --git a/openstackclient/identity/v3/federation_protocol.py b/openstackclient/identity/v3/federation_protocol.py index 20877fcd4d..2d7ab15dfa 100644 --- a/openstackclient/identity/v3/federation_protocol.py +++ b/openstackclient/identity/v3/federation_protocol.py @@ -14,21 +14,15 @@ """Identity v3 Protocols actions implementations""" -import logging import six -from cliff import command -from cliff import lister -from cliff import show - +from openstackclient.common import command from openstackclient.common import utils -class CreateProtocol(show.ShowOne): +class CreateProtocol(command.ShowOne): """Create new federation protocol""" - log = logging.getLogger(__name__ + 'CreateProtocol') - def get_parser(self, prog_name): parser = super(CreateProtocol, self).get_parser(prog_name) parser.add_argument( @@ -50,7 +44,6 @@ def get_parser(self, prog_name): return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity protocol = identity_client.federation.protocols.create( @@ -71,8 +64,6 @@ def take_action(self, parsed_args): class DeleteProtocol(command.Command): """Delete federation protocol""" - log = logging.getLogger(__name__ + '.DeleteProtocol') - def get_parser(self, prog_name): parser = super(DeleteProtocol, self).get_parser(prog_name) parser.add_argument( @@ -88,7 +79,6 @@ def get_parser(self, prog_name): return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity identity_client.federation.protocols.delete( @@ -96,11 +86,9 @@ def take_action(self, parsed_args): return -class ListProtocols(lister.Lister): +class ListProtocols(command.Lister): """List federation protocols""" - log = logging.getLogger(__name__ + '.ListProtocols') - def get_parser(self, prog_name): parser = super(ListProtocols, self).get_parser(prog_name) parser.add_argument( @@ -126,8 +114,6 @@ def take_action(self, parsed_args): class SetProtocol(command.Command): """Set federation protocol properties""" - log = logging.getLogger(__name__ + '.SetProtocol') - def get_parser(self, prog_name): parser = super(SetProtocol, self).get_parser(prog_name) parser.add_argument( @@ -166,11 +152,9 @@ def take_action(self, parsed_args): return zip(*sorted(six.iteritems(info))) -class ShowProtocol(show.ShowOne): +class ShowProtocol(command.ShowOne): """Display federation protocol details""" - log = logging.getLogger(__name__ + '.ShowProtocol') - def get_parser(self, prog_name): parser = super(ShowProtocol, self).get_parser(prog_name) parser.add_argument( diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py index d503a6b869..b3d893ced6 100644 --- a/openstackclient/identity/v3/group.py +++ b/openstackclient/identity/v3/group.py @@ -15,15 +15,12 @@ """Group action implementations""" -import logging import six import sys -from cliff import command -from cliff import lister -from cliff import show from keystoneauth1 import exceptions as ks_exc +from openstackclient.common import command from openstackclient.common import utils from openstackclient.i18n import _ # noqa from openstackclient.identity import common @@ -32,8 +29,6 @@ class AddUserToGroup(command.Command): """Add user to group""" - log = logging.getLogger(__name__ + '.AddUserToGroup') - def get_parser(self, prog_name): parser = super(AddUserToGroup, self).get_parser(prog_name) parser.add_argument( @@ -50,7 +45,6 @@ def get_parser(self, prog_name): common.add_user_domain_option_to_parser(parser) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity @@ -74,8 +68,6 @@ def take_action(self, parsed_args): class CheckUserInGroup(command.Command): """Check user membership in group""" - log = logging.getLogger(__name__ + '.CheckUserInGroup') - def get_parser(self, prog_name): parser = super(CheckUserInGroup, self).get_parser(prog_name) parser.add_argument( @@ -92,7 +84,6 @@ def get_parser(self, prog_name): common.add_user_domain_option_to_parser(parser) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity @@ -113,11 +104,9 @@ def take_action(self, parsed_args): (parsed_args.user, parsed_args.group)) -class CreateGroup(show.ShowOne): +class CreateGroup(command.ShowOne): """Create new group""" - log = logging.getLogger(__name__ + '.CreateGroup') - def get_parser(self, prog_name): parser = super(CreateGroup, self).get_parser(prog_name) parser.add_argument( @@ -142,7 +131,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity @@ -172,8 +160,6 @@ def take_action(self, parsed_args): class DeleteGroup(command.Command): """Delete group(s)""" - log = logging.getLogger(__name__ + '.DeleteGroup') - def get_parser(self, prog_name): parser = super(DeleteGroup, self).get_parser(prog_name) parser.add_argument( @@ -188,7 +174,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity @@ -200,11 +185,9 @@ def take_action(self, parsed_args): return -class ListGroup(lister.Lister): +class ListGroup(command.Lister): """List groups""" - log = logging.getLogger(__name__ + '.ListGroup') - def get_parser(self, prog_name): parser = super(ListGroup, self).get_parser(prog_name) parser.add_argument( @@ -226,7 +209,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity @@ -266,8 +248,6 @@ def take_action(self, parsed_args): class RemoveUserFromGroup(command.Command): """Remove user from group""" - log = logging.getLogger(__name__ + '.RemoveUserFromGroup') - def get_parser(self, prog_name): parser = super(RemoveUserFromGroup, self).get_parser(prog_name) parser.add_argument( @@ -284,7 +264,6 @@ def get_parser(self, prog_name): common.add_user_domain_option_to_parser(parser) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity @@ -308,8 +287,6 @@ def take_action(self, parsed_args): class SetGroup(command.Command): """Set group properties""" - log = logging.getLogger(__name__ + '.SetGroup') - def get_parser(self, prog_name): parser = super(SetGroup, self).get_parser(prog_name) parser.add_argument( @@ -331,7 +308,6 @@ def get_parser(self, prog_name): help='New group description') return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity group = common.find_group(identity_client, parsed_args.group, @@ -349,11 +325,9 @@ def take_action(self, parsed_args): return -class ShowGroup(show.ShowOne): +class ShowGroup(command.ShowOne): """Display group details""" - log = logging.getLogger(__name__ + '.ShowGroup') - def get_parser(self, prog_name): parser = super(ShowGroup, self).get_parser(prog_name) parser.add_argument( @@ -368,7 +342,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity diff --git a/openstackclient/identity/v3/identity_provider.py b/openstackclient/identity/v3/identity_provider.py index 4e086b681b..0ff8acb9a1 100644 --- a/openstackclient/identity/v3/identity_provider.py +++ b/openstackclient/identity/v3/identity_provider.py @@ -13,21 +13,15 @@ """Identity v3 IdentityProvider action implementations""" -import logging import six -from cliff import command -from cliff import lister -from cliff import show - +from openstackclient.common import command from openstackclient.common import utils -class CreateIdentityProvider(show.ShowOne): +class CreateIdentityProvider(command.ShowOne): """Create new identity provider""" - log = logging.getLogger(__name__ + '.CreateIdentityProvider') - def get_parser(self, prog_name): parser = super(CreateIdentityProvider, self).get_parser(prog_name) parser.add_argument( @@ -70,7 +64,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity if parsed_args.remote_id_file: @@ -96,8 +89,6 @@ def take_action(self, parsed_args): class DeleteIdentityProvider(command.Command): """Delete identity provider""" - log = logging.getLogger(__name__ + '.DeleteIdentityProvider') - def get_parser(self, prog_name): parser = super(DeleteIdentityProvider, self).get_parser(prog_name) parser.add_argument( @@ -107,7 +98,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity identity_client.federation.identity_providers.delete( @@ -115,12 +105,9 @@ def take_action(self, parsed_args): return -class ListIdentityProvider(lister.Lister): +class ListIdentityProvider(command.Lister): """List identity providers""" - log = logging.getLogger(__name__ + '.ListIdentityProvider') - - @utils.log_method(log) def take_action(self, parsed_args): columns = ('ID', 'Enabled', 'Description') identity_client = self.app.client_manager.identity @@ -135,8 +122,6 @@ def take_action(self, parsed_args): class SetIdentityProvider(command.Command): """Set identity provider properties""" - log = logging.getLogger(__name__ + '.SetIdentityProvider') - def get_parser(self, prog_name): parser = super(SetIdentityProvider, self).get_parser(prog_name) parser.add_argument( @@ -176,7 +161,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): federation_client = self.app.client_manager.identity.federation @@ -215,11 +199,9 @@ def take_action(self, parsed_args): return zip(*sorted(six.iteritems(identity_provider._info))) -class ShowIdentityProvider(show.ShowOne): +class ShowIdentityProvider(command.ShowOne): """Display identity provider details""" - log = logging.getLogger(__name__ + '.ShowIdentityProvider') - def get_parser(self, prog_name): parser = super(ShowIdentityProvider, self).get_parser(prog_name) parser.add_argument( @@ -229,7 +211,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity idp = utils.find_resource( diff --git a/openstackclient/identity/v3/mapping.py b/openstackclient/identity/v3/mapping.py index c79331ec7d..422d66bcbd 100644 --- a/openstackclient/identity/v3/mapping.py +++ b/openstackclient/identity/v3/mapping.py @@ -16,13 +16,10 @@ """Identity v3 federation mapping action implementations""" import json -import logging -from cliff import command -from cliff import lister -from cliff import show import six +from openstackclient.common import command from openstackclient.common import exceptions from openstackclient.common import utils @@ -79,11 +76,9 @@ def _read_rules(self, path): return rules -class CreateMapping(show.ShowOne, _RulesReader): +class CreateMapping(command.ShowOne, _RulesReader): """Create new mapping""" - log = logging.getLogger(__name__ + '.CreateMapping') - def get_parser(self, prog_name): parser = super(CreateMapping, self).get_parser(prog_name) parser.add_argument( @@ -99,7 +94,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) identity_client = self.app.client_manager.identity rules = self._read_rules(parsed_args.rules) @@ -114,8 +108,6 @@ def take_action(self, parsed_args): class DeleteMapping(command.Command): """Delete mapping""" - log = logging.getLogger(__name__ + '.DeleteMapping') - def get_parser(self, prog_name): parser = super(DeleteMapping, self).get_parser(prog_name) parser.add_argument( @@ -126,19 +118,16 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) identity_client = self.app.client_manager.identity identity_client.federation.mappings.delete(parsed_args.mapping) return -class ListMapping(lister.Lister): +class ListMapping(command.Lister): """List mappings""" - log = logging.getLogger(__name__ + '.ListMapping') def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) # NOTE(marek-denis): Since rules can be long and tedious I have decided # to only list ids of the mappings. If somebody wants to check the # rules, (s)he should show specific ones. @@ -152,8 +141,6 @@ def take_action(self, parsed_args): class SetMapping(command.Command, _RulesReader): """Set mapping properties""" - log = logging.getLogger(__name__ + '.SetMapping') - def get_parser(self, prog_name): parser = super(SetMapping, self).get_parser(prog_name) parser.add_argument( @@ -169,7 +156,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) identity_client = self.app.client_manager.identity if not parsed_args.rules: @@ -186,11 +172,9 @@ def take_action(self, parsed_args): return zip(*sorted(six.iteritems(mapping._info))) -class ShowMapping(show.ShowOne): +class ShowMapping(command.ShowOne): """Display mapping details""" - log = logging.getLogger(__name__ + '.ShowMapping') - def get_parser(self, prog_name): parser = super(ShowMapping, self).get_parser(prog_name) parser.add_argument( @@ -201,7 +185,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) identity_client = self.app.client_manager.identity mapping = identity_client.federation.mappings.get(parsed_args.mapping) diff --git a/openstackclient/identity/v3/policy.py b/openstackclient/identity/v3/policy.py index 9da94863f4..503df37404 100644 --- a/openstackclient/identity/v3/policy.py +++ b/openstackclient/identity/v3/policy.py @@ -15,22 +15,16 @@ """Identity v3 Policy action implementations""" -import logging import six import sys -from cliff import command -from cliff import lister -from cliff import show - +from openstackclient.common import command from openstackclient.common import utils -class CreatePolicy(show.ShowOne): +class CreatePolicy(command.ShowOne): """Create new policy""" - log = logging.getLogger(__name__ + '.CreatePolicy') - def get_parser(self, prog_name): parser = super(CreatePolicy, self).get_parser(prog_name) parser.add_argument( @@ -47,7 +41,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): blob = utils.read_blob_file_contents(parsed_args.rules) @@ -64,8 +57,6 @@ def take_action(self, parsed_args): class DeletePolicy(command.Command): """Delete policy""" - log = logging.getLogger(__name__ + '.DeletePolicy') - def get_parser(self, prog_name): parser = super(DeletePolicy, self).get_parser(prog_name) parser.add_argument( @@ -75,18 +66,15 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity identity_client.policies.delete(parsed_args.policy) return -class ListPolicy(lister.Lister): +class ListPolicy(command.Lister): """List policies""" - log = logging.getLogger(__name__ + '.ListPolicy') - def get_parser(self, prog_name): parser = super(ListPolicy, self).get_parser(prog_name) parser.add_argument( @@ -97,7 +85,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): if parsed_args.long: columns = ('ID', 'Type', 'Blob') @@ -116,8 +103,6 @@ def take_action(self, parsed_args): class SetPolicy(command.Command): """Set policy properties""" - log = logging.getLogger(__name__ + '.SetPolicy') - def get_parser(self, prog_name): parser = super(SetPolicy, self).get_parser(prog_name) parser.add_argument( @@ -137,7 +122,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity blob = None @@ -158,11 +142,9 @@ def take_action(self, parsed_args): return -class ShowPolicy(show.ShowOne): +class ShowPolicy(command.ShowOne): """Display policy details""" - log = logging.getLogger(__name__ + '.ShowPolicy') - def get_parser(self, prog_name): parser = super(ShowPolicy, self).get_parser(prog_name) parser.add_argument( @@ -172,7 +154,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity policy = utils.find_resource(identity_client.policies, diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index 22745aa409..61db861441 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -15,25 +15,20 @@ """Project action implementations""" -import logging import six -from cliff import command -from cliff import lister -from cliff import show from keystoneauth1 import exceptions as ks_exc +from openstackclient.common import command from openstackclient.common import parseractions from openstackclient.common import utils from openstackclient.i18n import _ # noqa from openstackclient.identity import common -class CreateProject(show.ShowOne): +class CreateProject(command.ShowOne): """Create new project""" - log = logging.getLogger(__name__ + '.CreateProject') - def get_parser(self, prog_name): parser = super(CreateProject, self).get_parser(prog_name) parser.add_argument( @@ -81,7 +76,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity @@ -129,8 +123,6 @@ def take_action(self, parsed_args): class DeleteProject(command.Command): """Delete project(s)""" - log = logging.getLogger(__name__ + '.DeleteProject') - def get_parser(self, prog_name): parser = super(DeleteProject, self).get_parser(prog_name) parser.add_argument( @@ -146,7 +138,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity @@ -165,11 +156,9 @@ def take_action(self, parsed_args): return -class ListProject(lister.Lister): +class ListProject(command.Lister): """List projects""" - log = logging.getLogger(__name__ + '.ListProject') - def get_parser(self, prog_name): parser = super(ListProject, self).get_parser(prog_name) parser.add_argument( @@ -190,7 +179,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity if parsed_args.long: @@ -227,8 +215,6 @@ def take_action(self, parsed_args): class SetProject(command.Command): """Set project properties""" - log = logging.getLogger(__name__ + '.SetProject') - def get_parser(self, prog_name): parser = super(SetProject, self).get_parser(prog_name) parser.add_argument( @@ -271,7 +257,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity @@ -302,11 +287,9 @@ def take_action(self, parsed_args): return -class ShowProject(show.ShowOne): +class ShowProject(command.ShowOne): """Display project details""" - log = logging.getLogger(__name__ + '.ShowProject') - def get_parser(self, prog_name): parser = super(ShowProject, self).get_parser(prog_name) parser.add_argument( @@ -333,7 +316,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity diff --git a/openstackclient/identity/v3/region.py b/openstackclient/identity/v3/region.py index 1ff0b8c069..1e15fd203c 100644 --- a/openstackclient/identity/v3/region.py +++ b/openstackclient/identity/v3/region.py @@ -13,22 +13,16 @@ """Identity v3 Region action implementations""" -import logging import six -from cliff import command -from cliff import lister -from cliff import show - +from openstackclient.common import command from openstackclient.common import utils from openstackclient.i18n import _ # noqa -class CreateRegion(show.ShowOne): +class CreateRegion(command.ShowOne): """Create new region""" - log = logging.getLogger(__name__ + '.CreateRegion') - def get_parser(self, prog_name): parser = super(CreateRegion, self).get_parser(prog_name) # NOTE(stevemar): The API supports an optional region ID, but that @@ -50,7 +44,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity @@ -69,8 +62,6 @@ def take_action(self, parsed_args): class DeleteRegion(command.Command): """Delete region""" - log = logging.getLogger(__name__ + '.DeleteRegion') - def get_parser(self, prog_name): parser = super(DeleteRegion, self).get_parser(prog_name) parser.add_argument( @@ -80,7 +71,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity @@ -88,11 +78,9 @@ def take_action(self, parsed_args): return -class ListRegion(lister.Lister): +class ListRegion(command.Lister): """List regions""" - log = logging.getLogger(__name__ + '.ListRegion') - def get_parser(self, prog_name): parser = super(ListRegion, self).get_parser(prog_name) parser.add_argument( @@ -102,7 +90,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity @@ -124,8 +111,6 @@ def take_action(self, parsed_args): class SetRegion(command.Command): """Set region properties""" - log = logging.getLogger(__name__ + '.SetRegion') - def get_parser(self, prog_name): parser = super(SetRegion, self).get_parser(prog_name) parser.add_argument( @@ -145,7 +130,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity @@ -162,11 +146,9 @@ def take_action(self, parsed_args): return -class ShowRegion(show.ShowOne): +class ShowRegion(command.ShowOne): """Display region details""" - log = logging.getLogger(__name__ + '.ShowRegion') - def get_parser(self, prog_name): parser = super(ShowRegion, self).get_parser(prog_name) parser.add_argument( @@ -176,7 +158,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index 66f189649b..4cced61170 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -15,15 +15,12 @@ """Identity v3 Role action implementations""" -import logging import six import sys -from cliff import command -from cliff import lister -from cliff import show from keystoneauth1 import exceptions as ks_exc +from openstackclient.common import command from openstackclient.common import utils from openstackclient.i18n import _ # noqa from openstackclient.identity import common @@ -110,8 +107,6 @@ def _process_identity_and_resource_options(parsed_args, class AddRole(command.Command): """Adds a role to a user or group on a domain or project""" - log = logging.getLogger(__name__ + '.AddRole') - def get_parser(self, prog_name): parser = super(AddRole, self).get_parser(prog_name) parser.add_argument( @@ -122,7 +117,6 @@ def get_parser(self, prog_name): _add_identity_and_resource_options_to_parser(parser) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity @@ -147,11 +141,9 @@ def take_action(self, parsed_args): return -class CreateRole(show.ShowOne): +class CreateRole(command.ShowOne): """Create new role""" - log = logging.getLogger(__name__ + '.CreateRole') - def get_parser(self, prog_name): parser = super(CreateRole, self).get_parser(prog_name) parser.add_argument( @@ -166,7 +158,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity @@ -187,8 +178,6 @@ def take_action(self, parsed_args): class DeleteRole(command.Command): """Delete role(s)""" - log = logging.getLogger(__name__ + '.DeleteRole') - def get_parser(self, prog_name): parser = super(DeleteRole, self).get_parser(prog_name) parser.add_argument( @@ -199,7 +188,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity @@ -212,18 +200,15 @@ def take_action(self, parsed_args): return -class ListRole(lister.Lister): +class ListRole(command.Lister): """List roles""" - log = logging.getLogger(__name__ + '.ListRole') - def get_parser(self, prog_name): parser = super(ListRole, self).get_parser(prog_name) _add_identity_and_resource_options_to_parser(parser) return parser def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) identity_client = self.app.client_manager.identity if parsed_args.user: @@ -318,8 +303,6 @@ def take_action(self, parsed_args): class RemoveRole(command.Command): """Remove role from domain/project : user/group""" - log = logging.getLogger(__name__ + '.RemoveRole') - def get_parser(self, prog_name): parser = super(RemoveRole, self).get_parser(prog_name) parser.add_argument( @@ -330,7 +313,6 @@ def get_parser(self, prog_name): _add_identity_and_resource_options_to_parser(parser) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity @@ -357,8 +339,6 @@ def take_action(self, parsed_args): class SetRole(command.Command): """Set role properties""" - log = logging.getLogger(__name__ + '.SetRole') - def get_parser(self, prog_name): parser = super(SetRole, self).get_parser(prog_name) parser.add_argument( @@ -373,7 +353,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity @@ -389,11 +368,9 @@ def take_action(self, parsed_args): return -class ShowRole(show.ShowOne): +class ShowRole(command.ShowOne): """Display role details""" - log = logging.getLogger(__name__ + '.ShowRole') - def get_parser(self, prog_name): parser = super(ShowRole, self).get_parser(prog_name) parser.add_argument( @@ -403,7 +380,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity diff --git a/openstackclient/identity/v3/role_assignment.py b/openstackclient/identity/v3/role_assignment.py index 169c6cb970..d766be686e 100644 --- a/openstackclient/identity/v3/role_assignment.py +++ b/openstackclient/identity/v3/role_assignment.py @@ -13,19 +13,14 @@ """Identity v3 Assignment action implementations """ -import logging - -from cliff import lister - +from openstackclient.common import command from openstackclient.common import utils from openstackclient.identity import common -class ListRoleAssignment(lister.Lister): +class ListRoleAssignment(command.Lister): """List role assignments""" - log = logging.getLogger(__name__ + '.ListRoleAssignment') - def get_parser(self, prog_name): parser = super(ListRoleAssignment, self).get_parser(prog_name) parser.add_argument( @@ -72,7 +67,6 @@ def _as_tuple(self, assignment): assignment.project, assignment.domain, assignment.inherited) def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) identity_client = self.app.client_manager.identity role = None @@ -114,7 +108,6 @@ def take_action(self, parsed_args): ) effective = True if parsed_args.effective else False - self.log.debug('take_action(%s)' % parsed_args) columns = ('Role', 'User', 'Group', 'Project', 'Domain', 'Inherited') inherited_to = 'projects' if parsed_args.inherited else None diff --git a/openstackclient/identity/v3/service.py b/openstackclient/identity/v3/service.py index 85081aa764..42117c8de9 100644 --- a/openstackclient/identity/v3/service.py +++ b/openstackclient/identity/v3/service.py @@ -15,22 +15,16 @@ """Identity v3 Service action implementations""" -import logging import six -from cliff import command -from cliff import lister -from cliff import show - +from openstackclient.common import command from openstackclient.common import utils from openstackclient.identity import common -class CreateService(show.ShowOne): +class CreateService(command.ShowOne): """Create new service""" - log = logging.getLogger(__name__ + '.CreateService') - def get_parser(self, prog_name): parser = super(CreateService, self).get_parser(prog_name) parser.add_argument( @@ -61,7 +55,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity @@ -83,8 +76,6 @@ def take_action(self, parsed_args): class DeleteService(command.Command): """Delete service""" - log = logging.getLogger(__name__ + '.DeleteService') - def get_parser(self, prog_name): parser = super(DeleteService, self).get_parser(prog_name) parser.add_argument( @@ -94,7 +85,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity @@ -104,11 +94,9 @@ def take_action(self, parsed_args): return -class ListService(lister.Lister): +class ListService(command.Lister): """List services""" - log = logging.getLogger(__name__ + '.ListService') - def get_parser(self, prog_name): parser = super(ListService, self).get_parser(prog_name) parser.add_argument( @@ -119,7 +107,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): if parsed_args.long: @@ -136,8 +123,6 @@ def take_action(self, parsed_args): class SetService(command.Command): """Set service properties""" - log = logging.getLogger(__name__ + '.SetService') - def get_parser(self, prog_name): parser = super(SetService, self).get_parser(prog_name) parser.add_argument( @@ -173,7 +158,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity @@ -205,11 +189,9 @@ def take_action(self, parsed_args): return -class ShowService(show.ShowOne): +class ShowService(command.ShowOne): """Display service details""" - log = logging.getLogger(__name__ + '.ShowService') - def get_parser(self, prog_name): parser = super(ShowService, self).get_parser(prog_name) parser.add_argument( @@ -219,7 +201,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity diff --git a/openstackclient/identity/v3/service_provider.py b/openstackclient/identity/v3/service_provider.py index 838ad4a29e..6016928c11 100644 --- a/openstackclient/identity/v3/service_provider.py +++ b/openstackclient/identity/v3/service_provider.py @@ -13,22 +13,16 @@ """Service Provider action implementations""" -import logging import six import sys -from cliff import command -from cliff import lister -from cliff import show - +from openstackclient.common import command from openstackclient.common import utils -class CreateServiceProvider(show.ShowOne): +class CreateServiceProvider(command.ShowOne): """Create new service provider""" - log = logging.getLogger(__name__ + '.CreateServiceProvider') - def get_parser(self, prog_name): parser = super(CreateServiceProvider, self).get_parser(prog_name) parser.add_argument( @@ -73,7 +67,6 @@ def get_parser(self, prog_name): return parser - @utils.log_method(log) def take_action(self, parsed_args): service_client = self.app.client_manager.identity sp = service_client.federation.service_providers.create( @@ -90,8 +83,6 @@ def take_action(self, parsed_args): class DeleteServiceProvider(command.Command): """Delete service provider""" - log = logging.getLogger(__name__ + '.DeleteServiceProvider') - def get_parser(self, prog_name): parser = super(DeleteServiceProvider, self).get_parser(prog_name) parser.add_argument( @@ -101,7 +92,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): service_client = self.app.client_manager.identity service_client.federation.service_providers.delete( @@ -109,12 +99,9 @@ def take_action(self, parsed_args): return -class ListServiceProvider(lister.Lister): +class ListServiceProvider(command.Lister): """List service providers""" - log = logging.getLogger(__name__ + '.ListServiceProvider') - - @utils.log_method(log) def take_action(self, parsed_args): service_client = self.app.client_manager.identity data = service_client.federation.service_providers.list() @@ -130,8 +117,6 @@ def take_action(self, parsed_args): class SetServiceProvider(command.Command): """Set service provider properties""" - log = logging.getLogger(__name__ + '.SetServiceProvider') - def get_parser(self, prog_name): parser = super(SetServiceProvider, self).get_parser(prog_name) parser.add_argument( @@ -168,7 +153,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): federation_client = self.app.client_manager.identity.federation @@ -193,11 +177,9 @@ def take_action(self, parsed_args): return zip(*sorted(six.iteritems(service_provider._info))) -class ShowServiceProvider(show.ShowOne): +class ShowServiceProvider(command.ShowOne): """Display service provider details""" - log = logging.getLogger(__name__ + '.ShowServiceProvider') - def get_parser(self, prog_name): parser = super(ShowServiceProvider, self).get_parser(prog_name) parser.add_argument( @@ -207,7 +189,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): service_client = self.app.client_manager.identity service_provider = utils.find_resource( diff --git a/openstackclient/identity/v3/token.py b/openstackclient/identity/v3/token.py index cd3dc798fd..588c5218ff 100644 --- a/openstackclient/identity/v3/token.py +++ b/openstackclient/identity/v3/token.py @@ -15,20 +15,16 @@ """Identity v3 Token action implementations""" -import logging import six -from cliff import show - +from openstackclient.common import command from openstackclient.common import utils from openstackclient.identity import common -class AuthorizeRequestToken(show.ShowOne): +class AuthorizeRequestToken(command.ShowOne): """Authorize a request token""" - log = logging.getLogger(__name__ + '.AuthorizeRequestToken') - def get_parser(self, prog_name): parser = super(AuthorizeRequestToken, self).get_parser(prog_name) parser.add_argument( @@ -49,7 +45,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) identity_client = self.app.client_manager.identity # NOTE(stevemar): We want a list of role ids @@ -68,11 +63,9 @@ def take_action(self, parsed_args): return zip(*sorted(six.iteritems(verifier_pin._info))) -class CreateAccessToken(show.ShowOne): +class CreateAccessToken(command.ShowOne): """Create an access token""" - log = logging.getLogger(__name__ + '.CreateAccessToken') - def get_parser(self, prog_name): parser = super(CreateAccessToken, self).get_parser(prog_name) parser.add_argument( @@ -108,7 +101,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) token_client = self.app.client_manager.identity.oauth1.access_tokens access_token = token_client.create( parsed_args.consumer_key, parsed_args.consumer_secret, @@ -117,11 +109,9 @@ def take_action(self, parsed_args): return zip(*sorted(six.iteritems(access_token._info))) -class CreateRequestToken(show.ShowOne): +class CreateRequestToken(command.ShowOne): """Create a request token""" - log = logging.getLogger(__name__ + '.CreateRequestToken') - def get_parser(self, prog_name): parser = super(CreateRequestToken, self).get_parser(prog_name) parser.add_argument( @@ -151,8 +141,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) - identity_client = self.app.client_manager.identity if parsed_args.domain: @@ -173,16 +161,13 @@ def take_action(self, parsed_args): return zip(*sorted(six.iteritems(request_token._info))) -class IssueToken(show.ShowOne): +class IssueToken(command.ShowOne): """Issue new token""" - log = logging.getLogger(__name__ + '.IssueToken') - def get_parser(self, prog_name): parser = super(IssueToken, self).get_parser(prog_name) return parser - @utils.log_method(log) def take_action(self, parsed_args): token = self.app.client_manager.auth_ref.service_catalog.get_token() if 'tenant_id' in token: diff --git a/openstackclient/identity/v3/trust.py b/openstackclient/identity/v3/trust.py index 2c3cf53775..26fb8338fe 100644 --- a/openstackclient/identity/v3/trust.py +++ b/openstackclient/identity/v3/trust.py @@ -14,22 +14,16 @@ """Identity v3 Trust action implementations""" import datetime -import logging import six -from cliff import command -from cliff import lister -from cliff import show - +from openstackclient.common import command from openstackclient.common import utils from openstackclient.identity import common -class CreateTrust(show.ShowOne): +class CreateTrust(command.ShowOne): """Create new trust""" - log = logging.getLogger(__name__ + '.CreateTrust') - def get_parser(self, prog_name): parser = super(CreateTrust, self).get_parser(prog_name) parser.add_argument( @@ -85,7 +79,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) identity_client = self.app.client_manager.identity # NOTE(stevemar): Find the two users, project and roles that @@ -138,8 +131,6 @@ def take_action(self, parsed_args): class DeleteTrust(command.Command): """Delete trust(s)""" - log = logging.getLogger(__name__ + '.DeleteTrust') - def get_parser(self, prog_name): parser = super(DeleteTrust, self).get_parser(prog_name) parser.add_argument( @@ -151,20 +142,16 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) identity_client = self.app.client_manager.identity for t in parsed_args.trust: trust_obj = utils.find_resource(identity_client.trusts, t) identity_client.trusts.delete(trust_obj.id) -class ListTrust(lister.Lister): +class ListTrust(command.Lister): """List trusts""" - log = logging.getLogger(__name__ + '.ListTrust') - def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) columns = ('ID', 'Expires At', 'Impersonation', 'Project ID', 'Trustee User ID', 'Trustor User ID') data = self.app.client_manager.identity.trusts.list() @@ -175,11 +162,9 @@ def take_action(self, parsed_args): ) for s in data)) -class ShowTrust(show.ShowOne): +class ShowTrust(command.ShowOne): """Display trust details""" - log = logging.getLogger(__name__ + '.ShowTrust') - def get_parser(self, prog_name): parser = super(ShowTrust, self).get_parser(prog_name) parser.add_argument( @@ -190,7 +175,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) identity_client = self.app.client_manager.identity trust = utils.find_resource(identity_client.trusts, parsed_args.trust) diff --git a/openstackclient/identity/v3/unscoped_saml.py b/openstackclient/identity/v3/unscoped_saml.py index e659e75e82..a42637ddc3 100644 --- a/openstackclient/identity/v3/unscoped_saml.py +++ b/openstackclient/identity/v3/unscoped_saml.py @@ -17,10 +17,7 @@ the user can list domains and projects they are allowed to access, and request a scoped token.""" -import logging - -from cliff import lister - +from openstackclient.common import command from openstackclient.common import exceptions from openstackclient.common import utils @@ -43,13 +40,10 @@ def _decorated(self, parsed_args): return _decorated -class ListAccessibleDomains(lister.Lister): +class ListAccessibleDomains(command.Lister): """List accessible domains""" - log = logging.getLogger(__name__ + '.ListAccessibleDomains') - @auth_with_unscoped_saml - @utils.log_method(log) def take_action(self, parsed_args): columns = ('ID', 'Enabled', 'Name', 'Description') identity_client = self.app.client_manager.identity @@ -61,13 +55,10 @@ def take_action(self, parsed_args): ) for s in data)) -class ListAccessibleProjects(lister.Lister): +class ListAccessibleProjects(command.Lister): """List accessible projects""" - log = logging.getLogger(__name__ + '.ListAccessibleProjects') - @auth_with_unscoped_saml - @utils.log_method(log) def take_action(self, parsed_args): columns = ('ID', 'Domain ID', 'Enabled', 'Name') identity_client = self.app.client_manager.identity diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index 43a116cb36..c694c6aeb6 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -16,24 +16,19 @@ """Identity v3 User action implementations""" import copy -import logging import six -from cliff import command -from cliff import lister -from cliff import show from keystoneauth1 import exceptions as ks_exc +from openstackclient.common import command from openstackclient.common import utils from openstackclient.i18n import _ # noqa from openstackclient.identity import common -class CreateUser(show.ShowOne): +class CreateUser(command.ShowOne): """Create new user""" - log = logging.getLogger(__name__ + '.CreateUser') - def get_parser(self, prog_name): parser = super(CreateUser, self).get_parser(prog_name) parser.add_argument( @@ -91,7 +86,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity @@ -138,8 +132,6 @@ def take_action(self, parsed_args): class DeleteUser(command.Command): """Delete user(s)""" - log = logging.getLogger(__name__ + '.DeleteUser') - def get_parser(self, prog_name): parser = super(DeleteUser, self).get_parser(prog_name) parser.add_argument( @@ -155,7 +147,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity @@ -174,11 +165,9 @@ def take_action(self, parsed_args): return -class ListUser(lister.Lister): +class ListUser(command.Lister): """List users""" - log = logging.getLogger(__name__ + '.ListUser') - def get_parser(self, prog_name): parser = super(ListUser, self).get_parser(prog_name) parser.add_argument( @@ -205,7 +194,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity @@ -280,8 +268,6 @@ def take_action(self, parsed_args): class SetUser(command.Command): """Set user properties""" - log = logging.getLogger(__name__ + '.SetUser') - def get_parser(self, prog_name): parser = super(SetUser, self).get_parser(prog_name) parser.add_argument( @@ -334,7 +320,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity @@ -383,8 +368,6 @@ def take_action(self, parsed_args): class SetPasswordUser(command.Command): """Change current user password""" - log = logging.getLogger(__name__ + '.SetPasswordUser') - def get_parser(self, prog_name): parser = super(SetPasswordUser, self).get_parser(prog_name) parser.add_argument( @@ -399,7 +382,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity @@ -436,11 +418,9 @@ def take_action(self, parsed_args): identity_client.users.update_password(current_password, password) -class ShowUser(show.ShowOne): +class ShowUser(command.ShowOne): """Display user details""" - log = logging.getLogger(__name__ + '.ShowUser') - def get_parser(self, prog_name): parser = super(ShowUser, self).get_parser(prog_name) parser.add_argument( @@ -455,7 +435,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index c18f3fc79d..9cc5facd43 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -17,7 +17,6 @@ import argparse import io -import logging import os import six import sys @@ -27,12 +26,9 @@ else: msvcrt = None -from cliff import command -from cliff import lister -from cliff import show - from glanceclient.common import utils as gc_utils from openstackclient.api import utils as api_utils +from openstackclient.common import command from openstackclient.common import parseractions from openstackclient.common import utils from openstackclient.i18n import _ # noqa @@ -57,11 +53,9 @@ def _format_visibility(data): return 'private' -class CreateImage(show.ShowOne): +class CreateImage(command.ShowOne): """Create/upload an image""" - log = logging.getLogger(__name__ + ".CreateImage") - def get_parser(self, prog_name): parser = super(CreateImage, self).get_parser(prog_name) parser.add_argument( @@ -191,7 +185,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) image_client = self.app.client_manager.image if getattr(parsed_args, 'owner', None) is not None: @@ -283,8 +276,6 @@ def take_action(self, parsed_args): class DeleteImage(command.Command): """Delete image(s)""" - log = logging.getLogger(__name__ + ".DeleteImage") - def get_parser(self, prog_name): parser = super(DeleteImage, self).get_parser(prog_name) parser.add_argument( @@ -296,8 +287,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) - image_client = self.app.client_manager.image for image in parsed_args.images: image_obj = utils.find_resource( @@ -307,11 +296,9 @@ def take_action(self, parsed_args): image_client.images.delete(image_obj.id) -class ListImage(lister.Lister): +class ListImage(command.Lister): """List available images""" - log = logging.getLogger(__name__ + ".ListImage") - def get_parser(self, prog_name): parser = super(ListImage, self).get_parser(prog_name) public_group = parser.add_mutually_exclusive_group() @@ -367,8 +354,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) - image_client = self.app.client_manager.image kwargs = {} @@ -452,8 +437,6 @@ def take_action(self, parsed_args): class SaveImage(command.Command): """Save an image locally""" - log = logging.getLogger(__name__ + ".SaveImage") - def get_parser(self, prog_name): parser = super(SaveImage, self).get_parser(prog_name) parser.add_argument( @@ -469,8 +452,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) - image_client = self.app.client_manager.image image = utils.find_resource( image_client.images, @@ -484,8 +465,6 @@ def take_action(self, parsed_args): class SetImage(command.Command): """Set image properties""" - log = logging.getLogger(__name__ + ".SetImage") - def get_parser(self, prog_name): parser = super(SetImage, self).get_parser(prog_name) parser.add_argument( @@ -624,7 +603,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) image_client = self.app.client_manager.image if getattr(parsed_args, 'owner', None) is not None: @@ -723,11 +701,9 @@ def take_action(self, parsed_args): kwargs['data'].close() -class ShowImage(show.ShowOne): +class ShowImage(command.ShowOne): """Display image details""" - log = logging.getLogger(__name__ + ".ShowImage") - def get_parser(self, prog_name): parser = super(ShowImage, self).get_parser(prog_name) parser.add_argument( @@ -738,8 +714,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) - image_client = self.app.client_manager.image image = utils.find_resource( image_client.images, diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 123599ede0..39a5319593 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -16,15 +16,12 @@ """Image V2 Action Implementations""" import argparse -import logging import six -from cliff import command -from cliff import lister -from cliff import show from glanceclient.common import utils as gc_utils from openstackclient.api import utils as api_utils +from openstackclient.common import command from openstackclient.common import exceptions from openstackclient.common import parseractions from openstackclient.common import utils @@ -67,11 +64,9 @@ def _format_image(image): return info -class AddProjectToImage(show.ShowOne): +class AddProjectToImage(command.ShowOne): """Associate project with image""" - log = logging.getLogger(__name__ + ".AddProjectToImage") - def get_parser(self, prog_name): parser = super(AddProjectToImage, self).get_parser(prog_name) parser.add_argument( @@ -88,8 +83,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) - image_client = self.app.client_manager.image identity_client = self.app.client_manager.identity @@ -109,10 +102,9 @@ def take_action(self, parsed_args): return zip(*sorted(six.iteritems(image_member))) -class CreateImage(show.ShowOne): +class CreateImage(command.ShowOne): """Create/upload an image""" - log = logging.getLogger(__name__ + ".CreateImage") deadopts = ('size', 'location', 'copy-from', 'checksum', 'store') def get_parser(self, prog_name): @@ -241,7 +233,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) identity_client = self.app.client_manager.identity image_client = self.app.client_manager.image @@ -366,8 +357,6 @@ def take_action(self, parsed_args): class DeleteImage(command.Command): """Delete image(s)""" - log = logging.getLogger(__name__ + ".DeleteImage") - def get_parser(self, prog_name): parser = super(DeleteImage, self).get_parser(prog_name) parser.add_argument( @@ -379,8 +368,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) - image_client = self.app.client_manager.image for image in parsed_args.images: image_obj = utils.find_resource( @@ -390,11 +377,9 @@ def take_action(self, parsed_args): image_client.images.delete(image_obj.id) -class ListImage(lister.Lister): +class ListImage(command.Lister): """List available images""" - log = logging.getLogger(__name__ + ".ListImage") - def get_parser(self, prog_name): parser = super(ListImage, self).get_parser(prog_name) public_group = parser.add_mutually_exclusive_group() @@ -449,8 +434,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) - image_client = self.app.client_manager.image kwargs = {} @@ -529,8 +512,6 @@ def take_action(self, parsed_args): class RemoveProjectImage(command.Command): """Disassociate project with image""" - log = logging.getLogger(__name__ + ".RemoveProjectImage") - def get_parser(self, prog_name): parser = super(RemoveProjectImage, self).get_parser(prog_name) parser.add_argument( @@ -547,8 +528,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) - image_client = self.app.client_manager.image identity_client = self.app.client_manager.identity @@ -566,8 +545,6 @@ def take_action(self, parsed_args): class SaveImage(command.Command): """Save an image locally""" - log = logging.getLogger(__name__ + ".SaveImage") - def get_parser(self, prog_name): parser = super(SaveImage, self).get_parser(prog_name) parser.add_argument( @@ -583,8 +560,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) - image_client = self.app.client_manager.image image = utils.find_resource( image_client.images, @@ -598,7 +573,6 @@ def take_action(self, parsed_args): class SetImage(command.Command): """Set image properties""" - log = logging.getLogger(__name__ + ".SetImage") deadopts = ('visibility',) def get_parser(self, prog_name): @@ -758,7 +732,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) identity_client = self.app.client_manager.identity image_client = self.app.client_manager.image @@ -848,11 +821,9 @@ def take_action(self, parsed_args): raise e -class ShowImage(show.ShowOne): +class ShowImage(command.ShowOne): """Display image details""" - log = logging.getLogger(__name__ + ".ShowImage") - def get_parser(self, prog_name): parser = super(ShowImage, self).get_parser(prog_name) parser.add_argument( @@ -863,8 +834,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) - image_client = self.app.client_manager.image image = utils.find_resource( image_client.images, diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 7d9324f085..fc94fd8277 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -13,12 +13,7 @@ """Network action implementations""" -import logging - -from cliff import command -from cliff import lister -from cliff import show - +from openstackclient.common import command from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.identity import common as identity_common @@ -52,11 +47,9 @@ def _get_columns(item): return tuple(sorted(columns)) -class CreateNetwork(show.ShowOne): +class CreateNetwork(command.ShowOne): """Create new network""" - log = logging.getLogger(__name__ + '.CreateNetwork') - def get_parser(self, prog_name): parser = super(CreateNetwork, self).get_parser(prog_name) parser.add_argument( @@ -111,7 +104,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) client = self.app.client_manager.network body = self.get_body(parsed_args) obj = client.create_network(**body) @@ -142,8 +134,6 @@ def get_body(self, parsed_args): class DeleteNetwork(command.Command): """Delete network(s)""" - log = logging.getLogger(__name__ + '.DeleteNetwork') - def get_parser(self, prog_name): parser = super(DeleteNetwork, self).get_parser(prog_name) parser.add_argument( @@ -155,18 +145,15 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) client = self.app.client_manager.network for network in parsed_args.network: obj = client.find_network(network) client.delete_network(obj) -class ListNetwork(lister.Lister): +class ListNetwork(command.Lister): """List networks""" - log = logging.getLogger(__name__ + '.ListNetwork') - def get_parser(self, prog_name): parser = super(ListNetwork, self).get_parser(prog_name) parser.add_argument( @@ -184,7 +171,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) client = self.app.client_manager.network if parsed_args.long: @@ -239,8 +225,6 @@ def take_action(self, parsed_args): class SetNetwork(command.Command): """Set network properties""" - log = logging.getLogger(__name__ + '.SetNetwork') - def get_parser(self, prog_name): parser = super(SetNetwork, self).get_parser(prog_name) parser.add_argument( @@ -284,7 +268,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) client = self.app.client_manager.network obj = client.find_network(parsed_args.identifier, ignore_missing=False) @@ -302,11 +285,9 @@ def take_action(self, parsed_args): client.update_network(obj) -class ShowNetwork(show.ShowOne): +class ShowNetwork(command.ShowOne): """Show network details""" - log = logging.getLogger(__name__ + '.ShowNetwork') - def get_parser(self, prog_name): parser = super(ShowNetwork, self).get_parser(prog_name) parser.add_argument( @@ -317,7 +298,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) client = self.app.client_manager.network obj = client.find_network(parsed_args.identifier, ignore_missing=False) columns = _get_columns(obj) diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index ad906a287a..b91ac58899 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -13,16 +13,12 @@ """Port action implementations""" -import logging - from cliff import command class DeletePort(command.Command): """Delete port(s)""" - log = logging.getLogger(__name__ + '.DeletePort') - def get_parser(self, prog_name): parser = super(DeletePort, self).get_parser(prog_name) parser.add_argument( @@ -34,7 +30,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) client = self.app.client_manager.network for port in parsed_args.port: diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 09e0fe4c84..6c8acb6315 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -14,12 +14,8 @@ """Router action implementations""" import json -import logging - -from cliff import command -from cliff import lister -from cliff import show +from openstackclient.common import command from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.identity import common as identity_common @@ -67,11 +63,9 @@ def _get_attrs(client_manager, parsed_args): return attrs -class CreateRouter(show.ShowOne): +class CreateRouter(command.ShowOne): """Create a new router""" - log = logging.getLogger(__name__ + '.CreateRouter') - def get_parser(self, prog_name): parser = super(CreateRouter, self).get_parser(prog_name) parser.add_argument( @@ -109,7 +103,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) client = self.app.client_manager.network attrs = _get_attrs(self.app.client_manager, parsed_args) @@ -128,8 +121,6 @@ def take_action(self, parsed_args): class DeleteRouter(command.Command): """Delete router(s)""" - log = logging.getLogger(__name__ + '.DeleteRouter') - def get_parser(self, prog_name): parser = super(DeleteRouter, self).get_parser(prog_name) parser.add_argument( @@ -141,18 +132,15 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) client = self.app.client_manager.network for router in parsed_args.router: obj = client.find_router(router) client.delete_router(obj) -class ListRouter(lister.Lister): +class ListRouter(command.Lister): """List routers""" - log = logging.getLogger(__name__ + '.ListRouter') - def get_parser(self, prog_name): parser = super(ListRouter, self).get_parser(prog_name) parser.add_argument( @@ -164,7 +152,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) client = self.app.client_manager.network columns = ( @@ -206,8 +193,6 @@ def take_action(self, parsed_args): class SetRouter(command.Command): """Set router properties""" - log = logging.getLogger(__name__ + '.SetRouter') - def get_parser(self, prog_name): parser = super(SetRouter, self).get_parser(prog_name) parser.add_argument( @@ -262,7 +247,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) client = self.app.client_manager.network obj = client.find_router(parsed_args.router, ignore_missing=False) @@ -274,11 +258,9 @@ def take_action(self, parsed_args): client.update_router(obj, **attrs) -class ShowRouter(show.ShowOne): +class ShowRouter(command.ShowOne): """Display router details""" - log = logging.getLogger(__name__ + '.ShowRouter') - def get_parser(self, prog_name): parser = super(ShowRouter, self).get_parser(prog_name) parser.add_argument( @@ -289,7 +271,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) client = self.app.client_manager.network obj = client.find_router(parsed_args.router, ignore_missing=False) columns = sorted(obj.keys()) diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 627471252a..467b6507b8 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -13,8 +13,6 @@ """Subnet action implementations""" -import logging - from cliff import lister from openstackclient.common import utils @@ -36,8 +34,6 @@ def _format_allocation_pools(data): class ListSubnet(lister.Lister): """List subnets""" - log = logging.getLogger(__name__ + '.ListSubnet') - def get_parser(self, prog_name): parser = super(ListSubnet, self).get_parser(prog_name) parser.add_argument( @@ -49,8 +45,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) - data = self.app.client_manager.network.subnets() headers = ('ID', 'Name', 'Network', 'Subnet') diff --git a/openstackclient/object/v1/account.py b/openstackclient/object/v1/account.py index aa94ff5c2c..543ce4f3a0 100644 --- a/openstackclient/object/v1/account.py +++ b/openstackclient/object/v1/account.py @@ -13,12 +13,9 @@ """Account v1 action implementations""" -import logging - -from cliff import command -from cliff import show import six +from openstackclient.common import command from openstackclient.common import parseractions from openstackclient.common import utils @@ -26,8 +23,6 @@ class SetAccount(command.Command): """Set account properties""" - log = logging.getLogger(__name__ + '.SetAccount') - def get_parser(self, prog_name): parser = super(SetAccount, self).get_parser(prog_name) parser.add_argument( @@ -40,19 +35,15 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): self.app.client_manager.object_store.account_set( properties=parsed_args.property, ) -class ShowAccount(show.ShowOne): +class ShowAccount(command.ShowOne): """Display account details""" - log = logging.getLogger(__name__ + '.ShowAccount') - - @utils.log_method(log) def take_action(self, parsed_args): data = self.app.client_manager.object_store.account_show() if 'properties' in data: @@ -63,8 +54,6 @@ def take_action(self, parsed_args): class UnsetAccount(command.Command): """Unset account properties""" - log = logging.getLogger(__name__ + '.UnsetAccount') - def get_parser(self, prog_name): parser = super(UnsetAccount, self).get_parser(prog_name) parser.add_argument( @@ -78,7 +67,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): self.app.client_manager.object_store.account_unset( properties=parsed_args.property, diff --git a/openstackclient/object/v1/container.py b/openstackclient/object/v1/container.py index 8c8844e2b9..e70afd9d0e 100644 --- a/openstackclient/object/v1/container.py +++ b/openstackclient/object/v1/container.py @@ -16,22 +16,16 @@ """Container v1 action implementations""" -import logging import six -from cliff import command -from cliff import lister -from cliff import show - +from openstackclient.common import command from openstackclient.common import parseractions from openstackclient.common import utils -class CreateContainer(lister.Lister): +class CreateContainer(command.Lister): """Create new container""" - log = logging.getLogger(__name__ + '.CreateContainer') - def get_parser(self, prog_name): parser = super(CreateContainer, self).get_parser(prog_name) parser.add_argument( @@ -42,7 +36,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): results = [] @@ -63,8 +56,6 @@ def take_action(self, parsed_args): class DeleteContainer(command.Command): """Delete container""" - log = logging.getLogger(__name__ + '.DeleteContainer') - def get_parser(self, prog_name): parser = super(DeleteContainer, self).get_parser(prog_name) parser.add_argument( @@ -75,7 +66,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): for container in parsed_args.containers: @@ -84,11 +74,9 @@ def take_action(self, parsed_args): ) -class ListContainer(lister.Lister): +class ListContainer(command.Lister): """List containers""" - log = logging.getLogger(__name__ + '.ListContainer') - def get_parser(self, prog_name): parser = super(ListContainer, self).get_parser(prog_name) parser.add_argument( @@ -126,7 +114,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): if parsed_args.long: @@ -160,8 +147,6 @@ def take_action(self, parsed_args): class SaveContainer(command.Command): """Save container contents locally""" - log = logging.getLogger(__name__ + ".SaveContainer") - def get_parser(self, prog_name): parser = super(SaveContainer, self).get_parser(prog_name) parser.add_argument( @@ -172,8 +157,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) - self.app.client_manager.object_store.container_save( container=parsed_args.container, ) @@ -182,8 +165,6 @@ def take_action(self, parsed_args): class SetContainer(command.Command): """Set container properties""" - log = logging.getLogger(__name__ + '.SetContainer') - def get_parser(self, prog_name): parser = super(SetContainer, self).get_parser(prog_name) parser.add_argument( @@ -201,7 +182,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): self.app.client_manager.object_store.container_set( parsed_args.container, @@ -209,11 +189,9 @@ def take_action(self, parsed_args): ) -class ShowContainer(show.ShowOne): +class ShowContainer(command.ShowOne): """Display container details""" - log = logging.getLogger(__name__ + '.ShowContainer') - def get_parser(self, prog_name): parser = super(ShowContainer, self).get_parser(prog_name) parser.add_argument( @@ -223,7 +201,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): data = self.app.client_manager.object_store.container_show( @@ -238,8 +215,6 @@ def take_action(self, parsed_args): class UnsetContainer(command.Command): """Unset container properties""" - log = logging.getLogger(__name__ + '.UnsetContainer') - def get_parser(self, prog_name): parser = super(UnsetContainer, self).get_parser(prog_name) parser.add_argument( @@ -258,7 +233,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): self.app.client_manager.object_store.container_unset( parsed_args.container, diff --git a/openstackclient/object/v1/object.py b/openstackclient/object/v1/object.py index 4bd06124d6..f9a55e9c96 100644 --- a/openstackclient/object/v1/object.py +++ b/openstackclient/object/v1/object.py @@ -16,22 +16,16 @@ """Object v1 action implementations""" -import logging import six -from cliff import command -from cliff import lister -from cliff import show - +from openstackclient.common import command from openstackclient.common import parseractions from openstackclient.common import utils -class CreateObject(lister.Lister): +class CreateObject(command.Lister): """Upload object to container""" - log = logging.getLogger(__name__ + '.CreateObject') - def get_parser(self, prog_name): parser = super(CreateObject, self).get_parser(prog_name) parser.add_argument( @@ -47,7 +41,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): results = [] @@ -69,8 +62,6 @@ def take_action(self, parsed_args): class DeleteObject(command.Command): """Delete object from container""" - log = logging.getLogger(__name__ + '.DeleteObject') - def get_parser(self, prog_name): parser = super(DeleteObject, self).get_parser(prog_name) parser.add_argument( @@ -86,7 +77,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): for obj in parsed_args.objects: @@ -96,11 +86,9 @@ def take_action(self, parsed_args): ) -class ListObject(lister.Lister): +class ListObject(command.Lister): """List objects""" - log = logging.getLogger(__name__ + '.ListObject') - def get_parser(self, prog_name): parser = super(ListObject, self).get_parser(prog_name) parser.add_argument( @@ -148,7 +136,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): if parsed_args.long: @@ -191,8 +178,6 @@ def take_action(self, parsed_args): class SaveObject(command.Command): """Save object locally""" - log = logging.getLogger(__name__ + ".SaveObject") - def get_parser(self, prog_name): parser = super(SaveObject, self).get_parser(prog_name) parser.add_argument( @@ -213,8 +198,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) - self.app.client_manager.object_store.object_save( container=parsed_args.container, object=parsed_args.object, @@ -225,8 +208,6 @@ def take_action(self, parsed_args): class SetObject(command.Command): """Set object properties""" - log = logging.getLogger(__name__ + '.SetObject') - def get_parser(self, prog_name): parser = super(SetObject, self).get_parser(prog_name) parser.add_argument( @@ -249,7 +230,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): self.app.client_manager.object_store.object_set( parsed_args.container, @@ -258,11 +238,9 @@ def take_action(self, parsed_args): ) -class ShowObject(show.ShowOne): +class ShowObject(command.ShowOne): """Display object details""" - log = logging.getLogger(__name__ + '.ShowObject') - def get_parser(self, prog_name): parser = super(ShowObject, self).get_parser(prog_name) parser.add_argument( @@ -277,7 +255,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): data = self.app.client_manager.object_store.object_show( @@ -293,8 +270,6 @@ def take_action(self, parsed_args): class UnsetObject(command.Command): """Unset object properties""" - log = logging.getLogger(__name__ + '.UnsetObject') - def get_parser(self, prog_name): parser = super(UnsetObject, self).get_parser(prog_name) parser.add_argument( @@ -318,7 +293,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): self.app.client_manager.object_store.object_unset( parsed_args.container, diff --git a/openstackclient/volume/v1/backup.py b/openstackclient/volume/v1/backup.py index 4f2ff8bb16..32f39fb539 100644 --- a/openstackclient/volume/v1/backup.py +++ b/openstackclient/volume/v1/backup.py @@ -16,21 +16,15 @@ """Volume v1 Backup action implementations""" import copy -import logging import six -from cliff import command -from cliff import lister -from cliff import show - +from openstackclient.common import command from openstackclient.common import utils -class CreateBackup(show.ShowOne): +class CreateBackup(command.ShowOne): """Create new backup""" - log = logging.getLogger(__name__ + '.CreateBackup') - def get_parser(self, prog_name): parser = super(CreateBackup, self).get_parser(prog_name) parser.add_argument( @@ -57,7 +51,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): volume_client = self.app.client_manager.volume volume_id = utils.find_resource(volume_client.volumes, @@ -76,8 +69,6 @@ def take_action(self, parsed_args): class DeleteBackup(command.Command): """Delete backup(s)""" - log = logging.getLogger(__name__ + '.DeleteBackup') - def get_parser(self, prog_name): parser = super(DeleteBackup, self).get_parser(prog_name) parser.add_argument( @@ -88,7 +79,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): volume_client = self.app.client_manager.volume for backup in parsed_args.backups: @@ -97,11 +87,9 @@ def take_action(self, parsed_args): volume_client.backups.delete(backup_id) -class ListBackup(lister.Lister): +class ListBackup(command.Lister): """List backups""" - log = logging.getLogger(__name__ + '.ListBackup') - def get_parser(self, prog_name): parser = super(ListBackup, self).get_parser(prog_name) parser.add_argument( @@ -112,7 +100,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): def _format_volume_id(volume_id): @@ -157,8 +144,6 @@ def _format_volume_id(volume_id): class RestoreBackup(command.Command): """Restore backup""" - log = logging.getLogger(__name__ + '.RestoreBackup') - def get_parser(self, prog_name): parser = super(RestoreBackup, self).get_parser(prog_name) parser.add_argument( @@ -171,7 +156,6 @@ def get_parser(self, prog_name): help='Volume to restore to (name or ID)') return parser - @utils.log_method(log) def take_action(self, parsed_args): volume_client = self.app.client_manager.volume backup = utils.find_resource(volume_client.backups, @@ -182,11 +166,9 @@ def take_action(self, parsed_args): destination_volume.id) -class ShowBackup(show.ShowOne): +class ShowBackup(command.ShowOne): """Display backup details""" - log = logging.getLogger(__name__ + '.ShowBackup') - def get_parser(self, prog_name): parser = super(ShowBackup, self).get_parser(prog_name) parser.add_argument( @@ -195,7 +177,6 @@ def get_parser(self, prog_name): help='Backup to display (ID only)') return parser - @utils.log_method(log) def take_action(self, parsed_args): volume_client = self.app.client_manager.volume backup = utils.find_resource(volume_client.backups, diff --git a/openstackclient/volume/v1/qos_specs.py b/openstackclient/volume/v1/qos_specs.py index 73e70a21fb..826e5c4938 100644 --- a/openstackclient/volume/v1/qos_specs.py +++ b/openstackclient/volume/v1/qos_specs.py @@ -15,13 +15,9 @@ """Volume v1 QoS action implementations""" -import logging import six -from cliff import command -from cliff import lister -from cliff import show - +from openstackclient.common import command from openstackclient.common import parseractions from openstackclient.common import utils @@ -29,8 +25,6 @@ class AssociateQos(command.Command): """Associate a QoS specification to a volume type""" - log = logging.getLogger(__name__ + '.AssociateQos') - def get_parser(self, prog_name): parser = super(AssociateQos, self).get_parser(prog_name) parser.add_argument( @@ -45,7 +39,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): volume_client = self.app.client_manager.volume qos_spec = utils.find_resource(volume_client.qos_specs, @@ -56,11 +49,9 @@ def take_action(self, parsed_args): volume_client.qos_specs.associate(qos_spec.id, volume_type.id) -class CreateQos(show.ShowOne): +class CreateQos(command.ShowOne): """Create new QoS specification""" - log = logging.getLogger(__name__ + '.CreateQos') - def get_parser(self, prog_name): parser = super(CreateQos, self).get_parser(prog_name) parser.add_argument( @@ -86,7 +77,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): volume_client = self.app.client_manager.volume specs = {} @@ -103,8 +93,6 @@ def take_action(self, parsed_args): class DeleteQos(command.Command): """Delete QoS specification""" - log = logging.getLogger(__name__ + '.DeleteQos') - def get_parser(self, prog_name): parser = super(DeleteQos, self).get_parser(prog_name) parser.add_argument( @@ -115,7 +103,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): volume_client = self.app.client_manager.volume for qos in parsed_args.qos_specs: @@ -126,8 +113,6 @@ def take_action(self, parsed_args): class DisassociateQos(command.Command): """Disassociate a QoS specification from a volume type""" - log = logging.getLogger(__name__ + '.DisassociateQos') - def get_parser(self, prog_name): parser = super(DisassociateQos, self).get_parser(prog_name) parser.add_argument( @@ -150,7 +135,6 @@ def get_parser(self, prog_name): return parser - @utils.log_method(log) def take_action(self, parsed_args): volume_client = self.app.client_manager.volume qos_spec = utils.find_resource(volume_client.qos_specs, @@ -164,12 +148,9 @@ def take_action(self, parsed_args): volume_client.qos_specs.disassociate_all(qos_spec.id) -class ListQos(lister.Lister): +class ListQos(command.Lister): """List QoS specifications""" - log = logging.getLogger(__name__ + '.ListQos') - - @utils.log_method(log) def take_action(self, parsed_args): volume_client = self.app.client_manager.volume qos_specs_list = volume_client.qos_specs.list() @@ -195,8 +176,6 @@ def take_action(self, parsed_args): class SetQos(command.Command): """Set QoS specification properties""" - log = logging.getLogger(__name__ + '.SetQos') - def get_parser(self, prog_name): parser = super(SetQos, self).get_parser(prog_name) parser.add_argument( @@ -213,7 +192,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): volume_client = self.app.client_manager.volume qos_spec = utils.find_resource(volume_client.qos_specs, @@ -226,11 +204,9 @@ def take_action(self, parsed_args): self.app.log.error("No changes requested\n") -class ShowQos(show.ShowOne): +class ShowQos(command.ShowOne): """Display QoS specification details""" - log = logging.getLogger(__name__ + '.ShowQos') - def get_parser(self, prog_name): parser = super(ShowQos, self).get_parser(prog_name) parser.add_argument( @@ -240,7 +216,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): volume_client = self.app.client_manager.volume qos_spec = utils.find_resource(volume_client.qos_specs, @@ -261,8 +236,6 @@ def take_action(self, parsed_args): class UnsetQos(command.Command): """Unset QoS specification properties""" - log = logging.getLogger(__name__ + '.SetQos') - def get_parser(self, prog_name): parser = super(UnsetQos, self).get_parser(prog_name) parser.add_argument( @@ -280,7 +253,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): volume_client = self.app.client_manager.volume qos_spec = utils.find_resource(volume_client.qos_specs, diff --git a/openstackclient/volume/v1/snapshot.py b/openstackclient/volume/v1/snapshot.py index 24379a9a93..95200e4074 100644 --- a/openstackclient/volume/v1/snapshot.py +++ b/openstackclient/volume/v1/snapshot.py @@ -16,22 +16,16 @@ """Volume v1 Snapshot action implementations""" import copy -import logging import six -from cliff import command -from cliff import lister -from cliff import show - +from openstackclient.common import command from openstackclient.common import parseractions from openstackclient.common import utils -class CreateSnapshot(show.ShowOne): +class CreateSnapshot(command.ShowOne): """Create new snapshot""" - log = logging.getLogger(__name__ + '.CreateSnapshot') - def get_parser(self, prog_name): parser = super(CreateSnapshot, self).get_parser(prog_name) parser.add_argument( @@ -59,7 +53,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): volume_client = self.app.client_manager.volume volume_id = utils.find_resource(volume_client.volumes, @@ -81,8 +74,6 @@ def take_action(self, parsed_args): class DeleteSnapshot(command.Command): """Delete snapshot(s)""" - log = logging.getLogger(__name__ + '.DeleteSnapshot') - def get_parser(self, prog_name): parser = super(DeleteSnapshot, self).get_parser(prog_name) parser.add_argument( @@ -93,7 +84,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): volume_client = self.app.client_manager.volume for snapshot in parsed_args.snapshots: @@ -102,11 +92,9 @@ def take_action(self, parsed_args): volume_client.volume_snapshots.delete(snapshot_id) -class ListSnapshot(lister.Lister): +class ListSnapshot(command.Lister): """List snapshots""" - log = logging.getLogger(__name__ + '.ListSnapshot') - def get_parser(self, prog_name): parser = super(ListSnapshot, self).get_parser(prog_name) parser.add_argument( @@ -123,7 +111,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): def _format_volume_id(volume_id): @@ -179,8 +166,6 @@ def _format_volume_id(volume_id): class SetSnapshot(command.Command): """Set snapshot properties""" - log = logging.getLogger(__name__ + '.SetSnapshot') - def get_parser(self, prog_name): parser = super(SetSnapshot, self).get_parser(prog_name) parser.add_argument( @@ -204,7 +189,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): volume_client = self.app.client_manager.volume snapshot = utils.find_resource(volume_client.volume_snapshots, @@ -227,11 +211,9 @@ def take_action(self, parsed_args): snapshot.update(**kwargs) -class ShowSnapshot(show.ShowOne): +class ShowSnapshot(command.ShowOne): """Display snapshot details""" - log = logging.getLogger(__name__ + '.ShowSnapshot') - def get_parser(self, prog_name): parser = super(ShowSnapshot, self).get_parser(prog_name) parser.add_argument( @@ -240,7 +222,6 @@ def get_parser(self, prog_name): help='Snapshot to display (name or ID)') return parser - @utils.log_method(log) def take_action(self, parsed_args): volume_client = self.app.client_manager.volume snapshot = utils.find_resource(volume_client.volume_snapshots, @@ -256,8 +237,6 @@ def take_action(self, parsed_args): class UnsetSnapshot(command.Command): """Unset snapshot properties""" - log = logging.getLogger(__name__ + '.UnsetSnapshot') - def get_parser(self, prog_name): parser = super(UnsetSnapshot, self).get_parser(prog_name) parser.add_argument( @@ -276,7 +255,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): volume_client = self.app.client_manager.volume snapshot = utils.find_resource( diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index 17b6c9c856..90827d2041 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -16,22 +16,16 @@ """Volume v1 Volume action implementations""" import argparse -import logging import six -from cliff import command -from cliff import lister -from cliff import show - +from openstackclient.common import command from openstackclient.common import parseractions from openstackclient.common import utils -class CreateVolume(show.ShowOne): +class CreateVolume(command.ShowOne): """Create new volume""" - log = logging.getLogger(__name__ + '.CreateVolume') - def get_parser(self, prog_name): parser = super(CreateVolume, self).get_parser(prog_name) parser.add_argument( @@ -102,7 +96,6 @@ def get_parser(self, prog_name): return parser - @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity @@ -166,8 +159,6 @@ def take_action(self, parsed_args): class DeleteVolume(command.Command): """Delete volume(s)""" - log = logging.getLogger(__name__ + '.DeleteVolume') - def get_parser(self, prog_name): parser = super(DeleteVolume, self).get_parser(prog_name) parser.add_argument( @@ -186,7 +177,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): volume_client = self.app.client_manager.volume for volume in parsed_args.volumes: @@ -198,11 +188,9 @@ def take_action(self, parsed_args): volume_client.volumes.delete(volume_obj.id) -class ListVolume(lister.Lister): +class ListVolume(command.Lister): """List volumes""" - log = logging.getLogger(__name__ + '.ListVolume') - def get_parser(self, prog_name): parser = super(ListVolume, self).get_parser(prog_name) parser.add_argument( @@ -229,7 +217,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): volume_client = self.app.client_manager.volume @@ -316,8 +303,6 @@ def _format_attach(attachments): class SetVolume(command.Command): """Set volume properties""" - log = logging.getLogger(__name__ + '.SetVolume') - def get_parser(self, prog_name): parser = super(SetVolume, self).get_parser(prog_name) parser.add_argument( @@ -350,7 +335,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): volume_client = self.app.client_manager.volume volume = utils.find_resource(volume_client.volumes, parsed_args.volume) @@ -382,11 +366,9 @@ def take_action(self, parsed_args): self.app.log.error("No changes requested\n") -class ShowVolume(show.ShowOne): +class ShowVolume(command.ShowOne): """Show volume details""" - log = logging.getLogger(__name__ + '.ShowVolume') - def get_parser(self, prog_name): parser = super(ShowVolume, self).get_parser(prog_name) parser.add_argument( @@ -396,7 +378,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): volume_client = self.app.client_manager.volume volume = utils.find_resource(volume_client.volumes, parsed_args.volume) @@ -418,8 +399,6 @@ def take_action(self, parsed_args): class UnsetVolume(command.Command): """Unset volume properties""" - log = logging.getLogger(__name__ + '.UnsetVolume') - def get_parser(self, prog_name): parser = super(UnsetVolume, self).get_parser(prog_name) parser.add_argument( @@ -438,7 +417,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): volume_client = self.app.client_manager.volume volume = utils.find_resource( diff --git a/openstackclient/volume/v1/volume_type.py b/openstackclient/volume/v1/volume_type.py index b664adfba6..24d0b235ff 100644 --- a/openstackclient/volume/v1/volume_type.py +++ b/openstackclient/volume/v1/volume_type.py @@ -15,22 +15,16 @@ """Volume v1 Type action implementations""" -import logging import six -from cliff import command -from cliff import lister -from cliff import show - +from openstackclient.common import command from openstackclient.common import parseractions from openstackclient.common import utils -class CreateVolumeType(show.ShowOne): +class CreateVolumeType(command.ShowOne): """Create new volume type""" - log = logging.getLogger(__name__ + '.CreateVolumeType') - def get_parser(self, prog_name): parser = super(CreateVolumeType, self).get_parser(prog_name) parser.add_argument( @@ -47,7 +41,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): volume_client = self.app.client_manager.volume volume_type = volume_client.volume_types.create(parsed_args.name) @@ -64,8 +57,6 @@ def take_action(self, parsed_args): class DeleteVolumeType(command.Command): """Delete volume type""" - log = logging.getLogger(__name__ + '.DeleteVolumeType') - def get_parser(self, prog_name): parser = super(DeleteVolumeType, self).get_parser(prog_name) parser.add_argument( @@ -75,7 +66,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): volume_client = self.app.client_manager.volume volume_type_id = utils.find_resource( @@ -83,11 +73,9 @@ def take_action(self, parsed_args): volume_client.volume_types.delete(volume_type_id) -class ListVolumeType(lister.Lister): +class ListVolumeType(command.Lister): """List volume types""" - log = logging.getLogger(__name__ + '.ListVolumeType') - def get_parser(self, prog_name): parser = super(ListVolumeType, self).get_parser(prog_name) parser.add_argument( @@ -97,7 +85,6 @@ def get_parser(self, prog_name): help='List additional fields in output') return parser - @utils.log_method(log) def take_action(self, parsed_args): if parsed_args.long: columns = ('ID', 'Name', 'Extra Specs') @@ -116,8 +103,6 @@ def take_action(self, parsed_args): class SetVolumeType(command.Command): """Set volume type properties""" - log = logging.getLogger(__name__ + '.SetVolumeType') - def get_parser(self, prog_name): parser = super(SetVolumeType, self).get_parser(prog_name) parser.add_argument( @@ -134,7 +119,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): volume_client = self.app.client_manager.volume volume_type = utils.find_resource( @@ -147,8 +131,6 @@ def take_action(self, parsed_args): class UnsetVolumeType(command.Command): """Unset volume type properties""" - log = logging.getLogger(__name__ + '.UnsetVolumeType') - def get_parser(self, prog_name): parser = super(UnsetVolumeType, self).get_parser(prog_name) parser.add_argument( @@ -167,7 +149,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): volume_client = self.app.client_manager.volume volume_type = utils.find_resource( @@ -181,11 +162,9 @@ def take_action(self, parsed_args): self.app.log.error("No changes requested\n") -class ShowVolumeType(show.ShowOne): +class ShowVolumeType(command.ShowOne): """Display volume type details""" - log = logging.getLogger(__name__ + ".ShowVolumeType") - def get_parser(self, prog_name): parser = super(ShowVolumeType, self).get_parser(prog_name) parser.add_argument( @@ -196,7 +175,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action: (%s)", parsed_args) volume_client = self.app.client_manager.volume volume_type = utils.find_resource( volume_client.volume_types, parsed_args.volume_type) diff --git a/openstackclient/volume/v2/backup.py b/openstackclient/volume/v2/backup.py index bc919d0bc5..64ca97ae8a 100644 --- a/openstackclient/volume/v2/backup.py +++ b/openstackclient/volume/v2/backup.py @@ -15,21 +15,16 @@ """Volume v2 Backup action implementations""" import copy -import logging -from cliff import command -from cliff import lister -from cliff import show import six +from openstackclient.common import command from openstackclient.common import utils -class CreateBackup(show.ShowOne): +class CreateBackup(command.ShowOne): """Create new backup""" - log = logging.getLogger(__name__ + ".CreateBackup") - def get_parser(self, prog_name): parser = super(CreateBackup, self).get_parser(prog_name) parser.add_argument( @@ -56,7 +51,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action: (%s)", parsed_args) volume_client = self.app.client_manager.volume volume_id = utils.find_resource( volume_client.volumes, parsed_args.volume).id @@ -73,8 +67,6 @@ def take_action(self, parsed_args): class DeleteBackup(command.Command): """Delete backup(s)""" - log = logging.getLogger(__name__ + ".DeleteBackup") - def get_parser(self, prog_name): parser = super(DeleteBackup, self).get_parser(prog_name) parser.add_argument( @@ -86,7 +78,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action: (%s)", parsed_args) volume_client = self.app.client_manager.volume for backup in parsed_args.backups: backup_id = utils.find_resource( @@ -94,11 +85,9 @@ def take_action(self, parsed_args): volume_client.backups.delete(backup_id) -class ListBackup(lister.Lister): +class ListBackup(command.Lister): """List backups""" - log = logging.getLogger(__name__ + ".ListBackup") - def get_parser(self, prog_name): parser = super(ListBackup, self).get_parser(prog_name) parser.add_argument( @@ -110,7 +99,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action: (%s)", parsed_args) def _format_volume_id(volume_id): """Return a volume name if available @@ -151,11 +139,9 @@ def _format_volume_id(volume_id): ) for s in data)) -class RestoreBackup(show.ShowOne): +class RestoreBackup(command.ShowOne): """Restore backup""" - log = logging.getLogger(__name__ + ".RestoreBackup") - def get_parser(self, prog_name): parser = super(RestoreBackup, self).get_parser(prog_name) parser.add_argument( @@ -171,7 +157,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action: (%s)", parsed_args) volume_client = self.app.client_manager.volume backup = utils.find_resource(volume_client.backups, parsed_args.backup) destination_volume = utils.find_resource(volume_client.volumes, @@ -179,11 +164,9 @@ def take_action(self, parsed_args): return volume_client.restores.restore(backup.id, destination_volume.id) -class ShowBackup(show.ShowOne): +class ShowBackup(command.ShowOne): """Display backup details""" - log = logging.getLogger(__name__ + ".ShowBackup") - def get_parser(self, prog_name): parser = super(ShowBackup, self).get_parser(prog_name) parser.add_argument( @@ -193,7 +176,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action: (%s)", parsed_args) volume_client = self.app.client_manager.volume backup = utils.find_resource(volume_client.backups, parsed_args.backup) diff --git a/openstackclient/volume/v2/qos_specs.py b/openstackclient/volume/v2/qos_specs.py index 678fde4f3c..961cc27e5c 100644 --- a/openstackclient/volume/v2/qos_specs.py +++ b/openstackclient/volume/v2/qos_specs.py @@ -15,13 +15,9 @@ """Volume v2 QoS action implementations""" -import logging import six -from cliff import command -from cliff import lister -from cliff import show - +from openstackclient.common import command from openstackclient.common import parseractions from openstackclient.common import utils @@ -29,8 +25,6 @@ class AssociateQos(command.Command): """Associate a QoS specification to a volume type""" - log = logging.getLogger(__name__ + '.AssociateQos') - def get_parser(self, prog_name): parser = super(AssociateQos, self).get_parser(prog_name) parser.add_argument( @@ -45,7 +39,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): volume_client = self.app.client_manager.volume qos_spec = utils.find_resource(volume_client.qos_specs, @@ -56,11 +49,9 @@ def take_action(self, parsed_args): volume_client.qos_specs.associate(qos_spec.id, volume_type.id) -class CreateQos(show.ShowOne): +class CreateQos(command.ShowOne): """Create new QoS specification""" - log = logging.getLogger(__name__ + '.CreateQos') - def get_parser(self, prog_name): parser = super(CreateQos, self).get_parser(prog_name) parser.add_argument( @@ -86,7 +77,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): volume_client = self.app.client_manager.volume specs = {} @@ -103,8 +93,6 @@ def take_action(self, parsed_args): class DeleteQos(command.Command): """Delete QoS specification""" - log = logging.getLogger(__name__ + '.DeleteQos') - def get_parser(self, prog_name): parser = super(DeleteQos, self).get_parser(prog_name) parser.add_argument( @@ -115,7 +103,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): volume_client = self.app.client_manager.volume for qos in parsed_args.qos_specs: @@ -126,8 +113,6 @@ def take_action(self, parsed_args): class DisassociateQos(command.Command): """Disassociate a QoS specification from a volume type""" - log = logging.getLogger(__name__ + '.DisassociateQos') - def get_parser(self, prog_name): parser = super(DisassociateQos, self).get_parser(prog_name) parser.add_argument( @@ -150,7 +135,6 @@ def get_parser(self, prog_name): return parser - @utils.log_method(log) def take_action(self, parsed_args): volume_client = self.app.client_manager.volume qos_spec = utils.find_resource(volume_client.qos_specs, @@ -164,12 +148,9 @@ def take_action(self, parsed_args): volume_client.qos_specs.disassociate_all(qos_spec.id) -class ListQos(lister.Lister): +class ListQos(command.Lister): """List QoS specifications""" - log = logging.getLogger(__name__ + '.ListQos') - - @utils.log_method(log) def take_action(self, parsed_args): volume_client = self.app.client_manager.volume qos_specs_list = volume_client.qos_specs.list() @@ -195,8 +176,6 @@ def take_action(self, parsed_args): class SetQos(command.Command): """Set QoS specification properties""" - log = logging.getLogger(__name__ + '.SetQos') - def get_parser(self, prog_name): parser = super(SetQos, self).get_parser(prog_name) parser.add_argument( @@ -213,7 +192,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): volume_client = self.app.client_manager.volume qos_spec = utils.find_resource(volume_client.qos_specs, @@ -226,11 +204,9 @@ def take_action(self, parsed_args): self.app.log.error("No changes requested\n") -class ShowQos(show.ShowOne): +class ShowQos(command.ShowOne): """Display QoS specification details""" - log = logging.getLogger(__name__ + '.ShowQos') - def get_parser(self, prog_name): parser = super(ShowQos, self).get_parser(prog_name) parser.add_argument( @@ -240,7 +216,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): volume_client = self.app.client_manager.volume qos_spec = utils.find_resource(volume_client.qos_specs, @@ -261,8 +236,6 @@ def take_action(self, parsed_args): class UnsetQos(command.Command): """Unset QoS specification properties""" - log = logging.getLogger(__name__ + '.SetQos') - def get_parser(self, prog_name): parser = super(UnsetQos, self).get_parser(prog_name) parser.add_argument( @@ -280,7 +253,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): volume_client = self.app.client_manager.volume qos_spec = utils.find_resource(volume_client.qos_specs, diff --git a/openstackclient/volume/v2/snapshot.py b/openstackclient/volume/v2/snapshot.py index f939a553f3..4d00b72623 100644 --- a/openstackclient/volume/v2/snapshot.py +++ b/openstackclient/volume/v2/snapshot.py @@ -15,22 +15,17 @@ """Volume v2 snapshot action implementations""" import copy -import logging -from cliff import command -from cliff import lister -from cliff import show import six +from openstackclient.common import command from openstackclient.common import parseractions from openstackclient.common import utils -class CreateSnapshot(show.ShowOne): +class CreateSnapshot(command.ShowOne): """Create new snapshot""" - log = logging.getLogger(__name__ + ".CreateSnapshot") - def get_parser(self, prog_name): parser = super(CreateSnapshot, self).get_parser(prog_name) parser.add_argument( @@ -59,7 +54,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action: (%s)", parsed_args) volume_client = self.app.client_manager.volume volume_id = utils.find_resource( volume_client.volumes, parsed_args.volume).id @@ -78,8 +72,6 @@ def take_action(self, parsed_args): class DeleteSnapshot(command.Command): """Delete volume snapshot(s)""" - log = logging.getLogger(__name__ + ".DeleteSnapshot") - def get_parser(self, prog_name): parser = super(DeleteSnapshot, self).get_parser(prog_name) parser.add_argument( @@ -91,7 +83,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action: (%s)", parsed_args) volume_client = self.app.client_manager.volume for snapshot in parsed_args.snapshots: snapshot_id = utils.find_resource( @@ -99,11 +90,9 @@ def take_action(self, parsed_args): volume_client.volume_snapshots.delete(snapshot_id) -class ListSnapshot(lister.Lister): +class ListSnapshot(command.Lister): """List snapshots""" - log = logging.getLogger(__name__ + ".ListSnapshot") - def get_parser(self, prog_name): parser = super(ListSnapshot, self).get_parser(prog_name) parser.add_argument( @@ -121,7 +110,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action: (%s)", parsed_args) def _format_volume_id(volume_id): """Return a volume name if available @@ -171,8 +159,6 @@ def _format_volume_id(volume_id): class SetSnapshot(command.Command): """Set snapshot properties""" - log = logging.getLogger(__name__ + '.SetSnapshot') - def get_parser(self, prog_name): parser = super(SetSnapshot, self).get_parser(prog_name) parser.add_argument( @@ -196,7 +182,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): volume_client = self.app.client_manager.volume snapshot = utils.find_resource(volume_client.volume_snapshots, @@ -218,11 +203,9 @@ def take_action(self, parsed_args): volume_client.volume_snapshots.update(snapshot.id, **kwargs) -class ShowSnapshot(show.ShowOne): +class ShowSnapshot(command.ShowOne): """Display snapshot details""" - log = logging.getLogger(__name__ + ".ShowSnapshot") - def get_parser(self, prog_name): parser = super(ShowSnapshot, self).get_parser(prog_name) parser.add_argument( @@ -233,7 +216,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action: (%s)", parsed_args) volume_client = self.app.client_manager.volume snapshot = utils.find_resource( volume_client.volume_snapshots, parsed_args.snapshot) @@ -246,8 +228,6 @@ def take_action(self, parsed_args): class UnsetSnapshot(command.Command): """Unset snapshot properties""" - log = logging.getLogger(__name__ + '.UnsetSnapshot') - def get_parser(self, prog_name): parser = super(UnsetSnapshot, self).get_parser(prog_name) parser.add_argument( @@ -265,7 +245,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): volume_client = self.app.client_manager.volume snapshot = utils.find_resource( diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index c636cf2fa1..436ec689dc 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -15,23 +15,18 @@ """Volume V2 Volume action implementations""" import copy -import logging -from cliff import command -from cliff import lister -from cliff import show import six +from openstackclient.common import command from openstackclient.common import parseractions from openstackclient.common import utils from openstackclient.identity import common as identity_common -class CreateVolume(show.ShowOne): +class CreateVolume(command.ShowOne): """Create new volume""" - log = logging.getLogger(__name__ + ".CreateVolume") - def get_parser(self, prog_name): parser = super(CreateVolume, self).get_parser(prog_name) parser.add_argument( @@ -96,8 +91,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action: (%s)", parsed_args) - identity_client = self.app.client_manager.identity volume_client = self.app.client_manager.volume image_client = self.app.client_manager.image @@ -159,8 +152,6 @@ def take_action(self, parsed_args): class DeleteVolume(command.Command): """Delete volume(s)""" - log = logging.getLogger(__name__ + ".DeleteVolume") - def get_parser(self, prog_name): parser = super(DeleteVolume, self).get_parser(prog_name) parser.add_argument( @@ -180,7 +171,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action: (%s)", parsed_args) volume_client = self.app.client_manager.volume for volume in parsed_args.volumes: volume_obj = utils.find_resource( @@ -191,11 +181,9 @@ def take_action(self, parsed_args): volume_client.volumes.delete(volume_obj.id) -class ListVolume(lister.Lister): +class ListVolume(command.Lister): """List volumes""" - log = logging.getLogger(__name__ + '.ListVolume') - def get_parser(self, prog_name): parser = super(ListVolume, self).get_parser(prog_name) parser.add_argument( @@ -234,7 +222,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): volume_client = self.app.client_manager.volume @@ -328,8 +315,6 @@ def _format_attach(attachments): class SetVolume(command.Command): """Set volume properties""" - log = logging.getLogger(__name__ + '.SetVolume') - def get_parser(self, prog_name): parser = super(SetVolume, self).get_parser(prog_name) parser.add_argument( @@ -362,7 +347,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): volume_client = self.app.client_manager.volume volume = utils.find_resource(volume_client.volumes, parsed_args.volume) @@ -394,11 +378,9 @@ def take_action(self, parsed_args): self.app.log.error("No changes requested\n") -class ShowVolume(show.ShowOne): +class ShowVolume(command.ShowOne): """Display volume details""" - log = logging.getLogger(__name__ + '.ShowVolume') - def get_parser(self, prog_name): parser = super(ShowVolume, self).get_parser(prog_name) parser.add_argument( @@ -408,7 +390,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): volume_client = self.app.client_manager.volume volume = utils.find_resource(volume_client.volumes, parsed_args.volume) @@ -421,8 +402,6 @@ def take_action(self, parsed_args): class UnsetVolume(command.Command): """Unset volume properties""" - log = logging.getLogger(__name__ + '.UnsetVolume') - def get_parser(self, prog_name): parser = super(UnsetVolume, self).get_parser(prog_name) parser.add_argument( @@ -441,7 +420,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): volume_client = self.app.client_manager.volume volume = utils.find_resource( diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index 06ab8f82c3..d2b3ed6a61 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -14,22 +14,16 @@ """Volume v2 Type action implementations""" -import logging - -from cliff import command -from cliff import lister -from cliff import show import six +from openstackclient.common import command from openstackclient.common import parseractions from openstackclient.common import utils -class CreateVolumeType(show.ShowOne): +class CreateVolumeType(command.ShowOne): """Create new volume type""" - log = logging.getLogger(__name__ + ".CreateVolumeType") - def get_parser(self, prog_name): parser = super(CreateVolumeType, self).get_parser(prog_name) parser.add_argument( @@ -66,7 +60,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): volume_client = self.app.client_manager.volume @@ -93,8 +86,6 @@ def take_action(self, parsed_args): class DeleteVolumeType(command.Command): """Delete volume type""" - log = logging.getLogger(__name__ + ".DeleteVolumeType") - def get_parser(self, prog_name): parser = super(DeleteVolumeType, self).get_parser(prog_name) parser.add_argument( @@ -105,18 +96,15 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.info("take_action: (%s)", parsed_args) volume_client = self.app.client_manager.volume volume_type = utils.find_resource( volume_client.volume_types, parsed_args.volume_type) volume_client.volume_types.delete(volume_type.id) -class ListVolumeType(lister.Lister): +class ListVolumeType(command.Lister): """List volume types""" - log = logging.getLogger(__name__ + '.ListVolumeType') - def get_parser(self, prog_name): parser = super(ListVolumeType, self).get_parser(prog_name) parser.add_argument( @@ -126,7 +114,6 @@ def get_parser(self, prog_name): help='List additional fields in output') return parser - @utils.log_method(log) def take_action(self, parsed_args): if parsed_args.long: columns = ['ID', 'Name', 'Description', 'Extra Specs'] @@ -145,8 +132,6 @@ def take_action(self, parsed_args): class SetVolumeType(command.Command): """Set volume type properties""" - log = logging.getLogger(__name__ + '.SetVolumeType') - def get_parser(self, prog_name): parser = super(SetVolumeType, self).get_parser(prog_name) parser.add_argument( @@ -173,7 +158,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): volume_client = self.app.client_manager.volume volume_type = utils.find_resource( @@ -201,11 +185,9 @@ def take_action(self, parsed_args): volume_type.set_keys(parsed_args.property) -class ShowVolumeType(show.ShowOne): +class ShowVolumeType(command.ShowOne): """Display volume type details""" - log = logging.getLogger(__name__ + ".ShowVolumeType") - def get_parser(self, prog_name): parser = super(ShowVolumeType, self).get_parser(prog_name) parser.add_argument( @@ -216,7 +198,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.log.debug("take_action: (%s)", parsed_args) volume_client = self.app.client_manager.volume volume_type = utils.find_resource( volume_client.volume_types, parsed_args.volume_type) @@ -228,8 +209,6 @@ def take_action(self, parsed_args): class UnsetVolumeType(command.Command): """Unset volume type properties""" - log = logging.getLogger(__name__ + '.UnsetVolumeType') - def get_parser(self, prog_name): parser = super(UnsetVolumeType, self).get_parser(prog_name) parser.add_argument( @@ -247,7 +226,6 @@ def get_parser(self, prog_name): ) return parser - @utils.log_method(log) def take_action(self, parsed_args): volume_client = self.app.client_manager.volume volume_type = utils.find_resource( From cd4998ef41d7bed8cfb3ae849f522e07162913f5 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 28 Jan 2016 11:27:46 -0600 Subject: [PATCH 0555/3095] Add missing release notes Add release notes for the user-visible features added since the 2.0.0 release Change-Id: I753a894f1a3f6eef3eef50661ee66c729a2567f4 --- releasenotes/notes/bug-1486597-857e20262c038514.yaml | 5 +++++ releasenotes/notes/bug-1516661-757ef629f899cca3.yaml | 5 +++++ releasenotes/notes/bug-1517134-9efecb257910989c.yaml | 6 ++++++ releasenotes/notes/bug-1519503-978a68a54819dbbc.yaml | 5 +++++ releasenotes/notes/bug-1519512-d20f44aca1f9e9b9.yaml | 6 ++++++ releasenotes/notes/bug-1521492-89b972c6362940a5.yaml | 5 +++++ releasenotes/notes/bug-1521492-8cde2601591a8c78.yaml | 5 +++++ releasenotes/notes/bug-1522969-63abf273c6e71a07.yaml | 6 ++++++ releasenotes/notes/bug-1524456-9967fac653c91cb2.yaml | 6 ++++++ ...e6ce0c3cd4945.yaml => bug-1525805-122e6ce0c3cd4945.yaml} | 0 10 files changed, 49 insertions(+) create mode 100644 releasenotes/notes/bug-1486597-857e20262c038514.yaml create mode 100644 releasenotes/notes/bug-1516661-757ef629f899cca3.yaml create mode 100644 releasenotes/notes/bug-1517134-9efecb257910989c.yaml create mode 100644 releasenotes/notes/bug-1519503-978a68a54819dbbc.yaml create mode 100644 releasenotes/notes/bug-1519512-d20f44aca1f9e9b9.yaml create mode 100644 releasenotes/notes/bug-1521492-89b972c6362940a5.yaml create mode 100644 releasenotes/notes/bug-1521492-8cde2601591a8c78.yaml create mode 100644 releasenotes/notes/bug-1522969-63abf273c6e71a07.yaml create mode 100644 releasenotes/notes/bug-1524456-9967fac653c91cb2.yaml rename releasenotes/notes/{bug_1525805-122e6ce0c3cd4945.yaml => bug-1525805-122e6ce0c3cd4945.yaml} (100%) diff --git a/releasenotes/notes/bug-1486597-857e20262c038514.yaml b/releasenotes/notes/bug-1486597-857e20262c038514.yaml new file mode 100644 index 0000000000..45eebd4f07 --- /dev/null +++ b/releasenotes/notes/bug-1486597-857e20262c038514.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``project unset`` command for Identity v2 + [Bug `1486597 `_] diff --git a/releasenotes/notes/bug-1516661-757ef629f899cca3.yaml b/releasenotes/notes/bug-1516661-757ef629f899cca3.yaml new file mode 100644 index 0000000000..21c4fb43ba --- /dev/null +++ b/releasenotes/notes/bug-1516661-757ef629f899cca3.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``image set --activate|--deactivate`` options (Image v2 only) + [Bug `1516661 `_] diff --git a/releasenotes/notes/bug-1517134-9efecb257910989c.yaml b/releasenotes/notes/bug-1517134-9efecb257910989c.yaml new file mode 100644 index 0000000000..f6b254b650 --- /dev/null +++ b/releasenotes/notes/bug-1517134-9efecb257910989c.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--project-domain`` option to ``image create`` and ``image set`` commands + (Image v2 only) + [Bug `1517134 `_] diff --git a/releasenotes/notes/bug-1519503-978a68a54819dbbc.yaml b/releasenotes/notes/bug-1519503-978a68a54819dbbc.yaml new file mode 100644 index 0000000000..89c4023555 --- /dev/null +++ b/releasenotes/notes/bug-1519503-978a68a54819dbbc.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``router`` commands ``create``, ``delete``, ``list``, ``set``, ``show`` + [Bug `1519503 `_] diff --git a/releasenotes/notes/bug-1519512-d20f44aca1f9e9b9.yaml b/releasenotes/notes/bug-1519512-d20f44aca1f9e9b9.yaml new file mode 100644 index 0000000000..ae0f1b58ce --- /dev/null +++ b/releasenotes/notes/bug-1519512-d20f44aca1f9e9b9.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Make ``security group rule list`` group argument optional to list all + security groups + [Bug `1519512 `_] diff --git a/releasenotes/notes/bug-1521492-89b972c6362940a5.yaml b/releasenotes/notes/bug-1521492-89b972c6362940a5.yaml new file mode 100644 index 0000000000..6f4766e20b --- /dev/null +++ b/releasenotes/notes/bug-1521492-89b972c6362940a5.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Change ``server list --flavor`` to now accept flavor ID or name + [Bug `1521492 `_] diff --git a/releasenotes/notes/bug-1521492-8cde2601591a8c78.yaml b/releasenotes/notes/bug-1521492-8cde2601591a8c78.yaml new file mode 100644 index 0000000000..8d86b355d9 --- /dev/null +++ b/releasenotes/notes/bug-1521492-8cde2601591a8c78.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Change ``server list --image`` to now accept image ID or name + [Bug `1521492 `_] diff --git a/releasenotes/notes/bug-1522969-63abf273c6e71a07.yaml b/releasenotes/notes/bug-1522969-63abf273c6e71a07.yaml new file mode 100644 index 0000000000..98452dbfe7 --- /dev/null +++ b/releasenotes/notes/bug-1522969-63abf273c6e71a07.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--src-group`` option to ``security group rule create`` to include a + 'remote' security group rule. + [Bug `1522969 `_] diff --git a/releasenotes/notes/bug-1524456-9967fac653c91cb2.yaml b/releasenotes/notes/bug-1524456-9967fac653c91cb2.yaml new file mode 100644 index 0000000000..37eb9503c3 --- /dev/null +++ b/releasenotes/notes/bug-1524456-9967fac653c91cb2.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Change the ``project set --domain`` option to use the argument as a lookup + for locating projects in non-default domains. + [Bug `1524456 `_] diff --git a/releasenotes/notes/bug_1525805-122e6ce0c3cd4945.yaml b/releasenotes/notes/bug-1525805-122e6ce0c3cd4945.yaml similarity index 100% rename from releasenotes/notes/bug_1525805-122e6ce0c3cd4945.yaml rename to releasenotes/notes/bug-1525805-122e6ce0c3cd4945.yaml From 5d1a93362da1109a9f49c32c142a8a4df0a97a9e Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Mon, 21 Dec 2015 16:17:34 -0600 Subject: [PATCH 0556/3095] Fix showing network quotas for a project The OpenStack SDK is now used for the network client. However, the 'openstack quota show' command wasn't updated for the client change. As a result, the command will fail to show network quotas when a project name is specified. For example: $ openstack quota show admin 'Proxy' object has no attribute 'show_quota' This patch set fixes the command by using the OpenStack SDK to get network quotas for a project. Change-Id: I59a7b6780a7b80cd09e79d40d214751b25d3016e Related-To: blueprint neutron-client Closes-Bug: #1528249 --- openstackclient/common/quota.py | 11 ++++++---- openstackclient/tests/common/test_quota.py | 25 ++++++++++++++++------ openstackclient/tests/network/v2/fakes.py | 12 +++++++++++ 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index 8a9b910f2f..56148790ef 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -196,10 +196,13 @@ def get_compute_volume_quota(self, client, parsed_args): def get_network_quota(self, parsed_args): if parsed_args.quota_class or parsed_args.default: return {} - service_catalog = self.app.client_manager.auth_ref.service_catalog - if 'network' in service_catalog.get_endpoints(): - network_client = self.app.client_manager.network - return network_client.show_quota(parsed_args.project)['quota'] + if self.app.client_manager.is_network_endpoint_enabled(): + identity_client = self.app.client_manager.identity + project = utils.find_resource( + identity_client.projects, + parsed_args.project, + ).id + return self.app.client_manager.network.get_quota(project) else: return {} diff --git a/openstackclient/tests/common/test_quota.py b/openstackclient/tests/common/test_quota.py index 485b8a8b97..edf29c9b1f 100644 --- a/openstackclient/tests/common/test_quota.py +++ b/openstackclient/tests/common/test_quota.py @@ -214,13 +214,15 @@ def setUp(self): loaded=True, ) - self.service_catalog_mock.get_endpoints.return_value = [ - fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.ENDPOINT), - loaded=True, - ) - ] + fake_network_endpoint = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.ENDPOINT), + loaded=True, + ) + + self.service_catalog_mock.get_endpoints.return_value = { + 'network': fake_network_endpoint + } self.quotas_class_mock.get.return_value = FakeQuotaResource( None, @@ -244,6 +246,8 @@ def setUp(self): endpoint=fakes.AUTH_URL, token=fakes.AUTH_TOKEN, ) + self.network = self.app.client_manager.network + self.network.get_quota = mock.Mock(return_value=network_fakes.QUOTA) self.cmd = quota.ShowQuota(self.app, None) @@ -260,6 +264,9 @@ def test_quota_show(self): self.cmd.take_action(parsed_args) self.quotas_mock.get.assert_called_with(identity_fakes.project_id) + self.volume_quotas_mock.get.assert_called_with( + identity_fakes.project_id) + self.network.get_quota.assert_called_with(identity_fakes.project_id) def test_quota_show_with_default(self): arglist = [ @@ -276,6 +283,8 @@ def test_quota_show_with_default(self): self.cmd.take_action(parsed_args) self.quotas_mock.defaults.assert_called_with(identity_fakes.project_id) + self.volume_quotas_mock.defaults.assert_called_with( + identity_fakes.project_id) def test_quota_show_with_class(self): arglist = [ @@ -293,3 +302,5 @@ def test_quota_show_with_class(self): self.quotas_class_mock.get.assert_called_with( identity_fakes.project_id) + self.volume_quotas_class_mock.get.assert_called_with( + identity_fakes.project_id) diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 88370feb16..4c862bd332 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -26,6 +26,18 @@ extension_alias = 'Dystopian' extension_links = '[{"href":''"https://github.com/os/network", "type"}]' +QUOTA = { + "subnet": 10, + "network": 10, + "floatingip": 50, + "subnetpool": -1, + "security_group_rule": 100, + "security_group": 10, + "router": 10, + "rbac_policy": -1, + "port": 50, +} + def create_extension(): extension = mock.Mock() From 77ce1c17a0b0709eaf11145b2953460d8c52a758 Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Tue, 2 Feb 2016 11:03:07 +0900 Subject: [PATCH 0557/3095] Consume openstackclient.common.command in subnet/port Follow-up patch of https://review.openstack.org/#/c/269613/ network/v2/subnet and port still use cliff classes directly. This patch fixes it. Change-Id: If9d90e5151ece7f4cf1e0d6fd2f32919865f2f2e --- openstackclient/network/v2/port.py | 2 +- openstackclient/network/v2/subnet.py | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index b91ac58899..0d5b183e47 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -13,7 +13,7 @@ """Port action implementations""" -from cliff import command +from openstackclient.common import command class DeletePort(command.Command): diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 467b6507b8..b948c6561b 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -13,8 +13,7 @@ """Subnet action implementations""" -from cliff import lister - +from openstackclient.common import command from openstackclient.common import utils @@ -31,7 +30,7 @@ def _format_allocation_pools(data): } -class ListSubnet(lister.Lister): +class ListSubnet(command.Lister): """List subnets""" def get_parser(self, prog_name): From 2548419e92d9ced02088d34b83a6537eaa17b373 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Mon, 1 Feb 2016 22:47:28 +0000 Subject: [PATCH 0558/3095] Updated from global requirements Change-Id: I2f00e8ff1609d76c2ab20226f2b4503231fdb003 --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index d0d6829563..6a73cd95c1 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -23,7 +23,7 @@ python-barbicanclient>=3.3.0 # Apache-2.0 python-congressclient>=1.0.0 # Apache-2.0 python-designateclient>=1.5.0 # Apache-2.0 python-heatclient>=0.6.0 # Apache-2.0 -python-ironicclient>=0.9.0 # Apache-2.0 +python-ironicclient>=1.1.0 # Apache-2.0 python-mistralclient>=1.0.0 # Apache-2.0 python-saharaclient>=0.10.0 # Apache-2.0 python-zaqarclient>=0.3.0 # Apache-2.0 From a05d9fd9f13e6e9da031d527a39f7f0628bf69c7 Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Tue, 2 Feb 2016 12:51:24 +0900 Subject: [PATCH 0559/3095] Drop log_method decorator As a result of the recent logging refactoring, log_method decorator is no longer required. oslo.log provides a similar decorator oslo_log.helpers.log_method_call. If a similar feature is needed, we can use the decorator from oslo_log. searchlightclient is the only OSC external plugin which uses this decorator. The depending patch removes it, so we can safely drop the decorator. Change-Id: If3df09cf6aa0a401d9f89e8924adce851d0c6dec Depends-On: Ib94e7ba77262a9a8cbfce71f3083c47cb1973364 --- openstackclient/common/utils.py | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index 3ae30c8f71..1ca2bc574a 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -26,33 +26,6 @@ from openstackclient.common import exceptions -class log_method(object): - - def __init__(self, log=None, level=logging.DEBUG): - self._log = log - self._level = level - - def __call__(self, func): - func_name = func.__name__ - if not self._log: - self._log = logging.getLogger(func.__class__.__name__) - - @six.wraps(func) - def wrapper(*args, **kwargs): - if self._log.isEnabledFor(self._level): - pretty_args = [] - if args: - pretty_args.extend(str(a) for a in args) - if kwargs: - pretty_args.extend( - "%s=%s" % (k, v) for k, v in six.iteritems(kwargs)) - self._log.log(self._level, "%s(%s)", - func_name, ", ".join(pretty_args)) - return func(*args, **kwargs) - - return wrapper - - def find_resource(manager, name_or_id, **kwargs): """Helper for the _find_* methods. From a83c1f0a4244d6c422a68cf5bf2bbaa3e4617b78 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 15 Dec 2015 11:40:23 +0800 Subject: [PATCH 0560/3095] Network: Abstract get_body() out to be a private helper. get_body() is needed in each network files to construct a dict to pass to sdk proxy. And it is also used by several functions in each file. So define it as a file level private helper function. The unified prototype should be: def _get_attrs(client_manager, parsed_args): 1. The name, in sdk, the parameter passed to proxy is named "attrs". And it is a private method. So let's call it _get_attrs(). 2. The parameters, besides parsed_args, when we deal with project and project_domain, we have to make use of identity_client. So let's pass in the client manager. Change-Id: Ib044ebd4ddedbcd805f46334a7fe99e4ebb5b249 --- openstackclient/network/v2/network.py | 65 ++++++++++--------- openstackclient/tests/network/v2/fakes.py | 1 - .../tests/network/v2/test_network.py | 19 +++--- 3 files changed, 46 insertions(+), 39 deletions(-) diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index fc94fd8277..6123721963 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -47,6 +47,33 @@ def _get_columns(item): return tuple(sorted(columns)) +def _get_attrs(client_manager, parsed_args): + attrs = {} + if parsed_args.name is not None: + attrs['name'] = str(parsed_args.name) + if parsed_args.admin_state is not None: + attrs['admin_state_up'] = parsed_args.admin_state + if parsed_args.shared is not None: + attrs['shared'] = parsed_args.shared + + # "network set" command doesn't support setting project. + if 'project' in parsed_args and parsed_args.project is not None: + identity_client = client_manager.identity + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + attrs['tenant_id'] = project_id + + # "network set" command doesn't support setting availability zone hints. + if 'availability_zone_hints' in parsed_args and \ + parsed_args.availability_zone_hints is not None: + attrs['availability_zone_hints'] = parsed_args.availability_zone_hints + + return attrs + + class CreateNetwork(command.ShowOne): """Create new network""" @@ -105,31 +132,14 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): client = self.app.client_manager.network - body = self.get_body(parsed_args) - obj = client.create_network(**body) + + attrs = _get_attrs(self.app.client_manager, parsed_args) + obj = client.create_network(**attrs) columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return (columns, data) - def get_body(self, parsed_args): - body = {'name': str(parsed_args.name), - 'admin_state_up': parsed_args.admin_state} - if parsed_args.shared is not None: - body['shared'] = parsed_args.shared - if parsed_args.project is not None: - identity_client = self.app.client_manager.identity - project_id = identity_common.find_project( - identity_client, - parsed_args.project, - parsed_args.project_domain, - ).id - body['tenant_id'] = project_id - if parsed_args.availability_zone_hints is not None: - body['availability_zone_hints'] = \ - parsed_args.availability_zone_hints - - return body - class DeleteNetwork(command.Command): """Delete network(s)""" @@ -271,18 +281,13 @@ def take_action(self, parsed_args): client = self.app.client_manager.network obj = client.find_network(parsed_args.identifier, ignore_missing=False) - if parsed_args.name is not None: - obj.name = str(parsed_args.name) - if parsed_args.admin_state is not None: - obj.admin_state_up = parsed_args.admin_state - if parsed_args.shared is not None: - obj.shared = parsed_args.shared - - if not obj.is_dirty: + attrs = _get_attrs(self.app.client_manager, parsed_args) + if attrs == {}: msg = "Nothing specified to be set" raise exceptions.CommandError(msg) - client.update_network(obj) + client.update_network(obj, **attrs) + return class ShowNetwork(command.ShowOne): diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 4c862bd332..9d1dc5440b 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -96,7 +96,6 @@ def create_one_network(attrs={}, methods={}): 'subnets': ['a', 'b'], 'provider_network_type': 'vlan', 'router_external': True, - 'is_dirty': True, 'availability_zones': [], 'availability_zone_hints': [], } diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index 37cc66742a..f96497a437 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -440,8 +440,6 @@ def setUp(self): self.cmd = network.SetNetwork(self.app, self.namespace) def test_set_this(self): - self._network.is_dirty = True - arglist = [ self._network.name, '--enable', @@ -458,12 +456,15 @@ def test_set_this(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.network.update_network.assert_called_with(self._network) + attrs = { + 'name': 'noob', + 'admin_state_up': True, + 'shared': True, + } + self.network.update_network.assert_called_with(self._network, **attrs) self.assertIsNone(result) def test_set_that(self): - self._network.is_dirty = True - arglist = [ self._network.name, '--disable', @@ -478,12 +479,14 @@ def test_set_that(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.network.update_network.assert_called_with(self._network) + attrs = { + 'admin_state_up': False, + 'shared': False, + } + self.network.update_network.assert_called_with(self._network, **attrs) self.assertIsNone(result) def test_set_nothing(self): - self._network.is_dirty = False - arglist = [self._network.name, ] verifylist = [('identifier', self._network.name), ] From f36177ebdd4ea25028337efaf667c23d62e3bf9e Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 2 Feb 2016 15:22:40 +0800 Subject: [PATCH 0561/3095] Trivial: Fix wrong comment in test_image.py Code in test_image.py has nothing to do with server. Change-Id: Ia73d7b99effb394c5db9635fee6da350b0b1086b --- openstackclient/tests/image/v2/test_image.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index 488eab20c9..41abdaab61 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -31,11 +31,13 @@ class TestImage(image_fakes.TestImagev2): def setUp(self): super(TestImage, self).setUp() - # Get a shortcut to the ServerManager Mock + # Get shortcuts to the Mocks in image client self.images_mock = self.app.client_manager.image.images self.images_mock.reset_mock() self.image_members_mock = self.app.client_manager.image.image_members self.image_members_mock.reset_mock() + + # Get shortcut to the Mocks in identity client self.project_mock = self.app.client_manager.identity.projects self.project_mock.reset_mock() self.domain_mock = self.app.client_manager.identity.domains From 4d332defbc4231f77b7459d4abda88a36a65d37d Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Fri, 15 Jan 2016 15:23:08 -0600 Subject: [PATCH 0562/3095] Support listing network availability zones Update the "os availability zone list" command to support listing network availability zones along with the currently listed compute and volume availability zones. This adds the --network option to the command in order to only list network availability zones. By default, all availability zones are listed. The --long option was also updated to include a "Zone Resource" column which is applicable to network availability zones. Example zone resources include "network" and "router". If the Network API does not support listing availability zones then a warning message will be issued when the --network option is specified. This support requires an updated release of the SDK in order to pull in [1]. [1] https://bugs.launchpad.net/python-openstacksdk/+bug/1532274 Change-Id: I78811d659b793d9d2111ea54665d5fe7e4887264 Closes-Bug: #1534202 --- .../command-objects/availability-zone.rst | 7 +- doc/source/commands.rst | 2 +- openstackclient/common/availability_zone.py | 66 ++++++++++++++++--- .../tests/common/test_availability_zone.py | 65 ++++++++++++++++++ openstackclient/tests/network/v2/fakes.py | 53 +++++++++++++++ .../notes/bug-1534202-1ba78f0bb744961f.yaml | 7 ++ 6 files changed, 188 insertions(+), 12 deletions(-) create mode 100644 releasenotes/notes/bug-1534202-1ba78f0bb744961f.yaml diff --git a/doc/source/command-objects/availability-zone.rst b/doc/source/command-objects/availability-zone.rst index 8c021529c8..1f5684383d 100644 --- a/doc/source/command-objects/availability-zone.rst +++ b/doc/source/command-objects/availability-zone.rst @@ -2,7 +2,7 @@ availability zone ================= -Compute v2, Block Storage v2 +Block Storage v2, Compute v2, Network v2 availability zone list ---------------------- @@ -14,6 +14,7 @@ List availability zones and their status os availability zone list [--compute] + [--network] [--volume] [--long] @@ -21,6 +22,10 @@ List availability zones and their status List compute availability zones +.. option:: --network + + List network availability zones + .. option:: --volume List volume availability zones diff --git a/doc/source/commands.rst b/doc/source/commands.rst index f5484b2118..1c4f84b28b 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -70,7 +70,7 @@ the API resources will be merged, as in the ``quota`` object that has options referring to both Compute and Volume quotas. * ``access token``: (**Identity**) long-lived OAuth-based token -* ``availability zone``: (**Compute**, **Volume**) a logical partition of hosts or block storage services +* ``availability zone``: (**Compute**, **Network**, **Volume**) a logical partition of hosts or block storage or network services * ``aggregate``: (**Compute**) a grouping of servers * ``backup``: (**Volume**) a volume copy * ``catalog``: (**Identity**) service catalog diff --git a/openstackclient/common/availability_zone.py b/openstackclient/common/availability_zone.py index fa5aee4726..a941418be2 100644 --- a/openstackclient/common/availability_zone.py +++ b/openstackclient/common/availability_zone.py @@ -30,6 +30,8 @@ def _xform_common_availability_zone(az, zone_info): if hasattr(az, 'zoneName'): zone_info['zone_name'] = az.zoneName + zone_info['zone_resource'] = '' + def _xform_compute_availability_zone(az, include_extra): result = [] @@ -69,6 +71,18 @@ def _xform_volume_availability_zone(az): return result +def _xform_network_availability_zone(az): + result = [] + zone_info = {} + zone_info['zone_name'] = getattr(az, 'name', '') + zone_info['zone_status'] = getattr(az, 'state', '') + if 'unavailable' == zone_info['zone_status']: + zone_info['zone_status'] = 'not available' + zone_info['zone_resource'] = getattr(az, 'resource', '') + result.append(zone_info) + return result + + class ListAvailabilityZone(command.Lister): """List availability zones and their status""" @@ -79,6 +93,11 @@ def get_parser(self, prog_name): action='store_true', default=False, help='List compute availability zones') + parser.add_argument( + '--network', + action='store_true', + default=False, + help='List network availability zones') parser.add_argument( '--volume', action='store_true', @@ -92,7 +111,7 @@ def get_parser(self, prog_name): ) return parser - def get_compute_availability_zones(self, parsed_args): + def _get_compute_availability_zones(self, parsed_args): compute_client = self.app.client_manager.compute try: data = compute_client.availability_zones.list() @@ -108,36 +127,63 @@ def get_compute_availability_zones(self, parsed_args): result += _xform_compute_availability_zone(zone, parsed_args.long) return result - def get_volume_availability_zones(self, parsed_args): + def _get_volume_availability_zones(self, parsed_args): volume_client = self.app.client_manager.volume + data = [] try: data = volume_client.availability_zones.list() - except Exception: - message = "Availability zones list not supported by " \ - "Block Storage API" - self.log.warning(message) + except Exception as e: + self.log.debug('Volume availability zone exception: ' + str(e)) + if parsed_args.volume: + message = "Availability zones list not supported by " \ + "Block Storage API" + self.log.warning(message) result = [] for zone in data: result += _xform_volume_availability_zone(zone) return result + def _get_network_availability_zones(self, parsed_args): + network_client = self.app.client_manager.network + data = [] + try: + # Verify that the extension exists. + network_client.find_extension('Availability Zone', + ignore_missing=False) + data = network_client.availability_zones() + except Exception as e: + self.log.debug('Network availability zone exception: ' + str(e)) + if parsed_args.network: + message = "Availability zones list not supported by " \ + "Network API" + self.log.warning(message) + + result = [] + for zone in data: + result += _xform_network_availability_zone(zone) + return result + def take_action(self, parsed_args): if parsed_args.long: - columns = ('Zone Name', 'Zone Status', + columns = ('Zone Name', 'Zone Status', 'Zone Resource', 'Host Name', 'Service Name', 'Service Status') else: columns = ('Zone Name', 'Zone Status') # Show everything by default. - show_all = (not parsed_args.compute and not parsed_args.volume) + show_all = (not parsed_args.compute and + not parsed_args.volume and + not parsed_args.network) result = [] if parsed_args.compute or show_all: - result += self.get_compute_availability_zones(parsed_args) + result += self._get_compute_availability_zones(parsed_args) if parsed_args.volume or show_all: - result += self.get_volume_availability_zones(parsed_args) + result += self._get_volume_availability_zones(parsed_args) + if parsed_args.network or show_all: + result += self._get_network_availability_zones(parsed_args) return (columns, (utils.get_dict_properties( diff --git a/openstackclient/tests/common/test_availability_zone.py b/openstackclient/tests/common/test_availability_zone.py index 232b56c99c..e44455c7b4 100644 --- a/openstackclient/tests/common/test_availability_zone.py +++ b/openstackclient/tests/common/test_availability_zone.py @@ -11,11 +11,13 @@ # under the License. # +import mock import six from openstackclient.common import availability_zone from openstackclient.tests.compute.v2 import fakes as compute_fakes from openstackclient.tests import fakes +from openstackclient.tests.network.v2 import fakes as network_fakes from openstackclient.tests import utils from openstackclient.tests.volume.v2 import fakes as volume_fakes @@ -33,6 +35,7 @@ def _build_compute_az_datalist(compute_az, long_datalist=False): datalist += ( compute_az.zoneName, 'available', + '', host, service, 'enabled :-) ' + state['updated_at'], @@ -51,6 +54,23 @@ def _build_volume_az_datalist(volume_az, long_datalist=False): datalist = ( volume_az.zoneName, 'available', + '', '', '', '', + ) + return (datalist,) + + +def _build_network_az_datalist(network_az, long_datalist=False): + datalist = () + if not long_datalist: + datalist = ( + network_az.name, + network_az.state, + ) + else: + datalist = ( + network_az.name, + network_az.state, + network_az.resource, '', '', '', ) return (datalist,) @@ -79,6 +99,16 @@ def setUp(self): self.volume_azs_mock = volume_client.availability_zones self.volume_azs_mock.reset_mock() + network_client = network_fakes.FakeNetworkV2Client( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN, + ) + self.app.client_manager.network = network_client + + network_client.availability_zones = mock.Mock() + network_client.find_extension = mock.Mock() + self.network_azs_mock = network_client.availability_zones + class TestAvailabilityZoneList(TestAvailabilityZone): @@ -86,11 +116,14 @@ class TestAvailabilityZoneList(TestAvailabilityZone): compute_fakes.FakeAvailabilityZone.create_availability_zones() volume_azs = \ volume_fakes.FakeAvailabilityZone.create_availability_zones(count=1) + network_azs = \ + network_fakes.FakeAvailabilityZone.create_availability_zones() short_columnslist = ('Zone Name', 'Zone Status') long_columnslist = ( 'Zone Name', 'Zone Status', + 'Zone Resource', 'Host Name', 'Service Name', 'Service Status', @@ -101,6 +134,7 @@ def setUp(self): self.compute_azs_mock.list.return_value = self.compute_azs self.volume_azs_mock.list.return_value = self.volume_azs + self.network_azs_mock.return_value = self.network_azs # Get the command object to test self.cmd = availability_zone.ListAvailabilityZone(self.app, None) @@ -115,6 +149,7 @@ def test_availability_zone_list_no_options(self): self.compute_azs_mock.list.assert_called_with() self.volume_azs_mock.list.assert_called_with() + self.network_azs_mock.assert_called_with() self.assertEqual(self.short_columnslist, columns) datalist = () @@ -122,6 +157,8 @@ def test_availability_zone_list_no_options(self): datalist += _build_compute_az_datalist(compute_az) for volume_az in self.volume_azs: datalist += _build_volume_az_datalist(volume_az) + for network_az in self.network_azs: + datalist += _build_network_az_datalist(network_az) self.assertEqual(datalist, tuple(data)) def test_availability_zone_list_long(self): @@ -138,6 +175,7 @@ def test_availability_zone_list_long(self): self.compute_azs_mock.list.assert_called_with() self.volume_azs_mock.list.assert_called_with() + self.network_azs_mock.assert_called_with() self.assertEqual(self.long_columnslist, columns) datalist = () @@ -147,6 +185,9 @@ def test_availability_zone_list_long(self): for volume_az in self.volume_azs: datalist += _build_volume_az_datalist(volume_az, long_datalist=True) + for network_az in self.network_azs: + datalist += _build_network_az_datalist(network_az, + long_datalist=True) self.assertEqual(datalist, tuple(data)) def test_availability_zone_list_compute(self): @@ -163,6 +204,7 @@ def test_availability_zone_list_compute(self): self.compute_azs_mock.list.assert_called_with() self.volume_azs_mock.list.assert_not_called() + self.network_azs_mock.assert_not_called() self.assertEqual(self.short_columnslist, columns) datalist = () @@ -184,9 +226,32 @@ def test_availability_zone_list_volume(self): self.compute_azs_mock.list.assert_not_called() self.volume_azs_mock.list.assert_called_with() + self.network_azs_mock.assert_not_called() self.assertEqual(self.short_columnslist, columns) datalist = () for volume_az in self.volume_azs: datalist += _build_volume_az_datalist(volume_az) self.assertEqual(datalist, tuple(data)) + + def test_availability_zone_list_network(self): + arglist = [ + '--network', + ] + verifylist = [ + ('network', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + self.compute_azs_mock.list.assert_not_called() + self.volume_azs_mock.list.assert_not_called() + self.network_azs_mock.assert_called_with() + + self.assertEqual(self.short_columnslist, columns) + datalist = () + for network_az in self.network_azs: + datalist += _build_network_az_datalist(network_az) + self.assertEqual(datalist, tuple(data)) diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 4c862bd332..6f03d79718 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -69,6 +69,59 @@ def setUp(self): ) +class FakeAvailabilityZone(object): + """Fake one or more network availability zones (AZs).""" + + @staticmethod + def create_one_availability_zone(attrs={}, methods={}): + """Create a fake AZ. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :return: + A FakeResource object with name, state, etc. + """ + # Set default attributes. + availability_zone = { + 'name': uuid.uuid4().hex, + 'state': 'available', + 'resource': 'network', + } + + # Overwrite default attributes. + availability_zone.update(attrs) + + availability_zone = fakes.FakeResource( + info=copy.deepcopy(availability_zone), + methods=methods, + loaded=True) + return availability_zone + + @staticmethod + def create_availability_zones(attrs={}, methods={}, count=2): + """Create multiple fake AZs. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :param int count: + The number of AZs to fake + :return: + A list of FakeResource objects faking the AZs + """ + availability_zones = [] + for i in range(0, count): + availability_zone = \ + FakeAvailabilityZone.create_one_availability_zone( + attrs, methods) + availability_zones.append(availability_zone) + + return availability_zones + + class FakeNetwork(object): """Fake one or more networks.""" diff --git a/releasenotes/notes/bug-1534202-1ba78f0bb744961f.yaml b/releasenotes/notes/bug-1534202-1ba78f0bb744961f.yaml new file mode 100644 index 0000000000..36c0a3dbf7 --- /dev/null +++ b/releasenotes/notes/bug-1534202-1ba78f0bb744961f.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Add network support to `os availability zone list` + [Bug `1534202 `_] + + * New `--network` option to only list network availability zones. From 981621e9845934c109c5e4abd9b2ab6828744346 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Mon, 25 Jan 2016 12:52:49 -0600 Subject: [PATCH 0563/3095] Add "os port show" command Add "os port show" command. Change-Id: Id87c81640e74c60ae8f247c722c64fdadff022a2 Partial-Bug: #1519909 Partially-Implements: blueprint neutron-client --- doc/source/command-objects/port.rst | 16 ++++ openstackclient/common/utils.py | 10 +++ openstackclient/network/v2/port.py | 56 ++++++++++++ openstackclient/tests/common/test_utils.py | 9 ++ openstackclient/tests/network/v2/fakes.py | 42 ++++++++- openstackclient/tests/network/v2/test_port.py | 87 +++++++++++++++++++ ...dd-port-show-command-de0a599017189a21.yaml | 5 ++ setup.cfg | 1 + 8 files changed, 222 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/add-port-show-command-de0a599017189a21.yaml diff --git a/doc/source/command-objects/port.rst b/doc/source/command-objects/port.rst index 78677332be..414b7437f5 100644 --- a/doc/source/command-objects/port.rst +++ b/doc/source/command-objects/port.rst @@ -19,3 +19,19 @@ Delete port(s) .. describe:: Port(s) to delete (name or ID) + +port show +--------- + +Display port details + +.. program:: port show +.. code:: bash + + os port show + + +.. _port_show-port: +.. describe:: + + Port to display (name or ID) diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index 3ae30c8f71..451484c857 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -169,6 +169,16 @@ def format_list(data, separator=', '): return separator.join(sorted(data)) +def format_list_of_dicts(data): + """Return a formatted string of key value pairs for each dict + + :param data: a list of dicts + :rtype: a string formatted to key='value' with dicts separated by new line + """ + + return '\n'.join(format_dict(i) for i in data) + + def get_field(item, field): try: if isinstance(item, dict): diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 0d5b183e47..46cb031f91 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -14,6 +14,42 @@ """Port action implementations""" from openstackclient.common import command +from openstackclient.common import utils + + +def _format_admin_state(state): + return 'UP' if state else 'DOWN' + + +_formatters = { + 'admin_state_up': _format_admin_state, + 'allowed_address_pairs': utils.format_list_of_dicts, + 'binding_profile': utils.format_dict, + 'binding_vif_details': utils.format_dict, + 'dns_assignment': utils.format_list_of_dicts, + 'extra_dhcp_opts': utils.format_list_of_dicts, + 'fixed_ips': utils.format_list_of_dicts, + 'security_groups': utils.format_list, +} + + +def _get_columns(item): + columns = item.keys() + if 'tenant_id' in columns: + columns.remove('tenant_id') + columns.append('project_id') + binding_columns = [ + 'binding:host_id', + 'binding:profile', + 'binding:vif_details', + 'binding:vif_type', + 'binding:vnic_type', + ] + for binding_column in binding_columns: + if binding_column in columns: + columns.remove(binding_column) + columns.append(binding_column.replace('binding:', 'binding_', 1)) + return sorted(columns) class DeletePort(command.Command): @@ -35,3 +71,23 @@ def take_action(self, parsed_args): for port in parsed_args.port: res = client.find_port(port) client.delete_port(res) + + +class ShowPort(command.ShowOne): + """Display port details""" + + def get_parser(self, prog_name): + parser = super(ShowPort, self).get_parser(prog_name) + parser.add_argument( + 'port', + metavar="", + help="Port to display (name or ID)" + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.find_port(parsed_args.port, ignore_missing=False) + columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns, formatters=_formatters) + return (tuple(columns), data) diff --git a/openstackclient/tests/common/test_utils.py b/openstackclient/tests/common/test_utils.py index 064ad417e6..62e3638eeb 100644 --- a/openstackclient/tests/common/test_utils.py +++ b/openstackclient/tests/common/test_utils.py @@ -348,6 +348,15 @@ def test_format_list(self): self.assertEqual(expected, utils.format_list(['a', 'b', 'c'])) self.assertEqual(expected, utils.format_list(['c', 'b', 'a'])) + def test_format_list_of_dicts(self): + expected = "a='b', c='d'\ne='f'" + sorted_data = [{'a': 'b', 'c': 'd'}, {'e': 'f'}] + unsorted_data = [{'c': 'd', 'a': 'b'}, {'e': 'f'}] + self.assertEqual(expected, utils.format_list_of_dicts(sorted_data)) + self.assertEqual(expected, utils.format_list_of_dicts(unsorted_data)) + self.assertEqual('', utils.format_list_of_dicts([])) + self.assertEqual('', utils.format_list_of_dicts([{}])) + def test_format_list_separator(self): expected = 'a\nb\nc' actual_pre_sorted = utils.format_list(['a', 'b', 'c'], separator='\n') diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 4c862bd332..a73ae5aadd 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -172,15 +172,31 @@ def create_one_port(attrs={}, methods={}): :param Dictionary methods: A dictionary with all methods :return: - A FakeResource object, with id, name, admin_state_up, - status, tenant_id + A FakeResource object, with id, name, etc. """ + # Set default attributes. port_attrs = { + 'admin_state_up': True, + 'allowed_address_pairs': [{}], + 'binding:host_id': 'binding-host-id-' + uuid.uuid4().hex, + 'binding:profile': {}, + 'binding:vif_details': {}, + 'binding:vif_type': 'ovs', + 'binding:vnic_type': 'normal', + 'device_id': 'device-id-' + uuid.uuid4().hex, + 'device_owner': 'compute:nova', + 'dns_assignment': [{}], + 'dns_name': 'dns-name-' + uuid.uuid4().hex, + 'extra_dhcp_opts': [{}], + 'fixed_ips': [{}], 'id': 'port-id-' + uuid.uuid4().hex, + 'mac_address': 'fa:16:3e:a9:4e:72', 'name': 'port-name-' + uuid.uuid4().hex, + 'network_id': 'network-id-' + uuid.uuid4().hex, + 'port_security_enabled': True, + 'security_groups': [], 'status': 'ACTIVE', - 'admin_state_up': True, 'tenant_id': 'project-id-' + uuid.uuid4().hex, } @@ -188,7 +204,16 @@ def create_one_port(attrs={}, methods={}): port_attrs.update(attrs) # Set default methods. - port_methods = {} + port_methods = { + 'keys': ['admin_state_up', 'allowed_address_pairs', + 'binding:host_id', 'binding:profile', + 'binding:vif_details', 'binding:vif_type', + 'binding:vnic_type', 'device_id', 'device_owner', + 'dns_assignment', 'dns_name', 'extra_dhcp_opts', + 'fixed_ips', 'id', 'mac_address', 'name', + 'network_id', 'port_security_enabled', + 'security_groups', 'status', 'tenant_id'], + } # Overwrite default methods. port_methods.update(methods) @@ -196,6 +221,15 @@ def create_one_port(attrs={}, methods={}): port = fakes.FakeResource(info=copy.deepcopy(port_attrs), methods=copy.deepcopy(port_methods), loaded=True) + + # Set attributes with special mappings. + port.project_id = port_attrs['tenant_id'] + port.binding_host_id = port_attrs['binding:host_id'] + port.binding_profile = port_attrs['binding:profile'] + port.binding_vif_details = port_attrs['binding:vif_details'] + port.binding_vif_type = port_attrs['binding:vif_type'] + port.binding_vnic_type = port_attrs['binding:vnic_type'] + return port @staticmethod diff --git a/openstackclient/tests/network/v2/test_port.py b/openstackclient/tests/network/v2/test_port.py index a1ddefa1de..bc246bd877 100644 --- a/openstackclient/tests/network/v2/test_port.py +++ b/openstackclient/tests/network/v2/test_port.py @@ -13,8 +13,10 @@ import mock +from openstackclient.common import utils from openstackclient.network.v2 import port from openstackclient.tests.network.v2 import fakes as network_fakes +from openstackclient.tests import utils as tests_utils class TestPort(network_fakes.TestNetworkV2): @@ -51,3 +53,88 @@ def test_delete(self): result = self.cmd.take_action(parsed_args) self.network.delete_port.assert_called_with(self._port) self.assertIsNone(result) + + +class TestShowPort(TestPort): + + # The port to show. + _port = network_fakes.FakePort.create_one_port() + + columns = ( + 'admin_state_up', + 'allowed_address_pairs', + 'binding_host_id', + 'binding_profile', + 'binding_vif_details', + 'binding_vif_type', + 'binding_vnic_type', + 'device_id', + 'device_owner', + 'dns_assignment', + 'dns_name', + 'extra_dhcp_opts', + 'fixed_ips', + 'id', + 'mac_address', + 'name', + 'network_id', + 'port_security_enabled', + 'project_id', + 'security_groups', + 'status', + ) + + data = ( + port._format_admin_state(_port.admin_state_up), + utils.format_list_of_dicts(_port.allowed_address_pairs), + _port.binding_host_id, + utils.format_dict(_port.binding_profile), + utils.format_dict(_port.binding_vif_details), + _port.binding_vif_type, + _port.binding_vnic_type, + _port.device_id, + _port.device_owner, + utils.format_list_of_dicts(_port.dns_assignment), + _port.dns_name, + utils.format_list_of_dicts(_port.extra_dhcp_opts), + utils.format_list_of_dicts(_port.fixed_ips), + _port.id, + _port.mac_address, + _port.name, + _port.network_id, + _port.port_security_enabled, + _port.project_id, + utils.format_list(_port.security_groups), + _port.status, + ) + + def setUp(self): + super(TestShowPort, self).setUp() + + self.network.find_port = mock.Mock(return_value=self._port) + + # Get the command object to test + self.cmd = port.ShowPort(self.app, self.namespace) + + def test_show_no_options(self): + arglist = [] + verifylist = [] + + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, arglist, verifylist) + + def test_show_all_options(self): + arglist = [ + self._port.name, + ] + verifylist = [ + ('port', self._port.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.find_port.assert_called_with(self._port.name, + ignore_missing=False) + self.assertEqual(tuple(self.columns), columns) + self.assertEqual(self.data, data) diff --git a/releasenotes/notes/add-port-show-command-de0a599017189a21.yaml b/releasenotes/notes/add-port-show-command-de0a599017189a21.yaml new file mode 100644 index 0000000000..cb65266353 --- /dev/null +++ b/releasenotes/notes/add-port-show-command-de0a599017189a21.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add support for the ``port show`` command. + [Bug `1519909 `_] diff --git a/setup.cfg b/setup.cfg index 2751eb3178..d24e7aeaaa 100644 --- a/setup.cfg +++ b/setup.cfg @@ -333,6 +333,7 @@ openstack.network.v2 = network_set = openstackclient.network.v2.network:SetNetwork network_show = openstackclient.network.v2.network:ShowNetwork port_delete = openstackclient.network.v2.port:DeletePort + port_show = openstackclient.network.v2.port:ShowPort router_create = openstackclient.network.v2.router:CreateRouter router_delete = openstackclient.network.v2.router:DeleteRouter router_list = openstackclient.network.v2.router:ListRouter From 580b0aff8810f234adcf364b0fe151b6e3c58182 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Mon, 14 Dec 2015 13:29:43 -0600 Subject: [PATCH 0564/3095] Refactor security group delete to use SDK Refactored the 'os security group delete' command to use the SDK when neutron is enabled, but continue to use the nova client when nova network is enabled. This patch set introduces a new NetworkAndComputeCommand class to be used for commands that must support neutron and nova network. The new class allows both the parser and actions to be unique. The current DeleteSecurityGroup class is now a subclass of this new class and has moved under the network v2 commands. This patch set also introduces a new FakeSecurityGroup class for testing security groups. And finally, this patch set updates the command documentation for security group and security group rule to indicate that Network v2 is also used. Change-Id: Ic21376b86b40cc6d97f360f3760ba5beed154537 Partial-Bug: #1519511 Related-to: blueprint neutron-client --- .../command-objects/security-group-rule.rst | 2 +- doc/source/command-objects/security-group.rst | 2 +- openstackclient/compute/v2/security_group.py | 22 ---- openstackclient/network/common.py | 62 +++++++++++ openstackclient/network/v2/security_group.py | 40 +++++++ openstackclient/tests/network/test_common.py | 103 ++++++++++++++++++ openstackclient/tests/network/v2/fakes.py | 78 +++++++++++++ .../tests/network/v2/test_security_group.py | 99 +++++++++++++++++ setup.cfg | 2 +- 9 files changed, 385 insertions(+), 25 deletions(-) create mode 100644 openstackclient/network/common.py create mode 100644 openstackclient/network/v2/security_group.py create mode 100644 openstackclient/tests/network/test_common.py create mode 100644 openstackclient/tests/network/v2/test_security_group.py diff --git a/doc/source/command-objects/security-group-rule.rst b/doc/source/command-objects/security-group-rule.rst index ec03644e9f..50bc64aa8e 100644 --- a/doc/source/command-objects/security-group-rule.rst +++ b/doc/source/command-objects/security-group-rule.rst @@ -2,7 +2,7 @@ security group rule =================== -Compute v2 +Compute v2, Network v2 security group rule create -------------------------- diff --git a/doc/source/command-objects/security-group.rst b/doc/source/command-objects/security-group.rst index 60de41d824..cf86bda6be 100644 --- a/doc/source/command-objects/security-group.rst +++ b/doc/source/command-objects/security-group.rst @@ -2,7 +2,7 @@ security group ============== -Compute v2 +Compute v2, Network v2 security group create --------------------- diff --git a/openstackclient/compute/v2/security_group.py b/openstackclient/compute/v2/security_group.py index b975a50533..2a8908d750 100644 --- a/openstackclient/compute/v2/security_group.py +++ b/openstackclient/compute/v2/security_group.py @@ -169,28 +169,6 @@ def take_action(self, parsed_args): return zip(*sorted(six.iteritems(info))) -class DeleteSecurityGroup(command.Command): - """Delete a security group""" - - def get_parser(self, prog_name): - parser = super(DeleteSecurityGroup, self).get_parser(prog_name) - parser.add_argument( - 'group', - metavar='', - help='Security group to delete (name or ID)', - ) - return parser - - def take_action(self, parsed_args): - - compute_client = self.app.client_manager.compute - data = utils.find_resource( - compute_client.security_groups, - parsed_args.group, - ) - compute_client.security_groups.delete(data.id) - - class DeleteSecurityGroupRule(command.Command): """Delete a security group rule""" diff --git a/openstackclient/network/common.py b/openstackclient/network/common.py new file mode 100644 index 0000000000..c539dd052f --- /dev/null +++ b/openstackclient/network/common.py @@ -0,0 +1,62 @@ +# 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 abc +import six + +from openstackclient.common import command + + +@six.add_metaclass(abc.ABCMeta) +class NetworkAndComputeCommand(command.Command): + """Network and Compute Command""" + + def take_action(self, parsed_args): + if self.app.client_manager.is_network_endpoint_enabled(): + return self.take_action_network(self.app.client_manager.network, + parsed_args) + else: + return self.take_action_compute(self.app.client_manager.compute, + parsed_args) + + def get_parser(self, prog_name): + self.log.debug('get_parser(%s)', prog_name) + parser = super(NetworkAndComputeCommand, self).get_parser(prog_name) + parser = self.update_parser_common(parser) + self.log.debug('common parser: %s', parser) + if self.app.client_manager.is_network_endpoint_enabled(): + return self.update_parser_network(parser) + else: + return self.update_parser_compute(parser) + + def update_parser_common(self, parser): + """Default is no updates to parser.""" + return parser + + def update_parser_network(self, parser): + """Default is no updates to parser.""" + return parser + + def update_parser_compute(self, parser): + """Default is no updates to parser.""" + return parser + + @abc.abstractmethod + def take_action_network(self, client, parsed_args): + """Override to do something useful.""" + pass + + @abc.abstractmethod + def take_action_compute(self, client, parsed_args): + """Override to do something useful.""" + pass diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py new file mode 100644 index 0000000000..4e122f21a0 --- /dev/null +++ b/openstackclient/network/v2/security_group.py @@ -0,0 +1,40 @@ +# 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. +# + +"""Security Group action implementations""" + +from openstackclient.common import utils +from openstackclient.network import common + + +class DeleteSecurityGroup(common.NetworkAndComputeCommand): + """Delete a security group""" + + def update_parser_common(self, parser): + parser.add_argument( + 'group', + metavar='', + help='Security group to delete (name or ID)', + ) + return parser + + def take_action_network(self, client, parsed_args): + obj = client.find_security_group(parsed_args.group) + client.delete_security_group(obj) + + def take_action_compute(self, client, parsed_args): + data = utils.find_resource( + client.security_groups, + parsed_args.group, + ) + client.security_groups.delete(data.id) diff --git a/openstackclient/tests/network/test_common.py b/openstackclient/tests/network/test_common.py new file mode 100644 index 0000000000..a3396b9da8 --- /dev/null +++ b/openstackclient/tests/network/test_common.py @@ -0,0 +1,103 @@ +# 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 argparse +import mock + +from openstackclient.network import common +from openstackclient.tests import utils + + +class FakeNetworkAndComputeCommand(common.NetworkAndComputeCommand): + def update_parser_common(self, parser): + parser.add_argument( + 'common', + metavar='', + help='Common argument', + ) + return parser + + def update_parser_network(self, parser): + parser.add_argument( + 'network', + metavar='', + help='Network argument', + ) + return parser + + def update_parser_compute(self, parser): + parser.add_argument( + 'compute', + metavar='', + help='Compute argument', + ) + return parser + + def take_action_network(self, client, parsed_args): + client.network_action(parsed_args) + return 'take_action_network' + + def take_action_compute(self, client, parsed_args): + client.compute_action(parsed_args) + return 'take_action_compute' + + +class TestNetworkAndComputeCommand(utils.TestCommand): + def setUp(self): + super(TestNetworkAndComputeCommand, self).setUp() + + self.namespace = argparse.Namespace() + + # Create network client mocks. + self.app.client_manager.network = mock.Mock() + self.network = self.app.client_manager.network + self.network.network_action = mock.Mock(return_value=None) + + # Create compute client mocks. + self.app.client_manager.compute = mock.Mock() + self.compute = self.app.client_manager.compute + self.compute.compute_action = mock.Mock(return_value=None) + + # Get the command object to test + self.cmd = FakeNetworkAndComputeCommand(self.app, self.namespace) + + def test_take_action_network(self): + arglist = [ + 'common', + 'network' + ] + verifylist = [ + ('common', 'common'), + ('network', 'network') + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.network.network_action.assert_called_with(parsed_args) + self.assertEqual('take_action_network', result) + + def test_take_action_compute(self): + arglist = [ + 'common', + 'compute' + ] + verifylist = [ + ('common', 'common'), + ('compute', 'compute') + ] + + self.app.client_manager.network_endpoint_enabled = False + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.compute.compute_action.assert_called_with(parsed_args) + self.assertEqual('take_action_compute', result) diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 4c862bd332..26a8664aff 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -321,6 +321,84 @@ def get_routers(routers=None, count=2): return mock.MagicMock(side_effect=routers) +class FakeSecurityGroup(object): + """Fake one or more security groups.""" + + @staticmethod + def create_one_security_group(attrs={}, methods={}): + """Create a fake security group. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :return: + A FakeResource object, with id, name, etc. + """ + # Set default attributes. + security_group_attrs = { + 'id': 'security-group-id-' + uuid.uuid4().hex, + 'name': 'security-group-name-' + uuid.uuid4().hex, + 'description': 'security-group-description-' + uuid.uuid4().hex, + 'tenant_id': 'project-id-' + uuid.uuid4().hex, + 'security_group_rules': [], + } + + # Overwrite default attributes. + security_group_attrs.update(attrs) + + # Set default methods. + security_group_methods = {} + + # Overwrite default methods. + security_group_methods.update(methods) + + security_group = fakes.FakeResource( + info=copy.deepcopy(security_group_attrs), + methods=copy.deepcopy(security_group_methods), + loaded=True) + return security_group + + @staticmethod + def create_security_groups(attrs={}, methods={}, count=2): + """Create multiple fake security groups. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :param int count: + The number of security groups to fake + :return: + A list of FakeResource objects faking the security groups + """ + security_groups = [] + for i in range(0, count): + security_groups.append( + FakeRouter.create_one_security_group(attrs, methods)) + + return security_groups + + @staticmethod + def get_security_groups(security_groups=None, count=2): + """Get an iterable MagicMock object with a list of faked security groups. + + If security group list is provided, then initialize the Mock object + with the list. Otherwise create one. + + :param List security groups: + A list of FakeResource objects faking security groups + :param int count: + The number of security groups to fake + :return: + An iterable Mock object with side_effect set to a list of faked + security groups + """ + if security_groups is None: + security_groups = FakeRouter.create_security_groups(count) + return mock.MagicMock(side_effect=security_groups) + + class FakeSubnet(object): """Fake one or more subnets.""" diff --git a/openstackclient/tests/network/v2/test_security_group.py b/openstackclient/tests/network/v2/test_security_group.py new file mode 100644 index 0000000000..98388ec7cb --- /dev/null +++ b/openstackclient/tests/network/v2/test_security_group.py @@ -0,0 +1,99 @@ +# 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 security_group +from openstackclient.tests.network.v2 import fakes as network_fakes + + +class TestSecurityGroup(network_fakes.TestNetworkV2): + + def setUp(self): + super(TestSecurityGroup, self).setUp() + + # Get a shortcut to the network client + self.network = self.app.client_manager.network + + # Create compute client mocks. + self.app.client_manager.compute = mock.Mock() + self.compute = self.app.client_manager.compute + self.compute.security_groups = mock.Mock() + + +class TestDeleteSecurityGroupNetwork(TestSecurityGroup): + + # The security group to be deleted. + _security_group = \ + network_fakes.FakeSecurityGroup.create_one_security_group() + + def setUp(self): + super(TestDeleteSecurityGroupNetwork, self).setUp() + + self.network.delete_security_group = mock.Mock(return_value=None) + + self.network.find_security_group = mock.Mock( + return_value=self._security_group) + + # Get the command object to test + self.cmd = security_group.DeleteSecurityGroup(self.app, self.namespace) + + def test_security_group_delete(self): + arglist = [ + self._security_group.name, + ] + verifylist = [ + ('group', self._security_group.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.network.delete_security_group.assert_called_with( + self._security_group) + self.assertEqual(None, result) + + +class TestDeleteSecurityGroupCompute(TestSecurityGroup): + + # The security group to be deleted. + _security_group = \ + network_fakes.FakeSecurityGroup.create_one_security_group() + + def setUp(self): + super(TestDeleteSecurityGroupCompute, self).setUp() + + self.app.client_manager.network_endpoint_enabled = False + + self.compute.security_groups.delete = mock.Mock(return_value=None) + + self.compute.security_groups.get = mock.Mock( + return_value=self._security_group) + + # Get the command object to test + self.cmd = security_group.DeleteSecurityGroup(self.app, self.namespace) + + def test_security_group_delete(self): + arglist = [ + self._security_group.name, + ] + verifylist = [ + ('group', self._security_group.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.compute.security_groups.delete.assert_called_with( + self._security_group.id) + self.assertEqual(None, result) diff --git a/setup.cfg b/setup.cfg index 2751eb3178..8e1e3e5ab8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -103,7 +103,6 @@ openstack.compute.v2 = keypair_show = openstackclient.compute.v2.keypair:ShowKeypair security_group_create = openstackclient.compute.v2.security_group:CreateSecurityGroup - security_group_delete = openstackclient.compute.v2.security_group:DeleteSecurityGroup security_group_list = openstackclient.compute.v2.security_group:ListSecurityGroup security_group_set = openstackclient.compute.v2.security_group:SetSecurityGroup security_group_show = openstackclient.compute.v2.security_group:ShowSecurityGroup @@ -338,6 +337,7 @@ openstack.network.v2 = router_list = openstackclient.network.v2.router:ListRouter router_set = openstackclient.network.v2.router:SetRouter router_show = openstackclient.network.v2.router:ShowRouter + security_group_delete = openstackclient.network.v2.security_group:DeleteSecurityGroup subnet_list = openstackclient.network.v2.subnet:ListSubnet openstack.object_store.v1 = From aba3fd9689ebbf18f459b4e598f26b0783bda976 Mon Sep 17 00:00:00 2001 From: xiexs Date: Sat, 5 Dec 2015 15:55:59 +0800 Subject: [PATCH 0565/3095] Refactor TestVolumeCreate to use FakeVolume Class FakeVolume should be used in volume tests. Change-Id: Idf7d3e2a0654cd7d7993f169c4743b1d38902f1b Implements: blueprint improve-volume-unittest-framework Co-Authored-By: Tang Chen --- .../tests/volume/v2/test_volume.py | 133 +++++++++--------- 1 file changed, 66 insertions(+), 67 deletions(-) diff --git a/openstackclient/tests/volume/v2/test_volume.py b/openstackclient/tests/volume/v2/test_volume.py index d61a6c1540..4c583abd12 100644 --- a/openstackclient/tests/volume/v2/test_volume.py +++ b/openstackclient/tests/volume/v2/test_volume.py @@ -16,6 +16,7 @@ from mock import call +from openstackclient.common import utils from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes from openstackclient.tests.volume.v2 import fakes as volume_fakes @@ -61,26 +62,24 @@ class TestVolumeCreate(TestVolume): 'status', 'type', ) - datalist = ( - volume_fakes.volume_attachments, - volume_fakes.volume_availability_zone, - volume_fakes.volume_description, - volume_fakes.volume_id, - volume_fakes.volume_name, - volume_fakes.volume_metadata_str, - volume_fakes.volume_size, - volume_fakes.volume_snapshot_id, - volume_fakes.volume_status, - volume_fakes.volume_type, - ) def setUp(self): super(TestVolumeCreate, self).setUp() - self.volumes_mock.create.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.VOLUME), - loaded=True, + self.new_volume = volume_fakes.FakeVolume.create_one_volume() + self.volumes_mock.create.return_value = self.new_volume + + self.datalist = ( + self.new_volume.attachments, + self.new_volume.availability_zone, + self.new_volume.description, + self.new_volume.id, + self.new_volume.name, + utils.format_dict(self.new_volume.metadata), + self.new_volume.size, + self.new_volume.snapshot_id, + self.new_volume.status, + self.new_volume.volume_type, ) # Get the command object to test @@ -88,12 +87,12 @@ def setUp(self): def test_volume_create_min_options(self): arglist = [ - '--size', str(volume_fakes.volume_size), - volume_fakes.volume_name, + '--size', str(self.new_volume.size), + self.new_volume.name, ] verifylist = [ - ('size', volume_fakes.volume_size), - ('name', volume_fakes.volume_name), + ('size', self.new_volume.size), + ('name', self.new_volume.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -101,9 +100,9 @@ def test_volume_create_min_options(self): columns, data = self.cmd.take_action(parsed_args) self.volumes_mock.create.assert_called_with( - size=volume_fakes.volume_size, + size=self.new_volume.size, snapshot_id=None, - name=volume_fakes.volume_name, + name=self.new_volume.name, description=None, volume_type=None, user_id=None, @@ -119,18 +118,18 @@ def test_volume_create_min_options(self): def test_volume_create_options(self): arglist = [ - '--size', str(volume_fakes.volume_size), - '--description', volume_fakes.volume_description, - '--type', volume_fakes.volume_type, - '--availability-zone', volume_fakes.volume_availability_zone, - volume_fakes.volume_name, + '--size', str(self.new_volume.size), + '--description', self.new_volume.description, + '--type', self.new_volume.volume_type, + '--availability-zone', self.new_volume.availability_zone, + self.new_volume.name, ] verifylist = [ - ('size', volume_fakes.volume_size), - ('description', volume_fakes.volume_description), - ('type', volume_fakes.volume_type), - ('availability_zone', volume_fakes.volume_availability_zone), - ('name', volume_fakes.volume_name), + ('size', self.new_volume.size), + ('description', self.new_volume.description), + ('type', self.new_volume.volume_type), + ('availability_zone', self.new_volume.availability_zone), + ('name', self.new_volume.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -138,14 +137,14 @@ def test_volume_create_options(self): columns, data = self.cmd.take_action(parsed_args) self.volumes_mock.create.assert_called_with( - size=volume_fakes.volume_size, + size=self.new_volume.size, snapshot_id=None, - name=volume_fakes.volume_name, - description=volume_fakes.volume_description, - volume_type=volume_fakes.volume_type, + name=self.new_volume.name, + description=self.new_volume.description, + volume_type=self.new_volume.volume_type, user_id=None, project_id=None, - availability_zone=volume_fakes.volume_availability_zone, + availability_zone=self.new_volume.availability_zone, metadata=None, imageRef=None, source_volid=None @@ -169,16 +168,16 @@ def test_volume_create_user_project_id(self): ) arglist = [ - '--size', str(volume_fakes.volume_size), + '--size', str(self.new_volume.size), '--project', identity_fakes.project_id, '--user', identity_fakes.user_id, - volume_fakes.volume_name, + self.new_volume.name, ] verifylist = [ - ('size', volume_fakes.volume_size), + ('size', self.new_volume.size), ('project', identity_fakes.project_id), ('user', identity_fakes.user_id), - ('name', volume_fakes.volume_name), + ('name', self.new_volume.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -186,9 +185,9 @@ def test_volume_create_user_project_id(self): columns, data = self.cmd.take_action(parsed_args) self.volumes_mock.create.assert_called_with( - size=volume_fakes.volume_size, + size=self.new_volume.size, snapshot_id=None, - name=volume_fakes.volume_name, + name=self.new_volume.name, description=None, volume_type=None, user_id=identity_fakes.user_id, @@ -217,16 +216,16 @@ def test_volume_create_user_project_name(self): ) arglist = [ - '--size', str(volume_fakes.volume_size), + '--size', str(self.new_volume.size), '--project', identity_fakes.project_name, '--user', identity_fakes.user_name, - volume_fakes.volume_name, + self.new_volume.name, ] verifylist = [ - ('size', volume_fakes.volume_size), + ('size', self.new_volume.size), ('project', identity_fakes.project_name), ('user', identity_fakes.user_name), - ('name', volume_fakes.volume_name), + ('name', self.new_volume.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -234,9 +233,9 @@ def test_volume_create_user_project_name(self): columns, data = self.cmd.take_action(parsed_args) self.volumes_mock.create.assert_called_with( - size=volume_fakes.volume_size, + size=self.new_volume.size, snapshot_id=None, - name=volume_fakes.volume_name, + name=self.new_volume.name, description=None, volume_type=None, user_id=identity_fakes.user_id, @@ -254,13 +253,13 @@ def test_volume_create_properties(self): arglist = [ '--property', 'Alpha=a', '--property', 'Beta=b', - '--size', str(volume_fakes.volume_size), - volume_fakes.volume_name, + '--size', str(self.new_volume.size), + self.new_volume.name, ] verifylist = [ ('property', {'Alpha': 'a', 'Beta': 'b'}), - ('size', volume_fakes.volume_size), - ('name', volume_fakes.volume_name), + ('size', self.new_volume.size), + ('name', self.new_volume.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -268,9 +267,9 @@ def test_volume_create_properties(self): columns, data = self.cmd.take_action(parsed_args) self.volumes_mock.create.assert_called_with( - size=volume_fakes.volume_size, + size=self.new_volume.size, snapshot_id=None, - name=volume_fakes.volume_name, + name=self.new_volume.name, description=None, volume_type=None, user_id=None, @@ -293,13 +292,13 @@ def test_volume_create_image_id(self): arglist = [ '--image', volume_fakes.image_id, - '--size', str(volume_fakes.volume_size), - volume_fakes.volume_name, + '--size', str(self.new_volume.size), + self.new_volume.name, ] verifylist = [ ('image', volume_fakes.image_id), - ('size', volume_fakes.volume_size), - ('name', volume_fakes.volume_name), + ('size', self.new_volume.size), + ('name', self.new_volume.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -307,9 +306,9 @@ def test_volume_create_image_id(self): columns, data = self.cmd.take_action(parsed_args) self.volumes_mock.create.assert_called_with( - size=volume_fakes.volume_size, + size=self.new_volume.size, snapshot_id=None, - name=volume_fakes.volume_name, + name=self.new_volume.name, description=None, volume_type=None, user_id=None, @@ -332,13 +331,13 @@ def test_volume_create_image_name(self): arglist = [ '--image', volume_fakes.image_name, - '--size', str(volume_fakes.volume_size), - volume_fakes.volume_name, + '--size', str(self.new_volume.size), + self.new_volume.name, ] verifylist = [ ('image', volume_fakes.image_name), - ('size', volume_fakes.volume_size), - ('name', volume_fakes.volume_name), + ('size', self.new_volume.size), + ('name', self.new_volume.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -346,9 +345,9 @@ def test_volume_create_image_name(self): columns, data = self.cmd.take_action(parsed_args) self.volumes_mock.create.assert_called_with( - size=volume_fakes.volume_size, + size=self.new_volume.size, snapshot_id=None, - name=volume_fakes.volume_name, + name=self.new_volume.name, description=None, volume_type=None, user_id=None, From d324aa652bfe5527e18ebe4666a6c8905c69e05d Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 2 Feb 2016 23:44:38 +0800 Subject: [PATCH 0566/3095] Fix wrong type of volume attachments in FakeVolume The volume attachments should be a list of dict, not a single dict. Change-Id: I3cec62bcb3953e4f38f9d3dd23f3eb6ef984464c --- openstackclient/tests/volume/v2/fakes.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py index 2e58e58df9..2fc5c8ff2e 100644 --- a/openstackclient/tests/volume/v2/fakes.py +++ b/openstackclient/tests/volume/v2/fakes.py @@ -256,9 +256,10 @@ def create_one_volume(attrs={}): 'key' + uuid.uuid4().hex: 'val' + uuid.uuid4().hex}, 'snapshot_id': random.randint(1, 5), 'availability_zone': 'zone' + uuid.uuid4().hex, - 'attachments': { + 'attachments': [{ 'device': '/dev/' + uuid.uuid4().hex, - 'server_id': uuid.uuid4().hex}, + 'server_id': uuid.uuid4().hex, + }, ], } # Overwrite default attributes if there are some attributes set From e0969655ac8257760e4afdadebf568b86a0ae2b3 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Wed, 3 Feb 2016 12:04:34 +0800 Subject: [PATCH 0567/3095] Trivial: Reorder test class in test_volume.py into alphabetical order TestVolumeDelete should be after TestVolumeCreate. Change-Id: I764543a0e0723633aec6b18c2d50a01931465e6b --- .../tests/volume/v2/test_volume.py | 82 +++++++++---------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/openstackclient/tests/volume/v2/test_volume.py b/openstackclient/tests/volume/v2/test_volume.py index d61a6c1540..fe22b2137b 100644 --- a/openstackclient/tests/volume/v2/test_volume.py +++ b/openstackclient/tests/volume/v2/test_volume.py @@ -363,6 +363,47 @@ def test_volume_create_image_name(self): self.assertEqual(self.datalist, data) +class TestVolumeDelete(TestVolume): + def setUp(self): + super(TestVolumeDelete, self).setUp() + + self.volumes_mock.delete.return_value = None + + # Get the command object to mock + self.cmd = volume.DeleteVolume(self.app, None) + + def test_volume_delete_one_volume(self): + volumes = self.setup_volumes_mock(count=1) + + arglist = [ + volumes[0].id + ] + verifylist = [ + ("volumes", [volumes[0].id]) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + self.volumes_mock.delete.assert_called_with(volumes[0].id) + + def test_volume_delete_multi_volumes(self): + volumes = self.setup_volumes_mock(count=3) + + arglist = [v.id for v in volumes] + verifylist = [ + ('volumes', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + calls = [call(v.id) for v in volumes] + + self.volumes_mock.delete.assert_has_calls(calls) + + class TestVolumeList(TestVolume): columns = [ @@ -696,44 +737,3 @@ def test_volume_show(self): self.assertEqual(volume_fakes.VOLUME_columns, columns) self.assertEqual(volume_fakes.VOLUME_data, data) - - -class TestVolumeDelete(TestVolume): - def setUp(self): - super(TestVolumeDelete, self).setUp() - - self.volumes_mock.delete.return_value = None - - # Get the command object to mock - self.cmd = volume.DeleteVolume(self.app, None) - - def test_volume_delete_one_volume(self): - volumes = self.setup_volumes_mock(count=1) - - arglist = [ - volumes[0].id - ] - verifylist = [ - ("volumes", [volumes[0].id]) - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - self.cmd.take_action(parsed_args) - self.volumes_mock.delete.assert_called_with(volumes[0].id) - - def test_volume_delete_multi_volumes(self): - volumes = self.setup_volumes_mock(count=3) - - arglist = [v.id for v in volumes] - verifylist = [ - ('volumes', arglist), - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - self.cmd.take_action(parsed_args) - - calls = [call(v.id) for v in volumes] - - self.volumes_mock.delete.assert_has_calls(calls) From 0b6fdcbe4c3f3142fdd18bfd827653d894607941 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Fri, 29 Jan 2016 18:59:03 +0800 Subject: [PATCH 0568/3095] Remove marker and loop from "image list" command Since --page-size has never worked, there is no paginate logic needs to be implemented in "image list" command. So remove the unnecessary loop. And also, the marker is not necessary because --marker option has not been implemented. Will add it back when implementing --marker option. Change-Id: I71fea1502f92f447a49697edb52e8e82f336772f Partial-Bug: #1540988 --- openstackclient/image/v2/image.py | 11 +---------- openstackclient/tests/image/v2/test_image.py | 19 ++++--------------- 2 files changed, 5 insertions(+), 25 deletions(-) diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 39a5319593..842c6f2007 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -474,16 +474,7 @@ def take_action(self, parsed_args): column_headers = columns # List of image data received - data = [] - # No pages received yet, so start the page marker at None. - marker = None - while True: - page = image_client.api.image_list(marker=marker, **kwargs) - if not page: - break - data.extend(page) - # Set the marker to the id of the last item we received - marker = page[-1]['id'] + data = image_client.api.image_list(**kwargs) if parsed_args.property: # NOTE(dtroyer): coerce to a list to subscript it in py3 diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index 41abdaab61..b02b379511 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -498,9 +498,7 @@ def test_image_list_no_options(self): # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) - self.api_mock.image_list.assert_called_with( - marker=image_fakes.image_id, - ) + self.api_mock.image_list.assert_called_with() self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) @@ -521,7 +519,6 @@ def test_image_list_public_option(self): columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( public=True, - marker=image_fakes.image_id, ) self.assertEqual(self.columns, columns) @@ -543,7 +540,6 @@ def test_image_list_private_option(self): columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( private=True, - marker=image_fakes.image_id, ) self.assertEqual(self.columns, columns) @@ -565,7 +561,6 @@ def test_image_list_shared_option(self): columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( shared=True, - marker=image_fakes.image_id, ) self.assertEqual(self.columns, columns) @@ -582,9 +577,7 @@ def test_image_list_long_option(self): # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) - self.api_mock.image_list.assert_called_with( - marker=image_fakes.image_id, - ) + self.api_mock.image_list.assert_called_with() collist = ( 'ID', @@ -630,9 +623,7 @@ def test_image_list_property_option(self, sf_mock): # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) - self.api_mock.image_list.assert_called_with( - marker=image_fakes.image_id, - ) + self.api_mock.image_list.assert_called_with() sf_mock.assert_called_with( [image_fakes.IMAGE], attr='a', @@ -655,9 +646,7 @@ def test_image_list_sort_option(self, si_mock): # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) - self.api_mock.image_list.assert_called_with( - marker=image_fakes.image_id, - ) + self.api_mock.image_list.assert_called_with() si_mock.assert_called_with( [image_fakes.IMAGE], 'name:asc' From 581280386533f8780a8d2e73495d3eff38c9ad50 Mon Sep 17 00:00:00 2001 From: xiexs Date: Tue, 17 Nov 2015 05:16:09 -0500 Subject: [PATCH 0569/3095] Add limit option to "image list" command This option is quite useful if there are too many images. Change-Id: If6a901c27c5da2d1f4412e8fa9ba3bed3b72fdd9 Co-Authored-By: Tang Chen Partial-Bug: #1540988 --- doc/source/command-objects/image.rst | 5 +++++ openstackclient/image/v2/image.py | 8 ++++++++ openstackclient/tests/image/v2/test_image.py | 17 +++++++++++++++++ .../notes/bug-1540988-17841cfd5accf7f5.yaml | 6 ++++++ 4 files changed, 36 insertions(+) create mode 100644 releasenotes/notes/bug-1540988-17841cfd5accf7f5.yaml diff --git a/doc/source/command-objects/image.rst b/doc/source/command-objects/image.rst index 1528676091..94745e06c8 100644 --- a/doc/source/command-objects/image.rst +++ b/doc/source/command-objects/image.rst @@ -167,6 +167,7 @@ List available images [--property ] [--long] [--sort [:]] + [--limit ] .. option:: --public @@ -195,6 +196,10 @@ List available images Sort output by selected keys and directions(asc or desc) (default: asc), multiple keys and directions can be specified separated by comma +.. option:: --limit + + Maximum number of images to display. + image save ---------- diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 842c6f2007..67d0e7f31d 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -431,6 +431,12 @@ def get_parser(self, prog_name): "(default: asc), multiple keys and directions can be " "specified separated by comma", ) + parser.add_argument( + "--limit", + metavar="", + type=int, + help="Maximum number of images to display.", + ) return parser def take_action(self, parsed_args): @@ -443,6 +449,8 @@ def take_action(self, parsed_args): kwargs['private'] = True if parsed_args.shared: kwargs['shared'] = True + if parsed_args.limit: + kwargs['limit'] = parsed_args.limit if parsed_args.long: columns = ( diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index b02b379511..3e31d0ad62 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -655,6 +655,23 @@ def test_image_list_sort_option(self, si_mock): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) + def test_image_list_limit_option(self): + arglist = [ + '--limit', str(1), + ] + verifylist = [ + ('limit', 1), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.api_mock.image_list.assert_called_with( + limit=1, + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(len(self.datalist), len(tuple(data))) + class TestRemoveProjectImage(TestImage): diff --git a/releasenotes/notes/bug-1540988-17841cfd5accf7f5.yaml b/releasenotes/notes/bug-1540988-17841cfd5accf7f5.yaml new file mode 100644 index 0000000000..56b502eb5b --- /dev/null +++ b/releasenotes/notes/bug-1540988-17841cfd5accf7f5.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--limit`` option to ``image list`` to limit the number of images + in output. + [Bug `1540988 `_] From 499369329c493f9734248393ff19a82b5e224078 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 2 Feb 2016 01:15:25 +0800 Subject: [PATCH 0570/3095] Add --marker option to "image list" command Users could specify the last image (name or ID) of the previous page with --marker option to control the start image of the output. Change-Id: Idca0235ee83b1226b00c89cf3d38500fa898b7d0 Closes-Bug: #1540988 --- doc/source/command-objects/image.rst | 6 ++++++ openstackclient/image/v2/image.py | 11 ++++++++++ openstackclient/tests/image/v2/test_image.py | 21 +++++++++++++++++++ .../notes/bug-1540988-17841cfd5accf7f5.yaml | 3 +++ 4 files changed, 41 insertions(+) diff --git a/doc/source/command-objects/image.rst b/doc/source/command-objects/image.rst index 94745e06c8..61872ec404 100644 --- a/doc/source/command-objects/image.rst +++ b/doc/source/command-objects/image.rst @@ -168,6 +168,7 @@ List available images [--long] [--sort [:]] [--limit ] + [--marker ] .. option:: --public @@ -200,6 +201,11 @@ List available images Maximum number of images to display. +.. option:: --marker + + The last image (name or ID) of the previous page. Display list of images + after marker. Display all images if not specified. + image save ---------- diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 67d0e7f31d..3f1621814c 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -437,6 +437,14 @@ def get_parser(self, prog_name): type=int, help="Maximum number of images to display.", ) + parser.add_argument( + '--marker', + metavar='', + default=None, + help="The last image (name or ID) of the previous page. Display " + "list of images after marker. Display all images if not " + "specified." + ) return parser def take_action(self, parsed_args): @@ -451,6 +459,9 @@ def take_action(self, parsed_args): kwargs['shared'] = True if parsed_args.limit: kwargs['limit'] = parsed_args.limit + if parsed_args.marker: + kwargs['marker'] = utils.find_resource(image_client.images, + parsed_args.marker).id if parsed_args.long: columns = ( diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index 3e31d0ad62..d399c9eda9 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -672,6 +672,27 @@ def test_image_list_limit_option(self): self.assertEqual(self.columns, columns) self.assertEqual(len(self.datalist), len(tuple(data))) + @mock.patch('openstackclient.common.utils.find_resource') + def test_image_list_marker_option(self, fr_mock): + # tangchen: Since image_fakes.IMAGE is a dict, it cannot offer a .id + # operation. Will fix this by using FakeImage class instead + # of IMAGE dict. + fr_mock.return_value = mock.Mock() + fr_mock.return_value.id = image_fakes.image_id + + arglist = [ + '--marker', image_fakes.image_name, + ] + verifylist = [ + ('marker', image_fakes.image_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.api_mock.image_list.assert_called_with( + marker=image_fakes.image_id, + ) + class TestRemoveProjectImage(TestImage): diff --git a/releasenotes/notes/bug-1540988-17841cfd5accf7f5.yaml b/releasenotes/notes/bug-1540988-17841cfd5accf7f5.yaml index 56b502eb5b..eabe420b7f 100644 --- a/releasenotes/notes/bug-1540988-17841cfd5accf7f5.yaml +++ b/releasenotes/notes/bug-1540988-17841cfd5accf7f5.yaml @@ -4,3 +4,6 @@ features: Add ``--limit`` option to ``image list`` to limit the number of images in output. [Bug `1540988 `_] + - | + Add ``--marker`` option to ``image list`` to handle paginate requests. + [Bug `1540988 `_] From 65007432dc3fee8d9ab5024bbd14261362f5f348 Mon Sep 17 00:00:00 2001 From: venkatamahesh Date: Wed, 3 Feb 2016 15:47:25 +0530 Subject: [PATCH 0571/3095] Fix a spell typos Change-Id: I422fc8086af83efa4c04fd1951b4738404c1a1b7 --- doc/source/command-options.rst | 2 +- openstackclient/api/utils.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/source/command-options.rst b/doc/source/command-options.rst index edafb15322..458c56bb19 100644 --- a/doc/source/command-options.rst +++ b/doc/source/command-options.rst @@ -19,7 +19,7 @@ Boolean Options Boolean options for any command that sets a resource state, such as 'enabled' or 'public', shall always have both positive and negative options defined. -The names of those options shall either be a naturally occuring pair of +The names of those options shall either be a naturally occurring pair of words (in English) or a positive option and a negative option with `no-` prepended (such as in the traditional GNU option usage) like `--share` and `--no-share`. diff --git a/openstackclient/api/utils.py b/openstackclient/api/utils.py index b7ff7f23b6..fa759cd349 100644 --- a/openstackclient/api/utils.py +++ b/openstackclient/api/utils.py @@ -27,11 +27,11 @@ def simple_filter( be changed if any filtering occurs. :param string attr: The name of the attribute to filter. If attr does not exist no - match will succeed and no rows will be retrurned. If attr is + match will succeed and no rows will be returned. If attr is None no filtering will be performed and all rows will be returned. :param sring value: The value to filter. None is considered to be a 'no filter' value. - '' matches agains a Python empty string. + '' matches against a Python empty string. :param string property_field: The name of the data field containing a property dict to filter. If property_field is None, attr is a field name. If property_field From 962389f404dd3b740934f21e85f4b04b26b2524a Mon Sep 17 00:00:00 2001 From: Jas Date: Wed, 3 Feb 2016 14:38:04 -0600 Subject: [PATCH 0572/3095] Minor typo in help text fix misspelled 'project' in router-create help text Change-Id: I4874563c0dd7ca35ef153cf24b99e357c0c076b9 --- openstackclient/network/v2/router.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 09e0fe4c84..1fff47968b 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -102,7 +102,7 @@ def get_parser(self, prog_name): ) parser.add_argument( '--project', - metavar='', + metavar='', help="Owner's project (name or ID)", ) identity_common.add_project_domain_option_to_parser(parser) From 38ff51baef6127975d6248783347eb4ef06a0d6f Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 3 Feb 2016 15:45:55 -0600 Subject: [PATCH 0573/3095] Use assert_not_called() in common tests In https://review.openstack.org/#/c/273653/ Bryan Jones made a point about using mock_sleep.assert_not_called() rather than self.assertFalse(mock_sleep.called), so let's make that change throughout these tests. Change-Id: I826d608836955383891b303355edcca7b62faa16 --- openstackclient/tests/common/test_utils.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/openstackclient/tests/common/test_utils.py b/openstackclient/tests/common/test_utils.py index 9c02df317f..6351acd410 100644 --- a/openstackclient/tests/common/test_utils.py +++ b/openstackclient/tests/common/test_utils.py @@ -143,7 +143,7 @@ def test_wait_for_status_ok(self, mock_sleep): status_f = mock.Mock(return_value=resource) res_id = str(uuid.uuid4()) self.assertTrue(utils.wait_for_status(status_f, res_id,)) - self.assertFalse(mock_sleep.called) + mock_sleep.assert_not_called() @mock.patch.object(time, 'sleep') def test_wait_for_status_ok__with_overrides(self, mock_sleep): @@ -154,7 +154,7 @@ def test_wait_for_status_ok__with_overrides(self, mock_sleep): self.assertTrue(utils.wait_for_status(status_f, res_id, status_field='my_status', success_status=['complete'])) - self.assertFalse(mock_sleep.called) + mock_sleep.assert_not_called() @mock.patch.object(time, 'sleep') def test_wait_for_status_error(self, mock_sleep): @@ -163,7 +163,7 @@ def test_wait_for_status_error(self, mock_sleep): status_f = mock.Mock(return_value=resource) res_id = str(uuid.uuid4()) self.assertFalse(utils.wait_for_status(status_f, res_id)) - self.assertFalse(mock_sleep.called) + mock_sleep.assert_not_called() @mock.patch.object(time, 'sleep') def test_wait_for_status_error_with_overrides(self, mock_sleep): @@ -174,7 +174,7 @@ def test_wait_for_status_error_with_overrides(self, mock_sleep): self.assertFalse(utils.wait_for_status(status_f, res_id, status_field='my_status', error_status=['failed'])) - self.assertFalse(mock_sleep.called) + mock_sleep.assert_not_called() @mock.patch.object(time, 'sleep') def test_wait_for_delete_ok(self, mock_sleep): @@ -210,7 +210,7 @@ def test_wait_for_delete_error(self, mock_sleep): manager = mock.MagicMock(get=mock_get) res_id = str(uuid.uuid4()) self.assertFalse(utils.wait_for_delete(manager, res_id)) - self.assertFalse(mock_sleep.called) + mock_sleep.assert_not_called() @mock.patch.object(time, 'sleep') def test_wait_for_delete_error_with_overrides(self, mock_sleep): @@ -222,7 +222,7 @@ def test_wait_for_delete_error_with_overrides(self, mock_sleep): self.assertFalse(utils.wait_for_delete(manager, res_id, status_field='my_status', error_status=['failed'])) - self.assertFalse(mock_sleep.called) + mock_sleep.assert_not_called() @mock.patch.object(time, 'sleep') def test_wait_for_delete_error_with_overrides_exception(self, mock_sleep): @@ -232,7 +232,7 @@ def test_wait_for_delete_error_with_overrides_exception(self, mock_sleep): res_id = str(uuid.uuid4()) self.assertTrue(utils.wait_for_delete(manager, res_id, exception_name=['Exception'])) - self.assertFalse(mock_sleep.called) + mock_sleep.assert_not_called() def test_build_kwargs_dict_value_set(self): self.assertEqual({'arg_bla': 'bla'}, From b95ce714dc336bf1a77e17e7f0160120da9e130a Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Tue, 11 Aug 2015 11:30:31 -0600 Subject: [PATCH 0574/3095] Allow custom log levels for other loggers It would be convenient to be able to enable special logging for various components that openstack uses. The biggest thing is the --debug prints a lot of information when often all I want to see is the outgoing requests/responses. To get just that logged you would put this in your clouds.yaml: logging: keystoneclient.session: debug Closes-Bug: #1484660 Change-Id: I15c2607e8262f10903dd831ee8622fb5d6315310 --- openstackclient/common/logs.py | 46 +++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/openstackclient/common/logs.py b/openstackclient/common/logs.py index 6d1aec13b7..7ad6e832b9 100644 --- a/openstackclient/common/logs.py +++ b/openstackclient/common/logs.py @@ -18,6 +18,13 @@ import warnings +def get_loggers(): + loggers = {} + for logkey in logging.Logger.manager.loggerDict.keys(): + loggers[logkey] = logging.getLevelName(logging.getLogger(logkey).level) + return loggers + + def log_level_from_options(options): # if --debug, --quiet or --verbose is not specified, # the default logging level is warning @@ -34,6 +41,17 @@ def log_level_from_options(options): return log_level +def log_level_from_string(level_string): + log_level = { + 'critical': logging.CRITICAL, + 'error': logging.ERROR, + 'warning': logging.WARNING, + 'info': logging.INFO, + 'debug': logging.DEBUG, + }.get(level_string, logging.WARNING) + return log_level + + def log_level_from_config(config): # Check the command line option verbose_level = config.get('verbose_level') @@ -49,15 +67,7 @@ def log_level_from_config(config): verbose_level = 'info' else: verbose_level = 'debug' - - log_level = { - 'critical': logging.CRITICAL, - 'error': logging.ERROR, - 'warning': logging.WARNING, - 'info': logging.INFO, - 'debug': logging.DEBUG, - }.get(verbose_level, logging.WARNING) - return log_level + return log_level_from_string(verbose_level) def set_warning_filter(log_level): @@ -168,3 +178,21 @@ def configure(self, cloud_config): self.file_logger.setFormatter(_FileFormatter(config=cloud_config)) self.file_logger.setLevel(log_level) self.root_logger.addHandler(self.file_logger) + + logconfig = cloud_config.config.get('logging', None) + if logconfig: + highest_level = logging.NOTSET + for k in logconfig.keys(): + level = log_level_from_string(logconfig[k]) + logging.getLogger(k).setLevel(level) + if (highest_level < level): + highest_level = level + self.console_logger.setLevel(highest_level) + if self.file_logger: + self.file_logger.setLevel(highest_level) + # loggers that are not set will use the handler level, so we + # need to set the global level for all the loggers + for logkey in logging.Logger.manager.loggerDict.keys(): + logger = logging.getLogger(logkey) + if logger.level == logging.NOTSET: + logger.setLevel(log_level) From 05b18749ef96e68198da974f3bc6ec41fd014266 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Fri, 5 Feb 2016 02:43:08 +0800 Subject: [PATCH 0575/3095] Add unit tests for "hypervisor list" command There is no unit tests for "hypervisor" command. This patch introudces a new class FakeHypervisor to fake one or more hypervisors, and a base class TestHypervisor. Also adds hypervisors mock to fake compute client. And also, this patch adds unit tests for "hypervisor list" command. Change-Id: I18733eae1a8f4fff72e830d9a060fb8f0f58fbf5 --- openstackclient/tests/compute/v2/fakes.py | 45 +++++++ .../tests/compute/v2/test_hypervisor.py | 115 ++++++++++++++++++ 2 files changed, 160 insertions(+) create mode 100644 openstackclient/tests/compute/v2/test_hypervisor.py diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index a90c9ee71a..1a876a229b 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -106,6 +106,8 @@ def __init__(self, **kwargs): self.quota_classes.resource_class = fakes.FakeResource(None, {}) self.volumes = mock.Mock() self.volumes.resource_class = fakes.FakeResource(None, {}) + self.hypervisors = mock.Mock() + self.hypervisors.resource_class = fakes.FakeResource(None, {}) self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] @@ -140,6 +142,49 @@ def setUp(self): ) +class FakeHypervisor(object): + """Fake one or more hypervisor.""" + + @staticmethod + def create_one_hypervisor(attrs={}): + """Create a fake hypervisor. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object, with id, hypervisor_hostname, and so on + """ + # Set default attributes. + hypervisor_info = { + 'id': 'hypervisor-id-' + uuid.uuid4().hex, + 'hypervisor_hostname': 'hypervisor-hostname-' + uuid.uuid4().hex, + } + + # Overwrite default attributes. + hypervisor_info.update(attrs) + + hypervisor = fakes.FakeResource(info=copy.deepcopy(hypervisor_info), + loaded=True) + return hypervisor + + @staticmethod + def create_hypervisors(attrs={}, count=2): + """Create multiple fake hypervisors. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of hypervisors to fake + :return: + A list of FakeResource objects faking the hypervisors + """ + hypervisors = [] + for i in range(0, count): + hypervisors.append(FakeHypervisor.create_one_hypervisor(attrs)) + + return hypervisors + + class FakeServer(object): """Fake one or more compute servers.""" diff --git a/openstackclient/tests/compute/v2/test_hypervisor.py b/openstackclient/tests/compute/v2/test_hypervisor.py new file mode 100644 index 0000000000..1f52ee0943 --- /dev/null +++ b/openstackclient/tests/compute/v2/test_hypervisor.py @@ -0,0 +1,115 @@ +# Copyright 2016 EasyStack Corporation +# +# 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.common import exceptions +from openstackclient.compute.v2 import hypervisor +from openstackclient.tests.compute.v2 import fakes as compute_fakes + + +class TestHypervisor(compute_fakes.TestComputev2): + + def setUp(self): + super(TestHypervisor, self).setUp() + + # Get a shortcut to the compute client hypervisors mock + self.hypervisors_mock = self.app.client_manager.compute.hypervisors + self.hypervisors_mock.reset_mock() + + +class TestHypervisorList(TestHypervisor): + + def setUp(self): + super(TestHypervisorList, self).setUp() + + # Fake hypervisors to be listed up + self.hypervisors = compute_fakes.FakeHypervisor.create_hypervisors() + self.hypervisors_mock.list.return_value = self.hypervisors + + self.columns = ( + "ID", + "Hypervisor Hostname" + ) + self.data = ( + ( + self.hypervisors[0].id, + self.hypervisors[0].hypervisor_hostname, + ), + ( + self.hypervisors[1].id, + self.hypervisors[1].hypervisor_hostname, + ), + ) + + # Get the command object to test + self.cmd = hypervisor.ListHypervisor(self.app, None) + + def test_hypervisor_list_no_option(self): + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstractmethod take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + self.hypervisors_mock.list.assert_called_with() + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, tuple(data)) + + def test_hypervisor_list_matching_option_found(self): + arglist = [ + '--matching', self.hypervisors[0].hypervisor_hostname, + ] + verifylist = [ + ('matching', self.hypervisors[0].hypervisor_hostname), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Fake the return value of search() + self.hypervisors_mock.search.return_value = [self.hypervisors[0]] + self.data = ( + ( + self.hypervisors[0].id, + self.hypervisors[0].hypervisor_hostname, + ), + ) + + # In base command class Lister in cliff, abstractmethod take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + self.hypervisors_mock.search.assert_called_with( + self.hypervisors[0].hypervisor_hostname + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, tuple(data)) + + def test_hypervisor_list_matching_option_not_found(self): + arglist = [ + '--matching', 'xxx', + ] + verifylist = [ + ('matching', 'xxx'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Fake exception raised from search() + self.hypervisors_mock.search.side_effect = exceptions.NotFound(None) + + self.assertRaises(exceptions.NotFound, + self.cmd.take_action, + parsed_args) From 6b3583ab06459dad8c2aa1b762538516047b8b41 Mon Sep 17 00:00:00 2001 From: xiexs Date: Tue, 26 Jan 2016 00:22:56 -0500 Subject: [PATCH 0576/3095] Add support for triggering an crash dump The triggering crash dump feature is supported by nova [1] and novaclient [2] now, it's time to introduce this feature into OSC correspondingly. [1]The change id is: I6ed777ff637254b4b79417008f9055dd19fc7405 [2]The change id is: If03b1864bbe7074c720b946fc2700bd5d07debc3 Change-Id: I5a411f283fdf0fc3c00380d069848a332c799cdd Closes-Bug: #1538372 Co-Authored-By: Tang Chen --- doc/source/command-objects/server.rst | 19 +++++++++++++ doc/source/commands.rst | 1 + openstackclient/compute/v2/server.py | 28 +++++++++++++++++++ .../tests/compute/v2/test_server.py | 20 +++++++++++++ .../notes/bug-1538372-ef3a30298357f972.yaml | 5 ++++ setup.cfg | 1 + 6 files changed, 74 insertions(+) create mode 100644 releasenotes/notes/bug-1538372-ef3a30298357f972.yaml diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index fc27597125..d50ad37edd 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -168,6 +168,25 @@ Delete server(s) Server(s) to delete (name or ID) +server dump create +------------------ +Create a dump file in server(s) + +Trigger crash dump in server(s) with features like kdump in Linux. It will +create a dump file in the server(s) dumping the server(s)' memory, and also +crash the server(s). OSC sees the dump file (server dump) as a kind of +resource. + +.. program:: server dump create +.. code:: bash + + os server dump create + [ ...] + +.. describe:: + + Server(s) to create dump file (name or ID) + server list ----------- diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 1c4f84b28b..bf5d7035a9 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -112,6 +112,7 @@ referring to both Compute and Volume quotas. * ``security group``: (**Compute**, **Network**) - groups of network access rules * ``security group rule``: (**Compute**, **Network**) - the individual rules that define protocol/IP/port access * ``server``: (**Compute**) virtual machine instance +* ``server dump``: (**Compute**) a dump file of a server created by features like kdump * ``server image``: (**Compute**) saved server disk image * ``service``: (**Identity**) a cloud service * ``service provider``: (**Identity**) a resource that consumes assertions from an ``identity provider`` diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index dd7bc470ba..4cb94822bd 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -542,6 +542,34 @@ def take_action(self, parsed_args): return zip(*sorted(six.iteritems(details))) +class CreateServerDump(command.Command): + """Create a dump file in server(s) + + Trigger crash dump in server(s) with features like kdump in Linux. + It will create a dump file in the server(s) dumping the server(s)' + memory, and also crash the server(s). OSC sees the dump file + (server dump) as a kind of resource. + """ + + def get_parser(self, prog_name): + parser = super(CreateServerDump, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + nargs='+', + help=_('Server(s) to create dump file (name or ID)'), + ) + return parser + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + for server in parsed_args.server: + utils.find_resource( + compute_client.servers, + server, + ).trigger_crash_dump() + + class CreateServerImage(command.ShowOne): """Create a new disk image from a running server""" diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index f6b622916b..141e137000 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -490,6 +490,26 @@ def test_server_delete_wait_fails(self, mock_wait_for_delete): ) +class TestServerDumpCreate(TestServer): + + def setUp(self): + super(TestServerDumpCreate, self).setUp() + + # Get the command object to test + self.cmd = server.CreateServerDump(self.app, None) + + # Set methods to be tested. + self.methods = { + 'trigger_crash_dump': None, + } + + def test_server_dump_one_server(self): + self.run_method_with_servers('trigger_crash_dump', 1) + + def test_server_dump_multi_servers(self): + self.run_method_with_servers('trigger_crash_dump', 3) + + class TestServerImageCreate(TestServer): columns = ( diff --git a/releasenotes/notes/bug-1538372-ef3a30298357f972.yaml b/releasenotes/notes/bug-1538372-ef3a30298357f972.yaml new file mode 100644 index 0000000000..e2c2d24765 --- /dev/null +++ b/releasenotes/notes/bug-1538372-ef3a30298357f972.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add support for the `server dump create` command + [Bug `1538372 `_] diff --git a/setup.cfg b/setup.cfg index c4d1000744..4cf9622ceb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -133,6 +133,7 @@ openstack.compute.v2 = server_start = openstackclient.compute.v2.server:StartServer server_stop = openstackclient.compute.v2.server:StopServer server_suspend = openstackclient.compute.v2.server:SuspendServer + server_dump_create = openstackclient.compute.v2.server:CreateServerDump server_unlock = openstackclient.compute.v2.server:UnlockServer server_unpause = openstackclient.compute.v2.server:UnpauseServer server_unrescue = openstackclient.compute.v2.server:UnrescueServer From e108719f77c546e3a02147f20850c9d5bf3d0faf Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Thu, 11 Jun 2015 14:17:31 -0600 Subject: [PATCH 0577/3095] Add functional tests for snapshots Change-Id: I863583d6c8263d144d45a0443fc6af04301d23e7 --- functional/tests/volume/v1/test_snapshot.py | 82 +++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 functional/tests/volume/v1/test_snapshot.py diff --git a/functional/tests/volume/v1/test_snapshot.py b/functional/tests/volume/v1/test_snapshot.py new file mode 100644 index 0000000000..c43a7456ae --- /dev/null +++ b/functional/tests/volume/v1/test_snapshot.py @@ -0,0 +1,82 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import time +import uuid + +from functional.common import test + + +class SnapshotTests(test.TestCase): + """Functional tests for snapshot. """ + + VOLLY = uuid.uuid4().hex + NAME = uuid.uuid4().hex + OTHER_NAME = uuid.uuid4().hex + HEADERS = ['"Name"'] + + @classmethod + def wait_for_status(cls, command, status, tries): + opts = cls.get_show_opts(['status']) + for attempt in range(tries): + time.sleep(1) + raw_output = cls.openstack(command + opts) + if (raw_output == status): + return + cls.assertOutput(status, raw_output) + + @classmethod + def setUpClass(cls): + cls.openstack('volume create --size 1 ' + cls.VOLLY) + cls.wait_for_status('volume show ' + cls.VOLLY, 'available\n', 3) + opts = cls.get_show_opts(['status']) + raw_output = cls.openstack('snapshot create --name ' + cls.NAME + + ' ' + cls.VOLLY + opts) + cls.assertOutput('creating\n', raw_output) + cls.wait_for_status('snapshot show ' + cls.NAME, 'available\n', 3) + + @classmethod + def tearDownClass(cls): + # Rename test + raw_output = cls.openstack( + 'snapshot set --name ' + cls.OTHER_NAME + ' ' + cls.NAME) + cls.assertOutput('', raw_output) + # Delete test + raw_output = cls.openstack('snapshot delete ' + cls.OTHER_NAME) + cls.assertOutput('', raw_output) + cls.openstack('volume delete --force ' + cls.VOLLY, fail_ok=True) + + def test_snapshot_list(self): + opts = self.get_list_opts(self.HEADERS) + raw_output = self.openstack('snapshot list' + opts) + self.assertIn(self.NAME, raw_output) + + def test_snapshot_properties(self): + raw_output = self.openstack( + 'snapshot set --property a=b --property c=d ' + self.NAME) + self.assertEqual("", raw_output) + opts = self.get_show_opts(["properties"]) + raw_output = self.openstack('snapshot show ' + self.NAME + opts) + self.assertEqual("a='b', c='d'\n", raw_output) + + raw_output = self.openstack('snapshot unset --property a ' + self.NAME) + self.assertEqual("", raw_output) + raw_output = self.openstack('snapshot show ' + self.NAME + opts) + self.assertEqual("c='d'\n", raw_output) + + def test_snapshot_set(self): + raw_output = self.openstack( + 'snapshot set --description backup ' + self.NAME) + self.assertEqual("", raw_output) + opts = self.get_show_opts(["description", "name"]) + raw_output = self.openstack('snapshot show ' + self.NAME + opts) + self.assertEqual("backup\n" + self.NAME + "\n", raw_output) From a83602341121ccd252a8eaaa390f387b5230cd75 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Fri, 5 Feb 2016 09:52:34 -0600 Subject: [PATCH 0578/3095] Refactor security group functional tests Moved the functional tests for "os security group" and "os security group rule" from the compute to the network directory to align with the refactoring to the commands. Change-Id: Ief6ab17775c6d7e3bef58d9fa025d9dd520b7370 Partial-Bug: #1519511 Partial-Bug: #1519512 Related-to: blueprint neutron-client --- functional/tests/{compute => network}/v2/test_security_group.py | 0 .../tests/{compute => network}/v2/test_security_group_rule.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename functional/tests/{compute => network}/v2/test_security_group.py (100%) rename functional/tests/{compute => network}/v2/test_security_group_rule.py (100%) diff --git a/functional/tests/compute/v2/test_security_group.py b/functional/tests/network/v2/test_security_group.py similarity index 100% rename from functional/tests/compute/v2/test_security_group.py rename to functional/tests/network/v2/test_security_group.py diff --git a/functional/tests/compute/v2/test_security_group_rule.py b/functional/tests/network/v2/test_security_group_rule.py similarity index 100% rename from functional/tests/compute/v2/test_security_group_rule.py rename to functional/tests/network/v2/test_security_group_rule.py From 514a168656823eb4897e38a15a1a769a7d18f44f Mon Sep 17 00:00:00 2001 From: TerryHowe Date: Thu, 15 Oct 2015 08:39:23 -0600 Subject: [PATCH 0579/3095] Add recursive object delete for containers Change-Id: Ib291e79864c218464e842a08efd3742193ba5ff0 --- doc/source/command-objects/container.rst | 7 +- functional/tests/object/v1/test_object.py | 5 +- openstackclient/object/v1/container.py | 14 +++ .../tests/object/v1/test_container.py | 90 +++++++++++++++++++ 4 files changed, 114 insertions(+), 2 deletions(-) diff --git a/doc/source/command-objects/container.rst b/doc/source/command-objects/container.rst index e6517e9bb8..130a6b3dad 100644 --- a/doc/source/command-objects/container.rst +++ b/doc/source/command-objects/container.rst @@ -28,8 +28,13 @@ Delete container .. code:: bash os container delete + [-r] | [--recursive] [ ...] +.. option:: --recursive, -r + + Recursively delete objects in container before container delete + .. describe:: Container(s) to delete @@ -40,7 +45,7 @@ container list List containers .. program:: container list -.. code::bash +.. code:: bash os container list [--prefix ] diff --git a/functional/tests/object/v1/test_object.py b/functional/tests/object/v1/test_object.py index 289e1ca77c..cd98012cb6 100644 --- a/functional/tests/object/v1/test_object.py +++ b/functional/tests/object/v1/test_object.py @@ -74,5 +74,8 @@ def test_object(self): + ' ' + self.OBJECT_NAME) self.assertEqual(0, len(raw_output)) - raw_output = self.openstack('container delete ' + self.CONTAINER_NAME) + self.openstack('object create ' + self.CONTAINER_NAME + + ' ' + self.OBJECT_NAME) + raw_output = self.openstack('container delete -r ' + + self.CONTAINER_NAME) self.assertEqual(0, len(raw_output)) diff --git a/openstackclient/object/v1/container.py b/openstackclient/object/v1/container.py index e70afd9d0e..80b8423824 100644 --- a/openstackclient/object/v1/container.py +++ b/openstackclient/object/v1/container.py @@ -58,6 +58,12 @@ class DeleteContainer(command.Command): def get_parser(self, prog_name): parser = super(DeleteContainer, self).get_parser(prog_name) + parser.add_argument( + '--recursive', '-r', + action='store_true', + default=False, + help='Recursively delete objects and container', + ) parser.add_argument( 'containers', metavar='', @@ -69,6 +75,14 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): for container in parsed_args.containers: + if parsed_args.recursive: + objs = self.app.client_manager.object_store.object_list( + container=container) + for obj in objs: + self.app.client_manager.object_store.object_delete( + container=container, + object=obj['name'], + ) self.app.client_manager.object_store.container_delete( container=container, ) diff --git a/openstackclient/tests/object/v1/test_container.py b/openstackclient/tests/object/v1/test_container.py index afcb3386b4..d34d73e2cd 100644 --- a/openstackclient/tests/object/v1/test_container.py +++ b/openstackclient/tests/object/v1/test_container.py @@ -44,6 +44,96 @@ def setUp(self): self.api = self.app.client_manager.object_store +@mock.patch('openstackclient.api.object_store_v1.APIv1.object_delete') +@mock.patch('openstackclient.api.object_store_v1.APIv1.object_list') +@mock.patch('openstackclient.api.object_store_v1.APIv1.container_delete') +class TestContainerDelete(TestContainer): + + def setUp(self): + super(TestContainerDelete, self).setUp() + + # Get the command object to test + self.cmd = container.DeleteContainer(self.app, None) + + def test_container_delete(self, c_mock, o_list_mock, o_delete_mock): + c_mock.return_value = None + + arglist = [ + object_fakes.container_name, + ] + verifylist = [ + ('containers', [object_fakes.container_name]), + ('recursive', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertEqual(None, self.cmd.take_action(parsed_args)) + + kwargs = {} + c_mock.assert_called_with( + container=object_fakes.container_name, + **kwargs + ) + self.assertFalse(o_list_mock.called) + self.assertFalse(o_delete_mock.called) + + def test_recursive_delete(self, c_mock, o_list_mock, o_delete_mock): + c_mock.return_value = None + o_list_mock.return_value = [object_fakes.OBJECT] + o_delete_mock.return_value = None + + arglist = [ + '--recursive', + object_fakes.container_name, + ] + verifylist = [ + ('containers', [object_fakes.container_name]), + ('recursive', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertEqual(None, self.cmd.take_action(parsed_args)) + + kwargs = {} + c_mock.assert_called_with( + container=object_fakes.container_name, + **kwargs + ) + o_list_mock.assert_called_with(container=object_fakes.container_name) + o_delete_mock.assert_called_with( + container=object_fakes.container_name, + object=object_fakes.OBJECT['name'], + ) + + def test_r_delete(self, c_mock, o_list_mock, o_delete_mock): + c_mock.return_value = None + o_list_mock.return_value = [object_fakes.OBJECT] + o_delete_mock.return_value = None + + arglist = [ + '-r', + object_fakes.container_name, + ] + verifylist = [ + ('containers', [object_fakes.container_name]), + ('recursive', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertEqual(None, self.cmd.take_action(parsed_args)) + + kwargs = {} + c_mock.assert_called_with( + container=object_fakes.container_name, + **kwargs + ) + o_list_mock.assert_called_with(container=object_fakes.container_name) + o_delete_mock.assert_called_with( + container=object_fakes.container_name, + object=object_fakes.OBJECT['name'], + ) + + @mock.patch( 'openstackclient.api.object_store_v1.APIv1.container_list' ) From 99f6795189b188b8c560507b7f8101b1cd820392 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 5 Feb 2016 15:38:55 -0600 Subject: [PATCH 0580/3095] Fix some release note formatting Fix the current notes that used single-backticks where double-backticks should have been used. Change-Id: I454e14dd084c9b706fab3255170a79765091a497 --- releasenotes/notes/bug-1520003-505af921c8afffc9.yaml | 2 +- releasenotes/notes/bug-1527833-42cde11d28b09ac3.yaml | 4 ++-- releasenotes/notes/bug-1532945-1a5485b8d0ebddb8.yaml | 6 +++--- releasenotes/notes/bug-1534202-1ba78f0bb744961f.yaml | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/releasenotes/notes/bug-1520003-505af921c8afffc9.yaml b/releasenotes/notes/bug-1520003-505af921c8afffc9.yaml index ca94b958f0..c7d702aaf3 100644 --- a/releasenotes/notes/bug-1520003-505af921c8afffc9.yaml +++ b/releasenotes/notes/bug-1520003-505af921c8afffc9.yaml @@ -1,5 +1,5 @@ --- fixes: - | - Add remote security group to `os security group rule list` + Add remote security group to ``os security group rule list`` [Bug `1520003 `_] diff --git a/releasenotes/notes/bug-1527833-42cde11d28b09ac3.yaml b/releasenotes/notes/bug-1527833-42cde11d28b09ac3.yaml index 21f748bbe0..76150755b8 100644 --- a/releasenotes/notes/bug-1527833-42cde11d28b09ac3.yaml +++ b/releasenotes/notes/bug-1527833-42cde11d28b09ac3.yaml @@ -1,7 +1,7 @@ --- other: - - Change the `--owner` option to `--project` in `image create` - and `image set` commands. `--owner` is deprecated and no longer + - Change the ``--owner`` option to ``--project`` in ``image create`` + and ``image set`` commands. ``--owner`` is deprecated and no longer documented but is still accepted; a warning message will be shown if it is used. [Bug `1527833 `_] diff --git a/releasenotes/notes/bug-1532945-1a5485b8d0ebddb8.yaml b/releasenotes/notes/bug-1532945-1a5485b8d0ebddb8.yaml index bee8102bcb..d2024bdc2c 100644 --- a/releasenotes/notes/bug-1532945-1a5485b8d0ebddb8.yaml +++ b/releasenotes/notes/bug-1532945-1a5485b8d0ebddb8.yaml @@ -1,8 +1,8 @@ --- features: - | - Add volume support to `os availability zone list` + Add volume support to ``os availability zone list`` [Bug `1532945 `_] - * New `--compute` option to only list compute availability zones. - * New `--volume` option to only list volume availability zones. + * New ``--compute`` option to only list compute availability zones. + * New ``--volume`` option to only list volume availability zones. diff --git a/releasenotes/notes/bug-1534202-1ba78f0bb744961f.yaml b/releasenotes/notes/bug-1534202-1ba78f0bb744961f.yaml index 36c0a3dbf7..1c1a56d57b 100644 --- a/releasenotes/notes/bug-1534202-1ba78f0bb744961f.yaml +++ b/releasenotes/notes/bug-1534202-1ba78f0bb744961f.yaml @@ -1,7 +1,7 @@ --- features: - | - Add network support to `os availability zone list` + Add network support to ``os availability zone list`` [Bug `1534202 `_] - * New `--network` option to only list network availability zones. + * New ``--network`` option to only list network availability zones. From 552eded9ad102bfafee734b2a7a4c9aa8d806ce6 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 5 Feb 2016 16:24:58 -0600 Subject: [PATCH 0581/3095] Fix formatting in release 2.0.0 notes This is a test to see if we can update release notes from previous releases. This may not work and will be abandoned if so. Change-Id: Id85a25c793aa403025c7e32038ea7072139700db --- releasenotes/notes/bug-1519181-932c1ff07ef16666.yaml | 2 +- releasenotes/notes/bug-1520115-0367e1c8917dc912.yaml | 3 ++- releasenotes/notes/bug-1520541-44d45e4693089c03.yaml | 3 ++- .../no-project-usage-list-e88eb49aa2e96cf7.yaml | 2 +- .../notes/server-changes-3962541b6ebdbbd8.yaml | 12 ++++++------ 5 files changed, 12 insertions(+), 10 deletions(-) diff --git a/releasenotes/notes/bug-1519181-932c1ff07ef16666.yaml b/releasenotes/notes/bug-1519181-932c1ff07ef16666.yaml index 919b0ec803..6a1ac205b9 100644 --- a/releasenotes/notes/bug-1519181-932c1ff07ef16666.yaml +++ b/releasenotes/notes/bug-1519181-932c1ff07ef16666.yaml @@ -1,6 +1,6 @@ --- fixes: - | - Add Status column to default `image list` output (v1 and v2) + Add Status column to default ``image list`` output (v1 and v2) [Bug `1519181 `_] diff --git a/releasenotes/notes/bug-1520115-0367e1c8917dc912.yaml b/releasenotes/notes/bug-1520115-0367e1c8917dc912.yaml index 98829c7595..6d1e52954e 100644 --- a/releasenotes/notes/bug-1520115-0367e1c8917dc912.yaml +++ b/releasenotes/notes/bug-1520115-0367e1c8917dc912.yaml @@ -1,5 +1,6 @@ --- fixes: - | - Fix `volume type create`, passes incorrect privacy argument + Fix ``--public|--private`` options for ``volume type create`` command + to correctly pass privacy argument to client library [Bug `1520115 `_] diff --git a/releasenotes/notes/bug-1520541-44d45e4693089c03.yaml b/releasenotes/notes/bug-1520541-44d45e4693089c03.yaml index d3e445574c..82c0fb1088 100644 --- a/releasenotes/notes/bug-1520541-44d45e4693089c03.yaml +++ b/releasenotes/notes/bug-1520541-44d45e4693089c03.yaml @@ -1,5 +1,6 @@ --- fixes: - | - `volume delete` only deletes last volume specified + Fix ``volume delete`` command to delete all specified volumes rather + than only the last volume [Bug `1520541 `_] diff --git a/releasenotes/notes/no-project-usage-list-e88eb49aa2e96cf7.yaml b/releasenotes/notes/no-project-usage-list-e88eb49aa2e96cf7.yaml index 67c58700f0..3ae3ff9034 100644 --- a/releasenotes/notes/no-project-usage-list-e88eb49aa2e96cf7.yaml +++ b/releasenotes/notes/no-project-usage-list-e88eb49aa2e96cf7.yaml @@ -1,3 +1,3 @@ --- upgrade: - - Removed the deprecated command `project usage list` in favor of `usage list` + - Removed the deprecated command ``project usage list`` in favor of ``usage list`` diff --git a/releasenotes/notes/server-changes-3962541b6ebdbbd8.yaml b/releasenotes/notes/server-changes-3962541b6ebdbbd8.yaml index ae3739bd2b..3830273515 100644 --- a/releasenotes/notes/server-changes-3962541b6ebdbbd8.yaml +++ b/releasenotes/notes/server-changes-3962541b6ebdbbd8.yaml @@ -1,10 +1,10 @@ --- other: - | - `server` resource changes: + Changes to ``server`` resource commands: - * Added `--limit` and `--marker` to `server list` - * Added `server shelve` - * Added `server unshelve` - * `server resume` now takes multiple servers - * `server suspend` now takes multiple servers + * Added ``--limit`` and ``--marker`` to ``server list`` + * Added ``server shelve`` + * Added ``server unshelve`` + * ``server resume`` now takes multiple server arguments + * ``server suspend`` now takes multiple server arguments From 553631a5d46e65f7da3323f87ea1c6d020d4f5d7 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Fri, 5 Feb 2016 01:48:42 +0800 Subject: [PATCH 0582/3095] Move security_groups mock definition to FakeComputev2Client All compute client related mocks should be defined in FakeComputev2Client. Change-Id: Ie75385af772ca23286c4cf131d3d54cc14f20a30 --- openstackclient/tests/compute/v2/fakes.py | 14 ++++++++++++++ .../tests/compute/v2/test_security_group.py | 5 ++--- .../tests/compute/v2/test_security_group_rule.py | 5 ++--- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 1a876a229b..051d8d545a 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -90,25 +90,39 @@ class FakeComputev2Client(object): def __init__(self, **kwargs): self.availability_zones = mock.Mock() self.availability_zones.resource_class = fakes.FakeResource(None, {}) + self.images = mock.Mock() self.images.resource_class = fakes.FakeResource(None, {}) + self.servers = mock.Mock() self.servers.resource_class = fakes.FakeResource(None, {}) + self.services = mock.Mock() self.services.resource_class = fakes.FakeResource(None, {}) + self.extensions = mock.Mock() self.extensions.resource_class = fakes.FakeResource(None, {}) + self.flavors = mock.Mock() self.flavors.resource_class = fakes.FakeResource(None, {}) + self.quotas = mock.Mock() self.quotas.resource_class = fakes.FakeResource(None, {}) + self.quota_classes = mock.Mock() self.quota_classes.resource_class = fakes.FakeResource(None, {}) + self.volumes = mock.Mock() self.volumes.resource_class = fakes.FakeResource(None, {}) + self.hypervisors = mock.Mock() self.hypervisors.resource_class = fakes.FakeResource(None, {}) + + self.security_groups = mock.Mock() + self.security_groups.resource_class = fakes.FakeResource(None, {}) + self.auth_token = kwargs['token'] + self.management_url = kwargs['endpoint'] diff --git a/openstackclient/tests/compute/v2/test_security_group.py b/openstackclient/tests/compute/v2/test_security_group.py index 87cc4870d8..a3f7dcb65b 100644 --- a/openstackclient/tests/compute/v2/test_security_group.py +++ b/openstackclient/tests/compute/v2/test_security_group.py @@ -43,9 +43,8 @@ class TestSecurityGroup(compute_fakes.TestComputev2): def setUp(self): super(TestSecurityGroup, self).setUp() - self.secgroups_mock = mock.Mock() - self.secgroups_mock.resource_class = fakes.FakeResource(None, {}) - self.app.client_manager.compute.security_groups = self.secgroups_mock + # Get a shortcut compute client security_groups mock + self.secgroups_mock = self.app.client_manager.compute.security_groups self.secgroups_mock.reset_mock() self.projects_mock = mock.Mock() diff --git a/openstackclient/tests/compute/v2/test_security_group_rule.py b/openstackclient/tests/compute/v2/test_security_group_rule.py index a2f9b10825..37eed33317 100644 --- a/openstackclient/tests/compute/v2/test_security_group_rule.py +++ b/openstackclient/tests/compute/v2/test_security_group_rule.py @@ -102,9 +102,8 @@ class TestSecurityGroupRule(compute_fakes.TestComputev2): def setUp(self): super(TestSecurityGroupRule, self).setUp() - self.secgroups_mock = mock.Mock() - self.secgroups_mock.resource_class = fakes.FakeResource(None, {}) - self.app.client_manager.compute.security_groups = self.secgroups_mock + # Get a shortcut compute client security_groups mock + self.secgroups_mock = self.app.client_manager.compute.security_groups self.secgroups_mock.reset_mock() self.sg_rules_mock = mock.Mock() From 1740218728ca55c50ec8d133de4acd379bce4f9c Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Thu, 4 Feb 2016 16:04:06 +0800 Subject: [PATCH 0583/3095] Define security_group_rules mock in FakeComputev2Client security_group_rules mock should be defined in FakeComputev2Client, and used in each test class. Change-Id: I44bb6379b2c1b6cb277296e08e25dd4d7255c276 --- openstackclient/tests/compute/v2/fakes.py | 3 +++ .../tests/compute/v2/test_security_group_rule.py | 8 +++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 051d8d545a..9e57fd97ac 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -121,6 +121,9 @@ def __init__(self, **kwargs): self.security_groups = mock.Mock() self.security_groups.resource_class = fakes.FakeResource(None, {}) + self.security_group_rules = mock.Mock() + self.security_group_rules.resource_class = fakes.FakeResource(None, {}) + self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] diff --git a/openstackclient/tests/compute/v2/test_security_group_rule.py b/openstackclient/tests/compute/v2/test_security_group_rule.py index 37eed33317..749f34debd 100644 --- a/openstackclient/tests/compute/v2/test_security_group_rule.py +++ b/openstackclient/tests/compute/v2/test_security_group_rule.py @@ -12,7 +12,6 @@ # import copy -import mock from openstackclient.compute.v2 import security_group from openstackclient.tests.compute.v2 import fakes as compute_fakes @@ -106,10 +105,9 @@ def setUp(self): self.secgroups_mock = self.app.client_manager.compute.security_groups self.secgroups_mock.reset_mock() - self.sg_rules_mock = mock.Mock() - self.sg_rules_mock.resource_class = fakes.FakeResource(None, {}) - self.app.client_manager.compute.security_group_rules = \ - self.sg_rules_mock + # Get a shortcut compute client security_group_rules mock + self.sg_rules_mock = \ + self.app.client_manager.compute.security_group_rules self.sg_rules_mock.reset_mock() From 90a4cf23aa9bddc0b347bf847be438591fc02ac3 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Thu, 4 Feb 2016 15:43:47 +0800 Subject: [PATCH 0584/3095] Remove identity_client.projects definition in TestSecurityGroup projects mock has been defined in FakeIdentityv2Client as tenants, and in FakeIdentityv3Client as projects. No need to define them again. Change-Id: Ieb97b32d7be6bd95c8621092b218ebfd8bc5b78d --- openstackclient/tests/compute/v2/test_security_group.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/openstackclient/tests/compute/v2/test_security_group.py b/openstackclient/tests/compute/v2/test_security_group.py index a3f7dcb65b..4d7c9b7cbc 100644 --- a/openstackclient/tests/compute/v2/test_security_group.py +++ b/openstackclient/tests/compute/v2/test_security_group.py @@ -12,7 +12,6 @@ # import copy -import mock from openstackclient.compute.v2 import security_group from openstackclient.tests.compute.v2 import fakes as compute_fakes @@ -47,9 +46,8 @@ def setUp(self): self.secgroups_mock = self.app.client_manager.compute.security_groups self.secgroups_mock.reset_mock() - self.projects_mock = mock.Mock() - self.projects_mock.resource_class = fakes.FakeResource(None, {}) - self.app.client_manager.identity.projects = self.projects_mock + # Get a shortcut identity client projects mock + self.projects_mock = self.app.client_manager.identity.projects self.projects_mock.reset_mock() From b9de23d906a0ba640c336006e1b373a079846995 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 6 Feb 2016 09:34:51 +0800 Subject: [PATCH 0585/3095] Compute: Fix DisplayCommandBase comments for cliff Command subclass tests As bug #1477199 describes, the wrong comment below is all over the unit test code of OSC. # DisplayCommandBase.take_action() returns two tuples There is no such class named DisplayCommandBase in OSC. It is in cliff. All OSC command classes inherit from the base classes in cliff, class Command, class Lister and class ShowOne. It is like this: Object |--> Command |--> DisplayCommandBase |--> Lister |--> ShowOne take_action() is an abstract method of class Command, and generally is overwritten by subclasses. * Command.take_action() returns nothing. * Lister.take_action() returns a tuple which contains a tuple of columns and a generator used to generate the data. * ShowOne.take_action() returns an iterator which contains a tuple of columns and a tuple of data So, this problem should be fixed in 3 steps: 1. Remove all DisplayCommandBase comments for tests of classes inheriting from class Command in cliff as it returns nothing. 2. Fix all DisplayCommandBase comments for tests of classes inheriting from class Lister in cliff. Lister.take_action() returns a tuple and a generator. 3. Fix all DisplayCommandBase comments for tests of classes inheriting from class ShowOne in cliff. ShowOne.take_action() returns two tuples. This patch finishes step 1 in compute tests. Change-Id: I99ab42a7de69af0e5de802a1bb5aac647245a200 Partial-bug: #1477199 --- openstackclient/tests/compute/v2/test_server.py | 9 --------- openstackclient/tests/compute/v2/test_service.py | 1 - 2 files changed, 10 deletions(-) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 141e137000..da5fc76fdc 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -79,7 +79,6 @@ def run_method_with_servers(self, method_name, server_count): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) for s in servers: @@ -411,7 +410,6 @@ def test_server_delete_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) self.servers_mock.delete.assert_called_with( @@ -431,7 +429,6 @@ def test_server_delete_multi_servers(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) calls = [] @@ -451,7 +448,6 @@ def test_server_delete_wait_ok(self, mock_wait_for_delete): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) self.servers_mock.delete.assert_called_with( @@ -476,7 +472,6 @@ def test_server_delete_wait_fails(self, mock_wait_for_delete): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.assertRaises(SystemExit, self.cmd.take_action, parsed_args) self.servers_mock.delete.assert_called_with( @@ -861,7 +856,6 @@ def test_server_resize_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) self.servers_mock.get.assert_called_with( @@ -885,7 +879,6 @@ def test_server_resize(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) self.servers_mock.get.assert_called_with( @@ -914,7 +907,6 @@ def test_server_resize_confirm(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) self.servers_mock.get.assert_called_with( @@ -939,7 +931,6 @@ def test_server_resize_revert(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) self.servers_mock.get.assert_called_with( diff --git a/openstackclient/tests/compute/v2/test_service.py b/openstackclient/tests/compute/v2/test_service.py index 380fbc4f3b..c9c45c1fa4 100644 --- a/openstackclient/tests/compute/v2/test_service.py +++ b/openstackclient/tests/compute/v2/test_service.py @@ -49,7 +49,6 @@ def test_service_delete_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) self.service_mock.delete.assert_called_with( From 3c67e8dd6ed20f9a3d6c1f8f000214cc7a8f384e Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 6 Feb 2016 10:30:34 +0800 Subject: [PATCH 0586/3095] Compute: Fix DisplayCommandBase comments for cliff Lister subclass tests As bug #1477199 describes, the wrong comment below is all over the unit test code of OSC. # DisplayCommandBase.take_action() returns two tuples There is no such class named DisplayCommandBase in OSC. It is in cliff. All OSC command classes inherit from the base classes in cliff, class Command, class Lister and class ShowOne. It is like this: Object |--> Command |--> DisplayCommandBase |--> Lister |--> ShowOne take_action() is an abstract method of class Command, and generally is overwritten by subclasses. * Command.take_action() returns nothing. * Lister.take_action() returns a tuple which contains a tuple of columns and a generator used to generate the data. * ShowOne.take_action() returns an iterator which contains a tuple of columns and a tuple of data So, this problem should be fixed in 3 steps: 1. Remove all DisplayCommandBase comments for tests of classes inheriting from class Command in cliff as it returns nothing. 2. Fix all DisplayCommandBase comments for tests of classes inheriting from class Lister in cliff. Lister.take_action() returns a tuple and a generator. 3. Fix all DisplayCommandBase comments for tests of classes inheriting from class ShowOne in cliff. ShowOne.take_action() returns two tuples. This patch finishes step 2 in compute tests. Change-Id: Idc54ad21eaa1371ebd601327b8d962c7039f2de0 Partial-bug: #1477199 --- .../tests/compute/v2/test_flavor.py | 20 ++++++++++++++----- .../tests/compute/v2/test_security_group.py | 4 +++- .../compute/v2/test_security_group_rule.py | 8 ++++++-- .../tests/compute/v2/test_service.py | 4 +++- 4 files changed, 27 insertions(+), 9 deletions(-) diff --git a/openstackclient/tests/compute/v2/test_flavor.py b/openstackclient/tests/compute/v2/test_flavor.py index 3a59020c49..9ae2696255 100644 --- a/openstackclient/tests/compute/v2/test_flavor.py +++ b/openstackclient/tests/compute/v2/test_flavor.py @@ -126,7 +126,9 @@ def test_flavor_list_no_options(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstractmethod take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) # Set expected values @@ -153,7 +155,9 @@ def test_flavor_list_all_flavors(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstractmethod take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) # Set expected values @@ -180,7 +184,9 @@ def test_flavor_list_private_flavors(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstractmethod take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) # Set expected values @@ -207,7 +213,9 @@ def test_flavor_list_public_flavors(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstractmethod take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) # Set expected values @@ -234,7 +242,9 @@ def test_flavor_list_long(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstractmethod take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) # Set expected values diff --git a/openstackclient/tests/compute/v2/test_security_group.py b/openstackclient/tests/compute/v2/test_security_group.py index 87cc4870d8..63ced7358d 100644 --- a/openstackclient/tests/compute/v2/test_security_group.py +++ b/openstackclient/tests/compute/v2/test_security_group.py @@ -158,7 +158,9 @@ def test_security_group_list_no_options(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstractmethod take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) # Set expected values diff --git a/openstackclient/tests/compute/v2/test_security_group_rule.py b/openstackclient/tests/compute/v2/test_security_group_rule.py index a2f9b10825..fc75bde93b 100644 --- a/openstackclient/tests/compute/v2/test_security_group_rule.py +++ b/openstackclient/tests/compute/v2/test_security_group_rule.py @@ -404,7 +404,9 @@ def test_security_group_rule_list(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstractmethod take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) collist = ( @@ -440,7 +442,9 @@ def test_security_group_rule_list_no_group(self): parsed_args = self.check_parser(self.cmd, [], []) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstractmethod take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) collist = ( diff --git a/openstackclient/tests/compute/v2/test_service.py b/openstackclient/tests/compute/v2/test_service.py index c9c45c1fa4..71700aa836 100644 --- a/openstackclient/tests/compute/v2/test_service.py +++ b/openstackclient/tests/compute/v2/test_service.py @@ -81,7 +81,9 @@ def test_service_list(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstractmethod take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. self.cmd.take_action(parsed_args) self.service_mock.list.assert_called_with( From 23faa33b1b2914bbc3e103fca75fde70eb317021 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 6 Feb 2016 10:47:56 +0800 Subject: [PATCH 0587/3095] Compute: Fix DisplayCommandBase comments for cliff ShowOne subclass tests As bug #1477199 describes, the wrong comment below is all over the unit test code of OSC. # DisplayCommandBase.take_action() returns two tuples There is no such class named DisplayCommandBase in OSC. It is in cliff. All OSC command classes inherit from the base classes in cliff, class Command, class Lister and class ShowOne. It is like this: Object |--> Command |--> DisplayCommandBase |--> Lister |--> ShowOne take_action() is an abstract method of class Command, and generally is overwritten by subclasses. * Command.take_action() returns nothing. * Lister.take_action() returns a tuple which contains a tuple of columns and a generator used to generate the data. * ShowOne.take_action() returns an iterator which contains a tuple of columns and a tuple of data So, this problem should be fixed in 3 steps: 1. Remove all DisplayCommandBase comments for tests of classes inheriting from class Command in cliff as it returns nothing. 2. Fix all DisplayCommandBase comments for tests of classes inheriting from class Lister in cliff. Lister.take_action() returns a tuple and a generator. 3. Fix all DisplayCommandBase comments for tests of classes inheriting from class ShowOne in cliff. ShowOne.take_action() returns two tuples. This patch finishes step 3 in compute tests. Change-Id: I4df224ec82b5d82a3d6d3f366c0f68a7ea0d87cd Partial-bug: #1477199 --- .../tests/compute/v2/test_security_group.py | 8 ++++++-- .../compute/v2/test_security_group_rule.py | 20 ++++++++++++++----- .../tests/compute/v2/test_server.py | 20 ++++++++++++++----- 3 files changed, 36 insertions(+), 12 deletions(-) diff --git a/openstackclient/tests/compute/v2/test_security_group.py b/openstackclient/tests/compute/v2/test_security_group.py index 63ced7358d..549e09251a 100644 --- a/openstackclient/tests/compute/v2/test_security_group.py +++ b/openstackclient/tests/compute/v2/test_security_group.py @@ -90,7 +90,9 @@ def test_security_group_create_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class ShowOne in cliff, abstractmethod 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) # SecurityGroupManager.create(name, description) @@ -113,7 +115,9 @@ def test_security_group_create_description(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class ShowOne in cliff, abstractmethod 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) # SecurityGroupManager.create(name, description) diff --git a/openstackclient/tests/compute/v2/test_security_group_rule.py b/openstackclient/tests/compute/v2/test_security_group_rule.py index fc75bde93b..154a397db8 100644 --- a/openstackclient/tests/compute/v2/test_security_group_rule.py +++ b/openstackclient/tests/compute/v2/test_security_group_rule.py @@ -152,7 +152,9 @@ def test_security_group_rule_create_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class ShowOne in cliff, abstractmethod 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) # SecurityGroupManager.create(name, description) @@ -196,7 +198,9 @@ def test_security_group_rule_create_ftp(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class ShowOne in cliff, abstractmethod 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) # SecurityGroupManager.create(name, description) @@ -244,7 +248,9 @@ def test_security_group_rule_create_ssh(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class ShowOne in cliff, abstractmethod 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) # SecurityGroupManager.create(name, description) @@ -287,7 +293,9 @@ def test_security_group_rule_create_udp(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class ShowOne in cliff, abstractmethod 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) # SecurityGroupManager.create(name, description) @@ -333,7 +341,9 @@ def test_security_group_rule_create_icmp(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class ShowOne in cliff, abstractmethod 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) # SecurityGroupManager.create(name, description) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index da5fc76fdc..84402ea53d 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -163,7 +163,9 @@ def test_server_create_minimal(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class ShowOne in cliff, abstractmethod 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) # Set expected values @@ -241,7 +243,9 @@ def test_server_create_with_network(self): self.app.client_manager.network.find_network = find_network self.app.client_manager.network.find_port = find_port - # DisplayCommandBase.take_action() returns two tuples + # In base command class ShowOne in cliff, abstractmethod 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) # Set expected values @@ -299,7 +303,9 @@ def test_server_create_userdata(self, mock_open): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class ShowOne in cliff, abstractmethod 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) # Ensure the userdata file is opened @@ -551,7 +557,9 @@ def test_server_image_create_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class ShowOne in cliff, abstractmethod 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) # ServerManager.create_image(server, image_name, metadata=) @@ -574,7 +582,9 @@ def test_server_image_create_name(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class ShowOne in cliff, abstractmethod 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) # ServerManager.create_image(server, image_name, metadata=) From fc708c4991c6191ca684dca194420a93fdc4a35e Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sun, 7 Feb 2016 02:38:09 +0800 Subject: [PATCH 0588/3095] Add unit tests for "hypervisor show" command Change-Id: Ib75e5eb5b197e9d58fb87a595a43b8774b7b1987 --- openstackclient/tests/compute/v2/fakes.py | 26 +++++ .../tests/compute/v2/test_hypervisor.py | 105 ++++++++++++++++++ 2 files changed, 131 insertions(+) diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 1a876a229b..ae6d9f5d16 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -88,6 +88,8 @@ class FakeComputev2Client(object): def __init__(self, **kwargs): + self.aggregates = mock.Mock() + self.aggregates.resource_class = fakes.FakeResource(None, {}) self.availability_zones = mock.Mock() self.availability_zones.resource_class = fakes.FakeResource(None, {}) self.images = mock.Mock() @@ -158,6 +160,30 @@ def create_one_hypervisor(attrs={}): hypervisor_info = { 'id': 'hypervisor-id-' + uuid.uuid4().hex, 'hypervisor_hostname': 'hypervisor-hostname-' + uuid.uuid4().hex, + 'status': 'enabled', + 'host_ip': '192.168.0.10', + 'cpu_info': { + 'aaa': 'aaa', + }, + 'free_disk_gb': 50, + 'hypervisor_version': 2004001, + 'disk_available_least': 50, + 'local_gb': 50, + 'free_ram_mb': 1024, + 'service': { + 'host': 'aaa', + 'disabled_reason': None, + 'id': 1, + }, + 'vcpus_used': 0, + 'hypervisor_type': 'QEMU', + 'local_gb_used': 0, + 'vcpus': 4, + 'memory_mb_used': 512, + 'memory_mb': 1024, + 'current_workload': 0, + 'state': 'up', + 'running_vms': 0, } # Overwrite default attributes. diff --git a/openstackclient/tests/compute/v2/test_hypervisor.py b/openstackclient/tests/compute/v2/test_hypervisor.py index 1f52ee0943..a11f59d2a8 100644 --- a/openstackclient/tests/compute/v2/test_hypervisor.py +++ b/openstackclient/tests/compute/v2/test_hypervisor.py @@ -13,9 +13,12 @@ # under the License. # +import copy + from openstackclient.common import exceptions from openstackclient.compute.v2 import hypervisor from openstackclient.tests.compute.v2 import fakes as compute_fakes +from openstackclient.tests import fakes class TestHypervisor(compute_fakes.TestComputev2): @@ -27,6 +30,10 @@ def setUp(self): self.hypervisors_mock = self.app.client_manager.compute.hypervisors self.hypervisors_mock.reset_mock() + # Get a shortcut to the compute client aggregates mock + self.aggregates_mock = self.app.client_manager.compute.aggregates + self.aggregates_mock.reset_mock() + class TestHypervisorList(TestHypervisor): @@ -113,3 +120,101 @@ def test_hypervisor_list_matching_option_not_found(self): self.assertRaises(exceptions.NotFound, self.cmd.take_action, parsed_args) + + +class TestHypervisorShow(TestHypervisor): + + def setUp(self): + super(TestHypervisorShow, self).setUp() + + # Fake hypervisors to be listed up + self.hypervisor = compute_fakes.FakeHypervisor.create_one_hypervisor() + + # Return value of utils.find_resource() + self.hypervisors_mock.get.return_value = self.hypervisor + + # Return value of compute_client.aggregates.list() + self.aggregates_mock.list.return_value = [] + + # Return value of compute_client.hypervisors.uptime() + uptime_info = { + 'status': self.hypervisor.status, + 'state': self.hypervisor.state, + 'id': self.hypervisor.id, + 'hypervisor_hostname': self.hypervisor.hypervisor_hostname, + 'uptime': ' 01:28:24 up 3 days, 11:15, 1 user, ' + ' load average: 0.94, 0.62, 0.50\n', + } + self.hypervisors_mock.uptime.return_value = fakes.FakeResource( + info=copy.deepcopy(uptime_info), + loaded=True + ) + + self.columns = ( + 'aggregates', + 'cpu_info', + 'current_workload', + 'disk_available_least', + 'free_disk_gb', + 'free_ram_mb', + 'host_ip', + 'hypervisor_hostname', + 'hypervisor_type', + 'hypervisor_version', + 'id', + 'local_gb', + 'local_gb_used', + 'memory_mb', + 'memory_mb_used', + 'running_vms', + 'service_host', + 'service_id', + 'state', + 'status', + 'vcpus', + 'vcpus_used', + ) + self.data = ( + [], + {'aaa': 'aaa'}, + 0, + 50, + 50, + 1024, + '192.168.0.10', + self.hypervisor.hypervisor_hostname, + 'QEMU', + 2004001, + self.hypervisor.id, + 50, + 0, + 1024, + 512, + 0, + 'aaa', + 1, + 'up', + 'enabled', + 4, + 0, + ) + + # Get the command object to test + self.cmd = hypervisor.ShowHypervisor(self.app, None) + + def test_hypervisor_show(self): + arglist = [ + self.hypervisor.hypervisor_hostname, + ] + verifylist = [ + ('hypervisor', self.hypervisor.hypervisor_hostname), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class ShowOne in cliff, abstractmethod 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.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) From 828f63f903ee71a9f63ebaab3638fe441768a35a Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Sat, 6 Feb 2016 13:52:38 -0700 Subject: [PATCH 0589/3095] Add release note for recursive delete Change-Id: I9c7a32d4e18f32ae1225e250d11b8e0a2d274dd7 --- .../notes/recursive-container-delete-983361aa9c35ffed.yaml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 releasenotes/notes/recursive-container-delete-983361aa9c35ffed.yaml diff --git a/releasenotes/notes/recursive-container-delete-983361aa9c35ffed.yaml b/releasenotes/notes/recursive-container-delete-983361aa9c35ffed.yaml new file mode 100644 index 0000000000..0986f801fb --- /dev/null +++ b/releasenotes/notes/recursive-container-delete-983361aa9c35ffed.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add support for recursive container delete. + [Bug `1542718 `_] From cdb7637b76027f74db9b24026a0757084a83195d Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Sat, 6 Feb 2016 14:26:52 -0700 Subject: [PATCH 0590/3095] Add release note for custom logging feature Change-Id: I5471fcb33cd4488b33d2c01d857344f52f361c9d --- .../notes/allow-custom-logging-12d55f8ed859ff8e.yaml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 releasenotes/notes/allow-custom-logging-12d55f8ed859ff8e.yaml diff --git a/releasenotes/notes/allow-custom-logging-12d55f8ed859ff8e.yaml b/releasenotes/notes/allow-custom-logging-12d55f8ed859ff8e.yaml new file mode 100644 index 0000000000..7d263d4f06 --- /dev/null +++ b/releasenotes/notes/allow-custom-logging-12d55f8ed859ff8e.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Allow custom logging of components + [Bug `1484660 `_] From e3e925d2f1bb1a58f9c7f33262c5cda2ccc73212 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Mon, 8 Feb 2016 02:44:02 +0000 Subject: [PATCH 0591/3095] Updated from global requirements Change-Id: I14a68f0ed63f1a3e3cac7379324d58b613afdfa9 --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 6a73cd95c1..2255cd3b3b 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -16,7 +16,7 @@ os-testr>=0.4.1 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD testtools>=1.4.0 # MIT WebOb>=1.2.3 # MIT -tempest-lib>=0.13.0 # Apache-2.0 +tempest-lib>=0.14.0 # Apache-2.0 # Install these to generate sphinx autodocs python-barbicanclient>=3.3.0 # Apache-2.0 From 43f80505cbaa0945380e366a8cada71b55296463 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 9 Feb 2016 18:27:48 +0800 Subject: [PATCH 0592/3095] Fix DisplayCommandBase comments for cliff Command subclass tests As bug #1477199 describes, the wrong comment below is all over the unit test code of OSC. # DisplayCommandBase.take_action() returns two tuples There is no such class named DisplayCommandBase in OSC. It is in cliff. All OSC command classes inherit from the base classes in cliff, class Command, class Lister and class ShowOne. It is like this: Object |--> Command |--> DisplayCommandBase |--> Lister |--> ShowOne take_action() is an abstract method of class Command, and generally is overwritten by subclasses. * Command.take_action() returns nothing. * Lister.take_action() returns a tuple which contains a tuple of columns and a generator used to generate the data. * ShowOne.take_action() returns an iterator which contains a tuple of columns and a tuple of data. So, this problem should be fixed in 3 steps: 1. Remove all DisplayCommandBase comments for tests of classes inheriting from class Command in cliff as it returns nothing. 2. Fix all DisplayCommandBase comments for tests of classes inheriting from class Lister in cliff. Lister.take_action() returns a tuple and a generator. 3. Fix all DisplayCommandBase comments for tests of classes inheriting from class ShowOne in cliff. ShowOne.take_action() returns two tuples. This patch finishes step 1 in all but identity tests. There are too many such comments in identity tests. So fix them all in another patch. Change-Id: I9849baa8141ea8af2042a69afd540b77ce6ae6bd Partial-bug: #1477199 --- openstackclient/tests/image/v1/test_image.py | 5 ----- openstackclient/tests/image/v2/test_image.py | 13 ------------- openstackclient/tests/volume/v1/test_volume.py | 4 ---- 3 files changed, 22 deletions(-) diff --git a/openstackclient/tests/image/v1/test_image.py b/openstackclient/tests/image/v1/test_image.py index 1e0b29aa33..afe8c75a14 100644 --- a/openstackclient/tests/image/v1/test_image.py +++ b/openstackclient/tests/image/v1/test_image.py @@ -230,7 +230,6 @@ def test_image_delete_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) self.images_mock.delete.assert_called_with( @@ -456,7 +455,6 @@ def test_image_set_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) # Verify update() was not called, if it was show the args @@ -517,7 +515,6 @@ def test_image_set_bools1(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) kwargs = { @@ -545,7 +542,6 @@ def test_image_set_bools2(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) kwargs = { @@ -570,7 +566,6 @@ def test_image_set_properties(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) kwargs = { diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index d399c9eda9..0da84a4ed3 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -435,7 +435,6 @@ def test_image_delete_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) self.images_mock.delete.assert_called_with( @@ -730,7 +729,6 @@ def test_remove_project_image_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) self.image_members_mock.delete.assert_called_with( image_fakes.image_id, @@ -750,7 +748,6 @@ def test_remove_project_image_with_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) self.image_members_mock.delete.assert_called_with( image_fakes.image_id, @@ -808,7 +805,6 @@ def test_image_set_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) kwargs = { @@ -876,7 +872,6 @@ def test_image_set_bools1(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) kwargs = { @@ -904,7 +899,6 @@ def test_image_set_bools2(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) kwargs = { @@ -929,7 +923,6 @@ def test_image_set_properties(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) kwargs = { @@ -963,7 +956,6 @@ def test_image_set_fake_properties(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) kwargs = { @@ -991,7 +983,6 @@ def test_image_set_tag(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) kwargs = { @@ -1015,7 +1006,6 @@ def test_image_set_activate(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) kwargs = { @@ -1044,7 +1034,6 @@ def test_image_set_deactivate(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) kwargs = { @@ -1075,7 +1064,6 @@ def test_image_set_tag_merge(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) kwargs = { @@ -1101,7 +1089,6 @@ def test_image_set_tag_merge_dupe(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) kwargs = { diff --git a/openstackclient/tests/volume/v1/test_volume.py b/openstackclient/tests/volume/v1/test_volume.py index 33255aacc1..be9356e6d4 100644 --- a/openstackclient/tests/volume/v1/test_volume.py +++ b/openstackclient/tests/volume/v1/test_volume.py @@ -582,7 +582,6 @@ def test_volume_set_name(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) # Set expected values @@ -608,7 +607,6 @@ def test_volume_set_description(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) # Set expected values @@ -634,7 +632,6 @@ def test_volume_set_size(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) # Set expected values @@ -700,7 +697,6 @@ def test_volume_set_property(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) # Set expected values From e69b88ef525445325843c129e1a113a92ed98e3a Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 9 Feb 2016 17:30:39 +0800 Subject: [PATCH 0593/3095] Fix DisplayCommandBase comments for cliff Lister subclass tests As bug #1477199 describes, the wrong comment below is all over the unit test code of OSC. # DisplayCommandBase.take_action() returns two tuples There is no such class named DisplayCommandBase in OSC. It is in cliff. All OSC command classes inherit from the base classes in cliff, class Command, class Lister and class ShowOne. It is like this: Object |--> Command |--> DisplayCommandBase |--> Lister |--> ShowOne take_action() is an abstract method of class Command, and generally is overwritten by subclasses. * Command.take_action() returns nothing. * Lister.take_action() returns a tuple which contains a tuple of columns and a generator used to generate the data. * ShowOne.take_action() returns an iterator which contains a tuple of columns and a tuple of data. So, this problem should be fixed in 3 steps: 1. Remove all DisplayCommandBase comments for tests of classes inheriting from class Command in cliff as it returns nothing. 2. Fix all DisplayCommandBase comments for tests of classes inheriting from class Lister in cliff. Lister.take_action() returns a tuple and a generator. 3. Fix all DisplayCommandBase comments for tests of classes inheriting from class ShowOne in cliff. ShowOne.take_action() returns two tuples. This patch finishes step 2 in all but identity tests. There are too many such comments in identity tests. So fix them all in another patch. Change-Id: I00f38d12f55abe20fa708f6349073da658622f8d Partial-bug: #1477199 --- .../tests/common/test_availability_zone.py | 20 +++++++++--- .../tests/common/test_extension.py | 12 +++++-- openstackclient/tests/common/test_module.py | 12 +++++-- openstackclient/tests/common/test_timing.py | 8 +++-- openstackclient/tests/image/v1/test_image.py | 24 ++++++++++---- openstackclient/tests/image/v2/test_image.py | 28 ++++++++++++---- .../tests/network/v2/test_network.py | 12 +++++-- .../tests/network/v2/test_router.py | 8 +++-- .../tests/object/v1/test_container.py | 24 ++++++++++---- .../tests/object/v1/test_object.py | 32 ++++++++++++++----- .../tests/object/v1/test_object_all.py | 4 ++- 11 files changed, 138 insertions(+), 46 deletions(-) diff --git a/openstackclient/tests/common/test_availability_zone.py b/openstackclient/tests/common/test_availability_zone.py index e44455c7b4..feecaf55b3 100644 --- a/openstackclient/tests/common/test_availability_zone.py +++ b/openstackclient/tests/common/test_availability_zone.py @@ -144,7 +144,9 @@ def test_availability_zone_list_no_options(self): verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.compute_azs_mock.list.assert_called_with() @@ -170,7 +172,9 @@ def test_availability_zone_list_long(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.compute_azs_mock.list.assert_called_with() @@ -199,7 +203,9 @@ def test_availability_zone_list_compute(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.compute_azs_mock.list.assert_called_with() @@ -221,7 +227,9 @@ def test_availability_zone_list_volume(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.compute_azs_mock.list.assert_not_called() @@ -243,7 +251,9 @@ def test_availability_zone_list_network(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.compute_azs_mock.list.assert_not_called() diff --git a/openstackclient/tests/common/test_extension.py b/openstackclient/tests/common/test_extension.py index 21c2cc24f1..66532827cb 100644 --- a/openstackclient/tests/common/test_extension.py +++ b/openstackclient/tests/common/test_extension.py @@ -63,7 +63,9 @@ def test_extension_list_no_options(self): verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) # no args should output from all services @@ -93,7 +95,9 @@ def test_extension_list_long(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) # no args should output from all services @@ -131,7 +135,9 @@ def test_extension_list_identity(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.identity_extensions_mock.list.assert_called_with() diff --git a/openstackclient/tests/common/test_module.py b/openstackclient/tests/common/test_module.py index 6918c1b419..2821da9ed7 100644 --- a/openstackclient/tests/common/test_module.py +++ b/openstackclient/tests/common/test_module.py @@ -62,7 +62,9 @@ def test_command_list_no_options(self): verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) collist = ('Command Group', 'Commands') @@ -94,7 +96,9 @@ def test_module_list_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) # Additional modules may be present, just check our additions @@ -110,7 +114,9 @@ def test_module_list_all(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) # Additional modules may be present, just check our additions diff --git a/openstackclient/tests/common/test_timing.py b/openstackclient/tests/common/test_timing.py index e7b9a04070..c4c738b259 100644 --- a/openstackclient/tests/common/test_timing.py +++ b/openstackclient/tests/common/test_timing.py @@ -61,7 +61,9 @@ def test_timing_list_no_data(self): verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) @@ -80,7 +82,9 @@ def test_timing_list(self): verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) datalist = [ diff --git a/openstackclient/tests/image/v1/test_image.py b/openstackclient/tests/image/v1/test_image.py index afe8c75a14..7044ea1da4 100644 --- a/openstackclient/tests/image/v1/test_image.py +++ b/openstackclient/tests/image/v1/test_image.py @@ -273,7 +273,9 @@ def test_image_list_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( detailed=True, @@ -294,7 +296,9 @@ def test_image_list_public_option(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( detailed=True, @@ -316,7 +320,9 @@ def test_image_list_private_option(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( detailed=True, @@ -336,7 +342,9 @@ def test_image_list_long_option(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( detailed=True, @@ -385,7 +393,9 @@ def test_image_list_property_option(self, sf_mock): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( detailed=True, @@ -411,7 +421,9 @@ def test_image_list_sort_option(self, si_mock): verifylist = [('sort', 'name:asc')] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( detailed=True, diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index 0da84a4ed3..3c478b53d3 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -495,7 +495,9 @@ def test_image_list_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with() @@ -514,7 +516,9 @@ def test_image_list_public_option(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( public=True, @@ -535,7 +539,9 @@ def test_image_list_private_option(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( private=True, @@ -556,7 +562,9 @@ def test_image_list_shared_option(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( shared=True, @@ -574,7 +582,9 @@ def test_image_list_long_option(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with() @@ -620,7 +630,9 @@ def test_image_list_property_option(self, sf_mock): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with() sf_mock.assert_called_with( @@ -643,7 +655,9 @@ def test_image_list_sort_option(self, si_mock): verifylist = [('sort', 'name:asc')] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with() si_mock.assert_called_with( diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index f96497a437..9829d578dd 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -380,7 +380,9 @@ def test_network_list_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.network.networks.assert_called_with() @@ -397,7 +399,9 @@ def test_list_external(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.network.networks.assert_called_with( @@ -416,7 +420,9 @@ def test_network_list_long(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.network.networks.assert_called_with() diff --git a/openstackclient/tests/network/v2/test_router.py b/openstackclient/tests/network/v2/test_router.py index 98e9f17a8f..69c548a0b7 100644 --- a/openstackclient/tests/network/v2/test_router.py +++ b/openstackclient/tests/network/v2/test_router.py @@ -201,7 +201,9 @@ def test_router_list_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.network.routers.assert_called_with() @@ -217,7 +219,9 @@ def test_router_list_long(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.network.routers.assert_called_with() diff --git a/openstackclient/tests/object/v1/test_container.py b/openstackclient/tests/object/v1/test_container.py index d34d73e2cd..62dc4f737b 100644 --- a/openstackclient/tests/object/v1/test_container.py +++ b/openstackclient/tests/object/v1/test_container.py @@ -156,7 +156,9 @@ def test_object_list_containers_no_options(self, c_mock): verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) # Set expected values @@ -188,7 +190,9 @@ def test_object_list_containers_prefix(self, c_mock): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) # Set expected values @@ -222,7 +226,9 @@ def test_object_list_containers_marker(self, c_mock): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) # Set expected values @@ -255,7 +261,9 @@ def test_object_list_containers_limit(self, c_mock): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) # Set expected values @@ -287,7 +295,9 @@ def test_object_list_containers_long(self, c_mock): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) # Set expected values @@ -328,7 +338,9 @@ def test_object_list_containers_all(self, c_mock): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) # Set expected values diff --git a/openstackclient/tests/object/v1/test_object.py b/openstackclient/tests/object/v1/test_object.py index f0d62f6e16..431ab472ef 100644 --- a/openstackclient/tests/object/v1/test_object.py +++ b/openstackclient/tests/object/v1/test_object.py @@ -67,7 +67,9 @@ def test_object_list_objects_no_options(self, o_mock): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) o_mock.assert_called_with( @@ -96,7 +98,9 @@ def test_object_list_objects_prefix(self, o_mock): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) # Set expected values @@ -126,7 +130,9 @@ def test_object_list_objects_delimiter(self, o_mock): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) # Set expected values @@ -156,7 +162,9 @@ def test_object_list_objects_marker(self, o_mock): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) # Set expected values @@ -186,7 +194,9 @@ def test_object_list_objects_end_marker(self, o_mock): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) # Set expected values @@ -216,7 +226,9 @@ def test_object_list_objects_limit(self, o_mock): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) # Set expected values @@ -247,7 +259,9 @@ def test_object_list_objects_long(self, o_mock): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) # Set expected values @@ -294,7 +308,9 @@ def test_object_list_objects_all(self, o_mock): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) # Set expected values diff --git a/openstackclient/tests/object/v1/test_object_all.py b/openstackclient/tests/object/v1/test_object_all.py index a90e5b6587..f36d2ea73b 100644 --- a/openstackclient/tests/object/v1/test_object_all.py +++ b/openstackclient/tests/object/v1/test_object_all.py @@ -102,7 +102,9 @@ def test_object_list_objects_prefix(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) From 686a26973809eaba3deb9aed63daddba3bb0521e Mon Sep 17 00:00:00 2001 From: lin-hua-cheng Date: Tue, 9 Feb 2016 16:49:11 -0800 Subject: [PATCH 0594/3095] Add "token revoke" for identity v3 Change-Id: Ie631600d02942fe6ce035f31af46abe44e543631 Closes-bug: #1543226 --- doc/source/command-objects/token.rst | 2 -- openstackclient/identity/v3/token.py | 19 +++++++++++++++++ openstackclient/tests/identity/v3/fakes.py | 2 ++ .../tests/identity/v3/test_token.py | 21 +++++++++++++++++++ .../notes/bug-1543226-7d885ecaa3715415.yaml | 5 +++++ setup.cfg | 1 + 6 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/bug-1543226-7d885ecaa3715415.yaml diff --git a/doc/source/command-objects/token.rst b/doc/source/command-objects/token.rst index 22260f0dec..5e7c7b2686 100644 --- a/doc/source/command-objects/token.rst +++ b/doc/source/command-objects/token.rst @@ -17,8 +17,6 @@ Issue new token token revoke ------------ -*Identity version 2 only.* - Revoke existing token .. program:: token revoke diff --git a/openstackclient/identity/v3/token.py b/openstackclient/identity/v3/token.py index 588c5218ff..9ebd17995a 100644 --- a/openstackclient/identity/v3/token.py +++ b/openstackclient/identity/v3/token.py @@ -173,3 +173,22 @@ def take_action(self, parsed_args): if 'tenant_id' in token: token['project_id'] = token.pop('tenant_id') return zip(*sorted(six.iteritems(token))) + + +class RevokeToken(command.Command): + """Revoke existing token""" + + def get_parser(self, prog_name): + parser = super(RevokeToken, self).get_parser(prog_name) + parser.add_argument( + 'token', + metavar='', + help='Token to be deleted', + ) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + + identity_client.tokens.revoke_token(parsed_args.token) + return diff --git a/openstackclient/tests/identity/v3/fakes.py b/openstackclient/tests/identity/v3/fakes.py index 9fe341ed6a..d0a2ef53d2 100644 --- a/openstackclient/tests/identity/v3/fakes.py +++ b/openstackclient/tests/identity/v3/fakes.py @@ -420,6 +420,8 @@ def __init__(self, **kwargs): self.session = mock.Mock() self.session.auth.auth_ref.service_catalog.resource_class = \ fakes.FakeResource(None, {}) + self.tokens = mock.Mock() + self.tokens.resource_class = fakes.FakeResource(None, {}) self.trusts = mock.Mock() self.trusts.resource_class = fakes.FakeResource(None, {}) self.users = mock.Mock() diff --git a/openstackclient/tests/identity/v3/test_token.py b/openstackclient/tests/identity/v3/test_token.py index 6ad4845da7..192d71ee12 100644 --- a/openstackclient/tests/identity/v3/test_token.py +++ b/openstackclient/tests/identity/v3/test_token.py @@ -80,3 +80,24 @@ def test_token_issue_with_domain_id(self): identity_fakes.user_id, ) self.assertEqual(datalist, data) + + +class TestTokenRevoke(TestToken): + + TOKEN = 'fob' + + def setUp(self): + super(TestTokenRevoke, self).setUp() + self.tokens_mock = self.app.client_manager.identity.tokens + self.tokens_mock.reset_mock() + self.tokens_mock.revoke_token.return_value = True + self.cmd = token.RevokeToken(self.app, None) + + def test_token_revoke(self): + arglist = [self.TOKEN] + verifylist = [('token', self.TOKEN)] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.tokens_mock.revoke_token.assert_called_with(self.TOKEN) diff --git a/releasenotes/notes/bug-1543226-7d885ecaa3715415.yaml b/releasenotes/notes/bug-1543226-7d885ecaa3715415.yaml new file mode 100644 index 0000000000..c7778d0e1a --- /dev/null +++ b/releasenotes/notes/bug-1543226-7d885ecaa3715415.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``token revoke`` command for Identity v3 + [Bug `1543226 `_] diff --git a/setup.cfg b/setup.cfg index 4cf9622ceb..8bcb99dede 100644 --- a/setup.cfg +++ b/setup.cfg @@ -295,6 +295,7 @@ openstack.identity.v3 = service_provider_show = openstackclient.identity.v3.service_provider:ShowServiceProvider token_issue = openstackclient.identity.v3.token:IssueToken + token_revoke = openstackclient.identity.v3.token:RevokeToken trust_create = openstackclient.identity.v3.trust:CreateTrust trust_delete = openstackclient.identity.v3.trust:DeleteTrust From 35833f7bd82ac5d7cad4b5fba0e498d8936e6f48 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 9 Feb 2016 21:04:49 +0800 Subject: [PATCH 0595/3095] Fix DisplayCommandBase comments for cliff ShowOne subclass tests As bug #1477199 describes, the wrong comment below is all over the unit test code of OSC. # DisplayCommandBase.take_action() returns two tuples There is no such class named DisplayCommandBase in OSC. It is in cliff. All OSC command classes inherit from the base classes in cliff, class Command, class Lister and class ShowOne. It is like this: Object |--> Command |--> DisplayCommandBase |--> Lister |--> ShowOne take_action() is an abstract method of class Command, and generally is overwritten by subclasses. * Command.take_action() returns nothing. * Lister.take_action() returns a tuple which contains a tuple of columns and a generator used to generate the data. * ShowOne.take_action() returns an iterator which contains a tuple of columns and a tuple of data. So, this problem should be fixed in 3 steps: 1. Remove all DisplayCommandBase comments for tests of classes inheriting from class Command in cliff as it returns nothing. 2. Fix all DisplayCommandBase comments for tests of classes inheriting from class Lister in cliff. Lister.take_action() returns a tuple and a generator. 3. Fix all DisplayCommandBase comments for tests of classes inheriting from class ShowOne in cliff. ShowOne.take_action() returns two tuples. This patch finishes step 3 in all but identity tests. There are too many such comments in identity tests. So fix them all in another patch. Change-Id: I1afe4852069d25d562a9448ec2bf2cff58955052 Partial-bug: #1477199 --- openstackclient/tests/image/v1/test_image.py | 16 ++++++++--- openstackclient/tests/image/v2/test_image.py | 24 ++++++++++++---- .../tests/object/v1/test_container.py | 4 ++- .../tests/object/v1/test_container_all.py | 12 ++++++-- .../tests/object/v1/test_object.py | 4 ++- .../tests/object/v1/test_object_all.py | 4 ++- .../tests/volume/v1/test_volume.py | 28 ++++++++++++++----- .../tests/volume/v2/test_volume.py | 28 ++++++++++++++----- 8 files changed, 90 insertions(+), 30 deletions(-) diff --git a/openstackclient/tests/image/v1/test_image.py b/openstackclient/tests/image/v1/test_image.py index 7044ea1da4..201105a409 100644 --- a/openstackclient/tests/image/v1/test_image.py +++ b/openstackclient/tests/image/v1/test_image.py @@ -73,7 +73,9 @@ def test_image_reserve_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # ImageManager.create(name=, **) @@ -120,7 +122,9 @@ def test_image_reserve_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # ImageManager.create(name=, **) @@ -172,7 +176,9 @@ def test_image_create_file(self, mock_open): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # Ensure input file is opened @@ -676,7 +682,9 @@ def test_image_show(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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.images_mock.get.assert_called_with( image_fakes.image_id, diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index 3c478b53d3..de37512f07 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -95,7 +95,9 @@ def test_image_reserve_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # ImageManager.create(name=, **) @@ -156,7 +158,9 @@ def test_image_reserve_options(self, mock_open): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # ImageManager.create(name=, **) @@ -288,7 +292,9 @@ def test_image_create_file(self, mock_open): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # ImageManager.create(name=, **) @@ -382,7 +388,9 @@ def test_add_project_to_image_no_option(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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.image_members_mock.create.assert_called_with( image_fakes.image_id, @@ -404,7 +412,9 @@ def test_add_project_to_image_with_option(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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.image_members_mock.create.assert_called_with( image_fakes.image_id, @@ -1156,7 +1166,9 @@ def test_image_show(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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.images_mock.get.assert_called_with( image_fakes.image_id, diff --git a/openstackclient/tests/object/v1/test_container.py b/openstackclient/tests/object/v1/test_container.py index 62dc4f737b..5b0fb48a34 100644 --- a/openstackclient/tests/object/v1/test_container.py +++ b/openstackclient/tests/object/v1/test_container.py @@ -382,7 +382,9 @@ def test_container_show(self, c_mock): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # Set expected values diff --git a/openstackclient/tests/object/v1/test_container_all.py b/openstackclient/tests/object/v1/test_container_all.py index c2dd02a59a..95e12f47f3 100644 --- a/openstackclient/tests/object/v1/test_container_all.py +++ b/openstackclient/tests/object/v1/test_container_all.py @@ -57,7 +57,9 @@ def test_object_create_container_single(self): )] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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.assertEqual(self.columns, columns) @@ -91,7 +93,9 @@ def test_object_create_container_more(self): )] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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.assertEqual(self.columns, columns) @@ -312,7 +316,9 @@ def test_object_show_container(self): )] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) collist = ( diff --git a/openstackclient/tests/object/v1/test_object.py b/openstackclient/tests/object/v1/test_object.py index 431ab472ef..990e4f46fa 100644 --- a/openstackclient/tests/object/v1/test_object.py +++ b/openstackclient/tests/object/v1/test_object.py @@ -354,7 +354,9 @@ def test_object_show(self, c_mock): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # Set expected values diff --git a/openstackclient/tests/object/v1/test_object_all.py b/openstackclient/tests/object/v1/test_object_all.py index f36d2ea73b..89286b00e4 100644 --- a/openstackclient/tests/object/v1/test_object_all.py +++ b/openstackclient/tests/object/v1/test_object_all.py @@ -152,7 +152,9 @@ def test_object_show(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) collist = ( diff --git a/openstackclient/tests/volume/v1/test_volume.py b/openstackclient/tests/volume/v1/test_volume.py index be9356e6d4..00c509b5d9 100644 --- a/openstackclient/tests/volume/v1/test_volume.py +++ b/openstackclient/tests/volume/v1/test_volume.py @@ -95,7 +95,9 @@ def test_volume_create_min_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # VolumeManager.create(size, snapshot_id=, source_volid=, @@ -136,7 +138,9 @@ def test_volume_create_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # VolumeManager.create(size, snapshot_id=, source_volid=, @@ -189,7 +193,9 @@ def test_volume_create_user_project_id(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # VolumeManager.create(size, snapshot_id=, source_volid=, @@ -242,7 +248,9 @@ def test_volume_create_user_project_name(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # VolumeManager.create(size, snapshot_id=, source_volid=, @@ -281,7 +289,9 @@ def test_volume_create_properties(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # VolumeManager.create(size, snapshot_id=, source_volid=, @@ -325,7 +335,9 @@ def test_volume_create_image_id(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # VolumeManager.create(size, snapshot_id=, source_volid=, @@ -369,7 +381,9 @@ def test_volume_create_image_name(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # VolumeManager.create(size, snapshot_id=, source_volid=, diff --git a/openstackclient/tests/volume/v2/test_volume.py b/openstackclient/tests/volume/v2/test_volume.py index 76c7a27a39..df43cb2b2a 100644 --- a/openstackclient/tests/volume/v2/test_volume.py +++ b/openstackclient/tests/volume/v2/test_volume.py @@ -96,7 +96,9 @@ def test_volume_create_min_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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( @@ -133,7 +135,9 @@ def test_volume_create_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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( @@ -181,7 +185,9 @@ def test_volume_create_user_project_id(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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( @@ -229,7 +235,9 @@ def test_volume_create_user_project_name(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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( @@ -263,7 +271,9 @@ def test_volume_create_properties(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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( @@ -302,7 +312,9 @@ def test_volume_create_image_id(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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( @@ -341,7 +353,9 @@ def test_volume_create_image_name(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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( From e1feed52217012da285ef94144ed82704b20d4e7 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Wed, 10 Feb 2016 19:01:52 +0800 Subject: [PATCH 0596/3095] Trivial: Fix "abstractmethod" to "abstract method" As Richard has pointed out, "abstractmethod" should be "abstract method". This is a small typo I have made when I fix DisplayCommandBase comment bug. Change-Id: I84f1a3158896257686a0a7efa1123eef1b85139f Partial-bug: #1477199 --- openstackclient/tests/compute/v2/test_flavor.py | 10 +++++----- .../tests/compute/v2/test_hypervisor.py | 6 +++--- .../tests/compute/v2/test_security_group.py | 6 +++--- .../tests/compute/v2/test_security_group_rule.py | 14 +++++++------- openstackclient/tests/compute/v2/test_server.py | 10 +++++----- openstackclient/tests/compute/v2/test_service.py | 2 +- 6 files changed, 24 insertions(+), 24 deletions(-) diff --git a/openstackclient/tests/compute/v2/test_flavor.py b/openstackclient/tests/compute/v2/test_flavor.py index 9ae2696255..bf78bee821 100644 --- a/openstackclient/tests/compute/v2/test_flavor.py +++ b/openstackclient/tests/compute/v2/test_flavor.py @@ -126,7 +126,7 @@ def test_flavor_list_no_options(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # In base command class Lister in cliff, abstractmethod take_action() + # In base command class Lister in cliff, abstract method take_action() # returns a tuple containing the column names and an iterable # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) @@ -155,7 +155,7 @@ def test_flavor_list_all_flavors(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # In base command class Lister in cliff, abstractmethod take_action() + # In base command class Lister in cliff, abstract method take_action() # returns a tuple containing the column names and an iterable # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) @@ -184,7 +184,7 @@ def test_flavor_list_private_flavors(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # In base command class Lister in cliff, abstractmethod take_action() + # In base command class Lister in cliff, abstract method take_action() # returns a tuple containing the column names and an iterable # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) @@ -213,7 +213,7 @@ def test_flavor_list_public_flavors(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # In base command class Lister in cliff, abstractmethod take_action() + # In base command class Lister in cliff, abstract method take_action() # returns a tuple containing the column names and an iterable # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) @@ -242,7 +242,7 @@ def test_flavor_list_long(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # In base command class Lister in cliff, abstractmethod take_action() + # In base command class Lister in cliff, abstract method take_action() # returns a tuple containing the column names and an iterable # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) diff --git a/openstackclient/tests/compute/v2/test_hypervisor.py b/openstackclient/tests/compute/v2/test_hypervisor.py index a11f59d2a8..8d717ba7f3 100644 --- a/openstackclient/tests/compute/v2/test_hypervisor.py +++ b/openstackclient/tests/compute/v2/test_hypervisor.py @@ -67,7 +67,7 @@ def test_hypervisor_list_no_option(self): verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # In base command class Lister in cliff, abstractmethod take_action() + # In base command class Lister in cliff, abstract method take_action() # returns a tuple containing the column names and an iterable # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) @@ -94,7 +94,7 @@ def test_hypervisor_list_matching_option_found(self): ), ) - # In base command class Lister in cliff, abstractmethod take_action() + # In base command class Lister in cliff, abstract method take_action() # returns a tuple containing the column names and an iterable # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) @@ -211,7 +211,7 @@ def test_hypervisor_show(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # In base command class ShowOne in cliff, abstractmethod take_action() + # 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) diff --git a/openstackclient/tests/compute/v2/test_security_group.py b/openstackclient/tests/compute/v2/test_security_group.py index 79eefe6c41..c6998cb542 100644 --- a/openstackclient/tests/compute/v2/test_security_group.py +++ b/openstackclient/tests/compute/v2/test_security_group.py @@ -87,7 +87,7 @@ def test_security_group_create_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # In base command class ShowOne in cliff, abstractmethod take_action() + # 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) @@ -112,7 +112,7 @@ def test_security_group_create_description(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # In base command class ShowOne in cliff, abstractmethod take_action() + # 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) @@ -159,7 +159,7 @@ def test_security_group_list_no_options(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # In base command class Lister in cliff, abstractmethod take_action() + # In base command class Lister in cliff, abstract method take_action() # returns a tuple containing the column names and an iterable # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) diff --git a/openstackclient/tests/compute/v2/test_security_group_rule.py b/openstackclient/tests/compute/v2/test_security_group_rule.py index d211ee4edc..9a8003f36a 100644 --- a/openstackclient/tests/compute/v2/test_security_group_rule.py +++ b/openstackclient/tests/compute/v2/test_security_group_rule.py @@ -149,7 +149,7 @@ def test_security_group_rule_create_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # In base command class ShowOne in cliff, abstractmethod take_action() + # 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) @@ -195,7 +195,7 @@ def test_security_group_rule_create_ftp(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # In base command class ShowOne in cliff, abstractmethod take_action() + # 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) @@ -245,7 +245,7 @@ def test_security_group_rule_create_ssh(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # In base command class ShowOne in cliff, abstractmethod take_action() + # 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) @@ -290,7 +290,7 @@ def test_security_group_rule_create_udp(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # In base command class ShowOne in cliff, abstractmethod take_action() + # 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) @@ -338,7 +338,7 @@ def test_security_group_rule_create_icmp(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # In base command class ShowOne in cliff, abstractmethod take_action() + # 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) @@ -411,7 +411,7 @@ def test_security_group_rule_list(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # In base command class Lister in cliff, abstractmethod take_action() + # In base command class Lister in cliff, abstract method take_action() # returns a tuple containing the column names and an iterable # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) @@ -449,7 +449,7 @@ def test_security_group_rule_list_no_group(self): parsed_args = self.check_parser(self.cmd, [], []) - # In base command class Lister in cliff, abstractmethod take_action() + # In base command class Lister in cliff, abstract method take_action() # returns a tuple containing the column names and an iterable # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 84402ea53d..a80eaf51e6 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -163,7 +163,7 @@ def test_server_create_minimal(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # In base command class ShowOne in cliff, abstractmethod take_action() + # 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) @@ -243,7 +243,7 @@ def test_server_create_with_network(self): self.app.client_manager.network.find_network = find_network self.app.client_manager.network.find_port = find_port - # In base command class ShowOne in cliff, abstractmethod take_action() + # 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) @@ -303,7 +303,7 @@ def test_server_create_userdata(self, mock_open): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # In base command class ShowOne in cliff, abstractmethod take_action() + # 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) @@ -557,7 +557,7 @@ def test_server_image_create_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # In base command class ShowOne in cliff, abstractmethod take_action() + # 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) @@ -582,7 +582,7 @@ def test_server_image_create_name(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # In base command class ShowOne in cliff, abstractmethod take_action() + # 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) diff --git a/openstackclient/tests/compute/v2/test_service.py b/openstackclient/tests/compute/v2/test_service.py index 71700aa836..54adaab3d5 100644 --- a/openstackclient/tests/compute/v2/test_service.py +++ b/openstackclient/tests/compute/v2/test_service.py @@ -81,7 +81,7 @@ def test_service_list(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # In base command class Lister in cliff, abstractmethod take_action() + # In base command class Lister in cliff, abstract method take_action() # returns a tuple containing the column names and an iterable # containing the data to be listed. self.cmd.take_action(parsed_args) From 1225ad5f7efc91fda27b7c6a1c84f1c5cadc54a6 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Wed, 10 Feb 2016 19:15:37 +0800 Subject: [PATCH 0597/3095] Identity: Fix DisplayCommandBase comments for cliff Command subclass tests As bug #1477199 describes, the wrong comment below is all over the unit test code of OSC. # DisplayCommandBase.take_action() returns two tuples There is no such class named DisplayCommandBase in OSC. It is in cliff. All OSC command classes inherit from the base classes in cliff, class Command, class Lister and class ShowOne. It is like this: Object |--> Command |--> DisplayCommandBase |--> Lister |--> ShowOne take_action() is an abstract method of class Command, and generally is overwritten by subclasses. * Command.take_action() returns nothing. * Lister.take_action() returns a tuple which contains a tuple of columns and a generator used to generate the data. * ShowOne.take_action() returns an iterator which contains a tuple of columns and a tuple of data. So, this problem should be fixed in 3 steps: 1. Remove all DisplayCommandBase comments for tests of classes inheriting from class Command in cliff as it returns nothing. 2. Fix all DisplayCommandBase comments for tests of classes inheriting from class Lister in cliff. Lister.take_action() returns a tuple and a generator. 3. Fix all DisplayCommandBase comments for tests of classes inheriting from class ShowOne in cliff. ShowOne.take_action() returns two tuples. This patch finishes step 1 in all identity tests. Change-Id: Id7180d10c050c6286b2c05cd990e2e275fbc3d38 Partial-bug: #1477199 --- openstackclient/tests/identity/v2_0/test_endpoint.py | 1 - openstackclient/tests/identity/v2_0/test_role.py | 2 -- openstackclient/tests/identity/v2_0/test_service.py | 1 - openstackclient/tests/identity/v2_0/test_user.py | 8 -------- openstackclient/tests/identity/v3/test_role.py | 6 ------ openstackclient/tests/identity/v3/test_user.py | 9 --------- 6 files changed, 27 deletions(-) diff --git a/openstackclient/tests/identity/v2_0/test_endpoint.py b/openstackclient/tests/identity/v2_0/test_endpoint.py index 354b1e4071..11696f4660 100644 --- a/openstackclient/tests/identity/v2_0/test_endpoint.py +++ b/openstackclient/tests/identity/v2_0/test_endpoint.py @@ -130,7 +130,6 @@ def test_endpoint_delete_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) self.endpoints_mock.delete.assert_called_with( diff --git a/openstackclient/tests/identity/v2_0/test_role.py b/openstackclient/tests/identity/v2_0/test_role.py index c2bacc5207..b660c61491 100644 --- a/openstackclient/tests/identity/v2_0/test_role.py +++ b/openstackclient/tests/identity/v2_0/test_role.py @@ -232,7 +232,6 @@ def test_role_delete_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) self.roles_mock.delete.assert_called_with( @@ -446,7 +445,6 @@ def test_role_remove(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) # RoleManager.remove_user_role(user, role, tenant=None) diff --git a/openstackclient/tests/identity/v2_0/test_service.py b/openstackclient/tests/identity/v2_0/test_service.py index b97786b420..9c0cf8cded 100644 --- a/openstackclient/tests/identity/v2_0/test_service.py +++ b/openstackclient/tests/identity/v2_0/test_service.py @@ -186,7 +186,6 @@ def test_service_delete_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) self.services_mock.delete.assert_called_with( diff --git a/openstackclient/tests/identity/v2_0/test_user.py b/openstackclient/tests/identity/v2_0/test_user.py index a25def8786..0c4370f25c 100644 --- a/openstackclient/tests/identity/v2_0/test_user.py +++ b/openstackclient/tests/identity/v2_0/test_user.py @@ -396,7 +396,6 @@ def test_user_delete_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) self.users_mock.delete.assert_called_with( @@ -554,7 +553,6 @@ def test_user_set_name(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) # Set expected values @@ -585,7 +583,6 @@ def test_user_set_password(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) # UserManager.update_password(user, password) @@ -611,7 +608,6 @@ def test_user_set_password_prompt(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples mocker = mock.Mock() mocker.return_value = 'abc123' with mock.patch("openstackclient.common.utils.get_password", mocker): @@ -639,7 +635,6 @@ def test_user_set_email(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) # Set expected values @@ -669,7 +664,6 @@ def test_user_set_project(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) # UserManager.update_tenant(user, tenant) @@ -694,7 +688,6 @@ def test_user_set_enable(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) # Set expected values @@ -723,7 +716,6 @@ def test_user_set_disable(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) # Set expected values diff --git a/openstackclient/tests/identity/v3/test_role.py b/openstackclient/tests/identity/v3/test_role.py index 1910c8742b..227b06439e 100644 --- a/openstackclient/tests/identity/v3/test_role.py +++ b/openstackclient/tests/identity/v3/test_role.py @@ -304,7 +304,6 @@ def test_role_delete_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) self.roles_mock.delete.assert_called_with( @@ -625,7 +624,6 @@ def test_role_remove_user_domain(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) # Set expected values @@ -658,7 +656,6 @@ def test_role_remove_user_project(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) # Set expected values @@ -692,7 +689,6 @@ def test_role_remove_group_domain(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) # Set expected values @@ -725,7 +721,6 @@ def test_role_remove_group_project(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) # Set expected values @@ -767,7 +762,6 @@ def test_role_set_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) # Set expected values diff --git a/openstackclient/tests/identity/v3/test_user.py b/openstackclient/tests/identity/v3/test_user.py index 1871ed189b..641def0e8a 100644 --- a/openstackclient/tests/identity/v3/test_user.py +++ b/openstackclient/tests/identity/v3/test_user.py @@ -481,7 +481,6 @@ def test_user_delete_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) self.users_mock.delete.assert_called_with( @@ -746,7 +745,6 @@ def test_user_set_name(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) # Set expected values @@ -778,7 +776,6 @@ def test_user_set_password(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) # Set expected values @@ -810,7 +807,6 @@ def test_user_set_password_prompt(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples mocker = mock.Mock() mocker.return_value = 'abc123' with mock.patch("openstackclient.common.utils.get_password", mocker): @@ -844,7 +840,6 @@ def test_user_set_email(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) # Set expected values @@ -875,7 +870,6 @@ def test_user_set_project(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) # Set expected values @@ -908,7 +902,6 @@ def test_user_set_project_domain(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) # Set expected values @@ -939,7 +932,6 @@ def test_user_set_enable(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) # Set expected values @@ -969,7 +961,6 @@ def test_user_set_disable(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) # Set expected values From d0c0cefb84b4d7abff23e7e313acd9725df1be9b Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Wed, 10 Feb 2016 20:51:58 +0800 Subject: [PATCH 0598/3095] Identity: Fix DisplayCommandBase comments for cliff Lister subclass tests As bug #1477199 describes, the wrong comment below is all over the unit test code of OSC. # DisplayCommandBase.take_action() returns two tuples There is no such class named DisplayCommandBase in OSC. It is in cliff. All OSC command classes inherit from the base classes in cliff, class Command, class Lister and class ShowOne. It is like this: Object |--> Command |--> DisplayCommandBase |--> Lister |--> ShowOne take_action() is an abstract method of class Command, and generally is overwritten by subclasses. * Command.take_action() returns nothing. * Lister.take_action() returns a tuple which contains a tuple of columns and a generator used to generate the data. * ShowOne.take_action() returns an iterator which contains a tuple of columns and a tuple of data. So, this problem should be fixed in 3 steps: 1. Remove all DisplayCommandBase comments for tests of classes inheriting from class Command in cliff as it returns nothing. 2. Fix all DisplayCommandBase comments for tests of classes inheriting from class Lister in cliff. Lister.take_action() returns a tuple and a generator. 3. Fix all DisplayCommandBase comments for tests of classes inheriting from class ShowOne in cliff. ShowOne.take_action() returns two tuples. This patch finishes step 2 in all identity tests. Change-Id: I2929ee688b1d7afc52c6ab325982bdc24c60a995 Partial-bug: #1477199 --- .../tests/identity/v2_0/test_catalog.py | 8 ++++-- .../tests/identity/v2_0/test_endpoint.py | 8 ++++-- .../tests/identity/v2_0/test_project.py | 8 ++++-- .../tests/identity/v2_0/test_role.py | 12 ++++++-- .../tests/identity/v2_0/test_service.py | 8 ++++-- .../tests/identity/v2_0/test_user.py | 12 ++++++-- .../tests/identity/v3/test_catalog.py | 4 ++- .../tests/identity/v3/test_consumer.py | 4 ++- .../tests/identity/v3/test_domain.py | 4 ++- .../tests/identity/v3/test_endpoint.py | 16 ++++++++--- .../tests/identity/v3/test_group.py | 16 ++++++++--- .../identity/v3/test_identity_provider.py | 4 ++- .../tests/identity/v3/test_project.py | 12 ++++++-- .../tests/identity/v3/test_region.py | 8 ++++-- .../tests/identity/v3/test_role.py | 28 ++++++++++++++----- .../tests/identity/v3/test_role_assignment.py | 28 ++++++++++++++----- .../tests/identity/v3/test_service.py | 8 ++++-- .../identity/v3/test_service_provider.py | 4 ++- .../tests/identity/v3/test_trust.py | 4 ++- .../tests/identity/v3/test_unscoped_saml.py | 8 ++++-- .../tests/identity/v3/test_user.py | 20 +++++++++---- 21 files changed, 168 insertions(+), 56 deletions(-) diff --git a/openstackclient/tests/identity/v2_0/test_catalog.py b/openstackclient/tests/identity/v2_0/test_catalog.py index ff1d993e98..adbf8c30e6 100644 --- a/openstackclient/tests/identity/v2_0/test_catalog.py +++ b/openstackclient/tests/identity/v2_0/test_catalog.py @@ -72,7 +72,9 @@ def test_catalog_list(self): verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.sc_mock.service_catalog.get_data.assert_called_with() @@ -114,7 +116,9 @@ def test_catalog_list_with_endpoint_url(self): verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.sc_mock.service_catalog.get_data.assert_called_with() diff --git a/openstackclient/tests/identity/v2_0/test_endpoint.py b/openstackclient/tests/identity/v2_0/test_endpoint.py index 11696f4660..9331b2565c 100644 --- a/openstackclient/tests/identity/v2_0/test_endpoint.py +++ b/openstackclient/tests/identity/v2_0/test_endpoint.py @@ -164,7 +164,9 @@ def test_endpoint_list_no_options(self): verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.endpoints_mock.list.assert_called_with() @@ -188,7 +190,9 @@ def test_endpoint_list_long(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.endpoints_mock.list.assert_called_with() diff --git a/openstackclient/tests/identity/v2_0/test_project.py b/openstackclient/tests/identity/v2_0/test_project.py index 69b2926871..ad4ab26555 100644 --- a/openstackclient/tests/identity/v2_0/test_project.py +++ b/openstackclient/tests/identity/v2_0/test_project.py @@ -322,7 +322,9 @@ def test_project_list_no_options(self): verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.projects_mock.list.assert_called_with() @@ -343,7 +345,9 @@ def test_project_list_long(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.projects_mock.list.assert_called_with() diff --git a/openstackclient/tests/identity/v2_0/test_role.py b/openstackclient/tests/identity/v2_0/test_role.py index b660c61491..f884720f7b 100644 --- a/openstackclient/tests/identity/v2_0/test_role.py +++ b/openstackclient/tests/identity/v2_0/test_role.py @@ -260,7 +260,9 @@ def test_role_list_no_options(self): verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.roles_mock.list.assert_called_with() @@ -330,7 +332,9 @@ def test_user_role_list_no_options_def_creds(self): verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.roles_mock.roles_for_user.assert_called_with( @@ -387,7 +391,9 @@ def test_user_role_list_project_def_creds(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.roles_mock.roles_for_user.assert_called_with( diff --git a/openstackclient/tests/identity/v2_0/test_service.py b/openstackclient/tests/identity/v2_0/test_service.py index 9c0cf8cded..27a592d5cf 100644 --- a/openstackclient/tests/identity/v2_0/test_service.py +++ b/openstackclient/tests/identity/v2_0/test_service.py @@ -214,7 +214,9 @@ def test_service_list_no_options(self): verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.services_mock.list.assert_called_with() @@ -237,7 +239,9 @@ def test_service_list_long(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.services_mock.list.assert_called_with() diff --git a/openstackclient/tests/identity/v2_0/test_user.py b/openstackclient/tests/identity/v2_0/test_user.py index 0c4370f25c..62d78c16b2 100644 --- a/openstackclient/tests/identity/v2_0/test_user.py +++ b/openstackclient/tests/identity/v2_0/test_user.py @@ -448,7 +448,9 @@ def test_user_list_no_options(self): verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.users_mock.list.assert_called_with(tenant_id=None) @@ -466,7 +468,9 @@ def test_user_list_project(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) project_id = identity_fakes.PROJECT_2['id'] - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.users_mock.list.assert_called_with(tenant_id=project_id) @@ -483,7 +487,9 @@ def test_user_list_long(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.users_mock.list.assert_called_with(tenant_id=None) diff --git a/openstackclient/tests/identity/v3/test_catalog.py b/openstackclient/tests/identity/v3/test_catalog.py index 6bb962de09..cfae05a1c9 100644 --- a/openstackclient/tests/identity/v3/test_catalog.py +++ b/openstackclient/tests/identity/v3/test_catalog.py @@ -68,7 +68,9 @@ def test_catalog_list(self): verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.sc_mock.service_catalog.get_data.assert_called_with() diff --git a/openstackclient/tests/identity/v3/test_consumer.py b/openstackclient/tests/identity/v3/test_consumer.py index 4e807562fc..7f6af7348c 100644 --- a/openstackclient/tests/identity/v3/test_consumer.py +++ b/openstackclient/tests/identity/v3/test_consumer.py @@ -121,7 +121,9 @@ def test_consumer_list(self): verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.consumers_mock.list.assert_called_with() diff --git a/openstackclient/tests/identity/v3/test_domain.py b/openstackclient/tests/identity/v3/test_domain.py index 969c2df7ef..71a48b4ed2 100644 --- a/openstackclient/tests/identity/v3/test_domain.py +++ b/openstackclient/tests/identity/v3/test_domain.py @@ -215,7 +215,9 @@ def test_domain_list_no_options(self): verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.domains_mock.list.assert_called_with() diff --git a/openstackclient/tests/identity/v3/test_endpoint.py b/openstackclient/tests/identity/v3/test_endpoint.py index fb88e004cd..816f3f9238 100644 --- a/openstackclient/tests/identity/v3/test_endpoint.py +++ b/openstackclient/tests/identity/v3/test_endpoint.py @@ -315,7 +315,9 @@ def test_endpoint_list_no_options(self): verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.endpoints_mock.list.assert_called_with() @@ -342,7 +344,9 @@ def test_endpoint_list_service(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) # Set expected values @@ -374,7 +378,9 @@ def test_endpoint_list_interface(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) # Set expected values @@ -406,7 +412,9 @@ def test_endpoint_list_region(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) # Set expected values diff --git a/openstackclient/tests/identity/v3/test_group.py b/openstackclient/tests/identity/v3/test_group.py index 59a3681971..c5f5dbca7b 100644 --- a/openstackclient/tests/identity/v3/test_group.py +++ b/openstackclient/tests/identity/v3/test_group.py @@ -85,7 +85,9 @@ def test_group_list_no_options(self): verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) # Set expected values @@ -110,7 +112,9 @@ def test_group_list_domain(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) # Set expected values @@ -135,7 +139,9 @@ def test_group_list_user(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) # Set expected values @@ -160,7 +166,9 @@ def test_group_list_long(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) # Set expected values diff --git a/openstackclient/tests/identity/v3/test_identity_provider.py b/openstackclient/tests/identity/v3/test_identity_provider.py index 50a922b89e..7591151091 100644 --- a/openstackclient/tests/identity/v3/test_identity_provider.py +++ b/openstackclient/tests/identity/v3/test_identity_provider.py @@ -290,7 +290,9 @@ def test_identity_provider_list_no_options(self): verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.identity_providers_mock.list.assert_called_with() diff --git a/openstackclient/tests/identity/v3/test_project.py b/openstackclient/tests/identity/v3/test_project.py index 36540201d0..61b6d2c6f7 100644 --- a/openstackclient/tests/identity/v3/test_project.py +++ b/openstackclient/tests/identity/v3/test_project.py @@ -464,7 +464,9 @@ def test_project_list_no_options(self): verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.projects_mock.list.assert_called_with() @@ -480,7 +482,9 @@ def test_project_list_long(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.projects_mock.list.assert_called_with() @@ -511,7 +515,9 @@ def test_project_list_domain(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.projects_mock.list.assert_called_with( domain=identity_fakes.domain_id) diff --git a/openstackclient/tests/identity/v3/test_region.py b/openstackclient/tests/identity/v3/test_region.py index 9ac9ddf8ed..c54480ee64 100644 --- a/openstackclient/tests/identity/v3/test_region.py +++ b/openstackclient/tests/identity/v3/test_region.py @@ -193,7 +193,9 @@ def test_region_list_no_options(self): verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.regions_mock.list.assert_called_with() @@ -209,7 +211,9 @@ def test_region_list_parent_region_id(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.regions_mock.list.assert_called_with( parent_region_id=identity_fakes.region_parent_region_id) diff --git a/openstackclient/tests/identity/v3/test_role.py b/openstackclient/tests/identity/v3/test_role.py index 227b06439e..7379fe8bc0 100644 --- a/openstackclient/tests/identity/v3/test_role.py +++ b/openstackclient/tests/identity/v3/test_role.py @@ -364,7 +364,9 @@ def test_role_list_no_options(self): verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.roles_mock.list.assert_called_with() @@ -383,7 +385,9 @@ def test_user_list_inherited(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) # Set expected values @@ -409,7 +413,9 @@ def test_user_list_user(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) # Set expected values @@ -437,7 +443,9 @@ def test_role_list_domain_user(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) # Set expected values @@ -472,7 +480,9 @@ def test_role_list_domain_group(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) # Set expected values @@ -507,7 +517,9 @@ def test_role_list_project_user(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) # Set expected values @@ -542,7 +554,9 @@ def test_role_list_project_group(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) # Set expected values diff --git a/openstackclient/tests/identity/v3/test_role_assignment.py b/openstackclient/tests/identity/v3/test_role_assignment.py index 5723a33477..c7ed289bac 100644 --- a/openstackclient/tests/identity/v3/test_role_assignment.py +++ b/openstackclient/tests/identity/v3/test_role_assignment.py @@ -86,7 +86,9 @@ def test_role_assignment_list_no_filters(self): verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.role_assignments_mock.list.assert_called_with( @@ -146,7 +148,9 @@ def test_role_assignment_list_user(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.role_assignments_mock.list.assert_called_with( @@ -206,7 +210,9 @@ def test_role_assignment_list_group(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.role_assignments_mock.list.assert_called_with( @@ -266,7 +272,9 @@ def test_role_assignment_list_domain(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.role_assignments_mock.list.assert_called_with( @@ -326,7 +334,9 @@ def test_role_assignment_list_project(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.role_assignments_mock.list.assert_called_with( @@ -384,7 +394,9 @@ def test_role_assignment_list_effective(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.role_assignments_mock.list.assert_called_with( @@ -444,7 +456,9 @@ def test_role_assignment_list_inherited(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.role_assignments_mock.list.assert_called_with( diff --git a/openstackclient/tests/identity/v3/test_service.py b/openstackclient/tests/identity/v3/test_service.py index c609142cd7..624606b7cf 100644 --- a/openstackclient/tests/identity/v3/test_service.py +++ b/openstackclient/tests/identity/v3/test_service.py @@ -225,7 +225,9 @@ def test_service_list_no_options(self): verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.services_mock.list.assert_called_with() @@ -248,7 +250,9 @@ def test_service_list_long(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.services_mock.list.assert_called_with() diff --git a/openstackclient/tests/identity/v3/test_service_provider.py b/openstackclient/tests/identity/v3/test_service_provider.py index 24fa7c7b64..fb7576f1cc 100644 --- a/openstackclient/tests/identity/v3/test_service_provider.py +++ b/openstackclient/tests/identity/v3/test_service_provider.py @@ -220,7 +220,9 @@ def test_service_provider_list_no_options(self): verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.service_providers_mock.list.assert_called_with() diff --git a/openstackclient/tests/identity/v3/test_trust.py b/openstackclient/tests/identity/v3/test_trust.py index b90e78159b..56d4d13477 100644 --- a/openstackclient/tests/identity/v3/test_trust.py +++ b/openstackclient/tests/identity/v3/test_trust.py @@ -167,7 +167,9 @@ def test_trust_list_no_options(self): verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.trusts_mock.list.assert_called_with() diff --git a/openstackclient/tests/identity/v3/test_unscoped_saml.py b/openstackclient/tests/identity/v3/test_unscoped_saml.py index 6a79909462..c2f14493a2 100644 --- a/openstackclient/tests/identity/v3/test_unscoped_saml.py +++ b/openstackclient/tests/identity/v3/test_unscoped_saml.py @@ -52,7 +52,9 @@ def test_accessible_projects_list(self): verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.projects_mock.list.assert_called_with() @@ -101,7 +103,9 @@ def test_accessible_domains_list(self): verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.domains_mock.list.assert_called_with() diff --git a/openstackclient/tests/identity/v3/test_user.py b/openstackclient/tests/identity/v3/test_user.py index 641def0e8a..f77809d0ed 100644 --- a/openstackclient/tests/identity/v3/test_user.py +++ b/openstackclient/tests/identity/v3/test_user.py @@ -552,7 +552,9 @@ def test_user_list_no_options(self): verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) # Set expected values @@ -577,7 +579,9 @@ def test_user_list_domain(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) # Set expected values @@ -602,7 +606,9 @@ def test_user_list_group(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) # Set expected values @@ -627,7 +633,9 @@ def test_user_list_long(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) # Set expected values @@ -672,7 +680,9 @@ def test_user_list_project(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) kwargs = { From 9f71b777ac7357c96ac7fa662ba18fa67b93baa0 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Wed, 10 Feb 2016 23:11:58 +0800 Subject: [PATCH 0599/3095] Identity: Fix DisplayCommandBase comments for cliff ShowOne subclass tests As bug #1477199 describes, the wrong comment below is all over the unit test code of OSC. # DisplayCommandBase.take_action() returns two tuples There is no such class named DisplayCommandBase in OSC. It is in cliff. All OSC command classes inherit from the base classes in cliff, class Command, class Lister and class ShowOne. It is like this: Object |--> Command |--> DisplayCommandBase |--> Lister |--> ShowOne take_action() is an abstract method of class Command, and generally is overwritten by subclasses. * Command.take_action() returns nothing. * Lister.take_action() returns a tuple which contains a tuple of columns and a generator used to generate the data. * ShowOne.take_action() returns an iterator which contains a tuple of columns and a tuple of data. So, this problem should be fixed in 3 steps: 1. Remove all DisplayCommandBase comments for tests of classes inheriting from class Command in cliff as it returns nothing. 2. Fix all DisplayCommandBase comments for tests of classes inheriting from class Lister in cliff. Lister.take_action() returns a tuple and a generator. 3. Fix all DisplayCommandBase comments for tests of classes inheriting from class ShowOne in cliff. ShowOne.take_action() returns two tuples. This patch finishes step 3 in all identity tests. Change-Id: I1f05e833cdacd30915954e4220b6e1f16ac1ed40 Closes-bug: #1477199 --- .../tests/identity/v2_0/test_catalog.py | 4 +- .../tests/identity/v2_0/test_endpoint.py | 8 +++- .../tests/identity/v2_0/test_project.py | 32 +++++++++++---- .../tests/identity/v2_0/test_role.py | 20 +++++++--- .../tests/identity/v2_0/test_service.py | 20 +++++++--- .../tests/identity/v2_0/test_token.py | 4 +- .../tests/identity/v2_0/test_user.py | 40 ++++++++++++++----- .../tests/identity/v3/test_catalog.py | 4 +- .../tests/identity/v3/test_domain.py | 20 +++++++--- .../tests/identity/v3/test_endpoint.py | 20 +++++++--- .../tests/identity/v3/test_project.py | 28 +++++++++---- .../tests/identity/v3/test_region.py | 16 ++++++-- .../tests/identity/v3/test_role.py | 8 +++- .../tests/identity/v3/test_service.py | 20 +++++++--- .../tests/identity/v3/test_token.py | 8 +++- .../tests/identity/v3/test_trust.py | 8 +++- .../tests/identity/v3/test_user.py | 40 ++++++++++++++----- 17 files changed, 225 insertions(+), 75 deletions(-) diff --git a/openstackclient/tests/identity/v2_0/test_catalog.py b/openstackclient/tests/identity/v2_0/test_catalog.py index adbf8c30e6..1e27bb3cfc 100644 --- a/openstackclient/tests/identity/v2_0/test_catalog.py +++ b/openstackclient/tests/identity/v2_0/test_catalog.py @@ -150,7 +150,9 @@ def test_catalog_show(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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.sc_mock.service_catalog.get_data.assert_called_with() diff --git a/openstackclient/tests/identity/v2_0/test_endpoint.py b/openstackclient/tests/identity/v2_0/test_endpoint.py index 9331b2565c..088fdcd1c6 100644 --- a/openstackclient/tests/identity/v2_0/test_endpoint.py +++ b/openstackclient/tests/identity/v2_0/test_endpoint.py @@ -69,7 +69,9 @@ def test_endpoint_create(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # EndpointManager.create(region, service_id, publicurl, adminurl, @@ -243,7 +245,9 @@ def test_endpoint_show(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # EndpointManager.list() diff --git a/openstackclient/tests/identity/v2_0/test_project.py b/openstackclient/tests/identity/v2_0/test_project.py index ad4ab26555..669c3eabe7 100644 --- a/openstackclient/tests/identity/v2_0/test_project.py +++ b/openstackclient/tests/identity/v2_0/test_project.py @@ -70,7 +70,9 @@ def test_project_create_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # Set expected values @@ -96,7 +98,9 @@ def test_project_create_description(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # Set expected values @@ -124,7 +128,9 @@ def test_project_create_enable(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # Set expected values @@ -152,7 +158,9 @@ def test_project_create_disable(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # Set expected values @@ -180,7 +188,9 @@ def test_project_create_property(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # Set expected values @@ -221,7 +231,9 @@ def _raise_conflict(*args, **kwargs): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # ProjectManager.create(name, description, enabled) @@ -251,7 +263,9 @@ def test_project_create_or_show_not_exists(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # Set expected values @@ -553,7 +567,9 @@ def test_project_show(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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.projects_mock.get.assert_called_with( identity_fakes.project_id, diff --git a/openstackclient/tests/identity/v2_0/test_role.py b/openstackclient/tests/identity/v2_0/test_role.py index f884720f7b..03b7f92481 100644 --- a/openstackclient/tests/identity/v2_0/test_role.py +++ b/openstackclient/tests/identity/v2_0/test_role.py @@ -86,7 +86,9 @@ def test_role_add(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # RoleManager.add_user_role(user, role, tenant=None) @@ -137,7 +139,9 @@ def test_role_create_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # RoleManager.create(name) @@ -171,7 +175,9 @@ def _raise_conflict(*args, **kwargs): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # RoleManager.get(name, description, enabled) @@ -196,7 +202,9 @@ def test_role_create_or_show_not_exists(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # RoleManager.create(name) @@ -484,7 +492,9 @@ def test_service_show(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # RoleManager.get(role) diff --git a/openstackclient/tests/identity/v2_0/test_service.py b/openstackclient/tests/identity/v2_0/test_service.py index 27a592d5cf..606b143327 100644 --- a/openstackclient/tests/identity/v2_0/test_service.py +++ b/openstackclient/tests/identity/v2_0/test_service.py @@ -69,7 +69,9 @@ def test_service_create_with_type_positional(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # ServiceManager.create(name, service_type, description) @@ -95,7 +97,9 @@ def test_service_create_with_type_option(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # ServiceManager.create(name, service_type, description) @@ -121,7 +125,9 @@ def test_service_create_with_name_option(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # ServiceManager.create(name, service_type, description) @@ -148,7 +154,9 @@ def test_service_create_description(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # ServiceManager.create(name, service_type, description) @@ -280,7 +288,9 @@ def test_service_show(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # ServiceManager.get(id) diff --git a/openstackclient/tests/identity/v2_0/test_token.py b/openstackclient/tests/identity/v2_0/test_token.py index ce2faef39f..7687a063f9 100644 --- a/openstackclient/tests/identity/v2_0/test_token.py +++ b/openstackclient/tests/identity/v2_0/test_token.py @@ -43,7 +43,9 @@ def test_token_issue(self): verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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.sc_mock.get_token.assert_called_with() diff --git a/openstackclient/tests/identity/v2_0/test_user.py b/openstackclient/tests/identity/v2_0/test_user.py index 62d78c16b2..a7332e6366 100644 --- a/openstackclient/tests/identity/v2_0/test_user.py +++ b/openstackclient/tests/identity/v2_0/test_user.py @@ -83,7 +83,9 @@ def test_user_create_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # Set expected values @@ -114,7 +116,9 @@ def test_user_create_password(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # Set expected values @@ -143,7 +147,9 @@ def test_user_create_password_prompt(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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. mocker = mock.Mock() mocker.return_value = 'abc123' with mock.patch("openstackclient.common.utils.get_password", mocker): @@ -176,7 +182,9 @@ def test_user_create_email(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # Set expected values @@ -221,7 +229,9 @@ def test_user_create_project(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # Set expected values @@ -259,7 +269,9 @@ def test_user_create_enable(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # Set expected values @@ -290,7 +302,9 @@ def test_user_create_disable(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # Set expected values @@ -332,7 +346,9 @@ def _raise_conflict(*args, **kwargs): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # UserManager.create(name, password, email, tenant_id=, enabled=) @@ -352,7 +368,9 @@ def test_user_create_or_show_not_exists(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # Set expected values @@ -758,7 +776,9 @@ def test_user_show(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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.users_mock.get.assert_called_with(identity_fakes.user_id) diff --git a/openstackclient/tests/identity/v3/test_catalog.py b/openstackclient/tests/identity/v3/test_catalog.py index cfae05a1c9..a03c9d3ead 100644 --- a/openstackclient/tests/identity/v3/test_catalog.py +++ b/openstackclient/tests/identity/v3/test_catalog.py @@ -103,7 +103,9 @@ def test_catalog_show(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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.sc_mock.service_catalog.get_data.assert_called_with() diff --git a/openstackclient/tests/identity/v3/test_domain.py b/openstackclient/tests/identity/v3/test_domain.py index 71a48b4ed2..9de6b26a61 100644 --- a/openstackclient/tests/identity/v3/test_domain.py +++ b/openstackclient/tests/identity/v3/test_domain.py @@ -63,7 +63,9 @@ def test_domain_create_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # Set expected values @@ -90,7 +92,9 @@ def test_domain_create_description(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # Set expected values @@ -117,7 +121,9 @@ def test_domain_create_enable(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # Set expected values @@ -144,7 +150,9 @@ def test_domain_create_disable(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # Set expected values @@ -382,7 +390,9 @@ def test_domain_show(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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.domains_mock.get.assert_called_with( identity_fakes.domain_id, diff --git a/openstackclient/tests/identity/v3/test_endpoint.py b/openstackclient/tests/identity/v3/test_endpoint.py index 816f3f9238..1c481930a7 100644 --- a/openstackclient/tests/identity/v3/test_endpoint.py +++ b/openstackclient/tests/identity/v3/test_endpoint.py @@ -81,7 +81,9 @@ def test_endpoint_create_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # Set expected values @@ -126,7 +128,9 @@ def test_endpoint_create_region(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # Set expected values @@ -170,7 +174,9 @@ def test_endpoint_create_enable(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # Set expected values @@ -214,7 +220,9 @@ def test_endpoint_create_disable(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # Set expected values @@ -673,7 +681,9 @@ def test_endpoint_show(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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.endpoints_mock.get.assert_called_with( identity_fakes.endpoint_id, diff --git a/openstackclient/tests/identity/v3/test_project.py b/openstackclient/tests/identity/v3/test_project.py index 61b6d2c6f7..b834e94308 100644 --- a/openstackclient/tests/identity/v3/test_project.py +++ b/openstackclient/tests/identity/v3/test_project.py @@ -83,7 +83,9 @@ def test_project_create_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # Set expected values @@ -125,7 +127,9 @@ def test_project_create_description(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # Set expected values @@ -159,7 +163,9 @@ def test_project_create_domain(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # Set expected values @@ -225,7 +231,9 @@ def test_project_create_enable(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # Set expected values @@ -258,7 +266,9 @@ def test_project_create_disable(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # Set expected values @@ -290,7 +300,9 @@ def test_project_create_property(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # Set expected values @@ -743,7 +755,9 @@ def test_project_show(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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.projects_mock.get.assert_called_with( identity_fakes.project_id, diff --git a/openstackclient/tests/identity/v3/test_region.py b/openstackclient/tests/identity/v3/test_region.py index c54480ee64..f5f5079337 100644 --- a/openstackclient/tests/identity/v3/test_region.py +++ b/openstackclient/tests/identity/v3/test_region.py @@ -64,7 +64,9 @@ def test_region_create_description(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # Set expected values @@ -89,7 +91,9 @@ def test_region_create_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # Set expected values @@ -116,7 +120,9 @@ def test_region_create_parent_region_id(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # Set expected values @@ -320,7 +326,9 @@ def test_region_show(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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.regions_mock.get.assert_called_with( identity_fakes.region_id, diff --git a/openstackclient/tests/identity/v3/test_role.py b/openstackclient/tests/identity/v3/test_role.py index 7379fe8bc0..f3661324c8 100644 --- a/openstackclient/tests/identity/v3/test_role.py +++ b/openstackclient/tests/identity/v3/test_role.py @@ -258,7 +258,9 @@ def test_role_create_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # Set expected values @@ -812,7 +814,9 @@ def test_role_show(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # RoleManager.get(role) diff --git a/openstackclient/tests/identity/v3/test_service.py b/openstackclient/tests/identity/v3/test_service.py index 624606b7cf..2bc5927f14 100644 --- a/openstackclient/tests/identity/v3/test_service.py +++ b/openstackclient/tests/identity/v3/test_service.py @@ -73,7 +73,9 @@ def test_service_create_name(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # ServiceManager.create(name=, type=, enabled=, **kwargs) @@ -101,7 +103,9 @@ def test_service_create_description(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # ServiceManager.create(name=, type=, enabled=, **kwargs) @@ -129,7 +133,9 @@ def test_service_create_enable(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # ServiceManager.create(name=, type=, enabled=, **kwargs) @@ -157,7 +163,9 @@ def test_service_create_disable(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # ServiceManager.create(name=, type=, enabled=, **kwargs) @@ -469,7 +477,9 @@ def test_service_show(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # ServiceManager.get(id) diff --git a/openstackclient/tests/identity/v3/test_token.py b/openstackclient/tests/identity/v3/test_token.py index 6ad4845da7..25019e863d 100644 --- a/openstackclient/tests/identity/v3/test_token.py +++ b/openstackclient/tests/identity/v3/test_token.py @@ -44,7 +44,9 @@ def test_token_issue_with_project_id(self): self.sc_mock.get_token.return_value = \ identity_fakes.TOKEN_WITH_PROJECT_ID - # DisplayCommandBase.take_action() returns two tuples + # 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.sc_mock.get_token.assert_called_with() @@ -66,7 +68,9 @@ def test_token_issue_with_domain_id(self): self.sc_mock.get_token.return_value = \ identity_fakes.TOKEN_WITH_DOMAIN_ID - # DisplayCommandBase.take_action() returns two tuples + # 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.sc_mock.get_token.assert_called_with() diff --git a/openstackclient/tests/identity/v3/test_trust.py b/openstackclient/tests/identity/v3/test_trust.py index 56d4d13477..2a74887228 100644 --- a/openstackclient/tests/identity/v3/test_trust.py +++ b/openstackclient/tests/identity/v3/test_trust.py @@ -81,7 +81,9 @@ def test_trust_create_basic(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # Set expected values @@ -211,7 +213,9 @@ def test_trust_show(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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.trusts_mock.get.assert_called_with(identity_fakes.trust_id) diff --git a/openstackclient/tests/identity/v3/test_user.py b/openstackclient/tests/identity/v3/test_user.py index f77809d0ed..3757c5f8a2 100644 --- a/openstackclient/tests/identity/v3/test_user.py +++ b/openstackclient/tests/identity/v3/test_user.py @@ -104,7 +104,9 @@ def test_user_create_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # Set expected values @@ -141,7 +143,9 @@ def test_user_create_password(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # Set expected values @@ -176,7 +180,9 @@ def test_user_create_password_prompt(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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. mocker = mock.Mock() mocker.return_value = 'abc123' with mock.patch("openstackclient.common.utils.get_password", mocker): @@ -214,7 +220,9 @@ def test_user_create_email(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # Set expected values @@ -264,7 +272,9 @@ def test_user_create_project(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # Set expected values @@ -324,7 +334,9 @@ def test_user_create_project_domain(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # Set expected values @@ -367,7 +379,9 @@ def test_user_create_domain(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # Set expected values @@ -401,7 +415,9 @@ def test_user_create_enable(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # Set expected values @@ -435,7 +451,9 @@ def test_user_create_disable(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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) # Set expected values @@ -1073,7 +1091,9 @@ def test_user_show(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # DisplayCommandBase.take_action() returns two tuples + # 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.users_mock.get.assert_called_with(identity_fakes.user_id) From a29c9732d7434902fd36e0417e16bb760875591f Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Fri, 5 Feb 2016 09:02:27 -0600 Subject: [PATCH 0600/3095] Refactor security group rule delete to use SDK Refactored the 'os security group rule delete' command to use the SDK when neutron is enabled, but continue to use the nova client when nova network is enabled. This patch set also introduces new FakeSecurityGroupRule classes for testing network and compute security group rules. And fixes were made to the network FakeSecurityGroup class. Change-Id: I8d0917925aa464e8255defae95a2a2adfb6cfb75 Partial-Bug: #1519512 Related-to: blueprint neutron-client --- openstackclient/compute/v2/security_group.py | 18 ---- openstackclient/network/common.py | 8 +- .../network/v2/security_group_rule.py | 35 ++++++ openstackclient/tests/compute/v2/fakes.py | 62 +++++++++++ openstackclient/tests/network/v2/fakes.py | 74 ++++++++++--- .../network/v2/test_security_group_rule.py | 100 ++++++++++++++++++ setup.cfg | 2 +- 7 files changed, 266 insertions(+), 33 deletions(-) create mode 100644 openstackclient/network/v2/security_group_rule.py create mode 100644 openstackclient/tests/network/v2/test_security_group_rule.py diff --git a/openstackclient/compute/v2/security_group.py b/openstackclient/compute/v2/security_group.py index 2a8908d750..6f2e1a52ca 100644 --- a/openstackclient/compute/v2/security_group.py +++ b/openstackclient/compute/v2/security_group.py @@ -169,24 +169,6 @@ def take_action(self, parsed_args): return zip(*sorted(six.iteritems(info))) -class DeleteSecurityGroupRule(command.Command): - """Delete a security group rule""" - - def get_parser(self, prog_name): - parser = super(DeleteSecurityGroupRule, self).get_parser(prog_name) - parser.add_argument( - 'rule', - metavar='', - help='Security group rule to delete (ID only)', - ) - return parser - - def take_action(self, parsed_args): - - compute_client = self.app.client_manager.compute - compute_client.security_group_rules.delete(parsed_args.rule) - - class ListSecurityGroup(command.Lister): """List security groups""" diff --git a/openstackclient/network/common.py b/openstackclient/network/common.py index c539dd052f..ae48952352 100644 --- a/openstackclient/network/common.py +++ b/openstackclient/network/common.py @@ -19,7 +19,13 @@ @six.add_metaclass(abc.ABCMeta) class NetworkAndComputeCommand(command.Command): - """Network and Compute Command""" + """Network and Compute Command + + Command class for commands that support implementation via + the network or compute endpoint. Such commands have different + implementations for take_action() and may even have different + arguments. + """ def take_action(self, parsed_args): if self.app.client_manager.is_network_endpoint_enabled(): diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py new file mode 100644 index 0000000000..beeeaff73b --- /dev/null +++ b/openstackclient/network/v2/security_group_rule.py @@ -0,0 +1,35 @@ +# 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. +# + +"""Security Group Rule action implementations""" + +from openstackclient.network import common + + +class DeleteSecurityGroupRule(common.NetworkAndComputeCommand): + """Delete a security group rule""" + + def update_parser_common(self, parser): + parser.add_argument( + 'rule', + metavar='', + help='Security group rule to delete (ID only)', + ) + return parser + + def take_action_network(self, client, parsed_args): + obj = client.find_security_group_rule(parsed_args.rule) + client.delete_security_group_rule(obj) + + def take_action_compute(self, client, parsed_args): + client.security_group_rules.delete(parsed_args.rule) diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 68a66740ca..d2070c2fda 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -228,6 +228,68 @@ def create_hypervisors(attrs={}, count=2): return hypervisors +class FakeSecurityGroupRule(object): + """Fake one or more security group rules.""" + + @staticmethod + def create_one_security_group_rule(attrs={}, methods={}): + """Create a fake security group rule. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :return: + A FakeResource object, with id, etc. + """ + # Set default attributes. + security_group_rule_attrs = { + 'from_port': -1, + 'group': {}, + 'id': 'security-group-rule-id-' + uuid.uuid4().hex, + 'ip_protocol': 'icmp', + 'ip_range': {'cidr': '0.0.0.0/0'}, + 'parent_group_id': 'security-group-id-' + uuid.uuid4().hex, + 'to_port': -1, + } + + # Overwrite default attributes. + security_group_rule_attrs.update(attrs) + + # Set default methods. + security_group_rule_methods = {} + + # Overwrite default methods. + security_group_rule_methods.update(methods) + + security_group_rule = fakes.FakeResource( + info=copy.deepcopy(security_group_rule_attrs), + methods=copy.deepcopy(security_group_rule_methods), + loaded=True) + return security_group_rule + + @staticmethod + def create_security_group_rules(attrs={}, methods={}, count=2): + """Create multiple fake security group rules. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :param int count: + The number of security group rules to fake + :return: + A list of FakeResource objects faking the security group rules + """ + security_group_rules = [] + for i in range(0, count): + security_group_rules.append( + FakeSecurityGroupRule.create_one_security_group_rule( + attrs, methods)) + + return security_group_rules + + class FakeServer(object): """Fake one or more compute servers.""" diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 516995eb02..d5de79101e 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -463,28 +463,76 @@ def create_security_groups(attrs={}, methods={}, count=2): security_groups = [] for i in range(0, count): security_groups.append( - FakeRouter.create_one_security_group(attrs, methods)) + FakeSecurityGroup.create_one_security_group(attrs, methods)) return security_groups + +class FakeSecurityGroupRule(object): + """Fake one or more security group rules.""" + @staticmethod - def get_security_groups(security_groups=None, count=2): - """Get an iterable MagicMock object with a list of faked security groups. + def create_one_security_group_rule(attrs={}, methods={}): + """Create a fake security group rule. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :return: + A FakeResource object, with id, name, etc. + """ + # Set default attributes. + security_group_rule_attrs = { + 'description': 'security-group-rule-desc-' + uuid.uuid4().hex, + 'direction': 'ingress', + 'ethertype': 'IPv4', + 'id': 'security-group-rule-id-' + uuid.uuid4().hex, + 'name': 'security-group-rule-name-' + uuid.uuid4().hex, + 'port_range_max': None, + 'port_range_min': None, + 'protocol': None, + 'remote_group_id': 'remote-security-group-id-' + uuid.uuid4().hex, + 'remote_ip_prefix': None, + 'security_group_id': 'security-group-id-' + uuid.uuid4().hex, + 'tenant_id': 'project-id-' + uuid.uuid4().hex, + } + + # Overwrite default attributes. + security_group_rule_attrs.update(attrs) - If security group list is provided, then initialize the Mock object - with the list. Otherwise create one. + # Set default methods. + security_group_rule_methods = {} + + # Overwrite default methods. + security_group_rule_methods.update(methods) + + security_group_rule = fakes.FakeResource( + info=copy.deepcopy(security_group_rule_attrs), + methods=copy.deepcopy(security_group_rule_methods), + loaded=True) + return security_group_rule + + @staticmethod + def create_security_group_rules(attrs={}, methods={}, count=2): + """Create multiple fake security group rules. - :param List security groups: - A list of FakeResource objects faking security groups + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods :param int count: - The number of security groups to fake + The number of security group rules to fake :return: - An iterable Mock object with side_effect set to a list of faked - security groups + A list of FakeResource objects faking the security group rules """ - if security_groups is None: - security_groups = FakeRouter.create_security_groups(count) - return mock.MagicMock(side_effect=security_groups) + security_group_rules = [] + for i in range(0, count): + security_group_rules.append( + FakeSecurityGroupRule.create_one_security_group_rule( + attrs, methods)) + + return security_group_rules class FakeSubnet(object): diff --git a/openstackclient/tests/network/v2/test_security_group_rule.py b/openstackclient/tests/network/v2/test_security_group_rule.py new file mode 100644 index 0000000000..e07066329c --- /dev/null +++ b/openstackclient/tests/network/v2/test_security_group_rule.py @@ -0,0 +1,100 @@ +# 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 security_group_rule +from openstackclient.tests.compute.v2 import fakes as compute_fakes +from openstackclient.tests.network.v2 import fakes as network_fakes + + +class TestSecurityGroupRuleNetwork(network_fakes.TestNetworkV2): + + def setUp(self): + super(TestSecurityGroupRuleNetwork, self).setUp() + + # Get a shortcut to the network client + self.network = self.app.client_manager.network + + +class TestSecurityGroupRuleCompute(compute_fakes.TestComputev2): + + def setUp(self): + super(TestSecurityGroupRuleCompute, self).setUp() + + # Get a shortcut to the network client + self.compute = self.app.client_manager.compute + + +class TestDeleteSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): + + # The security group rule to be deleted. + _security_group_rule = \ + network_fakes.FakeSecurityGroupRule.create_one_security_group_rule() + + def setUp(self): + super(TestDeleteSecurityGroupRuleNetwork, self).setUp() + + self.network.delete_security_group_rule = mock.Mock(return_value=None) + + self.network.find_security_group_rule = mock.Mock( + return_value=self._security_group_rule) + + # Get the command object to test + self.cmd = security_group_rule.DeleteSecurityGroupRule( + self.app, self.namespace) + + def test_security_group_rule_delete(self): + arglist = [ + self._security_group_rule.id, + ] + verifylist = [ + ('rule', self._security_group_rule.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.network.delete_security_group_rule.assert_called_with( + self._security_group_rule) + self.assertEqual(None, result) + + +class TestDeleteSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): + + # The security group rule to be deleted. + _security_group_rule = \ + compute_fakes.FakeSecurityGroupRule.create_one_security_group_rule() + + def setUp(self): + super(TestDeleteSecurityGroupRuleCompute, self).setUp() + + self.app.client_manager.network_endpoint_enabled = False + + # Get the command object to test + self.cmd = security_group_rule.DeleteSecurityGroupRule(self.app, None) + + def test_security_group_rule_delete(self): + arglist = [ + self._security_group_rule.id, + ] + verifylist = [ + ('rule', self._security_group_rule.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.compute.security_group_rules.delete.assert_called_with( + self._security_group_rule.id) + self.assertEqual(None, result) diff --git a/setup.cfg b/setup.cfg index 4cf9622ceb..59c0f0a8c1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -107,7 +107,6 @@ openstack.compute.v2 = security_group_set = openstackclient.compute.v2.security_group:SetSecurityGroup security_group_show = openstackclient.compute.v2.security_group:ShowSecurityGroup security_group_rule_create = openstackclient.compute.v2.security_group:CreateSecurityGroupRule - security_group_rule_delete = openstackclient.compute.v2.security_group:DeleteSecurityGroupRule security_group_rule_list = openstackclient.compute.v2.security_group:ListSecurityGroupRule server_add_security_group = openstackclient.compute.v2.server:AddServerSecurityGroup @@ -340,6 +339,7 @@ openstack.network.v2 = router_set = openstackclient.network.v2.router:SetRouter router_show = openstackclient.network.v2.router:ShowRouter security_group_delete = openstackclient.network.v2.security_group:DeleteSecurityGroup + security_group_rule_delete = openstackclient.network.v2.security_group_rule:DeleteSecurityGroupRule subnet_list = openstackclient.network.v2.subnet:ListSubnet openstack.object_store.v1 = From b3a4b8852a209c69d2b9432339b138385852addc Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Wed, 10 Feb 2016 13:37:42 -0600 Subject: [PATCH 0601/3095] Refactor network AZ exception handling Exceptions that occur while getting network availability zones should not be masked as if the extension does not exist. Change-Id: I07213ec6c4d83e97261b58bf8d42417c1cdfae6a Related-Bug: #1534202 --- openstackclient/common/availability_zone.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openstackclient/common/availability_zone.py b/openstackclient/common/availability_zone.py index a941418be2..a6d11b7849 100644 --- a/openstackclient/common/availability_zone.py +++ b/openstackclient/common/availability_zone.py @@ -146,21 +146,20 @@ def _get_volume_availability_zones(self, parsed_args): def _get_network_availability_zones(self, parsed_args): network_client = self.app.client_manager.network - data = [] try: # Verify that the extension exists. network_client.find_extension('Availability Zone', ignore_missing=False) - data = network_client.availability_zones() except Exception as e: self.log.debug('Network availability zone exception: ' + str(e)) if parsed_args.network: message = "Availability zones list not supported by " \ "Network API" self.log.warning(message) + return [] result = [] - for zone in data: + for zone in network_client.availability_zones(): result += _xform_network_availability_zone(zone) return result From dda45e3c39f32213ddbd74aab5421ed34cc95245 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 10 Feb 2016 21:59:14 +0000 Subject: [PATCH 0602/3095] Updated from global requirements Change-Id: Icdee08fa079eb3646388567050643c29eac1eca8 --- requirements.txt | 2 +- test-requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index cc189ba7ba..f527fc7503 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ pbr>=1.6 # Apache-2.0 six>=1.9.0 # MIT Babel>=1.3 # BSD -cliff>=1.15.0 # Apache-2.0 +cliff!=1.16.0,>=1.15.0 # Apache-2.0 keystoneauth1>=2.1.0 # Apache-2.0 openstacksdk>=0.7.4 # Apache-2.0 os-client-config>=1.13.1 # Apache-2.0 diff --git a/test-requirements.txt b/test-requirements.txt index 2255cd3b3b..6e9437233b 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -6,7 +6,7 @@ hacking<0.11,>=0.10.0 coverage>=3.6 # Apache-2.0 discover # BSD fixtures>=1.3.1 # Apache-2.0/BSD -mock>=1.2 # BSD +mock>=1.2;python_version<'3.3' # BSD oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 reno>=0.1.1 # Apache2 From 9d57709cccc7341b190f178bac1ddb7596a9472c Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Thu, 11 Feb 2016 20:02:12 +0800 Subject: [PATCH 0603/3095] Trivial: Fix a typo in test_network.py Change-Id: I5fe4865473ea885b54d02b5d174a632221f815c8 --- openstackclient/tests/network/v2/test_network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index 9829d578dd..d6c6fbca2f 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -503,7 +503,7 @@ def test_set_nothing(self): class TestShowNetwork(TestNetwork): - # The network to set. + # The network to show. _network = network_fakes.FakeNetwork.create_one_network() columns = ( From 7d6d23d3780ef176eca9b353b5645762b13846c0 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Thu, 11 Feb 2016 07:30:56 -0600 Subject: [PATCH 0604/3095] Fix identity test_role functional tests A recent keystone change [1] resulted in the domain_id field being included when showing a role. [1] https://github.com/openstack/keystone/commit/407eabde417e85bb35bc7cbf1995857f4b20aeca Change-Id: I344f4d727f2a16217c075ad8b8393c1e0a233c2e Closes-Bug: #1544547 --- functional/tests/identity/v2/test_identity.py | 2 +- functional/tests/identity/v3/test_identity.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/functional/tests/identity/v2/test_identity.py b/functional/tests/identity/v2/test_identity.py index 1badf2fdcc..91a77b6785 100644 --- a/functional/tests/identity/v2/test_identity.py +++ b/functional/tests/identity/v2/test_identity.py @@ -24,7 +24,7 @@ class IdentityTests(test.TestCase): 'username', 'domain_id', 'default_project_id'] PROJECT_FIELDS = ['enabled', 'id', 'name', 'description', 'domain_id'] TOKEN_FIELDS = ['expires', 'id', 'project_id', 'user_id'] - ROLE_FIELDS = ['id', 'name', 'links'] + ROLE_FIELDS = ['id', 'name', 'links', 'domain_id'] SERVICE_FIELDS = ['id', 'enabled', 'name', 'type', 'description'] ENDPOINT_FIELDS = ['id', 'region', 'service_id', 'service_name', 'service_type', 'enabled', 'publicurl', diff --git a/functional/tests/identity/v3/test_identity.py b/functional/tests/identity/v3/test_identity.py index 3164e8fbb5..88f5196b41 100644 --- a/functional/tests/identity/v3/test_identity.py +++ b/functional/tests/identity/v3/test_identity.py @@ -30,7 +30,7 @@ class IdentityTests(test.TestCase): 'domain_id', 'default_project_id', 'description'] PROJECT_FIELDS = ['description', 'id', 'domain_id', 'is_domain', 'enabled', 'name', 'parent_id', 'links'] - ROLE_FIELDS = ['id', 'name', 'links'] + ROLE_FIELDS = ['id', 'name', 'links', 'domain_id'] SERVICE_FIELDS = ['id', 'enabled', 'name', 'type', 'description'] REGION_FIELDS = ['description', 'enabled', 'parent_region', 'region'] ENDPOINT_FIELDS = ['id', 'region', 'region_id', 'service_id', From 04e45bbacdcb17294454a85a835cc8393477aa27 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Fri, 5 Feb 2016 13:39:40 -0600 Subject: [PATCH 0605/3095] Add NetworkAndCompute Lister and ShowOne classes This patch set introduces the NetworkAndComputeLister and NetworkAndComputeShowOne classes which are related to the NetworkAndComputeCommand class. These classes are for commands that must support neutron and nova network. The new classes allows both the parser and actions to be unique. Change-Id: I1b59264cd40aaf1062f4e8db233ccb7fd0e95f0e Partial-Bug: #1519511 Partial-Bug: #1519512 Related-to: blueprint neutron-client --- openstackclient/network/common.py | 102 ++++++++++++++++ openstackclient/tests/network/test_common.py | 118 ++++++++++++++----- 2 files changed, 193 insertions(+), 27 deletions(-) diff --git a/openstackclient/network/common.py b/openstackclient/network/common.py index c539dd052f..cc343c3c13 100644 --- a/openstackclient/network/common.py +++ b/openstackclient/network/common.py @@ -60,3 +60,105 @@ def take_action_network(self, client, parsed_args): def take_action_compute(self, client, parsed_args): """Override to do something useful.""" pass + + +@six.add_metaclass(abc.ABCMeta) +class NetworkAndComputeLister(command.Lister): + """Network and Compute Lister + + Lister class for commands that support implementation via + the network or compute endpoint. Such commands have different + implementations for take_action() and may even have different + arguments. + """ + + def take_action(self, parsed_args): + if self.app.client_manager.is_network_endpoint_enabled(): + return self.take_action_network(self.app.client_manager.network, + parsed_args) + else: + return self.take_action_compute(self.app.client_manager.compute, + parsed_args) + + def get_parser(self, prog_name): + self.log.debug('get_parser(%s)', prog_name) + parser = super(NetworkAndComputeLister, self).get_parser(prog_name) + parser = self.update_parser_common(parser) + self.log.debug('common parser: %s', parser) + if self.app.client_manager.is_network_endpoint_enabled(): + return self.update_parser_network(parser) + else: + return self.update_parser_compute(parser) + + def update_parser_common(self, parser): + """Default is no updates to parser.""" + return parser + + def update_parser_network(self, parser): + """Default is no updates to parser.""" + return parser + + def update_parser_compute(self, parser): + """Default is no updates to parser.""" + return parser + + @abc.abstractmethod + def take_action_network(self, client, parsed_args): + """Override to do something useful.""" + pass + + @abc.abstractmethod + def take_action_compute(self, client, parsed_args): + """Override to do something useful.""" + pass + + +@six.add_metaclass(abc.ABCMeta) +class NetworkAndComputeShowOne(command.ShowOne): + """Network and Compute ShowOne + + ShowOne class for commands that support implementation via + the network or compute endpoint. Such commands have different + implementations for take_action() and may even have different + arguments. + """ + + def take_action(self, parsed_args): + if self.app.client_manager.is_network_endpoint_enabled(): + return self.take_action_network(self.app.client_manager.network, + parsed_args) + else: + return self.take_action_compute(self.app.client_manager.compute, + parsed_args) + + def get_parser(self, prog_name): + self.log.debug('get_parser(%s)', prog_name) + parser = super(NetworkAndComputeShowOne, self).get_parser(prog_name) + parser = self.update_parser_common(parser) + self.log.debug('common parser: %s', parser) + if self.app.client_manager.is_network_endpoint_enabled(): + return self.update_parser_network(parser) + else: + return self.update_parser_compute(parser) + + def update_parser_common(self, parser): + """Default is no updates to parser.""" + return parser + + def update_parser_network(self, parser): + """Default is no updates to parser.""" + return parser + + def update_parser_compute(self, parser): + """Default is no updates to parser.""" + return parser + + @abc.abstractmethod + def take_action_network(self, client, parsed_args): + """Override to do something useful.""" + pass + + @abc.abstractmethod + def take_action_compute(self, client, parsed_args): + """Override to do something useful.""" + pass diff --git a/openstackclient/tests/network/test_common.py b/openstackclient/tests/network/test_common.py index a3396b9da8..4700c66a9e 100644 --- a/openstackclient/tests/network/test_common.py +++ b/openstackclient/tests/network/test_common.py @@ -18,57 +18,103 @@ from openstackclient.tests import utils +def _add_common_argument(parser): + parser.add_argument( + 'common', + metavar='', + help='Common argument', + ) + return parser + + +def _add_network_argument(parser): + parser.add_argument( + 'network', + metavar='', + help='Network argument', + ) + return parser + + +def _add_compute_argument(parser): + parser.add_argument( + 'compute', + metavar='', + help='Compute argument', + ) + return parser + + class FakeNetworkAndComputeCommand(common.NetworkAndComputeCommand): def update_parser_common(self, parser): - parser.add_argument( - 'common', - metavar='', - help='Common argument', - ) - return parser + return _add_common_argument(parser) def update_parser_network(self, parser): - parser.add_argument( - 'network', - metavar='', - help='Network argument', - ) - return parser + return _add_network_argument(parser) def update_parser_compute(self, parser): - parser.add_argument( - 'compute', - metavar='', - help='Compute argument', - ) - return parser + return _add_compute_argument(parser) def take_action_network(self, client, parsed_args): - client.network_action(parsed_args) - return 'take_action_network' + return client.network_action(parsed_args) def take_action_compute(self, client, parsed_args): - client.compute_action(parsed_args) - return 'take_action_compute' + return client.compute_action(parsed_args) + +class FakeNetworkAndComputeLister(common.NetworkAndComputeLister): + def update_parser_common(self, parser): + return _add_common_argument(parser) + + def update_parser_network(self, parser): + return _add_network_argument(parser) -class TestNetworkAndComputeCommand(utils.TestCommand): + def update_parser_compute(self, parser): + return _add_compute_argument(parser) + + def take_action_network(self, client, parsed_args): + return client.network_action(parsed_args) + + def take_action_compute(self, client, parsed_args): + return client.compute_action(parsed_args) + + +class FakeNetworkAndComputeShowOne(common.NetworkAndComputeShowOne): + def update_parser_common(self, parser): + return _add_common_argument(parser) + + def update_parser_network(self, parser): + return _add_network_argument(parser) + + def update_parser_compute(self, parser): + return _add_compute_argument(parser) + + def take_action_network(self, client, parsed_args): + return client.network_action(parsed_args) + + def take_action_compute(self, client, parsed_args): + return client.compute_action(parsed_args) + + +class TestNetworkAndCompute(utils.TestCommand): def setUp(self): - super(TestNetworkAndComputeCommand, self).setUp() + super(TestNetworkAndCompute, self).setUp() self.namespace = argparse.Namespace() # Create network client mocks. self.app.client_manager.network = mock.Mock() self.network = self.app.client_manager.network - self.network.network_action = mock.Mock(return_value=None) + self.network.network_action = mock.Mock( + return_value='take_action_network') # Create compute client mocks. self.app.client_manager.compute = mock.Mock() self.compute = self.app.client_manager.compute - self.compute.compute_action = mock.Mock(return_value=None) + self.compute.compute_action = mock.Mock( + return_value='take_action_compute') - # Get the command object to test + # Subclasses can override the command object to test. self.cmd = FakeNetworkAndComputeCommand(self.app, self.namespace) def test_take_action_network(self): @@ -101,3 +147,21 @@ def test_take_action_compute(self): result = self.cmd.take_action(parsed_args) self.compute.compute_action.assert_called_with(parsed_args) self.assertEqual('take_action_compute', result) + + +class TestNetworkAndComputeCommand(TestNetworkAndCompute): + def setUp(self): + super(TestNetworkAndComputeCommand, self).setUp() + self.cmd = FakeNetworkAndComputeCommand(self.app, self.namespace) + + +class TestNetworkAndComputeLister(TestNetworkAndCompute): + def setUp(self): + super(TestNetworkAndComputeLister, self).setUp() + self.cmd = FakeNetworkAndComputeLister(self.app, self.namespace) + + +class TestNetworkAndComputeShowOne(TestNetworkAndCompute): + def setUp(self): + super(TestNetworkAndComputeShowOne, self).setUp() + self.cmd = FakeNetworkAndComputeShowOne(self.app, self.namespace) From ea0b8f91627f5ef4a89f23e391a5733032bccc0e Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Fri, 12 Feb 2016 12:48:33 -0600 Subject: [PATCH 0606/3095] Add quota functional tests Add functional tests for "os quota" commands. Change-Id: I0f5939bf4ce553174c9b7ce55bdb3dce0506c409 Related-Bug: #1528249 Partially-Implements: blueprint neutron-client --- functional/common/test.py | 5 ++++ functional/tests/common/test_quota.py | 38 +++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 functional/tests/common/test_quota.py diff --git a/functional/common/test.py b/functional/common/test.py index 2fc355f82f..1e767af886 100644 --- a/functional/common/test.py +++ b/functional/common/test.py @@ -51,6 +51,11 @@ def openstack(cls, cmd, fail_ok=False): """Executes openstackclient command for the given action.""" return execute('openstack ' + cmd, fail_ok=fail_ok) + @classmethod + def get_openstack_configuration_value(cls, configuration): + opts = cls.get_show_opts([configuration]) + return cls.openstack('configuration show ' + opts) + @classmethod def get_show_opts(cls, fields=[]): return ' -f value ' + ' '.join(['-c ' + it for it in fields]) diff --git a/functional/tests/common/test_quota.py b/functional/tests/common/test_quota.py new file mode 100644 index 0000000000..7a0cb64026 --- /dev/null +++ b/functional/tests/common/test_quota.py @@ -0,0 +1,38 @@ +# 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 functional.common import test + + +class QuotaTests(test.TestCase): + """Functional tests for quota. """ + # Test quota information for compute, network and volume. + EXPECTED_FIELDS = ['instances', 'network', 'volumes'] + PROJECT_NAME = None + + @classmethod + def setUpClass(cls): + cls.PROJECT_NAME =\ + cls.get_openstack_configuration_value('auth.project_name') + + def test_quota_set(self): + # TODO(rtheis): Add --network option once supported on set. + self.openstack('quota set --instances 11 --volumes 11 ' + + self.PROJECT_NAME) + opts = self.get_show_opts(self.EXPECTED_FIELDS) + raw_output = self.openstack('quota show ' + self.PROJECT_NAME + opts) + self.assertEqual("11\n10\n11\n", raw_output) + + def test_quota_show(self): + raw_output = self.openstack('quota show ' + self.PROJECT_NAME) + for expected_field in self.EXPECTED_FIELDS: + self.assertInOutput(expected_field, raw_output) From 08e045282c97c4bd022da3802eb1535c6b264120 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Fri, 12 Feb 2016 20:06:35 +0000 Subject: [PATCH 0607/3095] Updated from global requirements Change-Id: I1583731e73699b71aa7a03ae4541e481abd6f1ea --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 6e9437233b..2255cd3b3b 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -6,7 +6,7 @@ hacking<0.11,>=0.10.0 coverage>=3.6 # Apache-2.0 discover # BSD fixtures>=1.3.1 # Apache-2.0/BSD -mock>=1.2;python_version<'3.3' # BSD +mock>=1.2 # BSD oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 reno>=0.1.1 # Apache2 From 6109dfcf63a666330e7323d957a37a251dd2b520 Mon Sep 17 00:00:00 2001 From: Jude Job Date: Tue, 2 Feb 2016 09:35:38 +0530 Subject: [PATCH 0608/3095] Floating IP: Neutron support for "ip floating delete" command This patch implements "ip floating delete" command for both compute and network. Also includes unit tests. Change-Id: Ie61f0faad65ec90f9d9956ae463412be8d963d05 partial-Bug: 1519502 Related-to: blueprint neutron-client Co-Authored-By: Tang Chen --- doc/source/command-objects/ip-floating.rst | 13 +-- openstackclient/compute/v2/floatingip.py | 23 ---- openstackclient/network/v2/floating_ip.py | 40 +++++++ openstackclient/tests/compute/v2/fakes.py | 3 + openstackclient/tests/network/v2/fakes.py | 77 ++++++++++++- .../tests/network/v2/test_floating_ip.py | 105 ++++++++++++++++++ setup.cfg | 3 +- 7 files changed, 231 insertions(+), 33 deletions(-) create mode 100644 openstackclient/network/v2/floating_ip.py create mode 100644 openstackclient/tests/network/v2/test_floating_ip.py diff --git a/doc/source/command-objects/ip-floating.rst b/doc/source/command-objects/ip-floating.rst index 6bfd7f4498..efdfb45313 100644 --- a/doc/source/command-objects/ip-floating.rst +++ b/doc/source/command-objects/ip-floating.rst @@ -2,7 +2,7 @@ ip floating =========== -Compute v2 +Compute v2, Network v2 ip floating add --------------- @@ -42,17 +42,16 @@ Create new floating IP address ip floating delete ------------------ -Delete a floating IP address +Delete floating IP .. program:: ip floating delete -.. code:: bash + .. code:: bash - os ip floating delete - + os ip floating delete -.. describe:: +.. describe:: - IP address to delete (ID only) + Floating IP to delete (IP address or ID) ip floating list ---------------- diff --git a/openstackclient/compute/v2/floatingip.py b/openstackclient/compute/v2/floatingip.py index 29ecbc9057..e4280de745 100644 --- a/openstackclient/compute/v2/floatingip.py +++ b/openstackclient/compute/v2/floatingip.py @@ -68,29 +68,6 @@ def take_action(self, parsed_args): return zip(*sorted(six.iteritems(info))) -class DeleteFloatingIP(command.Command): - """Delete a floating IP address""" - - def get_parser(self, prog_name): - parser = super(DeleteFloatingIP, self).get_parser(prog_name) - parser.add_argument( - "ip_address", - metavar="", - help="IP address to delete (ID only)", - ) - return parser - - def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - - floating_ip = utils.find_resource( - compute_client.floating_ips, - parsed_args.ip_address, - ) - - compute_client.floating_ips.delete(floating_ip) - - class ListFloatingIP(command.Lister): """List floating IP addresses""" diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py new file mode 100644 index 0000000000..91a93380a9 --- /dev/null +++ b/openstackclient/network/v2/floating_ip.py @@ -0,0 +1,40 @@ +# 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. +# + +"""IP Floating action implementations""" + +from openstackclient.common import utils +from openstackclient.network import common + + +class DeleteFloatingIP(common.NetworkAndComputeCommand): + """Delete floating IP""" + + def update_parser_common(self, parser): + parser.add_argument( + 'floating_ip', + metavar="", + help=("Floating IP to delete (IP address or ID)") + ) + return parser + + def take_action_network(self, client, parsed_args): + obj = client.find_ip(parsed_args.floating_ip) + client.delete_ip(obj) + + def take_action_compute(self, client, parsed_args): + obj = utils.find_resource( + client.floating_ips, + parsed_args.floating_ip, + ) + client.floating_ips.delete(obj.id) diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 68a66740ca..2e4cc1c56b 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -126,6 +126,9 @@ def __init__(self, **kwargs): self.security_group_rules = mock.Mock() self.security_group_rules.resource_class = fakes.FakeResource(None, {}) + self.floating_ips = mock.Mock() + self.floating_ips.resource_class = fakes.FakeResource(None, {}) + self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 516995eb02..7dee41e00e 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -226,7 +226,6 @@ def create_one_port(attrs={}, methods={}): :return: A FakeResource object, with id, name, etc. """ - # Set default attributes. port_attrs = { 'admin_state_up': True, @@ -553,3 +552,79 @@ def create_subnets(attrs={}, methods={}, count=2): subnets.append(FakeSubnet.create_one_subnet(attrs, methods)) return subnets + + +class FakeFloatingIP(object): + """Fake one or more floating ip.""" + + @staticmethod + def create_one_floating_ip(attrs={}, methods={}): + """Create a fake floating ip. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :return: + A FakeResource object, with id, ip + """ + # Set default attributes. + floating_ip_attrs = { + 'id': 'floating-ip-id-' + uuid.uuid4().hex, + 'ip': '1.0.9.0', + } + + # Overwrite default attributes. + floating_ip_attrs.update(attrs) + + # Set default methods. + floating_ip_methods = {} + + # Overwrite default methods. + floating_ip_methods.update(methods) + + floating_ip = fakes.FakeResource( + info=copy.deepcopy(floating_ip_attrs), + methods=copy.deepcopy(floating_ip_methods), + loaded=True) + return floating_ip + + @staticmethod + def create_floating_ips(attrs={}, methods={}, count=2): + """Create multiple fake floating ips. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :param int count: + The number of floating ips to fake + :return: + A list of FakeResource objects faking the floating ips + """ + floating_ips = [] + for i in range(0, count): + floating_ips.append(FakeFloatingIP.create_one_floating_ip( + attrs, + methods + )) + return floating_ips + + @staticmethod + def get_floating_ips(floating_ips=None, count=2): + """Get an iterable MagicMock object with a list of faked floating ips. + + If floating_ips list is provided, then initialize the Mock object + with the list. Otherwise create one. + + :param List floating ips: + A list of FakeResource objects faking floating ips + :param int count: + The number of floating ips to fake + :return: + An iterable Mock object with side_effect set to a list of faked + floating ips + """ + if floating_ips is None: + floating_ips = FakeFloatingIP.create_floating_ips(count) + return mock.MagicMock(side_effect=floating_ips) diff --git a/openstackclient/tests/network/v2/test_floating_ip.py b/openstackclient/tests/network/v2/test_floating_ip.py new file mode 100644 index 0000000000..49131f364c --- /dev/null +++ b/openstackclient/tests/network/v2/test_floating_ip.py @@ -0,0 +1,105 @@ +# 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 floating_ip +from openstackclient.tests.compute.v2 import fakes as compute_fakes +from openstackclient.tests.network.v2 import fakes as network_fakes + + +# Tests for Neutron network +# +class TestFloatingIPNetwork(network_fakes.TestNetworkV2): + + def setUp(self): + super(TestFloatingIPNetwork, self).setUp() + + # Get a shortcut to the network client + self.network = self.app.client_manager.network + + +class TestDeleteFloatingIPNetwork(TestFloatingIPNetwork): + + # The floating ip to be deleted. + floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip() + + def setUp(self): + super(TestDeleteFloatingIPNetwork, self).setUp() + + self.network.delete_ip = mock.Mock(return_value=self.floating_ip) + self.network.find_ip = mock.Mock(return_value=self.floating_ip) + + # Get the command object to test + self.cmd = floating_ip.DeleteFloatingIP(self.app, self.namespace) + + def test_floating_ip_delete(self): + arglist = [ + self.floating_ip.id, + ] + verifylist = [ + ('floating_ip', self.floating_ip.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.network.find_ip.assert_called_with(self.floating_ip.id) + self.network.delete_ip.assert_called_with(self.floating_ip) + self.assertIsNone(result) + + +# Tests for Nova network +# +class TestFloatingIPCompute(compute_fakes.TestComputev2): + + def setUp(self): + super(TestFloatingIPCompute, self).setUp() + + # Get a shortcut to the compute client + self.compute = self.app.client_manager.compute + + +class TestDeleteFloatingIPCompute(TestFloatingIPCompute): + + # The floating ip to be deleted. + floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip() + + def setUp(self): + super(TestDeleteFloatingIPCompute, self).setUp() + + self.app.client_manager.network_endpoint_enabled = False + + self.compute.floating_ips.delete.return_value = None + + # Return value of utils.find_resource() + self.compute.floating_ips.get.return_value = self.floating_ip + + # Get the command object to test + self.cmd = floating_ip.DeleteFloatingIP(self.app, None) + + def test_floating_ip_delete(self): + arglist = [ + self.floating_ip.id, + ] + verifylist = [ + ('floating_ip', self.floating_ip.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.compute.floating_ips.delete.assert_called_with( + self.floating_ip.id + ) + self.assertIsNone(result) diff --git a/setup.cfg b/setup.cfg index 4cf9622ceb..dabe779c3c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -91,10 +91,8 @@ openstack.compute.v2 = ip_floating_add = openstackclient.compute.v2.floatingip:AddFloatingIP ip_floating_create = openstackclient.compute.v2.floatingip:CreateFloatingIP - ip_floating_delete = openstackclient.compute.v2.floatingip:DeleteFloatingIP ip_floating_list = openstackclient.compute.v2.floatingip:ListFloatingIP ip_floating_remove = openstackclient.compute.v2.floatingip:RemoveFloatingIP - ip_floating_pool_list = openstackclient.compute.v2.floatingippool:ListFloatingIPPool keypair_create = openstackclient.compute.v2.keypair:CreateKeypair @@ -327,6 +325,7 @@ openstack.image.v2 = image_set = openstackclient.image.v2.image:SetImage openstack.network.v2 = + ip_floating_delete = openstackclient.network.v2.floating_ip:DeleteFloatingIP network_create = openstackclient.network.v2.network:CreateNetwork network_delete = openstackclient.network.v2.network:DeleteNetwork network_list = openstackclient.network.v2.network:ListNetwork From d8abec33ada8b2b028d52eb8bfad2640812b9af8 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 9 Feb 2016 14:23:28 +0800 Subject: [PATCH 0609/3095] Floating IP: Neutron support for "ip floating list" command Change-Id: I253f66f6bc64470e1a18ffea506048eb53f67d5c partial-Bug: 1519502 Related-to: blueprint neutron-client --- openstackclient/compute/v2/floatingip.py | 17 ----- openstackclient/network/v2/floating_ip.py | 26 +++++++ openstackclient/tests/network/v2/fakes.py | 5 +- .../tests/network/v2/test_floating_ip.py | 76 +++++++++++++++++++ setup.cfg | 2 +- 5 files changed, 107 insertions(+), 19 deletions(-) diff --git a/openstackclient/compute/v2/floatingip.py b/openstackclient/compute/v2/floatingip.py index e4280de745..6212989f3b 100644 --- a/openstackclient/compute/v2/floatingip.py +++ b/openstackclient/compute/v2/floatingip.py @@ -68,23 +68,6 @@ def take_action(self, parsed_args): return zip(*sorted(six.iteritems(info))) -class ListFloatingIP(command.Lister): - """List floating IP addresses""" - - def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - - columns = ('ID', 'Pool', 'IP', 'Fixed IP', 'Instance ID') - - data = compute_client.floating_ips.list() - - return (columns, - (utils.get_item_properties( - s, columns, - formatters={}, - ) for s in data)) - - class RemoveFloatingIP(command.Command): """Remove floating IP address from server""" diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index 91a93380a9..488950485b 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -38,3 +38,29 @@ def take_action_compute(self, client, parsed_args): parsed_args.floating_ip, ) client.floating_ips.delete(obj.id) + + +class ListFloatingIP(common.NetworkAndComputeLister): + """List floating IP(s)""" + + columns = ('ID', 'IP', 'Fixed IP', 'Instance ID', 'Pool') + column_headers = ('ID', 'Floating IP', 'Fixed IP', 'Server ID', 'Pool') + + def take_action_network(self, client, parsed_args): + query = {} + data = client.ips(**query) + + return (self.column_headers, + (utils.get_item_properties( + s, self.columns, + formatters={}, + ) for s in data)) + + def take_action_compute(self, client, parsed_args): + data = client.floating_ips.list() + + return (self.column_headers, + (utils.get_item_properties( + s, self.columns, + formatters={}, + ) for s in data)) diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 7dee41e00e..77720ee0ca 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -566,12 +566,15 @@ def create_one_floating_ip(attrs={}, methods={}): :param Dictionary methods: A dictionary with all methods :return: - A FakeResource object, with id, ip + A FakeResource object, with id, ip, and so on """ # Set default attributes. floating_ip_attrs = { 'id': 'floating-ip-id-' + uuid.uuid4().hex, 'ip': '1.0.9.0', + 'fixed_ip': '2.0.9.0', + 'instance_id': 'server-id-' + uuid.uuid4().hex, + 'pool': 'public', } # Overwrite default attributes. diff --git a/openstackclient/tests/network/v2/test_floating_ip.py b/openstackclient/tests/network/v2/test_floating_ip.py index 49131f364c..cfe3d11ddf 100644 --- a/openstackclient/tests/network/v2/test_floating_ip.py +++ b/openstackclient/tests/network/v2/test_floating_ip.py @@ -59,6 +59,43 @@ def test_floating_ip_delete(self): self.assertIsNone(result) +class TestListFloatingIPNetwork(TestFloatingIPNetwork): + + # The floating ips to list up + floating_ips = network_fakes.FakeFloatingIP.create_floating_ips(count=3) + + columns = ('ID', 'Floating IP', 'Fixed IP', 'Server ID', 'Pool') + + data = [] + for ip in floating_ips: + data.append(( + ip.id, + ip.ip, + ip.fixed_ip, + ip.instance_id, + ip.pool, + )) + + def setUp(self): + super(TestListFloatingIPNetwork, self).setUp() + + self.network.ips = mock.Mock(return_value=self.floating_ips) + + # Get the command object to test + self.cmd = floating_ip.ListFloatingIP(self.app, self.namespace) + + def test_floating_ip_list(self): + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.ips.assert_called_with(**{}) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + # Tests for Nova network # class TestFloatingIPCompute(compute_fakes.TestComputev2): @@ -103,3 +140,42 @@ def test_floating_ip_delete(self): self.floating_ip.id ) self.assertIsNone(result) + + +class TestListFloatingIPCompute(TestFloatingIPCompute): + + # The floating ips to be list up + floating_ips = network_fakes.FakeFloatingIP.create_floating_ips(count=3) + + columns = ('ID', 'Floating IP', 'Fixed IP', 'Server ID', 'Pool') + + data = [] + for ip in floating_ips: + data.append(( + ip.id, + ip.ip, + ip.fixed_ip, + ip.instance_id, + ip.pool, + )) + + def setUp(self): + super(TestListFloatingIPCompute, self).setUp() + + self.app.client_manager.network_endpoint_enabled = False + + self.compute.floating_ips.list.return_value = self.floating_ips + + # Get the command object to test + self.cmd = floating_ip.ListFloatingIP(self.app, None) + + def test_floating_ip_list(self): + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.compute.floating_ips.list.assert_called_with() + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) diff --git a/setup.cfg b/setup.cfg index dabe779c3c..e8100b5f8c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -91,7 +91,6 @@ openstack.compute.v2 = ip_floating_add = openstackclient.compute.v2.floatingip:AddFloatingIP ip_floating_create = openstackclient.compute.v2.floatingip:CreateFloatingIP - ip_floating_list = openstackclient.compute.v2.floatingip:ListFloatingIP ip_floating_remove = openstackclient.compute.v2.floatingip:RemoveFloatingIP ip_floating_pool_list = openstackclient.compute.v2.floatingippool:ListFloatingIPPool @@ -326,6 +325,7 @@ openstack.image.v2 = openstack.network.v2 = ip_floating_delete = openstackclient.network.v2.floating_ip:DeleteFloatingIP + ip_floating_list = openstackclient.network.v2.floating_ip:ListFloatingIP network_create = openstackclient.network.v2.network:CreateNetwork network_delete = openstackclient.network.v2.network:DeleteNetwork network_list = openstackclient.network.v2.network:ListNetwork From 27a0da65e37679bc042373d0e04ce88333d38a3b Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Wed, 10 Feb 2016 01:41:45 +0800 Subject: [PATCH 0610/3095] Support "network delete" command in nova network "network delete" command is not implemented in nova network. This patch implements it. Change-Id: I5dac1eed6eb8e67298bb446418835a6ab85c859c Depends-On: I1b59264cd40aaf1062f4e8db233ccb7fd0e95f0e partial-Bug: 1543672 --- doc/source/commands.rst | 2 +- openstackclient/network/v2/network.py | 17 +++++-- openstackclient/tests/compute/v2/fakes.py | 4 ++ .../tests/network/v2/test_network.py | 47 +++++++++++++++++++ 4 files changed, 64 insertions(+), 6 deletions(-) diff --git a/doc/source/commands.rst b/doc/source/commands.rst index bf5d7035a9..6b862f816f 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -98,7 +98,7 @@ referring to both Compute and Volume quotas. * ``limits``: (**Compute**, **Volume**) resource usage limits * ``mapping``: (**Identity**) a definition to translate identity provider attributes to Identity concepts * ``module``: internal - installed Python modules in the OSC process -* ``network``: (**Network**) - a virtual network for connecting servers and other resources +* ``network``: (**Compute**, **Network**) - a virtual network for connecting servers and other resources * ``object``: (**Object Storage**) a single file in the Object Storage * ``policy``: (**Identity**) determines authorization * ``port``: (**Network**) - a virtual port for connecting servers and other resources to a network diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 6123721963..ae80b48a15 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -17,6 +17,7 @@ from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.identity import common as identity_common +from openstackclient.network import common def _format_admin_state(item): @@ -141,11 +142,10 @@ def take_action(self, parsed_args): return (columns, data) -class DeleteNetwork(command.Command): +class DeleteNetwork(common.NetworkAndComputeCommand): """Delete network(s)""" - def get_parser(self, prog_name): - parser = super(DeleteNetwork, self).get_parser(prog_name) + def update_parser_common(self, parser): parser.add_argument( 'network', metavar="", @@ -154,12 +154,19 @@ def get_parser(self, prog_name): ) return parser - def take_action(self, parsed_args): - client = self.app.client_manager.network + def take_action_network(self, client, parsed_args): for network in parsed_args.network: obj = client.find_network(network) client.delete_network(obj) + def take_action_compute(self, client, parsed_args): + for network in parsed_args.network: + network = utils.find_resource( + client.networks, + network, + ) + client.networks.delete(network.id) + class ListNetwork(command.Lister): """List networks""" diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 2e4cc1c56b..c5e8f412c2 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -90,6 +90,7 @@ class FakeComputev2Client(object): def __init__(self, **kwargs): self.aggregates = mock.Mock() self.aggregates.resource_class = fakes.FakeResource(None, {}) + self.availability_zones = mock.Mock() self.availability_zones.resource_class = fakes.FakeResource(None, {}) @@ -129,6 +130,9 @@ def __init__(self, **kwargs): self.floating_ips = mock.Mock() self.floating_ips.resource_class = fakes.FakeResource(None, {}) + self.networks = mock.Mock() + self.networks.resource_class = fakes.FakeResource(None, {}) + self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index d6c6fbca2f..784a936653 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -17,6 +17,7 @@ from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.network.v2 import network +from openstackclient.tests.compute.v2 import fakes as compute_fakes from openstackclient.tests import fakes from openstackclient.tests.identity.v2_0 import fakes as identity_fakes_v2 from openstackclient.tests.identity.v3 import fakes as identity_fakes_v3 @@ -24,6 +25,8 @@ from openstackclient.tests import utils as tests_utils +# Tests for Neutron network +# class TestNetwork(network_fakes.TestNetworkV2): def setUp(self): @@ -564,3 +567,47 @@ def test_show_all_options(self): self.assertEqual(tuple(self.columns), columns) self.assertEqual(list(self.data), list(data)) + + +# Tests for Nova network +# +class TestNetworkCompute(compute_fakes.TestComputev2): + + def setUp(self): + super(TestNetworkCompute, self).setUp() + + # Get a shortcut to the compute client + self.compute = self.app.client_manager.compute + + +class TestDeleteNetworkCompute(TestNetworkCompute): + + # The network to delete. + _network = network_fakes.FakeNetwork.create_one_network() + + def setUp(self): + super(TestDeleteNetworkCompute, self).setUp() + + self.app.client_manager.network_endpoint_enabled = False + + self.compute.networks.delete.return_value = None + + # Return value of utils.find_resource() + self.compute.networks.get.return_value = self._network + + # Get the command object to test + self.cmd = network.DeleteNetwork(self.app, None) + + def test_network_delete(self): + arglist = [ + self._network.name, + ] + verifylist = [ + ('network', [self._network.name]), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.compute.networks.delete.assert_called_with(self._network.id) + self.assertIsNone(result) From cfcb750a97af1ab82b425532437456c22dcebad9 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sun, 14 Feb 2016 18:43:19 +0800 Subject: [PATCH 0611/3095] Use assertRaises() to check if an exception is raised In some test cases, try/except is used to check if an exception has been raised. We should use assertRaises() instead. Change-Id: I15c8e757dcab77fd6f895feb018184e1eb7e617b --- .../tests/compute/v2/test_server.py | 9 ++++---- .../tests/network/v2/test_network.py | 16 +++++-------- .../tests/network/v2/test_router.py | 23 ++++++++----------- 3 files changed, 19 insertions(+), 29 deletions(-) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index a80eaf51e6..95188522fa 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -143,11 +143,10 @@ def test_server_create_no_options(self): verifylist = [ ('server_name', self.new_server.name), ] - try: - # Missing required args should bail here - self.check_parser(self.cmd, arglist, verifylist) - except utils.ParserException: - pass + + # Missing required args should bail here + self.assertRaises(utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) def test_server_create_minimal(self): arglist = [ diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index d6c6fbca2f..4b73e68027 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -103,11 +103,9 @@ def test_create_no_options(self): arglist = [] verifylist = [] - try: - # Missing required args should bail here - self.check_parser(self.cmd, arglist, verifylist) - except tests_utils.ParserException: - pass + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) def test_create_default_options(self): arglist = [ @@ -542,11 +540,9 @@ def test_show_no_options(self): arglist = [] verifylist = [] - try: - # Missing required args should bail here - self.check_parser(self.cmd, arglist, verifylist) - except tests_utils.ParserException: - pass + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) def test_show_all_options(self): arglist = [ diff --git a/openstackclient/tests/network/v2/test_router.py b/openstackclient/tests/network/v2/test_router.py index 69c548a0b7..05bb7857e2 100644 --- a/openstackclient/tests/network/v2/test_router.py +++ b/openstackclient/tests/network/v2/test_router.py @@ -63,10 +63,9 @@ def test_create_no_options(self): arglist = [] verifylist = [] - try: - self.check_parser(self.cmd, arglist, verifylist) - except tests_utils.ParserException: - pass + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) def test_create_default_options(self): arglist = [ @@ -303,11 +302,9 @@ def test_set_distributed_centralized(self): ('distributed', False), ] - try: - # Argument parse failing should bail here - self.check_parser(self.cmd, arglist, verifylist) - except tests_utils.ParserException: - pass + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) def test_set_nothing(self): arglist = [self._router.name, ] @@ -353,11 +350,9 @@ def test_show_no_options(self): arglist = [] verifylist = [] - try: - # Missing required args should bail here - self.check_parser(self.cmd, arglist, verifylist) - except tests_utils.ParserException: - pass + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) def test_show_all_options(self): arglist = [ From 324e026f579041466a48ec4d93e41f05ca8314d2 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sun, 14 Feb 2016 18:57:34 +0800 Subject: [PATCH 0612/3095] Rename parameter "identifier" to "network" in network commands In other commands, the name or ID of an object is just the name of the object. For example, name or ID of a server is "server", router is "router". So, do not use "identifier" in network commands. Also, the parameter in doc file network.rst is not "identifier", but "network". Change-Id: I1ec3beefbb878a207bca280b994ca176ef04ee2d --- openstackclient/network/v2/network.py | 8 ++++---- openstackclient/tests/network/v2/test_network.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 6123721963..3f8bde2c2b 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -238,7 +238,7 @@ class SetNetwork(command.Command): def get_parser(self, prog_name): parser = super(SetNetwork, self).get_parser(prog_name) parser.add_argument( - 'identifier', + 'network', metavar="", help=("Network to modify (name or ID)") ) @@ -279,7 +279,7 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): client = self.app.client_manager.network - obj = client.find_network(parsed_args.identifier, ignore_missing=False) + obj = client.find_network(parsed_args.network, ignore_missing=False) attrs = _get_attrs(self.app.client_manager, parsed_args) if attrs == {}: @@ -296,7 +296,7 @@ class ShowNetwork(command.ShowOne): def get_parser(self, prog_name): parser = super(ShowNetwork, self).get_parser(prog_name) parser.add_argument( - 'identifier', + 'network', metavar="", help=("Network to display (name or ID)") ) @@ -304,7 +304,7 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): client = self.app.client_manager.network - obj = client.find_network(parsed_args.identifier, ignore_missing=False) + obj = client.find_network(parsed_args.network, ignore_missing=False) columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) return (columns, data) diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index d6c6fbca2f..6fcff9fc2b 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -453,7 +453,7 @@ def test_set_this(self): '--share', ] verifylist = [ - ('identifier', self._network.name), + ('network', self._network.name), ('admin_state', True), ('name', 'noob'), ('shared', True), @@ -477,7 +477,7 @@ def test_set_that(self): '--no-share', ] verifylist = [ - ('identifier', self._network.name), + ('network', self._network.name), ('admin_state', False), ('shared', False), ] @@ -494,7 +494,7 @@ def test_set_that(self): def test_set_nothing(self): arglist = [self._network.name, ] - verifylist = [('identifier', self._network.name), ] + verifylist = [('network', self._network.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.assertRaises(exceptions.CommandError, self.cmd.take_action, @@ -553,7 +553,7 @@ def test_show_all_options(self): self._network.name, ] verifylist = [ - ('identifier', self._network.name), + ('network', self._network.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) From e5b8e08eb1322447940ced48b492fdc1e87399b2 Mon Sep 17 00:00:00 2001 From: Hideki Saito Date: Sun, 14 Feb 2016 14:06:49 +0900 Subject: [PATCH 0613/3095] Fix 'openstack --help' fails if clouds.yaml cannot be read 'openstack --help' can display the basic information, even if openstack command does not have permission to read clouds.yaml. Change-Id: I7d5255c5ce3bd60af77fc70f433ca78dc011a79f Closes-Bug: #1541047 --- openstackclient/shell.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index c84e2b1d40..137446efb6 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -234,12 +234,17 @@ def initialize_app(self, argv): # Do configuration file handling # Ignore the default value of interface. Only if it is set later # will it be used. - cc = cloud_config.OpenStackConfig( - override_defaults={ - 'interface': None, - 'auth_type': auth_type, + try: + cc = cloud_config.OpenStackConfig( + override_defaults={ + 'interface': None, + 'auth_type': auth_type, }, ) + except (IOError, OSError) as e: + self.log.critical("Could not read clouds.yaml configuration file") + self.print_help_if_requested() + raise e # TODO(thowe): Change cliff so the default value for debug # can be set to None. From 5f40e1ea451558b7db904661151a6d353e2c02f5 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 16 Feb 2016 13:34:13 +0800 Subject: [PATCH 0614/3095] Add release note for "ip floating delete/list" commands for neutron network Change-Id: Ic90abf106a06edf9af4fe4c8938ab92bfdd8bb42 partial-Bug: 1519502 Related-to: blueprint neutron-client --- releasenotes/notes/bug-1519502-f72236598d14d350.yaml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 releasenotes/notes/bug-1519502-f72236598d14d350.yaml diff --git a/releasenotes/notes/bug-1519502-f72236598d14d350.yaml b/releasenotes/notes/bug-1519502-f72236598d14d350.yaml new file mode 100644 index 0000000000..297f96def9 --- /dev/null +++ b/releasenotes/notes/bug-1519502-f72236598d14d350.yaml @@ -0,0 +1,6 @@ +--- +features: + - Command ``ip floating delete`` is now available for neutron network. + [Bug `1519502 `_] + - Command ``ip floating list`` is now available for neutron network. + [Bug `1519502 `_] From da3d65299bc168ca86bfb4055d08556715149e0f Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 16 Feb 2016 15:11:19 +0800 Subject: [PATCH 0615/3095] Define FakeFloatingIP class in tests/compute for nova network commands "ip floating list" command is not available for Neutron now because the implementation is incorrect. The FloatingIP objects returned from Nova and Neutron network are quite different. So they need different FakeFloatingIP class to do the tests. This patch copies class FakeFloatingIP in tests/network to tests/compute for Nova network tests. Will fix the problem in "ip floating list" command and change FakeFloatingIP in tests/network to fit Neutron network tests. Change-Id: Ia29d257868e0f1dc6cd7cfe3819875e5913f76ec Partial-Bug: 1519502 Partially implements: blueprint neutron-client --- openstackclient/tests/compute/v2/fakes.py | 79 +++++++++++++++++++ .../tests/network/v2/test_floating_ip.py | 4 +- 2 files changed, 81 insertions(+), 2 deletions(-) diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index c5e8f412c2..00f7374807 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -446,3 +446,82 @@ def create_availability_zones(attrs={}, methods={}, count=2): availability_zones.append(availability_zone) return availability_zones + + +class FakeFloatingIP(object): + """Fake one or more floating ip.""" + + @staticmethod + def create_one_floating_ip(attrs={}, methods={}): + """Create a fake floating ip. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :return: + A FakeResource object, with id, ip, and so on + """ + # Set default attributes. + floating_ip_attrs = { + 'id': 'floating-ip-id-' + uuid.uuid4().hex, + 'ip': '1.0.9.0', + 'fixed_ip': '2.0.9.0', + 'instance_id': 'server-id-' + uuid.uuid4().hex, + 'pool': 'public', + } + + # Overwrite default attributes. + floating_ip_attrs.update(attrs) + + # Set default methods. + floating_ip_methods = {} + + # Overwrite default methods. + floating_ip_methods.update(methods) + + floating_ip = fakes.FakeResource( + info=copy.deepcopy(floating_ip_attrs), + methods=copy.deepcopy(floating_ip_methods), + loaded=True) + return floating_ip + + @staticmethod + def create_floating_ips(attrs={}, methods={}, count=2): + """Create multiple fake floating ips. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :param int count: + The number of floating ips to fake + :return: + A list of FakeResource objects faking the floating ips + """ + floating_ips = [] + for i in range(0, count): + floating_ips.append(FakeFloatingIP.create_one_floating_ip( + attrs, + methods + )) + return floating_ips + + @staticmethod + def get_floating_ips(floating_ips=None, count=2): + """Get an iterable MagicMock object with a list of faked floating ips. + + If floating_ips list is provided, then initialize the Mock object + with the list. Otherwise create one. + + :param List floating ips: + A list of FakeResource objects faking floating ips + :param int count: + The number of floating ips to fake + :return: + An iterable Mock object with side_effect set to a list of faked + floating ips + """ + if floating_ips is None: + floating_ips = FakeFloatingIP.create_floating_ips(count) + return mock.MagicMock(side_effect=floating_ips) diff --git a/openstackclient/tests/network/v2/test_floating_ip.py b/openstackclient/tests/network/v2/test_floating_ip.py index cfe3d11ddf..031dcdac37 100644 --- a/openstackclient/tests/network/v2/test_floating_ip.py +++ b/openstackclient/tests/network/v2/test_floating_ip.py @@ -110,7 +110,7 @@ def setUp(self): class TestDeleteFloatingIPCompute(TestFloatingIPCompute): # The floating ip to be deleted. - floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip() + floating_ip = compute_fakes.FakeFloatingIP.create_one_floating_ip() def setUp(self): super(TestDeleteFloatingIPCompute, self).setUp() @@ -145,7 +145,7 @@ def test_floating_ip_delete(self): class TestListFloatingIPCompute(TestFloatingIPCompute): # The floating ips to be list up - floating_ips = network_fakes.FakeFloatingIP.create_floating_ips(count=3) + floating_ips = compute_fakes.FakeFloatingIP.create_floating_ips(count=3) columns = ('ID', 'Floating IP', 'Fixed IP', 'Server ID', 'Pool') From 0a3ba91d53524cd0aa3a411f1f964594c8445cd3 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 16 Feb 2016 11:32:00 +0800 Subject: [PATCH 0616/3095] Add release note for "network delete" command for nova network Change-Id: Idb8a24465e447e90315c0f614ad91bd7eabd6878 partial-Bug: 1543672 --- releasenotes/notes/bug-1543672-bad2fc4c6c8f3125.yaml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 releasenotes/notes/bug-1543672-bad2fc4c6c8f3125.yaml diff --git a/releasenotes/notes/bug-1543672-bad2fc4c6c8f3125.yaml b/releasenotes/notes/bug-1543672-bad2fc4c6c8f3125.yaml new file mode 100644 index 0000000000..e8f75dd860 --- /dev/null +++ b/releasenotes/notes/bug-1543672-bad2fc4c6c8f3125.yaml @@ -0,0 +1,4 @@ +--- +features: + - Command ``network delete`` is now available for nova network. + [Bug `1543672 `_] From ddc97c6dc5bc36d678515aeb9f7b3f9e85bd70d0 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Wed, 10 Feb 2016 11:49:15 +0800 Subject: [PATCH 0617/3095] Support "network list" command in nova network "network list" command is not implemented in nova network. This patch implements it. The Network object in novaclient is quite different from the one in sdk. And the output of "network list" using Nova network is also quite different from using Neutron. It is like this: # openstack network list +--------------------------------------+---------+-------------+ | ID | Name | Subnet | +--------------------------------------+---------+-------------+ | 96a98ec4-31f6-45f6-99e6-9384569b3bb5 | private | 10.0.0.0/24 | +--------------------------------------+---------+-------------+ --long and --external options have not been implemented because the attrs in Network object in novaclient is too much different. This patch also introduces a new FakeNetwork class in compute/v2/fake.py to fake nova network. Change-Id: Id1fdf81fb2fa8b39f2c76b7bae37ac4fecafd0f7 Depends-On: I1b59264cd40aaf1062f4e8db233ccb7fd0e95f0e partial-Bug: 1543672 --- doc/source/command-objects/network.rst | 2 +- openstackclient/network/v2/network.py | 31 ++++++++-- openstackclient/tests/compute/v2/fakes.py | 58 +++++++++++++++++++ .../tests/network/v2/test_network.py | 53 ++++++++++++++++- .../notes/bug-1543672-bad2fc4c6c8f3125.yaml | 2 + 5 files changed, 136 insertions(+), 10 deletions(-) diff --git a/doc/source/command-objects/network.rst b/doc/source/command-objects/network.rst index bb36667253..0ef8f56d28 100644 --- a/doc/source/command-objects/network.rst +++ b/doc/source/command-objects/network.rst @@ -2,7 +2,7 @@ network ======= -Network v2 +Compute v2, Network v2 network create -------------- diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 636c333e7a..ed25117317 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -168,11 +168,10 @@ def take_action_compute(self, client, parsed_args): client.networks.delete(network.id) -class ListNetwork(command.Lister): +class ListNetwork(common.NetworkAndComputeLister): """List networks""" - def get_parser(self, prog_name): - parser = super(ListNetwork, self).get_parser(prog_name) + def update_parser_common(self, parser): parser.add_argument( '--external', action='store_true', @@ -187,9 +186,7 @@ def get_parser(self, prog_name): ) return parser - def take_action(self, parsed_args): - client = self.app.client_manager.network - + def take_action_network(self, client, parsed_args): if parsed_args.long: columns = ( 'id', @@ -231,7 +228,29 @@ def take_action(self, parsed_args): args = {'router:external': True} else: args = {} + data = client.networks(**args) + + return (column_headers, + (utils.get_item_properties( + s, columns, + formatters=_formatters, + ) for s in data)) + + def take_action_compute(self, client, parsed_args): + columns = ( + 'id', + 'label', + 'cidr', + ) + column_headers = ( + 'ID', + 'Name', + 'Subnet', + ) + + data = client.networks.list() + return (column_headers, (utils.get_item_properties( s, columns, diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 00f7374807..66e488b79b 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -525,3 +525,61 @@ def get_floating_ips(floating_ips=None, count=2): if floating_ips is None: floating_ips = FakeFloatingIP.create_floating_ips(count) return mock.MagicMock(side_effect=floating_ips) + + +class FakeNetwork(object): + """Fake one or more networks.""" + + @staticmethod + def create_one_network(attrs={}, methods={}): + """Create a fake network. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :return: + A FakeResource object, with id, label, cidr + """ + # Set default attributes. + network_attrs = { + 'id': 'network-id-' + uuid.uuid4().hex, + 'label': 'network-label-' + uuid.uuid4().hex, + 'cidr': '10.0.0.0/24', + } + + # Overwrite default attributes. + network_attrs.update(attrs) + + # Set default methods. + network_methods = { + 'keys': ['id', 'label', 'cidr'], + } + + # Overwrite default methods. + network_methods.update(methods) + + network = fakes.FakeResource(info=copy.deepcopy(network_attrs), + methods=copy.deepcopy(network_methods), + loaded=True) + + return network + + @staticmethod + def create_networks(attrs={}, methods={}, count=2): + """Create multiple fake networks. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :param int count: + The number of networks to fake + :return: + A list of FakeResource objects faking the networks + """ + networks = [] + for i in range(0, count): + networks.append(FakeNetwork.create_one_network(attrs, methods)) + + return networks diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index f7721951e7..c83e7e8447 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -579,7 +579,7 @@ def setUp(self): class TestDeleteNetworkCompute(TestNetworkCompute): # The network to delete. - _network = network_fakes.FakeNetwork.create_one_network() + _network = compute_fakes.FakeNetwork.create_one_network() def setUp(self): super(TestDeleteNetworkCompute, self).setUp() @@ -596,10 +596,10 @@ def setUp(self): def test_network_delete(self): arglist = [ - self._network.name, + self._network.label, ] verifylist = [ - ('network', [self._network.name]), + ('network', [self._network.label]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -607,3 +607,50 @@ def test_network_delete(self): self.compute.networks.delete.assert_called_with(self._network.id) self.assertIsNone(result) + + +class TestListNetworkCompute(TestNetworkCompute): + + # The networks going to be listed up. + _networks = compute_fakes.FakeNetwork.create_networks(count=3) + + columns = ( + 'ID', + 'Name', + 'Subnet', + ) + + data = [] + for net in _networks: + data.append(( + net.id, + net.label, + net.cidr, + )) + + def setUp(self): + super(TestListNetworkCompute, self).setUp() + + self.app.client_manager.network_endpoint_enabled = False + + self.compute.networks.list.return_value = self._networks + + # Get the command object to test + self.cmd = network.ListNetwork(self.app, None) + + def test_network_list_no_options(self): + arglist = [] + verifylist = [ + ('external', False), + ('long', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + self.compute.networks.list.assert_called_with() + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) diff --git a/releasenotes/notes/bug-1543672-bad2fc4c6c8f3125.yaml b/releasenotes/notes/bug-1543672-bad2fc4c6c8f3125.yaml index e8f75dd860..9cecc06039 100644 --- a/releasenotes/notes/bug-1543672-bad2fc4c6c8f3125.yaml +++ b/releasenotes/notes/bug-1543672-bad2fc4c6c8f3125.yaml @@ -2,3 +2,5 @@ features: - Command ``network delete`` is now available for nova network. [Bug `1543672 `_] + - Command ``network list`` is now available for nova network. + [Bug `1543672 `_] From d9d1809907256497289ae921bca9a7505a95f6ce Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Thu, 11 Feb 2016 19:09:43 +0800 Subject: [PATCH 0618/3095] Support "network show" command in nova network "network show" command is not implemented in nova network. This patch implements it. Change-Id: I1fadd890fe36c4e3ac5c9ed389b20c5b2fff8aca partial-Bug: 1543672 --- openstackclient/network/v2/network.py | 14 ++- openstackclient/tests/compute/v2/fakes.py | 33 +++++- .../tests/network/v2/test_network.py | 109 ++++++++++++++++++ .../notes/bug-1543672-bad2fc4c6c8f3125.yaml | 2 + 4 files changed, 153 insertions(+), 5 deletions(-) diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index ed25117317..a634378f63 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -316,7 +316,7 @@ def take_action(self, parsed_args): return -class ShowNetwork(command.ShowOne): +class ShowNetwork(common.NetworkAndComputeShowOne): """Show network details""" def get_parser(self, prog_name): @@ -328,9 +328,17 @@ def get_parser(self, prog_name): ) return parser - def take_action(self, parsed_args): - client = self.app.client_manager.network + def take_action_network(self, client, parsed_args): obj = client.find_network(parsed_args.network, ignore_missing=False) columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) return (columns, data) + + def take_action_compute(self, client, parsed_args): + network = utils.find_resource( + client.networks, + parsed_args.network, + ) + columns = sorted(network._info.keys()) + data = utils.get_dict_properties(network._info, columns) + return (columns, data) diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 66e488b79b..a4d99ca228 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -539,13 +539,42 @@ def create_one_network(attrs={}, methods={}): :param Dictionary methods: A dictionary with all methods :return: - A FakeResource object, with id, label, cidr + A FakeResource object, with id, label, cidr and so on """ # Set default attributes. network_attrs = { + 'bridge': 'br100', + 'bridge_interface': None, + 'broadcast': '10.0.0.255', + 'cidr': '10.0.0.0/24', + 'cidr_v6': None, + 'created_at': '2016-02-11T11:17:37.000000', + 'deleted': False, + 'deleted_at': None, + 'dhcp_server': '10.0.0.1', + 'dhcp_start': '10.0.0.2', + 'dns1': '8.8.4.4', + 'dns2': None, + 'enable_dhcp': True, + 'gateway': '10.0.0.1', + 'gateway_v6': None, + 'host': None, 'id': 'network-id-' + uuid.uuid4().hex, + 'injected': False, 'label': 'network-label-' + uuid.uuid4().hex, - 'cidr': '10.0.0.0/24', + 'mtu': None, + 'multi_host': False, + 'netmask': '255.255.255.0', + 'netmask_v6': None, + 'priority': None, + 'project_id': 'project-id-' + uuid.uuid4().hex, + 'rxtx_base': None, + 'share_address': False, + 'updated_at': None, + 'vlan': None, + 'vpn_private_address': None, + 'vpn_public_address': None, + 'vpn_public_port': None, } # Overwrite default attributes. diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index c83e7e8447..26a9da40d5 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -654,3 +654,112 @@ def test_network_list_no_options(self): self.compute.networks.list.assert_called_with() self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + + +class TestShowNetworkCompute(TestNetworkCompute): + + # The network to show. + _network = compute_fakes.FakeNetwork.create_one_network() + + columns = ( + 'bridge', + 'bridge_interface', + 'broadcast', + 'cidr', + 'cidr_v6', + 'created_at', + 'deleted', + 'deleted_at', + 'dhcp_server', + 'dhcp_start', + 'dns1', + 'dns2', + 'enable_dhcp', + 'gateway', + 'gateway_v6', + 'host', + 'id', + 'injected', + 'label', + 'mtu', + 'multi_host', + 'netmask', + 'netmask_v6', + 'priority', + 'project_id', + 'rxtx_base', + 'share_address', + 'updated_at', + 'vlan', + 'vpn_private_address', + 'vpn_public_address', + 'vpn_public_port', + ) + + data = ( + _network.bridge, + _network.bridge_interface, + _network.broadcast, + _network.cidr, + _network.cidr_v6, + _network.created_at, + _network.deleted, + _network.deleted_at, + _network.dhcp_server, + _network.dhcp_start, + _network.dns1, + _network.dns2, + _network.enable_dhcp, + _network.gateway, + _network.gateway_v6, + _network.host, + _network.id, + _network.injected, + _network.label, + _network.mtu, + _network.multi_host, + _network.netmask, + _network.netmask_v6, + _network.priority, + _network.project_id, + _network.rxtx_base, + _network.share_address, + _network.updated_at, + _network.vlan, + _network.vpn_private_address, + _network.vpn_public_address, + _network.vpn_public_port, + ) + + def setUp(self): + super(TestShowNetworkCompute, self).setUp() + + self.app.client_manager.network_endpoint_enabled = False + + # Return value of utils.find_resource() + self.compute.networks.get.return_value = self._network + + # Get the command object to test + self.cmd = network.ShowNetwork(self.app, None) + + def test_show_no_options(self): + arglist = [] + verifylist = [] + + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_show_all_options(self): + arglist = [ + self._network.label, + ] + verifylist = [ + ('network', self._network.label), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual(self.columns, tuple(columns)) + self.assertEqual(self.data, data) diff --git a/releasenotes/notes/bug-1543672-bad2fc4c6c8f3125.yaml b/releasenotes/notes/bug-1543672-bad2fc4c6c8f3125.yaml index 9cecc06039..ad4d5f5685 100644 --- a/releasenotes/notes/bug-1543672-bad2fc4c6c8f3125.yaml +++ b/releasenotes/notes/bug-1543672-bad2fc4c6c8f3125.yaml @@ -4,3 +4,5 @@ features: [Bug `1543672 `_] - Command ``network list`` is now available for nova network. [Bug `1543672 `_] + - Command ``network show`` is now available for nova network. + [Bug `1543672 `_] From 79fd6d3f2075ecdfdac8c856be135b3fd1260eb5 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Fri, 12 Feb 2016 13:31:15 +0800 Subject: [PATCH 0619/3095] Subnet Pool: Add "subnet pool delete" command Change-Id: Ic5ba5effcaea2410421a81da8ffce7c0295179e7 Closes-Bug: 1544587 Partially implements: blueprint neutron-client --- doc/source/command-objects/subnet_pool.rst | 21 +++++++ doc/source/commands.rst | 1 + openstackclient/network/v2/subnet_pool.py | 34 +++++++++++ openstackclient/tests/network/v2/fakes.py | 61 +++++++++++++++++++ .../tests/network/v2/test_subnet_pool.py | 57 +++++++++++++++++ .../notes/bug-1544587-ec3ca453c1340b4e.yaml | 4 ++ setup.cfg | 1 + 7 files changed, 179 insertions(+) create mode 100644 doc/source/command-objects/subnet_pool.rst create mode 100644 openstackclient/network/v2/subnet_pool.py create mode 100644 openstackclient/tests/network/v2/test_subnet_pool.py create mode 100644 releasenotes/notes/bug-1544587-ec3ca453c1340b4e.yaml diff --git a/doc/source/command-objects/subnet_pool.rst b/doc/source/command-objects/subnet_pool.rst new file mode 100644 index 0000000000..375fae81e1 --- /dev/null +++ b/doc/source/command-objects/subnet_pool.rst @@ -0,0 +1,21 @@ +=========== +subnet pool +=========== + +Network v2 + +subnet pool delete +------------------ + +Delete subnet pool + +.. program:: subnet pool delete +.. code:: bash + + os subnet pool delete + + +.. _subnet_pool_delete-subnet-pool: +.. describe:: + + Subnet pool to delete (name or ID) diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 6b862f816f..3598c284d2 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -118,6 +118,7 @@ referring to both Compute and Volume quotas. * ``service provider``: (**Identity**) a resource that consumes assertions from an ``identity provider`` * ``snapshot``: (**Volume**) a point-in-time copy of a volume * ``subnet``: (**Network**) - a contiguous range of IP addresses assigned to a network +* ``subnet pool``: (**Network**) - a pool of subnets * ``token``: (**Identity**) a bearer token managed by Identity service * ``usage``: (**Compute**) display host resources being consumed * ``user``: (**Identity**) individual cloud resources users diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py new file mode 100644 index 0000000000..133e3e7197 --- /dev/null +++ b/openstackclient/network/v2/subnet_pool.py @@ -0,0 +1,34 @@ +# 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. +# + +"""Subnet pool action implementations""" + +from openstackclient.common import command + + +class DeleteSubnetPool(command.Command): + """Delete subnet pool""" + + def get_parser(self, prog_name): + parser = super(DeleteSubnetPool, self).get_parser(prog_name) + parser.add_argument( + 'subnet_pool', + metavar="", + help=("Subnet pool to delete (name or ID)") + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.find_subnet_pool(parsed_args.subnet_pool) + client.delete_subnet_pool(obj) diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 77720ee0ca..aa04b1c06c 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -631,3 +631,64 @@ def get_floating_ips(floating_ips=None, count=2): if floating_ips is None: floating_ips = FakeFloatingIP.create_floating_ips(count) return mock.MagicMock(side_effect=floating_ips) + + +class FakeSubnetPool(object): + """Fake one or more subnet pools.""" + + @staticmethod + def create_one_subnet_pool(attrs={}, methods={}): + """Create a fake subnet pool. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :return: + A FakeResource object faking the subnet pool + """ + # Set default attributes. + subnet_pool_attrs = { + 'id': 'subnet-pool-id-' + uuid.uuid4().hex, + 'name': 'subnet-pool-name-' + uuid.uuid4().hex, + } + + # Overwrite default attributes. + subnet_pool_attrs.update(attrs) + + # Set default methods. + subnet_pool_methods = { + 'keys': ['id', 'name'] + } + + # Overwrite default methods. + subnet_pool_methods.update(methods) + + subnet_pool = fakes.FakeResource( + info=copy.deepcopy(subnet_pool_attrs), + methods=copy.deepcopy(subnet_pool_methods), + loaded=True + ) + + return subnet_pool + + @staticmethod + def create_subnet_pools(attrs={}, methods={}, count=2): + """Create multiple fake subnet pools. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :param int count: + The number of subnet pools to fake + :return: + A list of FakeResource objects faking the subnet pools + """ + subnet_pools = [] + for i in range(0, count): + subnet_pools.append( + FakeSubnetPool.create_one_subnet_pool(attrs, methods) + ) + + return subnet_pools diff --git a/openstackclient/tests/network/v2/test_subnet_pool.py b/openstackclient/tests/network/v2/test_subnet_pool.py new file mode 100644 index 0000000000..0cbfa1eeb8 --- /dev/null +++ b/openstackclient/tests/network/v2/test_subnet_pool.py @@ -0,0 +1,57 @@ +# 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 subnet_pool +from openstackclient.tests.network.v2 import fakes as network_fakes + + +class TestSubnetPool(network_fakes.TestNetworkV2): + def setUp(self): + super(TestSubnetPool, self).setUp() + + # Get a shortcut to the network client + self.network = self.app.client_manager.network + + +class TestDeleteSubnetPool(TestSubnetPool): + + # The subnet pool to delete. + _subnet_pool = network_fakes.FakeSubnetPool.create_one_subnet_pool() + + def setUp(self): + super(TestDeleteSubnetPool, self).setUp() + + self.network.delete_subnet_pool = mock.Mock(return_value=None) + + self.network.find_subnet_pool = mock.Mock( + return_value=self._subnet_pool + ) + + # Get the command object to test + self.cmd = subnet_pool.DeleteSubnetPool(self.app, self.namespace) + + def test_delete(self): + arglist = [ + self._subnet_pool.name, + ] + verifylist = [ + ('subnet_pool', self._subnet_pool.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.network.delete_subnet_pool.assert_called_with(self._subnet_pool) + self.assertIsNone(result) diff --git a/releasenotes/notes/bug-1544587-ec3ca453c1340b4e.yaml b/releasenotes/notes/bug-1544587-ec3ca453c1340b4e.yaml new file mode 100644 index 0000000000..61401c003e --- /dev/null +++ b/releasenotes/notes/bug-1544587-ec3ca453c1340b4e.yaml @@ -0,0 +1,4 @@ +--- +features: + - Add support for ``subnet pool delete`` command. + [Bug `1544587 `_] diff --git a/setup.cfg b/setup.cfg index ea522f9e45..8376f83e30 100644 --- a/setup.cfg +++ b/setup.cfg @@ -341,6 +341,7 @@ openstack.network.v2 = router_show = openstackclient.network.v2.router:ShowRouter security_group_delete = openstackclient.network.v2.security_group:DeleteSecurityGroup subnet_list = openstackclient.network.v2.subnet:ListSubnet + subnet_pool_delete = openstackclient.network.v2.subnet_pool:DeleteSubnetPool openstack.object_store.v1 = object_store_account_set = openstackclient.object.v1.account:SetAccount From 444fc6149db58361e5329e3f05eb8f056fb7479a Mon Sep 17 00:00:00 2001 From: "Chaozhe.Chen" Date: Thu, 18 Feb 2016 22:46:57 +0800 Subject: [PATCH 0620/3095] Remove unused test-requirments WebOb is not needed in our test code. So remove it to make less dependences. Change-Id: I4910263449ff3d49c4ee44a6ef7a7762875fe76f --- test-requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 2255cd3b3b..3f1707b671 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -15,7 +15,6 @@ sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 # BSD os-testr>=0.4.1 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD testtools>=1.4.0 # MIT -WebOb>=1.2.3 # MIT tempest-lib>=0.14.0 # Apache-2.0 # Install these to generate sphinx autodocs From a04012c3d50c3623c699f57d0dd320783b92e1cb Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 13 Feb 2016 09:49:46 +0800 Subject: [PATCH 0621/3095] Subnet Pool: Add "subnet pool list" command Change-Id: I7935be2488fb728ced9680d75880870e5d315655 Closes-Bug: 1544589 Implements: blueprint neutron-client --- doc/source/command-objects/subnet_pool.rst | 15 ++++ openstackclient/network/v2/subnet_pool.py | 51 ++++++++++++++ openstackclient/tests/network/v2/fakes.py | 6 +- .../tests/network/v2/test_subnet_pool.py | 69 +++++++++++++++++++ .../notes/bug-1544589-b9f669ef71aa5e57.yaml | 4 ++ setup.cfg | 1 + 6 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/bug-1544589-b9f669ef71aa5e57.yaml diff --git a/doc/source/command-objects/subnet_pool.rst b/doc/source/command-objects/subnet_pool.rst index 375fae81e1..4a2f9adc64 100644 --- a/doc/source/command-objects/subnet_pool.rst +++ b/doc/source/command-objects/subnet_pool.rst @@ -19,3 +19,18 @@ Delete subnet pool .. describe:: Subnet pool to delete (name or ID) + +subnet pool list +---------------- + +List subnet pools + +.. program:: subnet pool list +.. code:: bash + + os subnet pool list + [--long] + +.. option:: --long + + List additional fields in output diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index 133e3e7197..e82b2050f5 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -14,6 +14,7 @@ """Subnet pool action implementations""" from openstackclient.common import command +from openstackclient.common import utils class DeleteSubnetPool(command.Command): @@ -32,3 +33,53 @@ def take_action(self, parsed_args): client = self.app.client_manager.network obj = client.find_subnet_pool(parsed_args.subnet_pool) client.delete_subnet_pool(obj) + + +class ListSubnetPool(command.Lister): + """List subnet pools""" + + def get_parser(self, prog_name): + parser = super(ListSubnetPool, self).get_parser(prog_name) + parser.add_argument( + '--long', + action='store_true', + default=False, + help='List additional fields in output', + ) + return parser + + def take_action(self, parsed_args): + data = self.app.client_manager.network.subnet_pools() + + if parsed_args.long: + headers = ( + 'ID', + 'Name', + 'Prefixes', + 'Default Prefix Length', + 'Address Scope', + ) + columns = ( + 'id', + 'name', + 'prefixes', + 'default_prefixlen', + 'address_scope_id', + ) + else: + headers = ( + 'ID', + 'Name', + 'Prefixes', + ) + columns = ( + 'id', + 'name', + 'prefixes', + ) + + return (headers, + (utils.get_item_properties( + s, columns, + formatters={}, + ) for s in data)) diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index b48cde3ebb..dac3737b08 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -699,6 +699,9 @@ def create_one_subnet_pool(attrs={}, methods={}): subnet_pool_attrs = { 'id': 'subnet-pool-id-' + uuid.uuid4().hex, 'name': 'subnet-pool-name-' + uuid.uuid4().hex, + 'prefixes': ['10.0.0.0/24', '10.1.0.0/24'], + 'default_prefixlen': 8, + 'address_scope_id': 'address-scope-id-' + uuid.uuid4().hex, } # Overwrite default attributes. @@ -706,7 +709,8 @@ def create_one_subnet_pool(attrs={}, methods={}): # Set default methods. subnet_pool_methods = { - 'keys': ['id', 'name'] + 'keys': ['id', 'name', 'prefixes', 'default_prefixlen', + 'address_scope_id'] } # Overwrite default methods. diff --git a/openstackclient/tests/network/v2/test_subnet_pool.py b/openstackclient/tests/network/v2/test_subnet_pool.py index 0cbfa1eeb8..28be59378b 100644 --- a/openstackclient/tests/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/network/v2/test_subnet_pool.py @@ -55,3 +55,72 @@ def test_delete(self): self.network.delete_subnet_pool.assert_called_with(self._subnet_pool) self.assertIsNone(result) + + +class TestListSubnetPool(TestSubnetPool): + # The subnet pools going to be listed up. + _subnet_pools = network_fakes.FakeSubnetPool.create_subnet_pools(count=3) + + columns = ( + 'ID', + 'Name', + 'Prefixes', + ) + columns_long = columns + ( + 'Default Prefix Length', + 'Address Scope', + ) + + data = [] + for pool in _subnet_pools: + data.append(( + pool.id, + pool.name, + pool.prefixes, + )) + + data_long = [] + for pool in _subnet_pools: + data_long.append(( + pool.id, + pool.name, + pool.prefixes, + pool.default_prefixlen, + pool.address_scope_id, + )) + + def setUp(self): + super(TestListSubnetPool, self).setUp() + + # Get the command object to test + self.cmd = subnet_pool.ListSubnetPool(self.app, self.namespace) + + self.network.subnet_pools = mock.Mock(return_value=self._subnet_pools) + + def test_subnet_pool_list_no_option(self): + arglist = [] + verifylist = [ + ('long', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.subnet_pools.assert_called_with() + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_subnet_pool_list_long(self): + arglist = [ + '--long', + ] + verifylist = [ + ('long', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.subnet_pools.assert_called_with() + self.assertEqual(self.columns_long, columns) + self.assertEqual(self.data_long, list(data)) diff --git a/releasenotes/notes/bug-1544589-b9f669ef71aa5e57.yaml b/releasenotes/notes/bug-1544589-b9f669ef71aa5e57.yaml new file mode 100644 index 0000000000..eafa8f5ad7 --- /dev/null +++ b/releasenotes/notes/bug-1544589-b9f669ef71aa5e57.yaml @@ -0,0 +1,4 @@ +--- +features: + - Add support for ``subnet pool list`` command. + [Bug `1544589 `_] diff --git a/setup.cfg b/setup.cfg index c289e94b9e..4cc449a022 100644 --- a/setup.cfg +++ b/setup.cfg @@ -342,6 +342,7 @@ openstack.network.v2 = security_group_rule_delete = openstackclient.network.v2.security_group_rule:DeleteSecurityGroupRule subnet_list = openstackclient.network.v2.subnet:ListSubnet subnet_pool_delete = openstackclient.network.v2.subnet_pool:DeleteSubnetPool + subnet_pool_list = openstackclient.network.v2.subnet_pool:ListSubnetPool openstack.object_store.v1 = object_store_account_set = openstackclient.object.v1.account:SetAccount From 3c8bb165137ee1f17d99c270896b37fb9d00f07c Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 13 Feb 2016 10:13:14 +0800 Subject: [PATCH 0622/3095] Subnet Pool: Add "subnet pool show" command Change-Id: I8dda7bbf1e27b0ac773f62a5cd293387da96f8df Closes-Bug: 1544590 Implements: blueprint neutron-client --- doc/source/command-objects/subnet_pool.rst | 16 ++++ openstackclient/network/v2/subnet_pool.py | 36 +++++++++ openstackclient/tests/network/v2/fakes.py | 16 +++- .../tests/network/v2/test_subnet_pool.py | 75 +++++++++++++++++++ .../notes/bug-1544590-8cf42954e28c2f42.yaml | 4 + setup.cfg | 1 + 6 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/bug-1544590-8cf42954e28c2f42.yaml diff --git a/doc/source/command-objects/subnet_pool.rst b/doc/source/command-objects/subnet_pool.rst index 4a2f9adc64..7a8f79e42d 100644 --- a/doc/source/command-objects/subnet_pool.rst +++ b/doc/source/command-objects/subnet_pool.rst @@ -34,3 +34,19 @@ List subnet pools .. option:: --long List additional fields in output + +subnet pool show +---------------- + +Show subnet pool details + +.. program:: subnet pool show +.. code:: bash + + os subnet pool show + + +.. _subnet_pool_show-subnet-pool: +.. describe:: + + Subnet pool to show (name or ID) diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index e82b2050f5..1db1652f9c 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -17,6 +17,19 @@ from openstackclient.common import utils +def _get_columns(item): + columns = item.keys() + if 'tenant_id' in columns: + columns.remove('tenant_id') + columns.append('project_id') + return tuple(sorted(columns)) + + +_formatters = { + 'prefixes': utils.format_list, +} + + class DeleteSubnetPool(command.Command): """Delete subnet pool""" @@ -83,3 +96,26 @@ def take_action(self, parsed_args): s, columns, formatters={}, ) for s in data)) + + +class ShowSubnetPool(command.ShowOne): + """Show subnet pool details""" + + def get_parser(self, prog_name): + parser = super(ShowSubnetPool, self).get_parser(prog_name) + parser.add_argument( + 'subnet_pool', + metavar="", + help=("Subnet pool to show (name or ID)") + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.find_subnet_pool( + parsed_args.subnet_pool, + ignore_missing=False + ) + columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns, formatters=_formatters) + return (columns, data) diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index dac3737b08..7de0a69257 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -696,12 +696,24 @@ def create_one_subnet_pool(attrs={}, methods={}): A FakeResource object faking the subnet pool """ # Set default attributes. + project_id = 'project-id-' + uuid.uuid4().hex subnet_pool_attrs = { 'id': 'subnet-pool-id-' + uuid.uuid4().hex, 'name': 'subnet-pool-name-' + uuid.uuid4().hex, 'prefixes': ['10.0.0.0/24', '10.1.0.0/24'], 'default_prefixlen': 8, 'address_scope_id': 'address-scope-id-' + uuid.uuid4().hex, + 'tenant_id': project_id, + 'is_default': False, + 'shared': False, + 'max_prefixlen': 32, + 'min_prefixlen': 8, + 'default_quota': None, + 'ip_version': 4, + + # OpenStack SDK automatically translates project_id to tenant_id. + # So we need an additional attr to simulate this behavior. + 'project_id': project_id, } # Overwrite default attributes. @@ -710,7 +722,9 @@ def create_one_subnet_pool(attrs={}, methods={}): # Set default methods. subnet_pool_methods = { 'keys': ['id', 'name', 'prefixes', 'default_prefixlen', - 'address_scope_id'] + 'address_scope_id', 'tenant_id', 'is_default', + 'shared', 'max_prefixlen', 'min_prefixlen', + 'default_quota', 'ip_version'] } # Overwrite default methods. diff --git a/openstackclient/tests/network/v2/test_subnet_pool.py b/openstackclient/tests/network/v2/test_subnet_pool.py index 28be59378b..0ee5a7145e 100644 --- a/openstackclient/tests/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/network/v2/test_subnet_pool.py @@ -13,8 +13,10 @@ import mock +from openstackclient.common import utils from openstackclient.network.v2 import subnet_pool from openstackclient.tests.network.v2 import fakes as network_fakes +from openstackclient.tests import utils as tests_utils class TestSubnetPool(network_fakes.TestNetworkV2): @@ -124,3 +126,76 @@ def test_subnet_pool_list_long(self): self.network.subnet_pools.assert_called_with() self.assertEqual(self.columns_long, columns) self.assertEqual(self.data_long, list(data)) + + +class TestShowSubnetPool(TestSubnetPool): + + # The subnet_pool to set. + _subnet_pool = network_fakes.FakeSubnetPool.create_one_subnet_pool() + + columns = ( + 'address_scope_id', + 'default_prefixlen', + 'default_quota', + 'id', + 'ip_version', + 'is_default', + 'max_prefixlen', + 'min_prefixlen', + 'name', + 'prefixes', + 'project_id', + 'shared', + ) + + data = ( + _subnet_pool.address_scope_id, + _subnet_pool.default_prefixlen, + _subnet_pool.default_quota, + _subnet_pool.id, + _subnet_pool.ip_version, + _subnet_pool.is_default, + _subnet_pool.max_prefixlen, + _subnet_pool.min_prefixlen, + _subnet_pool.name, + utils.format_list(_subnet_pool.prefixes), + _subnet_pool.tenant_id, + _subnet_pool.shared, + ) + + def setUp(self): + super(TestShowSubnetPool, self).setUp() + + self.network.find_subnet_pool = mock.Mock( + return_value=self._subnet_pool + ) + + # Get the command object to test + self.cmd = subnet_pool.ShowSubnetPool(self.app, self.namespace) + + def test_show_no_options(self): + arglist = [] + verifylist = [] + + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_show_all_options(self): + arglist = [ + self._subnet_pool.name, + ] + verifylist = [ + ('subnet_pool', self._subnet_pool.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.find_subnet_pool.assert_called_with( + self._subnet_pool.name, + ignore_missing=False + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) diff --git a/releasenotes/notes/bug-1544590-8cf42954e28c2f42.yaml b/releasenotes/notes/bug-1544590-8cf42954e28c2f42.yaml new file mode 100644 index 0000000000..b6ee9a2ebc --- /dev/null +++ b/releasenotes/notes/bug-1544590-8cf42954e28c2f42.yaml @@ -0,0 +1,4 @@ +--- +features: + - Add support for ``subnet pool show`` command. + [Bug `1544590 `_] diff --git a/setup.cfg b/setup.cfg index 4cc449a022..a6e2702560 100644 --- a/setup.cfg +++ b/setup.cfg @@ -343,6 +343,7 @@ openstack.network.v2 = subnet_list = openstackclient.network.v2.subnet:ListSubnet subnet_pool_delete = openstackclient.network.v2.subnet_pool:DeleteSubnetPool subnet_pool_list = openstackclient.network.v2.subnet_pool:ListSubnetPool + subnet_pool_show = openstackclient.network.v2.subnet_pool:ShowSubnetPool openstack.object_store.v1 = object_store_account_set = openstackclient.object.v1.account:SetAccount From ca34aa1587212ce5ac456a988fd6b442e646ed16 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 16 Feb 2016 14:33:31 +0800 Subject: [PATCH 0623/3095] Floating IP: Fix "ip floating list" in neutron network The implementation of "ip floating list" in the commit below is incorrect: Change-Id: I253f66f6bc64470e1a18ffea506048eb53f67d5c This is because the FloatingIP objects returned from Nova and Neutron network are different. They need different handling. This patch fixes this problem. The output for Neutron network would be: +--------------------------------------+---------------------+------------------+------+ | ID | Floating IP Address | Fixed IP Address | Port | +--------------------------------------+---------------------+------------------+------+ | 1976df86-e66a-4f96-81bd-c6ffee6407f1 | 172.24.4.3 | None | None | +--------------------------------------+---------------------+------------------+------+ The output for Neutron network would be: +----+---------------------+------------------+-----------+--------+ | ID | Floating IP Address | Fixed IP Address | Server ID | Pool | +----+---------------------+------------------+-----------+--------+ | 1 | 172.24.4.1 | None | None | public | +----+---------------------+------------------+-----------+--------+ Change-Id: I1295e922df695414511d9a07ca4a8e2428040064 Partial-Bug: 1519502 Related-to: blueprint neutron-client --- openstackclient/network/v2/floating_ip.py | 39 +++++++++++++++---- openstackclient/tests/network/v2/fakes.py | 7 ++-- .../tests/network/v2/test_floating_ip.py | 22 ++++++++--- 3 files changed, 51 insertions(+), 17 deletions(-) diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index 488950485b..23b83201f6 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -43,24 +43,49 @@ def take_action_compute(self, client, parsed_args): class ListFloatingIP(common.NetworkAndComputeLister): """List floating IP(s)""" - columns = ('ID', 'IP', 'Fixed IP', 'Instance ID', 'Pool') - column_headers = ('ID', 'Floating IP', 'Fixed IP', 'Server ID', 'Pool') - def take_action_network(self, client, parsed_args): + columns = ( + 'id', + 'floating_ip_address', + 'fixed_ip_address', + 'port_id', + ) + headers = ( + 'ID', + 'Floating IP Address', + 'Fixed IP Address', + 'Port', + ) + query = {} data = client.ips(**query) - return (self.column_headers, + return (headers, (utils.get_item_properties( - s, self.columns, + s, columns, formatters={}, ) for s in data)) def take_action_compute(self, client, parsed_args): + columns = ( + 'ID', + 'IP', + 'Fixed IP', + 'Instance ID', + 'Pool', + ) + headers = ( + 'ID', + 'Floating IP Address', + 'Fixed IP Address', + 'Server', + 'Pool', + ) + data = client.floating_ips.list() - return (self.column_headers, + return (headers, (utils.get_item_properties( - s, self.columns, + s, columns, formatters={}, ) for s in data)) diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index b48cde3ebb..ae205a2d51 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -619,10 +619,9 @@ def create_one_floating_ip(attrs={}, methods={}): # Set default attributes. floating_ip_attrs = { 'id': 'floating-ip-id-' + uuid.uuid4().hex, - 'ip': '1.0.9.0', - 'fixed_ip': '2.0.9.0', - 'instance_id': 'server-id-' + uuid.uuid4().hex, - 'pool': 'public', + 'floating_ip_address': '1.0.9.0', + 'fixed_ip_address': '2.0.9.0', + 'port_id': 'port-id-' + uuid.uuid4().hex, } # Overwrite default attributes. diff --git a/openstackclient/tests/network/v2/test_floating_ip.py b/openstackclient/tests/network/v2/test_floating_ip.py index 031dcdac37..a29d691338 100644 --- a/openstackclient/tests/network/v2/test_floating_ip.py +++ b/openstackclient/tests/network/v2/test_floating_ip.py @@ -64,16 +64,20 @@ class TestListFloatingIPNetwork(TestFloatingIPNetwork): # The floating ips to list up floating_ips = network_fakes.FakeFloatingIP.create_floating_ips(count=3) - columns = ('ID', 'Floating IP', 'Fixed IP', 'Server ID', 'Pool') + columns = ( + 'ID', + 'Floating IP Address', + 'Fixed IP Address', + 'Port', + ) data = [] for ip in floating_ips: data.append(( ip.id, - ip.ip, - ip.fixed_ip, - ip.instance_id, - ip.pool, + ip.floating_ip_address, + ip.fixed_ip_address, + ip.port_id, )) def setUp(self): @@ -147,7 +151,13 @@ class TestListFloatingIPCompute(TestFloatingIPCompute): # The floating ips to be list up floating_ips = compute_fakes.FakeFloatingIP.create_floating_ips(count=3) - columns = ('ID', 'Floating IP', 'Fixed IP', 'Server ID', 'Pool') + columns = ( + 'ID', + 'Floating IP Address', + 'Fixed IP Address', + 'Server', + 'Pool', + ) data = [] for ip in floating_ips: From 48681af86a1dfd50a8e90a9d7cd9068417cc52a9 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Fri, 19 Feb 2016 13:44:54 +0800 Subject: [PATCH 0624/3095] Don't use Mock.called_once_with that does not exist Class mock.Mock does not exist method "called_once_with()", it just exists method "assert_called_once_with()". "called_once_with()" does nothing because it's a mock object. In OSC, only one place is still using "called_once_with()". Fix it. Change-Id: Ib890e95d775c3fc43df80fa05c82d726e78cdac8 Partial Bug: 1544522 --- openstackclient/tests/common/test_commandmanager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/tests/common/test_commandmanager.py b/openstackclient/tests/common/test_commandmanager.py index 056b637d2e..37dc90feb6 100644 --- a/openstackclient/tests/common/test_commandmanager.py +++ b/openstackclient/tests/common/test_commandmanager.py @@ -100,6 +100,6 @@ def test_get_command_names(self): mock_pkg_resources, ) as iter_entry_points: mgr = commandmanager.CommandManager('test') - assert iter_entry_points.called_once_with('test') + iter_entry_points.assert_called_once_with('test') cmds = mgr.get_command_names('test') self.assertEqual(['one', 'cmd two'], cmds) From ef64a8b47deb8c8c8a49dd3a3c11d675979c91e0 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Fri, 19 Feb 2016 17:14:08 +0800 Subject: [PATCH 0625/3095] Use assertIsNone() instead of assertEqual(None, xxx) Change-Id: Ibbd7d6d27b2ff20304e3121fbadd5d50c1836d9b --- openstackclient/tests/network/v2/test_security_group.py | 4 ++-- .../tests/network/v2/test_security_group_rule.py | 4 ++-- openstackclient/tests/object/v1/test_container.py | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/openstackclient/tests/network/v2/test_security_group.py b/openstackclient/tests/network/v2/test_security_group.py index 98388ec7cb..72da14009c 100644 --- a/openstackclient/tests/network/v2/test_security_group.py +++ b/openstackclient/tests/network/v2/test_security_group.py @@ -61,7 +61,7 @@ def test_security_group_delete(self): self.network.delete_security_group.assert_called_with( self._security_group) - self.assertEqual(None, result) + self.assertIsNone(result) class TestDeleteSecurityGroupCompute(TestSecurityGroup): @@ -96,4 +96,4 @@ def test_security_group_delete(self): self.compute.security_groups.delete.assert_called_with( self._security_group.id) - self.assertEqual(None, result) + self.assertIsNone(result) diff --git a/openstackclient/tests/network/v2/test_security_group_rule.py b/openstackclient/tests/network/v2/test_security_group_rule.py index e07066329c..c6ef388461 100644 --- a/openstackclient/tests/network/v2/test_security_group_rule.py +++ b/openstackclient/tests/network/v2/test_security_group_rule.py @@ -67,7 +67,7 @@ def test_security_group_rule_delete(self): self.network.delete_security_group_rule.assert_called_with( self._security_group_rule) - self.assertEqual(None, result) + self.assertIsNone(result) class TestDeleteSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): @@ -97,4 +97,4 @@ def test_security_group_rule_delete(self): self.compute.security_group_rules.delete.assert_called_with( self._security_group_rule.id) - self.assertEqual(None, result) + self.assertIsNone(result) diff --git a/openstackclient/tests/object/v1/test_container.py b/openstackclient/tests/object/v1/test_container.py index 5b0fb48a34..6982295de3 100644 --- a/openstackclient/tests/object/v1/test_container.py +++ b/openstackclient/tests/object/v1/test_container.py @@ -67,7 +67,7 @@ def test_container_delete(self, c_mock, o_list_mock, o_delete_mock): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.assertEqual(None, self.cmd.take_action(parsed_args)) + self.assertIsNone(self.cmd.take_action(parsed_args)) kwargs = {} c_mock.assert_called_with( @@ -92,7 +92,7 @@ def test_recursive_delete(self, c_mock, o_list_mock, o_delete_mock): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.assertEqual(None, self.cmd.take_action(parsed_args)) + self.assertIsNone(self.cmd.take_action(parsed_args)) kwargs = {} c_mock.assert_called_with( @@ -120,7 +120,7 @@ def test_r_delete(self, c_mock, o_list_mock, o_delete_mock): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.assertEqual(None, self.cmd.take_action(parsed_args)) + self.assertIsNone(self.cmd.take_action(parsed_args)) kwargs = {} c_mock.assert_called_with( From 41e1bd0be64e15a5e0c12b45bdf3dcde5fabf244 Mon Sep 17 00:00:00 2001 From: guang-yee Date: Mon, 8 Feb 2016 11:16:24 -0800 Subject: [PATCH 0626/3095] Support unscoped token request Make scope check optional for the "token issue" command as unscoped token is a valid Keystone V2/V3 API. Change-Id: Ie1cded4dbfdafd3a78c0ebdf89e3f66762509930 Closes-Bug: #1543214 --- openstackclient/api/auth.py | 11 +++++--- openstackclient/common/clientmanager.py | 22 ++++++++++++++-- openstackclient/identity/v2_0/token.py | 6 ++++- openstackclient/identity/v3/token.py | 3 +++ openstackclient/shell.py | 3 +++ .../tests/common/test_clientmanager.py | 25 +++++++++++++++++++ openstackclient/tests/identity/v2_0/fakes.py | 6 +++++ .../tests/identity/v2_0/test_token.py | 22 ++++++++++++++++ openstackclient/tests/identity/v3/fakes.py | 6 +++++ .../tests/identity/v3/test_token.py | 21 ++++++++++++++++ .../notes/bug-1543214-959aee7830db2b0d.yaml | 6 +++++ 11 files changed, 125 insertions(+), 6 deletions(-) create mode 100644 releasenotes/notes/bug-1543214-959aee7830db2b0d.yaml diff --git a/openstackclient/api/auth.py b/openstackclient/api/auth.py index 66272e4247..2cde7d4002 100644 --- a/openstackclient/api/auth.py +++ b/openstackclient/api/auth.py @@ -135,8 +135,12 @@ def build_auth_params(auth_plugin_name, cmd_options): return (auth_plugin_class, auth_params) -def check_valid_auth_options(options, auth_plugin_name): - """Perform basic option checking, provide helpful error messages""" +def check_valid_auth_options(options, auth_plugin_name, required_scope=True): + """Perform basic option checking, provide helpful error messages. + + :param required_scope: indicate whether a scoped token is required + + """ msg = '' if auth_plugin_name.endswith('password'): @@ -146,7 +150,8 @@ def check_valid_auth_options(options, auth_plugin_name): if not options.auth.get('auth_url', None): msg += _('Set an authentication URL, with --os-auth-url,' ' OS_AUTH_URL or auth.auth_url\n') - if (not options.auth.get('project_id', None) and not + if (required_scope and not + options.auth.get('project_id', None) and not options.auth.get('domain_id', None) and not options.auth.get('domain_name', None) and not options.auth.get('project_name', None) and not diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index dce1972515..48b3aca57c 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -113,19 +113,35 @@ def __init__( root_logger = logging.getLogger('') LOG.setLevel(root_logger.getEffectiveLevel()) - def setup_auth(self): + # NOTE(gyee): use this flag to indicate whether auth setup has already + # been completed. If so, do not perform auth setup again. The reason + # we need this flag is that we want to be able to perform auth setup + # outside of auth_ref as auth_ref itself is a property. We can not + # retrofit auth_ref to optionally skip scope check. Some operations + # do not require a scoped token. In those cases, we call setup_auth + # prior to dereferrencing auth_ref. + self._auth_setup_completed = False + + def setup_auth(self, required_scope=True): """Set up authentication + :param required_scope: indicate whether a scoped token is required + This is deferred until authentication is actually attempted because it gets in the way of things that do not require auth. """ + if self._auth_setup_completed: + return + # If no auth type is named by the user, select one based on # the supplied options self.auth_plugin_name = auth.select_auth_plugin(self._cli_options) # Basic option checking to avoid unhelpful error messages - auth.check_valid_auth_options(self._cli_options, self.auth_plugin_name) + auth.check_valid_auth_options(self._cli_options, + self.auth_plugin_name, + required_scope=required_scope) # Horrible hack alert...must handle prompt for null password if # password auth is requested. @@ -180,6 +196,8 @@ def setup_auth(self): user_agent=USER_AGENT, ) + self._auth_setup_completed = True + return @property diff --git a/openstackclient/identity/v2_0/token.py b/openstackclient/identity/v2_0/token.py index db38fae8e3..6a66a1c6d7 100644 --- a/openstackclient/identity/v2_0/token.py +++ b/openstackclient/identity/v2_0/token.py @@ -24,6 +24,9 @@ class IssueToken(command.ShowOne): """Issue new token""" + # scoped token is optional + required_scope = False + def get_parser(self, prog_name): parser = super(IssueToken, self).get_parser(prog_name) return parser @@ -31,7 +34,8 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): token = self.app.client_manager.auth_ref.service_catalog.get_token() - token['project_id'] = token.pop('tenant_id') + if 'tenant_id' in token: + token['project_id'] = token.pop('tenant_id') return zip(*sorted(six.iteritems(token))) diff --git a/openstackclient/identity/v3/token.py b/openstackclient/identity/v3/token.py index 9ebd17995a..5f131939cd 100644 --- a/openstackclient/identity/v3/token.py +++ b/openstackclient/identity/v3/token.py @@ -164,6 +164,9 @@ def take_action(self, parsed_args): class IssueToken(command.ShowOne): """Issue new token""" + # scoped token is optional + required_scope = False + def get_parser(self, prog_name): parser = super(IssueToken, self).get_parser(prog_name) return parser diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 137446efb6..dfec40af3a 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -353,6 +353,9 @@ def prepare_to_run_command(self, cmd): cmd.__class__.__name__, ) if cmd.auth_required: + if hasattr(cmd, 'required_scope'): + # let the command decide whether we need a scoped token + self.client_manager.setup_auth(cmd.required_scope) # Trigger the Identity client to initialize self.client_manager.auth_ref return diff --git a/openstackclient/tests/common/test_clientmanager.py b/openstackclient/tests/common/test_clientmanager.py index 523f79a32a..ef46f61c56 100644 --- a/openstackclient/tests/common/test_clientmanager.py +++ b/openstackclient/tests/common/test_clientmanager.py @@ -325,3 +325,28 @@ def test_client_manager_select_auth_plugin_failure(self): exc.CommandError, client_manager.setup_auth, ) + + @mock.patch('openstackclient.api.auth.check_valid_auth_options') + def test_client_manager_auth_setup_once(self, check_auth_options_func): + client_manager = clientmanager.ClientManager( + cli_options=FakeOptions( + auth=dict( + auth_url=fakes.AUTH_URL, + username=fakes.USERNAME, + password=fakes.PASSWORD, + project_name=fakes.PROJECT_NAME, + ), + ), + api_version=API_VERSION, + verify=False, + ) + self.assertFalse(client_manager._auth_setup_completed) + client_manager.setup_auth() + self.assertTrue(check_auth_options_func.called) + self.assertTrue(client_manager._auth_setup_completed) + + # now make sure we don't do auth setup the second time around + # by checking whether check_valid_auth_options() gets called again + check_auth_options_func.reset_mock() + client_manager.auth_ref + check_auth_options_func.assert_not_called() diff --git a/openstackclient/tests/identity/v2_0/fakes.py b/openstackclient/tests/identity/v2_0/fakes.py index 6688606a42..565606c161 100644 --- a/openstackclient/tests/identity/v2_0/fakes.py +++ b/openstackclient/tests/identity/v2_0/fakes.py @@ -80,6 +80,12 @@ 'user_id': user_id, } +UNSCOPED_TOKEN = { + 'expires': token_expires, + 'id': token_id, + 'user_id': user_id, +} + endpoint_name = service_name endpoint_adminurl = 'https://admin.example.com/v2/UUID' endpoint_region = 'RegionOne' diff --git a/openstackclient/tests/identity/v2_0/test_token.py b/openstackclient/tests/identity/v2_0/test_token.py index 7687a063f9..c90477f942 100644 --- a/openstackclient/tests/identity/v2_0/test_token.py +++ b/openstackclient/tests/identity/v2_0/test_token.py @@ -60,6 +60,28 @@ def test_token_issue(self): ) self.assertEqual(datalist, data) + def test_token_issue_with_unscoped_token(self): + # make sure we return an unscoped token + self.sc_mock.get_token.return_value = identity_fakes.UNSCOPED_TOKEN + + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + self.sc_mock.get_token.assert_called_with() + + collist = ('expires', 'id', 'user_id') + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.token_expires, + identity_fakes.token_id, + identity_fakes.user_id, + ) + self.assertEqual(datalist, data) + class TestTokenRevoke(TestToken): diff --git a/openstackclient/tests/identity/v3/fakes.py b/openstackclient/tests/identity/v3/fakes.py index a06802c563..420604f180 100644 --- a/openstackclient/tests/identity/v3/fakes.py +++ b/openstackclient/tests/identity/v3/fakes.py @@ -244,6 +244,12 @@ token_expires = '2014-01-01T00:00:00Z' token_id = 'tttttttt-tttt-tttt-tttt-tttttttttttt' +UNSCOPED_TOKEN = { + 'expires': token_expires, + 'id': token_id, + 'user_id': user_id, +} + TOKEN_WITH_PROJECT_ID = { 'expires': token_expires, 'id': token_id, diff --git a/openstackclient/tests/identity/v3/test_token.py b/openstackclient/tests/identity/v3/test_token.py index b051aacbf5..80c397bccf 100644 --- a/openstackclient/tests/identity/v3/test_token.py +++ b/openstackclient/tests/identity/v3/test_token.py @@ -85,6 +85,27 @@ def test_token_issue_with_domain_id(self): ) self.assertEqual(datalist, data) + def test_token_issue_with_unscoped(self): + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.sc_mock.get_token.return_value = \ + identity_fakes.UNSCOPED_TOKEN + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + self.sc_mock.get_token.assert_called_with() + + collist = ('expires', 'id', 'user_id') + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.token_expires, + identity_fakes.token_id, + identity_fakes.user_id, + ) + self.assertEqual(datalist, data) + class TestTokenRevoke(TestToken): diff --git a/releasenotes/notes/bug-1543214-959aee7830db2b0d.yaml b/releasenotes/notes/bug-1543214-959aee7830db2b0d.yaml new file mode 100644 index 0000000000..e228480bbb --- /dev/null +++ b/releasenotes/notes/bug-1543214-959aee7830db2b0d.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + The ``token issue`` can now return an unscoped token. If a `project` or `domain` + target scope are not specified, an unscoped token will be returned. + [Bug `1543214 `_] From 5a978b9ec137cece167f0164dbb1754002a81bec Mon Sep 17 00:00:00 2001 From: "ting.wang" Date: Sat, 20 Feb 2016 14:09:40 +0800 Subject: [PATCH 0627/3095] Replace string format arguments with function parameters There are files containing string format arguments inside logging messages. Using logging function parameters should be preferred. Change-Id: I15b405bf4d4715263fe1e1262982467b3d4bc1f4 Closes-Bug: #1321274 --- openstackclient/api/auth.py | 4 ++-- openstackclient/api/auth_plugin.py | 2 +- openstackclient/common/clientmanager.py | 4 ++-- openstackclient/compute/client.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/openstackclient/api/auth.py b/openstackclient/api/auth.py index 66272e4247..442873188e 100644 --- a/openstackclient/api/auth.py +++ b/openstackclient/api/auth.py @@ -106,7 +106,7 @@ def select_auth_plugin(options): # The ultimate default is similar to the original behaviour, # but this time with version discovery auth_plugin_name = 'osc_password' - LOG.debug("Auth plugin %s selected" % auth_plugin_name) + LOG.debug("Auth plugin %s selected", auth_plugin_name) return auth_plugin_name @@ -130,7 +130,7 @@ def build_auth_params(auth_plugin_name, cmd_options): auth_plugin_class = None plugin_options = set([o.replace('-', '_') for o in get_options_list()]) for option in plugin_options: - LOG.debug('fetching option %s' % option) + LOG.debug('fetching option %s', option) auth_params[option] = getattr(cmd_options.auth, option, None) return (auth_plugin_class, auth_params) diff --git a/openstackclient/api/auth_plugin.py b/openstackclient/api/auth_plugin.py index deddfcc48f..cff0b75dc9 100644 --- a/openstackclient/api/auth_plugin.py +++ b/openstackclient/api/auth_plugin.py @@ -97,7 +97,7 @@ def create_plugin(self, session, version, url, raw_status=None): ver_u.query, ver_u.fragment, )) - LOG.debug('Version URL updated: %s' % url) + LOG.debug('Version URL updated: %s', url) return super(OSCGenericPassword, self).create_plugin( session=session, diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index dce1972515..5696b9e1bc 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -167,8 +167,8 @@ def setup_auth(self): elif 'tenant_name' in self._auth_params: self._project_name = self._auth_params['tenant_name'] - LOG.info('Using auth plugin: %s' % self.auth_plugin_name) - LOG.debug('Using parameters %s' % + LOG.info('Using auth plugin: %s', self.auth_plugin_name) + LOG.debug('Using parameters %s', strutils.mask_password(self._auth_params)) self.auth = auth_plugin.load_from_options(**self._auth_params) # needed by SAML authentication diff --git a/openstackclient/compute/client.py b/openstackclient/compute/client.py index 23a4decaab..1481ed6535 100644 --- a/openstackclient/compute/client.py +++ b/openstackclient/compute/client.py @@ -42,7 +42,7 @@ def make_client(instance): else: version = instance._api_version[API_NAME] - LOG.debug('Instantiating compute client for V%s' % version) + LOG.debug('Instantiating compute client for V%s', version) # Set client http_log_debug to True if verbosity level is high enough http_log_debug = utils.get_effective_log_level() <= logging.DEBUG From 53e058fabc378575c96e324bc3a7da3a9d15238f Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 20 Feb 2016 15:01:07 +0800 Subject: [PATCH 0628/3095] Trivial: Rename subnet_pool.rst to subnet-pool.rst File names under doc/source/command-objects/ are words connected with "-". So rename subnet_pool.rst to subnet-pool.rst to keep the consistence. Also use "display" instead of "show" in the comment to keep the consistence. Change-Id: If486f6cec34b4572a8245af865267b063c1e877d --- .../command-objects/{subnet_pool.rst => subnet-pool.rst} | 4 ++-- openstackclient/network/v2/subnet_pool.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename doc/source/command-objects/{subnet_pool.rst => subnet-pool.rst} (91%) diff --git a/doc/source/command-objects/subnet_pool.rst b/doc/source/command-objects/subnet-pool.rst similarity index 91% rename from doc/source/command-objects/subnet_pool.rst rename to doc/source/command-objects/subnet-pool.rst index 7a8f79e42d..e181caec94 100644 --- a/doc/source/command-objects/subnet_pool.rst +++ b/doc/source/command-objects/subnet-pool.rst @@ -38,7 +38,7 @@ List subnet pools subnet pool show ---------------- -Show subnet pool details +Display subnet pool details .. program:: subnet pool show .. code:: bash @@ -49,4 +49,4 @@ Show subnet pool details .. _subnet_pool_show-subnet-pool: .. describe:: - Subnet pool to show (name or ID) + Subnet pool to display (name or ID) diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index 1db1652f9c..5bb45c120a 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -99,14 +99,14 @@ def take_action(self, parsed_args): class ShowSubnetPool(command.ShowOne): - """Show subnet pool details""" + """Display subnet pool details""" def get_parser(self, prog_name): parser = super(ShowSubnetPool, self).get_parser(prog_name) parser.add_argument( 'subnet_pool', metavar="", - help=("Subnet pool to show (name or ID)") + help=("Subnet pool to display (name or ID)") ) return parser From eb6da5f0ba0c0eb659a777e16a29318d1c87127b Mon Sep 17 00:00:00 2001 From: "ting.wang" Date: Sat, 20 Feb 2016 16:25:56 +0800 Subject: [PATCH 0629/3095] gitignore .idea Directory .idea is produces by pycharm. We'd better ignore it. Change-Id: Ia3cbca09398caeb88dd66f89adcd0fcfff8dac4c --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 34cd3a23e9..29b5574ce2 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ *.swp *~ .coverage +.idea .testrepository .tox AUTHORS From dc5a8faddd2f7f7afb29751b7d00eb6b5474857f Mon Sep 17 00:00:00 2001 From: "ting.wang" Date: Sat, 20 Feb 2016 16:35:11 +0800 Subject: [PATCH 0630/3095] Fix Mutable default argument MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Python’s default arguments are evaluated once when the function is defined, not each time the function is called. This means that if you use a mutable default argument (like list and dict) and mutate it, you will and have mutated that object for all future calls to the function as well. more details about this wrong usage here: http://docs.python-guide.org/en/latest/writing/gotchas/#mutable-default-arguments Change-Id: If187f16bfb305ac4fe6e4177e498a06c49c3f946 --- openstackclient/common/utils.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index 4142f830f5..840da40218 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -163,7 +163,7 @@ def get_field(item, field): raise exceptions.CommandError(msg) -def get_item_properties(item, fields, mixed_case_fields=[], formatters={}): +def get_item_properties(item, fields, mixed_case_fields=None, formatters=None): """Return a tuple containing the item properties. :param item: a single item resource (e.g. Server, Project, etc) @@ -172,6 +172,11 @@ def get_item_properties(item, fields, mixed_case_fields=[], formatters={}): :param formatters: dictionary mapping field names to callables to format the values """ + if mixed_case_fields is None: + mixed_case_fields = [] + if formatters is None: + formatters = {} + row = [] for field in fields: @@ -187,7 +192,7 @@ def get_item_properties(item, fields, mixed_case_fields=[], formatters={}): return tuple(row) -def get_dict_properties(item, fields, mixed_case_fields=[], formatters={}): +def get_dict_properties(item, fields, mixed_case_fields=None, formatters=None): """Return a tuple containing the item properties. :param item: a single dict resource @@ -196,6 +201,11 @@ def get_dict_properties(item, fields, mixed_case_fields=[], formatters={}): :param formatters: dictionary mapping field names to callables to format the values """ + if mixed_case_fields is None: + mixed_case_fields = [] + if formatters is None: + formatters = {} + row = [] for field in fields: From 6af28838854ecb20c34d3ba1708b5b255155ef93 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 20 Feb 2016 16:39:06 +0800 Subject: [PATCH 0631/3095] Refactor: Set "project_id" for FakeXXX in a consistent style OpenStack SDK will translate "project_id" into "tenant_id" automatically when referring to "tenant_id" attribute with the name "project_id". So when faking an object returned fron SDK, we need to fake this behavior. The original way is ugly. This patch turns it into a consistent style, and give better comments. Change-Id: I0dfb1f7552fc28eb4e7ebf5c614c9f3bde79ad80 --- openstackclient/tests/network/v2/fakes.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 60895e74ac..680e9cbfe9 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -138,12 +138,11 @@ def create_one_network(attrs={}, methods={}): router_external, status, subnets, tenant_id """ # Set default attributes. - project_id = 'project-id-' + uuid.uuid4().hex network_attrs = { 'id': 'network-id-' + uuid.uuid4().hex, 'name': 'network-name-' + uuid.uuid4().hex, 'status': 'ACTIVE', - 'tenant_id': project_id, + 'tenant_id': 'project-id-' + uuid.uuid4().hex, 'admin_state_up': True, 'shared': False, 'subnets': ['a', 'b'], @@ -169,7 +168,9 @@ def create_one_network(attrs={}, methods={}): network = fakes.FakeResource(info=copy.deepcopy(network_attrs), methods=copy.deepcopy(network_methods), loaded=True) - network.project_id = project_id + + # Set attributes with special mapping in OpenStack SDK. + network.project_id = network_attrs['tenant_id'] return network @@ -273,7 +274,7 @@ def create_one_port(attrs={}, methods={}): methods=copy.deepcopy(port_methods), loaded=True) - # Set attributes with special mappings. + # Set attributes with special mappings in OpenStack SDK. port.project_id = port_attrs['tenant_id'] port.binding_host_id = port_attrs['binding:host_id'] port.binding_profile = port_attrs['binding:profile'] @@ -695,24 +696,19 @@ def create_one_subnet_pool(attrs={}, methods={}): A FakeResource object faking the subnet pool """ # Set default attributes. - project_id = 'project-id-' + uuid.uuid4().hex subnet_pool_attrs = { 'id': 'subnet-pool-id-' + uuid.uuid4().hex, 'name': 'subnet-pool-name-' + uuid.uuid4().hex, 'prefixes': ['10.0.0.0/24', '10.1.0.0/24'], 'default_prefixlen': 8, 'address_scope_id': 'address-scope-id-' + uuid.uuid4().hex, - 'tenant_id': project_id, + 'tenant_id': 'project-id-' + uuid.uuid4().hex, 'is_default': False, 'shared': False, 'max_prefixlen': 32, 'min_prefixlen': 8, 'default_quota': None, 'ip_version': 4, - - # OpenStack SDK automatically translates project_id to tenant_id. - # So we need an additional attr to simulate this behavior. - 'project_id': project_id, } # Overwrite default attributes. @@ -735,6 +731,9 @@ def create_one_subnet_pool(attrs={}, methods={}): loaded=True ) + # Set attributes with special mapping in OpenStack SDK. + subnet_pool.project_id = subnet_pool_attrs['tenant_id'] + return subnet_pool @staticmethod From b4edbd55f2d22214fa9ebdba4b772d2c9d7b9e36 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 20 Feb 2016 10:34:40 +0800 Subject: [PATCH 0632/3095] Add unit test for "flavor show" command Change-Id: I1591649e5b97a885707042fcccad3335ee8c7aec --- openstackclient/tests/compute/v2/fakes.py | 12 +++ .../tests/compute/v2/test_flavor.py | 87 ++++++++++++++++--- 2 files changed, 89 insertions(+), 10 deletions(-) diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 974095ea32..96d85b0c06 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -404,6 +404,12 @@ def create_one_flavor(attrs={}): 'name': 'flavor-name-' + uuid.uuid4().hex, 'ram': 8192, 'vcpus': 4, + 'disk': 128, + 'swap': '', + 'rxtx_factor': '1.0', + 'OS-FLV-DISABLED:disabled': False, + 'os-flavor-access:is_public': True, + 'OS-FLV-EXT-DATA:ephemeral': 0, } # Overwrite default attributes. @@ -411,6 +417,12 @@ def create_one_flavor(attrs={}): flavor = FakeFlavorResource(info=copy.deepcopy(flavor_info), loaded=True) + + # Set attributes with special mappings in nova client. + flavor.disabled = flavor_info['OS-FLV-DISABLED:disabled'] + flavor.is_public = flavor_info['os-flavor-access:is_public'] + flavor.ephemeral = flavor_info['OS-FLV-EXT-DATA:ephemeral'] + return flavor @staticmethod diff --git a/openstackclient/tests/compute/v2/test_flavor.py b/openstackclient/tests/compute/v2/test_flavor.py index bf78bee821..781e3068ae 100644 --- a/openstackclient/tests/compute/v2/test_flavor.py +++ b/openstackclient/tests/compute/v2/test_flavor.py @@ -14,8 +14,10 @@ # from openstackclient.common import exceptions +from openstackclient.common import utils from openstackclient.compute.v2 import flavor from openstackclient.tests.compute.v2 import fakes as compute_fakes +from openstackclient.tests import utils as tests_utils class TestFlavor(compute_fakes.TestComputev2): @@ -97,15 +99,15 @@ class TestFlavorList(TestFlavor): flavors[0].id, flavors[0].name, flavors[0].ram, - '', - '', + flavors[0].disk, + flavors[0].ephemeral, flavors[0].vcpus, - '' + flavors[0].is_public, ), ) data_long = (data[0] + ( - '', - '', - 'property=\'value\'' + flavors[0].swap, + flavors[0].rxtx_factor, + u'property=\'value\'' ), ) def setUp(self): @@ -290,8 +292,73 @@ def test_flavor_set(self): self.flavors_mock.find.assert_called_with(name='baremetal') - self.assertEqual('properties', columns[2]) - self.assertIn('FOO=\'"B A R"\'', data[2]) + self.assertEqual('properties', columns[6]) + self.assertIn('FOO=\'"B A R"\'', data[6]) + + +class TestFlavorShow(TestFlavor): + + # Return value of self.flavors_mock.find(). + flavor = compute_fakes.FakeFlavor.create_one_flavor() + + columns = ( + 'OS-FLV-DISABLED:disabled', + 'OS-FLV-EXT-DATA:ephemeral', + 'disk', + 'id', + 'name', + 'os-flavor-access:is_public', + 'properties', + 'ram', + 'rxtx_factor', + 'swap', + 'vcpus', + ) + + data = ( + flavor.disabled, + flavor.ephemeral, + flavor.disk, + flavor.id, + flavor.name, + flavor.is_public, + utils.format_dict(flavor.get_keys()), + flavor.ram, + flavor.rxtx_factor, + flavor.swap, + flavor.vcpus, + ) + + def setUp(self): + super(TestFlavorShow, self).setUp() + + # Return value of utils.find_resource() + self.flavors_mock.get.return_value = self.flavor + + self.cmd = flavor.ShowFlavor(self.app, None) + + def test_show_no_options(self): + arglist = [] + verifylist = [] + + # Missing required args should boil here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_flavor_show(self): + arglist = [ + self.flavor.name, + ] + verifylist = [ + ('flavor', self.flavor.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) class TestFlavorUnset(TestFlavor): @@ -322,5 +389,5 @@ def test_flavor_unset(self): self.flavors_mock.find.assert_called_with(name='baremetal') - self.assertEqual('properties', columns[2]) - self.assertNotIn('property', data[2]) + self.assertEqual('properties', columns[6]) + self.assertNotIn('property', data[6]) From 097d35e1303415c0b07fc26c67a77d7fee036692 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sat, 20 Feb 2016 22:00:37 +0000 Subject: [PATCH 0633/3095] Updated from global requirements Change-Id: I387ac9f75fd542ade62869efaf173a4f8d769fe1 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f527fc7503..6a40b7993e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ openstacksdk>=0.7.4 # Apache-2.0 os-client-config>=1.13.1 # Apache-2.0 oslo.config>=3.4.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 -oslo.utils>=3.4.0 # Apache-2.0 +oslo.utils>=3.5.0 # Apache-2.0 python-glanceclient>=1.2.0 # Apache-2.0 python-keystoneclient!=1.8.0,!=2.1.0,>=1.6.0 # Apache-2.0 python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0 From 86cae7e170c8432223c122f617bcb757a101f179 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sun, 21 Feb 2016 08:25:27 +0800 Subject: [PATCH 0634/3095] Add functional tests for "image" command v2 The tests for image v2 are quite similar to the tests for v1. The only difference things are: 1. v2 "image set" command only allows to change the disk format for a queued image 2. v2 "image show" command output is different from v1 Change-Id: Ieb6bec7467887aab567743153ea3181afa49537d --- functional/tests/image/v2/__init__.py | 0 functional/tests/image/v2/test_image.py | 67 +++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 functional/tests/image/v2/__init__.py create mode 100644 functional/tests/image/v2/test_image.py diff --git a/functional/tests/image/v2/__init__.py b/functional/tests/image/v2/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/functional/tests/image/v2/test_image.py b/functional/tests/image/v2/test_image.py new file mode 100644 index 0000000000..f0ebc11665 --- /dev/null +++ b/functional/tests/image/v2/test_image.py @@ -0,0 +1,67 @@ +# 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 os +import uuid + +from functional.common import test + + +class ImageTests(test.TestCase): + """Functional tests for image. """ + + NAME = uuid.uuid4().hex + OTHER_NAME = uuid.uuid4().hex + HEADERS = ['Name'] + FIELDS = ['name'] + + @classmethod + def setUpClass(cls): + os.environ['OS_IMAGE_API_VERSION'] = '2' + opts = cls.get_show_opts(cls.FIELDS) + raw_output = cls.openstack('image create ' + cls.NAME + opts) + expected = cls.NAME + '\n' + cls.assertOutput(expected, raw_output) + + @classmethod + def tearDownClass(cls): + # Rename test + raw_output = cls.openstack('image set --name ' + cls.OTHER_NAME + ' ' + + cls.NAME) + cls.assertOutput('', raw_output) + # Delete test + raw_output = cls.openstack('image delete ' + cls.OTHER_NAME) + cls.assertOutput('', raw_output) + + def test_image_list(self): + opts = self.get_list_opts(self.HEADERS) + raw_output = self.openstack('image list' + opts) + self.assertIn(self.NAME, raw_output) + + def test_image_show(self): + opts = self.get_show_opts(self.FIELDS) + raw_output = self.openstack('image show ' + self.NAME + opts) + self.assertEqual(self.NAME + "\n", raw_output) + + def test_image_set(self): + opts = self.get_show_opts([ + "disk_format", "visibility", "min_disk", "min_ram", "name"]) + self.openstack('image set --min-disk 4 --min-ram 5 ' + + '--public ' + self.NAME) + raw_output = self.openstack('image show ' + self.NAME + opts) + self.assertEqual("raw\n4\n5\n" + self.NAME + '\npublic\n', raw_output) + + def test_image_metadata(self): + opts = self.get_show_opts(["name", "properties"]) + self.openstack('image set --property a=b --property c=d ' + self.NAME) + raw_output = self.openstack('image show ' + self.NAME + opts) + self.assertEqual(self.NAME + "\na='b', c='d'\n", raw_output) From acc0297fa61f1dbc23ac5106ea26fc940339c9aa Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sun, 21 Feb 2016 09:10:15 +0800 Subject: [PATCH 0635/3095] Add functional tests for "volume" commands v2 The tests for v2 "volume" commands are quite similar to v1. This patch also map 'metadata' to 'properties', 'volume_type' to 'type' to align to the v1 output. Change-Id: Icf2c5463b186fc78c890ccd96453090c4a2c2eb6 Partial-bug: #1519503 --- functional/tests/volume/v2/__init__.py | 0 functional/tests/volume/v2/test_volume.py | 80 +++++++++++++++++++++++ openstackclient/tests/volume/v2/fakes.py | 28 +++++++- openstackclient/volume/v2/volume.py | 10 +++ 4 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 functional/tests/volume/v2/__init__.py create mode 100644 functional/tests/volume/v2/test_volume.py diff --git a/functional/tests/volume/v2/__init__.py b/functional/tests/volume/v2/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/functional/tests/volume/v2/test_volume.py b/functional/tests/volume/v2/test_volume.py new file mode 100644 index 0000000000..b07751836d --- /dev/null +++ b/functional/tests/volume/v2/test_volume.py @@ -0,0 +1,80 @@ +# 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 os +import uuid + +from functional.common import test + + +class VolumeTests(test.TestCase): + """Functional tests for volume. """ + + NAME = uuid.uuid4().hex + OTHER_NAME = uuid.uuid4().hex + HEADERS = ['"Display Name"'] + FIELDS = ['name'] + + @classmethod + def setUpClass(cls): + os.environ['OS_VOLUME_API_VERSION'] = '2' + opts = cls.get_show_opts(cls.FIELDS) + raw_output = cls.openstack('volume create --size 1 ' + cls.NAME + opts) + expected = cls.NAME + '\n' + cls.assertOutput(expected, raw_output) + + @classmethod + def tearDownClass(cls): + # Rename test + raw_output = cls.openstack( + 'volume set --name ' + cls.OTHER_NAME + ' ' + cls.NAME) + cls.assertOutput('', raw_output) + # Delete test + raw_output = cls.openstack('volume delete ' + cls.OTHER_NAME) + cls.assertOutput('', raw_output) + + def test_volume_list(self): + opts = self.get_list_opts(self.HEADERS) + raw_output = self.openstack('volume list' + opts) + self.assertIn(self.NAME, raw_output) + + def test_volume_show(self): + opts = self.get_show_opts(self.FIELDS) + raw_output = self.openstack('volume show ' + self.NAME + opts) + self.assertEqual(self.NAME + "\n", raw_output) + + def test_volume_properties(self): + raw_output = self.openstack( + 'volume set --property a=b --property c=d ' + self.NAME) + self.assertEqual("", raw_output) + opts = self.get_show_opts(["properties"]) + raw_output = self.openstack('volume show ' + self.NAME + opts) + self.assertEqual("a='b', c='d'\n", raw_output) + + raw_output = self.openstack('volume unset --property a ' + self.NAME) + self.assertEqual("", raw_output) + raw_output = self.openstack('volume show ' + self.NAME + opts) + self.assertEqual("c='d'\n", raw_output) + + def test_volume_set(self): + discription = uuid.uuid4().hex + self.openstack('volume set --description ' + discription + ' ' + + self.NAME) + opts = self.get_show_opts(["description", "name"]) + raw_output = self.openstack('volume show ' + self.NAME + opts) + self.assertEqual(discription + "\n" + self.NAME + "\n", raw_output) + + def test_volume_set_size(self): + self.openstack('volume set --size 2 ' + self.NAME) + opts = self.get_show_opts(["name", "size"]) + raw_output = self.openstack('volume show ' + self.NAME + opts) + self.assertEqual(self.NAME + "\n2\n", raw_output) diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py index 2fc5c8ff2e..cfc58bb4c3 100644 --- a/openstackclient/tests/volume/v2/fakes.py +++ b/openstackclient/tests/volume/v2/fakes.py @@ -17,6 +17,7 @@ import random import uuid +from openstackclient.common import utils as common_utils from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes from openstackclient.tests.image.v2 import fakes as image_fakes @@ -56,8 +57,31 @@ "attachments": volume_attachments } -VOLUME_columns = tuple(sorted(VOLUME)) -VOLUME_data = tuple((VOLUME[x] for x in sorted(VOLUME))) +VOLUME_columns = ( + "attachments", + "availability_zone", + "description", + "id", + "name", + "properties", + "size", + "snapshot_id", + "status", + "type" +) + +VOLUME_data = ( + volume_attachments, + volume_availability_zone, + volume_description, + volume_id, + volume_name, + common_utils.format_dict(volume_metadata), + volume_size, + volume_snapshot_id, + volume_status, + volume_type +) snapshot_id = "cb2d364e-4d1c-451a-8c68-b5bbcb340fb2" diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index 436ec689dc..87affd07d3 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -394,6 +394,16 @@ def take_action(self, parsed_args): volume_client = self.app.client_manager.volume volume = utils.find_resource(volume_client.volumes, parsed_args.volume) + # Special mapping for columns to make the output easier to read: + # 'metadata' --> 'properties' + # 'volume_type' --> 'type' + volume._info.update( + { + 'properties': utils.format_dict(volume._info.pop('metadata')), + 'type': volume._info.pop('volume_type'), + }, + ) + # Remove key links from being displayed volume._info.pop("links", None) return zip(*sorted(six.iteritems(volume._info))) From e2158b7ef4c307ff5be7bd2ef0ca3044f37759d4 Mon Sep 17 00:00:00 2001 From: "ting.wang" Date: Sat, 20 Feb 2016 14:28:08 +0800 Subject: [PATCH 0636/3095] Clean redundant argument to dict.get `dict.get()` returns `None` by default, if a key wasn't found. Removing `None` as second argument to avoid redundancy. Change-Id: Ia82f7469cd019509bbeccbfe54b15eeedc7bb6ea --- openstackclient/api/auth.py | 30 +++++++++---------- openstackclient/api/object_store_v1.py | 39 +++++++++++-------------- openstackclient/common/clientmanager.py | 12 ++++---- openstackclient/common/exceptions.py | 4 +-- openstackclient/common/logs.py | 4 +-- 5 files changed, 42 insertions(+), 47 deletions(-) diff --git a/openstackclient/api/auth.py b/openstackclient/api/auth.py index e675692ecc..3d6f7bcf19 100644 --- a/openstackclient/api/auth.py +++ b/openstackclient/api/auth.py @@ -80,13 +80,13 @@ def select_auth_plugin(options): # Do the token/url check first as this must override the default # 'password' set by os-client-config # Also, url and token are not copied into o-c-c's auth dict (yet?) - if options.auth.get('url', None) and options.auth.get('token', None): + if options.auth.get('url') and options.auth.get('token'): # service token authentication auth_plugin_name = 'token_endpoint' elif options.auth_type in [plugin.name for plugin in PLUGIN_LIST]: # A direct plugin name was given, use it auth_plugin_name = options.auth_type - elif options.auth.get('username', None): + elif options.auth.get('username'): if options.identity_api_version == '3': auth_plugin_name = 'v3password' elif options.identity_api_version.startswith('2'): @@ -94,7 +94,7 @@ def select_auth_plugin(options): else: # let keystoneclient figure it out itself auth_plugin_name = 'osc_password' - elif options.auth.get('token', None): + elif options.auth.get('token'): if options.identity_api_version == '3': auth_plugin_name = 'v3token' elif options.identity_api_version.startswith('2'): @@ -144,33 +144,33 @@ def check_valid_auth_options(options, auth_plugin_name, required_scope=True): msg = '' if auth_plugin_name.endswith('password'): - if not options.auth.get('username', None): + if not options.auth.get('username'): msg += _('Set a username with --os-username, OS_USERNAME,' ' or auth.username\n') - if not options.auth.get('auth_url', None): + if not options.auth.get('auth_url'): msg += _('Set an authentication URL, with --os-auth-url,' ' OS_AUTH_URL or auth.auth_url\n') if (required_scope and not - options.auth.get('project_id', None) and not - options.auth.get('domain_id', None) and not - options.auth.get('domain_name', None) and not - options.auth.get('project_name', None) and not - options.auth.get('tenant_id', None) and not - options.auth.get('tenant_name', None)): + options.auth.get('project_id') and not + options.auth.get('domain_id') and not + options.auth.get('domain_name') and not + options.auth.get('project_name') and not + options.auth.get('tenant_id') and not + options.auth.get('tenant_name')): msg += _('Set a scope, such as a project or domain, set a ' 'project scope with --os-project-name, OS_PROJECT_NAME ' 'or auth.project_name, set a domain scope with ' '--os-domain-name, OS_DOMAIN_NAME or auth.domain_name') elif auth_plugin_name.endswith('token'): - if not options.auth.get('token', None): + if not options.auth.get('token'): msg += _('Set a token with --os-token, OS_TOKEN or auth.token\n') - if not options.auth.get('auth_url', None): + if not options.auth.get('auth_url'): msg += _('Set a service AUTH_URL, with --os-auth-url, ' 'OS_AUTH_URL or auth.auth_url\n') elif auth_plugin_name == 'token_endpoint': - if not options.auth.get('token', None): + if not options.auth.get('token'): msg += _('Set a token with --os-token, OS_TOKEN or auth.token\n') - if not options.auth.get('url', None): + if not options.auth.get('url'): msg += _('Set a service URL, with --os-url, OS_URL or auth.url\n') if msg: diff --git a/openstackclient/api/object_store_v1.py b/openstackclient/api/object_store_v1.py index d9f130bcec..307c8fe296 100644 --- a/openstackclient/api/object_store_v1.py +++ b/openstackclient/api/object_store_v1.py @@ -50,7 +50,7 @@ def container_create( data = { 'account': self._find_account_id(), 'container': container, - 'x-trans-id': response.headers.get('x-trans-id', None), + 'x-trans-id': response.headers.get('x-trans-id'), } return data @@ -176,21 +176,19 @@ def container_show( 'account': self._find_account_id(), 'container': container, 'object_count': response.headers.get( - 'x-container-object-count', - None, + 'x-container-object-count' ), - 'bytes_used': response.headers.get('x-container-bytes-used', None) + 'bytes_used': response.headers.get('x-container-bytes-used') } if 'x-container-read' in response.headers: - data['read_acl'] = response.headers.get('x-container-read', None) + data['read_acl'] = response.headers.get('x-container-read') if 'x-container-write' in response.headers: - data['write_acl'] = response.headers.get('x-container-write', None) + data['write_acl'] = response.headers.get('x-container-write') if 'x-container-sync-to' in response.headers: - data['sync_to'] = response.headers.get('x-container-sync-to', None) + data['sync_to'] = response.headers.get('x-container-sync-to') if 'x-container-sync-key' in response.headers: - data['sync_key'] = response.headers.get('x-container-sync-key', - None) + data['sync_key'] = response.headers.get('x-container-sync-key') properties = self._get_properties(response.headers, 'x-container-meta-') @@ -248,8 +246,8 @@ def object_create( 'account': self._find_account_id(), 'container': container, 'object': object, - 'x-trans-id': response.headers.get('X-Trans-Id', None), - 'etag': response.headers.get('Etag', None), + 'x-trans-id': response.headers.get('X-Trans-Id'), + 'etag': response.headers.get('Etag'), } return data @@ -453,21 +451,19 @@ def object_show( 'account': self._find_account_id(), 'container': container, 'object': object, - 'content-type': response.headers.get('content-type', None), + 'content-type': response.headers.get('content-type'), } if 'content-length' in response.headers: data['content-length'] = response.headers.get( - 'content-length', - None, + 'content-length' ) if 'last-modified' in response.headers: - data['last-modified'] = response.headers.get('last-modified', None) + data['last-modified'] = response.headers.get('last-modified') if 'etag' in response.headers: - data['etag'] = response.headers.get('etag', None) + data['etag'] = response.headers.get('etag') if 'x-object-manifest' in response.headers: data['x-object-manifest'] = response.headers.get( - 'x-object-manifest', - None, + 'x-object-manifest' ) properties = self._get_properties(response.headers, 'x-object-meta-') @@ -506,10 +502,9 @@ def account_show(self): data['properties'] = properties # Map containers, bytes and objects a bit nicer - data['Containers'] = response.headers.get('x-account-container-count', - None) - data['Objects'] = response.headers.get('x-account-object-count', None) - data['Bytes'] = response.headers.get('x-account-bytes-used', None) + data['Containers'] = response.headers.get('x-account-container-count') + data['Objects'] = response.headers.get('x-account-object-count') + data['Bytes'] = response.headers.get('x-account-bytes-used') # Add in Account info too data['Account'] = self._find_account_id() return data diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index 78a0ae6e3f..fd88bdecb9 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -90,7 +90,7 @@ def __init__( self._cli_options = cli_options self._api_version = api_version self._pw_callback = pw_func - self._url = self._cli_options.auth.get('url', None) + self._url = self._cli_options.auth.get('url') self._region_name = self._cli_options.region_name self._interface = self._cli_options.interface @@ -146,7 +146,7 @@ def setup_auth(self, required_scope=True): # Horrible hack alert...must handle prompt for null password if # password auth is requested. if (self.auth_plugin_name.endswith('password') and - not self._cli_options.auth.get('password', None)): + not self._cli_options.auth.get('password')): self._cli_options.auth['password'] = self._pw_callback() (auth_plugin, self._auth_params) = auth.build_auth_params( @@ -162,9 +162,9 @@ def setup_auth(self, required_scope=True): # PROJECT_DOMAIN_ID to 'OS_DEFAULT_DOMAIN' for better usability. if (self._api_version.get('identity') == '3' and self.auth_plugin_name.endswith('password') and - not self._auth_params.get('project_domain_id', None) and + not self._auth_params.get('project_domain_id') and not self.auth_plugin_name.startswith('v2') and - not self._auth_params.get('project_domain_name', None)): + not self._auth_params.get('project_domain_name')): self._auth_params['project_domain_id'] = default_domain # NOTE(stevemar): If USER_DOMAIN_ID or USER_DOMAIN_NAME is present, @@ -173,8 +173,8 @@ def setup_auth(self, required_scope=True): if (self._api_version.get('identity') == '3' and self.auth_plugin_name.endswith('password') and not self.auth_plugin_name.startswith('v2') and - not self._auth_params.get('user_domain_id', None) and - not self._auth_params.get('user_domain_name', None)): + not self._auth_params.get('user_domain_id') and + not self._auth_params.get('user_domain_name')): self._auth_params['user_domain_id'] = default_domain # For compatibility until all clients can be updated diff --git a/openstackclient/common/exceptions.py b/openstackclient/common/exceptions.py index ab043db042..8ec4993195 100644 --- a/openstackclient/common/exceptions.py +++ b/openstackclient/common/exceptions.py @@ -122,8 +122,8 @@ def from_response(response, body): if body: if hasattr(body, 'keys'): error = body[body.keys()[0]] - message = error.get('message', None) - details = error.get('details', None) + message = error.get('message') + details = error.get('details') else: # If we didn't get back a properly formed error message we # probably couldn't communicate with Keystone at all. diff --git a/openstackclient/common/logs.py b/openstackclient/common/logs.py index 7ad6e832b9..221c59973a 100644 --- a/openstackclient/common/logs.py +++ b/openstackclient/common/logs.py @@ -169,7 +169,7 @@ def configure(self, cloud_config): self.dump_trace = cloud_config.config.get('debug', self.dump_trace) self.console_logger.setLevel(log_level) - log_file = cloud_config.config.get('log_file', None) + log_file = cloud_config.config.get('log_file') if log_file: if not self.file_logger: self.file_logger = logging.FileHandler(filename=log_file) @@ -179,7 +179,7 @@ def configure(self, cloud_config): self.file_logger.setLevel(log_level) self.root_logger.addHandler(self.file_logger) - logconfig = cloud_config.config.get('logging', None) + logconfig = cloud_config.config.get('logging') if logconfig: highest_level = logging.NOTSET for k in logconfig.keys(): From 8825f0d8f36f46eaa4aae121ba49c0f8fe6e1026 Mon Sep 17 00:00:00 2001 From: "ting.wang" Date: Mon, 22 Feb 2016 23:36:29 +0800 Subject: [PATCH 0637/3095] Add unit tests for 'hypervisor stats' command 'hypervisor stats show' command isn't covered by unit tests, so add unit tests to test it. Change-Id: Ic355230cbdd596e848191b599803dca7f27c2ffb --- openstackclient/tests/compute/v2/fakes.py | 61 ++++++++++++++ .../tests/compute/v2/test_hypervisor_stats.py | 79 +++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 openstackclient/tests/compute/v2/test_hypervisor_stats.py diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 96d85b0c06..52279f2a96 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -121,6 +121,9 @@ def __init__(self, **kwargs): self.hypervisors = mock.Mock() self.hypervisors.resource_class = fakes.FakeResource(None, {}) + self.hypervisors_stats = mock.Mock() + self.hypervisors_stats.resource_class = fakes.FakeResource(None, {}) + self.security_groups = mock.Mock() self.security_groups.resource_class = fakes.FakeResource(None, {}) @@ -235,6 +238,64 @@ def create_hypervisors(attrs={}, count=2): return hypervisors +class FakehypervisorStats(object): + """Fake one or more hypervisor stats.""" + + @staticmethod + def create_one_hypervisor_stats(attrs={}, methods={}): + """Create a fake hypervisor stats. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object, with id, hypervisor_hostname, and so on + """ + # Set default attributes. + stats_info = { + 'count': 2, + 'current_workload': 0, + 'disk_available_least': 50, + 'free_disk_gb': 100, + 'free_ram_mb': 23000, + 'local_gb': 100, + 'local_gb_used': 0, + 'memory_mb': 23800, + 'memory_mb_used': 1400, + 'running_vms': 3, + 'vcpus': 8, + 'vcpus_used': 3, + } + stats_info.update(attrs) + + # Set default method. + hypervisor_stats_method = {'to_dict': stats_info} + hypervisor_stats_method.update(methods) + + hypervisor_stats = fakes.FakeResource( + info=copy.deepcopy(stats_info), + methods=copy.deepcopy(hypervisor_stats_method), + loaded=True) + return hypervisor_stats + + @staticmethod + def create_hypervisors_stats(attrs={}, count=2): + """Create multiple fake hypervisors stats. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of hypervisors to fake + :return: + A list of FakeResource objects faking the hypervisors + """ + hypervisors = [] + for i in range(0, count): + hypervisors.append( + FakehypervisorStats.create_one_hypervisor_stats(attrs)) + + return hypervisors + + class FakeSecurityGroupRule(object): """Fake one or more security group rules.""" diff --git a/openstackclient/tests/compute/v2/test_hypervisor_stats.py b/openstackclient/tests/compute/v2/test_hypervisor_stats.py new file mode 100644 index 0000000000..39e303a84e --- /dev/null +++ b/openstackclient/tests/compute/v2/test_hypervisor_stats.py @@ -0,0 +1,79 @@ +# Copyright 2016 EasyStack Corporation +# +# 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.compute.v2 import hypervisor_stats +from openstackclient.tests.compute.v2 import fakes as compute_fakes + + +class TestHypervisorStats(compute_fakes.TestComputev2): + + def setUp(self): + super(TestHypervisorStats, self).setUp() + + # Get a shortcut to the compute client hypervisors mock + self.hypervisors_mock = self.app.client_manager.compute.hypervisors + self.hypervisors_mock.reset_mock() + + +class TestHypervisorStatsShow(TestHypervisorStats): + + def setUp(self): + super(TestHypervisorStatsShow, self).setUp() + + self.hypervisor_stats = \ + compute_fakes.FakehypervisorStats.create_one_hypervisor_stats() + + self.hypervisors_mock.statistics.return_value =\ + self.hypervisor_stats + + self.cmd = hypervisor_stats.ShowHypervisorStats(self.app, None) + + self.columns = ( + 'count', + 'current_workload', + 'disk_available_least', + 'free_disk_gb', + 'free_ram_mb', + 'local_gb', + 'local_gb_used', + 'memory_mb', + 'memory_mb_used', + 'running_vms', + 'vcpus', + 'vcpus_used', + ) + + self.data = ( + 2, + 0, + 50, + 100, + 23000, + 100, + 0, + 23800, + 1400, + 3, + 8, + 3, + ) + + def test_hypervisor_show_stats(self): + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) From c57fc41c33df58237f6b3ec8a5b2a0ff9573da2e Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 23 Feb 2016 00:14:56 +0800 Subject: [PATCH 0638/3095] Initialize _keys in __init__() in FakeFlavorResource _keys is defined as a class attribute in FakeFlavorResource. So when we call set_keys() to update it, it changes. And this change may bring trouble to the other tests afterward. So define and initialize it in __init__() as an object attribute. Change-Id: Ib18c03877b67e1b7c2e107f598076b928a58e4fb Closes-bug: #1548378 --- openstackclient/tests/compute/v2/fakes.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 96d85b0c06..c0db9b24c9 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -372,8 +372,11 @@ class FakeFlavorResource(fakes.FakeResource): Need to fake them, otherwise the functions to be tested won't run properly. """ - # Fake properties. - _keys = {'property': 'value'} + def __init__(self, manager=None, info={}, loaded=False, methods={}): + super(FakeFlavorResource, self).__init__(manager, info, + loaded, methods) + # Fake properties. + self._keys = {'property': 'value'} def set_keys(self, args): self._keys.update(args) From 112d7b0e0966599aa940de4c0598cea759780785 Mon Sep 17 00:00:00 2001 From: Brad Behle Date: Thu, 4 Feb 2016 17:19:37 -0600 Subject: [PATCH 0639/3095] Add "os subnet show" command using SDK Implement the openstack client subnet show command using SDK calls. This shows the details of a specific subnet. Co-Authored-By: Terry Howe Partially implements: blueprint neutron-client Closes-Bug: #1542359 Change-Id: Iaf18b9e44af35ca0cd61033b468e0c60cd3b05d6 --- doc/source/command-objects/subnet.rst | 16 ++++ openstackclient/network/v2/subnet.py | 28 +++++++ openstackclient/tests/network/v2/fakes.py | 11 ++- .../tests/network/v2/test_subnet.py | 74 +++++++++++++++++++ .../notes/bug-1542359-181d28db21a2358a.yaml | 5 ++ setup.cfg | 1 + 6 files changed, 133 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/bug-1542359-181d28db21a2358a.yaml diff --git a/doc/source/command-objects/subnet.rst b/doc/source/command-objects/subnet.rst index 70a0eedfa1..12b056658d 100644 --- a/doc/source/command-objects/subnet.rst +++ b/doc/source/command-objects/subnet.rst @@ -18,3 +18,19 @@ List subnets .. option:: --long List additional fields in output + +subnet show +----------- + +Show subnet details + +.. program:: subnet show +.. code:: bash + + os subnet show + + +.. _subnet_show-subnet: +.. describe:: + + Subnet to show (name or ID) diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index b948c6561b..7ed02a3a30 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -30,6 +30,14 @@ def _format_allocation_pools(data): } +def _get_columns(item): + columns = item.keys() + if 'tenant_id' in columns: + columns.remove('tenant_id') + columns.append('project_id') + return tuple(sorted(columns)) + + class ListSubnet(command.Lister): """List subnets""" @@ -61,3 +69,23 @@ def take_action(self, parsed_args): s, columns, formatters=_formatters, ) for s in data)) + + +class ShowSubnet(command.ShowOne): + """Show subnet details""" + + def get_parser(self, prog_name): + parser = super(ShowSubnet, self).get_parser(prog_name) + parser.add_argument( + 'subnet', + metavar="", + help="Subnet to show (name or ID)" + ) + return parser + + def take_action(self, parsed_args): + obj = self.app.client_manager.network.find_subnet(parsed_args.subnet, + ignore_missing=False) + columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns, formatters=_formatters) + return (columns, data) diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 680e9cbfe9..c24410e120 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -550,18 +550,22 @@ def create_one_subnet(attrs={}, methods={}): A FakeResource object faking the subnet """ # Set default attributes. + project_id = 'project-id-' + uuid.uuid4().hex subnet_attrs = { 'id': 'subnet-id-' + uuid.uuid4().hex, 'name': 'subnet-name-' + uuid.uuid4().hex, 'network_id': 'network-id-' + uuid.uuid4().hex, 'cidr': '10.10.10.0/24', - 'tenant_id': 'project-id-' + uuid.uuid4().hex, + 'tenant_id': project_id, 'enable_dhcp': True, 'dns_nameservers': [], 'allocation_pools': [], 'host_routes': [], 'ip_version': '4', 'gateway_ip': '10.10.10.1', + 'ipv6_address_mode': 'None', + 'ipv6_ra_mode': 'None', + 'subnetpool_id': 'None', } # Overwrite default attributes. @@ -571,7 +575,8 @@ def create_one_subnet(attrs={}, methods={}): subnet_methods = { 'keys': ['id', 'name', 'network_id', 'cidr', 'enable_dhcp', 'allocation_pools', 'dns_nameservers', 'gateway_ip', - 'host_routes', 'ip_version', 'tenant_id'] + 'host_routes', 'ip_version', 'tenant_id', + 'ipv6_address_mode', 'ipv6_ra_mode', 'subnetpool_id'] } # Overwrite default methods. @@ -580,6 +585,8 @@ def create_one_subnet(attrs={}, methods={}): subnet = fakes.FakeResource(info=copy.deepcopy(subnet_attrs), methods=copy.deepcopy(subnet_methods), loaded=True) + # Set attributes with special mappings in OpenStack SDK. + subnet.project_id = subnet_attrs['tenant_id'] return subnet diff --git a/openstackclient/tests/network/v2/test_subnet.py b/openstackclient/tests/network/v2/test_subnet.py index 5fca5edd99..c02dc40736 100644 --- a/openstackclient/tests/network/v2/test_subnet.py +++ b/openstackclient/tests/network/v2/test_subnet.py @@ -16,6 +16,7 @@ from openstackclient.common import utils from openstackclient.network.v2 import subnet as subnet_v2 from openstackclient.tests.network.v2 import fakes as network_fakes +from openstackclient.tests import utils as tests_utils class TestSubnet(network_fakes.TestNetworkV2): @@ -106,3 +107,76 @@ def test_subnet_list_long(self): self.network.subnets.assert_called_with() self.assertEqual(self.columns_long, columns) self.assertEqual(self.data_long, list(data)) + + +class TestShowSubnet(TestSubnet): + # The subnets to be shown + _subnet = network_fakes.FakeSubnet.create_one_subnet() + + columns = ( + 'allocation_pools', + 'cidr', + 'dns_nameservers', + 'enable_dhcp', + 'gateway_ip', + 'host_routes', + 'id', + 'ip_version', + 'ipv6_address_mode', + 'ipv6_ra_mode', + 'name', + 'network_id', + 'project_id', + 'subnetpool_id', + ) + + data = ( + subnet_v2._format_allocation_pools(_subnet.allocation_pools), + _subnet.cidr, + utils.format_list(_subnet.dns_nameservers), + _subnet.enable_dhcp, + _subnet.gateway_ip, + utils.format_list(_subnet.host_routes), + _subnet.id, + _subnet.ip_version, + _subnet.ipv6_address_mode, + _subnet.ipv6_ra_mode, + _subnet.name, + _subnet.network_id, + _subnet.tenant_id, + _subnet.subnetpool_id, + ) + + def setUp(self): + super(TestShowSubnet, self).setUp() + + # Get the command object to test + self.cmd = subnet_v2.ShowSubnet(self.app, self.namespace) + + self.network.find_subnet = mock.Mock(return_value=self._subnet) + + def test_show_no_options(self): + arglist = [] + verifylist = [] + + # Testing that a call without the required argument will fail and + # throw a "ParserExecption" + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, arglist, verifylist) + + def test_show_all_options(self): + arglist = [ + self._subnet.name, + ] + verifylist = [ + ('subnet', self._subnet.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.find_subnet.assert_called_with(self._subnet.name, + ignore_missing=False) + + self.assertEqual(self.columns, columns) + self.assertEqual(list(self.data), list(data)) diff --git a/releasenotes/notes/bug-1542359-181d28db21a2358a.yaml b/releasenotes/notes/bug-1542359-181d28db21a2358a.yaml new file mode 100644 index 0000000000..3aa7ad8e5b --- /dev/null +++ b/releasenotes/notes/bug-1542359-181d28db21a2358a.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``subnet show`` command. + [Bug `1542359 `_] diff --git a/setup.cfg b/setup.cfg index a6e2702560..b535fd4c02 100644 --- a/setup.cfg +++ b/setup.cfg @@ -341,6 +341,7 @@ openstack.network.v2 = security_group_delete = openstackclient.network.v2.security_group:DeleteSecurityGroup security_group_rule_delete = openstackclient.network.v2.security_group_rule:DeleteSecurityGroupRule subnet_list = openstackclient.network.v2.subnet:ListSubnet + subnet_show = openstackclient.network.v2.subnet:ShowSubnet subnet_pool_delete = openstackclient.network.v2.subnet_pool:DeleteSubnetPool subnet_pool_list = openstackclient.network.v2.subnet_pool:ListSubnetPool subnet_pool_show = openstackclient.network.v2.subnet_pool:ShowSubnetPool From 11a8f911affe10bacce41ad3474a28aff3417ec3 Mon Sep 17 00:00:00 2001 From: Brandon Palm Date: Mon, 22 Feb 2016 16:20:30 -0600 Subject: [PATCH 0640/3095] Use instanceof instead of type Adjusted conditional statements to use instanceof when comparing variables. Instanceof supports inheritance type checking better than type. Change-Id: I4ee0004934dc2322d43ef07e797a6811e39a812c Closes-Bug: 1548530 --- openstackclient/api/api.py | 6 +++--- openstackclient/api/utils.py | 2 +- openstackclient/tests/test_shell.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/openstackclient/api/api.py b/openstackclient/api/api.py index 97eb7e4a75..6a88e7f7d6 100644 --- a/openstackclient/api/api.py +++ b/openstackclient/api/api.py @@ -243,14 +243,14 @@ def find_attr( def getlist(kw): """Do list call, unwrap resource dict if present""" ret = self.list(path, **kw) - if type(ret) == dict and resource in ret: + if isinstance(ret, dict) and resource in ret: ret = ret[resource] return ret # Search by attribute kwargs = {attr: value} data = getlist(kwargs) - if type(data) == dict: + if isinstance(data, dict): return data if len(data) == 1: return data[0] @@ -283,7 +283,7 @@ def find_bulk( """ items = self.list(path) - if type(items) == dict: + if isinstance(items, dict): # strip off the enclosing dict key = list(items.keys())[0] items = items[key] diff --git a/openstackclient/api/utils.py b/openstackclient/api/utils.py index fa759cd349..ab0e23c29d 100644 --- a/openstackclient/api/utils.py +++ b/openstackclient/api/utils.py @@ -61,7 +61,7 @@ def simple_filter( # Searching data fields search_value = d[attr] elif (property_field and property_field in d and - type(d[property_field]) is dict): + isinstance(d[property_field], dict)): # Searching a properties field - do this separately because # we don't want to fail over to checking the fields if a # property name is given. diff --git a/openstackclient/tests/test_shell.py b/openstackclient/tests/test_shell.py index c4546d89f2..80b181611a 100644 --- a/openstackclient/tests/test_shell.py +++ b/openstackclient/tests/test_shell.py @@ -317,7 +317,7 @@ def _test_options_init_app(self, test_opts): if not test_opts[opt][1]: continue key = opt2attr(opt) - if type(test_opts[opt][0]) is str: + if isinstance(test_opts[opt][0], str): cmd = opt + " " + test_opts[opt][0] else: cmd = opt @@ -331,7 +331,7 @@ def _test_options_get_one_cloud(self, test_opts): if not test_opts[opt][1]: continue key = opt2attr(opt) - if type(test_opts[opt][0]) is str: + if isinstance(test_opts[opt][0], str): cmd = opt + " " + test_opts[opt][0] else: cmd = opt From 042e2b7d53222618c76870effa3d74759ccc696a Mon Sep 17 00:00:00 2001 From: jichenjc Date: Sun, 21 Feb 2016 08:30:54 +0800 Subject: [PATCH 0641/3095] [compute] Add unit test for keypair keypair do not have unit test, this patch adds it. Change-Id: Id702ccaad239b916340bb17014d1ede0a28aaec9 --- openstackclient/compute/v2/keypair.py | 4 +- openstackclient/tests/compute/v2/fakes.py | 55 ++++ .../tests/compute/v2/test_keypair.py | 260 ++++++++++++++++++ 3 files changed, 318 insertions(+), 1 deletion(-) create mode 100644 openstackclient/tests/compute/v2/test_keypair.py diff --git a/openstackclient/compute/v2/keypair.py b/openstackclient/compute/v2/keypair.py index 71c9d6747b..22d918a4b3 100644 --- a/openstackclient/compute/v2/keypair.py +++ b/openstackclient/compute/v2/keypair.py @@ -15,6 +15,7 @@ """Keypair action implementations""" +import io import os import six import sys @@ -47,7 +48,8 @@ def take_action(self, parsed_args): public_key = parsed_args.public_key if public_key: try: - with open(os.path.expanduser(parsed_args.public_key)) as p: + with io.open(os.path.expanduser(parsed_args.public_key), + "rb") as p: public_key = p.read() except IOError as e: msg = "Key file %s not found: %s" diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index f4d79ff7c4..a9c7154a2f 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -137,6 +137,9 @@ def __init__(self, **kwargs): self.networks = mock.Mock() self.networks.resource_class = fakes.FakeResource(None, {}) + self.keypairs = mock.Mock() + self.keypairs.resource_class = fakes.FakeResource(None, {}) + self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] @@ -534,6 +537,58 @@ def get_flavors(flavors=None, count=2): return mock.MagicMock(side_effect=flavors) +class FakeKeypair(object): + """Fake one or more keypairs.""" + + @staticmethod + def create_one_keypair(attrs=None, no_pri=False): + """Create a fake keypair + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource + """ + # Set default attributes. + if attrs is None: + attrs = {} + + keypair_info = { + 'name': 'keypair-name-' + uuid.uuid4().hex, + 'fingerprint': 'dummy', + 'public_key': 'dummy', + 'user_id': 'user' + } + if not no_pri: + keypair_info['private_key'] = 'private_key' + + # Overwrite default attributes. + keypair_info.update(attrs) + + keypair = fakes.FakeResource(info=copy.deepcopy(keypair_info), + loaded=True) + + return keypair + + @staticmethod + def create_keypairs(attrs=None, count=2): + """Create multiple fake flavors. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of flavors to fake + :return: + A list of FakeFlavorResource objects faking the flavors + """ + + keypairs = [] + for i in range(0, count): + keypairs.append(FakeKeypair.create_one_keypair(attrs)) + + return keypairs + + class FakeAvailabilityZone(object): """Fake one or more compute availability zones (AZs).""" diff --git a/openstackclient/tests/compute/v2/test_keypair.py b/openstackclient/tests/compute/v2/test_keypair.py new file mode 100644 index 0000000000..a50a532392 --- /dev/null +++ b/openstackclient/tests/compute/v2/test_keypair.py @@ -0,0 +1,260 @@ +# Copyright 2016 IBM +# +# 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.compute.v2 import keypair +from openstackclient.tests.compute.v2 import fakes as compute_fakes +from openstackclient.tests import utils as tests_utils + + +class TestKeypair(compute_fakes.TestComputev2): + + def setUp(self): + super(TestKeypair, self).setUp() + + # Get a shortcut to the KeypairManager Mock + self.keypairs_mock = self.app.client_manager.compute.keypairs + self.keypairs_mock.reset_mock() + + +class TestKeypairCreate(TestKeypair): + + keypair = compute_fakes.FakeKeypair.create_one_keypair() + + def setUp(self): + super(TestKeypairCreate, self).setUp() + + self.columns = ( + 'fingerprint', + 'name', + 'user_id' + ) + self.data = ( + self.keypair.fingerprint, + self.keypair.name, + self.keypair.user_id + ) + + # Get the command object to test + self.cmd = keypair.CreateKeypair(self.app, None) + + self.keypairs_mock.create.return_value = self.keypair + + def test_key_pair_create_no_options(self): + + arglist = [ + self.keypair.name, + ] + verifylist = [ + ('name', self.keypair.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.keypairs_mock.create.assert_called_with( + self.keypair.name, + public_key=None + ) + + self.assertEqual({}, columns) + self.assertEqual({}, data) + + def test_keypair_create_public_key(self): + # overwrite the setup one because we want to omit private_key + self.keypair = compute_fakes.FakeKeypair.create_one_keypair( + no_pri=True) + self.keypairs_mock.create.return_value = self.keypair + + self.data = ( + self.keypair.fingerprint, + self.keypair.name, + self.keypair.user_id + ) + + arglist = [ + '--public-key', self.keypair.public_key, + self.keypair.name, + ] + verifylist = [ + ('public_key', self.keypair.public_key), + ('name', self.keypair.name) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + with mock.patch('io.open') as mock_open: + mock_open.return_value = mock.MagicMock() + m_file = mock_open.return_value.__enter__.return_value + m_file.read.return_value = 'dummy' + + columns, data = self.cmd.take_action(parsed_args) + + self.keypairs_mock.create.assert_called_with( + self.keypair.name, + public_key=self.keypair.public_key + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestKeypairDelete(TestKeypair): + + keypair = compute_fakes.FakeKeypair.create_one_keypair() + + def setUp(self): + super(TestKeypairDelete, self).setUp() + + self.keypairs_mock.get.return_value = self.keypair + self.keypairs_mock.delete.return_value = None + + self.cmd = keypair.DeleteKeypair(self.app, None) + + def test_keypair_delete(self): + arglist = [ + self.keypair.name + ] + verifylist = [ + ('name', self.keypair.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ret = self.cmd.take_action(parsed_args) + + self.assertIsNone(ret) + self.keypairs_mock.delete.assert_called_with(self.keypair.name) + + +class TestKeypairList(TestKeypair): + + # Return value of self.keypairs_mock.list(). + keypairs = compute_fakes.FakeKeypair.create_keypairs(count=1) + + columns = ( + "Name", + "Fingerprint" + ) + + data = (( + keypairs[0].name, + keypairs[0].fingerprint + ), ) + + def setUp(self): + super(TestKeypairList, self).setUp() + + self.keypairs_mock.list.return_value = self.keypairs + + # Get the command object to test + self.cmd = keypair.ListKeypair(self.app, None) + + def test_keypair_list_no_options(self): + arglist = [] + verifylist = [ + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + + self.keypairs_mock.list.assert_called_with() + + self.assertEqual(self.columns, columns) + self.assertEqual(tuple(self.data), tuple(data)) + + +class TestKeypairShow(TestKeypair): + + keypair = compute_fakes.FakeKeypair.create_one_keypair() + + def setUp(self): + super(TestKeypairShow, self).setUp() + + self.keypairs_mock.get.return_value = self.keypair + + self.cmd = keypair.ShowKeypair(self.app, None) + + self.columns = ( + "fingerprint", + "name", + "user_id" + ) + + self.data = ( + self.keypair.fingerprint, + self.keypair.name, + self.keypair.user_id + ) + + def test_show_no_options(self): + + arglist = [] + verifylist = [] + + # Missing required args should boil here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_keypair_show(self): + # overwrite the setup one because we want to omit private_key + self.keypair = compute_fakes.FakeKeypair.create_one_keypair( + no_pri=True) + self.keypairs_mock.get.return_value = self.keypair + + self.data = ( + self.keypair.fingerprint, + self.keypair.name, + self.keypair.user_id + ) + + arglist = [ + self.keypair.name + ] + verifylist = [ + ('name', self.keypair.name) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_keypair_show_public(self): + + arglist = [ + '--public-key', + self.keypair.name + ] + verifylist = [ + ('public_key', True), + ('name', self.keypair.name) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual({}, columns) + self.assertEqual({}, data) From 6902a288f8e403cfa143e5c8946ad39bcb2dc396 Mon Sep 17 00:00:00 2001 From: jichenjc Date: Sat, 20 Feb 2016 06:32:13 +0800 Subject: [PATCH 0642/3095] [compute] Support restore server Server in soft-delete state can be restored, add this command. Change-Id: Id9d7246f89ae65273505f36dcb664996534ae986 --- doc/source/command-objects/server.rst | 15 +++++++++++++ doc/source/commands.rst | 2 +- openstackclient/compute/v2/server.py | 22 +++++++++++++++++++ .../tests/compute/v2/test_server.py | 20 +++++++++++++++++ .../add-restore-server-d8c73e0e83df17dd.yaml | 4 ++++ setup.cfg | 1 + 6 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/add-restore-server-d8c73e0e83df17dd.yaml diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index d50ad37edd..674172f007 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -514,6 +514,21 @@ process for the user: the first is to perform the resize, the second is to either confirm (verify) success and release the old server, or to declare a revert to release the new server and restart the old one. +server restore +-------------- + +Restore server(s) from soft-deleted state + +.. program:: server restore +.. code:: bash + + os server restore + [ ...] + +.. describe:: + + Server(s) to restore (name or ID) + server resume ------------- diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 2195720f11..9a24afa394 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -199,7 +199,7 @@ Those actions with an opposite action are noted in parens if applicable. * ``remove`` (``add``) - remove an object from a group of objects * ``rescue`` (``unrescue``) - reboot a server in a special rescue mode allowing access to the original disks * ``resize`` - change a server's flavor -* ``restore`` - restore a heat stack snapshot +* ``restore`` - restore a heat stack snapshot or restore a server in soft-deleted state * ``resume`` (``suspend``) - return one or more suspended servers to running state * ``revoke`` (``issue``) - revoke a token * ``save`` - download an object locally diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 4cb94822bd..7088dd4c36 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1273,6 +1273,28 @@ def take_action(self, parsed_args): compute_client.servers.revert_resize(server) +class RestoreServer(command.Command): + """Restore server(s)""" + + def get_parser(self, prog_name): + parser = super(RestoreServer, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + nargs='+', + help=_('Server(s) to restore (name or ID)'), + ) + return parser + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + for server in parsed_args.server: + utils.find_resource( + compute_client.servers, + server + ).restore() + + class ResumeServer(command.Command): """Resume server(s)""" diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index d379b173f4..722a51a2a6 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -998,6 +998,26 @@ def test_server_resize_revert(self): ) +class TestServerRestore(TestServer): + + def setUp(self): + super(TestServerRestore, self).setUp() + + # Get the command object to test + self.cmd = server.RestoreServer(self.app, None) + + # Set methods to be tested. + self.methods = { + 'restore': None, + } + + def test_server_restore_one_server(self): + self.run_method_with_servers('restore', 1) + + def test_server_restore_multi_servers(self): + self.run_method_with_servers('restore', 3) + + class TestServerResume(TestServer): def setUp(self): diff --git a/releasenotes/notes/add-restore-server-d8c73e0e83df17dd.yaml b/releasenotes/notes/add-restore-server-d8c73e0e83df17dd.yaml new file mode 100644 index 0000000000..09d3b58289 --- /dev/null +++ b/releasenotes/notes/add-restore-server-d8c73e0e83df17dd.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Add support for the ``server restore`` command. diff --git a/setup.cfg b/setup.cfg index 2bb2fa38f3..c16524e2be 100644 --- a/setup.cfg +++ b/setup.cfg @@ -121,6 +121,7 @@ openstack.compute.v2 = server_remove_volume = openstackclient.compute.v2.server:RemoveServerVolume server_rescue = openstackclient.compute.v2.server:RescueServer server_resize = openstackclient.compute.v2.server:ResizeServer + server_restore = openstackclient.compute.v2.server:RestoreServer server_resume = openstackclient.compute.v2.server:ResumeServer server_set = openstackclient.compute.v2.server:SetServer server_shelve = openstackclient.compute.v2.server:ShelveServer From 07242fca3bf7965fd18155a2b4dea54899f941aa Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 23 Feb 2016 19:00:14 +0800 Subject: [PATCH 0643/3095] Use update_parser_common() in ShowNetwork ShowNetwork inherits from NetworkAndComputeCommand. So we should use update_parser_common() in it, not overwrite parent's get_parser(). Change-Id: I21bb1407962344b9800fd31caee4b2582674fe24 --- openstackclient/network/v2/network.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index a634378f63..fd7ab8fbed 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -319,8 +319,7 @@ def take_action(self, parsed_args): class ShowNetwork(common.NetworkAndComputeShowOne): """Show network details""" - def get_parser(self, prog_name): - parser = super(ShowNetwork, self).get_parser(prog_name) + def update_parser_common(self, parser): parser.add_argument( 'network', metavar="", From ab40add0c6a46bbdbbd7d90764c4ea5076373a64 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 23 Feb 2016 20:19:14 +0800 Subject: [PATCH 0644/3095] Fix wrong return value in TestDeleteFloatingIPNetwork delete_ip() should return None, not the fake floating IP. Change-Id: I1476189a09a94c76c90f9a3986e3ae57dc66d796 --- openstackclient/tests/network/v2/test_floating_ip.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/tests/network/v2/test_floating_ip.py b/openstackclient/tests/network/v2/test_floating_ip.py index a29d691338..0d3b413e42 100644 --- a/openstackclient/tests/network/v2/test_floating_ip.py +++ b/openstackclient/tests/network/v2/test_floating_ip.py @@ -37,7 +37,7 @@ class TestDeleteFloatingIPNetwork(TestFloatingIPNetwork): def setUp(self): super(TestDeleteFloatingIPNetwork, self).setUp() - self.network.delete_ip = mock.Mock(return_value=self.floating_ip) + self.network.delete_ip = mock.Mock(return_value=None) self.network.find_ip = mock.Mock(return_value=self.floating_ip) # Get the command object to test From dccde70c57baf9266a795a54198238515d7fdda6 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Fri, 19 Feb 2016 10:19:28 -0600 Subject: [PATCH 0645/3095] Add "security group rule show" command Add the "os security group rule show" command which will use the SDK when neutron is enabled, and use the nova client when nova network is enabled. Change-Id: I41efaa4468ec15e4e86d74144cc72edc25a29024 Partial-Bug: #1519512 Implements: blueprint neutron-client --- .../command-objects/security-group-rule.rst | 15 +++ .../network/v2/test_security_group_rule.py | 7 ++ .../network/v2/security_group_rule.py | 86 ++++++++++++++ openstackclient/tests/network/v2/fakes.py | 14 ++- .../network/v2/test_security_group_rule.py | 112 ++++++++++++++++++ .../notes/bug-1519512-48624c5a32432a47.yaml | 5 + setup.cfg | 1 + 7 files changed, 236 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/bug-1519512-48624c5a32432a47.yaml diff --git a/doc/source/command-objects/security-group-rule.rst b/doc/source/command-objects/security-group-rule.rst index 50bc64aa8e..58e7a0a579 100644 --- a/doc/source/command-objects/security-group-rule.rst +++ b/doc/source/command-objects/security-group-rule.rst @@ -67,3 +67,18 @@ List security group rules .. describe:: List all rules in this security group (name or ID) + +security group rule show +------------------------ + +Display security group rule details + +.. program:: security group rule show +.. code:: bash + + os security group rule show + + +.. describe:: + + Security group rule to display (ID only) diff --git a/functional/tests/network/v2/test_security_group_rule.py b/functional/tests/network/v2/test_security_group_rule.py index e864b08f6c..9c0b66e830 100644 --- a/functional/tests/network/v2/test_security_group_rule.py +++ b/functional/tests/network/v2/test_security_group_rule.py @@ -57,3 +57,10 @@ def test_security_group_rule_list(self): self.SECURITY_GROUP_NAME + opts) self.assertIn(self.SECURITY_GROUP_RULE_ID, raw_output) + + def test_security_group_rule_show(self): + opts = self.get_show_opts(self.ID_FIELD) + raw_output = self.openstack('security group rule show ' + + self.SECURITY_GROUP_RULE_ID + + opts) + self.assertEqual(self.SECURITY_GROUP_RULE_ID + "\n", raw_output) diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index beeeaff73b..a61e3233df 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -13,9 +13,54 @@ """Security Group Rule action implementations""" +import six + +from openstackclient.common import exceptions +from openstackclient.common import utils from openstackclient.network import common +def _xform_security_group_rule(sgroup): + info = {} + info.update(sgroup) + from_port = info.pop('from_port') + to_port = info.pop('to_port') + if isinstance(from_port, int) and isinstance(to_port, int): + port_range = {'port_range': "%u:%u" % (from_port, to_port)} + elif from_port is None and to_port is None: + port_range = {'port_range': ""} + else: + port_range = {'port_range': "%s:%s" % (from_port, to_port)} + info.update(port_range) + if 'cidr' in info['ip_range']: + info['ip_range'] = info['ip_range']['cidr'] + else: + info['ip_range'] = '' + if info['ip_protocol'] is None: + info['ip_protocol'] = '' + elif info['ip_protocol'].lower() == 'icmp': + info['port_range'] = '' + group = info.pop('group') + if 'name' in group: + info['remote_security_group'] = group['name'] + else: + info['remote_security_group'] = '' + return info + + +def _format_security_group_rule_show(obj): + data = _xform_security_group_rule(obj) + return zip(*sorted(six.iteritems(data))) + + +def _get_columns(item): + columns = item.keys() + if 'tenant_id' in columns: + columns.remove('tenant_id') + columns.append('project_id') + return tuple(sorted(columns)) + + class DeleteSecurityGroupRule(common.NetworkAndComputeCommand): """Delete a security group rule""" @@ -33,3 +78,44 @@ def take_action_network(self, client, parsed_args): def take_action_compute(self, client, parsed_args): client.security_group_rules.delete(parsed_args.rule) + + +class ShowSecurityGroupRule(common.NetworkAndComputeShowOne): + """Display security group rule details""" + + def update_parser_common(self, parser): + parser.add_argument( + 'rule', + metavar="", + help="Security group rule to display (ID only)" + ) + return parser + + def take_action_network(self, client, parsed_args): + obj = client.find_security_group_rule(parsed_args.rule, + ignore_missing=False) + columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns) + return (columns, data) + + def take_action_compute(self, client, parsed_args): + # NOTE(rtheis): Unfortunately, compute does not have an API + # to get or list security group rules so parse through the + # security groups to find all accessible rules in search of + # the requested rule. + obj = None + security_group_rules = [] + for security_group in client.security_groups.list(): + security_group_rules.extend(security_group.rules) + for security_group_rule in security_group_rules: + if parsed_args.rule == str(security_group_rule.get('id')): + obj = security_group_rule + break + + if obj is None: + msg = "Could not find security group rule " \ + "with ID %s" % parsed_args.rule + raise exceptions.CommandError(msg) + + # NOTE(rtheis): Format security group rule + return _format_security_group_rule_show(obj) diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index fe31aab956..63b929e01c 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -479,15 +479,13 @@ def create_one_security_group_rule(attrs={}, methods={}): :param Dictionary methods: A dictionary with all methods :return: - A FakeResource object, with id, name, etc. + A FakeResource object, with id, etc. """ # Set default attributes. security_group_rule_attrs = { - 'description': 'security-group-rule-desc-' + uuid.uuid4().hex, 'direction': 'ingress', 'ethertype': 'IPv4', 'id': 'security-group-rule-id-' + uuid.uuid4().hex, - 'name': 'security-group-rule-name-' + uuid.uuid4().hex, 'port_range_max': None, 'port_range_min': None, 'protocol': None, @@ -501,7 +499,11 @@ def create_one_security_group_rule(attrs={}, methods={}): security_group_rule_attrs.update(attrs) # Set default methods. - security_group_rule_methods = {} + security_group_rule_methods = { + 'keys': ['direction', 'ethertype', 'id', 'port_range_max', + 'port_range_min', 'protocol', 'remote_group_id', + 'remote_ip_prefix', 'security_group_id', 'tenant_id'], + } # Overwrite default methods. security_group_rule_methods.update(methods) @@ -510,6 +512,10 @@ def create_one_security_group_rule(attrs={}, methods={}): info=copy.deepcopy(security_group_rule_attrs), methods=copy.deepcopy(security_group_rule_methods), loaded=True) + + # Set attributes with special mappings. + security_group_rule.project_id = security_group_rule_attrs['tenant_id'] + return security_group_rule @staticmethod diff --git a/openstackclient/tests/network/v2/test_security_group_rule.py b/openstackclient/tests/network/v2/test_security_group_rule.py index c6ef388461..db15d0e266 100644 --- a/openstackclient/tests/network/v2/test_security_group_rule.py +++ b/openstackclient/tests/network/v2/test_security_group_rule.py @@ -11,11 +11,14 @@ # under the License. # +import copy import mock from openstackclient.network.v2 import security_group_rule from openstackclient.tests.compute.v2 import fakes as compute_fakes +from openstackclient.tests import fakes from openstackclient.tests.network.v2 import fakes as network_fakes +from openstackclient.tests import utils as tests_utils class TestSecurityGroupRuleNetwork(network_fakes.TestNetworkV2): @@ -98,3 +101,112 @@ def test_security_group_rule_delete(self): self.compute.security_group_rules.delete.assert_called_with( self._security_group_rule.id) self.assertIsNone(result) + + +class TestShowSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): + + # The security group rule to be shown. + _security_group_rule = \ + network_fakes.FakeSecurityGroupRule.create_one_security_group_rule() + + columns = ( + 'direction', + 'ethertype', + 'id', + 'port_range_max', + 'port_range_min', + 'project_id', + 'protocol', + 'remote_group_id', + 'remote_ip_prefix', + 'security_group_id', + ) + + data = ( + _security_group_rule.direction, + _security_group_rule.ethertype, + _security_group_rule.id, + _security_group_rule.port_range_max, + _security_group_rule.port_range_min, + _security_group_rule.project_id, + _security_group_rule.protocol, + _security_group_rule.remote_group_id, + _security_group_rule.remote_ip_prefix, + _security_group_rule.security_group_id, + ) + + def setUp(self): + super(TestShowSecurityGroupRuleNetwork, self).setUp() + + self.network.find_security_group_rule = mock.Mock( + return_value=self._security_group_rule) + + # Get the command object to test + self.cmd = security_group_rule.ShowSecurityGroupRule( + self.app, self.namespace) + + def test_show_no_options(self): + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, [], []) + + def test_show_all_options(self): + arglist = [ + self._security_group_rule.id, + ] + verifylist = [ + ('rule', self._security_group_rule.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.find_security_group_rule.assert_called_with( + self._security_group_rule.id, ignore_missing=False) + self.assertEqual(tuple(self.columns), columns) + self.assertEqual(self.data, data) + + +class TestShowSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): + + # The security group rule to be shown. + _security_group_rule = \ + compute_fakes.FakeSecurityGroupRule.create_one_security_group_rule() + + columns, data = \ + security_group_rule._format_security_group_rule_show( + _security_group_rule._info) + + def setUp(self): + super(TestShowSecurityGroupRuleCompute, self).setUp() + + self.app.client_manager.network_endpoint_enabled = False + + # Build a security group fake customized for this test. + security_group_rules = [self._security_group_rule._info] + security_group = fakes.FakeResource( + info=copy.deepcopy({'rules': security_group_rules}), + loaded=True) + security_group.rules = security_group_rules + self.compute.security_groups.list.return_value = [security_group] + + # Get the command object to test + self.cmd = security_group_rule.ShowSecurityGroupRule(self.app, None) + + def test_show_no_options(self): + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, [], []) + + def test_show_all_options(self): + arglist = [ + self._security_group_rule.id, + ] + verifylist = [ + ('rule', self._security_group_rule.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.compute.security_groups.list.assert_called_with() + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) diff --git a/releasenotes/notes/bug-1519512-48624c5a32432a47.yaml b/releasenotes/notes/bug-1519512-48624c5a32432a47.yaml new file mode 100644 index 0000000000..ba6b27377b --- /dev/null +++ b/releasenotes/notes/bug-1519512-48624c5a32432a47.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add support for ``security group rule show`` command. + [Bug `1519512 `_] diff --git a/setup.cfg b/setup.cfg index 4cc449a022..4f87bff1b0 100644 --- a/setup.cfg +++ b/setup.cfg @@ -340,6 +340,7 @@ openstack.network.v2 = router_show = openstackclient.network.v2.router:ShowRouter security_group_delete = openstackclient.network.v2.security_group:DeleteSecurityGroup security_group_rule_delete = openstackclient.network.v2.security_group_rule:DeleteSecurityGroupRule + security_group_rule_show = openstackclient.network.v2.security_group_rule:ShowSecurityGroupRule subnet_list = openstackclient.network.v2.subnet:ListSubnet subnet_pool_delete = openstackclient.network.v2.subnet_pool:DeleteSubnetPool subnet_pool_list = openstackclient.network.v2.subnet_pool:ListSubnetPool From f49f0fead2933ace4cb85c70bd14d13d0c479e6a Mon Sep 17 00:00:00 2001 From: Brandon Palm Date: Tue, 23 Feb 2016 10:38:58 -0600 Subject: [PATCH 0646/3095] Fixed a bunch of spacing Nothing too complicated here. I fixed a bunch of spacing issues that I saw in OSC. Change-Id: I935ab48e7c5bac5f88ecdb3a05f73fb44fc9f41d --- openstackclient/common/clientmanager.py | 1 + openstackclient/common/command.py | 1 + openstackclient/common/exceptions.py | 1 + openstackclient/common/quota.py | 4 +- openstackclient/compute/v2/flavor.py | 6 +- openstackclient/identity/client.py | 3 +- .../identity/v3/role_assignment.py | 2 +- openstackclient/identity/v3/unscoped_saml.py | 1 + openstackclient/image/v1/image.py | 8 +- openstackclient/network/v2/port.py | 4 +- .../tests/common/test_clientmanager.py | 2 + openstackclient/tests/common/test_command.py | 1 + .../tests/common/test_commandmanager.py | 2 + openstackclient/tests/common/test_logs.py | 2 + openstackclient/tests/common/test_timing.py | 2 +- openstackclient/tests/common/test_utils.py | 1 + openstackclient/tests/compute/v2/fakes.py | 2 + openstackclient/tests/fakes.py | 9 ++ openstackclient/tests/identity/v2_0/fakes.py | 2 + openstackclient/tests/identity/v3/fakes.py | 9 ++ .../tests/identity/v3/test_credential.py | 1 + .../identity/v3/test_identity_provider.py | 5 + .../tests/identity/v3/test_mappings.py | 116 +++++++++--------- .../tests/identity/v3/test_role_assignment.py | 2 +- .../identity/v3/test_service_provider.py | 2 + openstackclient/tests/image/v1/fakes.py | 2 + openstackclient/tests/image/v2/fakes.py | 2 + openstackclient/tests/image/v2/test_image.py | 2 +- openstackclient/tests/network/test_common.py | 7 ++ openstackclient/tests/network/v2/fakes.py | 2 + .../tests/network/v2/test_subnet.py | 1 + .../tests/network/v2/test_subnet_pool.py | 1 + openstackclient/tests/object/v1/fakes.py | 1 + .../tests/object/v1/test_container.py | 1 + .../tests/object/v1/test_object.py | 1 + .../tests/object/v1/test_object_all.py | 1 + openstackclient/tests/test_shell.py | 7 ++ openstackclient/tests/utils.py | 1 + openstackclient/tests/volume/v1/fakes.py | 3 + .../tests/volume/v1/test_qos_specs.py | 7 ++ openstackclient/tests/volume/v2/fakes.py | 2 + .../tests/volume/v2/test_backup.py | 4 + .../tests/volume/v2/test_qos_specs.py | 7 ++ .../tests/volume/v2/test_snapshot.py | 7 +- openstackclient/tests/volume/v2/test_type.py | 6 +- .../tests/volume/v2/test_volume.py | 3 + openstackclient/volume/v2/volume.py | 2 +- 47 files changed, 183 insertions(+), 76 deletions(-) diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index fd88bdecb9..938dd05cee 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -37,6 +37,7 @@ class ClientCache(object): """Descriptor class for caching created client handles.""" + def __init__(self, factory): self.factory = factory self._handle = None diff --git a/openstackclient/common/command.py b/openstackclient/common/command.py index 13b0bcc2c4..fee4559e75 100644 --- a/openstackclient/common/command.py +++ b/openstackclient/common/command.py @@ -22,6 +22,7 @@ class CommandMeta(abc.ABCMeta): + def __new__(mcs, name, bases, cls_dict): if 'log' not in cls_dict: cls_dict['log'] = logging.getLogger( diff --git a/openstackclient/common/exceptions.py b/openstackclient/common/exceptions.py index 8ec4993195..5f5f5ab1a6 100644 --- a/openstackclient/common/exceptions.py +++ b/openstackclient/common/exceptions.py @@ -41,6 +41,7 @@ class UnsupportedVersion(Exception): class ClientException(Exception): """The base exception class for all exceptions this library raises.""" + def __init__(self, code, message=None, details=None): self.code = code self.message = message or self.__class__.message diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index f208948e8f..b3d4c3b618 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -169,7 +169,7 @@ def get_compute_volume_quota(self, client, parsed_args): project = utils.find_resource( identity_client.projects, parsed_args.project, - ).id + ).id try: if parsed_args.quota_class: @@ -193,7 +193,7 @@ def get_network_quota(self, parsed_args): project = utils.find_resource( identity_client.projects, parsed_args.project, - ).id + ).id return self.app.client_manager.network.get_quota(project) else: return {} diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 093592cd82..0308d9409b 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -149,20 +149,20 @@ def get_parser(self, prog_name): action="store_true", default=True, help="List only public flavors (default)", - ) + ) public_group.add_argument( "--private", dest="public", action="store_false", help="List only private flavors", - ) + ) public_group.add_argument( "--all", dest="all", action="store_true", default=False, help="List all flavors, whether public or private", - ) + ) parser.add_argument( '--long', action='store_true', diff --git a/openstackclient/identity/client.py b/openstackclient/identity/client.py index bd882ce80b..c166e66a81 100644 --- a/openstackclient/identity/client.py +++ b/openstackclient/identity/client.py @@ -53,7 +53,7 @@ def make_client(instance): session=instance.session, region_name=instance._region_name, **kwargs - ) + ) return client @@ -72,6 +72,7 @@ def build_option_parser(parser): class IdentityClientv2(identity_client_v2.Client): """Tweak the earlier client class to deal with some changes""" + def __getattr__(self, name): # Map v3 'projects' back to v2 'tenants' if name == "projects": diff --git a/openstackclient/identity/v3/role_assignment.py b/openstackclient/identity/v3/role_assignment.py index e2b0fe1f06..a1418a8221 100644 --- a/openstackclient/identity/v3/role_assignment.py +++ b/openstackclient/identity/v3/role_assignment.py @@ -134,7 +134,7 @@ def take_action(self, parsed_args): if 'project' in scope: if include_names: prj = '@'.join([scope['project']['name'], - scope['project']['domain']['name']]) + scope['project']['domain']['name']]) setattr(assignment, 'project', prj) else: setattr(assignment, 'project', scope['project']['id']) diff --git a/openstackclient/identity/v3/unscoped_saml.py b/openstackclient/identity/v3/unscoped_saml.py index a42637ddc3..8e2616a691 100644 --- a/openstackclient/identity/v3/unscoped_saml.py +++ b/openstackclient/identity/v3/unscoped_saml.py @@ -27,6 +27,7 @@ def auth_with_unscoped_saml(func): """Check the unscoped federated context""" + def _decorated(self, parsed_args): auth_plugin_name = self.app.client_manager.auth_plugin_name if auth_plugin_name in UNSCOPED_AUTH_PLUGINS: diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index 9cc5facd43..6e172bce21 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -265,8 +265,8 @@ def take_action(self, parsed_args): finally: # Clean up open files - make sure data isn't a string if ('data' in kwargs and hasattr(kwargs['data'], 'close') and - kwargs['data'] != sys.stdin): - kwargs['data'].close() + kwargs['data'] != sys.stdin): + kwargs['data'].close() info.update(image._info) info['properties'] = utils.format_dict(info.get('properties', {})) @@ -697,8 +697,8 @@ def take_action(self, parsed_args): finally: # Clean up open files - make sure data isn't a string if ('data' in kwargs and hasattr(kwargs['data'], 'close') and - kwargs['data'] != sys.stdin): - kwargs['data'].close() + kwargs['data'] != sys.stdin): + kwargs['data'].close() class ShowImage(command.ShowOne): diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 46cb031f91..19b3701d87 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -26,9 +26,9 @@ def _format_admin_state(state): 'allowed_address_pairs': utils.format_list_of_dicts, 'binding_profile': utils.format_dict, 'binding_vif_details': utils.format_dict, - 'dns_assignment': utils.format_list_of_dicts, + 'dns_assignment': utils.format_list_of_dicts, 'extra_dhcp_opts': utils.format_list_of_dicts, - 'fixed_ips': utils.format_list_of_dicts, + 'fixed_ips': utils.format_list_of_dicts, 'security_groups': utils.format_list, } diff --git a/openstackclient/tests/common/test_clientmanager.py b/openstackclient/tests/common/test_clientmanager.py index ef46f61c56..2bd9e7836b 100644 --- a/openstackclient/tests/common/test_clientmanager.py +++ b/openstackclient/tests/common/test_clientmanager.py @@ -47,6 +47,7 @@ def __init__(self): class FakeOptions(object): + def __init__(self, **kwargs): for option in auth.OPTIONS_LIST: setattr(self, option.replace('-', '_'), None) @@ -71,6 +72,7 @@ def test_singleton(self): class TestClientManager(utils.TestCase): + def setUp(self): super(TestClientManager, self).setUp() self.mock = mock.Mock() diff --git a/openstackclient/tests/common/test_command.py b/openstackclient/tests/common/test_command.py index 1b2584bdde..7467d9eba4 100644 --- a/openstackclient/tests/common/test_command.py +++ b/openstackclient/tests/common/test_command.py @@ -19,6 +19,7 @@ class FakeCommand(command.Command): + def take_action(self, parsed_args): pass diff --git a/openstackclient/tests/common/test_commandmanager.py b/openstackclient/tests/common/test_commandmanager.py index 37dc90feb6..e2b274dcb0 100644 --- a/openstackclient/tests/common/test_commandmanager.py +++ b/openstackclient/tests/common/test_commandmanager.py @@ -20,6 +20,7 @@ class FakeCommand(object): + @classmethod def load(cls): return cls @@ -48,6 +49,7 @@ def load_commands(self, namespace): class TestCommandManager(utils.TestCase): + def test_add_command_group(self): mgr = FakeCommandManager('test') diff --git a/openstackclient/tests/common/test_logs.py b/openstackclient/tests/common/test_logs.py index a319533ace..0386cdfda0 100644 --- a/openstackclient/tests/common/test_logs.py +++ b/openstackclient/tests/common/test_logs.py @@ -66,6 +66,7 @@ def test_set_warning_filter(self, simplefilter): class TestFileFormatter(utils.TestCase): + def test_nothing(self): formatter = logs._FileFormatter() self.assertEqual(('%(asctime)s.%(msecs)03d %(process)d %(levelname)s ' @@ -93,6 +94,7 @@ def test_config(self): class TestLogConfigurator(utils.TestCase): + def setUp(self): super(TestLogConfigurator, self).setUp() self.options = mock.Mock() diff --git a/openstackclient/tests/common/test_timing.py b/openstackclient/tests/common/test_timing.py index c4c738b259..e33bb7ae27 100644 --- a/openstackclient/tests/common/test_timing.py +++ b/openstackclient/tests/common/test_timing.py @@ -75,7 +75,7 @@ def test_timing_list_no_data(self): def test_timing_list(self): self.app.timing_data = [( timing_url, - datetime.timedelta(microseconds=timing_elapsed*1000000), + datetime.timedelta(microseconds=timing_elapsed * 1000000), )] arglist = [] diff --git a/openstackclient/tests/common/test_utils.py b/openstackclient/tests/common/test_utils.py index da7ce06379..95bce45871 100644 --- a/openstackclient/tests/common/test_utils.py +++ b/openstackclient/tests/common/test_utils.py @@ -250,6 +250,7 @@ class NoUniqueMatch(Exception): class TestFindResource(test_utils.TestCase): + def setUp(self): super(TestFindResource, self).setUp() self.name = 'legos' diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index d2341ccc43..b106919409 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -87,6 +87,7 @@ class FakeComputev2Client(object): + def __init__(self, **kwargs): self.aggregates = mock.Mock() self.aggregates.resource_class = fakes.FakeResource(None, {}) @@ -142,6 +143,7 @@ def __init__(self, **kwargs): class TestComputev2(utils.TestCommand): + def setUp(self): super(TestComputev2, self).setUp() diff --git a/openstackclient/tests/fakes.py b/openstackclient/tests/fakes.py index 718dff694b..9fdcc7e923 100644 --- a/openstackclient/tests/fakes.py +++ b/openstackclient/tests/fakes.py @@ -51,6 +51,7 @@ class FakeStdout(object): + def __init__(self): self.content = [] @@ -65,6 +66,7 @@ def make_string(self): class FakeLog(object): + def __init__(self): self.messages = {} @@ -85,6 +87,7 @@ def critical(self, msg): class FakeApp(object): + def __init__(self, _stdout, _log): self.stdout = _stdout self.client_manager = None @@ -95,12 +98,14 @@ def __init__(self, _stdout, _log): class FakeClient(object): + def __init__(self, **kwargs): self.endpoint = kwargs['endpoint'] self.token = kwargs['token'] class FakeClientManager(object): + def __init__(self): self.compute = None self.identity = None @@ -129,12 +134,14 @@ def is_network_endpoint_enabled(self): class FakeModule(object): + def __init__(self, name, version): self.name = name self.__version__ = version class FakeResource(object): + def __init__(self, manager=None, info={}, loaded=False, methods={}): """Set attributes and methods for a resource. @@ -178,6 +185,7 @@ def __repr__(self): class FakeResponse(requests.Response): + def __init__(self, headers={}, status_code=200, data=None, encoding=None): super(FakeResponse, self).__init__() @@ -190,6 +198,7 @@ def __init__(self, headers={}, status_code=200, data=None, encoding=None): class FakeModel(dict): + def __getattr__(self, key): try: return self[key] diff --git a/openstackclient/tests/identity/v2_0/fakes.py b/openstackclient/tests/identity/v2_0/fakes.py index 565606c161..b37bd9da2f 100644 --- a/openstackclient/tests/identity/v2_0/fakes.py +++ b/openstackclient/tests/identity/v2_0/fakes.py @@ -128,6 +128,7 @@ class FakeIdentityv2Client(object): + def __init__(self, **kwargs): self.roles = mock.Mock() self.roles.resource_class = fakes.FakeResource(None, {}) @@ -157,6 +158,7 @@ def __getattr__(self, name): class TestIdentityv2(utils.TestCommand): + def setUp(self): super(TestIdentityv2, self).setUp() diff --git a/openstackclient/tests/identity/v3/fakes.py b/openstackclient/tests/identity/v3/fakes.py index 420604f180..1422166a90 100644 --- a/openstackclient/tests/identity/v3/fakes.py +++ b/openstackclient/tests/identity/v3/fakes.py @@ -420,6 +420,7 @@ class FakeAuth(object): + def __init__(self, auth_method_class=None): self._auth_method_class = auth_method_class @@ -428,11 +429,13 @@ def get_token(self, *args, **kwargs): class FakeSession(object): + def __init__(self, **kwargs): self.auth = FakeAuth() class FakeIdentityv3Client(object): + def __init__(self, **kwargs): self.domains = mock.Mock() self.domains.resource_class = fakes.FakeResource(None, {}) @@ -468,6 +471,7 @@ def __init__(self, **kwargs): class FakeFederationManager(object): + def __init__(self, **kwargs): self.identity_providers = mock.Mock() self.identity_providers.resource_class = fakes.FakeResource(None, {}) @@ -484,12 +488,14 @@ def __init__(self, **kwargs): class FakeFederatedClient(FakeIdentityv3Client): + def __init__(self, **kwargs): super(FakeFederatedClient, self).__init__(**kwargs) self.federation = FakeFederationManager() class FakeOAuth1Client(FakeIdentityv3Client): + def __init__(self, **kwargs): super(FakeOAuth1Client, self).__init__(**kwargs) @@ -502,6 +508,7 @@ def __init__(self, **kwargs): class TestIdentityv3(utils.TestCommand): + def setUp(self): super(TestIdentityv3, self).setUp() @@ -512,6 +519,7 @@ def setUp(self): class TestFederatedIdentity(utils.TestCommand): + def setUp(self): super(TestFederatedIdentity, self).setUp() @@ -522,6 +530,7 @@ def setUp(self): class TestOAuth1(utils.TestCommand): + def setUp(self): super(TestOAuth1, self).setUp() diff --git a/openstackclient/tests/identity/v3/test_credential.py b/openstackclient/tests/identity/v3/test_credential.py index e2e690c3b4..afff7b6c09 100644 --- a/openstackclient/tests/identity/v3/test_credential.py +++ b/openstackclient/tests/identity/v3/test_credential.py @@ -46,6 +46,7 @@ def setUp(self): class TestCredentialSet(TestCredential): + def setUp(self): super(TestCredentialSet, self).setUp() self.cmd = credential.SetCredential(self.app, None) diff --git a/openstackclient/tests/identity/v3/test_identity_provider.py b/openstackclient/tests/identity/v3/test_identity_provider.py index 7591151091..ddad6ffb88 100644 --- a/openstackclient/tests/identity/v3/test_identity_provider.py +++ b/openstackclient/tests/identity/v3/test_identity_provider.py @@ -368,6 +368,7 @@ def setUp(self): def test_identity_provider_set_description(self): """Set Identity Provider's description. """ + def prepare(self): """Prepare fake return objects before the test is executed""" updated_idp = copy.deepcopy(identity_fakes.IDENTITY_PROVIDER) @@ -412,6 +413,7 @@ def test_identity_provider_disable(self): Set Identity Provider's ``enabled`` attribute to False. """ + def prepare(self): """Prepare fake return objects before the test is executed""" updated_idp = copy.deepcopy(identity_fakes.IDENTITY_PROVIDER) @@ -459,6 +461,7 @@ def test_identity_provider_enable(self): Set Identity Provider's ``enabled`` attribute to True. """ + def prepare(self): """Prepare fake return objects before the test is executed""" resources = fakes.FakeResource( @@ -495,6 +498,7 @@ def test_identity_provider_replace_remote_ids(self): Set Identity Provider's ``enabled`` attribute to True. """ + def prepare(self): """Prepare fake return objects before the test is executed""" self.new_remote_id = 'new_entity' @@ -540,6 +544,7 @@ def test_identity_provider_replace_remote_ids_file(self): Set Identity Provider's ``enabled`` attribute to True. """ + def prepare(self): """Prepare fake return objects before the test is executed""" self.new_remote_id = 'new_entity' diff --git a/openstackclient/tests/identity/v3/test_mappings.py b/openstackclient/tests/identity/v3/test_mappings.py index f6e888858f..e811c0b644 100644 --- a/openstackclient/tests/identity/v3/test_mappings.py +++ b/openstackclient/tests/identity/v3/test_mappings.py @@ -33,71 +33,74 @@ def setUp(self): class TestMappingCreate(TestMapping): - def setUp(self): - super(TestMappingCreate, self).setUp() - self.mapping_mock.create.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.MAPPING_RESPONSE), - loaded=True - ) - self.cmd = mapping.CreateMapping(self.app, None) - - def test_create_mapping(self): - arglist = [ - '--rules', identity_fakes.mapping_rules_file_path, - identity_fakes.mapping_id - ] - verifylist = [ - ('mapping', identity_fakes.mapping_id), - ('rules', identity_fakes.mapping_rules_file_path) - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - mocker = mock.Mock() - mocker.return_value = identity_fakes.MAPPING_RULES - with mock.patch("openstackclient.identity.v3.mapping." - "CreateMapping._read_rules", mocker): - columns, data = self.cmd.take_action(parsed_args) - - self.mapping_mock.create.assert_called_with( - mapping_id=identity_fakes.mapping_id, - rules=identity_fakes.MAPPING_RULES) - - collist = ('id', 'rules') - self.assertEqual(collist, columns) - - datalist = (identity_fakes.mapping_id, - identity_fakes.MAPPING_RULES) - self.assertEqual(datalist, data) + + def setUp(self): + super(TestMappingCreate, self).setUp() + self.mapping_mock.create.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.MAPPING_RESPONSE), + loaded=True + ) + self.cmd = mapping.CreateMapping(self.app, None) + + def test_create_mapping(self): + arglist = [ + '--rules', identity_fakes.mapping_rules_file_path, + identity_fakes.mapping_id + ] + verifylist = [ + ('mapping', identity_fakes.mapping_id), + ('rules', identity_fakes.mapping_rules_file_path) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + mocker = mock.Mock() + mocker.return_value = identity_fakes.MAPPING_RULES + with mock.patch("openstackclient.identity.v3.mapping." + "CreateMapping._read_rules", mocker): + columns, data = self.cmd.take_action(parsed_args) + + self.mapping_mock.create.assert_called_with( + mapping_id=identity_fakes.mapping_id, + rules=identity_fakes.MAPPING_RULES) + + collist = ('id', 'rules') + self.assertEqual(collist, columns) + + datalist = (identity_fakes.mapping_id, + identity_fakes.MAPPING_RULES) + self.assertEqual(datalist, data) class TestMappingDelete(TestMapping): - def setUp(self): - super(TestMappingDelete, self).setUp() - self.mapping_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.MAPPING_RESPONSE), - loaded=True) - self.mapping_mock.delete.return_value = None - self.cmd = mapping.DeleteMapping(self.app, None) + def setUp(self): + super(TestMappingDelete, self).setUp() + self.mapping_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.MAPPING_RESPONSE), + loaded=True) - def test_delete_mapping(self): - arglist = [ - identity_fakes.mapping_id - ] - verifylist = [ - ('mapping', identity_fakes.mapping_id) - ] + self.mapping_mock.delete.return_value = None + self.cmd = mapping.DeleteMapping(self.app, None) - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) - self.mapping_mock.delete.assert_called_with( - identity_fakes.mapping_id) + def test_delete_mapping(self): + arglist = [ + identity_fakes.mapping_id + ] + verifylist = [ + ('mapping', identity_fakes.mapping_id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.mapping_mock.delete.assert_called_with( + identity_fakes.mapping_id) class TestMappingList(TestMapping): + def setUp(self): super(TestMappingList, self).setUp() self.mapping_mock.get.return_value = fakes.FakeResource( @@ -141,6 +144,7 @@ def test_mapping_list(self): class TestMappingShow(TestMapping): + def setUp(self): super(TestMappingShow, self).setUp() diff --git a/openstackclient/tests/identity/v3/test_role_assignment.py b/openstackclient/tests/identity/v3/test_role_assignment.py index 5def76327a..8956b74fb3 100644 --- a/openstackclient/tests/identity/v3/test_role_assignment.py +++ b/openstackclient/tests/identity/v3/test_role_assignment.py @@ -557,7 +557,7 @@ def test_role_assignment_list_include_names(self): '@'.join([identity_fakes.user_name, identity_fakes.domain_name]), '', '@'.join([identity_fakes.project_name, - identity_fakes.domain_name]), + identity_fakes.domain_name]), '', False ), (identity_fakes.role_name, diff --git a/openstackclient/tests/identity/v3/test_service_provider.py b/openstackclient/tests/identity/v3/test_service_provider.py index fb7576f1cc..39f779d0f7 100644 --- a/openstackclient/tests/identity/v3/test_service_provider.py +++ b/openstackclient/tests/identity/v3/test_service_provider.py @@ -305,6 +305,7 @@ def test_service_provider_disable(self): Set Service Provider's ``enabled`` attribute to False. """ + def prepare(self): """Prepare fake return objects before the test is executed""" updated_sp = copy.deepcopy(service_fakes.SERVICE_PROVIDER) @@ -343,6 +344,7 @@ def test_service_provider_enable(self): Set Service Provider's ``enabled`` attribute to True. """ + def prepare(self): """Prepare fake return objects before the test is executed""" resources = fakes.FakeResource( diff --git a/openstackclient/tests/image/v1/fakes.py b/openstackclient/tests/image/v1/fakes.py index 95a8a39ceb..1e49f17325 100644 --- a/openstackclient/tests/image/v1/fakes.py +++ b/openstackclient/tests/image/v1/fakes.py @@ -53,6 +53,7 @@ class FakeImagev1Client(object): + def __init__(self, **kwargs): self.images = mock.Mock() self.images.resource_class = fakes.FakeResource(None, {}) @@ -61,6 +62,7 @@ def __init__(self, **kwargs): class TestImagev1(utils.TestCommand): + def setUp(self): super(TestImagev1, self).setUp() diff --git a/openstackclient/tests/image/v2/fakes.py b/openstackclient/tests/image/v2/fakes.py index 5441a3e2ab..3555d2d4ed 100644 --- a/openstackclient/tests/image/v2/fakes.py +++ b/openstackclient/tests/image/v2/fakes.py @@ -148,6 +148,7 @@ class FakeImagev2Client(object): + def __init__(self, **kwargs): self.images = mock.Mock() self.images.resource_class = fakes.FakeResource(None, {}) @@ -158,6 +159,7 @@ def __init__(self, **kwargs): class TestImagev2(utils.TestCommand): + def setUp(self): super(TestImagev2, self).setUp() diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index de37512f07..b8e137f81c 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -381,7 +381,7 @@ def test_add_project_to_image_no_option(self): arglist = [ image_fakes.image_id, identity_fakes.project_id, - ] + ] verifylist = [ ('image', image_fakes.image_id), ('project', identity_fakes.project_id), diff --git a/openstackclient/tests/network/test_common.py b/openstackclient/tests/network/test_common.py index 4700c66a9e..48608734a2 100644 --- a/openstackclient/tests/network/test_common.py +++ b/openstackclient/tests/network/test_common.py @@ -46,6 +46,7 @@ def _add_compute_argument(parser): class FakeNetworkAndComputeCommand(common.NetworkAndComputeCommand): + def update_parser_common(self, parser): return _add_common_argument(parser) @@ -63,6 +64,7 @@ def take_action_compute(self, client, parsed_args): class FakeNetworkAndComputeLister(common.NetworkAndComputeLister): + def update_parser_common(self, parser): return _add_common_argument(parser) @@ -80,6 +82,7 @@ def take_action_compute(self, client, parsed_args): class FakeNetworkAndComputeShowOne(common.NetworkAndComputeShowOne): + def update_parser_common(self, parser): return _add_common_argument(parser) @@ -97,6 +100,7 @@ def take_action_compute(self, client, parsed_args): class TestNetworkAndCompute(utils.TestCommand): + def setUp(self): super(TestNetworkAndCompute, self).setUp() @@ -150,18 +154,21 @@ def test_take_action_compute(self): class TestNetworkAndComputeCommand(TestNetworkAndCompute): + def setUp(self): super(TestNetworkAndComputeCommand, self).setUp() self.cmd = FakeNetworkAndComputeCommand(self.app, self.namespace) class TestNetworkAndComputeLister(TestNetworkAndCompute): + def setUp(self): super(TestNetworkAndComputeLister, self).setUp() self.cmd = FakeNetworkAndComputeLister(self.app, self.namespace) class TestNetworkAndComputeShowOne(TestNetworkAndCompute): + def setUp(self): super(TestNetworkAndComputeShowOne, self).setUp() self.cmd = FakeNetworkAndComputeShowOne(self.app, self.namespace) diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index c24410e120..ea35a53758 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -51,11 +51,13 @@ def create_extension(): class FakeNetworkV2Client(object): + def __init__(self, **kwargs): self.extensions = mock.Mock(return_value=[create_extension()]) class TestNetworkV2(utils.TestCommand): + def setUp(self): super(TestNetworkV2, self).setUp() diff --git a/openstackclient/tests/network/v2/test_subnet.py b/openstackclient/tests/network/v2/test_subnet.py index c02dc40736..e1e663f462 100644 --- a/openstackclient/tests/network/v2/test_subnet.py +++ b/openstackclient/tests/network/v2/test_subnet.py @@ -20,6 +20,7 @@ class TestSubnet(network_fakes.TestNetworkV2): + def setUp(self): super(TestSubnet, self).setUp() diff --git a/openstackclient/tests/network/v2/test_subnet_pool.py b/openstackclient/tests/network/v2/test_subnet_pool.py index 0ee5a7145e..c4e3340de7 100644 --- a/openstackclient/tests/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/network/v2/test_subnet_pool.py @@ -20,6 +20,7 @@ class TestSubnetPool(network_fakes.TestNetworkV2): + def setUp(self): super(TestSubnetPool, self).setUp() diff --git a/openstackclient/tests/object/v1/fakes.py b/openstackclient/tests/object/v1/fakes.py index 986ab2f30b..b9e86db759 100644 --- a/openstackclient/tests/object/v1/fakes.py +++ b/openstackclient/tests/object/v1/fakes.py @@ -76,6 +76,7 @@ class TestObjectv1(utils.TestCommand): + def setUp(self): super(TestObjectv1, self).setUp() diff --git a/openstackclient/tests/object/v1/test_container.py b/openstackclient/tests/object/v1/test_container.py index 6982295de3..41bc6e8c6d 100644 --- a/openstackclient/tests/object/v1/test_container.py +++ b/openstackclient/tests/object/v1/test_container.py @@ -26,6 +26,7 @@ class FakeClient(object): + def __init__(self, endpoint=None, **kwargs): self.endpoint = AUTH_URL self.token = AUTH_TOKEN diff --git a/openstackclient/tests/object/v1/test_object.py b/openstackclient/tests/object/v1/test_object.py index 990e4f46fa..e11f78a130 100644 --- a/openstackclient/tests/object/v1/test_object.py +++ b/openstackclient/tests/object/v1/test_object.py @@ -26,6 +26,7 @@ class TestObject(object_fakes.TestObjectv1): + def setUp(self): super(TestObject, self).setUp() self.app.client_manager.object_store = object_store.APIv1( diff --git a/openstackclient/tests/object/v1/test_object_all.py b/openstackclient/tests/object/v1/test_object_all.py index 89286b00e4..2a1bf05905 100644 --- a/openstackclient/tests/object/v1/test_object_all.py +++ b/openstackclient/tests/object/v1/test_object_all.py @@ -20,6 +20,7 @@ class TestObjectAll(object_fakes.TestObjectv1): + def setUp(self): super(TestObjectAll, self).setUp() diff --git a/openstackclient/tests/test_shell.py b/openstackclient/tests/test_shell.py index 80b181611a..4a8968cf40 100644 --- a/openstackclient/tests/test_shell.py +++ b/openstackclient/tests/test_shell.py @@ -161,6 +161,7 @@ def fake_execute(shell, cmd): class TestShell(utils.TestCase): + def setUp(self): super(TestShell, self).setUp() patch = "openstackclient.shell.OpenStackShell.run_subcommand" @@ -280,6 +281,7 @@ def _assert_cli(self, cmd_options, default_args): class TestShellHelp(TestShell): """Test the deferred help flag""" + def setUp(self): super(TestShellHelp, self).setUp() self.orig_env, os.environ = os.environ, {} @@ -304,6 +306,7 @@ def test_help_options(self): class TestShellOptions(TestShell): + def setUp(self): super(TestShellOptions, self).setUp() self.orig_env, os.environ = os.environ, {} @@ -391,6 +394,7 @@ def test_auth_env(self): class TestShellTokenAuthEnv(TestShell): + def setUp(self): super(TestShellTokenAuthEnv, self).setUp() env = { @@ -438,6 +442,7 @@ def test_empty_auth(self): class TestShellTokenEndpointAuthEnv(TestShell): + def setUp(self): super(TestShellTokenEndpointAuthEnv, self).setUp() env = { @@ -485,6 +490,7 @@ def test_empty_auth(self): class TestShellCli(TestShell): + def setUp(self): super(TestShellCli, self).setUp() env = { @@ -706,6 +712,7 @@ def test_shell_args_precedence(self, config_mock, vendor_mock): class TestShellCliEnv(TestShell): + def setUp(self): super(TestShellCliEnv, self).setUp() env = { diff --git a/openstackclient/tests/utils.py b/openstackclient/tests/utils.py index d9abd572bf..d3f3853f5a 100644 --- a/openstackclient/tests/utils.py +++ b/openstackclient/tests/utils.py @@ -28,6 +28,7 @@ class ParserException(Exception): class TestCase(testtools.TestCase): + def setUp(self): testtools.TestCase.setUp(self) diff --git a/openstackclient/tests/volume/v1/fakes.py b/openstackclient/tests/volume/v1/fakes.py index c6b4f0b858..42673efaa7 100644 --- a/openstackclient/tests/volume/v1/fakes.py +++ b/openstackclient/tests/volume/v1/fakes.py @@ -130,11 +130,13 @@ class FakeImagev1Client(object): + def __init__(self, **kwargs): self.images = mock.Mock() class FakeVolumev1Client(object): + def __init__(self, **kwargs): self.volumes = mock.Mock() self.volumes.resource_class = fakes.FakeResource(None, {}) @@ -151,6 +153,7 @@ def __init__(self, **kwargs): class TestVolumev1(utils.TestCommand): + def setUp(self): super(TestVolumev1, self).setUp() diff --git a/openstackclient/tests/volume/v1/test_qos_specs.py b/openstackclient/tests/volume/v1/test_qos_specs.py index 7ecc8ee829..1a6c0fa4ed 100644 --- a/openstackclient/tests/volume/v1/test_qos_specs.py +++ b/openstackclient/tests/volume/v1/test_qos_specs.py @@ -34,6 +34,7 @@ def setUp(self): class TestQosAssociate(TestQos): + def setUp(self): super(TestQosAssociate, self).setUp() @@ -183,6 +184,7 @@ def test_qos_create_with_properties(self): class TestQosDelete(TestQos): + def setUp(self): super(TestQosDelete, self).setUp() @@ -223,6 +225,7 @@ def test_qos_delete_with_name(self): class TestQosDisassociate(TestQos): + def setUp(self): super(TestQosDisassociate, self).setUp() @@ -277,6 +280,7 @@ def test_qos_disassociate_with_all_volume_types(self): class TestQosList(TestQos): + def setUp(self): super(TestQosList, self).setUp() @@ -323,6 +327,7 @@ def test_qos_list(self): class TestQosSet(TestQos): + def setUp(self): super(TestQosSet, self).setUp() @@ -354,6 +359,7 @@ def test_qos_set_with_properties_with_id(self): class TestQosShow(TestQos): + def setUp(self): super(TestQosShow, self).setUp() @@ -405,6 +411,7 @@ def test_qos_show(self): class TestQosUnset(TestQos): + def setUp(self): super(TestQosUnset, self).setUp() diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py index cfc58bb4c3..61d9df3aa5 100644 --- a/openstackclient/tests/volume/v2/fakes.py +++ b/openstackclient/tests/volume/v2/fakes.py @@ -213,6 +213,7 @@ class FakeVolumeClient(object): + def __init__(self, **kwargs): self.volumes = mock.Mock() self.volumes.resource_class = fakes.FakeResource(None, {}) @@ -233,6 +234,7 @@ def __init__(self, **kwargs): class TestVolume(utils.TestCommand): + def setUp(self): super(TestVolume, self).setUp() diff --git a/openstackclient/tests/volume/v2/test_backup.py b/openstackclient/tests/volume/v2/test_backup.py index dc1d78776f..edb4eb8e73 100644 --- a/openstackclient/tests/volume/v2/test_backup.py +++ b/openstackclient/tests/volume/v2/test_backup.py @@ -33,6 +33,7 @@ def setUp(self): class TestBackupCreate(TestBackup): + def setUp(self): super(TestBackupCreate, self).setUp() @@ -78,6 +79,7 @@ def test_backup_create(self): class TestBackupShow(TestBackup): + def setUp(self): super(TestBackupShow, self).setUp() @@ -105,6 +107,7 @@ def test_backup_show(self): class TestBackupDelete(TestBackup): + def setUp(self): super(TestBackupDelete, self).setUp() @@ -132,6 +135,7 @@ def test_backup_delete(self): class TestBackupRestore(TestBackup): + def setUp(self): super(TestBackupRestore, self).setUp() diff --git a/openstackclient/tests/volume/v2/test_qos_specs.py b/openstackclient/tests/volume/v2/test_qos_specs.py index 403634a33f..c826925fda 100644 --- a/openstackclient/tests/volume/v2/test_qos_specs.py +++ b/openstackclient/tests/volume/v2/test_qos_specs.py @@ -34,6 +34,7 @@ def setUp(self): class TestQosAssociate(TestQos): + def setUp(self): super(TestQosAssociate, self).setUp() @@ -184,6 +185,7 @@ def test_qos_create_with_properties(self): class TestQosDelete(TestQos): + def setUp(self): super(TestQosDelete, self).setUp() @@ -224,6 +226,7 @@ def test_qos_delete_with_name(self): class TestQosDisassociate(TestQos): + def setUp(self): super(TestQosDisassociate, self).setUp() @@ -278,6 +281,7 @@ def test_qos_disassociate_with_all_volume_types(self): class TestQosList(TestQos): + def setUp(self): super(TestQosList, self).setUp() @@ -324,6 +328,7 @@ def test_qos_list(self): class TestQosSet(TestQos): + def setUp(self): super(TestQosSet, self).setUp() @@ -355,6 +360,7 @@ def test_qos_set_with_properties_with_id(self): class TestQosShow(TestQos): + def setUp(self): super(TestQosShow, self).setUp() @@ -406,6 +412,7 @@ def test_qos_show(self): class TestQosUnset(TestQos): + def setUp(self): super(TestQosUnset, self).setUp() diff --git a/openstackclient/tests/volume/v2/test_snapshot.py b/openstackclient/tests/volume/v2/test_snapshot.py index 1c1dd43789..b4fb004bee 100644 --- a/openstackclient/tests/volume/v2/test_snapshot.py +++ b/openstackclient/tests/volume/v2/test_snapshot.py @@ -31,6 +31,7 @@ def setUp(self): class TestSnapshotCreate(TestSnapshot): + def setUp(self): super(TestSnapshotCreate, self).setUp() @@ -76,6 +77,7 @@ def test_snapshot_create(self): class TestSnapshotShow(TestSnapshot): + def setUp(self): super(TestSnapshotShow, self).setUp() @@ -103,6 +105,7 @@ def test_snapshot_show(self): class TestSnapshotDelete(TestSnapshot): + def setUp(self): super(TestSnapshotDelete, self).setUp() @@ -130,6 +133,7 @@ def test_snapshot_delete(self): class TestSnapshotSet(TestSnapshot): + def setUp(self): super(TestSnapshotSet, self).setUp() @@ -171,6 +175,7 @@ def test_snapshot_set(self): class TestSnapshotUnset(TestSnapshot): + def setUp(self): super(TestSnapshotUnset, self).setUp() @@ -247,7 +252,7 @@ def test_snapshot_list_without_options(self): volume_fakes.snapshot_description, "available", volume_fakes.snapshot_size - ),) + ),) self.assertEqual(datalist, tuple(data)) def test_snapshot_list_with_options(self): diff --git a/openstackclient/tests/volume/v2/test_type.py b/openstackclient/tests/volume/v2/test_type.py index 9bf6036399..f394aff324 100644 --- a/openstackclient/tests/volume/v2/test_type.py +++ b/openstackclient/tests/volume/v2/test_type.py @@ -147,7 +147,7 @@ def test_type_list_without_options(self): datalist = (( volume_fakes.type_id, volume_fakes.type_name, - ),) + ),) self.assertEqual(datalist, tuple(data)) def test_type_list_with_options(self): @@ -166,11 +166,12 @@ def test_type_list_with_options(self): volume_fakes.type_name, volume_fakes.type_description, "foo='bar'" - ),) + ),) self.assertEqual(datalist, tuple(data)) class TestTypeShow(TestType): + def setUp(self): super(TestTypeShow, self).setUp() @@ -314,6 +315,7 @@ def test_type_unset(self): class TestTypeDelete(TestType): + def setUp(self): super(TestTypeDelete, self).setUp() diff --git a/openstackclient/tests/volume/v2/test_volume.py b/openstackclient/tests/volume/v2/test_volume.py index df43cb2b2a..cbca09b286 100644 --- a/openstackclient/tests/volume/v2/test_volume.py +++ b/openstackclient/tests/volume/v2/test_volume.py @@ -24,6 +24,7 @@ class TestVolume(volume_fakes.TestVolume): + def setUp(self): super(TestVolume, self).setUp() @@ -377,6 +378,7 @@ def test_volume_create_image_name(self): class TestVolumeDelete(TestVolume): + def setUp(self): super(TestVolumeDelete, self).setUp() @@ -726,6 +728,7 @@ def test_volume_list_long(self): class TestVolumeShow(TestVolume): + def setUp(self): super(TestVolumeShow, self).setUp() diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index 87affd07d3..8f2122eb83 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -267,7 +267,7 @@ def _format_attach(attachments): 'Status', 'Size', 'Attachments', - ] + ] column_headers = copy.deepcopy(columns) column_headers[1] = 'Display Name' column_headers[4] = 'Attached to' From 3d7a26bdec7b3a41090b6b092025ab5cd8f87b24 Mon Sep 17 00:00:00 2001 From: Tom Cocozzello Date: Tue, 23 Feb 2016 11:02:08 -0600 Subject: [PATCH 0647/3095] Defaults are ignored with flake8 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If “ignore” is not set under flake8 in the tox.ini file there there are defaults set to be ignored. The depended patch fixes many of the problems. Change-Id: Ieed2fe1c4654e201d3fe6d40ef93e247ee736f8b Doc: http://flake8.readthedocs.org/en/latest/config.html#default Depends-On: I935ab48e7c5bac5f88ecdb3a05f73fb44fc9f41d Closes-Bug: #1548910 --- functional/tests/compute/v2/test_server.py | 6 +++--- functional/tests/identity/v3/test_group.py | 2 +- openstackclient/common/timing.py | 3 ++- openstackclient/compute/v2/server.py | 2 +- tox.ini | 3 +++ 5 files changed, 10 insertions(+), 6 deletions(-) diff --git a/functional/tests/compute/v2/test_server.py b/functional/tests/compute/v2/test_server.py index bd1b2a876b..6c09a42e02 100644 --- a/functional/tests/compute/v2/test_server.py +++ b/functional/tests/compute/v2/test_server.py @@ -32,14 +32,14 @@ class ServerTests(test.TestCase): def get_flavor(cls): raw_output = cls.openstack('flavor list -f value -c ID') ray = raw_output.split('\n') - idx = int(len(ray)/2) + idx = int(len(ray) / 2) return ray[idx] @classmethod def get_image(cls): raw_output = cls.openstack('image list -f value -c ID') ray = raw_output.split('\n') - idx = int(len(ray)/2) + idx = int(len(ray) / 2) return ray[idx] @classmethod @@ -49,7 +49,7 @@ def get_network(cls): except exceptions.CommandFailed: return '' ray = raw_output.split('\n') - idx = int(len(ray)/2) + idx = int(len(ray) / 2) return ' --nic net-id=' + ray[idx] @classmethod diff --git a/functional/tests/identity/v3/test_group.py b/functional/tests/identity/v3/test_group.py index 2d7f8f383f..8e39cd5daa 100644 --- a/functional/tests/identity/v3/test_group.py +++ b/functional/tests/identity/v3/test_group.py @@ -131,7 +131,7 @@ def test_group_contains_user(self): '%(group)s %(user)s' % {'group_domain': self.domain_name, 'user_domain': self.domain_name, 'group': group_name, - 'user': username}) + 'user': username}) self.assertOutput( '%(user)s in group %(group)s\n' % {'user': username, 'group': group_name}, diff --git a/openstackclient/common/timing.py b/openstackclient/common/timing.py index 5f62875947..71c2fec7e7 100644 --- a/openstackclient/common/timing.py +++ b/openstackclient/common/timing.py @@ -30,7 +30,8 @@ def take_action(self, parsed_args): for url, td in self.app.timing_data: # NOTE(dtroyer): Take the long way here because total_seconds() # was added in py27. - sec = (td.microseconds + (td.seconds + td.days*86400) * 1e6) / 1e6 + sec = (td.microseconds + (td.seconds + td.days * + 86400) * 1e6) / 1e6 total += sec results.append((url, sec)) results.append(('Total', total)) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 4cb94822bd..ca239c5195 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1206,7 +1206,7 @@ def take_action(self, parsed_args): _, body = utils.find_resource( compute_client.servers, parsed_args.server, - ).rescue() + ).rescue() return zip(*sorted(six.iteritems(body))) diff --git a/tox.ini b/tox.ini index cc1407f45c..d2a3f3d321 100644 --- a/tox.ini +++ b/tox.ini @@ -36,3 +36,6 @@ commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasen [flake8] show-source = True exclude = .git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build,tools +# If 'ignore' is not set there are default errors and warnings that are set +# Doc: http://flake8.readthedocs.org/en/latest/config.html#default +ignore = __ \ No newline at end of file From 198371b0b70467cb4be0a47317ec32cbe904f520 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 24 Feb 2016 01:39:41 +0000 Subject: [PATCH 0648/3095] Updated from global requirements Change-Id: Ib681d0b07afdde821ab4f9a17b1333bd12739eec --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 3f1707b671..8a379036d3 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -24,5 +24,5 @@ python-designateclient>=1.5.0 # Apache-2.0 python-heatclient>=0.6.0 # Apache-2.0 python-ironicclient>=1.1.0 # Apache-2.0 python-mistralclient>=1.0.0 # Apache-2.0 -python-saharaclient>=0.10.0 # Apache-2.0 +python-saharaclient>=0.12.0 # Apache-2.0 python-zaqarclient>=0.3.0 # Apache-2.0 From e47c83d47a10df2cef77128c0252fbae69730603 Mon Sep 17 00:00:00 2001 From: "ting.wang" Date: Wed, 24 Feb 2016 10:14:56 +0800 Subject: [PATCH 0649/3095] Py3 replace dict.iteritems with six.iteritems All dict.iteritems in osc are replaced with six.iteritems except this one. So fix it to add py3 compatibility. Change-Id: I1aa51399a36e650d262d839ce2b4ec04d3f91db2 --- openstackclient/api/object_store_v1.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/api/object_store_v1.py b/openstackclient/api/object_store_v1.py index 307c8fe296..10139ea181 100644 --- a/openstackclient/api/object_store_v1.py +++ b/openstackclient/api/object_store_v1.py @@ -551,7 +551,7 @@ def _set_properties(self, properties, header_tag): log = logging.getLogger(__name__ + '._set_properties') headers = {} - for k, v in properties.iteritems(): + for k, v in six.iteritems(properties): if not utils.is_ascii(k) or not utils.is_ascii(v): log.error('Cannot set property %s to non-ascii value', k) continue From 3e08590600249134636361b114ca51f3a7f647c8 Mon Sep 17 00:00:00 2001 From: "ting.wang" Date: Wed, 24 Feb 2016 10:44:06 +0800 Subject: [PATCH 0650/3095] Improve tox to show coverage report on same window With this patch coverage report will be shown with the results when tox -e cover will be run. Change-Id: I96713a8dd5d82019631c9e48c1abd7a94e201569 --- tox.ini | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index cc1407f45c..8add2fe365 100644 --- a/tox.ini +++ b/tox.ini @@ -22,7 +22,9 @@ passenv = OS_* commands = {posargs} [testenv:cover] -commands = python setup.py test --coverage --testr-args='{posargs}' +commands = + python setup.py test --coverage --testr-args='{posargs}' + coverage report [testenv:debug] commands = oslo_debug_helper -t openstackclient/tests {posargs} From f0960f0fef263298e56d7e81acf50597073becc7 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Wed, 24 Feb 2016 17:26:13 +0800 Subject: [PATCH 0651/3095] Floating IP: Neutron support for "ip floating show" command Change-Id: I30350076621c83c758927444e5f8bcc2b7d0fc74 Partial-Bug: 1519502 Related-to: blueprint neutron-client --- doc/source/command-objects/ip-floating.rst | 14 +++ openstackclient/network/v2/floating_ip.py | 35 ++++++ openstackclient/tests/compute/v2/fakes.py | 1 + openstackclient/tests/network/v2/fakes.py | 19 +++- .../tests/network/v2/test_floating_ip.py | 105 ++++++++++++++++++ .../notes/bug-1519502-f72236598d14d350.yaml | 2 + setup.cfg | 1 + 7 files changed, 175 insertions(+), 2 deletions(-) diff --git a/doc/source/command-objects/ip-floating.rst b/doc/source/command-objects/ip-floating.rst index efdfb45313..99d06d071b 100644 --- a/doc/source/command-objects/ip-floating.rst +++ b/doc/source/command-objects/ip-floating.rst @@ -82,3 +82,17 @@ Remove floating IP address from server .. describe:: Server to remove the IP address from (name or ID) + +ip floating show +---------------- + +Display floating IP details + +.. program:: ip floating show + .. code:: bash + + os ip floating show + +.. describe:: + + Floating IP to display (IP address or ID) diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index 23b83201f6..e0a65a481f 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -17,6 +17,14 @@ from openstackclient.network import common +def _get_columns(item): + columns = item.keys() + if 'tenant_id' in columns: + columns.remove('tenant_id') + columns.append('project_id') + return tuple(sorted(columns)) + + class DeleteFloatingIP(common.NetworkAndComputeCommand): """Delete floating IP""" @@ -89,3 +97,30 @@ def take_action_compute(self, client, parsed_args): s, columns, formatters={}, ) for s in data)) + + +class ShowFloatingIP(common.NetworkAndComputeShowOne): + """Show floating IP details""" + + def update_parser_common(self, parser): + parser.add_argument( + 'floating_ip', + metavar="", + help=("Floating IP to display (IP address or ID)") + ) + return parser + + def take_action_network(self, client, parsed_args): + obj = client.find_ip(parsed_args.floating_ip, ignore_missing=False) + columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns) + return (columns, data) + + def take_action_compute(self, client, parsed_args): + obj = utils.find_resource( + client.floating_ips, + parsed_args.floating_ip, + ) + columns = _get_columns(obj._info) + data = utils.get_dict_properties(obj._info, columns) + return (columns, data) diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index d2341ccc43..a522aa65d4 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -622,6 +622,7 @@ def create_one_floating_ip(attrs={}, methods={}): info=copy.deepcopy(floating_ip_attrs), methods=copy.deepcopy(floating_ip_methods), loaded=True) + return floating_ip @staticmethod diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index b1784b6072..ea2d38d166 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -635,14 +635,24 @@ def create_one_floating_ip(attrs={}, methods={}): 'id': 'floating-ip-id-' + uuid.uuid4().hex, 'floating_ip_address': '1.0.9.0', 'fixed_ip_address': '2.0.9.0', + 'dns_domain': None, + 'dns_name': None, + 'status': 'DOWN', + 'floating_network_id': 'network-id-' + uuid.uuid4().hex, + 'router_id': 'router-id-' + uuid.uuid4().hex, 'port_id': 'port-id-' + uuid.uuid4().hex, + 'tenant_id': 'project-id-' + uuid.uuid4().hex, } # Overwrite default attributes. floating_ip_attrs.update(attrs) # Set default methods. - floating_ip_methods = {} + floating_ip_methods = { + 'keys': ['id', 'floating_ip_address', 'fixed_ip_address', + 'dns_domain', 'dns_name', 'status', 'router_id', + 'floating_network_id', 'port_id', 'tenant_id'] + } # Overwrite default methods. floating_ip_methods.update(methods) @@ -650,7 +660,12 @@ def create_one_floating_ip(attrs={}, methods={}): floating_ip = fakes.FakeResource( info=copy.deepcopy(floating_ip_attrs), methods=copy.deepcopy(floating_ip_methods), - loaded=True) + loaded=True + ) + + # Set attributes with special mappings in OpenStack SDK. + floating_ip.project_id = floating_ip_attrs['tenant_id'] + return floating_ip @staticmethod diff --git a/openstackclient/tests/network/v2/test_floating_ip.py b/openstackclient/tests/network/v2/test_floating_ip.py index 0d3b413e42..1c1088a3b4 100644 --- a/openstackclient/tests/network/v2/test_floating_ip.py +++ b/openstackclient/tests/network/v2/test_floating_ip.py @@ -100,6 +100,64 @@ def test_floating_ip_list(self): self.assertEqual(self.data, list(data)) +class TestShowFloatingIPNetwork(TestFloatingIPNetwork): + + # The floating ip to display. + floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip() + + columns = ( + 'dns_domain', + 'dns_name', + 'fixed_ip_address', + 'floating_ip_address', + 'floating_network_id', + 'id', + 'port_id', + 'project_id', + 'router_id', + 'status', + ) + + data = ( + floating_ip.dns_domain, + floating_ip.dns_name, + floating_ip.fixed_ip_address, + floating_ip.floating_ip_address, + floating_ip.floating_network_id, + floating_ip.id, + floating_ip.port_id, + floating_ip.tenant_id, + floating_ip.router_id, + floating_ip.status, + ) + + def setUp(self): + super(TestShowFloatingIPNetwork, self).setUp() + + self.network.find_ip = mock.Mock(return_value=self.floating_ip) + + # Get the command object to test + self.cmd = floating_ip.ShowFloatingIP(self.app, self.namespace) + + def test_floating_ip_show(self): + arglist = [ + self.floating_ip.id, + ] + verifylist = [ + ('floating_ip', self.floating_ip.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.find_ip.assert_called_with( + self.floating_ip.id, + ignore_missing=False + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + # Tests for Nova network # class TestFloatingIPCompute(compute_fakes.TestComputev2): @@ -189,3 +247,50 @@ def test_floating_ip_list(self): self.compute.floating_ips.list.assert_called_with() self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + + +class TestShowFloatingIPCompute(TestFloatingIPCompute): + + # The floating ip to display. + floating_ip = compute_fakes.FakeFloatingIP.create_one_floating_ip() + + columns = ( + 'fixed_ip', + 'id', + 'instance_id', + 'ip', + 'pool', + ) + + data = ( + floating_ip.fixed_ip, + floating_ip.id, + floating_ip.instance_id, + floating_ip.ip, + floating_ip.pool, + ) + + def setUp(self): + super(TestShowFloatingIPCompute, self).setUp() + + self.app.client_manager.network_endpoint_enabled = False + + # Return value of utils.find_resource() + self.compute.floating_ips.get.return_value = self.floating_ip + + # Get the command object to test + self.cmd = floating_ip.ShowFloatingIP(self.app, None) + + def test_floating_ip_show(self): + arglist = [ + self.floating_ip.id, + ] + verifylist = [ + ('floating_ip', self.floating_ip.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) diff --git a/releasenotes/notes/bug-1519502-f72236598d14d350.yaml b/releasenotes/notes/bug-1519502-f72236598d14d350.yaml index 297f96def9..73ead7f427 100644 --- a/releasenotes/notes/bug-1519502-f72236598d14d350.yaml +++ b/releasenotes/notes/bug-1519502-f72236598d14d350.yaml @@ -4,3 +4,5 @@ features: [Bug `1519502 `_] - Command ``ip floating list`` is now available for neutron network. [Bug `1519502 `_] + - Add command ``ip floating show`` for neutron and nova network. + [Bug `1519502 `_] diff --git a/setup.cfg b/setup.cfg index 2bb2fa38f3..0750d7ef7a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -326,6 +326,7 @@ openstack.image.v2 = openstack.network.v2 = ip_floating_delete = openstackclient.network.v2.floating_ip:DeleteFloatingIP ip_floating_list = openstackclient.network.v2.floating_ip:ListFloatingIP + ip_floating_show = openstackclient.network.v2.floating_ip:ShowFloatingIP network_create = openstackclient.network.v2.network:CreateNetwork network_delete = openstackclient.network.v2.network:DeleteNetwork network_list = openstackclient.network.v2.network:ListNetwork From e0b6cab09bfe5d83435afe069d08820fcc1c5aa7 Mon Sep 17 00:00:00 2001 From: "ting.wang" Date: Wed, 24 Feb 2016 16:42:03 +0800 Subject: [PATCH 0652/3095] Add some test cases for "server list" command Add some test cases that test 'server list' command when specifying flavor or image. Because I add some attribution to fake.py, I have to change some code in create server test. Despite all this, I think it's good for testing. Change-Id: I714deac1f6f940b790a3c20af5f7ffa724ac44d1 --- openstackclient/tests/compute/v2/fakes.py | 6 ++ .../tests/compute/v2/test_server.py | 99 ++++++++++++++----- 2 files changed, 78 insertions(+), 27 deletions(-) diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index d2341ccc43..0521c4c500 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -377,6 +377,12 @@ def create_one_server(attrs={}, methods={}): 'id': 'server-id-' + uuid.uuid4().hex, 'name': 'server-name-' + uuid.uuid4().hex, 'metadata': {}, + 'image': { + 'id': 'image-id-' + uuid.uuid4().hex, + }, + 'flavor': { + 'id': 'flavor-id-' + uuid.uuid4().hex, + } } # Overwrite default attributes. diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 95188522fa..d379b173f4 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -12,7 +12,6 @@ # License for the specific language governing permissions and limitations # under the License. # - import mock from mock import call @@ -92,6 +91,7 @@ class TestServerCreate(TestServer): 'addresses', 'flavor', 'id', + 'image', 'name', 'networks', 'properties', @@ -100,8 +100,9 @@ class TestServerCreate(TestServer): def datalist(self): datalist = ( '', - self.flavor.name + ' ()', + self.flavor.name + ' (' + self.new_server.flavor.get('id') + ')', self.new_server.id, + self.image.name + ' (' + self.new_server.image.get('id') + ')', self.new_server.name, self.new_server.networks, '', @@ -617,33 +618,32 @@ class TestServerList(TestServer): 'Properties', ) - # Default search options, in the case of no commandline option specified. - search_opts = { - 'reservation_id': None, - 'ip': None, - 'ip6': None, - 'name': None, - 'instance_name': None, - 'status': None, - 'flavor': None, - 'image': None, - 'host': None, - 'tenant_id': None, - 'all_tenants': False, - 'user_id': None, - } - - # Default params of the core function of the command in the case of no - # commandline option specified. - kwargs = { - 'search_opts': search_opts, - 'marker': None, - 'limit': None, - } - def setUp(self): super(TestServerList, self).setUp() + self.search_opts = { + 'reservation_id': None, + 'ip': None, + 'ip6': None, + 'name': None, + 'instance_name': None, + 'status': None, + 'flavor': None, + 'image': None, + 'host': None, + 'tenant_id': None, + 'all_tenants': False, + 'user_id': None, + } + + # Default params of the core function of the command in the case of no + # commandline option specified. + self.kwargs = { + 'search_opts': self.search_opts, + 'marker': None, + 'limit': None, + } + # The fake servers' attributes. Use the original attributes names in # nova, not the ones printed by "server list" command. self.attrs = { @@ -660,9 +660,14 @@ def setUp(self): # The servers to be listed. self.servers = self.setup_servers_mock(3) - self.servers_mock.list.return_value = self.servers + self.image = image_fakes.FakeImage.create_one_image() + self.cimages_mock.get.return_value = self.image + + self.flavor = compute_fakes.FakeFlavor.create_one_flavor() + self.flavors_mock.get.return_value = self.flavor + # Get the command object to test self.cmd = server.ListServer(self.app, None) @@ -721,6 +726,46 @@ def test_server_list_long_option(self): self.assertEqual(self.columns_long, columns) self.assertEqual(tuple(self.data_long), tuple(data)) + def test_server_list_with_image(self): + + arglist = [ + '--image', self.image.id + ] + verifylist = [ + ('image', self.image.id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.cimages_mock.get.assert_called_with(self.image.id) + + self.search_opts['image'] = self.image.id + self.servers_mock.list.assert_called_with(**self.kwargs) + + self.assertEqual(self.columns, columns) + self.assertEqual(tuple(self.data), tuple(data)) + + def test_server_list_with_flavor(self): + + arglist = [ + '--flavor', self.flavor.id + ] + verifylist = [ + ('flavor', self.flavor.id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.flavors_mock.get.assert_called_with(self.flavor.id) + + self.search_opts['flavor'] = self.flavor.id + self.servers_mock.list.assert_called_with(**self.kwargs) + + self.assertEqual(self.columns, columns) + self.assertEqual(tuple(self.data), tuple(data)) + class TestServerLock(TestServer): From 69d5bc4e554542735da4a277473251ec6ac47e6c Mon Sep 17 00:00:00 2001 From: Mark Vanderwiel Date: Thu, 10 Dec 2015 15:14:01 -0600 Subject: [PATCH 0653/3095] update heat object and command doc Many OpenStack client heat command patches are in progress, seems like good time to also update some of the highlevel doc. Added new objects for orchestration and software openstack orchestration resource type list/show openstack software config create/delete/show/list Added new action for restore openstack stack snapshot create/delete/list/show/restore Added link to CLI reference for complete plugin syntax and details Removed deprecated tasker client Change-Id: I2c94a5981954edcba95f364a0f909c799f1c403b Blueprint: heat-support-python-openstackclient --- doc/source/commands.rst | 12 +++++++++++- doc/source/plugin-commands.rst | 4 ++++ doc/source/plugins.rst | 2 +- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 3598c284d2..2195720f11 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -148,6 +148,8 @@ list check out :doc:`plugin-commands`. * ``dataprocessing plugin``: (**Data Processing (Sahara)**) * ``message-broker cluster``: (**Message Broker (Cue)**) * ``message flavor``: (**Messaging (Zaqar)**) +* ``orchestration resource``: (**Orchestration (Heat)**) +* ``orchestration template``: (**Orchestration (Heat)**) * ``pool``: (**Messaging (Zaqar)**) * ``ptr record``: (**DNS (Designate)**) * ``queue``: (**Messaging (Zaqar)**) @@ -155,7 +157,14 @@ list check out :doc:`plugin-commands`. * ``secret``: (**Key Manager (Barbican)**) * ``secret container``: (**Key Manager (Barbican)**) * ``secret order``: (**Key Manager (Barbican)**) -* ``stack``: (**Orchestration (Heat)**) +* ``software config``: (**Orchestration (Heat)**) +* ``software deployment``: (**Orchestration (Heat)**) +* ``stack event``: (**Orchestration (Heat)**) +* ``stack hook``: (**Orchestration (Heat)**) +* ``stack output``: (**Orchestration (Heat)**) +* ``stack resource``: (**Orchestration (Heat)**) +* ``stack snapshot``: (**Orchestration (Heat)**) +* ``stack template``: (**Orchestration (Heat)**) * ``task exeuction``: (**Workflow Engine (Mistral)**) * ``tld``: (**DNS (Designate)**) * ``workbook``: (**Workflow Engine (Mistral)**) @@ -190,6 +199,7 @@ Those actions with an opposite action are noted in parens if applicable. * ``remove`` (``add``) - remove an object from a group of objects * ``rescue`` (``unrescue``) - reboot a server in a special rescue mode allowing access to the original disks * ``resize`` - change a server's flavor +* ``restore`` - restore a heat stack snapshot * ``resume`` (``suspend``) - return one or more suspended servers to running state * ``revoke`` (``issue``) - revoke a token * ``save`` - download an object locally diff --git a/doc/source/plugin-commands.rst b/doc/source/plugin-commands.rst index 893c4c4968..a3f4152158 100644 --- a/doc/source/plugin-commands.rst +++ b/doc/source/plugin-commands.rst @@ -2,6 +2,8 @@ Plugin Commands ================= +Note: To see the complete syntax for the plugin commands, see the `CLI_Ref`_ + .. list-plugins:: openstack.cli.extension .. list-plugins:: openstack.key_manager.v1 @@ -30,3 +32,5 @@ .. list-plugins:: openstack.orchestration.v1 :detailed: + +.. _CLI_Ref: http://docs.openstack.org/cli-reference/openstack.html \ No newline at end of file diff --git a/doc/source/plugins.rst b/doc/source/plugins.rst index 0c838ec5e6..32a405995f 100644 --- a/doc/source/plugins.rst +++ b/doc/source/plugins.rst @@ -30,7 +30,7 @@ python-ceilometerclient using argparse python-congressclient using OpenStackClient python-cueclient using OpenStackClient python-designateclient using OpenStackClient -python-heatclient patch in progress (https://review.openstack.org/#/c/195867/) +python-heatclient using OpenStackClient python-ironicclient Using OpenStackClient python-magnumclient using argparse python-manilaclient using argparse From 16f00833a70893979ccdf7ffb7f6e1e7653cdc24 Mon Sep 17 00:00:00 2001 From: Dina Belova Date: Thu, 10 Dec 2015 15:48:52 +0300 Subject: [PATCH 0654/3095] Add shell --profile option to trigger osprofiler from CLI This will allow to trigger profiling of various services that allow it currently and which APIs support is added to openstackclient. Cinder and Glance have osprofiler support already, Nova and Keystone are in progress. To use this functionality osprofiler (and its storage backend) needs to be installed in the environment. If so, you will be able to trigger profiling via the following command, for example: $ openstack --profile SECRET_KEY user list At the end of output there will be message with , and to plot nice HTML graphs the following command should be used: $ osprofiler trace show --html --out result.html Related Keystone change: https://review.openstack.org/#/c/103368/ Related Nova change: https://review.openstack.org/#/c/254703/ The similar change to the keystoneclient (https://review.openstack.org/#/c/255308/) was abandoned as new CLI extenstions are not more accepted to python-keystoneclient. Change-Id: I3d6ac613e5da70619d0a4781e5d066fde073b407 --- doc/source/man/openstack.rst | 6 +++ openstackclient/shell.py | 51 +++++++++++++++++++ openstackclient/tests/test_shell.py | 1 + ...d-osprofiler-support-adf5286daf220914.yaml | 17 +++++++ test-requirements.txt | 1 + 5 files changed, 76 insertions(+) create mode 100644 releasenotes/notes/add-osprofiler-support-adf5286daf220914.yaml diff --git a/doc/source/man/openstack.rst b/doc/source/man/openstack.rst index a33b1891b4..f02705d817 100644 --- a/doc/source/man/openstack.rst +++ b/doc/source/man/openstack.rst @@ -123,6 +123,12 @@ OPTIONS :option:`--os-interface` Interface type. Valid options are `public`, `admin` and `internal`. +:option: `--profile` + HMAC key to use for encrypting context data for performance profiling of + requested operation. This key should be the value of one of the HMAC keys + defined in the configuration files of OpenStack services, user would like + to trace through. + :option:`--log-file` Specify a file to log output. Disabled by default. diff --git a/openstackclient/shell.py b/openstackclient/shell.py index c84e2b1d40..bd13fbb596 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -25,6 +25,7 @@ from cliff import command from cliff import complete from cliff import help +from oslo_utils import importutils from oslo_utils import strutils import openstackclient @@ -37,6 +38,8 @@ from os_client_config import config as cloud_config +osprofiler_profiler = importutils.try_import("osprofiler.profiler") + DEFAULT_DOMAIN = 'default' @@ -101,6 +104,8 @@ def __init__(self): self.client_manager = None self.command_options = None + self.do_profile = False + def configure_logging(self): """Configure logging for the app.""" self.log_configurator = logs.LogConfigurator(self.options) @@ -125,6 +130,39 @@ def run(self, argv): finally: self.log.info("END return value: %s", ret_val) + def init_profile(self): + self.do_profile = osprofiler_profiler and self.options.profile + if self.do_profile: + osprofiler_profiler.init(self.options.profile) + + def close_profile(self): + if self.do_profile: + trace_id = osprofiler_profiler.get().get_base_id() + + # NOTE(dbelova): let's use warning log level to see these messages + # printed. In fact we can define custom log level here with value + # bigger than most big default one (CRITICAL) or something like + # that (PROFILE = 60 for instance), but not sure we need it here. + self.log.warning("Trace ID: %s" % trace_id) + self.log.warning("To display trace use next command:\n" + "osprofiler trace show --html %s " % trace_id) + + def run_subcommand(self, argv): + self.init_profile() + try: + ret_value = super(OpenStackShell, self).run_subcommand(argv) + finally: + self.close_profile() + return ret_value + + def interact(self): + self.init_profile() + try: + ret_value = super(OpenStackShell, self).run_subcommand() + finally: + self.close_profile() + return ret_value + def build_option_parser(self, description, version): parser = super(OpenStackShell, self).build_option_parser( description, @@ -190,6 +228,19 @@ def build_option_parser(self, description, version): help="Print API call timing info", ) + # osprofiler HMAC key argument + if osprofiler_profiler: + parser.add_argument('--profile', + metavar='hmac-key', + help='HMAC key to use for encrypting context ' + 'data for performance profiling of operation. ' + 'This key should be the value of one of the ' + 'HMAC keys configured in osprofiler ' + 'middleware in the projects user would like ' + 'to profile. It needs to be specified in ' + 'configuration files of the required ' + 'projects.') + return clientmanager.build_plugin_option_parser(parser) def initialize_app(self, argv): diff --git a/openstackclient/tests/test_shell.py b/openstackclient/tests/test_shell.py index c4546d89f2..101aadd17b 100644 --- a/openstackclient/tests/test_shell.py +++ b/openstackclient/tests/test_shell.py @@ -109,6 +109,7 @@ '--os-default-domain': (DEFAULT_DOMAIN_NAME, True, True), '--os-cacert': ('/dev/null', True, True), '--timing': (True, True, False), + '--profile': ('SECRET_KEY', True, False), '--os-interface': (DEFAULT_INTERFACE, True, True) } diff --git a/releasenotes/notes/add-osprofiler-support-adf5286daf220914.yaml b/releasenotes/notes/add-osprofiler-support-adf5286daf220914.yaml new file mode 100644 index 0000000000..4176ded152 --- /dev/null +++ b/releasenotes/notes/add-osprofiler-support-adf5286daf220914.yaml @@ -0,0 +1,17 @@ +--- +features: + - | + OSprofiler support was added. To initiate OpenStack request tracing + ``--profile `` option needs to be added to the CLI command. This + key needs to present one of the secret keys defined in the OpenStack + projects configuration files (if there is a wish to generate cross-project + trace, the chosen key needs to be presented in all these configuration + files). By default all OpenStack projects, that support OSprofiler, + are using ``SECRET_KEY`` HMAC key. + + To use tracing functionality OSprofiler (and its storage backend) + needs to be installed in the environment. If so, you will be able to + trigger profiling via `openstack --profile SECRET_KEY ` command. + At the end of output there will be message with , and to plot + human-readable HTML chart the following command should be used - + ``osprofiler trace show --html --out result.html``. diff --git a/test-requirements.txt b/test-requirements.txt index 2255cd3b3b..51c660f508 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -17,6 +17,7 @@ testrepository>=0.0.18 # Apache-2.0/BSD testtools>=1.4.0 # MIT WebOb>=1.2.3 # MIT tempest-lib>=0.14.0 # Apache-2.0 +osprofiler>=1.1.0 # Apache-2.0 # Install these to generate sphinx autodocs python-barbicanclient>=3.3.0 # Apache-2.0 From ff3a1d3780486d98bd3205c8329576b396e8161f Mon Sep 17 00:00:00 2001 From: jichenjc Date: Fri, 19 Feb 2016 22:52:38 +0800 Subject: [PATCH 0655/3095] [compute] Add set host command set host command is missing, add it as SetHost class. Change-Id: I7acb94150718b7150598632cbebc3d85018a0d59 --- doc/source/command-objects/host.rst | 35 +++++++++ openstackclient/compute/v2/host.py | 57 ++++++++++++++ openstackclient/tests/compute/v2/fakes.py | 56 ++++++++++++++ openstackclient/tests/compute/v2/test_host.py | 75 +++++++++++++++++++ .../notes/bug-1556929-edd78cded88ecdc9.yaml | 4 + setup.cfg | 1 + 6 files changed, 228 insertions(+) create mode 100644 openstackclient/tests/compute/v2/test_host.py create mode 100644 releasenotes/notes/bug-1556929-edd78cded88ecdc9.yaml diff --git a/doc/source/command-objects/host.rst b/doc/source/command-objects/host.rst index 680efcc2e8..ede62c562e 100644 --- a/doc/source/command-objects/host.rst +++ b/doc/source/command-objects/host.rst @@ -21,6 +21,41 @@ List all hosts Only return hosts in the availability zone +host set +-------- + +Set host command + +.. program:: host set +.. code:: bash + + os host set + [--enable | --disable] + [--enable-maintenance | --disable-maintenance] + + +.. _host-set: +.. option:: --enable + + Enable the host + +.. option:: --disable + + Disable the host + +.. _maintenance-set: +.. option:: --enable-maintenance + + Enable maintenance mode for the host + +.. option:: --disable-maintenance + + Disable maintenance mode for the host + +.. describe:: + + The host (name or ID) + host show --------- diff --git a/openstackclient/compute/v2/host.py b/openstackclient/compute/v2/host.py index f2257d1219..5af2531084 100644 --- a/openstackclient/compute/v2/host.py +++ b/openstackclient/compute/v2/host.py @@ -44,6 +44,63 @@ def take_action(self, parsed_args): ) for s in data)) +class SetHost(command.Command): + """Set host properties""" + def get_parser(self, prog_name): + parser = super(SetHost, self).get_parser(prog_name) + parser.add_argument( + "host", + metavar="", + help="The host to modify (name or ID)" + ) + status = parser.add_mutually_exclusive_group() + status.add_argument( + '--enable', + action='store_true', + help='Enable the host' + ) + status.add_argument( + '--disable', + action='store_true', + help='Disable the host' + ) + maintenance = parser.add_mutually_exclusive_group() + maintenance.add_argument( + '--enable-maintenance', + action='store_true', + help='Enable maintenance mode for the host' + ) + maintenance.add_argument( + '--disable-maintenance', + action='store_true', + help='Disable maintenance mode for the host', + ) + return parser + + def take_action(self, parsed_args): + kwargs = {} + + if parsed_args.enable: + kwargs['status'] = True + if parsed_args.disable: + kwargs['status'] = False + if parsed_args.enable_maintenance: + kwargs['maintenance_mode'] = True + if parsed_args.disable_maintenance: + kwargs['maintenance_mode'] = False + + compute_client = self.app.client_manager.compute + foundhost = utils.find_resource( + compute_client.hosts, + parsed_args.host + ) + + compute_client.hosts.update( + foundhost.id, + kwargs + ) + + class ShowHost(command.Lister): """Show host command""" diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 32d257f13f..4531f3bf40 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -140,6 +140,9 @@ def __init__(self, **kwargs): self.keypairs = mock.Mock() self.keypairs.resource_class = fakes.FakeResource(None, {}) + self.hosts = mock.Mock() + self.hosts.resource_class = fakes.FakeResource(None, {}) + self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] @@ -878,3 +881,56 @@ def create_networks(attrs={}, methods={}, count=2): networks.append(FakeNetwork.create_one_network(attrs, methods)) return networks + + +class FakeHost(object): + """Fake one host.""" + + @staticmethod + def create_one_host(attrs=None): + """Create a fake host. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object, with id and other attributes + """ + if attrs is None: + attrs = {} + + # Set default attributes. + host_info = { + "id": 1, + "service_id": 1, + "host": "host1", + "uuid": 'host-id-' + uuid.uuid4().hex, + "vcpus": 10, + "memory_mb": 100, + "local_gb": 100, + "vcpus_used": 5, + "memory_mb_used": 50, + "local_gb_used": 10, + "hypervisor_type": "xen", + "hypervisor_version": 1, + "hypervisor_hostname": "devstack1", + "free_ram_mb": 50, + "free_disk_gb": 50, + "current_workload": 10, + "running_vms": 1, + "cpu_info": "", + "disk_available_least": 1, + "host_ip": "10.10.10.10", + "supported_instances": "", + "metrics": "", + "pci_stats": "", + "extra_resources": "", + "stats": "", + "numa_topology": "", + "ram_allocation_ratio": 1.0, + "cpu_allocation_ratio": 1.0 + } + host_info.update(attrs) + host = fakes.FakeResource( + info=copy.deepcopy(host_info), + loaded=True) + return host diff --git a/openstackclient/tests/compute/v2/test_host.py b/openstackclient/tests/compute/v2/test_host.py new file mode 100644 index 0000000000..de5375771f --- /dev/null +++ b/openstackclient/tests/compute/v2/test_host.py @@ -0,0 +1,75 @@ +# Copyright 2016 IBM Corporation +# +# 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.compute.v2 import host +from openstackclient.tests.compute.v2 import fakes as compute_fakes + + +class TestHost(compute_fakes.TestComputev2): + + def setUp(self): + super(TestHost, self).setUp() + + # Get a shortcut to the FlavorManager Mock + self.host_mock = self.app.client_manager.compute.hosts + self.host_mock.reset_mock() + + +class TestHostSet(TestHost): + + def setUp(self): + super(TestHostSet, self).setUp() + + self.host = compute_fakes.FakeHost.create_one_host() + self.host_mock.get.return_value = self.host + self.host_mock.update.return_value = None + + self.cmd = host.SetHost(self.app, None) + + def test_host_set_no_option(self): + arglist = [ + str(self.host.id) + ] + verifylist = [ + ('host', str(self.host.id)) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.assertIsNone(result) + + body = {} + self.host_mock.update.assert_called_with(self.host.id, body) + + def test_host_set(self): + arglist = [ + '--enable', + '--disable-maintenance', + str(self.host.id) + ] + verifylist = [ + ('enable', True), + ('enable_maintenance', False), + ('host', str(self.host.id)) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.assertIsNone(result) + + body = {'status': True, 'maintenance_mode': False} + self.host_mock.update.assert_called_with(self.host.id, body) diff --git a/releasenotes/notes/bug-1556929-edd78cded88ecdc9.yaml b/releasenotes/notes/bug-1556929-edd78cded88ecdc9.yaml new file mode 100644 index 0000000000..44f9a4646f --- /dev/null +++ b/releasenotes/notes/bug-1556929-edd78cded88ecdc9.yaml @@ -0,0 +1,4 @@ +--- +features: + - Command ``host set`` is now available for compute + [Bug `1556929 `_] diff --git a/setup.cfg b/setup.cfg index b49eeba9eb..91f09a3a5a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -79,6 +79,7 @@ openstack.compute.v2 = flavor_unset = openstackclient.compute.v2.flavor:UnsetFlavor host_list = openstackclient.compute.v2.host:ListHost + host_set = openstackclient.compute.v2.host:SetHost host_show = openstackclient.compute.v2.host:ShowHost hypervisor_list = openstackclient.compute.v2.hypervisor:ListHypervisor From 45d6c1fb1fd83451ef0e236cf4320acd7224e0b7 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Fri, 26 Feb 2016 02:01:44 +0000 Subject: [PATCH 0656/3095] Updated from global requirements Change-Id: I582644850642651c61d259e4a53f670b421dce95 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 6a40b7993e..7c01b7cd79 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ cliff!=1.16.0,>=1.15.0 # Apache-2.0 keystoneauth1>=2.1.0 # Apache-2.0 openstacksdk>=0.7.4 # Apache-2.0 os-client-config>=1.13.1 # Apache-2.0 -oslo.config>=3.4.0 # Apache-2.0 +oslo.config>=3.7.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 oslo.utils>=3.5.0 # Apache-2.0 python-glanceclient>=1.2.0 # Apache-2.0 From b733ecf3ddc06ee1d75f570dddc4d81f71a8d1bf Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Thu, 25 Feb 2016 15:28:53 +0800 Subject: [PATCH 0657/3095] Add missing command/configuration object Add missing 'command' and 'configuration' object into command object list, add command, module, host commands describe in command-objects, and add 'mask' and 'unmask' options in configuration command details, fix some format issues by the way. Change-Id: Iea80c7b1e413e02e49b9090b9d3cb9c59aab4c38 --- doc/source/command-objects/command.rst | 17 +++++++++ doc/source/command-objects/configuration.rst | 9 +++++ doc/source/command-objects/host.rst | 37 ++++++++++++++++++++ doc/source/command-objects/module.rst | 22 ++++++++++++ doc/source/commands.rst | 6 ++-- 5 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 doc/source/command-objects/command.rst create mode 100644 doc/source/command-objects/host.rst create mode 100644 doc/source/command-objects/module.rst diff --git a/doc/source/command-objects/command.rst b/doc/source/command-objects/command.rst new file mode 100644 index 0000000000..ac4f851438 --- /dev/null +++ b/doc/source/command-objects/command.rst @@ -0,0 +1,17 @@ +======= +command +======= + +Internal + +Installed commands in the OSC process. + +command list +------------ + +List recognized commands by group + +.. program:: command list +.. code:: bash + + os command list diff --git a/doc/source/command-objects/configuration.rst b/doc/source/command-objects/configuration.rst index 0ee8bd63e5..7bf054c072 100644 --- a/doc/source/command-objects/configuration.rst +++ b/doc/source/command-objects/configuration.rst @@ -16,3 +16,12 @@ show different configurations. .. code:: bash os configuration show + [--mask | --unmask] + +.. option:: --mask + + Attempt to mask passwords (default) + +.. option:: --unmask + + Show password in clear text diff --git a/doc/source/command-objects/host.rst b/doc/source/command-objects/host.rst new file mode 100644 index 0000000000..680efcc2e8 --- /dev/null +++ b/doc/source/command-objects/host.rst @@ -0,0 +1,37 @@ +==== +host +==== + +Compute v2 + +The physical computer running a hypervisor. + +host list +--------- + +List all hosts + +.. program:: host list +.. code:: bash + + os host list + [--zone ] + +.. option:: --zone + + Only return hosts in the availability zone + +host show +--------- + +Display host details + +.. program:: host show +.. code:: bash + + os host show + + +.. describe:: + + Name of host diff --git a/doc/source/command-objects/module.rst b/doc/source/command-objects/module.rst new file mode 100644 index 0000000000..c3bc137287 --- /dev/null +++ b/doc/source/command-objects/module.rst @@ -0,0 +1,22 @@ +====== +module +====== + +Internal + +Installed Python modules in the OSC process. + +module list +----------- + +List module versions + +.. program:: module list +.. code:: bash + + os module list + [--all] + +.. option:: --all + + Show all modules that have version information diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 2195720f11..5678e5d185 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -74,6 +74,8 @@ referring to both Compute and Volume quotas. * ``aggregate``: (**Compute**) a grouping of servers * ``backup``: (**Volume**) a volume copy * ``catalog``: (**Identity**) service catalog +* ``command``: (**Internal**) installed commands in the OSC process +* ``configuration``: (**Internal**) openstack client configuration * ``console log``: (**Compute**) server console text dump * ``console url``: (**Compute**) server remote console URL * ``consumer``: (**Identity**) OAuth-based delegatee @@ -86,7 +88,7 @@ referring to both Compute and Volume quotas. * ``federation protocol``: (**Identity**) the underlying protocol used while federating identities * ``flavor``: (**Compute**) predefined server configurations: ram, root disk, etc * ``group``: (**Identity**) a grouping of users -* ``host``: Compute - the physical computer running a hypervisor +* ``host``: (**Compute**) - the physical computer running a hypervisor * ``hypervisor``: (**Compute**) the virtual machine manager * ``hypervisor stats``: (**Compute**) hypervisor statistics over all compute nodes * ``identity provider``: (**Identity**) a source of users and authentication @@ -97,7 +99,7 @@ referring to both Compute and Volume quotas. * ``keypair``: (**Compute**) an SSH public key * ``limits``: (**Compute**, **Volume**) resource usage limits * ``mapping``: (**Identity**) a definition to translate identity provider attributes to Identity concepts -* ``module``: internal - installed Python modules in the OSC process +* ``module``: (**Internal**) - installed Python modules in the OSC process * ``network``: (**Compute**, **Network**) - a virtual network for connecting servers and other resources * ``object``: (**Object Storage**) a single file in the Object Storage * ``policy``: (**Identity**) determines authorization From f37eda3a27dc88d3186d21eca328cca086ee3647 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Wed, 17 Feb 2016 10:07:50 +0800 Subject: [PATCH 0658/3095] Make SetFlavor and UnsetFlavor inherit from cliff.Command set/unset comamnd classes should inherit from cliff.Command class. Change-Id: I54e5608ac0768d7d94b7f7d516ea1948daefdc1b Partial-Bug: 1546065 --- doc/source/backwards-incompatible.rst | 12 ++++++++++ functional/tests/compute/v2/test_flavor.py | 17 +++++++++---- openstackclient/compute/v2/flavor.py | 24 +++++-------------- .../tests/compute/v2/test_flavor.py | 14 ++++------- .../notes/bug-1546065-41d09ffbd8606513.yaml | 4 ++++ 5 files changed, 39 insertions(+), 32 deletions(-) create mode 100644 releasenotes/notes/bug-1546065-41d09ffbd8606513.yaml diff --git a/doc/source/backwards-incompatible.rst b/doc/source/backwards-incompatible.rst index 94873c149f..5152641991 100644 --- a/doc/source/backwards-incompatible.rst +++ b/doc/source/backwards-incompatible.rst @@ -114,6 +114,18 @@ List of Backwards Incompatible Changes * Bug: https://launchpad.net/bugs/1506841 * Commit: https://review.openstack.org/#/c/236736/ +9. `flavor set/unset` commands will no longer return the modified resource + + Previously, modifying a flavor would result in the new flavor being displayed + to the user. To keep things consistent with other `set/unset` commands, we + will no longer be showing the modified resource. + + * In favor of: Use `set/unset` then `show` + * As of: NA + * Removed in: NA + * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1546065 + * Commit: https://review.openstack.org/#/c/280663/ + For Developers ============== diff --git a/functional/tests/compute/v2/test_flavor.py b/functional/tests/compute/v2/test_flavor.py index becf217f6c..d1f5f95dd4 100644 --- a/functional/tests/compute/v2/test_flavor.py +++ b/functional/tests/compute/v2/test_flavor.py @@ -46,11 +46,20 @@ def test_flavor_show(self): self.assertEqual(self.NAME + "\n", raw_output) def test_flavor_properties(self): - opts = self.get_show_opts(["properties"]) + opts = self.get_show_opts(['properties']) + raw_output = self.openstack( - 'flavor set --property a=b --property c=d ' + self.NAME + opts) + 'flavor set --property a=b --property c=d ' + self.NAME + ) + self.assertEqual('', raw_output) + + raw_output = self.openstack('flavor show ' + self.NAME + opts) self.assertEqual("a='b', c='d'\n", raw_output) - raw_output = self.openstack('flavor unset --property a ' + - self.NAME + opts) + raw_output = self.openstack( + 'flavor unset --property a ' + self.NAME + ) + self.assertEqual('', raw_output) + + raw_output = self.openstack('flavor show ' + self.NAME + opts) self.assertEqual("c='d'\n", raw_output) diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 0308d9409b..e106bd65c4 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -242,7 +242,7 @@ def take_action(self, parsed_args): return zip(*sorted(six.iteritems(flavor))) -class SetFlavor(command.ShowOne): +class SetFlavor(command.Command): """Set flavor properties""" def get_parser(self, prog_name): @@ -263,17 +263,11 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): compute_client = self.app.client_manager.compute - resource_flavor = compute_client.flavors.find(name=parsed_args.flavor) + flavor = compute_client.flavors.find(name=parsed_args.flavor) + flavor.set_keys(parsed_args.property) - resource_flavor.set_keys(parsed_args.property) - flavor = resource_flavor._info.copy() - flavor['properties'] = utils.format_dict(resource_flavor.get_keys()) - flavor.pop("links", None) - return zip(*sorted(six.iteritems(flavor))) - - -class UnsetFlavor(command.ShowOne): +class UnsetFlavor(command.Command): """Unset flavor properties""" def get_parser(self, prog_name): @@ -295,11 +289,5 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): compute_client = self.app.client_manager.compute - resource_flavor = compute_client.flavors.find(name=parsed_args.flavor) - - resource_flavor.unset_keys(parsed_args.property) - - flavor = resource_flavor._info.copy() - flavor['properties'] = utils.format_dict(resource_flavor.get_keys()) - flavor.pop("links", None) - return zip(*sorted(six.iteritems(flavor))) + flavor = compute_client.flavors.find(name=parsed_args.flavor) + flavor.unset_keys(parsed_args.property) diff --git a/openstackclient/tests/compute/v2/test_flavor.py b/openstackclient/tests/compute/v2/test_flavor.py index 781e3068ae..77e0bfb726 100644 --- a/openstackclient/tests/compute/v2/test_flavor.py +++ b/openstackclient/tests/compute/v2/test_flavor.py @@ -285,15 +285,12 @@ def test_flavor_set(self): ('property', {'FOO': '"B A R"'}), ('flavor', 'baremetal') ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) self.flavors_mock.find.assert_called_with(name='baremetal') - - self.assertEqual('properties', columns[6]) - self.assertIn('FOO=\'"B A R"\'', data[6]) + self.assertIsNone(result) class TestFlavorShow(TestFlavor): @@ -382,12 +379,9 @@ def test_flavor_unset(self): ('property', ['property']), ('flavor', 'baremetal'), ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) self.flavors_mock.find.assert_called_with(name='baremetal') - - self.assertEqual('properties', columns[6]) - self.assertNotIn('property', data[6]) + self.assertIsNone(result) diff --git a/releasenotes/notes/bug-1546065-41d09ffbd8606513.yaml b/releasenotes/notes/bug-1546065-41d09ffbd8606513.yaml new file mode 100644 index 0000000000..1d7e126686 --- /dev/null +++ b/releasenotes/notes/bug-1546065-41d09ffbd8606513.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - Command ``flavor set/unset`` now outputs nothing. + [Bug `1546065 `_] From 859bfaf8757086c8607c1520c8018ab20e91a3ac Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Wed, 17 Feb 2016 15:20:36 +0800 Subject: [PATCH 0659/3095] Make SetSecurityGroup inherit from cliff.Command set/unset comamnd classes should inherit from cliff.Command class. Change-Id: Ie28711ac8823dc9eb13cf83877864ca436b928bc Partial-Bug: 1546065 --- doc/source/backwards-incompatible.rst | 12 ++++++++++++ .../tests/network/v2/test_security_group.py | 17 ++++++++++------- openstackclient/compute/v2/security_group.py | 13 +++---------- .../notes/bug-1546065-41d09ffbd8606513.yaml | 2 ++ 4 files changed, 27 insertions(+), 17 deletions(-) diff --git a/doc/source/backwards-incompatible.rst b/doc/source/backwards-incompatible.rst index 5152641991..752f52e3e2 100644 --- a/doc/source/backwards-incompatible.rst +++ b/doc/source/backwards-incompatible.rst @@ -126,6 +126,18 @@ List of Backwards Incompatible Changes * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1546065 * Commit: https://review.openstack.org/#/c/280663/ +10. `security group set` commands will no longer return the modified resource + + Previously, modifying a security group would result in the new security group + being displayed to the user. To keep things consistent with other `set` + commands, we will no longer be showing the modified resource. + + * In favor of: Use `set` then `show` + * As of: NA + * Removed in: NA + * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1546065 + * Commit: https://review.openstack.org/#/c/281087/ + For Developers ============== diff --git a/functional/tests/network/v2/test_security_group.py b/functional/tests/network/v2/test_security_group.py index 2089b1d5dd..4fc4d12df3 100644 --- a/functional/tests/network/v2/test_security_group.py +++ b/functional/tests/network/v2/test_security_group.py @@ -32,10 +32,9 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): # Rename test - opts = cls.get_show_opts(cls.FIELDS) raw_output = cls.openstack('security group set --name ' + - cls.OTHER_NAME + ' ' + cls.NAME + opts) - cls.assertOutput(cls.OTHER_NAME + "\n", raw_output) + cls.OTHER_NAME + ' ' + cls.NAME) + cls.assertOutput('', raw_output) # Delete test raw_output = cls.openstack('security group delete ' + cls.OTHER_NAME) cls.assertOutput('', raw_output) @@ -46,10 +45,14 @@ def test_security_group_list(self): self.assertIn(self.NAME, raw_output) def test_security_group_set(self): - opts = self.get_show_opts(['description', 'name']) - raw_output = self.openstack('security group set --description NSA ' + - self.NAME + opts) - self.assertEqual("NSA\n" + self.NAME + "\n", raw_output) + raw_output = self.openstack( + 'security group set --description NSA ' + self.NAME + ) + self.assertEqual('', raw_output) + + opts = self.get_show_opts(['description']) + raw_output = self.openstack('security group show ' + self.NAME + opts) + self.assertEqual("NSA\n", raw_output) def test_security_group_show(self): opts = self.get_show_opts(self.FIELDS) diff --git a/openstackclient/compute/v2/security_group.py b/openstackclient/compute/v2/security_group.py index 6f2e1a52ca..2a7b40f4cd 100644 --- a/openstackclient/compute/v2/security_group.py +++ b/openstackclient/compute/v2/security_group.py @@ -271,7 +271,7 @@ def take_action(self, parsed_args): ) for s in rules)) -class SetSecurityGroup(command.ShowOne): +class SetSecurityGroup(command.Command): """Set security group properties""" def get_parser(self, prog_name): @@ -294,7 +294,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute data = utils.find_resource( compute_client.security_groups, @@ -306,17 +305,11 @@ def take_action(self, parsed_args): if parsed_args.description: data.description = parsed_args.description - info = {} - info.update(compute_client.security_groups.update( + compute_client.security_groups.update( data, data.name, data.description, - )._info) - - if info: - return zip(*sorted(six.iteritems(info))) - else: - return ({}, {}) + ) class ShowSecurityGroup(command.ShowOne): diff --git a/releasenotes/notes/bug-1546065-41d09ffbd8606513.yaml b/releasenotes/notes/bug-1546065-41d09ffbd8606513.yaml index 1d7e126686..f0c3463828 100644 --- a/releasenotes/notes/bug-1546065-41d09ffbd8606513.yaml +++ b/releasenotes/notes/bug-1546065-41d09ffbd8606513.yaml @@ -2,3 +2,5 @@ fixes: - Command ``flavor set/unset`` now outputs nothing. [Bug `1546065 `_] + - Command ``security group set`` now outputs nothing. + [Bug `1546065 `_] From 9c91c1df4147cbd277c3384b0c648a6069c5f723 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Wed, 17 Feb 2016 14:39:57 +0800 Subject: [PATCH 0660/3095] Make SetAgent inherit from cliff.Command set/unset command classes should inherit from cliff.Command class. Also, this patch adds functional tests for compute agent. Change-Id: I25eafffd1167f82aa0d430628c22dee7516b1e19 Partial-Bug: 1546065 --- doc/source/backwards-incompatible.rst | 12 +++ functional/tests/compute/v2/test_agent.py | 76 +++++++++++++++++++ openstackclient/compute/v2/agent.py | 5 +- .../notes/bug-1546065-41d09ffbd8606513.yaml | 2 + 4 files changed, 92 insertions(+), 3 deletions(-) create mode 100644 functional/tests/compute/v2/test_agent.py diff --git a/doc/source/backwards-incompatible.rst b/doc/source/backwards-incompatible.rst index 752f52e3e2..76a3b95187 100644 --- a/doc/source/backwards-incompatible.rst +++ b/doc/source/backwards-incompatible.rst @@ -138,6 +138,18 @@ List of Backwards Incompatible Changes * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1546065 * Commit: https://review.openstack.org/#/c/281087/ +11. `compute agent set` commands will no longer return the modified resource + + Previously, modifying an agent would result in the new agent being displayed + to the user. To keep things consistent with other `set` commands, we will + no longer be showing the modified resource. + + * In favor of: Use `set` then `show` + * As of: NA + * Removed in: NA + * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1546065 + * Commit: https://review.openstack.org/#/c/281088/ + For Developers ============== diff --git a/functional/tests/compute/v2/test_agent.py b/functional/tests/compute/v2/test_agent.py new file mode 100644 index 0000000000..df7c21f264 --- /dev/null +++ b/functional/tests/compute/v2/test_agent.py @@ -0,0 +1,76 @@ +# 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 hashlib + +from functional.common import test + + +class ComputeAgentTests(test.TestCase): + """Functional tests for compute agent. """ + + ID = None + MD5HASH = hashlib.md5().hexdigest() + URL = "http://localhost" + VER = "v1" + OS = "TEST_OS" + ARCH = "x86_64" + HYPER = "kvm" + + HEADERS = ['agent_id', 'md5hash'] + FIELDS = ['agent_id', 'md5hash'] + + @classmethod + def setUpClass(cls): + opts = cls.get_show_opts(cls.HEADERS) + raw_output = cls.openstack('compute agent create ' + + cls.OS + ' ' + cls.ARCH + ' ' + + cls.VER + ' ' + cls.URL + ' ' + + cls.MD5HASH + ' ' + cls.HYPER + ' ' + + opts) + + # Get agent id because agent can only be deleted by ID + output_list = raw_output.split('\n', 1) + cls.ID = output_list[0] + + cls.assertOutput(cls.MD5HASH + '\n', output_list[1]) + + @classmethod + def tearDownClass(cls): + raw_output = cls.openstack('compute agent delete ' + cls.ID) + cls.assertOutput('', raw_output) + + def test_agent_list(self): + raw_output = self.openstack('compute agent list') + self.assertIn(self.ID, raw_output) + self.assertIn(self.OS, raw_output) + self.assertIn(self.ARCH, raw_output) + self.assertIn(self.VER, raw_output) + self.assertIn(self.URL, raw_output) + self.assertIn(self.MD5HASH, raw_output) + self.assertIn(self.HYPER, raw_output) + + def test_agent_set(self): + ver = 'v2' + url = "http://openstack" + md5hash = hashlib.md5().hexdigest() + + raw_output = self.openstack('compute agent set ' + + self.ID + ' ' + ver + ' ' + + url + ' ' + md5hash) + self.assertEqual('', raw_output) + + raw_output = self.openstack('compute agent list') + self.assertIn(self.ID, raw_output) + self.assertIn(ver, raw_output) + self.assertIn(url, raw_output) + self.assertIn(md5hash, raw_output) diff --git a/openstackclient/compute/v2/agent.py b/openstackclient/compute/v2/agent.py index 59d7dc66e3..d5e860330f 100644 --- a/openstackclient/compute/v2/agent.py +++ b/openstackclient/compute/v2/agent.py @@ -112,7 +112,7 @@ def take_action(self, parsed_args): ) for s in data)) -class SetAgent(command.ShowOne): +class SetAgent(command.Command): """Set compute agent command""" def get_parser(self, prog_name): @@ -143,5 +143,4 @@ def take_action(self, parsed_args): parsed_args.url, parsed_args.md5hash ) - agent = compute_client.agents.update(*args)._info.copy() - return zip(*sorted(six.iteritems(agent))) + compute_client.agents.update(*args) diff --git a/releasenotes/notes/bug-1546065-41d09ffbd8606513.yaml b/releasenotes/notes/bug-1546065-41d09ffbd8606513.yaml index f0c3463828..a28290cfde 100644 --- a/releasenotes/notes/bug-1546065-41d09ffbd8606513.yaml +++ b/releasenotes/notes/bug-1546065-41d09ffbd8606513.yaml @@ -4,3 +4,5 @@ fixes: [Bug `1546065 `_] - Command ``security group set`` now outputs nothing. [Bug `1546065 `_] + - Command ``compute agent set`` now outputs nothing. + [Bug `1546065 `_] From ba826fa04fd5f16658da0319f34e26f14d7716d2 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Wed, 17 Feb 2016 15:16:33 +0800 Subject: [PATCH 0661/3095] Make SetAggregate inherit from cliff.Command set/unset comamnd classes should inherit from cliff.Command class. Also, this patch adds functional tests for aggregate. And also, use utils.format_dict() to format the output of the properties dict. Change-Id: Idb50bef8990da95666960e2414dfd7c9be234bba Partial-bug: #1519503 Closes-Bug: 1546065 --- doc/source/backwards-incompatible.rst | 12 ++++ functional/tests/compute/v2/test_aggregate.py | 56 +++++++++++++++++++ openstackclient/compute/v2/aggregate.py | 28 +++++----- .../notes/bug-1546065-41d09ffbd8606513.yaml | 2 + 4 files changed, 84 insertions(+), 14 deletions(-) create mode 100644 functional/tests/compute/v2/test_aggregate.py diff --git a/doc/source/backwards-incompatible.rst b/doc/source/backwards-incompatible.rst index 76a3b95187..bb2b0bddb9 100644 --- a/doc/source/backwards-incompatible.rst +++ b/doc/source/backwards-incompatible.rst @@ -150,6 +150,18 @@ List of Backwards Incompatible Changes * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1546065 * Commit: https://review.openstack.org/#/c/281088/ +13. `aggregate set` commands will no longer return the modified resource + + Previously, modifying an aggregate would result in the new aggregate being + displayed to the user. To keep things consistent with other `set` commands, + we will no longer be showing the modified resource. + + * In favor of: Use `set` then `show` + * As of: NA + * Removed in: NA + * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1546065 + * Commit: https://review.openstack.org/#/c/281089/ + For Developers ============== diff --git a/functional/tests/compute/v2/test_aggregate.py b/functional/tests/compute/v2/test_aggregate.py new file mode 100644 index 0000000000..73e51ede96 --- /dev/null +++ b/functional/tests/compute/v2/test_aggregate.py @@ -0,0 +1,56 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import uuid + +from functional.common import test + + +class AggregateTests(test.TestCase): + """Functional tests for aggregate. """ + + NAME = uuid.uuid4().hex + HEADERS = ['Name'] + FIELDS = ['name'] + + @classmethod + def setUpClass(cls): + opts = cls.get_show_opts(cls.FIELDS) + raw_output = cls.openstack('aggregate create ' + cls.NAME + opts) + expected = cls.NAME + '\n' + cls.assertOutput(expected, raw_output) + + @classmethod + def tearDownClass(cls): + raw_output = cls.openstack('aggregate delete ' + cls.NAME) + cls.assertOutput('', raw_output) + + def test_aggregate_list(self): + opts = self.get_list_opts(self.HEADERS) + raw_output = self.openstack('aggregate list' + opts) + self.assertIn(self.NAME, raw_output) + + def test_aggregate_show(self): + opts = self.get_show_opts(self.FIELDS) + raw_output = self.openstack('aggregate show ' + self.NAME + opts) + self.assertEqual(self.NAME + "\n", raw_output) + + def test_aggregate_properties(self): + opts = self.get_show_opts(['properties']) + + raw_output = self.openstack( + 'aggregate set --property a=b --property c=d ' + self.NAME + ) + self.assertEqual('', raw_output) + + raw_output = self.openstack('aggregate show ' + self.NAME + opts) + self.assertIn("a='b', c='d'\n", raw_output) diff --git a/openstackclient/compute/v2/aggregate.py b/openstackclient/compute/v2/aggregate.py index 5f297257dd..e47c13a7b2 100644 --- a/openstackclient/compute/v2/aggregate.py +++ b/openstackclient/compute/v2/aggregate.py @@ -201,7 +201,7 @@ def take_action(self, parsed_args): return zip(*sorted(six.iteritems(info))) -class SetAggregate(command.ShowOne): +class SetAggregate(command.Command): """Set aggregate properties""" def get_parser(self, prog_name): @@ -238,28 +238,22 @@ def take_action(self, parsed_args): parsed_args.aggregate, ) - info = {} kwargs = {} if parsed_args.name: kwargs['name'] = parsed_args.name if parsed_args.zone: kwargs['availability_zone'] = parsed_args.zone if kwargs: - info.update(compute_client.aggregates.update( + compute_client.aggregates.update( aggregate, kwargs - )._info) + ) if parsed_args.property: - info.update(compute_client.aggregates.set_metadata( + compute_client.aggregates.set_metadata( aggregate, - parsed_args.property, - )._info) - - if info: - return zip(*sorted(six.iteritems(info))) - else: - return ({}, {}) + parsed_args.property + ) class ShowAggregate(command.ShowOne): @@ -284,8 +278,14 @@ def take_action(self, parsed_args): # Remove availability_zone from metadata because Nova doesn't if 'availability_zone' in data.metadata: data.metadata.pop('availability_zone') - # Map 'metadata' column to 'properties' - data._info.update({'properties': data._info.pop('metadata')}) + + # Special mapping for columns to make the output easier to read: + # 'metadata' --> 'properties' + data._info.update( + { + 'properties': utils.format_dict(data._info.pop('metadata')), + }, + ) info = {} info.update(data._info) diff --git a/releasenotes/notes/bug-1546065-41d09ffbd8606513.yaml b/releasenotes/notes/bug-1546065-41d09ffbd8606513.yaml index a28290cfde..7e8d9e7ad0 100644 --- a/releasenotes/notes/bug-1546065-41d09ffbd8606513.yaml +++ b/releasenotes/notes/bug-1546065-41d09ffbd8606513.yaml @@ -6,3 +6,5 @@ fixes: [Bug `1546065 `_] - Command ``compute agent set`` now outputs nothing. [Bug `1546065 `_] + - Command ``aggregate set`` now outputs nothing. + [Bug `1546065 `_] From ada06f4dc3df39db5e739ebd8d82ccd734a54a93 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Wed, 24 Feb 2016 14:56:35 +0800 Subject: [PATCH 0662/3095] Add MultiKeyValueAction to custom parser action Class MultiKeyValueAction will be used to parse arguments like this: --route destination=xxx,gateway=xxx --route destination=yyy,gateway=yyy The result is a list like this: [{destination:xxx, gateway:xxx}, {destination:yyy, gateway:yyy}] This action also contain validation of the parameters. Change-Id: Ie3aa8635c6a13fc2e429fe6922acd681dc7244cf --- openstackclient/common/parseractions.py | 81 +++++++++++ .../tests/common/test_parseractions.py | 129 ++++++++++++++++++ 2 files changed, 210 insertions(+) diff --git a/openstackclient/common/parseractions.py b/openstackclient/common/parseractions.py index fd90369a7f..7d332a5f5b 100644 --- a/openstackclient/common/parseractions.py +++ b/openstackclient/common/parseractions.py @@ -17,6 +17,8 @@ import argparse +from openstackclient.i18n import _ + class KeyValueAction(argparse.Action): """A custom action to parse arguments as key=value pairs @@ -36,6 +38,85 @@ def __call__(self, parser, namespace, values, option_string=None): getattr(namespace, self.dest, {}).pop(values, None) +class MultiKeyValueAction(argparse.Action): + """A custom action to parse arguments as key1=value1,key2=value2 pairs + + Ensure that ``dest`` is a list. The list will finally contain multiple + dicts, with key=value pairs in them. + + NOTE: The arguments string should be a comma separated key-value pairs. + And comma(',') and equal('=') may not be used in the key or value. + """ + + def __init__(self, option_strings, dest, nargs=None, + required_keys=None, optional_keys=None, **kwargs): + """Initialize the action object, and parse customized options + + Required keys and optional keys can be specified when initializing + the action to enable the key validation. If none of them specified, + the key validation will be skipped. + + :param required_keys: a list of required keys + :param optional_keys: a list of optional keys + """ + if nargs: + raise ValueError("Parameter 'nargs' is not allowed, but got %s" + % nargs) + + super(MultiKeyValueAction, self).__init__(option_strings, + dest, **kwargs) + + # required_keys: A list of keys that is required. None by default. + if required_keys and not isinstance(required_keys, list): + raise TypeError("'required_keys' must be a list") + self.required_keys = set(required_keys or []) + + # optional_keys: A list of keys that is optional. None by default. + if optional_keys and not isinstance(optional_keys, list): + raise TypeError("'optional_keys' must be a list") + self.optional_keys = set(optional_keys or []) + + def __call__(self, parser, namespace, values, metavar=None): + # Make sure we have an empty list rather than None + if getattr(namespace, self.dest, None) is None: + setattr(namespace, self.dest, []) + + params = {} + for kv in values.split(','): + # Add value if an assignment else raise ArgumentTypeError + if '=' in kv: + params.update([kv.split('=', 1)]) + else: + msg = ("Expected key=value pairs separated by comma, " + "but got: %s" % (str(kv))) + raise argparse.ArgumentTypeError(self, msg) + + # Check key validation + valid_keys = self.required_keys | self.optional_keys + if valid_keys: + invalid_keys = [k for k in params if k not in valid_keys] + if invalid_keys: + msg = _("Invalid keys %(invalid_keys)s specified.\n" + "Valid keys are: %(valid_keys)s.") + raise argparse.ArgumentTypeError( + msg % {'invalid_keys': ', '.join(invalid_keys), + 'valid_keys': ', '.join(valid_keys)} + ) + + if self.required_keys: + missing_keys = [k for k in self.required_keys if k not in params] + if missing_keys: + msg = _("Missing required keys %(missing_keys)s.\n" + "Required keys are: %(required_keys)s.") + raise argparse.ArgumentTypeError( + msg % {'missing_keys': ', '.join(missing_keys), + 'required_keys': ', '.join(self.required_keys)} + ) + + # Update the dest dict + getattr(namespace, self.dest, []).append(params) + + class RangeAction(argparse.Action): """A custom action to parse a single value or a range of values diff --git a/openstackclient/tests/common/test_parseractions.py b/openstackclient/tests/common/test_parseractions.py index 0109a3f3d9..a4ee07bf43 100644 --- a/openstackclient/tests/common/test_parseractions.py +++ b/openstackclient/tests/common/test_parseractions.py @@ -61,6 +61,135 @@ def test_error_values(self): self.assertDictEqual(expect, actual) +class TestMultiKeyValueAction(utils.TestCase): + + def setUp(self): + super(TestMultiKeyValueAction, self).setUp() + + self.parser = argparse.ArgumentParser() + + # Set up our typical usage + self.parser.add_argument( + '--test', + metavar='req1=xxx,req2=yyy', + action=parseractions.MultiKeyValueAction, + dest='test', + default=None, + required_keys=['req1', 'req2'], + optional_keys=['opt1', 'opt2'], + help='Test' + ) + + def test_good_values(self): + results = self.parser.parse_args([ + '--test', 'req1=aaa,req2=bbb', + '--test', 'req1=,req2=', + ]) + + actual = getattr(results, 'test', []) + expect = [ + {'req1': 'aaa', 'req2': 'bbb'}, + {'req1': '', 'req2': ''}, + ] + # Need to sort the lists before comparing them + key = lambda x: x['req1'] + expect.sort(key=key) + actual.sort(key=key) + self.assertListEqual(expect, actual) + + def test_empty_required_optional(self): + self.parser.add_argument( + '--test-empty', + metavar='req1=xxx,req2=yyy', + action=parseractions.MultiKeyValueAction, + dest='test_empty', + default=None, + required_keys=[], + optional_keys=[], + help='Test' + ) + + results = self.parser.parse_args([ + '--test-empty', 'req1=aaa,req2=bbb', + '--test-empty', 'req1=,req2=', + ]) + + actual = getattr(results, 'test_empty', []) + expect = [ + {'req1': 'aaa', 'req2': 'bbb'}, + {'req1': '', 'req2': ''}, + ] + # Need to sort the lists before comparing them + key = lambda x: x['req1'] + expect.sort(key=key) + actual.sort(key=key) + self.assertListEqual(expect, actual) + + def test_error_values_with_comma(self): + self.assertRaises( + argparse.ArgumentTypeError, + self.parser.parse_args, + [ + '--test', 'mmm,nnn=zzz', + ] + ) + + def test_error_values_without_comma(self): + self.assertRaises( + argparse.ArgumentTypeError, + self.parser.parse_args, + [ + '--test', 'mmmnnn', + ] + ) + + def test_missing_key(self): + self.assertRaises( + argparse.ArgumentTypeError, + self.parser.parse_args, + [ + '--test', 'req2=ddd', + ] + ) + + def test_invalid_key(self): + self.assertRaises( + argparse.ArgumentTypeError, + self.parser.parse_args, + [ + '--test', 'req1=aaa,req2=bbb,aaa=req1', + ] + ) + + def test_required_keys_not_list(self): + self.assertRaises( + TypeError, + self.parser.add_argument, + '--test-required-dict', + metavar='req1=xxx,req2=yyy', + action=parseractions.MultiKeyValueAction, + dest='test_required_dict', + default=None, + required_keys={'aaa': 'bbb'}, + optional_keys=['opt1', 'opt2'], + help='Test' + ) + + def test_optional_keys_not_list(self): + self.assertRaises( + TypeError, + self.parser.add_argument, + '--test-optional-dict', + metavar='req1=xxx,req2=yyy', + action=parseractions.MultiKeyValueAction, + dest='test_optional_dict', + default=None, + required_keys=['req1', 'req2'], + optional_keys={'aaa': 'bbb'}, + help='Test' + ) + + class TestNonNegativeAction(utils.TestCase): def setUp(self): From 01c19ef0bc6abc0dbbd76666bdc0c5dda7ba0196 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Thu, 4 Feb 2016 13:19:01 +0800 Subject: [PATCH 0663/3095] Router: Add --route and --clear-routes options to "router set" command --route option is used to set routes to the router. It is used like this: --route destination=subnet,gateway=ip-address destination: destination subnet CIDR gateway: nexthop IP address --clear-routes is used to clear all routes on the router. Change-Id: I97ce4871113c684b29c98cdad4dec9cc80ed20f7 Implements: blueprint neutron-client Partial-bug: #1519503 --- doc/source/command-objects/router.rst | 12 ++++ openstackclient/network/v2/router.py | 30 ++++++++-- .../tests/network/v2/test_router.py | 57 +++++++++++++++++++ 3 files changed, 95 insertions(+), 4 deletions(-) diff --git a/doc/source/command-objects/router.rst b/doc/source/command-objects/router.rst index 2a5bf0539c..6b8b357b1c 100644 --- a/doc/source/command-objects/router.rst +++ b/doc/source/command-objects/router.rst @@ -93,6 +93,7 @@ Set router properties [--name ] [--enable | --disable] [--distributed | --centralized] + [--route destination=,gateway= | --clear-routes] .. option:: --name @@ -115,6 +116,17 @@ Set router properties Set router to centralized mode (disabled router only) +.. option:: --route destination=,gateway= + + Routes associated with the router. + Repeat this option to set multiple routes. + destination: destination subnet (in CIDR notation). + gateway: nexthop IP address. + +.. option:: --clear-routes + + Clear routes associated with the router + .. _router_set-router: .. describe:: diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 60db816ac6..e4eea3f8ab 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -17,6 +17,7 @@ from openstackclient.common import command from openstackclient.common import exceptions +from openstackclient.common import parseractions from openstackclient.common import utils from openstackclient.identity import common as identity_common @@ -51,6 +52,12 @@ def _get_attrs(client_manager, parsed_args): if ('availability_zone_hints' in parsed_args and parsed_args.availability_zone_hints is not None): attrs['availability_zone_hints'] = parsed_args.availability_zone_hints + + if 'clear_routes' in parsed_args and parsed_args.clear_routes: + attrs['routes'] = [] + elif 'routes' in parsed_args and parsed_args.routes is not None: + attrs['routes'] = parsed_args.routes + # "router set" command doesn't support setting project. if 'project' in parsed_args and parsed_args.project is not None: identity_client = client_manager.identity @@ -63,7 +70,6 @@ def _get_attrs(client_manager, parsed_args): # TODO(tangchen): Support getting 'ha' property. # TODO(tangchen): Support getting 'external_gateway_info' property. - # TODO(tangchen): Support getting 'routes' property. return attrs @@ -250,6 +256,25 @@ def get_parser(self, prog_name): action='store_false', help="Set router to centralized mode (disabled router only)", ) + routes_group = parser.add_mutually_exclusive_group() + routes_group.add_argument( + '--route', + metavar='destination=,gateway=', + action=parseractions.MultiKeyValueAction, + dest='routes', + default=None, + required_keys=['destination', 'gateway'], + help="Routes associated with the router. " + "Repeat this option to set multiple routes. " + "destination: destination subnet (in CIDR notation). " + "gateway: nexthop IP address.", + ) + routes_group.add_argument( + '--clear-routes', + dest='clear_routes', + action='store_true', + help="Clear routes associated with the router", + ) # TODO(tangchen): Support setting 'ha' property in 'router set' # command. It appears that changing the ha state is supported by @@ -258,9 +283,6 @@ def get_parser(self, prog_name): # TODO(tangchen): Support setting 'external_gateway_info' property in # 'router set' command. - # TODO(tangchen): Support setting 'routes' property in 'router set' - # command. - return parser def take_action(self, parsed_args): diff --git a/openstackclient/tests/network/v2/test_router.py b/openstackclient/tests/network/v2/test_router.py index 05bb7857e2..794f8ab5c6 100644 --- a/openstackclient/tests/network/v2/test_router.py +++ b/openstackclient/tests/network/v2/test_router.py @@ -306,6 +306,63 @@ def test_set_distributed_centralized(self): self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) + def test_set_route(self): + arglist = [ + self._router.name, + '--route', 'destination=10.20.30.0/24,gateway=10.20.30.1', + ] + verifylist = [ + ('router', self._router.name), + ('routes', [{'destination': '10.20.30.0/24', + 'gateway': '10.20.30.1'}]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + attrs = { + 'routes': [{'destination': '10.20.30.0/24', + 'gateway': '10.20.30.1'}], + } + self.network.update_router.assert_called_with(self._router, **attrs) + self.assertIsNone(result) + + def test_set_clear_routes(self): + arglist = [ + self._router.name, + '--clear-routes', + ] + verifylist = [ + ('router', self._router.name), + ('clear_routes', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + attrs = { + 'routes': [], + } + self.network.update_router.assert_called_with(self._router, **attrs) + self.assertIsNone(result) + + def test_set_route_clear_routes(self): + arglist = [ + self._router.name, + '--route', 'destination=10.20.30.0/24,gateway=10.20.30.1', + '--clear-routes', + ] + verifylist = [ + ('router', self._router.name), + ('routes', [{'destination': '10.20.30.0/24', + 'gateway': '10.20.30.1'}]), + ('clear_routes', True), + ] + + # Argument parse failing should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + def test_set_nothing(self): arglist = [self._router.name, ] verifylist = [('router', self._router.name), ] From 058232b9998b760d508bf1e8746e429ad45e4e8d Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 27 Feb 2016 15:38:04 +0800 Subject: [PATCH 0664/3095] [Compute] Check return value is None in compute unit tests. take_action() in commands inheriting from Command returns nothing. So we should assert the return is None in the unit tests of these commands. Change-Id: I953480ecff3b5beb12255d866d0e1df45f130efd Partial-Bug: #1550636 --- .../tests/compute/v2/test_flavor.py | 4 +- .../tests/compute/v2/test_server.py | 65 +++++++------------ .../tests/compute/v2/test_service.py | 9 ++- 3 files changed, 33 insertions(+), 45 deletions(-) diff --git a/openstackclient/tests/compute/v2/test_flavor.py b/openstackclient/tests/compute/v2/test_flavor.py index 781e3068ae..5000e6a0e1 100644 --- a/openstackclient/tests/compute/v2/test_flavor.py +++ b/openstackclient/tests/compute/v2/test_flavor.py @@ -49,12 +49,12 @@ def test_flavor_delete(self): verifylist = [ ('flavor', self.flavor.id), ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) self.flavors_mock.delete.assert_called_with(self.flavor.id) + self.assertIsNone(result) def test_flavor_delete_with_unexist_flavor(self): self.flavors_mock.get.side_effect = exceptions.NotFound(None) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index d379b173f4..aa8d733552 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -78,11 +78,12 @@ def run_method_with_servers(self, method_name, server_count): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) for s in servers: method = getattr(s, method_name) method.assert_called_with() + self.assertIsNone(result) class TestServerCreate(TestServer): @@ -416,11 +417,10 @@ def test_server_delete_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) - self.servers_mock.delete.assert_called_with( - servers[0].id, - ) + self.servers_mock.delete.assert_called_with(servers[0].id) + self.assertIsNone(result) def test_server_delete_multi_servers(self): servers = self.setup_servers_mock(count=3) @@ -435,12 +435,13 @@ def test_server_delete_multi_servers(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) calls = [] for s in servers: calls.append(call(s.id)) self.servers_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) @mock.patch.object(common_utils, 'wait_for_delete', return_value=True) def test_server_delete_wait_ok(self, mock_wait_for_delete): @@ -454,17 +455,15 @@ def test_server_delete_wait_ok(self, mock_wait_for_delete): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) - - self.servers_mock.delete.assert_called_with( - servers[0].id, - ) + result = self.cmd.take_action(parsed_args) + self.servers_mock.delete.assert_called_with(servers[0].id) mock_wait_for_delete.assert_called_once_with( self.servers_mock, servers[0].id, callback=server._show_progress ) + self.assertIsNone(result) @mock.patch.object(common_utils, 'wait_for_delete', return_value=False) def test_server_delete_wait_fails(self, mock_wait_for_delete): @@ -480,10 +479,7 @@ def test_server_delete_wait_fails(self, mock_wait_for_delete): self.assertRaises(SystemExit, self.cmd.take_action, parsed_args) - self.servers_mock.delete.assert_called_with( - servers[0].id, - ) - + self.servers_mock.delete.assert_called_with(servers[0].id) mock_wait_for_delete.assert_called_once_with( self.servers_mock, servers[0].id, @@ -910,15 +906,14 @@ def test_server_resize_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) - self.servers_mock.get.assert_called_with( - self.server.id, - ) + self.servers_mock.get.assert_called_with(self.server.id) self.assertNotCalled(self.servers_mock.resize) self.assertNotCalled(self.servers_mock.confirm_resize) self.assertNotCalled(self.servers_mock.revert_resize) + self.assertIsNone(result) def test_server_resize(self): arglist = [ @@ -933,21 +928,19 @@ def test_server_resize(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) - self.servers_mock.get.assert_called_with( - self.server.id, - ) + self.servers_mock.get.assert_called_with(self.server.id) self.flavors_mock.get.assert_called_with( self.flavors_get_return_value.id, ) - self.servers_mock.resize.assert_called_with( self.server, self.flavors_get_return_value, ) self.assertNotCalled(self.servers_mock.confirm_resize) self.assertNotCalled(self.servers_mock.revert_resize) + self.assertIsNone(result) def test_server_resize_confirm(self): arglist = [ @@ -961,17 +954,13 @@ def test_server_resize_confirm(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) - - self.servers_mock.get.assert_called_with( - self.server.id, - ) + result = self.cmd.take_action(parsed_args) + self.servers_mock.get.assert_called_with(self.server.id) self.assertNotCalled(self.servers_mock.resize) - self.servers_mock.confirm_resize.assert_called_with( - self.server, - ) + self.servers_mock.confirm_resize.assert_called_with(self.server) self.assertNotCalled(self.servers_mock.revert_resize) + self.assertIsNone(result) def test_server_resize_revert(self): arglist = [ @@ -985,17 +974,13 @@ def test_server_resize_revert(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) - - self.servers_mock.get.assert_called_with( - self.server.id, - ) + result = self.cmd.take_action(parsed_args) + self.servers_mock.get.assert_called_with(self.server.id) self.assertNotCalled(self.servers_mock.resize) self.assertNotCalled(self.servers_mock.confirm_resize) - self.servers_mock.revert_resize.assert_called_with( - self.server, - ) + self.servers_mock.revert_resize.assert_called_with(self.server) + self.assertIsNone(result) class TestServerResume(TestServer): diff --git a/openstackclient/tests/compute/v2/test_service.py b/openstackclient/tests/compute/v2/test_service.py index 54adaab3d5..2f8b2e7d10 100644 --- a/openstackclient/tests/compute/v2/test_service.py +++ b/openstackclient/tests/compute/v2/test_service.py @@ -49,11 +49,12 @@ def test_service_delete_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) self.service_mock.delete.assert_called_with( compute_fakes.service_binary, ) + self.assertIsNone(result) class TestServiceList(TestService): @@ -124,12 +125,13 @@ def test_service_set_enable(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) self.service_mock.enable.assert_called_with( compute_fakes.service_host, compute_fakes.service_binary, ) + self.assertIsNone(result) def test_service_set_disable(self): arglist = [ @@ -144,9 +146,10 @@ def test_service_set_disable(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) self.service_mock.disable.assert_called_with( compute_fakes.service_host, compute_fakes.service_binary, ) + self.assertIsNone(result) From 61c1d985c76599ae95651573c440c2d1149ed3b0 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Mon, 29 Feb 2016 15:45:27 +0800 Subject: [PATCH 0665/3095] Fix return value of "image set" command "image set" command should return None. But in one path, it returns ({}, {}). This patch fixes this. Change-Id: I3847e661cb7e89863921a3f0a859d9b1a8077ede --- openstackclient/image/v1/image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index 6e172bce21..0d0855974c 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -691,7 +691,7 @@ def take_action(self, parsed_args): if not kwargs: self.log.warning('no arguments specified') - return {}, {} + return image = image_client.images.update(image.id, **kwargs) finally: From a253217fc2e593d7eca15ed79f444ee412ff1d71 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Mon, 29 Feb 2016 16:58:14 +0800 Subject: [PATCH 0666/3095] Trivial: Reorder unit tests in test_type.py Unit test classes should be in alphabetical order. Change-Id: Ie741e1c170d8cc361d95d036115d0952e5108088 --- openstackclient/tests/volume/v2/test_type.py | 113 ++++++++++--------- 1 file changed, 58 insertions(+), 55 deletions(-) diff --git a/openstackclient/tests/volume/v2/test_type.py b/openstackclient/tests/volume/v2/test_type.py index f394aff324..1408b9d93c 100644 --- a/openstackclient/tests/volume/v2/test_type.py +++ b/openstackclient/tests/volume/v2/test_type.py @@ -61,7 +61,7 @@ def setUp(self): self.types_mock.create.return_value = fakes.FakeResource( None, copy.deepcopy(volume_fakes.TYPE), - loaded=True, + loaded=True ) # Get the command object to test self.cmd = volume_type.CreateVolumeType(self.app, None) @@ -115,6 +115,35 @@ def test_type_create_private(self): self.assertEqual(self.datalist, data) +class TestTypeDelete(TestType): + + def setUp(self): + super(TestTypeDelete, self).setUp() + + self.types_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.TYPE), + loaded=True + ) + self.types_mock.delete.return_value = None + + # Get the command object to mock + self.cmd = volume_type.DeleteVolumeType(self.app, None) + + def test_type_delete(self): + arglist = [ + volume_fakes.type_id + ] + verifylist = [ + ("volume_type", volume_fakes.type_id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + self.types_mock.delete.assert_called_with(volume_fakes.type_id) + + class TestTypeList(TestType): columns = [ @@ -170,34 +199,6 @@ def test_type_list_with_options(self): self.assertEqual(datalist, tuple(data)) -class TestTypeShow(TestType): - - def setUp(self): - super(TestTypeShow, self).setUp() - - self.types_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.TYPE), - loaded=True) - # Get the command object to test - self.cmd = volume_type.ShowVolumeType(self.app, None) - - def test_type_show(self): - arglist = [ - volume_fakes.type_id - ] - verifylist = [ - ("volume_type", volume_fakes.type_id) - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - self.types_mock.get.assert_called_with(volume_fakes.type_id) - - self.assertEqual(volume_fakes.TYPE_FORMATTED_columns, columns) - self.assertEqual(volume_fakes.TYPE_FORMATTED_data, data) - - class TestTypeSet(TestType): def setUp(self): @@ -282,61 +283,63 @@ def test_type_set_property(self): self.assertEqual('myvalue', result['myprop']) -class TestTypeUnset(TestType): +class TestTypeShow(TestType): def setUp(self): - super(TestTypeUnset, self).setUp() + super(TestTypeShow, self).setUp() - self.types_mock.get.return_value = FakeTypeResource( + self.types_mock.get.return_value = fakes.FakeResource( None, copy.deepcopy(volume_fakes.TYPE), - loaded=True, + loaded=True ) - self.cmd = volume_type.UnsetVolumeType(self.app, None) + # Get the command object to test + self.cmd = volume_type.ShowVolumeType(self.app, None) - def test_type_unset(self): + def test_type_show(self): arglist = [ - '--property', 'property', - volume_fakes.type_id, + volume_fakes.type_id ] verifylist = [ - ('property', 'property'), - ('volume_type', volume_fakes.type_id), + ("volume_type", volume_fakes.type_id) ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) - - result = self.types_mock.get.return_value._keys + columns, data = self.cmd.take_action(parsed_args) + self.types_mock.get.assert_called_with(volume_fakes.type_id) - self.assertNotIn('property', result) + self.assertEqual(volume_fakes.TYPE_FORMATTED_columns, columns) + self.assertEqual(volume_fakes.TYPE_FORMATTED_data, data) -class TestTypeDelete(TestType): +class TestTypeUnset(TestType): def setUp(self): - super(TestTypeDelete, self).setUp() + super(TestTypeUnset, self).setUp() - self.types_mock.get.return_value = fakes.FakeResource( + self.types_mock.get.return_value = FakeTypeResource( None, copy.deepcopy(volume_fakes.TYPE), - loaded=True) - self.types_mock.delete.return_value = None + loaded=True + ) - # Get the command object to mock - self.cmd = volume_type.DeleteVolumeType(self.app, None) + self.cmd = volume_type.UnsetVolumeType(self.app, None) - def test_type_delete(self): + def test_type_unset(self): arglist = [ - volume_fakes.type_id + '--property', 'property', + volume_fakes.type_id, ] verifylist = [ - ("volume_type", volume_fakes.type_id) + ('property', 'property'), + ('volume_type', volume_fakes.type_id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) - self.types_mock.delete.assert_called_with(volume_fakes.type_id) + + result = self.types_mock.get.return_value._keys + + self.assertNotIn('property', result) From 3d7430463c733e1dfcf8487ce955abd863f9f617 Mon Sep 17 00:00:00 2001 From: Mohan Muppidi Date: Sun, 28 Feb 2016 09:43:44 +0000 Subject: [PATCH 0667/3095] take_action() method from command.Command shouldn't return command.Command and command.Showone are base classes implemented in cliff framework. Showone extends Command to allow take_action() to return data to be formatted using a user-selectable formatter. Most of the classes which are extended from Command in openstackclient/identity/v3/ in some cases return data or return nothing where it is not necessary, this commit fixes most of them. Change-Id: I84c72ea4d6680f8bdbef5449316dd9a8af8c8286 Closes-Bug: 1550892 --- openstackclient/identity/v3/consumer.py | 2 -- openstackclient/identity/v3/credential.py | 3 --- openstackclient/identity/v3/domain.py | 2 -- openstackclient/identity/v3/endpoint.py | 4 ---- openstackclient/identity/v3/federation_protocol.py | 1 - openstackclient/identity/v3/group.py | 2 -- openstackclient/identity/v3/identity_provider.py | 1 - openstackclient/identity/v3/mapping.py | 1 - openstackclient/identity/v3/policy.py | 2 -- openstackclient/identity/v3/project.py | 6 ++---- openstackclient/identity/v3/region.py | 3 --- openstackclient/identity/v3/role.py | 14 ++++++-------- openstackclient/identity/v3/service.py | 11 ++++++----- openstackclient/identity/v3/service_provider.py | 1 - openstackclient/identity/v3/token.py | 1 - openstackclient/identity/v3/user.py | 7 ++++--- 16 files changed, 18 insertions(+), 43 deletions(-) diff --git a/openstackclient/identity/v3/consumer.py b/openstackclient/identity/v3/consumer.py index 729839882a..0da4103d54 100644 --- a/openstackclient/identity/v3/consumer.py +++ b/openstackclient/identity/v3/consumer.py @@ -60,7 +60,6 @@ def take_action(self, parsed_args): consumer = utils.find_resource( identity_client.oauth1.consumers, parsed_args.consumer) identity_client.oauth1.consumers.delete(consumer.id) - return class ListConsumer(command.Lister): @@ -107,7 +106,6 @@ def take_action(self, parsed_args): consumer = identity_client.oauth1.consumers.update( consumer.id, **kwargs) - return class ShowConsumer(command.ShowOne): diff --git a/openstackclient/identity/v3/credential.py b/openstackclient/identity/v3/credential.py index 6208b32004..b0d2cafd0b 100644 --- a/openstackclient/identity/v3/credential.py +++ b/openstackclient/identity/v3/credential.py @@ -84,7 +84,6 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity identity_client.credentials.delete(parsed_args.credential) - return class ListCredential(command.Lister): @@ -155,8 +154,6 @@ def take_action(self, parsed_args): blob=parsed_args.data, project=project) - return - class ShowCredential(command.ShowOne): """Show credential command""" diff --git a/openstackclient/identity/v3/domain.py b/openstackclient/identity/v3/domain.py index cec967b897..bf248fab9a 100644 --- a/openstackclient/identity/v3/domain.py +++ b/openstackclient/identity/v3/domain.py @@ -100,7 +100,6 @@ def take_action(self, parsed_args): domain = utils.find_resource(identity_client.domains, parsed_args.domain) identity_client.domains.delete(domain.id) - return class ListDomain(command.Lister): @@ -168,7 +167,6 @@ def take_action(self, parsed_args): sys.stdout.write("Domain not updated, no arguments present") return identity_client.domains.update(domain.id, **kwargs) - return class ShowDomain(command.ShowOne): diff --git a/openstackclient/identity/v3/endpoint.py b/openstackclient/identity/v3/endpoint.py index 1eff3b3b14..6e4b356d0b 100644 --- a/openstackclient/identity/v3/endpoint.py +++ b/openstackclient/identity/v3/endpoint.py @@ -109,7 +109,6 @@ def take_action(self, parsed_args): endpoint_id = utils.find_resource(identity_client.endpoints, parsed_args.endpoint).id identity_client.endpoints.delete(endpoint_id) - return class ListEndpoint(command.Lister): @@ -221,7 +220,6 @@ def take_action(self, parsed_args): if parsed_args.service: service = common.find_service(identity_client, parsed_args.service) service_id = service.id - enabled = None if parsed_args.enabled: enabled = True @@ -237,8 +235,6 @@ def take_action(self, parsed_args): enabled=enabled ) - return - class ShowEndpoint(command.ShowOne): """Display endpoint details""" diff --git a/openstackclient/identity/v3/federation_protocol.py b/openstackclient/identity/v3/federation_protocol.py index 2d7ab15dfa..27c837c57b 100644 --- a/openstackclient/identity/v3/federation_protocol.py +++ b/openstackclient/identity/v3/federation_protocol.py @@ -83,7 +83,6 @@ def take_action(self, parsed_args): identity_client = self.app.client_manager.identity identity_client.federation.protocols.delete( parsed_args.identity_provider, parsed_args.federation_protocol) - return class ListProtocols(command.Lister): diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py index b3d893ced6..a4cdd583e8 100644 --- a/openstackclient/identity/v3/group.py +++ b/openstackclient/identity/v3/group.py @@ -182,7 +182,6 @@ def take_action(self, parsed_args): group, parsed_args.domain) identity_client.groups.delete(group_obj.id) - return class ListGroup(command.Lister): @@ -322,7 +321,6 @@ def take_action(self, parsed_args): sys.stderr.write("Group not updated, no arguments present") return identity_client.groups.update(group.id, **kwargs) - return class ShowGroup(command.ShowOne): diff --git a/openstackclient/identity/v3/identity_provider.py b/openstackclient/identity/v3/identity_provider.py index 0ff8acb9a1..37f79ed6d0 100644 --- a/openstackclient/identity/v3/identity_provider.py +++ b/openstackclient/identity/v3/identity_provider.py @@ -102,7 +102,6 @@ def take_action(self, parsed_args): identity_client = self.app.client_manager.identity identity_client.federation.identity_providers.delete( parsed_args.identity_provider) - return class ListIdentityProvider(command.Lister): diff --git a/openstackclient/identity/v3/mapping.py b/openstackclient/identity/v3/mapping.py index 422d66bcbd..3cdc8afc99 100644 --- a/openstackclient/identity/v3/mapping.py +++ b/openstackclient/identity/v3/mapping.py @@ -121,7 +121,6 @@ def take_action(self, parsed_args): identity_client = self.app.client_manager.identity identity_client.federation.mappings.delete(parsed_args.mapping) - return class ListMapping(command.Lister): diff --git a/openstackclient/identity/v3/policy.py b/openstackclient/identity/v3/policy.py index 503df37404..3c2d1a7c94 100644 --- a/openstackclient/identity/v3/policy.py +++ b/openstackclient/identity/v3/policy.py @@ -69,7 +69,6 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity identity_client.policies.delete(parsed_args.policy) - return class ListPolicy(command.Lister): @@ -139,7 +138,6 @@ def take_action(self, parsed_args): sys.stdout.write('Policy not updated, no arguments present \n') return identity_client.policies.update(parsed_args.policy, **kwargs) - return class ShowPolicy(command.ShowOne): diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index 61db861441..a379c6fa34 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -153,7 +153,6 @@ def take_action(self, parsed_args): project_obj = utils.find_resource(identity_client.projects, project) identity_client.projects.delete(project_obj.id) - return class ListProject(command.Lister): @@ -267,8 +266,8 @@ def take_action(self, parsed_args): and not parsed_args.property and not parsed_args.disable): return - - project = common.find_project(identity_client, parsed_args.project, + project = common.find_project(identity_client, + parsed_args.project, parsed_args.domain) kwargs = {} @@ -284,7 +283,6 @@ def take_action(self, parsed_args): kwargs.update(parsed_args.property) identity_client.projects.update(project.id, **kwargs) - return class ShowProject(command.ShowOne): diff --git a/openstackclient/identity/v3/region.py b/openstackclient/identity/v3/region.py index 1e15fd203c..ec5042286b 100644 --- a/openstackclient/identity/v3/region.py +++ b/openstackclient/identity/v3/region.py @@ -75,7 +75,6 @@ def take_action(self, parsed_args): identity_client = self.app.client_manager.identity identity_client.regions.delete(parsed_args.region) - return class ListRegion(command.Lister): @@ -135,7 +134,6 @@ def take_action(self, parsed_args): if not parsed_args.parent_region and not parsed_args.description: return - kwargs = {} if parsed_args.description: kwargs['description'] = parsed_args.description @@ -143,7 +141,6 @@ def take_action(self, parsed_args): kwargs['parent_region'] = parsed_args.parent_region identity_client.regions.update(parsed_args.region, **kwargs) - return class ShowRegion(command.ShowOne): diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index 4cced61170..1195ab21a5 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -123,7 +123,6 @@ def take_action(self, parsed_args): if (not parsed_args.user and not parsed_args.domain and not parsed_args.group and not parsed_args.project): return - role = utils.find_resource( identity_client.roles, parsed_args.role, @@ -138,7 +137,6 @@ def take_action(self, parsed_args): return identity_client.roles.grant(role.id, **kwargs) - return class CreateRole(command.ShowOne): @@ -197,7 +195,6 @@ def take_action(self, parsed_args): role, ) identity_client.roles.delete(role_obj.id) - return class ListRole(command.Lister): @@ -318,8 +315,10 @@ def take_action(self, parsed_args): if (not parsed_args.user and not parsed_args.domain and not parsed_args.group and not parsed_args.project): + sys.stderr.write("Incorrect set of arguments " + "provided. See openstack --help for more " + "details\n") return - role = utils.find_resource( identity_client.roles, parsed_args.role, @@ -331,9 +330,7 @@ def take_action(self, parsed_args): sys.stderr.write("Role not removed, incorrect set of arguments \ provided. See openstack --help for more details\n") return - identity_client.roles.revoke(role.id, **kwargs) - return class SetRole(command.Command): @@ -357,15 +354,16 @@ def take_action(self, parsed_args): identity_client = self.app.client_manager.identity if not parsed_args.name: + sys.stderr.write("Incorrect set of arguments " + "provided. See openstack --help for more " + "details\n") return - role = utils.find_resource( identity_client.roles, parsed_args.role, ) identity_client.roles.update(role.id, name=parsed_args.name) - return class ShowRole(command.ShowOne): diff --git a/openstackclient/identity/v3/service.py b/openstackclient/identity/v3/service.py index 42117c8de9..355583cc30 100644 --- a/openstackclient/identity/v3/service.py +++ b/openstackclient/identity/v3/service.py @@ -16,6 +16,7 @@ """Identity v3 Service action implementations""" import six +import sys from openstackclient.common import command from openstackclient.common import utils @@ -91,7 +92,6 @@ def take_action(self, parsed_args): service = common.find_service(identity_client, parsed_args.service) identity_client.services.delete(service.id) - return class ListService(command.Lister): @@ -166,10 +166,12 @@ def take_action(self, parsed_args): and not parsed_args.description and not parsed_args.enable and not parsed_args.disable): + sys.stderr.write("Incorrect set of arguments " + "provided. See openstack --help for more " + "details\n") return - - service = common.find_service(identity_client, parsed_args.service) - + service = common.find_service(identity_client, + parsed_args.service) kwargs = {} if parsed_args.type: kwargs['type'] = parsed_args.type @@ -186,7 +188,6 @@ def take_action(self, parsed_args): service.id, **kwargs ) - return class ShowService(command.ShowOne): diff --git a/openstackclient/identity/v3/service_provider.py b/openstackclient/identity/v3/service_provider.py index 6016928c11..e3a22ebb1a 100644 --- a/openstackclient/identity/v3/service_provider.py +++ b/openstackclient/identity/v3/service_provider.py @@ -96,7 +96,6 @@ def take_action(self, parsed_args): service_client = self.app.client_manager.identity service_client.federation.service_providers.delete( parsed_args.service_provider) - return class ListServiceProvider(command.Lister): diff --git a/openstackclient/identity/v3/token.py b/openstackclient/identity/v3/token.py index 5f131939cd..bf039d2fe6 100644 --- a/openstackclient/identity/v3/token.py +++ b/openstackclient/identity/v3/token.py @@ -194,4 +194,3 @@ def take_action(self, parsed_args): identity_client = self.app.client_manager.identity identity_client.tokens.revoke_token(parsed_args.token) - return diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index c694c6aeb6..93b3309077 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -17,6 +17,7 @@ import copy import six +import sys from keystoneauth1 import exceptions as ks_exc @@ -162,7 +163,6 @@ def take_action(self, parsed_args): user_obj = utils.find_resource(identity_client.users, user) identity_client.users.delete(user_obj.id) - return class ListUser(command.Lister): @@ -334,13 +334,15 @@ def take_action(self, parsed_args): and not parsed_args.description and not parsed_args.enable and not parsed_args.disable): + sys.stderr.write("Incorrect set of arguments " + "provided. See openstack --help for more " + "details\n") return user = utils.find_resource( identity_client.users, parsed_args.user, ) - kwargs = {} if parsed_args.name: kwargs['name'] = parsed_args.name @@ -362,7 +364,6 @@ def take_action(self, parsed_args): kwargs['enabled'] = False identity_client.users.update(user.id, **kwargs) - return class SetPasswordUser(command.Command): From 842882f3cbfca6df9a42bc49b0deefdb84509a8e Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Tue, 9 Feb 2016 07:21:01 -0600 Subject: [PATCH 0668/3095] Refactor security group list to use SDK Refactored the 'os security group list' command to use the SDK when neutron is enabled, but continue to use the nova client when nova network is enabled. This refactor also removes the logic for displaying project names instead of project IDs when the --all-projects option is specified. This logic was removed because it is inconsistent with the other network commands. Since neutron will always display security groups across all projects for an admin, the --all-projects option is now hidden when neutron is enabled and the Project column is always displayed. Change-Id: I934a1f5084ef3c5f929d0ffd38ebf5064d799941 Partial-Bug: #1519511 Related-to: blueprint neutron-client --- doc/source/command-objects/security-group.rst | 3 + openstackclient/compute/v2/security_group.py | 54 ------- openstackclient/network/v2/security_group.py | 50 ++++++ openstackclient/tests/compute/v2/fakes.py | 64 ++++++++ .../tests/compute/v2/test_security_group.py | 62 ------- .../tests/network/v2/test_security_group.py | 151 ++++++++++++++++-- .../notes/bug-1519511-74bab0e0d32db043.yaml | 7 + setup.cfg | 2 +- 8 files changed, 267 insertions(+), 126 deletions(-) create mode 100644 releasenotes/notes/bug-1519511-74bab0e0d32db043.yaml diff --git a/doc/source/command-objects/security-group.rst b/doc/source/command-objects/security-group.rst index cf86bda6be..22ea5cb78c 100644 --- a/doc/source/command-objects/security-group.rst +++ b/doc/source/command-objects/security-group.rst @@ -54,6 +54,9 @@ List security groups Display information from all projects (admin only) + *Network version 2 ignores this option and will always display information* + *for all projects.* + security group set ------------------ diff --git a/openstackclient/compute/v2/security_group.py b/openstackclient/compute/v2/security_group.py index 2a7b40f4cd..907175f7d0 100644 --- a/openstackclient/compute/v2/security_group.py +++ b/openstackclient/compute/v2/security_group.py @@ -18,8 +18,6 @@ import six -from keystoneauth1 import exceptions as ks_exc - try: from novaclient.v2 import security_group_rules except ImportError: @@ -169,58 +167,6 @@ def take_action(self, parsed_args): return zip(*sorted(six.iteritems(info))) -class ListSecurityGroup(command.Lister): - """List security groups""" - - def get_parser(self, prog_name): - parser = super(ListSecurityGroup, self).get_parser(prog_name) - parser.add_argument( - '--all-projects', - action='store_true', - default=False, - help='Display information from all projects (admin only)', - ) - return parser - - def take_action(self, parsed_args): - - def _get_project(project_id): - try: - return getattr(project_hash[project_id], 'name', project_id) - except KeyError: - return project_id - - compute_client = self.app.client_manager.compute - columns = ( - "ID", - "Name", - "Description", - ) - column_headers = columns - if parsed_args.all_projects: - # TODO(dtroyer): Translate Project_ID to Project (name) - columns = columns + ('Tenant ID',) - column_headers = column_headers + ('Project',) - search = {'all_tenants': parsed_args.all_projects} - data = compute_client.security_groups.list(search_opts=search) - - project_hash = {} - try: - projects = self.app.client_manager.identity.projects.list() - except ks_exc.ClientException: - # This fails when the user is not an admin, just move along - pass - else: - for project in projects: - project_hash[project.id] = project - - return (column_headers, - (utils.get_item_properties( - s, columns, - formatters={'Tenant ID': _get_project}, - ) for s in data)) - - class ListSecurityGroupRule(command.Lister): """List security group rules""" diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py index 4e122f21a0..29d82b08d3 100644 --- a/openstackclient/network/v2/security_group.py +++ b/openstackclient/network/v2/security_group.py @@ -13,6 +13,8 @@ """Security Group action implementations""" +import argparse + from openstackclient.common import utils from openstackclient.network import common @@ -38,3 +40,51 @@ def take_action_compute(self, client, parsed_args): parsed_args.group, ) client.security_groups.delete(data.id) + + +class ListSecurityGroup(common.NetworkAndComputeLister): + """List security groups""" + + def update_parser_network(self, parser): + # Maintain and hide the argument for backwards compatibility. + # Network will always return all projects for an admin. + parser.add_argument( + '--all-projects', + action='store_true', + default=False, + help=argparse.SUPPRESS, + ) + return parser + + def update_parser_compute(self, parser): + parser.add_argument( + '--all-projects', + action='store_true', + default=False, + help='Display information from all projects (admin only)', + ) + return parser + + def _get_return_data(self, data, include_project=True): + columns = ( + "ID", + "Name", + "Description", + ) + column_headers = columns + if include_project: + columns = columns + ('Tenant ID',) + column_headers = column_headers + ('Project',) + return (column_headers, + (utils.get_item_properties( + s, columns, + ) for s in data)) + + def take_action_network(self, client, parsed_args): + return self._get_return_data(client.security_groups()) + + def take_action_compute(self, client, parsed_args): + search = {'all_tenants': parsed_args.all_projects} + data = client.security_groups.list(search_opts=search) + return self._get_return_data(data, + include_project=parsed_args.all_projects) diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index f4d79ff7c4..29baa8e119 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -298,6 +298,70 @@ def create_hypervisors_stats(attrs={}, count=2): return hypervisors +class FakeSecurityGroup(object): + """Fake one or more security groups.""" + + @staticmethod + def create_one_security_group(attrs=None, methods=None): + """Create a fake security group. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :return: + A FakeResource object, with id, name, etc. + """ + if attrs is None: + attrs = {} + if methods is None: + methods = {} + + # Set default attributes. + security_group_attrs = { + 'id': 'security-group-id-' + uuid.uuid4().hex, + 'name': 'security-group-name-' + uuid.uuid4().hex, + 'description': 'security-group-description-' + uuid.uuid4().hex, + 'tenant_id': 'project-id-' + uuid.uuid4().hex, + 'rules': [], + } + + # Overwrite default attributes. + security_group_attrs.update(attrs) + + # Set default methods. + security_group_methods = {} + + # Overwrite default methods. + security_group_methods.update(methods) + + security_group = fakes.FakeResource( + info=copy.deepcopy(security_group_attrs), + methods=copy.deepcopy(security_group_methods), + loaded=True) + return security_group + + @staticmethod + def create_security_groups(attrs=None, methods=None, count=2): + """Create multiple fake security groups. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :param int count: + The number of security groups to fake + :return: + A list of FakeResource objects faking the security groups + """ + security_groups = [] + for i in range(0, count): + security_groups.append( + FakeSecurityGroup.create_one_security_group(attrs, methods)) + + return security_groups + + class FakeSecurityGroupRule(object): """Fake one or more security group rules.""" diff --git a/openstackclient/tests/compute/v2/test_security_group.py b/openstackclient/tests/compute/v2/test_security_group.py index c6998cb542..01e27b8201 100644 --- a/openstackclient/tests/compute/v2/test_security_group.py +++ b/openstackclient/tests/compute/v2/test_security_group.py @@ -125,65 +125,3 @@ def test_security_group_create_description(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) - - -class TestSecurityGroupList(TestSecurityGroup): - - def setUp(self): - super(TestSecurityGroupList, self).setUp() - - self.secgroups_mock.list.return_value = [ - FakeSecurityGroupResource( - None, - copy.deepcopy(SECURITY_GROUP), - loaded=True, - ), - ] - - # Get the command object to test - self.cmd = security_group.ListSecurityGroup(self.app, None) - - def test_security_group_list_no_options(self): - self.projects_mock.list.return_value = [ - fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ), - ] - - arglist = [] - verifylist = [ - ('all_projects', False), - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # In base command class Lister in cliff, abstract method take_action() - # returns a tuple containing the column names and an iterable - # containing the data to be listed. - columns, data = self.cmd.take_action(parsed_args) - - # Set expected values - kwargs = { - 'search_opts': { - 'all_tenants': False, - }, - } - - self.secgroups_mock.list.assert_called_with( - **kwargs - ) - - collist = ( - 'ID', - 'Name', - 'Description', - ) - self.assertEqual(collist, columns) - datalist = (( - security_group_id, - security_group_name, - security_group_description, - ), ) - self.assertEqual(datalist, tuple(data)) diff --git a/openstackclient/tests/network/v2/test_security_group.py b/openstackclient/tests/network/v2/test_security_group.py index 72da14009c..ea96442584 100644 --- a/openstackclient/tests/network/v2/test_security_group.py +++ b/openstackclient/tests/network/v2/test_security_group.py @@ -14,24 +14,29 @@ import mock from openstackclient.network.v2 import security_group +from openstackclient.tests.compute.v2 import fakes as compute_fakes from openstackclient.tests.network.v2 import fakes as network_fakes -class TestSecurityGroup(network_fakes.TestNetworkV2): +class TestSecurityGroupNetwork(network_fakes.TestNetworkV2): def setUp(self): - super(TestSecurityGroup, self).setUp() + super(TestSecurityGroupNetwork, self).setUp() # Get a shortcut to the network client self.network = self.app.client_manager.network - # Create compute client mocks. - self.app.client_manager.compute = mock.Mock() + +class TestSecurityGroupCompute(compute_fakes.TestComputev2): + + def setUp(self): + super(TestSecurityGroupCompute, self).setUp() + + # Get a shortcut to the compute client self.compute = self.app.client_manager.compute - self.compute.security_groups = mock.Mock() -class TestDeleteSecurityGroupNetwork(TestSecurityGroup): +class TestDeleteSecurityGroupNetwork(TestSecurityGroupNetwork): # The security group to be deleted. _security_group = \ @@ -64,11 +69,11 @@ def test_security_group_delete(self): self.assertIsNone(result) -class TestDeleteSecurityGroupCompute(TestSecurityGroup): +class TestDeleteSecurityGroupCompute(TestSecurityGroupCompute): # The security group to be deleted. _security_group = \ - network_fakes.FakeSecurityGroup.create_one_security_group() + compute_fakes.FakeSecurityGroup.create_one_security_group() def setUp(self): super(TestDeleteSecurityGroupCompute, self).setUp() @@ -81,7 +86,7 @@ def setUp(self): return_value=self._security_group) # Get the command object to test - self.cmd = security_group.DeleteSecurityGroup(self.app, self.namespace) + self.cmd = security_group.DeleteSecurityGroup(self.app, None) def test_security_group_delete(self): arglist = [ @@ -97,3 +102,131 @@ def test_security_group_delete(self): self.compute.security_groups.delete.assert_called_with( self._security_group.id) self.assertIsNone(result) + + +class TestListSecurityGroupNetwork(TestSecurityGroupNetwork): + + # The security group to be listed. + _security_group = \ + network_fakes.FakeSecurityGroup.create_one_security_group() + + expected_columns = ( + 'ID', + 'Name', + 'Description', + 'Project', + ) + + expected_data = (( + _security_group.id, + _security_group.name, + _security_group.description, + _security_group.tenant_id, + ),) + + def setUp(self): + super(TestListSecurityGroupNetwork, self).setUp() + + self.network.security_groups = mock.Mock( + return_value=[self._security_group]) + + # Get the command object to test + self.cmd = security_group.ListSecurityGroup(self.app, self.namespace) + + def test_security_group_list_no_options(self): + arglist = [] + verifylist = [ + ('all_projects', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.security_groups.assert_called_with() + self.assertEqual(self.expected_columns, columns) + self.assertEqual(self.expected_data, tuple(data)) + + def test_security_group_list_all_projects(self): + arglist = [ + '--all-projects', + ] + verifylist = [ + ('all_projects', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.security_groups.assert_called_with() + self.assertEqual(self.expected_columns, columns) + self.assertEqual(self.expected_data, tuple(data)) + + +class TestListSecurityGroupCompute(TestSecurityGroupCompute): + + # The security group to be listed. + _security_group = \ + compute_fakes.FakeSecurityGroup.create_one_security_group() + + expected_columns = ( + 'ID', + 'Name', + 'Description', + ) + expected_columns_all_projects = ( + 'ID', + 'Name', + 'Description', + 'Project', + ) + + expected_data = (( + _security_group.id, + _security_group.name, + _security_group.description, + ),) + expected_data_all_projects = (( + _security_group.id, + _security_group.name, + _security_group.description, + _security_group.tenant_id, + ),) + + def setUp(self): + super(TestListSecurityGroupCompute, self).setUp() + + self.app.client_manager.network_endpoint_enabled = False + self.compute.security_groups.list.return_value = [self._security_group] + + # Get the command object to test + self.cmd = security_group.ListSecurityGroup(self.app, None) + + def test_security_group_list_no_options(self): + arglist = [] + verifylist = [ + ('all_projects', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + kwargs = {'search_opts': {'all_tenants': False}} + self.compute.security_groups.list.assert_called_with(**kwargs) + self.assertEqual(self.expected_columns, columns) + self.assertEqual(self.expected_data, tuple(data)) + + def test_security_group_list_all_projects(self): + arglist = [ + '--all-projects', + ] + verifylist = [ + ('all_projects', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + kwargs = {'search_opts': {'all_tenants': True}} + self.compute.security_groups.list.assert_called_with(**kwargs) + self.assertEqual(self.expected_columns_all_projects, columns) + self.assertEqual(self.expected_data_all_projects, tuple(data)) diff --git a/releasenotes/notes/bug-1519511-74bab0e0d32db043.yaml b/releasenotes/notes/bug-1519511-74bab0e0d32db043.yaml new file mode 100644 index 0000000000..1a70c797e6 --- /dev/null +++ b/releasenotes/notes/bug-1519511-74bab0e0d32db043.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + Ignore the ``security group list`` command ``--all-projects`` option + for Network v2 since security groups will be displayed for all projects + by default (admin only). + [Bug `1519511 `_] diff --git a/setup.cfg b/setup.cfg index 0750d7ef7a..8a15529a89 100644 --- a/setup.cfg +++ b/setup.cfg @@ -100,7 +100,6 @@ openstack.compute.v2 = keypair_show = openstackclient.compute.v2.keypair:ShowKeypair security_group_create = openstackclient.compute.v2.security_group:CreateSecurityGroup - security_group_list = openstackclient.compute.v2.security_group:ListSecurityGroup security_group_set = openstackclient.compute.v2.security_group:SetSecurityGroup security_group_show = openstackclient.compute.v2.security_group:ShowSecurityGroup security_group_rule_create = openstackclient.compute.v2.security_group:CreateSecurityGroupRule @@ -340,6 +339,7 @@ openstack.network.v2 = router_set = openstackclient.network.v2.router:SetRouter router_show = openstackclient.network.v2.router:ShowRouter security_group_delete = openstackclient.network.v2.security_group:DeleteSecurityGroup + security_group_list = openstackclient.network.v2.security_group:ListSecurityGroup security_group_rule_delete = openstackclient.network.v2.security_group_rule:DeleteSecurityGroupRule security_group_rule_show = openstackclient.network.v2.security_group_rule:ShowSecurityGroupRule subnet_list = openstackclient.network.v2.subnet:ListSubnet From b8d72cee27dd81b5749c64945475ea8781cb5fdf Mon Sep 17 00:00:00 2001 From: Jas Date: Mon, 29 Feb 2016 16:06:05 -0600 Subject: [PATCH 0669/3095] fix: Exception message includes unnecessary class args Fix misusages of ArgumentTypeError which causes a tuple of class instance and error message string to be printed rather than just the error message string itsself. Change-Id: I0e997f86bb6603930cc92e90efcb48155f62ffb5 Closes-bug: #1551426 --- openstackclient/common/parseractions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openstackclient/common/parseractions.py b/openstackclient/common/parseractions.py index 7d332a5f5b..c30058c8e7 100644 --- a/openstackclient/common/parseractions.py +++ b/openstackclient/common/parseractions.py @@ -89,7 +89,7 @@ def __call__(self, parser, namespace, values, metavar=None): else: msg = ("Expected key=value pairs separated by comma, " "but got: %s" % (str(kv))) - raise argparse.ArgumentTypeError(self, msg) + raise argparse.ArgumentTypeError(msg) # Check key validation valid_keys = self.required_keys | self.optional_keys @@ -160,4 +160,4 @@ def __call__(self, parser, namespace, values, option_string=None): setattr(namespace, self.dest, values) except Exception: msg = "%s expected a non-negative integer" % (str(option_string)) - raise argparse.ArgumentTypeError(self, msg) + raise argparse.ArgumentTypeError(msg) From 88c92bf71ae026b90b07411772c66b718a7d59e1 Mon Sep 17 00:00:00 2001 From: reedip Date: Tue, 1 Mar 2016 10:46:04 +0900 Subject: [PATCH 0670/3095] Subnet: Add "subnet delete" command using SDK This patch adds "subnet delete" command to osc using sdk. Change-Id: I6be27406b16909c6db2b95417355be302e218a8d Implements: blueprint neutron-client Closes-bug: #1542362 --- doc/source/command-objects/subnet.rst | 16 ++++++++++ openstackclient/network/v2/subnet.py | 18 ++++++++++++ .../tests/network/v2/test_subnet.py | 29 +++++++++++++++++++ .../notes/bug-1542362-ddad607f6d3025f0.yaml | 5 ++++ setup.cfg | 1 + 5 files changed, 69 insertions(+) create mode 100644 releasenotes/notes/bug-1542362-ddad607f6d3025f0.yaml diff --git a/doc/source/command-objects/subnet.rst b/doc/source/command-objects/subnet.rst index 12b056658d..9520a22d6a 100644 --- a/doc/source/command-objects/subnet.rst +++ b/doc/source/command-objects/subnet.rst @@ -34,3 +34,19 @@ Show subnet details .. describe:: Subnet to show (name or ID) + +subnet delete +------------- + +Delete a subnet + +.. program:: subnet delete +.. code:: bash + + os subnet delete + + +.. _subnet_delete-subnet: +.. describe:: + + Subnet to delete (name or ID) diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 7ed02a3a30..96ab35ae74 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -89,3 +89,21 @@ def take_action(self, parsed_args): columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) return (columns, data) + + +class DeleteSubnet(command.Command): + """Delete subnet""" + + def get_parser(self, prog_name): + parser = super(DeleteSubnet, self).get_parser(prog_name) + parser.add_argument( + 'subnet', + metavar="", + help=("Subnet to delete (name or ID)") + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + client.delete_subnet( + client.find_subnet(parsed_args.subnet)) diff --git a/openstackclient/tests/network/v2/test_subnet.py b/openstackclient/tests/network/v2/test_subnet.py index e1e663f462..e844c1388b 100644 --- a/openstackclient/tests/network/v2/test_subnet.py +++ b/openstackclient/tests/network/v2/test_subnet.py @@ -181,3 +181,32 @@ def test_show_all_options(self): self.assertEqual(self.columns, columns) self.assertEqual(list(self.data), list(data)) + + +class TestDeleteSubnet(TestSubnet): + + # The subnet to delete. + _subnet = network_fakes.FakeSubnet.create_one_subnet() + + def setUp(self): + super(TestDeleteSubnet, self).setUp() + + self.network.delete_subnet = mock.Mock(return_value=None) + + self.network.find_subnet = mock.Mock(return_value=self._subnet) + + # Get the command object to test + self.cmd = subnet_v2.DeleteSubnet(self.app, self.namespace) + + def test_delete(self): + arglist = [ + self._subnet.name, + ] + verifylist = [ + ('subnet', self._subnet.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.network.delete_subnet.assert_called_with(self._subnet) + self.assertIsNone(result) diff --git a/releasenotes/notes/bug-1542362-ddad607f6d3025f0.yaml b/releasenotes/notes/bug-1542362-ddad607f6d3025f0.yaml new file mode 100644 index 0000000000..f405522164 --- /dev/null +++ b/releasenotes/notes/bug-1542362-ddad607f6d3025f0.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``subnet delete`` command to openstack-client. + [Bug `1542362 `_] diff --git a/setup.cfg b/setup.cfg index 0750d7ef7a..136510bf3a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -342,6 +342,7 @@ openstack.network.v2 = security_group_delete = openstackclient.network.v2.security_group:DeleteSecurityGroup security_group_rule_delete = openstackclient.network.v2.security_group_rule:DeleteSecurityGroupRule security_group_rule_show = openstackclient.network.v2.security_group_rule:ShowSecurityGroupRule + subnet_delete = openstackclient.network.v2.subnet:DeleteSubnet subnet_list = openstackclient.network.v2.subnet:ListSubnet subnet_show = openstackclient.network.v2.subnet:ShowSubnet subnet_pool_delete = openstackclient.network.v2.subnet_pool:DeleteSubnetPool From 37c0e57d11521c1041642e8b886689409f9b60fc Mon Sep 17 00:00:00 2001 From: Yang Hongyang Date: Tue, 1 Mar 2016 11:21:47 +0800 Subject: [PATCH 0671/3095] Trivial: Reorder flavor op order in flavor.py Classes should be in alphabetical order. Change-Id: I7a35c3a2dd6d36c49f0d54dec5c14609a9168bd0 --- openstackclient/compute/v2/flavor.py | 48 ++++++++++++++-------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index e106bd65c4..b5a7c60cbc 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -218,30 +218,6 @@ def take_action(self, parsed_args): ) for s in data)) -class ShowFlavor(command.ShowOne): - """Display flavor details""" - - def get_parser(self, prog_name): - parser = super(ShowFlavor, self).get_parser(prog_name) - parser.add_argument( - "flavor", - metavar="", - help="Flavor to display (name or ID)", - ) - return parser - - def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - resource_flavor = utils.find_resource(compute_client.flavors, - parsed_args.flavor) - flavor = resource_flavor._info.copy() - flavor.pop("links", None) - - flavor['properties'] = utils.format_dict(resource_flavor.get_keys()) - - return zip(*sorted(six.iteritems(flavor))) - - class SetFlavor(command.Command): """Set flavor properties""" @@ -267,6 +243,30 @@ def take_action(self, parsed_args): flavor.set_keys(parsed_args.property) +class ShowFlavor(command.ShowOne): + """Display flavor details""" + + def get_parser(self, prog_name): + parser = super(ShowFlavor, self).get_parser(prog_name) + parser.add_argument( + "flavor", + metavar="", + help="Flavor to display (name or ID)", + ) + return parser + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + resource_flavor = utils.find_resource(compute_client.flavors, + parsed_args.flavor) + flavor = resource_flavor._info.copy() + flavor.pop("links", None) + + flavor['properties'] = utils.format_dict(resource_flavor.get_keys()) + + return zip(*sorted(six.iteritems(flavor))) + + class UnsetFlavor(command.Command): """Unset flavor properties""" From 3f95e2dd052cf0b24507088da7883e281101b6b9 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Thu, 25 Feb 2016 16:42:45 -0500 Subject: [PATCH 0672/3095] add a checklist for creating a new plugin includes steps for both infra and osc changes. Change-Id: I35ae2eb29cff87f5c971e64badd2927fc9a59bf4 --- doc/source/plugins.rst | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/doc/source/plugins.rst b/doc/source/plugins.rst index 288ee4b1dc..f5bbd6dd61 100644 --- a/doc/source/plugins.rst +++ b/doc/source/plugins.rst @@ -129,3 +129,41 @@ so the version should not contain the leading 'v' character. DEFAULT_API_VERSION + ' (Env: OS_OSCPLUGIN_API_VERSION)') return parser + +Checklist for adding new OpenStack plugins +========================================== + +Creating the initial plugin described above is the first step. There are a few +more steps needed to fully integrate the client with openstackclient. + +Add the command checker to your CI +---------------------------------- + +#. Modify the section of ``zuul/layout.yaml`` related to your repository to + add ``osc-plugin-jobs`` to the list of job templates for your project. + This job checks that to see if any new commands are: duplicated, missing + entry points, or have overlap; across all openstackclient plugins. + +#. Update ``jenkins/scripts/check-osc-plugins.sh`` to include your new + library to be installed from source. This is essential in running the + previously mentioned check job. Simply add + ``install_from_source python-fooclient`` to the block of code where all + other clients are installed. + +Changes to python-openstackclient +--------------------------------- + +#. In ``doc/source/plugins.rst``, update the `Adoption` section to reflect the + status of the project. + +#. Update ``doc/source/commands.rst`` to include objects that are defined by + fooclient's new plugin. + +#. Update ``doc/source/plugin-commands.rst`` to include the entry point defined + in fooclient. We use `sphinxext`_ to automatically document commands that + are used. + +#. Update ``test-requirements.txt`` to include fooclient. This is necessary + to auto-document the commands in the previous step. + +.. _sphinxext: http://docs.openstack.org/developer/stevedore/sphinxext.html From a1f2f4af5831fc47a7efea61163643dc8b6c0bc8 Mon Sep 17 00:00:00 2001 From: reedip Date: Tue, 1 Mar 2016 16:11:51 +0900 Subject: [PATCH 0673/3095] TrivialOrder: Rearrange Class Names As per the comment given by Steve Martinelli in https://review.openstack.org/#/c/278209/ , the following patch just rearranges the classes as per the Alphabetical order. TrivialFix Change-Id: Ib8f0f703df4ef7d7ee6180ff8bd8a47062ae5b0f --- doc/source/command-objects/subnet.rst | 32 +++++----- openstackclient/network/v2/subnet.py | 36 ++++++------ .../tests/network/v2/test_subnet.py | 58 +++++++++---------- 3 files changed, 63 insertions(+), 63 deletions(-) diff --git a/doc/source/command-objects/subnet.rst b/doc/source/command-objects/subnet.rst index 9520a22d6a..97d5c68b89 100644 --- a/doc/source/command-objects/subnet.rst +++ b/doc/source/command-objects/subnet.rst @@ -4,6 +4,22 @@ subnet Network v2 +subnet delete +------------- + +Delete a subnet + +.. program:: subnet delete +.. code:: bash + + os subnet delete + + +.. _subnet_delete-subnet: +.. describe:: + + Subnet to delete (name or ID) + subnet list ----------- @@ -34,19 +50,3 @@ Show subnet details .. describe:: Subnet to show (name or ID) - -subnet delete -------------- - -Delete a subnet - -.. program:: subnet delete -.. code:: bash - - os subnet delete - - -.. _subnet_delete-subnet: -.. describe:: - - Subnet to delete (name or ID) diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 96ab35ae74..b514a88f7d 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -38,6 +38,24 @@ def _get_columns(item): return tuple(sorted(columns)) +class DeleteSubnet(command.Command): + """Delete subnet""" + + def get_parser(self, prog_name): + parser = super(DeleteSubnet, self).get_parser(prog_name) + parser.add_argument( + 'subnet', + metavar="", + help="Subnet to delete (name or ID)" + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + client.delete_subnet( + client.find_subnet(parsed_args.subnet)) + + class ListSubnet(command.Lister): """List subnets""" @@ -89,21 +107,3 @@ def take_action(self, parsed_args): columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) return (columns, data) - - -class DeleteSubnet(command.Command): - """Delete subnet""" - - def get_parser(self, prog_name): - parser = super(DeleteSubnet, self).get_parser(prog_name) - parser.add_argument( - 'subnet', - metavar="", - help=("Subnet to delete (name or ID)") - ) - return parser - - def take_action(self, parsed_args): - client = self.app.client_manager.network - client.delete_subnet( - client.find_subnet(parsed_args.subnet)) diff --git a/openstackclient/tests/network/v2/test_subnet.py b/openstackclient/tests/network/v2/test_subnet.py index e844c1388b..a95635ffad 100644 --- a/openstackclient/tests/network/v2/test_subnet.py +++ b/openstackclient/tests/network/v2/test_subnet.py @@ -28,6 +28,35 @@ def setUp(self): self.network = self.app.client_manager.network +class TestDeleteSubnet(TestSubnet): + + # The subnet to delete. + _subnet = network_fakes.FakeSubnet.create_one_subnet() + + def setUp(self): + super(TestDeleteSubnet, self).setUp() + + self.network.delete_subnet = mock.Mock(return_value=None) + + self.network.find_subnet = mock.Mock(return_value=self._subnet) + + # Get the command object to test + self.cmd = subnet_v2.DeleteSubnet(self.app, self.namespace) + + def test_delete(self): + arglist = [ + self._subnet.name, + ] + verifylist = [ + ('subnet', self._subnet.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.network.delete_subnet.assert_called_with(self._subnet) + self.assertIsNone(result) + + class TestListSubnet(TestSubnet): # The subnets going to be listed up. _subnet = network_fakes.FakeSubnet.create_subnets(count=3) @@ -181,32 +210,3 @@ def test_show_all_options(self): self.assertEqual(self.columns, columns) self.assertEqual(list(self.data), list(data)) - - -class TestDeleteSubnet(TestSubnet): - - # The subnet to delete. - _subnet = network_fakes.FakeSubnet.create_one_subnet() - - def setUp(self): - super(TestDeleteSubnet, self).setUp() - - self.network.delete_subnet = mock.Mock(return_value=None) - - self.network.find_subnet = mock.Mock(return_value=self._subnet) - - # Get the command object to test - self.cmd = subnet_v2.DeleteSubnet(self.app, self.namespace) - - def test_delete(self): - arglist = [ - self._subnet.name, - ] - verifylist = [ - ('subnet', self._subnet.name), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - self.network.delete_subnet.assert_called_with(self._subnet) - self.assertIsNone(result) From c832e2a7716de2693f8e0d1ad93f1e1dfea83dde Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Mon, 29 Feb 2016 15:48:43 +0800 Subject: [PATCH 0674/3095] Fix 'code-block' tag format issues Modify 'code-block' tag format so that the following python code could be showed exactly, and reformat the more than 79 chars of lines. Change-Id: Ic6721e4cc8f4c7a3e4a7c7dbd63d9089180cdc33 --- doc/source/command-options.rst | 30 ++++++++++++++++-------------- doc/source/command-wrappers.rst | 2 +- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/doc/source/command-options.rst b/doc/source/command-options.rst index 458c56bb19..3ac6d44be9 100644 --- a/doc/source/command-options.rst +++ b/doc/source/command-options.rst @@ -27,10 +27,10 @@ prepended (such as in the traditional GNU option usage) like `--share` and In order to handle those APIs that behave differently when a field is set to `None` and when the field is not present in a passed argument list or dict, each of the boolean options shall set its own variable to `True` as part of -a mutiually exclusive group, rather than the more common configuration of setting a -single destination variable `True` or `False` directly. This allows us to -detect the situation when neither option is present (both variables will be -`False`) and act accordingly for those APIs where this matters. +a mutiually exclusive group, rather than the more common configuration of +setting a single destination variable `True` or `False` directly. This allows +us to detect the situation when neither option is present (both variables will +be `False`) and act accordingly for those APIs where this matters. This also requires that each of the boolean values be tested in the `take_action()` method to correctly set (or not) the underlying API field @@ -47,9 +47,9 @@ values. Implementation ~~~~~~~~~~~~~~ -The parser declaration should look like this:: +The parser declaration should look like this: -.. code-block: python +.. code-block:: python enable_group = parser.add_mutually_exclusive_group() enable_group.add_argument( @@ -63,7 +63,9 @@ The parser declaration should look like this:: help=_('Disable '), ) -An example handler in `take_action()`:: +An example handler in `take_action()`: + +.. code-block:: python # This leaves 'enabled' undefined if neither option is present if parsed_args.enable: @@ -88,9 +90,9 @@ commands should allow `--long` even if they return all fields by default. Implementation ~~~~~~~~~~~~~~ -The parser declaration should look like this:: +The parser declaration should look like this: -.. code-block: python +.. code-block:: python parser.add_argument( '--long', @@ -102,9 +104,9 @@ The parser declaration should look like this:: Pagination ---------- -There are many ways to do pagination, some OpenStack APIs support it, some don't. -OpenStackClient attempts to define a single common way to specify pagination on -the command line. +There are many ways to do pagination, some OpenStack APIs support it, some +don't. OpenStackClient attempts to define a single common way to specify +pagination on the command line. .. option:: --marker @@ -117,9 +119,9 @@ the command line. Implementation ~~~~~~~~~~~~~~ -The parser declaration should look like this:: +The parser declaration should look like this: -.. code-block: python +.. code-block:: python parser.add_argument( "--marker", diff --git a/doc/source/command-wrappers.rst b/doc/source/command-wrappers.rst index b14eccdde8..2a5d92239d 100644 --- a/doc/source/command-wrappers.rst +++ b/doc/source/command-wrappers.rst @@ -34,7 +34,7 @@ deprecation message as a warning to the user then calls the original class. Example Deprecation Class ------------------------- -.. code-block: python +.. code-block:: python class ListFooOld(ListFoo): """List resources""" From 8a839ad8b82599cccf9d34e83ce3f0735334d817 Mon Sep 17 00:00:00 2001 From: Dina Belova Date: Tue, 1 Mar 2016 13:45:19 +0300 Subject: [PATCH 0675/3095] Fix regression in interactive client mode Fix typo introduced in OSprofiler intergation commit, that leaded to non-working interactive mode of the CLI client. Change-Id: If5dfc90dbbe64d4665c3e33e936f0cc674738351 Closes-Bug: 1551160 --- openstackclient/shell.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 659bbee72c..53e9be08bb 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -158,7 +158,7 @@ def run_subcommand(self, argv): def interact(self): self.init_profile() try: - ret_value = super(OpenStackShell, self).run_subcommand() + ret_value = super(OpenStackShell, self).interact() finally: self.close_profile() return ret_value From 8b17a1fa5b16037f259d70c9bc07e902d78d5049 Mon Sep 17 00:00:00 2001 From: Yang Hongyang Date: Tue, 1 Mar 2016 18:43:00 +0800 Subject: [PATCH 0676/3095] Trivial: Update image_list v2 docs This api doc is clearly copied from v1, we should update it to reflact v2 API. Added 'shared' param description. Change-Id: I73d36e3a2a0448c28edab788a9340fd46177f8ef --- openstackclient/api/image_v2.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/openstackclient/api/image_v2.py b/openstackclient/api/image_v2.py index d8bb280151..026498fa1d 100644 --- a/openstackclient/api/image_v2.py +++ b/openstackclient/api/image_v2.py @@ -44,11 +44,14 @@ def image_list( Return public images if True :param private: Return private images if True + :param shared: + Return shared images if True - If public and private are both True or both False then all images are - returned. Both arguments False is equivalent to no filter and all - images are returned. Both arguments True is a filter that includes - both public and private images which is the same set as all images. + If public, private and shared are all True or all False then all + images are returned. All arguments False is equivalent to no filter + and all images are returned. All arguments True is a filter that + includes all public, private and shared images which is the same set + as all images. http://docs.openstack.org/api/openstack-image-service/2.0/content/list-images.html """ From 059f54eee47f9ddefb0fea3eca9fa7568571d035 Mon Sep 17 00:00:00 2001 From: Yang Hongyang Date: Tue, 1 Mar 2016 22:03:02 +0800 Subject: [PATCH 0677/3095] Clean up unnecessary import of urlparse module six.moves.urllib already covers the py2 and py3 compatibility issues of urlparse module, use six.moves.urllib.parse.urlparse is enough. Change-Id: I785f4f872850e5d770fdcf4c0d3392be3978cc4a --- openstackclient/api/object_store_v1.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/openstackclient/api/object_store_v1.py b/openstackclient/api/object_store_v1.py index 10139ea181..632e8b19a7 100644 --- a/openstackclient/api/object_store_v1.py +++ b/openstackclient/api/object_store_v1.py @@ -20,11 +20,6 @@ import six from six.moves import urllib -try: - from urllib.parse import urlparse # noqa -except ImportError: - from urlparse import urlparse # noqa - from openstackclient.api import api from openstackclient.common import utils @@ -525,7 +520,7 @@ def account_unset( self.create("", headers=headers) def _find_account_id(self): - url_parts = urlparse(self.endpoint) + url_parts = urllib.parse.urlparse(self.endpoint) return url_parts.path.split('/')[-1] def _unset_properties(self, properties, header_tag): From fbe5dc657b4129b0322dfba57494fe9b00b6ade8 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Tue, 1 Mar 2016 05:45:26 -0600 Subject: [PATCH 0678/3095] Devref: Options with Choices Add a developer reference for options with choices. This patch set also includes RST doc formatting fixes. Change-Id: I5fd6a699806edf1d2d95110f0b8a2b8a385028ab --- doc/source/command-options.rst | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/doc/source/command-options.rst b/doc/source/command-options.rst index 3ac6d44be9..b723e988a5 100644 --- a/doc/source/command-options.rst +++ b/doc/source/command-options.rst @@ -14,8 +14,11 @@ for defining and using options in all situations. The alternative of only using it when necessary leads to errors when copy-n-paste is used for a new command without understanding why or why not that instance is correct. +General Command Options +======================= + Boolean Options -=============== +--------------- Boolean options for any command that sets a resource state, such as 'enabled' or 'public', shall always have both positive and negative options defined. @@ -73,6 +76,35 @@ An example handler in `take_action()`: if parsed_args.disable: kwargs['enabled'] = False +Options with Choices +-------------------- + +Some options have a specific set of values (or choices) that are valid. +These choices may be validated by the CLI. If the underlying API is stable +and the list of choices are unlikely to change then the CLI may validate +the choices. Otherwise, the CLI must defer validation of the choices to +the API. If the option has a default choice then it must be documented. + +Having the CLI validate choices will be faster and may provide a better +error message for the user if an invalid choice is specified +(for example: ``argument --test: invalid choice: 'choice4' (choose from 'choice1', 'choice2', 'choice3')``). +The trade-off is that CLI changes are required in order to take advantage +of new choices. + +Implementation +~~~~~~~~~~~~~~ + +An example parser declaration: + +.. code-block:: python + + choice_option.add_argument( + '--test', + metavar=', + choices=['choice1', 'choice2', 'choice3'], + help=_('Test type (choice1, choice2 or choice3)'), + ) + List Command Options ==================== From 84942bb11c44659458d3a10a30ef208d0cb66bdd Mon Sep 17 00:00:00 2001 From: "ting.wang" Date: Tue, 23 Feb 2016 18:43:41 +0800 Subject: [PATCH 0679/3095] Add test cases to test some commands with '--wait' and fix bug "server image create" "server rebuild" "server resize" Above 3 commands are not covered by unit test. So add some unit tests. Meanwhile, fix bug. Now that image name is an optional argument, we'd better record error messages with positional arguments instead of optional argument. So, record server name. Change-Id: I41bc025d4824dc46f63a3213d82e1528bacbbe12 --- openstackclient/compute/v2/server.py | 4 +- .../tests/compute/v2/test_server.py | 188 ++++++++++++++++++ 2 files changed, 190 insertions(+), 2 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 4cb94822bd..cbc0f256da 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -617,8 +617,8 @@ def take_action(self, parsed_args): ): sys.stdout.write('\n') else: - self.log.error(_('Error creating server snapshot: %s'), - parsed_args.image_name) + self.log.error(_('Error creating snapshot of server: %s'), + parsed_args.server) sys.stdout.write(_('\nError creating server snapshot')) raise SystemExit diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 95188522fa..e28b201555 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -595,6 +595,64 @@ def test_server_image_create_name(self): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist(), data) + @mock.patch.object(common_utils, 'wait_for_status', return_value=False) + def test_server_create_image_with_wait_fails(self, mock_wait_for_status): + arglist = [ + '--wait', + self.server.id, + ] + verifylist = [ + ('wait', True), + ('server', self.server.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises(SystemExit, self.cmd.take_action, parsed_args) + + mock_wait_for_status.assert_called_once_with( + self.images_mock.get, + self.image.id, + callback=server._show_progress + ) + + # ServerManager.create_image(server, image_name, metadata=) + self.servers_mock.create_image.assert_called_with( + self.servers_mock.get.return_value, + self.server.name, + ) + + @mock.patch.object(common_utils, 'wait_for_status', return_value=True) + def test_server_create_image_with_wait_ok(self, mock_wait_for_status): + arglist = [ + '--wait', + self.server.id, + ] + verifylist = [ + ('wait', True), + ('server', self.server.id), + ] + 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) + + # ServerManager.create_image(server, image_name, metadata=) + self.servers_mock.create_image.assert_called_with( + self.servers_mock.get.return_value, + self.server.name, + ) + + mock_wait_for_status.assert_called_once_with( + self.images_mock.get, + self.image.id, + callback=server._show_progress + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) + class TestServerList(TestServer): @@ -831,6 +889,58 @@ def test_rebuild_with_current_image_and_password(self): self.cimages_mock.get.assert_called_with(self.image.id) self.server.rebuild.assert_called_with(self.image, password) + @mock.patch.object(common_utils, 'wait_for_status', return_value=True) + def test_rebuild_with_wait_ok(self, mock_wait_for_status): + arglist = [ + '--wait', + self.server.id, + ] + verifylist = [ + ('wait', True), + ('server', self.server.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Get the command object to test. + self.cmd.take_action(parsed_args) + + # kwargs = dict(success_status=['active', 'verify_resize'],) + + mock_wait_for_status.assert_called_once_with( + self.servers_mock.get, + self.server.id, + callback=server._show_progress, + # **kwargs + ) + + self.servers_mock.get.assert_called_with(self.server.id) + self.cimages_mock.get.assert_called_with(self.image.id) + self.server.rebuild.assert_called_with(self.image, None) + + @mock.patch.object(common_utils, 'wait_for_status', return_value=False) + def test_rebuild_with_wait_fails(self, mock_wait_for_status): + arglist = [ + '--wait', + self.server.id, + ] + verifylist = [ + ('wait', True), + ('server', self.server.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises(SystemExit, self.cmd.take_action, parsed_args) + + mock_wait_for_status.assert_called_once_with( + self.servers_mock.get, + self.server.id, + callback=server._show_progress + ) + + self.servers_mock.get.assert_called_with(self.server.id) + self.cimages_mock.get.assert_called_with(self.image.id) + self.server.rebuild.assert_called_with(self.image, None) + class TestServerResize(TestServer): @@ -952,6 +1062,84 @@ def test_server_resize_revert(self): self.server, ) + @mock.patch.object(common_utils, 'wait_for_status', return_value=True) + def test_server_resize_with_wait_ok(self, mock_wait_for_status): + + arglist = [ + '--flavor', self.flavors_get_return_value.id, + '--wait', + self.server.id, + ] + + verifylist = [ + ('flavor', self.flavors_get_return_value.id), + ('confirm', False), + ('revert', False), + ('wait', True), + ('server', self.server.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with( + self.server.id, + ) + + kwargs = dict(success_status=['active', 'verify_resize'],) + + mock_wait_for_status.assert_called_once_with( + self.servers_mock.get, + self.server.id, + callback=server._show_progress, + **kwargs + ) + + self.servers_mock.resize.assert_called_with( + self.server, + self.flavors_get_return_value + ) + self.assertNotCalled(self.servers_mock.confirm_resize) + self.assertNotCalled(self.servers_mock.revert_resize) + + @mock.patch.object(common_utils, 'wait_for_status', return_value=False) + def test_server_resize_with_wait_fails(self, mock_wait_for_status): + + arglist = [ + '--flavor', self.flavors_get_return_value.id, + '--wait', + self.server.id, + ] + + verifylist = [ + ('flavor', self.flavors_get_return_value.id), + ('confirm', False), + ('revert', False), + ('wait', True), + ('server', self.server.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises(SystemExit, self.cmd.take_action, parsed_args) + + self.servers_mock.get.assert_called_with( + self.server.id, + ) + + kwargs = dict(success_status=['active', 'verify_resize'],) + + mock_wait_for_status.assert_called_once_with( + self.servers_mock.get, + self.server.id, + callback=server._show_progress, + **kwargs + ) + + self.servers_mock.resize.assert_called_with( + self.server, + self.flavors_get_return_value + ) + class TestServerResume(TestServer): From 359dfa1a06683354ace568c78706e3d0a6372c14 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Thu, 25 Feb 2016 16:35:57 +0800 Subject: [PATCH 0680/3095] Support "network create" command in nova network This patch only provide network name and subnet setting for "network create" command. The other options, such as --project which depends on identity v2 or v3, will make the unit tests too complicated. So I prefer to implement them in other patches. Change-Id: I9ec93f0af813c8fae4170c36e16bbe8f0f53cbb6 Partial-Bug: 1543672 --- doc/source/command-objects/network.rst | 14 ++- openstackclient/network/v2/network.py | 67 ++++++---- .../tests/network/v2/test_network.py | 114 ++++++++++++++++++ .../notes/bug-1543672-bad2fc4c6c8f3125.yaml | 2 + 4 files changed, 174 insertions(+), 23 deletions(-) diff --git a/doc/source/command-objects/network.rst b/doc/source/command-objects/network.rst index 0ef8f56d28..9e109279f6 100644 --- a/doc/source/command-objects/network.rst +++ b/doc/source/command-objects/network.rst @@ -22,19 +22,23 @@ Create new network .. option:: --project Owner's project (name or ID) + (Network v2 only) .. option:: --project-domain Domain the project belongs to (name or ID). This can be used in case collisions between project names exist. + (Network v2 only) .. option:: --enable Enable network (default) + (Network v2 only) .. option:: --disable Disable network + (Network v2 only) .. option:: --share @@ -46,8 +50,14 @@ Create new network .. option:: --availability-zone-hint - Availability Zone in which to create this network (requires the Network - Availability Zone extension, this option can be repeated). + Availability Zone in which to create this network (requires the Network + Availability Zone extension, this option can be repeated). + (Network v2 only) + +.. option:: --subnet + + IPv4 subnet for fixed IPs (in CIDR notation) + (Compute v2 network only) .. _network_create-name: .. describe:: diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index fd7ab8fbed..308e0e5249 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -75,30 +75,27 @@ def _get_attrs(client_manager, parsed_args): return attrs -class CreateNetwork(command.ShowOne): +def _get_attrs_compute(client_manager, parsed_args): + attrs = {} + if parsed_args.name is not None: + attrs['label'] = str(parsed_args.name) + if parsed_args.shared is not None: + attrs['share_address'] = parsed_args.shared + if parsed_args.subnet is not None: + attrs['cidr'] = parsed_args.subnet + + return attrs + + +class CreateNetwork(common.NetworkAndComputeShowOne): """Create new network""" - def get_parser(self, prog_name): - parser = super(CreateNetwork, self).get_parser(prog_name) + def update_parser_common(self, parser): parser.add_argument( 'name', metavar='', help='New network name', ) - admin_group = parser.add_mutually_exclusive_group() - admin_group.add_argument( - '--enable', - dest='admin_state', - action='store_true', - default=True, - help='Enable network (default)', - ) - admin_group.add_argument( - '--disable', - dest='admin_state', - action='store_false', - help='Disable network', - ) share_group = parser.add_mutually_exclusive_group() share_group.add_argument( '--share', @@ -113,13 +110,29 @@ def get_parser(self, prog_name): action='store_false', help='Do not share the network between projects', ) + return parser + + def update_parser_network(self, parser): + admin_group = parser.add_mutually_exclusive_group() + admin_group.add_argument( + '--enable', + dest='admin_state', + action='store_true', + default=True, + help='Enable network (default)', + ) + admin_group.add_argument( + '--disable', + dest='admin_state', + action='store_false', + help='Disable network', + ) parser.add_argument( '--project', metavar='', help="Owner's project (name or ID)" ) identity_common.add_project_domain_option_to_parser(parser) - parser.add_argument( '--availability-zone-hint', action='append', @@ -131,16 +144,28 @@ def get_parser(self, prog_name): ) return parser - def take_action(self, parsed_args): - client = self.app.client_manager.network + def update_parser_compute(self, parser): + parser.add_argument( + '--subnet', + metavar='', + help="IPv4 subnet for fixed IPs (in CIDR notation)" + ) + return parser + def take_action_network(self, client, parsed_args): attrs = _get_attrs(self.app.client_manager, parsed_args) obj = client.create_network(**attrs) columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns, formatters=_formatters) return (columns, data) + def take_action_compute(self, client, parsed_args): + attrs = _get_attrs_compute(self.app.client_manager, parsed_args) + obj = client.networks.create(**attrs) + columns = tuple(sorted(obj._info.keys())) + data = utils.get_dict_properties(obj._info, columns) + return (columns, data) + class DeleteNetwork(common.NetworkAndComputeCommand): """Delete network(s)""" diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index 26a9da40d5..e70a66c1f9 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -576,6 +576,120 @@ def setUp(self): self.compute = self.app.client_manager.compute +class TestCreateNetworkCompute(TestNetworkCompute): + + # The network to create. + _network = compute_fakes.FakeNetwork.create_one_network() + + columns = ( + 'bridge', + 'bridge_interface', + 'broadcast', + 'cidr', + 'cidr_v6', + 'created_at', + 'deleted', + 'deleted_at', + 'dhcp_server', + 'dhcp_start', + 'dns1', + 'dns2', + 'enable_dhcp', + 'gateway', + 'gateway_v6', + 'host', + 'id', + 'injected', + 'label', + 'mtu', + 'multi_host', + 'netmask', + 'netmask_v6', + 'priority', + 'project_id', + 'rxtx_base', + 'share_address', + 'updated_at', + 'vlan', + 'vpn_private_address', + 'vpn_public_address', + 'vpn_public_port', + ) + + data = ( + _network.bridge, + _network.bridge_interface, + _network.broadcast, + _network.cidr, + _network.cidr_v6, + _network.created_at, + _network.deleted, + _network.deleted_at, + _network.dhcp_server, + _network.dhcp_start, + _network.dns1, + _network.dns2, + _network.enable_dhcp, + _network.gateway, + _network.gateway_v6, + _network.host, + _network.id, + _network.injected, + _network.label, + _network.mtu, + _network.multi_host, + _network.netmask, + _network.netmask_v6, + _network.priority, + _network.project_id, + _network.rxtx_base, + _network.share_address, + _network.updated_at, + _network.vlan, + _network.vpn_private_address, + _network.vpn_public_address, + _network.vpn_public_port, + ) + + def setUp(self): + super(TestCreateNetworkCompute, self).setUp() + + self.app.client_manager.network_endpoint_enabled = False + + self.compute.networks.create.return_value = self._network + + # Get the command object to test + self.cmd = network.CreateNetwork(self.app, None) + + def test_create_no_options(self): + arglist = [] + verifylist = [] + + # Missing required args should raise exception here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_create_default_options(self): + arglist = [ + "--subnet", self._network.cidr, + self._network.label, + ] + verifylist = [ + ('subnet', self._network.cidr), + ('name', self._network.label), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.compute.networks.create.assert_called_with(**{ + 'cidr': self._network.cidr, + 'label': self._network.label, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + class TestDeleteNetworkCompute(TestNetworkCompute): # The network to delete. diff --git a/releasenotes/notes/bug-1543672-bad2fc4c6c8f3125.yaml b/releasenotes/notes/bug-1543672-bad2fc4c6c8f3125.yaml index ad4d5f5685..b93035c43a 100644 --- a/releasenotes/notes/bug-1543672-bad2fc4c6c8f3125.yaml +++ b/releasenotes/notes/bug-1543672-bad2fc4c6c8f3125.yaml @@ -6,3 +6,5 @@ features: [Bug `1543672 `_] - Command ``network show`` is now available for nova network. [Bug `1543672 `_] + - Command ``network create`` is now available for nova network. + [Bug `1543672 `_] From fd53a4980f27a72e9a0d39cde9fad01cb5c6744a Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Mon, 29 Feb 2016 14:53:57 +0800 Subject: [PATCH 0681/3095] [Image] Check return value is None in image unit tests. take_action() in commands inheriting from Command returns nothing. So we should assert the return is None in the unit tests of these commands. Change-Id: I237ea772f74fa52af2e9aacd35d4b9cfb225c94c Partial-Bug: #1550636 --- openstackclient/tests/image/v1/test_image.py | 26 ++++++---- openstackclient/tests/image/v2/test_image.py | 54 +++++++++++--------- 2 files changed, 46 insertions(+), 34 deletions(-) diff --git a/openstackclient/tests/image/v1/test_image.py b/openstackclient/tests/image/v1/test_image.py index 201105a409..018e119933 100644 --- a/openstackclient/tests/image/v1/test_image.py +++ b/openstackclient/tests/image/v1/test_image.py @@ -236,11 +236,10 @@ def test_image_delete_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) - self.images_mock.delete.assert_called_with( - image_fakes.image_id, - ) + self.images_mock.delete.assert_called_with(image_fakes.image_id) + self.assertIsNone(result) class TestImageList(TestImage): @@ -473,10 +472,11 @@ def test_image_set_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) # Verify update() was not called, if it was show the args self.assertEqual(self.images_mock.update.call_args_list, []) + self.assertIsNone(result) def test_image_set_options(self): arglist = [ @@ -501,7 +501,7 @@ def test_image_set_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) kwargs = { 'name': 'new-name', @@ -517,6 +517,7 @@ def test_image_set_options(self): image_fakes.image_id, **kwargs ) + self.assertIsNone(result) def test_image_set_bools1(self): arglist = [ @@ -533,7 +534,7 @@ def test_image_set_bools1(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) kwargs = { 'protected': True, @@ -544,6 +545,7 @@ def test_image_set_bools1(self): image_fakes.image_id, **kwargs ) + self.assertIsNone(result) def test_image_set_bools2(self): arglist = [ @@ -560,7 +562,7 @@ def test_image_set_bools2(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) kwargs = { 'protected': False, @@ -571,6 +573,7 @@ def test_image_set_bools2(self): image_fakes.image_id, **kwargs ) + self.assertIsNone(result) def test_image_set_properties(self): arglist = [ @@ -584,7 +587,7 @@ def test_image_set_properties(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) kwargs = { 'properties': { @@ -598,6 +601,7 @@ def test_image_set_properties(self): image_fakes.image_id, **kwargs ) + self.assertIsNone(result) def test_image_update_volume(self): # Set up VolumeManager Mock @@ -639,7 +643,7 @@ def test_image_update_volume(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) # VolumeManager.upload_to_image(volume, force, image_name, # container_format, disk_format) @@ -650,13 +654,13 @@ def test_image_update_volume(self): '', '', ) - # ImageManager.update(image_id, remove_props=, **) self.images_mock.update.assert_called_with( image_fakes.image_id, name='updated_image', volume='volly', ) + self.assertIsNone(result) class TestImageShow(TestImage): diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index b8e137f81c..0248f30b9a 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -445,11 +445,10 @@ def test_image_delete_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) - self.images_mock.delete.assert_called_with( - images[0].id, - ) + self.images_mock.delete.assert_called_with(images[0].id) + self.assertIsNone(result) def test_image_delete_multi_images(self): images = self.setup_images_mock(count=3) @@ -458,14 +457,13 @@ def test_image_delete_multi_images(self): verifylist = [ ('images', arglist), ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) calls = [mock.call(i.id) for i in images] - self.images_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) class TestImageList(TestImage): @@ -753,11 +751,13 @@ def test_remove_project_image_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) + self.image_members_mock.delete.assert_called_with( image_fakes.image_id, identity_fakes.project_id, ) + self.assertIsNone(result) def test_remove_project_image_with_options(self): arglist = [ @@ -772,11 +772,13 @@ def test_remove_project_image_with_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) + self.image_members_mock.delete.assert_called_with( image_fakes.image_id, identity_fakes.project_id, ) + self.assertIsNone(result) class TestImageSet(TestImage): @@ -829,7 +831,7 @@ def test_image_set_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) kwargs = { 'name': 'new-name', @@ -842,6 +844,7 @@ def test_image_set_options(self): # ImageManager.update(image, **kwargs) self.images_mock.update.assert_called_with( image_fakes.image_id, **kwargs) + self.assertIsNone(result) def test_image_set_with_unexist_owner(self): self.project_mock.get.side_effect = exceptions.NotFound(None) @@ -855,7 +858,6 @@ def test_image_set_with_unexist_owner(self): ('owner', 'unexist_owner'), ('image', image_fakes.image_id), ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.assertRaises( @@ -874,7 +876,6 @@ def test_image_set_with_unexist_project(self): ('project', 'unexist_owner'), ('image', image_fakes.image_id), ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.assertRaises( @@ -896,7 +897,7 @@ def test_image_set_bools1(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) kwargs = { 'protected': True, @@ -907,6 +908,7 @@ def test_image_set_bools1(self): image_fakes.image_id, **kwargs ) + self.assertIsNone(result) def test_image_set_bools2(self): arglist = [ @@ -923,7 +925,7 @@ def test_image_set_bools2(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) kwargs = { 'protected': False, @@ -934,6 +936,7 @@ def test_image_set_bools2(self): image_fakes.image_id, **kwargs ) + self.assertIsNone(result) def test_image_set_properties(self): arglist = [ @@ -947,7 +950,7 @@ def test_image_set_properties(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) kwargs = { 'Alpha': '1', @@ -958,6 +961,7 @@ def test_image_set_properties(self): image_fakes.image_id, **kwargs ) + self.assertIsNone(result) def test_image_set_fake_properties(self): arglist = [ @@ -980,7 +984,7 @@ def test_image_set_fake_properties(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) kwargs = { 'architecture': 'z80', @@ -995,6 +999,7 @@ def test_image_set_fake_properties(self): image_fakes.image_id, **kwargs ) + self.assertIsNone(result) def test_image_set_tag(self): arglist = [ @@ -1007,7 +1012,7 @@ def test_image_set_tag(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) kwargs = { 'tags': ['test-tag'], @@ -1017,6 +1022,7 @@ def test_image_set_tag(self): image_fakes.image_id, **kwargs ) + self.assertIsNone(result) def test_image_set_activate(self): arglist = [ @@ -1030,7 +1036,7 @@ def test_image_set_activate(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) kwargs = { 'tags': ['test-tag'], @@ -1039,12 +1045,12 @@ def test_image_set_activate(self): self.images_mock.reactivate.assert_called_with( image_fakes.image_id, ) - # ImageManager.update(image, **kwargs) self.images_mock.update.assert_called_with( image_fakes.image_id, **kwargs ) + self.assertIsNone(result) def test_image_set_deactivate(self): arglist = [ @@ -1058,7 +1064,7 @@ def test_image_set_deactivate(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) kwargs = { 'tags': ['test-tag'], @@ -1067,12 +1073,12 @@ def test_image_set_deactivate(self): self.images_mock.deactivate.assert_called_with( image_fakes.image_id, ) - # ImageManager.update(image, **kwargs) self.images_mock.update.assert_called_with( image_fakes.image_id, **kwargs ) + self.assertIsNone(result) def test_image_set_tag_merge(self): old_image = copy.copy(image_fakes.IMAGE) @@ -1088,7 +1094,7 @@ def test_image_set_tag_merge(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) kwargs = { 'tags': ['old1', 'new2', 'test-tag'], @@ -1098,6 +1104,7 @@ def test_image_set_tag_merge(self): self.assertEqual(image_fakes.image_id, a[0]) self.assertTrue('tags' in k) self.assertEqual(set(kwargs['tags']), set(k['tags'])) + self.assertIsNone(result) def test_image_set_tag_merge_dupe(self): old_image = copy.copy(image_fakes.IMAGE) @@ -1113,7 +1120,7 @@ def test_image_set_tag_merge_dupe(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) kwargs = { 'tags': ['new2', 'old1'], @@ -1123,6 +1130,7 @@ def test_image_set_tag_merge_dupe(self): self.assertEqual(image_fakes.image_id, a[0]) self.assertTrue('tags' in k) self.assertEqual(set(kwargs['tags']), set(k['tags'])) + self.assertIsNone(result) def test_image_set_dead_options(self): From f2ef9f2044abbe25eb4d7ba5d3998b0169abb757 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Wed, 2 Mar 2016 16:48:16 +0800 Subject: [PATCH 0682/3095] Trivial: Reorder unit tests in alphabetical order in volume tests Change-Id: I622123f68e2bb53f8767069e4a717fcc34e37b5c --- .../tests/volume/v2/test_backup.py | 126 +++++------ .../tests/volume/v2/test_snapshot.py | 204 +++++++++--------- 2 files changed, 165 insertions(+), 165 deletions(-) diff --git a/openstackclient/tests/volume/v2/test_backup.py b/openstackclient/tests/volume/v2/test_backup.py index edb4eb8e73..6fe3f6668c 100644 --- a/openstackclient/tests/volume/v2/test_backup.py +++ b/openstackclient/tests/volume/v2/test_backup.py @@ -78,34 +78,6 @@ def test_backup_create(self): self.assertEqual(data, volume_fakes.BACKUP_data) -class TestBackupShow(TestBackup): - - def setUp(self): - super(TestBackupShow, self).setUp() - - self.backups_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.BACKUP), - loaded=True) - # Get the command object to test - self.cmd = backup.ShowBackup(self.app, None) - - def test_backup_show(self): - arglist = [ - volume_fakes.backup_id - ] - verifylist = [ - ("backup", volume_fakes.backup_id) - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - self.backups_mock.get.assert_called_with(volume_fakes.backup_id) - - self.assertEqual(volume_fakes.BACKUP_columns, columns) - self.assertEqual(volume_fakes.BACKUP_data, data) - - class TestBackupDelete(TestBackup): def setUp(self): @@ -134,41 +106,6 @@ def test_backup_delete(self): self.backups_mock.delete.assert_called_with(volume_fakes.backup_id) -class TestBackupRestore(TestBackup): - - def setUp(self): - super(TestBackupRestore, self).setUp() - - self.backups_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.BACKUP), - loaded=True - ) - self.volumes_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.VOLUME), - loaded=True - ) - self.restores_mock.restore.return_value = None - # Get the command object to mock - self.cmd = backup.RestoreBackup(self.app, None) - - def test_backup_restore(self): - arglist = [ - volume_fakes.backup_id, - volume_fakes.volume_id - ] - verifylist = [ - ("backup", volume_fakes.backup_id), - ("volume", volume_fakes.volume_id) - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - self.cmd.take_action(parsed_args) - self.restores_mock.restore.assert_called_with(volume_fakes.backup_id, - volume_fakes.volume_id) - - class TestBackupList(TestBackup): columns = [ @@ -244,3 +181,66 @@ def test_backup_list_with_options(self): volume_fakes.backup_container ),) self.assertEqual(datalist, tuple(data)) + + +class TestBackupRestore(TestBackup): + + def setUp(self): + super(TestBackupRestore, self).setUp() + + self.backups_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.BACKUP), + loaded=True + ) + self.volumes_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.VOLUME), + loaded=True + ) + self.restores_mock.restore.return_value = None + # Get the command object to mock + self.cmd = backup.RestoreBackup(self.app, None) + + def test_backup_restore(self): + arglist = [ + volume_fakes.backup_id, + volume_fakes.volume_id + ] + verifylist = [ + ("backup", volume_fakes.backup_id), + ("volume", volume_fakes.volume_id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + self.restores_mock.restore.assert_called_with(volume_fakes.backup_id, + volume_fakes.volume_id) + + +class TestBackupShow(TestBackup): + + def setUp(self): + super(TestBackupShow, self).setUp() + + self.backups_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.BACKUP), + loaded=True) + # Get the command object to test + self.cmd = backup.ShowBackup(self.app, None) + + def test_backup_show(self): + arglist = [ + volume_fakes.backup_id + ] + verifylist = [ + ("backup", volume_fakes.backup_id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.backups_mock.get.assert_called_with(volume_fakes.backup_id) + + self.assertEqual(volume_fakes.BACKUP_columns, columns) + self.assertEqual(volume_fakes.BACKUP_data, data) diff --git a/openstackclient/tests/volume/v2/test_snapshot.py b/openstackclient/tests/volume/v2/test_snapshot.py index b4fb004bee..349e8dac85 100644 --- a/openstackclient/tests/volume/v2/test_snapshot.py +++ b/openstackclient/tests/volume/v2/test_snapshot.py @@ -76,34 +76,6 @@ def test_snapshot_create(self): self.assertEqual(data, volume_fakes.SNAPSHOT_data) -class TestSnapshotShow(TestSnapshot): - - def setUp(self): - super(TestSnapshotShow, self).setUp() - - self.snapshots_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.SNAPSHOT), - loaded=True) - # Get the command object to test - self.cmd = snapshot.ShowSnapshot(self.app, None) - - def test_snapshot_show(self): - arglist = [ - volume_fakes.snapshot_id - ] - verifylist = [ - ("snapshot", volume_fakes.snapshot_id) - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - self.snapshots_mock.get.assert_called_with(volume_fakes.snapshot_id) - - self.assertEqual(volume_fakes.SNAPSHOT_columns, columns) - self.assertEqual(volume_fakes.SNAPSHOT_data, data) - - class TestSnapshotDelete(TestSnapshot): def setUp(self): @@ -132,80 +104,6 @@ def test_snapshot_delete(self): self.snapshots_mock.delete.assert_called_with(volume_fakes.snapshot_id) -class TestSnapshotSet(TestSnapshot): - - def setUp(self): - super(TestSnapshotSet, self).setUp() - - self.snapshots_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.SNAPSHOT), - loaded=True - ) - self.snapshots_mock.set_metadata.return_value = None - self.snapshots_mock.update.return_value = None - # Get the command object to mock - self.cmd = snapshot.SetSnapshot(self.app, None) - - def test_snapshot_set(self): - arglist = [ - volume_fakes.snapshot_id, - "--name", "new_snapshot", - "--property", "x=y", - "--property", "foo=foo" - ] - new_property = {"x": "y", "foo": "foo"} - verifylist = [ - ("snapshot", volume_fakes.snapshot_id), - ("name", "new_snapshot"), - ("property", new_property) - ] - - kwargs = { - "name": "new_snapshot", - } - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) - - self.snapshots_mock.update.assert_called_with( - volume_fakes.snapshot_id, **kwargs) - self.snapshots_mock.set_metadata.assert_called_with( - volume_fakes.snapshot_id, new_property - ) - - -class TestSnapshotUnset(TestSnapshot): - - def setUp(self): - super(TestSnapshotUnset, self).setUp() - - self.snapshots_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.SNAPSHOT), - loaded=True - ) - self.snapshots_mock.delete_metadata.return_value = None - # Get the command object to mock - self.cmd = snapshot.UnsetSnapshot(self.app, None) - - def test_snapshot_unset(self): - arglist = [ - volume_fakes.snapshot_id, - "--property", "foo" - ] - verifylist = [ - ("snapshot", volume_fakes.snapshot_id), - ("property", ["foo"]) - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) - - self.snapshots_mock.delete_metadata.assert_called_with( - volume_fakes.snapshot_id, ["foo"] - ) - - class TestSnapshotList(TestSnapshot): columns = [ @@ -303,3 +201,105 @@ def test_snapshot_list_all_projects(self): volume_fakes.snapshot_size ), ) self.assertEqual(datalist, tuple(data)) + + +class TestSnapshotSet(TestSnapshot): + + def setUp(self): + super(TestSnapshotSet, self).setUp() + + self.snapshots_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.SNAPSHOT), + loaded=True + ) + self.snapshots_mock.set_metadata.return_value = None + self.snapshots_mock.update.return_value = None + # Get the command object to mock + self.cmd = snapshot.SetSnapshot(self.app, None) + + def test_snapshot_set(self): + arglist = [ + volume_fakes.snapshot_id, + "--name", "new_snapshot", + "--property", "x=y", + "--property", "foo=foo" + ] + new_property = {"x": "y", "foo": "foo"} + verifylist = [ + ("snapshot", volume_fakes.snapshot_id), + ("name", "new_snapshot"), + ("property", new_property) + ] + + kwargs = { + "name": "new_snapshot", + } + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.snapshots_mock.update.assert_called_with( + volume_fakes.snapshot_id, **kwargs) + self.snapshots_mock.set_metadata.assert_called_with( + volume_fakes.snapshot_id, new_property + ) + + +class TestSnapshotShow(TestSnapshot): + + def setUp(self): + super(TestSnapshotShow, self).setUp() + + self.snapshots_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.SNAPSHOT), + loaded=True) + # Get the command object to test + self.cmd = snapshot.ShowSnapshot(self.app, None) + + def test_snapshot_show(self): + arglist = [ + volume_fakes.snapshot_id + ] + verifylist = [ + ("snapshot", volume_fakes.snapshot_id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.snapshots_mock.get.assert_called_with(volume_fakes.snapshot_id) + + self.assertEqual(volume_fakes.SNAPSHOT_columns, columns) + self.assertEqual(volume_fakes.SNAPSHOT_data, data) + + +class TestSnapshotUnset(TestSnapshot): + + def setUp(self): + super(TestSnapshotUnset, self).setUp() + + self.snapshots_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.SNAPSHOT), + loaded=True + ) + self.snapshots_mock.delete_metadata.return_value = None + # Get the command object to mock + self.cmd = snapshot.UnsetSnapshot(self.app, None) + + def test_snapshot_unset(self): + arglist = [ + volume_fakes.snapshot_id, + "--property", "foo" + ] + verifylist = [ + ("snapshot", volume_fakes.snapshot_id), + ("property", ["foo"]) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.snapshots_mock.delete_metadata.assert_called_with( + volume_fakes.snapshot_id, ["foo"] + ) From 4ab66631d016cf1e01d7825c8eae678bf9f778ca Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 2 Mar 2016 19:23:53 +0000 Subject: [PATCH 0683/3095] Updated from global requirements Change-Id: Icc45c24eebbdd524254dc3200b016fe75b621b15 --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 9c93b11c4f..5db23369cb 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -25,5 +25,5 @@ python-designateclient>=1.5.0 # Apache-2.0 python-heatclient>=0.6.0 # Apache-2.0 python-ironicclient>=1.1.0 # Apache-2.0 python-mistralclient>=1.0.0 # Apache-2.0 -python-saharaclient>=0.12.0 # Apache-2.0 +python-saharaclient>=0.13.0 # Apache-2.0 python-zaqarclient>=0.3.0 # Apache-2.0 From bac9fb18c1455f6a309e7acff9230a8d6bf7079b Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Wed, 2 Mar 2016 15:45:01 -0600 Subject: [PATCH 0684/3095] Refactor security group set to use SDK Refactored the 'os security group set' command to use the SDK when neutron is enabled, but continue to use the nova client when nova network is enabled. This patch set also fixes a compute bug which ignores name and description when set to an empty value. Change-Id: I4225179dca4aedf799e1656ec49236bdedc5e9bd Partial-Bug: #1519511 Implements: blueprint neutron-client --- openstackclient/compute/v2/security_group.py | 41 ------ openstackclient/network/v2/security_group.py | 56 ++++++++ .../tests/network/v2/test_security_group.py | 133 ++++++++++++++++++ setup.cfg | 2 +- 4 files changed, 190 insertions(+), 42 deletions(-) diff --git a/openstackclient/compute/v2/security_group.py b/openstackclient/compute/v2/security_group.py index 907175f7d0..f378af14cb 100644 --- a/openstackclient/compute/v2/security_group.py +++ b/openstackclient/compute/v2/security_group.py @@ -217,47 +217,6 @@ def take_action(self, parsed_args): ) for s in rules)) -class SetSecurityGroup(command.Command): - """Set security group properties""" - - def get_parser(self, prog_name): - parser = super(SetSecurityGroup, self).get_parser(prog_name) - parser.add_argument( - 'group', - metavar='', - help='Security group to modify (name or ID)', - ) - parser.add_argument( - '--name', - metavar='', - help='New security group name', - ) - parser.add_argument( - "--description", - metavar="", - help="New security group description", - ) - return parser - - def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - data = utils.find_resource( - compute_client.security_groups, - parsed_args.group, - ) - - if parsed_args.name: - data.name = parsed_args.name - if parsed_args.description: - data.description = parsed_args.description - - compute_client.security_groups.update( - data, - data.name, - data.description, - ) - - class ShowSecurityGroup(command.ShowOne): """Display security group details""" diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py index 29d82b08d3..9cefb42066 100644 --- a/openstackclient/network/v2/security_group.py +++ b/openstackclient/network/v2/security_group.py @@ -88,3 +88,59 @@ def take_action_compute(self, client, parsed_args): data = client.security_groups.list(search_opts=search) return self._get_return_data(data, include_project=parsed_args.all_projects) + + +class SetSecurityGroup(common.NetworkAndComputeCommand): + """Set security group properties""" + + def update_parser_common(self, parser): + parser.add_argument( + 'group', + metavar='', + help='Security group to modify (name or ID)', + ) + parser.add_argument( + '--name', + metavar='', + help='New security group name', + ) + parser.add_argument( + "--description", + metavar="", + help="New security group description", + ) + return parser + + def take_action_network(self, client, parsed_args): + obj = client.find_security_group(parsed_args.group, + ignore_missing=False) + attrs = {} + if parsed_args.name is not None: + attrs['name'] = parsed_args.name + if parsed_args.description is not None: + attrs['description'] = parsed_args.description + # NOTE(rtheis): Previous behavior did not raise a CommandError + # if there were no updates. Maintain this behavior and issue + # the update. + client.update_security_group(obj, **attrs) + return + + def take_action_compute(self, client, parsed_args): + data = utils.find_resource( + client.security_groups, + parsed_args.group, + ) + + if parsed_args.name is not None: + data.name = parsed_args.name + if parsed_args.description is not None: + data.description = parsed_args.description + + # NOTE(rtheis): Previous behavior did not raise a CommandError + # if there were no updates. Maintain this behavior and issue + # the update. + client.security_groups.update( + data, + data.name, + data.description, + ) diff --git a/openstackclient/tests/network/v2/test_security_group.py b/openstackclient/tests/network/v2/test_security_group.py index ea96442584..b8114cbcf0 100644 --- a/openstackclient/tests/network/v2/test_security_group.py +++ b/openstackclient/tests/network/v2/test_security_group.py @@ -16,6 +16,7 @@ from openstackclient.network.v2 import security_group from openstackclient.tests.compute.v2 import fakes as compute_fakes from openstackclient.tests.network.v2 import fakes as network_fakes +from openstackclient.tests import utils as tests_utils class TestSecurityGroupNetwork(network_fakes.TestNetworkV2): @@ -230,3 +231,135 @@ def test_security_group_list_all_projects(self): self.compute.security_groups.list.assert_called_with(**kwargs) self.assertEqual(self.expected_columns_all_projects, columns) self.assertEqual(self.expected_data_all_projects, tuple(data)) + + +class TestSetSecurityGroupNetwork(TestSecurityGroupNetwork): + + # The security group to be set. + _security_group = \ + network_fakes.FakeSecurityGroup.create_one_security_group() + + def setUp(self): + super(TestSetSecurityGroupNetwork, self).setUp() + + self.network.update_security_group = mock.Mock(return_value=None) + + self.network.find_security_group = mock.Mock( + return_value=self._security_group) + + # Get the command object to test + self.cmd = security_group.SetSecurityGroup(self.app, self.namespace) + + def test_set_no_options(self): + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, [], []) + + def test_set_no_updates(self): + arglist = [ + self._security_group.name, + ] + verifylist = [ + ('group', self._security_group.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.network.update_security_group.assert_called_once_with( + self._security_group, + **{} + ) + self.assertIsNone(result) + + def test_set_all_options(self): + new_name = 'new-' + self._security_group.name + new_description = 'new-' + self._security_group.description + arglist = [ + '--name', new_name, + '--description', new_description, + self._security_group.name, + ] + verifylist = [ + ('description', new_description), + ('group', self._security_group.name), + ('name', new_name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = { + 'description': new_description, + 'name': new_name, + } + self.network.update_security_group.assert_called_once_with( + self._security_group, + **attrs + ) + self.assertIsNone(result) + + +class TestSetSecurityGroupCompute(TestSecurityGroupCompute): + + # The security group to be set. + _security_group = \ + compute_fakes.FakeSecurityGroup.create_one_security_group() + + def setUp(self): + super(TestSetSecurityGroupCompute, self).setUp() + + self.app.client_manager.network_endpoint_enabled = False + + self.compute.security_groups.update = mock.Mock(return_value=None) + + self.compute.security_groups.get = mock.Mock( + return_value=self._security_group) + + # Get the command object to test + self.cmd = security_group.SetSecurityGroup(self.app, None) + + def test_set_no_options(self): + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, [], []) + + def test_set_no_updates(self): + arglist = [ + self._security_group.name, + ] + verifylist = [ + ('group', self._security_group.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.compute.security_groups.update.assert_called_once_with( + self._security_group, + self._security_group.name, + self._security_group.description + ) + self.assertIsNone(result) + + def test_set_all_options(self): + new_name = 'new-' + self._security_group.name + new_description = 'new-' + self._security_group.description + arglist = [ + '--name', new_name, + '--description', new_description, + self._security_group.name, + ] + verifylist = [ + ('description', new_description), + ('group', self._security_group.name), + ('name', new_name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.compute.security_groups.update.assert_called_once_with( + self._security_group, + new_name, + new_description + ) + self.assertIsNone(result) diff --git a/setup.cfg b/setup.cfg index 284e6dec12..8cf1dd082a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -100,7 +100,6 @@ openstack.compute.v2 = keypair_show = openstackclient.compute.v2.keypair:ShowKeypair security_group_create = openstackclient.compute.v2.security_group:CreateSecurityGroup - security_group_set = openstackclient.compute.v2.security_group:SetSecurityGroup security_group_show = openstackclient.compute.v2.security_group:ShowSecurityGroup security_group_rule_create = openstackclient.compute.v2.security_group:CreateSecurityGroupRule security_group_rule_list = openstackclient.compute.v2.security_group:ListSecurityGroupRule @@ -340,6 +339,7 @@ openstack.network.v2 = router_show = openstackclient.network.v2.router:ShowRouter security_group_delete = openstackclient.network.v2.security_group:DeleteSecurityGroup security_group_list = openstackclient.network.v2.security_group:ListSecurityGroup + security_group_set = openstackclient.network.v2.security_group:SetSecurityGroup security_group_rule_delete = openstackclient.network.v2.security_group_rule:DeleteSecurityGroupRule security_group_rule_show = openstackclient.network.v2.security_group_rule:ShowSecurityGroupRule subnet_delete = openstackclient.network.v2.subnet:DeleteSubnet From 50443127c56e4de94bdd141ce22900d63736fbea Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Thu, 3 Mar 2016 20:50:17 +0800 Subject: [PATCH 0685/3095] Fix incorrect unit test for router Command "router show" will display router's "tenant_id" as "project_id". But in the unit test, it checks "tenant_id", which is incorrect. This patch fix this problem, and add a _get_columns() helper function to simplify the code. Change-Id: I0087ef7dfd0130b6c47222495848c4f2b9804b1b --- openstackclient/network/v2/router.py | 20 +++++++++++-------- openstackclient/tests/network/v2/fakes.py | 4 ++++ .../tests/network/v2/test_router.py | 4 ++-- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index e4eea3f8ab..96aa55b2f1 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -41,6 +41,14 @@ def _format_external_gateway_info(info): } +def _get_columns(item): + columns = item.keys() + if 'tenant_id' in columns: + columns.remove('tenant_id') + columns.append('project_id') + return tuple(sorted(columns)) + + def _get_attrs(client_manager, parsed_args): attrs = {} if parsed_args.name is not None: @@ -129,14 +137,10 @@ def take_action(self, parsed_args): attrs = _get_attrs(self.app.client_manager, parsed_args) obj = client.create_router(**attrs) - columns = sorted(obj.keys()) + columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) - if 'tenant_id' in columns: - # Rename "tenant_id" to "project_id". - index = columns.index('tenant_id') - columns[index] = 'project_id' - return (tuple(columns), data) + return columns, data class DeleteRouter(command.Command): @@ -312,6 +316,6 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): client = self.app.client_manager.network obj = client.find_router(parsed_args.router, ignore_missing=False) - columns = sorted(obj.keys()) + columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) - return (tuple(columns), data) + return columns, data diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index cfd057292a..9e6bf97f65 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -370,6 +370,10 @@ def create_one_router(attrs={}, methods={}): router = fakes.FakeResource(info=copy.deepcopy(router_attrs), methods=copy.deepcopy(router_methods), loaded=True) + + # Set attributes with special mapping in OpenStack SDK. + router.project_id = router_attrs['tenant_id'] + return router @staticmethod diff --git a/openstackclient/tests/network/v2/test_router.py b/openstackclient/tests/network/v2/test_router.py index 794f8ab5c6..68c225e77d 100644 --- a/openstackclient/tests/network/v2/test_router.py +++ b/openstackclient/tests/network/v2/test_router.py @@ -383,7 +383,7 @@ class TestShowRouter(TestRouter): 'ha', 'id', 'name', - 'tenant_id', + 'project_id', ) data = ( @@ -392,7 +392,7 @@ class TestShowRouter(TestRouter): _router.ha, _router.id, _router.name, - _router.tenant_id, + _router.project_id, ) def setUp(self): From b58dd4f17f60b3c6347683b619c093b8d1a40c0b Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Mon, 29 Feb 2016 16:50:54 +0800 Subject: [PATCH 0686/3095] [Volume] Check return value is None in volume unit tests take_action() in commands inheriting from Command returns nothing. So we should assert the return is None in the unit tests of these commands. Change-Id: Idd961a5fa3db825353700837a559621d17f782c5 Partial-Bug: #1550636 --- .../tests/volume/v1/test_qos_specs.py | 30 ++++++++++++------ .../tests/volume/v1/test_volume.py | 16 +++++++--- .../tests/volume/v2/test_backup.py | 8 +++-- .../tests/volume/v2/test_qos_specs.py | 31 +++++++++++++------ .../tests/volume/v2/test_snapshot.py | 17 +++++----- openstackclient/tests/volume/v2/test_type.py | 18 ++++++----- .../tests/volume/v2/test_volume.py | 10 +++--- 7 files changed, 84 insertions(+), 46 deletions(-) diff --git a/openstackclient/tests/volume/v1/test_qos_specs.py b/openstackclient/tests/volume/v1/test_qos_specs.py index 1a6c0fa4ed..4943f5df69 100644 --- a/openstackclient/tests/volume/v1/test_qos_specs.py +++ b/openstackclient/tests/volume/v1/test_qos_specs.py @@ -62,11 +62,13 @@ def test_qos_associate(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) + self.qos_mock.associate.assert_called_with( volume_fakes.qos_id, volume_fakes.type_id ) + self.assertIsNone(result) class TestQosCreate(TestQos): @@ -204,11 +206,12 @@ def test_qos_delete_with_id(self): verifylist = [ ('qos_specs', [volume_fakes.qos_id]) ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) + self.qos_mock.delete.assert_called_with(volume_fakes.qos_id) + self.assertIsNone(result) def test_qos_delete_with_name(self): arglist = [ @@ -217,11 +220,12 @@ def test_qos_delete_with_name(self): verifylist = [ ('qos_specs', [volume_fakes.qos_name]) ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) + self.qos_mock.delete.assert_called_with(volume_fakes.qos_id) + self.assertIsNone(result) class TestQosDisassociate(TestQos): @@ -253,11 +257,13 @@ def test_qos_disassociate_with_volume_type(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) + self.qos_mock.disassociate.assert_called_with( volume_fakes.qos_id, volume_fakes.type_id ) + self.assertIsNone(result) def test_qos_disassociate_with_all_volume_types(self): self.qos_mock.get.return_value = fakes.FakeResource( @@ -275,8 +281,10 @@ def test_qos_disassociate_with_all_volume_types(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) + self.qos_mock.disassociate_all.assert_called_with(volume_fakes.qos_id) + self.assertIsNone(result) class TestQosList(TestQos): @@ -351,11 +359,13 @@ def test_qos_set_with_properties_with_id(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) + self.qos_mock.set_keys.assert_called_with( volume_fakes.qos_id, volume_fakes.qos_specs ) + self.assertIsNone(result) class TestQosShow(TestQos): @@ -436,8 +446,10 @@ def test_qos_unset_with_properties(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) + self.qos_mock.unset_keys.assert_called_with( volume_fakes.qos_id, ['iops', 'foo'] ) + self.assertIsNone(result) diff --git a/openstackclient/tests/volume/v1/test_volume.py b/openstackclient/tests/volume/v1/test_volume.py index 00c509b5d9..35fc917f11 100644 --- a/openstackclient/tests/volume/v1/test_volume.py +++ b/openstackclient/tests/volume/v1/test_volume.py @@ -578,6 +578,7 @@ def test_volume_set_no_options(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.run(parsed_args) + self.assertEqual(0, result) self.assertEqual("No changes requested\n", self.app.log.messages.get('error')) @@ -596,7 +597,7 @@ def test_volume_set_name(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -606,6 +607,7 @@ def test_volume_set_name(self): volume_fakes.volume_id, **kwargs ) + self.assertIsNone(result) def test_volume_set_description(self): arglist = [ @@ -621,7 +623,7 @@ def test_volume_set_description(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -631,6 +633,7 @@ def test_volume_set_description(self): volume_fakes.volume_id, **kwargs ) + self.assertIsNone(result) def test_volume_set_size(self): arglist = [ @@ -646,15 +649,15 @@ def test_volume_set_size(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) # Set expected values size = 130 - self.volumes_mock.extend.assert_called_with( volume_fakes.volume_id, size ) + self.assertIsNone(result) def test_volume_set_size_smaller(self): arglist = [ @@ -671,6 +674,7 @@ def test_volume_set_size_smaller(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.run(parsed_args) + self.assertEqual(0, result) self.assertEqual("New size must be greater than %s GB" % volume_fakes.volume_size, @@ -692,6 +696,7 @@ def test_volume_set_size_not_available(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.run(parsed_args) + self.assertEqual(0, result) self.assertEqual("Volume is in %s state, it must be available before " "size can be extended" % 'error', @@ -711,7 +716,7 @@ def test_volume_set_property(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) # Set expected values metadata = { @@ -721,3 +726,4 @@ def test_volume_set_property(self): volume_fakes.volume_id, metadata ) + self.assertIsNone(result) diff --git a/openstackclient/tests/volume/v2/test_backup.py b/openstackclient/tests/volume/v2/test_backup.py index 6fe3f6668c..cc8dff5a36 100644 --- a/openstackclient/tests/volume/v2/test_backup.py +++ b/openstackclient/tests/volume/v2/test_backup.py @@ -99,11 +99,12 @@ def test_backup_delete(self): verifylist = [ ("backups", [volume_fakes.backup_id]) ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) + self.backups_mock.delete.assert_called_with(volume_fakes.backup_id) + self.assertIsNone(result) class TestBackupList(TestBackup): @@ -213,9 +214,10 @@ def test_backup_restore(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) self.restores_mock.restore.assert_called_with(volume_fakes.backup_id, volume_fakes.volume_id) + self.assertIsNone(result) class TestBackupShow(TestBackup): diff --git a/openstackclient/tests/volume/v2/test_qos_specs.py b/openstackclient/tests/volume/v2/test_qos_specs.py index c826925fda..5232285c85 100644 --- a/openstackclient/tests/volume/v2/test_qos_specs.py +++ b/openstackclient/tests/volume/v2/test_qos_specs.py @@ -62,11 +62,13 @@ def test_qos_associate(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) + self.qos_mock.associate.assert_called_with( volume_fakes.qos_id, volume_fakes.type_id ) + self.assertIsNone(result) class TestQosCreate(TestQos): @@ -205,11 +207,12 @@ def test_qos_delete_with_id(self): verifylist = [ ('qos_specs', [volume_fakes.qos_id]) ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) + self.qos_mock.delete.assert_called_with(volume_fakes.qos_id) + self.assertIsNone(result) def test_qos_delete_with_name(self): arglist = [ @@ -218,11 +221,12 @@ def test_qos_delete_with_name(self): verifylist = [ ('qos_specs', [volume_fakes.qos_name]) ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) + self.qos_mock.delete.assert_called_with(volume_fakes.qos_id) + self.assertIsNone(result) class TestQosDisassociate(TestQos): @@ -254,11 +258,13 @@ def test_qos_disassociate_with_volume_type(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) + self.qos_mock.disassociate.assert_called_with( volume_fakes.qos_id, volume_fakes.type_id ) + self.assertIsNone(result) def test_qos_disassociate_with_all_volume_types(self): self.qos_mock.get.return_value = fakes.FakeResource( @@ -276,8 +282,10 @@ def test_qos_disassociate_with_all_volume_types(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) + self.qos_mock.disassociate_all.assert_called_with(volume_fakes.qos_id) + self.assertIsNone(result) class TestQosList(TestQos): @@ -352,11 +360,13 @@ def test_qos_set_with_properties_with_id(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) + self.qos_mock.set_keys.assert_called_with( volume_fakes.qos_id, volume_fakes.qos_specs ) + self.assertIsNone(result) class TestQosShow(TestQos): @@ -430,15 +440,16 @@ def test_qos_unset_with_properties(self): '--property', 'iops', '--property', 'foo' ] - verifylist = [ ('qos_spec', volume_fakes.qos_id), ('property', ['iops', 'foo']) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) + self.qos_mock.unset_keys.assert_called_with( volume_fakes.qos_id, ['iops', 'foo'] ) + self.assertIsNone(result) diff --git a/openstackclient/tests/volume/v2/test_snapshot.py b/openstackclient/tests/volume/v2/test_snapshot.py index 349e8dac85..87e2fccfa8 100644 --- a/openstackclient/tests/volume/v2/test_snapshot.py +++ b/openstackclient/tests/volume/v2/test_snapshot.py @@ -97,11 +97,12 @@ def test_snapshot_delete(self): verifylist = [ ("snapshots", [volume_fakes.snapshot_id]) ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) + self.snapshots_mock.delete.assert_called_with(volume_fakes.snapshot_id) + self.assertIsNone(result) class TestSnapshotList(TestSnapshot): @@ -231,18 +232,19 @@ def test_snapshot_set(self): ("name", "new_snapshot"), ("property", new_property) ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) kwargs = { "name": "new_snapshot", } - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) - self.snapshots_mock.update.assert_called_with( volume_fakes.snapshot_id, **kwargs) self.snapshots_mock.set_metadata.assert_called_with( volume_fakes.snapshot_id, new_property ) + self.assertIsNone(result) class TestSnapshotShow(TestSnapshot): @@ -296,10 +298,11 @@ def test_snapshot_unset(self): ("snapshot", volume_fakes.snapshot_id), ("property", ["foo"]) ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + + result = self.cmd.take_action(parsed_args) self.snapshots_mock.delete_metadata.assert_called_with( volume_fakes.snapshot_id, ["foo"] ) + self.assertIsNone(result) diff --git a/openstackclient/tests/volume/v2/test_type.py b/openstackclient/tests/volume/v2/test_type.py index 1408b9d93c..b014706b69 100644 --- a/openstackclient/tests/volume/v2/test_type.py +++ b/openstackclient/tests/volume/v2/test_type.py @@ -137,11 +137,12 @@ def test_type_delete(self): verifylist = [ ("volume_type", volume_fakes.type_id) ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) + self.types_mock.delete.assert_called_with(volume_fakes.type_id) + self.assertIsNone(result) class TestTypeList(TestType): @@ -227,7 +228,7 @@ def test_type_set_name(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -237,6 +238,7 @@ def test_type_set_name(self): volume_fakes.type_id, **kwargs ) + self.assertIsNone(result) def test_type_set_description(self): new_desc = 'new_desc' @@ -252,7 +254,7 @@ def test_type_set_description(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -262,6 +264,7 @@ def test_type_set_description(self): volume_fakes.type_id, **kwargs ) + self.assertIsNone(result) def test_type_set_property(self): arglist = [ @@ -276,7 +279,8 @@ def test_type_set_property(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) + self.assertIsNone(result) result = self.types_mock.get.return_value._keys self.assertIn('myprop', result) @@ -338,8 +342,8 @@ def test_type_unset(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) + self.assertIsNone(result) result = self.types_mock.get.return_value._keys - self.assertNotIn('property', result) diff --git a/openstackclient/tests/volume/v2/test_volume.py b/openstackclient/tests/volume/v2/test_volume.py index cbca09b286..a836f79ec6 100644 --- a/openstackclient/tests/volume/v2/test_volume.py +++ b/openstackclient/tests/volume/v2/test_volume.py @@ -396,11 +396,12 @@ def test_volume_delete_one_volume(self): verifylist = [ ("volumes", [volumes[0].id]) ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) + self.volumes_mock.delete.assert_called_with(volumes[0].id) + self.assertIsNone(result) def test_volume_delete_multi_volumes(self): volumes = self.setup_volumes_mock(count=3) @@ -409,14 +410,13 @@ def test_volume_delete_multi_volumes(self): verifylist = [ ('volumes', arglist), ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) calls = [call(v.id) for v in volumes] - self.volumes_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) class TestVolumeList(TestVolume): From 8cf28a34ab29a1630f937b8dd77ebf27455f8471 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Thu, 3 Mar 2016 12:32:09 -0600 Subject: [PATCH 0687/3095] Fix test_aggregate functional test Nova API validation changes [1] appear to have broken the 'os aggregate create' and 'nova aggregate-create' commands when an availability zone name is not specified. This patch set updates the test_aggregate functional test to set the availability zone name in order to unblock the osc gate while nova investigates the issue. [1] https://review.openstack.org/#/c/281143/ Change-Id: I00b497be61c4bc4bc467c66c1e49b2e0636ab841 Related-Bug: #1541691 --- functional/tests/compute/v2/test_aggregate.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/functional/tests/compute/v2/test_aggregate.py b/functional/tests/compute/v2/test_aggregate.py index 73e51ede96..b4d27fae0c 100644 --- a/functional/tests/compute/v2/test_aggregate.py +++ b/functional/tests/compute/v2/test_aggregate.py @@ -25,7 +25,10 @@ class AggregateTests(test.TestCase): @classmethod def setUpClass(cls): opts = cls.get_show_opts(cls.FIELDS) - raw_output = cls.openstack('aggregate create ' + cls.NAME + opts) + # Use the default 'nova' availability zone for the aggregate. + raw_output = cls.openstack( + 'aggregate create --zone nova ' + cls.NAME + opts + ) expected = cls.NAME + '\n' cls.assertOutput(expected, raw_output) From e354d17d2c7603e7f4b9e5ba4abf474dc709be9f Mon Sep 17 00:00:00 2001 From: Mohan Muppidi Date: Tue, 1 Mar 2016 07:13:09 +0000 Subject: [PATCH 0688/3095] " openstack server image create " doesn't print proper info After creating a snapshot of a running instance, a print out similar to server create is expected, but it prints out something like "_info" which is nothing related to created image. _prep_image_detail method is added to /compute/v2/server.py to enable the priting, while running the test properly. Change-Id: I4b06be959768bcdaafd9aa8df497490958bee649 Closes-Bug:1551586 --- openstackclient/compute/v2/server.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index ca239c5195..ad399d7857 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -161,6 +161,23 @@ def _prep_server_detail(compute_client, server): return info +def _prep_image_detail(image_client, image_id): + """Prepare the detailed image dict for printing + + :param image_client: an image client instance + :param image_id: id of image created + :rtype: a dict of image details + """ + + info = utils.find_resource( + image_client.images, + image_id, + ) + # Glance client V2 doesn't have _info attribute + # The following condition deals with it. + return getattr(info, "_info", info) + + def _show_progress(progress): if progress: sys.stdout.write('\rProgress: %s' % progress) @@ -622,12 +639,9 @@ def take_action(self, parsed_args): sys.stdout.write(_('\nError creating server snapshot')) raise SystemExit - image = utils.find_resource( - image_client.images, - image_id, - ) + image = _prep_image_detail(image_client, image_id) - return zip(*sorted(six.iteritems(image._info))) + return zip(*sorted(six.iteritems(image))) class DeleteServer(command.Command): From 2d3ded5e9dd51e1965b6a1fd67fbbebe0d25d1f1 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 3 Mar 2016 23:16:04 +0000 Subject: [PATCH 0689/3095] Updated from global requirements Change-Id: I49cfc47d791d890941dc199bd0d5fa0205b1f1ca --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 7c01b7cd79..4b0a972683 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ pbr>=1.6 # Apache-2.0 six>=1.9.0 # MIT Babel>=1.3 # BSD -cliff!=1.16.0,>=1.15.0 # Apache-2.0 +cliff!=1.16.0,!=1.17.0,>=1.15.0 # Apache-2.0 keystoneauth1>=2.1.0 # Apache-2.0 openstacksdk>=0.7.4 # Apache-2.0 os-client-config>=1.13.1 # Apache-2.0 From 6a96ffc22130f2a852f3371df9aa1e0eb5de9934 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Fri, 4 Mar 2016 10:21:02 +0000 Subject: [PATCH 0690/3095] Updated from global requirements Change-Id: I8bfe67141572a0bf406959dea83eaf1f2c30b890 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 4b0a972683..57b2376e01 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ os-client-config>=1.13.1 # Apache-2.0 oslo.config>=3.7.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 oslo.utils>=3.5.0 # Apache-2.0 -python-glanceclient>=1.2.0 # Apache-2.0 +python-glanceclient>=2.0.0 # Apache-2.0 python-keystoneclient!=1.8.0,!=2.1.0,>=1.6.0 # Apache-2.0 python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0 python-cinderclient>=1.3.1 # Apache-2.0 From bf2dbf3256aec882ad558b1f38dd568fa6f08ff1 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Fri, 12 Feb 2016 10:11:24 -0600 Subject: [PATCH 0691/3095] Add subnet functional tests Add functional tests for "os subnet" commands. Change-Id: Ie80763334f2fb4099b3e549256576d71cc213c07 Depends-On: Ia6120b8dccf2ee83dc89b3f496f7180d4dc5199a Related-Bug: #1523258 Related-Bug: #1542359 Related-Bug: #1542362 Related-Bug: #1542364 Related-Bug: #1542363 Partially-Implements: blueprint neutron-client --- functional/tests/network/v2/test_subnet.py | 62 ++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 functional/tests/network/v2/test_subnet.py diff --git a/functional/tests/network/v2/test_subnet.py b/functional/tests/network/v2/test_subnet.py new file mode 100644 index 0000000000..afecfab07b --- /dev/null +++ b/functional/tests/network/v2/test_subnet.py @@ -0,0 +1,62 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import uuid + +import testtools + +from functional.common import test + + +class SubnetTests(test.TestCase): + """Functional tests for subnet. """ + NAME = uuid.uuid4().hex + NETWORK_NAME = uuid.uuid4().hex + HEADERS = ['Name'] + FIELDS = ['name'] + + @classmethod + def setUpClass(cls): + # Create a network for the subnet. + cls.openstack('network create ' + cls.NETWORK_NAME) + opts = cls.get_show_opts(cls.FIELDS) + raw_output = cls.openstack( + 'subnet create --network ' + cls.NETWORK_NAME + + ' --subnet-range 10.10.10.0/24 ' + + cls.NAME + opts + ) + expected = cls.NAME + '\n' + cls.assertOutput(expected, raw_output) + + @classmethod + def tearDownClass(cls): + raw_output = cls.openstack('subnet delete ' + cls.NAME) + cls.assertOutput('', raw_output) + raw_output = cls.openstack('network delete ' + cls.NETWORK_NAME) + cls.assertOutput('', raw_output) + + def test_subnet_list(self): + opts = self.get_list_opts(self.HEADERS) + raw_output = self.openstack('subnet list' + opts) + self.assertIn(self.NAME, raw_output) + + @testtools.skip('bug/1542363') + def test_subnet_set(self): + self.openstack('subnet set --no-dhcp ' + self.NAME) + opts = self.get_show_opts(['name', 'enable_dhcp']) + raw_output = self.openstack('subnet show ' + self.NAME + opts) + self.assertEqual("False\n" + self.NAME + "\n", raw_output) + + def test_subnet_show(self): + opts = self.get_show_opts(self.FIELDS) + raw_output = self.openstack('subnet show ' + self.NAME + opts) + self.assertEqual(self.NAME + "\n", raw_output) From d1d4a40808741c416ecc51294fab78138f9184fa Mon Sep 17 00:00:00 2001 From: Jas Date: Thu, 28 Jan 2016 11:08:17 -0600 Subject: [PATCH 0692/3095] Add 'port create' command This patch adds usage of 'port create' in CLI Change-Id: I888af50784c3b6c7ec30552ade79f05a5e974711 Partial-bug: #1519909 Partially-implements: blueprint neutron-client --- doc/source/command-objects/port.rst | 78 +++++++ openstackclient/network/v2/port.py | 171 ++++++++++++++- openstackclient/tests/network/v2/test_port.py | 198 +++++++++++++----- ...-port-create-command-a3580662721a6312.yaml | 5 + setup.cfg | 1 + 5 files changed, 400 insertions(+), 53 deletions(-) create mode 100644 releasenotes/notes/add-port-create-command-a3580662721a6312.yaml diff --git a/doc/source/command-objects/port.rst b/doc/source/command-objects/port.rst index 414b7437f5..0c91f3ac00 100644 --- a/doc/source/command-objects/port.rst +++ b/doc/source/command-objects/port.rst @@ -4,6 +4,84 @@ port Network v2 +port create +----------- + +Create new port + +.. program:: port create +.. code:: bash + + os port create + --network + [--fixed-ip subnet=,ip-address=] + [--device-id ] + [--device-owner ] + [--vnic-type ] + [--binding-profile ] + [--host-id ] + [--enable | --disable] + [--mac-address ] + [--project [--project-domain ]] + + +.. option:: --network + + Network this port belongs to (name or ID) + +.. option:: --fixed-ip subnet=,ip-address= + + Desired IP and/or subnet (name or ID) for this port: + subnet=,ip-address= + (this option can be repeated) + +.. option:: --device-id + + Device ID of this port + +.. option:: --device-owner + + Device owner of this port + +.. option:: --vnic-type + + VNIC type for this port (direct | direct-physical | macvtap | normal(default) | baremetal) + +.. option:: --binding-profile + + Custom data to be passed as binding:profile: = + (this option can be repeated) + +.. option:: --host-id + + The ID of the host where the port is allocated + +.. option:: --enable + + Enable port (default) + +.. option:: --disable + + Disable port + +.. option:: --mac-address + + MAC address of this port + +.. option:: --project + + Owner's project (name or ID) + +.. option:: --project-domain + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + +.. _port_create-name: +.. describe:: + + Name of this port + port delete ----------- diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 19b3701d87..f9d0fc957f 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -14,13 +14,14 @@ """Port action implementations""" from openstackclient.common import command +from openstackclient.common import parseractions from openstackclient.common import utils +from openstackclient.identity import common as identity_common def _format_admin_state(state): return 'UP' if state else 'DOWN' - _formatters = { 'admin_state_up': _format_admin_state, 'allowed_address_pairs': utils.format_list_of_dicts, @@ -49,7 +50,171 @@ def _get_columns(item): if binding_column in columns: columns.remove(binding_column) columns.append(binding_column.replace('binding:', 'binding_', 1)) - return sorted(columns) + return tuple(sorted(columns)) + + +def _get_attrs(client_manager, parsed_args): + attrs = {} + + if parsed_args.name is not None: + attrs['name'] = str(parsed_args.name) + if parsed_args.fixed_ip is not None: + attrs['fixed_ips'] = parsed_args.fixed_ip + if parsed_args.device_id is not None: + attrs['device_id'] = parsed_args.device_id + if parsed_args.device_owner is not None: + attrs['device_owner'] = parsed_args.device_owner + if parsed_args.admin_state is not None: + attrs['admin_state_up'] = parsed_args.admin_state + if parsed_args.binding_profile is not None: + attrs['binding:profile'] = parsed_args.binding_profile + if parsed_args.vnic_type is not None: + attrs['binding:vnic_type'] = parsed_args.vnic_type + if parsed_args.host_id is not None: + attrs['binding:host_id'] = parsed_args.host_id + + # The remaining options do not support 'port set' command, so they require + # additional check + if 'mac_address' in parsed_args and parsed_args.mac_address is not None: + attrs['mac_address'] = parsed_args.mac_address + if 'network' in parsed_args and parsed_args.network is not None: + attrs['network_id'] = parsed_args.network + if 'project' in parsed_args and parsed_args.project is not None: + # TODO(singhj): since 'project' logic is common among + # router, network, port etc., maybe move it to a common file. + identity_client = client_manager.identity + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + attrs['tenant_id'] = project_id + + return attrs + + +def _prepare_fixed_ips(client_manager, parsed_args): + """Fix and properly format fixed_ip option. + + Appropriately convert any subnet names to their respective ids. + Convert fixed_ips in parsed args to be in valid dictionary format: + {'subnet': 'foo'}. + """ + client = client_manager.network + ips = [] + + if parsed_args.fixed_ip: + for ip_spec in parsed_args.fixed_ip: + if 'subnet' in ip_spec: + subnet_name_id = ip_spec['subnet'] + if subnet_name_id: + _subnet = client.find_subnet(subnet_name_id, + ignore_missing=False) + ip_spec['subnet_id'] = _subnet.id + del ip_spec['subnet'] + + if 'ip-address' in ip_spec: + ip_spec['ip_address'] = ip_spec['ip-address'] + del ip_spec['ip-address'] + + ips.append(ip_spec) + + if ips: + parsed_args.fixed_ip = ips + + +def _add_updatable_args(parser): + parser.add_argument( + '--fixed-ip', + metavar='subnet=,ip-address=', + action=parseractions.MultiKeyValueAction, + optional_keys=['subnet', 'ip-address'], + help='Desired IP and/or subnet (name or ID) for this port: ' + 'subnet=,ip-address= ' + '(this option can be repeated)') + parser.add_argument( + '--device-id', + metavar='', + help='Device ID of this port') + parser.add_argument( + '--device-owner', + metavar='', + help='Device owner of this port') + parser.add_argument( + '--vnic-type', + metavar='', + choices=['direct', 'direct-physical', 'macvtap', + 'normal', 'baremetal'], + help='VNIC type for this port (direct | direct-physical |' + ' macvtap | normal(default) | baremetal)') + parser.add_argument( + '--binding-profile', + metavar='', + action=parseractions.KeyValueAction, + help='Custom data to be passed as binding:profile: = ' + '(this option can be repeated)') + parser.add_argument( + '--host-id', + metavar='', + help='The ID of the host where the port is allocated' + ) + + +class CreatePort(command.ShowOne): + """Create a new port""" + + def get_parser(self, prog_name): + parser = super(CreatePort, self).get_parser(prog_name) + + parser.add_argument( + '--network', + metavar='', + required=True, + help='Network this port belongs to (name or ID)') + _add_updatable_args(parser) + admin_group = parser.add_mutually_exclusive_group() + admin_group.add_argument( + '--enable', + dest='admin_state', + action='store_true', + default=True, + help='Enable port (default)', + ) + admin_group.add_argument( + '--disable', + dest='admin_state', + action='store_false', + help='Disable port', + ) + parser.add_argument( + '--mac-address', + metavar='', + help='MAC address of this port') + parser.add_argument( + '--project', + metavar='', + help="Owner's project (name or ID)") + parser.add_argument( + 'name', + metavar='', + help='Name of this port') + identity_common.add_project_domain_option_to_parser(parser) + # TODO(singhj): Add support for extended options: + # qos,security groups,dhcp, address pairs + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + _network = client.find_network(parsed_args.network, + ignore_missing=False) + parsed_args.network = _network.id + _prepare_fixed_ips(self.app.client_manager, parsed_args) + attrs = _get_attrs(self.app.client_manager, parsed_args) + obj = client.create_port(**attrs) + columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns, formatters=_formatters) + + return columns, data class DeletePort(command.Command): @@ -90,4 +255,4 @@ def take_action(self, parsed_args): obj = client.find_port(parsed_args.port, ignore_missing=False) columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) - return (tuple(columns), data) + return columns, data diff --git a/openstackclient/tests/network/v2/test_port.py b/openstackclient/tests/network/v2/test_port.py index bc246bd877..907d8a7d10 100644 --- a/openstackclient/tests/network/v2/test_port.py +++ b/openstackclient/tests/network/v2/test_port.py @@ -27,6 +27,150 @@ def setUp(self): # Get a shortcut to the network client self.network = self.app.client_manager.network + def _get_common_cols_data(self, fake_port): + columns = ( + 'admin_state_up', + 'allowed_address_pairs', + 'binding_host_id', + 'binding_profile', + 'binding_vif_details', + 'binding_vif_type', + 'binding_vnic_type', + 'device_id', + 'device_owner', + 'dns_assignment', + 'dns_name', + 'extra_dhcp_opts', + 'fixed_ips', + 'id', + 'mac_address', + 'name', + 'network_id', + 'port_security_enabled', + 'project_id', + 'security_groups', + 'status', + ) + + data = ( + port._format_admin_state(fake_port.admin_state_up), + utils.format_list_of_dicts(fake_port.allowed_address_pairs), + fake_port.binding_host_id, + utils.format_dict(fake_port.binding_profile), + utils.format_dict(fake_port.binding_vif_details), + fake_port.binding_vif_type, + fake_port.binding_vnic_type, + fake_port.device_id, + fake_port.device_owner, + utils.format_list_of_dicts(fake_port.dns_assignment), + fake_port.dns_name, + utils.format_list_of_dicts(fake_port.extra_dhcp_opts), + utils.format_list_of_dicts(fake_port.fixed_ips), + fake_port.id, + fake_port.mac_address, + fake_port.name, + fake_port.network_id, + fake_port.port_security_enabled, + fake_port.project_id, + utils.format_list(fake_port.security_groups), + fake_port.status, + ) + + return columns, data + + +class TestCreatePort(TestPort): + + _port = network_fakes.FakePort.create_one_port() + + def setUp(self): + super(TestCreatePort, self).setUp() + + self.network.create_port = mock.Mock(return_value=self._port) + fake_net = network_fakes.FakeNetwork.create_one_network({ + 'id': self._port.network_id, + }) + self.network.find_network = mock.Mock(return_value=fake_net) + self.fake_subnet = network_fakes.FakeSubnet.create_one_subnet() + self.network.find_subnet = mock.Mock(return_value=self.fake_subnet) + # Get the command object to test + self.cmd = port.CreatePort(self.app, self.namespace) + + def test_create_default_options(self): + arglist = [ + '--network', self._port.network_id, + 'test-port', + ] + verifylist = [ + ('network', self._port.network_id,), + ('admin_state', True), + ('name', 'test-port'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_port.assert_called_with(**{ + 'admin_state_up': True, + 'network_id': self._port.network_id, + 'name': 'test-port', + }) + + ref_columns, ref_data = self._get_common_cols_data(self._port) + self.assertEqual(ref_columns, columns) + self.assertEqual(ref_data, data) + + def test_create_full_options(self): + arglist = [ + '--mac-address', 'aa:aa:aa:aa:aa:aa', + '--fixed-ip', 'subnet=%s,ip-address=10.0.0.2' + % self.fake_subnet.id, + '--device-id', 'deviceid', + '--device-owner', 'fakeowner', + '--disable', + '--vnic-type', 'macvtap', + '--binding-profile', 'foo=bar', + '--binding-profile', 'foo2=bar2', + '--network', self._port.network_id, + 'test-port', + + ] + verifylist = [ + ('mac_address', 'aa:aa:aa:aa:aa:aa'), + ( + 'fixed_ip', + [{'subnet': self.fake_subnet.id, 'ip-address': '10.0.0.2'}] + ), + ('device_id', 'deviceid'), + ('device_owner', 'fakeowner'), + ('admin_state', False), + ('vnic_type', 'macvtap'), + ('binding_profile', {'foo': 'bar', 'foo2': 'bar2'}), + ('network', self._port.network_id), + ('name', 'test-port'), + + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_port.assert_called_with(**{ + 'mac_address': 'aa:aa:aa:aa:aa:aa', + 'fixed_ips': [{'subnet_id': self.fake_subnet.id, + 'ip_address': '10.0.0.2'}], + 'device_id': 'deviceid', + 'device_owner': 'fakeowner', + 'admin_state_up': False, + 'binding:vnic_type': 'macvtap', + 'binding:profile': {'foo': 'bar', 'foo2': 'bar2'}, + 'network_id': self._port.network_id, + 'name': 'test-port', + }) + + ref_columns, ref_data = self._get_common_cols_data(self._port) + self.assertEqual(ref_columns, columns) + self.assertEqual(ref_data, data) + class TestDeletePort(TestPort): @@ -60,54 +204,6 @@ class TestShowPort(TestPort): # The port to show. _port = network_fakes.FakePort.create_one_port() - columns = ( - 'admin_state_up', - 'allowed_address_pairs', - 'binding_host_id', - 'binding_profile', - 'binding_vif_details', - 'binding_vif_type', - 'binding_vnic_type', - 'device_id', - 'device_owner', - 'dns_assignment', - 'dns_name', - 'extra_dhcp_opts', - 'fixed_ips', - 'id', - 'mac_address', - 'name', - 'network_id', - 'port_security_enabled', - 'project_id', - 'security_groups', - 'status', - ) - - data = ( - port._format_admin_state(_port.admin_state_up), - utils.format_list_of_dicts(_port.allowed_address_pairs), - _port.binding_host_id, - utils.format_dict(_port.binding_profile), - utils.format_dict(_port.binding_vif_details), - _port.binding_vif_type, - _port.binding_vnic_type, - _port.device_id, - _port.device_owner, - utils.format_list_of_dicts(_port.dns_assignment), - _port.dns_name, - utils.format_list_of_dicts(_port.extra_dhcp_opts), - utils.format_list_of_dicts(_port.fixed_ips), - _port.id, - _port.mac_address, - _port.name, - _port.network_id, - _port.port_security_enabled, - _port.project_id, - utils.format_list(_port.security_groups), - _port.status, - ) - def setUp(self): super(TestShowPort, self).setUp() @@ -136,5 +232,7 @@ def test_show_all_options(self): self.network.find_port.assert_called_with(self._port.name, ignore_missing=False) - self.assertEqual(tuple(self.columns), columns) - self.assertEqual(self.data, data) + + ref_columns, ref_data = self._get_common_cols_data(self._port) + self.assertEqual(ref_columns, columns) + self.assertEqual(ref_data, data) diff --git a/releasenotes/notes/add-port-create-command-a3580662721a6312.yaml b/releasenotes/notes/add-port-create-command-a3580662721a6312.yaml new file mode 100644 index 0000000000..4fafb42c00 --- /dev/null +++ b/releasenotes/notes/add-port-create-command-a3580662721a6312.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add support for the ``port create`` command. + [Bug `1519909 `_] diff --git a/setup.cfg b/setup.cfg index 284e6dec12..d00c5c5f92 100644 --- a/setup.cfg +++ b/setup.cfg @@ -331,6 +331,7 @@ openstack.network.v2 = network_list = openstackclient.network.v2.network:ListNetwork network_set = openstackclient.network.v2.network:SetNetwork network_show = openstackclient.network.v2.network:ShowNetwork + port_create = openstackclient.network.v2.port:CreatePort port_delete = openstackclient.network.v2.port:DeletePort port_show = openstackclient.network.v2.port:ShowPort router_create = openstackclient.network.v2.router:CreateRouter From 3ede46d4d089a315bc1792bca318d8481013b585 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Fri, 4 Mar 2016 19:42:17 +0000 Subject: [PATCH 0693/3095] Updated from global requirements Change-Id: Ife3956a1109ffa2faf367953cc13b8cb5f64e5c2 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 57b2376e01..7349d9f74a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ six>=1.9.0 # MIT Babel>=1.3 # BSD cliff!=1.16.0,!=1.17.0,>=1.15.0 # Apache-2.0 keystoneauth1>=2.1.0 # Apache-2.0 -openstacksdk>=0.7.4 # Apache-2.0 +openstacksdk>=0.8.1 # Apache-2.0 os-client-config>=1.13.1 # Apache-2.0 oslo.config>=3.7.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 From eb1574281b84e86ee4c4b65d900e1de82c24471e Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 5 Mar 2016 17:12:42 +0800 Subject: [PATCH 0694/3095] Test take_action() instead of run() in unit tests Some of the unit tests test run(), but not take_action(). For example, in openstackclient/tests/volume/v1/test_volume.py, there is: def test_volume_set_size_smaller(self): ...... result = self.cmd.run(parsed_args) self.assertEqual(0, result) ...... run() is defined in class Command in cliff. We don't need to test it in OSC unit tests. On the contrary, we should test take_action(), which is overwritten in each command classes in OSC. Change-Id: If07e89953d40ac530f08cbb1ec05f5805171364b Closes-bug: #1553468 --- .../tests/identity/v2_0/test_project.py | 31 +++++++++--------- .../tests/identity/v2_0/test_user.py | 5 +-- .../tests/identity/v3/test_consumer.py | 9 +++--- .../tests/identity/v3/test_domain.py | 24 +++++++------- .../tests/identity/v3/test_endpoint.py | 32 +++++++++---------- .../tests/identity/v3/test_project.py | 29 +++++++++-------- .../tests/identity/v3/test_region.py | 16 +++++----- .../tests/identity/v3/test_role.py | 16 +++++----- .../tests/identity/v3/test_service.py | 29 +++++++++-------- .../tests/identity/v3/test_user.py | 5 +-- .../tests/volume/v1/test_volume.py | 12 +++---- 11 files changed, 107 insertions(+), 101 deletions(-) diff --git a/openstackclient/tests/identity/v2_0/test_project.py b/openstackclient/tests/identity/v2_0/test_project.py index 669c3eabe7..9857029766 100644 --- a/openstackclient/tests/identity/v2_0/test_project.py +++ b/openstackclient/tests/identity/v2_0/test_project.py @@ -307,12 +307,12 @@ def test_project_delete_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + result = self.cmd.take_action(parsed_args) self.projects_mock.delete.assert_called_with( identity_fakes.project_id, ) + self.assertIsNone(result) class TestProjectList(TestProject): @@ -406,8 +406,9 @@ def test_project_set_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + result = self.cmd.take_action(parsed_args) + + self.assertIsNone(result) def test_project_set_name(self): arglist = [ @@ -422,8 +423,7 @@ def test_project_set_name(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -435,6 +435,7 @@ def test_project_set_name(self): identity_fakes.project_id, **kwargs ) + self.assertIsNone(result) def test_project_set_description(self): arglist = [ @@ -449,8 +450,7 @@ def test_project_set_description(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -462,6 +462,7 @@ def test_project_set_description(self): identity_fakes.project_id, **kwargs ) + self.assertIsNone(result) def test_project_set_enable(self): arglist = [ @@ -475,8 +476,7 @@ def test_project_set_enable(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -488,6 +488,7 @@ def test_project_set_enable(self): identity_fakes.project_id, **kwargs ) + self.assertIsNone(result) def test_project_set_disable(self): arglist = [ @@ -501,8 +502,7 @@ def test_project_set_disable(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -514,6 +514,7 @@ def test_project_set_disable(self): identity_fakes.project_id, **kwargs ) + self.assertIsNone(result) def test_project_set_property(self): arglist = [ @@ -527,8 +528,7 @@ def test_project_set_property(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -542,6 +542,7 @@ def test_project_set_property(self): identity_fakes.project_id, **kwargs ) + self.assertIsNone(result) class TestProjectShow(TestProject): @@ -615,7 +616,7 @@ def test_project_unset_key(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.run(parsed_args) + self.cmd.take_action(parsed_args) # Set expected values kwargs = { 'description': identity_fakes.project_description, diff --git a/openstackclient/tests/identity/v2_0/test_user.py b/openstackclient/tests/identity/v2_0/test_user.py index a7332e6366..9afe4ad12d 100644 --- a/openstackclient/tests/identity/v2_0/test_user.py +++ b/openstackclient/tests/identity/v2_0/test_user.py @@ -558,8 +558,9 @@ def test_user_set_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + result = self.cmd.take_action(parsed_args) + + self.assertIsNone(result) def test_user_set_name(self): arglist = [ diff --git a/openstackclient/tests/identity/v3/test_consumer.py b/openstackclient/tests/identity/v3/test_consumer.py index 7f6af7348c..4d15d5d836 100644 --- a/openstackclient/tests/identity/v3/test_consumer.py +++ b/openstackclient/tests/identity/v3/test_consumer.py @@ -87,12 +87,12 @@ def test_delete_consumer(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + result = self.cmd.take_action(parsed_args) self.consumers_mock.delete.assert_called_with( identity_fakes.consumer_id, ) + self.assertIsNone(result) class TestConsumerList(TestOAuth1): @@ -208,11 +208,12 @@ def test_consumer_update(self): ('consumer', identity_fakes.consumer_id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + + result = self.cmd.take_action(parsed_args) kwargs = {'description': new_description} self.consumers_mock.update.assert_called_with( identity_fakes.consumer_id, **kwargs ) + self.assertIsNone(result) diff --git a/openstackclient/tests/identity/v3/test_domain.py b/openstackclient/tests/identity/v3/test_domain.py index 9de6b26a61..f3777f12eb 100644 --- a/openstackclient/tests/identity/v3/test_domain.py +++ b/openstackclient/tests/identity/v3/test_domain.py @@ -194,12 +194,12 @@ def test_domain_delete(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + result = self.cmd.take_action(parsed_args) self.domains_mock.delete.assert_called_with( identity_fakes.domain_id, ) + self.assertIsNone(result) class TestDomainList(TestDomain): @@ -269,10 +269,10 @@ def test_domain_set_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + result = self.cmd.take_action(parsed_args) self.assertNotCalled(self.domains_mock.update) + self.assertIsNone(result) def test_domain_set_name(self): arglist = [ @@ -285,8 +285,7 @@ def test_domain_set_name(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -296,6 +295,7 @@ def test_domain_set_name(self): identity_fakes.domain_id, **kwargs ) + self.assertIsNone(result) def test_domain_set_description(self): arglist = [ @@ -308,8 +308,7 @@ def test_domain_set_description(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -319,6 +318,7 @@ def test_domain_set_description(self): identity_fakes.domain_id, **kwargs ) + self.assertIsNone(result) def test_domain_set_enable(self): arglist = [ @@ -331,8 +331,7 @@ def test_domain_set_enable(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -342,6 +341,7 @@ def test_domain_set_enable(self): identity_fakes.domain_id, **kwargs ) + self.assertIsNone(result) def test_domain_set_disable(self): arglist = [ @@ -354,8 +354,7 @@ def test_domain_set_disable(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -365,6 +364,7 @@ def test_domain_set_disable(self): identity_fakes.domain_id, **kwargs ) + self.assertIsNone(result) class TestDomainShow(TestDomain): diff --git a/openstackclient/tests/identity/v3/test_endpoint.py b/openstackclient/tests/identity/v3/test_endpoint.py index 1c481930a7..d953459cae 100644 --- a/openstackclient/tests/identity/v3/test_endpoint.py +++ b/openstackclient/tests/identity/v3/test_endpoint.py @@ -277,12 +277,12 @@ def test_endpoint_delete(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + result = self.cmd.take_action(parsed_args) self.endpoints_mock.delete.assert_called_with( identity_fakes.endpoint_id, ) + self.assertIsNone(result) class TestEndpointList(TestEndpoint): @@ -483,10 +483,10 @@ def test_endpoint_set_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + result = self.cmd.take_action(parsed_args) self.assertNotCalled(self.endpoints_mock.update) + self.assertIsNone(result) def test_endpoint_set_interface(self): arglist = [ @@ -499,8 +499,7 @@ def test_endpoint_set_interface(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -514,6 +513,7 @@ def test_endpoint_set_interface(self): identity_fakes.endpoint_id, **kwargs ) + self.assertIsNone(result) def test_endpoint_set_url(self): arglist = [ @@ -526,8 +526,7 @@ def test_endpoint_set_url(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -541,6 +540,7 @@ def test_endpoint_set_url(self): identity_fakes.endpoint_id, **kwargs ) + self.assertIsNone(result) def test_endpoint_set_service(self): arglist = [ @@ -553,8 +553,7 @@ def test_endpoint_set_service(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -568,6 +567,7 @@ def test_endpoint_set_service(self): identity_fakes.endpoint_id, **kwargs ) + self.assertIsNone(result) def test_endpoint_set_region(self): arglist = [ @@ -580,8 +580,7 @@ def test_endpoint_set_region(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -595,6 +594,7 @@ def test_endpoint_set_region(self): identity_fakes.endpoint_id, **kwargs ) + self.assertIsNone(result) def test_endpoint_set_enable(self): arglist = [ @@ -607,8 +607,7 @@ def test_endpoint_set_enable(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -622,6 +621,7 @@ def test_endpoint_set_enable(self): identity_fakes.endpoint_id, **kwargs ) + self.assertIsNone(result) def test_endpoint_set_disable(self): arglist = [ @@ -634,8 +634,7 @@ def test_endpoint_set_disable(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -649,6 +648,7 @@ def test_endpoint_set_disable(self): identity_fakes.endpoint_id, **kwargs ) + self.assertIsNone(result) class TestEndpointShow(TestEndpoint): diff --git a/openstackclient/tests/identity/v3/test_project.py b/openstackclient/tests/identity/v3/test_project.py index b834e94308..1e9d1c8bf1 100644 --- a/openstackclient/tests/identity/v3/test_project.py +++ b/openstackclient/tests/identity/v3/test_project.py @@ -436,12 +436,12 @@ def test_project_delete_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + result = self.cmd.take_action(parsed_args) self.projects_mock.delete.assert_called_with( identity_fakes.project_id, ) + self.assertIsNone(result) class TestProjectList(TestProject): @@ -593,8 +593,9 @@ def test_project_set_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + result = self.cmd.take_action(parsed_args) + + self.assertIsNone(result) def test_project_set_name(self): arglist = [ @@ -611,8 +612,7 @@ def test_project_set_name(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -624,6 +624,7 @@ def test_project_set_name(self): identity_fakes.project_id, **kwargs ) + self.assertIsNone(result) def test_project_set_description(self): arglist = [ @@ -640,8 +641,7 @@ def test_project_set_description(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -651,6 +651,7 @@ def test_project_set_description(self): identity_fakes.project_id, **kwargs ) + self.assertIsNone(result) def test_project_set_enable(self): arglist = [ @@ -666,8 +667,7 @@ def test_project_set_enable(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -677,6 +677,7 @@ def test_project_set_enable(self): identity_fakes.project_id, **kwargs ) + self.assertIsNone(result) def test_project_set_disable(self): arglist = [ @@ -692,8 +693,7 @@ def test_project_set_disable(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -703,6 +703,7 @@ def test_project_set_disable(self): identity_fakes.project_id, **kwargs ) + self.assertIsNone(result) def test_project_set_property(self): arglist = [ @@ -718,8 +719,7 @@ def test_project_set_property(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -730,6 +730,7 @@ def test_project_set_property(self): identity_fakes.project_id, **kwargs ) + self.assertIsNone(result) class TestProjectShow(TestProject): diff --git a/openstackclient/tests/identity/v3/test_region.py b/openstackclient/tests/identity/v3/test_region.py index f5f5079337..02dec5681c 100644 --- a/openstackclient/tests/identity/v3/test_region.py +++ b/openstackclient/tests/identity/v3/test_region.py @@ -157,12 +157,12 @@ def test_region_delete_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + result = self.cmd.take_action(parsed_args) self.regions_mock.delete.assert_called_with( identity_fakes.region_id, ) + self.assertIsNone(result) class TestRegionList(TestRegion): @@ -251,10 +251,10 @@ def test_region_set_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + result = self.cmd.take_action(parsed_args) self.assertNotCalled(self.regions_mock.update) + self.assertIsNone(result) def test_region_set_description(self): arglist = [ @@ -267,8 +267,7 @@ def test_region_set_description(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -278,6 +277,7 @@ def test_region_set_description(self): identity_fakes.region_id, **kwargs ) + self.assertIsNone(result) def test_region_set_parent_region_id(self): arglist = [ @@ -290,8 +290,7 @@ def test_region_set_parent_region_id(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -301,6 +300,7 @@ def test_region_set_parent_region_id(self): identity_fakes.region_id, **kwargs ) + self.assertIsNone(result) class TestRegionShow(TestRegion): diff --git a/openstackclient/tests/identity/v3/test_role.py b/openstackclient/tests/identity/v3/test_role.py index f3661324c8..19410debe4 100644 --- a/openstackclient/tests/identity/v3/test_role.py +++ b/openstackclient/tests/identity/v3/test_role.py @@ -116,8 +116,7 @@ def test_role_add_user_domain(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -130,6 +129,7 @@ def test_role_add_user_domain(self): identity_fakes.role_id, **kwargs ) + self.assertIsNone(result) def test_role_add_user_project(self): arglist = [ @@ -149,8 +149,7 @@ def test_role_add_user_project(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -163,6 +162,7 @@ def test_role_add_user_project(self): identity_fakes.role_id, **kwargs ) + self.assertIsNone(result) def test_role_add_group_domain(self): arglist = [ @@ -182,8 +182,7 @@ def test_role_add_group_domain(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -196,6 +195,7 @@ def test_role_add_group_domain(self): identity_fakes.role_id, **kwargs ) + self.assertIsNone(result) def test_role_add_group_project(self): arglist = [ @@ -215,8 +215,7 @@ def test_role_add_group_project(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -229,6 +228,7 @@ def test_role_add_group_project(self): identity_fakes.role_id, **kwargs ) + self.assertIsNone(result) class TestRoleAddInherited(TestRoleAdd, TestRoleInherited): diff --git a/openstackclient/tests/identity/v3/test_service.py b/openstackclient/tests/identity/v3/test_service.py index 2bc5927f14..1e70383f5a 100644 --- a/openstackclient/tests/identity/v3/test_service.py +++ b/openstackclient/tests/identity/v3/test_service.py @@ -204,12 +204,12 @@ def test_service_delete_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + result = self.cmd.take_action(parsed_args) self.services_mock.delete.assert_called_with( identity_fakes.service_id, ) + self.assertIsNone(result) class TestServiceList(TestService): @@ -310,8 +310,9 @@ def test_service_set_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + result = self.cmd.take_action(parsed_args) + + self.assertIsNone(result) def test_service_set_type(self): arglist = [ @@ -328,8 +329,7 @@ def test_service_set_type(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -340,6 +340,7 @@ def test_service_set_type(self): identity_fakes.service_id, **kwargs ) + self.assertIsNone(result) def test_service_set_name(self): arglist = [ @@ -356,8 +357,7 @@ def test_service_set_name(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -368,6 +368,7 @@ def test_service_set_name(self): identity_fakes.service_id, **kwargs ) + self.assertIsNone(result) def test_service_set_description(self): arglist = [ @@ -384,8 +385,7 @@ def test_service_set_description(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -396,6 +396,7 @@ def test_service_set_description(self): identity_fakes.service_id, **kwargs ) + self.assertIsNone(result) def test_service_set_enable(self): arglist = [ @@ -412,8 +413,7 @@ def test_service_set_enable(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -424,6 +424,7 @@ def test_service_set_enable(self): identity_fakes.service_id, **kwargs ) + self.assertIsNone(result) def test_service_set_disable(self): arglist = [ @@ -440,8 +441,7 @@ def test_service_set_disable(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -452,6 +452,7 @@ def test_service_set_disable(self): identity_fakes.service_id, **kwargs ) + self.assertIsNone(result) class TestServiceShow(TestService): diff --git a/openstackclient/tests/identity/v3/test_user.py b/openstackclient/tests/identity/v3/test_user.py index 3757c5f8a2..571e2d798e 100644 --- a/openstackclient/tests/identity/v3/test_user.py +++ b/openstackclient/tests/identity/v3/test_user.py @@ -754,8 +754,9 @@ def test_user_set_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) + result = self.cmd.take_action(parsed_args) + + self.assertIsNone(result) def test_user_set_name(self): arglist = [ diff --git a/openstackclient/tests/volume/v1/test_volume.py b/openstackclient/tests/volume/v1/test_volume.py index 35fc917f11..e0fd1c0800 100644 --- a/openstackclient/tests/volume/v1/test_volume.py +++ b/openstackclient/tests/volume/v1/test_volume.py @@ -577,11 +577,11 @@ def test_volume_set_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) + result = self.cmd.take_action(parsed_args) - self.assertEqual(0, result) self.assertEqual("No changes requested\n", self.app.log.messages.get('error')) + self.assertIsNone(result) def test_volume_set_name(self): arglist = [ @@ -673,12 +673,12 @@ def test_volume_set_size_smaller(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) + result = self.cmd.take_action(parsed_args) - self.assertEqual(0, result) self.assertEqual("New size must be greater than %s GB" % volume_fakes.volume_size, self.app.log.messages.get('error')) + self.assertIsNone(result) def test_volume_set_size_not_available(self): self.volumes_mock.get.return_value.status = 'error' @@ -695,12 +695,12 @@ def test_volume_set_size_not_available(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.run(parsed_args) + result = self.cmd.take_action(parsed_args) - self.assertEqual(0, result) self.assertEqual("Volume is in %s state, it must be available before " "size can be extended" % 'error', self.app.log.messages.get('error')) + self.assertIsNone(result) def test_volume_set_property(self): arglist = [ From 46e86e5a4a8f147b02559d6e612a57bc7ced7171 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Sat, 5 Mar 2016 07:33:24 -0600 Subject: [PATCH 0695/3095] Add release note for security group set refactor Add a release note for [1]. [1] https://review.openstack.org/#/c/287763/ Change-Id: I30812c3ead477267dc7e3dc774c09b3435152eb9 Partial-Bug: #1519511 Implements: blueprint neutron-client --- releasenotes/notes/bug-1519511-74bab0e0d32db043.yaml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/releasenotes/notes/bug-1519511-74bab0e0d32db043.yaml b/releasenotes/notes/bug-1519511-74bab0e0d32db043.yaml index 1a70c797e6..ca66b64a13 100644 --- a/releasenotes/notes/bug-1519511-74bab0e0d32db043.yaml +++ b/releasenotes/notes/bug-1519511-74bab0e0d32db043.yaml @@ -1,7 +1,10 @@ --- fixes: - - | - Ignore the ``security group list`` command ``--all-projects`` option + - Ignore the ``security group list`` command ``--all-projects`` option for Network v2 since security groups will be displayed for all projects by default (admin only). [Bug `1519511 `_] + - The ``security group set`` command now uses Network v2 when + enabled which allows the security group name and description + to be set to an empty value. + [Bug `1519511 `_] From 7ba73845c11b216bf3c634049380784ce767a2a4 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sun, 6 Mar 2016 09:01:42 +0800 Subject: [PATCH 0696/3095] Use _get_columns() to obtain columns in network.py Objects returned by Network v2 and Compute v2 are different. When getting columns to display, Network v2 uses obj.keys(), while Compute v2 uses obj._info.keys(). But both of them could obtain the keys of the object by _get_columns(). Change-Id: I347815f2d28822a95bd6f57d429b84b7ca96e0ee --- openstackclient/network/v2/network.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 308e0e5249..f9b19606ab 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -162,7 +162,7 @@ def take_action_network(self, client, parsed_args): def take_action_compute(self, client, parsed_args): attrs = _get_attrs_compute(self.app.client_manager, parsed_args) obj = client.networks.create(**attrs) - columns = tuple(sorted(obj._info.keys())) + columns = _get_columns(obj._info) data = utils.get_dict_properties(obj._info, columns) return (columns, data) @@ -359,10 +359,10 @@ def take_action_network(self, client, parsed_args): return (columns, data) def take_action_compute(self, client, parsed_args): - network = utils.find_resource( + obj = utils.find_resource( client.networks, parsed_args.network, ) - columns = sorted(network._info.keys()) - data = utils.get_dict_properties(network._info, columns) + columns = _get_columns(obj._info) + data = utils.get_dict_properties(obj._info, columns) return (columns, data) From fc24f37ae28e1b7f6b9587a8062a314d660a0136 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Mon, 7 Mar 2016 17:54:06 +0800 Subject: [PATCH 0697/3095] Trivial: Remove useless return If a function returns nothing, do not add return in the end. Change-Id: I298b8717462f68d3076a1619d674775be2a94c42 --- openstackclient/common/clientmanager.py | 2 -- openstackclient/identity/v2_0/endpoint.py | 1 - openstackclient/identity/v2_0/project.py | 3 --- openstackclient/identity/v2_0/role.py | 1 - openstackclient/identity/v2_0/service.py | 1 - openstackclient/identity/v2_0/token.py | 1 - openstackclient/identity/v2_0/user.py | 2 -- openstackclient/network/v2/network.py | 1 - openstackclient/network/v2/security_group.py | 1 - 9 files changed, 13 deletions(-) diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index 938dd05cee..56ddcbad77 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -199,8 +199,6 @@ def setup_auth(self, required_scope=True): self._auth_setup_completed = True - return - @property def auth_ref(self): """Dereference will trigger an auth if it hasn't already""" diff --git a/openstackclient/identity/v2_0/endpoint.py b/openstackclient/identity/v2_0/endpoint.py index 08c09565dd..e92f54127a 100644 --- a/openstackclient/identity/v2_0/endpoint.py +++ b/openstackclient/identity/v2_0/endpoint.py @@ -87,7 +87,6 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity identity_client.endpoints.delete(parsed_args.endpoint) - return class ListEndpoint(command.Lister): diff --git a/openstackclient/identity/v2_0/project.py b/openstackclient/identity/v2_0/project.py index 9e26c308b4..71c77623ea 100644 --- a/openstackclient/identity/v2_0/project.py +++ b/openstackclient/identity/v2_0/project.py @@ -119,7 +119,6 @@ def take_action(self, parsed_args): project, ) identity_client.tenants.delete(project_obj.id) - return class ListProject(command.Lister): @@ -222,7 +221,6 @@ def take_action(self, parsed_args): del kwargs['name'] identity_client.tenants.update(project.id, **kwargs) - return class ShowProject(command.ShowOne): @@ -317,4 +315,3 @@ def take_action(self, parsed_args): if key in kwargs: kwargs[key] = None identity_client.tenants.update(project.id, **kwargs) - return diff --git a/openstackclient/identity/v2_0/role.py b/openstackclient/identity/v2_0/role.py index 892ce006e5..01c340896b 100644 --- a/openstackclient/identity/v2_0/role.py +++ b/openstackclient/identity/v2_0/role.py @@ -126,7 +126,6 @@ def take_action(self, parsed_args): role, ) identity_client.roles.delete(role_obj.id) - return class ListRole(command.Lister): diff --git a/openstackclient/identity/v2_0/service.py b/openstackclient/identity/v2_0/service.py index 3af85d7db0..0b1e8dbdae 100644 --- a/openstackclient/identity/v2_0/service.py +++ b/openstackclient/identity/v2_0/service.py @@ -101,7 +101,6 @@ def take_action(self, parsed_args): identity_client = self.app.client_manager.identity service = common.find_service(identity_client, parsed_args.service) identity_client.services.delete(service.id) - return class ListService(command.Lister): diff --git a/openstackclient/identity/v2_0/token.py b/openstackclient/identity/v2_0/token.py index 6a66a1c6d7..1ccf2f261d 100644 --- a/openstackclient/identity/v2_0/token.py +++ b/openstackclient/identity/v2_0/token.py @@ -55,4 +55,3 @@ def take_action(self, parsed_args): identity_client = self.app.client_manager.identity identity_client.tokens.delete(parsed_args.token) - return diff --git a/openstackclient/identity/v2_0/user.py b/openstackclient/identity/v2_0/user.py index 3d848737e5..fc868bab70 100644 --- a/openstackclient/identity/v2_0/user.py +++ b/openstackclient/identity/v2_0/user.py @@ -143,7 +143,6 @@ def take_action(self, parsed_args): user, ) identity_client.users.delete(user_obj.id) - return class ListUser(command.Lister): @@ -330,7 +329,6 @@ def take_action(self, parsed_args): kwargs['enabled'] = False identity_client.users.update(user.id, **kwargs) - return class ShowUser(command.ShowOne): diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 308e0e5249..678656426f 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -338,7 +338,6 @@ def take_action(self, parsed_args): raise exceptions.CommandError(msg) client.update_network(obj, **attrs) - return class ShowNetwork(common.NetworkAndComputeShowOne): diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py index 9cefb42066..62699ffd3d 100644 --- a/openstackclient/network/v2/security_group.py +++ b/openstackclient/network/v2/security_group.py @@ -123,7 +123,6 @@ def take_action_network(self, client, parsed_args): # if there were no updates. Maintain this behavior and issue # the update. client.update_security_group(obj, **attrs) - return def take_action_compute(self, client, parsed_args): data = utils.find_resource( From d5489426458e9b4b96772983797263d8807571cb Mon Sep 17 00:00:00 2001 From: Jas Date: Thu, 4 Feb 2016 11:33:13 -0600 Subject: [PATCH 0698/3095] Add port list command This patch adds the ability to list all created ports Change-Id: Ie1a48c203cabc96346a4950f21b83493d58a66a5 Partial-bug: #1519909 Partially-implements: blueprint neutron-client --- doc/source/command-objects/port.rst | 10 +++++ openstackclient/network/v2/port.py | 27 ++++++++++++ openstackclient/tests/network/v2/test_port.py | 41 +++++++++++++++++++ ...dd-port-list-command-29b4452003bc6bab.yaml | 5 +++ setup.cfg | 7 ++++ 5 files changed, 90 insertions(+) create mode 100644 releasenotes/notes/add-port-list-command-29b4452003bc6bab.yaml diff --git a/doc/source/command-objects/port.rst b/doc/source/command-objects/port.rst index 0c91f3ac00..3083b07518 100644 --- a/doc/source/command-objects/port.rst +++ b/doc/source/command-objects/port.rst @@ -98,6 +98,16 @@ Delete port(s) Port(s) to delete (name or ID) +port list +--------- + +List ports + +.. program:: port list +.. code:: bash + + os port list + port show --------- diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index f9d0fc957f..449dcfd498 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -238,6 +238,33 @@ def take_action(self, parsed_args): client.delete_port(res) +class ListPort(command.Lister): + """List ports""" + + def take_action(self, parsed_args): + client = self.app.client_manager.network + + columns = ( + 'id', + 'name', + 'mac_address', + 'fixed_ips', + ) + column_headers = ( + 'ID', + 'Name', + 'MAC Address', + 'Fixed IP Addresses', + ) + + data = client.ports() + return (column_headers, + (utils.get_item_properties( + s, columns, + formatters=_formatters, + ) for s in data)) + + class ShowPort(command.ShowOne): """Display port details""" diff --git a/openstackclient/tests/network/v2/test_port.py b/openstackclient/tests/network/v2/test_port.py index 907d8a7d10..30e290c6ad 100644 --- a/openstackclient/tests/network/v2/test_port.py +++ b/openstackclient/tests/network/v2/test_port.py @@ -199,6 +199,47 @@ def test_delete(self): self.assertIsNone(result) +class TestListPort(TestPort): + + _ports = network_fakes.FakePort.create_ports(count=3) + + columns = ( + 'ID', + 'Name', + 'MAC Address', + 'Fixed IP Addresses', + ) + + data = [] + for prt in _ports: + data.append(( + prt.id, + prt.name, + prt.mac_address, + utils.format_list_of_dicts(prt.fixed_ips), + )) + + def setUp(self): + super(TestListPort, self).setUp() + + # Get the command object to test + self.cmd = port.ListPort(self.app, self.namespace) + + self.network.ports = mock.Mock(return_value=self._ports) + + def test_port_list_no_options(self): + arglist = [] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.ports.assert_called_with() + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + class TestShowPort(TestPort): # The port to show. diff --git a/releasenotes/notes/add-port-list-command-29b4452003bc6bab.yaml b/releasenotes/notes/add-port-list-command-29b4452003bc6bab.yaml new file mode 100644 index 0000000000..6c62539714 --- /dev/null +++ b/releasenotes/notes/add-port-list-command-29b4452003bc6bab.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add support for the ``port list`` command. + [Bug `1519909 `_] \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index b49eeba9eb..728b5d646b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -326,27 +326,34 @@ openstack.network.v2 = ip_floating_delete = openstackclient.network.v2.floating_ip:DeleteFloatingIP ip_floating_list = openstackclient.network.v2.floating_ip:ListFloatingIP ip_floating_show = openstackclient.network.v2.floating_ip:ShowFloatingIP + network_create = openstackclient.network.v2.network:CreateNetwork network_delete = openstackclient.network.v2.network:DeleteNetwork network_list = openstackclient.network.v2.network:ListNetwork network_set = openstackclient.network.v2.network:SetNetwork network_show = openstackclient.network.v2.network:ShowNetwork + port_create = openstackclient.network.v2.port:CreatePort port_delete = openstackclient.network.v2.port:DeletePort + port_list = openstackclient.network.v2.port:ListPort port_show = openstackclient.network.v2.port:ShowPort + router_create = openstackclient.network.v2.router:CreateRouter router_delete = openstackclient.network.v2.router:DeleteRouter router_list = openstackclient.network.v2.router:ListRouter router_set = openstackclient.network.v2.router:SetRouter router_show = openstackclient.network.v2.router:ShowRouter + security_group_delete = openstackclient.network.v2.security_group:DeleteSecurityGroup security_group_list = openstackclient.network.v2.security_group:ListSecurityGroup security_group_set = openstackclient.network.v2.security_group:SetSecurityGroup security_group_rule_delete = openstackclient.network.v2.security_group_rule:DeleteSecurityGroupRule security_group_rule_show = openstackclient.network.v2.security_group_rule:ShowSecurityGroupRule + subnet_delete = openstackclient.network.v2.subnet:DeleteSubnet subnet_list = openstackclient.network.v2.subnet:ListSubnet subnet_show = openstackclient.network.v2.subnet:ShowSubnet + subnet_pool_delete = openstackclient.network.v2.subnet_pool:DeleteSubnetPool subnet_pool_list = openstackclient.network.v2.subnet_pool:ListSubnetPool subnet_pool_show = openstackclient.network.v2.subnet_pool:ShowSubnetPool From 762c4c9bdf66995198fa03751b861a859b9d44a1 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 5 Mar 2016 16:29:18 +0800 Subject: [PATCH 0699/3095] [Identity] Check return value is None in identity v3 unit tests take_action() in commands inheriting from Command returns nothing. So we should assert the return is None in the unit tests of these commands. Change-Id: I02af06b3d476aac2d93a23ef2111cdc7fa0892ec Partial-Bug: #1550636 --- .../tests/identity/v3/test_credential.py | 12 ++++--- .../identity/v3/test_identity_provider.py | 5 ++- .../tests/identity/v3/test_mappings.py | 7 ++-- .../tests/identity/v3/test_protocol.py | 5 ++- .../tests/identity/v3/test_role.py | 18 ++++++---- .../identity/v3/test_service_provider.py | 5 ++- .../tests/identity/v3/test_token.py | 3 +- .../tests/identity/v3/test_trust.py | 3 +- .../tests/identity/v3/test_user.py | 36 ++++++++++++------- 9 files changed, 64 insertions(+), 30 deletions(-) diff --git a/openstackclient/tests/identity/v3/test_credential.py b/openstackclient/tests/identity/v3/test_credential.py index afff7b6c09..d8866124f9 100644 --- a/openstackclient/tests/identity/v3/test_credential.py +++ b/openstackclient/tests/identity/v3/test_credential.py @@ -96,9 +96,11 @@ def test_credential_set_valid(self): '--data', self.json_data, identity_fakes.credential_id, ] - parsed_args = self.check_parser(self.cmd, arglist, []) - self.cmd.take_action(parsed_args) + + result = self.cmd.take_action(parsed_args) + + self.assertIsNone(result) def test_credential_set_valid_with_project(self): arglist = [ @@ -108,6 +110,8 @@ def test_credential_set_valid_with_project(self): '--project', identity_fakes.project_name, identity_fakes.credential_id, ] - parsed_args = self.check_parser(self.cmd, arglist, []) - self.cmd.take_action(parsed_args) + + result = self.cmd.take_action(parsed_args) + + self.assertIsNone(result) diff --git a/openstackclient/tests/identity/v3/test_identity_provider.py b/openstackclient/tests/identity/v3/test_identity_provider.py index ddad6ffb88..1ee7aade27 100644 --- a/openstackclient/tests/identity/v3/test_identity_provider.py +++ b/openstackclient/tests/identity/v3/test_identity_provider.py @@ -258,10 +258,13 @@ def test_delete_identity_provider(self): ('identity_provider', identity_fakes.idp_id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + + result = self.cmd.take_action(parsed_args) + self.identity_providers_mock.delete.assert_called_with( identity_fakes.idp_id, ) + self.assertIsNone(result) class TestIdentityProviderList(TestIdentityProvider): diff --git a/openstackclient/tests/identity/v3/test_mappings.py b/openstackclient/tests/identity/v3/test_mappings.py index e811c0b644..106a419c49 100644 --- a/openstackclient/tests/identity/v3/test_mappings.py +++ b/openstackclient/tests/identity/v3/test_mappings.py @@ -92,11 +92,13 @@ def test_delete_mapping(self): verifylist = [ ('mapping', identity_fakes.mapping_id) ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + + result = self.cmd.take_action(parsed_args) + self.mapping_mock.delete.assert_called_with( identity_fakes.mapping_id) + self.assertIsNone(result) class TestMappingList(TestMapping): @@ -234,7 +236,6 @@ def test_set_rules_wrong_file_path(self): ('mapping', identity_fakes.mapping_id), ('rules', identity_fakes.mapping_rules_file_path) ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.assertRaises( diff --git a/openstackclient/tests/identity/v3/test_protocol.py b/openstackclient/tests/identity/v3/test_protocol.py index 3c9c3f0a7d..238b0ff8f1 100644 --- a/openstackclient/tests/identity/v3/test_protocol.py +++ b/openstackclient/tests/identity/v3/test_protocol.py @@ -92,9 +92,12 @@ def test_delete_identity_provider(self): ('identity_provider', identity_fakes.idp_id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + + result = self.cmd.take_action(parsed_args) + self.protocols_mock.delete.assert_called_with( identity_fakes.idp_id, identity_fakes.protocol_id) + self.assertIsNone(result) class TestProtocolList(TestProtocol): diff --git a/openstackclient/tests/identity/v3/test_role.py b/openstackclient/tests/identity/v3/test_role.py index 19410debe4..d2398e5d91 100644 --- a/openstackclient/tests/identity/v3/test_role.py +++ b/openstackclient/tests/identity/v3/test_role.py @@ -306,11 +306,12 @@ def test_role_delete_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) self.roles_mock.delete.assert_called_with( identity_fakes.role_id, ) + self.assertIsNone(result) class TestRoleList(TestRole): @@ -640,7 +641,7 @@ def test_role_remove_user_domain(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -653,6 +654,7 @@ def test_role_remove_user_domain(self): identity_fakes.role_id, **kwargs ) + self.assertIsNone(result) def test_role_remove_user_project(self): arglist = [ @@ -672,7 +674,7 @@ def test_role_remove_user_project(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -685,6 +687,7 @@ def test_role_remove_user_project(self): identity_fakes.role_id, **kwargs ) + self.assertIsNone(result) def test_role_remove_group_domain(self): arglist = [ @@ -705,7 +708,7 @@ def test_role_remove_group_domain(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -718,6 +721,7 @@ def test_role_remove_group_domain(self): identity_fakes.role_id, **kwargs ) + self.assertIsNone(result) def test_role_remove_group_project(self): arglist = [ @@ -737,7 +741,7 @@ def test_role_remove_group_project(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -750,6 +754,7 @@ def test_role_remove_group_project(self): identity_fakes.role_id, **kwargs ) + self.assertIsNone(result) class TestRoleSet(TestRole): @@ -778,7 +783,7 @@ def test_role_set_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -789,6 +794,7 @@ def test_role_set_no_options(self): identity_fakes.role_id, **kwargs ) + self.assertIsNone(result) class TestRoleShow(TestRole): diff --git a/openstackclient/tests/identity/v3/test_service_provider.py b/openstackclient/tests/identity/v3/test_service_provider.py index 39f779d0f7..b1285b1080 100644 --- a/openstackclient/tests/identity/v3/test_service_provider.py +++ b/openstackclient/tests/identity/v3/test_service_provider.py @@ -188,10 +188,13 @@ def test_delete_service_provider(self): ('service_provider', service_fakes.sp_id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + + result = self.cmd.take_action(parsed_args) + self.service_providers_mock.delete.assert_called_with( service_fakes.sp_id, ) + self.assertIsNone(result) class TestServiceProviderList(TestServiceProvider): diff --git a/openstackclient/tests/identity/v3/test_token.py b/openstackclient/tests/identity/v3/test_token.py index 80c397bccf..b68bc242ec 100644 --- a/openstackclient/tests/identity/v3/test_token.py +++ b/openstackclient/tests/identity/v3/test_token.py @@ -123,6 +123,7 @@ def test_token_revoke(self): verifylist = [('token', self.TOKEN)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) self.tokens_mock.revoke_token.assert_called_with(self.TOKEN) + self.assertIsNone(result) diff --git a/openstackclient/tests/identity/v3/test_trust.py b/openstackclient/tests/identity/v3/test_trust.py index 2a74887228..1ea2feb444 100644 --- a/openstackclient/tests/identity/v3/test_trust.py +++ b/openstackclient/tests/identity/v3/test_trust.py @@ -141,11 +141,12 @@ def test_trust_delete(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) self.trusts_mock.delete.assert_called_with( identity_fakes.trust_id, ) + self.assertIsNone(result) class TestTrustList(TestTrust): diff --git a/openstackclient/tests/identity/v3/test_user.py b/openstackclient/tests/identity/v3/test_user.py index 571e2d798e..5dafa772ff 100644 --- a/openstackclient/tests/identity/v3/test_user.py +++ b/openstackclient/tests/identity/v3/test_user.py @@ -499,11 +499,12 @@ def test_user_delete_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) self.users_mock.delete.assert_called_with( identity_fakes.user_id, ) + self.assertIsNone(result) class TestUserList(TestUser): @@ -774,7 +775,7 @@ def test_user_set_name(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -787,6 +788,7 @@ def test_user_set_name(self): identity_fakes.user_id, **kwargs ) + self.assertIsNone(result) def test_user_set_password(self): arglist = [ @@ -805,7 +807,7 @@ def test_user_set_password(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -818,6 +820,7 @@ def test_user_set_password(self): identity_fakes.user_id, **kwargs ) + self.assertIsNone(result) def test_user_set_password_prompt(self): arglist = [ @@ -839,7 +842,7 @@ def test_user_set_password_prompt(self): mocker = mock.Mock() mocker.return_value = 'abc123' with mock.patch("openstackclient.common.utils.get_password", mocker): - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -852,6 +855,7 @@ def test_user_set_password_prompt(self): identity_fakes.user_id, **kwargs ) + self.assertIsNone(result) def test_user_set_email(self): arglist = [ @@ -869,7 +873,7 @@ def test_user_set_email(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -882,6 +886,7 @@ def test_user_set_email(self): identity_fakes.user_id, **kwargs ) + self.assertIsNone(result) def test_user_set_project(self): arglist = [ @@ -899,7 +904,7 @@ def test_user_set_project(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -912,6 +917,7 @@ def test_user_set_project(self): identity_fakes.user_id, **kwargs ) + self.assertIsNone(result) def test_user_set_project_domain(self): arglist = [ @@ -931,7 +937,7 @@ def test_user_set_project_domain(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -944,6 +950,7 @@ def test_user_set_project_domain(self): identity_fakes.user_id, **kwargs ) + self.assertIsNone(result) def test_user_set_enable(self): arglist = [ @@ -961,7 +968,7 @@ def test_user_set_enable(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -973,6 +980,7 @@ def test_user_set_enable(self): identity_fakes.user_id, **kwargs ) + self.assertIsNone(result) def test_user_set_disable(self): arglist = [ @@ -990,7 +998,7 @@ def test_user_set_disable(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -1002,6 +1010,7 @@ def test_user_set_disable(self): identity_fakes.user_id, **kwargs ) + self.assertIsNone(result) class TestUserSetPassword(TestUser): @@ -1030,11 +1039,12 @@ def test_user_password_change(self): # Mock getting user current password. with self._mock_get_password(current_pass): - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) self.users_mock.update_password.assert_called_with( current_pass, new_pass ) + self.assertIsNone(result) def test_user_create_password_prompt(self): current_pass = 'old_pass' @@ -1043,11 +1053,12 @@ def test_user_create_password_prompt(self): # Mock getting user current and new password. with self._mock_get_password(current_pass, new_pass): - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) self.users_mock.update_password.assert_called_with( current_pass, new_pass ) + self.assertIsNone(result) def test_user_password_change_no_prompt(self): current_pass = 'old_pass' @@ -1062,11 +1073,12 @@ def test_user_password_change_no_prompt(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) self.users_mock.update_password.assert_called_with( current_pass, new_pass ) + self.assertIsNone(result) class TestUserShow(TestUser): From 3ed5d232bbd6dcf4e9e23e8c7f3567ce2999315a Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Tue, 8 Mar 2016 07:29:03 -0500 Subject: [PATCH 0700/3095] remove py26 workaround in osc we don't support py2.6, so let's remove the workarounds we have. Change-Id: Id9c8fda065d4aceba3192b044b5c5f2124ee204f --- openstackclient/tests/utils.py | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/openstackclient/tests/utils.py b/openstackclient/tests/utils.py index d3f3853f5a..319c1c1142 100644 --- a/openstackclient/tests/utils.py +++ b/openstackclient/tests/utils.py @@ -17,7 +17,6 @@ import os import fixtures -import sys import testtools from openstackclient.tests import fakes @@ -50,29 +49,6 @@ def assertNotCalled(self, m, msg=None): msg = 'method %s should not have been called' % m self.fail(msg) - # 2.6 doesn't have the assert dict equals so make sure that it exists - if tuple(sys.version_info)[0:2] < (2, 7): - - def assertIsInstance(self, obj, cls, msg=None): - """self.assertTrue(isinstance(obj, cls)), with a nicer message""" - - if not isinstance(obj, cls): - standardMsg = '%s is not an instance of %r' % (obj, cls) - self.fail(self._formatMessage(msg, standardMsg)) - - def assertDictEqual(self, d1, d2, msg=None): - # Simple version taken from 2.7 - self.assertIsInstance(d1, dict, - 'First argument is not a dictionary') - self.assertIsInstance(d2, dict, - 'Second argument is not a dictionary') - if d1 != d2: - if msg: - self.fail(msg) - else: - standardMsg = '%r != %r' % (d1, d2) - self.fail(standardMsg) - class TestCommand(TestCase): """Test cliff command classes""" From 11c253ca1925fa8cf7fac1453cd957f9ba0c44fd Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 8 Mar 2016 20:48:58 +0800 Subject: [PATCH 0701/3095] Trivial: Fix incorrect comments in compute fakes.py Change-Id: I18b1720af13b444527dda1ecab52e3cc8d8d9376 --- openstackclient/tests/compute/v2/fakes.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 32d257f13f..f735fe42d8 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -636,14 +636,14 @@ def create_one_keypair(attrs=None, no_pri=False): @staticmethod def create_keypairs(attrs=None, count=2): - """Create multiple fake flavors. + """Create multiple fake keypairs. :param Dictionary attrs: A dictionary with all attributes :param int count: - The number of flavors to fake + The number of keypairs to fake :return: - A list of FakeFlavorResource objects faking the flavors + A list of FakeResource objects faking the keypairs """ keypairs = [] From f2fb007e820ffd8585d8c63af4aab27944d544eb Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Mon, 7 Mar 2016 11:51:39 +0800 Subject: [PATCH 0702/3095] [Subnet pool] Add 'subnet pool set' command support This patch supports setting a new name, pool prefix, default prefix length, minimum prefix length, and maximum prefix length for a subnet pool. Change-Id: I65bd71e0f54f2f65acefbc542df67a1b1ec26397 Partial-Bug: #1544591 Related-to: blueprint neutron-client --- doc/source/command-objects/subnet-pool.rst | 42 ++++++++ openstackclient/network/v2/subnet_pool.py | 91 +++++++++++++++++- .../tests/network/v2/test_subnet_pool.py | 95 ++++++++++++++++++- .../notes/bug-1544591-267fd16cc5efffe6.yaml | 4 + setup.cfg | 1 + 5 files changed, 227 insertions(+), 6 deletions(-) create mode 100644 releasenotes/notes/bug-1544591-267fd16cc5efffe6.yaml diff --git a/doc/source/command-objects/subnet-pool.rst b/doc/source/command-objects/subnet-pool.rst index e181caec94..658616d5bc 100644 --- a/doc/source/command-objects/subnet-pool.rst +++ b/doc/source/command-objects/subnet-pool.rst @@ -35,6 +35,48 @@ List subnet pools List additional fields in output +subnet pool set +--------------- + +Set subnet pool properties + +.. program:: subnet pool set + .. code:: bash + + os subnet pool set + [--name ] + [--pool-prefix [...]] + [--default-prefix-length ] + [--min-prefix-length ] + [--max-prefix-length ] + + +.. option:: --name + + Set subnet pool name + +.. option:: --pool-prefix + + Set subnet pool prefixes (in CIDR notation). + Repeat this option to set multiple prefixes. + +.. option:: --default-prefix-length + + Set subnet pool default prefix length + +.. option:: --min-prefix-length + + Set subnet pool minimum prefix length + +.. option:: --max-prefix-length + + Set subnet pool maximum prefix length + +.. _subnet_pool_set-subnet-pool: + .. describe:: + + Subnet pool to modify (name or ID) + subnet pool show ---------------- diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index 5bb45c120a..19cd46c986 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -14,6 +14,8 @@ """Subnet pool action implementations""" from openstackclient.common import command +from openstackclient.common import exceptions +from openstackclient.common import parseractions from openstackclient.common import utils @@ -30,6 +32,51 @@ def _get_columns(item): } +def _get_attrs(parsed_args): + attrs = {} + if parsed_args.name is not None: + attrs['name'] = str(parsed_args.name) + if parsed_args.prefixes is not None: + attrs['prefixes'] = parsed_args.prefixes + if parsed_args.default_prefix_length is not None: + attrs['default_prefix_length'] = parsed_args.default_prefix_length + if parsed_args.min_prefix_length is not None: + attrs['min_prefix_length'] = parsed_args.min_prefix_length + if parsed_args.max_prefix_length is not None: + attrs['max_prefix_length'] = parsed_args.max_prefix_length + + return attrs + + +def _add_prefix_options(parser): + parser.add_argument( + '--pool-prefix', + metavar='', + dest='prefixes', + action='append', + help='Set subnet pool prefixes (in CIDR notation). ' + 'Repeat this option to set multiple prefixes.', + ) + parser.add_argument( + '--default-prefix-length', + metavar='', + action=parseractions.NonNegativeAction, + help='Set subnet pool default prefix length', + ) + parser.add_argument( + '--min-prefix-length', + metavar='', + action=parseractions.NonNegativeAction, + help='Set subnet pool minimum prefix length', + ) + parser.add_argument( + '--max-prefix-length', + metavar='', + action=parseractions.NonNegativeAction, + help='Set subnet pool maximum prefix length', + ) + + class DeleteSubnetPool(command.Command): """Delete subnet pool""" @@ -37,8 +84,8 @@ def get_parser(self, prog_name): parser = super(DeleteSubnetPool, self).get_parser(prog_name) parser.add_argument( 'subnet_pool', - metavar="", - help=("Subnet pool to delete (name or ID)") + metavar='', + help='Subnet pool to delete (name or ID)' ) return parser @@ -98,6 +145,42 @@ def take_action(self, parsed_args): ) for s in data)) +class SetSubnetPool(command.Command): + """Set subnet pool properties""" + + def get_parser(self, prog_name): + parser = super(SetSubnetPool, self).get_parser(prog_name) + parser.add_argument( + 'subnet_pool', + metavar='', + help='Subnet pool to modify (name or ID)' + ) + parser.add_argument( + '--name', + metavar='', + help='Set subnet pool name', + ) + _add_prefix_options(parser) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.find_subnet_pool(parsed_args.subnet_pool, + ignore_missing=False) + + attrs = _get_attrs(parsed_args) + if attrs == {}: + msg = "Nothing specified to be set" + raise exceptions.CommandError(msg) + + # Existing prefixes must be a subset of the new prefixes. + if 'prefixes' in attrs: + attrs['prefixes'].extend(obj.prefixes) + + client.update_subnet_pool(obj, **attrs) + + class ShowSubnetPool(command.ShowOne): """Display subnet pool details""" @@ -105,8 +188,8 @@ def get_parser(self, prog_name): parser = super(ShowSubnetPool, self).get_parser(prog_name) parser.add_argument( 'subnet_pool', - metavar="", - help=("Subnet pool to display (name or ID)") + metavar='', + help='Subnet pool to display (name or ID)' ) return parser diff --git a/openstackclient/tests/network/v2/test_subnet_pool.py b/openstackclient/tests/network/v2/test_subnet_pool.py index c4e3340de7..9c372ac0fd 100644 --- a/openstackclient/tests/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/network/v2/test_subnet_pool.py @@ -11,8 +11,10 @@ # under the License. # +import argparse import mock +from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.network.v2 import subnet_pool from openstackclient.tests.network.v2 import fakes as network_fakes @@ -129,6 +131,96 @@ def test_subnet_pool_list_long(self): self.assertEqual(self.data_long, list(data)) +class TestSetSubnetPool(TestSubnetPool): + + # The subnet_pool to set. + _subnet_pool = network_fakes.FakeSubnetPool.create_one_subnet_pool() + + def setUp(self): + super(TestSetSubnetPool, self).setUp() + + self.network.update_subnet_pool = mock.Mock(return_value=None) + + self.network.find_subnet_pool = mock.Mock( + return_value=self._subnet_pool) + + # Get the command object to test + self.cmd = subnet_pool.SetSubnetPool(self.app, self.namespace) + + def test_set_this(self): + arglist = [ + self._subnet_pool.name, + '--name', 'noob', + '--default-prefix-length', '8', + '--min-prefix-length', '8', + ] + verifylist = [ + ('subnet_pool', self._subnet_pool.name), + ('name', 'noob'), + ('default_prefix_length', '8'), + ('min_prefix_length', '8'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + attrs = { + 'name': 'noob', + 'default_prefix_length': '8', + 'min_prefix_length': '8', + } + self.network.update_subnet_pool.assert_called_with( + self._subnet_pool, **attrs) + self.assertIsNone(result) + + def test_set_that(self): + arglist = [ + self._subnet_pool.name, + '--pool-prefix', '10.0.1.0/24', + '--pool-prefix', '10.0.2.0/24', + '--max-prefix-length', '16', + ] + verifylist = [ + ('subnet_pool', self._subnet_pool.name), + ('prefixes', ['10.0.1.0/24', '10.0.2.0/24']), + ('max_prefix_length', '16'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + prefixes = ['10.0.1.0/24', '10.0.2.0/24'] + prefixes.extend(self._subnet_pool.prefixes) + attrs = { + 'prefixes': prefixes, + 'max_prefix_length': '16', + } + self.network.update_subnet_pool.assert_called_with( + self._subnet_pool, **attrs) + self.assertIsNone(result) + + def test_set_nothing(self): + arglist = [self._subnet_pool.name, ] + verifylist = [('subnet_pool', self._subnet_pool.name), ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + + def test_set_len_negative(self): + arglist = [ + self._subnet_pool.name, + '--max-prefix-length', '-16', + ] + verifylist = [ + ('subnet_pool', self._subnet_pool.name), + ('max_prefix_length', '-16'), + ] + + self.assertRaises(argparse.ArgumentTypeError, self.check_parser, + self.cmd, arglist, verifylist) + + class TestShowSubnetPool(TestSubnetPool): # The subnet_pool to set. @@ -189,14 +281,13 @@ def test_show_all_options(self): verifylist = [ ('subnet_pool', self._subnet_pool.name), ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) self.network.find_subnet_pool.assert_called_with( self._subnet_pool.name, ignore_missing=False ) - self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) diff --git a/releasenotes/notes/bug-1544591-267fd16cc5efffe6.yaml b/releasenotes/notes/bug-1544591-267fd16cc5efffe6.yaml new file mode 100644 index 0000000000..130af91130 --- /dev/null +++ b/releasenotes/notes/bug-1544591-267fd16cc5efffe6.yaml @@ -0,0 +1,4 @@ +--- +features: + - Add ``subnet pool set`` command. + [Bug `1544591 `_] diff --git a/setup.cfg b/setup.cfg index 728b5d646b..a2e04ce676 100644 --- a/setup.cfg +++ b/setup.cfg @@ -356,6 +356,7 @@ openstack.network.v2 = subnet_pool_delete = openstackclient.network.v2.subnet_pool:DeleteSubnetPool subnet_pool_list = openstackclient.network.v2.subnet_pool:ListSubnetPool + subnet_pool_set = openstackclient.network.v2.subnet_pool:SetSubnetPool subnet_pool_show = openstackclient.network.v2.subnet_pool:ShowSubnetPool openstack.object_store.v1 = From 3d741d3757b1c41201fe72f604ab8c35547c8cc0 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Mon, 7 Mar 2016 20:48:03 +0800 Subject: [PATCH 0703/3095] [Subnet pool] Add 'subnet pool create' command support This patch supports creating a new subnet pool, with pool prefixes, default prefix length, minimum prefix length, and maximum prefix length specified. Change-Id: I9150797c8cfa794d5264ad07965aa967d9a8f5bc Partial-Bug: #1544586 Related-to: blueprint neutron-client --- doc/source/command-objects/subnet-pool.rst | 37 ++++++ openstackclient/network/v2/subnet_pool.py | 23 ++++ openstackclient/tests/network/v2/fakes.py | 8 +- .../tests/network/v2/test_subnet_pool.py | 111 ++++++++++++++++++ setup.cfg | 1 + 5 files changed, 176 insertions(+), 4 deletions(-) diff --git a/doc/source/command-objects/subnet-pool.rst b/doc/source/command-objects/subnet-pool.rst index 658616d5bc..722f92997d 100644 --- a/doc/source/command-objects/subnet-pool.rst +++ b/doc/source/command-objects/subnet-pool.rst @@ -4,6 +4,43 @@ subnet pool Network v2 +subnet pool create +------------------ + +Create subnet pool + +.. program:: subnet pool create + .. code:: bash + + os subnet pool create + [--pool-prefix [...]] + [--default-prefix-length ] + [--min-prefix-length ] + [--max-prefix-length ] + + +.. option:: --pool-prefix + + Set subnet pool prefixes (in CIDR notation). + Repeat this option to set multiple prefixes. + +.. option:: --default-prefix-length + + Set subnet pool default prefix length + +.. option:: --min-prefix-length + + Set subnet pool minimum prefix length + +.. option:: --max-prefix-length + + Set subnet pool maximum prefix length + +.. _subnet_pool_create-name: + .. describe:: + + Name of the new subnet pool + subnet pool delete ------------------ diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index 19cd46c986..44f30207a1 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -77,6 +77,29 @@ def _add_prefix_options(parser): ) +class CreateSubnetPool(command.ShowOne): + """Create subnet pool""" + + def get_parser(self, prog_name): + parser = super(CreateSubnetPool, self).get_parser(prog_name) + parser.add_argument( + 'name', + metavar="", + help='Name of the new subnet pool' + ) + _add_prefix_options(parser) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + attrs = _get_attrs(parsed_args) + obj = client.create_subnet_pool(**attrs) + columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns, formatters=_formatters) + return (columns, data) + + class DeleteSubnetPool(command.Command): """Delete subnet pool""" diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 9e6bf97f65..26213b1f12 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -734,15 +734,15 @@ def create_one_subnet_pool(attrs={}, methods={}): 'id': 'subnet-pool-id-' + uuid.uuid4().hex, 'name': 'subnet-pool-name-' + uuid.uuid4().hex, 'prefixes': ['10.0.0.0/24', '10.1.0.0/24'], - 'default_prefixlen': 8, + 'default_prefixlen': '8', 'address_scope_id': 'address-scope-id-' + uuid.uuid4().hex, 'tenant_id': 'project-id-' + uuid.uuid4().hex, 'is_default': False, 'shared': False, - 'max_prefixlen': 32, - 'min_prefixlen': 8, + 'max_prefixlen': '32', + 'min_prefixlen': '8', 'default_quota': None, - 'ip_version': 4, + 'ip_version': '4', } # Overwrite default attributes. diff --git a/openstackclient/tests/network/v2/test_subnet_pool.py b/openstackclient/tests/network/v2/test_subnet_pool.py index 9c372ac0fd..99994681d5 100644 --- a/openstackclient/tests/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/network/v2/test_subnet_pool.py @@ -30,6 +30,117 @@ def setUp(self): self.network = self.app.client_manager.network +class TestCreateSubnetPool(TestSubnetPool): + + # The new subnet pool to create. + _subnet_pool = network_fakes.FakeSubnetPool.create_one_subnet_pool() + + columns = ( + 'address_scope_id', + 'default_prefixlen', + 'default_quota', + 'id', + 'ip_version', + 'is_default', + 'max_prefixlen', + 'min_prefixlen', + 'name', + 'prefixes', + 'project_id', + 'shared', + ) + data = ( + _subnet_pool.address_scope_id, + _subnet_pool.default_prefixlen, + _subnet_pool.default_quota, + _subnet_pool.id, + _subnet_pool.ip_version, + _subnet_pool.is_default, + _subnet_pool.max_prefixlen, + _subnet_pool.min_prefixlen, + _subnet_pool.name, + utils.format_list(_subnet_pool.prefixes), + _subnet_pool.project_id, + _subnet_pool.shared, + ) + + def setUp(self): + super(TestCreateSubnetPool, self).setUp() + + self.network.create_subnet_pool = mock.Mock( + return_value=self._subnet_pool) + + # Get the command object to test + self.cmd = subnet_pool.CreateSubnetPool(self.app, self.namespace) + + def test_create_no_options(self): + arglist = [] + verifylist = [] + + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_create_default_options(self): + arglist = [ + '--pool-prefix', '10.0.10.0/24', + self._subnet_pool.name, + ] + verifylist = [ + ('prefixes', ['10.0.10.0/24']), + ('name', self._subnet_pool.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_subnet_pool.assert_called_with(**{ + 'prefixes': ['10.0.10.0/24'], + 'name': self._subnet_pool.name, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_prefixlen_options(self): + arglist = [ + '--default-prefix-length', self._subnet_pool.default_prefixlen, + '--max-prefix-length', self._subnet_pool.max_prefixlen, + '--min-prefix-length', self._subnet_pool.min_prefixlen, + self._subnet_pool.name, + ] + verifylist = [ + ('default_prefix_length', self._subnet_pool.default_prefixlen), + ('max_prefix_length', self._subnet_pool.max_prefixlen), + ('min_prefix_length', self._subnet_pool.min_prefixlen), + ('name', self._subnet_pool.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_subnet_pool.assert_called_with(**{ + 'default_prefix_length': self._subnet_pool.default_prefixlen, + 'max_prefix_length': self._subnet_pool.max_prefixlen, + 'min_prefix_length': self._subnet_pool.min_prefixlen, + 'name': self._subnet_pool.name, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_len_negative(self): + arglist = [ + self._subnet_pool.name, + '--min-prefix-length', '-16', + ] + verifylist = [ + ('subnet_pool', self._subnet_pool.name), + ('min_prefix_length', '-16'), + ] + + self.assertRaises(argparse.ArgumentTypeError, self.check_parser, + self.cmd, arglist, verifylist) + + class TestDeleteSubnetPool(TestSubnetPool): # The subnet pool to delete. diff --git a/setup.cfg b/setup.cfg index a2e04ce676..21658af994 100644 --- a/setup.cfg +++ b/setup.cfg @@ -354,6 +354,7 @@ openstack.network.v2 = subnet_list = openstackclient.network.v2.subnet:ListSubnet subnet_show = openstackclient.network.v2.subnet:ShowSubnet + subnet_pool_create = openstackclient.network.v2.subnet_pool:CreateSubnetPool subnet_pool_delete = openstackclient.network.v2.subnet_pool:DeleteSubnetPool subnet_pool_list = openstackclient.network.v2.subnet_pool:ListSubnetPool subnet_pool_set = openstackclient.network.v2.subnet_pool:SetSubnetPool From e0d58641bba8cacab855ed5aa3b7506737470441 Mon Sep 17 00:00:00 2001 From: Jas Date: Mon, 22 Feb 2016 14:05:17 -0600 Subject: [PATCH 0704/3095] Add 'port set' command Add CLI support for the 'port set' command Change-Id: I2bea508e11290284aa64b1ab548a0bb61e7290d3 Partial-bug: #1519909 Partially-implements: blueprint neutron-client --- doc/source/command-objects/port.rst | 62 ++++++++++++++- openstackclient/network/v2/port.py | 52 +++++++++++- openstackclient/tests/network/v2/test_port.py | 79 +++++++++++++++++++ ...add-port-set-command-2b4fe38ec71ad48f.yaml | 5 ++ setup.cfg | 1 + 5 files changed, 194 insertions(+), 5 deletions(-) create mode 100644 releasenotes/notes/add-port-set-command-2b4fe38ec71ad48f.yaml diff --git a/doc/source/command-objects/port.rst b/doc/source/command-objects/port.rst index 3083b07518..e9c091736a 100644 --- a/doc/source/command-objects/port.rst +++ b/doc/source/command-objects/port.rst @@ -45,7 +45,8 @@ Create new port .. option:: --vnic-type - VNIC type for this port (direct | direct-physical | macvtap | normal(default) | baremetal) + VNIC type for this port (direct | direct-physical | macvtap | normal | baremetal). + If unspecified during port creation, default value will be 'normal'. .. option:: --binding-profile @@ -108,6 +109,65 @@ List ports os port list +port set +-------- + +Set port properties + +.. program:: port set +.. code:: bash + + os port set + [--fixed-ip subnet=,ip-address=] + [--device-id ] + [--device-owner ] + [--vnic-type ] + [--binding-profile ] + [--host-id ] + [--enable | --disable] + + +.. option:: --fixed-ip subnet=,ip-address= + + Desired IP and/or subnet for this port: + subnet=,ip-address= + (you can repeat this option) + +.. option:: --device-id + + Device ID of this port + +.. option:: --device-owner + + Device owner of this port + +.. option:: --vnic-type + + VNIC type for this port (direct | direct-physical | macvtap | normal | baremetal). + If unspecified during port creation, default value will be 'normal'. + +.. option:: --binding-profile + + Custom data to be passed as binding:profile: = + (this option can be repeated) + +.. option:: --host-id + + The ID of the host where the port is allocated + +.. option:: --enable + + Enable port + +.. option:: --disable + + Disable port + +.. _port_set-port: +.. describe:: + + Port to modify (name or ID) + port show --------- diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 449dcfd498..b618a4b0c7 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -14,6 +14,7 @@ """Port action implementations""" from openstackclient.common import command +from openstackclient.common import exceptions from openstackclient.common import parseractions from openstackclient.common import utils from openstackclient.identity import common as identity_common @@ -56,8 +57,6 @@ def _get_columns(item): def _get_attrs(client_manager, parsed_args): attrs = {} - if parsed_args.name is not None: - attrs['name'] = str(parsed_args.name) if parsed_args.fixed_ip is not None: attrs['fixed_ips'] = parsed_args.fixed_ip if parsed_args.device_id is not None: @@ -75,6 +74,8 @@ def _get_attrs(client_manager, parsed_args): # The remaining options do not support 'port set' command, so they require # additional check + if 'name' in parsed_args and parsed_args.name is not None: + attrs['name'] = str(parsed_args.name) if 'mac_address' in parsed_args and parsed_args.mac_address is not None: attrs['mac_address'] = parsed_args.mac_address if 'network' in parsed_args and parsed_args.network is not None: @@ -145,8 +146,9 @@ def _add_updatable_args(parser): metavar='', choices=['direct', 'direct-physical', 'macvtap', 'normal', 'baremetal'], - help='VNIC type for this port (direct | direct-physical |' - ' macvtap | normal(default) | baremetal)') + help="VNIC type for this port (direct | direct-physical |" + " macvtap | normal | baremetal). If unspecified during" + " port creation, default value will be 'normal'.") parser.add_argument( '--binding-profile', metavar='', @@ -265,6 +267,48 @@ def take_action(self, parsed_args): ) for s in data)) +class SetPort(command.Command): + """Set port properties""" + + def get_parser(self, prog_name): + parser = super(SetPort, self).get_parser(prog_name) + _add_updatable_args(parser) + admin_group = parser.add_mutually_exclusive_group() + admin_group.add_argument( + '--enable', + dest='admin_state', + action='store_true', + default=None, + help='Enable port', + ) + admin_group.add_argument( + '--disable', + dest='admin_state', + action='store_false', + help='Disable port', + ) + parser.add_argument( + 'port', + metavar="", + help=("Port to modify (name or ID)") + ) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + + _prepare_fixed_ips(self.app.client_manager, parsed_args) + attrs = _get_attrs(self.app.client_manager, parsed_args) + + if attrs == {}: + msg = "Nothing specified to be set" + raise exceptions.CommandError(msg) + + obj = client.find_port(parsed_args.port, ignore_missing=False) + client.update_port(obj, **attrs) + + class ShowPort(command.ShowOne): """Display port details""" diff --git a/openstackclient/tests/network/v2/test_port.py b/openstackclient/tests/network/v2/test_port.py index 30e290c6ad..7b1c655f67 100644 --- a/openstackclient/tests/network/v2/test_port.py +++ b/openstackclient/tests/network/v2/test_port.py @@ -240,6 +240,85 @@ def test_port_list_no_options(self): self.assertEqual(self.data, list(data)) +class TestSetPort(TestPort): + + _port = network_fakes.FakePort.create_one_port() + + def setUp(self): + super(TestSetPort, self).setUp() + + self.fake_subnet = network_fakes.FakeSubnet.create_one_subnet() + self.network.find_subnet = mock.Mock(return_value=self.fake_subnet) + self.network.find_port = mock.Mock(return_value=self._port) + self.network.update_port = mock.Mock(return_value=None) + + # Get the command object to test + self.cmd = port.SetPort(self.app, self.namespace) + + def test_set_fixed_ip(self): + arglist = [ + '--fixed-ip', 'ip-address=10.0.0.11', + self._port.name, + ] + verifylist = [ + ('fixed_ip', [{'ip-address': '10.0.0.11'}]), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = { + 'fixed_ips': [{'ip_address': '10.0.0.11'}], + } + self.network.update_port.assert_called_with(self._port, **attrs) + self.assertIsNone(result) + + def test_set_this(self): + arglist = [ + '--disable', + self._port.name, + ] + verifylist = [ + ('admin_state', False), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = { + 'admin_state_up': False, + } + self.network.update_port.assert_called_with(self._port, **attrs) + self.assertIsNone(result) + + def test_set_that(self): + arglist = [ + '--enable', + '--vnic-type', 'macvtap', + '--binding-profile', 'foo=bar', + '--host-id', 'binding-host-id-xxxx', + self._port.name, + ] + verifylist = [ + ('admin_state', True), + ('vnic_type', 'macvtap'), + ('binding_profile', {'foo': 'bar'}), + ('host_id', 'binding-host-id-xxxx'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = { + 'admin_state_up': True, + 'binding:vnic_type': 'macvtap', + 'binding:profile': {'foo': 'bar'}, + 'binding:host_id': 'binding-host-id-xxxx', + } + self.network.update_port.assert_called_with(self._port, **attrs) + self.assertIsNone(result) + + class TestShowPort(TestPort): # The port to show. diff --git a/releasenotes/notes/add-port-set-command-2b4fe38ec71ad48f.yaml b/releasenotes/notes/add-port-set-command-2b4fe38ec71ad48f.yaml new file mode 100644 index 0000000000..89784d2b7f --- /dev/null +++ b/releasenotes/notes/add-port-set-command-2b4fe38ec71ad48f.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add support for the ``port set`` command. + [Bug `1519909 `_] diff --git a/setup.cfg b/setup.cfg index 728b5d646b..981d2fef49 100644 --- a/setup.cfg +++ b/setup.cfg @@ -336,6 +336,7 @@ openstack.network.v2 = port_create = openstackclient.network.v2.port:CreatePort port_delete = openstackclient.network.v2.port:DeletePort port_list = openstackclient.network.v2.port:ListPort + port_set = openstackclient.network.v2.port:SetPort port_show = openstackclient.network.v2.port:ShowPort router_create = openstackclient.network.v2.router:CreateRouter From 9fcbd0ad4b05310d9cb7c3d26cde7cccd353ae09 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Fri, 12 Feb 2016 09:58:38 -0600 Subject: [PATCH 0705/3095] Add port functional tests Add functional tests for "os port" commands. Change-Id: I162eff6abacd9ffdde369647491ae472b604c692 Depends-On: I2bea508e11290284aa64b1ab548a0bb61e7290d3 Partial-Bug: #1519909 Partially-Implements: blueprint neutron-client --- functional/tests/network/v2/test_port.py | 58 ++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 functional/tests/network/v2/test_port.py diff --git a/functional/tests/network/v2/test_port.py b/functional/tests/network/v2/test_port.py new file mode 100644 index 0000000000..5b358a387a --- /dev/null +++ b/functional/tests/network/v2/test_port.py @@ -0,0 +1,58 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import uuid + +from functional.common import test + + +class PortTests(test.TestCase): + """Functional tests for port. """ + NAME = uuid.uuid4().hex + NETWORK_NAME = uuid.uuid4().hex + HEADERS = ['Name'] + FIELDS = ['name'] + + @classmethod + def setUpClass(cls): + # Create a network for the subnet. + cls.openstack('network create ' + cls.NETWORK_NAME) + opts = cls.get_show_opts(cls.FIELDS) + raw_output = cls.openstack( + 'port create --network ' + cls.NETWORK_NAME + ' ' + + cls.NAME + opts + ) + expected = cls.NAME + '\n' + cls.assertOutput(expected, raw_output) + + @classmethod + def tearDownClass(cls): + raw_output = cls.openstack('port delete ' + cls.NAME) + cls.assertOutput('', raw_output) + raw_output = cls.openstack('network delete ' + cls.NETWORK_NAME) + cls.assertOutput('', raw_output) + + def test_port_list(self): + opts = self.get_list_opts(self.HEADERS) + raw_output = self.openstack('port list' + opts) + self.assertIn(self.NAME, raw_output) + + def test_port_set(self): + self.openstack('port set --disable ' + self.NAME) + opts = self.get_show_opts(['name', 'admin_state_up']) + raw_output = self.openstack('port show ' + self.NAME + opts) + self.assertEqual("DOWN\n" + self.NAME + "\n", raw_output) + + def test_port_show(self): + opts = self.get_show_opts(self.FIELDS) + raw_output = self.openstack('port show ' + self.NAME + opts) + self.assertEqual(self.NAME + "\n", raw_output) From 189e4774f88243669ee1b9089d6c39021094c83d Mon Sep 17 00:00:00 2001 From: Xi Yang Date: Mon, 18 Jan 2016 17:09:14 +0800 Subject: [PATCH 0706/3095] Add support of setting snapshot state This patch is going to add the functionality of setting snapshot state which OSC currently lacks. Closes-Bug:#1535239 Change-Id: I2afd6567416e75ba0c70b73351cf1eb5394b3373 --- doc/source/command-objects/snapshot.rst | 11 +++++++++- .../tests/volume/v2/test_snapshot.py | 20 +++++++++++++++++-- openstackclient/volume/v2/snapshot.py | 14 ++++++++++++- .../notes/bug-1535239-767e6cf1990eda01.yaml | 6 ++++++ 4 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/bug-1535239-767e6cf1990eda01.yaml diff --git a/doc/source/command-objects/snapshot.rst b/doc/source/command-objects/snapshot.rst index b483868498..9033011822 100644 --- a/doc/source/command-objects/snapshot.rst +++ b/doc/source/command-objects/snapshot.rst @@ -2,7 +2,7 @@ snapshot ======== -Block Storage v1 +Block Storage v1, v2 snapshot create --------------- @@ -82,6 +82,7 @@ Set snapshot properties [--name ] [--description ] [--property [...] ] + [--state ] .. _snapshot_restore-snapshot: @@ -97,6 +98,14 @@ Set snapshot properties Property to add or modify for this snapshot (repeat option to set multiple properties) +.. option:: --state + + New snapshot state. + Valid values are "available", "error", "creating", + "deleting", and "error_deleting". + + *Volume version 2 only* + .. describe:: Snapshot to modify (name or ID) diff --git a/openstackclient/tests/volume/v2/test_snapshot.py b/openstackclient/tests/volume/v2/test_snapshot.py index 87e2fccfa8..9151a1d50d 100644 --- a/openstackclient/tests/volume/v2/test_snapshot.py +++ b/openstackclient/tests/volume/v2/test_snapshot.py @@ -205,7 +205,6 @@ def test_snapshot_list_all_projects(self): class TestSnapshotSet(TestSnapshot): - def setUp(self): super(TestSnapshotSet, self).setUp() @@ -246,6 +245,23 @@ def test_snapshot_set(self): ) self.assertIsNone(result) + def test_snapshot_set_state_to_error(self): + arglist = [ + "--state", "error", + volume_fakes.snapshot_id + ] + verifylist = [ + ("state", "error"), + ("snapshot", volume_fakes.snapshot_id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.snapshots_mock.reset_state.assert_called_with( + volume_fakes.snapshot_id, "error") + self.assertIsNone(result) + class TestSnapshotShow(TestSnapshot): @@ -276,7 +292,6 @@ def test_snapshot_show(self): class TestSnapshotUnset(TestSnapshot): - def setUp(self): super(TestSnapshotUnset, self).setUp() @@ -298,6 +313,7 @@ def test_snapshot_unset(self): ("snapshot", volume_fakes.snapshot_id), ("property", ["foo"]) ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) diff --git a/openstackclient/volume/v2/snapshot.py b/openstackclient/volume/v2/snapshot.py index 4d00b72623..bddefc723c 100644 --- a/openstackclient/volume/v2/snapshot.py +++ b/openstackclient/volume/v2/snapshot.py @@ -180,6 +180,14 @@ def get_parser(self, prog_name): help='Property to add/change for this snapshot ' '(repeat option to set multiple properties)', ) + parser.add_argument( + '--state', + metavar='', + choices=['available', 'error', 'creating', 'deleting', + 'error-deleting'], + help='New snapshot state. Valid values are available, ' + 'error, creating, deleting, and error-deleting.', + ) return parser def take_action(self, parsed_args): @@ -193,13 +201,17 @@ def take_action(self, parsed_args): if parsed_args.description: kwargs['description'] = parsed_args.description - if not kwargs and not parsed_args.property: + if (not kwargs and not parsed_args.property and not + parsed_args.state): self.app.log.error("No changes requested\n") return if parsed_args.property: volume_client.volume_snapshots.set_metadata(snapshot.id, parsed_args.property) + if parsed_args.state: + volume_client.volume_snapshots.reset_state(snapshot.id, + parsed_args.state) volume_client.volume_snapshots.update(snapshot.id, **kwargs) diff --git a/releasenotes/notes/bug-1535239-767e6cf1990eda01.yaml b/releasenotes/notes/bug-1535239-767e6cf1990eda01.yaml new file mode 100644 index 0000000000..36f8e687a5 --- /dev/null +++ b/releasenotes/notes/bug-1535239-767e6cf1990eda01.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Support a new ``--state`` option for ``snapshot set`` command that + changes the state of a snapshot. + [Bug `1535239 `_] From 81930abdcbee68a7687a53625ac3b5d34f16168f Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 8 Mar 2016 20:41:52 +0800 Subject: [PATCH 0707/3095] Remove FakeFlavorResource class In unit tests, all real methods are faked. They should not do any real operations in the tests. So, FakeFlavorResource is not necessary. Just fake get_keys(), set_keys and unset_keys() in FakeResource would be enough. Change-Id: Icc3473ba9c77f4817d0edddb7ff3e1bd2946fac7 --- openstackclient/tests/compute/v2/fakes.py | 45 ++++++++--------------- 1 file changed, 16 insertions(+), 29 deletions(-) diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index f735fe42d8..11d9ff1b46 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -501,42 +501,21 @@ def get_servers(servers=None, count=2): return mock.MagicMock(side_effect=servers) -class FakeFlavorResource(fakes.FakeResource): - """Fake flavor object's methods to help test. - - The flavor object has three methods to get, set, unset its properties. - Need to fake them, otherwise the functions to be tested won't run properly. - """ - - def __init__(self, manager=None, info={}, loaded=False, methods={}): - super(FakeFlavorResource, self).__init__(manager, info, - loaded, methods) - # Fake properties. - self._keys = {'property': 'value'} - - def set_keys(self, args): - self._keys.update(args) - - def unset_keys(self, keys): - for key in keys: - self._keys.pop(key, None) - - def get_keys(self): - return self._keys - - class FakeFlavor(object): """Fake one or more flavors.""" @staticmethod - def create_one_flavor(attrs={}): + def create_one_flavor(attrs=None): """Create a fake flavor. :param Dictionary attrs: A dictionary with all attributes :return: - A FakeFlavorResource object, with id, name, ram, vcpus, properties + A FakeResource object, with id, name, ram, vcpus, properties """ + if attrs is None: + attrs = {} + # Set default attributes. flavor_info = { 'id': 'flavor-id-' + uuid.uuid4().hex, @@ -554,7 +533,15 @@ def create_one_flavor(attrs={}): # Overwrite default attributes. flavor_info.update(attrs) - flavor = FakeFlavorResource(info=copy.deepcopy(flavor_info), + # Set default methods. + flavor_methods = { + 'set_keys': None, + 'unset_keys': None, + 'get_keys': {'property': 'value'}, + } + + flavor = fakes.FakeResource(info=copy.deepcopy(flavor_info), + methods=flavor_methods, loaded=True) # Set attributes with special mappings in nova client. @@ -573,7 +560,7 @@ def create_flavors(attrs={}, count=2): :param int count: The number of flavors to fake :return: - A list of FakeFlavorResource objects faking the flavors + A list of FakeResource objects faking the flavors """ flavors = [] for i in range(0, count): @@ -589,7 +576,7 @@ def get_flavors(flavors=None, count=2): list. Otherwise create one. :param List flavors: - A list of FakeFlavorResource objects faking flavors + A list of FakeResource objects faking flavors :param int count: The number of flavors to fake :return: From b711c3a0ca622389b7f41021eb8ecd5d633b81d7 Mon Sep 17 00:00:00 2001 From: jichenjc Date: Mon, 22 Feb 2016 13:38:44 +0800 Subject: [PATCH 0708/3095] Add --reason for disable service disbale service allow a reason to be input as disable reason. This patch add support for it. Change-Id: I59622c3970e055ebd46bf03c33c864b6d064db28 --- .../command-objects/compute-service.rst | 11 +++++--- openstackclient/compute/v2/service.py | 22 +++++++++++----- .../tests/compute/v2/test_service.py | 26 +++++++++++++++++++ .../add-disable-reason-6e0f28459a09a60d.yaml | 4 +++ 4 files changed, 54 insertions(+), 9 deletions(-) create mode 100644 releasenotes/notes/add-disable-reason-6e0f28459a09a60d.yaml diff --git a/doc/source/command-objects/compute-service.rst b/doc/source/command-objects/compute-service.rst index 7bcab4d692..25a133d97c 100644 --- a/doc/source/command-objects/compute-service.rst +++ b/doc/source/command-objects/compute-service.rst @@ -50,19 +50,24 @@ Set service command .. program:: compute service set .. code:: bash - os compute service list + os compute service set [--enable | --disable] + [--disable-reason ] .. _compute-service-set: -.. describe:: --enable +.. option:: --enable Enable service (default) -.. describe:: --disable +.. option:: --disable Disable service +.. option:: --disable-reason + + Reason for disabling the service (in quotes) + .. describe:: Name of host diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index 3c06272490..1cc91711eb 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.py @@ -95,14 +95,24 @@ def get_parser(self, prog_name): dest="enabled", help="Disable a service", action="store_false") + parser.add_argument( + "--disable-reason", + default=None, + metavar="", + help="Reason for disabling the service (in quotas)" + ) return parser def take_action(self, parsed_args): compute_client = self.app.client_manager.compute - - if parsed_args.enabled: - action = compute_client.services.enable + cs = compute_client.services + + if not parsed_args.enabled: + if parsed_args.disable_reason: + cs.disable_log_reason(parsed_args.host, + parsed_args.service, + parsed_args.disable_reason) + else: + cs.disable(parsed_args.host, parsed_args.service) else: - action = compute_client.services.disable - - action(parsed_args.host, parsed_args.service) + cs.enable(parsed_args.host, parsed_args.service) diff --git a/openstackclient/tests/compute/v2/test_service.py b/openstackclient/tests/compute/v2/test_service.py index 2f8b2e7d10..0246fbc86b 100644 --- a/openstackclient/tests/compute/v2/test_service.py +++ b/openstackclient/tests/compute/v2/test_service.py @@ -153,3 +153,29 @@ def test_service_set_disable(self): compute_fakes.service_binary, ) self.assertIsNone(result) + + def test_service_set_disable_with_reason(self): + reason = 'earthquake' + arglist = [ + compute_fakes.service_host, + compute_fakes.service_binary, + '--disable', + '--disable-reason', + reason + ] + verifylist = [ + ('host', compute_fakes.service_host), + ('service', compute_fakes.service_binary), + ('enabled', False), + ('disable_reason', reason) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.service_mock.disable_log_reason.assert_called_with( + compute_fakes.service_host, + compute_fakes.service_binary, + reason + ) + self.assertIsNone(result) diff --git a/releasenotes/notes/add-disable-reason-6e0f28459a09a60d.yaml b/releasenotes/notes/add-disable-reason-6e0f28459a09a60d.yaml new file mode 100644 index 0000000000..298ccad5c4 --- /dev/null +++ b/releasenotes/notes/add-disable-reason-6e0f28459a09a60d.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Add support for the ``--disable-reason`` of ``service set`` command From 237d7feca8c56fccd65acf86736747402634aaf2 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Wed, 9 Mar 2016 14:17:05 +0800 Subject: [PATCH 0709/3095] [Identity] Check return value is None in identity v3 unit tests take_action() in commands inheriting from Command returns nothing. So we should assert the return is None in the unit tests of these commands. Change-Id: I53eeb88316b2c20882fed97149d55cb04bcb2b2e Closes-Bug: #1550636 --- .../tests/identity/v2_0/test_endpoint.py | 3 ++- .../tests/identity/v2_0/test_project.py | 4 ++-- .../tests/identity/v2_0/test_role.py | 6 +++-- .../tests/identity/v2_0/test_service.py | 3 ++- .../tests/identity/v2_0/test_token.py | 3 ++- .../tests/identity/v2_0/test_user.py | 24 ++++++++++++------- 6 files changed, 28 insertions(+), 15 deletions(-) diff --git a/openstackclient/tests/identity/v2_0/test_endpoint.py b/openstackclient/tests/identity/v2_0/test_endpoint.py index 088fdcd1c6..45ece45af9 100644 --- a/openstackclient/tests/identity/v2_0/test_endpoint.py +++ b/openstackclient/tests/identity/v2_0/test_endpoint.py @@ -132,11 +132,12 @@ def test_endpoint_delete_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) self.endpoints_mock.delete.assert_called_with( identity_fakes.endpoint_id, ) + self.assertIsNone(result) class TestEndpointList(TestEndpoint): diff --git a/openstackclient/tests/identity/v2_0/test_project.py b/openstackclient/tests/identity/v2_0/test_project.py index 9857029766..38684aaffe 100644 --- a/openstackclient/tests/identity/v2_0/test_project.py +++ b/openstackclient/tests/identity/v2_0/test_project.py @@ -613,10 +613,9 @@ def test_project_unset_key(self): verifylist = [ ('property', ['fee', 'fo']), ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { 'description': identity_fakes.project_description, @@ -631,3 +630,4 @@ def test_project_unset_key(self): identity_fakes.project_id, **kwargs ) + self.assertIsNone(result) diff --git a/openstackclient/tests/identity/v2_0/test_role.py b/openstackclient/tests/identity/v2_0/test_role.py index 03b7f92481..3c4b79a4a3 100644 --- a/openstackclient/tests/identity/v2_0/test_role.py +++ b/openstackclient/tests/identity/v2_0/test_role.py @@ -240,11 +240,12 @@ def test_role_delete_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) self.roles_mock.delete.assert_called_with( identity_fakes.role_id, ) + self.assertIsNone(result) class TestRoleList(TestRole): @@ -459,7 +460,7 @@ def test_role_remove(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) # RoleManager.remove_user_role(user, role, tenant=None) self.roles_mock.remove_user_role.assert_called_with( @@ -467,6 +468,7 @@ def test_role_remove(self): identity_fakes.role_id, identity_fakes.project_id, ) + self.assertIsNone(result) class TestRoleShow(TestRole): diff --git a/openstackclient/tests/identity/v2_0/test_service.py b/openstackclient/tests/identity/v2_0/test_service.py index 606b143327..dc0fbcd122 100644 --- a/openstackclient/tests/identity/v2_0/test_service.py +++ b/openstackclient/tests/identity/v2_0/test_service.py @@ -194,11 +194,12 @@ def test_service_delete_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) self.services_mock.delete.assert_called_with( identity_fakes.service_id, ) + self.assertIsNone(result) class TestServiceList(TestService): diff --git a/openstackclient/tests/identity/v2_0/test_token.py b/openstackclient/tests/identity/v2_0/test_token.py index c90477f942..613139dd3a 100644 --- a/openstackclient/tests/identity/v2_0/test_token.py +++ b/openstackclient/tests/identity/v2_0/test_token.py @@ -99,6 +99,7 @@ def test_token_revoke(self): verifylist = [('token', self.TOKEN)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) self.tokens_mock.delete.assert_called_with(self.TOKEN) + self.assertIsNone(result) diff --git a/openstackclient/tests/identity/v2_0/test_user.py b/openstackclient/tests/identity/v2_0/test_user.py index 9afe4ad12d..921e215da8 100644 --- a/openstackclient/tests/identity/v2_0/test_user.py +++ b/openstackclient/tests/identity/v2_0/test_user.py @@ -414,11 +414,12 @@ def test_user_delete_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) self.users_mock.delete.assert_called_with( identity_fakes.user_id, ) + self.assertIsNone(result) class TestUserList(TestUser): @@ -578,7 +579,7 @@ def test_user_set_name(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -590,6 +591,7 @@ def test_user_set_name(self): identity_fakes.user_id, **kwargs ) + self.assertIsNone(result) def test_user_set_password(self): arglist = [ @@ -608,13 +610,14 @@ def test_user_set_password(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) # UserManager.update_password(user, password) self.users_mock.update_password.assert_called_with( identity_fakes.user_id, 'secret', ) + self.assertIsNone(result) def test_user_set_password_prompt(self): arglist = [ @@ -636,13 +639,14 @@ def test_user_set_password_prompt(self): mocker = mock.Mock() mocker.return_value = 'abc123' with mock.patch("openstackclient.common.utils.get_password", mocker): - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) # UserManager.update_password(user, password) self.users_mock.update_password.assert_called_with( identity_fakes.user_id, 'abc123', ) + self.assertIsNone(result) def test_user_set_email(self): arglist = [ @@ -660,7 +664,7 @@ def test_user_set_email(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -672,6 +676,7 @@ def test_user_set_email(self): identity_fakes.user_id, **kwargs ) + self.assertIsNone(result) def test_user_set_project(self): arglist = [ @@ -689,13 +694,14 @@ def test_user_set_project(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) # UserManager.update_tenant(user, tenant) self.users_mock.update_tenant.assert_called_with( identity_fakes.user_id, identity_fakes.project_id, ) + self.assertIsNone(result) def test_user_set_enable(self): arglist = [ @@ -713,7 +719,7 @@ def test_user_set_enable(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -724,6 +730,7 @@ def test_user_set_enable(self): identity_fakes.user_id, **kwargs ) + self.assertIsNone(result) def test_user_set_disable(self): arglist = [ @@ -741,7 +748,7 @@ def test_user_set_disable(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { @@ -752,6 +759,7 @@ def test_user_set_disable(self): identity_fakes.user_id, **kwargs ) + self.assertIsNone(result) class TestUserShow(TestUser): From 13e2bb9b9621f2aa3d85f068ac5346d7c01d472d Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 9 Mar 2016 14:37:04 +0000 Subject: [PATCH 0710/3095] Trivial: Use 'SSH' rather than 'Ssh' This is reflected in the '--help' screen. Change-Id: Ic22a65ff6a56b069b37a0ea8365cce2b3f93621c --- doc/source/command-objects/server.rst | 2 +- openstackclient/compute/v2/server.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index 674172f007..54f4ee73f5 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -614,7 +614,7 @@ Show server details server ssh ---------- -Ssh to server +SSH to server .. program:: server ssh .. code:: bash diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 7376eabb77..58174018c0 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1445,7 +1445,7 @@ def take_action(self, parsed_args): class SshServer(command.Command): - """Ssh to server""" + """SSH to server""" def get_parser(self, prog_name): parser = super(SshServer, self).get_parser(prog_name) From f0c3b4e69dc56934305442b505d5f5f68579f1f2 Mon Sep 17 00:00:00 2001 From: Brandon Palm Date: Wed, 17 Feb 2016 20:01:23 +0000 Subject: [PATCH 0711/3095] Fixed command list The cliff module expects an array of tuples however the array that this function was returning was an array of tuples that was also containing an array of values for the commands attached to each group and the cliff module wasn't liking it. The output now comes out looking like: | openstack.common | limits show | | | extension list | | openstack.baremetal.v1 | baremetal set | Change-Id: Ifa1c149cb5c66ba27dc72bf72d7c8f2f50e42f73 Closes-Bug: 1545609 --- openstackclient/common/module.py | 20 +++++++++++++++++-- openstackclient/tests/common/test_module.py | 16 +++++++++------ .../notes/bug-1545609-bdc1efc17214463b.yaml | 5 +++++ 3 files changed, 33 insertions(+), 8 deletions(-) create mode 100644 releasenotes/notes/bug-1545609-bdc1efc17214463b.yaml diff --git a/openstackclient/common/module.py b/openstackclient/common/module.py index a3dea5da50..30c67c683f 100644 --- a/openstackclient/common/module.py +++ b/openstackclient/common/module.py @@ -19,6 +19,7 @@ import sys from openstackclient.common import command +from openstackclient.common import utils class ListCommand(command.Lister): @@ -29,9 +30,24 @@ class ListCommand(command.Lister): def take_action(self, parsed_args): cm = self.app.command_manager groups = cm.get_command_groups() - + groups = sorted(groups) columns = ('Command Group', 'Commands') - return (columns, ((c, cm.get_command_names(group=c)) for c in groups)) + + commands = [] + for group in groups: + command_names = cm.get_command_names(group) + command_names = sorted(command_names) + + if command_names != []: + + # TODO(bapalm): Fix this when cliff properly supports + # handling the detection rather than using the hard-code below. + if parsed_args.formatter == 'table': + command_names = utils.format_list(command_names, "\n") + + commands.append((group, command_names)) + + return (columns, commands) class ListModule(command.ShowOne): diff --git a/openstackclient/tests/common/test_module.py b/openstackclient/tests/common/test_module.py index 2821da9ed7..7d08dae7b7 100644 --- a/openstackclient/tests/common/test_module.py +++ b/openstackclient/tests/common/test_module.py @@ -48,10 +48,11 @@ def setUp(self): super(TestCommandList, self).setUp() self.app.command_manager = mock.Mock() - self.app.command_manager.get_command_groups.return_value = ['test'] + self.app.command_manager.get_command_groups.return_value = [ + 'openstack.common' + ] self.app.command_manager.get_command_names.return_value = [ - 'one', - 'cmd two', + 'limits show\nextension list' ] # Get the command object to test @@ -67,12 +68,15 @@ def test_command_list_no_options(self): # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) + # TODO(bapalm): Adjust this when cliff properly supports + # handling the detection rather than using the hard-code below. collist = ('Command Group', 'Commands') self.assertEqual(collist, columns) datalist = (( - 'test', - ['one', 'cmd two'], - ), ) + 'openstack.common', + 'limits show\nextension list' + ),) + self.assertEqual(datalist, tuple(data)) diff --git a/releasenotes/notes/bug-1545609-bdc1efc17214463b.yaml b/releasenotes/notes/bug-1545609-bdc1efc17214463b.yaml new file mode 100644 index 0000000000..73ce8db09a --- /dev/null +++ b/releasenotes/notes/bug-1545609-bdc1efc17214463b.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Fixed ``openstack command list`` to display properly + [Bug `1545609 `_] From 87d90f3e53e2c5fe110ef5e099e8289e1d85f7d0 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Fri, 12 Feb 2016 15:17:27 -0600 Subject: [PATCH 0712/3095] Add subnet pool functional tests Add functional tests for "os subnet pool" commands. Change-Id: I51ffabcdb4d0f8608cc847aae298c8cbfd1f6a3d Depends-On: I9150797c8cfa794d5264ad07965aa967d9a8f5bc Depends-On: I65bd71e0f54f2f65acefbc542df67a1b1ec26397 Related-Bug: #1544586 Related-Bug: #1544587 Related-Bug: #1544589 Related-Bug: #1544590 Related-Bug: #1544591 Partially-Implements: blueprint neutron-client --- .../tests/network/v2/test_subnet_pool.py | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 functional/tests/network/v2/test_subnet_pool.py diff --git a/functional/tests/network/v2/test_subnet_pool.py b/functional/tests/network/v2/test_subnet_pool.py new file mode 100644 index 0000000000..1515487add --- /dev/null +++ b/functional/tests/network/v2/test_subnet_pool.py @@ -0,0 +1,55 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import uuid + +from functional.common import test + + +class SubnetPoolTests(test.TestCase): + """Functional tests for subnet pool. """ + NAME = uuid.uuid4().hex + CREATE_POOL_PREFIX = '10.100.0.0/24' + SET_POOL_PREFIX = '10.100.0.0/16' + HEADERS = ['Name'] + FIELDS = ['name'] + + @classmethod + def setUpClass(cls): + opts = cls.get_show_opts(cls.FIELDS) + raw_output = cls.openstack('subnet pool create --pool-prefix ' + + cls.CREATE_POOL_PREFIX + ' ' + + cls.NAME + opts) + cls.assertOutput(cls.NAME + '\n', raw_output) + + @classmethod + def tearDownClass(cls): + raw_output = cls.openstack('subnet pool delete ' + cls.NAME) + cls.assertOutput('', raw_output) + + def test_subnet_list(self): + opts = self.get_list_opts(self.HEADERS) + raw_output = self.openstack('subnet pool list' + opts) + self.assertIn(self.NAME, raw_output) + + def test_subnet_set(self): + self.openstack('subnet pool set --pool-prefix ' + + self.SET_POOL_PREFIX + ' ' + self.NAME) + opts = self.get_show_opts(['prefixes', 'name']) + raw_output = self.openstack('subnet pool show ' + self.NAME + opts) + self.assertEqual(self.NAME + '\n' + self.SET_POOL_PREFIX + '\n', + raw_output) + + def test_subnet_show(self): + opts = self.get_show_opts(self.FIELDS) + raw_output = self.openstack('subnet pool show ' + self.NAME + opts) + self.assertEqual(self.NAME + '\n', raw_output) From 564c8ff2403da87b96562076865f42426a4f8eac Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Fri, 26 Feb 2016 11:33:45 -0600 Subject: [PATCH 0713/3095] Refactor security group show to use SDK Refactored the 'os security group show' command to use the SDK when neutron is enabled, but continue to use the nova client when nova network is enabled. Added a release note for the change in security group rules output due to Network v2. The column names remain unchanged to maintain backwards compatibility. Change-Id: I25233ddb8115d18b8b88affb3de13346084a339d Partial-Bug: #1519511 Implements: blueprint neutron-client --- openstackclient/compute/v2/security_group.py | 54 -------- openstackclient/network/utils.py | 41 ++++++ openstackclient/network/v2/security_group.py | 108 ++++++++++++++++ .../network/v2/security_group_rule.py | 31 +---- openstackclient/tests/compute/v2/fakes.py | 13 +- openstackclient/tests/network/v2/fakes.py | 29 ++++- .../tests/network/v2/test_security_group.py | 119 ++++++++++++++++++ .../notes/bug-1519511-68ca30ad5519d3d8.yaml | 6 + setup.cfg | 2 +- 9 files changed, 310 insertions(+), 93 deletions(-) create mode 100644 openstackclient/network/utils.py create mode 100644 releasenotes/notes/bug-1519511-68ca30ad5519d3d8.yaml diff --git a/openstackclient/compute/v2/security_group.py b/openstackclient/compute/v2/security_group.py index f378af14cb..042e5caf3d 100644 --- a/openstackclient/compute/v2/security_group.py +++ b/openstackclient/compute/v2/security_group.py @@ -56,23 +56,6 @@ def _xform_security_group_rule(sgroup): return info -def _xform_and_trim_security_group_rule(sgroup): - info = _xform_security_group_rule(sgroup) - # Trim parent security group ID since caller has this information. - info.pop('parent_group_id', None) - # Trim keys with empty string values. - keys_to_trim = [ - 'ip_protocol', - 'ip_range', - 'port_range', - 'remote_security_group', - ] - for key in keys_to_trim: - if key in info and not info[key]: - info.pop(key) - return info - - class CreateSecurityGroup(command.ShowOne): """Create a new security group""" @@ -215,40 +198,3 @@ def take_action(self, parsed_args): (utils.get_item_properties( s, columns, ) for s in rules)) - - -class ShowSecurityGroup(command.ShowOne): - """Display security group details""" - - def get_parser(self, prog_name): - parser = super(ShowSecurityGroup, self).get_parser(prog_name) - parser.add_argument( - 'group', - metavar='', - help='Security group to display (name or ID)', - ) - return parser - - def take_action(self, parsed_args): - - compute_client = self.app.client_manager.compute - info = {} - info.update(utils.find_resource( - compute_client.security_groups, - parsed_args.group, - )._info) - rules = [] - for r in info['rules']: - formatted_rule = _xform_and_trim_security_group_rule(r) - rules.append(utils.format_dict(formatted_rule)) - - # Format rules into a list of strings - info.update( - {'rules': utils.format_list(rules, separator='\n')} - ) - # Map 'tenant_id' column to 'project_id' - info.update( - {'project_id': info.pop('tenant_id')} - ) - - return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/network/utils.py b/openstackclient/network/utils.py new file mode 100644 index 0000000000..287f027163 --- /dev/null +++ b/openstackclient/network/utils.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. +# + + +# Transform compute security group rule for display. +def transform_compute_security_group_rule(sg_rule): + info = {} + info.update(sg_rule) + from_port = info.pop('from_port') + to_port = info.pop('to_port') + if isinstance(from_port, int) and isinstance(to_port, int): + port_range = {'port_range': "%u:%u" % (from_port, to_port)} + elif from_port is None and to_port is None: + port_range = {'port_range': ""} + else: + port_range = {'port_range': "%s:%s" % (from_port, to_port)} + info.update(port_range) + if 'cidr' in info['ip_range']: + info['ip_range'] = info['ip_range']['cidr'] + else: + info['ip_range'] = '' + if info['ip_protocol'] is None: + info['ip_protocol'] = '' + elif info['ip_protocol'].lower() == 'icmp': + info['port_range'] = '' + group = info.pop('group') + if 'name' in group: + info['remote_security_group'] = group['name'] + else: + info['remote_security_group'] = '' + return info diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py index 62699ffd3d..64717945f3 100644 --- a/openstackclient/network/v2/security_group.py +++ b/openstackclient/network/v2/security_group.py @@ -14,9 +14,81 @@ """Security Group action implementations""" import argparse +import six from openstackclient.common import utils from openstackclient.network import common +from openstackclient.network import utils as network_utils + + +def _format_network_security_group_rules(sg_rules): + # For readability and to align with formatting compute security group + # rules, trim keys with caller known (e.g. security group and tenant ID) + # or empty values. + for sg_rule in sg_rules: + empty_keys = [k for k, v in six.iteritems(sg_rule) if not v] + for key in empty_keys: + sg_rule.pop(key) + sg_rule.pop('security_group_id', None) + sg_rule.pop('tenant_id', None) + return utils.format_list_of_dicts(sg_rules) + + +def _format_compute_security_group_rule(sg_rule): + info = network_utils.transform_compute_security_group_rule(sg_rule) + # Trim parent security group ID since caller has this information. + info.pop('parent_group_id', None) + # Trim keys with empty string values. + keys_to_trim = [ + 'ip_protocol', + 'ip_range', + 'port_range', + 'remote_security_group', + ] + for key in keys_to_trim: + if key in info and not info[key]: + info.pop(key) + return utils.format_dict(info) + + +def _format_compute_security_group_rules(sg_rules): + rules = [] + for sg_rule in sg_rules: + rules.append(_format_compute_security_group_rule(sg_rule)) + return utils.format_list(rules, separator='\n') + + +_formatters_network = { + 'security_group_rules': _format_network_security_group_rules, +} + + +_formatters_compute = { + 'rules': _format_compute_security_group_rules, +} + + +def _get_columns(item): + # Build the display columns and a list of the property columns + # that need to be mapped (display column name, property name). + columns = list(item.keys()) + property_column_mappings = [] + if 'security_group_rules' in columns: + columns.append('rules') + columns.remove('security_group_rules') + property_column_mappings.append(('rules', 'security_group_rules')) + if 'tenant_id' in columns: + columns.append('project_id') + columns.remove('tenant_id') + property_column_mappings.append(('project_id', 'tenant_id')) + display_columns = sorted(columns) + + # Build the property columns and apply any column mappings. + property_columns = sorted(columns) + for property_column_mapping in property_column_mappings: + property_index = property_columns.index(property_column_mapping[0]) + property_columns[property_index] = property_column_mapping[1] + return tuple(display_columns), property_columns class DeleteSecurityGroup(common.NetworkAndComputeCommand): @@ -143,3 +215,39 @@ def take_action_compute(self, client, parsed_args): data.name, data.description, ) + + +class ShowSecurityGroup(common.NetworkAndComputeShowOne): + """Display security group details""" + + def update_parser_common(self, parser): + parser.add_argument( + 'group', + metavar='', + help='Security group to display (name or ID)', + ) + return parser + + def take_action_network(self, client, parsed_args): + obj = client.find_security_group(parsed_args.group, + ignore_missing=False) + display_columns, property_columns = _get_columns(obj) + data = utils.get_item_properties( + obj, + property_columns, + formatters=_formatters_network + ) + return (display_columns, data) + + def take_action_compute(self, client, parsed_args): + obj = utils.find_resource( + client.security_groups, + parsed_args.group, + ) + display_columns, property_columns = _get_columns(obj._info) + data = utils.get_dict_properties( + obj._info, + property_columns, + formatters=_formatters_compute + ) + return (display_columns, data) diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index a61e3233df..92f28cce77 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -18,38 +18,11 @@ from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.network import common - - -def _xform_security_group_rule(sgroup): - info = {} - info.update(sgroup) - from_port = info.pop('from_port') - to_port = info.pop('to_port') - if isinstance(from_port, int) and isinstance(to_port, int): - port_range = {'port_range': "%u:%u" % (from_port, to_port)} - elif from_port is None and to_port is None: - port_range = {'port_range': ""} - else: - port_range = {'port_range': "%s:%s" % (from_port, to_port)} - info.update(port_range) - if 'cidr' in info['ip_range']: - info['ip_range'] = info['ip_range']['cidr'] - else: - info['ip_range'] = '' - if info['ip_protocol'] is None: - info['ip_protocol'] = '' - elif info['ip_protocol'].lower() == 'icmp': - info['port_range'] = '' - group = info.pop('group') - if 'name' in group: - info['remote_security_group'] = group['name'] - else: - info['remote_security_group'] = '' - return info +from openstackclient.network import utils as network_utils def _format_security_group_rule_show(obj): - data = _xform_security_group_rule(obj) + data = network_utils.transform_compute_security_group_rule(obj) return zip(*sorted(six.iteritems(data))) diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 11d9ff1b46..26d3a28309 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -333,7 +333,9 @@ def create_one_security_group(attrs=None, methods=None): security_group_attrs.update(attrs) # Set default methods. - security_group_methods = {} + security_group_methods = { + 'keys': ['id', 'name', 'description', 'tenant_id', 'rules'], + } # Overwrite default methods. security_group_methods.update(methods) @@ -369,7 +371,7 @@ class FakeSecurityGroupRule(object): """Fake one or more security group rules.""" @staticmethod - def create_one_security_group_rule(attrs={}, methods={}): + def create_one_security_group_rule(attrs=None, methods=None): """Create a fake security group rule. :param Dictionary attrs: @@ -379,6 +381,11 @@ def create_one_security_group_rule(attrs={}, methods={}): :return: A FakeResource object, with id, etc. """ + if attrs is None: + attrs = {} + if methods is None: + methods = {} + # Set default attributes. security_group_rule_attrs = { 'from_port': -1, @@ -406,7 +413,7 @@ def create_one_security_group_rule(attrs={}, methods={}): return security_group_rule @staticmethod - def create_security_group_rules(attrs={}, methods={}, count=2): + def create_security_group_rules(attrs=None, methods=None, count=2): """Create multiple fake security group rules. :param Dictionary attrs: diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 9e6bf97f65..6cb7e997da 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -419,7 +419,7 @@ class FakeSecurityGroup(object): """Fake one or more security groups.""" @staticmethod - def create_one_security_group(attrs={}, methods={}): + def create_one_security_group(attrs=None, methods=None): """Create a fake security group. :param Dictionary attrs: @@ -429,6 +429,11 @@ def create_one_security_group(attrs={}, methods={}): :return: A FakeResource object, with id, name, etc. """ + if attrs is None: + attrs = {} + if methods is None: + methods = {} + # Set default attributes. security_group_attrs = { 'id': 'security-group-id-' + uuid.uuid4().hex, @@ -442,7 +447,10 @@ def create_one_security_group(attrs={}, methods={}): security_group_attrs.update(attrs) # Set default methods. - security_group_methods = {} + security_group_methods = { + 'keys': ['id', 'name', 'description', 'tenant_id', + 'security_group_rules'], + } # Overwrite default methods. security_group_methods.update(methods) @@ -451,10 +459,14 @@ def create_one_security_group(attrs={}, methods={}): info=copy.deepcopy(security_group_attrs), methods=copy.deepcopy(security_group_methods), loaded=True) + + # Set attributes with special mapping in OpenStack SDK. + security_group.project_id = security_group_attrs['tenant_id'] + return security_group @staticmethod - def create_security_groups(attrs={}, methods={}, count=2): + def create_security_groups(attrs=None, methods=None, count=2): """Create multiple fake security groups. :param Dictionary attrs: @@ -478,7 +490,7 @@ class FakeSecurityGroupRule(object): """Fake one or more security group rules.""" @staticmethod - def create_one_security_group_rule(attrs={}, methods={}): + def create_one_security_group_rule(attrs=None, methods=None): """Create a fake security group rule. :param Dictionary attrs: @@ -488,6 +500,11 @@ def create_one_security_group_rule(attrs={}, methods={}): :return: A FakeResource object, with id, etc. """ + if attrs is None: + attrs = {} + if methods is None: + methods = {} + # Set default attributes. security_group_rule_attrs = { 'direction': 'ingress', @@ -520,13 +537,13 @@ def create_one_security_group_rule(attrs={}, methods={}): methods=copy.deepcopy(security_group_rule_methods), loaded=True) - # Set attributes with special mappings. + # Set attributes with special mapping in OpenStack SDK. security_group_rule.project_id = security_group_rule_attrs['tenant_id'] return security_group_rule @staticmethod - def create_security_group_rules(attrs={}, methods={}, count=2): + def create_security_group_rules(attrs=None, methods=None, count=2): """Create multiple fake security group rules. :param Dictionary attrs: diff --git a/openstackclient/tests/network/v2/test_security_group.py b/openstackclient/tests/network/v2/test_security_group.py index b8114cbcf0..a66b7b0061 100644 --- a/openstackclient/tests/network/v2/test_security_group.py +++ b/openstackclient/tests/network/v2/test_security_group.py @@ -363,3 +363,122 @@ def test_set_all_options(self): new_description ) self.assertIsNone(result) + + +class TestShowSecurityGroupNetwork(TestSecurityGroupNetwork): + + # The security group rule to be shown with the group. + _security_group_rule = \ + network_fakes.FakeSecurityGroupRule.create_one_security_group_rule() + + # The security group to be shown. + _security_group = \ + network_fakes.FakeSecurityGroup.create_one_security_group( + attrs={'security_group_rules': [_security_group_rule._info]} + ) + + columns = ( + 'description', + 'id', + 'name', + 'project_id', + 'rules', + ) + + data = ( + _security_group.description, + _security_group.id, + _security_group.name, + _security_group.project_id, + security_group._format_network_security_group_rules( + [_security_group_rule._info]), + ) + + def setUp(self): + super(TestShowSecurityGroupNetwork, self).setUp() + + self.network.find_security_group = mock.Mock( + return_value=self._security_group) + + # Get the command object to test + self.cmd = security_group.ShowSecurityGroup(self.app, self.namespace) + + def test_show_no_options(self): + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, [], []) + + def test_show_all_options(self): + arglist = [ + self._security_group.id, + ] + verifylist = [ + ('group', self._security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.find_security_group.assert_called_once_with( + self._security_group.id, ignore_missing=False) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestShowSecurityGroupCompute(TestSecurityGroupCompute): + + # The security group rule to be shown with the group. + _security_group_rule = \ + compute_fakes.FakeSecurityGroupRule.create_one_security_group_rule() + + # The security group to be shown. + _security_group = \ + compute_fakes.FakeSecurityGroup.create_one_security_group( + attrs={'rules': [_security_group_rule._info]} + ) + + columns = ( + 'description', + 'id', + 'name', + 'project_id', + 'rules', + ) + + data = ( + _security_group.description, + _security_group.id, + _security_group.name, + _security_group.tenant_id, + security_group._format_compute_security_group_rules( + [_security_group_rule._info]), + ) + + def setUp(self): + super(TestShowSecurityGroupCompute, self).setUp() + + self.app.client_manager.network_endpoint_enabled = False + + self.compute.security_groups.get.return_value = self._security_group + + # Get the command object to test + self.cmd = security_group.ShowSecurityGroup(self.app, None) + + def test_show_no_options(self): + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, [], []) + + def test_show_all_options(self): + arglist = [ + self._security_group.id, + ] + verifylist = [ + ('group', self._security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.compute.security_groups.get.assert_called_once_with( + self._security_group.id) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) diff --git a/releasenotes/notes/bug-1519511-68ca30ad5519d3d8.yaml b/releasenotes/notes/bug-1519511-68ca30ad5519d3d8.yaml new file mode 100644 index 0000000000..d69244e6c2 --- /dev/null +++ b/releasenotes/notes/bug-1519511-68ca30ad5519d3d8.yaml @@ -0,0 +1,6 @@ +--- +features: + - The ``security group show`` command now uses Network v2 when + enabled which results in a more detailed output for network + security group rules. + [Bug `1519511 `_] diff --git a/setup.cfg b/setup.cfg index 981d2fef49..4809cd2283 100644 --- a/setup.cfg +++ b/setup.cfg @@ -100,7 +100,6 @@ openstack.compute.v2 = keypair_show = openstackclient.compute.v2.keypair:ShowKeypair security_group_create = openstackclient.compute.v2.security_group:CreateSecurityGroup - security_group_show = openstackclient.compute.v2.security_group:ShowSecurityGroup security_group_rule_create = openstackclient.compute.v2.security_group:CreateSecurityGroupRule security_group_rule_list = openstackclient.compute.v2.security_group:ListSecurityGroupRule @@ -348,6 +347,7 @@ openstack.network.v2 = security_group_delete = openstackclient.network.v2.security_group:DeleteSecurityGroup security_group_list = openstackclient.network.v2.security_group:ListSecurityGroup security_group_set = openstackclient.network.v2.security_group:SetSecurityGroup + security_group_show = openstackclient.network.v2.security_group:ShowSecurityGroup security_group_rule_delete = openstackclient.network.v2.security_group_rule:DeleteSecurityGroupRule security_group_rule_show = openstackclient.network.v2.security_group_rule:ShowSecurityGroupRule From ea2dd8e141ef1cf446a42ee93992d8fb44f4ba05 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Tue, 1 Mar 2016 15:34:35 -0600 Subject: [PATCH 0714/3095] Refactor security group create to use SDK Refactored the 'os security group create' command to use the SDK when neutron is enabled, but continue to use the nova client when nova network is enabled. Added a release note for the change in security group rules output due to Network v2. The tenant_id column name was fixed to align with the 'os security group show' command. Change-Id: Ib29df42edcddcc73a123fff6a64743a6bfcb7fbf Partial-Bug: #1519511 Implements: blueprint neutron-client --- openstackclient/compute/v2/security_group.py | 32 ---- openstackclient/network/v2/security_group.py | 50 ++++++ .../tests/compute/v2/test_security_group.py | 127 -------------- .../tests/network/v2/test_security_group.py | 159 +++++++++++++++++- .../notes/bug-1519511-65b8901ae6ea2e63.yaml | 13 ++ setup.cfg | 2 +- 6 files changed, 217 insertions(+), 166 deletions(-) delete mode 100644 openstackclient/tests/compute/v2/test_security_group.py create mode 100644 releasenotes/notes/bug-1519511-65b8901ae6ea2e63.yaml diff --git a/openstackclient/compute/v2/security_group.py b/openstackclient/compute/v2/security_group.py index 042e5caf3d..734bd78e3a 100644 --- a/openstackclient/compute/v2/security_group.py +++ b/openstackclient/compute/v2/security_group.py @@ -56,38 +56,6 @@ def _xform_security_group_rule(sgroup): return info -class CreateSecurityGroup(command.ShowOne): - """Create a new security group""" - - def get_parser(self, prog_name): - parser = super(CreateSecurityGroup, self).get_parser(prog_name) - parser.add_argument( - "name", - metavar="", - help="New security group name", - ) - parser.add_argument( - "--description", - metavar="", - help="Security group description", - ) - return parser - - def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - - description = parsed_args.description or parsed_args.name - - data = compute_client.security_groups.create( - parsed_args.name, - description, - ) - - info = {} - info.update(data._info) - return zip(*sorted(six.iteritems(info))) - - class CreateSecurityGroupRule(command.ShowOne): """Create a new security group rule""" diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py index 64717945f3..f8162477b8 100644 --- a/openstackclient/network/v2/security_group.py +++ b/openstackclient/network/v2/security_group.py @@ -91,6 +91,56 @@ def _get_columns(item): return tuple(display_columns), property_columns +class CreateSecurityGroup(common.NetworkAndComputeShowOne): + """Create a new security group""" + + def update_parser_common(self, parser): + parser.add_argument( + "name", + metavar="", + help="New security group name", + ) + parser.add_argument( + "--description", + metavar="", + help="Security group description", + ) + return parser + + def _get_description(self, parsed_args): + if parsed_args.description is not None: + return parsed_args.description + else: + return parsed_args.name + + def take_action_network(self, client, parsed_args): + attrs = {} + attrs['name'] = parsed_args.name + attrs['description'] = self._get_description(parsed_args) + obj = client.create_security_group(**attrs) + display_columns, property_columns = _get_columns(obj) + data = utils.get_item_properties( + obj, + property_columns, + formatters=_formatters_network + ) + return (display_columns, data) + + def take_action_compute(self, client, parsed_args): + description = self._get_description(parsed_args) + obj = client.security_groups.create( + parsed_args.name, + description, + ) + display_columns, property_columns = _get_columns(obj._info) + data = utils.get_dict_properties( + obj._info, + property_columns, + formatters=_formatters_compute + ) + return (display_columns, data) + + class DeleteSecurityGroup(common.NetworkAndComputeCommand): """Delete a security group""" diff --git a/openstackclient/tests/compute/v2/test_security_group.py b/openstackclient/tests/compute/v2/test_security_group.py deleted file mode 100644 index 01e27b8201..0000000000 --- a/openstackclient/tests/compute/v2/test_security_group.py +++ /dev/null @@ -1,127 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - -import copy - -from openstackclient.compute.v2 import security_group -from openstackclient.tests.compute.v2 import fakes as compute_fakes -from openstackclient.tests import fakes -from openstackclient.tests.identity.v2_0 import fakes as identity_fakes - - -security_group_id = '11' -security_group_name = 'wide-open' -security_group_description = 'nothing but net' - -SECURITY_GROUP = { - 'id': security_group_id, - 'name': security_group_name, - 'description': security_group_description, - 'tenant_id': identity_fakes.project_id, -} - - -class FakeSecurityGroupResource(fakes.FakeResource): - - def get_keys(self): - return {'property': 'value'} - - -class TestSecurityGroup(compute_fakes.TestComputev2): - - def setUp(self): - super(TestSecurityGroup, self).setUp() - - # Get a shortcut compute client security_groups mock - self.secgroups_mock = self.app.client_manager.compute.security_groups - self.secgroups_mock.reset_mock() - - # Get a shortcut identity client projects mock - self.projects_mock = self.app.client_manager.identity.projects - self.projects_mock.reset_mock() - - -class TestSecurityGroupCreate(TestSecurityGroup): - - columns = ( - 'description', - 'id', - 'name', - 'tenant_id', - ) - data = ( - security_group_description, - security_group_id, - security_group_name, - identity_fakes.project_id, - ) - - def setUp(self): - super(TestSecurityGroupCreate, self).setUp() - - self.secgroups_mock.create.return_value = FakeSecurityGroupResource( - None, - copy.deepcopy(SECURITY_GROUP), - loaded=True, - ) - - # Get the command object to test - self.cmd = security_group.CreateSecurityGroup(self.app, None) - - def test_security_group_create_no_options(self): - arglist = [ - security_group_name, - ] - verifylist = [ - ('name', security_group_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) - - # SecurityGroupManager.create(name, description) - self.secgroups_mock.create.assert_called_with( - security_group_name, - security_group_name, - ) - - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) - - def test_security_group_create_description(self): - arglist = [ - security_group_name, - '--description', security_group_description, - ] - verifylist = [ - ('name', security_group_name), - ('description', security_group_description), - ] - 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) - - # SecurityGroupManager.create(name, description) - self.secgroups_mock.create.assert_called_with( - security_group_name, - security_group_description, - ) - - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) diff --git a/openstackclient/tests/network/v2/test_security_group.py b/openstackclient/tests/network/v2/test_security_group.py index a66b7b0061..2d43d87218 100644 --- a/openstackclient/tests/network/v2/test_security_group.py +++ b/openstackclient/tests/network/v2/test_security_group.py @@ -37,6 +37,153 @@ def setUp(self): self.compute = self.app.client_manager.compute +class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork): + + # The security group to be created. + _security_group = \ + network_fakes.FakeSecurityGroup.create_one_security_group() + + columns = ( + 'description', + 'id', + 'name', + 'project_id', + 'rules', + ) + + data = ( + _security_group.description, + _security_group.id, + _security_group.name, + _security_group.project_id, + '', + ) + + def setUp(self): + super(TestCreateSecurityGroupNetwork, self).setUp() + + self.network.create_security_group = mock.Mock( + return_value=self._security_group) + + # Get the command object to test + self.cmd = security_group.CreateSecurityGroup(self.app, self.namespace) + + def test_create_no_options(self): + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, [], []) + + def test_create_min_options(self): + arglist = [ + self._security_group.name, + ] + verifylist = [ + ('name', self._security_group.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_security_group.assert_called_once_with(**{ + 'description': self._security_group.name, + 'name': self._security_group.name, + }) + self.assertEqual(tuple(self.columns), columns) + self.assertEqual(self.data, data) + + def test_create_all_options(self): + arglist = [ + '--description', self._security_group.description, + self._security_group.name, + ] + verifylist = [ + ('description', self._security_group.description), + ('name', self._security_group.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_security_group.assert_called_once_with(**{ + 'description': self._security_group.description, + 'name': self._security_group.name, + }) + self.assertEqual(tuple(self.columns), columns) + self.assertEqual(self.data, data) + + +class TestCreateSecurityGroupCompute(TestSecurityGroupCompute): + + # The security group to be shown. + _security_group = \ + compute_fakes.FakeSecurityGroup.create_one_security_group() + + columns = ( + 'description', + 'id', + 'name', + 'project_id', + 'rules', + ) + + data = ( + _security_group.description, + _security_group.id, + _security_group.name, + _security_group.tenant_id, + '', + ) + + def setUp(self): + super(TestCreateSecurityGroupCompute, self).setUp() + + self.app.client_manager.network_endpoint_enabled = False + + self.compute.security_groups.create.return_value = self._security_group + + # Get the command object to test + self.cmd = security_group.CreateSecurityGroup(self.app, None) + + def test_create_no_options(self): + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, [], []) + + def test_create_min_options(self): + arglist = [ + self._security_group.name, + ] + verifylist = [ + ('name', self._security_group.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.compute.security_groups.create.assert_called_once_with( + self._security_group.name, + self._security_group.name) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_all_options(self): + arglist = [ + '--description', self._security_group.description, + self._security_group.name, + ] + verifylist = [ + ('description', self._security_group.description), + ('name', self._security_group.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.compute.security_groups.create.assert_called_once_with( + self._security_group.name, + self._security_group.description) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + class TestDeleteSecurityGroupNetwork(TestSecurityGroupNetwork): # The security group to be deleted. @@ -65,7 +212,7 @@ def test_security_group_delete(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.network.delete_security_group.assert_called_with( + self.network.delete_security_group.assert_called_once_with( self._security_group) self.assertIsNone(result) @@ -100,7 +247,7 @@ def test_security_group_delete(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.compute.security_groups.delete.assert_called_with( + self.compute.security_groups.delete.assert_called_once_with( self._security_group.id) self.assertIsNone(result) @@ -143,7 +290,7 @@ def test_security_group_list_no_options(self): columns, data = self.cmd.take_action(parsed_args) - self.network.security_groups.assert_called_with() + self.network.security_groups.assert_called_once_with() self.assertEqual(self.expected_columns, columns) self.assertEqual(self.expected_data, tuple(data)) @@ -158,7 +305,7 @@ def test_security_group_list_all_projects(self): columns, data = self.cmd.take_action(parsed_args) - self.network.security_groups.assert_called_with() + self.network.security_groups.assert_called_once_with() self.assertEqual(self.expected_columns, columns) self.assertEqual(self.expected_data, tuple(data)) @@ -212,7 +359,7 @@ def test_security_group_list_no_options(self): columns, data = self.cmd.take_action(parsed_args) kwargs = {'search_opts': {'all_tenants': False}} - self.compute.security_groups.list.assert_called_with(**kwargs) + self.compute.security_groups.list.assert_called_once_with(**kwargs) self.assertEqual(self.expected_columns, columns) self.assertEqual(self.expected_data, tuple(data)) @@ -228,7 +375,7 @@ def test_security_group_list_all_projects(self): columns, data = self.cmd.take_action(parsed_args) kwargs = {'search_opts': {'all_tenants': True}} - self.compute.security_groups.list.assert_called_with(**kwargs) + self.compute.security_groups.list.assert_called_once_with(**kwargs) self.assertEqual(self.expected_columns_all_projects, columns) self.assertEqual(self.expected_data_all_projects, tuple(data)) diff --git a/releasenotes/notes/bug-1519511-65b8901ae6ea2e63.yaml b/releasenotes/notes/bug-1519511-65b8901ae6ea2e63.yaml new file mode 100644 index 0000000000..843c6cafa7 --- /dev/null +++ b/releasenotes/notes/bug-1519511-65b8901ae6ea2e63.yaml @@ -0,0 +1,13 @@ +--- +features: + - The ``security group create`` command now uses Network v2 when + enabled which results in a more detailed output for network + security group rules. + [Bug `1519511 `_] +fixes: + - The ``security group create`` command now uses Network v2 when + enabled which allows the security group description to be created + with an empty value. In addition, the ``tenant_id`` field changed + to ``project_id`` to match the ``security group show`` command + output. + [Bug `1519511 `_] diff --git a/setup.cfg b/setup.cfg index 4809cd2283..88d2203d63 100644 --- a/setup.cfg +++ b/setup.cfg @@ -99,7 +99,6 @@ openstack.compute.v2 = keypair_list = openstackclient.compute.v2.keypair:ListKeypair keypair_show = openstackclient.compute.v2.keypair:ShowKeypair - security_group_create = openstackclient.compute.v2.security_group:CreateSecurityGroup security_group_rule_create = openstackclient.compute.v2.security_group:CreateSecurityGroupRule security_group_rule_list = openstackclient.compute.v2.security_group:ListSecurityGroupRule @@ -344,6 +343,7 @@ openstack.network.v2 = router_set = openstackclient.network.v2.router:SetRouter router_show = openstackclient.network.v2.router:ShowRouter + security_group_create = openstackclient.network.v2.security_group:CreateSecurityGroup security_group_delete = openstackclient.network.v2.security_group:DeleteSecurityGroup security_group_list = openstackclient.network.v2.security_group:ListSecurityGroup security_group_set = openstackclient.network.v2.security_group:SetSecurityGroup From 71b8919054fc7cc7f95006f6d7e2bcee18c955e5 Mon Sep 17 00:00:00 2001 From: Brad Behle Date: Thu, 11 Feb 2016 15:20:27 -0600 Subject: [PATCH 0715/3095] Add "os subnet create" command using SDK Implement the openstack client subnet create command using SDK calls. Co-Authored-By: Terry Howe Partially implements: blueprint neutron-client Closes-Bug: #1542364 Change-Id: Ia6120b8dccf2ee83dc89b3f496f7180d4dc5199a --- doc/source/command-objects/subnet.rst | 108 ++++++ openstackclient/network/v2/subnet.py | 226 +++++++++++- openstackclient/tests/network/v2/fakes.py | 2 +- .../tests/network/v2/test_subnet.py | 336 +++++++++++++++++- .../notes/bug-1542364-5d1e93cfd24f0b65.yaml | 5 + setup.cfg | 1 + 6 files changed, 671 insertions(+), 7 deletions(-) create mode 100644 releasenotes/notes/bug-1542364-5d1e93cfd24f0b65.yaml diff --git a/doc/source/command-objects/subnet.rst b/doc/source/command-objects/subnet.rst index 97d5c68b89..56a28d737d 100644 --- a/doc/source/command-objects/subnet.rst +++ b/doc/source/command-objects/subnet.rst @@ -20,6 +20,114 @@ Delete a subnet Subnet to delete (name or ID) +subnet create +-------------- + +Create new subnet + +.. program:: subnet create +.. code:: bash + + os subnet create + [--project [--project-domain ]] + [--subnet-pool | --use-default-subnet-pool [--prefix-length ]] + [--subnet-range ] + [--allocation-pool start=,end=] + [--dhcp | --no-dhcp] + [--dns-nameserver ] + [--gateway ] + [--host-route destination=,gateway=] + [--ip-version {4,6}] + [--ipv6-ra-mode {dhcpv6-stateful,dhcpv6-stateless,slaac}] + [--ipv6-address-mode {dhcpv6-stateful,dhcpv6-stateless,slaac}] + --network + + +.. option:: --project + + Owner's project (name or ID) + +.. option:: --project-domain + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + +.. option:: --subnet-pool + + Subnet pool from which this subnet will obtain a CIDR (name or ID) + +.. option:: --use-default-subnet-pool + + Use default subnet pool for --ip-version + +.. option:: --prefix-length + + Prefix length for subnet allocation from subnet pool + +.. option:: --subnet-range + + Subnet range in CIDR notation + (required if --subnet-pool is not specified, optional otherwise) + +.. option:: --allocation-pool start=,end= + + Allocation pool IP addresses for this subnet e.g.: + start=192.168.199.2,end=192.168.199.254 (This option can be repeated) + +.. option:: --dhcp + + Enable DHCP (default) + +.. option:: --no-dhcp + + Disable DHCP + +.. option:: --dns-nameserver + + DNS name server for this subnet (This option can be repeated) + +.. option:: --gateway + + Specify a gateway for the subnet. The three options are: + : Specific IP address to use as the gateway + 'auto': Gateway address should automatically be chosen from + within the subnet itself + 'none': This subnet will not use a gateway + e.g.: --gateway 192.168.9.1, --gateway auto, --gateway none + (default is 'auto') + +.. option:: --host-route destination=,gateway= + + Additional route for this subnet e.g.: + destination=10.10.0.0/16,gateway=192.168.71.254 + destination: destination subnet (in CIDR notation) + gateway: nexthop IP address + (This option can be repeated) + +.. option:: --ip-version {4,6} + + IP version (default is 4). Note that when subnet pool is specified, + IP version is determined from the subnet pool and this option + is ignored. + +.. option:: --ipv6-ra-mode {dhcpv6-stateful,dhcpv6-stateless,slaac} + + IPv6 RA (Router Advertisement) mode, + valid modes: [dhcpv6-stateful, dhcpv6-stateless, slaac] + +.. option:: --ipv6-address-mode {dhcpv6-stateful,dhcpv6-stateless,slaac} + + IPv6 address mode, valid modes: [dhcpv6-stateful, dhcpv6-stateless, slaac] + +.. option:: --network + + Network this subnet belongs to (name or ID) + +.. _subnet_create-name: +.. describe:: + + Name of subnet to create + subnet list ----------- diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index b514a88f7d..11646b4a72 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -12,9 +12,14 @@ # """Subnet action implementations""" +import copy + +from json.encoder import JSONEncoder from openstackclient.common import command +from openstackclient.common import parseractions from openstackclient.common import utils +from openstackclient.identity import common as identity_common def _format_allocation_pools(data): @@ -23,10 +28,17 @@ def _format_allocation_pools(data): return ','.join(pool_formatted) +def _format_host_routes(data): + try: + return '\n'.join([JSONEncoder().encode(route) for route in data]) + except (TypeError, KeyError): + return '' + + _formatters = { 'allocation_pools': _format_allocation_pools, 'dns_nameservers': utils.format_list, - 'host_routes': utils.format_list, + 'host_routes': _format_host_routes, } @@ -38,6 +50,214 @@ def _get_columns(item): return tuple(sorted(columns)) +def convert_entries_to_nexthop(entries): + # Change 'gateway' entry to 'nexthop' + changed_entries = copy.deepcopy(entries) + for entry in changed_entries: + entry['nexthop'] = entry['gateway'] + del entry['gateway'] + + return changed_entries + + +def convert_entries_to_gateway(entries): + # Change 'nexhop' entry to 'gateway' + changed_entries = copy.deepcopy(entries) + for entry in changed_entries: + entry['gateway'] = entry['nexthop'] + del entry['nexthop'] + + return changed_entries + + +def _get_attrs(client_manager, parsed_args): + attrs = {} + if parsed_args.name is not None: + attrs['name'] = str(parsed_args.name) + + if 'project' in parsed_args and parsed_args.project is not None: + identity_client = client_manager.identity + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + attrs['tenant_id'] = project_id + + client = client_manager.network + attrs['network_id'] = client.find_network(parsed_args.network, + ignore_missing=False).id + + if parsed_args.subnet_pool is not None: + subnet_pool = client.find_subnet_pool(parsed_args.subnet_pool, + ignore_missing=False) + attrs['subnetpool_id'] = subnet_pool.id + + if parsed_args.use_default_subnet_pool: + attrs['use_default_subnetpool'] = True + if parsed_args.gateway.lower() != 'auto': + if parsed_args.gateway.lower() == 'none': + attrs['gateway_ip'] = None + else: + attrs['gateway_ip'] = parsed_args.gateway + if parsed_args.prefix_length is not None: + attrs['prefixlen'] = parsed_args.prefix_length + if parsed_args.subnet_range is not None: + attrs['cidr'] = parsed_args.subnet_range + if parsed_args.ip_version is not None: + attrs['ip_version'] = parsed_args.ip_version + if parsed_args.ipv6_ra_mode is not None: + attrs['ipv6_ra_mode'] = parsed_args.ipv6_ra_mode + if parsed_args.ipv6_address_mode is not None: + attrs['ipv6_address_mode'] = parsed_args.ipv6_address_mode + if parsed_args.allocation_pools is not None: + attrs['allocation_pools'] = parsed_args.allocation_pools + if parsed_args.enable_dhcp is not None: + attrs['enable_dhcp'] = parsed_args.enable_dhcp + if parsed_args.dns_nameservers is not None: + attrs['dns_nameservers'] = parsed_args.dns_nameservers + if parsed_args.host_routes is not None: + # Change 'gateway' entry to 'nexthop' to match the API + attrs['host_routes'] = convert_entries_to_nexthop( + parsed_args.host_routes) + + return attrs + + +class CreateSubnet(command.ShowOne): + """Create a subnet""" + + def get_parser(self, prog_name): + parser = super(CreateSubnet, self).get_parser(prog_name) + parser.add_argument( + 'name', + help='New subnet name', + ) + parser.add_argument( + '--project', + metavar='', + help="Owner's project (name or ID)", + ) + identity_common.add_project_domain_option_to_parser(parser) + subnet_pool_group = parser.add_mutually_exclusive_group() + subnet_pool_group.add_argument( + '--subnet-pool', + metavar='', + help='Subnet pool from which this subnet will obtain a CIDR ' + '(Name or ID)', + ) + subnet_pool_group.add_argument( + '--use-default-subnet-pool', + action='store_true', + help='Use default subnet pool for --ip-version', + ) + parser.add_argument( + '--prefix-length', + metavar='', + help='Prefix length for subnet allocation from subnetpool', + ) + parser.add_argument( + '--subnet-range', + metavar='', + help='Subnet range in CIDR notation ' + '(required if --subnet-pool is not specified, ' + 'optional otherwise)', + ) + parser.add_argument( + '--allocation-pool', + metavar='start=,end=', + dest='allocation_pools', + action=parseractions.MultiKeyValueAction, + required_keys=['start', 'end'], + help='Allocation pool IP addresses for this subnet ' + 'e.g.: start=192.168.199.2,end=192.168.199.254 ' + '(This option can be repeated)', + ) + dhcp_enable_group = parser.add_mutually_exclusive_group() + dhcp_enable_group.add_argument( + '--dhcp', + dest='enable_dhcp', + action='store_true', + default=True, + help='Enable DHCP (default)', + ) + dhcp_enable_group.add_argument( + '--no-dhcp', + dest='enable_dhcp', + action='store_false', + help='Disable DHCP', + ) + parser.add_argument( + '--dns-nameserver', + metavar='', + action='append', + dest='dns_nameservers', + help='DNS name server for this subnet ' + '(This option can be repeated)', + ) + parser.add_argument( + '--gateway', + metavar='', + default='auto', + help="Specify a gateway for the subnet. The three options are: " + " : Specific IP address to use as the gateway " + " 'auto': Gateway address should automatically be " + " chosen from within the subnet itself " + " 'none': This subnet will not use a gateway " + "e.g.: --gateway 192.168.9.1, --gateway auto, --gateway none" + "(default is 'auto')", + ) + parser.add_argument( + '--host-route', + metavar='destination=,gateway=', + dest='host_routes', + action=parseractions.MultiKeyValueAction, + required_keys=['destination', 'gateway'], + help='Additional route for this subnet ' + 'e.g.: destination=10.10.0.0/16,gateway=192.168.71.254 ' + 'destination: destination subnet (in CIDR notation) ' + 'gateway: nexthop IP address ' + '(This option can be repeated)', + ) + parser.add_argument( + '--ip-version', + type=int, + default=4, + choices=[4, 6], + help='IP version (default is 4). Note that when subnet pool is ' + 'specified, IP version is determined from the subnet pool ' + 'and this option is ignored.', + ) + parser.add_argument( + '--ipv6-ra-mode', + choices=['dhcpv6-stateful', 'dhcpv6-stateless', 'slaac'], + help='IPv6 RA (Router Advertisement) mode, ' + 'valid modes: [dhcpv6-stateful, dhcpv6-stateless, slaac]', + ) + parser.add_argument( + '--ipv6-address-mode', + choices=['dhcpv6-stateful', 'dhcpv6-stateless', 'slaac'], + help='IPv6 address mode, ' + 'valid modes: [dhcpv6-stateful, dhcpv6-stateless, slaac]', + ) + parser.add_argument( + '--network', + required=True, + metavar='', + help='Network this subnet belongs to (name or ID)', + ) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + attrs = _get_attrs(self.app.client_manager, parsed_args) + obj = client.create_subnet(**attrs) + columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns, formatters=_formatters) + return (columns, data) + + class DeleteSubnet(command.Command): """Delete subnet""" @@ -46,7 +266,7 @@ def get_parser(self, prog_name): parser.add_argument( 'subnet', metavar="", - help="Subnet to delete (name or ID)" + help="Subnet to delete (name or ID)", ) return parser @@ -97,7 +317,7 @@ def get_parser(self, prog_name): parser.add_argument( 'subnet', metavar="", - help="Subnet to show (name or ID)" + help="Subnet to show (name or ID)", ) return parser diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 26213b1f12..59886d2043 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -573,7 +573,7 @@ def create_one_subnet(attrs={}, methods={}): 'dns_nameservers': [], 'allocation_pools': [], 'host_routes': [], - 'ip_version': '4', + 'ip_version': 4, 'gateway_ip': '10.10.10.1', 'ipv6_address_mode': 'None', 'ipv6_ra_mode': 'None', diff --git a/openstackclient/tests/network/v2/test_subnet.py b/openstackclient/tests/network/v2/test_subnet.py index a95635ffad..b718d262ae 100644 --- a/openstackclient/tests/network/v2/test_subnet.py +++ b/openstackclient/tests/network/v2/test_subnet.py @@ -11,10 +11,13 @@ # under the License. # +import copy import mock from openstackclient.common import utils from openstackclient.network.v2 import subnet as subnet_v2 +from openstackclient.tests import fakes +from openstackclient.tests.identity.v3 import fakes as identity_fakes_v3 from openstackclient.tests.network.v2 import fakes as network_fakes from openstackclient.tests import utils as tests_utils @@ -28,6 +31,333 @@ def setUp(self): self.network = self.app.client_manager.network +class TestCreateSubnet(TestSubnet): + + # An IPv4 subnet to be created with mostly default values + _subnet = network_fakes.FakeSubnet.create_one_subnet( + attrs={ + 'tenant_id': identity_fakes_v3.project_id, + } + ) + + # Subnet pool to be used to create a subnet from a pool + _subnet_pool = network_fakes.FakeSubnetPool.create_one_subnet_pool() + + # An IPv4 subnet to be created using a specific subnet pool + _subnet_from_pool = network_fakes.FakeSubnet.create_one_subnet( + attrs={ + 'tenant_id': identity_fakes_v3.project_id, + 'subnetpool_id': _subnet_pool.id, + 'dns_nameservers': ['8.8.8.8', + '8.8.4.4'], + 'host_routes': [{'destination': '10.20.20.0/24', + 'nexthop': '10.20.20.1'}, + {'destination': '10.30.30.0/24', + 'nexthop': '10.30.30.1'}], + } + ) + + # An IPv6 subnet to be created with most options specified + _subnet_ipv6 = network_fakes.FakeSubnet.create_one_subnet( + attrs={ + 'tenant_id': identity_fakes_v3.project_id, + 'cidr': 'fe80:0:0:a00a::/64', + 'enable_dhcp': True, + 'dns_nameservers': ['fe80:27ff:a00a:f00f::ffff', + 'fe80:37ff:a00a:f00f::ffff'], + 'allocation_pools': [{'start': 'fe80::a00a:0:c0de:0:100', + 'end': 'fe80::a00a:0:c0de:0:f000'}, + {'start': 'fe80::a00a:0:c0de:1:100', + 'end': 'fe80::a00a:0:c0de:1:f000'}], + 'host_routes': [{'destination': 'fe80:27ff:a00a:f00f::/64', + 'nexthop': 'fe80:27ff:a00a:f00f::1'}, + {'destination': 'fe80:37ff:a00a:f00f::/64', + 'nexthop': 'fe80:37ff:a00a:f00f::1'}], + 'ip_version': 6, + 'gateway_ip': 'fe80::a00a:0:c0de:0:1', + 'ipv6_address_mode': 'slaac', + 'ipv6_ra_mode': 'slaac', + 'subnetpool_id': 'None', + } + ) + + # The network to be returned from find_network + _network = network_fakes.FakeNetwork.create_one_network( + attrs={ + 'id': _subnet.network_id, + } + ) + + columns = ( + 'allocation_pools', + 'cidr', + 'dns_nameservers', + 'enable_dhcp', + 'gateway_ip', + 'host_routes', + 'id', + 'ip_version', + 'ipv6_address_mode', + 'ipv6_ra_mode', + 'name', + 'network_id', + 'project_id', + 'subnetpool_id', + ) + + data = ( + subnet_v2._format_allocation_pools(_subnet.allocation_pools), + _subnet.cidr, + utils.format_list(_subnet.dns_nameservers), + _subnet.enable_dhcp, + _subnet.gateway_ip, + subnet_v2._format_host_routes(_subnet.host_routes), + _subnet.id, + _subnet.ip_version, + _subnet.ipv6_address_mode, + _subnet.ipv6_ra_mode, + _subnet.name, + _subnet.network_id, + _subnet.project_id, + _subnet.subnetpool_id, + ) + + data_subnet_pool = ( + subnet_v2._format_allocation_pools(_subnet_from_pool.allocation_pools), + _subnet_from_pool.cidr, + utils.format_list(_subnet_from_pool.dns_nameservers), + _subnet_from_pool.enable_dhcp, + _subnet_from_pool.gateway_ip, + subnet_v2._format_host_routes(_subnet_from_pool.host_routes), + _subnet_from_pool.id, + _subnet_from_pool.ip_version, + _subnet_from_pool.ipv6_address_mode, + _subnet_from_pool.ipv6_ra_mode, + _subnet_from_pool.name, + _subnet_from_pool.network_id, + _subnet_from_pool.project_id, + _subnet_from_pool.subnetpool_id, + ) + + data_ipv6 = ( + subnet_v2._format_allocation_pools(_subnet_ipv6.allocation_pools), + _subnet_ipv6.cidr, + utils.format_list(_subnet_ipv6.dns_nameservers), + _subnet_ipv6.enable_dhcp, + _subnet_ipv6.gateway_ip, + subnet_v2._format_host_routes(_subnet_ipv6.host_routes), + _subnet_ipv6.id, + _subnet_ipv6.ip_version, + _subnet_ipv6.ipv6_address_mode, + _subnet_ipv6.ipv6_ra_mode, + _subnet_ipv6.name, + _subnet_ipv6.network_id, + _subnet_ipv6.project_id, + _subnet_ipv6.subnetpool_id, + ) + + def setUp(self): + super(TestCreateSubnet, self).setUp() + + # Get the command object to test + self.cmd = subnet_v2.CreateSubnet(self.app, self.namespace) + + # Set identity client v3. And get a shortcut to Identity client. + identity_client = identity_fakes_v3.FakeIdentityv3Client( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN, + ) + self.app.client_manager.identity = identity_client + self.identity = self.app.client_manager.identity + + # Get a shortcut to the ProjectManager Mock + self.projects_mock = self.identity.projects + self.projects_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes_v3.PROJECT), + loaded=True, + ) + + # Get a shortcut to the DomainManager Mock + self.domains_mock = self.identity.domains + self.domains_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes_v3.DOMAIN), + loaded=True, + ) + + def test_create_no_options(self): + arglist = [] + verifylist = [] + + # Testing that a call without the required argument will fail and + # throw a "ParserExecption" + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, arglist, verifylist) + + def test_create_default_options(self): + # Mock create_subnet and find_network sdk calls to return the + # values we want for this test + self.network.create_subnet = mock.Mock(return_value=self._subnet) + self._network.id = self._subnet.network_id + self.network.find_network = mock.Mock(return_value=self._network) + + arglist = [ + self._subnet.name, + "--subnet-range", self._subnet.cidr, + "--network", self._subnet.network_id, + ] + verifylist = [ + ('name', self._subnet.name), + ('subnet_range', self._subnet.cidr), + ('network', self._subnet.network_id), + ('ip_version', self._subnet.ip_version), + ('gateway', 'auto'), + + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_subnet.assert_called_with(**{ + 'cidr': self._subnet.cidr, + 'enable_dhcp': self._subnet.enable_dhcp, + 'ip_version': self._subnet.ip_version, + 'name': self._subnet.name, + 'network_id': self._subnet.network_id, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_from_subnet_pool_options(self): + # Mock create_subnet, find_subnet_pool, and find_network sdk calls + # to return the values we want for this test + self.network.create_subnet = \ + mock.Mock(return_value=self._subnet_from_pool) + self._network.id = self._subnet_from_pool.network_id + self.network.find_network = mock.Mock(return_value=self._network) + self.network.find_subnet_pool = \ + mock.Mock(return_value=self._subnet_pool) + + arglist = [ + self._subnet_from_pool.name, + "--subnet-pool", self._subnet_from_pool.subnetpool_id, + "--prefix-length", '24', + "--network", self._subnet_from_pool.network_id, + "--ip-version", str(self._subnet_from_pool.ip_version), + "--gateway", self._subnet_from_pool.gateway_ip, + "--dhcp", + ] + + for dns_addr in self._subnet_from_pool.dns_nameservers: + arglist.append('--dns-nameserver') + arglist.append(dns_addr) + + for host_route in self._subnet_from_pool.host_routes: + arglist.append('--host-route') + value = 'gateway=' + host_route.get('nexthop', '') + \ + ',destination=' + host_route.get('destination', '') + arglist.append(value) + + verifylist = [ + ('name', self._subnet_from_pool.name), + ('prefix_length', '24'), + ('network', self._subnet_from_pool.network_id), + ('ip_version', self._subnet_from_pool.ip_version), + ('gateway', self._subnet_from_pool.gateway_ip), + ('dns_nameservers', self._subnet_from_pool.dns_nameservers), + ('enable_dhcp', self._subnet_from_pool.enable_dhcp), + ('host_routes', subnet_v2.convert_entries_to_gateway( + self._subnet_from_pool.host_routes)), + ('subnet_pool', self._subnet_from_pool.subnetpool_id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_subnet.assert_called_with(**{ + 'dns_nameservers': self._subnet_from_pool.dns_nameservers, + 'enable_dhcp': self._subnet_from_pool.enable_dhcp, + 'gateway_ip': self._subnet_from_pool.gateway_ip, + 'host_routes': self._subnet_from_pool.host_routes, + 'ip_version': self._subnet_from_pool.ip_version, + 'name': self._subnet_from_pool.name, + 'network_id': self._subnet_from_pool.network_id, + 'prefixlen': '24', + 'subnetpool_id': self._subnet_from_pool.subnetpool_id, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data_subnet_pool, data) + + def test_create_options_subnet_range_ipv6(self): + # Mock create_subnet and find_network sdk calls to return the + # values we want for this test + self.network.create_subnet = mock.Mock(return_value=self._subnet_ipv6) + self._network.id = self._subnet_ipv6.network_id + self.network.find_network = mock.Mock(return_value=self._network) + + arglist = [ + self._subnet_ipv6.name, + "--subnet-range", self._subnet_ipv6.cidr, + "--network", self._subnet_ipv6.network_id, + "--ip-version", str(self._subnet_ipv6.ip_version), + "--ipv6-ra-mode", self._subnet_ipv6.ipv6_ra_mode, + "--ipv6-address-mode", self._subnet_ipv6.ipv6_address_mode, + "--gateway", self._subnet_ipv6.gateway_ip, + "--dhcp", + ] + + for dns_addr in self._subnet_ipv6.dns_nameservers: + arglist.append('--dns-nameserver') + arglist.append(dns_addr) + + for host_route in self._subnet_ipv6.host_routes: + arglist.append('--host-route') + value = 'gateway=' + host_route.get('nexthop', '') + \ + ',destination=' + host_route.get('destination', '') + arglist.append(value) + + for pool in self._subnet_ipv6.allocation_pools: + arglist.append('--allocation-pool') + value = 'start=' + pool.get('start', '') + \ + ',end=' + pool.get('end', '') + arglist.append(value) + + verifylist = [ + ('name', self._subnet_ipv6.name), + ('subnet_range', self._subnet_ipv6.cidr), + ('network', self._subnet_ipv6.network_id), + ('ip_version', self._subnet_ipv6.ip_version), + ('ipv6_ra_mode', self._subnet_ipv6.ipv6_ra_mode), + ('ipv6_address_mode', self._subnet_ipv6.ipv6_address_mode), + ('gateway', self._subnet_ipv6.gateway_ip), + ('dns_nameservers', self._subnet_ipv6.dns_nameservers), + ('enable_dhcp', self._subnet_ipv6.enable_dhcp), + ('host_routes', subnet_v2.convert_entries_to_gateway( + self._subnet_ipv6.host_routes)), + ('allocation_pools', self._subnet_ipv6.allocation_pools), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_subnet.assert_called_with(**{ + 'cidr': self._subnet_ipv6.cidr, + 'dns_nameservers': self._subnet_ipv6.dns_nameservers, + 'enable_dhcp': self._subnet_ipv6.enable_dhcp, + 'gateway_ip': self._subnet_ipv6.gateway_ip, + 'host_routes': self._subnet_ipv6.host_routes, + 'ip_version': self._subnet_ipv6.ip_version, + 'ipv6_address_mode': self._subnet_ipv6.ipv6_address_mode, + 'ipv6_ra_mode': self._subnet_ipv6.ipv6_ra_mode, + 'name': self._subnet_ipv6.name, + 'network_id': self._subnet_ipv6.network_id, + 'allocation_pools': self._subnet_ipv6.allocation_pools, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data_ipv6, data) + + class TestDeleteSubnet(TestSubnet): # The subnet to delete. @@ -65,7 +395,7 @@ class TestListSubnet(TestSubnet): 'ID', 'Name', 'Network', - 'Subnet' + 'Subnet', ) columns_long = columns + ( 'Project', @@ -74,7 +404,7 @@ class TestListSubnet(TestSubnet): 'Allocation Pools', 'Host Routes', 'IP Version', - 'Gateway' + 'Gateway', ) data = [] @@ -99,7 +429,7 @@ class TestListSubnet(TestSubnet): subnet_v2._format_allocation_pools(subnet.allocation_pools), utils.format_list(subnet.host_routes), subnet.ip_version, - subnet.gateway_ip + subnet.gateway_ip, )) def setUp(self): diff --git a/releasenotes/notes/bug-1542364-5d1e93cfd24f0b65.yaml b/releasenotes/notes/bug-1542364-5d1e93cfd24f0b65.yaml new file mode 100644 index 0000000000..0d61ba36c0 --- /dev/null +++ b/releasenotes/notes/bug-1542364-5d1e93cfd24f0b65.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``subnet create`` command. + [Bug `1542364 `_] diff --git a/setup.cfg b/setup.cfg index 5a1e32c850..1d2580a7b6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -351,6 +351,7 @@ openstack.network.v2 = security_group_rule_delete = openstackclient.network.v2.security_group_rule:DeleteSecurityGroupRule security_group_rule_show = openstackclient.network.v2.security_group_rule:ShowSecurityGroupRule + subnet_create = openstackclient.network.v2.subnet:CreateSubnet subnet_delete = openstackclient.network.v2.subnet:DeleteSubnet subnet_list = openstackclient.network.v2.subnet:ListSubnet subnet_show = openstackclient.network.v2.subnet:ShowSubnet From ab9f80e9b910e78e3894e960b19af24b24d95d8a Mon Sep 17 00:00:00 2001 From: Doug Hellmann Date: Thu, 10 Mar 2016 17:59:09 -0500 Subject: [PATCH 0716/3095] Update reno for stable/mitaka Change-Id: I4737774ee596bd3cf64c5deed3132afb1add3bcf --- releasenotes/source/index.rst | 1 + releasenotes/source/mitaka.rst | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 releasenotes/source/mitaka.rst diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index c89ed747ee..e3af0e6263 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,3 +6,4 @@ OpenStackClient Release Notes :maxdepth: 1 unreleased + mitaka diff --git a/releasenotes/source/mitaka.rst b/releasenotes/source/mitaka.rst new file mode 100644 index 0000000000..e545609650 --- /dev/null +++ b/releasenotes/source/mitaka.rst @@ -0,0 +1,6 @@ +=================================== + Mitaka Series Release Notes +=================================== + +.. release-notes:: + :branch: origin/stable/mitaka From 2debde35ef934735a280d83ab326add3efece7e1 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Fri, 11 Mar 2016 10:02:25 +0800 Subject: [PATCH 0717/3095] Trivial: Reorder classes in identity v3 in alphabetical order Change-Id: Iaf2e336c2415416ec69f6b974743c26509bba561 --- .../tests/identity/v3/test_consumer.py | 78 +++++++++--------- .../identity/v3/test_identity_provider.py | 80 +++++++++--------- .../tests/identity/v3/test_mappings.py | 70 ++++++++-------- .../tests/identity/v3/test_oauth.py | 80 +++++++++--------- .../identity/v3/test_service_provider.py | 82 +++++++++---------- .../tests/identity/v3/test_unscoped_saml.py | 44 +++++----- 6 files changed, 217 insertions(+), 217 deletions(-) diff --git a/openstackclient/tests/identity/v3/test_consumer.py b/openstackclient/tests/identity/v3/test_consumer.py index 4d15d5d836..4a8cf0871d 100644 --- a/openstackclient/tests/identity/v3/test_consumer.py +++ b/openstackclient/tests/identity/v3/test_consumer.py @@ -136,45 +136,6 @@ def test_consumer_list(self): self.assertEqual(datalist, tuple(data)) -class TestConsumerShow(TestOAuth1): - - def setUp(self): - super(TestConsumerShow, self).setUp() - - consumer_no_secret = copy.deepcopy(identity_fakes.OAUTH_CONSUMER) - del consumer_no_secret['secret'] - self.consumers_mock.get.return_value = fakes.FakeResource( - None, - consumer_no_secret, - loaded=True, - ) - - # Get the command object to test - self.cmd = consumer.ShowConsumer(self.app, None) - - def test_consumer_show(self): - arglist = [ - identity_fakes.consumer_id, - ] - verifylist = [ - ('consumer', identity_fakes.consumer_id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) - - self.consumers_mock.get.assert_called_with( - identity_fakes.consumer_id, - ) - - collist = ('description', 'id') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.consumer_description, - identity_fakes.consumer_id, - ) - self.assertEqual(datalist, data) - - class TestConsumerSet(TestOAuth1): def setUp(self): @@ -217,3 +178,42 @@ def test_consumer_update(self): **kwargs ) self.assertIsNone(result) + + +class TestConsumerShow(TestOAuth1): + + def setUp(self): + super(TestConsumerShow, self).setUp() + + consumer_no_secret = copy.deepcopy(identity_fakes.OAUTH_CONSUMER) + del consumer_no_secret['secret'] + self.consumers_mock.get.return_value = fakes.FakeResource( + None, + consumer_no_secret, + loaded=True, + ) + + # Get the command object to test + self.cmd = consumer.ShowConsumer(self.app, None) + + def test_consumer_show(self): + arglist = [ + identity_fakes.consumer_id, + ] + verifylist = [ + ('consumer', identity_fakes.consumer_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.consumers_mock.get.assert_called_with( + identity_fakes.consumer_id, + ) + + collist = ('description', 'id') + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.consumer_description, + identity_fakes.consumer_id, + ) + self.assertEqual(datalist, data) diff --git a/openstackclient/tests/identity/v3/test_identity_provider.py b/openstackclient/tests/identity/v3/test_identity_provider.py index 1ee7aade27..465e79bad7 100644 --- a/openstackclient/tests/identity/v3/test_identity_provider.py +++ b/openstackclient/tests/identity/v3/test_identity_provider.py @@ -310,46 +310,6 @@ def test_identity_provider_list_no_options(self): self.assertEqual(datalist, tuple(data)) -class TestIdentityProviderShow(TestIdentityProvider): - - def setUp(self): - super(TestIdentityProviderShow, self).setUp() - - ret = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.IDENTITY_PROVIDER), - loaded=True, - ) - self.identity_providers_mock.get.return_value = ret - # Get the command object to test - self.cmd = identity_provider.ShowIdentityProvider(self.app, None) - - def test_identity_provider_show(self): - arglist = [ - identity_fakes.idp_id, - ] - verifylist = [ - ('identity_provider', identity_fakes.idp_id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.identity_providers_mock.get.assert_called_with( - identity_fakes.idp_id, - ) - - collist = ('description', 'enabled', 'id', 'remote_ids') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.idp_description, - True, - identity_fakes.idp_id, - identity_fakes.formatted_idp_remote_ids - ) - self.assertEqual(datalist, data) - - class TestIdentityProviderSet(TestIdentityProvider): columns = ( @@ -627,3 +587,43 @@ def prepare(self): # neither --enable nor --disable was specified self.assertIsNone(columns) self.assertIsNone(data) + + +class TestIdentityProviderShow(TestIdentityProvider): + + def setUp(self): + super(TestIdentityProviderShow, self).setUp() + + ret = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.IDENTITY_PROVIDER), + loaded=True, + ) + self.identity_providers_mock.get.return_value = ret + # Get the command object to test + self.cmd = identity_provider.ShowIdentityProvider(self.app, None) + + def test_identity_provider_show(self): + arglist = [ + identity_fakes.idp_id, + ] + verifylist = [ + ('identity_provider', identity_fakes.idp_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.identity_providers_mock.get.assert_called_with( + identity_fakes.idp_id, + ) + + collist = ('description', 'enabled', 'id', 'remote_ids') + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.idp_description, + True, + identity_fakes.idp_id, + identity_fakes.formatted_idp_remote_ids + ) + self.assertEqual(datalist, data) diff --git a/openstackclient/tests/identity/v3/test_mappings.py b/openstackclient/tests/identity/v3/test_mappings.py index 106a419c49..b9e3b1d5d6 100644 --- a/openstackclient/tests/identity/v3/test_mappings.py +++ b/openstackclient/tests/identity/v3/test_mappings.py @@ -145,41 +145,6 @@ def test_mapping_list(self): self.assertEqual(datalist, data) -class TestMappingShow(TestMapping): - - def setUp(self): - super(TestMappingShow, self).setUp() - - self.mapping_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.MAPPING_RESPONSE), - loaded=True - ) - - self.cmd = mapping.ShowMapping(self.app, None) - - def test_mapping_show(self): - arglist = [ - identity_fakes.mapping_id - ] - verifylist = [ - ('mapping', identity_fakes.mapping_id) - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.mapping_mock.get.assert_called_with( - identity_fakes.mapping_id) - - collist = ('id', 'rules') - self.assertEqual(collist, columns) - - datalist = (identity_fakes.mapping_id, - identity_fakes.MAPPING_RULES) - self.assertEqual(datalist, data) - - class TestMappingSet(TestMapping): def setUp(self): @@ -242,3 +207,38 @@ def test_set_rules_wrong_file_path(self): exceptions.CommandError, self.cmd.take_action, parsed_args) + + +class TestMappingShow(TestMapping): + + def setUp(self): + super(TestMappingShow, self).setUp() + + self.mapping_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.MAPPING_RESPONSE), + loaded=True + ) + + self.cmd = mapping.ShowMapping(self.app, None) + + def test_mapping_show(self): + arglist = [ + identity_fakes.mapping_id + ] + verifylist = [ + ('mapping', identity_fakes.mapping_id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.mapping_mock.get.assert_called_with( + identity_fakes.mapping_id) + + collist = ('id', 'rules') + self.assertEqual(collist, columns) + + datalist = (identity_fakes.mapping_id, + identity_fakes.MAPPING_RULES) + self.assertEqual(datalist, data) diff --git a/openstackclient/tests/identity/v3/test_oauth.py b/openstackclient/tests/identity/v3/test_oauth.py index dba6d03419..d3cf36553a 100644 --- a/openstackclient/tests/identity/v3/test_oauth.py +++ b/openstackclient/tests/identity/v3/test_oauth.py @@ -32,52 +32,52 @@ def setUp(self): self.roles_mock.reset_mock() -class TestRequestTokenCreate(TestOAuth1): +class TestAccessTokenCreate(TestOAuth1): def setUp(self): - super(TestRequestTokenCreate, self).setUp() - - self.request_tokens_mock.create.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.OAUTH_REQUEST_TOKEN), - loaded=True, - ) + super(TestAccessTokenCreate, self).setUp() - self.projects_mock.get.return_value = fakes.FakeResource( + self.access_tokens_mock.create.return_value = fakes.FakeResource( None, - copy.deepcopy(identity_fakes.PROJECT), + copy.deepcopy(identity_fakes.OAUTH_ACCESS_TOKEN), loaded=True, ) - self.cmd = token.CreateRequestToken(self.app, None) + self.cmd = token.CreateAccessToken(self.app, None) - def test_create_request_tokens(self): + def test_create_access_tokens(self): arglist = [ '--consumer-key', identity_fakes.consumer_id, '--consumer-secret', identity_fakes.consumer_secret, - '--project', identity_fakes.project_id, + '--request-key', identity_fakes.request_token_id, + '--request-secret', identity_fakes.request_token_secret, + '--verifier', identity_fakes.oauth_verifier_pin, ] verifylist = [ ('consumer_key', identity_fakes.consumer_id), ('consumer_secret', identity_fakes.consumer_secret), - ('project', identity_fakes.project_id), + ('request_key', identity_fakes.request_token_id), + ('request_secret', identity_fakes.request_token_secret), + ('verifier', identity_fakes.oauth_verifier_pin), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.request_tokens_mock.create.assert_called_with( + self.access_tokens_mock.create.assert_called_with( identity_fakes.consumer_id, identity_fakes.consumer_secret, - identity_fakes.project_id, + identity_fakes.request_token_id, + identity_fakes.request_token_secret, + identity_fakes.oauth_verifier_pin, ) collist = ('expires', 'id', 'key', 'secret') self.assertEqual(collist, columns) datalist = ( - identity_fakes.request_token_expires, - identity_fakes.request_token_id, - identity_fakes.request_token_id, - identity_fakes.request_token_secret, + identity_fakes.access_token_expires, + identity_fakes.access_token_id, + identity_fakes.access_token_id, + identity_fakes.access_token_secret, ) self.assertEqual(datalist, data) @@ -123,51 +123,51 @@ def test_authorize_request_tokens(self): self.assertEqual(datalist, data) -class TestAccessTokenCreate(TestOAuth1): +class TestRequestTokenCreate(TestOAuth1): def setUp(self): - super(TestAccessTokenCreate, self).setUp() + super(TestRequestTokenCreate, self).setUp() - self.access_tokens_mock.create.return_value = fakes.FakeResource( + self.request_tokens_mock.create.return_value = fakes.FakeResource( None, - copy.deepcopy(identity_fakes.OAUTH_ACCESS_TOKEN), + copy.deepcopy(identity_fakes.OAUTH_REQUEST_TOKEN), loaded=True, ) - self.cmd = token.CreateAccessToken(self.app, None) + self.projects_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.PROJECT), + loaded=True, + ) - def test_create_access_tokens(self): + self.cmd = token.CreateRequestToken(self.app, None) + + def test_create_request_tokens(self): arglist = [ '--consumer-key', identity_fakes.consumer_id, '--consumer-secret', identity_fakes.consumer_secret, - '--request-key', identity_fakes.request_token_id, - '--request-secret', identity_fakes.request_token_secret, - '--verifier', identity_fakes.oauth_verifier_pin, + '--project', identity_fakes.project_id, ] verifylist = [ ('consumer_key', identity_fakes.consumer_id), ('consumer_secret', identity_fakes.consumer_secret), - ('request_key', identity_fakes.request_token_id), - ('request_secret', identity_fakes.request_token_secret), - ('verifier', identity_fakes.oauth_verifier_pin), + ('project', identity_fakes.project_id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.access_tokens_mock.create.assert_called_with( + self.request_tokens_mock.create.assert_called_with( identity_fakes.consumer_id, identity_fakes.consumer_secret, - identity_fakes.request_token_id, - identity_fakes.request_token_secret, - identity_fakes.oauth_verifier_pin, + identity_fakes.project_id, ) collist = ('expires', 'id', 'key', 'secret') self.assertEqual(collist, columns) datalist = ( - identity_fakes.access_token_expires, - identity_fakes.access_token_id, - identity_fakes.access_token_id, - identity_fakes.access_token_secret, + identity_fakes.request_token_expires, + identity_fakes.request_token_id, + identity_fakes.request_token_id, + identity_fakes.request_token_secret, ) self.assertEqual(datalist, data) diff --git a/openstackclient/tests/identity/v3/test_service_provider.py b/openstackclient/tests/identity/v3/test_service_provider.py index b1285b1080..80d60c5a4e 100644 --- a/openstackclient/tests/identity/v3/test_service_provider.py +++ b/openstackclient/tests/identity/v3/test_service_provider.py @@ -241,47 +241,6 @@ def test_service_provider_list_no_options(self): self.assertEqual(tuple(data), datalist) -class TestServiceProviderShow(TestServiceProvider): - - def setUp(self): - super(TestServiceProviderShow, self).setUp() - - ret = fakes.FakeResource( - None, - copy.deepcopy(service_fakes.SERVICE_PROVIDER), - loaded=True, - ) - self.service_providers_mock.get.return_value = ret - # Get the command object to test - self.cmd = service_provider.ShowServiceProvider(self.app, None) - - def test_service_provider_show(self): - arglist = [ - service_fakes.sp_id, - ] - verifylist = [ - ('service_provider', service_fakes.sp_id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.service_providers_mock.get.assert_called_with( - service_fakes.sp_id, - ) - - collist = ('auth_url', 'description', 'enabled', 'id', 'sp_url') - self.assertEqual(collist, columns) - datalist = ( - service_fakes.sp_auth_url, - service_fakes.sp_description, - True, - service_fakes.sp_id, - service_fakes.service_provider_url - ) - self.assertEqual(data, datalist) - - class TestServiceProviderSet(TestServiceProvider): columns = ( @@ -420,3 +379,44 @@ def prepare(self): # was set. self.assertIsNone(columns) self.assertIsNone(data) + + +class TestServiceProviderShow(TestServiceProvider): + + def setUp(self): + super(TestServiceProviderShow, self).setUp() + + ret = fakes.FakeResource( + None, + copy.deepcopy(service_fakes.SERVICE_PROVIDER), + loaded=True, + ) + self.service_providers_mock.get.return_value = ret + # Get the command object to test + self.cmd = service_provider.ShowServiceProvider(self.app, None) + + def test_service_provider_show(self): + arglist = [ + service_fakes.sp_id, + ] + verifylist = [ + ('service_provider', service_fakes.sp_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.service_providers_mock.get.assert_called_with( + service_fakes.sp_id, + ) + + collist = ('auth_url', 'description', 'enabled', 'id', 'sp_url') + self.assertEqual(collist, columns) + datalist = ( + service_fakes.sp_auth_url, + service_fakes.sp_description, + True, + service_fakes.sp_id, + service_fakes.service_provider_url + ) + self.assertEqual(data, datalist) diff --git a/openstackclient/tests/identity/v3/test_unscoped_saml.py b/openstackclient/tests/identity/v3/test_unscoped_saml.py index c2f14493a2..d12cb454df 100644 --- a/openstackclient/tests/identity/v3/test_unscoped_saml.py +++ b/openstackclient/tests/identity/v3/test_unscoped_saml.py @@ -30,23 +30,23 @@ def setUp(self): self.domains_mock.reset_mock() -class TestProjectList(TestUnscopedSAML): +class TestDomainList(TestUnscopedSAML): def setUp(self): - super(TestProjectList, self).setUp() + super(TestDomainList, self).setUp() - self.projects_mock.list.return_value = [ + self.domains_mock.list.return_value = [ fakes.FakeResource( None, - copy.deepcopy(identity_fakes.PROJECT), + copy.deepcopy(identity_fakes.DOMAIN), loaded=True, ), ] # Get the command object to test - self.cmd = unscoped_saml.ListAccessibleProjects(self.app, None) + self.cmd = unscoped_saml.ListAccessibleDomains(self.app, None) - def test_accessible_projects_list(self): + def test_accessible_domains_list(self): self.app.client_manager.auth_plugin_name = 'v3unscopedsaml' arglist = [] verifylist = [] @@ -57,19 +57,19 @@ def test_accessible_projects_list(self): # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.projects_mock.list.assert_called_with() + self.domains_mock.list.assert_called_with() - collist = ('ID', 'Domain ID', 'Enabled', 'Name') + collist = ('ID', 'Enabled', 'Name', 'Description') self.assertEqual(collist, columns) datalist = (( - identity_fakes.project_id, identity_fakes.domain_id, True, - identity_fakes.project_name, + identity_fakes.domain_name, + identity_fakes.domain_description, ), ) self.assertEqual(datalist, tuple(data)) - def test_accessible_projects_list_wrong_auth(self): + def test_accessible_domains_list_wrong_auth(self): auth = identity_fakes.FakeAuth("wrong auth") self.app.client_manager.identity.session.auth = auth arglist = [] @@ -81,23 +81,23 @@ def test_accessible_projects_list_wrong_auth(self): parsed_args) -class TestDomainList(TestUnscopedSAML): +class TestProjectList(TestUnscopedSAML): def setUp(self): - super(TestDomainList, self).setUp() + super(TestProjectList, self).setUp() - self.domains_mock.list.return_value = [ + self.projects_mock.list.return_value = [ fakes.FakeResource( None, - copy.deepcopy(identity_fakes.DOMAIN), + copy.deepcopy(identity_fakes.PROJECT), loaded=True, ), ] # Get the command object to test - self.cmd = unscoped_saml.ListAccessibleDomains(self.app, None) + self.cmd = unscoped_saml.ListAccessibleProjects(self.app, None) - def test_accessible_domains_list(self): + def test_accessible_projects_list(self): self.app.client_manager.auth_plugin_name = 'v3unscopedsaml' arglist = [] verifylist = [] @@ -108,19 +108,19 @@ def test_accessible_domains_list(self): # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.domains_mock.list.assert_called_with() + self.projects_mock.list.assert_called_with() - collist = ('ID', 'Enabled', 'Name', 'Description') + collist = ('ID', 'Domain ID', 'Enabled', 'Name') self.assertEqual(collist, columns) datalist = (( + identity_fakes.project_id, identity_fakes.domain_id, True, - identity_fakes.domain_name, - identity_fakes.domain_description, + identity_fakes.project_name, ), ) self.assertEqual(datalist, tuple(data)) - def test_accessible_domains_list_wrong_auth(self): + def test_accessible_projects_list_wrong_auth(self): auth = identity_fakes.FakeAuth("wrong auth") self.app.client_manager.identity.session.auth = auth arglist = [] From 4208f02a20af6d7561bd925406893b261a5fbbe1 Mon Sep 17 00:00:00 2001 From: Fang Zhen Date: Thu, 10 Mar 2016 18:51:22 +0800 Subject: [PATCH 0718/3095] Enhance list extension unit test Extension list involves identity, compute, volume and network. Current test covers only identity and network. This patch added test against compute and volum. Also refactored current implentation. Change-Id: If9b36cba24c50a817a17f685801e418fb898596a --- .../tests/common/test_extension.py | 157 +++++++++++++----- openstackclient/tests/volume/v2/fakes.py | 20 +++ 2 files changed, 137 insertions(+), 40 deletions(-) diff --git a/openstackclient/tests/common/test_extension.py b/openstackclient/tests/common/test_extension.py index 66532827cb..0736a3e54d 100644 --- a/openstackclient/tests/common/test_extension.py +++ b/openstackclient/tests/common/test_extension.py @@ -12,13 +12,16 @@ # import copy +import mock from openstackclient.common import extension from openstackclient.tests import fakes from openstackclient.tests import utils +from openstackclient.tests.compute.v2 import fakes as compute_fakes from openstackclient.tests.identity.v2_0 import fakes as identity_fakes from openstackclient.tests.network.v2 import fakes as network_fakes +from openstackclient.tests.volume.v2 import fakes as volume_fakes class TestExtension(utils.TestCommand): @@ -34,6 +37,16 @@ def setUp(self): self.app.client_manager.identity.extensions) self.identity_extensions_mock.reset_mock() + self.app.client_manager.compute = compute_fakes.FakeComputev2Client( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN, + ) + + self.app.client_manager.volume = volume_fakes.FakeVolumeClient( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN, + ) + network_client = network_fakes.FakeNetworkV2Client() self.app.client_manager.network = network_client self.network_extensions_mock = network_client.extensions @@ -43,6 +56,8 @@ def setUp(self): class TestExtensionList(TestExtension): columns = ('Name', 'Alias', 'Description') + long_columns = ('Name', 'Namespace', 'Description', 'Alias', 'Updated', + 'Links') def setUp(self): super(TestExtensionList, self).setUp() @@ -55,12 +70,33 @@ def setUp(self): ), ] + self.app.client_manager.compute.list_extensions = mock.Mock() + self.compute_extensions_mock = ( + self.app.client_manager.compute.list_extensions) + self.compute_extensions_mock.show_all.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy(compute_fakes.EXTENSION), + loaded=True, + ), + ] + + self.app.client_manager.volume.list_extensions = mock.Mock() + self.volume_extensions_mock = ( + self.app.client_manager.volume.list_extensions) + self.volume_extensions_mock.show_all.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.EXTENSION), + loaded=True, + ), + ] + # Get the command object to test self.cmd = extension.ListExtension(self.app, None) - def test_extension_list_no_options(self): - arglist = [] - verifylist = [] + def _test_extension_list_helper(self, arglist, verifylist, + expected_data, long=False): parsed_args = self.check_parser(self.cmd, arglist, verifylist) # In base command class Lister in cliff, abstract method take_action() @@ -68,23 +104,42 @@ def test_extension_list_no_options(self): # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - # no args should output from all services - self.identity_extensions_mock.list.assert_called_with() + if long: + self.assertEqual(self.long_columns, columns) + else: + self.assertEqual(self.columns, columns) + self.assertEqual(expected_data, tuple(data)) - self.assertEqual(self.columns, columns) + def test_extension_list_no_options(self): + arglist = [] + verifylist = [] datalist = ( ( identity_fakes.extension_name, identity_fakes.extension_alias, identity_fakes.extension_description, ), + ( + compute_fakes.extension_name, + compute_fakes.extension_alias, + compute_fakes.extension_description, + ), + ( + volume_fakes.extension_name, + volume_fakes.extension_alias, + volume_fakes.extension_description, + ), ( network_fakes.extension_name, network_fakes.extension_alias, network_fakes.extension_description, ), ) - self.assertEqual(datalist, tuple(data)) + self._test_extension_list_helper(arglist, verifylist, datalist) + self.identity_extensions_mock.list.assert_called_with() + self.compute_extensions_mock.show_all.assert_called_with() + self.volume_extensions_mock.show_all.assert_called_with() + self.network_extensions_mock.assert_called_with() def test_extension_list_long(self): arglist = [ @@ -93,19 +148,6 @@ def test_extension_list_long(self): verifylist = [ ('long', True), ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # In base command class Lister in cliff, abstract method take_action() - # returns a tuple containing the column names and an iterable - # containing the data to be listed. - columns, data = self.cmd.take_action(parsed_args) - - # no args should output from all services - self.identity_extensions_mock.list.assert_called_with() - - collist = ('Name', 'Namespace', 'Description', 'Alias', 'Updated', - 'Links') - self.assertEqual(collist, columns) datalist = ( ( identity_fakes.extension_name, @@ -115,6 +157,22 @@ def test_extension_list_long(self): identity_fakes.extension_updated, identity_fakes.extension_links, ), + ( + compute_fakes.extension_name, + compute_fakes.extension_namespace, + compute_fakes.extension_description, + compute_fakes.extension_alias, + compute_fakes.extension_updated, + compute_fakes.extension_links, + ), + ( + volume_fakes.extension_name, + volume_fakes.extension_namespace, + volume_fakes.extension_description, + volume_fakes.extension_alias, + volume_fakes.extension_updated, + volume_fakes.extension_links, + ), ( network_fakes.extension_name, network_fakes.extension_namespace, @@ -124,7 +182,11 @@ def test_extension_list_long(self): network_fakes.extension_links, ), ) - self.assertEqual(datalist, tuple(data)) + self._test_extension_list_helper(arglist, verifylist, datalist, True) + self.identity_extensions_mock.list.assert_called_with() + self.compute_extensions_mock.show_all.assert_called_with() + self.volume_extensions_mock.show_all.assert_called_with() + self.network_extensions_mock.assert_called_with() def test_extension_list_identity(self): arglist = [ @@ -133,22 +195,13 @@ def test_extension_list_identity(self): verifylist = [ ('identity', True), ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # In base command class Lister in cliff, abstract method take_action() - # returns a tuple containing the column names and an iterable - # containing the data to be listed. - columns, data = self.cmd.take_action(parsed_args) - - self.identity_extensions_mock.list.assert_called_with() - - self.assertEqual(self.columns, columns) datalist = (( identity_fakes.extension_name, identity_fakes.extension_alias, identity_fakes.extension_description, ), ) - self.assertEqual(datalist, tuple(data)) + self._test_extension_list_helper(arglist, verifylist, datalist) + self.identity_extensions_mock.list.assert_called_with() def test_extension_list_network(self): arglist = [ @@ -157,13 +210,6 @@ def test_extension_list_network(self): verifylist = [ ('network', True), ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.network_extensions_mock.assert_called_with() - - self.assertEqual(self.columns, columns) datalist = ( ( network_fakes.extension_name, @@ -171,4 +217,35 @@ def test_extension_list_network(self): network_fakes.extension_description, ), ) - self.assertEqual(datalist, tuple(data)) + self._test_extension_list_helper(arglist, verifylist, datalist) + self.network_extensions_mock.assert_called_with() + + def test_extension_list_compute(self): + arglist = [ + '--compute', + ] + verifylist = [ + ('compute', True), + ] + datalist = (( + compute_fakes.extension_name, + compute_fakes.extension_alias, + compute_fakes.extension_description, + ), ) + self._test_extension_list_helper(arglist, verifylist, datalist) + self.compute_extensions_mock.show_all.assert_called_with() + + def test_extension_list_volume(self): + arglist = [ + '--volume', + ] + verifylist = [ + ('volume', True), + ] + datalist = (( + volume_fakes.extension_name, + volume_fakes.extension_alias, + volume_fakes.extension_description, + ), ) + self._test_extension_list_helper(arglist, verifylist, datalist) + self.volume_extensions_mock.show_all.assert_called_with() diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py index 61d9df3aa5..97bbc59bce 100644 --- a/openstackclient/tests/volume/v2/fakes.py +++ b/openstackclient/tests/volume/v2/fakes.py @@ -211,6 +211,26 @@ 'name': image_name } +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 = '2013-04-18T00:00:00+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, +} + class FakeVolumeClient(object): From 515cc87174e1ea150c77e63bb8e17b007dde44d1 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Fri, 11 Mar 2016 16:38:48 +0800 Subject: [PATCH 0719/3095] Add incompatibility info for "ip floating list" command Commit d8abec33ada8b2b028d52eb8bfad2640812b9af8 changed the output of "ip floating list" command. But forgot to add any backward incompatibility info. Output of command "ip floating list" for nova network has been changed. And it is different from the output of neutron network. This patch adds this incompatibility info. Change-Id: I45858fda3b9bcc0bdf4d0891637fa7dd712872af Partial-Bug: 1519502 Related-to: blueprint neutron-client --- doc/source/backwards-incompatible.rst | 30 +++++++++++++++++++ .../notes/bug-1519502-f72236598d14d350.yaml | 4 +++ 2 files changed, 34 insertions(+) diff --git a/doc/source/backwards-incompatible.rst b/doc/source/backwards-incompatible.rst index bb2b0bddb9..4b90d6e1bc 100644 --- a/doc/source/backwards-incompatible.rst +++ b/doc/source/backwards-incompatible.rst @@ -162,6 +162,36 @@ List of Backwards Incompatible Changes * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1546065 * Commit: https://review.openstack.org/#/c/281089/ +14. Output of `ip floating list` command has changed. + + When using Compute v2, the original output of `ip floating list` command is: + +----+--------+------------+----------+-------------+ + | ID | Pool | IP | Fixed IP | Instance ID | + +----+--------+-----------------------+-------------+ + | 1 | public | 172.24.4.1 | None | None | + +----+--------+------------+----------+-------------+ + + Now it changes to: + +----+---------------------+------------------+-----------+--------+ + | ID | Floating IP Address | Fixed IP Address | Server ID | Pool | + +----+---------------------+------------------+-----------+--------+ + | 1 | 172.24.4.1 | None | None | public | + +----+---------------------+------------------+-----------+--------+ + + When using Network v2, the output of `ip floating list` command is: + +--------------------------------------+---------------------+------------------+------+ + | ID | Floating IP Address | Fixed IP Address | Port | + +--------------------------------------+---------------------+------------------+------+ + | 1976df86-e66a-4f96-81bd-c6ffee6407f1 | 172.24.4.3 | None | None | + +--------------------------------------+---------------------+------------------+------+ + which is different from Compute v2. + + * In favor of: Use `ip floating list` command + * As of: NA + * Removed in: NA + * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1519502 + * Commit: https://review.openstack.org/#/c/277720/ + For Developers ============== diff --git a/releasenotes/notes/bug-1519502-f72236598d14d350.yaml b/releasenotes/notes/bug-1519502-f72236598d14d350.yaml index 73ead7f427..1ace59bd66 100644 --- a/releasenotes/notes/bug-1519502-f72236598d14d350.yaml +++ b/releasenotes/notes/bug-1519502-f72236598d14d350.yaml @@ -6,3 +6,7 @@ features: [Bug `1519502 `_] - Add command ``ip floating show`` for neutron and nova network. [Bug `1519502 `_] +upgrade: + - Output of command ``ip floating list`` for nova network has been changed. + And it is different from the output of neutron network. + [Ref ``_] From a7c76878da02da406c9ccbcd62cc40def1108faa Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Fri, 11 Mar 2016 08:11:12 -0600 Subject: [PATCH 0720/3095] Add project options to security group create Add the --project and --project-domain options to the 'os security group create' command. These options are for Network v2 only. Change-Id: I9e1667080a1a49389d51ade2e76a08b08a09870b Closes-Bug: #1519511 Implements: blueprint neutron-client --- doc/source/command-objects/security-group.rst | 14 +++++++ openstackclient/network/v2/security_group.py | 21 ++++++++++ .../tests/network/v2/test_security_group.py | 41 +++++++++++++++++++ .../notes/bug-1519511-65d8d21dde31e5e2.yaml | 5 +++ 4 files changed, 81 insertions(+) create mode 100644 releasenotes/notes/bug-1519511-65d8d21dde31e5e2.yaml diff --git a/doc/source/command-objects/security-group.rst b/doc/source/command-objects/security-group.rst index 22ea5cb78c..9fc4c987ba 100644 --- a/doc/source/command-objects/security-group.rst +++ b/doc/source/command-objects/security-group.rst @@ -14,12 +14,26 @@ Create a new security group os security group create [--description ] + [--project [--project-domain ]] .. option:: --description Security group description +.. option:: --project + + Owner's project (name or ID) + + *Network version 2 only* + +.. option:: --project-domain + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + + *Network version 2 only* + .. describe:: New security group name diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py index f8162477b8..92498144a5 100644 --- a/openstackclient/network/v2/security_group.py +++ b/openstackclient/network/v2/security_group.py @@ -17,6 +17,7 @@ import six from openstackclient.common import utils +from openstackclient.identity import common as identity_common from openstackclient.network import common from openstackclient.network import utils as network_utils @@ -107,6 +108,15 @@ def update_parser_common(self, parser): ) return parser + def update_parser_network(self, parser): + parser.add_argument( + '--project', + metavar='', + help="Owner's project (name or ID)" + ) + identity_common.add_project_domain_option_to_parser(parser) + return parser + def _get_description(self, parsed_args): if parsed_args.description is not None: return parsed_args.description @@ -114,9 +124,20 @@ def _get_description(self, parsed_args): return parsed_args.name def take_action_network(self, client, parsed_args): + # Build the create attributes. attrs = {} attrs['name'] = parsed_args.name attrs['description'] = self._get_description(parsed_args) + if parsed_args.project is not None: + identity_client = self.app.client_manager.identity + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + attrs['tenant_id'] = project_id + + # Create the security group and display the results. obj = client.create_security_group(**attrs) display_columns, property_columns = _get_columns(obj) data = utils.get_item_properties( diff --git a/openstackclient/tests/network/v2/test_security_group.py b/openstackclient/tests/network/v2/test_security_group.py index 2d43d87218..dd6a3d416c 100644 --- a/openstackclient/tests/network/v2/test_security_group.py +++ b/openstackclient/tests/network/v2/test_security_group.py @@ -11,10 +11,13 @@ # under the License. # +import copy import mock from openstackclient.network.v2 import security_group from openstackclient.tests.compute.v2 import fakes as compute_fakes +from openstackclient.tests import fakes +from openstackclient.tests.identity.v3 import fakes as identity_fakes from openstackclient.tests.network.v2 import fakes as network_fakes from openstackclient.tests import utils as tests_utils @@ -65,6 +68,30 @@ def setUp(self): self.network.create_security_group = mock.Mock( return_value=self._security_group) + # Set identity client v3. And get a shortcut to Identity client. + identity_client = identity_fakes.FakeIdentityv3Client( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN, + ) + self.app.client_manager.identity = identity_client + self.identity = self.app.client_manager.identity + + # Get a shortcut to the ProjectManager Mock + self.projects_mock = self.identity.projects + self.projects_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.PROJECT), + loaded=True, + ) + + # Get a shortcut to the DomainManager Mock + self.domains_mock = self.identity.domains + self.domains_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.DOMAIN), + loaded=True, + ) + # Get the command object to test self.cmd = security_group.CreateSecurityGroup(self.app, self.namespace) @@ -93,11 +120,15 @@ def test_create_min_options(self): def test_create_all_options(self): arglist = [ '--description', self._security_group.description, + '--project', identity_fakes.project_name, + '--project-domain', identity_fakes.domain_name, self._security_group.name, ] verifylist = [ ('description', self._security_group.description), ('name', self._security_group.name), + ('project', identity_fakes.project_name), + ('project_domain', identity_fakes.domain_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -106,6 +137,7 @@ def test_create_all_options(self): self.network.create_security_group.assert_called_once_with(**{ 'description': self._security_group.description, 'name': self._security_group.name, + 'tenant_id': identity_fakes.project_id, }) self.assertEqual(tuple(self.columns), columns) self.assertEqual(self.data, data) @@ -147,6 +179,15 @@ def test_create_no_options(self): self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, [], []) + def test_create_network_options(self): + arglist = [ + '--project', identity_fakes.project_name, + '--project-domain', identity_fakes.domain_name, + self._security_group.name, + ] + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, arglist, []) + def test_create_min_options(self): arglist = [ self._security_group.name, diff --git a/releasenotes/notes/bug-1519511-65d8d21dde31e5e2.yaml b/releasenotes/notes/bug-1519511-65d8d21dde31e5e2.yaml new file mode 100644 index 0000000000..8800db80d8 --- /dev/null +++ b/releasenotes/notes/bug-1519511-65d8d21dde31e5e2.yaml @@ -0,0 +1,5 @@ +--- +features: + - Add ``--project`` and ``--project-domain`` options to the + ``security group create`` command for Network v2. + [Bug `1519511 `_] From 3d6b072111385791f0a46e0694217658d09f8c3d Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 9 Mar 2016 11:47:28 -0600 Subject: [PATCH 0721/3095] Add doc describing how to handle API errors Include the following scenarios: * general external errors * a command with multiple API calls Change-Id: Ie5c4b775e11898bacf2156a34457f5397fd2c891 --- doc/source/command-errors.rst | 163 ++++++++++++++++++++++++++++++++++ doc/source/index.rst | 1 + 2 files changed, 164 insertions(+) create mode 100644 doc/source/command-errors.rst diff --git a/doc/source/command-errors.rst b/doc/source/command-errors.rst new file mode 100644 index 0000000000..35d7f9d072 --- /dev/null +++ b/doc/source/command-errors.rst @@ -0,0 +1,163 @@ +============== +Command Errors +============== + +Handling errors in OpenStackClient commands is fairly straightforward. An +exception is thrown and handled by the application-level caller. + +Note: There are many cases that need to be filled out here. The initial +version of this document considers the general command error handling as well +as the specific case of commands that make multiple REST API calls and how to +handle when one or more of those calls fails. + +General Command Errors +====================== + +The general pattern for handling OpenStackClient command-level errors is to +raise a CommandError exception with an appropriate message. This should include +conditions arising from arguments that are not valid/allowed (that are not otherwise +enforced by ``argparse``) as well as errors arising from external conditions. + +External Errors +--------------- + +External errors are a result of things outside OpenStackClient not being as +expected. + +Example +~~~~~~~ + +This example is taken from ``keypair create`` where the ``--public-key`` option +specifies a file containing the public key to upload. If the file is not found, +the IOError exception is trapped and a more specific CommandError exception is +raised that includes the name of the file that was attempted to be opened. + +.. code-block:: python + + class CreateKeypair(command.ShowOne): + """Create new public key""" + + ## ... + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + + public_key = parsed_args.public_key + if public_key: + try: + with io.open( + os.path.expanduser(parsed_args.public_key), + "rb" + ) as p: + public_key = p.read() + except IOError as e: + msg = "Key file %s not found: %s" + raise exceptions.CommandError( + msg % (parsed_args.public_key, e), + ) + + keypair = compute_client.keypairs.create( + parsed_args.name, + public_key=public_key, + ) + + ## ... + +REST API Errors +=============== + +Most commands make a single REST API call via the supporting client library +or SDK. Errors based on HTML return codes are usually handled well by default, +but in some cases more specific or user-friendly messages need to be logged. +Trapping the exception and raising a CommandError exception with a useful +message is the correct approach. + +Multiple REST API Calls +----------------------- + +Some CLI commands make multiple calls to library APIs and thus REST APIs. +Most of the time these are ``create`` or ``set`` commands that expect to add or +change a resource on the server. When one of these calls fails, the behaviour +of the remainder of the command handler is defined as such: + +* Whenever possible, all API calls will be made. This may not be possible for + specific commands where the subsequent calls are dependent on the results of + an earlier call. + +* Any failure of an API call will be logged for the user + +* A failure of any API call results in a non-zero exit code + +* In the cases of failures in a ``create`` command a follow-up mode needs to + be present that allows the user to attempt to complete the call, or cleanly + remove the partially-created resource and re-try. + +The desired behaviour is for commands to appear to the user as idempotent +whenever possible, i.e. a partial failure in a ``set`` command can be safely +retried without harm. ``create`` commands are a harder problem and may need +to be handled by having the proper options in a set command available to allow +recovery in the case where the primary resource has been created but the +subsequent calls did not complete. + +Example +~~~~~~~ + +This example is taken from the ``volume snapshot set`` command where ``--property`` +arguments are set using the volume manager's ``set_metadata()`` method, +``--state`` arguments are set using the ``reset_state()`` method, and the +remaining arguments are set using the ``update()`` method. + +.. code-block:: python + + class SetSnapshot(command.Command): + """Set snapshot properties""" + + ## ... + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + snapshot = utils.find_resource( + volume_client.volume_snapshots, + parsed_args.snapshot, + ) + + kwargs = {} + if parsed_args.name: + kwargs['name'] = parsed_args.name + if parsed_args.description: + kwargs['description'] = parsed_args.description + + result = 0 + if parsed_args.property: + try: + volume_client.volume_snapshots.set_metadata( + snapshot.id, + parsed_args.property, + ) + except SomeException: # Need to define the exceptions to catch here + self.app.log.error("Property set failed") + result += 1 + + if parsed_args.state: + try: + volume_client.volume_snapshots.reset_state( + snapshot.id, + parsed_args.state, + ) + except SomeException: # Need to define the exceptions to catch here + self.app.log.error("State set failed") + result += 1 + + try: + volume_client.volume_snapshots.update( + snapshot.id, + **kwargs + ) + except SomeException: # Need to define the exceptions to catch here + self.app.log.error("Update failed") + result += 1 + + # NOTE(dtroyer): We need to signal the error, and a non-zero return code, + # without aborting prematurely + if result > 0: + raise SomeNonFatalException diff --git a/doc/source/index.rst b/doc/source/index.rst index b1cc0564cb..bfa16a72e3 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -49,6 +49,7 @@ Developer Documentation developing command-options command-wrappers + command-errors specs/commands Project Goals From dc7e4fc15d80eed5a814f0b87e1860f0cd86c2ee Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 12 Mar 2016 10:58:28 +0800 Subject: [PATCH 0722/3095] Fix dict.keys() compatibility for python 3 In Python 2, dict.keys() will return a list. But in Python 3, it will return an iterator. So we need to fix all the places that assuming dict.keys() is a list. Change-Id: I8d1cc536377b3e5c644cfaa0892e40d0bd7c11b1 Closes-Bug: #1556350 --- openstackclient/common/commandmanager.py | 2 +- openstackclient/common/exceptions.py | 2 +- openstackclient/common/utils.py | 2 +- openstackclient/network/v2/floating_ip.py | 2 +- openstackclient/network/v2/network.py | 2 +- openstackclient/network/v2/port.py | 2 +- openstackclient/network/v2/router.py | 2 +- openstackclient/network/v2/security_group_rule.py | 2 +- openstackclient/network/v2/subnet.py | 2 +- openstackclient/network/v2/subnet_pool.py | 2 +- openstackclient/shell.py | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/openstackclient/common/commandmanager.py b/openstackclient/common/commandmanager.py index b809d63aea..c190e33e0d 100644 --- a/openstackclient/common/commandmanager.py +++ b/openstackclient/common/commandmanager.py @@ -56,4 +56,4 @@ def get_command_names(self, group=None): ) group_list.append(cmd_name) return group_list - return self.commands.keys() + return list(self.commands.keys()) diff --git a/openstackclient/common/exceptions.py b/openstackclient/common/exceptions.py index 5f5f5ab1a6..ee0f7a1104 100644 --- a/openstackclient/common/exceptions.py +++ b/openstackclient/common/exceptions.py @@ -122,7 +122,7 @@ def from_response(response, body): cls = _code_map.get(response.status, ClientException) if body: if hasattr(body, 'keys'): - error = body[body.keys()[0]] + error = body[list(body.keys())[0]] message = error.get('message') details = error.get('details') else: diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index 840da40218..c6ed6a716e 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -281,7 +281,7 @@ def get_client_class(api_name, version, version_map): client_path = version_map[str(version)] except (KeyError, ValueError): msg = "Invalid %s client version '%s'. must be one of: %s" % ( - (api_name, version, ', '.join(version_map.keys()))) + (api_name, version, ', '.join(list(version_map.keys())))) raise exceptions.UnsupportedVersion(msg) return importutils.import_class(client_path) diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index e0a65a481f..16f2b57472 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -18,7 +18,7 @@ def _get_columns(item): - columns = item.keys() + columns = list(item.keys()) if 'tenant_id' in columns: columns.remove('tenant_id') columns.append('project_id') diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 612a17750d..074b27543d 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -38,7 +38,7 @@ def _format_router_external(item): def _get_columns(item): - columns = item.keys() + columns = list(item.keys()) if 'tenant_id' in columns: columns.remove('tenant_id') columns.append('project_id') diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index b618a4b0c7..241699ae4d 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -36,7 +36,7 @@ def _format_admin_state(state): def _get_columns(item): - columns = item.keys() + columns = list(item.keys()) if 'tenant_id' in columns: columns.remove('tenant_id') columns.append('project_id') diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 96aa55b2f1..394311112e 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -42,7 +42,7 @@ def _format_external_gateway_info(info): def _get_columns(item): - columns = item.keys() + columns = list(item.keys()) if 'tenant_id' in columns: columns.remove('tenant_id') columns.append('project_id') diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index 92f28cce77..9309b326a1 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -27,7 +27,7 @@ def _format_security_group_rule_show(obj): def _get_columns(item): - columns = item.keys() + columns = list(item.keys()) if 'tenant_id' in columns: columns.remove('tenant_id') columns.append('project_id') diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index b514a88f7d..9e53ee84d2 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -31,7 +31,7 @@ def _format_allocation_pools(data): def _get_columns(item): - columns = item.keys() + columns = list(item.keys()) if 'tenant_id' in columns: columns.remove('tenant_id') columns.append('project_id') diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index 44f30207a1..d0d8d058c8 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -20,7 +20,7 @@ def _get_columns(item): - columns = item.keys() + columns = list(item.keys()) if 'tenant_id' in columns: columns.remove('tenant_id') columns.append('project_id') diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 53e9be08bb..7750f2a391 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -355,7 +355,7 @@ def initialize_app(self, argv): self.log.warning( "%s version %s is not in supported versions %s" % (api, version_opt, - ', '.join(mod.API_VERSIONS.keys()))) + ', '.join(list(mod.API_VERSIONS.keys())))) # Command groups deal only with major versions version = '.v' + version_opt.replace('.', '_').split('_')[0] From 6abed7a471c5df3da1f455d84c50ab425448336c Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Mon, 14 Mar 2016 14:49:17 +0800 Subject: [PATCH 0723/3095] Trivial: Add release note for "subnet pool create" command Change-Id: I45c4304ca2100db9f0ef8f82ac69368f6798495d Partial-Bug: #1544586 --- releasenotes/notes/bug-1544586-0e6ca9a09dac0726.yaml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 releasenotes/notes/bug-1544586-0e6ca9a09dac0726.yaml diff --git a/releasenotes/notes/bug-1544586-0e6ca9a09dac0726.yaml b/releasenotes/notes/bug-1544586-0e6ca9a09dac0726.yaml new file mode 100644 index 0000000000..e1595ed374 --- /dev/null +++ b/releasenotes/notes/bug-1544586-0e6ca9a09dac0726.yaml @@ -0,0 +1,4 @@ +--- +features: + - Add ``subnet pool create`` command. + [Bug `1544586 `_] From 7b2e3c7d1cbe6e55de4bf1d51cb4cc0862c60d44 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Mon, 14 Mar 2016 16:22:24 +0800 Subject: [PATCH 0724/3095] Use assertItemsEqual() instead of assertListEqual() assertListEqual() is order sensitive. So we need to sort the lists before we compare them. Use assertItemsEqual() instead is better. Change-Id: I9eaa98716c7401f5b099b007438acc916dae619b --- openstackclient/tests/common/test_parseractions.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/openstackclient/tests/common/test_parseractions.py b/openstackclient/tests/common/test_parseractions.py index a4ee07bf43..5c5ca3d321 100644 --- a/openstackclient/tests/common/test_parseractions.py +++ b/openstackclient/tests/common/test_parseractions.py @@ -91,11 +91,7 @@ def test_good_values(self): {'req1': 'aaa', 'req2': 'bbb'}, {'req1': '', 'req2': ''}, ] - # Need to sort the lists before comparing them - key = lambda x: x['req1'] - expect.sort(key=key) - actual.sort(key=key) - self.assertListEqual(expect, actual) + self.assertItemsEqual(expect, actual) def test_empty_required_optional(self): self.parser.add_argument( @@ -119,11 +115,7 @@ def test_empty_required_optional(self): {'req1': 'aaa', 'req2': 'bbb'}, {'req1': '', 'req2': ''}, ] - # Need to sort the lists before comparing them - key = lambda x: x['req1'] - expect.sort(key=key) - actual.sort(key=key) - self.assertListEqual(expect, actual) + self.assertItemsEqual(expect, actual) def test_error_values_with_comma(self): self.assertRaises( From 8664a2f8aecd3d91a929e7e2b76772cae20f41e1 Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Mon, 14 Mar 2016 18:07:23 +0800 Subject: [PATCH 0725/3095] Support "--long" option in ListService Add "--long" option in ListService so that compute service disabled reason can be showed. Change-Id: I1ace8f1c4e4efe0a1a8f6710425d73eb5db9e5e1 Closes-Bug: #1556815 --- .../command-objects/compute-service.rst | 5 +++ openstackclient/compute/v2/service.py | 36 ++++++++++++++----- openstackclient/tests/compute/v2/fakes.py | 2 ++ .../tests/compute/v2/test_service.py | 26 +++++++++++++- 4 files changed, 59 insertions(+), 10 deletions(-) diff --git a/doc/source/command-objects/compute-service.rst b/doc/source/command-objects/compute-service.rst index 25a133d97c..aefe55db7a 100644 --- a/doc/source/command-objects/compute-service.rst +++ b/doc/source/command-objects/compute-service.rst @@ -31,6 +31,7 @@ List service command os compute service list [--host ] [--service ] + [--long] .. _compute-service-list: .. describe:: --host @@ -41,6 +42,10 @@ List service command Name of service +.. describe:: --long + + List additional fields in output + compute service set ------------------- diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index 1cc91711eb..89f5cad94f 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.py @@ -49,19 +49,37 @@ def get_parser(self, prog_name): "--service", metavar="", help="Name of service") + parser.add_argument( + "--long", + action="store_true", + default=False, + help="List additional fields in output" + ) return parser def take_action(self, parsed_args): compute_client = self.app.client_manager.compute - columns = ( - "Id", - "Binary", - "Host", - "Zone", - "Status", - "State", - "Updated At" - ) + if parsed_args.long: + columns = ( + "Id", + "Binary", + "Host", + "Zone", + "Status", + "State", + "Updated At", + "Disabled Reason" + ) + else: + columns = ( + "Id", + "Binary", + "Host", + "Zone", + "Status", + "State", + "Updated At" + ) data = compute_client.services.list(parsed_args.host, parsed_args.service) return (columns, diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 26d3a28309..ccd8cc6bcc 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -79,10 +79,12 @@ service_host = 'host_test' service_binary = 'compute_test' service_status = 'enabled' +service_disabled_reason = 'earthquake' SERVICE = { 'host': service_host, 'binary': service_binary, 'status': service_status, + 'disabled_reason': service_disabled_reason, } diff --git a/openstackclient/tests/compute/v2/test_service.py b/openstackclient/tests/compute/v2/test_service.py index 0246fbc86b..2feaf1566f 100644 --- a/openstackclient/tests/compute/v2/test_service.py +++ b/openstackclient/tests/compute/v2/test_service.py @@ -85,13 +85,37 @@ def test_service_list(self): # In base command class Lister in cliff, abstract method take_action() # returns a tuple containing the column names and an iterable # containing the data to be listed. - self.cmd.take_action(parsed_args) + columns, data = self.cmd.take_action(parsed_args) self.service_mock.list.assert_called_with( compute_fakes.service_host, compute_fakes.service_binary, ) + self.assertNotIn("Disabled Reason", columns) + self.assertNotIn(compute_fakes.service_disabled_reason, list(data)[0]) + + def test_service_list_with_long_option(self): + arglist = [ + '--host', compute_fakes.service_host, + '--service', compute_fakes.service_binary, + '--long' + ] + verifylist = [ + ('host', compute_fakes.service_host), + ('service', compute_fakes.service_binary), + ('long', True) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + self.assertIn("Disabled Reason", columns) + self.assertIn(compute_fakes.service_disabled_reason, list(data)[0]) + class TestServiceSet(TestService): From 1f021427defd405afff7508905a45e6ebd6a5098 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Mon, 14 Mar 2016 13:39:22 -0500 Subject: [PATCH 0726/3095] Doc: Fix documentation errors for command object Fix documentation errors impacting the "os ip floating", "os subnet pool" and "os configuration show" commands. Change-Id: Id033416df7ed06ef4b8a89e4f486fc9d546d9caf --- doc/source/command-objects/ip-floating.rst | 4 ++-- doc/source/command-objects/subnet-pool.rst | 4 ++-- doc/source/configuration.rst | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/source/command-objects/ip-floating.rst b/doc/source/command-objects/ip-floating.rst index 99d06d071b..05e4a13243 100644 --- a/doc/source/command-objects/ip-floating.rst +++ b/doc/source/command-objects/ip-floating.rst @@ -45,7 +45,7 @@ ip floating delete Delete floating IP .. program:: ip floating delete - .. code:: bash +.. code:: bash os ip floating delete @@ -89,7 +89,7 @@ ip floating show Display floating IP details .. program:: ip floating show - .. code:: bash +.. code:: bash os ip floating show diff --git a/doc/source/command-objects/subnet-pool.rst b/doc/source/command-objects/subnet-pool.rst index 722f92997d..439c4f5033 100644 --- a/doc/source/command-objects/subnet-pool.rst +++ b/doc/source/command-objects/subnet-pool.rst @@ -10,7 +10,7 @@ subnet pool create Create subnet pool .. program:: subnet pool create - .. code:: bash +.. code:: bash os subnet pool create [--pool-prefix [...]] @@ -78,7 +78,7 @@ subnet pool set Set subnet pool properties .. program:: subnet pool set - .. code:: bash +.. code:: bash os subnet pool set [--name ] diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst index c770014091..d80b3b3634 100644 --- a/doc/source/configuration.rst +++ b/doc/source/configuration.rst @@ -140,8 +140,8 @@ that appears in :file:`clouds.yaml` Debugging ~~~~~~~~~ -You may find the :doc:`config show ` -helpful to debug configuration issues. It will display your current +You may find the :doc:`configuration show ` +command helpful to debug configuration issues. It will display your current configuration. Logging Settings From 8c3138390af7c0a3b8272f7ebef67575f5083f34 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Mon, 14 Mar 2016 15:20:42 -0500 Subject: [PATCH 0727/3095] Sort commands in docs No changes are intended other than sorting the sections... * image add/remove project * flavor set Change-Id: I1ac240e12889227f2f470b9167904ce35609a227 --- doc/source/command-objects/flavor.rst | 32 ++++---- doc/source/command-objects/image.rst | 108 +++++++++++++------------- 2 files changed, 70 insertions(+), 70 deletions(-) diff --git a/doc/source/command-objects/flavor.rst b/doc/source/command-objects/flavor.rst index 2389f7dc94..5893ff07a5 100644 --- a/doc/source/command-objects/flavor.rst +++ b/doc/source/command-objects/flavor.rst @@ -118,22 +118,6 @@ List flavors Maximum number of flavors to display -flavor show ------------ - -Display flavor details - -.. program:: flavor show -.. code:: bash - - os flavor show - - -.. _flavor_show-flavor: -.. describe:: - - Flavor to display (name or ID) - flavor set ---------- @@ -154,6 +138,22 @@ Set flavor properties Flavor to modify (name or ID) +flavor show +----------- + +Display flavor details + +.. program:: flavor show +.. code:: bash + + os flavor show + + +.. _flavor_show-flavor: +.. describe:: + + Flavor to display (name or ID) + flavor unset ------------ diff --git a/doc/source/command-objects/image.rst b/doc/source/command-objects/image.rst index 61872ec404..942c03d57c 100644 --- a/doc/source/command-objects/image.rst +++ b/doc/source/command-objects/image.rst @@ -4,6 +4,33 @@ image Image v1, v2 +image add project +----------------- + +*Only supported for Image v2* + +Associate project with image + +.. program:: image add project +.. code:: bash + + os image add project + [--project-domain ] + + +.. option:: --project-domain + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + +.. describe:: + + Image to share (name or ID). + +.. describe:: + + Project to associate with image (name or ID) + image create ------------ @@ -206,6 +233,33 @@ List available images The last image (name or ID) of the previous page. Display list of images after marker. Display all images if not specified. +image remove project +-------------------- + +*Only supported for Image v2* + +Disassociate project with image + +.. program:: image remove project +.. code:: bash + + os image remove remove + [--project-domain ] + + +.. option:: --project-domain + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + +.. describe:: + + Image to unshare (name or ID). + +.. describe:: + + Project to disassociate with image (name or ID) + image save ---------- @@ -445,57 +499,3 @@ Display image details .. describe:: Image to display (name or ID) - -image add project ------------------ - -*Only supported for Image v2* - -Associate project with image - -.. program:: image add project -.. code:: bash - - os image add project - [--project-domain ] - - -.. option:: --project-domain - - Domain the project belongs to (name or ID). - This can be used in case collisions between project names exist. - -.. describe:: - - Image to share (name or ID). - -.. describe:: - - Project to associate with image (name or ID) - -image remove project --------------------- - -*Only supported for Image v2* - -Disassociate project with image - -.. program:: image remove project -.. code:: bash - - os image remove remove - [--project-domain ] - - -.. option:: --project-domain - - Domain the project belongs to (name or ID). - This can be used in case collisions between project names exist. - -.. describe:: - - Image to unshare (name or ID). - -.. describe:: - - Project to disassociate with image (name or ID) From 4d5c5d9dcb6421e56823aad932bff5d87b728bb2 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 9 Mar 2016 20:59:40 +0530 Subject: [PATCH 0728/3095] Add support for setting Image-property OSC does not support to set volume's image property. This patch will provide support for adding image property to existing volume. Closes-Bug:#1554877 Implements: bp cinder-command-support Change-Id: I4ff5532c228f010789b81c7587dd4a2838a90f20 --- doc/source/command-objects/volume.rst | 8 +++++ .../tests/volume/v2/test_volume.py | 30 +++++++++++++++++++ openstackclient/volume/v2/volume.py | 13 +++++++- .../image-property_add-7f8479791eab45b7.yaml | 14 +++++++++ 4 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/image-property_add-7f8479791eab45b7.yaml diff --git a/doc/source/command-objects/volume.rst b/doc/source/command-objects/volume.rst index bf89dc44e0..f51b5ae19d 100644 --- a/doc/source/command-objects/volume.rst +++ b/doc/source/command-objects/volume.rst @@ -164,6 +164,7 @@ Set volume properties [--description ] [--size ] [--property [...] ] + [--image-property [...] ] .. option:: --name @@ -182,6 +183,13 @@ Set volume properties Property to add or modify for this volume (repeat option to set multiple properties) +.. option:: --image-property + + To add or modify image properties for this volume. + (repeat option to set multiple image properties) + + *Volume version 2 only* + .. describe:: Volume to modify (name or ID) diff --git a/openstackclient/tests/volume/v2/test_volume.py b/openstackclient/tests/volume/v2/test_volume.py index a836f79ec6..29fc391ee9 100644 --- a/openstackclient/tests/volume/v2/test_volume.py +++ b/openstackclient/tests/volume/v2/test_volume.py @@ -753,3 +753,33 @@ def test_volume_show(self): self.assertEqual(volume_fakes.VOLUME_columns, columns) self.assertEqual(volume_fakes.VOLUME_data, data) + + +class TestVolumeSet(TestVolume): + + def setUp(self): + super(TestVolumeSet, self).setUp() + + self.new_volume = volume_fakes.FakeVolume.create_one_volume() + self.volumes_mock.create.return_value = self.new_volume + + # Get the command object to test + self.cmd = volume.SetVolume(self.app, None) + + def test_volume_set_image_property(self): + arglist = [ + '--image-property', 'Alpha=a', + '--image-property', 'Beta=b', + self.new_volume.id, + ] + verifylist = [ + ('image_property', {'Alpha': 'a', 'Beta': 'b'}), + ('volume', self.new_volume.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class ShowOne in cliff, abstract method take_action() + # returns nothing + self.cmd.take_action(parsed_args) + self.volumes_mock.set_image_metadata.assert_called_with( + self.volumes_mock.get().id, parsed_args.image_property) diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index 8f2122eb83..9b58d73daf 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -345,6 +345,13 @@ def get_parser(self, prog_name): help='Property to add or modify for this volume ' '(repeat option to set multiple properties)', ) + parser.add_argument( + '--image-property', + metavar='', + action=parseractions.KeyValueAction, + help='To add or modify image properties for this volume ' + '(repeat option to set multiple image properties)', + ) return parser def take_action(self, parsed_args): @@ -365,6 +372,9 @@ def take_action(self, parsed_args): if parsed_args.property: volume_client.volumes.set_metadata(volume.id, parsed_args.property) + if parsed_args.image_property: + volume_client.volumes.set_image_metadata( + volume.id, parsed_args.image_property) kwargs = {} if parsed_args.name: @@ -374,7 +384,8 @@ def take_action(self, parsed_args): if kwargs: volume_client.volumes.update(volume.id, **kwargs) - if not kwargs and not parsed_args.property and not parsed_args.size: + if (not kwargs and not parsed_args.property + and not parsed_args.image_property and not parsed_args.size): self.app.log.error("No changes requested\n") diff --git a/releasenotes/notes/image-property_add-7f8479791eab45b7.yaml b/releasenotes/notes/image-property_add-7f8479791eab45b7.yaml new file mode 100644 index 0000000000..724da45159 --- /dev/null +++ b/releasenotes/notes/image-property_add-7f8479791eab45b7.yaml @@ -0,0 +1,14 @@ +--- +fixes: + - | + Added support for setting volume image property. + + Image properties are copied from image when volume is created. + But since a volume is mutable, user sometime wants to update + image properties for volume. + + So, this fix enables user to update image properties of volume + using below command: + ``volume set --image-property ``. + + [Bug 'https://bugs.launchpad.net/python-openstackclient/+bug/1554877'_] From 9bafea555d6ef84817976621d40522172c5b351f Mon Sep 17 00:00:00 2001 From: root Date: Thu, 10 Mar 2016 09:30:47 +0530 Subject: [PATCH 0729/3095] Add support for deleting Image-property OSC does not support to delete volume's image property. This patch will provide support for deleting image property to existing volume. Closes-Bug:#1554879 Change-Id: I9256913948fae9e9a03fed173b826dfa918f78e9 Implements: bp cinder-command-support --- doc/source/command-objects/volume.rst | 6 +++ .../tests/volume/v2/test_volume.py | 53 +++++++++++++++++++ openstackclient/volume/v2/volume.py | 20 +++++-- ...mage-property-delete-118c6007eba8357a.yaml | 14 +++++ 4 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/image-property-delete-118c6007eba8357a.yaml diff --git a/doc/source/command-objects/volume.rst b/doc/source/command-objects/volume.rst index f51b5ae19d..fd32c3278f 100644 --- a/doc/source/command-objects/volume.rst +++ b/doc/source/command-objects/volume.rst @@ -219,12 +219,18 @@ Unset volume properties os volume unset [--property ] + [--image-property ] .. option:: --property Property to remove from volume (repeat option to remove multiple properties) +.. option:: --image-property + + To remove image properties from volume + (repeat option to remove multiple image properties) + .. describe:: Volume to modify (name or ID) diff --git a/openstackclient/tests/volume/v2/test_volume.py b/openstackclient/tests/volume/v2/test_volume.py index 29fc391ee9..12253806b9 100644 --- a/openstackclient/tests/volume/v2/test_volume.py +++ b/openstackclient/tests/volume/v2/test_volume.py @@ -783,3 +783,56 @@ def test_volume_set_image_property(self): self.cmd.take_action(parsed_args) self.volumes_mock.set_image_metadata.assert_called_with( self.volumes_mock.get().id, parsed_args.image_property) + + +class TestVolumeUnset(TestVolume): + + def setUp(self): + super(TestVolumeUnset, self).setUp() + + self.new_volume = volume_fakes.FakeVolume.create_one_volume() + self.volumes_mock.create.return_value = self.new_volume + + # Get the command object to set property + self.cmd_set = volume.SetVolume(self.app, None) + + # Get the command object to unset property + self.cmd_unset = volume.UnsetVolume(self.app, None) + + def test_volume_unset_image_property(self): + + # Arguments for setting image properties + arglist = [ + '--image-property', 'Alpha=a', + '--image-property', 'Beta=b', + self.new_volume.id, + ] + verifylist = [ + ('image_property', {'Alpha': 'a', 'Beta': 'b'}), + ('volume', self.new_volume.id), + ] + parsed_args = self.check_parser(self.cmd_set, arglist, verifylist) + + # In base command class ShowOne in cliff, abstract method take_action() + # returns nothing + self.cmd_set.take_action(parsed_args) + + # Arguments for unsetting image properties + arglist_unset = [ + '--image-property', 'Alpha', + self.new_volume.id, + ] + verifylist_unset = [ + ('image_property', ['Alpha']), + ('volume', self.new_volume.id), + ] + parsed_args_unset = self.check_parser(self.cmd_unset, + arglist_unset, + verifylist_unset) + + # In base command class ShowOne in cliff, abstract method take_action() + # returns nothing + self.cmd_unset.take_action(parsed_args_unset) + + self.volumes_mock.delete_image_metadata.assert_called_with( + self.volumes_mock.get().id, parsed_args_unset.image_property) diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index 9b58d73daf..5d9d2d9e35 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -433,12 +433,17 @@ def get_parser(self, prog_name): parser.add_argument( '--property', metavar='', - required=True, action='append', - default=[], help='Property to remove from volume ' '(repeat option to remove multiple properties)', ) + parser.add_argument( + '--image-property', + metavar='', + action='append', + help='To remove image properties from volume ' + '(repeat option to remove multiple image properties)', + ) return parser def take_action(self, parsed_args): @@ -446,5 +451,12 @@ def take_action(self, parsed_args): volume = utils.find_resource( volume_client.volumes, parsed_args.volume) - volume_client.volumes.delete_metadata( - volume.id, parsed_args.property) + if parsed_args.property: + volume_client.volumes.delete_metadata( + volume.id, parsed_args.property) + if parsed_args.image_property: + volume_client.volumes.delete_image_metadata( + volume.id, parsed_args.image_property) + + if (not parsed_args.image_property and not parsed_args.property): + self.app.log.error("No changes requested\n") diff --git a/releasenotes/notes/image-property-delete-118c6007eba8357a.yaml b/releasenotes/notes/image-property-delete-118c6007eba8357a.yaml new file mode 100644 index 0000000000..deb1556799 --- /dev/null +++ b/releasenotes/notes/image-property-delete-118c6007eba8357a.yaml @@ -0,0 +1,14 @@ +--- +fixes: + - | + Added support for deleting volume image property. + + Image properties are copied from image when volume is created. + But since a volume is mutable, user sometime wants to delete + image properties for volume. + + So, this fix enables user to delete image properties of volume + using below command: + ``volume unset [--image-property ] ``. + + [Bug 'https://bugs.launchpad.net/python-openstackclient/+bug/1554879'_] From 62a02466c30e32f8eb54a70497eacacb7fa5c9bf Mon Sep 17 00:00:00 2001 From: Jas Date: Fri, 11 Mar 2016 13:02:47 -0600 Subject: [PATCH 0730/3095] Add option to allow filtering by router on port list Added support to allow filtering ports via --router option to list ports that are applicable to specific router. Partial-bug: #1519909 Partially-implements: blueprint neutron-client Change-Id: I6dd958603909f641735c821a62fc0d45afd5c7ec --- doc/source/command-objects/port.rst | 5 ++++ openstackclient/network/v2/port.py | 19 ++++++++++++++- openstackclient/tests/network/v2/test_port.py | 24 ++++++++++++++++++- 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/doc/source/command-objects/port.rst b/doc/source/command-objects/port.rst index e9c091736a..1910716294 100644 --- a/doc/source/command-objects/port.rst +++ b/doc/source/command-objects/port.rst @@ -108,6 +108,11 @@ List ports .. code:: bash os port list + [--router ] + +.. option:: --router + + List only ports attached to this router (name or ID) port set -------- diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index b618a4b0c7..9894e6da62 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -243,6 +243,16 @@ def take_action(self, parsed_args): class ListPort(command.Lister): """List ports""" + def get_parser(self, prog_name): + parser = super(ListPort, self).get_parser(prog_name) + parser.add_argument( + '--router', + metavar='', + dest='router', + help='List only ports attached to this router (name or ID)', + ) + return parser + def take_action(self, parsed_args): client = self.app.client_manager.network @@ -259,7 +269,14 @@ def take_action(self, parsed_args): 'Fixed IP Addresses', ) - data = client.ports() + filters = {} + if parsed_args.router: + _router = client.find_router(parsed_args.router, + ignore_missing=False) + filters = {'device_id': _router.id} + + data = client.ports(**filters) + return (column_headers, (utils.get_item_properties( s, columns, diff --git a/openstackclient/tests/network/v2/test_port.py b/openstackclient/tests/network/v2/test_port.py index 7b1c655f67..54f8285333 100644 --- a/openstackclient/tests/network/v2/test_port.py +++ b/openstackclient/tests/network/v2/test_port.py @@ -224,8 +224,11 @@ def setUp(self): # Get the command object to test self.cmd = port.ListPort(self.app, self.namespace) - self.network.ports = mock.Mock(return_value=self._ports) + fake_router = network_fakes.FakeRouter.create_one_router({ + 'id': 'fake-router-id', + }) + self.network.find_router = mock.Mock(return_value=fake_router) def test_port_list_no_options(self): arglist = [] @@ -239,6 +242,25 @@ def test_port_list_no_options(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + def test_port_list_router_opt(self): + arglist = [ + '--router', 'fake-router-name', + ] + + verifylist = [ + ('router', 'fake-router-name') + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.ports.assert_called_with(**{ + 'device_id': 'fake-router-id' + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + class TestSetPort(TestPort): From f70d9b876882d7c571f4fd16615afcec4259fb4d Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 16 Mar 2016 19:20:30 +0000 Subject: [PATCH 0731/3095] Updated from global requirements Change-Id: If9408d7057b7936427b4b6842318c583ebad828e --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 5db23369cb..20a2d5a9f0 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -20,7 +20,7 @@ osprofiler>=1.1.0 # Apache-2.0 # Install these to generate sphinx autodocs python-barbicanclient>=3.3.0 # Apache-2.0 -python-congressclient>=1.0.0 # Apache-2.0 +python-congressclient<2000,>=1.0.0 # Apache-2.0 python-designateclient>=1.5.0 # Apache-2.0 python-heatclient>=0.6.0 # Apache-2.0 python-ironicclient>=1.1.0 # Apache-2.0 From aeef56818941a72cc10e96669ad6ff317461046f Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 11 Mar 2016 15:42:17 -0600 Subject: [PATCH 0732/3095] Fix options in port create/set * --device-id should have been --device * --host-id should have been --host Old options are deprecated and retained for compatibility since they appear in a release. Closes-Bug: 1558677 Change-Id: Ic733523c8d57060f2cb5d420fdb1f7598e7d5e71 --- doc/source/command-objects/port.rst | 12 ++-- openstackclient/network/v2/port.py | 56 ++++++++++++++++--- openstackclient/tests/network/v2/test_port.py | 8 +-- .../notes/bug-1558677-a85f0c548306ba80.yaml | 7 +++ 4 files changed, 65 insertions(+), 18 deletions(-) create mode 100644 releasenotes/notes/bug-1558677-a85f0c548306ba80.yaml diff --git a/doc/source/command-objects/port.rst b/doc/source/command-objects/port.rst index e9c091736a..0ee6212b9c 100644 --- a/doc/source/command-objects/port.rst +++ b/doc/source/command-objects/port.rst @@ -15,11 +15,11 @@ Create new port os port create --network [--fixed-ip subnet=,ip-address=] - [--device-id ] + [--device ] [--device-owner ] [--vnic-type ] [--binding-profile ] - [--host-id ] + [--host ] [--enable | --disable] [--mac-address ] [--project [--project-domain ]] @@ -35,9 +35,9 @@ Create new port subnet=,ip-address= (this option can be repeated) -.. option:: --device-id +.. option:: --device - Device ID of this port + Port device ID .. option:: --device-owner @@ -53,9 +53,9 @@ Create new port Custom data to be passed as binding:profile: = (this option can be repeated) -.. option:: --host-id +.. option:: --host - The ID of the host where the port is allocated + Allocate port on host ```` (ID only) .. option:: --enable diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index b618a4b0c7..48e1cef583 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -13,13 +13,20 @@ """Port action implementations""" +import argparse +import logging + from openstackclient.common import command from openstackclient.common import exceptions from openstackclient.common import parseractions from openstackclient.common import utils +from openstackclient.i18n import _ # noqa from openstackclient.identity import common as identity_common +LOG = logging.getLogger(__name__) + + def _format_admin_state(state): return 'UP' if state else 'DOWN' @@ -57,10 +64,26 @@ def _get_columns(item): def _get_attrs(client_manager, parsed_args): attrs = {} + # Handle deprecated options + # NOTE(dtroyer): --device-id and --host-id were deprecated in Mar 2016. + # Do not remove before 3.x release or Mar 2017. + if parsed_args.device_id: + attrs['device_id'] = parsed_args.device_id + LOG.warning(_( + 'The --device-id option is deprecated, ' + 'please use --device instead.' + )) + if parsed_args.host_id: + attrs['binding:host_id'] = parsed_args.host_id + LOG.warning(_( + 'The --host-id option is deprecated, ' + 'please use --host instead.' + )) + if parsed_args.fixed_ip is not None: attrs['fixed_ips'] = parsed_args.fixed_ip - if parsed_args.device_id is not None: - attrs['device_id'] = parsed_args.device_id + if parsed_args.device: + attrs['device_id'] = parsed_args.device if parsed_args.device_owner is not None: attrs['device_owner'] = parsed_args.device_owner if parsed_args.admin_state is not None: @@ -69,8 +92,8 @@ def _get_attrs(client_manager, parsed_args): attrs['binding:profile'] = parsed_args.binding_profile if parsed_args.vnic_type is not None: attrs['binding:vnic_type'] = parsed_args.vnic_type - if parsed_args.host_id is not None: - attrs['binding:host_id'] = parsed_args.host_id + if parsed_args.host: + attrs['binding:host_id'] = parsed_args.host # The remaining options do not support 'port set' command, so they require # additional check @@ -133,10 +156,19 @@ def _add_updatable_args(parser): help='Desired IP and/or subnet (name or ID) for this port: ' 'subnet=,ip-address= ' '(this option can be repeated)') - parser.add_argument( + # NOTE(dtroyer): --device-id is deprecated in Mar 2016. Do not + # remove before 3.x release or Mar 2017. + device_group = parser.add_mutually_exclusive_group() + device_group.add_argument( + '--device', + metavar='', + help='Port device ID', + ) + device_group.add_argument( '--device-id', metavar='', - help='Device ID of this port') + help=argparse.SUPPRESS, + ) parser.add_argument( '--device-owner', metavar='', @@ -155,10 +187,18 @@ def _add_updatable_args(parser): action=parseractions.KeyValueAction, help='Custom data to be passed as binding:profile: = ' '(this option can be repeated)') - parser.add_argument( + # NOTE(dtroyer): --host-id is deprecated in Mar 2016. Do not + # remove before 3.x release or Mar 2017. + host_group = parser.add_mutually_exclusive_group() + host_group.add_argument( + '--host', + metavar='', + help='Allocate port on host (ID only)', + ) + host_group.add_argument( '--host-id', metavar='', - help='The ID of the host where the port is allocated' + help=argparse.SUPPRESS, ) diff --git a/openstackclient/tests/network/v2/test_port.py b/openstackclient/tests/network/v2/test_port.py index 7b1c655f67..ad4ec82401 100644 --- a/openstackclient/tests/network/v2/test_port.py +++ b/openstackclient/tests/network/v2/test_port.py @@ -125,7 +125,7 @@ def test_create_full_options(self): '--mac-address', 'aa:aa:aa:aa:aa:aa', '--fixed-ip', 'subnet=%s,ip-address=10.0.0.2' % self.fake_subnet.id, - '--device-id', 'deviceid', + '--device', 'deviceid', '--device-owner', 'fakeowner', '--disable', '--vnic-type', 'macvtap', @@ -141,7 +141,7 @@ def test_create_full_options(self): 'fixed_ip', [{'subnet': self.fake_subnet.id, 'ip-address': '10.0.0.2'}] ), - ('device_id', 'deviceid'), + ('device', 'deviceid'), ('device_owner', 'fakeowner'), ('admin_state', False), ('vnic_type', 'macvtap'), @@ -296,14 +296,14 @@ def test_set_that(self): '--enable', '--vnic-type', 'macvtap', '--binding-profile', 'foo=bar', - '--host-id', 'binding-host-id-xxxx', + '--host', 'binding-host-id-xxxx', self._port.name, ] verifylist = [ ('admin_state', True), ('vnic_type', 'macvtap'), ('binding_profile', {'foo': 'bar'}), - ('host_id', 'binding-host-id-xxxx'), + ('host', 'binding-host-id-xxxx'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/releasenotes/notes/bug-1558677-a85f0c548306ba80.yaml b/releasenotes/notes/bug-1558677-a85f0c548306ba80.yaml new file mode 100644 index 0000000000..15451698f0 --- /dev/null +++ b/releasenotes/notes/bug-1558677-a85f0c548306ba80.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - Change the ``--device-id`` option to ``--device`` and the ``--host-id`` + option to ``--host`` for the ``port create`` and ``pot set`` commands. + The original options are deprecated and maintained for backward compatibility + until at least March 2017. + [Bug `1558677 `_] From 92aa981fa8ad48133b568e345d4c79d3fbba947b Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Thu, 17 Mar 2016 18:32:06 -0400 Subject: [PATCH 0733/3095] update docs with status of plugins several other projects have joined the bandwagon, let's add them to the list, these include: python-ironic-inspector-client python-muranoclient python-senlinclient python-tripleoclient python-searchlightclient Change-Id: I9165daf64bf817b9e12ee8fc27ce6e11ebe01e5e --- doc/source/plugin-commands.rst | 20 ++++++++++++++ doc/source/plugins.rst | 49 +++++++++++++++++++--------------- test-requirements.txt | 4 +++ 3 files changed, 51 insertions(+), 22 deletions(-) diff --git a/doc/source/plugin-commands.rst b/doc/source/plugin-commands.rst index a3f4152158..b23e7a338e 100644 --- a/doc/source/plugin-commands.rst +++ b/doc/source/plugin-commands.rst @@ -33,4 +33,24 @@ Note: To see the complete syntax for the plugin commands, see the `CLI_Ref`_ .. list-plugins:: openstack.orchestration.v1 :detailed: +.. list-plugins:: openstack.search.v1 + :detailed: + +.. list-plugins:: openstack.baremetal_introspection.v1 + :detailed: + +.. list-plugins:: openstack.application_catalog.v1 + :detailed: + +.. list-plugins:: openstack.clustering.v1 + :detailed: + +.. # tripleoclient is not in global-requirements +.. #.. list-plugins:: openstack.tripleoclient.v1 +.. # :detailed: + +.. # cueclient is not in global-requirements +.. #.. list-plugins:: openstack.mb.v1 +.. # :detailed: + .. _CLI_Ref: http://docs.openstack.org/cli-reference/openstack.html \ No newline at end of file diff --git a/doc/source/plugins.rst b/doc/source/plugins.rst index f5bbd6dd61..7d19ed17fb 100644 --- a/doc/source/plugins.rst +++ b/doc/source/plugins.rst @@ -19,28 +19,33 @@ Other OpenStack services, such as Orchestration or Telemetry may create an OpenStackClient plugin. The source code will not be hosted by OpenStackClient. -The following is a list of projects and their status as an OpenStackClient -plugin. - -============================= ====================================== - project notes -============================= ====================================== -python-barbicanclient using OpenStackClient -python-ceilometerclient using argparse -python-congressclient using OpenStackClient -python-cueclient using OpenStackClient -python-designateclient using OpenStackClient -python-heatclient using OpenStackClient -python-ironicclient Using OpenStackClient -python-magnumclient using argparse -python-manilaclient using argparse -python-mistralclient using OpenStackClient -python-muranoclient using argparse -python-saharaclient using OpenStackClient -python-searchlightclient using OpenStackClient -python-troveclient using argparse -python-zaqarclient using OpenStackClient -============================= ====================================== +The following is a list of projects that are an OpenStackClient plugin. + +- python-barbicanclient +- python-congressclient +- python-cueclient\*\* +- python-designateclient +- python-heatclient +- python-ironicclient +- python-ironic-inspector-client +- python-mistralclient +- python-muranoclient +- python-saharaclient +- python-searchlightclient +- python-senlinclient +- python-tripleoclient\*\* +- python-zaqarclient + +\*\* Note that some clients are not listed in global-requirements + +The following is a list of projects that are not an OpenStackClient plugin. + +- aodhclient +- gnocchiclient +- python-troveclient +- python-magnumclient +- python-ceilometerclient +- python-solumclient Implementation ============== diff --git a/test-requirements.txt b/test-requirements.txt index 20a2d5a9f0..549ecc2345 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -24,6 +24,10 @@ python-congressclient<2000,>=1.0.0 # Apache-2.0 python-designateclient>=1.5.0 # Apache-2.0 python-heatclient>=0.6.0 # Apache-2.0 python-ironicclient>=1.1.0 # Apache-2.0 +python-ironic-inspector-client>=1.3.0 # Apache-2.0 python-mistralclient>=1.0.0 # Apache-2.0 +python-muranoclient>=0.8.2 # Apache-2.0 python-saharaclient>=0.13.0 # Apache-2.0 +python-searchlightclient>=0.2.0 #Apache-2.0 +python-senlinclient>=0.3.0 # Apache-2.0 python-zaqarclient>=0.3.0 # Apache-2.0 From 3ccc4f7d06234ad901c0090870b4ec7070caec50 Mon Sep 17 00:00:00 2001 From: reedip Date: Fri, 18 Mar 2016 15:27:29 +0900 Subject: [PATCH 0734/3095] Trivial-Fix : Add a ' to the choices documentation Change-Id: Ic2023e91602fa23512bc5cc9c6a395f2311adb1a --- doc/source/command-options.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/command-options.rst b/doc/source/command-options.rst index b723e988a5..b3cc002a48 100644 --- a/doc/source/command-options.rst +++ b/doc/source/command-options.rst @@ -100,7 +100,7 @@ An example parser declaration: choice_option.add_argument( '--test', - metavar=', + metavar='', choices=['choice1', 'choice2', 'choice3'], help=_('Test type (choice1, choice2 or choice3)'), ) From 89182c4825340211ab9fc8ee780d59fb96212528 Mon Sep 17 00:00:00 2001 From: Kyrylo Romanenko Date: Tue, 15 Mar 2016 18:49:25 +0200 Subject: [PATCH 0735/3095] Functional tests for openstackclient help messages Add tests for list and description for Server commands: - Check server commands in main help message. - Check list of server-related commands only. Commands: server add security group, server add volume, server create, server delete, server dump create, server image create, server list, server lock, server migrate, server pause, server reboot, server rebuild, server remove security group, server remove volume, server rescue, server resize, server resume, server set, server shelve, server show, server ssh, server start, server stop, server suspend, server unlock, server unpause, server unrescue, server unset, server unshelve. Change-Id: Ib4bf9ab0264fb482d36cf5688c0f939bcd2cb6d8 --- functional/tests/common/test_help.py | 65 ++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 functional/tests/common/test_help.py diff --git a/functional/tests/common/test_help.py b/functional/tests/common/test_help.py new file mode 100644 index 0000000000..fcce5f99bc --- /dev/null +++ b/functional/tests/common/test_help.py @@ -0,0 +1,65 @@ +# 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 functional.common import test + + +class HelpTests(test.TestCase): + """Functional tests for openstackclient help output.""" + + SERVER_COMMANDS = [ + ('server add security group', 'Add security group to server'), + ('server add volume', 'Add volume to server'), + ('server create', 'Create a new server'), + ('server delete', 'Delete server(s)'), + ('server dump create', 'Create a dump file in server(s)'), + ('server image create', + 'Create a new disk image from a running server'), + ('server list', 'List servers'), + ('server lock', + 'Lock server(s). ' + 'A non-admin user will not be able to execute actions'), + ('server migrate', 'Migrate server to different host'), + ('server pause', 'Pause server(s)'), + ('server reboot', 'Perform a hard or soft server reboot'), + ('server rebuild', 'Rebuild server'), + ('server remove security group', 'Remove security group from server'), + ('server remove volume', 'Remove volume from server'), + ('server rescue', 'Put server in rescue mode'), + ('server resize', 'Scale server to a new flavor'), + ('server resume', 'Resume server(s)'), + ('server set', 'Set server properties'), + ('server shelve', 'Shelve server(s)'), + ('server show', 'Show server details'), + ('server ssh', 'SSH to server'), + ('server start', 'Start server(s).'), + ('server stop', 'Stop server(s).'), + ('server suspend', 'Suspend server(s)'), + ('server unlock', 'Unlock server(s)'), + ('server unpause', 'Unpause server(s)'), + ('server unrescue', 'Restore server from rescue mode'), + ('server unset', 'Unset server properties'), + ('server unshelve', 'Unshelve server(s)') + ] + + def test_server_commands_main_help(self): + """Check server commands in main help message.""" + raw_output = self.openstack('help') + for command, description in self.SERVER_COMMANDS: + self.assertIn(command, raw_output) + self.assertIn(description, raw_output) + + def test_server_only_help(self): + """Check list of server-related commands only.""" + raw_output = self.openstack('help server') + for command in [row[0] for row in self.SERVER_COMMANDS]: + self.assertIn(command, raw_output) From 21e414d860347b080ba98fc023029caa16d686f4 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 18 Mar 2016 11:36:33 -0500 Subject: [PATCH 0736/3095] Fix keypair create --public-key Commit Id702ccaad239b916340bb17014d1ede0a28aaec9 changed the keypair create --public-key to use io.open but incorrectly reads the file in binary mode, which causes JSON serialization to fail. The unit tests mock out io.ioen (the reason for adding it in the first place actually) so any testing for this specific problem would have to be done in functional tests...yet to come. Closes-bug: 1559125 Change-Id: I7a299a542d9df543bff43d3ea1e7907fc8c5f640 --- openstackclient/compute/v2/keypair.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openstackclient/compute/v2/keypair.py b/openstackclient/compute/v2/keypair.py index 22d918a4b3..1db0f942c7 100644 --- a/openstackclient/compute/v2/keypair.py +++ b/openstackclient/compute/v2/keypair.py @@ -48,8 +48,7 @@ def take_action(self, parsed_args): public_key = parsed_args.public_key if public_key: try: - with io.open(os.path.expanduser(parsed_args.public_key), - "rb") as p: + with io.open(os.path.expanduser(parsed_args.public_key)) as p: public_key = p.read() except IOError as e: msg = "Key file %s not found: %s" From 91eeacd89ea9fefbbd98a1a45462debbf275d5f1 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Mon, 14 Mar 2016 13:12:03 -0500 Subject: [PATCH 0737/3095] Devref: Document OSC interfaces available to plugins This devref documents the OSC interfaces are officially available for plugins to implement commands and related unit tests. It also covers requirements for plugins. Change-Id: I68caa188e389e400fa9f5fd38f32c76cdd3e0986 --- doc/source/plugins.rst | 64 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/doc/source/plugins.rst b/doc/source/plugins.rst index f5bbd6dd61..2426506a2d 100644 --- a/doc/source/plugins.rst +++ b/doc/source/plugins.rst @@ -45,6 +45,9 @@ python-zaqarclient using OpenStackClient Implementation ============== +Client module +------------- + Plugins are discovered by enumerating the entry points found under :py:mod:`openstack.cli.extension` and initializing the specified client module. @@ -60,7 +63,9 @@ The client module must define the following top-level variables: * ``API_NAME`` - A string containing the plugin API name; this is the name of the entry point declaring the plugin client module (``oscplugin = ...`` in the example above) and the group name for - the plugin commands (``openstack.oscplugin.v1 =`` in the example below) + the plugin commands (``openstack.oscplugin.v1 =`` in the example below). + OSC reserves the following API names: ``compute``, ``identity``, + ``image``, ``network``, ``object_store`` and ``volume``. * ``API_VERSION_OPTION`` (optional) - If set, the name of the API version attribute; this must be a valid Python identifier and match the destination set in ``build_option_parser()``. @@ -85,6 +90,9 @@ so the version should not contain the leading 'v' character. .. code-block:: python + from openstackclient.common import utils + + DEFAULT_API_VERSION = '1' # Required by the OSC plugin interface @@ -130,6 +138,60 @@ so the version should not contain the leading 'v' character. ' (Env: OS_OSCPLUGIN_API_VERSION)') return parser +Client usage of OSC interfaces +------------------------------ + +OSC provides the following interfaces that may be used to implement +the plugin commands: + +.. code-block:: python + + # OSC common interfaces available to plugins: + from openstackclient.common import command + from openstackclient.common import exceptions + from openstackclient.common import parseractions + from openstackclient.common import logs + from openstackclient.common import utils + + + class DeleteMypluginobject(command.Command): + """Delete mypluginobject""" + + ... + + def take_action(self, parsed_args): + # Client manager interfaces are availble to plugins. + # This includes the OSC clients created. + client_manager = self.app.client_manager + + ... + + return + +OSC provides the following interfaces that may be used to implement +unit tests for the plugin commands: + +.. code-block:: python + + # OSC unit test interfaces available to plugins: + from openstackclient.tests import fakes + from openstackclient.tests import utils + + ... + +Requirements +------------ + +OSC must be included in ``requirements.txt`` or ``test-requirements.txt`` +for the plugin project. Update ``requirements.txt`` if the plugin project +considers the CLI a required feature. Update ``test-requirements.txt`` if +the plugin project can be installed as a library with the CLI being an +optional feature (available when OSC is also installed). + +.. code-block:: ini + + python-openstackclient>=X.Y.Z # Apache-2.0 + Checklist for adding new OpenStack plugins ========================================== From 15edb2f61a4d193c069dce903b29c391f04ba0b8 Mon Sep 17 00:00:00 2001 From: timothy-symanczyk Date: Fri, 18 Mar 2016 15:44:02 -0700 Subject: [PATCH 0738/3095] Improve error for token issue command without auth Currently when you perform the 'token issue' command with the admin_token, the error is a nonsense python error. This commit changes it to be user-friendly. Change-Id: I5cc92c342e3f83e099354cd04301c7b8d8d2dabc Closes-Bug: #1547721 --- openstackclient/identity/v3/token.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openstackclient/identity/v3/token.py b/openstackclient/identity/v3/token.py index bf039d2fe6..62a4c4a363 100644 --- a/openstackclient/identity/v3/token.py +++ b/openstackclient/identity/v3/token.py @@ -18,6 +18,7 @@ import six from openstackclient.common import command +from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.identity import common @@ -172,6 +173,9 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): + if not self.app.client_manager.auth_ref: + raise exceptions.AuthorizationFailure( + "Only an authorized user may issue a new token.") token = self.app.client_manager.auth_ref.service_catalog.get_token() if 'tenant_id' in token: token['project_id'] = token.pop('tenant_id') From 1d1c7a06311f49a8a1adfc2431871f686af18af4 Mon Sep 17 00:00:00 2001 From: Nicolas Simonds Date: Fri, 18 Mar 2016 16:04:07 -0700 Subject: [PATCH 0739/3095] Image API v2: make volume_type optional It is perfectly valid for a Cinder volume to have a volume_type of "none", so don't make the Image API v2 require that one be set. Change-Id: I1a6da8d791fa0ae67cac46eec81bcbcb420729c3 Closes-Bug: #1559344 --- openstackclient/image/v2/image.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 3f1621814c..40ddd4b9ce 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -325,7 +325,10 @@ def take_action(self, parsed_args): parsed_args.disk_format, ) info = body['os-volume_upload_image'] - info['volume_type'] = info['volume_type']['name'] + try: + info['volume_type'] = info['volume_type']['name'] + except TypeError: + info['volume_type'] = None else: image = image_client.images.create(**kwargs) From 236e74d46f9038ba2dc85e3edea00ee6d6caa1ff Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sun, 20 Mar 2016 15:47:37 +0000 Subject: [PATCH 0740/3095] Updated from global requirements Change-Id: Icaa101bded765c81774d73d84d552d41e40fec72 --- test-requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test-requirements.txt b/test-requirements.txt index 549ecc2345..39a881c01e 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -24,10 +24,10 @@ python-congressclient<2000,>=1.0.0 # Apache-2.0 python-designateclient>=1.5.0 # Apache-2.0 python-heatclient>=0.6.0 # Apache-2.0 python-ironicclient>=1.1.0 # Apache-2.0 -python-ironic-inspector-client>=1.3.0 # Apache-2.0 +python-ironic-inspector-client>=1.3.0 # Apache-2.0 python-mistralclient>=1.0.0 # Apache-2.0 -python-muranoclient>=0.8.2 # Apache-2.0 +python-muranoclient>=0.8.2 # Apache-2.0 python-saharaclient>=0.13.0 # Apache-2.0 python-searchlightclient>=0.2.0 #Apache-2.0 -python-senlinclient>=0.3.0 # Apache-2.0 +python-senlinclient>=0.3.0 # Apache-2.0 python-zaqarclient>=0.3.0 # Apache-2.0 From 2e94f2803fca3862589fe2b10c76c2ebc9e17229 Mon Sep 17 00:00:00 2001 From: reedip Date: Thu, 18 Feb 2016 17:12:05 +0900 Subject: [PATCH 0741/3095] Add "router add port" to osc This patch proposes the migration of port addition of Neutron's "router-interface-add" to OSC's "router add port". Change-Id: I3a8a76d384caa32975f8e77f50f3cf3c3be13786 Closes-Bug: #1546849 Depends-On: I9783bc4ccceae3d361dce52d51483ef2187920a9 Implements: blueprint neutron-client-advanced-router --- doc/source/command-objects/router.rst | 25 ++++++++++- openstackclient/network/v2/router.py | 26 +++++++++++- .../tests/network/v2/test_router.py | 41 +++++++++++++++++++ .../router-port-add-0afe7392c080bcb8.yaml | 5 +++ setup.cfg | 1 + 5 files changed, 95 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/router-port-add-0afe7392c080bcb8.yaml diff --git a/doc/source/command-objects/router.rst b/doc/source/command-objects/router.rst index 6b8b357b1c..9d094d3769 100644 --- a/doc/source/command-objects/router.rst +++ b/doc/source/command-objects/router.rst @@ -4,6 +4,27 @@ router Network v2 +router add port +--------------- +Add a port to a router + +.. program:: router add port +.. code:: bash + + os router add port + + + +.. _router_add_port: + +.. describe:: + + Router to which port will be added (name or ID) + +.. describe:: + + Port to be added (name or ID) + router create ------------- @@ -16,7 +37,7 @@ Create new router [--project [--project-domain ]] [--enable | --disable] [--distributed] - [--availability-zone-hint ] + [--availability-zone-hint ] .. option:: --project @@ -146,4 +167,4 @@ Display router details .. _router_show-router: .. describe:: - Router to display (name or ID) + Router to display (name or ID) \ No newline at end of file diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 96aa55b2f1..9fcb8bce13 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -82,6 +82,30 @@ def _get_attrs(client_manager, parsed_args): return attrs +class AddPortToRouter(command.Command): + """Add a port to a router""" + + def get_parser(self, prog_name): + parser = super(AddPortToRouter, self).get_parser(prog_name) + parser.add_argument( + 'router', + metavar='', + help="Router to which port will be added (name or ID)", + ) + parser.add_argument( + 'port', + metavar='', + help="Port to be added (name or ID)", + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + port = client.find_port(parsed_args.port, ignore_missing=False) + client.router_add_interface(client.find_router( + parsed_args.router, ignore_missing=False), port_id=port.id) + + class CreateRouter(command.ShowOne): """Create a new router""" @@ -318,4 +342,4 @@ def take_action(self, parsed_args): obj = client.find_router(parsed_args.router, ignore_missing=False) columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) - return columns, data + return columns, data \ No newline at end of file diff --git a/openstackclient/tests/network/v2/test_router.py b/openstackclient/tests/network/v2/test_router.py index 68c225e77d..03a00003e7 100644 --- a/openstackclient/tests/network/v2/test_router.py +++ b/openstackclient/tests/network/v2/test_router.py @@ -29,6 +29,47 @@ def setUp(self): self.network = self.app.client_manager.network +class TestAddPortToRouter(TestRouter): + '''Add port to Router ''' + + _port = network_fakes.FakePort.create_one_port() + _router = network_fakes.FakeRouter.create_one_router( + attrs={'port': _port.id}) + + def setUp(self): + super(TestAddPortToRouter, self).setUp() + self.network.router_add_interface = mock.Mock() + self.cmd = router.AddPortToRouter(self.app, self.namespace) + self.network.find_router = mock.Mock(return_value=self._router) + self.network.find_port = mock.Mock(return_value=self._port) + + def test_add_port_no_option(self): + arglist = [] + verifylist = [] + + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_add_port_required_options(self): + arglist = [ + self._router.id, + self._router.port, + ] + verifylist = [ + ('router', self._router.id), + ('port', self._router.port), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.network.router_add_interface.assert_called_with(self._router, **{ + 'port_id': self._router.port, + }) + self.assertIsNone(result) + + class TestCreateRouter(TestRouter): # The new router created. diff --git a/releasenotes/notes/router-port-add-0afe7392c080bcb8.yaml b/releasenotes/notes/router-port-add-0afe7392c080bcb8.yaml new file mode 100644 index 0000000000..1e2ee7ed32 --- /dev/null +++ b/releasenotes/notes/router-port-add-0afe7392c080bcb8.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``router add port`` command + [Bug `1546849 `_] diff --git a/setup.cfg b/setup.cfg index 728b5d646b..2fc3f5ce92 100644 --- a/setup.cfg +++ b/setup.cfg @@ -338,6 +338,7 @@ openstack.network.v2 = port_list = openstackclient.network.v2.port:ListPort port_show = openstackclient.network.v2.port:ShowPort + router_add_port = openstackclient.network.v2.router:AddPortToRouter router_create = openstackclient.network.v2.router:CreateRouter router_delete = openstackclient.network.v2.router:DeleteRouter router_list = openstackclient.network.v2.router:ListRouter From 8ecdc57ea680b7e20835bea69a2d18e1460d9406 Mon Sep 17 00:00:00 2001 From: reedip Date: Sat, 19 Mar 2016 12:04:00 +0900 Subject: [PATCH 0742/3095] Add "router remove port" to osc This patch proposes the migration of port deletion of Neutron's "router-interface-delete" to OSC's "router remove port". Change-Id: Ifceabce080aacac9f6410c809fcc097760e0c5ee Partial-Bug: #1546849 Implements: blueprint neutron-client-advanced-router --- doc/source/command-objects/router.rst | 26 +++++++++++- openstackclient/network/v2/router.py | 26 +++++++++++- .../tests/network/v2/test_router.py | 40 +++++++++++++++++++ .../router-remove-port-058078c93819b0f4.yaml | 5 +++ setup.cfg | 1 + 5 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/router-remove-port-058078c93819b0f4.yaml diff --git a/doc/source/command-objects/router.rst b/doc/source/command-objects/router.rst index 9d094d3769..d4e3f4a5e2 100644 --- a/doc/source/command-objects/router.rst +++ b/doc/source/command-objects/router.rst @@ -6,6 +6,7 @@ Network v2 router add port --------------- + Add a port to a router .. program:: router add port @@ -102,6 +103,29 @@ List routers List additional fields in output +router remove port +------------------ + +Remove a port from a router + +.. program:: router remove port +.. code:: bash + + os router remove port + + + +.. _router_remove_port: + +.. describe:: + + Router from which port will be removed (name or ID) + +.. describe:: + + Port to be removed (name or ID) + + router set ---------- @@ -167,4 +191,4 @@ Display router details .. _router_show-router: .. describe:: - Router to display (name or ID) \ No newline at end of file + Router to display (name or ID) diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 9fcb8bce13..caf6d5ce23 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -241,6 +241,30 @@ def take_action(self, parsed_args): ) for s in data)) +class RemovePortFromRouter(command.Command): + """Remove a port from a router""" + + def get_parser(self, prog_name): + parser = super(RemovePortFromRouter, self).get_parser(prog_name) + parser.add_argument( + 'router', + metavar='', + help="Router from which port will be removed (name or ID)", + ) + parser.add_argument( + 'port', + metavar='', + help="Port to be removed (name or ID).", + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + port = client.find_port(parsed_args.port, ignore_missing=False) + client.router_remove_interface(client.find_router( + parsed_args.router, ignore_missing=False), port_id=port.id) + + class SetRouter(command.Command): """Set router properties""" @@ -342,4 +366,4 @@ def take_action(self, parsed_args): obj = client.find_router(parsed_args.router, ignore_missing=False) columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) - return columns, data \ No newline at end of file + return columns, data diff --git a/openstackclient/tests/network/v2/test_router.py b/openstackclient/tests/network/v2/test_router.py index 03a00003e7..40941fbcc5 100644 --- a/openstackclient/tests/network/v2/test_router.py +++ b/openstackclient/tests/network/v2/test_router.py @@ -269,6 +269,46 @@ def test_router_list_long(self): self.assertEqual(self.data_long, list(data)) +class TestRemovePortFromRouter(TestRouter): + '''Remove port from a Router ''' + + _port = network_fakes.FakePort.create_one_port() + _router = network_fakes.FakeRouter.create_one_router( + attrs={'port': _port.id}) + + def setUp(self): + super(TestRemovePortFromRouter, self).setUp() + self.network.router_remove_interface = mock.Mock() + self.cmd = router.RemovePortFromRouter(self.app, self.namespace) + self.network.find_router = mock.Mock(return_value=self._router) + self.network.find_port = mock.Mock(return_value=self._port) + + def test_remove_port_no_option(self): + arglist = [] + verifylist = [] + + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_remove_port_required_options(self): + arglist = [ + self._router.id, + self._router.port, + ] + verifylist = [ + ('router', self._router.id), + ('port', self._router.port), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.network.router_remove_interface.assert_called_with( + self._router, **{'port_id': self._router.port}) + self.assertIsNone(result) + + class TestSetRouter(TestRouter): # The router to set. diff --git a/releasenotes/notes/router-remove-port-058078c93819b0f4.yaml b/releasenotes/notes/router-remove-port-058078c93819b0f4.yaml new file mode 100644 index 0000000000..187026a7d3 --- /dev/null +++ b/releasenotes/notes/router-remove-port-058078c93819b0f4.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``router remove port`` command + [Bug `1546849 `_] diff --git a/setup.cfg b/setup.cfg index 2fc3f5ce92..942496833a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -342,6 +342,7 @@ openstack.network.v2 = router_create = openstackclient.network.v2.router:CreateRouter router_delete = openstackclient.network.v2.router:DeleteRouter router_list = openstackclient.network.v2.router:ListRouter + router_remove_port = openstackclient.network.v2.router:RemovePortFromRouter router_set = openstackclient.network.v2.router:SetRouter router_show = openstackclient.network.v2.router:ShowRouter From 0a65e8df702b242275188aeb4a81c40358bb7a39 Mon Sep 17 00:00:00 2001 From: Kyrylo Romanenko Date: Mon, 21 Mar 2016 18:32:44 +0200 Subject: [PATCH 0743/3095] Style fix for one line docstring according to flake8 Change-Id: I639819a5b1d8a476cdd8b340b8c339754f471048 --- functional/tests/compute/v2/test_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/functional/tests/compute/v2/test_server.py b/functional/tests/compute/v2/test_server.py index 6c09a42e02..51d3f8f87a 100644 --- a/functional/tests/compute/v2/test_server.py +++ b/functional/tests/compute/v2/test_server.py @@ -20,7 +20,7 @@ class ServerTests(test.TestCase): - """Functional tests for server. """ + """Functional tests for server.""" NAME = uuid.uuid4().hex OTHER_NAME = uuid.uuid4().hex From 526f2f9f77dbfd27443e7e4957682110191265cc Mon Sep 17 00:00:00 2001 From: Kyrylo Romanenko Date: Mon, 21 Mar 2016 19:34:14 +0200 Subject: [PATCH 0744/3095] Remove superfluous variable assignment statements wait and interval are already defaulted in the function signature Change-Id: I66317a24f8327c464343ac13fc0126c34915eeda --- functional/tests/compute/v2/test_server.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/functional/tests/compute/v2/test_server.py b/functional/tests/compute/v2/test_server.py index 6c09a42e02..c5a83bb227 100644 --- a/functional/tests/compute/v2/test_server.py +++ b/functional/tests/compute/v2/test_server.py @@ -87,8 +87,6 @@ def test_server_show(self): def wait_for(self, desired, wait=120, interval=5, failures=['ERROR']): # TODO(thowe): Add a server wait command to osc status = "notset" - wait = 120 - interval = 5 total_sleep = 0 opts = self.get_show_opts(['status']) while total_sleep < wait: From 9e42daa577b0f15c349c2f4a79b3632ffec97720 Mon Sep 17 00:00:00 2001 From: reedip Date: Sun, 20 Mar 2016 16:05:49 +0900 Subject: [PATCH 0745/3095] Add Subnet add/remove support to router The following patch adds the support for "router add subnet" and "router remove subnet" to the OSC as a part of migration of Neutron's CLI commands. Partial-Bug: #1546849 Implements: blueprint neutron-client-advanced-router Change-Id: Ia3770c41026194bdb1543d4e67446f81936d44d1 --- doc/source/command-objects/router.rst | 43 ++++++++++ openstackclient/network/v2/router.py | 54 +++++++++++++ .../tests/network/v2/test_router.py | 79 +++++++++++++++++++ .../notes/router-subnet-469d095ae0bac884.yaml | 8 ++ setup.cfg | 2 + 5 files changed, 186 insertions(+) create mode 100644 releasenotes/notes/router-subnet-469d095ae0bac884.yaml diff --git a/doc/source/command-objects/router.rst b/doc/source/command-objects/router.rst index d4e3f4a5e2..9479af5f5d 100644 --- a/doc/source/command-objects/router.rst +++ b/doc/source/command-objects/router.rst @@ -26,6 +26,28 @@ Add a port to a router Port to be added (name or ID) +router add subnet +----------------- + +Add a subnet to a router + +.. program:: router add subnet +.. code:: bash + + os router add subnet + + + +.. _router_add_subnet: + +.. describe:: + + Router to which subnet will be added (name or ID) + +.. describe:: + + Subnet to be added (name or ID) + router create ------------- @@ -125,6 +147,27 @@ Remove a port from a router Port to be removed (name or ID) +router remove subnet +-------------------- + +Remove a subnet from a router + +.. program:: router remove subnet +.. code:: bash + + os router remove subnet + + + +.. _router_remove_subnet: + +.. describe:: + + Router from which subnet will be removed (name or ID) + +.. describe:: + + Subnet to be removed (name or ID) router set ---------- diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index caf6d5ce23..f4f12087d6 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -106,6 +106,33 @@ def take_action(self, parsed_args): parsed_args.router, ignore_missing=False), port_id=port.id) +class AddSubnetToRouter(command.Command): + """Add a subnet to a router""" + + def get_parser(self, prog_name): + parser = super(AddSubnetToRouter, self).get_parser(prog_name) + parser.add_argument( + 'router', + metavar='', + help="Router to which subnet will be added (name or ID)", + ) + parser.add_argument( + 'subnet', + metavar='', + help="Subnet to be added (name or ID)", + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + subnet = client.find_subnet(parsed_args.subnet, + ignore_missing=False) + client.router_add_interface( + client.find_router(parsed_args.router, + ignore_missing=False), + subnet_id=subnet.id) + + class CreateRouter(command.ShowOne): """Create a new router""" @@ -265,6 +292,33 @@ def take_action(self, parsed_args): parsed_args.router, ignore_missing=False), port_id=port.id) +class RemoveSubnetFromRouter(command.Command): + """Remove a subnet from a router""" + + def get_parser(self, prog_name): + parser = super(RemoveSubnetFromRouter, self).get_parser(prog_name) + parser.add_argument( + 'router', + metavar='', + help="Router from which the subnet will be removed (name or ID)", + ) + parser.add_argument( + 'subnet', + metavar='', + help="Subnet to be removed (name or ID)", + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + subnet = client.find_subnet(parsed_args.subnet, + ignore_missing=False) + client.router_remove_interface( + client.find_router(parsed_args.router, + ignore_missing=False), + subnet_id=subnet.id) + + class SetRouter(command.Command): """Set router properties""" diff --git a/openstackclient/tests/network/v2/test_router.py b/openstackclient/tests/network/v2/test_router.py index 40941fbcc5..8de4b3b2d2 100644 --- a/openstackclient/tests/network/v2/test_router.py +++ b/openstackclient/tests/network/v2/test_router.py @@ -70,6 +70,46 @@ def test_add_port_required_options(self): self.assertIsNone(result) +class TestAddSubnetToRouter(TestRouter): + '''Add subnet to Router ''' + + _subnet = network_fakes.FakeSubnet.create_one_subnet() + _router = network_fakes.FakeRouter.create_one_router( + attrs={'subnet': _subnet.id}) + + def setUp(self): + super(TestAddSubnetToRouter, self).setUp() + self.network.router_add_interface = mock.Mock() + self.cmd = router.AddSubnetToRouter(self.app, self.namespace) + self.network.find_router = mock.Mock(return_value=self._router) + self.network.find_subnet = mock.Mock(return_value=self._subnet) + + def test_add_subnet_no_option(self): + arglist = [] + verifylist = [] + + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_add_subnet_required_options(self): + arglist = [ + self._router.id, + self._router.subnet, + ] + verifylist = [ + ('router', self._router.id), + ('subnet', self._router.subnet), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.network.router_add_interface.assert_called_with( + self._router, **{'subnet_id': self._router.subnet}) + + self.assertIsNone(result) + + class TestCreateRouter(TestRouter): # The new router created. @@ -309,6 +349,45 @@ def test_remove_port_required_options(self): self.assertIsNone(result) +class TestRemoveSubnetFromRouter(TestRouter): + '''Remove subnet from Router ''' + + _subnet = network_fakes.FakeSubnet.create_one_subnet() + _router = network_fakes.FakeRouter.create_one_router( + attrs={'subnet': _subnet.id}) + + def setUp(self): + super(TestRemoveSubnetFromRouter, self).setUp() + self.network.router_remove_interface = mock.Mock() + self.cmd = router.RemoveSubnetFromRouter(self.app, self.namespace) + self.network.find_router = mock.Mock(return_value=self._router) + self.network.find_subnet = mock.Mock(return_value=self._subnet) + + def test_remove_subnet_no_option(self): + arglist = [] + verifylist = [] + + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_remove_subnet_required_options(self): + arglist = [ + self._router.id, + self._router.subnet, + ] + verifylist = [ + ('subnet', self._router.subnet), + ('router', self._router.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.network.router_remove_interface.assert_called_with( + self._router, **{'subnet_id': self._router.subnet}) + self.assertIsNone(result) + + class TestSetRouter(TestRouter): # The router to set. diff --git a/releasenotes/notes/router-subnet-469d095ae0bac884.yaml b/releasenotes/notes/router-subnet-469d095ae0bac884.yaml new file mode 100644 index 0000000000..db94b641e6 --- /dev/null +++ b/releasenotes/notes/router-subnet-469d095ae0bac884.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Add ``router add subnet`` command + [Bug `1546849 `_] + - | + Add ``router remove subnet`` command + [Bug `1546849 `_] diff --git a/setup.cfg b/setup.cfg index 942496833a..3af3b3503f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -339,10 +339,12 @@ openstack.network.v2 = port_show = openstackclient.network.v2.port:ShowPort router_add_port = openstackclient.network.v2.router:AddPortToRouter + router_add_subnet = openstackclient.network.v2.router:AddSubnetToRouter router_create = openstackclient.network.v2.router:CreateRouter router_delete = openstackclient.network.v2.router:DeleteRouter router_list = openstackclient.network.v2.router:ListRouter router_remove_port = openstackclient.network.v2.router:RemovePortFromRouter + router_remove_subnet = openstackclient.network.v2.router:RemoveSubnetFromRouter router_set = openstackclient.network.v2.router:SetRouter router_show = openstackclient.network.v2.router:ShowRouter From 41853985bb654e8417e5c9850ed1eeccd9828a73 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 19 Mar 2016 10:56:43 +0800 Subject: [PATCH 0746/3095] Use assert_called_once_with() instead of assert_called_with() If one API is only called once, we'd better use assert_called_once_with() to check the call. Change-Id: Id05055bb90592d218826732259ed84fc366734ce --- .../tests/network/v2/test_floating_ip.py | 12 +++---- .../tests/network/v2/test_network.py | 32 ++++++++++--------- openstackclient/tests/network/v2/test_port.py | 20 ++++++------ .../tests/network/v2/test_router.py | 26 ++++++++------- .../tests/network/v2/test_subnet.py | 16 +++++----- .../tests/network/v2/test_subnet_pool.py | 17 +++++----- 6 files changed, 65 insertions(+), 58 deletions(-) diff --git a/openstackclient/tests/network/v2/test_floating_ip.py b/openstackclient/tests/network/v2/test_floating_ip.py index 1c1088a3b4..c9da0fa07d 100644 --- a/openstackclient/tests/network/v2/test_floating_ip.py +++ b/openstackclient/tests/network/v2/test_floating_ip.py @@ -54,8 +54,8 @@ def test_floating_ip_delete(self): result = self.cmd.take_action(parsed_args) - self.network.find_ip.assert_called_with(self.floating_ip.id) - self.network.delete_ip.assert_called_with(self.floating_ip) + self.network.find_ip.assert_called_once_with(self.floating_ip.id) + self.network.delete_ip.assert_called_once_with(self.floating_ip) self.assertIsNone(result) @@ -95,7 +95,7 @@ def test_floating_ip_list(self): columns, data = self.cmd.take_action(parsed_args) - self.network.ips.assert_called_with(**{}) + self.network.ips.assert_called_once_with(**{}) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) @@ -150,7 +150,7 @@ def test_floating_ip_show(self): columns, data = self.cmd.take_action(parsed_args) - self.network.find_ip.assert_called_with( + self.network.find_ip.assert_called_once_with( self.floating_ip.id, ignore_missing=False ) @@ -198,7 +198,7 @@ def test_floating_ip_delete(self): result = self.cmd.take_action(parsed_args) - self.compute.floating_ips.delete.assert_called_with( + self.compute.floating_ips.delete.assert_called_once_with( self.floating_ip.id ) self.assertIsNone(result) @@ -244,7 +244,7 @@ def test_floating_ip_list(self): columns, data = self.cmd.take_action(parsed_args) - self.compute.floating_ips.list.assert_called_with() + self.compute.floating_ips.list.assert_called_once_with() self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index e70a66c1f9..884a6e8181 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -124,7 +124,7 @@ def test_create_default_options(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.network.create_network.assert_called_with(**{ + self.network.create_network.assert_called_once_with(**{ 'admin_state_up': True, 'name': self._network.name, }) @@ -152,7 +152,7 @@ def test_create_all_options(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = (self.cmd.take_action(parsed_args)) - self.network.create_network.assert_called_with(**{ + self.network.create_network.assert_called_once_with(**{ 'admin_state_up': False, 'availability_zone_hints': ["nova"], 'name': self._network.name, @@ -177,7 +177,7 @@ def test_create_other_options(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.network.create_network.assert_called_with(**{ + self.network.create_network.assert_called_once_with(**{ 'admin_state_up': True, 'name': self._network.name, 'shared': False, @@ -258,7 +258,7 @@ def test_create_with_project_identityv2(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.network.create_network.assert_called_with(**{ + self.network.create_network.assert_called_once_with(**{ 'admin_state_up': True, 'name': self._network.name, 'tenant_id': identity_fakes_v2.project_id, @@ -315,7 +315,7 @@ def test_delete(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.network.delete_network.assert_called_with(self._network) + self.network.delete_network.assert_called_once_with(self._network) self.assertIsNone(result) @@ -386,7 +386,7 @@ def test_network_list_no_options(self): # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.network.networks.assert_called_with() + self.network.networks.assert_called_once_with() self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) @@ -405,7 +405,7 @@ def test_list_external(self): # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.network.networks.assert_called_with( + self.network.networks.assert_called_once_with( **{'router:external': True} ) self.assertEqual(self.columns, columns) @@ -426,7 +426,7 @@ def test_network_list_long(self): # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.network.networks.assert_called_with() + self.network.networks.assert_called_once_with() self.assertEqual(self.columns_long, columns) self.assertEqual(self.data_long, list(data)) @@ -468,7 +468,8 @@ def test_set_this(self): 'admin_state_up': True, 'shared': True, } - self.network.update_network.assert_called_with(self._network, **attrs) + self.network.update_network.assert_called_once_with( + self._network, **attrs) self.assertIsNone(result) def test_set_that(self): @@ -490,7 +491,8 @@ def test_set_that(self): 'admin_state_up': False, 'shared': False, } - self.network.update_network.assert_called_with(self._network, **attrs) + self.network.update_network.assert_called_once_with( + self._network, **attrs) self.assertIsNone(result) def test_set_nothing(self): @@ -558,8 +560,8 @@ def test_show_all_options(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.network.find_network.assert_called_with(self._network.name, - ignore_missing=False) + self.network.find_network.assert_called_once_with( + self._network.name, ignore_missing=False) self.assertEqual(tuple(self.columns), columns) self.assertEqual(list(self.data), list(data)) @@ -682,7 +684,7 @@ def test_create_default_options(self): columns, data = self.cmd.take_action(parsed_args) - self.compute.networks.create.assert_called_with(**{ + self.compute.networks.create.assert_called_once_with(**{ 'cidr': self._network.cidr, 'label': self._network.label, }) @@ -719,7 +721,7 @@ def test_network_delete(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.compute.networks.delete.assert_called_with(self._network.id) + self.compute.networks.delete.assert_called_once_with(self._network.id) self.assertIsNone(result) @@ -765,7 +767,7 @@ def test_network_list_no_options(self): # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.compute.networks.list.assert_called_with() + self.compute.networks.list.assert_called_once_with() self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) diff --git a/openstackclient/tests/network/v2/test_port.py b/openstackclient/tests/network/v2/test_port.py index 5532fed5b3..cdbc699a6e 100644 --- a/openstackclient/tests/network/v2/test_port.py +++ b/openstackclient/tests/network/v2/test_port.py @@ -110,7 +110,7 @@ def test_create_default_options(self): columns, data = (self.cmd.take_action(parsed_args)) - self.network.create_port.assert_called_with(**{ + self.network.create_port.assert_called_once_with(**{ 'admin_state_up': True, 'network_id': self._port.network_id, 'name': 'test-port', @@ -154,7 +154,7 @@ def test_create_full_options(self): columns, data = (self.cmd.take_action(parsed_args)) - self.network.create_port.assert_called_with(**{ + self.network.create_port.assert_called_once_with(**{ 'mac_address': 'aa:aa:aa:aa:aa:aa', 'fixed_ips': [{'subnet_id': self.fake_subnet.id, 'ip_address': '10.0.0.2'}], @@ -195,7 +195,7 @@ def test_delete(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.network.delete_port.assert_called_with(self._port) + self.network.delete_port.assert_called_once_with(self._port) self.assertIsNone(result) @@ -238,7 +238,7 @@ def test_port_list_no_options(self): columns, data = self.cmd.take_action(parsed_args) - self.network.ports.assert_called_with() + self.network.ports.assert_called_once_with() self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) @@ -255,7 +255,7 @@ def test_port_list_router_opt(self): columns, data = self.cmd.take_action(parsed_args) - self.network.ports.assert_called_with(**{ + self.network.ports.assert_called_once_with(**{ 'device_id': 'fake-router-id' }) self.assertEqual(self.columns, columns) @@ -292,7 +292,7 @@ def test_set_fixed_ip(self): attrs = { 'fixed_ips': [{'ip_address': '10.0.0.11'}], } - self.network.update_port.assert_called_with(self._port, **attrs) + self.network.update_port.assert_called_once_with(self._port, **attrs) self.assertIsNone(result) def test_set_this(self): @@ -310,7 +310,7 @@ def test_set_this(self): attrs = { 'admin_state_up': False, } - self.network.update_port.assert_called_with(self._port, **attrs) + self.network.update_port.assert_called_once_with(self._port, **attrs) self.assertIsNone(result) def test_set_that(self): @@ -337,7 +337,7 @@ def test_set_that(self): 'binding:profile': {'foo': 'bar'}, 'binding:host_id': 'binding-host-id-xxxx', } - self.network.update_port.assert_called_with(self._port, **attrs) + self.network.update_port.assert_called_once_with(self._port, **attrs) self.assertIsNone(result) @@ -372,8 +372,8 @@ def test_show_all_options(self): columns, data = self.cmd.take_action(parsed_args) - self.network.find_port.assert_called_with(self._port.name, - ignore_missing=False) + self.network.find_port.assert_called_once_with( + self._port.name, ignore_missing=False) ref_columns, ref_data = self._get_common_cols_data(self._port) self.assertEqual(ref_columns, columns) diff --git a/openstackclient/tests/network/v2/test_router.py b/openstackclient/tests/network/v2/test_router.py index 68c225e77d..f3bf363d2a 100644 --- a/openstackclient/tests/network/v2/test_router.py +++ b/openstackclient/tests/network/v2/test_router.py @@ -80,7 +80,7 @@ def test_create_default_options(self): columns, data = (self.cmd.take_action(parsed_args)) - self.network.create_router.assert_called_with(**{ + self.network.create_router.assert_called_once_with(**{ 'admin_state_up': True, 'name': self.new_router.name, 'distributed': False, @@ -103,7 +103,7 @@ def test_create_with_AZ_hints(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = (self.cmd.take_action(parsed_args)) - self.network.create_router.assert_called_with(**{ + self.network.create_router.assert_called_once_with(**{ 'admin_state_up': True, 'name': self.new_router.name, 'distributed': False, @@ -139,7 +139,7 @@ def test_delete(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.network.delete_router.assert_called_with(self._router) + self.network.delete_router.assert_called_once_with(self._router) self.assertIsNone(result) @@ -205,7 +205,7 @@ def test_router_list_no_options(self): # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.network.routers.assert_called_with() + self.network.routers.assert_called_once_with() self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) @@ -223,7 +223,7 @@ def test_router_list_long(self): # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.network.routers.assert_called_with() + self.network.routers.assert_called_once_with() self.assertEqual(self.columns_long, columns) self.assertEqual(self.data_long, list(data)) @@ -265,7 +265,8 @@ def test_set_this(self): 'distributed': True, 'name': 'noob', } - self.network.update_router.assert_called_with(self._router, **attrs) + self.network.update_router.assert_called_once_with( + self._router, **attrs) self.assertIsNone(result) def test_set_that(self): @@ -287,7 +288,8 @@ def test_set_that(self): 'admin_state_up': False, 'distributed': False, } - self.network.update_router.assert_called_with(self._router, **attrs) + self.network.update_router.assert_called_once_with( + self._router, **attrs) self.assertIsNone(result) def test_set_distributed_centralized(self): @@ -324,7 +326,8 @@ def test_set_route(self): 'routes': [{'destination': '10.20.30.0/24', 'gateway': '10.20.30.1'}], } - self.network.update_router.assert_called_with(self._router, **attrs) + self.network.update_router.assert_called_once_with( + self._router, **attrs) self.assertIsNone(result) def test_set_clear_routes(self): @@ -343,7 +346,8 @@ def test_set_clear_routes(self): attrs = { 'routes': [], } - self.network.update_router.assert_called_with(self._router, **attrs) + self.network.update_router.assert_called_once_with( + self._router, **attrs) self.assertIsNone(result) def test_set_route_clear_routes(self): @@ -422,7 +426,7 @@ def test_show_all_options(self): columns, data = self.cmd.take_action(parsed_args) - self.network.find_router.assert_called_with(self._router.name, - ignore_missing=False) + self.network.find_router.assert_called_once_with( + self._router.name, ignore_missing=False) self.assertEqual(tuple(self.columns), columns) self.assertEqual(self.data, data) diff --git a/openstackclient/tests/network/v2/test_subnet.py b/openstackclient/tests/network/v2/test_subnet.py index b718d262ae..de17c78922 100644 --- a/openstackclient/tests/network/v2/test_subnet.py +++ b/openstackclient/tests/network/v2/test_subnet.py @@ -219,7 +219,7 @@ def test_create_default_options(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.network.create_subnet.assert_called_with(**{ + self.network.create_subnet.assert_called_once_with(**{ 'cidr': self._subnet.cidr, 'enable_dhcp': self._subnet.enable_dhcp, 'ip_version': self._subnet.ip_version, @@ -275,7 +275,7 @@ def test_create_from_subnet_pool_options(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.network.create_subnet.assert_called_with(**{ + self.network.create_subnet.assert_called_once_with(**{ 'dns_nameservers': self._subnet_from_pool.dns_nameservers, 'enable_dhcp': self._subnet_from_pool.enable_dhcp, 'gateway_ip': self._subnet_from_pool.gateway_ip, @@ -341,7 +341,7 @@ def test_create_options_subnet_range_ipv6(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.network.create_subnet.assert_called_with(**{ + self.network.create_subnet.assert_called_once_with(**{ 'cidr': self._subnet_ipv6.cidr, 'dns_nameservers': self._subnet_ipv6.dns_nameservers, 'enable_dhcp': self._subnet_ipv6.enable_dhcp, @@ -383,7 +383,7 @@ def test_delete(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.network.delete_subnet.assert_called_with(self._subnet) + self.network.delete_subnet.assert_called_once_with(self._subnet) self.assertIsNone(result) @@ -449,7 +449,7 @@ def test_subnet_list_no_options(self): columns, data = self.cmd.take_action(parsed_args) - self.network.subnets.assert_called_with() + self.network.subnets.assert_called_once_with() self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) @@ -464,7 +464,7 @@ def test_subnet_list_long(self): columns, data = self.cmd.take_action(parsed_args) - self.network.subnets.assert_called_with() + self.network.subnets.assert_called_once_with() self.assertEqual(self.columns_long, columns) self.assertEqual(self.data_long, list(data)) @@ -535,8 +535,8 @@ def test_show_all_options(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.network.find_subnet.assert_called_with(self._subnet.name, - ignore_missing=False) + self.network.find_subnet.assert_called_once_with( + self._subnet.name, ignore_missing=False) self.assertEqual(self.columns, columns) self.assertEqual(list(self.data), list(data)) diff --git a/openstackclient/tests/network/v2/test_subnet_pool.py b/openstackclient/tests/network/v2/test_subnet_pool.py index 99994681d5..ebedaf6436 100644 --- a/openstackclient/tests/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/network/v2/test_subnet_pool.py @@ -94,7 +94,7 @@ def test_create_default_options(self): columns, data = (self.cmd.take_action(parsed_args)) - self.network.create_subnet_pool.assert_called_with(**{ + self.network.create_subnet_pool.assert_called_once_with(**{ 'prefixes': ['10.0.10.0/24'], 'name': self._subnet_pool.name, }) @@ -118,7 +118,7 @@ def test_create_prefixlen_options(self): columns, data = (self.cmd.take_action(parsed_args)) - self.network.create_subnet_pool.assert_called_with(**{ + self.network.create_subnet_pool.assert_called_once_with(**{ 'default_prefix_length': self._subnet_pool.default_prefixlen, 'max_prefix_length': self._subnet_pool.max_prefixlen, 'min_prefix_length': self._subnet_pool.min_prefixlen, @@ -169,7 +169,8 @@ def test_delete(self): result = self.cmd.take_action(parsed_args) - self.network.delete_subnet_pool.assert_called_with(self._subnet_pool) + self.network.delete_subnet_pool.assert_called_once_with( + self._subnet_pool) self.assertIsNone(result) @@ -222,7 +223,7 @@ def test_subnet_pool_list_no_option(self): columns, data = self.cmd.take_action(parsed_args) - self.network.subnet_pools.assert_called_with() + self.network.subnet_pools.assert_called_once_with() self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) @@ -237,7 +238,7 @@ def test_subnet_pool_list_long(self): columns, data = self.cmd.take_action(parsed_args) - self.network.subnet_pools.assert_called_with() + self.network.subnet_pools.assert_called_once_with() self.assertEqual(self.columns_long, columns) self.assertEqual(self.data_long, list(data)) @@ -280,7 +281,7 @@ def test_set_this(self): 'default_prefix_length': '8', 'min_prefix_length': '8', } - self.network.update_subnet_pool.assert_called_with( + self.network.update_subnet_pool.assert_called_once_with( self._subnet_pool, **attrs) self.assertIsNone(result) @@ -306,7 +307,7 @@ def test_set_that(self): 'prefixes': prefixes, 'max_prefix_length': '16', } - self.network.update_subnet_pool.assert_called_with( + self.network.update_subnet_pool.assert_called_once_with( self._subnet_pool, **attrs) self.assertIsNone(result) @@ -396,7 +397,7 @@ def test_show_all_options(self): columns, data = self.cmd.take_action(parsed_args) - self.network.find_subnet_pool.assert_called_with( + self.network.find_subnet_pool.assert_called_once_with( self._subnet_pool.name, ignore_missing=False ) From ee621509be394c433301d2a691159dffacd2b82c Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Wed, 23 Mar 2016 14:45:04 +0800 Subject: [PATCH 0747/3095] Fix "server unset" document issue Fix "--property" option describe issue in "server unset" document, and update the help message to keep consistent. Change-Id: I68022a187e83fad6320365400ad2a1b0c8cf9a61 --- doc/source/command-objects/server.rst | 2 +- openstackclient/compute/v2/server.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index 54f4ee73f5..5141600d78 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -764,7 +764,7 @@ Unset server properties .. option:: --property - Property key to remove from server (repeat to set multiple values) + Property key to remove from server (repeat to remove multiple values) .. describe:: diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 58174018c0..d3b601b0a6 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1738,7 +1738,7 @@ def get_parser(self, prog_name): action='append', default=[], help=_('Property key to remove from server ' - '(repeat to unset multiple values)'), + '(repeat to remove multiple values)'), ) return parser From 3e0e1f8c4198d8d85cb88dbc29f6717659b1dccc Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Wed, 23 Mar 2016 16:04:21 +0800 Subject: [PATCH 0748/3095] Trivial: Fix typo in common/limits.py Change-Id: I3040cc69512df50459960f96b862de55804068ac --- doc/source/command-objects/limits.rst | 2 +- openstackclient/common/limits.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/source/command-objects/limits.rst b/doc/source/command-objects/limits.rst index 6a7509f204..87a5b33c98 100644 --- a/doc/source/command-objects/limits.rst +++ b/doc/source/command-objects/limits.rst @@ -37,4 +37,4 @@ Show compute and block storage limits .. option:: --domain - Domain that owns --project (name or ID) [only valid with --absolute] + Domain the project belongs to (name or ID) [only valid with --absolute] diff --git a/openstackclient/common/limits.py b/openstackclient/common/limits.py index bd546c01ee..1f87abf3de 100644 --- a/openstackclient/common/limits.py +++ b/openstackclient/common/limits.py @@ -55,8 +55,8 @@ def get_parser(self, prog_name): parser.add_argument( '--domain', metavar='', - help='Domain that owns --project (name or ID)' - ' [only valid with --absolute]', + help='Domain the project belongs to (name or ID)' + ' [only valid with --absolute]', ) return parser From d90650796217fbb9cdd19297ee6ff59f0e009413 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Fri, 11 Mar 2016 15:30:47 -0600 Subject: [PATCH 0749/3095] Refactor security group rule create to use SDK Refactored the 'os security group rule create' command to use the SDK when neutron is enabled, but continue to use the nova client when nova network is enabled. Added a release note for the change in security group rules output due to Network v2. Change-Id: I8c6c99d5272ff5d410a449f73d198d834c5cd96e Partial-Bug: #1519512 Implements: blueprint neutron-client --- .../command-objects/security-group-rule.rst | 3 +- openstackclient/compute/v2/security_group.py | 65 ---- .../network/v2/security_group_rule.py | 100 ++++++ openstackclient/tests/compute/v2/fakes.py | 6 +- .../compute/v2/test_security_group_rule.py | 266 --------------- openstackclient/tests/network/v2/fakes.py | 10 +- .../network/v2/test_security_group_rule.py | 319 +++++++++++++++++- .../notes/bug-1519512-4231ac6014109142.yaml | 7 + setup.cfg | 3 +- 9 files changed, 434 insertions(+), 345 deletions(-) create mode 100644 releasenotes/notes/bug-1519512-4231ac6014109142.yaml diff --git a/doc/source/command-objects/security-group-rule.rst b/doc/source/command-objects/security-group-rule.rst index 58e7a0a579..ad496e79d1 100644 --- a/doc/source/command-objects/security-group-rule.rst +++ b/doc/source/command-objects/security-group-rule.rst @@ -32,7 +32,8 @@ Create a new security group rule .. option:: --dst-port - Destination port, may be a range: 137:139 (default: 0; only required for proto tcp and udp) + Destination port, may be a single port or port range: 137:139 + (only required for IP protocols tcp and udp) .. describe:: diff --git a/openstackclient/compute/v2/security_group.py b/openstackclient/compute/v2/security_group.py index 734bd78e3a..ca3bf5dce1 100644 --- a/openstackclient/compute/v2/security_group.py +++ b/openstackclient/compute/v2/security_group.py @@ -16,15 +16,12 @@ """Compute v2 Security Group action implementations""" -import six - try: from novaclient.v2 import security_group_rules except ImportError: from novaclient.v1_1 import security_group_rules from openstackclient.common import command -from openstackclient.common import parseractions from openstackclient.common import utils @@ -56,68 +53,6 @@ def _xform_security_group_rule(sgroup): return info -class CreateSecurityGroupRule(command.ShowOne): - """Create a new security group rule""" - - def get_parser(self, prog_name): - parser = super(CreateSecurityGroupRule, self).get_parser(prog_name) - parser.add_argument( - 'group', - metavar='', - help='Create rule in this security group (name or ID)', - ) - parser.add_argument( - "--proto", - metavar="", - default="tcp", - help="IP protocol (icmp, tcp, udp; default: tcp)", - ) - source_group = parser.add_mutually_exclusive_group() - source_group.add_argument( - "--src-ip", - metavar="", - default="0.0.0.0/0", - help="Source IP address block (may use CIDR notation; default: " - "0.0.0.0/0)", - ) - source_group.add_argument( - "--src-group", - metavar="", - help="Source security group (ID only)", - ) - parser.add_argument( - "--dst-port", - metavar="", - default=(0, 0), - action=parseractions.RangeAction, - help="Destination port, may be a range: 137:139 (default: 0; " - "only required for proto tcp and udp)", - ) - return parser - - def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - group = utils.find_resource( - compute_client.security_groups, - parsed_args.group, - ) - if parsed_args.proto.lower() == 'icmp': - from_port, to_port = -1, -1 - else: - from_port, to_port = parsed_args.dst_port - data = compute_client.security_group_rules.create( - group.id, - parsed_args.proto, - from_port, - to_port, - parsed_args.src_ip, - parsed_args.src_group, - ) - - info = _xform_security_group_rule(data._info) - return zip(*sorted(six.iteritems(info))) - - class ListSecurityGroupRule(command.Lister): """List security group rules""" diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index 9309b326a1..e024465432 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -16,6 +16,7 @@ import six from openstackclient.common import exceptions +from openstackclient.common import parseractions from openstackclient.common import utils from openstackclient.network import common from openstackclient.network import utils as network_utils @@ -34,6 +35,105 @@ def _get_columns(item): return tuple(sorted(columns)) +def _convert_to_lowercase(string): + return string.lower() + + +class CreateSecurityGroupRule(common.NetworkAndComputeShowOne): + """Create a new security group rule""" + + def update_parser_common(self, parser): + parser.add_argument( + 'group', + metavar='', + help='Create rule in this security group (name or ID)', + ) + # TODO(rtheis): Add support for additional protocols for network. + # Until then, continue enforcing the compute choices. + parser.add_argument( + "--proto", + metavar="", + default="tcp", + choices=['icmp', 'tcp', 'udp'], + type=_convert_to_lowercase, + help="IP protocol (icmp, tcp, udp; default: tcp)", + ) + source_group = parser.add_mutually_exclusive_group() + source_group.add_argument( + "--src-ip", + metavar="", + default="0.0.0.0/0", + help="Source IP address block (may use CIDR notation; default: " + "0.0.0.0/0)", + ) + source_group.add_argument( + "--src-group", + metavar="", + help="Source security group (ID only)", + ) + parser.add_argument( + "--dst-port", + metavar="", + default=(0, 0), + action=parseractions.RangeAction, + help="Destination port, may be a single port or port range: " + "137:139 (only required for IP protocols tcp and udp)", + ) + return parser + + def take_action_network(self, client, parsed_args): + # Get the security group ID to hold the rule. + security_group_id = client.find_security_group( + parsed_args.group, + ignore_missing=False + ).id + + # Build the create attributes. + attrs = {} + # TODO(rtheis): Add --direction option. Until then, continue + # with the default of 'ingress'. + attrs['direction'] = 'ingress' + # TODO(rtheis): Add --ethertype option. Until then, continue + # with the default of 'IPv4' + attrs['ethertype'] = 'IPv4' + # TODO(rtheis): Add port range support (type and code) for icmp + # protocol. Until then, continue ignoring the port range. + if parsed_args.proto != 'icmp': + attrs['port_range_min'] = parsed_args.dst_port[0] + attrs['port_range_max'] = parsed_args.dst_port[1] + attrs['protocol'] = parsed_args.proto + if parsed_args.src_group is not None: + attrs['remote_group_id'] = parsed_args.src_group + else: + attrs['remote_ip_prefix'] = parsed_args.src_ip + attrs['security_group_id'] = security_group_id + + # Create and show the security group rule. + obj = client.create_security_group_rule(**attrs) + columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns) + return (columns, data) + + def take_action_compute(self, client, parsed_args): + group = utils.find_resource( + client.security_groups, + parsed_args.group, + ) + if parsed_args.proto == 'icmp': + from_port, to_port = -1, -1 + else: + from_port, to_port = parsed_args.dst_port + obj = client.security_group_rules.create( + group.id, + parsed_args.proto, + from_port, + to_port, + parsed_args.src_ip, + parsed_args.src_group, + ) + return _format_security_group_rule_show(obj._info) + + class DeleteSecurityGroupRule(common.NetworkAndComputeCommand): """Delete a security group rule""" diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 809825d0a5..fe9b3c75c9 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -393,13 +393,13 @@ def create_one_security_group_rule(attrs=None, methods=None): # Set default attributes. security_group_rule_attrs = { - 'from_port': -1, + 'from_port': 0, 'group': {}, 'id': 'security-group-rule-id-' + uuid.uuid4().hex, - 'ip_protocol': 'icmp', + 'ip_protocol': 'tcp', 'ip_range': {'cidr': '0.0.0.0/0'}, 'parent_group_id': 'security-group-id-' + uuid.uuid4().hex, - 'to_port': -1, + 'to_port': 0, } # Overwrite default attributes. diff --git a/openstackclient/tests/compute/v2/test_security_group_rule.py b/openstackclient/tests/compute/v2/test_security_group_rule.py index 9a8003f36a..42bf2c2682 100644 --- a/openstackclient/tests/compute/v2/test_security_group_rule.py +++ b/openstackclient/tests/compute/v2/test_security_group_rule.py @@ -17,7 +17,6 @@ from openstackclient.tests.compute.v2 import fakes as compute_fakes from openstackclient.tests import fakes from openstackclient.tests.identity.v2_0 import fakes as identity_fakes -from openstackclient.tests import utils security_group_id = '11' @@ -111,271 +110,6 @@ def setUp(self): self.sg_rules_mock.reset_mock() -class TestSecurityGroupRuleCreate(TestSecurityGroupRule): - - columns = ( - 'id', - 'ip_protocol', - 'ip_range', - 'parent_group_id', - 'port_range', - 'remote_security_group', - ) - - def setUp(self): - super(TestSecurityGroupRuleCreate, self).setUp() - - self.secgroups_mock.get.return_value = FakeSecurityGroupRuleResource( - None, - copy.deepcopy(SECURITY_GROUP), - loaded=True, - ) - - # Get the command object to test - self.cmd = security_group.CreateSecurityGroupRule(self.app, None) - - def test_security_group_rule_create_no_options(self): - self.sg_rules_mock.create.return_value = FakeSecurityGroupRuleResource( - None, - copy.deepcopy(SECURITY_GROUP_RULE), - loaded=True, - ) - - arglist = [ - security_group_name, - ] - verifylist = [ - ('group', security_group_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) - - # SecurityGroupManager.create(name, description) - self.sg_rules_mock.create.assert_called_with( - security_group_id, - 'tcp', - 0, - 0, - security_group_rule_cidr, - None, - ) - - self.assertEqual(self.columns, columns) - datalist = ( - security_group_rule_id, - 'tcp', - security_group_rule_cidr, - security_group_id, - '0:0', - '', - ) - self.assertEqual(datalist, data) - - def test_security_group_rule_create_ftp(self): - sg_rule = copy.deepcopy(SECURITY_GROUP_RULE) - sg_rule['from_port'] = 20 - sg_rule['to_port'] = 21 - self.sg_rules_mock.create.return_value = FakeSecurityGroupRuleResource( - None, - sg_rule, - loaded=True, - ) - - arglist = [ - security_group_name, - '--dst-port', '20:21', - ] - verifylist = [ - ('group', security_group_name), - ('dst_port', (20, 21)), - ] - 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) - - # SecurityGroupManager.create(name, description) - self.sg_rules_mock.create.assert_called_with( - security_group_id, - 'tcp', - 20, - 21, - security_group_rule_cidr, - None, - ) - - self.assertEqual(self.columns, columns) - datalist = ( - security_group_rule_id, - 'tcp', - security_group_rule_cidr, - security_group_id, - '20:21', - '', - ) - self.assertEqual(datalist, data) - - def test_security_group_rule_create_ssh(self): - sg_rule = copy.deepcopy(SECURITY_GROUP_RULE) - sg_rule['from_port'] = 22 - sg_rule['to_port'] = 22 - sg_rule['ip_range'] = {} - sg_rule['group'] = {'name': security_group_name} - self.sg_rules_mock.create.return_value = FakeSecurityGroupRuleResource( - None, - sg_rule, - loaded=True, - ) - - arglist = [ - security_group_name, - '--dst-port', '22', - '--src-group', security_group_id, - ] - verifylist = [ - ('group', security_group_name), - ('dst_port', (22, 22)), - ('src_group', security_group_id), - ] - 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) - - # SecurityGroupManager.create(name, description) - self.sg_rules_mock.create.assert_called_with( - security_group_id, - 'tcp', - 22, - 22, - security_group_rule_cidr, - security_group_id, - ) - - self.assertEqual(self.columns, columns) - datalist = ( - security_group_rule_id, - 'tcp', - '', - security_group_id, - '22:22', - security_group_name, - ) - self.assertEqual(datalist, data) - - def test_security_group_rule_create_udp(self): - sg_rule = copy.deepcopy(SECURITY_GROUP_RULE) - sg_rule['ip_protocol'] = 'udp' - self.sg_rules_mock.create.return_value = FakeSecurityGroupRuleResource( - None, - sg_rule, - loaded=True, - ) - - arglist = [ - security_group_name, - '--proto', 'udp', - ] - verifylist = [ - ('group', security_group_name), - ('proto', 'udp'), - ] - 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) - - # SecurityGroupManager.create(name, description) - self.sg_rules_mock.create.assert_called_with( - security_group_id, - 'udp', - 0, - 0, - security_group_rule_cidr, - None, - ) - - self.assertEqual(self.columns, columns) - datalist = ( - security_group_rule_id, - 'udp', - security_group_rule_cidr, - security_group_id, - '0:0', - '', - ) - self.assertEqual(datalist, data) - - def test_security_group_rule_create_icmp(self): - sg_rule_cidr = '10.0.2.0/24' - sg_rule = copy.deepcopy(SECURITY_GROUP_RULE_ICMP) - sg_rule['ip_range'] = {'cidr': sg_rule_cidr} - self.sg_rules_mock.create.return_value = FakeSecurityGroupRuleResource( - None, - sg_rule, - loaded=True, - ) - - arglist = [ - security_group_name, - '--proto', 'ICMP', - '--src-ip', sg_rule_cidr, - ] - verifylist = [ - ('group', security_group_name), - ('proto', 'ICMP'), - ('src_ip', sg_rule_cidr) - ] - 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) - - # SecurityGroupManager.create(name, description) - self.sg_rules_mock.create.assert_called_with( - security_group_id, - 'ICMP', - -1, - -1, - sg_rule_cidr, - None, - ) - - self.assertEqual(self.columns, columns) - datalist = ( - security_group_rule_id, - 'icmp', - sg_rule_cidr, - security_group_id, - '', - '', - ) - self.assertEqual(datalist, data) - - def test_security_group_rule_create_src_invalid(self): - arglist = [ - security_group_name, - '--proto', 'ICMP', - '--src-ip', security_group_rule_cidr, - '--src-group', security_group_id, - ] - - self.assertRaises(utils.ParserException, - self.check_parser, self.cmd, arglist, []) - - class TestSecurityGroupRuleList(TestSecurityGroupRule): def setUp(self): diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 7154327413..e35fbe165c 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -510,11 +510,11 @@ def create_one_security_group_rule(attrs=None, methods=None): 'direction': 'ingress', 'ethertype': 'IPv4', 'id': 'security-group-rule-id-' + uuid.uuid4().hex, - 'port_range_max': None, - 'port_range_min': None, - 'protocol': None, - 'remote_group_id': 'remote-security-group-id-' + uuid.uuid4().hex, - 'remote_ip_prefix': None, + 'port_range_max': 0, + 'port_range_min': 0, + 'protocol': 'tcp', + 'remote_group_id': None, + 'remote_ip_prefix': '0.0.0.0/0', 'security_group_id': 'security-group-id-' + uuid.uuid4().hex, 'tenant_id': 'project-id-' + uuid.uuid4().hex, } diff --git a/openstackclient/tests/network/v2/test_security_group_rule.py b/openstackclient/tests/network/v2/test_security_group_rule.py index db15d0e266..a0d6185c59 100644 --- a/openstackclient/tests/network/v2/test_security_group_rule.py +++ b/openstackclient/tests/network/v2/test_security_group_rule.py @@ -39,6 +39,317 @@ def setUp(self): self.compute = self.app.client_manager.compute +class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): + + # The security group rule to be created. + _security_group_rule = None + + # The security group that will contain the rule created. + _security_group = \ + network_fakes.FakeSecurityGroup.create_one_security_group() + + expected_columns = ( + 'direction', + 'ethertype', + 'id', + 'port_range_max', + 'port_range_min', + 'project_id', + 'protocol', + 'remote_group_id', + 'remote_ip_prefix', + 'security_group_id', + ) + + expected_data = None + + def _setup_security_group_rule(self, attrs=None): + self._security_group_rule = \ + network_fakes.FakeSecurityGroupRule.create_one_security_group_rule( + attrs) + self.network.create_security_group_rule = mock.Mock( + return_value=self._security_group_rule) + self.expected_data = ( + self._security_group_rule.direction, + self._security_group_rule.ethertype, + self._security_group_rule.id, + self._security_group_rule.port_range_max, + self._security_group_rule.port_range_min, + self._security_group_rule.project_id, + self._security_group_rule.protocol, + self._security_group_rule.remote_group_id, + self._security_group_rule.remote_ip_prefix, + self._security_group_rule.security_group_id, + ) + + def setUp(self): + super(TestCreateSecurityGroupRuleNetwork, self).setUp() + + self.network.find_security_group = mock.Mock( + return_value=self._security_group) + + # Get the command object to test + self.cmd = security_group_rule.CreateSecurityGroupRule( + self.app, self.namespace) + + def test_create_no_options(self): + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, [], []) + + def test_create_source_group_and_ip(self): + arglist = [ + '--src-ip', '10.10.0.0/24', + '--src-group', self._security_group.id, + self._security_group.id, + ] + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, arglist, []) + + def test_create_bad_protocol(self): + arglist = [ + '--protocol', 'foo', + self._security_group.id, + ] + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, arglist, []) + + def test_create_default_rule(self): + self._setup_security_group_rule({ + 'port_range_max': 443, + 'port_range_min': 443, + }) + arglist = [ + '--dst-port', str(self._security_group_rule.port_range_min), + self._security_group.id, + ] + verifylist = [ + ('group', self._security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_security_group_rule.assert_called_once_with(**{ + 'direction': self._security_group_rule.direction, + 'ethertype': self._security_group_rule.ethertype, + 'port_range_max': self._security_group_rule.port_range_max, + 'port_range_min': self._security_group_rule.port_range_min, + 'protocol': self._security_group_rule.protocol, + 'remote_ip_prefix': self._security_group_rule.remote_ip_prefix, + 'security_group_id': self._security_group.id, + }) + self.assertEqual(tuple(self.expected_columns), columns) + self.assertEqual(self.expected_data, data) + + def test_create_source_group(self): + self._setup_security_group_rule({ + 'port_range_max': 22, + 'port_range_min': 22, + 'remote_group_id': self._security_group.id, + }) + arglist = [ + '--dst-port', str(self._security_group_rule.port_range_min), + '--src-group', self._security_group.id, + self._security_group.id, + ] + verifylist = [ + ('dst_port', (self._security_group_rule.port_range_min, + self._security_group_rule.port_range_max)), + ('src_group', self._security_group.id), + ('group', self._security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_security_group_rule.assert_called_once_with(**{ + 'direction': self._security_group_rule.direction, + 'ethertype': self._security_group_rule.ethertype, + 'port_range_max': self._security_group_rule.port_range_max, + 'port_range_min': self._security_group_rule.port_range_min, + 'protocol': self._security_group_rule.protocol, + 'remote_group_id': self._security_group_rule.remote_group_id, + 'security_group_id': self._security_group.id, + }) + self.assertEqual(tuple(self.expected_columns), columns) + self.assertEqual(self.expected_data, data) + + def test_create_source_ip(self): + self._setup_security_group_rule({ + 'protocol': 'icmp', + 'port_range_max': -1, + 'port_range_min': -1, + 'remote_ip_prefix': '10.0.2.0/24', + }) + arglist = [ + '--proto', self._security_group_rule.protocol, + '--src-ip', self._security_group_rule.remote_ip_prefix, + self._security_group.id, + ] + verifylist = [ + ('proto', self._security_group_rule.protocol), + ('src_ip', self._security_group_rule.remote_ip_prefix), + ('group', self._security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_security_group_rule.assert_called_once_with(**{ + 'direction': self._security_group_rule.direction, + 'ethertype': self._security_group_rule.ethertype, + 'protocol': self._security_group_rule.protocol, + 'remote_ip_prefix': self._security_group_rule.remote_ip_prefix, + 'security_group_id': self._security_group.id, + }) + self.assertEqual(tuple(self.expected_columns), columns) + self.assertEqual(self.expected_data, data) + + +class TestCreateSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): + + # The security group rule to be created. + _security_group_rule = None + + # The security group that will contain the rule created. + _security_group = \ + compute_fakes.FakeSecurityGroup.create_one_security_group() + + def _setup_security_group_rule(self, attrs=None): + self._security_group_rule = \ + compute_fakes.FakeSecurityGroupRule.create_one_security_group_rule( + attrs) + self.compute.security_group_rules.create.return_value = \ + self._security_group_rule + expected_columns, expected_data = \ + security_group_rule._format_security_group_rule_show( + self._security_group_rule._info) + return expected_columns, expected_data + + def setUp(self): + super(TestCreateSecurityGroupRuleCompute, self).setUp() + + self.app.client_manager.network_endpoint_enabled = False + + self.compute.security_groups.get.return_value = self._security_group + + # Get the command object to test + self.cmd = security_group_rule.CreateSecurityGroupRule(self.app, None) + + def test_create_no_options(self): + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, [], []) + + def test_create_source_group_and_ip(self): + arglist = [ + '--src-ip', '10.10.0.0/24', + '--src-group', self._security_group.id, + self._security_group.id, + ] + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, arglist, []) + + def test_create_bad_protocol(self): + arglist = [ + '--protocol', 'foo', + self._security_group.id, + ] + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, arglist, []) + + def test_create_default_rule(self): + expected_columns, expected_data = self._setup_security_group_rule() + dst_port = str(self._security_group_rule.from_port) + ':' + \ + str(self._security_group_rule.to_port) + arglist = [ + '--dst-port', dst_port, + self._security_group.id, + ] + verifylist = [ + ('dst_port', (self._security_group_rule.from_port, + self._security_group_rule.to_port)), + ('group', self._security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.compute.security_group_rules.create.assert_called_once_with( + self._security_group.id, + self._security_group_rule.ip_protocol, + self._security_group_rule.from_port, + self._security_group_rule.to_port, + self._security_group_rule.ip_range['cidr'], + None, + ) + self.assertEqual(expected_columns, columns) + self.assertEqual(expected_data, data) + + def test_create_source_group(self): + expected_columns, expected_data = self._setup_security_group_rule({ + 'from_port': 22, + 'to_port': 22, + 'group': {'name': self._security_group.id}, + }) + arglist = [ + '--dst-port', str(self._security_group_rule.from_port), + '--src-group', self._security_group.id, + self._security_group.id, + ] + verifylist = [ + ('dst_port', (self._security_group_rule.from_port, + self._security_group_rule.to_port)), + ('src_group', self._security_group.id), + ('group', self._security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.compute.security_group_rules.create.assert_called_once_with( + self._security_group.id, + self._security_group_rule.ip_protocol, + self._security_group_rule.from_port, + self._security_group_rule.to_port, + self._security_group_rule.ip_range['cidr'], + self._security_group.id, + ) + self.assertEqual(expected_columns, columns) + self.assertEqual(expected_data, data) + + def test_create_source_ip(self): + expected_columns, expected_data = self._setup_security_group_rule({ + 'ip_protocol': 'icmp', + 'from_port': -1, + 'to_port': -1, + 'ip_range': {'cidr': '10.0.2.0/24'}, + }) + arglist = [ + '--proto', self._security_group_rule.ip_protocol, + '--src-ip', self._security_group_rule.ip_range['cidr'], + self._security_group.id, + ] + verifylist = [ + ('proto', self._security_group_rule.ip_protocol), + ('src_ip', self._security_group_rule.ip_range['cidr']), + ('group', self._security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.compute.security_group_rules.create.assert_called_once_with( + self._security_group.id, + self._security_group_rule.ip_protocol, + self._security_group_rule.from_port, + self._security_group_rule.to_port, + self._security_group_rule.ip_range['cidr'], + None, + ) + self.assertEqual(expected_columns, columns) + self.assertEqual(expected_data, data) + + class TestDeleteSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): # The security group rule to be deleted. @@ -68,7 +379,7 @@ def test_security_group_rule_delete(self): result = self.cmd.take_action(parsed_args) - self.network.delete_security_group_rule.assert_called_with( + self.network.delete_security_group_rule.assert_called_once_with( self._security_group_rule) self.assertIsNone(result) @@ -98,7 +409,7 @@ def test_security_group_rule_delete(self): result = self.cmd.take_action(parsed_args) - self.compute.security_group_rules.delete.assert_called_with( + self.compute.security_group_rules.delete.assert_called_once_with( self._security_group_rule.id) self.assertIsNone(result) @@ -160,7 +471,7 @@ def test_show_all_options(self): columns, data = self.cmd.take_action(parsed_args) - self.network.find_security_group_rule.assert_called_with( + self.network.find_security_group_rule.assert_called_once_with( self._security_group_rule.id, ignore_missing=False) self.assertEqual(tuple(self.columns), columns) self.assertEqual(self.data, data) @@ -207,6 +518,6 @@ def test_show_all_options(self): columns, data = self.cmd.take_action(parsed_args) - self.compute.security_groups.list.assert_called_with() + self.compute.security_groups.list.assert_called_once_with() self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) diff --git a/releasenotes/notes/bug-1519512-4231ac6014109142.yaml b/releasenotes/notes/bug-1519512-4231ac6014109142.yaml new file mode 100644 index 0000000000..c9beda4c80 --- /dev/null +++ b/releasenotes/notes/bug-1519512-4231ac6014109142.yaml @@ -0,0 +1,7 @@ +--- +upgrade: + - The ``security group rule create`` command now uses Network v2 + when enabled which results in a more detailed output for network + security group rules that matches the ``security group rule show`` + command. + [Bug `1519512 `_] diff --git a/setup.cfg b/setup.cfg index fcfd4e47bf..b177f8a01d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -100,7 +100,6 @@ openstack.compute.v2 = keypair_list = openstackclient.compute.v2.keypair:ListKeypair keypair_show = openstackclient.compute.v2.keypair:ShowKeypair - security_group_rule_create = openstackclient.compute.v2.security_group:CreateSecurityGroupRule security_group_rule_list = openstackclient.compute.v2.security_group:ListSecurityGroupRule server_add_security_group = openstackclient.compute.v2.server:AddServerSecurityGroup @@ -349,6 +348,8 @@ openstack.network.v2 = security_group_list = openstackclient.network.v2.security_group:ListSecurityGroup security_group_set = openstackclient.network.v2.security_group:SetSecurityGroup security_group_show = openstackclient.network.v2.security_group:ShowSecurityGroup + + security_group_rule_create = openstackclient.network.v2.security_group_rule:CreateSecurityGroupRule security_group_rule_delete = openstackclient.network.v2.security_group_rule:DeleteSecurityGroupRule security_group_rule_show = openstackclient.network.v2.security_group_rule:ShowSecurityGroupRule From 2109bce85a40da3e3cbebed1cac661611564ccb2 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Thu, 17 Mar 2016 12:58:44 -0500 Subject: [PATCH 0750/3095] Support security group name for --src-group Support security group name for the "--src-group" option on the "os security group rule create" command. Change-Id: Ic23d0671dad77566269c9a588644c8d774368733 Closes-Bug: #1540656 --- doc/source/command-objects/security-group-rule.rst | 2 +- openstackclient/network/v2/security_group_rule.py | 12 ++++++++++-- .../tests/network/v2/test_security_group_rule.py | 10 +++++----- releasenotes/notes/bug-1540656-f7b7b7e3feef2440.yaml | 5 +++++ 4 files changed, 21 insertions(+), 8 deletions(-) create mode 100644 releasenotes/notes/bug-1540656-f7b7b7e3feef2440.yaml diff --git a/doc/source/command-objects/security-group-rule.rst b/doc/source/command-objects/security-group-rule.rst index ad496e79d1..fa07c3772c 100644 --- a/doc/source/command-objects/security-group-rule.rst +++ b/doc/source/command-objects/security-group-rule.rst @@ -28,7 +28,7 @@ Create a new security group rule .. option:: --src-group - Source security group (ID only) + Source security group (name or ID) .. option:: --dst-port diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index e024465432..f60995ab2f 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -69,7 +69,7 @@ def update_parser_common(self, parser): source_group.add_argument( "--src-group", metavar="", - help="Source security group (ID only)", + help="Source security group (name or ID)", ) parser.add_argument( "--dst-port", @@ -103,7 +103,10 @@ def take_action_network(self, client, parsed_args): attrs['port_range_max'] = parsed_args.dst_port[1] attrs['protocol'] = parsed_args.proto if parsed_args.src_group is not None: - attrs['remote_group_id'] = parsed_args.src_group + attrs['remote_group_id'] = client.find_security_group( + parsed_args.src_group, + ignore_missing=False + ).id else: attrs['remote_ip_prefix'] = parsed_args.src_ip attrs['security_group_id'] = security_group_id @@ -123,6 +126,11 @@ def take_action_compute(self, client, parsed_args): from_port, to_port = -1, -1 else: from_port, to_port = parsed_args.dst_port + if parsed_args.src_group is not None: + parsed_args.src_group = utils.find_resource( + client.security_groups, + parsed_args.src_group, + ).id obj = client.security_group_rules.create( group.id, parsed_args.proto, diff --git a/openstackclient/tests/network/v2/test_security_group_rule.py b/openstackclient/tests/network/v2/test_security_group_rule.py index a0d6185c59..81b9e18bdc 100644 --- a/openstackclient/tests/network/v2/test_security_group_rule.py +++ b/openstackclient/tests/network/v2/test_security_group_rule.py @@ -149,13 +149,13 @@ def test_create_source_group(self): }) arglist = [ '--dst-port', str(self._security_group_rule.port_range_min), - '--src-group', self._security_group.id, + '--src-group', self._security_group.name, self._security_group.id, ] verifylist = [ ('dst_port', (self._security_group_rule.port_range_min, self._security_group_rule.port_range_max)), - ('src_group', self._security_group.id), + ('src_group', self._security_group.name), ('group', self._security_group.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -289,17 +289,17 @@ def test_create_source_group(self): expected_columns, expected_data = self._setup_security_group_rule({ 'from_port': 22, 'to_port': 22, - 'group': {'name': self._security_group.id}, + 'group': {'name': self._security_group.name}, }) arglist = [ '--dst-port', str(self._security_group_rule.from_port), - '--src-group', self._security_group.id, + '--src-group', self._security_group.name, self._security_group.id, ] verifylist = [ ('dst_port', (self._security_group_rule.from_port, self._security_group_rule.to_port)), - ('src_group', self._security_group.id), + ('src_group', self._security_group.name), ('group', self._security_group.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/releasenotes/notes/bug-1540656-f7b7b7e3feef2440.yaml b/releasenotes/notes/bug-1540656-f7b7b7e3feef2440.yaml new file mode 100644 index 0000000000..4abb284425 --- /dev/null +++ b/releasenotes/notes/bug-1540656-f7b7b7e3feef2440.yaml @@ -0,0 +1,5 @@ +--- +features: + - The ``security group rule create`` command now supports a security + group name for the ``--src-group`` option. + [Bug `1540656 `_] From 860dbc132d512c6df16d29875bb6388d10d9fab8 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Thu, 3 Mar 2016 17:17:40 +0800 Subject: [PATCH 0751/3095] [Floating IP] Neutron support for "ip floating create" command This patch adds --subnet, --port, --floating-ip-address and --fixed-ip-address options only. Project related options will be added in another patch because it relates to identity v2 and v3, which will make the unit tests too complicated in one single patch. Change-Id: I3cce4404a114ff128b74e4596f0e847be2846b17 Partial-Bug: 1519502 Related-to: blueprint neutron-client --- doc/source/command-objects/ip-floating.rst | 30 +++- openstackclient/compute/v2/floatingip.py | 23 --- openstackclient/network/v2/floating_ip.py | 83 +++++++++ .../tests/network/v2/test_floating_ip.py | 166 ++++++++++++++++++ .../notes/bug-1519502-d534db6c18adef20.yaml | 4 + setup.cfg | 2 +- 6 files changed, 281 insertions(+), 27 deletions(-) create mode 100644 releasenotes/notes/bug-1519502-d534db6c18adef20.yaml diff --git a/doc/source/command-objects/ip-floating.rst b/doc/source/command-objects/ip-floating.rst index 05e4a13243..869b7af88f 100644 --- a/doc/source/command-objects/ip-floating.rst +++ b/doc/source/command-objects/ip-floating.rst @@ -33,11 +33,35 @@ Create new floating IP address .. code:: bash os ip floating create - + [--subnet ] + [--port ] + [--floating-ip-address ] + [--fixed-ip-address ] + -.. describe:: +.. option:: --subnet - Pool to fetch IP address from (name or ID) + Subnet on which you want to create the floating IP (name or ID) + (Network v2 only) + +.. option:: --port + + Port to be associated with the floating IP (name or ID) + (Network v2 only) + +.. option:: --floating-ip-address + + Floating IP address + (Network v2 only) + +.. option:: --fixed-ip-address + + Fixed IP address mapped to the floating IP + (Network v2 only) + +.. describe:: + + Network to allocate floating IP from (name or ID) ip floating delete ------------------ diff --git a/openstackclient/compute/v2/floatingip.py b/openstackclient/compute/v2/floatingip.py index 6212989f3b..fac4d2e361 100644 --- a/openstackclient/compute/v2/floatingip.py +++ b/openstackclient/compute/v2/floatingip.py @@ -15,8 +15,6 @@ """Floating IP action implementations""" -import six - from openstackclient.common import command from openstackclient.common import utils @@ -47,27 +45,6 @@ def take_action(self, parsed_args): server.add_floating_ip(parsed_args.ip_address) -class CreateFloatingIP(command.ShowOne): - """Create new floating IP address""" - - def get_parser(self, prog_name): - parser = super(CreateFloatingIP, self).get_parser(prog_name) - parser.add_argument( - 'pool', - metavar='', - help='Pool to fetch IP address from (name or ID)', - ) - return parser - - def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - floating_ip = compute_client.floating_ips.create(parsed_args.pool) - - info = {} - info.update(floating_ip._info) - return zip(*sorted(six.iteritems(info))) - - class RemoveFloatingIP(command.Command): """Remove floating IP address from server""" diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index 16f2b57472..b21d6e968d 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -25,6 +25,89 @@ def _get_columns(item): return tuple(sorted(columns)) +def _get_attrs(client_manager, parsed_args): + attrs = {} + network_client = client_manager.network + + if parsed_args.network is not None: + network = network_client.find_network(parsed_args.network, + ignore_missing=False) + attrs['floating_network_id'] = network.id + + if parsed_args.subnet is not None: + subnet = network_client.find_subnet(parsed_args.subnet, + ignore_missing=False) + attrs['subnet_id'] = subnet.id + + if parsed_args.port is not None: + port = network_client.find_port(parsed_args.port, + ignore_missing=False) + attrs['port_id'] = port.id + + if parsed_args.floating_ip_address is not None: + attrs['floating_ip_address'] = parsed_args.floating_ip_address + + if parsed_args.fixed_ip_address is not None: + attrs['fixed_ip_address'] = parsed_args.fixed_ip_address + + return attrs + + +class CreateFloatingIP(common.NetworkAndComputeShowOne): + """Create floating IP""" + + def update_parser_common(self, parser): + # In Compute v2 network, floating IPs could be allocated from floating + # IP pools, which are actually external networks. So deprecate the + # parameter "pool", and use "network" instead. + parser.add_argument( + 'network', + metavar='', + help='Network to allocate floating IP from (name or ID)', + ) + return parser + + def update_parser_network(self, parser): + parser.add_argument( + '--subnet', + metavar='', + help="Subnet on which you want to create the floating IP " + "(name or ID)" + ) + parser.add_argument( + '--port', + metavar='', + help="Port to be associated with the floating IP " + "(name or ID)" + ) + parser.add_argument( + '--floating-ip-address', + metavar='', + dest='floating_ip_address', + help="Floating IP address" + ) + parser.add_argument( + '--fixed-ip-address', + metavar='', + dest='fixed_ip_address', + help="Fixed IP address mapped to the floating IP" + ) + return parser + + def take_action_network(self, client, parsed_args): + attrs = _get_attrs(self.app.client_manager, parsed_args) + obj = client.create_ip(**attrs) + columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns) + return (columns, data) + + def take_action_compute(self, client, parsed_args): + obj = client.floating_ips.create(parsed_args.network) + columns = _get_columns(obj._info) + data = utils.get_dict_properties(obj._info, columns) + return (columns, data) + + class DeleteFloatingIP(common.NetworkAndComputeCommand): """Delete floating IP""" diff --git a/openstackclient/tests/network/v2/test_floating_ip.py b/openstackclient/tests/network/v2/test_floating_ip.py index c9da0fa07d..3e261fb5ef 100644 --- a/openstackclient/tests/network/v2/test_floating_ip.py +++ b/openstackclient/tests/network/v2/test_floating_ip.py @@ -16,6 +16,7 @@ from openstackclient.network.v2 import floating_ip from openstackclient.tests.compute.v2 import fakes as compute_fakes from openstackclient.tests.network.v2 import fakes as network_fakes +from openstackclient.tests import utils as tests_utils # Tests for Neutron network @@ -29,6 +30,115 @@ def setUp(self): self.network = self.app.client_manager.network +class TestCreateFloatingIPNetwork(TestFloatingIPNetwork): + + # Fake data for option tests. + floating_network = network_fakes.FakeNetwork.create_one_network() + subnet = network_fakes.FakeSubnet.create_one_subnet() + port = network_fakes.FakePort.create_one_port() + + # The floating ip to be deleted. + floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip( + attrs={ + 'floating_network_id': floating_network.id, + 'port_id': port.id, + } + ) + + columns = ( + 'dns_domain', + 'dns_name', + 'fixed_ip_address', + 'floating_ip_address', + 'floating_network_id', + 'id', + 'port_id', + 'project_id', + 'router_id', + 'status', + ) + + data = ( + floating_ip.dns_domain, + floating_ip.dns_name, + floating_ip.fixed_ip_address, + floating_ip.floating_ip_address, + floating_ip.floating_network_id, + floating_ip.id, + floating_ip.port_id, + floating_ip.project_id, + floating_ip.router_id, + floating_ip.status, + ) + + def setUp(self): + super(TestCreateFloatingIPNetwork, self).setUp() + + self.network.create_ip = mock.Mock(return_value=self.floating_ip) + + self.network.find_network = mock.Mock( + return_value=self.floating_network) + self.network.find_subnet = mock.Mock(return_value=self.subnet) + self.network.find_port = mock.Mock(return_value=self.port) + + # Get the command object to test + self.cmd = floating_ip.CreateFloatingIP(self.app, self.namespace) + + def test_create_no_options(self): + arglist = [] + verifylist = [] + + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_create_default_options(self): + arglist = [ + self.floating_ip.floating_network_id, + ] + verifylist = [ + ('network', self.floating_ip.floating_network_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_ip.assert_called_once_with(**{ + 'floating_network_id': self.floating_ip.floating_network_id, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_all_options(self): + arglist = [ + '--subnet', self.subnet.id, + '--port', self.floating_ip.port_id, + '--floating-ip-address', self.floating_ip.floating_ip_address, + '--fixed-ip-address', self.floating_ip.fixed_ip_address, + self.floating_ip.floating_network_id, + ] + verifylist = [ + ('subnet', self.subnet.id), + ('port', self.floating_ip.port_id), + ('floating_ip_address', self.floating_ip.floating_ip_address), + ('fixed_ip_address', self.floating_ip.fixed_ip_address), + ('network', self.floating_ip.floating_network_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_ip.assert_called_once_with(**{ + 'subnet_id': self.subnet.id, + 'port_id': self.floating_ip.port_id, + 'floating_ip_address': self.floating_ip.floating_ip_address, + 'fixed_ip_address': self.floating_ip.fixed_ip_address, + 'floating_network_id': self.floating_ip.floating_network_id, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + class TestDeleteFloatingIPNetwork(TestFloatingIPNetwork): # The floating ip to be deleted. @@ -169,6 +279,62 @@ def setUp(self): self.compute = self.app.client_manager.compute +class TestCreateFloatingIPCompute(TestFloatingIPCompute): + + # The floating ip to be deleted. + floating_ip = compute_fakes.FakeFloatingIP.create_one_floating_ip() + + columns = ( + 'fixed_ip', + 'id', + 'instance_id', + 'ip', + 'pool', + ) + + data = ( + floating_ip.fixed_ip, + floating_ip.id, + floating_ip.instance_id, + floating_ip.ip, + floating_ip.pool, + ) + + def setUp(self): + super(TestCreateFloatingIPCompute, self).setUp() + + self.app.client_manager.network_endpoint_enabled = False + + self.compute.floating_ips.create.return_value = self.floating_ip + + # Get the command object to test + self.cmd = floating_ip.CreateFloatingIP(self.app, None) + + def test_create_no_options(self): + arglist = [] + verifylist = [] + + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_create_default_options(self): + arglist = [ + self.floating_ip.pool, + ] + verifylist = [ + ('network', self.floating_ip.pool), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.compute.floating_ips.create.assert_called_once_with( + self.floating_ip.pool) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + class TestDeleteFloatingIPCompute(TestFloatingIPCompute): # The floating ip to be deleted. diff --git a/releasenotes/notes/bug-1519502-d534db6c18adef20.yaml b/releasenotes/notes/bug-1519502-d534db6c18adef20.yaml new file mode 100644 index 0000000000..7f089b9884 --- /dev/null +++ b/releasenotes/notes/bug-1519502-d534db6c18adef20.yaml @@ -0,0 +1,4 @@ +--- +features: + - Command ``ip floating create`` is now available for neutron network. + [Bug `1519502 `_] diff --git a/setup.cfg b/setup.cfg index fcfd4e47bf..0ec5f4c166 100644 --- a/setup.cfg +++ b/setup.cfg @@ -91,7 +91,6 @@ openstack.compute.v2 = ip_fixed_remove = openstackclient.compute.v2.fixedip:RemoveFixedIP ip_floating_add = openstackclient.compute.v2.floatingip:AddFloatingIP - ip_floating_create = openstackclient.compute.v2.floatingip:CreateFloatingIP ip_floating_remove = openstackclient.compute.v2.floatingip:RemoveFloatingIP ip_floating_pool_list = openstackclient.compute.v2.floatingippool:ListFloatingIPPool @@ -322,6 +321,7 @@ openstack.image.v2 = image_set = openstackclient.image.v2.image:SetImage openstack.network.v2 = + ip_floating_create = openstackclient.network.v2.floating_ip:CreateFloatingIP ip_floating_delete = openstackclient.network.v2.floating_ip:DeleteFloatingIP ip_floating_list = openstackclient.network.v2.floating_ip:ListFloatingIP ip_floating_show = openstackclient.network.v2.floating_ip:ShowFloatingIP From 2b95e363d325c686db229a9da1d9a3a7677e8294 Mon Sep 17 00:00:00 2001 From: reedip Date: Tue, 15 Mar 2016 09:37:10 +0900 Subject: [PATCH 0752/3095] Subnet: Add "subnet set" command using SDK This patch adds "subnet set" command to osc using sdk. Implements: blueprint neutron-client Closes-bug: #1542363 Change-Id: Id3b7f4b9190b4d73ca3ae423321a65f94a6da62e --- doc/source/command-objects/subnet.rst | 61 ++++- functional/tests/network/v2/test_subnet.py | 3 - openstackclient/network/v2/subnet.py | 215 +++++++++++------- .../tests/network/v2/test_subnet.py | 74 +++++- .../notes/subnet-set-bbc26ecc16929302.yaml | 5 + setup.cfg | 1 + 6 files changed, 275 insertions(+), 84 deletions(-) create mode 100644 releasenotes/notes/subnet-set-bbc26ecc16929302.yaml diff --git a/doc/source/command-objects/subnet.rst b/doc/source/command-objects/subnet.rst index 56a28d737d..eb64c9e732 100644 --- a/doc/source/command-objects/subnet.rst +++ b/doc/source/command-objects/subnet.rst @@ -21,7 +21,7 @@ Delete a subnet Subnet to delete (name or ID) subnet create --------------- +------------- Create new subnet @@ -143,6 +143,65 @@ List subnets List additional fields in output +subnet set +---------- + +Set subnet properties + +.. program:: subnet set +.. code:: bash + + os subnet set + [--allocation-pool start=,end=] + [--dhcp | --no-dhcp] + [--dns-nameserver ] + [--gateway ] + [--host-route destination=,gateway=] + [--name ] + + +.. option:: --allocation-pool start=,end= + + Allocation pool IP addresses for this subnet e.g.: + start=192.168.199.2,end=192.168.199.254 (This option can be repeated) + +.. option:: --dhcp + + Enable DHCP + +.. option:: --no-dhcp + + Disable DHCP + +.. option:: --dns-nameserver + + DNS name server for this subnet (This option can be repeated) + +.. option:: --gateway + + Specify a gateway for the subnet. The options are: + : Specific IP address to use as the gateway + 'none': This subnet will not use a gateway + e.g.: --gateway 192.168.9.1, --gateway none + +.. option:: --host-route destination=,gateway= + + Additional route for this subnet e.g.: + destination=10.10.0.0/16,gateway=192.168.71.254 + destination: destination subnet (in CIDR notation) + gateway: nexthop IP address + (This option can be repeated) + +.. option:: --name + + Updated name of the subnet + +.. _subnet_set-subnet: +.. describe:: + + Subnet to modify (name or ID) + + subnet show ----------- diff --git a/functional/tests/network/v2/test_subnet.py b/functional/tests/network/v2/test_subnet.py index afecfab07b..7697e0f4e6 100644 --- a/functional/tests/network/v2/test_subnet.py +++ b/functional/tests/network/v2/test_subnet.py @@ -12,8 +12,6 @@ import uuid -import testtools - from functional.common import test @@ -49,7 +47,6 @@ def test_subnet_list(self): raw_output = self.openstack('subnet list' + opts) self.assertIn(self.NAME, raw_output) - @testtools.skip('bug/1542363') def test_subnet_set(self): self.openstack('subnet set --no-dhcp ' + self.NAME) opts = self.get_show_opts(['name', 'enable_dhcp']) diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 794c787f07..da4f6536ec 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -17,6 +17,7 @@ from json.encoder import JSONEncoder from openstackclient.common import command +from openstackclient.common import exceptions from openstackclient.common import parseractions from openstackclient.common import utils from openstackclient.identity import common as identity_common @@ -42,6 +43,39 @@ def _format_host_routes(data): } +def _get_common_parse_arguments(parser): + parser.add_argument( + '--allocation-pool', + metavar='start=,end=', + dest='allocation_pools', + action=parseractions.MultiKeyValueAction, + required_keys=['start', 'end'], + help='Allocation pool IP addresses for this subnet ' + 'e.g.: start=192.168.199.2,end=192.168.199.254 ' + '(This option can be repeated)', + ) + parser.add_argument( + '--dns-nameserver', + metavar='', + action='append', + dest='dns_nameservers', + help='DNS name server for this subnet ' + '(This option can be repeated)', + ) + parser.add_argument( + '--host-route', + metavar='destination=,gateway=', + dest='host_routes', + action=parseractions.MultiKeyValueAction, + required_keys=['destination', 'gateway'], + help='Additional route for this subnet ' + 'e.g.: destination=10.10.0.0/16,gateway=192.168.71.254 ' + 'destination: destination subnet (in CIDR notation) ' + 'gateway: nexthop IP address ' + '(This option can be repeated)', + ) + + def _get_columns(item): columns = list(item.keys()) if 'tenant_id' in columns: @@ -70,57 +104,66 @@ def convert_entries_to_gateway(entries): return changed_entries -def _get_attrs(client_manager, parsed_args): +def _get_attrs(client_manager, parsed_args, is_create=True): attrs = {} - if parsed_args.name is not None: + if 'name' in parsed_args and parsed_args.name is not None: attrs['name'] = str(parsed_args.name) - if 'project' in parsed_args and parsed_args.project is not None: - identity_client = client_manager.identity - project_id = identity_common.find_project( - identity_client, - parsed_args.project, - parsed_args.project_domain, - ).id - attrs['tenant_id'] = project_id - - client = client_manager.network - attrs['network_id'] = client.find_network(parsed_args.network, - ignore_missing=False).id - - if parsed_args.subnet_pool is not None: - subnet_pool = client.find_subnet_pool(parsed_args.subnet_pool, - ignore_missing=False) - attrs['subnetpool_id'] = subnet_pool.id - - if parsed_args.use_default_subnet_pool: - attrs['use_default_subnetpool'] = True - if parsed_args.gateway.lower() != 'auto': - if parsed_args.gateway.lower() == 'none': - attrs['gateway_ip'] = None - else: - attrs['gateway_ip'] = parsed_args.gateway - if parsed_args.prefix_length is not None: - attrs['prefixlen'] = parsed_args.prefix_length - if parsed_args.subnet_range is not None: - attrs['cidr'] = parsed_args.subnet_range - if parsed_args.ip_version is not None: - attrs['ip_version'] = parsed_args.ip_version - if parsed_args.ipv6_ra_mode is not None: - attrs['ipv6_ra_mode'] = parsed_args.ipv6_ra_mode - if parsed_args.ipv6_address_mode is not None: - attrs['ipv6_address_mode'] = parsed_args.ipv6_address_mode - if parsed_args.allocation_pools is not None: + if is_create: + if 'project' in parsed_args and parsed_args.project is not None: + identity_client = client_manager.identity + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + attrs['tenant_id'] = project_id + client = client_manager.network + attrs['network_id'] = client.find_network(parsed_args.network, + ignore_missing=False).id + if parsed_args.subnet_pool is not None: + subnet_pool = client.find_subnet_pool(parsed_args.subnet_pool, + ignore_missing=False) + attrs['subnetpool_id'] = subnet_pool.id + if parsed_args.use_default_subnet_pool: + attrs['use_default_subnetpool'] = True + if parsed_args.prefix_length is not None: + attrs['prefixlen'] = parsed_args.prefix_length + if parsed_args.subnet_range is not None: + attrs['cidr'] = parsed_args.subnet_range + if parsed_args.ip_version is not None: + attrs['ip_version'] = parsed_args.ip_version + if parsed_args.ipv6_ra_mode is not None: + attrs['ipv6_ra_mode'] = parsed_args.ipv6_ra_mode + if parsed_args.ipv6_address_mode is not None: + attrs['ipv6_address_mode'] = parsed_args.ipv6_address_mode + + if 'gateway' in parsed_args and parsed_args.gateway is not None: + gateway = parsed_args.gateway.lower() + + if not is_create and gateway == 'auto': + raise exceptions.CommandError("Auto option is not available" + " for Subnet Set. Valid options are" + " or none") + elif gateway != 'auto': + if gateway == 'none': + attrs['gateway_ip'] = None + else: + attrs['gateway_ip'] = gateway + if ('allocation_pools' in parsed_args and + parsed_args.allocation_pools is not None): attrs['allocation_pools'] = parsed_args.allocation_pools - if parsed_args.enable_dhcp is not None: - attrs['enable_dhcp'] = parsed_args.enable_dhcp - if parsed_args.dns_nameservers is not None: + if parsed_args.dhcp: + attrs['enable_dhcp'] = True + elif parsed_args.no_dhcp: + attrs['enable_dhcp'] = False + if ('dns_nameservers' in parsed_args and + parsed_args.dns_nameservers is not None): attrs['dns_nameservers'] = parsed_args.dns_nameservers - if parsed_args.host_routes is not None: + if 'host_routes' in parsed_args and parsed_args.host_routes is not None: # Change 'gateway' entry to 'nexthop' to match the API attrs['host_routes'] = convert_entries_to_nexthop( parsed_args.host_routes) - return attrs @@ -163,38 +206,18 @@ def get_parser(self, prog_name): '(required if --subnet-pool is not specified, ' 'optional otherwise)', ) - parser.add_argument( - '--allocation-pool', - metavar='start=,end=', - dest='allocation_pools', - action=parseractions.MultiKeyValueAction, - required_keys=['start', 'end'], - help='Allocation pool IP addresses for this subnet ' - 'e.g.: start=192.168.199.2,end=192.168.199.254 ' - '(This option can be repeated)', - ) dhcp_enable_group = parser.add_mutually_exclusive_group() dhcp_enable_group.add_argument( '--dhcp', - dest='enable_dhcp', action='store_true', default=True, help='Enable DHCP (default)', ) dhcp_enable_group.add_argument( '--no-dhcp', - dest='enable_dhcp', - action='store_false', + action='store_true', help='Disable DHCP', ) - parser.add_argument( - '--dns-nameserver', - metavar='', - action='append', - dest='dns_nameservers', - help='DNS name server for this subnet ' - '(This option can be repeated)', - ) parser.add_argument( '--gateway', metavar='', @@ -207,18 +230,6 @@ def get_parser(self, prog_name): "e.g.: --gateway 192.168.9.1, --gateway auto, --gateway none" "(default is 'auto')", ) - parser.add_argument( - '--host-route', - metavar='destination=,gateway=', - dest='host_routes', - action=parseractions.MultiKeyValueAction, - required_keys=['destination', 'gateway'], - help='Additional route for this subnet ' - 'e.g.: destination=10.10.0.0/16,gateway=192.168.71.254 ' - 'destination: destination subnet (in CIDR notation) ' - 'gateway: nexthop IP address ' - '(This option can be repeated)', - ) parser.add_argument( '--ip-version', type=int, @@ -246,7 +257,7 @@ def get_parser(self, prog_name): metavar='', help='Network this subnet belongs to (name or ID)', ) - + _get_common_parse_arguments(parser) return parser def take_action(self, parsed_args): @@ -309,6 +320,56 @@ def take_action(self, parsed_args): ) for s in data)) +class SetSubnet(command.Command): + """Set subnet properties""" + + def get_parser(self, prog_name): + parser = super(SetSubnet, self).get_parser(prog_name) + parser.add_argument( + 'subnet', + metavar="", + help=("Subnet to modify (name or ID)") + ) + parser.add_argument( + '--name', + metavar='', + help='Updated name of the subnet', + ) + dhcp_enable_group = parser.add_mutually_exclusive_group() + dhcp_enable_group.add_argument( + '--dhcp', + action='store_true', + default=None, + help='Enable DHCP', + ) + dhcp_enable_group.add_argument( + '--no-dhcp', + action='store_true', + help='Disable DHCP', + ) + parser.add_argument( + '--gateway', + metavar='', + help="Specify a gateway for the subnet. The options are: " + " : Specific IP address to use as the gateway " + " 'none': This subnet will not use a gateway " + "e.g.: --gateway 192.168.9.1, --gateway none" + ) + _get_common_parse_arguments(parser) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.find_subnet(parsed_args.subnet, ignore_missing=False) + attrs = _get_attrs(self.app.client_manager, parsed_args, + is_create=False) + if not attrs: + msg = "Nothing specified to be set" + raise exceptions.CommandError(msg) + client.update_subnet(obj, **attrs) + return + + class ShowSubnet(command.ShowOne): """Show subnet details""" diff --git a/openstackclient/tests/network/v2/test_subnet.py b/openstackclient/tests/network/v2/test_subnet.py index b718d262ae..80501483eb 100644 --- a/openstackclient/tests/network/v2/test_subnet.py +++ b/openstackclient/tests/network/v2/test_subnet.py @@ -14,6 +14,7 @@ import copy import mock +from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.network.v2 import subnet as subnet_v2 from openstackclient.tests import fakes @@ -203,9 +204,9 @@ def test_create_default_options(self): self.network.find_network = mock.Mock(return_value=self._network) arglist = [ - self._subnet.name, "--subnet-range", self._subnet.cidr, "--network", self._subnet.network_id, + self._subnet.name, ] verifylist = [ ('name', self._subnet.name), @@ -266,7 +267,7 @@ def test_create_from_subnet_pool_options(self): ('ip_version', self._subnet_from_pool.ip_version), ('gateway', self._subnet_from_pool.gateway_ip), ('dns_nameservers', self._subnet_from_pool.dns_nameservers), - ('enable_dhcp', self._subnet_from_pool.enable_dhcp), + ('dhcp', self._subnet_from_pool.enable_dhcp), ('host_routes', subnet_v2.convert_entries_to_gateway( self._subnet_from_pool.host_routes)), ('subnet_pool', self._subnet_from_pool.subnetpool_id), @@ -332,7 +333,7 @@ def test_create_options_subnet_range_ipv6(self): ('ipv6_address_mode', self._subnet_ipv6.ipv6_address_mode), ('gateway', self._subnet_ipv6.gateway_ip), ('dns_nameservers', self._subnet_ipv6.dns_nameservers), - ('enable_dhcp', self._subnet_ipv6.enable_dhcp), + ('dhcp', self._subnet_ipv6.enable_dhcp), ('host_routes', subnet_v2.convert_entries_to_gateway( self._subnet_ipv6.host_routes)), ('allocation_pools', self._subnet_ipv6.allocation_pools), @@ -469,6 +470,73 @@ def test_subnet_list_long(self): self.assertEqual(self.data_long, list(data)) +class TestSetSubnet(TestSubnet): + + _subnet = network_fakes.FakeSubnet.create_one_subnet() + + def setUp(self): + super(TestSetSubnet, self).setUp() + self.network.update_subnet = mock.Mock(return_value=None) + self.network.find_subnet = mock.Mock(return_value=self._subnet) + self.cmd = subnet_v2.SetSubnet(self.app, self.namespace) + + def test_set_this(self): + arglist = [ + "--name", "new_subnet", + "--dhcp", + "--gateway", self._subnet.gateway_ip, + self._subnet.name, + ] + verifylist = [ + ('name', "new_subnet"), + ('dhcp', True), + ('gateway', self._subnet.gateway_ip), + ('subnet', self._subnet.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + attrs = { + 'enable_dhcp': True, + 'gateway_ip': self._subnet.gateway_ip, + 'name': "new_subnet", + } + self.network.update_subnet.assert_called_with(self._subnet, **attrs) + self.assertIsNone(result) + + def test_set_that(self): + arglist = [ + "--name", "new_subnet", + "--no-dhcp", + "--gateway", "none", + self._subnet.name, + ] + verifylist = [ + ('name', "new_subnet"), + ('no_dhcp', True), + ('gateway', "none"), + ('subnet', self._subnet.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + attrs = { + 'enable_dhcp': False, + 'gateway_ip': None, + 'name': "new_subnet", + } + self.network.update_subnet.assert_called_with(self._subnet, **attrs) + self.assertIsNone(result) + + def test_set_nothing(self): + arglist = [self._subnet.name, ] + verifylist = [('subnet', self._subnet.name)] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + + class TestShowSubnet(TestSubnet): # The subnets to be shown _subnet = network_fakes.FakeSubnet.create_one_subnet() diff --git a/releasenotes/notes/subnet-set-bbc26ecc16929302.yaml b/releasenotes/notes/subnet-set-bbc26ecc16929302.yaml new file mode 100644 index 0000000000..8fb0c3275b --- /dev/null +++ b/releasenotes/notes/subnet-set-bbc26ecc16929302.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``subnet set`` command. + [Bug `1542363 `_] diff --git a/setup.cfg b/setup.cfg index fcfd4e47bf..e2e27f9e2d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -355,6 +355,7 @@ openstack.network.v2 = subnet_create = openstackclient.network.v2.subnet:CreateSubnet subnet_delete = openstackclient.network.v2.subnet:DeleteSubnet subnet_list = openstackclient.network.v2.subnet:ListSubnet + subnet_set = openstackclient.network.v2.subnet:SetSubnet subnet_show = openstackclient.network.v2.subnet:ShowSubnet subnet_pool_create = openstackclient.network.v2.subnet_pool:CreateSubnetPool From 4cbcd02a5787f402a95c84da613d3f8cd7d09312 Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Mon, 21 Mar 2016 16:29:14 +0800 Subject: [PATCH 0753/3095] Add "aggregate unset" to osc Support "aggregate unset" command in order to remove the property of aggregate object in OSC. Change-Id: I49645135586362f0fd251f5e4a4c03eff273d9e9 Closes-Bug: #1559866 --- doc/source/command-objects/aggregate.rst | 23 +++++++ openstackclient/compute/v2/aggregate.py | 31 +++++++++ openstackclient/tests/compute/v2/fakes.py | 32 +++++++++ .../tests/compute/v2/test_aggregate.py | 66 +++++++++++++++++++ .../notes/bug-1559866-733988f5dd5b07bb.yaml | 4 ++ setup.cfg | 1 + 6 files changed, 157 insertions(+) create mode 100644 openstackclient/tests/compute/v2/test_aggregate.py create mode 100644 releasenotes/notes/bug-1559866-733988f5dd5b07bb.yaml diff --git a/doc/source/command-objects/aggregate.rst b/doc/source/command-objects/aggregate.rst index 6a1dd9089e..be403179ea 100644 --- a/doc/source/command-objects/aggregate.rst +++ b/doc/source/command-objects/aggregate.rst @@ -150,3 +150,26 @@ Display aggregate details .. describe:: Aggregate to display (name or ID) + +aggregate unset +--------------- + +Unset aggregate properties + +.. program:: aggregate unset +.. code-block:: bash + + os aggregate unset + --property + [--property ] ... + + +.. option:: --property + + Property to remove from :ref:`\ ` + (repeat option to remove multiple properties) + +.. _aggregate_unset-aggregate: +.. describe:: + + Aggregate to modify (name or ID) diff --git a/openstackclient/compute/v2/aggregate.py b/openstackclient/compute/v2/aggregate.py index e47c13a7b2..1a02a3886d 100644 --- a/openstackclient/compute/v2/aggregate.py +++ b/openstackclient/compute/v2/aggregate.py @@ -290,3 +290,34 @@ def take_action(self, parsed_args): info = {} info.update(data._info) return zip(*sorted(six.iteritems(info))) + + +class UnsetAggregate(command.Command): + """Unset aggregate properties""" + + def get_parser(self, prog_name): + parser = super(UnsetAggregate, self).get_parser(prog_name) + parser.add_argument( + "aggregate", + metavar="", + help="Aggregate to modify (name or ID)", + ) + parser.add_argument( + "--property", + metavar="", + action='append', + help='Property to remove from aggregate ' + '(repeat option to remove multiple properties)', + required=True, + ) + return parser + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + aggregate = utils.find_resource( + compute_client.aggregates, + parsed_args.aggregate) + + unset_property = {key: None for key in parsed_args.property} + compute_client.aggregates.set_metadata(aggregate, + unset_property) diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 809825d0a5..2bb0fbfc9b 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -88,6 +88,38 @@ } +class FakeAggregate(object): + """Fake one aggregate.""" + + @staticmethod + def create_one_aggregate(attrs=None): + """Create a fake aggregate. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object, with id and other attributes + """ + if attrs is None: + attrs = {} + + # Set default attribute + aggregate_info = { + "name": "aggregate-name-" + uuid.uuid4().hex, + "availability_zone": "ag_zone", + "hosts": [], + "id": "aggregate-id-" + uuid.uuid4().hex, + "metadata": { + "availability_zone": "ag_zone", + } + } + aggregate_info.update(attrs) + aggregate = fakes.FakeResource( + info=copy.deepcopy(aggregate_info), + loaded=True) + return aggregate + + class FakeComputev2Client(object): def __init__(self, **kwargs): diff --git a/openstackclient/tests/compute/v2/test_aggregate.py b/openstackclient/tests/compute/v2/test_aggregate.py new file mode 100644 index 0000000000..1e8732110f --- /dev/null +++ b/openstackclient/tests/compute/v2/test_aggregate.py @@ -0,0 +1,66 @@ +# Copyright 2016 Huawei, Inc. 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.compute.v2 import aggregate +from openstackclient.tests.compute.v2 import fakes as compute_fakes +from openstackclient.tests import utils as tests_utils + + +class TestAggregate(compute_fakes.TestComputev2): + + def setUp(self): + super(TestAggregate, self).setUp() + + # Get a shortcut to the AggregateManager Mock + self.aggregate_mock = self.app.client_manager.compute.aggregates + self.aggregate_mock.reset_mock() + + +class TestAggregateUnset(TestAggregate): + + fake_ag = compute_fakes.FakeAggregate.create_one_aggregate() + + def setUp(self): + super(TestAggregateUnset, self).setUp() + + self.aggregate_mock.get.return_value = self.fake_ag + self.cmd = aggregate.UnsetAggregate(self.app, None) + + def test_aggregate_unset(self): + arglist = [ + '--property', 'unset_key', + 'ag1' + ] + verifylist = [ + ('property', ['unset_key']), + ('aggregate', 'ag1') + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.aggregate_mock.set_metadata.assert_called_once_with( + self.fake_ag, {'unset_key': None}) + self.assertIsNone(result) + + def test_aggregate_unset_no_property(self): + arglist = [ + 'ag1' + ] + verifylist = None + self.assertRaises(tests_utils.ParserException, + self.check_parser, + self.cmd, + arglist, + verifylist) diff --git a/releasenotes/notes/bug-1559866-733988f5dd5b07bb.yaml b/releasenotes/notes/bug-1559866-733988f5dd5b07bb.yaml new file mode 100644 index 0000000000..26bd9a0334 --- /dev/null +++ b/releasenotes/notes/bug-1559866-733988f5dd5b07bb.yaml @@ -0,0 +1,4 @@ +--- +features: + - Add ``aggregate unset`` command for compute v2 + [Bug `1559866 `_] diff --git a/setup.cfg b/setup.cfg index fcfd4e47bf..b3e09380ac 100644 --- a/setup.cfg +++ b/setup.cfg @@ -63,6 +63,7 @@ openstack.compute.v2 = aggregate_remove_host = openstackclient.compute.v2.aggregate:RemoveAggregateHost aggregate_set = openstackclient.compute.v2.aggregate:SetAggregate aggregate_show = openstackclient.compute.v2.aggregate:ShowAggregate + aggregate_unset = openstackclient.compute.v2.aggregate:UnsetAggregate compute_service_delete = openstackclient.compute.v2.service:DeleteService compute_service_list = openstackclient.compute.v2.service:ListService From e1e68809aea293d4b387afb0f95cd695ce05bae6 Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Wed, 23 Mar 2016 17:08:10 +0800 Subject: [PATCH 0754/3095] Add unit tests for compute v2 aggregate This patch add the complete unit tests to cover compute v2 aggregate object. Change-Id: Ifcf9248eabb5ca3ee5ca01c5843e96ba681c3b5a Closes-Bug: #1560832 --- .../tests/compute/v2/test_aggregate.py | 336 +++++++++++++++++- 1 file changed, 331 insertions(+), 5 deletions(-) diff --git a/openstackclient/tests/compute/v2/test_aggregate.py b/openstackclient/tests/compute/v2/test_aggregate.py index 1e8732110f..58dd775525 100644 --- a/openstackclient/tests/compute/v2/test_aggregate.py +++ b/openstackclient/tests/compute/v2/test_aggregate.py @@ -20,6 +20,24 @@ class TestAggregate(compute_fakes.TestComputev2): + fake_ag = compute_fakes.FakeAggregate.create_one_aggregate() + + columns = ( + 'availability_zone', + 'hosts', + 'id', + 'metadata', + 'name', + ) + + data = ( + fake_ag.availability_zone, + fake_ag.hosts, + fake_ag.id, + fake_ag.metadata, + fake_ag.name, + ) + def setUp(self): super(TestAggregate, self).setUp() @@ -28,9 +46,317 @@ def setUp(self): self.aggregate_mock.reset_mock() -class TestAggregateUnset(TestAggregate): +class TestAggregateAddHost(TestAggregate): - fake_ag = compute_fakes.FakeAggregate.create_one_aggregate() + def setUp(self): + super(TestAggregateAddHost, self).setUp() + + self.aggregate_mock.get.return_value = self.fake_ag + self.aggregate_mock.add_host.return_value = self.fake_ag + self.cmd = aggregate.AddAggregateHost(self.app, None) + + def test_aggregate_add_host(self): + arglist = [ + 'ag1', + 'host1', + ] + verifylist = [ + ('aggregate', 'ag1'), + ('host', 'host1'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate) + self.aggregate_mock.add_host.assert_called_once_with(self.fake_ag, + parsed_args.host) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestAggregateCreate(TestAggregate): + + def setUp(self): + super(TestAggregateCreate, self).setUp() + + self.aggregate_mock.create.return_value = self.fake_ag + self.aggregate_mock.set_metadata.return_value = self.fake_ag + self.cmd = aggregate.CreateAggregate(self.app, None) + + def test_aggregate_create(self): + arglist = [ + 'ag1', + ] + verifylist = [ + ('name', 'ag1'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.aggregate_mock.create.assert_called_once_with(parsed_args.name, + None) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_aggregate_create_with_zone(self): + arglist = [ + '--zone', 'zone1', + 'ag1', + ] + verifylist = [ + ('zone', 'zone1'), + ('name', 'ag1'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.aggregate_mock.create.assert_called_once_with(parsed_args.name, + parsed_args.zone) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_aggregate_create_with_property(self): + arglist = [ + '--property', 'key1=value1', + '--property', 'key2=value2', + 'ag1', + ] + verifylist = [ + ('property', {'key1': 'value1', 'key2': 'value2'}), + ('name', 'ag1'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.aggregate_mock.create.assert_called_once_with(parsed_args.name, + None) + self.aggregate_mock.set_metadata.assert_called_once_with( + self.fake_ag, parsed_args.property) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestAggregateDelete(TestAggregate): + + def setUp(self): + super(TestAggregateDelete, self).setUp() + + self.aggregate_mock.get.return_value = self.fake_ag + self.cmd = aggregate.DeleteAggregate(self.app, None) + + def test_aggregate_delete(self): + arglist = [ + 'ag1', + ] + verifylist = [ + ('aggregate', 'ag1'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate) + self.aggregate_mock.delete.assert_called_once_with(self.fake_ag.id) + self.assertIsNone(result) + + +class TestAggregateList(TestAggregate): + + list_columns = ( + "ID", + "Name", + "Availability Zone", + ) + + list_columns_long = ( + "ID", + "Name", + "Availability Zone", + "Properties", + ) + + list_data = (( + TestAggregate.fake_ag.id, + TestAggregate.fake_ag.name, + TestAggregate.fake_ag.availability_zone, + ), ) + + list_data_long = (( + TestAggregate.fake_ag.id, + TestAggregate.fake_ag.name, + TestAggregate.fake_ag.availability_zone, + {}, + ), ) + + def setUp(self): + super(TestAggregateList, self).setUp() + + self.aggregate_mock.list.return_value = [self.fake_ag] + self.cmd = aggregate.ListAggregate(self.app, None) + + def test_aggregate_list(self): + + parsed_args = self.check_parser(self.cmd, [], []) + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual(self.list_columns, columns) + self.assertEqual(self.list_data, tuple(data)) + + def test_aggregate_list_with_long(self): + arglist = [ + '--long', + ] + vertifylist = [ + ('long', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, vertifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual(self.list_columns_long, columns) + self.assertEqual(self.list_data_long, tuple(data)) + + +class TestAggregateRemoveHost(TestAggregate): + + def setUp(self): + super(TestAggregateRemoveHost, self).setUp() + + self.aggregate_mock.get.return_value = self.fake_ag + self.aggregate_mock.remove_host.return_value = self.fake_ag + self.cmd = aggregate.RemoveAggregateHost(self.app, None) + + def test_aggregate_add_host(self): + arglist = [ + 'ag1', + 'host1', + ] + verifylist = [ + ('aggregate', 'ag1'), + ('host', 'host1'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate) + self.aggregate_mock.remove_host.assert_called_once_with( + self.fake_ag, parsed_args.host) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestAggregateSet(TestAggregate): + + def setUp(self): + super(TestAggregateSet, self).setUp() + + self.aggregate_mock.get.return_value = self.fake_ag + self.cmd = aggregate.SetAggregate(self.app, None) + + def test_aggregate_set_no_option(self): + arglist = [ + 'ag1', + ] + verifylist = [ + ('aggregate', 'ag1'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate) + self.assertNotCalled(self.aggregate_mock.update) + self.assertNotCalled(self.aggregate_mock.set_metadata) + self.assertIsNone(result) + + def test_aggregate_set_with_name(self): + arglist = [ + '--name', 'new_name', + 'ag1', + ] + verifylist = [ + ('name', 'new_name'), + ('aggregate', 'ag1'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate) + self.aggregate_mock.update.assert_called_once_with( + self.fake_ag, {'name': parsed_args.name}) + self.assertNotCalled(self.aggregate_mock.set_metadata) + self.assertIsNone(result) + + def test_aggregate_set_with_zone(self): + arglist = [ + '--zone', 'new_zone', + 'ag1', + ] + verifylist = [ + ('zone', 'new_zone'), + ('aggregate', 'ag1'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate) + self.aggregate_mock.update.assert_called_once_with( + self.fake_ag, {'availability_zone': parsed_args.zone}) + self.assertNotCalled(self.aggregate_mock.set_metadata) + self.assertIsNone(result) + + def test_aggregate_set_with_property(self): + arglist = [ + '--property', 'key1=value1', + '--property', 'key2=value2', + 'ag1', + ] + verifylist = [ + ('property', {'key1': 'value1', 'key2': 'value2'}), + ('aggregate', 'ag1'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate) + self.assertNotCalled(self.aggregate_mock.update) + self.aggregate_mock.set_metadata.assert_called_once_with( + self.fake_ag, parsed_args.property) + self.assertIsNone(result) + + +class TestAggregateShow(TestAggregate): + + columns = ( + 'availability_zone', + 'hosts', + 'id', + 'name', + 'properties', + ) + + data = ( + TestAggregate.fake_ag.availability_zone, + TestAggregate.fake_ag.hosts, + TestAggregate.fake_ag.id, + TestAggregate.fake_ag.name, + '', + ) + + def setUp(self): + super(TestAggregateShow, self).setUp() + + self.aggregate_mock.get.return_value = self.fake_ag + self.cmd = aggregate.ShowAggregate(self.app, None) + + def test_aggregate_show(self): + arglist = [ + 'ag1', + ] + verifylist = [ + ('aggregate', 'ag1'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestAggregateUnset(TestAggregate): def setUp(self): super(TestAggregateUnset, self).setUp() @@ -41,11 +367,11 @@ def setUp(self): def test_aggregate_unset(self): arglist = [ '--property', 'unset_key', - 'ag1' + 'ag1', ] verifylist = [ ('property', ['unset_key']), - ('aggregate', 'ag1') + ('aggregate', 'ag1'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -56,7 +382,7 @@ def test_aggregate_unset(self): def test_aggregate_unset_no_property(self): arglist = [ - 'ag1' + 'ag1', ] verifylist = None self.assertRaises(tests_utils.ParserException, From add0e1002624630ce858a1c2b2199a3c22f39da6 Mon Sep 17 00:00:00 2001 From: Kristi Nikolla Date: Fri, 25 Mar 2016 12:12:55 -0400 Subject: [PATCH 0755/3095] Added functional tests for 'service provider' v3 commands 'identity provider' commands had functional tests but 'service provider' commands did not. Added the tests in a similar way to how it is done it test_idp. Change-Id: Id4b24ef7d34db65c6b0260c89327ec9be683284d --- functional/tests/identity/v3/test_identity.py | 24 +++++++++ .../identity/v3/test_service_provider.py | 54 +++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 functional/tests/identity/v3/test_service_provider.py diff --git a/functional/tests/identity/v3/test_identity.py b/functional/tests/identity/v3/test_identity.py index 88f5196b41..d0eec6f744 100644 --- a/functional/tests/identity/v3/test_identity.py +++ b/functional/tests/identity/v3/test_identity.py @@ -44,6 +44,11 @@ class IdentityTests(test.TestCase): IDENTITY_PROVIDER_FIELDS = ['description', 'enabled', 'id', 'remote_ids'] IDENTITY_PROVIDER_LIST_HEADERS = ['ID', 'Enabled', 'Description'] + SERVICE_PROVIDER_FIELDS = ['auth_url', 'description', 'enabled', + 'id', 'relay_state_prefix', 'sp_url'] + SERVICE_PROVIDER_LIST_HEADERS = ['ID', 'Enabled', 'Description', + 'Auth URL'] + @classmethod def setUpClass(cls): if hasattr(super(IdentityTests, cls), 'setUpClass'): @@ -269,3 +274,22 @@ def _create_dummy_idp(self, add_clean_up=True): self.openstack, 'identity provider delete %s' % identity_provider) return identity_provider + + def _create_dummy_sp(self, add_clean_up=True): + service_provider = data_utils.rand_name('ServiceProvider') + description = data_utils.rand_name('description') + raw_output = self.openstack( + 'service provider create ' + ' %(name)s ' + '--description %(description)s ' + '--auth-url https://sp.example.com:35357 ' + '--service-provider-url https://sp.example.com:5000 ' + '--enable ' % {'name': service_provider, + 'description': description}) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.SERVICE_PROVIDER_FIELDS) + if add_clean_up: + self.addCleanup( + self.openstack, + 'service provider delete %s' % service_provider) + return service_provider diff --git a/functional/tests/identity/v3/test_service_provider.py b/functional/tests/identity/v3/test_service_provider.py new file mode 100644 index 0000000000..eed9fccb72 --- /dev/null +++ b/functional/tests/identity/v3/test_service_provider.py @@ -0,0 +1,54 @@ +# 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 functional.tests.identity.v3 import test_identity +from tempest_lib.common.utils import data_utils + + +class ServiceProviderTests(test_identity.IdentityTests): + # Introduce functional test cases for command 'Service Provider' + + def test_sp_create(self): + self._create_dummy_sp(add_clean_up=True) + + def test_sp_delete(self): + service_provider = self._create_dummy_sp(add_clean_up=False) + raw_output = self.openstack('service provider delete %s' + % service_provider) + self.assertEqual(0, len(raw_output)) + + def test_sp_show(self): + service_provider = self._create_dummy_sp(add_clean_up=True) + raw_output = self.openstack('service provider show %s' + % service_provider) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.SERVICE_PROVIDER_FIELDS) + + def test_sp_list(self): + self._create_dummy_sp(add_clean_up=True) + raw_output = self.openstack('service provider list') + items = self.parse_listing(raw_output) + self.assert_table_structure(items, self.SERVICE_PROVIDER_LIST_HEADERS) + + def test_sp_set(self): + service_provider = self._create_dummy_sp(add_clean_up=True) + new_description = data_utils.rand_name('newDescription') + raw_output = self.openstack('service provider set ' + '%(service-provider)s ' + '--description %(description)s ' + % {'service-provider': service_provider, + 'description': new_description}) + self.assertEqual(0, len(raw_output)) + raw_output = self.openstack('service provider show %s' + % service_provider) + updated_value = self.parse_show_as_object(raw_output) + self.assertIn(new_description, updated_value['description']) From c5b58a47b0178cca0ab4a5b4a6329bd7ec9d42fa Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Tue, 22 Mar 2016 16:14:21 -0500 Subject: [PATCH 0756/3095] Refactor security group rule list to use SDK Refactored the 'os security group rule list' command to use the SDK when neutron is enabled, but continue to use the nova client when nova network is enabled. In addition, a release note was added to document the features and fixes resulting from this refactor. Change-Id: I24d04b720102ed1c60776e1ca67d4ca20e31b663 Partial-Bug: #1519512 Implements: blueprint neutron-client --- openstackclient/compute/v2/security_group.py | 103 -------- .../network/v2/security_group_rule.py | 115 +++++++++ .../compute/v2/test_security_group_rule.py | 229 ------------------ .../network/v2/test_security_group_rule.py | 186 ++++++++++++++ .../notes/bug-1519512-65df002102b7fb99.yaml | 12 + setup.cfg | 3 +- 6 files changed, 314 insertions(+), 334 deletions(-) delete mode 100644 openstackclient/compute/v2/security_group.py delete mode 100644 openstackclient/tests/compute/v2/test_security_group_rule.py create mode 100644 releasenotes/notes/bug-1519512-65df002102b7fb99.yaml diff --git a/openstackclient/compute/v2/security_group.py b/openstackclient/compute/v2/security_group.py deleted file mode 100644 index ca3bf5dce1..0000000000 --- a/openstackclient/compute/v2/security_group.py +++ /dev/null @@ -1,103 +0,0 @@ -# Copyright 2012 OpenStack Foundation -# Copyright 2013 Nebula Inc -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - -"""Compute v2 Security Group action implementations""" - -try: - from novaclient.v2 import security_group_rules -except ImportError: - from novaclient.v1_1 import security_group_rules - -from openstackclient.common import command -from openstackclient.common import utils - - -def _xform_security_group_rule(sgroup): - info = {} - info.update(sgroup) - from_port = info.pop('from_port') - to_port = info.pop('to_port') - if isinstance(from_port, int) and isinstance(to_port, int): - port_range = {'port_range': "%u:%u" % (from_port, to_port)} - elif from_port is None and to_port is None: - port_range = {'port_range': ""} - else: - port_range = {'port_range': "%s:%s" % (from_port, to_port)} - info.update(port_range) - if 'cidr' in info['ip_range']: - info['ip_range'] = info['ip_range']['cidr'] - else: - info['ip_range'] = '' - if info['ip_protocol'] is None: - info['ip_protocol'] = '' - elif info['ip_protocol'].lower() == 'icmp': - info['port_range'] = '' - group = info.pop('group') - if 'name' in group: - info['remote_security_group'] = group['name'] - else: - info['remote_security_group'] = '' - return info - - -class ListSecurityGroupRule(command.Lister): - """List security group rules""" - - def get_parser(self, prog_name): - parser = super(ListSecurityGroupRule, self).get_parser(prog_name) - parser.add_argument( - 'group', - metavar='', - nargs='?', - help='List all rules in this security group (name or ID)', - ) - return parser - - def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - columns = column_headers = ( - "ID", - "IP Protocol", - "IP Range", - "Port Range", - "Remote Security Group", - ) - - rules_to_list = [] - if parsed_args.group: - group = utils.find_resource( - compute_client.security_groups, - parsed_args.group, - ) - rules_to_list = group.rules - else: - columns = columns + ('parent_group_id',) - column_headers = column_headers + ('Security Group',) - for group in compute_client.security_groups.list(): - rules_to_list.extend(group.rules) - - # Argh, the rules are not Resources... - rules = [] - for rule in rules_to_list: - rules.append(security_group_rules.SecurityGroupRule( - compute_client.security_group_rules, - _xform_security_group_rule(rule), - )) - - return (column_headers, - (utils.get_item_properties( - s, columns, - ) for s in rules)) diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index f60995ab2f..c6fb355853 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -15,6 +15,11 @@ import six +try: + from novaclient.v2 import security_group_rules as compute_secgroup_rules +except ImportError: + from novaclient.v1_1 import security_group_rules as compute_secgroup_rules + from openstackclient.common import exceptions from openstackclient.common import parseractions from openstackclient.common import utils @@ -27,6 +32,20 @@ def _format_security_group_rule_show(obj): return zip(*sorted(six.iteritems(data))) +def _format_network_port_range(rule): + port_range = '' + if (rule.protocol != 'icmp' and + (rule.port_range_min or rule.port_range_max)): + port_range_min = str(rule.port_range_min) + port_range_max = str(rule.port_range_max) + if rule.port_range_min is None: + port_range_min = port_range_max + if rule.port_range_max is None: + port_range_max = port_range_min + port_range = port_range_min + ':' + port_range_max + return port_range + + def _get_columns(item): columns = list(item.keys()) if 'tenant_id' in columns: @@ -161,6 +180,102 @@ def take_action_compute(self, client, parsed_args): client.security_group_rules.delete(parsed_args.rule) +class ListSecurityGroupRule(common.NetworkAndComputeLister): + """List security group rules""" + + def update_parser_common(self, parser): + parser.add_argument( + 'group', + metavar='', + nargs='?', + help='List all rules in this security group (name or ID)', + ) + return parser + + def _get_column_headers(self, parsed_args): + column_headers = ( + 'ID', + 'IP Protocol', + 'IP Range', + 'Port Range', + 'Remote Security Group', + ) + if parsed_args.group is None: + column_headers = column_headers + ('Security Group',) + return column_headers + + def take_action_network(self, client, parsed_args): + column_headers = self._get_column_headers(parsed_args) + columns = ( + 'id', + 'protocol', + 'remote_ip_prefix', + 'port_range_min', + 'remote_group_id', + ) + + # Get the security group rules using the requested query. + query = {} + if parsed_args.group is not None: + # NOTE(rtheis): Unfortunately, the security group resource + # does not contain security group rules resources. So use + # the security group ID in a query to get the resources. + security_group_id = client.find_security_group( + parsed_args.group, + ignore_missing=False + ).id + query = {'security_group_id': security_group_id} + else: + columns = columns + ('security_group_id',) + rules = list(client.security_group_rules(**query)) + + # Reformat the rules to display a port range instead + # of just the port range minimum. This maintains + # output compatibility with compute. + for rule in rules: + rule.port_range_min = _format_network_port_range(rule) + + return (column_headers, + (utils.get_item_properties( + s, columns, + ) for s in rules)) + + def take_action_compute(self, client, parsed_args): + column_headers = self._get_column_headers(parsed_args) + columns = ( + "ID", + "IP Protocol", + "IP Range", + "Port Range", + "Remote Security Group", + ) + + rules_to_list = [] + if parsed_args.group is not None: + group = utils.find_resource( + client.security_groups, + parsed_args.group, + ) + rules_to_list = group.rules + else: + columns = columns + ('parent_group_id',) + for group in client.security_groups.list(): + rules_to_list.extend(group.rules) + + # NOTE(rtheis): Turn the raw rules into resources. + rules = [] + for rule in rules_to_list: + rules.append(compute_secgroup_rules.SecurityGroupRule( + client.security_group_rules, + network_utils.transform_compute_security_group_rule(rule), + )) + + return (column_headers, + (utils.get_item_properties( + s, columns, + ) for s in rules)) + + class ShowSecurityGroupRule(common.NetworkAndComputeShowOne): """Display security group rule details""" diff --git a/openstackclient/tests/compute/v2/test_security_group_rule.py b/openstackclient/tests/compute/v2/test_security_group_rule.py deleted file mode 100644 index 42bf2c2682..0000000000 --- a/openstackclient/tests/compute/v2/test_security_group_rule.py +++ /dev/null @@ -1,229 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - -import copy - -from openstackclient.compute.v2 import security_group -from openstackclient.tests.compute.v2 import fakes as compute_fakes -from openstackclient.tests import fakes -from openstackclient.tests.identity.v2_0 import fakes as identity_fakes - - -security_group_id = '11' -security_group_name = 'wide-open' -security_group_description = 'nothing but net' - -security_group_rule_id = '1' -security_group_rule_cidr = '0.0.0.0/0' - -SECURITY_GROUP_RULE = { - 'id': security_group_rule_id, - 'group': {}, - 'ip_protocol': 'tcp', - 'ip_range': {'cidr': security_group_rule_cidr}, - 'parent_group_id': security_group_id, - 'from_port': 0, - 'to_port': 0, -} - -SECURITY_GROUP_RULE_ICMP = { - 'id': security_group_rule_id, - 'group': {}, - 'ip_protocol': 'icmp', - 'ip_range': {'cidr': security_group_rule_cidr}, - 'parent_group_id': security_group_id, - 'from_port': -1, - 'to_port': -1, -} - -SECURITY_GROUP_RULE_REMOTE_GROUP = { - 'id': security_group_rule_id, - 'group': {"tenant_id": "14", "name": "default"}, - 'ip_protocol': 'tcp', - 'ip_range': {}, - 'parent_group_id': security_group_id, - 'from_port': 80, - 'to_port': 80, -} - -SECURITY_GROUP = { - 'id': security_group_id, - 'name': security_group_name, - 'description': security_group_description, - 'tenant_id': identity_fakes.project_id, - 'rules': [SECURITY_GROUP_RULE, - SECURITY_GROUP_RULE_ICMP, - SECURITY_GROUP_RULE_REMOTE_GROUP], -} - -security_group_2_id = '12' -security_group_2_name = 'he-shoots' -security_group_2_description = 'he scores' - -SECURITY_GROUP_2_RULE = { - 'id': '2', - 'group': {}, - 'ip_protocol': 'tcp', - 'ip_range': {}, - 'parent_group_id': security_group_2_id, - 'from_port': 80, - 'to_port': 80, -} - -SECURITY_GROUP_2 = { - 'id': security_group_2_id, - 'name': security_group_2_name, - 'description': security_group_2_description, - 'tenant_id': identity_fakes.project_id, - 'rules': [SECURITY_GROUP_2_RULE], -} - - -class FakeSecurityGroupRuleResource(fakes.FakeResource): - - def get_keys(self): - return {'property': 'value'} - - -class TestSecurityGroupRule(compute_fakes.TestComputev2): - - def setUp(self): - super(TestSecurityGroupRule, self).setUp() - - # Get a shortcut compute client security_groups mock - self.secgroups_mock = self.app.client_manager.compute.security_groups - self.secgroups_mock.reset_mock() - - # Get a shortcut compute client security_group_rules mock - self.sg_rules_mock = \ - self.app.client_manager.compute.security_group_rules - self.sg_rules_mock.reset_mock() - - -class TestSecurityGroupRuleList(TestSecurityGroupRule): - - def setUp(self): - super(TestSecurityGroupRuleList, self).setUp() - - security_group_mock = FakeSecurityGroupRuleResource( - None, - copy.deepcopy(SECURITY_GROUP), - loaded=True, - ) - - security_group_2_mock = FakeSecurityGroupRuleResource( - None, - copy.deepcopy(SECURITY_GROUP_2), - loaded=True, - ) - - self.secgroups_mock.get.return_value = security_group_mock - self.secgroups_mock.list.return_value = [security_group_mock, - security_group_2_mock] - - # Get the command object to test - self.cmd = security_group.ListSecurityGroupRule(self.app, None) - - def test_security_group_rule_list(self): - - arglist = [ - security_group_name, - ] - verifylist = [ - ('group', security_group_name), - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # In base command class Lister in cliff, abstract method take_action() - # returns a tuple containing the column names and an iterable - # containing the data to be listed. - columns, data = self.cmd.take_action(parsed_args) - - collist = ( - 'ID', - 'IP Protocol', - 'IP Range', - 'Port Range', - 'Remote Security Group', - ) - self.assertEqual(collist, columns) - datalist = (( - security_group_rule_id, - 'tcp', - security_group_rule_cidr, - '0:0', - '', - ), ( - security_group_rule_id, - 'icmp', - security_group_rule_cidr, - '', - '', - ), ( - security_group_rule_id, - 'tcp', - '', - '80:80', - 'default', - ),) - self.assertEqual(datalist, tuple(data)) - - def test_security_group_rule_list_no_group(self): - - parsed_args = self.check_parser(self.cmd, [], []) - - # In base command class Lister in cliff, abstract method take_action() - # returns a tuple containing the column names and an iterable - # containing the data to be listed. - columns, data = self.cmd.take_action(parsed_args) - - collist = ( - 'ID', - 'IP Protocol', - 'IP Range', - 'Port Range', - 'Remote Security Group', - 'Security Group', - ) - self.assertEqual(collist, columns) - datalist = (( - security_group_rule_id, - 'tcp', - security_group_rule_cidr, - '0:0', - '', - security_group_id, - ), ( - security_group_rule_id, - 'icmp', - security_group_rule_cidr, - '', - '', - security_group_id, - ), ( - security_group_rule_id, - 'tcp', - '', - '80:80', - 'default', - security_group_id, - ), ( - '2', - 'tcp', - '', - '80:80', - '', - security_group_2_id, - ),) - self.assertEqual(datalist, tuple(data)) diff --git a/openstackclient/tests/network/v2/test_security_group_rule.py b/openstackclient/tests/network/v2/test_security_group_rule.py index 81b9e18bdc..6aad859976 100644 --- a/openstackclient/tests/network/v2/test_security_group_rule.py +++ b/openstackclient/tests/network/v2/test_security_group_rule.py @@ -14,6 +14,7 @@ import copy import mock +from openstackclient.network import utils as network_utils from openstackclient.network.v2 import security_group_rule from openstackclient.tests.compute.v2 import fakes as compute_fakes from openstackclient.tests import fakes @@ -414,6 +415,191 @@ def test_security_group_rule_delete(self): self.assertIsNone(result) +class TestListSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): + + # The security group to hold the rules. + _security_group = \ + network_fakes.FakeSecurityGroup.create_one_security_group() + + # The security group rule to be listed. + _security_group_rule_tcp = \ + network_fakes.FakeSecurityGroupRule.create_one_security_group_rule({ + 'protocol': 'tcp', + 'port_range_max': 80, + 'port_range_min': 80, + 'security_group_id': _security_group.id, + }) + _security_group_rule_icmp = \ + network_fakes.FakeSecurityGroupRule.create_one_security_group_rule({ + 'protocol': 'icmp', + 'port_range_max': -1, + 'port_range_min': -1, + 'remote_ip_prefix': '10.0.2.0/24', + 'security_group_id': _security_group.id, + }) + _security_group.security_group_rules = [_security_group_rule_tcp._info, + _security_group_rule_icmp._info] + _security_group_rules = [_security_group_rule_tcp, + _security_group_rule_icmp] + + expected_columns_with_group = ( + 'ID', + 'IP Protocol', + 'IP Range', + 'Port Range', + 'Remote Security Group', + ) + expected_columns_no_group = \ + expected_columns_with_group + ('Security Group',) + + expected_data_with_group = [] + expected_data_no_group = [] + for _security_group_rule in _security_group_rules: + expected_rule_with_group = ( + _security_group_rule.id, + _security_group_rule.protocol, + _security_group_rule.remote_ip_prefix, + security_group_rule._format_network_port_range( + _security_group_rule), + _security_group_rule.remote_group_id, + ) + expected_rule_no_group = expected_rule_with_group + \ + (_security_group_rule.security_group_id,) + expected_data_with_group.append(expected_rule_with_group) + expected_data_no_group.append(expected_rule_no_group) + + def setUp(self): + super(TestListSecurityGroupRuleNetwork, self).setUp() + + self.network.find_security_group = mock.Mock( + return_value=self._security_group) + self.network.security_group_rules = mock.Mock( + return_value=self._security_group_rules) + + # Get the command object to test + self.cmd = security_group_rule.ListSecurityGroupRule( + self.app, self.namespace) + + def test_list_no_group(self): + self._security_group_rule_tcp.port_range_min = 80 + parsed_args = self.check_parser(self.cmd, [], []) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.security_group_rules.assert_called_once_with(**{}) + self.assertEqual(self.expected_columns_no_group, columns) + self.assertEqual(self.expected_data_no_group, list(data)) + + def test_list_with_group(self): + self._security_group_rule_tcp.port_range_min = 80 + arglist = [ + self._security_group.id, + ] + verifylist = [ + ('group', self._security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.security_group_rules.assert_called_once_with(**{ + 'security_group_id': self._security_group.id, + }) + self.assertEqual(self.expected_columns_with_group, columns) + self.assertEqual(self.expected_data_with_group, list(data)) + + +class TestListSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): + + # The security group to hold the rules. + _security_group = \ + compute_fakes.FakeSecurityGroup.create_one_security_group() + + # The security group rule to be listed. + _security_group_rule_tcp = \ + compute_fakes.FakeSecurityGroupRule.create_one_security_group_rule({ + 'ip_protocol': 'tcp', + 'from_port': 80, + 'to_port': 80, + 'group': {'name': _security_group.name}, + }) + _security_group_rule_icmp = \ + compute_fakes.FakeSecurityGroupRule.create_one_security_group_rule({ + 'ip_protocol': 'icmp', + 'from_port': -1, + 'to_port': -1, + 'ip_range': {'cidr': '10.0.2.0/24'}, + 'group': {'name': _security_group.name}, + }) + _security_group.rules = [_security_group_rule_tcp._info, + _security_group_rule_icmp._info] + + expected_columns_with_group = ( + 'ID', + 'IP Protocol', + 'IP Range', + 'Port Range', + 'Remote Security Group', + ) + expected_columns_no_group = \ + expected_columns_with_group + ('Security Group',) + + expected_data_with_group = [] + expected_data_no_group = [] + for _security_group_rule in _security_group.rules: + rule = network_utils.transform_compute_security_group_rule( + _security_group_rule + ) + expected_rule_with_group = ( + rule['id'], + rule['ip_protocol'], + rule['ip_range'], + rule['port_range'], + rule['remote_security_group'], + ) + expected_rule_no_group = expected_rule_with_group + \ + (_security_group_rule['parent_group_id'],) + expected_data_with_group.append(expected_rule_with_group) + expected_data_no_group.append(expected_rule_no_group) + + def setUp(self): + super(TestListSecurityGroupRuleCompute, self).setUp() + + self.app.client_manager.network_endpoint_enabled = False + + self.compute.security_groups.get.return_value = \ + self._security_group + self.compute.security_groups.list.return_value = \ + [self._security_group] + + # Get the command object to test + self.cmd = security_group_rule.ListSecurityGroupRule(self.app, None) + + def test_list_no_group(self): + parsed_args = self.check_parser(self.cmd, [], []) + + columns, data = self.cmd.take_action(parsed_args) + self.compute.security_groups.list.assert_called_once_with() + self.assertEqual(self.expected_columns_no_group, columns) + self.assertEqual(self.expected_data_no_group, list(data)) + + def test_list_with_group(self): + arglist = [ + self._security_group.id, + ] + verifylist = [ + ('group', self._security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.compute.security_groups.get.assert_called_once_with( + self._security_group.id + ) + self.assertEqual(self.expected_columns_with_group, columns) + self.assertEqual(self.expected_data_with_group, list(data)) + + class TestShowSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): # The security group rule to be shown. diff --git a/releasenotes/notes/bug-1519512-65df002102b7fb99.yaml b/releasenotes/notes/bug-1519512-65df002102b7fb99.yaml new file mode 100644 index 0000000000..b5f5fbb531 --- /dev/null +++ b/releasenotes/notes/bug-1519512-65df002102b7fb99.yaml @@ -0,0 +1,12 @@ +--- +features: + - The ``security group rule list`` command now uses Network v2 + when enabled which results in ``egress`` security group rules + being displayed. In addition, security group rules for all + projects will be displayed when the ``group`` argument is not + specified (admin only). + [Bug `1519512 `_] +fixes: + - The ``security group rule list`` command no longer ignores + the ``group`` argument when it is set to an empty value. + [Bug `1519512 `_] diff --git a/setup.cfg b/setup.cfg index 7689713c33..ad3dcc257f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -99,8 +99,6 @@ openstack.compute.v2 = keypair_list = openstackclient.compute.v2.keypair:ListKeypair keypair_show = openstackclient.compute.v2.keypair:ShowKeypair - security_group_rule_list = openstackclient.compute.v2.security_group:ListSecurityGroupRule - server_add_security_group = openstackclient.compute.v2.server:AddServerSecurityGroup server_add_volume = openstackclient.compute.v2.server:AddServerVolume server_create = openstackclient.compute.v2.server:CreateServer @@ -351,6 +349,7 @@ openstack.network.v2 = security_group_rule_create = openstackclient.network.v2.security_group_rule:CreateSecurityGroupRule security_group_rule_delete = openstackclient.network.v2.security_group_rule:DeleteSecurityGroupRule + security_group_rule_list = openstackclient.network.v2.security_group_rule:ListSecurityGroupRule security_group_rule_show = openstackclient.network.v2.security_group_rule:ShowSecurityGroupRule subnet_create = openstackclient.network.v2.subnet:CreateSubnet From 9d65abcaa65340b1d0b5ec3be34e87d692ee51e5 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 23 Mar 2016 14:20:19 -0500 Subject: [PATCH 0757/3095] Docs cleanup: sort subnet commands Change-Id: I43c0052ec5ed2e94b203a0befc4b39d3522d4e9c --- doc/source/command-objects/subnet.rst | 32 +++++++++++++-------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/doc/source/command-objects/subnet.rst b/doc/source/command-objects/subnet.rst index eb64c9e732..e81a335998 100644 --- a/doc/source/command-objects/subnet.rst +++ b/doc/source/command-objects/subnet.rst @@ -4,22 +4,6 @@ subnet Network v2 -subnet delete -------------- - -Delete a subnet - -.. program:: subnet delete -.. code:: bash - - os subnet delete - - -.. _subnet_delete-subnet: -.. describe:: - - Subnet to delete (name or ID) - subnet create ------------- @@ -128,6 +112,22 @@ Create new subnet Name of subnet to create +subnet delete +------------- + +Delete a subnet + +.. program:: subnet delete +.. code:: bash + + os subnet delete + + +.. _subnet_delete-subnet: +.. describe:: + + Subnet to delete (name or ID) + subnet list ----------- From b4402a0468ac1362f0928b37d5c7da54313c7668 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Mon, 28 Mar 2016 14:30:07 -0500 Subject: [PATCH 0758/3095] Docs cleanup: volume type Clean up volume type command help, add volume type show to doc, sort command classes in v1/volume_type.py. Change-Id: I1f8e5c047d9c08f5704fc23cfb694f23d32e3caf --- doc/source/command-objects/volume-type.rst | 27 ++++++++++-- openstackclient/volume/v1/volume_type.py | 50 +++++++++++----------- openstackclient/volume/v2/volume_type.py | 10 ++--- 3 files changed, 54 insertions(+), 33 deletions(-) diff --git a/doc/source/command-objects/volume-type.rst b/doc/source/command-objects/volume-type.rst index 8a11384790..69944fb954 100644 --- a/doc/source/command-objects/volume-type.rst +++ b/doc/source/command-objects/volume-type.rst @@ -20,7 +20,7 @@ Create new volume type .. option:: --description - New volume type description + Volume type description .. versionadded:: 2 @@ -40,9 +40,10 @@ Create new volume type Set a property on this volume type (repeat option to set multiple properties) +.. _volume_type_create-name: .. describe:: - New volume type name + Volume type name volume type delete ------------------ @@ -55,6 +56,7 @@ Delete volume type os volume type delete +.. _volume_type_delete-volume-type: .. describe:: Volume type to delete (name or ID) @@ -102,12 +104,30 @@ Set volume type properties .. option:: --property - Property to add or modify for this volume type (repeat option to set multiple properties) + Set a property on this volume type (repeat option to set multiple properties) +.. _volume_type_set-volume-type: .. describe:: Volume type to modify (name or ID) +volume type show +---------------- + +Display volume type details + + +.. program:: volume type show +.. code:: bash + + os volume type show + + +.. _volume_type_show-volume-type: +.. describe:: + + Volume type to display (name or ID) + volume type unset ----------------- @@ -124,6 +144,7 @@ Unset volume type properties Property to remove from volume type (repeat option to remove multiple properties) +.. _volume_type_unset-volume-type: .. describe:: Volume type to modify (name or ID) diff --git a/openstackclient/volume/v1/volume_type.py b/openstackclient/volume/v1/volume_type.py index 24d0b235ff..05671c1fe3 100644 --- a/openstackclient/volume/v1/volume_type.py +++ b/openstackclient/volume/v1/volume_type.py @@ -30,13 +30,13 @@ def get_parser(self, prog_name): parser.add_argument( 'name', metavar='', - help='New volume type name', + help='Volume type name', ) parser.add_argument( '--property', metavar='', action=parseractions.KeyValueAction, - help='Property to add for this volume type ' + help='Set a property on this volume type ' '(repeat option to set multiple properties)', ) return parser @@ -114,7 +114,7 @@ def get_parser(self, prog_name): '--property', metavar='', action=parseractions.KeyValueAction, - help='Property to add or modify for this volume type ' + help='Set a property on this volume type ' '(repeat option to set multiple properties)', ) return parser @@ -128,6 +128,27 @@ def take_action(self, parsed_args): volume_type.set_keys(parsed_args.property) +class ShowVolumeType(command.ShowOne): + """Display volume type details""" + + def get_parser(self, prog_name): + parser = super(ShowVolumeType, self).get_parser(prog_name) + parser.add_argument( + "volume_type", + metavar="", + help="Volume type to display (name or ID)" + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + volume_type = utils.find_resource( + volume_client.volume_types, parsed_args.volume_type) + properties = utils.format_dict(volume_type._info.pop('extra_specs')) + volume_type._info.update({'properties': properties}) + return zip(*sorted(six.iteritems(volume_type._info))) + + class UnsetVolumeType(command.Command): """Unset volume type properties""" @@ -143,7 +164,7 @@ def get_parser(self, prog_name): metavar='', action='append', default=[], - help='Property to remove from volume type ' + help='Remove a property from this volume type ' '(repeat option to remove multiple properties)', required=True, ) @@ -160,24 +181,3 @@ def take_action(self, parsed_args): volume_type.unset_keys(parsed_args.property) else: self.app.log.error("No changes requested\n") - - -class ShowVolumeType(command.ShowOne): - """Display volume type details""" - - def get_parser(self, prog_name): - parser = super(ShowVolumeType, self).get_parser(prog_name) - parser.add_argument( - "volume_type", - metavar="", - help="Volume type to display (name or ID)" - ) - return parser - - def take_action(self, parsed_args): - volume_client = self.app.client_manager.volume - volume_type = utils.find_resource( - volume_client.volume_types, parsed_args.volume_type) - properties = utils.format_dict(volume_type._info.pop('extra_specs')) - volume_type._info.update({'properties': properties}) - return zip(*sorted(six.iteritems(volume_type._info))) diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index d2b3ed6a61..5509ac5261 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -29,12 +29,12 @@ def get_parser(self, prog_name): parser.add_argument( "name", metavar="", - help="New volume type name" + help="Volume type name", ) parser.add_argument( "--description", metavar="", - help="New volume type description", + help="Volume type description", ) public_group = parser.add_mutually_exclusive_group() public_group.add_argument( @@ -55,7 +55,7 @@ def get_parser(self, prog_name): '--property', metavar='', action=parseractions.KeyValueAction, - help='Property to add for this volume type' + help='Set a property on this volume type' '(repeat option to set multiple properties)', ) return parser @@ -153,7 +153,7 @@ def get_parser(self, prog_name): '--property', metavar='', action=parseractions.KeyValueAction, - help='Property to add or modify for this volume type ' + help='Set a property on this volume type ' '(repeat option to set multiple properties)', ) return parser @@ -221,7 +221,7 @@ def get_parser(self, prog_name): metavar='', default=[], required=True, - help='Property to remove from volume type ' + help='Remove a property from this volume type ' '(repeat option to remove multiple properties)', ) return parser From a3a2a7e9f0b59c151c87df3860350df10a48963f Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Mon, 28 Mar 2016 14:02:33 -0500 Subject: [PATCH 0759/3095] Docs cleanup: volume command help This formats the volume command help text consistent with the rest of OSC, adds some reference targets and some explanation text to some commands. No functional changes have been made, only cosmetic/help output. Change-Id: Ib86ec3ca58bdea5f33078ced3ec3583b2be0e89a --- doc/source/command-objects/volume.rst | 82 ++++++++++++++++----------- openstackclient/volume/v1/volume.py | 46 +++++++-------- openstackclient/volume/v2/volume.py | 66 ++++++++++----------- 3 files changed, 106 insertions(+), 88 deletions(-) diff --git a/doc/source/command-objects/volume.rst b/doc/source/command-objects/volume.rst index fd32c3278f..a51d1117d1 100644 --- a/doc/source/command-objects/volume.rst +++ b/doc/source/command-objects/volume.rst @@ -14,32 +14,45 @@ Create new volume os volume create --size + [--type ] + [--image ] [--snapshot ] + [--source ] [--description ] - [--type ] [--user ] [--project ] [--availability-zone ] - [--image ] - [--source ] [--property [...] ] .. option:: --size (required) - New volume size in GB + Volume size in GB + +.. option:: --type + + Set the type of volume + + Select :option:`\` from the available types as shown + by ``volume type list``. + +.. option:: --image + + Use :option:`\` as source of volume (name or ID) + + This is commonly used to create a boot volume for a server. .. option:: --snapshot - Use as source of new volume + Use :option:`\` as source of volume (name or ID) -.. option:: --description +.. option:: --source - New volume description + Volume to clone (name or ID) -.. option:: --type +.. option:: --description - Use as the new volume type + Volume description .. option:: --user @@ -51,23 +64,16 @@ Create new volume .. option:: --availability-zone - Create new volume in - -.. option:: --image - - Use as source of new volume (name or ID) - -.. option:: --source - - Volume to clone (name or ID) + Create volume in :option:`\` .. option:: --property Set a property on this volume (repeat option to set multiple properties) +.. _volume_create-name: .. describe:: - New volume name + Volume name The :option:`--project` and :option:`--user` options are typically only useful for admin users, but may be allowed for other users depending on @@ -83,12 +89,13 @@ Delete volume(s) os volume delete [--force] - [ ...] + [ ...] .. option:: --force Attempt forced removal of volume(s), regardless of state (defaults to False) +.. _volume_delete-volume: .. describe:: Volume(s) to delete (name or ID) @@ -102,35 +109,37 @@ List volumes .. code:: bash os volume list - [--all-projects] [--project [--project-domain ]] [--user [--user-domain ]] [--name ] [--status ] + [--all-projects] [--long] .. option:: --project - Filter results by project (name or ID) (admin only) + Filter results by :option:`\` (name or ID) (admin only) *Volume version 2 only* .. option:: --project-domain Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. *Volume version 2 only* .. option:: --user - Filter results by user (name or ID) (admin only) + Filter results by :option:`\` (name or ID) (admin only) *Volume version 2 only* .. option:: --user-domain Domain the user belongs to (name or ID). + This can be used in case collisions between user names exist. *Volume version 2 only* @@ -161,8 +170,8 @@ Set volume properties os volume set [--name ] - [--description ] [--size ] + [--description ] [--property [...] ] [--image-property [...] ] @@ -171,25 +180,30 @@ Set volume properties New volume name -.. option:: --description - - New volume description - .. option:: --size Extend volume size in GB +.. option:: --description + + New volume description + .. option:: --property - Property to add or modify for this volume (repeat option to set multiple properties) + Set a property on this volume (repeat option to set multiple properties) .. option:: --image-property - To add or modify image properties for this volume. + Set an image property on this volume (repeat option to set multiple image properties) + Image properties are copied along with the image when creating a volume + using :option:`--image`. Note that these properties are immutable on the + image itself, this option updates the copy attached to this volume. + *Volume version 2 only* +.. _volume_set-volume: .. describe:: Volume to modify (name or ID) @@ -205,6 +219,7 @@ Show volume details os volume show +.. _volume_show-volume: .. describe:: Volume to display (name or ID) @@ -224,13 +239,16 @@ Unset volume properties .. option:: --property - Property to remove from volume (repeat option to remove multiple properties) + Remove a property from volume (repeat option to remove multiple properties) .. option:: --image-property - To remove image properties from volume + Remove an image property from volume (repeat option to remove multiple image properties) + *Volume version 2 only* + +.. _volume_unset-volume: .. describe:: Volume to modify (name or ID) diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index 90827d2041..29c197ef81 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -31,20 +31,30 @@ def get_parser(self, prog_name): parser.add_argument( 'name', metavar='', - help='New volume name', + help='Volume name', ) parser.add_argument( '--size', metavar='', required=True, type=int, - help='New volume size in GB', + help='Volume size in GB', + ) + parser.add_argument( + '--type', + metavar='', + help="Set the type of volume", + ) + parser.add_argument( + '--image', + metavar='', + help='Use as source of volume (name or ID)', ) snapshot_group = parser.add_mutually_exclusive_group() snapshot_group.add_argument( '--snapshot', metavar='', - help='Use as source of new volume', + help='Use as source of volume (name or ID)', ) snapshot_group.add_argument( '--snapshot-id', @@ -52,14 +62,14 @@ def get_parser(self, prog_name): help=argparse.SUPPRESS, ) parser.add_argument( - '--description', - metavar='', - help='New volume description', + '--source', + metavar='', + help='Volume to clone (name or ID)', ) parser.add_argument( - '--type', - metavar='', - help='Use as the new volume type', + '--description', + metavar='', + help='Volume description', ) parser.add_argument( '--user', @@ -74,17 +84,7 @@ def get_parser(self, prog_name): parser.add_argument( '--availability-zone', metavar='', - help='Create new volume in ', - ) - parser.add_argument( - '--image', - metavar='', - help='Use as source of new volume (name or ID)', - ) - parser.add_argument( - '--source', - metavar='', - help='Volume to clone (name or ID)', + help='Create volume in ', ) parser.add_argument( '--property', @@ -308,7 +308,7 @@ def get_parser(self, prog_name): parser.add_argument( 'volume', metavar='', - help='Volume to change (name or ID)', + help='Volume to modify (name or ID)', ) parser.add_argument( '--name', @@ -330,7 +330,7 @@ def get_parser(self, prog_name): '--property', metavar='', action=parseractions.KeyValueAction, - help='Property to add or modify for this volume ' + help='Set a property on this volume ' '(repeat option to set multiple properties)', ) return parser @@ -411,7 +411,7 @@ def get_parser(self, prog_name): metavar='', action='append', default=[], - help='Property to remove from volume ' + help='Remove a property from volume ' '(repeat option to remove multiple properties)', required=True, ) diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index 5d9d2d9e35..5b7511e8ea 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -32,29 +32,39 @@ def get_parser(self, prog_name): parser.add_argument( "name", metavar="", - help="New volume name" + help="Volume name", ) parser.add_argument( "--size", metavar="", type=int, required=True, - help="New volume size in GB" + help="Volume size in GB", + ) + parser.add_argument( + "--type", + metavar="", + help="Set the type of volume", + ) + parser.add_argument( + "--image", + metavar="", + help="Use as source of volume (name or ID)", ) parser.add_argument( "--snapshot", metavar="", - help="Use as source of new volume (name or ID)" + help="Use as source of volume (name or ID)", ) parser.add_argument( - "--description", - metavar="", - help="New volume description" + "--source", + metavar="", + help="Volume to clone (name or ID)", ) parser.add_argument( - "--type", - metavar="", - help="Use as the new volume type", + "--description", + metavar="", + help="Volume description", ) parser.add_argument( '--user', @@ -69,24 +79,14 @@ def get_parser(self, prog_name): parser.add_argument( "--availability-zone", metavar="", - help="Create new volume in " - ) - parser.add_argument( - "--image", - metavar="", - help="Use as source of new volume (name or ID)" - ) - parser.add_argument( - "--source", - metavar="", - help="Volume to clone (name or ID)" + help="Create volume in ", ) parser.add_argument( "--property", metavar="", action=parseractions.KeyValueAction, help="Set a property to this volume " - "(repeat option to set multiple properties)" + "(repeat option to set multiple properties)", ) return parser @@ -188,13 +188,13 @@ def get_parser(self, prog_name): parser = super(ListVolume, self).get_parser(prog_name) parser.add_argument( '--project', - metavar='', + metavar='', help='Filter results by project (name or ID) (admin only)' ) identity_common.add_project_domain_option_to_parser(parser) parser.add_argument( '--user', - metavar='', + metavar='', help='Filter results by user (name or ID) (admin only)' ) identity_common.add_user_domain_option_to_parser(parser) @@ -320,36 +320,36 @@ def get_parser(self, prog_name): parser.add_argument( 'volume', metavar='', - help='Volume to change (name or ID)', + help='Volume to modify (name or ID)', ) parser.add_argument( '--name', metavar='', help='New volume name', ) - parser.add_argument( - '--description', - metavar='', - help='New volume description', - ) parser.add_argument( '--size', metavar='', type=int, help='Extend volume size in GB', ) + parser.add_argument( + '--description', + metavar='', + help='New volume description', + ) parser.add_argument( '--property', metavar='', action=parseractions.KeyValueAction, - help='Property to add or modify for this volume ' + help='Set a property on this volume ' '(repeat option to set multiple properties)', ) parser.add_argument( '--image-property', metavar='', action=parseractions.KeyValueAction, - help='To add or modify image properties for this volume ' + help='Set an image property on this volume ' '(repeat option to set multiple image properties)', ) return parser @@ -434,14 +434,14 @@ def get_parser(self, prog_name): '--property', metavar='', action='append', - help='Property to remove from volume ' + help='Remove a property from volume ' '(repeat option to remove multiple properties)', ) parser.add_argument( '--image-property', metavar='', action='append', - help='To remove image properties from volume ' + help='Remove an image property from volume ' '(repeat option to remove multiple image properties)', ) return parser From b2e47b62280a3e559207c3083d57d6a354f9c6a0 Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Tue, 29 Mar 2016 12:01:31 +0800 Subject: [PATCH 0760/3095] Aggregate object should be "grouping of compute hosts" The describe of Compute "aggregate" in command-objects and commands documents use "a grouping of servers", but exactly that should be a grouping of compute hosts. "server" object in OSC is a virtual machine instance, and server group should be "grouping of servers". Change-Id: Ib034fed15f11fc3e756985b3131a9922129ed6bf Closes-Bug: #1563172 Related-Bug: #1542171 --- doc/source/command-objects/aggregate.rst | 2 +- doc/source/commands.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/command-objects/aggregate.rst b/doc/source/command-objects/aggregate.rst index 6a1dd9089e..c002704f8a 100644 --- a/doc/source/command-objects/aggregate.rst +++ b/doc/source/command-objects/aggregate.rst @@ -2,7 +2,7 @@ aggregate ========= -Server aggregates provide a mechanism to group servers according to certain +Host aggregates provide a mechanism to group hosts according to certain criteria. Compute v2 diff --git a/doc/source/commands.rst b/doc/source/commands.rst index c54cadb1b9..d7f2d3c759 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -71,7 +71,7 @@ referring to both Compute and Volume quotas. * ``access token``: (**Identity**) long-lived OAuth-based token * ``availability zone``: (**Compute**, **Network**, **Volume**) a logical partition of hosts or block storage or network services -* ``aggregate``: (**Compute**) a grouping of servers +* ``aggregate``: (**Compute**) a grouping of compute hosts * ``backup``: (**Volume**) a volume copy * ``catalog``: (**Identity**) service catalog * ``command``: (**Internal**) installed commands in the OSC process From 848d5312fffa358a789a46a8657af3a92cb3ef6f Mon Sep 17 00:00:00 2001 From: Wenzhi Yu Date: Tue, 29 Mar 2016 17:20:05 +0800 Subject: [PATCH 0761/3095] Remove unused method 'from_response' 'openstackclient.common.exceptions.from_response' method is never called in openstackclient code base, so we should remove it. Change-Id: I04254a4e66863942e6c273d77bbd66ce2ce7804c Related-Bug: #1559072 --- openstackclient/common/exceptions.py | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/openstackclient/common/exceptions.py b/openstackclient/common/exceptions.py index ee0f7a1104..5f81e6a6e9 100644 --- a/openstackclient/common/exceptions.py +++ b/openstackclient/common/exceptions.py @@ -108,28 +108,3 @@ class HTTPNotImplemented(ClientException): OverLimit, HTTPNotImplemented ]) - - -def from_response(response, body): - """Return an instance of a ClientException based on an httplib2 response. - - Usage:: - - resp, body = http.request(...) - if resp.status != 200: - raise exception_from_response(resp, body) - """ - cls = _code_map.get(response.status, ClientException) - if body: - if hasattr(body, 'keys'): - error = body[list(body.keys())[0]] - message = error.get('message') - details = error.get('details') - else: - # If we didn't get back a properly formed error message we - # probably couldn't communicate with Keystone at all. - message = "Unable to communicate with image service: %s." % body - details = None - return cls(code=response.status, message=message, details=details) - else: - return cls(code=response.status) From 63c57eb56eb7767086f586d5251a788dd150e113 Mon Sep 17 00:00:00 2001 From: Sheel Rana Date: Wed, 30 Mar 2016 13:23:37 +0530 Subject: [PATCH 0762/3095] Wrong param type in compute-service.rst compute-service.rst file uses 'describe' for below optional parameters 1. --long 2. --service 3. --host This patchset changes type of these argument to option. Change-Id: Ifd57bdf058efaeaa6cb43a7d4a60ee61ddb8de14 Closes-bug:#1563700 --- doc/source/command-objects/compute-service.rst | 10 +++++----- openstackclient/compute/v2/service.py | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/source/command-objects/compute-service.rst b/doc/source/command-objects/compute-service.rst index aefe55db7a..db75bef426 100644 --- a/doc/source/command-objects/compute-service.rst +++ b/doc/source/command-objects/compute-service.rst @@ -34,15 +34,15 @@ List service command [--long] .. _compute-service-list: -.. describe:: --host +.. option:: --host - Name of host + List services on specified host (name only) -.. describe:: --service +.. option:: --service - Name of service + List only specified service (name only) -.. describe:: --long +.. option:: --long List additional fields in output diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index 89f5cad94f..68037c9445 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.py @@ -44,11 +44,11 @@ def get_parser(self, prog_name): parser.add_argument( "--host", metavar="", - help="Name of host") + help="List services on specified host (name only)") parser.add_argument( "--service", metavar="", - help="Name of service") + help="List only specified service (name only)") parser.add_argument( "--long", action="store_true", From 9174bc0f021d8401e5bbe17a47cb64298d0bc643 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Wed, 30 Mar 2016 13:21:45 -0500 Subject: [PATCH 0763/3095] Fix subnet pool prefix length option Fix the --min-prefix-length, --max-prefix-length and --default-prefix-length options for the "os subnet pool create" and "os subnet pool set" commands. Using these options caused the commands to fail with "HttpException: Bad Request" because unrecognized attributes were passed to the REST API. No release note was added since these commands are new and haven't been released. Change-Id: I2f765750f79dc91d6c1f5962f699fbf2ee38657a Closes-Bug: #1564004 --- openstackclient/network/v2/subnet_pool.py | 6 +++--- openstackclient/tests/network/v2/test_subnet_pool.py | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index d0d8d058c8..834760fb43 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -39,11 +39,11 @@ def _get_attrs(parsed_args): if parsed_args.prefixes is not None: attrs['prefixes'] = parsed_args.prefixes if parsed_args.default_prefix_length is not None: - attrs['default_prefix_length'] = parsed_args.default_prefix_length + attrs['default_prefixlen'] = parsed_args.default_prefix_length if parsed_args.min_prefix_length is not None: - attrs['min_prefix_length'] = parsed_args.min_prefix_length + attrs['min_prefixlen'] = parsed_args.min_prefix_length if parsed_args.max_prefix_length is not None: - attrs['max_prefix_length'] = parsed_args.max_prefix_length + attrs['max_prefixlen'] = parsed_args.max_prefix_length return attrs diff --git a/openstackclient/tests/network/v2/test_subnet_pool.py b/openstackclient/tests/network/v2/test_subnet_pool.py index ebedaf6436..6ed2352d67 100644 --- a/openstackclient/tests/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/network/v2/test_subnet_pool.py @@ -119,9 +119,9 @@ def test_create_prefixlen_options(self): columns, data = (self.cmd.take_action(parsed_args)) self.network.create_subnet_pool.assert_called_once_with(**{ - 'default_prefix_length': self._subnet_pool.default_prefixlen, - 'max_prefix_length': self._subnet_pool.max_prefixlen, - 'min_prefix_length': self._subnet_pool.min_prefixlen, + 'default_prefixlen': self._subnet_pool.default_prefixlen, + 'max_prefixlen': self._subnet_pool.max_prefixlen, + 'min_prefixlen': self._subnet_pool.min_prefixlen, 'name': self._subnet_pool.name, }) self.assertEqual(self.columns, columns) @@ -278,8 +278,8 @@ def test_set_this(self): attrs = { 'name': 'noob', - 'default_prefix_length': '8', - 'min_prefix_length': '8', + 'default_prefixlen': '8', + 'min_prefixlen': '8', } self.network.update_subnet_pool.assert_called_once_with( self._subnet_pool, **attrs) @@ -305,7 +305,7 @@ def test_set_that(self): prefixes.extend(self._subnet_pool.prefixes) attrs = { 'prefixes': prefixes, - 'max_prefix_length': '16', + 'max_prefixlen': '16', } self.network.update_subnet_pool.assert_called_once_with( self._subnet_pool, **attrs) From 029654b9a8c39f8dfc966f31c36f27cab84c6bc9 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 18 Mar 2016 09:30:03 -0500 Subject: [PATCH 0764/3095] Add fixed keypair create functional test This adds a test case to exercise the --public-key option of the 'keypair create' command. It is a follow-on to I7a299a542d9df543bff43d3ea1e7907fc8c5f640 that fixed a key file read bug. Change-Id: Id78c1c7ece02f619aca69dc397185fc426b92306 --- functional/tests/compute/v2/test_keypair.py | 26 +++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/functional/tests/compute/v2/test_keypair.py b/functional/tests/compute/v2/test_keypair.py index 1c6e1b1685..c9e8f3bcf5 100644 --- a/functional/tests/compute/v2/test_keypair.py +++ b/functional/tests/compute/v2/test_keypair.py @@ -10,11 +10,22 @@ # License for the specific language governing permissions and limitations # under the License. +import os import uuid from functional.common import test +PUBLIC_KEY = ( + 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDWNGczJxNaFUrJJVhta4dWsZY6bU' + '5HUMPbyfSMu713ca3mYtG848W4dfDCB98KmSQx2Bl0D6Q2nrOszOXEQWAXNdfMadnW' + 'c4mNwhZcPBVohIFoC1KZJC8kcBTvFZcoz3mdIijxJtywZNpGNh34VRJlZeHyYjg8/D' + 'esHzdoBVd5c/4R36emQSIV9ukY6PHeZ3scAH4B3K9PxItJBwiFtouSRphQG0bJgOv/' + 'gjAjMElAvg5oku98cb4QiHZ8T8WY68id804raHR6pJxpVVJN4TYJmlUs+NOVM+pPKb' + 'KJttqrIBTkawGK9pLHNfn7z6v1syvUo/4enc1l0Q/Qn2kWiz67 fake@openstack' +) + + class KeypairTests(test.TestCase): """Functional tests for compute keypairs. """ NAME = uuid.uuid4().hex @@ -32,6 +43,21 @@ def tearDownClass(cls): raw_output = cls.openstack('keypair delete ' + cls.NAME) cls.assertOutput('', raw_output) + def test_keypair_create(self): + TMP_FILE = uuid.uuid4().hex + self.addCleanup(os.remove, TMP_FILE) + with open(TMP_FILE, 'w') as f: + f.write(PUBLIC_KEY) + + raw_output = self.openstack( + 'keypair create --public-key ' + TMP_FILE + ' tmpkey', + ) + self.addCleanup( + self.openstack, + 'keypair delete tmpkey', + ) + self.assertIn('tmpkey', raw_output) + def test_keypair_list(self): opts = self.get_list_opts(self.HEADERS) raw_output = self.openstack('keypair list' + opts) From c5c15a0135beb61012b2d0282728a984d859e680 Mon Sep 17 00:00:00 2001 From: reedip Date: Wed, 23 Mar 2016 13:02:25 +0900 Subject: [PATCH 0765/3095] Follow Boolean Option rule OSC has a specific rule for handling boolean based options in [1]. This patch modifies the exisiting code so that it matches the criteria that have been specified in [1]. [1]: http://docs.openstack.org/developer/python-openstackclient/command-options.html#boolean-options Closes-Bug: #1559418 Change-Id: I182381d5579efbc75a5d3e8a91f412398abf5c3c --- openstackclient/network/v2/network.py | 34 +++++++++---------- openstackclient/network/v2/port.py | 14 ++++---- openstackclient/network/v2/router.py | 27 +++++++-------- .../tests/network/v2/test_network.py | 28 +++++++-------- openstackclient/tests/network/v2/test_port.py | 8 ++--- .../tests/network/v2/test_router.py | 12 +++---- 6 files changed, 57 insertions(+), 66 deletions(-) diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 074b27543d..42648e4558 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -52,10 +52,14 @@ def _get_attrs(client_manager, parsed_args): attrs = {} if parsed_args.name is not None: attrs['name'] = str(parsed_args.name) - if parsed_args.admin_state is not None: - attrs['admin_state_up'] = parsed_args.admin_state - if parsed_args.shared is not None: - attrs['shared'] = parsed_args.shared + if parsed_args.enable: + attrs['admin_state_up'] = True + if parsed_args.disable: + attrs['admin_state_up'] = False + if parsed_args.share: + attrs['shared'] = True + if parsed_args.no_share: + attrs['shared'] = False # "network set" command doesn't support setting project. if 'project' in parsed_args and parsed_args.project is not None: @@ -79,8 +83,10 @@ def _get_attrs_compute(client_manager, parsed_args): attrs = {} if parsed_args.name is not None: attrs['label'] = str(parsed_args.name) - if parsed_args.shared is not None: - attrs['share_address'] = parsed_args.shared + if parsed_args.share: + attrs['share_address'] = True + if parsed_args.no_share: + attrs['share_address'] = False if parsed_args.subnet is not None: attrs['cidr'] = parsed_args.subnet @@ -99,15 +105,13 @@ def update_parser_common(self, parser): share_group = parser.add_mutually_exclusive_group() share_group.add_argument( '--share', - dest='shared', action='store_true', default=None, help='Share the network between projects', ) share_group.add_argument( '--no-share', - dest='shared', - action='store_false', + action='store_true', help='Do not share the network between projects', ) return parser @@ -116,15 +120,13 @@ def update_parser_network(self, parser): admin_group = parser.add_mutually_exclusive_group() admin_group.add_argument( '--enable', - dest='admin_state', action='store_true', default=True, help='Enable network (default)', ) admin_group.add_argument( '--disable', - dest='admin_state', - action='store_false', + action='store_true', help='Disable network', ) parser.add_argument( @@ -301,29 +303,25 @@ def get_parser(self, prog_name): admin_group = parser.add_mutually_exclusive_group() admin_group.add_argument( '--enable', - dest='admin_state', action='store_true', default=None, help='Enable network', ) admin_group.add_argument( '--disable', - dest='admin_state', - action='store_false', + action='store_true', help='Disable network', ) share_group = parser.add_mutually_exclusive_group() share_group.add_argument( '--share', - dest='shared', action='store_true', default=None, help='Share the network between projects', ) share_group.add_argument( '--no-share', - dest='shared', - action='store_false', + action='store_true', help='Do not share the network between projects', ) return parser diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 23350cf85e..45ad1c72ba 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -86,8 +86,10 @@ def _get_attrs(client_manager, parsed_args): attrs['device_id'] = parsed_args.device if parsed_args.device_owner is not None: attrs['device_owner'] = parsed_args.device_owner - if parsed_args.admin_state is not None: - attrs['admin_state_up'] = parsed_args.admin_state + if parsed_args.enable: + attrs['admin_state_up'] = True + if parsed_args.disable: + attrs['admin_state_up'] = False if parsed_args.binding_profile is not None: attrs['binding:profile'] = parsed_args.binding_profile if parsed_args.vnic_type is not None: @@ -217,15 +219,13 @@ def get_parser(self, prog_name): admin_group = parser.add_mutually_exclusive_group() admin_group.add_argument( '--enable', - dest='admin_state', action='store_true', default=True, help='Enable port (default)', ) admin_group.add_argument( '--disable', - dest='admin_state', - action='store_false', + action='store_true', help='Disable port', ) parser.add_argument( @@ -333,15 +333,13 @@ def get_parser(self, prog_name): admin_group = parser.add_mutually_exclusive_group() admin_group.add_argument( '--enable', - dest='admin_state', action='store_true', default=None, help='Enable port', ) admin_group.add_argument( '--disable', - dest='admin_state', - action='store_false', + action='store_true', help='Disable port', ) parser.add_argument( diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 394311112e..6819733b6b 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -53,10 +53,15 @@ def _get_attrs(client_manager, parsed_args): attrs = {} if parsed_args.name is not None: attrs['name'] = str(parsed_args.name) - if parsed_args.admin_state_up is not None: - attrs['admin_state_up'] = parsed_args.admin_state_up - if parsed_args.distributed is not None: - attrs['distributed'] = parsed_args.distributed + if parsed_args.enable: + attrs['admin_state_up'] = True + if parsed_args.disable: + attrs['admin_state_up'] = False + # centralized is available only for SetRouter and not for CreateRouter + if 'centralized' in parsed_args and parsed_args.centralized: + attrs['distributed'] = False + if parsed_args.distributed: + attrs['distributed'] = True if ('availability_zone_hints' in parsed_args and parsed_args.availability_zone_hints is not None): attrs['availability_zone_hints'] = parsed_args.availability_zone_hints @@ -95,15 +100,13 @@ def get_parser(self, prog_name): admin_group = parser.add_mutually_exclusive_group() admin_group.add_argument( '--enable', - dest='admin_state_up', action='store_true', default=True, help="Enable router (default)", ) admin_group.add_argument( '--disable', - dest='admin_state_up', - action='store_false', + action='store_true', help="Disable router", ) parser.add_argument( @@ -235,29 +238,24 @@ def get_parser(self, prog_name): admin_group = parser.add_mutually_exclusive_group() admin_group.add_argument( '--enable', - dest='admin_state_up', action='store_true', default=None, help='Enable router', ) admin_group.add_argument( '--disable', - dest='admin_state_up', - action='store_false', + action='store_true', help='Disable router', ) distribute_group = parser.add_mutually_exclusive_group() distribute_group.add_argument( '--distributed', - dest='distributed', action='store_true', - default=None, help="Set router to distributed mode (disabled router only)", ) distribute_group.add_argument( '--centralized', - dest='distributed', - action='store_false', + action='store_true', help="Set router to centralized mode (disabled router only)", ) routes_group = parser.add_mutually_exclusive_group() @@ -275,7 +273,6 @@ def get_parser(self, prog_name): ) routes_group.add_argument( '--clear-routes', - dest='clear_routes', action='store_true', help="Clear routes associated with the router", ) diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index 884a6e8181..26b98f7727 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -116,8 +116,8 @@ def test_create_default_options(self): ] verifylist = [ ('name', self._network.name), - ('admin_state', True), - ('shared', None), + ('enable', True), + ('share', None), ('project', None), ] @@ -141,8 +141,8 @@ def test_create_all_options(self): self._network.name, ] verifylist = [ - ('admin_state', False), - ('shared', True), + ('disable', True), + ('share', True), ('project', identity_fakes_v3.project_name), ('project_domain', identity_fakes_v3.domain_name), ('availability_zone_hints', ["nova"]), @@ -169,8 +169,8 @@ def test_create_other_options(self): self._network.name, ] verifylist = [ - ('admin_state', True), - ('shared', False), + ('enable', True), + ('no_share', True), ('name', self._network.name), ] @@ -249,8 +249,8 @@ def test_create_with_project_identityv2(self): self._network.name, ] verifylist = [ - ('admin_state', True), - ('shared', None), + ('enable', True), + ('share', None), ('name', self._network.name), ('project', identity_fakes_v2.project_name), ] @@ -273,8 +273,8 @@ def test_create_with_domain_identityv2(self): self._network.name, ] verifylist = [ - ('admin_state', True), - ('shared', None), + ('enable', True), + ('share', None), ('project', identity_fakes_v3.project_name), ('project_domain', identity_fakes_v3.domain_name), ('name', self._network.name), @@ -455,9 +455,9 @@ def test_set_this(self): ] verifylist = [ ('network', self._network.name), - ('admin_state', True), + ('enable', True), ('name', 'noob'), - ('shared', True), + ('share', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -480,8 +480,8 @@ def test_set_that(self): ] verifylist = [ ('network', self._network.name), - ('admin_state', False), - ('shared', False), + ('disable', True), + ('no_share', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/network/v2/test_port.py b/openstackclient/tests/network/v2/test_port.py index cdbc699a6e..cb1af2b86e 100644 --- a/openstackclient/tests/network/v2/test_port.py +++ b/openstackclient/tests/network/v2/test_port.py @@ -103,7 +103,7 @@ def test_create_default_options(self): ] verifylist = [ ('network', self._port.network_id,), - ('admin_state', True), + ('enable', True), ('name', 'test-port'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -143,7 +143,7 @@ def test_create_full_options(self): ), ('device', 'deviceid'), ('device_owner', 'fakeowner'), - ('admin_state', False), + ('disable', True), ('vnic_type', 'macvtap'), ('binding_profile', {'foo': 'bar', 'foo2': 'bar2'}), ('network', self._port.network_id), @@ -301,7 +301,7 @@ def test_set_this(self): self._port.name, ] verifylist = [ - ('admin_state', False), + ('disable', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -322,7 +322,7 @@ def test_set_that(self): self._port.name, ] verifylist = [ - ('admin_state', True), + ('enable', True), ('vnic_type', 'macvtap'), ('binding_profile', {'foo': 'bar'}), ('host', 'binding-host-id-xxxx'), diff --git a/openstackclient/tests/network/v2/test_router.py b/openstackclient/tests/network/v2/test_router.py index f3bf363d2a..4aaa68e4f9 100644 --- a/openstackclient/tests/network/v2/test_router.py +++ b/openstackclient/tests/network/v2/test_router.py @@ -73,7 +73,7 @@ def test_create_default_options(self): ] verifylist = [ ('name', self.new_router.name), - ('admin_state_up', True), + ('enable', True), ('distributed', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -83,7 +83,6 @@ def test_create_default_options(self): self.network.create_router.assert_called_once_with(**{ 'admin_state_up': True, 'name': self.new_router.name, - 'distributed': False, }) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -97,7 +96,7 @@ def test_create_with_AZ_hints(self): verifylist = [ ('name', self.new_router.name), ('availability_zone_hints', ['fake-az', 'fake-az2']), - ('admin_state_up', True), + ('enable', True), ('distributed', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -106,7 +105,6 @@ def test_create_with_AZ_hints(self): self.network.create_router.assert_called_once_with(**{ 'admin_state_up': True, 'name': self.new_router.name, - 'distributed': False, 'availability_zone_hints': ['fake-az', 'fake-az2'], }) @@ -252,7 +250,7 @@ def test_set_this(self): ] verifylist = [ ('router', self._router.name), - ('admin_state_up', True), + ('enable', True), ('distributed', True), ('name', 'noob'), ] @@ -277,8 +275,8 @@ def test_set_that(self): ] verifylist = [ ('router', self._router.name), - ('admin_state_up', False), - ('distributed', False), + ('disable', True), + ('centralized', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) From a1a470693e264e991b1dc9497512769bb8d510d8 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Wed, 23 Mar 2016 11:27:37 +0800 Subject: [PATCH 0766/3095] Add --project to "subnet pool create" This patch adds --project and --project-domain options to "subnet pool create" command. Change-Id: I2fe006013a194861299a9c77234a7cf988a8dad8 Partial-Bug: #1544586 --- doc/source/command-objects/subnet-pool.rst | 10 ++++ openstackclient/network/v2/subnet_pool.py | 25 +++++++-- .../tests/network/v2/test_subnet_pool.py | 52 +++++++++++++++++++ .../notes/bug-1544586-0e6ca9a09dac0726.yaml | 3 ++ 4 files changed, 86 insertions(+), 4 deletions(-) diff --git a/doc/source/command-objects/subnet-pool.rst b/doc/source/command-objects/subnet-pool.rst index 439c4f5033..cb86e2273c 100644 --- a/doc/source/command-objects/subnet-pool.rst +++ b/doc/source/command-objects/subnet-pool.rst @@ -17,6 +17,7 @@ Create subnet pool [--default-prefix-length ] [--min-prefix-length ] [--max-prefix-length ] + [--project [--project-domain ]] .. option:: --pool-prefix @@ -36,6 +37,15 @@ Create subnet pool Set subnet pool maximum prefix length +.. option:: --project + + Owner's project (name or ID) + +.. option:: --project-domain + + Domain the project belongs to (name or ID). This can be used in case + collisions between project names exist. + .. _subnet_pool_create-name: .. describe:: diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index 834760fb43..8c90b95511 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -17,6 +17,7 @@ from openstackclient.common import exceptions from openstackclient.common import parseractions from openstackclient.common import utils +from openstackclient.identity import common as identity_common def _get_columns(item): @@ -32,7 +33,7 @@ def _get_columns(item): } -def _get_attrs(parsed_args): +def _get_attrs(client_manager, parsed_args): attrs = {} if parsed_args.name is not None: attrs['name'] = str(parsed_args.name) @@ -45,6 +46,16 @@ def _get_attrs(parsed_args): if parsed_args.max_prefix_length is not None: attrs['max_prefixlen'] = parsed_args.max_prefix_length + # "subnet pool set" command doesn't support setting project. + if 'project' in parsed_args and parsed_args.project is not None: + identity_client = client_manager.identity + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + attrs['tenant_id'] = project_id + return attrs @@ -84,16 +95,22 @@ def get_parser(self, prog_name): parser = super(CreateSubnetPool, self).get_parser(prog_name) parser.add_argument( 'name', - metavar="", + metavar='', help='Name of the new subnet pool' ) _add_prefix_options(parser) + parser.add_argument( + '--project', + metavar='', + help="Owner's project (name or ID)", + ) + identity_common.add_project_domain_option_to_parser(parser) return parser def take_action(self, parsed_args): client = self.app.client_manager.network - attrs = _get_attrs(parsed_args) + attrs = _get_attrs(self.app.client_manager, parsed_args) obj = client.create_subnet_pool(**attrs) columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) @@ -192,7 +209,7 @@ def take_action(self, parsed_args): obj = client.find_subnet_pool(parsed_args.subnet_pool, ignore_missing=False) - attrs = _get_attrs(parsed_args) + attrs = _get_attrs(self.app.client_manager, parsed_args) if attrs == {}: msg = "Nothing specified to be set" raise exceptions.CommandError(msg) diff --git a/openstackclient/tests/network/v2/test_subnet_pool.py b/openstackclient/tests/network/v2/test_subnet_pool.py index 6ed2352d67..c79b91799c 100644 --- a/openstackclient/tests/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/network/v2/test_subnet_pool.py @@ -12,11 +12,14 @@ # import argparse +import copy import mock from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.network.v2 import subnet_pool +from openstackclient.tests import fakes +from openstackclient.tests.identity.v3 import fakes as identity_fakes_v3 from openstackclient.tests.network.v2 import fakes as network_fakes from openstackclient.tests import utils as tests_utils @@ -73,6 +76,30 @@ def setUp(self): # Get the command object to test self.cmd = subnet_pool.CreateSubnetPool(self.app, self.namespace) + # Set identity client. And get a shortcut to Identity client. + identity_client = identity_fakes_v3.FakeIdentityv3Client( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN, + ) + self.app.client_manager.identity = identity_client + self.identity = self.app.client_manager.identity + + # Get a shortcut to the ProjectManager Mock + self.projects_mock = self.identity.projects + self.projects_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes_v3.PROJECT), + loaded=True, + ) + + # Get a shortcut to the DomainManager Mock + self.domains_mock = self.identity.domains + self.domains_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes_v3.DOMAIN), + loaded=True, + ) + def test_create_no_options(self): arglist = [] verifylist = [] @@ -140,6 +167,31 @@ def test_create_len_negative(self): self.assertRaises(argparse.ArgumentTypeError, self.check_parser, self.cmd, arglist, verifylist) + def test_create_project_domain(self): + arglist = [ + '--pool-prefix', '10.0.10.0/24', + "--project", identity_fakes_v3.project_name, + "--project-domain", identity_fakes_v3.domain_name, + self._subnet_pool.name, + ] + verifylist = [ + ('prefixes', ['10.0.10.0/24']), + ('project', identity_fakes_v3.project_name), + ('project_domain', identity_fakes_v3.domain_name), + ('name', self._subnet_pool.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_subnet_pool.assert_called_once_with(**{ + 'prefixes': ['10.0.10.0/24'], + 'tenant_id': identity_fakes_v3.project_id, + 'name': self._subnet_pool.name, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + class TestDeleteSubnetPool(TestSubnetPool): diff --git a/releasenotes/notes/bug-1544586-0e6ca9a09dac0726.yaml b/releasenotes/notes/bug-1544586-0e6ca9a09dac0726.yaml index e1595ed374..fdbd6fd18b 100644 --- a/releasenotes/notes/bug-1544586-0e6ca9a09dac0726.yaml +++ b/releasenotes/notes/bug-1544586-0e6ca9a09dac0726.yaml @@ -2,3 +2,6 @@ features: - Add ``subnet pool create`` command. [Bug `1544586 `_] + - Command ``subnet pool create`` now supports ``--project`` and + ``--project-domain`` options. + [Bug `1544586 `_] From cd96d2966f031f695ed58221f5112c1bb544baef Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Fri, 25 Mar 2016 08:06:32 -0500 Subject: [PATCH 0767/3095] Doc: Add missing command objects Add missing command objects that have command documentation. Change-Id: I2a34cc632c00c7f6bcf6481e6779b79093cd6488 --- doc/source/commands.rst | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/doc/source/commands.rst b/doc/source/commands.rst index d7f2d3c759..f71fbccb4e 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -70,11 +70,13 @@ the API resources will be merged, as in the ``quota`` object that has options referring to both Compute and Volume quotas. * ``access token``: (**Identity**) long-lived OAuth-based token -* ``availability zone``: (**Compute**, **Network**, **Volume**) a logical partition of hosts or block storage or network services * ``aggregate``: (**Compute**) a grouping of compute hosts +* ``availability zone``: (**Compute**, **Network**, **Volume**) a logical partition of hosts or block storage or network services * ``backup``: (**Volume**) a volume copy * ``catalog``: (**Identity**) service catalog * ``command``: (**Internal**) installed commands in the OSC process +* ``compute agent``: (**Compute**) a cloud Compute agent available to a hypervisor +* ``compute service``: (**Compute**) a cloud Compute process running on a host * ``configuration``: (**Internal**) openstack client configuration * ``console log``: (**Compute**) server console text dump * ``console url``: (**Compute**) server remote console URL @@ -88,7 +90,7 @@ referring to both Compute and Volume quotas. * ``federation protocol``: (**Identity**) the underlying protocol used while federating identities * ``flavor``: (**Compute**) predefined server configurations: ram, root disk, etc * ``group``: (**Identity**) a grouping of users -* ``host``: (**Compute**) - the physical computer running a hypervisor +* ``host``: (**Compute**) - the physical computer running compute services * ``hypervisor``: (**Compute**) the virtual machine manager * ``hypervisor stats``: (**Compute**) hypervisor statistics over all compute nodes * ``identity provider``: (**Identity**) a source of users and authentication @@ -102,6 +104,7 @@ referring to both Compute and Volume quotas. * ``module``: (**Internal**) - installed Python modules in the OSC process * ``network``: (**Compute**, **Network**) - a virtual network for connecting servers and other resources * ``object``: (**Object Storage**) a single file in the Object Storage +* ``object store account``: (**Object Storage**) owns a group of Object Storage resources * ``policy``: (**Identity**) determines authorization * ``port``: (**Network**) - a virtual port for connecting servers and other resources to a network * ``project``: (**Identity**) owns a group of resources @@ -122,10 +125,12 @@ referring to both Compute and Volume quotas. * ``subnet``: (**Network**) - a contiguous range of IP addresses assigned to a network * ``subnet pool``: (**Network**) - a pool of subnets * ``token``: (**Identity**) a bearer token managed by Identity service +* ``trust``: (**Identity**) project-specific role delegation between users, with optional impersonation * ``usage``: (**Compute**) display host resources being consumed * ``user``: (**Identity**) individual cloud resources users * ``user role``: (**Identity**) roles assigned to a user * ``volume``: (**Volume**) block volumes +* ``volume qos``: (**Volume**) quality-of-service (QoS) specification for volumes * ``volume type``: (**Volume**) deployment-specific types of volumes available From 8ba257cb303bd1870c7421a61f81dfd043182fec Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Fri, 25 Mar 2016 10:02:54 -0500 Subject: [PATCH 0768/3095] Devref: Options with Multiple Values Add a devref for options with multiple values. Change-Id: Ic90c2317eb6c0445d234964c5243ecc689d5f4c7 --- doc/source/command-options.rst | 81 ++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/doc/source/command-options.rst b/doc/source/command-options.rst index b3cc002a48..d47a56dc23 100644 --- a/doc/source/command-options.rst +++ b/doc/source/command-options.rst @@ -105,6 +105,87 @@ An example parser declaration: help=_('Test type (choice1, choice2 or choice3)'), ) +Options with Multiple Values +---------------------------- + +Some options can be repeated to build a collection of values for a property. +Adding a value to the collection must be provided via the ``set`` action. +Removing a value from the collection must be provided via an ``unset`` action. +As a convenience, removing all values from the collection may be provided via a +``--no`` option on the ``set`` and ``unset`` actions. The ``--no`` option must +be part of a mutually exclusive group with the related property option. + +The example below assumes a property that contains a list of unique values. +However, this example can also be applied to other collections using the +appropriate parser action and action implementation (e.g. a dict of key/value +pairs). Implementations will vary depending on how the REST API handles +adding/removing values to/from the collection and whether or not duplicate +values are allowed. + +Implementation +~~~~~~~~~~~~~~ + +An example parser declaration for `set` action: + +.. code-block:: python + + example_property_group = parser.add_mutually_exclusive_group() + example_property_group.add_argument( + '--example-property', + metavar='', + dest='example_property', + action='append', + help=_('Example property for this ' + '(repeat option to set multiple properties)'), + ) + example_property_group.add_argument( + '--no-example-property', + dest='no_example_property', + action='store_true', + help=_('Remove all example properties for this '), + ) + +An example handler in `take_action()` for `set` action: + +.. code-block:: python + + if parsed_args.example_property: + kwargs['example_property'] = \ + resource_example_property + parsed_args.example_property + if parsed_args.no_example_property: + kwargs['example_property'] = [] + +An example parser declaration for `unset` action: + +.. code-block:: python + + example_property_group = parser.add_mutually_exclusive_group() + example_property_group.add_argument( + '--example-property', + metavar='', + dest='example_property', + action='append', + help=_('Example property for this ' + '(repeat option to remove multiple properties)'), + ) + example_property_group.add_argument( + '--no-example-property', + dest='no_example_property', + action='store_true', + help=_('Remove all example properties for this '), + ) + +An example handler in `take_action()` for `unset` action: + +.. code-block:: python + + if parsed_args.example_property: + kwargs['example_property'] = \ + list(set(resource_example_property) - \ + set(parsed_args.example_property)) + if parsed_args.no_example_property: + kwargs['example_property'] = [] + List Command Options ==================== From 7027d915b5d00e719ec2035b34ea9a3702b2f3fc Mon Sep 17 00:00:00 2001 From: reedip Date: Thu, 31 Mar 2016 17:33:32 +0900 Subject: [PATCH 0769/3095] Add default value to pool-prefix in Subnet-pool If user tries to create a subnet pool without --pool-prefix, it fails at the NeutronServer [1]. This patch tries to add a default value to the --pool-prefix to try and resolve it. Closes-Bug: #1564271 [1] : http://paste.openstack.org/show/492537/ Change-Id: I6cf324a5a8037048602e59c0bbfc93b40e73a74e --- openstackclient/network/v2/subnet_pool.py | 3 +++ openstackclient/tests/network/v2/test_subnet_pool.py | 1 + 2 files changed, 4 insertions(+) diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index 8c90b95511..6b6fc090f9 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -111,6 +111,9 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): client = self.app.client_manager.network attrs = _get_attrs(self.app.client_manager, parsed_args) + # NeutronServer expects prefixes to be a List + if "prefixes" not in attrs: + attrs['prefixes'] = [] obj = client.create_subnet_pool(**attrs) columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) diff --git a/openstackclient/tests/network/v2/test_subnet_pool.py b/openstackclient/tests/network/v2/test_subnet_pool.py index c79b91799c..093e26c67e 100644 --- a/openstackclient/tests/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/network/v2/test_subnet_pool.py @@ -149,6 +149,7 @@ def test_create_prefixlen_options(self): 'default_prefixlen': self._subnet_pool.default_prefixlen, 'max_prefixlen': self._subnet_pool.max_prefixlen, 'min_prefixlen': self._subnet_pool.min_prefixlen, + 'prefixes': [], 'name': self._subnet_pool.name, }) self.assertEqual(self.columns, columns) From 55b37d5e33f322077303a895d3453320b3895f11 Mon Sep 17 00:00:00 2001 From: Hidekazu Nakamura Date: Fri, 6 Nov 2015 00:47:38 +0900 Subject: [PATCH 0770/3095] Don't mask authorization errors Project show with name argument returns 'Could not find resource' error when the user is not authorized. It should report the authorization error instead. This patch makes that change. Change-Id: Iac3521f8a411060b0ec9ef46c8f0e1f3551e56ae Closes-Bug: #1511625 --- openstackclient/common/utils.py | 25 ++++++++-------------- openstackclient/tests/common/test_utils.py | 12 +++++++++++ 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index c6ed6a716e..daa65c25fc 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -100,22 +100,15 @@ def find_resource(manager, name_or_id, **kwargs): else: pass - try: - for resource in manager.list(): - # short circuit and return the first match - if (resource.get('id') == name_or_id or - resource.get('name') == name_or_id): - return resource - else: - # we found no match, keep going to bomb out - pass - except Exception: - # in case the list fails for some reason - pass - - # if we hit here, we've failed, report back this error: - msg = "Could not find resource %s" % name_or_id - raise exceptions.CommandError(msg) + for resource in manager.list(): + # short circuit and return the first match + if (resource.get('id') == name_or_id or + resource.get('name') == name_or_id): + return resource + else: + # we found no match, report back this error: + msg = "Could not find resource %s" % name_or_id + raise exceptions.CommandError(msg) def format_dict(data): diff --git a/openstackclient/tests/common/test_utils.py b/openstackclient/tests/common/test_utils.py index 95bce45871..2248d0430e 100644 --- a/openstackclient/tests/common/test_utils.py +++ b/openstackclient/tests/common/test_utils.py @@ -306,6 +306,18 @@ def test_find_resource_find_not_found(self): self.manager.get.assert_called_with(self.name) self.manager.find.assert_called_with(name=self.name) + def test_find_resource_list_forbidden(self): + self.manager.get = mock.Mock(side_effect=Exception('Boom!')) + self.manager.find = mock.Mock(side_effect=Exception('Boom!')) + self.manager.list = mock.Mock( + side_effect=exceptions.Forbidden(403) + ) + self.assertRaises(exceptions.Forbidden, + utils.find_resource, + self.manager, + self.name) + self.manager.list.assert_called_with() + def test_find_resource_find_no_unique(self): self.manager.get = mock.Mock(side_effect=Exception('Boom!')) self.manager.find = mock.Mock(side_effect=NoUniqueMatch()) From 97492c168f395eb80009162eb2e242dde23e0ba5 Mon Sep 17 00:00:00 2001 From: Sean Dague Date: Mon, 4 Apr 2016 15:38:34 -0400 Subject: [PATCH 0771/3095] rxtx factor should be a float The rxtx factor on compute flavors is a float in the API. OSC is currently blocking float values. Change-Id: Ifa9c14825f388d2821ff4b63ab8ae83fa9c8d88b --- doc/source/command-objects/flavor.rst | 2 +- openstackclient/compute/v2/flavor.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/source/command-objects/flavor.rst b/doc/source/command-objects/flavor.rst index 5893ff07a5..322943d763 100644 --- a/doc/source/command-objects/flavor.rst +++ b/doc/source/command-objects/flavor.rst @@ -49,7 +49,7 @@ Create new flavor .. option:: --rxtx-factor - RX/TX factor (default 1) + RX/TX factor (default 1.0) .. option:: --public diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index b5a7c60cbc..29e0e9d45e 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -76,10 +76,10 @@ def get_parser(self, prog_name): ) parser.add_argument( "--rxtx-factor", - type=int, + type=float, metavar="", - default=1, - help="RX/TX factor (default 1)", + default=1.0, + help="RX/TX factor (default 1.0)", ) public_group = parser.add_mutually_exclusive_group() public_group.add_argument( From 66f94dca5c524cbe80dfa669cd5422cbb2663fbd Mon Sep 17 00:00:00 2001 From: reedip Date: Fri, 1 Apr 2016 13:48:57 +0900 Subject: [PATCH 0772/3095] Add name option to 'port set' Port's name can be updated in NeutronClient and it is a very good feature for a user to have the ability to rename a port. This was missing in the openstackclient, and the same has been added in this patch. Change-Id: I6e712ef08ab1c0a23786c4bb6972d3e0f8f0f999 Implements: blueprint neutron-client --- doc/source/command-objects/port.rst | 5 +++++ openstackclient/network/v2/port.py | 9 +++++++-- openstackclient/tests/network/v2/test_port.py | 3 +++ .../notes/add-name-to-port-set-eefa081225bf8b49.yaml | 4 ++++ 4 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/add-name-to-port-set-eefa081225bf8b49.yaml diff --git a/doc/source/command-objects/port.rst b/doc/source/command-objects/port.rst index 4d58745bc3..46bd6339ae 100644 --- a/doc/source/command-objects/port.rst +++ b/doc/source/command-objects/port.rst @@ -130,6 +130,7 @@ Set port properties [--binding-profile ] [--host-id ] [--enable | --disable] + [--name ] .. option:: --fixed-ip subnet=,ip-address= @@ -168,6 +169,10 @@ Set port properties Disable port +.. option:: --name + + Set port name + .. _port_set-port: .. describe:: diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 45ad1c72ba..d7866cccdf 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -97,10 +97,11 @@ def _get_attrs(client_manager, parsed_args): if parsed_args.host: attrs['binding:host_id'] = parsed_args.host + # It is possible that name is not updated during 'port set' + if parsed_args.name is not None: + attrs['name'] = str(parsed_args.name) # The remaining options do not support 'port set' command, so they require # additional check - if 'name' in parsed_args and parsed_args.name is not None: - attrs['name'] = str(parsed_args.name) if 'mac_address' in parsed_args and parsed_args.mac_address is not None: attrs['mac_address'] = parsed_args.mac_address if 'network' in parsed_args and parsed_args.network is not None: @@ -342,6 +343,10 @@ def get_parser(self, prog_name): action='store_true', help='Disable port', ) + parser.add_argument( + '--name', + metavar="", + help=('Set port name')) parser.add_argument( 'port', metavar="", diff --git a/openstackclient/tests/network/v2/test_port.py b/openstackclient/tests/network/v2/test_port.py index cb1af2b86e..31454dba57 100644 --- a/openstackclient/tests/network/v2/test_port.py +++ b/openstackclient/tests/network/v2/test_port.py @@ -319,6 +319,7 @@ def test_set_that(self): '--vnic-type', 'macvtap', '--binding-profile', 'foo=bar', '--host', 'binding-host-id-xxxx', + '--name', 'newName', self._port.name, ] verifylist = [ @@ -326,6 +327,7 @@ def test_set_that(self): ('vnic_type', 'macvtap'), ('binding_profile', {'foo': 'bar'}), ('host', 'binding-host-id-xxxx'), + ('name', 'newName') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -336,6 +338,7 @@ def test_set_that(self): 'binding:vnic_type': 'macvtap', 'binding:profile': {'foo': 'bar'}, 'binding:host_id': 'binding-host-id-xxxx', + 'name': 'newName', } self.network.update_port.assert_called_once_with(self._port, **attrs) self.assertIsNone(result) diff --git a/releasenotes/notes/add-name-to-port-set-eefa081225bf8b49.yaml b/releasenotes/notes/add-name-to-port-set-eefa081225bf8b49.yaml new file mode 100644 index 0000000000..13ecad265a --- /dev/null +++ b/releasenotes/notes/add-name-to-port-set-eefa081225bf8b49.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Port name can now be updated using ``port set`` \ No newline at end of file From c3f6ee95709d1ccb7de7818e4403645a1e9a5662 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 5 Apr 2016 19:43:33 +0800 Subject: [PATCH 0773/3095] Trivial: Rename FakehypervisorStats to FakeHypervisorStats Change-Id: I138b1b8a3327947b8cd032d8d0c32d98548ce2ad --- openstackclient/tests/compute/v2/fakes.py | 4 ++-- openstackclient/tests/compute/v2/test_hypervisor_stats.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 860963eb17..6c67c470b8 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -280,7 +280,7 @@ def create_hypervisors(attrs={}, count=2): return hypervisors -class FakehypervisorStats(object): +class FakeHypervisorStats(object): """Fake one or more hypervisor stats.""" @staticmethod @@ -333,7 +333,7 @@ def create_hypervisors_stats(attrs={}, count=2): hypervisors = [] for i in range(0, count): hypervisors.append( - FakehypervisorStats.create_one_hypervisor_stats(attrs)) + FakeHypervisorStats.create_one_hypervisor_stats(attrs)) return hypervisors diff --git a/openstackclient/tests/compute/v2/test_hypervisor_stats.py b/openstackclient/tests/compute/v2/test_hypervisor_stats.py index 39e303a84e..ca5ce29b38 100644 --- a/openstackclient/tests/compute/v2/test_hypervisor_stats.py +++ b/openstackclient/tests/compute/v2/test_hypervisor_stats.py @@ -33,7 +33,7 @@ def setUp(self): super(TestHypervisorStatsShow, self).setUp() self.hypervisor_stats = \ - compute_fakes.FakehypervisorStats.create_one_hypervisor_stats() + compute_fakes.FakeHypervisorStats.create_one_hypervisor_stats() self.hypervisors_mock.statistics.return_value =\ self.hypervisor_stats From 4d3f996e7ce5385688f6e570b8ac73327bac9f44 Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Mon, 14 Mar 2016 16:53:51 +0800 Subject: [PATCH 0774/3095] Log hint when --enable present with --disable-reason --enable and --disable-reason should be mutually exclusive in "compute service set" command, but now when they are present at the same time, --disable-reason would be ignored silently. Fix these and add some hints about --disable-reason argument is ignored in this situation. Change-Id: I43254b6bc40fcae4fd0dc3457f26fad84c267072 Closes-Bug: #1556801 --- .../command-objects/compute-service.rst | 5 +- openstackclient/compute/v2/service.py | 9 ++- .../tests/compute/v2/test_service.py | 73 ++++++++++++++++--- 3 files changed, 74 insertions(+), 13 deletions(-) diff --git a/doc/source/command-objects/compute-service.rst b/doc/source/command-objects/compute-service.rst index db75bef426..bda64c811d 100644 --- a/doc/source/command-objects/compute-service.rst +++ b/doc/source/command-objects/compute-service.rst @@ -36,7 +36,7 @@ List service command .. _compute-service-list: .. option:: --host - List services on specified host (name only) + List services on specified host (name only) .. option:: --service @@ -71,7 +71,8 @@ Set service command .. option:: --disable-reason - Reason for disabling the service (in quotes) + Reason for disabling the service (in quotes). Note that when the service + is enabled, this option is ignored. .. describe:: diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index 68037c9445..2b51af3d64 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.py @@ -17,6 +17,7 @@ from openstackclient.common import command from openstackclient.common import utils +from openstackclient.i18n import _ # noqa class DeleteService(command.Command): @@ -117,8 +118,8 @@ def get_parser(self, prog_name): "--disable-reason", default=None, metavar="", - help="Reason for disabling the service (in quotas)" - ) + help="Reason for disabling the service (in quotas). Note that " + "when the service is enabled, this option is ignored.") return parser def take_action(self, parsed_args): @@ -133,4 +134,8 @@ def take_action(self, parsed_args): else: cs.disable(parsed_args.host, parsed_args.service) else: + if parsed_args.disable_reason: + msg = _("argument --disable-reason has been ignored") + self.log.info(msg) + cs.enable(parsed_args.host, parsed_args.service) diff --git a/openstackclient/tests/compute/v2/test_service.py b/openstackclient/tests/compute/v2/test_service.py index 2feaf1566f..db09720457 100644 --- a/openstackclient/tests/compute/v2/test_service.py +++ b/openstackclient/tests/compute/v2/test_service.py @@ -14,6 +14,7 @@ # import copy +import mock from openstackclient.compute.v2 import service from openstackclient.tests.compute.v2 import fakes as compute_fakes @@ -138,14 +139,14 @@ def setUp(self): def test_service_set_enable(self): arglist = [ + '--enable', compute_fakes.service_host, compute_fakes.service_binary, - '--enable', ] verifylist = [ + ('enabled', True), ('host', compute_fakes.service_host), ('service', compute_fakes.service_binary), - ('enabled', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -159,14 +160,14 @@ def test_service_set_enable(self): def test_service_set_disable(self): arglist = [ + '--disable', compute_fakes.service_host, compute_fakes.service_binary, - '--disable', ] verifylist = [ + ('enabled', False), ('host', compute_fakes.service_host), ('service', compute_fakes.service_binary), - ('enabled', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -181,17 +182,16 @@ def test_service_set_disable(self): def test_service_set_disable_with_reason(self): reason = 'earthquake' arglist = [ + '--disable', + '--disable-reason', reason, compute_fakes.service_host, compute_fakes.service_binary, - '--disable', - '--disable-reason', - reason ] verifylist = [ + ('enabled', False), + ('disable_reason', reason), ('host', compute_fakes.service_host), ('service', compute_fakes.service_binary), - ('enabled', False), - ('disable_reason', reason) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -203,3 +203,58 @@ def test_service_set_disable_with_reason(self): reason ) self.assertIsNone(result) + + def test_service_set_only_with_disable_reason(self): + reason = 'earthquake' + arglist = [ + '--disable-reason', reason, + compute_fakes.service_host, + compute_fakes.service_binary, + ] + verifylist = [ + ('enabled', True), + ('disable_reason', reason), + ('host', compute_fakes.service_host), + ('service', compute_fakes.service_binary), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + with mock.patch.object(self.cmd.log, 'info') as mock_log: + result = self.cmd.take_action(parsed_args) + + msg = "argument --disable-reason has been ignored" + mock_log.assert_called_once_with(msg) + + self.service_mock.enable.assert_called_with( + compute_fakes.service_host, + compute_fakes.service_binary + ) + self.assertIsNone(result) + + def test_service_set_enable_with_disable_reason(self): + reason = 'earthquake' + arglist = [ + '--enable', + '--disable-reason', reason, + compute_fakes.service_host, + compute_fakes.service_binary, + ] + verifylist = [ + ('enabled', True), + ('disable_reason', reason), + ('host', compute_fakes.service_host), + ('service', compute_fakes.service_binary), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + with mock.patch.object(self.cmd.log, 'info') as mock_log: + result = self.cmd.take_action(parsed_args) + + msg = "argument --disable-reason has been ignored" + mock_log.assert_called_once_with(msg) + + self.service_mock.enable.assert_called_with( + compute_fakes.service_host, + compute_fakes.service_binary + ) + self.assertIsNone(result) From bad21594be3a38cce44742b406f35b30040193eb Mon Sep 17 00:00:00 2001 From: Cedric Brandily Date: Tue, 5 Apr 2016 17:57:55 +0200 Subject: [PATCH 0775/3095] Use fixtures and addCleanup instead of tearDown Nothing ensures tearDown call as tearDown is called only if test succeeds. This change replaces tearDown use with: * addCleanup use to stop mocks * EnvFixture which ensures to unmock environment thanks to useFixture. Change-Id: I1ff422e6a7585bc48b04b8f5c4cc1e7e9ddab1bc --- openstackclient/tests/test_shell.py | 59 ++++++++++++----------------- 1 file changed, 25 insertions(+), 34 deletions(-) diff --git a/openstackclient/tests/test_shell.py b/openstackclient/tests/test_shell.py index ea3c6fe25b..4058f1f848 100644 --- a/openstackclient/tests/test_shell.py +++ b/openstackclient/tests/test_shell.py @@ -14,6 +14,7 @@ # import copy +import fixtures import mock import os import testtools @@ -161,6 +162,23 @@ def fake_execute(shell, cmd): return shell.run(cmd.split()) +class EnvFixture(fixtures.Fixture): + """Environment Fixture. + + This fixture replaces os.environ with provided env or an empty env. + """ + + def __init__(self, env=None): + self.new_env = env or {} + + def _setUp(self): + self.orig_env, os.environ = os.environ, self.new_env + self.addCleanup(self.revert) + + def revert(self): + os.environ = self.orig_env + + class TestShell(utils.TestCase): def setUp(self): @@ -168,12 +186,9 @@ def setUp(self): patch = "openstackclient.shell.OpenStackShell.run_subcommand" self.cmd_patch = mock.patch(patch) self.cmd_save = self.cmd_patch.start() + self.addCleanup(self.cmd_patch.stop) self.app = mock.Mock("Test Shell") - def tearDown(self): - super(TestShell, self).tearDown() - self.cmd_patch.stop() - def _assert_initialize_app_arg(self, cmd_options, default_args): """Check the args passed to initialize_app() @@ -285,11 +300,7 @@ class TestShellHelp(TestShell): def setUp(self): super(TestShellHelp, self).setUp() - self.orig_env, os.environ = os.environ, {} - - def tearDown(self): - super(TestShellHelp, self).tearDown() - os.environ = self.orig_env + self.useFixture(EnvFixture()) @testtools.skip("skip until bug 1444983 is resolved") def test_help_options(self): @@ -310,11 +321,7 @@ class TestShellOptions(TestShell): def setUp(self): super(TestShellOptions, self).setUp() - self.orig_env, os.environ = os.environ, {} - - def tearDown(self): - super(TestShellOptions, self).tearDown() - os.environ = self.orig_env + self.useFixture(EnvFixture()) def _test_options_init_app(self, test_opts): for opt in test_opts.keys(): @@ -402,11 +409,7 @@ def setUp(self): "OS_TOKEN": DEFAULT_TOKEN, "OS_AUTH_URL": DEFAULT_AUTH_URL, } - self.orig_env, os.environ = os.environ, env.copy() - - def tearDown(self): - super(TestShellTokenAuthEnv, self).tearDown() - os.environ = self.orig_env + self.useFixture(EnvFixture(env.copy())) def test_env(self): flag = "" @@ -450,11 +453,7 @@ def setUp(self): "OS_TOKEN": DEFAULT_TOKEN, "OS_URL": DEFAULT_SERVICE_URL, } - self.orig_env, os.environ = os.environ, env.copy() - - def tearDown(self): - super(TestShellTokenEndpointAuthEnv, self).tearDown() - os.environ = self.orig_env + self.useFixture(EnvFixture(env.copy())) def test_env(self): flag = "" @@ -501,11 +500,7 @@ def setUp(self): "OS_VOLUME_API_VERSION": DEFAULT_VOLUME_API_VERSION, "OS_NETWORK_API_VERSION": DEFAULT_NETWORK_API_VERSION, } - self.orig_env, os.environ = os.environ, env.copy() - - def tearDown(self): - super(TestShellCli, self).tearDown() - os.environ = self.orig_env + self.useFixture(EnvFixture(env.copy())) def test_shell_args_no_options(self): _shell = make_shell() @@ -719,11 +714,7 @@ def setUp(self): env = { 'OS_REGION_NAME': 'occ-env', } - self.orig_env, os.environ = os.environ, env.copy() - - def tearDown(self): - super(TestShellCliEnv, self).tearDown() - os.environ = self.orig_env + self.useFixture(EnvFixture(env.copy())) @mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file") @mock.patch("os_client_config.config.OpenStackConfig._load_config_file") From 139a45bb71ca2e99b0c8947c43c8d0165dec165e Mon Sep 17 00:00:00 2001 From: Cedric Brandily Date: Wed, 6 Apr 2016 00:35:57 +0200 Subject: [PATCH 0776/3095] Remove unused method cleanup_tmpfile This change removes unused cleanup_tmpfile method from functests code. Change-Id: I5d8e7edb0e50e94a5a469ce393d411b390b4db34 --- functional/common/test.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/functional/common/test.py b/functional/common/test.py index 1e767af886..810c4e43fe 100644 --- a/functional/common/test.py +++ b/functional/common/test.py @@ -76,13 +76,6 @@ def assertInOutput(cls, expected, actual): if expected not in actual: raise Exception(expected + ' not in ' + actual) - @classmethod - def cleanup_tmpfile(cls, filename): - try: - os.remove(filename) - except OSError: - pass - def assert_table_structure(self, items, field_names): """Verify that all items have keys listed in field_names.""" for item in items: From 3c82c0e62e5b661d03ad7d9a3e93f95876805c2d Mon Sep 17 00:00:00 2001 From: Cedric Brandily Date: Wed, 6 Apr 2016 00:37:16 +0200 Subject: [PATCH 0777/3095] Improve tmpfile cleanup in functests This change replaces when possible homemade temporary file management by tempfile.NamedTemporaryFile[1][2] and defines only when needed a cleanup for a temporary file[2]. [1] functional/tests/compute/v2/test_keypair.py [2] functional/tests/object/v1/test_object.py Change-Id: I728ab96381ca9f3fd1f899dd50e5ceb5e97b9397 --- functional/tests/compute/v2/test_keypair.py | 23 ++++++++-------- functional/tests/object/v1/test_object.py | 30 ++++++++++----------- 2 files changed, 26 insertions(+), 27 deletions(-) diff --git a/functional/tests/compute/v2/test_keypair.py b/functional/tests/compute/v2/test_keypair.py index c9e8f3bcf5..d6c5ad2891 100644 --- a/functional/tests/compute/v2/test_keypair.py +++ b/functional/tests/compute/v2/test_keypair.py @@ -10,7 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. -import os +import tempfile import uuid from functional.common import test @@ -44,19 +44,18 @@ def tearDownClass(cls): cls.assertOutput('', raw_output) def test_keypair_create(self): - TMP_FILE = uuid.uuid4().hex - self.addCleanup(os.remove, TMP_FILE) - with open(TMP_FILE, 'w') as f: + with tempfile.NamedTemporaryFile() as f: f.write(PUBLIC_KEY) + f.flush() - raw_output = self.openstack( - 'keypair create --public-key ' + TMP_FILE + ' tmpkey', - ) - self.addCleanup( - self.openstack, - 'keypair delete tmpkey', - ) - self.assertIn('tmpkey', raw_output) + raw_output = self.openstack( + 'keypair create --public-key %s tmpkey' % f.name, + ) + self.addCleanup( + self.openstack, + 'keypair delete tmpkey', + ) + self.assertIn('tmpkey', raw_output) def test_keypair_list(self): opts = self.get_list_opts(self.HEADERS) diff --git a/functional/tests/object/v1/test_object.py b/functional/tests/object/v1/test_object.py index cd98012cb6..8ea16da785 100644 --- a/functional/tests/object/v1/test_object.py +++ b/functional/tests/object/v1/test_object.py @@ -11,6 +11,7 @@ # under the License. import os +import tempfile import uuid from functional.common import test @@ -24,17 +25,14 @@ class ObjectTests(test.TestCase): """Functional tests for Object commands. """ CONTAINER_NAME = uuid.uuid4().hex - OBJECT_NAME = uuid.uuid4().hex - TMP_FILE = 'tmp.txt' - - def setUp(self): - super(ObjectTests, self).setUp() - self.addCleanup(os.remove, self.OBJECT_NAME) - self.addCleanup(os.remove, self.TMP_FILE) - with open(self.OBJECT_NAME, 'w') as f: - f.write('test content') def test_object(self): + with tempfile.NamedTemporaryFile() as f: + f.write('test content') + f.flush() + self._test_object(f.name) + + def _test_object(self, object_file): raw_output = self.openstack('container create ' + self.CONTAINER_NAME) items = self.parse_listing(raw_output) self.assert_show_fields(items, CONTAINER_FIELDS) @@ -50,7 +48,7 @@ def test_object(self): # TODO(stevemar): Assert returned fields raw_output = self.openstack('object create ' + self.CONTAINER_NAME - + ' ' + self.OBJECT_NAME) + + ' ' + object_file) items = self.parse_listing(raw_output) self.assert_show_fields(items, OBJECT_FIELDS) @@ -59,23 +57,25 @@ def test_object(self): self.assert_table_structure(items, BASIC_LIST_HEADERS) self.openstack('object save ' + self.CONTAINER_NAME - + ' ' + self.OBJECT_NAME) + + ' ' + object_file) # TODO(stevemar): Assert returned fields + tmp_file = 'tmp.txt' + self.addCleanup(os.remove, tmp_file) self.openstack('object save ' + self.CONTAINER_NAME - + ' ' + self.OBJECT_NAME + ' --file ' + self.TMP_FILE) + + ' ' + object_file + ' --file ' + tmp_file) # TODO(stevemar): Assert returned fields self.openstack('object show ' + self.CONTAINER_NAME - + ' ' + self.OBJECT_NAME) + + ' ' + object_file) # TODO(stevemar): Assert returned fields raw_output = self.openstack('object delete ' + self.CONTAINER_NAME - + ' ' + self.OBJECT_NAME) + + ' ' + object_file) self.assertEqual(0, len(raw_output)) self.openstack('object create ' + self.CONTAINER_NAME - + ' ' + self.OBJECT_NAME) + + ' ' + object_file) raw_output = self.openstack('container delete -r ' + self.CONTAINER_NAME) self.assertEqual(0, len(raw_output)) From 67a8947ea233584b41ee5dfb874a88fc43bcf4e9 Mon Sep 17 00:00:00 2001 From: Dao Cong Tien Date: Wed, 6 Apr 2016 10:27:28 +0700 Subject: [PATCH 0778/3095] Fix typos in docstrings and comments Change-Id: Ic2dc057dca87212f715970f8325956c42f62ea9f --- openstackclient/api/api.py | 2 +- openstackclient/api/utils.py | 2 +- openstackclient/network/v2/subnet.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openstackclient/api/api.py b/openstackclient/api/api.py index 6a88e7f7d6..bd2abd8818 100644 --- a/openstackclient/api/api.py +++ b/openstackclient/api/api.py @@ -331,7 +331,7 @@ def find( :param string path: The API-specific portion of the URL path - :param string search: + :param string value: search expression :param string attr: name of attribute for secondary search diff --git a/openstackclient/api/utils.py b/openstackclient/api/utils.py index ab0e23c29d..6407cd4457 100644 --- a/openstackclient/api/utils.py +++ b/openstackclient/api/utils.py @@ -29,7 +29,7 @@ def simple_filter( The name of the attribute to filter. If attr does not exist no match will succeed and no rows will be returned. If attr is None no filtering will be performed and all rows will be returned. - :param sring value: + :param string value: The value to filter. None is considered to be a 'no filter' value. '' matches against a Python empty string. :param string property_field: diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index da4f6536ec..10e5859a5f 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -95,7 +95,7 @@ def convert_entries_to_nexthop(entries): def convert_entries_to_gateway(entries): - # Change 'nexhop' entry to 'gateway' + # Change 'nexthop' entry to 'gateway' changed_entries = copy.deepcopy(entries) for entry in changed_entries: entry['gateway'] = entry['nexthop'] From 107bc5164f99ef956d952273235648c7f8f26764 Mon Sep 17 00:00:00 2001 From: reedip Date: Mon, 14 Mar 2016 15:35:46 +0900 Subject: [PATCH 0779/3095] Add external network options to osc network create The following patch adds the options "--external" & "--internal" and the suboptions to "external": "--default" & "--no-default", to "osc network create" CLI to provide the user an option to create a network as an external network. Change-Id: Idf73714bb94c0610ea164131140a51848908b00b Partial-Bug: #1545537 --- doc/source/command-objects/network.rst | 24 +++++++++++++++ openstackclient/network/v2/network.py | 29 +++++++++++++++++++ openstackclient/tests/network/v2/fakes.py | 3 +- .../tests/network/v2/test_network.py | 15 ++++++++++ ...add-external-options-7a66219d263bb1e5.yaml | 9 ++++++ 5 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/add-external-options-7a66219d263bb1e5.yaml diff --git a/doc/source/command-objects/network.rst b/doc/source/command-objects/network.rst index 9e109279f6..e0a649c15d 100644 --- a/doc/source/command-objects/network.rst +++ b/doc/source/command-objects/network.rst @@ -17,6 +17,7 @@ Create new network [--enable | --disable] [--share | --no-share] [--availability-zone-hint ] + [--external [--default | --no-default] | --internal] .. option:: --project @@ -59,6 +60,29 @@ Create new network IPv4 subnet for fixed IPs (in CIDR notation) (Compute v2 network only) +.. option:: --external + + Set this network as an external network. + Requires the "external-net" extension to be enabled. + (Network v2 only) + +.. option:: --internal + + Set this network as an internal network (default) + (Network v2 only) + +.. option:: --default + + Specify if this network should be used as + the default external network + (Network v2 only) + +.. option:: --no-default + + Do not use the network as the default external network. + By default, no network is set as an external network. + (Network v2 only) + .. _network_create-name: .. describe:: diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 42648e4558..6fd18efb4b 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -144,6 +144,27 @@ def update_parser_network(self, parser): '(requires the Network Availability Zone extension, ' 'this option can be repeated).', ) + external_router_grp = parser.add_mutually_exclusive_group() + external_router_grp.add_argument( + '--external', + action='store_true', + help='Set this network as an external network. ' + 'Requires the "external-net" extension to be enabled.') + external_router_grp.add_argument( + '--internal', + action='store_true', + help='Set this network as an internal network (default)') + default_router_grp = parser.add_mutually_exclusive_group() + default_router_grp.add_argument( + '--default', + action='store_true', + help='Specify if this network should be used as ' + 'the default external network') + default_router_grp.add_argument( + '--no-default', + action='store_true', + help='Do not use the network as the default external network.' + 'By default, no network is set as an external network.') return parser def update_parser_compute(self, parser): @@ -156,6 +177,14 @@ def update_parser_compute(self, parser): def take_action_network(self, client, parsed_args): attrs = _get_attrs(self.app.client_manager, parsed_args) + if parsed_args.internal: + attrs['router:external'] = False + if parsed_args.external: + attrs['router:external'] = True + if parsed_args.no_default: + attrs['is_default'] = False + if parsed_args.default: + attrs['is_default'] = True obj = client.create_network(**attrs) columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index e35fbe165c..7f89ef7a97 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -152,6 +152,7 @@ def create_one_network(attrs={}, methods={}): 'router_external': True, 'availability_zones': [], 'availability_zone_hints': [], + 'is_default': False, } # Overwrite default attributes. @@ -161,7 +162,7 @@ def create_one_network(attrs={}, methods={}): network_methods = { 'keys': ['id', 'name', 'admin_state_up', 'router_external', 'status', 'subnets', 'tenant_id', 'availability_zones', - 'availability_zone_hints'], + 'availability_zone_hints', 'is_default'], } # Overwrite default methods. diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index 26b98f7727..572bc6aee4 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -51,6 +51,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): 'availability_zone_hints', 'availability_zones', 'id', + 'is_default', 'name', 'project_id', 'router_external', @@ -63,6 +64,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): utils.format_list(_network.availability_zone_hints), utils.format_list(_network.availability_zones), _network.id, + _network.is_default, _network.name, _network.project_id, network._format_router_external(_network.router_external), @@ -119,6 +121,7 @@ def test_create_default_options(self): ('enable', True), ('share', None), ('project', None), + ('external', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -138,6 +141,7 @@ def test_create_all_options(self): "--project", identity_fakes_v3.project_name, "--project-domain", identity_fakes_v3.domain_name, "--availability-zone-hint", "nova", + "--external", "--default", self._network.name, ] verifylist = [ @@ -146,6 +150,8 @@ def test_create_all_options(self): ('project', identity_fakes_v3.project_name), ('project_domain', identity_fakes_v3.domain_name), ('availability_zone_hints', ["nova"]), + ('external', True), + ('default', True), ('name', self._network.name), ] @@ -158,6 +164,8 @@ def test_create_all_options(self): 'name': self._network.name, 'shared': True, 'tenant_id': identity_fakes_v3.project_id, + 'is_default': True, + 'router:external': True, }) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -172,6 +180,7 @@ def test_create_other_options(self): ('enable', True), ('no_share', True), ('name', self._network.name), + ('external', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -198,6 +207,7 @@ class TestCreateNetworkIdentityV2(TestNetwork): 'availability_zone_hints', 'availability_zones', 'id', + 'is_default', 'name', 'project_id', 'router_external', @@ -210,6 +220,7 @@ class TestCreateNetworkIdentityV2(TestNetwork): utils.format_list(_network.availability_zone_hints), utils.format_list(_network.availability_zones), _network.id, + _network.is_default, _network.name, _network.project_id, network._format_router_external(_network.router_external), @@ -253,6 +264,7 @@ def test_create_with_project_identityv2(self): ('share', None), ('name', self._network.name), ('project', identity_fakes_v2.project_name), + ('external', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -278,6 +290,7 @@ def test_create_with_domain_identityv2(self): ('project', identity_fakes_v3.project_name), ('project_domain', identity_fakes_v3.domain_name), ('name', self._network.name), + ('external', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -514,6 +527,7 @@ class TestShowNetwork(TestNetwork): 'availability_zone_hints', 'availability_zones', 'id', + 'is_default', 'name', 'project_id', 'router_external', @@ -526,6 +540,7 @@ class TestShowNetwork(TestNetwork): utils.format_list(_network.availability_zone_hints), utils.format_list(_network.availability_zones), _network.id, + _network.is_default, _network.name, _network.project_id, network._format_router_external(_network.router_external), diff --git a/releasenotes/notes/add-external-options-7a66219d263bb1e5.yaml b/releasenotes/notes/add-external-options-7a66219d263bb1e5.yaml new file mode 100644 index 0000000000..7b8eaff00f --- /dev/null +++ b/releasenotes/notes/add-external-options-7a66219d263bb1e5.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + New options have been added to the ``network create`` command + to support external network functionality. The new options are + ``--external/--internal`` and suboptions to ``external``: + ``--default/--no-default``. + These options are available for Networkv2 only. + [Bug `1545537 `_] From 1b351e16956563916cdf4f27071653e4b69569c2 Mon Sep 17 00:00:00 2001 From: reedip Date: Mon, 4 Apr 2016 11:17:29 +0900 Subject: [PATCH 0780/3095] Add provider network options to osc network create The following patch adds the provider network options to OSC "network create". Change-Id: Ib8449c00ee4b4285889588f03ddd7a686ce8f987 Partial-Bug: #1545537 --- doc/source/command-objects/network.rst | 19 ++++++++++++++ openstackclient/network/v2/network.py | 26 +++++++++++++++++++ .../tests/network/v2/test_network.py | 9 +++++++ ...add-provider-options-12bbf01d2280dd2f.yaml | 9 +++++++ 4 files changed, 63 insertions(+) create mode 100644 releasenotes/notes/add-provider-options-12bbf01d2280dd2f.yaml diff --git a/doc/source/command-objects/network.rst b/doc/source/command-objects/network.rst index e0a649c15d..9fbc02164c 100644 --- a/doc/source/command-objects/network.rst +++ b/doc/source/command-objects/network.rst @@ -18,6 +18,9 @@ Create new network [--share | --no-share] [--availability-zone-hint ] [--external [--default | --no-default] | --internal] + [--provider-network-type ] + [--provider-physical-network ] + [--provider-segmentation-id ] .. option:: --project @@ -83,6 +86,22 @@ Create new network By default, no network is set as an external network. (Network v2 only) +.. option:: --provider-network-type + + The physical mechanism by which the virtual network is implemented. + The supported options are: flat, gre, local, vlan, vxlan + (Network v2 only) + +.. option:: --provider-physical-network + + Name of the physical network over which the virtual network is implemented + (Network v2 only) + +.. option:: --provider-segmentation-id + + VLAN ID for VLAN networks or tunnel-id for GRE/VXLAN networks + (Network v2 only) + .. _network_create-name: .. describe:: diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 6fd18efb4b..d3b20c0f3e 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -165,6 +165,26 @@ def update_parser_network(self, parser): action='store_true', help='Do not use the network as the default external network.' 'By default, no network is set as an external network.') + parser.add_argument( + '--provider-network-type', + metavar='', + choices=['flat', 'gre', 'local', + 'vlan', 'vxlan'], + help='The physical mechanism by which the virtual network ' + 'is implemented. The supported options are: ' + 'flat, gre, local, vlan, vxlan') + parser.add_argument( + '--provider-physical-network', + metavar='', + dest='physical_network', + help='Name of the physical network over which the virtual ' + 'network is implemented') + parser.add_argument( + '--provider-segmentation-id', + metavar='', + dest='segmentation_id', + help='VLAN ID for VLAN networks or tunnel-id for GRE/VXLAN ' + 'networks') return parser def update_parser_compute(self, parser): @@ -185,6 +205,12 @@ def take_action_network(self, client, parsed_args): attrs['is_default'] = False if parsed_args.default: attrs['is_default'] = True + if parsed_args.provider_network_type: + attrs['provider:network_type'] = parsed_args.provider_network_type + if parsed_args.physical_network: + attrs['provider:physical_network'] = parsed_args.physical_network + if parsed_args.segmentation_id: + attrs['provider:segmentation_id'] = parsed_args.segmentation_id obj = client.create_network(**attrs) columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index 572bc6aee4..2493362e29 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -142,6 +142,9 @@ def test_create_all_options(self): "--project-domain", identity_fakes_v3.domain_name, "--availability-zone-hint", "nova", "--external", "--default", + "--provider-network-type", "vlan", + "--provider-physical-network", "physnet1", + "--provider-segmentation-id", "400", self._network.name, ] verifylist = [ @@ -152,6 +155,9 @@ def test_create_all_options(self): ('availability_zone_hints', ["nova"]), ('external', True), ('default', True), + ('provider_network_type', 'vlan'), + ('physical_network', 'physnet1'), + ('segmentation_id', '400'), ('name', self._network.name), ] @@ -166,6 +172,9 @@ def test_create_all_options(self): 'tenant_id': identity_fakes_v3.project_id, 'is_default': True, 'router:external': True, + 'provider:network_type': 'vlan', + 'provider:physical_network': 'physnet1', + 'provider:segmentation_id': '400', }) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) diff --git a/releasenotes/notes/add-provider-options-12bbf01d2280dd2f.yaml b/releasenotes/notes/add-provider-options-12bbf01d2280dd2f.yaml new file mode 100644 index 0000000000..68de08190f --- /dev/null +++ b/releasenotes/notes/add-provider-options-12bbf01d2280dd2f.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + New options have been added to the ``network create`` command + to support provider network functionality. + These options are ``--provider-network-type``, ``--provider-physical-network``, + and ``--provider-segmentation-id``. + These options are available for Networkv2 only + [Bug `1545537 `_] From 3a8320a1d73444b3bb823300e94c3e2ee85fd6ef Mon Sep 17 00:00:00 2001 From: Cedric Brandily Date: Fri, 1 Apr 2016 23:42:27 +0200 Subject: [PATCH 0781/3095] Support client certificate/key This change enables to specify a client certificate/key with: * usual CLI options (--os-cert/--os-key) * usual environment variables ($OS_CERT/$OS_KEY) * os-client-config Change-Id: Ibeaaa5897ae37b37c1e91f3e47076e4e8e4a8ded Closes-Bug: #1565112 --- doc/source/man/openstack.rst | 12 ++++++++++ openstackclient/common/clientmanager.py | 10 ++++++++ openstackclient/shell.py | 12 ++++++++++ .../tests/common/test_clientmanager.py | 17 ++++++++++++++ openstackclient/tests/test_shell.py | 23 +++++++++++++++++++ .../notes/bug_1565112-e0cea9bfbcab954f.yaml | 6 +++++ 6 files changed, 80 insertions(+) create mode 100644 releasenotes/notes/bug_1565112-e0cea9bfbcab954f.yaml diff --git a/doc/source/man/openstack.rst b/doc/source/man/openstack.rst index f02705d817..a4be351ae0 100644 --- a/doc/source/man/openstack.rst +++ b/doc/source/man/openstack.rst @@ -114,6 +114,12 @@ OPTIONS :option:`--verify` | :option:`--insecure` Verify or ignore server certificate (default: verify) +:option:`--os-cert` + Client certificate bundle file + +:option:`--os-key` + Client certificate key file + :option:`--os-identity-api-version` Identity API version (Default: 2.0) @@ -367,6 +373,12 @@ The following environment variables can be set to alter the behaviour of :progra :envvar:`OS_CACERT` CA certificate bundle file +:envvar:`OS_CERT` + Client certificate bundle file + +:envvar:`OS_KEY` + Client certificate key file + :envvar:`OS_IDENTITY_API_VERSION` Identity API version (Default: 2.0) diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index 56ddcbad77..6d23b55e64 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -110,6 +110,15 @@ def __init__( self._cacert = verify self._insecure = False + # Set up client certificate and key + # NOTE(cbrandily): This converts client certificate/key to requests + # cert argument: None (no client certificate), a path + # to client certificate or a tuple with client + # certificate/key paths. + self._cert = self._cli_options.cert + if self._cert and self._cli_options.key: + self._cert = self._cert, self._cli_options.key + # Get logging from root logger root_logger = logging.getLogger('') LOG.setLevel(root_logger.getEffectiveLevel()) @@ -194,6 +203,7 @@ def setup_auth(self, required_scope=True): auth=self.auth, session=request_session, verify=self._verify, + cert=self._cert, user_agent=USER_AGENT, ) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 7750f2a391..b7bc7b1a44 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -189,6 +189,18 @@ def build_option_parser(self, description, version): dest='cacert', default=utils.env('OS_CACERT'), help='CA certificate bundle file (Env: OS_CACERT)') + parser.add_argument( + '--os-cert', + metavar='', + dest='cert', + default=utils.env('OS_CERT'), + help='Client certificate bundle file (Env: OS_CERT)') + parser.add_argument( + '--os-key', + metavar='', + dest='key', + default=utils.env('OS_KEY'), + help='Client certificate key file (Env: OS_KEY)') verify_group = parser.add_mutually_exclusive_group() verify_group.add_argument( '--verify', diff --git a/openstackclient/tests/common/test_clientmanager.py b/openstackclient/tests/common/test_clientmanager.py index 2bd9e7836b..6fc5b41e69 100644 --- a/openstackclient/tests/common/test_clientmanager.py +++ b/openstackclient/tests/common/test_clientmanager.py @@ -58,6 +58,8 @@ def __init__(self, **kwargs): self.interface = None self.url = None self.auth = {} + self.cert = None + self.key = None self.default_domain = 'default' self.__dict__.update(kwargs) @@ -268,6 +270,21 @@ def test_client_manager_password_verify_ca(self): self.assertEqual('cafile', client_manager._cacert) self.assertTrue(client_manager.is_network_endpoint_enabled()) + def test_client_manager_password_no_cert(self): + client_manager = clientmanager.ClientManager( + cli_options=FakeOptions()) + self.assertIsNone(client_manager._cert) + + def test_client_manager_password_client_cert(self): + client_manager = clientmanager.ClientManager( + cli_options=FakeOptions(cert='cert')) + self.assertEqual('cert', client_manager._cert) + + def test_client_manager_password_client_cert_and_key(self): + client_manager = clientmanager.ClientManager( + cli_options=FakeOptions(cert='cert', key='key')) + self.assertEqual(('cert', 'key'), client_manager._cert) + def _select_auth_plugin(self, auth_params, api_version, auth_plugin_name): auth_params['auth_type'] = auth_plugin_name auth_params['identity_api_version'] = api_version diff --git a/openstackclient/tests/test_shell.py b/openstackclient/tests/test_shell.py index ea3c6fe25b..c134cb9351 100644 --- a/openstackclient/tests/test_shell.py +++ b/openstackclient/tests/test_shell.py @@ -79,6 +79,8 @@ 'region_name': 'occ-cloud,krikkit,occ-env', 'log_file': '/tmp/test_log_file', 'log_level': 'debug', + 'cert': 'mycert', + 'key': 'mickey', } } } @@ -567,6 +569,24 @@ def test_shell_args_ca_options(self): self.assertEqual('foo', _shell.options.cacert) self.assertFalse(_shell.verify) + def test_shell_args_cert_options(self): + _shell = make_shell() + + # Default + fake_execute(_shell, "list user") + self.assertEqual('', _shell.options.cert) + self.assertEqual('', _shell.options.key) + + # --os-cert + fake_execute(_shell, "--os-cert mycert list user") + self.assertEqual('mycert', _shell.options.cert) + self.assertEqual('', _shell.options.key) + + # --os-key + fake_execute(_shell, "--os-key mickey list user") + self.assertEqual('', _shell.options.cert) + self.assertEqual('mickey', _shell.options.key) + def test_default_env(self): flag = "" kwargs = { @@ -670,6 +690,9 @@ def test_shell_args_cloud_public(self, config_mock, public_mock): _shell.cloud.config['region_name'], ) + self.assertEqual('mycert', _shell.cloud.config['cert']) + self.assertEqual('mickey', _shell.cloud.config['key']) + @mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file") @mock.patch("os_client_config.config.OpenStackConfig._load_config_file") def test_shell_args_precedence(self, config_mock, vendor_mock): diff --git a/releasenotes/notes/bug_1565112-e0cea9bfbcab954f.yaml b/releasenotes/notes/bug_1565112-e0cea9bfbcab954f.yaml new file mode 100644 index 0000000000..864b0ac855 --- /dev/null +++ b/releasenotes/notes/bug_1565112-e0cea9bfbcab954f.yaml @@ -0,0 +1,6 @@ +--- +features: + - Support client certificate/key. Client certificate/key can be provided + using --os-cert/--os-key options, $OS_CERT/$OS_KEY environment + variables or os-client-config config. + [Bug `1565112 `_] From e2e9c49cd920947eec1f873e74a98ec5ab6834fd Mon Sep 17 00:00:00 2001 From: Cedric Brandily Date: Wed, 6 Apr 2016 00:54:31 +0200 Subject: [PATCH 0782/3095] Correct addCleanup use in functests This change replaces in many identity functests the pattern: raw_resource = create_resource(...) check(raw_resource) self.addCleanup(delete_resource, ...) ... by the pattern: raw_resource = create_resource(...) self.addCleanup(delete_resource, ...) check(raw_resource) ... which ensures that cleanup is defined and called after the test even if check(resource) fails. Change-Id: I4da541b7552d06eaffafda446e389bb552422cda --- functional/tests/identity/v2/test_identity.py | 30 ++++++------- functional/tests/identity/v2/test_role.py | 31 ++++++------- functional/tests/identity/v3/test_domain.py | 4 +- functional/tests/identity/v3/test_group.py | 44 +++++++++---------- functional/tests/identity/v3/test_identity.py | 34 +++++++------- functional/tests/identity/v3/test_role.py | 34 +++++++------- 6 files changed, 89 insertions(+), 88 deletions(-) diff --git a/functional/tests/identity/v2/test_identity.py b/functional/tests/identity/v2/test_identity.py index 91a77b6785..4346499ce6 100644 --- a/functional/tests/identity/v2/test_identity.py +++ b/functional/tests/identity/v2/test_identity.py @@ -67,13 +67,13 @@ def _create_dummy_project(self, add_clean_up=True): '--description %(description)s ' '--enable %(name)s' % {'description': project_description, 'name': project_name}) - items = self.parse_show(raw_output) - self.assert_show_fields(items, self.PROJECT_FIELDS) project = self.parse_show_as_object(raw_output) if add_clean_up: self.addCleanup( self.openstack, 'project delete %s' % project['id']) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.PROJECT_FIELDS) return project_name def _create_dummy_user(self, add_clean_up=True): @@ -90,47 +90,47 @@ def _create_dummy_user(self, add_clean_up=True): 'email': email, 'password': password, 'name': username}) - items = self.parse_show(raw_output) - self.assert_show_fields(items, self.USER_FIELDS) if add_clean_up: self.addCleanup( self.openstack, 'user delete %s' % self.parse_show_as_object(raw_output)['id']) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.USER_FIELDS) return username def _create_dummy_role(self, add_clean_up=True): role_name = data_utils.rand_name('TestRole') raw_output = self.openstack('role create %s' % role_name) - items = self.parse_show(raw_output) - self.assert_show_fields(items, self.ROLE_FIELDS) role = self.parse_show_as_object(raw_output) - self.assertEqual(role_name, role['name']) if add_clean_up: self.addCleanup( self.openstack, 'role delete %s' % role['id']) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.ROLE_FIELDS) + self.assertEqual(role_name, role['name']) return role_name def _create_dummy_ec2_credentials(self, add_clean_up=True): raw_output = self.openstack('ec2 credentials create') - items = self.parse_show(raw_output) - self.assert_show_fields(items, self.EC2_CREDENTIALS_FIELDS) ec2_credentials = self.parse_show_as_object(raw_output) access_key = ec2_credentials['access'] if add_clean_up: self.addCleanup( self.openstack, 'ec2 credentials delete %s' % access_key) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.EC2_CREDENTIALS_FIELDS) return access_key def _create_dummy_token(self, add_clean_up=True): raw_output = self.openstack('token issue') - items = self.parse_show(raw_output) - self.assert_show_fields(items, self.TOKEN_FIELDS) token = self.parse_show_as_object(raw_output) if add_clean_up: self.addCleanup(self.openstack, 'token revoke %s' % token['id']) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.TOKEN_FIELDS) return token['id'] def _create_dummy_service(self, add_clean_up=True): @@ -144,12 +144,12 @@ def _create_dummy_service(self, add_clean_up=True): '%(type)s' % {'name': service_name, 'description': description, 'type': type_name}) - items = self.parse_show(raw_output) - self.assert_show_fields(items, self.SERVICE_FIELDS) if add_clean_up: service = self.parse_show_as_object(raw_output) self.addCleanup(self.openstack, 'service delete %s' % service['id']) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.SERVICE_FIELDS) return service_name def _create_dummy_endpoint(self, add_clean_up=True): @@ -169,11 +169,11 @@ def _create_dummy_endpoint(self, add_clean_up=True): 'internalurl': internal_url, 'region': region_id, 'service': service_name}) - items = self.parse_show(raw_output) - self.assert_show_fields(items, self.ENDPOINT_FIELDS) endpoint = self.parse_show_as_object(raw_output) if add_clean_up: self.addCleanup( self.openstack, 'endpoint delete %s' % endpoint['id']) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.ENDPOINT_FIELDS) return endpoint['id'] diff --git a/functional/tests/identity/v2/test_role.py b/functional/tests/identity/v2/test_role.py index e542a5fbdd..9ee60069bc 100644 --- a/functional/tests/identity/v2/test_role.py +++ b/functional/tests/identity/v2/test_role.py @@ -40,8 +40,17 @@ def test_role_list_with_user_project(self): '%(role)s' % {'project': project_name, 'user': username, 'role': role_name}) + self.addCleanup( + self.openstack, + 'role remove ' + '--project %(project)s ' + '--user %(user)s ' + '%(role)s' % {'project': project_name, + 'user': username, + 'role': role_name}) items = self.parse_show(raw_output) self.assert_show_fields(items, self.ROLE_FIELDS) + raw_output = self.openstack( 'role list ' '--project %(project)s ' @@ -51,14 +60,6 @@ def test_role_list_with_user_project(self): items = self.parse_listing(raw_output) self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS) self.assertEqual(1, len(items)) - self.addCleanup( - self.openstack, - 'role remove ' - '--project %(project)s ' - '--user %(user)s ' - '%(role)s' % {'project': project_name, - 'user': username, - 'role': role_name}) def test_role_show(self): role_name = self._create_dummy_role() @@ -76,8 +77,6 @@ def test_role_add(self): '%(role)s' % {'project': self.project_name, 'user': username, 'role': role_name}) - items = self.parse_show(raw_output) - self.assert_show_fields(items, self.ROLE_FIELDS) self.addCleanup( self.openstack, 'role remove ' @@ -86,24 +85,26 @@ def test_role_add(self): '%(role)s' % {'project': self.project_name, 'user': username, 'role': role_name}) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.ROLE_FIELDS) def test_role_remove(self): role_name = self._create_dummy_role() username = self._create_dummy_user() - raw_output = self.openstack( + add_raw_output = self.openstack( 'role add ' '--project %(project)s ' '--user %(user)s ' '%(role)s' % {'project': self.project_name, 'user': username, 'role': role_name}) - items = self.parse_show(raw_output) - self.assert_show_fields(items, self.ROLE_FIELDS) - raw_output = self.openstack( + del_raw_output = self.openstack( 'role remove ' '--project %(project)s ' '--user %(user)s ' '%(role)s' % {'project': self.project_name, 'user': username, 'role': role_name}) - self.assertEqual(0, len(raw_output)) + items = self.parse_show(add_raw_output) + self.assert_show_fields(items, self.ROLE_FIELDS) + self.assertEqual(0, len(del_raw_output)) diff --git a/functional/tests/identity/v3/test_domain.py b/functional/tests/identity/v3/test_domain.py index f3ae4e890b..a60028b456 100644 --- a/functional/tests/identity/v3/test_domain.py +++ b/functional/tests/identity/v3/test_domain.py @@ -21,13 +21,13 @@ class DomainTests(test_identity.IdentityTests): def test_domain_create(self): domain_name = data_utils.rand_name('TestDomain') raw_output = self.openstack('domain create %s' % domain_name) - items = self.parse_show(raw_output) - self.assert_show_fields(items, self.DOMAIN_FIELDS) # disable domain first before deleting it self.addCleanup(self.openstack, 'domain delete %s' % domain_name) self.addCleanup(self.openstack, 'domain set --disable %s' % domain_name) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.DOMAIN_FIELDS) def test_domain_list(self): self._create_dummy_domain() diff --git a/functional/tests/identity/v3/test_group.py b/functional/tests/identity/v3/test_group.py index 8e39cd5daa..4c6ed19b2d 100644 --- a/functional/tests/identity/v3/test_group.py +++ b/functional/tests/identity/v3/test_group.py @@ -93,11 +93,6 @@ def test_group_add_user(self): 'user_domain': self.domain_name, 'group': group_name, 'user': username}) - self.assertOutput( - '%(user)s added to group %(group)s\n' % {'user': username, - 'group': group_name}, - raw_output - ) self.addCleanup( self.openstack, 'group remove user ' @@ -107,6 +102,11 @@ def test_group_add_user(self): 'user_domain': self.domain_name, 'group': group_name, 'user': username}) + self.assertOutput( + '%(user)s added to group %(group)s\n' % {'user': username, + 'group': group_name}, + raw_output + ) def test_group_contains_user(self): group_name = self._create_dummy_group() @@ -119,6 +119,15 @@ def test_group_contains_user(self): 'user_domain': self.domain_name, 'group': group_name, 'user': username}) + self.addCleanup( + self.openstack, + 'group remove user ' + '--group-domain %(group_domain)s ' + '--user-domain %(user_domain)s ' + '%(group)s %(user)s' % {'group_domain': self.domain_name, + 'user_domain': self.domain_name, + 'group': group_name, + 'user': username}) self.assertOutput( '%(user)s added to group %(group)s\n' % {'user': username, 'group': group_name}, @@ -136,20 +145,11 @@ def test_group_contains_user(self): '%(user)s in group %(group)s\n' % {'user': username, 'group': group_name}, raw_output) - self.addCleanup( - self.openstack, - 'group remove user ' - '--group-domain %(group_domain)s ' - '--user-domain %(user_domain)s ' - '%(group)s %(user)s' % {'group_domain': self.domain_name, - 'user_domain': self.domain_name, - 'group': group_name, - 'user': username}) def test_group_remove_user(self): group_name = self._create_dummy_group() username = self._create_dummy_user() - raw_output = self.openstack( + add_raw_output = self.openstack( 'group add user ' '--group-domain %(group_domain)s ' '--user-domain %(user_domain)s ' @@ -157,12 +157,7 @@ def test_group_remove_user(self): 'user_domain': self.domain_name, 'group': group_name, 'user': username}) - self.assertOutput( - '%(user)s added to group %(group)s\n' % {'user': username, - 'group': group_name}, - raw_output - ) - raw_output = self.openstack( + remove_raw_output = self.openstack( 'group remove user ' '--group-domain %(group_domain)s ' '--user-domain %(user_domain)s ' @@ -170,9 +165,14 @@ def test_group_remove_user(self): 'user_domain': self.domain_name, 'group': group_name, 'user': username}) + self.assertOutput( + '%(user)s added to group %(group)s\n' % {'user': username, + 'group': group_name}, + add_raw_output + ) self.assertOutput( '%(user)s removed from ' 'group %(group)s\n' % {'user': username, 'group': group_name}, - raw_output + remove_raw_output ) diff --git a/functional/tests/identity/v3/test_identity.py b/functional/tests/identity/v3/test_identity.py index d0eec6f744..b8c652abc6 100644 --- a/functional/tests/identity/v3/test_identity.py +++ b/functional/tests/identity/v3/test_identity.py @@ -115,25 +115,25 @@ def _create_dummy_user(self, add_clean_up=True): 'password': password, 'description': description, 'name': username}) - items = self.parse_show(raw_output) - self.assert_show_fields(items, self.USER_FIELDS) if add_clean_up: self.addCleanup( self.openstack, 'user delete %s' % self.parse_show_as_object(raw_output)['id']) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.USER_FIELDS) return username def _create_dummy_role(self, add_clean_up=True): role_name = data_utils.rand_name('TestRole') raw_output = self.openstack('role create %s' % role_name) - items = self.parse_show(raw_output) - self.assert_show_fields(items, self.ROLE_FIELDS) role = self.parse_show_as_object(raw_output) - self.assertEqual(role_name, role['name']) if add_clean_up: self.addCleanup( self.openstack, 'role delete %s' % role['id']) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.ROLE_FIELDS) + self.assertEqual(role_name, role['name']) return role_name def _create_dummy_group(self, add_clean_up=True): @@ -146,8 +146,6 @@ def _create_dummy_group(self, add_clean_up=True): '%(name)s' % {'domain': self.domain_name, 'description': description, 'name': group_name}) - items = self.parse_show(raw_output) - self.assert_show_fields(items, self.GROUP_FIELDS) if add_clean_up: self.addCleanup( self.openstack, @@ -155,6 +153,8 @@ def _create_dummy_group(self, add_clean_up=True): '--domain %(domain)s ' '%(name)s' % {'domain': self.domain_name, 'name': group_name}) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.GROUP_FIELDS) return group_name def _create_dummy_domain(self, add_clean_up=True): @@ -208,11 +208,11 @@ def _create_dummy_region(self, parent_region=None, add_clean_up=True): '%(id)s' % {'parent_region_arg': parent_region_arg, 'description': description, 'id': region_id}) - items = self.parse_show(raw_output) - self.assert_show_fields(items, self.REGION_FIELDS) if add_clean_up: self.addCleanup(self.openstack, 'region delete %s' % region_id) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.REGION_FIELDS) return region_id def _create_dummy_service(self, add_clean_up=True): @@ -227,12 +227,12 @@ def _create_dummy_service(self, add_clean_up=True): '%(type)s' % {'name': service_name, 'description': description, 'type': type_name}) - items = self.parse_show(raw_output) - self.assert_show_fields(items, self.SERVICE_FIELDS) if add_clean_up: service = self.parse_show_as_object(raw_output) self.addCleanup(self.openstack, 'service delete %s' % service['id']) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.SERVICE_FIELDS) return service_name def _create_dummy_endpoint(self, interface='public', add_clean_up=True): @@ -249,13 +249,13 @@ def _create_dummy_endpoint(self, interface='public', add_clean_up=True): 'service': service_name, 'interface': interface, 'url': endpoint_url}) - items = self.parse_show(raw_output) - self.assert_show_fields(items, self.ENDPOINT_FIELDS) endpoint = self.parse_show_as_object(raw_output) if add_clean_up: self.addCleanup( self.openstack, 'endpoint delete %s' % endpoint['id']) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.ENDPOINT_FIELDS) return endpoint['id'] def _create_dummy_idp(self, add_clean_up=True): @@ -267,12 +267,12 @@ def _create_dummy_idp(self, add_clean_up=True): '--description %(description)s ' '--enable ' % {'name': identity_provider, 'description': description}) - items = self.parse_show(raw_output) - self.assert_show_fields(items, self.IDENTITY_PROVIDER_FIELDS) if add_clean_up: self.addCleanup( self.openstack, 'identity provider delete %s' % identity_provider) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.IDENTITY_PROVIDER_FIELDS) return identity_provider def _create_dummy_sp(self, add_clean_up=True): @@ -286,10 +286,10 @@ def _create_dummy_sp(self, add_clean_up=True): '--service-provider-url https://sp.example.com:5000 ' '--enable ' % {'name': service_provider, 'description': description}) - items = self.parse_show(raw_output) - self.assert_show_fields(items, self.SERVICE_PROVIDER_FIELDS) if add_clean_up: self.addCleanup( self.openstack, 'service provider delete %s' % service_provider) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.SERVICE_PROVIDER_FIELDS) return service_provider diff --git a/functional/tests/identity/v3/test_role.py b/functional/tests/identity/v3/test_role.py index 7e0cf76e95..5f150b052d 100644 --- a/functional/tests/identity/v3/test_role.py +++ b/functional/tests/identity/v3/test_role.py @@ -45,6 +45,18 @@ def test_role_list_with_user_project(self): 'user': username, 'user_domain': self.domain_name, 'role': role_name}) + self.addCleanup( + self.openstack, + 'role remove ' + '--project %(project)s ' + '--project-domain %(project_domain)s ' + '--user %(user)s ' + '--user-domain %(user_domain)s ' + '%(role)s' % {'project': self.project_name, + 'project_domain': self.domain_name, + 'user': username, + 'user_domain': self.domain_name, + 'role': role_name}) self.assertEqual(0, len(raw_output)) raw_output = self.openstack( 'role list ' @@ -59,18 +71,6 @@ def test_role_list_with_user_project(self): items = self.parse_listing(raw_output) self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS) self.assertEqual(1, len(items)) - self.addCleanup( - self.openstack, - 'role remove ' - '--project %(project)s ' - '--project-domain %(project_domain)s ' - '--user %(user)s ' - '--user-domain %(user_domain)s ' - '%(role)s' % {'project': self.project_name, - 'project_domain': self.domain_name, - 'user': username, - 'user_domain': self.domain_name, - 'role': role_name}) def test_role_show(self): role_name = self._create_dummy_role() @@ -102,7 +102,6 @@ def test_role_add(self): 'user': username, 'user_domain': self.domain_name, 'role': role_name}) - self.assertEqual(0, len(raw_output)) self.addCleanup( self.openstack, 'role remove ' @@ -115,11 +114,12 @@ def test_role_add(self): 'user': username, 'user_domain': self.domain_name, 'role': role_name}) + self.assertEqual(0, len(raw_output)) def test_role_remove(self): role_name = self._create_dummy_role() username = self._create_dummy_user() - raw_output = self.openstack( + add_raw_output = self.openstack( 'role add ' '--project %(project)s ' '--project-domain %(project_domain)s ' @@ -130,8 +130,7 @@ def test_role_remove(self): 'user': username, 'user_domain': self.domain_name, 'role': role_name}) - self.assertEqual(0, len(raw_output)) - raw_output = self.openstack( + remove_raw_output = self.openstack( 'role remove ' '--project %(project)s ' '--project-domain %(project_domain)s ' @@ -142,4 +141,5 @@ def test_role_remove(self): 'user': username, 'user_domain': self.domain_name, 'role': role_name}) - self.assertEqual(0, len(raw_output)) + self.assertEqual(0, len(add_raw_output)) + self.assertEqual(0, len(remove_raw_output)) From b13ec98467f2d374e741efa5888937c5f2b86ebb Mon Sep 17 00:00:00 2001 From: Cedric Brandily Date: Wed, 6 Apr 2016 01:12:40 +0200 Subject: [PATCH 0783/3095] Prefer assertEqual/assertIn over assertOutput/assertInOutput functional.common.tests module defines: * assertOutput (similar to assertEqual) * assertInOutput (similar to assertIn) in order to allow the usage of assertions in testcase classmethods but there is no reason to use them in testcase instancemethods at least because they raise Exception instances instead of AssertionError instances. Change-Id: I9ffcaf9c6e6a1ff5df6ea2d79be3fb4496db4b85 --- functional/tests/common/test_quota.py | 2 +- functional/tests/identity/v2/test_endpoint.py | 2 +- functional/tests/identity/v3/test_endpoint.py | 2 +- functional/tests/identity/v3/test_group.py | 14 +++++++------- functional/tests/identity/v3/test_project.py | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/functional/tests/common/test_quota.py b/functional/tests/common/test_quota.py index 7a0cb64026..62b43a34ab 100644 --- a/functional/tests/common/test_quota.py +++ b/functional/tests/common/test_quota.py @@ -35,4 +35,4 @@ def test_quota_set(self): def test_quota_show(self): raw_output = self.openstack('quota show ' + self.PROJECT_NAME) for expected_field in self.EXPECTED_FIELDS: - self.assertInOutput(expected_field, raw_output) + self.assertIn(expected_field, raw_output) diff --git a/functional/tests/identity/v2/test_endpoint.py b/functional/tests/identity/v2/test_endpoint.py index 0aed3220c8..8064365e01 100644 --- a/functional/tests/identity/v2/test_endpoint.py +++ b/functional/tests/identity/v2/test_endpoint.py @@ -27,7 +27,7 @@ def test_endpoint_delete(self): def test_endpoint_list(self): endpoint_id = self._create_dummy_endpoint() raw_output = self.openstack('endpoint list') - self.assertInOutput(endpoint_id, raw_output) + self.assertIn(endpoint_id, raw_output) items = self.parse_listing(raw_output) self.assert_table_structure(items, self.ENDPOINT_LIST_HEADERS) diff --git a/functional/tests/identity/v3/test_endpoint.py b/functional/tests/identity/v3/test_endpoint.py index a7590787d9..e68aa848da 100644 --- a/functional/tests/identity/v3/test_endpoint.py +++ b/functional/tests/identity/v3/test_endpoint.py @@ -31,7 +31,7 @@ def test_endpoint_delete(self): def test_endpoint_list(self): endpoint_id = self._create_dummy_endpoint() raw_output = self.openstack('endpoint list') - self.assertInOutput(endpoint_id, raw_output) + self.assertIn(endpoint_id, raw_output) items = self.parse_listing(raw_output) self.assert_table_structure(items, self.ENDPOINT_LIST_HEADERS) diff --git a/functional/tests/identity/v3/test_group.py b/functional/tests/identity/v3/test_group.py index 4c6ed19b2d..156a9ff1e8 100644 --- a/functional/tests/identity/v3/test_group.py +++ b/functional/tests/identity/v3/test_group.py @@ -25,7 +25,7 @@ def test_group_list(self): raw_output = self.openstack('group list') items = self.parse_listing(raw_output) self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS) - self.assertInOutput(group_name, raw_output) + self.assertIn(group_name, raw_output) def test_group_list_with_domain(self): group_name = self._create_dummy_group() @@ -33,7 +33,7 @@ def test_group_list_with_domain(self): 'group list --domain %s' % self.domain_name) items = self.parse_listing(raw_output) self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS) - self.assertInOutput(group_name, raw_output) + self.assertIn(group_name, raw_output) def test_group_delete(self): group_name = self._create_dummy_group(add_clean_up=False) @@ -102,7 +102,7 @@ def test_group_add_user(self): 'user_domain': self.domain_name, 'group': group_name, 'user': username}) - self.assertOutput( + self.assertEqual( '%(user)s added to group %(group)s\n' % {'user': username, 'group': group_name}, raw_output @@ -128,7 +128,7 @@ def test_group_contains_user(self): 'user_domain': self.domain_name, 'group': group_name, 'user': username}) - self.assertOutput( + self.assertEqual( '%(user)s added to group %(group)s\n' % {'user': username, 'group': group_name}, raw_output @@ -141,7 +141,7 @@ def test_group_contains_user(self): 'user_domain': self.domain_name, 'group': group_name, 'user': username}) - self.assertOutput( + self.assertEqual( '%(user)s in group %(group)s\n' % {'user': username, 'group': group_name}, raw_output) @@ -165,12 +165,12 @@ def test_group_remove_user(self): 'user_domain': self.domain_name, 'group': group_name, 'user': username}) - self.assertOutput( + self.assertEqual( '%(user)s added to group %(group)s\n' % {'user': username, 'group': group_name}, add_raw_output ) - self.assertOutput( + self.assertEqual( '%(user)s removed from ' 'group %(group)s\n' % {'user': username, 'group': group_name}, diff --git a/functional/tests/identity/v3/test_project.py b/functional/tests/identity/v3/test_project.py index 204a8d14e4..6c278691f4 100644 --- a/functional/tests/identity/v3/test_project.py +++ b/functional/tests/identity/v3/test_project.py @@ -65,7 +65,7 @@ def test_project_list_with_domain(self): 'project list --domain %s' % self.domain_name) items = self.parse_listing(raw_output) self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS) - self.assertInOutput(project_name, raw_output) + self.assertIn(project_name, raw_output) self.assertTrue(len(items) > 0) def test_project_set(self): From 311e775c81419c3b28d03e616a0e415a614b9cff Mon Sep 17 00:00:00 2001 From: Ivan Kolodyazhny Date: Thu, 17 Mar 2016 18:35:41 +0200 Subject: [PATCH 0784/3095] Make snapshot and backup name optional Cinder does not require snapshot and backup name. These arguments are optional. Change-Id: I05d59efc9642205a25684bf0b77758328296d959 --- .../tests/volume/v2/test_backup.py | 24 +++++++++++++++++++ .../tests/volume/v2/test_snapshot.py | 24 +++++++++++++++++++ openstackclient/volume/v1/backup.py | 1 - openstackclient/volume/v1/snapshot.py | 1 - openstackclient/volume/v2/backup.py | 1 - openstackclient/volume/v2/snapshot.py | 1 - ...backup-name-optional-01971d33640ef1c8.yaml | 4 ++++ 7 files changed, 52 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/make-snapshot-and-backup-name-optional-01971d33640ef1c8.yaml diff --git a/openstackclient/tests/volume/v2/test_backup.py b/openstackclient/tests/volume/v2/test_backup.py index cc8dff5a36..0e906e7bf1 100644 --- a/openstackclient/tests/volume/v2/test_backup.py +++ b/openstackclient/tests/volume/v2/test_backup.py @@ -77,6 +77,30 @@ def test_backup_create(self): self.assertEqual(columns, volume_fakes.BACKUP_columns) self.assertEqual(data, volume_fakes.BACKUP_data) + def test_backup_create_without_name(self): + arglist = [ + volume_fakes.volume_id, + "--description", volume_fakes.backup_description, + "--container", volume_fakes.backup_name + ] + verifylist = [ + ("volume", volume_fakes.volume_id), + ("description", volume_fakes.backup_description), + ("container", volume_fakes.backup_name) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.backups_mock.create.assert_called_with( + volume_fakes.volume_id, + container=volume_fakes.backup_name, + name=None, + description=volume_fakes.backup_description + ) + self.assertEqual(columns, volume_fakes.BACKUP_columns) + self.assertEqual(data, volume_fakes.BACKUP_data) + class TestBackupDelete(TestBackup): diff --git a/openstackclient/tests/volume/v2/test_snapshot.py b/openstackclient/tests/volume/v2/test_snapshot.py index 87e2fccfa8..8c75dfb26e 100644 --- a/openstackclient/tests/volume/v2/test_snapshot.py +++ b/openstackclient/tests/volume/v2/test_snapshot.py @@ -75,6 +75,30 @@ def test_snapshot_create(self): self.assertEqual(columns, volume_fakes.SNAPSHOT_columns) self.assertEqual(data, volume_fakes.SNAPSHOT_data) + def test_snapshot_create_without_name(self): + arglist = [ + volume_fakes.volume_id, + "--description", volume_fakes.snapshot_description, + "--force" + ] + verifylist = [ + ("volume", volume_fakes.volume_id), + ("description", volume_fakes.snapshot_description), + ("force", True) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.snapshots_mock.create.assert_called_with( + volume_fakes.volume_id, + force=True, + name=None, + description=volume_fakes.snapshot_description + ) + self.assertEqual(columns, volume_fakes.SNAPSHOT_columns) + self.assertEqual(data, volume_fakes.SNAPSHOT_data) + class TestSnapshotDelete(TestSnapshot): diff --git a/openstackclient/volume/v1/backup.py b/openstackclient/volume/v1/backup.py index 32f39fb539..40b603151f 100644 --- a/openstackclient/volume/v1/backup.py +++ b/openstackclient/volume/v1/backup.py @@ -41,7 +41,6 @@ def get_parser(self, prog_name): parser.add_argument( '--name', metavar='', - required=False, help='Name of the backup', ) parser.add_argument( diff --git a/openstackclient/volume/v1/snapshot.py b/openstackclient/volume/v1/snapshot.py index 95200e4074..c54bac8aef 100644 --- a/openstackclient/volume/v1/snapshot.py +++ b/openstackclient/volume/v1/snapshot.py @@ -36,7 +36,6 @@ def get_parser(self, prog_name): parser.add_argument( '--name', metavar='', - required=True, help='Name of the snapshot', ) parser.add_argument( diff --git a/openstackclient/volume/v2/backup.py b/openstackclient/volume/v2/backup.py index 64ca97ae8a..016a414c67 100644 --- a/openstackclient/volume/v2/backup.py +++ b/openstackclient/volume/v2/backup.py @@ -35,7 +35,6 @@ def get_parser(self, prog_name): parser.add_argument( "--name", metavar="", - required=True, help="Name of the backup" ) parser.add_argument( diff --git a/openstackclient/volume/v2/snapshot.py b/openstackclient/volume/v2/snapshot.py index 4d00b72623..c9e502976a 100644 --- a/openstackclient/volume/v2/snapshot.py +++ b/openstackclient/volume/v2/snapshot.py @@ -36,7 +36,6 @@ def get_parser(self, prog_name): parser.add_argument( "--name", metavar="", - required=True, help="Name of the snapshot" ) parser.add_argument( diff --git a/releasenotes/notes/make-snapshot-and-backup-name-optional-01971d33640ef1c8.yaml b/releasenotes/notes/make-snapshot-and-backup-name-optional-01971d33640ef1c8.yaml new file mode 100644 index 0000000000..8ecbe6b6b3 --- /dev/null +++ b/releasenotes/notes/make-snapshot-and-backup-name-optional-01971d33640ef1c8.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - Make ``--name`` optional in ``volume snapshot create`` and + ``volume backup create`` commands. From 73d15e3768e28008515781b30e533ddf3d8d4159 Mon Sep 17 00:00:00 2001 From: reedip Date: Fri, 1 Apr 2016 14:26:16 +0900 Subject: [PATCH 0785/3095] Add option to clear information from ports This patch adds the option of "no-fixed-ip" and "no-binding-profile" which is used to clear the fixed-ip and binding:profile information from the ports. Change-Id: I946301eaf6c647bae55e4f416aa0d98e5f06e699 --- doc/source/command-objects/port.rst | 12 +++- openstackclient/network/v2/port.py | 58 ++++++++++++++----- openstackclient/tests/network/v2/test_port.py | 6 ++ ...nset-fixed-ip-option-9b1832d0f7f71eda.yaml | 5 ++ 4 files changed, 64 insertions(+), 17 deletions(-) create mode 100644 releasenotes/notes/add-unset-fixed-ip-option-9b1832d0f7f71eda.yaml diff --git a/doc/source/command-objects/port.rst b/doc/source/command-objects/port.rst index 46bd6339ae..b666b6528b 100644 --- a/doc/source/command-objects/port.rst +++ b/doc/source/command-objects/port.rst @@ -123,11 +123,11 @@ Set port properties .. code:: bash os port set - [--fixed-ip subnet=,ip-address=] + [--fixed-ip subnet=,ip-address= | --no-fixed-ip] [--device-id ] [--device-owner ] [--vnic-type ] - [--binding-profile ] + [--binding-profile | --no-binding-profile] [--host-id ] [--enable | --disable] [--name ] @@ -139,6 +139,10 @@ Set port properties subnet=,ip-address= (you can repeat this option) +.. option:: --no-fixed-ip + + Clear existing information of fixed-ips + .. option:: --device-id Device ID of this port @@ -157,6 +161,10 @@ Set port properties Custom data to be passed as binding:profile: = (this option can be repeated) +.. option:: --no-binding-profile + + Clear existing information of binding:profile + .. option:: --host-id The ID of the host where the port is allocated diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index d7866cccdf..a9e8042868 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -151,14 +151,6 @@ def _prepare_fixed_ips(client_manager, parsed_args): def _add_updatable_args(parser): - parser.add_argument( - '--fixed-ip', - metavar='subnet=,ip-address=', - action=parseractions.MultiKeyValueAction, - optional_keys=['subnet', 'ip-address'], - help='Desired IP and/or subnet (name or ID) for this port: ' - 'subnet=,ip-address= ' - '(this option can be repeated)') # NOTE(dtroyer): --device-id is deprecated in Mar 2016. Do not # remove before 3.x release or Mar 2017. device_group = parser.add_mutually_exclusive_group() @@ -184,12 +176,6 @@ def _add_updatable_args(parser): help="VNIC type for this port (direct | direct-physical |" " macvtap | normal | baremetal). If unspecified during" " port creation, default value will be 'normal'.") - parser.add_argument( - '--binding-profile', - metavar='', - action=parseractions.KeyValueAction, - help='Custom data to be passed as binding:profile: = ' - '(this option can be repeated)') # NOTE(dtroyer): --host-id is deprecated in Mar 2016. Do not # remove before 3.x release or Mar 2017. host_group = parser.add_mutually_exclusive_group() @@ -217,6 +203,20 @@ def get_parser(self, prog_name): required=True, help='Network this port belongs to (name or ID)') _add_updatable_args(parser) + parser.add_argument( + '--fixed-ip', + metavar='subnet=,ip-address=', + action=parseractions.MultiKeyValueAction, + optional_keys=['subnet', 'ip-address'], + help='Desired IP and/or subnet (name or ID) for this port: ' + 'subnet=,ip-address= ' + '(this option can be repeated)') + parser.add_argument( + '--binding-profile', + metavar='', + action=parseractions.KeyValueAction, + help='Custom data to be passed as binding:profile: = ' + '(this option can be repeated)') admin_group = parser.add_mutually_exclusive_group() admin_group.add_argument( '--enable', @@ -352,7 +352,30 @@ def get_parser(self, prog_name): metavar="", help=("Port to modify (name or ID)") ) - + fixed_ip = parser.add_mutually_exclusive_group() + fixed_ip.add_argument( + '--fixed-ip', + metavar='subnet=,ip-address=', + action=parseractions.MultiKeyValueAction, + optional_keys=['subnet', 'ip-address'], + help='Desired IP and/or subnet (name or ID) for this port: ' + 'subnet=,ip-address= ' + '(this option can be repeated)') + fixed_ip.add_argument( + '--no-fixed-ip', + action='store_true', + help='Clear existing information of fixed-ips') + binding_profile = parser.add_mutually_exclusive_group() + binding_profile.add_argument( + '--binding-profile', + metavar='', + action=parseractions.KeyValueAction, + help='Custom data to be passed as binding:profile: = ' + '(this option can be repeated)') + binding_profile.add_argument( + '--no-binding-profile', + action='store_true', + help='Clear existing information of binding:profile') return parser def take_action(self, parsed_args): @@ -361,6 +384,11 @@ def take_action(self, parsed_args): _prepare_fixed_ips(self.app.client_manager, parsed_args) attrs = _get_attrs(self.app.client_manager, parsed_args) + if parsed_args.no_fixed_ip: + attrs['fixed_ips'] = [] + if parsed_args.no_binding_profile: + attrs['binding:profile'] = {} + if attrs == {}: msg = "Nothing specified to be set" raise exceptions.CommandError(msg) diff --git a/openstackclient/tests/network/v2/test_port.py b/openstackclient/tests/network/v2/test_port.py index 31454dba57..3b1a641a3e 100644 --- a/openstackclient/tests/network/v2/test_port.py +++ b/openstackclient/tests/network/v2/test_port.py @@ -298,10 +298,14 @@ def test_set_fixed_ip(self): def test_set_this(self): arglist = [ '--disable', + '--no-fixed-ip', + '--no-binding-profile', self._port.name, ] verifylist = [ ('disable', True), + ('no_binding_profile', True), + ('no_fixed_ip', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -309,6 +313,8 @@ def test_set_this(self): attrs = { 'admin_state_up': False, + 'binding:profile': {}, + 'fixed_ips': [], } self.network.update_port.assert_called_once_with(self._port, **attrs) self.assertIsNone(result) diff --git a/releasenotes/notes/add-unset-fixed-ip-option-9b1832d0f7f71eda.yaml b/releasenotes/notes/add-unset-fixed-ip-option-9b1832d0f7f71eda.yaml new file mode 100644 index 0000000000..759e2ca904 --- /dev/null +++ b/releasenotes/notes/add-unset-fixed-ip-option-9b1832d0f7f71eda.yaml @@ -0,0 +1,5 @@ +--- +features: + - Fixed-IP information and binding profile information + in ports can now be cleared using ``--no-fixed-ip`` + and ``--no-binding-profile`` with ``port set`` From 2a9ba9db30f8d57aa32f7614ee4b4afee15fba9d Mon Sep 17 00:00:00 2001 From: reedip Date: Thu, 7 Apr 2016 11:00:07 +0900 Subject: [PATCH 0786/3095] TrivialFix: Fix help messages for port set Certain messages in ``port set`` were incorrect as mentioned in [1] which are fixed in this patch. [1]:https://review.openstack.org/#/c/300309/6/doc/source/command-objects/port.rst TrivialFix Change-Id: Icb8e91664fea590cd4202f80d61a82eb50b22403 --- doc/source/command-objects/port.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/command-objects/port.rst b/doc/source/command-objects/port.rst index b666b6528b..2e83eea1d1 100644 --- a/doc/source/command-objects/port.rst +++ b/doc/source/command-objects/port.rst @@ -135,9 +135,9 @@ Set port properties .. option:: --fixed-ip subnet=,ip-address= - Desired IP and/or subnet for this port: + Desired IP and/or subnet (name or ID) for this port: subnet=,ip-address= - (you can repeat this option) + (this option can be repeated) .. option:: --no-fixed-ip From ad6727df8800a61a5890995a1e572e6f5b9bab01 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 7 Apr 2016 09:54:54 -0500 Subject: [PATCH 0787/3095] Clean up release notes since 2.2.0 release * Remove release notes for changes to commands that have not been released yet * Combine notes for common command sets (ie, new port commands) * Rename notes with bugs to the bug-NNNNN-XXXXXXX form Change-Id: I6216aad443056ada2fe28ba533bf20a09f118e4d --- .../notes/add-disable-reason-6e0f28459a09a60d.yaml | 2 +- .../notes/add-external-options-7a66219d263bb1e5.yaml | 9 --------- .../notes/add-name-to-port-set-eefa081225bf8b49.yaml | 4 ---- ...d48f.yaml => add-port-commands-a3580662721a6312.yaml} | 2 +- .../notes/add-port-create-command-a3580662721a6312.yaml | 5 ----- .../notes/add-port-list-command-29b4452003bc6bab.yaml | 5 ----- .../notes/add-provider-options-12bbf01d2280dd2f.yaml | 9 --------- .../notes/add-restore-server-d8c73e0e83df17dd.yaml | 2 +- .../add-unset-fixed-ip-option-9b1832d0f7f71eda.yaml | 5 ----- releasenotes/notes/bug-1519511-65b8901ae6ea2e63.yaml | 3 ++- releasenotes/notes/bug-1519511-68ca30ad5519d3d8.yaml | 6 ------ releasenotes/notes/bug-1544586-0e6ca9a09dac0726.yaml | 6 ++---- releasenotes/notes/bug-1544591-267fd16cc5efffe6.yaml | 4 ---- releasenotes/notes/bug-1545537-12bbf01d2280dd2f.yaml | 8 ++++++++ releasenotes/notes/bug-1545537-7a66219d263bb1e5.yaml | 7 +++++++ ...791eab45b7.yaml => bug-1554877-7f8479791eab45b7.yaml} | 6 +++--- ...07eba8357a.yaml => bug-1554879-118c6007eba8357a.yaml} | 6 +++--- releasenotes/notes/bug-1558677-a85f0c548306ba80.yaml | 7 ------- releasenotes/notes/bug-1565112-e0cea9bfbcab954f.yaml | 6 ++++++ releasenotes/notes/bug_1565112-e0cea9bfbcab954f.yaml | 6 ------ 20 files changed, 34 insertions(+), 74 deletions(-) delete mode 100644 releasenotes/notes/add-external-options-7a66219d263bb1e5.yaml delete mode 100644 releasenotes/notes/add-name-to-port-set-eefa081225bf8b49.yaml rename releasenotes/notes/{add-port-set-command-2b4fe38ec71ad48f.yaml => add-port-commands-a3580662721a6312.yaml} (56%) delete mode 100644 releasenotes/notes/add-port-create-command-a3580662721a6312.yaml delete mode 100644 releasenotes/notes/add-port-list-command-29b4452003bc6bab.yaml delete mode 100644 releasenotes/notes/add-provider-options-12bbf01d2280dd2f.yaml delete mode 100644 releasenotes/notes/add-unset-fixed-ip-option-9b1832d0f7f71eda.yaml delete mode 100644 releasenotes/notes/bug-1519511-68ca30ad5519d3d8.yaml delete mode 100644 releasenotes/notes/bug-1544591-267fd16cc5efffe6.yaml create mode 100644 releasenotes/notes/bug-1545537-12bbf01d2280dd2f.yaml create mode 100644 releasenotes/notes/bug-1545537-7a66219d263bb1e5.yaml rename releasenotes/notes/{image-property_add-7f8479791eab45b7.yaml => bug-1554877-7f8479791eab45b7.yaml} (67%) rename releasenotes/notes/{image-property-delete-118c6007eba8357a.yaml => bug-1554879-118c6007eba8357a.yaml} (66%) delete mode 100644 releasenotes/notes/bug-1558677-a85f0c548306ba80.yaml create mode 100644 releasenotes/notes/bug-1565112-e0cea9bfbcab954f.yaml delete mode 100644 releasenotes/notes/bug_1565112-e0cea9bfbcab954f.yaml diff --git a/releasenotes/notes/add-disable-reason-6e0f28459a09a60d.yaml b/releasenotes/notes/add-disable-reason-6e0f28459a09a60d.yaml index 298ccad5c4..7f9e441431 100644 --- a/releasenotes/notes/add-disable-reason-6e0f28459a09a60d.yaml +++ b/releasenotes/notes/add-disable-reason-6e0f28459a09a60d.yaml @@ -1,4 +1,4 @@ --- features: - | - Add support for the ``--disable-reason`` of ``service set`` command + Add ``--disable-reason`` option to the ``service set`` command diff --git a/releasenotes/notes/add-external-options-7a66219d263bb1e5.yaml b/releasenotes/notes/add-external-options-7a66219d263bb1e5.yaml deleted file mode 100644 index 7b8eaff00f..0000000000 --- a/releasenotes/notes/add-external-options-7a66219d263bb1e5.yaml +++ /dev/null @@ -1,9 +0,0 @@ ---- -features: - - | - New options have been added to the ``network create`` command - to support external network functionality. The new options are - ``--external/--internal`` and suboptions to ``external``: - ``--default/--no-default``. - These options are available for Networkv2 only. - [Bug `1545537 `_] diff --git a/releasenotes/notes/add-name-to-port-set-eefa081225bf8b49.yaml b/releasenotes/notes/add-name-to-port-set-eefa081225bf8b49.yaml deleted file mode 100644 index 13ecad265a..0000000000 --- a/releasenotes/notes/add-name-to-port-set-eefa081225bf8b49.yaml +++ /dev/null @@ -1,4 +0,0 @@ ---- -features: - - | - Port name can now be updated using ``port set`` \ No newline at end of file diff --git a/releasenotes/notes/add-port-set-command-2b4fe38ec71ad48f.yaml b/releasenotes/notes/add-port-commands-a3580662721a6312.yaml similarity index 56% rename from releasenotes/notes/add-port-set-command-2b4fe38ec71ad48f.yaml rename to releasenotes/notes/add-port-commands-a3580662721a6312.yaml index 89784d2b7f..a5a12567da 100644 --- a/releasenotes/notes/add-port-set-command-2b4fe38ec71ad48f.yaml +++ b/releasenotes/notes/add-port-commands-a3580662721a6312.yaml @@ -1,5 +1,5 @@ --- features: - | - Add support for the ``port set`` command. + Add support for the ``port create``, ``port list`` and ``port set`` commands. [Bug `1519909 `_] diff --git a/releasenotes/notes/add-port-create-command-a3580662721a6312.yaml b/releasenotes/notes/add-port-create-command-a3580662721a6312.yaml deleted file mode 100644 index 4fafb42c00..0000000000 --- a/releasenotes/notes/add-port-create-command-a3580662721a6312.yaml +++ /dev/null @@ -1,5 +0,0 @@ ---- -features: - - | - Add support for the ``port create`` command. - [Bug `1519909 `_] diff --git a/releasenotes/notes/add-port-list-command-29b4452003bc6bab.yaml b/releasenotes/notes/add-port-list-command-29b4452003bc6bab.yaml deleted file mode 100644 index 6c62539714..0000000000 --- a/releasenotes/notes/add-port-list-command-29b4452003bc6bab.yaml +++ /dev/null @@ -1,5 +0,0 @@ ---- -features: - - | - Add support for the ``port list`` command. - [Bug `1519909 `_] \ No newline at end of file diff --git a/releasenotes/notes/add-provider-options-12bbf01d2280dd2f.yaml b/releasenotes/notes/add-provider-options-12bbf01d2280dd2f.yaml deleted file mode 100644 index 68de08190f..0000000000 --- a/releasenotes/notes/add-provider-options-12bbf01d2280dd2f.yaml +++ /dev/null @@ -1,9 +0,0 @@ ---- -features: - - | - New options have been added to the ``network create`` command - to support provider network functionality. - These options are ``--provider-network-type``, ``--provider-physical-network``, - and ``--provider-segmentation-id``. - These options are available for Networkv2 only - [Bug `1545537 `_] diff --git a/releasenotes/notes/add-restore-server-d8c73e0e83df17dd.yaml b/releasenotes/notes/add-restore-server-d8c73e0e83df17dd.yaml index 09d3b58289..e6d221117b 100644 --- a/releasenotes/notes/add-restore-server-d8c73e0e83df17dd.yaml +++ b/releasenotes/notes/add-restore-server-d8c73e0e83df17dd.yaml @@ -1,4 +1,4 @@ --- features: - | - Add support for the ``server restore`` command. + Add ``server restore`` command diff --git a/releasenotes/notes/add-unset-fixed-ip-option-9b1832d0f7f71eda.yaml b/releasenotes/notes/add-unset-fixed-ip-option-9b1832d0f7f71eda.yaml deleted file mode 100644 index 759e2ca904..0000000000 --- a/releasenotes/notes/add-unset-fixed-ip-option-9b1832d0f7f71eda.yaml +++ /dev/null @@ -1,5 +0,0 @@ ---- -features: - - Fixed-IP information and binding profile information - in ports can now be cleared using ``--no-fixed-ip`` - and ``--no-binding-profile`` with ``port set`` diff --git a/releasenotes/notes/bug-1519511-65b8901ae6ea2e63.yaml b/releasenotes/notes/bug-1519511-65b8901ae6ea2e63.yaml index 843c6cafa7..3122f83cac 100644 --- a/releasenotes/notes/bug-1519511-65b8901ae6ea2e63.yaml +++ b/releasenotes/notes/bug-1519511-65b8901ae6ea2e63.yaml @@ -1,6 +1,7 @@ --- features: - - The ``security group create`` command now uses Network v2 when + - The ``security group create``, ``security group set`` and + ``security group show`` commands now uses Network v2 when enabled which results in a more detailed output for network security group rules. [Bug `1519511 `_] diff --git a/releasenotes/notes/bug-1519511-68ca30ad5519d3d8.yaml b/releasenotes/notes/bug-1519511-68ca30ad5519d3d8.yaml deleted file mode 100644 index d69244e6c2..0000000000 --- a/releasenotes/notes/bug-1519511-68ca30ad5519d3d8.yaml +++ /dev/null @@ -1,6 +0,0 @@ ---- -features: - - The ``security group show`` command now uses Network v2 when - enabled which results in a more detailed output for network - security group rules. - [Bug `1519511 `_] diff --git a/releasenotes/notes/bug-1544586-0e6ca9a09dac0726.yaml b/releasenotes/notes/bug-1544586-0e6ca9a09dac0726.yaml index fdbd6fd18b..d1fc20c9e1 100644 --- a/releasenotes/notes/bug-1544586-0e6ca9a09dac0726.yaml +++ b/releasenotes/notes/bug-1544586-0e6ca9a09dac0726.yaml @@ -1,7 +1,5 @@ --- features: - - Add ``subnet pool create`` command. - [Bug `1544586 `_] - - Command ``subnet pool create`` now supports ``--project`` and - ``--project-domain`` options. + - Add ``subnet pool create`` and ``subnet pool set`` commands. [Bug `1544586 `_] + [Bug `1544591 `_] \ No newline at end of file diff --git a/releasenotes/notes/bug-1544591-267fd16cc5efffe6.yaml b/releasenotes/notes/bug-1544591-267fd16cc5efffe6.yaml deleted file mode 100644 index 130af91130..0000000000 --- a/releasenotes/notes/bug-1544591-267fd16cc5efffe6.yaml +++ /dev/null @@ -1,4 +0,0 @@ ---- -features: - - Add ``subnet pool set`` command. - [Bug `1544591 `_] diff --git a/releasenotes/notes/bug-1545537-12bbf01d2280dd2f.yaml b/releasenotes/notes/bug-1545537-12bbf01d2280dd2f.yaml new file mode 100644 index 0000000000..7be07a86a5 --- /dev/null +++ b/releasenotes/notes/bug-1545537-12bbf01d2280dd2f.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Add provider network options ``--provider-network-type``, + ``--provider-physical-network``, and ``--provider-segmentation-id`` + to the ``network create`` command. + These options are available for Networkv2 only + [Bug `1545537 `_] diff --git a/releasenotes/notes/bug-1545537-7a66219d263bb1e5.yaml b/releasenotes/notes/bug-1545537-7a66219d263bb1e5.yaml new file mode 100644 index 0000000000..db0e22a1cd --- /dev/null +++ b/releasenotes/notes/bug-1545537-7a66219d263bb1e5.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Add external network options ``--external|--internal`` and ``--external`` + suboptions ``--default|--no-default`` to the ``network create`` command. + These options are available for Networkv2 only. + [Bug `1545537 `_] diff --git a/releasenotes/notes/image-property_add-7f8479791eab45b7.yaml b/releasenotes/notes/bug-1554877-7f8479791eab45b7.yaml similarity index 67% rename from releasenotes/notes/image-property_add-7f8479791eab45b7.yaml rename to releasenotes/notes/bug-1554877-7f8479791eab45b7.yaml index 724da45159..a5d602633e 100644 --- a/releasenotes/notes/image-property_add-7f8479791eab45b7.yaml +++ b/releasenotes/notes/bug-1554877-7f8479791eab45b7.yaml @@ -1,7 +1,7 @@ --- -fixes: +features: - | - Added support for setting volume image property. + Added ``--image-property`` option to ``volume set`` command Image properties are copied from image when volume is created. But since a volume is mutable, user sometime wants to update @@ -11,4 +11,4 @@ fixes: using below command: ``volume set --image-property ``. - [Bug 'https://bugs.launchpad.net/python-openstackclient/+bug/1554877'_] + [Bug '1554877 '_] diff --git a/releasenotes/notes/image-property-delete-118c6007eba8357a.yaml b/releasenotes/notes/bug-1554879-118c6007eba8357a.yaml similarity index 66% rename from releasenotes/notes/image-property-delete-118c6007eba8357a.yaml rename to releasenotes/notes/bug-1554879-118c6007eba8357a.yaml index deb1556799..3b9ed48782 100644 --- a/releasenotes/notes/image-property-delete-118c6007eba8357a.yaml +++ b/releasenotes/notes/bug-1554879-118c6007eba8357a.yaml @@ -1,7 +1,7 @@ --- -fixes: +features: - | - Added support for deleting volume image property. + Added ``--image-property`` option to ``volume unset`` command Image properties are copied from image when volume is created. But since a volume is mutable, user sometime wants to delete @@ -11,4 +11,4 @@ fixes: using below command: ``volume unset [--image-property ] ``. - [Bug 'https://bugs.launchpad.net/python-openstackclient/+bug/1554879'_] + [Bug '1554879 '_] diff --git a/releasenotes/notes/bug-1558677-a85f0c548306ba80.yaml b/releasenotes/notes/bug-1558677-a85f0c548306ba80.yaml deleted file mode 100644 index 15451698f0..0000000000 --- a/releasenotes/notes/bug-1558677-a85f0c548306ba80.yaml +++ /dev/null @@ -1,7 +0,0 @@ ---- -fixes: - - Change the ``--device-id`` option to ``--device`` and the ``--host-id`` - option to ``--host`` for the ``port create`` and ``pot set`` commands. - The original options are deprecated and maintained for backward compatibility - until at least March 2017. - [Bug `1558677 `_] diff --git a/releasenotes/notes/bug-1565112-e0cea9bfbcab954f.yaml b/releasenotes/notes/bug-1565112-e0cea9bfbcab954f.yaml new file mode 100644 index 0000000000..f8976e6372 --- /dev/null +++ b/releasenotes/notes/bug-1565112-e0cea9bfbcab954f.yaml @@ -0,0 +1,6 @@ +--- +features: + - Add global options ``os-cert`` and --os-key`` to support client + certificate/key. Environment variables OS_CERT and OS_KEY, as well + as the ``cert`` and ``key`` values in clouds.yaml may also be used + [Bug `1565112 `_] diff --git a/releasenotes/notes/bug_1565112-e0cea9bfbcab954f.yaml b/releasenotes/notes/bug_1565112-e0cea9bfbcab954f.yaml deleted file mode 100644 index 864b0ac855..0000000000 --- a/releasenotes/notes/bug_1565112-e0cea9bfbcab954f.yaml +++ /dev/null @@ -1,6 +0,0 @@ ---- -features: - - Support client certificate/key. Client certificate/key can be provided - using --os-cert/--os-key options, $OS_CERT/$OS_KEY environment - variables or os-client-config config. - [Bug `1565112 `_] From 29c92f5d24ea7269785e002f7e8ab5563deb4287 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 7 Apr 2016 17:33:22 +0000 Subject: [PATCH 0788/3095] Updated from global requirements Change-Id: I2af3dc17e6f2c4cada96f1ff08627a3b41196f4d --- requirements.txt | 6 +++--- test-requirements.txt | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/requirements.txt b/requirements.txt index 7349d9f74a..b05bdc3330 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,14 +7,14 @@ six>=1.9.0 # MIT Babel>=1.3 # BSD cliff!=1.16.0,!=1.17.0,>=1.15.0 # Apache-2.0 keystoneauth1>=2.1.0 # Apache-2.0 -openstacksdk>=0.8.1 # Apache-2.0 +openstacksdk>=0.8.4 # Apache-2.0 os-client-config>=1.13.1 # Apache-2.0 -oslo.config>=3.7.0 # Apache-2.0 +oslo.config>=3.9.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 oslo.utils>=3.5.0 # Apache-2.0 python-glanceclient>=2.0.0 # Apache-2.0 python-keystoneclient!=1.8.0,!=2.1.0,>=1.6.0 # Apache-2.0 python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0 -python-cinderclient>=1.3.1 # Apache-2.0 +python-cinderclient>=1.6.0 # Apache-2.0 requests!=2.9.0,>=2.8.1 # Apache-2.0 stevedore>=1.5.0 # Apache-2.0 diff --git a/test-requirements.txt b/test-requirements.txt index 39a881c01e..240c7df143 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -9,7 +9,7 @@ fixtures>=1.3.1 # Apache-2.0/BSD mock>=1.2 # BSD oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 -reno>=0.1.1 # Apache2 +reno>=1.6.2 # Apache2 requests-mock>=0.7.0 # Apache-2.0 sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 # BSD os-testr>=0.4.1 # Apache-2.0 @@ -19,15 +19,15 @@ tempest-lib>=0.14.0 # Apache-2.0 osprofiler>=1.1.0 # Apache-2.0 # Install these to generate sphinx autodocs -python-barbicanclient>=3.3.0 # Apache-2.0 +python-barbicanclient>=4.0.0 # Apache-2.0 python-congressclient<2000,>=1.0.0 # Apache-2.0 python-designateclient>=1.5.0 # Apache-2.0 python-heatclient>=0.6.0 # Apache-2.0 python-ironicclient>=1.1.0 # Apache-2.0 -python-ironic-inspector-client>=1.3.0 # Apache-2.0 +python-ironic-inspector-client>=1.5.0 # Apache-2.0 python-mistralclient>=1.0.0 # Apache-2.0 python-muranoclient>=0.8.2 # Apache-2.0 python-saharaclient>=0.13.0 # Apache-2.0 python-searchlightclient>=0.2.0 #Apache-2.0 python-senlinclient>=0.3.0 # Apache-2.0 -python-zaqarclient>=0.3.0 # Apache-2.0 +python-zaqarclient>=1.0.0 # Apache-2.0 From 3485c223ea4b5a7b532df377aabbeca66a3e0e11 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Fri, 8 Apr 2016 00:33:16 +0000 Subject: [PATCH 0789/3095] Updated from global requirements Change-Id: I9fe06c9739c3625abd7cb03ac97c58bb9cdbc5bd --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 240c7df143..99fcbe8d7a 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -5,7 +5,7 @@ hacking<0.11,>=0.10.0 coverage>=3.6 # Apache-2.0 discover # BSD -fixtures>=1.3.1 # Apache-2.0/BSD +fixtures<2.0,>=1.3.1 # Apache-2.0/BSD mock>=1.2 # BSD oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 From a8c085fdd53b4bdb0e22bd5e715f2530eb24ffd5 Mon Sep 17 00:00:00 2001 From: reedip Date: Fri, 8 Apr 2016 10:05:17 +0900 Subject: [PATCH 0790/3095] TrivialFix: Rename provider segment option As per [1], provider-segmentation-id is changed to provider-segment as segments may soon have a "name" support as well. [1]: https://review.openstack.org/#/c/294422/12/doc/source/command-objects/network.rst Change-Id: I74d4366301b41fa181faccbc12cff9530df60353 --- doc/source/command-objects/network.rst | 6 +++--- openstackclient/network/v2/network.py | 6 +++--- openstackclient/tests/network/v2/test_network.py | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/source/command-objects/network.rst b/doc/source/command-objects/network.rst index 9fbc02164c..3d01f25330 100644 --- a/doc/source/command-objects/network.rst +++ b/doc/source/command-objects/network.rst @@ -20,7 +20,7 @@ Create new network [--external [--default | --no-default] | --internal] [--provider-network-type ] [--provider-physical-network ] - [--provider-segmentation-id ] + [--provider-segment ] .. option:: --project @@ -97,9 +97,9 @@ Create new network Name of the physical network over which the virtual network is implemented (Network v2 only) -.. option:: --provider-segmentation-id +.. option:: --provider-segment - VLAN ID for VLAN networks or tunnel-id for GRE/VXLAN networks + VLAN ID for VLAN networks or Tunnel ID for GRE/VXLAN networks (Network v2 only) .. _network_create-name: diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index d3b20c0f3e..ebd5cb63de 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -180,10 +180,10 @@ def update_parser_network(self, parser): help='Name of the physical network over which the virtual ' 'network is implemented') parser.add_argument( - '--provider-segmentation-id', - metavar='', + '--provider-segment', + metavar='', dest='segmentation_id', - help='VLAN ID for VLAN networks or tunnel-id for GRE/VXLAN ' + help='VLAN ID for VLAN networks or Tunnel ID for GRE/VXLAN ' 'networks') return parser diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index 2493362e29..0dec0e2ff8 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -144,7 +144,7 @@ def test_create_all_options(self): "--external", "--default", "--provider-network-type", "vlan", "--provider-physical-network", "physnet1", - "--provider-segmentation-id", "400", + "--provider-segment", "400", self._network.name, ] verifylist = [ From 03d932ea0b3074187bfcdd9c0422a968f5f56c59 Mon Sep 17 00:00:00 2001 From: reedip Date: Fri, 8 Apr 2016 11:41:33 +0900 Subject: [PATCH 0791/3095] Append existing information during subnet set Existing values of --dns-nameserver, --allocation-pool and --houst-routes is currently overwritten when a user executes 'port set', but actually that data should be appended. This patch fixes the issue. Closes-Bug: #1564447 Change-Id: I3dba9afa68d869abb3960b55a6880401a10eebf7 --- openstackclient/network/v2/subnet.py | 6 ++++++ .../tests/network/v2/test_subnet.py | 20 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 10e5859a5f..45e68235b2 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -366,6 +366,12 @@ def take_action(self, parsed_args): if not attrs: msg = "Nothing specified to be set" raise exceptions.CommandError(msg) + if 'dns_nameservers' in attrs: + attrs['dns_nameservers'] += obj.dns_nameservers + if 'host_routes' in attrs: + attrs['host_routes'] += obj.host_routes + if 'allocation_pools' in attrs: + attrs['allocation_pools'] += obj.allocation_pools client.update_subnet(obj, **attrs) return diff --git a/openstackclient/tests/network/v2/test_subnet.py b/openstackclient/tests/network/v2/test_subnet.py index 2535bbe6d8..ede3741615 100644 --- a/openstackclient/tests/network/v2/test_subnet.py +++ b/openstackclient/tests/network/v2/test_subnet.py @@ -536,6 +536,26 @@ def test_set_nothing(self): self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) + def test_append_options(self): + _testsubnet = network_fakes.FakeSubnet.create_one_subnet( + {'dns_nameservers': ["10.0.0.1"]}) + self.network.find_subnet = mock.Mock(return_value=_testsubnet) + arglist = [ + '--dns-nameserver', '10.0.0.2', + _testsubnet.name, + ] + verifylist = [ + ('dns_nameservers', ['10.0.0.2']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + attrs = { + 'dns_nameservers': ['10.0.0.2', '10.0.0.1'], + } + self.network.update_subnet.assert_called_once_with( + _testsubnet, **attrs) + self.assertIsNone(result) + class TestShowSubnet(TestSubnet): # The subnets to be shown From 6b76860c451251fd3c95e1e0e1af12c560bf4043 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Fri, 1 Apr 2016 16:16:08 -0500 Subject: [PATCH 0792/3095] Doc: Fix network command documentation issues Fix the following network command documentation issues: - Fix format of subnet pool positional arguments - Update port set options to match help - Fix network command documentation for Network v2 versus Compute v2 options and commands - Fix subnet command documentation errors and formatting problems in help text Change-Id: I808c2a70ca62eafc9e42d6873539cdd142ffe50c Related-Bug: #1558677 --- doc/source/command-objects/network.rst | 41 +++++++++++++------ doc/source/command-objects/port.rst | 12 +++--- doc/source/command-objects/subnet-pool.rst | 4 +- doc/source/command-objects/subnet.rst | 47 +++++++++++----------- openstackclient/network/v2/subnet.py | 17 ++++---- 5 files changed, 67 insertions(+), 54 deletions(-) diff --git a/doc/source/command-objects/network.rst b/doc/source/command-objects/network.rst index 3d01f25330..029e82e367 100644 --- a/doc/source/command-objects/network.rst +++ b/doc/source/command-objects/network.rst @@ -26,23 +26,27 @@ Create new network .. option:: --project Owner's project (name or ID) - (Network v2 only) + + *Network version 2 only* .. option:: --project-domain Domain the project belongs to (name or ID). This can be used in case collisions between project names exist. - (Network v2 only) + + *Network version 2 only* .. option:: --enable Enable network (default) - (Network v2 only) + + *Network version 2 only* .. option:: --disable Disable network - (Network v2 only) + + *Network version 2 only* .. option:: --share @@ -56,51 +60,60 @@ Create new network Availability Zone in which to create this network (requires the Network Availability Zone extension, this option can be repeated). - (Network v2 only) + + *Network version 2 only* .. option:: --subnet IPv4 subnet for fixed IPs (in CIDR notation) - (Compute v2 network only) + + *Compute version 2 only* .. option:: --external Set this network as an external network. Requires the "external-net" extension to be enabled. - (Network v2 only) + + *Network version 2 only* .. option:: --internal Set this network as an internal network (default) - (Network v2 only) + + *Network version 2 only* .. option:: --default Specify if this network should be used as the default external network - (Network v2 only) + + *Network version 2 only* .. option:: --no-default Do not use the network as the default external network. By default, no network is set as an external network. - (Network v2 only) + + *Network version 2 only* .. option:: --provider-network-type The physical mechanism by which the virtual network is implemented. The supported options are: flat, gre, local, vlan, vxlan - (Network v2 only) + + *Network version 2 only* .. option:: --provider-physical-network Name of the physical network over which the virtual network is implemented - (Network v2 only) + + *Network version 2 only* .. option:: --provider-segment VLAN ID for VLAN networks or Tunnel ID for GRE/VXLAN networks - (Network v2 only) + + *Network version 2 only* .. _network_create-name: .. describe:: @@ -148,6 +161,8 @@ network set Set network properties +*Network version 2 only* + .. program:: network set .. code:: bash diff --git a/doc/source/command-objects/port.rst b/doc/source/command-objects/port.rst index 2e83eea1d1..c229524936 100644 --- a/doc/source/command-objects/port.rst +++ b/doc/source/command-objects/port.rst @@ -124,11 +124,11 @@ Set port properties os port set [--fixed-ip subnet=,ip-address= | --no-fixed-ip] - [--device-id ] + [--device ] [--device-owner ] [--vnic-type ] [--binding-profile | --no-binding-profile] - [--host-id ] + [--host ] [--enable | --disable] [--name ] @@ -143,9 +143,9 @@ Set port properties Clear existing information of fixed-ips -.. option:: --device-id +.. option:: --device - Device ID of this port + Port device ID .. option:: --device-owner @@ -165,9 +165,9 @@ Set port properties Clear existing information of binding:profile -.. option:: --host-id +.. option:: --host - The ID of the host where the port is allocated + Allocate port on host ```` (ID only) .. option:: --enable diff --git a/doc/source/command-objects/subnet-pool.rst b/doc/source/command-objects/subnet-pool.rst index cb86e2273c..5eff2dcd5b 100644 --- a/doc/source/command-objects/subnet-pool.rst +++ b/doc/source/command-objects/subnet-pool.rst @@ -47,7 +47,7 @@ Create subnet pool collisions between project names exist. .. _subnet_pool_create-name: - .. describe:: +.. describe:: Name of the new subnet pool @@ -120,7 +120,7 @@ Set subnet pool properties Set subnet pool maximum prefix length .. _subnet_pool_set-subnet-pool: - .. describe:: +.. describe:: Subnet pool to modify (name or ID) diff --git a/doc/source/command-objects/subnet.rst b/doc/source/command-objects/subnet.rst index e81a335998..e5026f5a02 100644 --- a/doc/source/command-objects/subnet.rst +++ b/doc/source/command-objects/subnet.rst @@ -42,7 +42,7 @@ Create new subnet .. option:: --use-default-subnet-pool - Use default subnet pool for --ip-version + Use default subnet pool for ``--ip-version`` .. option:: --prefix-length @@ -51,12 +51,12 @@ Create new subnet .. option:: --subnet-range Subnet range in CIDR notation - (required if --subnet-pool is not specified, optional otherwise) + (required if ``--subnet-pool`` is not specified, optional otherwise) .. option:: --allocation-pool start=,end= Allocation pool IP addresses for this subnet e.g.: - start=192.168.199.2,end=192.168.199.254 (This option can be repeated) + ``start=192.168.199.2,end=192.168.199.254`` (This option can be repeated) .. option:: --dhcp @@ -73,31 +73,30 @@ Create new subnet .. option:: --gateway Specify a gateway for the subnet. The three options are: - : Specific IP address to use as the gateway - 'auto': Gateway address should automatically be chosen from - within the subnet itself - 'none': This subnet will not use a gateway - e.g.: --gateway 192.168.9.1, --gateway auto, --gateway none - (default is 'auto') + : Specific IP address to use as the gateway, + 'auto': Gateway address should automatically be chosen from + within the subnet itself, 'none': This subnet will not use + a gateway, e.g.: ``--gateway 192.168.9.1``, ``--gateway auto``, + ``--gateway none`` (default is 'auto') .. option:: --host-route destination=,gateway= Additional route for this subnet e.g.: - destination=10.10.0.0/16,gateway=192.168.71.254 - destination: destination subnet (in CIDR notation) - gateway: nexthop IP address - (This option can be repeated) + ``destination=10.10.0.0/16,gateway=192.168.71.254`` + destination: destination subnet (in CIDR notation) + gateway: nexthop IP address + (This option can be repeated) .. option:: --ip-version {4,6} IP version (default is 4). Note that when subnet pool is specified, - IP version is determined from the subnet pool and this option - is ignored. + IP version is determined from the subnet pool and this option + is ignored. .. option:: --ipv6-ra-mode {dhcpv6-stateful,dhcpv6-stateless,slaac} IPv6 RA (Router Advertisement) mode, - valid modes: [dhcpv6-stateful, dhcpv6-stateless, slaac] + valid modes: [dhcpv6-stateful, dhcpv6-stateless, slaac] .. option:: --ipv6-address-mode {dhcpv6-stateful,dhcpv6-stateless,slaac} @@ -163,7 +162,7 @@ Set subnet properties .. option:: --allocation-pool start=,end= Allocation pool IP addresses for this subnet e.g.: - start=192.168.199.2,end=192.168.199.254 (This option can be repeated) + ``start=192.168.199.2,end=192.168.199.254`` (This option can be repeated) .. option:: --dhcp @@ -180,17 +179,17 @@ Set subnet properties .. option:: --gateway Specify a gateway for the subnet. The options are: - : Specific IP address to use as the gateway - 'none': This subnet will not use a gateway - e.g.: --gateway 192.168.9.1, --gateway none + : Specific IP address to use as the gateway, + 'none': This subnet will not use a gateway, + e.g.: ``--gateway 192.168.9.1``, ``--gateway none`` .. option:: --host-route destination=,gateway= Additional route for this subnet e.g.: - destination=10.10.0.0/16,gateway=192.168.71.254 - destination: destination subnet (in CIDR notation) - gateway: nexthop IP address - (This option can be repeated) + ``destination=10.10.0.0/16,gateway=192.168.71.254`` + destination: destination subnet (in CIDR notation) + gateway: nexthop IP address + (This option can be repeated) .. option:: --name diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 10e5859a5f..eb96bb1f5d 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -197,7 +197,7 @@ def get_parser(self, prog_name): parser.add_argument( '--prefix-length', metavar='', - help='Prefix length for subnet allocation from subnetpool', + help='Prefix length for subnet allocation from subnet pool', ) parser.add_argument( '--subnet-range', @@ -223,12 +223,11 @@ def get_parser(self, prog_name): metavar='', default='auto', help="Specify a gateway for the subnet. The three options are: " - " : Specific IP address to use as the gateway " - " 'auto': Gateway address should automatically be " - " chosen from within the subnet itself " - " 'none': This subnet will not use a gateway " - "e.g.: --gateway 192.168.9.1, --gateway auto, --gateway none" - "(default is 'auto')", + ": Specific IP address to use as the gateway, " + "'auto': Gateway address should automatically be chosen from " + "within the subnet itself, 'none': This subnet will not use " + "a gateway, e.g.: --gateway 192.168.9.1, --gateway auto, " + "--gateway none (default is 'auto')", ) parser.add_argument( '--ip-version', @@ -351,8 +350,8 @@ def get_parser(self, prog_name): '--gateway', metavar='', help="Specify a gateway for the subnet. The options are: " - " : Specific IP address to use as the gateway " - " 'none': This subnet will not use a gateway " + ": Specific IP address to use as the gateway, " + "'none': This subnet will not use a gateway, " "e.g.: --gateway 192.168.9.1, --gateway none" ) _get_common_parse_arguments(parser) From b5f10f43eb9fd1a046a3e80db09d8bc8c350c218 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Thu, 7 Apr 2016 16:35:38 -0500 Subject: [PATCH 0793/3095] Fix SSL/TLS verification for network commands The network commands ignored the --insecure and --os-cacert options and OS_CACERT environment variable which prevented them from properly completing SSL/TLS verification. This resulted in the network commands failing with "An SSL error occurred." Change-Id: I15167631ef58335e1476c16b828b079e3b0f13c1 Closes-Bug: #1560157 --- openstackclient/network/client.py | 3 ++- releasenotes/notes/bug-1560157-bce572f58b43efa1.yaml | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/bug-1560157-bce572f58b43efa1.yaml diff --git a/openstackclient/network/client.py b/openstackclient/network/client.py index 7714c52504..dca9efc48a 100644 --- a/openstackclient/network/client.py +++ b/openstackclient/network/client.py @@ -31,7 +31,8 @@ def make_client(instance): """Returns a network proxy""" - conn = connection.Connection(authenticator=instance.session.auth) + conn = connection.Connection(authenticator=instance.session.auth, + verify=instance.session.verify) LOG.debug('Connection: %s', conn) LOG.debug('Network client initialized using OpenStack SDK: %s', conn.network) diff --git a/releasenotes/notes/bug-1560157-bce572f58b43efa1.yaml b/releasenotes/notes/bug-1560157-bce572f58b43efa1.yaml new file mode 100644 index 0000000000..e5c394bc23 --- /dev/null +++ b/releasenotes/notes/bug-1560157-bce572f58b43efa1.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - Fixed SSL/TLS verification for Network v2 commands. The commands + were ignoring the ``--insecure`` and ``--os-cacert`` options and + the ``OS_CACERT`` environment variable which caused them to fail + with ``An SSL error occurred.`` when authenticating using SSL/TLS. + [Bug `1560157 `_] From f3f8c20a8c7b89ae4fbf888c0b78dc1f5ea4857c Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Fri, 8 Apr 2016 10:33:43 -0400 Subject: [PATCH 0794/3095] use correct manager for volume snapshots the snapshot manager is volume_snapshots, not snapshots. Closes-Bug: #1567895 Change-Id: I12c0238fc04507c97e92089fda3bec816171a973 --- openstackclient/volume/v2/volume.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index 5b7511e8ea..5a739f61cc 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -110,7 +110,7 @@ def take_action(self, parsed_args): snapshot = None if parsed_args.snapshot: snapshot = utils.find_resource( - volume_client.snapshots, + volume_client.volume_snapshots, parsed_args.snapshot).id project = None From 0ea80a92f07054a9f50f7b4a166f4d70eeda08b8 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 8 Apr 2016 19:33:11 -0500 Subject: [PATCH 0795/3095] Fix pep8 fail that crept in Not sure how, but this crept in via https://review.openstack.org/#/c/281691/ Change-Id: I919276ee5204277dbc5e96f4de4b98381142423a --- openstackclient/network/v2/router.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 1fe918c096..73075de09a 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -339,4 +339,4 @@ def take_action(self, parsed_args): obj = client.find_router(parsed_args.router, ignore_missing=False) columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) - return columns, data \ No newline at end of file + return columns, data From b3649a54cd59dc069c76cbbcd876a1610aee00c0 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Thu, 24 Mar 2016 19:22:07 +0800 Subject: [PATCH 0796/3095] Add --address-scope option "subnet pool create/set" This patch adds --address-scope option to "subnet pool create/set" commands, and --no-address-scope option to "subnet pool set" command to clear the address scope setting. Change-Id: Ie2c370a50b52574fa6ec268083ad013b7544361e Partial-Bug: #1544586 Partial-Bug: #1544591 --- doc/source/command-objects/subnet-pool.rst | 16 +++ openstackclient/network/v2/subnet_pool.py | 30 +++++- openstackclient/tests/network/v2/fakes.py | 37 +++++++ .../tests/network/v2/test_subnet_pool.py | 101 ++++++++++++++++-- 4 files changed, 176 insertions(+), 8 deletions(-) diff --git a/doc/source/command-objects/subnet-pool.rst b/doc/source/command-objects/subnet-pool.rst index cb86e2273c..f9255f795e 100644 --- a/doc/source/command-objects/subnet-pool.rst +++ b/doc/source/command-objects/subnet-pool.rst @@ -18,6 +18,7 @@ Create subnet pool [--min-prefix-length ] [--max-prefix-length ] [--project [--project-domain ]] + [--address-scope ] .. option:: --pool-prefix @@ -46,6 +47,11 @@ Create subnet pool Domain the project belongs to (name or ID). This can be used in case collisions between project names exist. +.. option:: --address-scope + + Set address scope associated with the subnet pool (name or ID). + Prefixes must be unique across address scopes. + .. _subnet_pool_create-name: .. describe:: @@ -96,6 +102,7 @@ Set subnet pool properties [--default-prefix-length ] [--min-prefix-length ] [--max-prefix-length ] + [--address-scope | --no-address-scope] .. option:: --name @@ -119,6 +126,15 @@ Set subnet pool properties Set subnet pool maximum prefix length +.. option:: --address-scope + + Set address scope associated with the subnet pool (name or ID). + Prefixes must be unique across address scopes. + +.. option:: --no-address-scope + + Remove address scope associated with the subnet pool + .. _subnet_pool_set-subnet-pool: .. describe:: diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index 6b6fc090f9..497b7f7fba 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -35,6 +35,8 @@ def _get_columns(item): def _get_attrs(client_manager, parsed_args): attrs = {} + network_client = client_manager.network + if parsed_args.name is not None: attrs['name'] = str(parsed_args.name) if parsed_args.prefixes is not None: @@ -46,6 +48,12 @@ def _get_attrs(client_manager, parsed_args): if parsed_args.max_prefix_length is not None: attrs['max_prefixlen'] = parsed_args.max_prefix_length + if parsed_args.address_scope is not None: + attrs['address_scope_id'] = network_client.find_address_scope( + parsed_args.address_scope, ignore_missing=False).id + if 'no_address_scope' in parsed_args and parsed_args.no_address_scope: + attrs['address_scope_id'] = None + # "subnet pool set" command doesn't support setting project. if 'project' in parsed_args and parsed_args.project is not None: identity_client = client_manager.identity @@ -105,7 +113,13 @@ def get_parser(self, prog_name): help="Owner's project (name or ID)", ) identity_common.add_project_domain_option_to_parser(parser) - + parser.add_argument( + '--address-scope', + metavar='', + help="Set address scope associated with the subnet pool " + "(name or ID). Prefixes must be unique across address " + "scopes.", + ) return parser def take_action(self, parsed_args): @@ -204,7 +218,19 @@ def get_parser(self, prog_name): help='Set subnet pool name', ) _add_prefix_options(parser) - + address_scope_group = parser.add_mutually_exclusive_group() + address_scope_group.add_argument( + '--address-scope', + metavar='', + help="Set address scope associated with the subnet pool " + "(name or ID). Prefixes must be unique across address " + "scopes.", + ) + address_scope_group.add_argument( + '--no-address-scope', + action='store_true', + help="Remove address scope associated with the subnet pool", + ) return parser def take_action(self, parsed_args): diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 7f89ef7a97..409b18f2dd 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -71,6 +71,43 @@ def setUp(self): ) +class FakeAddressScope(object): + """Fake one or more address scopes.""" + + @staticmethod + def create_one_address_scope(attrs=None): + """Create a fake address scope. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object with name, id, etc. + """ + if attrs is None: + attrs = {} + + # Set default attributes. + address_scope_attrs = { + 'name': 'address-scope-name-' + uuid.uuid4().hex, + 'id': 'address-scope-id-' + uuid.uuid4().hex, + 'tenant_id': 'project-id-' + uuid.uuid4().hex, + 'shared': False, + 'ip_version': 4, + } + + # Overwrite default attributes. + address_scope_attrs.update(attrs) + + address_scope = fakes.FakeResource( + info=copy.deepcopy(address_scope_attrs), + loaded=True) + + # Set attributes with special mapping in OpenStack SDK. + address_scope.project_id = address_scope_attrs['tenant_id'] + + return address_scope + + class FakeAvailabilityZone(object): """Fake one or more network availability zones (AZs).""" diff --git a/openstackclient/tests/network/v2/test_subnet_pool.py b/openstackclient/tests/network/v2/test_subnet_pool.py index 093e26c67e..acdae0247c 100644 --- a/openstackclient/tests/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/network/v2/test_subnet_pool.py @@ -38,6 +38,8 @@ class TestCreateSubnetPool(TestSubnetPool): # The new subnet pool to create. _subnet_pool = network_fakes.FakeSubnetPool.create_one_subnet_pool() + _address_scope = network_fakes.FakeAddressScope.create_one_address_scope() + columns = ( 'address_scope_id', 'default_prefixlen', @@ -76,6 +78,9 @@ def setUp(self): # Get the command object to test self.cmd = subnet_pool.CreateSubnetPool(self.app, self.namespace) + self.network.find_address_scope = mock.Mock( + return_value=self._address_scope) + # Set identity client. And get a shortcut to Identity client. identity_client = identity_fakes_v3.FakeIdentityv3Client( endpoint=fakes.AUTH_URL, @@ -193,6 +198,29 @@ def test_create_project_domain(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_create_address_scope_option(self): + arglist = [ + '--pool-prefix', '10.0.10.0/24', + '--address-scope', self._address_scope.id, + self._subnet_pool.name, + ] + verifylist = [ + ('prefixes', ['10.0.10.0/24']), + ('address_scope', self._address_scope.id), + ('name', self._subnet_pool.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_subnet_pool.assert_called_once_with(**{ + 'prefixes': ['10.0.10.0/24'], + 'address_scope_id': self._address_scope.id, + 'name': self._subnet_pool.name, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + class TestDeleteSubnetPool(TestSubnetPool): @@ -301,6 +329,8 @@ class TestSetSubnetPool(TestSubnetPool): # The subnet_pool to set. _subnet_pool = network_fakes.FakeSubnetPool.create_one_subnet_pool() + _address_scope = network_fakes.FakeAddressScope.create_one_address_scope() + def setUp(self): super(TestSetSubnetPool, self).setUp() @@ -309,21 +339,24 @@ def setUp(self): self.network.find_subnet_pool = mock.Mock( return_value=self._subnet_pool) + self.network.find_address_scope = mock.Mock( + return_value=self._address_scope) + # Get the command object to test self.cmd = subnet_pool.SetSubnetPool(self.app, self.namespace) def test_set_this(self): arglist = [ - self._subnet_pool.name, '--name', 'noob', '--default-prefix-length', '8', '--min-prefix-length', '8', + self._subnet_pool.name, ] verifylist = [ - ('subnet_pool', self._subnet_pool.name), ('name', 'noob'), ('default_prefix_length', '8'), ('min_prefix_length', '8'), + ('subnet_pool', self._subnet_pool.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -340,15 +373,15 @@ def test_set_this(self): def test_set_that(self): arglist = [ - self._subnet_pool.name, '--pool-prefix', '10.0.1.0/24', '--pool-prefix', '10.0.2.0/24', '--max-prefix-length', '16', + self._subnet_pool.name, ] verifylist = [ - ('subnet_pool', self._subnet_pool.name), ('prefixes', ['10.0.1.0/24', '10.0.2.0/24']), ('max_prefix_length', '16'), + ('subnet_pool', self._subnet_pool.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -374,17 +407,73 @@ def test_set_nothing(self): def test_set_len_negative(self): arglist = [ - self._subnet_pool.name, '--max-prefix-length', '-16', + self._subnet_pool.name, ] verifylist = [ - ('subnet_pool', self._subnet_pool.name), ('max_prefix_length', '-16'), + ('subnet_pool', self._subnet_pool.name), ] self.assertRaises(argparse.ArgumentTypeError, self.check_parser, self.cmd, arglist, verifylist) + def test_set_address_scope(self): + arglist = [ + '--address-scope', self._address_scope.id, + self._subnet_pool.name, + ] + verifylist = [ + ('address_scope', self._address_scope.id), + ('subnet_pool', self._subnet_pool.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + attrs = { + 'address_scope_id': self._address_scope.id, + } + self.network.update_subnet_pool.assert_called_once_with( + self._subnet_pool, **attrs) + self.assertIsNone(result) + + def test_set_no_address_scope(self): + arglist = [ + '--no-address-scope', + self._subnet_pool.name, + ] + verifylist = [ + ('no_address_scope', True), + ('subnet_pool', self._subnet_pool.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + attrs = { + 'address_scope_id': None, + } + self.network.update_subnet_pool.assert_called_once_with( + self._subnet_pool, **attrs) + self.assertIsNone(result) + + def test_set_no_address_scope_conflict(self): + arglist = [ + '--address-scope', self._address_scope.id, + '--no-address-scope', + self._subnet_pool.name, + ] + verifylist = [ + ('address_scope', self._address_scope.id), + ('no_address_scope', True), + ('subnet_pool', self._subnet_pool.name), + ] + + # Exclusive arguments will conflict here. + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + class TestShowSubnetPool(TestSubnetPool): From 827be8fb8cf4508c7ac0488652c4e0e93ec443f5 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Mon, 11 Apr 2016 16:32:58 +0800 Subject: [PATCH 0797/3095] Move keys() methods in each resource class to FakeResource FakeXXX classes in network don't need to fake any method, except keys(). But keys() can be put in FakeResource since it just returns all attributes in _info. This patch moves removes all unnecessary fake methods code, moves keys() method to FakeResource. This patch also finds out some missing attributes in network and router tests. Change-Id: I799822c8715b9ac4f95b98f8350f196757d79d3e --- openstackclient/tests/fakes.py | 3 + openstackclient/tests/network/v2/fakes.py | 191 +++--------------- .../tests/network/v2/test_network.py | 12 ++ .../tests/network/v2/test_router.py | 23 ++- 4 files changed, 63 insertions(+), 166 deletions(-) diff --git a/openstackclient/tests/fakes.py b/openstackclient/tests/fakes.py index 9fdcc7e923..f0cebb06d3 100644 --- a/openstackclient/tests/fakes.py +++ b/openstackclient/tests/fakes.py @@ -183,6 +183,9 @@ def __repr__(self): info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys) return "<%s %s>" % (self.__class__.__name__, info) + def keys(self): + return self._info.keys() + class FakeResponse(requests.Response): diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 7f89ef7a97..815e677daf 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -75,13 +75,11 @@ class FakeAvailabilityZone(object): """Fake one or more network availability zones (AZs).""" @staticmethod - def create_one_availability_zone(attrs={}, methods={}): + def create_one_availability_zone(attrs={}): """Create a fake AZ. :param Dictionary attrs: A dictionary with all attributes - :param Dictionary methods: - A dictionary with all methods :return: A FakeResource object with name, state, etc. """ @@ -97,18 +95,15 @@ def create_one_availability_zone(attrs={}, methods={}): availability_zone = fakes.FakeResource( info=copy.deepcopy(availability_zone), - methods=methods, loaded=True) return availability_zone @staticmethod - def create_availability_zones(attrs={}, methods={}, count=2): + def create_availability_zones(attrs={}, count=2): """Create multiple fake AZs. :param Dictionary attrs: A dictionary with all attributes - :param Dictionary methods: - A dictionary with all methods :param int count: The number of AZs to fake :return: @@ -117,8 +112,7 @@ def create_availability_zones(attrs={}, methods={}, count=2): availability_zones = [] for i in range(0, count): availability_zone = \ - FakeAvailabilityZone.create_one_availability_zone( - attrs, methods) + FakeAvailabilityZone.create_one_availability_zone(attrs) availability_zones.append(availability_zone) return availability_zones @@ -128,13 +122,11 @@ class FakeNetwork(object): """Fake one or more networks.""" @staticmethod - def create_one_network(attrs={}, methods={}): + def create_one_network(attrs={}): """Create a fake network. :param Dictionary attrs: A dictionary with all attributes - :param Dictionary methods: - A dictionary with all methods :return: A FakeResource object, with id, name, admin_state_up, router_external, status, subnets, tenant_id @@ -158,18 +150,7 @@ def create_one_network(attrs={}, methods={}): # Overwrite default attributes. network_attrs.update(attrs) - # Set default methods. - network_methods = { - 'keys': ['id', 'name', 'admin_state_up', 'router_external', - 'status', 'subnets', 'tenant_id', 'availability_zones', - 'availability_zone_hints', 'is_default'], - } - - # Overwrite default methods. - network_methods.update(methods) - network = fakes.FakeResource(info=copy.deepcopy(network_attrs), - methods=copy.deepcopy(network_methods), loaded=True) # Set attributes with special mapping in OpenStack SDK. @@ -178,13 +159,11 @@ def create_one_network(attrs={}, methods={}): return network @staticmethod - def create_networks(attrs={}, methods={}, count=2): + def create_networks(attrs={}, count=2): """Create multiple fake networks. :param Dictionary attrs: A dictionary with all attributes - :param Dictionary methods: - A dictionary with all methods :param int count: The number of networks to fake :return: @@ -192,7 +171,7 @@ def create_networks(attrs={}, methods={}, count=2): """ networks = [] for i in range(0, count): - networks.append(FakeNetwork.create_one_network(attrs, methods)) + networks.append(FakeNetwork.create_one_network(attrs)) return networks @@ -220,7 +199,7 @@ class FakePort(object): """Fake one or more ports.""" @staticmethod - def create_one_port(attrs={}, methods={}): + def create_one_port(attrs={}): """Create a fake port. :param Dictionary attrs: @@ -258,23 +237,7 @@ def create_one_port(attrs={}, methods={}): # Overwrite default attributes. port_attrs.update(attrs) - # Set default methods. - port_methods = { - 'keys': ['admin_state_up', 'allowed_address_pairs', - 'binding:host_id', 'binding:profile', - 'binding:vif_details', 'binding:vif_type', - 'binding:vnic_type', 'device_id', 'device_owner', - 'dns_assignment', 'dns_name', 'extra_dhcp_opts', - 'fixed_ips', 'id', 'mac_address', 'name', - 'network_id', 'port_security_enabled', - 'security_groups', 'status', 'tenant_id'], - } - - # Overwrite default methods. - port_methods.update(methods) - port = fakes.FakeResource(info=copy.deepcopy(port_attrs), - methods=copy.deepcopy(port_methods), loaded=True) # Set attributes with special mappings in OpenStack SDK. @@ -288,13 +251,11 @@ def create_one_port(attrs={}, methods={}): return port @staticmethod - def create_ports(attrs={}, methods={}, count=2): + def create_ports(attrs={}, count=2): """Create multiple fake ports. :param Dictionary attrs: A dictionary with all attributes - :param Dictionary methods: - A dictionary with all methods :param int count: The number of ports to fake :return: @@ -302,7 +263,7 @@ def create_ports(attrs={}, methods={}, count=2): """ ports = [] for i in range(0, count): - ports.append(FakePort.create_one_port(attrs, methods)) + ports.append(FakePort.create_one_port(attrs)) return ports @@ -330,13 +291,11 @@ class FakeRouter(object): """Fake one or more routers.""" @staticmethod - def create_one_router(attrs={}, methods={}): + def create_one_router(attrs={}): """Create a fake router. :param Dictionary attrs: A dictionary with all attributes - :param Dictionary methods: - A dictionary with all methods :return: A FakeResource object, with id, name, admin_state_up, status, tenant_id @@ -359,17 +318,7 @@ def create_one_router(attrs={}, methods={}): # Overwrite default attributes. router_attrs.update(attrs) - # Set default methods. - router_methods = { - 'keys': ['id', 'name', 'admin_state_up', 'distributed', 'ha', - 'tenant_id'], - } - - # Overwrite default methods. - router_methods.update(methods) - router = fakes.FakeResource(info=copy.deepcopy(router_attrs), - methods=copy.deepcopy(router_methods), loaded=True) # Set attributes with special mapping in OpenStack SDK. @@ -378,13 +327,11 @@ def create_one_router(attrs={}, methods={}): return router @staticmethod - def create_routers(attrs={}, methods={}, count=2): + def create_routers(attrs={}, count=2): """Create multiple fake routers. :param Dictionary attrs: A dictionary with all attributes - :param Dictionary methods: - A dictionary with all methods :param int count: The number of routers to fake :return: @@ -392,7 +339,7 @@ def create_routers(attrs={}, methods={}, count=2): """ routers = [] for i in range(0, count): - routers.append(FakeRouter.create_one_router(attrs, methods)) + routers.append(FakeRouter.create_one_router(attrs)) return routers @@ -420,20 +367,16 @@ class FakeSecurityGroup(object): """Fake one or more security groups.""" @staticmethod - def create_one_security_group(attrs=None, methods=None): + def create_one_security_group(attrs=None): """Create a fake security group. :param Dictionary attrs: A dictionary with all attributes - :param Dictionary methods: - A dictionary with all methods :return: A FakeResource object, with id, name, etc. """ if attrs is None: attrs = {} - if methods is None: - methods = {} # Set default attributes. security_group_attrs = { @@ -447,18 +390,8 @@ def create_one_security_group(attrs=None, methods=None): # Overwrite default attributes. security_group_attrs.update(attrs) - # Set default methods. - security_group_methods = { - 'keys': ['id', 'name', 'description', 'tenant_id', - 'security_group_rules'], - } - - # Overwrite default methods. - security_group_methods.update(methods) - security_group = fakes.FakeResource( info=copy.deepcopy(security_group_attrs), - methods=copy.deepcopy(security_group_methods), loaded=True) # Set attributes with special mapping in OpenStack SDK. @@ -467,13 +400,11 @@ def create_one_security_group(attrs=None, methods=None): return security_group @staticmethod - def create_security_groups(attrs=None, methods=None, count=2): + def create_security_groups(attrs=None, count=2): """Create multiple fake security groups. :param Dictionary attrs: A dictionary with all attributes - :param Dictionary methods: - A dictionary with all methods :param int count: The number of security groups to fake :return: @@ -482,7 +413,7 @@ def create_security_groups(attrs=None, methods=None, count=2): security_groups = [] for i in range(0, count): security_groups.append( - FakeSecurityGroup.create_one_security_group(attrs, methods)) + FakeSecurityGroup.create_one_security_group(attrs)) return security_groups @@ -491,20 +422,16 @@ class FakeSecurityGroupRule(object): """Fake one or more security group rules.""" @staticmethod - def create_one_security_group_rule(attrs=None, methods=None): + def create_one_security_group_rule(attrs=None): """Create a fake security group rule. :param Dictionary attrs: A dictionary with all attributes - :param Dictionary methods: - A dictionary with all methods :return: A FakeResource object, with id, etc. """ if attrs is None: attrs = {} - if methods is None: - methods = {} # Set default attributes. security_group_rule_attrs = { @@ -523,19 +450,8 @@ def create_one_security_group_rule(attrs=None, methods=None): # Overwrite default attributes. security_group_rule_attrs.update(attrs) - # Set default methods. - security_group_rule_methods = { - 'keys': ['direction', 'ethertype', 'id', 'port_range_max', - 'port_range_min', 'protocol', 'remote_group_id', - 'remote_ip_prefix', 'security_group_id', 'tenant_id'], - } - - # Overwrite default methods. - security_group_rule_methods.update(methods) - security_group_rule = fakes.FakeResource( info=copy.deepcopy(security_group_rule_attrs), - methods=copy.deepcopy(security_group_rule_methods), loaded=True) # Set attributes with special mapping in OpenStack SDK. @@ -544,13 +460,11 @@ def create_one_security_group_rule(attrs=None, methods=None): return security_group_rule @staticmethod - def create_security_group_rules(attrs=None, methods=None, count=2): + def create_security_group_rules(attrs=None, count=2): """Create multiple fake security group rules. :param Dictionary attrs: A dictionary with all attributes - :param Dictionary methods: - A dictionary with all methods :param int count: The number of security group rules to fake :return: @@ -559,8 +473,7 @@ def create_security_group_rules(attrs=None, methods=None, count=2): security_group_rules = [] for i in range(0, count): security_group_rules.append( - FakeSecurityGroupRule.create_one_security_group_rule( - attrs, methods)) + FakeSecurityGroupRule.create_one_security_group_rule(attrs)) return security_group_rules @@ -569,13 +482,11 @@ class FakeSubnet(object): """Fake one or more subnets.""" @staticmethod - def create_one_subnet(attrs={}, methods={}): + def create_one_subnet(attrs={}): """Create a fake subnet. :param Dictionary attrs: A dictionary with all attributes - :param Dictionary methods: - A dictionary with all methods :return: A FakeResource object faking the subnet """ @@ -601,19 +512,7 @@ def create_one_subnet(attrs={}, methods={}): # Overwrite default attributes. subnet_attrs.update(attrs) - # Set default methods. - subnet_methods = { - 'keys': ['id', 'name', 'network_id', 'cidr', 'enable_dhcp', - 'allocation_pools', 'dns_nameservers', 'gateway_ip', - 'host_routes', 'ip_version', 'tenant_id', - 'ipv6_address_mode', 'ipv6_ra_mode', 'subnetpool_id'] - } - - # Overwrite default methods. - subnet_methods.update(methods) - subnet = fakes.FakeResource(info=copy.deepcopy(subnet_attrs), - methods=copy.deepcopy(subnet_methods), loaded=True) # Set attributes with special mappings in OpenStack SDK. subnet.project_id = subnet_attrs['tenant_id'] @@ -621,13 +520,11 @@ def create_one_subnet(attrs={}, methods={}): return subnet @staticmethod - def create_subnets(attrs={}, methods={}, count=2): + def create_subnets(attrs={}, count=2): """Create multiple fake subnets. :param Dictionary attrs: A dictionary with all attributes - :param Dictionary methods: - A dictionary with all methods :param int count: The number of subnets to fake :return: @@ -635,7 +532,7 @@ def create_subnets(attrs={}, methods={}, count=2): """ subnets = [] for i in range(0, count): - subnets.append(FakeSubnet.create_one_subnet(attrs, methods)) + subnets.append(FakeSubnet.create_one_subnet(attrs)) return subnets @@ -644,13 +541,11 @@ class FakeFloatingIP(object): """Fake one or more floating ip.""" @staticmethod - def create_one_floating_ip(attrs={}, methods={}): + def create_one_floating_ip(attrs={}): """Create a fake floating ip. :param Dictionary attrs: A dictionary with all attributes - :param Dictionary methods: - A dictionary with all methods :return: A FakeResource object, with id, ip, and so on """ @@ -671,19 +566,8 @@ def create_one_floating_ip(attrs={}, methods={}): # Overwrite default attributes. floating_ip_attrs.update(attrs) - # Set default methods. - floating_ip_methods = { - 'keys': ['id', 'floating_ip_address', 'fixed_ip_address', - 'dns_domain', 'dns_name', 'status', 'router_id', - 'floating_network_id', 'port_id', 'tenant_id'] - } - - # Overwrite default methods. - floating_ip_methods.update(methods) - floating_ip = fakes.FakeResource( info=copy.deepcopy(floating_ip_attrs), - methods=copy.deepcopy(floating_ip_methods), loaded=True ) @@ -693,13 +577,11 @@ def create_one_floating_ip(attrs={}, methods={}): return floating_ip @staticmethod - def create_floating_ips(attrs={}, methods={}, count=2): + def create_floating_ips(attrs={}, count=2): """Create multiple fake floating ips. :param Dictionary attrs: A dictionary with all attributes - :param Dictionary methods: - A dictionary with all methods :param int count: The number of floating ips to fake :return: @@ -707,10 +589,7 @@ def create_floating_ips(attrs={}, methods={}, count=2): """ floating_ips = [] for i in range(0, count): - floating_ips.append(FakeFloatingIP.create_one_floating_ip( - attrs, - methods - )) + floating_ips.append(FakeFloatingIP.create_one_floating_ip(attrs)) return floating_ips @staticmethod @@ -737,13 +616,11 @@ class FakeSubnetPool(object): """Fake one or more subnet pools.""" @staticmethod - def create_one_subnet_pool(attrs={}, methods={}): + def create_one_subnet_pool(attrs={}): """Create a fake subnet pool. :param Dictionary attrs: A dictionary with all attributes - :param Dictionary methods: - A dictionary with all methods :return: A FakeResource object faking the subnet pool """ @@ -766,20 +643,8 @@ def create_one_subnet_pool(attrs={}, methods={}): # Overwrite default attributes. subnet_pool_attrs.update(attrs) - # Set default methods. - subnet_pool_methods = { - 'keys': ['id', 'name', 'prefixes', 'default_prefixlen', - 'address_scope_id', 'tenant_id', 'is_default', - 'shared', 'max_prefixlen', 'min_prefixlen', - 'default_quota', 'ip_version'] - } - - # Overwrite default methods. - subnet_pool_methods.update(methods) - subnet_pool = fakes.FakeResource( info=copy.deepcopy(subnet_pool_attrs), - methods=copy.deepcopy(subnet_pool_methods), loaded=True ) @@ -789,13 +654,11 @@ def create_one_subnet_pool(attrs={}, methods={}): return subnet_pool @staticmethod - def create_subnet_pools(attrs={}, methods={}, count=2): + def create_subnet_pools(attrs={}, count=2): """Create multiple fake subnet pools. :param Dictionary attrs: A dictionary with all attributes - :param Dictionary methods: - A dictionary with all methods :param int count: The number of subnet pools to fake :return: @@ -804,7 +667,7 @@ def create_subnet_pools(attrs={}, methods={}, count=2): subnet_pools = [] for i in range(0, count): subnet_pools.append( - FakeSubnetPool.create_one_subnet_pool(attrs, methods) + FakeSubnetPool.create_one_subnet_pool(attrs) ) return subnet_pools diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index 0dec0e2ff8..8a75101b5c 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -54,7 +54,9 @@ class TestCreateNetworkIdentityV3(TestNetwork): 'is_default', 'name', 'project_id', + 'provider_network_type', 'router_external', + 'shared', 'status', 'subnets', ) @@ -67,7 +69,9 @@ class TestCreateNetworkIdentityV3(TestNetwork): _network.is_default, _network.name, _network.project_id, + _network.provider_network_type, network._format_router_external(_network.router_external), + _network.shared, _network.status, utils.format_list(_network.subnets), ) @@ -219,7 +223,9 @@ class TestCreateNetworkIdentityV2(TestNetwork): 'is_default', 'name', 'project_id', + 'provider_network_type', 'router_external', + 'shared', 'status', 'subnets', ) @@ -232,7 +238,9 @@ class TestCreateNetworkIdentityV2(TestNetwork): _network.is_default, _network.name, _network.project_id, + _network.provider_network_type, network._format_router_external(_network.router_external), + _network.shared, _network.status, utils.format_list(_network.subnets), ) @@ -539,7 +547,9 @@ class TestShowNetwork(TestNetwork): 'is_default', 'name', 'project_id', + 'provider_network_type', 'router_external', + 'shared', 'status', 'subnets', ) @@ -552,7 +562,9 @@ class TestShowNetwork(TestNetwork): _network.is_default, _network.name, _network.project_id, + _network.provider_network_type, network._format_router_external(_network.router_external), + _network.shared, _network.status, utils.format_list(_network.subnets), ) diff --git a/openstackclient/tests/network/v2/test_router.py b/openstackclient/tests/network/v2/test_router.py index cf94bfd097..e804149867 100644 --- a/openstackclient/tests/network/v2/test_router.py +++ b/openstackclient/tests/network/v2/test_router.py @@ -117,19 +117,29 @@ class TestCreateRouter(TestRouter): columns = ( 'admin_state_up', + 'availability_zone_hints', + 'availability_zones', 'distributed', + 'external_gateway_info', 'ha', 'id', 'name', 'project_id', + 'routes', + 'status', ) data = ( router._format_admin_state(new_router.admin_state_up), + osc_utils.format_list(new_router.availability_zone_hints), + osc_utils.format_list(new_router.availability_zones), new_router.distributed, + router._format_external_gateway_info(new_router.external_gateway_info), new_router.ha, new_router.id, new_router.name, new_router.tenant_id, + new_router.routes, + new_router.status, ) def setUp(self): @@ -541,20 +551,29 @@ class TestShowRouter(TestRouter): columns = ( 'admin_state_up', + 'availability_zone_hints', + 'availability_zones', 'distributed', + 'external_gateway_info', 'ha', 'id', 'name', 'project_id', + 'routes', + 'status', ) - data = ( router._format_admin_state(_router.admin_state_up), + osc_utils.format_list(_router.availability_zone_hints), + osc_utils.format_list(_router.availability_zones), _router.distributed, + router._format_external_gateway_info(_router.external_gateway_info), _router.ha, _router.id, _router.name, - _router.project_id, + _router.tenant_id, + _router.routes, + _router.status, ) def setUp(self): From be2d2a1b8da02d6b8ab05240e4ab61b26f65e442 Mon Sep 17 00:00:00 2001 From: Sheel Rana Date: Wed, 30 Mar 2016 08:37:22 +0530 Subject: [PATCH 0798/3095] Add support for setting volume-type-access OSC does not support to set volume type access to project. This patch will provide support for adding volume type access to existing project. Closes-Bug:#1554889 Implements: bp cinder-command-support Change-Id: Ie36e202bdde7de36eb263a476eb66699d82f7565 --- doc/source/command-objects/volume-type.rst | 13 ++++ openstackclient/tests/volume/v2/fakes.py | 2 + openstackclient/tests/volume/v2/test_type.py | 66 +++++++++++++++++++ openstackclient/volume/v2/volume_type.py | 54 +++++++++++++-- ...d_volume_type_access-32ba8d4bfb0f5f3d.yaml | 18 +++++ 5 files changed, 147 insertions(+), 6 deletions(-) create mode 100644 releasenotes/notes/add_volume_type_access-32ba8d4bfb0f5f3d.yaml diff --git a/doc/source/command-objects/volume-type.rst b/doc/source/command-objects/volume-type.rst index 69944fb954..64b1bd52d9 100644 --- a/doc/source/command-objects/volume-type.rst +++ b/doc/source/command-objects/volume-type.rst @@ -88,6 +88,8 @@ Set volume type properties [--name ] [--description ] [--property [...] ] + [--project ] + [--project-domain ] .. option:: --name @@ -102,6 +104,17 @@ Set volume type properties .. versionadded:: 2 +.. option:: --project + + Set volume type access to project (name or ID) (admin only) + + *Volume version 2 only* + +.. option:: --project-domain + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + .. option:: --property Set a property on this volume type (repeat option to set multiple properties) diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py index 97bbc59bce..3c238d1006 100644 --- a/openstackclient/tests/volume/v2/fakes.py +++ b/openstackclient/tests/volume/v2/fakes.py @@ -243,6 +243,8 @@ def __init__(self, **kwargs): self.backups.resource_class = fakes.FakeResource(None, {}) self.volume_types = mock.Mock() self.volume_types.resource_class = fakes.FakeResource(None, {}) + self.volume_type_access = mock.Mock() + self.volume_type_access.resource_class = fakes.FakeResource(None, {}) self.restores = mock.Mock() self.restores.resource_class = fakes.FakeResource(None, {}) self.qos_specs = mock.Mock() diff --git a/openstackclient/tests/volume/v2/test_type.py b/openstackclient/tests/volume/v2/test_type.py index b014706b69..448da4320e 100644 --- a/openstackclient/tests/volume/v2/test_type.py +++ b/openstackclient/tests/volume/v2/test_type.py @@ -15,6 +15,8 @@ import copy from openstackclient.tests import fakes +from openstackclient.tests.identity.v3 import fakes as identity_fakes +from openstackclient.tests import utils as tests_utils from openstackclient.tests.volume.v2 import fakes as volume_fakes from openstackclient.volume.v2 import volume_type @@ -41,6 +43,13 @@ def setUp(self): self.types_mock = self.app.client_manager.volume.volume_types self.types_mock.reset_mock() + self.types_access_mock = ( + self.app.client_manager.volume.volume_type_access) + self.types_access_mock.reset_mock() + + self.projects_mock = self.app.client_manager.identity.projects + self.projects_mock.reset_mock() + class TestTypeCreate(TestType): @@ -211,6 +220,13 @@ def setUp(self): loaded=True, ) + # Return a project + self.projects_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.PROJECT), + loaded=True, + ) + # Get the command object to test self.cmd = volume_type.SetVolumeType(self.app, None) @@ -286,6 +302,56 @@ def test_type_set_property(self): self.assertIn('myprop', result) self.assertEqual('myvalue', result['myprop']) + def test_type_set_not_called_without_project_argument(self): + arglist = [ + '--project', '', + volume_fakes.type_id, + ] + verifylist = [ + ('project', ''), + ('volume_type', volume_fakes.type_id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.assertIsNone(result) + + self.assertFalse(self.types_access_mock.add_project_access.called) + + def test_type_set_failed_with_missing_volume_type_argument(self): + arglist = [ + '--project', 'identity_fakes.project_id', + ] + verifylist = [ + ('project', 'identity_fakes.project_id'), + ] + + self.assertRaises(tests_utils.ParserException, + self.check_parser, + self.cmd, + arglist, + verifylist) + + def test_type_set_project_access(self): + arglist = [ + '--project', identity_fakes.project_id, + volume_fakes.type_id, + ] + verifylist = [ + ('project', identity_fakes.project_id), + ('volume_type', volume_fakes.type_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.assertIsNone(result) + + self.types_access_mock.add_project_access.assert_called_with( + volume_fakes.type_id, + identity_fakes.project_id, + ) + class TestTypeShow(TestType): diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index 5509ac5261..12828de2c4 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -17,8 +17,10 @@ import six from openstackclient.common import command +from openstackclient.common import exceptions from openstackclient.common import parseractions from openstackclient.common import utils +from openstackclient.identity import common as identity_common class CreateVolumeType(command.ShowOne): @@ -156,19 +158,30 @@ def get_parser(self, prog_name): help='Set a property on this volume type ' '(repeat option to set multiple properties)', ) + parser.add_argument( + '--project', + metavar='', + help='Set volume type access to project (name or ID) (admin only)', + ) + identity_common.add_project_domain_option_to_parser(parser) + return parser def take_action(self, parsed_args): volume_client = self.app.client_manager.volume + identity_client = self.app.client_manager.identity + volume_type = utils.find_resource( volume_client.volume_types, parsed_args.volume_type) if (not parsed_args.name and not parsed_args.description - and not parsed_args.property): + and not parsed_args.property + and not parsed_args.project): self.app.log.error("No changes requested\n") return + result = 0 kwargs = {} if parsed_args.name: kwargs['name'] = parsed_args.name @@ -176,13 +189,42 @@ def take_action(self, parsed_args): kwargs['description'] = parsed_args.description if kwargs: - volume_client.volume_types.update( - volume_type.id, - **kwargs - ) + try: + volume_client.volume_types.update( + volume_type.id, + **kwargs + ) + except Exception as e: + self.app.log.error("Failed to update volume type name or" + " description: " + str(e)) + result += 1 if parsed_args.property: - volume_type.set_keys(parsed_args.property) + try: + volume_type.set_keys(parsed_args.property) + except Exception as e: + self.app.log.error("Failed to set volume type property: " + + str(e)) + result += 1 + + if parsed_args.project: + project_info = None + try: + project_info = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain) + + volume_client.volume_type_access.add_project_access( + volume_type.id, project_info.id) + except Exception as e: + self.app.log.error("Failed to set volume type access to" + " project: " + str(e)) + result += 1 + + if result > 0: + raise exceptions.CommandError("Command Failed: One or more of the" + " operations failed") class ShowVolumeType(command.ShowOne): diff --git a/releasenotes/notes/add_volume_type_access-32ba8d4bfb0f5f3d.yaml b/releasenotes/notes/add_volume_type_access-32ba8d4bfb0f5f3d.yaml new file mode 100644 index 0000000000..69ae6e76f0 --- /dev/null +++ b/releasenotes/notes/add_volume_type_access-32ba8d4bfb0f5f3d.yaml @@ -0,0 +1,18 @@ +--- +features: + - | + Added support for setting volume type access to project. + + By default, volumes types are public. + To create a private volume type, user needs to set is_public boolean + field to false at volume type creation time. + To control access to a private volume type, user needs to add access + of a private volume type to project. + + So, this feature enables user to add private volume type access to a + project using below command + + ``volume type set --project ``. + + [Bug 1554889 'https://bugs.launchpad.net/python-openstackclient/+bug/1554889'_] + From 3a3f33b9265b63b681727f220d26286c763e67e3 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Thu, 31 Mar 2016 16:19:20 -0500 Subject: [PATCH 0799/3095] Add network options to security group rule create Add the following network options to the "os security group rule" command: (1) --ingress and --egress (2) --ethertype These options enable egress and IPv6 security group rules for Network v2. Change-Id: Ie30b5e95f94e0c087b0ce81e518de72d2dda25ad Partial-Bug: #1519512 Implements: blueprint neutron-client --- .../command-objects/security-group-rule.rst | 23 +++++++- .../network/v2/test_security_group_rule.py | 1 + .../network/v2/security_group_rule.py | 59 +++++++++++++++---- .../network/v2/test_security_group_rule.py | 53 ++++++++++++++++- .../notes/bug-1519512-48d98f09e44220a3.yaml | 6 ++ 5 files changed, 127 insertions(+), 15 deletions(-) create mode 100644 releasenotes/notes/bug-1519512-48d98f09e44220a3.yaml diff --git a/doc/source/command-objects/security-group-rule.rst b/doc/source/command-objects/security-group-rule.rst index fa07c3772c..9aa82cd30d 100644 --- a/doc/source/command-objects/security-group-rule.rst +++ b/doc/source/command-objects/security-group-rule.rst @@ -16,6 +16,8 @@ Create a new security group rule [--proto ] [--src-ip | --src-group ] [--dst-port ] + [--ingress | --egress] + [--ethertype ] .. option:: --proto @@ -24,7 +26,8 @@ Create a new security group rule .. option:: --src-ip - Source IP address block (may use CIDR notation; default: 0.0.0.0/0) + Source IP address block + (may use CIDR notation; default for IPv4 rule: 0.0.0.0/0) .. option:: --src-group @@ -35,6 +38,24 @@ Create a new security group rule Destination port, may be a single port or port range: 137:139 (only required for IP protocols tcp and udp) +.. option:: --ingress + + Rule applies to incoming network traffic (default) + + *Network version 2 only* + +.. option:: --egress + + Rule applies to outgoing network traffic + + *Network version 2 only* + +.. option:: --ethertype + + Ethertype of network traffic (IPv4, IPv6; default: IPv4) + + *Network version 2 only* + .. describe:: Create rule in this security group (name or ID) diff --git a/functional/tests/network/v2/test_security_group_rule.py b/functional/tests/network/v2/test_security_group_rule.py index 9c0b66e830..26e6e0e457 100644 --- a/functional/tests/network/v2/test_security_group_rule.py +++ b/functional/tests/network/v2/test_security_group_rule.py @@ -38,6 +38,7 @@ def setUpClass(cls): raw_output = cls.openstack('security group rule create ' + cls.SECURITY_GROUP_NAME + ' --proto tcp --dst-port 80:80' + + ' --ingress --ethertype IPv4' + opts) cls.SECURITY_GROUP_RULE_ID = raw_output.strip('\n') diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index c6fb355853..832509143a 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -68,7 +68,9 @@ def update_parser_common(self, parser): help='Create rule in this security group (name or ID)', ) # TODO(rtheis): Add support for additional protocols for network. - # Until then, continue enforcing the compute choices. + # Until then, continue enforcing the compute choices. When additional + # protocols are added, the default ethertype must be determined + # based on the protocol. parser.add_argument( "--proto", metavar="", @@ -81,9 +83,8 @@ def update_parser_common(self, parser): source_group.add_argument( "--src-ip", metavar="", - default="0.0.0.0/0", - help="Source IP address block (may use CIDR notation; default: " - "0.0.0.0/0)", + help="Source IP address block (may use CIDR notation; " + "default for IPv4 rule: 0.0.0.0/0)", ) source_group.add_argument( "--src-group", @@ -100,6 +101,27 @@ def update_parser_common(self, parser): ) return parser + def update_parser_network(self, parser): + direction_group = parser.add_mutually_exclusive_group() + direction_group.add_argument( + '--ingress', + action='store_true', + help='Rule applies to incoming network traffic (default)', + ) + direction_group.add_argument( + '--egress', + action='store_true', + help='Rule applies to outgoing network traffic', + ) + parser.add_argument( + '--ethertype', + metavar='', + choices=['IPv4', 'IPv6'], + help='Ethertype of network traffic ' + '(IPv4, IPv6; default: IPv4)', + ) + return parser + def take_action_network(self, client, parsed_args): # Get the security group ID to hold the rule. security_group_id = client.find_security_group( @@ -109,12 +131,18 @@ def take_action_network(self, client, parsed_args): # Build the create attributes. attrs = {} - # TODO(rtheis): Add --direction option. Until then, continue - # with the default of 'ingress'. - attrs['direction'] = 'ingress' - # TODO(rtheis): Add --ethertype option. Until then, continue - # with the default of 'IPv4' - attrs['ethertype'] = 'IPv4' + # NOTE(rtheis): A direction must be specified and ingress + # is the default. + if parsed_args.ingress or not parsed_args.egress: + attrs['direction'] = 'ingress' + if parsed_args.egress: + attrs['direction'] = 'egress' + if parsed_args.ethertype: + attrs['ethertype'] = parsed_args.ethertype + else: + # NOTE(rtheis): Default based on protocol is IPv4 for now. + # Once IPv6 protocols are added, this will need to be updated. + attrs['ethertype'] = 'IPv4' # TODO(rtheis): Add port range support (type and code) for icmp # protocol. Until then, continue ignoring the port range. if parsed_args.proto != 'icmp': @@ -126,8 +154,10 @@ def take_action_network(self, client, parsed_args): parsed_args.src_group, ignore_missing=False ).id - else: + elif parsed_args.src_ip is not None: attrs['remote_ip_prefix'] = parsed_args.src_ip + elif attrs['ethertype'] == 'IPv4': + attrs['remote_ip_prefix'] = '0.0.0.0/0' attrs['security_group_id'] = security_group_id # Create and show the security group rule. @@ -145,17 +175,22 @@ def take_action_compute(self, client, parsed_args): from_port, to_port = -1, -1 else: from_port, to_port = parsed_args.dst_port + src_ip = None if parsed_args.src_group is not None: parsed_args.src_group = utils.find_resource( client.security_groups, parsed_args.src_group, ).id + if parsed_args.src_ip is not None: + src_ip = parsed_args.src_ip + else: + src_ip = '0.0.0.0/0' obj = client.security_group_rules.create( group.id, parsed_args.proto, from_port, to_port, - parsed_args.src_ip, + src_ip, parsed_args.src_group, ) return _format_security_group_rule_show(obj._info) diff --git a/openstackclient/tests/network/v2/test_security_group_rule.py b/openstackclient/tests/network/v2/test_security_group_rule.py index 6aad859976..9e9fd120dc 100644 --- a/openstackclient/tests/network/v2/test_security_group_rule.py +++ b/openstackclient/tests/network/v2/test_security_group_rule.py @@ -97,7 +97,7 @@ def test_create_no_options(self): self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, [], []) - def test_create_source_group_and_ip(self): + def test_create_all_source_options(self): arglist = [ '--src-ip', '10.10.0.0/24', '--src-group', self._security_group.id, @@ -114,6 +114,14 @@ def test_create_bad_protocol(self): self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, []) + def test_create_bad_ethertype(self): + arglist = [ + '--ethertype', 'foo', + self._security_group.id, + ] + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, arglist, []) + def test_create_default_rule(self): self._setup_security_group_rule({ 'port_range_max': 443, @@ -124,6 +132,8 @@ def test_create_default_rule(self): self._security_group.id, ] verifylist = [ + ('dst_port', (self._security_group_rule.port_range_min, + self._security_group_rule.port_range_max)), ('group', self._security_group.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -150,12 +160,14 @@ def test_create_source_group(self): }) arglist = [ '--dst-port', str(self._security_group_rule.port_range_min), + '--ingress', '--src-group', self._security_group.name, self._security_group.id, ] verifylist = [ ('dst_port', (self._security_group_rule.port_range_min, self._security_group_rule.port_range_max)), + ('ingress', True), ('src_group', self._security_group.name), ('group', self._security_group.id), ] @@ -206,6 +218,43 @@ def test_create_source_ip(self): self.assertEqual(tuple(self.expected_columns), columns) self.assertEqual(self.expected_data, data) + def test_create_network_options(self): + self._setup_security_group_rule({ + 'direction': 'egress', + 'ethertype': 'IPv6', + 'port_range_max': 443, + 'port_range_min': 443, + 'remote_group_id': None, + 'remote_ip_prefix': None, + }) + arglist = [ + '--dst-port', str(self._security_group_rule.port_range_min), + '--egress', + '--ethertype', self._security_group_rule.ethertype, + self._security_group.id, + ] + verifylist = [ + ('dst_port', (self._security_group_rule.port_range_min, + self._security_group_rule.port_range_max)), + ('egress', True), + ('ethertype', self._security_group_rule.ethertype), + ('group', self._security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_security_group_rule.assert_called_once_with(**{ + 'direction': self._security_group_rule.direction, + 'ethertype': self._security_group_rule.ethertype, + 'port_range_max': self._security_group_rule.port_range_max, + 'port_range_min': self._security_group_rule.port_range_min, + 'protocol': self._security_group_rule.protocol, + 'security_group_id': self._security_group.id, + }) + self.assertEqual(tuple(self.expected_columns), columns) + self.assertEqual(self.expected_data, data) + class TestCreateSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): @@ -241,7 +290,7 @@ def test_create_no_options(self): self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, [], []) - def test_create_source_group_and_ip(self): + def test_create_all_source_options(self): arglist = [ '--src-ip', '10.10.0.0/24', '--src-group', self._security_group.id, diff --git a/releasenotes/notes/bug-1519512-48d98f09e44220a3.yaml b/releasenotes/notes/bug-1519512-48d98f09e44220a3.yaml new file mode 100644 index 0000000000..1d275c573d --- /dev/null +++ b/releasenotes/notes/bug-1519512-48d98f09e44220a3.yaml @@ -0,0 +1,6 @@ +--- +features: + - Add ``--ingress``, ``--egress``, and ``--ethertype`` options to the + ``security group rule create`` command for Network v2 only. These + options enable ``egress`` and ``IPv6`` security group rules. + [Bug `1519512 `_] From a5a9caea2b06a69953f692289866e59f52d78a4c Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Mon, 4 Apr 2016 16:20:20 -0500 Subject: [PATCH 0800/3095] Add project options to security group rule create Add the --project and --project-domain options to the 'os security group rule create' command. These options are for Network v2 only. Change-Id: Ie3e136be076f0f2c22fbe7048d1d6eaebf5aa655 Partial-Bug: #1519512 Implements: blueprint neutron-client --- .../command-objects/security-group-rule.rst | 14 +++++++ .../network/v2/security_group_rule.py | 15 +++++++ .../network/v2/test_security_group_rule.py | 41 +++++++++++++++++++ .../notes/bug-1519512-48d98f09e44220a3.yaml | 7 ++-- 4 files changed, 74 insertions(+), 3 deletions(-) diff --git a/doc/source/command-objects/security-group-rule.rst b/doc/source/command-objects/security-group-rule.rst index 9aa82cd30d..2f212e5ebf 100644 --- a/doc/source/command-objects/security-group-rule.rst +++ b/doc/source/command-objects/security-group-rule.rst @@ -18,6 +18,7 @@ Create a new security group rule [--dst-port ] [--ingress | --egress] [--ethertype ] + [--project [--project-domain ]] .. option:: --proto @@ -56,6 +57,19 @@ Create a new security group rule *Network version 2 only* +.. option:: --project + + Owner's project (name or ID) + + *Network version 2 only* + +.. option:: --project-domain + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + + *Network version 2 only* + .. describe:: Create rule in this security group (name or ID) diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index 832509143a..509b197442 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -23,6 +23,7 @@ from openstackclient.common import exceptions from openstackclient.common import parseractions from openstackclient.common import utils +from openstackclient.identity import common as identity_common from openstackclient.network import common from openstackclient.network import utils as network_utils @@ -120,6 +121,12 @@ def update_parser_network(self, parser): help='Ethertype of network traffic ' '(IPv4, IPv6; default: IPv4)', ) + parser.add_argument( + '--project', + metavar='', + help="Owner's project (name or ID)" + ) + identity_common.add_project_domain_option_to_parser(parser) return parser def take_action_network(self, client, parsed_args): @@ -159,6 +166,14 @@ def take_action_network(self, client, parsed_args): elif attrs['ethertype'] == 'IPv4': attrs['remote_ip_prefix'] = '0.0.0.0/0' attrs['security_group_id'] = security_group_id + if parsed_args.project is not None: + identity_client = self.app.client_manager.identity + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + attrs['tenant_id'] = project_id # Create and show the security group rule. obj = client.create_security_group_rule(**attrs) diff --git a/openstackclient/tests/network/v2/test_security_group_rule.py b/openstackclient/tests/network/v2/test_security_group_rule.py index 9e9fd120dc..c2fa12568f 100644 --- a/openstackclient/tests/network/v2/test_security_group_rule.py +++ b/openstackclient/tests/network/v2/test_security_group_rule.py @@ -18,6 +18,7 @@ from openstackclient.network.v2 import security_group_rule from openstackclient.tests.compute.v2 import fakes as compute_fakes from openstackclient.tests import fakes +from openstackclient.tests.identity.v3 import fakes as identity_fakes from openstackclient.tests.network.v2 import fakes as network_fakes from openstackclient.tests import utils as tests_utils @@ -89,6 +90,30 @@ def setUp(self): self.network.find_security_group = mock.Mock( return_value=self._security_group) + # Set identity client v3. And get a shortcut to Identity client. + identity_client = identity_fakes.FakeIdentityv3Client( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN, + ) + self.app.client_manager.identity = identity_client + self.identity = self.app.client_manager.identity + + # Get a shortcut to the ProjectManager Mock + self.projects_mock = self.identity.projects + self.projects_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.PROJECT), + loaded=True, + ) + + # Get a shortcut to the DomainManager Mock + self.domains_mock = self.identity.domains + self.domains_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.DOMAIN), + loaded=True, + ) + # Get the command object to test self.cmd = security_group_rule.CreateSecurityGroupRule( self.app, self.namespace) @@ -231,6 +256,8 @@ def test_create_network_options(self): '--dst-port', str(self._security_group_rule.port_range_min), '--egress', '--ethertype', self._security_group_rule.ethertype, + '--project', identity_fakes.project_name, + '--project-domain', identity_fakes.domain_name, self._security_group.id, ] verifylist = [ @@ -238,6 +265,8 @@ def test_create_network_options(self): self._security_group_rule.port_range_max)), ('egress', True), ('ethertype', self._security_group_rule.ethertype), + ('project', identity_fakes.project_name), + ('project_domain', identity_fakes.domain_name), ('group', self._security_group.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -251,6 +280,7 @@ def test_create_network_options(self): 'port_range_min': self._security_group_rule.port_range_min, 'protocol': self._security_group_rule.protocol, 'security_group_id': self._security_group.id, + 'tenant_id': identity_fakes.project_id, }) self.assertEqual(tuple(self.expected_columns), columns) self.assertEqual(self.expected_data, data) @@ -307,6 +337,17 @@ def test_create_bad_protocol(self): self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, []) + def test_create_network_options(self): + arglist = [ + '--ingress', + '--ethertype', 'IPv4', + '--project', identity_fakes.project_name, + '--project-domain', identity_fakes.domain_name, + self._security_group.id, + ] + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, arglist, []) + def test_create_default_rule(self): expected_columns, expected_data = self._setup_security_group_rule() dst_port = str(self._security_group_rule.from_port) + ':' + \ diff --git a/releasenotes/notes/bug-1519512-48d98f09e44220a3.yaml b/releasenotes/notes/bug-1519512-48d98f09e44220a3.yaml index 1d275c573d..0161b5cf89 100644 --- a/releasenotes/notes/bug-1519512-48d98f09e44220a3.yaml +++ b/releasenotes/notes/bug-1519512-48d98f09e44220a3.yaml @@ -1,6 +1,7 @@ --- features: - - Add ``--ingress``, ``--egress``, and ``--ethertype`` options to the - ``security group rule create`` command for Network v2 only. These - options enable ``egress`` and ``IPv6`` security group rules. + - Add ``--ingress``, ``--egress``, ``--ethertype``, ``--project`` + and ``--project-domain`` options to the ``security group rule create`` + command for Network v2 only. These options enable ``egress`` and + ``IPv6`` security group rules along with setting the project. [Bug `1519512 `_] From 61a60ef926d3d433e73dc30bdc419138265903bd Mon Sep 17 00:00:00 2001 From: Ankur Gupta Date: Mon, 11 Apr 2016 10:55:04 -0500 Subject: [PATCH 0801/3095] Add Testing Ref in README.rst Add a reference in the README directed to docs page for testing within python-openstackclient. Avoid creating TESTING.rst due to unneeded duplication. Updated Developer URL to correct one. Change-Id: I40b4c949d155aea6e0e25ecd4a88797481a4d8ac --- README.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 53b42a7d84..340a0ea9eb 100644 --- a/README.rst +++ b/README.rst @@ -25,6 +25,7 @@ language to describe operations in OpenStack. * `Source`_ * `Developer` - getting started as a developer * `Contributing` - contributing code +* `Testing` - testing code * IRC: #openstack-sdks on Freenode (irc.freenode.net) * License: Apache 2.0 @@ -34,8 +35,9 @@ language to describe operations in OpenStack. .. _Blueprints: https://blueprints.launchpad.net/python-openstackclient .. _Bugs: https://bugs.launchpad.net/python-openstackclient .. _Source: https://git.openstack.org/cgit/openstack/python-openstackclient -.. _Developer: http://docs.openstack.org/infra/manual/python.html +.. _Developer: http://docs.openstack.org/project-team-guide/project-setup/python.html .. _Contributing: http://docs.openstack.org/infra/manual/developers.html +.. _Testing: http://docs.openstack.org/developer/python-openstackclient/developing.html#testing Getting Started =============== From a281ef89a502a9e1a1def22fcba77cc666e6c610 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 12 Apr 2016 13:41:52 +0800 Subject: [PATCH 0802/3095] Remove fake methods code from compute network Network objects don't have any method needs to fake. keys() method is only used by _get_columns() helper to obtain all attributes of an object. But in compute network implementation, attributes are obtained from obj._info directly, which is a dictionary itself. So there is no need to fake this method. Change-Id: Ie6a46ef6a3042641e55a7002573ef501db7b60e1 --- openstackclient/tests/compute/v2/fakes.py | 92 ++++------------------- 1 file changed, 15 insertions(+), 77 deletions(-) diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 6c67c470b8..1cca278772 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -342,20 +342,16 @@ class FakeSecurityGroup(object): """Fake one or more security groups.""" @staticmethod - def create_one_security_group(attrs=None, methods=None): + def create_one_security_group(attrs=None): """Create a fake security group. :param Dictionary attrs: A dictionary with all attributes - :param Dictionary methods: - A dictionary with all methods :return: A FakeResource object, with id, name, etc. """ if attrs is None: attrs = {} - if methods is None: - methods = {} # Set default attributes. security_group_attrs = { @@ -369,28 +365,17 @@ def create_one_security_group(attrs=None, methods=None): # Overwrite default attributes. security_group_attrs.update(attrs) - # Set default methods. - security_group_methods = { - 'keys': ['id', 'name', 'description', 'tenant_id', 'rules'], - } - - # Overwrite default methods. - security_group_methods.update(methods) - security_group = fakes.FakeResource( info=copy.deepcopy(security_group_attrs), - methods=copy.deepcopy(security_group_methods), loaded=True) return security_group @staticmethod - def create_security_groups(attrs=None, methods=None, count=2): + def create_security_groups(attrs=None, count=2): """Create multiple fake security groups. :param Dictionary attrs: A dictionary with all attributes - :param Dictionary methods: - A dictionary with all methods :param int count: The number of security groups to fake :return: @@ -399,7 +384,7 @@ def create_security_groups(attrs=None, methods=None, count=2): security_groups = [] for i in range(0, count): security_groups.append( - FakeSecurityGroup.create_one_security_group(attrs, methods)) + FakeSecurityGroup.create_one_security_group(attrs)) return security_groups @@ -408,20 +393,16 @@ class FakeSecurityGroupRule(object): """Fake one or more security group rules.""" @staticmethod - def create_one_security_group_rule(attrs=None, methods=None): + def create_one_security_group_rule(attrs=None): """Create a fake security group rule. :param Dictionary attrs: A dictionary with all attributes - :param Dictionary methods: - A dictionary with all methods :return: A FakeResource object, with id, etc. """ if attrs is None: attrs = {} - if methods is None: - methods = {} # Set default attributes. security_group_rule_attrs = { @@ -437,26 +418,17 @@ def create_one_security_group_rule(attrs=None, methods=None): # Overwrite default attributes. security_group_rule_attrs.update(attrs) - # Set default methods. - security_group_rule_methods = {} - - # Overwrite default methods. - security_group_rule_methods.update(methods) - security_group_rule = fakes.FakeResource( info=copy.deepcopy(security_group_rule_attrs), - methods=copy.deepcopy(security_group_rule_methods), loaded=True) return security_group_rule @staticmethod - def create_security_group_rules(attrs=None, methods=None, count=2): + def create_security_group_rules(attrs=None, count=2): """Create multiple fake security group rules. :param Dictionary attrs: A dictionary with all attributes - :param Dictionary methods: - A dictionary with all methods :param int count: The number of security group rules to fake :return: @@ -465,8 +437,7 @@ def create_security_group_rules(attrs=None, methods=None, count=2): security_group_rules = [] for i in range(0, count): security_group_rules.append( - FakeSecurityGroupRule.create_one_security_group_rule( - attrs, methods)) + FakeSecurityGroupRule.create_one_security_group_rule(attrs)) return security_group_rules @@ -688,13 +659,11 @@ class FakeAvailabilityZone(object): """Fake one or more compute availability zones (AZs).""" @staticmethod - def create_one_availability_zone(attrs={}, methods={}): + def create_one_availability_zone(attrs={}): """Create a fake AZ. :param Dictionary attrs: A dictionary with all attributes - :param Dictionary methods: - A dictionary with all methods :return: A FakeResource object with zoneName, zoneState, etc. """ @@ -717,18 +686,15 @@ def create_one_availability_zone(attrs={}, methods={}): availability_zone = fakes.FakeResource( info=copy.deepcopy(availability_zone), - methods=methods, loaded=True) return availability_zone @staticmethod - def create_availability_zones(attrs={}, methods={}, count=2): + def create_availability_zones(attrs={}, count=2): """Create multiple fake AZs. :param Dictionary attrs: A dictionary with all attributes - :param Dictionary methods: - A dictionary with all methods :param int count: The number of AZs to fake :return: @@ -737,8 +703,7 @@ def create_availability_zones(attrs={}, methods={}, count=2): availability_zones = [] for i in range(0, count): availability_zone = \ - FakeAvailabilityZone.create_one_availability_zone( - attrs, methods) + FakeAvailabilityZone.create_one_availability_zone(attrs) availability_zones.append(availability_zone) return availability_zones @@ -748,13 +713,11 @@ class FakeFloatingIP(object): """Fake one or more floating ip.""" @staticmethod - def create_one_floating_ip(attrs={}, methods={}): + def create_one_floating_ip(attrs={}): """Create a fake floating ip. :param Dictionary attrs: A dictionary with all attributes - :param Dictionary methods: - A dictionary with all methods :return: A FakeResource object, with id, ip, and so on """ @@ -770,27 +733,18 @@ def create_one_floating_ip(attrs={}, methods={}): # Overwrite default attributes. floating_ip_attrs.update(attrs) - # Set default methods. - floating_ip_methods = {} - - # Overwrite default methods. - floating_ip_methods.update(methods) - floating_ip = fakes.FakeResource( info=copy.deepcopy(floating_ip_attrs), - methods=copy.deepcopy(floating_ip_methods), loaded=True) return floating_ip @staticmethod - def create_floating_ips(attrs={}, methods={}, count=2): + def create_floating_ips(attrs={}, count=2): """Create multiple fake floating ips. :param Dictionary attrs: A dictionary with all attributes - :param Dictionary methods: - A dictionary with all methods :param int count: The number of floating ips to fake :return: @@ -798,10 +752,7 @@ def create_floating_ips(attrs={}, methods={}, count=2): """ floating_ips = [] for i in range(0, count): - floating_ips.append(FakeFloatingIP.create_one_floating_ip( - attrs, - methods - )) + floating_ips.append(FakeFloatingIP.create_one_floating_ip(attrs)) return floating_ips @staticmethod @@ -828,13 +779,11 @@ class FakeNetwork(object): """Fake one or more networks.""" @staticmethod - def create_one_network(attrs={}, methods={}): + def create_one_network(attrs={}): """Create a fake network. :param Dictionary attrs: A dictionary with all attributes - :param Dictionary methods: - A dictionary with all methods :return: A FakeResource object, with id, label, cidr and so on """ @@ -877,28 +826,17 @@ def create_one_network(attrs={}, methods={}): # Overwrite default attributes. network_attrs.update(attrs) - # Set default methods. - network_methods = { - 'keys': ['id', 'label', 'cidr'], - } - - # Overwrite default methods. - network_methods.update(methods) - network = fakes.FakeResource(info=copy.deepcopy(network_attrs), - methods=copy.deepcopy(network_methods), loaded=True) return network @staticmethod - def create_networks(attrs={}, methods={}, count=2): + def create_networks(attrs={}, count=2): """Create multiple fake networks. :param Dictionary attrs: A dictionary with all attributes - :param Dictionary methods: - A dictionary with all methods :param int count: The number of networks to fake :return: @@ -906,7 +844,7 @@ def create_networks(attrs={}, methods={}, count=2): """ networks = [] for i in range(0, count): - networks.append(FakeNetwork.create_one_network(attrs, methods)) + networks.append(FakeNetwork.create_one_network(attrs)) return networks From 32c627eaf0481eb593388d2d76abffcf2b721136 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 12 Apr 2016 15:57:17 +0800 Subject: [PATCH 0803/3095] Doc: Unify repeatable option comments There are lots of "this option can be repeated" comments in the doc, which are not consistent to other similar docs. This patch changes them to the following format: "repeat option to do something" Change-Id: I54e01053091c428bf87bb36bb95f73a0b80ab6e7 --- doc/source/command-objects/identity-provider.rst | 8 ++++---- doc/source/command-objects/network.rst | 5 +++-- doc/source/command-objects/port.rst | 8 ++++---- doc/source/command-objects/router.rst | 13 +++++++------ doc/source/command-objects/server.rst | 15 +++++++++------ doc/source/command-objects/subnet-pool.rst | 8 ++++---- doc/source/command-objects/subnet.rst | 14 ++++++++------ doc/source/command-objects/trust.rst | 2 +- openstackclient/compute/v2/server.py | 8 ++++---- openstackclient/identity/v3/identity_provider.py | 4 ++-- openstackclient/identity/v3/token.py | 2 +- openstackclient/identity/v3/trust.py | 2 +- openstackclient/network/v2/network.py | 2 +- openstackclient/network/v2/port.py | 8 ++++---- openstackclient/network/v2/router.py | 10 +++++----- openstackclient/network/v2/subnet.py | 8 ++++---- openstackclient/network/v2/subnet_pool.py | 4 ++-- openstackclient/volume/v1/snapshot.py | 2 +- openstackclient/volume/v2/snapshot.py | 2 +- 19 files changed, 66 insertions(+), 59 deletions(-) diff --git a/doc/source/command-objects/identity-provider.rst b/doc/source/command-objects/identity-provider.rst index ac46273638..ca773d811f 100644 --- a/doc/source/command-objects/identity-provider.rst +++ b/doc/source/command-objects/identity-provider.rst @@ -22,8 +22,8 @@ Create new identity provider .. option:: --remote-id - Remote IDs to associate with the Identity Provider (repeat to provide - multiple values) + Remote IDs to associate with the Identity Provider + (repeat option to provide multiple values) .. option:: --remote-id-file @@ -87,8 +87,8 @@ Set identity provider properties .. option:: --remote-id - Remote IDs to associate with the Identity Provider (repeat to provide - multiple values) + Remote IDs to associate with the Identity Provider + (repeat option to provide multiple values) .. option:: --remote-id-file diff --git a/doc/source/command-objects/network.rst b/doc/source/command-objects/network.rst index 029e82e367..622568e4ee 100644 --- a/doc/source/command-objects/network.rst +++ b/doc/source/command-objects/network.rst @@ -58,8 +58,9 @@ Create new network .. option:: --availability-zone-hint - Availability Zone in which to create this network (requires the Network - Availability Zone extension, this option can be repeated). + Availability Zone in which to create this network + (requires the Network Availability Zone extension, + repeat option to set multiple availability zones) *Network version 2 only* diff --git a/doc/source/command-objects/port.rst b/doc/source/command-objects/port.rst index c229524936..cc7723eaf6 100644 --- a/doc/source/command-objects/port.rst +++ b/doc/source/command-objects/port.rst @@ -33,7 +33,7 @@ Create new port Desired IP and/or subnet (name or ID) for this port: subnet=,ip-address= - (this option can be repeated) + (repeat option to set multiple fixed IP addresses) .. option:: --device @@ -51,7 +51,7 @@ Create new port .. option:: --binding-profile Custom data to be passed as binding:profile: = - (this option can be repeated) + (repeat option to set multiple binding:profile data) .. option:: --host @@ -137,7 +137,7 @@ Set port properties Desired IP and/or subnet (name or ID) for this port: subnet=,ip-address= - (this option can be repeated) + (repeat option to set multiple fixed IP addresses) .. option:: --no-fixed-ip @@ -159,7 +159,7 @@ Set port properties .. option:: --binding-profile Custom data to be passed as binding:profile: = - (this option can be repeated) + (repeat option to set multiple binding:profile data) .. option:: --no-binding-profile diff --git a/doc/source/command-objects/router.rst b/doc/source/command-objects/router.rst index 9479af5f5d..dc645712ec 100644 --- a/doc/source/command-objects/router.rst +++ b/doc/source/command-objects/router.rst @@ -86,8 +86,9 @@ Create new router .. option:: --availability-zone-hint - Availability Zone in which to create this router (requires the Router - Availability Zone extension, this option can be repeated). + Availability Zone in which to create this router + (requires the Router Availability Zone extension, + repeat option to set multiple availability zones) .. _router_create-name: .. describe:: @@ -206,10 +207,10 @@ Set router properties .. option:: --route destination=,gateway= - Routes associated with the router. - Repeat this option to set multiple routes. - destination: destination subnet (in CIDR notation). - gateway: nexthop IP address. + Routes associated with the router + destination: destination subnet (in CIDR notation) + gateway: nexthop IP address + (repeat option to set multiple routes) .. option:: --clear-routes diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index 5141600d78..bf972986a9 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -90,7 +90,7 @@ Create a new server .. option:: --security-group Security group to assign to this server (name or ID) - (repeat for multiple groups) + (repeat option to set multiple groups) .. option:: --key-name @@ -98,11 +98,13 @@ Create a new server .. option:: --property - Set a property on this server (repeat for multiple values) + Set a property on this server + (repeat option to set multiple values) .. option:: --file - File to inject into image before boot (repeat for multiple files) + File to inject into image before boot + (repeat option to set multiple files) .. option:: --user-data @@ -569,8 +571,8 @@ Set server properties .. option:: --property - Property to add/change for this server (repeat option to set - multiple properties) + Property to add/change for this server + (repeat option to set multiple properties) .. describe:: @@ -764,7 +766,8 @@ Unset server properties .. option:: --property - Property key to remove from server (repeat to remove multiple values) + Property key to remove from server + (repeat option to remove multiple values) .. describe:: diff --git a/doc/source/command-objects/subnet-pool.rst b/doc/source/command-objects/subnet-pool.rst index 5eff2dcd5b..ce2ac60d43 100644 --- a/doc/source/command-objects/subnet-pool.rst +++ b/doc/source/command-objects/subnet-pool.rst @@ -22,8 +22,8 @@ Create subnet pool .. option:: --pool-prefix - Set subnet pool prefixes (in CIDR notation). - Repeat this option to set multiple prefixes. + Set subnet pool prefixes (in CIDR notation) + (repeat option to set multiple prefixes) .. option:: --default-prefix-length @@ -104,8 +104,8 @@ Set subnet pool properties .. option:: --pool-prefix - Set subnet pool prefixes (in CIDR notation). - Repeat this option to set multiple prefixes. + Set subnet pool prefixes (in CIDR notation) + (repeat option to set multiple prefixes) .. option:: --default-prefix-length diff --git a/doc/source/command-objects/subnet.rst b/doc/source/command-objects/subnet.rst index e5026f5a02..50be5b0a96 100644 --- a/doc/source/command-objects/subnet.rst +++ b/doc/source/command-objects/subnet.rst @@ -56,7 +56,8 @@ Create new subnet .. option:: --allocation-pool start=,end= Allocation pool IP addresses for this subnet e.g.: - ``start=192.168.199.2,end=192.168.199.254`` (This option can be repeated) + ``start=192.168.199.2,end=192.168.199.254`` + (repeat option to add multiple IP addresses) .. option:: --dhcp @@ -68,7 +69,7 @@ Create new subnet .. option:: --dns-nameserver - DNS name server for this subnet (This option can be repeated) + DNS server for this subnet (repeat option to set multiple DNS servers) .. option:: --gateway @@ -85,7 +86,7 @@ Create new subnet ``destination=10.10.0.0/16,gateway=192.168.71.254`` destination: destination subnet (in CIDR notation) gateway: nexthop IP address - (This option can be repeated) + (repeat option to add multiple routes) .. option:: --ip-version {4,6} @@ -162,7 +163,8 @@ Set subnet properties .. option:: --allocation-pool start=,end= Allocation pool IP addresses for this subnet e.g.: - ``start=192.168.199.2,end=192.168.199.254`` (This option can be repeated) + ``start=192.168.199.2,end=192.168.199.254`` + (repeat option to add multiple IP addresses) .. option:: --dhcp @@ -174,7 +176,7 @@ Set subnet properties .. option:: --dns-nameserver - DNS name server for this subnet (This option can be repeated) + DNS server for this subnet (repeat option to set multiple DNS servers) .. option:: --gateway @@ -189,7 +191,7 @@ Set subnet properties ``destination=10.10.0.0/16,gateway=192.168.71.254`` destination: destination subnet (in CIDR notation) gateway: nexthop IP address - (This option can be repeated) + (repeat option to add multiple routes) .. option:: --name diff --git a/doc/source/command-objects/trust.rst b/doc/source/command-objects/trust.rst index 556edc54d4..cb44c6b6cd 100644 --- a/doc/source/command-objects/trust.rst +++ b/doc/source/command-objects/trust.rst @@ -29,7 +29,7 @@ Create new trust .. option:: --role - Roles to authorize (name or ID) (repeat to set multiple values) (required) + Roles to authorize (name or ID) (repeat option to set multiple values, required) .. option:: --impersonate diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index d3b601b0a6..bf9f0985df 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -293,7 +293,7 @@ def get_parser(self, prog_name): action='append', default=[], help=_('Security group to assign to this server (name or ID) ' - '(repeat for multiple groups)'), + '(repeat option to set multiple groups)'), ) parser.add_argument( '--key-name', @@ -305,7 +305,7 @@ def get_parser(self, prog_name): metavar='', action=parseractions.KeyValueAction, help=_('Set a property on this server ' - '(repeat for multiple values)'), + '(repeat option to set multiple values)'), ) parser.add_argument( '--file', @@ -313,7 +313,7 @@ def get_parser(self, prog_name): action='append', default=[], help=_('File to inject into image before boot ' - '(repeat for multiple files)'), + '(repeat option to set multiple files)'), ) parser.add_argument( '--user-data', @@ -1738,7 +1738,7 @@ def get_parser(self, prog_name): action='append', default=[], help=_('Property key to remove from server ' - '(repeat to remove multiple values)'), + '(repeat option to remove multiple values)'), ) return parser diff --git a/openstackclient/identity/v3/identity_provider.py b/openstackclient/identity/v3/identity_provider.py index 37f79ed6d0..276a7f576a 100644 --- a/openstackclient/identity/v3/identity_provider.py +++ b/openstackclient/identity/v3/identity_provider.py @@ -35,7 +35,7 @@ def get_parser(self, prog_name): metavar='', action='append', help='Remote IDs to associate with the Identity Provider ' - '(repeat to provide multiple values)' + '(repeat option to provide multiple values)' ) identity_remote_id_provider.add_argument( '--remote-id-file', @@ -139,7 +139,7 @@ def get_parser(self, prog_name): metavar='', action='append', help='Remote IDs to associate with the Identity Provider ' - '(repeat to provide multiple values)' + '(repeat option to provide multiple values)' ) identity_remote_id_provider.add_argument( '--remote-id-file', diff --git a/openstackclient/identity/v3/token.py b/openstackclient/identity/v3/token.py index 62a4c4a363..bdc5e95f69 100644 --- a/openstackclient/identity/v3/token.py +++ b/openstackclient/identity/v3/token.py @@ -40,7 +40,7 @@ def get_parser(self, prog_name): action='append', default=[], help='Roles to authorize (name or ID) ' - '(repeat to set multiple values) (required)', + '(repeat option to set multiple values, required)', required=True ) return parser diff --git a/openstackclient/identity/v3/trust.py b/openstackclient/identity/v3/trust.py index 26fb8338fe..b6f5c6b41f 100644 --- a/openstackclient/identity/v3/trust.py +++ b/openstackclient/identity/v3/trust.py @@ -48,7 +48,7 @@ def get_parser(self, prog_name): action='append', default=[], help='Roles to authorize (name or ID) ' - '(repeat to set multiple values) (required)', + '(repeat option to set multiple values, required)', required=True ) parser.add_argument( diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index ebd5cb63de..20d943ed67 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -142,7 +142,7 @@ def update_parser_network(self, parser): metavar='', help='Availability Zone in which to create this network ' '(requires the Network Availability Zone extension, ' - 'this option can be repeated).', + 'repeat option to set multiple availability zones)', ) external_router_grp = parser.add_mutually_exclusive_group() external_router_grp.add_argument( diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index a9e8042868..820f2ac22f 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -210,13 +210,13 @@ def get_parser(self, prog_name): optional_keys=['subnet', 'ip-address'], help='Desired IP and/or subnet (name or ID) for this port: ' 'subnet=,ip-address= ' - '(this option can be repeated)') + '(repeat option to set multiple fixed IP addresses)') parser.add_argument( '--binding-profile', metavar='', action=parseractions.KeyValueAction, help='Custom data to be passed as binding:profile: = ' - '(this option can be repeated)') + '(repeat option to set multiple binding:profile data)') admin_group = parser.add_mutually_exclusive_group() admin_group.add_argument( '--enable', @@ -360,7 +360,7 @@ def get_parser(self, prog_name): optional_keys=['subnet', 'ip-address'], help='Desired IP and/or subnet (name or ID) for this port: ' 'subnet=,ip-address= ' - '(this option can be repeated)') + '(repeat option to set multiple fixed IP addresses)') fixed_ip.add_argument( '--no-fixed-ip', action='store_true', @@ -371,7 +371,7 @@ def get_parser(self, prog_name): metavar='', action=parseractions.KeyValueAction, help='Custom data to be passed as binding:profile: = ' - '(this option can be repeated)') + '(repeat option to set multiple binding:profile data)') binding_profile.add_argument( '--no-binding-profile', action='store_true', diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 6a78be6c8e..2ededae87e 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -179,7 +179,7 @@ def get_parser(self, prog_name): dest='availability_zone_hints', help='Availability Zone in which to create this router ' '(requires the Router Availability Zone extension, ' - 'this option can be repeated).', + 'repeat option to set multiple availability zones)', ) identity_common.add_project_domain_option_to_parser(parser) @@ -368,10 +368,10 @@ def get_parser(self, prog_name): dest='routes', default=None, required_keys=['destination', 'gateway'], - help="Routes associated with the router. " - "Repeat this option to set multiple routes. " - "destination: destination subnet (in CIDR notation). " - "gateway: nexthop IP address.", + help="Routes associated with the router " + "destination: destination subnet (in CIDR notation) " + "gateway: nexthop IP address " + "(repeat option to set multiple routes)", ) routes_group.add_argument( '--clear-routes', diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index eb96bb1f5d..8fdb44599e 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -52,15 +52,15 @@ def _get_common_parse_arguments(parser): required_keys=['start', 'end'], help='Allocation pool IP addresses for this subnet ' 'e.g.: start=192.168.199.2,end=192.168.199.254 ' - '(This option can be repeated)', + '(repeat option to add multiple IP addresses)', ) parser.add_argument( '--dns-nameserver', metavar='', action='append', dest='dns_nameservers', - help='DNS name server for this subnet ' - '(This option can be repeated)', + help='DNS server for this subnet ' + '(repeat option to set multiple DNS servers)', ) parser.add_argument( '--host-route', @@ -72,7 +72,7 @@ def _get_common_parse_arguments(parser): 'e.g.: destination=10.10.0.0/16,gateway=192.168.71.254 ' 'destination: destination subnet (in CIDR notation) ' 'gateway: nexthop IP address ' - '(This option can be repeated)', + '(repeat option to add multiple routes)', ) diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index 6b6fc090f9..a4f268b23b 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -65,8 +65,8 @@ def _add_prefix_options(parser): metavar='', dest='prefixes', action='append', - help='Set subnet pool prefixes (in CIDR notation). ' - 'Repeat this option to set multiple prefixes.', + help='Set subnet pool prefixes (in CIDR notation) ' + '(repeat option to set multiple prefixes)', ) parser.add_argument( '--default-prefix-length', diff --git a/openstackclient/volume/v1/snapshot.py b/openstackclient/volume/v1/snapshot.py index c54bac8aef..46ea86ae60 100644 --- a/openstackclient/volume/v1/snapshot.py +++ b/openstackclient/volume/v1/snapshot.py @@ -249,7 +249,7 @@ def get_parser(self, prog_name): action='append', default=[], help='Property to remove from snapshot ' - '(repeat to remove multiple values)', + '(repeat option to remove multiple values)', required=True, ) return parser diff --git a/openstackclient/volume/v2/snapshot.py b/openstackclient/volume/v2/snapshot.py index c9e502976a..0b3da128ab 100644 --- a/openstackclient/volume/v2/snapshot.py +++ b/openstackclient/volume/v2/snapshot.py @@ -240,7 +240,7 @@ def get_parser(self, prog_name): action='append', default=[], help='Property to remove from snapshot ' - '(repeat to remove multiple values)', + '(repeat option to remove multiple values)', ) return parser From 3a4d53a93bb62f5bd2e21db1727ef74f771075d8 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Tue, 12 Apr 2016 12:42:13 -0500 Subject: [PATCH 0804/3095] Fix prefixes output for subnet pool list Fixed "os subnet pool list" command to properly disply the list of subnet pool prefixes in the "Prefixes" column. This fix is consistent with the "os subnet pool create" and "os subnet pool show" command output. Change-Id: I431d85c3b7f5bf8a327500decf3a15063fc5b120 Closes-Bug: #1569480 --- openstackclient/network/v2/subnet_pool.py | 2 +- openstackclient/tests/network/v2/test_subnet_pool.py | 4 ++-- releasenotes/notes/bug-1569480-c52e330548bfbd78.yaml | 7 +++++++ 3 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/bug-1569480-c52e330548bfbd78.yaml diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index 6b6fc090f9..f1fd9a61e9 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -184,7 +184,7 @@ def take_action(self, parsed_args): return (headers, (utils.get_item_properties( s, columns, - formatters={}, + formatters=_formatters, ) for s in data)) diff --git a/openstackclient/tests/network/v2/test_subnet_pool.py b/openstackclient/tests/network/v2/test_subnet_pool.py index 093e26c67e..df4d27eba1 100644 --- a/openstackclient/tests/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/network/v2/test_subnet_pool.py @@ -246,7 +246,7 @@ class TestListSubnetPool(TestSubnetPool): data.append(( pool.id, pool.name, - pool.prefixes, + utils.format_list(pool.prefixes), )) data_long = [] @@ -254,7 +254,7 @@ class TestListSubnetPool(TestSubnetPool): data_long.append(( pool.id, pool.name, - pool.prefixes, + utils.format_list(pool.prefixes), pool.default_prefixlen, pool.address_scope_id, )) diff --git a/releasenotes/notes/bug-1569480-c52e330548bfbd78.yaml b/releasenotes/notes/bug-1569480-c52e330548bfbd78.yaml new file mode 100644 index 0000000000..ccec658bc3 --- /dev/null +++ b/releasenotes/notes/bug-1569480-c52e330548bfbd78.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - Fixed ``subnet pool list`` command to properly disply the + list of subnet pool prefixes in the ``Prefixes`` column. + This fix is consistent with the ``subnet pool create`` and + ``subnet pool show`` command output. + [Bug `1569480 `_] From 379fd726b67e192992b7389a83d3ff43285ab08f Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 13 Apr 2016 12:48:31 +0000 Subject: [PATCH 0805/3095] Updated from global requirements Change-Id: I17ae521c6d9cf2c2cf73527929984aed5fe225ca --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index b05bdc3330..76a0e46220 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ pbr>=1.6 # Apache-2.0 six>=1.9.0 # MIT -Babel>=1.3 # BSD +Babel!=2.3.0,!=2.3.1,!=2.3.2,!=2.3.3,>=1.3 # BSD cliff!=1.16.0,!=1.17.0,>=1.15.0 # Apache-2.0 keystoneauth1>=2.1.0 # Apache-2.0 openstacksdk>=0.8.4 # Apache-2.0 From 573bb6b941199d32e016257f37c649ea804484a3 Mon Sep 17 00:00:00 2001 From: Kyrylo Romanenko Date: Wed, 13 Apr 2016 19:09:07 +0300 Subject: [PATCH 0806/3095] Use CommandFailed exception from tempest_lib Remove exceptions.py from functional/common. Import exception with the same code from the tempest_lib to tests. Change-Id: Ifaa658209c18dd608836079f57ed18fcf10fb84e --- functional/common/exceptions.py | 26 --------------------- functional/common/test.py | 2 +- functional/tests/compute/v2/test_server.py | 2 +- functional/tests/identity/v2/test_user.py | 2 +- functional/tests/identity/v3/test_domain.py | 2 +- 5 files changed, 4 insertions(+), 30 deletions(-) delete mode 100644 functional/common/exceptions.py diff --git a/functional/common/exceptions.py b/functional/common/exceptions.py deleted file mode 100644 index 47c6071e28..0000000000 --- a/functional/common/exceptions.py +++ /dev/null @@ -1,26 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - - -class CommandFailed(Exception): - def __init__(self, returncode, cmd, output, stderr): - super(CommandFailed, self).__init__() - self.returncode = returncode - self.cmd = cmd - self.stdout = output - self.stderr = stderr - - def __str__(self): - return ("Command '%s' returned non-zero exit status %d.\n" - "stdout:\n%s\n" - "stderr:\n%s" % (self.cmd, self.returncode, - self.stdout, self.stderr)) diff --git a/functional/common/test.py b/functional/common/test.py index 810c4e43fe..c1bf3f36a5 100644 --- a/functional/common/test.py +++ b/functional/common/test.py @@ -18,7 +18,7 @@ import six -from functional.common import exceptions +from tempest_lib import exceptions COMMON_DIR = os.path.dirname(os.path.abspath(__file__)) FUNCTIONAL_DIR = os.path.normpath(os.path.join(COMMON_DIR, '..')) diff --git a/functional/tests/compute/v2/test_server.py b/functional/tests/compute/v2/test_server.py index 1d10bbdb2d..fe6691ec08 100644 --- a/functional/tests/compute/v2/test_server.py +++ b/functional/tests/compute/v2/test_server.py @@ -15,8 +15,8 @@ import testtools -from functional.common import exceptions from functional.common import test +from tempest_lib import exceptions class ServerTests(test.TestCase): diff --git a/functional/tests/identity/v2/test_user.py b/functional/tests/identity/v2/test_user.py index 41895e7ecc..a0c1a46b86 100644 --- a/functional/tests/identity/v2/test_user.py +++ b/functional/tests/identity/v2/test_user.py @@ -11,8 +11,8 @@ # under the License. from tempest_lib.common.utils import data_utils +from tempest_lib import exceptions -from functional.common import exceptions from functional.tests.identity.v2 import test_identity diff --git a/functional/tests/identity/v3/test_domain.py b/functional/tests/identity/v3/test_domain.py index a60028b456..221efd6a27 100644 --- a/functional/tests/identity/v3/test_domain.py +++ b/functional/tests/identity/v3/test_domain.py @@ -11,8 +11,8 @@ # under the License. from tempest_lib.common.utils import data_utils +from tempest_lib import exceptions -from functional.common import exceptions from functional.tests.identity.v3 import test_identity From c92ac9d9110524ffb4c672a5b1c3cdc08e38e717 Mon Sep 17 00:00:00 2001 From: reedip Date: Fri, 1 Apr 2016 13:40:45 +0900 Subject: [PATCH 0807/3095] Append existing information during port set Existing --fixed-ip and --binding-profile information is currently overwritten when a user executes 'port set', but actually that data should be appended. This patch fixes the issue. Closes-Bug: #1564453 Change-Id: I62500c10ccbbc68167f24e9d4fa49e85345d82c4 --- openstackclient/network/v2/port.py | 20 ++++++++++++------ openstackclient/tests/network/v2/test_port.py | 21 ++++++++++++++++++- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index a9e8042868..9a73a62c89 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -30,6 +30,7 @@ def _format_admin_state(state): return 'UP' if state else 'DOWN' + _formatters = { 'admin_state_up': _format_admin_state, 'allowed_address_pairs': utils.format_list_of_dicts, @@ -383,17 +384,24 @@ def take_action(self, parsed_args): _prepare_fixed_ips(self.app.client_manager, parsed_args) attrs = _get_attrs(self.app.client_manager, parsed_args) - - if parsed_args.no_fixed_ip: - attrs['fixed_ips'] = [] - if parsed_args.no_binding_profile: + obj = client.find_port(parsed_args.port, ignore_missing=False) + if 'binding:profile' in attrs: + attrs['binding:profile'].update(obj.binding_profile) + elif parsed_args.no_binding_profile: attrs['binding:profile'] = {} + if 'fixed_ips' in attrs: + # When user unsets the fixed_ips, obj.fixed_ips = [{}]. + # Adding the obj.fixed_ips list to attrs['fixed_ips'] + # would therefore add an empty dictionary, while we need + # to append the attrs['fixed_ips'] iff there is some info + # in the obj.fixed_ips. Therefore I have opted for this `for` loop + attrs['fixed_ips'] += [ip for ip in obj.fixed_ips if ip] + elif parsed_args.no_fixed_ip: + attrs['fixed_ips'] = [] if attrs == {}: msg = "Nothing specified to be set" raise exceptions.CommandError(msg) - - obj = client.find_port(parsed_args.port, ignore_missing=False) client.update_port(obj, **attrs) diff --git a/openstackclient/tests/network/v2/test_port.py b/openstackclient/tests/network/v2/test_port.py index 3b1a641a3e..f2aa26cf88 100644 --- a/openstackclient/tests/network/v2/test_port.py +++ b/openstackclient/tests/network/v2/test_port.py @@ -268,7 +268,6 @@ class TestSetPort(TestPort): def setUp(self): super(TestSetPort, self).setUp() - self.fake_subnet = network_fakes.FakeSubnet.create_one_subnet() self.network.find_subnet = mock.Mock(return_value=self.fake_subnet) self.network.find_port = mock.Mock(return_value=self._port) @@ -295,6 +294,26 @@ def test_set_fixed_ip(self): self.network.update_port.assert_called_once_with(self._port, **attrs) self.assertIsNone(result) + def test_append_fixed_ip(self): + _testport = network_fakes.FakePort.create_one_port( + {'fixed_ips': [{'ip_address': '0.0.0.1'}]}) + self.network.find_port = mock.Mock(return_value=_testport) + arglist = [ + '--fixed-ip', 'ip-address=10.0.0.12', + _testport.name, + ] + verifylist = [ + ('fixed_ip', [{'ip-address': '10.0.0.12'}]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + attrs = { + 'fixed_ips': [ + {'ip_address': '10.0.0.12'}, {'ip_address': '0.0.0.1'}], + } + self.network.update_port.assert_called_once_with(_testport, **attrs) + self.assertIsNone(result) + def test_set_this(self): arglist = [ '--disable', From 56f9227063cb86594600ccc80c661101f0f0c2c8 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Mon, 14 Mar 2016 14:03:04 +0800 Subject: [PATCH 0808/3095] Enhance exception handling for "network delete" command This patch rework "network delete" command following the rules in doc/source/command-errors.rst. In "network delete" command, there are multiple REST API calls, and we should make as many of them as possible. And log error for each one, give a better error message. Also return a non-zero exit code. Change-Id: I39ae087dd7bd08d049d513abfa6c5cab2bd13b2b Partial-Bug: #1556719 --- doc/source/command-errors.rst | 42 ++++- openstackclient/network/common.py | 37 +++++ openstackclient/network/v2/network.py | 20 +-- openstackclient/tests/compute/v2/fakes.py | 19 +++ .../tests/network/v2/test_network.py | 149 ++++++++++++++++-- .../notes/bug-1556719-d2dcf61acf87e856.yaml | 6 + 6 files changed, 245 insertions(+), 28 deletions(-) create mode 100644 releasenotes/notes/bug-1556719-d2dcf61acf87e856.yaml diff --git a/doc/source/command-errors.rst b/doc/source/command-errors.rst index 35d7f9d072..a24dc317bb 100644 --- a/doc/source/command-errors.rst +++ b/doc/source/command-errors.rst @@ -99,8 +99,8 @@ to be handled by having the proper options in a set command available to allow recovery in the case where the primary resource has been created but the subsequent calls did not complete. -Example -~~~~~~~ +Example 1 +~~~~~~~~~ This example is taken from the ``volume snapshot set`` command where ``--property`` arguments are set using the volume manager's ``set_metadata()`` method, @@ -161,3 +161,41 @@ remaining arguments are set using the ``update()`` method. # without aborting prematurely if result > 0: raise SomeNonFatalException + +Example 2 +~~~~~~~~~ + +This example is taken from the ``network delete`` command which takes multiple +networks to delete. All networks will be delete in a loop, which makes multiple +``delete_network()`` calls. + +.. code-block:: python + + class DeleteNetwork(common.NetworkAndComputeCommand): + """Delete network(s)""" + + def update_parser_common(self, parser): + parser.add_argument( + 'network', + metavar="", + nargs="+", + help=("Network(s) to delete (name or ID)") + ) + return parser + + def take_action(self, client, parsed_args): + ret = 0 + + for network in parsed_args.network: + try: + obj = client.find_network(network, ignore_missing=False) + client.delete_network(obj) + except Exception: + self.app.log.error("Failed to delete network with name " + "or ID %s." % network) + ret += 1 + + if ret > 0: + total = len(parsed_args.network) + msg = "Failed to delete %s of %s networks." % (ret, total) + raise exceptions.CommandError(msg) diff --git a/openstackclient/network/common.py b/openstackclient/network/common.py index 1e2c4cce99..a3047d84ba 100644 --- a/openstackclient/network/common.py +++ b/openstackclient/network/common.py @@ -15,6 +15,7 @@ import six from openstackclient.common import command +from openstackclient.common import exceptions @six.add_metaclass(abc.ABCMeta) @@ -68,6 +69,42 @@ def take_action_compute(self, client, parsed_args): pass +@six.add_metaclass(abc.ABCMeta) +class NetworkAndComputeDelete(NetworkAndComputeCommand): + """Network and Compute Delete + + Delete class for commands that support implementation via + the network or compute endpoint. Such commands have different + implementations for take_action() and may even have different + arguments. This class supports bulk deletion, and error handling + following the rules in doc/source/command-errors.rst. + """ + + def take_action(self, parsed_args): + ret = 0 + resources = getattr(parsed_args, self.resource, []) + + for r in resources: + self.r = r + try: + if self.app.client_manager.is_network_endpoint_enabled(): + self.take_action_network(self.app.client_manager.network, + parsed_args) + else: + self.take_action_compute(self.app.client_manager.compute, + parsed_args) + except Exception as e: + self.app.log.error("Failed to delete %s with name or ID " + "'%s': %s" % (self.resource, r, e)) + ret += 1 + + if ret: + total = len(resources) + msg = "%s of %s %ss failed to delete." % (ret, total, + self.resource) + raise exceptions.CommandError(msg) + + @six.add_metaclass(abc.ABCMeta) class NetworkAndComputeLister(command.Lister): """Network and Compute Lister diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 20d943ed67..fcbe77c7ba 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -224,9 +224,13 @@ def take_action_compute(self, client, parsed_args): return (columns, data) -class DeleteNetwork(common.NetworkAndComputeCommand): +class DeleteNetwork(common.NetworkAndComputeDelete): """Delete network(s)""" + # Used by base class to find resources in parsed_args. + resource = 'network' + r = None + def update_parser_common(self, parser): parser.add_argument( 'network', @@ -234,20 +238,16 @@ def update_parser_common(self, parser): nargs="+", help=("Network(s) to delete (name or ID)") ) + return parser def take_action_network(self, client, parsed_args): - for network in parsed_args.network: - obj = client.find_network(network) - client.delete_network(obj) + obj = client.find_network(self.r, ignore_missing=False) + client.delete_network(obj) def take_action_compute(self, client, parsed_args): - for network in parsed_args.network: - network = utils.find_resource( - client.networks, - network, - ) - client.networks.delete(network.id) + network = utils.find_resource(client.networks, self.r) + client.networks.delete(network.id) class ListNetwork(common.NetworkAndComputeLister): diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 6c67c470b8..c88ef85b88 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -910,6 +910,25 @@ def create_networks(attrs={}, methods={}, count=2): return networks + @staticmethod + def get_networks(networks=None, count=2): + """Get an iterable MagicMock object with a list of faked networks. + + If networks list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List networks: + A list of FakeResource objects faking networks + :param int count: + The number of networks to fake + :return: + An iterable Mock object with side_effect set to a list of faked + networks + """ + if networks is None: + networks = FakeNetwork.create_networks(count=count) + return mock.Mock(side_effect=networks) + class FakeHost(object): """Fake one host.""" diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index 8a75101b5c..c8a6d0820e 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -14,6 +14,7 @@ import copy import mock +from mock import call from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.network.v2 import network @@ -321,33 +322,88 @@ def test_create_with_domain_identityv2(self): class TestDeleteNetwork(TestNetwork): - # The network to delete. - _network = network_fakes.FakeNetwork.create_one_network() - def setUp(self): super(TestDeleteNetwork, self).setUp() + # The networks to delete + self._networks = network_fakes.FakeNetwork.create_networks(count=3) + self.network.delete_network = mock.Mock(return_value=None) - self.network.find_network = mock.Mock(return_value=self._network) + self.network.find_network = network_fakes.FakeNetwork.get_networks( + networks=self._networks) # Get the command object to test self.cmd = network.DeleteNetwork(self.app, self.namespace) - def test_delete(self): + def test_delete_one_network(self): arglist = [ - self._network.name, + self._networks[0].name, ] verifylist = [ - ('network', [self._network.name]), + ('network', [self._networks[0].name]), ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.network.delete_network.assert_called_once_with(self._networks[0]) + self.assertIsNone(result) + + def test_delete_multiple_networks(self): + arglist = [] + for n in self._networks: + arglist.append(n.id) + verifylist = [ + ('network', arglist), + ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) - self.network.delete_network.assert_called_once_with(self._network) + calls = [] + for n in self._networks: + calls.append(call(n)) + self.network.delete_network.assert_has_calls(calls) self.assertIsNone(result) + def test_delete_multiple_networks_exception(self): + arglist = [ + self._networks[0].id, + 'xxxx-yyyy-zzzz', + self._networks[1].id, + ] + verifylist = [ + ('network', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Fake exception in find_network() + ret_find = [ + self._networks[0], + exceptions.NotFound('404'), + self._networks[1], + ] + self.network.find_network = mock.Mock(side_effect=ret_find) + + # Fake exception in delete_network() + ret_delete = [ + None, + exceptions.NotFound('404'), + ] + self.network.delete_network = mock.Mock(side_effect=ret_delete) + + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + + # The second call of find_network() should fail. So delete_network() + # was only called twice. + calls = [ + call(self._networks[0]), + call(self._networks[1]), + ] + self.network.delete_network.assert_has_calls(calls) + class TestListNetwork(TestNetwork): @@ -730,36 +786,97 @@ def test_create_default_options(self): class TestDeleteNetworkCompute(TestNetworkCompute): - # The network to delete. - _network = compute_fakes.FakeNetwork.create_one_network() - def setUp(self): super(TestDeleteNetworkCompute, self).setUp() self.app.client_manager.network_endpoint_enabled = False + # The networks to delete + self._networks = compute_fakes.FakeNetwork.create_networks(count=3) + self.compute.networks.delete.return_value = None # Return value of utils.find_resource() - self.compute.networks.get.return_value = self._network + self.compute.networks.get = \ + compute_fakes.FakeNetwork.get_networks(networks=self._networks) # Get the command object to test self.cmd = network.DeleteNetwork(self.app, None) - def test_network_delete(self): + def test_delete_one_network(self): arglist = [ - self._network.label, + self._networks[0].label, ] verifylist = [ - ('network', [self._network.label]), + ('network', [self._networks[0].label]), ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.compute.networks.delete.assert_called_once_with( + self._networks[0].id) + self.assertIsNone(result) + + def test_delete_multiple_networks(self): + arglist = [] + for n in self._networks: + arglist.append(n.label) + verifylist = [ + ('network', arglist), + ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) - self.compute.networks.delete.assert_called_once_with(self._network.id) + calls = [] + for n in self._networks: + calls.append(call(n.id)) + self.compute.networks.delete.assert_has_calls(calls) self.assertIsNone(result) + def test_delete_multiple_networks_exception(self): + arglist = [ + self._networks[0].id, + 'xxxx-yyyy-zzzz', + self._networks[1].id, + ] + verifylist = [ + ('network', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Fake exception in utils.find_resource() + # In compute v2, we use utils.find_resource() to find a network. + # It calls get() several times, but find() only one time. So we + # choose to fake get() always raise exception, then pass through. + # And fake find() to find the real network or not. + self.compute.networks.get.side_effect = Exception() + ret_find = [ + self._networks[0], + Exception(), + self._networks[1], + ] + self.compute.networks.find.side_effect = ret_find + + # Fake exception in delete() + ret_delete = [ + None, + Exception(), + ] + self.compute.networks.delete = mock.Mock(side_effect=ret_delete) + + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + + # The second call of utils.find_resource() should fail. So delete() + # was only called twice. + calls = [ + call(self._networks[0].id), + call(self._networks[1].id), + ] + self.compute.networks.delete.assert_has_calls(calls) + class TestListNetworkCompute(TestNetworkCompute): diff --git a/releasenotes/notes/bug-1556719-d2dcf61acf87e856.yaml b/releasenotes/notes/bug-1556719-d2dcf61acf87e856.yaml new file mode 100644 index 0000000000..7c8e5c0628 --- /dev/null +++ b/releasenotes/notes/bug-1556719-d2dcf61acf87e856.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - Command ``network delete`` will delete as many networks as possible, log + and report failures in the end. + [Bug `1556719 `_] + [Bug `1537856 `_] From 46decfbd7a13ad025ba36d819e7598c6332747cf Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Thu, 14 Apr 2016 17:21:53 +0800 Subject: [PATCH 0809/3095] Trivial: Fix incorrect comment text Change-Id: Ia6ec15f11a535a8de5769569d75e81094caed171 --- openstackclient/volume/v1/snapshot.py | 2 +- openstackclient/volume/v2/snapshot.py | 2 +- openstackclient/volume/v2/volume_type.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openstackclient/volume/v1/snapshot.py b/openstackclient/volume/v1/snapshot.py index 46ea86ae60..6c6131ead4 100644 --- a/openstackclient/volume/v1/snapshot.py +++ b/openstackclient/volume/v1/snapshot.py @@ -249,7 +249,7 @@ def get_parser(self, prog_name): action='append', default=[], help='Property to remove from snapshot ' - '(repeat option to remove multiple values)', + '(repeat option to remove multiple properties)', required=True, ) return parser diff --git a/openstackclient/volume/v2/snapshot.py b/openstackclient/volume/v2/snapshot.py index 0b3da128ab..f124a5e226 100644 --- a/openstackclient/volume/v2/snapshot.py +++ b/openstackclient/volume/v2/snapshot.py @@ -240,7 +240,7 @@ def get_parser(self, prog_name): action='append', default=[], help='Property to remove from snapshot ' - '(repeat option to remove multiple values)', + '(repeat option to remove multiple properties)', ) return parser diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index 12828de2c4..203974dad5 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -57,7 +57,7 @@ def get_parser(self, prog_name): '--property', metavar='', action=parseractions.KeyValueAction, - help='Set a property on this volume type' + help='Set a property on this volume type ' '(repeat option to set multiple properties)', ) return parser From 67f8b898eb6d48b11b9f0624ac70f65c4311f8e8 Mon Sep 17 00:00:00 2001 From: reedip Date: Fri, 8 Apr 2016 14:24:30 +0900 Subject: [PATCH 0810/3095] Add external network options to osc network set The following patch adds the options "--external" & "--internal" and the suboptions to "external": "--default" & "--no-default", to "osc network set" CLI to provide the user an option to set a network as an external network or remove the setting. Change-Id: I3a7f2cb249bc8101cbb01322d7732e913237d6cd Partial-Bug: #1545537 --- doc/source/command-objects/network.rst | 19 ++++++++++ openstackclient/network/v2/network.py | 38 +++++++++++++++---- .../tests/network/v2/test_network.py | 9 +++++ .../notes/bug-1545537-7a66219d263bb1e5.yaml | 7 ++-- 4 files changed, 62 insertions(+), 11 deletions(-) diff --git a/doc/source/command-objects/network.rst b/doc/source/command-objects/network.rst index 622568e4ee..6d3f505ab3 100644 --- a/doc/source/command-objects/network.rst +++ b/doc/source/command-objects/network.rst @@ -171,6 +171,7 @@ Set network properties [--name ] [--enable | --disable] [--share | --no-share] + [--external [--default | --no-default] | --internal] .. option:: --name @@ -193,6 +194,24 @@ Set network properties Do not share the network between projects +.. option:: --external + + Set this network as an external network. + Requires the "external-net" extension to be enabled. + +.. option:: --internal + + Set this network as an internal network + +.. option:: --default + + Specify if this network should be used as + the default external network + +.. option:: --no-default + + Do not use the network as the default external network. + .. _network_set-network: .. describe:: diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 20d943ed67..afac471a7d 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -76,6 +76,16 @@ def _get_attrs(client_manager, parsed_args): parsed_args.availability_zone_hints is not None: attrs['availability_zone_hints'] = parsed_args.availability_zone_hints + # update_external_network_options + if parsed_args.internal: + attrs['router:external'] = False + if parsed_args.external: + attrs['router:external'] = True + if parsed_args.no_default: + attrs['is_default'] = False + if parsed_args.default: + attrs['is_default'] = True + return attrs @@ -197,14 +207,6 @@ def update_parser_compute(self, parser): def take_action_network(self, client, parsed_args): attrs = _get_attrs(self.app.client_manager, parsed_args) - if parsed_args.internal: - attrs['router:external'] = False - if parsed_args.external: - attrs['router:external'] = True - if parsed_args.no_default: - attrs['is_default'] = False - if parsed_args.default: - attrs['is_default'] = True if parsed_args.provider_network_type: attrs['provider:network_type'] = parsed_args.provider_network_type if parsed_args.physical_network: @@ -379,6 +381,26 @@ def get_parser(self, prog_name): action='store_true', help='Do not share the network between projects', ) + external_router_grp = parser.add_mutually_exclusive_group() + external_router_grp.add_argument( + '--external', + action='store_true', + help='Set this network as an external network. ' + 'Requires the "external-net" extension to be enabled.') + external_router_grp.add_argument( + '--internal', + action='store_true', + help='Set this network as an internal network') + default_router_grp = parser.add_mutually_exclusive_group() + default_router_grp.add_argument( + '--default', + action='store_true', + help='Specify if this network should be used as ' + 'the default external network') + default_router_grp.add_argument( + '--no-default', + action='store_true', + help='Do not use the network as the default external network.') return parser def take_action(self, parsed_args): diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index 8a75101b5c..7d0f8717f5 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -482,12 +482,16 @@ def test_set_this(self): '--enable', '--name', 'noob', '--share', + '--external', + '--default', ] verifylist = [ ('network', self._network.name), ('enable', True), ('name', 'noob'), ('share', True), + ('external', True), + ('default', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -497,6 +501,8 @@ def test_set_this(self): 'name': 'noob', 'admin_state_up': True, 'shared': True, + 'router:external': True, + 'is_default': True, } self.network.update_network.assert_called_once_with( self._network, **attrs) @@ -507,11 +513,13 @@ def test_set_that(self): self._network.name, '--disable', '--no-share', + '--internal', ] verifylist = [ ('network', self._network.name), ('disable', True), ('no_share', True), + ('internal', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -520,6 +528,7 @@ def test_set_that(self): attrs = { 'admin_state_up': False, 'shared': False, + 'router:external': False, } self.network.update_network.assert_called_once_with( self._network, **attrs) diff --git a/releasenotes/notes/bug-1545537-7a66219d263bb1e5.yaml b/releasenotes/notes/bug-1545537-7a66219d263bb1e5.yaml index db0e22a1cd..30056eb7d0 100644 --- a/releasenotes/notes/bug-1545537-7a66219d263bb1e5.yaml +++ b/releasenotes/notes/bug-1545537-7a66219d263bb1e5.yaml @@ -2,6 +2,7 @@ features: - | Add external network options ``--external|--internal`` and ``--external`` - suboptions ``--default|--no-default`` to the ``network create`` command. - These options are available for Networkv2 only. - [Bug `1545537 `_] + suboptions ``--default|--no-default`` to the ``network create`` and + ``network set`` commands. + These options are available for Network version 2 only. + [Bug `1545537 `_] \ No newline at end of file From 926330d3726e9ce7001190730c69bf6ee45b422a Mon Sep 17 00:00:00 2001 From: Tim Burke Date: Thu, 14 Apr 2016 14:18:16 -0700 Subject: [PATCH 0811/3095] Propagate AttributeErrors when lazily loading plugins Previously, if an AttributeError was raised in a plugin's make_client method, the plugin simply wouldn't be an attribute of the ClientManager, producing tracebacks like Traceback (most recent call last): File ".../openstackclient/shell.py", line 118, in run ret_val = super(OpenStackShell, self).run(argv) ... File ".../openstackclient/object/v1/container.py", line 150, in take_action data = self.app.client_manager.object_store.container_list( File ".../openstackclient/common/clientmanager.py", line 66, in __getattr__ raise AttributeError(name) AttributeError: object_store This made writing minimal third-party auth plugins difficult, as it obliterated the original AttributeError. Now, AttributeErrors that are raised during plugin initialization will be re-raised as PluginAttributeErrors, and the original traceback will be preserved. This gives much more useful information to plugin developers, as in Traceback (most recent call last): File ".../openstackclient/shell.py", line 118, in run ret_val = super(OpenStackShell, self).run(argv) ... File ".../openstackclient/object/v1/container.py", line 150, in take_action data = self.app.client_manager.object_store.container_list( File ".../openstackclient/common/clientmanager.py", line 57, in __get__ err_val, err_tb) File ".../openstackclient/common/clientmanager.py", line 51, in __get__ self._handle = self.factory(instance) File ".../openstackclient/object/client.py", line 35, in make_client interface=instance._interface, File ".../openstackclient/common/clientmanager.py", line 258, in get_endpoint_for_service_type endpoint = self.auth_ref.service_catalog.url_for( PluginAttributeError: 'NoneType' object has no attribute 'url_for' Change-Id: I0eee7eba6eccc6d471a699a381185c4e76da10bd --- openstackclient/common/clientmanager.py | 10 +++++++++- openstackclient/common/exceptions.py | 7 +++++++ openstackclient/tests/common/test_clientmanager.py | 8 ++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index 6d23b55e64..8b0fb921be 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -22,8 +22,10 @@ from oslo_utils import strutils import requests +import six from openstackclient.api import auth +from openstackclient.common import exceptions from openstackclient.common import session as osc_session from openstackclient.identity import client as identity_client @@ -45,7 +47,13 @@ def __init__(self, factory): def __get__(self, instance, owner): # Tell the ClientManager to login to keystone if self._handle is None: - self._handle = self.factory(instance) + try: + self._handle = self.factory(instance) + except AttributeError as err: + # Make sure the failure propagates. Otherwise, the plugin just + # quietly isn't there. + new_err = exceptions.PluginAttributeError(err) + six.reraise(new_err.__class__, new_err, sys.exc_info()[2]) return self._handle diff --git a/openstackclient/common/exceptions.py b/openstackclient/common/exceptions.py index 5f81e6a6e9..bdc33ddb54 100644 --- a/openstackclient/common/exceptions.py +++ b/openstackclient/common/exceptions.py @@ -24,6 +24,13 @@ class AuthorizationFailure(Exception): pass +class PluginAttributeError(Exception): + """A plugin threw an AttributeError while being lazily loaded.""" + # This *must not* inherit from AttributeError; + # that would defeat the whole purpose. + pass + + class NoTokenLookupException(Exception): """This does not support looking up endpoints from an existing token.""" pass diff --git a/openstackclient/tests/common/test_clientmanager.py b/openstackclient/tests/common/test_clientmanager.py index 6fc5b41e69..fa6c3fcc25 100644 --- a/openstackclient/tests/common/test_clientmanager.py +++ b/openstackclient/tests/common/test_clientmanager.py @@ -41,6 +41,7 @@ class Container(object): attr = clientmanager.ClientCache(lambda x: object()) + buggy_attr = clientmanager.ClientCache(lambda x: x.foo) def __init__(self): pass @@ -72,6 +73,13 @@ def test_singleton(self): c = Container() self.assertEqual(c.attr, c.attr) + def test_attribute_error_propagates(self): + c = Container() + err = self.assertRaises(exc.PluginAttributeError, + getattr, c, 'buggy_attr') + self.assertNotIsInstance(err, AttributeError) + self.assertEqual("'Container' object has no attribute 'foo'", str(err)) + class TestClientManager(utils.TestCase): From df3ee61be37fc4e841b7c20c4847d50cb1fc23fe Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Fri, 15 Apr 2016 16:10:09 +0800 Subject: [PATCH 0812/3095] Remove methods argument from FakeHypervisorStats methods argument is not necessary in FakeHypervisorStats class. Remove it. Change-Id: I288f71b412beb3e583e957a9fd99210bc6f3f543 --- openstackclient/tests/compute/v2/fakes.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 1cca278772..7f39bad0be 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -284,7 +284,7 @@ class FakeHypervisorStats(object): """Fake one or more hypervisor stats.""" @staticmethod - def create_one_hypervisor_stats(attrs={}, methods={}): + def create_one_hypervisor_stats(attrs={}): """Create a fake hypervisor stats. :param Dictionary attrs: @@ -311,7 +311,6 @@ def create_one_hypervisor_stats(attrs={}, methods={}): # Set default method. hypervisor_stats_method = {'to_dict': stats_info} - hypervisor_stats_method.update(methods) hypervisor_stats = fakes.FakeResource( info=copy.deepcopy(stats_info), From e12837067698062ce3fea89dd543f1cab39866c1 Mon Sep 17 00:00:00 2001 From: Jirayut Nimsaeng Date: Mon, 11 Apr 2016 15:54:23 +0700 Subject: [PATCH 0813/3095] Fix wrong attribute name and add functional test for --snapshot Change-Id: I91f2091ef06a55bcf5373d1beeea2dd81e9f1334 Closes-Bug: #1567895 --- functional/tests/volume/v2/test_volume.py | 54 ++++++++++++++++++- .../tests/volume/v2/test_volume.py | 43 +++++++++++++++ 2 files changed, 95 insertions(+), 2 deletions(-) diff --git a/functional/tests/volume/v2/test_volume.py b/functional/tests/volume/v2/test_volume.py index b07751836d..9c7f11a1b4 100644 --- a/functional/tests/volume/v2/test_volume.py +++ b/functional/tests/volume/v2/test_volume.py @@ -11,6 +11,7 @@ # under the License. import os +import time import uuid from functional.common import test @@ -20,6 +21,8 @@ class VolumeTests(test.TestCase): """Functional tests for volume. """ NAME = uuid.uuid4().hex + SNAPSHOT_NAME = uuid.uuid4().hex + VOLUME_FROM_SNAPSHOT_NAME = uuid.uuid4().hex OTHER_NAME = uuid.uuid4().hex HEADERS = ['"Display Name"'] FIELDS = ['name'] @@ -28,17 +31,20 @@ class VolumeTests(test.TestCase): def setUpClass(cls): os.environ['OS_VOLUME_API_VERSION'] = '2' opts = cls.get_show_opts(cls.FIELDS) + + # Create test volume raw_output = cls.openstack('volume create --size 1 ' + cls.NAME + opts) expected = cls.NAME + '\n' cls.assertOutput(expected, raw_output) @classmethod def tearDownClass(cls): - # Rename test + # Rename test volume raw_output = cls.openstack( 'volume set --name ' + cls.OTHER_NAME + ' ' + cls.NAME) cls.assertOutput('', raw_output) - # Delete test + + # Delete test volume raw_output = cls.openstack('volume delete ' + cls.OTHER_NAME) cls.assertOutput('', raw_output) @@ -78,3 +84,47 @@ def test_volume_set_size(self): opts = self.get_show_opts(["name", "size"]) raw_output = self.openstack('volume show ' + self.NAME + opts) self.assertEqual(self.NAME + "\n2\n", raw_output) + + def test_volume_snapshot(self): + opts = self.get_show_opts(self.FIELDS) + + # Create snapshot from test volume + raw_output = self.openstack('snapshot create ' + self.NAME + + ' --name ' + self.SNAPSHOT_NAME + opts) + expected = self.SNAPSHOT_NAME + '\n' + self.assertOutput(expected, raw_output) + self.wait_for("snapshot", self.SNAPSHOT_NAME, "available") + + # Create volume from snapshot + raw_output = self.openstack('volume create --size 2 --snapshot ' + + self.SNAPSHOT_NAME + ' ' + + self.VOLUME_FROM_SNAPSHOT_NAME + opts) + expected = self.VOLUME_FROM_SNAPSHOT_NAME + '\n' + self.assertOutput(expected, raw_output) + self.wait_for("volume", self.VOLUME_FROM_SNAPSHOT_NAME, "available") + + # Delete volume that create from snapshot + raw_output = self.openstack('volume delete ' + + self.VOLUME_FROM_SNAPSHOT_NAME) + self.assertOutput('', raw_output) + + # Delete test snapshot + raw_output = self.openstack('snapshot delete ' + self.SNAPSHOT_NAME) + self.assertOutput('', raw_output) + + def wait_for(self, check_type, check_name, desired_status, wait=120, + interval=5, failures=['ERROR']): + status = "notset" + total_sleep = 0 + opts = self.get_show_opts(['status']) + while total_sleep < wait: + status = self.openstack(check_type + ' show ' + check_name + opts) + status = status.rstrip() + print('Checking {} {} Waiting for {} current status: {}' + .format(check_type, check_name, desired_status, status)) + if status == desired_status: + break + self.assertNotIn(status, failures) + time.sleep(interval) + total_sleep += interval + self.assertEqual(desired_status, status) diff --git a/openstackclient/tests/volume/v2/test_volume.py b/openstackclient/tests/volume/v2/test_volume.py index 12253806b9..e4ac7c10f1 100644 --- a/openstackclient/tests/volume/v2/test_volume.py +++ b/openstackclient/tests/volume/v2/test_volume.py @@ -14,6 +14,7 @@ import copy +import mock from mock import call from openstackclient.common import utils @@ -40,6 +41,9 @@ def setUp(self): self.images_mock = self.app.client_manager.image.images self.images_mock.reset_mock() + self.snapshots_mock = self.app.client_manager.volume.volume_snapshots + self.snapshots_mock.reset_mock() + def setup_volumes_mock(self, count): volumes = volume_fakes.FakeVolume.create_volumes(count=count) @@ -376,6 +380,45 @@ def test_volume_create_image_name(self): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, data) + def test_volume_create_with_snapshot(self): + arglist = [ + '--size', str(self.new_volume.size), + '--snapshot', volume_fakes.snapshot_id, + self.new_volume.name, + ] + verifylist = [ + ('size', self.new_volume.size), + ('snapshot', volume_fakes.snapshot_id), + ('name', self.new_volume.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + fake_snapshot = mock.Mock() + fake_snapshot.id = volume_fakes.snapshot_id + self.snapshots_mock.get.return_value = fake_snapshot + + # In base command class ShowOne in cliff, abstract method take_action() + # returns a two-part tuple with a tuple of column names and a tuple of + # data to be shown. + columns, data = self.cmd.take_action(parsed_args) + + self.volumes_mock.create.assert_called_once_with( + size=self.new_volume.size, + snapshot_id=fake_snapshot.id, + name=self.new_volume.name, + description=None, + volume_type=None, + user_id=None, + project_id=None, + availability_zone=None, + metadata=None, + imageRef=None, + source_volid=None + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) + class TestVolumeDelete(TestVolume): From 92950b6f5fcaae972ef51235223394b501844354 Mon Sep 17 00:00:00 2001 From: Sheel Rana Date: Sat, 16 Apr 2016 23:00:00 +0530 Subject: [PATCH 0814/3095] Add support for removing volume-type-access OSC does not support to remove volume type access to project. This feature will provide support to remove volume type access from project. Closes-Bug:#1554890 Implements: bp cinder-command-support Change-Id: I029a4292da05f028e8937962cb845ec6e00b0279 --- doc/source/command-objects/volume-type.rst | 13 +++++ openstackclient/tests/volume/v2/test_type.py | 58 +++++++++++++++++++ openstackclient/volume/v2/volume_type.py | 46 ++++++++++++++- .../type_access_remove-a6a55921d2dae48d.yaml | 18 ++++++ 4 files changed, 132 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/type_access_remove-a6a55921d2dae48d.yaml diff --git a/doc/source/command-objects/volume-type.rst b/doc/source/command-objects/volume-type.rst index 64b1bd52d9..b7aea63262 100644 --- a/doc/source/command-objects/volume-type.rst +++ b/doc/source/command-objects/volume-type.rst @@ -151,12 +151,25 @@ Unset volume type properties os volume type unset [--property ] + [--project ] + [--project-domain ] .. option:: --property Property to remove from volume type (repeat option to remove multiple properties) +.. option:: --project + + Removes volume type access from project (name or ID) (admin only) + + *Volume version 2 only* + +.. option:: --project-domain + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + .. _volume_type_unset-volume-type: .. describe:: diff --git a/openstackclient/tests/volume/v2/test_type.py b/openstackclient/tests/volume/v2/test_type.py index 448da4320e..f0ca9b0108 100644 --- a/openstackclient/tests/volume/v2/test_type.py +++ b/openstackclient/tests/volume/v2/test_type.py @@ -394,6 +394,14 @@ def setUp(self): loaded=True ) + # Return a project + self.projects_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.PROJECT), + loaded=True, + ) + + # Get the command object to test self.cmd = volume_type.UnsetVolumeType(self.app, None) def test_type_unset(self): @@ -413,3 +421,53 @@ def test_type_unset(self): result = self.types_mock.get.return_value._keys self.assertNotIn('property', result) + + def test_type_unset_project_access(self): + arglist = [ + '--project', identity_fakes.project_id, + volume_fakes.type_id, + ] + verifylist = [ + ('project', identity_fakes.project_id), + ('volume_type', volume_fakes.type_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.assertIsNone(result) + + self.types_access_mock.remove_project_access.assert_called_with( + volume_fakes.type_id, + identity_fakes.project_id, + ) + + def test_type_unset_not_called_without_project_argument(self): + arglist = [ + '--project', '', + volume_fakes.type_id, + ] + verifylist = [ + ('project', ''), + ('volume_type', volume_fakes.type_id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.assertIsNone(result) + + self.assertFalse(self.types_access_mock.remove_project_access.called) + + def test_type_unset_failed_with_missing_volume_type_argument(self): + arglist = [ + '--project', 'identity_fakes.project_id', + ] + verifylist = [ + ('project', 'identity_fakes.project_id'), + ] + + self.assertRaises(tests_utils.ParserException, + self.check_parser, + self.cmd, + arglist, + verifylist) diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index 203974dad5..3050051860 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -261,17 +261,57 @@ def get_parser(self, prog_name): parser.add_argument( '--property', metavar='', - default=[], - required=True, help='Remove a property from this volume type ' '(repeat option to remove multiple properties)', ) + parser.add_argument( + '--project', + metavar='', + help='Removes volume type access to project (name or ID) ' + ' (admin only)', + ) + identity_common.add_project_domain_option_to_parser(parser) + return parser def take_action(self, parsed_args): volume_client = self.app.client_manager.volume + identity_client = self.app.client_manager.identity + volume_type = utils.find_resource( volume_client.volume_types, parsed_args.volume_type, ) - volume_type.unset_keys(parsed_args.property) + + if (not parsed_args.property + and not parsed_args.project): + self.app.log.error("No changes requested\n") + return + + result = 0 + if parsed_args.property: + try: + volume_type.unset_keys(parsed_args.property) + except Exception as e: + self.app.log.error("Failed to unset volume type property: " + + str(e)) + result += 1 + + if parsed_args.project: + project_info = None + try: + project_info = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain) + + volume_client.volume_type_access.remove_project_access( + volume_type.id, project_info.id) + except Exception as e: + self.app.log.error("Failed to remove volume type access from" + " project: " + str(e)) + result += 1 + + if result > 0: + raise exceptions.CommandError("Command Failed: One or more of the" + " operations failed") diff --git a/releasenotes/notes/type_access_remove-a6a55921d2dae48d.yaml b/releasenotes/notes/type_access_remove-a6a55921d2dae48d.yaml new file mode 100644 index 0000000000..7dfc949bbb --- /dev/null +++ b/releasenotes/notes/type_access_remove-a6a55921d2dae48d.yaml @@ -0,0 +1,18 @@ +--- +features: + - | + Added support for removing volume type access to project. + + By default, volumes types are public. + To create a private volume type the ``--private`` option must be included + in the ``volume type create`` command. + + To control access to a private volume type, user needs to add or remove + access of a private volume type to project. + + This feature enables user to remove private volume type access to a + project using below command: + + ``volume type unset --project `` + + [Bug 1554890 'https://bugs.launchpad.net/python-openstackclient/+bug/1554890'_] From 89445855acffc5ae4cf87dc501c09f3434d08bad Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 16 Apr 2016 10:59:37 +0800 Subject: [PATCH 0815/3095] State i18() changes and help messages improved Add _() to wrap help message in network commands. And also some improvement for help message. Change-Id: Ib3e498f5976ba98c44fd4eec2d1623263b3db53e Partial-bug: 1570924 --- doc/source/command-objects/network.rst | 15 ++- doc/source/command-objects/port.rst | 10 +- doc/source/command-objects/router.rst | 2 +- doc/source/command-objects/subnet-pool.rst | 8 +- doc/source/command-objects/subnet.rst | 4 +- openstackclient/network/v2/floating_ip.py | 19 ++-- openstackclient/network/v2/network.py | 85 +++++++++-------- openstackclient/network/v2/port.py | 81 +++++++++------- openstackclient/network/v2/router.py | 63 ++++++------ openstackclient/network/v2/security_group.py | 19 ++-- .../network/v2/security_group_rule.py | 28 +++--- openstackclient/network/v2/subnet.py | 95 ++++++++++--------- openstackclient/network/v2/subnet_pool.py | 39 ++++---- 13 files changed, 248 insertions(+), 220 deletions(-) diff --git a/doc/source/command-objects/network.rst b/doc/source/command-objects/network.rst index 6d3f505ab3..e2f5eaf0ee 100644 --- a/doc/source/command-objects/network.rst +++ b/doc/source/command-objects/network.rst @@ -59,7 +59,7 @@ Create new network .. option:: --availability-zone-hint Availability Zone in which to create this network - (requires the Network Availability Zone extension, + (Network Availability Zone extension required, repeat option to set multiple availability zones) *Network version 2 only* @@ -72,8 +72,8 @@ Create new network .. option:: --external - Set this network as an external network. - Requires the "external-net" extension to be enabled. + Set this network as an external network + (external-net extension required) *Network version 2 only* @@ -92,8 +92,8 @@ Create new network .. option:: --no-default - Do not use the network as the default external network. - By default, no network is set as an external network. + Do not use the network as the default external network + (default) *Network version 2 only* @@ -197,7 +197,7 @@ Set network properties .. option:: --external Set this network as an external network. - Requires the "external-net" extension to be enabled. + (external-net extension required) .. option:: --internal @@ -205,8 +205,7 @@ Set network properties .. option:: --default - Specify if this network should be used as - the default external network + Set the network as the default external network .. option:: --no-default diff --git a/doc/source/command-objects/port.rst b/doc/source/command-objects/port.rst index cc7723eaf6..36e830822e 100644 --- a/doc/source/command-objects/port.rst +++ b/doc/source/command-objects/port.rst @@ -45,8 +45,8 @@ Create new port .. option:: --vnic-type - VNIC type for this port (direct | direct-physical | macvtap | normal | baremetal). - If unspecified during port creation, default value will be 'normal'. + VNIC type for this port (direct | direct-physical | macvtap | normal | baremetal, + default: normal) .. option:: --binding-profile @@ -141,7 +141,7 @@ Set port properties .. option:: --no-fixed-ip - Clear existing information of fixed-ips + Clear existing information of fixed IP addresses .. option:: --device @@ -153,8 +153,8 @@ Set port properties .. option:: --vnic-type - VNIC type for this port (direct | direct-physical | macvtap | normal | baremetal). - If unspecified during port creation, default value will be 'normal'. + VNIC type for this port (direct | direct-physical | macvtap | normal | baremetal, + default: normal) .. option:: --binding-profile diff --git a/doc/source/command-objects/router.rst b/doc/source/command-objects/router.rst index dc645712ec..1503516e44 100644 --- a/doc/source/command-objects/router.rst +++ b/doc/source/command-objects/router.rst @@ -87,7 +87,7 @@ Create new router .. option:: --availability-zone-hint Availability Zone in which to create this router - (requires the Router Availability Zone extension, + (Router Availability Zone extension required, repeat option to set multiple availability zones) .. _router_create-name: diff --git a/doc/source/command-objects/subnet-pool.rst b/doc/source/command-objects/subnet-pool.rst index 80a9c013bb..3b7d0e91ed 100644 --- a/doc/source/command-objects/subnet-pool.rst +++ b/doc/source/command-objects/subnet-pool.rst @@ -49,8 +49,8 @@ Create subnet pool .. option:: --address-scope - Set address scope associated with the subnet pool (name or ID). - Prefixes must be unique across address scopes. + Set address scope associated with the subnet pool (name or ID), + prefixes must be unique across address scopes .. _subnet_pool_create-name: .. describe:: @@ -128,8 +128,8 @@ Set subnet pool properties .. option:: --address-scope - Set address scope associated with the subnet pool (name or ID). - Prefixes must be unique across address scopes. + Set address scope associated with the subnet pool (name or ID), + prefixes must be unique across address scopes .. option:: --no-address-scope diff --git a/doc/source/command-objects/subnet.rst b/doc/source/command-objects/subnet.rst index 50be5b0a96..35134f4699 100644 --- a/doc/source/command-objects/subnet.rst +++ b/doc/source/command-objects/subnet.rst @@ -206,7 +206,7 @@ Set subnet properties subnet show ----------- -Show subnet details +Display subnet details .. program:: subnet show .. code:: bash @@ -217,4 +217,4 @@ Show subnet details .. _subnet_show-subnet: .. describe:: - Subnet to show (name or ID) + Subnet to display (name or ID) diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index b21d6e968d..21f8659910 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -14,6 +14,7 @@ """IP Floating action implementations""" from openstackclient.common import utils +from openstackclient.i18n import _ from openstackclient.network import common @@ -63,7 +64,7 @@ def update_parser_common(self, parser): parser.add_argument( 'network', metavar='', - help='Network to allocate floating IP from (name or ID)', + help=_("Network to allocate floating IP from (name or ID)") ) return parser @@ -71,26 +72,26 @@ def update_parser_network(self, parser): parser.add_argument( '--subnet', metavar='', - help="Subnet on which you want to create the floating IP " - "(name or ID)" + help=_("Subnet on which you want to create the floating IP " + "(name or ID)") ) parser.add_argument( '--port', metavar='', - help="Port to be associated with the floating IP " - "(name or ID)" + help=_("Port to be associated with the floating IP " + "(name or ID)") ) parser.add_argument( '--floating-ip-address', metavar='', dest='floating_ip_address', - help="Floating IP address" + help=_("Floating IP address") ) parser.add_argument( '--fixed-ip-address', metavar='', dest='fixed_ip_address', - help="Fixed IP address mapped to the floating IP" + help=_("Fixed IP address mapped to the floating IP") ) return parser @@ -115,7 +116,7 @@ def update_parser_common(self, parser): parser.add_argument( 'floating_ip', metavar="", - help=("Floating IP to delete (IP address or ID)") + help=_("Floating IP to delete (IP address or ID)") ) return parser @@ -189,7 +190,7 @@ def update_parser_common(self, parser): parser.add_argument( 'floating_ip', metavar="", - help=("Floating IP to display (IP address or ID)") + help=_("Floating IP to display (IP address or ID)") ) return parser diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index afac471a7d..d57a1ed6c2 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -16,6 +16,7 @@ from openstackclient.common import command from openstackclient.common import exceptions from openstackclient.common import utils +from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common @@ -110,19 +111,19 @@ def update_parser_common(self, parser): parser.add_argument( 'name', metavar='', - help='New network name', + help=_("New network name") ) share_group = parser.add_mutually_exclusive_group() share_group.add_argument( '--share', action='store_true', default=None, - help='Share the network between projects', + help=_("Share the network between projects") ) share_group.add_argument( '--no-share', action='store_true', - help='Do not share the network between projects', + help=_("Do not share the network between projects") ) return parser @@ -132,17 +133,17 @@ def update_parser_network(self, parser): '--enable', action='store_true', default=True, - help='Enable network (default)', + help=_("Enable network (default)") ) admin_group.add_argument( '--disable', action='store_true', - help='Disable network', + help=_("Disable network") ) parser.add_argument( '--project', metavar='', - help="Owner's project (name or ID)" + help=_("Owner's project (name or ID)") ) identity_common.add_project_domain_option_to_parser(parser) parser.add_argument( @@ -150,58 +151,65 @@ def update_parser_network(self, parser): action='append', dest='availability_zone_hints', metavar='', - help='Availability Zone in which to create this network ' - '(requires the Network Availability Zone extension, ' - 'repeat option to set multiple availability zones)', + help=_("Availability Zone in which to create this network " + "(Network Availability Zone extension required, " + "repeat option to set multiple availability zones)") ) external_router_grp = parser.add_mutually_exclusive_group() external_router_grp.add_argument( '--external', action='store_true', - help='Set this network as an external network. ' - 'Requires the "external-net" extension to be enabled.') + help=_("Set this network as an external network " + "(external-net extension required)") + ) external_router_grp.add_argument( '--internal', action='store_true', - help='Set this network as an internal network (default)') + help=_("Set this network as an internal network (default)") + ) default_router_grp = parser.add_mutually_exclusive_group() default_router_grp.add_argument( '--default', action='store_true', - help='Specify if this network should be used as ' - 'the default external network') + help=_("Specify if this network should be used as " + "the default external network") + ) default_router_grp.add_argument( '--no-default', action='store_true', - help='Do not use the network as the default external network.' - 'By default, no network is set as an external network.') + help=_("Do not use the network as the default external network. " + "(default)") + ) parser.add_argument( '--provider-network-type', metavar='', choices=['flat', 'gre', 'local', 'vlan', 'vxlan'], - help='The physical mechanism by which the virtual network ' - 'is implemented. The supported options are: ' - 'flat, gre, local, vlan, vxlan') + help=_("The physical mechanism by which the virtual network " + "is implemented. The supported options are: " + "flat, gre, local, vlan, vxlan") + ) parser.add_argument( '--provider-physical-network', metavar='', dest='physical_network', - help='Name of the physical network over which the virtual ' - 'network is implemented') + help=_("Name of the physical network over which the virtual " + "network is implemented") + ) parser.add_argument( '--provider-segment', metavar='', dest='segmentation_id', - help='VLAN ID for VLAN networks or Tunnel ID for GRE/VXLAN ' - 'networks') + help=_("VLAN ID for VLAN networks or Tunnel ID for GRE/VXLAN " + "networks") + ) return parser def update_parser_compute(self, parser): parser.add_argument( '--subnet', metavar='', - help="IPv4 subnet for fixed IPs (in CIDR notation)" + help=_("IPv4 subnet for fixed IPs (in CIDR notation)") ) return parser @@ -350,57 +358,60 @@ def get_parser(self, prog_name): parser.add_argument( 'network', metavar="", - help=("Network to modify (name or ID)") + help=_("Network to modify (name or ID)") ) parser.add_argument( '--name', metavar='', - help='Set network name', + help=_("Set network name") ) admin_group = parser.add_mutually_exclusive_group() admin_group.add_argument( '--enable', action='store_true', default=None, - help='Enable network', + help=_("Enable network") ) admin_group.add_argument( '--disable', action='store_true', - help='Disable network', + help=_("Disable network") ) share_group = parser.add_mutually_exclusive_group() share_group.add_argument( '--share', action='store_true', default=None, - help='Share the network between projects', + help=_("Share the network between projects") ) share_group.add_argument( '--no-share', action='store_true', - help='Do not share the network between projects', + help=_("Do not share the network between projects") ) external_router_grp = parser.add_mutually_exclusive_group() external_router_grp.add_argument( '--external', action='store_true', - help='Set this network as an external network. ' - 'Requires the "external-net" extension to be enabled.') + help=_("Set this network as an external network " + "(external-net extension required)") + ) external_router_grp.add_argument( '--internal', action='store_true', - help='Set this network as an internal network') + help=_("Set this network as an internal network") + ) default_router_grp = parser.add_mutually_exclusive_group() default_router_grp.add_argument( '--default', action='store_true', - help='Specify if this network should be used as ' - 'the default external network') + help=_("Set the network as the default external network") + ) default_router_grp.add_argument( '--no-default', action='store_true', - help='Do not use the network as the default external network.') + help=_("Do not use the network as the default external network") + ) return parser def take_action(self, parsed_args): @@ -422,7 +433,7 @@ def update_parser_common(self, parser): parser.add_argument( 'network', metavar="", - help=("Network to display (name or ID)") + help=_("Network to display (name or ID)") ) return parser diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index c54cb257a1..fbfce4d38f 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -158,7 +158,7 @@ def _add_updatable_args(parser): device_group.add_argument( '--device', metavar='', - help='Port device ID', + help=_("Port device ID") ) device_group.add_argument( '--device-id', @@ -168,22 +168,23 @@ def _add_updatable_args(parser): parser.add_argument( '--device-owner', metavar='', - help='Device owner of this port') + help=_("Device owner of this port") + ) parser.add_argument( '--vnic-type', metavar='', choices=['direct', 'direct-physical', 'macvtap', 'normal', 'baremetal'], - help="VNIC type for this port (direct | direct-physical |" - " macvtap | normal | baremetal). If unspecified during" - " port creation, default value will be 'normal'.") + help=_("VNIC type for this port (direct | direct-physical | " + "macvtap | normal | baremetal, default: normal)") + ) # NOTE(dtroyer): --host-id is deprecated in Mar 2016. Do not # remove before 3.x release or Mar 2017. host_group = parser.add_mutually_exclusive_group() host_group.add_argument( '--host', metavar='', - help='Allocate port on host (ID only)', + help=_("Allocate port on host (ID only)") ) host_group.add_argument( '--host-id', @@ -202,47 +203,53 @@ def get_parser(self, prog_name): '--network', metavar='', required=True, - help='Network this port belongs to (name or ID)') + help=_("Network this port belongs to (name or ID)") + ) _add_updatable_args(parser) parser.add_argument( '--fixed-ip', metavar='subnet=,ip-address=', action=parseractions.MultiKeyValueAction, optional_keys=['subnet', 'ip-address'], - help='Desired IP and/or subnet (name or ID) for this port: ' - 'subnet=,ip-address= ' - '(repeat option to set multiple fixed IP addresses)') + help=_("Desired IP and/or subnet (name or ID) for this port: " + "subnet=,ip-address= " + "(repeat option to set multiple fixed IP addresses)") + ) parser.add_argument( '--binding-profile', metavar='', action=parseractions.KeyValueAction, - help='Custom data to be passed as binding:profile: = ' - '(repeat option to set multiple binding:profile data)') + help=_("Custom data to be passed as binding:profile: " + "= " + "(repeat option to set multiple binding:profile data)") + ) admin_group = parser.add_mutually_exclusive_group() admin_group.add_argument( '--enable', action='store_true', default=True, - help='Enable port (default)', + help=_("Enable port (default)") ) admin_group.add_argument( '--disable', action='store_true', - help='Disable port', + help=_("Disable port") ) parser.add_argument( '--mac-address', metavar='', - help='MAC address of this port') + help=_("MAC address of this port") + ) parser.add_argument( '--project', metavar='', - help="Owner's project (name or ID)") + help=_("Owner's project (name or ID)") + ) + identity_common.add_project_domain_option_to_parser(parser) parser.add_argument( 'name', metavar='', help='Name of this port') - identity_common.add_project_domain_option_to_parser(parser) # TODO(singhj): Add support for extended options: # qos,security groups,dhcp, address pairs return parser @@ -270,7 +277,7 @@ def get_parser(self, prog_name): 'port', metavar="", nargs="+", - help=("Port(s) to delete (name or ID)") + help=_("Port(s) to delete (name or ID)") ) return parser @@ -291,7 +298,7 @@ def get_parser(self, prog_name): '--router', metavar='', dest='router', - help='List only ports attached to this router (name or ID)', + help=_("List only ports attached to this router (name or ID)") ) return parser @@ -337,21 +344,17 @@ def get_parser(self, prog_name): '--enable', action='store_true', default=None, - help='Enable port', + help=_("Enable port") ) admin_group.add_argument( '--disable', action='store_true', - help='Disable port', + help=_("Disable port") ) parser.add_argument( '--name', metavar="", - help=('Set port name')) - parser.add_argument( - 'port', - metavar="", - help=("Port to modify (name or ID)") + help=_("Set port name") ) fixed_ip = parser.add_mutually_exclusive_group() fixed_ip.add_argument( @@ -359,24 +362,34 @@ def get_parser(self, prog_name): metavar='subnet=,ip-address=', action=parseractions.MultiKeyValueAction, optional_keys=['subnet', 'ip-address'], - help='Desired IP and/or subnet (name or ID) for this port: ' - 'subnet=,ip-address= ' - '(repeat option to set multiple fixed IP addresses)') + help=_("Desired IP and/or subnet (name or ID) for this port: " + "subnet=,ip-address= " + "(repeat option to set multiple fixed IP addresses)") + ) fixed_ip.add_argument( '--no-fixed-ip', action='store_true', - help='Clear existing information of fixed-ips') + help=_("Clear existing information of fixed IP addresses") + ) binding_profile = parser.add_mutually_exclusive_group() binding_profile.add_argument( '--binding-profile', metavar='', action=parseractions.KeyValueAction, - help='Custom data to be passed as binding:profile: = ' - '(repeat option to set multiple binding:profile data)') + help=_("Custom data to be passed as binding:profile: " + "= " + "(repeat option to set multiple binding:profile data)") + ) binding_profile.add_argument( '--no-binding-profile', action='store_true', - help='Clear existing information of binding:profile') + help=_("Clear existing information of binding:profile") + ) + parser.add_argument( + 'port', + metavar="", + help=_("Port to modify (name or ID)") + ) return parser def take_action(self, parsed_args): @@ -413,7 +426,7 @@ def get_parser(self, prog_name): parser.add_argument( 'port', metavar="", - help="Port to display (name or ID)" + help=_("Port to display (name or ID)") ) return parser diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 2ededae87e..56630a2303 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -19,6 +19,7 @@ from openstackclient.common import exceptions from openstackclient.common import parseractions from openstackclient.common import utils +from openstackclient.i18n import _ from openstackclient.identity import common as identity_common @@ -95,12 +96,12 @@ def get_parser(self, prog_name): parser.add_argument( 'router', metavar='', - help="Router to which port will be added (name or ID)", + help=_("Router to which port will be added (name or ID)") ) parser.add_argument( 'port', metavar='', - help="Port to be added (name or ID)", + help=_("Port to be added (name or ID)") ) return parser @@ -119,12 +120,12 @@ def get_parser(self, prog_name): parser.add_argument( 'router', metavar='', - help="Router to which subnet will be added (name or ID)", + help=_("Router to which subnet will be added (name or ID)") ) parser.add_argument( 'subnet', metavar='', - help="Subnet to be added (name or ID)", + help=_("Subnet to be added (name or ID)") ) return parser @@ -146,43 +147,43 @@ def get_parser(self, prog_name): parser.add_argument( 'name', metavar='', - help="New router name", + help=_("New router name") ) admin_group = parser.add_mutually_exclusive_group() admin_group.add_argument( '--enable', action='store_true', default=True, - help="Enable router (default)", + help=_("Enable router (default)") ) admin_group.add_argument( '--disable', action='store_true', - help="Disable router", + help=_("Disable router") ) parser.add_argument( '--distributed', dest='distributed', action='store_true', default=False, - help="Create a distributed router", + help=_("Create a distributed router") ) parser.add_argument( '--project', metavar='', - help="Owner's project (name or ID)", + help=_("Owner's project (name or ID)") ) + identity_common.add_project_domain_option_to_parser(parser) parser.add_argument( '--availability-zone-hint', metavar='', action='append', dest='availability_zone_hints', - help='Availability Zone in which to create this router ' - '(requires the Router Availability Zone extension, ' - 'repeat option to set multiple availability zones)', + help=_("Availability Zone in which to create this router " + "(Router Availability Zone extension required, " + "repeat option to set multiple availability zones)") ) - identity_common.add_project_domain_option_to_parser(parser) return parser def take_action(self, parsed_args): @@ -206,7 +207,7 @@ def get_parser(self, prog_name): 'router', metavar="", nargs="+", - help=("Router(s) to delete (name or ID)") + help=_("Router(s) to delete (name or ID)") ) return parser @@ -226,7 +227,7 @@ def get_parser(self, prog_name): '--long', action='store_true', default=False, - help='List additional fields in output', + help=_("List additional fields in output") ) return parser @@ -279,12 +280,12 @@ def get_parser(self, prog_name): parser.add_argument( 'router', metavar='', - help="Router from which port will be removed (name or ID)", + help=_("Router from which port will be removed (name or ID)") ) parser.add_argument( 'port', metavar='', - help="Port to be removed (name or ID).", + help=_("Port to be removed (name or ID)") ) return parser @@ -303,12 +304,12 @@ def get_parser(self, prog_name): parser.add_argument( 'router', metavar='', - help="Router from which the subnet will be removed (name or ID)", + help=_("Router from which the subnet will be removed (name or ID)") ) parser.add_argument( 'subnet', metavar='', - help="Subnet to be removed (name or ID)", + help=_("Subnet to be removed (name or ID)") ) return parser @@ -330,35 +331,35 @@ def get_parser(self, prog_name): parser.add_argument( 'router', metavar="", - help=("Router to modify (name or ID)") + help=_("Router to modify (name or ID)") ) parser.add_argument( '--name', metavar='', - help='Set router name', + help=_("Set router name") ) admin_group = parser.add_mutually_exclusive_group() admin_group.add_argument( '--enable', action='store_true', default=None, - help='Enable router', + help=_("Enable router") ) admin_group.add_argument( '--disable', action='store_true', - help='Disable router', + help=_("Disable router") ) distribute_group = parser.add_mutually_exclusive_group() distribute_group.add_argument( '--distributed', action='store_true', - help="Set router to distributed mode (disabled router only)", + help=_("Set router to distributed mode (disabled router only)") ) distribute_group.add_argument( '--centralized', action='store_true', - help="Set router to centralized mode (disabled router only)", + help=_("Set router to centralized mode (disabled router only)") ) routes_group = parser.add_mutually_exclusive_group() routes_group.add_argument( @@ -368,15 +369,15 @@ def get_parser(self, prog_name): dest='routes', default=None, required_keys=['destination', 'gateway'], - help="Routes associated with the router " - "destination: destination subnet (in CIDR notation) " - "gateway: nexthop IP address " - "(repeat option to set multiple routes)", + help=_("Routes associated with the router " + "destination: destination subnet (in CIDR notation) " + "gateway: nexthop IP address " + "(repeat option to set multiple routes)") ) routes_group.add_argument( '--clear-routes', action='store_true', - help="Clear routes associated with the router", + help=_("Clear routes associated with the router") ) # TODO(tangchen): Support setting 'ha' property in 'router set' @@ -408,7 +409,7 @@ def get_parser(self, prog_name): parser.add_argument( 'router', metavar="", - help="Router to display (name or ID)" + help=_("Router to display (name or ID)") ) return parser diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py index 92498144a5..1ef2754e43 100644 --- a/openstackclient/network/v2/security_group.py +++ b/openstackclient/network/v2/security_group.py @@ -17,6 +17,7 @@ import six from openstackclient.common import utils +from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common from openstackclient.network import utils as network_utils @@ -99,12 +100,12 @@ def update_parser_common(self, parser): parser.add_argument( "name", metavar="", - help="New security group name", + help=_("New security group name") ) parser.add_argument( "--description", metavar="", - help="Security group description", + help=_("Security group description") ) return parser @@ -112,7 +113,7 @@ def update_parser_network(self, parser): parser.add_argument( '--project', metavar='', - help="Owner's project (name or ID)" + help=_("Owner's project (name or ID)") ) identity_common.add_project_domain_option_to_parser(parser) return parser @@ -169,7 +170,7 @@ def update_parser_common(self, parser): parser.add_argument( 'group', metavar='', - help='Security group to delete (name or ID)', + help=_("Security group to delete (name or ID)") ) return parser @@ -204,7 +205,7 @@ def update_parser_compute(self, parser): '--all-projects', action='store_true', default=False, - help='Display information from all projects (admin only)', + help=_("Display information from all projects (admin only)") ) return parser @@ -240,17 +241,17 @@ def update_parser_common(self, parser): parser.add_argument( 'group', metavar='', - help='Security group to modify (name or ID)', + help=_("Security group to modify (name or ID)") ) parser.add_argument( '--name', metavar='', - help='New security group name', + help=_("New security group name") ) parser.add_argument( "--description", metavar="", - help="New security group description", + help=_("New security group description") ) return parser @@ -295,7 +296,7 @@ def update_parser_common(self, parser): parser.add_argument( 'group', metavar='', - help='Security group to display (name or ID)', + help=_("Security group to display (name or ID)") ) return parser diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index 509b197442..67472de05d 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -23,6 +23,7 @@ from openstackclient.common import exceptions from openstackclient.common import parseractions from openstackclient.common import utils +from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common from openstackclient.network import utils as network_utils @@ -78,27 +79,27 @@ def update_parser_common(self, parser): default="tcp", choices=['icmp', 'tcp', 'udp'], type=_convert_to_lowercase, - help="IP protocol (icmp, tcp, udp; default: tcp)", + help=_("IP protocol (icmp, tcp, udp; default: tcp)") ) source_group = parser.add_mutually_exclusive_group() source_group.add_argument( "--src-ip", metavar="", - help="Source IP address block (may use CIDR notation; " - "default for IPv4 rule: 0.0.0.0/0)", + help=_("Source IP address block (may use CIDR notation; " + "default for IPv4 rule: 0.0.0.0/0)") ) source_group.add_argument( "--src-group", metavar="", - help="Source security group (name or ID)", + help=_("Source security group (name or ID)") ) parser.add_argument( "--dst-port", metavar="", default=(0, 0), action=parseractions.RangeAction, - help="Destination port, may be a single port or port range: " - "137:139 (only required for IP protocols tcp and udp)", + help=_("Destination port, may be a single port or port range: " + "137:139 (only required for IP protocols tcp and udp)") ) return parser @@ -107,24 +108,23 @@ def update_parser_network(self, parser): direction_group.add_argument( '--ingress', action='store_true', - help='Rule applies to incoming network traffic (default)', + help=_("Rule applies to incoming network traffic (default)") ) direction_group.add_argument( '--egress', action='store_true', - help='Rule applies to outgoing network traffic', + help=_("Rule applies to outgoing network traffic") ) parser.add_argument( '--ethertype', metavar='', choices=['IPv4', 'IPv6'], - help='Ethertype of network traffic ' - '(IPv4, IPv6; default: IPv4)', + help=_("Ethertype of network traffic (IPv4, IPv6; default: IPv4)") ) parser.add_argument( '--project', metavar='', - help="Owner's project (name or ID)" + help=_("Owner's project (name or ID)") ) identity_common.add_project_domain_option_to_parser(parser) return parser @@ -218,7 +218,7 @@ def update_parser_common(self, parser): parser.add_argument( 'rule', metavar='', - help='Security group rule to delete (ID only)', + help=_("Security group rule to delete (ID only)") ) return parser @@ -238,7 +238,7 @@ def update_parser_common(self, parser): 'group', metavar='', nargs='?', - help='List all rules in this security group (name or ID)', + help=_("List all rules in this security group (name or ID)") ) return parser @@ -333,7 +333,7 @@ def update_parser_common(self, parser): parser.add_argument( 'rule', metavar="", - help="Security group rule to display (ID only)" + help=_("Security group rule to display (ID only)") ) return parser diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 3d539fea4d..715e662026 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -20,6 +20,7 @@ from openstackclient.common import exceptions from openstackclient.common import parseractions from openstackclient.common import utils +from openstackclient.i18n import _ from openstackclient.identity import common as identity_common @@ -50,17 +51,17 @@ def _get_common_parse_arguments(parser): dest='allocation_pools', action=parseractions.MultiKeyValueAction, required_keys=['start', 'end'], - help='Allocation pool IP addresses for this subnet ' - 'e.g.: start=192.168.199.2,end=192.168.199.254 ' - '(repeat option to add multiple IP addresses)', + help=_("Allocation pool IP addresses for this subnet " + "e.g.: start=192.168.199.2,end=192.168.199.254 " + "(repeat option to add multiple IP addresses)") ) parser.add_argument( '--dns-nameserver', metavar='', action='append', dest='dns_nameservers', - help='DNS server for this subnet ' - '(repeat option to set multiple DNS servers)', + help=_("DNS server for this subnet " + "(repeat option to set multiple DNS servers)") ) parser.add_argument( '--host-route', @@ -68,11 +69,11 @@ def _get_common_parse_arguments(parser): dest='host_routes', action=parseractions.MultiKeyValueAction, required_keys=['destination', 'gateway'], - help='Additional route for this subnet ' - 'e.g.: destination=10.10.0.0/16,gateway=192.168.71.254 ' - 'destination: destination subnet (in CIDR notation) ' - 'gateway: nexthop IP address ' - '(repeat option to add multiple routes)', + help=_("Additional route for this subnet " + "e.g.: destination=10.10.0.0/16,gateway=192.168.71.254 " + "destination: destination subnet (in CIDR notation) " + "gateway: nexthop IP address " + "(repeat option to add multiple routes)") ) @@ -174,87 +175,87 @@ def get_parser(self, prog_name): parser = super(CreateSubnet, self).get_parser(prog_name) parser.add_argument( 'name', - help='New subnet name', + help=_("New subnet name") ) parser.add_argument( '--project', metavar='', - help="Owner's project (name or ID)", + help=_("Owner's project (name or ID)") ) identity_common.add_project_domain_option_to_parser(parser) subnet_pool_group = parser.add_mutually_exclusive_group() subnet_pool_group.add_argument( '--subnet-pool', metavar='', - help='Subnet pool from which this subnet will obtain a CIDR ' - '(Name or ID)', + help=_("Subnet pool from which this subnet will obtain a CIDR " + "(Name or ID)") ) subnet_pool_group.add_argument( '--use-default-subnet-pool', action='store_true', - help='Use default subnet pool for --ip-version', + help=_("Use default subnet pool for --ip-version") ) parser.add_argument( '--prefix-length', metavar='', - help='Prefix length for subnet allocation from subnet pool', + help=_("Prefix length for subnet allocation from subnet pool") ) parser.add_argument( '--subnet-range', metavar='', - help='Subnet range in CIDR notation ' - '(required if --subnet-pool is not specified, ' - 'optional otherwise)', + help=_("Subnet range in CIDR notation " + "(required if --subnet-pool is not specified, " + "optional otherwise)") ) dhcp_enable_group = parser.add_mutually_exclusive_group() dhcp_enable_group.add_argument( '--dhcp', action='store_true', default=True, - help='Enable DHCP (default)', + help=_("Enable DHCP (default)") ) dhcp_enable_group.add_argument( '--no-dhcp', action='store_true', - help='Disable DHCP', + help=_("Disable DHCP") ) parser.add_argument( '--gateway', metavar='', default='auto', - help="Specify a gateway for the subnet. The three options are: " - ": Specific IP address to use as the gateway, " - "'auto': Gateway address should automatically be chosen from " - "within the subnet itself, 'none': This subnet will not use " - "a gateway, e.g.: --gateway 192.168.9.1, --gateway auto, " - "--gateway none (default is 'auto')", + help=_("Specify a gateway for the subnet. The three options are: " + ": Specific IP address to use as the gateway, " + "'auto': Gateway address should automatically be chosen " + "from within the subnet itself, 'none': This subnet will " + "not use a gateway, e.g.: --gateway 192.168.9.1, " + "--gateway auto, --gateway none (default is 'auto')") ) parser.add_argument( '--ip-version', type=int, default=4, choices=[4, 6], - help='IP version (default is 4). Note that when subnet pool is ' - 'specified, IP version is determined from the subnet pool ' - 'and this option is ignored.', + help=_("IP version (default is 4). Note that when subnet pool is " + "specified, IP version is determined from the subnet pool " + "and this option is ignored") ) parser.add_argument( '--ipv6-ra-mode', choices=['dhcpv6-stateful', 'dhcpv6-stateless', 'slaac'], - help='IPv6 RA (Router Advertisement) mode, ' - 'valid modes: [dhcpv6-stateful, dhcpv6-stateless, slaac]', + help=_("IPv6 RA (Router Advertisement) mode, " + "valid modes: [dhcpv6-stateful, dhcpv6-stateless, slaac]") ) parser.add_argument( '--ipv6-address-mode', choices=['dhcpv6-stateful', 'dhcpv6-stateless', 'slaac'], - help='IPv6 address mode, ' - 'valid modes: [dhcpv6-stateful, dhcpv6-stateless, slaac]', + help=_("IPv6 address mode, " + "valid modes: [dhcpv6-stateful, dhcpv6-stateless, slaac]") ) parser.add_argument( '--network', required=True, metavar='', - help='Network this subnet belongs to (name or ID)', + help=_("Network this subnet belongs to (name or ID)") ) _get_common_parse_arguments(parser) return parser @@ -276,7 +277,7 @@ def get_parser(self, prog_name): parser.add_argument( 'subnet', metavar="", - help="Subnet to delete (name or ID)", + help=_("Subnet to delete (name or ID)") ) return parser @@ -295,7 +296,7 @@ def get_parser(self, prog_name): '--long', action='store_true', default=False, - help='List additional fields in output', + help=_("List additional fields in output") ) return parser @@ -327,32 +328,32 @@ def get_parser(self, prog_name): parser.add_argument( 'subnet', metavar="", - help=("Subnet to modify (name or ID)") + help=_("Subnet to modify (name or ID)") ) parser.add_argument( '--name', metavar='', - help='Updated name of the subnet', + help=_("Updated name of the subnet") ) dhcp_enable_group = parser.add_mutually_exclusive_group() dhcp_enable_group.add_argument( '--dhcp', action='store_true', default=None, - help='Enable DHCP', + help=_("Enable DHCP") ) dhcp_enable_group.add_argument( '--no-dhcp', action='store_true', - help='Disable DHCP', + help=_("Disable DHCP") ) parser.add_argument( '--gateway', metavar='', - help="Specify a gateway for the subnet. The options are: " - ": Specific IP address to use as the gateway, " - "'none': This subnet will not use a gateway, " - "e.g.: --gateway 192.168.9.1, --gateway none" + help=_("Specify a gateway for the subnet. The options are: " + ": Specific IP address to use as the gateway, " + "'none': This subnet will not use a gateway, " + "e.g.: --gateway 192.168.9.1, --gateway none") ) _get_common_parse_arguments(parser) return parser @@ -376,14 +377,14 @@ def take_action(self, parsed_args): class ShowSubnet(command.ShowOne): - """Show subnet details""" + """Display subnet details""" def get_parser(self, prog_name): parser = super(ShowSubnet, self).get_parser(prog_name) parser.add_argument( 'subnet', metavar="", - help="Subnet to show (name or ID)", + help=_("Subnet to display (name or ID)") ) return parser diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index 0837f81b51..482b5ecf51 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -17,6 +17,7 @@ from openstackclient.common import exceptions from openstackclient.common import parseractions from openstackclient.common import utils +from openstackclient.i18n import _ from openstackclient.identity import common as identity_common @@ -73,26 +74,26 @@ def _add_prefix_options(parser): metavar='', dest='prefixes', action='append', - help='Set subnet pool prefixes (in CIDR notation) ' - '(repeat option to set multiple prefixes)', + help=_("Set subnet pool prefixes (in CIDR notation) " + "(repeat option to set multiple prefixes)") ) parser.add_argument( '--default-prefix-length', metavar='', action=parseractions.NonNegativeAction, - help='Set subnet pool default prefix length', + help=_("Set subnet pool default prefix length") ) parser.add_argument( '--min-prefix-length', metavar='', action=parseractions.NonNegativeAction, - help='Set subnet pool minimum prefix length', + help=_("Set subnet pool minimum prefix length") ) parser.add_argument( '--max-prefix-length', metavar='', action=parseractions.NonNegativeAction, - help='Set subnet pool maximum prefix length', + help=_("Set subnet pool maximum prefix length") ) @@ -104,21 +105,21 @@ def get_parser(self, prog_name): parser.add_argument( 'name', metavar='', - help='Name of the new subnet pool' + help=_("Name of the new subnet pool") ) _add_prefix_options(parser) parser.add_argument( '--project', metavar='', - help="Owner's project (name or ID)", + help=_("Owner's project (name or ID)") ) identity_common.add_project_domain_option_to_parser(parser) parser.add_argument( '--address-scope', metavar='', - help="Set address scope associated with the subnet pool " - "(name or ID). Prefixes must be unique across address " - "scopes.", + help=_("Set address scope associated with the subnet pool " + "(name or ID), prefixes must be unique across address " + "scopes") ) return parser @@ -142,7 +143,7 @@ def get_parser(self, prog_name): parser.add_argument( 'subnet_pool', metavar='', - help='Subnet pool to delete (name or ID)' + help=_("Subnet pool to delete (name or ID)") ) return parser @@ -161,7 +162,7 @@ def get_parser(self, prog_name): '--long', action='store_true', default=False, - help='List additional fields in output', + help=_("List additional fields in output") ) return parser @@ -210,26 +211,26 @@ def get_parser(self, prog_name): parser.add_argument( 'subnet_pool', metavar='', - help='Subnet pool to modify (name or ID)' + help=_("Subnet pool to modify (name or ID)") ) parser.add_argument( '--name', metavar='', - help='Set subnet pool name', + help=_("Set subnet pool name") ) _add_prefix_options(parser) address_scope_group = parser.add_mutually_exclusive_group() address_scope_group.add_argument( '--address-scope', metavar='', - help="Set address scope associated with the subnet pool " - "(name or ID). Prefixes must be unique across address " - "scopes.", + help=_("Set address scope associated with the subnet pool " + "(name or ID), prefixes must be unique across address " + "scopes") ) address_scope_group.add_argument( '--no-address-scope', action='store_true', - help="Remove address scope associated with the subnet pool", + help=_("Remove address scope associated with the subnet pool") ) return parser @@ -258,7 +259,7 @@ def get_parser(self, prog_name): parser.add_argument( 'subnet_pool', metavar='', - help='Subnet pool to display (name or ID)' + help=_("Subnet pool to display (name or ID)") ) return parser From cc3a0625291746d90eec371304d410070870dab9 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Mon, 18 Apr 2016 15:20:17 +0800 Subject: [PATCH 0816/3095] Remove methods argument from vloume/v2/fakes.py methods argument in FakeAvailabilityZone class is not necessary. Remove it. Change-Id: Idf136bf90bd94e7045b0d471b8e03cd843693251 --- openstackclient/tests/volume/v2/fakes.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py index 3c238d1006..6e631c41ea 100644 --- a/openstackclient/tests/volume/v2/fakes.py +++ b/openstackclient/tests/volume/v2/fakes.py @@ -361,13 +361,11 @@ class FakeAvailabilityZone(object): """Fake one or more volume availability zones (AZs).""" @staticmethod - def create_one_availability_zone(attrs={}, methods={}): + def create_one_availability_zone(attrs={}): """Create a fake AZ. :param Dictionary attrs: A dictionary with all attributes - :param Dictionary methods: - A dictionary with all methods :return: A FakeResource object with zoneName, zoneState, etc. """ @@ -382,18 +380,15 @@ def create_one_availability_zone(attrs={}, methods={}): availability_zone = fakes.FakeResource( info=copy.deepcopy(availability_zone), - methods=methods, loaded=True) return availability_zone @staticmethod - def create_availability_zones(attrs={}, methods={}, count=2): + def create_availability_zones(attrs={}, count=2): """Create multiple fake AZs. :param Dictionary attrs: A dictionary with all attributes - :param Dictionary methods: - A dictionary with all methods :param int count: The number of AZs to fake :return: @@ -402,8 +397,7 @@ def create_availability_zones(attrs={}, methods={}, count=2): availability_zones = [] for i in range(0, count): availability_zone = \ - FakeAvailabilityZone.create_one_availability_zone( - attrs, methods) + FakeAvailabilityZone.create_one_availability_zone(attrs) availability_zones.append(availability_zone) return availability_zones From f9e3d4517334beea34dc20c98ddd331ffa5c4639 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Mon, 18 Apr 2016 16:34:57 +0000 Subject: [PATCH 0817/3095] Updated from global requirements Change-Id: I12de03c42bc661a6a4e55fb70a06883e987fbc5f --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 76a0e46220..8cf531a34d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ six>=1.9.0 # MIT Babel!=2.3.0,!=2.3.1,!=2.3.2,!=2.3.3,>=1.3 # BSD cliff!=1.16.0,!=1.17.0,>=1.15.0 # Apache-2.0 keystoneauth1>=2.1.0 # Apache-2.0 -openstacksdk>=0.8.4 # Apache-2.0 +openstacksdk>=0.8.5 # Apache-2.0 os-client-config>=1.13.1 # Apache-2.0 oslo.config>=3.9.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 From 8c6b5a087a4b0dca16faffdbcb5fc9f2d424ddf8 Mon Sep 17 00:00:00 2001 From: Andrey Larionov Date: Fri, 15 Apr 2016 21:23:20 +0300 Subject: [PATCH 0818/3095] Initialize neutron client with region name All clients except neutron are initialized with region name. This makes unable to use network related commands of openstackclient in multi-region configurations Change-Id: I200dc9a2f938c3e69357f91c79810df167e4fccb Closes-bug: 1570491 --- openstackclient/network/client.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openstackclient/network/client.py b/openstackclient/network/client.py index dca9efc48a..3abe18e8a0 100644 --- a/openstackclient/network/client.py +++ b/openstackclient/network/client.py @@ -14,6 +14,7 @@ import logging from openstack import connection +from openstack import profile from openstackclient.common import utils @@ -31,8 +32,11 @@ def make_client(instance): """Returns a network proxy""" + prof = profile.Profile() + prof.set_region(API_NAME, instance._region_name) + prof.set_version(API_NAME, instance._api_version[API_NAME]) conn = connection.Connection(authenticator=instance.session.auth, - verify=instance.session.verify) + verify=instance.session.verify, profile=prof) LOG.debug('Connection: %s', conn) LOG.debug('Network client initialized using OpenStack SDK: %s', conn.network) From a90c824e0407f931b5c45df53103b43aa564de12 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Tue, 5 Apr 2016 13:27:42 -0500 Subject: [PATCH 0819/3095] Fix router set --route option Fix the "--route" option on the "os router set" command. The option did not properly format the new routes to set which resulted in a "HttpException: Bad Request" error. In addition, the output for routes was fixed to improve readability and to align with the "--route" option on the "os router set" command. Change-Id: I9c514153ec201e2feae32be6dd281771e3298b9c Closes-Bug: #1564460 --- openstackclient/network/v2/router.py | 26 +++++++++++++++---- openstackclient/tests/network/v2/fakes.py | 6 +++-- .../tests/network/v2/test_router.py | 15 ++++++----- .../notes/bug-1564460-ab7ad35c02392cb4.yaml | 9 +++++++ 4 files changed, 43 insertions(+), 13 deletions(-) create mode 100644 releasenotes/notes/bug-1564460-ab7ad35c02392cb4.yaml diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 56630a2303..a32ab5ea79 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -34,11 +34,20 @@ def _format_external_gateway_info(info): return '' +def _format_routes(routes): + # Map the route keys to match --route option. + for route in routes: + if 'nexthop' in route: + route['gateway'] = route.pop('nexthop') + return utils.format_list_of_dicts(routes) + + _formatters = { 'admin_state_up': _format_admin_state, 'external_gateway_info': _format_external_gateway_info, 'availability_zones': utils.format_list, 'availability_zone_hints': utils.format_list, + 'routes': _format_routes, } @@ -67,11 +76,6 @@ def _get_attrs(client_manager, parsed_args): and parsed_args.availability_zone_hints is not None): attrs['availability_zone_hints'] = parsed_args.availability_zone_hints - if 'clear_routes' in parsed_args and parsed_args.clear_routes: - attrs['routes'] = [] - elif 'routes' in parsed_args and parsed_args.routes is not None: - attrs['routes'] = parsed_args.routes - # "router set" command doesn't support setting project. if 'project' in parsed_args and parsed_args.project is not None: identity_client = client_manager.identity @@ -393,7 +397,19 @@ def take_action(self, parsed_args): client = self.app.client_manager.network obj = client.find_router(parsed_args.router, ignore_missing=False) + # Get the common attributes. attrs = _get_attrs(self.app.client_manager, parsed_args) + + # Get the route attributes. + if parsed_args.clear_routes: + attrs['routes'] = [] + elif parsed_args.routes is not None: + # Map the route keys and append to the current routes. + # The REST API will handle route validation and duplicates. + for route in parsed_args.routes: + route['nexthop'] = route.pop('gateway') + attrs['routes'] = obj.routes + parsed_args.routes + if attrs == {}: msg = "Nothing specified to be set" raise exceptions.CommandError(msg) diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 5fd7ea3b86..73c6b09eb8 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -328,7 +328,7 @@ class FakeRouter(object): """Fake one or more routers.""" @staticmethod - def create_one_router(attrs={}): + def create_one_router(attrs=None): """Create a fake router. :param Dictionary attrs: @@ -337,6 +337,8 @@ def create_one_router(attrs={}): A FakeResource object, with id, name, admin_state_up, status, tenant_id """ + attrs = attrs or {} + # Set default attributes. router_attrs = { 'id': 'router-id-' + uuid.uuid4().hex, @@ -364,7 +366,7 @@ def create_one_router(attrs={}): return router @staticmethod - def create_routers(attrs={}, count=2): + def create_routers(attrs=None, count=2): """Create multiple fake routers. :param Dictionary attrs: diff --git a/openstackclient/tests/network/v2/test_router.py b/openstackclient/tests/network/v2/test_router.py index e804149867..e3f32e4ae3 100644 --- a/openstackclient/tests/network/v2/test_router.py +++ b/openstackclient/tests/network/v2/test_router.py @@ -138,7 +138,7 @@ class TestCreateRouter(TestRouter): new_router.id, new_router.name, new_router.tenant_id, - new_router.routes, + router._format_routes(new_router.routes), new_router.status, ) @@ -268,7 +268,7 @@ class TestListRouter(TestRouter): r = routers[i] data_long.append( data[i] + ( - r.routes, + router._format_routes(r.routes), router._format_external_gateway_info(r.external_gateway_info), osc_utils.format_list(r.availability_zones), ) @@ -399,7 +399,10 @@ def test_remove_subnet_required_options(self): class TestSetRouter(TestRouter): # The router to set. - _router = network_fakes.FakeRouter.create_one_router() + _default_route = {'destination': '10.20.20.0/24', 'nexthop': '10.20.30.1'} + _router = network_fakes.FakeRouter.create_one_router( + attrs={'routes': [_default_route]} + ) def setUp(self): super(TestSetRouter, self).setUp() @@ -491,8 +494,8 @@ def test_set_route(self): result = self.cmd.take_action(parsed_args) attrs = { - 'routes': [{'destination': '10.20.30.0/24', - 'gateway': '10.20.30.1'}], + 'routes': self._router.routes + [{'destination': '10.20.30.0/24', + 'nexthop': '10.20.30.1'}], } self.network.update_router.assert_called_once_with( self._router, **attrs) @@ -572,7 +575,7 @@ class TestShowRouter(TestRouter): _router.id, _router.name, _router.tenant_id, - _router.routes, + router._format_routes(_router.routes), _router.status, ) diff --git a/releasenotes/notes/bug-1564460-ab7ad35c02392cb4.yaml b/releasenotes/notes/bug-1564460-ab7ad35c02392cb4.yaml new file mode 100644 index 0000000000..54b9bdd7bf --- /dev/null +++ b/releasenotes/notes/bug-1564460-ab7ad35c02392cb4.yaml @@ -0,0 +1,9 @@ +--- +fixes: + - Fixed the ``--route`` option on the ``router set`` command + which did not properly format the new routes to set resulting + in a ``Bad Request`` error. In addition, the ``router create``, + ``router list`` and ``router show`` command output for routes + was fixed to improve readability and to align with the + ``--route`` option on the ``router set`` command. + [Bug `1564460 `_] From 94c9cd5c66512d52b31dfaa42bc3d1cc7fd81702 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Mon, 11 Apr 2016 13:45:12 -0500 Subject: [PATCH 0820/3095] Add options to security group rule list Add the following options to the 'os security group rule list' command: --long: Display direction and ethertype for Network v2 --all-projects: Display information from all projects for Compute v2 Change-Id: If8a1cbd7669cdfa6577d6d2f6fffd9e999a39a82 Partial-Bug: #1519512 Implements: blueprint neutron-client --- .../command-objects/security-group-rule.rst | 15 +++ doc/source/command-objects/security-group.rst | 2 +- .../network/v2/security_group_rule.py | 46 ++++++++- .../network/v2/test_security_group_rule.py | 99 ++++++++++++++++--- .../notes/bug-1519512-65df002102b7fb99.yaml | 10 +- 5 files changed, 149 insertions(+), 23 deletions(-) diff --git a/doc/source/command-objects/security-group-rule.rst b/doc/source/command-objects/security-group-rule.rst index 2f212e5ebf..8218c81aa0 100644 --- a/doc/source/command-objects/security-group-rule.rst +++ b/doc/source/command-objects/security-group-rule.rst @@ -98,8 +98,23 @@ List security group rules .. code:: bash os security group rule list + [--all-projects] + [--long] [] +.. option:: --all-projects + + Display information from all projects (admin only) + + *Network version 2 ignores this option and will always display information* + *for all projects (admin only).* + +.. option:: --long + + List additional fields in output + + *Compute version 2 does not have additional fields to display.* + .. describe:: List all rules in this security group (name or ID) diff --git a/doc/source/command-objects/security-group.rst b/doc/source/command-objects/security-group.rst index 9fc4c987ba..2c6e7a8adc 100644 --- a/doc/source/command-objects/security-group.rst +++ b/doc/source/command-objects/security-group.rst @@ -69,7 +69,7 @@ List security groups Display information from all projects (admin only) *Network version 2 ignores this option and will always display information* - *for all projects.* + *for all projects (admin only).* security group set ------------------ diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index 67472de05d..5b22a0dd83 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -13,6 +13,7 @@ """Security Group Rule action implementations""" +import argparse import six try: @@ -242,14 +243,50 @@ def update_parser_common(self, parser): ) return parser + def update_parser_network(self, parser): + # Accept but hide the argument for consistency with compute. + # Network will always return all projects for an admin. + parser.add_argument( + '--all-projects', + action='store_true', + default=False, + help=argparse.SUPPRESS + ) + parser.add_argument( + '--long', + action='store_true', + default=False, + help=_("List additional fields in output") + ) + return parser + + def update_parser_compute(self, parser): + parser.add_argument( + '--all-projects', + action='store_true', + default=False, + help=_("Display information from all projects (admin only)") + ) + # Accept but hide the argument for consistency with network. + # There are no additional fields to display at this time. + parser.add_argument( + '--long', + action='store_false', + default=False, + help=argparse.SUPPRESS + ) + return parser + def _get_column_headers(self, parsed_args): column_headers = ( 'ID', 'IP Protocol', 'IP Range', 'Port Range', - 'Remote Security Group', ) + if parsed_args.long: + column_headers = column_headers + ('Direction', 'Ethertype',) + column_headers = column_headers + ('Remote Security Group',) if parsed_args.group is None: column_headers = column_headers + ('Security Group',) return column_headers @@ -261,8 +298,10 @@ def take_action_network(self, client, parsed_args): 'protocol', 'remote_ip_prefix', 'port_range_min', - 'remote_group_id', ) + if parsed_args.long: + columns = columns + ('direction', 'ethertype',) + columns = columns + ('remote_group_id',) # Get the security group rules using the requested query. query = {} @@ -309,7 +348,8 @@ def take_action_compute(self, client, parsed_args): rules_to_list = group.rules else: columns = columns + ('parent_group_id',) - for group in client.security_groups.list(): + search = {'all_tenants': parsed_args.all_projects} + for group in client.security_groups.list(search_opts=search): rules_to_list.extend(group.rules) # NOTE(rtheis): Turn the raw rules into resources. diff --git a/openstackclient/tests/network/v2/test_security_group_rule.py b/openstackclient/tests/network/v2/test_security_group_rule.py index c2fa12568f..df7414aa48 100644 --- a/openstackclient/tests/network/v2/test_security_group_rule.py +++ b/openstackclient/tests/network/v2/test_security_group_rule.py @@ -532,31 +532,46 @@ class TestListSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): _security_group_rules = [_security_group_rule_tcp, _security_group_rule_icmp] - expected_columns_with_group = ( + expected_columns_with_group_and_long = ( 'ID', 'IP Protocol', 'IP Range', 'Port Range', + 'Direction', + 'Ethertype', 'Remote Security Group', ) - expected_columns_no_group = \ - expected_columns_with_group + ('Security Group',) + expected_columns_no_group = ( + 'ID', + 'IP Protocol', + 'IP Range', + 'Port Range', + 'Remote Security Group', + 'Security Group', + ) - expected_data_with_group = [] + expected_data_with_group_and_long = [] expected_data_no_group = [] for _security_group_rule in _security_group_rules: - expected_rule_with_group = ( + expected_data_with_group_and_long.append(( _security_group_rule.id, _security_group_rule.protocol, _security_group_rule.remote_ip_prefix, security_group_rule._format_network_port_range( _security_group_rule), + _security_group_rule.direction, + _security_group_rule.ethertype, _security_group_rule.remote_group_id, - ) - expected_rule_no_group = expected_rule_with_group + \ - (_security_group_rule.security_group_id,) - expected_data_with_group.append(expected_rule_with_group) - expected_data_no_group.append(expected_rule_no_group) + )) + expected_data_no_group.append(( + _security_group_rule.id, + _security_group_rule.protocol, + _security_group_rule.remote_ip_prefix, + security_group_rule._format_network_port_range( + _security_group_rule), + _security_group_rule.remote_group_id, + _security_group_rule.security_group_id, + )) def setUp(self): super(TestListSecurityGroupRuleNetwork, self).setUp() @@ -570,7 +585,7 @@ def setUp(self): self.cmd = security_group_rule.ListSecurityGroupRule( self.app, self.namespace) - def test_list_no_group(self): + def test_list_default(self): self._security_group_rule_tcp.port_range_min = 80 parsed_args = self.check_parser(self.cmd, [], []) @@ -580,12 +595,14 @@ def test_list_no_group(self): self.assertEqual(self.expected_columns_no_group, columns) self.assertEqual(self.expected_data_no_group, list(data)) - def test_list_with_group(self): + def test_list_with_group_and_long(self): self._security_group_rule_tcp.port_range_min = 80 arglist = [ + '--long', self._security_group.id, ] verifylist = [ + ('long', True), ('group', self._security_group.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -595,8 +612,24 @@ def test_list_with_group(self): self.network.security_group_rules.assert_called_once_with(**{ 'security_group_id': self._security_group.id, }) - self.assertEqual(self.expected_columns_with_group, columns) - self.assertEqual(self.expected_data_with_group, list(data)) + self.assertEqual(self.expected_columns_with_group_and_long, columns) + self.assertEqual(self.expected_data_with_group_and_long, list(data)) + + def test_list_with_ignored_options(self): + self._security_group_rule_tcp.port_range_min = 80 + arglist = [ + '--all-projects', + ] + verifylist = [ + ('all_projects', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.security_group_rules.assert_called_once_with(**{}) + self.assertEqual(self.expected_columns_no_group, columns) + self.assertEqual(self.expected_data_no_group, list(data)) class TestListSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): @@ -665,11 +698,13 @@ def setUp(self): # Get the command object to test self.cmd = security_group_rule.ListSecurityGroupRule(self.app, None) - def test_list_no_group(self): + def test_list_default(self): parsed_args = self.check_parser(self.cmd, [], []) columns, data = self.cmd.take_action(parsed_args) - self.compute.security_groups.list.assert_called_once_with() + self.compute.security_groups.list.assert_called_once_with( + search_opts={'all_tenants': False} + ) self.assertEqual(self.expected_columns_no_group, columns) self.assertEqual(self.expected_data_no_group, list(data)) @@ -689,6 +724,38 @@ def test_list_with_group(self): self.assertEqual(self.expected_columns_with_group, columns) self.assertEqual(self.expected_data_with_group, list(data)) + def test_list_all_projects(self): + arglist = [ + '--all-projects', + ] + verifylist = [ + ('all_projects', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.compute.security_groups.list.assert_called_once_with( + search_opts={'all_tenants': True} + ) + self.assertEqual(self.expected_columns_no_group, columns) + self.assertEqual(self.expected_data_no_group, list(data)) + + def test_list_with_ignored_options(self): + arglist = [ + '--long', + ] + verifylist = [ + ('long', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.compute.security_groups.list.assert_called_once_with( + search_opts={'all_tenants': False} + ) + self.assertEqual(self.expected_columns_no_group, columns) + self.assertEqual(self.expected_data_no_group, list(data)) + class TestShowSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): diff --git a/releasenotes/notes/bug-1519512-65df002102b7fb99.yaml b/releasenotes/notes/bug-1519512-65df002102b7fb99.yaml index b5f5fbb531..87b14d6556 100644 --- a/releasenotes/notes/bug-1519512-65df002102b7fb99.yaml +++ b/releasenotes/notes/bug-1519512-65df002102b7fb99.yaml @@ -2,9 +2,13 @@ features: - The ``security group rule list`` command now uses Network v2 when enabled which results in ``egress`` security group rules - being displayed. In addition, security group rules for all - projects will be displayed when the ``group`` argument is not - specified (admin only). + being displayed. The ``--long`` option was also added for + Network v2 to display direction and ethertype information. + In addition, security group rules for all projects will be + displayed when the ``group`` argument is not specified + (admin only). This is done by default when using Network v2, + but requires the new ``--all-projects`` option when using + Compute v2. [Bug `1519512 `_] fixes: - The ``security group rule list`` command no longer ignores From baf96411fed22ad97ff219601830be63563ae03a Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Mon, 18 Apr 2016 13:49:01 -0500 Subject: [PATCH 0821/3095] Clean up for next release * Release notes * Docs Table of Contents Change-Id: I4bef26ed348923671a2b50f90606661d519b40e3 --- doc/source/index.rst | 2 +- .../add-port-commands-a3580662721a6312.yaml | 2 +- ...dd_volume_type_access-32ba8d4bfb0f5f3d.yaml | 18 ------------------ .../notes/bug-1519502-d534db6c18adef20.yaml | 4 ++-- .../notes/bug-1554877-7f8479791eab45b7.yaml | 15 ++++++--------- .../notes/bug-1554879-118c6007eba8357a.yaml | 14 -------------- .../notes/bug-1554889-32ba8d4bfb0f5f3d.yaml | 12 ++++++++++++ .../notes/bug-1556929-edd78cded88ecdc9.yaml | 2 +- .../notes/bug-1559866-733988f5dd5b07bb.yaml | 2 +- .../notes/bug-1565112-e0cea9bfbcab954f.yaml | 4 ++-- .../type_access_remove-a6a55921d2dae48d.yaml | 18 ------------------ 11 files changed, 26 insertions(+), 67 deletions(-) delete mode 100644 releasenotes/notes/add_volume_type_access-32ba8d4bfb0f5f3d.yaml delete mode 100644 releasenotes/notes/bug-1554879-118c6007eba8357a.yaml create mode 100644 releasenotes/notes/bug-1554889-32ba8d4bfb0f5f3d.yaml delete mode 100644 releasenotes/notes/type_access_remove-a6a55921d2dae48d.yaml diff --git a/doc/source/index.rst b/doc/source/index.rst index bfa16a72e3..fe12b862ea 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -13,6 +13,7 @@ User Documentation .. toctree:: :maxdepth: 1 + Manual Page command-list commands configuration @@ -22,7 +23,6 @@ User Documentation interactive humaninterfaceguide backwards-incompatible - man/openstack Getting Started --------------- diff --git a/releasenotes/notes/add-port-commands-a3580662721a6312.yaml b/releasenotes/notes/add-port-commands-a3580662721a6312.yaml index a5a12567da..4a6ea9b6b7 100644 --- a/releasenotes/notes/add-port-commands-a3580662721a6312.yaml +++ b/releasenotes/notes/add-port-commands-a3580662721a6312.yaml @@ -1,5 +1,5 @@ --- features: - | - Add support for the ``port create``, ``port list`` and ``port set`` commands. + Add ``port create``, ``port list`` and ``port set`` commands [Bug `1519909 `_] diff --git a/releasenotes/notes/add_volume_type_access-32ba8d4bfb0f5f3d.yaml b/releasenotes/notes/add_volume_type_access-32ba8d4bfb0f5f3d.yaml deleted file mode 100644 index 69ae6e76f0..0000000000 --- a/releasenotes/notes/add_volume_type_access-32ba8d4bfb0f5f3d.yaml +++ /dev/null @@ -1,18 +0,0 @@ ---- -features: - - | - Added support for setting volume type access to project. - - By default, volumes types are public. - To create a private volume type, user needs to set is_public boolean - field to false at volume type creation time. - To control access to a private volume type, user needs to add access - of a private volume type to project. - - So, this feature enables user to add private volume type access to a - project using below command - - ``volume type set --project ``. - - [Bug 1554889 'https://bugs.launchpad.net/python-openstackclient/+bug/1554889'_] - diff --git a/releasenotes/notes/bug-1519502-d534db6c18adef20.yaml b/releasenotes/notes/bug-1519502-d534db6c18adef20.yaml index 7f089b9884..43317a0181 100644 --- a/releasenotes/notes/bug-1519502-d534db6c18adef20.yaml +++ b/releasenotes/notes/bug-1519502-d534db6c18adef20.yaml @@ -1,4 +1,4 @@ --- -features: - - Command ``ip floating create`` is now available for neutron network. +upgrade: + - The ``ip floating create`` command now uses Network v2 when enabled [Bug `1519502 `_] diff --git a/releasenotes/notes/bug-1554877-7f8479791eab45b7.yaml b/releasenotes/notes/bug-1554877-7f8479791eab45b7.yaml index a5d602633e..b8120fb475 100644 --- a/releasenotes/notes/bug-1554877-7f8479791eab45b7.yaml +++ b/releasenotes/notes/bug-1554877-7f8479791eab45b7.yaml @@ -1,14 +1,11 @@ --- features: - | - Added ``--image-property`` option to ``volume set`` command + Add ``--image-property`` option to ``volume set`` and ``volume unset`` commands - Image properties are copied from image when volume is created. - But since a volume is mutable, user sometime wants to update - image properties for volume. + Image properties are copied when a volume is created from an image. + The properties are immutable on the image itself but may be updated + or removed from the volume created from that image. - So, this fix enables user to update image properties of volume - using below command: - ``volume set --image-property ``. - - [Bug '1554877 '_] + [Bug `1554877 `_] + [Bug `1554879 `_] diff --git a/releasenotes/notes/bug-1554879-118c6007eba8357a.yaml b/releasenotes/notes/bug-1554879-118c6007eba8357a.yaml deleted file mode 100644 index 3b9ed48782..0000000000 --- a/releasenotes/notes/bug-1554879-118c6007eba8357a.yaml +++ /dev/null @@ -1,14 +0,0 @@ ---- -features: - - | - Added ``--image-property`` option to ``volume unset`` command - - Image properties are copied from image when volume is created. - But since a volume is mutable, user sometime wants to delete - image properties for volume. - - So, this fix enables user to delete image properties of volume - using below command: - ``volume unset [--image-property ] ``. - - [Bug '1554879 '_] diff --git a/releasenotes/notes/bug-1554889-32ba8d4bfb0f5f3d.yaml b/releasenotes/notes/bug-1554889-32ba8d4bfb0f5f3d.yaml new file mode 100644 index 0000000000..ab9ae81865 --- /dev/null +++ b/releasenotes/notes/bug-1554889-32ba8d4bfb0f5f3d.yaml @@ -0,0 +1,12 @@ +--- +features: + - | + Add ``--project`` and ``--project-domain`` options to ``volume type set`` + and ``volume type unset`` commands + + Use the ``--project`` option to restrict a volume type to a specific project. + Volume types are public by default, restricted volume types should be made + private with the ``--private`` option to the ``volume create`` command. + + [Bug `1554889 `_] + [Bug `1554890 `_] diff --git a/releasenotes/notes/bug-1556929-edd78cded88ecdc9.yaml b/releasenotes/notes/bug-1556929-edd78cded88ecdc9.yaml index 44f9a4646f..9cfdd48704 100644 --- a/releasenotes/notes/bug-1556929-edd78cded88ecdc9.yaml +++ b/releasenotes/notes/bug-1556929-edd78cded88ecdc9.yaml @@ -1,4 +1,4 @@ --- features: - - Command ``host set`` is now available for compute + - Add ``host set`` command [Bug `1556929 `_] diff --git a/releasenotes/notes/bug-1559866-733988f5dd5b07bb.yaml b/releasenotes/notes/bug-1559866-733988f5dd5b07bb.yaml index 26bd9a0334..db30ed73ad 100644 --- a/releasenotes/notes/bug-1559866-733988f5dd5b07bb.yaml +++ b/releasenotes/notes/bug-1559866-733988f5dd5b07bb.yaml @@ -1,4 +1,4 @@ --- features: - - Add ``aggregate unset`` command for compute v2 + - Add ``aggregate unset`` command [Bug `1559866 `_] diff --git a/releasenotes/notes/bug-1565112-e0cea9bfbcab954f.yaml b/releasenotes/notes/bug-1565112-e0cea9bfbcab954f.yaml index f8976e6372..e813427b4e 100644 --- a/releasenotes/notes/bug-1565112-e0cea9bfbcab954f.yaml +++ b/releasenotes/notes/bug-1565112-e0cea9bfbcab954f.yaml @@ -1,6 +1,6 @@ --- features: - - Add global options ``os-cert`` and --os-key`` to support client - certificate/key. Environment variables OS_CERT and OS_KEY, as well + - Add global options ``os-cert`` and ``--os-key`` to support client + certificate/key. Environment variables ``OS_CERT`` and ``OS_KEY``, as well as the ``cert`` and ``key`` values in clouds.yaml may also be used [Bug `1565112 `_] diff --git a/releasenotes/notes/type_access_remove-a6a55921d2dae48d.yaml b/releasenotes/notes/type_access_remove-a6a55921d2dae48d.yaml deleted file mode 100644 index 7dfc949bbb..0000000000 --- a/releasenotes/notes/type_access_remove-a6a55921d2dae48d.yaml +++ /dev/null @@ -1,18 +0,0 @@ ---- -features: - - | - Added support for removing volume type access to project. - - By default, volumes types are public. - To create a private volume type the ``--private`` option must be included - in the ``volume type create`` command. - - To control access to a private volume type, user needs to add or remove - access of a private volume type to project. - - This feature enables user to remove private volume type access to a - project using below command: - - ``volume type unset --project `` - - [Bug 1554890 'https://bugs.launchpad.net/python-openstackclient/+bug/1554890'_] From aa1495e241e99903bc8704f1981a7e3941803e35 Mon Sep 17 00:00:00 2001 From: reedip Date: Fri, 8 Apr 2016 14:47:04 +0900 Subject: [PATCH 0822/3095] Add provider network options to osc network set The following patch adds the provider network options to OSC "network set". Change-Id: I23b617077eda25d16164172a8e280082750eaf18 Partial-Bug: #1545537 --- doc/source/command-objects/network.rst | 16 +++++ openstackclient/network/v2/network.py | 64 ++++++++++--------- .../tests/network/v2/test_network.py | 9 +++ .../notes/bug-1545537-12bbf01d2280dd2f.yaml | 8 +-- 4 files changed, 62 insertions(+), 35 deletions(-) diff --git a/doc/source/command-objects/network.rst b/doc/source/command-objects/network.rst index e2f5eaf0ee..5d534c59f5 100644 --- a/doc/source/command-objects/network.rst +++ b/doc/source/command-objects/network.rst @@ -172,6 +172,9 @@ Set network properties [--enable | --disable] [--share | --no-share] [--external [--default | --no-default] | --internal] + [--provider-network-type ] + [--provider-physical-network ] + [--provider-segment ] .. option:: --name @@ -211,6 +214,19 @@ Set network properties Do not use the network as the default external network. +.. option:: --provider-network-type + + The physical mechanism by which the virtual network is implemented. + The supported options are: flat, gre, local, vlan, vxlan + +.. option:: --provider-physical-network + + Name of the physical network over which the virtual network is implemented + +.. option:: --provider-segment + + VLAN ID for VLAN networks or Tunnel ID for GRE/VXLAN networks + .. _network_set-network: .. describe:: diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index d57a1ed6c2..4b77971a21 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -86,10 +86,40 @@ def _get_attrs(client_manager, parsed_args): attrs['is_default'] = False if parsed_args.default: attrs['is_default'] = True - + # Update Provider network options + if parsed_args.provider_network_type: + attrs['provider:network_type'] = parsed_args.provider_network_type + if parsed_args.physical_network: + attrs['provider:physical_network'] = parsed_args.physical_network + if parsed_args.segmentation_id: + attrs['provider:segmentation_id'] = parsed_args.segmentation_id return attrs +def _add_provider_network_options(parser): + # Add provider network options + parser.add_argument( + '--provider-network-type', + metavar='', + choices=['flat', 'gre', 'local', + 'vlan', 'vxlan'], + help=_("The physical mechanism by which the virtual network " + "is implemented. The supported options are: " + "flat, gre, local, vlan, vxlan")) + parser.add_argument( + '--provider-physical-network', + metavar='', + dest='physical_network', + help=_("Name of the physical network over which the virtual " + "network is implemented")) + parser.add_argument( + '--provider-segment', + metavar='', + dest='segmentation_id', + help=_("VLAN ID for VLAN networks or Tunnel ID for GRE/VXLAN " + "networks")) + + def _get_attrs_compute(client_manager, parsed_args): attrs = {} if parsed_args.name is not None: @@ -100,7 +130,6 @@ def _get_attrs_compute(client_manager, parsed_args): attrs['share_address'] = False if parsed_args.subnet is not None: attrs['cidr'] = parsed_args.subnet - return attrs @@ -180,29 +209,7 @@ def update_parser_network(self, parser): help=_("Do not use the network as the default external network. " "(default)") ) - parser.add_argument( - '--provider-network-type', - metavar='', - choices=['flat', 'gre', 'local', - 'vlan', 'vxlan'], - help=_("The physical mechanism by which the virtual network " - "is implemented. The supported options are: " - "flat, gre, local, vlan, vxlan") - ) - parser.add_argument( - '--provider-physical-network', - metavar='', - dest='physical_network', - help=_("Name of the physical network over which the virtual " - "network is implemented") - ) - parser.add_argument( - '--provider-segment', - metavar='', - dest='segmentation_id', - help=_("VLAN ID for VLAN networks or Tunnel ID for GRE/VXLAN " - "networks") - ) + _add_provider_network_options(parser) return parser def update_parser_compute(self, parser): @@ -215,12 +222,6 @@ def update_parser_compute(self, parser): def take_action_network(self, client, parsed_args): attrs = _get_attrs(self.app.client_manager, parsed_args) - if parsed_args.provider_network_type: - attrs['provider:network_type'] = parsed_args.provider_network_type - if parsed_args.physical_network: - attrs['provider:physical_network'] = parsed_args.physical_network - if parsed_args.segmentation_id: - attrs['provider:segmentation_id'] = parsed_args.segmentation_id obj = client.create_network(**attrs) columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) @@ -412,6 +413,7 @@ def get_parser(self, prog_name): action='store_true', help=_("Do not use the network as the default external network") ) + _add_provider_network_options(parser) return parser def take_action(self, parsed_args): diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index 7d0f8717f5..a1b0aec986 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -484,6 +484,9 @@ def test_set_this(self): '--share', '--external', '--default', + '--provider-network-type', 'vlan', + '--provider-physical-network', 'physnet1', + '--provider-segment', '400', ] verifylist = [ ('network', self._network.name), @@ -492,6 +495,9 @@ def test_set_this(self): ('share', True), ('external', True), ('default', True), + ('provider_network_type', 'vlan'), + ('physical_network', 'physnet1'), + ('segmentation_id', '400'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -503,6 +509,9 @@ def test_set_this(self): 'shared': True, 'router:external': True, 'is_default': True, + 'provider:network_type': 'vlan', + 'provider:physical_network': 'physnet1', + 'provider:segmentation_id': '400', } self.network.update_network.assert_called_once_with( self._network, **attrs) diff --git a/releasenotes/notes/bug-1545537-12bbf01d2280dd2f.yaml b/releasenotes/notes/bug-1545537-12bbf01d2280dd2f.yaml index 7be07a86a5..fa205a692b 100644 --- a/releasenotes/notes/bug-1545537-12bbf01d2280dd2f.yaml +++ b/releasenotes/notes/bug-1545537-12bbf01d2280dd2f.yaml @@ -2,7 +2,7 @@ features: - | Add provider network options ``--provider-network-type``, - ``--provider-physical-network``, and ``--provider-segmentation-id`` - to the ``network create`` command. - These options are available for Networkv2 only - [Bug `1545537 `_] + ``--provider-physical-network`` and ``--provider-segment`` + to the ``network create`` and ``network set`` commands. + These options are available for NetworkV2 only. + [Bug `1545537 `_] \ No newline at end of file From c95d68eaea8dfc36cf7ea656522fcca7ae58b1c8 Mon Sep 17 00:00:00 2001 From: Kyrylo Romanenko Date: Mon, 18 Apr 2016 20:07:26 +0300 Subject: [PATCH 0823/3095] Deduplicate CLI output parser code in test.py Use methods from tempest-lib.cli.output_parser. Change-Id: I0655141a0ef967675e41b1da49cf999da3382018 --- functional/common/test.py | 63 +++------------------------------------ 1 file changed, 4 insertions(+), 59 deletions(-) diff --git a/functional/common/test.py b/functional/common/test.py index c1bf3f36a5..9887c76570 100644 --- a/functional/common/test.py +++ b/functional/common/test.py @@ -17,9 +17,10 @@ import testtools import six - +from tempest_lib.cli import output_parser from tempest_lib import exceptions + COMMON_DIR = os.path.dirname(os.path.abspath(__file__)) FUNCTIONAL_DIR = os.path.normpath(os.path.join(COMMON_DIR, '..')) ROOT_DIR = os.path.normpath(os.path.join(FUNCTIONAL_DIR, '..')) @@ -112,7 +113,7 @@ def parse_show(self, raw_output): """Return list of dicts with item values parsed from cli output.""" items = [] - table_ = self.table(raw_output) + table_ = output_parser.table(raw_output) for row in table_['values']: item = {} item[row[0]] = row[1] @@ -121,60 +122,4 @@ def parse_show(self, raw_output): def parse_listing(self, raw_output): """Return list of dicts with basic item parsed from cli output.""" - - items = [] - table_ = self.table(raw_output) - for row in table_['values']: - item = {} - for col_idx, col_key in enumerate(table_['headers']): - item[col_key] = row[col_idx] - items.append(item) - return items - - def table(self, output_lines): - """Parse single table from cli output. - - Return dict with list of column names in 'headers' key and - rows in 'values' key. - """ - table_ = {'headers': [], 'values': []} - columns = None - - if not isinstance(output_lines, list): - output_lines = output_lines.split('\n') - - if not output_lines[-1]: - # skip last line if empty (just newline at the end) - output_lines = output_lines[:-1] - - for line in output_lines: - if self.delimiter_line.match(line): - columns = self._table_columns(line) - continue - if '|' not in line: - continue - row = [] - for col in columns: - row.append(line[col[0]:col[1]].strip()) - if table_['headers']: - table_['values'].append(row) - else: - table_['headers'] = row - - return table_ - - def _table_columns(self, first_table_row): - """Find column ranges in output line. - - Return list of tuples (start,end) for each column - detected by plus (+) characters in delimiter line. - """ - positions = [] - start = 1 # there is '+' at 0 - while start < len(first_table_row): - end = first_table_row.find('+', start) - if end == -1: - break - positions.append((start, end)) - start = end + 1 - return positions + return output_parser.listing(raw_output) From 2a66493ff0ed3390c65e706cdebc28a01795a4b7 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Tue, 19 Apr 2016 06:14:07 +0000 Subject: [PATCH 0824/3095] Imported Translations from Zanata For more information about this automatic import see: https://wiki.openstack.org/wiki/Translations/Infrastructure Change-Id: I7e1c948a2bb7f4843cd8a58bb66fbedec79af39b --- .../locale/de/LC_MESSAGES/openstackclient.po | 28 +- openstackclient/locale/openstackclient.pot | 1045 +++++++++++++---- .../zh_TW/LC_MESSAGES/openstackclient.po | 21 +- 3 files changed, 821 insertions(+), 273 deletions(-) diff --git a/openstackclient/locale/de/LC_MESSAGES/openstackclient.po b/openstackclient/locale/de/LC_MESSAGES/openstackclient.po index 1c94b9d4b2..f7445b1cc2 100644 --- a/openstackclient/locale/de/LC_MESSAGES/openstackclient.po +++ b/openstackclient/locale/de/LC_MESSAGES/openstackclient.po @@ -5,13 +5,12 @@ # # Translators: # Ettore Atalan , 2014-2015 -# Andreas Jaeger , 2015. #zanata -# OpenStack Infra , 2015. #zanata +# Andreas Jaeger , 2016. #zanata msgid "" msgstr "" -"Project-Id-Version: python-openstackclient 2.0.1.dev168\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2016-01-19 02:20+0000\n" +"Project-Id-Version: python-openstackclient 2.2.1.dev235\n" +"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" +"POT-Creation-Date: 2016-04-19 05:02+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -166,10 +165,6 @@ msgstr "Zu löschende Endpunktkennung" msgid "Endpoint ID to display" msgstr "Anzuzeigende Endpunktkennung" -#, python-format -msgid "Error creating server snapshot: %s" -msgstr "Fehler beim Erstellen der Server-Schattenkopie: %s" - #, python-format msgid "Error creating server: %s" msgstr "Fehler beim Erstellen des Servers: %s" @@ -181,11 +176,6 @@ msgstr "Fehler beim Löschen des Servers: %s" msgid "Error retrieving diagnostics data" msgstr "Fehler beim Abrufen der Diagnosedaten" -msgid "File to inject into image before boot (repeat for multiple files)" -msgstr "" -"Vor dem Start auf diesem Abbild einzufügende Datei (für mehrere Dateien " -"wiederholen)" - msgid "Filter by parent region ID" msgstr "Nach übergeordneter Regionskennung filtern" @@ -338,11 +328,6 @@ msgstr "Zu löschende(s) Projekt(e) (Name oder Kennung)" msgid "Prompt interactively for password" msgstr "Interaktiv nach dem Passwort abfragen" -msgid "Property key to remove from server (repeat to unset multiple values)" -msgstr "" -"Vom Server zu entfernender Eigenschaftsschlüssel (zum Aufheben von mehreren " -"Werten wiederholen)" - msgid "" "Property to add/change for this server (repeat option to set multiple " "properties)" @@ -446,11 +431,6 @@ msgstr "" "Legen Sie eine Projekteigenschaft fest (wiederholen Sie die Option, um " "mehrere Eigenschaften festzulegen)" -msgid "Set a property on this server (repeat for multiple values)" -msgstr "" -"Legen Sie eine Eigenschaft auf diesem Server fest (für mehrere Werte " -"wiederholen)" - msgid "" "Set a scope, such as a project or domain, set a project scope with --os-" "project-name, OS_PROJECT_NAME or auth.project_name, set a domain scope with " diff --git a/openstackclient/locale/openstackclient.pot b/openstackclient/locale/openstackclient.pot index afc892669d..e197fa7b79 100644 --- a/openstackclient/locale/openstackclient.pot +++ b/openstackclient/locale/openstackclient.pot @@ -7,9 +7,9 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: python-openstackclient 2.0.1.dev168\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2016-01-19 06:03+0000\n" +"Project-Id-Version: python-openstackclient 2.2.1.dev235\n" +"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" +"POT-Creation-Date: 2016-04-19 06:14+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -18,123 +18,142 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.2.0\n" -#: openstackclient/api/auth.py:144 +#: openstackclient/api/auth.py:148 msgid "Set a username with --os-username, OS_USERNAME, or auth.username\n" msgstr "" -#: openstackclient/api/auth.py:147 +#: openstackclient/api/auth.py:151 msgid "" "Set an authentication URL, with --os-auth-url, OS_AUTH_URL or " "auth.auth_url\n" msgstr "" -#: openstackclient/api/auth.py:155 +#: openstackclient/api/auth.py:160 msgid "" "Set a scope, such as a project or domain, set a project scope with --os-" "project-name, OS_PROJECT_NAME or auth.project_name, set a domain scope " "with --os-domain-name, OS_DOMAIN_NAME or auth.domain_name" msgstr "" -#: openstackclient/api/auth.py:161 openstackclient/api/auth.py:167 +#: openstackclient/api/auth.py:166 openstackclient/api/auth.py:172 msgid "Set a token with --os-token, OS_TOKEN or auth.token\n" msgstr "" -#: openstackclient/api/auth.py:163 +#: openstackclient/api/auth.py:168 msgid "Set a service AUTH_URL, with --os-auth-url, OS_AUTH_URL or auth.auth_url\n" msgstr "" -#: openstackclient/api/auth.py:169 +#: openstackclient/api/auth.py:174 msgid "Set a service URL, with --os-url, OS_URL or auth.url\n" msgstr "" -#: openstackclient/compute/v2/availability_zone.py:72 -#: openstackclient/compute/v2/server.py:737 -#: openstackclient/identity/v2_0/endpoint.py:114 -#: openstackclient/identity/v2_0/project.py:145 -#: openstackclient/identity/v2_0/service.py:128 -#: openstackclient/identity/v2_0/user.py:174 +#: openstackclient/common/availability_zone.py:110 +#: openstackclient/compute/v2/server.py:757 +#: openstackclient/identity/v2_0/endpoint.py:101 +#: openstackclient/identity/v2_0/project.py:133 +#: openstackclient/identity/v2_0/service.py:115 +#: openstackclient/identity/v2_0/user.py:162 +#: openstackclient/network/v2/router.py:230 +#: openstackclient/network/v2/subnet.py:299 +#: openstackclient/network/v2/subnet_pool.py:165 msgid "List additional fields in output" msgstr "" -#: openstackclient/compute/v2/server.py:183 -#: openstackclient/compute/v2/server.py:219 -#: openstackclient/compute/v2/server.py:569 -#: openstackclient/compute/v2/server.py:923 -#: openstackclient/compute/v2/server.py:1031 -#: openstackclient/compute/v2/server.py:1086 -#: openstackclient/compute/v2/server.py:1179 -#: openstackclient/compute/v2/server.py:1219 -#: openstackclient/compute/v2/server.py:1245 -#: openstackclient/compute/v2/server.py:1336 -#: openstackclient/compute/v2/server.py:1420 -#: openstackclient/compute/v2/server.py:1457 -#: openstackclient/compute/v2/server.py:1732 -#: openstackclient/compute/v2/server.py:1756 +#: openstackclient/common/parseractions.py:99 +#, python-format +msgid "" +"Invalid keys %(invalid_keys)s specified.\n" +"Valid keys are: %(valid_keys)s." +msgstr "" + +#: openstackclient/common/parseractions.py:109 +#, python-format +msgid "" +"Missing required keys %(missing_keys)s.\n" +"Required keys are: %(required_keys)s." +msgstr "" + +#: openstackclient/compute/v2/server.py:195 +#: openstackclient/compute/v2/server.py:227 +#: openstackclient/compute/v2/server.py:598 +#: openstackclient/compute/v2/server.py:937 +#: openstackclient/compute/v2/server.py:1039 +#: openstackclient/compute/v2/server.py:1091 +#: openstackclient/compute/v2/server.py:1177 +#: openstackclient/compute/v2/server.py:1213 +#: openstackclient/compute/v2/server.py:1236 +#: openstackclient/compute/v2/server.py:1343 +#: openstackclient/compute/v2/server.py:1421 +#: openstackclient/compute/v2/server.py:1455 +#: openstackclient/compute/v2/server.py:1712 +#: openstackclient/compute/v2/server.py:1733 msgid "Server (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:188 +#: openstackclient/compute/v2/server.py:200 msgid "Security group to add (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:224 +#: openstackclient/compute/v2/server.py:232 msgid "Volume to add (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:229 +#: openstackclient/compute/v2/server.py:237 msgid "Server internal device name for volume" msgstr "" -#: openstackclient/compute/v2/server.py:265 -#: openstackclient/compute/v2/server.py:1341 +#: openstackclient/compute/v2/server.py:269 +#: openstackclient/compute/v2/server.py:1348 msgid "New server name" msgstr "" -#: openstackclient/compute/v2/server.py:273 +#: openstackclient/compute/v2/server.py:277 msgid "Create server from this image (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:278 +#: openstackclient/compute/v2/server.py:282 msgid "Create server from this volume (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:284 +#: openstackclient/compute/v2/server.py:288 msgid "Create server with this flavor (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:291 +#: openstackclient/compute/v2/server.py:295 msgid "" -"Security group to assign to this server (name or ID) (repeat for multiple" -" groups)" +"Security group to assign to this server (name or ID) (repeat option to " +"set multiple groups)" msgstr "" -#: openstackclient/compute/v2/server.py:297 +#: openstackclient/compute/v2/server.py:301 msgid "Keypair to inject into this server (optional extension)" msgstr "" -#: openstackclient/compute/v2/server.py:303 -msgid "Set a property on this server (repeat for multiple values)" +#: openstackclient/compute/v2/server.py:307 +msgid "Set a property on this server (repeat option to set multiple values)" msgstr "" -#: openstackclient/compute/v2/server.py:311 -msgid "File to inject into image before boot (repeat for multiple files)" +#: openstackclient/compute/v2/server.py:315 +msgid "" +"File to inject into image before boot (repeat option to set multiple " +"files)" msgstr "" -#: openstackclient/compute/v2/server.py:317 +#: openstackclient/compute/v2/server.py:321 msgid "User data file to serve from the metadata server" msgstr "" -#: openstackclient/compute/v2/server.py:322 +#: openstackclient/compute/v2/server.py:326 msgid "Select an availability zone for the server" msgstr "" -#: openstackclient/compute/v2/server.py:329 +#: openstackclient/compute/v2/server.py:333 msgid "" "Map block devices; map is ::: " "(optional extension)" msgstr "" -#: openstackclient/compute/v2/server.py:339 +#: openstackclient/compute/v2/server.py:343 msgid "" "Create a NIC on the server. Specify option multiple times to create " "multiple NICs. Either net-id or port-id must be provided, but not both. " @@ -143,296 +162,304 @@ msgid "" "-fixed-ip: IPv6 fixed address for NIC (optional)." msgstr "" -#: openstackclient/compute/v2/server.py:352 +#: openstackclient/compute/v2/server.py:356 msgid "Hints for the scheduler (optional extension)" msgstr "" -#: openstackclient/compute/v2/server.py:358 +#: openstackclient/compute/v2/server.py:362 msgid "" "Use specified volume as the config drive, or 'True' to use an ephemeral " "drive" msgstr "" -#: openstackclient/compute/v2/server.py:366 +#: openstackclient/compute/v2/server.py:370 msgid "Minimum number of servers to launch (default=1)" msgstr "" -#: openstackclient/compute/v2/server.py:373 +#: openstackclient/compute/v2/server.py:377 msgid "Maximum number of servers to launch (default=1)" msgstr "" -#: openstackclient/compute/v2/server.py:378 +#: openstackclient/compute/v2/server.py:382 msgid "Wait for build to complete" msgstr "" -#: openstackclient/compute/v2/server.py:418 +#: openstackclient/compute/v2/server.py:421 msgid "min instances should be <= max instances" msgstr "" -#: openstackclient/compute/v2/server.py:421 +#: openstackclient/compute/v2/server.py:424 msgid "min instances should be > 0" msgstr "" -#: openstackclient/compute/v2/server.py:424 +#: openstackclient/compute/v2/server.py:427 msgid "max instances should be > 0" msgstr "" -#: openstackclient/compute/v2/server.py:453 +#: openstackclient/compute/v2/server.py:456 msgid "Volume name or ID must be specified if --block-device-mapping is specified" msgstr "" -#: openstackclient/compute/v2/server.py:465 +#: openstackclient/compute/v2/server.py:468 msgid "either net-id or port-id should be specified but not both" msgstr "" -#: openstackclient/compute/v2/server.py:485 +#: openstackclient/compute/v2/server.py:488 msgid "can't create server with port specified since network endpoint not enabled" msgstr "" -#: openstackclient/compute/v2/server.py:550 +#: openstackclient/compute/v2/server.py:553 #, python-format msgid "Error creating server: %s" msgstr "" -#: openstackclient/compute/v2/server.py:552 +#: openstackclient/compute/v2/server.py:555 msgid "" "\n" "Error creating server" msgstr "" -#: openstackclient/compute/v2/server.py:574 +#: openstackclient/compute/v2/server.py:577 +msgid "Server(s) to create dump file (name or ID)" +msgstr "" + +#: openstackclient/compute/v2/server.py:603 msgid "Name of new image (default is server name)" msgstr "" -#: openstackclient/compute/v2/server.py:579 +#: openstackclient/compute/v2/server.py:608 msgid "Wait for image create to complete" msgstr "" -#: openstackclient/compute/v2/server.py:609 +#: openstackclient/compute/v2/server.py:637 #, python-format -msgid "Error creating server snapshot: %s" +msgid "Error creating snapshot of server: %s" msgstr "" -#: openstackclient/compute/v2/server.py:611 +#: openstackclient/compute/v2/server.py:639 msgid "" "\n" "Error creating server snapshot" msgstr "" -#: openstackclient/compute/v2/server.py:633 +#: openstackclient/compute/v2/server.py:656 msgid "Server(s) to delete (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:638 +#: openstackclient/compute/v2/server.py:661 msgid "Wait for delete to complete" msgstr "" -#: openstackclient/compute/v2/server.py:657 +#: openstackclient/compute/v2/server.py:679 #, python-format msgid "Error deleting server: %s" msgstr "" -#: openstackclient/compute/v2/server.py:659 +#: openstackclient/compute/v2/server.py:681 msgid "" "\n" "Error deleting server" msgstr "" -#: openstackclient/compute/v2/server.py:673 +#: openstackclient/compute/v2/server.py:693 msgid "Only return instances that match the reservation" msgstr "" -#: openstackclient/compute/v2/server.py:678 +#: openstackclient/compute/v2/server.py:698 msgid "Regular expression to match IP addresses" msgstr "" -#: openstackclient/compute/v2/server.py:683 +#: openstackclient/compute/v2/server.py:703 msgid "Regular expression to match IPv6 addresses" msgstr "" -#: openstackclient/compute/v2/server.py:688 +#: openstackclient/compute/v2/server.py:708 msgid "Regular expression to match names" msgstr "" -#: openstackclient/compute/v2/server.py:693 +#: openstackclient/compute/v2/server.py:713 msgid "Regular expression to match instance name (admin only)" msgstr "" -#: openstackclient/compute/v2/server.py:699 +#: openstackclient/compute/v2/server.py:719 msgid "Search by server status" msgstr "" -#: openstackclient/compute/v2/server.py:704 +#: openstackclient/compute/v2/server.py:724 msgid "Search by flavor (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:709 +#: openstackclient/compute/v2/server.py:729 msgid "Search by image (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:714 +#: openstackclient/compute/v2/server.py:734 msgid "Search by hostname" msgstr "" -#: openstackclient/compute/v2/server.py:720 +#: openstackclient/compute/v2/server.py:740 msgid "Include all projects (admin only)" msgstr "" -#: openstackclient/compute/v2/server.py:730 +#: openstackclient/compute/v2/server.py:750 msgid "Search by user (admin only) (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:888 +#: openstackclient/compute/v2/server.py:905 msgid "Server(s) to lock (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:928 +#: openstackclient/compute/v2/server.py:942 msgid "Target hostname" msgstr "" -#: openstackclient/compute/v2/server.py:936 +#: openstackclient/compute/v2/server.py:950 msgid "Perform a shared live migration (default)" msgstr "" -#: openstackclient/compute/v2/server.py:942 +#: openstackclient/compute/v2/server.py:956 msgid "Perform a block live migration" msgstr "" -#: openstackclient/compute/v2/server.py:949 +#: openstackclient/compute/v2/server.py:963 msgid "Allow disk over-commit on the destination host" msgstr "" -#: openstackclient/compute/v2/server.py:956 +#: openstackclient/compute/v2/server.py:970 msgid "Do not over-commit disk on the destination host (default)" msgstr "" -#: openstackclient/compute/v2/server.py:962 -#: openstackclient/compute/v2/server.py:1265 +#: openstackclient/compute/v2/server.py:976 +#: openstackclient/compute/v2/server.py:1256 msgid "Wait for resize to complete" msgstr "" -#: openstackclient/compute/v2/server.py:990 -#: openstackclient/compute/v2/server.py:1290 +#: openstackclient/compute/v2/server.py:1003 +#: openstackclient/compute/v2/server.py:1280 msgid "Complete\n" msgstr "" -#: openstackclient/compute/v2/server.py:992 +#: openstackclient/compute/v2/server.py:1005 msgid "" "\n" "Error migrating server" msgstr "" -#: openstackclient/compute/v2/server.py:1007 +#: openstackclient/compute/v2/server.py:1018 msgid "Server(s) to pause (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:1040 +#: openstackclient/compute/v2/server.py:1048 msgid "Perform a hard reboot" msgstr "" -#: openstackclient/compute/v2/server.py:1048 +#: openstackclient/compute/v2/server.py:1056 msgid "Perform a soft reboot" msgstr "" -#: openstackclient/compute/v2/server.py:1053 +#: openstackclient/compute/v2/server.py:1061 msgid "Wait for reboot to complete" msgstr "" -#: openstackclient/compute/v2/server.py:1070 +#: openstackclient/compute/v2/server.py:1077 msgid "" "\n" "Reboot complete\n" msgstr "" -#: openstackclient/compute/v2/server.py:1072 +#: openstackclient/compute/v2/server.py:1079 msgid "" "\n" "Error rebooting server\n" msgstr "" -#: openstackclient/compute/v2/server.py:1091 +#: openstackclient/compute/v2/server.py:1096 msgid "" "Recreate server from the specified image (name or ID). Defaults to the " "currently used one." msgstr "" -#: openstackclient/compute/v2/server.py:1102 +#: openstackclient/compute/v2/server.py:1107 msgid "Wait for rebuild to complete" msgstr "" -#: openstackclient/compute/v2/server.py:1124 +#: openstackclient/compute/v2/server.py:1128 msgid "" "\n" "Complete\n" msgstr "" -#: openstackclient/compute/v2/server.py:1126 +#: openstackclient/compute/v2/server.py:1130 msgid "" "\n" "Error rebuilding server" msgstr "" -#: openstackclient/compute/v2/server.py:1143 +#: openstackclient/compute/v2/server.py:1145 msgid "Name or ID of server to use" msgstr "" -#: openstackclient/compute/v2/server.py:1148 +#: openstackclient/compute/v2/server.py:1150 msgid "Name or ID of security group to remove from server" msgstr "" -#: openstackclient/compute/v2/server.py:1184 +#: openstackclient/compute/v2/server.py:1182 msgid "Volume to remove (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:1250 +#: openstackclient/compute/v2/server.py:1241 msgid "Resize server to specified flavor" msgstr "" -#: openstackclient/compute/v2/server.py:1255 +#: openstackclient/compute/v2/server.py:1246 msgid "Confirm server resize is complete" msgstr "" -#: openstackclient/compute/v2/server.py:1260 +#: openstackclient/compute/v2/server.py:1251 msgid "Restore server state before resize" msgstr "" -#: openstackclient/compute/v2/server.py:1292 +#: openstackclient/compute/v2/server.py:1282 msgid "" "\n" "Error resizing server" msgstr "" -#: openstackclient/compute/v2/server.py:1311 +#: openstackclient/compute/v2/server.py:1299 +msgid "Server(s) to restore (name or ID)" +msgstr "" + +#: openstackclient/compute/v2/server.py:1321 msgid "Server(s) to resume (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:1346 +#: openstackclient/compute/v2/server.py:1353 msgid "Set new root password (interactive only)" msgstr "" -#: openstackclient/compute/v2/server.py:1352 +#: openstackclient/compute/v2/server.py:1359 msgid "" "Property to add/change for this server (repeat option to set multiple " "properties)" msgstr "" -#: openstackclient/compute/v2/server.py:1376 +#: openstackclient/compute/v2/server.py:1382 msgid "New password: " msgstr "" -#: openstackclient/compute/v2/server.py:1377 +#: openstackclient/compute/v2/server.py:1383 msgid "Retype new password: " msgstr "" -#: openstackclient/compute/v2/server.py:1381 +#: openstackclient/compute/v2/server.py:1387 msgid "Passwords do not match, password unchanged" msgstr "" -#: openstackclient/compute/v2/server.py:1396 +#: openstackclient/compute/v2/server.py:1400 msgid "Server(s) to shelve (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:1426 +#: openstackclient/compute/v2/server.py:1427 msgid "Display server diagnostics information" msgstr "" @@ -440,390 +467,944 @@ msgstr "" msgid "Error retrieving diagnostics data" msgstr "" -#: openstackclient/compute/v2/server.py:1462 +#: openstackclient/compute/v2/server.py:1460 msgid "Login name (ssh -l option)" msgstr "" -#: openstackclient/compute/v2/server.py:1474 +#: openstackclient/compute/v2/server.py:1472 msgid "Destination port (ssh -p option)" msgstr "" -#: openstackclient/compute/v2/server.py:1486 +#: openstackclient/compute/v2/server.py:1484 msgid "Private key file (ssh -i option)" msgstr "" -#: openstackclient/compute/v2/server.py:1497 +#: openstackclient/compute/v2/server.py:1495 msgid "Options in ssh_config(5) format (ssh -o option)" msgstr "" -#: openstackclient/compute/v2/server.py:1511 +#: openstackclient/compute/v2/server.py:1509 msgid "Use only IPv4 addresses" msgstr "" -#: openstackclient/compute/v2/server.py:1518 +#: openstackclient/compute/v2/server.py:1516 msgid "Use only IPv6 addresses" msgstr "" -#: openstackclient/compute/v2/server.py:1527 +#: openstackclient/compute/v2/server.py:1525 msgid "Use public IP address" msgstr "" -#: openstackclient/compute/v2/server.py:1535 +#: openstackclient/compute/v2/server.py:1533 msgid "Use private IP address" msgstr "" -#: openstackclient/compute/v2/server.py:1542 +#: openstackclient/compute/v2/server.py:1540 msgid "Use other IP address (public, private, etc)" msgstr "" -#: openstackclient/compute/v2/server.py:1605 +#: openstackclient/compute/v2/server.py:1600 msgid "Server(s) to start (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:1630 +#: openstackclient/compute/v2/server.py:1622 msgid "Server(s) to stop (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:1655 +#: openstackclient/compute/v2/server.py:1644 msgid "Server(s) to suspend (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:1681 +#: openstackclient/compute/v2/server.py:1667 msgid "Server(s) to unlock (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:1707 +#: openstackclient/compute/v2/server.py:1690 msgid "Server(s) to unpause (name or ID)" msgstr "" -#: openstackclient/compute/v2/server.py:1763 -msgid "Property key to remove from server (repeat to unset multiple values)" +#: openstackclient/compute/v2/server.py:1740 +msgid "" +"Property key to remove from server (repeat option to remove multiple " +"values)" msgstr "" -#: openstackclient/compute/v2/server.py:1794 +#: openstackclient/compute/v2/server.py:1768 msgid "Server(s) to unshelve (name or ID)" msgstr "" -#: openstackclient/identity/v2_0/catalog.py:75 -#: openstackclient/identity/v3/catalog.py:72 +#: openstackclient/compute/v2/service.py:138 +msgid "argument --disable-reason has been ignored" +msgstr "" + +#: openstackclient/identity/v2_0/catalog.py:67 +#: openstackclient/identity/v3/catalog.py:64 msgid "Service to display (type or name)" msgstr "" -#: openstackclient/identity/v2_0/ec2creds.py:40 -#: openstackclient/identity/v3/ec2creds.py:65 +#: openstackclient/identity/v2_0/ec2creds.py:34 +#: openstackclient/identity/v3/ec2creds.py:59 msgid "" "Create credentials in project (name or ID; default: current authenticated" " project)" msgstr "" -#: openstackclient/identity/v2_0/ec2creds.py:48 -#: openstackclient/identity/v3/ec2creds.py:73 +#: openstackclient/identity/v2_0/ec2creds.py:42 +#: openstackclient/identity/v3/ec2creds.py:67 msgid "" "Create credentials for user (name or ID; default: current authenticated " "user)" msgstr "" -#: openstackclient/identity/v2_0/ec2creds.py:99 -#: openstackclient/identity/v2_0/ec2creds.py:172 -#: openstackclient/identity/v3/ec2creds.py:129 -#: openstackclient/identity/v3/ec2creds.py:187 +#: openstackclient/identity/v2_0/ec2creds.py:90 +#: openstackclient/identity/v2_0/ec2creds.py:157 +#: openstackclient/identity/v3/ec2creds.py:120 +#: openstackclient/identity/v3/ec2creds.py:172 msgid "Credentials access key" msgstr "" -#: openstackclient/identity/v2_0/ec2creds.py:104 -#: openstackclient/identity/v3/ec2creds.py:134 +#: openstackclient/identity/v2_0/ec2creds.py:95 +#: openstackclient/identity/v3/ec2creds.py:125 msgid "Delete credentials for user (name or ID)" msgstr "" -#: openstackclient/identity/v2_0/ec2creds.py:134 -#: openstackclient/identity/v3/ec2creds.py:156 +#: openstackclient/identity/v2_0/ec2creds.py:122 +#: openstackclient/identity/v3/ec2creds.py:144 msgid "Filter list by user (name or ID)" msgstr "" -#: openstackclient/identity/v2_0/ec2creds.py:177 -#: openstackclient/identity/v3/ec2creds.py:192 +#: openstackclient/identity/v2_0/ec2creds.py:162 +#: openstackclient/identity/v3/ec2creds.py:177 msgid "Show credentials for user (name or ID)" msgstr "" -#: openstackclient/identity/v2_0/endpoint.py:40 +#: openstackclient/identity/v2_0/endpoint.py:34 msgid "New endpoint service (name or ID)" msgstr "" -#: openstackclient/identity/v2_0/endpoint.py:46 +#: openstackclient/identity/v2_0/endpoint.py:40 msgid "New endpoint public URL (required)" msgstr "" -#: openstackclient/identity/v2_0/endpoint.py:51 +#: openstackclient/identity/v2_0/endpoint.py:45 msgid "New endpoint admin URL" msgstr "" -#: openstackclient/identity/v2_0/endpoint.py:56 +#: openstackclient/identity/v2_0/endpoint.py:50 msgid "New endpoint internal URL" msgstr "" -#: openstackclient/identity/v2_0/endpoint.py:61 +#: openstackclient/identity/v2_0/endpoint.py:55 msgid "New endpoint region ID" msgstr "" -#: openstackclient/identity/v2_0/endpoint.py:93 +#: openstackclient/identity/v2_0/endpoint.py:84 msgid "Endpoint ID to delete" msgstr "" -#: openstackclient/identity/v2_0/endpoint.py:149 +#: openstackclient/identity/v2_0/endpoint.py:133 msgid "Endpoint ID to display" msgstr "" -#: openstackclient/identity/v2_0/project.py:41 +#: openstackclient/identity/v2_0/project.py:36 msgid "New project name" msgstr "" -#: openstackclient/identity/v2_0/project.py:46 +#: openstackclient/identity/v2_0/project.py:41 msgid "Project description" msgstr "" -#: openstackclient/identity/v2_0/project.py:52 +#: openstackclient/identity/v2_0/project.py:47 msgid "Enable project (default)" msgstr "" -#: openstackclient/identity/v2_0/project.py:57 -#: openstackclient/identity/v2_0/project.py:194 +#: openstackclient/identity/v2_0/project.py:52 +#: openstackclient/identity/v2_0/project.py:179 msgid "Disable project" msgstr "" -#: openstackclient/identity/v2_0/project.py:63 +#: openstackclient/identity/v2_0/project.py:58 msgid "Add a property to (repeat option to set multiple properties)" msgstr "" -#: openstackclient/identity/v2_0/project.py:69 -#: openstackclient/identity/v3/project.py:80 +#: openstackclient/identity/v2_0/project.py:64 +#: openstackclient/identity/v3/project.py:75 msgid "Return existing project" msgstr "" -#: openstackclient/identity/v2_0/project.py:117 +#: openstackclient/identity/v2_0/project.py:109 msgid "Project(s) to delete (name or ID)" msgstr "" -#: openstackclient/identity/v2_0/project.py:173 -#: openstackclient/identity/v2_0/project.py:313 +#: openstackclient/identity/v2_0/project.py:158 +#: openstackclient/identity/v2_0/project.py:291 msgid "Project to modify (name or ID)" msgstr "" -#: openstackclient/identity/v2_0/project.py:178 +#: openstackclient/identity/v2_0/project.py:163 msgid "Set project name" msgstr "" -#: openstackclient/identity/v2_0/project.py:183 +#: openstackclient/identity/v2_0/project.py:168 msgid "Set project description" msgstr "" -#: openstackclient/identity/v2_0/project.py:189 +#: openstackclient/identity/v2_0/project.py:174 msgid "Enable project" msgstr "" -#: openstackclient/identity/v2_0/project.py:200 +#: openstackclient/identity/v2_0/project.py:185 msgid "Set a project property (repeat option to set multiple properties)" msgstr "" -#: openstackclient/identity/v2_0/project.py:253 +#: openstackclient/identity/v2_0/project.py:234 msgid "Project to display (name or ID)" msgstr "" -#: openstackclient/identity/v2_0/project.py:320 +#: openstackclient/identity/v2_0/project.py:298 msgid "Unset a project property (repeat option to unset multiple properties)" msgstr "" -#: openstackclient/identity/v2_0/role.py:41 +#: openstackclient/identity/v2_0/role.py:36 msgid "Role to add to : (name or ID)" msgstr "" -#: openstackclient/identity/v2_0/role.py:47 -#: openstackclient/identity/v2_0/role.py:309 +#: openstackclient/identity/v2_0/role.py:42 +#: openstackclient/identity/v2_0/role.py:288 msgid "Include (name or ID)" msgstr "" -#: openstackclient/identity/v2_0/role.py:53 -#: openstackclient/identity/v2_0/role.py:315 +#: openstackclient/identity/v2_0/role.py:48 +#: openstackclient/identity/v2_0/role.py:294 msgid "Include (name or ID)" msgstr "" -#: openstackclient/identity/v2_0/role.py:87 +#: openstackclient/identity/v2_0/role.py:79 msgid "New role name" msgstr "" -#: openstackclient/identity/v2_0/role.py:92 -#: openstackclient/identity/v3/role.py:165 +#: openstackclient/identity/v2_0/role.py:84 +#: openstackclient/identity/v3/role.py:155 msgid "Return existing role" msgstr "" -#: openstackclient/identity/v2_0/role.py:127 +#: openstackclient/identity/v2_0/role.py:116 msgid "Role(s) to delete (name or ID)" msgstr "" -#: openstackclient/identity/v2_0/role.py:194 -#: openstackclient/identity/v2_0/role.py:257 +#: openstackclient/identity/v2_0/role.py:178 +#: openstackclient/identity/v2_0/role.py:238 msgid "Project must be specified" msgstr "" -#: openstackclient/identity/v2_0/role.py:208 -#: openstackclient/identity/v2_0/role.py:263 +#: openstackclient/identity/v2_0/role.py:192 +#: openstackclient/identity/v2_0/role.py:244 msgid "User must be specified" msgstr "" -#: openstackclient/identity/v2_0/role.py:236 +#: openstackclient/identity/v2_0/role.py:218 msgid "User to list (name or ID)" msgstr "" -#: openstackclient/identity/v2_0/role.py:241 +#: openstackclient/identity/v2_0/role.py:223 msgid "Filter users by (name or ID)" msgstr "" -#: openstackclient/identity/v2_0/role.py:303 +#: openstackclient/identity/v2_0/role.py:282 msgid "Role to remove (name or ID)" msgstr "" -#: openstackclient/identity/v2_0/role.py:344 +#: openstackclient/identity/v2_0/role.py:320 msgid "Role to display (name or ID)" msgstr "" -#: openstackclient/identity/v2_0/service.py:42 +#: openstackclient/identity/v2_0/service.py:36 msgid "New service type (compute, image, identity, volume, etc)" msgstr "" -#: openstackclient/identity/v2_0/service.py:53 +#: openstackclient/identity/v2_0/service.py:47 msgid "New service name" msgstr "" -#: openstackclient/identity/v2_0/service.py:58 +#: openstackclient/identity/v2_0/service.py:52 msgid "New service description" msgstr "" -#: openstackclient/identity/v2_0/service.py:78 +#: openstackclient/identity/v2_0/service.py:71 msgid "" "The argument --type is deprecated, use service create --name type instead." msgstr "" -#: openstackclient/identity/v2_0/service.py:105 +#: openstackclient/identity/v2_0/service.py:96 msgid "Service to delete (name or ID)" msgstr "" -#: openstackclient/identity/v2_0/service.py:156 +#: openstackclient/identity/v2_0/service.py:140 msgid "Service to display (type, name or ID)" msgstr "" -#: openstackclient/identity/v2_0/service.py:162 +#: openstackclient/identity/v2_0/service.py:146 msgid "Show service catalog information" msgstr "" -#: openstackclient/identity/v2_0/service.py:180 +#: openstackclient/identity/v2_0/service.py:163 #, python-format msgid "No service catalog with a type, name or ID of '%s' exists." msgstr "" -#: openstackclient/identity/v2_0/token.py:55 +#: openstackclient/identity/v2_0/token.py:50 msgid "Token to be deleted" msgstr "" -#: openstackclient/identity/v2_0/user.py:40 +#: openstackclient/identity/v2_0/user.py:35 msgid "New user name" msgstr "" -#: openstackclient/identity/v2_0/user.py:45 +#: openstackclient/identity/v2_0/user.py:40 msgid "Default project (name or ID)" msgstr "" -#: openstackclient/identity/v2_0/user.py:50 -#: openstackclient/identity/v2_0/user.py:273 +#: openstackclient/identity/v2_0/user.py:45 +#: openstackclient/identity/v2_0/user.py:258 msgid "Set user password" msgstr "" -#: openstackclient/identity/v2_0/user.py:56 -#: openstackclient/identity/v2_0/user.py:279 +#: openstackclient/identity/v2_0/user.py:51 +#: openstackclient/identity/v2_0/user.py:264 msgid "Prompt interactively for password" msgstr "" -#: openstackclient/identity/v2_0/user.py:61 -#: openstackclient/identity/v2_0/user.py:284 +#: openstackclient/identity/v2_0/user.py:56 +#: openstackclient/identity/v2_0/user.py:269 msgid "Set user email address" msgstr "" -#: openstackclient/identity/v2_0/user.py:67 -#: openstackclient/identity/v2_0/user.py:290 +#: openstackclient/identity/v2_0/user.py:62 +#: openstackclient/identity/v2_0/user.py:275 msgid "Enable user (default)" msgstr "" -#: openstackclient/identity/v2_0/user.py:72 -#: openstackclient/identity/v2_0/user.py:295 +#: openstackclient/identity/v2_0/user.py:67 +#: openstackclient/identity/v2_0/user.py:280 msgid "Disable user" msgstr "" -#: openstackclient/identity/v2_0/user.py:77 -#: openstackclient/identity/v3/user.py:90 +#: openstackclient/identity/v2_0/user.py:72 +#: openstackclient/identity/v3/user.py:86 msgid "Return existing user" msgstr "" -#: openstackclient/identity/v2_0/user.py:141 +#: openstackclient/identity/v2_0/user.py:133 msgid "User(s) to delete (name or ID)" msgstr "" -#: openstackclient/identity/v2_0/user.py:168 +#: openstackclient/identity/v2_0/user.py:156 msgid "Filter users by project (name or ID)" msgstr "" -#: openstackclient/identity/v2_0/user.py:258 +#: openstackclient/identity/v2_0/user.py:243 msgid "User to change (name or ID)" msgstr "" -#: openstackclient/identity/v2_0/user.py:263 +#: openstackclient/identity/v2_0/user.py:248 msgid "Set user name" msgstr "" -#: openstackclient/identity/v2_0/user.py:268 +#: openstackclient/identity/v2_0/user.py:253 msgid "Set default project (name or ID)" msgstr "" -#: openstackclient/identity/v2_0/user.py:361 +#: openstackclient/identity/v2_0/user.py:342 msgid "User to display (name or ID)" msgstr "" -#: openstackclient/identity/v3/domain.py:62 +#: openstackclient/identity/v3/domain.py:57 msgid "Return existing domain" msgstr "" -#: openstackclient/identity/v3/group.py:141 +#: openstackclient/identity/v3/group.py:130 msgid "Return existing group" msgstr "" -#: openstackclient/identity/v3/region.py:39 +#: openstackclient/identity/v3/region.py:33 msgid "New region ID" msgstr "" -#: openstackclient/identity/v3/region.py:44 +#: openstackclient/identity/v3/region.py:38 msgid "Parent region ID" msgstr "" -#: openstackclient/identity/v3/region.py:49 -#: openstackclient/identity/v3/region.py:144 +#: openstackclient/identity/v3/region.py:43 +#: openstackclient/identity/v3/region.py:128 msgid "New region description" msgstr "" -#: openstackclient/identity/v3/region.py:79 +#: openstackclient/identity/v3/region.py:70 msgid "Region ID to delete" msgstr "" -#: openstackclient/identity/v3/region.py:101 +#: openstackclient/identity/v3/region.py:88 msgid "Filter by parent region ID" msgstr "" -#: openstackclient/identity/v3/region.py:134 +#: openstackclient/identity/v3/region.py:118 msgid "Region to modify" msgstr "" -#: openstackclient/identity/v3/region.py:139 +#: openstackclient/identity/v3/region.py:123 msgid "New parent region ID" msgstr "" -#: openstackclient/identity/v3/region.py:175 +#: openstackclient/identity/v3/region.py:154 msgid "Region to display" msgstr "" +#: openstackclient/image/v1/image.py:191 openstackclient/image/v1/image.py:609 +#: openstackclient/image/v2/image.py:283 openstackclient/image/v2/image.py:794 +msgid "The --owner option is deprecated, please use --project instead." +msgstr "" + +#: openstackclient/network/v2/floating_ip.py:67 +msgid "Network to allocate floating IP from (name or ID)" +msgstr "" + +#: openstackclient/network/v2/floating_ip.py:75 +msgid "Subnet on which you want to create the floating IP (name or ID)" +msgstr "" + +#: openstackclient/network/v2/floating_ip.py:81 +msgid "Port to be associated with the floating IP (name or ID)" +msgstr "" + +#: openstackclient/network/v2/floating_ip.py:88 +msgid "Floating IP address" +msgstr "" + +#: openstackclient/network/v2/floating_ip.py:94 +msgid "Fixed IP address mapped to the floating IP" +msgstr "" + +#: openstackclient/network/v2/floating_ip.py:119 +msgid "Floating IP to delete (IP address or ID)" +msgstr "" + +#: openstackclient/network/v2/floating_ip.py:193 +msgid "Floating IP to display (IP address or ID)" +msgstr "" + +#: openstackclient/network/v2/network.py:114 +msgid "New network name" +msgstr "" + +#: openstackclient/network/v2/network.py:121 +#: openstackclient/network/v2/network.py:385 +msgid "Share the network between projects" +msgstr "" + +#: openstackclient/network/v2/network.py:126 +#: openstackclient/network/v2/network.py:390 +msgid "Do not share the network between projects" +msgstr "" + +#: openstackclient/network/v2/network.py:136 +msgid "Enable network (default)" +msgstr "" + +#: openstackclient/network/v2/network.py:141 +#: openstackclient/network/v2/network.py:378 +msgid "Disable network" +msgstr "" + +#: openstackclient/network/v2/network.py:146 +#: openstackclient/network/v2/port.py:246 +#: openstackclient/network/v2/router.py:174 +#: openstackclient/network/v2/security_group.py:116 +#: openstackclient/network/v2/security_group_rule.py:127 +#: openstackclient/network/v2/subnet.py:183 +#: openstackclient/network/v2/subnet_pool.py:114 +msgid "Owner's project (name or ID)" +msgstr "" + +#: openstackclient/network/v2/network.py:154 +msgid "" +"Availability Zone in which to create this network (Network Availability " +"Zone extension required, repeat option to set multiple availability " +"zones)" +msgstr "" + +#: openstackclient/network/v2/network.py:162 +#: openstackclient/network/v2/network.py:396 +msgid "Set this network as an external network (external-net extension required)" +msgstr "" + +#: openstackclient/network/v2/network.py:168 +msgid "Set this network as an internal network (default)" +msgstr "" + +#: openstackclient/network/v2/network.py:174 +msgid "Specify if this network should be used as the default external network" +msgstr "" + +#: openstackclient/network/v2/network.py:180 +msgid "Do not use the network as the default external network. (default)" +msgstr "" + +#: openstackclient/network/v2/network.py:188 +msgid "" +"The physical mechanism by which the virtual network is implemented. The " +"supported options are: flat, gre, local, vlan, vxlan" +msgstr "" + +#: openstackclient/network/v2/network.py:196 +msgid "Name of the physical network over which the virtual network is implemented" +msgstr "" + +#: openstackclient/network/v2/network.py:203 +msgid "VLAN ID for VLAN networks or Tunnel ID for GRE/VXLAN networks" +msgstr "" + +#: openstackclient/network/v2/network.py:212 +msgid "IPv4 subnet for fixed IPs (in CIDR notation)" +msgstr "" + +#: openstackclient/network/v2/network.py:361 +msgid "Network to modify (name or ID)" +msgstr "" + +#: openstackclient/network/v2/network.py:366 +msgid "Set network name" +msgstr "" + +#: openstackclient/network/v2/network.py:373 +msgid "Enable network" +msgstr "" + +#: openstackclient/network/v2/network.py:402 +msgid "Set this network as an internal network" +msgstr "" + +#: openstackclient/network/v2/network.py:408 +msgid "Set the network as the default external network" +msgstr "" + +#: openstackclient/network/v2/network.py:413 +msgid "Do not use the network as the default external network" +msgstr "" + +#: openstackclient/network/v2/network.py:436 +msgid "Network to display (name or ID)" +msgstr "" + +#: openstackclient/network/v2/port.py:73 +msgid "The --device-id option is deprecated, please use --device instead." +msgstr "" + +#: openstackclient/network/v2/port.py:79 +msgid "The --host-id option is deprecated, please use --host instead." +msgstr "" + +#: openstackclient/network/v2/port.py:161 +msgid "Port device ID" +msgstr "" + +#: openstackclient/network/v2/port.py:171 +msgid "Device owner of this port" +msgstr "" + +#: openstackclient/network/v2/port.py:178 +msgid "" +"VNIC type for this port (direct | direct-physical | macvtap | normal | " +"baremetal, default: normal)" +msgstr "" + +#: openstackclient/network/v2/port.py:187 +msgid "Allocate port on host (ID only)" +msgstr "" + +#: openstackclient/network/v2/port.py:206 +msgid "Network this port belongs to (name or ID)" +msgstr "" + +#: openstackclient/network/v2/port.py:214 +#: openstackclient/network/v2/port.py:365 +msgid "" +"Desired IP and/or subnet (name or ID) for this port: subnet=,ip-" +"address= (repeat option to set multiple fixed IP addresses)" +msgstr "" + +#: openstackclient/network/v2/port.py:222 +#: openstackclient/network/v2/port.py:379 +msgid "" +"Custom data to be passed as binding:profile: = (repeat option" +" to set multiple binding:profile data)" +msgstr "" + +#: openstackclient/network/v2/port.py:231 +msgid "Enable port (default)" +msgstr "" + +#: openstackclient/network/v2/port.py:236 +#: openstackclient/network/v2/port.py:352 +msgid "Disable port" +msgstr "" + +#: openstackclient/network/v2/port.py:241 +msgid "MAC address of this port" +msgstr "" + +#: openstackclient/network/v2/port.py:280 +msgid "Port(s) to delete (name or ID)" +msgstr "" + +#: openstackclient/network/v2/port.py:301 +msgid "List only ports attached to this router (name or ID)" +msgstr "" + +#: openstackclient/network/v2/port.py:347 +msgid "Enable port" +msgstr "" + +#: openstackclient/network/v2/port.py:357 +msgid "Set port name" +msgstr "" + +#: openstackclient/network/v2/port.py:372 +msgid "Clear existing information of fixed IP addresses" +msgstr "" + +#: openstackclient/network/v2/port.py:386 +msgid "Clear existing information of binding:profile" +msgstr "" + +#: openstackclient/network/v2/port.py:391 +msgid "Port to modify (name or ID)" +msgstr "" + +#: openstackclient/network/v2/port.py:429 +msgid "Port to display (name or ID)" +msgstr "" + +#: openstackclient/network/v2/router.py:99 +msgid "Router to which port will be added (name or ID)" +msgstr "" + +#: openstackclient/network/v2/router.py:104 +msgid "Port to be added (name or ID)" +msgstr "" + +#: openstackclient/network/v2/router.py:123 +msgid "Router to which subnet will be added (name or ID)" +msgstr "" + +#: openstackclient/network/v2/router.py:128 +msgid "Subnet to be added (name or ID)" +msgstr "" + +#: openstackclient/network/v2/router.py:150 +msgid "New router name" +msgstr "" + +#: openstackclient/network/v2/router.py:157 +msgid "Enable router (default)" +msgstr "" + +#: openstackclient/network/v2/router.py:162 +#: openstackclient/network/v2/router.py:351 +msgid "Disable router" +msgstr "" + +#: openstackclient/network/v2/router.py:169 +msgid "Create a distributed router" +msgstr "" + +#: openstackclient/network/v2/router.py:182 +msgid "" +"Availability Zone in which to create this router (Router Availability " +"Zone extension required, repeat option to set multiple availability " +"zones)" +msgstr "" + +#: openstackclient/network/v2/router.py:210 +msgid "Router(s) to delete (name or ID)" +msgstr "" + +#: openstackclient/network/v2/router.py:283 +msgid "Router from which port will be removed (name or ID)" +msgstr "" + +#: openstackclient/network/v2/router.py:288 +msgid "Port to be removed (name or ID)" +msgstr "" + +#: openstackclient/network/v2/router.py:307 +msgid "Router from which the subnet will be removed (name or ID)" +msgstr "" + +#: openstackclient/network/v2/router.py:312 +msgid "Subnet to be removed (name or ID)" +msgstr "" + +#: openstackclient/network/v2/router.py:334 +msgid "Router to modify (name or ID)" +msgstr "" + +#: openstackclient/network/v2/router.py:339 +msgid "Set router name" +msgstr "" + +#: openstackclient/network/v2/router.py:346 +msgid "Enable router" +msgstr "" + +#: openstackclient/network/v2/router.py:357 +msgid "Set router to distributed mode (disabled router only)" +msgstr "" + +#: openstackclient/network/v2/router.py:362 +msgid "Set router to centralized mode (disabled router only)" +msgstr "" + +#: openstackclient/network/v2/router.py:372 +msgid "" +"Routes associated with the router destination: destination subnet (in " +"CIDR notation) gateway: nexthop IP address (repeat option to set multiple" +" routes)" +msgstr "" + +#: openstackclient/network/v2/router.py:380 +msgid "Clear routes associated with the router" +msgstr "" + +#: openstackclient/network/v2/router.py:412 +msgid "Router to display (name or ID)" +msgstr "" + +#: openstackclient/network/v2/security_group.py:103 +#: openstackclient/network/v2/security_group.py:249 +msgid "New security group name" +msgstr "" + +#: openstackclient/network/v2/security_group.py:108 +msgid "Security group description" +msgstr "" + +#: openstackclient/network/v2/security_group.py:173 +msgid "Security group to delete (name or ID)" +msgstr "" + +#: openstackclient/network/v2/security_group.py:208 +msgid "Display information from all projects (admin only)" +msgstr "" + +#: openstackclient/network/v2/security_group.py:244 +msgid "Security group to modify (name or ID)" +msgstr "" + +#: openstackclient/network/v2/security_group.py:254 +msgid "New security group description" +msgstr "" + +#: openstackclient/network/v2/security_group.py:299 +msgid "Security group to display (name or ID)" +msgstr "" + +#: openstackclient/network/v2/security_group_rule.py:82 +msgid "IP protocol (icmp, tcp, udp; default: tcp)" +msgstr "" + +#: openstackclient/network/v2/security_group_rule.py:88 +msgid "" +"Source IP address block (may use CIDR notation; default for IPv4 rule: " +"0.0.0.0/0)" +msgstr "" + +#: openstackclient/network/v2/security_group_rule.py:94 +msgid "Source security group (name or ID)" +msgstr "" + +#: openstackclient/network/v2/security_group_rule.py:101 +msgid "" +"Destination port, may be a single port or port range: 137:139 (only " +"required for IP protocols tcp and udp)" +msgstr "" + +#: openstackclient/network/v2/security_group_rule.py:111 +msgid "Rule applies to incoming network traffic (default)" +msgstr "" + +#: openstackclient/network/v2/security_group_rule.py:116 +msgid "Rule applies to outgoing network traffic" +msgstr "" + +#: openstackclient/network/v2/security_group_rule.py:122 +msgid "Ethertype of network traffic (IPv4, IPv6; default: IPv4)" +msgstr "" + +#: openstackclient/network/v2/security_group_rule.py:221 +msgid "Security group rule to delete (ID only)" +msgstr "" + +#: openstackclient/network/v2/security_group_rule.py:241 +msgid "List all rules in this security group (name or ID)" +msgstr "" + +#: openstackclient/network/v2/security_group_rule.py:336 +msgid "Security group rule to display (ID only)" +msgstr "" + +#: openstackclient/network/v2/subnet.py:54 +msgid "" +"Allocation pool IP addresses for this subnet e.g.: " +"start=192.168.199.2,end=192.168.199.254 (repeat option to add multiple IP" +" addresses)" +msgstr "" + +#: openstackclient/network/v2/subnet.py:63 +msgid "DNS server for this subnet (repeat option to set multiple DNS servers)" +msgstr "" + +#: openstackclient/network/v2/subnet.py:72 +msgid "" +"Additional route for this subnet e.g.: " +"destination=10.10.0.0/16,gateway=192.168.71.254 destination: destination " +"subnet (in CIDR notation) gateway: nexthop IP address (repeat option to " +"add multiple routes)" +msgstr "" + +#: openstackclient/network/v2/subnet.py:178 +msgid "New subnet name" +msgstr "" + +#: openstackclient/network/v2/subnet.py:190 +msgid "Subnet pool from which this subnet will obtain a CIDR (Name or ID)" +msgstr "" + +#: openstackclient/network/v2/subnet.py:196 +msgid "Use default subnet pool for --ip-version" +msgstr "" + +#: openstackclient/network/v2/subnet.py:201 +msgid "Prefix length for subnet allocation from subnet pool" +msgstr "" + +#: openstackclient/network/v2/subnet.py:206 +msgid "" +"Subnet range in CIDR notation (required if --subnet-pool is not " +"specified, optional otherwise)" +msgstr "" + +#: openstackclient/network/v2/subnet.py:215 +msgid "Enable DHCP (default)" +msgstr "" + +#: openstackclient/network/v2/subnet.py:220 +#: openstackclient/network/v2/subnet.py:348 +msgid "Disable DHCP" +msgstr "" + +#: openstackclient/network/v2/subnet.py:226 +msgid "" +"Specify a gateway for the subnet. The three options are: : " +"Specific IP address to use as the gateway, 'auto': Gateway address should" +" automatically be chosen from within the subnet itself, 'none': This " +"subnet will not use a gateway, e.g.: --gateway 192.168.9.1, --gateway " +"auto, --gateway none (default is 'auto')" +msgstr "" + +#: openstackclient/network/v2/subnet.py:238 +msgid "" +"IP version (default is 4). Note that when subnet pool is specified, IP " +"version is determined from the subnet pool and this option is ignored" +msgstr "" + +#: openstackclient/network/v2/subnet.py:245 +msgid "" +"IPv6 RA (Router Advertisement) mode, valid modes: [dhcpv6-stateful, " +"dhcpv6-stateless, slaac]" +msgstr "" + +#: openstackclient/network/v2/subnet.py:251 +msgid "IPv6 address mode, valid modes: [dhcpv6-stateful, dhcpv6-stateless, slaac]" +msgstr "" + +#: openstackclient/network/v2/subnet.py:258 +msgid "Network this subnet belongs to (name or ID)" +msgstr "" + +#: openstackclient/network/v2/subnet.py:280 +msgid "Subnet to delete (name or ID)" +msgstr "" + +#: openstackclient/network/v2/subnet.py:331 +msgid "Subnet to modify (name or ID)" +msgstr "" + +#: openstackclient/network/v2/subnet.py:336 +msgid "Updated name of the subnet" +msgstr "" + +#: openstackclient/network/v2/subnet.py:343 +msgid "Enable DHCP" +msgstr "" + +#: openstackclient/network/v2/subnet.py:353 +msgid "" +"Specify a gateway for the subnet. The options are: : Specific" +" IP address to use as the gateway, 'none': This subnet will not use a " +"gateway, e.g.: --gateway 192.168.9.1, --gateway none" +msgstr "" + +#: openstackclient/network/v2/subnet.py:387 +msgid "Subnet to display (name or ID)" +msgstr "" + +#: openstackclient/network/v2/subnet_pool.py:77 +msgid "" +"Set subnet pool prefixes (in CIDR notation) (repeat option to set " +"multiple prefixes)" +msgstr "" + +#: openstackclient/network/v2/subnet_pool.py:84 +msgid "Set subnet pool default prefix length" +msgstr "" + +#: openstackclient/network/v2/subnet_pool.py:90 +msgid "Set subnet pool minimum prefix length" +msgstr "" + +#: openstackclient/network/v2/subnet_pool.py:96 +msgid "Set subnet pool maximum prefix length" +msgstr "" + +#: openstackclient/network/v2/subnet_pool.py:108 +msgid "Name of the new subnet pool" +msgstr "" + +#: openstackclient/network/v2/subnet_pool.py:120 +#: openstackclient/network/v2/subnet_pool.py:226 +msgid "" +"Set address scope associated with the subnet pool (name or ID), prefixes " +"must be unique across address scopes" +msgstr "" + +#: openstackclient/network/v2/subnet_pool.py:146 +msgid "Subnet pool to delete (name or ID)" +msgstr "" + +#: openstackclient/network/v2/subnet_pool.py:214 +msgid "Subnet pool to modify (name or ID)" +msgstr "" + +#: openstackclient/network/v2/subnet_pool.py:219 +msgid "Set subnet pool name" +msgstr "" + +#: openstackclient/network/v2/subnet_pool.py:233 +msgid "Remove address scope associated with the subnet pool" +msgstr "" + +#: openstackclient/network/v2/subnet_pool.py:262 +msgid "Subnet pool to display (name or ID)" +msgstr "" + diff --git a/openstackclient/locale/zh_TW/LC_MESSAGES/openstackclient.po b/openstackclient/locale/zh_TW/LC_MESSAGES/openstackclient.po index 2603266684..a121acabb8 100644 --- a/openstackclient/locale/zh_TW/LC_MESSAGES/openstackclient.po +++ b/openstackclient/locale/zh_TW/LC_MESSAGES/openstackclient.po @@ -4,12 +4,12 @@ # python-openstackclient project. # # Translators: -# OpenStack Infra , 2015. #zanata +# Andreas Jaeger , 2016. #zanata msgid "" msgstr "" -"Project-Id-Version: python-openstackclient 2.0.1.dev168\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2016-01-19 02:20+0000\n" +"Project-Id-Version: python-openstackclient 2.2.1.dev235\n" +"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" +"POT-Creation-Date: 2016-04-19 05:02+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -125,10 +125,6 @@ msgstr "要刪除的端點識別號" msgid "Endpoint ID to display" msgstr "要顯示的端點識別號" -#, python-format -msgid "Error creating server snapshot: %s" -msgstr "新增雲實例即時存檔時出錯:%s" - #, python-format msgid "Error creating server: %s" msgstr "新增雲實例時出錯:%s" @@ -136,9 +132,6 @@ msgstr "新增雲實例時出錯:%s" msgid "Error retrieving diagnostics data" msgstr "獲得診斷資料時出錯" -msgid "File to inject into image before boot (repeat for multiple files)" -msgstr "在開機前要注入映像檔的檔案(為多個檔案重復指定)" - msgid "Filter by parent region ID" msgstr "以父地區識別號來篩選" @@ -287,9 +280,6 @@ msgstr "要刪除的專案(名稱或識別號)" msgid "Prompt interactively for password" msgstr "為密碼互動提示" -msgid "Property key to remove from server (repeat to unset multiple values)" -msgstr "要從雲實例上移除的屬性鍵(重復來取消選擇多個值)" - msgid "" "Property to add/change for this server (repeat option to set multiple " "properties)" @@ -385,9 +375,6 @@ msgstr "要顯示的伺服器(類型、名稱或識別號)" msgid "Set a project property (repeat option to set multiple properties)" msgstr "設定專案屬性(重復這選項來設定多個屬性)" -msgid "Set a property on this server (repeat for multiple values)" -msgstr "為此伺服器設定屬性(為多個值重複設定)" - msgid "Set default project (name or ID)" msgstr "設定預設專案(名稱或識別號)" From 02a46a067b8cbc0ac27328c0e77b7529ca9daee4 Mon Sep 17 00:00:00 2001 From: Cedric Brandily Date: Tue, 12 Apr 2016 21:07:11 +0200 Subject: [PATCH 0825/3095] Fix client certificate/key support for Network v2 commands Currently network v2 commands don't support client certificate/key because they were not passed to OpenStackSDK Connection which is used by network v2 commands. This changes corrects the integration with OpenStacKSDK to pass client certificate/key. Closes-Bug: #1569513 Related-Bug: #1569508 Depends-On: Ic093f8515e7b15931994e4516ebec8f4399d021e Change-Id: Ie37e8e988ca695a09894c6c93560dacd83f17030 --- openstackclient/network/client.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openstackclient/network/client.py b/openstackclient/network/client.py index 3abe18e8a0..be06d2b552 100644 --- a/openstackclient/network/client.py +++ b/openstackclient/network/client.py @@ -36,7 +36,9 @@ def make_client(instance): prof.set_region(API_NAME, instance._region_name) prof.set_version(API_NAME, instance._api_version[API_NAME]) conn = connection.Connection(authenticator=instance.session.auth, - verify=instance.session.verify, profile=prof) + verify=instance.session.verify, + cert=instance.session.cert, + profile=prof) LOG.debug('Connection: %s', conn) LOG.debug('Network client initialized using OpenStack SDK: %s', conn.network) From ff62c832b5397b1cb4d948f5f88c303434f49b11 Mon Sep 17 00:00:00 2001 From: Kyrylo Romanenko Date: Thu, 24 Mar 2016 19:43:53 +0200 Subject: [PATCH 0826/3095] Update keypair tests Make testcases separate instances of test class. Use setUp with addCleanup methods for every test case. Add negative test cases. Closes-Bug: #1564480 Change-Id: I5d8fd2a238e6cf0584777eb0d24dfcaed4133ee1 --- functional/tests/compute/v2/test_keypair.py | 162 ++++++++++++++++---- 1 file changed, 131 insertions(+), 31 deletions(-) diff --git a/functional/tests/compute/v2/test_keypair.py b/functional/tests/compute/v2/test_keypair.py index d6c5ad2891..80cd4b464b 100644 --- a/functional/tests/compute/v2/test_keypair.py +++ b/functional/tests/compute/v2/test_keypair.py @@ -11,41 +11,84 @@ # under the License. import tempfile -import uuid from functional.common import test +from tempest_lib.common.utils import data_utils +from tempest_lib import exceptions -PUBLIC_KEY = ( - 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDWNGczJxNaFUrJJVhta4dWsZY6bU' - '5HUMPbyfSMu713ca3mYtG848W4dfDCB98KmSQx2Bl0D6Q2nrOszOXEQWAXNdfMadnW' - 'c4mNwhZcPBVohIFoC1KZJC8kcBTvFZcoz3mdIijxJtywZNpGNh34VRJlZeHyYjg8/D' - 'esHzdoBVd5c/4R36emQSIV9ukY6PHeZ3scAH4B3K9PxItJBwiFtouSRphQG0bJgOv/' - 'gjAjMElAvg5oku98cb4QiHZ8T8WY68id804raHR6pJxpVVJN4TYJmlUs+NOVM+pPKb' - 'KJttqrIBTkawGK9pLHNfn7z6v1syvUo/4enc1l0Q/Qn2kWiz67 fake@openstack' -) +class KeypairBase(test.TestCase): + """Methods for functional tests.""" -class KeypairTests(test.TestCase): - """Functional tests for compute keypairs. """ - NAME = uuid.uuid4().hex - HEADERS = ['Name'] - FIELDS = ['name'] + def keypair_create(self, name=data_utils.rand_uuid()): + """Create keypair and add cleanup.""" + raw_output = self.openstack('keypair create ' + name) + self.addCleanup(self.keypair_delete, name, True) + if not raw_output: + self.fail('Keypair has not been created!') - @classmethod - def setUpClass(cls): - private_key = cls.openstack('keypair create ' + cls.NAME) - cls.assertInOutput('-----BEGIN RSA PRIVATE KEY-----', private_key) - cls.assertInOutput('-----END RSA PRIVATE KEY-----', private_key) + def keypair_list(self, params=''): + """Return dictionary with list of keypairs.""" + raw_output = self.openstack('keypair list') + keypairs = self.parse_show_as_object(raw_output) + return keypairs - @classmethod - def tearDownClass(cls): - raw_output = cls.openstack('keypair delete ' + cls.NAME) - cls.assertOutput('', raw_output) + def keypair_delete(self, name, ignore_exceptions=False): + """Try to delete keypair by name.""" + try: + self.openstack('keypair delete ' + name) + except exceptions.CommandFailed: + if not ignore_exceptions: + raise - def test_keypair_create(self): + +class KeypairTests(KeypairBase): + """Functional tests for compute keypairs.""" + + PUBLIC_KEY = ( + 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDWNGczJxNaFUrJJVhta4dWsZY6bU' + '5HUMPbyfSMu713ca3mYtG848W4dfDCB98KmSQx2Bl0D6Q2nrOszOXEQWAXNdfMadnW' + 'c4mNwhZcPBVohIFoC1KZJC8kcBTvFZcoz3mdIijxJtywZNpGNh34VRJlZeHyYjg8/D' + 'esHzdoBVd5c/4R36emQSIV9ukY6PHeZ3scAH4B3K9PxItJBwiFtouSRphQG0bJgOv/' + 'gjAjMElAvg5oku98cb4QiHZ8T8WY68id804raHR6pJxpVVJN4TYJmlUs+NOVM+pPKb' + 'KJttqrIBTkawGK9pLHNfn7z6v1syvUo/4enc1l0Q/Qn2kWiz67 fake@openstack' + ) + + def setUp(self): + """Create keypair with randomized name for tests.""" + super(KeypairTests, self).setUp() + self.KPName = data_utils.rand_name('TestKeyPair') + self.keypair = self.keypair_create(self.KPName) + + def test_keypair_create_duplicate(self): + """Try to create duplicate name keypair. + + Test steps: + 1) Create keypair in setUp + 2) Try to create duplicate keypair with the same name + """ + self.assertRaises(exceptions.CommandFailed, + self.openstack, 'keypair create ' + self.KPName) + + def test_keypair_create_noname(self): + """Try to create keypair without name. + + Test steps: + 1) Try to create keypair without a name + """ + self.assertRaises(exceptions.CommandFailed, + self.openstack, 'keypair create') + + def test_keypair_create_public_key(self): + """Test for create keypair with --public-key option. + + Test steps: + 1) Create keypair with given public key + 2) Delete keypair + """ with tempfile.NamedTemporaryFile() as f: - f.write(PUBLIC_KEY) + f.write(self.PUBLIC_KEY) f.flush() raw_output = self.openstack( @@ -57,12 +100,69 @@ def test_keypair_create(self): ) self.assertIn('tmpkey', raw_output) + def test_keypair_create(self): + """Test keypair create command. + + Test steps: + 1) Create keypair in setUp + 2) Check RSA private key in output + 3) Check for new keypair in keypairs list + """ + NewName = data_utils.rand_name('TestKeyPairCreated') + raw_output = self.openstack('keypair create ' + NewName) + self.addCleanup(self.openstack, 'keypair delete ' + NewName) + self.assertInOutput('-----BEGIN RSA PRIVATE KEY-----', raw_output) + self.assertRegex(raw_output, "[0-9A-Za-z+/]+[=]{0,3}\n") + self.assertInOutput('-----END RSA PRIVATE KEY-----', raw_output) + self.assertIn(NewName, self.keypair_list()) + + def test_keypair_delete_not_existing(self): + """Try to delete keypair with not existing name. + + Test steps: + 1) Create keypair in setUp + 2) Try to delete not existing keypair + """ + self.assertRaises(exceptions.CommandFailed, + self.openstack, 'keypair delete not_existing') + + def test_keypair_delete(self): + """Test keypair delete command. + + Test steps: + 1) Create keypair in setUp + 2) Delete keypair + 3) Check that keypair not in keypairs list + """ + self.openstack('keypair delete ' + self.KPName) + self.assertNotIn(self.KPName, self.keypair_list()) + def test_keypair_list(self): - opts = self.get_list_opts(self.HEADERS) - raw_output = self.openstack('keypair list' + opts) - self.assertIn(self.NAME, raw_output) + """Test keypair list command. + + Test steps: + 1) Create keypair in setUp + 2) List keypairs + 3) Check output table structure + 4) Check keypair name in output + """ + HEADERS = ['Name', 'Fingerprint'] + raw_output = self.openstack('keypair list') + items = self.parse_listing(raw_output) + self.assert_table_structure(items, HEADERS) + self.assertIn(self.KPName, raw_output) def test_keypair_show(self): - opts = self.get_show_opts(self.FIELDS) - raw_output = self.openstack('keypair show ' + self.NAME + opts) - self.assertEqual(self.NAME + "\n", raw_output) + """Test keypair show command. + + Test steps: + 1) Create keypair in setUp + 2) Show keypair + 3) Check output table structure + 4) Check keypair name in output + """ + HEADERS = ['Field', 'Value'] + raw_output = self.openstack('keypair show ' + self.KPName) + items = self.parse_listing(raw_output) + self.assert_table_structure(items, HEADERS) + self.assertInOutput(self.KPName, raw_output) From 8ab267c2ebaf7d46734b6b42dd176f6b695ad199 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Tue, 19 Apr 2016 12:28:55 +0000 Subject: [PATCH 0827/3095] Updated from global requirements Change-Id: Ia35e711f1916749b156a1344ffafb4391a7a5408 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 8cf531a34d..f88e771ea6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,4 +17,4 @@ python-keystoneclient!=1.8.0,!=2.1.0,>=1.6.0 # Apache-2.0 python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0 python-cinderclient>=1.6.0 # Apache-2.0 requests!=2.9.0,>=2.8.1 # Apache-2.0 -stevedore>=1.5.0 # Apache-2.0 +stevedore>=1.9.0 # Apache-2.0 From 530fe42589a2138278f100f791d8c6d3fbed8950 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Mon, 18 Apr 2016 14:39:21 -0500 Subject: [PATCH 0828/3095] Rename --profile to --os-profile * The --profile global option is deprecated but will be supported through at least April 2017. * Update man page Closes-bug: #1571812 Change-Id: I2e623411a56096b4cc352f4eedbf770632ae2cc3 --- doc/source/man/openstack.rst | 10 ++--- openstackclient/shell.py | 39 +++++++++++++------ openstackclient/tests/test_shell.py | 2 +- .../notes/bug-1571812-49cdce4df5f3d481.yaml | 10 +++++ 4 files changed, 44 insertions(+), 17 deletions(-) create mode 100644 releasenotes/notes/bug-1571812-49cdce4df5f3d481.yaml diff --git a/doc/source/man/openstack.rst b/doc/source/man/openstack.rst index a4be351ae0..a2ecd8db73 100644 --- a/doc/source/man/openstack.rst +++ b/doc/source/man/openstack.rst @@ -129,11 +129,11 @@ OPTIONS :option:`--os-interface` Interface type. Valid options are `public`, `admin` and `internal`. -:option: `--profile` - HMAC key to use for encrypting context data for performance profiling of - requested operation. This key should be the value of one of the HMAC keys - defined in the configuration files of OpenStack services, user would like - to trace through. +:option:`--os-profile` + Performance profiling HMAC key for encrypting context data + + This key should be the value of one of the HMAC keys defined in the + configuration files of OpenStack services to be traced. :option:`--log-file` Specify a file to log output. Disabled by default. diff --git a/openstackclient/shell.py b/openstackclient/shell.py index b7bc7b1a44..b96fb089b9 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -16,6 +16,7 @@ """Command-line interface to the OpenStack APIs""" +import argparse import getpass import logging import sys @@ -131,6 +132,16 @@ def run(self, argv): self.log.info("END return value: %s", ret_val) def init_profile(self): + # NOTE(dtroyer): Remove this 'if' block when the --profile global + # option is removed + if osprofiler_profiler and self.options.old_profile: + self.log.warning( + 'The --profile option is deprecated, ' + 'please use --os-profile instead' + ) + if not self.options.profile: + self.options.profile = self.options.old_profile + self.do_profile = osprofiler_profiler and self.options.profile if self.do_profile: osprofiler_profiler.init(self.options.profile) @@ -144,7 +155,7 @@ def close_profile(self): # bigger than most big default one (CRITICAL) or something like # that (PROFILE = 60 for instance), but not sure we need it here. self.log.warning("Trace ID: %s" % trace_id) - self.log.warning("To display trace use next command:\n" + self.log.warning("Display trace with command:\n" "osprofiler trace show --html %s " % trace_id) def run_subcommand(self, argv): @@ -242,16 +253,22 @@ def build_option_parser(self, description, version): # osprofiler HMAC key argument if osprofiler_profiler: - parser.add_argument('--profile', - metavar='hmac-key', - help='HMAC key to use for encrypting context ' - 'data for performance profiling of operation. ' - 'This key should be the value of one of the ' - 'HMAC keys configured in osprofiler ' - 'middleware in the projects user would like ' - 'to profile. It needs to be specified in ' - 'configuration files of the required ' - 'projects.') + parser.add_argument( + '--os-profile', + metavar='hmac-key', + dest='profile', + help='HMAC key for encrypting profiling context data', + ) + # NOTE(dtroyer): This global option should have been named + # --os-profile as --profile interferes with at + # least one existing command option. Deprecate + # --profile and remove after Apr 2017. + parser.add_argument( + '--profile', + metavar='hmac-key', + dest='old_profile', + help=argparse.SUPPRESS, + ) return clientmanager.build_plugin_option_parser(parser) diff --git a/openstackclient/tests/test_shell.py b/openstackclient/tests/test_shell.py index ab97dd91b1..90454fc22e 100644 --- a/openstackclient/tests/test_shell.py +++ b/openstackclient/tests/test_shell.py @@ -112,7 +112,7 @@ '--os-default-domain': (DEFAULT_DOMAIN_NAME, True, True), '--os-cacert': ('/dev/null', True, True), '--timing': (True, True, False), - '--profile': ('SECRET_KEY', True, False), + '--os-profile': ('SECRET_KEY', True, False), '--os-interface': (DEFAULT_INTERFACE, True, True) } diff --git a/releasenotes/notes/bug-1571812-49cdce4df5f3d481.yaml b/releasenotes/notes/bug-1571812-49cdce4df5f3d481.yaml new file mode 100644 index 0000000000..f331b4b1a7 --- /dev/null +++ b/releasenotes/notes/bug-1571812-49cdce4df5f3d481.yaml @@ -0,0 +1,10 @@ +--- +upgrade: + - | + Deprecate global option ``--profile`` in favor of ``--os-profile``. + + ``--profile`` interferes with existing command options with the same name. + Unfortunately it appeared in a release so we must follow the deprecation + process and wait one year (April 2017) before removing it. + + [Bug `1571812 `_] From 09c20b2b5c53024c47da8828095ea95dc63810f6 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Fri, 15 Apr 2016 15:46:19 +0800 Subject: [PATCH 0829/3095] Fix mutable default arguments in tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Python’s default arguments are evaluated only once when the function is defined, not each time the function is called. This means that if you use a mutable default argument (like list and dict) and mutate it, you will and have mutated that object for all future calls to the function as well. More details about this wrong usage here: http://docs.python-guide.org/en/latest/writing/gotchas/#mutable-default-arguments In unit tests, most FakeXXX classes' methods take mutable arguments with default values [] or {}. We should change them to None. Change-Id: Iea833b66aa1379829511ad5c6d4432b72f3488e2 Closed-bug: #1550320 --- openstackclient/tests/compute/v2/fakes.py | 59 +++++++++++++---------- openstackclient/tests/fakes.py | 10 +++- openstackclient/tests/image/v2/fakes.py | 6 ++- openstackclient/tests/network/v2/fakes.py | 54 ++++++++++++--------- openstackclient/tests/volume/v2/fakes.py | 12 +++-- 5 files changed, 85 insertions(+), 56 deletions(-) diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 7f39bad0be..708daaa59f 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -100,8 +100,7 @@ def create_one_aggregate(attrs=None): :return: A FakeResource object, with id and other attributes """ - if attrs is None: - attrs = {} + attrs = attrs or {} # Set default attribute aggregate_info = { @@ -217,7 +216,7 @@ class FakeHypervisor(object): """Fake one or more hypervisor.""" @staticmethod - def create_one_hypervisor(attrs={}): + def create_one_hypervisor(attrs=None): """Create a fake hypervisor. :param Dictionary attrs: @@ -225,6 +224,8 @@ def create_one_hypervisor(attrs={}): :return: A FakeResource object, with id, hypervisor_hostname, and so on """ + attrs = attrs or {} + # Set default attributes. hypervisor_info = { 'id': 'hypervisor-id-' + uuid.uuid4().hex, @@ -263,7 +264,7 @@ def create_one_hypervisor(attrs={}): return hypervisor @staticmethod - def create_hypervisors(attrs={}, count=2): + def create_hypervisors(attrs=None, count=2): """Create multiple fake hypervisors. :param Dictionary attrs: @@ -284,7 +285,7 @@ class FakeHypervisorStats(object): """Fake one or more hypervisor stats.""" @staticmethod - def create_one_hypervisor_stats(attrs={}): + def create_one_hypervisor_stats(attrs=None): """Create a fake hypervisor stats. :param Dictionary attrs: @@ -292,6 +293,8 @@ def create_one_hypervisor_stats(attrs={}): :return: A FakeResource object, with id, hypervisor_hostname, and so on """ + attrs = attrs or {} + # Set default attributes. stats_info = { 'count': 2, @@ -319,7 +322,7 @@ def create_one_hypervisor_stats(attrs={}): return hypervisor_stats @staticmethod - def create_hypervisors_stats(attrs={}, count=2): + def create_hypervisors_stats(attrs=None, count=2): """Create multiple fake hypervisors stats. :param Dictionary attrs: @@ -349,8 +352,7 @@ def create_one_security_group(attrs=None): :return: A FakeResource object, with id, name, etc. """ - if attrs is None: - attrs = {} + attrs = attrs or {} # Set default attributes. security_group_attrs = { @@ -400,8 +402,7 @@ def create_one_security_group_rule(attrs=None): :return: A FakeResource object, with id, etc. """ - if attrs is None: - attrs = {} + attrs = attrs or {} # Set default attributes. security_group_rule_attrs = { @@ -445,7 +446,7 @@ class FakeServer(object): """Fake one or more compute servers.""" @staticmethod - def create_one_server(attrs={}, methods={}): + def create_one_server(attrs=None, methods=None): """Create a fake server. :param Dictionary attrs: @@ -455,6 +456,9 @@ def create_one_server(attrs={}, methods={}): :return: A FakeResource object, with id, name, metadata """ + attrs = attrs or {} + methods = methods or {} + # Set default attributes. server_info = { 'id': 'server-id-' + uuid.uuid4().hex, @@ -477,7 +481,7 @@ def create_one_server(attrs={}, methods={}): return server @staticmethod - def create_servers(attrs={}, methods={}, count=2): + def create_servers(attrs=None, methods=None, count=2): """Create multiple fake servers. :param Dictionary attrs: @@ -527,8 +531,7 @@ def create_one_flavor(attrs=None): :return: A FakeResource object, with id, name, ram, vcpus, properties """ - if attrs is None: - attrs = {} + attrs = attrs or {} # Set default attributes. flavor_info = { @@ -566,7 +569,7 @@ def create_one_flavor(attrs=None): return flavor @staticmethod - def create_flavors(attrs={}, count=2): + def create_flavors(attrs=None, count=2): """Create multiple fake flavors. :param Dictionary attrs: @@ -614,10 +617,9 @@ def create_one_keypair(attrs=None, no_pri=False): :return: A FakeResource """ - # Set default attributes. - if attrs is None: - attrs = {} + attrs = attrs or {} + # Set default attributes. keypair_info = { 'name': 'keypair-name-' + uuid.uuid4().hex, 'fingerprint': 'dummy', @@ -658,7 +660,7 @@ class FakeAvailabilityZone(object): """Fake one or more compute availability zones (AZs).""" @staticmethod - def create_one_availability_zone(attrs={}): + def create_one_availability_zone(attrs=None): """Create a fake AZ. :param Dictionary attrs: @@ -666,6 +668,8 @@ def create_one_availability_zone(attrs={}): :return: A FakeResource object with zoneName, zoneState, etc. """ + attrs = attrs or {} + # Set default attributes. host_name = uuid.uuid4().hex service_name = uuid.uuid4().hex @@ -689,7 +693,7 @@ def create_one_availability_zone(attrs={}): return availability_zone @staticmethod - def create_availability_zones(attrs={}, count=2): + def create_availability_zones(attrs=None, count=2): """Create multiple fake AZs. :param Dictionary attrs: @@ -712,7 +716,7 @@ class FakeFloatingIP(object): """Fake one or more floating ip.""" @staticmethod - def create_one_floating_ip(attrs={}): + def create_one_floating_ip(attrs=None): """Create a fake floating ip. :param Dictionary attrs: @@ -720,6 +724,8 @@ def create_one_floating_ip(attrs={}): :return: A FakeResource object, with id, ip, and so on """ + attrs = attrs or {} + # Set default attributes. floating_ip_attrs = { 'id': 'floating-ip-id-' + uuid.uuid4().hex, @@ -739,7 +745,7 @@ def create_one_floating_ip(attrs={}): return floating_ip @staticmethod - def create_floating_ips(attrs={}, count=2): + def create_floating_ips(attrs=None, count=2): """Create multiple fake floating ips. :param Dictionary attrs: @@ -778,7 +784,7 @@ class FakeNetwork(object): """Fake one or more networks.""" @staticmethod - def create_one_network(attrs={}): + def create_one_network(attrs=None): """Create a fake network. :param Dictionary attrs: @@ -786,6 +792,8 @@ def create_one_network(attrs={}): :return: A FakeResource object, with id, label, cidr and so on """ + attrs = attrs or {} + # Set default attributes. network_attrs = { 'bridge': 'br100', @@ -831,7 +839,7 @@ def create_one_network(attrs={}): return network @staticmethod - def create_networks(attrs={}, count=2): + def create_networks(attrs=None, count=2): """Create multiple fake networks. :param Dictionary attrs: @@ -860,8 +868,7 @@ def create_one_host(attrs=None): :return: A FakeResource object, with id and other attributes """ - if attrs is None: - attrs = {} + attrs = attrs or {} # Set default attributes. host_info = { diff --git a/openstackclient/tests/fakes.py b/openstackclient/tests/fakes.py index f0cebb06d3..46f983dc46 100644 --- a/openstackclient/tests/fakes.py +++ b/openstackclient/tests/fakes.py @@ -142,7 +142,7 @@ def __init__(self, name, version): class FakeResource(object): - def __init__(self, manager=None, info={}, loaded=False, methods={}): + def __init__(self, manager=None, info=None, loaded=False, methods=None): """Set attributes and methods for a resource. :param manager: @@ -154,6 +154,9 @@ def __init__(self, manager=None, info={}, loaded=False, methods={}): :param Dictionary methods: A dictionary with all methods """ + info = info or {} + methods = methods or {} + self.__name__ = type(self).__name__ self.manager = manager self._info = info @@ -189,9 +192,12 @@ def keys(self): class FakeResponse(requests.Response): - def __init__(self, headers={}, status_code=200, data=None, encoding=None): + def __init__(self, headers=None, status_code=200, + data=None, encoding=None): super(FakeResponse, self).__init__() + headers = headers or {} + self.status_code = status_code self.headers.update(headers) diff --git a/openstackclient/tests/image/v2/fakes.py b/openstackclient/tests/image/v2/fakes.py index 3555d2d4ed..f90d846d6a 100644 --- a/openstackclient/tests/image/v2/fakes.py +++ b/openstackclient/tests/image/v2/fakes.py @@ -181,7 +181,7 @@ class FakeImage(object): """ @staticmethod - def create_one_image(attrs={}): + def create_one_image(attrs=None): """Create a fake image. :param Dictionary attrs: @@ -190,6 +190,8 @@ def create_one_image(attrs={}): A FakeResource object with id, name, owner, protected, visibility and tags attrs """ + attrs = attrs or {} + # Set default attribute image_info = { 'id': 'image-id' + uuid.uuid4().hex, @@ -210,7 +212,7 @@ def create_one_image(attrs={}): return image @staticmethod - def create_images(attrs={}, count=2): + def create_images(attrs=None, count=2): """Create multiple fake images. :param Dictionary attrs: diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 5fd7ea3b86..1989b515eb 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -83,8 +83,7 @@ def create_one_address_scope(attrs=None): :return: A FakeResource object with name, id, etc. """ - if attrs is None: - attrs = {} + attrs = attrs or {} # Set default attributes. address_scope_attrs = { @@ -112,7 +111,7 @@ class FakeAvailabilityZone(object): """Fake one or more network availability zones (AZs).""" @staticmethod - def create_one_availability_zone(attrs={}): + def create_one_availability_zone(attrs=None): """Create a fake AZ. :param Dictionary attrs: @@ -120,6 +119,8 @@ def create_one_availability_zone(attrs={}): :return: A FakeResource object with name, state, etc. """ + attrs = attrs or {} + # Set default attributes. availability_zone = { 'name': uuid.uuid4().hex, @@ -136,7 +137,7 @@ def create_one_availability_zone(attrs={}): return availability_zone @staticmethod - def create_availability_zones(attrs={}, count=2): + def create_availability_zones(attrs=None, count=2): """Create multiple fake AZs. :param Dictionary attrs: @@ -159,7 +160,7 @@ class FakeNetwork(object): """Fake one or more networks.""" @staticmethod - def create_one_network(attrs={}): + def create_one_network(attrs=None): """Create a fake network. :param Dictionary attrs: @@ -168,6 +169,8 @@ def create_one_network(attrs={}): A FakeResource object, with id, name, admin_state_up, router_external, status, subnets, tenant_id """ + attrs = attrs or {} + # Set default attributes. network_attrs = { 'id': 'network-id-' + uuid.uuid4().hex, @@ -196,7 +199,7 @@ def create_one_network(attrs={}): return network @staticmethod - def create_networks(attrs={}, count=2): + def create_networks(attrs=None, count=2): """Create multiple fake networks. :param Dictionary attrs: @@ -236,16 +239,16 @@ class FakePort(object): """Fake one or more ports.""" @staticmethod - def create_one_port(attrs={}): + def create_one_port(attrs=None): """Create a fake port. :param Dictionary attrs: A dictionary with all attributes - :param Dictionary methods: - A dictionary with all methods :return: A FakeResource object, with id, name, etc. """ + attrs = attrs or {} + # Set default attributes. port_attrs = { 'admin_state_up': True, @@ -288,7 +291,7 @@ def create_one_port(attrs={}): return port @staticmethod - def create_ports(attrs={}, count=2): + def create_ports(attrs=None, count=2): """Create multiple fake ports. :param Dictionary attrs: @@ -328,7 +331,7 @@ class FakeRouter(object): """Fake one or more routers.""" @staticmethod - def create_one_router(attrs={}): + def create_one_router(attrs=None): """Create a fake router. :param Dictionary attrs: @@ -337,6 +340,8 @@ def create_one_router(attrs={}): A FakeResource object, with id, name, admin_state_up, status, tenant_id """ + attrs = attrs or {} + # Set default attributes. router_attrs = { 'id': 'router-id-' + uuid.uuid4().hex, @@ -364,7 +369,7 @@ def create_one_router(attrs={}): return router @staticmethod - def create_routers(attrs={}, count=2): + def create_routers(attrs=None, count=2): """Create multiple fake routers. :param Dictionary attrs: @@ -412,8 +417,7 @@ def create_one_security_group(attrs=None): :return: A FakeResource object, with id, name, etc. """ - if attrs is None: - attrs = {} + attrs = attrs or {} # Set default attributes. security_group_attrs = { @@ -467,8 +471,7 @@ def create_one_security_group_rule(attrs=None): :return: A FakeResource object, with id, etc. """ - if attrs is None: - attrs = {} + attrs = attrs or {} # Set default attributes. security_group_rule_attrs = { @@ -519,7 +522,7 @@ class FakeSubnet(object): """Fake one or more subnets.""" @staticmethod - def create_one_subnet(attrs={}): + def create_one_subnet(attrs=None): """Create a fake subnet. :param Dictionary attrs: @@ -527,6 +530,8 @@ def create_one_subnet(attrs={}): :return: A FakeResource object faking the subnet """ + attrs = attrs or {} + # Set default attributes. project_id = 'project-id-' + uuid.uuid4().hex subnet_attrs = { @@ -551,13 +556,14 @@ def create_one_subnet(attrs={}): subnet = fakes.FakeResource(info=copy.deepcopy(subnet_attrs), loaded=True) + # Set attributes with special mappings in OpenStack SDK. subnet.project_id = subnet_attrs['tenant_id'] return subnet @staticmethod - def create_subnets(attrs={}, count=2): + def create_subnets(attrs=None, count=2): """Create multiple fake subnets. :param Dictionary attrs: @@ -578,7 +584,7 @@ class FakeFloatingIP(object): """Fake one or more floating ip.""" @staticmethod - def create_one_floating_ip(attrs={}): + def create_one_floating_ip(attrs=None): """Create a fake floating ip. :param Dictionary attrs: @@ -586,6 +592,8 @@ def create_one_floating_ip(attrs={}): :return: A FakeResource object, with id, ip, and so on """ + attrs = attrs or {} + # Set default attributes. floating_ip_attrs = { 'id': 'floating-ip-id-' + uuid.uuid4().hex, @@ -614,7 +622,7 @@ def create_one_floating_ip(attrs={}): return floating_ip @staticmethod - def create_floating_ips(attrs={}, count=2): + def create_floating_ips(attrs=None, count=2): """Create multiple fake floating ips. :param Dictionary attrs: @@ -653,7 +661,7 @@ class FakeSubnetPool(object): """Fake one or more subnet pools.""" @staticmethod - def create_one_subnet_pool(attrs={}): + def create_one_subnet_pool(attrs=None): """Create a fake subnet pool. :param Dictionary attrs: @@ -661,6 +669,8 @@ def create_one_subnet_pool(attrs={}): :return: A FakeResource object faking the subnet pool """ + attrs = attrs or {} + # Set default attributes. subnet_pool_attrs = { 'id': 'subnet-pool-id-' + uuid.uuid4().hex, @@ -691,7 +701,7 @@ def create_one_subnet_pool(attrs={}): return subnet_pool @staticmethod - def create_subnet_pools(attrs={}, count=2): + def create_subnet_pools(attrs=None, count=2): """Create multiple fake subnet pools. :param Dictionary attrs: diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py index 6e631c41ea..b7994b5f85 100644 --- a/openstackclient/tests/volume/v2/fakes.py +++ b/openstackclient/tests/volume/v2/fakes.py @@ -281,7 +281,7 @@ class FakeVolume(object): """ @staticmethod - def create_one_volume(attrs={}): + def create_one_volume(attrs=None): """Create a fake volume. :param Dictionary attrs: @@ -289,6 +289,8 @@ def create_one_volume(attrs={}): :retrun: A FakeResource object with id, name, status, etc. """ + attrs = attrs or {} + # Set default attribute volume_info = { 'id': 'volume-id' + uuid.uuid4().hex, @@ -320,7 +322,7 @@ def create_one_volume(attrs={}): return volume @staticmethod - def create_volumes(attrs={}, count=2): + def create_volumes(attrs=None, count=2): """Create multiple fake volumes. :param Dictionary attrs: @@ -361,7 +363,7 @@ class FakeAvailabilityZone(object): """Fake one or more volume availability zones (AZs).""" @staticmethod - def create_one_availability_zone(attrs={}): + def create_one_availability_zone(attrs=None): """Create a fake AZ. :param Dictionary attrs: @@ -369,6 +371,8 @@ def create_one_availability_zone(attrs={}): :return: A FakeResource object with zoneName, zoneState, etc. """ + attrs = attrs or {} + # Set default attributes. availability_zone = { 'zoneName': uuid.uuid4().hex, @@ -384,7 +388,7 @@ def create_one_availability_zone(attrs={}): return availability_zone @staticmethod - def create_availability_zones(attrs={}, count=2): + def create_availability_zones(attrs=None, count=2): """Create multiple fake AZs. :param Dictionary attrs: From a5a343a5a86658246cc136a330eee951e32c7b56 Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Fri, 25 Mar 2016 14:51:06 +0800 Subject: [PATCH 0830/3095] Support X.latest format for OS_COMPUTE_API_VERSION OSC don't support to use "X.latest" format in order to talk with the latest nova microversion API, that is very helpful shortcut usage to use new nova side features, this patch implement it. Change-Id: I87918addff1f50fbc6eb72ca82b31813330753b5 Closes-Bug: #1561838 --- openstackclient/compute/client.py | 45 +++++++++++-------- .../notes/bug-1561838-3a006a8263d7536d.yaml | 6 +++ 2 files changed, 32 insertions(+), 19 deletions(-) create mode 100644 releasenotes/notes/bug-1561838-3a006a8263d7536d.yaml diff --git a/openstackclient/compute/client.py b/openstackclient/compute/client.py index 1481ed6535..82f09cec0f 100644 --- a/openstackclient/compute/client.py +++ b/openstackclient/compute/client.py @@ -41,8 +41,18 @@ def make_client(instance): version = _compute_api_version else: version = instance._api_version[API_NAME] + from novaclient import api_versions + # convert to APIVersion object + version = api_versions.get_api_version(version) + + if version.is_latest(): + import novaclient + # NOTE(RuiChen): executing version discovery make sense, but that need + # an initialized REST client, it's not available now, + # fallback to use the max version of novaclient side. + version = novaclient.API_MAX_VERSION - LOG.debug('Instantiating compute client for V%s', version) + LOG.debug('Instantiating compute client for %s', version) # Set client http_log_debug to True if verbosity level is high enough http_log_debug = utils.get_effective_log_level() <= logging.DEBUG @@ -91,30 +101,27 @@ def check_api_version(check_version): """ # Defer client imports until we actually need them - try: - from novaclient import api_versions - except ImportError: - # Retain previous behaviour - return False - import novaclient + from novaclient import api_versions global _compute_api_version - # Copy some logic from novaclient 2.27.0 for basic version detection + # Copy some logic from novaclient 3.3.0 for basic version detection # NOTE(dtroyer): This is only enough to resume operations using API # version 2.0 or any valid version supplied by the user. _compute_api_version = api_versions.get_api_version(check_version) - if _compute_api_version > api_versions.APIVersion("2.0"): - if not _compute_api_version.matches( - novaclient.API_MIN_VERSION, - novaclient.API_MAX_VERSION, - ): - raise exceptions.CommandError( - "versions supported by client: %s - %s" % ( - novaclient.API_MIN_VERSION.get_string(), - novaclient.API_MAX_VERSION.get_string(), - ), - ) + # Bypass X.latest format microversion + if not _compute_api_version.is_latest(): + if _compute_api_version > api_versions.APIVersion("2.0"): + if not _compute_api_version.matches( + novaclient.API_MIN_VERSION, + novaclient.API_MAX_VERSION, + ): + raise exceptions.CommandError( + "versions supported by client: %s - %s" % ( + novaclient.API_MIN_VERSION.get_string(), + novaclient.API_MAX_VERSION.get_string(), + ), + ) return True diff --git a/releasenotes/notes/bug-1561838-3a006a8263d7536d.yaml b/releasenotes/notes/bug-1561838-3a006a8263d7536d.yaml new file mode 100644 index 0000000000..71e5ba26c5 --- /dev/null +++ b/releasenotes/notes/bug-1561838-3a006a8263d7536d.yaml @@ -0,0 +1,6 @@ +--- +features: + - Support X.latest format for OS_COMPUTE_API_VERSION in order to talk with + the latest nova microversion API, that is very helpful shortcut usage to + use new nova side features. + [Bug `1561838 `_] From a06bb28bcc86ed6e99f78c2d1b2a90d4a93a77b2 Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Sat, 26 Mar 2016 18:15:31 +0800 Subject: [PATCH 0831/3095] Add "server group create" command Support compute v2 "server group create" command in OSC. Implements: blueprint nova-server-group-support Partial-Bug: #1542171 Change-Id: I96ffb07764d3adb715e048943cfee3b879c280f6 --- doc/source/command-objects/server-group.rst | 29 +++++ doc/source/commands.rst | 1 + openstackclient/compute/v2/server_group.py | 68 +++++++++++ openstackclient/tests/compute/v2/fakes.py | 34 ++++++ .../tests/compute/v2/test_server_group.py | 108 ++++++++++++++++++ setup.cfg | 2 + 6 files changed, 242 insertions(+) create mode 100644 doc/source/command-objects/server-group.rst create mode 100644 openstackclient/compute/v2/server_group.py create mode 100644 openstackclient/tests/compute/v2/test_server_group.py diff --git a/doc/source/command-objects/server-group.rst b/doc/source/command-objects/server-group.rst new file mode 100644 index 0000000000..01e9900ba1 --- /dev/null +++ b/doc/source/command-objects/server-group.rst @@ -0,0 +1,29 @@ +============ +server group +============ + +Server group provide a mechanism to group servers according to certain policy. + +Compute v2 + +server group create +------------------- + +Create a new server group + +.. program:: server group create +.. code-block:: bash + + os server group create + --policy [--policy ] ... + + +.. option:: --policy + + Add a policy to :ref:`\ ` + (repeat option to add multiple policies) + +.. _server_group_create-name: +.. describe:: + + New server group name diff --git a/doc/source/commands.rst b/doc/source/commands.rst index f71fbccb4e..c759e3047f 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -118,6 +118,7 @@ referring to both Compute and Volume quotas. * ``security group rule``: (**Compute**, **Network**) - the individual rules that define protocol/IP/port access * ``server``: (**Compute**) virtual machine instance * ``server dump``: (**Compute**) a dump file of a server created by features like kdump +* ``server group``: (**Compute**) a grouping of servers * ``server image``: (**Compute**) saved server disk image * ``service``: (**Identity**) a cloud service * ``service provider``: (**Identity**) a resource that consumes assertions from an ``identity provider`` diff --git a/openstackclient/compute/v2/server_group.py b/openstackclient/compute/v2/server_group.py new file mode 100644 index 0000000000..4d0baddccd --- /dev/null +++ b/openstackclient/compute/v2/server_group.py @@ -0,0 +1,68 @@ +# Copyright 2016 Huawei, Inc. 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. +# + +"""Compute v2 Server Group action implementations""" + +from openstackclient.common import command +from openstackclient.common import utils + + +_formatters = { + 'policies': utils.format_list, + 'members': utils.format_list, +} + + +def _get_columns(info): + columns = list(info.keys()) + if 'metadata' in columns: + # NOTE(RuiChen): The metadata of server group is always empty since API + # compatible, so hide it in order to avoid confusion. + columns.remove('metadata') + return tuple(sorted(columns)) + + +class CreateServerGroup(command.ShowOne): + """Create a new server group.""" + + def get_parser(self, prog_name): + parser = super(CreateServerGroup, self).get_parser(prog_name) + parser.add_argument( + 'name', + metavar='', + help='New server group name', + ) + parser.add_argument( + '--policy', + metavar='', + action='append', + required=True, + help='Add a policy to ' + '(repeat option to add multiple policies)', + ) + return parser + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + info = {} + server_group = compute_client.server_groups.create( + name=parsed_args.name, + policies=parsed_args.policy) + info.update(server_group._info) + + columns = _get_columns(info) + data = utils.get_dict_properties(info, columns, + formatters=_formatters) + return columns, data diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 7f39bad0be..7f38b32b9b 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -177,6 +177,9 @@ def __init__(self, **kwargs): self.hosts = mock.Mock() self.hosts.resource_class = fakes.FakeResource(None, {}) + self.server_groups = mock.Mock() + self.server_groups.resource_class = fakes.FakeResource(None, {}) + self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] @@ -899,3 +902,34 @@ def create_one_host(attrs=None): info=copy.deepcopy(host_info), loaded=True) return host + + +class FakeServerGroup(object): + """Fake one server group""" + + @staticmethod + def create_one_server_group(attrs=None): + """Create a fake server group + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object, with id and other attributes + """ + if attrs is None: + attrs = {} + + server_group_info = { + 'id': 'server-group-id-' + uuid.uuid4().hex, + 'members': [], + 'metadata': {}, + 'name': 'server-group-name-' + uuid.uuid4().hex, + 'policies': [], + 'project_id': 'server-group-project-id-' + uuid.uuid4().hex, + 'user_id': 'server-group-user-id-' + uuid.uuid4().hex, + } + server_group_info.update(attrs) + server_group = fakes.FakeResource( + info=copy.deepcopy(server_group_info), + loaded=True) + return server_group diff --git a/openstackclient/tests/compute/v2/test_server_group.py b/openstackclient/tests/compute/v2/test_server_group.py new file mode 100644 index 0000000000..da1927c79f --- /dev/null +++ b/openstackclient/tests/compute/v2/test_server_group.py @@ -0,0 +1,108 @@ +# Copyright 2016 Huawei, Inc. 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.common import utils +from openstackclient.compute.v2 import server_group +from openstackclient.tests.compute.v2 import fakes as compute_fakes +from openstackclient.tests import utils as tests_utils + + +class TestServerGroup(compute_fakes.TestComputev2): + + fake_server_group = compute_fakes.FakeServerGroup.create_one_server_group() + + columns = ( + 'id', + 'members', + 'name', + 'policies', + 'project_id', + 'user_id', + ) + + data = ( + fake_server_group.id, + utils.format_list(fake_server_group.members), + fake_server_group.name, + utils.format_list(fake_server_group.policies), + fake_server_group.project_id, + fake_server_group.user_id, + ) + + def setUp(self): + super(TestServerGroup, self).setUp() + + # Get a shortcut to the ServerGroupsManager Mock + self.server_groups_mock = self.app.client_manager.compute.server_groups + self.server_groups_mock.reset_mock() + + +class TestServerGroupCreate(TestServerGroup): + + def setUp(self): + super(TestServerGroupCreate, self).setUp() + + self.server_groups_mock.create.return_value = self.fake_server_group + self.cmd = server_group.CreateServerGroup(self.app, None) + + def test_server_group_create(self): + arglist = [ + '--policy', 'affinity', + 'affinity_group', + ] + verifylist = [ + ('policy', ['affinity']), + ('name', 'affinity_group'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.server_groups_mock.create.assert_called_once_with( + name=parsed_args.name, + policies=parsed_args.policy, + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_server_group_create_with_multiple_policies(self): + arglist = [ + '--policy', 'affinity', + '--policy', 'soft-affinity', + 'affinity_group', + ] + verifylist = [ + ('policy', ['affinity', 'soft-affinity']), + ('name', 'affinity_group'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.server_groups_mock.create.assert_called_once_with( + name=parsed_args.name, + policies=parsed_args.policy, + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_server_group_create_no_policy(self): + arglist = [ + 'affinity_group', + ] + verifylist = None + self.assertRaises(tests_utils.ParserException, + self.check_parser, + self.cmd, + arglist, + verifylist) diff --git a/setup.cfg b/setup.cfg index 684214127a..82b845adfb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -131,6 +131,8 @@ openstack.compute.v2 = server_unset = openstackclient.compute.v2.server:UnsetServer server_unshelve = openstackclient.compute.v2.server:UnshelveServer + server_group_create = openstackclient.compute.v2.server_group:CreateServerGroup + usage_list = openstackclient.compute.v2.usage:ListUsage usage_show = openstackclient.compute.v2.usage:ShowUsage From 7458c612eda86fe8a616d8daa74e428a7664bbf0 Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Tue, 29 Mar 2016 15:15:37 +0800 Subject: [PATCH 0832/3095] Add "server group delete" command Support compute v2 "server group delete" command in OSC. Implements: blueprint nova-server-group-support Partial-Bug: #1542171 Related-Bug: #1563301 Change-Id: I7d792d669b147b24dc774844cfc9dbacd60d017b --- doc/source/command-objects/server-group.rst | 16 ++++ openstackclient/compute/v2/server_group.py | 33 ++++++++ .../tests/compute/v2/test_server_group.py | 84 +++++++++++++++++++ setup.cfg | 1 + 4 files changed, 134 insertions(+) diff --git a/doc/source/command-objects/server-group.rst b/doc/source/command-objects/server-group.rst index 01e9900ba1..af0090c7f9 100644 --- a/doc/source/command-objects/server-group.rst +++ b/doc/source/command-objects/server-group.rst @@ -27,3 +27,19 @@ Create a new server group .. describe:: New server group name + +server group delete +------------------- + +Delete an existing server group + +.. program:: server group delete +.. code-block:: bash + + os server group delete + [ ...] + +.. describe:: + + Server group(s) to delete (name or ID) + (repeat to delete multiple server groups) diff --git a/openstackclient/compute/v2/server_group.py b/openstackclient/compute/v2/server_group.py index 4d0baddccd..02de011efe 100644 --- a/openstackclient/compute/v2/server_group.py +++ b/openstackclient/compute/v2/server_group.py @@ -16,6 +16,7 @@ """Compute v2 Server Group action implementations""" from openstackclient.common import command +from openstackclient.common import exceptions from openstackclient.common import utils @@ -66,3 +67,35 @@ def take_action(self, parsed_args): data = utils.get_dict_properties(info, columns, formatters=_formatters) return columns, data + + +class DeleteServerGroup(command.Command): + """Delete an existing server group.""" + + def get_parser(self, prog_name): + parser = super(DeleteServerGroup, self).get_parser(prog_name) + parser.add_argument( + 'server_group', + metavar='', + nargs='+', + help='server group(s) to delete (name or ID)', + ) + return parser + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + result = 0 + for group in parsed_args.server_group: + try: + group_obj = utils.find_resource(compute_client.server_groups, + group) + compute_client.server_groups.delete(group_obj.id) + # Catch all exceptions in order to avoid to block the next deleting + except Exception as e: + result += 1 + self.app.log.error(e) + + if result > 0: + total = len(parsed_args.server_group) + msg = "%s of %s server groups failed to delete." % (result, total) + raise exceptions.CommandError(msg) diff --git a/openstackclient/tests/compute/v2/test_server_group.py b/openstackclient/tests/compute/v2/test_server_group.py index da1927c79f..3bd1177b89 100644 --- a/openstackclient/tests/compute/v2/test_server_group.py +++ b/openstackclient/tests/compute/v2/test_server_group.py @@ -13,6 +13,9 @@ # under the License. # +import mock + +from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.compute.v2 import server_group from openstackclient.tests.compute.v2 import fakes as compute_fakes @@ -106,3 +109,84 @@ def test_server_group_create_no_policy(self): self.cmd, arglist, verifylist) + + +class TestServerGroupDelete(TestServerGroup): + + def setUp(self): + super(TestServerGroupDelete, self).setUp() + + self.server_groups_mock.get.return_value = self.fake_server_group + self.cmd = server_group.DeleteServerGroup(self.app, None) + + def test_server_group_delete(self): + arglist = [ + 'affinity_group', + ] + verifylist = [ + ('server_group', ['affinity_group']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.server_groups_mock.get.assert_called_once_with('affinity_group') + self.server_groups_mock.delete.assert_called_once_with( + self.fake_server_group.id + ) + self.assertIsNone(result) + + def test_server_group_multiple_delete(self): + arglist = [ + 'affinity_group', + 'anti_affinity_group' + ] + verifylist = [ + ('server_group', ['affinity_group', 'anti_affinity_group']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.server_groups_mock.get.assert_any_call('affinity_group') + self.server_groups_mock.get.assert_any_call('anti_affinity_group') + self.server_groups_mock.delete.assert_called_with( + self.fake_server_group.id + ) + self.assertEqual(2, self.server_groups_mock.get.call_count) + self.assertEqual(2, self.server_groups_mock.delete.call_count) + self.assertIsNone(result) + + def test_server_group_delete_no_input(self): + arglist = [] + verifylist = None + self.assertRaises(tests_utils.ParserException, + self.check_parser, + self.cmd, + arglist, + verifylist) + + def test_server_group_multiple_delete_with_exception(self): + arglist = [ + 'affinity_group', + 'anti_affinity_group' + ] + verifylist = [ + ('server_group', ['affinity_group', 'anti_affinity_group']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + find_mock_result = [self.fake_server_group, exceptions.CommandError] + with mock.patch.object(utils, 'find_resource', + side_effect=find_mock_result) as find_mock: + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 server groups failed to delete.', + str(e)) + + find_mock.assert_any_call(self.server_groups_mock, + 'affinity_group') + find_mock.assert_any_call(self.server_groups_mock, + 'anti_affinity_group') + + self.assertEqual(2, find_mock.call_count) + self.server_groups_mock.delete.assert_called_once_with( + self.fake_server_group.id + ) diff --git a/setup.cfg b/setup.cfg index 82b845adfb..dedd607ea8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -132,6 +132,7 @@ openstack.compute.v2 = server_unshelve = openstackclient.compute.v2.server:UnshelveServer server_group_create = openstackclient.compute.v2.server_group:CreateServerGroup + server_group_delete = openstackclient.compute.v2.server_group:DeleteServerGroup usage_list = openstackclient.compute.v2.usage:ListUsage usage_show = openstackclient.compute.v2.usage:ShowUsage From 4e2272801377e50be25523d5a487d02da0614220 Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Wed, 30 Mar 2016 11:48:39 +0800 Subject: [PATCH 0833/3095] Add "server group list" command Support compute v2 "server group list" command in OSC. Implements: blueprint nova-server-group-support Partial-Bug: #1542171 Change-Id: I68b80e3c4458692472af671028cd1f939736bcb8 --- doc/source/command-objects/server-group.rst | 20 ++++++ openstackclient/compute/v2/server_group.py | 57 ++++++++++++++++ .../tests/compute/v2/test_server_group.py | 68 +++++++++++++++++++ setup.cfg | 1 + 4 files changed, 146 insertions(+) diff --git a/doc/source/command-objects/server-group.rst b/doc/source/command-objects/server-group.rst index af0090c7f9..acf9be345a 100644 --- a/doc/source/command-objects/server-group.rst +++ b/doc/source/command-objects/server-group.rst @@ -43,3 +43,23 @@ Delete an existing server group Server group(s) to delete (name or ID) (repeat to delete multiple server groups) + +server group list +----------------- + +List all server groups + +.. program:: server group list +.. code-block:: bash + + os server group list + [--all-projects] + [--long] + +.. option:: --all-projects + + Display information from all projects (admin only) + +.. option:: --long + + List additional fields in output diff --git a/openstackclient/compute/v2/server_group.py b/openstackclient/compute/v2/server_group.py index 02de011efe..56d7e27a80 100644 --- a/openstackclient/compute/v2/server_group.py +++ b/openstackclient/compute/v2/server_group.py @@ -99,3 +99,60 @@ def take_action(self, parsed_args): total = len(parsed_args.server_group) msg = "%s of %s server groups failed to delete." % (result, total) raise exceptions.CommandError(msg) + + +class ListServerGroup(command.Lister): + """List all server groups.""" + + def get_parser(self, prog_name): + parser = super(ListServerGroup, self).get_parser(prog_name) + parser.add_argument( + '--all-projects', + action='store_true', + default=False, + help='Display information from all projects (admin only)', + ) + parser.add_argument( + '--long', + action='store_true', + default=False, + help='List additional fields in output', + ) + return parser + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + data = compute_client.server_groups.list(parsed_args.all_projects) + + if parsed_args.long: + column_headers = ( + 'ID', + 'Name', + 'Policies', + 'Members', + 'Project Id', + 'User Id', + ) + columns = ( + 'ID', + 'Name', + 'Policies', + 'Members', + 'Project Id', + 'User Id', + ) + else: + column_headers = columns = ( + 'ID', + 'Name', + 'Policies', + ) + + return (column_headers, + (utils.get_item_properties( + s, columns, + formatters={ + 'Policies': utils.format_list, + 'Members': utils.format_list, + } + ) for s in data)) diff --git a/openstackclient/tests/compute/v2/test_server_group.py b/openstackclient/tests/compute/v2/test_server_group.py index 3bd1177b89..a18c60148b 100644 --- a/openstackclient/tests/compute/v2/test_server_group.py +++ b/openstackclient/tests/compute/v2/test_server_group.py @@ -190,3 +190,71 @@ def test_server_group_multiple_delete_with_exception(self): self.server_groups_mock.delete.assert_called_once_with( self.fake_server_group.id ) + + +class TestServerGroupList(TestServerGroup): + + list_columns = ( + 'ID', + 'Name', + 'Policies', + ) + + list_columns_long = ( + 'ID', + 'Name', + 'Policies', + 'Members', + 'Project Id', + 'User Id', + ) + + list_data = (( + TestServerGroup.fake_server_group.id, + TestServerGroup.fake_server_group.name, + utils.format_list(TestServerGroup.fake_server_group.policies), + ),) + + list_data_long = (( + TestServerGroup.fake_server_group.id, + TestServerGroup.fake_server_group.name, + utils.format_list(TestServerGroup.fake_server_group.policies), + utils.format_list(TestServerGroup.fake_server_group.members), + TestServerGroup.fake_server_group.project_id, + TestServerGroup.fake_server_group.user_id, + ),) + + def setUp(self): + super(TestServerGroupList, self).setUp() + + self.server_groups_mock.list.return_value = [self.fake_server_group] + self.cmd = server_group.ListServerGroup(self.app, None) + + def test_server_group_list(self): + arglist = [] + verifylist = [ + ('all_projects', False), + ('long', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.server_groups_mock.list.assert_called_once_with(False) + + self.assertEqual(self.list_columns, columns) + self.assertEqual(self.list_data, tuple(data)) + + def test_server_group_list_with_all_projects_and_long(self): + arglist = [ + '--all-projects', + '--long', + ] + verifylist = [ + ('all_projects', True), + ('long', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.server_groups_mock.list.assert_called_once_with(True) + + self.assertEqual(self.list_columns_long, columns) + self.assertEqual(self.list_data_long, tuple(data)) diff --git a/setup.cfg b/setup.cfg index dedd607ea8..c6e7619c2d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -133,6 +133,7 @@ openstack.compute.v2 = server_group_create = openstackclient.compute.v2.server_group:CreateServerGroup server_group_delete = openstackclient.compute.v2.server_group:DeleteServerGroup + server_group_list = openstackclient.compute.v2.server_group:ListServerGroup usage_list = openstackclient.compute.v2.usage:ListUsage usage_show = openstackclient.compute.v2.usage:ShowUsage From a0d79968ca14d7856e1813151f474bfef968c1e8 Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Thu, 31 Mar 2016 15:52:08 +0800 Subject: [PATCH 0834/3095] Add "server group show" command Support compute v2 "server group show" command in OSC. Implements: blueprint nova-server-group-support Closes-Bug: #1542171 Change-Id: I1a6d103151c704bda5b67cb9a49cc43c7d9f1d6a --- doc/source/command-objects/server-group.rst | 15 ++++++++++++ openstackclient/compute/v2/server_group.py | 24 +++++++++++++++++++ .../tests/compute/v2/test_server_group.py | 23 ++++++++++++++++++ ...server-group-command-fde165df53216726.yaml | 7 ++++++ setup.cfg | 1 + 5 files changed, 70 insertions(+) create mode 100644 releasenotes/notes/add-server-group-command-fde165df53216726.yaml diff --git a/doc/source/command-objects/server-group.rst b/doc/source/command-objects/server-group.rst index acf9be345a..35d903dcca 100644 --- a/doc/source/command-objects/server-group.rst +++ b/doc/source/command-objects/server-group.rst @@ -63,3 +63,18 @@ List all server groups .. option:: --long List additional fields in output + +server group show +----------------- + +Display server group details + +.. program:: server group show +.. code-block:: bash + + os server group show + + +.. describe:: + + Server group to display (name or ID) diff --git a/openstackclient/compute/v2/server_group.py b/openstackclient/compute/v2/server_group.py index 56d7e27a80..eb5745f5c8 100644 --- a/openstackclient/compute/v2/server_group.py +++ b/openstackclient/compute/v2/server_group.py @@ -156,3 +156,27 @@ def take_action(self, parsed_args): 'Members': utils.format_list, } ) for s in data)) + + +class ShowServerGroup(command.ShowOne): + """Display server group details.""" + + def get_parser(self, prog_name): + parser = super(ShowServerGroup, self).get_parser(prog_name) + parser.add_argument( + 'server_group', + metavar='', + help='server group to display (name or ID)', + ) + return parser + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + group = utils.find_resource(compute_client.server_groups, + parsed_args.server_group) + info = {} + info.update(group._info) + columns = _get_columns(info) + data = utils.get_dict_properties(info, columns, + formatters=_formatters) + return columns, data diff --git a/openstackclient/tests/compute/v2/test_server_group.py b/openstackclient/tests/compute/v2/test_server_group.py index a18c60148b..70ff23f9dc 100644 --- a/openstackclient/tests/compute/v2/test_server_group.py +++ b/openstackclient/tests/compute/v2/test_server_group.py @@ -258,3 +258,26 @@ def test_server_group_list_with_all_projects_and_long(self): self.assertEqual(self.list_columns_long, columns) self.assertEqual(self.list_data_long, tuple(data)) + + +class TestServerGroupShow(TestServerGroup): + + def setUp(self): + super(TestServerGroupShow, self).setUp() + + self.server_groups_mock.get.return_value = self.fake_server_group + self.cmd = server_group.ShowServerGroup(self.app, None) + + def test_server_group_show(self): + arglist = [ + 'affinity_group', + ] + verifylist = [ + ('server_group', 'affinity_group'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) diff --git a/releasenotes/notes/add-server-group-command-fde165df53216726.yaml b/releasenotes/notes/add-server-group-command-fde165df53216726.yaml new file mode 100644 index 0000000000..63b7b7a4d7 --- /dev/null +++ b/releasenotes/notes/add-server-group-command-fde165df53216726.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Add support for compute v2 ``server group`` related commands, include: + create, delete, list and show. + [Bug `1542171 `_] + [Blueprint `nova-server-group-support `_] diff --git a/setup.cfg b/setup.cfg index c6e7619c2d..fb60bab510 100644 --- a/setup.cfg +++ b/setup.cfg @@ -134,6 +134,7 @@ openstack.compute.v2 = server_group_create = openstackclient.compute.v2.server_group:CreateServerGroup server_group_delete = openstackclient.compute.v2.server_group:DeleteServerGroup server_group_list = openstackclient.compute.v2.server_group:ListServerGroup + server_group_show = openstackclient.compute.v2.server_group:ShowServerGroup usage_list = openstackclient.compute.v2.usage:ListUsage usage_show = openstackclient.compute.v2.usage:ShowUsage From 0e8c78c91a31a8ba2664b84d812f967bc45f0865 Mon Sep 17 00:00:00 2001 From: Kyrylo Romanenko Date: Wed, 16 Mar 2016 20:17:55 +0200 Subject: [PATCH 0835/3095] Update tests for server Make testcases separate instances of test class. Use setUp with addCleanup methods for every test case. Add more descriptive docstrings for tests. Closes-Bug: #1483422 Partial-Bug: #1566962 Change-Id: I1a16b5bad1dafd19fd7dc94794c0bd4587b7f516 --- functional/tests/compute/v2/test_server.py | 238 ++++++++++++++++----- 1 file changed, 181 insertions(+), 57 deletions(-) diff --git a/functional/tests/compute/v2/test_server.py b/functional/tests/compute/v2/test_server.py index 1d10bbdb2d..f52cbb0eb3 100644 --- a/functional/tests/compute/v2/test_server.py +++ b/functional/tests/compute/v2/test_server.py @@ -11,22 +11,15 @@ # under the License. import time -import uuid +from tempest_lib.common.utils import data_utils import testtools -from functional.common import exceptions from functional.common import test class ServerTests(test.TestCase): - """Functional tests for server.""" - - NAME = uuid.uuid4().hex - OTHER_NAME = uuid.uuid4().hex - HEADERS = ['"Name"'] - FIELDS = ['name'] - IP_POOL = 'public' + """Functional tests for openstack server commands.""" @classmethod def get_flavor(cls): @@ -52,99 +45,196 @@ def get_network(cls): idx = int(len(ray) / 2) return ' --nic net-id=' + ray[idx] - @classmethod - def setUpClass(cls): - opts = cls.get_show_opts(cls.FIELDS) - flavor = cls.get_flavor() - image = cls.get_image() - network = cls.get_network() - raw_output = cls.openstack('server create --flavor ' + flavor + - ' --image ' + image + network + ' ' + - cls.NAME + opts) - expected = cls.NAME + '\n' - cls.assertOutput(expected, raw_output) + def server_create(self, name=None): + """Create server. Add cleanup.""" + name = name or data_utils.rand_uuid() + opts = self.get_show_opts(self.FIELDS) + flavor = self.get_flavor() + image = self.get_image() + network = self.get_network() + raw_output = self.openstack('--debug server create --flavor ' + + flavor + + ' --image ' + image + network + ' ' + + name + opts) + if not raw_output: + self.fail('Server has not been created!') + self.addCleanup(self.server_delete, name) - @classmethod - def tearDownClass(cls): - # Rename test - raw_output = cls.openstack('server set --name ' + cls.OTHER_NAME + - ' ' + cls.NAME) - cls.assertOutput("", raw_output) - # Delete test - raw_output = cls.openstack('server delete ' + cls.OTHER_NAME) - cls.assertOutput('', raw_output) + def server_list(self, params=[]): + """List servers.""" + opts = self.get_list_opts(params) + return self.openstack('server list' + opts) + + def server_delete(self, name): + """Delete server by name.""" + self.openstack('server delete ' + name) + + def setUp(self): + """Set necessary variables and create server.""" + super(ServerTests, self).setUp() + self.NAME = data_utils.rand_name('TestServer') + self.OTHER_NAME = data_utils.rand_name('TestServer') + self.HEADERS = ['"Name"'] + self.FIELDS = ['name'] + self.IP_POOL = 'public' + self.server_create(self.NAME) + + def test_server_rename(self): + """Test server rename command. + + Test steps: + 1) Boot server in setUp + 2) Rename server + 3) Check output + 4) Rename server back to original name + """ + raw_output = self.openstack('server set --name ' + self.OTHER_NAME + + ' ' + self.NAME) + self.assertOutput("", raw_output) + self.assertNotIn(self.NAME, self.server_list(['Name'])) + self.assertIn(self.OTHER_NAME, self.server_list(['Name'])) + self.openstack('server set --name ' + self.NAME + ' ' + + self.OTHER_NAME) def test_server_list(self): + """Test server list command. + + Test steps: + 1) Boot server in setUp + 2) List servers + 3) Check output + """ opts = self.get_list_opts(self.HEADERS) raw_output = self.openstack('server list' + opts) self.assertIn(self.NAME, raw_output) def test_server_show(self): + """Test server show command. + + Test steps: + 1) Boot server in setUp + 2) Show server + 3) Check output + """ opts = self.get_show_opts(self.FIELDS) raw_output = self.openstack('server show ' + self.NAME + opts) self.assertEqual(self.NAME + "\n", raw_output) - def wait_for(self, desired, wait=120, interval=5, failures=['ERROR']): - # TODO(thowe): Add a server wait command to osc - status = "notset" - total_sleep = 0 - opts = self.get_show_opts(['status']) - while total_sleep < wait: - status = self.openstack('server show ' + self.NAME + opts) - status = status.rstrip() - print('Waiting for {} current status: {}'.format(desired, status)) - if status == desired: - break - self.assertNotIn(status, failures) - time.sleep(interval) - total_sleep += interval - self.assertEqual(desired, status) + def test_server_metadata(self): + """Test command to set server metadata. - @testtools.skip('skipping due to bug 1483422') - def test_server_up_test(self): - self.wait_for("ACTIVE") - # give it a little bit more time - time.sleep(5) + Test steps: + 1) Boot server in setUp + 2) Set properties for server + 3) Check server properties in server show output + """ + self.wait_for_status("ACTIVE") # metadata raw_output = self.openstack( 'server set --property a=b --property c=d ' + self.NAME) opts = self.get_show_opts(["name", "properties"]) raw_output = self.openstack('server show ' + self.NAME + opts) self.assertEqual(self.NAME + "\na='b', c='d'\n", raw_output) + + def test_server_suspend_resume(self): + """Test server suspend and resume commands. + + Test steps: + 1) Boot server in setUp + 2) Suspend server + 3) Check for SUSPENDED server status + 4) Resume server + 5) Check for ACTIVE server status + """ + self.wait_for_status("ACTIVE") # suspend raw_output = self.openstack('server suspend ' + self.NAME) self.assertEqual("", raw_output) - self.wait_for("SUSPENDED") + self.wait_for_status("SUSPENDED") # resume raw_output = self.openstack('server resume ' + self.NAME) self.assertEqual("", raw_output) - self.wait_for("ACTIVE") + self.wait_for_status("ACTIVE") + + def test_server_lock_unlock(self): + """Test server lock and unlock commands. + + Test steps: + 1) Boot server in setUp + 2) Lock server + 3) Check output + 4) Unlock server + 5) Check output + """ + self.wait_for_status("ACTIVE") # lock raw_output = self.openstack('server lock ' + self.NAME) self.assertEqual("", raw_output) # unlock raw_output = self.openstack('server unlock ' + self.NAME) self.assertEqual("", raw_output) + + def test_server_pause_unpause(self): + """Test server pause and unpause commands. + + Test steps: + 1) Boot server in setUp + 2) Pause server + 3) Check for PAUSED server status + 4) Unpause server + 5) Check for ACTIVE server status + """ + self.wait_for_status("ACTIVE") # pause raw_output = self.openstack('server pause ' + self.NAME) self.assertEqual("", raw_output) - self.wait_for("PAUSED") + self.wait_for_status("PAUSED") # unpause raw_output = self.openstack('server unpause ' + self.NAME) self.assertEqual("", raw_output) - self.wait_for("ACTIVE") + self.wait_for_status("ACTIVE") + + def test_server_rescue_unrescue(self): + """Test server rescue and unrescue commands. + + Test steps: + 1) Boot server in setUp + 2) Rescue server + 3) Check for RESCUE server status + 4) Unrescue server + 5) Check for ACTIVE server status + """ + self.wait_for_status("ACTIVE") # rescue opts = self.get_show_opts(["adminPass"]) raw_output = self.openstack('server rescue ' + self.NAME + opts) self.assertNotEqual("", raw_output) - self.wait_for("RESCUE") + self.wait_for_status("RESCUE") # unrescue raw_output = self.openstack('server unrescue ' + self.NAME) self.assertEqual("", raw_output) - self.wait_for("ACTIVE") + self.wait_for_status("ACTIVE") + + @testtools.skip('this test needs to be re-worked completely') + def test_server_attach_detach_floating_ip(self): + """Test commands to attach and detach floating IP for server. + + Test steps: + 1) Boot server in setUp + 2) Create floating IP + 3) Add floating IP to server + 4) Check for floating IP in server show output + 5) Remove floating IP from server + 6) Check that floating IP is not in server show output + 7) Delete floating IP + 8) Check output + """ + self.wait_for_status("ACTIVE") # attach ip opts = self.get_show_opts(["id", "ip"]) - raw_output = self.openstack('ip floating create ' + self.IP_POOL + + raw_output = self.openstack('ip floating create ' + '--debug ' + + self.IP_POOL + opts) ipid, ip, rol = tuple(raw_output.split('\n')) self.assertNotEqual("", ipid) @@ -153,6 +243,7 @@ def test_server_up_test(self): self.assertEqual("", raw_output) raw_output = self.openstack('server show ' + self.NAME) self.assertIn(ip, raw_output) + # detach ip raw_output = self.openstack('ip floating remove ' + ip + ' ' + self.NAME) @@ -161,7 +252,40 @@ def test_server_up_test(self): self.assertNotIn(ip, raw_output) raw_output = self.openstack('ip floating delete ' + ipid) self.assertEqual("", raw_output) + + def test_server_reboot(self): + """Test server reboot command. + + Test steps: + 1) Boot server in setUp + 2) Reboot server + 3) Check for ACTIVE server status + """ + self.wait_for_status("ACTIVE") # reboot raw_output = self.openstack('server reboot ' + self.NAME) self.assertEqual("", raw_output) - self.wait_for("ACTIVE") + self.wait_for_status("ACTIVE") + + def wait_for_status(self, expected_status='ACTIVE', wait=600, interval=30): + """Wait until server reaches expected status.""" + # TODO(thowe): Add a server wait command to osc + failures = ['ERROR'] + total_sleep = 0 + opts = self.get_show_opts(['status']) + while total_sleep < wait: + status = self.openstack('server show ' + self.NAME + opts) + status = status.rstrip() + print('Waiting for {} current status: {}'.format(expected_status, + status)) + if status == expected_status: + break + self.assertNotIn(status, failures) + time.sleep(interval) + total_sleep += interval + + status = self.openstack('server show ' + self.NAME + opts) + status = status.rstrip() + self.assertEqual(status, expected_status) + # give it a little bit more time + time.sleep(5) From f995185c104f55bfefa78dd6e13ab5880036227b Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Wed, 20 Apr 2016 15:21:29 +0800 Subject: [PATCH 0836/3095] Trivial: Fix an omited i18n issue Change-Id: I064ae79d31f96021dfb5d7cd16a3ace9725803f6 --- openstackclient/network/v2/port.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index fbfce4d38f..9b6161fdd9 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -249,7 +249,8 @@ def get_parser(self, prog_name): parser.add_argument( 'name', metavar='', - help='Name of this port') + help=_("Name of this port") + ) # TODO(singhj): Add support for extended options: # qos,security groups,dhcp, address pairs return parser From f753bad742476cc9a0fc1e5fef8e6c8253eff7a7 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Tue, 19 Apr 2016 16:08:12 -0500 Subject: [PATCH 0837/3095] Fixed subnet command host route output Fixed the "os subnet create", "os subnet list" and "os subnet show" command output for host routes to improve readability and to align with the "--host-route" option on the "os subnet create" and "os subnet set" commands. Change-Id: Ida69ae1a0bdb2e1648f8b5c978fc80cf1bbe752f Closes-Bug: #1572309 --- openstackclient/network/v2/subnet.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 715e662026..fb441cbf46 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -14,8 +14,6 @@ """Subnet action implementations""" import copy -from json.encoder import JSONEncoder - from openstackclient.common import command from openstackclient.common import exceptions from openstackclient.common import parseractions @@ -31,10 +29,8 @@ def _format_allocation_pools(data): def _format_host_routes(data): - try: - return '\n'.join([JSONEncoder().encode(route) for route in data]) - except (TypeError, KeyError): - return '' + # Map the host route keys to match --host-route option. + return utils.format_list_of_dicts(convert_entries_to_gateway(data)) _formatters = { @@ -89,8 +85,9 @@ def convert_entries_to_nexthop(entries): # Change 'gateway' entry to 'nexthop' changed_entries = copy.deepcopy(entries) for entry in changed_entries: - entry['nexthop'] = entry['gateway'] - del entry['gateway'] + if 'gateway' in entry: + entry['nexthop'] = entry['gateway'] + del entry['gateway'] return changed_entries @@ -99,8 +96,9 @@ def convert_entries_to_gateway(entries): # Change 'nexthop' entry to 'gateway' changed_entries = copy.deepcopy(entries) for entry in changed_entries: - entry['gateway'] = entry['nexthop'] - del entry['nexthop'] + if 'nexthop' in entry: + entry['gateway'] = entry['nexthop'] + del entry['nexthop'] return changed_entries From 745dc7633e20432ef9b8aa0a6617a2a0fa7fd73c Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 20 Apr 2016 14:41:58 +0000 Subject: [PATCH 0838/3095] Updated from global requirements Change-Id: I8e2060cd483262c88c76b49a060aa7288fa2bbb6 --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 99fcbe8d7a..5694550a8a 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -16,7 +16,7 @@ os-testr>=0.4.1 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD testtools>=1.4.0 # MIT tempest-lib>=0.14.0 # Apache-2.0 -osprofiler>=1.1.0 # Apache-2.0 +osprofiler>=1.3.0 # Apache-2.0 # Install these to generate sphinx autodocs python-barbicanclient>=4.0.0 # Apache-2.0 From 48ebc49f201baea443146a8d1c3b6cbde3b2f297 Mon Sep 17 00:00:00 2001 From: Brad Behle Date: Mon, 18 Apr 2016 21:22:24 -0500 Subject: [PATCH 0839/3095] Add new share and default parms to subnet pool cmds Add the "share" and "default" parms to subnet pool create command. Add the "default" and "no-default" parms to subnet pool set command. Note that "share" can not be modified once subnet pool has been created, so do not add this to the set command. Change-Id: I1eecad69527a1cde7fb234669f4aff2be2db491e Partial-Bug: #1544591 Partial-Bug: #1544586 --- doc/source/command-objects/subnet-pool.rst | 27 ++++++ openstackclient/network/v2/subnet_pool.py | 42 +++++++++ .../tests/network/v2/test_subnet_pool.py | 86 +++++++++++++++++++ .../notes/bug-1544586-0fe19a567d3e31fc.yaml | 6 ++ 4 files changed, 161 insertions(+) create mode 100644 releasenotes/notes/bug-1544586-0fe19a567d3e31fc.yaml diff --git a/doc/source/command-objects/subnet-pool.rst b/doc/source/command-objects/subnet-pool.rst index 3b7d0e91ed..867153efa4 100644 --- a/doc/source/command-objects/subnet-pool.rst +++ b/doc/source/command-objects/subnet-pool.rst @@ -19,6 +19,8 @@ Create subnet pool [--max-prefix-length ] [--project [--project-domain ]] [--address-scope ] + [--default | --no-default] + [--share | --no-share] .. option:: --pool-prefix @@ -52,6 +54,22 @@ Create subnet pool Set address scope associated with the subnet pool (name or ID), prefixes must be unique across address scopes +.. option:: --default + + Set this as a default subnet pool + +.. option:: --no-default + + Set this as a non-default subnet pool + +.. option:: --share + + Set this subnet pool as shared + +.. option:: --no-share + + Set this subnet pool as not shared + .. _subnet_pool_create-name: .. describe:: @@ -103,6 +121,7 @@ Set subnet pool properties [--min-prefix-length ] [--max-prefix-length ] [--address-scope | --no-address-scope] + [--default | --no-default] .. option:: --name @@ -135,6 +154,14 @@ Set subnet pool properties Remove address scope associated with the subnet pool +.. option:: --default + + Set this as a default subnet pool + +.. option:: --no-default + + Set this as a non-default subnet pool + .. _subnet_pool_set-subnet-pool: .. describe:: diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index 482b5ecf51..688dd2ca7c 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -55,6 +55,16 @@ def _get_attrs(client_manager, parsed_args): if 'no_address_scope' in parsed_args and parsed_args.no_address_scope: attrs['address_scope_id'] = None + if parsed_args.default: + attrs['is_default'] = True + if parsed_args.no_default: + attrs['is_default'] = False + + if 'share' in parsed_args and parsed_args.share: + attrs['shared'] = True + if 'no_share' in parsed_args and parsed_args.no_share: + attrs['shared'] = False + # "subnet pool set" command doesn't support setting project. if 'project' in parsed_args and parsed_args.project is not None: identity_client = client_manager.identity @@ -97,6 +107,20 @@ def _add_prefix_options(parser): ) +def _add_default_options(parser): + default_group = parser.add_mutually_exclusive_group() + default_group.add_argument( + '--default', + action='store_true', + help=_("Set this as a default subnet pool"), + ) + default_group.add_argument( + '--no-default', + action='store_true', + help=_("Set this as a non-default subnet pool"), + ) + + class CreateSubnetPool(command.ShowOne): """Create subnet pool""" @@ -121,6 +145,18 @@ def get_parser(self, prog_name): "(name or ID), prefixes must be unique across address " "scopes") ) + _add_default_options(parser) + shared_group = parser.add_mutually_exclusive_group() + shared_group.add_argument( + '--share', + action='store_true', + help=_("Set this subnet pool as shared"), + ) + shared_group.add_argument( + '--no-share', + action='store_true', + help=_("Set this subnet pool as not shared"), + ) return parser def take_action(self, parsed_args): @@ -176,6 +212,8 @@ def take_action(self, parsed_args): 'Prefixes', 'Default Prefix Length', 'Address Scope', + 'Default Subnet Pool', + 'Shared', ) columns = ( 'id', @@ -183,6 +221,8 @@ def take_action(self, parsed_args): 'prefixes', 'default_prefixlen', 'address_scope_id', + 'is_default', + 'shared', ) else: headers = ( @@ -232,6 +272,8 @@ def get_parser(self, prog_name): action='store_true', help=_("Remove address scope associated with the subnet pool") ) + _add_default_options(parser) + return parser def take_action(self, parsed_args): diff --git a/openstackclient/tests/network/v2/test_subnet_pool.py b/openstackclient/tests/network/v2/test_subnet_pool.py index cbb32fc331..369a8b1125 100644 --- a/openstackclient/tests/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/network/v2/test_subnet_pool.py @@ -221,6 +221,32 @@ def test_create_address_scope_option(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_create_default_and_shared_options(self): + arglist = [ + '--pool-prefix', '10.0.10.0/24', + '--default', + '--share', + self._subnet_pool.name, + ] + verifylist = [ + ('prefixes', ['10.0.10.0/24']), + ('default', True), + ('share', True), + ('name', self._subnet_pool.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_subnet_pool.assert_called_once_with(**{ + 'is_default': True, + 'name': self._subnet_pool.name, + 'prefixes': ['10.0.10.0/24'], + 'shared': True, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + class TestDeleteSubnetPool(TestSubnetPool): @@ -267,6 +293,8 @@ class TestListSubnetPool(TestSubnetPool): columns_long = columns + ( 'Default Prefix Length', 'Address Scope', + 'Default Subnet Pool', + 'Shared', ) data = [] @@ -285,6 +313,8 @@ class TestListSubnetPool(TestSubnetPool): utils.format_list(pool.prefixes), pool.default_prefixlen, pool.address_scope_id, + pool.is_default, + pool.shared, )) def setUp(self): @@ -474,6 +504,62 @@ def test_set_no_address_scope_conflict(self): self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) + def test_set_default(self): + arglist = [ + '--default', + self._subnet_pool.name, + ] + verifylist = [ + ('default', True), + ('subnet_pool', self._subnet_pool.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + attrs = { + 'is_default': True + } + self.network.update_subnet_pool.assert_called_once_with( + self._subnet_pool, **attrs) + self.assertIsNone(result) + + def test_set_no_default(self): + arglist = [ + '--no-default', + self._subnet_pool.name, + ] + verifylist = [ + ('no_default', True), + ('subnet_pool', self._subnet_pool.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + attrs = { + 'is_default': False, + } + self.network.update_subnet_pool.assert_called_once_with( + self._subnet_pool, **attrs) + self.assertIsNone(result) + + def test_set_no_default_conflict(self): + arglist = [ + '--default', + '--no-default', + self._subnet_pool.name, + ] + verifylist = [ + ('default', True), + ('no_default', True), + ('subnet_pool', self._subnet_pool.name), + ] + + # Exclusive arguments will conflict here. + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + class TestShowSubnetPool(TestSubnetPool): diff --git a/releasenotes/notes/bug-1544586-0fe19a567d3e31fc.yaml b/releasenotes/notes/bug-1544586-0fe19a567d3e31fc.yaml new file mode 100644 index 0000000000..fb73325edd --- /dev/null +++ b/releasenotes/notes/bug-1544586-0fe19a567d3e31fc.yaml @@ -0,0 +1,6 @@ +--- +features: + - Add ``--share`` and ``--default`` options to ``subnet pool create`` + and ``--default`` option to ``subnet pool set`` + [Bug `1544586 `_] + [Bug `1544591 `_] From 518ef26895db22b30fa286f83c88e9021561ba8e Mon Sep 17 00:00:00 2001 From: tengqm Date: Thu, 21 Apr 2016 01:20:36 -0400 Subject: [PATCH 0840/3095] Add commands of clustering service to doc This patch adds the 'objects'/'resources' provided by the clustering service (senlin) to the commands doc. Change-Id: I028bd919f5b357d1b6e806c85e83a1a63c42839c --- doc/source/commands.rst | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/doc/source/commands.rst b/doc/source/commands.rst index c759e3047f..e9de65dc64 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -145,6 +145,17 @@ list check out :doc:`plugin-commands`. * ``action definition``: (**Workflow Engine (Mistral)**) * ``action execution``: (**Workflow Engine (Mistral)**) * ``baremetal``: (**Baremetal (Ironic)**) +* ``cluster``: (**Clustering (Senlin)**) +* ``cluster action``: (**Clustering (Senlin)**) +* ``cluster event``: (**Clustering (Senlin)**) +* ``cluster members``: (**Clustering (Senlin)**) +* ``cluster node``: (**Clustering (Senlin)**) +* ``cluster policy``: (**CLustering (Senlin)**) +* ``cluster policy binding``: (**Clustering (Senlin)**) +* ``cluster policy type``: (**Clustering (Senlin)**) +* ``cluster profile``: (**Clustering (Senlin)**) +* ``cluster profile type``: (**Clustering (Senlin)**) +* ``cluster receiver``: (**Clustering (Senlin)**) * ``congress datasource``: (**Policy (Congress)**) * ``congress driver``: (**Policy (Congress)**) * ``congress policy``: (**Policy (Congress)**) @@ -196,6 +207,7 @@ Those actions with an opposite action are noted in parens if applicable. the positional arguments appear in the same order * ``create`` (``delete``) - create a new occurrence of the specified object * ``delete`` (``create``) - delete specific occurrences of the specified objects +* ``expand`` (``shrink``) - increase the capacity of a cluster * ``issue`` (``revoke``) - issue a token * ``list`` - display summary information about multiple objects * ``lock`` (``unlock``) - lock one or more servers so that non-admin user won't be able to execute actions @@ -206,7 +218,7 @@ Those actions with an opposite action are noted in parens if applicable. * ``rebuild`` - rebuild a server using (most of) the same arguments as in the original create * ``remove`` (``add``) - remove an object from a group of objects * ``rescue`` (``unrescue``) - reboot a server in a special rescue mode allowing access to the original disks -* ``resize`` - change a server's flavor +* ``resize`` - change a server's flavor or a cluster's capacity * ``restore`` - restore a heat stack snapshot or restore a server in soft-deleted state * ``resume`` (``suspend``) - return one or more suspended servers to running state * ``revoke`` (``issue``) - revoke a token @@ -214,6 +226,7 @@ Those actions with an opposite action are noted in parens if applicable. * ``set`` (``unset``) - set a property on the object, formerly called metadata * ``shelve`` (``unshelve``) - shelve one or more servers * ``show`` - display detailed information about the specific object +* ``shrink`` (``expand``) - reduce the capacity of a cluster * ``start`` (``stop``) - start one or more servers * ``stop`` (``start``) - stop one or more servers * ``suspend`` (``resume``) - stop one or more servers and save to disk freeing memory From 0d3a06db1ce3871e4153255d21c9c9f6ac321030 Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Thu, 21 Apr 2016 14:49:58 +0800 Subject: [PATCH 0841/3095] Fix server group document issue The patch fix server group document typo and format issue. Implements: blueprint nova-server-group-support Related-Bug: #1542171 Change-Id: I3e24ddce91ecabc6be1b7be95d0f4e5a2ebe93ab --- doc/source/command-objects/server-group.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/command-objects/server-group.rst b/doc/source/command-objects/server-group.rst index 35d903dcca..55d789c746 100644 --- a/doc/source/command-objects/server-group.rst +++ b/doc/source/command-objects/server-group.rst @@ -2,7 +2,7 @@ server group ============ -Server group provide a mechanism to group servers according to certain policy. +Server group provides a mechanism to group servers according to certain policy. Compute v2 @@ -77,4 +77,4 @@ Display server group details .. describe:: - Server group to display (name or ID) + Server group to display (name or ID) From 27024d70af4756cb6e4b210b025ed7427541f773 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Thu, 21 Apr 2016 11:33:24 -0500 Subject: [PATCH 0842/3095] Support quota show for current project The "os quota show" command "" argument is now optional. If not specified, the user's current project is used. This allows non-admin users to show quotas for their current project. Change-Id: I602d4cc09c9d29ce84271eff78137f8810cb1a47 Closes-Bug: #1572733 --- doc/source/command-objects/quota.rst | 12 ++++---- functional/tests/common/test_quota.py | 5 ++++ openstackclient/common/quota.py | 29 ++++++++++++------- openstackclient/tests/common/test_quota.py | 11 +++++++ .../notes/bug-1572733-874b37a7fa8292d0.yaml | 6 ++++ 5 files changed, 46 insertions(+), 17 deletions(-) create mode 100644 releasenotes/notes/bug-1572733-874b37a7fa8292d0.yaml diff --git a/doc/source/command-objects/quota.rst b/doc/source/command-objects/quota.rst index 98e6df3317..9e09bd484f 100644 --- a/doc/source/command-objects/quota.rst +++ b/doc/source/command-objects/quota.rst @@ -4,7 +4,7 @@ quota Resource quotas appear in multiple APIs, OpenStackClient presents them as a single object with multiple properties. -Compute v2, Block Storage v1 +Block Storage v1, Compute v2, Network v2 quota set --------- @@ -129,14 +129,14 @@ Set quotas for class quota show ---------- -Show quotas for project +Show quotas for project or class .. program:: quota show .. code:: bash os quota show [--default] - + [] .. option:: --default @@ -146,13 +146,13 @@ Show quotas for project .. _quota_show-project: .. describe:: - Show quotas for class + Show quotas for this project (name or ID) .. code:: bash os quota show --class - + [] .. option:: --class @@ -161,4 +161,4 @@ Show quotas for project .. _quota_show-class: .. describe:: - Class to show + Show quotas for this class (name or ID) diff --git a/functional/tests/common/test_quota.py b/functional/tests/common/test_quota.py index 62b43a34ab..22549efe8c 100644 --- a/functional/tests/common/test_quota.py +++ b/functional/tests/common/test_quota.py @@ -36,3 +36,8 @@ def test_quota_show(self): raw_output = self.openstack('quota show ' + self.PROJECT_NAME) for expected_field in self.EXPECTED_FIELDS: self.assertIn(expected_field, raw_output) + + def test_quota_show_default_project(self): + raw_output = self.openstack('quota show') + for expected_field in self.EXPECTED_FIELDS: + self.assertIn(expected_field, raw_output) diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index b3d4c3b618..b497a44d62 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -145,7 +145,8 @@ def get_parser(self, prog_name): parser.add_argument( 'project', metavar='', - help='Show this project or class (name/ID)', + nargs='?', + help='Show quotas for this project or class (name or ID)', ) type_group = parser.add_mutually_exclusive_group() type_group.add_argument( @@ -164,12 +165,22 @@ def get_parser(self, prog_name): ) return parser + def _get_project(self, parsed_args): + if parsed_args.project is not None: + identity_client = self.app.client_manager.identity + project = utils.find_resource( + identity_client.projects, + parsed_args.project, + ).id + elif self.app.client_manager.auth_ref: + # Get the project from the current auth + project = self.app.client_manager.auth_ref.project_id + else: + project = None + return project + def get_compute_volume_quota(self, client, parsed_args): - identity_client = self.app.client_manager.identity - project = utils.find_resource( - identity_client.projects, - parsed_args.project, - ).id + project = self._get_project(parsed_args) try: if parsed_args.quota_class: @@ -189,11 +200,7 @@ def get_network_quota(self, parsed_args): if parsed_args.quota_class or parsed_args.default: return {} if self.app.client_manager.is_network_endpoint_enabled(): - identity_client = self.app.client_manager.identity - project = utils.find_resource( - identity_client.projects, - parsed_args.project, - ).id + project = self._get_project(parsed_args) return self.app.client_manager.network.get_quota(project) else: return {} diff --git a/openstackclient/tests/common/test_quota.py b/openstackclient/tests/common/test_quota.py index edf29c9b1f..ba7ee469c9 100644 --- a/openstackclient/tests/common/test_quota.py +++ b/openstackclient/tests/common/test_quota.py @@ -59,6 +59,7 @@ def setUp(self): self.service_catalog_mock = \ self.app.client_manager.auth_ref.service_catalog self.service_catalog_mock.reset_mock() + self.app.client_manager.auth_ref.project_id = identity_fakes.project_id class TestQuotaSet(TestQuota): @@ -304,3 +305,13 @@ def test_quota_show_with_class(self): identity_fakes.project_id) self.volume_quotas_class_mock.get.assert_called_with( identity_fakes.project_id) + + def test_quota_show_no_project(self): + parsed_args = self.check_parser(self.cmd, [], []) + + self.cmd.take_action(parsed_args) + + self.quotas_mock.get.assert_called_with(identity_fakes.project_id) + self.volume_quotas_mock.get.assert_called_with( + identity_fakes.project_id) + self.network.get_quota.assert_called_with(identity_fakes.project_id) diff --git a/releasenotes/notes/bug-1572733-874b37a7fa8292d0.yaml b/releasenotes/notes/bug-1572733-874b37a7fa8292d0.yaml new file mode 100644 index 0000000000..3ddf493705 --- /dev/null +++ b/releasenotes/notes/bug-1572733-874b37a7fa8292d0.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - The ``quota show`` command ```` argument is now + optional. If not specified, the user's current project is used. + This allows non-admin users to show quotas for their current project. + [Bug `1572733 `_] From 1a2e12832db6f3871c2c9356ff7b017dcb23a672 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Tue, 12 Apr 2016 16:07:30 -0500 Subject: [PATCH 0843/3095] Devref: Command Beta The devref proposes OSC support for beta commands. Change-Id: I538a38be33734faf6eb69a3cb50946b2396b0c57 --- doc/source/backwards-incompatible.rst | 3 ++ doc/source/command-beta.rst | 72 +++++++++++++++++++++++++++ doc/source/index.rst | 1 + doc/source/man/openstack.rst | 3 ++ openstackclient/shell.py | 5 ++ 5 files changed, 84 insertions(+) create mode 100644 doc/source/command-beta.rst diff --git a/doc/source/backwards-incompatible.rst b/doc/source/backwards-incompatible.rst index 4b90d6e1bc..00b314a53c 100644 --- a/doc/source/backwards-incompatible.rst +++ b/doc/source/backwards-incompatible.rst @@ -10,6 +10,9 @@ Should positional arguments for a command need to change, the OpenStackClient team attempts to make the transition as painless as possible. Look for deprecation warnings that indicate the new commands (or options) to use. +Commands labeled as a beta according to :doc:`command-beta` are exempt from +this backwards incompatible change handling. + List of Backwards Incompatible Changes ====================================== diff --git a/doc/source/command-beta.rst b/doc/source/command-beta.rst new file mode 100644 index 0000000000..53a442047f --- /dev/null +++ b/doc/source/command-beta.rst @@ -0,0 +1,72 @@ +============ +Command Beta +============ + +OpenStackClient releases do not always coincide with OpenStack +releases. This creates challenges when developing new OpenStackClient +commands for the current OpenStack release under development +since there may not be an official release of the REST API +enhancements necessary for the command. In addition, backwards +compatibility may not be guaranteed until an official OpenStack release. +To address these challenges, an OpenStackClient command may +be labeled as a beta command according to the guidelines +below. Such commands may introduce backwards incompatible +changes and may use REST API enhancements not yet released. + +See the examples below on how to label a command as a beta +by updating the command documentation, help and implementation. + +The initial release note must label the new command as a beta. +No further release notes are required until the command +is no longer a beta. At which time, the command beta label +or the command itself must be removed and a new release note +must be provided. + +Documentation +------------- + +The command documentation must label the command as a beta. + +example list +~~~~~~~~~~~~ + +List examples + +.. caution:: This is a beta command and subject to change. + Use global option ``--enable-beta-commands`` to + enable this command. + +.. program:: example list +.. code:: bash + + os example list + +Help +---- + +The command help must label the command as a beta. + +.. code-block:: python + + class ShowExample(command.ShowOne): + """Display example details + + (Caution: This is a beta command and subject to change. + Use global option --enable-beta-commands to enable + this command) + """ + +Implementation +-------------- + +The command must raise a ``CommandError`` exception if beta commands +are not enabled via ``--enable-beta-commands`` global option. + +.. code-block:: python + + def take_action(self, parsed_args): + if not self.app.options.enable_beta_commands: + msg = _('Caution: This is a beta command and subject to ' + 'change. Use global option --enable-beta-commands ' + 'to enable this command.') + raise exceptions.CommandError(msg) diff --git a/doc/source/index.rst b/doc/source/index.rst index fe12b862ea..0869344142 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -47,6 +47,7 @@ Developer Documentation :maxdepth: 1 developing + command-beta command-options command-wrappers command-errors diff --git a/doc/source/man/openstack.rst b/doc/source/man/openstack.rst index a2ecd8db73..8cd10a7933 100644 --- a/doc/source/man/openstack.rst +++ b/doc/source/man/openstack.rst @@ -147,6 +147,9 @@ OPTIONS :option:`--debug` show tracebacks on errors and set verbosity to debug +:option:`--enable-beta-commands` + Enable beta commands which are subject to change + COMMANDS ======== diff --git a/openstackclient/shell.py b/openstackclient/shell.py index b96fb089b9..9968d73fc1 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -250,6 +250,11 @@ def build_option_parser(self, description, version): action='store_true', help="Print API call timing info", ) + parser.add_argument( + '--enable-beta-commands', + action='store_true', + help="Enable beta commands which are subject to change", + ) # osprofiler HMAC key argument if osprofiler_profiler: From d0885e5d5a9f12a74bbf79623f1058f424939e4a Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Sun, 24 Apr 2016 19:17:36 -0500 Subject: [PATCH 0844/3095] Doc: Add network resource descriptions Add descriptions to the network resource command documentation. Change-Id: I547ffb48f8950311a5ee65d6b535846f2aca0efc --- doc/source/command-objects/availability-zone.rst | 3 +++ doc/source/command-objects/network.rst | 7 +++++++ doc/source/command-objects/port.rst | 4 ++++ doc/source/command-objects/router.rst | 4 ++++ doc/source/command-objects/security-group-rule.rst | 3 +++ doc/source/command-objects/security-group.rst | 4 ++++ doc/source/command-objects/subnet-pool.rst | 3 +++ doc/source/command-objects/subnet.rst | 4 ++++ 8 files changed, 32 insertions(+) diff --git a/doc/source/command-objects/availability-zone.rst b/doc/source/command-objects/availability-zone.rst index 1f5684383d..149e10811d 100644 --- a/doc/source/command-objects/availability-zone.rst +++ b/doc/source/command-objects/availability-zone.rst @@ -2,6 +2,9 @@ availability zone ================= +An **availability zone** is a logical partition of cloud block storage, +compute and network services. + Block Storage v2, Compute v2, Network v2 availability zone list diff --git a/doc/source/command-objects/network.rst b/doc/source/command-objects/network.rst index 5d534c59f5..91eb2b08c9 100644 --- a/doc/source/command-objects/network.rst +++ b/doc/source/command-objects/network.rst @@ -2,6 +2,13 @@ network ======= +A **network** is an isolated Layer 2 networking segment. There are two types +of networks, project and provider networks. Project networks are fully isolated +and are not shared with other projects. Provider networks map to existing +physical networks in the data center and provide external network access for +servers and other resources. Only an OpenStack administrator can create +provider networks. Networks can be connected via routers. + Compute v2, Network v2 network create diff --git a/doc/source/command-objects/port.rst b/doc/source/command-objects/port.rst index 36e830822e..e4cf2cd212 100644 --- a/doc/source/command-objects/port.rst +++ b/doc/source/command-objects/port.rst @@ -2,6 +2,10 @@ port ==== +A **port** is a connection point for attaching a single device, such as the +NIC of a server, to a network. The port also describes the associated network +configuration, such as the MAC and IP addresses to be used on that port. + Network v2 port create diff --git a/doc/source/command-objects/router.rst b/doc/source/command-objects/router.rst index 1503516e44..1bb9341ebf 100644 --- a/doc/source/command-objects/router.rst +++ b/doc/source/command-objects/router.rst @@ -2,6 +2,10 @@ router ====== +A **router** is a logical component that forwards data packets between +networks. It also provides Layer 3 and NAT forwarding to provide external +network access for servers on project networks. + Network v2 router add port diff --git a/doc/source/command-objects/security-group-rule.rst b/doc/source/command-objects/security-group-rule.rst index 8218c81aa0..b0ac3c9449 100644 --- a/doc/source/command-objects/security-group-rule.rst +++ b/doc/source/command-objects/security-group-rule.rst @@ -2,6 +2,9 @@ security group rule =================== +A **security group rule** specifies the network access rules for servers +and other resources on the network. + Compute v2, Network v2 security group rule create diff --git a/doc/source/command-objects/security-group.rst b/doc/source/command-objects/security-group.rst index 2c6e7a8adc..3af11b5af7 100644 --- a/doc/source/command-objects/security-group.rst +++ b/doc/source/command-objects/security-group.rst @@ -2,6 +2,10 @@ security group ============== +A **security group** acts as a virtual firewall for servers and other +resources on a network. It is a container for security group rules +which specify the network access rules. + Compute v2, Network v2 security group create diff --git a/doc/source/command-objects/subnet-pool.rst b/doc/source/command-objects/subnet-pool.rst index 867153efa4..6edbe8e817 100644 --- a/doc/source/command-objects/subnet-pool.rst +++ b/doc/source/command-objects/subnet-pool.rst @@ -2,6 +2,9 @@ subnet pool =========== +A **subnet pool** contains a collection of prefixes in CIDR notation +that are available for IP address allocation. + Network v2 subnet pool create diff --git a/doc/source/command-objects/subnet.rst b/doc/source/command-objects/subnet.rst index 35134f4699..8daa251fdc 100644 --- a/doc/source/command-objects/subnet.rst +++ b/doc/source/command-objects/subnet.rst @@ -2,6 +2,10 @@ subnet ====== +A **subnet** is a block of IP addresses and associated configuration state. +Subnets are used to allocate IP addresses when new ports are created on a +network. + Network v2 subnet create From 4072554608abd4828f281dcc0e20ce99ed6611b9 Mon Sep 17 00:00:00 2001 From: Sheel Rana Date: Wed, 30 Mar 2016 17:05:09 +0530 Subject: [PATCH 0845/3095] Support for volume service list OSC does not support to list volume services. This patch will provide support for adding volume service related support. Closes-bug:#1550999 Implements: bp cinder-command-support Change-Id: I50ac14aeb96c4b8ddbf7b33e519feea0d126f752 --- doc/source/command-objects/volume-service.rst | 31 ++++ doc/source/commands.rst | 1 + openstackclient/tests/volume/v1/fakes.py | 91 +++++++++++ .../tests/volume/v1/test_service.py | 141 ++++++++++++++++++ openstackclient/tests/volume/v2/fakes.py | 113 ++++++++++++-- .../tests/volume/v2/test_service.py | 141 ++++++++++++++++++ openstackclient/volume/v1/service.py | 70 +++++++++ openstackclient/volume/v2/service.py | 70 +++++++++ .../volume_service-5e352a71dfbc828d.yaml | 9 ++ setup.cfg | 4 + 10 files changed, 660 insertions(+), 11 deletions(-) create mode 100644 doc/source/command-objects/volume-service.rst create mode 100644 openstackclient/tests/volume/v1/test_service.py create mode 100644 openstackclient/tests/volume/v2/test_service.py create mode 100644 openstackclient/volume/v1/service.py create mode 100644 openstackclient/volume/v2/service.py create mode 100644 releasenotes/notes/volume_service-5e352a71dfbc828d.yaml diff --git a/doc/source/command-objects/volume-service.rst b/doc/source/command-objects/volume-service.rst new file mode 100644 index 0000000000..aa9fa6d262 --- /dev/null +++ b/doc/source/command-objects/volume-service.rst @@ -0,0 +1,31 @@ +============== +volume service +============== + +Volume v1, v2 + +volume service list +------------------- + +List volume service + +.. program:: volume service list +.. code:: bash + + os volume service list + [--host ] + [--service ] + [--long] + +.. _volume-service-list: +.. option:: --host + + List services on specified host (name only) + +.. option:: --service + + List only specified service (name only) + +.. option:: --long + + List additional fields in output diff --git a/doc/source/commands.rst b/doc/source/commands.rst index c54cadb1b9..45b229abf8 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -127,6 +127,7 @@ referring to both Compute and Volume quotas. * ``user role``: (**Identity**) roles assigned to a user * ``volume``: (**Volume**) block volumes * ``volume type``: (**Volume**) deployment-specific types of volumes available +* ``volume service``: (**Volume**) services to manage block storage operations Plugin Objects diff --git a/openstackclient/tests/volume/v1/fakes.py b/openstackclient/tests/volume/v1/fakes.py index 42673efaa7..d6c46439c6 100644 --- a/openstackclient/tests/volume/v1/fakes.py +++ b/openstackclient/tests/volume/v1/fakes.py @@ -129,6 +129,97 @@ } +class FakeServiceClient(object): + + def __init__(self, **kwargs): + self.services = mock.Mock() + self.services.resource_class = fakes.FakeResource(None, {}) + + +class TestService(utils.TestCommand): + + def setUp(self): + super(TestService, self).setUp() + + self.app.client_manager.volume = FakeServiceClient( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN + ) + + +class FakeService(object): + """Fake one or more Services.""" + + @staticmethod + def create_one_service(attrs=None): + """Create a fake service. + + :param Dictionary attrs: + A dictionary with all attributes of service + :retrun: + A FakeResource object with host, status, etc. + """ + # Set default attribute + service_info = { + 'host': 'host_test', + 'binary': 'cinder_test', + 'status': 'enabled', + 'disabled_reason': 'LongHoliday-GoldenWeek', + 'zone': 'fake_zone', + 'updated_at': 'fake_date', + 'state': 'fake_state', + } + + # Overwrite default attributes if there are some attributes set + if attrs is None: + attrs = {} + service_info.update(attrs) + + service = fakes.FakeResource( + None, + service_info, + loaded=True) + + return service + + @staticmethod + def create_services(attrs=None, count=2): + """Create multiple fake services. + + :param Dictionary attrs: + A dictionary with all attributes of service + :param Integer count: + The number of services to be faked + :return: + A list of FakeResource objects + """ + services = [] + for n in range(0, count): + services.append(FakeService.create_one_service(attrs)) + + return services + + @staticmethod + def get_services(services=None, count=2): + """Get an iterable MagicMock object with a list of faked services. + + If services list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List services: + A list of FakeResource objects faking services + :param Integer count: + The number of services to be faked + :return + An iterable Mock object with side_effect set to a list of faked + services + """ + if services is None: + services = FakeService.create_services(count) + + return mock.MagicMock(side_effect=services) + + class FakeImagev1Client(object): def __init__(self, **kwargs): diff --git a/openstackclient/tests/volume/v1/test_service.py b/openstackclient/tests/volume/v1/test_service.py new file mode 100644 index 0000000000..7168434496 --- /dev/null +++ b/openstackclient/tests/volume/v1/test_service.py @@ -0,0 +1,141 @@ +# +# 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.volume.v1 import fakes as service_fakes +from openstackclient.volume.v1 import service + + +class TestService(service_fakes.TestService): + + def setUp(self): + super(TestService, self).setUp() + + # Get a shortcut to the ServiceManager Mock + self.service_mock = self.app.client_manager.volume.services + self.service_mock.reset_mock() + + +class TestServiceList(TestService): + + # The service to be listed + services = service_fakes.FakeService.create_one_service() + + def setUp(self): + super(TestServiceList, self).setUp() + + self.service_mock.list.return_value = [self.services] + + # Get the command object to test + self.cmd = service.ListService(self.app, None) + + def test_service_list(self): + arglist = [ + '--host', self.services.host, + '--service', self.services.binary, + ] + verifylist = [ + ('host', self.services.host), + ('service', self.services.binary), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + expected_columns = [ + 'Binary', + 'Host', + 'Zone', + 'Status', + 'State', + 'Updated At', + ] + + # confirming if all expected columns are present in the result. + self.assertEqual(expected_columns, columns) + + datalist = (( + self.services.binary, + self.services.host, + self.services.zone, + self.services.status, + self.services.state, + self.services.updated_at, + ), ) + + # confirming if all expected values are present in the result. + self.assertEqual(datalist, tuple(data)) + + # checking if proper call was made to list services + self.service_mock.list.assert_called_with( + self.services.host, + self.services.binary, + ) + + # checking if prohibited columns are present in output + self.assertNotIn("Disabled Reason", columns) + self.assertNotIn(self.services.disabled_reason, + tuple(data)) + + def test_service_list_with_long_option(self): + arglist = [ + '--host', self.services.host, + '--service', self.services.binary, + '--long' + ] + verifylist = [ + ('host', self.services.host), + ('service', self.services.binary), + ('long', True) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + expected_columns = [ + 'Binary', + 'Host', + 'Zone', + 'Status', + 'State', + 'Updated At', + 'Disabled Reason' + ] + + # confirming if all expected columns are present in the result. + self.assertEqual(expected_columns, columns) + + datalist = (( + self.services.binary, + self.services.host, + self.services.zone, + self.services.status, + self.services.state, + self.services.updated_at, + self.services.disabled_reason, + ), ) + + # confirming if all expected values are present in the result. + self.assertEqual(datalist, tuple(data)) + + self.service_mock.list.assert_called_with( + self.services.host, + self.services.binary, + ) diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py index 97bbc59bce..120666a059 100644 --- a/openstackclient/tests/volume/v2/fakes.py +++ b/openstackclient/tests/volume/v2/fakes.py @@ -232,6 +232,97 @@ } +class FakeServiceClient(object): + + def __init__(self, **kwargs): + self.services = mock.Mock() + self.services.resource_class = fakes.FakeResource(None, {}) + + +class TestService(utils.TestCommand): + + def setUp(self): + super(TestService, self).setUp() + + self.app.client_manager.volume = FakeServiceClient( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN + ) + + +class FakeService(object): + """Fake one or more Services.""" + + @staticmethod + def create_one_service(attrs=None): + """Create a fake service. + + :param Dictionary attrs: + A dictionary with all attributes of service + :retrun: + A FakeResource object with host, status, etc. + """ + # Set default attribute + service_info = { + 'host': 'host_test', + 'binary': 'cinder_test', + 'status': 'enabled', + 'disabled_reason': 'LongHoliday-GoldenWeek', + 'zone': 'fake_zone', + 'updated_at': 'fake_date', + 'state': 'fake_state', + } + + # Overwrite default attributes if there are some attributes set + if attrs is None: + attrs = {} + service_info.update(attrs) + + service = fakes.FakeResource( + None, + service_info, + loaded=True) + + return service + + @staticmethod + def create_services(attrs=None, count=2): + """Create multiple fake services. + + :param Dictionary attrs: + A dictionary with all attributes of service + :param Integer count: + The number of services to be faked + :return: + A list of FakeResource objects + """ + services = [] + for n in range(0, count): + services.append(FakeService.create_one_service(attrs)) + + return services + + @staticmethod + def get_services(services=None, count=2): + """Get an iterable MagicMock object with a list of faked services. + + If services list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List services: + A list of FakeResource objects faking services + :param Integer count: + The number of services to be faked + :return + An iterable Mock object with side_effect set to a list of faked + services + """ + if services is None: + services = FakeService.create_services(count) + + return mock.MagicMock(side_effect=services) + + class FakeVolumeClient(object): def __init__(self, **kwargs): @@ -243,6 +334,8 @@ def __init__(self, **kwargs): self.backups.resource_class = fakes.FakeResource(None, {}) self.volume_types = mock.Mock() self.volume_types.resource_class = fakes.FakeResource(None, {}) + self.volume_type_access = mock.Mock() + self.volume_type_access.resource_class = fakes.FakeResource(None, {}) self.restores = mock.Mock() self.restores.resource_class = fakes.FakeResource(None, {}) self.qos_specs = mock.Mock() @@ -279,7 +372,7 @@ class FakeVolume(object): """ @staticmethod - def create_one_volume(attrs={}): + def create_one_volume(attrs=None): """Create a fake volume. :param Dictionary attrs: @@ -287,6 +380,8 @@ def create_one_volume(attrs={}): :retrun: A FakeResource object with id, name, status, etc. """ + attrs = attrs or {} + # Set default attribute volume_info = { 'id': 'volume-id' + uuid.uuid4().hex, @@ -318,7 +413,7 @@ def create_one_volume(attrs={}): return volume @staticmethod - def create_volumes(attrs={}, count=2): + def create_volumes(attrs=None, count=2): """Create multiple fake volumes. :param Dictionary attrs: @@ -359,16 +454,16 @@ class FakeAvailabilityZone(object): """Fake one or more volume availability zones (AZs).""" @staticmethod - def create_one_availability_zone(attrs={}, methods={}): + def create_one_availability_zone(attrs=None): """Create a fake AZ. :param Dictionary attrs: A dictionary with all attributes - :param Dictionary methods: - A dictionary with all methods :return: A FakeResource object with zoneName, zoneState, etc. """ + attrs = attrs or {} + # Set default attributes. availability_zone = { 'zoneName': uuid.uuid4().hex, @@ -380,18 +475,15 @@ def create_one_availability_zone(attrs={}, methods={}): availability_zone = fakes.FakeResource( info=copy.deepcopy(availability_zone), - methods=methods, loaded=True) return availability_zone @staticmethod - def create_availability_zones(attrs={}, methods={}, count=2): + def create_availability_zones(attrs=None, count=2): """Create multiple fake AZs. :param Dictionary attrs: A dictionary with all attributes - :param Dictionary methods: - A dictionary with all methods :param int count: The number of AZs to fake :return: @@ -400,8 +492,7 @@ def create_availability_zones(attrs={}, methods={}, count=2): availability_zones = [] for i in range(0, count): availability_zone = \ - FakeAvailabilityZone.create_one_availability_zone( - attrs, methods) + FakeAvailabilityZone.create_one_availability_zone(attrs) availability_zones.append(availability_zone) return availability_zones diff --git a/openstackclient/tests/volume/v2/test_service.py b/openstackclient/tests/volume/v2/test_service.py new file mode 100644 index 0000000000..ba2e1b3217 --- /dev/null +++ b/openstackclient/tests/volume/v2/test_service.py @@ -0,0 +1,141 @@ +# +# 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.volume.v2 import fakes as service_fakes +from openstackclient.volume.v2 import service + + +class TestService(service_fakes.TestService): + + def setUp(self): + super(TestService, self).setUp() + + # Get a shortcut to the ServiceManager Mock + self.service_mock = self.app.client_manager.volume.services + self.service_mock.reset_mock() + + +class TestServiceList(TestService): + + # The service to be listed + services = service_fakes.FakeService.create_one_service() + + def setUp(self): + super(TestServiceList, self).setUp() + + self.service_mock.list.return_value = [self.services] + + # Get the command object to test + self.cmd = service.ListService(self.app, None) + + def test_service_list(self): + arglist = [ + '--host', self.services.host, + '--service', self.services.binary, + ] + verifylist = [ + ('host', self.services.host), + ('service', self.services.binary), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + expected_columns = [ + 'Binary', + 'Host', + 'Zone', + 'Status', + 'State', + 'Updated At', + ] + + # confirming if all expected columns are present in the result. + self.assertEqual(expected_columns, columns) + + datalist = (( + self.services.binary, + self.services.host, + self.services.zone, + self.services.status, + self.services.state, + self.services.updated_at, + ), ) + + # confirming if all expected values are present in the result. + self.assertEqual(datalist, tuple(data)) + + # checking if proper call was made to list services + self.service_mock.list.assert_called_with( + self.services.host, + self.services.binary, + ) + + # checking if prohibited columns are present in output + self.assertNotIn("Disabled Reason", columns) + self.assertNotIn(self.services.disabled_reason, + tuple(data)) + + def test_service_list_with_long_option(self): + arglist = [ + '--host', self.services.host, + '--service', self.services.binary, + '--long' + ] + verifylist = [ + ('host', self.services.host), + ('service', self.services.binary), + ('long', True) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + expected_columns = [ + 'Binary', + 'Host', + 'Zone', + 'Status', + 'State', + 'Updated At', + 'Disabled Reason' + ] + + # confirming if all expected columns are present in the result. + self.assertEqual(expected_columns, columns) + + datalist = (( + self.services.binary, + self.services.host, + self.services.zone, + self.services.status, + self.services.state, + self.services.updated_at, + self.services.disabled_reason, + ), ) + + # confirming if all expected values are present in the result. + self.assertEqual(datalist, tuple(data)) + + self.service_mock.list.assert_called_with( + self.services.host, + self.services.binary, + ) diff --git a/openstackclient/volume/v1/service.py b/openstackclient/volume/v1/service.py new file mode 100644 index 0000000000..f26be13e0c --- /dev/null +++ b/openstackclient/volume/v1/service.py @@ -0,0 +1,70 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +"""Service action implementations""" + +from openstackclient.common import command +from openstackclient.common import utils + + +class ListService(command.Lister): + """List service command""" + + def get_parser(self, prog_name): + parser = super(ListService, self).get_parser(prog_name) + parser.add_argument( + "--host", + metavar="", + help="List services on specified host (name only)") + parser.add_argument( + "--service", + metavar="", + help="List only specified service (name only)") + parser.add_argument( + "--long", + action="store_true", + default=False, + help="List additional fields in output" + ) + return parser + + def take_action(self, parsed_args): + service_client = self.app.client_manager.volume + + if parsed_args.long: + columns = [ + "Binary", + "Host", + "Zone", + "Status", + "State", + "Updated At", + "Disabled Reason" + ] + else: + columns = [ + "Binary", + "Host", + "Zone", + "Status", + "State", + "Updated At" + ] + + data = service_client.services.list(parsed_args.host, + parsed_args.service) + return (columns, + (utils.get_item_properties( + s, columns, + ) for s in data)) diff --git a/openstackclient/volume/v2/service.py b/openstackclient/volume/v2/service.py new file mode 100644 index 0000000000..f26be13e0c --- /dev/null +++ b/openstackclient/volume/v2/service.py @@ -0,0 +1,70 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +"""Service action implementations""" + +from openstackclient.common import command +from openstackclient.common import utils + + +class ListService(command.Lister): + """List service command""" + + def get_parser(self, prog_name): + parser = super(ListService, self).get_parser(prog_name) + parser.add_argument( + "--host", + metavar="", + help="List services on specified host (name only)") + parser.add_argument( + "--service", + metavar="", + help="List only specified service (name only)") + parser.add_argument( + "--long", + action="store_true", + default=False, + help="List additional fields in output" + ) + return parser + + def take_action(self, parsed_args): + service_client = self.app.client_manager.volume + + if parsed_args.long: + columns = [ + "Binary", + "Host", + "Zone", + "Status", + "State", + "Updated At", + "Disabled Reason" + ] + else: + columns = [ + "Binary", + "Host", + "Zone", + "Status", + "State", + "Updated At" + ] + + data = service_client.services.list(parsed_args.host, + parsed_args.service) + return (columns, + (utils.get_item_properties( + s, columns, + ) for s in data)) diff --git a/releasenotes/notes/volume_service-5e352a71dfbc828d.yaml b/releasenotes/notes/volume_service-5e352a71dfbc828d.yaml new file mode 100644 index 0000000000..1cfa81cfae --- /dev/null +++ b/releasenotes/notes/volume_service-5e352a71dfbc828d.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + Adds support for volume service list. + + An user can list available volume services by using + ``volume service list`` + + [Bug 1550999 'https://bugs.launchpad.net/python-openstackclient/+bug/1550999'_] diff --git a/setup.cfg b/setup.cfg index 7689713c33..0407c3cf7e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -421,6 +421,8 @@ openstack.volume.v1 = volume_qos_show = openstackclient.volume.v1.qos_specs:ShowQos volume_qos_unset = openstackclient.volume.v1.qos_specs:UnsetQos + volume_service_list = openstackclient.volume.v1.service:ListService + openstack.volume.v2 = backup_create = openstackclient.volume.v2.backup:CreateBackup backup_delete = openstackclient.volume.v2.backup:DeleteBackup @@ -458,6 +460,8 @@ openstack.volume.v2 = volume_qos_show = openstackclient.volume.v2.qos_specs:ShowQos volume_qos_unset = openstackclient.volume.v2.qos_specs:UnsetQos + volume_service_list = openstackclient.volume.v2.service:ListService + [build_sphinx] source-dir = doc/source build-dir = doc/build From 08759b853a2611144a2d3f0e9216d6801fc23ef2 Mon Sep 17 00:00:00 2001 From: Inessa Vasilevskaya Date: Mon, 25 Apr 2016 12:22:14 +0000 Subject: [PATCH 0846/3095] Fixes BadRequest when no --pool-prefix given --pool-prefix is made required on subnetpool creation. Closes-bug: #1536479 Change-Id: I3d183e45e9b96bc08011c36f45ec2b7a9c01b627 --- doc/source/command-objects/subnet-pool.rst | 12 ++++++------ openstackclient/network/v2/subnet_pool.py | 5 +++-- .../tests/network/v2/test_subnet_pool.py | 15 ++++++++++++++- .../notes/bug-1536479-d1f03ed2177d06ed.yaml | 5 +++++ 4 files changed, 28 insertions(+), 9 deletions(-) create mode 100644 releasenotes/notes/bug-1536479-d1f03ed2177d06ed.yaml diff --git a/doc/source/command-objects/subnet-pool.rst b/doc/source/command-objects/subnet-pool.rst index 867153efa4..523f78c2f5 100644 --- a/doc/source/command-objects/subnet-pool.rst +++ b/doc/source/command-objects/subnet-pool.rst @@ -13,7 +13,6 @@ Create subnet pool .. code:: bash os subnet pool create - [--pool-prefix [...]] [--default-prefix-length ] [--min-prefix-length ] [--max-prefix-length ] @@ -21,13 +20,9 @@ Create subnet pool [--address-scope ] [--default | --no-default] [--share | --no-share] + --pool-prefix [...] -.. option:: --pool-prefix - - Set subnet pool prefixes (in CIDR notation) - (repeat option to set multiple prefixes) - .. option:: --default-prefix-length Set subnet pool default prefix length @@ -70,6 +65,11 @@ Create subnet pool Set this subnet pool as not shared +.. describe:: --pool-prefix + + Set subnet pool prefixes (in CIDR notation) + (repeat option to set multiple prefixes) + .. _subnet_pool_create-name: .. describe:: diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index 688dd2ca7c..435db2e116 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -78,12 +78,13 @@ def _get_attrs(client_manager, parsed_args): return attrs -def _add_prefix_options(parser): +def _add_prefix_options(parser, for_create=False): parser.add_argument( '--pool-prefix', metavar='', dest='prefixes', action='append', + required=for_create, help=_("Set subnet pool prefixes (in CIDR notation) " "(repeat option to set multiple prefixes)") ) @@ -131,7 +132,7 @@ def get_parser(self, prog_name): metavar='', help=_("Name of the new subnet pool") ) - _add_prefix_options(parser) + _add_prefix_options(parser, for_create=True) parser.add_argument( '--project', metavar='', diff --git a/openstackclient/tests/network/v2/test_subnet_pool.py b/openstackclient/tests/network/v2/test_subnet_pool.py index 369a8b1125..b40390a143 100644 --- a/openstackclient/tests/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/network/v2/test_subnet_pool.py @@ -113,6 +113,17 @@ def test_create_no_options(self): self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) + def test_create_no_pool_prefix(self): + """Make sure --pool-prefix is a required argument""" + arglist = [ + self._subnet_pool.name, + ] + verifylist = [ + ('name', self._subnet_pool.name), + ] + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + def test_create_default_options(self): arglist = [ '--pool-prefix', '10.0.10.0/24', @@ -138,6 +149,7 @@ def test_create_prefixlen_options(self): '--default-prefix-length', self._subnet_pool.default_prefixlen, '--max-prefix-length', self._subnet_pool.max_prefixlen, '--min-prefix-length', self._subnet_pool.min_prefixlen, + '--pool-prefix', '10.0.10.0/24', self._subnet_pool.name, ] verifylist = [ @@ -145,6 +157,7 @@ def test_create_prefixlen_options(self): ('max_prefix_length', self._subnet_pool.max_prefixlen), ('min_prefix_length', self._subnet_pool.min_prefixlen), ('name', self._subnet_pool.name), + ('prefixes', ['10.0.10.0/24']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -154,7 +167,7 @@ def test_create_prefixlen_options(self): 'default_prefixlen': self._subnet_pool.default_prefixlen, 'max_prefixlen': self._subnet_pool.max_prefixlen, 'min_prefixlen': self._subnet_pool.min_prefixlen, - 'prefixes': [], + 'prefixes': ['10.0.10.0/24'], 'name': self._subnet_pool.name, }) self.assertEqual(self.columns, columns) diff --git a/releasenotes/notes/bug-1536479-d1f03ed2177d06ed.yaml b/releasenotes/notes/bug-1536479-d1f03ed2177d06ed.yaml new file mode 100644 index 0000000000..b9932ad43b --- /dev/null +++ b/releasenotes/notes/bug-1536479-d1f03ed2177d06ed.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + ``--pool-prefix`` option made required for ``subnet pool create`` + [Bug `1536479 `_] From 9dba843bdeede54eab30e7f3b537c75965748110 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Mon, 25 Apr 2016 11:20:51 +0800 Subject: [PATCH 0847/3095] Add functional tests for commands of floating ip This patch add functinal tests for commands of floating ip Change-Id: I7f29578d0e14884f21183bfb82228d2fe7b7a029 --- .../tests/network/v2/test_floating_ip.py | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 functional/tests/network/v2/test_floating_ip.py diff --git a/functional/tests/network/v2/test_floating_ip.py b/functional/tests/network/v2/test_floating_ip.py new file mode 100644 index 0000000000..f9ecd92823 --- /dev/null +++ b/functional/tests/network/v2/test_floating_ip.py @@ -0,0 +1,58 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import uuid + +from functional.common import test + + +class FloatingIpTests(test.TestCase): + """Functional tests for floating ip. """ + SUBNET_NAME = uuid.uuid4().hex + NETWORK_NAME = uuid.uuid4().hex + ID = None + HEADERS = ['ID'] + FIELDS = ['id'] + + @classmethod + def setUpClass(cls): + # Create a network for the floating ip. + cls.openstack('network create --external ' + cls.NETWORK_NAME) + # Create a subnet for the network. + cls.openstack( + 'subnet create --network ' + cls.NETWORK_NAME + + ' --subnet-range 10.10.10.0/24 ' + + cls.SUBNET_NAME + ) + opts = cls.get_show_opts(cls.FIELDS) + raw_output = cls.openstack( + 'ip floating create ' + cls.NETWORK_NAME + opts) + cls.ID = raw_output.strip('\n') + + @classmethod + def tearDownClass(cls): + raw_output = cls.openstack('ip floating delete ' + cls.ID) + cls.assertOutput('', raw_output) + raw_output = cls.openstack('subnet delete ' + cls.SUBNET_NAME) + cls.assertOutput('', raw_output) + raw_output = cls.openstack('network delete ' + cls.NETWORK_NAME) + cls.assertOutput('', raw_output) + + def test_floating_ip_list(self): + opts = self.get_list_opts(self.HEADERS) + raw_output = self.openstack('ip floating list' + opts) + self.assertIn(self.ID, raw_output) + + def test_floating_ip_show(self): + opts = self.get_show_opts(self.FIELDS) + raw_output = self.openstack('ip floating show ' + self.ID + opts) + self.assertEqual(self.ID + "\n", raw_output) From 4524b3605fa4260a2f916421eebfc7a99b320e4b Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Thu, 28 Apr 2016 11:09:49 +0800 Subject: [PATCH 0848/3095] Fix error in flavor set/unset command In the "flavor set/unset" command,the "flavor" parameter can be a name but can not be a id of a flavor. I think we should find a flavor by using "utils.find_resource()" in these commands. Change-Id: I5836788f7ed18813f1ebde31bb808b7c3f932b80 Closes-Bug: #1575624 --- openstackclient/compute/v2/flavor.py | 6 ++++-- openstackclient/tests/compute/v2/test_flavor.py | 12 ++++++++---- releasenotes/notes/bug-1575624-87957ff60ad661a6.yaml | 5 +++++ 3 files changed, 17 insertions(+), 6 deletions(-) create mode 100644 releasenotes/notes/bug-1575624-87957ff60ad661a6.yaml diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 29e0e9d45e..04674614b5 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -239,7 +239,8 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): compute_client = self.app.client_manager.compute - flavor = compute_client.flavors.find(name=parsed_args.flavor) + flavor = utils.find_resource(compute_client.flavors, + parsed_args.flavor) flavor.set_keys(parsed_args.property) @@ -289,5 +290,6 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): compute_client = self.app.client_manager.compute - flavor = compute_client.flavors.find(name=parsed_args.flavor) + flavor = utils.find_resource(compute_client.flavors, + parsed_args.flavor) flavor.unset_keys(parsed_args.property) diff --git a/openstackclient/tests/compute/v2/test_flavor.py b/openstackclient/tests/compute/v2/test_flavor.py index 03ca880729..fa29111b34 100644 --- a/openstackclient/tests/compute/v2/test_flavor.py +++ b/openstackclient/tests/compute/v2/test_flavor.py @@ -288,8 +288,10 @@ def test_flavor_set(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - - self.flavors_mock.find.assert_called_with(name='baremetal') + try: + self.flavors_mock.find.assert_called_with(name=parsed_args.flavor) + except Exception: + self.flavors_mock.get.assert_called_with(parsed_args.flavor) self.assertIsNone(result) @@ -382,6 +384,8 @@ def test_flavor_unset(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - - self.flavors_mock.find.assert_called_with(name='baremetal') + try: + self.flavors_mock.find.assert_called_with(name=parsed_args.flavor) + except Exception: + self.flavors_mock.get.assert_called_with(parsed_args.flavor) self.assertIsNone(result) diff --git a/releasenotes/notes/bug-1575624-87957ff60ad661a6.yaml b/releasenotes/notes/bug-1575624-87957ff60ad661a6.yaml new file mode 100644 index 0000000000..950022020d --- /dev/null +++ b/releasenotes/notes/bug-1575624-87957ff60ad661a6.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - Fixed ``flavor set/unset`` command to properly find a + flavor to be set/unset by flavor id. + [Bug `1575624 `_] From 505659b0aabe95ee659370fab92e8cb7bd9dc430 Mon Sep 17 00:00:00 2001 From: Michael McCune Date: Thu, 28 Apr 2016 11:38:38 -0400 Subject: [PATCH 0849/3095] add a bandit environment to tox This change is being proposed as part of the OpenStack Security Project working session at the Austin 2016 summit. It adds support for running the bandit[1] security linting tool against the python-openstackclient codebase. This change adds a targetted environment for bandit and also adds bandit as part of the pep8 job. The bandit configuration has been tailored to exclude tests that are currently producing warning against the codebase. These issues will be followed up with bug reports and patches. [1]: https://wiki.openstack.org/wiki/Security/Projects/Bandit Depends-On: Iccd81c17e84df03d249c1012277dad9cb68c5845 Change-Id: I691829c1224557d1d239c9f665ac539d0f13c4d3 --- test-requirements.txt | 1 + tox.ini | 33 +++++++++++++++++++++++++++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/test-requirements.txt b/test-requirements.txt index 5694550a8a..26de9fd22e 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -17,6 +17,7 @@ testrepository>=0.0.18 # Apache-2.0/BSD testtools>=1.4.0 # MIT tempest-lib>=0.14.0 # Apache-2.0 osprofiler>=1.3.0 # Apache-2.0 +bandit>=1.0.1 # Apache-2.0 # Install these to generate sphinx autodocs python-barbicanclient>=4.0.0 # Apache-2.0 diff --git a/tox.ini b/tox.ini index 15f623636d..ced4dc95f5 100644 --- a/tox.ini +++ b/tox.ini @@ -12,7 +12,36 @@ commands = ostestr {posargs} whitelist_externals = ostestr [testenv:pep8] -commands = flake8 +commands = + flake8 + bandit -r openstackclient -x tests -s B105,B106,B107,B401,B404,B603,B606,B607,B110,B605,B101 + +[testenv:bandit] +# This command runs the bandit security linter against the openstackclient +# codebase minus the tests directory. Some tests are being excluded to +# reduce the number of positives before a team inspection, and to ensure a +# passing gate job for initial addition. The excluded tests are: +# B105-B107: hardcoded password checks - likely to generate false positives +# in a gate environment +# B401: import subprocess - not necessarily a security issue; this plugin is +# mainly used for penetration testing workflow +# B603,B606: process without shell - not necessarily a security issue; this +# plugin is mainly used for penetration testing workflow +# B607: start process with a partial path - this should be a project level +# decision +# NOTE(elmiko): The following tests are being excluded specifically for +# python-openstackclient, they are being excluded to ensure that voting jobs +# in the project and in bandit integration tests continue to pass. These +# tests have generated issue within the project and should be investigated +# by the project. +# B110: try, except, pass detected - possible security issue; this should be +# investigated by the project for possible exploitation +# B605: process with a shell - possible security issue; this should be +# investigated by the project for possible exploitation +# B101: use of assert - this code will be removed when compiling to optimized +# byte code +commands = + bandit -r openstackclient -x tests -s B105,B106,B107,B401,B404,B603,B606,B607,B110,B605,B101 [testenv:functional] setenv = OS_TEST_PATH=./functional/tests @@ -40,4 +69,4 @@ show-source = True exclude = .git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build,tools # If 'ignore' is not set there are default errors and warnings that are set # Doc: http://flake8.readthedocs.org/en/latest/config.html#default -ignore = __ \ No newline at end of file +ignore = __ From 9ec41c0397dbcf818513efd9bc92ad488b66ceca Mon Sep 17 00:00:00 2001 From: Daniel Gonzalez Date: Thu, 28 Apr 2016 15:15:12 -0500 Subject: [PATCH 0850/3095] Replace tempest-lib with tempest.lib tempest-lib is deprecated, so replace it with tempest.lib. Co-Authored-By: Sheel Rana Change-Id: I0495eba110bb7581623fbcf49dc63a27e9cb6d64 Closes-Bug: #1553047 --- functional/common/test.py | 4 ++-- functional/tests/compute/v2/test_keypair.py | 4 ++-- functional/tests/compute/v2/test_server.py | 4 ++-- functional/tests/identity/v2/test_identity.py | 2 +- functional/tests/identity/v2/test_project.py | 2 +- functional/tests/identity/v2/test_user.py | 4 ++-- functional/tests/identity/v3/test_domain.py | 4 ++-- functional/tests/identity/v3/test_endpoint.py | 2 +- functional/tests/identity/v3/test_group.py | 2 +- functional/tests/identity/v3/test_identity.py | 2 +- functional/tests/identity/v3/test_idp.py | 2 +- functional/tests/identity/v3/test_project.py | 2 +- functional/tests/identity/v3/test_role.py | 2 +- functional/tests/identity/v3/test_service.py | 2 +- functional/tests/identity/v3/test_service_provider.py | 2 +- functional/tests/identity/v3/test_user.py | 2 +- test-requirements.txt | 2 +- 17 files changed, 22 insertions(+), 22 deletions(-) diff --git a/functional/common/test.py b/functional/common/test.py index 9887c76570..436156151e 100644 --- a/functional/common/test.py +++ b/functional/common/test.py @@ -17,8 +17,8 @@ import testtools import six -from tempest_lib.cli import output_parser -from tempest_lib import exceptions +from tempest.lib.cli import output_parser +from tempest.lib import exceptions COMMON_DIR = os.path.dirname(os.path.abspath(__file__)) diff --git a/functional/tests/compute/v2/test_keypair.py b/functional/tests/compute/v2/test_keypair.py index 80cd4b464b..6bc5cdb7c1 100644 --- a/functional/tests/compute/v2/test_keypair.py +++ b/functional/tests/compute/v2/test_keypair.py @@ -14,8 +14,8 @@ from functional.common import test -from tempest_lib.common.utils import data_utils -from tempest_lib import exceptions +from tempest.lib.common.utils import data_utils +from tempest.lib import exceptions class KeypairBase(test.TestCase): diff --git a/functional/tests/compute/v2/test_server.py b/functional/tests/compute/v2/test_server.py index 198bd56b2f..4309aeaa53 100644 --- a/functional/tests/compute/v2/test_server.py +++ b/functional/tests/compute/v2/test_server.py @@ -12,11 +12,11 @@ import time -from tempest_lib.common.utils import data_utils +from tempest.lib.common.utils import data_utils import testtools from functional.common import test -from tempest_lib import exceptions +from tempest.lib import exceptions class ServerTests(test.TestCase): diff --git a/functional/tests/identity/v2/test_identity.py b/functional/tests/identity/v2/test_identity.py index 4346499ce6..9adbe49f4f 100644 --- a/functional/tests/identity/v2/test_identity.py +++ b/functional/tests/identity/v2/test_identity.py @@ -10,7 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. -from tempest_lib.common.utils import data_utils +from tempest.lib.common.utils import data_utils from functional.common import test diff --git a/functional/tests/identity/v2/test_project.py b/functional/tests/identity/v2/test_project.py index 3a5e8e81a9..e9580ecfb6 100644 --- a/functional/tests/identity/v2/test_project.py +++ b/functional/tests/identity/v2/test_project.py @@ -10,7 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. -from tempest_lib.common.utils import data_utils +from tempest.lib.common.utils import data_utils from functional.tests.identity.v2 import test_identity diff --git a/functional/tests/identity/v2/test_user.py b/functional/tests/identity/v2/test_user.py index a0c1a46b86..34cabf7bf4 100644 --- a/functional/tests/identity/v2/test_user.py +++ b/functional/tests/identity/v2/test_user.py @@ -10,8 +10,8 @@ # License for the specific language governing permissions and limitations # under the License. -from tempest_lib.common.utils import data_utils -from tempest_lib import exceptions +from tempest.lib.common.utils import data_utils +from tempest.lib import exceptions from functional.tests.identity.v2 import test_identity diff --git a/functional/tests/identity/v3/test_domain.py b/functional/tests/identity/v3/test_domain.py index 221efd6a27..0708e4200d 100644 --- a/functional/tests/identity/v3/test_domain.py +++ b/functional/tests/identity/v3/test_domain.py @@ -10,8 +10,8 @@ # License for the specific language governing permissions and limitations # under the License. -from tempest_lib.common.utils import data_utils -from tempest_lib import exceptions +from tempest.lib.common.utils import data_utils +from tempest.lib import exceptions from functional.tests.identity.v3 import test_identity diff --git a/functional/tests/identity/v3/test_endpoint.py b/functional/tests/identity/v3/test_endpoint.py index e68aa848da..ec11deab30 100644 --- a/functional/tests/identity/v3/test_endpoint.py +++ b/functional/tests/identity/v3/test_endpoint.py @@ -10,7 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. -from tempest_lib.common.utils import data_utils +from tempest.lib.common.utils import data_utils from functional.tests.identity.v3 import test_identity diff --git a/functional/tests/identity/v3/test_group.py b/functional/tests/identity/v3/test_group.py index 156a9ff1e8..3f58864dc2 100644 --- a/functional/tests/identity/v3/test_group.py +++ b/functional/tests/identity/v3/test_group.py @@ -10,7 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. -from tempest_lib.common.utils import data_utils +from tempest.lib.common.utils import data_utils from functional.tests.identity.v3 import test_identity diff --git a/functional/tests/identity/v3/test_identity.py b/functional/tests/identity/v3/test_identity.py index b8c652abc6..3f9887428b 100644 --- a/functional/tests/identity/v3/test_identity.py +++ b/functional/tests/identity/v3/test_identity.py @@ -12,7 +12,7 @@ import os -from tempest_lib.common.utils import data_utils +from tempest.lib.common.utils import data_utils from functional.common import test diff --git a/functional/tests/identity/v3/test_idp.py b/functional/tests/identity/v3/test_idp.py index 3d6739d7f4..08f660f66d 100644 --- a/functional/tests/identity/v3/test_idp.py +++ b/functional/tests/identity/v3/test_idp.py @@ -11,7 +11,7 @@ # under the License. from functional.tests.identity.v3 import test_identity -from tempest_lib.common.utils import data_utils +from tempest.lib.common.utils import data_utils class IdentityProviderTests(test_identity.IdentityTests): diff --git a/functional/tests/identity/v3/test_project.py b/functional/tests/identity/v3/test_project.py index 6c278691f4..d060c7b1a1 100644 --- a/functional/tests/identity/v3/test_project.py +++ b/functional/tests/identity/v3/test_project.py @@ -10,7 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. -from tempest_lib.common.utils import data_utils +from tempest.lib.common.utils import data_utils from functional.tests.identity.v3 import test_identity diff --git a/functional/tests/identity/v3/test_role.py b/functional/tests/identity/v3/test_role.py index 5f150b052d..29dc4b2005 100644 --- a/functional/tests/identity/v3/test_role.py +++ b/functional/tests/identity/v3/test_role.py @@ -10,7 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. -from tempest_lib.common.utils import data_utils +from tempest.lib.common.utils import data_utils from functional.tests.identity.v3 import test_identity diff --git a/functional/tests/identity/v3/test_service.py b/functional/tests/identity/v3/test_service.py index 147208a2bc..684fa5fea8 100644 --- a/functional/tests/identity/v3/test_service.py +++ b/functional/tests/identity/v3/test_service.py @@ -10,7 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. -from tempest_lib.common.utils import data_utils +from tempest.lib.common.utils import data_utils from functional.tests.identity.v3 import test_identity diff --git a/functional/tests/identity/v3/test_service_provider.py b/functional/tests/identity/v3/test_service_provider.py index eed9fccb72..936c6620d6 100644 --- a/functional/tests/identity/v3/test_service_provider.py +++ b/functional/tests/identity/v3/test_service_provider.py @@ -11,7 +11,7 @@ # under the License. from functional.tests.identity.v3 import test_identity -from tempest_lib.common.utils import data_utils +from tempest.lib.common.utils import data_utils class ServiceProviderTests(test_identity.IdentityTests): diff --git a/functional/tests/identity/v3/test_user.py b/functional/tests/identity/v3/test_user.py index 00b9bdc2fc..cc3e08a0b3 100644 --- a/functional/tests/identity/v3/test_user.py +++ b/functional/tests/identity/v3/test_user.py @@ -10,7 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. -from tempest_lib.common.utils import data_utils +from tempest.lib.common.utils import data_utils from functional.tests.identity.v3 import test_identity diff --git a/test-requirements.txt b/test-requirements.txt index 5694550a8a..f39b86bb60 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -15,7 +15,7 @@ sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 # BSD os-testr>=0.4.1 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD testtools>=1.4.0 # MIT -tempest-lib>=0.14.0 # Apache-2.0 +tempest>=11.0.0 # Apache-2.0 osprofiler>=1.3.0 # Apache-2.0 # Install these to generate sphinx autodocs From 166a7b8a41091cf19c8d7669dd1e808322749ee3 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Sat, 30 Apr 2016 09:06:55 -0500 Subject: [PATCH 0851/3095] Documentation updates * Update link to meeting information Change-Id: I8dc6044b1faaa411e3b8d3b93fb401a480e82348 --- doc/source/developing.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/developing.rst b/doc/source/developing.rst index 9f8e55ca2a..2ddaeb2cba 100644 --- a/doc/source/developing.rst +++ b/doc/source/developing.rst @@ -8,9 +8,9 @@ Communication Meetings ========= The OpenStackClient team meets regularly on every Thursday. For details -please refer to the `wiki`_. +please refer to the `OpenStack IRC meetings`_ page. -.. _`wiki`: https://wiki.openstack.org/wiki/Meetings/OpenStackClient +.. _`OpenStack IRC meetings`: http://eavesdrop.openstack.org/#OpenStackClient_Team_Meeting Testing ------- From 681d6dc2de83ef13b4fb2fb4abe70f3c1ccb0e10 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Mon, 2 May 2016 16:30:29 +0800 Subject: [PATCH 0852/3095] Make "flavor show" command to show a private flavor properly The "flavor show" command could not show a private flavor by flavor name becauce it could not find a private flavor by flavor name. In "until.find_resource(parsed_args.flavor)", If parsed_args.falvor is a name of a flavor, "flavors.find(name=parsed_args.flavor)"will be called to find a flavor.But the default value of "is_public" is "Ture" in "flavors.find()" so that we can only find public flavors.If we want to find all flaovrs by flavor name,we should add "is_public=None" in "flavors.find()". So I tried to change "until.find_resource(parsed_args.flavor)" to "until.find_resource(parsed_args.flavor, is_public=None)", but then I could not find any flavor by flavor id because "is_public" is an unexpected argument of "flavors.get()" in "until.find_resource()". In this case,I think "until.find_resource()" can not find a private flavor properly,and we should combine "manager.get(flavor.id)" and "manager.find(name=flavor.name, is_public=None)" by ourselve to find a flavor. Also,this bug affects other flavor commands like "flavor set/unset/delete",so I fix them in this patch too. Change-Id: I4a4ed7b0a2f522ee04d1c3270afcda7064285c39 Closes-Bug: #1575478 --- openstackclient/compute/v2/flavor.py | 31 ++++++++++++++----- .../tests/compute/v2/test_flavor.py | 22 ++++++------- .../notes/bug-1575478-5a0a923c3a32f96a.yaml | 5 +++ 3 files changed, 37 insertions(+), 21 deletions(-) create mode 100644 releasenotes/notes/bug-1575478-5a0a923c3a32f96a.yaml diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 04674614b5..4b918369f2 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -18,10 +18,29 @@ import six from openstackclient.common import command +from openstackclient.common import exceptions from openstackclient.common import parseractions from openstackclient.common import utils +def _find_flavor(compute_client, flavor): + try: + return compute_client.flavors.get(flavor) + except Exception as ex: + if type(ex).__name__ == 'NotFound': + pass + else: + raise + try: + return compute_client.flavors.find(name=flavor, is_public=None) + except Exception as ex: + if type(ex).__name__ == 'NotFound': + msg = "No flavor with a name or ID of '%s' exists." % flavor + raise exceptions.CommandError(msg) + else: + raise + + class CreateFlavor(command.ShowOne): """Create new flavor""" @@ -132,8 +151,7 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): compute_client = self.app.client_manager.compute - flavor = utils.find_resource(compute_client.flavors, - parsed_args.flavor) + flavor = _find_flavor(compute_client, parsed_args.flavor) compute_client.flavors.delete(flavor.id) @@ -239,8 +257,7 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): compute_client = self.app.client_manager.compute - flavor = utils.find_resource(compute_client.flavors, - parsed_args.flavor) + flavor = _find_flavor(compute_client, parsed_args.flavor) flavor.set_keys(parsed_args.property) @@ -258,8 +275,7 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): compute_client = self.app.client_manager.compute - resource_flavor = utils.find_resource(compute_client.flavors, - parsed_args.flavor) + resource_flavor = _find_flavor(compute_client, parsed_args.flavor) flavor = resource_flavor._info.copy() flavor.pop("links", None) @@ -290,6 +306,5 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): compute_client = self.app.client_manager.compute - flavor = utils.find_resource(compute_client.flavors, - parsed_args.flavor) + flavor = _find_flavor(compute_client, parsed_args.flavor) flavor.unset_keys(parsed_args.property) diff --git a/openstackclient/tests/compute/v2/test_flavor.py b/openstackclient/tests/compute/v2/test_flavor.py index fa29111b34..529a3c25b3 100644 --- a/openstackclient/tests/compute/v2/test_flavor.py +++ b/openstackclient/tests/compute/v2/test_flavor.py @@ -273,7 +273,7 @@ def setUp(self): super(TestFlavorSet, self).setUp() self.flavors_mock.find.return_value = self.flavor - + self.flavors_mock.get.side_effect = exceptions.NotFound(None) self.cmd = flavor.SetFlavor(self.app, None) def test_flavor_set(self): @@ -288,10 +288,8 @@ def test_flavor_set(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - try: - self.flavors_mock.find.assert_called_with(name=parsed_args.flavor) - except Exception: - self.flavors_mock.get.assert_called_with(parsed_args.flavor) + self.flavors_mock.find.assert_called_with(name=parsed_args.flavor, + is_public=None) self.assertIsNone(result) @@ -331,9 +329,9 @@ class TestFlavorShow(TestFlavor): def setUp(self): super(TestFlavorShow, self).setUp() - # Return value of utils.find_resource() - self.flavors_mock.get.return_value = self.flavor - + # Return value of _find_resource() + self.flavors_mock.find.return_value = self.flavor + self.flavors_mock.get.side_effect = exceptions.NotFound(None) self.cmd = flavor.ShowFlavor(self.app, None) def test_show_no_options(self): @@ -369,7 +367,7 @@ def setUp(self): super(TestFlavorUnset, self).setUp() self.flavors_mock.find.return_value = self.flavor - + self.flavors_mock.get.side_effect = exceptions.NotFound(None) self.cmd = flavor.UnsetFlavor(self.app, None) def test_flavor_unset(self): @@ -384,8 +382,6 @@ def test_flavor_unset(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - try: - self.flavors_mock.find.assert_called_with(name=parsed_args.flavor) - except Exception: - self.flavors_mock.get.assert_called_with(parsed_args.flavor) + self.flavors_mock.find.assert_called_with(name=parsed_args.flavor, + is_public=None) self.assertIsNone(result) diff --git a/releasenotes/notes/bug-1575478-5a0a923c3a32f96a.yaml b/releasenotes/notes/bug-1575478-5a0a923c3a32f96a.yaml new file mode 100644 index 0000000000..b043a0e751 --- /dev/null +++ b/releasenotes/notes/bug-1575478-5a0a923c3a32f96a.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - Fixed ``flavor show/delete/set/unset`` command to properly + find a private flavor by flavor name. + [Bug `1575478 `_] From dc3d3ea77beb73f0b8cfaa8ac66ed02e03508066 Mon Sep 17 00:00:00 2001 From: Ankur Gupta Date: Tue, 29 Mar 2016 10:51:06 -0600 Subject: [PATCH 0853/3095] Spec to Implement IP Availability Specification to lay out new commands to be added to OSC. New feature has been implemented and released for Neutron and python-neutronclient to display IP usages. Commands will display the IP usage across all networks, or detailed IP usage specifics for a given network Partially Implements Blueprint: neutron-ip-capacity Change-Id: I1c25de1bd924d92a96c20d60fa24d9e966b92dcd --- doc/source/specs/ip-availability.rst | 61 ++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 doc/source/specs/ip-availability.rst diff --git a/doc/source/specs/ip-availability.rst b/doc/source/specs/ip-availability.rst new file mode 100644 index 0000000000..cf0c71ff38 --- /dev/null +++ b/doc/source/specs/ip-availability.rst @@ -0,0 +1,61 @@ +=============== +ip availability +=============== + +Network v2 + +ip availability list +-------------------- + +List IP availability for network + +This command retrieves information about IP availability. +Useful for admins who need a quick way to check the +IP availability for all associated networks. +List specifically returns total IP capacity and the +number of allocated IP addresses from that pool. + +.. program:: ip availability list +.. code:: bash + + os ip availability list + [--ip-version {4,6}] + [--project ] + +.. option:: --ip-version {4,6} + + List IP availability for specific version + (Default is 4) + +.. option:: --project + + List IP availability for specific project + (name or ID) + +ip availability show +-------------------- + +Show network IP availability details + +This command retrieves information about IP availability. +Useful for admins who need a quick way to +check the IP availability and details for a +specific network. + +This command will return information about +IP availability for the network as a whole, and +return availability information for each individual +subnet within the network as well. + + +.. program:: ip availability show +.. code:: bash + + os ip availability show + + +.. _ip_availability_show-network +.. describe:: + + Show network IP availability for specific + network (name or ID) From b33ee3daf6eebc74fb176b1f2d8018e0e2214377 Mon Sep 17 00:00:00 2001 From: Michael McCune Date: Thu, 28 Apr 2016 15:15:57 -0400 Subject: [PATCH 0854/3095] remove assert in favor an if/else the assert usage in the NonNegativeAction has the potential to allow unexpected behavior when the python is byte-compiled with optimization turned on. Changes * remove assert in favor of if/else in NonNegativeAction class * add type specifier to parser arguments for non-negative actions * correct tests for new int based values Change-Id: I093e7440b8beff4f179e2c4ed81daff82704c40e Closes-Bug: #1576375 --- openstackclient/common/parseractions.py | 5 ++-- openstackclient/network/v2/subnet_pool.py | 3 +++ .../tests/network/v2/test_subnet_pool.py | 25 ++++++++++--------- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/openstackclient/common/parseractions.py b/openstackclient/common/parseractions.py index c30058c8e7..77798f9024 100644 --- a/openstackclient/common/parseractions.py +++ b/openstackclient/common/parseractions.py @@ -155,9 +155,8 @@ class NonNegativeAction(argparse.Action): """ def __call__(self, parser, namespace, values, option_string=None): - try: - assert(int(values) >= 0) + if int(values) >= 0: setattr(namespace, self.dest, values) - except Exception: + else: msg = "%s expected a non-negative integer" % (str(option_string)) raise argparse.ArgumentTypeError(msg) diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index 435db2e116..f1174dda8c 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -91,6 +91,7 @@ def _add_prefix_options(parser, for_create=False): parser.add_argument( '--default-prefix-length', metavar='', + type=int, action=parseractions.NonNegativeAction, help=_("Set subnet pool default prefix length") ) @@ -98,11 +99,13 @@ def _add_prefix_options(parser, for_create=False): '--min-prefix-length', metavar='', action=parseractions.NonNegativeAction, + type=int, help=_("Set subnet pool minimum prefix length") ) parser.add_argument( '--max-prefix-length', metavar='', + type=int, action=parseractions.NonNegativeAction, help=_("Set subnet pool maximum prefix length") ) diff --git a/openstackclient/tests/network/v2/test_subnet_pool.py b/openstackclient/tests/network/v2/test_subnet_pool.py index b40390a143..7797e4d053 100644 --- a/openstackclient/tests/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/network/v2/test_subnet_pool.py @@ -153,9 +153,10 @@ def test_create_prefixlen_options(self): self._subnet_pool.name, ] verifylist = [ - ('default_prefix_length', self._subnet_pool.default_prefixlen), - ('max_prefix_length', self._subnet_pool.max_prefixlen), - ('min_prefix_length', self._subnet_pool.min_prefixlen), + ('default_prefix_length', + int(self._subnet_pool.default_prefixlen)), + ('max_prefix_length', int(self._subnet_pool.max_prefixlen)), + ('min_prefix_length', int(self._subnet_pool.min_prefixlen)), ('name', self._subnet_pool.name), ('prefixes', ['10.0.10.0/24']), ] @@ -164,9 +165,9 @@ def test_create_prefixlen_options(self): columns, data = (self.cmd.take_action(parsed_args)) self.network.create_subnet_pool.assert_called_once_with(**{ - 'default_prefixlen': self._subnet_pool.default_prefixlen, - 'max_prefixlen': self._subnet_pool.max_prefixlen, - 'min_prefixlen': self._subnet_pool.min_prefixlen, + 'default_prefixlen': int(self._subnet_pool.default_prefixlen), + 'max_prefixlen': int(self._subnet_pool.max_prefixlen), + 'min_prefixlen': int(self._subnet_pool.min_prefixlen), 'prefixes': ['10.0.10.0/24'], 'name': self._subnet_pool.name, }) @@ -397,8 +398,8 @@ def test_set_this(self): ] verifylist = [ ('name', 'noob'), - ('default_prefix_length', '8'), - ('min_prefix_length', '8'), + ('default_prefix_length', 8), + ('min_prefix_length', 8), ('subnet_pool', self._subnet_pool.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -407,8 +408,8 @@ def test_set_this(self): attrs = { 'name': 'noob', - 'default_prefixlen': '8', - 'min_prefixlen': '8', + 'default_prefixlen': 8, + 'min_prefixlen': 8, } self.network.update_subnet_pool.assert_called_once_with( self._subnet_pool, **attrs) @@ -423,7 +424,7 @@ def test_set_that(self): ] verifylist = [ ('prefixes', ['10.0.1.0/24', '10.0.2.0/24']), - ('max_prefix_length', '16'), + ('max_prefix_length', 16), ('subnet_pool', self._subnet_pool.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -434,7 +435,7 @@ def test_set_that(self): prefixes.extend(self._subnet_pool.prefixes) attrs = { 'prefixes': prefixes, - 'max_prefixlen': '16', + 'max_prefixlen': 16, } self.network.update_subnet_pool.assert_called_once_with( self._subnet_pool, **attrs) From 403e6cad5e110362cf529cb8b27e6cbb6f5e4d00 Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Thu, 28 Apr 2016 11:55:32 +0800 Subject: [PATCH 0855/3095] Add describe of overwrite options behavior into devref Update the devref to add the describe and code example about overwrite options behavior. Change-Id: I65e9a3a30acf8d427906096bde24fa8b4c3ac3f7 Implements: blueprint allow-overwrite-set-options --- doc/source/command-options.rst | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/doc/source/command-options.rst b/doc/source/command-options.rst index d47a56dc23..5cb84684c9 100644 --- a/doc/source/command-options.rst +++ b/doc/source/command-options.rst @@ -112,8 +112,26 @@ Some options can be repeated to build a collection of values for a property. Adding a value to the collection must be provided via the ``set`` action. Removing a value from the collection must be provided via an ``unset`` action. As a convenience, removing all values from the collection may be provided via a -``--no`` option on the ``set`` and ``unset`` actions. The ``--no`` option must -be part of a mutually exclusive group with the related property option. +``--no`` option on the ``set`` and ``unset`` actions. If both ``--no`` option +and option are specified, the values specified on the command would overwrite +the collection property instead of appending on the ``set`` action. The +``--no`` option must be part of a mutually exclusive group with the related +property option on the ``unset`` action, overwrite case don't exist in +``unset`` action. + +An example behavior for ``set`` action: + +Append: + +.. code-block:: bash + + object set --example-property xxx + +Overwrite: + +.. code-block:: bash + + object set --no-example-property --example-property xxx The example below assumes a property that contains a list of unique values. However, this example can also be applied to other collections using the @@ -129,8 +147,7 @@ An example parser declaration for `set` action: .. code-block:: python - example_property_group = parser.add_mutually_exclusive_group() - example_property_group.add_argument( + parser.add_argument( '--example-property', metavar='', dest='example_property', @@ -138,7 +155,7 @@ An example parser declaration for `set` action: help=_('Example property for this ' '(repeat option to set multiple properties)'), ) - example_property_group.add_argument( + parser.add_argument( '--no-example-property', dest='no_example_property', action='store_true', @@ -149,10 +166,12 @@ An example handler in `take_action()` for `set` action: .. code-block:: python - if parsed_args.example_property: + if parsed_args.example_property and parsed_args.no_example_property: + kwargs['example_property'] = parsed_args.example_property + elif parsed_args.example_property: kwargs['example_property'] = \ resource_example_property + parsed_args.example_property - if parsed_args.no_example_property: + elif parsed_args.no_example_property: kwargs['example_property'] = [] An example parser declaration for `unset` action: From 68224eafc3967fcb843eae0cd6e2b67d46821018 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Wed, 4 May 2016 18:55:49 +0800 Subject: [PATCH 0856/3095] Add a unit test for "flavor create" command There was not a unit test for "flavor create" command in the "test_flavor.py".So I add the unit test. Change-Id: Ib1e821ea524eb33c0ba73643164228c7b83253b4 --- openstackclient/tests/compute/v2/fakes.py | 4 +- .../tests/compute/v2/test_flavor.py | 160 ++++++++++++++++++ 2 files changed, 162 insertions(+), 2 deletions(-) diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 948d9e9740..23957738a3 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -543,8 +543,8 @@ def create_one_flavor(attrs=None): 'ram': 8192, 'vcpus': 4, 'disk': 128, - 'swap': '', - 'rxtx_factor': '1.0', + 'swap': 0, + 'rxtx_factor': 1.0, 'OS-FLV-DISABLED:disabled': False, 'os-flavor-access:is_public': True, 'OS-FLV-EXT-DATA:ephemeral': 0, diff --git a/openstackclient/tests/compute/v2/test_flavor.py b/openstackclient/tests/compute/v2/test_flavor.py index fa29111b34..6d8dc40bd5 100644 --- a/openstackclient/tests/compute/v2/test_flavor.py +++ b/openstackclient/tests/compute/v2/test_flavor.py @@ -30,6 +30,166 @@ def setUp(self): self.flavors_mock.reset_mock() +class TestFlavorCreate(TestFlavor): + + flavor = compute_fakes.FakeFlavor.create_one_flavor( + attrs={'links': 'flavor-links'}) + + columns = ( + 'OS-FLV-DISABLED:disabled', + 'OS-FLV-EXT-DATA:ephemeral', + 'disk', + 'id', + 'name', + 'os-flavor-access:is_public', + 'ram', + 'rxtx_factor', + 'swap', + 'vcpus', + ) + data = ( + flavor.disabled, + flavor.ephemeral, + flavor.disk, + flavor.id, + flavor.name, + flavor.is_public, + flavor.ram, + flavor.rxtx_factor, + flavor.swap, + flavor.vcpus, + ) + + def setUp(self): + super(TestFlavorCreate, self).setUp() + + self.flavors_mock.create.return_value = self.flavor + self.cmd = flavor.CreateFlavor(self.app, None) + + def test_flavor_create_default_options(self): + + arglist = [ + self.flavor.name + ] + verifylist = [ + ('name', self.flavor.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + default_args = ( + self.flavor.name, + 256, + 1, + 0, + 'auto', + 0, + 0, + 1.0, + True + ) + columns, data = self.cmd.take_action(parsed_args) + self.flavors_mock.create.assert_called_once_with(*default_args) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_flavor_create_all_options(self): + + arglist = [ + self.flavor.name, + '--id', self.flavor.id, + '--ram', str(self.flavor.ram), + '--disk', str(self.flavor.disk), + '--ephemeral', str(self.flavor.ephemeral), + '--swap', str(self.flavor.swap), + '--vcpus', str(self.flavor.vcpus), + '--rxtx-factor', str(self.flavor.rxtx_factor), + '--public', + ] + verifylist = [ + ('name', self.flavor.name), + ('id', self.flavor.id), + ('ram', self.flavor.ram), + ('disk', self.flavor.disk), + ('ephemeral', self.flavor.ephemeral), + ('swap', self.flavor.swap), + ('vcpus', self.flavor.vcpus), + ('rxtx_factor', self.flavor.rxtx_factor), + ('public', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + args = ( + self.flavor.name, + self.flavor.ram, + self.flavor.vcpus, + self.flavor.disk, + self.flavor.id, + self.flavor.ephemeral, + self.flavor.swap, + self.flavor.rxtx_factor, + self.flavor.is_public, + ) + columns, data = self.cmd.take_action(parsed_args) + self.flavors_mock.create.assert_called_once_with(*args) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_flavor_create_other_options(self): + + self.flavor.is_public = False + arglist = [ + self.flavor.name, + '--id', self.flavor.id, + '--ram', str(self.flavor.ram), + '--disk', str(self.flavor.disk), + '--ephemeral', str(self.flavor.ephemeral), + '--swap', str(self.flavor.swap), + '--vcpus', str(self.flavor.vcpus), + '--rxtx-factor', str(self.flavor.rxtx_factor), + '--private', + ] + verifylist = [ + ('name', self.flavor.name), + ('id', self.flavor.id), + ('ram', self.flavor.ram), + ('disk', self.flavor.disk), + ('ephemeral', self.flavor.ephemeral), + ('swap', self.flavor.swap), + ('vcpus', self.flavor.vcpus), + ('rxtx_factor', self.flavor.rxtx_factor), + ('public', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + args = ( + self.flavor.name, + self.flavor.ram, + self.flavor.vcpus, + self.flavor.disk, + self.flavor.id, + self.flavor.ephemeral, + self.flavor.swap, + self.flavor.rxtx_factor, + self.flavor.is_public, + ) + columns, data = self.cmd.take_action(parsed_args) + self.flavors_mock.create.assert_called_once_with(*args) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_flavor_create_no_options(self): + arglist = [] + verifylist = None + self.assertRaises(tests_utils.ParserException, + self.check_parser, + self.cmd, + arglist, + verifylist) + + class TestFlavorDelete(TestFlavor): flavor = compute_fakes.FakeFlavor.create_one_flavor() From f924fa39e149e9989c201a14cc331d5842f2cd54 Mon Sep 17 00:00:00 2001 From: "sharat.sharma" Date: Wed, 4 May 2016 14:50:51 +0530 Subject: [PATCH 0857/3095] Added "name" parameter to the help message. In OS_IDENTITY_API_VERSION=3, the "openstack help service delete" allows the user to remove a service based on the name of the service. So, this patch includes name as positional argument. Change-Id: Iae7cb0a82af3cdd4d88e0ed2eb651abf9af30fd9 Closes-Bug: #1566909 --- openstackclient/identity/v3/service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/identity/v3/service.py b/openstackclient/identity/v3/service.py index 355583cc30..f43ada56e4 100644 --- a/openstackclient/identity/v3/service.py +++ b/openstackclient/identity/v3/service.py @@ -82,7 +82,7 @@ def get_parser(self, prog_name): parser.add_argument( 'service', metavar='', - help='Service to delete (type or ID)', + help='Service to delete (type, name or ID)', ) return parser From 461a203f2d0e653eabe9a023cfdbe90669ff0618 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Thu, 5 May 2016 09:19:41 -0700 Subject: [PATCH 0858/3095] bump timeout to prevent gate failures attempt to fix the transient gate failures by increasing the timeout Change-Id: I837652013f94b0d1ed3f4b40fe14ce5a47c687b6 --- functional/tests/compute/v2/test_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/functional/tests/compute/v2/test_server.py b/functional/tests/compute/v2/test_server.py index 4309aeaa53..247784e1fc 100644 --- a/functional/tests/compute/v2/test_server.py +++ b/functional/tests/compute/v2/test_server.py @@ -268,7 +268,7 @@ def test_server_reboot(self): self.assertEqual("", raw_output) self.wait_for_status("ACTIVE") - def wait_for_status(self, expected_status='ACTIVE', wait=600, interval=30): + def wait_for_status(self, expected_status='ACTIVE', wait=900, interval=30): """Wait until server reaches expected status.""" # TODO(thowe): Add a server wait command to osc failures = ['ERROR'] From 9f3fa5ee3bf87c47f7a38ef78f4022ac46b2f2f6 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Tue, 19 Apr 2016 14:42:01 -0500 Subject: [PATCH 0859/3095] Fix network router type display The OpenStack SDK maps the network "router:external" field to "is_router_external". However, OSC was using the incorrect mapping, "router_external". This caused OSC to display router type as "Internal" for all networks. Change-Id: Ifcd1349ab7c5881baee751936d076bf6aa058852 Closes-Bug: #1572228 --- openstackclient/network/v2/network.py | 7 ++----- openstackclient/tests/network/v2/fakes.py | 6 +++--- openstackclient/tests/network/v2/test_network.py | 14 +++++++------- .../notes/bug-1572228-03638a7adec5da8b.yaml | 6 ++++++ 4 files changed, 18 insertions(+), 15 deletions(-) create mode 100644 releasenotes/notes/bug-1572228-03638a7adec5da8b.yaml diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 4b77971a21..9fd7e28b8f 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -32,7 +32,7 @@ def _format_router_external(item): _formatters = { 'subnets': utils.format_list, 'admin_state_up': _format_admin_state, - 'router_external': _format_router_external, + 'router:external': _format_router_external, 'availability_zones': utils.format_list, 'availability_zone_hints': utils.format_list, } @@ -43,9 +43,6 @@ def _get_columns(item): if 'tenant_id' in columns: columns.remove('tenant_id') columns.append('project_id') - if 'router:external' in columns: - columns.remove('router:external') - columns.append('router_external') return tuple(sorted(columns)) @@ -290,7 +287,7 @@ def take_action_network(self, client, parsed_args): 'shared', 'subnets', 'provider_network_type', - 'router_external', + 'router:external', 'availability_zones', ) column_headers = ( diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 1989b515eb..97a7f84eb7 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -166,8 +166,7 @@ def create_one_network(attrs=None): :param Dictionary attrs: A dictionary with all attributes :return: - A FakeResource object, with id, name, admin_state_up, - router_external, status, subnets, tenant_id + A FakeResource object, with id, name, etc. """ attrs = attrs or {} @@ -181,7 +180,7 @@ def create_one_network(attrs=None): 'shared': False, 'subnets': ['a', 'b'], 'provider_network_type': 'vlan', - 'router_external': True, + 'router:external': True, 'availability_zones': [], 'availability_zone_hints': [], 'is_default': False, @@ -195,6 +194,7 @@ def create_one_network(attrs=None): # Set attributes with special mapping in OpenStack SDK. network.project_id = network_attrs['tenant_id'] + network.is_router_external = network_attrs['router:external'] return network diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index a1b0aec986..72dc7ac24c 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -55,7 +55,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): 'name', 'project_id', 'provider_network_type', - 'router_external', + 'router:external', 'shared', 'status', 'subnets', @@ -70,7 +70,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): _network.name, _network.project_id, _network.provider_network_type, - network._format_router_external(_network.router_external), + network._format_router_external(_network.is_router_external), _network.shared, _network.status, utils.format_list(_network.subnets), @@ -224,7 +224,7 @@ class TestCreateNetworkIdentityV2(TestNetwork): 'name', 'project_id', 'provider_network_type', - 'router_external', + 'router:external', 'shared', 'status', 'subnets', @@ -239,7 +239,7 @@ class TestCreateNetworkIdentityV2(TestNetwork): _network.name, _network.project_id, _network.provider_network_type, - network._format_router_external(_network.router_external), + network._format_router_external(_network.is_router_external), _network.shared, _network.status, utils.format_list(_network.subnets), @@ -391,7 +391,7 @@ class TestListNetwork(TestNetwork): net.shared, utils.format_list(net.subnets), net.provider_network_type, - network._format_router_external(net.router_external), + network._format_router_external(net.is_router_external), utils.format_list(net.availability_zones), )) @@ -566,7 +566,7 @@ class TestShowNetwork(TestNetwork): 'name', 'project_id', 'provider_network_type', - 'router_external', + 'router:external', 'shared', 'status', 'subnets', @@ -581,7 +581,7 @@ class TestShowNetwork(TestNetwork): _network.name, _network.project_id, _network.provider_network_type, - network._format_router_external(_network.router_external), + network._format_router_external(_network.is_router_external), _network.shared, _network.status, utils.format_list(_network.subnets), diff --git a/releasenotes/notes/bug-1572228-03638a7adec5da8b.yaml b/releasenotes/notes/bug-1572228-03638a7adec5da8b.yaml new file mode 100644 index 0000000000..9db0ac0b77 --- /dev/null +++ b/releasenotes/notes/bug-1572228-03638a7adec5da8b.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - Fixed ``network create``, ``network show`` and ``network list`` + commands to correctly display the router type in the + ``router:external`` and ``Router Type`` columns. + [Bug `1572228 `_] From 59de9c477cb91573372bd363dfd0fc72af461327 Mon Sep 17 00:00:00 2001 From: Hieu LE Date: Mon, 4 Apr 2016 18:19:50 +0700 Subject: [PATCH 0860/3095] Ignore domain related config when using with keystone v2 Currently, "/usr/bin/openstack --insecure token issue" fails when OS_AUTH_URL and OS_IDENTITY_API_VERSION indicate keystone v2 if OS_PROJECT_DOMAIN_NAME or OS_USER_DOMAIN_NAME are set. This patchset ignore domain related configs if using with keystone v2. Change-Id: If7eea2ed1a4877c60d055ed0114a5e5f31e282a0 Closes-bug: #1447704 --- openstackclient/common/clientmanager.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index 8b0fb921be..c29bf224a2 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -195,6 +195,18 @@ def setup_auth(self, required_scope=True): not self._auth_params.get('user_domain_name')): self._auth_params['user_domain_id'] = default_domain + # NOTE(hieulq): If USER_DOMAIN_NAME, USER_DOMAIN_ID, PROJECT_DOMAIN_ID + # or PROJECT_DOMAIN_NAME is present and API_VERSION is 2.0, then + # ignore all domain related configs. + if (self._api_version.get('identity') == '2.0' and + self.auth_plugin_name.endswith('password')): + LOG.warning("Ignoring domain related configs " + "because identity API version is 2.0") + domain_props = ['project_domain_name', 'project_domain_id', + 'user_domain_name', 'user_domain_id'] + for prop in domain_props: + self._auth_params.pop(prop, None) + # For compatibility until all clients can be updated if 'project_name' in self._auth_params: self._project_name = self._auth_params['project_name'] From f91685f391cce2699ba6e4f2577a84e12d590aba Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Thu, 5 May 2016 15:12:18 +0800 Subject: [PATCH 0861/3095] Remove unnecessary type conversions in network unit tests In some tests, when comparing the results data with the expected ones, many unnecessary type conversions are used. So remove them to clean up. Change-Id: I560ca78a3ab5e7b99087bfe1667de500f92c68de Partial-bug: #1550633 --- .../tests/network/v2/test_network.py | 6 +- .../tests/network/v2/test_router.py | 2 +- .../tests/network/v2/test_security_group.py | 78 ++++++++++--------- .../network/v2/test_security_group_rule.py | 10 +-- .../tests/network/v2/test_subnet.py | 2 +- 5 files changed, 52 insertions(+), 46 deletions(-) diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index a1b0aec986..9c521a23c3 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -617,8 +617,8 @@ def test_show_all_options(self): self.network.find_network.assert_called_once_with( self._network.name, ignore_missing=False) - self.assertEqual(tuple(self.columns), columns) - self.assertEqual(list(self.data), list(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) # Tests for Nova network @@ -931,5 +931,5 @@ def test_show_all_options(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.assertEqual(self.columns, tuple(columns)) + self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) diff --git a/openstackclient/tests/network/v2/test_router.py b/openstackclient/tests/network/v2/test_router.py index e3f32e4ae3..ead919b283 100644 --- a/openstackclient/tests/network/v2/test_router.py +++ b/openstackclient/tests/network/v2/test_router.py @@ -608,5 +608,5 @@ def test_show_all_options(self): self.network.find_router.assert_called_once_with( self._router.name, ignore_missing=False) - self.assertEqual(tuple(self.columns), columns) + self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) diff --git a/openstackclient/tests/network/v2/test_security_group.py b/openstackclient/tests/network/v2/test_security_group.py index dd6a3d416c..213367a4d5 100644 --- a/openstackclient/tests/network/v2/test_security_group.py +++ b/openstackclient/tests/network/v2/test_security_group.py @@ -114,7 +114,7 @@ def test_create_min_options(self): 'description': self._security_group.name, 'name': self._security_group.name, }) - self.assertEqual(tuple(self.columns), columns) + self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) def test_create_all_options(self): @@ -139,7 +139,7 @@ def test_create_all_options(self): 'name': self._security_group.name, 'tenant_id': identity_fakes.project_id, }) - self.assertEqual(tuple(self.columns), columns) + self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -296,28 +296,30 @@ def test_security_group_delete(self): class TestListSecurityGroupNetwork(TestSecurityGroupNetwork): # The security group to be listed. - _security_group = \ - network_fakes.FakeSecurityGroup.create_one_security_group() + _security_groups = \ + network_fakes.FakeSecurityGroup.create_security_groups(count=3) - expected_columns = ( + columns = ( 'ID', 'Name', 'Description', 'Project', ) - expected_data = (( - _security_group.id, - _security_group.name, - _security_group.description, - _security_group.tenant_id, - ),) + data = [] + for grp in _security_groups: + data.append(( + grp.id, + grp.name, + grp.description, + grp.tenant_id, + )) def setUp(self): super(TestListSecurityGroupNetwork, self).setUp() self.network.security_groups = mock.Mock( - return_value=[self._security_group]) + return_value=self._security_groups) # Get the command object to test self.cmd = security_group.ListSecurityGroup(self.app, self.namespace) @@ -332,8 +334,8 @@ def test_security_group_list_no_options(self): columns, data = self.cmd.take_action(parsed_args) self.network.security_groups.assert_called_once_with() - self.assertEqual(self.expected_columns, columns) - self.assertEqual(self.expected_data, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) def test_security_group_list_all_projects(self): arglist = [ @@ -347,45 +349,49 @@ def test_security_group_list_all_projects(self): columns, data = self.cmd.take_action(parsed_args) self.network.security_groups.assert_called_once_with() - self.assertEqual(self.expected_columns, columns) - self.assertEqual(self.expected_data, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) class TestListSecurityGroupCompute(TestSecurityGroupCompute): # The security group to be listed. - _security_group = \ - compute_fakes.FakeSecurityGroup.create_one_security_group() + _security_groups = \ + compute_fakes.FakeSecurityGroup.create_security_groups(count=3) - expected_columns = ( + columns = ( 'ID', 'Name', 'Description', ) - expected_columns_all_projects = ( + columns_all_projects = ( 'ID', 'Name', 'Description', 'Project', ) - expected_data = (( - _security_group.id, - _security_group.name, - _security_group.description, - ),) - expected_data_all_projects = (( - _security_group.id, - _security_group.name, - _security_group.description, - _security_group.tenant_id, - ),) + data = [] + for grp in _security_groups: + data.append(( + grp.id, + grp.name, + grp.description, + )) + data_all_projects = [] + for grp in _security_groups: + data_all_projects.append(( + grp.id, + grp.name, + grp.description, + grp.tenant_id, + )) def setUp(self): super(TestListSecurityGroupCompute, self).setUp() self.app.client_manager.network_endpoint_enabled = False - self.compute.security_groups.list.return_value = [self._security_group] + self.compute.security_groups.list.return_value = self._security_groups # Get the command object to test self.cmd = security_group.ListSecurityGroup(self.app, None) @@ -401,8 +407,8 @@ def test_security_group_list_no_options(self): kwargs = {'search_opts': {'all_tenants': False}} self.compute.security_groups.list.assert_called_once_with(**kwargs) - self.assertEqual(self.expected_columns, columns) - self.assertEqual(self.expected_data, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) def test_security_group_list_all_projects(self): arglist = [ @@ -417,8 +423,8 @@ def test_security_group_list_all_projects(self): kwargs = {'search_opts': {'all_tenants': True}} self.compute.security_groups.list.assert_called_once_with(**kwargs) - self.assertEqual(self.expected_columns_all_projects, columns) - self.assertEqual(self.expected_data_all_projects, tuple(data)) + self.assertEqual(self.columns_all_projects, columns) + self.assertEqual(self.data_all_projects, list(data)) class TestSetSecurityGroupNetwork(TestSecurityGroupNetwork): diff --git a/openstackclient/tests/network/v2/test_security_group_rule.py b/openstackclient/tests/network/v2/test_security_group_rule.py index df7414aa48..bd903f9e72 100644 --- a/openstackclient/tests/network/v2/test_security_group_rule.py +++ b/openstackclient/tests/network/v2/test_security_group_rule.py @@ -174,7 +174,7 @@ def test_create_default_rule(self): 'remote_ip_prefix': self._security_group_rule.remote_ip_prefix, 'security_group_id': self._security_group.id, }) - self.assertEqual(tuple(self.expected_columns), columns) + self.assertEqual(self.expected_columns, columns) self.assertEqual(self.expected_data, data) def test_create_source_group(self): @@ -209,7 +209,7 @@ def test_create_source_group(self): 'remote_group_id': self._security_group_rule.remote_group_id, 'security_group_id': self._security_group.id, }) - self.assertEqual(tuple(self.expected_columns), columns) + self.assertEqual(self.expected_columns, columns) self.assertEqual(self.expected_data, data) def test_create_source_ip(self): @@ -240,7 +240,7 @@ def test_create_source_ip(self): 'remote_ip_prefix': self._security_group_rule.remote_ip_prefix, 'security_group_id': self._security_group.id, }) - self.assertEqual(tuple(self.expected_columns), columns) + self.assertEqual(self.expected_columns, columns) self.assertEqual(self.expected_data, data) def test_create_network_options(self): @@ -282,7 +282,7 @@ def test_create_network_options(self): 'security_group_id': self._security_group.id, 'tenant_id': identity_fakes.project_id, }) - self.assertEqual(tuple(self.expected_columns), columns) + self.assertEqual(self.expected_columns, columns) self.assertEqual(self.expected_data, data) @@ -816,7 +816,7 @@ def test_show_all_options(self): self.network.find_security_group_rule.assert_called_once_with( self._security_group_rule.id, ignore_missing=False) - self.assertEqual(tuple(self.columns), columns) + self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) diff --git a/openstackclient/tests/network/v2/test_subnet.py b/openstackclient/tests/network/v2/test_subnet.py index ede3741615..1923286dc2 100644 --- a/openstackclient/tests/network/v2/test_subnet.py +++ b/openstackclient/tests/network/v2/test_subnet.py @@ -627,4 +627,4 @@ def test_show_all_options(self): self._subnet.name, ignore_missing=False) self.assertEqual(self.columns, columns) - self.assertEqual(list(self.data), list(data)) + self.assertEqual(self.data, data) From 15c9576817c5925ebf89835a2c35b7b7524d4e57 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Fri, 6 May 2016 19:55:50 +0800 Subject: [PATCH 0862/3095] Trivial: Remove unuseful comments for assertRaise() checking "Missing required args should bail here" is not understandable and not necessary. The code is obvious enough. And some of the comments are misused because of code copy. So remove them. Change-Id: I031395f2c882386c7a708db5cf4eee75393dc639 --- openstackclient/tests/compute/v2/test_server.py | 1 - openstackclient/tests/network/v2/test_floating_ip.py | 2 -- openstackclient/tests/network/v2/test_network.py | 3 --- openstackclient/tests/network/v2/test_router.py | 8 -------- openstackclient/tests/network/v2/test_subnet_pool.py | 2 -- 5 files changed, 16 deletions(-) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 17681672b5..2e74545746 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -146,7 +146,6 @@ def test_server_create_no_options(self): ('server_name', self.new_server.name), ] - # Missing required args should bail here self.assertRaises(utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/network/v2/test_floating_ip.py b/openstackclient/tests/network/v2/test_floating_ip.py index 3e261fb5ef..f9ccfe1c74 100644 --- a/openstackclient/tests/network/v2/test_floating_ip.py +++ b/openstackclient/tests/network/v2/test_floating_ip.py @@ -88,7 +88,6 @@ def test_create_no_options(self): arglist = [] verifylist = [] - # Missing required args should bail here self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) @@ -314,7 +313,6 @@ def test_create_no_options(self): arglist = [] verifylist = [] - # Missing required args should bail here self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index 9c521a23c3..1269b0a101 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -112,7 +112,6 @@ def test_create_no_options(self): arglist = [] verifylist = [] - # Missing required args should bail here self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) @@ -599,7 +598,6 @@ def test_show_no_options(self): arglist = [] verifylist = [] - # Missing required args should bail here self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) @@ -916,7 +914,6 @@ def test_show_no_options(self): arglist = [] verifylist = [] - # Missing required args should bail here self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/network/v2/test_router.py b/openstackclient/tests/network/v2/test_router.py index ead919b283..655e86c95e 100644 --- a/openstackclient/tests/network/v2/test_router.py +++ b/openstackclient/tests/network/v2/test_router.py @@ -47,7 +47,6 @@ def test_add_port_no_option(self): arglist = [] verifylist = [] - # Missing required args should bail here self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) @@ -88,7 +87,6 @@ def test_add_subnet_no_option(self): arglist = [] verifylist = [] - # Missing required args should bail here self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) @@ -154,7 +152,6 @@ def test_create_no_options(self): arglist = [] verifylist = [] - # Missing required args should bail here self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) @@ -335,7 +332,6 @@ def test_remove_port_no_option(self): arglist = [] verifylist = [] - # Missing required args should bail here self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) @@ -375,7 +371,6 @@ def test_remove_subnet_no_option(self): arglist = [] verifylist = [] - # Missing required args should bail here self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) @@ -475,7 +470,6 @@ def test_set_distributed_centralized(self): ('distributed', False), ] - # Missing required args should bail here self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) @@ -534,7 +528,6 @@ def test_set_route_clear_routes(self): ('clear_routes', True), ] - # Argument parse failing should bail here self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) @@ -591,7 +584,6 @@ def test_show_no_options(self): arglist = [] verifylist = [] - # Missing required args should bail here self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/network/v2/test_subnet_pool.py b/openstackclient/tests/network/v2/test_subnet_pool.py index 7797e4d053..de12c9e9df 100644 --- a/openstackclient/tests/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/network/v2/test_subnet_pool.py @@ -109,7 +109,6 @@ def test_create_no_options(self): arglist = [] verifylist = [] - # Missing required args should bail here self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) @@ -624,7 +623,6 @@ def test_show_no_options(self): arglist = [] verifylist = [] - # Missing required args should bail here self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) From 2244915b506b7342e3e2ce6b0b3e88a38d13c390 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Fri, 6 May 2016 22:22:39 +0000 Subject: [PATCH 0863/3095] Updated from global requirements Change-Id: Ic26729d1ce6e367d1b1cff38f6802cfed0d509ec --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f88e771ea6..5c05cfb0d6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ pbr>=1.6 # Apache-2.0 six>=1.9.0 # MIT -Babel!=2.3.0,!=2.3.1,!=2.3.2,!=2.3.3,>=1.3 # BSD +Babel>=2.3.4 # BSD cliff!=1.16.0,!=1.17.0,>=1.15.0 # Apache-2.0 keystoneauth1>=2.1.0 # Apache-2.0 openstacksdk>=0.8.5 # Apache-2.0 From 809239ca1e404c8077dd2499eafc8d12bd21c4cd Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 7 May 2016 14:54:44 +0800 Subject: [PATCH 0864/3095] Map server power state num to meanful string In _prep_server_detail(), power_state is not formatted by _format_servers_list_power_state(). So when executing "server show" or "server create", the power state is represented by number. This patch map the numbers to meanful strings. This patch also adds power_state attribute to FakeServer, and improves unit tests for this attribute. Change-Id: I2ec674327de4e5133b8712ba6bb53fa5ce55e3f4 --- openstackclient/compute/v2/server.py | 5 +++++ openstackclient/tests/compute/v2/fakes.py | 3 ++- openstackclient/tests/compute/v2/test_server.py | 5 +++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index bf9f0985df..8c53315822 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -155,6 +155,11 @@ def _prep_server_detail(compute_client, server): if 'tenant_id' in info: info['project_id'] = info.pop('tenant_id') + # Map power state num to meanful string + if 'OS-EXT-STS:power_state' in info: + info['OS-EXT-STS:power_state'] = _format_servers_list_power_state( + info['OS-EXT-STS:power_state']) + # Remove values that are long and not too useful info.pop('links', None) diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 23957738a3..b3f3fb4917 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -472,7 +472,8 @@ def create_one_server(attrs=None, methods=None): }, 'flavor': { 'id': 'flavor-id-' + uuid.uuid4().hex, - } + }, + 'OS-EXT-STS:power_state': 1, } # Overwrite default attributes. diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 17681672b5..0ba033d234 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -89,6 +89,7 @@ def run_method_with_servers(self, method_name, server_count): class TestServerCreate(TestServer): columns = ( + 'OS-EXT-STS:power_state', 'addresses', 'flavor', 'id', @@ -100,6 +101,8 @@ class TestServerCreate(TestServer): def datalist(self): datalist = ( + server._format_servers_list_power_state( + getattr(self.new_server, 'OS-EXT-STS:power_state')), '', self.flavor.name + ' (' + self.new_server.flavor.get('id') + ')', self.new_server.id, @@ -1476,6 +1479,8 @@ def test_prep_server_detail(self, find_resource): 'image': u'%s (%s)' % (_image.name, _image.id), 'project_id': u'tenant-id-xxx', 'properties': '', + 'OS-EXT-STS:power_state': server._format_servers_list_power_state( + getattr(_server, 'OS-EXT-STS:power_state')), } # Call _prep_server_detail(). From c1dba6ab1b2d5999fe0c3c24b7d8aa0ac8be47f1 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Sat, 7 May 2016 07:56:47 -0500 Subject: [PATCH 0865/3095] Fix functional test failures There have been a lot of functional test failures for compute and network. I believe they are timing related in that the test_server is sometimes booting a server with a flavor, image and/or network created by another functional test. Such resources are then deleted by the owning functional tests causing various types of failures Change-Id: Ib82edd487da46610374fb0eb5796cb98f2d3aa2c --- functional/tests/compute/v2/test_server.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/functional/tests/compute/v2/test_server.py b/functional/tests/compute/v2/test_server.py index 247784e1fc..845c5c5a56 100644 --- a/functional/tests/compute/v2/test_server.py +++ b/functional/tests/compute/v2/test_server.py @@ -24,14 +24,16 @@ class ServerTests(test.TestCase): @classmethod def get_flavor(cls): - raw_output = cls.openstack('flavor list -f value -c ID') - ray = raw_output.split('\n') - idx = int(len(ray) / 2) - return ray[idx] + # NOTE(rtheis): Get m1.tiny flavor since functional tests may + # create other flavors. + raw_output = cls.openstack('flavor show m1.tiny -c id -f value') + return raw_output.strip('\n') @classmethod def get_image(cls): - raw_output = cls.openstack('image list -f value -c ID') + # NOTE(rtheis): Get public images since functional tests may + # create private images. + raw_output = cls.openstack('image list --public -f value -c ID') ray = raw_output.split('\n') idx = int(len(ray) / 2) return ray[idx] @@ -39,12 +41,12 @@ def get_image(cls): @classmethod def get_network(cls): try: - raw_output = cls.openstack('network list -f value -c ID') + # NOTE(rtheis): Get private network since functional tests may + # create other networks. + raw_output = cls.openstack('network show private -c id -f value') except exceptions.CommandFailed: return '' - ray = raw_output.split('\n') - idx = int(len(ray) / 2) - return ' --nic net-id=' + ray[idx] + return ' --nic net-id=' + raw_output.strip('\n') def server_create(self, name=None): """Create server. Add cleanup.""" From b8432408e68855d7ce9269b7e681de3c5b733b09 Mon Sep 17 00:00:00 2001 From: Hieu LE Date: Mon, 9 May 2016 12:29:44 +0700 Subject: [PATCH 0866/3095] Ignore domain related config when using with keystone v2 Currently, "/usr/bin/openstack --insecure token issue" fails when OS_AUTH_URL and OS_IDENTITY_API_VERSION indicate keystone v2 if OS_PROJECT_DOMAIN_NAME or OS_USER_DOMAIN_NAME are set. This patchset ignore domain related configs if using with keystone v2 and print warning for each ignored config. Change-Id: I8afbda787df7855c3f8e868b0f07cbf3b9cd97fd Closes-bug: #1447704 --- openstackclient/common/clientmanager.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index c29bf224a2..9c2b320c1d 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -200,12 +200,12 @@ def setup_auth(self, required_scope=True): # ignore all domain related configs. if (self._api_version.get('identity') == '2.0' and self.auth_plugin_name.endswith('password')): - LOG.warning("Ignoring domain related configs " - "because identity API version is 2.0") domain_props = ['project_domain_name', 'project_domain_id', 'user_domain_name', 'user_domain_id'] for prop in domain_props: - self._auth_params.pop(prop, None) + if self._auth_params.pop(prop, None) is not None: + LOG.warning("Ignoring domain related configs " + + prop + " because identity API version is 2.0") # For compatibility until all clients can be updated if 'project_name' in self._auth_params: From d58280a27989ab781cbf2598a6f7c263cb2275e8 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Mon, 9 May 2016 14:59:39 +0800 Subject: [PATCH 0867/3095] Use find_resource() instead of get() in _prep_server_detail() There is such a comment in test_server.py: # Call .get() to retrieve all of the server information # as findall(name=blah) and REST /details are not the same # and do not return flavor and image information. This is an out of date comment. There is no function named findall() in OSC now. So use find_resource() instead of get(), and remove this comment. Change-Id: I8d79afc5f341fb5caf771d905506b7f1c7a41ae8 --- openstackclient/compute/v2/server.py | 5 +---- openstackclient/tests/compute/v2/test_server.py | 12 +++++------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index bf9f0985df..d32f7adea0 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -117,10 +117,7 @@ def _prep_server_detail(compute_client, server): """ info = server._info.copy() - # Call .get() to retrieve all of the server information - # as findall(name=blah) and REST /details are not the same - # and do not return flavor and image information. - server = compute_client.servers.get(info['id']) + server = utils.find_resource(compute_client.servers, info['id']) info.update(server._info) # Convert the image blob to a name diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 2e74545746..dfb7649ec9 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -1448,14 +1448,12 @@ def test_format_servers_list_networks(self): @mock.patch('openstackclient.common.utils.find_resource') def test_prep_server_detail(self, find_resource): # Setup mock method return value. utils.find_resource() will be called - # twice in _prep_server_detail(): - # - The first time, return image info. - # - The second time, return flavor info. + # three times in _prep_server_detail(): + # - The first time, return server info. + # - The second time, return image info. + # - The third time, return flavor info. _image = image_fakes.FakeImage.create_one_image() _flavor = compute_fakes.FakeFlavor.create_one_flavor() - find_resource.side_effect = [_image, _flavor] - - # compute_client.servers.get() will be called once, return server info. server_info = { 'image': {u'id': _image.id}, 'flavor': {u'id': _flavor.id}, @@ -1464,7 +1462,7 @@ def test_prep_server_detail(self, find_resource): 'links': u'http://xxx.yyy.com', } _server = compute_fakes.FakeServer.create_one_server(attrs=server_info) - self.servers_mock.get.return_value = _server + find_resource.side_effect = [_server, _image, _flavor] # Prepare result data. info = { From 34decd83f1ef498db169a993975764b94500024b Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Tue, 10 May 2016 00:49:36 +0000 Subject: [PATCH 0868/3095] Updated from global requirements Change-Id: Id56a0313f630dd812218b9b7dfa812f185958a5d --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 5c05cfb0d6..e2353be376 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,4 +17,4 @@ python-keystoneclient!=1.8.0,!=2.1.0,>=1.6.0 # Apache-2.0 python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0 python-cinderclient>=1.6.0 # Apache-2.0 requests!=2.9.0,>=2.8.1 # Apache-2.0 -stevedore>=1.9.0 # Apache-2.0 +stevedore>=1.10.0 # Apache-2.0 From 06780dd880a316ce4f0750f22c794916aa904fcf Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Fri, 6 May 2016 19:49:42 +0800 Subject: [PATCH 0869/3095] Add unit tests for "server show" command Change-Id: I6eb0c4e7d5d3e677764b253e3324720a784a110b --- .../tests/compute/v2/test_server.py | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 903d0bff51..234e4719ef 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -1233,6 +1233,101 @@ def test_shelve_multi_servers(self): self.run_method_with_servers('shelve', 3) +class TestServerShow(TestServer): + + def setUp(self): + super(TestServerShow, self).setUp() + + self.image = image_fakes.FakeImage.create_one_image() + self.flavor = compute_fakes.FakeFlavor.create_one_flavor() + server_info = { + 'image': {'id': self.image.id}, + 'flavor': {'id': self.flavor.id}, + 'tenant_id': 'tenant-id-xxx', + 'networks': {'public': ['10.20.30.40', '2001:db8::f']}, + } + # Fake the server.diagnostics() method. The return value contains http + # response and data. The data is a dict. Sincce this method itself is + # faked, we don't need to fake everything of the return value exactly. + resp = mock.Mock() + resp.status_code = 200 + server_method = { + 'diagnostics': (resp, {'test': 'test'}), + } + self.server = compute_fakes.FakeServer.create_one_server( + attrs=server_info, methods=server_method) + + # This is the return value for utils.find_resource() + self.servers_mock.get.return_value = self.server + self.cimages_mock.get.return_value = self.image + self.flavors_mock.get.return_value = self.flavor + + # Get the command object to test + self.cmd = server.ShowServer(self.app, None) + + self.columns = ( + 'OS-EXT-STS:power_state', + 'addresses', + 'flavor', + 'id', + 'image', + 'name', + 'networks', + 'project_id', + 'properties', + ) + + self.data = ( + 'Running', + 'public=10.20.30.40, 2001:db8::f', + self.flavor.name + " (" + self.flavor.id + ")", + self.server.id, + self.image.name + " (" + self.image.id + ")", + self.server.name, + {'public': ['10.20.30.40', '2001:db8::f']}, + 'tenant-id-xxx', + '', + ) + + def test_show_no_options(self): + arglist = [] + verifylist = [] + + self.assertRaises(utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_show(self): + arglist = [ + self.server.name, + ] + verifylist = [ + ('diagnostics', False), + ('server', self.server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_show_diagnostics(self): + arglist = [ + '--diagnostics', + self.server.name, + ] + verifylist = [ + ('diagnostics', True), + ('server', self.server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual(('test',), columns) + self.assertEqual(('test',), data) + + class TestServerStart(TestServer): def setUp(self): From 98bee08e0ff9bd0eae185265d20ee3b40a12efd4 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Sat, 7 May 2016 16:00:19 +0800 Subject: [PATCH 0870/3095] Implement "address scope create" command This patch supports creating a new address scope, with --ip-version,--project,--project-domain and --share or --no-share options. Change-Id: I37c73391a41ac239dd72d55dbc0adbebd7701f4a Partial-Bug: #1566269 --- doc/source/command-objects/address-scope.rst | 48 +++++ doc/source/commands.rst | 1 + openstackclient/network/v2/address_scope.py | 96 ++++++++++ .../tests/network/v2/test_address_scope.py | 164 ++++++++++++++++++ setup.cfg | 2 + 5 files changed, 311 insertions(+) create mode 100644 doc/source/command-objects/address-scope.rst create mode 100644 openstackclient/network/v2/address_scope.py create mode 100644 openstackclient/tests/network/v2/test_address_scope.py diff --git a/doc/source/command-objects/address-scope.rst b/doc/source/command-objects/address-scope.rst new file mode 100644 index 0000000000..d7eac28316 --- /dev/null +++ b/doc/source/command-objects/address-scope.rst @@ -0,0 +1,48 @@ +============= +address scope +============= + +An **address scope** is a scope of IPv4 or IPv6 addresses that belongs +to a given project and may be shared between projects. + +Network v2 + +address scope create +-------------------- + +Create new address scope + +.. program:: address scope create +.. code:: bash + + os address scope create + [--project [--project-domain ]] + [--ip-version ] + [--share | --no-share] + + +.. option:: --project + + Owner's project (name or ID) + +.. option:: --project-domain + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + +.. option:: --ip-version + + IP version (4 or 6, default is 4) + +.. option:: --share + + Share the address scope between projects + +.. option:: --no-share + + Do not share the address scope between projects (default) + +.. _address_scope_create-name: +.. describe:: + + New address scope name diff --git a/doc/source/commands.rst b/doc/source/commands.rst index f71fbccb4e..bccd6cb1ba 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -70,6 +70,7 @@ the API resources will be merged, as in the ``quota`` object that has options referring to both Compute and Volume quotas. * ``access token``: (**Identity**) long-lived OAuth-based token +* ``address scope``: (**Network**) a scope of IPv4 or IPv6 addresses * ``aggregate``: (**Compute**) a grouping of compute hosts * ``availability zone``: (**Compute**, **Network**, **Volume**) a logical partition of hosts or block storage or network services * ``backup``: (**Volume**) a volume copy diff --git a/openstackclient/network/v2/address_scope.py b/openstackclient/network/v2/address_scope.py new file mode 100644 index 0000000000..eba889514d --- /dev/null +++ b/openstackclient/network/v2/address_scope.py @@ -0,0 +1,96 @@ +# 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. +# + +"""Address scope action implementations""" + +from openstackclient.common import command +from openstackclient.common import utils +from openstackclient.i18n import _ +from openstackclient.identity import common as identity_common + + +def _get_columns(item): + columns = list(item.keys()) + if 'tenant_id' in columns: + columns.remove('tenant_id') + columns.append('project_id') + + return tuple(sorted(columns)) + + +def _get_attrs(client_manager, parsed_args): + attrs = {} + attrs['name'] = parsed_args.name + attrs['ip_version'] = parsed_args.ip_version + if parsed_args.share: + attrs['shared'] = True + if parsed_args.no_share: + attrs['shared'] = False + if 'project' in parsed_args and parsed_args.project is not None: + identity_client = client_manager.identity + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + attrs['tenant_id'] = project_id + + return attrs + + +class CreateAddressScope(command.ShowOne): + """Create a new Address Scope""" + + def get_parser(self, prog_name): + parser = super(CreateAddressScope, self).get_parser(prog_name) + parser.add_argument( + 'name', + metavar="", + help=_("New address scope name") + ) + parser.add_argument( + '--ip-version', + type=int, + default=4, + choices=[4, 6], + help=_("IP version (default is 4)") + ) + parser.add_argument( + '--project', + metavar="", + help=_("Owner's project (name or ID)") + ) + identity_common.add_project_domain_option_to_parser(parser) + + share_group = parser.add_mutually_exclusive_group() + share_group.add_argument( + '--share', + action='store_true', + help=_('Share the address scope between projects') + ) + share_group.add_argument( + '--no-share', + action='store_true', + help=_('Do not share the address scope between projects (default)') + ) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + attrs = _get_attrs(self.app.client_manager, parsed_args) + obj = client.create_address_scope(**attrs) + columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns, formatters={}) + + return columns, data diff --git a/openstackclient/tests/network/v2/test_address_scope.py b/openstackclient/tests/network/v2/test_address_scope.py new file mode 100644 index 0000000000..f37512cd5d --- /dev/null +++ b/openstackclient/tests/network/v2/test_address_scope.py @@ -0,0 +1,164 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +import copy +import mock + +from openstackclient.network.v2 import address_scope +from openstackclient.tests import fakes +from openstackclient.tests.identity.v3 import fakes as identity_fakes_v3 +from openstackclient.tests.network.v2 import fakes as network_fakes +from openstackclient.tests import utils as tests_utils + + +class TestAddressScope(network_fakes.TestNetworkV2): + + def setUp(self): + super(TestAddressScope, self).setUp() + + # Get a shortcut to the network client + self.network = self.app.client_manager.network + + +class TestCreateAddressScope(TestAddressScope): + + # The new address scope created. + new_address_scope = ( + network_fakes.FakeAddressScope.create_one_address_scope( + attrs={ + 'tenant_id': identity_fakes_v3.project_id, + } + )) + columns = ( + 'id', + 'ip_version', + 'name', + 'project_id', + 'shared' + ) + data = ( + new_address_scope.id, + new_address_scope.ip_version, + new_address_scope.name, + new_address_scope.project_id, + new_address_scope.shared, + ) + + def setUp(self): + super(TestCreateAddressScope, self).setUp() + self.network.create_address_scope = mock.Mock( + return_value=self.new_address_scope) + + # Get the command object to test + self.cmd = address_scope.CreateAddressScope(self.app, self.namespace) + + # Set identity client v3. And get a shortcut to Identity client. + identity_client = identity_fakes_v3.FakeIdentityv3Client( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN, + ) + self.app.client_manager.identity = identity_client + self.identity = self.app.client_manager.identity + + # Get a shortcut to the ProjectManager Mock + self.projects_mock = self.identity.projects + self.projects_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes_v3.PROJECT), + loaded=True, + ) + + # Get a shortcut to the DomainManager Mock + self.domains_mock = self.identity.domains + self.domains_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes_v3.DOMAIN), + loaded=True, + ) + + def test_create_no_options(self): + arglist = [] + verifylist = [] + + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_create_default_options(self): + arglist = [ + self.new_address_scope.name, + ] + verifylist = [ + ('project', None), + ('ip_version', self.new_address_scope.ip_version), + ('name', self.new_address_scope.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_address_scope.assert_called_once_with(**{ + 'ip_version': self.new_address_scope.ip_version, + 'name': self.new_address_scope.name, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_all_options(self): + arglist = [ + '--ip-version', str(self.new_address_scope.ip_version), + '--share', + '--project', identity_fakes_v3.project_name, + '--project-domain', identity_fakes_v3.domain_name, + self.new_address_scope.name, + ] + verifylist = [ + ('ip_version', self.new_address_scope.ip_version), + ('share', True), + ('project', identity_fakes_v3.project_name), + ('project_domain', identity_fakes_v3.domain_name), + ('name', self.new_address_scope.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_address_scope.assert_called_once_with(**{ + 'ip_version': self.new_address_scope.ip_version, + 'shared': True, + 'tenant_id': identity_fakes_v3.project_id, + 'name': self.new_address_scope.name, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_no_share(self): + arglist = [ + '--no-share', + self.new_address_scope.name, + ] + verifylist = [ + ('no_share', True), + ('name', self.new_address_scope.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_address_scope.assert_called_once_with(**{ + 'ip_version': self.new_address_scope.ip_version, + 'shared': False, + 'name': self.new_address_scope.name, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) diff --git a/setup.cfg b/setup.cfg index 684214127a..de8f716501 100644 --- a/setup.cfg +++ b/setup.cfg @@ -319,6 +319,8 @@ openstack.image.v2 = image_set = openstackclient.image.v2.image:SetImage openstack.network.v2 = + address_scope_create = openstackclient.network.v2.address_scope:CreateAddressScope + ip_floating_create = openstackclient.network.v2.floating_ip:CreateFloatingIP ip_floating_delete = openstackclient.network.v2.floating_ip:DeleteFloatingIP ip_floating_list = openstackclient.network.v2.floating_ip:ListFloatingIP From 4cb5e0bc7959694db70d11c6be8589f1c20d787c Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Sat, 7 May 2016 16:09:41 +0800 Subject: [PATCH 0871/3095] Implement "address scope delete" command This patch add a command that supports deleting a address scope Change-Id: Ie028058c759b9511d105a530d7e89b841865e7d6 Partial-Bug: #1566269 --- doc/source/command-objects/address-scope.rst | 16 ++++++++++ openstackclient/network/v2/address_scope.py | 19 ++++++++++++ .../tests/network/v2/test_address_scope.py | 31 +++++++++++++++++++ setup.cfg | 1 + 4 files changed, 67 insertions(+) diff --git a/doc/source/command-objects/address-scope.rst b/doc/source/command-objects/address-scope.rst index d7eac28316..2f922353b0 100644 --- a/doc/source/command-objects/address-scope.rst +++ b/doc/source/command-objects/address-scope.rst @@ -46,3 +46,19 @@ Create new address scope .. describe:: New address scope name + +address scope delete +-------------------- + +Delete an address scope + +.. program:: address scope delete +.. code:: bash + + os address scope delete + + +.. _address_scope_delete-address-scope: +.. describe:: + + Address scope to delete (name or ID) diff --git a/openstackclient/network/v2/address_scope.py b/openstackclient/network/v2/address_scope.py index eba889514d..361fd3600a 100644 --- a/openstackclient/network/v2/address_scope.py +++ b/openstackclient/network/v2/address_scope.py @@ -94,3 +94,22 @@ def take_action(self, parsed_args): data = utils.get_item_properties(obj, columns, formatters={}) return columns, data + + +class DeleteAddressScope(command.Command): + """Delete an address scope""" + + def get_parser(self, prog_name): + parser = super(DeleteAddressScope, self).get_parser(prog_name) + parser.add_argument( + 'address_scope', + metavar="", + help=_("Address scope to delete (name or ID)") + ) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.find_address_scope(parsed_args.address_scope) + client.delete_address_scope(obj) diff --git a/openstackclient/tests/network/v2/test_address_scope.py b/openstackclient/tests/network/v2/test_address_scope.py index f37512cd5d..3168ea3f6f 100644 --- a/openstackclient/tests/network/v2/test_address_scope.py +++ b/openstackclient/tests/network/v2/test_address_scope.py @@ -162,3 +162,34 @@ def test_create_no_share(self): }) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + + +class TestDeleteAddressScope(TestAddressScope): + + # The address scope to delete. + _address_scope = ( + network_fakes.FakeAddressScope.create_one_address_scope()) + + def setUp(self): + super(TestDeleteAddressScope, self).setUp() + self.network.delete_address_scope = mock.Mock(return_value=None) + self.network.find_address_scope = mock.Mock( + return_value=self._address_scope) + + # Get the command object to test + self.cmd = address_scope.DeleteAddressScope(self.app, self.namespace) + + def test_delete(self): + arglist = [ + self._address_scope.name, + ] + verifylist = [ + ('address_scope', self._address_scope.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.network.delete_address_scope.assert_called_once_with( + self._address_scope) + self.assertIsNone(result) diff --git a/setup.cfg b/setup.cfg index de8f716501..ae9842301b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -320,6 +320,7 @@ openstack.image.v2 = openstack.network.v2 = address_scope_create = openstackclient.network.v2.address_scope:CreateAddressScope + address_scope_delete = openstackclient.network.v2.address_scope:DeleteAddressScope ip_floating_create = openstackclient.network.v2.floating_ip:CreateFloatingIP ip_floating_delete = openstackclient.network.v2.floating_ip:DeleteFloatingIP From aa5ff67e3fa2b9497218e9bdd4ac3fdf432e007c Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Sat, 7 May 2016 16:15:53 +0800 Subject: [PATCH 0872/3095] Implement "address scope list" command This patch add a command that supports listing address scopes Change-Id: Id14757011560cacf28011ba51841a8e23b824f33 Partial-Bug: #1566269 --- doc/source/command-objects/address-scope.rst | 10 +++++ openstackclient/network/v2/address_scope.py | 27 ++++++++++++ openstackclient/tests/network/v2/fakes.py | 18 ++++++++ .../tests/network/v2/test_address_scope.py | 42 +++++++++++++++++++ setup.cfg | 1 + 5 files changed, 98 insertions(+) diff --git a/doc/source/command-objects/address-scope.rst b/doc/source/command-objects/address-scope.rst index 2f922353b0..2c701a4941 100644 --- a/doc/source/command-objects/address-scope.rst +++ b/doc/source/command-objects/address-scope.rst @@ -62,3 +62,13 @@ Delete an address scope .. describe:: Address scope to delete (name or ID) + +address scope list +------------------ + +List address scopes + +.. program:: address scope list +.. code:: bash + + os address scope list diff --git a/openstackclient/network/v2/address_scope.py b/openstackclient/network/v2/address_scope.py index 361fd3600a..dd43ef5b21 100644 --- a/openstackclient/network/v2/address_scope.py +++ b/openstackclient/network/v2/address_scope.py @@ -113,3 +113,30 @@ def take_action(self, parsed_args): client = self.app.client_manager.network obj = client.find_address_scope(parsed_args.address_scope) client.delete_address_scope(obj) + + +class ListAddressScope(command.Lister): + """List address scopes""" + + def take_action(self, parsed_args): + client = self.app.client_manager.network + columns = ( + 'id', + 'name', + 'ip_version', + 'shared', + 'tenant_id', + ) + column_headers = ( + 'ID', + 'Name', + 'IP Version', + 'Shared', + 'Project', + ) + data = client.address_scopes() + + return (column_headers, + (utils.get_item_properties( + s, columns, formatters={}, + ) for s in data)) diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 5fd7ea3b86..413ec7d2c5 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -107,6 +107,24 @@ def create_one_address_scope(attrs=None): return address_scope + @staticmethod + def create_address_scopes(attrs=None, count=2): + """Create multiple fake address scopes. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of address scopes to fake + :return: + A list of FakeResource objects faking the address scopes + """ + address_scopes = [] + for i in range(0, count): + address_scopes.append( + FakeAddressScope.create_one_address_scope(attrs)) + + return address_scopes + class FakeAvailabilityZone(object): """Fake one or more network availability zones (AZs).""" diff --git a/openstackclient/tests/network/v2/test_address_scope.py b/openstackclient/tests/network/v2/test_address_scope.py index 3168ea3f6f..d33013ce9a 100644 --- a/openstackclient/tests/network/v2/test_address_scope.py +++ b/openstackclient/tests/network/v2/test_address_scope.py @@ -193,3 +193,45 @@ def test_delete(self): self.network.delete_address_scope.assert_called_once_with( self._address_scope) self.assertIsNone(result) + + +class TestListAddressScope(TestAddressScope): + + # The address scopes to list up. + address_scopes = ( + network_fakes.FakeAddressScope.create_address_scopes(count=3)) + columns = ( + 'ID', + 'Name', + 'IP Version', + 'Shared', + 'Project', + ) + data = [] + for scope in address_scopes: + data.append(( + scope.id, + scope.name, + scope.ip_version, + scope.shared, + scope.project_id, + )) + + def setUp(self): + super(TestListAddressScope, self).setUp() + self.network.address_scopes = mock.Mock( + return_value=self.address_scopes) + + # Get the command object to test + self.cmd = address_scope.ListAddressScope(self.app, self.namespace) + + def test_address_scope_list(self): + arglist = [] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.address_scopes.assert_called_once_with(**{}) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) diff --git a/setup.cfg b/setup.cfg index ae9842301b..18f177dc48 100644 --- a/setup.cfg +++ b/setup.cfg @@ -321,6 +321,7 @@ openstack.image.v2 = openstack.network.v2 = address_scope_create = openstackclient.network.v2.address_scope:CreateAddressScope address_scope_delete = openstackclient.network.v2.address_scope:DeleteAddressScope + address_scope_list = openstackclient.network.v2.address_scope:ListAddressScope ip_floating_create = openstackclient.network.v2.floating_ip:CreateFloatingIP ip_floating_delete = openstackclient.network.v2.floating_ip:DeleteFloatingIP From 32da111c1757a884466814ac000fb7e662e6da42 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Sat, 7 May 2016 16:21:06 +0800 Subject: [PATCH 0873/3095] Implement "address scope show" command This patch add a command that supports showing address scope details Change-Id: Ic0b41c1cab8c618904c7a6046d7493db5b74b430 Partial-Bug: #1566269 --- doc/source/command-objects/address-scope.rst | 16 ++++++ openstackclient/network/v2/address_scope.py | 24 +++++++++ .../tests/network/v2/test_address_scope.py | 53 +++++++++++++++++++ setup.cfg | 1 + 4 files changed, 94 insertions(+) diff --git a/doc/source/command-objects/address-scope.rst b/doc/source/command-objects/address-scope.rst index 2c701a4941..f37bc321a0 100644 --- a/doc/source/command-objects/address-scope.rst +++ b/doc/source/command-objects/address-scope.rst @@ -72,3 +72,19 @@ List address scopes .. code:: bash os address scope list + +address scope show +------------------ + +Display address scope details + +.. program:: address scope show +.. code:: bash + + os address scope show + + +.. _address_scope_show-address-scope: +.. describe:: + + Address scope to display (name or ID) diff --git a/openstackclient/network/v2/address_scope.py b/openstackclient/network/v2/address_scope.py index dd43ef5b21..86f39c3477 100644 --- a/openstackclient/network/v2/address_scope.py +++ b/openstackclient/network/v2/address_scope.py @@ -140,3 +140,27 @@ def take_action(self, parsed_args): (utils.get_item_properties( s, columns, formatters={}, ) for s in data)) + + +class ShowAddressScope(command.ShowOne): + """Display address scope details""" + + def get_parser(self, prog_name): + parser = super(ShowAddressScope, self).get_parser(prog_name) + parser.add_argument( + 'address_scope', + metavar="", + help=_("Address scope to display (name or ID)") + ) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.find_address_scope( + parsed_args.address_scope, + ignore_missing=False) + columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns, formatters={}) + + return columns, data diff --git a/openstackclient/tests/network/v2/test_address_scope.py b/openstackclient/tests/network/v2/test_address_scope.py index d33013ce9a..da658fefb1 100644 --- a/openstackclient/tests/network/v2/test_address_scope.py +++ b/openstackclient/tests/network/v2/test_address_scope.py @@ -235,3 +235,56 @@ def test_address_scope_list(self): self.network.address_scopes.assert_called_once_with(**{}) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + + +class TestShowAddressScope(TestAddressScope): + + # The address scope to show. + _address_scope = ( + network_fakes.FakeAddressScope.create_one_address_scope()) + columns = ( + 'id', + 'ip_version', + 'name', + 'project_id', + 'shared', + ) + data = ( + _address_scope.id, + _address_scope.ip_version, + _address_scope.name, + _address_scope.project_id, + _address_scope.shared, + ) + + def setUp(self): + super(TestShowAddressScope, self).setUp() + self.network.find_address_scope = mock.Mock( + return_value=self._address_scope) + + # Get the command object to test + self.cmd = address_scope.ShowAddressScope(self.app, self.namespace) + + def test_show_no_options(self): + arglist = [] + verifylist = [] + + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_show_all_options(self): + arglist = [ + self._address_scope.name, + ] + verifylist = [ + ('address_scope', self._address_scope.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.find_address_scope.assert_called_once_with( + self._address_scope.name, ignore_missing=False) + self.assertEqual(self.columns, columns) + self.assertEqual(list(self.data), list(data)) diff --git a/setup.cfg b/setup.cfg index 18f177dc48..3c8c829040 100644 --- a/setup.cfg +++ b/setup.cfg @@ -322,6 +322,7 @@ openstack.network.v2 = address_scope_create = openstackclient.network.v2.address_scope:CreateAddressScope address_scope_delete = openstackclient.network.v2.address_scope:DeleteAddressScope address_scope_list = openstackclient.network.v2.address_scope:ListAddressScope + address_scope_show = openstackclient.network.v2.address_scope:ShowAddressScope ip_floating_create = openstackclient.network.v2.floating_ip:CreateFloatingIP ip_floating_delete = openstackclient.network.v2.floating_ip:DeleteFloatingIP From cc78d48a2895413f0ae7547db19ea843ae796cca Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Sat, 7 May 2016 17:08:54 +0800 Subject: [PATCH 0874/3095] Implement "address scope set" command This patch add a command that supports setting address scope properties. Change-Id: I9c4b5068a8abb986a9dc18b167b48b924d16ff42 Closes-Bug: #1566269 --- doc/source/command-objects/address-scope.rst | 32 ++++++++- .../tests/network/v2/test_address_scope.py | 49 ++++++++++++++ openstackclient/network/v2/address_scope.py | 48 +++++++++++++ .../tests/network/v2/test_address_scope.py | 67 +++++++++++++++++++ .../notes/bug-1566269-2572bca9157ca107.yaml | 5 ++ setup.cfg | 1 + 6 files changed, 201 insertions(+), 1 deletion(-) create mode 100644 functional/tests/network/v2/test_address_scope.py create mode 100644 releasenotes/notes/bug-1566269-2572bca9157ca107.yaml diff --git a/doc/source/command-objects/address-scope.rst b/doc/source/command-objects/address-scope.rst index f37bc321a0..a2ab566c82 100644 --- a/doc/source/command-objects/address-scope.rst +++ b/doc/source/command-objects/address-scope.rst @@ -45,7 +45,7 @@ Create new address scope .. _address_scope_create-name: .. describe:: - New address scope name + New address scope name address scope delete -------------------- @@ -73,6 +73,36 @@ List address scopes os address scope list +address scope set +----------------- + +Set address scope properties + +.. program:: address scope set +.. code:: bash + + os address scope set + [--name ] + [--share | --no-share] + + +.. option:: --name + + Set address scope name + +.. option:: --share + + Share the address scope between projects + +.. option:: --no-share + + Do not share the address scope between projects + +.. _address_scope_set-address-scope: +.. describe:: + + Address scope to modify (name or ID) + address scope show ------------------ diff --git a/functional/tests/network/v2/test_address_scope.py b/functional/tests/network/v2/test_address_scope.py new file mode 100644 index 0000000000..8e25e46aa9 --- /dev/null +++ b/functional/tests/network/v2/test_address_scope.py @@ -0,0 +1,49 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import uuid + +from functional.common import test + + +class AddressScopeTests(test.TestCase): + """Functional tests for address scope. """ + NAME = uuid.uuid4().hex + HEADERS = ['Name'] + FIELDS = ['name'] + + @classmethod + def setUpClass(cls): + opts = cls.get_show_opts(cls.FIELDS) + raw_output = cls.openstack('address scope create ' + cls.NAME + opts) + cls.assertOutput(cls.NAME + "\n", raw_output) + + @classmethod + def tearDownClass(cls): + raw_output = cls.openstack('address scope delete ' + cls.NAME) + cls.assertOutput('', raw_output) + + def test_address_scope_list(self): + opts = self.get_list_opts(self.HEADERS) + raw_output = self.openstack('address scope list' + opts) + self.assertIn(self.NAME, raw_output) + + def test_address_scope_show(self): + opts = self.get_show_opts(self.FIELDS) + raw_output = self.openstack('address scope show ' + self.NAME + opts) + self.assertEqual(self.NAME + "\n", raw_output) + + def test_address_scope_set(self): + self.openstack('address scope set --share ' + self.NAME) + opts = self.get_show_opts(['shared']) + raw_output = self.openstack('address scope show ' + self.NAME + opts) + self.assertEqual("True\n", raw_output) diff --git a/openstackclient/network/v2/address_scope.py b/openstackclient/network/v2/address_scope.py index 86f39c3477..fac0849f6b 100644 --- a/openstackclient/network/v2/address_scope.py +++ b/openstackclient/network/v2/address_scope.py @@ -14,6 +14,7 @@ """Address scope action implementations""" from openstackclient.common import command +from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common @@ -142,6 +143,53 @@ def take_action(self, parsed_args): ) for s in data)) +class SetAddressScope(command.Command): + """Set address scope properties""" + + def get_parser(self, prog_name): + parser = super(SetAddressScope, self).get_parser(prog_name) + parser.add_argument( + 'address_scope', + metavar="", + help=_("Address scope to modify (name or ID)") + ) + parser.add_argument( + '--name', + metavar="", + help=_('Set address scope name') + ) + share_group = parser.add_mutually_exclusive_group() + share_group.add_argument( + '--share', + action='store_true', + help=_('Share the address scope between projects') + ) + share_group.add_argument( + '--no-share', + action='store_true', + help=_('Do not share the address scope between projects') + ) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.find_address_scope( + parsed_args.address_scope, + ignore_missing=False) + attrs = {} + if parsed_args.name is not None: + attrs['name'] = parsed_args.name + if parsed_args.share: + attrs['shared'] = True + if parsed_args.no_share: + attrs['shared'] = False + if attrs == {}: + msg = "Nothing specified to be set." + raise exceptions.CommandError(msg) + client.update_address_scope(obj, **attrs) + + class ShowAddressScope(command.ShowOne): """Display address scope details""" diff --git a/openstackclient/tests/network/v2/test_address_scope.py b/openstackclient/tests/network/v2/test_address_scope.py index da658fefb1..ac94b489b1 100644 --- a/openstackclient/tests/network/v2/test_address_scope.py +++ b/openstackclient/tests/network/v2/test_address_scope.py @@ -14,6 +14,7 @@ import copy import mock +from openstackclient.common import exceptions from openstackclient.network.v2 import address_scope from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes_v3 @@ -237,6 +238,72 @@ def test_address_scope_list(self): self.assertEqual(self.data, list(data)) +class TestSetAddressScope(TestAddressScope): + + # The address scope to set. + _address_scope = network_fakes.FakeAddressScope.create_one_address_scope() + + def setUp(self): + super(TestSetAddressScope, self).setUp() + self.network.update_address_scope = mock.Mock(return_value=None) + self.network.find_address_scope = mock.Mock( + return_value=self._address_scope) + + # Get the command object to test + self.cmd = address_scope.SetAddressScope(self.app, self.namespace) + + def test_set_nothing(self): + arglist = [self._address_scope.name, ] + verifylist = [ + ('address_scope', self._address_scope.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + + def test_set_name_and_share(self): + arglist = [ + '--name', 'new_address_scope', + '--share', + self._address_scope.name, + ] + verifylist = [ + ('name', 'new_address_scope'), + ('share', True), + ('address_scope', self._address_scope.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + attrs = { + 'name': "new_address_scope", + 'shared': True, + } + self.network.update_address_scope.assert_called_with( + self._address_scope, **attrs) + self.assertIsNone(result) + + def test_set_no_share(self): + arglist = [ + '--no-share', + self._address_scope.name, + ] + verifylist = [ + ('no_share', True), + ('address_scope', self._address_scope.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + attrs = { + 'shared': False, + } + self.network.update_address_scope.assert_called_with( + self._address_scope, **attrs) + self.assertIsNone(result) + + class TestShowAddressScope(TestAddressScope): # The address scope to show. diff --git a/releasenotes/notes/bug-1566269-2572bca9157ca107.yaml b/releasenotes/notes/bug-1566269-2572bca9157ca107.yaml new file mode 100644 index 0000000000..9f34f67c7b --- /dev/null +++ b/releasenotes/notes/bug-1566269-2572bca9157ca107.yaml @@ -0,0 +1,5 @@ +--- +features: + - Add ``address scope create``,``address scope delete``,``address scope list``, + ``address scope set`` and ``address scope show`` commands. + [Bug `1566269 `_] diff --git a/setup.cfg b/setup.cfg index 3c8c829040..7c1d68c9f8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -322,6 +322,7 @@ openstack.network.v2 = address_scope_create = openstackclient.network.v2.address_scope:CreateAddressScope address_scope_delete = openstackclient.network.v2.address_scope:DeleteAddressScope address_scope_list = openstackclient.network.v2.address_scope:ListAddressScope + address_scope_set = openstackclient.network.v2.address_scope:SetAddressScope address_scope_show = openstackclient.network.v2.address_scope:ShowAddressScope ip_floating_create = openstackclient.network.v2.floating_ip:CreateFloatingIP From 0e0f314e2ed05c269be1d204131216dfbfd5df4e Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Wed, 11 May 2016 11:13:38 +0800 Subject: [PATCH 0875/3095] Fix functional test for floatingip add/remove in ComputeV2 Updata test_server_attach_detach_floating_ip in test_server.py Change-Id: I2963991dfafbb17431b44e2f37bb26fa4daac9aa --- functional/tests/compute/v2/test_server.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/functional/tests/compute/v2/test_server.py b/functional/tests/compute/v2/test_server.py index 845c5c5a56..6cb82cb04f 100644 --- a/functional/tests/compute/v2/test_server.py +++ b/functional/tests/compute/v2/test_server.py @@ -13,7 +13,6 @@ import time from tempest.lib.common.utils import data_utils -import testtools from functional.common import test from tempest.lib import exceptions @@ -218,7 +217,6 @@ def test_server_rescue_unrescue(self): self.assertEqual("", raw_output) self.wait_for_status("ACTIVE") - @testtools.skip('this test needs to be re-worked completely') def test_server_attach_detach_floating_ip(self): """Test commands to attach and detach floating IP for server. @@ -234,12 +232,11 @@ def test_server_attach_detach_floating_ip(self): """ self.wait_for_status("ACTIVE") # attach ip - opts = self.get_show_opts(["id", "ip"]) - raw_output = self.openstack('ip floating create ' - '--debug ' + + opts = self.get_show_opts(["id", "floating_ip_address"]) + raw_output = self.openstack('ip floating create ' + self.IP_POOL + opts) - ipid, ip, rol = tuple(raw_output.split('\n')) + ip, ipid, rol = tuple(raw_output.split('\n')) self.assertNotEqual("", ipid) self.assertNotEqual("", ip) raw_output = self.openstack('ip floating add ' + ip + ' ' + self.NAME) From 553e154960c6a9da8481c4602c0a7a0d1147d2ad Mon Sep 17 00:00:00 2001 From: "ting.wang" Date: Wed, 11 May 2016 16:37:28 +0800 Subject: [PATCH 0876/3095] Refactor TestVolumeList with FakeVolume Change-Id: Idbe7ee1d9688ea5937852cce1a746016bf98fa74 Co-Authored-By: xiexs Implements: blueprint improve-volume-unittest-framework --- openstackclient/tests/volume/v2/fakes.py | 2 + .../tests/volume/v2/test_volume.py | 125 +++++++++--------- 2 files changed, 63 insertions(+), 64 deletions(-) diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py index 120666a059..fc45e47b9d 100644 --- a/openstackclient/tests/volume/v2/fakes.py +++ b/openstackclient/tests/volume/v2/fakes.py @@ -391,6 +391,8 @@ def create_one_volume(attrs=None): 'size': random.randint(1, 20), 'volume_type': random.choice(['fake_lvmdriver-1', 'fake_lvmdriver-2']), + 'bootable': + random.randint(0, 1), 'metadata': { 'key' + uuid.uuid4().hex: 'val' + uuid.uuid4().hex, 'key' + uuid.uuid4().hex: 'val' + uuid.uuid4().hex, diff --git a/openstackclient/tests/volume/v2/test_volume.py b/openstackclient/tests/volume/v2/test_volume.py index e4ac7c10f1..5689d008ec 100644 --- a/openstackclient/tests/volume/v2/test_volume.py +++ b/openstackclient/tests/volume/v2/test_volume.py @@ -58,6 +58,7 @@ class TestVolumeCreate(TestVolume): columns = ( 'attachments', 'availability_zone', + 'bootable', 'description', 'id', 'name', @@ -77,6 +78,7 @@ def setUp(self): self.datalist = ( self.new_volume.attachments, self.new_volume.availability_zone, + self.new_volume.bootable, self.new_volume.description, self.new_volume.id, self.new_volume.name, @@ -475,13 +477,8 @@ class TestVolumeList(TestVolume): def setUp(self): super(TestVolumeList, self).setUp() - self.volumes_mock.list.return_value = [ - fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.VOLUME), - loaded=True, - ), - ] + self.mock_volume = volume_fakes.FakeVolume.create_one_volume() + self.volumes_mock.list.return_value = [self.mock_volume] self.users_mock.get.return_value = [ fakes.FakeResource( @@ -516,14 +513,14 @@ def test_volume_list_no_options(self): self.assertEqual(self.columns, columns) - server = volume_fakes.volume_attachment_server['server_id'] - device = volume_fakes.volume_attachment_server['device'] + server = self.mock_volume.attachments[0]['server_id'] + device = self.mock_volume.attachments[0]['device'] msg = 'Attached to %s on %s ' % (server, device) datalist = (( - volume_fakes.volume_id, - volume_fakes.volume_name, - volume_fakes.volume_status, - volume_fakes.volume_size, + self.mock_volume.id, + self.mock_volume.name, + self.mock_volume.status, + self.mock_volume.size, msg, ), ) self.assertEqual(datalist, tuple(data)) @@ -544,14 +541,14 @@ def test_volume_list_project(self): self.assertEqual(self.columns, columns) - server = volume_fakes.volume_attachment_server['server_id'] - device = volume_fakes.volume_attachment_server['device'] + server = self.mock_volume.attachments[0]['server_id'] + device = self.mock_volume.attachments[0]['device'] msg = 'Attached to %s on %s ' % (server, device) datalist = (( - volume_fakes.volume_id, - volume_fakes.volume_name, - volume_fakes.volume_status, - volume_fakes.volume_size, + self.mock_volume.id, + self.mock_volume.name, + self.mock_volume.status, + self.mock_volume.size, msg, ), ) self.assertEqual(datalist, tuple(data)) @@ -574,14 +571,14 @@ def test_volume_list_project_domain(self): self.assertEqual(self.columns, columns) - server = volume_fakes.volume_attachment_server['server_id'] - device = volume_fakes.volume_attachment_server['device'] + server = self.mock_volume.attachments[0]['server_id'] + device = self.mock_volume.attachments[0]['device'] msg = 'Attached to %s on %s ' % (server, device) datalist = (( - volume_fakes.volume_id, - volume_fakes.volume_name, - volume_fakes.volume_status, - volume_fakes.volume_size, + self.mock_volume.id, + self.mock_volume.name, + self.mock_volume.status, + self.mock_volume.size, msg, ), ) self.assertEqual(datalist, tuple(data)) @@ -601,14 +598,14 @@ def test_volume_list_user(self): columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) - server = volume_fakes.volume_attachment_server['server_id'] - device = volume_fakes.volume_attachment_server['device'] + server = self.mock_volume.attachments[0]['server_id'] + device = self.mock_volume.attachments[0]['device'] msg = 'Attached to %s on %s ' % (server, device) datalist = (( - volume_fakes.volume_id, - volume_fakes.volume_name, - volume_fakes.volume_status, - volume_fakes.volume_size, + self.mock_volume.id, + self.mock_volume.name, + self.mock_volume.status, + self.mock_volume.size, msg, ), ) self.assertEqual(datalist, tuple(data)) @@ -631,14 +628,14 @@ def test_volume_list_user_domain(self): self.assertEqual(self.columns, columns) - server = volume_fakes.volume_attachment_server['server_id'] - device = volume_fakes.volume_attachment_server['device'] + server = self.mock_volume.attachments[0]['server_id'] + device = self.mock_volume.attachments[0]['device'] msg = 'Attached to %s on %s ' % (server, device) datalist = (( - volume_fakes.volume_id, - volume_fakes.volume_name, - volume_fakes.volume_status, - volume_fakes.volume_size, + self.mock_volume.id, + self.mock_volume.name, + self.mock_volume.status, + self.mock_volume.size, msg, ), ) self.assertEqual(datalist, tuple(data)) @@ -659,14 +656,14 @@ def test_volume_list_name(self): self.assertEqual(self.columns, columns) - server = volume_fakes.volume_attachment_server['server_id'] - device = volume_fakes.volume_attachment_server['device'] + server = self.mock_volume.attachments[0]['server_id'] + device = self.mock_volume.attachments[0]['device'] msg = 'Attached to %s on %s ' % (server, device) datalist = (( - volume_fakes.volume_id, - volume_fakes.volume_name, - volume_fakes.volume_status, - volume_fakes.volume_size, + self.mock_volume.id, + self.mock_volume.name, + self.mock_volume.status, + self.mock_volume.size, msg, ), ) self.assertEqual(datalist, tuple(data)) @@ -687,14 +684,14 @@ def test_volume_list_status(self): self.assertEqual(self.columns, columns) - server = volume_fakes.volume_attachment_server['server_id'] - device = volume_fakes.volume_attachment_server['device'] + server = self.mock_volume.attachments[0]['server_id'] + device = self.mock_volume.attachments[0]['device'] msg = 'Attached to %s on %s ' % (server, device) datalist = (( - volume_fakes.volume_id, - volume_fakes.volume_name, - volume_fakes.volume_status, - volume_fakes.volume_size, + self.mock_volume.id, + self.mock_volume.name, + self.mock_volume.status, + self.mock_volume.size, msg, ), ) self.assertEqual(datalist, tuple(data)) @@ -715,14 +712,14 @@ def test_volume_list_all_projects(self): self.assertEqual(self.columns, columns) - server = volume_fakes.volume_attachment_server['server_id'] - device = volume_fakes.volume_attachment_server['device'] + server = self.mock_volume.attachments[0]['server_id'] + device = self.mock_volume.attachments[0]['device'] msg = 'Attached to %s on %s ' % (server, device) datalist = (( - volume_fakes.volume_id, - volume_fakes.volume_name, - volume_fakes.volume_status, - volume_fakes.volume_size, + self.mock_volume.id, + self.mock_volume.name, + self.mock_volume.status, + self.mock_volume.size, msg, ), ) self.assertEqual(datalist, tuple(data)) @@ -754,18 +751,18 @@ def test_volume_list_long(self): ] self.assertEqual(collist, columns) - server = volume_fakes.volume_attachment_server['server_id'] - device = volume_fakes.volume_attachment_server['device'] + server = self.mock_volume.attachments[0]['server_id'] + device = self.mock_volume.attachments[0]['device'] msg = 'Attached to %s on %s ' % (server, device) datalist = (( - volume_fakes.volume_id, - volume_fakes.volume_name, - volume_fakes.volume_status, - volume_fakes.volume_size, - volume_fakes.volume_type, - '', + self.mock_volume.id, + self.mock_volume.name, + self.mock_volume.status, + self.mock_volume.size, + self.mock_volume.volume_type, + self.mock_volume.bootable, msg, - "Alpha='a', Beta='b', Gamma='g'", + utils.format_dict(self.mock_volume.metadata), ), ) self.assertEqual(datalist, tuple(data)) From 095edbc3678dd9d7cc630becf098f3626fe3237d Mon Sep 17 00:00:00 2001 From: Sheel Rana Date: Thu, 12 May 2016 00:53:23 +0530 Subject: [PATCH 0877/3095] Pep8 environment to run on delta code only Currently tox -epep8 will run flake8 on whole code. To make this fast, flake8 support is added for only updated(delta) code. Same can be run by "tox -efast8". Change-Id: I9c55fed32ae3060c21ec278398e9e07fb4a0fe13 Implements: BP tox-pep8-diff-part-only --- tools/fast8.sh | 15 +++++++++++++++ tox.ini | 6 ++++++ 2 files changed, 21 insertions(+) create mode 100755 tools/fast8.sh diff --git a/tools/fast8.sh b/tools/fast8.sh new file mode 100755 index 0000000000..2b3e22abda --- /dev/null +++ b/tools/fast8.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +cd $(dirname "$0")/.. +CHANGED=$(git diff --name-only HEAD~1 | tr '\n' ' ') + +# Skip files that don't exist +# (have been git rm'd) +CHECK="" +for FILE in $CHANGED; do + if [ -f "$FILE" ]; then + CHECK="$CHECK $FILE" + fi +done + +diff -u --from-file /dev/null $CHECK | flake8 --diff diff --git a/tox.ini b/tox.ini index ced4dc95f5..6dfa126d1a 100644 --- a/tox.ini +++ b/tox.ini @@ -11,6 +11,12 @@ deps = -r{toxinidir}/test-requirements.txt commands = ostestr {posargs} whitelist_externals = ostestr +[testenv:fast8] +# Use same environment directory as pep8 env to save space and install time +envdir = {toxworkdir}/pep8 +commands = + {toxinidir}/tools/fast8.sh + [testenv:pep8] commands = flake8 From ebcbd6ba7199edd7f659da4dc525c9702919d296 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Fri, 13 May 2016 13:14:02 -0700 Subject: [PATCH 0878/3095] remove #noqa from i18n imports hacking checks no longer fail on `import _` Change-Id: Idd60f0a0e71e5081691eacb39e5091ab08fcce6d --- openstackclient/common/availability_zone.py | 2 +- openstackclient/compute/v2/server.py | 2 +- openstackclient/compute/v2/service.py | 2 +- openstackclient/identity/v2_0/catalog.py | 2 +- openstackclient/identity/v2_0/ec2creds.py | 2 +- openstackclient/identity/v2_0/endpoint.py | 2 +- openstackclient/identity/v2_0/project.py | 2 +- openstackclient/identity/v2_0/role.py | 2 +- openstackclient/identity/v2_0/service.py | 2 +- openstackclient/identity/v2_0/token.py | 2 +- openstackclient/identity/v2_0/user.py | 2 +- openstackclient/identity/v3/catalog.py | 2 +- openstackclient/identity/v3/domain.py | 2 +- openstackclient/identity/v3/ec2creds.py | 2 +- openstackclient/identity/v3/group.py | 2 +- openstackclient/identity/v3/project.py | 2 +- openstackclient/identity/v3/region.py | 2 +- openstackclient/identity/v3/role.py | 2 +- openstackclient/identity/v3/user.py | 2 +- openstackclient/image/v1/image.py | 2 +- openstackclient/image/v2/image.py | 2 +- openstackclient/network/v2/port.py | 2 +- 22 files changed, 22 insertions(+), 22 deletions(-) diff --git a/openstackclient/common/availability_zone.py b/openstackclient/common/availability_zone.py index a6d11b7849..3b0270ad7f 100644 --- a/openstackclient/common/availability_zone.py +++ b/openstackclient/common/availability_zone.py @@ -20,7 +20,7 @@ from openstackclient.common import command from openstackclient.common import utils -from openstackclient.i18n import _ # noqa +from openstackclient.i18n import _ def _xform_common_availability_zone(az, zone_info): diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 40c3c2a279..8f0748e736 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -32,7 +32,7 @@ from openstackclient.common import exceptions from openstackclient.common import parseractions from openstackclient.common import utils -from openstackclient.i18n import _ # noqa +from openstackclient.i18n import _ from openstackclient.identity import common as identity_common diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index 2b51af3d64..b1ebde4eb2 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.py @@ -17,7 +17,7 @@ from openstackclient.common import command from openstackclient.common import utils -from openstackclient.i18n import _ # noqa +from openstackclient.i18n import _ class DeleteService(command.Command): diff --git a/openstackclient/identity/v2_0/catalog.py b/openstackclient/identity/v2_0/catalog.py index c927943f85..669b04f31a 100644 --- a/openstackclient/identity/v2_0/catalog.py +++ b/openstackclient/identity/v2_0/catalog.py @@ -17,7 +17,7 @@ from openstackclient.common import command from openstackclient.common import utils -from openstackclient.i18n import _ # noqa +from openstackclient.i18n import _ def _format_endpoints(eps=None): diff --git a/openstackclient/identity/v2_0/ec2creds.py b/openstackclient/identity/v2_0/ec2creds.py index a16b3d9eff..dfd675913d 100644 --- a/openstackclient/identity/v2_0/ec2creds.py +++ b/openstackclient/identity/v2_0/ec2creds.py @@ -20,7 +20,7 @@ from openstackclient.common import command from openstackclient.common import utils -from openstackclient.i18n import _ # noqa +from openstackclient.i18n import _ class CreateEC2Creds(command.ShowOne): diff --git a/openstackclient/identity/v2_0/endpoint.py b/openstackclient/identity/v2_0/endpoint.py index e92f54127a..e515fc9b3b 100644 --- a/openstackclient/identity/v2_0/endpoint.py +++ b/openstackclient/identity/v2_0/endpoint.py @@ -19,7 +19,7 @@ from openstackclient.common import command from openstackclient.common import utils -from openstackclient.i18n import _ # noqa +from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v2_0/project.py b/openstackclient/identity/v2_0/project.py index 71c77623ea..d90162c924 100644 --- a/openstackclient/identity/v2_0/project.py +++ b/openstackclient/identity/v2_0/project.py @@ -22,7 +22,7 @@ from openstackclient.common import command from openstackclient.common import parseractions from openstackclient.common import utils -from openstackclient.i18n import _ # noqa +from openstackclient.i18n import _ class CreateProject(command.ShowOne): diff --git a/openstackclient/identity/v2_0/role.py b/openstackclient/identity/v2_0/role.py index 01c340896b..1fcee15f3a 100644 --- a/openstackclient/identity/v2_0/role.py +++ b/openstackclient/identity/v2_0/role.py @@ -22,7 +22,7 @@ from openstackclient.common import command from openstackclient.common import exceptions from openstackclient.common import utils -from openstackclient.i18n import _ # noqa +from openstackclient.i18n import _ class AddRole(command.ShowOne): diff --git a/openstackclient/identity/v2_0/service.py b/openstackclient/identity/v2_0/service.py index 0b1e8dbdae..7fe66d91ee 100644 --- a/openstackclient/identity/v2_0/service.py +++ b/openstackclient/identity/v2_0/service.py @@ -21,7 +21,7 @@ from openstackclient.common import command from openstackclient.common import exceptions from openstackclient.common import utils -from openstackclient.i18n import _ # noqa +from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v2_0/token.py b/openstackclient/identity/v2_0/token.py index 1ccf2f261d..f435d7ce98 100644 --- a/openstackclient/identity/v2_0/token.py +++ b/openstackclient/identity/v2_0/token.py @@ -18,7 +18,7 @@ import six from openstackclient.common import command -from openstackclient.i18n import _ # noqa +from openstackclient.i18n import _ class IssueToken(command.ShowOne): diff --git a/openstackclient/identity/v2_0/user.py b/openstackclient/identity/v2_0/user.py index fc868bab70..bc9bf83789 100644 --- a/openstackclient/identity/v2_0/user.py +++ b/openstackclient/identity/v2_0/user.py @@ -21,7 +21,7 @@ from openstackclient.common import command from openstackclient.common import utils -from openstackclient.i18n import _ # noqa +from openstackclient.i18n import _ class CreateUser(command.ShowOne): diff --git a/openstackclient/identity/v3/catalog.py b/openstackclient/identity/v3/catalog.py index 795f5d5c6d..38a57d900e 100644 --- a/openstackclient/identity/v3/catalog.py +++ b/openstackclient/identity/v3/catalog.py @@ -17,7 +17,7 @@ from openstackclient.common import command from openstackclient.common import utils -from openstackclient.i18n import _ # noqa +from openstackclient.i18n import _ def _format_endpoints(eps=None): diff --git a/openstackclient/identity/v3/domain.py b/openstackclient/identity/v3/domain.py index bf248fab9a..7fcab4f1c1 100644 --- a/openstackclient/identity/v3/domain.py +++ b/openstackclient/identity/v3/domain.py @@ -22,7 +22,7 @@ from openstackclient.common import command from openstackclient.common import utils -from openstackclient.i18n import _ # noqa +from openstackclient.i18n import _ class CreateDomain(command.ShowOne): diff --git a/openstackclient/identity/v3/ec2creds.py b/openstackclient/identity/v3/ec2creds.py index 777c0430e7..a12b2d3b4e 100644 --- a/openstackclient/identity/v3/ec2creds.py +++ b/openstackclient/identity/v3/ec2creds.py @@ -16,7 +16,7 @@ from openstackclient.common import command from openstackclient.common import utils -from openstackclient.i18n import _ # noqa +from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py index a4cdd583e8..3c2435316d 100644 --- a/openstackclient/identity/v3/group.py +++ b/openstackclient/identity/v3/group.py @@ -22,7 +22,7 @@ from openstackclient.common import command from openstackclient.common import utils -from openstackclient.i18n import _ # noqa +from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index a379c6fa34..4990b1b9a3 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -22,7 +22,7 @@ from openstackclient.common import command from openstackclient.common import parseractions from openstackclient.common import utils -from openstackclient.i18n import _ # noqa +from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v3/region.py b/openstackclient/identity/v3/region.py index ec5042286b..053e4b31d2 100644 --- a/openstackclient/identity/v3/region.py +++ b/openstackclient/identity/v3/region.py @@ -17,7 +17,7 @@ from openstackclient.common import command from openstackclient.common import utils -from openstackclient.i18n import _ # noqa +from openstackclient.i18n import _ class CreateRegion(command.ShowOne): diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index 1195ab21a5..f93c9d804f 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -22,7 +22,7 @@ from openstackclient.common import command from openstackclient.common import utils -from openstackclient.i18n import _ # noqa +from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index 93b3309077..9a7ced9279 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -23,7 +23,7 @@ from openstackclient.common import command from openstackclient.common import utils -from openstackclient.i18n import _ # noqa +from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index 0d0855974c..14e894b034 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -31,7 +31,7 @@ from openstackclient.common import command from openstackclient.common import parseractions from openstackclient.common import utils -from openstackclient.i18n import _ # noqa +from openstackclient.i18n import _ DEFAULT_CONTAINER_FORMAT = 'bare' diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 40ddd4b9ce..818f375a0f 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -25,7 +25,7 @@ from openstackclient.common import exceptions from openstackclient.common import parseractions from openstackclient.common import utils -from openstackclient.i18n import _ # noqa +from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 9b6161fdd9..3cc76a6e4e 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -20,7 +20,7 @@ from openstackclient.common import exceptions from openstackclient.common import parseractions from openstackclient.common import utils -from openstackclient.i18n import _ # noqa +from openstackclient.i18n import _ from openstackclient.identity import common as identity_common From 2b4c543cddbb910d801beea079e861b041e8170a Mon Sep 17 00:00:00 2001 From: xiexs Date: Wed, 9 Dec 2015 18:09:08 +0800 Subject: [PATCH 0879/3095] Refactor TestAddProjectToImage with FakeImage class Change-Id: I6733601f12389eb744dbf0be64b7c556356b4730 Co-Authored-By: ting wang Implements: blueprint improve-image-unittest-framework --- openstackclient/tests/image/v2/test_image.py | 32 +++++++++++--------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index 0248f30b9a..a60859aafd 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -341,28 +341,31 @@ def test_image_create_dead_options(self): class TestAddProjectToImage(TestImage): + _image = image_fakes.FakeImage.create_one_image() + columns = ( 'image_id', 'member_id', 'status', ) + datalist = ( - image_fakes.image_id, + _image.id, identity_fakes.project_id, - image_fakes.member_status, + image_fakes.member_status ) def setUp(self): super(TestAddProjectToImage, self).setUp() # This is the return value for utils.find_resource() - self.images_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(image_fakes.IMAGE), - loaded=True, - ) + self.images_mock.get.return_value = self._image + + # Update the image_id in the MEMBER dict + self.new_member = copy.deepcopy(image_fakes.MEMBER) + self.new_member['image_id'] = self._image.id self.image_members_mock.create.return_value = fakes.FakeModel( - copy.deepcopy(image_fakes.MEMBER), + self.new_member, ) self.project_mock.get.return_value = fakes.FakeResource( None, @@ -379,11 +382,11 @@ def setUp(self): def test_add_project_to_image_no_option(self): arglist = [ - image_fakes.image_id, + self._image.id, identity_fakes.project_id, ] verifylist = [ - ('image', image_fakes.image_id), + ('image', self._image.id), ('project', identity_fakes.project_id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -393,20 +396,21 @@ def test_add_project_to_image_no_option(self): # data to be shown. columns, data = self.cmd.take_action(parsed_args) self.image_members_mock.create.assert_called_with( - image_fakes.image_id, + self._image.id, identity_fakes.project_id ) + self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, data) def test_add_project_to_image_with_option(self): arglist = [ - image_fakes.image_id, + self._image.id, identity_fakes.project_id, '--project-domain', identity_fakes.domain_id, ] verifylist = [ - ('image', image_fakes.image_id), + ('image', self._image.id), ('project', identity_fakes.project_id), ('project_domain', identity_fakes.domain_id), ] @@ -417,7 +421,7 @@ def test_add_project_to_image_with_option(self): # data to be shown. columns, data = self.cmd.take_action(parsed_args) self.image_members_mock.create.assert_called_with( - image_fakes.image_id, + self._image.id, identity_fakes.project_id ) self.assertEqual(self.columns, columns) From 2724eaac5d1e8b341cdd85f4e5b6817ae7f48394 Mon Sep 17 00:00:00 2001 From: "ting.wang" Date: Sat, 14 May 2016 14:03:50 +0800 Subject: [PATCH 0880/3095] Refactor TestImageList with FakeImage class Change-Id: Ia60f75f65bba4c25a0a87b570b081424f982efca Co-Authored-By: xiexs Implements: blueprint improve-image-unittest-framework --- openstackclient/tests/image/v2/test_image.py | 39 +++++++++++--------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index a60859aafd..67b86d111a 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -20,6 +20,7 @@ from glanceclient.v2 import schemas from openstackclient.common import exceptions +from openstackclient.common import utils as common_utils from openstackclient.image.v2 import image from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes @@ -472,25 +473,26 @@ def test_image_delete_multi_images(self): class TestImageList(TestImage): + _image = image_fakes.FakeImage.create_one_image() + columns = ( 'ID', 'Name', 'Status', ) + datalist = ( - ( - image_fakes.image_id, - image_fakes.image_name, - '', - ), - ) + _image.id, + _image.name, + '', + ), def setUp(self): super(TestImageList, self).setUp() self.api_mock = mock.Mock() self.api_mock.image_list.side_effect = [ - [copy.deepcopy(image_fakes.IMAGE)], [], + [image_fakes.FakeImage.get_image_info(self._image)], [], ] self.app.client_manager.image.api = self.api_mock @@ -615,23 +617,24 @@ def test_image_list_long_option(self): self.assertEqual(collist, columns) datalist = (( - image_fakes.image_id, - image_fakes.image_name, - '', + self._image.id, + self._image.name, '', '', '', - 'public', - False, - image_fakes.image_owner, '', + self._image.visibility, + self._image.protected, + self._image.owner, + common_utils.format_list(self._image.tags), ), ) self.assertEqual(datalist, tuple(data)) @mock.patch('openstackclient.api.utils.simple_filter') def test_image_list_property_option(self, sf_mock): sf_mock.return_value = [ - copy.deepcopy(image_fakes.IMAGE), + copy.deepcopy( + image_fakes.FakeImage.get_image_info(self._image)), ] arglist = [ @@ -648,7 +651,7 @@ def test_image_list_property_option(self, sf_mock): columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with() sf_mock.assert_called_with( - [image_fakes.IMAGE], + [image_fakes.FakeImage.get_image_info(self._image)], attr='a', value='1', property_field='properties', @@ -660,7 +663,8 @@ def test_image_list_property_option(self, sf_mock): @mock.patch('openstackclient.common.utils.sort_items') def test_image_list_sort_option(self, si_mock): si_mock.return_value = [ - copy.deepcopy(image_fakes.IMAGE) + copy.deepcopy( + image_fakes.FakeImage.get_image_info(self._image)) ] arglist = ['--sort', 'name:asc'] @@ -673,10 +677,9 @@ def test_image_list_sort_option(self, si_mock): columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with() si_mock.assert_called_with( - [image_fakes.IMAGE], + [image_fakes.FakeImage.get_image_info(self._image)], 'name:asc' ) - self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) From fd5fd924d152338204fcf69673fedd31a3904977 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Fri, 15 Apr 2016 07:36:43 -0500 Subject: [PATCH 0881/3095] Additional network protocol support Add the following network protocol support to the "os security group rule create" command: - Add "--icmp-type" and "--icmp-code" options for Network v2 only. These options can be used to set the ICMP type and code for ICMP IP protocols. - Change the "--proto" option to "--protocol". Using the "--proto" option is still supported, but is no longer documented and may be deprecated in a future release. - Add the following Network v2 IP protocols to the "--protocol" option: "ah", "dccp", "egp", "esp", "gre", "igmp", "ipv6-encap", "ipv6-frag", "ipv6-icmp", "ipv6-nonxt", "ipv6-opts", "ipv6-route", "ospf", "pgm", "rsvp", "sctp", "udplite", "vrrp" and integer representations [0-255]. The "os security group rule list" command now supports displaying the ICMP type and code for security group rules with the ICMP IP protocols. Change-Id: Ic84bc92bc7aa5ac08f6ef91660eb6c125a200eb3 Closes-Bug: #1519512 Implements: blueprint neutron-client --- .../command-objects/security-group-rule.rst | 42 ++- .../network/v2/test_security_group_rule.py | 2 +- .../network/v2/security_group_rule.py | 184 ++++++++++--- openstackclient/tests/network/v2/fakes.py | 4 +- .../network/v2/test_security_group_rule.py | 242 +++++++++++++++++- .../notes/bug-1519512-dbf4368fe10dc495.yaml | 24 ++ 6 files changed, 444 insertions(+), 54 deletions(-) create mode 100644 releasenotes/notes/bug-1519512-dbf4368fe10dc495.yaml diff --git a/doc/source/command-objects/security-group-rule.rst b/doc/source/command-objects/security-group-rule.rst index b0ac3c9449..97cce35cf5 100644 --- a/doc/source/command-objects/security-group-rule.rst +++ b/doc/source/command-objects/security-group-rule.rst @@ -16,18 +16,14 @@ Create a new security group rule .. code:: bash os security group rule create - [--proto ] [--src-ip | --src-group ] - [--dst-port ] + [--dst-port | [--icmp-type [--icmp-code ]]] + [--protocol ] [--ingress | --egress] [--ethertype ] [--project [--project-domain ]] -.. option:: --proto - - IP protocol (icmp, tcp, udp; default: tcp) - .. option:: --src-ip Source IP address block @@ -39,8 +35,35 @@ Create a new security group rule .. option:: --dst-port - Destination port, may be a single port or port range: 137:139 - (only required for IP protocols tcp and udp) + Destination port, may be a single port or a starting and + ending port range: 137:139. Required for IP protocols TCP + and UDP. Ignored for ICMP IP protocols. + +.. option:: --icmp-type + + ICMP type for ICMP IP protocols + + *Network version 2 only* + +.. option:: --icmp-code + + ICMP code for ICMP IP protocols + + *Network version 2 only* + +.. option:: --protocol + + IP protocol (icmp, tcp, udp; default: tcp) + + *Compute version 2* + + IP protocol (ah, dccp, egp, esp, gre, icmp, igmp, + ipv6-encap, ipv6-frag, ipv6-icmp, ipv6-nonxt, + ipv6-opts, ipv6-route, ospf, pgm, rsvp, sctp, tcp, + udp, udplite, vrrp and integer representations [0-255]; + default: tcp) + + *Network version 2* .. option:: --ingress @@ -56,7 +79,8 @@ Create a new security group rule .. option:: --ethertype - Ethertype of network traffic (IPv4, IPv6; default: IPv4) + Ethertype of network traffic + (IPv4, IPv6; default: based on IP protocol) *Network version 2 only* diff --git a/functional/tests/network/v2/test_security_group_rule.py b/functional/tests/network/v2/test_security_group_rule.py index 26e6e0e457..64e1fcdf6a 100644 --- a/functional/tests/network/v2/test_security_group_rule.py +++ b/functional/tests/network/v2/test_security_group_rule.py @@ -37,7 +37,7 @@ def setUpClass(cls): opts = cls.get_show_opts(cls.ID_FIELD) raw_output = cls.openstack('security group rule create ' + cls.SECURITY_GROUP_NAME + - ' --proto tcp --dst-port 80:80' + + ' --protocol tcp --dst-port 80:80' + ' --ingress --ethertype IPv4' + opts) cls.SECURITY_GROUP_RULE_ID = raw_output.strip('\n') diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index 5b22a0dd83..92dd1e5a2d 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -36,9 +36,21 @@ def _format_security_group_rule_show(obj): def _format_network_port_range(rule): + # Display port range or ICMP type and code. For example: + # - ICMP type: 'type=3' + # - ICMP type and code: 'type=3:code=0' + # - ICMP code: Not supported + # - Matching port range: '443:443' + # - Different port range: '22:24' + # - Single port: '80:80' + # - No port range: '' port_range = '' - if (rule.protocol != 'icmp' and - (rule.port_range_min or rule.port_range_max)): + if _is_icmp_protocol(rule.protocol): + if rule.port_range_min: + port_range += 'type=' + str(rule.port_range_min) + if rule.port_range_max: + port_range += ':code=' + str(rule.port_range_max) + elif rule.port_range_min or rule.port_range_max: port_range_min = str(rule.port_range_min) port_range_max = str(rule.port_range_max) if rule.port_range_min is None: @@ -61,6 +73,17 @@ def _convert_to_lowercase(string): return string.lower() +def _is_icmp_protocol(protocol): + # NOTE(rtheis): Neutron has deprecated protocol icmpv6. + # However, while the OSC CLI doesn't document the protocol, + # the code must still handle it. In addition, handle both + # protocol names and numbers. + if protocol in ['icmp', 'icmpv6', 'ipv6-icmp', '1', '58']: + return True + else: + return False + + class CreateSecurityGroupRule(common.NetworkAndComputeShowOne): """Create a new security group rule""" @@ -68,19 +91,7 @@ def update_parser_common(self, parser): parser.add_argument( 'group', metavar='', - help='Create rule in this security group (name or ID)', - ) - # TODO(rtheis): Add support for additional protocols for network. - # Until then, continue enforcing the compute choices. When additional - # protocols are added, the default ethertype must be determined - # based on the protocol. - parser.add_argument( - "--proto", - metavar="", - default="tcp", - choices=['icmp', 'tcp', 'udp'], - type=_convert_to_lowercase, - help=_("IP protocol (icmp, tcp, udp; default: tcp)") + help=_("Create rule in this security group (name or ID)") ) source_group = parser.add_mutually_exclusive_group() source_group.add_argument( @@ -94,17 +105,49 @@ def update_parser_common(self, parser): metavar="", help=_("Source security group (name or ID)") ) - parser.add_argument( - "--dst-port", - metavar="", - default=(0, 0), - action=parseractions.RangeAction, - help=_("Destination port, may be a single port or port range: " - "137:139 (only required for IP protocols tcp and udp)") - ) return parser def update_parser_network(self, parser): + parser.add_argument( + '--dst-port', + metavar='', + action=parseractions.RangeAction, + help=_("Destination port, may be a single port or a starting and " + "ending port range: 137:139. Required for IP protocols TCP " + "and UDP. Ignored for ICMP IP protocols.") + ) + parser.add_argument( + '--icmp-type', + metavar='', + type=int, + help=_("ICMP type for ICMP IP protocols") + ) + parser.add_argument( + '--icmp-code', + metavar='', + type=int, + help=_("ICMP code for ICMP IP protocols") + ) + # NOTE(rtheis): Support either protocol option name for now. + # However, consider deprecating and then removing --proto in + # a future release. + protocol_group = parser.add_mutually_exclusive_group() + protocol_group.add_argument( + '--protocol', + metavar='', + type=_convert_to_lowercase, + help=_("IP protocol (ah, dccp, egp, esp, gre, icmp, igmp, " + "ipv6-encap, ipv6-frag, ipv6-icmp, ipv6-nonxt, " + "ipv6-opts, ipv6-route, ospf, pgm, rsvp, sctp, tcp, " + "udp, udplite, vrrp and integer representations [0-255]; " + "default: tcp)") + ) + protocol_group.add_argument( + '--proto', + metavar='', + type=_convert_to_lowercase, + help=argparse.SUPPRESS + ) direction_group = parser.add_mutually_exclusive_group() direction_group.add_argument( '--ingress', @@ -120,7 +163,8 @@ def update_parser_network(self, parser): '--ethertype', metavar='', choices=['IPv4', 'IPv6'], - help=_("Ethertype of network traffic (IPv4, IPv6; default: IPv4)") + help=_("Ethertype of network traffic " + "(IPv4, IPv6; default: based on IP protocol)") ) parser.add_argument( '--project', @@ -130,6 +174,55 @@ def update_parser_network(self, parser): identity_common.add_project_domain_option_to_parser(parser) return parser + def update_parser_compute(self, parser): + parser.add_argument( + '--dst-port', + metavar='', + default=(0, 0), + action=parseractions.RangeAction, + help=_("Destination port, may be a single port or a starting and " + "ending port range: 137:139. Required for IP protocols TCP " + "and UDP. Ignored for ICMP IP protocols.") + ) + # NOTE(rtheis): Support either protocol option name for now. + # However, consider deprecating and then removing --proto in + # a future release. + protocol_group = parser.add_mutually_exclusive_group() + protocol_group.add_argument( + '--protocol', + metavar='', + choices=['icmp', 'tcp', 'udp'], + type=_convert_to_lowercase, + help=_("IP protocol (icmp, tcp, udp; default: tcp)") + ) + protocol_group.add_argument( + '--proto', + metavar='', + choices=['icmp', 'tcp', 'udp'], + type=_convert_to_lowercase, + help=argparse.SUPPRESS + ) + return parser + + def _get_protocol(self, parsed_args): + protocol = 'tcp' + if parsed_args.protocol is not None: + protocol = parsed_args.protocol + if parsed_args.proto is not None: + protocol = parsed_args.proto + return protocol + + def _is_ipv6_protocol(self, protocol): + # NOTE(rtheis): Neutron has deprecated protocol icmpv6. + # However, while the OSC CLI doesn't document the protocol, + # the code must still handle it. In addition, handle both + # protocol names and numbers. + if (protocol.startswith('ipv6-') or + protocol in ['icmpv6', '41', '43', '44', '58', '59', '60']): + return True + else: + return False + def take_action_network(self, client, parsed_args): # Get the security group ID to hold the rule. security_group_id = client.find_security_group( @@ -139,24 +232,50 @@ def take_action_network(self, client, parsed_args): # Build the create attributes. attrs = {} + attrs['protocol'] = self._get_protocol(parsed_args) + # NOTE(rtheis): A direction must be specified and ingress # is the default. if parsed_args.ingress or not parsed_args.egress: attrs['direction'] = 'ingress' if parsed_args.egress: attrs['direction'] = 'egress' + + # NOTE(rtheis): Use ethertype specified else default based + # on IP protocol. if parsed_args.ethertype: attrs['ethertype'] = parsed_args.ethertype + elif self._is_ipv6_protocol(attrs['protocol']): + attrs['ethertype'] = 'IPv6' else: - # NOTE(rtheis): Default based on protocol is IPv4 for now. - # Once IPv6 protocols are added, this will need to be updated. attrs['ethertype'] = 'IPv4' - # TODO(rtheis): Add port range support (type and code) for icmp - # protocol. Until then, continue ignoring the port range. - if parsed_args.proto != 'icmp': + + # NOTE(rtheis): Validate the port range and ICMP type and code. + # It would be ideal if argparse could do this. + if parsed_args.dst_port and (parsed_args.icmp_type or + parsed_args.icmp_code): + msg = _('Argument --dst-port not allowed with arguments ' + '--icmp-type and --icmp-code') + raise exceptions.CommandError(msg) + if parsed_args.icmp_type is None and parsed_args.icmp_code is not None: + msg = _('Argument --icmp-type required with argument --icmp-code') + raise exceptions.CommandError(msg) + is_icmp_protocol = _is_icmp_protocol(attrs['protocol']) + if not is_icmp_protocol and (parsed_args.icmp_type or + parsed_args.icmp_code): + msg = _('ICMP IP protocol required with arguments ' + '--icmp-type and --icmp-code') + raise exceptions.CommandError(msg) + # NOTE(rtheis): For backwards compatibility, continue ignoring + # the destination port range when an ICMP IP protocol is specified. + if parsed_args.dst_port and not is_icmp_protocol: attrs['port_range_min'] = parsed_args.dst_port[0] attrs['port_range_max'] = parsed_args.dst_port[1] - attrs['protocol'] = parsed_args.proto + if parsed_args.icmp_type: + attrs['port_range_min'] = parsed_args.icmp_type + if parsed_args.icmp_code: + attrs['port_range_max'] = parsed_args.icmp_code + if parsed_args.src_group is not None: attrs['remote_group_id'] = client.find_security_group( parsed_args.src_group, @@ -187,7 +306,8 @@ def take_action_compute(self, client, parsed_args): client.security_groups, parsed_args.group, ) - if parsed_args.proto == 'icmp': + protocol = self._get_protocol(parsed_args) + if protocol == 'icmp': from_port, to_port = -1, -1 else: from_port, to_port = parsed_args.dst_port @@ -203,7 +323,7 @@ def take_action_compute(self, client, parsed_args): src_ip = '0.0.0.0/0' obj = client.security_group_rules.create( group.id, - parsed_args.proto, + protocol, from_port, to_port, src_ip, diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 7c4604bd84..a0baf78479 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -496,8 +496,8 @@ def create_one_security_group_rule(attrs=None): 'direction': 'ingress', 'ethertype': 'IPv4', 'id': 'security-group-rule-id-' + uuid.uuid4().hex, - 'port_range_max': 0, - 'port_range_min': 0, + 'port_range_max': None, + 'port_range_min': None, 'protocol': 'tcp', 'remote_group_id': None, 'remote_ip_prefix': '0.0.0.0/0', diff --git a/openstackclient/tests/network/v2/test_security_group_rule.py b/openstackclient/tests/network/v2/test_security_group_rule.py index bd903f9e72..2a64b88442 100644 --- a/openstackclient/tests/network/v2/test_security_group_rule.py +++ b/openstackclient/tests/network/v2/test_security_group_rule.py @@ -14,6 +14,7 @@ import copy import mock +from openstackclient.common import exceptions from openstackclient.network import utils as network_utils from openstackclient.network.v2 import security_group_rule from openstackclient.tests.compute.v2 import fakes as compute_fakes @@ -131,22 +132,40 @@ def test_create_all_source_options(self): self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, []) - def test_create_bad_protocol(self): + def test_create_bad_ethertype(self): arglist = [ - '--protocol', 'foo', + '--ethertype', 'foo', self._security_group.id, ] self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, []) - def test_create_bad_ethertype(self): + def test_create_all_protocol_options(self): arglist = [ - '--ethertype', 'foo', + '--protocol', 'tcp', + '--proto', 'tcp', self._security_group.id, ] self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, []) + def test_create_all_port_range_options(self): + arglist = [ + '--dst-port', '80:80', + '--icmp-type', '3', + '--icmp-code', '1', + self._security_group.id, + ] + verifylist = [ + ('dst_port', (80, 80)), + ('icmp_type', 3), + ('icmp_code', 1), + ('group', self._security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + def test_create_default_rule(self): self._setup_security_group_rule({ 'port_range_max': 443, @@ -177,6 +196,36 @@ def test_create_default_rule(self): self.assertEqual(self.expected_columns, columns) self.assertEqual(self.expected_data, data) + def test_create_proto_option(self): + self._setup_security_group_rule({ + 'protocol': 'icmp', + 'remote_ip_prefix': '10.0.2.0/24', + }) + arglist = [ + '--proto', self._security_group_rule.protocol, + '--src-ip', self._security_group_rule.remote_ip_prefix, + self._security_group.id, + ] + verifylist = [ + ('proto', self._security_group_rule.protocol), + ('protocol', None), + ('src_ip', self._security_group_rule.remote_ip_prefix), + ('group', self._security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_security_group_rule.assert_called_once_with(**{ + 'direction': self._security_group_rule.direction, + 'ethertype': self._security_group_rule.ethertype, + 'protocol': self._security_group_rule.protocol, + 'remote_ip_prefix': self._security_group_rule.remote_ip_prefix, + 'security_group_id': self._security_group.id, + }) + self.assertEqual(self.expected_columns, columns) + self.assertEqual(self.expected_data, data) + def test_create_source_group(self): self._setup_security_group_rule({ 'port_range_max': 22, @@ -215,17 +264,15 @@ def test_create_source_group(self): def test_create_source_ip(self): self._setup_security_group_rule({ 'protocol': 'icmp', - 'port_range_max': -1, - 'port_range_min': -1, 'remote_ip_prefix': '10.0.2.0/24', }) arglist = [ - '--proto', self._security_group_rule.protocol, + '--protocol', self._security_group_rule.protocol, '--src-ip', self._security_group_rule.remote_ip_prefix, self._security_group.id, ] verifylist = [ - ('proto', self._security_group_rule.protocol), + ('protocol', self._security_group_rule.protocol), ('src_ip', self._security_group_rule.remote_ip_prefix), ('group', self._security_group.id), ] @@ -249,6 +296,7 @@ def test_create_network_options(self): 'ethertype': 'IPv6', 'port_range_max': 443, 'port_range_min': 443, + 'protocol': '6', 'remote_group_id': None, 'remote_ip_prefix': None, }) @@ -258,6 +306,7 @@ def test_create_network_options(self): '--ethertype', self._security_group_rule.ethertype, '--project', identity_fakes.project_name, '--project-domain', identity_fakes.domain_name, + '--protocol', self._security_group_rule.protocol, self._security_group.id, ] verifylist = [ @@ -267,6 +316,7 @@ def test_create_network_options(self): ('ethertype', self._security_group_rule.ethertype), ('project', identity_fakes.project_name), ('project_domain', identity_fakes.domain_name), + ('protocol', self._security_group_rule.protocol), ('group', self._security_group.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -285,6 +335,136 @@ def test_create_network_options(self): self.assertEqual(self.expected_columns, columns) self.assertEqual(self.expected_data, data) + def test_create_tcp_with_icmp_type(self): + arglist = [ + '--protocol', 'tcp', + '--icmp-type', '15', + self._security_group.id, + ] + verifylist = [ + ('protocol', 'tcp'), + ('icmp_type', 15), + ('group', self._security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + + def test_create_icmp_code(self): + arglist = [ + '--protocol', '1', + '--icmp-code', '1', + self._security_group.id, + ] + verifylist = [ + ('protocol', '1'), + ('icmp_code', 1), + ('group', self._security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + + def test_create_icmp_type(self): + self._setup_security_group_rule({ + 'port_range_min': 15, + 'protocol': 'icmp', + 'remote_ip_prefix': '0.0.0.0/0', + }) + arglist = [ + '--icmp-type', str(self._security_group_rule.port_range_min), + '--protocol', self._security_group_rule.protocol, + self._security_group.id, + ] + verifylist = [ + ('dst_port', None), + ('icmp_type', self._security_group_rule.port_range_min), + ('icmp_code', None), + ('protocol', self._security_group_rule.protocol), + ('group', self._security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_security_group_rule.assert_called_once_with(**{ + 'direction': self._security_group_rule.direction, + 'ethertype': self._security_group_rule.ethertype, + 'port_range_min': self._security_group_rule.port_range_min, + 'protocol': self._security_group_rule.protocol, + 'remote_ip_prefix': self._security_group_rule.remote_ip_prefix, + 'security_group_id': self._security_group.id, + }) + self.assertEqual(self.expected_columns, columns) + self.assertEqual(self.expected_data, data) + + def test_create_ipv6_icmp_type_code(self): + self._setup_security_group_rule({ + 'ethertype': 'IPv6', + 'port_range_min': 139, + 'port_range_max': 2, + 'protocol': 'ipv6-icmp', + }) + arglist = [ + '--icmp-type', str(self._security_group_rule.port_range_min), + '--icmp-code', str(self._security_group_rule.port_range_max), + '--protocol', self._security_group_rule.protocol, + self._security_group.id, + ] + verifylist = [ + ('dst_port', None), + ('icmp_type', self._security_group_rule.port_range_min), + ('icmp_code', self._security_group_rule.port_range_max), + ('protocol', self._security_group_rule.protocol), + ('group', self._security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_security_group_rule.assert_called_once_with(**{ + 'direction': self._security_group_rule.direction, + 'ethertype': self._security_group_rule.ethertype, + 'port_range_min': self._security_group_rule.port_range_min, + 'port_range_max': self._security_group_rule.port_range_max, + 'protocol': self._security_group_rule.protocol, + 'security_group_id': self._security_group.id, + }) + self.assertEqual(self.expected_columns, columns) + self.assertEqual(self.expected_data, data) + + def test_create_icmpv6_type(self): + self._setup_security_group_rule({ + 'ethertype': 'IPv6', + 'port_range_min': 139, + 'protocol': 'icmpv6', + }) + arglist = [ + '--icmp-type', str(self._security_group_rule.port_range_min), + '--protocol', self._security_group_rule.protocol, + self._security_group.id, + ] + verifylist = [ + ('dst_port', None), + ('icmp_type', self._security_group_rule.port_range_min), + ('icmp_code', None), + ('protocol', self._security_group_rule.protocol), + ('group', self._security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_security_group_rule.assert_called_once_with(**{ + 'direction': self._security_group_rule.direction, + 'ethertype': self._security_group_rule.ethertype, + 'port_range_min': self._security_group_rule.port_range_min, + 'protocol': self._security_group_rule.protocol, + 'security_group_id': self._security_group.id, + }) + self.assertEqual(self.expected_columns, columns) + self.assertEqual(self.expected_data, data) + class TestCreateSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): @@ -337,10 +517,21 @@ def test_create_bad_protocol(self): self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, []) + def test_create_all_protocol_options(self): + arglist = [ + '--protocol', 'tcp', + '--proto', 'tcp', + self._security_group.id, + ] + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, arglist, []) + def test_create_network_options(self): arglist = [ '--ingress', '--ethertype', 'IPv4', + '--icmp-type', '3', + '--icmp-code', '11', '--project', identity_fakes.project_name, '--project-domain', identity_fakes.domain_name, self._security_group.id, @@ -409,6 +600,38 @@ def test_create_source_group(self): self.assertEqual(expected_data, data) def test_create_source_ip(self): + expected_columns, expected_data = self._setup_security_group_rule({ + 'ip_protocol': 'icmp', + 'from_port': -1, + 'to_port': -1, + 'ip_range': {'cidr': '10.0.2.0/24'}, + }) + arglist = [ + '--protocol', self._security_group_rule.ip_protocol, + '--src-ip', self._security_group_rule.ip_range['cidr'], + self._security_group.id, + ] + verifylist = [ + ('protocol', self._security_group_rule.ip_protocol), + ('src_ip', self._security_group_rule.ip_range['cidr']), + ('group', self._security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.compute.security_group_rules.create.assert_called_once_with( + self._security_group.id, + self._security_group_rule.ip_protocol, + self._security_group_rule.from_port, + self._security_group_rule.to_port, + self._security_group_rule.ip_range['cidr'], + None, + ) + self.assertEqual(expected_columns, columns) + self.assertEqual(expected_data, data) + + def test_create_proto_option(self): expected_columns, expected_data = self._setup_security_group_rule({ 'ip_protocol': 'icmp', 'from_port': -1, @@ -422,6 +645,7 @@ def test_create_source_ip(self): ] verifylist = [ ('proto', self._security_group_rule.ip_protocol), + ('protocol', None), ('src_ip', self._security_group_rule.ip_range['cidr']), ('group', self._security_group.id), ] @@ -522,8 +746,6 @@ class TestListSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): _security_group_rule_icmp = \ network_fakes.FakeSecurityGroupRule.create_one_security_group_rule({ 'protocol': 'icmp', - 'port_range_max': -1, - 'port_range_min': -1, 'remote_ip_prefix': '10.0.2.0/24', 'security_group_id': _security_group.id, }) diff --git a/releasenotes/notes/bug-1519512-dbf4368fe10dc495.yaml b/releasenotes/notes/bug-1519512-dbf4368fe10dc495.yaml new file mode 100644 index 0000000000..f8f2387fea --- /dev/null +++ b/releasenotes/notes/bug-1519512-dbf4368fe10dc495.yaml @@ -0,0 +1,24 @@ +--- +features: + - Add ``--icmp-type`` and ``--icmp-code`` options to the + ``security group rule create`` command for Network v2 only. + These options can be used to set ICMP type and code for + ICMP IP protocols. + [Bug `1519512 `_] + - The following Network v2 IP protocols are supported by the + ``security group rule create`` command ``--protocol`` option, + ``ah``, ``dccp``, ``egp``, ``esp``, ``gre``, ``igmp``, + ``ipv6-encap``, ``ipv6-frag``, ``ipv6-icmp``, ``ipv6-nonxt``, + ``ipv6-opts``, ``ipv6-route``, ``ospf``, ``pgm``, ``rsvp``, ``sctp``, + ``udplite``, ``vrrp`` and integer representations [0-255]. + [Bug `1519512 `_] + - The ``security group rule list`` command supports displaying + the ICMP type and code for security group rules with the + ICMP IP protocols. + [Bug `1519512 `_] +upgrade: + - Changed the ``security group rule create`` command ``--proto`` + option to ``--protocol``. Using the ``--proto`` option is still + supported, but is no longer documented and may be deprecated in + a future release. + [Bug `1519512 `_] From 2629fda94708fb0e335087b5e158629b15a95e4b Mon Sep 17 00:00:00 2001 From: Adriano Fialho Date: Sat, 14 May 2016 13:22:13 -0300 Subject: [PATCH 0882/3095] Changed the nomenclature of credentials command Removed the "s" of the "credentials command" in the documentation and renaming the file to credential.rst (instead of credentials) Change-Id: If1df15b9a630a5452e05d14fb45e9ebb124583a7 --- .../{credentials.rst => credential.rst} | 10 +++++----- doc/source/commands.rst | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) rename doc/source/command-objects/{credentials.rst => credential.rst} (75%) diff --git a/doc/source/command-objects/credentials.rst b/doc/source/command-objects/credential.rst similarity index 75% rename from doc/source/command-objects/credentials.rst rename to doc/source/command-objects/credential.rst index 9f4aabe4f3..ed8a00dbd7 100644 --- a/doc/source/command-objects/credentials.rst +++ b/doc/source/command-objects/credential.rst @@ -1,27 +1,27 @@ =========== -credentials +credential =========== Identity v3 -credentials create +credential create ------------------ .. ''[consider rolling the ec2 creds into this too]'' .. code:: bash - os credentials create + os credential create --x509 [] [] -credentials show +credential show ---------------- .. code:: bash - os credentials show + os credential show [--token] [--user] [--x509 [--root]] diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 7e6e558900..12542d1c73 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -83,7 +83,7 @@ referring to both Compute and Volume quotas. * ``console url``: (**Compute**) server remote console URL * ``consumer``: (**Identity**) OAuth-based delegatee * ``container``: (**Object Storage**) a grouping of objects -* ``credentials``: (**Identity**) specific to identity providers +* ``credential``: (**Identity**) specific to identity providers * ``domain``: (**Identity**) a grouping of projects * ``ec2 credentials``: (**Identity**) AWS EC2-compatible credentials * ``endpoint``: (**Identity**) the base URL used to contact a specific service From 7a252128f98ecc727b47f8bac13f2534bf9e016c Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sun, 15 May 2016 02:57:05 +0000 Subject: [PATCH 0883/3095] Updated from global requirements Change-Id: I61b3e84cbca369a5581aff38d65bb51af96e8baa --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 39d250ee6d..ffc8326079 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -23,7 +23,7 @@ bandit>=1.0.1 # Apache-2.0 python-barbicanclient>=4.0.0 # Apache-2.0 python-congressclient<2000,>=1.0.0 # Apache-2.0 python-designateclient>=1.5.0 # Apache-2.0 -python-heatclient>=0.6.0 # Apache-2.0 +python-heatclient>=1.1.0 # Apache-2.0 python-ironicclient>=1.1.0 # Apache-2.0 python-ironic-inspector-client>=1.5.0 # Apache-2.0 python-mistralclient>=1.0.0 # Apache-2.0 From bc93ebfe5c0fa4d29b79fa3fd93ec603425997ea Mon Sep 17 00:00:00 2001 From: Hideki Saito Date: Sat, 14 May 2016 20:48:43 +0900 Subject: [PATCH 0884/3095] Added --no-route to the router set command Deprecated --clear-routes Closes-Bug #1565034 Change-Id: I4a8975edc026aecd2a362fd1929c984cfab8ade6 --- doc/source/command-objects/router.rst | 4 +-- openstackclient/network/v2/router.py | 20 +++++++++-- .../tests/network/v2/test_router.py | 36 +++++++++++++++++++ .../notes/bug-1565034-dd404bfb42d7778d.yaml | 5 +++ 4 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/bug-1565034-dd404bfb42d7778d.yaml diff --git a/doc/source/command-objects/router.rst b/doc/source/command-objects/router.rst index 1bb9341ebf..9ca7661ede 100644 --- a/doc/source/command-objects/router.rst +++ b/doc/source/command-objects/router.rst @@ -186,7 +186,7 @@ Set router properties [--name ] [--enable | --disable] [--distributed | --centralized] - [--route destination=,gateway= | --clear-routes] + [--route destination=,gateway= | --no-route] .. option:: --name @@ -216,7 +216,7 @@ Set router properties gateway: nexthop IP address (repeat option to set multiple routes) -.. option:: --clear-routes +.. option:: --no-route Clear routes associated with the router diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index a32ab5ea79..e479eee3a0 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -13,7 +13,9 @@ """Router action implementations""" +import argparse import json +import logging from openstackclient.common import command from openstackclient.common import exceptions @@ -23,6 +25,9 @@ from openstackclient.identity import common as identity_common +LOG = logging.getLogger(__name__) + + def _format_admin_state(state): return 'UP' if state else 'DOWN' @@ -379,10 +384,15 @@ def get_parser(self, prog_name): "(repeat option to set multiple routes)") ) routes_group.add_argument( - '--clear-routes', + '--no-route', action='store_true', help=_("Clear routes associated with the router") ) + routes_group.add_argument( + '--clear-routes', + action='store_true', + help=argparse.SUPPRESS, + ) # TODO(tangchen): Support setting 'ha' property in 'router set' # command. It appears that changing the ha state is supported by @@ -401,8 +411,14 @@ def take_action(self, parsed_args): attrs = _get_attrs(self.app.client_manager, parsed_args) # Get the route attributes. - if parsed_args.clear_routes: + if parsed_args.no_route: + attrs['routes'] = [] + elif parsed_args.clear_routes: attrs['routes'] = [] + LOG.warning(_( + 'The --clear-routes option is deprecated, ' + 'please use --no-route instead.' + )) elif parsed_args.routes is not None: # Map the route keys and append to the current routes. # The REST API will handle route validation and duplicates. diff --git a/openstackclient/tests/network/v2/test_router.py b/openstackclient/tests/network/v2/test_router.py index 655e86c95e..99b41d2dc4 100644 --- a/openstackclient/tests/network/v2/test_router.py +++ b/openstackclient/tests/network/v2/test_router.py @@ -495,6 +495,42 @@ def test_set_route(self): self._router, **attrs) self.assertIsNone(result) + def test_set_no_route(self): + arglist = [ + self._router.name, + '--no-route', + ] + verifylist = [ + ('router', self._router.name), + ('no_route', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + attrs = { + 'routes': [], + } + self.network.update_router.assert_called_once_with( + self._router, **attrs) + self.assertIsNone(result) + + def test_set_route_no_route(self): + arglist = [ + self._router.name, + '--route', 'destination=10.20.30.0/24,gateway=10.20.30.1', + '--no-route', + ] + verifylist = [ + ('router', self._router.name), + ('routes', [{'destination': '10.20.30.0/24', + 'gateway': '10.20.30.1'}]), + ('no_route', True), + ] + + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + def test_set_clear_routes(self): arglist = [ self._router.name, diff --git a/releasenotes/notes/bug-1565034-dd404bfb42d7778d.yaml b/releasenotes/notes/bug-1565034-dd404bfb42d7778d.yaml new file mode 100644 index 0000000000..e5ff38e8b2 --- /dev/null +++ b/releasenotes/notes/bug-1565034-dd404bfb42d7778d.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - Added ``--no-route`` to the ``router set`` command. + Deprecated ``--clear-routes``. + [Bug `1565034 `_] From 69be96ae724cbc8dffe4919755c98e08721af1cd Mon Sep 17 00:00:00 2001 From: ting wang Date: Mon, 16 May 2016 13:02:44 +0800 Subject: [PATCH 0885/3095] Refactor TestRemoveProjectImage with FakeImage class Change-Id: Id40ea8a3cf59738fa254da0bca4ad6551565f5de Co-Authored-By: xiexs Implements: blueprint improve-image-unittest-framework --- openstackclient/tests/image/v2/test_image.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index 67b86d111a..33f2533105 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -727,12 +727,10 @@ class TestRemoveProjectImage(TestImage): def setUp(self): super(TestRemoveProjectImage, self).setUp() + self._image = image_fakes.FakeImage.create_one_image() # This is the return value for utils.find_resource() - self.images_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(image_fakes.IMAGE), - loaded=True, - ) + self.images_mock.get.return_value = self._image + self.project_mock.get.return_value = fakes.FakeResource( None, copy.deepcopy(identity_fakes.PROJECT), @@ -749,11 +747,11 @@ def setUp(self): def test_remove_project_image_no_options(self): arglist = [ - image_fakes.image_id, + self._image.id, identity_fakes.project_id, ] verifylist = [ - ('image', image_fakes.image_id), + ('image', self._image.id), ('project', identity_fakes.project_id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -761,19 +759,19 @@ def test_remove_project_image_no_options(self): result = self.cmd.take_action(parsed_args) self.image_members_mock.delete.assert_called_with( - image_fakes.image_id, + self._image.id, identity_fakes.project_id, ) self.assertIsNone(result) def test_remove_project_image_with_options(self): arglist = [ - image_fakes.image_id, + self._image.id, identity_fakes.project_id, '--project-domain', identity_fakes.domain_id, ] verifylist = [ - ('image', image_fakes.image_id), + ('image', self._image.id), ('project', identity_fakes.project_id), ('project_domain', identity_fakes.domain_id), ] @@ -782,7 +780,7 @@ def test_remove_project_image_with_options(self): result = self.cmd.take_action(parsed_args) self.image_members_mock.delete.assert_called_with( - image_fakes.image_id, + self._image.id, identity_fakes.project_id, ) self.assertIsNone(result) From 40f51c32ace92d9f3afae8df83a06ca5056c9f06 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 14 May 2016 12:52:36 +0800 Subject: [PATCH 0886/3095] Fix i18n support for help and error msg in network Change-Id: Iab0dc489226601ac4ff123bb758f84ff16989395 --- openstackclient/network/v2/address_scope.py | 2 +- openstackclient/network/v2/network.py | 10 +++++----- openstackclient/network/v2/port.py | 2 +- openstackclient/network/v2/router.py | 2 +- openstackclient/network/v2/security_group_rule.py | 4 ++-- openstackclient/network/v2/subnet.py | 6 +++--- openstackclient/network/v2/subnet_pool.py | 2 +- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/openstackclient/network/v2/address_scope.py b/openstackclient/network/v2/address_scope.py index fac0849f6b..614900c92e 100644 --- a/openstackclient/network/v2/address_scope.py +++ b/openstackclient/network/v2/address_scope.py @@ -185,7 +185,7 @@ def take_action(self, parsed_args): if parsed_args.no_share: attrs['shared'] = False if attrs == {}: - msg = "Nothing specified to be set." + msg = _("Nothing specified to be set.") raise exceptions.CommandError(msg) client.update_address_scope(obj, **attrs) diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 4b77971a21..014dc16e49 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -206,7 +206,7 @@ def update_parser_network(self, parser): default_router_grp.add_argument( '--no-default', action='store_true', - help=_("Do not use the network as the default external network. " + help=_("Do not use the network as the default external network " "(default)") ) _add_provider_network_options(parser) @@ -243,7 +243,7 @@ def update_parser_common(self, parser): 'network', metavar="", nargs="+", - help=("Network(s) to delete (name or ID)") + help=_("Network(s) to delete (name or ID)") ) return parser @@ -269,13 +269,13 @@ def update_parser_common(self, parser): '--external', action='store_true', default=False, - help='List external networks', + help=_("List external networks") ) parser.add_argument( '--long', action='store_true', default=False, - help='List additional fields in output', + help=_("List additional fields in output") ) return parser @@ -422,7 +422,7 @@ def take_action(self, parsed_args): attrs = _get_attrs(self.app.client_manager, parsed_args) if attrs == {}: - msg = "Nothing specified to be set" + msg = _("Nothing specified to be set") raise exceptions.CommandError(msg) client.update_network(obj, **attrs) diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 9b6161fdd9..aa0894f828 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -414,7 +414,7 @@ def take_action(self, parsed_args): attrs['fixed_ips'] = [] if attrs == {}: - msg = "Nothing specified to be set" + msg = _("Nothing specified to be set") raise exceptions.CommandError(msg) client.update_port(obj, **attrs) diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index a32ab5ea79..12fea0cba9 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -411,7 +411,7 @@ def take_action(self, parsed_args): attrs['routes'] = obj.routes + parsed_args.routes if attrs == {}: - msg = "Nothing specified to be set" + msg = _("Nothing specified to be set") raise exceptions.CommandError(msg) client.update_router(obj, **attrs) diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index 92dd1e5a2d..5abe9b9d8d 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -519,8 +519,8 @@ def take_action_compute(self, client, parsed_args): break if obj is None: - msg = "Could not find security group rule " \ - "with ID %s" % parsed_args.rule + msg = _("Could not find security group rule with ID ") + \ + parsed_args.rule raise exceptions.CommandError(msg) # NOTE(rtheis): Format security group rule diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index fb441cbf46..f51aec5b6b 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -141,9 +141,9 @@ def _get_attrs(client_manager, parsed_args, is_create=True): gateway = parsed_args.gateway.lower() if not is_create and gateway == 'auto': - raise exceptions.CommandError("Auto option is not available" - " for Subnet Set. Valid options are" - " or none") + msg = _("Auto option is not available for Subnet Set. " + "Valid options are or none") + raise exceptions.CommandError(msg) elif gateway != 'auto': if gateway == 'none': attrs['gateway_ip'] = None diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index f1174dda8c..a1a94426dd 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -287,7 +287,7 @@ def take_action(self, parsed_args): attrs = _get_attrs(self.app.client_manager, parsed_args) if attrs == {}: - msg = "Nothing specified to be set" + msg = _("Nothing specified to be set") raise exceptions.CommandError(msg) # Existing prefixes must be a subset of the new prefixes. From 00c149a28f72ee7236e836a7f128fce9ca5e1e09 Mon Sep 17 00:00:00 2001 From: reedip Date: Tue, 19 Apr 2016 13:46:15 +0900 Subject: [PATCH 0887/3095] Add VLAN Transparent option to ``osc network`` osc network set and network create now support --transparent-vlan|--no-transparent-vlan options to add/remove vlan transparency from the network. Change-Id: I845eb8f541cd32a4c4b28f929a63b205e7e31756 Closes-Bug: 1545537 --- doc/source/command-objects/network.rst | 22 +++++++++++++++++ openstackclient/network/v2/network.py | 24 +++++++++++++++---- .../tests/network/v2/test_network.py | 6 +++++ ...n-transparent-option-4fa72fbfbbe3f31e.yaml | 9 +++++++ 4 files changed, 57 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/add-vlan-transparent-option-4fa72fbfbbe3f31e.yaml diff --git a/doc/source/command-objects/network.rst b/doc/source/command-objects/network.rst index 5d534c59f5..bb8947e3d8 100644 --- a/doc/source/command-objects/network.rst +++ b/doc/source/command-objects/network.rst @@ -21,6 +21,7 @@ Create new network [--provider-network-type ] [--provider-physical-network ] [--provider-segment ] + [--transparent-vlan | --no-transparent-vlan] .. option:: --project @@ -116,6 +117,18 @@ Create new network *Network version 2 only* +.. option:: --transparent-vlan + + Make the network VLAN transparent + + *Network version 2 only* + +.. option:: --no-transparent-vlan + + Do not make the network VLAN transparent + + *Network version 2 only* + .. _network_create-name: .. describe:: @@ -175,6 +188,7 @@ Set network properties [--provider-network-type ] [--provider-physical-network ] [--provider-segment ] + [--transparent-vlan | --no-transparent-vlan] .. option:: --name @@ -227,6 +241,14 @@ Set network properties VLAN ID for VLAN networks or Tunnel ID for GRE/VXLAN networks +.. option:: --transparent-vlan + + Make the network VLAN transparent + +.. option:: --no-transparent-vlan + + Do not make the network VLAN transparent + .. _network_set-network: .. describe:: diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 4b77971a21..dab20b7459 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -93,11 +93,17 @@ 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 + # Update VLAN Transparency for networks + if parsed_args.transparent_vlan: + attrs['vlan_transparent'] = True + if parsed_args.no_transparent_vlan: + attrs['vlan_transparent'] = False return attrs -def _add_provider_network_options(parser): - # Add provider network options +def _add_additional_network_options(parser): + # Add additional network options + parser.add_argument( '--provider-network-type', metavar='', @@ -119,6 +125,16 @@ def _add_provider_network_options(parser): help=_("VLAN ID for VLAN networks or Tunnel ID for GRE/VXLAN " "networks")) + vlan_transparent_grp = parser.add_mutually_exclusive_group() + vlan_transparent_grp.add_argument( + '--transparent-vlan', + action='store_true', + help=_("Make the network VLAN transparent")) + vlan_transparent_grp.add_argument( + '--no-transparent-vlan', + action='store_true', + help=_("Do not make the network VLAN transparent")) + def _get_attrs_compute(client_manager, parsed_args): attrs = {} @@ -209,7 +225,7 @@ def update_parser_network(self, parser): help=_("Do not use the network as the default external network. " "(default)") ) - _add_provider_network_options(parser) + _add_additional_network_options(parser) return parser def update_parser_compute(self, parser): @@ -413,7 +429,7 @@ def get_parser(self, prog_name): action='store_true', help=_("Do not use the network as the default external network") ) - _add_provider_network_options(parser) + _add_additional_network_options(parser) return parser def take_action(self, parsed_args): diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index a1b0aec986..93450e23fd 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -149,6 +149,7 @@ def test_create_all_options(self): "--provider-network-type", "vlan", "--provider-physical-network", "physnet1", "--provider-segment", "400", + "--transparent-vlan", self._network.name, ] verifylist = [ @@ -162,6 +163,7 @@ def test_create_all_options(self): ('provider_network_type', 'vlan'), ('physical_network', 'physnet1'), ('segmentation_id', '400'), + ('transparent_vlan', True), ('name', self._network.name), ] @@ -179,6 +181,7 @@ def test_create_all_options(self): 'provider:network_type': 'vlan', 'provider:physical_network': 'physnet1', 'provider:segmentation_id': '400', + 'vlan_transparent': True, }) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -487,6 +490,7 @@ def test_set_this(self): '--provider-network-type', 'vlan', '--provider-physical-network', 'physnet1', '--provider-segment', '400', + '--no-transparent-vlan', ] verifylist = [ ('network', self._network.name), @@ -498,6 +502,7 @@ def test_set_this(self): ('provider_network_type', 'vlan'), ('physical_network', 'physnet1'), ('segmentation_id', '400'), + ('no_transparent_vlan', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -512,6 +517,7 @@ def test_set_this(self): 'provider:network_type': 'vlan', 'provider:physical_network': 'physnet1', 'provider:segmentation_id': '400', + 'vlan_transparent': False, } self.network.update_network.assert_called_once_with( self._network, **attrs) diff --git a/releasenotes/notes/add-vlan-transparent-option-4fa72fbfbbe3f31e.yaml b/releasenotes/notes/add-vlan-transparent-option-4fa72fbfbbe3f31e.yaml new file mode 100644 index 0000000000..508fb81c7a --- /dev/null +++ b/releasenotes/notes/add-vlan-transparent-option-4fa72fbfbbe3f31e.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + ``network create`` and ``network set`` now support + ``--transparent-vlan`` and ``--no-transparent-vlan`` + options to add/remove VLAN transparency attributes + from networks. + This option is available in Network V2 only. + [Bug `1545537 `_] \ No newline at end of file From 093f0ff003f493667c0c28854a07ce882d834e3f Mon Sep 17 00:00:00 2001 From: venkatamahesh Date: Mon, 16 May 2016 17:42:48 +0530 Subject: [PATCH 0888/3095] Added CONTRIBUTING.rst file Change-Id: I7edb273d36ba3536d5538029377e7b7535cfd115 --- CONTRIBUTING.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 CONTRIBUTING.rst diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst new file mode 100644 index 0000000000..de9324ec7f --- /dev/null +++ b/CONTRIBUTING.rst @@ -0,0 +1,16 @@ +If you would like to contribute to the development of OpenStack, +you must follow the steps documented at: + + http://docs.openstack.org/infra/manual/developers.html#development-workflow + +Once those steps have been completed, changes to OpenStack +should be submitted for review via the Gerrit tool, following +the workflow documented at: + + http://docs.openstack.org/infra/manual/developers.html#development-workflow + +Pull requests submitted through GitHub will be ignored. + +Bugs should be filed on Launchpad, not GitHub: + + https://bugs.launchpad.net/python-openstackclient From 668bc028d13b80dfc6ecbef6193678a97e64fdc1 Mon Sep 17 00:00:00 2001 From: Manjeet Singh Bhatia Date: Thu, 12 May 2016 18:59:58 +0000 Subject: [PATCH 0889/3095] Add ip version filter to subnet list This patch will add argument ip-version to command subnet list Change-Id: If7458d4979e53aec7e2633c4f1779c3810f9a3f1 Closes-Bug: #1581179 --- doc/source/command-objects/subnet.rst | 4 ++++ openstackclient/network/v2/subnet.py | 14 +++++++++++++- openstackclient/tests/network/v2/test_subnet.py | 16 ++++++++++++++++ .../notes/bug-1581179-4d15dc504777f9e7.yaml | 6 ++++++ 4 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/bug-1581179-4d15dc504777f9e7.yaml diff --git a/doc/source/command-objects/subnet.rst b/doc/source/command-objects/subnet.rst index 8daa251fdc..ff6354e658 100644 --- a/doc/source/command-objects/subnet.rst +++ b/doc/source/command-objects/subnet.rst @@ -147,6 +147,10 @@ List subnets List additional fields in output +.. option:: --ip-version {4, 6} + + List only subnets of given IP version in output + subnet set ---------- diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index fb441cbf46..5fc01b642e 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -296,10 +296,22 @@ def get_parser(self, prog_name): default=False, help=_("List additional fields in output") ) + parser.add_argument( + '--ip-version', + type=int, + choices=[4, 6], + metavar='', + dest='ip_version', + help=_("List only subnets of given IP version in output" + "Allowed values for IP version are 4 and 6."), + ) return parser def take_action(self, parsed_args): - data = self.app.client_manager.network.subnets() + filters = {} + if parsed_args.ip_version: + filters['ip_version'] = parsed_args.ip_version + data = self.app.client_manager.network.subnets(**filters) headers = ('ID', 'Name', 'Network', 'Subnet') columns = ('id', 'name', 'network_id', 'cidr') diff --git a/openstackclient/tests/network/v2/test_subnet.py b/openstackclient/tests/network/v2/test_subnet.py index 1923286dc2..22c288f9f1 100644 --- a/openstackclient/tests/network/v2/test_subnet.py +++ b/openstackclient/tests/network/v2/test_subnet.py @@ -469,6 +469,22 @@ def test_subnet_list_long(self): self.assertEqual(self.columns_long, columns) self.assertEqual(self.data_long, list(data)) + def test_subnet_list_ip_version(self): + arglist = [ + '--ip-version', str(4), + ] + verifylist = [ + ('ip_version', 4), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'ip_version': 4} + + self.network.subnets.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + class TestSetSubnet(TestSubnet): diff --git a/releasenotes/notes/bug-1581179-4d15dc504777f9e7.yaml b/releasenotes/notes/bug-1581179-4d15dc504777f9e7.yaml new file mode 100644 index 0000000000..53a52849a3 --- /dev/null +++ b/releasenotes/notes/bug-1581179-4d15dc504777f9e7.yaml @@ -0,0 +1,6 @@ +--- +features: + - + Add the ``--ip-version`` option to the ``subnet list`` command. This + will output subnets based on IP version filter. + [`Bug 1581179 `_] From 4956c354b22f44e9bcab89d796539555da3ce2cb Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Mon, 16 May 2016 23:00:10 +0800 Subject: [PATCH 0890/3095] Add FakeBackup class and updata backup unittest in volumeV2 Change-Id: I39762bedaeaaf1894f48912ca1b7d59ab50f9f78 --- openstackclient/tests/volume/v2/fakes.py | 54 ++++ .../tests/volume/v2/test_backup.py | 252 ++++++++++-------- 2 files changed, 193 insertions(+), 113 deletions(-) diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py index fc45e47b9d..c4155c69be 100644 --- a/openstackclient/tests/volume/v2/fakes.py +++ b/openstackclient/tests/volume/v2/fakes.py @@ -498,3 +498,57 @@ def create_availability_zones(attrs=None, count=2): availability_zones.append(availability_zone) return availability_zones + + +class FakeBackup(object): + """Fake one or more backup.""" + + @staticmethod + def create_one_backup(attrs=None): + """Create a fake backup. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object with id, name, volume_id, etc. + """ + attrs = attrs or {} + + # Set default attributes. + backup_info = { + "id": 'backup-id-' + uuid.uuid4().hex, + "name": 'backup-name-' + uuid.uuid4().hex, + "volume_id": 'volume-id-' + uuid.uuid4().hex, + "description": 'description-' + uuid.uuid4().hex, + "object_count": None, + "container": 'container-' + uuid.uuid4().hex, + "size": random.randint(1, 20), + "status": "error", + "availability_zone": 'zone' + uuid.uuid4().hex, + } + + # Overwrite default attributes. + backup_info.update(attrs) + + backup = fakes.FakeResource( + info=copy.deepcopy(backup_info), + loaded=True) + return backup + + @staticmethod + def create_backups(attrs=None, count=2): + """Create multiple fake backups. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of backups to fake + :return: + A list of FakeResource objects faking the backups + """ + backups = [] + for i in range(0, count): + backup = FakeBackup.create_one_backup(attrs) + backups.append(backup) + + return backups diff --git a/openstackclient/tests/volume/v2/test_backup.py b/openstackclient/tests/volume/v2/test_backup.py index 0e906e7bf1..8a151a9108 100644 --- a/openstackclient/tests/volume/v2/test_backup.py +++ b/openstackclient/tests/volume/v2/test_backup.py @@ -12,9 +12,6 @@ # under the License. # -import copy - -from openstackclient.tests import fakes from openstackclient.tests.volume.v2 import fakes as volume_fakes from openstackclient.volume.v2 import backup @@ -34,83 +31,101 @@ def setUp(self): class TestBackupCreate(TestBackup): + volume = volume_fakes.FakeVolume.create_one_volume() + new_backup = volume_fakes.FakeBackup.create_one_backup( + attrs={'volume_id': volume.id}) + + columns = ( + 'availability_zone', + 'container', + 'description', + 'id', + 'name', + 'object_count', + 'size', + 'status', + 'volume_id', + ) + data = ( + new_backup.availability_zone, + new_backup.container, + new_backup.description, + new_backup.id, + new_backup.name, + new_backup.object_count, + new_backup.size, + new_backup.status, + new_backup.volume_id, + ) + def setUp(self): super(TestBackupCreate, self).setUp() - self.volumes_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.VOLUME), - loaded=True - ) + self.volumes_mock.get.return_value = self.volume + self.backups_mock.create.return_value = self.new_backup - self.backups_mock.create.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.BACKUP), - loaded=True - ) # Get the command object to test self.cmd = backup.CreateBackup(self.app, None) def test_backup_create(self): arglist = [ - volume_fakes.volume_id, - "--name", volume_fakes.backup_name, - "--description", volume_fakes.backup_description, - "--container", volume_fakes.backup_name + "--name", self.new_backup.name, + "--description", self.new_backup.description, + "--container", self.new_backup.container, + self.new_backup.volume_id, ] verifylist = [ - ("volume", volume_fakes.volume_id), - ("name", volume_fakes.backup_name), - ("description", volume_fakes.backup_description), - ("container", volume_fakes.backup_name) + ("name", self.new_backup.name), + ("description", self.new_backup.description), + ("container", self.new_backup.container), + ("volume", self.new_backup.volume_id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.backups_mock.create.assert_called_with( - volume_fakes.volume_id, - container=volume_fakes.backup_name, - name=volume_fakes.backup_name, - description=volume_fakes.backup_description + self.new_backup.volume_id, + container=self.new_backup.container, + name=self.new_backup.name, + description=self.new_backup.description ) - self.assertEqual(columns, volume_fakes.BACKUP_columns) - self.assertEqual(data, volume_fakes.BACKUP_data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) def test_backup_create_without_name(self): arglist = [ - volume_fakes.volume_id, - "--description", volume_fakes.backup_description, - "--container", volume_fakes.backup_name + "--description", self.new_backup.description, + "--container", self.new_backup.container, + self.new_backup.volume_id, ] verifylist = [ - ("volume", volume_fakes.volume_id), - ("description", volume_fakes.backup_description), - ("container", volume_fakes.backup_name) + ("description", self.new_backup.description), + ("container", self.new_backup.container), + ("volume", self.new_backup.volume_id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.backups_mock.create.assert_called_with( - volume_fakes.volume_id, - container=volume_fakes.backup_name, + self.new_backup.volume_id, + container=self.new_backup.container, name=None, - description=volume_fakes.backup_description + description=self.new_backup.description ) - self.assertEqual(columns, volume_fakes.BACKUP_columns) - self.assertEqual(data, volume_fakes.BACKUP_data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) class TestBackupDelete(TestBackup): + backup = volume_fakes.FakeBackup.create_one_backup() + def setUp(self): super(TestBackupDelete, self).setUp() - self.backups_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.BACKUP), - loaded=True) + self.backups_mock.get.return_value = self.backup self.backups_mock.delete.return_value = None # Get the command object to mock @@ -118,21 +133,25 @@ def setUp(self): def test_backup_delete(self): arglist = [ - volume_fakes.backup_id + self.backup.id ] verifylist = [ - ("backups", [volume_fakes.backup_id]) + ("backups", [self.backup.id]) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.backups_mock.delete.assert_called_with(volume_fakes.backup_id) + self.backups_mock.delete.assert_called_with(self.backup.id) self.assertIsNone(result) class TestBackupList(TestBackup): + volume = volume_fakes.FakeVolume.create_one_volume() + backups = volume_fakes.FakeBackup.create_backups( + attrs={'volume_id': volume.name}, count=3) + columns = [ 'ID', 'Name', @@ -140,33 +159,39 @@ class TestBackupList(TestBackup): 'Status', 'Size', ] - datalist = ( - ( - volume_fakes.backup_id, - volume_fakes.backup_name, - volume_fakes.backup_description, - volume_fakes.backup_status, - volume_fakes.backup_size - ), - ) + columns_long = columns + [ + 'Availability Zone', + 'Volume', + 'Container', + ] + + data = [] + for b in backups: + data.append(( + b.id, + b.name, + b.description, + b.status, + b.size, + )) + data_long = [] + for b in backups: + data_long.append(( + b.id, + b.name, + b.description, + b.status, + b.size, + b.availability_zone, + b.volume_id, + b.container, + )) def setUp(self): super(TestBackupList, self).setUp() - self.volumes_mock.list.return_value = [ - fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.VOLUME), - loaded=True - ) - ] - self.backups_mock.list.return_value = [ - fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.BACKUP), - loaded=True - ) - ] + self.volumes_mock.list.return_value = [self.volume] + self.backups_mock.list.return_value = self.backups # Get the command to test self.cmd = backup.ListBackup(self.app, None) @@ -178,7 +203,7 @@ def test_backup_list_without_options(self): columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, tuple(data)) + self.assertEqual(self.data, list(data)) def test_backup_list_with_options(self): arglist = ["--long"] @@ -187,86 +212,87 @@ def test_backup_list_with_options(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - columns = self.columns + [ - 'Availability Zone', - 'Volume', - 'Container', - ] - - self.assertEqual(columns, columns) - - datalist = (( - volume_fakes.backup_id, - volume_fakes.backup_name, - volume_fakes.backup_description, - volume_fakes.backup_status, - volume_fakes.backup_size, - volume_fakes.volume_availability_zone, - volume_fakes.backup_volume_id, - volume_fakes.backup_container - ),) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns_long, columns) + self.assertEqual(self.data_long, list(data)) class TestBackupRestore(TestBackup): + volume = volume_fakes.FakeVolume.create_one_volume() + backup = volume_fakes.FakeBackup.create_one_backup( + attrs={'volume_id': volume.id}) + def setUp(self): super(TestBackupRestore, self).setUp() - self.backups_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.BACKUP), - loaded=True - ) - self.volumes_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.VOLUME), - loaded=True - ) + self.backups_mock.get.return_value = self.backup + self.volumes_mock.get.return_value = self.volume self.restores_mock.restore.return_value = None # Get the command object to mock self.cmd = backup.RestoreBackup(self.app, None) def test_backup_restore(self): arglist = [ - volume_fakes.backup_id, - volume_fakes.volume_id + self.backup.id, + self.backup.volume_id ] verifylist = [ - ("backup", volume_fakes.backup_id), - ("volume", volume_fakes.volume_id) + ("backup", self.backup.id), + ("volume", self.backup.volume_id) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.restores_mock.restore.assert_called_with(volume_fakes.backup_id, - volume_fakes.volume_id) + self.restores_mock.restore.assert_called_with(self.backup.id, + self.backup.volume_id) self.assertIsNone(result) class TestBackupShow(TestBackup): + backup = volume_fakes.FakeBackup.create_one_backup() + + columns = ( + 'availability_zone', + 'container', + 'description', + 'id', + 'name', + 'object_count', + 'size', + 'status', + 'volume_id', + ) + data = ( + backup.availability_zone, + backup.container, + backup.description, + backup.id, + backup.name, + backup.object_count, + backup.size, + backup.status, + backup.volume_id, + ) + def setUp(self): super(TestBackupShow, self).setUp() - self.backups_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.BACKUP), - loaded=True) + self.backups_mock.get.return_value = self.backup # Get the command object to test self.cmd = backup.ShowBackup(self.app, None) def test_backup_show(self): arglist = [ - volume_fakes.backup_id + self.backup.id ] verifylist = [ - ("backup", volume_fakes.backup_id) + ("backup", self.backup.id) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.backups_mock.get.assert_called_with(volume_fakes.backup_id) + self.backups_mock.get.assert_called_with(self.backup.id) - self.assertEqual(volume_fakes.BACKUP_columns, columns) - self.assertEqual(volume_fakes.BACKUP_data, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) From 0fa2e8df92986bf7cbbba131042c1d3196b54365 Mon Sep 17 00:00:00 2001 From: "ting.wang" Date: Thu, 12 May 2016 10:13:13 +0800 Subject: [PATCH 0891/3095] Refactor TestVolumeShow with FakeVolume In the meantime, add some static methods in FakeVolume for ease of use and add info() method with "property" decorator in FakeResource to allow those static methods to get fake information. Change-Id: I98ad520f32afd529fda77a4592f645130282537f Co-Authored-By: xiexs Implements: blueprint improve-volume-unittest-framework --- openstackclient/tests/fakes.py | 4 ++ openstackclient/tests/volume/v2/fakes.py | 38 +++++++++++++++++++ .../tests/volume/v2/test_volume.py | 21 +++++----- 3 files changed, 54 insertions(+), 9 deletions(-) diff --git a/openstackclient/tests/fakes.py b/openstackclient/tests/fakes.py index 46f983dc46..229b46529c 100644 --- a/openstackclient/tests/fakes.py +++ b/openstackclient/tests/fakes.py @@ -189,6 +189,10 @@ def __repr__(self): def keys(self): return self._info.keys() + @property + def info(self): + return self._info + class FakeResponse(requests.Response): diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py index fc45e47b9d..ebb0c2ff1a 100644 --- a/openstackclient/tests/volume/v2/fakes.py +++ b/openstackclient/tests/volume/v2/fakes.py @@ -451,6 +451,44 @@ def get_volumes(volumes=None, count=2): return mock.MagicMock(side_effect=volumes) + @staticmethod + def get_volume_columns(volume=None): + """Get the volume columns from a faked volume object. + + :param volume: + A FakeResource objects faking volume + :return + A tuple which may include the following keys: + ('id', 'name', 'description', 'status', 'size', 'volume_type', + 'metadata', 'snapshot', 'availability_zone', 'attachments') + """ + if volume is not None: + return tuple(k for k in sorted(volume.keys())) + return tuple([]) + + @staticmethod + def get_volume_data(volume=None): + """Get the volume data from a faked volume object. + + :param volume: + A FakeResource objects faking volume + :return + A tuple which may include the following values: + ('ce26708d', 'fake_volume', 'fake description', 'available', + 20, 'fake_lvmdriver-1', "Alpha='a', Beta='b', Gamma='g'", + 1, 'nova', [{'device': '/dev/ice', 'server_id': '1233'}]) + """ + data_list = [] + if volume is not None: + for x in sorted(volume.keys()): + if x == 'tags': + # The 'tags' should be format_list + data_list.append( + common_utils.format_list(volume.info.get(x))) + else: + data_list.append(volume.info.get(x)) + return tuple(data_list) + class FakeAvailabilityZone(object): """Fake one or more volume availability zones (AZs).""" diff --git a/openstackclient/tests/volume/v2/test_volume.py b/openstackclient/tests/volume/v2/test_volume.py index 5689d008ec..85ff61422b 100644 --- a/openstackclient/tests/volume/v2/test_volume.py +++ b/openstackclient/tests/volume/v2/test_volume.py @@ -772,27 +772,30 @@ class TestVolumeShow(TestVolume): def setUp(self): super(TestVolumeShow, self).setUp() - self.volumes_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.VOLUME), - loaded=True) + self._volume = volume_fakes.FakeVolume.create_one_volume() + self.volumes_mock.get.return_value = self._volume # Get the command object to test self.cmd = volume.ShowVolume(self.app, None) def test_volume_show(self): arglist = [ - volume_fakes.volume_id + self._volume.id ] verifylist = [ - ("volume", volume_fakes.volume_id) + ("volume", self._volume.id) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.volumes_mock.get.assert_called_with(volume_fakes.volume_id) + self.volumes_mock.get.assert_called_with(self._volume.id) + + self.assertEqual( + volume_fakes.FakeVolume.get_volume_columns(self._volume), + columns) - self.assertEqual(volume_fakes.VOLUME_columns, columns) - self.assertEqual(volume_fakes.VOLUME_data, data) + self.assertEqual( + volume_fakes.FakeVolume.get_volume_data(self._volume), + data) class TestVolumeSet(TestVolume): From 78c20f6f38f26d18ad769c0660451237a856c60e Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 18 May 2016 02:40:12 +0000 Subject: [PATCH 0892/3095] Updated from global requirements Change-Id: Ib27d5b23d33a72da7b9eed8f1861de70ae586c85 --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index e2353be376..b24c68de98 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,8 +13,8 @@ oslo.config>=3.9.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 oslo.utils>=3.5.0 # Apache-2.0 python-glanceclient>=2.0.0 # Apache-2.0 -python-keystoneclient!=1.8.0,!=2.1.0,>=1.6.0 # Apache-2.0 +python-keystoneclient!=1.8.0,!=2.1.0,>=1.7.0 # Apache-2.0 python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0 python-cinderclient>=1.6.0 # Apache-2.0 -requests!=2.9.0,>=2.8.1 # Apache-2.0 +requests>=2.10.0 # Apache-2.0 stevedore>=1.10.0 # Apache-2.0 From 6c8e0dc1dfc026644e1da894b52d102c32168d9c Mon Sep 17 00:00:00 2001 From: Xiaoyang Zhang Date: Thu, 19 May 2016 13:56:34 +0800 Subject: [PATCH 0893/3095] Fix i18n support for help and log.warning in image Change-Id: I6e2764aaf4b8c0efa78872646a7b3482ba044dc8 --- openstackclient/image/client.py | 7 +- openstackclient/image/v1/image.py | 128 ++++++++++++------------- openstackclient/image/v2/image.py | 151 +++++++++++++++--------------- 3 files changed, 145 insertions(+), 141 deletions(-) diff --git a/openstackclient/image/client.py b/openstackclient/image/client.py index 8dd146e9f2..9c45a63ff0 100644 --- a/openstackclient/image/client.py +++ b/openstackclient/image/client.py @@ -16,6 +16,7 @@ import logging from openstackclient.common import utils +from openstackclient.i18n import _ LOG = logging.getLogger(__name__) @@ -82,7 +83,7 @@ def build_option_parser(parser): '--os-image-api-version', metavar='', default=utils.env('OS_IMAGE_API_VERSION'), - help='Image API version, default=' + - DEFAULT_API_VERSION + - ' (Env: OS_IMAGE_API_VERSION)') + help=_('Image API version, default=%s (Env: OS_IMAGE_API_VERSION)') % + DEFAULT_API_VERSION, + ) return parser diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index 0d0855974c..e2109fca07 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -31,7 +31,7 @@ from openstackclient.common import command from openstackclient.common import parseractions from openstackclient.common import utils -from openstackclient.i18n import _ # noqa +from openstackclient.i18n import _ DEFAULT_CONTAINER_FORMAT = 'bare' @@ -61,112 +61,112 @@ def get_parser(self, prog_name): parser.add_argument( "name", metavar="", - help="New image name", + help=_("New image name"), ) parser.add_argument( "--id", metavar="", - help="Image ID to reserve", + help=_("Image ID to reserve"), ) parser.add_argument( "--store", metavar="", - help="Upload image to this store", + help=_("Upload image to this store"), ) parser.add_argument( "--container-format", default=DEFAULT_CONTAINER_FORMAT, metavar="", - help="Image container format " - "(default: %s)" % DEFAULT_CONTAINER_FORMAT, + help=_("Image container format " + "(default: %s)") % DEFAULT_CONTAINER_FORMAT, ) parser.add_argument( "--disk-format", default=DEFAULT_DISK_FORMAT, metavar="", - help="Image disk format " - "(default: %s)" % DEFAULT_DISK_FORMAT, + help=_("Image disk format " + "(default: %s)") % DEFAULT_DISK_FORMAT, ) parser.add_argument( "--size", metavar="", - help="Image size, in bytes (only used with --location and" - " --copy-from)", + help=_("Image size, in bytes (only used with --location and" + " --copy-from)"), ) parser.add_argument( "--min-disk", metavar="", type=int, - help="Minimum disk size needed to boot image, in gigabytes", + help=_("Minimum disk size needed to boot image, in gigabytes"), ) parser.add_argument( "--min-ram", metavar="", type=int, - help="Minimum RAM size needed to boot image, in megabytes", + help=_("Minimum RAM size needed to boot image, in megabytes"), ) parser.add_argument( "--location", metavar="", - help="Download image from an existing URL", + help=_("Download image from an existing URL"), ) parser.add_argument( "--copy-from", metavar="", - help="Copy image from the data store (similar to --location)", + help=_("Copy image from the data store (similar to --location)"), ) parser.add_argument( "--file", metavar="", - help="Upload image from local file", + help=_("Upload image from local file"), ) parser.add_argument( "--volume", metavar="", - help="Create image from a volume", + help=_("Create image from a volume"), ) parser.add_argument( "--force", dest='force', action='store_true', default=False, - help="Force image creation if volume is in use " - "(only meaningful with --volume)", + help=_("Force image creation if volume is in use " + "(only meaningful with --volume)"), ) parser.add_argument( "--checksum", metavar="", - help="Image hash used for verification", + help=_("Image hash used for verification"), ) protected_group = parser.add_mutually_exclusive_group() protected_group.add_argument( "--protected", action="store_true", - help="Prevent image from being deleted", + help=_("Prevent image from being deleted"), ) protected_group.add_argument( "--unprotected", action="store_true", - help="Allow image to be deleted (default)", + help=_("Allow image to be deleted (default)"), ) public_group = parser.add_mutually_exclusive_group() public_group.add_argument( "--public", action="store_true", - help="Image is accessible to the public", + help=_("Image is accessible to the public"), ) public_group.add_argument( "--private", action="store_true", - help="Image is inaccessible to the public (default)", + help=_("Image is inaccessible to the public (default)"), ) parser.add_argument( "--property", dest="properties", metavar="", action=parseractions.KeyValueAction, - help="Set a property on this image " - "(repeat option to set multiple properties)", + help=_("Set a property on this image " + "(repeat option to set multiple properties)"), ) # NOTE(dtroyer): --owner is deprecated in Jan 2016 in an early # 2.x release. Do not remove before Jan 2017 @@ -175,7 +175,7 @@ def get_parser(self, prog_name): project_group.add_argument( "--project", metavar="", - help="Set an alternate project on this image (name or ID)", + help=_("Set an alternate project on this image (name or ID)"), ) project_group.add_argument( "--owner", @@ -282,7 +282,7 @@ def get_parser(self, prog_name): "images", metavar="", nargs="+", - help="Image(s) to delete (name or ID)", + help=_("Image(s) to delete (name or ID)"), ) return parser @@ -307,14 +307,14 @@ def get_parser(self, prog_name): dest="public", action="store_true", default=False, - help="List only public images", + help=_("List only public images"), ) public_group.add_argument( "--private", dest="private", action="store_true", default=False, - help="List only private images", + help=_("List only private images"), ) # Included for silent CLI compatibility with v2 public_group.add_argument( @@ -328,13 +328,13 @@ def get_parser(self, prog_name): '--property', metavar='', action=parseractions.KeyValueAction, - help='Filter output based on property', + help=_('Filter output based on property'), ) parser.add_argument( '--long', action='store_true', default=False, - help='List additional fields in output', + help=_('List additional fields in output'), ) # --page-size has never worked, leave here for silent compatibility @@ -347,9 +347,9 @@ def get_parser(self, prog_name): parser.add_argument( '--sort', metavar="[:]", - help="Sort output by selected keys and directions(asc or desc) " - "(default: asc), multiple keys and directions can be " - "specified separated by comma", + help=_("Sort output by selected keys and directions(asc or desc) " + "(default: asc), multiple keys and directions can be " + "specified separated by comma"), ) return parser @@ -442,12 +442,12 @@ def get_parser(self, prog_name): parser.add_argument( "--file", metavar="", - help="Downloaded image save filename (default: stdout)", + help=_("Downloaded image save filename (default: stdout)"), ) parser.add_argument( "image", metavar="", - help="Image to save (name or ID)", + help=_("Image to save (name or ID)"), ) return parser @@ -470,31 +470,31 @@ def get_parser(self, prog_name): parser.add_argument( "image", metavar="", - help="Image to modify (name or ID)", + help=_("Image to modify (name or ID)"), ) parser.add_argument( "--name", metavar="", - help="New image name", + help=_("New image name"), ) parser.add_argument( "--min-disk", metavar="", type=int, - help="Minimum disk size needed to boot image, in gigabytes", + help=_("Minimum disk size needed to boot image, in gigabytes"), ) parser.add_argument( "--min-ram", metavar="", type=int, - help="Minimum RAM size needed to boot image, in megabytes", + help=_("Minimum RAM size needed to boot image, in megabytes"), ) container_choices = ["ami", "ari", "aki", "bare", "ovf"] parser.add_argument( "--container-format", metavar="", - help=("Container format of image. Acceptable formats: %s" % - container_choices), + help=_("Container format of image. Acceptable formats: %s") % + container_choices, choices=container_choices ) disk_choices = ["ami", "ari", "aki", "vhd", "vmdk", "raw", "qcow2", @@ -502,89 +502,90 @@ def get_parser(self, prog_name): parser.add_argument( "--disk-format", metavar="", - help="Disk format of image. Acceptable formats: %s" % disk_choices, + help=_("Disk format of image. Acceptable formats: %s") % + disk_choices, choices=disk_choices ) parser.add_argument( "--size", metavar="", type=int, - help="Size of image data (in bytes)" + help=_("Size of image data (in bytes)") ) protected_group = parser.add_mutually_exclusive_group() protected_group.add_argument( "--protected", action="store_true", - help="Prevent image from being deleted", + help=_("Prevent image from being deleted"), ) protected_group.add_argument( "--unprotected", action="store_true", - help="Allow image to be deleted (default)", + help=_("Allow image to be deleted (default)"), ) public_group = parser.add_mutually_exclusive_group() public_group.add_argument( "--public", action="store_true", - help="Image is accessible to the public", + help=_("Image is accessible to the public"), ) public_group.add_argument( "--private", action="store_true", - help="Image is inaccessible to the public (default)", + help=_("Image is inaccessible to the public (default)"), ) parser.add_argument( "--property", dest="properties", metavar="", action=parseractions.KeyValueAction, - help="Set a property on this image " - "(repeat option to set multiple properties)", + help=_("Set a property on this image " + "(repeat option to set multiple properties)"), ) parser.add_argument( "--store", metavar="", - help="Upload image to this store", + help=_("Upload image to this store"), ) parser.add_argument( "--location", metavar="", - help="Download image from an existing URL", + help=_("Download image from an existing URL"), ) parser.add_argument( "--copy-from", metavar="", - help="Copy image from the data store (similar to --location)", + help=_("Copy image from the data store (similar to --location)"), ) parser.add_argument( "--file", metavar="", - help="Upload image from local file", + help=_("Upload image from local file"), ) parser.add_argument( "--volume", metavar="", - help="Create image from a volume", + help=_("Create image from a volume"), ) parser.add_argument( "--force", dest='force', action='store_true', default=False, - help="Force image change if volume is in use " - "(only meaningful with --volume)", + help=_("Force image change if volume is in use " + "(only meaningful with --volume)"), ) parser.add_argument( "--stdin", dest='stdin', action='store_true', default=False, - help="Read image data from standard input", + help=_("Read image data from standard input"), ) parser.add_argument( "--checksum", metavar="", - help="Image hash used for verification", + help=_("Image hash used for verification"), ) # NOTE(dtroyer): --owner is deprecated in Jan 2016 in an early # 2.x release. Do not remove before Jan 2017 @@ -593,7 +594,7 @@ def get_parser(self, prog_name): project_group.add_argument( "--project", metavar="", - help="Set an alternate project on this image (name or ID)", + help=_("Set an alternate project on this image (name or ID)"), ) project_group.add_argument( "--owner", @@ -682,8 +683,9 @@ def take_action(self, parsed_args): # will do a chunked transfer kwargs["data"] = sys.stdin else: - self.log.warning('Use --stdin to enable read image' - ' data from standard input') + self.log.warning(_('Use --stdin to enable read ' + 'image data from standard ' + 'input')) if image.properties and parsed_args.properties: image.properties.update(kwargs['properties']) @@ -709,7 +711,7 @@ def get_parser(self, prog_name): parser.add_argument( "image", metavar="", - help="Image to display (name or ID)", + help=_("Image to display (name or ID)"), ) return parser diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 40ddd4b9ce..a9c0f1fdc1 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -25,7 +25,7 @@ from openstackclient.common import exceptions from openstackclient.common import parseractions from openstackclient.common import utils -from openstackclient.i18n import _ # noqa +from openstackclient.i18n import _ from openstackclient.identity import common @@ -72,12 +72,12 @@ def get_parser(self, prog_name): parser.add_argument( "image", metavar="", - help="Image to share (name or ID)", + help=_("Image to share (name or ID)"), ) parser.add_argument( "project", metavar="", - help="Project to associate with image (name or ID)", + help=_("Project to associate with image (name or ID)"), ) common.add_project_domain_option_to_parser(parser) return parser @@ -119,94 +119,94 @@ def get_parser(self, prog_name): parser.add_argument( "name", metavar="", - help="New image name", + help=_("New image name"), ) parser.add_argument( "--id", metavar="", - help="Image ID to reserve", + help=_("Image ID to reserve"), ) parser.add_argument( "--container-format", default=DEFAULT_CONTAINER_FORMAT, metavar="", - help="Image container format " - "(default: %s)" % DEFAULT_CONTAINER_FORMAT, + help=_("Image container format " + "(default: %s)") % DEFAULT_CONTAINER_FORMAT, ) parser.add_argument( "--disk-format", default=DEFAULT_DISK_FORMAT, metavar="", - help="Image disk format " - "(default: %s)" % DEFAULT_DISK_FORMAT, + help=_("Image disk format " + "(default: %s)") % DEFAULT_DISK_FORMAT, ) parser.add_argument( "--min-disk", metavar="", type=int, - help="Minimum disk size needed to boot image, in gigabytes", + help=_("Minimum disk size needed to boot image, in gigabytes"), ) parser.add_argument( "--min-ram", metavar="", type=int, - help="Minimum RAM size needed to boot image, in megabytes", + help=_("Minimum RAM size needed to boot image, in megabytes"), ) parser.add_argument( "--file", metavar="", - help="Upload image from local file", + help=_("Upload image from local file"), ) parser.add_argument( "--volume", metavar="", - help="Create image from a volume", + help=_("Create image from a volume"), ) parser.add_argument( "--force", dest='force', action='store_true', default=False, - help="Force image creation if volume is in use " - "(only meaningful with --volume)", + help=_("Force image creation if volume is in use " + "(only meaningful with --volume)"), ) protected_group = parser.add_mutually_exclusive_group() protected_group.add_argument( "--protected", action="store_true", - help="Prevent image from being deleted", + help=_("Prevent image from being deleted"), ) protected_group.add_argument( "--unprotected", action="store_true", - help="Allow image to be deleted (default)", + help=_("Allow image to be deleted (default)"), ) public_group = parser.add_mutually_exclusive_group() public_group.add_argument( "--public", action="store_true", - help="Image is accessible to the public", + help=_("Image is accessible to the public"), ) public_group.add_argument( "--private", action="store_true", - help="Image is inaccessible to the public (default)", + help=_("Image is inaccessible to the public (default)"), ) parser.add_argument( "--property", dest="properties", metavar="", action=parseractions.KeyValueAction, - help="Set a property on this image " - "(repeat option to set multiple properties)", + help=_("Set a property on this image " + "(repeat option to set multiple properties)"), ) parser.add_argument( "--tag", dest="tags", metavar="", action='append', - help="Set a tag on this image " - "(repeat option to set multiple tags)", + help=_("Set a tag on this image " + "(repeat option to set multiple tags)"), ) # NOTE(dtroyer): --owner is deprecated in Jan 2016 in an early # 2.x release. Do not remove before Jan 2017 @@ -215,7 +215,7 @@ def get_parser(self, prog_name): project_group.add_argument( "--project", metavar="", - help="Set an alternate project on this image (name or ID)", + help=_("Set an alternate project on this image (name or ID)"), ) project_group.add_argument( "--owner", @@ -239,8 +239,8 @@ def take_action(self, parsed_args): for deadopt in self.deadopts: if getattr(parsed_args, deadopt.replace('-', '_'), None): raise exceptions.CommandError( - "ERROR: --%s was given, which is an Image v1 option" - " that is no longer supported in Image v2" % deadopt) + _("ERROR: --%s was given, which is an Image v1 option" + " that is no longer supported in Image v2") % deadopt) # Build an attribute dict from the parsed args, only include # attributes that were actually set on the command line @@ -296,11 +296,12 @@ def take_action(self, parsed_args): fp = gc_utils.get_data_file(parsed_args) info = {} if fp is not None and parsed_args.volume: - raise exceptions.CommandError("Uploading data and using container " - "are not allowed at the same time") + raise exceptions.CommandError(_("Uploading data and using " + "container are not allowed at " + "the same time")) if fp is None and parsed_args.file: - self.log.warning("Failed to get an image file.") + self.log.warning(_("Failed to get an image file.")) return {}, {} if parsed_args.owner: @@ -366,7 +367,7 @@ def get_parser(self, prog_name): "images", metavar="", nargs="+", - help="Image(s) to delete (name or ID)", + help=_("Image(s) to delete (name or ID)"), ) return parser @@ -391,33 +392,33 @@ def get_parser(self, prog_name): dest="public", action="store_true", default=False, - help="List only public images", + help=_("List only public images"), ) public_group.add_argument( "--private", dest="private", action="store_true", default=False, - help="List only private images", + help=_("List only private images"), ) public_group.add_argument( "--shared", dest="shared", action="store_true", default=False, - help="List only shared images", + help=_("List only shared images"), ) parser.add_argument( '--property', metavar='', action=parseractions.KeyValueAction, - help='Filter output based on property', + help=_('Filter output based on property'), ) parser.add_argument( '--long', action='store_true', default=False, - help='List additional fields in output', + help=_('List additional fields in output'), ) # --page-size has never worked, leave here for silent compatibility @@ -430,23 +431,23 @@ def get_parser(self, prog_name): parser.add_argument( '--sort', metavar="[:]", - help="Sort output by selected keys and directions(asc or desc) " - "(default: asc), multiple keys and directions can be " - "specified separated by comma", + help=_("Sort output by selected keys and directions(asc or desc) " + "(default: asc), multiple keys and directions can be " + "specified separated by comma"), ) parser.add_argument( "--limit", metavar="", type=int, - help="Maximum number of images to display.", + help=_("Maximum number of images to display."), ) parser.add_argument( '--marker', metavar='', default=None, - help="The last image (name or ID) of the previous page. Display " - "list of images after marker. Display all images if not " - "specified." + help=_("The last image (name or ID) of the previous page. Display " + "list of images after marker. Display all images if not " + "specified."), ) return parser @@ -530,12 +531,12 @@ def get_parser(self, prog_name): parser.add_argument( "image", metavar="", - help="Image to unshare (name or ID)", + help=_("Image to unshare (name or ID)"), ) parser.add_argument( "project", metavar="", - help="Project to disassociate with image (name or ID)", + help=_("Project to disassociate with image (name or ID)"), ) common.add_project_domain_option_to_parser(parser) return parser @@ -563,12 +564,12 @@ def get_parser(self, prog_name): parser.add_argument( "--file", metavar="", - help="Downloaded image save filename (default: stdout)", + help=_("Downloaded image save filename (default: stdout)"), ) parser.add_argument( "image", metavar="", - help="Image to save (name or ID)", + help=_("Image to save (name or ID)"), ) return parser @@ -603,66 +604,66 @@ def get_parser(self, prog_name): parser.add_argument( "image", metavar="", - help="Image to modify (name or ID)" + help=_("Image to modify (name or ID)") ) parser.add_argument( "--name", metavar="", - help="New image name" + help=_("New image name") ) parser.add_argument( "--min-disk", type=int, metavar="", - help="Minimum disk size needed to boot image, in gigabytes" + help=_("Minimum disk size needed to boot image, in gigabytes") ) parser.add_argument( "--min-ram", type=int, metavar="", - help="Minimum RAM size needed to boot image, in megabytes", + help=_("Minimum RAM size needed to boot image, in megabytes"), ) parser.add_argument( "--container-format", metavar="", - help="Image container format " - "(default: %s)" % DEFAULT_CONTAINER_FORMAT, + help=_("Image container format " + "(default: %s)") % DEFAULT_CONTAINER_FORMAT, ) parser.add_argument( "--disk-format", metavar="", - help="Image disk format " - "(default: %s)" % DEFAULT_DISK_FORMAT, + help=_("Image disk format " + "(default: %s)") % DEFAULT_DISK_FORMAT, ) protected_group = parser.add_mutually_exclusive_group() protected_group.add_argument( "--protected", action="store_true", - help="Prevent image from being deleted", + help=_("Prevent image from being deleted"), ) protected_group.add_argument( "--unprotected", action="store_true", - help="Allow image to be deleted (default)", + help=_("Allow image to be deleted (default)"), ) public_group = parser.add_mutually_exclusive_group() public_group.add_argument( "--public", action="store_true", - help="Image is accessible to the public", + help=_("Image is accessible to the public"), ) public_group.add_argument( "--private", action="store_true", - help="Image is inaccessible to the public (default)", + help=_("Image is inaccessible to the public (default)"), ) parser.add_argument( "--property", dest="properties", metavar="", action=parseractions.KeyValueAction, - help="Set a property on this image " - "(repeat option to set multiple properties)", + help=_("Set a property on this image " + "(repeat option to set multiple properties)"), ) parser.add_argument( "--tag", @@ -670,18 +671,18 @@ def get_parser(self, prog_name): metavar="", default=[], action='append', - help="Set a tag on this image " - "(repeat option to set multiple tags)", + help=_("Set a tag on this image " + "(repeat option to set multiple tags)"), ) parser.add_argument( "--architecture", metavar="", - help="Operating system architecture", + help=_("Operating system architecture"), ) parser.add_argument( "--instance-id", metavar="", - help="ID of server instance used to create this image", + help=_("ID of server instance used to create this image"), ) parser.add_argument( "--instance-uuid", @@ -692,33 +693,33 @@ def get_parser(self, prog_name): parser.add_argument( "--kernel-id", metavar="", - help="ID of kernel image used to boot this disk image", + help=_("ID of kernel image used to boot this disk image"), ) parser.add_argument( "--os-distro", metavar="", - help="Operating system distribution name", + help=_("Operating system distribution name"), ) parser.add_argument( "--os-version", metavar="", - help="Operating system distribution version", + help=_("Operating system distribution version"), ) parser.add_argument( "--ramdisk-id", metavar="", - help="ID of ramdisk image used to boot this disk image", + help=_("ID of ramdisk image used to boot this disk image"), ) deactivate_group = parser.add_mutually_exclusive_group() deactivate_group.add_argument( "--deactivate", action="store_true", - help="Deactivate the image", + help=_("Deactivate the image"), ) deactivate_group.add_argument( "--activate", action="store_true", - help="Activate the image", + help=_("Activate the image"), ) # NOTE(dtroyer): --owner is deprecated in Jan 2016 in an early # 2.x release. Do not remove before Jan 2017 @@ -727,7 +728,7 @@ def get_parser(self, prog_name): project_group.add_argument( "--project", metavar="", - help="Set an alternate project on this image (name or ID)", + help=_("Set an alternate project on this image (name or ID)"), ) project_group.add_argument( "--owner", @@ -751,8 +752,8 @@ def take_action(self, parsed_args): for deadopt in self.deadopts: if getattr(parsed_args, deadopt.replace('-', '_'), None): raise exceptions.CommandError( - "ERROR: --%s was given, which is an Image v1 option" - " that is no longer supported in Image v2" % deadopt) + _("ERROR: --%s was given, which is an Image v1 option" + " that is no longer supported in Image v2") % deadopt) kwargs = {} copy_attrs = ('architecture', 'container_format', 'disk_format', @@ -804,7 +805,7 @@ def take_action(self, parsed_args): # Checks if anything that requires getting the image if not (kwargs or parsed_args.deactivate or parsed_args.activate): - self.log.warning("No arguments specified") + self.log.warning(_("No arguments specified")) return {}, {} image = utils.find_resource( @@ -842,7 +843,7 @@ def get_parser(self, prog_name): parser.add_argument( "image", metavar="", - help="Image to display (name or ID)", + help=_("Image to display (name or ID)"), ) return parser From b92cf77fb58b4aafcf4b3a1ef6b89fd15166505b Mon Sep 17 00:00:00 2001 From: Fang Zhen Date: Mon, 14 Mar 2016 19:27:36 +0800 Subject: [PATCH 0894/3095] Add network support for "quota set" The "quota set" command support compute and volume quotas previously. This patch add support network. Partially-implements: blueprint neutron-client-quota Closes-bug: 1489441 Change-Id: I9d297f52bc30614b3493f09ed15f8f1d3f8ff952 --- doc/source/command-objects/quota.rst | 50 ++++++++++++++++ functional/tests/common/test_quota.py | 9 ++- openstackclient/common/quota.py | 59 ++++++++++++++++--- openstackclient/tests/common/test_quota.py | 59 +++++++++++++++++++ openstackclient/tests/network/v2/fakes.py | 3 + ...uota-set-for-network-11fcd7b9e08624b5.yaml | 9 +++ 6 files changed, 175 insertions(+), 14 deletions(-) create mode 100644 releasenotes/notes/add-quota-set-for-network-11fcd7b9e08624b5.yaml diff --git a/doc/source/command-objects/quota.rst b/doc/source/command-objects/quota.rst index 9e09bd484f..dc5e362355 100644 --- a/doc/source/command-objects/quota.rst +++ b/doc/source/command-objects/quota.rst @@ -32,6 +32,20 @@ Set quotas for project [--volumes ] [--volume-type ] + # Network settings + [--floating-ips ] + [--secgroup-rules ] + [--secgroups ] + [--networks ] + [--subnets ] + [--ports ] + [--routers ] + [--rbac-policies ] + [--vips ] + [--subnetpools ] + [--members ] + [--health-monitors ] + Set quotas for class @@ -126,6 +140,42 @@ Set quotas for class Set quotas for a specific +.. option:: --networks + + New value for the networks quota + +.. option:: --subnets + + New value for the subnets quota + +.. option:: --ports + + New value for the ports quota + +.. option:: --routers + + New value for the routers quota + +.. option:: --rbac-policies + + New value for the rbac-policies quota + +.. option:: --vips + + New value for the vips quota + +.. option:: --subnetpools + + New value for the subnetpools quota + +.. option:: --members + + New value for the members quota + +.. option:: --health-monitors + + New value for the health-monitors quota + quota show ---------- diff --git a/functional/tests/common/test_quota.py b/functional/tests/common/test_quota.py index 22549efe8c..0bc93db283 100644 --- a/functional/tests/common/test_quota.py +++ b/functional/tests/common/test_quota.py @@ -16,7 +16,7 @@ class QuotaTests(test.TestCase): """Functional tests for quota. """ # Test quota information for compute, network and volume. - EXPECTED_FIELDS = ['instances', 'network', 'volumes'] + EXPECTED_FIELDS = ['instances', 'networks', 'volumes'] PROJECT_NAME = None @classmethod @@ -25,12 +25,11 @@ def setUpClass(cls): cls.get_openstack_configuration_value('auth.project_name') def test_quota_set(self): - # TODO(rtheis): Add --network option once supported on set. - self.openstack('quota set --instances 11 --volumes 11 ' + - self.PROJECT_NAME) + self.openstack('quota set --instances 11 --volumes 11 --networks 11 ' + + self.PROJECT_NAME) opts = self.get_show_opts(self.EXPECTED_FIELDS) raw_output = self.openstack('quota show ' + self.PROJECT_NAME + opts) - self.assertEqual("11\n10\n11\n", raw_output) + self.assertEqual("11\n11\n11\n", raw_output) def test_quota_show(self): raw_output = self.openstack('quota show ' + self.PROJECT_NAME) diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index b497a44d62..e177fbcee8 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -29,7 +29,6 @@ COMPUTE_QUOTAS = { 'cores': 'cores', 'fixed_ips': 'fixed-ips', - 'floating_ips': 'floating-ips', 'injected_file_content_bytes': 'injected-file-size', 'injected_file_path_bytes': 'injected-path-size', 'injected_files': 'injected-files', @@ -37,8 +36,6 @@ 'key_pairs': 'key-pairs', 'metadata_items': 'properties', 'ram': 'ram', - 'security_group_rules': 'secgroup-rules', - 'security_groups': 'secgroups', } VOLUME_QUOTAS = { @@ -47,16 +44,41 @@ 'volumes': 'volumes', } +NOVA_NETWORK_QUOTAS = { + 'floating_ips': 'floating-ips', + 'security_group_rules': 'secgroup-rules', + 'security_groups': 'secgroups', +} + NETWORK_QUOTAS = { 'floatingip': 'floating-ips', 'security_group_rule': 'secgroup-rules', 'security_group': 'secgroups', + 'network': 'networks', + 'subnet': 'subnets', + 'port': 'ports', + 'router': 'routers', + 'rbac_policy': 'rbac-policies', + 'vip': 'vips', + 'subnetpool': 'subnetpools', + 'member': 'members', + 'health_monitor': 'health-monitors', } class SetQuota(command.Command): """Set quotas for project or class""" + def _build_options_list(self): + if self.app.client_manager.is_network_endpoint_enabled(): + return itertools.chain(COMPUTE_QUOTAS.items(), + VOLUME_QUOTAS.items(), + NETWORK_QUOTAS.items()) + else: + return itertools.chain(COMPUTE_QUOTAS.items(), + VOLUME_QUOTAS.items(), + NOVA_NETWORK_QUOTAS.items()) + def get_parser(self, prog_name): parser = super(SetQuota, self).get_parser(prog_name) parser.add_argument( @@ -71,8 +93,7 @@ def get_parser(self, prog_name): default=False, help='Set quotas for ', ) - for k, v in itertools.chain( - COMPUTE_QUOTAS.items(), VOLUME_QUOTAS.items()): + for k, v in self._build_options_list(): parser.add_argument( '--%s' % v, metavar='<%s>' % v, @@ -92,7 +113,7 @@ def take_action(self, parsed_args): identity_client = self.app.client_manager.identity compute_client = self.app.client_manager.compute volume_client = self.app.client_manager.volume - + network_client = self.app.client_manager.network compute_kwargs = {} for k, v in COMPUTE_QUOTAS.items(): value = getattr(parsed_args, k, None) @@ -107,7 +128,20 @@ def take_action(self, parsed_args): k = k + '_%s' % parsed_args.volume_type volume_kwargs[k] = value - if compute_kwargs == {} and volume_kwargs == {}: + network_kwargs = {} + if self.app.client_manager.is_network_endpoint_enabled(): + for k, v in NETWORK_QUOTAS.items(): + value = getattr(parsed_args, k, None) + if value is not None: + network_kwargs[k] = value + else: + for k, v in NOVA_NETWORK_QUOTAS.items(): + value = getattr(parsed_args, k, None) + if value is not None: + compute_kwargs[k] = value + + if (compute_kwargs == {} and volume_kwargs == {} + and network_kwargs == {}): sys.stderr.write("No quotas updated") return @@ -126,6 +160,9 @@ def take_action(self, parsed_args): volume_client.quota_classes.update( project.id, **volume_kwargs) + if network_kwargs: + sys.stderr.write("Network quotas are ignored since quota class" + "is not supported.") else: if compute_kwargs: compute_client.quotas.update( @@ -135,6 +172,10 @@ def take_action(self, parsed_args): volume_client.quotas.update( project.id, **volume_kwargs) + if network_kwargs: + network_client.update_quota( + project.id, + **network_kwargs) class ShowQuota(command.ShowOne): @@ -232,8 +273,8 @@ def take_action(self, parsed_args): # neutron is enabled, quotas of these three resources # in nova will be replaced by neutron's. for k, v in itertools.chain( - COMPUTE_QUOTAS.items(), VOLUME_QUOTAS.items(), - NETWORK_QUOTAS.items()): + COMPUTE_QUOTAS.items(), NOVA_NETWORK_QUOTAS.items(), + VOLUME_QUOTAS.items(), NETWORK_QUOTAS.items()): if not k == v and info.get(k): info[v] = info[k] info.pop(k) diff --git a/openstackclient/tests/common/test_quota.py b/openstackclient/tests/common/test_quota.py index ba7ee469c9..c9ec5599f1 100644 --- a/openstackclient/tests/common/test_quota.py +++ b/openstackclient/tests/common/test_quota.py @@ -97,6 +97,9 @@ def setUp(self): loaded=True, ) + self.network_mock = self.app.client_manager.network + self.network_mock.update_quota = mock.Mock() + self.cmd = quota.SetQuota(self.app, None) def test_quota_set(self): @@ -132,6 +135,7 @@ def test_quota_set(self): ('project', identity_fakes.project_name), ] + self.app.client_manager.network_endpoint_enabled = False parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) @@ -185,6 +189,61 @@ def test_quota_set_volume(self): **kwargs ) + def test_quota_set_network(self): + arglist = [ + '--subnets', str(network_fakes.QUOTA['subnet']), + '--networks', str(network_fakes.QUOTA['network']), + '--floating-ips', str(network_fakes.QUOTA['floatingip']), + '--subnetpools', str(network_fakes.QUOTA['subnetpool']), + '--secgroup-rules', + str(network_fakes.QUOTA['security_group_rule']), + '--secgroups', str(network_fakes.QUOTA['security_group']), + '--routers', str(network_fakes.QUOTA['router']), + '--rbac-policies', str(network_fakes.QUOTA['rbac_policy']), + '--ports', str(network_fakes.QUOTA['port']), + '--vips', str(network_fakes.QUOTA['vip']), + '--members', str(network_fakes.QUOTA['member']), + '--health-monitors', str(network_fakes.QUOTA['health_monitor']), + identity_fakes.project_name, + ] + verifylist = [ + ('subnet', network_fakes.QUOTA['subnet']), + ('network', network_fakes.QUOTA['network']), + ('floatingip', network_fakes.QUOTA['floatingip']), + ('subnetpool', network_fakes.QUOTA['subnetpool']), + ('security_group_rule', + network_fakes.QUOTA['security_group_rule']), + ('security_group', network_fakes.QUOTA['security_group']), + ('router', network_fakes.QUOTA['router']), + ('rbac_policy', network_fakes.QUOTA['rbac_policy']), + ('port', network_fakes.QUOTA['port']), + ('vip', network_fakes.QUOTA['vip']), + ('member', network_fakes.QUOTA['member']), + ('health_monitor', network_fakes.QUOTA['health_monitor']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + kwargs = { + 'subnet': network_fakes.QUOTA['subnet'], + 'network': network_fakes.QUOTA['network'], + 'floatingip': network_fakes.QUOTA['floatingip'], + 'subnetpool': network_fakes.QUOTA['subnetpool'], + 'security_group_rule': + network_fakes.QUOTA['security_group_rule'], + 'security_group': network_fakes.QUOTA['security_group'], + 'router': network_fakes.QUOTA['router'], + 'rbac_policy': network_fakes.QUOTA['rbac_policy'], + 'port': network_fakes.QUOTA['port'], + 'vip': network_fakes.QUOTA['vip'], + 'member': network_fakes.QUOTA['member'], + 'health_monitor': network_fakes.QUOTA['health_monitor'], + } + self.network_mock.update_quota.assert_called_with( + identity_fakes.project_id, + **kwargs + ) + class TestQuotaShow(TestQuota): diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 84ede381bf..417cf26ee7 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -36,6 +36,9 @@ "router": 10, "rbac_policy": -1, "port": 50, + "vip": 10, + "member": 10, + "health_monitor": 10, } diff --git a/releasenotes/notes/add-quota-set-for-network-11fcd7b9e08624b5.yaml b/releasenotes/notes/add-quota-set-for-network-11fcd7b9e08624b5.yaml new file mode 100644 index 0000000000..0f7dd55926 --- /dev/null +++ b/releasenotes/notes/add-quota-set-for-network-11fcd7b9e08624b5.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + Add network support for ``quota set`` command. Options added includes + ``--networks --subnets --subnetpools --ports --routers --rbac-policies`` + ``--vips --members --health-monitors``. + Options ``--floating-ips --secgroup-rules --secgroups`` now support + both network and compute API. + [Bug `1489441 `_] From a4d4e81c88da400d4ec62fb9fdc00eeeafafc0e5 Mon Sep 17 00:00:00 2001 From: Madhu Mohan Nelemane Date: Wed, 27 Apr 2016 18:43:57 +0200 Subject: [PATCH 0895/3095] Avoid TypeError on message object additions Change-Id: I634c1e158e93eeb55ab17fef8a0715b6678dffec Closes-Bug: #1575787 --- openstackclient/api/auth.py | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/openstackclient/api/auth.py b/openstackclient/api/auth.py index 3d6f7bcf19..c74e8005de 100644 --- a/openstackclient/api/auth.py +++ b/openstackclient/api/auth.py @@ -142,14 +142,14 @@ def check_valid_auth_options(options, auth_plugin_name, required_scope=True): """ - msg = '' + msgs = [] if auth_plugin_name.endswith('password'): if not options.auth.get('username'): - msg += _('Set a username with --os-username, OS_USERNAME,' - ' or auth.username\n') + msgs.append(_('Set a username with --os-username, OS_USERNAME,' + ' or auth.username')) if not options.auth.get('auth_url'): - msg += _('Set an authentication URL, with --os-auth-url,' - ' OS_AUTH_URL or auth.auth_url\n') + msgs.append(_('Set an authentication URL, with --os-auth-url,' + ' OS_AUTH_URL or auth.auth_url')) if (required_scope and not options.auth.get('project_id') and not options.auth.get('domain_id') and not @@ -157,24 +157,28 @@ def check_valid_auth_options(options, auth_plugin_name, required_scope=True): options.auth.get('project_name') and not options.auth.get('tenant_id') and not options.auth.get('tenant_name')): - msg += _('Set a scope, such as a project or domain, set a ' - 'project scope with --os-project-name, OS_PROJECT_NAME ' - 'or auth.project_name, set a domain scope with ' - '--os-domain-name, OS_DOMAIN_NAME or auth.domain_name') + msgs.append(_('Set a scope, such as a project or domain, set a ' + 'project scope with --os-project-name, ' + 'OS_PROJECT_NAME or auth.project_name, set a domain ' + 'scope with --os-domain-name, OS_DOMAIN_NAME or ' + 'auth.domain_name')) elif auth_plugin_name.endswith('token'): if not options.auth.get('token'): - msg += _('Set a token with --os-token, OS_TOKEN or auth.token\n') + msgs.append(_('Set a token with --os-token, OS_TOKEN or ' + 'auth.token')) if not options.auth.get('auth_url'): - msg += _('Set a service AUTH_URL, with --os-auth-url, ' - 'OS_AUTH_URL or auth.auth_url\n') + msgs.append(_('Set a service AUTH_URL, with --os-auth-url, ' + 'OS_AUTH_URL or auth.auth_url')) elif auth_plugin_name == 'token_endpoint': if not options.auth.get('token'): - msg += _('Set a token with --os-token, OS_TOKEN or auth.token\n') + msgs.append(_('Set a token with --os-token, OS_TOKEN or ' + 'auth.token')) if not options.auth.get('url'): - msg += _('Set a service URL, with --os-url, OS_URL or auth.url\n') + msgs.append(_('Set a service URL, with --os-url, OS_URL or ' + 'auth.url')) - if msg: - raise exc.CommandError('Missing parameter(s): \n%s' % msg) + if msgs: + raise exc.CommandError('Missing parameter(s): \n%s' % '\n'.join(msgs)) def build_auth_plugins_option_parser(parser): From c3d481085022ffffc4fb02bbf4766ca49a4c5806 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 19 May 2016 12:53:01 -0500 Subject: [PATCH 0896/3095] Fix image tests to use warlock resources We have been testing (incorrectly) Image v2 using our usual FakeResource objects, when the v2 API actually uses warlock schema modelled resources. Bring this to the tests (TestImageSet was already doing this) Change-Id: Ia6ed3a8e28a8961f770c241b49d47cce9ff328d3 --- openstackclient/tests/image/v2/fakes.py | 45 ++++++-------------- openstackclient/tests/image/v2/test_image.py | 19 +++------ 2 files changed, 21 insertions(+), 43 deletions(-) diff --git a/openstackclient/tests/image/v2/fakes.py b/openstackclient/tests/image/v2/fakes.py index f90d846d6a..a662a58524 100644 --- a/openstackclient/tests/image/v2/fakes.py +++ b/openstackclient/tests/image/v2/fakes.py @@ -18,6 +18,9 @@ import random import uuid +from glanceclient.v2 import schemas +import warlock + from openstackclient.common import utils as common_utils from openstackclient.tests import fakes from openstackclient.tests import utils @@ -194,7 +197,7 @@ def create_one_image(attrs=None): # Set default attribute image_info = { - 'id': 'image-id' + uuid.uuid4().hex, + 'id': str(uuid.uuid4()), 'name': 'image-name' + uuid.uuid4().hex, 'owner': 'image-owner' + uuid.uuid4().hex, 'protected': bool(random.choice([0, 1])), @@ -205,11 +208,13 @@ def create_one_image(attrs=None): # Overwrite default attributes if there are some attributes set image_info.update(attrs) - image = fakes.FakeResource( - None, - image_info, - loaded=True) - return image + # Set up the schema + model = warlock.model_factory( + IMAGE_schema, + schemas.SchemaBasedModel, + ) + + return model(**image_info) @staticmethod def create_images(attrs=None, count=2): @@ -248,27 +253,6 @@ def get_images(images=None, count=2): return mock.MagicMock(side_effect=images) - @staticmethod - def get_image_info(image=None): - """Get the image info from a faked image object. - - :param image: - A FakeResource objects faking image - :return - A dictionary which includes the faked image info as follows: - { - 'id': image_id, - 'name': image_name, - 'owner': image_owner, - 'protected': image_protected, - 'visibility': image_visibility, - 'tags': image_tags - } - """ - if image is not None: - return image._info - return {} - @staticmethod def get_image_columns(image=None): """Get the image columns from a faked image object. @@ -280,9 +264,8 @@ def get_image_columns(image=None): ('id', 'name', 'owner', 'protected', 'visibility', 'tags') """ if image is not None: - return tuple(k for k in sorted( - FakeImage.get_image_info(image).keys())) - return tuple([]) + return tuple(sorted(image)) + return IMAGE_columns @staticmethod def get_image_data(image=None): @@ -296,7 +279,7 @@ def get_image_data(image=None): """ data_list = [] if image is not None: - for x in sorted(FakeImage.get_image_info(image).keys()): + for x in sorted(image.keys()): if x == 'tags': # The 'tags' should be format_list data_list.append( diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index 33f2533105..beebdef995 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -75,7 +75,8 @@ def setUp(self): # This is the return value for utils.find_resource() self.images_mock.get.return_value = copy.deepcopy( - image_fakes.FakeImage.get_image_info(self.new_image)) + self.new_image + ) self.images_mock.update.return_value = self.new_image # Get the command object to test @@ -492,7 +493,7 @@ def setUp(self): self.api_mock = mock.Mock() self.api_mock.image_list.side_effect = [ - [image_fakes.FakeImage.get_image_info(self._image)], [], + [self._image], [], ] self.app.client_manager.image.api = self.api_mock @@ -632,10 +633,7 @@ def test_image_list_long_option(self): @mock.patch('openstackclient.api.utils.simple_filter') def test_image_list_property_option(self, sf_mock): - sf_mock.return_value = [ - copy.deepcopy( - image_fakes.FakeImage.get_image_info(self._image)), - ] + sf_mock.return_value = [copy.deepcopy(self._image)] arglist = [ '--property', 'a=1', @@ -651,7 +649,7 @@ def test_image_list_property_option(self, sf_mock): columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with() sf_mock.assert_called_with( - [image_fakes.FakeImage.get_image_info(self._image)], + [self._image], attr='a', value='1', property_field='properties', @@ -662,10 +660,7 @@ def test_image_list_property_option(self, sf_mock): @mock.patch('openstackclient.common.utils.sort_items') def test_image_list_sort_option(self, si_mock): - si_mock.return_value = [ - copy.deepcopy( - image_fakes.FakeImage.get_image_info(self._image)) - ] + si_mock.return_value = [copy.deepcopy(self._image)] arglist = ['--sort', 'name:asc'] verifylist = [('sort', 'name:asc')] @@ -677,7 +672,7 @@ def test_image_list_sort_option(self, si_mock): columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with() si_mock.assert_called_with( - [image_fakes.FakeImage.get_image_info(self._image)], + [self._image], 'name:asc' ) self.assertEqual(self.columns, columns) From e10a5979bc4cbe5b6ff89ae7af6ea726eb1a0000 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Sat, 21 May 2016 16:01:37 +0800 Subject: [PATCH 0897/3095] Add functional tests for server group in ComputeV2 Change-Id: I43a6ce3a6d976f3d1bd68c0483c929977b660f0d --- .../tests/compute/v2/test_server_group.py | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 functional/tests/compute/v2/test_server_group.py diff --git a/functional/tests/compute/v2/test_server_group.py b/functional/tests/compute/v2/test_server_group.py new file mode 100644 index 0000000000..ce4f97a67b --- /dev/null +++ b/functional/tests/compute/v2/test_server_group.py @@ -0,0 +1,46 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import uuid + +from functional.common import test + + +class ServerGroupTests(test.TestCase): + """Functional tests for servergroup. """ + + NAME = uuid.uuid4().hex + HEADERS = ['Name'] + FIELDS = ['name'] + + @classmethod + def setUpClass(cls): + opts = cls.get_show_opts(cls.FIELDS) + raw_output = cls.openstack('server group create --policy affinity ' + + cls.NAME + opts) + expected = cls.NAME + '\n' + cls.assertOutput(expected, raw_output) + + @classmethod + def tearDownClass(cls): + raw_output = cls.openstack('server group delete ' + cls.NAME) + cls.assertOutput('', raw_output) + + def test_server_group_list(self): + opts = self.get_list_opts(self.HEADERS) + raw_output = self.openstack('server group list' + opts) + self.assertIn(self.NAME, raw_output) + + def test_server_group_show(self): + opts = self.get_show_opts(self.FIELDS) + raw_output = self.openstack('server group show ' + self.NAME + opts) + self.assertEqual(self.NAME + "\n", raw_output) From dc71d165298c38f99eb62583bcd80bc4bc9a75f4 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Thu, 19 May 2016 11:03:13 +0800 Subject: [PATCH 0898/3095] Add FakeSnapshot class and update snapshot test in VolumeV2 Add FakeSnapshot class and update unit tests for snapshot commands with the FakeSnapshot class. Change-Id: If039a48b9d5f8430cc3d041b8c7ec30af8ff0e03 --- openstackclient/tests/volume/v2/fakes.py | 53 ++++ .../tests/volume/v2/test_snapshot.py | 265 ++++++++++-------- 2 files changed, 196 insertions(+), 122 deletions(-) diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py index c4155c69be..6e372de3b9 100644 --- a/openstackclient/tests/volume/v2/fakes.py +++ b/openstackclient/tests/volume/v2/fakes.py @@ -552,3 +552,56 @@ def create_backups(attrs=None, count=2): backups.append(backup) return backups + + +class FakeSnapshot(object): + """Fake one or more snapshot.""" + + @staticmethod + def create_one_snapshot(attrs=None): + """Create a fake snapshot. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object with id, name, description, etc. + """ + attrs = attrs or {} + + # Set default attributes. + snapshot_info = { + "id": 'snapshot-id-' + uuid.uuid4().hex, + "name": 'snapshot-name-' + uuid.uuid4().hex, + "description": 'snapshot-description-' + uuid.uuid4().hex, + "size": 10, + "status": "available", + "metadata": {"foo": "bar"}, + "created_at": "2015-06-03T18:49:19.000000", + "volume_id": 'vloume-id-' + uuid.uuid4().hex, + } + + # Overwrite default attributes. + snapshot_info.update(attrs) + + snapshot = fakes.FakeResource( + info=copy.deepcopy(snapshot_info), + loaded=True) + return snapshot + + @staticmethod + def create_snapshots(attrs=None, count=2): + """Create multiple fake snapshots. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of snapshots to fake + :return: + A list of FakeResource objects faking the snapshots + """ + snapshots = [] + for i in range(0, count): + snapshot = FakeSnapshot.create_one_snapshot(attrs) + snapshots.append(snapshot) + + return snapshots diff --git a/openstackclient/tests/volume/v2/test_snapshot.py b/openstackclient/tests/volume/v2/test_snapshot.py index d2fa5e07de..fe6fbb520d 100644 --- a/openstackclient/tests/volume/v2/test_snapshot.py +++ b/openstackclient/tests/volume/v2/test_snapshot.py @@ -12,9 +12,7 @@ # under the License. # -import copy - -from openstackclient.tests import fakes +from openstackclient.common import utils from openstackclient.tests.volume.v2 import fakes as volume_fakes from openstackclient.volume.v2 import snapshot @@ -32,58 +30,75 @@ def setUp(self): class TestSnapshotCreate(TestSnapshot): + columns = ( + 'created_at', + 'description', + 'id', + 'name', + 'properties', + 'size', + 'status', + 'volume_id', + ) + def setUp(self): super(TestSnapshotCreate, self).setUp() - self.volumes_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.VOLUME), - loaded=True + self.volume = volume_fakes.FakeVolume.create_one_volume() + self.new_snapshot = volume_fakes.FakeSnapshot.create_one_snapshot( + attrs={'volume_id': self.volume.id}) + + self.data = ( + self.new_snapshot.created_at, + self.new_snapshot.description, + self.new_snapshot.id, + self.new_snapshot.name, + utils.format_dict(self.new_snapshot.metadata), + self.new_snapshot.size, + self.new_snapshot.status, + self.new_snapshot.volume_id, ) - self.snapshots_mock.create.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.SNAPSHOT), - loaded=True - ) + self.volumes_mock.get.return_value = self.volume + self.snapshots_mock.create.return_value = self.new_snapshot # Get the command object to test self.cmd = snapshot.CreateSnapshot(self.app, None) def test_snapshot_create(self): arglist = [ - volume_fakes.volume_id, - "--name", volume_fakes.snapshot_name, - "--description", volume_fakes.snapshot_description, - "--force" + "--name", self.new_snapshot.name, + "--description", self.new_snapshot.description, + "--force", + self.new_snapshot.volume_id, ] verifylist = [ - ("volume", volume_fakes.volume_id), - ("name", volume_fakes.snapshot_name), - ("description", volume_fakes.snapshot_description), - ("force", True) + ("name", self.new_snapshot.name), + ("description", self.new_snapshot.description), + ("force", True), + ("volume", self.new_snapshot.volume_id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.snapshots_mock.create.assert_called_with( - volume_fakes.volume_id, + self.new_snapshot.volume_id, force=True, - name=volume_fakes.snapshot_name, - description=volume_fakes.snapshot_description + name=self.new_snapshot.name, + description=self.new_snapshot.description ) - self.assertEqual(columns, volume_fakes.SNAPSHOT_columns) - self.assertEqual(data, volume_fakes.SNAPSHOT_data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) def test_snapshot_create_without_name(self): arglist = [ - volume_fakes.volume_id, - "--description", volume_fakes.snapshot_description, + self.new_snapshot.volume_id, + "--description", self.new_snapshot.description, "--force" ] verifylist = [ - ("volume", volume_fakes.volume_id), - ("description", volume_fakes.snapshot_description), + ("volume", self.new_snapshot.volume_id), + ("description", self.new_snapshot.description), ("force", True) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -91,24 +106,23 @@ def test_snapshot_create_without_name(self): columns, data = self.cmd.take_action(parsed_args) self.snapshots_mock.create.assert_called_with( - volume_fakes.volume_id, + self.new_snapshot.volume_id, force=True, name=None, - description=volume_fakes.snapshot_description + description=self.new_snapshot.description ) - self.assertEqual(columns, volume_fakes.SNAPSHOT_columns) - self.assertEqual(data, volume_fakes.SNAPSHOT_data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) class TestSnapshotDelete(TestSnapshot): + snapshot = volume_fakes.FakeSnapshot.create_one_snapshot() + def setUp(self): super(TestSnapshotDelete, self).setUp() - self.snapshots_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.SNAPSHOT), - loaded=True) + self.snapshots_mock.get.return_value = self.snapshot self.snapshots_mock.delete.return_value = None # Get the command object to mock @@ -116,21 +130,25 @@ def setUp(self): def test_snapshot_delete(self): arglist = [ - volume_fakes.snapshot_id + self.snapshot.id ] verifylist = [ - ("snapshots", [volume_fakes.snapshot_id]) + ("snapshots", [self.snapshot.id]) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.snapshots_mock.delete.assert_called_with(volume_fakes.snapshot_id) + self.snapshots_mock.delete.assert_called_with(self.snapshot.id) self.assertIsNone(result) class TestSnapshotList(TestSnapshot): + volume = volume_fakes.FakeVolume.create_one_volume() + snapshots = volume_fakes.FakeSnapshot.create_snapshots( + attrs={'volume_id': volume.name}, count=3) + columns = [ "ID", "Name", @@ -138,24 +156,39 @@ class TestSnapshotList(TestSnapshot): "Status", "Size" ] + columns_long = columns + [ + "Created At", + "Volume", + "Properties" + ] + + data = [] + for s in snapshots: + data.append(( + s.id, + s.name, + s.description, + s.status, + s.size, + )) + data_long = [] + for s in snapshots: + data_long.append(( + s.id, + s.name, + s.description, + s.status, + s.size, + s.created_at, + s.volume_id, + utils.format_dict(s.metadata), + )) def setUp(self): super(TestSnapshotList, self).setUp() - self.volumes_mock.list.return_value = [ - fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.VOLUME), - loaded=True - ) - ] - self.snapshots_mock.list.return_value = [ - fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.SNAPSHOT), - loaded=True - ) - ] + self.volumes_mock.list.return_value = [self.volume] + self.snapshots_mock.list.return_value = self.snapshots # Get the command to test self.cmd = snapshot.ListSnapshot(self.app, None) @@ -169,14 +202,7 @@ def test_snapshot_list_without_options(self): columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) - datalist = (( - volume_fakes.snapshot_id, - volume_fakes.snapshot_name, - volume_fakes.snapshot_description, - "available", - volume_fakes.snapshot_size - ),) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.data, list(data)) def test_snapshot_list_with_options(self): arglist = ["--long"] @@ -185,24 +211,8 @@ def test_snapshot_list_with_options(self): columns, data = self.cmd.take_action(parsed_args) - columns = self.columns + [ - "Created At", - "Volume", - "Properties" - ] - self.assertEqual(columns, columns) - - datalist = (( - volume_fakes.snapshot_id, - volume_fakes.snapshot_name, - volume_fakes.snapshot_description, - "available", - volume_fakes.snapshot_size, - "2015-06-03T18:49:19.000000", - volume_fakes.volume_name, - volume_fakes.EXPECTED_SNAPSHOT.get("properties") - ),) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns_long, columns) + self.assertEqual(self.data_long, list(data)) def test_snapshot_list_all_projects(self): arglist = [ @@ -217,26 +227,17 @@ def test_snapshot_list_all_projects(self): columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) - - datalist = (( - volume_fakes.snapshot_id, - volume_fakes.snapshot_name, - volume_fakes.snapshot_description, - "available", - volume_fakes.snapshot_size - ), ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.data, list(data)) class TestSnapshotSet(TestSnapshot): + + snapshot = volume_fakes.FakeSnapshot.create_one_snapshot() + def setUp(self): super(TestSnapshotSet, self).setUp() - self.snapshots_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.SNAPSHOT), - loaded=True - ) + self.snapshots_mock.get.return_value = self.snapshot self.snapshots_mock.set_metadata.return_value = None self.snapshots_mock.update.return_value = None # Get the command object to mock @@ -244,16 +245,16 @@ def setUp(self): def test_snapshot_set(self): arglist = [ - volume_fakes.snapshot_id, "--name", "new_snapshot", "--property", "x=y", - "--property", "foo=foo" + "--property", "foo=foo", + self.snapshot.id, ] new_property = {"x": "y", "foo": "foo"} verifylist = [ - ("snapshot", volume_fakes.snapshot_id), ("name", "new_snapshot"), - ("property", new_property) + ("property", new_property), + ("snapshot", self.snapshot.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -263,79 +264,99 @@ def test_snapshot_set(self): "name": "new_snapshot", } self.snapshots_mock.update.assert_called_with( - volume_fakes.snapshot_id, **kwargs) + self.snapshot.id, **kwargs) self.snapshots_mock.set_metadata.assert_called_with( - volume_fakes.snapshot_id, new_property + self.snapshot.id, new_property ) self.assertIsNone(result) def test_snapshot_set_state_to_error(self): arglist = [ "--state", "error", - volume_fakes.snapshot_id + self.snapshot.id ] verifylist = [ ("state", "error"), - ("snapshot", volume_fakes.snapshot_id) + ("snapshot", self.snapshot.id) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.snapshots_mock.reset_state.assert_called_with( - volume_fakes.snapshot_id, "error") + self.snapshot.id, "error") self.assertIsNone(result) class TestSnapshotShow(TestSnapshot): + columns = ( + 'created_at', + 'description', + 'id', + 'name', + 'properties', + 'size', + 'status', + 'volume_id', + ) + def setUp(self): super(TestSnapshotShow, self).setUp() - self.snapshots_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.SNAPSHOT), - loaded=True) + self.snapshot = volume_fakes.FakeSnapshot.create_one_snapshot() + + self.data = ( + self.snapshot.created_at, + self.snapshot.description, + self.snapshot.id, + self.snapshot.name, + utils.format_dict(self.snapshot.metadata), + self.snapshot.size, + self.snapshot.status, + self.snapshot.volume_id, + ) + + self.snapshots_mock.get.return_value = self.snapshot # Get the command object to test self.cmd = snapshot.ShowSnapshot(self.app, None) def test_snapshot_show(self): arglist = [ - volume_fakes.snapshot_id + self.snapshot.id ] verifylist = [ - ("snapshot", volume_fakes.snapshot_id) + ("snapshot", self.snapshot.id) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.snapshots_mock.get.assert_called_with(volume_fakes.snapshot_id) + self.snapshots_mock.get.assert_called_with(self.snapshot.id) - self.assertEqual(volume_fakes.SNAPSHOT_columns, columns) - self.assertEqual(volume_fakes.SNAPSHOT_data, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) class TestSnapshotUnset(TestSnapshot): + + snapshot = volume_fakes.FakeSnapshot.create_one_snapshot() + def setUp(self): super(TestSnapshotUnset, self).setUp() - self.snapshots_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.SNAPSHOT), - loaded=True - ) + self.snapshots_mock.get.return_value = self.snapshot self.snapshots_mock.delete_metadata.return_value = None # Get the command object to mock self.cmd = snapshot.UnsetSnapshot(self.app, None) def test_snapshot_unset(self): arglist = [ - volume_fakes.snapshot_id, - "--property", "foo" + "--property", "foo", + self.snapshot.id, ] verifylist = [ - ("snapshot", volume_fakes.snapshot_id), - ("property", ["foo"]) + ("property", ["foo"]), + ("snapshot", self.snapshot.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -343,6 +364,6 @@ def test_snapshot_unset(self): result = self.cmd.take_action(parsed_args) self.snapshots_mock.delete_metadata.assert_called_with( - volume_fakes.snapshot_id, ["foo"] + self.snapshot.id, ["foo"] ) self.assertIsNone(result) From 6c13212725e3975eb2bd9145cb4089bb73353003 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sat, 21 May 2016 15:52:23 +0000 Subject: [PATCH 0899/3095] Updated from global requirements Change-Id: I16dd97fa5cb3d7e008c9d3c357fe31f7c081c492 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index b24c68de98..0b899e234d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,6 +15,6 @@ oslo.utils>=3.5.0 # Apache-2.0 python-glanceclient>=2.0.0 # Apache-2.0 python-keystoneclient!=1.8.0,!=2.1.0,>=1.7.0 # Apache-2.0 python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0 -python-cinderclient>=1.6.0 # Apache-2.0 +python-cinderclient!=1.7.0,>=1.6.0 # Apache-2.0 requests>=2.10.0 # Apache-2.0 stevedore>=1.10.0 # Apache-2.0 From 4d44a3c13fe6f75cbb10b148ab332a69e64791f5 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Mon, 23 May 2016 12:19:49 +0800 Subject: [PATCH 0900/3095] Add some functional tests for commands in VolumeV2 VolumeV2 lacked functional tests for qos specs and volume type commands, so I add them. These tests are quite similar to those in v1,just three difference: 1.Importing functional.common.test instead of functional.tests.volume.v1.common 2.Adding test_volume_type_set_unset_project() in test_volume_type.py. 3.Adding a test for "qos unset" command in test_qos.py Change-Id: Ic50e8c49ef01ac967c01ec41fb3f04cd51fea9e4 --- functional/tests/volume/v2/test_qos.py | 61 ++++++++++++++++ .../tests/volume/v2/test_volume_type.py | 70 +++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 functional/tests/volume/v2/test_qos.py create mode 100644 functional/tests/volume/v2/test_volume_type.py diff --git a/functional/tests/volume/v2/test_qos.py b/functional/tests/volume/v2/test_qos.py new file mode 100644 index 0000000000..24ce1b326a --- /dev/null +++ b/functional/tests/volume/v2/test_qos.py @@ -0,0 +1,61 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import uuid + +from functional.common import test + + +class VolumeTests(test.TestCase): + """Functional tests for volume qos. """ + + NAME = uuid.uuid4().hex + HEADERS = ['Name'] + FIELDS = ['id', 'name'] + ID = None + + @classmethod + def setUpClass(cls): + opts = cls.get_show_opts(cls.FIELDS) + raw_output = cls.openstack('volume qos create ' + cls.NAME + opts) + cls.ID, name, rol = raw_output.split('\n') + cls.assertOutput(cls.NAME, name) + + @classmethod + def tearDownClass(cls): + raw_output = cls.openstack('volume qos delete ' + cls.ID) + cls.assertOutput('', raw_output) + + def test_volume_qos_list(self): + opts = self.get_list_opts(self.HEADERS) + raw_output = self.openstack('volume qos list' + opts) + self.assertIn(self.NAME, raw_output) + + def test_volume_qos_show(self): + opts = self.get_show_opts(self.FIELDS) + raw_output = self.openstack('volume qos show ' + self.ID + opts) + self.assertEqual(self.ID + "\n" + self.NAME + "\n", raw_output) + + def test_volume_qos_metadata(self): + raw_output = self.openstack( + 'volume qos set --property a=b --property c=d ' + self.ID) + self.assertEqual("", raw_output) + opts = self.get_show_opts(['name', 'specs']) + raw_output = self.openstack('volume qos show ' + self.ID + opts) + self.assertEqual(self.NAME + "\na='b', c='d'\n", raw_output) + + raw_output = self.openstack( + 'volume qos unset --property a ' + self.ID) + self.assertEqual("", raw_output) + opts = self.get_show_opts(['name', 'specs']) + raw_output = self.openstack('volume qos show ' + self.ID + opts) + self.assertEqual(self.NAME + "\nc='d'\n", raw_output) diff --git a/functional/tests/volume/v2/test_volume_type.py b/functional/tests/volume/v2/test_volume_type.py new file mode 100644 index 0000000000..d8a7a7eb39 --- /dev/null +++ b/functional/tests/volume/v2/test_volume_type.py @@ -0,0 +1,70 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import uuid + +from functional.common import test + + +class VolumeTypeTests(test.TestCase): + """Functional tests for volume type. """ + + NAME = uuid.uuid4().hex + HEADERS = ['"Name"'] + FIELDS = ['name'] + + @classmethod + def setUpClass(cls): + opts = cls.get_show_opts(cls.FIELDS) + raw_output = cls.openstack( + 'volume type create --private ' + cls.NAME + opts) + expected = cls.NAME + '\n' + cls.assertOutput(expected, raw_output) + + @classmethod + def tearDownClass(cls): + raw_output = cls.openstack('volume type delete ' + cls.NAME) + cls.assertOutput('', raw_output) + + def test_volume_type_list(self): + opts = self.get_list_opts(self.HEADERS) + raw_output = self.openstack('volume type list' + opts) + self.assertIn(self.NAME, raw_output) + + def test_volume_type_show(self): + opts = self.get_show_opts(self.FIELDS) + raw_output = self.openstack('volume type show ' + self.NAME + opts) + self.assertEqual(self.NAME + "\n", raw_output) + + def test_volume_type_set_unset_properties(self): + raw_output = self.openstack( + 'volume type set --property a=b --property c=d ' + self.NAME) + self.assertEqual("", raw_output) + + opts = self.get_show_opts(["properties"]) + raw_output = self.openstack('volume type show ' + self.NAME + opts) + self.assertEqual("a='b', c='d'\n", raw_output) + + raw_output = self.openstack('volume type unset --property a ' + + self.NAME) + self.assertEqual("", raw_output) + raw_output = self.openstack('volume type show ' + self.NAME + opts) + self.assertEqual("c='d'\n", raw_output) + + def test_volume_type_set_unset_project(self): + raw_output = self.openstack( + 'volume type set --project admin ' + self.NAME) + self.assertEqual("", raw_output) + + raw_output = self.openstack( + 'volume type unset --project admin ' + self.NAME) + self.assertEqual("", raw_output) From 5f6989475309a7b4c28b433723b9261018541a79 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Fri, 20 May 2016 10:52:03 +0800 Subject: [PATCH 0901/3095] Add FakeType class and update volumetype test in VolumeV2 This patch adds FakeType class and update unit tests for volume type with FakeType class Change-Id: I86ecc68dd1a1e919f3325cd9456974a0cfadbe61 --- openstackclient/tests/volume/v2/fakes.py | 53 +++++ openstackclient/tests/volume/v2/test_type.py | 222 +++++++++---------- 2 files changed, 158 insertions(+), 117 deletions(-) diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py index 6e372de3b9..eadbe5e8ff 100644 --- a/openstackclient/tests/volume/v2/fakes.py +++ b/openstackclient/tests/volume/v2/fakes.py @@ -605,3 +605,56 @@ def create_snapshots(attrs=None, count=2): snapshots.append(snapshot) return snapshots + + +class FakeType(object): + """Fake one or more type.""" + + @staticmethod + def create_one_type(attrs=None, methods=None): + """Create a fake type. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :return: + A FakeResource object with id, name, description, etc. + """ + attrs = attrs or {} + methods = methods or {} + + # Set default attributes. + type_info = { + "id": 'type-id-' + uuid.uuid4().hex, + "name": 'type-name-' + uuid.uuid4().hex, + "description": 'type-description-' + uuid.uuid4().hex, + "extra_specs": {"foo": "bar"}, + } + + # Overwrite default attributes. + type_info.update(attrs) + + volume_type = fakes.FakeResource( + info=copy.deepcopy(type_info), + methods=methods, + loaded=True) + return volume_type + + @staticmethod + def create_types(attrs=None, count=2): + """Create multiple fake types. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of types to fake + :return: + A list of FakeResource objects faking the types + """ + volume_types = [] + for i in range(0, count): + volume_type = FakeType.create_one_type(attrs) + volume_types.append(volume_type) + + return volume_types diff --git a/openstackclient/tests/volume/v2/test_type.py b/openstackclient/tests/volume/v2/test_type.py index f0ca9b0108..872b4ae936 100644 --- a/openstackclient/tests/volume/v2/test_type.py +++ b/openstackclient/tests/volume/v2/test_type.py @@ -14,6 +14,7 @@ import copy +from openstackclient.common import utils from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes from openstackclient.tests import utils as tests_utils @@ -21,20 +22,6 @@ from openstackclient.volume.v2 import volume_type -class FakeTypeResource(fakes.FakeResource): - - _keys = {'property': 'value'} - - def set_keys(self, args): - self._keys.update(args) - - def unset_keys(self, key): - self._keys.pop(key, None) - - def get_keys(self): - return self._keys - - class TestType(volume_fakes.TestVolume): def setUp(self): @@ -58,82 +45,78 @@ class TestTypeCreate(TestType): 'id', 'name', ) - datalist = ( - volume_fakes.type_description, - volume_fakes.type_id, - volume_fakes.type_name, - ) def setUp(self): super(TestTypeCreate, self).setUp() - self.types_mock.create.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.TYPE), - loaded=True + self.new_volume_type = volume_fakes.FakeType.create_one_type() + self.data = ( + self.new_volume_type.description, + self.new_volume_type.id, + self.new_volume_type.name, ) + + self.types_mock.create.return_value = self.new_volume_type # Get the command object to test self.cmd = volume_type.CreateVolumeType(self.app, None) def test_type_create_public(self): arglist = [ - volume_fakes.type_name, - "--description", volume_fakes.type_description, - "--public" + "--description", self.new_volume_type.description, + "--public", + self.new_volume_type.name, ] verifylist = [ - ("name", volume_fakes.type_name), - ("description", volume_fakes.type_description), + ("description", self.new_volume_type.description), ("public", True), ("private", False), + ("name", self.new_volume_type.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.types_mock.create.assert_called_with( - volume_fakes.type_name, - description=volume_fakes.type_description, + self.new_volume_type.name, + description=self.new_volume_type.description, is_public=True, ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertEqual(self.data, data) def test_type_create_private(self): arglist = [ - volume_fakes.type_name, - "--description", volume_fakes.type_description, + "--description", self.new_volume_type.description, "--private", + self.new_volume_type.name, ] verifylist = [ - ("name", volume_fakes.type_name), - ("description", volume_fakes.type_description), + ("description", self.new_volume_type.description), ("public", False), ("private", True), + ("name", self.new_volume_type.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.types_mock.create.assert_called_with( - volume_fakes.type_name, - description=volume_fakes.type_description, + self.new_volume_type.name, + description=self.new_volume_type.description, is_public=False, ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertEqual(self.data, data) class TestTypeDelete(TestType): + volume_type = volume_fakes.FakeType.create_one_type() + def setUp(self): super(TestTypeDelete, self).setUp() - self.types_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.TYPE), - loaded=True - ) + self.types_mock.get.return_value = self.volume_type self.types_mock.delete.return_value = None # Get the command object to mock @@ -141,36 +124,51 @@ def setUp(self): def test_type_delete(self): arglist = [ - volume_fakes.type_id + self.volume_type.id ] verifylist = [ - ("volume_type", volume_fakes.type_id) + ("volume_type", self.volume_type.id) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.types_mock.delete.assert_called_with(volume_fakes.type_id) + self.types_mock.delete.assert_called_with(self.volume_type.id) self.assertIsNone(result) class TestTypeList(TestType): + volume_types = volume_fakes.FakeType.create_types() + columns = [ "ID", "Name" ] + columns_long = columns + [ + "Description", + "Properties" + ] + + data = [] + for t in volume_types: + data.append(( + t.id, + t.name, + )) + data_long = [] + for t in volume_types: + data_long.append(( + t.id, + t.name, + t.description, + utils.format_dict(t.extra_specs), + )) def setUp(self): super(TestTypeList, self).setUp() - self.types_mock.list.return_value = [ - fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.TYPE), - loaded=True - ) - ] + self.types_mock.list.return_value = self.volume_types # get the command to test self.cmd = volume_type.ListVolumeType(self.app, None) @@ -183,11 +181,7 @@ def test_type_list_without_options(self): columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) - datalist = (( - volume_fakes.type_id, - volume_fakes.type_name, - ),) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.data, list(data)) def test_type_list_with_options(self): arglist = ["--long"] @@ -195,30 +189,19 @@ def test_type_list_with_options(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - columns = self.columns + [ - "Description", - "Properties" - ] - self.assertEqual(columns, columns) - datalist = (( - volume_fakes.type_id, - volume_fakes.type_name, - volume_fakes.type_description, - "foo='bar'" - ),) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns_long, columns) + self.assertEqual(self.data_long, list(data)) class TestTypeSet(TestType): + volume_type = volume_fakes.FakeType.create_one_type( + methods={'set_keys': None}) + def setUp(self): super(TestTypeSet, self).setUp() - self.types_mock.get.return_value = FakeTypeResource( - None, - copy.deepcopy(volume_fakes.TYPE), - loaded=True, - ) + self.types_mock.get.return_value = self.volume_type # Return a project self.projects_mock.get.return_value = fakes.FakeResource( @@ -226,7 +209,6 @@ def setUp(self): copy.deepcopy(identity_fakes.PROJECT), loaded=True, ) - # Get the command object to test self.cmd = volume_type.SetVolumeType(self.app, None) @@ -234,13 +216,13 @@ def test_type_set_name(self): new_name = 'new_name' arglist = [ '--name', new_name, - volume_fakes.type_id, + self.volume_type.id, ] verifylist = [ ('name', new_name), ('description', None), ('property', None), - ('volume_type', volume_fakes.type_id), + ('volume_type', self.volume_type.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -251,7 +233,7 @@ def test_type_set_name(self): 'name': new_name, } self.types_mock.update.assert_called_with( - volume_fakes.type_id, + self.volume_type.id, **kwargs ) self.assertIsNone(result) @@ -260,13 +242,13 @@ def test_type_set_description(self): new_desc = 'new_desc' arglist = [ '--description', new_desc, - volume_fakes.type_id, + self.volume_type.id, ] verifylist = [ ('name', None), ('description', new_desc), ('property', None), - ('volume_type', volume_fakes.type_id), + ('volume_type', self.volume_type.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -277,7 +259,7 @@ def test_type_set_description(self): 'description': new_desc, } self.types_mock.update.assert_called_with( - volume_fakes.type_id, + self.volume_type.id, **kwargs ) self.assertIsNone(result) @@ -285,31 +267,29 @@ def test_type_set_description(self): def test_type_set_property(self): arglist = [ '--property', 'myprop=myvalue', - volume_fakes.type_id, + self.volume_type.id, ] verifylist = [ ('name', None), ('description', None), ('property', {'myprop': 'myvalue'}), - ('volume_type', volume_fakes.type_id), + ('volume_type', self.volume_type.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) + self.volume_type.set_keys.assert_called_once_with( + {'myprop': 'myvalue'}) self.assertIsNone(result) - result = self.types_mock.get.return_value._keys - self.assertIn('myprop', result) - self.assertEqual('myvalue', result['myprop']) - def test_type_set_not_called_without_project_argument(self): arglist = [ '--project', '', - volume_fakes.type_id, + self.volume_type.id, ] verifylist = [ ('project', ''), - ('volume_type', volume_fakes.type_id), + ('volume_type', self.volume_type.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -336,11 +316,11 @@ def test_type_set_failed_with_missing_volume_type_argument(self): def test_type_set_project_access(self): arglist = [ '--project', identity_fakes.project_id, - volume_fakes.type_id, + self.volume_type.id, ] verifylist = [ ('project', identity_fakes.project_id), - ('volume_type', volume_fakes.type_id), + ('volume_type', self.volume_type.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -348,51 +328,61 @@ def test_type_set_project_access(self): self.assertIsNone(result) self.types_access_mock.add_project_access.assert_called_with( - volume_fakes.type_id, + self.volume_type.id, identity_fakes.project_id, ) class TestTypeShow(TestType): + columns = ( + 'description', + 'id', + 'name', + 'properties', + ) + def setUp(self): super(TestTypeShow, self).setUp() - self.types_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.TYPE), - loaded=True + self.volume_type = volume_fakes.FakeType.create_one_type() + self.data = ( + self.volume_type.description, + self.volume_type.id, + self.volume_type.name, + utils.format_dict(self.volume_type.extra_specs) ) + self.types_mock.get.return_value = self.volume_type + # Get the command object to test self.cmd = volume_type.ShowVolumeType(self.app, None) def test_type_show(self): arglist = [ - volume_fakes.type_id + self.volume_type.id ] verifylist = [ - ("volume_type", volume_fakes.type_id) + ("volume_type", self.volume_type.id) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.types_mock.get.assert_called_with(volume_fakes.type_id) + self.types_mock.get.assert_called_with(self.volume_type.id) - self.assertEqual(volume_fakes.TYPE_FORMATTED_columns, columns) - self.assertEqual(volume_fakes.TYPE_FORMATTED_data, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) class TestTypeUnset(TestType): + volume_type = volume_fakes.FakeType.create_one_type( + methods={'unset_keys': None}) + def setUp(self): super(TestTypeUnset, self).setUp() - self.types_mock.get.return_value = FakeTypeResource( - None, - copy.deepcopy(volume_fakes.TYPE), - loaded=True - ) + self.types_mock.get.return_value = self.volume_type # Return a project self.projects_mock.get.return_value = fakes.FakeResource( @@ -407,29 +397,27 @@ def setUp(self): def test_type_unset(self): arglist = [ '--property', 'property', - volume_fakes.type_id, + self.volume_type.id, ] verifylist = [ ('property', 'property'), - ('volume_type', volume_fakes.type_id), + ('volume_type', self.volume_type.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) + self.volume_type.unset_keys.assert_called_once_with('property') self.assertIsNone(result) - result = self.types_mock.get.return_value._keys - self.assertNotIn('property', result) - def test_type_unset_project_access(self): arglist = [ '--project', identity_fakes.project_id, - volume_fakes.type_id, + self.volume_type.id, ] verifylist = [ ('project', identity_fakes.project_id), - ('volume_type', volume_fakes.type_id), + ('volume_type', self.volume_type.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -437,18 +425,18 @@ def test_type_unset_project_access(self): self.assertIsNone(result) self.types_access_mock.remove_project_access.assert_called_with( - volume_fakes.type_id, + self.volume_type.id, identity_fakes.project_id, ) def test_type_unset_not_called_without_project_argument(self): arglist = [ '--project', '', - volume_fakes.type_id, + self.volume_type.id, ] verifylist = [ ('project', ''), - ('volume_type', volume_fakes.type_id), + ('volume_type', self.volume_type.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) From 53e05e7c2d36ab2ff1ba05a8334286d41c5f40e4 Mon Sep 17 00:00:00 2001 From: Sheel Rana Date: Mon, 23 May 2016 22:26:24 +0530 Subject: [PATCH 0902/3095] i18n support for help and error messages in cinder Change-Id: I98fbc959034fe0530966291643b381855801de20 --- openstackclient/volume/client.py | 7 +- openstackclient/volume/v1/backup.py | 22 ++++--- openstackclient/volume/v1/qos_specs.py | 42 ++++++------ openstackclient/volume/v1/service.py | 9 ++- openstackclient/volume/v1/snapshot.py | 42 ++++++------ openstackclient/volume/v1/volume.py | 69 ++++++++++---------- openstackclient/volume/v1/volume_type.py | 28 ++++---- openstackclient/volume/v2/backup.py | 20 +++--- openstackclient/volume/v2/qos_specs.py | 42 ++++++------ openstackclient/volume/v2/service.py | 9 ++- openstackclient/volume/v2/snapshot.py | 45 +++++++------ openstackclient/volume/v2/volume.py | 81 ++++++++++++------------ openstackclient/volume/v2/volume_type.py | 64 ++++++++++--------- 13 files changed, 257 insertions(+), 223 deletions(-) diff --git a/openstackclient/volume/client.py b/openstackclient/volume/client.py index 0973868b86..a60f4b0ed2 100644 --- a/openstackclient/volume/client.py +++ b/openstackclient/volume/client.py @@ -16,6 +16,7 @@ import logging from openstackclient.common import utils +from openstackclient.i18n import _ LOG = logging.getLogger(__name__) @@ -73,7 +74,7 @@ def build_option_parser(parser): '--os-volume-api-version', metavar='', default=utils.env('OS_VOLUME_API_VERSION'), - help='Volume API version, default=' + - DEFAULT_API_VERSION + - ' (Env: OS_VOLUME_API_VERSION)') + help=_('Volume API version, default=%s ' + '(Env: OS_VOLUME_API_VERSION)') % DEFAULT_API_VERSION + ) return parser diff --git a/openstackclient/volume/v1/backup.py b/openstackclient/volume/v1/backup.py index 40b603151f..607b521146 100644 --- a/openstackclient/volume/v1/backup.py +++ b/openstackclient/volume/v1/backup.py @@ -20,6 +20,7 @@ from openstackclient.common import command from openstackclient.common import utils +from openstackclient.i18n import _ class CreateBackup(command.ShowOne): @@ -30,23 +31,23 @@ def get_parser(self, prog_name): parser.add_argument( 'volume', metavar='', - help='Volume to backup (name or ID)', + help=_('Volume to backup (name or ID)'), ) parser.add_argument( '--container', metavar='', required=False, - help='Optional backup container name', + help=_('Optional backup container name'), ) parser.add_argument( '--name', metavar='', - help='Name of the backup', + help=_('Name of the backup'), ) parser.add_argument( '--description', metavar='', - help='Description of the backup', + help=_('Description of the backup'), ) return parser @@ -74,7 +75,7 @@ def get_parser(self, prog_name): 'backups', metavar='', nargs="+", - help='Backup(s) to delete (ID only)', + help=_('Backup(s) to delete (ID only)'), ) return parser @@ -95,7 +96,7 @@ def get_parser(self, prog_name): '--long', action='store_true', default=False, - help='List additional fields in output', + help=_('List additional fields in output'), ) return parser @@ -148,11 +149,13 @@ def get_parser(self, prog_name): parser.add_argument( 'backup', metavar='', - help='Backup to restore (ID only)') + help=_('Backup to restore (ID only)') + ) parser.add_argument( 'volume', metavar='', - help='Volume to restore to (name or ID)') + help=_('Volume to restore to (name or ID)') + ) return parser def take_action(self, parsed_args): @@ -173,7 +176,8 @@ def get_parser(self, prog_name): parser.add_argument( 'backup', metavar='', - help='Backup to display (ID only)') + help=_('Backup to display (ID only)') + ) return parser def take_action(self, parsed_args): diff --git a/openstackclient/volume/v1/qos_specs.py b/openstackclient/volume/v1/qos_specs.py index 826e5c4938..b9eb87523b 100644 --- a/openstackclient/volume/v1/qos_specs.py +++ b/openstackclient/volume/v1/qos_specs.py @@ -20,6 +20,7 @@ from openstackclient.common import command from openstackclient.common import parseractions from openstackclient.common import utils +from openstackclient.i18n import _ class AssociateQos(command.Command): @@ -30,12 +31,12 @@ def get_parser(self, prog_name): parser.add_argument( 'qos_spec', metavar='', - help='QoS specification to modify (name or ID)', + help=_('QoS specification to modify (name or ID)'), ) parser.add_argument( 'volume_type', metavar='', - help='Volume type to associate the QoS (name or ID)', + help=_('Volume type to associate the QoS (name or ID)'), ) return parser @@ -57,7 +58,7 @@ def get_parser(self, prog_name): parser.add_argument( 'name', metavar='', - help='New QoS specification name', + help=_('New QoS specification name'), ) consumer_choices = ['front-end', 'back-end', 'both'] parser.add_argument( @@ -65,15 +66,16 @@ def get_parser(self, prog_name): metavar='', choices=consumer_choices, default='both', - help='Consumer of the QoS. Valid consumers: %s ' - "(defaults to 'both')" % utils.format_list(consumer_choices) + help=(_('Consumer of the QoS. Valid consumers: %s ' + "(defaults to 'both')") % + utils.format_list(consumer_choices)) ) parser.add_argument( '--property', metavar='', action=parseractions.KeyValueAction, - help='Set a QoS specification property ' - '(repeat option to set multiple properties)', + help=_('Set a QoS specification property ' + '(repeat option to set multiple properties)'), ) return parser @@ -99,7 +101,7 @@ def get_parser(self, prog_name): 'qos_specs', metavar='', nargs="+", - help='QoS specification(s) to delete (name or ID)', + help=_('QoS specification(s) to delete (name or ID)'), ) return parser @@ -118,19 +120,19 @@ def get_parser(self, prog_name): parser.add_argument( 'qos_spec', metavar='', - help='QoS specification to modify (name or ID)', + help=_('QoS specification to modify (name or ID)'), ) volume_type_group = parser.add_mutually_exclusive_group() volume_type_group.add_argument( '--volume-type', metavar='', - help='Volume type to disassociate the QoS from (name or ID)', + help=_('Volume type to disassociate the QoS from (name or ID)'), ) volume_type_group.add_argument( '--all', action='store_true', default=False, - help='Disassociate the QoS from every volume type', + help=_('Disassociate the QoS from every volume type'), ) return parser @@ -181,14 +183,14 @@ def get_parser(self, prog_name): parser.add_argument( 'qos_spec', metavar='', - help='QoS specification to modify (name or ID)', + help=_('QoS specification to modify (name or ID)'), ) parser.add_argument( '--property', metavar='', action=parseractions.KeyValueAction, - help='Property to add or modify for this QoS specification ' - '(repeat option to set multiple properties)', + help=_('Property to add or modify for this QoS specification ' + '(repeat option to set multiple properties)'), ) return parser @@ -201,7 +203,7 @@ def take_action(self, parsed_args): volume_client.qos_specs.set_keys(qos_spec.id, parsed_args.property) else: - self.app.log.error("No changes requested\n") + self.app.log.error(_("No changes requested\n")) class ShowQos(command.ShowOne): @@ -212,7 +214,7 @@ def get_parser(self, prog_name): parser.add_argument( 'qos_spec', metavar='', - help='QoS specification to display (name or ID)', + help=_('QoS specification to display (name or ID)'), ) return parser @@ -241,15 +243,15 @@ def get_parser(self, prog_name): parser.add_argument( 'qos_spec', metavar='', - help='QoS specification to modify (name or ID)', + help=_('QoS specification to modify (name or ID)'), ) parser.add_argument( '--property', metavar='', action='append', default=[], - help='Property to remove from the QoS specification. ' - '(repeat option to unset multiple properties)', + help=_('Property to remove from the QoS specification. ' + '(repeat option to unset multiple properties)'), ) return parser @@ -262,4 +264,4 @@ def take_action(self, parsed_args): volume_client.qos_specs.unset_keys(qos_spec.id, parsed_args.property) else: - self.app.log.error("No changes requested\n") + self.app.log.error(_("No changes requested\n")) diff --git a/openstackclient/volume/v1/service.py b/openstackclient/volume/v1/service.py index f26be13e0c..023dda9850 100644 --- a/openstackclient/volume/v1/service.py +++ b/openstackclient/volume/v1/service.py @@ -16,6 +16,7 @@ from openstackclient.common import command from openstackclient.common import utils +from openstackclient.i18n import _ class ListService(command.Lister): @@ -26,16 +27,18 @@ def get_parser(self, prog_name): parser.add_argument( "--host", metavar="", - help="List services on specified host (name only)") + help=_("List services on specified host (name only)") + ) parser.add_argument( "--service", metavar="", - help="List only specified service (name only)") + help=_("List only specified service (name only)") + ) parser.add_argument( "--long", action="store_true", default=False, - help="List additional fields in output" + help=_("List additional fields in output") ) return parser diff --git a/openstackclient/volume/v1/snapshot.py b/openstackclient/volume/v1/snapshot.py index 6c6131ead4..bf5bf26466 100644 --- a/openstackclient/volume/v1/snapshot.py +++ b/openstackclient/volume/v1/snapshot.py @@ -21,6 +21,7 @@ from openstackclient.common import command from openstackclient.common import parseractions from openstackclient.common import utils +from openstackclient.i18n import _ class CreateSnapshot(command.ShowOne): @@ -31,24 +32,25 @@ def get_parser(self, prog_name): parser.add_argument( 'volume', metavar='', - help='Volume to snapshot (name or ID)', + help=_('Volume to snapshot (name or ID)'), ) parser.add_argument( '--name', metavar='', - help='Name of the snapshot', + help=_('Name of the snapshot'), ) parser.add_argument( '--description', metavar='', - help='Description of the snapshot', + help=_('Description of the snapshot'), ) parser.add_argument( '--force', dest='force', action='store_true', default=False, - help='Create a snapshot attached to an instance. Default is False', + help=_('Create a snapshot attached to an instance. ' + 'Default is False'), ) return parser @@ -79,7 +81,7 @@ def get_parser(self, prog_name): 'snapshots', metavar='', nargs="+", - help='Snapshot(s) to delete (name or ID)', + help=_('Snapshot(s) to delete (name or ID)'), ) return parser @@ -100,13 +102,13 @@ def get_parser(self, prog_name): '--all-projects', action='store_true', default=False, - help='Include all projects (admin only)', + help=_('Include all projects (admin only)'), ) parser.add_argument( '--long', action='store_true', default=False, - help='List additional fields in output', + help=_('List additional fields in output'), ) return parser @@ -170,21 +172,24 @@ def get_parser(self, prog_name): parser.add_argument( 'snapshot', metavar='', - help='Snapshot to modify (name or ID)') + help=_('Snapshot to modify (name or ID)') + ) parser.add_argument( '--name', metavar='', - help='New snapshot name') + help=_('New snapshot name') + ) parser.add_argument( '--description', metavar='', - help='New snapshot description') + help=_('New snapshot description') + ) parser.add_argument( '--property', metavar='', action=parseractions.KeyValueAction, - help='Property to add/change for this snapshot ' - '(repeat option to set multiple properties)', + help=_('Property to add/change for this snapshot ' + '(repeat option to set multiple properties)'), ) return parser @@ -204,7 +209,7 @@ def take_action(self, parsed_args): kwargs['display_description'] = parsed_args.description if not kwargs and not parsed_args.property: - self.app.log.error("No changes requested\n") + self.app.log.error(_("No changes requested\n")) return snapshot.update(**kwargs) @@ -218,7 +223,8 @@ def get_parser(self, prog_name): parser.add_argument( 'snapshot', metavar='', - help='Snapshot to display (name or ID)') + help=_('Snapshot to display (name or ID)') + ) return parser def take_action(self, parsed_args): @@ -241,16 +247,16 @@ def get_parser(self, prog_name): parser.add_argument( 'snapshot', metavar='', - help='Snapshot to modify (name or ID)', + help=_('Snapshot to modify (name or ID)'), ) parser.add_argument( '--property', metavar='', action='append', default=[], - help='Property to remove from snapshot ' - '(repeat option to remove multiple properties)', required=True, + help=_('Property to remove from snapshot ' + '(repeat option to remove multiple properties)'), ) return parser @@ -265,4 +271,4 @@ def take_action(self, parsed_args): parsed_args.property, ) else: - self.app.log.error("No changes requested\n") + self.app.log.error(_("No changes requested\n")) diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index 29c197ef81..11e42f8372 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -21,6 +21,7 @@ from openstackclient.common import command from openstackclient.common import parseractions from openstackclient.common import utils +from openstackclient.i18n import _ class CreateVolume(command.ShowOne): @@ -31,30 +32,30 @@ def get_parser(self, prog_name): parser.add_argument( 'name', metavar='', - help='Volume name', + help=_('Volume name'), ) parser.add_argument( '--size', metavar='', required=True, type=int, - help='Volume size in GB', + help=_('Volume size in GB'), ) parser.add_argument( '--type', metavar='', - help="Set the type of volume", + help=_("Set the type of volume"), ) parser.add_argument( '--image', metavar='', - help='Use as source of volume (name or ID)', + help=_('Use as source of volume (name or ID)'), ) snapshot_group = parser.add_mutually_exclusive_group() snapshot_group.add_argument( '--snapshot', metavar='', - help='Use as source of volume (name or ID)', + help=_('Use as source of volume (name or ID)'), ) snapshot_group.add_argument( '--snapshot-id', @@ -64,34 +65,34 @@ def get_parser(self, prog_name): parser.add_argument( '--source', metavar='', - help='Volume to clone (name or ID)', + help=_('Volume to clone (name or ID)'), ) parser.add_argument( '--description', metavar='', - help='Volume description', + help=_('Volume description'), ) parser.add_argument( '--user', metavar='', - help='Specify an alternate user (name or ID)', + help=_('Specify an alternate user (name or ID)'), ) parser.add_argument( '--project', metavar='', - help='Specify an alternate project (name or ID)', + help=_('Specify an alternate project (name or ID)'), ) parser.add_argument( '--availability-zone', metavar='', - help='Create volume in ', + help=_('Create volume in '), ) parser.add_argument( '--property', metavar='', action=parseractions.KeyValueAction, - help='Set a property on this volume ' - '(repeat option to set multiple properties)', + help=_('Set a property on this volume ' + '(repeat option to set multiple properties)'), ) return parser @@ -165,15 +166,15 @@ def get_parser(self, prog_name): 'volumes', metavar='', nargs="+", - help='Volume(s) to delete (name or ID)', + help=_('Volume(s) to delete (name or ID)'), ) parser.add_argument( '--force', dest='force', action='store_true', default=False, - help='Attempt forced removal of volume(s), regardless of state ' - '(defaults to False)', + help=_('Attempt forced removal of volume(s), regardless of state ' + '(defaults to False)'), ) return parser @@ -196,24 +197,24 @@ def get_parser(self, prog_name): parser.add_argument( '--name', metavar='', - help='Filter results by volume name', + help=_('Filter results by volume name'), ) parser.add_argument( '--status', metavar='', - help='Filter results by status', + help=_('Filter results by status'), ) parser.add_argument( '--all-projects', action='store_true', default=False, - help='Include all projects (admin only)', + help=_('Include all projects (admin only)'), ) parser.add_argument( '--long', action='store_true', default=False, - help='List additional fields in output', + help=_('List additional fields in output'), ) return parser @@ -308,30 +309,30 @@ def get_parser(self, prog_name): parser.add_argument( 'volume', metavar='', - help='Volume to modify (name or ID)', + help=_('Volume to modify (name or ID)'), ) parser.add_argument( '--name', metavar='', - help='New volume name', + help=_('New volume name'), ) parser.add_argument( '--description', metavar='', - help='New volume description', + help=_('New volume description'), ) parser.add_argument( '--size', metavar='', type=int, - help='Extend volume size in GB', + help=_('Extend volume size in GB'), ) parser.add_argument( '--property', metavar='', action=parseractions.KeyValueAction, - help='Set a property on this volume ' - '(repeat option to set multiple properties)', + help=_('Set a property on this volume ' + '(repeat option to set multiple properties)'), ) return parser @@ -341,12 +342,12 @@ def take_action(self, parsed_args): if parsed_args.size: if volume.status != 'available': - self.app.log.error("Volume is in %s state, it must be " - "available before size can be extended" % + self.app.log.error(_("Volume is in %s state, it must be " + "available before size can be extended") % volume.status) return if parsed_args.size <= volume.size: - self.app.log.error("New size must be greater than %s GB" % + self.app.log.error(_("New size must be greater than %s GB") % volume.size) return volume_client.volumes.extend(volume.id, parsed_args.size) @@ -363,7 +364,7 @@ def take_action(self, parsed_args): volume_client.volumes.update(volume.id, **kwargs) if not kwargs and not parsed_args.property and not parsed_args.size: - self.app.log.error("No changes requested\n") + self.app.log.error(_("No changes requested\n")) class ShowVolume(command.ShowOne): @@ -374,7 +375,7 @@ def get_parser(self, prog_name): parser.add_argument( 'volume', metavar='', - help='Volume to display (name or ID)', + help=_('Volume to display (name or ID)'), ) return parser @@ -404,15 +405,15 @@ def get_parser(self, prog_name): parser.add_argument( 'volume', metavar='', - help='Volume to modify (name or ID)', + help=_('Volume to modify (name or ID)'), ) parser.add_argument( '--property', metavar='', action='append', default=[], - help='Remove a property from volume ' - '(repeat option to remove multiple properties)', + help=_('Remove a property from volume ' + '(repeat option to remove multiple properties)'), required=True, ) return parser @@ -428,4 +429,4 @@ def take_action(self, parsed_args): parsed_args.property, ) else: - self.app.log.error("No changes requested\n") + self.app.log.error(_("No changes requested\n")) diff --git a/openstackclient/volume/v1/volume_type.py b/openstackclient/volume/v1/volume_type.py index 05671c1fe3..739270220a 100644 --- a/openstackclient/volume/v1/volume_type.py +++ b/openstackclient/volume/v1/volume_type.py @@ -20,6 +20,7 @@ from openstackclient.common import command from openstackclient.common import parseractions from openstackclient.common import utils +from openstackclient.i18n import _ class CreateVolumeType(command.ShowOne): @@ -30,14 +31,14 @@ def get_parser(self, prog_name): parser.add_argument( 'name', metavar='', - help='Volume type name', + help=_('Volume type name'), ) parser.add_argument( '--property', metavar='', action=parseractions.KeyValueAction, - help='Set a property on this volume type ' - '(repeat option to set multiple properties)', + help=_('Set a property on this volume type ' + '(repeat option to set multiple properties)'), ) return parser @@ -62,7 +63,7 @@ def get_parser(self, prog_name): parser.add_argument( 'volume_type', metavar='', - help='Volume type to delete (name or ID)', + help=_('Volume type to delete (name or ID)'), ) return parser @@ -82,7 +83,8 @@ def get_parser(self, prog_name): '--long', action='store_true', default=False, - help='List additional fields in output') + help=_('List additional fields in output') + ) return parser def take_action(self, parsed_args): @@ -108,14 +110,14 @@ def get_parser(self, prog_name): parser.add_argument( 'volume_type', metavar='', - help='Volume type to modify (name or ID)', + help=_('Volume type to modify (name or ID)'), ) parser.add_argument( '--property', metavar='', action=parseractions.KeyValueAction, - help='Set a property on this volume type ' - '(repeat option to set multiple properties)', + help=_('Set a property on this volume type ' + '(repeat option to set multiple properties)'), ) return parser @@ -136,7 +138,7 @@ def get_parser(self, prog_name): parser.add_argument( "volume_type", metavar="", - help="Volume type to display (name or ID)" + help=_("Volume type to display (name or ID)") ) return parser @@ -157,15 +159,15 @@ def get_parser(self, prog_name): parser.add_argument( 'volume_type', metavar='', - help='Volume type to modify (name or ID)', + help=_('Volume type to modify (name or ID)'), ) parser.add_argument( '--property', metavar='', action='append', default=[], - help='Remove a property from this volume type ' - '(repeat option to remove multiple properties)', + help=_('Remove a property from this volume type ' + '(repeat option to remove multiple properties)'), required=True, ) return parser @@ -180,4 +182,4 @@ def take_action(self, parsed_args): if parsed_args.property: volume_type.unset_keys(parsed_args.property) else: - self.app.log.error("No changes requested\n") + self.app.log.error(_("No changes requested\n")) diff --git a/openstackclient/volume/v2/backup.py b/openstackclient/volume/v2/backup.py index 016a414c67..e6fbe78dd1 100644 --- a/openstackclient/volume/v2/backup.py +++ b/openstackclient/volume/v2/backup.py @@ -20,6 +20,7 @@ from openstackclient.common import command from openstackclient.common import utils +from openstackclient.i18n import _ class CreateBackup(command.ShowOne): @@ -30,22 +31,22 @@ def get_parser(self, prog_name): parser.add_argument( "volume", metavar="", - help="Volume to backup (name or ID)" + help=_("Volume to backup (name or ID)") ) parser.add_argument( "--name", metavar="", - help="Name of the backup" + help=_("Name of the backup") ) parser.add_argument( "--description", metavar="", - help="Description of the backup" + help=_("Description of the backup") ) parser.add_argument( "--container", metavar="", - help="Optional backup container name" + help=_("Optional backup container name") ) return parser @@ -72,7 +73,7 @@ def get_parser(self, prog_name): "backups", metavar="", nargs="+", - help="Backup(s) to delete (name or ID)" + help=_("Backup(s) to delete (name or ID)") ) return parser @@ -93,7 +94,7 @@ def get_parser(self, prog_name): "--long", action="store_true", default=False, - help="List additional fields in output" + help=_("List additional fields in output") ) return parser @@ -146,12 +147,12 @@ def get_parser(self, prog_name): parser.add_argument( "backup", metavar="", - help="Backup to restore (ID only)" + help=_("Backup to restore (ID only)") ) parser.add_argument( "volume", metavar="", - help="Volume to restore to (name or ID)" + help=_("Volume to restore to (name or ID)") ) return parser @@ -171,7 +172,8 @@ def get_parser(self, prog_name): parser.add_argument( "backup", metavar="", - help="Backup to display (name or ID)") + help=_("Backup to display (name or ID)") + ) return parser def take_action(self, parsed_args): diff --git a/openstackclient/volume/v2/qos_specs.py b/openstackclient/volume/v2/qos_specs.py index 961cc27e5c..aa2059d7bc 100644 --- a/openstackclient/volume/v2/qos_specs.py +++ b/openstackclient/volume/v2/qos_specs.py @@ -20,6 +20,7 @@ from openstackclient.common import command from openstackclient.common import parseractions from openstackclient.common import utils +from openstackclient.i18n import _ class AssociateQos(command.Command): @@ -30,12 +31,12 @@ def get_parser(self, prog_name): parser.add_argument( 'qos_spec', metavar='', - help='QoS specification to modify (name or ID)', + help=_('QoS specification to modify (name or ID)'), ) parser.add_argument( 'volume_type', metavar='', - help='Volume type to associate the QoS (name or ID)', + help=_('Volume type to associate the QoS (name or ID)'), ) return parser @@ -57,7 +58,7 @@ def get_parser(self, prog_name): parser.add_argument( 'name', metavar='', - help='New QoS specification name', + help=_('New QoS specification name'), ) consumer_choices = ['front-end', 'back-end', 'both'] parser.add_argument( @@ -65,15 +66,16 @@ def get_parser(self, prog_name): metavar='', choices=consumer_choices, default='both', - help='Consumer of the QoS. Valid consumers: %s ' - "(defaults to 'both')" % utils.format_list(consumer_choices) + help=(_('Consumer of the QoS. Valid consumers: %s ' + "(defaults to 'both')") % + utils.format_list(consumer_choices)) ) parser.add_argument( '--property', metavar='', action=parseractions.KeyValueAction, - help='Set a QoS specification property ' - '(repeat option to set multiple properties)', + help=_('Set a QoS specification property ' + '(repeat option to set multiple properties)'), ) return parser @@ -99,7 +101,7 @@ def get_parser(self, prog_name): 'qos_specs', metavar='', nargs="+", - help='QoS specification(s) to delete (name or ID)', + help=_('QoS specification(s) to delete (name or ID)'), ) return parser @@ -118,19 +120,19 @@ def get_parser(self, prog_name): parser.add_argument( 'qos_spec', metavar='', - help='QoS specification to modify (name or ID)', + help=_('QoS specification to modify (name or ID)'), ) volume_type_group = parser.add_mutually_exclusive_group() volume_type_group.add_argument( '--volume-type', metavar='', - help='Volume type to disassociate the QoS from (name or ID)', + help=_('Volume type to disassociate the QoS from (name or ID)'), ) volume_type_group.add_argument( '--all', action='store_true', default=False, - help='Disassociate the QoS from every volume type', + help=_('Disassociate the QoS from every volume type'), ) return parser @@ -181,14 +183,14 @@ def get_parser(self, prog_name): parser.add_argument( 'qos_spec', metavar='', - help='QoS specification to modify (name or ID)', + help=_('QoS specification to modify (name or ID)'), ) parser.add_argument( '--property', metavar='', action=parseractions.KeyValueAction, - help='Property to add or modify for this QoS specification ' - '(repeat option to set multiple properties)', + help=_('Property to add or modify for this QoS specification ' + '(repeat option to set multiple properties)'), ) return parser @@ -201,7 +203,7 @@ def take_action(self, parsed_args): volume_client.qos_specs.set_keys(qos_spec.id, parsed_args.property) else: - self.app.log.error("No changes requested\n") + self.app.log.error(_("No changes requested\n")) class ShowQos(command.ShowOne): @@ -212,7 +214,7 @@ def get_parser(self, prog_name): parser.add_argument( 'qos_spec', metavar='', - help='QoS specification to display (name or ID)', + help=_('QoS specification to display (name or ID)'), ) return parser @@ -241,15 +243,15 @@ def get_parser(self, prog_name): parser.add_argument( 'qos_spec', metavar='', - help='QoS specification to modify (name or ID)', + help=_('QoS specification to modify (name or ID)'), ) parser.add_argument( '--property', metavar='', action='append', default=[], - help='Property to remove from the QoS specification. ' - '(repeat option to unset multiple properties)', + help=('Property to remove from the QoS specification. ' + '(repeat option to unset multiple properties)'), ) return parser @@ -262,4 +264,4 @@ def take_action(self, parsed_args): volume_client.qos_specs.unset_keys(qos_spec.id, parsed_args.property) else: - self.app.log.error("No changes requested\n") + self.app.log.error(_("No changes requested\n")) diff --git a/openstackclient/volume/v2/service.py b/openstackclient/volume/v2/service.py index f26be13e0c..023dda9850 100644 --- a/openstackclient/volume/v2/service.py +++ b/openstackclient/volume/v2/service.py @@ -16,6 +16,7 @@ from openstackclient.common import command from openstackclient.common import utils +from openstackclient.i18n import _ class ListService(command.Lister): @@ -26,16 +27,18 @@ def get_parser(self, prog_name): parser.add_argument( "--host", metavar="", - help="List services on specified host (name only)") + help=_("List services on specified host (name only)") + ) parser.add_argument( "--service", metavar="", - help="List only specified service (name only)") + help=_("List only specified service (name only)") + ) parser.add_argument( "--long", action="store_true", default=False, - help="List additional fields in output" + help=_("List additional fields in output") ) return parser diff --git a/openstackclient/volume/v2/snapshot.py b/openstackclient/volume/v2/snapshot.py index 65cb9a755e..db4ce6c335 100644 --- a/openstackclient/volume/v2/snapshot.py +++ b/openstackclient/volume/v2/snapshot.py @@ -21,6 +21,7 @@ from openstackclient.common import command from openstackclient.common import parseractions from openstackclient.common import utils +from openstackclient.i18n import _ class CreateSnapshot(command.ShowOne): @@ -31,24 +32,25 @@ def get_parser(self, prog_name): parser.add_argument( "volume", metavar="", - help="Volume to snapshot (name or ID)" + help=_("Volume to snapshot (name or ID)") ) parser.add_argument( "--name", metavar="", - help="Name of the snapshot" + help=("Name of the snapshot") ) parser.add_argument( "--description", metavar="", - help="Description of the snapshot" + help=_("Description of the snapshot") ) parser.add_argument( "--force", dest="force", action="store_true", default=False, - help="Create a snapshot attached to an instance. Default is False" + help=_("Create a snapshot attached to an instance. " + "Default is False") ) return parser @@ -77,7 +79,7 @@ def get_parser(self, prog_name): "snapshots", metavar="", nargs="+", - help="Snapshot(s) to delete (name or ID)" + help=_("Snapshot(s) to delete (name or ID)") ) return parser @@ -98,13 +100,13 @@ def get_parser(self, prog_name): '--all-projects', action='store_true', default=False, - help='Include all projects (admin only)', + help=_('Include all projects (admin only)'), ) parser.add_argument( '--long', action='store_true', default=False, - help='List additional fields in output', + help=_('List additional fields in output'), ) return parser @@ -163,29 +165,32 @@ def get_parser(self, prog_name): parser.add_argument( 'snapshot', metavar='', - help='Snapshot to modify (name or ID)') + help=_('Snapshot to modify (name or ID)') + ) parser.add_argument( '--name', metavar='', - help='New snapshot name') + help=_('New snapshot name') + ) parser.add_argument( '--description', metavar='', - help='New snapshot description') + help=_('New snapshot description') + ) parser.add_argument( '--property', metavar='', action=parseractions.KeyValueAction, - help='Property to add/change for this snapshot ' - '(repeat option to set multiple properties)', + help=_('Property to add/change for this snapshot ' + '(repeat option to set multiple properties)'), ) parser.add_argument( '--state', metavar='', choices=['available', 'error', 'creating', 'deleting', 'error-deleting'], - help='New snapshot state. Valid values are available, ' - 'error, creating, deleting, and error-deleting.', + help=_('New snapshot state. Valid values are available, ' + 'error, creating, deleting, and error-deleting.'), ) return parser @@ -202,7 +207,7 @@ def take_action(self, parsed_args): if (not kwargs and not parsed_args.property and not parsed_args.state): - self.app.log.error("No changes requested\n") + self.app.log.error(_("No changes requested\n")) return if parsed_args.property: @@ -222,7 +227,7 @@ def get_parser(self, prog_name): parser.add_argument( "snapshot", metavar="", - help="Snapshot to display (name or ID)" + help=_("Snapshot to display (name or ID)") ) return parser @@ -244,15 +249,15 @@ def get_parser(self, prog_name): parser.add_argument( 'snapshot', metavar='', - help='Snapshot to modify (name or ID)', + help=_('Snapshot to modify (name or ID)'), ) parser.add_argument( '--property', metavar='', action='append', default=[], - help='Property to remove from snapshot ' - '(repeat option to remove multiple properties)', + help=_('Property to remove from snapshot ' + '(repeat option to remove multiple properties)'), ) return parser @@ -267,4 +272,4 @@ def take_action(self, parsed_args): parsed_args.property, ) else: - self.app.log.error("No changes requested\n") + self.app.log.error(_("No changes requested\n")) diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index 5a739f61cc..0e07aa78d0 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -21,6 +21,7 @@ from openstackclient.common import command from openstackclient.common import parseractions from openstackclient.common import utils +from openstackclient.i18n import _ from openstackclient.identity import common as identity_common @@ -32,61 +33,61 @@ def get_parser(self, prog_name): parser.add_argument( "name", metavar="", - help="Volume name", + help=_("Volume name"), ) parser.add_argument( "--size", metavar="", type=int, required=True, - help="Volume size in GB", + help=_("Volume size in GB"), ) parser.add_argument( "--type", metavar="", - help="Set the type of volume", + help=_("Set the type of volume"), ) parser.add_argument( "--image", metavar="", - help="Use as source of volume (name or ID)", + help=_("Use as source of volume (name or ID)"), ) parser.add_argument( "--snapshot", metavar="", - help="Use as source of volume (name or ID)", + help=_("Use as source of volume (name or ID)"), ) parser.add_argument( "--source", metavar="", - help="Volume to clone (name or ID)", + help=_("Volume to clone (name or ID)"), ) parser.add_argument( "--description", metavar="", - help="Volume description", + help=_("Volume description"), ) parser.add_argument( '--user', metavar='', - help='Specify an alternate user (name or ID)', + help=_('Specify an alternate user (name or ID)'), ) parser.add_argument( '--project', metavar='', - help='Specify an alternate project (name or ID)', + help=_('Specify an alternate project (name or ID)'), ) parser.add_argument( "--availability-zone", metavar="", - help="Create volume in ", + help=_("Create volume in "), ) parser.add_argument( "--property", metavar="", action=parseractions.KeyValueAction, - help="Set a property to this volume " - "(repeat option to set multiple properties)", + help=_("Set a property to this volume " + "(repeat option to set multiple properties)"), ) return parser @@ -158,15 +159,15 @@ def get_parser(self, prog_name): "volumes", metavar="", nargs="+", - help="Volume(s) to delete (name or ID)" + help=_("Volume(s) to delete (name or ID)") ) parser.add_argument( "--force", dest="force", action="store_true", default=False, - help="Attempt forced removal of volume(s), regardless of state " - "(defaults to False)" + help=_("Attempt forced removal of volume(s), regardless of state " + "(defaults to False)") ) return parser @@ -189,36 +190,36 @@ def get_parser(self, prog_name): parser.add_argument( '--project', metavar='', - help='Filter results by project (name or ID) (admin only)' + help=_('Filter results by project (name or ID) (admin only)') ) identity_common.add_project_domain_option_to_parser(parser) parser.add_argument( '--user', metavar='', - help='Filter results by user (name or ID) (admin only)' + help=_('Filter results by user (name or ID) (admin only)') ) identity_common.add_user_domain_option_to_parser(parser) parser.add_argument( '--name', metavar='', - help='Filter results by volume name', + help=_('Filter results by volume name'), ) parser.add_argument( '--status', metavar='', - help='Filter results by status', + help=_('Filter results by status'), ) parser.add_argument( '--all-projects', action='store_true', default=False, - help='Include all projects (admin only)', + help=_('Include all projects (admin only)'), ) parser.add_argument( '--long', action='store_true', default=False, - help='List additional fields in output', + help=_('List additional fields in output'), ) return parser @@ -320,37 +321,37 @@ def get_parser(self, prog_name): parser.add_argument( 'volume', metavar='', - help='Volume to modify (name or ID)', + help=_('Volume to modify (name or ID)'), ) parser.add_argument( '--name', metavar='', - help='New volume name', + help=_('New volume name'), ) parser.add_argument( '--size', metavar='', type=int, - help='Extend volume size in GB', + help=_('Extend volume size in GB'), ) parser.add_argument( '--description', metavar='', - help='New volume description', + help=_('New volume description'), ) parser.add_argument( '--property', metavar='', action=parseractions.KeyValueAction, - help='Set a property on this volume ' - '(repeat option to set multiple properties)', + help=_('Set a property on this volume ' + '(repeat option to set multiple properties)'), ) parser.add_argument( '--image-property', metavar='', action=parseractions.KeyValueAction, - help='Set an image property on this volume ' - '(repeat option to set multiple image properties)', + help=_('Set an image property on this volume ' + '(repeat option to set multiple image properties)'), ) return parser @@ -360,12 +361,12 @@ def take_action(self, parsed_args): if parsed_args.size: if volume.status != 'available': - self.app.log.error("Volume is in %s state, it must be " - "available before size can be extended" % + self.app.log.error(_("Volume is in %s state, it must be " + "available before size can be extended") % volume.status) return if parsed_args.size <= volume.size: - self.app.log.error("New size must be greater than %s GB" % + self.app.log.error(_("New size must be greater than %s GB") % volume.size) return volume_client.volumes.extend(volume.id, parsed_args.size) @@ -386,7 +387,7 @@ def take_action(self, parsed_args): if (not kwargs and not parsed_args.property and not parsed_args.image_property and not parsed_args.size): - self.app.log.error("No changes requested\n") + self.app.log.error(_("No changes requested\n")) class ShowVolume(command.ShowOne): @@ -397,7 +398,7 @@ def get_parser(self, prog_name): parser.add_argument( 'volume', metavar="", - help="Volume to display (name or ID)" + help=_("Volume to display (name or ID)") ) return parser @@ -428,21 +429,21 @@ def get_parser(self, prog_name): parser.add_argument( 'volume', metavar='', - help='Volume to modify (name or ID)', + help=_('Volume to modify (name or ID)'), ) parser.add_argument( '--property', metavar='', action='append', - help='Remove a property from volume ' - '(repeat option to remove multiple properties)', + help=_('Remove a property from volume ' + '(repeat option to remove multiple properties)'), ) parser.add_argument( '--image-property', metavar='', action='append', - help='Remove an image property from volume ' - '(repeat option to remove multiple image properties)', + help=_('Remove an image property from volume ' + '(repeat option to remove multiple image properties)'), ) return parser @@ -459,4 +460,4 @@ def take_action(self, parsed_args): volume.id, parsed_args.image_property) if (not parsed_args.image_property and not parsed_args.property): - self.app.log.error("No changes requested\n") + self.app.log.error(_("No changes requested\n")) diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index 3050051860..adaccb0a66 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -20,6 +20,7 @@ from openstackclient.common import exceptions from openstackclient.common import parseractions from openstackclient.common import utils +from openstackclient.i18n import _ from openstackclient.identity import common as identity_common @@ -31,12 +32,12 @@ def get_parser(self, prog_name): parser.add_argument( "name", metavar="", - help="Volume type name", + help=_("Volume type name"), ) parser.add_argument( "--description", metavar="", - help="Volume type description", + help=_("Volume type description"), ) public_group = parser.add_mutually_exclusive_group() public_group.add_argument( @@ -44,21 +45,21 @@ def get_parser(self, prog_name): dest="public", action="store_true", default=False, - help="Volume type is accessible to the public", + help=_("Volume type is accessible to the public"), ) public_group.add_argument( "--private", dest="private", action="store_true", default=False, - help="Volume type is not accessible to the public", + help=_("Volume type is not accessible to the public"), ) parser.add_argument( '--property', metavar='', action=parseractions.KeyValueAction, - help='Set a property on this volume type ' - '(repeat option to set multiple properties)', + help=_('Set a property on this volume type ' + '(repeat option to set multiple properties)'), ) return parser @@ -93,7 +94,7 @@ def get_parser(self, prog_name): parser.add_argument( "volume_type", metavar="", - help="Volume type to delete (name or ID)" + help=_("Volume type to delete (name or ID)") ) return parser @@ -113,7 +114,7 @@ def get_parser(self, prog_name): '--long', action='store_true', default=False, - help='List additional fields in output') + help=_('List additional fields in output')) return parser def take_action(self, parsed_args): @@ -139,29 +140,30 @@ def get_parser(self, prog_name): parser.add_argument( 'volume_type', metavar='', - help='Volume type to modify (name or ID)', + help=_('Volume type to modify (name or ID)'), ) parser.add_argument( '--name', metavar='', - help='Set volume type name', + help=_('Set volume type name'), ) parser.add_argument( '--description', metavar='', - help='Set volume type description', + help=_('Set volume type description'), ) parser.add_argument( '--property', metavar='', action=parseractions.KeyValueAction, - help='Set a property on this volume type ' - '(repeat option to set multiple properties)', + help=_('Set a property on this volume type ' + '(repeat option to set multiple properties)'), ) parser.add_argument( '--project', metavar='', - help='Set volume type access to project (name or ID) (admin only)', + help=_('Set volume type access to project (name or ID) ' + '(admin only)'), ) identity_common.add_project_domain_option_to_parser(parser) @@ -178,7 +180,7 @@ def take_action(self, parsed_args): and not parsed_args.description and not parsed_args.property and not parsed_args.project): - self.app.log.error("No changes requested\n") + self.app.log.error(_("No changes requested\n")) return result = 0 @@ -195,15 +197,15 @@ def take_action(self, parsed_args): **kwargs ) except Exception as e: - self.app.log.error("Failed to update volume type name or" - " description: " + str(e)) + self.app.log.error(_("Failed to update volume type name or" + " description: %s") % str(e)) result += 1 if parsed_args.property: try: volume_type.set_keys(parsed_args.property) except Exception as e: - self.app.log.error("Failed to set volume type property: " + + self.app.log.error(_("Failed to set volume type property: ") + str(e)) result += 1 @@ -218,8 +220,8 @@ def take_action(self, parsed_args): volume_client.volume_type_access.add_project_access( volume_type.id, project_info.id) except Exception as e: - self.app.log.error("Failed to set volume type access to" - " project: " + str(e)) + self.app.log.error(_("Failed to set volume type access to" + " project: %s") % str(e)) result += 1 if result > 0: @@ -235,7 +237,7 @@ def get_parser(self, prog_name): parser.add_argument( "volume_type", metavar="", - help="Volume type to display (name or ID)" + help=_("Volume type to display (name or ID)") ) return parser @@ -256,19 +258,19 @@ def get_parser(self, prog_name): parser.add_argument( 'volume_type', metavar='', - help='Volume type to modify (name or ID)', + help=_('Volume type to modify (name or ID)'), ) parser.add_argument( '--property', metavar='', - help='Remove a property from this volume type ' - '(repeat option to remove multiple properties)', + help=_('Remove a property from this volume type ' + '(repeat option to remove multiple properties)'), ) parser.add_argument( '--project', metavar='', - help='Removes volume type access to project (name or ID) ' - ' (admin only)', + help=_('Removes volume type access to project (name or ID) ' + ' (admin only)'), ) identity_common.add_project_domain_option_to_parser(parser) @@ -285,7 +287,7 @@ def take_action(self, parsed_args): if (not parsed_args.property and not parsed_args.project): - self.app.log.error("No changes requested\n") + self.app.log.error(_("No changes requested\n")) return result = 0 @@ -293,8 +295,8 @@ def take_action(self, parsed_args): try: volume_type.unset_keys(parsed_args.property) except Exception as e: - self.app.log.error("Failed to unset volume type property: " + - str(e)) + self.app.log.error(_("Failed to unset volume type property: %s" + ) % str(e)) result += 1 if parsed_args.project: @@ -308,8 +310,8 @@ def take_action(self, parsed_args): volume_client.volume_type_access.remove_project_access( volume_type.id, project_info.id) except Exception as e: - self.app.log.error("Failed to remove volume type access from" - " project: " + str(e)) + self.app.log.error(_("Failed to remove volume type access from" + " project: ") + str(e)) result += 1 if result > 0: From 21530d026e4d14142bea4ce6736326b78022ff86 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Fri, 11 Mar 2016 04:13:21 -0500 Subject: [PATCH 0903/3095] Search by user defined ID for identity providers IDs for service providers can be user defined (like, Bob). This causes issues with the usual get by ID method. Keystone server side has implemented changes to search by ID when listing, which should resolve the issue with minimal changes to the client side. Change-Id: Ic58df22b3445d3293a8e1c76c5da79badebf6528 Closes-Bug: 1479837 --- openstackclient/identity/v3/identity_provider.py | 3 ++- openstackclient/tests/identity/v3/test_identity_provider.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/openstackclient/identity/v3/identity_provider.py b/openstackclient/identity/v3/identity_provider.py index 276a7f576a..39f440f4fd 100644 --- a/openstackclient/identity/v3/identity_provider.py +++ b/openstackclient/identity/v3/identity_provider.py @@ -214,7 +214,8 @@ def take_action(self, parsed_args): identity_client = self.app.client_manager.identity idp = utils.find_resource( identity_client.federation.identity_providers, - parsed_args.identity_provider) + parsed_args.identity_provider, + id=parsed_args.identity_provider) idp._info.pop('links', None) remote_ids = utils.format_list(idp._info.pop('remote_ids', [])) diff --git a/openstackclient/tests/identity/v3/test_identity_provider.py b/openstackclient/tests/identity/v3/test_identity_provider.py index 465e79bad7..3ff7981253 100644 --- a/openstackclient/tests/identity/v3/test_identity_provider.py +++ b/openstackclient/tests/identity/v3/test_identity_provider.py @@ -616,6 +616,7 @@ def test_identity_provider_show(self): self.identity_providers_mock.get.assert_called_with( identity_fakes.idp_id, + id='test_idp' ) collist = ('description', 'enabled', 'id', 'remote_ids') From 5398c96e2a816148cbcde7ce43c46de6721051f6 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 14 May 2016 15:12:57 +0800 Subject: [PATCH 0904/3095] Fix i18n support for help and error messages in compute Change-Id: Id6eebcb48d1b7b49b6636524506294edbc44a83f Partial-bug: #1574965 --- openstackclient/compute/v2/agent.py | 39 ++++++++++----- openstackclient/compute/v2/aggregate.py | 40 ++++++++-------- openstackclient/compute/v2/console.py | 15 +++--- openstackclient/compute/v2/flavor.py | 56 ++++++++++++---------- openstackclient/compute/v2/host.py | 17 ++++--- openstackclient/compute/v2/hypervisor.py | 6 ++- openstackclient/compute/v2/keypair.py | 19 ++++---- openstackclient/compute/v2/server.py | 37 ++++++++------ openstackclient/compute/v2/server_group.py | 22 +++++---- openstackclient/compute/v2/service.py | 32 ++++++++----- openstackclient/compute/v2/usage.py | 34 ++++++------- 11 files changed, 184 insertions(+), 133 deletions(-) diff --git a/openstackclient/compute/v2/agent.py b/openstackclient/compute/v2/agent.py index d5e860330f..e358399db3 100644 --- a/openstackclient/compute/v2/agent.py +++ b/openstackclient/compute/v2/agent.py @@ -19,6 +19,7 @@ from openstackclient.common import command from openstackclient.common import utils +from openstackclient.i18n import _ class CreateAgent(command.ShowOne): @@ -29,28 +30,34 @@ def get_parser(self, prog_name): parser.add_argument( "os", metavar="", - help="Type of OS") + help=_("Type of OS") + ) parser.add_argument( "architecture", metavar="", - help="Type of architecture") + help=_("Type of architecture") + ) parser.add_argument( "version", metavar="", - help="Version") + help=_("Version") + ) parser.add_argument( "url", metavar="", - help="URL") + help=_("URL") + ) parser.add_argument( "md5hash", metavar="", - help="MD5 hash") + help=_("MD5 hash") + ) parser.add_argument( "hypervisor", metavar="", - help="Type of hypervisor", - default="xen") + default="xen", + help=_("Type of hypervisor") + ) return parser def take_action(self, parsed_args): @@ -75,7 +82,8 @@ def get_parser(self, prog_name): parser.add_argument( "id", metavar="", - help="ID of agent to delete") + help=_("ID of agent to delete") + ) return parser def take_action(self, parsed_args): @@ -91,7 +99,8 @@ def get_parser(self, prog_name): parser.add_argument( "--hypervisor", metavar="", - help="Type of hypervisor") + help=_("Type of hypervisor") + ) return parser def take_action(self, parsed_args): @@ -120,19 +129,23 @@ def get_parser(self, prog_name): parser.add_argument( "id", metavar="", - help="ID of the agent") + help=_("ID of the agent") + ) parser.add_argument( "version", metavar="", - help="Version of the agent") + help=_("Version of the agent") + ) parser.add_argument( "url", metavar="", - help="URL") + help=_("URL") + ) parser.add_argument( "md5hash", metavar="", - help="MD5 hash") + help=_("MD5 hash") + ) return parser def take_action(self, parsed_args): diff --git a/openstackclient/compute/v2/aggregate.py b/openstackclient/compute/v2/aggregate.py index 1a02a3886d..752e0fdfbe 100644 --- a/openstackclient/compute/v2/aggregate.py +++ b/openstackclient/compute/v2/aggregate.py @@ -21,6 +21,7 @@ from openstackclient.common import command from openstackclient.common import parseractions from openstackclient.common import utils +from openstackclient.i18n import _ class AddAggregateHost(command.ShowOne): @@ -31,12 +32,12 @@ def get_parser(self, prog_name): parser.add_argument( 'aggregate', metavar='', - help='Aggregate (name or ID)', + help=_("Aggregate (name or ID)") ) parser.add_argument( 'host', metavar='', - help='Host to add to ', + help=_("Host to add to ") ) return parser @@ -62,19 +63,19 @@ def get_parser(self, prog_name): parser.add_argument( "name", metavar="", - help="New aggregate name", + help=_("New aggregate name") ) parser.add_argument( "--zone", metavar="", - help="Availability zone name", + help=_("Availability zone name") ) parser.add_argument( "--property", metavar="", action=parseractions.KeyValueAction, - help='Property to add to this aggregate ' - '(repeat option to set multiple properties)', + help=_("Property to add to this aggregate " + "(repeat option to set multiple properties)") ) return parser @@ -105,7 +106,7 @@ def get_parser(self, prog_name): parser.add_argument( 'aggregate', metavar='', - help='Aggregate to delete (name or ID)', + help=_("Aggregate to delete (name or ID)") ) return parser @@ -128,7 +129,8 @@ def get_parser(self, prog_name): '--long', action='store_true', default=False, - help='List additional fields in output') + help=_("List additional fields in output") + ) return parser def take_action(self, parsed_args): @@ -175,12 +177,12 @@ def get_parser(self, prog_name): parser.add_argument( 'aggregate', metavar='', - help='Aggregate (name or ID)', + help=_("Aggregate (name or ID)") ) parser.add_argument( 'host', metavar='', - help='Host to remove from ', + help=_("Host to remove from ") ) return parser @@ -209,24 +211,24 @@ def get_parser(self, prog_name): parser.add_argument( 'aggregate', metavar='', - help='Aggregate to modify (name or ID)', + help=_("Aggregate to modify (name or ID)") ) parser.add_argument( '--name', metavar='', - help='Set aggregate name', + help=_("Set aggregate name") ) parser.add_argument( "--zone", metavar="", - help="Set availability zone name", + help=_("Set availability zone name") ) parser.add_argument( "--property", metavar="", action=parseractions.KeyValueAction, - help='Property to set on ' - '(repeat option to set multiple properties)', + help=_("Property to set on " + "(repeat option to set multiple properties)") ) return parser @@ -264,7 +266,7 @@ def get_parser(self, prog_name): parser.add_argument( 'aggregate', metavar='', - help='Aggregate to display (name or ID)', + help=_("Aggregate to display (name or ID)") ) return parser @@ -300,15 +302,15 @@ def get_parser(self, prog_name): parser.add_argument( "aggregate", metavar="", - help="Aggregate to modify (name or ID)", + help=_("Aggregate to modify (name or ID)") ) parser.add_argument( "--property", metavar="", action='append', - help='Property to remove from aggregate ' - '(repeat option to remove multiple properties)', required=True, + help=_("Property to remove from aggregate " + "(repeat option to remove multiple properties)") ) return parser diff --git a/openstackclient/compute/v2/console.py b/openstackclient/compute/v2/console.py index 6c0ec31a56..1165862c91 100644 --- a/openstackclient/compute/v2/console.py +++ b/openstackclient/compute/v2/console.py @@ -21,6 +21,7 @@ from openstackclient.common import command from openstackclient.common import parseractions from openstackclient.common import utils +from openstackclient.i18n import _ class ShowConsoleLog(command.Command): @@ -31,7 +32,7 @@ def get_parser(self, prog_name): parser.add_argument( 'server', metavar='', - help='Server to show console log (name or ID)', + help=_("Server to show console log (name or ID)") ) parser.add_argument( '--lines', @@ -39,8 +40,8 @@ def get_parser(self, prog_name): type=int, default=None, action=parseractions.NonNegativeAction, - help='Number of lines to display from the end of the log ' - '(default=all)', + help=_("Number of lines to display from the end of the log " + "(default=all)") ) return parser @@ -69,7 +70,7 @@ def get_parser(self, prog_name): parser.add_argument( 'server', metavar='', - help='Server to show URL (name or ID)', + help=_("Server to show URL (name or ID)") ) type_group = parser.add_mutually_exclusive_group() type_group.add_argument( @@ -78,21 +79,21 @@ def get_parser(self, prog_name): action='store_const', const='novnc', default='novnc', - help='Show noVNC console URL (default)', + help=_("Show noVNC console URL (default)") ) type_group.add_argument( '--xvpvnc', dest='url_type', action='store_const', const='xvpvnc', - help='Show xpvnc console URL', + help=_("Show xpvnc console URL") ) type_group.add_argument( '--spice', dest='url_type', action='store_const', const='spice', - help='Show SPICE console URL', + help=_("Show SPICE console URL") ) return parser diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 4b918369f2..37ff831d59 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -21,6 +21,7 @@ from openstackclient.common import exceptions from openstackclient.common import parseractions from openstackclient.common import utils +from openstackclient.i18n import _ def _find_flavor(compute_client, flavor): @@ -35,7 +36,7 @@ def _find_flavor(compute_client, flavor): return compute_client.flavors.find(name=flavor, is_public=None) except Exception as ex: if type(ex).__name__ == 'NotFound': - msg = "No flavor with a name or ID of '%s' exists." % flavor + msg = _("No flavor with a name or ID of '%s' exists.") % flavor raise exceptions.CommandError(msg) else: raise @@ -49,56 +50,56 @@ def get_parser(self, prog_name): parser.add_argument( "name", metavar="", - help="New flavor name", + help=_("New flavor name") ) parser.add_argument( "--id", metavar="", default='auto', - help="Unique flavor ID; 'auto' creates a UUID " - "(default: auto)", + help=_("Unique flavor ID; 'auto' creates a UUID " + "(default: auto)") ) parser.add_argument( "--ram", type=int, metavar="", default=256, - help="Memory size in MB (default 256M)", + help=_("Memory size in MB (default 256M)") ) parser.add_argument( "--disk", type=int, metavar="", default=0, - help="Disk size in GB (default 0G)", + help=_("Disk size in GB (default 0G)") ) parser.add_argument( "--ephemeral", type=int, metavar="", default=0, - help="Ephemeral disk size in GB (default 0G)", + help=_("Ephemeral disk size in GB (default 0G)") ) parser.add_argument( "--swap", type=int, metavar="", default=0, - help="Swap space size in GB (default 0G)", + help=_("Swap space size in GB (default 0G)") ) parser.add_argument( "--vcpus", type=int, metavar="", default=1, - help="Number of vcpus (default 1)", + help=_("Number of vcpus (default 1)") ) parser.add_argument( "--rxtx-factor", type=float, metavar="", default=1.0, - help="RX/TX factor (default 1.0)", + help=_("RX/TX factor (default 1.0)") ) public_group = parser.add_mutually_exclusive_group() public_group.add_argument( @@ -106,13 +107,13 @@ def get_parser(self, prog_name): dest="public", action="store_true", default=True, - help="Flavor is available to other projects (default)", + help=_("Flavor is available to other projects (default)") ) public_group.add_argument( "--private", dest="public", action="store_false", - help="Flavor is not available to other projects", + help=_("Flavor is not available to other projects") ) return parser @@ -145,7 +146,7 @@ def get_parser(self, prog_name): parser.add_argument( "flavor", metavar="", - help="Flavor to delete (name or ID)", + help=_("Flavor to delete (name or ID)") ) return parser @@ -166,35 +167,38 @@ def get_parser(self, prog_name): dest="public", action="store_true", default=True, - help="List only public flavors (default)", + help=_("List only public flavors (default)") ) public_group.add_argument( "--private", dest="public", action="store_false", - help="List only private flavors", + help=_("List only private flavors") ) public_group.add_argument( "--all", dest="all", action="store_true", default=False, - help="List all flavors, whether public or private", + help=_("List all flavors, whether public or private") ) parser.add_argument( '--long', action='store_true', default=False, - help='List additional fields in output') + help=_("List additional fields in output") + ) parser.add_argument( '--marker', metavar="", - help='The last flavor ID of the previous page') + help=_("The last flavor ID of the previous page") + ) parser.add_argument( '--limit', type=int, metavar="", - help='Maximum number of flavors to display') + help=_("Maximum number of flavors to display") + ) return parser def take_action(self, parsed_args): @@ -245,13 +249,13 @@ def get_parser(self, prog_name): "--property", metavar="", action=parseractions.KeyValueAction, - help='Property to add or modify for this flavor ' - '(repeat option to set multiple properties)', + help=_("Property to add or modify for this flavor " + "(repeat option to set multiple properties)") ) parser.add_argument( "flavor", metavar="", - help="Flavor to modify (name or ID)", + help=_("Flavor to modify (name or ID)") ) return parser @@ -269,7 +273,7 @@ def get_parser(self, prog_name): parser.add_argument( "flavor", metavar="", - help="Flavor to display (name or ID)", + help=_("Flavor to display (name or ID)") ) return parser @@ -293,14 +297,14 @@ def get_parser(self, prog_name): "--property", metavar="", action='append', - help='Property to remove from flavor ' - '(repeat option to unset multiple properties)', required=True, + help=_("Property to remove from flavor " + "(repeat option to unset multiple properties)") ) parser.add_argument( "flavor", metavar="", - help="Flavor to modify (name or ID)", + help=_("Flavor to modify (name or ID)") ) return parser diff --git a/openstackclient/compute/v2/host.py b/openstackclient/compute/v2/host.py index 5af2531084..73e2cdf9ab 100644 --- a/openstackclient/compute/v2/host.py +++ b/openstackclient/compute/v2/host.py @@ -17,6 +17,7 @@ from openstackclient.common import command from openstackclient.common import utils +from openstackclient.i18n import _ class ListHost(command.Lister): @@ -27,7 +28,8 @@ def get_parser(self, prog_name): parser.add_argument( "--zone", metavar="", - help="Only return hosts in the availability zone.") + help=_("Only return hosts in the availability zone") + ) return parser def take_action(self, parsed_args): @@ -51,29 +53,29 @@ def get_parser(self, prog_name): parser.add_argument( "host", metavar="", - help="The host to modify (name or ID)" + help=_("The host to modify (name or ID)") ) status = parser.add_mutually_exclusive_group() status.add_argument( '--enable', action='store_true', - help='Enable the host' + help=_("Enable the host") ) status.add_argument( '--disable', action='store_true', - help='Disable the host' + help=_("Disable the host") ) maintenance = parser.add_mutually_exclusive_group() maintenance.add_argument( '--enable-maintenance', action='store_true', - help='Enable maintenance mode for the host' + help=_("Enable maintenance mode for the host") ) maintenance.add_argument( '--disable-maintenance', action='store_true', - help='Disable maintenance mode for the host', + help=_("Disable maintenance mode for the host") ) return parser @@ -109,7 +111,8 @@ def get_parser(self, prog_name): parser.add_argument( "host", metavar="", - help="Name of host") + help=_("Name of host") + ) return parser def take_action(self, parsed_args): diff --git a/openstackclient/compute/v2/hypervisor.py b/openstackclient/compute/v2/hypervisor.py index f5288a35a5..333a7dea4d 100644 --- a/openstackclient/compute/v2/hypervisor.py +++ b/openstackclient/compute/v2/hypervisor.py @@ -20,6 +20,7 @@ from openstackclient.common import command from openstackclient.common import utils +from openstackclient.i18n import _ class ListHypervisor(command.Lister): @@ -30,7 +31,7 @@ def get_parser(self, prog_name): parser.add_argument( "--matching", metavar="", - help="Filter hypervisors using substring", + help=_("Filter hypervisors using substring") ) return parser @@ -60,7 +61,8 @@ def get_parser(self, prog_name): parser.add_argument( "hypervisor", metavar="", - help="Hypervisor to display (name or ID)") + help=_("Hypervisor to display (name or ID)") + ) return parser def take_action(self, parsed_args): diff --git a/openstackclient/compute/v2/keypair.py b/openstackclient/compute/v2/keypair.py index 1db0f942c7..8a58e8f2e6 100644 --- a/openstackclient/compute/v2/keypair.py +++ b/openstackclient/compute/v2/keypair.py @@ -23,6 +23,7 @@ from openstackclient.common import command from openstackclient.common import exceptions from openstackclient.common import utils +from openstackclient.i18n import _ class CreateKeypair(command.ShowOne): @@ -33,12 +34,12 @@ def get_parser(self, prog_name): parser.add_argument( 'name', metavar='', - help='New public key name', + help=_("New public key name") ) parser.add_argument( '--public-key', metavar='', - help='Filename for public key to add', + help=_("Filename for public key to add") ) return parser @@ -51,9 +52,11 @@ def take_action(self, parsed_args): with io.open(os.path.expanduser(parsed_args.public_key)) as p: public_key = p.read() except IOError as e: - msg = "Key file %s not found: %s" - raise exceptions.CommandError(msg - % (parsed_args.public_key, e)) + msg = _("Key file %(public_key)s not found: %(exception)s") + raise exceptions.CommandError( + msg % {"public_key": parsed_args.public_key, + "exception": e} + ) keypair = compute_client.keypairs.create( parsed_args.name, @@ -81,7 +84,7 @@ def get_parser(self, prog_name): parser.add_argument( 'name', metavar='', - help='Public key to delete', + help=_("Public key to delete") ) return parser @@ -115,13 +118,13 @@ def get_parser(self, prog_name): parser.add_argument( 'name', metavar='', - help='Public key to display', + help=_("Public key to display") ) parser.add_argument( '--public-key', action='store_true', default=False, - help='Show only bare public key', + help=_("Show only bare public key") ) return parser diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 8f0748e736..781ccb1b8e 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -102,9 +102,10 @@ def _get_ip_address(addresses, address_type, ip_address_family): if addy['OS-EXT-IPS:type'] == new_address_type: if int(addy['version']) in ip_address_family: return addy['addr'] + msg = _("ERROR: No %(type)s IP version %(family)s address found") raise exceptions.CommandError( - "ERROR: No %s IP version %s address found" % - (address_type, ip_address_family) + msg % {"type": address_type, + "family": ip_address_family} ) @@ -417,7 +418,11 @@ def take_action(self, parsed_args): try: files[dst] = io.open(src, 'rb') except IOError as e: - raise exceptions.CommandError("Can't open '%s': %s" % (src, e)) + msg = _("Can't open '%(source)s': %(exception)s") + raise exceptions.CommandError( + msg % {"source": src, + "exception": e} + ) if parsed_args.min > parsed_args.max: msg = _("min instances should be <= max instances") @@ -434,8 +439,11 @@ def take_action(self, parsed_args): try: userdata = io.open(parsed_args.user_data) except IOError as e: - msg = "Can't open '%s': %s" - raise exceptions.CommandError(msg % (parsed_args.user_data, e)) + msg = _("Can't open '%(data)s': %(exception)s") + raise exceptions.CommandError( + msg % {"data": parsed_args.user_data, + "exception": e} + ) block_device_mapping = {} if volume: @@ -744,7 +752,8 @@ def get_parser(self, prog_name): parser.add_argument( '--project', metavar='', - help="Search by project (admin only) (name or ID)") + help=_("Search by project (admin only) (name or ID)") + ) identity_common.add_project_domain_option_to_parser(parser) parser.add_argument( '--user', @@ -762,19 +771,19 @@ def get_parser(self, prog_name): '--marker', metavar='', default=None, - help=('The last server (name or ID) of the previous page. Display' - ' list of servers after marker. Display all servers if not' - ' specified.') + help=_('The last server (name or ID) of the previous page. Display' + ' list of servers after marker. Display all servers if not' + ' specified.') ) parser.add_argument( '--limit', metavar='', type=int, default=None, - help=("Maximum number of servers to display. If limit equals -1," - " all servers will be displayed. If limit is greater than" - " 'osapi_max_limit' option of Nova API," - " 'osapi_max_limit' will be used instead."), + help=_("Maximum number of servers to display. If limit equals -1," + " all servers will be displayed. If limit is greater than" + " 'osapi_max_limit' option of Nova API," + " 'osapi_max_limit' will be used instead."), ) return parser @@ -1101,7 +1110,7 @@ def get_parser(self, prog_name): parser.add_argument( '--password', metavar='', - help="Set the password on the rebuilt instance", + help=_("Set the password on the rebuilt instance"), ) parser.add_argument( '--wait', diff --git a/openstackclient/compute/v2/server_group.py b/openstackclient/compute/v2/server_group.py index eb5745f5c8..973095ca98 100644 --- a/openstackclient/compute/v2/server_group.py +++ b/openstackclient/compute/v2/server_group.py @@ -18,6 +18,7 @@ from openstackclient.common import command from openstackclient.common import exceptions from openstackclient.common import utils +from openstackclient.i18n import _ _formatters = { @@ -43,15 +44,15 @@ def get_parser(self, prog_name): parser.add_argument( 'name', metavar='', - help='New server group name', + help=_("New server group name") ) parser.add_argument( '--policy', metavar='', action='append', required=True, - help='Add a policy to ' - '(repeat option to add multiple policies)', + help=_("Add a policy to " + "(repeat option to add multiple policies)") ) return parser @@ -78,7 +79,7 @@ def get_parser(self, prog_name): 'server_group', metavar='', nargs='+', - help='server group(s) to delete (name or ID)', + help=_("server group(s) to delete (name or ID)") ) return parser @@ -97,8 +98,11 @@ def take_action(self, parsed_args): if result > 0: total = len(parsed_args.server_group) - msg = "%s of %s server groups failed to delete." % (result, total) - raise exceptions.CommandError(msg) + msg = _("%(result)s of %(total)s server groups failed to delete.") + raise exceptions.CommandError( + msg % {"result": result, + "total": total} + ) class ListServerGroup(command.Lister): @@ -110,13 +114,13 @@ def get_parser(self, prog_name): '--all-projects', action='store_true', default=False, - help='Display information from all projects (admin only)', + help=_("Display information from all projects (admin only)") ) parser.add_argument( '--long', action='store_true', default=False, - help='List additional fields in output', + help=_("List additional fields in output") ) return parser @@ -166,7 +170,7 @@ def get_parser(self, prog_name): parser.add_argument( 'server_group', metavar='', - help='server group to display (name or ID)', + help=_("server group to display (name or ID)") ) return parser diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index b1ebde4eb2..6093b8ce17 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.py @@ -28,7 +28,8 @@ def get_parser(self, prog_name): parser.add_argument( "service", metavar="", - help="Compute service to delete (ID only)") + help=_("Compute service to delete (ID only)") + ) return parser def take_action(self, parsed_args): @@ -45,16 +46,18 @@ def get_parser(self, prog_name): parser.add_argument( "--host", metavar="", - help="List services on specified host (name only)") + help=_("List services on specified host (name only)") + ) parser.add_argument( "--service", metavar="", - help="List only specified service (name only)") + help=_("List only specified service (name only)") + ) parser.add_argument( "--long", action="store_true", default=False, - help="List additional fields in output" + help=_("List additional fields in output") ) return parser @@ -97,29 +100,34 @@ def get_parser(self, prog_name): parser.add_argument( "host", metavar="", - help="Name of host") + help=_("Name of host") + ) parser.add_argument( "service", metavar="", - help="Name of service") + help=_("Name of service") + ) enabled_group = parser.add_mutually_exclusive_group() enabled_group.add_argument( "--enable", dest="enabled", default=True, - help="Enable a service (default)", - action="store_true") + action="store_true", + help=_("Enable a service (default)") + ) enabled_group.add_argument( "--disable", dest="enabled", - help="Disable a service", - action="store_false") + action="store_false", + help=_("Disable a service") + ) parser.add_argument( "--disable-reason", default=None, metavar="", - help="Reason for disabling the service (in quotas). Note that " - "when the service is enabled, this option is ignored.") + help=_("Reason for disabling the service (in quotas). Note that " + "when the service is enabled, this option is ignored.") + ) return parser def take_action(self, parsed_args): diff --git a/openstackclient/compute/v2/usage.py b/openstackclient/compute/v2/usage.py index baa501706a..b83bef13df 100644 --- a/openstackclient/compute/v2/usage.py +++ b/openstackclient/compute/v2/usage.py @@ -22,6 +22,7 @@ from openstackclient.common import command from openstackclient.common import utils +from openstackclient.i18n import _ class ListUsage(command.Lister): @@ -33,14 +34,14 @@ def get_parser(self, prog_name): "--start", metavar="", default=None, - help="Usage range start date, ex 2012-01-20" - " (default: 4 weeks ago)" + help=_("Usage range start date, ex 2012-01-20" + " (default: 4 weeks ago)") ) parser.add_argument( "--end", metavar="", default=None, - help="Usage range end date, ex 2012-01-20 (default: tomorrow)" + help=_("Usage range end date, ex 2012-01-20 (default: tomorrow)") ) return parser @@ -95,10 +96,10 @@ def _format_project(project): pass if parsed_args.formatter == 'table' and len(usage_list) > 0: - sys.stdout.write("Usage from %s to %s: \n" % ( - start.strftime(dateformat), - end.strftime(dateformat), - )) + sys.stdout.write(_("Usage from %(start)s to %(end)s: \n") % { + "start": start.strftime(dateformat), + "end": end.strftime(dateformat), + }) return (column_headers, (utils.get_item_properties( @@ -122,20 +123,20 @@ def get_parser(self, prog_name): "--project", metavar="", default=None, - help="Name or ID of project to show usage for" + help=_("Name or ID of project to show usage for") ) parser.add_argument( "--start", metavar="", default=None, - help="Usage range start date, ex 2012-01-20" - " (default: 4 weeks ago)" + help=_("Usage range start date, ex 2012-01-20" + " (default: 4 weeks ago)") ) parser.add_argument( "--end", metavar="", default=None, - help="Usage range end date, ex 2012-01-20 (default: tomorrow)" + help=_("Usage range end date, ex 2012-01-20 (default: tomorrow)") ) return parser @@ -167,11 +168,12 @@ def take_action(self, parsed_args): usage = compute_client.usage.get(project, start, end) if parsed_args.formatter == 'table': - sys.stdout.write("Usage from %s to %s on project %s: \n" % ( - start.strftime(dateformat), - end.strftime(dateformat), - project - )) + sys.stdout.write(_("Usage from %(start)s to %(end)s on " + "project %(project)s: \n") % { + "start": start.strftime(dateformat), + "end": end.strftime(dateformat), + "project": project, + }) info = {} info['Servers'] = ( From 5ae8f1b7d52ca9c94a01aeef13754b1d5a15a91f Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Tue, 24 May 2016 03:16:04 +0000 Subject: [PATCH 0905/3095] Updated from global requirements Change-Id: Ia94ac7e112817cfcea5e446e7966bb18e8dd9afc --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 0b899e234d..62bcfe3870 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ six>=1.9.0 # MIT Babel>=2.3.4 # BSD cliff!=1.16.0,!=1.17.0,>=1.15.0 # Apache-2.0 keystoneauth1>=2.1.0 # Apache-2.0 -openstacksdk>=0.8.5 # Apache-2.0 +openstacksdk>=0.8.6 # Apache-2.0 os-client-config>=1.13.1 # Apache-2.0 oslo.config>=3.9.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 From 58094bff80584ba9afac0073aea545a96a170bff Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Fri, 11 Mar 2016 04:06:18 -0500 Subject: [PATCH 0906/3095] Search by user defined ID for service providers IDs for service providers can be user defined (like, Bob). This causes issues with the usual get by ID method. Keystone server side has implemented changes to search by ID when listing, which should resolve the issue with minimal changes to the client side. Change-Id: Ic705806e4bc7bb24f946a1fce803a0a0a4d788c1 Closes-Bug: 1555830 --- openstackclient/identity/v3/service_provider.py | 3 ++- openstackclient/tests/identity/v3/test_service_provider.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/openstackclient/identity/v3/service_provider.py b/openstackclient/identity/v3/service_provider.py index e3a22ebb1a..8b433b4d66 100644 --- a/openstackclient/identity/v3/service_provider.py +++ b/openstackclient/identity/v3/service_provider.py @@ -192,7 +192,8 @@ def take_action(self, parsed_args): service_client = self.app.client_manager.identity service_provider = utils.find_resource( service_client.federation.service_providers, - parsed_args.service_provider) + parsed_args.service_provider, + id=parsed_args.service_provider) service_provider._info.pop('links', None) return zip(*sorted(six.iteritems(service_provider._info))) diff --git a/openstackclient/tests/identity/v3/test_service_provider.py b/openstackclient/tests/identity/v3/test_service_provider.py index 80d60c5a4e..99ea1f75ce 100644 --- a/openstackclient/tests/identity/v3/test_service_provider.py +++ b/openstackclient/tests/identity/v3/test_service_provider.py @@ -408,6 +408,7 @@ def test_service_provider_show(self): self.service_providers_mock.get.assert_called_with( service_fakes.sp_id, + id='BETA' ) collist = ('auth_url', 'description', 'enabled', 'id', 'sp_url') From 8ce5d90136363322b33f78b705670eeadd76f1b1 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Wed, 25 May 2016 13:06:39 +0800 Subject: [PATCH 0907/3095] Refactor service unit tests Add a FakeService class, and refactor service unit tests to use this class. Change-Id: I650ad83386a58205ebe42274d2bf2f508436bfa6 --- openstackclient/tests/compute/v2/fakes.py | 59 +++++++-- .../tests/compute/v2/test_service.py | 115 ++++++++---------- 2 files changed, 101 insertions(+), 73 deletions(-) diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 70fc386f1b..62a46b1d53 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -76,17 +76,6 @@ QUOTA_columns = tuple(sorted(QUOTA)) QUOTA_data = tuple(QUOTA[x] for x in sorted(QUOTA)) -service_host = 'host_test' -service_binary = 'compute_test' -service_status = 'enabled' -service_disabled_reason = 'earthquake' -SERVICE = { - 'host': service_host, - 'binary': service_binary, - 'status': service_status, - 'disabled_reason': service_disabled_reason, -} - class FakeAggregate(object): """Fake one aggregate.""" @@ -523,6 +512,54 @@ def get_servers(servers=None, count=2): return mock.MagicMock(side_effect=servers) +class FakeService(object): + """Fake one or more services.""" + + @staticmethod + def create_one_service(attrs=None): + """Create a fake service. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object, with id, name, ram, vcpus, properties + """ + attrs = attrs or {} + + # Set default attributes. + service_info = { + 'host': 'host-' + uuid.uuid4().hex, + 'binary': 'binary-' + uuid.uuid4().hex, + 'status': 'enabled', + 'disabled_reason': 'earthquake', + } + + # Overwrite default attributes. + service_info.update(attrs) + + service = fakes.FakeResource(info=copy.deepcopy(service_info), + loaded=True) + + return service + + @staticmethod + def create_services(attrs=None, count=2): + """Create multiple fake services. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of services to fake + :return: + A list of FakeResource objects faking the services + """ + services = [] + for i in range(0, count): + services.append(FakeService.create_one_service(attrs)) + + return services + + class FakeFlavor(object): """Fake one or more flavors.""" diff --git a/openstackclient/tests/compute/v2/test_service.py b/openstackclient/tests/compute/v2/test_service.py index db09720457..7a5a840f05 100644 --- a/openstackclient/tests/compute/v2/test_service.py +++ b/openstackclient/tests/compute/v2/test_service.py @@ -13,12 +13,10 @@ # under the License. # -import copy import mock from openstackclient.compute.v2 import service from openstackclient.tests.compute.v2 import fakes as compute_fakes -from openstackclient.tests import fakes class TestService(compute_fakes.TestComputev2): @@ -36,6 +34,8 @@ class TestServiceDelete(TestService): def setUp(self): super(TestServiceDelete, self).setUp() + self.service = compute_fakes.FakeService.create_one_service() + self.service_mock.delete.return_value = None # Get the command object to test @@ -43,17 +43,17 @@ def setUp(self): def test_service_delete_no_options(self): arglist = [ - compute_fakes.service_binary, + self.service.binary, ] verifylist = [ - ('service', compute_fakes.service_binary), + ('service', self.service.binary), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.service_mock.delete.assert_called_with( - compute_fakes.service_binary, + self.service.binary, ) self.assertIsNone(result) @@ -63,23 +63,21 @@ class TestServiceList(TestService): def setUp(self): super(TestServiceList, self).setUp() - self.service_mock.list.return_value = [fakes.FakeResource( - None, - copy.deepcopy(compute_fakes.SERVICE), - loaded=True, - )] + self.service = compute_fakes.FakeService.create_one_service() + + self.service_mock.list.return_value = [self.service] # Get the command object to test self.cmd = service.ListService(self.app, None) def test_service_list(self): arglist = [ - '--host', compute_fakes.service_host, - '--service', compute_fakes.service_binary, + '--host', self.service.host, + '--service', self.service.binary, ] verifylist = [ - ('host', compute_fakes.service_host), - ('service', compute_fakes.service_binary), + ('host', self.service.host), + ('service', self.service.binary), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -89,22 +87,22 @@ def test_service_list(self): columns, data = self.cmd.take_action(parsed_args) self.service_mock.list.assert_called_with( - compute_fakes.service_host, - compute_fakes.service_binary, + self.service.host, + self.service.binary, ) self.assertNotIn("Disabled Reason", columns) - self.assertNotIn(compute_fakes.service_disabled_reason, list(data)[0]) + self.assertNotIn(self.service.disabled_reason, list(data)[0]) def test_service_list_with_long_option(self): arglist = [ - '--host', compute_fakes.service_host, - '--service', compute_fakes.service_binary, + '--host', self.service.host, + '--service', self.service.binary, '--long' ] verifylist = [ - ('host', compute_fakes.service_host), - ('service', compute_fakes.service_binary), + ('host', self.service.host), + ('service', self.service.binary), ('long', True) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -115,7 +113,7 @@ def test_service_list_with_long_option(self): columns, data = self.cmd.take_action(parsed_args) self.assertIn("Disabled Reason", columns) - self.assertIn(compute_fakes.service_disabled_reason, list(data)[0]) + self.assertIn(self.service.disabled_reason, list(data)[0]) class TestServiceSet(TestService): @@ -123,59 +121,52 @@ class TestServiceSet(TestService): def setUp(self): super(TestServiceSet, self).setUp() - self.service_mock.enable.return_value = [fakes.FakeResource( - None, - copy.deepcopy(compute_fakes.SERVICE), - loaded=True, - )] + self.service = compute_fakes.FakeService.create_one_service() - self.service_mock.disable.return_value = [fakes.FakeResource( - None, - copy.deepcopy(compute_fakes.SERVICE), - loaded=True, - )] + self.service_mock.enable.return_value = self.service + self.service_mock.disable.return_value = self.service self.cmd = service.SetService(self.app, None) def test_service_set_enable(self): arglist = [ '--enable', - compute_fakes.service_host, - compute_fakes.service_binary, + self.service.host, + self.service.binary, ] verifylist = [ ('enabled', True), - ('host', compute_fakes.service_host), - ('service', compute_fakes.service_binary), + ('host', self.service.host), + ('service', self.service.binary), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.service_mock.enable.assert_called_with( - compute_fakes.service_host, - compute_fakes.service_binary, + self.service.host, + self.service.binary ) self.assertIsNone(result) def test_service_set_disable(self): arglist = [ '--disable', - compute_fakes.service_host, - compute_fakes.service_binary, + self.service.host, + self.service.binary, ] verifylist = [ ('enabled', False), - ('host', compute_fakes.service_host), - ('service', compute_fakes.service_binary), + ('host', self.service.host), + ('service', self.service.binary), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.service_mock.disable.assert_called_with( - compute_fakes.service_host, - compute_fakes.service_binary, + self.service.host, + self.service.binary ) self.assertIsNone(result) @@ -184,22 +175,22 @@ def test_service_set_disable_with_reason(self): arglist = [ '--disable', '--disable-reason', reason, - compute_fakes.service_host, - compute_fakes.service_binary, + self.service.host, + self.service.binary, ] verifylist = [ ('enabled', False), ('disable_reason', reason), - ('host', compute_fakes.service_host), - ('service', compute_fakes.service_binary), + ('host', self.service.host), + ('service', self.service.binary), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.service_mock.disable_log_reason.assert_called_with( - compute_fakes.service_host, - compute_fakes.service_binary, + self.service.host, + self.service.binary, reason ) self.assertIsNone(result) @@ -208,14 +199,14 @@ def test_service_set_only_with_disable_reason(self): reason = 'earthquake' arglist = [ '--disable-reason', reason, - compute_fakes.service_host, - compute_fakes.service_binary, + self.service.host, + self.service.binary, ] verifylist = [ ('enabled', True), ('disable_reason', reason), - ('host', compute_fakes.service_host), - ('service', compute_fakes.service_binary), + ('host', self.service.host), + ('service', self.service.binary), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -226,8 +217,8 @@ def test_service_set_only_with_disable_reason(self): mock_log.assert_called_once_with(msg) self.service_mock.enable.assert_called_with( - compute_fakes.service_host, - compute_fakes.service_binary + self.service.host, + self.service.binary ) self.assertIsNone(result) @@ -236,14 +227,14 @@ def test_service_set_enable_with_disable_reason(self): arglist = [ '--enable', '--disable-reason', reason, - compute_fakes.service_host, - compute_fakes.service_binary, + self.service.host, + self.service.binary, ] verifylist = [ ('enabled', True), ('disable_reason', reason), - ('host', compute_fakes.service_host), - ('service', compute_fakes.service_binary), + ('host', self.service.host), + ('service', self.service.binary), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -254,7 +245,7 @@ def test_service_set_enable_with_disable_reason(self): mock_log.assert_called_once_with(msg) self.service_mock.enable.assert_called_with( - compute_fakes.service_host, - compute_fakes.service_binary + self.service.host, + self.service.binary ) self.assertIsNone(result) From 99031cf1ed36b3c3b8c09e2c64aaa4b789eb25b9 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Tue, 24 May 2016 18:52:24 -0500 Subject: [PATCH 0908/3095] Release notes cleanup Preparing for the next release Change-Id: Ia185063d12d4bd235170b06c3da400e93cd934da --- .../add-vlan-transparent-option-4fa72fbfbbe3f31e.yaml | 9 --------- ...df53216726.yaml => bug-1542171-fde165df53216726.yaml} | 4 ++-- releasenotes/notes/bug-1545537-4fa72fbfbbe3f31e.yaml | 8 ++++++++ releasenotes/notes/bug-1550999-5e352a71dfbc828d.yaml | 5 +++++ releasenotes/notes/bug-1566269-2572bca9157ca107.yaml | 2 +- releasenotes/notes/volume_service-5e352a71dfbc828d.yaml | 9 --------- 6 files changed, 16 insertions(+), 21 deletions(-) delete mode 100644 releasenotes/notes/add-vlan-transparent-option-4fa72fbfbbe3f31e.yaml rename releasenotes/notes/{add-server-group-command-fde165df53216726.yaml => bug-1542171-fde165df53216726.yaml} (67%) create mode 100644 releasenotes/notes/bug-1545537-4fa72fbfbbe3f31e.yaml create mode 100644 releasenotes/notes/bug-1550999-5e352a71dfbc828d.yaml delete mode 100644 releasenotes/notes/volume_service-5e352a71dfbc828d.yaml diff --git a/releasenotes/notes/add-vlan-transparent-option-4fa72fbfbbe3f31e.yaml b/releasenotes/notes/add-vlan-transparent-option-4fa72fbfbbe3f31e.yaml deleted file mode 100644 index 508fb81c7a..0000000000 --- a/releasenotes/notes/add-vlan-transparent-option-4fa72fbfbbe3f31e.yaml +++ /dev/null @@ -1,9 +0,0 @@ ---- -features: - - | - ``network create`` and ``network set`` now support - ``--transparent-vlan`` and ``--no-transparent-vlan`` - options to add/remove VLAN transparency attributes - from networks. - This option is available in Network V2 only. - [Bug `1545537 `_] \ No newline at end of file diff --git a/releasenotes/notes/add-server-group-command-fde165df53216726.yaml b/releasenotes/notes/bug-1542171-fde165df53216726.yaml similarity index 67% rename from releasenotes/notes/add-server-group-command-fde165df53216726.yaml rename to releasenotes/notes/bug-1542171-fde165df53216726.yaml index 63b7b7a4d7..b0f3a2569d 100644 --- a/releasenotes/notes/add-server-group-command-fde165df53216726.yaml +++ b/releasenotes/notes/bug-1542171-fde165df53216726.yaml @@ -1,7 +1,7 @@ --- features: - | - Add support for compute v2 ``server group`` related commands, include: - create, delete, list and show. + Add ``server group create``, ``server group delete``, + ``server group list``, ``server group show`` commands. [Bug `1542171 `_] [Blueprint `nova-server-group-support `_] diff --git a/releasenotes/notes/bug-1545537-4fa72fbfbbe3f31e.yaml b/releasenotes/notes/bug-1545537-4fa72fbfbbe3f31e.yaml new file mode 100644 index 0000000000..c38eac9651 --- /dev/null +++ b/releasenotes/notes/bug-1545537-4fa72fbfbbe3f31e.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Add ``--transparent-vlan`` and ``--no-transparent-vlan`` options to + ``network create`` and ``network set`` commands to add/remove VLAN + transparency attributes from networks. + This option is available in Network V2 only. + [Bug `1545537 `_] \ No newline at end of file diff --git a/releasenotes/notes/bug-1550999-5e352a71dfbc828d.yaml b/releasenotes/notes/bug-1550999-5e352a71dfbc828d.yaml new file mode 100644 index 0000000000..168da1a0cd --- /dev/null +++ b/releasenotes/notes/bug-1550999-5e352a71dfbc828d.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Adds ``volume service list`` command. + [Bug `1550999 `_] diff --git a/releasenotes/notes/bug-1566269-2572bca9157ca107.yaml b/releasenotes/notes/bug-1566269-2572bca9157ca107.yaml index 9f34f67c7b..6354bbf5ae 100644 --- a/releasenotes/notes/bug-1566269-2572bca9157ca107.yaml +++ b/releasenotes/notes/bug-1566269-2572bca9157ca107.yaml @@ -1,5 +1,5 @@ --- features: - - Add ``address scope create``,``address scope delete``,``address scope list``, + - Add ``address scope create``, ``address scope delete``, ``address scope list``, ``address scope set`` and ``address scope show`` commands. [Bug `1566269 `_] diff --git a/releasenotes/notes/volume_service-5e352a71dfbc828d.yaml b/releasenotes/notes/volume_service-5e352a71dfbc828d.yaml deleted file mode 100644 index 1cfa81cfae..0000000000 --- a/releasenotes/notes/volume_service-5e352a71dfbc828d.yaml +++ /dev/null @@ -1,9 +0,0 @@ ---- -features: - - | - Adds support for volume service list. - - An user can list available volume services by using - ``volume service list`` - - [Bug 1550999 'https://bugs.launchpad.net/python-openstackclient/+bug/1550999'_] From e44bb009d36bddc0fdfb6e949ddc293de291dcca Mon Sep 17 00:00:00 2001 From: Julien Danjou Date: Tue, 24 May 2016 15:56:27 +0200 Subject: [PATCH 0909/3095] keystone: fix catalog output when region is unset If no region is set in Keystone, null is deserialized as None and the region has None has value, which triggers a type error when building the output string. This patch fixes that. Change-Id: I7637dc2595655cf452f38308f99fe66ac782e16d --- openstackclient/identity/v2_0/catalog.py | 4 +++- openstackclient/identity/v3/catalog.py | 2 +- .../tests/identity/v2_0/test_catalog.py | 16 ++++++++++++++-- .../tests/identity/v3/test_catalog.py | 11 +++++++++-- 4 files changed, 27 insertions(+), 6 deletions(-) diff --git a/openstackclient/identity/v2_0/catalog.py b/openstackclient/identity/v2_0/catalog.py index 669b04f31a..53a6fe3451 100644 --- a/openstackclient/identity/v2_0/catalog.py +++ b/openstackclient/identity/v2_0/catalog.py @@ -25,7 +25,9 @@ def _format_endpoints(eps=None): return "" ret = '' for index, ep in enumerate(eps): - region = eps[index].get('region', '') + region = eps[index].get('region') + if region is None: + region = '' ret += region + '\n' for endpoint_type in ['publicURL', 'internalURL', 'adminURL']: url = eps[index].get(endpoint_type) diff --git a/openstackclient/identity/v3/catalog.py b/openstackclient/identity/v3/catalog.py index 38a57d900e..78d71f59ab 100644 --- a/openstackclient/identity/v3/catalog.py +++ b/openstackclient/identity/v3/catalog.py @@ -25,7 +25,7 @@ def _format_endpoints(eps=None): return "" ret = '' for ep in eps: - region = ep.get('region_id') or ep.get('region', '') + region = ep.get('region_id') or ep.get('region') or '' ret += region + '\n' ret += " %s: %s\n" % (ep['interface'], ep['url']) return ret diff --git a/openstackclient/tests/identity/v2_0/test_catalog.py b/openstackclient/tests/identity/v2_0/test_catalog.py index 1e27bb3cfc..d9ae6a80b6 100644 --- a/openstackclient/tests/identity/v2_0/test_catalog.py +++ b/openstackclient/tests/identity/v2_0/test_catalog.py @@ -36,6 +36,12 @@ class TestCatalog(utils.TestCommand): 'internalURL': 'https://internal.two.example.com', 'adminURL': 'https://admin.two.example.com', }, + { + 'region': None, + 'publicURL': 'https://public.none.example.com', + 'internalURL': 'https://internal.none.example.com', + 'adminURL': 'https://admin.none.example.com', + }, ], } @@ -87,7 +93,10 @@ def test_catalog_list(self): 'adminURL: https://admin.one.example.com\n' 'two\n publicURL: https://public.two.example.com\n ' 'internalURL: https://internal.two.example.com\n ' - 'adminURL: https://admin.two.example.com\n', + 'adminURL: https://admin.two.example.com\n' + '\n publicURL: https://public.none.example.com\n ' + 'internalURL: https://internal.none.example.com\n ' + 'adminURL: https://admin.none.example.com\n', ), ) self.assertEqual(datalist, tuple(data)) @@ -164,7 +173,10 @@ def test_catalog_show(self): 'adminURL: https://admin.one.example.com\n' 'two\n publicURL: https://public.two.example.com\n ' 'internalURL: https://internal.two.example.com\n ' - 'adminURL: https://admin.two.example.com\n', + 'adminURL: https://admin.two.example.com\n' + '\n publicURL: https://public.none.example.com\n ' + 'internalURL: https://internal.none.example.com\n ' + 'adminURL: https://admin.none.example.com\n', 'qwertyuiop', 'supernova', 'compute', diff --git a/openstackclient/tests/identity/v3/test_catalog.py b/openstackclient/tests/identity/v3/test_catalog.py index a03c9d3ead..1b8fa08586 100644 --- a/openstackclient/tests/identity/v3/test_catalog.py +++ b/openstackclient/tests/identity/v3/test_catalog.py @@ -38,6 +38,11 @@ class TestCatalog(utils.TestCommand): 'url': 'https://internal.example.com', 'interface': 'internal', }, + { + 'region': None, + 'url': 'https://none.example.com', + 'interface': 'none', + }, ], } @@ -81,7 +86,8 @@ def test_catalog_list(self): 'compute', 'onlyone\n public: https://public.example.com\n' 'onlyone\n admin: https://admin.example.com\n' - '\n internal: https://internal.example.com\n', + '\n internal: https://internal.example.com\n' + '\n none: https://none.example.com\n', ), ) self.assertEqual(datalist, tuple(data)) @@ -114,7 +120,8 @@ def test_catalog_show(self): datalist = ( 'onlyone\n public: https://public.example.com\nonlyone\n' ' admin: https://admin.example.com\n' - '\n internal: https://internal.example.com\n', + '\n internal: https://internal.example.com\n' + '\n none: https://none.example.com\n', 'qwertyuiop', 'supernova', 'compute', From 9e9e4e6f59f7d6a4e0fd8d96c4586d58a8f0059e Mon Sep 17 00:00:00 2001 From: sunyajing Date: Thu, 26 May 2016 16:52:54 +0800 Subject: [PATCH 0910/3095] fix endpoint show help endpoint show command can also work on service name or type or ID option Change-Id: I43c8df4bc093d4130cf33fd2520736ce9077dc82 --- doc/source/command-objects/endpoint.rst | 6 +++--- openstackclient/identity/v2_0/endpoint.py | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/doc/source/command-objects/endpoint.rst b/doc/source/command-objects/endpoint.rst index 074f20a0b4..9872a58737 100644 --- a/doc/source/command-objects/endpoint.rst +++ b/doc/source/command-objects/endpoint.rst @@ -188,9 +188,9 @@ Display endpoint details .. code:: bash os endpoint show - + .. _endpoint_show-endpoint: -.. describe:: +.. describe:: - Endpoint ID to display + Endpoint to display (endpoint ID, service ID, service name, service type) diff --git a/openstackclient/identity/v2_0/endpoint.py b/openstackclient/identity/v2_0/endpoint.py index e92f54127a..eabf341dc0 100644 --- a/openstackclient/identity/v2_0/endpoint.py +++ b/openstackclient/identity/v2_0/endpoint.py @@ -129,8 +129,9 @@ def get_parser(self, prog_name): parser = super(ShowEndpoint, self).get_parser(prog_name) parser.add_argument( 'endpoint_or_service', - metavar='', - help=_('Endpoint ID to display'), + metavar='', + help=_('Endpoint to display (endpoint ID, service ID,' + ' service name, service type)'), ) return parser From 5f950788ffe528f509fe88c70179ec5e732bff82 Mon Sep 17 00:00:00 2001 From: Alvaro Lopez Garcia Date: Fri, 27 May 2016 09:04:54 +0200 Subject: [PATCH 0911/3095] Do not require an scope when setting a password Changing the password in Keystone V3 is an unscoped operation, but we were requiring a scope. Change-Id: If0653ac7b59320c2cd9d42a2c73dd29c3626d389 Closes-Bug: 1543222 --- openstackclient/identity/v3/user.py | 2 ++ .../releasenotes/notes/bug-1543222-6f8579344ff5c958.yaml | 6 ++++++ 2 files changed, 8 insertions(+) create mode 100644 openstackclient/releasenotes/notes/bug-1543222-6f8579344ff5c958.yaml diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index 9a7ced9279..8bc4183b4d 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -369,6 +369,8 @@ def take_action(self, parsed_args): class SetPasswordUser(command.Command): """Change current user password""" + required_scope = False + def get_parser(self, prog_name): parser = super(SetPasswordUser, self).get_parser(prog_name) parser.add_argument( diff --git a/openstackclient/releasenotes/notes/bug-1543222-6f8579344ff5c958.yaml b/openstackclient/releasenotes/notes/bug-1543222-6f8579344ff5c958.yaml new file mode 100644 index 0000000000..c783d01309 --- /dev/null +++ b/openstackclient/releasenotes/notes/bug-1543222-6f8579344ff5c958.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - Keystone V3 `user password set` is a self-service operation. It should + not required a scoped token as it is not considered a `scoped operation`. + [Bug `1543222 `_] + From 62ec00ff3add4263d39d9330bbb4f638573e75e4 Mon Sep 17 00:00:00 2001 From: Sheel Rana Date: Fri, 27 May 2016 17:05:10 +0530 Subject: [PATCH 0912/3095] i18n support for leftover exception messages in volume Change-Id: I7be168fc587cb717075095cf6e12db19b0884dda --- openstackclient/volume/v2/volume_type.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index adaccb0a66..2261f4c35b 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -311,7 +311,7 @@ def take_action(self, parsed_args): volume_type.id, project_info.id) except Exception as e: self.app.log.error(_("Failed to remove volume type access from" - " project: ") + str(e)) + " project: %s") % str(e)) result += 1 if result > 0: From 460846cef28a502a51e4d52865743aea73e2f960 Mon Sep 17 00:00:00 2001 From: jichenjc Date: Sat, 20 Feb 2016 22:33:18 +0800 Subject: [PATCH 0913/3095] [compute] Add server backup function Add server backup function There is no return value for this command per following doc http://developer.openstack.org/api-ref-compute-v2.1.html#createBackup, also novaclient can't be updated now due to backward compatible issue http://lists.openstack.org/pipermail/openstack-dev/2016-March/089376.html, so we have to get the information ourselves. The Image tests were not using warlock images, so that needed to be fixed before we could completely test things like --wait. Change-Id: I30159518c4d3fdec89f15963bda641a0b03962d1 --- doc/source/command-objects/server-backup.rst | 44 +++ doc/source/commands.rst | 1 + openstackclient/compute/v2/server_backup.py | 134 +++++++++ .../tests/compute/v2/test_server_backup.py | 270 ++++++++++++++++++ openstackclient/tests/fakes.py | 3 + .../add-server-backup-e63feaebb6140f83.yaml | 4 + setup.cfg | 2 + 7 files changed, 458 insertions(+) create mode 100644 doc/source/command-objects/server-backup.rst create mode 100644 openstackclient/compute/v2/server_backup.py create mode 100644 openstackclient/tests/compute/v2/test_server_backup.py create mode 100644 releasenotes/notes/add-server-backup-e63feaebb6140f83.yaml diff --git a/doc/source/command-objects/server-backup.rst b/doc/source/command-objects/server-backup.rst new file mode 100644 index 0000000000..23e17d5f5d --- /dev/null +++ b/doc/source/command-objects/server-backup.rst @@ -0,0 +1,44 @@ +============= +server backup +============= + +A server backup is a disk image created in the Image store from a running server +instance. The backup command manages the number of archival copies to retain. + +Compute v2 + +server backup create +-------------------- + +Create a server backup image + +.. program:: server create +.. code:: bash + + os server backup create + [--name ] + [--type ] + [--rotate ] + [--wait] + + +.. option:: --name + + Name of the backup image (default: server name) + +.. option:: --type + + Used to populate the ``backup_type`` property of the backup + image (default: empty) + +.. option:: --rotate + + Number of backup images to keep (default: 1) + +.. option:: --wait + + Wait for operation to complete + +.. describe:: + + Server to back up (name or ID) diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 12542d1c73..a6d9404790 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -118,6 +118,7 @@ referring to both Compute and Volume quotas. * ``security group``: (**Compute**, **Network**) - groups of network access rules * ``security group rule``: (**Compute**, **Network**) - the individual rules that define protocol/IP/port access * ``server``: (**Compute**) virtual machine instance +* ``server backup``: (**Compute**) backup server disk image by using snapshot method * ``server dump``: (**Compute**) a dump file of a server created by features like kdump * ``server group``: (**Compute**) a grouping of servers * ``server image``: (**Compute**) saved server disk image diff --git a/openstackclient/compute/v2/server_backup.py b/openstackclient/compute/v2/server_backup.py new file mode 100644 index 0000000000..24d7101521 --- /dev/null +++ b/openstackclient/compute/v2/server_backup.py @@ -0,0 +1,134 @@ +# Copyright 2012-2013 OpenStack Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +"""Compute v2 Server action implementations""" + +import sys + +from oslo_utils import importutils +import six + +from openstackclient.common import command +from openstackclient.common import exceptions +from openstackclient.common import utils +from openstackclient.i18n import _ + + +def _show_progress(progress): + if progress: + sys.stderr.write('\rProgress: %s' % progress) + sys.stderr.flush() + + +class CreateServerBackup(command.ShowOne): + """Create a server backup image""" + + IMAGE_API_VERSIONS = { + "1": "openstackclient.image.v1.image", + "2": "openstackclient.image.v2.image", + } + + def get_parser(self, prog_name): + parser = super(CreateServerBackup, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + help=_('Server to back up (name or ID)'), + ) + parser.add_argument( + '--name', + metavar='', + help=_('Name of the backup image (default: server name)'), + ) + parser.add_argument( + '--type', + metavar='', + help=_( + 'Used to populate the backup_type property of the backup ' + 'image (default: empty)' + ), + ) + parser.add_argument( + '--rotate', + metavar='', + type=int, + help=_('Number of backups to keep (default: 1)'), + ) + parser.add_argument( + '--wait', + action='store_true', + help=_('Wait for backup image create to complete'), + ) + return parser + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + + server = utils.find_resource( + compute_client.servers, + parsed_args.server, + ) + + # Set sane defaults as this API wants all mouths to be fed + if parsed_args.name is None: + backup_name = server.name + else: + backup_name = parsed_args.name + if parsed_args.type is None: + backup_type = "" + else: + backup_type = parsed_args.type + if parsed_args.rotate is None: + backup_rotation = 1 + else: + backup_rotation = parsed_args.rotate + + compute_client.servers.backup( + server.id, + backup_name, + backup_type, + backup_rotation, + ) + + image_client = self.app.client_manager.image + image = utils.find_resource( + image_client.images, + backup_name, + ) + + if parsed_args.wait: + if utils.wait_for_status( + image_client.images.get, + image.id, + callback=_show_progress, + ): + sys.stdout.write('\n') + else: + msg = _('Error creating server backup: %s') % parsed_args.name + raise exceptions.CommandError(msg) + + if self.app.client_manager._api_version['image'] == '1': + info = {} + info.update(image._info) + info['properties'] = utils.format_dict(info.get('properties', {})) + else: + # Get the right image module to format the output + image_module = importutils.import_module( + self.IMAGE_API_VERSIONS[ + self.app.client_manager._api_version['image'] + ] + ) + info = image_module._format_image(image) + return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/tests/compute/v2/test_server_backup.py b/openstackclient/tests/compute/v2/test_server_backup.py new file mode 100644 index 0000000000..b35f9f52a8 --- /dev/null +++ b/openstackclient/tests/compute/v2/test_server_backup.py @@ -0,0 +1,270 @@ +# 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.common import exceptions +from openstackclient.common import utils as common_utils +from openstackclient.compute.v2 import server_backup +from openstackclient.tests.compute.v2 import fakes as compute_fakes +from openstackclient.tests.image.v2 import fakes as image_fakes + + +class TestServerBackup(compute_fakes.TestComputev2): + + def setUp(self): + super(TestServerBackup, self).setUp() + + # Get a shortcut to the compute client ServerManager Mock + self.servers_mock = self.app.client_manager.compute.servers + self.servers_mock.reset_mock() + + # Get a shortcut to the image client ImageManager Mock + self.images_mock = self.app.client_manager.image.images + self.images_mock.reset_mock() + + # Set object attributes to be tested. Could be overwriten in subclass. + self.attrs = {} + + # Set object methods to be tested. Could be overwriten in subclass. + self.methods = {} + + def setup_servers_mock(self, count): + servers = compute_fakes.FakeServer.create_servers( + attrs=self.attrs, + methods=self.methods, + count=count, + ) + + # This is the return value for utils.find_resource() + self.servers_mock.get = compute_fakes.FakeServer.get_servers( + servers, + 0, + ) + return servers + + +class TestServerBackupCreate(TestServerBackup): + + # Just return whatever Image is testing with these days + def image_columns(self, image): + columnlist = tuple(sorted(image.keys())) + return columnlist + + def image_data(self, image): + datalist = ( + image['id'], + image['name'], + image['owner'], + image['protected'], + 'active', + common_utils.format_list(image.get('tags')), + image['visibility'], + ) + return datalist + + def setUp(self): + super(TestServerBackupCreate, self).setUp() + + # Get the command object to test + self.cmd = server_backup.CreateServerBackup(self.app, None) + + self.methods = { + 'backup': None, + } + + def setup_images_mock(self, count, servers=None): + if servers: + images = image_fakes.FakeImage.create_images( + attrs={ + 'name': servers[0].name, + 'status': 'active', + }, + count=count, + ) + else: + images = image_fakes.FakeImage.create_images( + attrs={ + 'status': 'active', + }, + count=count, + ) + + self.images_mock.get = mock.MagicMock(side_effect=images) + return images + + def test_server_backup_defaults(self): + servers = self.setup_servers_mock(count=1) + images = self.setup_images_mock(count=1, servers=servers) + + arglist = [ + servers[0].id, + ] + verifylist = [ + ('name', None), + ('type', None), + ('rotate', None), + ('wait', False), + ('server', servers[0].id), + ] + 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) + + # ServerManager.backup(server, backup_name, backup_type, rotation) + self.servers_mock.backup.assert_called_with( + servers[0].id, + servers[0].name, + '', + 1, + ) + + self.assertEqual(self.image_columns(images[0]), columns) + self.assertEqual(self.image_data(images[0]), data) + + def test_server_backup_create_options(self): + servers = self.setup_servers_mock(count=1) + images = self.setup_images_mock(count=1, servers=servers) + + arglist = [ + '--name', 'image', + '--type', 'daily', + '--rotate', '2', + servers[0].id, + ] + verifylist = [ + ('name', 'image'), + ('type', 'daily'), + ('rotate', 2), + ('server', servers[0].id), + ] + 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) + + # ServerManager.backup(server, backup_name, backup_type, rotation) + self.servers_mock.backup.assert_called_with( + servers[0].id, + 'image', + 'daily', + 2, + ) + + self.assertEqual(self.image_columns(images[0]), columns) + self.assertEqual(self.image_data(images[0]), data) + + @mock.patch.object(common_utils, 'wait_for_status', return_value=False) + def test_server_backup_wait_fail(self, mock_wait_for_status): + servers = self.setup_servers_mock(count=1) + images = image_fakes.FakeImage.create_images( + attrs={ + 'name': servers[0].name, + 'status': 'active', + }, + count=5, + ) + + self.images_mock.get = mock.MagicMock( + side_effect=images, + ) + + arglist = [ + '--name', 'image', + '--type', 'daily', + '--wait', + servers[0].id, + ] + verifylist = [ + ('name', 'image'), + ('type', 'daily'), + ('wait', True), + ('server', servers[0].id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args, + ) + + # ServerManager.backup(server, backup_name, backup_type, rotation) + self.servers_mock.backup.assert_called_with( + servers[0].id, + 'image', + 'daily', + 1, + ) + + mock_wait_for_status.assert_called_once_with( + self.images_mock.get, + images[0].id, + callback=mock.ANY + ) + + @mock.patch.object(common_utils, 'wait_for_status', return_value=True) + def test_server_backup_wait_ok(self, mock_wait_for_status): + servers = self.setup_servers_mock(count=1) + images = image_fakes.FakeImage.create_images( + attrs={ + 'name': servers[0].name, + 'status': 'active', + }, + count=5, + ) + + self.images_mock.get = mock.MagicMock( + side_effect=images, + ) + + arglist = [ + '--name', 'image', + '--type', 'daily', + '--wait', + servers[0].id, + ] + verifylist = [ + ('name', 'image'), + ('type', 'daily'), + ('wait', True), + ('server', servers[0].id), + ] + 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) + + # ServerManager.backup(server, backup_name, backup_type, rotation) + self.servers_mock.backup.assert_called_with( + servers[0].id, + 'image', + 'daily', + 1, + ) + + mock_wait_for_status.assert_called_once_with( + self.images_mock.get, + images[0].id, + callback=mock.ANY + ) + + self.assertEqual(self.image_columns(images[0]), columns) + self.assertEqual(self.image_data(images[0]), data) diff --git a/openstackclient/tests/fakes.py b/openstackclient/tests/fakes.py index 229b46529c..fb7a957af4 100644 --- a/openstackclient/tests/fakes.py +++ b/openstackclient/tests/fakes.py @@ -105,6 +105,9 @@ def __init__(self, **kwargs): class FakeClientManager(object): + _api_version = { + 'image': '2', + } def __init__(self): self.compute = None diff --git a/releasenotes/notes/add-server-backup-e63feaebb6140f83.yaml b/releasenotes/notes/add-server-backup-e63feaebb6140f83.yaml new file mode 100644 index 0000000000..f8aa4291fb --- /dev/null +++ b/releasenotes/notes/add-server-backup-e63feaebb6140f83.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Add support for the ``server backup create`` command diff --git a/setup.cfg b/setup.cfg index a62f5d25cd..6a2ce01db7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -131,6 +131,8 @@ openstack.compute.v2 = server_unset = openstackclient.compute.v2.server:UnsetServer server_unshelve = openstackclient.compute.v2.server:UnshelveServer + server_backup_create = openstackclient.compute.v2.server_backup:CreateServerBackup + server_group_create = openstackclient.compute.v2.server_group:CreateServerGroup server_group_delete = openstackclient.compute.v2.server_group:DeleteServerGroup server_group_list = openstackclient.compute.v2.server_group:ListServerGroup From 6a55e05cbf72509898ccc490ca894d766f34d9dc Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Tue, 8 Mar 2016 15:18:16 -0600 Subject: [PATCH 0914/3095] Add network segment command object Add network segment command object in support of routed networks. This patch set includes documentation, unit tests and functional tests (currently skipped until segments enabled in neutron by default) for the following new commands: - "os network segment list" - "os network segment show" These new commands are currently marked as beta commands. Change-Id: I1a79b48dc6820fe2a39fcceb11c8cae3bda413a0 Partially-Implements: blueprint routed-networks --- .../command-objects/network-segment.rst | 54 +++++ doc/source/commands.rst | 1 + .../tests/network/v2/test_network_segment.py | 60 ++++++ openstackclient/network/v2/network_segment.py | 124 +++++++++++ openstackclient/tests/fakes.py | 5 + openstackclient/tests/network/v2/fakes.py | 52 +++++ .../tests/network/v2/test_network_segment.py | 199 ++++++++++++++++++ openstackclient/tests/utils.py | 1 + .../bp-routed-networks-3eea4978c93aa126.yaml | 7 + setup.cfg | 3 + 10 files changed, 506 insertions(+) create mode 100644 doc/source/command-objects/network-segment.rst create mode 100644 functional/tests/network/v2/test_network_segment.py create mode 100644 openstackclient/network/v2/network_segment.py create mode 100644 openstackclient/tests/network/v2/test_network_segment.py create mode 100644 releasenotes/notes/bp-routed-networks-3eea4978c93aa126.yaml diff --git a/doc/source/command-objects/network-segment.rst b/doc/source/command-objects/network-segment.rst new file mode 100644 index 0000000000..8e177d6af5 --- /dev/null +++ b/doc/source/command-objects/network-segment.rst @@ -0,0 +1,54 @@ +=============== +network segment +=============== + +A **network segment** is an isolated Layer 2 segment within a network. +A network may contain multiple network segments. Depending on the +network configuration, Layer 2 connectivity between network segments +within a network may not be guaranteed. + +Network v2 + +network segment list +-------------------- + +List network segments + +.. caution:: This is a beta command and subject to change. + Use global option ``--enable-beta-commands`` to + enable this command. + +.. program:: network segment list +.. code:: bash + + os network segment list + [--long] + [--network ] + +.. option:: --long + + List additional fields in output + +.. option:: --network + + List network segments that belong to this network (name or ID) + +network segment show +-------------------- + +Display network segment details + +.. caution:: This is a beta command and subject to change. + Use global option ``--enable-beta-commands`` to + enable this command. + +.. program:: network segment show +.. code:: bash + + os network segment show + + +.. _network_segment_show-segment: +.. describe:: + + Network segment to display (ID only) diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 12542d1c73..118fd5d5ba 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -104,6 +104,7 @@ referring to both Compute and Volume quotas. * ``mapping``: (**Identity**) a definition to translate identity provider attributes to Identity concepts * ``module``: (**Internal**) - installed Python modules in the OSC process * ``network``: (**Compute**, **Network**) - a virtual network for connecting servers and other resources +* ``network segment``: (**Network**) - a segment of a virtual network * ``object``: (**Object Storage**) a single file in the Object Storage * ``object store account``: (**Object Storage**) owns a group of Object Storage resources * ``policy``: (**Identity**) determines authorization diff --git a/functional/tests/network/v2/test_network_segment.py b/functional/tests/network/v2/test_network_segment.py new file mode 100644 index 0000000000..a998093811 --- /dev/null +++ b/functional/tests/network/v2/test_network_segment.py @@ -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. + +import testtools +import uuid + +from functional.common import test + + +# NOTE(rtheis): Routed networks is still a WIP and not enabled by default. +@testtools.skip("bp/routed-networks") +class NetworkSegmentTests(test.TestCase): + """Functional tests for network segment. """ + NETWORK_NAME = uuid.uuid4().hex + PHYSICAL_NETWORK_NAME = uuid.uuid4().hex + NETWORK_SEGMENT_ID = None + NETWORK_ID = None + + @classmethod + def setUpClass(cls): + # Create a network for the segment. + opts = cls.get_show_opts(['id']) + raw_output = cls.openstack('network create ' + cls.NETWORK_NAME + opts) + cls.NETWORK_ID = raw_output.strip('\n') + + # Get the segment for the network. + opts = cls.get_show_opts(['ID', 'Network']) + raw_output = cls.openstack('--enable-beta-commands ' + 'network segment list ' + ' --network ' + cls.NETWORK_NAME + + ' ' + opts) + raw_output_row = raw_output.split('\n')[0] + cls.NETWORK_SEGMENT_ID = raw_output_row.split(' ')[0] + + @classmethod + def tearDownClass(cls): + raw_output = cls.openstack('network delete ' + cls.NETWORK_NAME) + cls.assertOutput('', raw_output) + + def test_network_segment_list(self): + opts = self.get_list_opts(['ID']) + raw_output = self.openstack('--enable-beta-commands ' + 'network segment list' + opts) + self.assertIn(self.NETWORK_SEGMENT_ID, raw_output) + + def test_network_segment_show(self): + opts = self.get_show_opts(['network_id']) + raw_output = self.openstack('--enable-beta-commands ' + 'network segment show ' + + self.NETWORK_SEGMENT_ID + opts) + self.assertEqual(self.NETWORK_ID + "\n", raw_output) diff --git a/openstackclient/network/v2/network_segment.py b/openstackclient/network/v2/network_segment.py new file mode 100644 index 0000000000..d8a91fd200 --- /dev/null +++ b/openstackclient/network/v2/network_segment.py @@ -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. +# + +"""Network segment action implementations""" + +# TODO(rtheis): Add description and name properties when support is available. + +from openstackclient.common import command +from openstackclient.common import exceptions +from openstackclient.common import utils +from openstackclient.i18n import _ + + +class ListNetworkSegment(command.Lister): + """List network segments + + (Caution: This is a beta command and subject to change. + Use global option --enable-beta-commands to enable + this command) + """ + + def get_parser(self, prog_name): + parser = super(ListNetworkSegment, self).get_parser(prog_name) + parser.add_argument( + '--long', + action='store_true', + default=False, + help=_('List additional fields in output'), + ) + parser.add_argument( + '--network', + metavar='', + help=_('List network segments that belong to this ' + 'network (name or ID)'), + ) + return parser + + def take_action(self, parsed_args): + if not self.app.options.enable_beta_commands: + msg = _('Caution: This is a beta command and subject to ' + 'change. Use global option --enable-beta-commands ' + 'to enable this command.') + raise exceptions.CommandError(msg) + + network_client = self.app.client_manager.network + + filters = {} + if parsed_args.network: + _network = network_client.find_network( + parsed_args.network, + ignore_missing=False + ) + filters = {'network_id': _network.id} + data = network_client.segments(**filters) + + headers = ( + 'ID', + 'Network', + 'Network Type', + 'Segment', + ) + columns = ( + 'id', + 'network_id', + 'network_type', + 'segmentation_id', + ) + if parsed_args.long: + headers = headers + ( + 'Physical Network', + ) + columns = columns + ( + 'physical_network', + ) + + return (headers, + (utils.get_item_properties( + s, columns, + formatters={}, + ) for s in data)) + + +class ShowNetworkSegment(command.ShowOne): + """Display network segment details + + (Caution: This is a beta command and subject to change. + Use global option --enable-beta-commands to enable + this command) + """ + + def get_parser(self, prog_name): + parser = super(ShowNetworkSegment, self).get_parser(prog_name) + parser.add_argument( + 'network_segment', + metavar='', + help=_('Network segment to display (ID only)'), + ) + return parser + + def take_action(self, parsed_args): + if not self.app.options.enable_beta_commands: + msg = _('Caution: This is a beta command and subject to ' + 'change. Use global option --enable-beta-commands ' + 'to enable this command.') + raise exceptions.CommandError(msg) + + client = self.app.client_manager.network + obj = client.find_segment( + parsed_args.network_segment, + ignore_missing=False + ) + columns = tuple(sorted(obj.keys())) + data = utils.get_item_properties(obj, columns) + return (columns, data) diff --git a/openstackclient/tests/fakes.py b/openstackclient/tests/fakes.py index 229b46529c..ac91257ebe 100644 --- a/openstackclient/tests/fakes.py +++ b/openstackclient/tests/fakes.py @@ -97,6 +97,11 @@ def __init__(self, _stdout, _log): self.log = _log +class FakeOptions(object): + def __init__(self, **kwargs): + self.enable_beta_commands = False + + class FakeClient(object): def __init__(self, **kwargs): diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 417cf26ee7..8507e27804 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -256,6 +256,58 @@ def get_networks(networks=None, count=2): return mock.MagicMock(side_effect=networks) +class FakeNetworkSegment(object): + """Fake one or more network segments.""" + + @staticmethod + def create_one_network_segment(attrs=None): + """Create a fake network segment. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object faking the network segment + """ + attrs = attrs or {} + + # Set default attributes. + network_segment_attrs = { + 'id': 'segment-id-' + uuid.uuid4().hex, + 'network_id': 'network-id-' + uuid.uuid4().hex, + 'network_type': 'vlan', + 'physical_network': 'physical-network-name-' + uuid.uuid4().hex, + 'segmentation_id': 1024, + } + + # Overwrite default attributes. + network_segment_attrs.update(attrs) + + network_segment = fakes.FakeResource( + info=copy.deepcopy(network_segment_attrs), + loaded=True + ) + + return network_segment + + @staticmethod + def create_network_segments(attrs=None, count=2): + """Create multiple fake network segments. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of network segments to fake + :return: + A list of FakeResource objects faking the network segments + """ + network_segments = [] + for i in range(0, count): + network_segments.append( + FakeNetworkSegment.create_one_network_segment(attrs) + ) + return network_segments + + class FakePort(object): """Fake one or more ports.""" diff --git a/openstackclient/tests/network/v2/test_network_segment.py b/openstackclient/tests/network/v2/test_network_segment.py new file mode 100644 index 0000000000..2822581c67 --- /dev/null +++ b/openstackclient/tests/network/v2/test_network_segment.py @@ -0,0 +1,199 @@ +# 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.common import exceptions +from openstackclient.network.v2 import network_segment +from openstackclient.tests.network.v2 import fakes as network_fakes +from openstackclient.tests import utils as tests_utils + + +class TestNetworkSegment(network_fakes.TestNetworkV2): + + def setUp(self): + super(TestNetworkSegment, self).setUp() + + # Enable beta commands. + self.app.options.enable_beta_commands = True + + # Get a shortcut to the network client + self.network = self.app.client_manager.network + + +class TestListNetworkSegment(TestNetworkSegment): + _network = network_fakes.FakeNetwork.create_one_network() + _network_segments = \ + network_fakes.FakeNetworkSegment.create_network_segments(count=3) + + columns = ( + 'ID', + 'Network', + 'Network Type', + 'Segment', + ) + columns_long = columns + ( + 'Physical Network', + ) + + data = [] + for _network_segment in _network_segments: + data.append(( + _network_segment.id, + _network_segment.network_id, + _network_segment.network_type, + _network_segment.segmentation_id, + )) + + data_long = [] + for _network_segment in _network_segments: + data_long.append(( + _network_segment.id, + _network_segment.network_id, + _network_segment.network_type, + _network_segment.segmentation_id, + _network_segment.physical_network, + )) + + def setUp(self): + super(TestListNetworkSegment, self).setUp() + + # Get the command object to test + self.cmd = network_segment.ListNetworkSegment(self.app, self.namespace) + + self.network.find_network = mock.Mock(return_value=self._network) + self.network.segments = mock.Mock(return_value=self._network_segments) + + def test_list_no_option(self): + arglist = [] + verifylist = [ + ('long', False), + ('network', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.segments.assert_called_once_with() + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_list_no_beta_commands(self): + self.app.options.enable_beta_commands = False + parsed_args = self.check_parser(self.cmd, [], []) + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + + def test_list_long(self): + arglist = [ + '--long', + ] + verifylist = [ + ('long', True), + ('network', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.segments.assert_called_once_with() + self.assertEqual(self.columns_long, columns) + self.assertEqual(self.data_long, list(data)) + + def test_list_network(self): + arglist = [ + '--network', + self._network.id, + ] + verifylist = [ + ('long', False), + ('network', self._network.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.segments.assert_called_once_with( + **{'network_id': self._network.id} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + +class TestShowNetworkSegment(TestNetworkSegment): + + # The network segment to show. + _network_segment = \ + network_fakes.FakeNetworkSegment.create_one_network_segment() + + columns = ( + 'id', + 'network_id', + 'network_type', + 'physical_network', + 'segmentation_id', + ) + + data = ( + _network_segment.id, + _network_segment.network_id, + _network_segment.network_type, + _network_segment.physical_network, + _network_segment.segmentation_id, + ) + + def setUp(self): + super(TestShowNetworkSegment, self).setUp() + + self.network.find_segment = mock.Mock( + return_value=self._network_segment + ) + + # Get the command object to test + self.cmd = network_segment.ShowNetworkSegment(self.app, self.namespace) + + def test_show_no_options(self): + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, [], []) + + def test_show_no_beta_commands(self): + arglist = [ + self._network_segment.id, + ] + verifylist = [ + ('network_segment', self._network_segment.id), + ] + self.app.options.enable_beta_commands = False + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + + def test_show_all_options(self): + arglist = [ + self._network_segment.id, + ] + verifylist = [ + ('network_segment', self._network_segment.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.find_segment.assert_called_once_with( + self._network_segment.id, + ignore_missing=False + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) diff --git a/openstackclient/tests/utils.py b/openstackclient/tests/utils.py index 319c1c1142..8dead71840 100644 --- a/openstackclient/tests/utils.py +++ b/openstackclient/tests/utils.py @@ -60,6 +60,7 @@ def setUp(self): self.fake_log = fakes.FakeLog() self.app = fakes.FakeApp(self.fake_stdout, self.fake_log) self.app.client_manager = fakes.FakeClientManager() + self.app.options = fakes.FakeOptions() def check_parser(self, cmd, args, verify_args): cmd_parser = cmd.get_parser('check_parser') diff --git a/releasenotes/notes/bp-routed-networks-3eea4978c93aa126.yaml b/releasenotes/notes/bp-routed-networks-3eea4978c93aa126.yaml new file mode 100644 index 0000000000..82080f7e6a --- /dev/null +++ b/releasenotes/notes/bp-routed-networks-3eea4978c93aa126.yaml @@ -0,0 +1,7 @@ +--- +features: + - Add support for the ``network segment`` command object via the + ``network segment list`` and ``network segment show`` commands. + These are beta commands and subject to change. Use global option + ``--enable-beta-commands`` to enable these commands. + [Blueprint `routed-networks `_] diff --git a/setup.cfg b/setup.cfg index a62f5d25cd..9492de70df 100644 --- a/setup.cfg +++ b/setup.cfg @@ -341,6 +341,9 @@ openstack.network.v2 = network_set = openstackclient.network.v2.network:SetNetwork network_show = openstackclient.network.v2.network:ShowNetwork + network_segment_list = openstackclient.network.v2.network_segment:ListNetworkSegment + network_segment_show = openstackclient.network.v2.network_segment:ShowNetworkSegment + port_create = openstackclient.network.v2.port:CreatePort port_delete = openstackclient.network.v2.port:DeletePort port_list = openstackclient.network.v2.port:ListPort From b2a0e633eb506727209d72a8063eac4c3f40546c Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Sat, 28 May 2016 13:21:39 +0800 Subject: [PATCH 0915/3095] Fix help message for "server group delete" command "Server group delete" command supported deleting multi server groups, but the help message was "Delete an existing server group", so I change it in this patch. Change-Id: I05b1a7e3f29b84b9190e7cc05c01734e3daa7a6d --- doc/source/command-objects/server-group.rst | 2 +- openstackclient/compute/v2/server_group.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/command-objects/server-group.rst b/doc/source/command-objects/server-group.rst index 55d789c746..414f49b1aa 100644 --- a/doc/source/command-objects/server-group.rst +++ b/doc/source/command-objects/server-group.rst @@ -31,7 +31,7 @@ Create a new server group server group delete ------------------- -Delete an existing server group +Delete existing server group(s) .. program:: server group delete .. code-block:: bash diff --git a/openstackclient/compute/v2/server_group.py b/openstackclient/compute/v2/server_group.py index 973095ca98..7baa6fe74f 100644 --- a/openstackclient/compute/v2/server_group.py +++ b/openstackclient/compute/v2/server_group.py @@ -71,7 +71,7 @@ def take_action(self, parsed_args): class DeleteServerGroup(command.Command): - """Delete an existing server group.""" + """Delete existing server group(s).""" def get_parser(self, prog_name): parser = super(DeleteServerGroup, self).get_parser(prog_name) From 366adf98c0e28c7538a6d6cb8349eb3033addfda Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Mon, 23 May 2016 14:04:50 +0800 Subject: [PATCH 0916/3095] Fix functest "test_server_metadata()" in test_sever.py There is only a functional test for "server set" command in test_server_metadata(), we also need a functional test for "server unset" command, so I add it in this patch. Change-Id: I23c40ac1c5adcc2563d8aa9dcb5551df695e98e3 --- functional/tests/compute/v2/test_server.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/functional/tests/compute/v2/test_server.py b/functional/tests/compute/v2/test_server.py index 6cb82cb04f..96c1c1a50b 100644 --- a/functional/tests/compute/v2/test_server.py +++ b/functional/tests/compute/v2/test_server.py @@ -129,6 +129,8 @@ def test_server_metadata(self): 1) Boot server in setUp 2) Set properties for server 3) Check server properties in server show output + 4) Unset properties for server + 5) Check server properties in server show output """ self.wait_for_status("ACTIVE") # metadata @@ -138,6 +140,12 @@ def test_server_metadata(self): raw_output = self.openstack('server show ' + self.NAME + opts) self.assertEqual(self.NAME + "\na='b', c='d'\n", raw_output) + raw_output = self.openstack( + 'server unset --property a ' + self.NAME) + opts = self.get_show_opts(["name", "properties"]) + raw_output = self.openstack('server show ' + self.NAME + opts) + self.assertEqual(self.NAME + "\nc='d'\n", raw_output) + def test_server_suspend_resume(self): """Test server suspend and resume commands. From 537f5cbe8ac9439351afe365360eeb38fb340fcb Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Sat, 28 May 2016 12:50:14 +0800 Subject: [PATCH 0917/3095] Support deleting multi address scopes in networkv2 This patch adds support for deleting multi address scopes by using "address scope delete" command. Change-Id: Ic8d3ebc17db44ca5d42c336d2c4d5633f70d4e8b Partially-Implements: blueprint multi-argument-network --- doc/source/command-objects/address-scope.rst | 6 +- openstackclient/network/v2/address_scope.py | 24 +++++-- openstackclient/tests/network/v2/fakes.py | 19 +++++ .../tests/network/v2/test_address_scope.py | 70 ++++++++++++++++--- 4 files changed, 104 insertions(+), 15 deletions(-) diff --git a/doc/source/command-objects/address-scope.rst b/doc/source/command-objects/address-scope.rst index a2ab566c82..7481ed53de 100644 --- a/doc/source/command-objects/address-scope.rst +++ b/doc/source/command-objects/address-scope.rst @@ -50,18 +50,18 @@ Create new address scope address scope delete -------------------- -Delete an address scope +Delete address scope(s) .. program:: address scope delete .. code:: bash os address scope delete - + [ ...] .. _address_scope_delete-address-scope: .. describe:: - Address scope to delete (name or ID) + Address scope(s) to delete (name or ID) address scope list ------------------ diff --git a/openstackclient/network/v2/address_scope.py b/openstackclient/network/v2/address_scope.py index 614900c92e..dbc6865fb4 100644 --- a/openstackclient/network/v2/address_scope.py +++ b/openstackclient/network/v2/address_scope.py @@ -98,22 +98,38 @@ def take_action(self, parsed_args): class DeleteAddressScope(command.Command): - """Delete an address scope""" + """Delete address scope(s)""" def get_parser(self, prog_name): parser = super(DeleteAddressScope, self).get_parser(prog_name) parser.add_argument( 'address_scope', metavar="", - help=_("Address scope to delete (name or ID)") + nargs='+', + help=_("Address scope(s) to delete (name or ID)") ) return parser def take_action(self, parsed_args): client = self.app.client_manager.network - obj = client.find_address_scope(parsed_args.address_scope) - client.delete_address_scope(obj) + result = 0 + + for scope in parsed_args.address_scope: + try: + obj = client.find_address_scope(scope, ignore_missing=False) + client.delete_address_scope(obj) + except Exception as e: + result += 1 + self.app.log.error(_("Failed to delete address scope with " + "name or ID '%(scope)s': %(e)s") + % {'scope': scope, 'e': e}) + + if result > 0: + total = len(parsed_args.address_scope) + msg = (_("%(result)s of %(total)s address scopes failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListAddressScope(command.Lister): diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 417cf26ee7..ce6f63e8c9 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -127,6 +127,25 @@ def create_address_scopes(attrs=None, count=2): return address_scopes + @staticmethod + def get_address_scopes(address_scopes=None, count=2): + """Get an iterable MagicMock object with a list of faked address scopes. + + If address scopes list is provided, then initialize the Mock object + with the list. Otherwise create one. + + :param List address scopes: + A list of FakeResource objects faking address scopes + :param int count: + The number of address scopes to fake + :return: + An iterable Mock object with side_effect set to a list of faked + address scopes + """ + if address_scopes is None: + address_scopes = FakeAddressScope.create_address_scopes(count) + return mock.MagicMock(side_effect=address_scopes) + class FakeAvailabilityZone(object): """Fake one or more network availability zones (AZs).""" diff --git a/openstackclient/tests/network/v2/test_address_scope.py b/openstackclient/tests/network/v2/test_address_scope.py index ac94b489b1..b4f4fa885f 100644 --- a/openstackclient/tests/network/v2/test_address_scope.py +++ b/openstackclient/tests/network/v2/test_address_scope.py @@ -14,6 +14,7 @@ import copy import mock +from mock import call from openstackclient.common import exceptions from openstackclient.network.v2 import address_scope from openstackclient.tests import fakes @@ -168,33 +169,86 @@ def test_create_no_share(self): class TestDeleteAddressScope(TestAddressScope): # The address scope to delete. - _address_scope = ( - network_fakes.FakeAddressScope.create_one_address_scope()) + _address_scopes = ( + network_fakes.FakeAddressScope.create_address_scopes(count=2)) def setUp(self): super(TestDeleteAddressScope, self).setUp() self.network.delete_address_scope = mock.Mock(return_value=None) - self.network.find_address_scope = mock.Mock( - return_value=self._address_scope) + self.network.find_address_scope = ( + network_fakes.FakeAddressScope.get_address_scopes( + address_scopes=self._address_scopes) + ) # Get the command object to test self.cmd = address_scope.DeleteAddressScope(self.app, self.namespace) - def test_delete(self): + def test_address_scope_delete(self): arglist = [ - self._address_scope.name, + self._address_scopes[0].name, ] verifylist = [ - ('address_scope', self._address_scope.name), + ('address_scope', [self._address_scopes[0].name]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) + self.network.find_address_scope.assert_called_once_with( + self._address_scopes[0].name, ignore_missing=False) self.network.delete_address_scope.assert_called_once_with( - self._address_scope) + self._address_scopes[0]) self.assertIsNone(result) + def test_multi_address_scopes_delete(self): + arglist = [] + verifylist = [] + + for a in self._address_scopes: + arglist.append(a.name) + verifylist = [ + ('address_scope', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [] + for a in self._address_scopes: + calls.append(call(a)) + self.network.delete_address_scope.assert_has_calls(calls) + self.assertIsNone(result) + + def test_multi_address_scopes_delete_with_exception(self): + arglist = [ + self._address_scopes[0].name, + 'unexist_address_scope', + ] + verifylist = [ + ('address_scope', + [self._address_scopes[0].name, 'unexist_address_scope']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self._address_scopes[0], exceptions.CommandError] + self.network.find_address_scope = ( + mock.MagicMock(side_effect=find_mock_result) + ) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 address scopes failed to delete.', str(e)) + + self.network.find_address_scope.assert_any_call( + self._address_scopes[0].name, ignore_missing=False) + self.network.find_address_scope.assert_any_call( + 'unexist_address_scope', ignore_missing=False) + self.network.delete_address_scope.assert_called_once_with( + self._address_scopes[0] + ) + class TestListAddressScope(TestAddressScope): From 97162ecd2277a3b50bbe3c7d672438d75b23b43b Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 28 May 2016 15:46:32 +0800 Subject: [PATCH 0918/3095] Fix output and error log in server.py This patch fixes 2 problems: 1. The '\n' should be in the end of a message, not the beginning. i.e.: The original code was: sys.stdout.write(_('\nError deleting server')) It will make the output look like this: [root@tangchen /]# openstack server delete aaa bbb Error deleting server Error deleting server[root@tangchen /]# We change it to: sys.stdout.write(_('Error deleting server\n')) Then the output will become: [root@tangchen /]# openstack server delete aaa bbb Error deleting server Error deleting server [root@tangchen /]# which is much better. 2. Record the error in log for those who didn't. Change-Id: I38b00c2321014757970183205f95f026e20a8090 --- openstackclient/compute/v2/server.py | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 781ccb1b8e..5ab48f047e 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -562,7 +562,7 @@ def take_action(self, parsed_args): else: self.log.error(_('Error creating server: %s'), parsed_args.server_name) - sys.stdout.write(_('\nError creating server')) + sys.stdout.write(_('Error creating server\n')) raise SystemExit details = _prep_server_detail(compute_client, server) @@ -646,7 +646,7 @@ def take_action(self, parsed_args): else: self.log.error(_('Error creating snapshot of server: %s'), parsed_args.server) - sys.stdout.write(_('\nError creating server snapshot')) + sys.stdout.write(_('Error creating server snapshot\n')) raise SystemExit image = _prep_image_detail(image_client, image_id) @@ -688,7 +688,7 @@ def take_action(self, parsed_args): else: self.log.error(_('Error deleting server: %s'), server_obj.id) - sys.stdout.write(_('\nError deleting server')) + sys.stdout.write(_('Error deleting server\n')) raise SystemExit @@ -1013,7 +1013,9 @@ def take_action(self, parsed_args): ): sys.stdout.write(_('Complete\n')) else: - sys.stdout.write(_('\nError migrating server')) + self.log.error(_('Error migrating server: %s'), + server.id) + sys.stdout.write(_('Error migrating server\n')) raise SystemExit @@ -1085,9 +1087,11 @@ def take_action(self, parsed_args): server.id, callback=_show_progress, ): - sys.stdout.write(_('\nReboot complete\n')) + sys.stdout.write(_('Complete\n')) else: - sys.stdout.write(_('\nError rebooting server\n')) + self.log.error(_('Error rebooting server: %s'), + server.id) + sys.stdout.write(_('Error rebooting server\n')) raise SystemExit @@ -1136,9 +1140,11 @@ def take_action(self, parsed_args): server.id, callback=_show_progress, ): - sys.stdout.write(_('\nComplete\n')) + sys.stdout.write(_('Complete\n')) else: - sys.stdout.write(_('\nError rebuilding server')) + self.log.error(_('Error rebuilding server: %s'), + server.id) + sys.stdout.write(_('Error rebuilding server\n')) raise SystemExit details = _prep_server_detail(compute_client, server) @@ -1290,7 +1296,9 @@ def take_action(self, parsed_args): ): sys.stdout.write(_('Complete\n')) else: - sys.stdout.write(_('\nError resizing server')) + self.log.error(_('Error resizing server: %s'), + server.id) + sys.stdout.write(_('Error resizing server\n')) raise SystemExit elif parsed_args.confirm: compute_client.servers.confirm_resize(server) From 3e11661074e1a7e051e0ebff5800f8f1aac85153 Mon Sep 17 00:00:00 2001 From: sunyajing Date: Sat, 28 May 2016 11:01:22 +0800 Subject: [PATCH 0919/3095] Add "image unset" command This patch add a command that supports unsetting image tags and properties Change-Id: I6f2cf45a61ff89da6664f3a34ae49fdd85d8c986 Closes-Bug:#1582968 --- doc/source/command-objects/image.rst | 27 ++++++ functional/tests/image/v2/test_image.py | 9 ++ openstackclient/image/v2/image.py | 89 +++++++++++++++++- openstackclient/tests/image/v2/fakes.py | 2 + openstackclient/tests/image/v2/test_image.py | 93 +++++++++++++++++++ .../notes/bug-1582968-4d44912a033b242c.yaml | 4 + setup.cfg | 1 + 7 files changed, 223 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/bug-1582968-4d44912a033b242c.yaml diff --git a/doc/source/command-objects/image.rst b/doc/source/command-objects/image.rst index 942c03d57c..d6451af76e 100644 --- a/doc/source/command-objects/image.rst +++ b/doc/source/command-objects/image.rst @@ -499,3 +499,30 @@ Display image details .. describe:: Image to display (name or ID) + +image unset +----------- + +*Only supported for Image v2* + +Unset image tags or properties + +.. program:: image unset +.. code:: bash + + os image set + [--tag ] + [--property ] + + +.. option:: --tag + + Unset a tag on this image (repeat option to unset multiple tags) + +.. option:: --property + + Unset a property on this image (repeat option to unset multiple properties) + +.. describe:: + + Image to modify (name or ID) diff --git a/functional/tests/image/v2/test_image.py b/functional/tests/image/v2/test_image.py index f0ebc11665..6a33ad88e1 100644 --- a/functional/tests/image/v2/test_image.py +++ b/functional/tests/image/v2/test_image.py @@ -65,3 +65,12 @@ def test_image_metadata(self): self.openstack('image set --property a=b --property c=d ' + self.NAME) raw_output = self.openstack('image show ' + self.NAME + opts) self.assertEqual(self.NAME + "\na='b', c='d'\n", raw_output) + + def test_image_unset(self): + opts = self.get_show_opts(["name", "tags", "properties"]) + self.openstack('image set --tag 01 ' + self.NAME) + self.openstack('image unset --tag 01 ' + self.NAME) + # test_image_metadata has set image properties "a" and "c" + self.openstack('image unset --property a --property c ' + self.NAME) + raw_output = self.openstack('image show ' + self.NAME + opts) + self.assertEqual(self.NAME + "\n\n", raw_output) diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index a9c0f1fdc1..a81f092c31 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -805,8 +805,8 @@ def take_action(self, parsed_args): # Checks if anything that requires getting the image if not (kwargs or parsed_args.deactivate or parsed_args.activate): - self.log.warning(_("No arguments specified")) - return {}, {} + msg = _("No arguments specified") + raise exceptions.CommandError(msg) image = utils.find_resource( image_client.images, parsed_args.image) @@ -856,3 +856,88 @@ def take_action(self, parsed_args): info = _format_image(image) return zip(*sorted(six.iteritems(info))) + + +class UnsetImage(command.Command): + """Unset image tags and properties""" + + def get_parser(self, prog_name): + parser = super(UnsetImage, self).get_parser(prog_name) + parser.add_argument( + "image", + metavar="", + help=_("Image to modify (name or ID)"), + ) + parser.add_argument( + "--tag", + dest="tags", + metavar="", + default=[], + action='append', + help=_("Unset a tag on this image " + "(repeat option to set multiple tags)"), + ) + parser.add_argument( + "--property", + dest="properties", + metavar="", + default=[], + action='append', + help=_("Unset a property on this image " + "(repeat option to set multiple properties)"), + ) + return parser + + def take_action(self, parsed_args): + image_client = self.app.client_manager.image + image = utils.find_resource( + image_client.images, + parsed_args.image, + ) + + if not (parsed_args.tags or parsed_args.properties): + msg = _("No arguments specified") + raise exceptions.CommandError(msg) + + kwargs = {} + tagret = 0 + propret = 0 + if parsed_args.tags: + for k in parsed_args.tags: + try: + image_client.image_tags.delete(image.id, k) + except Exception: + self.log.error(_("tag unset failed," + " '%s' is a nonexistent tag ") % k) + tagret += 1 + + if parsed_args.properties: + for k in parsed_args.properties: + try: + assert(k in image.keys()) + except AssertionError: + self.log.error(_("property unset failed," + " '%s' is a nonexistent property ") % k) + propret += 1 + image_client.images.update( + image.id, + parsed_args.properties, + **kwargs) + + tagtotal = len(parsed_args.tags) + proptotal = len(parsed_args.properties) + if (tagret > 0 and propret > 0): + msg = (_("Failed to unset %(tagret)s of %(tagtotal)s tags," + "Failed to unset %(propret)s of %(proptotal)s properties.") + % {'tagret': tagret, 'tagtotal': tagtotal, + 'propret': propret, 'proptotal': proptotal}) + raise exceptions.CommandError(msg) + elif tagret > 0: + msg = (_("Failed to unset %(target)s of %(tagtotal)s tags.") + % {'tagret': tagret, 'tagtotal': tagtotal}) + raise exceptions.CommandError(msg) + elif propret > 0: + msg = (_("Failed to unset %(propret)s of %(proptotal)s" + " properties.") + % {'propret': propret, 'proptotal': proptotal}) + raise exceptions.CommandError(msg) diff --git a/openstackclient/tests/image/v2/fakes.py b/openstackclient/tests/image/v2/fakes.py index a662a58524..24aaec51f6 100644 --- a/openstackclient/tests/image/v2/fakes.py +++ b/openstackclient/tests/image/v2/fakes.py @@ -157,6 +157,8 @@ def __init__(self, **kwargs): self.images.resource_class = fakes.FakeResource(None, {}) self.image_members = mock.Mock() self.image_members.resource_class = fakes.FakeResource(None, {}) + self.image_tags = mock.Mock() + self.image_tags.resource_class = fakes.FakeResource(None, {}) self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index beebdef995..ca20d83da4 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -37,6 +37,8 @@ def setUp(self): self.images_mock.reset_mock() self.image_members_mock = self.app.client_manager.image.image_members self.image_members_mock.reset_mock() + self.image_tags_mock = self.app.client_manager.image.image_tags + self.image_tags_mock.reset_mock() # Get shortcut to the Mocks in identity client self.project_mock = self.app.client_manager.identity.projects @@ -1184,3 +1186,94 @@ def test_image_show(self): self.assertEqual(image_fakes.IMAGE_columns, columns) self.assertEqual(image_fakes.IMAGE_SHOW_data, data) + + +class TestImageUnset(TestImage): + + attrs = {} + attrs['tags'] = ['test'] + attrs['prop'] = 'test' + image = image_fakes.FakeImage.create_one_image(attrs) + + def setUp(self): + super(TestImageUnset, self).setUp() + + # Set up the schema + self.model = warlock.model_factory( + image_fakes.IMAGE_schema, + schemas.SchemaBasedModel, + ) + + self.images_mock.get.return_value = self.image + self.image_tags_mock.delete.return_value = self.image + + # Get the command object to test + self.cmd = image.UnsetImage(self.app, None) + + def test_image_unset_tag_option(self): + + arglist = [ + '--tag', 'test', + self.image.id, + ] + + verifylist = [ + ('tags', ['test']), + ('image', self.image.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.image_tags_mock.delete.assert_called_with( + self.image.id, 'test' + ) + self.assertIsNone(result) + + def test_image_unset_property_option(self): + + arglist = [ + '--property', 'prop', + self.image.id, + ] + + verifylist = [ + ('properties', ['prop']), + ('image', self.image.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + kwargs = {} + self.images_mock.update.assert_called_with( + self.image.id, + parsed_args.properties, + **kwargs) + + self.assertIsNone(result) + + def test_image_unset_mixed_option(self): + + arglist = [ + '--tag', 'test', + '--property', 'prop', + self.image.id, + ] + + verifylist = [ + ('tags', ['test']), + ('properties', ['prop']), + ('image', self.image.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + kwargs = {} + self.images_mock.update.assert_called_with( + self.image.id, + parsed_args.properties, + **kwargs) + + self.image_tags_mock.delete.assert_called_with( + self.image.id, 'test' + ) + self.assertIsNone(result) diff --git a/releasenotes/notes/bug-1582968-4d44912a033b242c.yaml b/releasenotes/notes/bug-1582968-4d44912a033b242c.yaml new file mode 100644 index 0000000000..9d9794e31a --- /dev/null +++ b/releasenotes/notes/bug-1582968-4d44912a033b242c.yaml @@ -0,0 +1,4 @@ +--- +features: + - Add "image unset" command. + [Bug '1582968 '] diff --git a/setup.cfg b/setup.cfg index a62f5d25cd..a2da70489c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -322,6 +322,7 @@ openstack.image.v2 = image_save = openstackclient.image.v2.image:SaveImage image_show = openstackclient.image.v2.image:ShowImage image_set = openstackclient.image.v2.image:SetImage + image_unset = openstackclient.image.v2.image:UnsetImage openstack.network.v2 = address_scope_create = openstackclient.network.v2.address_scope:CreateAddressScope From 1a7284f63ad13f41c6ff4295d69f065310242524 Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Sat, 28 May 2016 18:55:50 +0800 Subject: [PATCH 0920/3095] Support to set server state When a server is unexpected state, OSC don't support reset the server to active or error state, that's supported by novaclient, and it's an important command for operators, the patch implement this function. Change-Id: I3e7800feb192832b0719ef9a353945beb6bfd509 Implements: blueprint server-reset-state --- doc/source/command-objects/server.rst | 5 ++ openstackclient/compute/v2/server.py | 9 +++ .../tests/compute/v2/test_server.py | 61 +++++++++++++++++++ .../server-set-state-214b12ec2161de4d.yaml | 6 ++ 4 files changed, 81 insertions(+) create mode 100644 releasenotes/notes/server-set-state-214b12ec2161de4d.yaml diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index bf972986a9..f11355b6dc 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -559,6 +559,7 @@ Set server properties --property [--property ] ... --root-password + --state .. option:: --name @@ -574,6 +575,10 @@ Set server properties Property to add/change for this server (repeat option to set multiple properties) +.. option:: --state + + New server state (valid value: active, error) + .. describe:: Server (name or ID) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 781ccb1b8e..2312575abf 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1370,6 +1370,12 @@ def get_parser(self, prog_name): help=_('Property to add/change for this server ' '(repeat option to set multiple properties)'), ) + parser.add_argument( + '--state', + metavar='', + choices=['active', 'error'], + help=_('New server state (valid value: active, error)'), + ) return parser def take_action(self, parsed_args): @@ -1389,6 +1395,9 @@ def take_action(self, parsed_args): parsed_args.property, ) + if parsed_args.state: + server.reset_state(state=parsed_args.state) + if parsed_args.root_password: p1 = getpass.getpass(_('New password: ')) p2 = getpass.getpass(_('Retype new password: ')) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 7d184b3ae9..2dfdb68ae7 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -1213,6 +1213,67 @@ def test_server_resume_multi_servers(self): self.run_method_with_servers('resume', 3) +class TestServerSet(TestServer): + + def setUp(self): + super(TestServerSet, self).setUp() + + self.methods = { + 'update': None, + 'reset_state': None, + 'change_password': None, + } + + self.fake_servers = self.setup_servers_mock(2) + + # Get the command object to test + self.cmd = server.SetServer(self.app, None) + + def test_server_set_no_option(self): + arglist = [ + 'foo_vm' + ] + verifylist = [ + ('server', 'foo_vm') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.assertNotCalled(self.fake_servers[0].update) + self.assertNotCalled(self.fake_servers[0].reset_state) + self.assertNotCalled(self.fake_servers[0].change_password) + self.assertNotCalled(self.servers_mock.set_meta) + self.assertIsNone(result) + + def test_server_set_with_state(self): + for index, state in enumerate(['active', 'error']): + arglist = [ + '--state', state, + 'foo_vm', + ] + verifylist = [ + ('state', state), + ('server', 'foo_vm'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.fake_servers[index].reset_state.assert_called_once_with( + state=state) + self.assertIsNone(result) + + def test_server_set_with_invalid_state(self): + arglist = [ + '--state', 'foo_state', + 'foo_vm', + ] + verifylist = [ + ('state', 'foo_state'), + ('server', 'foo_vm'), + ] + self.assertRaises(utils.ParserException, + self.check_parser, + self.cmd, arglist, verifylist) + + class TestServerShelve(TestServer): def setUp(self): diff --git a/releasenotes/notes/server-set-state-214b12ec2161de4d.yaml b/releasenotes/notes/server-set-state-214b12ec2161de4d.yaml new file mode 100644 index 0000000000..a48384a802 --- /dev/null +++ b/releasenotes/notes/server-set-state-214b12ec2161de4d.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--state`` option to ``server set`` command to set the server to + active or error state. + [Blueprint `server-reset-state `_] From c46a5597bec3764a96ea4b7f757f428674b8ae21 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Thu, 26 May 2016 20:00:12 +0800 Subject: [PATCH 0921/3095] Add support for setting flavor-access This patch adds "--project" option in "flavor set" command to support for setting flavor access. Change-Id: I75b473600080d8ab1dd6ad01561c4f989ed3c3bd Partial-Bug: #1575461 --- doc/source/command-objects/flavor.rst | 11 +++ openstackclient/compute/v2/flavor.py | 52 +++++++++- openstackclient/tests/compute/v2/fakes.py | 3 + .../tests/compute/v2/test_flavor.py | 99 ++++++++++++++++++- .../notes/bug-1575461-4d7d90e792132064.yaml | 6 ++ 5 files changed, 165 insertions(+), 6 deletions(-) create mode 100644 releasenotes/notes/bug-1575461-4d7d90e792132064.yaml diff --git a/doc/source/command-objects/flavor.rst b/doc/source/command-objects/flavor.rst index 322943d763..c6bde88287 100644 --- a/doc/source/command-objects/flavor.rst +++ b/doc/source/command-objects/flavor.rst @@ -128,12 +128,23 @@ Set flavor properties os flavor set [--property [...] ] + [--project ] + [--project-domain ] .. option:: --property Property to add or modify for this flavor (repeat option to set multiple properties) +.. option:: --project + + Set flavor access to project (name or ID) (admin only) + +.. option:: --project-domain + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + .. describe:: Flavor to modify (name or ID) diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 37ff831d59..48d0e27e18 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -22,6 +22,7 @@ from openstackclient.common import parseractions from openstackclient.common import utils from openstackclient.i18n import _ +from openstackclient.identity import common as identity_common def _find_flavor(compute_client, flavor): @@ -245,6 +246,11 @@ class SetFlavor(command.Command): def get_parser(self, prog_name): parser = super(SetFlavor, self).get_parser(prog_name) + parser.add_argument( + "flavor", + metavar="", + help=_("Flavor to modify (name or ID)") + ) parser.add_argument( "--property", metavar="", @@ -253,16 +259,54 @@ def get_parser(self, prog_name): "(repeat option to set multiple properties)") ) parser.add_argument( - "flavor", - metavar="", - help=_("Flavor to modify (name or ID)") + '--project', + metavar='', + help=_('Set flavor access to project (name or ID) ' + '(admin only)'), ) + identity_common.add_project_domain_option_to_parser(parser) + return parser def take_action(self, parsed_args): compute_client = self.app.client_manager.compute + identity_client = self.app.client_manager.identity + flavor = _find_flavor(compute_client, parsed_args.flavor) - flavor.set_keys(parsed_args.property) + + if not parsed_args.property and not parsed_args.project: + raise exceptions.CommandError(_("Nothing specified to be set.")) + + result = 0 + if parsed_args.property: + try: + flavor.set_keys(parsed_args.property) + except Exception as e: + self.app.log.error( + _("Failed to set flavor property: %s") % str(e)) + result += 1 + + if parsed_args.project: + try: + if flavor.is_public: + msg = _("Cannot set access for a public flavor") + raise exceptions.CommandError(msg) + else: + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + compute_client.flavor_access.add_tenant_access( + flavor.id, project_id) + except Exception as e: + self.app.log.error(_("Failed to set flavor access to" + " project: %s") % str(e)) + result += 1 + + if result > 0: + raise exceptions.CommandError(_("Command Failed: One or more of" + " the operations failed")) class ShowFlavor(command.ShowOne): diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 62a46b1d53..505469ad12 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -132,6 +132,9 @@ def __init__(self, **kwargs): self.flavors = mock.Mock() self.flavors.resource_class = fakes.FakeResource(None, {}) + self.flavor_access = mock.Mock() + self.flavor_access.resource_class = fakes.FakeResource(None, {}) + self.quotas = mock.Mock() self.quotas.resource_class = fakes.FakeResource(None, {}) diff --git a/openstackclient/tests/compute/v2/test_flavor.py b/openstackclient/tests/compute/v2/test_flavor.py index 6f507b1696..e5bdffe4ed 100644 --- a/openstackclient/tests/compute/v2/test_flavor.py +++ b/openstackclient/tests/compute/v2/test_flavor.py @@ -13,10 +13,14 @@ # under the License. # +import copy + from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.compute.v2 import flavor from openstackclient.tests.compute.v2 import fakes as compute_fakes +from openstackclient.tests import fakes +from openstackclient.tests.identity.v3 import fakes as identity_fakes from openstackclient.tests import utils as tests_utils @@ -29,6 +33,13 @@ def setUp(self): self.flavors_mock = self.app.client_manager.compute.flavors self.flavors_mock.reset_mock() + # Get a shortcut to the FlavorAccessManager Mock + self.flavor_access_mock = self.app.client_manager.compute.flavor_access + self.flavor_access_mock.reset_mock() + + self.projects_mock = self.app.client_manager.identity.projects + self.projects_mock.reset_mock() + class TestFlavorCreate(TestFlavor): @@ -427,16 +438,23 @@ def test_flavor_list_long(self): class TestFlavorSet(TestFlavor): # Return value of self.flavors_mock.find(). - flavor = compute_fakes.FakeFlavor.create_one_flavor() + flavor = compute_fakes.FakeFlavor.create_one_flavor( + attrs={'os-flavor-access:is_public': False}) def setUp(self): super(TestFlavorSet, self).setUp() self.flavors_mock.find.return_value = self.flavor self.flavors_mock.get.side_effect = exceptions.NotFound(None) + # Return a project + self.projects_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.PROJECT), + loaded=True, + ) self.cmd = flavor.SetFlavor(self.app, None) - def test_flavor_set(self): + def test_flavor_set_property(self): arglist = [ '--property', 'FOO="B A R"', 'baremetal' @@ -452,6 +470,83 @@ def test_flavor_set(self): is_public=None) self.assertIsNone(result) + def test_flavor_set_project(self): + arglist = [ + '--project', identity_fakes.project_id, + self.flavor.id, + ] + verifylist = [ + ('project', identity_fakes.project_id), + ('flavor', self.flavor.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.assertIsNone(result) + + self.flavor_access_mock.add_tenant_access.assert_called_with( + self.flavor.id, + identity_fakes.project_id, + ) + + def test_flavor_set_no_project(self): + arglist = [ + '--project', '', + self.flavor.id, + ] + verifylist = [ + ('project', ''), + ('flavor', self.flavor.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + + def test_flavor_set_no_flavor(self): + arglist = [ + '--project', identity_fakes.project_id, + ] + verifylist = [ + ('project', identity_fakes.project_id), + ] + + self.assertRaises(tests_utils.ParserException, + self.check_parser, + self.cmd, + arglist, + verifylist) + + def test_flavor_set_with_unexist_flavor(self): + self.flavors_mock.get.side_effect = exceptions.NotFound(None) + self.flavors_mock.find.side_effect = exceptions.NotFound(None) + + arglist = [ + '--project', identity_fakes.project_id, + 'unexist_flavor', + ] + verifylist = [ + ('project', identity_fakes.project_id), + ('flavor', 'unexist_flavor'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + def test_flavor_set_nothing(self): + arglist = [ + self.flavor.id, + ] + verifylist = [ + ('flavor', self.flavor.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + class TestFlavorShow(TestFlavor): diff --git a/releasenotes/notes/bug-1575461-4d7d90e792132064.yaml b/releasenotes/notes/bug-1575461-4d7d90e792132064.yaml new file mode 100644 index 0000000000..6bc9f73a67 --- /dev/null +++ b/releasenotes/notes/bug-1575461-4d7d90e792132064.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Added support for setting flavor access to project by using below command + ``flavor set --project `` + [Bug `1575461 `_] From 7def236718b4c68aec4d9532f9556a8800b64106 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Mon, 30 May 2016 15:39:12 +0800 Subject: [PATCH 0922/3095] Fix i18n support in cinder I checked all the cinder files and found some small issues of i18n support.So I fix them. Change-Id: I2df06cb9db4643bd734105664d83299726f7b4e9 Partial-bug: #1574965 --- openstackclient/volume/v2/qos_specs.py | 4 ++-- openstackclient/volume/v2/snapshot.py | 2 +- openstackclient/volume/v2/volume_type.py | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/openstackclient/volume/v2/qos_specs.py b/openstackclient/volume/v2/qos_specs.py index aa2059d7bc..3cea3303a2 100644 --- a/openstackclient/volume/v2/qos_specs.py +++ b/openstackclient/volume/v2/qos_specs.py @@ -250,8 +250,8 @@ def get_parser(self, prog_name): metavar='', action='append', default=[], - help=('Property to remove from the QoS specification. ' - '(repeat option to unset multiple properties)'), + help=_('Property to remove from the QoS specification. ' + '(repeat option to unset multiple properties)'), ) return parser diff --git a/openstackclient/volume/v2/snapshot.py b/openstackclient/volume/v2/snapshot.py index db4ce6c335..2886aaefeb 100644 --- a/openstackclient/volume/v2/snapshot.py +++ b/openstackclient/volume/v2/snapshot.py @@ -37,7 +37,7 @@ def get_parser(self, prog_name): parser.add_argument( "--name", metavar="", - help=("Name of the snapshot") + help=_("Name of the snapshot") ) parser.add_argument( "--description", diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index 2261f4c35b..96f13c830c 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -225,8 +225,8 @@ def take_action(self, parsed_args): result += 1 if result > 0: - raise exceptions.CommandError("Command Failed: One or more of the" - " operations failed") + raise exceptions.CommandError(_("Command Failed: One or more of" + " the operations failed")) class ShowVolumeType(command.ShowOne): @@ -315,5 +315,5 @@ def take_action(self, parsed_args): result += 1 if result > 0: - raise exceptions.CommandError("Command Failed: One or more of the" - " operations failed") + raise exceptions.CommandError(_("Command Failed: One or more of" + " the operations failed")) From dfea25d70c6cdc7f10aa2fd5a348d7071ded3d32 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Mon, 30 May 2016 17:50:11 +0000 Subject: [PATCH 0923/3095] Updated from global requirements Change-Id: I2a70d88480db366bffc8e69d318be89625e9177c --- requirements.txt | 2 +- test-requirements.txt | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index 62bcfe3870..e287406274 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,6 +15,6 @@ oslo.utils>=3.5.0 # Apache-2.0 python-glanceclient>=2.0.0 # Apache-2.0 python-keystoneclient!=1.8.0,!=2.1.0,>=1.7.0 # Apache-2.0 python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0 -python-cinderclient!=1.7.0,>=1.6.0 # Apache-2.0 +python-cinderclient!=1.7.0,!=1.7.1,>=1.6.0 # Apache-2.0 requests>=2.10.0 # Apache-2.0 stevedore>=1.10.0 # Apache-2.0 diff --git a/test-requirements.txt b/test-requirements.txt index ffc8326079..a8d6aa47e4 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -5,14 +5,14 @@ hacking<0.11,>=0.10.0 coverage>=3.6 # Apache-2.0 discover # BSD -fixtures<2.0,>=1.3.1 # Apache-2.0/BSD -mock>=1.2 # BSD +fixtures>=3.0.0 # Apache-2.0/BSD +mock>=2.0 # BSD oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 reno>=1.6.2 # Apache2 requests-mock>=0.7.0 # Apache-2.0 sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 # BSD -os-testr>=0.4.1 # Apache-2.0 +os-testr>=0.7.0 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD testtools>=1.4.0 # MIT tempest>=11.0.0 # Apache-2.0 From dce3962ffdb228bd24ccefa913544facd35c6766 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Mon, 30 May 2016 14:15:34 +0800 Subject: [PATCH 0924/3095] Add FakeQos class and update unit test for qos_specs in VolumeV2 Change-Id: I108c8d343678f3d19d1ca1f93d998c46c7c32eff --- openstackclient/tests/volume/v2/fakes.py | 75 ++++ .../tests/volume/v2/test_qos_specs.py | 336 +++++++----------- 2 files changed, 207 insertions(+), 204 deletions(-) diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py index 181552d9a2..158ab0ab85 100644 --- a/openstackclient/tests/volume/v2/fakes.py +++ b/openstackclient/tests/volume/v2/fakes.py @@ -592,6 +592,81 @@ def create_backups(attrs=None, count=2): return backups +class FakeQos(object): + """Fake one or more Qos specification.""" + + @staticmethod + def create_one_qos(attrs=None): + """Create a fake Qos specification. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object with id, name, consumer, etc. + """ + attrs = attrs or {} + + # Set default attributes. + qos_info = { + "id": 'qos-id-' + uuid.uuid4().hex, + "name": 'qos-name-' + uuid.uuid4().hex, + "consumer": 'front-end', + "specs": {"foo": "bar", "iops": "9001"}, + } + + # Overwrite default attributes. + qos_info.update(attrs) + + qos = fakes.FakeResource( + info=copy.deepcopy(qos_info), + loaded=True) + return qos + + @staticmethod + def create_one_qos_association(attrs=None): + """Create a fake Qos specification association. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object with id, name, association_type, etc. + """ + attrs = attrs or {} + + # Set default attributes. + qos_association_info = { + "id": 'type-id-' + uuid.uuid4().hex, + "name": 'type-name-' + uuid.uuid4().hex, + "association_type": 'volume_type', + } + + # Overwrite default attributes. + qos_association_info.update(attrs) + + qos_association = fakes.FakeResource( + info=copy.deepcopy(qos_association_info), + loaded=True) + return qos_association + + @staticmethod + def create_qoses(attrs=None, count=2): + """Create multiple fake Qos specifications. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of Qos specifications to fake + :return: + A list of FakeResource objects faking the Qos specifications + """ + qoses = [] + for i in range(0, count): + qos = FakeQos.create_one_qos(attrs) + qoses.append(qos) + + return qoses + + class FakeSnapshot(object): """Fake one or more snapshot.""" diff --git a/openstackclient/tests/volume/v2/test_qos_specs.py b/openstackclient/tests/volume/v2/test_qos_specs.py index 5232285c85..741f4e7043 100644 --- a/openstackclient/tests/volume/v2/test_qos_specs.py +++ b/openstackclient/tests/volume/v2/test_qos_specs.py @@ -13,10 +13,7 @@ # under the License. # -import copy - from openstackclient.common import utils -from openstackclient.tests import fakes from openstackclient.tests.volume.v2 import fakes as volume_fakes from openstackclient.volume.v2 import qos_specs @@ -35,275 +32,228 @@ def setUp(self): class TestQosAssociate(TestQos): + volume_type = volume_fakes.FakeType.create_one_type() + qos_spec = volume_fakes.FakeQos.create_one_qos() + def setUp(self): super(TestQosAssociate, self).setUp() + self.qos_mock.get.return_value = self.qos_spec + self.types_mock.get.return_value = self.volume_type # Get the command object to test self.cmd = qos_specs.AssociateQos(self.app, None) def test_qos_associate(self): - self.qos_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.QOS), - loaded=True - ) - self.types_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.TYPE), - loaded=True - ) arglist = [ - volume_fakes.qos_id, - volume_fakes.type_id + self.qos_spec.id, + self.volume_type.id ] verifylist = [ - ('qos_spec', volume_fakes.qos_id), - ('volume_type', volume_fakes.type_id) + ('qos_spec', self.qos_spec.id), + ('volume_type', self.volume_type.id) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.qos_mock.associate.assert_called_with( - volume_fakes.qos_id, - volume_fakes.type_id + self.qos_spec.id, + self.volume_type.id ) self.assertIsNone(result) class TestQosCreate(TestQos): + new_qos_spec = volume_fakes.FakeQos.create_one_qos() columns = ( 'consumer', 'id', - 'name' + 'name', + 'specs' ) - datalist = ( - volume_fakes.qos_consumer, - volume_fakes.qos_id, - volume_fakes.qos_name + data = ( + new_qos_spec.consumer, + new_qos_spec.id, + new_qos_spec.name, + new_qos_spec.specs ) def setUp(self): super(TestQosCreate, self).setUp() + self.qos_mock.create.return_value = self.new_qos_spec # Get the command object to test self.cmd = qos_specs.CreateQos(self.app, None) def test_qos_create_without_properties(self): - self.qos_mock.create.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.QOS_DEFAULT_CONSUMER), - loaded=True - ) - arglist = [ - volume_fakes.qos_name, + self.new_qos_spec.name, ] verifylist = [ - ('name', volume_fakes.qos_name), + ('name', self.new_qos_spec.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.qos_mock.create.assert_called_with( - volume_fakes.qos_name, - {'consumer': volume_fakes.qos_default_consumer} + self.new_qos_spec.name, + {'consumer': 'both'} ) self.assertEqual(self.columns, columns) - datalist = ( - volume_fakes.qos_default_consumer, - volume_fakes.qos_id, - volume_fakes.qos_name - ) - self.assertEqual(datalist, data) + self.assertEqual(self.data, data) def test_qos_create_with_consumer(self): - self.qos_mock.create.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.QOS), - loaded=True - ) - arglist = [ - volume_fakes.qos_name, - '--consumer', volume_fakes.qos_consumer + '--consumer', self.new_qos_spec.consumer, + self.new_qos_spec.name, ] verifylist = [ - ('name', volume_fakes.qos_name), - ('consumer', volume_fakes.qos_consumer) + ('consumer', self.new_qos_spec.consumer), + ('name', self.new_qos_spec.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.qos_mock.create.assert_called_with( - volume_fakes.qos_name, - {'consumer': volume_fakes.qos_consumer} + self.new_qos_spec.name, + {'consumer': self.new_qos_spec.consumer} ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertEqual(self.data, data) def test_qos_create_with_properties(self): - self.qos_mock.create.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.QOS_WITH_SPECS), - loaded=True - ) - arglist = [ - volume_fakes.qos_name, - '--consumer', volume_fakes.qos_consumer, + '--consumer', self.new_qos_spec.consumer, '--property', 'foo=bar', - '--property', 'iops=9001' + '--property', 'iops=9001', + self.new_qos_spec.name, ] verifylist = [ - ('name', volume_fakes.qos_name), - ('consumer', volume_fakes.qos_consumer), - ('property', volume_fakes.qos_specs) + ('consumer', self.new_qos_spec.consumer), + ('property', self.new_qos_spec.specs), + ('name', self.new_qos_spec.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - specs = volume_fakes.qos_specs.copy() - specs.update({'consumer': volume_fakes.qos_consumer}) + self.new_qos_spec.specs.update( + {'consumer': self.new_qos_spec.consumer}) self.qos_mock.create.assert_called_with( - volume_fakes.qos_name, - specs + self.new_qos_spec.name, + self.new_qos_spec.specs ) - columns = self.columns + ( - 'specs', - ) - self.assertEqual(columns, columns) - datalist = self.datalist + ( - volume_fakes.qos_specs, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) class TestQosDelete(TestQos): + qos_spec = volume_fakes.FakeQos.create_one_qos() + def setUp(self): super(TestQosDelete, self).setUp() - self.qos_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.QOS), - loaded=True, - ) - + self.qos_mock.get.return_value = self.qos_spec # Get the command object to test self.cmd = qos_specs.DeleteQos(self.app, None) - def test_qos_delete_with_id(self): - arglist = [ - volume_fakes.qos_id - ] - verifylist = [ - ('qos_specs', [volume_fakes.qos_id]) - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - self.qos_mock.delete.assert_called_with(volume_fakes.qos_id) - self.assertIsNone(result) - - def test_qos_delete_with_name(self): + def test_qos_delete(self): arglist = [ - volume_fakes.qos_name + self.qos_spec.id ] verifylist = [ - ('qos_specs', [volume_fakes.qos_name]) + ('qos_specs', [self.qos_spec.id]) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.qos_mock.delete.assert_called_with(volume_fakes.qos_id) + self.qos_mock.delete.assert_called_with(self.qos_spec.id) self.assertIsNone(result) class TestQosDisassociate(TestQos): + volume_type = volume_fakes.FakeType.create_one_type() + qos_spec = volume_fakes.FakeQos.create_one_qos() + def setUp(self): super(TestQosDisassociate, self).setUp() + self.qos_mock.get.return_value = self.qos_spec + self.types_mock.get.return_value = self.volume_type # Get the command object to test self.cmd = qos_specs.DisassociateQos(self.app, None) def test_qos_disassociate_with_volume_type(self): - self.qos_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.QOS), - loaded=True - ) - self.types_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.TYPE), - loaded=True - ) arglist = [ - volume_fakes.qos_id, - '--volume-type', volume_fakes.type_id + '--volume-type', self.volume_type.id, + self.qos_spec.id, ] verifylist = [ - ('qos_spec', volume_fakes.qos_id), - ('volume_type', volume_fakes.type_id) + ('volume_type', self.volume_type.id), + ('qos_spec', self.qos_spec.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.qos_mock.disassociate.assert_called_with( - volume_fakes.qos_id, - volume_fakes.type_id + self.qos_spec.id, + self.volume_type.id ) self.assertIsNone(result) def test_qos_disassociate_with_all_volume_types(self): - self.qos_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.QOS), - loaded=True - ) - arglist = [ - volume_fakes.qos_id, - '--all' + '--all', + self.qos_spec.id, ] verifylist = [ - ('qos_spec', volume_fakes.qos_id) + ('qos_spec', self.qos_spec.id) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.qos_mock.disassociate_all.assert_called_with(volume_fakes.qos_id) + self.qos_mock.disassociate_all.assert_called_with(self.qos_spec.id) self.assertIsNone(result) class TestQosList(TestQos): + qos_specs = volume_fakes.FakeQos.create_qoses(count=2) + qos_association = volume_fakes.FakeQos.create_one_qos_association() + + columns = ( + 'ID', + 'Name', + 'Consumer', + 'Associations', + 'Specs', + ) + data = [] + for q in qos_specs: + data.append(( + q.id, + q.name, + q.consumer, + qos_association.name, + utils.format_dict(q.specs), + )) + def setUp(self): super(TestQosList, self).setUp() - self.qos_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.QOS_WITH_ASSOCIATIONS), - loaded=True, - ) - self.qos_mock.list.return_value = [self.qos_mock.get.return_value] - self.qos_mock.get_associations.return_value = [fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.qos_association), - loaded=True, - )] + self.qos_mock.list.return_value = self.qos_specs + self.qos_mock.get_associations.return_value = [self.qos_association] # Get the command object to test self.cmd = qos_specs.ListQos(self.app, None) @@ -317,139 +267,117 @@ def test_qos_list(self): columns, data = self.cmd.take_action(parsed_args) self.qos_mock.list.assert_called_with() - collist = ( - 'ID', - 'Name', - 'Consumer', - 'Associations', - 'Specs', - ) - self.assertEqual(collist, columns) - datalist = (( - volume_fakes.qos_id, - volume_fakes.qos_name, - volume_fakes.qos_consumer, - volume_fakes.type_name, - utils.format_dict(volume_fakes.qos_specs), - ), ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) class TestQosSet(TestQos): + qos_spec = volume_fakes.FakeQos.create_one_qos() + def setUp(self): super(TestQosSet, self).setUp() + self.qos_mock.get.return_value = self.qos_spec # Get the command object to test self.cmd = qos_specs.SetQos(self.app, None) def test_qos_set_with_properties_with_id(self): - self.qos_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.QOS_WITH_SPECS), - loaded=True - ) arglist = [ - volume_fakes.qos_id, '--property', 'foo=bar', - '--property', 'iops=9001' + '--property', 'iops=9001', + self.qos_spec.id, ] verifylist = [ - ('qos_spec', volume_fakes.qos_id), - ('property', volume_fakes.qos_specs) + ('property', self.qos_spec.specs), + ('qos_spec', self.qos_spec.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.qos_mock.set_keys.assert_called_with( - volume_fakes.qos_id, - volume_fakes.qos_specs + self.qos_spec.id, + self.qos_spec.specs ) self.assertIsNone(result) class TestQosShow(TestQos): + qos_spec = volume_fakes.FakeQos.create_one_qos() + qos_association = volume_fakes.FakeQos.create_one_qos_association() + + columns = ( + 'associations', + 'consumer', + 'id', + 'name', + 'specs' + ) + data = ( + qos_association.name, + qos_spec.consumer, + qos_spec.id, + qos_spec.name, + utils.format_dict(qos_spec.specs), + ) + def setUp(self): super(TestQosShow, self).setUp() - self.qos_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.QOS_WITH_ASSOCIATIONS), - loaded=True, - ) - self.qos_mock.get_associations.return_value = [fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.qos_association), - loaded=True, - )] + self.qos_mock.get.return_value = self.qos_spec + self.qos_mock.get_associations.return_value = [self.qos_association] # Get the command object to test self.cmd = qos_specs.ShowQos(self.app, None) def test_qos_show(self): arglist = [ - volume_fakes.qos_id + self.qos_spec.id ] verifylist = [ - ('qos_spec', volume_fakes.qos_id) + ('qos_spec', self.qos_spec.id) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.qos_mock.get.assert_called_with( - volume_fakes.qos_id + self.qos_spec.id ) - collist = ( - 'associations', - 'consumer', - 'id', - 'name', - 'specs' - ) - self.assertEqual(collist, columns) - datalist = ( - volume_fakes.type_name, - volume_fakes.qos_consumer, - volume_fakes.qos_id, - volume_fakes.qos_name, - utils.format_dict(volume_fakes.qos_specs), - ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, tuple(data)) class TestQosUnset(TestQos): + qos_spec = volume_fakes.FakeQos.create_one_qos() + def setUp(self): super(TestQosUnset, self).setUp() + self.qos_mock.get.return_value = self.qos_spec # Get the command object to test self.cmd = qos_specs.UnsetQos(self.app, None) def test_qos_unset_with_properties(self): - self.qos_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.QOS), - loaded=True - ) arglist = [ - volume_fakes.qos_id, '--property', 'iops', - '--property', 'foo' + '--property', 'foo', + self.qos_spec.id, ] verifylist = [ - ('qos_spec', volume_fakes.qos_id), - ('property', ['iops', 'foo']) + ('property', ['iops', 'foo']), + ('qos_spec', self.qos_spec.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.qos_mock.unset_keys.assert_called_with( - volume_fakes.qos_id, + self.qos_spec.id, ['iops', 'foo'] ) self.assertIsNone(result) From a207fdfda6e4f888c730c89288a64fcb3c491ea2 Mon Sep 17 00:00:00 2001 From: sunyajing Date: Tue, 31 May 2016 10:54:16 +0800 Subject: [PATCH 0925/3095] add unit test for compute agent command Change-Id: I966d5a3a307fcd7f4efb1267aa2896efd53be50d --- openstackclient/tests/compute/v2/fakes.py | 52 +++++ .../tests/compute/v2/test_agent.py | 203 ++++++++++++++++++ 2 files changed, 255 insertions(+) create mode 100644 openstackclient/tests/compute/v2/test_agent.py diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 62a46b1d53..94964e6587 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -111,6 +111,9 @@ def create_one_aggregate(attrs=None): class FakeComputev2Client(object): def __init__(self, **kwargs): + self.agents = mock.Mock() + self.agents.resource_class = fakes.FakeResource(None, {}) + self.aggregates = mock.Mock() self.aggregates.resource_class = fakes.FakeResource(None, {}) @@ -204,6 +207,55 @@ def setUp(self): ) +class FakeAgent(object): + """Fake one or more agent.""" + + @staticmethod + def create_one_agent(attrs=None): + """Create a fake agent. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object, with agent_id, os, and so on + """ + + attrs = attrs or {} + + # set default attributes. + agent_info = { + 'agent_id': 'agent-id-' + uuid.uuid4().hex, + 'os': 'agent-os-' + uuid.uuid4().hex, + 'architecture': 'agent-architecture', + 'version': '8.0', + 'url': 'http://127.0.0.1', + 'md5hash': 'agent-md5hash', + 'hypervisor': 'hypervisor', + } + agent_info.update(attrs) + + agent = fakes.FakeResource(info=copy.deepcopy(agent_info), + loaded=True) + return agent + + @staticmethod + def create_agents(attrs=None, count=2): + """Create multiple fake agents. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of agents to fake + :return: + A list of FakeResource objects faking the agents + """ + agents = [] + for i in range(0, count): + agents.append(FakeAgent.create_one_agent(attrs)) + + return agents + + class FakeHypervisor(object): """Fake one or more hypervisor.""" diff --git a/openstackclient/tests/compute/v2/test_agent.py b/openstackclient/tests/compute/v2/test_agent.py new file mode 100644 index 0000000000..bdff8c5e2a --- /dev/null +++ b/openstackclient/tests/compute/v2/test_agent.py @@ -0,0 +1,203 @@ +# Copyright 2016 Easystack. 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.compute.v2 import agent +from openstackclient.tests.compute.v2 import fakes as compute_fakes + + +class TestAgent(compute_fakes.TestComputev2): + + fake_agent = compute_fakes.FakeAgent.create_one_agent() + + columns = ( + 'agent_id', + 'architecture', + 'hypervisor', + 'md5hash', + 'os', + 'url', + 'version', + ) + + data = ( + fake_agent.agent_id, + fake_agent.architecture, + fake_agent.hypervisor, + fake_agent.md5hash, + fake_agent.os, + fake_agent.url, + fake_agent.version, + ) + + def setUp(self): + super(TestAgent, self).setUp() + + self.agents_mock = self.app.client_manager.compute.agents + self.agents_mock.reset_mock() + + +class TestAgentCreate(TestAgent): + + def setUp(self): + super(TestAgentCreate, self).setUp() + + self.agents_mock.create.return_value = self.fake_agent + self.cmd = agent.CreateAgent(self.app, None) + + def test_agent_create(self): + arglist = [ + self.fake_agent.os, + self.fake_agent.architecture, + self.fake_agent.version, + self.fake_agent.url, + self.fake_agent.md5hash, + self.fake_agent.hypervisor, + ] + + verifylist = [ + ('os', self.fake_agent.os), + ('architecture', self.fake_agent.architecture), + ('version', self.fake_agent.version), + ('url', self.fake_agent.url), + ('md5hash', self.fake_agent.md5hash), + ('hypervisor', self.fake_agent.hypervisor), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.agents_mock.create.assert_called_with(parsed_args.os, + parsed_args.architecture, + parsed_args.version, + parsed_args.url, + parsed_args.md5hash, + parsed_args.hypervisor) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestAgentDelete(TestAgent): + + def setUp(self): + super(TestAgentDelete, self).setUp() + + self.agents_mock.get.return_value = self.fake_agent + self.cmd = agent.DeleteAgent(self.app, None) + + def test_one_agent_delete(self): + arglist = [ + 'test' + ] + + verifylist = [ + ('id', 'test'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.agents_mock.delete.assert_called_with(parsed_args.id) + self.assertIsNone(result) + + +class TestAgentList(TestAgent): + + agents = compute_fakes.FakeAgent.create_agents(count=3) + list_columns = ( + "Agent ID", + "Hypervisor", + "OS", + "Architecture", + "Version", + "Md5Hash", + "URL", + ) + + list_data = [] + for _agent in agents: + list_data.append(( + _agent.agent_id, + _agent.hypervisor, + _agent.os, + _agent.architecture, + _agent.version, + _agent.md5hash, + _agent.url, + )) + + def setUp(self): + + super(TestAgentList, self).setUp() + + self.agents_mock.list.return_value = self.agents + self.cmd = agent.ListAgent(self.app, None) + + def test_agent_list(self): + + arglist = [] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual(self.list_columns, columns) + self.assertEqual(self.list_data, list(data)) + + def test_agent_list_with_hypervisor(self): + + arglist = [ + '--hypervisor', + 'hypervisor', + ] + verifylist = [ + ('hypervisor', 'hypervisor'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual(self.list_columns, columns) + self.assertEqual(self.list_data, list(data)) + + +class TestAgentSet(TestAgent): + + def setUp(self): + super(TestAgentSet, self).setUp() + + self.agents_mock.update.return_value = self.fake_agent + self.cmd = agent.SetAgent(self.app, None) + + def test_agent_set(self): + arglist = [ + 'id', + 'new-version', + 'new-url', + 'new-md5hash', + ] + + verifylist = [ + ('id', 'id'), + ('version', 'new-version'), + ('url', 'new-url'), + ('md5hash', 'new-md5hash'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.agents_mock.update.assert_called_with(parsed_args.id, + parsed_args.version, + parsed_args.url, + parsed_args.md5hash) + self.assertIsNone(result) From 550ebac71474c2321b153ee97e1ea6c4cdc95a73 Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Sat, 28 May 2016 10:58:23 +0800 Subject: [PATCH 0926/3095] include old release notes in reno Currently OSC release notes are in the OpenStack releasenote site. There is a collection of old non-reno release notes in our docs folder. Move the non-reno release notes to the reno directionary. Change-Id: Ie51fd1e1115d606e5d2739014d3720eedc8dc225 --- doc/source/index.rst | 2 +- releasenotes/source/index.rst | 1 + releasenotes/source/mitaka.rst | 6 +++--- .../source/previous_releases.rst | 14 ++++++-------- 4 files changed, 11 insertions(+), 12 deletions(-) rename doc/source/releases.rst => releasenotes/source/previous_releases.rst (99%) diff --git a/doc/source/index.rst b/doc/source/index.rst index 0869344142..5889553384 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -37,7 +37,7 @@ Release Notes .. toctree:: :maxdepth: 1 - releases + Release Notes history Developer Documentation diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index e3af0e6263..396028942a 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -7,3 +7,4 @@ OpenStackClient Release Notes unreleased mitaka + previous_releases diff --git a/releasenotes/source/mitaka.rst b/releasenotes/source/mitaka.rst index e545609650..05ed3e48eb 100644 --- a/releasenotes/source/mitaka.rst +++ b/releasenotes/source/mitaka.rst @@ -1,6 +1,6 @@ -=================================== - Mitaka Series Release Notes -=================================== +=========================== +Mitaka Series Release Notes +=========================== .. release-notes:: :branch: origin/stable/mitaka diff --git a/doc/source/releases.rst b/releasenotes/source/previous_releases.rst similarity index 99% rename from doc/source/releases.rst rename to releasenotes/source/previous_releases.rst index 063066acb6..1999d38cfc 100644 --- a/doc/source/releases.rst +++ b/releasenotes/source/previous_releases.rst @@ -1,6 +1,6 @@ -============= -Release Notes -============= +================= +Previous Releases +================= As of release 2.0 the release notes can be found on the OpenStack `Release Notes site`_. @@ -364,8 +364,7 @@ As of release 2.0 the release notes can be found on the OpenStack `Release Notes * Fix ``backup create`` to correctly use the ``--container`` value if supplied. Bug `1446751 `_ -* Document the backward-compatibility-breaking changes in - :doc:`backwards-incompatible`. +* Document the backward-compatibility-breaking changes. Bug `1406470 `_ * Add `--parent`` option to `projct create` command. @@ -375,7 +374,6 @@ As of release 2.0 the release notes can be found on the OpenStack `Release Notes =================== * Add global ``--os-cloud`` option to select from a list of cloud configurations. - See :doc:`configuration` for more details. * Fix global ``--timing`` option operation. Bug `1402577 `_ @@ -463,8 +461,8 @@ As of release 2.0 the release notes can be found on the OpenStack `Release Notes =================== * The OpenStackClient content from the OpenStack Wiki has been migrated into - the OSC source repo. This includes the :doc:`commands`, :doc:`command-list` - and :doc:`humaninterfaceguide` documents. + the OSC source repo. This includes the `commands`, `command-list` + and `humaninterfaceguide` documents. * Set a default domain ID when both ``OS_USER_DOMAIN_ID`` and ``OS_USER_DOMAIN_NAME`` are not set. This is also done for From 677dfaa925cacb6f4d3482b5a71feace442b078d Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Tue, 31 May 2016 16:46:16 +0800 Subject: [PATCH 0927/3095] Fix unit test for volume commands in volumev2 There are some issues in test_volume.py in volumev2, I make three changes in this patch: 1.modified some codes that not used FakeVolume class. 2.added a fake image by FakeImage class for this test. 3.added a fake snapshot by FakeSnapshot class for this test. Change-Id: I02ba73d3aaee95624b0e2307b255e0e485b0c3a3 --- .../tests/volume/v2/test_volume.py | 48 ++++++++----------- 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/openstackclient/tests/volume/v2/test_volume.py b/openstackclient/tests/volume/v2/test_volume.py index 85ff61422b..fb48d8acf5 100644 --- a/openstackclient/tests/volume/v2/test_volume.py +++ b/openstackclient/tests/volume/v2/test_volume.py @@ -14,12 +14,12 @@ import copy -import mock from mock import call from openstackclient.common import utils from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes +from openstackclient.tests.image.v2 import fakes as image_fakes from openstackclient.tests.volume.v2 import fakes as volume_fakes from openstackclient.volume.v2 import volume @@ -301,19 +301,16 @@ def test_volume_create_properties(self): self.assertEqual(self.datalist, data) def test_volume_create_image_id(self): - self.images_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.IMAGE), - loaded=True, - ) + image = image_fakes.FakeImage.create_one_image() + self.images_mock.get.return_value = image arglist = [ - '--image', volume_fakes.image_id, + '--image', image.id, '--size', str(self.new_volume.size), self.new_volume.name, ] verifylist = [ - ('image', volume_fakes.image_id), + ('image', image.id), ('size', self.new_volume.size), ('name', self.new_volume.name), ] @@ -334,7 +331,7 @@ def test_volume_create_image_id(self): project_id=None, availability_zone=None, metadata=None, - imageRef=volume_fakes.image_id, + imageRef=image.id, source_volid=None, ) @@ -342,19 +339,16 @@ def test_volume_create_image_id(self): self.assertEqual(self.datalist, data) def test_volume_create_image_name(self): - self.images_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.IMAGE), - loaded=True, - ) + image = image_fakes.FakeImage.create_one_image() + self.images_mock.get.return_value = image arglist = [ - '--image', volume_fakes.image_name, + '--image', image.name, '--size', str(self.new_volume.size), self.new_volume.name, ] verifylist = [ - ('image', volume_fakes.image_name), + ('image', image.name), ('size', self.new_volume.size), ('name', self.new_volume.name), ] @@ -375,7 +369,7 @@ def test_volume_create_image_name(self): project_id=None, availability_zone=None, metadata=None, - imageRef=volume_fakes.image_id, + imageRef=image.id, source_volid=None ) @@ -383,21 +377,21 @@ def test_volume_create_image_name(self): self.assertEqual(self.datalist, data) def test_volume_create_with_snapshot(self): + snapshot = volume_fakes.FakeSnapshot.create_one_snapshot() + self.new_volume.snapshot_id = snapshot.id arglist = [ '--size', str(self.new_volume.size), - '--snapshot', volume_fakes.snapshot_id, + '--snapshot', self.new_volume.snapshot_id, self.new_volume.name, ] verifylist = [ ('size', self.new_volume.size), - ('snapshot', volume_fakes.snapshot_id), + ('snapshot', self.new_volume.snapshot_id), ('name', self.new_volume.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - fake_snapshot = mock.Mock() - fake_snapshot.id = volume_fakes.snapshot_id - self.snapshots_mock.get.return_value = fake_snapshot + 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 @@ -406,7 +400,7 @@ def test_volume_create_with_snapshot(self): self.volumes_mock.create.assert_called_once_with( size=self.new_volume.size, - snapshot_id=fake_snapshot.id, + snapshot_id=snapshot.id, name=self.new_volume.name, description=None, volume_type=None, @@ -642,12 +636,12 @@ def test_volume_list_user_domain(self): def test_volume_list_name(self): arglist = [ - '--name', volume_fakes.volume_name, + '--name', self.mock_volume.name, ] verifylist = [ ('long', False), ('all_projects', False), - ('name', volume_fakes.volume_name), + ('name', self.mock_volume.name), ('status', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -670,13 +664,13 @@ def test_volume_list_name(self): def test_volume_list_status(self): arglist = [ - '--status', volume_fakes.volume_status, + '--status', self.mock_volume.status, ] verifylist = [ ('long', False), ('all_projects', False), ('name', None), - ('status', volume_fakes.volume_status), + ('status', self.mock_volume.status), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) From 5a3a7867a823bfc9012e438e69497540f4606753 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 31 May 2016 20:31:28 +0800 Subject: [PATCH 0928/3095] Trivial: Fix i18n support in network/common.py Some missing i18n problems in network. Change-Id: I45a09a6ada1aad5a64256c0d0a0a2b6e250df670 --- openstackclient/network/client.py | 7 ++++--- openstackclient/network/common.py | 17 +++++++++++++---- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/openstackclient/network/client.py b/openstackclient/network/client.py index be06d2b552..d711f4fcf5 100644 --- a/openstackclient/network/client.py +++ b/openstackclient/network/client.py @@ -17,6 +17,7 @@ from openstack import profile from openstackclient.common import utils +from openstackclient.i18n import _ LOG = logging.getLogger(__name__) @@ -51,7 +52,7 @@ def build_option_parser(parser): '--os-network-api-version', metavar='', default=utils.env('OS_NETWORK_API_VERSION'), - help='Network API version, default=' + - DEFAULT_API_VERSION + - ' (Env: OS_NETWORK_API_VERSION)') + help=_("Network API version, default=%s " + "(Env: OS_NETWORK_API_VERSION)") % DEFAULT_API_VERSION + ) return parser diff --git a/openstackclient/network/common.py b/openstackclient/network/common.py index a3047d84ba..4028ac0db2 100644 --- a/openstackclient/network/common.py +++ b/openstackclient/network/common.py @@ -16,6 +16,7 @@ from openstackclient.common import command from openstackclient.common import exceptions +from openstackclient.i18n import _ @six.add_metaclass(abc.ABCMeta) @@ -94,14 +95,22 @@ def take_action(self, parsed_args): self.take_action_compute(self.app.client_manager.compute, parsed_args) except Exception as e: - self.app.log.error("Failed to delete %s with name or ID " - "'%s': %s" % (self.resource, r, e)) + msg = _("Failed to delete %(resource)s with name or ID " + "'%(name_or_id)s': %(e)s") % { + "resource": self.resource, + "name_or_id": r, + "e": e, + } + self.app.log.error(msg) ret += 1 if ret: total = len(resources) - msg = "%s of %s %ss failed to delete." % (ret, total, - self.resource) + msg = _("%(num)s of %(total)s %(resource)s failed to delete.") % { + "num": ret, + "total": total, + "resource": self.resource, + } raise exceptions.CommandError(msg) From e7be9731ce6ace5feb5808d0f8005f065c1d2148 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Tue, 31 May 2016 18:56:32 +0000 Subject: [PATCH 0929/3095] Updated from global requirements Change-Id: I04e0189527b1affabf351002d66fe857ec73a694 --- requirements.txt | 2 +- test-requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index e287406274..61dc7ec237 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ openstacksdk>=0.8.6 # Apache-2.0 os-client-config>=1.13.1 # Apache-2.0 oslo.config>=3.9.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 -oslo.utils>=3.5.0 # Apache-2.0 +oslo.utils>=3.9.0 # Apache-2.0 python-glanceclient>=2.0.0 # Apache-2.0 python-keystoneclient!=1.8.0,!=2.1.0,>=1.7.0 # Apache-2.0 python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0 diff --git a/test-requirements.txt b/test-requirements.txt index a8d6aa47e4..b3a04f611f 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -5,7 +5,7 @@ hacking<0.11,>=0.10.0 coverage>=3.6 # Apache-2.0 discover # BSD -fixtures>=3.0.0 # Apache-2.0/BSD +fixtures<2.0,>=1.3.1 # Apache-2.0/BSD mock>=2.0 # BSD oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 From 478dd6482229654b4795e295b5f53f9678de9e81 Mon Sep 17 00:00:00 2001 From: "zhang.xiuhua" Date: Tue, 31 May 2016 18:11:46 +0800 Subject: [PATCH 0930/3095] Modify lowercase to uppercase Change-Id: Ia59355d824ba61da824612ea0e03243b1bec4a57 --- doc/source/man/openstack.rst | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/source/man/openstack.rst b/doc/source/man/openstack.rst index 8cd10a7933..5fea5aa625 100644 --- a/doc/source/man/openstack.rst +++ b/doc/source/man/openstack.rst @@ -82,7 +82,7 @@ OPTIONS Project-level authentication scope (name or ID) :option:`--os-project-domain-name` | :option:`--os-project-domain-id` - Domain name or id containing project + Domain name or ID containing project :option:`--os-username` Authentication username @@ -94,13 +94,13 @@ OPTIONS Authenticated token or service token :option:`--os-user-domain-name` | :option:`--os-user-domain-id` - Domain name or id containing user + Domain name or ID containing user :option:`--os-user-domain-name` | :option:`--os-user-domain-id` Domain name or ID containing user :option:`--os-trust-id` - id of the trust to use as a trustee user + ID of the trust to use as a trustee user :option:`--os-default-domain` Default domain ID (Default: 'default') @@ -142,10 +142,10 @@ OPTIONS Increase verbosity of output. Can be repeated. :option:`-q, --quiet` - suppress output except warnings and errors + Suppress output except warnings and errors :option:`--debug` - show tracebacks on errors and set verbosity to debug + Show tracebacks on errors and set verbosity to debug :option:`--enable-beta-commands` Enable beta commands which are subject to change @@ -350,7 +350,7 @@ The following environment variables can be set to alter the behaviour of :progra Project-level authentication scope (name or ID) :envvar:`OS_PROJECT_DOMAIN_NAME` - Domain name or id containing project + Domain name or ID containing project :envvar:`OS_USERNAME` Authentication username @@ -362,10 +362,10 @@ The following environment variables can be set to alter the behaviour of :progra Authentication password :envvar:`OS_USER_DOMAIN_NAME` - Domain name or id containing user + Domain name or ID containing user :envvar:`OS_TRUST_ID` - id of the trust to use as a trustee user + ID of the trust to use as a trustee user :envvar:`OS_DEFAULT_DOMAIN` Default domain ID (Default: 'default') From 6ab1eab4144d3f434fe251344d148856f12d9f3e Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 2 Jun 2016 04:09:24 +0000 Subject: [PATCH 0931/3095] Updated from global requirements Change-Id: I5e7045eae42c9cc6e1005134b8c589e5d1eca08d --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 61dc7ec237..39f95f1efc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ openstacksdk>=0.8.6 # Apache-2.0 os-client-config>=1.13.1 # Apache-2.0 oslo.config>=3.9.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 -oslo.utils>=3.9.0 # Apache-2.0 +oslo.utils>=3.11.0 # Apache-2.0 python-glanceclient>=2.0.0 # Apache-2.0 python-keystoneclient!=1.8.0,!=2.1.0,>=1.7.0 # Apache-2.0 python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0 From 55466d8158d7fb1a2a7658b6d3f36737fe9012e6 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Thu, 2 Jun 2016 12:19:08 +0800 Subject: [PATCH 0932/3095] Trivial: Remove duplicated line in man page Change-Id: I3f72308e639ddcdfb0289e50f9bafd097461c896 --- doc/source/man/openstack.rst | 3 --- 1 file changed, 3 deletions(-) diff --git a/doc/source/man/openstack.rst b/doc/source/man/openstack.rst index 5fea5aa625..e14f1f95cb 100644 --- a/doc/source/man/openstack.rst +++ b/doc/source/man/openstack.rst @@ -96,9 +96,6 @@ OPTIONS :option:`--os-user-domain-name` | :option:`--os-user-domain-id` Domain name or ID containing user -:option:`--os-user-domain-name` | :option:`--os-user-domain-id` - Domain name or ID containing user - :option:`--os-trust-id` ID of the trust to use as a trustee user From 22c60f3ac7a482609ba393a96db3989dca4c280c Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Wed, 1 Jun 2016 09:42:40 +0800 Subject: [PATCH 0933/3095] Add support for removing flavor-access Add "--project" and "--project-domain" options in "flavor unset" command to remove flavor access. Change-Id: Ia5c5a80d9890d5af066b75b4e202647c18c7d915 Partial-Bug: #1575461 --- doc/source/command-objects/flavor.rst | 11 +++ openstackclient/compute/v2/flavor.py | 52 +++++++++-- .../tests/compute/v2/test_flavor.py | 88 ++++++++++++++++++- .../notes/bug-1575461-54a23327081cbec7.yaml | 6 ++ 4 files changed, 150 insertions(+), 7 deletions(-) create mode 100644 releasenotes/notes/bug-1575461-54a23327081cbec7.yaml diff --git a/doc/source/command-objects/flavor.rst b/doc/source/command-objects/flavor.rst index c6bde88287..a30bedecb6 100644 --- a/doc/source/command-objects/flavor.rst +++ b/doc/source/command-objects/flavor.rst @@ -175,12 +175,23 @@ Unset flavor properties os flavor unset [--property [...] ] + [--project ] + [--project-domain ] .. option:: --property Property to remove from flavor (repeat option to remove multiple properties) +.. option:: --project + + Remove flavor access from project (name or ID) (admin only) + +.. option:: --project-domain + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + .. describe:: Flavor to modify (name or ID) diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 48d0e27e18..87909a1825 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -337,22 +337,64 @@ class UnsetFlavor(command.Command): def get_parser(self, prog_name): parser = super(UnsetFlavor, self).get_parser(prog_name) + parser.add_argument( + "flavor", + metavar="", + help=_("Flavor to modify (name or ID)") + ) parser.add_argument( "--property", metavar="", action='append', - required=True, help=_("Property to remove from flavor " "(repeat option to unset multiple properties)") ) parser.add_argument( - "flavor", - metavar="", - help=_("Flavor to modify (name or ID)") + '--project', + metavar='', + help=_('Remove flavor access from project (name or ID) ' + '(admin only)'), ) + identity_common.add_project_domain_option_to_parser(parser) + return parser def take_action(self, parsed_args): compute_client = self.app.client_manager.compute + identity_client = self.app.client_manager.identity + flavor = _find_flavor(compute_client, parsed_args.flavor) - flavor.unset_keys(parsed_args.property) + + if not parsed_args.property and not parsed_args.project: + raise exceptions.CommandError(_("Nothing specified to be unset.")) + + result = 0 + if parsed_args.property: + try: + flavor.unset_keys(parsed_args.property) + except Exception as e: + self.app.log.error( + _("Failed to unset flavor property: %s") % str(e)) + result += 1 + + if parsed_args.project: + try: + if flavor.is_public: + msg = _("Cannot remove access for a public flavor") + raise exceptions.CommandError(msg) + else: + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + compute_client.flavor_access.remove_tenant_access( + flavor.id, project_id) + except Exception as e: + self.app.log.error(_("Failed to remove flavor access from" + " project: %s") % str(e)) + result += 1 + + if result > 0: + raise exceptions.CommandError(_("Command Failed: One or more of" + " the operations failed")) diff --git a/openstackclient/tests/compute/v2/test_flavor.py b/openstackclient/tests/compute/v2/test_flavor.py index e5bdffe4ed..9d424890ed 100644 --- a/openstackclient/tests/compute/v2/test_flavor.py +++ b/openstackclient/tests/compute/v2/test_flavor.py @@ -616,16 +616,23 @@ def test_flavor_show(self): class TestFlavorUnset(TestFlavor): # Return value of self.flavors_mock.find(). - flavor = compute_fakes.FakeFlavor.create_one_flavor() + flavor = compute_fakes.FakeFlavor.create_one_flavor( + attrs={'os-flavor-access:is_public': False}) def setUp(self): super(TestFlavorUnset, self).setUp() self.flavors_mock.find.return_value = self.flavor self.flavors_mock.get.side_effect = exceptions.NotFound(None) + # Return a project + self.projects_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.PROJECT), + loaded=True, + ) self.cmd = flavor.UnsetFlavor(self.app, None) - def test_flavor_unset(self): + def test_flavor_unset_property(self): arglist = [ '--property', 'property', 'baremetal' @@ -640,3 +647,80 @@ def test_flavor_unset(self): self.flavors_mock.find.assert_called_with(name=parsed_args.flavor, is_public=None) self.assertIsNone(result) + + def test_flavor_unset_project(self): + arglist = [ + '--project', identity_fakes.project_id, + self.flavor.id, + ] + verifylist = [ + ('project', identity_fakes.project_id), + ('flavor', self.flavor.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.assertIsNone(result) + + self.flavor_access_mock.remove_tenant_access.assert_called_with( + self.flavor.id, + identity_fakes.project_id, + ) + + def test_flavor_unset_no_project(self): + arglist = [ + '--project', '', + self.flavor.id, + ] + verifylist = [ + ('project', ''), + ('flavor', self.flavor.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + + def test_flavor_unset_no_flavor(self): + arglist = [ + '--project', identity_fakes.project_id, + ] + verifylist = [ + ('project', identity_fakes.project_id), + ] + + self.assertRaises(tests_utils.ParserException, + self.check_parser, + self.cmd, + arglist, + verifylist) + + def test_flavor_unset_with_unexist_flavor(self): + self.flavors_mock.get.side_effect = exceptions.NotFound(None) + self.flavors_mock.find.side_effect = exceptions.NotFound(None) + + arglist = [ + '--project', identity_fakes.project_id, + 'unexist_flavor', + ] + verifylist = [ + ('project', identity_fakes.project_id), + ('flavor', 'unexist_flavor'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + def test_flavor_unset_nothing(self): + arglist = [ + self.flavor.id, + ] + verifylist = [ + ('flavor', self.flavor.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) diff --git a/releasenotes/notes/bug-1575461-54a23327081cbec7.yaml b/releasenotes/notes/bug-1575461-54a23327081cbec7.yaml new file mode 100644 index 0000000000..a498bfbdac --- /dev/null +++ b/releasenotes/notes/bug-1575461-54a23327081cbec7.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Added support for removing flavor access from project by using below command + ``flavor unset --project --project-domain `` + [Bug `1575461 `_] From 6f2c1734e3d66e261f231711455821321c1fc254 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Thu, 2 Jun 2016 09:53:55 -0500 Subject: [PATCH 0934/3095] Fix --enable options on commands The --enable option on commands is ignored when the arguments are parsed. This is related to the --enable-beta-commands option. Renaming the option to --os-beta-command fixes the problem. There's no need to handle backwards compatibility for the option name change because there hasn't been an OSC release yet with beta commands. Change-Id: I0327ba8a2058858a83e9a42e231470ed733cc834 Closes-Bug: #1588384 --- doc/source/command-beta.rst | 12 ++++-------- doc/source/command-objects/network-segment.rst | 4 ++-- doc/source/man/openstack.rst | 6 +++--- .../tests/network/v2/test_network_segment.py | 6 +++--- openstackclient/common/command.py | 10 ++++++++++ openstackclient/network/v2/network_segment.py | 17 ++++------------- openstackclient/shell.py | 2 +- openstackclient/tests/common/test_command.py | 15 +++++++++++++++ openstackclient/tests/fakes.py | 2 +- .../tests/network/v2/test_network_segment.py | 6 +++--- .../bp-routed-networks-3eea4978c93aa126.yaml | 2 +- .../notes/bug-1588384-eb6976fcfb90cb4c.yaml | 7 +++++++ 12 files changed, 54 insertions(+), 35 deletions(-) create mode 100644 releasenotes/notes/bug-1588384-eb6976fcfb90cb4c.yaml diff --git a/doc/source/command-beta.rst b/doc/source/command-beta.rst index 53a442047f..f0a4f85102 100644 --- a/doc/source/command-beta.rst +++ b/doc/source/command-beta.rst @@ -33,7 +33,7 @@ example list List examples .. caution:: This is a beta command and subject to change. - Use global option ``--enable-beta-commands`` to + Use global option ``--os-beta-command`` to enable this command. .. program:: example list @@ -52,7 +52,7 @@ The command help must label the command as a beta. """Display example details (Caution: This is a beta command and subject to change. - Use global option --enable-beta-commands to enable + Use global option --os-beta-command to enable this command) """ @@ -60,13 +60,9 @@ Implementation -------------- The command must raise a ``CommandError`` exception if beta commands -are not enabled via ``--enable-beta-commands`` global option. +are not enabled via ``--os-beta-command`` global option. .. code-block:: python def take_action(self, parsed_args): - if not self.app.options.enable_beta_commands: - msg = _('Caution: This is a beta command and subject to ' - 'change. Use global option --enable-beta-commands ' - 'to enable this command.') - raise exceptions.CommandError(msg) + self.validate_os_beta_command_enabled() diff --git a/doc/source/command-objects/network-segment.rst b/doc/source/command-objects/network-segment.rst index 8e177d6af5..56a11e1ed4 100644 --- a/doc/source/command-objects/network-segment.rst +++ b/doc/source/command-objects/network-segment.rst @@ -15,7 +15,7 @@ network segment list List network segments .. caution:: This is a beta command and subject to change. - Use global option ``--enable-beta-commands`` to + Use global option ``--os-beta-command`` to enable this command. .. program:: network segment list @@ -39,7 +39,7 @@ network segment show Display network segment details .. caution:: This is a beta command and subject to change. - Use global option ``--enable-beta-commands`` to + Use global option ``--os-beta-command`` to enable this command. .. program:: network segment show diff --git a/doc/source/man/openstack.rst b/doc/source/man/openstack.rst index e14f1f95cb..7af380a31c 100644 --- a/doc/source/man/openstack.rst +++ b/doc/source/man/openstack.rst @@ -132,6 +132,9 @@ OPTIONS This key should be the value of one of the HMAC keys defined in the configuration files of OpenStack services to be traced. +:option:`--os-beta-command` + Enable beta commands which are subject to change + :option:`--log-file` Specify a file to log output. Disabled by default. @@ -144,9 +147,6 @@ OPTIONS :option:`--debug` Show tracebacks on errors and set verbosity to debug -:option:`--enable-beta-commands` - Enable beta commands which are subject to change - COMMANDS ======== diff --git a/functional/tests/network/v2/test_network_segment.py b/functional/tests/network/v2/test_network_segment.py index a998093811..b5b5dcd9fe 100644 --- a/functional/tests/network/v2/test_network_segment.py +++ b/functional/tests/network/v2/test_network_segment.py @@ -34,7 +34,7 @@ def setUpClass(cls): # Get the segment for the network. opts = cls.get_show_opts(['ID', 'Network']) - raw_output = cls.openstack('--enable-beta-commands ' + raw_output = cls.openstack('--os-beta-command ' 'network segment list ' ' --network ' + cls.NETWORK_NAME + ' ' + opts) @@ -48,13 +48,13 @@ def tearDownClass(cls): def test_network_segment_list(self): opts = self.get_list_opts(['ID']) - raw_output = self.openstack('--enable-beta-commands ' + raw_output = self.openstack('--os-beta-command ' 'network segment list' + opts) self.assertIn(self.NETWORK_SEGMENT_ID, raw_output) def test_network_segment_show(self): opts = self.get_show_opts(['network_id']) - raw_output = self.openstack('--enable-beta-commands ' + raw_output = self.openstack('--os-beta-command ' 'network segment show ' + self.NETWORK_SEGMENT_ID + opts) self.assertEqual(self.NETWORK_ID + "\n", raw_output) diff --git a/openstackclient/common/command.py b/openstackclient/common/command.py index fee4559e75..144a0db10d 100644 --- a/openstackclient/common/command.py +++ b/openstackclient/common/command.py @@ -20,6 +20,9 @@ from cliff import show import six +from openstackclient.common import exceptions +from openstackclient.i18n import _ + class CommandMeta(abc.ABCMeta): @@ -37,6 +40,13 @@ def run(self, parsed_args): self.log.debug('run(%s)', parsed_args) return super(Command, self).run(parsed_args) + def validate_os_beta_command_enabled(self): + if not self.app.options.os_beta_command: + msg = _('Caution: This is a beta command and subject to ' + 'change. Use global option --os-beta-command ' + 'to enable this command.') + raise exceptions.CommandError(msg) + class Lister(Command, lister.Lister): pass diff --git a/openstackclient/network/v2/network_segment.py b/openstackclient/network/v2/network_segment.py index d8a91fd200..818ffc026f 100644 --- a/openstackclient/network/v2/network_segment.py +++ b/openstackclient/network/v2/network_segment.py @@ -16,7 +16,6 @@ # TODO(rtheis): Add description and name properties when support is available. from openstackclient.common import command -from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.i18n import _ @@ -25,7 +24,7 @@ class ListNetworkSegment(command.Lister): """List network segments (Caution: This is a beta command and subject to change. - Use global option --enable-beta-commands to enable + Use global option --os-beta-command to enable this command) """ @@ -46,11 +45,7 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - if not self.app.options.enable_beta_commands: - msg = _('Caution: This is a beta command and subject to ' - 'change. Use global option --enable-beta-commands ' - 'to enable this command.') - raise exceptions.CommandError(msg) + self.validate_os_beta_command_enabled() network_client = self.app.client_manager.network @@ -94,7 +89,7 @@ class ShowNetworkSegment(command.ShowOne): """Display network segment details (Caution: This is a beta command and subject to change. - Use global option --enable-beta-commands to enable + Use global option --os-beta-command to enable this command) """ @@ -108,11 +103,7 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - if not self.app.options.enable_beta_commands: - msg = _('Caution: This is a beta command and subject to ' - 'change. Use global option --enable-beta-commands ' - 'to enable this command.') - raise exceptions.CommandError(msg) + self.validate_os_beta_command_enabled() client = self.app.client_manager.network obj = client.find_segment( diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 9968d73fc1..9179ad019e 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -251,7 +251,7 @@ def build_option_parser(self, description, version): help="Print API call timing info", ) parser.add_argument( - '--enable-beta-commands', + '--os-beta-command', action='store_true', help="Enable beta commands which are subject to change", ) diff --git a/openstackclient/tests/common/test_command.py b/openstackclient/tests/common/test_command.py index 7467d9eba4..722a4c06ad 100644 --- a/openstackclient/tests/common/test_command.py +++ b/openstackclient/tests/common/test_command.py @@ -15,6 +15,8 @@ import mock from openstackclient.common import command +from openstackclient.common import exceptions +from openstackclient.tests import fakes as test_fakes from openstackclient.tests import utils as test_utils @@ -31,3 +33,16 @@ def test_command_has_logger(self): self.assertTrue(hasattr(cmd, 'log')) self.assertEqual('openstackclient.tests.common.test_command.' 'FakeCommand', cmd.log.name) + + def test_validate_os_beta_command_enabled(self): + cmd = FakeCommand(mock.Mock(), mock.Mock()) + cmd.app = mock.Mock() + cmd.app.options = test_fakes.FakeOptions() + + # No exception is raised when enabled. + cmd.app.options.os_beta_command = True + cmd.validate_os_beta_command_enabled() + + cmd.app.options.os_beta_command = False + self.assertRaises(exceptions.CommandError, + cmd.validate_os_beta_command_enabled) diff --git a/openstackclient/tests/fakes.py b/openstackclient/tests/fakes.py index 7fb1daa980..ad4705a4de 100644 --- a/openstackclient/tests/fakes.py +++ b/openstackclient/tests/fakes.py @@ -99,7 +99,7 @@ def __init__(self, _stdout, _log): class FakeOptions(object): def __init__(self, **kwargs): - self.enable_beta_commands = False + self.os_beta_command = False class FakeClient(object): diff --git a/openstackclient/tests/network/v2/test_network_segment.py b/openstackclient/tests/network/v2/test_network_segment.py index 2822581c67..0a99eced69 100644 --- a/openstackclient/tests/network/v2/test_network_segment.py +++ b/openstackclient/tests/network/v2/test_network_segment.py @@ -25,7 +25,7 @@ def setUp(self): super(TestNetworkSegment, self).setUp() # Enable beta commands. - self.app.options.enable_beta_commands = True + self.app.options.os_beta_command = True # Get a shortcut to the network client self.network = self.app.client_manager.network @@ -89,7 +89,7 @@ def test_list_no_option(self): self.assertEqual(self.data, list(data)) def test_list_no_beta_commands(self): - self.app.options.enable_beta_commands = False + self.app.options.os_beta_command = False parsed_args = self.check_parser(self.cmd, [], []) self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) @@ -174,7 +174,7 @@ def test_show_no_beta_commands(self): verifylist = [ ('network_segment', self._network_segment.id), ] - self.app.options.enable_beta_commands = False + self.app.options.os_beta_command = False parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) diff --git a/releasenotes/notes/bp-routed-networks-3eea4978c93aa126.yaml b/releasenotes/notes/bp-routed-networks-3eea4978c93aa126.yaml index 82080f7e6a..30661e6d20 100644 --- a/releasenotes/notes/bp-routed-networks-3eea4978c93aa126.yaml +++ b/releasenotes/notes/bp-routed-networks-3eea4978c93aa126.yaml @@ -3,5 +3,5 @@ features: - Add support for the ``network segment`` command object via the ``network segment list`` and ``network segment show`` commands. These are beta commands and subject to change. Use global option - ``--enable-beta-commands`` to enable these commands. + ``--os-beta-command`` to enable these commands. [Blueprint `routed-networks `_] diff --git a/releasenotes/notes/bug-1588384-eb6976fcfb90cb4c.yaml b/releasenotes/notes/bug-1588384-eb6976fcfb90cb4c.yaml new file mode 100644 index 0000000000..797d6732b6 --- /dev/null +++ b/releasenotes/notes/bug-1588384-eb6976fcfb90cb4c.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - Fix the ``--enable`` option on all commands by changing the + ``--enable-beta-commands`` global option to ``--os-beta-command``. + There are no upgrade impacts for the global option rename since + the old name isn't used. + [Bug `1588384 `_] From 41f5521ee9d9fef549c75bbdc34c870646b5eb45 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 2 Jun 2016 14:30:08 -0500 Subject: [PATCH 0935/3095] Update v2 endpoint show help https://review.openstack.org/#/c/319821/ updated v2 endpoint show and the doc, but not the v3 help. Change-Id: Ifaa90f6266eabafb9de544199759f4ee8bcc1c83 --- openstackclient/identity/v3/endpoint.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openstackclient/identity/v3/endpoint.py b/openstackclient/identity/v3/endpoint.py index 6e4b356d0b..b03b78ce3b 100644 --- a/openstackclient/identity/v3/endpoint.py +++ b/openstackclient/identity/v3/endpoint.py @@ -243,8 +243,9 @@ def get_parser(self, prog_name): parser = super(ShowEndpoint, self).get_parser(prog_name) parser.add_argument( 'endpoint', - metavar='', - help='Endpoint ID to display', + metavar='', + help='Endpoint to display (endpoint ID, service ID,' + ' service name, service type)', ) return parser From 909bab1e07ec564055dd7dab578af9970a8e648e Mon Sep 17 00:00:00 2001 From: sunyajing Date: Wed, 1 Jun 2016 16:43:57 +0800 Subject: [PATCH 0936/3095] Support multiple argument for compute agent delete command Change-Id: I3b19e4914d475b86d7e8aa8d76e62a2ac811272f Partially-Implements: blueprint multi-argument-compute --- doc/source/command-objects/compute-agent.rst | 6 +- openstackclient/compute/v2/agent.py | 22 ++++++- .../tests/compute/v2/test_agent.py | 61 +++++++++++++++++-- .../notes/compute-agent-deff48988e81b30e.yaml | 3 + 4 files changed, 81 insertions(+), 11 deletions(-) create mode 100644 releasenotes/notes/compute-agent-deff48988e81b30e.yaml diff --git a/doc/source/command-objects/compute-agent.rst b/doc/source/command-objects/compute-agent.rst index 395ee4f372..b00a981d1f 100644 --- a/doc/source/command-objects/compute-agent.rst +++ b/doc/source/command-objects/compute-agent.rst @@ -44,17 +44,17 @@ Create compute agent compute agent delete -------------------- -Delete compute agent command +Delete compute agent(s) .. program:: compute agent delete .. code:: bash - os compute agent delete + os compute agent delete [ ...] .. _compute_agent-delete: .. describe:: - ID of agent to delete + ID of agent(s) to delete compute agent list ------------------ diff --git a/openstackclient/compute/v2/agent.py b/openstackclient/compute/v2/agent.py index e358399db3..064fe5a66b 100644 --- a/openstackclient/compute/v2/agent.py +++ b/openstackclient/compute/v2/agent.py @@ -18,6 +18,7 @@ import six from openstackclient.common import command +from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.i18n import _ @@ -75,20 +76,35 @@ def take_action(self, parsed_args): class DeleteAgent(command.Command): - """Delete compute agent command""" + """Delete compute agent(s)""" def get_parser(self, prog_name): parser = super(DeleteAgent, self).get_parser(prog_name) parser.add_argument( "id", metavar="", - help=_("ID of agent to delete") + nargs='+', + help=_("ID of agent(s) to delete") ) return parser def take_action(self, parsed_args): compute_client = self.app.client_manager.compute - compute_client.agents.delete(parsed_args.id) + result = 0 + for id in parsed_args.id: + try: + compute_client.agents.delete(id) + except Exception as e: + result += 1 + self.app.log.error(_("Failed to delete agent with " + "ID '%(id)s': %(e)s") + % {'id': id, 'e': e}) + + if result > 0: + total = len(parsed_args.id) + msg = (_("%(result)s of %(total)s agents failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListAgent(command.Lister): diff --git a/openstackclient/tests/compute/v2/test_agent.py b/openstackclient/tests/compute/v2/test_agent.py index bdff8c5e2a..d3d1ff29e0 100644 --- a/openstackclient/tests/compute/v2/test_agent.py +++ b/openstackclient/tests/compute/v2/test_agent.py @@ -13,6 +13,10 @@ # under the License. # +import mock + +from mock import call +from openstackclient.common import exceptions from openstackclient.compute.v2 import agent from openstackclient.tests.compute.v2 import fakes as compute_fakes @@ -89,26 +93,73 @@ def test_agent_create(self): class TestAgentDelete(TestAgent): + fake_agents = compute_fakes.FakeAgent.create_agents(count=2) + def setUp(self): super(TestAgentDelete, self).setUp() - self.agents_mock.get.return_value = self.fake_agent + self.agents_mock.get.return_value = self.fake_agents self.cmd = agent.DeleteAgent(self.app, None) - def test_one_agent_delete(self): + def test_delete_one_agent(self): arglist = [ - 'test' + self.fake_agents[0].agent_id ] verifylist = [ - ('id', 'test'), + ('id', [self.fake_agents[0].agent_id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.agents_mock.delete.assert_called_with(parsed_args.id) + self.agents_mock.delete.assert_called_with( + self.fake_agents[0].agent_id) + self.assertIsNone(result) + + def test_delete_multiple_agents(self): + arglist = [] + for n in self.fake_agents: + arglist.append(n.agent_id) + verifylist = [ + ('id', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + calls = [] + for n in self.fake_agents: + calls.append(call(n.agent_id)) + self.agents_mock.delete.assert_has_calls(calls) self.assertIsNone(result) + def test_delete_multiple_agents_exception(self): + arglist = [ + self.fake_agents[0].agent_id, + self.fake_agents[1].agent_id, + 'x-y-z', + ] + verifylist = [ + ('id', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ret_delete = [ + None, + None, + exceptions.NotFound('404') + ] + self.agents_mock.delete = mock.Mock(side_effect=ret_delete) + + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + calls = [ + call(self.fake_agents[0].agent_id), + call(self.fake_agents[1].agent_id), + ] + self.agents_mock.delete.assert_has_calls(calls) + class TestAgentList(TestAgent): diff --git a/releasenotes/notes/compute-agent-deff48988e81b30e.yaml b/releasenotes/notes/compute-agent-deff48988e81b30e.yaml new file mode 100644 index 0000000000..2f3c3f2e7e --- /dev/null +++ b/releasenotes/notes/compute-agent-deff48988e81b30e.yaml @@ -0,0 +1,3 @@ +--- +upgrade: + - Command ``compute agent delete`` now supports deleting multiple agents. From 3078540161e35cee89cd87fccc9b9561690ad6b1 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Fri, 3 Jun 2016 03:44:52 +0000 Subject: [PATCH 0937/3095] Updated from global requirements Change-Id: I17243642ec7d2c4d27ba42f722f848dead2f820a --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index b3a04f611f..a8d6aa47e4 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -5,7 +5,7 @@ hacking<0.11,>=0.10.0 coverage>=3.6 # Apache-2.0 discover # BSD -fixtures<2.0,>=1.3.1 # Apache-2.0/BSD +fixtures>=3.0.0 # Apache-2.0/BSD mock>=2.0 # BSD oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 From 95d5d9f76164628a6914f69255fdcd7a23179f38 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Fri, 3 Jun 2016 10:23:58 +0800 Subject: [PATCH 0938/3095] Fix some missing i18n support problems in compute Found and Fix some missing i18n support problems in compute/client.py Change-Id: I54374f4eddafd9f80b6ccdaf8f8c30e098df105e Partial-bug: #1574965 --- openstackclient/compute/client.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/openstackclient/compute/client.py b/openstackclient/compute/client.py index 82f09cec0f..8e6eedcf39 100644 --- a/openstackclient/compute/client.py +++ b/openstackclient/compute/client.py @@ -17,6 +17,7 @@ from openstackclient.common import exceptions from openstackclient.common import utils +from openstackclient.i18n import _ LOG = logging.getLogger(__name__) @@ -82,9 +83,9 @@ def build_option_parser(parser): '--os-compute-api-version', metavar='', default=utils.env('OS_COMPUTE_API_VERSION'), - help='Compute API version, default=' + - DEFAULT_API_VERSION + - ' (Env: OS_COMPUTE_API_VERSION)') + help=_("Compute API version, default=%s " + "(Env: OS_COMPUTE_API_VERSION)") % DEFAULT_API_VERSION + ) return parser @@ -118,10 +119,9 @@ def check_api_version(check_version): novaclient.API_MIN_VERSION, novaclient.API_MAX_VERSION, ): - raise exceptions.CommandError( - "versions supported by client: %s - %s" % ( - novaclient.API_MIN_VERSION.get_string(), - novaclient.API_MAX_VERSION.get_string(), - ), - ) + msg = _("versions supported by client: %(min)s - %(max)s") % { + "min": novaclient.API_MIN_VERSION.get_string(), + "max": novaclient.API_MAX_VERSION.get_string(), + } + raise exceptions.CommandError(msg) return True From c95c73f8e239301b3be7a5264dcb87293cf2dad5 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Fri, 3 Jun 2016 15:53:49 +0800 Subject: [PATCH 0939/3095] Fix wrong test in flavor unit tests In test_flavor_set_no_project(), we aimed to test a situation like this: User specifies "--project" option, but didn't specifies the project name or ID. But in the source code, it becomes "--project ''". The test could past because if project and property are both None, the command will raise an exception. Change-Id: I39567306debb901e8bad420fa2492f1b207efddc --- openstackclient/tests/compute/v2/test_flavor.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/openstackclient/tests/compute/v2/test_flavor.py b/openstackclient/tests/compute/v2/test_flavor.py index 9d424890ed..434d5f92be 100644 --- a/openstackclient/tests/compute/v2/test_flavor.py +++ b/openstackclient/tests/compute/v2/test_flavor.py @@ -491,17 +491,15 @@ def test_flavor_set_project(self): def test_flavor_set_no_project(self): arglist = [ - '--project', '', + '--project', self.flavor.id, ] verifylist = [ ('project', ''), ('flavor', self.flavor.id), ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.assertRaises(exceptions.CommandError, self.cmd.take_action, - parsed_args) + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) def test_flavor_set_no_flavor(self): arglist = [ From 6d1dd680761552e1a16ca9e1d739f106fe5868c2 Mon Sep 17 00:00:00 2001 From: zhouqi Date: Fri, 3 Jun 2016 15:21:11 +0800 Subject: [PATCH 0940/3095] Check port name in set port tests Change-Id: I1bf11245b107f82fedee70dacc37c4c6dc5210ea --- openstackclient/tests/network/v2/test_port.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openstackclient/tests/network/v2/test_port.py b/openstackclient/tests/network/v2/test_port.py index f2aa26cf88..c3f175bf41 100644 --- a/openstackclient/tests/network/v2/test_port.py +++ b/openstackclient/tests/network/v2/test_port.py @@ -283,6 +283,7 @@ def test_set_fixed_ip(self): ] verifylist = [ ('fixed_ip', [{'ip-address': '10.0.0.11'}]), + ('port', self._port.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -304,6 +305,7 @@ def test_append_fixed_ip(self): ] verifylist = [ ('fixed_ip', [{'ip-address': '10.0.0.12'}]), + ('port', _testport.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) @@ -325,6 +327,7 @@ def test_set_this(self): ('disable', True), ('no_binding_profile', True), ('no_fixed_ip', True), + ('port', self._port.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -352,7 +355,8 @@ def test_set_that(self): ('vnic_type', 'macvtap'), ('binding_profile', {'foo': 'bar'}), ('host', 'binding-host-id-xxxx'), - ('name', 'newName') + ('name', 'newName'), + ('port', self._port.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) From 01ad9ab368508f6ef118af919417cb095f886dee Mon Sep 17 00:00:00 2001 From: Jackie Yuan Date: Fri, 3 Jun 2016 16:08:56 +0800 Subject: [PATCH 0941/3095] Modify the style of translated messages Translated messages should not be combined with orther literal strings to create partially translated message. Although this change is very small, but this is the only one in the directory VOLUME. Change-Id: If798aacde9d5d5e8ac2edd49e75099ec255c858e --- openstackclient/volume/v2/volume_type.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index 96f13c830c..200d9bd51a 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -205,8 +205,8 @@ def take_action(self, parsed_args): try: volume_type.set_keys(parsed_args.property) except Exception as e: - self.app.log.error(_("Failed to set volume type property: ") + - str(e)) + self.app.log.error(_("Failed to set volume type" + " property: %s") % str(e)) result += 1 if parsed_args.project: From eef20541093d4b2d531fd8b32a3d4ebd84bb240d Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 19 May 2016 15:14:43 -0500 Subject: [PATCH 0942/3095] Move server image create command to its own resource file. Change-Id: If37e82072bd7a32b81bfb1a8bb048f018dd5b04f --- doc/source/command-objects/server-image.rst | 8 +- functional/tests/common/test_help.py | 3 +- openstackclient/compute/v2/server.py | 74 ------ openstackclient/compute/v2/server_image.py | 111 +++++++++ .../tests/compute/v2/test_server.py | 144 ----------- .../tests/compute/v2/test_server_image.py | 227 ++++++++++++++++++ setup.cfg | 3 +- 7 files changed, 346 insertions(+), 224 deletions(-) create mode 100644 openstackclient/compute/v2/server_image.py create mode 100644 openstackclient/tests/compute/v2/test_server_image.py diff --git a/doc/source/command-objects/server-image.rst b/doc/source/command-objects/server-image.rst index 8b48934298..eb44e47e07 100644 --- a/doc/source/command-objects/server-image.rst +++ b/doc/source/command-objects/server-image.rst @@ -10,7 +10,7 @@ Compute v2 server image create ------------------- -Create a new disk image from a running server +Create a new server disk image from an existing server .. program:: server image create .. code:: bash @@ -22,12 +22,12 @@ Create a new disk image from a running server .. option:: --name - Name of new image (default is server name) + Name of new disk image (default: server name) .. option:: --wait - Wait for image create to complete + Wait for operation to complete .. describe:: - Server (name or ID) + Server to create image (name or ID) diff --git a/functional/tests/common/test_help.py b/functional/tests/common/test_help.py index fcce5f99bc..7601c41bb9 100644 --- a/functional/tests/common/test_help.py +++ b/functional/tests/common/test_help.py @@ -19,11 +19,12 @@ class HelpTests(test.TestCase): SERVER_COMMANDS = [ ('server add security group', 'Add security group to server'), ('server add volume', 'Add volume to server'), + ('server backup create', 'Create a server backup image'), ('server create', 'Create a new server'), ('server delete', 'Delete server(s)'), ('server dump create', 'Create a dump file in server(s)'), ('server image create', - 'Create a new disk image from a running server'), + 'Create a new server disk image from an existing server'), ('server list', 'List servers'), ('server lock', 'Lock server(s). ' diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 27abbe631b..1638407418 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -164,23 +164,6 @@ def _prep_server_detail(compute_client, server): return info -def _prep_image_detail(image_client, image_id): - """Prepare the detailed image dict for printing - - :param image_client: an image client instance - :param image_id: id of image created - :rtype: a dict of image details - """ - - info = utils.find_resource( - image_client.images, - image_id, - ) - # Glance client V2 doesn't have _info attribute - # The following condition deals with it. - return getattr(info, "_info", info) - - def _show_progress(progress): if progress: sys.stdout.write('\rProgress: %s' % progress) @@ -597,63 +580,6 @@ def take_action(self, parsed_args): ).trigger_crash_dump() -class CreateServerImage(command.ShowOne): - """Create a new disk image from a running server""" - - def get_parser(self, prog_name): - parser = super(CreateServerImage, self).get_parser(prog_name) - parser.add_argument( - 'server', - metavar='', - help=_('Server (name or ID)'), - ) - parser.add_argument( - '--name', - metavar='', - help=_('Name of new image (default is server name)'), - ) - parser.add_argument( - '--wait', - action='store_true', - help=_('Wait for image create to complete'), - ) - return parser - - def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - image_client = self.app.client_manager.image - server = utils.find_resource( - compute_client.servers, - parsed_args.server, - ) - if parsed_args.name: - name = parsed_args.name - else: - name = server.name - - image_id = compute_client.servers.create_image( - server, - name, - ) - - if parsed_args.wait: - if utils.wait_for_status( - image_client.images.get, - image_id, - callback=_show_progress, - ): - sys.stdout.write('\n') - else: - self.log.error(_('Error creating snapshot of server: %s'), - parsed_args.server) - sys.stdout.write(_('Error creating server snapshot\n')) - raise SystemExit - - image = _prep_image_detail(image_client, image_id) - - return zip(*sorted(six.iteritems(image))) - - class DeleteServer(command.Command): """Delete server(s)""" diff --git a/openstackclient/compute/v2/server_image.py b/openstackclient/compute/v2/server_image.py new file mode 100644 index 0000000000..85ee7f2d18 --- /dev/null +++ b/openstackclient/compute/v2/server_image.py @@ -0,0 +1,111 @@ +# Copyright 2012-2013 OpenStack Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +"""Compute v2 Server action implementations""" + +import sys + +from oslo_utils import importutils +import six + +from openstackclient.common import command +from openstackclient.common import exceptions +from openstackclient.common import utils +from openstackclient.i18n import _ + + +def _show_progress(progress): + if progress: + sys.stdout.write('\rProgress: %s' % progress) + sys.stdout.flush() + + +class CreateServerImage(command.ShowOne): + """Create a new server disk image from an existing server""" + + IMAGE_API_VERSIONS = { + "1": "openstackclient.image.v1.image", + "2": "openstackclient.image.v2.image", + } + + def get_parser(self, prog_name): + parser = super(CreateServerImage, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + help=_('Server to create image (name or ID)'), + ) + parser.add_argument( + '--name', + metavar='', + help=_('Name of new disk image (default: server name)'), + ) + parser.add_argument( + '--wait', + action='store_true', + help=_('Wait for operation to complete'), + ) + return parser + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + + server = utils.find_resource( + compute_client.servers, + parsed_args.server, + ) + if parsed_args.name: + image_name = parsed_args.name + else: + image_name = server.name + + image_id = compute_client.servers.create_image( + server.id, + image_name, + ) + + image_client = self.app.client_manager.image + image = utils.find_resource( + image_client.images, + image_id, + ) + + if parsed_args.wait: + if utils.wait_for_status( + image_client.images.get, + image_id, + callback=_show_progress, + ): + sys.stdout.write('\n') + else: + self.log.error( + _('Error creating server image: %s') % + parsed_args.server, + ) + raise exceptions.CommandError + + if self.app.client_manager._api_version['image'] == '1': + info = {} + info.update(image._info) + info['properties'] = utils.format_dict(info.get('properties', {})) + else: + # Get the right image module to format the output + image_module = importutils.import_module( + self.IMAGE_API_VERSIONS[ + self.app.client_manager._api_version['image'] + ] + ) + info = image_module._format_image(image) + return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 2dfdb68ae7..01aa9fc165 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -509,150 +509,6 @@ def test_server_dump_multi_servers(self): self.run_method_with_servers('trigger_crash_dump', 3) -class TestServerImageCreate(TestServer): - - columns = ( - 'id', - 'name', - 'owner', - 'protected', - 'tags', - 'visibility', - ) - - def datalist(self): - datalist = ( - self.image.id, - self.image.name, - self.image.owner, - self.image.protected, - self.image.tags, - self.image.visibility, - ) - return datalist - - def setUp(self): - super(TestServerImageCreate, self).setUp() - - self.server = compute_fakes.FakeServer.create_one_server() - - # This is the return value for utils.find_resource() - self.servers_mock.get.return_value = self.server - - self.image = image_fakes.FakeImage.create_one_image() - self.images_mock.get.return_value = self.image - self.servers_mock.create_image.return_value = self.image.id - - # Get the command object to test - self.cmd = server.CreateServerImage(self.app, None) - - def test_server_image_create_no_options(self): - arglist = [ - self.server.id, - ] - verifylist = [ - ('server', self.server.id), - ] - 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) - - # ServerManager.create_image(server, image_name, metadata=) - self.servers_mock.create_image.assert_called_with( - self.servers_mock.get.return_value, - self.server.name, - ) - - self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist(), data) - - def test_server_image_create_name(self): - arglist = [ - '--name', 'img-nam', - self.server.id, - ] - verifylist = [ - ('name', 'img-nam'), - ('server', self.server.id), - ] - 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) - - # ServerManager.create_image(server, image_name, metadata=) - self.servers_mock.create_image.assert_called_with( - self.servers_mock.get.return_value, - 'img-nam', - ) - - self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist(), data) - - @mock.patch.object(common_utils, 'wait_for_status', return_value=False) - def test_server_create_image_with_wait_fails(self, mock_wait_for_status): - arglist = [ - '--wait', - self.server.id, - ] - verifylist = [ - ('wait', True), - ('server', self.server.id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - self.assertRaises(SystemExit, self.cmd.take_action, parsed_args) - - mock_wait_for_status.assert_called_once_with( - self.images_mock.get, - self.image.id, - callback=server._show_progress - ) - - # ServerManager.create_image(server, image_name, metadata=) - self.servers_mock.create_image.assert_called_with( - self.servers_mock.get.return_value, - self.server.name, - ) - - @mock.patch.object(common_utils, 'wait_for_status', return_value=True) - def test_server_create_image_with_wait_ok(self, mock_wait_for_status): - arglist = [ - '--wait', - self.server.id, - ] - verifylist = [ - ('wait', True), - ('server', self.server.id), - ] - 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) - - # ServerManager.create_image(server, image_name, metadata=) - self.servers_mock.create_image.assert_called_with( - self.servers_mock.get.return_value, - self.server.name, - ) - - mock_wait_for_status.assert_called_once_with( - self.images_mock.get, - self.image.id, - callback=server._show_progress - ) - - self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist(), data) - - class TestServerList(TestServer): # Columns to be listed up. diff --git a/openstackclient/tests/compute/v2/test_server_image.py b/openstackclient/tests/compute/v2/test_server_image.py new file mode 100644 index 0000000000..660e981784 --- /dev/null +++ b/openstackclient/tests/compute/v2/test_server_image.py @@ -0,0 +1,227 @@ +# 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.common import exceptions +from openstackclient.common import utils as common_utils +from openstackclient.compute.v2 import server_image +from openstackclient.tests.compute.v2 import fakes as compute_fakes +from openstackclient.tests.image.v2 import fakes as image_fakes + + +class TestServerImage(compute_fakes.TestComputev2): + + def setUp(self): + super(TestServerImage, self).setUp() + + # Get a shortcut to the compute client ServerManager Mock + self.servers_mock = self.app.client_manager.compute.servers + self.servers_mock.reset_mock() + + # Get a shortcut to the image client ImageManager Mock + self.images_mock = self.app.client_manager.image.images + self.images_mock.reset_mock() + + # Set object attributes to be tested. Could be overwriten in subclass. + self.attrs = {} + + # Set object methods to be tested. Could be overwriten in subclass. + self.methods = {} + + def setup_servers_mock(self, count): + servers = compute_fakes.FakeServer.create_servers( + attrs=self.attrs, + methods=self.methods, + count=count, + ) + + # This is the return value for utils.find_resource() + self.servers_mock.get = compute_fakes.FakeServer.get_servers( + servers, + 0, + ) + return servers + + +class TestServerImageCreate(TestServerImage): + + def image_columns(self, image): + columnlist = tuple(sorted(image.keys())) + return columnlist + + def image_data(self, image): + datalist = ( + image['id'], + image['name'], + image['owner'], + image['protected'], + 'active', + common_utils.format_list(image.get('tags')), + image['visibility'], + ) + return datalist + + def setUp(self): + super(TestServerImageCreate, self).setUp() + + # Get the command object to test + self.cmd = server_image.CreateServerImage(self.app, None) + + self.methods = { + 'create_image': None, + } + + def setup_images_mock(self, count, servers=None): + if servers: + images = image_fakes.FakeImage.create_images( + attrs={ + 'name': servers[0].name, + 'status': 'active', + }, + count=count, + ) + else: + images = image_fakes.FakeImage.create_images( + attrs={ + 'status': 'active', + }, + count=count, + ) + + self.images_mock.get = mock.MagicMock(side_effect=images) + self.servers_mock.create_image = mock.MagicMock( + return_value=images[0].id, + ) + return images + + def test_server_image_create_defaults(self): + servers = self.setup_servers_mock(count=1) + images = self.setup_images_mock(count=1, servers=servers) + + arglist = [ + servers[0].id, + ] + verifylist = [ + ('server', servers[0].id), + ] + 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) + + # ServerManager.create_image(server, image_name, metadata=) + self.servers_mock.create_image.assert_called_with( + servers[0].id, + servers[0].name, + ) + + self.assertEqual(self.image_columns(images[0]), columns) + self.assertEqual(self.image_data(images[0]), data) + + def test_server_image_create_options(self): + servers = self.setup_servers_mock(count=1) + images = self.setup_images_mock(count=1, servers=servers) + + arglist = [ + '--name', 'img-nam', + servers[0].id, + ] + verifylist = [ + ('name', 'img-nam'), + ('server', servers[0].id), + ] + 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) + + # ServerManager.create_image(server, image_name, metadata=) + self.servers_mock.create_image.assert_called_with( + servers[0].id, + 'img-nam', + ) + + self.assertEqual(self.image_columns(images[0]), columns) + self.assertEqual(self.image_data(images[0]), data) + + @mock.patch.object(common_utils, 'wait_for_status', return_value=False) + def test_server_create_image_wait_fail(self, mock_wait_for_status): + servers = self.setup_servers_mock(count=1) + images = self.setup_images_mock(count=1, servers=servers) + + arglist = [ + '--wait', + servers[0].id, + ] + verifylist = [ + ('wait', True), + ('server', servers[0].id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args, + ) + + # ServerManager.create_image(server, image_name, metadata=) + self.servers_mock.create_image.assert_called_with( + servers[0].id, + servers[0].name, + ) + + mock_wait_for_status.assert_called_once_with( + self.images_mock.get, + images[0].id, + callback=mock.ANY + ) + + @mock.patch.object(common_utils, 'wait_for_status', return_value=True) + def test_server_create_image_wait_ok(self, mock_wait_for_status): + servers = self.setup_servers_mock(count=1) + images = self.setup_images_mock(count=1, servers=servers) + + arglist = [ + '--wait', + servers[0].id, + ] + verifylist = [ + ('wait', True), + ('server', servers[0].id), + ] + 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) + + # ServerManager.create_image(server, image_name, metadata=) + self.servers_mock.create_image.assert_called_with( + servers[0].id, + servers[0].name, + ) + + mock_wait_for_status.assert_called_once_with( + self.images_mock.get, + images[0].id, + callback=mock.ANY + ) + + self.assertEqual(self.image_columns(images[0]), columns) + self.assertEqual(self.image_data(images[0]), data) diff --git a/setup.cfg b/setup.cfg index 78450c007f..3a554228fd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -104,7 +104,6 @@ openstack.compute.v2 = server_add_volume = openstackclient.compute.v2.server:AddServerVolume server_create = openstackclient.compute.v2.server:CreateServer server_delete = openstackclient.compute.v2.server:DeleteServer - server_image_create = openstackclient.compute.v2.server:CreateServerImage server_list = openstackclient.compute.v2.server:ListServer server_lock = openstackclient.compute.v2.server:LockServer server_migrate = openstackclient.compute.v2.server:MigrateServer @@ -138,6 +137,8 @@ openstack.compute.v2 = server_group_list = openstackclient.compute.v2.server_group:ListServerGroup server_group_show = openstackclient.compute.v2.server_group:ShowServerGroup + server_image_create = openstackclient.compute.v2.server_image:CreateServerImage + usage_list = openstackclient.compute.v2.usage:ListUsage usage_show = openstackclient.compute.v2.usage:ShowUsage From eab6cdebdc28c7eaaf330bbf6c9261c4eedf4dce Mon Sep 17 00:00:00 2001 From: Manjeet Singh Bhatia Date: Thu, 14 Apr 2016 20:45:27 +0000 Subject: [PATCH 0943/3095] Add network availability for osc This patch implements openstack client for network ip availability. Implements: blueprint neutron-ip-capacity Depends-On: I3b40d8edea87c068c4e8133e436511765064d5f8 Change-Id: Iffaa2e20ff495fbd205d3397e027e8141d04385e --- .../ip-availability.rst | 7 +- doc/source/commands.rst | 1 + openstackclient/network/v2/ip_availability.py | 109 +++++++++++ openstackclient/tests/network/v2/fakes.py | 44 +++++ .../tests/network/v2/test_ip_availability.py | 180 ++++++++++++++++++ .../ip-availability-ca1cf440f6c70afc.yaml | 5 + setup.cfg | 3 + 7 files changed, 345 insertions(+), 4 deletions(-) rename doc/source/{specs => command-objects}/ip-availability.rst (88%) create mode 100644 openstackclient/network/v2/ip_availability.py create mode 100644 openstackclient/tests/network/v2/test_ip_availability.py create mode 100644 releasenotes/notes/ip-availability-ca1cf440f6c70afc.yaml diff --git a/doc/source/specs/ip-availability.rst b/doc/source/command-objects/ip-availability.rst similarity index 88% rename from doc/source/specs/ip-availability.rst rename to doc/source/command-objects/ip-availability.rst index cf0c71ff38..55b7842775 100644 --- a/doc/source/specs/ip-availability.rst +++ b/doc/source/command-objects/ip-availability.rst @@ -24,12 +24,12 @@ number of allocated IP addresses from that pool. .. option:: --ip-version {4,6} - List IP availability for specific version + List IP availability of given IP version networks (Default is 4) .. option:: --project - List IP availability for specific project + List IP availability of given project (name or ID) ip availability show @@ -57,5 +57,4 @@ subnet within the network as well. .. _ip_availability_show-network .. describe:: - Show network IP availability for specific - network (name or ID) + Show IP availability for a specific network (name or ID) diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 12542d1c73..a165fbfcd2 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -96,6 +96,7 @@ referring to both Compute and Volume quotas. * ``hypervisor stats``: (**Compute**) hypervisor statistics over all compute nodes * ``identity provider``: (**Identity**) a source of users and authentication * ``image``: (**Image**) a disk image +* ``ip availability``: (**Network**) - details of IP usage of a network * ``ip fixed``: (**Compute**, **Network**) - an internal IP address assigned to a server * ``ip floating``: (**Compute**, **Network**) - a public IP address that can be mapped to a server * ``ip floating pool``: (**Compute**, **Network**) - a pool of public IP addresses diff --git a/openstackclient/network/v2/ip_availability.py b/openstackclient/network/v2/ip_availability.py new file mode 100644 index 0000000000..cc240338f3 --- /dev/null +++ b/openstackclient/network/v2/ip_availability.py @@ -0,0 +1,109 @@ +# 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. +# + +"""IP Availability Info implementations""" + +from openstackclient.common import command +from openstackclient.common import utils +from openstackclient.i18n import _ +from openstackclient.identity import common as identity_common + + +_formatters = { + 'subnet_ip_availability': utils.format_list_of_dicts, +} + + +def _get_columns(item): + columns = list(item.keys()) + if 'tenant_id' in columns: + columns.remove('tenant_id') + columns.append('project_id') + return tuple(sorted(columns)) + + +class ListIPAvailability(command.Lister): + """List IP availability for network""" + + def get_parser(self, prog_name): + parser = super(ListIPAvailability, self).get_parser(prog_name) + parser.add_argument( + '--ip-version', + type=int, + choices=[4, 6], + metavar='', + dest='ip_version', + help=_("List IP availability of given IP version networks"), + ) + parser.add_argument( + '--project', + metavar='', + help=_("List IP availability of given project"), + ) + identity_common.add_project_domain_option_to_parser(parser) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + + columns = ( + 'network_id', + 'network_name', + 'total_ips', + 'used_ips', + ) + column_headers = ( + 'Network ID', + 'Network Name', + 'Total IPs', + 'Used IPs', + ) + + filters = {} + if parsed_args.ip_version: + filters['ip_version'] = parsed_args.ip_version + + if parsed_args.project: + identity_client = self.app.client_manager.identity + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + filters['tenant_id'] = project_id + data = client.network_ip_availabilities(**filters) + return (column_headers, + (utils.get_item_properties( + s, columns, + ) for s in data)) + + +class ShowIPAvailability(command.ShowOne): + """Show network IP availability details""" + + def get_parser(self, prog_name): + parser = super(ShowIPAvailability, self).get_parser(prog_name) + parser.add_argument( + 'network', + metavar="", + help=_("Show IP availability for a specific network (name or ID)"), + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.find_network_ip_availability(parsed_args.network, + ignore_missing=False) + columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns, formatters=_formatters) + return columns, data diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 84ede381bf..ee63a0e0f5 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -174,6 +174,50 @@ def create_availability_zones(attrs=None, count=2): return availability_zones +class FakeIPAvailability(object): + """Fake one or more network ip availabilities.""" + + @staticmethod + def create_one_ip_availability(): + """Create a fake list with ip availability stats of a network. + + :return: + A FakeResource object with network_name, network_id, etc. + """ + + # Set default attributes. + network_ip_availability = { + 'network_id': 'network-id-' + uuid.uuid4().hex, + 'network_name': 'network-name-' + uuid.uuid4().hex, + 'tenant_id': '', + 'subnet_ip_availability': [], + 'total_ips': 254, + 'used_ips': 6, + } + + network_ip_availability = fakes.FakeResource( + info=copy.deepcopy(network_ip_availability), + loaded=True) + return network_ip_availability + + @staticmethod + def create_ip_availability(count=2): + """Create fake list of ip availability stats of multiple networks. + + :param int count: + The number of networks to fake + :return: + A list of FakeResource objects faking network ip availability stats + """ + network_ip_availabilities = [] + for i in range(0, count): + network_ip_availability = \ + FakeIPAvailability.create_one_ip_availability() + network_ip_availabilities.append(network_ip_availability) + + return network_ip_availabilities + + class FakeNetwork(object): """Fake one or more networks.""" diff --git a/openstackclient/tests/network/v2/test_ip_availability.py b/openstackclient/tests/network/v2/test_ip_availability.py new file mode 100644 index 0000000000..04979e7710 --- /dev/null +++ b/openstackclient/tests/network/v2/test_ip_availability.py @@ -0,0 +1,180 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +import copy +import mock + +from openstackclient.common import utils as osc_utils +from openstackclient.network.v2 import ip_availability +from openstackclient.tests import fakes +from openstackclient.tests.identity.v3 import fakes as identity_fakes +from openstackclient.tests.network.v2 import fakes as network_fakes +from openstackclient.tests import utils as tests_utils + + +class TestIPAvailability(network_fakes.TestNetworkV2): + + def setUp(self): + super(TestIPAvailability, self).setUp() + + # Get a shortcut to the network client + self.network = self.app.client_manager.network + + # Set identity client v3. And get a shortcut to Identity client. + identity_client = identity_fakes.FakeIdentityv3Client( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN, + ) + self.app.client_manager.identity = identity_client + self.identity = self.app.client_manager.identity + + # Get a shortcut to the ProjectManager Mock + self.projects_mock = self.identity.projects + self.projects_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.PROJECT), + loaded=True, + ) + + +class TestListIPAvailability(TestIPAvailability): + + _ip_availability = \ + network_fakes.FakeIPAvailability.create_ip_availability(count=3) + columns = ( + 'Network ID', + 'Network Name', + 'Total IPs', + 'Used IPs', + ) + data = [] + for net in _ip_availability: + data.append(( + net.network_id, + net.network_name, + net.total_ips, + net.used_ips, + )) + + def setUp(self): + super(TestListIPAvailability, self).setUp() + + self.cmd = ip_availability.ListIPAvailability( + self.app, self.namespace) + self.network.network_ip_availabilities = mock.Mock( + return_value=self._ip_availability) + + def test_list_no_options(self): + arglist = [] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.network_ip_availabilities.assert_called_once_with() + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_list_ip_version(self): + arglist = [ + '--ip-version', str(4), + ] + verifylist = [ + ('ip_version', 4) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'ip_version': 4} + + self.network.network_ip_availabilities.assert_called_once_with( + **filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_list_project(self): + arglist = [ + '--project', identity_fakes.project_name + ] + verifylist = [ + ('project', identity_fakes.project_name) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'tenant_id': identity_fakes.project_id} + + self.network.network_ip_availabilities.assert_called_once_with( + **filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + +class TestShowIPAvailability(TestIPAvailability): + + _ip_availability = \ + network_fakes.FakeIPAvailability.create_one_ip_availability() + + columns = ( + 'network_id', + 'network_name', + 'project_id', + 'subnet_ip_availability', + 'total_ips', + 'used_ips', + ) + data = ( + _ip_availability.network_id, + _ip_availability.network_name, + _ip_availability.tenant_id, + osc_utils.format_list( + _ip_availability.subnet_ip_availability), + _ip_availability.total_ips, + _ip_availability.used_ips, + ) + + def setUp(self): + super(TestShowIPAvailability, self).setUp() + + self.network.find_network_ip_availability = mock.Mock( + return_value=self._ip_availability) + + # Get the command object to test + self.cmd = ip_availability.ShowIPAvailability( + self.app, self.namespace) + + def test_show_no_option(self): + arglist = [] + verifylist = [] + + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, arglist, verifylist) + + def test_show_all_options(self): + arglist = [ + self._ip_availability.network_name, + ] + verifylist = [ + ('network', self._ip_availability.network_name) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.network.find_network_ip_availability.assert_called_once_with( + self._ip_availability.network_name, + ignore_missing=False) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) diff --git a/releasenotes/notes/ip-availability-ca1cf440f6c70afc.yaml b/releasenotes/notes/ip-availability-ca1cf440f6c70afc.yaml new file mode 100644 index 0000000000..81b217c092 --- /dev/null +++ b/releasenotes/notes/ip-availability-ca1cf440f6c70afc.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add support for the ``ip availability list`` and ``ip availability show`` commands. + [Blueprint `neutron-ip-capacity `_] diff --git a/setup.cfg b/setup.cfg index a62f5d25cd..97a47f31c0 100644 --- a/setup.cfg +++ b/setup.cfg @@ -330,6 +330,9 @@ openstack.network.v2 = address_scope_set = openstackclient.network.v2.address_scope:SetAddressScope address_scope_show = openstackclient.network.v2.address_scope:ShowAddressScope + ip_availability_list = openstackclient.network.v2.ip_availability:ListIPAvailability + ip_availability_show = openstackclient.network.v2.ip_availability:ShowIPAvailability + ip_floating_create = openstackclient.network.v2.floating_ip:CreateFloatingIP ip_floating_delete = openstackclient.network.v2.floating_ip:DeleteFloatingIP ip_floating_list = openstackclient.network.v2.floating_ip:ListFloatingIP From 4883c2b72ab2c8086acfdb60085d3d3d80adf33f Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sat, 4 Jun 2016 06:18:46 +0000 Subject: [PATCH 0944/3095] Imported Translations from Zanata For more information about this automatic import see: https://wiki.openstack.org/wiki/Translations/Infrastructure Change-Id: Icda8a1c87ded27c1ba061d5692e95a1477206b92 --- .../locale/de/LC_MESSAGES/openstackclient.po | 117 +- openstackclient/locale/openstackclient.pot | 1410 ----------------- .../zh_TW/LC_MESSAGES/openstackclient.po | 63 +- 3 files changed, 41 insertions(+), 1549 deletions(-) delete mode 100644 openstackclient/locale/openstackclient.pot diff --git a/openstackclient/locale/de/LC_MESSAGES/openstackclient.po b/openstackclient/locale/de/LC_MESSAGES/openstackclient.po index f7445b1cc2..06f1c8d672 100644 --- a/openstackclient/locale/de/LC_MESSAGES/openstackclient.po +++ b/openstackclient/locale/de/LC_MESSAGES/openstackclient.po @@ -8,13 +8,13 @@ # Andreas Jaeger , 2016. #zanata msgid "" msgstr "" -"Project-Id-Version: python-openstackclient 2.2.1.dev235\n" +"Project-Id-Version: python-openstackclient 2.5.1.dev51\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2016-04-19 05:02+0000\n" +"POT-Creation-Date: 2016-06-03 19:37+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2015-09-19 07:10+0000\n" +"PO-Revision-Date: 2016-06-02 01:43+0000\n" "Last-Translator: Andreas Jaeger \n" "Language: de\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" @@ -22,69 +22,6 @@ msgstr "" "X-Generator: Zanata 3.7.3\n" "Language-Team: German\n" -msgid "" -"\n" -"Complete\n" -msgstr "" -"\n" -"Fertig\n" - -msgid "" -"\n" -"Error creating server" -msgstr "" -"\n" -"Fehler beim Erstellen des Servers" - -msgid "" -"\n" -"Error creating server snapshot" -msgstr "" -"\n" -"Fehler beim Erstellen der Server-Schattenkopie" - -msgid "" -"\n" -"Error deleting server" -msgstr "" -"\n" -"Fehler beim Löschen des Servers" - -msgid "" -"\n" -"Error migrating server" -msgstr "" -"\n" -"Fehler beim Migrieren des Servers" - -msgid "" -"\n" -"Error rebooting server\n" -msgstr "" -"\n" -"Fehler beim Neustarten des Servers\n" - -msgid "" -"\n" -"Error rebuilding server" -msgstr "" -"\n" -"Fehler bei der Wiederherstellung des Servers" - -msgid "" -"\n" -"Error resizing server" -msgstr "" -"\n" -"Fehler bei der Größenänderung des Servers" - -msgid "" -"\n" -"Reboot complete\n" -msgstr "" -"\n" -"Neustart abgeschlossen\n" - msgid "Add a property to (repeat option to set multiple properties)" msgstr "" "Fügen Sie eine Eigenschaft zu hinzu (wiederholen Sie die Option, um " @@ -93,6 +30,9 @@ msgstr "" msgid "Allow disk over-commit on the destination host" msgstr "Festplattenüberladung auf dem Zielhost erlauben" +msgid "Availability zone name" +msgstr "Name der Verfügbarkeitszone" + msgid "Complete\n" msgstr "Fertig\n" @@ -138,6 +78,9 @@ msgstr "Anmeldedaten für Benutzer löschen (name or ID)" msgid "Destination port (ssh -p option)" msgstr "Zielport (ssh -p Option)" +msgid "Disable a service" +msgstr "Dienst deaktivieren" + msgid "Disable project" msgstr "Projekt deaktivieren" @@ -162,13 +105,19 @@ msgstr "Benutzer aktivieren (Standardeinstellung)" msgid "Endpoint ID to delete" msgstr "Zu löschende Endpunktkennung" -msgid "Endpoint ID to display" -msgstr "Anzuzeigende Endpunktkennung" +msgid "Error creating server\n" +msgstr "Fehler beim Erstellen des Servers\n" + +msgid "Error creating server snapshot\n" +msgstr "Fehler beim Erstellen der Server-Schattenkopie\n" #, python-format msgid "Error creating server: %s" msgstr "Fehler beim Erstellen des Servers: %s" +msgid "Error deleting server\n" +msgstr "Fehler beim Löschen des Servers\n" + #, python-format msgid "Error deleting server: %s" msgstr "Fehler beim Löschen des Servers: %s" @@ -188,6 +137,9 @@ msgstr "Benutzer nach filtern (Name oder Kennung)" msgid "Filter users by project (name or ID)" msgstr "Benutzer nach Projekt filtern (Name oder Kennung)" +msgid "Floating IP address" +msgstr "Bewegliche IP-Adresse" + msgid "Hints for the scheduler (optional extension)" msgstr "Hinweise für den Planer (optionale Erweiterung)" @@ -209,6 +161,9 @@ msgstr "Zusätzliche Felder in der Ausgabe auflisten" msgid "Login name (ssh -l option)" msgstr "Anmeldename (ssh -l Option)" +msgid "MD5 hash" +msgstr "MD5-Hashwert" + msgid "" "Map block devices; map is ::: " "(optional extension)" @@ -442,27 +397,27 @@ msgstr "" "OS_DOMAIN_NAME oder auth.domain_name." msgid "" -"Set a service AUTH_URL, with --os-auth-url, OS_AUTH_URL or auth.auth_url\n" +"Set a service AUTH_URL, with --os-auth-url, OS_AUTH_URL or auth.auth_url" msgstr "" "Legen Sie eine Dienst-AUTH_URL mit --os-auth-url, OS_AUTH_URL oder auth." -"auth_url fest\n" +"auth_url fest" -msgid "Set a service URL, with --os-url, OS_URL or auth.url\n" -msgstr "Legen Sie eine Dienst-URL mit --os-url, OS_URL oder auth.url fest\n" +msgid "Set a service URL, with --os-url, OS_URL or auth.url" +msgstr "Legen Sie eine Dienst-URL mit --os-url, OS_URL oder auth.url fest" -msgid "Set a token with --os-token, OS_TOKEN or auth.token\n" -msgstr "Legen Sie einen Token mit --os-token, OS_TOKEN oder auth.token fest\n" +msgid "Set a token with --os-token, OS_TOKEN or auth.token" +msgstr "Legen Sie einen Token mit --os-token, OS_TOKEN oder auth.token fest" -msgid "Set a username with --os-username, OS_USERNAME, or auth.username\n" +msgid "Set a username with --os-username, OS_USERNAME, or auth.username" msgstr "" "Legen Sie einen Benutzernamen mit --os-username, OS_USERNAME oder auth." -"username fest\n" +"username fest" msgid "" -"Set an authentication URL, with --os-auth-url, OS_AUTH_URL or auth.auth_url\n" +"Set an authentication URL, with --os-auth-url, OS_AUTH_URL or auth.auth_url" msgstr "" "Legen Sie eine Authentifizierungs-URL mit --os-auth-url, OS_AUTH_URL oder " -"auth.auth_url fest\n" +"auth.auth_url fest" msgid "Set default project (name or ID)" msgstr "Standardprojekt festlegen (Name oder Kennung)" @@ -504,6 +459,9 @@ msgstr "" msgid "Token to be deleted" msgstr "Zu löschender Token" +msgid "URL" +msgstr "URL" + msgid "Use only IPv4 addresses" msgstr "Nur IPv4-Adressen verwenden" @@ -543,6 +501,9 @@ msgstr "Aufzulistender Benutzer (Name oder Kennung)" msgid "User(s) to delete (name or ID)" msgstr "Zu löschende(r) Benutzer (Name oder Kennung)" +msgid "Version" +msgstr "Version" + msgid "Volume to add (name or ID)" msgstr "Zu hinzufügender Datenträger (Name oder Kennung)" diff --git a/openstackclient/locale/openstackclient.pot b/openstackclient/locale/openstackclient.pot deleted file mode 100644 index e197fa7b79..0000000000 --- a/openstackclient/locale/openstackclient.pot +++ /dev/null @@ -1,1410 +0,0 @@ -# Translations template for python-openstackclient. -# Copyright (C) 2016 ORGANIZATION -# This file is distributed under the same license as the -# python-openstackclient project. -# FIRST AUTHOR , 2016. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: python-openstackclient 2.2.1.dev235\n" -"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2016-04-19 06:14+0000\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.2.0\n" - -#: openstackclient/api/auth.py:148 -msgid "Set a username with --os-username, OS_USERNAME, or auth.username\n" -msgstr "" - -#: openstackclient/api/auth.py:151 -msgid "" -"Set an authentication URL, with --os-auth-url, OS_AUTH_URL or " -"auth.auth_url\n" -msgstr "" - -#: openstackclient/api/auth.py:160 -msgid "" -"Set a scope, such as a project or domain, set a project scope with --os-" -"project-name, OS_PROJECT_NAME or auth.project_name, set a domain scope " -"with --os-domain-name, OS_DOMAIN_NAME or auth.domain_name" -msgstr "" - -#: openstackclient/api/auth.py:166 openstackclient/api/auth.py:172 -msgid "Set a token with --os-token, OS_TOKEN or auth.token\n" -msgstr "" - -#: openstackclient/api/auth.py:168 -msgid "Set a service AUTH_URL, with --os-auth-url, OS_AUTH_URL or auth.auth_url\n" -msgstr "" - -#: openstackclient/api/auth.py:174 -msgid "Set a service URL, with --os-url, OS_URL or auth.url\n" -msgstr "" - -#: openstackclient/common/availability_zone.py:110 -#: openstackclient/compute/v2/server.py:757 -#: openstackclient/identity/v2_0/endpoint.py:101 -#: openstackclient/identity/v2_0/project.py:133 -#: openstackclient/identity/v2_0/service.py:115 -#: openstackclient/identity/v2_0/user.py:162 -#: openstackclient/network/v2/router.py:230 -#: openstackclient/network/v2/subnet.py:299 -#: openstackclient/network/v2/subnet_pool.py:165 -msgid "List additional fields in output" -msgstr "" - -#: openstackclient/common/parseractions.py:99 -#, python-format -msgid "" -"Invalid keys %(invalid_keys)s specified.\n" -"Valid keys are: %(valid_keys)s." -msgstr "" - -#: openstackclient/common/parseractions.py:109 -#, python-format -msgid "" -"Missing required keys %(missing_keys)s.\n" -"Required keys are: %(required_keys)s." -msgstr "" - -#: openstackclient/compute/v2/server.py:195 -#: openstackclient/compute/v2/server.py:227 -#: openstackclient/compute/v2/server.py:598 -#: openstackclient/compute/v2/server.py:937 -#: openstackclient/compute/v2/server.py:1039 -#: openstackclient/compute/v2/server.py:1091 -#: openstackclient/compute/v2/server.py:1177 -#: openstackclient/compute/v2/server.py:1213 -#: openstackclient/compute/v2/server.py:1236 -#: openstackclient/compute/v2/server.py:1343 -#: openstackclient/compute/v2/server.py:1421 -#: openstackclient/compute/v2/server.py:1455 -#: openstackclient/compute/v2/server.py:1712 -#: openstackclient/compute/v2/server.py:1733 -msgid "Server (name or ID)" -msgstr "" - -#: openstackclient/compute/v2/server.py:200 -msgid "Security group to add (name or ID)" -msgstr "" - -#: openstackclient/compute/v2/server.py:232 -msgid "Volume to add (name or ID)" -msgstr "" - -#: openstackclient/compute/v2/server.py:237 -msgid "Server internal device name for volume" -msgstr "" - -#: openstackclient/compute/v2/server.py:269 -#: openstackclient/compute/v2/server.py:1348 -msgid "New server name" -msgstr "" - -#: openstackclient/compute/v2/server.py:277 -msgid "Create server from this image (name or ID)" -msgstr "" - -#: openstackclient/compute/v2/server.py:282 -msgid "Create server from this volume (name or ID)" -msgstr "" - -#: openstackclient/compute/v2/server.py:288 -msgid "Create server with this flavor (name or ID)" -msgstr "" - -#: openstackclient/compute/v2/server.py:295 -msgid "" -"Security group to assign to this server (name or ID) (repeat option to " -"set multiple groups)" -msgstr "" - -#: openstackclient/compute/v2/server.py:301 -msgid "Keypair to inject into this server (optional extension)" -msgstr "" - -#: openstackclient/compute/v2/server.py:307 -msgid "Set a property on this server (repeat option to set multiple values)" -msgstr "" - -#: openstackclient/compute/v2/server.py:315 -msgid "" -"File to inject into image before boot (repeat option to set multiple " -"files)" -msgstr "" - -#: openstackclient/compute/v2/server.py:321 -msgid "User data file to serve from the metadata server" -msgstr "" - -#: openstackclient/compute/v2/server.py:326 -msgid "Select an availability zone for the server" -msgstr "" - -#: openstackclient/compute/v2/server.py:333 -msgid "" -"Map block devices; map is ::: " -"(optional extension)" -msgstr "" - -#: openstackclient/compute/v2/server.py:343 -msgid "" -"Create a NIC on the server. Specify option multiple times to create " -"multiple NICs. Either net-id or port-id must be provided, but not both. " -"net-id: attach NIC to network with this UUID, port-id: attach NIC to port" -" with this UUID, v4-fixed-ip: IPv4 fixed address for NIC (optional), v6" -"-fixed-ip: IPv6 fixed address for NIC (optional)." -msgstr "" - -#: openstackclient/compute/v2/server.py:356 -msgid "Hints for the scheduler (optional extension)" -msgstr "" - -#: openstackclient/compute/v2/server.py:362 -msgid "" -"Use specified volume as the config drive, or 'True' to use an ephemeral " -"drive" -msgstr "" - -#: openstackclient/compute/v2/server.py:370 -msgid "Minimum number of servers to launch (default=1)" -msgstr "" - -#: openstackclient/compute/v2/server.py:377 -msgid "Maximum number of servers to launch (default=1)" -msgstr "" - -#: openstackclient/compute/v2/server.py:382 -msgid "Wait for build to complete" -msgstr "" - -#: openstackclient/compute/v2/server.py:421 -msgid "min instances should be <= max instances" -msgstr "" - -#: openstackclient/compute/v2/server.py:424 -msgid "min instances should be > 0" -msgstr "" - -#: openstackclient/compute/v2/server.py:427 -msgid "max instances should be > 0" -msgstr "" - -#: openstackclient/compute/v2/server.py:456 -msgid "Volume name or ID must be specified if --block-device-mapping is specified" -msgstr "" - -#: openstackclient/compute/v2/server.py:468 -msgid "either net-id or port-id should be specified but not both" -msgstr "" - -#: openstackclient/compute/v2/server.py:488 -msgid "can't create server with port specified since network endpoint not enabled" -msgstr "" - -#: openstackclient/compute/v2/server.py:553 -#, python-format -msgid "Error creating server: %s" -msgstr "" - -#: openstackclient/compute/v2/server.py:555 -msgid "" -"\n" -"Error creating server" -msgstr "" - -#: openstackclient/compute/v2/server.py:577 -msgid "Server(s) to create dump file (name or ID)" -msgstr "" - -#: openstackclient/compute/v2/server.py:603 -msgid "Name of new image (default is server name)" -msgstr "" - -#: openstackclient/compute/v2/server.py:608 -msgid "Wait for image create to complete" -msgstr "" - -#: openstackclient/compute/v2/server.py:637 -#, python-format -msgid "Error creating snapshot of server: %s" -msgstr "" - -#: openstackclient/compute/v2/server.py:639 -msgid "" -"\n" -"Error creating server snapshot" -msgstr "" - -#: openstackclient/compute/v2/server.py:656 -msgid "Server(s) to delete (name or ID)" -msgstr "" - -#: openstackclient/compute/v2/server.py:661 -msgid "Wait for delete to complete" -msgstr "" - -#: openstackclient/compute/v2/server.py:679 -#, python-format -msgid "Error deleting server: %s" -msgstr "" - -#: openstackclient/compute/v2/server.py:681 -msgid "" -"\n" -"Error deleting server" -msgstr "" - -#: openstackclient/compute/v2/server.py:693 -msgid "Only return instances that match the reservation" -msgstr "" - -#: openstackclient/compute/v2/server.py:698 -msgid "Regular expression to match IP addresses" -msgstr "" - -#: openstackclient/compute/v2/server.py:703 -msgid "Regular expression to match IPv6 addresses" -msgstr "" - -#: openstackclient/compute/v2/server.py:708 -msgid "Regular expression to match names" -msgstr "" - -#: openstackclient/compute/v2/server.py:713 -msgid "Regular expression to match instance name (admin only)" -msgstr "" - -#: openstackclient/compute/v2/server.py:719 -msgid "Search by server status" -msgstr "" - -#: openstackclient/compute/v2/server.py:724 -msgid "Search by flavor (name or ID)" -msgstr "" - -#: openstackclient/compute/v2/server.py:729 -msgid "Search by image (name or ID)" -msgstr "" - -#: openstackclient/compute/v2/server.py:734 -msgid "Search by hostname" -msgstr "" - -#: openstackclient/compute/v2/server.py:740 -msgid "Include all projects (admin only)" -msgstr "" - -#: openstackclient/compute/v2/server.py:750 -msgid "Search by user (admin only) (name or ID)" -msgstr "" - -#: openstackclient/compute/v2/server.py:905 -msgid "Server(s) to lock (name or ID)" -msgstr "" - -#: openstackclient/compute/v2/server.py:942 -msgid "Target hostname" -msgstr "" - -#: openstackclient/compute/v2/server.py:950 -msgid "Perform a shared live migration (default)" -msgstr "" - -#: openstackclient/compute/v2/server.py:956 -msgid "Perform a block live migration" -msgstr "" - -#: openstackclient/compute/v2/server.py:963 -msgid "Allow disk over-commit on the destination host" -msgstr "" - -#: openstackclient/compute/v2/server.py:970 -msgid "Do not over-commit disk on the destination host (default)" -msgstr "" - -#: openstackclient/compute/v2/server.py:976 -#: openstackclient/compute/v2/server.py:1256 -msgid "Wait for resize to complete" -msgstr "" - -#: openstackclient/compute/v2/server.py:1003 -#: openstackclient/compute/v2/server.py:1280 -msgid "Complete\n" -msgstr "" - -#: openstackclient/compute/v2/server.py:1005 -msgid "" -"\n" -"Error migrating server" -msgstr "" - -#: openstackclient/compute/v2/server.py:1018 -msgid "Server(s) to pause (name or ID)" -msgstr "" - -#: openstackclient/compute/v2/server.py:1048 -msgid "Perform a hard reboot" -msgstr "" - -#: openstackclient/compute/v2/server.py:1056 -msgid "Perform a soft reboot" -msgstr "" - -#: openstackclient/compute/v2/server.py:1061 -msgid "Wait for reboot to complete" -msgstr "" - -#: openstackclient/compute/v2/server.py:1077 -msgid "" -"\n" -"Reboot complete\n" -msgstr "" - -#: openstackclient/compute/v2/server.py:1079 -msgid "" -"\n" -"Error rebooting server\n" -msgstr "" - -#: openstackclient/compute/v2/server.py:1096 -msgid "" -"Recreate server from the specified image (name or ID). Defaults to the " -"currently used one." -msgstr "" - -#: openstackclient/compute/v2/server.py:1107 -msgid "Wait for rebuild to complete" -msgstr "" - -#: openstackclient/compute/v2/server.py:1128 -msgid "" -"\n" -"Complete\n" -msgstr "" - -#: openstackclient/compute/v2/server.py:1130 -msgid "" -"\n" -"Error rebuilding server" -msgstr "" - -#: openstackclient/compute/v2/server.py:1145 -msgid "Name or ID of server to use" -msgstr "" - -#: openstackclient/compute/v2/server.py:1150 -msgid "Name or ID of security group to remove from server" -msgstr "" - -#: openstackclient/compute/v2/server.py:1182 -msgid "Volume to remove (name or ID)" -msgstr "" - -#: openstackclient/compute/v2/server.py:1241 -msgid "Resize server to specified flavor" -msgstr "" - -#: openstackclient/compute/v2/server.py:1246 -msgid "Confirm server resize is complete" -msgstr "" - -#: openstackclient/compute/v2/server.py:1251 -msgid "Restore server state before resize" -msgstr "" - -#: openstackclient/compute/v2/server.py:1282 -msgid "" -"\n" -"Error resizing server" -msgstr "" - -#: openstackclient/compute/v2/server.py:1299 -msgid "Server(s) to restore (name or ID)" -msgstr "" - -#: openstackclient/compute/v2/server.py:1321 -msgid "Server(s) to resume (name or ID)" -msgstr "" - -#: openstackclient/compute/v2/server.py:1353 -msgid "Set new root password (interactive only)" -msgstr "" - -#: openstackclient/compute/v2/server.py:1359 -msgid "" -"Property to add/change for this server (repeat option to set multiple " -"properties)" -msgstr "" - -#: openstackclient/compute/v2/server.py:1382 -msgid "New password: " -msgstr "" - -#: openstackclient/compute/v2/server.py:1383 -msgid "Retype new password: " -msgstr "" - -#: openstackclient/compute/v2/server.py:1387 -msgid "Passwords do not match, password unchanged" -msgstr "" - -#: openstackclient/compute/v2/server.py:1400 -msgid "Server(s) to shelve (name or ID)" -msgstr "" - -#: openstackclient/compute/v2/server.py:1427 -msgid "Display server diagnostics information" -msgstr "" - -#: openstackclient/compute/v2/server.py:1439 -msgid "Error retrieving diagnostics data" -msgstr "" - -#: openstackclient/compute/v2/server.py:1460 -msgid "Login name (ssh -l option)" -msgstr "" - -#: openstackclient/compute/v2/server.py:1472 -msgid "Destination port (ssh -p option)" -msgstr "" - -#: openstackclient/compute/v2/server.py:1484 -msgid "Private key file (ssh -i option)" -msgstr "" - -#: openstackclient/compute/v2/server.py:1495 -msgid "Options in ssh_config(5) format (ssh -o option)" -msgstr "" - -#: openstackclient/compute/v2/server.py:1509 -msgid "Use only IPv4 addresses" -msgstr "" - -#: openstackclient/compute/v2/server.py:1516 -msgid "Use only IPv6 addresses" -msgstr "" - -#: openstackclient/compute/v2/server.py:1525 -msgid "Use public IP address" -msgstr "" - -#: openstackclient/compute/v2/server.py:1533 -msgid "Use private IP address" -msgstr "" - -#: openstackclient/compute/v2/server.py:1540 -msgid "Use other IP address (public, private, etc)" -msgstr "" - -#: openstackclient/compute/v2/server.py:1600 -msgid "Server(s) to start (name or ID)" -msgstr "" - -#: openstackclient/compute/v2/server.py:1622 -msgid "Server(s) to stop (name or ID)" -msgstr "" - -#: openstackclient/compute/v2/server.py:1644 -msgid "Server(s) to suspend (name or ID)" -msgstr "" - -#: openstackclient/compute/v2/server.py:1667 -msgid "Server(s) to unlock (name or ID)" -msgstr "" - -#: openstackclient/compute/v2/server.py:1690 -msgid "Server(s) to unpause (name or ID)" -msgstr "" - -#: openstackclient/compute/v2/server.py:1740 -msgid "" -"Property key to remove from server (repeat option to remove multiple " -"values)" -msgstr "" - -#: openstackclient/compute/v2/server.py:1768 -msgid "Server(s) to unshelve (name or ID)" -msgstr "" - -#: openstackclient/compute/v2/service.py:138 -msgid "argument --disable-reason has been ignored" -msgstr "" - -#: openstackclient/identity/v2_0/catalog.py:67 -#: openstackclient/identity/v3/catalog.py:64 -msgid "Service to display (type or name)" -msgstr "" - -#: openstackclient/identity/v2_0/ec2creds.py:34 -#: openstackclient/identity/v3/ec2creds.py:59 -msgid "" -"Create credentials in project (name or ID; default: current authenticated" -" project)" -msgstr "" - -#: openstackclient/identity/v2_0/ec2creds.py:42 -#: openstackclient/identity/v3/ec2creds.py:67 -msgid "" -"Create credentials for user (name or ID; default: current authenticated " -"user)" -msgstr "" - -#: openstackclient/identity/v2_0/ec2creds.py:90 -#: openstackclient/identity/v2_0/ec2creds.py:157 -#: openstackclient/identity/v3/ec2creds.py:120 -#: openstackclient/identity/v3/ec2creds.py:172 -msgid "Credentials access key" -msgstr "" - -#: openstackclient/identity/v2_0/ec2creds.py:95 -#: openstackclient/identity/v3/ec2creds.py:125 -msgid "Delete credentials for user (name or ID)" -msgstr "" - -#: openstackclient/identity/v2_0/ec2creds.py:122 -#: openstackclient/identity/v3/ec2creds.py:144 -msgid "Filter list by user (name or ID)" -msgstr "" - -#: openstackclient/identity/v2_0/ec2creds.py:162 -#: openstackclient/identity/v3/ec2creds.py:177 -msgid "Show credentials for user (name or ID)" -msgstr "" - -#: openstackclient/identity/v2_0/endpoint.py:34 -msgid "New endpoint service (name or ID)" -msgstr "" - -#: openstackclient/identity/v2_0/endpoint.py:40 -msgid "New endpoint public URL (required)" -msgstr "" - -#: openstackclient/identity/v2_0/endpoint.py:45 -msgid "New endpoint admin URL" -msgstr "" - -#: openstackclient/identity/v2_0/endpoint.py:50 -msgid "New endpoint internal URL" -msgstr "" - -#: openstackclient/identity/v2_0/endpoint.py:55 -msgid "New endpoint region ID" -msgstr "" - -#: openstackclient/identity/v2_0/endpoint.py:84 -msgid "Endpoint ID to delete" -msgstr "" - -#: openstackclient/identity/v2_0/endpoint.py:133 -msgid "Endpoint ID to display" -msgstr "" - -#: openstackclient/identity/v2_0/project.py:36 -msgid "New project name" -msgstr "" - -#: openstackclient/identity/v2_0/project.py:41 -msgid "Project description" -msgstr "" - -#: openstackclient/identity/v2_0/project.py:47 -msgid "Enable project (default)" -msgstr "" - -#: openstackclient/identity/v2_0/project.py:52 -#: openstackclient/identity/v2_0/project.py:179 -msgid "Disable project" -msgstr "" - -#: openstackclient/identity/v2_0/project.py:58 -msgid "Add a property to (repeat option to set multiple properties)" -msgstr "" - -#: openstackclient/identity/v2_0/project.py:64 -#: openstackclient/identity/v3/project.py:75 -msgid "Return existing project" -msgstr "" - -#: openstackclient/identity/v2_0/project.py:109 -msgid "Project(s) to delete (name or ID)" -msgstr "" - -#: openstackclient/identity/v2_0/project.py:158 -#: openstackclient/identity/v2_0/project.py:291 -msgid "Project to modify (name or ID)" -msgstr "" - -#: openstackclient/identity/v2_0/project.py:163 -msgid "Set project name" -msgstr "" - -#: openstackclient/identity/v2_0/project.py:168 -msgid "Set project description" -msgstr "" - -#: openstackclient/identity/v2_0/project.py:174 -msgid "Enable project" -msgstr "" - -#: openstackclient/identity/v2_0/project.py:185 -msgid "Set a project property (repeat option to set multiple properties)" -msgstr "" - -#: openstackclient/identity/v2_0/project.py:234 -msgid "Project to display (name or ID)" -msgstr "" - -#: openstackclient/identity/v2_0/project.py:298 -msgid "Unset a project property (repeat option to unset multiple properties)" -msgstr "" - -#: openstackclient/identity/v2_0/role.py:36 -msgid "Role to add to : (name or ID)" -msgstr "" - -#: openstackclient/identity/v2_0/role.py:42 -#: openstackclient/identity/v2_0/role.py:288 -msgid "Include (name or ID)" -msgstr "" - -#: openstackclient/identity/v2_0/role.py:48 -#: openstackclient/identity/v2_0/role.py:294 -msgid "Include (name or ID)" -msgstr "" - -#: openstackclient/identity/v2_0/role.py:79 -msgid "New role name" -msgstr "" - -#: openstackclient/identity/v2_0/role.py:84 -#: openstackclient/identity/v3/role.py:155 -msgid "Return existing role" -msgstr "" - -#: openstackclient/identity/v2_0/role.py:116 -msgid "Role(s) to delete (name or ID)" -msgstr "" - -#: openstackclient/identity/v2_0/role.py:178 -#: openstackclient/identity/v2_0/role.py:238 -msgid "Project must be specified" -msgstr "" - -#: openstackclient/identity/v2_0/role.py:192 -#: openstackclient/identity/v2_0/role.py:244 -msgid "User must be specified" -msgstr "" - -#: openstackclient/identity/v2_0/role.py:218 -msgid "User to list (name or ID)" -msgstr "" - -#: openstackclient/identity/v2_0/role.py:223 -msgid "Filter users by (name or ID)" -msgstr "" - -#: openstackclient/identity/v2_0/role.py:282 -msgid "Role to remove (name or ID)" -msgstr "" - -#: openstackclient/identity/v2_0/role.py:320 -msgid "Role to display (name or ID)" -msgstr "" - -#: openstackclient/identity/v2_0/service.py:36 -msgid "New service type (compute, image, identity, volume, etc)" -msgstr "" - -#: openstackclient/identity/v2_0/service.py:47 -msgid "New service name" -msgstr "" - -#: openstackclient/identity/v2_0/service.py:52 -msgid "New service description" -msgstr "" - -#: openstackclient/identity/v2_0/service.py:71 -msgid "" -"The argument --type is deprecated, use service create --name type instead." -msgstr "" - -#: openstackclient/identity/v2_0/service.py:96 -msgid "Service to delete (name or ID)" -msgstr "" - -#: openstackclient/identity/v2_0/service.py:140 -msgid "Service to display (type, name or ID)" -msgstr "" - -#: openstackclient/identity/v2_0/service.py:146 -msgid "Show service catalog information" -msgstr "" - -#: openstackclient/identity/v2_0/service.py:163 -#, python-format -msgid "No service catalog with a type, name or ID of '%s' exists." -msgstr "" - -#: openstackclient/identity/v2_0/token.py:50 -msgid "Token to be deleted" -msgstr "" - -#: openstackclient/identity/v2_0/user.py:35 -msgid "New user name" -msgstr "" - -#: openstackclient/identity/v2_0/user.py:40 -msgid "Default project (name or ID)" -msgstr "" - -#: openstackclient/identity/v2_0/user.py:45 -#: openstackclient/identity/v2_0/user.py:258 -msgid "Set user password" -msgstr "" - -#: openstackclient/identity/v2_0/user.py:51 -#: openstackclient/identity/v2_0/user.py:264 -msgid "Prompt interactively for password" -msgstr "" - -#: openstackclient/identity/v2_0/user.py:56 -#: openstackclient/identity/v2_0/user.py:269 -msgid "Set user email address" -msgstr "" - -#: openstackclient/identity/v2_0/user.py:62 -#: openstackclient/identity/v2_0/user.py:275 -msgid "Enable user (default)" -msgstr "" - -#: openstackclient/identity/v2_0/user.py:67 -#: openstackclient/identity/v2_0/user.py:280 -msgid "Disable user" -msgstr "" - -#: openstackclient/identity/v2_0/user.py:72 -#: openstackclient/identity/v3/user.py:86 -msgid "Return existing user" -msgstr "" - -#: openstackclient/identity/v2_0/user.py:133 -msgid "User(s) to delete (name or ID)" -msgstr "" - -#: openstackclient/identity/v2_0/user.py:156 -msgid "Filter users by project (name or ID)" -msgstr "" - -#: openstackclient/identity/v2_0/user.py:243 -msgid "User to change (name or ID)" -msgstr "" - -#: openstackclient/identity/v2_0/user.py:248 -msgid "Set user name" -msgstr "" - -#: openstackclient/identity/v2_0/user.py:253 -msgid "Set default project (name or ID)" -msgstr "" - -#: openstackclient/identity/v2_0/user.py:342 -msgid "User to display (name or ID)" -msgstr "" - -#: openstackclient/identity/v3/domain.py:57 -msgid "Return existing domain" -msgstr "" - -#: openstackclient/identity/v3/group.py:130 -msgid "Return existing group" -msgstr "" - -#: openstackclient/identity/v3/region.py:33 -msgid "New region ID" -msgstr "" - -#: openstackclient/identity/v3/region.py:38 -msgid "Parent region ID" -msgstr "" - -#: openstackclient/identity/v3/region.py:43 -#: openstackclient/identity/v3/region.py:128 -msgid "New region description" -msgstr "" - -#: openstackclient/identity/v3/region.py:70 -msgid "Region ID to delete" -msgstr "" - -#: openstackclient/identity/v3/region.py:88 -msgid "Filter by parent region ID" -msgstr "" - -#: openstackclient/identity/v3/region.py:118 -msgid "Region to modify" -msgstr "" - -#: openstackclient/identity/v3/region.py:123 -msgid "New parent region ID" -msgstr "" - -#: openstackclient/identity/v3/region.py:154 -msgid "Region to display" -msgstr "" - -#: openstackclient/image/v1/image.py:191 openstackclient/image/v1/image.py:609 -#: openstackclient/image/v2/image.py:283 openstackclient/image/v2/image.py:794 -msgid "The --owner option is deprecated, please use --project instead." -msgstr "" - -#: openstackclient/network/v2/floating_ip.py:67 -msgid "Network to allocate floating IP from (name or ID)" -msgstr "" - -#: openstackclient/network/v2/floating_ip.py:75 -msgid "Subnet on which you want to create the floating IP (name or ID)" -msgstr "" - -#: openstackclient/network/v2/floating_ip.py:81 -msgid "Port to be associated with the floating IP (name or ID)" -msgstr "" - -#: openstackclient/network/v2/floating_ip.py:88 -msgid "Floating IP address" -msgstr "" - -#: openstackclient/network/v2/floating_ip.py:94 -msgid "Fixed IP address mapped to the floating IP" -msgstr "" - -#: openstackclient/network/v2/floating_ip.py:119 -msgid "Floating IP to delete (IP address or ID)" -msgstr "" - -#: openstackclient/network/v2/floating_ip.py:193 -msgid "Floating IP to display (IP address or ID)" -msgstr "" - -#: openstackclient/network/v2/network.py:114 -msgid "New network name" -msgstr "" - -#: openstackclient/network/v2/network.py:121 -#: openstackclient/network/v2/network.py:385 -msgid "Share the network between projects" -msgstr "" - -#: openstackclient/network/v2/network.py:126 -#: openstackclient/network/v2/network.py:390 -msgid "Do not share the network between projects" -msgstr "" - -#: openstackclient/network/v2/network.py:136 -msgid "Enable network (default)" -msgstr "" - -#: openstackclient/network/v2/network.py:141 -#: openstackclient/network/v2/network.py:378 -msgid "Disable network" -msgstr "" - -#: openstackclient/network/v2/network.py:146 -#: openstackclient/network/v2/port.py:246 -#: openstackclient/network/v2/router.py:174 -#: openstackclient/network/v2/security_group.py:116 -#: openstackclient/network/v2/security_group_rule.py:127 -#: openstackclient/network/v2/subnet.py:183 -#: openstackclient/network/v2/subnet_pool.py:114 -msgid "Owner's project (name or ID)" -msgstr "" - -#: openstackclient/network/v2/network.py:154 -msgid "" -"Availability Zone in which to create this network (Network Availability " -"Zone extension required, repeat option to set multiple availability " -"zones)" -msgstr "" - -#: openstackclient/network/v2/network.py:162 -#: openstackclient/network/v2/network.py:396 -msgid "Set this network as an external network (external-net extension required)" -msgstr "" - -#: openstackclient/network/v2/network.py:168 -msgid "Set this network as an internal network (default)" -msgstr "" - -#: openstackclient/network/v2/network.py:174 -msgid "Specify if this network should be used as the default external network" -msgstr "" - -#: openstackclient/network/v2/network.py:180 -msgid "Do not use the network as the default external network. (default)" -msgstr "" - -#: openstackclient/network/v2/network.py:188 -msgid "" -"The physical mechanism by which the virtual network is implemented. The " -"supported options are: flat, gre, local, vlan, vxlan" -msgstr "" - -#: openstackclient/network/v2/network.py:196 -msgid "Name of the physical network over which the virtual network is implemented" -msgstr "" - -#: openstackclient/network/v2/network.py:203 -msgid "VLAN ID for VLAN networks or Tunnel ID for GRE/VXLAN networks" -msgstr "" - -#: openstackclient/network/v2/network.py:212 -msgid "IPv4 subnet for fixed IPs (in CIDR notation)" -msgstr "" - -#: openstackclient/network/v2/network.py:361 -msgid "Network to modify (name or ID)" -msgstr "" - -#: openstackclient/network/v2/network.py:366 -msgid "Set network name" -msgstr "" - -#: openstackclient/network/v2/network.py:373 -msgid "Enable network" -msgstr "" - -#: openstackclient/network/v2/network.py:402 -msgid "Set this network as an internal network" -msgstr "" - -#: openstackclient/network/v2/network.py:408 -msgid "Set the network as the default external network" -msgstr "" - -#: openstackclient/network/v2/network.py:413 -msgid "Do not use the network as the default external network" -msgstr "" - -#: openstackclient/network/v2/network.py:436 -msgid "Network to display (name or ID)" -msgstr "" - -#: openstackclient/network/v2/port.py:73 -msgid "The --device-id option is deprecated, please use --device instead." -msgstr "" - -#: openstackclient/network/v2/port.py:79 -msgid "The --host-id option is deprecated, please use --host instead." -msgstr "" - -#: openstackclient/network/v2/port.py:161 -msgid "Port device ID" -msgstr "" - -#: openstackclient/network/v2/port.py:171 -msgid "Device owner of this port" -msgstr "" - -#: openstackclient/network/v2/port.py:178 -msgid "" -"VNIC type for this port (direct | direct-physical | macvtap | normal | " -"baremetal, default: normal)" -msgstr "" - -#: openstackclient/network/v2/port.py:187 -msgid "Allocate port on host (ID only)" -msgstr "" - -#: openstackclient/network/v2/port.py:206 -msgid "Network this port belongs to (name or ID)" -msgstr "" - -#: openstackclient/network/v2/port.py:214 -#: openstackclient/network/v2/port.py:365 -msgid "" -"Desired IP and/or subnet (name or ID) for this port: subnet=,ip-" -"address= (repeat option to set multiple fixed IP addresses)" -msgstr "" - -#: openstackclient/network/v2/port.py:222 -#: openstackclient/network/v2/port.py:379 -msgid "" -"Custom data to be passed as binding:profile: = (repeat option" -" to set multiple binding:profile data)" -msgstr "" - -#: openstackclient/network/v2/port.py:231 -msgid "Enable port (default)" -msgstr "" - -#: openstackclient/network/v2/port.py:236 -#: openstackclient/network/v2/port.py:352 -msgid "Disable port" -msgstr "" - -#: openstackclient/network/v2/port.py:241 -msgid "MAC address of this port" -msgstr "" - -#: openstackclient/network/v2/port.py:280 -msgid "Port(s) to delete (name or ID)" -msgstr "" - -#: openstackclient/network/v2/port.py:301 -msgid "List only ports attached to this router (name or ID)" -msgstr "" - -#: openstackclient/network/v2/port.py:347 -msgid "Enable port" -msgstr "" - -#: openstackclient/network/v2/port.py:357 -msgid "Set port name" -msgstr "" - -#: openstackclient/network/v2/port.py:372 -msgid "Clear existing information of fixed IP addresses" -msgstr "" - -#: openstackclient/network/v2/port.py:386 -msgid "Clear existing information of binding:profile" -msgstr "" - -#: openstackclient/network/v2/port.py:391 -msgid "Port to modify (name or ID)" -msgstr "" - -#: openstackclient/network/v2/port.py:429 -msgid "Port to display (name or ID)" -msgstr "" - -#: openstackclient/network/v2/router.py:99 -msgid "Router to which port will be added (name or ID)" -msgstr "" - -#: openstackclient/network/v2/router.py:104 -msgid "Port to be added (name or ID)" -msgstr "" - -#: openstackclient/network/v2/router.py:123 -msgid "Router to which subnet will be added (name or ID)" -msgstr "" - -#: openstackclient/network/v2/router.py:128 -msgid "Subnet to be added (name or ID)" -msgstr "" - -#: openstackclient/network/v2/router.py:150 -msgid "New router name" -msgstr "" - -#: openstackclient/network/v2/router.py:157 -msgid "Enable router (default)" -msgstr "" - -#: openstackclient/network/v2/router.py:162 -#: openstackclient/network/v2/router.py:351 -msgid "Disable router" -msgstr "" - -#: openstackclient/network/v2/router.py:169 -msgid "Create a distributed router" -msgstr "" - -#: openstackclient/network/v2/router.py:182 -msgid "" -"Availability Zone in which to create this router (Router Availability " -"Zone extension required, repeat option to set multiple availability " -"zones)" -msgstr "" - -#: openstackclient/network/v2/router.py:210 -msgid "Router(s) to delete (name or ID)" -msgstr "" - -#: openstackclient/network/v2/router.py:283 -msgid "Router from which port will be removed (name or ID)" -msgstr "" - -#: openstackclient/network/v2/router.py:288 -msgid "Port to be removed (name or ID)" -msgstr "" - -#: openstackclient/network/v2/router.py:307 -msgid "Router from which the subnet will be removed (name or ID)" -msgstr "" - -#: openstackclient/network/v2/router.py:312 -msgid "Subnet to be removed (name or ID)" -msgstr "" - -#: openstackclient/network/v2/router.py:334 -msgid "Router to modify (name or ID)" -msgstr "" - -#: openstackclient/network/v2/router.py:339 -msgid "Set router name" -msgstr "" - -#: openstackclient/network/v2/router.py:346 -msgid "Enable router" -msgstr "" - -#: openstackclient/network/v2/router.py:357 -msgid "Set router to distributed mode (disabled router only)" -msgstr "" - -#: openstackclient/network/v2/router.py:362 -msgid "Set router to centralized mode (disabled router only)" -msgstr "" - -#: openstackclient/network/v2/router.py:372 -msgid "" -"Routes associated with the router destination: destination subnet (in " -"CIDR notation) gateway: nexthop IP address (repeat option to set multiple" -" routes)" -msgstr "" - -#: openstackclient/network/v2/router.py:380 -msgid "Clear routes associated with the router" -msgstr "" - -#: openstackclient/network/v2/router.py:412 -msgid "Router to display (name or ID)" -msgstr "" - -#: openstackclient/network/v2/security_group.py:103 -#: openstackclient/network/v2/security_group.py:249 -msgid "New security group name" -msgstr "" - -#: openstackclient/network/v2/security_group.py:108 -msgid "Security group description" -msgstr "" - -#: openstackclient/network/v2/security_group.py:173 -msgid "Security group to delete (name or ID)" -msgstr "" - -#: openstackclient/network/v2/security_group.py:208 -msgid "Display information from all projects (admin only)" -msgstr "" - -#: openstackclient/network/v2/security_group.py:244 -msgid "Security group to modify (name or ID)" -msgstr "" - -#: openstackclient/network/v2/security_group.py:254 -msgid "New security group description" -msgstr "" - -#: openstackclient/network/v2/security_group.py:299 -msgid "Security group to display (name or ID)" -msgstr "" - -#: openstackclient/network/v2/security_group_rule.py:82 -msgid "IP protocol (icmp, tcp, udp; default: tcp)" -msgstr "" - -#: openstackclient/network/v2/security_group_rule.py:88 -msgid "" -"Source IP address block (may use CIDR notation; default for IPv4 rule: " -"0.0.0.0/0)" -msgstr "" - -#: openstackclient/network/v2/security_group_rule.py:94 -msgid "Source security group (name or ID)" -msgstr "" - -#: openstackclient/network/v2/security_group_rule.py:101 -msgid "" -"Destination port, may be a single port or port range: 137:139 (only " -"required for IP protocols tcp and udp)" -msgstr "" - -#: openstackclient/network/v2/security_group_rule.py:111 -msgid "Rule applies to incoming network traffic (default)" -msgstr "" - -#: openstackclient/network/v2/security_group_rule.py:116 -msgid "Rule applies to outgoing network traffic" -msgstr "" - -#: openstackclient/network/v2/security_group_rule.py:122 -msgid "Ethertype of network traffic (IPv4, IPv6; default: IPv4)" -msgstr "" - -#: openstackclient/network/v2/security_group_rule.py:221 -msgid "Security group rule to delete (ID only)" -msgstr "" - -#: openstackclient/network/v2/security_group_rule.py:241 -msgid "List all rules in this security group (name or ID)" -msgstr "" - -#: openstackclient/network/v2/security_group_rule.py:336 -msgid "Security group rule to display (ID only)" -msgstr "" - -#: openstackclient/network/v2/subnet.py:54 -msgid "" -"Allocation pool IP addresses for this subnet e.g.: " -"start=192.168.199.2,end=192.168.199.254 (repeat option to add multiple IP" -" addresses)" -msgstr "" - -#: openstackclient/network/v2/subnet.py:63 -msgid "DNS server for this subnet (repeat option to set multiple DNS servers)" -msgstr "" - -#: openstackclient/network/v2/subnet.py:72 -msgid "" -"Additional route for this subnet e.g.: " -"destination=10.10.0.0/16,gateway=192.168.71.254 destination: destination " -"subnet (in CIDR notation) gateway: nexthop IP address (repeat option to " -"add multiple routes)" -msgstr "" - -#: openstackclient/network/v2/subnet.py:178 -msgid "New subnet name" -msgstr "" - -#: openstackclient/network/v2/subnet.py:190 -msgid "Subnet pool from which this subnet will obtain a CIDR (Name or ID)" -msgstr "" - -#: openstackclient/network/v2/subnet.py:196 -msgid "Use default subnet pool for --ip-version" -msgstr "" - -#: openstackclient/network/v2/subnet.py:201 -msgid "Prefix length for subnet allocation from subnet pool" -msgstr "" - -#: openstackclient/network/v2/subnet.py:206 -msgid "" -"Subnet range in CIDR notation (required if --subnet-pool is not " -"specified, optional otherwise)" -msgstr "" - -#: openstackclient/network/v2/subnet.py:215 -msgid "Enable DHCP (default)" -msgstr "" - -#: openstackclient/network/v2/subnet.py:220 -#: openstackclient/network/v2/subnet.py:348 -msgid "Disable DHCP" -msgstr "" - -#: openstackclient/network/v2/subnet.py:226 -msgid "" -"Specify a gateway for the subnet. The three options are: : " -"Specific IP address to use as the gateway, 'auto': Gateway address should" -" automatically be chosen from within the subnet itself, 'none': This " -"subnet will not use a gateway, e.g.: --gateway 192.168.9.1, --gateway " -"auto, --gateway none (default is 'auto')" -msgstr "" - -#: openstackclient/network/v2/subnet.py:238 -msgid "" -"IP version (default is 4). Note that when subnet pool is specified, IP " -"version is determined from the subnet pool and this option is ignored" -msgstr "" - -#: openstackclient/network/v2/subnet.py:245 -msgid "" -"IPv6 RA (Router Advertisement) mode, valid modes: [dhcpv6-stateful, " -"dhcpv6-stateless, slaac]" -msgstr "" - -#: openstackclient/network/v2/subnet.py:251 -msgid "IPv6 address mode, valid modes: [dhcpv6-stateful, dhcpv6-stateless, slaac]" -msgstr "" - -#: openstackclient/network/v2/subnet.py:258 -msgid "Network this subnet belongs to (name or ID)" -msgstr "" - -#: openstackclient/network/v2/subnet.py:280 -msgid "Subnet to delete (name or ID)" -msgstr "" - -#: openstackclient/network/v2/subnet.py:331 -msgid "Subnet to modify (name or ID)" -msgstr "" - -#: openstackclient/network/v2/subnet.py:336 -msgid "Updated name of the subnet" -msgstr "" - -#: openstackclient/network/v2/subnet.py:343 -msgid "Enable DHCP" -msgstr "" - -#: openstackclient/network/v2/subnet.py:353 -msgid "" -"Specify a gateway for the subnet. The options are: : Specific" -" IP address to use as the gateway, 'none': This subnet will not use a " -"gateway, e.g.: --gateway 192.168.9.1, --gateway none" -msgstr "" - -#: openstackclient/network/v2/subnet.py:387 -msgid "Subnet to display (name or ID)" -msgstr "" - -#: openstackclient/network/v2/subnet_pool.py:77 -msgid "" -"Set subnet pool prefixes (in CIDR notation) (repeat option to set " -"multiple prefixes)" -msgstr "" - -#: openstackclient/network/v2/subnet_pool.py:84 -msgid "Set subnet pool default prefix length" -msgstr "" - -#: openstackclient/network/v2/subnet_pool.py:90 -msgid "Set subnet pool minimum prefix length" -msgstr "" - -#: openstackclient/network/v2/subnet_pool.py:96 -msgid "Set subnet pool maximum prefix length" -msgstr "" - -#: openstackclient/network/v2/subnet_pool.py:108 -msgid "Name of the new subnet pool" -msgstr "" - -#: openstackclient/network/v2/subnet_pool.py:120 -#: openstackclient/network/v2/subnet_pool.py:226 -msgid "" -"Set address scope associated with the subnet pool (name or ID), prefixes " -"must be unique across address scopes" -msgstr "" - -#: openstackclient/network/v2/subnet_pool.py:146 -msgid "Subnet pool to delete (name or ID)" -msgstr "" - -#: openstackclient/network/v2/subnet_pool.py:214 -msgid "Subnet pool to modify (name or ID)" -msgstr "" - -#: openstackclient/network/v2/subnet_pool.py:219 -msgid "Set subnet pool name" -msgstr "" - -#: openstackclient/network/v2/subnet_pool.py:233 -msgid "Remove address scope associated with the subnet pool" -msgstr "" - -#: openstackclient/network/v2/subnet_pool.py:262 -msgid "Subnet pool to display (name or ID)" -msgstr "" - diff --git a/openstackclient/locale/zh_TW/LC_MESSAGES/openstackclient.po b/openstackclient/locale/zh_TW/LC_MESSAGES/openstackclient.po index a121acabb8..d9bbf6fe7f 100644 --- a/openstackclient/locale/zh_TW/LC_MESSAGES/openstackclient.po +++ b/openstackclient/locale/zh_TW/LC_MESSAGES/openstackclient.po @@ -7,9 +7,9 @@ # Andreas Jaeger , 2016. #zanata msgid "" msgstr "" -"Project-Id-Version: python-openstackclient 2.2.1.dev235\n" +"Project-Id-Version: python-openstackclient 2.5.1.dev51\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2016-04-19 05:02+0000\n" +"POT-Creation-Date: 2016-06-03 19:37+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -21,62 +21,6 @@ msgstr "" "X-Generator: Zanata 3.7.3\n" "Language-Team: Chinese (Taiwan)\n" -msgid "" -"\n" -"Complete\n" -msgstr "" -"\n" -"完成\n" - -msgid "" -"\n" -"Error creating server" -msgstr "" -"\n" -"新增雲實例時出錯" - -msgid "" -"\n" -"Error creating server snapshot" -msgstr "" -"\n" -"新增雲實例即時存檔時出錯" - -msgid "" -"\n" -"Error migrating server" -msgstr "" -"\n" -"轉移雲實例出錯" - -msgid "" -"\n" -"Error rebooting server\n" -msgstr "" -"\n" -"重開雲實例出錯\n" - -msgid "" -"\n" -"Error rebuilding server" -msgstr "" -"\n" -"重建雲實例出錯" - -msgid "" -"\n" -"Error resizing server" -msgstr "" -"\n" -"調整雲實例容量時出錯" - -msgid "" -"\n" -"Reboot complete\n" -msgstr "" -"\n" -"重開機完成\n" - msgid "Add a property to (repeat option to set multiple properties)" msgstr "加入屬性到 (重復這選項來設定多個屬性)" @@ -122,9 +66,6 @@ msgstr "啟用用戶(預設)" msgid "Endpoint ID to delete" msgstr "要刪除的端點識別號" -msgid "Endpoint ID to display" -msgstr "要顯示的端點識別號" - #, python-format msgid "Error creating server: %s" msgstr "新增雲實例時出錯:%s" From 2672e37ee211bf08a4b6078668bd36a08c9c606a Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sat, 4 Jun 2016 21:29:44 +0800 Subject: [PATCH 0945/3095] Make set/unset commands in volume return normally when nothing specified set/unset commands should ends up normally instead of logging an error when nothing is specified to modify. The main reason is: When nothing is specified, the command sets/unsets nothing, which is a normal behavior, and ends up normally. No API call fails. No error happens. Change-Id: Ib03a512650e5da90aa1ef38019772448383d0d33 Partial-bug: #1588588 --- openstackclient/tests/volume/v1/test_volume.py | 3 --- openstackclient/volume/v1/qos_specs.py | 4 ---- openstackclient/volume/v1/snapshot.py | 6 ------ openstackclient/volume/v1/volume.py | 5 ----- openstackclient/volume/v1/volume_type.py | 2 -- openstackclient/volume/v2/qos_specs.py | 4 ---- openstackclient/volume/v2/snapshot.py | 7 ------- openstackclient/volume/v2/volume.py | 4 ---- openstackclient/volume/v2/volume_type.py | 12 ------------ 9 files changed, 47 deletions(-) diff --git a/openstackclient/tests/volume/v1/test_volume.py b/openstackclient/tests/volume/v1/test_volume.py index e0fd1c0800..e4f51bb535 100644 --- a/openstackclient/tests/volume/v1/test_volume.py +++ b/openstackclient/tests/volume/v1/test_volume.py @@ -578,9 +578,6 @@ def test_volume_set_no_options(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - - self.assertEqual("No changes requested\n", - self.app.log.messages.get('error')) self.assertIsNone(result) def test_volume_set_name(self): diff --git a/openstackclient/volume/v1/qos_specs.py b/openstackclient/volume/v1/qos_specs.py index b9eb87523b..c49477a0e9 100644 --- a/openstackclient/volume/v1/qos_specs.py +++ b/openstackclient/volume/v1/qos_specs.py @@ -202,8 +202,6 @@ def take_action(self, parsed_args): if parsed_args.property: volume_client.qos_specs.set_keys(qos_spec.id, parsed_args.property) - else: - self.app.log.error(_("No changes requested\n")) class ShowQos(command.ShowOne): @@ -263,5 +261,3 @@ def take_action(self, parsed_args): if parsed_args.property: volume_client.qos_specs.unset_keys(qos_spec.id, parsed_args.property) - else: - self.app.log.error(_("No changes requested\n")) diff --git a/openstackclient/volume/v1/snapshot.py b/openstackclient/volume/v1/snapshot.py index bf5bf26466..5132d71ee6 100644 --- a/openstackclient/volume/v1/snapshot.py +++ b/openstackclient/volume/v1/snapshot.py @@ -208,10 +208,6 @@ def take_action(self, parsed_args): if parsed_args.description: kwargs['display_description'] = parsed_args.description - if not kwargs and not parsed_args.property: - self.app.log.error(_("No changes requested\n")) - return - snapshot.update(**kwargs) @@ -270,5 +266,3 @@ def take_action(self, parsed_args): snapshot.id, parsed_args.property, ) - else: - self.app.log.error(_("No changes requested\n")) diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index 11e42f8372..ffec180348 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -363,9 +363,6 @@ def take_action(self, parsed_args): if kwargs: volume_client.volumes.update(volume.id, **kwargs) - if not kwargs and not parsed_args.property and not parsed_args.size: - self.app.log.error(_("No changes requested\n")) - class ShowVolume(command.ShowOne): """Show volume details""" @@ -428,5 +425,3 @@ def take_action(self, parsed_args): volume.id, parsed_args.property, ) - else: - self.app.log.error(_("No changes requested\n")) diff --git a/openstackclient/volume/v1/volume_type.py b/openstackclient/volume/v1/volume_type.py index 739270220a..4e9b1920aa 100644 --- a/openstackclient/volume/v1/volume_type.py +++ b/openstackclient/volume/v1/volume_type.py @@ -181,5 +181,3 @@ def take_action(self, parsed_args): if parsed_args.property: volume_type.unset_keys(parsed_args.property) - else: - self.app.log.error(_("No changes requested\n")) diff --git a/openstackclient/volume/v2/qos_specs.py b/openstackclient/volume/v2/qos_specs.py index 3cea3303a2..90e11c77b7 100644 --- a/openstackclient/volume/v2/qos_specs.py +++ b/openstackclient/volume/v2/qos_specs.py @@ -202,8 +202,6 @@ def take_action(self, parsed_args): if parsed_args.property: volume_client.qos_specs.set_keys(qos_spec.id, parsed_args.property) - else: - self.app.log.error(_("No changes requested\n")) class ShowQos(command.ShowOne): @@ -263,5 +261,3 @@ def take_action(self, parsed_args): if parsed_args.property: volume_client.qos_specs.unset_keys(qos_spec.id, parsed_args.property) - else: - self.app.log.error(_("No changes requested\n")) diff --git a/openstackclient/volume/v2/snapshot.py b/openstackclient/volume/v2/snapshot.py index 2886aaefeb..d90170809d 100644 --- a/openstackclient/volume/v2/snapshot.py +++ b/openstackclient/volume/v2/snapshot.py @@ -205,11 +205,6 @@ def take_action(self, parsed_args): if parsed_args.description: kwargs['description'] = parsed_args.description - if (not kwargs and not parsed_args.property and not - parsed_args.state): - self.app.log.error(_("No changes requested\n")) - return - if parsed_args.property: volume_client.volume_snapshots.set_metadata(snapshot.id, parsed_args.property) @@ -271,5 +266,3 @@ def take_action(self, parsed_args): snapshot.id, parsed_args.property, ) - else: - self.app.log.error(_("No changes requested\n")) diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index 0e07aa78d0..18473da3c2 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -385,10 +385,6 @@ def take_action(self, parsed_args): if kwargs: volume_client.volumes.update(volume.id, **kwargs) - if (not kwargs and not parsed_args.property - and not parsed_args.image_property and not parsed_args.size): - self.app.log.error(_("No changes requested\n")) - class ShowVolume(command.ShowOne): """Display volume details""" diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index 200d9bd51a..e44e39ff1e 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -176,13 +176,6 @@ def take_action(self, parsed_args): volume_type = utils.find_resource( volume_client.volume_types, parsed_args.volume_type) - if (not parsed_args.name - and not parsed_args.description - and not parsed_args.property - and not parsed_args.project): - self.app.log.error(_("No changes requested\n")) - return - result = 0 kwargs = {} if parsed_args.name: @@ -285,11 +278,6 @@ def take_action(self, parsed_args): parsed_args.volume_type, ) - if (not parsed_args.property - and not parsed_args.project): - self.app.log.error(_("No changes requested\n")) - return - result = 0 if parsed_args.property: try: From ac1d86c34333780e30b9393d155ae84a769ac222 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Wed, 25 May 2016 11:12:57 +0800 Subject: [PATCH 0946/3095] Refactor SetService --enable/disable option This patch changes the following: 1. --enable/disable option should follow the rules in the doc below: http://docs.openstack.org/developer/python-openstackclient/command-options.html#boolean-options 2. "--disable-resion" is specified but not "--disable", an exception is raised instead of igoring "--disable-reason" option. Change-Id: I92e9234111e661bfe7119a8e19389a87c874ab0c --- .../command-objects/compute-service.rst | 5 +- openstackclient/compute/v2/service.py | 38 ++++++++----- .../tests/compute/v2/test_service.py | 57 +++++++++---------- .../service-set-option-61772a8940ad0778.yaml | 6 ++ 4 files changed, 57 insertions(+), 49 deletions(-) create mode 100644 releasenotes/notes/service-set-option-61772a8940ad0778.yaml diff --git a/doc/source/command-objects/compute-service.rst b/doc/source/command-objects/compute-service.rst index bda64c811d..71225bc37a 100644 --- a/doc/source/command-objects/compute-service.rst +++ b/doc/source/command-objects/compute-service.rst @@ -63,7 +63,7 @@ Set service command .. _compute-service-set: .. option:: --enable - Enable service (default) + Enable service .. option:: --disable @@ -71,8 +71,7 @@ Set service command .. option:: --disable-reason - Reason for disabling the service (in quotes). Note that when the service - is enabled, this option is ignored. + Reason for disabling the service (in quotes). Should be used with --disable option. .. describe:: diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index 6093b8ce17..e7966a8a1a 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.py @@ -16,6 +16,7 @@ """Service action implementations""" from openstackclient.common import command +from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.i18n import _ @@ -110,23 +111,20 @@ def get_parser(self, prog_name): enabled_group = parser.add_mutually_exclusive_group() enabled_group.add_argument( "--enable", - dest="enabled", - default=True, action="store_true", - help=_("Enable a service (default)") + help=_("Enable service") ) enabled_group.add_argument( "--disable", - dest="enabled", - action="store_false", - help=_("Disable a service") + action="store_true", + help=_("Disable service") ) parser.add_argument( "--disable-reason", default=None, metavar="", - help=_("Reason for disabling the service (in quotas). Note that " - "when the service is enabled, this option is ignored.") + help=_("Reason for disabling the service (in quotas). " + "Should be used with --disable option.") ) return parser @@ -134,16 +132,26 @@ def take_action(self, parsed_args): compute_client = self.app.client_manager.compute cs = compute_client.services - if not parsed_args.enabled: + if (parsed_args.enable or not parsed_args.disable) and \ + parsed_args.disable_reason: + msg = _("Cannot specify option --disable-reason without " + "--disable specified.") + raise exceptions.CommandError(msg) + + enabled = None + if parsed_args.enable: + enabled = True + if parsed_args.disable: + enabled = False + + if enabled is None: + return + elif enabled: + cs.enable(parsed_args.host, parsed_args.service) + else: if parsed_args.disable_reason: cs.disable_log_reason(parsed_args.host, parsed_args.service, parsed_args.disable_reason) else: cs.disable(parsed_args.host, parsed_args.service) - else: - if parsed_args.disable_reason: - msg = _("argument --disable-reason has been ignored") - self.log.info(msg) - - cs.enable(parsed_args.host, parsed_args.service) diff --git a/openstackclient/tests/compute/v2/test_service.py b/openstackclient/tests/compute/v2/test_service.py index 7a5a840f05..cf53497878 100644 --- a/openstackclient/tests/compute/v2/test_service.py +++ b/openstackclient/tests/compute/v2/test_service.py @@ -13,8 +13,7 @@ # under the License. # -import mock - +from openstackclient.common import exceptions from openstackclient.compute.v2 import service from openstackclient.tests.compute.v2 import fakes as compute_fakes @@ -128,6 +127,23 @@ def setUp(self): self.cmd = service.SetService(self.app, None) + def test_set_nothing(self): + arglist = [ + self.service.host, + self.service.binary, + ] + verifylist = [ + ('host', self.service.host), + ('service', self.service.binary), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.service_mock.enable.assert_not_called() + self.service_mock.disable.assert_not_called() + self.service_mock.disable_log_reason.assert_not_called() + self.assertIsNone(result) + def test_service_set_enable(self): arglist = [ '--enable', @@ -135,7 +151,7 @@ def test_service_set_enable(self): self.service.binary, ] verifylist = [ - ('enabled', True), + ('enable', True), ('host', self.service.host), ('service', self.service.binary), ] @@ -156,7 +172,7 @@ def test_service_set_disable(self): self.service.binary, ] verifylist = [ - ('enabled', False), + ('disable', True), ('host', self.service.host), ('service', self.service.binary), ] @@ -179,7 +195,7 @@ def test_service_set_disable_with_reason(self): self.service.binary, ] verifylist = [ - ('enabled', False), + ('disable', True), ('disable_reason', reason), ('host', self.service.host), ('service', self.service.binary), @@ -203,24 +219,13 @@ def test_service_set_only_with_disable_reason(self): self.service.binary, ] verifylist = [ - ('enabled', True), ('disable_reason', reason), ('host', self.service.host), ('service', self.service.binary), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - with mock.patch.object(self.cmd.log, 'info') as mock_log: - result = self.cmd.take_action(parsed_args) - - msg = "argument --disable-reason has been ignored" - mock_log.assert_called_once_with(msg) - - self.service_mock.enable.assert_called_with( - self.service.host, - self.service.binary - ) - self.assertIsNone(result) + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) def test_service_set_enable_with_disable_reason(self): reason = 'earthquake' @@ -231,21 +236,11 @@ def test_service_set_enable_with_disable_reason(self): self.service.binary, ] verifylist = [ - ('enabled', True), + ('enable', True), ('disable_reason', reason), ('host', self.service.host), ('service', self.service.binary), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - with mock.patch.object(self.cmd.log, 'info') as mock_log: - result = self.cmd.take_action(parsed_args) - - msg = "argument --disable-reason has been ignored" - mock_log.assert_called_once_with(msg) - - self.service_mock.enable.assert_called_with( - self.service.host, - self.service.binary - ) - self.assertIsNone(result) + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) diff --git a/releasenotes/notes/service-set-option-61772a8940ad0778.yaml b/releasenotes/notes/service-set-option-61772a8940ad0778.yaml new file mode 100644 index 0000000000..02c7fcba2e --- /dev/null +++ b/releasenotes/notes/service-set-option-61772a8940ad0778.yaml @@ -0,0 +1,6 @@ +--- +upgrade: + - An exception is not raised by command ``service set`` when nothing + specified. Instead, the service is not enabled by default. And if + ``--disable-resion`` is specified but not ``--disable``, an + exception will be raised. From 2178cedef1fbd1fbf2adda2ca40161414dc75c1b Mon Sep 17 00:00:00 2001 From: Sheel Rana Date: Sun, 1 May 2016 21:14:36 +0530 Subject: [PATCH 0947/3095] Add support for volume transfer request list OSC does not support to list volume transfer from one user to other user. This patch will provide support for listning volume transfer requests. Closes-Bug:#1554886 Implements: bp cinder-command-support Change-Id: Ie659bc39cb1d1e931cca7a40b2f126a067ee484c --- .../volume-transfer-request.rst | 21 ++++ doc/source/commands.rst | 1 + openstackclient/tests/volume/v1/fakes.py | 55 ++++++++- .../tests/volume/v1/test_transfer_request.py | 114 ++++++++++++++++++ openstackclient/tests/volume/v2/fakes.py | 55 ++++++++- .../tests/volume/v2/test_transfer_request.py | 114 ++++++++++++++++++ .../volume/v1/volume_transfer_request.py | 51 ++++++++ .../volume/v2/volume_transfer_request.py | 51 ++++++++ ...ume_transfer_request-8e5249a655e7e7b6.yaml | 9 ++ setup.cfg | 4 + 10 files changed, 471 insertions(+), 4 deletions(-) create mode 100644 doc/source/command-objects/volume-transfer-request.rst create mode 100644 openstackclient/tests/volume/v1/test_transfer_request.py create mode 100644 openstackclient/tests/volume/v2/test_transfer_request.py create mode 100644 openstackclient/volume/v1/volume_transfer_request.py create mode 100644 openstackclient/volume/v2/volume_transfer_request.py create mode 100644 releasenotes/notes/list_volume_transfer_request-8e5249a655e7e7b6.yaml diff --git a/doc/source/command-objects/volume-transfer-request.rst b/doc/source/command-objects/volume-transfer-request.rst new file mode 100644 index 0000000000..79d84f7551 --- /dev/null +++ b/doc/source/command-objects/volume-transfer-request.rst @@ -0,0 +1,21 @@ +======================= +volume transfer request +======================= + +Block Storage v1, v2 + +volume transfer request list +---------------------------- + +Lists all volume transfer requests. + +.. program:: volume transfer request list +.. code:: bash + + os volume transfer request list + --all-projects + +.. option:: --all-projects + + Shows detail for all projects. Admin only. + (defaults to False) \ No newline at end of file diff --git a/doc/source/commands.rst b/doc/source/commands.rst index da5604fede..39b9afea2b 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -134,6 +134,7 @@ referring to both Compute and Volume quotas. * ``volume qos``: (**Volume**) quality-of-service (QoS) specification for volumes * ``volume type``: (**Volume**) deployment-specific types of volumes available * ``volume service``: (**Volume**) services to manage block storage operations +* ``volume transfer request``: (**Volume**) volume owner transfer request Plugin Objects diff --git a/openstackclient/tests/volume/v1/fakes.py b/openstackclient/tests/volume/v1/fakes.py index d6c46439c6..6c349866dd 100644 --- a/openstackclient/tests/volume/v1/fakes.py +++ b/openstackclient/tests/volume/v1/fakes.py @@ -129,6 +129,57 @@ } +class FakeTransferClient(object): + + def __init__(self, **kwargs): + + self.transfers = mock.Mock() + self.transfers.resource_class = fakes.FakeResource(None, {}) + + +class TestTransfer(utils.TestCommand): + + def setUp(self): + super(TestTransfer, self).setUp() + + self.app.client_manager.volume = FakeTransferClient( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN + ) + + +class FakeTransfer(object): + """Fake one or more Transfer.""" + + @staticmethod + def create_one_transfer(attrs=None): + """Create a fake transfer. + + :param Dictionary attrs: + A dictionary with all attributes of Transfer Request + :retrun: + A FakeResource object with volume_id, name, id. + """ + # Set default attribute + transfer_info = { + 'volume_id': 'ce26708d-a7f8-4b4b-9861-4a80256615a7', + 'name': 'fake_transfer_name', + 'id': '731a7f53-aa92-4fbd-9de3-6f7d729c926b' + } + + # Overwrite default attributes if there are some attributes set + attrs = attrs or {} + + transfer_info.update(attrs) + + transfer = fakes.FakeResource( + None, + transfer_info, + loaded=True) + + return transfer + + class FakeServiceClient(object): def __init__(self, **kwargs): @@ -171,8 +222,8 @@ def create_one_service(attrs=None): } # Overwrite default attributes if there are some attributes set - if attrs is None: - attrs = {} + attrs = attrs or {} + service_info.update(attrs) service = fakes.FakeResource( diff --git a/openstackclient/tests/volume/v1/test_transfer_request.py b/openstackclient/tests/volume/v1/test_transfer_request.py new file mode 100644 index 0000000000..94e02d62c9 --- /dev/null +++ b/openstackclient/tests/volume/v1/test_transfer_request.py @@ -0,0 +1,114 @@ +# +# 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.volume.v1 import fakes as transfer_fakes +from openstackclient.volume.v1 import volume_transfer_request + + +class TestTransfer(transfer_fakes.TestTransfer): + + def setUp(self): + super(TestTransfer, self).setUp() + + # Get a shortcut to the TransferManager Mock + self.transfer_mock = self.app.client_manager.volume.transfers + self.transfer_mock.reset_mock() + + +class TestTransferList(TestTransfer): + + # The Transfers to be listed + volume_transfers = transfer_fakes.FakeTransfer.create_one_transfer() + + def setUp(self): + super(TestTransferList, self).setUp() + + self.transfer_mock.list.return_value = [self.volume_transfers] + + # Get the command object to test + self.cmd = volume_transfer_request.ListTransferRequests(self.app, None) + + def test_transfer_list_without_argument(self): + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + expected_columns = [ + 'ID', + 'Volume', + 'Name', + ] + + # confirming if all expected columns are present in the result. + self.assertEqual(expected_columns, columns) + + datalist = (( + self.volume_transfers.id, + self.volume_transfers.volume_id, + self.volume_transfers.name, + ), ) + + # confirming if all expected values are present in the result. + self.assertEqual(datalist, tuple(data)) + + # checking if proper call was made to list volume_transfers + self.transfer_mock.list.assert_called_with( + detailed=True, + search_opts={'all_tenants': 0} + ) + + def test_transfer_list_with_argument(self): + arglist = [ + "--all-projects" + ] + verifylist = [ + ("all_projects", True) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + expected_columns = [ + 'ID', + 'Volume', + 'Name', + ] + + # confirming if all expected columns are present in the result. + self.assertEqual(expected_columns, columns) + + datalist = (( + self.volume_transfers.id, + self.volume_transfers.volume_id, + self.volume_transfers.name, + ), ) + + # confirming if all expected values are present in the result. + self.assertEqual(datalist, tuple(data)) + + # checking if proper call was made to list volume_transfers + self.transfer_mock.list.assert_called_with( + detailed=True, + search_opts={'all_tenants': 1} + ) diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py index 120666a059..bd15076ff6 100644 --- a/openstackclient/tests/volume/v2/fakes.py +++ b/openstackclient/tests/volume/v2/fakes.py @@ -232,6 +232,57 @@ } +class FakeTransferClient(object): + + def __init__(self, **kwargs): + + self.transfers = mock.Mock() + self.transfers.resource_class = fakes.FakeResource(None, {}) + + +class TestTransfer(utils.TestCommand): + + def setUp(self): + super(TestTransfer, self).setUp() + + self.app.client_manager.volume = FakeTransferClient( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN + ) + + +class FakeTransfer(object): + """Fake one or more Transfer.""" + + @staticmethod + def create_one_transfer(attrs=None): + """Create a fake transfer. + + :param Dictionary attrs: + A dictionary with all attributes of Transfer Request + :retrun: + A FakeResource object with volume_id, name, id. + """ + # Set default attribute + transfer_info = { + 'volume_id': 'ce26708d-a7f8-4b4b-9861-4a80256615a7', + 'name': 'fake_transfer_name', + 'id': '731a7f53-aa92-4fbd-9de3-6f7d729c926b' + } + + # Overwrite default attributes if there are some attributes set + attrs = attrs or {} + + transfer_info.update(attrs) + + transfer = fakes.FakeResource( + None, + transfer_info, + loaded=True) + + return transfer + + class FakeServiceClient(object): def __init__(self, **kwargs): @@ -274,8 +325,8 @@ def create_one_service(attrs=None): } # Overwrite default attributes if there are some attributes set - if attrs is None: - attrs = {} + attrs = attrs or {} + service_info.update(attrs) service = fakes.FakeResource( diff --git a/openstackclient/tests/volume/v2/test_transfer_request.py b/openstackclient/tests/volume/v2/test_transfer_request.py new file mode 100644 index 0000000000..945833c939 --- /dev/null +++ b/openstackclient/tests/volume/v2/test_transfer_request.py @@ -0,0 +1,114 @@ +# +# 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.volume.v2 import fakes as transfer_fakes +from openstackclient.volume.v2 import volume_transfer_request + + +class TestTransfer(transfer_fakes.TestTransfer): + + def setUp(self): + super(TestTransfer, self).setUp() + + # Get a shortcut to the TransferManager Mock + self.transfer_mock = self.app.client_manager.volume.transfers + self.transfer_mock.reset_mock() + + +class TestTransferList(TestTransfer): + + # The Transfers to be listed + volume_transfers = transfer_fakes.FakeTransfer.create_one_transfer() + + def setUp(self): + super(TestTransferList, self).setUp() + + self.transfer_mock.list.return_value = [self.volume_transfers] + + # Get the command object to test + self.cmd = volume_transfer_request.ListTransferRequests(self.app, None) + + def test_transfer_list_without_argument(self): + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + expected_columns = [ + 'ID', + 'Volume', + 'Name', + ] + + # confirming if all expected columns are present in the result. + self.assertEqual(expected_columns, columns) + + datalist = (( + self.volume_transfers.id, + self.volume_transfers.volume_id, + self.volume_transfers.name, + ), ) + + # confirming if all expected values are present in the result. + self.assertEqual(datalist, tuple(data)) + + # checking if proper call was made to list volume_transfers + self.transfer_mock.list.assert_called_with( + detailed=True, + search_opts={'all_tenants': 0} + ) + + def test_transfer_list_with_argument(self): + arglist = [ + "--all-projects" + ] + verifylist = [ + ("all_projects", True) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + expected_columns = [ + 'ID', + 'Volume', + 'Name', + ] + + # confirming if all expected columns are present in the result. + self.assertEqual(expected_columns, columns) + + datalist = (( + self.volume_transfers.id, + self.volume_transfers.volume_id, + self.volume_transfers.name, + ), ) + + # confirming if all expected values are present in the result. + self.assertEqual(datalist, tuple(data)) + + # checking if proper call was made to list volume_transfers + self.transfer_mock.list.assert_called_with( + detailed=True, + search_opts={'all_tenants': 1} + ) diff --git a/openstackclient/volume/v1/volume_transfer_request.py b/openstackclient/volume/v1/volume_transfer_request.py new file mode 100644 index 0000000000..98689e7bc5 --- /dev/null +++ b/openstackclient/volume/v1/volume_transfer_request.py @@ -0,0 +1,51 @@ +# +# 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 transfer action implementations""" + + +from openstackclient.common import command +from openstackclient.common import utils +from openstackclient.i18n import _ + + +class ListTransferRequests(command.Lister): + """Lists all volume transfer requests.""" + + def get_parser(self, prog_name): + parser = super(ListTransferRequests, self).get_parser(prog_name) + parser.add_argument( + '--all-projects', + dest='all_projects', + action="store_true", + default=False, + help=_('Shows detail for all projects. Admin only. ' + '(defaults to False)') + ) + return parser + + def take_action(self, parsed_args): + columns = ['ID', 'Volume ID', 'Name'] + column_headers = ['ID', 'Volume', 'Name'] + + volume_client = self.app.client_manager.volume + + volume_transfer_result = volume_client.transfers.list( + detailed=True, + search_opts={'all_tenants': parsed_args.all_projects} + ) + + return (column_headers, ( + utils.get_item_properties(s, columns) + for s in volume_transfer_result)) diff --git a/openstackclient/volume/v2/volume_transfer_request.py b/openstackclient/volume/v2/volume_transfer_request.py new file mode 100644 index 0000000000..98689e7bc5 --- /dev/null +++ b/openstackclient/volume/v2/volume_transfer_request.py @@ -0,0 +1,51 @@ +# +# 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 transfer action implementations""" + + +from openstackclient.common import command +from openstackclient.common import utils +from openstackclient.i18n import _ + + +class ListTransferRequests(command.Lister): + """Lists all volume transfer requests.""" + + def get_parser(self, prog_name): + parser = super(ListTransferRequests, self).get_parser(prog_name) + parser.add_argument( + '--all-projects', + dest='all_projects', + action="store_true", + default=False, + help=_('Shows detail for all projects. Admin only. ' + '(defaults to False)') + ) + return parser + + def take_action(self, parsed_args): + columns = ['ID', 'Volume ID', 'Name'] + column_headers = ['ID', 'Volume', 'Name'] + + volume_client = self.app.client_manager.volume + + volume_transfer_result = volume_client.transfers.list( + detailed=True, + search_opts={'all_tenants': parsed_args.all_projects} + ) + + return (column_headers, ( + utils.get_item_properties(s, columns) + for s in volume_transfer_result)) diff --git a/releasenotes/notes/list_volume_transfer_request-8e5249a655e7e7b6.yaml b/releasenotes/notes/list_volume_transfer_request-8e5249a655e7e7b6.yaml new file mode 100644 index 0000000000..3f7fba5831 --- /dev/null +++ b/releasenotes/notes/list_volume_transfer_request-8e5249a655e7e7b6.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + Adds support for volume transfer request. + + An user can list available volume transfer requests using + ``volume transfer request list`` + + [Bug `1554886 `_] \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index 95dd1859fc..96ce8cd43a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -432,6 +432,8 @@ openstack.volume.v1 = volume_service_list = openstackclient.volume.v1.service:ListService + volume_transfer_request_list = openstackclient.volume.v1.volume_transfer_request:ListTransferRequests + openstack.volume.v2 = backup_create = openstackclient.volume.v2.backup:CreateBackup backup_delete = openstackclient.volume.v2.backup:DeleteBackup @@ -471,6 +473,8 @@ openstack.volume.v2 = volume_service_list = openstackclient.volume.v2.service:ListService + volume_transfer_request_list = openstackclient.volume.v2.volume_transfer_request:ListTransferRequests + [build_sphinx] source-dir = doc/source build-dir = doc/build From d4c86de0f1669fe620a1456875186b95a934dfc7 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sun, 5 Jun 2016 16:27:02 +0000 Subject: [PATCH 0948/3095] Updated from global requirements Change-Id: Ic65f591351d97d3965d86d846b5b82d95b0a3bd2 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 39f95f1efc..5d8c844a71 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ cliff!=1.16.0,!=1.17.0,>=1.15.0 # Apache-2.0 keystoneauth1>=2.1.0 # Apache-2.0 openstacksdk>=0.8.6 # Apache-2.0 os-client-config>=1.13.1 # Apache-2.0 -oslo.config>=3.9.0 # Apache-2.0 +oslo.config>=3.10.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 oslo.utils>=3.11.0 # Apache-2.0 python-glanceclient>=2.0.0 # Apache-2.0 From 08e7801ff445d7dcf2e23d8be4b513463629dde3 Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Mon, 6 Jun 2016 09:54:04 +0800 Subject: [PATCH 0949/3095] Add server set/unset unit test cases Unit test cases don't cover compute "server set/unset" commands, the patch add some test cases for them. Change-Id: I440c32968bd41b948352a9764a37c9af3e68803d --- .../tests/compute/v2/test_server.py | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 2dfdb68ae7..d642e6dd59 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -12,6 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. # +import getpass import mock from mock import call @@ -1273,6 +1274,53 @@ def test_server_set_with_invalid_state(self): self.check_parser, self.cmd, arglist, verifylist) + def test_server_set_with_name(self): + arglist = [ + '--name', 'foo_name', + 'foo_vm', + ] + verifylist = [ + ('name', 'foo_name'), + ('server', 'foo_vm'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.fake_servers[0].update.assert_called_once_with(name='foo_name') + self.assertIsNone(result) + + def test_server_set_with_property(self): + arglist = [ + '--property', 'key1=value1', + '--property', 'key2=value2', + 'foo_vm', + ] + verifylist = [ + ('property', {'key1': 'value1', 'key2': 'value2'}), + ('server', 'foo_vm'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.servers_mock.set_meta.assert_called_once_with( + self.fake_servers[0], parsed_args.property) + self.assertIsNone(result) + + @mock.patch.object(getpass, 'getpass', + return_value=mock.sentinel.fake_pass) + def test_server_set_with_root_password(self, mock_getpass): + arglist = [ + '--root-password', + 'foo_vm', + ] + verifylist = [ + ('root_password', True), + ('server', 'foo_vm'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.fake_servers[0].change_password.assert_called_once_with( + mock.sentinel.fake_pass) + self.assertIsNone(result) + class TestServerShelve(TestServer): @@ -1489,6 +1537,45 @@ def test_server_unpause_multi_servers(self): self.run_method_with_servers('unpause', 3) +class TestServerUnset(TestServer): + + def setUp(self): + super(TestServerUnset, self).setUp() + + self.fake_server = self.setup_servers_mock(1)[0] + + # Get the command object to test + self.cmd = server.UnsetServer(self.app, None) + + def test_server_unset_no_option(self): + arglist = [ + 'foo_vm', + ] + verifylist = [ + ('server', 'foo_vm'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.assertNotCalled(self.servers_mock.delete_meta) + self.assertIsNone(result) + + def test_server_unset_with_property(self): + arglist = [ + '--property', 'key1', + '--property', 'key2', + 'foo_vm', + ] + verifylist = [ + ('property', ['key1', 'key2']), + ('server', 'foo_vm'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.servers_mock.delete_meta.assert_called_once_with( + self.fake_server, ['key1', 'key2']) + self.assertIsNone(result) + + class TestServerUnshelve(TestServer): def setUp(self): From 2c965b88f6f15d7eee1b7f68dc8ce344b8592746 Mon Sep 17 00:00:00 2001 From: zheng yin Date: Mon, 6 Jun 2016 11:09:25 +0800 Subject: [PATCH 0950/3095] fix image unset modify "target" to "tagret" Change-Id: I80674b23804b26430aed13b5e6c6dc2b240771cd --- openstackclient/image/v2/image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index a81f092c31..fa1de42419 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -933,7 +933,7 @@ def take_action(self, parsed_args): 'propret': propret, 'proptotal': proptotal}) raise exceptions.CommandError(msg) elif tagret > 0: - msg = (_("Failed to unset %(target)s of %(tagtotal)s tags.") + msg = (_("Failed to unset %(tagret)s of %(tagtotal)s tags.") % {'tagret': tagret, 'tagtotal': tagtotal}) raise exceptions.CommandError(msg) elif propret > 0: From 6de7bf0abc5a9785ffdaf29f5ebf34c61d9c2112 Mon Sep 17 00:00:00 2001 From: sunyajing Date: Mon, 6 Jun 2016 11:47:59 +0800 Subject: [PATCH 0951/3095] modify server group make column_headers equal to columns, to keep code consistence Change-Id: Ia96b398ad822fc5fac3753e28709c370165bda8a --- openstackclient/compute/v2/server_group.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/openstackclient/compute/v2/server_group.py b/openstackclient/compute/v2/server_group.py index 7baa6fe74f..2e275b7175 100644 --- a/openstackclient/compute/v2/server_group.py +++ b/openstackclient/compute/v2/server_group.py @@ -129,15 +129,7 @@ def take_action(self, parsed_args): data = compute_client.server_groups.list(parsed_args.all_projects) if parsed_args.long: - column_headers = ( - 'ID', - 'Name', - 'Policies', - 'Members', - 'Project Id', - 'User Id', - ) - columns = ( + column_headers = columns = ( 'ID', 'Name', 'Policies', From 54e81a9984fc9558461a45807188b6892a7b97ce Mon Sep 17 00:00:00 2001 From: Lu lei Date: Mon, 6 Jun 2016 13:40:43 +0800 Subject: [PATCH 0952/3095] Add newline to strings in stdout/stderr.write() Function stdout/stderr.write() can't break line automatically. Change-Id: I903c2d1cc1a669adb6be5aa4eb783d3b9943e685 --- openstackclient/common/quota.py | 2 +- openstackclient/compute/v2/server.py | 2 +- openstackclient/identity/v3/consumer.py | 2 +- openstackclient/identity/v3/domain.py | 2 +- openstackclient/identity/v3/endpoint.py | 2 +- openstackclient/identity/v3/group.py | 2 +- openstackclient/identity/v3/service_provider.py | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index e177fbcee8..f85d550b78 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -142,7 +142,7 @@ def take_action(self, parsed_args): if (compute_kwargs == {} and volume_kwargs == {} and network_kwargs == {}): - sys.stderr.write("No quotas updated") + sys.stderr.write("No quotas updated\n") return if parsed_args.project: diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 27abbe631b..5688b55fa2 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1464,7 +1464,7 @@ def take_action(self, parsed_args): if parsed_args.diagnostics: (resp, data) = server.diagnostics() if not resp.status_code == 200: - sys.stderr.write(_("Error retrieving diagnostics data")) + sys.stderr.write(_("Error retrieving diagnostics data\n")) return ({}, {}) else: data = _prep_server_detail(compute_client, server) diff --git a/openstackclient/identity/v3/consumer.py b/openstackclient/identity/v3/consumer.py index 0da4103d54..83809e51f0 100644 --- a/openstackclient/identity/v3/consumer.py +++ b/openstackclient/identity/v3/consumer.py @@ -101,7 +101,7 @@ def take_action(self, parsed_args): kwargs['description'] = parsed_args.description if not len(kwargs): - sys.stdout.write('Consumer not updated, no arguments present') + sys.stdout.write('Consumer not updated, no arguments present\n') return consumer = identity_client.oauth1.consumers.update( diff --git a/openstackclient/identity/v3/domain.py b/openstackclient/identity/v3/domain.py index 7fcab4f1c1..fc5aba12fc 100644 --- a/openstackclient/identity/v3/domain.py +++ b/openstackclient/identity/v3/domain.py @@ -164,7 +164,7 @@ def take_action(self, parsed_args): kwargs['enabled'] = False if not kwargs: - sys.stdout.write("Domain not updated, no arguments present") + sys.stdout.write("Domain not updated, no arguments present\n") return identity_client.domains.update(domain.id, **kwargs) diff --git a/openstackclient/identity/v3/endpoint.py b/openstackclient/identity/v3/endpoint.py index b03b78ce3b..5bc9d06b8e 100644 --- a/openstackclient/identity/v3/endpoint.py +++ b/openstackclient/identity/v3/endpoint.py @@ -213,7 +213,7 @@ def take_action(self, parsed_args): if (not parsed_args.interface and not parsed_args.url and not parsed_args.service and not parsed_args.region and not parsed_args.enabled and not parsed_args.disabled): - sys.stdout.write("Endpoint not updated, no arguments present") + sys.stdout.write("Endpoint not updated, no arguments present\n") return service_id = None diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py index 3c2435316d..8fb8a0478a 100644 --- a/openstackclient/identity/v3/group.py +++ b/openstackclient/identity/v3/group.py @@ -318,7 +318,7 @@ def take_action(self, parsed_args): kwargs['description'] = parsed_args.description if not len(kwargs): - sys.stderr.write("Group not updated, no arguments present") + sys.stderr.write("Group not updated, no arguments present\n") return identity_client.groups.update(group.id, **kwargs) diff --git a/openstackclient/identity/v3/service_provider.py b/openstackclient/identity/v3/service_provider.py index 8b433b4d66..d4fe287eec 100644 --- a/openstackclient/identity/v3/service_provider.py +++ b/openstackclient/identity/v3/service_provider.py @@ -165,7 +165,7 @@ def take_action(self, parsed_args): parsed_args.service_provider_url, parsed_args.auth_url)): sys.stdout.write("Service Provider not updated, no arguments " - "present") + "present\n") return (None, None) service_provider = federation_client.service_providers.update( From 5b317936cb04382291c56edd03d7c4c8b46671ad Mon Sep 17 00:00:00 2001 From: sunyajing Date: Mon, 6 Jun 2016 13:16:55 +0800 Subject: [PATCH 0953/3095] fix keypair help msg keypair delete and keypair show commands cannot work on keypair ID Change-Id: I958fcd2ed184706b28d63cc9c73a8b566e86a16c Closes-Bug:#1588701 --- doc/source/command-objects/keypair.rst | 4 ++-- openstackclient/compute/v2/keypair.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/source/command-objects/keypair.rst b/doc/source/command-objects/keypair.rst index e49acace3d..6638b8c93a 100644 --- a/doc/source/command-objects/keypair.rst +++ b/doc/source/command-objects/keypair.rst @@ -40,7 +40,7 @@ Delete public key .. describe:: - Public key to delete + Public key to delete (name only) keypair list ------------ @@ -66,7 +66,7 @@ Display public key details .. option:: --public-key - Show only bare public key + Show only bare public key (name only) .. describe:: diff --git a/openstackclient/compute/v2/keypair.py b/openstackclient/compute/v2/keypair.py index 8a58e8f2e6..8af209fe33 100644 --- a/openstackclient/compute/v2/keypair.py +++ b/openstackclient/compute/v2/keypair.py @@ -84,7 +84,7 @@ def get_parser(self, prog_name): parser.add_argument( 'name', metavar='', - help=_("Public key to delete") + help=_("Public key to delete (name only)") ) return parser @@ -118,7 +118,7 @@ def get_parser(self, prog_name): parser.add_argument( 'name', metavar='', - help=_("Public key to display") + help=_("Public key to display (name only)") ) parser.add_argument( '--public-key', From 4bea5d37d9feb4a5925d70ea2a6298793bc924a4 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Mon, 6 Jun 2016 18:40:38 +0800 Subject: [PATCH 0954/3095] Fix errors for "volume type unset" command Normally, we can unset multi properties but the "volume type unset" command could not, because the action "append" was missed. So I add it and also fix the unit test and doc in this patch. Change-Id: I20470f2b7bb2a8d7f292cea498826669c3418c77 --- doc/source/command-objects/volume-type.rst | 2 +- openstackclient/tests/volume/v2/test_type.py | 6 ++++-- openstackclient/volume/v2/volume_type.py | 1 + 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/doc/source/command-objects/volume-type.rst b/doc/source/command-objects/volume-type.rst index b7aea63262..50acf9fa65 100644 --- a/doc/source/command-objects/volume-type.rst +++ b/doc/source/command-objects/volume-type.rst @@ -150,7 +150,7 @@ Unset volume type properties .. code:: bash os volume type unset - [--property ] + [--property [...] ] [--project ] [--project-domain ] diff --git a/openstackclient/tests/volume/v2/test_type.py b/openstackclient/tests/volume/v2/test_type.py index 872b4ae936..10c386129d 100644 --- a/openstackclient/tests/volume/v2/test_type.py +++ b/openstackclient/tests/volume/v2/test_type.py @@ -397,17 +397,19 @@ def setUp(self): def test_type_unset(self): arglist = [ '--property', 'property', + '--property', 'multi_property', self.volume_type.id, ] verifylist = [ - ('property', 'property'), + ('property', ['property', 'multi_property']), ('volume_type', self.volume_type.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.volume_type.unset_keys.assert_called_once_with('property') + self.volume_type.unset_keys.assert_called_once_with( + ['property', 'multi_property']) self.assertIsNone(result) def test_type_unset_project_access(self): diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index 200d9bd51a..d881ce1f51 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -263,6 +263,7 @@ def get_parser(self, prog_name): parser.add_argument( '--property', metavar='', + action='append', help=_('Remove a property from this volume type ' '(repeat option to remove multiple properties)'), ) From 658dc23f26e6313dc56eba373f0406e1eac4efdd Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 3 Jun 2016 16:56:55 -0500 Subject: [PATCH 0955/3095] Fix release note links to (now) external docs Moving the old release notes into the Reno releasenots tree broke the embedded :doc: roles. Restore these using sphinx.ext.extlinks to manage the URL to the OSC docs. Add the following aliases: * lpbug - generic Launchpad bug :lpbug:`123456` * oscbp - OSC blueprints :oscbp:`Blue Print ` * oscdoc - OSC Docs :oscdoc:`Comamnd List ` Change-Id: Iecfdd666d961c35f0ccb3b9f10df1d2620cb099f --- releasenotes/source/conf.py | 20 +++++++++++++++ releasenotes/source/index.rst | 2 +- ...vious_releases.rst => pre_20_releases.rst} | 25 +++++++++---------- 3 files changed, 33 insertions(+), 14 deletions(-) rename releasenotes/source/{previous_releases.rst => pre_20_releases.rst} (97%) diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py index 1b6929e228..12ed68df29 100644 --- a/releasenotes/source/conf.py +++ b/releasenotes/source/conf.py @@ -40,8 +40,28 @@ extensions = [ 'oslosphinx', 'reno.sphinxext', + 'sphinx.ext.extlinks', ] +# Set aliases for extlinks +# * lpbug - generic Launchpad bug :lpbug:`123456` +# * oscbp - OSC blueprints :oscbp:`Blue Print ` +# * oscdoc - OSC Docs :oscdoc:`Comamnd List ` +extlinks = { + 'lpbug': ( + 'https://bugs.launchpad.net/bugs/%s', + 'Bug ', + ), + 'oscbp': ( + 'https://blueprints.launchpad.net/python-openstackclient/+spec/%s', + '', + ), + 'oscdoc': ( + 'http://docs.openstack.org/developer/python-openstackclient/%s.html', + '', + ), +} + # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index 396028942a..e0477df08b 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -7,4 +7,4 @@ OpenStackClient Release Notes unreleased mitaka - previous_releases + pre_20_releases diff --git a/releasenotes/source/previous_releases.rst b/releasenotes/source/pre_20_releases.rst similarity index 97% rename from releasenotes/source/previous_releases.rst rename to releasenotes/source/pre_20_releases.rst index 1999d38cfc..a2af7a9a23 100644 --- a/releasenotes/source/previous_releases.rst +++ b/releasenotes/source/pre_20_releases.rst @@ -1,16 +1,12 @@ -================= -Previous Releases -================= - -As of release 2.0 the release notes can be found on the OpenStack `Release Notes site`_. - -.. _`Release Notes site`: http://docs.openstack.org/releasenotes/python-openstackclient +================ +Pre-2.0 Releases +================ 1.9.0 (17 Nov 2015) =================== * Several updates to `openstack server` - Blueprint `servers `_ + Blueprint :oscbp:`servers ` * `openstack server start` * `openstack server stop` @@ -364,16 +360,18 @@ As of release 2.0 the release notes can be found on the OpenStack `Release Notes * Fix ``backup create`` to correctly use the ``--container`` value if supplied. Bug `1446751 `_ -* Document the backward-compatibility-breaking changes. - Bug `1406470 `_ +* Document the backward-compatibility-breaking changes in + :oscdoc:`Backwards Incompatible Changes `. + :lpbug:`1406470` -* Add `--parent`` option to `projct create` command. +* Add ``--parent`` option to ``project create`` command 1.1.0 (21 Apr 2015) =================== * Add global ``--os-cloud`` option to select from a list of cloud configurations. + See :oscdoc:`Configuration ` for more details. * Fix global ``--timing`` option operation. Bug `1402577 `_ @@ -461,8 +459,9 @@ As of release 2.0 the release notes can be found on the OpenStack `Release Notes =================== * The OpenStackClient content from the OpenStack Wiki has been migrated into - the OSC source repo. This includes the `commands`, `command-list` - and `humaninterfaceguide` documents. + the OSC source repo. This includes the :oscdoc:`Command Structure `, + :oscdoc:`Command List ` and + :oscdoc:`Human Interface Guide ` documents. * Set a default domain ID when both ``OS_USER_DOMAIN_ID`` and ``OS_USER_DOMAIN_NAME`` are not set. This is also done for From 5d404ecd05b46697e90e60775541eff33ac2cb22 Mon Sep 17 00:00:00 2001 From: Manjeet Singh Bhatia Date: Tue, 17 May 2016 17:40:42 +0000 Subject: [PATCH 0956/3095] Add functional tests for IP availability This patch adds functional tests for IP availability Partially-Implements: blueprint neutron-ip-capacity Change-Id: I1c1dc01801707fe8f0dc3c976e5d345d2b0db0ec --- .../tests/network/v2/test_ip_availability.py | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 functional/tests/network/v2/test_ip_availability.py diff --git a/functional/tests/network/v2/test_ip_availability.py b/functional/tests/network/v2/test_ip_availability.py new file mode 100644 index 0000000000..f1302d5f6f --- /dev/null +++ b/functional/tests/network/v2/test_ip_availability.py @@ -0,0 +1,53 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import uuid + +from functional.common import test + + +class IPAvailabilityTests(test.TestCase): + """Functional tests for IP availability. """ + NAME = uuid.uuid4().hex + NETWORK_NAME = uuid.uuid4().hex + FIELDS = ['network_name'] + + @classmethod + def setUpClass(cls): + # Create a network for the subnet. + cls.openstack('network create ' + cls.NETWORK_NAME) + opts = cls.get_show_opts(['name']) + raw_output = cls.openstack( + 'subnet create --network ' + cls.NETWORK_NAME + + ' --subnet-range 10.10.10.0/24 ' + + cls.NAME + opts + ) + expected = cls.NAME + '\n' + cls.assertOutput(expected, raw_output) + + @classmethod + def tearDownClass(cls): + raw_subnet = cls.openstack('subnet delete ' + cls.NAME) + raw_network = cls.openstack('network delete ' + cls.NETWORK_NAME) + cls.assertOutput('', raw_subnet) + cls.assertOutput('', raw_network) + + def test_ip_availability_list(self): + opts = ' -f csv -c "Network Name"' + raw_output = self.openstack('ip availability list' + opts) + self.assertIn(self.NETWORK_NAME, raw_output) + + def test_ip_availability_show(self): + opts = self.get_show_opts(self.FIELDS) + raw_output = self.openstack( + 'ip availability show ' + self.NETWORK_NAME + opts) + self.assertEqual(self.NETWORK_NAME + "\n", raw_output) From 5293bb103e75542d9defb9d0d5ed3c144f0657fe Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Sun, 5 Jun 2016 10:58:48 +0800 Subject: [PATCH 0957/3095] Fix i18n support problems in identity Change-Id: I3b48d17850343051239b5b69e8b890dba32d3ac8 Partial-bug: #1574965 --- openstackclient/identity/v2_0/catalog.py | 2 +- openstackclient/identity/v2_0/endpoint.py | 3 +- openstackclient/identity/v2_0/project.py | 9 +- openstackclient/identity/v2_0/role.py | 6 +- openstackclient/identity/v2_0/user.py | 5 +- openstackclient/identity/v3/catalog.py | 2 +- openstackclient/identity/v3/consumer.py | 13 +-- openstackclient/identity/v3/credential.py | 25 +++--- openstackclient/identity/v3/domain.py | 26 +++--- openstackclient/identity/v3/ec2creds.py | 12 +-- openstackclient/identity/v3/endpoint.py | 41 ++++----- .../identity/v3/federation_protocol.py | 46 ++++++---- openstackclient/identity/v3/group.py | 88 ++++++++++++------- .../identity/v3/identity_provider.py | 39 ++++---- openstackclient/identity/v3/mapping.py | 21 ++--- openstackclient/identity/v3/policy.py | 21 ++--- openstackclient/identity/v3/project.py | 52 +++++------ openstackclient/identity/v3/role.py | 51 ++++++----- .../identity/v3/role_assignment.py | 15 ++-- openstackclient/identity/v3/service.py | 34 +++---- .../identity/v3/service_provider.py | 38 ++++---- openstackclient/identity/v3/token.py | 35 ++++---- openstackclient/identity/v3/trust.py | 27 +++--- openstackclient/identity/v3/unscoped_saml.py | 9 +- openstackclient/identity/v3/user.py | 63 +++++++------ 25 files changed, 364 insertions(+), 319 deletions(-) diff --git a/openstackclient/identity/v2_0/catalog.py b/openstackclient/identity/v2_0/catalog.py index 53a6fe3451..c8f48cb63f 100644 --- a/openstackclient/identity/v2_0/catalog.py +++ b/openstackclient/identity/v2_0/catalog.py @@ -89,7 +89,7 @@ def take_action(self, parsed_args): break if not data: - self.app.log.error('service %s not found\n' % + self.app.log.error(_('service %s not found\n') % parsed_args.service) return ([], []) diff --git a/openstackclient/identity/v2_0/endpoint.py b/openstackclient/identity/v2_0/endpoint.py index fcb82eb7ae..09ea738f4f 100644 --- a/openstackclient/identity/v2_0/endpoint.py +++ b/openstackclient/identity/v2_0/endpoint.py @@ -81,7 +81,8 @@ def get_parser(self, prog_name): parser.add_argument( 'endpoint', metavar='', - help=_('Endpoint ID to delete')) + help=_('Endpoint ID to delete'), + ) return parser def take_action(self, parsed_args): diff --git a/openstackclient/identity/v2_0/project.py b/openstackclient/identity/v2_0/project.py index d90162c924..80f88d739f 100644 --- a/openstackclient/identity/v2_0/project.py +++ b/openstackclient/identity/v2_0/project.py @@ -88,7 +88,7 @@ def take_action(self, parsed_args): identity_client.tenants, parsed_args.name, ) - self.log.info('Returning existing project %s', project.name) + self.log.info(_('Returning existing project %s'), project.name) else: raise e @@ -231,7 +231,8 @@ def get_parser(self, prog_name): parser.add_argument( 'project', metavar='', - help=_('Project to display (name or ID)')) + help=_('Project to display (name or ID)'), + ) return parser def take_action(self, parsed_args): @@ -295,9 +296,9 @@ def get_parser(self, prog_name): metavar='', action='append', default=[], + required=True, help=_('Unset a project property ' '(repeat option to unset multiple properties)'), - required=True, ) return parser @@ -308,7 +309,7 @@ def take_action(self, parsed_args): parsed_args.project, ) if not parsed_args.property: - self.app.log.error("No changes requested\n") + self.app.log.error(_("No changes requested\n")) else: kwargs = project._info for key in parsed_args.property: diff --git a/openstackclient/identity/v2_0/role.py b/openstackclient/identity/v2_0/role.py index 1fcee15f3a..6b014d8651 100644 --- a/openstackclient/identity/v2_0/role.py +++ b/openstackclient/identity/v2_0/role.py @@ -95,7 +95,7 @@ def take_action(self, parsed_args): identity_client.roles, parsed_args.role_name, ) - self.log.info('Returning existing role %s', role.name) + self.log.info(_('Returning existing role %s'), role.name) else: raise e @@ -136,12 +136,12 @@ def get_parser(self, prog_name): parser.add_argument( '--project', metavar='', - help='Filter roles by (name or ID)', + help=_('Filter roles by (name or ID)'), ) parser.add_argument( '--user', metavar='', - help='Filter roles by (name or ID)', + help=_('Filter roles by (name or ID)'), ) return parser diff --git a/openstackclient/identity/v2_0/user.py b/openstackclient/identity/v2_0/user.py index bc9bf83789..f8f5df2997 100644 --- a/openstackclient/identity/v2_0/user.py +++ b/openstackclient/identity/v2_0/user.py @@ -104,7 +104,7 @@ def take_action(self, parsed_args): identity_client.users, parsed_args.name, ) - self.log.info('Returning existing user %s', user.name) + self.log.info(_('Returning existing user %s'), user.name) else: raise e @@ -159,7 +159,8 @@ def get_parser(self, prog_name): '--long', action='store_true', default=False, - help=_('List additional fields in output')) + help=_('List additional fields in output'), + ) return parser def take_action(self, parsed_args): diff --git a/openstackclient/identity/v3/catalog.py b/openstackclient/identity/v3/catalog.py index 78d71f59ab..4c794692d4 100644 --- a/openstackclient/identity/v3/catalog.py +++ b/openstackclient/identity/v3/catalog.py @@ -84,7 +84,7 @@ def take_action(self, parsed_args): break if not data: - self.app.log.error('service %s not found\n' % + self.app.log.error(_('service %s not found\n') % parsed_args.service) return ([], []) diff --git a/openstackclient/identity/v3/consumer.py b/openstackclient/identity/v3/consumer.py index 83809e51f0..a062b74381 100644 --- a/openstackclient/identity/v3/consumer.py +++ b/openstackclient/identity/v3/consumer.py @@ -20,6 +20,7 @@ from openstackclient.common import command from openstackclient.common import utils +from openstackclient.i18n import _ class CreateConsumer(command.ShowOne): @@ -30,7 +31,7 @@ def get_parser(self, prog_name): parser.add_argument( '--description', metavar='', - help='New consumer description', + help=_('New consumer description'), ) return parser @@ -51,7 +52,7 @@ def get_parser(self, prog_name): parser.add_argument( 'consumer', metavar='', - help='Consumer to delete', + help=_('Consumer to delete'), ) return parser @@ -83,12 +84,12 @@ def get_parser(self, prog_name): parser.add_argument( 'consumer', metavar='', - help='Consumer to modify', + help=_('Consumer to modify'), ) parser.add_argument( '--description', metavar='', - help='New consumer description', + help=_('New consumer description'), ) return parser @@ -101,7 +102,7 @@ def take_action(self, parsed_args): kwargs['description'] = parsed_args.description if not len(kwargs): - sys.stdout.write('Consumer not updated, no arguments present\n') + sys.stdout.write(_('Consumer not updated, no arguments present\n')) return consumer = identity_client.oauth1.consumers.update( @@ -116,7 +117,7 @@ def get_parser(self, prog_name): parser.add_argument( 'consumer', metavar='', - help='Consumer to display', + help=_('Consumer to display'), ) return parser diff --git a/openstackclient/identity/v3/credential.py b/openstackclient/identity/v3/credential.py index b0d2cafd0b..9901347852 100644 --- a/openstackclient/identity/v3/credential.py +++ b/openstackclient/identity/v3/credential.py @@ -19,6 +19,7 @@ from openstackclient.common import command from openstackclient.common import utils +from openstackclient.i18n import _ class CreateCredential(command.ShowOne): @@ -29,24 +30,25 @@ def get_parser(self, prog_name): parser.add_argument( 'user', metavar='', - help='Name or ID of user that owns the credential', + help=_('Name or ID of user that owns the credential'), ) parser.add_argument( '--type', default="cert", metavar='', choices=['ec2', 'cert'], - help='New credential type', + help=_('New credential type'), ) parser.add_argument( 'data', metavar='', - help='New credential data', + help=_('New credential data'), ) parser.add_argument( '--project', metavar='', - help='Project name or ID which limits the scope of the credential', + help=_('Project name or ID which limits the ' + 'scope of the credential'), ) return parser @@ -77,7 +79,7 @@ def get_parser(self, prog_name): parser.add_argument( 'credential', metavar='', - help='ID of credential to delete', + help=_('ID of credential to delete'), ) return parser @@ -108,31 +110,32 @@ def get_parser(self, prog_name): parser.add_argument( 'credential', metavar='', - help='ID of credential to change', + help=_('ID of credential to change'), ) parser.add_argument( '--user', metavar='', required=True, - help='Name or ID of user that owns the credential', + help=_('Name or ID of user that owns the credential'), ) parser.add_argument( '--type', metavar='', choices=['ec2', 'cert'], required=True, - help='New credential type', + help=_('New credential type'), ) parser.add_argument( '--data', metavar='', required=True, - help='New credential data', + help=_('New credential data'), ) parser.add_argument( '--project', metavar='', - help='Project name or ID which limits the scope of the credential', + help=_('Project name or ID which limits the ' + 'scope of the credential'), ) return parser @@ -163,7 +166,7 @@ def get_parser(self, prog_name): parser.add_argument( 'credential', metavar='', - help='ID of credential to display', + help=_('ID of credential to display'), ) return parser diff --git a/openstackclient/identity/v3/domain.py b/openstackclient/identity/v3/domain.py index fc5aba12fc..c345028f8a 100644 --- a/openstackclient/identity/v3/domain.py +++ b/openstackclient/identity/v3/domain.py @@ -33,23 +33,23 @@ def get_parser(self, prog_name): parser.add_argument( 'name', metavar='', - help='New domain name', + help=_('New domain name'), ) parser.add_argument( '--description', metavar='', - help='New domain description', + help=_('New domain description'), ) enable_group = parser.add_mutually_exclusive_group() enable_group.add_argument( '--enable', action='store_true', - help='Enable domain (default)', + help=_('Enable domain (default)'), ) enable_group.add_argument( '--disable', action='store_true', - help='Disable domain', + help=_('Disable domain'), ) parser.add_argument( '--or-show', @@ -75,7 +75,7 @@ def take_action(self, parsed_args): if parsed_args.or_show: domain = utils.find_resource(identity_client.domains, parsed_args.name) - self.log.info('Returning existing domain %s', domain.name) + self.log.info(_('Returning existing domain %s'), domain.name) else: raise e @@ -91,7 +91,7 @@ def get_parser(self, prog_name): parser.add_argument( 'domain', metavar='', - help='Domain to delete (name or ID)', + help=_('Domain to delete (name or ID)'), ) return parser @@ -123,28 +123,28 @@ def get_parser(self, prog_name): parser.add_argument( 'domain', metavar='', - help='Domain to modify (name or ID)', + help=_('Domain to modify (name or ID)'), ) parser.add_argument( '--name', metavar='', - help='New domain name', + help=_('New domain name'), ) parser.add_argument( '--description', metavar='', - help='New domain description', + help=_('New domain description'), ) enable_group = parser.add_mutually_exclusive_group() enable_group.add_argument( '--enable', action='store_true', - help='Enable domain', + help=_('Enable domain'), ) enable_group.add_argument( '--disable', action='store_true', - help='Disable domain', + help=_('Disable domain'), ) return parser @@ -164,7 +164,7 @@ def take_action(self, parsed_args): kwargs['enabled'] = False if not kwargs: - sys.stdout.write("Domain not updated, no arguments present\n") + sys.stdout.write(_("Domain not updated, no arguments present\n")) return identity_client.domains.update(domain.id, **kwargs) @@ -177,7 +177,7 @@ def get_parser(self, prog_name): parser.add_argument( 'domain', metavar='', - help='Domain to display (name or ID)', + help=_('Domain to display (name or ID)'), ) return parser diff --git a/openstackclient/identity/v3/ec2creds.py b/openstackclient/identity/v3/ec2creds.py index a12b2d3b4e..859ec2a730 100644 --- a/openstackclient/identity/v3/ec2creds.py +++ b/openstackclient/identity/v3/ec2creds.py @@ -56,18 +56,14 @@ def get_parser(self, prog_name): parser.add_argument( '--project', metavar='', - help=_( - 'Create credentials in project ' - '(name or ID; default: current authenticated project)' - ), + help=_('Create credentials in project ' + '(name or ID; default: current authenticated project)'), ) parser.add_argument( '--user', metavar='', - help=_( - 'Create credentials for user ' - '(name or ID; default: current authenticated user)' - ), + help=_('Create credentials for user ' + '(name or ID; default: current authenticated user)'), ) common.add_user_domain_option_to_parser(parser) common.add_project_domain_option_to_parser(parser) diff --git a/openstackclient/identity/v3/endpoint.py b/openstackclient/identity/v3/endpoint.py index 5bc9d06b8e..39022d27db 100644 --- a/openstackclient/identity/v3/endpoint.py +++ b/openstackclient/identity/v3/endpoint.py @@ -20,6 +20,7 @@ from openstackclient.common import command from openstackclient.common import utils +from openstackclient.i18n import _ from openstackclient.identity import common @@ -38,23 +39,23 @@ def get_parser(self, prog_name): parser.add_argument( 'service', metavar='', - help='New endpoint service (name or ID)', + help=_('New endpoint service (name or ID)'), ) parser.add_argument( 'interface', metavar='', choices=['admin', 'public', 'internal'], - help='New endpoint interface type (admin, public or internal)', + help=_('New endpoint interface type (admin, public or internal)'), ) parser.add_argument( 'url', metavar='', - help='New endpoint URL', + help=_('New endpoint URL'), ) parser.add_argument( '--region', metavar='', - help='New endpoint region ID', + help=_('New endpoint region ID'), ) enable_group = parser.add_mutually_exclusive_group() enable_group.add_argument( @@ -62,13 +63,13 @@ def get_parser(self, prog_name): dest='enabled', action='store_true', default=True, - help='Enable endpoint (default)', + help=_('Enable endpoint (default)'), ) enable_group.add_argument( '--disable', dest='enabled', action='store_false', - help='Disable endpoint', + help=_('Disable endpoint'), ) return parser @@ -100,7 +101,7 @@ def get_parser(self, prog_name): parser.add_argument( 'endpoint', metavar='', - help='Endpoint ID to delete', + help=_('Endpoint ID to delete'), ) return parser @@ -119,18 +120,18 @@ def get_parser(self, prog_name): parser.add_argument( '--service', metavar='', - help='Filter by service', + help=_('Filter by service'), ) parser.add_argument( '--interface', metavar='', choices=['admin', 'public', 'internal'], - help='Filter by interface type (admin, public or internal)', + help=_('Filter by interface type (admin, public or internal)'), ) parser.add_argument( '--region', metavar='', - help='Filter by region ID', + help=_('Filter by region ID'), ) return parser @@ -167,41 +168,41 @@ def get_parser(self, prog_name): parser.add_argument( 'endpoint', metavar='', - help='Endpoint ID to modify', + help=_('Endpoint ID to modify'), ) parser.add_argument( '--region', metavar='', - help='New endpoint region ID', + help=_('New endpoint region ID'), ) parser.add_argument( '--interface', metavar='', choices=['admin', 'public', 'internal'], - help='New endpoint interface type (admin, public or internal)', + help=_('New endpoint interface type (admin, public or internal)'), ) parser.add_argument( '--url', metavar='', - help='New endpoint URL', + help=_('New endpoint URL'), ) parser.add_argument( '--service', metavar='', - help='New endpoint service (name or ID)', + help=_('New endpoint service (name or ID)'), ) enable_group = parser.add_mutually_exclusive_group() enable_group.add_argument( '--enable', dest='enabled', action='store_true', - help='Enable endpoint', + help=_('Enable endpoint'), ) enable_group.add_argument( '--disable', dest='disabled', action='store_true', - help='Disable endpoint', + help=_('Disable endpoint'), ) return parser @@ -213,7 +214,7 @@ def take_action(self, parsed_args): if (not parsed_args.interface and not parsed_args.url and not parsed_args.service and not parsed_args.region and not parsed_args.enabled and not parsed_args.disabled): - sys.stdout.write("Endpoint not updated, no arguments present\n") + sys.stdout.write(_("Endpoint not updated, no arguments present\n")) return service_id = None @@ -244,8 +245,8 @@ def get_parser(self, prog_name): parser.add_argument( 'endpoint', metavar='', - help='Endpoint to display (endpoint ID, service ID,' - ' service name, service type)', + help=_('Endpoint to display (endpoint ID, service ID,' + ' service name, service type)'), ) return parser diff --git a/openstackclient/identity/v3/federation_protocol.py b/openstackclient/identity/v3/federation_protocol.py index 27c837c57b..c0f4bc93db 100644 --- a/openstackclient/identity/v3/federation_protocol.py +++ b/openstackclient/identity/v3/federation_protocol.py @@ -18,6 +18,7 @@ from openstackclient.common import command from openstackclient.common import utils +from openstackclient.i18n import _ class CreateProtocol(command.ShowOne): @@ -28,19 +29,22 @@ def get_parser(self, prog_name): parser.add_argument( 'federation_protocol', metavar='', - help='New federation protocol name (must be unique per identity ' - ' provider)') + help=_('New federation protocol name (must be unique ' + 'per identity provider)'), + ) parser.add_argument( '--identity-provider', metavar='', required=True, - help='Identity provider that will support the new federation ' - ' protocol (name or ID) (required)') + help=_('Identity provider that will support the new federation ' + ' protocol (name or ID) (required)'), + ) parser.add_argument( '--mapping', metavar='', required=True, - help='Mapping that is to be used (name or ID) (required)') + help=_('Mapping that is to be used (name or ID) (required)'), + ) return parser @@ -69,13 +73,15 @@ def get_parser(self, prog_name): parser.add_argument( 'federation_protocol', metavar='', - help='Federation protocol to delete (name or ID)') + help=_('Federation protocol to delete (name or ID)'), + ) parser.add_argument( '--identity-provider', metavar='', required=True, - help='Identity provider that supports ' - '(name or ID) (required)') + help=_('Identity provider that supports ' + '(name or ID) (required)'), + ) return parser @@ -94,7 +100,8 @@ def get_parser(self, prog_name): '--identity-provider', metavar='', required=True, - help='Identity provider to list (name or ID) (required)') + help=_('Identity provider to list (name or ID) (required)'), + ) return parser @@ -118,24 +125,27 @@ def get_parser(self, prog_name): parser.add_argument( 'federation_protocol', metavar='', - help='Federation protocol to modify (name or ID)') + help=_('Federation protocol to modify (name or ID)'), + ) parser.add_argument( '--identity-provider', metavar='', required=True, - help='Identity provider that supports ' - '(name or ID) (required)') + help=_('Identity provider that supports ' + '(name or ID) (required)'), + ) parser.add_argument( '--mapping', metavar='', - help='Mapping that is to be used (name or ID)') + help=_('Mapping that is to be used (name or ID)'), + ) return parser def take_action(self, parsed_args): identity_client = self.app.client_manager.identity if not parsed_args.mapping: - self.app.log.error("No changes requested") + self.app.log.error(_("No changes requested")) return protocol = identity_client.federation.protocols.update( @@ -159,13 +169,15 @@ def get_parser(self, prog_name): parser.add_argument( 'federation_protocol', metavar='', - help='Federation protocol to display (name or ID)') + help=_('Federation protocol to display (name or ID)'), + ) parser.add_argument( '--identity-provider', metavar='', required=True, - help=('Identity provider that supports ' - '(name or ID) (required)')) + help=_('Identity provider that supports ' + '(name or ID) (required)'), + ) return parser def take_action(self, parsed_args): diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py index 8fb8a0478a..fdb94da641 100644 --- a/openstackclient/identity/v3/group.py +++ b/openstackclient/identity/v3/group.py @@ -34,12 +34,12 @@ def get_parser(self, prog_name): parser.add_argument( 'group', metavar='', - help='Group to contain (name or ID)', + help=_('Group to contain (name or ID)'), ) parser.add_argument( 'user', metavar='', - help='User to add to (name or ID)', + help=_('User to add to (name or ID)'), ) common.add_group_domain_option_to_parser(parser) common.add_user_domain_option_to_parser(parser) @@ -58,11 +58,17 @@ def take_action(self, parsed_args): try: identity_client.users.add_to_group(user_id, group_id) except Exception: - sys.stderr.write("%s not added to group %s\n" % - (parsed_args.user, parsed_args.group)) + msg = _("%(user)s not added to group %(group)s\n") % { + 'user': parsed_args.user, + 'group': parsed_args.group, + } + sys.stderr.write(msg) else: - sys.stdout.write("%s added to group %s\n" % - (parsed_args.user, parsed_args.group)) + msg = _("%(user)s added to group %(group)s\n") % { + 'user': parsed_args.user, + 'group': parsed_args.group, + } + sys.stdout.write(msg) class CheckUserInGroup(command.Command): @@ -73,12 +79,12 @@ def get_parser(self, prog_name): parser.add_argument( 'group', metavar='', - help='Group to check (name or ID)', + help=_('Group to check (name or ID)'), ) parser.add_argument( 'user', metavar='', - help='User to check (name or ID)', + help=_('User to check (name or ID)'), ) common.add_group_domain_option_to_parser(parser) common.add_user_domain_option_to_parser(parser) @@ -97,11 +103,17 @@ def take_action(self, parsed_args): try: identity_client.users.check_in_group(user_id, group_id) except Exception: - sys.stderr.write("%s not in group %s\n" % - (parsed_args.user, parsed_args.group)) + msg = _("%(user)s not in group %(group)s\n") % { + 'user': parsed_args.user, + 'group': parsed_args.group, + } + sys.stderr.write(msg) else: - sys.stdout.write("%s in group %s\n" % - (parsed_args.user, parsed_args.group)) + msg = _("%(user)s in group %(group)s\n") % { + 'user': parsed_args.user, + 'group': parsed_args.group, + } + sys.stdout.write(msg) class CreateGroup(command.ShowOne): @@ -112,17 +124,17 @@ def get_parser(self, prog_name): parser.add_argument( 'name', metavar='', - help='New group name', + help=_('New group name'), ) parser.add_argument( '--domain', metavar='', - help='Domain to contain new group (name or ID)', + help=_('Domain to contain new group (name or ID)'), ) parser.add_argument( '--description', metavar='', - help='New group description', + help=_('New group description'), ) parser.add_argument( '--or-show', @@ -149,7 +161,7 @@ def take_action(self, parsed_args): group = utils.find_resource(identity_client.groups, parsed_args.name, domain_id=domain) - self.log.info('Returning existing group %s', group.name) + self.log.info(_('Returning existing group %s'), group.name) else: raise e @@ -166,11 +178,12 @@ def get_parser(self, prog_name): 'groups', metavar='', nargs="+", - help='Group(s) to delete (name or ID)') + help=_('Group(s) to delete (name or ID)'), + ) parser.add_argument( '--domain', metavar='', - help='Domain containing group(s) (name or ID)', + help=_('Domain containing group(s) (name or ID)'), ) return parser @@ -192,19 +205,19 @@ def get_parser(self, prog_name): parser.add_argument( '--domain', metavar='', - help='Filter group list by (name or ID)', + help=_('Filter group list by (name or ID)'), ) parser.add_argument( '--user', metavar='', - help='Filter group list by (name or ID)', + help=_('Filter group list by (name or ID)'), ) common.add_user_domain_option_to_parser(parser) parser.add_argument( '--long', action='store_true', default=False, - help='List additional fields in output', + help=_('List additional fields in output'), ) return parser @@ -252,12 +265,12 @@ def get_parser(self, prog_name): parser.add_argument( 'group', metavar='', - help='Group containing (name or ID)', + help=_('Group containing (name or ID)'), ) parser.add_argument( 'user', metavar='', - help='User to remove from (name or ID)', + help=_('User to remove from (name or ID)'), ) common.add_group_domain_option_to_parser(parser) common.add_user_domain_option_to_parser(parser) @@ -276,11 +289,17 @@ def take_action(self, parsed_args): try: identity_client.users.remove_from_group(user_id, group_id) except Exception: - sys.stderr.write("%s not removed from group %s\n" % - (parsed_args.user, parsed_args.group)) + msg = _("%(user)s not removed from group %(group)s\n") % { + 'user': parsed_args.user, + 'group': parsed_args.group, + } + sys.stderr.write(msg) else: - sys.stdout.write("%s removed from group %s\n" % - (parsed_args.user, parsed_args.group)) + msg = _("%(user)s removed from group %(group)s\n") % { + 'user': parsed_args.user, + 'group': parsed_args.group, + } + sys.stdout.write(msg) class SetGroup(command.Command): @@ -291,20 +310,23 @@ def get_parser(self, prog_name): parser.add_argument( 'group', metavar='', - help='Group to modify (name or ID)') + help=_('Group to modify (name or ID)'), + ) parser.add_argument( '--domain', metavar='', - help='Domain containing (name or ID)', + help=_('Domain containing (name or ID)'), ) parser.add_argument( '--name', metavar='', - help='New group name') + help=_('New group name'), + ) parser.add_argument( '--description', metavar='', - help='New group description') + help=_('New group description'), + ) return parser def take_action(self, parsed_args): @@ -331,12 +353,12 @@ def get_parser(self, prog_name): parser.add_argument( 'group', metavar='', - help='Group to display (name or ID)', + help=_('Group to display (name or ID)'), ) parser.add_argument( '--domain', metavar='', - help='Domain containing (name or ID)', + help=_('Domain containing (name or ID)'), ) return parser diff --git a/openstackclient/identity/v3/identity_provider.py b/openstackclient/identity/v3/identity_provider.py index 39f440f4fd..3749aa35e5 100644 --- a/openstackclient/identity/v3/identity_provider.py +++ b/openstackclient/identity/v3/identity_provider.py @@ -17,6 +17,7 @@ from openstackclient.common import command from openstackclient.common import utils +from openstackclient.i18n import _ class CreateIdentityProvider(command.ShowOne): @@ -27,26 +28,26 @@ def get_parser(self, prog_name): parser.add_argument( 'identity_provider_id', metavar='', - help='New identity provider name (must be unique)' + help=_('New identity provider name (must be unique)'), ) identity_remote_id_provider = parser.add_mutually_exclusive_group() identity_remote_id_provider.add_argument( '--remote-id', metavar='', action='append', - help='Remote IDs to associate with the Identity Provider ' - '(repeat option to provide multiple values)' + help=_('Remote IDs to associate with the Identity Provider ' + '(repeat option to provide multiple values)'), ) identity_remote_id_provider.add_argument( '--remote-id-file', metavar='', - help='Name of a file that contains many remote IDs to associate ' - 'with the identity provider, one per line' + help=_('Name of a file that contains many remote IDs to associate ' + 'with the identity provider, one per line'), ) parser.add_argument( '--description', metavar='', - help='New identity provider description', + help=_('New identity provider description'), ) enable_identity_provider = parser.add_mutually_exclusive_group() enable_identity_provider.add_argument( @@ -54,13 +55,13 @@ def get_parser(self, prog_name): dest='enabled', action='store_true', default=True, - help='Enable identity provider (default)', + help=_('Enable identity provider (default)'), ) enable_identity_provider.add_argument( '--disable', dest='enabled', action='store_false', - help='Disable the identity provider', + help=_('Disable the identity provider'), ) return parser @@ -94,7 +95,7 @@ def get_parser(self, prog_name): parser.add_argument( 'identity_provider', metavar='', - help='Identity provider to delete', + help=_('Identity provider to delete'), ) return parser @@ -126,37 +127,37 @@ def get_parser(self, prog_name): parser.add_argument( 'identity_provider', metavar='', - help='Identity provider to modify', + help=_('Identity provider to modify'), ) parser.add_argument( '--description', metavar='', - help='Set identity provider description', + help=_('Set identity provider description'), ) identity_remote_id_provider = parser.add_mutually_exclusive_group() identity_remote_id_provider.add_argument( '--remote-id', metavar='', action='append', - help='Remote IDs to associate with the Identity Provider ' - '(repeat option to provide multiple values)' + help=_('Remote IDs to associate with the Identity Provider ' + '(repeat option to provide multiple values)'), ) identity_remote_id_provider.add_argument( '--remote-id-file', metavar='', - help='Name of a file that contains many remote IDs to associate ' - 'with the identity provider, one per line' + help=_('Name of a file that contains many remote IDs to associate ' + 'with the identity provider, one per line'), ) enable_identity_provider = parser.add_mutually_exclusive_group() enable_identity_provider.add_argument( '--enable', action='store_true', - help='Enable the identity provider', + help=_('Enable the identity provider'), ) enable_identity_provider.add_argument( '--disable', action='store_true', - help='Disable the identity provider', + help=_('Disable the identity provider'), ) return parser @@ -168,7 +169,7 @@ def take_action(self, parsed_args): not parsed_args.remote_id and not parsed_args.remote_id_file and not parsed_args.description): - self.log.error('No changes requested') + self.log.error(_('No changes requested')) return (None, None) # Always set remote_ids if either is passed in @@ -206,7 +207,7 @@ def get_parser(self, prog_name): parser.add_argument( 'identity_provider', metavar='', - help='Identity provider to display', + help=_('Identity provider to display'), ) return parser diff --git a/openstackclient/identity/v3/mapping.py b/openstackclient/identity/v3/mapping.py index 3cdc8afc99..c45796e482 100644 --- a/openstackclient/identity/v3/mapping.py +++ b/openstackclient/identity/v3/mapping.py @@ -22,6 +22,7 @@ from openstackclient.common import command from openstackclient.common import exceptions from openstackclient.common import utils +from openstackclient.i18n import _ class _RulesReader(object): @@ -69,9 +70,9 @@ def _read_rules(self, path): try: rules = json.loads(blob) except ValueError as e: - raise exceptions.CommandError( - 'An error occurred when reading ' - 'rules from file %s: %s' % (path, e)) + msg = _("An error occurred when reading rules from file " + "%(path)s: %(error)s") % {"path": path, "error": e} + raise exceptions.CommandError(msg) else: return rules @@ -84,12 +85,12 @@ def get_parser(self, prog_name): parser.add_argument( 'mapping', metavar='', - help='New mapping name (must be unique)', + help=_('New mapping name (must be unique)'), ) parser.add_argument( '--rules', metavar='', required=True, - help='Filename that contains a set of mapping rules (required)', + help=_('Filename that contains a set of mapping rules (required)'), ) return parser @@ -113,7 +114,7 @@ def get_parser(self, prog_name): parser.add_argument( 'mapping', metavar='', - help='Mapping to delete', + help=_('Mapping to delete'), ) return parser @@ -145,12 +146,12 @@ def get_parser(self, prog_name): parser.add_argument( 'mapping', metavar='', - help='Mapping to modify', + help=_('Mapping to modify'), ) parser.add_argument( '--rules', metavar='', - help='Filename that contains a new set of mapping rules', + help=_('Filename that contains a new set of mapping rules'), ) return parser @@ -158,7 +159,7 @@ def take_action(self, parsed_args): identity_client = self.app.client_manager.identity if not parsed_args.rules: - self.app.log.error("No changes requested") + self.app.log.error(_("No changes requested")) return rules = self._read_rules(parsed_args.rules) @@ -179,7 +180,7 @@ def get_parser(self, prog_name): parser.add_argument( 'mapping', metavar='', - help='Mapping to display', + help=_('Mapping to display'), ) return parser diff --git a/openstackclient/identity/v3/policy.py b/openstackclient/identity/v3/policy.py index 3c2d1a7c94..74a783b06a 100644 --- a/openstackclient/identity/v3/policy.py +++ b/openstackclient/identity/v3/policy.py @@ -20,6 +20,7 @@ from openstackclient.common import command from openstackclient.common import utils +from openstackclient.i18n import _ class CreatePolicy(command.ShowOne): @@ -31,13 +32,13 @@ def get_parser(self, prog_name): '--type', metavar='', default="application/json", - help='New MIME type of the policy rules file ' - '(defaults to application/json)', + help=_('New MIME type of the policy rules file ' + '(defaults to application/json)'), ) parser.add_argument( 'rules', metavar='', - help='New serialized policy rules file', + help=_('New serialized policy rules file'), ) return parser @@ -62,7 +63,7 @@ def get_parser(self, prog_name): parser.add_argument( 'policy', metavar='', - help='Policy to delete', + help=_('Policy to delete'), ) return parser @@ -80,7 +81,7 @@ def get_parser(self, prog_name): '--long', action='store_true', default=False, - help='List additional fields in output', + help=_('List additional fields in output'), ) return parser @@ -107,17 +108,17 @@ def get_parser(self, prog_name): parser.add_argument( 'policy', metavar='', - help='Policy to modify', + help=_('Policy to modify'), ) parser.add_argument( '--type', metavar='', - help='New MIME type of the policy rules file', + help=_('New MIME type of the policy rules file'), ) parser.add_argument( '--rules', metavar='', - help='New serialized policy rules file', + help=_('New serialized policy rules file'), ) return parser @@ -135,7 +136,7 @@ def take_action(self, parsed_args): kwargs['type'] = parsed_args.type if not kwargs: - sys.stdout.write('Policy not updated, no arguments present \n') + sys.stdout.write(_('Policy not updated, no arguments present\n')) return identity_client.policies.update(parsed_args.policy, **kwargs) @@ -148,7 +149,7 @@ def get_parser(self, prog_name): parser.add_argument( 'policy', metavar='', - help='Policy to display', + help=_('Policy to display'), ) return parser diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index 4990b1b9a3..acf639f29d 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -34,40 +34,40 @@ def get_parser(self, prog_name): parser.add_argument( 'name', metavar='', - help='New project name', + help=_('New project name'), ) parser.add_argument( '--domain', metavar='', - help='Domain owning the project (name or ID)', + help=_('Domain owning the project (name or ID)'), ) parser.add_argument( '--parent', metavar='', - help='Parent of the project (name or ID)', + help=_('Parent of the project (name or ID)'), ) parser.add_argument( '--description', metavar='', - help='Project description', + help=_('Project description'), ) enable_group = parser.add_mutually_exclusive_group() enable_group.add_argument( '--enable', action='store_true', - help='Enable project', + help=_('Enable project'), ) enable_group.add_argument( '--disable', action='store_true', - help='Disable project', + help=_('Disable project'), ) parser.add_argument( '--property', metavar='', action=parseractions.KeyValueAction, - help='Add a property to ' - '(repeat option to set multiple properties)', + help=_('Add a property to ' + '(repeat option to set multiple properties)'), ) parser.add_argument( '--or-show', @@ -112,7 +112,7 @@ def take_action(self, parsed_args): project = utils.find_resource(identity_client.projects, parsed_args.name, domain_id=domain) - self.log.info('Returning existing project %s', project.name) + self.log.info(_('Returning existing project %s'), project.name) else: raise e @@ -129,12 +129,12 @@ def get_parser(self, prog_name): 'projects', metavar='', nargs="+", - help='Project(s) to delete (name or ID)', + help=_('Project(s) to delete (name or ID)'), ) parser.add_argument( '--domain', metavar='', - help='Domain owning (name or ID)', + help=_('Domain owning (name or ID)'), ) return parser @@ -163,18 +163,18 @@ def get_parser(self, prog_name): parser.add_argument( '--domain', metavar='', - help='Filter projects by (name or ID)', + help=_('Filter projects by (name or ID)'), ) parser.add_argument( '--user', metavar='', - help='Filter projects by (name or ID)', + help=_('Filter projects by (name or ID)'), ) parser.add_argument( '--long', action='store_true', default=False, - help='List additional fields in output', + help=_('List additional fields in output'), ) return parser @@ -219,40 +219,40 @@ def get_parser(self, prog_name): parser.add_argument( 'project', metavar='', - help='Project to modify (name or ID)', + help=_('Project to modify (name or ID)'), ) parser.add_argument( '--name', metavar='', - help='Set project name', + help=_('Set project name'), ) parser.add_argument( '--domain', metavar='', - help='Domain owning (name or ID)', + help=_('Domain owning (name or ID)'), ) parser.add_argument( '--description', metavar='', - help='Set project description', + help=_('Set project description'), ) enable_group = parser.add_mutually_exclusive_group() enable_group.add_argument( '--enable', action='store_true', - help='Enable project', + help=_('Enable project'), ) enable_group.add_argument( '--disable', action='store_true', - help='Disable project', + help=_('Disable project'), ) parser.add_argument( '--property', metavar='', action=parseractions.KeyValueAction, - help='Set a property on ' - '(repeat option to set multiple properties)', + help=_('Set a property on ' + '(repeat option to set multiple properties)'), ) return parser @@ -293,24 +293,24 @@ def get_parser(self, prog_name): parser.add_argument( 'project', metavar='', - help='Project to display (name or ID)', + help=_('Project to display (name or ID)'), ) parser.add_argument( '--domain', metavar='', - help='Domain owning (name or ID)', + help=_('Domain owning (name or ID)'), ) parser.add_argument( '--parents', action='store_true', default=False, - help='Show the project\'s parents as a list', + help=_('Show the project\'s parents as a list'), ) parser.add_argument( '--children', action='store_true', default=False, - help='Show project\'s subtree (children) as a list', + help=_('Show project\'s subtree (children) as a list'), ) return parser diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index f93c9d804f..e7078f44c6 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -31,23 +31,23 @@ def _add_identity_and_resource_options_to_parser(parser): domain_or_project.add_argument( '--domain', metavar='', - help='Include (name or ID)', + help=_('Include (name or ID)'), ) domain_or_project.add_argument( '--project', metavar='', - help='Include (name or ID)', + help=_('Include (name or ID)'), ) user_or_group = parser.add_mutually_exclusive_group() user_or_group.add_argument( '--user', metavar='', - help='Include (name or ID)', + help=_('Include (name or ID)'), ) user_or_group.add_argument( '--group', metavar='', - help='Include (name or ID)', + help=_('Include (name or ID)'), ) common.add_group_domain_option_to_parser(parser) common.add_project_domain_option_to_parser(parser) @@ -112,7 +112,7 @@ def get_parser(self, prog_name): parser.add_argument( 'role', metavar='', - help='Role to add to (name or ID)', + help=_('Role to add to (name or ID)'), ) _add_identity_and_resource_options_to_parser(parser) return parser @@ -131,9 +131,9 @@ def take_action(self, parsed_args): kwargs = _process_identity_and_resource_options( parsed_args, self.app.client_manager.identity) if not kwargs: - sys.stderr.write("Role not added, incorrect set of arguments " - "provided. See openstack --help for more " - "details\n") + sys.stderr.write(_("Role not added, incorrect set of arguments " + "provided. See openstack --help for more " + "details\n")) return identity_client.roles.grant(role.id, **kwargs) @@ -147,7 +147,7 @@ def get_parser(self, prog_name): parser.add_argument( 'name', metavar='', - help='New role name', + help=_('New role name'), ) parser.add_argument( '--or-show', @@ -165,7 +165,7 @@ def take_action(self, parsed_args): if parsed_args.or_show: role = utils.find_resource(identity_client.roles, parsed_args.name) - self.log.info('Returning existing role %s', role.name) + self.log.info(_('Returning existing role %s'), role.name) else: raise e @@ -182,7 +182,7 @@ def get_parser(self, prog_name): 'roles', metavar='', nargs="+", - help='Role(s) to delete (name or ID)', + help=_('Role(s) to delete (name or ID)'), ) return parser @@ -285,9 +285,9 @@ def take_action(self, parsed_args): group_role.group = group.name group_role.project = project.name else: - sys.stderr.write("Error: If a user or group is specified, either " - "--domain or --project must also be specified to " - "list role grants.\n") + sys.stderr.write(_("Error: If a user or group is specified, " + "either --domain or --project must also be " + "specified to list role grants.\n")) return ([], []) return (columns, @@ -305,7 +305,7 @@ def get_parser(self, prog_name): parser.add_argument( 'role', metavar='', - help='Role to remove (name or ID)', + help=_('Role to remove (name or ID)'), ) _add_identity_and_resource_options_to_parser(parser) return parser @@ -315,9 +315,8 @@ def take_action(self, parsed_args): if (not parsed_args.user and not parsed_args.domain and not parsed_args.group and not parsed_args.project): - sys.stderr.write("Incorrect set of arguments " - "provided. See openstack --help for more " - "details\n") + sys.stderr.write(_("Incorrect set of arguments provided. " + "See openstack --help for more details\n")) return role = utils.find_resource( identity_client.roles, @@ -327,8 +326,9 @@ def take_action(self, parsed_args): kwargs = _process_identity_and_resource_options( parsed_args, self.app.client_manager.identity) if not kwargs: - sys.stderr.write("Role not removed, incorrect set of arguments \ - provided. See openstack --help for more details\n") + sys.stderr.write(_("Role not removed, incorrect set of arguments " + "provided. See openstack --help for more " + "details\n")) return identity_client.roles.revoke(role.id, **kwargs) @@ -341,12 +341,12 @@ def get_parser(self, prog_name): parser.add_argument( 'role', metavar='', - help='Role to modify (name or ID)', + help=_('Role to modify (name or ID)'), ) parser.add_argument( '--name', metavar='', - help='Set role name', + help=_('Set role name'), ) return parser @@ -354,9 +354,8 @@ def take_action(self, parsed_args): identity_client = self.app.client_manager.identity if not parsed_args.name: - sys.stderr.write("Incorrect set of arguments " - "provided. See openstack --help for more " - "details\n") + sys.stderr.write(_("Incorrect set of arguments provided. " + "See openstack --help for more details\n")) return role = utils.find_resource( identity_client.roles, @@ -374,7 +373,7 @@ def get_parser(self, prog_name): parser.add_argument( 'role', metavar='', - help='Role to display (name or ID)', + help=_('Role to display (name or ID)'), ) return parser diff --git a/openstackclient/identity/v3/role_assignment.py b/openstackclient/identity/v3/role_assignment.py index a1418a8221..521075fef5 100644 --- a/openstackclient/identity/v3/role_assignment.py +++ b/openstackclient/identity/v3/role_assignment.py @@ -15,6 +15,7 @@ from openstackclient.common import command from openstackclient.common import utils +from openstackclient.i18n import _ from openstackclient.identity import common @@ -27,41 +28,41 @@ def get_parser(self, prog_name): '--effective', action="store_true", default=False, - help='Returns only effective role assignments', + help=_('Returns only effective role assignments'), ) parser.add_argument( '--role', metavar='', - help='Role to filter (name or ID)', + help=_('Role to filter (name or ID)'), ) parser.add_argument( '--names', action="store_true", - help='Display names instead of IDs', + help=_('Display names instead of IDs'), ) user_or_group = parser.add_mutually_exclusive_group() user_or_group.add_argument( '--user', metavar='', - help='User to filter (name or ID)', + help=_('User to filter (name or ID)'), ) common.add_user_domain_option_to_parser(parser) user_or_group.add_argument( '--group', metavar='', - help='Group to filter (name or ID)', + help=_('Group to filter (name or ID)'), ) common.add_group_domain_option_to_parser(parser) domain_or_project = parser.add_mutually_exclusive_group() domain_or_project.add_argument( '--domain', metavar='', - help='Domain to filter (name or ID)', + help=_('Domain to filter (name or ID)'), ) domain_or_project.add_argument( '--project', metavar='', - help='Project to filter (name or ID)', + help=_('Project to filter (name or ID)'), ) common.add_project_domain_option_to_parser(parser) common.add_inherited_option_to_parser(parser) diff --git a/openstackclient/identity/v3/service.py b/openstackclient/identity/v3/service.py index f43ada56e4..35507a63c4 100644 --- a/openstackclient/identity/v3/service.py +++ b/openstackclient/identity/v3/service.py @@ -20,6 +20,7 @@ from openstackclient.common import command from openstackclient.common import utils +from openstackclient.i18n import _ from openstackclient.identity import common @@ -31,28 +32,28 @@ def get_parser(self, prog_name): parser.add_argument( 'type', metavar='', - help='New service type (compute, image, identity, volume, etc)', + help=_('New service type (compute, image, identity, volume, etc)'), ) parser.add_argument( '--name', metavar='', - help='New service name', + help=_('New service name'), ) parser.add_argument( '--description', metavar='', - help='New service description', + help=_('New service description'), ) enable_group = parser.add_mutually_exclusive_group() enable_group.add_argument( '--enable', action='store_true', - help='Enable service (default)', + help=_('Enable service (default)'), ) enable_group.add_argument( '--disable', action='store_true', - help='Disable service', + help=_('Disable service'), ) return parser @@ -82,7 +83,7 @@ def get_parser(self, prog_name): parser.add_argument( 'service', metavar='', - help='Service to delete (type, name or ID)', + help=_('Service to delete (type, name or ID)'), ) return parser @@ -103,7 +104,7 @@ def get_parser(self, prog_name): '--long', action='store_true', default=False, - help='List additional fields in output', + help=_('List additional fields in output'), ) return parser @@ -128,33 +129,33 @@ def get_parser(self, prog_name): parser.add_argument( 'service', metavar='', - help='Service to update (type, name or ID)', + help=_('Service to update (type, name or ID)'), ) parser.add_argument( '--type', metavar='', - help='New service type (compute, image, identity, volume, etc)', + help=_('New service type (compute, image, identity, volume, etc)'), ) parser.add_argument( '--name', metavar='', - help='New service name', + help=_('New service name'), ) parser.add_argument( '--description', metavar='', - help='New service description', + help=_('New service description'), ) enable_group = parser.add_mutually_exclusive_group() enable_group.add_argument( '--enable', action='store_true', - help='Enable service', + help=_('Enable service'), ) enable_group.add_argument( '--disable', action='store_true', - help='Disable service', + help=_('Disable service'), ) return parser @@ -166,9 +167,8 @@ def take_action(self, parsed_args): and not parsed_args.description and not parsed_args.enable and not parsed_args.disable): - sys.stderr.write("Incorrect set of arguments " - "provided. See openstack --help for more " - "details\n") + sys.stderr.write(_("Incorrect set of arguments provided. " + "See openstack --help for more details\n")) return service = common.find_service(identity_client, parsed_args.service) @@ -198,7 +198,7 @@ def get_parser(self, prog_name): parser.add_argument( 'service', metavar='', - help='Service to display (type, name or ID)', + help=_('Service to display (type, name or ID)'), ) return parser diff --git a/openstackclient/identity/v3/service_provider.py b/openstackclient/identity/v3/service_provider.py index d4fe287eec..f1e9f35d07 100644 --- a/openstackclient/identity/v3/service_provider.py +++ b/openstackclient/identity/v3/service_provider.py @@ -18,6 +18,7 @@ from openstackclient.common import command from openstackclient.common import utils +from openstackclient.i18n import _ class CreateServiceProvider(command.ShowOne): @@ -28,26 +29,26 @@ def get_parser(self, prog_name): parser.add_argument( 'service_provider_id', metavar='', - help='New service provider name (must be unique)' + help=_('New service provider name (must be unique)'), ) parser.add_argument( '--auth-url', metavar='', required=True, - help='Authentication URL of remote federated service provider ' - '(required)', + help=_('Authentication URL of remote federated service provider ' + '(required)'), ) parser.add_argument( '--description', metavar='', - help='New service provider description', + help=_('New service provider description'), ) parser.add_argument( '--service-provider-url', metavar='', required=True, - help='A service URL where SAML assertions are being sent ' - '(required)', + help=_('A service URL where SAML assertions are being sent ' + '(required)'), ) enable_service_provider = parser.add_mutually_exclusive_group() @@ -56,13 +57,13 @@ def get_parser(self, prog_name): dest='enabled', action='store_true', default=True, - help='Enable the service provider (default)', + help=_('Enable the service provider (default)'), ) enable_service_provider.add_argument( '--disable', dest='enabled', action='store_false', - help='Disable the service provider', + help=_('Disable the service provider'), ) return parser @@ -88,7 +89,7 @@ def get_parser(self, prog_name): parser.add_argument( 'service_provider', metavar='', - help='Service provider to delete', + help=_('Service provider to delete'), ) return parser @@ -121,34 +122,35 @@ def get_parser(self, prog_name): parser.add_argument( 'service_provider', metavar='', - help='Service provider to modify', + help=_('Service provider to modify'), ) parser.add_argument( '--auth-url', metavar='', - help='New Authentication URL of remote federated service provider', + help=_('New Authentication URL of remote ' + 'federated service provider'), ) parser.add_argument( '--description', metavar='', - help='New service provider description', + help=_('New service provider description'), ) parser.add_argument( '--service-provider-url', metavar='', - help='New service provider URL, where SAML assertions are sent', + help=_('New service provider URL, where SAML assertions are sent'), ) enable_service_provider = parser.add_mutually_exclusive_group() enable_service_provider.add_argument( '--enable', action='store_true', - help='Enable the service provider', + help=_('Enable the service provider'), ) enable_service_provider.add_argument( '--disable', action='store_true', - help='Disable the service provider', + help=_('Disable the service provider'), ) return parser @@ -164,8 +166,8 @@ def take_action(self, parsed_args): if not any((enabled is not None, parsed_args.description, parsed_args.service_provider_url, parsed_args.auth_url)): - sys.stdout.write("Service Provider not updated, no arguments " - "present\n") + sys.stdout.write(_("Service Provider not updated, no arguments " + "present\n")) return (None, None) service_provider = federation_client.service_providers.update( @@ -184,7 +186,7 @@ def get_parser(self, prog_name): parser.add_argument( 'service_provider', metavar='', - help='Service provider to display', + help=_('Service provider to display'), ) return parser diff --git a/openstackclient/identity/v3/token.py b/openstackclient/identity/v3/token.py index bdc5e95f69..56a7497cfc 100644 --- a/openstackclient/identity/v3/token.py +++ b/openstackclient/identity/v3/token.py @@ -20,6 +20,7 @@ from openstackclient.common import command from openstackclient.common import exceptions from openstackclient.common import utils +from openstackclient.i18n import _ from openstackclient.identity import common @@ -31,17 +32,17 @@ def get_parser(self, prog_name): parser.add_argument( '--request-key', metavar='', - help='Request token to authorize (ID only) (required)', - required=True + required=True, + help=_('Request token to authorize (ID only) (required)'), ) parser.add_argument( '--role', metavar='', action='append', default=[], - help='Roles to authorize (name or ID) ' - '(repeat option to set multiple values, required)', - required=True + required=True, + help=_('Roles to authorize (name or ID) ' + '(repeat option to set multiple values, required)'), ) return parser @@ -72,31 +73,31 @@ def get_parser(self, prog_name): parser.add_argument( '--consumer-key', metavar='', - help='Consumer key (required)', + help=_('Consumer key (required)'), required=True ) parser.add_argument( '--consumer-secret', metavar='', - help='Consumer secret (required)', + help=_('Consumer secret (required)'), required=True ) parser.add_argument( '--request-key', metavar='', - help='Request token to exchange for access token (required)', + help=_('Request token to exchange for access token (required)'), required=True ) parser.add_argument( '--request-secret', metavar='', - help='Secret associated with (required)', + help=_('Secret associated with (required)'), required=True ) parser.add_argument( '--verifier', metavar='', - help='Verifier associated with (required)', + help=_('Verifier associated with (required)'), required=True ) return parser @@ -118,26 +119,26 @@ def get_parser(self, prog_name): parser.add_argument( '--consumer-key', metavar='', - help='Consumer key (required)', + help=_('Consumer key (required)'), required=True ) parser.add_argument( '--consumer-secret', metavar='', - help='Consumer secret (required)', + help=_('Consumer secret (required)'), required=True ) parser.add_argument( '--project', metavar='', - help='Project that consumer wants to access (name or ID)' - ' (required)', + help=_('Project that consumer wants to access (name or ID)' + ' (required)'), required=True ) parser.add_argument( '--domain', metavar='', - help='Domain owning (name or ID)', + help=_('Domain owning (name or ID)'), ) return parser @@ -175,7 +176,7 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): if not self.app.client_manager.auth_ref: raise exceptions.AuthorizationFailure( - "Only an authorized user may issue a new token.") + _("Only an authorized user may issue a new token.")) token = self.app.client_manager.auth_ref.service_catalog.get_token() if 'tenant_id' in token: token['project_id'] = token.pop('tenant_id') @@ -190,7 +191,7 @@ def get_parser(self, prog_name): parser.add_argument( 'token', metavar='', - help='Token to be deleted', + help=_('Token to be deleted'), ) return parser diff --git a/openstackclient/identity/v3/trust.py b/openstackclient/identity/v3/trust.py index b6f5c6b41f..336b703cbd 100644 --- a/openstackclient/identity/v3/trust.py +++ b/openstackclient/identity/v3/trust.py @@ -18,6 +18,7 @@ from openstackclient.common import command from openstackclient.common import utils +from openstackclient.i18n import _ from openstackclient.identity import common @@ -29,26 +30,26 @@ def get_parser(self, prog_name): parser.add_argument( 'trustor', metavar='', - help='User that is delegating authorization (name or ID)', + help=_('User that is delegating authorization (name or ID)'), ) parser.add_argument( 'trustee', metavar='', - help='User that is assuming authorization (name or ID)', + help=_('User that is assuming authorization (name or ID)'), ) parser.add_argument( '--project', metavar='', required=True, - help='Project being delegated (name or ID) (required)', + help=_('Project being delegated (name or ID) (required)'), ) parser.add_argument( '--role', metavar='', action='append', default=[], - help='Roles to authorize (name or ID) ' - '(repeat option to set multiple values, required)', + help=_('Roles to authorize (name or ID) ' + '(repeat option to set multiple values, required)'), required=True ) parser.add_argument( @@ -56,25 +57,25 @@ def get_parser(self, prog_name): dest='impersonate', action='store_true', default=False, - help='Tokens generated from the trust will represent ' - ' (defaults to False)', + help=_('Tokens generated from the trust will represent ' + ' (defaults to False)'), ) parser.add_argument( '--expiration', metavar='', - help='Sets an expiration date for the trust' - ' (format of YYYY-mm-ddTHH:MM:SS)', + help=_('Sets an expiration date for the trust' + ' (format of YYYY-mm-ddTHH:MM:SS)'), ) common.add_project_domain_option_to_parser(parser) parser.add_argument( '--trustor-domain', metavar='', - help='Domain that contains (name or ID)', + help=_('Domain that contains (name or ID)'), ) parser.add_argument( '--trustee-domain', metavar='', - help='Domain that contains (name or ID)', + help=_('Domain that contains (name or ID)'), ) return parser @@ -136,8 +137,8 @@ def get_parser(self, prog_name): parser.add_argument( 'trust', metavar='', - help='Trust(s) to delete', nargs="+", + help=_('Trust(s) to delete'), ) return parser @@ -170,7 +171,7 @@ def get_parser(self, prog_name): parser.add_argument( 'trust', metavar='', - help='Trust to display', + help=_('Trust to display'), ) return parser diff --git a/openstackclient/identity/v3/unscoped_saml.py b/openstackclient/identity/v3/unscoped_saml.py index 8e2616a691..5cb8e4868d 100644 --- a/openstackclient/identity/v3/unscoped_saml.py +++ b/openstackclient/identity/v3/unscoped_saml.py @@ -20,6 +20,7 @@ from openstackclient.common import command from openstackclient.common import exceptions from openstackclient.common import utils +from openstackclient.i18n import _ UNSCOPED_AUTH_PLUGINS = ['v3unscopedsaml', 'v3unscopedadfs', 'v3oidc'] @@ -33,10 +34,10 @@ def _decorated(self, parsed_args): if auth_plugin_name in UNSCOPED_AUTH_PLUGINS: return func(self, parsed_args) else: - msg = ('This command requires the use of an unscoped SAML ' - 'authentication plugin. Please use argument ' - '--os-auth-type with one of the following ' - 'plugins: ' + ', '.join(UNSCOPED_AUTH_PLUGINS)) + msg = (_('This command requires the use of an unscoped SAML ' + 'authentication plugin. Please use argument ' + '--os-auth-type with one of the following ' + 'plugins: %s') % ', '.join(UNSCOPED_AUTH_PLUGINS)) raise exceptions.CommandError(msg) return _decorated diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index 8bc4183b4d..a71b4b7808 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -35,50 +35,50 @@ def get_parser(self, prog_name): parser.add_argument( 'name', metavar='', - help='New user name', + help=_('New user name'), ) parser.add_argument( '--domain', metavar='', - help='Default domain (name or ID)', + help=_('Default domain (name or ID)'), ) parser.add_argument( '--project', metavar='', - help='Default project (name or ID)', + help=_('Default project (name or ID)'), ) common.add_project_domain_option_to_parser(parser) parser.add_argument( '--password', metavar='', - help='Set user password', + help=_('Set user password'), ) parser.add_argument( '--password-prompt', dest="password_prompt", action="store_true", - help='Prompt interactively for password', + help=_('Prompt interactively for password'), ) parser.add_argument( '--email', metavar='', - help='Set user email address', + help=_('Set user email address'), ) parser.add_argument( '--description', metavar='', - help='User description', + help=_('User description'), ) enable_group = parser.add_mutually_exclusive_group() enable_group.add_argument( '--enable', action='store_true', - help='Enable user (default)', + help=_('Enable user (default)'), ) enable_group.add_argument( '--disable', action='store_true', - help='Disable user', + help=_('Disable user'), ) parser.add_argument( '--or-show', @@ -122,7 +122,7 @@ def take_action(self, parsed_args): user = utils.find_resource(identity_client.users, parsed_args.name, domain_id=domain_id) - self.log.info('Returning existing user %s', user.name) + self.log.info(_('Returning existing user %s'), user.name) else: raise e @@ -139,12 +139,12 @@ def get_parser(self, prog_name): 'users', metavar='', nargs="+", - help='User(s) to delete (name or ID)', + help=_('User(s) to delete (name or ID)'), ) parser.add_argument( '--domain', metavar='', - help='Domain owning (name or ID)', + help=_('Domain owning (name or ID)'), ) return parser @@ -173,24 +173,24 @@ def get_parser(self, prog_name): parser.add_argument( '--domain', metavar='', - help='Filter users by (name or ID)', + help=_('Filter users by (name or ID)'), ) project_or_group = parser.add_mutually_exclusive_group() project_or_group.add_argument( '--group', metavar='', - help='Filter users by membership (name or ID)', + help=_('Filter users by membership (name or ID)'), ) project_or_group.add_argument( '--project', metavar='', - help='Filter users by (name or ID)', + help=_('Filter users by (name or ID)'), ) parser.add_argument( '--long', action='store_true', default=False, - help='List additional fields in output', + help=_('List additional fields in output'), ) return parser @@ -273,50 +273,50 @@ def get_parser(self, prog_name): parser.add_argument( 'user', metavar='', - help='User to change (name or ID)', + help=_('User to change (name or ID)'), ) parser.add_argument( '--name', metavar='', - help='Set user name', + help=_('Set user name'), ) parser.add_argument( '--project', metavar='', - help='Set default project (name or ID)', + help=_('Set default project (name or ID)'), ) common.add_project_domain_option_to_parser(parser) parser.add_argument( '--password', metavar='', - help='Set user password', + help=_('Set user password'), ) parser.add_argument( '--password-prompt', dest="password_prompt", action="store_true", - help='Prompt interactively for password', + help=_('Prompt interactively for password'), ) parser.add_argument( '--email', metavar='', - help='Set user email address', + help=_('Set user email address'), ) parser.add_argument( '--description', metavar='', - help='Set user description', + help=_('Set user description'), ) enable_group = parser.add_mutually_exclusive_group() enable_group.add_argument( '--enable', action='store_true', - help='Enable user (default)', + help=_('Enable user (default)'), ) enable_group.add_argument( '--disable', action='store_true', - help='Disable user', + help=_('Disable user'), ) return parser @@ -334,9 +334,8 @@ def take_action(self, parsed_args): and not parsed_args.description and not parsed_args.enable and not parsed_args.disable): - sys.stderr.write("Incorrect set of arguments " - "provided. See openstack --help for more " - "details\n") + sys.stderr.write(_("Incorrect set of arguments provided. " + "See openstack --help for more details\n")) return user = utils.find_resource( @@ -376,12 +375,12 @@ def get_parser(self, prog_name): parser.add_argument( '--password', metavar='', - help='New user password' + help=_('New user password'), ) parser.add_argument( '--original-password', metavar='', - help='Original user password' + help=_('Original user password'), ) return parser @@ -429,12 +428,12 @@ def get_parser(self, prog_name): parser.add_argument( 'user', metavar='', - help='User to display (name or ID)', + help=_('User to display (name or ID)'), ) parser.add_argument( '--domain', metavar='', - help='Domain owning (name or ID)', + help=_('Domain owning (name or ID)'), ) return parser From eb421f6dabead63318139e2db5d0d11498c40a68 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Fri, 3 Jun 2016 17:29:41 +0800 Subject: [PATCH 0958/3095] Support error handling for "port delete" command "Port delete" command supported deleting multi ports before but didn't support error handing, This patch add the error handling following the rules in doc/source/command-errors.rst. Change-Id: I4ea69f2279763626d6a27cad1ca0ee99822d016d Partially-Implements: blueprint multi-argument-network --- openstackclient/network/v2/port.py | 17 ++++- openstackclient/tests/network/v2/test_port.py | 68 +++++++++++++++++-- 2 files changed, 76 insertions(+), 9 deletions(-) diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index ca02281fd3..7ef3964bf9 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -284,10 +284,23 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): client = self.app.client_manager.network + result = 0 for port in parsed_args.port: - res = client.find_port(port) - client.delete_port(res) + try: + obj = client.find_port(port, ignore_missing=False) + client.delete_port(obj) + except Exception as e: + result += 1 + self.app.log.error(_("Failed to delete port with " + "name or ID '%(port)s': %(e)s") + % {'port': port, 'e': e}) + + if result > 0: + total = len(parsed_args.port) + msg = (_("%(result)s of %(total)s ports failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListPort(command.Lister): diff --git a/openstackclient/tests/network/v2/test_port.py b/openstackclient/tests/network/v2/test_port.py index f2aa26cf88..bb23cf631c 100644 --- a/openstackclient/tests/network/v2/test_port.py +++ b/openstackclient/tests/network/v2/test_port.py @@ -13,6 +13,8 @@ import mock +from mock import call +from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.network.v2 import port from openstackclient.tests.network.v2 import fakes as network_fakes @@ -174,30 +176,82 @@ def test_create_full_options(self): class TestDeletePort(TestPort): - # The port to delete. - _port = network_fakes.FakePort.create_one_port() + # Ports to delete. + _ports = network_fakes.FakePort.create_ports(count=2) def setUp(self): super(TestDeletePort, self).setUp() self.network.delete_port = mock.Mock(return_value=None) - self.network.find_port = mock.Mock(return_value=self._port) + self.network.find_port = network_fakes.FakePort.get_ports( + ports=self._ports) # Get the command object to test self.cmd = port.DeletePort(self.app, self.namespace) - def test_delete(self): + def test_port_delete(self): arglist = [ - self._port.name, + self._ports[0].name, ] verifylist = [ - ('port', [self._port.name]), + ('port', [self._ports[0].name]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.network.delete_port.assert_called_once_with(self._port) + self.network.find_port.assert_called_once_with( + self._ports[0].name, ignore_missing=False) + self.network.delete_port.assert_called_once_with(self._ports[0]) self.assertIsNone(result) + def test_multi_ports_delete(self): + arglist = [] + verifylist = [] + + for p in self._ports: + arglist.append(p.name) + verifylist = [ + ('port', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [] + for p in self._ports: + calls.append(call(p)) + self.network.delete_port.assert_has_calls(calls) + self.assertIsNone(result) + + def test_multi_ports_delete_with_exception(self): + arglist = [ + self._ports[0].name, + 'unexist_port', + ] + verifylist = [ + ('port', + [self._ports[0].name, 'unexist_port']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self._ports[0], exceptions.CommandError] + self.network.find_port = ( + mock.MagicMock(side_effect=find_mock_result) + ) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 ports failed to delete.', str(e)) + + self.network.find_port.assert_any_call( + self._ports[0].name, ignore_missing=False) + self.network.find_port.assert_any_call( + 'unexist_port', ignore_missing=False) + self.network.delete_port.assert_called_once_with( + self._ports[0] + ) + class TestListPort(TestPort): From 0fd3a8c7877a8a04cd3234f2b44b7dd3117e2a80 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Fri, 3 Jun 2016 20:58:23 +0800 Subject: [PATCH 0959/3095] Update unit test test_extension with fake class Add FakeExtension class in networkv2, computev2, volumev2, identityv2_0 and update unit test test/common/test_extension.py Change-Id: I94815de7801860edb7fa91a7d146455cab946652 --- .../tests/common/test_extension.py | 156 +++++++++--------- openstackclient/tests/compute/v2/fakes.py | 56 ++++--- openstackclient/tests/identity/v2_0/fakes.py | 58 ++++--- openstackclient/tests/network/v2/fakes.py | 53 ++++-- openstackclient/tests/volume/v2/fakes.py | 58 ++++--- 5 files changed, 222 insertions(+), 159 deletions(-) diff --git a/openstackclient/tests/common/test_extension.py b/openstackclient/tests/common/test_extension.py index 0736a3e54d..17a3b492dd 100644 --- a/openstackclient/tests/common/test_extension.py +++ b/openstackclient/tests/common/test_extension.py @@ -11,7 +11,6 @@ # under the License. # -import copy import mock from openstackclient.common import extension @@ -29,26 +28,38 @@ class TestExtension(utils.TestCommand): def setUp(self): super(TestExtension, self).setUp() - self.app.client_manager.identity = identity_fakes.FakeIdentityv2Client( + identity_client = identity_fakes.FakeIdentityv2Client( endpoint=fakes.AUTH_URL, token=fakes.AUTH_TOKEN, ) - self.identity_extensions_mock = ( - self.app.client_manager.identity.extensions) + self.app.client_manager.identity = identity_client + self.identity_extensions_mock = identity_client.extensions self.identity_extensions_mock.reset_mock() - self.app.client_manager.compute = compute_fakes.FakeComputev2Client( + compute_client = compute_fakes.FakeComputev2Client( endpoint=fakes.AUTH_URL, token=fakes.AUTH_TOKEN, ) + self.app.client_manager.compute = compute_client + compute_client.list_extensions = mock.Mock() + self.compute_extensions_mock = compute_client.list_extensions + self.compute_extensions_mock.reset_mock() - self.app.client_manager.volume = volume_fakes.FakeVolumeClient( + volume_client = volume_fakes.FakeVolumeClient( endpoint=fakes.AUTH_URL, token=fakes.AUTH_TOKEN, ) + self.app.client_manager.volume = volume_client + volume_client.list_extensions = mock.Mock() + self.volume_extensions_mock = volume_client.list_extensions + self.volume_extensions_mock.reset_mock() - network_client = network_fakes.FakeNetworkV2Client() + network_client = network_fakes.FakeNetworkV2Client( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN, + ) self.app.client_manager.network = network_client + network_client.extensions = mock.Mock() self.network_extensions_mock = network_client.extensions self.network_extensions_mock.reset_mock() @@ -59,38 +70,21 @@ class TestExtensionList(TestExtension): long_columns = ('Name', 'Namespace', 'Description', 'Alias', 'Updated', 'Links') + volume_extension = volume_fakes.FakeExtension.create_one_extension() + identity_extension = identity_fakes.FakeExtension.create_one_extension() + compute_extension = compute_fakes.FakeExtension.create_one_extension() + network_extension = network_fakes.FakeExtension.create_one_extension() + def setUp(self): super(TestExtensionList, self).setUp() self.identity_extensions_mock.list.return_value = [ - fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.EXTENSION), - loaded=True, - ), - ] - - self.app.client_manager.compute.list_extensions = mock.Mock() - self.compute_extensions_mock = ( - self.app.client_manager.compute.list_extensions) + self.identity_extension] self.compute_extensions_mock.show_all.return_value = [ - fakes.FakeResource( - None, - copy.deepcopy(compute_fakes.EXTENSION), - loaded=True, - ), - ] - - self.app.client_manager.volume.list_extensions = mock.Mock() - self.volume_extensions_mock = ( - self.app.client_manager.volume.list_extensions) + self.compute_extension] self.volume_extensions_mock.show_all.return_value = [ - fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.EXTENSION), - loaded=True, - ), - ] + self.volume_extension] + self.network_extensions_mock.return_value = [self.network_extension] # Get the command object to test self.cmd = extension.ListExtension(self.app, None) @@ -115,24 +109,24 @@ def test_extension_list_no_options(self): verifylist = [] datalist = ( ( - identity_fakes.extension_name, - identity_fakes.extension_alias, - identity_fakes.extension_description, + self.identity_extension.name, + self.identity_extension.alias, + self.identity_extension.description, ), ( - compute_fakes.extension_name, - compute_fakes.extension_alias, - compute_fakes.extension_description, + self.compute_extension.name, + self.compute_extension.alias, + self.compute_extension.description, ), ( - volume_fakes.extension_name, - volume_fakes.extension_alias, - volume_fakes.extension_description, + self.volume_extension.name, + self.volume_extension.alias, + self.volume_extension.description, ), ( - network_fakes.extension_name, - network_fakes.extension_alias, - network_fakes.extension_description, + self.network_extension.name, + self.network_extension.alias, + self.network_extension.description, ), ) self._test_extension_list_helper(arglist, verifylist, datalist) @@ -150,36 +144,36 @@ def test_extension_list_long(self): ] datalist = ( ( - identity_fakes.extension_name, - identity_fakes.extension_namespace, - identity_fakes.extension_description, - identity_fakes.extension_alias, - identity_fakes.extension_updated, - identity_fakes.extension_links, + self.identity_extension.name, + self.identity_extension.namespace, + self.identity_extension.description, + self.identity_extension.alias, + self.identity_extension.updated, + self.identity_extension.links, ), ( - compute_fakes.extension_name, - compute_fakes.extension_namespace, - compute_fakes.extension_description, - compute_fakes.extension_alias, - compute_fakes.extension_updated, - compute_fakes.extension_links, + self.compute_extension.name, + self.compute_extension.namespace, + self.compute_extension.description, + self.compute_extension.alias, + self.compute_extension.updated, + self.compute_extension.links, ), ( - volume_fakes.extension_name, - volume_fakes.extension_namespace, - volume_fakes.extension_description, - volume_fakes.extension_alias, - volume_fakes.extension_updated, - volume_fakes.extension_links, + self.volume_extension.name, + self.volume_extension.namespace, + self.volume_extension.description, + self.volume_extension.alias, + self.volume_extension.updated, + self.volume_extension.links, ), ( - network_fakes.extension_name, - network_fakes.extension_namespace, - network_fakes.extension_description, - network_fakes.extension_alias, - network_fakes.extension_updated, - network_fakes.extension_links, + self.network_extension.name, + self.network_extension.namespace, + self.network_extension.description, + self.network_extension.alias, + self.network_extension.updated, + self.network_extension.links, ), ) self._test_extension_list_helper(arglist, verifylist, datalist, True) @@ -196,9 +190,9 @@ def test_extension_list_identity(self): ('identity', True), ] datalist = (( - identity_fakes.extension_name, - identity_fakes.extension_alias, - identity_fakes.extension_description, + self.identity_extension.name, + self.identity_extension.alias, + self.identity_extension.description, ), ) self._test_extension_list_helper(arglist, verifylist, datalist) self.identity_extensions_mock.list.assert_called_with() @@ -212,9 +206,9 @@ def test_extension_list_network(self): ] datalist = ( ( - network_fakes.extension_name, - network_fakes.extension_alias, - network_fakes.extension_description, + self.network_extension.name, + self.network_extension.alias, + self.network_extension.description, ), ) self._test_extension_list_helper(arglist, verifylist, datalist) @@ -228,9 +222,9 @@ def test_extension_list_compute(self): ('compute', True), ] datalist = (( - compute_fakes.extension_name, - compute_fakes.extension_alias, - compute_fakes.extension_description, + self.compute_extension.name, + self.compute_extension.alias, + self.compute_extension.description, ), ) self._test_extension_list_helper(arglist, verifylist, datalist) self.compute_extensions_mock.show_all.assert_called_with() @@ -243,9 +237,9 @@ def test_extension_list_volume(self): ('volume', True), ] datalist = (( - volume_fakes.extension_name, - volume_fakes.extension_alias, - volume_fakes.extension_description, + self.volume_extension.name, + self.volume_extension.alias, + self.volume_extension.description, ), ) self._test_extension_list_helper(arglist, verifylist, datalist) self.volume_extensions_mock.show_all.assert_called_with() diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index c9e2025dc0..9682eec470 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -24,26 +24,6 @@ from openstackclient.tests import utils from openstackclient.tests.volume.v2 import fakes as volume_fakes - -extension_name = 'Multinic' -extension_namespace = 'http://docs.openstack.org/compute/ext/'\ - 'multinic/api/v1.1' -extension_description = 'Multiple network support' -extension_updated = '2014-01-07T12:00:0-00:00' -extension_alias = 'NMN' -extension_links = '[{"href":'\ - '"https://github.com/openstack/compute-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, -} - floating_ip_num = 100 fix_ip_num = 100 injected_file_num = 100 @@ -259,6 +239,42 @@ def create_agents(attrs=None, count=2): return agents +class FakeExtension(object): + """Fake one or more extension.""" + + @staticmethod + def create_one_extension(attrs=None): + """Create a fake extension. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object with name, namespace, etc. + """ + attrs = attrs or {} + + # Set default attributes. + extension_info = { + 'name': 'name-' + uuid.uuid4().hex, + 'namespace': ( + 'http://docs.openstack.org/compute/ext/multinic/api/v1.1'), + 'description': 'description-' + uuid.uuid4().hex, + 'updated': '2014-01-07T12:00:0-00:00', + 'alias': 'NMN', + 'links': ('[{"href":' + '"https://github.com/openstack/compute-api", "type":' + ' "text/html", "rel": "describedby"}]') + } + + # Overwrite default attributes. + extension_info.update(attrs) + + extension = fakes.FakeResource( + info=copy.deepcopy(extension_info), + loaded=True) + return extension + + class FakeHypervisor(object): """Fake one or more hypervisor.""" diff --git a/openstackclient/tests/identity/v2_0/fakes.py b/openstackclient/tests/identity/v2_0/fakes.py index b37bd9da2f..b80938729a 100644 --- a/openstackclient/tests/identity/v2_0/fakes.py +++ b/openstackclient/tests/identity/v2_0/fakes.py @@ -13,7 +13,9 @@ # under the License. # +import copy import mock +import uuid from openstackclient.tests import fakes from openstackclient.tests import utils @@ -106,26 +108,6 @@ 'service_id': endpoint_service_id, } -extension_name = 'OpenStack Keystone User CRUD' -extension_namespace = 'http://docs.openstack.org/identity/'\ - 'api/ext/OS-KSCRUD/v1.0' -extension_description = 'OpenStack extensions to Keystone v2.0 API'\ - ' enabling User Operations.' -extension_updated = '2013-07-07T12:00:0-00:00' -extension_alias = 'OS-KSCRUD' -extension_links = '[{"href":'\ - '"https://github.com/openstack/identity-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, -} - class FakeIdentityv2Client(object): @@ -166,3 +148,39 @@ def setUp(self): endpoint=fakes.AUTH_URL, token=fakes.AUTH_TOKEN, ) + + +class FakeExtension(object): + """Fake one or more extension.""" + + @staticmethod + def create_one_extension(attrs=None): + """Create a fake extension. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object with name, namespace, etc. + """ + attrs = attrs or {} + + # Set default attributes. + extension_info = { + 'name': 'name-' + uuid.uuid4().hex, + 'namespace': ('http://docs.openstack.org/identity/' + 'api/ext/OS-KSCRUD/v1.0'), + 'description': 'description-' + uuid.uuid4().hex, + 'updated': '2013-07-07T12:00:0-00:00', + 'alias': 'OS-KSCRUD', + 'links': ('[{"href":' + '"https://github.com/openstack/identity-api", "type":' + ' "text/html", "rel": "describedby"}]') + } + + # Overwrite default attributes. + extension_info.update(attrs) + + extension = fakes.FakeResource( + info=copy.deepcopy(extension_info), + loaded=True) + return extension diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index ccbe395b7c..9efbe8c608 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -19,12 +19,6 @@ from openstackclient.tests import fakes from openstackclient.tests import utils -extension_name = 'Matrix' -extension_namespace = 'http://docs.openstack.org/network/' -extension_description = 'Simulated reality' -extension_updated = '2013-07-09T12:00:0-00:00' -extension_alias = 'Dystopian' -extension_links = '[{"href":''"https://github.com/os/network", "type"}]' QUOTA = { "subnet": 10, @@ -42,21 +36,11 @@ } -def create_extension(): - extension = mock.Mock() - extension.name = extension_name - extension.namespace = extension_namespace - extension.description = extension_description - extension.updated = extension_updated - extension.alias = extension_alias - extension.links = extension_links - return extension - - class FakeNetworkV2Client(object): def __init__(self, **kwargs): - self.extensions = mock.Mock(return_value=[create_extension()]) + self.extensions = mock.Mock() + self.extensions.resource_class = fakes.FakeResource(None, {}) class TestNetworkV2(utils.TestCommand): @@ -240,6 +224,39 @@ def create_ip_availability(count=2): return network_ip_availabilities +class FakeExtension(object): + """Fake one or more extension.""" + + @staticmethod + def create_one_extension(attrs=None): + """Create a fake extension. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object with name, namespace, etc. + """ + attrs = attrs or {} + + # Set default attributes. + extension_info = { + 'name': 'name-' + uuid.uuid4().hex, + 'namespace': 'http://docs.openstack.org/network/', + 'description': 'description-' + uuid.uuid4().hex, + 'updated': '2013-07-09T12:00:0-00:00', + 'alias': 'Dystopian', + 'links': '[{"href":''"https://github.com/os/network", "type"}]', + } + + # Overwrite default attributes. + extension_info.update(attrs) + + extension = fakes.FakeResource( + info=copy.deepcopy(extension_info), + loaded=True) + return extension + + class FakeNetwork(object): """Fake one or more networks.""" diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py index e61fe8aaca..3571890a41 100644 --- a/openstackclient/tests/volume/v2/fakes.py +++ b/openstackclient/tests/volume/v2/fakes.py @@ -211,26 +211,6 @@ 'name': image_name } -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 = '2013-04-18T00:00:00+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, -} - class FakeTransferClient(object): @@ -379,6 +359,8 @@ class FakeVolumeClient(object): def __init__(self, **kwargs): self.volumes = mock.Mock() self.volumes.resource_class = fakes.FakeResource(None, {}) + self.extensions = mock.Mock() + self.extensions.resource_class = fakes.FakeResource(None, {}) self.volume_snapshots = mock.Mock() self.volume_snapshots.resource_class = fakes.FakeResource(None, {}) self.backups = mock.Mock() @@ -643,6 +625,42 @@ def create_backups(attrs=None, count=2): return backups +class FakeExtension(object): + """Fake one or more extension.""" + + @staticmethod + def create_one_extension(attrs=None): + """Create a fake extension. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object with name, namespace, etc. + """ + attrs = attrs or {} + + # Set default attributes. + extension_info = { + 'name': 'name-' + uuid.uuid4().hex, + 'namespace': ('http://docs.openstack.org/' + 'block-service/ext/scheduler-hints/api/v2'), + 'description': 'description-' + uuid.uuid4().hex, + 'updated': '2013-04-18T00:00:00+00:00', + 'alias': 'OS-SCH-HNT', + 'links': ('[{"href":' + '"https://github.com/openstack/block-api", "type":' + ' "text/html", "rel": "describedby"}]'), + } + + # Overwrite default attributes. + extension_info.update(attrs) + + extension = fakes.FakeResource( + info=copy.deepcopy(extension_info), + loaded=True) + return extension + + class FakeQos(object): """Fake one or more Qos specification.""" From a7cc5c8006aa282d91cae3a8133e715bde251d57 Mon Sep 17 00:00:00 2001 From: "zhang.xiuhua" Date: Thu, 2 Jun 2016 16:03:33 +0800 Subject: [PATCH 0960/3095] Add Tox prerequisites and installation Change-Id: I48f4c571d1f1ae26ac930d8abad0cfbcb826ae46 --- doc/source/developing.rst | 62 +++++++++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 19 deletions(-) diff --git a/doc/source/developing.rst b/doc/source/developing.rst index 2ddaeb2cba..399e4a5a43 100644 --- a/doc/source/developing.rst +++ b/doc/source/developing.rst @@ -15,17 +15,40 @@ please refer to the `OpenStack IRC meetings`_ page. Testing ------- -Using ``tox`` -============= +Tox prerequisites and installation +=================================== -Before running tests, you should have ``tox`` installed and available in your -environment: +Install the prerequisites for Tox: + +* On Ubuntu or Debian: + + .. code-block:: bash + + $ apt-get install gcc gettext python-dev libxml2-dev libxslt1-dev \ + zlib1g-dev + + You may need to use pip install for some packages. + + +* On RHEL or CentOS including Fefora: + + .. code-block:: bash + + $ yum install gcc python-devel libxml2-devel libxslt-devel + +* On openSUSE or SUSE linux Enterprise: + + .. code-block:: bash + + $ zypper install gcc python-devel libxml2-devel libxslt-devel + +Install python-tox: .. code-block:: bash $ pip install tox -To execute the full suite of tests maintained within OpenStackClient, run: +To run the full suite of tests maintained within OpenStackClient. .. code-block:: bash @@ -37,9 +60,9 @@ To execute the full suite of tests maintained within OpenStackClient, run: virtualenvs. You can later use the ``-r`` option with ``tox`` to rebuild your virtualenv in a similar manner. -To run tests for one or more specific test environments (for example, the most -common configuration of Python 2.7 and PEP-8), list the environments with the -``-e`` option, separated by spaces: + +To run tests for one or more specific test environments(for example, the most common configuration of +Python 2.7 and PEP-8), list the environments with the ``-e`` option, separated by spaces: .. code-block:: bash @@ -53,7 +76,7 @@ Running functional tests OpenStackClient also maintains a set of functional tests that are optimally designed to be run against OpenStack's gate. Optionally, a developer may choose to run these tests against any OpenStack deployment, however depending -on the services available, results will vary. +on the services available, results vary. To run the entire suite of functional tests: @@ -70,33 +93,34 @@ To run a specific functional test: Running with PDB ================ -Using PDB breakpoints with ``tox`` and ``testr`` normally doesn't work since +Using PDB breakpoints with ``tox`` and ``testr`` normally does not work since the tests fail with a `BdbQuit` exception rather than stopping at the breakpoint. To run with PDB breakpoints during testing, use the `debug` ``tox`` environment -rather than ``py27``. Here's an example, passing the name of a test since -you'll normally only want to run the test that hits your breakpoint: +rather than ``py27``. For example, passing a test name since you will normally +only want to run the test that hits your breakpoint: .. code-block:: bash $ tox -e debug opentackclient.tests.identity.v3.test_group -For reference, the `debug` ``tox`` environment implements the instructions -here: https://wiki.openstack.org/wiki/Testr#Debugging_.28pdb.29_Tests +For reference, the `debug`_ ``tox`` environment implements the instructions + +.. _`debug`: https://wiki.openstack.org/wiki/Testr#Debugging_.28pdb.29_Tests Building the Documentation -------------------------- The documentation is generated with Sphinx using the ``tox`` command. To -create HTML docs, run the following: +create HTML docs, run the commands: .. code-block:: bash $ tox -e docs -The resultant HTML will be the ``doc/build/html`` directory. +The resultant HTML will be in the ``doc/build/html`` directory. Release Notes ------------- @@ -115,7 +139,7 @@ If any of the following applies to the patch, a release note is required: * Current behavior is changed * A security bug is fixed -Reno is used to generate release notes. Please read the docs for details. In summary, use +Reno is used to generate release notes. Use the commands: .. code-block:: bash @@ -123,7 +147,7 @@ Reno is used to generate release notes. Please read the docs for details. In sum Then edit the sample file that was created and push it with your change. -To see the results: +To run the commands and see results: .. code-block:: bash @@ -131,7 +155,7 @@ To see the results: $ tox -e releasenotes -Then look at the generated release notes files in releasenotes/build/html in your favorite browser. +At last, look at the generated release notes files in ``releasenotes/build/html`` in your browser. Testing new code ---------------- From b36d521ff6d4f7a994d4472c25f8267b6e268d41 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Tue, 7 Jun 2016 19:16:24 +0800 Subject: [PATCH 0961/3095] Fix i18n supports in commom I checked all the files in openstackclient/common and fixed the missing i18n supprots. Change-Id: Id7f76a24aae663f5832ef9bcf1bd5a6b7081af24 Partial-bug: #1574965 --- openstackclient/common/availability_zone.py | 9 ++-- openstackclient/common/configuration.py | 5 +- openstackclient/common/extension.py | 16 +++++-- openstackclient/common/limits.py | 18 ++++--- openstackclient/common/module.py | 3 +- openstackclient/common/parseractions.py | 14 +++--- openstackclient/common/quota.py | 15 +++--- openstackclient/common/utils.py | 53 +++++++++++++-------- 8 files changed, 83 insertions(+), 50 deletions(-) diff --git a/openstackclient/common/availability_zone.py b/openstackclient/common/availability_zone.py index 3b0270ad7f..df30313741 100644 --- a/openstackclient/common/availability_zone.py +++ b/openstackclient/common/availability_zone.py @@ -92,17 +92,20 @@ def get_parser(self, prog_name): '--compute', action='store_true', default=False, - help='List compute availability zones') + help=_('List compute availability zones'), + ) parser.add_argument( '--network', action='store_true', default=False, - help='List network availability zones') + help=_('List network availability zones'), + ) parser.add_argument( '--volume', action='store_true', default=False, - help='List volume availability zones') + help=_('List volume availability zones'), + ) parser.add_argument( '--long', action='store_true', diff --git a/openstackclient/common/configuration.py b/openstackclient/common/configuration.py index a70e4d1436..ba4de12d81 100644 --- a/openstackclient/common/configuration.py +++ b/openstackclient/common/configuration.py @@ -16,6 +16,7 @@ import six from openstackclient.common import command +from openstackclient.i18n import _ REDACTED = "" @@ -31,13 +32,13 @@ def get_parser(self, prog_name): dest="mask", action="store_true", default=True, - help="Attempt to mask passwords (default)", + help=_("Attempt to mask passwords (default)"), ) mask_group.add_argument( "--unmask", dest="mask", action="store_false", - help="Show password in clear text", + help=_("Show password in clear text"), ) return parser diff --git a/openstackclient/common/extension.py b/openstackclient/common/extension.py index 8b556b4cb2..ab46e7d8b0 100644 --- a/openstackclient/common/extension.py +++ b/openstackclient/common/extension.py @@ -19,6 +19,7 @@ from openstackclient.common import command from openstackclient.common import utils +from openstackclient.i18n import _ class ListExtension(command.Lister): @@ -30,27 +31,32 @@ def get_parser(self, prog_name): '--compute', action='store_true', default=False, - help='List extensions for the Compute API') + help=_('List extensions for the Compute API'), + ) parser.add_argument( '--identity', action='store_true', default=False, - help='List extensions for the Identity API') + help=_('List extensions for the Identity API'), + ) parser.add_argument( '--network', action='store_true', default=False, - help='List extensions for the Network API') + help=_('List extensions for the Network API'), + ) parser.add_argument( '--volume', action='store_true', default=False, - help='List extensions for the Block Storage API') + help=_('List extensions for the Block Storage API'), + ) parser.add_argument( '--long', action='store_true', default=False, - help='List additional fields in output') + help=_('List additional fields in output'), + ) return parser def take_action(self, parsed_args): diff --git a/openstackclient/common/limits.py b/openstackclient/common/limits.py index 1f87abf3de..939b9efb3e 100644 --- a/openstackclient/common/limits.py +++ b/openstackclient/common/limits.py @@ -19,6 +19,7 @@ from openstackclient.common import command from openstackclient.common import utils +from openstackclient.i18n import _ from openstackclient.identity import common as identity_common @@ -33,30 +34,33 @@ def get_parser(self, prog_name): dest="is_absolute", action="store_true", default=False, - help="Show absolute limits") + help=_("Show absolute limits"), + ) type_group.add_argument( "--rate", dest="is_rate", action="store_true", default=False, - help="Show rate limits") + help=_("Show rate limits"), + ) parser.add_argument( "--reserved", dest="is_reserved", action="store_true", default=False, - help="Include reservations count [only valid with --absolute]") + help=_("Include reservations count [only valid with --absolute]"), + ) parser.add_argument( '--project', metavar='', - help='Show limits for a specific project (name or ID)' - ' [only valid with --absolute]', + help=_('Show limits for a specific project (name or ID)' + ' [only valid with --absolute]'), ) parser.add_argument( '--domain', metavar='', - help='Domain the project belongs to (name or ID)' - ' [only valid with --absolute]', + help=_('Domain the project belongs to (name or ID)' + ' [only valid with --absolute]'), ) return parser diff --git a/openstackclient/common/module.py b/openstackclient/common/module.py index 30c67c683f..11895f7aca 100644 --- a/openstackclient/common/module.py +++ b/openstackclient/common/module.py @@ -20,6 +20,7 @@ from openstackclient.common import command from openstackclient.common import utils +from openstackclient.i18n import _ class ListCommand(command.Lister): @@ -61,7 +62,7 @@ def get_parser(self, prog_name): '--all', action='store_true', default=False, - help='Show all modules that have version information', + help=_('Show all modules that have version information'), ) return parser diff --git a/openstackclient/common/parseractions.py b/openstackclient/common/parseractions.py index 77798f9024..c535b4f352 100644 --- a/openstackclient/common/parseractions.py +++ b/openstackclient/common/parseractions.py @@ -87,8 +87,8 @@ def __call__(self, parser, namespace, values, metavar=None): if '=' in kv: params.update([kv.split('=', 1)]) else: - msg = ("Expected key=value pairs separated by comma, " - "but got: %s" % (str(kv))) + msg = _("Expected key=value pairs separated by comma, " + "but got: %s") % (str(kv)) raise argparse.ArgumentTypeError(msg) # Check key validation @@ -139,12 +139,13 @@ def __call__(self, parser, namespace, values, option_string=None): if int(range[0]) <= int(range[1]): setattr(namespace, self.dest, (int(range[0]), int(range[1]))) else: - msg = "Invalid range, %s is not less than %s" % \ - (range[0], range[1]) + msg = (_("Invalid range, %(range0)s is not " + "less than %(range1)s") + % {'range0': range[0], 'range1': range[1]}) raise argparse.ArgumentError(self, msg) else: # Too many values - msg = "Invalid range, too many values" + msg = _("Invalid range, too many values") raise argparse.ArgumentError(self, msg) @@ -158,5 +159,6 @@ def __call__(self, parser, namespace, values, option_string=None): if int(values) >= 0: setattr(namespace, self.dest, values) else: - msg = "%s expected a non-negative integer" % (str(option_string)) + msg = (_("%s expected a non-negative integer") + % (str(option_string))) raise argparse.ArgumentTypeError(msg) diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index f85d550b78..67e442b324 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -21,6 +21,7 @@ from openstackclient.common import command from openstackclient.common import utils +from openstackclient.i18n import _ # List the quota items, map the internal argument name to the option @@ -84,14 +85,14 @@ def get_parser(self, prog_name): parser.add_argument( 'project', metavar='', - help='Set quotas for this project or class (name/ID)', + help=_('Set quotas for this project or class (name/ID)'), ) parser.add_argument( '--class', dest='quota_class', action='store_true', default=False, - help='Set quotas for ', + help=_('Set quotas for '), ) for k, v in self._build_options_list(): parser.add_argument( @@ -99,12 +100,12 @@ def get_parser(self, prog_name): metavar='<%s>' % v, dest=k, type=int, - help='New value for the %s quota' % v, + help=_('New value for the %s quota') % v, ) parser.add_argument( '--volume-type', metavar='', - help='Set quotas for a specific ', + help=_('Set quotas for a specific '), ) return parser @@ -187,7 +188,7 @@ def get_parser(self, prog_name): 'project', metavar='', nargs='?', - help='Show quotas for this project or class (name or ID)', + help=_('Show quotas for this project or class (name or ID)'), ) type_group = parser.add_mutually_exclusive_group() type_group.add_argument( @@ -195,14 +196,14 @@ def get_parser(self, prog_name): dest='quota_class', action='store_true', default=False, - help='Show quotas for ', + help=_('Show quotas for '), ) type_group.add_argument( '--default', dest='default', action='store_true', default=False, - help='Show default quotas for ' + help=_('Show default quotas for ') ) return parser diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index daa65c25fc..5e058547b4 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -24,6 +24,7 @@ from oslo_utils import importutils from openstackclient.common import exceptions +from openstackclient.i18n import _ def find_resource(manager, name_or_id, **kwargs): @@ -90,13 +91,19 @@ def find_resource(manager, name_or_id, **kwargs): # of client exceptions. except Exception as ex: if type(ex).__name__ == 'NotFound': - msg = "No %s with a name or ID of '%s' exists." % \ - (manager.resource_class.__name__.lower(), name_or_id) - raise exceptions.CommandError(msg) + msg = _("No %(resource)s with a name or ID " + "of '%(name_or_id)s' exists.") + raise exceptions.CommandError( + msg % {'resource': manager.resource_class.__name__.lower(), + 'name_or_id': name_or_id} + ) if type(ex).__name__ == 'NoUniqueMatch': - msg = "More than one %s exists with the name '%s'." % \ - (manager.resource_class.__name__.lower(), name_or_id) - raise exceptions.CommandError(msg) + msg = _("More than one %(resource)s exists with " + "the name '%(name_or_id)s'.") + raise exceptions.CommandError( + msg % {'resource': manager.resource_class.__name__.lower(), + 'name_or_id': name_or_id} + ) else: pass @@ -107,7 +114,7 @@ def find_resource(manager, name_or_id, **kwargs): return resource else: # we found no match, report back this error: - msg = "Could not find resource %s" % name_or_id + msg = _("Could not find resource %s") % name_or_id raise exceptions.CommandError(msg) @@ -152,7 +159,7 @@ def get_field(item, field): else: return getattr(item, field) except Exception: - msg = "Resource doesn't have field %s" % field + msg = _("Resource doesn't have field %s") % field raise exceptions.CommandError(msg) @@ -234,14 +241,17 @@ def sort_items(items, sort_str): if ':' in sort_key: sort_key, direction = sort_key.split(':', 1) if not sort_key: - msg = "empty string is not a valid sort key" + msg = _("empty string is not a valid sort key") raise exceptions.CommandError(msg) if direction not in ['asc', 'desc']: if not direction: direction = "empty string" - msg = ("%s is not a valid sort direction for sort key %s, " - "use asc or desc instead" % (direction, sort_key)) - raise exceptions.CommandError(msg) + msg = _("%(direction)s is not a valid sort direction for " + "sort key %(sort_key)s, use asc or desc instead") + raise exceptions.CommandError( + msg % {'direction': direction, + 'sort_key': sort_key} + ) if direction == 'desc': reverse = True items.sort(key=lambda item: get_field(item, sort_key), @@ -273,9 +283,13 @@ def get_client_class(api_name, version, version_map): try: client_path = version_map[str(version)] except (KeyError, ValueError): - msg = "Invalid %s client version '%s'. must be one of: %s" % ( - (api_name, version, ', '.join(list(version_map.keys())))) - raise exceptions.UnsupportedVersion(msg) + msg = _("Invalid %(api_name)s client version '%(version)s'. " + "must be one of: %(version_map)s") + raise exceptions.UnsupportedVersion( + msg % {'api_name': api_name, + 'version': version, + 'version_map': ', '.join(list(version_map.keys()))} + ) return importutils.import_class(client_path) @@ -391,9 +405,10 @@ def get_password(stdin, prompt=None, confirm=True): return first_pass print("The passwords entered were not the same") except EOFError: # Ctl-D - raise exceptions.CommandError("Error reading password.") - raise exceptions.CommandError("There was a request to be prompted for a" - " password and a terminal was not detected.") + raise exceptions.CommandError(_("Error reading password.")) + raise exceptions.CommandError(_("There was a request to be prompted " + "for a password and a terminal was " + "not detected.")) def read_blob_file_contents(blob_file): @@ -402,7 +417,7 @@ def read_blob_file_contents(blob_file): blob = file.read().strip() return blob except IOError: - msg = "Error occurred trying to read from file %s" + msg = _("Error occurred trying to read from file %s") raise exceptions.CommandError(msg % blob_file) From d24c255e4302c37b3a4fa4051b263b3d5e0b9b92 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 3 Jun 2016 15:01:12 -0500 Subject: [PATCH 0962/3095] Release note cleanups for 2.6.0 Change-Id: I7c19cac77f1236c8241d18f2bdde83917e50fae9 --- .../notes/add-server-backup-e63feaebb6140f83.yaml | 3 +-- .../address-scope-delete-multi-31c3af73feb31265.yaml | 5 +++++ .../notes/bp-routed-networks-3eea4978c93aa126.yaml | 3 +-- releasenotes/notes/bug-1554886-8e5249a655e7e7b6.yaml | 5 +++++ releasenotes/notes/bug-1575461-4d7d90e792132064.yaml | 10 +++++++--- releasenotes/notes/bug-1575461-54a23327081cbec7.yaml | 6 ------ releasenotes/notes/bug-1582968-4d44912a033b242c.yaml | 4 ++-- releasenotes/notes/compute-agent-deff48988e81b30e.yaml | 4 +++- .../notes/ip-availability-ca1cf440f6c70afc.yaml | 4 ++-- .../list_volume_transfer_request-8e5249a655e7e7b6.yaml | 9 --------- 10 files changed, 26 insertions(+), 27 deletions(-) create mode 100644 releasenotes/notes/address-scope-delete-multi-31c3af73feb31265.yaml create mode 100644 releasenotes/notes/bug-1554886-8e5249a655e7e7b6.yaml delete mode 100644 releasenotes/notes/bug-1575461-54a23327081cbec7.yaml delete mode 100644 releasenotes/notes/list_volume_transfer_request-8e5249a655e7e7b6.yaml diff --git a/releasenotes/notes/add-server-backup-e63feaebb6140f83.yaml b/releasenotes/notes/add-server-backup-e63feaebb6140f83.yaml index f8aa4291fb..670f506543 100644 --- a/releasenotes/notes/add-server-backup-e63feaebb6140f83.yaml +++ b/releasenotes/notes/add-server-backup-e63feaebb6140f83.yaml @@ -1,4 +1,3 @@ --- features: - - | - Add support for the ``server backup create`` command + - Add ``server backup create`` command diff --git a/releasenotes/notes/address-scope-delete-multi-31c3af73feb31265.yaml b/releasenotes/notes/address-scope-delete-multi-31c3af73feb31265.yaml new file mode 100644 index 0000000000..91bce23d36 --- /dev/null +++ b/releasenotes/notes/address-scope-delete-multi-31c3af73feb31265.yaml @@ -0,0 +1,5 @@ +--- +upgrade: + - | + ``address scope delete`` command now accepts multiple address scopes + in a single command diff --git a/releasenotes/notes/bp-routed-networks-3eea4978c93aa126.yaml b/releasenotes/notes/bp-routed-networks-3eea4978c93aa126.yaml index 30661e6d20..91579586c0 100644 --- a/releasenotes/notes/bp-routed-networks-3eea4978c93aa126.yaml +++ b/releasenotes/notes/bp-routed-networks-3eea4978c93aa126.yaml @@ -1,7 +1,6 @@ --- features: - - Add support for the ``network segment`` command object via the - ``network segment list`` and ``network segment show`` commands. + - Add ``network segment list`` and ``network segment show`` commands. These are beta commands and subject to change. Use global option ``--os-beta-command`` to enable these commands. [Blueprint `routed-networks `_] diff --git a/releasenotes/notes/bug-1554886-8e5249a655e7e7b6.yaml b/releasenotes/notes/bug-1554886-8e5249a655e7e7b6.yaml new file mode 100644 index 0000000000..bafb87d784 --- /dev/null +++ b/releasenotes/notes/bug-1554886-8e5249a655e7e7b6.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``volume transfer request list`` command + [:lpbug:`1554886`] diff --git a/releasenotes/notes/bug-1575461-4d7d90e792132064.yaml b/releasenotes/notes/bug-1575461-4d7d90e792132064.yaml index 6bc9f73a67..3ae0075ba2 100644 --- a/releasenotes/notes/bug-1575461-4d7d90e792132064.yaml +++ b/releasenotes/notes/bug-1575461-4d7d90e792132064.yaml @@ -1,6 +1,10 @@ --- features: - | - Added support for setting flavor access to project by using below command - ``flavor set --project `` - [Bug `1575461 `_] + Add ``--project`` option to ``flavor set`` command to set project access + to a flavor + [:lpbug:`1575461`] + - | + Add ``--project`` option to ``flavor unset`` command to remove project access + to a flavor + [:lpbug:`1575461`] diff --git a/releasenotes/notes/bug-1575461-54a23327081cbec7.yaml b/releasenotes/notes/bug-1575461-54a23327081cbec7.yaml deleted file mode 100644 index a498bfbdac..0000000000 --- a/releasenotes/notes/bug-1575461-54a23327081cbec7.yaml +++ /dev/null @@ -1,6 +0,0 @@ ---- -features: - - | - Added support for removing flavor access from project by using below command - ``flavor unset --project --project-domain `` - [Bug `1575461 `_] diff --git a/releasenotes/notes/bug-1582968-4d44912a033b242c.yaml b/releasenotes/notes/bug-1582968-4d44912a033b242c.yaml index 9d9794e31a..9fd50c1b2e 100644 --- a/releasenotes/notes/bug-1582968-4d44912a033b242c.yaml +++ b/releasenotes/notes/bug-1582968-4d44912a033b242c.yaml @@ -1,4 +1,4 @@ --- features: - - Add "image unset" command. - [Bug '1582968 '] + - Add ``image unset`` command + [:lpbug:`1582968`] diff --git a/releasenotes/notes/compute-agent-deff48988e81b30e.yaml b/releasenotes/notes/compute-agent-deff48988e81b30e.yaml index 2f3c3f2e7e..219ef9693d 100644 --- a/releasenotes/notes/compute-agent-deff48988e81b30e.yaml +++ b/releasenotes/notes/compute-agent-deff48988e81b30e.yaml @@ -1,3 +1,5 @@ --- upgrade: - - Command ``compute agent delete`` now supports deleting multiple agents. + - | + ``compute agent delete`` command now supports deleting multiple agents + in a single command diff --git a/releasenotes/notes/ip-availability-ca1cf440f6c70afc.yaml b/releasenotes/notes/ip-availability-ca1cf440f6c70afc.yaml index 81b217c092..2c783880db 100644 --- a/releasenotes/notes/ip-availability-ca1cf440f6c70afc.yaml +++ b/releasenotes/notes/ip-availability-ca1cf440f6c70afc.yaml @@ -1,5 +1,5 @@ --- features: - | - Add support for the ``ip availability list`` and ``ip availability show`` commands. - [Blueprint `neutron-ip-capacity `_] + Add ``ip availability list`` and ``ip availability show`` commands + [Blueprint :oscbp:`neutron-ip-capacity`] diff --git a/releasenotes/notes/list_volume_transfer_request-8e5249a655e7e7b6.yaml b/releasenotes/notes/list_volume_transfer_request-8e5249a655e7e7b6.yaml deleted file mode 100644 index 3f7fba5831..0000000000 --- a/releasenotes/notes/list_volume_transfer_request-8e5249a655e7e7b6.yaml +++ /dev/null @@ -1,9 +0,0 @@ ---- -features: - - | - Adds support for volume transfer request. - - An user can list available volume transfer requests using - ``volume transfer request list`` - - [Bug `1554886 `_] \ No newline at end of file From b8730114b9a2f5519a0405a44c80f50367f2b5c0 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Sat, 4 Jun 2016 11:09:52 -0500 Subject: [PATCH 0963/3095] Set up 3.x release notes Give the 2.x series its own release notes page, put the 3.x notes into the 'current' series page. This removes the OpenStack development cycle name from the release notes. We follow the intermediary release model, however the association with the common cycle release is something that we do not need to reflect in the doc structure. We want users to use the current version even with older clouds, not to think they need to use a 'Mitaka' OSC with a Mitaka cloud. Change-Id: Idbb6844824d50e5cd0fa64871b5aa625d4d237af --- releasenotes/source/20_releases.rst | 6 ++++++ releasenotes/source/index.rst | 25 ++++++++++++++++++++++++- releasenotes/source/mitaka.rst | 6 ------ releasenotes/source/unreleased.rst | 1 + 4 files changed, 31 insertions(+), 7 deletions(-) create mode 100644 releasenotes/source/20_releases.rst delete mode 100644 releasenotes/source/mitaka.rst diff --git a/releasenotes/source/20_releases.rst b/releasenotes/source/20_releases.rst new file mode 100644 index 0000000000..ca2508fe22 --- /dev/null +++ b/releasenotes/source/20_releases.rst @@ -0,0 +1,6 @@ +======================== +2.0 Series Release Notes +======================== + +.. release-notes:: + :version: 2.6.0,2.5.0,2.4.0,2.2.0,2.1.0,2.0.0 diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index e0477df08b..4004e3574d 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,5 +6,28 @@ OpenStackClient Release Notes :maxdepth: 1 unreleased - mitaka + 20_releases pre_20_releases + +OpenStack Releases +------------------ + +OpenStackClient is compatible with all currently supported OpenStack releases, +it does not require maintaining a 'Mitaka' version to match to a Mitala-release +cloud. The OpenStackClient release that was current when the corresponding +OpenStack release was made is shown below: + +================= ======================= +OpenStack Release OpenStackClient Release +================= ======================= +Mitaka 2.3.0 +Liberty 1.7.3 +Kilo 1.0.6 +Juno 0.4.1 +Icehouse 0.3.1 +================= ======================= + +Further details for historical OpenStack releases are found at the +`OpenStack Releases`_ page. + +.. _`OpenStack Releases`: http://releases.openstack.org/ diff --git a/releasenotes/source/mitaka.rst b/releasenotes/source/mitaka.rst deleted file mode 100644 index 05ed3e48eb..0000000000 --- a/releasenotes/source/mitaka.rst +++ /dev/null @@ -1,6 +0,0 @@ -=========================== -Mitaka Series Release Notes -=========================== - -.. release-notes:: - :branch: origin/stable/mitaka diff --git a/releasenotes/source/unreleased.rst b/releasenotes/source/unreleased.rst index cb3cccf2c6..62d0c014d5 100644 --- a/releasenotes/source/unreleased.rst +++ b/releasenotes/source/unreleased.rst @@ -3,3 +3,4 @@ Current Release Notes ===================== .. release-notes:: + :earliest-version: 3.0 From cf122397733b8795530577b7824aeae305719658 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Tue, 7 Jun 2016 16:41:21 +0800 Subject: [PATCH 0964/3095] Error handling for KeyValueAction class. The set --property command requires that the input match the "key=value" type, but if the type don't match, the return value will be None, and the command still can be implemented successfully, this may confuse the users. I think we should raise exception if the argument type don't match "key=value". So I make some changes in KeyValueAction class in this patch. Change-Id: I14e64922faa7e083bc8b5e7e1cac41ef8117c224 Closes-Bug: #1589935 --- openstackclient/common/parseractions.py | 4 +++- .../tests/common/test_parseractions.py | 17 +++++++---------- .../notes/bug-1589935-8a56e6a18d836db9.yaml | 5 +++++ 3 files changed, 15 insertions(+), 11 deletions(-) create mode 100644 releasenotes/notes/bug-1589935-8a56e6a18d836db9.yaml diff --git a/openstackclient/common/parseractions.py b/openstackclient/common/parseractions.py index 77798f9024..8f772344e5 100644 --- a/openstackclient/common/parseractions.py +++ b/openstackclient/common/parseractions.py @@ -35,7 +35,9 @@ def __call__(self, parser, namespace, values, option_string=None): if '=' in values: getattr(namespace, self.dest, {}).update([values.split('=', 1)]) else: - getattr(namespace, self.dest, {}).pop(values, None) + msg = _("Expected 'key=value' type, " + "but got: %s") % (str(values)) + raise argparse.ArgumentTypeError(msg) class MultiKeyValueAction(argparse.Action): diff --git a/openstackclient/tests/common/test_parseractions.py b/openstackclient/tests/common/test_parseractions.py index 5c5ca3d321..894b224c46 100644 --- a/openstackclient/tests/common/test_parseractions.py +++ b/openstackclient/tests/common/test_parseractions.py @@ -49,16 +49,13 @@ def test_good_values(self): self.assertDictEqual(expect, actual) def test_error_values(self): - results = self.parser.parse_args([ - '--property', 'red', - '--property', 'green=100%', - '--property', 'blue', - ]) - - actual = getattr(results, 'property', {}) - # There should be no red or blue - expect = {'green': '100%', 'format': '#rgb'} - self.assertDictEqual(expect, actual) + self.assertRaises( + argparse.ArgumentTypeError, + self.parser.parse_args, + [ + '--property', 'red', + ] + ) class TestMultiKeyValueAction(utils.TestCase): diff --git a/releasenotes/notes/bug-1589935-8a56e6a18d836db9.yaml b/releasenotes/notes/bug-1589935-8a56e6a18d836db9.yaml new file mode 100644 index 0000000000..53c1ea0f0b --- /dev/null +++ b/releasenotes/notes/bug-1589935-8a56e6a18d836db9.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - Raise ``ArgumentTypeError`` if the input arguments do not match the type + ``key=value`` when we set properties. + [Bug `1589935 `_] From 3c883e97559c7c477e8c85fbbdb9e628874f1fa0 Mon Sep 17 00:00:00 2001 From: sunyajing Date: Wed, 8 Jun 2016 11:03:44 +0800 Subject: [PATCH 0965/3095] Modify unit tests of compute agent delete add no-input test to ``compute agent delete`` unit tests. Change-Id: Iee22b75c9a9431e57cb634dc28a5efa9b43b7369 --- openstackclient/tests/compute/v2/test_agent.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/openstackclient/tests/compute/v2/test_agent.py b/openstackclient/tests/compute/v2/test_agent.py index d3d1ff29e0..9726e0bef1 100644 --- a/openstackclient/tests/compute/v2/test_agent.py +++ b/openstackclient/tests/compute/v2/test_agent.py @@ -19,6 +19,7 @@ from openstackclient.common import exceptions from openstackclient.compute.v2 import agent from openstackclient.tests.compute.v2 import fakes as compute_fakes +from openstackclient.tests import utils as tests_utils class TestAgent(compute_fakes.TestComputev2): @@ -160,6 +161,15 @@ def test_delete_multiple_agents_exception(self): ] self.agents_mock.delete.assert_has_calls(calls) + def test_agent_delete_no_input(self): + arglist = [] + verifylist = None + self.assertRaises(tests_utils.ParserException, + self.check_parser, + self.cmd, + arglist, + verifylist) + class TestAgentList(TestAgent): From 565499577f158c5242e4759c55b1dc187d9c9c4b Mon Sep 17 00:00:00 2001 From: sunyajing Date: Wed, 8 Jun 2016 09:57:41 +0800 Subject: [PATCH 0966/3095] Fix compute service set command add ``Binary name`` to help msg of compute service set command. Change-Id: I5ed3824ec5bede250ce41f187835e8901b5e11fd --- doc/source/command-objects/compute-service.rst | 2 +- openstackclient/compute/v2/service.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/command-objects/compute-service.rst b/doc/source/command-objects/compute-service.rst index bda64c811d..99b09b13bf 100644 --- a/doc/source/command-objects/compute-service.rst +++ b/doc/source/command-objects/compute-service.rst @@ -80,5 +80,5 @@ Set service command .. describe:: - Name of service + Name of service (Binary name) diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index 6093b8ce17..38d2870f51 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.py @@ -105,7 +105,7 @@ def get_parser(self, prog_name): parser.add_argument( "service", metavar="", - help=_("Name of service") + help=_("Name of service (Binary name)") ) enabled_group = parser.add_mutually_exclusive_group() enabled_group.add_argument( From 72d3ebd94c48cf0412332521bd94002c39d902ae Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Wed, 8 Jun 2016 13:36:16 +0800 Subject: [PATCH 0967/3095] Clean up fakes.py in volumev2 All of volumev2 fake classes are completed. Now the unit tests of volumev2 are using fake classes so that the old codes are useless and can be removed. Change-Id: I502a8a0b0404975744d3b208af3e95c8692e6c47 --- openstackclient/tests/volume/v2/fakes.py | 188 ----------------------- 1 file changed, 188 deletions(-) diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py index 3571890a41..d3ca009eea 100644 --- a/openstackclient/tests/volume/v2/fakes.py +++ b/openstackclient/tests/volume/v2/fakes.py @@ -23,194 +23,6 @@ from openstackclient.tests.image.v2 import fakes as image_fakes from openstackclient.tests import utils -volume_attachment_server = { - 'device': '/dev/ice', - 'server_id': '1233', -} - -volume_id = "ce26708d-a7f8-4b4b-9861-4a80256615a6" -volume_name = "fake_volume" -volume_description = "fake description" -volume_status = "available" -volume_size = 20 -volume_type = "fake_lvmdriver-1" -volume_metadata = { - 'Alpha': 'a', - 'Beta': 'b', - 'Gamma': 'g', -} -volume_metadata_str = "Alpha='a', Beta='b', Gamma='g'" -volume_snapshot_id = 1 -volume_availability_zone = "nova" -volume_attachments = [volume_attachment_server] - -VOLUME = { - "id": volume_id, - "name": volume_name, - "description": volume_description, - "status": volume_status, - "size": volume_size, - "volume_type": volume_type, - "metadata": volume_metadata, - "snapshot_id": volume_snapshot_id, - "availability_zone": volume_availability_zone, - "attachments": volume_attachments -} - -VOLUME_columns = ( - "attachments", - "availability_zone", - "description", - "id", - "name", - "properties", - "size", - "snapshot_id", - "status", - "type" -) - -VOLUME_data = ( - volume_attachments, - volume_availability_zone, - volume_description, - volume_id, - volume_name, - common_utils.format_dict(volume_metadata), - volume_size, - volume_snapshot_id, - volume_status, - volume_type -) - - -snapshot_id = "cb2d364e-4d1c-451a-8c68-b5bbcb340fb2" -snapshot_name = "fake_snapshot" -snapshot_description = "fake description" -snapshot_size = 10 -snapshot_metadata = { - "foo": "bar" -} -snapshot_volume_id = "bdbae8dc-e6ca-43c0-8076-951cc1b093a4" - -SNAPSHOT = { - "id": snapshot_id, - "name": snapshot_name, - "description": snapshot_description, - "size": snapshot_size, - "status": "available", - "metadata": snapshot_metadata, - "created_at": "2015-06-03T18:49:19.000000", - "volume_id": volume_name -} -EXPECTED_SNAPSHOT = copy.deepcopy(SNAPSHOT) -EXPECTED_SNAPSHOT.pop("metadata") -EXPECTED_SNAPSHOT['properties'] = "foo='bar'" -SNAPSHOT_columns = tuple(sorted(EXPECTED_SNAPSHOT)) -SNAPSHOT_data = tuple((EXPECTED_SNAPSHOT[x] - for x in sorted(EXPECTED_SNAPSHOT))) - - -type_id = "5520dc9e-6f9b-4378-a719-729911c0f407" -type_description = "fake description" -type_name = "fake-lvmdriver-1" -type_extra_specs = { - "foo": "bar" -} - -TYPE = { - 'id': type_id, - 'name': type_name, - 'description': type_description, - 'extra_specs': type_extra_specs -} - -TYPE_columns = tuple(sorted(TYPE)) -TYPE_data = tuple((TYPE[x] for x in sorted(TYPE))) - -formatted_type_properties = "foo='bar'" -TYPE_FORMATTED = { - 'id': type_id, - 'name': type_name, - 'description': type_description, - 'properties': formatted_type_properties -} -TYPE_FORMATTED_columns = tuple(sorted(TYPE_FORMATTED)) -TYPE_FORMATTED_data = tuple((TYPE_FORMATTED[x] for x in - sorted(TYPE_FORMATTED))) - -backup_id = "3c409fe6-4d03-4a06-aeab-18bdcdf3c8f4" -backup_volume_id = "bdbae8dc-e6ca-43c0-8076-951cc1b093a4" -backup_name = "fake_backup" -backup_description = "fake description" -backup_object_count = None -backup_container = None -backup_size = 10 -backup_status = "error" - -BACKUP = { - "id": backup_id, - "name": backup_name, - "volume_id": backup_volume_id, - "description": backup_description, - "object_count": backup_object_count, - "container": backup_container, - "size": backup_size, - "status": backup_status, - "availability_zone": volume_availability_zone, -} - -BACKUP_columns = tuple(sorted(BACKUP)) -BACKUP_data = tuple((BACKUP[x] for x in sorted(BACKUP))) - -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] -} - -image_id = 'im1' -image_name = 'graven' -IMAGE = { - 'id': image_id, - 'name': image_name -} - class FakeTransferClient(object): From e3270cdfd8fce895b8f32b8e23e48399be6ac85c Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Fri, 3 Jun 2016 12:21:45 +0800 Subject: [PATCH 0968/3095] Make set/unset commands in network return normally when nothing specified set/unset commands should ends up normally instead of raising an exception when nothing is specified to modify. The main reason is: When nothing is specified, the command sets/unsets nothing, which is a normal behavior, and ends up normally. No API call fails. No error happens. This patch also adds a releasenote for both network, and volume commands that fix patch has been merged. Change-Id: I78c348066078decd350417a431f3b8bea8fcf9ef Partial-bug: #1588588 --- openstackclient/network/v2/address_scope.py | 3 --- openstackclient/network/v2/network.py | 5 ----- openstackclient/network/v2/port.py | 3 --- openstackclient/network/v2/router.py | 5 ----- openstackclient/network/v2/subnet.py | 3 --- openstackclient/network/v2/subnet_pool.py | 4 ---- .../tests/network/v2/test_address_scope.py | 8 ++++++-- .../tests/network/v2/test_network.py | 8 ++++++-- openstackclient/tests/network/v2/test_port.py | 15 +++++++++++++++ openstackclient/tests/network/v2/test_router.py | 17 ++++++++++++----- openstackclient/tests/network/v2/test_subnet.py | 8 +++++--- .../tests/network/v2/test_subnet_pool.py | 9 ++++++--- .../notes/bug-1588588-39927ef06ca35730.yaml | 6 ++++++ 13 files changed, 56 insertions(+), 38 deletions(-) create mode 100644 releasenotes/notes/bug-1588588-39927ef06ca35730.yaml diff --git a/openstackclient/network/v2/address_scope.py b/openstackclient/network/v2/address_scope.py index dbc6865fb4..2e6cc30b77 100644 --- a/openstackclient/network/v2/address_scope.py +++ b/openstackclient/network/v2/address_scope.py @@ -200,9 +200,6 @@ def take_action(self, parsed_args): attrs['shared'] = True if parsed_args.no_share: attrs['shared'] = False - if attrs == {}: - msg = _("Nothing specified to be set.") - raise exceptions.CommandError(msg) client.update_address_scope(obj, **attrs) diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index bf01e2ec71..87e65dad11 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -14,7 +14,6 @@ """Network action implementations""" from openstackclient.common import command -from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common @@ -434,10 +433,6 @@ def take_action(self, parsed_args): obj = client.find_network(parsed_args.network, ignore_missing=False) attrs = _get_attrs(self.app.client_manager, parsed_args) - if attrs == {}: - msg = _("Nothing specified to be set") - raise exceptions.CommandError(msg) - client.update_network(obj, **attrs) diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 7ef3964bf9..b117897a0f 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -426,9 +426,6 @@ def take_action(self, parsed_args): elif parsed_args.no_fixed_ip: attrs['fixed_ips'] = [] - if attrs == {}: - msg = _("Nothing specified to be set") - raise exceptions.CommandError(msg) client.update_port(obj, **attrs) diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index a2f0df1d8b..2548714960 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -18,7 +18,6 @@ import logging from openstackclient.common import command -from openstackclient.common import exceptions from openstackclient.common import parseractions from openstackclient.common import utils from openstackclient.i18n import _ @@ -426,10 +425,6 @@ def take_action(self, parsed_args): route['nexthop'] = route.pop('gateway') attrs['routes'] = obj.routes + parsed_args.routes - if attrs == {}: - msg = _("Nothing specified to be set") - raise exceptions.CommandError(msg) - client.update_router(obj, **attrs) diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index e7e1be99c1..8e378c7e19 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -373,9 +373,6 @@ def take_action(self, parsed_args): obj = client.find_subnet(parsed_args.subnet, ignore_missing=False) attrs = _get_attrs(self.app.client_manager, parsed_args, is_create=False) - if not attrs: - msg = "Nothing specified to be set" - raise exceptions.CommandError(msg) if 'dns_nameservers' in attrs: attrs['dns_nameservers'] += obj.dns_nameservers if 'host_routes' in attrs: diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index a1a94426dd..17f1e97ddc 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -14,7 +14,6 @@ """Subnet pool action implementations""" from openstackclient.common import command -from openstackclient.common import exceptions from openstackclient.common import parseractions from openstackclient.common import utils from openstackclient.i18n import _ @@ -286,9 +285,6 @@ def take_action(self, parsed_args): ignore_missing=False) attrs = _get_attrs(self.app.client_manager, parsed_args) - if attrs == {}: - msg = _("Nothing specified to be set") - raise exceptions.CommandError(msg) # Existing prefixes must be a subset of the new prefixes. if 'prefixes' in attrs: diff --git a/openstackclient/tests/network/v2/test_address_scope.py b/openstackclient/tests/network/v2/test_address_scope.py index b4f4fa885f..d02f01ca24 100644 --- a/openstackclient/tests/network/v2/test_address_scope.py +++ b/openstackclient/tests/network/v2/test_address_scope.py @@ -313,8 +313,12 @@ def test_set_nothing(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.assertRaises(exceptions.CommandError, self.cmd.take_action, - parsed_args) + result = self.cmd.take_action(parsed_args) + + attrs = {} + self.network.update_address_scope.assert_called_with( + self._address_scope, **attrs) + self.assertIsNone(result) def test_set_name_and_share(self): arglist = [ diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index ba810f1649..509259a8ed 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -609,8 +609,12 @@ def test_set_nothing(self): verifylist = [('network', self._network.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.assertRaises(exceptions.CommandError, self.cmd.take_action, - parsed_args) + result = self.cmd.take_action(parsed_args) + + attrs = {} + self.network.update_network.assert_called_once_with( + self._network, **attrs) + self.assertIsNone(result) class TestShowNetwork(TestNetwork): diff --git a/openstackclient/tests/network/v2/test_port.py b/openstackclient/tests/network/v2/test_port.py index dd98a6371e..628a5d52cb 100644 --- a/openstackclient/tests/network/v2/test_port.py +++ b/openstackclient/tests/network/v2/test_port.py @@ -426,6 +426,21 @@ def test_set_that(self): self.network.update_port.assert_called_once_with(self._port, **attrs) self.assertIsNone(result) + def test_set_nothing(self): + arglist = [ + self._port.name, + ] + verifylist = [ + ('port', self._port.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = {} + self.network.update_port.assert_called_once_with(self._port, **attrs) + self.assertIsNone(result) + class TestShowPort(TestPort): diff --git a/openstackclient/tests/network/v2/test_router.py b/openstackclient/tests/network/v2/test_router.py index 99b41d2dc4..b25381ef3e 100644 --- a/openstackclient/tests/network/v2/test_router.py +++ b/openstackclient/tests/network/v2/test_router.py @@ -13,7 +13,6 @@ import mock -from openstackclient.common import exceptions from openstackclient.common import utils as osc_utils from openstackclient.network.v2 import router from openstackclient.tests.network.v2 import fakes as network_fakes @@ -568,12 +567,20 @@ def test_set_route_clear_routes(self): self.cmd, arglist, verifylist) def test_set_nothing(self): - arglist = [self._router.name, ] - verifylist = [('router', self._router.name), ] + arglist = [ + self._router.name, + ] + verifylist = [ + ('router', self._router.name), + ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.assertRaises(exceptions.CommandError, self.cmd.take_action, - parsed_args) + result = self.cmd.take_action(parsed_args) + + attrs = {} + self.network.update_router.assert_called_once_with( + self._router, **attrs) + self.assertIsNone(result) class TestShowRouter(TestRouter): diff --git a/openstackclient/tests/network/v2/test_subnet.py b/openstackclient/tests/network/v2/test_subnet.py index 22c288f9f1..25684d63e0 100644 --- a/openstackclient/tests/network/v2/test_subnet.py +++ b/openstackclient/tests/network/v2/test_subnet.py @@ -14,7 +14,6 @@ import copy import mock -from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.network.v2 import subnet as subnet_v2 from openstackclient.tests import fakes @@ -549,8 +548,11 @@ def test_set_nothing(self): verifylist = [('subnet', self._subnet.name)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.assertRaises(exceptions.CommandError, self.cmd.take_action, - parsed_args) + result = self.cmd.take_action(parsed_args) + + attrs = {} + self.network.update_subnet.assert_called_with(self._subnet, **attrs) + self.assertIsNone(result) def test_append_options(self): _testsubnet = network_fakes.FakeSubnet.create_one_subnet( diff --git a/openstackclient/tests/network/v2/test_subnet_pool.py b/openstackclient/tests/network/v2/test_subnet_pool.py index de12c9e9df..f7bb5895c3 100644 --- a/openstackclient/tests/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/network/v2/test_subnet_pool.py @@ -15,7 +15,6 @@ import copy import mock -from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.network.v2 import subnet_pool from openstackclient.tests import fakes @@ -443,10 +442,14 @@ def test_set_that(self): def test_set_nothing(self): arglist = [self._subnet_pool.name, ] verifylist = [('subnet_pool', self._subnet_pool.name), ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) - self.assertRaises(exceptions.CommandError, self.cmd.take_action, - parsed_args) + attrs = {} + self.network.update_subnet_pool.assert_called_once_with( + self._subnet_pool, **attrs) + self.assertIsNone(result) def test_set_len_negative(self): arglist = [ diff --git a/releasenotes/notes/bug-1588588-39927ef06ca35730.yaml b/releasenotes/notes/bug-1588588-39927ef06ca35730.yaml new file mode 100644 index 0000000000..440bb7d31c --- /dev/null +++ b/releasenotes/notes/bug-1588588-39927ef06ca35730.yaml @@ -0,0 +1,6 @@ +--- +upgrade: + - All ``set`` and ``unset`` commands in network and volume now return + normally when nothing specified to modify. This will become the default + behavior of OSC ``set`` and ``unset`` commands. + [Bug `1588588 `_] From a4670054c649841511226be0caed31a1c5f24478 Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Wed, 8 Jun 2016 16:28:59 +0800 Subject: [PATCH 0969/3095] Remove duplicate file logger formatter setting Change-Id: Ia7851a4266fa61658c48cc894163c6afb7252750 --- openstackclient/common/logs.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openstackclient/common/logs.py b/openstackclient/common/logs.py index 221c59973a..8671555b60 100644 --- a/openstackclient/common/logs.py +++ b/openstackclient/common/logs.py @@ -173,8 +173,6 @@ def configure(self, cloud_config): if log_file: if not self.file_logger: self.file_logger = logging.FileHandler(filename=log_file) - formatter = _FileFormatter(cloud_config=cloud_config) - self.file_logger.setFormatter(formatter) self.file_logger.setFormatter(_FileFormatter(config=cloud_config)) self.file_logger.setLevel(log_level) self.root_logger.addHandler(self.file_logger) From aefd2195b156e10884b580e1e3630380ea02c952 Mon Sep 17 00:00:00 2001 From: sunyajing Date: Wed, 8 Jun 2016 17:08:36 +0800 Subject: [PATCH 0970/3095] Fix network modify ``columns, data`` to ``(columns, data)`` in network, to keep code consistence. Change-Id: I0522c499d1651b4ea948bebccbdfd520934e8e6a --- openstackclient/network/v2/address_scope.py | 4 ++-- openstackclient/network/v2/ip_availability.py | 2 +- openstackclient/network/v2/port.py | 4 ++-- openstackclient/network/v2/router.py | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/openstackclient/network/v2/address_scope.py b/openstackclient/network/v2/address_scope.py index dbc6865fb4..ffd987ce5f 100644 --- a/openstackclient/network/v2/address_scope.py +++ b/openstackclient/network/v2/address_scope.py @@ -94,7 +94,7 @@ def take_action(self, parsed_args): columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters={}) - return columns, data + return (columns, data) class DeleteAddressScope(command.Command): @@ -227,4 +227,4 @@ def take_action(self, parsed_args): columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters={}) - return columns, data + return (columns, data) diff --git a/openstackclient/network/v2/ip_availability.py b/openstackclient/network/v2/ip_availability.py index cc240338f3..ff7b07c7c7 100644 --- a/openstackclient/network/v2/ip_availability.py +++ b/openstackclient/network/v2/ip_availability.py @@ -106,4 +106,4 @@ def take_action(self, parsed_args): ignore_missing=False) columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) - return columns, data + return (columns, data) diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 7ef3964bf9..1a69d16c73 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -266,7 +266,7 @@ def take_action(self, parsed_args): columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) - return columns, data + return (columns, data) class DeletePort(command.Command): @@ -449,4 +449,4 @@ def take_action(self, parsed_args): obj = client.find_port(parsed_args.port, ignore_missing=False) columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) - return columns, data + return (columns, data) diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index a2f0df1d8b..310f67406f 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -204,7 +204,7 @@ def take_action(self, parsed_args): columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) - return columns, data + return (columns, data) class DeleteRouter(command.Command): @@ -450,4 +450,4 @@ def take_action(self, parsed_args): obj = client.find_router(parsed_args.router, ignore_missing=False) columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) - return columns, data + return (columns, data) From db0849e7119cfe0bea0609dbdcd2a76610ab0ad9 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 8 Jun 2016 22:04:01 +0000 Subject: [PATCH 0971/3095] Updated from global requirements Change-Id: I944891584a7479c8d8bdf545af8a6af0460f42d5 --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index a8d6aa47e4..ef309af956 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -26,7 +26,7 @@ python-designateclient>=1.5.0 # Apache-2.0 python-heatclient>=1.1.0 # Apache-2.0 python-ironicclient>=1.1.0 # Apache-2.0 python-ironic-inspector-client>=1.5.0 # Apache-2.0 -python-mistralclient>=1.0.0 # Apache-2.0 +python-mistralclient>=2.0.0 # Apache-2.0 python-muranoclient>=0.8.2 # Apache-2.0 python-saharaclient>=0.13.0 # Apache-2.0 python-searchlightclient>=0.2.0 #Apache-2.0 From 6ae0d2e8a54fd5139e63a990ab4bdce634e73c5e Mon Sep 17 00:00:00 2001 From: Navid Pustchi Date: Thu, 4 Feb 2016 16:45:38 +0000 Subject: [PATCH 0972/3095] Moving authentication from keystoneclient to keystoneauth Currently OpenStackClient uses keystoneclient for authentication. This change will update OpenStackClient to use keystoneauth for authentication. All dependant test have been updated. Updating how auth_ref is set in the tests to use KSA fixtures had some racy side-effects. The user_role_list tests failed when they picked up an auth_ref that was a fixture. This exposed a weakness in ListUserRole that needed to be fixed at the same time re handling of unscoped tokens and options. Change-Id: I4ddb2dbbb3bf2ab37494468eaf65cef9213a6e00 Closes-Bug: 1533369 --- openstackclient/api/auth.py | 29 ++++----- openstackclient/api/auth_plugin.py | 19 ++---- openstackclient/common/clientmanager.py | 2 +- openstackclient/identity/v2_0/catalog.py | 29 +++++---- openstackclient/identity/v2_0/role.py | 21 +++--- openstackclient/identity/v2_0/token.py | 19 ++++-- openstackclient/identity/v3/catalog.py | 29 +++++---- openstackclient/identity/v3/token.py | 20 ++++-- .../tests/common/test_clientmanager.py | 21 +++--- openstackclient/tests/fakes.py | 15 +++++ openstackclient/tests/identity/v2_0/fakes.py | 40 ++++++++++++ .../tests/identity/v2_0/test_catalog.py | 29 +++++++-- .../tests/identity/v2_0/test_role.py | 64 ++++++++++++++----- .../tests/identity/v2_0/test_token.py | 35 ++++++---- openstackclient/tests/identity/v3/fakes.py | 34 ++++++++++ .../tests/identity/v3/test_catalog.py | 19 +++++- .../tests/identity/v3/test_token.py | 49 ++++++++------ setup.cfg | 2 +- 18 files changed, 330 insertions(+), 146 deletions(-) diff --git a/openstackclient/api/auth.py b/openstackclient/api/auth.py index c74e8005de..ded0e3699f 100644 --- a/openstackclient/api/auth.py +++ b/openstackclient/api/auth.py @@ -16,15 +16,12 @@ import argparse import logging -import stevedore - -from keystoneclient.auth import base +from keystoneauth1.loading import base from openstackclient.common import exceptions as exc from openstackclient.common import utils from openstackclient.i18n import _ - LOG = logging.getLogger(__name__) # Initialize the list of Authentication plugins early in order @@ -37,15 +34,10 @@ def get_plugin_list(): """Gather plugin list and cache it""" - global PLUGIN_LIST if PLUGIN_LIST is None: - PLUGIN_LIST = stevedore.ExtensionManager( - base.PLUGIN_NAMESPACE, - invoke_on_load=False, - propagate_map_exceptions=True, - ) + PLUGIN_LIST = base.get_available_plugin_names() return PLUGIN_LIST @@ -55,8 +47,9 @@ def get_options_list(): global OPTIONS_LIST if not OPTIONS_LIST: - for plugin in get_plugin_list(): - for o in plugin.plugin.get_options(): + for plugin_name in get_plugin_list(): + plugin_options = base.get_plugin_options(plugin_name) + for o in plugin_options: os_name = o.dest.lower().replace('_', '-') os_env_name = 'OS_' + os_name.upper().replace('-', '_') OPTIONS_LIST.setdefault( @@ -66,7 +59,7 @@ def get_options_list(): # help texts if they vary from one auth plugin to another # also the text rendering is ugly in the CLI ... OPTIONS_LIST[os_name]['help'] += 'With %s: %s\n' % ( - plugin.name, + plugin_name, o.help, ) return OPTIONS_LIST @@ -83,7 +76,7 @@ def select_auth_plugin(options): if options.auth.get('url') and options.auth.get('token'): # service token authentication auth_plugin_name = 'token_endpoint' - elif options.auth_type in [plugin.name for plugin in PLUGIN_LIST]: + elif options.auth_type in PLUGIN_LIST: # A direct plugin name was given, use it auth_plugin_name = options.auth_type elif options.auth.get('username'): @@ -115,7 +108,7 @@ def build_auth_params(auth_plugin_name, cmd_options): auth_params = dict(cmd_options.auth) if auth_plugin_name: LOG.debug('auth_type: %s', auth_plugin_name) - auth_plugin_class = base.get_plugin_class(auth_plugin_name) + auth_plugin_loader = base.get_plugin_loader(auth_plugin_name) # grab tenant from project for v2.0 API compatibility if auth_plugin_name.startswith("v2"): if 'project_id' in auth_params: @@ -127,12 +120,12 @@ def build_auth_params(auth_plugin_name, cmd_options): else: LOG.debug('no auth_type') # delay the plugin choice, grab every option - auth_plugin_class = None + auth_plugin_loader = None plugin_options = set([o.replace('-', '_') for o in get_options_list()]) for option in plugin_options: LOG.debug('fetching option %s', option) auth_params[option] = getattr(cmd_options.auth, option, None) - return (auth_plugin_class, auth_params) + return (auth_plugin_loader, auth_params) def check_valid_auth_options(options, auth_plugin_name, required_scope=True): @@ -188,7 +181,7 @@ def build_auth_plugins_option_parser(parser): authentication plugin. """ - available_plugins = [plugin.name for plugin in get_plugin_list()] + available_plugins = list(get_plugin_list()) parser.add_argument( '--os-auth-type', metavar='', diff --git a/openstackclient/api/auth_plugin.py b/openstackclient/api/auth_plugin.py index cff0b75dc9..44d3b38ea4 100644 --- a/openstackclient/api/auth_plugin.py +++ b/openstackclient/api/auth_plugin.py @@ -18,13 +18,13 @@ from oslo_config import cfg from six.moves.urllib import parse as urlparse -from keystoneclient.auth.identity.generic import password as ksc_password -from keystoneclient.auth import token_endpoint +from keystoneauth1.loading._plugins import admin_token as token_endpoint +from keystoneauth1.loading._plugins.identity import generic as ksa_password LOG = logging.getLogger(__name__) -class TokenEndpoint(token_endpoint.Token): +class TokenEndpoint(token_endpoint.AdminToken): """Auth plugin to handle traditional token/endpoint usage Implements the methods required to handle token authentication @@ -36,20 +36,15 @@ class TokenEndpoint(token_endpoint.Token): is for bootstrapping the Keystone database. """ - def __init__(self, url, token, **kwargs): + def load_from_options(self, url, token): """A plugin for static authentication with an existing token :param string url: Service endpoint :param string token: Existing token """ - super(TokenEndpoint, self).__init__(endpoint=url, - token=token) + return super(TokenEndpoint, self).load_from_options(endpoint=url, + token=token) - def get_auth_ref(self, session, **kwargs): - # Stub this method for compatibility - return None - - @classmethod def get_options(self): options = super(TokenEndpoint, self).get_options() @@ -65,7 +60,7 @@ def get_options(self): return options -class OSCGenericPassword(ksc_password.Password): +class OSCGenericPassword(ksa_password.Password): """Auth plugin hack to work around broken Keystone configurations The default Keystone configuration uses http://localhost:xxxx in diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index 9c2b320c1d..e8ba7ec446 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -269,7 +269,7 @@ def get_endpoint_for_service_type(self, service_type, region_name=None, endpoint = self.auth_ref.service_catalog.url_for( service_type=service_type, region_name=region_name, - endpoint_type=interface, + interface=interface, ) else: # Get the passed endpoint directly from the auth plugin diff --git a/openstackclient/identity/v2_0/catalog.py b/openstackclient/identity/v2_0/catalog.py index c8f48cb63f..33692a0d6c 100644 --- a/openstackclient/identity/v2_0/catalog.py +++ b/openstackclient/identity/v2_0/catalog.py @@ -16,6 +16,7 @@ import six from openstackclient.common import command +from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.i18n import _ @@ -41,13 +42,14 @@ class ListCatalog(command.Lister): def take_action(self, parsed_args): - # This is ugly because if auth hasn't happened yet we need - # to trigger it here. - sc = self.app.client_manager.session.auth.get_auth_ref( - self.app.client_manager.session, - ).service_catalog + # Trigger auth if it has not happened yet + auth_ref = self.app.client_manager.auth_ref + if not auth_ref: + raise exceptions.AuthorizationFailure( + "Only an authorized user may issue a new token." + ) - data = sc.get_data() + data = auth_ref.service_catalog.catalog columns = ('Name', 'Type', 'Endpoints') return (columns, (utils.get_dict_properties( @@ -72,14 +74,15 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): - # This is ugly because if auth hasn't happened yet we need - # to trigger it here. - sc = self.app.client_manager.session.auth.get_auth_ref( - self.app.client_manager.session, - ).service_catalog + # Trigger auth if it has not happened yet + auth_ref = self.app.client_manager.auth_ref + if not auth_ref: + raise exceptions.AuthorizationFailure( + "Only an authorized user may issue a new token." + ) data = None - for service in sc.get_data(): + for service in auth_ref.service_catalog.catalog: if (service.get('name') == parsed_args.service or service.get('type') == parsed_args.service): data = service @@ -91,6 +94,6 @@ def take_action(self, parsed_args): if not data: self.app.log.error(_('service %s not found\n') % parsed_args.service) - return ([], []) + return ((), ()) return zip(*sorted(six.iteritems(data))) diff --git a/openstackclient/identity/v2_0/role.py b/openstackclient/identity/v2_0/role.py index 6b014d8651..0f8da99249 100644 --- a/openstackclient/identity/v2_0/role.py +++ b/openstackclient/identity/v2_0/role.py @@ -231,18 +231,19 @@ def take_action(self, parsed_args): # Project and user are required, if not included in command args # default to the values used for authentication. For token-flow # authentication they must be included on the command line. + if (not parsed_args.project and + self.app.client_manager.auth_ref.project_id): + parsed_args.project = auth_ref.project_id if not parsed_args.project: - if self.app.client_manager.auth_ref: - parsed_args.project = auth_ref.project_id - else: - msg = _("Project must be specified") - raise exceptions.CommandError(msg) + msg = _("Project must be specified") + raise exceptions.CommandError(msg) + + if (not parsed_args.user and + self.app.client_manager.auth_ref.user_id): + parsed_args.user = auth_ref.user_id if not parsed_args.user: - if self.app.client_manager.auth_ref: - parsed_args.user = auth_ref.user_id - else: - msg = _("User must be specified") - raise exceptions.CommandError(msg) + msg = _("User must be specified") + raise exceptions.CommandError(msg) project = utils.find_resource( identity_client.tenants, diff --git a/openstackclient/identity/v2_0/token.py b/openstackclient/identity/v2_0/token.py index f435d7ce98..d708749d30 100644 --- a/openstackclient/identity/v2_0/token.py +++ b/openstackclient/identity/v2_0/token.py @@ -18,6 +18,7 @@ import six from openstackclient.common import command +from openstackclient.common import exceptions from openstackclient.i18n import _ @@ -32,11 +33,21 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): + auth_ref = self.app.client_manager.auth_ref + if not auth_ref: + raise exceptions.AuthorizationFailure( + "Only an authorized user may issue a new token.") - token = self.app.client_manager.auth_ref.service_catalog.get_token() - if 'tenant_id' in token: - token['project_id'] = token.pop('tenant_id') - return zip(*sorted(six.iteritems(token))) + data = {} + if auth_ref.auth_token: + data['id'] = auth_ref.auth_token + if auth_ref.expires: + data['expires'] = auth_ref.expires + if auth_ref.project_id: + data['project_id'] = auth_ref.project_id + if auth_ref.user_id: + data['user_id'] = auth_ref.user_id + return zip(*sorted(six.iteritems(data))) class RevokeToken(command.Command): diff --git a/openstackclient/identity/v3/catalog.py b/openstackclient/identity/v3/catalog.py index 4c794692d4..c2b4359d28 100644 --- a/openstackclient/identity/v3/catalog.py +++ b/openstackclient/identity/v3/catalog.py @@ -16,6 +16,7 @@ import six from openstackclient.common import command +from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.i18n import _ @@ -36,13 +37,14 @@ class ListCatalog(command.Lister): def take_action(self, parsed_args): - # This is ugly because if auth hasn't happened yet we need - # to trigger it here. - sc = self.app.client_manager.session.auth.get_auth_ref( - self.app.client_manager.session, - ).service_catalog + # Trigger auth if it has not happened yet + auth_ref = self.app.client_manager.auth_ref + if not auth_ref: + raise exceptions.AuthorizationFailure( + "Only an authorized user may issue a new token." + ) - data = sc.get_data() + data = auth_ref.service_catalog.catalog columns = ('Name', 'Type', 'Endpoints') return (columns, (utils.get_dict_properties( @@ -67,14 +69,15 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): - # This is ugly because if auth hasn't happened yet we need - # to trigger it here. - sc = self.app.client_manager.session.auth.get_auth_ref( - self.app.client_manager.session, - ).service_catalog + # Trigger auth if it has not happened yet + auth_ref = self.app.client_manager.auth_ref + if not auth_ref: + raise exceptions.AuthorizationFailure( + "Only an authorized user may issue a new token." + ) data = None - for service in sc.get_data(): + for service in auth_ref.service_catalog.catalog: if (service.get('name') == parsed_args.service or service.get('type') == parsed_args.service): data = dict(service) @@ -86,6 +89,6 @@ def take_action(self, parsed_args): if not data: self.app.log.error(_('service %s not found\n') % parsed_args.service) - return ([], []) + return ((), ()) return zip(*sorted(six.iteritems(data))) diff --git a/openstackclient/identity/v3/token.py b/openstackclient/identity/v3/token.py index 56a7497cfc..cc3993631b 100644 --- a/openstackclient/identity/v3/token.py +++ b/openstackclient/identity/v3/token.py @@ -174,13 +174,23 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - if not self.app.client_manager.auth_ref: + auth_ref = self.app.client_manager.auth_ref + if not auth_ref: raise exceptions.AuthorizationFailure( _("Only an authorized user may issue a new token.")) - token = self.app.client_manager.auth_ref.service_catalog.get_token() - if 'tenant_id' in token: - token['project_id'] = token.pop('tenant_id') - return zip(*sorted(six.iteritems(token))) + + data = {} + if auth_ref.auth_token: + data['id'] = auth_ref.auth_token + if auth_ref.expires: + data['expires'] = auth_ref.expires + if auth_ref.project_id: + data['project_id'] = auth_ref.project_id + if auth_ref.user_id: + data['user_id'] = auth_ref.user_id + if auth_ref.domain_id: + data['domain_id'] = auth_ref.domain_id + return zip(*sorted(six.iteritems(data))) class RevokeToken(command.Command): diff --git a/openstackclient/tests/common/test_clientmanager.py b/openstackclient/tests/common/test_clientmanager.py index fa6c3fcc25..33485b00a9 100644 --- a/openstackclient/tests/common/test_clientmanager.py +++ b/openstackclient/tests/common/test_clientmanager.py @@ -17,11 +17,11 @@ import mock from requests_mock.contrib import fixture -from keystoneclient.auth.identity import v2 as auth_v2 -from keystoneclient import service_catalog +from keystoneauth1.access import service_catalog +from keystoneauth1.identity import v2 as auth_v2 +from keystoneauth1 import token_endpoint from openstackclient.api import auth -from openstackclient.api import auth_plugin from openstackclient.common import clientmanager from openstackclient.common import exceptions as exc from openstackclient.tests import fakes @@ -29,7 +29,6 @@ API_VERSION = {"identity": "2.0"} - AUTH_REF = {'version': 'v2.0'} AUTH_REF.update(fakes.TEST_RESPONSE_DICT['access']) SERVICE_CATALOG = service_catalog.ServiceCatalogV2(AUTH_REF) @@ -126,7 +125,7 @@ def test_client_manager_token_endpoint(self): ) self.assertIsInstance( client_manager.auth, - auth_plugin.TokenEndpoint, + token_endpoint.Token, ) self.assertFalse(client_manager._insecure) self.assertTrue(client_manager._verify) @@ -205,11 +204,14 @@ def test_client_manager_password(self): ) self.assertTrue(client_manager._insecure) self.assertFalse(client_manager._verify) - # These need to stick around until the old-style clients are gone self.assertEqual( - AUTH_REF, - client_manager.auth_ref, + AUTH_REF.pop('version'), + client_manager.auth_ref.version, + ) + self.assertEqual( + fakes.to_unicode_dict(AUTH_REF), + client_manager.auth_ref._data['access'], ) self.assertEqual( dir(SERVICE_CATALOG), @@ -296,9 +298,10 @@ def test_client_manager_password_client_cert_and_key(self): def _select_auth_plugin(self, auth_params, api_version, auth_plugin_name): auth_params['auth_type'] = auth_plugin_name auth_params['identity_api_version'] = api_version + client_manager = clientmanager.ClientManager( cli_options=FakeOptions(**auth_params), - api_version=API_VERSION, + api_version={"identity": api_version}, verify=True ) client_manager.setup_auth() diff --git a/openstackclient/tests/fakes.py b/openstackclient/tests/fakes.py index ad4705a4de..d0cab0191a 100644 --- a/openstackclient/tests/fakes.py +++ b/openstackclient/tests/fakes.py @@ -50,6 +50,21 @@ TEST_VERSIONS = fixture.DiscoveryList(href=AUTH_URL) +def to_unicode_dict(catalog_dict): + """Converts dict to unicode dict + + """ + if isinstance(catalog_dict, dict): + return {to_unicode_dict(key): to_unicode_dict(value) + for key, value in catalog_dict.items()} + elif isinstance(catalog_dict, list): + return [to_unicode_dict(element) for element in catalog_dict] + elif isinstance(catalog_dict, str): + return catalog_dict + u"" + else: + return catalog_dict + + class FakeStdout(object): def __init__(self): diff --git a/openstackclient/tests/identity/v2_0/fakes.py b/openstackclient/tests/identity/v2_0/fakes.py index b80938729a..c613ad82a3 100644 --- a/openstackclient/tests/identity/v2_0/fakes.py +++ b/openstackclient/tests/identity/v2_0/fakes.py @@ -17,6 +17,9 @@ import mock import uuid +from keystoneauth1 import access +from keystoneauth1 import fixture + from openstackclient.tests import fakes from openstackclient.tests import utils @@ -109,6 +112,43 @@ } +def fake_auth_ref(fake_token, fake_service=None): + """Create an auth_ref using keystoneauth's fixtures""" + token_copy = copy.deepcopy(fake_token) + token_copy['token_id'] = token_copy.pop('id') + token = fixture.V2Token(**token_copy) + # An auth_ref is actually an access info object + auth_ref = access.create(body=token) + + # Create a service catalog + if fake_service: + service = token.add_service( + fake_service['type'], + fake_service['name'], + ) + # TODO(dtroyer): Add an 'id' element to KSA's _Service fixure + service['id'] = fake_service['id'] + for e in fake_service['endpoints']: + # KSA's _Service fixture copies publicURL to internalURL and + # adminURL if they do not exist. Soooo helpful... + internal = e.get('internalURL', None) + admin = e.get('adminURL', None) + region = e.get('region_id') or e.get('region', '') + endpoint = service.add_endpoint( + public=e['publicURL'], + internal=internal, + admin=admin, + region=region, + ) + # ...so undo that helpfulness + if not internal: + endpoint['internalURL'] = None + if not admin: + endpoint['adminURL'] = None + + return auth_ref + + class FakeIdentityv2Client(object): def __init__(self, **kwargs): diff --git a/openstackclient/tests/identity/v2_0/test_catalog.py b/openstackclient/tests/identity/v2_0/test_catalog.py index d9ae6a80b6..2fdbafbea2 100644 --- a/openstackclient/tests/identity/v2_0/test_catalog.py +++ b/openstackclient/tests/identity/v2_0/test_catalog.py @@ -14,6 +14,7 @@ import mock from openstackclient.identity.v2_0 import catalog +from openstackclient.tests.identity.v2_0 import fakes as identity_fakes from openstackclient.tests import utils @@ -49,7 +50,7 @@ def setUp(self): super(TestCatalog, self).setUp() self.sc_mock = mock.MagicMock() - self.sc_mock.service_catalog.get_data.return_value = [ + self.sc_mock.service_catalog.catalog.return_value = [ self.fake_service, ] @@ -74,6 +75,13 @@ def setUp(self): self.cmd = catalog.ListCatalog(self.app, None) def test_catalog_list(self): + auth_ref = identity_fakes.fake_auth_ref( + identity_fakes.TOKEN, + fake_service=self.fake_service, + ) + self.ar_mock = mock.PropertyMock(return_value=auth_ref) + type(self.app.client_manager).auth_ref = self.ar_mock + arglist = [] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -82,7 +90,6 @@ def test_catalog_list(self): # returns a tuple containing the column names and an iterable # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.sc_mock.service_catalog.get_data.assert_called_with() self.assertEqual(self.columns, columns) datalist = (( @@ -117,9 +124,12 @@ def test_catalog_list_with_endpoint_url(self): }, ], } - self.sc_mock.service_catalog.get_data.return_value = [ - fake_service, - ] + auth_ref = identity_fakes.fake_auth_ref( + identity_fakes.TOKEN, + fake_service=fake_service, + ) + self.ar_mock = mock.PropertyMock(return_value=auth_ref) + type(self.app.client_manager).auth_ref = self.ar_mock arglist = [] verifylist = [] @@ -129,7 +139,6 @@ def test_catalog_list_with_endpoint_url(self): # returns a tuple containing the column names and an iterable # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.sc_mock.service_catalog.get_data.assert_called_with() self.assertEqual(self.columns, columns) datalist = (( @@ -151,6 +160,13 @@ def setUp(self): self.cmd = catalog.ShowCatalog(self.app, None) def test_catalog_show(self): + auth_ref = identity_fakes.fake_auth_ref( + identity_fakes.UNSCOPED_TOKEN, + fake_service=self.fake_service, + ) + self.ar_mock = mock.PropertyMock(return_value=auth_ref) + type(self.app.client_manager).auth_ref = self.ar_mock + arglist = [ 'compute', ] @@ -163,7 +179,6 @@ def test_catalog_show(self): # 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.sc_mock.service_catalog.get_data.assert_called_with() collist = ('endpoints', 'id', 'name', 'type') self.assertEqual(collist, columns) diff --git a/openstackclient/tests/identity/v2_0/test_role.py b/openstackclient/tests/identity/v2_0/test_role.py index 3c4b79a4a3..486a4a2ab4 100644 --- a/openstackclient/tests/identity/v2_0/test_role.py +++ b/openstackclient/tests/identity/v2_0/test_role.py @@ -26,6 +26,13 @@ class TestRole(identity_fakes.TestIdentityv2): + fake_service = copy.deepcopy(identity_fakes.SERVICE) + fake_service['endpoints'] = [ + { + 'publicURL': identity_fakes.ENDPOINT['publicurl'], + }, + ] + def setUp(self): super(TestRole, self).setUp() @@ -41,6 +48,13 @@ def setUp(self): self.roles_mock = self.app.client_manager.identity.roles self.roles_mock.reset_mock() + auth_ref = identity_fakes.fake_auth_ref( + identity_fakes.TOKEN, + fake_service=self.fake_service, + ) + self.ar_mock = mock.PropertyMock(return_value=auth_ref) + type(self.app.client_manager).auth_ref = self.ar_mock + class TestRoleAdd(TestRole): @@ -320,7 +334,14 @@ def setUp(self): # Get the command object to test self.cmd = role.ListUserRole(self.app, None) - def test_user_role_list_no_options(self): + def test_user_role_list_no_options_unscoped_token(self): + auth_ref = identity_fakes.fake_auth_ref( + identity_fakes.UNSCOPED_TOKEN, + fake_service=self.fake_service, + ) + self.ar_mock = mock.PropertyMock(return_value=auth_ref) + type(self.app.client_manager).auth_ref = self.ar_mock + arglist = [] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -332,11 +353,7 @@ def test_user_role_list_no_options(self): parsed_args, ) - def test_user_role_list_no_options_def_creds(self): - auth_ref = self.app.client_manager.auth_ref = mock.MagicMock() - auth_ref.project_id.return_value = identity_fakes.project_id - auth_ref.user_id.return_value = identity_fakes.user_id - + def test_user_role_list_no_options_scoped_token(self): arglist = [] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -361,7 +378,14 @@ def test_user_role_list_no_options_def_creds(self): ), ) self.assertEqual(datalist, tuple(data)) - def test_user_role_list_project(self): + def test_user_role_list_project_unscoped_token(self): + auth_ref = identity_fakes.fake_auth_ref( + identity_fakes.UNSCOPED_TOKEN, + fake_service=self.fake_service, + ) + self.ar_mock = mock.PropertyMock(return_value=auth_ref) + type(self.app.client_manager).auth_ref = self.ar_mock + self.projects_mock.get.return_value = fakes.FakeResource( None, copy.deepcopy(identity_fakes.PROJECT_2), @@ -375,18 +399,26 @@ def test_user_role_list_project(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # This argument combination should raise a CommandError - self.assertRaises( - exceptions.CommandError, - self.cmd.take_action, - parsed_args, + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + self.roles_mock.roles_for_user.assert_called_with( + identity_fakes.user_id, + identity_fakes.PROJECT_2['id'], ) - def test_user_role_list_project_def_creds(self): - auth_ref = self.app.client_manager.auth_ref = mock.MagicMock() - auth_ref.project_id.return_value = identity_fakes.project_id - auth_ref.user_id.return_value = identity_fakes.user_id + self.assertEqual(columns, columns) + datalist = (( + identity_fakes.role_id, + identity_fakes.role_name, + identity_fakes.PROJECT_2['name'], + identity_fakes.user_name, + ), ) + self.assertEqual(datalist, tuple(data)) + def test_user_role_list_project_scoped_token(self): self.projects_mock.get.return_value = fakes.FakeResource( None, copy.deepcopy(identity_fakes.PROJECT_2), diff --git a/openstackclient/tests/identity/v2_0/test_token.py b/openstackclient/tests/identity/v2_0/test_token.py index 613139dd3a..96f08e8708 100644 --- a/openstackclient/tests/identity/v2_0/test_token.py +++ b/openstackclient/tests/identity/v2_0/test_token.py @@ -24,10 +24,9 @@ class TestToken(identity_fakes.TestIdentityv2): def setUp(self): super(TestToken, self).setUp() - # Get a shortcut to the Service Catalog Mock - self.sc_mock = mock.Mock() - self.app.client_manager.auth_ref = mock.Mock() - self.app.client_manager.auth_ref.service_catalog = self.sc_mock + # Get a shortcut to the Auth Ref Mock + self.ar_mock = mock.PropertyMock() + type(self.app.client_manager).auth_ref = self.ar_mock class TestTokenIssue(TestToken): @@ -35,10 +34,15 @@ class TestTokenIssue(TestToken): def setUp(self): super(TestTokenIssue, self).setUp() - self.sc_mock.get_token.return_value = identity_fakes.TOKEN self.cmd = token.IssueToken(self.app, None) def test_token_issue(self): + auth_ref = identity_fakes.fake_auth_ref( + identity_fakes.TOKEN, + ) + self.ar_mock = mock.PropertyMock(return_value=auth_ref) + type(self.app.client_manager).auth_ref = self.ar_mock + arglist = [] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -48,12 +52,10 @@ def test_token_issue(self): # data to be shown. columns, data = self.cmd.take_action(parsed_args) - self.sc_mock.get_token.assert_called_with() - collist = ('expires', 'id', 'project_id', 'user_id') self.assertEqual(collist, columns) datalist = ( - identity_fakes.token_expires, + auth_ref.expires, identity_fakes.token_id, identity_fakes.project_id, identity_fakes.user_id, @@ -61,8 +63,11 @@ def test_token_issue(self): self.assertEqual(datalist, data) def test_token_issue_with_unscoped_token(self): - # make sure we return an unscoped token - self.sc_mock.get_token.return_value = identity_fakes.UNSCOPED_TOKEN + auth_ref = identity_fakes.fake_auth_ref( + identity_fakes.UNSCOPED_TOKEN, + ) + self.ar_mock = mock.PropertyMock(return_value=auth_ref) + type(self.app.client_manager).auth_ref = self.ar_mock arglist = [] verifylist = [] @@ -71,12 +76,14 @@ def test_token_issue_with_unscoped_token(self): # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) - self.sc_mock.get_token.assert_called_with() - - collist = ('expires', 'id', 'user_id') + collist = ( + 'expires', + 'id', + 'user_id', + ) self.assertEqual(collist, columns) datalist = ( - identity_fakes.token_expires, + auth_ref.expires, identity_fakes.token_id, identity_fakes.user_id, ) diff --git a/openstackclient/tests/identity/v3/fakes.py b/openstackclient/tests/identity/v3/fakes.py index 1422166a90..cd1b4bd77d 100644 --- a/openstackclient/tests/identity/v3/fakes.py +++ b/openstackclient/tests/identity/v3/fakes.py @@ -13,8 +13,12 @@ # under the License. # +import copy import mock +from keystoneauth1 import access +from keystoneauth1 import fixture + from openstackclient.tests import fakes from openstackclient.tests import utils @@ -419,6 +423,36 @@ } +def fake_auth_ref(fake_token, fake_service=None): + """Create an auth_ref using keystoneauth's fixtures""" + token_copy = copy.deepcopy(fake_token) + token_id = token_copy.pop('id') + token = fixture.V3Token(**token_copy) + # An auth_ref is actually an access info object + auth_ref = access.create( + body=token, + auth_token=token_id, + ) + + # Create a service catalog + if fake_service: + service = token.add_service( + fake_service['type'], + fake_service['name'], + ) + # TODO(dtroyer): Add an 'id' element to KSA's _Service fixure + service['id'] = fake_service['id'] + for e in fake_service['endpoints']: + region = e.get('region_id') or e.get('region', '') + service.add_endpoint( + e['interface'], + e['url'], + region=region, + ) + + return auth_ref + + class FakeAuth(object): def __init__(self, auth_method_class=None): diff --git a/openstackclient/tests/identity/v3/test_catalog.py b/openstackclient/tests/identity/v3/test_catalog.py index 1b8fa08586..e3c5ed3d88 100644 --- a/openstackclient/tests/identity/v3/test_catalog.py +++ b/openstackclient/tests/identity/v3/test_catalog.py @@ -14,6 +14,7 @@ import mock from openstackclient.identity.v3 import catalog +from openstackclient.tests.identity.v3 import fakes as identity_fakes from openstackclient.tests import utils @@ -50,7 +51,7 @@ def setUp(self): super(TestCatalog, self).setUp() self.sc_mock = mock.MagicMock() - self.sc_mock.service_catalog.get_data.return_value = [ + self.sc_mock.service_catalog.catalog.return_value = [ self.fake_service, ] @@ -69,6 +70,13 @@ def setUp(self): self.cmd = catalog.ListCatalog(self.app, None) def test_catalog_list(self): + auth_ref = identity_fakes.fake_auth_ref( + identity_fakes.TOKEN_WITH_PROJECT_ID, + fake_service=self.fake_service, + ) + self.ar_mock = mock.PropertyMock(return_value=auth_ref) + type(self.app.client_manager).auth_ref = self.ar_mock + arglist = [] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -77,7 +85,6 @@ def test_catalog_list(self): # returns a tuple containing the column names and an iterable # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.sc_mock.service_catalog.get_data.assert_called_with() collist = ('Name', 'Type', 'Endpoints') self.assertEqual(collist, columns) @@ -101,6 +108,13 @@ def setUp(self): self.cmd = catalog.ShowCatalog(self.app, None) def test_catalog_show(self): + auth_ref = identity_fakes.fake_auth_ref( + identity_fakes.TOKEN_WITH_PROJECT_ID, + fake_service=self.fake_service, + ) + self.ar_mock = mock.PropertyMock(return_value=auth_ref) + type(self.app.client_manager).auth_ref = self.ar_mock + arglist = [ 'compute', ] @@ -113,7 +127,6 @@ def test_catalog_show(self): # 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.sc_mock.service_catalog.get_data.assert_called_with() collist = ('endpoints', 'id', 'name', 'type') self.assertEqual(collist, columns) diff --git a/openstackclient/tests/identity/v3/test_token.py b/openstackclient/tests/identity/v3/test_token.py index b68bc242ec..9728c6e134 100644 --- a/openstackclient/tests/identity/v3/test_token.py +++ b/openstackclient/tests/identity/v3/test_token.py @@ -24,10 +24,9 @@ class TestToken(identity_fakes.TestIdentityv3): def setUp(self): super(TestToken, self).setUp() - # Get a shortcut to the Service Catalog Mock - self.sc_mock = mock.Mock() - self.app.client_manager.auth_ref = mock.Mock() - self.app.client_manager.auth_ref.service_catalog = self.sc_mock + # Get a shortcut to the Auth Ref Mock + self.ar_mock = mock.PropertyMock() + type(self.app.client_manager).auth_ref = self.ar_mock class TestTokenIssue(TestToken): @@ -38,23 +37,25 @@ def setUp(self): self.cmd = token.IssueToken(self.app, None) def test_token_issue_with_project_id(self): + auth_ref = identity_fakes.fake_auth_ref( + identity_fakes.TOKEN_WITH_PROJECT_ID, + ) + self.ar_mock = mock.PropertyMock(return_value=auth_ref) + type(self.app.client_manager).auth_ref = self.ar_mock + arglist = [] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.sc_mock.get_token.return_value = \ - identity_fakes.TOKEN_WITH_PROJECT_ID # 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.sc_mock.get_token.assert_called_with() - collist = ('expires', 'id', 'project_id', 'user_id') self.assertEqual(collist, columns) datalist = ( - identity_fakes.token_expires, + auth_ref.expires, identity_fakes.token_id, identity_fakes.project_id, identity_fakes.user_id, @@ -62,45 +63,53 @@ def test_token_issue_with_project_id(self): self.assertEqual(datalist, data) def test_token_issue_with_domain_id(self): + auth_ref = identity_fakes.fake_auth_ref( + identity_fakes.TOKEN_WITH_DOMAIN_ID, + ) + self.ar_mock = mock.PropertyMock(return_value=auth_ref) + type(self.app.client_manager).auth_ref = self.ar_mock + arglist = [] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.sc_mock.get_token.return_value = \ - identity_fakes.TOKEN_WITH_DOMAIN_ID # 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.sc_mock.get_token.assert_called_with() - collist = ('domain_id', 'expires', 'id', 'user_id') self.assertEqual(collist, columns) datalist = ( identity_fakes.domain_id, - identity_fakes.token_expires, + auth_ref.expires, identity_fakes.token_id, identity_fakes.user_id, ) self.assertEqual(datalist, data) def test_token_issue_with_unscoped(self): + auth_ref = identity_fakes.fake_auth_ref( + identity_fakes.UNSCOPED_TOKEN, + ) + self.ar_mock = mock.PropertyMock(return_value=auth_ref) + type(self.app.client_manager).auth_ref = self.ar_mock + arglist = [] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.sc_mock.get_token.return_value = \ - identity_fakes.UNSCOPED_TOKEN # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) - self.sc_mock.get_token.assert_called_with() - - collist = ('expires', 'id', 'user_id') + collist = ( + 'expires', + 'id', + 'user_id', + ) self.assertEqual(collist, columns) datalist = ( - identity_fakes.token_expires, + auth_ref.expires, identity_fakes.token_id, identity_fakes.user_id, ) diff --git a/setup.cfg b/setup.cfg index 2bff76b1c5..db5fa0966a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -26,7 +26,7 @@ packages = console_scripts = openstack = openstackclient.shell:main -keystoneclient.auth.plugin = +keystoneauth1.plugin = token_endpoint = openstackclient.api.auth_plugin:TokenEndpoint osc_password = openstackclient.api.auth_plugin:OSCGenericPassword From df71ae814e06965970ab13dbd3bb159023a18afc Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 9 Jun 2016 16:23:05 +0000 Subject: [PATCH 0973/3095] Updated from global requirements Change-Id: I729673d3c5e7f32ddf6f912d8ac5b7ca3b1cb394 --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index ef309af956..50b3806881 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -21,7 +21,7 @@ bandit>=1.0.1 # Apache-2.0 # Install these to generate sphinx autodocs python-barbicanclient>=4.0.0 # Apache-2.0 -python-congressclient<2000,>=1.0.0 # Apache-2.0 +python-congressclient<2000,>=1.3.0 # Apache-2.0 python-designateclient>=1.5.0 # Apache-2.0 python-heatclient>=1.1.0 # Apache-2.0 python-ironicclient>=1.1.0 # Apache-2.0 From 6c269efda87185a1bbecf22c62dead1cd78132af Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 8 Jun 2016 14:33:39 -0500 Subject: [PATCH 0974/3095] Use osc-lib and set up deprecation warnings The initial use of osc-lib is behind the compatibility/deprecation modules that we will leave in place for a time for plugins to catch up. * openstackclient.common.exceptions * openstackclient.common.utils Module-level warnings are emitted directly on stderr since logging has not been configured yet. Change-Id: I79e57ce9523a20366bccaf9b949ab5906792ea0d --- openstackclient/common/exceptions.py | 108 +------ openstackclient/common/utils.py | 428 +-------------------------- requirements.txt | 1 + 3 files changed, 17 insertions(+), 520 deletions(-) diff --git a/openstackclient/common/exceptions.py b/openstackclient/common/exceptions.py index bdc33ddb54..7124074c99 100644 --- a/openstackclient/common/exceptions.py +++ b/openstackclient/common/exceptions.py @@ -1,5 +1,3 @@ -# Copyright 2012-2013 OpenStack, LLC. -# # 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 @@ -13,105 +11,15 @@ # under the License. # -"""Exception definitions.""" - - -class CommandError(Exception): - pass - - -class AuthorizationFailure(Exception): - pass - - -class PluginAttributeError(Exception): - """A plugin threw an AttributeError while being lazily loaded.""" - # This *must not* inherit from AttributeError; - # that would defeat the whole purpose. - pass - - -class NoTokenLookupException(Exception): - """This does not support looking up endpoints from an existing token.""" - pass - - -class EndpointNotFound(Exception): - """Could not find Service or Region in Service Catalog.""" - pass - - -class UnsupportedVersion(Exception): - """The user is trying to use an unsupported version of the API""" - pass - - -class ClientException(Exception): - """The base exception class for all exceptions this library raises.""" +# NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release +# or Jun 2017. - def __init__(self, code, message=None, details=None): - self.code = code - self.message = message or self.__class__.message - self.details = details +import sys - def __str__(self): - return "%s (HTTP %s)" % (self.message, self.code) +from osc_lib.exceptions import * # noqa -class BadRequest(ClientException): - """HTTP 400 - Bad request: you sent some malformed data.""" - http_status = 400 - message = "Bad request" - - -class Unauthorized(ClientException): - """HTTP 401 - Unauthorized: bad credentials.""" - http_status = 401 - message = "Unauthorized" - - -class Forbidden(ClientException): - """HTTP 403 - Forbidden: not authorized to access to this resource.""" - http_status = 403 - message = "Forbidden" - - -class NotFound(ClientException): - """HTTP 404 - Not found""" - http_status = 404 - message = "Not found" - - -class Conflict(ClientException): - """HTTP 409 - Conflict""" - http_status = 409 - message = "Conflict" - - -class OverLimit(ClientException): - """HTTP 413 - Over limit: reached the API limits for this time period.""" - http_status = 413 - message = "Over limit" - - -# NotImplemented is a python keyword. -class HTTPNotImplemented(ClientException): - """HTTP 501 - Not Implemented: server does not support this operation.""" - http_status = 501 - message = "Not Implemented" - - -# In Python 2.4 Exception is old-style and thus doesn't have a __subclasses__() -# so we can do this: -# _code_map = dict((c.http_status, c) -# for c in ClientException.__subclasses__()) -# -# Instead, we have to hardcode it: -_code_map = dict((c.http_status, c) for c in [ - BadRequest, - Unauthorized, - Forbidden, - NotFound, - OverLimit, - HTTPNotImplemented -]) +sys.stderr.write( + "WARNING: %s is deprecated and will be removed after Jun 2017. " + "Please use osc_lib.exceptions\n" % __name__ +) diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index 5e058547b4..73cd3dc9d3 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -1,5 +1,3 @@ -# Copyright 2012-2013 OpenStack Foundation -# # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at @@ -13,425 +11,15 @@ # under the License. # -"""Common client utilities""" - -import getpass -import logging -import os -import six -import time - -from oslo_utils import importutils - -from openstackclient.common import exceptions -from openstackclient.i18n import _ - - -def find_resource(manager, name_or_id, **kwargs): - """Helper for the _find_* methods. - - :param manager: A client manager class - :param name_or_id: The resource we are trying to find - :param kwargs: To be used in calling .find() - :rtype: The found resource - - This method will attempt to find a resource in a variety of ways. - Primarily .get() methods will be called with `name_or_id` as an integer - value, and tried again as a string value. - - If both fail, then a .find() is attempted, which is essentially calling - a .list() function with a 'name' query parameter that is set to - `name_or_id`. - - Lastly, if any kwargs are passed in, they will be treated as additional - query parameters. This is particularly handy in the case of finding - resources in a domain. - - """ - - # Try to get entity as integer id - try: - if isinstance(name_or_id, int) or name_or_id.isdigit(): - return manager.get(int(name_or_id), **kwargs) - # FIXME(dtroyer): The exception to catch here is dependent on which - # client library the manager passed in belongs to. - # Eventually this should be pulled from a common set - # of client exceptions. - except Exception as ex: - if type(ex).__name__ == 'NotFound': - pass - else: - raise - - # Try directly using the passed value - try: - return manager.get(name_or_id, **kwargs) - except Exception: - pass - - if len(kwargs) == 0: - kwargs = {} - - try: - # Prepare the kwargs for calling find - if 'NAME_ATTR' in manager.resource_class.__dict__: - # novaclient does this for oddball resources - kwargs[manager.resource_class.NAME_ATTR] = name_or_id - else: - kwargs['name'] = name_or_id - except Exception: - pass - - # finally try to find entity by name - try: - return manager.find(**kwargs) - # FIXME(dtroyer): The exception to catch here is dependent on which - # client library the manager passed in belongs to. - # Eventually this should be pulled from a common set - # of client exceptions. - except Exception as ex: - if type(ex).__name__ == 'NotFound': - msg = _("No %(resource)s with a name or ID " - "of '%(name_or_id)s' exists.") - raise exceptions.CommandError( - msg % {'resource': manager.resource_class.__name__.lower(), - 'name_or_id': name_or_id} - ) - if type(ex).__name__ == 'NoUniqueMatch': - msg = _("More than one %(resource)s exists with " - "the name '%(name_or_id)s'.") - raise exceptions.CommandError( - msg % {'resource': manager.resource_class.__name__.lower(), - 'name_or_id': name_or_id} - ) - else: - pass - - for resource in manager.list(): - # short circuit and return the first match - if (resource.get('id') == name_or_id or - resource.get('name') == name_or_id): - return resource - else: - # we found no match, report back this error: - msg = _("Could not find resource %s") % name_or_id - raise exceptions.CommandError(msg) - - -def format_dict(data): - """Return a formatted string of key value pairs - - :param data: a dict - :rtype: a string formatted to key='value' - """ - - output = "" - for s in sorted(data): - output = output + s + "='" + six.text_type(data[s]) + "', " - return output[:-2] - - -def format_list(data, separator=', '): - """Return a formatted strings - - :param data: a list of strings - :param separator: the separator to use between strings (default: ', ') - :rtype: a string formatted based on separator - """ - - return separator.join(sorted(data)) - - -def format_list_of_dicts(data): - """Return a formatted string of key value pairs for each dict - - :param data: a list of dicts - :rtype: a string formatted to key='value' with dicts separated by new line - """ - - return '\n'.join(format_dict(i) for i in data) - - -def get_field(item, field): - try: - if isinstance(item, dict): - return item[field] - else: - return getattr(item, field) - except Exception: - msg = _("Resource doesn't have field %s") % field - raise exceptions.CommandError(msg) - - -def get_item_properties(item, fields, mixed_case_fields=None, formatters=None): - """Return a tuple containing the item properties. - - :param item: a single item resource (e.g. Server, Project, etc) - :param fields: tuple of strings with the desired field names - :param mixed_case_fields: tuple of field names to preserve case - :param formatters: dictionary mapping field names to callables - to format the values - """ - if mixed_case_fields is None: - mixed_case_fields = [] - if formatters is None: - formatters = {} - - row = [] - - for field in fields: - if field in mixed_case_fields: - field_name = field.replace(' ', '_') - else: - field_name = field.lower().replace(' ', '_') - data = getattr(item, field_name, '') - if field in formatters: - row.append(formatters[field](data)) - else: - row.append(data) - return tuple(row) - - -def get_dict_properties(item, fields, mixed_case_fields=None, formatters=None): - """Return a tuple containing the item properties. - - :param item: a single dict resource - :param fields: tuple of strings with the desired field names - :param mixed_case_fields: tuple of field names to preserve case - :param formatters: dictionary mapping field names to callables - to format the values - """ - if mixed_case_fields is None: - mixed_case_fields = [] - if formatters is None: - formatters = {} - - row = [] - - for field in fields: - if field in mixed_case_fields: - field_name = field.replace(' ', '_') - else: - field_name = field.lower().replace(' ', '_') - data = item[field_name] if field_name in item else '' - if field in formatters: - row.append(formatters[field](data)) - else: - row.append(data) - return tuple(row) - - -def sort_items(items, sort_str): - """Sort items based on sort keys and sort directions given by sort_str. - - :param items: a list or generator object of items - :param sort_str: a string defining the sort rules, the format is - ':[direction1],:[direction2]...', direction can be 'asc' - for ascending or 'desc' for descending, if direction is not given, - it's ascending by default - :return: sorted items - """ - if not sort_str: - return items - # items may be a generator object, transform it to a list - items = list(items) - sort_keys = sort_str.strip().split(',') - for sort_key in reversed(sort_keys): - reverse = False - if ':' in sort_key: - sort_key, direction = sort_key.split(':', 1) - if not sort_key: - msg = _("empty string is not a valid sort key") - raise exceptions.CommandError(msg) - if direction not in ['asc', 'desc']: - if not direction: - direction = "empty string" - msg = _("%(direction)s is not a valid sort direction for " - "sort key %(sort_key)s, use asc or desc instead") - raise exceptions.CommandError( - msg % {'direction': direction, - 'sort_key': sort_key} - ) - if direction == 'desc': - reverse = True - items.sort(key=lambda item: get_field(item, sort_key), - reverse=reverse) - return items - - -def env(*vars, **kwargs): - """Search for the first defined of possibly many env vars - - Returns the first environment variable defined in vars, or - returns the default defined in kwargs. - """ - for v in vars: - value = os.environ.get(v, None) - if value: - return value - return kwargs.get('default', '') - - -def get_client_class(api_name, version, version_map): - """Returns the client class for the requested API version - - :param api_name: the name of the API, e.g. 'compute', 'image', etc - :param version: the requested API version - :param version_map: a dict of client classes keyed by version - :rtype: a client class for the requested API version - """ - try: - client_path = version_map[str(version)] - except (KeyError, ValueError): - msg = _("Invalid %(api_name)s client version '%(version)s'. " - "must be one of: %(version_map)s") - raise exceptions.UnsupportedVersion( - msg % {'api_name': api_name, - 'version': version, - 'version_map': ', '.join(list(version_map.keys()))} - ) - - return importutils.import_class(client_path) - - -def wait_for_status(status_f, - res_id, - status_field='status', - success_status=['active'], - error_status=['error'], - sleep_time=5, - callback=None): - """Wait for status change on a resource during a long-running operation - - :param status_f: a status function that takes a single id argument - :param res_id: the resource id to watch - :param status_field: the status attribute in the returned resource object - :param success_status: a list of status strings for successful completion - :param error_status: a list of status strings for error - :param sleep_time: wait this long (seconds) - :param callback: called per sleep cycle, useful to display progress - :rtype: True on success - """ - while True: - res = status_f(res_id) - status = getattr(res, status_field, '').lower() - if status in success_status: - retval = True - break - elif status in error_status: - retval = False - break - if callback: - progress = getattr(res, 'progress', None) or 0 - callback(progress) - time.sleep(sleep_time) - return retval - - -def wait_for_delete(manager, - res_id, - status_field='status', - error_status=['error'], - exception_name=['NotFound'], - sleep_time=5, - timeout=300, - callback=None): - """Wait for resource deletion - - :param manager: the manager from which we can get the resource - :param res_id: the resource id to watch - :param status_field: the status attribute in the returned resource object, - this is used to check for error states while the resource is being - deleted - :param error_status: a list of status strings for error - :param exception_name: a list of exception strings for deleted case - :param sleep_time: wait this long between checks (seconds) - :param timeout: check until this long (seconds) - :param callback: called per sleep cycle, useful to display progress; this - function is passed a progress value during each iteration of the wait - loop - :rtype: True on success, False if the resource has gone to error state or - the timeout has been reached - """ - total_time = 0 - while total_time < timeout: - try: - # might not be a bad idea to re-use find_resource here if it was - # a bit more friendly in the exceptions it raised so we could just - # handle a NotFound exception here without parsing the message - res = manager.get(res_id) - except Exception as ex: - if type(ex).__name__ in exception_name: - return True - raise - - status = getattr(res, status_field, '').lower() - if status in error_status: - return False - - if callback: - progress = getattr(res, 'progress', None) or 0 - callback(progress) - time.sleep(sleep_time) - total_time += sleep_time - - # if we got this far we've timed out - return False - - -def get_effective_log_level(): - """Returns the lowest logging level considered by logging handlers - - Retrieve and return the smallest log level set among the root - logger's handlers (in case of multiple handlers). - """ - root_log = logging.getLogger() - min_log_lvl = logging.CRITICAL - for handler in root_log.handlers: - min_log_lvl = min(min_log_lvl, handler.level) - return min_log_lvl - - -def get_password(stdin, prompt=None, confirm=True): - message = prompt or "User Password:" - if hasattr(stdin, 'isatty') and stdin.isatty(): - try: - while True: - first_pass = getpass.getpass(message) - if not confirm: - return first_pass - second_pass = getpass.getpass("Repeat " + message) - if first_pass == second_pass: - return first_pass - print("The passwords entered were not the same") - except EOFError: # Ctl-D - raise exceptions.CommandError(_("Error reading password.")) - raise exceptions.CommandError(_("There was a request to be prompted " - "for a password and a terminal was " - "not detected.")) - - -def read_blob_file_contents(blob_file): - try: - with open(blob_file) as file: - blob = file.read().strip() - return blob - except IOError: - msg = _("Error occurred trying to read from file %s") - raise exceptions.CommandError(msg % blob_file) +# NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release +# or Jun 2017. +import sys -def build_kwargs_dict(arg_name, value): - """Return a dictionary containing `arg_name` if `value` is set.""" - kwargs = {} - if value: - kwargs[arg_name] = value - return kwargs +from osc_lib.utils import * # noqa -def is_ascii(string): - try: - string.decode('ascii') - return True - except UnicodeDecodeError: - return False +sys.stderr.write( + "WARNING: %s is deprecated and will be removed after Jun 2017. " + "Please use osc_lib.utils\n" % __name__ +) diff --git a/requirements.txt b/requirements.txt index 5d8c844a71..f3affbd37d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,6 +9,7 @@ cliff!=1.16.0,!=1.17.0,>=1.15.0 # Apache-2.0 keystoneauth1>=2.1.0 # Apache-2.0 openstacksdk>=0.8.6 # Apache-2.0 os-client-config>=1.13.1 # Apache-2.0 +osc-lib>=0.1.0 # Apache-2.0 oslo.config>=3.10.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 oslo.utils>=3.11.0 # Apache-2.0 From 56a081fa238967c18e77fd12c7ae697289e34c81 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Fri, 10 Jun 2016 14:44:50 -0500 Subject: [PATCH 0975/3095] Add geneve provider network type Add the "geneve" choice to the "os network create" command's "--provider-network-type" option. Change-Id: I7573232ec3594ec4acbfae43a8456b8c3fcd1a83 Implements: blueprint neutron-client --- doc/source/command-objects/network.rst | 4 ++-- openstackclient/network/v2/network.py | 8 ++++---- .../notes/bp-neutron-client-a0552f8ca909b665.yaml | 5 +++++ 3 files changed, 11 insertions(+), 6 deletions(-) create mode 100644 releasenotes/notes/bp-neutron-client-a0552f8ca909b665.yaml diff --git a/doc/source/command-objects/network.rst b/doc/source/command-objects/network.rst index 8668daeee4..1cf442f222 100644 --- a/doc/source/command-objects/network.rst +++ b/doc/source/command-objects/network.rst @@ -108,7 +108,7 @@ Create new network .. option:: --provider-network-type The physical mechanism by which the virtual network is implemented. - The supported options are: flat, gre, local, vlan, vxlan + The supported options are: flat, geneve, gre, local, vlan, vxlan *Network version 2 only* @@ -120,7 +120,7 @@ Create new network .. option:: --provider-segment - VLAN ID for VLAN networks or Tunnel ID for GRE/VXLAN networks + VLAN ID for VLAN networks or Tunnel ID for GENEVE/GRE/VXLAN networks *Network version 2 only* diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 87e65dad11..df4d596d55 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -103,11 +103,11 @@ def _add_additional_network_options(parser): parser.add_argument( '--provider-network-type', metavar='', - choices=['flat', 'gre', 'local', + choices=['flat', 'geneve', 'gre', 'local', 'vlan', 'vxlan'], help=_("The physical mechanism by which the virtual network " "is implemented. The supported options are: " - "flat, gre, local, vlan, vxlan")) + "flat, geneve, gre, local, vlan, vxlan")) parser.add_argument( '--provider-physical-network', metavar='', @@ -118,8 +118,8 @@ def _add_additional_network_options(parser): '--provider-segment', metavar='', dest='segmentation_id', - help=_("VLAN ID for VLAN networks or Tunnel ID for GRE/VXLAN " - "networks")) + help=_("VLAN ID for VLAN networks or Tunnel ID for " + "GENEVE/GRE/VXLAN networks")) vlan_transparent_grp = parser.add_mutually_exclusive_group() vlan_transparent_grp.add_argument( diff --git a/releasenotes/notes/bp-neutron-client-a0552f8ca909b665.yaml b/releasenotes/notes/bp-neutron-client-a0552f8ca909b665.yaml new file mode 100644 index 0000000000..f8de4ee6ae --- /dev/null +++ b/releasenotes/notes/bp-neutron-client-a0552f8ca909b665.yaml @@ -0,0 +1,5 @@ +--- +features: + - Add ``geneve`` choice to the ``network create`` command + ``--provider-network-type`` option. + [Blueprint :oscbp:`neutron-client`] From 0695d1495e8337149850b8f753c603e7cac1989c Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Wed, 8 Jun 2016 14:25:59 +0800 Subject: [PATCH 0976/3095] Trivial: Fix coding style in examples in doc Now, we use i18n strings for help and log messages, and standardize the usage of logger. So fix those in the example in doc. Change-Id: Ibbc051b12133699811dd35a7e77a10de50ed8e44 --- doc/source/command-errors.rst | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/doc/source/command-errors.rst b/doc/source/command-errors.rst index a24dc317bb..48b828fd42 100644 --- a/doc/source/command-errors.rst +++ b/doc/source/command-errors.rst @@ -51,7 +51,7 @@ raised that includes the name of the file that was attempted to be opened. ) as p: public_key = p.read() except IOError as e: - msg = "Key file %s not found: %s" + msg = _("Key file %s not found: %s") raise exceptions.CommandError( msg % (parsed_args.public_key, e), ) @@ -135,7 +135,7 @@ remaining arguments are set using the ``update()`` method. parsed_args.property, ) except SomeException: # Need to define the exceptions to catch here - self.app.log.error("Property set failed") + LOG.error(_("Property set failed")) result += 1 if parsed_args.state: @@ -145,7 +145,7 @@ remaining arguments are set using the ``update()`` method. parsed_args.state, ) except SomeException: # Need to define the exceptions to catch here - self.app.log.error("State set failed") + LOG.error(_("State set failed")) result += 1 try: @@ -154,7 +154,7 @@ remaining arguments are set using the ``update()`` method. **kwargs ) except SomeException: # Need to define the exceptions to catch here - self.app.log.error("Update failed") + LOG.error(_("Update failed")) result += 1 # NOTE(dtroyer): We need to signal the error, and a non-zero return code, @@ -166,8 +166,8 @@ Example 2 ~~~~~~~~~ This example is taken from the ``network delete`` command which takes multiple -networks to delete. All networks will be delete in a loop, which makes multiple -``delete_network()`` calls. +networks to delete. All networks will be deleted in a loop, which makes +multiple ``delete_network()`` calls. .. code-block:: python @@ -179,7 +179,7 @@ networks to delete. All networks will be delete in a loop, which makes multiple 'network', metavar="", nargs="+", - help=("Network(s) to delete (name or ID)") + help=_("Network(s) to delete (name or ID)") ) return parser @@ -191,11 +191,15 @@ networks to delete. All networks will be delete in a loop, which makes multiple obj = client.find_network(network, ignore_missing=False) client.delete_network(obj) except Exception: - self.app.log.error("Failed to delete network with name " - "or ID %s." % network) + LOG.error(_("Failed to delete network with name " + "or ID %s."), network) ret += 1 if ret > 0: total = len(parsed_args.network) - msg = "Failed to delete %s of %s networks." % (ret, total) + msg = _("Failed to delete %(ret)s of %(total)s networks.") % + { + "ret": ret, + "total": total, + } raise exceptions.CommandError(msg) From 8c7e34d65ca6217eee54389fcd40554a8cb454e6 Mon Sep 17 00:00:00 2001 From: sunyajing Date: Wed, 1 Jun 2016 13:48:23 +0800 Subject: [PATCH 0977/3095] Fix image delete multiple arguments error Fix image delete command, support processing multiple arguments delete error. Fix doc/source/command-errors.rst, make the msg format correct. Change-Id: Icbe347fe077bc148bf71ea8f7399b0e934b7cdf9 Partially-Implements: blueprint multi-argument-image --- doc/source/command-errors.rst | 7 ++--- openstackclient/image/v2/image.py | 24 +++++++++++---- openstackclient/tests/image/v2/test_image.py | 31 ++++++++++++++++++++ 3 files changed, 52 insertions(+), 10 deletions(-) diff --git a/doc/source/command-errors.rst b/doc/source/command-errors.rst index 48b828fd42..c4adb7d190 100644 --- a/doc/source/command-errors.rst +++ b/doc/source/command-errors.rst @@ -197,9 +197,6 @@ multiple ``delete_network()`` calls. if ret > 0: total = len(parsed_args.network) - msg = _("Failed to delete %(ret)s of %(total)s networks.") % - { - "ret": ret, - "total": total, - } + msg = (_("Failed to delete %(ret)s of %(total)s networks.") + % {"ret": ret, "total": total}) raise exceptions.CommandError(msg) diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index fa1de42419..a6d244c311 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -372,13 +372,27 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): + + del_result = 0 image_client = self.app.client_manager.image for image in parsed_args.images: - image_obj = utils.find_resource( - image_client.images, - image, - ) - image_client.images.delete(image_obj.id) + try: + image_obj = utils.find_resource( + image_client.images, + image, + ) + image_client.images.delete(image_obj.id) + except Exception as e: + del_result += 1 + self.app.log.error(_("Failed to delete image with " + "name or ID '%(image)s': %(e)s") + % {'image': image, 'e': e}) + + total = len(parsed_args.images) + if (del_result > 0): + msg = (_("Failed to delete %(dresult)s of %(total)s images.") + % {'dresult': del_result, 'total': total}) + raise exceptions.CommandError(msg) class ListImage(command.Lister): diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index ca20d83da4..f940b865b8 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -473,6 +473,37 @@ def test_image_delete_multi_images(self): self.images_mock.delete.assert_has_calls(calls) self.assertIsNone(result) + def test_image_delete_multi_images_exception(self): + + images = image_fakes.FakeImage.create_images(count=2) + arglist = [ + images[0].id, + images[1].id, + 'x-y-x', + ] + verifylist = [ + ('images', arglist) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Fake exception in utils.find_resource() + # In image v2, we use utils.find_resource() to find a network. + # It calls get() several times, but find() only one time. So we + # choose to fake get() always raise exception, then pass through. + # And fake find() to find the real network or not. + ret_find = [ + images[0], + images[1], + exceptions.NotFound('404'), + ] + + self.images_mock.get = Exception() + self.images_mock.find.side_effect = ret_find + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + calls = [mock.call(i.id) for i in images] + self.images_mock.delete.assert_has_calls(calls) + class TestImageList(TestImage): From c7e6973ff50ff84af9ad55f7bdaeeea83ae40f0b Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Mon, 13 Jun 2016 19:19:19 +0800 Subject: [PATCH 0978/3095] Fix errors in flavor unit tests This patch fixes the problems below: 1. flavor.unset_keys() is not checked in flavor unit tests. So check them in the tests. 2. test_flavor_unset_no_project makes no sense. It is OK to specify ``--project ''`` without raising any exception. It can pass because in the test, we set nither project nor property. So remove this test. Change-Id: I04e537349936343b6d8c85b06bc6d0ba6bd41d6f --- .../tests/compute/v2/test_flavor.py | 33 +++++-------------- 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/openstackclient/tests/compute/v2/test_flavor.py b/openstackclient/tests/compute/v2/test_flavor.py index 434d5f92be..a35718fe10 100644 --- a/openstackclient/tests/compute/v2/test_flavor.py +++ b/openstackclient/tests/compute/v2/test_flavor.py @@ -644,6 +644,8 @@ def test_flavor_unset_property(self): result = self.cmd.take_action(parsed_args) self.flavors_mock.find.assert_called_with(name=parsed_args.flavor, is_public=None) + self.flavor.unset_keys.assert_called_with(['property']) + self.flavor_access_mock.remove_tenant_access.assert_not_called() self.assertIsNone(result) def test_flavor_unset_project(self): @@ -660,24 +662,14 @@ def test_flavor_unset_project(self): result = self.cmd.take_action(parsed_args) self.assertIsNone(result) + self.flavors_mock.find.assert_called_with(name=parsed_args.flavor, + is_public=None) self.flavor_access_mock.remove_tenant_access.assert_called_with( self.flavor.id, identity_fakes.project_id, ) - - def test_flavor_unset_no_project(self): - arglist = [ - '--project', '', - self.flavor.id, - ] - verifylist = [ - ('project', ''), - ('flavor', self.flavor.id), - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.assertRaises(exceptions.CommandError, self.cmd.take_action, - parsed_args) + self.flavor.unset_keys.assert_not_called() + self.assertIsNone(result) def test_flavor_unset_no_flavor(self): arglist = [ @@ -686,12 +678,8 @@ def test_flavor_unset_no_flavor(self): verifylist = [ ('project', identity_fakes.project_id), ] - - self.assertRaises(tests_utils.ParserException, - self.check_parser, - self.cmd, - arglist, - verifylist) + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) def test_flavor_unset_with_unexist_flavor(self): self.flavors_mock.get.side_effect = exceptions.NotFound(None) @@ -706,9 +694,7 @@ def test_flavor_unset_with_unexist_flavor(self): ('flavor', 'unexist_flavor'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - self.assertRaises(exceptions.CommandError, - self.cmd.take_action, + self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) def test_flavor_unset_nothing(self): @@ -718,7 +704,6 @@ def test_flavor_unset_nothing(self): verifylist = [ ('flavor', self.flavor.id), ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) From 6a6b192ddeb80b516778b1d6e3d34f4261dca85d Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Fri, 10 Jun 2016 16:01:31 -0500 Subject: [PATCH 0979/3095] Add "--network-segment" option to "subnet create" Add "--network-segment" option to the "subnet create" command. This is a beta command option and subject to change. Use global option "--os-beta-command" to enable this option. This patch set also provides a devref update for beta command options. Change-Id: I4d0fbe079b2a873307364c41c22ce9ba88e632e6 Partially-Implements: blueprint routed-networks --- doc/source/command-beta.rst | 58 +++++++++--- doc/source/command-objects/subnet.rst | 9 ++ openstackclient/network/v2/subnet.py | 10 +++ openstackclient/tests/network/v2/fakes.py | 9 +- .../tests/network/v2/test_subnet.py | 90 ++++++++++++++++--- .../bp-routed-networks-86a24f34d86fca53.yaml | 6 ++ 6 files changed, 157 insertions(+), 25 deletions(-) create mode 100644 releasenotes/notes/bp-routed-networks-86a24f34d86fca53.yaml diff --git a/doc/source/command-beta.rst b/doc/source/command-beta.rst index f0a4f85102..bc8c04c5e7 100644 --- a/doc/source/command-beta.rst +++ b/doc/source/command-beta.rst @@ -12,23 +12,29 @@ To address these challenges, an OpenStackClient command may be labeled as a beta command according to the guidelines below. Such commands may introduce backwards incompatible changes and may use REST API enhancements not yet released. +This also applies to command options associated with the beta +command object. -See the examples below on how to label a command as a beta -by updating the command documentation, help and implementation. +See the examples below on how to label an entire command or +a specific option as a beta by updating the documentation +and implementation. -The initial release note must label the new command as a beta. -No further release notes are required until the command -is no longer a beta. At which time, the command beta label -or the command itself must be removed and a new release note +The initial release note must label the new command or option +as a beta. No further release notes are required until the command +or option is no longer a beta. At which time, the beta label or +the command or option itself must be removed and a new release note must be provided. +Beta Command Example +-------------------- + Documentation -------------- +~~~~~~~~~~~~~ The command documentation must label the command as a beta. example list -~~~~~~~~~~~~ +++++++++++++ List examples @@ -42,7 +48,7 @@ List examples os example list Help ----- +~~~~ The command help must label the command as a beta. @@ -57,7 +63,7 @@ The command help must label the command as a beta. """ Implementation --------------- +~~~~~~~~~~~~~~ The command must raise a ``CommandError`` exception if beta commands are not enabled via ``--os-beta-command`` global option. @@ -66,3 +72,35 @@ are not enabled via ``--os-beta-command`` global option. def take_action(self, parsed_args): self.validate_os_beta_command_enabled() + +Beta Option Example +------------------- + +Documentation +~~~~~~~~~~~~~ + +The option documentation must label the option as a beta. + +.. option:: --example + + Example + + .. caution:: This is a beta command option and subject + to change. Use global option ``--os-beta-command`` + to enable this command option. + +Implementation +~~~~~~~~~~~~~~ + +The option must not be added if beta commands are not +enabled via ``--os-beta-command`` global option. + +.. code-block:: python + + def get_parser(self, prog_name): + if self.app.options.os_beta_command: + parser.add_argument( + '--example', + metavar='', + help=_("Example") + ) diff --git a/doc/source/command-objects/subnet.rst b/doc/source/command-objects/subnet.rst index ff6354e658..dd54408b71 100644 --- a/doc/source/command-objects/subnet.rst +++ b/doc/source/command-objects/subnet.rst @@ -28,6 +28,7 @@ Create new subnet [--ip-version {4,6}] [--ipv6-ra-mode {dhcpv6-stateful,dhcpv6-stateless,slaac}] [--ipv6-address-mode {dhcpv6-stateful,dhcpv6-stateless,slaac}] + [--network-segment ] --network @@ -107,6 +108,14 @@ Create new subnet IPv6 address mode, valid modes: [dhcpv6-stateful, dhcpv6-stateless, slaac] +.. option:: --network-segment + + Network segment to associate with this subnet (ID only) + + .. caution:: This is a beta command option and subject + to change. Use global option ``--os-beta-command`` + to enable this command option. + .. option:: --network Network this subnet belongs to (name or ID) diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 8e378c7e19..ee5b7df0ce 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -136,6 +136,9 @@ def _get_attrs(client_manager, parsed_args, is_create=True): attrs['ipv6_ra_mode'] = parsed_args.ipv6_ra_mode if parsed_args.ipv6_address_mode is not None: attrs['ipv6_address_mode'] = parsed_args.ipv6_address_mode + if 'network_segment' in parsed_args: + attrs['segment_id'] = client.find_segment( + parsed_args.network_segment, ignore_missing=False).id if 'gateway' in parsed_args and parsed_args.gateway is not None: gateway = parsed_args.gateway.lower() @@ -249,6 +252,13 @@ def get_parser(self, prog_name): help=_("IPv6 address mode, " "valid modes: [dhcpv6-stateful, dhcpv6-stateless, slaac]") ) + if self.app.options.os_beta_command: + parser.add_argument( + '--network-segment', + metavar='', + help=_("Network segment to associate with this subnet " + "(ID only)") + ) parser.add_argument( '--network', required=True, diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 9efbe8c608..41ff10238c 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -352,7 +352,7 @@ def create_one_network_segment(attrs=None): # Set default attributes. network_segment_attrs = { - 'id': 'segment-id-' + uuid.uuid4().hex, + 'id': 'network-segment-id-' + uuid.uuid4().hex, 'network_id': 'network-id-' + uuid.uuid4().hex, 'network_type': 'vlan', 'physical_network': 'physical-network-name-' + uuid.uuid4().hex, @@ -699,9 +699,10 @@ def create_one_subnet(attrs=None): 'host_routes': [], 'ip_version': 4, 'gateway_ip': '10.10.10.1', - 'ipv6_address_mode': 'None', - 'ipv6_ra_mode': 'None', - 'subnetpool_id': 'None', + 'ipv6_address_mode': None, + 'ipv6_ra_mode': None, + 'segment_id': None, + 'subnetpool_id': None, } # Overwrite default attributes. diff --git a/openstackclient/tests/network/v2/test_subnet.py b/openstackclient/tests/network/v2/test_subnet.py index 25684d63e0..0c1854c102 100644 --- a/openstackclient/tests/network/v2/test_subnet.py +++ b/openstackclient/tests/network/v2/test_subnet.py @@ -88,6 +88,14 @@ class TestCreateSubnet(TestSubnet): } ) + # The network segment to be returned from find_segment + _network_segment = \ + network_fakes.FakeNetworkSegment.create_one_network_segment( + attrs={ + 'network_id': _subnet.network_id, + } + ) + columns = ( 'allocation_pools', 'cidr', @@ -102,6 +110,7 @@ class TestCreateSubnet(TestSubnet): 'name', 'network_id', 'project_id', + 'segment_id', 'subnetpool_id', ) @@ -119,6 +128,7 @@ class TestCreateSubnet(TestSubnet): _subnet.name, _subnet.network_id, _subnet.project_id, + _subnet.segment_id, _subnet.subnetpool_id, ) @@ -136,6 +146,7 @@ class TestCreateSubnet(TestSubnet): _subnet_from_pool.name, _subnet_from_pool.network_id, _subnet_from_pool.project_id, + _subnet_from_pool.segment_id, _subnet_from_pool.subnetpool_id, ) @@ -153,6 +164,7 @@ class TestCreateSubnet(TestSubnet): _subnet_ipv6.name, _subnet_ipv6.network_id, _subnet_ipv6.project_id, + _subnet_ipv6.segment_id, _subnet_ipv6.subnetpool_id, ) @@ -186,6 +198,15 @@ def setUp(self): loaded=True, ) + # Mock SDK calls for all tests. + self.network.find_network = mock.Mock(return_value=self._network) + self.network.find_segment = mock.Mock( + return_value=self._network_segment + ) + self.network.find_subnet_pool = mock.Mock( + return_value=self._subnet_pool + ) + def test_create_no_options(self): arglist = [] verifylist = [] @@ -196,11 +217,9 @@ def test_create_no_options(self): self.check_parser, self.cmd, arglist, verifylist) def test_create_default_options(self): - # Mock create_subnet and find_network sdk calls to return the - # values we want for this test + # Mock SDK calls for this test. self.network.create_subnet = mock.Mock(return_value=self._subnet) self._network.id = self._subnet.network_id - self.network.find_network = mock.Mock(return_value=self._network) arglist = [ "--subnet-range", self._subnet.cidr, @@ -230,14 +249,10 @@ def test_create_default_options(self): self.assertEqual(self.data, data) def test_create_from_subnet_pool_options(self): - # Mock create_subnet, find_subnet_pool, and find_network sdk calls - # to return the values we want for this test + # Mock SDK calls for this test. self.network.create_subnet = \ mock.Mock(return_value=self._subnet_from_pool) self._network.id = self._subnet_from_pool.network_id - self.network.find_network = mock.Mock(return_value=self._network) - self.network.find_subnet_pool = \ - mock.Mock(return_value=self._subnet_pool) arglist = [ self._subnet_from_pool.name, @@ -290,11 +305,9 @@ def test_create_from_subnet_pool_options(self): self.assertEqual(self.data_subnet_pool, data) def test_create_options_subnet_range_ipv6(self): - # Mock create_subnet and find_network sdk calls to return the - # values we want for this test + # Mock SDK calls for this test. self.network.create_subnet = mock.Mock(return_value=self._subnet_ipv6) self._network.id = self._subnet_ipv6.network_id - self.network.find_network = mock.Mock(return_value=self._network) arglist = [ self._subnet_ipv6.name, @@ -357,6 +370,59 @@ def test_create_options_subnet_range_ipv6(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data_ipv6, data) + def test_create_no_beta_command_options(self): + arglist = [ + "--subnet-range", self._subnet.cidr, + "--network-segment", self._network_segment.id, + "--network", self._subnet.network_id, + self._subnet.name, + ] + verifylist = [ + ('name', self._subnet.name), + ('subnet_range', self._subnet.cidr), + ('network-segment', self._network_segment.id), + ('network', self._subnet.network_id), + ] + self.app.options.os_beta_command = False + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, arglist, verifylist) + + def test_create_with_network_segment(self): + # Mock SDK calls for this test. + self.network.create_subnet = mock.Mock(return_value=self._subnet) + self._network.id = self._subnet.network_id + + arglist = [ + "--subnet-range", self._subnet.cidr, + "--network-segment", self._network_segment.id, + "--network", self._subnet.network_id, + self._subnet.name, + ] + verifylist = [ + ('name', self._subnet.name), + ('subnet_range', self._subnet.cidr), + ('network_segment', self._network_segment.id), + ('network', self._subnet.network_id), + ('ip_version', self._subnet.ip_version), + ('gateway', 'auto'), + + ] + + self.app.options.os_beta_command = True + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_subnet.assert_called_once_with(**{ + 'cidr': self._subnet.cidr, + 'enable_dhcp': self._subnet.enable_dhcp, + 'ip_version': self._subnet.ip_version, + 'name': self._subnet.name, + 'network_id': self._subnet.network_id, + 'segment_id': self._network_segment.id, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + class TestDeleteSubnet(TestSubnet): @@ -593,6 +659,7 @@ class TestShowSubnet(TestSubnet): 'name', 'network_id', 'project_id', + 'segment_id', 'subnetpool_id', ) @@ -610,6 +677,7 @@ class TestShowSubnet(TestSubnet): _subnet.name, _subnet.network_id, _subnet.tenant_id, + _subnet.segment_id, _subnet.subnetpool_id, ) diff --git a/releasenotes/notes/bp-routed-networks-86a24f34d86fca53.yaml b/releasenotes/notes/bp-routed-networks-86a24f34d86fca53.yaml new file mode 100644 index 0000000000..6c4a185c7a --- /dev/null +++ b/releasenotes/notes/bp-routed-networks-86a24f34d86fca53.yaml @@ -0,0 +1,6 @@ +--- +features: + - Add ``--network-segment`` option to the ``subnet create`` command. + This is a beta command option and subject to change. Use global option + ``--os-beta-command`` to enable this option. + [Blueprint `routed-networks `_] From d20c863ebc11cecaaefe043e615b2fb4f5f26063 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 8 Jun 2016 14:17:14 -0500 Subject: [PATCH 0980/3095] osc-lib: exceptions Use osc-lib directly for exceptions. Leave openstackclient.common.exceptions for deprecation period. Change-Id: Iea3e862302372e1b31ccd27f69db59b4953ca828 --- doc/source/plugins.rst | 4 +++- openstackclient/api/api.py | 3 +-- openstackclient/api/auth.py | 2 +- openstackclient/common/clientmanager.py | 2 +- openstackclient/common/command.py | 2 +- openstackclient/compute/client.py | 3 ++- openstackclient/compute/v2/agent.py | 2 +- openstackclient/compute/v2/flavor.py | 2 +- openstackclient/compute/v2/keypair.py | 5 +++-- openstackclient/compute/v2/server.py | 6 +++--- openstackclient/compute/v2/server_backup.py | 2 +- openstackclient/compute/v2/server_group.py | 3 ++- openstackclient/compute/v2/service.py | 3 ++- openstackclient/identity/common.py | 2 +- openstackclient/identity/v2_0/catalog.py | 2 +- openstackclient/identity/v2_0/role.py | 5 ++--- openstackclient/identity/v2_0/service.py | 3 ++- openstackclient/identity/v3/catalog.py | 2 +- openstackclient/identity/v3/mapping.py | 3 ++- openstackclient/identity/v3/token.py | 3 ++- openstackclient/identity/v3/unscoped_saml.py | 3 ++- openstackclient/image/v2/image.py | 2 +- openstackclient/network/common.py | 3 ++- openstackclient/network/v2/address_scope.py | 3 ++- openstackclient/network/v2/port.py | 3 ++- openstackclient/network/v2/security_group_rule.py | 5 +++-- openstackclient/network/v2/subnet.py | 4 +++- openstackclient/shell.py | 2 +- openstackclient/tests/api/test_api.py | 3 ++- openstackclient/tests/common/test_clientmanager.py | 4 ++-- openstackclient/tests/common/test_command.py | 3 ++- openstackclient/tests/common/test_utils.py | 3 ++- openstackclient/tests/compute/v2/test_agent.py | 3 ++- openstackclient/tests/compute/v2/test_flavor.py | 3 ++- openstackclient/tests/compute/v2/test_hypervisor.py | 3 ++- openstackclient/tests/compute/v2/test_server.py | 5 +++-- openstackclient/tests/compute/v2/test_server_backup.py | 3 ++- openstackclient/tests/compute/v2/test_server_group.py | 3 ++- openstackclient/tests/compute/v2/test_service.py | 3 ++- openstackclient/tests/identity/v2_0/test_role.py | 2 +- openstackclient/tests/identity/v3/test_mappings.py | 3 ++- openstackclient/tests/identity/v3/test_project.py | 3 ++- openstackclient/tests/identity/v3/test_unscoped_saml.py | 3 ++- openstackclient/tests/image/v1/test_image.py | 3 ++- openstackclient/tests/image/v2/test_image.py | 3 ++- openstackclient/tests/network/v2/test_address_scope.py | 3 ++- openstackclient/tests/network/v2/test_network.py | 5 +++-- openstackclient/tests/network/v2/test_network_segment.py | 3 ++- openstackclient/tests/network/v2/test_port.py | 3 ++- .../tests/network/v2/test_security_group_rule.py | 3 ++- openstackclient/tests/volume/test_find_resource.py | 2 +- openstackclient/volume/v2/volume_type.py | 2 +- 52 files changed, 96 insertions(+), 62 deletions(-) diff --git a/doc/source/plugins.rst b/doc/source/plugins.rst index 5911b33044..de3018b9a4 100644 --- a/doc/source/plugins.rst +++ b/doc/source/plugins.rst @@ -151,9 +151,11 @@ the plugin commands: .. code-block:: python + # osc-lib interfaces available to plugins: + from osc_lib import exceptions + # OSC common interfaces available to plugins: from openstackclient.common import command - from openstackclient.common import exceptions from openstackclient.common import parseractions from openstackclient.common import logs from openstackclient.common import utils diff --git a/openstackclient/api/api.py b/openstackclient/api/api.py index bd2abd8818..0b00ff5067 100644 --- a/openstackclient/api/api.py +++ b/openstackclient/api/api.py @@ -17,8 +17,7 @@ from keystoneauth1 import exceptions as ks_exceptions from keystoneauth1 import session as ks_session - -from openstackclient.common import exceptions +from osc_lib import exceptions class KeystoneSession(object): diff --git a/openstackclient/api/auth.py b/openstackclient/api/auth.py index ded0e3699f..f1e53c49f0 100644 --- a/openstackclient/api/auth.py +++ b/openstackclient/api/auth.py @@ -17,8 +17,8 @@ import logging from keystoneauth1.loading import base +from osc_lib import exceptions as exc -from openstackclient.common import exceptions as exc from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index e8ba7ec446..04f624d0b0 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -20,12 +20,12 @@ import pkg_resources import sys +from osc_lib import exceptions from oslo_utils import strutils import requests import six from openstackclient.api import auth -from openstackclient.common import exceptions from openstackclient.common import session as osc_session from openstackclient.identity import client as identity_client diff --git a/openstackclient/common/command.py b/openstackclient/common/command.py index 144a0db10d..adf984fa13 100644 --- a/openstackclient/common/command.py +++ b/openstackclient/common/command.py @@ -18,9 +18,9 @@ from cliff import command from cliff import lister from cliff import show +from osc_lib import exceptions import six -from openstackclient.common import exceptions from openstackclient.i18n import _ diff --git a/openstackclient/compute/client.py b/openstackclient/compute/client.py index 8e6eedcf39..aa4c431f7d 100644 --- a/openstackclient/compute/client.py +++ b/openstackclient/compute/client.py @@ -15,7 +15,8 @@ import logging -from openstackclient.common import exceptions +from osc_lib import exceptions + from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/compute/v2/agent.py b/openstackclient/compute/v2/agent.py index 064fe5a66b..7b1a301e14 100644 --- a/openstackclient/compute/v2/agent.py +++ b/openstackclient/compute/v2/agent.py @@ -15,10 +15,10 @@ """Agent action implementations""" +from osc_lib import exceptions import six from openstackclient.common import command -from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 87909a1825..7761ab4331 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -15,10 +15,10 @@ """Flavor action implementations""" +from osc_lib import exceptions import six from openstackclient.common import command -from openstackclient.common import exceptions from openstackclient.common import parseractions from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/compute/v2/keypair.py b/openstackclient/compute/v2/keypair.py index 8af209fe33..3810c1a38d 100644 --- a/openstackclient/compute/v2/keypair.py +++ b/openstackclient/compute/v2/keypair.py @@ -17,11 +17,12 @@ import io import os -import six import sys +from osc_lib import exceptions +import six + from openstackclient.common import command -from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 5688b55fa2..ee59bb340b 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -19,17 +19,17 @@ import getpass import io import os -import six import sys -from openstackclient.common import command +from osc_lib import exceptions +import six try: from novaclient.v2 import servers except ImportError: from novaclient.v1_1 import servers -from openstackclient.common import exceptions +from openstackclient.common import command from openstackclient.common import parseractions from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/compute/v2/server_backup.py b/openstackclient/compute/v2/server_backup.py index 24d7101521..f0b3edc587 100644 --- a/openstackclient/compute/v2/server_backup.py +++ b/openstackclient/compute/v2/server_backup.py @@ -17,11 +17,11 @@ import sys +from osc_lib import exceptions from oslo_utils import importutils import six from openstackclient.common import command -from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/compute/v2/server_group.py b/openstackclient/compute/v2/server_group.py index 2e275b7175..1b817018ea 100644 --- a/openstackclient/compute/v2/server_group.py +++ b/openstackclient/compute/v2/server_group.py @@ -15,8 +15,9 @@ """Compute v2 Server Group action implementations""" +from osc_lib import exceptions + from openstackclient.common import command -from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index 5660ff5584..2a589f6b1d 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.py @@ -15,8 +15,9 @@ """Service action implementations""" +from osc_lib import exceptions + from openstackclient.common import command -from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/identity/common.py b/openstackclient/identity/common.py index 2afa41fb35..5a65c47cbe 100644 --- a/openstackclient/identity/common.py +++ b/openstackclient/identity/common.py @@ -20,8 +20,8 @@ from keystoneclient.v3 import groups from keystoneclient.v3 import projects from keystoneclient.v3 import users +from osc_lib import exceptions -from openstackclient.common import exceptions from openstackclient.common import utils diff --git a/openstackclient/identity/v2_0/catalog.py b/openstackclient/identity/v2_0/catalog.py index 33692a0d6c..3210df76a5 100644 --- a/openstackclient/identity/v2_0/catalog.py +++ b/openstackclient/identity/v2_0/catalog.py @@ -13,10 +13,10 @@ """Identity v2 Service Catalog action implementations""" +from osc_lib import exceptions import six from openstackclient.common import command -from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/identity/v2_0/role.py b/openstackclient/identity/v2_0/role.py index 0f8da99249..8914abfde5 100644 --- a/openstackclient/identity/v2_0/role.py +++ b/openstackclient/identity/v2_0/role.py @@ -15,12 +15,11 @@ """Identity v2 Role action implementations""" -import six - from keystoneauth1 import exceptions as ks_exc +from osc_lib import exceptions +import six from openstackclient.common import command -from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/identity/v2_0/service.py b/openstackclient/identity/v2_0/service.py index 7fe66d91ee..1293afc7b6 100644 --- a/openstackclient/identity/v2_0/service.py +++ b/openstackclient/identity/v2_0/service.py @@ -16,10 +16,11 @@ """Service action implementations""" import argparse + +from osc_lib import exceptions import six from openstackclient.common import command -from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v3/catalog.py b/openstackclient/identity/v3/catalog.py index c2b4359d28..202c3a3f51 100644 --- a/openstackclient/identity/v3/catalog.py +++ b/openstackclient/identity/v3/catalog.py @@ -13,10 +13,10 @@ """Identity v3 Service Catalog action implementations""" +from osc_lib import exceptions import six from openstackclient.common import command -from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/identity/v3/mapping.py b/openstackclient/identity/v3/mapping.py index c45796e482..acf3a081a0 100644 --- a/openstackclient/identity/v3/mapping.py +++ b/openstackclient/identity/v3/mapping.py @@ -19,8 +19,9 @@ import six +from osc_lib import exceptions + from openstackclient.common import command -from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/identity/v3/token.py b/openstackclient/identity/v3/token.py index cc3993631b..5a47dfdd39 100644 --- a/openstackclient/identity/v3/token.py +++ b/openstackclient/identity/v3/token.py @@ -17,8 +17,9 @@ import six +from osc_lib import exceptions + from openstackclient.common import command -from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v3/unscoped_saml.py b/openstackclient/identity/v3/unscoped_saml.py index 5cb8e4868d..6eee27d2c3 100644 --- a/openstackclient/identity/v3/unscoped_saml.py +++ b/openstackclient/identity/v3/unscoped_saml.py @@ -17,8 +17,9 @@ the user can list domains and projects they are allowed to access, and request a scoped token.""" +from osc_lib import exceptions + from openstackclient.common import command -from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index fa1de42419..f23ba5e2aa 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -19,10 +19,10 @@ import six from glanceclient.common import utils as gc_utils +from osc_lib import exceptions from openstackclient.api import utils as api_utils from openstackclient.common import command -from openstackclient.common import exceptions from openstackclient.common import parseractions from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/network/common.py b/openstackclient/network/common.py index 4028ac0db2..10c61ab1b8 100644 --- a/openstackclient/network/common.py +++ b/openstackclient/network/common.py @@ -12,10 +12,11 @@ # import abc + +from osc_lib import exceptions import six from openstackclient.common import command -from openstackclient.common import exceptions from openstackclient.i18n import _ diff --git a/openstackclient/network/v2/address_scope.py b/openstackclient/network/v2/address_scope.py index 40a0b8f692..9b670ea48d 100644 --- a/openstackclient/network/v2/address_scope.py +++ b/openstackclient/network/v2/address_scope.py @@ -13,8 +13,9 @@ """Address scope action implementations""" +from osc_lib import exceptions + from openstackclient.common import command -from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index bc9321d14d..ae6d2dffc1 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -16,8 +16,9 @@ import argparse import logging +from osc_lib import exceptions + from openstackclient.common import command -from openstackclient.common import exceptions from openstackclient.common import parseractions from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index 5abe9b9d8d..a7f4fa7e70 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -14,14 +14,15 @@ """Security Group Rule action implementations""" import argparse -import six try: from novaclient.v2 import security_group_rules as compute_secgroup_rules except ImportError: from novaclient.v1_1 import security_group_rules as compute_secgroup_rules -from openstackclient.common import exceptions +from osc_lib import exceptions +import six + from openstackclient.common import parseractions from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 8e378c7e19..95c7e1aea5 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -12,10 +12,12 @@ # """Subnet action implementations""" + import copy +from osc_lib import exceptions + from openstackclient.common import command -from openstackclient.common import exceptions from openstackclient.common import parseractions from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 9179ad019e..f2881d004d 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -26,13 +26,13 @@ from cliff import command from cliff import complete from cliff import help +from osc_lib import exceptions as exc from oslo_utils import importutils from oslo_utils import strutils import openstackclient from openstackclient.common import clientmanager from openstackclient.common import commandmanager -from openstackclient.common import exceptions as exc from openstackclient.common import logs from openstackclient.common import timing from openstackclient.common import utils diff --git a/openstackclient/tests/api/test_api.py b/openstackclient/tests/api/test_api.py index 8119796751..b444d9f1f3 100644 --- a/openstackclient/tests/api/test_api.py +++ b/openstackclient/tests/api/test_api.py @@ -13,8 +13,9 @@ """Base API Library Tests""" +from osc_lib import exceptions + from openstackclient.api import api -from openstackclient.common import exceptions from openstackclient.tests.api import fakes as api_fakes diff --git a/openstackclient/tests/common/test_clientmanager.py b/openstackclient/tests/common/test_clientmanager.py index 33485b00a9..520cce1330 100644 --- a/openstackclient/tests/common/test_clientmanager.py +++ b/openstackclient/tests/common/test_clientmanager.py @@ -15,15 +15,15 @@ import json as jsonutils import mock -from requests_mock.contrib import fixture from keystoneauth1.access import service_catalog from keystoneauth1.identity import v2 as auth_v2 from keystoneauth1 import token_endpoint +from osc_lib import exceptions as exc +from requests_mock.contrib import fixture from openstackclient.api import auth from openstackclient.common import clientmanager -from openstackclient.common import exceptions as exc from openstackclient.tests import fakes from openstackclient.tests import utils diff --git a/openstackclient/tests/common/test_command.py b/openstackclient/tests/common/test_command.py index 722a4c06ad..658bc895c0 100644 --- a/openstackclient/tests/common/test_command.py +++ b/openstackclient/tests/common/test_command.py @@ -14,8 +14,9 @@ import mock +from osc_lib import exceptions + from openstackclient.common import command -from openstackclient.common import exceptions from openstackclient.tests import fakes as test_fakes from openstackclient.tests import utils as test_utils diff --git a/openstackclient/tests/common/test_utils.py b/openstackclient/tests/common/test_utils.py index 2248d0430e..e521530aef 100644 --- a/openstackclient/tests/common/test_utils.py +++ b/openstackclient/tests/common/test_utils.py @@ -18,7 +18,8 @@ import mock -from openstackclient.common import exceptions +from osc_lib import exceptions + from openstackclient.common import utils from openstackclient.tests import fakes from openstackclient.tests import utils as test_utils diff --git a/openstackclient/tests/compute/v2/test_agent.py b/openstackclient/tests/compute/v2/test_agent.py index d3d1ff29e0..b9040ba903 100644 --- a/openstackclient/tests/compute/v2/test_agent.py +++ b/openstackclient/tests/compute/v2/test_agent.py @@ -16,7 +16,8 @@ import mock from mock import call -from openstackclient.common import exceptions +from osc_lib import exceptions + from openstackclient.compute.v2 import agent from openstackclient.tests.compute.v2 import fakes as compute_fakes diff --git a/openstackclient/tests/compute/v2/test_flavor.py b/openstackclient/tests/compute/v2/test_flavor.py index 434d5f92be..139585c284 100644 --- a/openstackclient/tests/compute/v2/test_flavor.py +++ b/openstackclient/tests/compute/v2/test_flavor.py @@ -15,7 +15,8 @@ import copy -from openstackclient.common import exceptions +from osc_lib import exceptions + from openstackclient.common import utils from openstackclient.compute.v2 import flavor from openstackclient.tests.compute.v2 import fakes as compute_fakes diff --git a/openstackclient/tests/compute/v2/test_hypervisor.py b/openstackclient/tests/compute/v2/test_hypervisor.py index 8d717ba7f3..ee0f40ed94 100644 --- a/openstackclient/tests/compute/v2/test_hypervisor.py +++ b/openstackclient/tests/compute/v2/test_hypervisor.py @@ -15,7 +15,8 @@ import copy -from openstackclient.common import exceptions +from osc_lib import exceptions + from openstackclient.compute.v2 import hypervisor from openstackclient.tests.compute.v2 import fakes as compute_fakes from openstackclient.tests import fakes diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index d642e6dd59..bd73624843 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -14,9 +14,10 @@ # import getpass import mock - from mock import call -from openstackclient.common import exceptions + +from osc_lib import exceptions + from openstackclient.common import utils as common_utils from openstackclient.compute.v2 import server from openstackclient.tests.compute.v2 import fakes as compute_fakes diff --git a/openstackclient/tests/compute/v2/test_server_backup.py b/openstackclient/tests/compute/v2/test_server_backup.py index b35f9f52a8..3495d1b858 100644 --- a/openstackclient/tests/compute/v2/test_server_backup.py +++ b/openstackclient/tests/compute/v2/test_server_backup.py @@ -13,7 +13,8 @@ import mock -from openstackclient.common import exceptions +from osc_lib import exceptions + from openstackclient.common import utils as common_utils from openstackclient.compute.v2 import server_backup from openstackclient.tests.compute.v2 import fakes as compute_fakes diff --git a/openstackclient/tests/compute/v2/test_server_group.py b/openstackclient/tests/compute/v2/test_server_group.py index 70ff23f9dc..4e0dbe4571 100644 --- a/openstackclient/tests/compute/v2/test_server_group.py +++ b/openstackclient/tests/compute/v2/test_server_group.py @@ -15,7 +15,8 @@ import mock -from openstackclient.common import exceptions +from osc_lib import exceptions + from openstackclient.common import utils from openstackclient.compute.v2 import server_group from openstackclient.tests.compute.v2 import fakes as compute_fakes diff --git a/openstackclient/tests/compute/v2/test_service.py b/openstackclient/tests/compute/v2/test_service.py index cf53497878..3f7413408e 100644 --- a/openstackclient/tests/compute/v2/test_service.py +++ b/openstackclient/tests/compute/v2/test_service.py @@ -13,7 +13,8 @@ # under the License. # -from openstackclient.common import exceptions +from osc_lib import exceptions + from openstackclient.compute.v2 import service from openstackclient.tests.compute.v2 import fakes as compute_fakes diff --git a/openstackclient/tests/identity/v2_0/test_role.py b/openstackclient/tests/identity/v2_0/test_role.py index 486a4a2ab4..7241c7bddf 100644 --- a/openstackclient/tests/identity/v2_0/test_role.py +++ b/openstackclient/tests/identity/v2_0/test_role.py @@ -17,8 +17,8 @@ import mock from keystoneauth1 import exceptions as ks_exc +from osc_lib import exceptions -from openstackclient.common import exceptions from openstackclient.identity.v2_0 import role from openstackclient.tests import fakes from openstackclient.tests.identity.v2_0 import fakes as identity_fakes diff --git a/openstackclient/tests/identity/v3/test_mappings.py b/openstackclient/tests/identity/v3/test_mappings.py index b9e3b1d5d6..af7b135d0e 100644 --- a/openstackclient/tests/identity/v3/test_mappings.py +++ b/openstackclient/tests/identity/v3/test_mappings.py @@ -16,7 +16,8 @@ import mock -from openstackclient.common import exceptions +from osc_lib import exceptions + from openstackclient.identity.v3 import mapping from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes diff --git a/openstackclient/tests/identity/v3/test_project.py b/openstackclient/tests/identity/v3/test_project.py index 1e9d1c8bf1..5be4e38932 100644 --- a/openstackclient/tests/identity/v3/test_project.py +++ b/openstackclient/tests/identity/v3/test_project.py @@ -16,7 +16,8 @@ import copy import mock -from openstackclient.common import exceptions +from osc_lib import exceptions + from openstackclient.identity.v3 import project from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes diff --git a/openstackclient/tests/identity/v3/test_unscoped_saml.py b/openstackclient/tests/identity/v3/test_unscoped_saml.py index d12cb454df..62623902b1 100644 --- a/openstackclient/tests/identity/v3/test_unscoped_saml.py +++ b/openstackclient/tests/identity/v3/test_unscoped_saml.py @@ -12,7 +12,8 @@ import copy -from openstackclient.common import exceptions +from osc_lib import exceptions + from openstackclient.identity.v3 import unscoped_saml from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes diff --git a/openstackclient/tests/image/v1/test_image.py b/openstackclient/tests/image/v1/test_image.py index 018e119933..b0436b9a9c 100644 --- a/openstackclient/tests/image/v1/test_image.py +++ b/openstackclient/tests/image/v1/test_image.py @@ -16,7 +16,8 @@ import copy import mock -from openstackclient.common import exceptions +from osc_lib import exceptions + from openstackclient.image.v1 import image from openstackclient.tests import fakes from openstackclient.tests.image.v1 import fakes as image_fakes diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index ca20d83da4..d8693df0f7 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -19,7 +19,8 @@ import warlock from glanceclient.v2 import schemas -from openstackclient.common import exceptions +from osc_lib import exceptions + from openstackclient.common import utils as common_utils from openstackclient.image.v2 import image from openstackclient.tests import fakes diff --git a/openstackclient/tests/network/v2/test_address_scope.py b/openstackclient/tests/network/v2/test_address_scope.py index d02f01ca24..722371f9d9 100644 --- a/openstackclient/tests/network/v2/test_address_scope.py +++ b/openstackclient/tests/network/v2/test_address_scope.py @@ -15,7 +15,8 @@ import mock from mock import call -from openstackclient.common import exceptions +from osc_lib import exceptions + from openstackclient.network.v2 import address_scope from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes_v3 diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index 509259a8ed..581e927885 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -13,9 +13,10 @@ import copy import mock - from mock import call -from openstackclient.common import exceptions + +from osc_lib import exceptions + from openstackclient.common import utils from openstackclient.network.v2 import network from openstackclient.tests.compute.v2 import fakes as compute_fakes diff --git a/openstackclient/tests/network/v2/test_network_segment.py b/openstackclient/tests/network/v2/test_network_segment.py index 0a99eced69..a635d84597 100644 --- a/openstackclient/tests/network/v2/test_network_segment.py +++ b/openstackclient/tests/network/v2/test_network_segment.py @@ -13,7 +13,8 @@ import mock -from openstackclient.common import exceptions +from osc_lib import exceptions + from openstackclient.network.v2 import network_segment from openstackclient.tests.network.v2 import fakes as network_fakes from openstackclient.tests import utils as tests_utils diff --git a/openstackclient/tests/network/v2/test_port.py b/openstackclient/tests/network/v2/test_port.py index 628a5d52cb..02c7d4a4cc 100644 --- a/openstackclient/tests/network/v2/test_port.py +++ b/openstackclient/tests/network/v2/test_port.py @@ -14,7 +14,8 @@ import mock from mock import call -from openstackclient.common import exceptions +from osc_lib import exceptions + from openstackclient.common import utils from openstackclient.network.v2 import port from openstackclient.tests.network.v2 import fakes as network_fakes diff --git a/openstackclient/tests/network/v2/test_security_group_rule.py b/openstackclient/tests/network/v2/test_security_group_rule.py index 2a64b88442..b1f2209d39 100644 --- a/openstackclient/tests/network/v2/test_security_group_rule.py +++ b/openstackclient/tests/network/v2/test_security_group_rule.py @@ -14,7 +14,8 @@ import copy import mock -from openstackclient.common import exceptions +from osc_lib import exceptions + from openstackclient.network import utils as network_utils from openstackclient.network.v2 import security_group_rule from openstackclient.tests.compute.v2 import fakes as compute_fakes diff --git a/openstackclient/tests/volume/test_find_resource.py b/openstackclient/tests/volume/test_find_resource.py index 00cc46a6ed..d29a955db4 100644 --- a/openstackclient/tests/volume/test_find_resource.py +++ b/openstackclient/tests/volume/test_find_resource.py @@ -17,8 +17,8 @@ from cinderclient.v1 import volume_snapshots from cinderclient.v1 import volumes +from osc_lib import exceptions -from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.tests import utils as test_utils from openstackclient.volume import client # noqa diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index 9aed17bc79..e2e1c53ee8 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -14,10 +14,10 @@ """Volume v2 Type action implementations""" +from osc_lib import exceptions import six from openstackclient.common import command -from openstackclient.common import exceptions from openstackclient.common import parseractions from openstackclient.common import utils from openstackclient.i18n import _ From e5e29a8fef7ba2396015918545a49e717fe75d15 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 13 May 2016 16:14:09 -0500 Subject: [PATCH 0981/3095] osc-lib: utils Use osc-lib directly for utils. Leave openstackclient.common.utils for deprecation period. Change-Id: I5bd9579abc4e07f45219ccd0565626e6667472f7 --- doc/source/plugins.rst | 4 +- openstackclient/api/auth.py | 2 +- openstackclient/api/object_store_v1.py | 3 +- openstackclient/common/availability_zone.py | 2 +- openstackclient/common/extension.py | 3 +- openstackclient/common/limits.py | 3 +- openstackclient/common/module.py | 5 +- openstackclient/common/quota.py | 5 +- openstackclient/compute/client.py | 3 +- openstackclient/compute/v2/agent.py | 2 +- openstackclient/compute/v2/aggregate.py | 2 +- openstackclient/compute/v2/console.py | 5 +- openstackclient/compute/v2/fixedip.py | 3 +- openstackclient/compute/v2/flavor.py | 2 +- openstackclient/compute/v2/floatingip.py | 3 +- openstackclient/compute/v2/floatingippool.py | 3 +- openstackclient/compute/v2/host.py | 3 +- openstackclient/compute/v2/hypervisor.py | 3 +- openstackclient/compute/v2/keypair.py | 2 +- openstackclient/compute/v2/server.py | 2 +- openstackclient/compute/v2/server_backup.py | 2 +- openstackclient/compute/v2/server_group.py | 2 +- openstackclient/compute/v2/service.py | 2 +- openstackclient/compute/v2/usage.py | 2 +- openstackclient/identity/client.py | 3 +- openstackclient/identity/common.py | 3 +- openstackclient/identity/v2_0/catalog.py | 2 +- openstackclient/identity/v2_0/ec2creds.py | 2 +- openstackclient/identity/v2_0/endpoint.py | 2 +- openstackclient/identity/v2_0/project.py | 5 +- openstackclient/identity/v2_0/role.py | 2 +- openstackclient/identity/v2_0/service.py | 2 +- openstackclient/identity/v2_0/user.py | 5 +- openstackclient/identity/v3/catalog.py | 2 +- openstackclient/identity/v3/consumer.py | 5 +- openstackclient/identity/v3/credential.py | 2 +- openstackclient/identity/v3/domain.py | 4 +- openstackclient/identity/v3/ec2creds.py | 2 +- openstackclient/identity/v3/endpoint.py | 5 +- .../identity/v3/federation_protocol.py | 2 +- openstackclient/identity/v3/group.py | 4 +- .../identity/v3/identity_provider.py | 2 +- openstackclient/identity/v3/mapping.py | 5 +- openstackclient/identity/v3/policy.py | 5 +- openstackclient/identity/v3/project.py | 5 +- openstackclient/identity/v3/region.py | 2 +- openstackclient/identity/v3/role.py | 4 +- .../identity/v3/role_assignment.py | 3 +- openstackclient/identity/v3/service.py | 5 +- .../identity/v3/service_provider.py | 5 +- openstackclient/identity/v3/token.py | 5 +- openstackclient/identity/v3/trust.py | 3 +- openstackclient/identity/v3/unscoped_saml.py | 2 +- openstackclient/identity/v3/user.py | 4 +- openstackclient/image/client.py | 3 +- openstackclient/image/v1/image.py | 3 +- openstackclient/image/v2/image.py | 4 +- openstackclient/network/client.py | 2 +- openstackclient/network/v2/address_scope.py | 2 +- openstackclient/network/v2/floating_ip.py | 3 +- openstackclient/network/v2/ip_availability.py | 3 +- openstackclient/network/v2/network.py | 3 +- openstackclient/network/v2/network_segment.py | 3 +- openstackclient/network/v2/port.py | 2 +- openstackclient/network/v2/router.py | 3 +- openstackclient/network/v2/security_group.py | 3 +- .../network/v2/security_group_rule.py | 2 +- openstackclient/network/v2/subnet.py | 2 +- openstackclient/network/v2/subnet_pool.py | 3 +- openstackclient/object/client.py | 3 +- openstackclient/object/v1/account.py | 2 +- openstackclient/object/v1/container.py | 2 +- openstackclient/object/v1/object.py | 2 +- openstackclient/shell.py | 2 +- openstackclient/tests/common/test_utils.py | 401 ------------------ .../tests/compute/v2/test_flavor.py | 2 +- .../tests/compute/v2/test_server.py | 4 +- .../tests/compute/v2/test_server_backup.py | 2 +- .../tests/compute/v2/test_server_group.py | 2 +- .../tests/identity/v2_0/test_user.py | 4 +- .../tests/identity/v3/test_project.py | 4 +- .../tests/identity/v3/test_user.py | 6 +- openstackclient/tests/image/v1/test_image.py | 2 +- openstackclient/tests/image/v2/fakes.py | 5 +- openstackclient/tests/image/v2/test_image.py | 6 +- .../tests/network/v2/test_ip_availability.py | 5 +- .../tests/network/v2/test_network.py | 2 +- openstackclient/tests/network/v2/test_port.py | 2 +- .../tests/network/v2/test_router.py | 3 +- .../tests/network/v2/test_subnet.py | 3 +- .../tests/network/v2/test_subnet_pool.py | 3 +- .../tests/volume/test_find_resource.py | 2 +- .../tests/volume/v1/test_qos_specs.py | 3 +- openstackclient/tests/volume/v2/fakes.py | 3 +- .../tests/volume/v2/test_qos_specs.py | 2 +- .../tests/volume/v2/test_snapshot.py | 3 +- openstackclient/tests/volume/v2/test_type.py | 3 +- .../tests/volume/v2/test_volume.py | 3 +- openstackclient/volume/client.py | 3 +- openstackclient/volume/v1/backup.py | 3 +- openstackclient/volume/v1/qos_specs.py | 2 +- openstackclient/volume/v1/service.py | 3 +- openstackclient/volume/v1/snapshot.py | 3 +- openstackclient/volume/v1/volume.py | 3 +- .../volume/v1/volume_transfer_request.py | 2 +- openstackclient/volume/v1/volume_type.py | 2 +- openstackclient/volume/v2/backup.py | 2 +- openstackclient/volume/v2/qos_specs.py | 2 +- openstackclient/volume/v2/service.py | 3 +- openstackclient/volume/v2/snapshot.py | 2 +- openstackclient/volume/v2/volume.py | 2 +- .../volume/v2/volume_transfer_request.py | 2 +- openstackclient/volume/v2/volume_type.py | 2 +- 113 files changed, 185 insertions(+), 548 deletions(-) delete mode 100644 openstackclient/tests/common/test_utils.py diff --git a/doc/source/plugins.rst b/doc/source/plugins.rst index de3018b9a4..27846318b3 100644 --- a/doc/source/plugins.rst +++ b/doc/source/plugins.rst @@ -95,7 +95,7 @@ so the version should not contain the leading 'v' character. .. code-block:: python - from openstackclient.common import utils + from osc_lib import utils DEFAULT_API_VERSION = '1' @@ -153,12 +153,12 @@ the plugin commands: # osc-lib interfaces available to plugins: from osc_lib import exceptions + from osc_lib import utils # OSC common interfaces available to plugins: from openstackclient.common import command from openstackclient.common import parseractions from openstackclient.common import logs - from openstackclient.common import utils class DeleteMypluginobject(command.Command): diff --git a/openstackclient/api/auth.py b/openstackclient/api/auth.py index f1e53c49f0..a55af29389 100644 --- a/openstackclient/api/auth.py +++ b/openstackclient/api/auth.py @@ -18,8 +18,8 @@ from keystoneauth1.loading import base from osc_lib import exceptions as exc +from osc_lib import utils -from openstackclient.common import utils from openstackclient.i18n import _ LOG = logging.getLogger(__name__) diff --git a/openstackclient/api/object_store_v1.py b/openstackclient/api/object_store_v1.py index 632e8b19a7..ae49922ded 100644 --- a/openstackclient/api/object_store_v1.py +++ b/openstackclient/api/object_store_v1.py @@ -20,8 +20,9 @@ import six from six.moves import urllib +from osc_lib import utils + from openstackclient.api import api -from openstackclient.common import utils class APIv1(api.BaseAPI): diff --git a/openstackclient/common/availability_zone.py b/openstackclient/common/availability_zone.py index df30313741..6117c918a0 100644 --- a/openstackclient/common/availability_zone.py +++ b/openstackclient/common/availability_zone.py @@ -16,10 +16,10 @@ import copy from novaclient import exceptions as nova_exceptions +from osc_lib import utils import six from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/common/extension.py b/openstackclient/common/extension.py index ab46e7d8b0..6fcf9fa80d 100644 --- a/openstackclient/common/extension.py +++ b/openstackclient/common/extension.py @@ -17,8 +17,9 @@ import itertools +from osc_lib import utils + from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/common/limits.py b/openstackclient/common/limits.py index 939b9efb3e..249d1b443d 100644 --- a/openstackclient/common/limits.py +++ b/openstackclient/common/limits.py @@ -17,8 +17,9 @@ import itertools +from osc_lib import utils + from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common diff --git a/openstackclient/common/module.py b/openstackclient/common/module.py index 11895f7aca..91e779fb28 100644 --- a/openstackclient/common/module.py +++ b/openstackclient/common/module.py @@ -15,11 +15,12 @@ """Module action implementation""" -import six import sys +from osc_lib import utils +import six + from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index 67e442b324..6fd1e2f5cc 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -16,11 +16,12 @@ """Quota action implementations""" import itertools -import six import sys +from osc_lib import utils +import six + from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/compute/client.py b/openstackclient/compute/client.py index aa4c431f7d..a83700dba7 100644 --- a/openstackclient/compute/client.py +++ b/openstackclient/compute/client.py @@ -16,10 +16,11 @@ import logging from osc_lib import exceptions +from osc_lib import utils -from openstackclient.common import utils from openstackclient.i18n import _ + LOG = logging.getLogger(__name__) DEFAULT_API_VERSION = '2' diff --git a/openstackclient/compute/v2/agent.py b/openstackclient/compute/v2/agent.py index 7b1a301e14..5f7050761c 100644 --- a/openstackclient/compute/v2/agent.py +++ b/openstackclient/compute/v2/agent.py @@ -16,10 +16,10 @@ """Agent action implementations""" from osc_lib import exceptions +from osc_lib import utils import six from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/compute/v2/aggregate.py b/openstackclient/compute/v2/aggregate.py index 752e0fdfbe..69227b0c9b 100644 --- a/openstackclient/compute/v2/aggregate.py +++ b/openstackclient/compute/v2/aggregate.py @@ -16,11 +16,11 @@ """Compute v2 Aggregate action implementations""" +from osc_lib import utils import six from openstackclient.common import command from openstackclient.common import parseractions -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/compute/v2/console.py b/openstackclient/compute/v2/console.py index 1165862c91..ee16801663 100644 --- a/openstackclient/compute/v2/console.py +++ b/openstackclient/compute/v2/console.py @@ -15,12 +15,13 @@ """Compute v2 Console action implementations""" -import six import sys +from osc_lib import utils +import six + from openstackclient.common import command from openstackclient.common import parseractions -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/compute/v2/fixedip.py b/openstackclient/compute/v2/fixedip.py index daac97d1be..e3b80c5f39 100644 --- a/openstackclient/compute/v2/fixedip.py +++ b/openstackclient/compute/v2/fixedip.py @@ -15,8 +15,9 @@ """Fixed IP action implementations""" +from osc_lib import utils + from openstackclient.common import command -from openstackclient.common import utils class AddFixedIP(command.Command): diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 7761ab4331..7cca37e594 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -16,11 +16,11 @@ """Flavor action implementations""" from osc_lib import exceptions +from osc_lib import utils import six from openstackclient.common import command from openstackclient.common import parseractions -from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common diff --git a/openstackclient/compute/v2/floatingip.py b/openstackclient/compute/v2/floatingip.py index fac4d2e361..780ea68474 100644 --- a/openstackclient/compute/v2/floatingip.py +++ b/openstackclient/compute/v2/floatingip.py @@ -15,8 +15,9 @@ """Floating IP action implementations""" +from osc_lib import utils + from openstackclient.common import command -from openstackclient.common import utils class AddFloatingIP(command.Command): diff --git a/openstackclient/compute/v2/floatingippool.py b/openstackclient/compute/v2/floatingippool.py index 997e03247a..eb7c9e86be 100644 --- a/openstackclient/compute/v2/floatingippool.py +++ b/openstackclient/compute/v2/floatingippool.py @@ -15,8 +15,9 @@ """Floating IP Pool action implementations""" +from osc_lib import utils + from openstackclient.common import command -from openstackclient.common import utils class ListFloatingIPPool(command.Lister): diff --git a/openstackclient/compute/v2/host.py b/openstackclient/compute/v2/host.py index 73e2cdf9ab..15396d0cb7 100644 --- a/openstackclient/compute/v2/host.py +++ b/openstackclient/compute/v2/host.py @@ -15,8 +15,9 @@ """Host action implementations""" +from osc_lib import utils + from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/compute/v2/hypervisor.py b/openstackclient/compute/v2/hypervisor.py index 333a7dea4d..b13cdb4741 100644 --- a/openstackclient/compute/v2/hypervisor.py +++ b/openstackclient/compute/v2/hypervisor.py @@ -16,10 +16,11 @@ """Hypervisor action implementations""" import re + +from osc_lib import utils import six from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/compute/v2/keypair.py b/openstackclient/compute/v2/keypair.py index 3810c1a38d..acf467b303 100644 --- a/openstackclient/compute/v2/keypair.py +++ b/openstackclient/compute/v2/keypair.py @@ -20,10 +20,10 @@ import sys from osc_lib import exceptions +from osc_lib import utils import six from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index ee59bb340b..7e810898b7 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -22,6 +22,7 @@ import sys from osc_lib import exceptions +from osc_lib import utils import six try: @@ -31,7 +32,6 @@ from openstackclient.common import command from openstackclient.common import parseractions -from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common diff --git a/openstackclient/compute/v2/server_backup.py b/openstackclient/compute/v2/server_backup.py index f0b3edc587..972db66099 100644 --- a/openstackclient/compute/v2/server_backup.py +++ b/openstackclient/compute/v2/server_backup.py @@ -18,11 +18,11 @@ import sys from osc_lib import exceptions +from osc_lib import utils from oslo_utils import importutils import six from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/compute/v2/server_group.py b/openstackclient/compute/v2/server_group.py index 1b817018ea..5763ba2fac 100644 --- a/openstackclient/compute/v2/server_group.py +++ b/openstackclient/compute/v2/server_group.py @@ -16,9 +16,9 @@ """Compute v2 Server Group action implementations""" from osc_lib import exceptions +from osc_lib import utils from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index 2a589f6b1d..206344dc9a 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.py @@ -16,9 +16,9 @@ """Service action implementations""" from osc_lib import exceptions +from osc_lib import utils from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/compute/v2/usage.py b/openstackclient/compute/v2/usage.py index b83bef13df..cc24346f89 100644 --- a/openstackclient/compute/v2/usage.py +++ b/openstackclient/compute/v2/usage.py @@ -18,10 +18,10 @@ import datetime import sys +from osc_lib import utils import six from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/identity/client.py b/openstackclient/identity/client.py index c166e66a81..1868b2249d 100644 --- a/openstackclient/identity/client.py +++ b/openstackclient/identity/client.py @@ -16,8 +16,9 @@ import logging from keystoneclient.v2_0 import client as identity_client_v2 +from osc_lib import utils + from openstackclient.api import auth -from openstackclient.common import utils LOG = logging.getLogger(__name__) diff --git a/openstackclient/identity/common.py b/openstackclient/identity/common.py index 5a65c47cbe..16ee63e162 100644 --- a/openstackclient/identity/common.py +++ b/openstackclient/identity/common.py @@ -21,8 +21,7 @@ from keystoneclient.v3 import projects from keystoneclient.v3 import users from osc_lib import exceptions - -from openstackclient.common import utils +from osc_lib import utils def find_service(identity_client, name_type_or_id): diff --git a/openstackclient/identity/v2_0/catalog.py b/openstackclient/identity/v2_0/catalog.py index 3210df76a5..2de1e8e7a2 100644 --- a/openstackclient/identity/v2_0/catalog.py +++ b/openstackclient/identity/v2_0/catalog.py @@ -14,10 +14,10 @@ """Identity v2 Service Catalog action implementations""" from osc_lib import exceptions +from osc_lib import utils import six from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/identity/v2_0/ec2creds.py b/openstackclient/identity/v2_0/ec2creds.py index dfd675913d..86f07bce8f 100644 --- a/openstackclient/identity/v2_0/ec2creds.py +++ b/openstackclient/identity/v2_0/ec2creds.py @@ -16,10 +16,10 @@ """Identity v2 EC2 Credentials action implementations""" +from osc_lib import utils import six from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/identity/v2_0/endpoint.py b/openstackclient/identity/v2_0/endpoint.py index 09ea738f4f..89c8a38ba9 100644 --- a/openstackclient/identity/v2_0/endpoint.py +++ b/openstackclient/identity/v2_0/endpoint.py @@ -15,10 +15,10 @@ """Endpoint action implementations""" +from osc_lib import utils import six from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v2_0/project.py b/openstackclient/identity/v2_0/project.py index 80f88d739f..688849fa69 100644 --- a/openstackclient/identity/v2_0/project.py +++ b/openstackclient/identity/v2_0/project.py @@ -15,13 +15,12 @@ """Identity v2 Project action implementations""" -import six - from keystoneauth1 import exceptions as ks_exc +from osc_lib import utils +import six from openstackclient.common import command from openstackclient.common import parseractions -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/identity/v2_0/role.py b/openstackclient/identity/v2_0/role.py index 8914abfde5..075234fcdb 100644 --- a/openstackclient/identity/v2_0/role.py +++ b/openstackclient/identity/v2_0/role.py @@ -17,10 +17,10 @@ from keystoneauth1 import exceptions as ks_exc from osc_lib import exceptions +from osc_lib import utils import six from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/identity/v2_0/service.py b/openstackclient/identity/v2_0/service.py index 1293afc7b6..631b873798 100644 --- a/openstackclient/identity/v2_0/service.py +++ b/openstackclient/identity/v2_0/service.py @@ -18,10 +18,10 @@ import argparse from osc_lib import exceptions +from osc_lib import utils import six from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v2_0/user.py b/openstackclient/identity/v2_0/user.py index f8f5df2997..a10d213b35 100644 --- a/openstackclient/identity/v2_0/user.py +++ b/openstackclient/identity/v2_0/user.py @@ -15,12 +15,11 @@ """Identity v2.0 User action implementations""" -import six - from keystoneauth1 import exceptions as ks_exc +from osc_lib import utils +import six from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/identity/v3/catalog.py b/openstackclient/identity/v3/catalog.py index 202c3a3f51..a3286003b8 100644 --- a/openstackclient/identity/v3/catalog.py +++ b/openstackclient/identity/v3/catalog.py @@ -14,10 +14,10 @@ """Identity v3 Service Catalog action implementations""" from osc_lib import exceptions +from osc_lib import utils import six from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/identity/v3/consumer.py b/openstackclient/identity/v3/consumer.py index a062b74381..8aa92f6de2 100644 --- a/openstackclient/identity/v3/consumer.py +++ b/openstackclient/identity/v3/consumer.py @@ -15,11 +15,12 @@ """Identity v3 Consumer action implementations""" -import six import sys +from osc_lib import utils +import six + from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/identity/v3/credential.py b/openstackclient/identity/v3/credential.py index 9901347852..6c65a20658 100644 --- a/openstackclient/identity/v3/credential.py +++ b/openstackclient/identity/v3/credential.py @@ -15,10 +15,10 @@ """Identity v3 Credential action implementations""" +from osc_lib import utils import six from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/identity/v3/domain.py b/openstackclient/identity/v3/domain.py index c345028f8a..b0cd4d553f 100644 --- a/openstackclient/identity/v3/domain.py +++ b/openstackclient/identity/v3/domain.py @@ -15,13 +15,13 @@ """Identity v3 Domain action implementations""" -import six import sys from keystoneauth1 import exceptions as ks_exc +from osc_lib import utils +import six from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/identity/v3/ec2creds.py b/openstackclient/identity/v3/ec2creds.py index 859ec2a730..fe052a3c65 100644 --- a/openstackclient/identity/v3/ec2creds.py +++ b/openstackclient/identity/v3/ec2creds.py @@ -12,10 +12,10 @@ """Identity v3 EC2 Credentials action implementations""" +from osc_lib import utils import six from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v3/endpoint.py b/openstackclient/identity/v3/endpoint.py index 39022d27db..8e433fb9eb 100644 --- a/openstackclient/identity/v3/endpoint.py +++ b/openstackclient/identity/v3/endpoint.py @@ -15,11 +15,12 @@ """Identity v3 Endpoint action implementations""" -import six import sys +from osc_lib import utils +import six + from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v3/federation_protocol.py b/openstackclient/identity/v3/federation_protocol.py index c0f4bc93db..5050ec6f9a 100644 --- a/openstackclient/identity/v3/federation_protocol.py +++ b/openstackclient/identity/v3/federation_protocol.py @@ -14,10 +14,10 @@ """Identity v3 Protocols actions implementations""" +from osc_lib import utils import six from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py index fdb94da641..85087214cd 100644 --- a/openstackclient/identity/v3/group.py +++ b/openstackclient/identity/v3/group.py @@ -15,13 +15,13 @@ """Group action implementations""" -import six import sys from keystoneauth1 import exceptions as ks_exc +from osc_lib import utils +import six from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v3/identity_provider.py b/openstackclient/identity/v3/identity_provider.py index 3749aa35e5..a082f2f4f7 100644 --- a/openstackclient/identity/v3/identity_provider.py +++ b/openstackclient/identity/v3/identity_provider.py @@ -13,10 +13,10 @@ """Identity v3 IdentityProvider action implementations""" +from osc_lib import utils import six from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/identity/v3/mapping.py b/openstackclient/identity/v3/mapping.py index acf3a081a0..723e3bbc4e 100644 --- a/openstackclient/identity/v3/mapping.py +++ b/openstackclient/identity/v3/mapping.py @@ -17,12 +17,11 @@ import json -import six - from osc_lib import exceptions +from osc_lib import utils +import six from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/identity/v3/policy.py b/openstackclient/identity/v3/policy.py index 74a783b06a..f09920318c 100644 --- a/openstackclient/identity/v3/policy.py +++ b/openstackclient/identity/v3/policy.py @@ -15,11 +15,12 @@ """Identity v3 Policy action implementations""" -import six import sys +from osc_lib import utils +import six + from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index acf639f29d..2d504950fb 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -15,13 +15,12 @@ """Project action implementations""" -import six - from keystoneauth1 import exceptions as ks_exc +from osc_lib import utils +import six from openstackclient.common import command from openstackclient.common import parseractions -from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v3/region.py b/openstackclient/identity/v3/region.py index 053e4b31d2..da8e36325c 100644 --- a/openstackclient/identity/v3/region.py +++ b/openstackclient/identity/v3/region.py @@ -13,10 +13,10 @@ """Identity v3 Region action implementations""" +from osc_lib import utils import six from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index e7078f44c6..879dbc33d0 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -15,13 +15,13 @@ """Identity v3 Role action implementations""" -import six import sys from keystoneauth1 import exceptions as ks_exc +from osc_lib import utils +import six from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v3/role_assignment.py b/openstackclient/identity/v3/role_assignment.py index 521075fef5..5e1d997a31 100644 --- a/openstackclient/identity/v3/role_assignment.py +++ b/openstackclient/identity/v3/role_assignment.py @@ -13,8 +13,9 @@ """Identity v3 Assignment action implementations """ +from osc_lib import utils + from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v3/service.py b/openstackclient/identity/v3/service.py index 35507a63c4..450b066c14 100644 --- a/openstackclient/identity/v3/service.py +++ b/openstackclient/identity/v3/service.py @@ -15,11 +15,12 @@ """Identity v3 Service action implementations""" -import six import sys +from osc_lib import utils +import six + from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v3/service_provider.py b/openstackclient/identity/v3/service_provider.py index f1e9f35d07..5bb15463f9 100644 --- a/openstackclient/identity/v3/service_provider.py +++ b/openstackclient/identity/v3/service_provider.py @@ -13,11 +13,12 @@ """Service Provider action implementations""" -import six import sys +from osc_lib import utils +import six + from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/identity/v3/token.py b/openstackclient/identity/v3/token.py index 5a47dfdd39..0eefbf3b6e 100644 --- a/openstackclient/identity/v3/token.py +++ b/openstackclient/identity/v3/token.py @@ -15,12 +15,11 @@ """Identity v3 Token action implementations""" -import six - from osc_lib import exceptions +from osc_lib import utils +import six from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v3/trust.py b/openstackclient/identity/v3/trust.py index 336b703cbd..0f900250e7 100644 --- a/openstackclient/identity/v3/trust.py +++ b/openstackclient/identity/v3/trust.py @@ -14,10 +14,11 @@ """Identity v3 Trust action implementations""" import datetime + +from osc_lib import utils import six from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v3/unscoped_saml.py b/openstackclient/identity/v3/unscoped_saml.py index 6eee27d2c3..901fa26e37 100644 --- a/openstackclient/identity/v3/unscoped_saml.py +++ b/openstackclient/identity/v3/unscoped_saml.py @@ -18,9 +18,9 @@ a scoped token.""" from osc_lib import exceptions +from osc_lib import utils from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index a71b4b7808..b2e60f8f1b 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -16,13 +16,13 @@ """Identity v3 User action implementations""" import copy -import six import sys from keystoneauth1 import exceptions as ks_exc +from osc_lib import utils +import six from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/image/client.py b/openstackclient/image/client.py index 9c45a63ff0..0ef694f46d 100644 --- a/openstackclient/image/client.py +++ b/openstackclient/image/client.py @@ -15,7 +15,8 @@ import logging -from openstackclient.common import utils +from osc_lib import utils + from openstackclient.i18n import _ diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index e2109fca07..73fbe29051 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -27,10 +27,11 @@ msvcrt = None from glanceclient.common import utils as gc_utils +from osc_lib import utils + from openstackclient.api import utils as api_utils from openstackclient.common import command from openstackclient.common import parseractions -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index f23ba5e2aa..668e4ed90e 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -16,15 +16,15 @@ """Image V2 Action Implementations""" import argparse -import six from glanceclient.common import utils as gc_utils from osc_lib import exceptions +from osc_lib import utils +import six from openstackclient.api import utils as api_utils from openstackclient.common import command from openstackclient.common import parseractions -from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/network/client.py b/openstackclient/network/client.py index d711f4fcf5..c81b7d8768 100644 --- a/openstackclient/network/client.py +++ b/openstackclient/network/client.py @@ -15,8 +15,8 @@ from openstack import connection from openstack import profile +from osc_lib import utils -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/network/v2/address_scope.py b/openstackclient/network/v2/address_scope.py index 9b670ea48d..fc2ff3f345 100644 --- a/openstackclient/network/v2/address_scope.py +++ b/openstackclient/network/v2/address_scope.py @@ -14,9 +14,9 @@ """Address scope action implementations""" from osc_lib import exceptions +from osc_lib import utils from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index 21f8659910..c734c2ed03 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -13,7 +13,8 @@ """IP Floating action implementations""" -from openstackclient.common import utils +from osc_lib import utils + from openstackclient.i18n import _ from openstackclient.network import common diff --git a/openstackclient/network/v2/ip_availability.py b/openstackclient/network/v2/ip_availability.py index ff7b07c7c7..6974a24626 100644 --- a/openstackclient/network/v2/ip_availability.py +++ b/openstackclient/network/v2/ip_availability.py @@ -13,8 +13,9 @@ """IP Availability Info implementations""" +from osc_lib import utils + from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 87e65dad11..81e96fb254 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -13,8 +13,9 @@ """Network action implementations""" +from osc_lib import utils + from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common diff --git a/openstackclient/network/v2/network_segment.py b/openstackclient/network/v2/network_segment.py index 818ffc026f..e965d2ebb2 100644 --- a/openstackclient/network/v2/network_segment.py +++ b/openstackclient/network/v2/network_segment.py @@ -15,8 +15,9 @@ # TODO(rtheis): Add description and name properties when support is available. +from osc_lib import utils + from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index ae6d2dffc1..643207d9b3 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -17,10 +17,10 @@ import logging from osc_lib import exceptions +from osc_lib import utils from openstackclient.common import command from openstackclient.common import parseractions -from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 60908294a0..eab8b22ca6 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -17,9 +17,10 @@ import json import logging +from osc_lib import utils + from openstackclient.common import command from openstackclient.common import parseractions -from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py index 1ef2754e43..959718001b 100644 --- a/openstackclient/network/v2/security_group.py +++ b/openstackclient/network/v2/security_group.py @@ -14,9 +14,10 @@ """Security Group action implementations""" import argparse + +from osc_lib import utils import six -from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index a7f4fa7e70..0132ef4eb5 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -21,10 +21,10 @@ from novaclient.v1_1 import security_group_rules as compute_secgroup_rules from osc_lib import exceptions +from osc_lib import utils import six from openstackclient.common import parseractions -from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 95c7e1aea5..2b12ee03a0 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -16,10 +16,10 @@ import copy from osc_lib import exceptions +from osc_lib import utils from openstackclient.common import command from openstackclient.common import parseractions -from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index 17f1e97ddc..23993300fe 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -13,9 +13,10 @@ """Subnet pool action implementations""" +from osc_lib import utils + from openstackclient.common import command from openstackclient.common import parseractions -from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common diff --git a/openstackclient/object/client.py b/openstackclient/object/client.py index 3af6f8a0b7..cb3ddc288f 100644 --- a/openstackclient/object/client.py +++ b/openstackclient/object/client.py @@ -15,8 +15,9 @@ """Object client""" +from osc_lib import utils + from openstackclient.api import object_store_v1 -from openstackclient.common import utils DEFAULT_API_VERSION = '1' API_VERSION_OPTION = 'os_object_api_version' diff --git a/openstackclient/object/v1/account.py b/openstackclient/object/v1/account.py index 543ce4f3a0..189f086bb9 100644 --- a/openstackclient/object/v1/account.py +++ b/openstackclient/object/v1/account.py @@ -13,11 +13,11 @@ """Account v1 action implementations""" +from osc_lib import utils import six from openstackclient.common import command from openstackclient.common import parseractions -from openstackclient.common import utils class SetAccount(command.Command): diff --git a/openstackclient/object/v1/container.py b/openstackclient/object/v1/container.py index 80b8423824..b3c6af7498 100644 --- a/openstackclient/object/v1/container.py +++ b/openstackclient/object/v1/container.py @@ -16,11 +16,11 @@ """Container v1 action implementations""" +from osc_lib import utils import six from openstackclient.common import command from openstackclient.common import parseractions -from openstackclient.common import utils class CreateContainer(command.Lister): diff --git a/openstackclient/object/v1/object.py b/openstackclient/object/v1/object.py index f9a55e9c96..9141c1da45 100644 --- a/openstackclient/object/v1/object.py +++ b/openstackclient/object/v1/object.py @@ -16,11 +16,11 @@ """Object v1 action implementations""" +from osc_lib import utils import six from openstackclient.common import command from openstackclient.common import parseractions -from openstackclient.common import utils class CreateObject(command.Lister): diff --git a/openstackclient/shell.py b/openstackclient/shell.py index f2881d004d..2697128bd4 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -27,6 +27,7 @@ from cliff import complete from cliff import help from osc_lib import exceptions as exc +from osc_lib import utils from oslo_utils import importutils from oslo_utils import strutils @@ -35,7 +36,6 @@ from openstackclient.common import commandmanager from openstackclient.common import logs from openstackclient.common import timing -from openstackclient.common import utils from os_client_config import config as cloud_config diff --git a/openstackclient/tests/common/test_utils.py b/openstackclient/tests/common/test_utils.py deleted file mode 100644 index e521530aef..0000000000 --- a/openstackclient/tests/common/test_utils.py +++ /dev/null @@ -1,401 +0,0 @@ -# Copyright 2012-2013 OpenStack, LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - -import time -import uuid - -import mock - -from osc_lib import exceptions - -from openstackclient.common import utils -from openstackclient.tests import fakes -from openstackclient.tests import utils as test_utils - -PASSWORD = "Pa$$w0rd" -WASSPORD = "Wa$$p0rd" -DROWSSAP = "dr0w$$aP" - - -class FakeOddballResource(fakes.FakeResource): - - def get(self, attr): - """get() is needed for utils.find_resource()""" - if attr == 'id': - return self.id - elif attr == 'name': - return self.name - else: - return None - - -class TestUtils(test_utils.TestCase): - - def test_get_password_good(self): - with mock.patch("getpass.getpass", return_value=PASSWORD): - mock_stdin = mock.Mock() - mock_stdin.isatty = mock.Mock() - mock_stdin.isatty.return_value = True - self.assertEqual(PASSWORD, utils.get_password(mock_stdin)) - - def test_get_password_bad_once(self): - answers = [PASSWORD, WASSPORD, DROWSSAP, DROWSSAP] - with mock.patch("getpass.getpass", side_effect=answers): - mock_stdin = mock.Mock() - mock_stdin.isatty = mock.Mock() - mock_stdin.isatty.return_value = True - self.assertEqual(DROWSSAP, utils.get_password(mock_stdin)) - - def test_get_password_no_tty(self): - mock_stdin = mock.Mock() - mock_stdin.isatty = mock.Mock() - mock_stdin.isatty.return_value = False - self.assertRaises(exceptions.CommandError, - utils.get_password, - mock_stdin) - - def test_get_password_cntrl_d(self): - with mock.patch("getpass.getpass", side_effect=EOFError()): - mock_stdin = mock.Mock() - mock_stdin.isatty = mock.Mock() - mock_stdin.isatty.return_value = True - self.assertRaises(exceptions.CommandError, - utils.get_password, - mock_stdin) - - def get_test_items(self): - item1 = {'a': 1, 'b': 2} - item2 = {'a': 1, 'b': 3} - item3 = {'a': 2, 'b': 2} - item4 = {'a': 2, 'b': 1} - return [item1, item2, item3, item4] - - def test_sort_items_with_one_key(self): - items = self.get_test_items() - sort_str = 'b' - expect_items = [items[3], items[0], items[2], items[1]] - self.assertEqual(expect_items, utils.sort_items(items, sort_str)) - - def test_sort_items_with_multiple_keys(self): - items = self.get_test_items() - sort_str = 'a,b' - expect_items = [items[0], items[1], items[3], items[2]] - self.assertEqual(expect_items, utils.sort_items(items, sort_str)) - - def test_sort_items_all_with_direction(self): - items = self.get_test_items() - sort_str = 'a:desc,b:desc' - expect_items = [items[2], items[3], items[1], items[0]] - self.assertEqual(expect_items, utils.sort_items(items, sort_str)) - - def test_sort_items_some_with_direction(self): - items = self.get_test_items() - sort_str = 'a,b:desc' - expect_items = [items[1], items[0], items[2], items[3]] - self.assertEqual(expect_items, utils.sort_items(items, sort_str)) - - def test_sort_items_with_object(self): - item1 = mock.Mock(a=1, b=2) - item2 = mock.Mock(a=1, b=3) - item3 = mock.Mock(a=2, b=2) - item4 = mock.Mock(a=2, b=1) - items = [item1, item2, item3, item4] - sort_str = 'b,a' - expect_items = [item4, item1, item3, item2] - self.assertEqual(expect_items, utils.sort_items(items, sort_str)) - - def test_sort_items_with_empty_key(self): - items = self.get_test_items() - sort_srt = '' - self.assertEqual(items, utils.sort_items(items, sort_srt)) - sort_srt = None - self.assertEqual(items, utils.sort_items(items, sort_srt)) - - def test_sort_items_with_invalid_key(self): - items = self.get_test_items() - sort_str = 'c' - self.assertRaises(exceptions.CommandError, - utils.sort_items, - items, sort_str) - - def test_sort_items_with_invalid_direction(self): - items = self.get_test_items() - sort_str = 'a:bad_dir' - self.assertRaises(exceptions.CommandError, - utils.sort_items, - items, sort_str) - - @mock.patch.object(time, 'sleep') - def test_wait_for_status_ok(self, mock_sleep): - # Tests the normal flow that the resource is status=active - resource = mock.MagicMock(status='ACTIVE') - status_f = mock.Mock(return_value=resource) - res_id = str(uuid.uuid4()) - self.assertTrue(utils.wait_for_status(status_f, res_id,)) - mock_sleep.assert_not_called() - - @mock.patch.object(time, 'sleep') - def test_wait_for_status_ok__with_overrides(self, mock_sleep): - # Tests the normal flow that the resource is status=complete - resource = mock.MagicMock(my_status='COMPLETE') - status_f = mock.Mock(return_value=resource) - res_id = str(uuid.uuid4()) - self.assertTrue(utils.wait_for_status(status_f, res_id, - status_field='my_status', - success_status=['complete'])) - mock_sleep.assert_not_called() - - @mock.patch.object(time, 'sleep') - def test_wait_for_status_error(self, mock_sleep): - # Tests that we fail if the resource is status=error - resource = mock.MagicMock(status='ERROR') - status_f = mock.Mock(return_value=resource) - res_id = str(uuid.uuid4()) - self.assertFalse(utils.wait_for_status(status_f, res_id)) - mock_sleep.assert_not_called() - - @mock.patch.object(time, 'sleep') - def test_wait_for_status_error_with_overrides(self, mock_sleep): - # Tests that we fail if the resource is my_status=failed - resource = mock.MagicMock(my_status='FAILED') - status_f = mock.Mock(return_value=resource) - res_id = str(uuid.uuid4()) - self.assertFalse(utils.wait_for_status(status_f, res_id, - status_field='my_status', - error_status=['failed'])) - mock_sleep.assert_not_called() - - @mock.patch.object(time, 'sleep') - def test_wait_for_delete_ok(self, mock_sleep): - # Tests the normal flow that the resource is deleted with a 404 coming - # back on the 2nd iteration of the wait loop. - resource = mock.MagicMock(status='ACTIVE', progress=None) - mock_get = mock.Mock(side_effect=[resource, - exceptions.NotFound(404)]) - manager = mock.MagicMock(get=mock_get) - res_id = str(uuid.uuid4()) - callback = mock.Mock() - self.assertTrue(utils.wait_for_delete(manager, res_id, - callback=callback)) - mock_sleep.assert_called_once_with(5) - callback.assert_called_once_with(0) - - @mock.patch.object(time, 'sleep') - def test_wait_for_delete_timeout(self, mock_sleep): - # Tests that we fail if the resource is not deleted before the timeout. - resource = mock.MagicMock(status='ACTIVE') - mock_get = mock.Mock(return_value=resource) - manager = mock.MagicMock(get=mock_get) - res_id = str(uuid.uuid4()) - self.assertFalse(utils.wait_for_delete(manager, res_id, sleep_time=1, - timeout=1)) - mock_sleep.assert_called_once_with(1) - - @mock.patch.object(time, 'sleep') - def test_wait_for_delete_error(self, mock_sleep): - # Tests that we fail if the resource goes to error state while waiting. - resource = mock.MagicMock(status='ERROR') - mock_get = mock.Mock(return_value=resource) - manager = mock.MagicMock(get=mock_get) - res_id = str(uuid.uuid4()) - self.assertFalse(utils.wait_for_delete(manager, res_id)) - mock_sleep.assert_not_called() - - @mock.patch.object(time, 'sleep') - def test_wait_for_delete_error_with_overrides(self, mock_sleep): - # Tests that we fail if the resource is my_status=failed - resource = mock.MagicMock(my_status='FAILED') - mock_get = mock.Mock(return_value=resource) - manager = mock.MagicMock(get=mock_get) - res_id = str(uuid.uuid4()) - self.assertFalse(utils.wait_for_delete(manager, res_id, - status_field='my_status', - error_status=['failed'])) - mock_sleep.assert_not_called() - - @mock.patch.object(time, 'sleep') - def test_wait_for_delete_error_with_overrides_exception(self, mock_sleep): - # Tests that we succeed if the resource is specific exception - mock_get = mock.Mock(side_effect=Exception) - manager = mock.MagicMock(get=mock_get) - res_id = str(uuid.uuid4()) - self.assertTrue(utils.wait_for_delete(manager, res_id, - exception_name=['Exception'])) - mock_sleep.assert_not_called() - - def test_build_kwargs_dict_value_set(self): - self.assertEqual({'arg_bla': 'bla'}, - utils.build_kwargs_dict('arg_bla', 'bla')) - - def test_build_kwargs_dict_value_None(self): - self.assertEqual({}, utils.build_kwargs_dict('arg_bla', None)) - - def test_build_kwargs_dict_value_empty_str(self): - self.assertEqual({}, utils.build_kwargs_dict('arg_bla', '')) - - -class NoUniqueMatch(Exception): - pass - - -class TestFindResource(test_utils.TestCase): - - def setUp(self): - super(TestFindResource, self).setUp() - self.name = 'legos' - self.expected = mock.Mock() - self.manager = mock.Mock() - self.manager.resource_class = mock.Mock() - self.manager.resource_class.__name__ = 'lego' - - def test_find_resource_get_int(self): - self.manager.get = mock.Mock(return_value=self.expected) - result = utils.find_resource(self.manager, 1) - self.assertEqual(self.expected, result) - self.manager.get.assert_called_with(1) - - def test_find_resource_get_int_string(self): - self.manager.get = mock.Mock(return_value=self.expected) - result = utils.find_resource(self.manager, "2") - self.assertEqual(self.expected, result) - self.manager.get.assert_called_with(2) - - def test_find_resource_get_uuid(self): - uuid = '9a0dc2a0-ad0d-11e3-a5e2-0800200c9a66' - self.manager.get = mock.Mock(return_value=self.expected) - result = utils.find_resource(self.manager, uuid) - self.assertEqual(self.expected, result) - self.manager.get.assert_called_with(uuid) - - def test_find_resource_get_whatever(self): - self.manager.get = mock.Mock(return_value=self.expected) - result = utils.find_resource(self.manager, 'whatever') - self.assertEqual(self.expected, result) - self.manager.get.assert_called_with('whatever') - - def test_find_resource_find(self): - self.manager.get = mock.Mock(side_effect=Exception('Boom!')) - self.manager.find = mock.Mock(return_value=self.expected) - result = utils.find_resource(self.manager, self.name) - self.assertEqual(self.expected, result) - self.manager.get.assert_called_with(self.name) - self.manager.find.assert_called_with(name=self.name) - - def test_find_resource_find_not_found(self): - self.manager.get = mock.Mock(side_effect=Exception('Boom!')) - self.manager.find = mock.Mock( - side_effect=exceptions.NotFound(404, "2") - ) - result = self.assertRaises(exceptions.CommandError, - utils.find_resource, - self.manager, - self.name) - self.assertEqual("No lego with a name or ID of 'legos' exists.", - str(result)) - self.manager.get.assert_called_with(self.name) - self.manager.find.assert_called_with(name=self.name) - - def test_find_resource_list_forbidden(self): - self.manager.get = mock.Mock(side_effect=Exception('Boom!')) - self.manager.find = mock.Mock(side_effect=Exception('Boom!')) - self.manager.list = mock.Mock( - side_effect=exceptions.Forbidden(403) - ) - self.assertRaises(exceptions.Forbidden, - utils.find_resource, - self.manager, - self.name) - self.manager.list.assert_called_with() - - def test_find_resource_find_no_unique(self): - self.manager.get = mock.Mock(side_effect=Exception('Boom!')) - self.manager.find = mock.Mock(side_effect=NoUniqueMatch()) - result = self.assertRaises(exceptions.CommandError, - utils.find_resource, - self.manager, - self.name) - self.assertEqual("More than one lego exists with the name 'legos'.", - str(result)) - self.manager.get.assert_called_with(self.name) - self.manager.find.assert_called_with(name=self.name) - - def test_find_resource_silly_resource(self): - # We need a resource with no resource_class for this test, start fresh - self.manager = mock.Mock() - self.manager.get = mock.Mock(side_effect=Exception('Boom!')) - self.manager.find = mock.Mock( - side_effect=AttributeError( - "'Controller' object has no attribute 'find'", - ) - ) - silly_resource = FakeOddballResource( - None, - {'id': '12345', 'name': self.name}, - loaded=True, - ) - self.manager.list = mock.Mock( - return_value=[silly_resource, ], - ) - result = utils.find_resource(self.manager, self.name) - self.assertEqual(silly_resource, result) - self.manager.get.assert_called_with(self.name) - self.manager.find.assert_called_with(name=self.name) - - def test_find_resource_silly_resource_not_found(self): - # We need a resource with no resource_class for this test, start fresh - self.manager = mock.Mock() - self.manager.get = mock.Mock(side_effect=Exception('Boom!')) - self.manager.find = mock.Mock( - side_effect=AttributeError( - "'Controller' object has no attribute 'find'", - ) - ) - self.manager.list = mock.Mock(return_value=[]) - result = self.assertRaises(exceptions.CommandError, - utils.find_resource, - self.manager, - self.name) - self.assertEqual("Could not find resource legos", - str(result)) - self.manager.get.assert_called_with(self.name) - self.manager.find.assert_called_with(name=self.name) - - def test_format_dict(self): - expected = "a='b', c='d', e='f'" - self.assertEqual(expected, - utils.format_dict({'a': 'b', 'c': 'd', 'e': 'f'})) - self.assertEqual(expected, - utils.format_dict({'e': 'f', 'c': 'd', 'a': 'b'})) - - def test_format_list(self): - expected = 'a, b, c' - self.assertEqual(expected, utils.format_list(['a', 'b', 'c'])) - self.assertEqual(expected, utils.format_list(['c', 'b', 'a'])) - - def test_format_list_of_dicts(self): - expected = "a='b', c='d'\ne='f'" - sorted_data = [{'a': 'b', 'c': 'd'}, {'e': 'f'}] - unsorted_data = [{'c': 'd', 'a': 'b'}, {'e': 'f'}] - self.assertEqual(expected, utils.format_list_of_dicts(sorted_data)) - self.assertEqual(expected, utils.format_list_of_dicts(unsorted_data)) - self.assertEqual('', utils.format_list_of_dicts([])) - self.assertEqual('', utils.format_list_of_dicts([{}])) - - def test_format_list_separator(self): - expected = 'a\nb\nc' - actual_pre_sorted = utils.format_list(['a', 'b', 'c'], separator='\n') - actual_unsorted = utils.format_list(['c', 'b', 'a'], separator='\n') - self.assertEqual(expected, actual_pre_sorted) - self.assertEqual(expected, actual_unsorted) diff --git a/openstackclient/tests/compute/v2/test_flavor.py b/openstackclient/tests/compute/v2/test_flavor.py index 139585c284..8ce7bb273f 100644 --- a/openstackclient/tests/compute/v2/test_flavor.py +++ b/openstackclient/tests/compute/v2/test_flavor.py @@ -16,8 +16,8 @@ import copy from osc_lib import exceptions +from osc_lib import utils -from openstackclient.common import utils from openstackclient.compute.v2 import flavor from openstackclient.tests.compute.v2 import fakes as compute_fakes from openstackclient.tests import fakes diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index bd73624843..e10f43a1ab 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -17,8 +17,8 @@ from mock import call from osc_lib import exceptions +from osc_lib import utils as common_utils -from openstackclient.common import utils as common_utils from openstackclient.compute.v2 import server from openstackclient.tests.compute.v2 import fakes as compute_fakes from openstackclient.tests.image.v2 import fakes as image_fakes @@ -1692,7 +1692,7 @@ def test_format_servers_list_networks(self): (data_1, data_2, networks_format)) self.assertIn(networks_format, (data_1, data_2), msg) - @mock.patch('openstackclient.common.utils.find_resource') + @mock.patch('osc_lib.utils.find_resource') def test_prep_server_detail(self, find_resource): # Setup mock method return value. utils.find_resource() will be called # three times in _prep_server_detail(): diff --git a/openstackclient/tests/compute/v2/test_server_backup.py b/openstackclient/tests/compute/v2/test_server_backup.py index 3495d1b858..b6802ff032 100644 --- a/openstackclient/tests/compute/v2/test_server_backup.py +++ b/openstackclient/tests/compute/v2/test_server_backup.py @@ -14,8 +14,8 @@ import mock from osc_lib import exceptions +from osc_lib import utils as common_utils -from openstackclient.common import utils as common_utils from openstackclient.compute.v2 import server_backup from openstackclient.tests.compute.v2 import fakes as compute_fakes from openstackclient.tests.image.v2 import fakes as image_fakes diff --git a/openstackclient/tests/compute/v2/test_server_group.py b/openstackclient/tests/compute/v2/test_server_group.py index 4e0dbe4571..bd5f84714c 100644 --- a/openstackclient/tests/compute/v2/test_server_group.py +++ b/openstackclient/tests/compute/v2/test_server_group.py @@ -16,8 +16,8 @@ import mock from osc_lib import exceptions +from osc_lib import utils -from openstackclient.common import utils from openstackclient.compute.v2 import server_group from openstackclient.tests.compute.v2 import fakes as compute_fakes from openstackclient.tests import utils as tests_utils diff --git a/openstackclient/tests/identity/v2_0/test_user.py b/openstackclient/tests/identity/v2_0/test_user.py index 921e215da8..caf38a6f26 100644 --- a/openstackclient/tests/identity/v2_0/test_user.py +++ b/openstackclient/tests/identity/v2_0/test_user.py @@ -152,7 +152,7 @@ def test_user_create_password_prompt(self): # data to be shown. mocker = mock.Mock() mocker.return_value = 'abc123' - with mock.patch("openstackclient.common.utils.get_password", mocker): + with mock.patch("osc_lib.utils.get_password", mocker): columns, data = self.cmd.take_action(parsed_args) # Set expected values @@ -638,7 +638,7 @@ def test_user_set_password_prompt(self): mocker = mock.Mock() mocker.return_value = 'abc123' - with mock.patch("openstackclient.common.utils.get_password", mocker): + with mock.patch("osc_lib.utils.get_password", mocker): result = self.cmd.take_action(parsed_args) # UserManager.update_password(user, password) diff --git a/openstackclient/tests/identity/v3/test_project.py b/openstackclient/tests/identity/v3/test_project.py index 5be4e38932..8fcada6e4d 100644 --- a/openstackclient/tests/identity/v3/test_project.py +++ b/openstackclient/tests/identity/v3/test_project.py @@ -202,7 +202,7 @@ def test_project_create_domain_no_perms(self): mocker = mock.Mock() mocker.return_value = None - with mock.patch("openstackclient.common.utils.find_resource", mocker): + with mock.patch("osc_lib.utils.find_resource", mocker): columns, data = self.cmd.take_action(parsed_args) # Set expected values @@ -549,7 +549,7 @@ def test_project_list_domain_no_perms(self): mocker = mock.Mock() mocker.return_value = None - with mock.patch("openstackclient.common.utils.find_resource", mocker): + with mock.patch("osc_lib.utils.find_resource", mocker): columns, data = self.cmd.take_action(parsed_args) self.projects_mock.list.assert_called_with( diff --git a/openstackclient/tests/identity/v3/test_user.py b/openstackclient/tests/identity/v3/test_user.py index 5dafa772ff..85522e577f 100644 --- a/openstackclient/tests/identity/v3/test_user.py +++ b/openstackclient/tests/identity/v3/test_user.py @@ -185,7 +185,7 @@ def test_user_create_password_prompt(self): # data to be shown. mocker = mock.Mock() mocker.return_value = 'abc123' - with mock.patch("openstackclient.common.utils.get_password", mocker): + with mock.patch("osc_lib.utils.get_password", mocker): columns, data = self.cmd.take_action(parsed_args) # Set expected values @@ -841,7 +841,7 @@ def test_user_set_password_prompt(self): mocker = mock.Mock() mocker.return_value = 'abc123' - with mock.patch("openstackclient.common.utils.get_password", mocker): + with mock.patch("osc_lib.utils.get_password", mocker): result = self.cmd.take_action(parsed_args) # Set expected values @@ -1023,7 +1023,7 @@ def setUp(self): @contextlib.contextmanager def _mock_get_password(*passwords): mocker = mock.Mock(side_effect=passwords) - with mock.patch("openstackclient.common.utils.get_password", mocker): + with mock.patch("osc_lib.utils.get_password", mocker): yield def test_user_password_change(self): diff --git a/openstackclient/tests/image/v1/test_image.py b/openstackclient/tests/image/v1/test_image.py index b0436b9a9c..99c0b0eee7 100644 --- a/openstackclient/tests/image/v1/test_image.py +++ b/openstackclient/tests/image/v1/test_image.py @@ -417,7 +417,7 @@ def test_image_list_property_option(self, sf_mock): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) - @mock.patch('openstackclient.common.utils.sort_items') + @mock.patch('osc_lib.utils.sort_items') def test_image_list_sort_option(self, si_mock): si_mock.side_effect = [ [copy.deepcopy(image_fakes.IMAGE)], [], diff --git a/openstackclient/tests/image/v2/fakes.py b/openstackclient/tests/image/v2/fakes.py index 24aaec51f6..8e22fbb2e5 100644 --- a/openstackclient/tests/image/v2/fakes.py +++ b/openstackclient/tests/image/v2/fakes.py @@ -19,13 +19,12 @@ import uuid from glanceclient.v2 import schemas +from osc_lib import utils as common_utils import warlock -from openstackclient.common import utils as common_utils from openstackclient.tests import fakes -from openstackclient.tests import utils - from openstackclient.tests.identity.v3 import fakes as identity_fakes +from openstackclient.tests import utils image_id = '0f41529e-7c12-4de8-be2d-181abb825b3c' image_name = 'graven' diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index d8693df0f7..92e0660faf 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -20,8 +20,8 @@ from glanceclient.v2 import schemas from osc_lib import exceptions +from osc_lib import utils as common_utils -from openstackclient.common import utils as common_utils from openstackclient.image.v2 import image from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes @@ -661,7 +661,7 @@ def test_image_list_property_option(self, sf_mock): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) - @mock.patch('openstackclient.common.utils.sort_items') + @mock.patch('osc_lib.utils.sort_items') def test_image_list_sort_option(self, si_mock): si_mock.return_value = [copy.deepcopy(self._image)] @@ -698,7 +698,7 @@ def test_image_list_limit_option(self): self.assertEqual(self.columns, columns) self.assertEqual(len(self.datalist), len(tuple(data))) - @mock.patch('openstackclient.common.utils.find_resource') + @mock.patch('osc_lib.utils.find_resource') def test_image_list_marker_option(self, fr_mock): # tangchen: Since image_fakes.IMAGE is a dict, it cannot offer a .id # operation. Will fix this by using FakeImage class instead diff --git a/openstackclient/tests/network/v2/test_ip_availability.py b/openstackclient/tests/network/v2/test_ip_availability.py index 04979e7710..39e11cd5db 100644 --- a/openstackclient/tests/network/v2/test_ip_availability.py +++ b/openstackclient/tests/network/v2/test_ip_availability.py @@ -14,7 +14,8 @@ import copy import mock -from openstackclient.common import utils as osc_utils +from osc_lib import utils as common_utils + from openstackclient.network.v2 import ip_availability from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes @@ -140,7 +141,7 @@ class TestShowIPAvailability(TestIPAvailability): _ip_availability.network_id, _ip_availability.network_name, _ip_availability.tenant_id, - osc_utils.format_list( + common_utils.format_list( _ip_availability.subnet_ip_availability), _ip_availability.total_ips, _ip_availability.used_ips, diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index 581e927885..8fc9dadf8f 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -16,8 +16,8 @@ from mock import call from osc_lib import exceptions +from osc_lib import utils -from openstackclient.common import utils from openstackclient.network.v2 import network from openstackclient.tests.compute.v2 import fakes as compute_fakes from openstackclient.tests import fakes diff --git a/openstackclient/tests/network/v2/test_port.py b/openstackclient/tests/network/v2/test_port.py index 02c7d4a4cc..779dca0519 100644 --- a/openstackclient/tests/network/v2/test_port.py +++ b/openstackclient/tests/network/v2/test_port.py @@ -15,8 +15,8 @@ from mock import call from osc_lib import exceptions +from osc_lib import utils -from openstackclient.common import utils from openstackclient.network.v2 import port from openstackclient.tests.network.v2 import fakes as network_fakes from openstackclient.tests import utils as tests_utils diff --git a/openstackclient/tests/network/v2/test_router.py b/openstackclient/tests/network/v2/test_router.py index b25381ef3e..1629613998 100644 --- a/openstackclient/tests/network/v2/test_router.py +++ b/openstackclient/tests/network/v2/test_router.py @@ -13,7 +13,8 @@ import mock -from openstackclient.common import utils as osc_utils +from osc_lib import utils as osc_utils + from openstackclient.network.v2 import router from openstackclient.tests.network.v2 import fakes as network_fakes from openstackclient.tests import utils as tests_utils diff --git a/openstackclient/tests/network/v2/test_subnet.py b/openstackclient/tests/network/v2/test_subnet.py index 25684d63e0..de7e182123 100644 --- a/openstackclient/tests/network/v2/test_subnet.py +++ b/openstackclient/tests/network/v2/test_subnet.py @@ -14,7 +14,8 @@ import copy import mock -from openstackclient.common import utils +from osc_lib import utils + from openstackclient.network.v2 import subnet as subnet_v2 from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes_v3 diff --git a/openstackclient/tests/network/v2/test_subnet_pool.py b/openstackclient/tests/network/v2/test_subnet_pool.py index f7bb5895c3..10ef76d81a 100644 --- a/openstackclient/tests/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/network/v2/test_subnet_pool.py @@ -15,7 +15,8 @@ import copy import mock -from openstackclient.common import utils +from osc_lib import utils + from openstackclient.network.v2 import subnet_pool from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes_v3 diff --git a/openstackclient/tests/volume/test_find_resource.py b/openstackclient/tests/volume/test_find_resource.py index d29a955db4..227d6ca76c 100644 --- a/openstackclient/tests/volume/test_find_resource.py +++ b/openstackclient/tests/volume/test_find_resource.py @@ -18,8 +18,8 @@ from cinderclient.v1 import volume_snapshots from cinderclient.v1 import volumes from osc_lib import exceptions +from osc_lib import utils -from openstackclient.common import utils from openstackclient.tests import utils as test_utils from openstackclient.volume import client # noqa diff --git a/openstackclient/tests/volume/v1/test_qos_specs.py b/openstackclient/tests/volume/v1/test_qos_specs.py index 4943f5df69..392017c65d 100644 --- a/openstackclient/tests/volume/v1/test_qos_specs.py +++ b/openstackclient/tests/volume/v1/test_qos_specs.py @@ -15,7 +15,8 @@ import copy -from openstackclient.common import utils +from osc_lib import utils + from openstackclient.tests import fakes from openstackclient.tests.volume.v1 import fakes as volume_fakes from openstackclient.volume.v1 import qos_specs diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py index d3ca009eea..1cbbf68a1b 100644 --- a/openstackclient/tests/volume/v2/fakes.py +++ b/openstackclient/tests/volume/v2/fakes.py @@ -17,7 +17,8 @@ import random import uuid -from openstackclient.common import utils as common_utils +from osc_lib import utils as common_utils + from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes from openstackclient.tests.image.v2 import fakes as image_fakes diff --git a/openstackclient/tests/volume/v2/test_qos_specs.py b/openstackclient/tests/volume/v2/test_qos_specs.py index 741f4e7043..11047535cb 100644 --- a/openstackclient/tests/volume/v2/test_qos_specs.py +++ b/openstackclient/tests/volume/v2/test_qos_specs.py @@ -13,9 +13,9 @@ # under the License. # -from openstackclient.common import utils from openstackclient.tests.volume.v2 import fakes as volume_fakes from openstackclient.volume.v2 import qos_specs +from osc_lib import utils class TestQos(volume_fakes.TestVolume): diff --git a/openstackclient/tests/volume/v2/test_snapshot.py b/openstackclient/tests/volume/v2/test_snapshot.py index fe6fbb520d..ef199cbc7f 100644 --- a/openstackclient/tests/volume/v2/test_snapshot.py +++ b/openstackclient/tests/volume/v2/test_snapshot.py @@ -12,7 +12,8 @@ # under the License. # -from openstackclient.common import utils +from osc_lib import utils + from openstackclient.tests.volume.v2 import fakes as volume_fakes from openstackclient.volume.v2 import snapshot diff --git a/openstackclient/tests/volume/v2/test_type.py b/openstackclient/tests/volume/v2/test_type.py index 10c386129d..7eea1c3c45 100644 --- a/openstackclient/tests/volume/v2/test_type.py +++ b/openstackclient/tests/volume/v2/test_type.py @@ -14,7 +14,8 @@ import copy -from openstackclient.common import utils +from osc_lib import utils + from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes from openstackclient.tests import utils as tests_utils diff --git a/openstackclient/tests/volume/v2/test_volume.py b/openstackclient/tests/volume/v2/test_volume.py index fb48d8acf5..68158df0e0 100644 --- a/openstackclient/tests/volume/v2/test_volume.py +++ b/openstackclient/tests/volume/v2/test_volume.py @@ -16,7 +16,8 @@ from mock import call -from openstackclient.common import utils +from osc_lib import utils + from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes from openstackclient.tests.image.v2 import fakes as image_fakes diff --git a/openstackclient/volume/client.py b/openstackclient/volume/client.py index a60f4b0ed2..cc19546044 100644 --- a/openstackclient/volume/client.py +++ b/openstackclient/volume/client.py @@ -15,7 +15,8 @@ import logging -from openstackclient.common import utils +from osc_lib import utils + from openstackclient.i18n import _ LOG = logging.getLogger(__name__) diff --git a/openstackclient/volume/v1/backup.py b/openstackclient/volume/v1/backup.py index 607b521146..f70d9b42e1 100644 --- a/openstackclient/volume/v1/backup.py +++ b/openstackclient/volume/v1/backup.py @@ -16,10 +16,11 @@ """Volume v1 Backup action implementations""" import copy + +from osc_lib import utils import six from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/volume/v1/qos_specs.py b/openstackclient/volume/v1/qos_specs.py index c49477a0e9..70f6826901 100644 --- a/openstackclient/volume/v1/qos_specs.py +++ b/openstackclient/volume/v1/qos_specs.py @@ -15,11 +15,11 @@ """Volume v1 QoS action implementations""" +from osc_lib import utils import six from openstackclient.common import command from openstackclient.common import parseractions -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/volume/v1/service.py b/openstackclient/volume/v1/service.py index 023dda9850..c065b526bd 100644 --- a/openstackclient/volume/v1/service.py +++ b/openstackclient/volume/v1/service.py @@ -14,8 +14,9 @@ """Service action implementations""" +from osc_lib import utils + from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/volume/v1/snapshot.py b/openstackclient/volume/v1/snapshot.py index 5132d71ee6..af0b522924 100644 --- a/openstackclient/volume/v1/snapshot.py +++ b/openstackclient/volume/v1/snapshot.py @@ -16,11 +16,12 @@ """Volume v1 Snapshot action implementations""" import copy + +from osc_lib import utils import six from openstackclient.common import command from openstackclient.common import parseractions -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index ffec180348..359d931ecc 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -16,11 +16,12 @@ """Volume v1 Volume action implementations""" import argparse + +from osc_lib import utils import six from openstackclient.common import command from openstackclient.common import parseractions -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/volume/v1/volume_transfer_request.py b/openstackclient/volume/v1/volume_transfer_request.py index 98689e7bc5..9cffe98831 100644 --- a/openstackclient/volume/v1/volume_transfer_request.py +++ b/openstackclient/volume/v1/volume_transfer_request.py @@ -14,9 +14,9 @@ """Volume v2 transfer action implementations""" +from osc_lib import utils from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/volume/v1/volume_type.py b/openstackclient/volume/v1/volume_type.py index 4e9b1920aa..e4d22abb98 100644 --- a/openstackclient/volume/v1/volume_type.py +++ b/openstackclient/volume/v1/volume_type.py @@ -15,11 +15,11 @@ """Volume v1 Type action implementations""" +from osc_lib import utils import six from openstackclient.common import command from openstackclient.common import parseractions -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/volume/v2/backup.py b/openstackclient/volume/v2/backup.py index e6fbe78dd1..371ddafddf 100644 --- a/openstackclient/volume/v2/backup.py +++ b/openstackclient/volume/v2/backup.py @@ -16,10 +16,10 @@ import copy +from osc_lib import utils import six from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/volume/v2/qos_specs.py b/openstackclient/volume/v2/qos_specs.py index 90e11c77b7..c4473117b9 100644 --- a/openstackclient/volume/v2/qos_specs.py +++ b/openstackclient/volume/v2/qos_specs.py @@ -15,11 +15,11 @@ """Volume v2 QoS action implementations""" +from osc_lib import utils import six from openstackclient.common import command from openstackclient.common import parseractions -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/volume/v2/service.py b/openstackclient/volume/v2/service.py index 023dda9850..c065b526bd 100644 --- a/openstackclient/volume/v2/service.py +++ b/openstackclient/volume/v2/service.py @@ -14,8 +14,9 @@ """Service action implementations""" +from osc_lib import utils + from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/volume/v2/snapshot.py b/openstackclient/volume/v2/snapshot.py index d90170809d..99b7462b52 100644 --- a/openstackclient/volume/v2/snapshot.py +++ b/openstackclient/volume/v2/snapshot.py @@ -16,11 +16,11 @@ import copy +from osc_lib import utils import six from openstackclient.common import command from openstackclient.common import parseractions -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index 18473da3c2..c6262d1761 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -16,11 +16,11 @@ import copy +from osc_lib import utils import six from openstackclient.common import command from openstackclient.common import parseractions -from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common diff --git a/openstackclient/volume/v2/volume_transfer_request.py b/openstackclient/volume/v2/volume_transfer_request.py index 98689e7bc5..9cffe98831 100644 --- a/openstackclient/volume/v2/volume_transfer_request.py +++ b/openstackclient/volume/v2/volume_transfer_request.py @@ -14,9 +14,9 @@ """Volume v2 transfer action implementations""" +from osc_lib import utils from openstackclient.common import command -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index e2e1c53ee8..0896ecffc8 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -15,11 +15,11 @@ """Volume v2 Type action implementations""" from osc_lib import exceptions +from osc_lib import utils import six from openstackclient.common import command from openstackclient.common import parseractions -from openstackclient.common import utils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common From 59dffb9c62b48dbf5e99395e8f58e317e43cf036 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 9 Jun 2016 16:29:42 -0500 Subject: [PATCH 0982/3095] osc-lib: logs Change-Id: I2a4d40cd72cc22e97a600751ae29c2309ebed28b --- doc/source/plugins.rst | 2 +- openstackclient/common/logs.py | 186 +--------------------- openstackclient/shell.py | 2 +- openstackclient/tests/common/test_logs.py | 15 +- 4 files changed, 19 insertions(+), 186 deletions(-) diff --git a/doc/source/plugins.rst b/doc/source/plugins.rst index 27846318b3..62c0aedc2e 100644 --- a/doc/source/plugins.rst +++ b/doc/source/plugins.rst @@ -153,12 +153,12 @@ the plugin commands: # osc-lib interfaces available to plugins: from osc_lib import exceptions + from osc_lib import logs from osc_lib import utils # OSC common interfaces available to plugins: from openstackclient.common import command from openstackclient.common import parseractions - from openstackclient.common import logs class DeleteMypluginobject(command.Command): diff --git a/openstackclient/common/logs.py b/openstackclient/common/logs.py index 8671555b60..8aa97d5bae 100644 --- a/openstackclient/common/logs.py +++ b/openstackclient/common/logs.py @@ -11,186 +11,16 @@ # under the License. # -"""Application logging""" +# NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release +# or Jun 2017. -import logging import sys -import warnings +from osc_lib.logs import * # noqa +from osc_lib.logs import _FileFormatter # noqa -def get_loggers(): - loggers = {} - for logkey in logging.Logger.manager.loggerDict.keys(): - loggers[logkey] = logging.getLevelName(logging.getLogger(logkey).level) - return loggers - -def log_level_from_options(options): - # if --debug, --quiet or --verbose is not specified, - # the default logging level is warning - log_level = logging.WARNING - if options.verbose_level == 0: - # --quiet - log_level = logging.ERROR - elif options.verbose_level == 2: - # One --verbose - log_level = logging.INFO - elif options.verbose_level >= 3: - # Two or more --verbose - log_level = logging.DEBUG - return log_level - - -def log_level_from_string(level_string): - log_level = { - 'critical': logging.CRITICAL, - 'error': logging.ERROR, - 'warning': logging.WARNING, - 'info': logging.INFO, - 'debug': logging.DEBUG, - }.get(level_string, logging.WARNING) - return log_level - - -def log_level_from_config(config): - # Check the command line option - verbose_level = config.get('verbose_level') - if config.get('debug', False): - verbose_level = 3 - if verbose_level == 0: - verbose_level = 'error' - elif verbose_level == 1: - # If a command line option has not been specified, check the - # configuration file - verbose_level = config.get('log_level', 'warning') - elif verbose_level == 2: - verbose_level = 'info' - else: - verbose_level = 'debug' - return log_level_from_string(verbose_level) - - -def set_warning_filter(log_level): - if log_level == logging.ERROR: - warnings.simplefilter("ignore") - elif log_level == logging.WARNING: - warnings.simplefilter("ignore") - elif log_level == logging.INFO: - warnings.simplefilter("once") - - -class _FileFormatter(logging.Formatter): - """Customize the logging format for logging handler""" - _LOG_MESSAGE_BEGIN = ( - '%(asctime)s.%(msecs)03d %(process)d %(levelname)s %(name)s ') - _LOG_MESSAGE_CONTEXT = '[%(cloud)s %(username)s %(project)s] ' - _LOG_MESSAGE_END = '%(message)s' - _LOG_DATE_FORMAT = '%Y-%m-%d %H:%M:%S' - - def __init__(self, options=None, config=None, **kwargs): - context = {} - if options: - context = { - 'cloud': getattr(options, 'cloud', ''), - 'project': getattr(options, 'os_project_name', ''), - 'username': getattr(options, 'username', ''), - } - elif config: - context = { - 'cloud': config.config.get('cloud', ''), - 'project': config.auth.get('project_name', ''), - 'username': config.auth.get('username', ''), - } - if context: - self.fmt = (self._LOG_MESSAGE_BEGIN + - (self._LOG_MESSAGE_CONTEXT % context) + - self._LOG_MESSAGE_END) - else: - self.fmt = self._LOG_MESSAGE_BEGIN + self._LOG_MESSAGE_END - logging.Formatter.__init__(self, self.fmt, self._LOG_DATE_FORMAT) - - -class LogConfigurator(object): - - _CONSOLE_MESSAGE_FORMAT = '%(message)s' - - def __init__(self, options): - self.root_logger = logging.getLogger('') - self.root_logger.setLevel(logging.DEBUG) - - # Force verbose_level 3 on --debug - self.dump_trace = False - if options.debug: - options.verbose_level = 3 - self.dump_trace = True - - # Always send higher-level messages to the console via stderr - self.console_logger = logging.StreamHandler(sys.stderr) - log_level = log_level_from_options(options) - self.console_logger.setLevel(log_level) - formatter = logging.Formatter(self._CONSOLE_MESSAGE_FORMAT) - self.console_logger.setFormatter(formatter) - self.root_logger.addHandler(self.console_logger) - - # Set the warning filter now - set_warning_filter(log_level) - - # Set up logging to a file - self.file_logger = None - log_file = options.log_file - if log_file: - self.file_logger = logging.FileHandler(filename=log_file) - self.file_logger.setFormatter(_FileFormatter(options=options)) - self.file_logger.setLevel(log_level) - self.root_logger.addHandler(self.file_logger) - - # Requests logs some stuff at INFO that we don't want - # unless we have DEBUG - requests_log = logging.getLogger("requests") - - # Other modules we don't want DEBUG output for - cliff_log = logging.getLogger('cliff') - stevedore_log = logging.getLogger('stevedore') - iso8601_log = logging.getLogger("iso8601") - - if options.debug: - # --debug forces traceback - requests_log.setLevel(logging.DEBUG) - else: - requests_log.setLevel(logging.ERROR) - - cliff_log.setLevel(logging.ERROR) - stevedore_log.setLevel(logging.ERROR) - iso8601_log.setLevel(logging.ERROR) - - def configure(self, cloud_config): - log_level = log_level_from_config(cloud_config.config) - set_warning_filter(log_level) - self.dump_trace = cloud_config.config.get('debug', self.dump_trace) - self.console_logger.setLevel(log_level) - - log_file = cloud_config.config.get('log_file') - if log_file: - if not self.file_logger: - self.file_logger = logging.FileHandler(filename=log_file) - self.file_logger.setFormatter(_FileFormatter(config=cloud_config)) - self.file_logger.setLevel(log_level) - self.root_logger.addHandler(self.file_logger) - - logconfig = cloud_config.config.get('logging') - if logconfig: - highest_level = logging.NOTSET - for k in logconfig.keys(): - level = log_level_from_string(logconfig[k]) - logging.getLogger(k).setLevel(level) - if (highest_level < level): - highest_level = level - self.console_logger.setLevel(highest_level) - if self.file_logger: - self.file_logger.setLevel(highest_level) - # loggers that are not set will use the handler level, so we - # need to set the global level for all the loggers - for logkey in logging.Logger.manager.loggerDict.keys(): - logger = logging.getLogger(logkey) - if logger.level == logging.NOTSET: - logger.setLevel(log_level) +sys.stderr.write( + "WARNING: %s is deprecated and will be removed after Jun 2017. " + "Please use osc_lib.logs\n" % __name__ +) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 2697128bd4..3e899cb51c 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -27,6 +27,7 @@ from cliff import complete from cliff import help from osc_lib import exceptions as exc +from osc_lib import logs from osc_lib import utils from oslo_utils import importutils from oslo_utils import strutils @@ -34,7 +35,6 @@ import openstackclient from openstackclient.common import clientmanager from openstackclient.common import commandmanager -from openstackclient.common import logs from openstackclient.common import timing from os_client_config import config as cloud_config diff --git a/openstackclient/tests/common/test_logs.py b/openstackclient/tests/common/test_logs.py index 0386cdfda0..5091510cd3 100644 --- a/openstackclient/tests/common/test_logs.py +++ b/openstackclient/tests/common/test_logs.py @@ -11,6 +11,9 @@ # under the License. # +# NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release +# or Jun 2017. + import logging import mock @@ -121,7 +124,7 @@ def setUp(self): @mock.patch('logging.StreamHandler') @mock.patch('logging.getLogger') - @mock.patch('openstackclient.common.logs.set_warning_filter') + @mock.patch('osc_lib.logs.set_warning_filter') def test_init(self, warning_filter, getLogger, handle): getLogger.side_effect = self.loggers console_logger = mock.Mock() @@ -142,7 +145,7 @@ def test_init(self, warning_filter, getLogger, handle): self.assertFalse(configurator.dump_trace) @mock.patch('logging.getLogger') - @mock.patch('openstackclient.common.logs.set_warning_filter') + @mock.patch('osc_lib.logs.set_warning_filter') def test_init_no_debug(self, warning_filter, getLogger): getLogger.side_effect = self.loggers self.options.debug = True @@ -155,8 +158,8 @@ def test_init_no_debug(self, warning_filter, getLogger): @mock.patch('logging.FileHandler') @mock.patch('logging.getLogger') - @mock.patch('openstackclient.common.logs.set_warning_filter') - @mock.patch('openstackclient.common.logs._FileFormatter') + @mock.patch('osc_lib.logs.set_warning_filter') + @mock.patch('osc_lib.logs._FileFormatter') def test_init_log_file(self, formatter, warning_filter, getLogger, handle): getLogger.side_effect = self.loggers self.options.log_file = '/tmp/log_file' @@ -176,8 +179,8 @@ def test_init_log_file(self, formatter, warning_filter, getLogger, handle): @mock.patch('logging.FileHandler') @mock.patch('logging.getLogger') - @mock.patch('openstackclient.common.logs.set_warning_filter') - @mock.patch('openstackclient.common.logs._FileFormatter') + @mock.patch('osc_lib.logs.set_warning_filter') + @mock.patch('osc_lib.logs._FileFormatter') def test_configure(self, formatter, warning_filter, getLogger, handle): getLogger.side_effect = self.loggers configurator = logs.LogConfigurator(self.options) From be192676bd2f2beeea0ec4265d5f78a8cf9af637 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 13 May 2016 14:14:06 -0500 Subject: [PATCH 0983/3095] osc-lib: parseractions Leave parseractions.py and test_parseractions.py as a sanity check during the deprecation period. Change-Id: I1a7469b6d872284e0276502a1a287bc0b87f8f83 --- doc/source/plugins.rst | 2 +- openstackclient/common/parseractions.py | 157 +----------------- openstackclient/compute/v2/aggregate.py | 2 +- openstackclient/compute/v2/console.py | 2 +- openstackclient/compute/v2/flavor.py | 2 +- openstackclient/compute/v2/server.py | 2 +- openstackclient/identity/v2_0/project.py | 2 +- openstackclient/identity/v3/project.py | 2 +- openstackclient/image/v1/image.py | 4 +- openstackclient/image/v2/image.py | 2 +- openstackclient/network/v2/port.py | 2 +- openstackclient/network/v2/router.py | 2 +- .../network/v2/security_group_rule.py | 2 +- openstackclient/network/v2/subnet.py | 2 +- openstackclient/network/v2/subnet_pool.py | 2 +- openstackclient/object/v1/account.py | 2 +- openstackclient/object/v1/container.py | 2 +- openstackclient/object/v1/object.py | 2 +- .../tests/common/test_parseractions.py | 3 + openstackclient/volume/v1/qos_specs.py | 2 +- openstackclient/volume/v1/snapshot.py | 2 +- openstackclient/volume/v1/volume.py | 2 +- openstackclient/volume/v1/volume_type.py | 2 +- openstackclient/volume/v2/qos_specs.py | 2 +- openstackclient/volume/v2/snapshot.py | 2 +- openstackclient/volume/v2/volume.py | 2 +- openstackclient/volume/v2/volume_type.py | 2 +- 27 files changed, 37 insertions(+), 175 deletions(-) diff --git a/doc/source/plugins.rst b/doc/source/plugins.rst index 62c0aedc2e..8640efbccd 100644 --- a/doc/source/plugins.rst +++ b/doc/source/plugins.rst @@ -152,13 +152,13 @@ the plugin commands: .. code-block:: python # osc-lib interfaces available to plugins: + from osc_lib.cli import parseractions from osc_lib import exceptions from osc_lib import logs from osc_lib import utils # OSC common interfaces available to plugins: from openstackclient.common import command - from openstackclient.common import parseractions class DeleteMypluginobject(command.Command): diff --git a/openstackclient/common/parseractions.py b/openstackclient/common/parseractions.py index 1e92dc46fe..fa5148eca5 100644 --- a/openstackclient/common/parseractions.py +++ b/openstackclient/common/parseractions.py @@ -1,5 +1,3 @@ -# Copyright 2013 OpenStack Foundation -# # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at @@ -13,154 +11,15 @@ # under the License. # -"""argparse Custom Actions""" - -import argparse - -from openstackclient.i18n import _ - - -class KeyValueAction(argparse.Action): - """A custom action to parse arguments as key=value pairs - - Ensures that ``dest`` is a dict - """ - - def __call__(self, parser, namespace, values, option_string=None): - # Make sure we have an empty dict rather than None - if getattr(namespace, self.dest, None) is None: - setattr(namespace, self.dest, {}) - - # Add value if an assignment else remove it - if '=' in values: - getattr(namespace, self.dest, {}).update([values.split('=', 1)]) - else: - msg = _("Expected 'key=value' type, " - "but got: %s") % (str(values)) - raise argparse.ArgumentTypeError(msg) - - -class MultiKeyValueAction(argparse.Action): - """A custom action to parse arguments as key1=value1,key2=value2 pairs - - Ensure that ``dest`` is a list. The list will finally contain multiple - dicts, with key=value pairs in them. - - NOTE: The arguments string should be a comma separated key-value pairs. - And comma(',') and equal('=') may not be used in the key or value. - """ - - def __init__(self, option_strings, dest, nargs=None, - required_keys=None, optional_keys=None, **kwargs): - """Initialize the action object, and parse customized options - - Required keys and optional keys can be specified when initializing - the action to enable the key validation. If none of them specified, - the key validation will be skipped. - - :param required_keys: a list of required keys - :param optional_keys: a list of optional keys - """ - if nargs: - raise ValueError("Parameter 'nargs' is not allowed, but got %s" - % nargs) - - super(MultiKeyValueAction, self).__init__(option_strings, - dest, **kwargs) - - # required_keys: A list of keys that is required. None by default. - if required_keys and not isinstance(required_keys, list): - raise TypeError("'required_keys' must be a list") - self.required_keys = set(required_keys or []) - - # optional_keys: A list of keys that is optional. None by default. - if optional_keys and not isinstance(optional_keys, list): - raise TypeError("'optional_keys' must be a list") - self.optional_keys = set(optional_keys or []) - - def __call__(self, parser, namespace, values, metavar=None): - # Make sure we have an empty list rather than None - if getattr(namespace, self.dest, None) is None: - setattr(namespace, self.dest, []) - - params = {} - for kv in values.split(','): - # Add value if an assignment else raise ArgumentTypeError - if '=' in kv: - params.update([kv.split('=', 1)]) - else: - msg = _("Expected key=value pairs separated by comma, " - "but got: %s") % (str(kv)) - raise argparse.ArgumentTypeError(msg) - - # Check key validation - valid_keys = self.required_keys | self.optional_keys - if valid_keys: - invalid_keys = [k for k in params if k not in valid_keys] - if invalid_keys: - msg = _("Invalid keys %(invalid_keys)s specified.\n" - "Valid keys are: %(valid_keys)s.") - raise argparse.ArgumentTypeError( - msg % {'invalid_keys': ', '.join(invalid_keys), - 'valid_keys': ', '.join(valid_keys)} - ) - - if self.required_keys: - missing_keys = [k for k in self.required_keys if k not in params] - if missing_keys: - msg = _("Missing required keys %(missing_keys)s.\n" - "Required keys are: %(required_keys)s.") - raise argparse.ArgumentTypeError( - msg % {'missing_keys': ', '.join(missing_keys), - 'required_keys': ', '.join(self.required_keys)} - ) - - # Update the dest dict - getattr(namespace, self.dest, []).append(params) - - -class RangeAction(argparse.Action): - """A custom action to parse a single value or a range of values - - Parses single integer values or a range of integer values delimited - by a colon and returns a tuple of integers: - '4' sets ``dest`` to (4, 4) - '6:9' sets ``dest`` to (6, 9) - """ - - def __call__(self, parser, namespace, values, option_string=None): - range = values.split(':') - if len(range) == 0: - # Nothing passed, return a zero default - setattr(namespace, self.dest, (0, 0)) - elif len(range) == 1: - # Only a single value is present - setattr(namespace, self.dest, (int(range[0]), int(range[0]))) - elif len(range) == 2: - # Range of two values - if int(range[0]) <= int(range[1]): - setattr(namespace, self.dest, (int(range[0]), int(range[1]))) - else: - msg = (_("Invalid range, %(range0)s is not " - "less than %(range1)s") - % {'range0': range[0], 'range1': range[1]}) - raise argparse.ArgumentError(self, msg) - else: - # Too many values - msg = _("Invalid range, too many values") - raise argparse.ArgumentError(self, msg) +# NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release +# or Jun 2017. +import sys -class NonNegativeAction(argparse.Action): - """A custom action to check whether the value is non-negative or not +from osc_lib.cli.parseractions import * # noqa - Ensures the value is >= 0. - """ - def __call__(self, parser, namespace, values, option_string=None): - if int(values) >= 0: - setattr(namespace, self.dest, values) - else: - msg = (_("%s expected a non-negative integer") - % (str(option_string))) - raise argparse.ArgumentTypeError(msg) +sys.stderr.write( + "WARNING: %s is deprecated and will be removed after Jun 2017. " + "Please use osc_lib.cli.parseractions\n" % __name__ +) diff --git a/openstackclient/compute/v2/aggregate.py b/openstackclient/compute/v2/aggregate.py index 69227b0c9b..9e44615d4d 100644 --- a/openstackclient/compute/v2/aggregate.py +++ b/openstackclient/compute/v2/aggregate.py @@ -16,11 +16,11 @@ """Compute v2 Aggregate action implementations""" +from osc_lib.cli import parseractions from osc_lib import utils import six from openstackclient.common import command -from openstackclient.common import parseractions from openstackclient.i18n import _ diff --git a/openstackclient/compute/v2/console.py b/openstackclient/compute/v2/console.py index ee16801663..ff7f1af6fa 100644 --- a/openstackclient/compute/v2/console.py +++ b/openstackclient/compute/v2/console.py @@ -17,11 +17,11 @@ import sys +from osc_lib.cli import parseractions from osc_lib import utils import six from openstackclient.common import command -from openstackclient.common import parseractions from openstackclient.i18n import _ diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 7cca37e594..439818ebd2 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -15,12 +15,12 @@ """Flavor action implementations""" +from osc_lib.cli import parseractions from osc_lib import exceptions from osc_lib import utils import six from openstackclient.common import command -from openstackclient.common import parseractions from openstackclient.i18n import _ from openstackclient.identity import common as identity_common diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 7e810898b7..99429c5a24 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -21,6 +21,7 @@ import os import sys +from osc_lib.cli import parseractions from osc_lib import exceptions from osc_lib import utils import six @@ -31,7 +32,6 @@ from novaclient.v1_1 import servers from openstackclient.common import command -from openstackclient.common import parseractions from openstackclient.i18n import _ from openstackclient.identity import common as identity_common diff --git a/openstackclient/identity/v2_0/project.py b/openstackclient/identity/v2_0/project.py index 688849fa69..b03f8381dd 100644 --- a/openstackclient/identity/v2_0/project.py +++ b/openstackclient/identity/v2_0/project.py @@ -16,11 +16,11 @@ """Identity v2 Project action implementations""" from keystoneauth1 import exceptions as ks_exc +from osc_lib.cli import parseractions from osc_lib import utils import six from openstackclient.common import command -from openstackclient.common import parseractions from openstackclient.i18n import _ diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index 2d504950fb..843767aa14 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -16,11 +16,11 @@ """Project action implementations""" from keystoneauth1 import exceptions as ks_exc +from osc_lib.cli import parseractions from osc_lib import utils import six from openstackclient.common import command -from openstackclient.common import parseractions from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index 73fbe29051..f137f262a9 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -18,7 +18,6 @@ import argparse import io import os -import six import sys if os.name == "nt": @@ -27,11 +26,12 @@ msvcrt = None from glanceclient.common import utils as gc_utils +from osc_lib.cli import parseractions from osc_lib import utils +import six from openstackclient.api import utils as api_utils from openstackclient.common import command -from openstackclient.common import parseractions from openstackclient.i18n import _ diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 668e4ed90e..7d663a6c83 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -18,13 +18,13 @@ import argparse from glanceclient.common import utils as gc_utils +from osc_lib.cli import parseractions from osc_lib import exceptions from osc_lib import utils import six from openstackclient.api import utils as api_utils from openstackclient.common import command -from openstackclient.common import parseractions from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 643207d9b3..a040f20f48 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -16,11 +16,11 @@ import argparse import logging +from osc_lib.cli import parseractions from osc_lib import exceptions from osc_lib import utils from openstackclient.common import command -from openstackclient.common import parseractions from openstackclient.i18n import _ from openstackclient.identity import common as identity_common diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index eab8b22ca6..db3a90e114 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -17,10 +17,10 @@ import json import logging +from osc_lib.cli import parseractions from osc_lib import utils from openstackclient.common import command -from openstackclient.common import parseractions from openstackclient.i18n import _ from openstackclient.identity import common as identity_common diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index 0132ef4eb5..7c81078691 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -20,11 +20,11 @@ except ImportError: from novaclient.v1_1 import security_group_rules as compute_secgroup_rules +from osc_lib.cli import parseractions from osc_lib import exceptions from osc_lib import utils import six -from openstackclient.common import parseractions from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 2b12ee03a0..6bdb9a5486 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -15,11 +15,11 @@ import copy +from osc_lib.cli import parseractions from osc_lib import exceptions from osc_lib import utils from openstackclient.common import command -from openstackclient.common import parseractions from openstackclient.i18n import _ from openstackclient.identity import common as identity_common diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index 23993300fe..7e0bb2c0a0 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -13,10 +13,10 @@ """Subnet pool action implementations""" +from osc_lib.cli import parseractions from osc_lib import utils from openstackclient.common import command -from openstackclient.common import parseractions from openstackclient.i18n import _ from openstackclient.identity import common as identity_common diff --git a/openstackclient/object/v1/account.py b/openstackclient/object/v1/account.py index 189f086bb9..8ef3468726 100644 --- a/openstackclient/object/v1/account.py +++ b/openstackclient/object/v1/account.py @@ -13,11 +13,11 @@ """Account v1 action implementations""" +from osc_lib.cli import parseractions from osc_lib import utils import six from openstackclient.common import command -from openstackclient.common import parseractions class SetAccount(command.Command): diff --git a/openstackclient/object/v1/container.py b/openstackclient/object/v1/container.py index b3c6af7498..f26b64a1f4 100644 --- a/openstackclient/object/v1/container.py +++ b/openstackclient/object/v1/container.py @@ -16,11 +16,11 @@ """Container v1 action implementations""" +from osc_lib.cli import parseractions from osc_lib import utils import six from openstackclient.common import command -from openstackclient.common import parseractions class CreateContainer(command.Lister): diff --git a/openstackclient/object/v1/object.py b/openstackclient/object/v1/object.py index 9141c1da45..820780ff39 100644 --- a/openstackclient/object/v1/object.py +++ b/openstackclient/object/v1/object.py @@ -16,11 +16,11 @@ """Object v1 action implementations""" +from osc_lib.cli import parseractions from osc_lib import utils import six from openstackclient.common import command -from openstackclient.common import parseractions class CreateObject(command.Lister): diff --git a/openstackclient/tests/common/test_parseractions.py b/openstackclient/tests/common/test_parseractions.py index 894b224c46..60d4a8cfa2 100644 --- a/openstackclient/tests/common/test_parseractions.py +++ b/openstackclient/tests/common/test_parseractions.py @@ -13,6 +13,9 @@ # under the License. # +# NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release +# or Jun 2017. + import argparse from openstackclient.common import parseractions diff --git a/openstackclient/volume/v1/qos_specs.py b/openstackclient/volume/v1/qos_specs.py index 70f6826901..d26d9af48d 100644 --- a/openstackclient/volume/v1/qos_specs.py +++ b/openstackclient/volume/v1/qos_specs.py @@ -15,11 +15,11 @@ """Volume v1 QoS action implementations""" +from osc_lib.cli import parseractions from osc_lib import utils import six from openstackclient.common import command -from openstackclient.common import parseractions from openstackclient.i18n import _ diff --git a/openstackclient/volume/v1/snapshot.py b/openstackclient/volume/v1/snapshot.py index af0b522924..c931eeffa8 100644 --- a/openstackclient/volume/v1/snapshot.py +++ b/openstackclient/volume/v1/snapshot.py @@ -17,11 +17,11 @@ import copy +from osc_lib.cli import parseractions from osc_lib import utils import six from openstackclient.common import command -from openstackclient.common import parseractions from openstackclient.i18n import _ diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index 359d931ecc..a302ab38d2 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -17,11 +17,11 @@ import argparse +from osc_lib.cli import parseractions from osc_lib import utils import six from openstackclient.common import command -from openstackclient.common import parseractions from openstackclient.i18n import _ diff --git a/openstackclient/volume/v1/volume_type.py b/openstackclient/volume/v1/volume_type.py index e4d22abb98..fb93753076 100644 --- a/openstackclient/volume/v1/volume_type.py +++ b/openstackclient/volume/v1/volume_type.py @@ -15,11 +15,11 @@ """Volume v1 Type action implementations""" +from osc_lib.cli import parseractions from osc_lib import utils import six from openstackclient.common import command -from openstackclient.common import parseractions from openstackclient.i18n import _ diff --git a/openstackclient/volume/v2/qos_specs.py b/openstackclient/volume/v2/qos_specs.py index c4473117b9..43772b55d9 100644 --- a/openstackclient/volume/v2/qos_specs.py +++ b/openstackclient/volume/v2/qos_specs.py @@ -15,11 +15,11 @@ """Volume v2 QoS action implementations""" +from osc_lib.cli import parseractions from osc_lib import utils import six from openstackclient.common import command -from openstackclient.common import parseractions from openstackclient.i18n import _ diff --git a/openstackclient/volume/v2/snapshot.py b/openstackclient/volume/v2/snapshot.py index 99b7462b52..39af1bd2fd 100644 --- a/openstackclient/volume/v2/snapshot.py +++ b/openstackclient/volume/v2/snapshot.py @@ -16,11 +16,11 @@ import copy +from osc_lib.cli import parseractions from osc_lib import utils import six from openstackclient.common import command -from openstackclient.common import parseractions from openstackclient.i18n import _ diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index c6262d1761..c6175c4b6a 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -16,11 +16,11 @@ import copy +from osc_lib.cli import parseractions from osc_lib import utils import six from openstackclient.common import command -from openstackclient.common import parseractions from openstackclient.i18n import _ from openstackclient.identity import common as identity_common diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index 0896ecffc8..edea91fd77 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -14,12 +14,12 @@ """Volume v2 Type action implementations""" +from osc_lib.cli import parseractions from osc_lib import exceptions from osc_lib import utils import six from openstackclient.common import command -from openstackclient.common import parseractions from openstackclient.i18n import _ from openstackclient.identity import common as identity_common From 9e2b8e67307e739008307e977ce545d49d0956a6 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 13 May 2016 17:27:12 -0500 Subject: [PATCH 0984/3095] osc-lib: command Leave command.py and test_command.py as a sanity check during the deprecation period. Change-Id: I24e1b755cbfbcbcaeb5273ec0c9706b82384fc85 --- doc/source/plugins.rst | 4 +--- openstackclient/common/availability_zone.py | 2 +- openstackclient/common/configuration.py | 2 +- openstackclient/common/extension.py | 2 +- openstackclient/common/limits.py | 2 +- openstackclient/common/module.py | 2 +- openstackclient/common/quota.py | 2 +- openstackclient/common/timing.py | 2 +- openstackclient/compute/v2/agent.py | 2 +- openstackclient/compute/v2/aggregate.py | 2 +- openstackclient/compute/v2/console.py | 2 +- openstackclient/compute/v2/fixedip.py | 3 +-- openstackclient/compute/v2/flavor.py | 2 +- openstackclient/compute/v2/floatingip.py | 3 +-- openstackclient/compute/v2/floatingippool.py | 3 +-- openstackclient/compute/v2/host.py | 2 +- openstackclient/compute/v2/hypervisor.py | 2 +- openstackclient/compute/v2/hypervisor_stats.py | 2 +- openstackclient/compute/v2/keypair.py | 2 +- openstackclient/compute/v2/server.py | 2 +- openstackclient/compute/v2/server_backup.py | 2 +- openstackclient/compute/v2/server_group.py | 2 +- openstackclient/compute/v2/service.py | 2 +- openstackclient/compute/v2/usage.py | 2 +- openstackclient/identity/v2_0/catalog.py | 2 +- openstackclient/identity/v2_0/ec2creds.py | 2 +- openstackclient/identity/v2_0/endpoint.py | 2 +- openstackclient/identity/v2_0/project.py | 2 +- openstackclient/identity/v2_0/role.py | 2 +- openstackclient/identity/v2_0/service.py | 2 +- openstackclient/identity/v2_0/token.py | 4 ++-- openstackclient/identity/v2_0/user.py | 2 +- openstackclient/identity/v3/catalog.py | 2 +- openstackclient/identity/v3/consumer.py | 2 +- openstackclient/identity/v3/credential.py | 2 +- openstackclient/identity/v3/domain.py | 2 +- openstackclient/identity/v3/ec2creds.py | 2 +- openstackclient/identity/v3/endpoint.py | 2 +- openstackclient/identity/v3/federation_protocol.py | 2 +- openstackclient/identity/v3/group.py | 2 +- openstackclient/identity/v3/identity_provider.py | 2 +- openstackclient/identity/v3/mapping.py | 2 +- openstackclient/identity/v3/policy.py | 2 +- openstackclient/identity/v3/project.py | 2 +- openstackclient/identity/v3/region.py | 2 +- openstackclient/identity/v3/role.py | 2 +- openstackclient/identity/v3/role_assignment.py | 2 +- openstackclient/identity/v3/service.py | 2 +- openstackclient/identity/v3/service_provider.py | 2 +- openstackclient/identity/v3/token.py | 2 +- openstackclient/identity/v3/trust.py | 2 +- openstackclient/identity/v3/unscoped_saml.py | 2 +- openstackclient/identity/v3/user.py | 2 +- openstackclient/image/v1/image.py | 2 +- openstackclient/image/v2/image.py | 2 +- openstackclient/network/common.py | 2 +- openstackclient/network/v2/address_scope.py | 2 +- openstackclient/network/v2/ip_availability.py | 2 +- openstackclient/network/v2/network.py | 2 +- openstackclient/network/v2/network_segment.py | 2 +- openstackclient/network/v2/port.py | 2 +- openstackclient/network/v2/router.py | 2 +- openstackclient/network/v2/subnet.py | 2 +- openstackclient/network/v2/subnet_pool.py | 2 +- openstackclient/object/v1/account.py | 3 +-- openstackclient/object/v1/container.py | 3 +-- openstackclient/object/v1/object.py | 3 +-- openstackclient/tests/common/test_command.py | 2 +- openstackclient/volume/v1/backup.py | 2 +- openstackclient/volume/v1/qos_specs.py | 2 +- openstackclient/volume/v1/service.py | 2 +- openstackclient/volume/v1/snapshot.py | 2 +- openstackclient/volume/v1/volume.py | 2 +- openstackclient/volume/v1/volume_transfer_request.py | 2 +- openstackclient/volume/v1/volume_type.py | 2 +- openstackclient/volume/v2/backup.py | 2 +- openstackclient/volume/v2/qos_specs.py | 2 +- openstackclient/volume/v2/service.py | 2 +- openstackclient/volume/v2/snapshot.py | 2 +- openstackclient/volume/v2/volume.py | 2 +- openstackclient/volume/v2/volume_transfer_request.py | 2 +- openstackclient/volume/v2/volume_type.py | 2 +- 82 files changed, 83 insertions(+), 91 deletions(-) diff --git a/doc/source/plugins.rst b/doc/source/plugins.rst index 8640efbccd..00aa5137ae 100644 --- a/doc/source/plugins.rst +++ b/doc/source/plugins.rst @@ -153,13 +153,11 @@ the plugin commands: # osc-lib interfaces available to plugins: from osc_lib.cli import parseractions + from osc_lib.command import command from osc_lib import exceptions from osc_lib import logs from osc_lib import utils - # OSC common interfaces available to plugins: - from openstackclient.common import command - class DeleteMypluginobject(command.Command): """Delete mypluginobject""" diff --git a/openstackclient/common/availability_zone.py b/openstackclient/common/availability_zone.py index 6117c918a0..af161d1f61 100644 --- a/openstackclient/common/availability_zone.py +++ b/openstackclient/common/availability_zone.py @@ -16,10 +16,10 @@ import copy from novaclient import exceptions as nova_exceptions +from osc_lib.command import command from osc_lib import utils import six -from openstackclient.common import command from openstackclient.i18n import _ diff --git a/openstackclient/common/configuration.py b/openstackclient/common/configuration.py index ba4de12d81..d6e2ab45bb 100644 --- a/openstackclient/common/configuration.py +++ b/openstackclient/common/configuration.py @@ -13,9 +13,9 @@ """Configuration action implementations""" +from osc_lib.command import command import six -from openstackclient.common import command from openstackclient.i18n import _ REDACTED = "" diff --git a/openstackclient/common/extension.py b/openstackclient/common/extension.py index 6fcf9fa80d..327fc2238c 100644 --- a/openstackclient/common/extension.py +++ b/openstackclient/common/extension.py @@ -17,9 +17,9 @@ import itertools +from osc_lib.command import command from osc_lib import utils -from openstackclient.common import command from openstackclient.i18n import _ diff --git a/openstackclient/common/limits.py b/openstackclient/common/limits.py index 249d1b443d..f7aa82f62c 100644 --- a/openstackclient/common/limits.py +++ b/openstackclient/common/limits.py @@ -17,9 +17,9 @@ import itertools +from osc_lib.command import command from osc_lib import utils -from openstackclient.common import command from openstackclient.i18n import _ from openstackclient.identity import common as identity_common diff --git a/openstackclient/common/module.py b/openstackclient/common/module.py index 91e779fb28..7c5fcd55a2 100644 --- a/openstackclient/common/module.py +++ b/openstackclient/common/module.py @@ -17,10 +17,10 @@ import sys +from osc_lib.command import command from osc_lib import utils import six -from openstackclient.common import command from openstackclient.i18n import _ diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index 6fd1e2f5cc..087d8ea3fd 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -18,10 +18,10 @@ import itertools import sys +from osc_lib.command import command from osc_lib import utils import six -from openstackclient.common import command from openstackclient.i18n import _ diff --git a/openstackclient/common/timing.py b/openstackclient/common/timing.py index 71c2fec7e7..dd2aeb8343 100644 --- a/openstackclient/common/timing.py +++ b/openstackclient/common/timing.py @@ -13,7 +13,7 @@ """Timing Implementation""" -from openstackclient.common import command +from osc_lib.command import command class Timing(command.Lister): diff --git a/openstackclient/compute/v2/agent.py b/openstackclient/compute/v2/agent.py index 5f7050761c..ce6898c122 100644 --- a/openstackclient/compute/v2/agent.py +++ b/openstackclient/compute/v2/agent.py @@ -15,11 +15,11 @@ """Agent action implementations""" +from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils import six -from openstackclient.common import command from openstackclient.i18n import _ diff --git a/openstackclient/compute/v2/aggregate.py b/openstackclient/compute/v2/aggregate.py index 9e44615d4d..8000c93a0e 100644 --- a/openstackclient/compute/v2/aggregate.py +++ b/openstackclient/compute/v2/aggregate.py @@ -17,10 +17,10 @@ """Compute v2 Aggregate action implementations""" from osc_lib.cli import parseractions +from osc_lib.command import command from osc_lib import utils import six -from openstackclient.common import command from openstackclient.i18n import _ diff --git a/openstackclient/compute/v2/console.py b/openstackclient/compute/v2/console.py index ff7f1af6fa..97d3f318f8 100644 --- a/openstackclient/compute/v2/console.py +++ b/openstackclient/compute/v2/console.py @@ -18,10 +18,10 @@ import sys from osc_lib.cli import parseractions +from osc_lib.command import command from osc_lib import utils import six -from openstackclient.common import command from openstackclient.i18n import _ diff --git a/openstackclient/compute/v2/fixedip.py b/openstackclient/compute/v2/fixedip.py index e3b80c5f39..8bd72ca3d6 100644 --- a/openstackclient/compute/v2/fixedip.py +++ b/openstackclient/compute/v2/fixedip.py @@ -15,10 +15,9 @@ """Fixed IP action implementations""" +from osc_lib.command import command from osc_lib import utils -from openstackclient.common import command - class AddFixedIP(command.Command): """Add fixed IP address to server""" diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 439818ebd2..ab2bc85d0f 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -16,11 +16,11 @@ """Flavor action implementations""" from osc_lib.cli import parseractions +from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils import six -from openstackclient.common import command from openstackclient.i18n import _ from openstackclient.identity import common as identity_common diff --git a/openstackclient/compute/v2/floatingip.py b/openstackclient/compute/v2/floatingip.py index 780ea68474..98079fbc38 100644 --- a/openstackclient/compute/v2/floatingip.py +++ b/openstackclient/compute/v2/floatingip.py @@ -15,10 +15,9 @@ """Floating IP action implementations""" +from osc_lib.command import command from osc_lib import utils -from openstackclient.common import command - class AddFloatingIP(command.Command): """Add floating IP address to server""" diff --git a/openstackclient/compute/v2/floatingippool.py b/openstackclient/compute/v2/floatingippool.py index eb7c9e86be..0d46e32b12 100644 --- a/openstackclient/compute/v2/floatingippool.py +++ b/openstackclient/compute/v2/floatingippool.py @@ -15,10 +15,9 @@ """Floating IP Pool action implementations""" +from osc_lib.command import command from osc_lib import utils -from openstackclient.common import command - class ListFloatingIPPool(command.Lister): """List pools of floating IP addresses""" diff --git a/openstackclient/compute/v2/host.py b/openstackclient/compute/v2/host.py index 15396d0cb7..620b4abbab 100644 --- a/openstackclient/compute/v2/host.py +++ b/openstackclient/compute/v2/host.py @@ -15,9 +15,9 @@ """Host action implementations""" +from osc_lib.command import command from osc_lib import utils -from openstackclient.common import command from openstackclient.i18n import _ diff --git a/openstackclient/compute/v2/hypervisor.py b/openstackclient/compute/v2/hypervisor.py index b13cdb4741..a34f2e1d7f 100644 --- a/openstackclient/compute/v2/hypervisor.py +++ b/openstackclient/compute/v2/hypervisor.py @@ -17,10 +17,10 @@ import re +from osc_lib.command import command from osc_lib import utils import six -from openstackclient.common import command from openstackclient.i18n import _ diff --git a/openstackclient/compute/v2/hypervisor_stats.py b/openstackclient/compute/v2/hypervisor_stats.py index 290f54185b..a70f0ce1ca 100644 --- a/openstackclient/compute/v2/hypervisor_stats.py +++ b/openstackclient/compute/v2/hypervisor_stats.py @@ -16,7 +16,7 @@ import six -from openstackclient.common import command +from osc_lib.command import command class ShowHypervisorStats(command.ShowOne): diff --git a/openstackclient/compute/v2/keypair.py b/openstackclient/compute/v2/keypair.py index acf467b303..13c0a99ca1 100644 --- a/openstackclient/compute/v2/keypair.py +++ b/openstackclient/compute/v2/keypair.py @@ -19,11 +19,11 @@ import os import sys +from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils import six -from openstackclient.common import command from openstackclient.i18n import _ diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 99429c5a24..eb954c3631 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -22,6 +22,7 @@ import sys from osc_lib.cli import parseractions +from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils import six @@ -31,7 +32,6 @@ except ImportError: from novaclient.v1_1 import servers -from openstackclient.common import command from openstackclient.i18n import _ from openstackclient.identity import common as identity_common diff --git a/openstackclient/compute/v2/server_backup.py b/openstackclient/compute/v2/server_backup.py index 972db66099..c0e2e5ee4c 100644 --- a/openstackclient/compute/v2/server_backup.py +++ b/openstackclient/compute/v2/server_backup.py @@ -17,12 +17,12 @@ import sys +from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils from oslo_utils import importutils import six -from openstackclient.common import command from openstackclient.i18n import _ diff --git a/openstackclient/compute/v2/server_group.py b/openstackclient/compute/v2/server_group.py index 5763ba2fac..955b147e71 100644 --- a/openstackclient/compute/v2/server_group.py +++ b/openstackclient/compute/v2/server_group.py @@ -15,10 +15,10 @@ """Compute v2 Server Group action implementations""" +from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -from openstackclient.common import command from openstackclient.i18n import _ diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index 206344dc9a..304657836c 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.py @@ -15,10 +15,10 @@ """Service action implementations""" +from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -from openstackclient.common import command from openstackclient.i18n import _ diff --git a/openstackclient/compute/v2/usage.py b/openstackclient/compute/v2/usage.py index cc24346f89..2f35b01b13 100644 --- a/openstackclient/compute/v2/usage.py +++ b/openstackclient/compute/v2/usage.py @@ -18,10 +18,10 @@ import datetime import sys +from osc_lib.command import command from osc_lib import utils import six -from openstackclient.common import command from openstackclient.i18n import _ diff --git a/openstackclient/identity/v2_0/catalog.py b/openstackclient/identity/v2_0/catalog.py index 2de1e8e7a2..62e6b1f97a 100644 --- a/openstackclient/identity/v2_0/catalog.py +++ b/openstackclient/identity/v2_0/catalog.py @@ -13,11 +13,11 @@ """Identity v2 Service Catalog action implementations""" +from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils import six -from openstackclient.common import command from openstackclient.i18n import _ diff --git a/openstackclient/identity/v2_0/ec2creds.py b/openstackclient/identity/v2_0/ec2creds.py index 86f07bce8f..78f65824a6 100644 --- a/openstackclient/identity/v2_0/ec2creds.py +++ b/openstackclient/identity/v2_0/ec2creds.py @@ -16,10 +16,10 @@ """Identity v2 EC2 Credentials action implementations""" +from osc_lib.command import command from osc_lib import utils import six -from openstackclient.common import command from openstackclient.i18n import _ diff --git a/openstackclient/identity/v2_0/endpoint.py b/openstackclient/identity/v2_0/endpoint.py index 89c8a38ba9..4ffa363ef7 100644 --- a/openstackclient/identity/v2_0/endpoint.py +++ b/openstackclient/identity/v2_0/endpoint.py @@ -15,10 +15,10 @@ """Endpoint action implementations""" +from osc_lib.command import command from osc_lib import utils import six -from openstackclient.common import command from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v2_0/project.py b/openstackclient/identity/v2_0/project.py index b03f8381dd..8be482fe57 100644 --- a/openstackclient/identity/v2_0/project.py +++ b/openstackclient/identity/v2_0/project.py @@ -17,10 +17,10 @@ from keystoneauth1 import exceptions as ks_exc from osc_lib.cli import parseractions +from osc_lib.command import command from osc_lib import utils import six -from openstackclient.common import command from openstackclient.i18n import _ diff --git a/openstackclient/identity/v2_0/role.py b/openstackclient/identity/v2_0/role.py index 075234fcdb..4c3fe6e236 100644 --- a/openstackclient/identity/v2_0/role.py +++ b/openstackclient/identity/v2_0/role.py @@ -16,11 +16,11 @@ """Identity v2 Role action implementations""" from keystoneauth1 import exceptions as ks_exc +from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils import six -from openstackclient.common import command from openstackclient.i18n import _ diff --git a/openstackclient/identity/v2_0/service.py b/openstackclient/identity/v2_0/service.py index 631b873798..947f361cf3 100644 --- a/openstackclient/identity/v2_0/service.py +++ b/openstackclient/identity/v2_0/service.py @@ -17,11 +17,11 @@ import argparse +from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils import six -from openstackclient.common import command from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v2_0/token.py b/openstackclient/identity/v2_0/token.py index d708749d30..56f920f313 100644 --- a/openstackclient/identity/v2_0/token.py +++ b/openstackclient/identity/v2_0/token.py @@ -15,10 +15,10 @@ """Identity v2 Token action implementations""" +from osc_lib.command import command +from osc_lib import exceptions import six -from openstackclient.common import command -from openstackclient.common import exceptions from openstackclient.i18n import _ diff --git a/openstackclient/identity/v2_0/user.py b/openstackclient/identity/v2_0/user.py index a10d213b35..7777bab886 100644 --- a/openstackclient/identity/v2_0/user.py +++ b/openstackclient/identity/v2_0/user.py @@ -16,10 +16,10 @@ """Identity v2.0 User action implementations""" from keystoneauth1 import exceptions as ks_exc +from osc_lib.command import command from osc_lib import utils import six -from openstackclient.common import command from openstackclient.i18n import _ diff --git a/openstackclient/identity/v3/catalog.py b/openstackclient/identity/v3/catalog.py index a3286003b8..a6e141c945 100644 --- a/openstackclient/identity/v3/catalog.py +++ b/openstackclient/identity/v3/catalog.py @@ -13,11 +13,11 @@ """Identity v3 Service Catalog action implementations""" +from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils import six -from openstackclient.common import command from openstackclient.i18n import _ diff --git a/openstackclient/identity/v3/consumer.py b/openstackclient/identity/v3/consumer.py index 8aa92f6de2..a4620bf9ef 100644 --- a/openstackclient/identity/v3/consumer.py +++ b/openstackclient/identity/v3/consumer.py @@ -17,10 +17,10 @@ import sys +from osc_lib.command import command from osc_lib import utils import six -from openstackclient.common import command from openstackclient.i18n import _ diff --git a/openstackclient/identity/v3/credential.py b/openstackclient/identity/v3/credential.py index 6c65a20658..eeeddfa554 100644 --- a/openstackclient/identity/v3/credential.py +++ b/openstackclient/identity/v3/credential.py @@ -15,10 +15,10 @@ """Identity v3 Credential action implementations""" +from osc_lib.command import command from osc_lib import utils import six -from openstackclient.common import command from openstackclient.i18n import _ diff --git a/openstackclient/identity/v3/domain.py b/openstackclient/identity/v3/domain.py index b0cd4d553f..8eb7bc91de 100644 --- a/openstackclient/identity/v3/domain.py +++ b/openstackclient/identity/v3/domain.py @@ -18,10 +18,10 @@ import sys from keystoneauth1 import exceptions as ks_exc +from osc_lib.command import command from osc_lib import utils import six -from openstackclient.common import command from openstackclient.i18n import _ diff --git a/openstackclient/identity/v3/ec2creds.py b/openstackclient/identity/v3/ec2creds.py index fe052a3c65..835fe96fd9 100644 --- a/openstackclient/identity/v3/ec2creds.py +++ b/openstackclient/identity/v3/ec2creds.py @@ -12,10 +12,10 @@ """Identity v3 EC2 Credentials action implementations""" +from osc_lib.command import command from osc_lib import utils import six -from openstackclient.common import command from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v3/endpoint.py b/openstackclient/identity/v3/endpoint.py index 8e433fb9eb..367b4e7b1f 100644 --- a/openstackclient/identity/v3/endpoint.py +++ b/openstackclient/identity/v3/endpoint.py @@ -17,10 +17,10 @@ import sys +from osc_lib.command import command from osc_lib import utils import six -from openstackclient.common import command from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v3/federation_protocol.py b/openstackclient/identity/v3/federation_protocol.py index 5050ec6f9a..377ddcceec 100644 --- a/openstackclient/identity/v3/federation_protocol.py +++ b/openstackclient/identity/v3/federation_protocol.py @@ -14,10 +14,10 @@ """Identity v3 Protocols actions implementations""" +from osc_lib.command import command from osc_lib import utils import six -from openstackclient.common import command from openstackclient.i18n import _ diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py index 85087214cd..fb0a25b973 100644 --- a/openstackclient/identity/v3/group.py +++ b/openstackclient/identity/v3/group.py @@ -18,10 +18,10 @@ import sys from keystoneauth1 import exceptions as ks_exc +from osc_lib.command import command from osc_lib import utils import six -from openstackclient.common import command from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v3/identity_provider.py b/openstackclient/identity/v3/identity_provider.py index a082f2f4f7..0b530a26fc 100644 --- a/openstackclient/identity/v3/identity_provider.py +++ b/openstackclient/identity/v3/identity_provider.py @@ -13,10 +13,10 @@ """Identity v3 IdentityProvider action implementations""" +from osc_lib.command import command from osc_lib import utils import six -from openstackclient.common import command from openstackclient.i18n import _ diff --git a/openstackclient/identity/v3/mapping.py b/openstackclient/identity/v3/mapping.py index 723e3bbc4e..5937474e76 100644 --- a/openstackclient/identity/v3/mapping.py +++ b/openstackclient/identity/v3/mapping.py @@ -17,11 +17,11 @@ import json +from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils import six -from openstackclient.common import command from openstackclient.i18n import _ diff --git a/openstackclient/identity/v3/policy.py b/openstackclient/identity/v3/policy.py index f09920318c..68fb273820 100644 --- a/openstackclient/identity/v3/policy.py +++ b/openstackclient/identity/v3/policy.py @@ -17,10 +17,10 @@ import sys +from osc_lib.command import command from osc_lib import utils import six -from openstackclient.common import command from openstackclient.i18n import _ diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index 843767aa14..6145dcf14c 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -17,10 +17,10 @@ from keystoneauth1 import exceptions as ks_exc from osc_lib.cli import parseractions +from osc_lib.command import command from osc_lib import utils import six -from openstackclient.common import command from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v3/region.py b/openstackclient/identity/v3/region.py index da8e36325c..b5e46a9adb 100644 --- a/openstackclient/identity/v3/region.py +++ b/openstackclient/identity/v3/region.py @@ -13,10 +13,10 @@ """Identity v3 Region action implementations""" +from osc_lib.command import command from osc_lib import utils import six -from openstackclient.common import command from openstackclient.i18n import _ diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index 879dbc33d0..1bb2758587 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -18,10 +18,10 @@ import sys from keystoneauth1 import exceptions as ks_exc +from osc_lib.command import command from osc_lib import utils import six -from openstackclient.common import command from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v3/role_assignment.py b/openstackclient/identity/v3/role_assignment.py index 5e1d997a31..39e2336d6f 100644 --- a/openstackclient/identity/v3/role_assignment.py +++ b/openstackclient/identity/v3/role_assignment.py @@ -13,9 +13,9 @@ """Identity v3 Assignment action implementations """ +from osc_lib.command import command from osc_lib import utils -from openstackclient.common import command from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v3/service.py b/openstackclient/identity/v3/service.py index 450b066c14..7beadc9d18 100644 --- a/openstackclient/identity/v3/service.py +++ b/openstackclient/identity/v3/service.py @@ -17,10 +17,10 @@ import sys +from osc_lib.command import command from osc_lib import utils import six -from openstackclient.common import command from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v3/service_provider.py b/openstackclient/identity/v3/service_provider.py index 5bb15463f9..0beea81379 100644 --- a/openstackclient/identity/v3/service_provider.py +++ b/openstackclient/identity/v3/service_provider.py @@ -15,10 +15,10 @@ import sys +from osc_lib.command import command from osc_lib import utils import six -from openstackclient.common import command from openstackclient.i18n import _ diff --git a/openstackclient/identity/v3/token.py b/openstackclient/identity/v3/token.py index 0eefbf3b6e..ecf09693e0 100644 --- a/openstackclient/identity/v3/token.py +++ b/openstackclient/identity/v3/token.py @@ -15,11 +15,11 @@ """Identity v3 Token action implementations""" +from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils import six -from openstackclient.common import command from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v3/trust.py b/openstackclient/identity/v3/trust.py index 0f900250e7..bbc86adb90 100644 --- a/openstackclient/identity/v3/trust.py +++ b/openstackclient/identity/v3/trust.py @@ -15,10 +15,10 @@ import datetime +from osc_lib.command import command from osc_lib import utils import six -from openstackclient.common import command from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v3/unscoped_saml.py b/openstackclient/identity/v3/unscoped_saml.py index 901fa26e37..f116174b32 100644 --- a/openstackclient/identity/v3/unscoped_saml.py +++ b/openstackclient/identity/v3/unscoped_saml.py @@ -17,10 +17,10 @@ the user can list domains and projects they are allowed to access, and request a scoped token.""" +from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -from openstackclient.common import command from openstackclient.i18n import _ diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index b2e60f8f1b..39125e2cd2 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -19,10 +19,10 @@ import sys from keystoneauth1 import exceptions as ks_exc +from osc_lib.command import command from osc_lib import utils import six -from openstackclient.common import command from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index f137f262a9..1644809d76 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -27,11 +27,11 @@ from glanceclient.common import utils as gc_utils from osc_lib.cli import parseractions +from osc_lib.command import command from osc_lib import utils import six from openstackclient.api import utils as api_utils -from openstackclient.common import command from openstackclient.i18n import _ diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 7d663a6c83..62f7bee88c 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -19,12 +19,12 @@ from glanceclient.common import utils as gc_utils from osc_lib.cli import parseractions +from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils import six from openstackclient.api import utils as api_utils -from openstackclient.common import command from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/network/common.py b/openstackclient/network/common.py index 10c61ab1b8..16cc7379ba 100644 --- a/openstackclient/network/common.py +++ b/openstackclient/network/common.py @@ -13,10 +13,10 @@ import abc +from osc_lib.command import command from osc_lib import exceptions import six -from openstackclient.common import command from openstackclient.i18n import _ diff --git a/openstackclient/network/v2/address_scope.py b/openstackclient/network/v2/address_scope.py index fc2ff3f345..bc1a96c326 100644 --- a/openstackclient/network/v2/address_scope.py +++ b/openstackclient/network/v2/address_scope.py @@ -13,10 +13,10 @@ """Address scope action implementations""" +from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -from openstackclient.common import command from openstackclient.i18n import _ from openstackclient.identity import common as identity_common diff --git a/openstackclient/network/v2/ip_availability.py b/openstackclient/network/v2/ip_availability.py index 6974a24626..d429e86c8b 100644 --- a/openstackclient/network/v2/ip_availability.py +++ b/openstackclient/network/v2/ip_availability.py @@ -13,9 +13,9 @@ """IP Availability Info implementations""" +from osc_lib.command import command from osc_lib import utils -from openstackclient.common import command from openstackclient.i18n import _ from openstackclient.identity import common as identity_common diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 81e96fb254..4d01accda9 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -13,9 +13,9 @@ """Network action implementations""" +from osc_lib.command import command from osc_lib import utils -from openstackclient.common import command from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common diff --git a/openstackclient/network/v2/network_segment.py b/openstackclient/network/v2/network_segment.py index e965d2ebb2..bedf15f7d7 100644 --- a/openstackclient/network/v2/network_segment.py +++ b/openstackclient/network/v2/network_segment.py @@ -15,9 +15,9 @@ # TODO(rtheis): Add description and name properties when support is available. +from osc_lib.command import command from osc_lib import utils -from openstackclient.common import command from openstackclient.i18n import _ diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index a040f20f48..1c5db706e3 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -17,10 +17,10 @@ import logging from osc_lib.cli import parseractions +from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -from openstackclient.common import command from openstackclient.i18n import _ from openstackclient.identity import common as identity_common diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index db3a90e114..92ecf84d2c 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -18,9 +18,9 @@ import logging from osc_lib.cli import parseractions +from osc_lib.command import command from osc_lib import utils -from openstackclient.common import command from openstackclient.i18n import _ from openstackclient.identity import common as identity_common diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 6bdb9a5486..ceb1cb1471 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -16,10 +16,10 @@ import copy from osc_lib.cli import parseractions +from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -from openstackclient.common import command from openstackclient.i18n import _ from openstackclient.identity import common as identity_common diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index 7e0bb2c0a0..79f98fd919 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -14,9 +14,9 @@ """Subnet pool action implementations""" from osc_lib.cli import parseractions +from osc_lib.command import command from osc_lib import utils -from openstackclient.common import command from openstackclient.i18n import _ from openstackclient.identity import common as identity_common diff --git a/openstackclient/object/v1/account.py b/openstackclient/object/v1/account.py index 8ef3468726..801fe4504a 100644 --- a/openstackclient/object/v1/account.py +++ b/openstackclient/object/v1/account.py @@ -14,11 +14,10 @@ """Account v1 action implementations""" from osc_lib.cli import parseractions +from osc_lib.command import command from osc_lib import utils import six -from openstackclient.common import command - class SetAccount(command.Command): """Set account properties""" diff --git a/openstackclient/object/v1/container.py b/openstackclient/object/v1/container.py index f26b64a1f4..2b021ec25f 100644 --- a/openstackclient/object/v1/container.py +++ b/openstackclient/object/v1/container.py @@ -17,11 +17,10 @@ from osc_lib.cli import parseractions +from osc_lib.command import command from osc_lib import utils import six -from openstackclient.common import command - class CreateContainer(command.Lister): """Create new container""" diff --git a/openstackclient/object/v1/object.py b/openstackclient/object/v1/object.py index 820780ff39..39dba3d5c7 100644 --- a/openstackclient/object/v1/object.py +++ b/openstackclient/object/v1/object.py @@ -17,11 +17,10 @@ from osc_lib.cli import parseractions +from osc_lib.command import command from osc_lib import utils import six -from openstackclient.common import command - class CreateObject(command.Lister): """Upload object to container""" diff --git a/openstackclient/tests/common/test_command.py b/openstackclient/tests/common/test_command.py index 658bc895c0..a8bcf6a81a 100644 --- a/openstackclient/tests/common/test_command.py +++ b/openstackclient/tests/common/test_command.py @@ -14,9 +14,9 @@ import mock +from osc_lib.command import command from osc_lib import exceptions -from openstackclient.common import command from openstackclient.tests import fakes as test_fakes from openstackclient.tests import utils as test_utils diff --git a/openstackclient/volume/v1/backup.py b/openstackclient/volume/v1/backup.py index f70d9b42e1..212c74eff2 100644 --- a/openstackclient/volume/v1/backup.py +++ b/openstackclient/volume/v1/backup.py @@ -17,10 +17,10 @@ import copy +from osc_lib.command import command from osc_lib import utils import six -from openstackclient.common import command from openstackclient.i18n import _ diff --git a/openstackclient/volume/v1/qos_specs.py b/openstackclient/volume/v1/qos_specs.py index d26d9af48d..56a96256ac 100644 --- a/openstackclient/volume/v1/qos_specs.py +++ b/openstackclient/volume/v1/qos_specs.py @@ -16,10 +16,10 @@ """Volume v1 QoS action implementations""" from osc_lib.cli import parseractions +from osc_lib.command import command from osc_lib import utils import six -from openstackclient.common import command from openstackclient.i18n import _ diff --git a/openstackclient/volume/v1/service.py b/openstackclient/volume/v1/service.py index c065b526bd..2df38573d4 100644 --- a/openstackclient/volume/v1/service.py +++ b/openstackclient/volume/v1/service.py @@ -14,9 +14,9 @@ """Service action implementations""" +from osc_lib.command import command from osc_lib import utils -from openstackclient.common import command from openstackclient.i18n import _ diff --git a/openstackclient/volume/v1/snapshot.py b/openstackclient/volume/v1/snapshot.py index c931eeffa8..41127af5dd 100644 --- a/openstackclient/volume/v1/snapshot.py +++ b/openstackclient/volume/v1/snapshot.py @@ -18,10 +18,10 @@ import copy from osc_lib.cli import parseractions +from osc_lib.command import command from osc_lib import utils import six -from openstackclient.common import command from openstackclient.i18n import _ diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index a302ab38d2..7a3bfb971b 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -18,10 +18,10 @@ import argparse from osc_lib.cli import parseractions +from osc_lib.command import command from osc_lib import utils import six -from openstackclient.common import command from openstackclient.i18n import _ diff --git a/openstackclient/volume/v1/volume_transfer_request.py b/openstackclient/volume/v1/volume_transfer_request.py index 9cffe98831..5d8ff6839c 100644 --- a/openstackclient/volume/v1/volume_transfer_request.py +++ b/openstackclient/volume/v1/volume_transfer_request.py @@ -14,9 +14,9 @@ """Volume v2 transfer action implementations""" +from osc_lib.command import command from osc_lib import utils -from openstackclient.common import command from openstackclient.i18n import _ diff --git a/openstackclient/volume/v1/volume_type.py b/openstackclient/volume/v1/volume_type.py index fb93753076..289966e52e 100644 --- a/openstackclient/volume/v1/volume_type.py +++ b/openstackclient/volume/v1/volume_type.py @@ -16,10 +16,10 @@ """Volume v1 Type action implementations""" from osc_lib.cli import parseractions +from osc_lib.command import command from osc_lib import utils import six -from openstackclient.common import command from openstackclient.i18n import _ diff --git a/openstackclient/volume/v2/backup.py b/openstackclient/volume/v2/backup.py index 371ddafddf..6a44e30c57 100644 --- a/openstackclient/volume/v2/backup.py +++ b/openstackclient/volume/v2/backup.py @@ -16,10 +16,10 @@ import copy +from osc_lib.command import command from osc_lib import utils import six -from openstackclient.common import command from openstackclient.i18n import _ diff --git a/openstackclient/volume/v2/qos_specs.py b/openstackclient/volume/v2/qos_specs.py index 43772b55d9..7ec272b38e 100644 --- a/openstackclient/volume/v2/qos_specs.py +++ b/openstackclient/volume/v2/qos_specs.py @@ -16,10 +16,10 @@ """Volume v2 QoS action implementations""" from osc_lib.cli import parseractions +from osc_lib.command import command from osc_lib import utils import six -from openstackclient.common import command from openstackclient.i18n import _ diff --git a/openstackclient/volume/v2/service.py b/openstackclient/volume/v2/service.py index c065b526bd..2df38573d4 100644 --- a/openstackclient/volume/v2/service.py +++ b/openstackclient/volume/v2/service.py @@ -14,9 +14,9 @@ """Service action implementations""" +from osc_lib.command import command from osc_lib import utils -from openstackclient.common import command from openstackclient.i18n import _ diff --git a/openstackclient/volume/v2/snapshot.py b/openstackclient/volume/v2/snapshot.py index 39af1bd2fd..439904e744 100644 --- a/openstackclient/volume/v2/snapshot.py +++ b/openstackclient/volume/v2/snapshot.py @@ -17,10 +17,10 @@ import copy from osc_lib.cli import parseractions +from osc_lib.command import command from osc_lib import utils import six -from openstackclient.common import command from openstackclient.i18n import _ diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index c6175c4b6a..b1a3af2efc 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -17,10 +17,10 @@ import copy from osc_lib.cli import parseractions +from osc_lib.command import command from osc_lib import utils import six -from openstackclient.common import command from openstackclient.i18n import _ from openstackclient.identity import common as identity_common diff --git a/openstackclient/volume/v2/volume_transfer_request.py b/openstackclient/volume/v2/volume_transfer_request.py index 9cffe98831..5d8ff6839c 100644 --- a/openstackclient/volume/v2/volume_transfer_request.py +++ b/openstackclient/volume/v2/volume_transfer_request.py @@ -14,9 +14,9 @@ """Volume v2 transfer action implementations""" +from osc_lib.command import command from osc_lib import utils -from openstackclient.common import command from openstackclient.i18n import _ diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index edea91fd77..dc6b4f9b27 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -15,11 +15,11 @@ """Volume v2 Type action implementations""" from osc_lib.cli import parseractions +from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils import six -from openstackclient.common import command from openstackclient.i18n import _ from openstackclient.identity import common as identity_common From a55eb915a0d563ead7bd90ad91b9c86e98e87f7c Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 13 May 2016 17:34:03 -0500 Subject: [PATCH 0985/3095] osc-lib: timing Change-Id: I3fe27d98efa5090e084c676f7f8e6dad0157ed21 --- openstackclient/common/timing.py | 32 ++----- openstackclient/shell.py | 2 +- openstackclient/tests/common/test_timing.py | 94 --------------------- 3 files changed, 9 insertions(+), 119 deletions(-) delete mode 100644 openstackclient/tests/common/test_timing.py diff --git a/openstackclient/common/timing.py b/openstackclient/common/timing.py index dd2aeb8343..facbec350d 100644 --- a/openstackclient/common/timing.py +++ b/openstackclient/common/timing.py @@ -11,31 +11,15 @@ # under the License. # -"""Timing Implementation""" +# NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release +# or Jun 2017. -from osc_lib.command import command +import sys +from osc_lib.command.timing import * # noqa -class Timing(command.Lister): - """Show timing data""" - def take_action(self, parsed_args): - column_headers = ( - 'URL', - 'Seconds', - ) - - results = [] - total = 0.0 - for url, td in self.app.timing_data: - # NOTE(dtroyer): Take the long way here because total_seconds() - # was added in py27. - sec = (td.microseconds + (td.seconds + td.days * - 86400) * 1e6) / 1e6 - total += sec - results.append((url, sec)) - results.append(('Total', total)) - return ( - column_headers, - results, - ) +sys.stderr.write( + "WARNING: %s is deprecated and will be removed after Jun 2017. " + "Please use osc_lib.command.timing\n" % __name__ +) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 3e899cb51c..a15e04dd69 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -26,6 +26,7 @@ from cliff import command from cliff import complete from cliff import help +from osc_lib.command import timing from osc_lib import exceptions as exc from osc_lib import logs from osc_lib import utils @@ -35,7 +36,6 @@ import openstackclient from openstackclient.common import clientmanager from openstackclient.common import commandmanager -from openstackclient.common import timing from os_client_config import config as cloud_config diff --git a/openstackclient/tests/common/test_timing.py b/openstackclient/tests/common/test_timing.py deleted file mode 100644 index e33bb7ae27..0000000000 --- a/openstackclient/tests/common/test_timing.py +++ /dev/null @@ -1,94 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - -"""Test Timing pseudo-command""" - -import datetime - -from openstackclient.common import timing -from openstackclient.tests import fakes -from openstackclient.tests import utils - - -timing_url = 'GET http://localhost:5000' -timing_elapsed = 0.872809 - - -class FakeGenericClient(object): - - def __init__(self, **kwargs): - self.auth_token = kwargs['token'] - self.management_url = kwargs['endpoint'] - - -class TestTiming(utils.TestCommand): - - columns = ( - 'URL', - 'Seconds', - ) - - def setUp(self): - super(TestTiming, self).setUp() - - self.app.timing_data = [] - - self.app.client_manager.compute = FakeGenericClient( - endpoint=fakes.AUTH_URL, - token=fakes.AUTH_TOKEN, - ) - - self.app.client_manager.volume = FakeGenericClient( - endpoint=fakes.AUTH_URL, - token=fakes.AUTH_TOKEN, - ) - - # Get the command object to test - self.cmd = timing.Timing(self.app, None) - - def test_timing_list_no_data(self): - arglist = [] - verifylist = [] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # In base command class Lister in cliff, abstract method take_action() - # returns a tuple containing the column names and an iterable - # containing the data to be listed. - columns, data = self.cmd.take_action(parsed_args) - - self.assertEqual(self.columns, columns) - datalist = [ - ('Total', 0.0,) - ] - self.assertEqual(datalist, data) - - def test_timing_list(self): - self.app.timing_data = [( - timing_url, - datetime.timedelta(microseconds=timing_elapsed * 1000000), - )] - - arglist = [] - verifylist = [] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # In base command class Lister in cliff, abstract method take_action() - # returns a tuple containing the column names and an iterable - # containing the data to be listed. - columns, data = self.cmd.take_action(parsed_args) - self.assertEqual(self.columns, columns) - datalist = [ - (timing_url, timing_elapsed), - ('Total', timing_elapsed), - ] - self.assertEqual(datalist, data) From 304f565439c5950587f7ad31a2f799076e702036 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Mon, 13 Jun 2016 16:44:21 +0800 Subject: [PATCH 0986/3095] Fix i18n problems for common files in identity Some missing parts in identity. Change-Id: I8777b845613d7d7df36ac3c198da552e11aaad1b Partial-bug: #1574965 --- openstackclient/identity/client.py | 7 +++--- openstackclient/identity/common.py | 35 +++++++++++++++--------------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/openstackclient/identity/client.py b/openstackclient/identity/client.py index 1868b2249d..be7b643f5c 100644 --- a/openstackclient/identity/client.py +++ b/openstackclient/identity/client.py @@ -19,6 +19,7 @@ from osc_lib import utils from openstackclient.api import auth +from openstackclient.i18n import _ LOG = logging.getLogger(__name__) @@ -65,9 +66,9 @@ def build_option_parser(parser): '--os-identity-api-version', metavar='', default=utils.env('OS_IDENTITY_API_VERSION'), - help='Identity API version, default=' + - DEFAULT_API_VERSION + - ' (Env: OS_IDENTITY_API_VERSION)') + help=_('Identity API version, default=%s ' + '(Env: OS_IDENTITY_API_VERSION)') % DEFAULT_API_VERSION, + ) return auth.build_auth_plugins_option_parser(parser) diff --git a/openstackclient/identity/common.py b/openstackclient/identity/common.py index 16ee63e162..cd767d2f46 100644 --- a/openstackclient/identity/common.py +++ b/openstackclient/identity/common.py @@ -23,6 +23,8 @@ from osc_lib import exceptions from osc_lib import utils +from openstackclient.i18n import _ + def find_service(identity_client, name_type_or_id): """Find a service by id, name or type.""" @@ -37,14 +39,12 @@ def find_service(identity_client, name_type_or_id): # FIXME(dtroyer): This exception should eventually come from # common client exceptions except identity_exc.NotFound: - msg = ("No service with a type, name or ID of '%s' exists." - % name_type_or_id) - raise exceptions.CommandError(msg) + msg = _("No service with a type, name or ID of '%s' exists.") + raise exceptions.CommandError(msg % name_type_or_id) except identity_exc.NoUniqueMatch: - msg = ("Multiple service matches found for '%s', " - "use an ID to be more specific." - % name_type_or_id) - raise exceptions.CommandError(msg) + msg = _("Multiple service matches found for '%s', " + "use an ID to be more specific.") + raise exceptions.CommandError(msg % name_type_or_id) def _get_domain_id_if_requested(identity_client, domain_name_or_id): @@ -131,9 +131,9 @@ def add_user_domain_option_to_parser(parser): parser.add_argument( '--user-domain', metavar='', - help=('Domain the user belongs to (name or ID). ' - 'This can be used in case collisions between user names ' - 'exist.') + help=_('Domain the user belongs to (name or ID). ' + 'This can be used in case collisions between user names ' + 'exist.'), ) @@ -141,9 +141,9 @@ def add_group_domain_option_to_parser(parser): parser.add_argument( '--group-domain', metavar='', - help=('Domain the group belongs to (name or ID). ' - 'This can be used in case collisions between group names ' - 'exist.') + help=_('Domain the group belongs to (name or ID). ' + 'This can be used in case collisions between group names ' + 'exist.'), ) @@ -151,9 +151,9 @@ def add_project_domain_option_to_parser(parser): parser.add_argument( '--project-domain', metavar='', - help=('Domain the project belongs to (name or ID). ' - 'This can be used in case collisions between project names ' - 'exist.') + help=_('Domain the project belongs to (name or ID). ' + 'This can be used in case collisions between project names ' + 'exist.'), ) @@ -162,5 +162,6 @@ def add_inherited_option_to_parser(parser): '--inherited', action='store_true', default=False, - help=('Specifies if the role grant is inheritable to the sub projects') + help=_('Specifies if the role grant is inheritable to the sub ' + 'projects'), ) From c4b590748a059f760beb74509efbf5a825e24d0f Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 14 Jun 2016 14:51:37 +0800 Subject: [PATCH 0987/3095] Fix errors in ``set/unset flavor`` unit tests 1. We should assert flavor.set_keys() is called correctly or not called in ``set flavor`` unit tests. 2. Commit c7e6973ff50ff84af9ad55f7bdaeeea83ae40f0b from me removed test_flavor_unset_no_project which used to test if --project was specified as '', and assert CommandError was raised, which is incorrect. So I removed it. But after looking into the code, I think we should not remove it, but specify nothing after --project, and assert ParserException was raised. So in this patch, we fix it as so. ('--project', '') --> ('--project') assert CommandError --> assert ParserException Change-Id: Ifd33c72d5b7581aaabffb09e9b5e38ecc67e18c0 --- .../tests/compute/v2/test_flavor.py | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/openstackclient/tests/compute/v2/test_flavor.py b/openstackclient/tests/compute/v2/test_flavor.py index 27b53bbfe1..4365a540bc 100644 --- a/openstackclient/tests/compute/v2/test_flavor.py +++ b/openstackclient/tests/compute/v2/test_flavor.py @@ -469,6 +469,7 @@ def test_flavor_set_property(self): result = self.cmd.take_action(parsed_args) self.flavors_mock.find.assert_called_with(name=parsed_args.flavor, is_public=None) + self.flavor.set_keys.assert_called_with({'FOO': '"B A R"'}) self.assertIsNone(result) def test_flavor_set_project(self): @@ -483,12 +484,15 @@ def test_flavor_set_project(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.assertIsNone(result) + self.flavors_mock.find.assert_called_with(name=parsed_args.flavor, + is_public=None) self.flavor_access_mock.add_tenant_access.assert_called_with( self.flavor.id, identity_fakes.project_id, ) + self.flavor.set_keys.assert_not_called() + self.assertIsNone(result) def test_flavor_set_no_project(self): arglist = [ @@ -496,7 +500,7 @@ def test_flavor_set_no_project(self): self.flavor.id, ] verifylist = [ - ('project', ''), + ('project', None), ('flavor', self.flavor.id), ] self.assertRaises(tests_utils.ParserException, self.check_parser, @@ -509,12 +513,8 @@ def test_flavor_set_no_flavor(self): verifylist = [ ('project', identity_fakes.project_id), ] - - self.assertRaises(tests_utils.ParserException, - self.check_parser, - self.cmd, - arglist, - verifylist) + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) def test_flavor_set_with_unexist_flavor(self): self.flavors_mock.get.side_effect = exceptions.NotFound(None) @@ -541,7 +541,6 @@ def test_flavor_set_nothing(self): verifylist = [ ('flavor', self.flavor.id), ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) @@ -672,6 +671,18 @@ def test_flavor_unset_project(self): self.flavor.unset_keys.assert_not_called() self.assertIsNone(result) + def test_flavor_unset_no_project(self): + arglist = [ + '--project', + self.flavor.id, + ] + verifylist = [ + ('project', None), + ('flavor', self.flavor.id), + ] + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + def test_flavor_unset_no_flavor(self): arglist = [ '--project', identity_fakes.project_id, From eccd943acc9526311fcfc318280b4be51c42a566 Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Mon, 6 Jun 2016 17:51:56 +0800 Subject: [PATCH 0988/3095] Support compute service force down/up Aims to evacuate servers from compute host as soon as possible, operators might set the compute service force down manually. Novaclient support the behavior, this patch support it in OSC. Change-Id: I22ff1c5d670c449771fdcb3f4f39cd82f428531a Closes-Bug: #1589348 --- .../command-objects/compute-service.rst | 9 ++ openstackclient/compute/v2/service.py | 69 +++++++++--- .../tests/compute/v2/test_service.py | 102 +++++++++++++++++- .../notes/bug-1589348-4a612a4efc7ed0e5.yaml | 5 + 4 files changed, 165 insertions(+), 20 deletions(-) create mode 100644 releasenotes/notes/bug-1589348-4a612a4efc7ed0e5.yaml diff --git a/doc/source/command-objects/compute-service.rst b/doc/source/command-objects/compute-service.rst index 7fe92ad313..f43b5fa636 100644 --- a/doc/source/command-objects/compute-service.rst +++ b/doc/source/command-objects/compute-service.rst @@ -58,6 +58,7 @@ Set service command os compute service set [--enable | --disable] [--disable-reason ] + [--up | --down] .. _compute-service-set: @@ -73,6 +74,14 @@ Set service command Reason for disabling the service (in quotes). Should be used with --disable option. +.. option:: --up + + Force up service + +.. option:: --down + + Force down service + .. describe:: Name of host diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index 304657836c..18631837fa 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.py @@ -20,6 +20,7 @@ from osc_lib import utils from openstackclient.i18n import _ +from openstackclient.i18n import _LE class DeleteService(command.Command): @@ -127,6 +128,17 @@ def get_parser(self, prog_name): help=_("Reason for disabling the service (in quotas). " "Should be used with --disable option.") ) + up_down_group = parser.add_mutually_exclusive_group() + up_down_group.add_argument( + '--up', + action='store_true', + help=_('Force up service'), + ) + up_down_group.add_argument( + '--down', + action='store_true', + help=_('Force down service'), + ) return parser def take_action(self, parsed_args): @@ -139,20 +151,45 @@ def take_action(self, parsed_args): "--disable specified.") raise exceptions.CommandError(msg) + result = 0 enabled = None - if parsed_args.enable: - enabled = True - if parsed_args.disable: - enabled = False - - if enabled is None: - return - elif enabled: - cs.enable(parsed_args.host, parsed_args.service) - else: - if parsed_args.disable_reason: - cs.disable_log_reason(parsed_args.host, - parsed_args.service, - parsed_args.disable_reason) - else: - cs.disable(parsed_args.host, parsed_args.service) + try: + if parsed_args.enable: + enabled = True + if parsed_args.disable: + enabled = False + + if enabled is not None: + if enabled: + cs.enable(parsed_args.host, parsed_args.service) + else: + if parsed_args.disable_reason: + cs.disable_log_reason(parsed_args.host, + parsed_args.service, + parsed_args.disable_reason) + else: + cs.disable(parsed_args.host, parsed_args.service) + except Exception: + status = "enabled" if enabled else "disabled" + self.log.error(_LE("Failed to set service status to %s"), status) + result += 1 + + force_down = None + try: + if parsed_args.down: + force_down = True + if parsed_args.up: + force_down = False + if force_down is not None: + cs.force_down(parsed_args.host, parsed_args.service, + force_down=force_down) + except Exception: + state = "down" if force_down else "up" + self.log.error(_LE("Failed to set service state to %s"), state) + result += 1 + + if result > 0: + msg = _("Compute service %(service)s of host %(host)s failed to " + "set.") % {"service": parsed_args.service, + "host": parsed_args.host} + raise exceptions.CommandError(msg) diff --git a/openstackclient/tests/compute/v2/test_service.py b/openstackclient/tests/compute/v2/test_service.py index 3f7413408e..b360c9dce4 100644 --- a/openstackclient/tests/compute/v2/test_service.py +++ b/openstackclient/tests/compute/v2/test_service.py @@ -13,6 +13,8 @@ # under the License. # +import mock + from osc_lib import exceptions from openstackclient.compute.v2 import service @@ -225,8 +227,12 @@ def test_service_set_only_with_disable_reason(self): ('service', self.service.binary), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.assertRaises(exceptions.CommandError, self.cmd.take_action, - parsed_args) + try: + self.cmd.take_action(parsed_args) + self.fail("CommandError should be raised.") + except exceptions.CommandError as e: + self.assertEqual("Cannot specify option --disable-reason without " + "--disable specified.", str(e)) def test_service_set_enable_with_disable_reason(self): reason = 'earthquake' @@ -243,5 +249,93 @@ def test_service_set_enable_with_disable_reason(self): ('service', self.service.binary), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.assertRaises(exceptions.CommandError, self.cmd.take_action, - parsed_args) + try: + self.cmd.take_action(parsed_args) + self.fail("CommandError should be raised.") + except exceptions.CommandError as e: + self.assertEqual("Cannot specify option --disable-reason without " + "--disable specified.", str(e)) + + def test_service_set_state_up(self): + arglist = [ + '--up', + self.service.host, + self.service.binary, + ] + verifylist = [ + ('up', True), + ('host', self.service.host), + ('service', self.service.binary), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.service_mock.force_down.assert_called_once_with( + self.service.host, self.service.binary, force_down=False) + self.assertNotCalled(self.service_mock.enable) + self.assertNotCalled(self.service_mock.disable) + self.assertIsNone(result) + + def test_service_set_state_down(self): + arglist = [ + '--down', + self.service.host, + self.service.binary, + ] + verifylist = [ + ('down', True), + ('host', self.service.host), + ('service', self.service.binary), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.service_mock.force_down.assert_called_once_with( + self.service.host, self.service.binary, force_down=True) + self.assertNotCalled(self.service_mock.enable) + self.assertNotCalled(self.service_mock.disable) + self.assertIsNone(result) + + def test_service_set_enable_and_state_down(self): + arglist = [ + '--enable', + '--down', + self.service.host, + self.service.binary, + ] + verifylist = [ + ('enable', True), + ('down', True), + ('host', self.service.host), + ('service', self.service.binary), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.service_mock.enable.assert_called_once_with( + self.service.host, self.service.binary) + self.service_mock.force_down.assert_called_once_with( + self.service.host, self.service.binary, force_down=True) + self.assertIsNone(result) + + def test_service_set_enable_and_state_down_with_exception(self): + arglist = [ + '--enable', + '--down', + self.service.host, + self.service.binary, + ] + verifylist = [ + ('enable', True), + ('down', True), + ('host', self.service.host), + ('service', self.service.binary), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + with mock.patch.object(self.cmd.log, 'error') as mock_log: + with mock.patch.object(self.service_mock, 'enable', + side_effect=Exception()): + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, parsed_args) + mock_log.assert_called_once_with( + "Failed to set service status to %s", "enabled") + self.service_mock.force_down.assert_called_once_with( + self.service.host, self.service.binary, force_down=True) diff --git a/releasenotes/notes/bug-1589348-4a612a4efc7ed0e5.yaml b/releasenotes/notes/bug-1589348-4a612a4efc7ed0e5.yaml new file mode 100644 index 0000000000..cee2cbf05d --- /dev/null +++ b/releasenotes/notes/bug-1589348-4a612a4efc7ed0e5.yaml @@ -0,0 +1,5 @@ +--- +features: + - Add options ``--up`` and ``--down`` for compute v2 ``compute service set`` + command to support force up/down compute service. + [Bug `1589348 `_] From 7c603e4a67c3d44367afbf2f2f6811d2436295e0 Mon Sep 17 00:00:00 2001 From: Stuart McLaren Date: Tue, 14 Jun 2016 11:16:12 +0000 Subject: [PATCH 0989/3095] Ensure endpoint type is used for network commands Currently OS_ENDPOINT_TYPE and --os-interface are being ignored for network commands. This means the public URL is always used. Make sure that these are picked up correctly so we hit the correct endpoint (internal/admin/etc.) for commands such as: $ openstack --os-interface internal network list Change-Id: Iac05204e3056e386d84d3644b5da1a2bb322bb0a Closes-bug: 1592368 --- openstackclient/network/client.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openstackclient/network/client.py b/openstackclient/network/client.py index c81b7d8768..d12987dd5d 100644 --- a/openstackclient/network/client.py +++ b/openstackclient/network/client.py @@ -36,6 +36,7 @@ def make_client(instance): prof = profile.Profile() prof.set_region(API_NAME, instance._region_name) prof.set_version(API_NAME, instance._api_version[API_NAME]) + prof.set_interface(API_NAME, instance._interface) conn = connection.Connection(authenticator=instance.session.auth, verify=instance.session.verify, cert=instance.session.cert, From 6357202971d7172c20679c4a468b1f15311fe5ce Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Tue, 14 Jun 2016 09:49:24 -0500 Subject: [PATCH 0990/3095] Fix foundation copyrights The OpenStack LLC copyrights date from before OpenStack was transferred to the foundation. These appear to be getting copy-pasted to new files so at least should reflect the correct entity. Change-Id: I02953d752cb24ead6aa4ad8bfe257a48317c9f13 --- openstackclient/compute/client.py | 2 +- openstackclient/compute/v2/host.py | 2 +- openstackclient/compute/v2/hypervisor.py | 2 +- openstackclient/compute/v2/service.py | 2 +- openstackclient/tests/test_shell.py | 2 +- openstackclient/volume/client.py | 2 +- openstackclient/volume/v1/snapshot.py | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/openstackclient/compute/client.py b/openstackclient/compute/client.py index a83700dba7..1583676a8c 100644 --- a/openstackclient/compute/client.py +++ b/openstackclient/compute/client.py @@ -1,4 +1,4 @@ -# Copyright 2012-2013 OpenStack, LLC. +# Copyright 2012-2013 OpenStack Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain diff --git a/openstackclient/compute/v2/host.py b/openstackclient/compute/v2/host.py index 620b4abbab..764b22af0b 100644 --- a/openstackclient/compute/v2/host.py +++ b/openstackclient/compute/v2/host.py @@ -1,4 +1,4 @@ -# Copyright 2013 OpenStack, LLC. +# Copyright 2012-2013 OpenStack Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain diff --git a/openstackclient/compute/v2/hypervisor.py b/openstackclient/compute/v2/hypervisor.py index a34f2e1d7f..00625050a7 100644 --- a/openstackclient/compute/v2/hypervisor.py +++ b/openstackclient/compute/v2/hypervisor.py @@ -1,4 +1,4 @@ -# Copyright 2013 OpenStack, LLC. +# Copyright 2012-2013 OpenStack Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index 304657836c..6db5acc3e8 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.py @@ -1,4 +1,4 @@ -# Copyright 2013 OpenStack, LLC. +# Copyright 2012-2013 OpenStack Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain diff --git a/openstackclient/tests/test_shell.py b/openstackclient/tests/test_shell.py index 90454fc22e..7d0bbd12e4 100644 --- a/openstackclient/tests/test_shell.py +++ b/openstackclient/tests/test_shell.py @@ -1,4 +1,4 @@ -# Copyright 2012-2013 OpenStack, LLC. +# Copyright 2012-2013 OpenStack Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain diff --git a/openstackclient/volume/client.py b/openstackclient/volume/client.py index cc19546044..ea0c441c69 100644 --- a/openstackclient/volume/client.py +++ b/openstackclient/volume/client.py @@ -1,4 +1,4 @@ -# Copyright 2012-2013 OpenStack, LLC. +# Copyright 2012-2013 OpenStack Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain diff --git a/openstackclient/volume/v1/snapshot.py b/openstackclient/volume/v1/snapshot.py index 41127af5dd..bb3a1fc354 100644 --- a/openstackclient/volume/v1/snapshot.py +++ b/openstackclient/volume/v1/snapshot.py @@ -1,4 +1,4 @@ -# Copyright 2012-2013 OpenStack, LLC. +# Copyright 2012-2013 OpenStack Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain From f25a3519c525cd8f3ff04c841b63c10f640c27f6 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Sun, 12 Jun 2016 19:59:10 +0800 Subject: [PATCH 0991/3095] Fix missing i18n supports in api/ and shell.py Change-Id: I28d79d7f44b27d2b600dedad2a3601180650ad83 Partial-bug: #1574965 --- openstackclient/api/api.py | 22 ++++++++++----- openstackclient/api/auth.py | 18 ++++++------- openstackclient/api/auth_plugin.py | 6 +++-- openstackclient/shell.py | 43 +++++++++++++++++------------- 4 files changed, 52 insertions(+), 37 deletions(-) diff --git a/openstackclient/api/api.py b/openstackclient/api/api.py index 0b00ff5067..04d88f31e9 100644 --- a/openstackclient/api/api.py +++ b/openstackclient/api/api.py @@ -19,6 +19,8 @@ from keystoneauth1 import session as ks_session from osc_lib import exceptions +from openstackclient.i18n import _ + class KeystoneSession(object): """Wrapper for the Keystone Session @@ -254,9 +256,11 @@ def getlist(kw): if len(data) == 1: return data[0] if len(data) > 1: - msg = "Multiple %s exist with %s='%s'" + msg = _("Multiple %(resource)s exist with %(attr)s='%(value)s'") raise exceptions.CommandError( - msg % (resource, attr, value), + msg % {'resource': resource, + 'attr': attr, + 'value': value} ) # Search by id @@ -264,8 +268,12 @@ def getlist(kw): data = getlist(kwargs) if len(data) == 1: return data[0] - msg = "No %s with a %s or ID of '%s' found" - raise exceptions.CommandError(msg % (resource, attr, value)) + msg = _("No %(resource)s with a %(attr)s or ID of '%(value)s' found") + raise exceptions.CommandError( + msg % {'resource': resource, + 'attr': attr, + 'value': value} + ) def find_bulk( self, @@ -313,10 +321,10 @@ def find_one( bulk_list = self.find_bulk(path, **kwargs) num_bulk = len(bulk_list) if num_bulk == 0: - msg = "none found" + msg = _("none found") raise exceptions.NotFound(msg) elif num_bulk > 1: - msg = "many found" + msg = _("many found") raise RuntimeError(msg) return bulk_list[0] @@ -343,7 +351,7 @@ def find( try: ret = self.find_one("/%s/detail" % (path), **kwargs) except ks_exceptions.NotFound: - msg = "%s not found" % value + msg = _("%s not found") % value raise exceptions.NotFound(msg) return ret diff --git a/openstackclient/api/auth.py b/openstackclient/api/auth.py index a55af29389..b56035e4dd 100644 --- a/openstackclient/api/auth.py +++ b/openstackclient/api/auth.py @@ -171,7 +171,8 @@ def check_valid_auth_options(options, auth_plugin_name, required_scope=True): 'auth.url')) if msgs: - raise exc.CommandError('Missing parameter(s): \n%s' % '\n'.join(msgs)) + raise exc.CommandError( + _('Missing parameter(s): \n%s') % '\n'.join(msgs)) def build_auth_plugins_option_parser(parser): @@ -187,10 +188,9 @@ def build_auth_plugins_option_parser(parser): metavar='', dest='auth_type', default=utils.env('OS_AUTH_TYPE'), - help='Select an authentication type. Available types: ' + - ', '.join(available_plugins) + - '. Default: selected based on --os-username/--os-token' + - ' (Env: OS_AUTH_TYPE)', + help=_('Select an authentication type. Available types: %s.' + ' Default: selected based on --os-username/--os-token' + ' (Env: OS_AUTH_TYPE)') % ', '.join(available_plugins), choices=available_plugins ) # Maintain compatibility with old tenant env vars @@ -215,10 +215,10 @@ def build_auth_plugins_option_parser(parser): OPTIONS_LIST[o]['env'], utils.env(OPTIONS_LIST[o]['env']), ), - help='%s\n(Env: %s)' % ( - OPTIONS_LIST[o]['help'], - OPTIONS_LIST[o]['env'], - ), + help=_('%(help)s\n(Env: %(env)s)') % { + 'help': OPTIONS_LIST[o]['help'], + 'env': OPTIONS_LIST[o]['env'], + }, ) # add tenant-related options for compatibility # this is deprecated but still used in some tempest tests... diff --git a/openstackclient/api/auth_plugin.py b/openstackclient/api/auth_plugin.py index 44d3b38ea4..36dc51605f 100644 --- a/openstackclient/api/auth_plugin.py +++ b/openstackclient/api/auth_plugin.py @@ -21,6 +21,8 @@ from keystoneauth1.loading._plugins import admin_token as token_endpoint from keystoneauth1.loading._plugins.identity import generic as ksa_password +from openstackclient.i18n import _ + LOG = logging.getLogger(__name__) @@ -51,10 +53,10 @@ def get_options(self): options.extend([ # Maintain name 'url' for compatibility cfg.StrOpt('url', - help='Specific service endpoint to use'), + help=_('Specific service endpoint to use')), cfg.StrOpt('token', secret=True, - help='Authentication token to use'), + help=_('Authentication token to use')), ]) return options diff --git a/openstackclient/shell.py b/openstackclient/shell.py index a15e04dd69..12a63af219 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -36,6 +36,7 @@ import openstackclient from openstackclient.common import clientmanager from openstackclient.common import commandmanager +from openstackclient.i18n import _ from os_client_config import config as cloud_config @@ -63,9 +64,8 @@ def prompt_for_password(prompt=None): pass # No password because we did't have a tty or nothing was entered if not pw: - raise exc.CommandError( - "No password entered, or found via --os-password or OS_PASSWORD", - ) + raise exc.CommandError(_("No password entered, or found via" + " --os-password or OS_PASSWORD"),) return pw @@ -185,7 +185,7 @@ def build_option_parser(self, description, version): metavar='', dest='cloud', default=utils.env('OS_CLOUD'), - help='Cloud name in clouds.yaml (Env: OS_CLOUD)', + help=_('Cloud name in clouds.yaml (Env: OS_CLOUD)'), ) # Global arguments parser.add_argument( @@ -193,37 +193,41 @@ def build_option_parser(self, description, version): metavar='', dest='region_name', default=utils.env('OS_REGION_NAME'), - help='Authentication region name (Env: OS_REGION_NAME)') + help=_('Authentication region name (Env: OS_REGION_NAME)'), + ) parser.add_argument( '--os-cacert', metavar='', dest='cacert', default=utils.env('OS_CACERT'), - help='CA certificate bundle file (Env: OS_CACERT)') + help=_('CA certificate bundle file (Env: OS_CACERT)'), + ) parser.add_argument( '--os-cert', metavar='', dest='cert', default=utils.env('OS_CERT'), - help='Client certificate bundle file (Env: OS_CERT)') + help=_('Client certificate bundle file (Env: OS_CERT)'), + ) parser.add_argument( '--os-key', metavar='', dest='key', default=utils.env('OS_KEY'), - help='Client certificate key file (Env: OS_KEY)') + help=_('Client certificate key file (Env: OS_KEY)'), + ) verify_group = parser.add_mutually_exclusive_group() verify_group.add_argument( '--verify', action='store_true', default=None, - help='Verify server certificate (default)', + help=_('Verify server certificate (default)'), ) verify_group.add_argument( '--insecure', action='store_true', default=None, - help='Disable server certificate verification', + help=_('Disable server certificate verification'), ) parser.add_argument( '--os-default-domain', @@ -232,28 +236,29 @@ def build_option_parser(self, description, version): default=utils.env( 'OS_DEFAULT_DOMAIN', default=DEFAULT_DOMAIN), - help='Default domain ID, default=' + - DEFAULT_DOMAIN + - ' (Env: OS_DEFAULT_DOMAIN)') + help=_('Default domain ID, default=%s. ' + '(Env: OS_DEFAULT_DOMAIN)') % DEFAULT_DOMAIN, + ) parser.add_argument( '--os-interface', metavar='', dest='interface', choices=['admin', 'public', 'internal'], default=utils.env('OS_INTERFACE'), - help='Select an interface type.' - ' Valid interface types: [admin, public, internal].' - ' (Env: OS_INTERFACE)') + help=_('Select an interface type.' + ' Valid interface types: [admin, public, internal].' + ' (Env: OS_INTERFACE)'), + ) parser.add_argument( '--timing', default=False, action='store_true', - help="Print API call timing info", + help=_("Print API call timing info"), ) parser.add_argument( '--os-beta-command', action='store_true', - help="Enable beta commands which are subject to change", + help=_("Enable beta commands which are subject to change"), ) # osprofiler HMAC key argument @@ -262,7 +267,7 @@ def build_option_parser(self, description, version): '--os-profile', metavar='hmac-key', dest='profile', - help='HMAC key for encrypting profiling context data', + help=_('HMAC key for encrypting profiling context data'), ) # NOTE(dtroyer): This global option should have been named # --os-profile as --profile interferes with at From b875f63a6f2a7ac381d4bb80b9c032272126ef49 Mon Sep 17 00:00:00 2001 From: Stuart McLaren Date: Tue, 14 Jun 2016 17:34:22 +0000 Subject: [PATCH 0992/3095] Add release note for network endpoint type bugfix. Related-bug: 1592368 Change-Id: I9bd8c3490e72b32ba48dc9d884c101d5e50867f9 --- releasenotes/notes/bug-1592368-a4af69f163a1e208.yaml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 releasenotes/notes/bug-1592368-a4af69f163a1e208.yaml diff --git a/releasenotes/notes/bug-1592368-a4af69f163a1e208.yaml b/releasenotes/notes/bug-1592368-a4af69f163a1e208.yaml new file mode 100644 index 0000000000..acea6faf7b --- /dev/null +++ b/releasenotes/notes/bug-1592368-a4af69f163a1e208.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - Fix for network OS_ENDPOINT_TYPE/--os-interface. + Previously these were being ignored for network commands + which resulted in the public endpoint always being used. + [Bug `1592368 `_] From 4a2924ded757ad6635efa3ea1f01645882bd8562 Mon Sep 17 00:00:00 2001 From: SamYaple Date: Tue, 14 Jun 2016 18:43:48 +0000 Subject: [PATCH 0993/3095] Remove blank line in release notes The blank line was causing yamllint to fail in other projects. [1] Related-Id: I400131bf84a4cf48a83952efa7dfac8e4954b587 [1] http://logs.openstack.org/64/329564/1/check/gate-heat-templates-pep8/e538974/console.html#_2016-06-14_17_39_02_316 Change-Id: Icfd902b6c9408ccb9a710d10d52d65efe753b77f --- .../releasenotes/notes/bug-1543222-6f8579344ff5c958.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/openstackclient/releasenotes/notes/bug-1543222-6f8579344ff5c958.yaml b/openstackclient/releasenotes/notes/bug-1543222-6f8579344ff5c958.yaml index c783d01309..98e037bcb5 100644 --- a/openstackclient/releasenotes/notes/bug-1543222-6f8579344ff5c958.yaml +++ b/openstackclient/releasenotes/notes/bug-1543222-6f8579344ff5c958.yaml @@ -3,4 +3,3 @@ fixes: - Keystone V3 `user password set` is a self-service operation. It should not required a scoped token as it is not considered a `scoped operation`. [Bug `1543222 `_] - From 0e9862be7af9a88c9c0e6a9ef4e11e48a1192727 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 7 Jun 2016 14:29:41 +0800 Subject: [PATCH 0994/3095] Standardize logger usage in volume self.app.log is the logger in class OpenStackShell, which should be used to record logs that have nothing to do with any specific command. So, use the file logger instead. This patch also fixes some usage that doesn't follow rules in: http://docs.openstack.org/developer/oslo.i18n/guidelines.html 1. add variables to logger as an argument 2. do not wrap variables with str() Change-Id: I248861a38a4de0412a080046aa7a6f6473c3e082 Implements: blueprint log-usage --- .../tests/volume/v1/test_volume.py | 19 ++++++++------ openstackclient/volume/v1/volume.py | 13 ++++++---- openstackclient/volume/v2/volume.py | 15 ++++++----- openstackclient/volume/v2/volume_type.py | 25 +++++++++++-------- 4 files changed, 42 insertions(+), 30 deletions(-) diff --git a/openstackclient/tests/volume/v1/test_volume.py b/openstackclient/tests/volume/v1/test_volume.py index e4f51bb535..380bc632d6 100644 --- a/openstackclient/tests/volume/v1/test_volume.py +++ b/openstackclient/tests/volume/v1/test_volume.py @@ -14,6 +14,7 @@ # import copy +import mock from openstackclient.tests import fakes from openstackclient.tests.identity.v2_0 import fakes as identity_fakes @@ -656,7 +657,8 @@ def test_volume_set_size(self): ) self.assertIsNone(result) - def test_volume_set_size_smaller(self): + @mock.patch.object(volume.LOG, 'error') + def test_volume_set_size_smaller(self, mock_log_error): arglist = [ '--size', '100', volume_fakes.volume_name, @@ -672,12 +674,13 @@ def test_volume_set_size_smaller(self): result = self.cmd.take_action(parsed_args) - self.assertEqual("New size must be greater than %s GB" % - volume_fakes.volume_size, - self.app.log.messages.get('error')) + mock_log_error.assert_called_with("New size must be greater " + "than %s GB", + volume_fakes.volume_size) self.assertIsNone(result) - def test_volume_set_size_not_available(self): + @mock.patch.object(volume.LOG, 'error') + def test_volume_set_size_not_available(self, mock_log_error): self.volumes_mock.get.return_value.status = 'error' arglist = [ '--size', '130', @@ -694,9 +697,9 @@ def test_volume_set_size_not_available(self): result = self.cmd.take_action(parsed_args) - self.assertEqual("Volume is in %s state, it must be available before " - "size can be extended" % 'error', - self.app.log.messages.get('error')) + mock_log_error.assert_called_with("Volume is in %s state, it must be " + "available before size can be " + "extended", 'error') self.assertIsNone(result) def test_volume_set_property(self): diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index 7a3bfb971b..e11aa1a71c 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -16,6 +16,7 @@ """Volume v1 Volume action implementations""" import argparse +import logging from osc_lib.cli import parseractions from osc_lib.command import command @@ -25,6 +26,9 @@ from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + class CreateVolume(command.ShowOne): """Create new volume""" @@ -343,13 +347,12 @@ def take_action(self, parsed_args): if parsed_args.size: if volume.status != 'available': - self.app.log.error(_("Volume is in %s state, it must be " - "available before size can be extended") % - volume.status) + LOG.error(_("Volume is in %s state, it must be available " + "before size can be extended"), volume.status) return if parsed_args.size <= volume.size: - self.app.log.error(_("New size must be greater than %s GB") % - volume.size) + LOG.error(_("New size must be greater than %s GB"), + volume.size) return volume_client.volumes.extend(volume.id, parsed_args.size) diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index b1a3af2efc..e54395fa01 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -15,6 +15,7 @@ """Volume V2 Volume action implementations""" import copy +import logging from osc_lib.cli import parseractions from osc_lib.command import command @@ -25,6 +26,9 @@ from openstackclient.identity import common as identity_common +LOG = logging.getLogger(__name__) + + class CreateVolume(command.ShowOne): """Create new volume""" @@ -361,13 +365,12 @@ def take_action(self, parsed_args): if parsed_args.size: if volume.status != 'available': - self.app.log.error(_("Volume is in %s state, it must be " - "available before size can be extended") % - volume.status) + LOG.error(_("Volume is in %s state, it must be available " + "before size can be extended"), volume.status) return if parsed_args.size <= volume.size: - self.app.log.error(_("New size must be greater than %s GB") % - volume.size) + LOG.error(_("New size must be greater than %s GB"), + volume.size) return volume_client.volumes.extend(volume.id, parsed_args.size) @@ -456,4 +459,4 @@ def take_action(self, parsed_args): volume.id, parsed_args.image_property) if (not parsed_args.image_property and not parsed_args.property): - self.app.log.error(_("No changes requested\n")) + LOG.error(_("No changes requested")) diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index dc6b4f9b27..5e9faa1ded 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -14,6 +14,8 @@ """Volume v2 Type action implementations""" +import logging + from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -24,6 +26,9 @@ from openstackclient.identity import common as identity_common +LOG = logging.getLogger(__name__) + + class CreateVolumeType(command.ShowOne): """Create new volume type""" @@ -190,16 +195,15 @@ def take_action(self, parsed_args): **kwargs ) except Exception as e: - self.app.log.error(_("Failed to update volume type name or" - " description: %s") % str(e)) + LOG.error(_("Failed to update volume type name or" + " description: %s"), e) result += 1 if parsed_args.property: try: volume_type.set_keys(parsed_args.property) except Exception as e: - self.app.log.error(_("Failed to set volume type" - " property: %s") % str(e)) + LOG.error(_("Failed to set volume type property: %s"), e) result += 1 if parsed_args.project: @@ -213,13 +217,13 @@ def take_action(self, parsed_args): volume_client.volume_type_access.add_project_access( volume_type.id, project_info.id) except Exception as e: - self.app.log.error(_("Failed to set volume type access to" - " project: %s") % str(e)) + LOG.error(_("Failed to set volume type access to " + "project: %s"), e) result += 1 if result > 0: raise exceptions.CommandError(_("Command Failed: One or more of" - " the operations failed")) + " the operations failed")) class ShowVolumeType(command.ShowOne): @@ -284,8 +288,7 @@ def take_action(self, parsed_args): try: volume_type.unset_keys(parsed_args.property) except Exception as e: - self.app.log.error(_("Failed to unset volume type property: %s" - ) % str(e)) + LOG.error(_("Failed to unset volume type property: %s"), e) result += 1 if parsed_args.project: @@ -299,8 +302,8 @@ def take_action(self, parsed_args): volume_client.volume_type_access.remove_project_access( volume_type.id, project_info.id) except Exception as e: - self.app.log.error(_("Failed to remove volume type access from" - " project: %s") % str(e)) + LOG.error(_("Failed to remove volume type access from " + "project: %s"), e) result += 1 if result > 0: From 8e2f49fbf22c242270c8162254fc83fbb4580a24 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Sun, 12 Jun 2016 12:50:30 +0800 Subject: [PATCH 0995/3095] Support bulk deletion for commands that exist in both network and compute. Some delete commands in networkv2 are exist in both network and compute, They can use NetworkAndComputeDeleteclass to supprot bulk deletion and error handling and the codes are similar, so I change them all in this patch. The changed commands including: 1.floating ip delete 2.security group delete 3.security group rule delete Also, I update unit tests and docs for these commands in this patch. Change-Id: I6c94c3d10ba579ddd9b14d17673c821e3481fd8a Partially-Implements: blueprint multi-argument-network --- doc/source/command-objects/ip-floating.rst | 7 +- .../command-objects/security-group-rule.rst | 6 +- doc/source/command-objects/security-group.rst | 6 +- openstackclient/network/v2/floating_ip.py | 18 ++- openstackclient/network/v2/security_group.py | 18 ++- .../network/v2/security_group_rule.py | 16 +- openstackclient/tests/compute/v2/fakes.py | 19 +++ openstackclient/tests/network/v2/fakes.py | 39 +++++ .../tests/network/v2/test_floating_ip.py | 131 +++++++++++++++-- .../tests/network/v2/test_security_group.py | 139 ++++++++++++++++-- .../network/v2/test_security_group_rule.py | 127 ++++++++++++++-- ...lti-argument-network-e43e192ac95db94d.yaml | 5 + 12 files changed, 459 insertions(+), 72 deletions(-) create mode 100644 releasenotes/notes/bp-multi-argument-network-e43e192ac95db94d.yaml diff --git a/doc/source/command-objects/ip-floating.rst b/doc/source/command-objects/ip-floating.rst index 869b7af88f..8dd9478218 100644 --- a/doc/source/command-objects/ip-floating.rst +++ b/doc/source/command-objects/ip-floating.rst @@ -66,16 +66,17 @@ Create new floating IP address ip floating delete ------------------ -Delete floating IP +Delete floating IP(s) .. program:: ip floating delete .. code:: bash - os ip floating delete + os ip floating delete + [ ...] .. describe:: - Floating IP to delete (IP address or ID) + Floating IP(s) to delete (IP address or ID) ip floating list ---------------- diff --git a/doc/source/command-objects/security-group-rule.rst b/doc/source/command-objects/security-group-rule.rst index 97cce35cf5..5284b2dc22 100644 --- a/doc/source/command-objects/security-group-rule.rst +++ b/doc/source/command-objects/security-group-rule.rst @@ -104,17 +104,17 @@ Create a new security group rule security group rule delete -------------------------- -Delete a security group rule +Delete security group rule(s) .. program:: security group rule delete .. code:: bash os security group rule delete - + [ ...] .. describe:: - Security group rule to delete (ID only) + Security group rule(s) to delete (ID only) security group rule list ------------------------ diff --git a/doc/source/command-objects/security-group.rst b/doc/source/command-objects/security-group.rst index 3af11b5af7..ba054554df 100644 --- a/doc/source/command-objects/security-group.rst +++ b/doc/source/command-objects/security-group.rst @@ -45,17 +45,17 @@ Create a new security group security group delete --------------------- -Delete a security group +Delete security group(s) .. program:: security group delete .. code:: bash os security group delete - + [ ...] .. describe:: - Security group to delete (name or ID) + Security group(s) to delete (name or ID) security group list ------------------- diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index c734c2ed03..8fbf049e3f 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -110,26 +110,28 @@ def take_action_compute(self, client, parsed_args): return (columns, data) -class DeleteFloatingIP(common.NetworkAndComputeCommand): - """Delete floating IP""" +class DeleteFloatingIP(common.NetworkAndComputeDelete): + """Delete floating IP(s)""" + + # Used by base class to find resources in parsed_args. + resource = 'floating_ip' + r = None def update_parser_common(self, parser): parser.add_argument( 'floating_ip', metavar="", - help=_("Floating IP to delete (IP address or ID)") + nargs="+", + help=_("Floating IP(s) to delete (IP address or ID)") ) return parser def take_action_network(self, client, parsed_args): - obj = client.find_ip(parsed_args.floating_ip) + obj = client.find_ip(self.r, ignore_missing=False) client.delete_ip(obj) def take_action_compute(self, client, parsed_args): - obj = utils.find_resource( - client.floating_ips, - parsed_args.floating_ip, - ) + obj = utils.find_resource(client.floating_ips, self.r) client.floating_ips.delete(obj.id) diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py index 959718001b..f832f721ef 100644 --- a/openstackclient/network/v2/security_group.py +++ b/openstackclient/network/v2/security_group.py @@ -164,26 +164,28 @@ def take_action_compute(self, client, parsed_args): return (display_columns, data) -class DeleteSecurityGroup(common.NetworkAndComputeCommand): - """Delete a security group""" +class DeleteSecurityGroup(common.NetworkAndComputeDelete): + """Delete security group(s)""" + + # Used by base class to find resources in parsed_args. + resource = 'group' + r = None def update_parser_common(self, parser): parser.add_argument( 'group', metavar='', - help=_("Security group to delete (name or ID)") + nargs="+", + help=_("Security group(s) to delete (name or ID)"), ) return parser def take_action_network(self, client, parsed_args): - obj = client.find_security_group(parsed_args.group) + obj = client.find_security_group(self.r, ignore_missing=False) client.delete_security_group(obj) def take_action_compute(self, client, parsed_args): - data = utils.find_resource( - client.security_groups, - parsed_args.group, - ) + data = utils.find_resource(client.security_groups, self.r) client.security_groups.delete(data.id) diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index 7c81078691..18863223ae 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -333,23 +333,29 @@ def take_action_compute(self, client, parsed_args): return _format_security_group_rule_show(obj._info) -class DeleteSecurityGroupRule(common.NetworkAndComputeCommand): - """Delete a security group rule""" +class DeleteSecurityGroupRule(common.NetworkAndComputeDelete): + """Delete security group rule(s)""" + + # Used by base class to find resources in parsed_args. + resource = 'rule' + r = None def update_parser_common(self, parser): parser.add_argument( 'rule', metavar='', - help=_("Security group rule to delete (ID only)") + nargs="+", + help=_("Security group rule(s) to delete (ID only)") ) return parser def take_action_network(self, client, parsed_args): - obj = client.find_security_group_rule(parsed_args.rule) + obj = client.find_security_group_rule( + self.r, ignore_missing=False) client.delete_security_group_rule(obj) def take_action_compute(self, client, parsed_args): - client.security_group_rules.delete(parsed_args.rule) + client.security_group_rules.delete(self.r) class ListSecurityGroupRule(common.NetworkAndComputeLister): diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 9682eec470..60abb8ef1e 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -452,6 +452,25 @@ def create_security_groups(attrs=None, count=2): return security_groups + @staticmethod + def get_security_groups(security_groups=None, count=2): + """Get an iterable MagicMock object with a list of faked security groups. + + If security groups list is provided, then initialize the Mock object + with the list. Otherwise create one. + + :param List security groups: + A list of FakeResource objects faking security groups + :param int count: + The number of security groups to fake + :return: + An iterable Mock object with side_effect set to a list of faked + security groups + """ + if security_groups is None: + security_groups = FakeSecurityGroup.create_security_groups(count) + return mock.MagicMock(side_effect=security_groups) + class FakeSecurityGroupRule(object): """Fake one or more security group rules.""" diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 9efbe8c608..6b09e2978b 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -611,6 +611,25 @@ def create_security_groups(attrs=None, count=2): return security_groups + @staticmethod + def get_security_groups(security_groups=None, count=2): + """Get an iterable MagicMock object with a list of faked security groups. + + If security groups list is provided, then initialize the Mock object + with the list. Otherwise create one. + + :param List security groups: + A list of FakeResource objects faking security groups + :param int count: + The number of security groups to fake + :return: + An iterable Mock object with side_effect set to a list of faked + security groups + """ + if security_groups is None: + security_groups = FakeSecurityGroup.create_security_groups(count) + return mock.MagicMock(side_effect=security_groups) + class FakeSecurityGroupRule(object): """Fake one or more security group rules.""" @@ -670,6 +689,26 @@ def create_security_group_rules(attrs=None, count=2): return security_group_rules + @staticmethod + def get_security_group_rules(security_group_rules=None, count=2): + """Get an iterable MagicMock object with a list of faked security group rules. + + If security group rules list is provided, then initialize the Mock + object with the list. Otherwise create one. + + :param List security group rules: + A list of FakeResource objects faking security group rules + :param int count: + The number of security group rules to fake + :return: + An iterable Mock object with side_effect set to a list of faked + security group rules + """ + if security_group_rules is None: + security_group_rules = ( + FakeSecurityGroupRule.create_security_group_rules(count)) + return mock.MagicMock(side_effect=security_group_rules) + class FakeSubnet(object): """Fake one or more subnets.""" diff --git a/openstackclient/tests/network/v2/test_floating_ip.py b/openstackclient/tests/network/v2/test_floating_ip.py index f9ccfe1c74..5cd5279aff 100644 --- a/openstackclient/tests/network/v2/test_floating_ip.py +++ b/openstackclient/tests/network/v2/test_floating_ip.py @@ -12,6 +12,9 @@ # import mock +from mock import call + +from osc_lib import exceptions from openstackclient.network.v2 import floating_ip from openstackclient.tests.compute.v2 import fakes as compute_fakes @@ -140,33 +143,84 @@ def test_create_all_options(self): class TestDeleteFloatingIPNetwork(TestFloatingIPNetwork): - # The floating ip to be deleted. - floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip() + # The floating ips to be deleted. + floating_ips = network_fakes.FakeFloatingIP.create_floating_ips(count=2) def setUp(self): super(TestDeleteFloatingIPNetwork, self).setUp() self.network.delete_ip = mock.Mock(return_value=None) - self.network.find_ip = mock.Mock(return_value=self.floating_ip) + self.network.find_ip = ( + network_fakes.FakeFloatingIP.get_floating_ips(self.floating_ips)) # Get the command object to test self.cmd = floating_ip.DeleteFloatingIP(self.app, self.namespace) def test_floating_ip_delete(self): arglist = [ - self.floating_ip.id, + self.floating_ips[0].id, ] verifylist = [ - ('floating_ip', self.floating_ip.id), + ('floating_ip', [self.floating_ips[0].id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.network.find_ip.assert_called_once_with(self.floating_ip.id) - self.network.delete_ip.assert_called_once_with(self.floating_ip) + self.network.find_ip.assert_called_once_with( + self.floating_ips[0].id, ignore_missing=False) + self.network.delete_ip.assert_called_once_with(self.floating_ips[0]) self.assertIsNone(result) + def test_multi_floating_ips_delete(self): + arglist = [] + verifylist = [] + + for f in self.floating_ips: + arglist.append(f.id) + verifylist = [ + ('floating_ip', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [] + for f in self.floating_ips: + calls.append(call(f)) + self.network.delete_ip.assert_has_calls(calls) + self.assertIsNone(result) + + def test_multi_floating_ips_delete_with_exception(self): + arglist = [ + self.floating_ips[0].id, + 'unexist_floating_ip', + ] + verifylist = [ + ('floating_ip', + [self.floating_ips[0].id, 'unexist_floating_ip']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self.floating_ips[0], exceptions.CommandError] + self.network.find_ip = ( + mock.MagicMock(side_effect=find_mock_result) + ) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 floating_ip failed to delete.', str(e)) + + self.network.find_ip.assert_any_call( + self.floating_ips[0].id, ignore_missing=False) + self.network.find_ip.assert_any_call( + 'unexist_floating_ip', ignore_missing=False) + self.network.delete_ip.assert_called_once_with( + self.floating_ips[0] + ) + class TestListFloatingIPNetwork(TestFloatingIPNetwork): @@ -335,8 +389,8 @@ def test_create_default_options(self): class TestDeleteFloatingIPCompute(TestFloatingIPCompute): - # The floating ip to be deleted. - floating_ip = compute_fakes.FakeFloatingIP.create_one_floating_ip() + # The floating ips to be deleted. + floating_ips = compute_fakes.FakeFloatingIP.create_floating_ips(count=2) def setUp(self): super(TestDeleteFloatingIPCompute, self).setUp() @@ -346,27 +400,78 @@ def setUp(self): self.compute.floating_ips.delete.return_value = None # Return value of utils.find_resource() - self.compute.floating_ips.get.return_value = self.floating_ip + self.compute.floating_ips.get = ( + compute_fakes.FakeFloatingIP.get_floating_ips(self.floating_ips)) # Get the command object to test self.cmd = floating_ip.DeleteFloatingIP(self.app, None) def test_floating_ip_delete(self): arglist = [ - self.floating_ip.id, + self.floating_ips[0].id, ] verifylist = [ - ('floating_ip', self.floating_ip.id), + ('floating_ip', [self.floating_ips[0].id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.compute.floating_ips.delete.assert_called_once_with( - self.floating_ip.id + self.floating_ips[0].id ) self.assertIsNone(result) + def test_multi_floating_ips_delete(self): + arglist = [] + verifylist = [] + + for f in self.floating_ips: + arglist.append(f.id) + verifylist = [ + ('floating_ip', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [] + for f in self.floating_ips: + calls.append(call(f.id)) + self.compute.floating_ips.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_multi_floating_ips_delete_with_exception(self): + arglist = [ + self.floating_ips[0].id, + 'unexist_floating_ip', + ] + verifylist = [ + ('floating_ip', + [self.floating_ips[0].id, 'unexist_floating_ip']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self.floating_ips[0], exceptions.CommandError] + self.compute.floating_ips.get = ( + mock.MagicMock(side_effect=find_mock_result) + ) + self.compute.floating_ips.find.side_effect = exceptions.NotFound(None) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 floating_ip failed to delete.', str(e)) + + self.compute.floating_ips.get.assert_any_call( + self.floating_ips[0].id) + self.compute.floating_ips.get.assert_any_call( + 'unexist_floating_ip') + self.compute.floating_ips.delete.assert_called_once_with( + self.floating_ips[0].id + ) + class TestListFloatingIPCompute(TestFloatingIPCompute): diff --git a/openstackclient/tests/network/v2/test_security_group.py b/openstackclient/tests/network/v2/test_security_group.py index 213367a4d5..b0c1498519 100644 --- a/openstackclient/tests/network/v2/test_security_group.py +++ b/openstackclient/tests/network/v2/test_security_group.py @@ -13,6 +13,9 @@ import copy import mock +from mock import call + +from osc_lib import exceptions from openstackclient.network.v2 import security_group from openstackclient.tests.compute.v2 import fakes as compute_fakes @@ -227,42 +230,93 @@ def test_create_all_options(self): class TestDeleteSecurityGroupNetwork(TestSecurityGroupNetwork): - # The security group to be deleted. - _security_group = \ - network_fakes.FakeSecurityGroup.create_one_security_group() + # The security groups to be deleted. + _security_groups = \ + network_fakes.FakeSecurityGroup.create_security_groups() def setUp(self): super(TestDeleteSecurityGroupNetwork, self).setUp() self.network.delete_security_group = mock.Mock(return_value=None) - self.network.find_security_group = mock.Mock( - return_value=self._security_group) + self.network.find_security_group = ( + network_fakes.FakeSecurityGroup.get_security_groups( + self._security_groups) + ) # Get the command object to test self.cmd = security_group.DeleteSecurityGroup(self.app, self.namespace) def test_security_group_delete(self): arglist = [ - self._security_group.name, + self._security_groups[0].name, ] verifylist = [ - ('group', self._security_group.name), + ('group', [self._security_groups[0].name]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.network.delete_security_group.assert_called_once_with( - self._security_group) + self._security_groups[0]) + self.assertIsNone(result) + + def test_multi_security_groups_delete(self): + arglist = [] + verifylist = [] + + for s in self._security_groups: + arglist.append(s.name) + verifylist = [ + ('group', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [] + for s in self._security_groups: + calls.append(call(s)) + self.network.delete_security_group.assert_has_calls(calls) self.assertIsNone(result) + def test_multi_security_groups_delete_with_exception(self): + arglist = [ + self._security_groups[0].name, + 'unexist_security_group', + ] + verifylist = [ + ('group', + [self._security_groups[0].name, 'unexist_security_group']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self._security_groups[0], exceptions.CommandError] + self.network.find_security_group = ( + mock.MagicMock(side_effect=find_mock_result) + ) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 group failed to delete.', str(e)) + + self.network.find_security_group.assert_any_call( + self._security_groups[0].name, ignore_missing=False) + self.network.find_security_group.assert_any_call( + 'unexist_security_group', ignore_missing=False) + self.network.delete_security_group.assert_called_once_with( + self._security_groups[0] + ) + class TestDeleteSecurityGroupCompute(TestSecurityGroupCompute): - # The security group to be deleted. - _security_group = \ - compute_fakes.FakeSecurityGroup.create_one_security_group() + # The security groups to be deleted. + _security_groups = \ + compute_fakes.FakeSecurityGroup.create_security_groups() def setUp(self): super(TestDeleteSecurityGroupCompute, self).setUp() @@ -271,27 +325,80 @@ def setUp(self): self.compute.security_groups.delete = mock.Mock(return_value=None) - self.compute.security_groups.get = mock.Mock( - return_value=self._security_group) + self.compute.security_groups.get = ( + compute_fakes.FakeSecurityGroup.get_security_groups( + self._security_groups) + ) # Get the command object to test self.cmd = security_group.DeleteSecurityGroup(self.app, None) def test_security_group_delete(self): arglist = [ - self._security_group.name, + self._security_groups[0].id, ] verifylist = [ - ('group', self._security_group.name), + ('group', [self._security_groups[0].id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.compute.security_groups.delete.assert_called_once_with( - self._security_group.id) + self._security_groups[0].id) + self.assertIsNone(result) + + def test_multi_security_groups_delete(self): + arglist = [] + verifylist = [] + + for s in self._security_groups: + arglist.append(s.id) + verifylist = [ + ('group', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [] + for s in self._security_groups: + calls.append(call(s.id)) + self.compute.security_groups.delete.assert_has_calls(calls) self.assertIsNone(result) + def test_multi_security_groups_delete_with_exception(self): + arglist = [ + self._security_groups[0].id, + 'unexist_security_group', + ] + verifylist = [ + ('group', + [self._security_groups[0].id, 'unexist_security_group']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self._security_groups[0], exceptions.CommandError] + self.compute.security_groups.get = ( + mock.MagicMock(side_effect=find_mock_result) + ) + self.compute.security_groups.find.side_effect = ( + exceptions.NotFound(None)) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 group failed to delete.', str(e)) + + self.compute.security_groups.get.assert_any_call( + self._security_groups[0].id) + self.compute.security_groups.get.assert_any_call( + 'unexist_security_group') + self.compute.security_groups.delete.assert_called_once_with( + self._security_groups[0].id + ) + class TestListSecurityGroupNetwork(TestSecurityGroupNetwork): diff --git a/openstackclient/tests/network/v2/test_security_group_rule.py b/openstackclient/tests/network/v2/test_security_group_rule.py index b1f2209d39..b2862679cb 100644 --- a/openstackclient/tests/network/v2/test_security_group_rule.py +++ b/openstackclient/tests/network/v2/test_security_group_rule.py @@ -13,6 +13,7 @@ import copy import mock +from mock import call from osc_lib import exceptions @@ -668,17 +669,20 @@ def test_create_proto_option(self): class TestDeleteSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): - # The security group rule to be deleted. - _security_group_rule = \ - network_fakes.FakeSecurityGroupRule.create_one_security_group_rule() + # The security group rules to be deleted. + _security_group_rules = \ + network_fakes.FakeSecurityGroupRule.create_security_group_rules( + count=2) def setUp(self): super(TestDeleteSecurityGroupRuleNetwork, self).setUp() self.network.delete_security_group_rule = mock.Mock(return_value=None) - self.network.find_security_group_rule = mock.Mock( - return_value=self._security_group_rule) + self.network.find_security_group_rule = ( + network_fakes.FakeSecurityGroupRule.get_security_group_rules( + self._security_group_rules) + ) # Get the command object to test self.cmd = security_group_rule.DeleteSecurityGroupRule( @@ -686,25 +690,76 @@ def setUp(self): def test_security_group_rule_delete(self): arglist = [ - self._security_group_rule.id, + self._security_group_rules[0].id, ] verifylist = [ - ('rule', self._security_group_rule.id), + ('rule', [self._security_group_rules[0].id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.network.delete_security_group_rule.assert_called_once_with( - self._security_group_rule) + self._security_group_rules[0]) + self.assertIsNone(result) + + def test_multi_security_group_rules_delete(self): + arglist = [] + verifylist = [] + + for s in self._security_group_rules: + arglist.append(s.id) + verifylist = [ + ('rule', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [] + for s in self._security_group_rules: + calls.append(call(s)) + self.network.delete_security_group_rule.assert_has_calls(calls) self.assertIsNone(result) + def test_multi_security_group_rules_delete_with_exception(self): + arglist = [ + self._security_group_rules[0].id, + 'unexist_rule', + ] + verifylist = [ + ('rule', + [self._security_group_rules[0].id, 'unexist_rule']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [ + self._security_group_rules[0], exceptions.CommandError] + self.network.find_security_group_rule = ( + mock.MagicMock(side_effect=find_mock_result) + ) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 rule failed to delete.', str(e)) + + self.network.find_security_group_rule.assert_any_call( + self._security_group_rules[0].id, ignore_missing=False) + self.network.find_security_group_rule.assert_any_call( + 'unexist_rule', ignore_missing=False) + self.network.delete_security_group_rule.assert_called_once_with( + self._security_group_rules[0] + ) + class TestDeleteSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): # The security group rule to be deleted. - _security_group_rule = \ - compute_fakes.FakeSecurityGroupRule.create_one_security_group_rule() + _security_group_rules = \ + compute_fakes.FakeSecurityGroupRule.create_security_group_rules( + count=2) def setUp(self): super(TestDeleteSecurityGroupRuleCompute, self).setUp() @@ -716,19 +771,65 @@ def setUp(self): def test_security_group_rule_delete(self): arglist = [ - self._security_group_rule.id, + self._security_group_rules[0].id, ] verifylist = [ - ('rule', self._security_group_rule.id), + ('rule', [self._security_group_rules[0].id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.compute.security_group_rules.delete.assert_called_once_with( - self._security_group_rule.id) + self._security_group_rules[0].id) + self.assertIsNone(result) + + def test_multi_security_group_rules_delete(self): + arglist = [] + verifylist = [] + + for s in self._security_group_rules: + arglist.append(s.id) + verifylist = [ + ('rule', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [] + for s in self._security_group_rules: + calls.append(call(s.id)) + self.compute.security_group_rules.delete.assert_has_calls(calls) self.assertIsNone(result) + def test_multi_security_group_rules_delete_with_exception(self): + arglist = [ + self._security_group_rules[0].id, + 'unexist_rule', + ] + verifylist = [ + ('rule', + [self._security_group_rules[0].id, 'unexist_rule']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [None, exceptions.CommandError] + self.compute.security_group_rules.delete = ( + mock.MagicMock(side_effect=find_mock_result) + ) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 rule failed to delete.', str(e)) + + self.compute.security_group_rules.delete.assert_any_call( + self._security_group_rules[0].id) + self.compute.security_group_rules.delete.assert_any_call( + 'unexist_rule') + class TestListSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): diff --git a/releasenotes/notes/bp-multi-argument-network-e43e192ac95db94d.yaml b/releasenotes/notes/bp-multi-argument-network-e43e192ac95db94d.yaml new file mode 100644 index 0000000000..0c56e8205e --- /dev/null +++ b/releasenotes/notes/bp-multi-argument-network-e43e192ac95db94d.yaml @@ -0,0 +1,5 @@ +--- +features: + - Support bulk deletion for ``floating ip delete``, ``security group delete``, + and ``security group rule delete`` commands in networkv2. + [Blueprint `multi-argument-network `_] From ca5e8e6c8540e457a620cc90d321a08e7417de32 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Wed, 15 Jun 2016 13:57:39 +0800 Subject: [PATCH 0996/3095] Add default IP version and fix help messages for "ip availability list" There was not a default IP version in "ip availability list" command, if we used this command without "--ip-version" option, the output was different from the outputs with the option "--ip-version 4" and "--ip-version 6" and it is not right. This patch add default IP version (default is 4) in ``ip availability list`` command and make this command work properly without ``--ip-version`` option. And also fix the help message. Change-Id: Idc08ab6eaf05946eb2ab59bfb3d4497a383d987d Closes-Bug: #1592761 --- doc/source/command-objects/ip-availability.rst | 2 +- openstackclient/network/v2/ip_availability.py | 6 ++++-- openstackclient/tests/network/v2/test_ip_availability.py | 7 +++++-- releasenotes/notes/bug-1592761-f45998453d6801f7.yaml | 5 +++++ 4 files changed, 15 insertions(+), 5 deletions(-) create mode 100644 releasenotes/notes/bug-1592761-f45998453d6801f7.yaml diff --git a/doc/source/command-objects/ip-availability.rst b/doc/source/command-objects/ip-availability.rst index 55b7842775..f200ab13a6 100644 --- a/doc/source/command-objects/ip-availability.rst +++ b/doc/source/command-objects/ip-availability.rst @@ -25,7 +25,7 @@ number of allocated IP addresses from that pool. .. option:: --ip-version {4,6} List IP availability of given IP version networks - (Default is 4) + (default is 4) .. option:: --project diff --git a/openstackclient/network/v2/ip_availability.py b/openstackclient/network/v2/ip_availability.py index d429e86c8b..1d7b2aed87 100644 --- a/openstackclient/network/v2/ip_availability.py +++ b/openstackclient/network/v2/ip_availability.py @@ -41,15 +41,17 @@ def get_parser(self, prog_name): parser.add_argument( '--ip-version', type=int, + default=4, choices=[4, 6], metavar='', dest='ip_version', - help=_("List IP availability of given IP version networks"), + help=_("List IP availability of given IP version " + "networks (default is 4)"), ) parser.add_argument( '--project', metavar='', - help=_("List IP availability of given project"), + help=_("List IP availability of given project (name or ID)"), ) identity_common.add_project_domain_option_to_parser(parser) return parser diff --git a/openstackclient/tests/network/v2/test_ip_availability.py b/openstackclient/tests/network/v2/test_ip_availability.py index 39e11cd5db..c6ec2b0b06 100644 --- a/openstackclient/tests/network/v2/test_ip_availability.py +++ b/openstackclient/tests/network/v2/test_ip_availability.py @@ -82,8 +82,10 @@ def test_list_no_options(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) + filters = {'ip_version': 4} - self.network.network_ip_availabilities.assert_called_once_with() + self.network.network_ip_availabilities.assert_called_once_with( + **filters) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) @@ -116,7 +118,8 @@ def test_list_project(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - filters = {'tenant_id': identity_fakes.project_id} + filters = {'tenant_id': identity_fakes.project_id, + 'ip_version': 4} self.network.network_ip_availabilities.assert_called_once_with( **filters) diff --git a/releasenotes/notes/bug-1592761-f45998453d6801f7.yaml b/releasenotes/notes/bug-1592761-f45998453d6801f7.yaml new file mode 100644 index 0000000000..78f5801acc --- /dev/null +++ b/releasenotes/notes/bug-1592761-f45998453d6801f7.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - Add default IP version in ``ip availability list`` command and make + this command work properly without ``--ip-version`` option. + [Bug `1592761 `_] From ec630e033a7bc25e01fd9f49ebe60da52ced75e5 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Wed, 15 Jun 2016 19:39:16 +0800 Subject: [PATCH 0997/3095] Setup deprecate msg for command.py This could be a miss when we use osc_lib first in OSC. command.py has been moved to osc_lib, and should reuse it and print a deprecate msg if the old file is used. Change-Id: Ibc35659f6f78b8f5e3c6026db2644a876ef7c549 --- openstackclient/common/command.py | 46 ++++++------------------------- 1 file changed, 8 insertions(+), 38 deletions(-) diff --git a/openstackclient/common/command.py b/openstackclient/common/command.py index adf984fa13..9abaae0b87 100644 --- a/openstackclient/common/command.py +++ b/openstackclient/common/command.py @@ -12,45 +12,15 @@ # License for the specific language governing permissions and limitations # under the License. -import abc -import logging +# NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release +# or Jun 2017. -from cliff import command -from cliff import lister -from cliff import show -from osc_lib import exceptions -import six +import sys -from openstackclient.i18n import _ +from osc_lib.command import * # noqa -class CommandMeta(abc.ABCMeta): - - def __new__(mcs, name, bases, cls_dict): - if 'log' not in cls_dict: - cls_dict['log'] = logging.getLogger( - cls_dict['__module__'] + '.' + name) - return super(CommandMeta, mcs).__new__(mcs, name, bases, cls_dict) - - -@six.add_metaclass(CommandMeta) -class Command(command.Command): - - def run(self, parsed_args): - self.log.debug('run(%s)', parsed_args) - return super(Command, self).run(parsed_args) - - def validate_os_beta_command_enabled(self): - if not self.app.options.os_beta_command: - msg = _('Caution: This is a beta command and subject to ' - 'change. Use global option --os-beta-command ' - 'to enable this command.') - raise exceptions.CommandError(msg) - - -class Lister(Command, lister.Lister): - pass - - -class ShowOne(Command, show.ShowOne): - pass +sys.stderr.write( + "WARNING: %s is deprecated and will be removed after Jun 2017. " + "Please use osc_lib.command\n" % __name__ +) From 09b783ad3e5051564bf6321feace3b65739febbf Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Wed, 15 Jun 2016 10:31:26 -0500 Subject: [PATCH 0998/3095] Fix volume functional tests The volume v2 functional tests occasionally fail with "unrecognized arguments: --private". This appears to occur when volume v1 and volume v2 functional tests are run at the same time when one or more of the tests do not set OS_VOLUME_API_VERSION appropriately. This patch also fixes a timing issue seen when running the tests locally due to a missing wait and moves the snapshot test to volume v2 since it was written for v2. Change-Id: I9c9863fe1aef81b0564920f36452c6f27211d5ba Closes-Bug: #1589793 --- functional/tests/volume/v1/test_qos.py | 3 ++- functional/tests/volume/v1/test_volume.py | 3 +-- .../tests/volume/v1/test_volume_type.py | 1 + functional/tests/volume/v2/common.py | 23 +++++++++++++++++++ functional/tests/volume/v2/test_qos.py | 5 ++-- .../tests/volume/{v1 => v2}/test_snapshot.py | 5 ++-- functional/tests/volume/v2/test_volume.py | 8 +++---- .../tests/volume/v2/test_volume_type.py | 5 ++-- 8 files changed, 40 insertions(+), 13 deletions(-) create mode 100644 functional/tests/volume/v2/common.py rename functional/tests/volume/{v1 => v2}/test_snapshot.py (95%) diff --git a/functional/tests/volume/v1/test_qos.py b/functional/tests/volume/v1/test_qos.py index f4b2fec3a6..9324830c8a 100644 --- a/functional/tests/volume/v1/test_qos.py +++ b/functional/tests/volume/v1/test_qos.py @@ -15,7 +15,7 @@ from functional.tests.volume.v1 import common -class VolumeTests(common.BaseVolumeTests): +class QosTests(common.BaseVolumeTests): """Functional tests for volume qos. """ NAME = uuid.uuid4().hex @@ -25,6 +25,7 @@ class VolumeTests(common.BaseVolumeTests): @classmethod def setUpClass(cls): + super(QosTests, cls).setUpClass() opts = cls.get_show_opts(cls.FIELDS) raw_output = cls.openstack('volume qos create ' + cls.NAME + opts) cls.ID, name, rol = raw_output.split('\n') diff --git a/functional/tests/volume/v1/test_volume.py b/functional/tests/volume/v1/test_volume.py index 874be6e115..f574075d75 100644 --- a/functional/tests/volume/v1/test_volume.py +++ b/functional/tests/volume/v1/test_volume.py @@ -10,7 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -import os import uuid from functional.tests.volume.v1 import common @@ -26,7 +25,7 @@ class VolumeTests(common.BaseVolumeTests): @classmethod def setUpClass(cls): - os.environ['OS_VOLUME_API_VERSION'] = '1' + super(VolumeTests, cls).setUpClass() opts = cls.get_show_opts(cls.FIELDS) raw_output = cls.openstack('volume create --size 1 ' + cls.NAME + opts) expected = cls.NAME + '\n' diff --git a/functional/tests/volume/v1/test_volume_type.py b/functional/tests/volume/v1/test_volume_type.py index dc8b1f4580..824b20d7c4 100644 --- a/functional/tests/volume/v1/test_volume_type.py +++ b/functional/tests/volume/v1/test_volume_type.py @@ -24,6 +24,7 @@ class VolumeTypeTests(common.BaseVolumeTests): @classmethod def setUpClass(cls): + super(VolumeTypeTests, cls).setUpClass() opts = cls.get_show_opts(cls.FIELDS) raw_output = cls.openstack('volume type create ' + cls.NAME + opts) expected = cls.NAME + '\n' diff --git a/functional/tests/volume/v2/common.py b/functional/tests/volume/v2/common.py new file mode 100644 index 0000000000..8652c2d182 --- /dev/null +++ b/functional/tests/volume/v2/common.py @@ -0,0 +1,23 @@ +# 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 os + +from functional.common import test + + +class BaseVolumeTests(test.TestCase): + """Base class for Volume functional tests. """ + + @classmethod + def setUpClass(cls): + os.environ['OS_VOLUME_API_VERSION'] = '2' diff --git a/functional/tests/volume/v2/test_qos.py b/functional/tests/volume/v2/test_qos.py index 24ce1b326a..64c3b6c108 100644 --- a/functional/tests/volume/v2/test_qos.py +++ b/functional/tests/volume/v2/test_qos.py @@ -12,10 +12,10 @@ import uuid -from functional.common import test +from functional.tests.volume.v2 import common -class VolumeTests(test.TestCase): +class QosTests(common.BaseVolumeTests): """Functional tests for volume qos. """ NAME = uuid.uuid4().hex @@ -25,6 +25,7 @@ class VolumeTests(test.TestCase): @classmethod def setUpClass(cls): + super(QosTests, cls).setUpClass() opts = cls.get_show_opts(cls.FIELDS) raw_output = cls.openstack('volume qos create ' + cls.NAME + opts) cls.ID, name, rol = raw_output.split('\n') diff --git a/functional/tests/volume/v1/test_snapshot.py b/functional/tests/volume/v2/test_snapshot.py similarity index 95% rename from functional/tests/volume/v1/test_snapshot.py rename to functional/tests/volume/v2/test_snapshot.py index c43a7456ae..40f075323f 100644 --- a/functional/tests/volume/v1/test_snapshot.py +++ b/functional/tests/volume/v2/test_snapshot.py @@ -13,10 +13,10 @@ import time import uuid -from functional.common import test +from functional.tests.volume.v2 import common -class SnapshotTests(test.TestCase): +class SnapshotTests(common.BaseVolumeTests): """Functional tests for snapshot. """ VOLLY = uuid.uuid4().hex @@ -36,6 +36,7 @@ def wait_for_status(cls, command, status, tries): @classmethod def setUpClass(cls): + super(SnapshotTests, cls).setUpClass() cls.openstack('volume create --size 1 ' + cls.VOLLY) cls.wait_for_status('volume show ' + cls.VOLLY, 'available\n', 3) opts = cls.get_show_opts(['status']) diff --git a/functional/tests/volume/v2/test_volume.py b/functional/tests/volume/v2/test_volume.py index 9c7f11a1b4..e0c1219cc4 100644 --- a/functional/tests/volume/v2/test_volume.py +++ b/functional/tests/volume/v2/test_volume.py @@ -10,14 +10,13 @@ # License for the specific language governing permissions and limitations # under the License. -import os import time import uuid -from functional.common import test +from functional.tests.volume.v2 import common -class VolumeTests(test.TestCase): +class VolumeTests(common.BaseVolumeTests): """Functional tests for volume. """ NAME = uuid.uuid4().hex @@ -29,7 +28,7 @@ class VolumeTests(test.TestCase): @classmethod def setUpClass(cls): - os.environ['OS_VOLUME_API_VERSION'] = '2' + super(VolumeTests, cls).setUpClass() opts = cls.get_show_opts(cls.FIELDS) # Create test volume @@ -111,6 +110,7 @@ def test_volume_snapshot(self): # Delete test snapshot raw_output = self.openstack('snapshot delete ' + self.SNAPSHOT_NAME) self.assertOutput('', raw_output) + self.wait_for("volume", self.NAME, "available") def wait_for(self, check_type, check_name, desired_status, wait=120, interval=5, failures=['ERROR']): diff --git a/functional/tests/volume/v2/test_volume_type.py b/functional/tests/volume/v2/test_volume_type.py index d8a7a7eb39..4c315334c8 100644 --- a/functional/tests/volume/v2/test_volume_type.py +++ b/functional/tests/volume/v2/test_volume_type.py @@ -12,10 +12,10 @@ import uuid -from functional.common import test +from functional.tests.volume.v2 import common -class VolumeTypeTests(test.TestCase): +class VolumeTypeTests(common.BaseVolumeTests): """Functional tests for volume type. """ NAME = uuid.uuid4().hex @@ -24,6 +24,7 @@ class VolumeTypeTests(test.TestCase): @classmethod def setUpClass(cls): + super(VolumeTypeTests, cls).setUpClass() opts = cls.get_show_opts(cls.FIELDS) raw_output = cls.openstack( 'volume type create --private ' + cls.NAME + opts) From e7ed403712f4d12182d0c2d040ae6eed0177a05c Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 15 Jun 2016 15:26:35 -0500 Subject: [PATCH 0999/3095] Finish osc-lib transition for command.py https://review.openstack.org/#/c/328318/ was the transition of openstackclient/common/command.py to osc-lib but it was incomplete; https://review.openstack.org/#/c/329885/ started to fix it but was also incomplete. Third time is charm? Change-Id: Ib6c0266155c2155ae59067ce7b15bebca1e375e8 --- openstackclient/common/command.py | 4 ++-- openstackclient/tests/common/test_command.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openstackclient/common/command.py b/openstackclient/common/command.py index 9abaae0b87..29c1534d63 100644 --- a/openstackclient/common/command.py +++ b/openstackclient/common/command.py @@ -17,10 +17,10 @@ import sys -from osc_lib.command import * # noqa +from osc_lib.command.command import * # noqa sys.stderr.write( "WARNING: %s is deprecated and will be removed after Jun 2017. " - "Please use osc_lib.command\n" % __name__ + "Please use osc_lib.command.command\n" % __name__ ) diff --git a/openstackclient/tests/common/test_command.py b/openstackclient/tests/common/test_command.py index a8bcf6a81a..658bc895c0 100644 --- a/openstackclient/tests/common/test_command.py +++ b/openstackclient/tests/common/test_command.py @@ -14,9 +14,9 @@ import mock -from osc_lib.command import command from osc_lib import exceptions +from openstackclient.common import command from openstackclient.tests import fakes as test_fakes from openstackclient.tests import utils as test_utils From 9eb77ae1def29294881b27b593782edad3d26c96 Mon Sep 17 00:00:00 2001 From: sunyajing Date: Mon, 13 Jun 2016 19:16:48 +0800 Subject: [PATCH 1000/3095] Fix help msg of identity endpoint argument of `endpoint create` command doesn't mean `new endpoint service`, but an existent service that the new endpoint attached to. Change-Id: I846fdb501bdea14499f42288186f375a3b2b5951 --- doc/source/command-objects/endpoint.rst | 10 +++++----- openstackclient/identity/v2_0/endpoint.py | 4 ++-- openstackclient/identity/v3/endpoint.py | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/doc/source/command-objects/endpoint.rst b/doc/source/command-objects/endpoint.rst index 9872a58737..817ad49143 100644 --- a/doc/source/command-objects/endpoint.rst +++ b/doc/source/command-objects/endpoint.rst @@ -40,7 +40,7 @@ Create new endpoint .. _endpoint_create-endpoint: .. describe:: - New endpoint service (name or ID) + Service to be associated with new endpoint (name or ID) *Identity version 3 only* @@ -68,7 +68,7 @@ Create new endpoint .. describe:: - New endpoint service (name or ID) + Service to be associated with new endpoint(name or ID) .. describe:: @@ -92,7 +92,7 @@ Delete endpoint .. _endpoint_delete-endpoint: .. describe:: - Endpoint ID to delete + Endpoint to delete (ID only) endpoint list ------------- @@ -110,7 +110,7 @@ List endpoints .. option:: --service - Filter by service + Filter by service (name or ID) *Identity version 3 only* @@ -177,7 +177,7 @@ Set endpoint properties .. _endpoint_set-endpoint: .. describe:: - Endpoint ID to modify + Endpoint to modify (ID only) endpoint show ------------- diff --git a/openstackclient/identity/v2_0/endpoint.py b/openstackclient/identity/v2_0/endpoint.py index 09ea738f4f..8065e197b9 100644 --- a/openstackclient/identity/v2_0/endpoint.py +++ b/openstackclient/identity/v2_0/endpoint.py @@ -31,7 +31,7 @@ def get_parser(self, prog_name): parser.add_argument( 'service', metavar='', - help=_('New endpoint service (name or ID)'), + help=_('Service to be associated with new endpoint (name or ID)'), ) parser.add_argument( '--publicurl', @@ -81,7 +81,7 @@ def get_parser(self, prog_name): parser.add_argument( 'endpoint', metavar='', - help=_('Endpoint ID to delete'), + help=_('Endpoint to delete (ID only)'), ) return parser diff --git a/openstackclient/identity/v3/endpoint.py b/openstackclient/identity/v3/endpoint.py index 39022d27db..6bb73b4de4 100644 --- a/openstackclient/identity/v3/endpoint.py +++ b/openstackclient/identity/v3/endpoint.py @@ -39,7 +39,7 @@ def get_parser(self, prog_name): parser.add_argument( 'service', metavar='', - help=_('New endpoint service (name or ID)'), + help=_('Service to be associated with new endpoint (name or ID)'), ) parser.add_argument( 'interface', @@ -101,7 +101,7 @@ def get_parser(self, prog_name): parser.add_argument( 'endpoint', metavar='', - help=_('Endpoint ID to delete'), + help=_('Endpoint to delete (ID only)'), ) return parser @@ -120,7 +120,7 @@ def get_parser(self, prog_name): parser.add_argument( '--service', metavar='', - help=_('Filter by service'), + help=_('Filter by service (name or ID)'), ) parser.add_argument( '--interface', @@ -168,7 +168,7 @@ def get_parser(self, prog_name): parser.add_argument( 'endpoint', metavar='', - help=_('Endpoint ID to modify'), + help=_('Endpoint to modify (ID only)'), ) parser.add_argument( '--region', From 0ac6b4fa46068773d0c4849388ce2e931d0608bc Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Wed, 15 Jun 2016 14:09:08 +0800 Subject: [PATCH 1001/3095] Add doc for logger usage This patch adds a doc to record rules of usage of loggers in OSC. Change-Id: I27a09f9abb523393031560e2310bfdd1af0b8922 Implements: blueprint log-usage --- doc/source/command-logs.rst | 73 +++++++++++++++++++++++++++++++++++++ doc/source/index.rst | 1 + 2 files changed, 74 insertions(+) create mode 100644 doc/source/command-logs.rst diff --git a/doc/source/command-logs.rst b/doc/source/command-logs.rst new file mode 100644 index 0000000000..6212651067 --- /dev/null +++ b/doc/source/command-logs.rst @@ -0,0 +1,73 @@ +============ +Command Logs +============ + +Logger usage in OpenStackClient is not exactly the same as those in other +OpenStack projects. The following basic rules should be followed. + +1. OpenStackClient uses python standard logging library instead of oslo.log + so that it will depend on oslo as little as possible. + +2. All logs except debug log need to be translated. The log message strings + that need to be translated should follow the rule of i18n guidelines: + http://docs.openstack.org/developer/oslo.i18n/guidelines.html + +3. There are mainly two kinds of logs in OpenStackClient: command specific + log and general log. Use different logger to record them. The examples + below will show the detail. + +Command specific log +==================== + +Command specific logs are those messages that used to record info, warning +and error generated from a specific command. OpenStackClient uses the logger +of the module the command belongs to to record the command specific logs. + +Example +~~~~~~~ + +This example shows how to log command specific logs in OpenStackClient. + +.. code-block:: python + + import logging + + from openstackclient.i18n import _ + + + LOG = logging.getLogger(__name__) # Get the logger of this module + + ## ... + + LOG.error(_("Error message")) + LOG.warning(_("Warning message")) + LOG.info(_("Info message")) + LOG.debug("Debug message") # Debug messages do not need to be translated + + ## ... + +General log +=========== + +General logs are those messages that not specific to any single command. Use +the logger of ``openstackclient.shell`` to record them. In each command class, +we can simply get this logger by ``self.app.log``. + +Example +~~~~~~~ + +This example shows how to log general logs in OpenStackClient. + +.. code-block:: python + + from openstackclient.i18n import _ + + + ## ... + + self.app.log.error(_("Error message")) + self.app.log.warning(_("Warning message")) + self.app.log.info(_("Info message")) + self.app.log.debug("Debug message") # Debug messages do not need to be translated + + ## ... diff --git a/doc/source/index.rst b/doc/source/index.rst index 5889553384..c8fc6f2718 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -51,6 +51,7 @@ Developer Documentation command-options command-wrappers command-errors + command-logs specs/commands Project Goals From 2d4a585841f031343c8586543b4573cc0967e9c2 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Thu, 16 Jun 2016 16:41:25 +0800 Subject: [PATCH 1002/3095] Add functional test for "aggregate unset" command There was not functional test for "aggregate unset" command, this patch add the test. Change-Id: Icc8f51e863231e915b2a8cca59baaedd54a96de5 --- functional/tests/compute/v2/test_aggregate.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/functional/tests/compute/v2/test_aggregate.py b/functional/tests/compute/v2/test_aggregate.py index b4d27fae0c..2ad6559904 100644 --- a/functional/tests/compute/v2/test_aggregate.py +++ b/functional/tests/compute/v2/test_aggregate.py @@ -57,3 +57,11 @@ def test_aggregate_properties(self): raw_output = self.openstack('aggregate show ' + self.NAME + opts) self.assertIn("a='b', c='d'\n", raw_output) + + raw_output = self.openstack( + 'aggregate unset --property a ' + self.NAME + ) + self.assertEqual('', raw_output) + + raw_output = self.openstack('aggregate show ' + self.NAME + opts) + self.assertIn("c='d'\n", raw_output) From 15821fadf34a5044ee22d20aa9d0f5a42510187e Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Thu, 16 Jun 2016 16:52:58 +0800 Subject: [PATCH 1003/3095] Modify doc issues about property option of aggregate The formal of multi properties in most docs likes this: [--property [...] ] [--property [...] ] This patch modify the formal of multi properties in aggregate.rst. Change-Id: I557cd51696d2721c8955c4a79f1ab85de3a0b9ce --- doc/source/command-objects/aggregate.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/doc/source/command-objects/aggregate.rst b/doc/source/command-objects/aggregate.rst index 6c4baa53a2..7616c49656 100644 --- a/doc/source/command-objects/aggregate.rst +++ b/doc/source/command-objects/aggregate.rst @@ -38,7 +38,7 @@ Create a new aggregate os aggregate create [--zone ] - [--property ] + [--property [...] ] .. option:: --zone @@ -115,7 +115,7 @@ Set aggregate properties os aggregate set [--name ] [--zone ] - [--property ] + [--property [...] ] .. option:: --name @@ -160,8 +160,7 @@ Unset aggregate properties .. code-block:: bash os aggregate unset - --property - [--property ] ... + [--property [...] ] .. option:: --property From ce98ed1e64e55286a11adeac852b402038da53ff Mon Sep 17 00:00:00 2001 From: sunyajing Date: Thu, 16 Jun 2016 18:04:59 +0800 Subject: [PATCH 1004/3095] Add "--password-prompt" to user.rst Change-Id: I2e86114698887a6343c248783c343f269bf4988d --- doc/source/command-objects/user.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/command-objects/user.rst b/doc/source/command-objects/user.rst index fbc0b6de75..b3707a2a0c 100644 --- a/doc/source/command-objects/user.rst +++ b/doc/source/command-objects/user.rst @@ -143,6 +143,7 @@ Set user properties [--name ] [--project [--project-domain ]] [--password ] + [--password-prompt] [--email ] [--description ] [--enable|--disable] From 041ea4978b94149d5037b5afc7743db939b75331 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Thu, 16 Jun 2016 13:09:27 +0800 Subject: [PATCH 1005/3095] Support bulk deletion for delete commands in networkv2 This patch support bulk deletion for delete commands below: 1.subnet delete 2.subnet pool delete Up to now, all the delete commands in networkv2 support bulk deletion. Change-Id: I63f6d1d02bad1fcc26e72b7028b53958a68ce2dc Partially-Implements: blueprint multi-argument-network Partial-Bug: #1592906 --- doc/source/command-objects/subnet-pool.rst | 6 +- doc/source/command-objects/subnet.rst | 6 +- openstackclient/network/v2/subnet.py | 28 ++++++-- openstackclient/network/v2/subnet_pool.py | 30 +++++++-- openstackclient/tests/network/v2/fakes.py | 38 +++++++++++ .../tests/network/v2/test_subnet.py | 66 ++++++++++++++++-- .../tests/network/v2/test_subnet_pool.py | 67 ++++++++++++++++--- ...lti-argument-network-e43e192ac95db94d.yaml | 5 +- 8 files changed, 215 insertions(+), 31 deletions(-) diff --git a/doc/source/command-objects/subnet-pool.rst b/doc/source/command-objects/subnet-pool.rst index 8abf25a671..516b9bf4a8 100644 --- a/doc/source/command-objects/subnet-pool.rst +++ b/doc/source/command-objects/subnet-pool.rst @@ -81,18 +81,18 @@ Create subnet pool subnet pool delete ------------------ -Delete subnet pool +Delete subnet pool(s) .. program:: subnet pool delete .. code:: bash os subnet pool delete - + [ ...] .. _subnet_pool_delete-subnet-pool: .. describe:: - Subnet pool to delete (name or ID) + Subnet pool(s) to delete (name or ID) subnet pool list ---------------- diff --git a/doc/source/command-objects/subnet.rst b/doc/source/command-objects/subnet.rst index ff6354e658..c52d73f932 100644 --- a/doc/source/command-objects/subnet.rst +++ b/doc/source/command-objects/subnet.rst @@ -119,18 +119,18 @@ Create new subnet subnet delete ------------- -Delete a subnet +Delete subnet(s) .. program:: subnet delete .. code:: bash os subnet delete - + [ ...] .. _subnet_delete-subnet: .. describe:: - Subnet to delete (name or ID) + Subnet(s) to delete (name or ID) subnet list ----------- diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index ceb1cb1471..a2e3262262 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -14,6 +14,7 @@ """Subnet action implementations""" import copy +import logging from osc_lib.cli import parseractions from osc_lib.command import command @@ -24,6 +25,9 @@ from openstackclient.identity import common as identity_common +LOG = logging.getLogger(__name__) + + def _format_allocation_pools(data): pool_formatted = ['%s-%s' % (pool.get('start', ''), pool.get('end', '')) for pool in data] @@ -270,21 +274,37 @@ def take_action(self, parsed_args): class DeleteSubnet(command.Command): - """Delete subnet""" + """Delete subnet(s)""" def get_parser(self, prog_name): parser = super(DeleteSubnet, self).get_parser(prog_name) parser.add_argument( 'subnet', metavar="", - help=_("Subnet to delete (name or ID)") + nargs='+', + help=_("Subnet(s) to delete (name or ID)") ) return parser def take_action(self, parsed_args): client = self.app.client_manager.network - client.delete_subnet( - client.find_subnet(parsed_args.subnet)) + result = 0 + + for subnet in parsed_args.subnet: + try: + obj = client.find_subnet(subnet, ignore_missing=False) + client.delete_subnet(obj) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete subnet with " + "name or ID '%(subnet)s': %(e)s") + % {'subnet': subnet, 'e': e}) + + if result > 0: + total = len(parsed_args.subnet) + msg = (_("%(result)s of %(total)s subnets failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListSubnet(command.Lister): diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index 79f98fd919..55dfed8392 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -13,14 +13,20 @@ """Subnet pool action implementations""" +import logging + from osc_lib.cli import parseractions from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +LOG = logging.getLogger(__name__) + + def _get_columns(item): columns = list(item.keys()) if 'tenant_id' in columns: @@ -176,21 +182,37 @@ def take_action(self, parsed_args): class DeleteSubnetPool(command.Command): - """Delete subnet pool""" + """Delete subnet pool(s)""" def get_parser(self, prog_name): parser = super(DeleteSubnetPool, self).get_parser(prog_name) parser.add_argument( 'subnet_pool', metavar='', - help=_("Subnet pool to delete (name or ID)") + nargs='+', + help=_("Subnet pool(s) to delete (name or ID)") ) return parser def take_action(self, parsed_args): client = self.app.client_manager.network - obj = client.find_subnet_pool(parsed_args.subnet_pool) - client.delete_subnet_pool(obj) + result = 0 + + for pool in parsed_args.subnet_pool: + try: + obj = client.find_subnet_pool(pool, ignore_missing=False) + client.delete_subnet_pool(obj) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete subnet pool with " + "name or ID '%(pool)s': %(e)s") + % {'pool': pool, 'e': e}) + + if result > 0: + total = len(parsed_args.subnet_pool) + msg = (_("%(result)s of %(total)s subnet pools failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListSubnetPool(command.Lister): diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 6b09e2978b..a23efc2d1d 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -771,6 +771,25 @@ def create_subnets(attrs=None, count=2): return subnets + @staticmethod + def get_subnets(subnets=None, count=2): + """Get an iterable MagicMock object with a list of faked subnets. + + If subnets list is provided, then initialize the Mock object + with the list. Otherwise create one. + + :param List subnets: + A list of FakeResource objects faking subnets + :param int count: + The number of subnets to fake + :return: + An iterable Mock object with side_effect set to a list of faked + subnets + """ + if subnets is None: + subnets = FakeSubnet.create_subnets(count) + return mock.MagicMock(side_effect=subnets) + class FakeFloatingIP(object): """Fake one or more floating ip.""" @@ -910,3 +929,22 @@ def create_subnet_pools(attrs=None, count=2): ) return subnet_pools + + @staticmethod + def get_subnet_pools(subnet_pools=None, count=2): + """Get an iterable MagicMock object with a list of faked subnet pools. + + If subnet_pools list is provided, then initialize the Mock object + with the list. Otherwise create one. + + :param List subnet pools: + A list of FakeResource objects faking subnet pools + :param int count: + The number of subnet pools to fake + :return: + An iterable Mock object with side_effect set to a list of faked + subnet pools + """ + if subnet_pools is None: + subnet_pools = FakeSubnetPool.create_subnet_pools(count) + return mock.MagicMock(side_effect=subnet_pools) diff --git a/openstackclient/tests/network/v2/test_subnet.py b/openstackclient/tests/network/v2/test_subnet.py index de7e182123..a57a030897 100644 --- a/openstackclient/tests/network/v2/test_subnet.py +++ b/openstackclient/tests/network/v2/test_subnet.py @@ -13,7 +13,9 @@ import copy import mock +from mock import call +from osc_lib import exceptions from osc_lib import utils from openstackclient.network.v2 import subnet as subnet_v2 @@ -361,32 +363,82 @@ def test_create_options_subnet_range_ipv6(self): class TestDeleteSubnet(TestSubnet): - # The subnet to delete. - _subnet = network_fakes.FakeSubnet.create_one_subnet() + # The subnets to delete. + _subnets = network_fakes.FakeSubnet.create_subnets(count=2) def setUp(self): super(TestDeleteSubnet, self).setUp() self.network.delete_subnet = mock.Mock(return_value=None) - self.network.find_subnet = mock.Mock(return_value=self._subnet) + self.network.find_subnet = ( + network_fakes.FakeSubnet.get_subnets(self._subnets)) # Get the command object to test self.cmd = subnet_v2.DeleteSubnet(self.app, self.namespace) - def test_delete(self): + def test_subnet_delete(self): arglist = [ - self._subnet.name, + self._subnets[0].name, ] verifylist = [ - ('subnet', self._subnet.name), + ('subnet', [self._subnets[0].name]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.network.delete_subnet.assert_called_once_with(self._subnet) + self.network.delete_subnet.assert_called_once_with(self._subnets[0]) self.assertIsNone(result) + def test_multi_subnets_delete(self): + arglist = [] + verifylist = [] + + for s in self._subnets: + arglist.append(s.name) + verifylist = [ + ('subnet', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [] + for s in self._subnets: + calls.append(call(s)) + self.network.delete_subnet.assert_has_calls(calls) + self.assertIsNone(result) + + def test_multi_subnets_delete_with_exception(self): + arglist = [ + self._subnets[0].name, + 'unexist_subnet', + ] + verifylist = [ + ('subnet', + [self._subnets[0].name, 'unexist_subnet']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self._subnets[0], exceptions.CommandError] + self.network.find_subnet = ( + mock.MagicMock(side_effect=find_mock_result) + ) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 subnets failed to delete.', str(e)) + + self.network.find_subnet.assert_any_call( + self._subnets[0].name, ignore_missing=False) + self.network.find_subnet.assert_any_call( + 'unexist_subnet', ignore_missing=False) + self.network.delete_subnet.assert_called_once_with( + self._subnets[0] + ) + class TestListSubnet(TestSubnet): # The subnets going to be listed up. diff --git a/openstackclient/tests/network/v2/test_subnet_pool.py b/openstackclient/tests/network/v2/test_subnet_pool.py index 10ef76d81a..7a96b30f67 100644 --- a/openstackclient/tests/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/network/v2/test_subnet_pool.py @@ -14,7 +14,9 @@ import argparse import copy import mock +from mock import call +from osc_lib import exceptions from osc_lib import utils from openstackclient.network.v2 import subnet_pool @@ -263,36 +265,85 @@ def test_create_default_and_shared_options(self): class TestDeleteSubnetPool(TestSubnetPool): - # The subnet pool to delete. - _subnet_pool = network_fakes.FakeSubnetPool.create_one_subnet_pool() + # The subnet pools to delete. + _subnet_pools = network_fakes.FakeSubnetPool.create_subnet_pools(count=2) def setUp(self): super(TestDeleteSubnetPool, self).setUp() self.network.delete_subnet_pool = mock.Mock(return_value=None) - self.network.find_subnet_pool = mock.Mock( - return_value=self._subnet_pool + self.network.find_subnet_pool = ( + network_fakes.FakeSubnetPool.get_subnet_pools(self._subnet_pools) ) # Get the command object to test self.cmd = subnet_pool.DeleteSubnetPool(self.app, self.namespace) - def test_delete(self): + def test_subnet_pool_delete(self): arglist = [ - self._subnet_pool.name, + self._subnet_pools[0].name, ] verifylist = [ - ('subnet_pool', self._subnet_pool.name), + ('subnet_pool', [self._subnet_pools[0].name]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.network.delete_subnet_pool.assert_called_once_with( - self._subnet_pool) + self._subnet_pools[0]) + self.assertIsNone(result) + + def test_multi_subnet_pools_delete(self): + arglist = [] + verifylist = [] + + for s in self._subnet_pools: + arglist.append(s.name) + verifylist = [ + ('subnet_pool', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [] + for s in self._subnet_pools: + calls.append(call(s)) + self.network.delete_subnet_pool.assert_has_calls(calls) self.assertIsNone(result) + def test_multi_subnet_pools_delete_with_exception(self): + arglist = [ + self._subnet_pools[0].name, + 'unexist_subnet_pool', + ] + verifylist = [ + ('subnet_pool', + [self._subnet_pools[0].name, 'unexist_subnet_pool']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self._subnet_pools[0], exceptions.CommandError] + self.network.find_subnet_pool = ( + mock.MagicMock(side_effect=find_mock_result) + ) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 subnet pools failed to delete.', str(e)) + + self.network.find_subnet_pool.assert_any_call( + self._subnet_pools[0].name, ignore_missing=False) + self.network.find_subnet_pool.assert_any_call( + 'unexist_subnet_pool', ignore_missing=False) + self.network.delete_subnet_pool.assert_called_once_with( + self._subnet_pools[0] + ) + class TestListSubnetPool(TestSubnetPool): # The subnet pools going to be listed up. diff --git a/releasenotes/notes/bp-multi-argument-network-e43e192ac95db94d.yaml b/releasenotes/notes/bp-multi-argument-network-e43e192ac95db94d.yaml index 0c56e8205e..9bfc86a713 100644 --- a/releasenotes/notes/bp-multi-argument-network-e43e192ac95db94d.yaml +++ b/releasenotes/notes/bp-multi-argument-network-e43e192ac95db94d.yaml @@ -1,5 +1,6 @@ --- features: - - Support bulk deletion for ``floating ip delete``, ``security group delete``, - and ``security group rule delete`` commands in networkv2. + - Support bulk deletion for ``subnet pool delete``, ``subnet delete``, + ``floating ip delete``, ``security group delete`` and + ``security group rule delete``. [Blueprint `multi-argument-network `_] From 8cceaddaac43729c068f0f99675967ee3a7b228a Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Wed, 8 Jun 2016 18:23:01 +0800 Subject: [PATCH 1006/3095] Fix console url show command broken in microversion case The response data of nova get_xxx_console API is changed from "console" to "remote_console" in microversion 2.6, and nova server side API schema verify the spice vnc type to "spice-html5", update OSC code to apply these change so that OSC can work in different nova microversion cases. Change-Id: I3bb4fe057e656209d00d2bb308ac3f7f837cb03f Closes-Bug: #1590318 --- openstackclient/compute/v2/console.py | 12 +- .../tests/compute/v2/test_console.py | 149 ++++++++++++++++++ 2 files changed, 158 insertions(+), 3 deletions(-) create mode 100644 openstackclient/tests/compute/v2/test_console.py diff --git a/openstackclient/compute/v2/console.py b/openstackclient/compute/v2/console.py index 97d3f318f8..4179fa5c1e 100644 --- a/openstackclient/compute/v2/console.py +++ b/openstackclient/compute/v2/console.py @@ -93,7 +93,7 @@ def get_parser(self, prog_name): '--spice', dest='url_type', action='store_const', - const='spice', + const='spice-html5', help=_("Show SPICE console URL") ) return parser @@ -105,14 +105,20 @@ def take_action(self, parsed_args): parsed_args.server, ) + data = None if parsed_args.url_type in ['novnc', 'xvpvnc']: data = server.get_vnc_console(parsed_args.url_type) - if parsed_args.url_type in ['spice']: + if parsed_args.url_type in ['spice-html5']: data = server.get_spice_console(parsed_args.url_type) if not data: return ({}, {}) info = {} - info.update(data['console']) + # NOTE(Rui Chen): Return 'remote_console' in compute microversion API + # 2.6 and later, return 'console' in compute + # microversion API from 2.0 to 2.5, do compatibility + # handle for different microversion API. + console_data = data.get('remote_console', data.get('console')) + info.update(console_data) return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/tests/compute/v2/test_console.py b/openstackclient/tests/compute/v2/test_console.py new file mode 100644 index 0000000000..6be081263c --- /dev/null +++ b/openstackclient/tests/compute/v2/test_console.py @@ -0,0 +1,149 @@ +# Copyright 2016 Huawei, Inc. 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.compute.v2 import console +from openstackclient.tests.compute.v2 import fakes as compute_fakes + + +class TestConsole(compute_fakes.TestComputev2): + + def setUp(self): + super(TestConsole, self).setUp() + self.servers_mock = self.app.client_manager.compute.servers + self.servers_mock.reset_mock() + + +class TestConsoleUrlShow(TestConsole): + + def setUp(self): + super(TestConsoleUrlShow, self).setUp() + fake_console_data = {'remote_console': {'url': 'http://localhost', + 'protocol': 'fake_protocol', + 'type': 'fake_type'}} + methods = { + 'get_vnc_console': fake_console_data, + 'get_spice_console': fake_console_data, + 'get_serial_console': fake_console_data, + 'get_rdp_console': fake_console_data, + 'get_mks_console': fake_console_data, + } + self.fake_server = compute_fakes.FakeServer.create_one_server( + methods=methods) + self.servers_mock.get.return_value = self.fake_server + + self.columns = ( + 'protocol', + 'type', + 'url', + ) + self.data = ( + fake_console_data['remote_console']['protocol'], + fake_console_data['remote_console']['type'], + fake_console_data['remote_console']['url'] + ) + + self.cmd = console.ShowConsoleURL(self.app, None) + + def test_console_url_show_by_default(self): + arglist = [ + 'foo_vm', + ] + verifylist = [ + ('url_type', 'novnc'), + ('server', 'foo_vm'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.fake_server.get_vnc_console.assert_called_once_with('novnc') + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_console_url_show_with_novnc(self): + arglist = [ + '--novnc', + 'foo_vm', + ] + verifylist = [ + ('url_type', 'novnc'), + ('server', 'foo_vm'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.fake_server.get_vnc_console.assert_called_once_with('novnc') + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_console_url_show_with_xvpvnc(self): + arglist = [ + '--xvpvnc', + 'foo_vm', + ] + verifylist = [ + ('url_type', 'xvpvnc'), + ('server', 'foo_vm'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.fake_server.get_vnc_console.assert_called_once_with('xvpvnc') + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_console_url_show_with_spice(self): + arglist = [ + '--spice', + 'foo_vm', + ] + verifylist = [ + ('url_type', 'spice-html5'), + ('server', 'foo_vm'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.fake_server.get_spice_console.assert_called_once_with( + 'spice-html5') + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_console_url_show_compatible(self): + methods = { + 'get_vnc_console': {'console': {'url': 'http://localhost', + 'type': 'fake_type'}}, + } + old_fake_server = compute_fakes.FakeServer.create_one_server( + methods=methods) + old_columns = ( + 'type', + 'url', + ) + old_data = ( + methods['get_vnc_console']['console']['type'], + methods['get_vnc_console']['console']['url'] + ) + arglist = [ + 'foo_vm', + ] + verifylist = [ + ('url_type', 'novnc'), + ('server', 'foo_vm'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + with mock.patch.object(self.servers_mock, 'get', + return_value=old_fake_server): + columns, data = self.cmd.take_action(parsed_args) + old_fake_server.get_vnc_console.assert_called_once_with('novnc') + self.assertEqual(old_columns, columns) + self.assertEqual(old_data, data) From 2740291f491804be5f521770ee773fa62894e8a3 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Thu, 16 Jun 2016 14:01:01 -0400 Subject: [PATCH 1007/3095] move release note to correct directory not sure how this one release note was slotted into it's own directory, but let's move it with the others. Change-Id: I20eb36b1778b6fc9b5e550390962ec11dae38db4 --- .../notes/bug-1543222-6f8579344ff5c958.yaml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {openstackclient/releasenotes => releasenotes}/notes/bug-1543222-6f8579344ff5c958.yaml (100%) diff --git a/openstackclient/releasenotes/notes/bug-1543222-6f8579344ff5c958.yaml b/releasenotes/notes/bug-1543222-6f8579344ff5c958.yaml similarity index 100% rename from openstackclient/releasenotes/notes/bug-1543222-6f8579344ff5c958.yaml rename to releasenotes/notes/bug-1543222-6f8579344ff5c958.yaml From 8a12a39ece3882af56b42898ffee0d537c96edc8 Mon Sep 17 00:00:00 2001 From: sunyajing Date: Tue, 14 Jun 2016 15:44:41 +0800 Subject: [PATCH 1008/3095] Make set/unset command in identity and image pass normally when nothing specified Also update its unit tests. Change-Id: I82b90658b0d4247cdc9a650f14aceda640a32059 Partial-bug: #1588588 --- openstackclient/identity/v2_0/project.py | 21 +++--------- openstackclient/identity/v2_0/user.py | 9 ----- openstackclient/image/v2/image.py | 13 ------- .../tests/identity/v2_0/test_project.py | 34 +++++++++++++++++++ .../tests/identity/v2_0/test_user.py | 22 ++++++++++++ openstackclient/tests/image/v2/test_image.py | 26 ++++++++++++++ .../notes/bug-1588588-39927ef06ca35730.yaml | 4 +-- 7 files changed, 89 insertions(+), 40 deletions(-) diff --git a/openstackclient/identity/v2_0/project.py b/openstackclient/identity/v2_0/project.py index 8be482fe57..c4f730e0a7 100644 --- a/openstackclient/identity/v2_0/project.py +++ b/openstackclient/identity/v2_0/project.py @@ -189,13 +189,6 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - if (not parsed_args.name - and not parsed_args.description - and not parsed_args.enable - and not parsed_args.property - and not parsed_args.disable): - return - project = utils.find_resource( identity_client.tenants, parsed_args.project, @@ -295,7 +288,6 @@ def get_parser(self, prog_name): metavar='', action='append', default=[], - required=True, help=_('Unset a project property ' '(repeat option to unset multiple properties)'), ) @@ -307,11 +299,8 @@ def take_action(self, parsed_args): identity_client.tenants, parsed_args.project, ) - if not parsed_args.property: - self.app.log.error(_("No changes requested\n")) - else: - kwargs = project._info - for key in parsed_args.property: - if key in kwargs: - kwargs[key] = None - identity_client.tenants.update(project.id, **kwargs) + kwargs = project._info + for key in parsed_args.property: + if key in kwargs: + kwargs[key] = None + identity_client.tenants.update(project.id, **kwargs) diff --git a/openstackclient/identity/v2_0/user.py b/openstackclient/identity/v2_0/user.py index 7777bab886..3ee2a65e55 100644 --- a/openstackclient/identity/v2_0/user.py +++ b/openstackclient/identity/v2_0/user.py @@ -287,15 +287,6 @@ def take_action(self, parsed_args): if parsed_args.password_prompt: parsed_args.password = utils.get_password(self.app.stdin) - if (not parsed_args.name - and not parsed_args.name - and not parsed_args.password - and not parsed_args.email - and not parsed_args.project - and not parsed_args.enable - and not parsed_args.disable): - return - user = utils.find_resource( identity_client.users, parsed_args.user, diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 62f7bee88c..47ba649386 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -803,11 +803,6 @@ def take_action(self, parsed_args): parsed_args.project_domain, ).id - # Checks if anything that requires getting the image - if not (kwargs or parsed_args.deactivate or parsed_args.activate): - msg = _("No arguments specified") - raise exceptions.CommandError(msg) - image = utils.find_resource( image_client.images, parsed_args.image) @@ -819,10 +814,6 @@ def take_action(self, parsed_args): image_client.images.reactivate(image.id) activation_status = "activated" - # Check if need to do the actual update - if not kwargs: - return {}, {} - if parsed_args.tags: # Tags should be extended, but duplicates removed kwargs['tags'] = list(set(image.tags).union(set(parsed_args.tags))) @@ -895,10 +886,6 @@ def take_action(self, parsed_args): parsed_args.image, ) - if not (parsed_args.tags or parsed_args.properties): - msg = _("No arguments specified") - raise exceptions.CommandError(msg) - kwargs = {} tagret = 0 propret = 0 diff --git a/openstackclient/tests/identity/v2_0/test_project.py b/openstackclient/tests/identity/v2_0/test_project.py index 38684aaffe..1eb1260419 100644 --- a/openstackclient/tests/identity/v2_0/test_project.py +++ b/openstackclient/tests/identity/v2_0/test_project.py @@ -16,6 +16,7 @@ import copy from keystoneauth1 import exceptions as ks_exc +from osc_lib import exceptions from openstackclient.identity.v2_0 import project from openstackclient.tests import fakes @@ -410,6 +411,26 @@ def test_project_set_no_options(self): self.assertIsNone(result) + def test_project_set_unexist_project(self): + arglist = [ + "unexist-project", + ] + verifylist = [ + ('project', "unexist-project"), + ('name', None), + ('description', None), + ('enable', False), + ('disable', False), + ('property', None), + ] + self.projects_mock.get.side_effect = exceptions.NotFound(None) + self.projects_mock.find.side_effect = exceptions.NotFound(None) + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, self.cmd.take_action, parsed_args) + def test_project_set_name(self): arglist = [ '--name', 'qwerty', @@ -604,6 +625,19 @@ def setUp(self): # Get the command object to test self.cmd = project.UnsetProject(self.app, None) + def test_project_unset_no_options(self): + arglist = [ + identity_fakes.project_name, + ] + verifylist = [ + ('project', identity_fakes.project_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.assertIsNone(result) + def test_project_unset_key(self): arglist = [ '--property', 'fee', diff --git a/openstackclient/tests/identity/v2_0/test_user.py b/openstackclient/tests/identity/v2_0/test_user.py index caf38a6f26..f7a7b08c3f 100644 --- a/openstackclient/tests/identity/v2_0/test_user.py +++ b/openstackclient/tests/identity/v2_0/test_user.py @@ -17,6 +17,7 @@ import mock from keystoneauth1 import exceptions as ks_exc +from osc_lib import exceptions from openstackclient.identity.v2_0 import user from openstackclient.tests import fakes @@ -563,6 +564,27 @@ def test_user_set_no_options(self): self.assertIsNone(result) + def test_user_set_unexist_user(self): + arglist = [ + "unexist-user", + ] + verifylist = [ + ('name', None), + ('password', None), + ('email', None), + ('project', None), + ('enable', False), + ('disable', False), + ('user', "unexist-user"), + ] + self.users_mock.get.side_effect = exceptions.NotFound(None) + self.users_mock.find.side_effect = exceptions.NotFound(None) + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, self.cmd.take_action, parsed_args) + def test_user_set_name(self): arglist = [ '--name', 'qwerty', diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index 92e0660faf..3dbf504aff 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -811,6 +811,19 @@ def setUp(self): # Get the command object to test self.cmd = image.SetImage(self.app, None) + def test_image_set_no_options(self): + arglist = [ + image_fakes.image_id, + ] + verifylist = [ + ('image', image_fakes.image_id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.assertIsNone(result) + def test_image_set_options(self): arglist = [ '--name', 'new-name', @@ -1211,6 +1224,19 @@ def setUp(self): # Get the command object to test self.cmd = image.UnsetImage(self.app, None) + def test_image_unset_no_options(self): + arglist = [ + image_fakes.image_id, + ] + verifylist = [ + ('image', image_fakes.image_id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.assertIsNone(result) + def test_image_unset_tag_option(self): arglist = [ diff --git a/releasenotes/notes/bug-1588588-39927ef06ca35730.yaml b/releasenotes/notes/bug-1588588-39927ef06ca35730.yaml index 440bb7d31c..c745b6bef2 100644 --- a/releasenotes/notes/bug-1588588-39927ef06ca35730.yaml +++ b/releasenotes/notes/bug-1588588-39927ef06ca35730.yaml @@ -1,6 +1,6 @@ --- upgrade: - - All ``set`` and ``unset`` commands in network and volume now return - normally when nothing specified to modify. This will become the default + - All ``set`` and ``unset`` commands in network, identity, image, and volume now + return normally when nothing specified to modify. This will become the default behavior of OSC ``set`` and ``unset`` commands. [Bug `1588588 `_] From 6dbe911800b9e8e5fdad88e6e47e718db29f180d Mon Sep 17 00:00:00 2001 From: sunyajing Date: Fri, 17 Jun 2016 17:03:31 +0800 Subject: [PATCH 1009/3095] Modify help msg and docs in identity Migrate 'change', 'update' to 'modify', migrate 'user to delete' to 'user(s) to delete', migrate '(name or ID)' to '(type, name or ID)'. Change-Id: Ie425e178bb5ddf773e6e793fcd91c78e9c4a5053 --- doc/source/command-objects/service.rst | 2 +- doc/source/command-objects/user.rst | 2 +- openstackclient/identity/v2_0/service.py | 2 +- openstackclient/identity/v2_0/user.py | 2 +- openstackclient/identity/v3/service.py | 2 +- openstackclient/identity/v3/user.py | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/source/command-objects/service.rst b/doc/source/command-objects/service.rst index 352f68a0d4..421a298b31 100644 --- a/doc/source/command-objects/service.rst +++ b/doc/source/command-objects/service.rst @@ -117,7 +117,7 @@ Set service properties .. _service_set-service: .. describe:: - Service to update (type, name or ID) + Service to modify (type, name or ID) service show ------------ diff --git a/doc/source/command-objects/user.rst b/doc/source/command-objects/user.rst index b3707a2a0c..a9fee72811 100644 --- a/doc/source/command-objects/user.rst +++ b/doc/source/command-objects/user.rst @@ -95,7 +95,7 @@ Delete user(s) .. _user_delete-user: .. describe:: - User to delete (name or ID) + User(s) to delete (name or ID) user list --------- diff --git a/openstackclient/identity/v2_0/service.py b/openstackclient/identity/v2_0/service.py index 947f361cf3..c67dede716 100644 --- a/openstackclient/identity/v2_0/service.py +++ b/openstackclient/identity/v2_0/service.py @@ -94,7 +94,7 @@ def get_parser(self, prog_name): parser.add_argument( 'service', metavar='', - help=_('Service to delete (name or ID)'), + help=_('Service to delete (type, name or ID)'), ) return parser diff --git a/openstackclient/identity/v2_0/user.py b/openstackclient/identity/v2_0/user.py index 3ee2a65e55..b8bbdaf47a 100644 --- a/openstackclient/identity/v2_0/user.py +++ b/openstackclient/identity/v2_0/user.py @@ -240,7 +240,7 @@ def get_parser(self, prog_name): parser.add_argument( 'user', metavar='', - help=_('User to change (name or ID)'), + help=_('User to modify (name or ID)'), ) parser.add_argument( '--name', diff --git a/openstackclient/identity/v3/service.py b/openstackclient/identity/v3/service.py index 7beadc9d18..195b2701fb 100644 --- a/openstackclient/identity/v3/service.py +++ b/openstackclient/identity/v3/service.py @@ -130,7 +130,7 @@ def get_parser(self, prog_name): parser.add_argument( 'service', metavar='', - help=_('Service to update (type, name or ID)'), + help=_('Service to modify (type, name or ID)'), ) parser.add_argument( '--type', diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index 39125e2cd2..f916c8fc62 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -273,7 +273,7 @@ def get_parser(self, prog_name): parser.add_argument( 'user', metavar='', - help=_('User to change (name or ID)'), + help=_('User to modify (name or ID)'), ) parser.add_argument( '--name', From cd8d49590b444a10ba362b606494a31ca75984a4 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Fri, 17 Jun 2016 14:20:56 +0000 Subject: [PATCH 1010/3095] Updated from global requirements Change-Id: I2d6aadd0509f5f311d7433ea457ecf5ad6e4e64e --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f3affbd37d..a9a5b0c633 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ six>=1.9.0 # MIT Babel>=2.3.4 # BSD cliff!=1.16.0,!=1.17.0,>=1.15.0 # Apache-2.0 -keystoneauth1>=2.1.0 # Apache-2.0 +keystoneauth1>=2.7.0 # Apache-2.0 openstacksdk>=0.8.6 # Apache-2.0 os-client-config>=1.13.1 # Apache-2.0 osc-lib>=0.1.0 # Apache-2.0 From fe0c8e955be0331aef9cc6847c9bddc43ce66d92 Mon Sep 17 00:00:00 2001 From: Dolph Mathews Date: Wed, 15 Jun 2016 16:26:35 +0000 Subject: [PATCH 1011/3095] Do not prompt for scope options with default scoped tokens This changes the scope validation to occur after a token has already been created. Previous flow: 1. Validate authentication options. 2. Validate authorization options if the command requires a scope. 3. Create a token (using authentication + authorization options) 4. Run command. This means that scope was being checked, even if a default scope was applied in step 3 by Keystone. New flow: 1. Validate authentication options. 2. Create token (using authentication + authorization options) 3 Validate authorization options if the command requires a scope and the token is not scoped. 4. Run command. Change-Id: Idae368a11249f425b14b891fc68b4176e2b3e981 Closes-Bug: 1592062 --- openstackclient/api/auth.py | 36 +++++++++---------- openstackclient/common/clientmanager.py | 25 +++++++++---- openstackclient/shell.py | 6 ++-- .../tests/common/test_clientmanager.py | 10 +++--- .../notes/bug-1592062-e327a31a5ae66809.yaml | 5 +++ 5 files changed, 49 insertions(+), 33 deletions(-) create mode 100644 releasenotes/notes/bug-1592062-e327a31a5ae66809.yaml diff --git a/openstackclient/api/auth.py b/openstackclient/api/auth.py index b56035e4dd..0018e76e61 100644 --- a/openstackclient/api/auth.py +++ b/openstackclient/api/auth.py @@ -128,12 +128,24 @@ def build_auth_params(auth_plugin_name, cmd_options): return (auth_plugin_loader, auth_params) -def check_valid_auth_options(options, auth_plugin_name, required_scope=True): - """Perform basic option checking, provide helpful error messages. - - :param required_scope: indicate whether a scoped token is required - - """ +def check_valid_authorization_options(options, auth_plugin_name): + """Validate authorization options, and provide helpful error messages.""" + if (options.auth.get('project_id') and not + options.auth.get('domain_id') and not + options.auth.get('domain_name') and not + options.auth.get('project_name') and not + options.auth.get('tenant_id') and not + options.auth.get('tenant_name')): + raise exc.CommandError(_( + 'Missing parameter(s): ' + 'Set either a project or a domain scope, but not both. Set a ' + 'project scope with --os-project-name, OS_PROJECT_NAME, or ' + 'auth.project_name. Alternatively, set a domain scope with ' + '--os-domain-name, OS_DOMAIN_NAME or auth.domain_name.')) + + +def check_valid_authentication_options(options, auth_plugin_name): + """Validate authentication options, and provide helpful error messages.""" msgs = [] if auth_plugin_name.endswith('password'): @@ -143,18 +155,6 @@ def check_valid_auth_options(options, auth_plugin_name, required_scope=True): if not options.auth.get('auth_url'): msgs.append(_('Set an authentication URL, with --os-auth-url,' ' OS_AUTH_URL or auth.auth_url')) - if (required_scope and not - options.auth.get('project_id') and not - options.auth.get('domain_id') and not - options.auth.get('domain_name') and not - options.auth.get('project_name') and not - options.auth.get('tenant_id') and not - options.auth.get('tenant_name')): - msgs.append(_('Set a scope, such as a project or domain, set a ' - 'project scope with --os-project-name, ' - 'OS_PROJECT_NAME or auth.project_name, set a domain ' - 'scope with --os-domain-name, OS_DOMAIN_NAME or ' - 'auth.domain_name')) elif auth_plugin_name.endswith('token'): if not options.auth.get('token'): msgs.append(_('Set a token with --os-token, OS_TOKEN or ' diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index 04f624d0b0..5dbfb41712 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -140,10 +140,8 @@ def __init__( # prior to dereferrencing auth_ref. self._auth_setup_completed = False - def setup_auth(self, required_scope=True): - """Set up authentication - - :param required_scope: indicate whether a scoped token is required + def setup_auth(self): + """Set up authentication. This is deferred until authentication is actually attempted because it gets in the way of things that do not require auth. @@ -157,9 +155,8 @@ def setup_auth(self, required_scope=True): self.auth_plugin_name = auth.select_auth_plugin(self._cli_options) # Basic option checking to avoid unhelpful error messages - auth.check_valid_auth_options(self._cli_options, - self.auth_plugin_name, - required_scope=required_scope) + auth.check_valid_authentication_options(self._cli_options, + self.auth_plugin_name) # Horrible hack alert...must handle prompt for null password if # password auth is requested. @@ -229,6 +226,20 @@ def setup_auth(self, required_scope=True): self._auth_setup_completed = True + def validate_scope(self): + if self._auth_ref.project_id is not None: + # We already have a project scope. + return + if self._auth_ref.domain_id is not None: + # We already have a domain scope. + return + + # We do not have a scoped token (and the user's default project scope + # was not implied), so the client needs to be explicitly configured + # with a scope. + auth.check_valid_authorization_options(self._cli_options, + self.auth_plugin_name) + @property def auth_ref(self): """Dereference will trigger an auth if it hasn't already""" diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 12a63af219..49a0604081 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -443,12 +443,12 @@ def prepare_to_run_command(self, cmd): cmd.__class__.__name__, ) if cmd.auth_required: - if hasattr(cmd, 'required_scope'): + self.client_manager.setup_auth() + if hasattr(cmd, 'required_scope') and cmd.required_scope: # let the command decide whether we need a scoped token - self.client_manager.setup_auth(cmd.required_scope) + self.client_manager.validate_scope() # Trigger the Identity client to initialize self.client_manager.auth_ref - return def clean_up(self, cmd, result, err): self.log.debug('clean_up %s: %s', cmd.__class__.__name__, err or '') diff --git a/openstackclient/tests/common/test_clientmanager.py b/openstackclient/tests/common/test_clientmanager.py index 520cce1330..0a9965e0f3 100644 --- a/openstackclient/tests/common/test_clientmanager.py +++ b/openstackclient/tests/common/test_clientmanager.py @@ -356,8 +356,8 @@ def test_client_manager_select_auth_plugin_failure(self): client_manager.setup_auth, ) - @mock.patch('openstackclient.api.auth.check_valid_auth_options') - def test_client_manager_auth_setup_once(self, check_auth_options_func): + @mock.patch('openstackclient.api.auth.check_valid_authentication_options') + def test_client_manager_auth_setup_once(self, check_authn_options_func): client_manager = clientmanager.ClientManager( cli_options=FakeOptions( auth=dict( @@ -372,11 +372,11 @@ def test_client_manager_auth_setup_once(self, check_auth_options_func): ) self.assertFalse(client_manager._auth_setup_completed) client_manager.setup_auth() - self.assertTrue(check_auth_options_func.called) + self.assertTrue(check_authn_options_func.called) self.assertTrue(client_manager._auth_setup_completed) # now make sure we don't do auth setup the second time around # by checking whether check_valid_auth_options() gets called again - check_auth_options_func.reset_mock() + check_authn_options_func.reset_mock() client_manager.auth_ref - check_auth_options_func.assert_not_called() + check_authn_options_func.assert_not_called() diff --git a/releasenotes/notes/bug-1592062-e327a31a5ae66809.yaml b/releasenotes/notes/bug-1592062-e327a31a5ae66809.yaml new file mode 100644 index 0000000000..2a7751aae3 --- /dev/null +++ b/releasenotes/notes/bug-1592062-e327a31a5ae66809.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - Scope options are now validated after authentication occurs, and only if + the user does not have a default project scope. + [Bug `1592062 `_] From 8b6626e9b60abb15a94c6c2eac2d1536f6a1918b Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Mon, 20 Jun 2016 18:05:20 +0800 Subject: [PATCH 1012/3095] Error handling of "router delete" command "Router delete" command supports multi deletion but no error handling. This patch add the error handling follow the rule in doc/source/command-error.rst Change-Id: I3376d957b4dc28d8282599dc909ecc5ed2b5f46a --- openstackclient/network/v2/router.py | 19 +++++- .../tests/network/v2/test_router.py | 66 +++++++++++++++++-- 2 files changed, 76 insertions(+), 9 deletions(-) diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 92ecf84d2c..6271a8784f 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -19,6 +19,7 @@ from osc_lib.cli import parseractions from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils from openstackclient.i18n import _ @@ -222,9 +223,23 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): client = self.app.client_manager.network + result = 0 + for router in parsed_args.router: - obj = client.find_router(router) - client.delete_router(obj) + try: + obj = client.find_router(router, ignore_missing=False) + client.delete_router(obj) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete router with " + "name or ID '%(router)s': %(e)s") + % {'router': router, 'e': e}) + + if result > 0: + total = len(parsed_args.router) + msg = (_("%(result)s of %(total)s routers failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListRouter(command.Lister): diff --git a/openstackclient/tests/network/v2/test_router.py b/openstackclient/tests/network/v2/test_router.py index 1629613998..e3da253aa7 100644 --- a/openstackclient/tests/network/v2/test_router.py +++ b/openstackclient/tests/network/v2/test_router.py @@ -12,7 +12,9 @@ # import mock +from mock import call +from osc_lib import exceptions from osc_lib import utils as osc_utils from openstackclient.network.v2 import router @@ -202,32 +204,82 @@ def test_create_with_AZ_hints(self): class TestDeleteRouter(TestRouter): - # The router to delete. - _router = network_fakes.FakeRouter.create_one_router() + # The routers to delete. + _routers = network_fakes.FakeRouter.create_routers(count=2) def setUp(self): super(TestDeleteRouter, self).setUp() self.network.delete_router = mock.Mock(return_value=None) - self.network.find_router = mock.Mock(return_value=self._router) + self.network.find_router = ( + network_fakes.FakeRouter.get_routers(self._routers)) # Get the command object to test self.cmd = router.DeleteRouter(self.app, self.namespace) - def test_delete(self): + def test_router_delete(self): arglist = [ - self._router.name, + self._routers[0].name, ] verifylist = [ - ('router', [self._router.name]), + ('router', [self._routers[0].name]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.network.delete_router.assert_called_once_with(self._routers[0]) + self.assertIsNone(result) + + def test_multi_routers_delete(self): + arglist = [] + verifylist = [] + + for r in self._routers: + arglist.append(r.name) + verifylist = [ + ('router', arglist), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.network.delete_router.assert_called_once_with(self._router) + + calls = [] + for r in self._routers: + calls.append(call(r)) + self.network.delete_router.assert_has_calls(calls) self.assertIsNone(result) + def test_multi_routers_delete_with_exception(self): + arglist = [ + self._routers[0].name, + 'unexist_router', + ] + verifylist = [ + ('router', + [self._routers[0].name, 'unexist_router']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self._routers[0], exceptions.CommandError] + self.network.find_router = ( + mock.MagicMock(side_effect=find_mock_result) + ) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 routers failed to delete.', str(e)) + + self.network.find_router.assert_any_call( + self._routers[0].name, ignore_missing=False) + self.network.find_router.assert_any_call( + 'unexist_router', ignore_missing=False) + self.network.delete_router.assert_called_once_with( + self._routers[0] + ) + class TestListRouter(TestRouter): From d67c2e838320f79b4ddc74238bb827026f5275bd Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Mon, 20 Jun 2016 20:02:05 +0800 Subject: [PATCH 1013/3095] Use osc_lib in server_image.py server_image.py is a newly created file. So I think we forgot to use osc_lib in it. Change-Id: Ieda13438662ea55b03f549108aac63c18b9af913 --- openstackclient/compute/v2/server_image.py | 6 +++--- openstackclient/tests/compute/v2/test_server_image.py | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/openstackclient/compute/v2/server_image.py b/openstackclient/compute/v2/server_image.py index 85ee7f2d18..88d8f0830f 100644 --- a/openstackclient/compute/v2/server_image.py +++ b/openstackclient/compute/v2/server_image.py @@ -17,12 +17,12 @@ import sys +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils from oslo_utils import importutils import six -from openstackclient.common import command -from openstackclient.common import exceptions -from openstackclient.common import utils from openstackclient.i18n import _ diff --git a/openstackclient/tests/compute/v2/test_server_image.py b/openstackclient/tests/compute/v2/test_server_image.py index 660e981784..8a8bd9bc90 100644 --- a/openstackclient/tests/compute/v2/test_server_image.py +++ b/openstackclient/tests/compute/v2/test_server_image.py @@ -12,8 +12,9 @@ # import mock -from openstackclient.common import exceptions -from openstackclient.common import utils as common_utils +from osc_lib import exceptions +from osc_lib import utils as common_utils + from openstackclient.compute.v2 import server_image from openstackclient.tests.compute.v2 import fakes as compute_fakes from openstackclient.tests.image.v2 import fakes as image_fakes From 047cb6849354f4fdf8d365bd109a0ed56a77d200 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Thu, 16 Jun 2016 20:01:15 +0800 Subject: [PATCH 1014/3095] Standardize logger usage Use file logger for all command specific logs. This patch also fixes some usage that doesn't follow rules in: http://docs.openstack.org/developer/oslo.i18n/guidelines.html After this patch, all self.log and self.app.log will be standardized to LOG(). NOTE: In shell.py, we got the log in class OpenStackShell, which is also known as self.app.log in other classes. This logger is used to record non-command-specific logs. So we leave it as-is. Change-Id: I114f73ee6c7e84593d71e724bc1ad00d343c1896 Implements: blueprint log-usage --- openstackclient/common/availability_zone.py | 20 ++++++----- openstackclient/common/extension.py | 21 ++++++----- openstackclient/compute/v2/agent.py | 12 ++++--- openstackclient/compute/v2/flavor.py | 22 ++++++------ openstackclient/compute/v2/server.py | 36 ++++++++++--------- openstackclient/compute/v2/server_group.py | 7 +++- openstackclient/compute/v2/server_image.py | 10 +++--- openstackclient/compute/v2/service.py | 9 +++-- openstackclient/identity/v2_0/project.py | 7 +++- openstackclient/identity/v2_0/role.py | 7 +++- openstackclient/identity/v2_0/service.py | 8 +++-- openstackclient/identity/v2_0/user.py | 7 +++- openstackclient/identity/v3/domain.py | 6 +++- .../identity/v3/federation_protocol.py | 7 +++- openstackclient/identity/v3/group.py | 6 +++- .../identity/v3/identity_provider.py | 7 +++- openstackclient/identity/v3/mapping.py | 6 +++- openstackclient/identity/v3/project.py | 7 +++- openstackclient/identity/v3/role.py | 6 +++- openstackclient/identity/v3/user.py | 6 +++- openstackclient/image/v1/image.py | 23 ++++++------ openstackclient/image/v2/image.py | 32 ++++++++--------- openstackclient/network/common.py | 18 ++++++---- openstackclient/network/v2/address_scope.py | 11 ++++-- openstackclient/network/v2/port.py | 6 ++-- .../tests/compute/v2/test_service.py | 15 ++++---- 26 files changed, 206 insertions(+), 116 deletions(-) diff --git a/openstackclient/common/availability_zone.py b/openstackclient/common/availability_zone.py index af161d1f61..89d77d1585 100644 --- a/openstackclient/common/availability_zone.py +++ b/openstackclient/common/availability_zone.py @@ -14,6 +14,7 @@ """Availability Zone action implementations""" import copy +import logging from novaclient import exceptions as nova_exceptions from osc_lib.command import command @@ -23,6 +24,9 @@ from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + def _xform_common_availability_zone(az, zone_info): if hasattr(az, 'zoneState'): zone_info['zone_status'] = ('available' if az.zoneState['available'] @@ -136,11 +140,11 @@ def _get_volume_availability_zones(self, parsed_args): try: data = volume_client.availability_zones.list() except Exception as e: - self.log.debug('Volume availability zone exception: ' + str(e)) + LOG.debug('Volume availability zone exception: %s', e) if parsed_args.volume: - message = "Availability zones list not supported by " \ - "Block Storage API" - self.log.warning(message) + message = _("Availability zones list not supported by " + "Block Storage API") + LOG.warning(message) result = [] for zone in data: @@ -154,11 +158,11 @@ def _get_network_availability_zones(self, parsed_args): network_client.find_extension('Availability Zone', ignore_missing=False) except Exception as e: - self.log.debug('Network availability zone exception: ' + str(e)) + LOG.debug('Network availability zone exception: ', e) if parsed_args.network: - message = "Availability zones list not supported by " \ - "Network API" - self.log.warning(message) + message = _("Availability zones list not supported by " + "Network API") + LOG.warning(message) return [] result = [] diff --git a/openstackclient/common/extension.py b/openstackclient/common/extension.py index 327fc2238c..de48001609 100644 --- a/openstackclient/common/extension.py +++ b/openstackclient/common/extension.py @@ -16,6 +16,7 @@ """Extension action implementations""" import itertools +import logging from osc_lib.command import command from osc_lib import utils @@ -23,6 +24,9 @@ from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + class ListExtension(command.Lister): """List API extensions""" @@ -80,24 +84,25 @@ def take_action(self, parsed_args): try: data += identity_client.extensions.list() except Exception: - message = "Extensions list not supported by Identity API" - self.log.warning(message) + message = _("Extensions list not supported by Identity API") + LOG.warning(message) if parsed_args.compute or show_all: compute_client = self.app.client_manager.compute try: data += compute_client.list_extensions.show_all() except Exception: - message = "Extensions list not supported by Compute API" - self.log.warning(message) + message = _("Extensions list not supported by Compute API") + LOG.warning(message) if parsed_args.volume or show_all: volume_client = self.app.client_manager.volume try: data += volume_client.list_extensions.show_all() except Exception: - message = "Extensions list not supported by Block Storage API" - self.log.warning(message) + message = _("Extensions list not supported by " + "Block Storage API") + LOG.warning(message) # Resource classes for the above extension_tuples = ( @@ -125,7 +130,7 @@ def take_action(self, parsed_args): dict_tuples ) except Exception: - message = "Extensions list not supported by Network API" - self.log.warning(message) + message = _("Extensions list not supported by Network API") + LOG.warning(message) return (columns, extension_tuples) diff --git a/openstackclient/compute/v2/agent.py b/openstackclient/compute/v2/agent.py index ce6898c122..62be2424bf 100644 --- a/openstackclient/compute/v2/agent.py +++ b/openstackclient/compute/v2/agent.py @@ -15,6 +15,8 @@ """Agent action implementations""" +import logging + from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -23,6 +25,9 @@ from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + class CreateAgent(command.ShowOne): """Create compute agent command""" @@ -96,14 +101,13 @@ def take_action(self, parsed_args): compute_client.agents.delete(id) except Exception as e: result += 1 - self.app.log.error(_("Failed to delete agent with " - "ID '%(id)s': %(e)s") - % {'id': id, 'e': e}) + LOG.error(_("Failed to delete agent with ID '%(id)s': %(e)s"), + {'id': id, 'e': e}) if result > 0: total = len(parsed_args.id) msg = (_("%(result)s of %(total)s agents failed " - "to delete.") % {'result': result, 'total': total}) + "to delete.") % {'result': result, 'total': total}) raise exceptions.CommandError(msg) diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index ab2bc85d0f..0a0d25c2de 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -15,6 +15,8 @@ """Flavor action implementations""" +import logging + from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -25,6 +27,9 @@ from openstackclient.identity import common as identity_common +LOG = logging.getLogger(__name__) + + def _find_flavor(compute_client, flavor): try: return compute_client.flavors.get(flavor) @@ -282,8 +287,7 @@ def take_action(self, parsed_args): try: flavor.set_keys(parsed_args.property) except Exception as e: - self.app.log.error( - _("Failed to set flavor property: %s") % str(e)) + LOG.error(_("Failed to set flavor property: %s"), e) result += 1 if parsed_args.project: @@ -300,13 +304,12 @@ def take_action(self, parsed_args): compute_client.flavor_access.add_tenant_access( flavor.id, project_id) except Exception as e: - self.app.log.error(_("Failed to set flavor access to" - " project: %s") % str(e)) + LOG.error(_("Failed to set flavor access to project: %s"), e) result += 1 if result > 0: raise exceptions.CommandError(_("Command Failed: One or more of" - " the operations failed")) + " the operations failed")) class ShowFlavor(command.ShowOne): @@ -373,8 +376,7 @@ def take_action(self, parsed_args): try: flavor.unset_keys(parsed_args.property) except Exception as e: - self.app.log.error( - _("Failed to unset flavor property: %s") % str(e)) + LOG.error(_("Failed to unset flavor property: %s"), e) result += 1 if parsed_args.project: @@ -391,10 +393,10 @@ def take_action(self, parsed_args): compute_client.flavor_access.remove_tenant_access( flavor.id, project_id) except Exception as e: - self.app.log.error(_("Failed to remove flavor access from" - " project: %s") % str(e)) + LOG.error(_("Failed to remove flavor access from project: %s"), + e) result += 1 if result > 0: raise exceptions.CommandError(_("Command Failed: One or more of" - " the operations failed")) + " the operations failed")) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 42736d660f..7e4b0dc1f7 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -18,6 +18,7 @@ import argparse import getpass import io +import logging import os import sys @@ -36,6 +37,9 @@ from openstackclient.identity import common as identity_common +LOG = logging.getLogger(__name__) + + def _format_servers_list_networks(networks): """Return a formatted string of a server's networks @@ -521,8 +525,8 @@ def take_action(self, parsed_args): scheduler_hints=hints, config_drive=config_drive) - self.log.debug('boot_args: %s', boot_args) - self.log.debug('boot_kwargs: %s', boot_kwargs) + LOG.debug('boot_args: %s', boot_args) + LOG.debug('boot_kwargs: %s', boot_kwargs) # Wrap the call to catch exceptions in order to close files try: @@ -543,8 +547,8 @@ def take_action(self, parsed_args): ): sys.stdout.write('\n') else: - self.log.error(_('Error creating server: %s'), - parsed_args.server_name) + LOG.error(_('Error creating server: %s'), + parsed_args.server_name) sys.stdout.write(_('Error creating server\n')) raise SystemExit @@ -612,8 +616,8 @@ def take_action(self, parsed_args): ): sys.stdout.write('\n') else: - self.log.error(_('Error deleting server: %s'), - server_obj.id) + LOG.error(_('Error deleting server: %s'), + server_obj.id) sys.stdout.write(_('Error deleting server\n')) raise SystemExit @@ -762,7 +766,7 @@ def take_action(self, parsed_args): 'all_tenants': parsed_args.all_projects, 'user_id': user_id, } - self.log.debug('search options: %s', search_opts) + LOG.debug('search options: %s', search_opts) if parsed_args.long: columns = ( @@ -939,8 +943,8 @@ def take_action(self, parsed_args): ): sys.stdout.write(_('Complete\n')) else: - self.log.error(_('Error migrating server: %s'), - server.id) + LOG.error(_('Error migrating server: %s'), + server.id) sys.stdout.write(_('Error migrating server\n')) raise SystemExit @@ -1015,8 +1019,8 @@ def take_action(self, parsed_args): ): sys.stdout.write(_('Complete\n')) else: - self.log.error(_('Error rebooting server: %s'), - server.id) + LOG.error(_('Error rebooting server: %s'), + server.id) sys.stdout.write(_('Error rebooting server\n')) raise SystemExit @@ -1068,8 +1072,8 @@ def take_action(self, parsed_args): ): sys.stdout.write(_('Complete\n')) else: - self.log.error(_('Error rebuilding server: %s'), - server.id) + LOG.error(_('Error rebuilding server: %s'), + server.id) sys.stdout.write(_('Error rebuilding server\n')) raise SystemExit @@ -1222,8 +1226,8 @@ def take_action(self, parsed_args): ): sys.stdout.write(_('Complete\n')) else: - self.log.error(_('Error resizing server: %s'), - server.id) + LOG.error(_('Error resizing server: %s'), + server.id) sys.stdout.write(_('Error resizing server\n')) raise SystemExit elif parsed_args.confirm: @@ -1538,7 +1542,7 @@ def take_action(self, parsed_args): ip_address = _get_ip_address(server.addresses, parsed_args.address_type, ip_address_family) - self.log.debug("ssh command: %s", (cmd % (login, ip_address))) + LOG.debug("ssh command: %s", (cmd % (login, ip_address))) os.system(cmd % (login, ip_address)) diff --git a/openstackclient/compute/v2/server_group.py b/openstackclient/compute/v2/server_group.py index 955b147e71..d51b1ec23e 100644 --- a/openstackclient/compute/v2/server_group.py +++ b/openstackclient/compute/v2/server_group.py @@ -15,6 +15,8 @@ """Compute v2 Server Group action implementations""" +import logging + from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -22,6 +24,9 @@ from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + _formatters = { 'policies': utils.format_list, 'members': utils.format_list, @@ -95,7 +100,7 @@ def take_action(self, parsed_args): # Catch all exceptions in order to avoid to block the next deleting except Exception as e: result += 1 - self.app.log.error(e) + LOG.error(e) if result > 0: total = len(parsed_args.server_group) diff --git a/openstackclient/compute/v2/server_image.py b/openstackclient/compute/v2/server_image.py index 85ee7f2d18..6f5a754699 100644 --- a/openstackclient/compute/v2/server_image.py +++ b/openstackclient/compute/v2/server_image.py @@ -15,6 +15,7 @@ """Compute v2 Server action implementations""" +import logging import sys from oslo_utils import importutils @@ -26,6 +27,9 @@ from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + def _show_progress(progress): if progress: sys.stdout.write('\rProgress: %s' % progress) @@ -90,10 +94,8 @@ def take_action(self, parsed_args): ): sys.stdout.write('\n') else: - self.log.error( - _('Error creating server image: %s') % - parsed_args.server, - ) + LOG.error(_('Error creating server image: %s'), + parsed_args.server) raise exceptions.CommandError if self.app.client_manager._api_version['image'] == '1': diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index d10af2cad9..36917e2035 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.py @@ -15,6 +15,8 @@ """Service action implementations""" +import logging + from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -23,6 +25,9 @@ from openstackclient.i18n import _LE +LOG = logging.getLogger(__name__) + + class DeleteService(command.Command): """Delete service command""" @@ -171,7 +176,7 @@ def take_action(self, parsed_args): cs.disable(parsed_args.host, parsed_args.service) except Exception: status = "enabled" if enabled else "disabled" - self.log.error(_LE("Failed to set service status to %s"), status) + LOG.error(_LE("Failed to set service status to %s"), status) result += 1 force_down = None @@ -185,7 +190,7 @@ def take_action(self, parsed_args): force_down=force_down) except Exception: state = "down" if force_down else "up" - self.log.error(_LE("Failed to set service state to %s"), state) + LOG.error(_LE("Failed to set service state to %s"), state) result += 1 if result > 0: diff --git a/openstackclient/identity/v2_0/project.py b/openstackclient/identity/v2_0/project.py index c4f730e0a7..6c5db13c68 100644 --- a/openstackclient/identity/v2_0/project.py +++ b/openstackclient/identity/v2_0/project.py @@ -15,6 +15,8 @@ """Identity v2 Project action implementations""" +import logging + from keystoneauth1 import exceptions as ks_exc from osc_lib.cli import parseractions from osc_lib.command import command @@ -24,6 +26,9 @@ from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + class CreateProject(command.ShowOne): """Create new project""" @@ -87,7 +92,7 @@ def take_action(self, parsed_args): identity_client.tenants, parsed_args.name, ) - self.log.info(_('Returning existing project %s'), project.name) + LOG.info(_('Returning existing project %s'), project.name) else: raise e diff --git a/openstackclient/identity/v2_0/role.py b/openstackclient/identity/v2_0/role.py index 4c3fe6e236..6d06230c44 100644 --- a/openstackclient/identity/v2_0/role.py +++ b/openstackclient/identity/v2_0/role.py @@ -15,6 +15,8 @@ """Identity v2 Role action implementations""" +import logging + from keystoneauth1 import exceptions as ks_exc from osc_lib.command import command from osc_lib import exceptions @@ -24,6 +26,9 @@ from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + class AddRole(command.ShowOne): """Add role to project:user""" @@ -94,7 +99,7 @@ def take_action(self, parsed_args): identity_client.roles, parsed_args.role_name, ) - self.log.info(_('Returning existing role %s'), role.name) + LOG.info(_('Returning existing role %s'), role.name) else: raise e diff --git a/openstackclient/identity/v2_0/service.py b/openstackclient/identity/v2_0/service.py index 947f361cf3..413977ec15 100644 --- a/openstackclient/identity/v2_0/service.py +++ b/openstackclient/identity/v2_0/service.py @@ -16,6 +16,7 @@ """Service action implementations""" import argparse +import logging from osc_lib.command import command from osc_lib import exceptions @@ -26,6 +27,9 @@ from openstackclient.identity import common +LOG = logging.getLogger(__name__) + + class CreateService(command.ShowOne): """Create new service""" @@ -69,8 +73,8 @@ def take_action(self, parsed_args): # display deprecation message. elif type: name = type_or_name - self.log.warning(_('The argument --type is deprecated, use service' - ' create --name type instead.')) + LOG.warning(_('The argument --type is deprecated, use service' + ' create --name type instead.')) # If --name option is present the positional is handled as . # Making --type optional is new, but back-compatible elif name: diff --git a/openstackclient/identity/v2_0/user.py b/openstackclient/identity/v2_0/user.py index 3ee2a65e55..80edfab605 100644 --- a/openstackclient/identity/v2_0/user.py +++ b/openstackclient/identity/v2_0/user.py @@ -15,6 +15,8 @@ """Identity v2.0 User action implementations""" +import logging + from keystoneauth1 import exceptions as ks_exc from osc_lib.command import command from osc_lib import utils @@ -23,6 +25,9 @@ from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + class CreateUser(command.ShowOne): """Create new user""" @@ -103,7 +108,7 @@ def take_action(self, parsed_args): identity_client.users, parsed_args.name, ) - self.log.info(_('Returning existing user %s'), user.name) + LOG.info(_('Returning existing user %s'), user.name) else: raise e diff --git a/openstackclient/identity/v3/domain.py b/openstackclient/identity/v3/domain.py index 8eb7bc91de..001d520167 100644 --- a/openstackclient/identity/v3/domain.py +++ b/openstackclient/identity/v3/domain.py @@ -15,6 +15,7 @@ """Identity v3 Domain action implementations""" +import logging import sys from keystoneauth1 import exceptions as ks_exc @@ -25,6 +26,9 @@ from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + class CreateDomain(command.ShowOne): """Create new domain""" @@ -75,7 +79,7 @@ def take_action(self, parsed_args): if parsed_args.or_show: domain = utils.find_resource(identity_client.domains, parsed_args.name) - self.log.info(_('Returning existing domain %s'), domain.name) + LOG.info(_('Returning existing domain %s'), domain.name) else: raise e diff --git a/openstackclient/identity/v3/federation_protocol.py b/openstackclient/identity/v3/federation_protocol.py index 377ddcceec..094802455a 100644 --- a/openstackclient/identity/v3/federation_protocol.py +++ b/openstackclient/identity/v3/federation_protocol.py @@ -14,6 +14,8 @@ """Identity v3 Protocols actions implementations""" +import logging + from osc_lib.command import command from osc_lib import utils import six @@ -21,6 +23,9 @@ from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + class CreateProtocol(command.ShowOne): """Create new federation protocol""" @@ -145,7 +150,7 @@ def take_action(self, parsed_args): identity_client = self.app.client_manager.identity if not parsed_args.mapping: - self.app.log.error(_("No changes requested")) + LOG.error(_("No changes requested")) return protocol = identity_client.federation.protocols.update( diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py index fb0a25b973..8351fe640f 100644 --- a/openstackclient/identity/v3/group.py +++ b/openstackclient/identity/v3/group.py @@ -15,6 +15,7 @@ """Group action implementations""" +import logging import sys from keystoneauth1 import exceptions as ks_exc @@ -26,6 +27,9 @@ from openstackclient.identity import common +LOG = logging.getLogger(__name__) + + class AddUserToGroup(command.Command): """Add user to group""" @@ -161,7 +165,7 @@ def take_action(self, parsed_args): group = utils.find_resource(identity_client.groups, parsed_args.name, domain_id=domain) - self.log.info(_('Returning existing group %s'), group.name) + LOG.info(_('Returning existing group %s'), group.name) else: raise e diff --git a/openstackclient/identity/v3/identity_provider.py b/openstackclient/identity/v3/identity_provider.py index 0b530a26fc..5c638f9b6b 100644 --- a/openstackclient/identity/v3/identity_provider.py +++ b/openstackclient/identity/v3/identity_provider.py @@ -13,6 +13,8 @@ """Identity v3 IdentityProvider action implementations""" +import logging + from osc_lib.command import command from osc_lib import utils import six @@ -20,6 +22,9 @@ from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + class CreateIdentityProvider(command.ShowOne): """Create new identity provider""" @@ -169,7 +174,7 @@ def take_action(self, parsed_args): not parsed_args.remote_id and not parsed_args.remote_id_file and not parsed_args.description): - self.log.error(_('No changes requested')) + LOG.error(_('No changes requested')) return (None, None) # Always set remote_ids if either is passed in diff --git a/openstackclient/identity/v3/mapping.py b/openstackclient/identity/v3/mapping.py index 5937474e76..74ead2281a 100644 --- a/openstackclient/identity/v3/mapping.py +++ b/openstackclient/identity/v3/mapping.py @@ -16,6 +16,7 @@ """Identity v3 federation mapping action implementations""" import json +import logging from osc_lib.command import command from osc_lib import exceptions @@ -25,6 +26,9 @@ from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + class _RulesReader(object): """Helper class capable of reading rules from files""" @@ -159,7 +163,7 @@ def take_action(self, parsed_args): identity_client = self.app.client_manager.identity if not parsed_args.rules: - self.app.log.error(_("No changes requested")) + LOG.error(_("No changes requested")) return rules = self._read_rules(parsed_args.rules) diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index 6145dcf14c..4db5bef132 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -15,6 +15,8 @@ """Project action implementations""" +import logging + from keystoneauth1 import exceptions as ks_exc from osc_lib.cli import parseractions from osc_lib.command import command @@ -25,6 +27,9 @@ from openstackclient.identity import common +LOG = logging.getLogger(__name__) + + class CreateProject(command.ShowOne): """Create new project""" @@ -111,7 +116,7 @@ def take_action(self, parsed_args): project = utils.find_resource(identity_client.projects, parsed_args.name, domain_id=domain) - self.log.info(_('Returning existing project %s'), project.name) + LOG.info(_('Returning existing project %s'), project.name) else: raise e diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index 1bb2758587..965ca3f59f 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -15,6 +15,7 @@ """Identity v3 Role action implementations""" +import logging import sys from keystoneauth1 import exceptions as ks_exc @@ -26,6 +27,9 @@ from openstackclient.identity import common +LOG = logging.getLogger(__name__) + + def _add_identity_and_resource_options_to_parser(parser): domain_or_project = parser.add_mutually_exclusive_group() domain_or_project.add_argument( @@ -165,7 +169,7 @@ def take_action(self, parsed_args): if parsed_args.or_show: role = utils.find_resource(identity_client.roles, parsed_args.name) - self.log.info(_('Returning existing role %s'), role.name) + LOG.info(_('Returning existing role %s'), role.name) else: raise e diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index 39125e2cd2..96dd42449c 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -16,6 +16,7 @@ """Identity v3 User action implementations""" import copy +import logging import sys from keystoneauth1 import exceptions as ks_exc @@ -27,6 +28,9 @@ from openstackclient.identity import common +LOG = logging.getLogger(__name__) + + class CreateUser(command.ShowOne): """Create new user""" @@ -122,7 +126,7 @@ def take_action(self, parsed_args): user = utils.find_resource(identity_client.users, parsed_args.name, domain_id=domain_id) - self.log.info(_('Returning existing user %s'), user.name) + LOG.info(_('Returning existing user %s'), user.name) else: raise e diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index 1644809d76..27467b0c28 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -17,6 +17,7 @@ import argparse import io +import logging import os import sys @@ -39,6 +40,9 @@ DEFAULT_DISK_FORMAT = 'raw' +LOG = logging.getLogger(__name__) + + def _format_visibility(data): """Return a formatted visibility string @@ -189,10 +193,8 @@ def take_action(self, parsed_args): image_client = self.app.client_manager.image if getattr(parsed_args, 'owner', None) is not None: - self.log.warning(_( - 'The --owner option is deprecated, ' - 'please use --project instead.' - )) + LOG.warning(_('The --owner option is deprecated, ' + 'please use --project instead.')) # Build an attribute dict from the parsed args, only include # attributes that were actually set on the command line @@ -608,10 +610,8 @@ def take_action(self, parsed_args): image_client = self.app.client_manager.image if getattr(parsed_args, 'owner', None) is not None: - self.log.warning(_( - 'The --owner option is deprecated, ' - 'please use --project instead.' - )) + LOG.warning(_('The --owner option is deprecated, ' + 'please use --project instead.')) kwargs = {} copy_attrs = ('name', 'owner', 'min_disk', 'min_ram', 'properties', @@ -684,16 +684,15 @@ def take_action(self, parsed_args): # will do a chunked transfer kwargs["data"] = sys.stdin else: - self.log.warning(_('Use --stdin to enable read ' - 'image data from standard ' - 'input')) + LOG.warning(_('Use --stdin to enable read image ' + 'data from standard input')) if image.properties and parsed_args.properties: image.properties.update(kwargs['properties']) kwargs['properties'] = image.properties if not kwargs: - self.log.warning('no arguments specified') + LOG.warning(_('no arguments specified')) return image = image_client.images.update(image.id, **kwargs) diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 53f9530b96..82566ff379 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -16,6 +16,7 @@ """Image V2 Action Implementations""" import argparse +import logging from glanceclient.common import utils as gc_utils from osc_lib.cli import parseractions @@ -33,6 +34,9 @@ DEFAULT_DISK_FORMAT = 'raw' +LOG = logging.getLogger(__name__) + + def _format_image(image): """Format an image to make it more consistent with OSC operations. """ @@ -280,10 +284,8 @@ def take_action(self, parsed_args): project_arg = parsed_args.project if parsed_args.owner: project_arg = parsed_args.owner - self.log.warning(_( - 'The --owner option is deprecated, ' - 'please use --project instead.' - )) + LOG.warning(_('The --owner option is deprecated, ' + 'please use --project instead.')) if project_arg: kwargs['owner'] = common.find_project( identity_client, @@ -301,7 +303,7 @@ def take_action(self, parsed_args): "the same time")) if fp is None and parsed_args.file: - self.log.warning(_("Failed to get an image file.")) + LOG.warning(_("Failed to get an image file.")) return {}, {} if parsed_args.owner: @@ -384,9 +386,9 @@ def take_action(self, parsed_args): image_client.images.delete(image_obj.id) except Exception as e: del_result += 1 - self.app.log.error(_("Failed to delete image with " - "name or ID '%(image)s': %(e)s") - % {'image': image, 'e': e}) + LOG.error(_("Failed to delete image with name or " + "ID '%(image)s': %(e)s"), + {'image': image, 'e': e}) total = len(parsed_args.images) if (del_result > 0): @@ -806,10 +808,8 @@ def take_action(self, parsed_args): project_arg = parsed_args.project if parsed_args.owner: project_arg = parsed_args.owner - self.log.warning(_( - 'The --owner option is deprecated, ' - 'please use --project instead.' - )) + LOG.warning(_('The --owner option is deprecated, ' + 'please use --project instead.')) if project_arg: kwargs['owner'] = common.find_project( identity_client, @@ -908,8 +908,8 @@ def take_action(self, parsed_args): try: image_client.image_tags.delete(image.id, k) except Exception: - self.log.error(_("tag unset failed," - " '%s' is a nonexistent tag ") % k) + LOG.error(_("tag unset failed, '%s' is a " + "nonexistent tag "), k) tagret += 1 if parsed_args.properties: @@ -917,8 +917,8 @@ def take_action(self, parsed_args): try: assert(k in image.keys()) except AssertionError: - self.log.error(_("property unset failed," - " '%s' is a nonexistent property ") % k) + LOG.error(_("property unset failed, '%s' is a " + "nonexistent property "), k) propret += 1 image_client.images.update( image.id, diff --git a/openstackclient/network/common.py b/openstackclient/network/common.py index 16cc7379ba..f62840fc04 100644 --- a/openstackclient/network/common.py +++ b/openstackclient/network/common.py @@ -12,6 +12,7 @@ # import abc +import logging from osc_lib.command import command from osc_lib import exceptions @@ -20,6 +21,9 @@ from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + @six.add_metaclass(abc.ABCMeta) class NetworkAndComputeCommand(command.Command): """Network and Compute Command @@ -39,10 +43,10 @@ def take_action(self, parsed_args): parsed_args) def get_parser(self, prog_name): - self.log.debug('get_parser(%s)', prog_name) + LOG.debug('get_parser(%s)', prog_name) parser = super(NetworkAndComputeCommand, self).get_parser(prog_name) parser = self.update_parser_common(parser) - self.log.debug('common parser: %s', parser) + LOG.debug('common parser: %s', parser) if self.app.client_manager.is_network_endpoint_enabled(): return self.update_parser_network(parser) else: @@ -102,7 +106,7 @@ def take_action(self, parsed_args): "name_or_id": r, "e": e, } - self.app.log.error(msg) + LOG.error(msg) ret += 1 if ret: @@ -134,10 +138,10 @@ def take_action(self, parsed_args): parsed_args) def get_parser(self, prog_name): - self.log.debug('get_parser(%s)', prog_name) + LOG.debug('get_parser(%s)', prog_name) parser = super(NetworkAndComputeLister, self).get_parser(prog_name) parser = self.update_parser_common(parser) - self.log.debug('common parser: %s', parser) + LOG.debug('common parser: %s', parser) if self.app.client_manager.is_network_endpoint_enabled(): return self.update_parser_network(parser) else: @@ -185,10 +189,10 @@ def take_action(self, parsed_args): parsed_args) def get_parser(self, prog_name): - self.log.debug('get_parser(%s)', prog_name) + LOG.debug('get_parser(%s)', prog_name) parser = super(NetworkAndComputeShowOne, self).get_parser(prog_name) parser = self.update_parser_common(parser) - self.log.debug('common parser: %s', parser) + LOG.debug('common parser: %s', parser) if self.app.client_manager.is_network_endpoint_enabled(): return self.update_parser_network(parser) else: diff --git a/openstackclient/network/v2/address_scope.py b/openstackclient/network/v2/address_scope.py index bc1a96c326..6cd13f8ce7 100644 --- a/openstackclient/network/v2/address_scope.py +++ b/openstackclient/network/v2/address_scope.py @@ -13,6 +13,8 @@ """Address scope action implementations""" +import logging + from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -21,6 +23,9 @@ from openstackclient.identity import common as identity_common +LOG = logging.getLogger(__name__) + + def _get_columns(item): columns = list(item.keys()) if 'tenant_id' in columns: @@ -122,9 +127,9 @@ def take_action(self, parsed_args): client.delete_address_scope(obj) except Exception as e: result += 1 - self.app.log.error(_("Failed to delete address scope with " - "name or ID '%(scope)s': %(e)s") - % {'scope': scope, 'e': e}) + LOG.error(_("Failed to delete address scope with " + "name or ID '%(scope)s': %(e)s"), + {'scope': scope, 'e': e}) if result > 0: total = len(parsed_args.address_scope) diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 1c5db706e3..57461f8949 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -293,9 +293,9 @@ def take_action(self, parsed_args): client.delete_port(obj) except Exception as e: result += 1 - self.app.log.error(_("Failed to delete port with " - "name or ID '%(port)s': %(e)s") - % {'port': port, 'e': e}) + LOG.error(_("Failed to delete port with " + "name or ID '%(port)s': %(e)s"), + {'port': port, 'e': e}) if result > 0: total = len(parsed_args.port) diff --git a/openstackclient/tests/compute/v2/test_service.py b/openstackclient/tests/compute/v2/test_service.py index b360c9dce4..3afe964f72 100644 --- a/openstackclient/tests/compute/v2/test_service.py +++ b/openstackclient/tests/compute/v2/test_service.py @@ -330,12 +330,9 @@ def test_service_set_enable_and_state_down_with_exception(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - with mock.patch.object(self.cmd.log, 'error') as mock_log: - with mock.patch.object(self.service_mock, 'enable', - side_effect=Exception()): - self.assertRaises(exceptions.CommandError, - self.cmd.take_action, parsed_args) - mock_log.assert_called_once_with( - "Failed to set service status to %s", "enabled") - self.service_mock.force_down.assert_called_once_with( - self.service.host, self.service.binary, force_down=True) + with mock.patch.object(self.service_mock, 'enable', + side_effect=Exception()): + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, parsed_args) + self.service_mock.force_down.assert_called_once_with( + self.service.host, self.service.binary, force_down=True) From 4e62e1e2e18cb93ba0f88bff8727182b1002de4b Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Thu, 16 Jun 2016 14:25:33 -0400 Subject: [PATCH 1015/3095] support multi-delete for volume-type Added the ability to delete multiple volume types at once. Note there are no unit tests exist for v1 volume-types, so instead a functional test was created. Partial-Bug: #1592906 Change-Id: I99f3f22901ab35252b91a3072b14de7d19cb17ca --- doc/source/command-objects/volume-type.rst | 6 ++-- .../tests/volume/v1/test_volume_type.py | 13 +++++++ .../tests/volume/v2/test_volume_type.py | 13 +++++++ openstackclient/tests/volume/v2/test_type.py | 4 +-- openstackclient/volume/v1/volume_type.py | 35 +++++++++++++++---- openstackclient/volume/v2/volume_type.py | 29 +++++++++++---- .../notes/bug-1592906-e69b37377278d9c2.yaml | 5 +++ 7 files changed, 88 insertions(+), 17 deletions(-) create mode 100644 releasenotes/notes/bug-1592906-e69b37377278d9c2.yaml diff --git a/doc/source/command-objects/volume-type.rst b/doc/source/command-objects/volume-type.rst index 50acf9fa65..dfc169cd14 100644 --- a/doc/source/command-objects/volume-type.rst +++ b/doc/source/command-objects/volume-type.rst @@ -48,18 +48,18 @@ Create new volume type volume type delete ------------------ -Delete volume type +Delete volume type(s) .. program:: volume type delete .. code:: bash os volume type delete - + [ ...] .. _volume_type_delete-volume-type: .. describe:: - Volume type to delete (name or ID) + Volume type(s) to delete (name or ID) volume type list ---------------- diff --git a/functional/tests/volume/v1/test_volume_type.py b/functional/tests/volume/v1/test_volume_type.py index 824b20d7c4..5f1f957e8c 100644 --- a/functional/tests/volume/v1/test_volume_type.py +++ b/functional/tests/volume/v1/test_volume_type.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import time import uuid from functional.tests.volume.v1 import common @@ -59,3 +60,15 @@ def test_volume_type_set_unset_properties(self): self.assertEqual("", raw_output) raw_output = self.openstack('volume type show ' + self.NAME + opts) self.assertEqual("c='d'\n", raw_output) + + def test_multi_delete(self): + vol_type1 = uuid.uuid4().hex + vol_type2 = uuid.uuid4().hex + self.openstack('volume type create ' + vol_type1) + time.sleep(5) + self.openstack('volume type create ' + vol_type2) + time.sleep(5) + cmd = 'volume type delete %s %s' % (vol_type1, vol_type2) + time.sleep(5) + raw_output = self.openstack(cmd) + self.assertOutput('', raw_output) diff --git a/functional/tests/volume/v2/test_volume_type.py b/functional/tests/volume/v2/test_volume_type.py index 4c315334c8..114e429802 100644 --- a/functional/tests/volume/v2/test_volume_type.py +++ b/functional/tests/volume/v2/test_volume_type.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import time import uuid from functional.tests.volume.v2 import common @@ -69,3 +70,15 @@ def test_volume_type_set_unset_project(self): raw_output = self.openstack( 'volume type unset --project admin ' + self.NAME) self.assertEqual("", raw_output) + + def test_multi_delete(self): + vol_type1 = uuid.uuid4().hex + vol_type2 = uuid.uuid4().hex + self.openstack('volume type create ' + vol_type1) + time.sleep(5) + self.openstack('volume type create ' + vol_type2) + time.sleep(5) + cmd = 'volume type delete %s %s' % (vol_type1, vol_type2) + time.sleep(5) + raw_output = self.openstack(cmd) + self.assertOutput('', raw_output) diff --git a/openstackclient/tests/volume/v2/test_type.py b/openstackclient/tests/volume/v2/test_type.py index 7eea1c3c45..174f33f2fb 100644 --- a/openstackclient/tests/volume/v2/test_type.py +++ b/openstackclient/tests/volume/v2/test_type.py @@ -128,13 +128,13 @@ def test_type_delete(self): self.volume_type.id ] verifylist = [ - ("volume_type", self.volume_type.id) + ("volume_types", [self.volume_type.id]) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.types_mock.delete.assert_called_with(self.volume_type.id) + self.types_mock.delete.assert_called_with(self.volume_type) self.assertIsNone(result) diff --git a/openstackclient/volume/v1/volume_type.py b/openstackclient/volume/v1/volume_type.py index 289966e52e..3fe4fa05f1 100644 --- a/openstackclient/volume/v1/volume_type.py +++ b/openstackclient/volume/v1/volume_type.py @@ -15,14 +15,20 @@ """Volume v1 Type action implementations""" +import logging + from osc_lib.cli import parseractions 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 CreateVolumeType(command.ShowOne): """Create new volume type""" @@ -56,22 +62,39 @@ def take_action(self, parsed_args): class DeleteVolumeType(command.Command): - """Delete volume type""" + """Delete volume type(s)""" def get_parser(self, prog_name): parser = super(DeleteVolumeType, self).get_parser(prog_name) parser.add_argument( - 'volume_type', + 'volume_types', metavar='', - help=_('Volume type to delete (name or ID)'), + nargs='+', + help=_('Volume type(s) to delete (name or ID)'), ) return parser def take_action(self, parsed_args): volume_client = self.app.client_manager.volume - volume_type_id = utils.find_resource( - volume_client.volume_types, parsed_args.volume_type).id - volume_client.volume_types.delete(volume_type_id) + result = 0 + + for volume_type in parsed_args.volume_types: + try: + vol_type = utils.find_resource(volume_client.volume_types, + volume_type) + + volume_client.volume_types.delete(vol_type) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete volume type with " + "name or ID '%(volume_type)s': %(e)s") + % {'volume_type': volume_type, 'e': e}) + + if result > 0: + total = len(parsed_args.volume_types) + msg = (_("%(result)s of %(total)s volume types failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListVolumeType(command.Lister): diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index 5e9faa1ded..87f4c5472a 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -92,22 +92,39 @@ def take_action(self, parsed_args): class DeleteVolumeType(command.Command): - """Delete volume type""" + """Delete volume type(s)""" def get_parser(self, prog_name): parser = super(DeleteVolumeType, self).get_parser(prog_name) parser.add_argument( - "volume_type", + "volume_types", metavar="", - help=_("Volume type to delete (name or ID)") + nargs="+", + help=_("Volume type(s) to delete (name or ID)") ) return parser def take_action(self, parsed_args): volume_client = self.app.client_manager.volume - volume_type = utils.find_resource( - volume_client.volume_types, parsed_args.volume_type) - volume_client.volume_types.delete(volume_type.id) + result = 0 + + for volume_type in parsed_args.volume_types: + try: + vol_type = utils.find_resource(volume_client.volume_types, + volume_type) + + volume_client.volume_types.delete(vol_type) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete volume type with " + "name or ID '%(volume_type)s': %(e)s") + % {'volume_type': volume_type, 'e': e}) + + if result > 0: + total = len(parsed_args.volume_types) + msg = (_("%(result)s of %(total)s volume types failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListVolumeType(command.Lister): diff --git a/releasenotes/notes/bug-1592906-e69b37377278d9c2.yaml b/releasenotes/notes/bug-1592906-e69b37377278d9c2.yaml new file mode 100644 index 0000000000..fee971d6dd --- /dev/null +++ b/releasenotes/notes/bug-1592906-e69b37377278d9c2.yaml @@ -0,0 +1,5 @@ +--- +features: + - Support bulk deletion for ``volume type delete``. + [Bug `1592906 `_] + From 5cc62d90b03714d65af04c73e01a89ac8d96c895 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Thu, 16 Jun 2016 16:06:35 -0500 Subject: [PATCH 1016/3095] Support JSON data for port binding profile Update the "--binding-profile" option on the "port create" and "port set" commands to support both = and JSON input for the port custom binding profile data. The JSON input is sometimes needed to maintain the value type (e.g. integer) for more advanced data. The port custom binding profile data is unique across neutron so a custom argparse.Action class was created instead of writting a generic class in osc-lib. Change-Id: I82ac6d4f95afdc866f5282fc00d390f850f54d21 Implements: blueprint neutron-client --- doc/source/command-objects/port.rst | 6 +- openstackclient/network/v2/port.py | 39 ++++++-- openstackclient/tests/network/v2/test_port.py | 95 +++++++++++++++++++ .../bp-neutron-client-a0552f8ca909b665.yaml | 4 + 4 files changed, 136 insertions(+), 8 deletions(-) diff --git a/doc/source/command-objects/port.rst b/doc/source/command-objects/port.rst index e4cf2cd212..880161f971 100644 --- a/doc/source/command-objects/port.rst +++ b/doc/source/command-objects/port.rst @@ -54,7 +54,8 @@ Create new port .. option:: --binding-profile - Custom data to be passed as binding:profile: = + Custom data to be passed as binding:profile. Data may + be passed as = or JSON. (repeat option to set multiple binding:profile data) .. option:: --host @@ -162,7 +163,8 @@ Set port properties .. option:: --binding-profile - Custom data to be passed as binding:profile: = + Custom data to be passed as binding:profile. Data may + be passed as = or JSON. (repeat option to set multiple binding:profile data) .. option:: --no-binding-profile diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 1c5db706e3..a25bdf8d71 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -14,6 +14,7 @@ """Port action implementations""" import argparse +import json import logging from osc_lib.cli import parseractions @@ -63,6 +64,32 @@ def _get_columns(item): return tuple(sorted(columns)) +class JSONKeyValueAction(argparse.Action): + """A custom action to parse arguments as JSON or key=value pairs + + Ensures that ``dest`` is a dict + """ + + def __call__(self, parser, namespace, values, option_string=None): + + # Make sure we have an empty dict rather than None + if getattr(namespace, self.dest, None) is None: + setattr(namespace, self.dest, {}) + + # Try to load JSON first before falling back to =. + current_dest = getattr(namespace, self.dest) + try: + current_dest.update(json.loads(values)) + except ValueError as e: + if '=' in values: + current_dest.update([values.split('=', 1)]) + else: + msg = _("Expected '=' or JSON data for option " + "%(option)s, but encountered JSON parsing error: " + "%(error)s") % {"option": option_string, "error": e} + raise argparse.ArgumentTypeError(msg) + + def _get_attrs(client_manager, parsed_args): attrs = {} @@ -219,9 +246,9 @@ def get_parser(self, prog_name): parser.add_argument( '--binding-profile', metavar='', - action=parseractions.KeyValueAction, - help=_("Custom data to be passed as binding:profile: " - "= " + action=JSONKeyValueAction, + help=_("Custom data to be passed as binding:profile. Data may " + "be passed as = or JSON. " "(repeat option to set multiple binding:profile data)") ) admin_group = parser.add_mutually_exclusive_group() @@ -390,9 +417,9 @@ def get_parser(self, prog_name): binding_profile.add_argument( '--binding-profile', metavar='', - action=parseractions.KeyValueAction, - help=_("Custom data to be passed as binding:profile: " - "= " + action=JSONKeyValueAction, + help=_("Custom data to be passed as binding:profile. Data may " + "be passed as = or JSON. " "(repeat option to set multiple binding:profile data)") ) binding_profile.add_argument( diff --git a/openstackclient/tests/network/v2/test_port.py b/openstackclient/tests/network/v2/test_port.py index 779dca0519..a998585e4b 100644 --- a/openstackclient/tests/network/v2/test_port.py +++ b/openstackclient/tests/network/v2/test_port.py @@ -11,6 +11,7 @@ # under the License. # +import argparse import mock from mock import call @@ -174,6 +175,58 @@ def test_create_full_options(self): self.assertEqual(ref_columns, columns) self.assertEqual(ref_data, data) + def test_create_invalid_json_binding_profile(self): + arglist = [ + '--network', self._port.network_id, + '--binding-profile', '{"parent_name":"fake_parent"', + 'test-port', + ] + self.assertRaises(argparse.ArgumentTypeError, + self.check_parser, + self.cmd, + arglist, + None) + + def test_create_invalid_key_value_binding_profile(self): + arglist = [ + '--network', self._port.network_id, + '--binding-profile', 'key', + 'test-port', + ] + self.assertRaises(argparse.ArgumentTypeError, + self.check_parser, + self.cmd, + arglist, + None) + + def test_create_json_binding_profile(self): + arglist = [ + '--network', self._port.network_id, + '--binding-profile', '{"parent_name":"fake_parent"}', + '--binding-profile', '{"tag":42}', + 'test-port', + ] + verifylist = [ + ('network', self._port.network_id,), + ('enable', True), + ('binding_profile', {'parent_name': 'fake_parent', 'tag': 42}), + ('name', 'test-port'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_port.assert_called_once_with(**{ + 'admin_state_up': True, + 'network_id': self._port.network_id, + 'binding:profile': {'parent_name': 'fake_parent', 'tag': 42}, + 'name': 'test-port', + }) + + ref_columns, ref_data = self._get_common_cols_data(self._port) + self.assertEqual(ref_columns, columns) + self.assertEqual(ref_data, data) + class TestDeletePort(TestPort): @@ -442,6 +495,48 @@ def test_set_nothing(self): self.network.update_port.assert_called_once_with(self._port, **attrs) self.assertIsNone(result) + def test_set_invalid_json_binding_profile(self): + arglist = [ + '--binding-profile', '{"parent_name"}', + 'test-port', + ] + self.assertRaises(argparse.ArgumentTypeError, + self.check_parser, + self.cmd, + arglist, + None) + + def test_set_invalid_key_value_binding_profile(self): + arglist = [ + '--binding-profile', 'key', + 'test-port', + ] + self.assertRaises(argparse.ArgumentTypeError, + self.check_parser, + self.cmd, + arglist, + None) + + def test_set_mixed_binding_profile(self): + arglist = [ + '--binding-profile', 'foo=bar', + '--binding-profile', '{"foo2": "bar2"}', + self._port.name, + ] + verifylist = [ + ('binding_profile', {'foo': 'bar', 'foo2': 'bar2'}), + ('port', self._port.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = { + 'binding:profile': {'foo': 'bar', 'foo2': 'bar2'}, + } + self.network.update_port.assert_called_once_with(self._port, **attrs) + self.assertIsNone(result) + class TestShowPort(TestPort): diff --git a/releasenotes/notes/bp-neutron-client-a0552f8ca909b665.yaml b/releasenotes/notes/bp-neutron-client-a0552f8ca909b665.yaml index f8de4ee6ae..8402e2c9c3 100644 --- a/releasenotes/notes/bp-neutron-client-a0552f8ca909b665.yaml +++ b/releasenotes/notes/bp-neutron-client-a0552f8ca909b665.yaml @@ -1,5 +1,9 @@ --- features: + - Update ``--binding-profile`` option on the ``port create`` and + ``port set`` commands to support JSON input for more advanced + binding profile data. + [Blueprint :oscbp:`neutron-client`] - Add ``geneve`` choice to the ``network create`` command ``--provider-network-type`` option. [Blueprint :oscbp:`neutron-client`] From cb28fb55884a9be7cd70c37343181116cf000a42 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Mon, 20 Jun 2016 17:12:44 -0400 Subject: [PATCH 1017/3095] use env vars to specify OS_IDENTITY_API_VERSION If the default identity API version were to change in devstack, the v2.0 tests would fail today, resulting in a broken OSC gate. Change-Id: Id634ea7e0fab9f3772383b5512ccac19f5119ac0 --- .../tests/identity/v2/{test_identity.py => common.py} | 9 +++++++-- functional/tests/identity/v2/test_catalog.py | 4 ++-- functional/tests/identity/v2/test_ec2_credentials.py | 4 ++-- functional/tests/identity/v2/test_endpoint.py | 4 ++-- functional/tests/identity/v2/test_project.py | 6 +++--- functional/tests/identity/v2/test_role.py | 8 ++++---- functional/tests/identity/v2/test_service.py | 6 +++--- functional/tests/identity/v2/test_token.py | 4 ++-- functional/tests/identity/v2/test_user.py | 6 +++--- .../tests/identity/v3/{test_identity.py => common.py} | 6 +----- functional/tests/identity/v3/test_catalog.py | 4 ++-- functional/tests/identity/v3/test_domain.py | 6 +++--- functional/tests/identity/v3/test_endpoint.py | 4 ++-- functional/tests/identity/v3/test_group.py | 8 ++++---- functional/tests/identity/v3/test_idp.py | 4 ++-- functional/tests/identity/v3/test_project.py | 8 ++++---- functional/tests/identity/v3/test_region.py | 4 ++-- functional/tests/identity/v3/test_role.py | 8 ++++---- functional/tests/identity/v3/test_service.py | 6 +++--- functional/tests/identity/v3/test_service_provider.py | 4 ++-- functional/tests/identity/v3/test_token.py | 4 ++-- functional/tests/identity/v3/test_user.py | 6 +++--- 22 files changed, 62 insertions(+), 61 deletions(-) rename functional/tests/identity/v2/{test_identity.py => common.py} (96%) rename functional/tests/identity/v3/{test_identity.py => common.py} (98%) diff --git a/functional/tests/identity/v2/test_identity.py b/functional/tests/identity/v2/common.py similarity index 96% rename from functional/tests/identity/v2/test_identity.py rename to functional/tests/identity/v2/common.py index 9adbe49f4f..9d6a7bb5b0 100644 --- a/functional/tests/identity/v2/test_identity.py +++ b/functional/tests/identity/v2/common.py @@ -10,6 +10,8 @@ # License for the specific language governing permissions and limitations # under the License. +import os + from tempest.lib.common.utils import data_utils from functional.common import test @@ -39,8 +41,11 @@ class IdentityTests(test.TestCase): @classmethod def setUpClass(cls): - if hasattr(super(IdentityTests, cls), 'setUpClass'): - super(IdentityTests, cls).setUpClass() + # prepare v2 env + os.environ['OS_IDENTITY_API_VERSION'] = '2.0' + auth_url = os.environ.get('OS_AUTH_URL') + auth_url = auth_url.replace('v3', 'v2.0') + os.environ['OS_AUTH_URL'] = auth_url # create dummy project cls.project_name = data_utils.rand_name('TestProject') diff --git a/functional/tests/identity/v2/test_catalog.py b/functional/tests/identity/v2/test_catalog.py index 3a1f7e112a..b6291e05d1 100644 --- a/functional/tests/identity/v2/test_catalog.py +++ b/functional/tests/identity/v2/test_catalog.py @@ -10,10 +10,10 @@ # License for the specific language governing permissions and limitations # under the License. -from functional.tests.identity.v2 import test_identity +from functional.tests.identity.v2 import common -class CatalogTests(test_identity.IdentityTests): +class CatalogTests(common.IdentityTests): def test_catalog_list(self): raw_output = self.openstack('catalog list') diff --git a/functional/tests/identity/v2/test_ec2_credentials.py b/functional/tests/identity/v2/test_ec2_credentials.py index 86702c0c6f..319bd11a64 100644 --- a/functional/tests/identity/v2/test_ec2_credentials.py +++ b/functional/tests/identity/v2/test_ec2_credentials.py @@ -10,10 +10,10 @@ # License for the specific language governing permissions and limitations # under the License. -from functional.tests.identity.v2 import test_identity +from functional.tests.identity.v2 import common -class EC2CredentialsTests(test_identity.IdentityTests): +class EC2CredentialsTests(common.IdentityTests): def test_ec2_credentials_create(self): self._create_dummy_ec2_credentials() diff --git a/functional/tests/identity/v2/test_endpoint.py b/functional/tests/identity/v2/test_endpoint.py index 8064365e01..0682e6b476 100644 --- a/functional/tests/identity/v2/test_endpoint.py +++ b/functional/tests/identity/v2/test_endpoint.py @@ -10,10 +10,10 @@ # License for the specific language governing permissions and limitations # under the License. -from functional.tests.identity.v2 import test_identity +from functional.tests.identity.v2 import common -class EndpointTests(test_identity.IdentityTests): +class EndpointTests(common.IdentityTests): def test_endpoint_create(self): self._create_dummy_endpoint() diff --git a/functional/tests/identity/v2/test_project.py b/functional/tests/identity/v2/test_project.py index e9580ecfb6..7fb1a98de0 100644 --- a/functional/tests/identity/v2/test_project.py +++ b/functional/tests/identity/v2/test_project.py @@ -12,10 +12,10 @@ from tempest.lib.common.utils import data_utils -from functional.tests.identity.v2 import test_identity +from functional.tests.identity.v2 import common -class ProjectTests(test_identity.IdentityTests): +class ProjectTests(common.IdentityTests): def test_project_create(self): project_name = data_utils.rand_name('TestProject') @@ -49,7 +49,7 @@ def test_project_delete(self): def test_project_list(self): raw_output = self.openstack('project list') items = self.parse_listing(raw_output) - self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS) + self.assert_table_structure(items, common.BASIC_LIST_HEADERS) def test_project_set(self): project_name = self._create_dummy_project() diff --git a/functional/tests/identity/v2/test_role.py b/functional/tests/identity/v2/test_role.py index 9ee60069bc..0f8d5ed4b8 100644 --- a/functional/tests/identity/v2/test_role.py +++ b/functional/tests/identity/v2/test_role.py @@ -10,10 +10,10 @@ # License for the specific language governing permissions and limitations # under the License. -from functional.tests.identity.v2 import test_identity +from functional.tests.identity.v2 import common -class RoleTests(test_identity.IdentityTests): +class RoleTests(common.IdentityTests): def test_role_create(self): self._create_dummy_role() @@ -27,7 +27,7 @@ def test_role_list(self): self._create_dummy_role() raw_output = self.openstack('role list') items = self.parse_listing(raw_output) - self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS) + self.assert_table_structure(items, common.BASIC_LIST_HEADERS) def test_role_list_with_user_project(self): project_name = self._create_dummy_project() @@ -58,7 +58,7 @@ def test_role_list_with_user_project(self): '' % {'project': project_name, 'user': username}) items = self.parse_listing(raw_output) - self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS) + self.assert_table_structure(items, common.BASIC_LIST_HEADERS) self.assertEqual(1, len(items)) def test_role_show(self): diff --git a/functional/tests/identity/v2/test_service.py b/functional/tests/identity/v2/test_service.py index bd982be1bd..219ed33f07 100644 --- a/functional/tests/identity/v2/test_service.py +++ b/functional/tests/identity/v2/test_service.py @@ -10,10 +10,10 @@ # License for the specific language governing permissions and limitations # under the License. -from functional.tests.identity.v2 import test_identity +from functional.tests.identity.v2 import common -class ServiceTests(test_identity.IdentityTests): +class ServiceTests(common.IdentityTests): def test_service_create(self): self._create_dummy_service() @@ -27,7 +27,7 @@ def test_service_list(self): self._create_dummy_service() raw_output = self.openstack('service list') items = self.parse_listing(raw_output) - self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS) + self.assert_table_structure(items, common.BASIC_LIST_HEADERS) def test_service_show(self): service_name = self._create_dummy_service() diff --git a/functional/tests/identity/v2/test_token.py b/functional/tests/identity/v2/test_token.py index bac2b0ac9c..ca9b7d683b 100644 --- a/functional/tests/identity/v2/test_token.py +++ b/functional/tests/identity/v2/test_token.py @@ -10,10 +10,10 @@ # License for the specific language governing permissions and limitations # under the License. -from functional.tests.identity.v2 import test_identity +from functional.tests.identity.v2 import common -class TokenTests(test_identity.IdentityTests): +class TokenTests(common.IdentityTests): def test_token_issue(self): self._create_dummy_token() diff --git a/functional/tests/identity/v2/test_user.py b/functional/tests/identity/v2/test_user.py index 34cabf7bf4..ef4deface2 100644 --- a/functional/tests/identity/v2/test_user.py +++ b/functional/tests/identity/v2/test_user.py @@ -13,10 +13,10 @@ from tempest.lib.common.utils import data_utils from tempest.lib import exceptions -from functional.tests.identity.v2 import test_identity +from functional.tests.identity.v2 import common -class UserTests(test_identity.IdentityTests): +class UserTests(common.IdentityTests): def test_user_create(self): self._create_dummy_user() @@ -29,7 +29,7 @@ def test_user_delete(self): def test_user_list(self): raw_output = self.openstack('user list') items = self.parse_listing(raw_output) - self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS) + self.assert_table_structure(items, common.BASIC_LIST_HEADERS) def test_user_set(self): username = self._create_dummy_user() diff --git a/functional/tests/identity/v3/test_identity.py b/functional/tests/identity/v3/common.py similarity index 98% rename from functional/tests/identity/v3/test_identity.py rename to functional/tests/identity/v3/common.py index 3f9887428b..a7cddfc2c9 100644 --- a/functional/tests/identity/v3/test_identity.py +++ b/functional/tests/identity/v3/common.py @@ -51,15 +51,11 @@ class IdentityTests(test.TestCase): @classmethod def setUpClass(cls): - if hasattr(super(IdentityTests, cls), 'setUpClass'): - super(IdentityTests, cls).setUpClass() - # prepare v3 env + os.environ['OS_IDENTITY_API_VERSION'] = '3' auth_url = os.environ.get('OS_AUTH_URL') auth_url = auth_url.replace('v2.0', 'v3') os.environ['OS_AUTH_URL'] = auth_url - os.environ['OS_IDENTITY_API_VERSION'] = '3' - os.environ['OS_AUTH_TYPE'] = 'v3password' # create dummy domain cls.domain_name = data_utils.rand_name('TestDomain') diff --git a/functional/tests/identity/v3/test_catalog.py b/functional/tests/identity/v3/test_catalog.py index 6836452954..e33876b083 100644 --- a/functional/tests/identity/v3/test_catalog.py +++ b/functional/tests/identity/v3/test_catalog.py @@ -10,10 +10,10 @@ # License for the specific language governing permissions and limitations # under the License. -from functional.tests.identity.v3 import test_identity +from functional.tests.identity.v3 import common -class CatalogTests(test_identity.IdentityTests): +class CatalogTests(common.IdentityTests): def test_catalog_list(self): raw_output = self.openstack('catalog list') diff --git a/functional/tests/identity/v3/test_domain.py b/functional/tests/identity/v3/test_domain.py index 0708e4200d..305ed58da5 100644 --- a/functional/tests/identity/v3/test_domain.py +++ b/functional/tests/identity/v3/test_domain.py @@ -13,10 +13,10 @@ from tempest.lib.common.utils import data_utils from tempest.lib import exceptions -from functional.tests.identity.v3 import test_identity +from functional.tests.identity.v3 import common -class DomainTests(test_identity.IdentityTests): +class DomainTests(common.IdentityTests): def test_domain_create(self): domain_name = data_utils.rand_name('TestDomain') @@ -33,7 +33,7 @@ def test_domain_list(self): self._create_dummy_domain() raw_output = self.openstack('domain list') items = self.parse_listing(raw_output) - self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS) + self.assert_table_structure(items, common.BASIC_LIST_HEADERS) def test_domain_delete(self): domain_name = self._create_dummy_domain(add_clean_up=False) diff --git a/functional/tests/identity/v3/test_endpoint.py b/functional/tests/identity/v3/test_endpoint.py index ec11deab30..162c9e58cd 100644 --- a/functional/tests/identity/v3/test_endpoint.py +++ b/functional/tests/identity/v3/test_endpoint.py @@ -12,10 +12,10 @@ from tempest.lib.common.utils import data_utils -from functional.tests.identity.v3 import test_identity +from functional.tests.identity.v3 import common -class EndpointTests(test_identity.IdentityTests): +class EndpointTests(common.IdentityTests): def test_endpoint_create(self): self._create_dummy_endpoint(interface='public') diff --git a/functional/tests/identity/v3/test_group.py b/functional/tests/identity/v3/test_group.py index 3f58864dc2..350756989f 100644 --- a/functional/tests/identity/v3/test_group.py +++ b/functional/tests/identity/v3/test_group.py @@ -12,10 +12,10 @@ from tempest.lib.common.utils import data_utils -from functional.tests.identity.v3 import test_identity +from functional.tests.identity.v3 import common -class GroupTests(test_identity.IdentityTests): +class GroupTests(common.IdentityTests): def test_group_create(self): self._create_dummy_group() @@ -24,7 +24,7 @@ def test_group_list(self): group_name = self._create_dummy_group() raw_output = self.openstack('group list') items = self.parse_listing(raw_output) - self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS) + self.assert_table_structure(items, common.BASIC_LIST_HEADERS) self.assertIn(group_name, raw_output) def test_group_list_with_domain(self): @@ -32,7 +32,7 @@ def test_group_list_with_domain(self): raw_output = self.openstack( 'group list --domain %s' % self.domain_name) items = self.parse_listing(raw_output) - self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS) + self.assert_table_structure(items, common.BASIC_LIST_HEADERS) self.assertIn(group_name, raw_output) def test_group_delete(self): diff --git a/functional/tests/identity/v3/test_idp.py b/functional/tests/identity/v3/test_idp.py index 08f660f66d..8d86471245 100644 --- a/functional/tests/identity/v3/test_idp.py +++ b/functional/tests/identity/v3/test_idp.py @@ -10,11 +10,11 @@ # License for the specific language governing permissions and limitations # under the License. -from functional.tests.identity.v3 import test_identity +from functional.tests.identity.v3 import common from tempest.lib.common.utils import data_utils -class IdentityProviderTests(test_identity.IdentityTests): +class IdentityProviderTests(common.IdentityTests): # Introduce functional test case for command 'Identity Provider' def test_idp_create(self): diff --git a/functional/tests/identity/v3/test_project.py b/functional/tests/identity/v3/test_project.py index d060c7b1a1..a27c58fb49 100644 --- a/functional/tests/identity/v3/test_project.py +++ b/functional/tests/identity/v3/test_project.py @@ -12,10 +12,10 @@ from tempest.lib.common.utils import data_utils -from functional.tests.identity.v3 import test_identity +from functional.tests.identity.v3 import common -class ProjectTests(test_identity.IdentityTests): +class ProjectTests(common.IdentityTests): def test_project_create(self): project_name = data_utils.rand_name('TestProject') @@ -57,14 +57,14 @@ def test_project_delete(self): def test_project_list(self): raw_output = self.openstack('project list') items = self.parse_listing(raw_output) - self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS) + self.assert_table_structure(items, common.BASIC_LIST_HEADERS) def test_project_list_with_domain(self): project_name = self._create_dummy_project() raw_output = self.openstack( 'project list --domain %s' % self.domain_name) items = self.parse_listing(raw_output) - self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS) + self.assert_table_structure(items, common.BASIC_LIST_HEADERS) self.assertIn(project_name, raw_output) self.assertTrue(len(items) > 0) diff --git a/functional/tests/identity/v3/test_region.py b/functional/tests/identity/v3/test_region.py index be6ef1cb68..5ba0c231c6 100644 --- a/functional/tests/identity/v3/test_region.py +++ b/functional/tests/identity/v3/test_region.py @@ -10,10 +10,10 @@ # License for the specific language governing permissions and limitations # under the License. -from functional.tests.identity.v3 import test_identity +from functional.tests.identity.v3 import common -class RegionTests(test_identity.IdentityTests): +class RegionTests(common.IdentityTests): def test_region_create(self): self._create_dummy_region() diff --git a/functional/tests/identity/v3/test_role.py b/functional/tests/identity/v3/test_role.py index 29dc4b2005..60aaf3f457 100644 --- a/functional/tests/identity/v3/test_role.py +++ b/functional/tests/identity/v3/test_role.py @@ -12,10 +12,10 @@ from tempest.lib.common.utils import data_utils -from functional.tests.identity.v3 import test_identity +from functional.tests.identity.v3 import common -class RoleTests(test_identity.IdentityTests): +class RoleTests(common.IdentityTests): def test_role_create(self): self._create_dummy_role() @@ -29,7 +29,7 @@ def test_role_list(self): self._create_dummy_role() raw_output = self.openstack('role list') items = self.parse_listing(raw_output) - self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS) + self.assert_table_structure(items, common.BASIC_LIST_HEADERS) def test_role_list_with_user_project(self): role_name = self._create_dummy_role() @@ -69,7 +69,7 @@ def test_role_list_with_user_project(self): 'user': username, 'user_domain': self.domain_name}) items = self.parse_listing(raw_output) - self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS) + self.assert_table_structure(items, common.BASIC_LIST_HEADERS) self.assertEqual(1, len(items)) def test_role_show(self): diff --git a/functional/tests/identity/v3/test_service.py b/functional/tests/identity/v3/test_service.py index 684fa5fea8..c757d914ee 100644 --- a/functional/tests/identity/v3/test_service.py +++ b/functional/tests/identity/v3/test_service.py @@ -12,10 +12,10 @@ from tempest.lib.common.utils import data_utils -from functional.tests.identity.v3 import test_identity +from functional.tests.identity.v3 import common -class ServiceTests(test_identity.IdentityTests): +class ServiceTests(common.IdentityTests): def test_service_create(self): self._create_dummy_service() @@ -29,7 +29,7 @@ def test_service_list(self): self._create_dummy_service() raw_output = self.openstack('service list') items = self.parse_listing(raw_output) - self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS) + self.assert_table_structure(items, common.BASIC_LIST_HEADERS) def test_service_set(self): service_name = self._create_dummy_service() diff --git a/functional/tests/identity/v3/test_service_provider.py b/functional/tests/identity/v3/test_service_provider.py index 936c6620d6..6cfa7e56db 100644 --- a/functional/tests/identity/v3/test_service_provider.py +++ b/functional/tests/identity/v3/test_service_provider.py @@ -10,11 +10,11 @@ # License for the specific language governing permissions and limitations # under the License. -from functional.tests.identity.v3 import test_identity +from functional.tests.identity.v3 import common from tempest.lib.common.utils import data_utils -class ServiceProviderTests(test_identity.IdentityTests): +class ServiceProviderTests(common.IdentityTests): # Introduce functional test cases for command 'Service Provider' def test_sp_create(self): diff --git a/functional/tests/identity/v3/test_token.py b/functional/tests/identity/v3/test_token.py index 67fddccf68..d8d3f43d66 100644 --- a/functional/tests/identity/v3/test_token.py +++ b/functional/tests/identity/v3/test_token.py @@ -10,10 +10,10 @@ # License for the specific language governing permissions and limitations # under the License. -from functional.tests.identity.v3 import test_identity +from functional.tests.identity.v3 import common -class TokenTests(test_identity.IdentityTests): +class TokenTests(common.IdentityTests): def test_token_issue(self): raw_output = self.openstack('token issue') diff --git a/functional/tests/identity/v3/test_user.py b/functional/tests/identity/v3/test_user.py index cc3e08a0b3..f3657064c9 100644 --- a/functional/tests/identity/v3/test_user.py +++ b/functional/tests/identity/v3/test_user.py @@ -12,10 +12,10 @@ from tempest.lib.common.utils import data_utils -from functional.tests.identity.v3 import test_identity +from functional.tests.identity.v3 import common -class UserTests(test_identity.IdentityTests): +class UserTests(common.IdentityTests): def test_user_create(self): self._create_dummy_user() @@ -31,7 +31,7 @@ def test_user_delete(self): def test_user_list(self): raw_output = self.openstack('user list') items = self.parse_listing(raw_output) - self.assert_table_structure(items, test_identity.BASIC_LIST_HEADERS) + self.assert_table_structure(items, common.BASIC_LIST_HEADERS) def test_user_set(self): username = self._create_dummy_user() From c7fb3b36556a6e7622dd7b2bf2ddb5209e518773 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Wed, 15 Jun 2016 08:53:00 -0500 Subject: [PATCH 1018/3095] Add "--device-owner" option to "port list" Add "--device-owner" option to the "port list" command to enable listing ports based on device owner. Change-Id: I0a538ec41800b9f842e86dceb6ca4180ef239c95 Implements: blueprint neutron-client --- doc/source/command-objects/port.rst | 12 +++++- openstackclient/network/v2/port.py | 14 ++++++- openstackclient/tests/network/v2/test_port.py | 41 +++++++++++++++++++ .../bp-neutron-client-a0552f8ca909b665.yaml | 3 ++ 4 files changed, 66 insertions(+), 4 deletions(-) diff --git a/doc/source/command-objects/port.rst b/doc/source/command-objects/port.rst index e4cf2cd212..0a74f88c7d 100644 --- a/doc/source/command-objects/port.rst +++ b/doc/source/command-objects/port.rst @@ -45,7 +45,8 @@ Create new port .. option:: --device-owner - Device owner of this port + Device owner of this port. This is the entity that uses + the port (for example, network:dhcp). .. option:: --vnic-type @@ -112,8 +113,14 @@ List ports .. code:: bash os port list + [--device-owner ] [--router ] +.. option:: --device-owner + + List only ports with the specified device owner. This is + the entity that uses the port (for example, network:dhcp). + .. option:: --router List only ports attached to this router (name or ID) @@ -153,7 +160,8 @@ Set port properties .. option:: --device-owner - Device owner of this port + Device owner of this port. This is the entity that uses + the port (for example, network:dhcp). .. option:: --vnic-type diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 1c5db706e3..71dc108340 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -169,7 +169,8 @@ def _add_updatable_args(parser): parser.add_argument( '--device-owner', metavar='', - help=_("Device owner of this port") + help=_("Device owner of this port. This is the entity that uses " + "the port (for example, network:dhcp).") ) parser.add_argument( '--vnic-type', @@ -309,6 +310,13 @@ class ListPort(command.Lister): def get_parser(self, prog_name): parser = super(ListPort, self).get_parser(prog_name) + parser.add_argument( + '--device-owner', + metavar='', + help=_("List only ports with the specified device owner. " + "This is the entity that uses the port (for example, " + "network:dhcp).") + ) parser.add_argument( '--router', metavar='', @@ -334,10 +342,12 @@ def take_action(self, parsed_args): ) filters = {} + if parsed_args.device_owner is not None: + filters['device_owner'] = parsed_args.device_owner if parsed_args.router: _router = client.find_router(parsed_args.router, ignore_missing=False) - filters = {'device_id': _router.id} + filters['device_id'] = _router.id data = client.ports(**filters) diff --git a/openstackclient/tests/network/v2/test_port.py b/openstackclient/tests/network/v2/test_port.py index 779dca0519..8da6ec108b 100644 --- a/openstackclient/tests/network/v2/test_port.py +++ b/openstackclient/tests/network/v2/test_port.py @@ -316,6 +316,47 @@ def test_port_list_router_opt(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + def test_port_list_device_owner_opt(self): + arglist = [ + '--device-owner', self._ports[0].device_owner, + ] + + verifylist = [ + ('device_owner', self._ports[0].device_owner) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.ports.assert_called_once_with(**{ + 'device_owner': self._ports[0].device_owner + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_port_list_all_opt(self): + arglist = [ + '--device-owner', self._ports[0].device_owner, + '--router', 'fake-router-name', + ] + + verifylist = [ + ('device_owner', self._ports[0].device_owner), + ('router', 'fake-router-name') + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.ports.assert_called_once_with(**{ + 'device_owner': self._ports[0].device_owner, + 'device_id': 'fake-router-id' + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + class TestSetPort(TestPort): diff --git a/releasenotes/notes/bp-neutron-client-a0552f8ca909b665.yaml b/releasenotes/notes/bp-neutron-client-a0552f8ca909b665.yaml index f8de4ee6ae..3665dd8122 100644 --- a/releasenotes/notes/bp-neutron-client-a0552f8ca909b665.yaml +++ b/releasenotes/notes/bp-neutron-client-a0552f8ca909b665.yaml @@ -3,3 +3,6 @@ features: - Add ``geneve`` choice to the ``network create`` command ``--provider-network-type`` option. [Blueprint :oscbp:`neutron-client`] + - Add ``--device-owner`` option to the ``port list`` command + to enable listing ports based on device owner. + [Blueprint :oscbp:`neutron-client`] From 640014fa91b939e802f261346473d3ec025f2acb Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Mon, 20 Jun 2016 15:42:40 +0800 Subject: [PATCH 1019/3095] Support bulk deletion for "flavor/aggregate delete" Support bulk deletion and error handling for "aggregate delete" and "flavor delete" commands. Change-Id: I3f6105cbeeab1c9f8cd571c63ce0e7ac3d4252b3 Partially-Implements: blueprint multi-argument-compute Partial-Bug: #1592906 --- doc/source/command-objects/aggregate.rst | 6 +- doc/source/command-objects/flavor.rst | 6 +- openstackclient/compute/v2/aggregate.py | 34 +++++++--- openstackclient/compute/v2/flavor.py | 28 ++++++-- openstackclient/tests/compute/v2/fakes.py | 38 ++++++++++- .../tests/compute/v2/test_aggregate.py | 65 +++++++++++++++++-- .../tests/compute/v2/test_flavor.py | 58 ++++++++++++----- ...lti-argument-compute-0bc4522f6edca355.yaml | 5 ++ 8 files changed, 201 insertions(+), 39 deletions(-) create mode 100644 releasenotes/notes/bp-multi-argument-compute-0bc4522f6edca355.yaml diff --git a/doc/source/command-objects/aggregate.rst b/doc/source/command-objects/aggregate.rst index 7616c49656..25c7041a48 100644 --- a/doc/source/command-objects/aggregate.rst +++ b/doc/source/command-objects/aggregate.rst @@ -56,17 +56,17 @@ Create a new aggregate aggregate delete ---------------- -Delete an existing aggregate +Delete existing aggregate(s) .. program:: aggregate delete .. code:: bash os aggregate delete - + [ ...] .. describe:: - Aggregate to delete (name or ID) + Aggregate(s) to delete (name or ID) aggregate list -------------- diff --git a/doc/source/command-objects/flavor.rst b/doc/source/command-objects/flavor.rst index a30bedecb6..dd25f44283 100644 --- a/doc/source/command-objects/flavor.rst +++ b/doc/source/command-objects/flavor.rst @@ -67,18 +67,18 @@ Create new flavor flavor delete ------------- -Delete flavor +Delete flavor(s) .. program:: flavor delete .. code:: bash os flavor delete - + [ ...] .. _flavor_delete-flavor: .. describe:: - Flavor to delete (name or ID) + Flavor(s) to delete (name or ID) flavor list ----------- diff --git a/openstackclient/compute/v2/aggregate.py b/openstackclient/compute/v2/aggregate.py index 8000c93a0e..2e2838c54c 100644 --- a/openstackclient/compute/v2/aggregate.py +++ b/openstackclient/compute/v2/aggregate.py @@ -16,14 +16,20 @@ """Compute v2 Aggregate action implementations""" +import logging + from osc_lib.cli import parseractions 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 AddAggregateHost(command.ShowOne): """Add host to aggregate""" @@ -99,25 +105,37 @@ def take_action(self, parsed_args): class DeleteAggregate(command.Command): - """Delete an existing aggregate""" + """Delete existing aggregate(s)""" def get_parser(self, prog_name): parser = super(DeleteAggregate, self).get_parser(prog_name) parser.add_argument( 'aggregate', metavar='', - help=_("Aggregate to delete (name or ID)") + nargs='+', + help=_("Aggregate(s) to delete (name or ID)") ) return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - data = utils.find_resource( - compute_client.aggregates, - parsed_args.aggregate, - ) - compute_client.aggregates.delete(data.id) + result = 0 + for a in parsed_args.aggregate: + try: + data = utils.find_resource( + compute_client.aggregates, a) + compute_client.aggregates.delete(data.id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete aggregate with name or " + "ID '%(aggregate)s': %(e)s") + % {'aggregate': a, 'e': e}) + + if result > 0: + total = len(parsed_args.aggregate) + msg = (_("%(result)s of %(total)s aggregates failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListAggregate(command.Lister): diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index ab2bc85d0f..dc276109f9 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -15,6 +15,8 @@ """Flavor action implementations""" +import logging + from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -25,6 +27,9 @@ from openstackclient.identity import common as identity_common +LOG = logging.getLogger(__name__) + + def _find_flavor(compute_client, flavor): try: return compute_client.flavors.get(flavor) @@ -140,21 +145,36 @@ def take_action(self, parsed_args): class DeleteFlavor(command.Command): - """Delete flavor""" + """Delete flavor(s)""" def get_parser(self, prog_name): parser = super(DeleteFlavor, self).get_parser(prog_name) parser.add_argument( "flavor", metavar="", - help=_("Flavor to delete (name or ID)") + nargs='+', + help=_("Flavor(s) to delete (name or ID)") ) return parser def take_action(self, parsed_args): compute_client = self.app.client_manager.compute - flavor = _find_flavor(compute_client, parsed_args.flavor) - compute_client.flavors.delete(flavor.id) + result = 0 + for f in parsed_args.flavor: + try: + flavor = _find_flavor(compute_client, f) + compute_client.flavors.delete(flavor.id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete flavor with name or " + "ID '%(flavor)s': %(e)s") + % {'flavor': f, 'e': e}) + + if result > 0: + total = len(parsed_args.flavor) + msg = (_("%(result)s of %(total)s flavors failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListFlavor(command.Lister): diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 60abb8ef1e..8416b630bf 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -87,6 +87,42 @@ def create_one_aggregate(attrs=None): loaded=True) return aggregate + @staticmethod + def create_aggregates(attrs=None, count=2): + """Create multiple fake aggregates. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of aggregates to fake + :return: + A list of FakeResource objects faking the aggregates + """ + aggregates = [] + for i in range(0, count): + aggregates.append(FakeAggregate.create_one_aggregate(attrs)) + + return aggregates + + @staticmethod + def get_aggregates(aggregates=None, count=2): + """Get an iterable MagicMock object with a list of faked aggregates. + + If aggregates list is provided, then initialize the Mock object + with the list. Otherwise create one. + + :param List aggregates: + A list of FakeResource objects faking aggregates + :param int count: + The number of aggregates to fake + :return: + An iterable Mock object with side_effect set to a list of faked + aggregates + """ + if aggregates is None: + aggregates = FakeAggregate.create_aggregates(count) + return mock.MagicMock(side_effect=aggregates) + class FakeComputev2Client(object): @@ -732,7 +768,7 @@ def get_flavors(flavors=None, count=2): flavors """ if flavors is None: - flavors = FakeServer.create_flavors(count) + flavors = FakeFlavor.create_flavors(count) return mock.MagicMock(side_effect=flavors) diff --git a/openstackclient/tests/compute/v2/test_aggregate.py b/openstackclient/tests/compute/v2/test_aggregate.py index 58dd775525..3ebca35f9c 100644 --- a/openstackclient/tests/compute/v2/test_aggregate.py +++ b/openstackclient/tests/compute/v2/test_aggregate.py @@ -13,6 +13,12 @@ # under the License. # +import mock +from mock import call + +from osc_lib import exceptions +from osc_lib import utils + from openstackclient.compute.v2 import aggregate from openstackclient.tests.compute.v2 import fakes as compute_fakes from openstackclient.tests import utils as tests_utils @@ -135,25 +141,74 @@ def test_aggregate_create_with_property(self): class TestAggregateDelete(TestAggregate): + fake_ags = compute_fakes.FakeAggregate.create_aggregates(count=2) + def setUp(self): super(TestAggregateDelete, self).setUp() - self.aggregate_mock.get.return_value = self.fake_ag + self.aggregate_mock.get = ( + compute_fakes.FakeAggregate.get_aggregates(self.fake_ags)) self.cmd = aggregate.DeleteAggregate(self.app, None) def test_aggregate_delete(self): arglist = [ - 'ag1', + self.fake_ags[0].id ] verifylist = [ - ('aggregate', 'ag1'), + ('aggregate', [self.fake_ags[0].id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate) - self.aggregate_mock.delete.assert_called_once_with(self.fake_ag.id) + self.aggregate_mock.get.assert_called_once_with(self.fake_ags[0].id) + self.aggregate_mock.delete.assert_called_once_with(self.fake_ags[0].id) self.assertIsNone(result) + def test_delete_multiple_aggregates(self): + arglist = [] + for a in self.fake_ags: + arglist.append(a.id) + verifylist = [ + ('aggregate', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + calls = [] + for a in self.fake_ags: + calls.append(call(a.id)) + self.aggregate_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_delete_multiple_agggregates_with_exception(self): + arglist = [ + self.fake_ags[0].id, + 'unexist_aggregate', + ] + verifylist = [ + ('aggregate', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self.fake_ags[0], exceptions.CommandError] + with mock.patch.object(utils, 'find_resource', + side_effect=find_mock_result) as find_mock: + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 aggregates failed to delete.', + str(e)) + + find_mock.assert_any_call(self.aggregate_mock, self.fake_ags[0].id) + find_mock.assert_any_call(self.aggregate_mock, 'unexist_aggregate') + + self.assertEqual(2, find_mock.call_count) + self.aggregate_mock.delete.assert_called_once_with( + self.fake_ags[0].id + ) + class TestAggregateList(TestAggregate): diff --git a/openstackclient/tests/compute/v2/test_flavor.py b/openstackclient/tests/compute/v2/test_flavor.py index 4365a540bc..d8d02e111d 100644 --- a/openstackclient/tests/compute/v2/test_flavor.py +++ b/openstackclient/tests/compute/v2/test_flavor.py @@ -14,6 +14,8 @@ # import copy +import mock +from mock import call from osc_lib import exceptions from osc_lib import utils @@ -204,47 +206,73 @@ def test_flavor_create_no_options(self): class TestFlavorDelete(TestFlavor): - flavor = compute_fakes.FakeFlavor.create_one_flavor() + flavors = compute_fakes.FakeFlavor.create_flavors(count=2) def setUp(self): super(TestFlavorDelete, self).setUp() - self.flavors_mock.get.return_value = self.flavor + self.flavors_mock.get = ( + compute_fakes.FakeFlavor.get_flavors(self.flavors)) self.flavors_mock.delete.return_value = None self.cmd = flavor.DeleteFlavor(self.app, None) def test_flavor_delete(self): arglist = [ - self.flavor.id + self.flavors[0].id ] verifylist = [ - ('flavor', self.flavor.id), + ('flavor', [self.flavors[0].id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.flavors_mock.delete.assert_called_with(self.flavor.id) + self.flavors_mock.delete.assert_called_with(self.flavors[0].id) self.assertIsNone(result) - def test_flavor_delete_with_unexist_flavor(self): - self.flavors_mock.get.side_effect = exceptions.NotFound(None) - self.flavors_mock.find.side_effect = exceptions.NotFound(None) + def test_delete_multiple_flavors(self): + arglist = [] + for f in self.flavors: + arglist.append(f.id) + verifylist = [ + ('flavor', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + calls = [] + for f in self.flavors: + calls.append(call(f.id)) + self.flavors_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_multi_flavors_delete_with_exception(self): arglist = [ - 'unexist_flavor' + self.flavors[0].id, + 'unexist_flavor', ] verifylist = [ - ('flavor', 'unexist_flavor'), + ('flavor', [self.flavors[0].id, 'unexist_flavor']) ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.assertRaises( - exceptions.CommandError, - self.cmd.take_action, - parsed_args) + find_mock_result = [self.flavors[0], exceptions.CommandError] + self.flavors_mock.get = ( + mock.MagicMock(side_effect=find_mock_result) + ) + self.flavors_mock.find.side_effect = exceptions.NotFound(None) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 flavors failed to delete.', str(e)) + + self.flavors_mock.get.assert_any_call(self.flavors[0].id) + self.flavors_mock.get.assert_any_call('unexist_flavor') + self.flavors_mock.delete.assert_called_once_with(self.flavors[0].id) class TestFlavorList(TestFlavor): diff --git a/releasenotes/notes/bp-multi-argument-compute-0bc4522f6edca355.yaml b/releasenotes/notes/bp-multi-argument-compute-0bc4522f6edca355.yaml new file mode 100644 index 0000000000..286dc7ea67 --- /dev/null +++ b/releasenotes/notes/bp-multi-argument-compute-0bc4522f6edca355.yaml @@ -0,0 +1,5 @@ +--- +features: + - Support bulk deletion and error handling for ``aggregate delete`` and + ``flavor delete`` commands. + [Blueprint `multi-argument-compute `_] From a4dd1fc71402c8ccc10387c889f40c09e7396b8b Mon Sep 17 00:00:00 2001 From: liyifeng <307419146@qq.com> Date: Tue, 21 Jun 2016 10:12:08 +0800 Subject: [PATCH 1020/3095] Make the print info support i18n When OSC failed to set a image's property, it will print the image name and status, which is an useful info to users. So translate this massage, and record it in log. Change-Id: Icdff4dab17dedcb40289700c4cd278e6e62eea25 --- openstackclient/image/v2/image.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 82566ff379..309b1b6b18 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -836,7 +836,8 @@ def take_action(self, parsed_args): image = image_client.images.update(image.id, **kwargs) except Exception as e: if activation_status is not None: - print("Image %s was %s." % (image.id, activation_status)) + LOG.info(_("Image %(id)s was %(status)s."), + {'id': image.id, 'status': activation_status}) raise e From 78ae57112c846b3a230a928fe5fc3d8ee909dc15 Mon Sep 17 00:00:00 2001 From: Alvaro Lopez Garcia Date: Wed, 18 May 2016 17:55:19 +0200 Subject: [PATCH 1021/3095] Refactor check_valid_auth_options function The functions check_valid_auth_options() function was relying on the name for checking the set of required options, but this could cause errors with external auth plugins. If somebody defines an auth plugin plugin named "footoken" the check function would check for a "token" option, even if the plugin has not defined that option. This change tries to improve this situation, cheking for some options only if they have been defined in the plugin. Change-Id: I4255f2e7d4d23449c95be957ea7b6b60983f2608 --- openstackclient/api/auth.py | 43 ++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/openstackclient/api/auth.py b/openstackclient/api/auth.py index 0018e76e61..9981f6d57c 100644 --- a/openstackclient/api/auth.py +++ b/openstackclient/api/auth.py @@ -147,29 +147,28 @@ def check_valid_authorization_options(options, auth_plugin_name): def check_valid_authentication_options(options, auth_plugin_name): """Validate authentication options, and provide helpful error messages.""" + # Get all the options defined within the plugin. + plugin_opts = base.get_plugin_options(auth_plugin_name) + plugin_opts = {opt.dest: opt for opt in plugin_opts} + + # NOTE(aloga): this is an horrible hack. We need a way to specify the + # required options in the plugins. Using the "required" argument for + # the oslo_config.cfg.Opt does not work, as it is not possible to load the + # plugin if the option is not defined, so the error will simply be: + # "NoMatchingPlugin: The plugin foobar could not be found" msgs = [] - if auth_plugin_name.endswith('password'): - if not options.auth.get('username'): - msgs.append(_('Set a username with --os-username, OS_USERNAME,' - ' or auth.username')) - if not options.auth.get('auth_url'): - msgs.append(_('Set an authentication URL, with --os-auth-url,' - ' OS_AUTH_URL or auth.auth_url')) - elif auth_plugin_name.endswith('token'): - if not options.auth.get('token'): - msgs.append(_('Set a token with --os-token, OS_TOKEN or ' - 'auth.token')) - if not options.auth.get('auth_url'): - msgs.append(_('Set a service AUTH_URL, with --os-auth-url, ' - 'OS_AUTH_URL or auth.auth_url')) - elif auth_plugin_name == 'token_endpoint': - if not options.auth.get('token'): - msgs.append(_('Set a token with --os-token, OS_TOKEN or ' - 'auth.token')) - if not options.auth.get('url'): - msgs.append(_('Set a service URL, with --os-url, OS_URL or ' - 'auth.url')) - + if 'password' in plugin_opts and not options.auth.get('username'): + msgs.append(_('Set a username with --os-username, OS_USERNAME,' + ' or auth.username')) + if 'auth_url' in plugin_opts and not options.auth.get('auth_url'): + msgs.append(_('Set a service AUTH_URL, with --os-auth-url, ' + 'OS_AUTH_URL or auth.auth_url')) + if 'url' in plugin_opts and not options.auth.get('url'): + msgs.append(_('Set a service URL, with --os-url, ' + 'OS_URL or auth.url')) + if 'token' in plugin_opts and not options.auth.get('token'): + msgs.append(_('Set a token with --os-token, ' + 'OS_TOKEN or auth.token')) if msgs: raise exc.CommandError( _('Missing parameter(s): \n%s') % '\n'.join(msgs)) From 099a2c38b99dff6a0909c0a3ba2909f1aea58644 Mon Sep 17 00:00:00 2001 From: Alvaro Lopez Garcia Date: Fri, 27 May 2016 11:03:15 +0200 Subject: [PATCH 1022/3095] Refactor setting defaults for some scope parameters The code is setting defaults for some scope parameters, cheking if the name ends with some specific substring (namely ending in "password") causing failures in some plugins that end with the same string, but do not allow those parameters (like "user_domain_id" in "v3oidcpassword"). Closes-Bug: #1582774 Change-Id: Id7036db3b783b135353d035dc4c1df7c808d6474 --- openstackclient/api/auth.py | 5 +- openstackclient/api/auth_plugin.py | 2 +- openstackclient/common/clientmanager.py | 78 ++++++++++--------- .../notes/bug-1582774-3bba709ef61e33b7.yaml | 5 ++ 4 files changed, 53 insertions(+), 37 deletions(-) create mode 100644 releasenotes/notes/bug-1582774-3bba709ef61e33b7.yaml diff --git a/openstackclient/api/auth.py b/openstackclient/api/auth.py index 9981f6d57c..d5412594a0 100644 --- a/openstackclient/api/auth.py +++ b/openstackclient/api/auth.py @@ -105,10 +105,12 @@ def select_auth_plugin(options): def build_auth_params(auth_plugin_name, cmd_options): - auth_params = dict(cmd_options.auth) if auth_plugin_name: LOG.debug('auth_type: %s', auth_plugin_name) auth_plugin_loader = base.get_plugin_loader(auth_plugin_name) + auth_params = {opt.dest: opt.default + for opt in base.get_plugin_options(auth_plugin_name)} + auth_params.update(dict(cmd_options.auth)) # grab tenant from project for v2.0 API compatibility if auth_plugin_name.startswith("v2"): if 'project_id' in auth_params: @@ -121,6 +123,7 @@ def build_auth_params(auth_plugin_name, cmd_options): LOG.debug('no auth_type') # delay the plugin choice, grab every option auth_plugin_loader = None + auth_params = dict(cmd_options.auth) plugin_options = set([o.replace('-', '_') for o in get_options_list()]) for option in plugin_options: LOG.debug('fetching option %s', option) diff --git a/openstackclient/api/auth_plugin.py b/openstackclient/api/auth_plugin.py index 36dc51605f..4434bc8ffc 100644 --- a/openstackclient/api/auth_plugin.py +++ b/openstackclient/api/auth_plugin.py @@ -38,7 +38,7 @@ class TokenEndpoint(token_endpoint.AdminToken): is for bootstrapping the Keystone database. """ - def load_from_options(self, url, token): + def load_from_options(self, url, token, **kwargs): """A plugin for static authentication with an existing token :param string url: Service endpoint diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index 5dbfb41712..3c35b52933 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -140,8 +140,49 @@ def __init__( # prior to dereferrencing auth_ref. self._auth_setup_completed = False + def _set_default_scope_options(self): + # TODO(mordred): This is a usability improvement that's broadly useful + # We should port it back up into os-client-config. + default_domain = self._cli_options.default_domain + + # NOTE(hieulq): If USER_DOMAIN_NAME, USER_DOMAIN_ID, PROJECT_DOMAIN_ID + # or PROJECT_DOMAIN_NAME is present and API_VERSION is 2.0, then + # ignore all domain related configs. + if (self._api_version.get('identity') == '2.0' and + self.auth_plugin_name.endswith('password')): + domain_props = ['project_domain_name', 'project_domain_id', + 'user_domain_name', 'user_domain_id'] + for prop in domain_props: + if self._auth_params.pop(prop, None) is not None: + LOG.warning("Ignoring domain related configs " + + prop + " because identity API version is 2.0") + return + + # NOTE(aloga): The scope parameters below only apply to v3 and v3 + # related auth plugins, so we stop the parameter checking if v2 is + # being used. + if (self._api_version.get('identity') != '3' or + self.auth_plugin_name.startswith('v2')): + return + + # NOTE(stevemar): If PROJECT_DOMAIN_ID or PROJECT_DOMAIN_NAME is + # present, then do not change the behaviour. Otherwise, set the + # PROJECT_DOMAIN_ID to 'OS_DEFAULT_DOMAIN' for better usability. + if ('project_domain_id' in self._auth_params and + not self._auth_params.get('project_domain_id') and + not self._auth_params.get('project_domain_name')): + self._auth_params['project_domain_id'] = default_domain + + # NOTE(stevemar): If USER_DOMAIN_ID or USER_DOMAIN_NAME is present, + # then do not change the behaviour. Otherwise, set the + # USER_DOMAIN_ID to 'OS_DEFAULT_DOMAIN' for better usability. + if ('user_domain_id' in self._auth_params and + not self._auth_params.get('user_domain_id') and + not self._auth_params.get('user_domain_name')): + self._auth_params['user_domain_id'] = default_domain + def setup_auth(self): - """Set up authentication. + """Set up authentication This is deferred until authentication is actually attempted because it gets in the way of things that do not require auth. @@ -169,40 +210,7 @@ def setup_auth(self): self._cli_options, ) - # TODO(mordred): This is a usability improvement that's broadly useful - # We should port it back up into os-client-config. - default_domain = self._cli_options.default_domain - # NOTE(stevemar): If PROJECT_DOMAIN_ID or PROJECT_DOMAIN_NAME is - # present, then do not change the behaviour. Otherwise, set the - # PROJECT_DOMAIN_ID to 'OS_DEFAULT_DOMAIN' for better usability. - if (self._api_version.get('identity') == '3' and - self.auth_plugin_name.endswith('password') and - not self._auth_params.get('project_domain_id') and - not self.auth_plugin_name.startswith('v2') and - not self._auth_params.get('project_domain_name')): - self._auth_params['project_domain_id'] = default_domain - - # NOTE(stevemar): If USER_DOMAIN_ID or USER_DOMAIN_NAME is present, - # then do not change the behaviour. Otherwise, set the USER_DOMAIN_ID - # to 'OS_DEFAULT_DOMAIN' for better usability. - if (self._api_version.get('identity') == '3' and - self.auth_plugin_name.endswith('password') and - not self.auth_plugin_name.startswith('v2') and - not self._auth_params.get('user_domain_id') and - not self._auth_params.get('user_domain_name')): - self._auth_params['user_domain_id'] = default_domain - - # NOTE(hieulq): If USER_DOMAIN_NAME, USER_DOMAIN_ID, PROJECT_DOMAIN_ID - # or PROJECT_DOMAIN_NAME is present and API_VERSION is 2.0, then - # ignore all domain related configs. - if (self._api_version.get('identity') == '2.0' and - self.auth_plugin_name.endswith('password')): - domain_props = ['project_domain_name', 'project_domain_id', - 'user_domain_name', 'user_domain_id'] - for prop in domain_props: - if self._auth_params.pop(prop, None) is not None: - LOG.warning("Ignoring domain related configs " + - prop + " because identity API version is 2.0") + self._set_default_scope_options() # For compatibility until all clients can be updated if 'project_name' in self._auth_params: diff --git a/releasenotes/notes/bug-1582774-3bba709ef61e33b7.yaml b/releasenotes/notes/bug-1582774-3bba709ef61e33b7.yaml new file mode 100644 index 0000000000..35c72171f8 --- /dev/null +++ b/releasenotes/notes/bug-1582774-3bba709ef61e33b7.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - Fix setting defaults for some scope parameters, that were putting invalid + scope parameters for some auth plugins. + [Bug `1582774 `_] From 1de4c66009485b6e42791ac84684da7b5a1f0736 Mon Sep 17 00:00:00 2001 From: Alvaro Lopez Garcia Date: Mon, 20 Jun 2016 12:37:22 +0200 Subject: [PATCH 1023/3095] Improve masking of secrets in configuration show The command "configuration show" tries to redact some of the secrets that are shown on the screen. However, this failed redacting options that were marked as secrete by the auth plugins (if any) and it redacted other options that were not redacted at all. For example, when using the OpenID Connect plugins, it redacted the "access_token_endpoint" as the word "token" appears there, but it failed to redact "client_secret" even when this option is marked as secret in the corresponding plugin. Change-Id: Idfad4fbbe5ddcff5e729e1dcd756d0379ad31dee --- openstackclient/common/configuration.py | 10 ++++++---- .../tests/common/test_configuration.py | 17 ++++++++++++++--- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/openstackclient/common/configuration.py b/openstackclient/common/configuration.py index d6e2ab45bb..016e91917f 100644 --- a/openstackclient/common/configuration.py +++ b/openstackclient/common/configuration.py @@ -13,6 +13,7 @@ """Configuration action implementations""" +from keystoneauth1.loading import base from osc_lib.command import command import six @@ -44,12 +45,13 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): + auth_plg_name = self.app.client_manager.auth_plugin_name + secret_opts = [o.dest for o in base.get_plugin_options(auth_plg_name) + if o.secret] + info = self.app.client_manager.get_configuration() for key, value in six.iteritems(info.pop('auth', {})): - if parsed_args.mask: - if 'password' in key.lower(): - value = REDACTED - if 'token' in key.lower(): + if parsed_args.mask and key.lower() in secret_opts: value = REDACTED info['auth.' + key] = value return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/tests/common/test_configuration.py b/openstackclient/tests/common/test_configuration.py index e81550ed0a..915e5bd394 100644 --- a/openstackclient/tests/common/test_configuration.py +++ b/openstackclient/tests/common/test_configuration.py @@ -11,6 +11,8 @@ # under the License. # +import mock + from openstackclient.common import configuration from openstackclient.tests import fakes from openstackclient.tests import utils @@ -33,7 +35,12 @@ class TestConfiguration(utils.TestCommand): fakes.REGION_NAME, ) - def test_show(self): + opts = [mock.Mock(secret=True, dest="password"), + mock.Mock(secret=True, dest="token")] + + @mock.patch("keystoneauth1.loading.base.get_plugin_options", + return_value=opts) + def test_show(self, m_get_plugin_opts): arglist = [] verifylist = [('mask', True)] cmd = configuration.ShowConfiguration(self.app, None) @@ -44,7 +51,9 @@ def test_show(self): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, data) - def test_show_unmask(self): + @mock.patch("keystoneauth1.loading.base.get_plugin_options", + return_value=opts) + def test_show_unmask(self, m_get_plugin_opts): arglist = ['--unmask'] verifylist = [('mask', False)] cmd = configuration.ShowConfiguration(self.app, None) @@ -62,7 +71,9 @@ def test_show_unmask(self): ) self.assertEqual(datalist, data) - def test_show_mask(self): + @mock.patch("keystoneauth1.loading.base.get_plugin_options", + return_value=opts) + def test_show_mask(self, m_get_plugin_opts): arglist = ['--mask'] verifylist = [('mask', True)] cmd = configuration.ShowConfiguration(self.app, None) From 9c62af8a42ebfeb60d88f5ad0af7c1c2fd562853 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 21 Jun 2016 15:15:18 +0800 Subject: [PATCH 1024/3095] Make set/unset commands in compute/image/common return normally when nothing specified After this patch, all set/unset commands will return normally when nothing specified. Change-Id: Id94d0329faa1a674006a9aae901f834b41917317 Close-bug: #1588588 --- openstackclient/common/quota.py | 5 ----- openstackclient/compute/v2/flavor.py | 6 ------ openstackclient/image/v1/image.py | 4 ---- openstackclient/tests/compute/v2/test_flavor.py | 15 +++++++++++---- openstackclient/tests/image/v1/test_image.py | 4 ++-- .../notes/bug-1588588-39927ef06ca35730.yaml | 6 +++--- 6 files changed, 16 insertions(+), 24 deletions(-) diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index 087d8ea3fd..69415f0dc0 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -142,11 +142,6 @@ def take_action(self, parsed_args): if value is not None: compute_kwargs[k] = value - if (compute_kwargs == {} and volume_kwargs == {} - and network_kwargs == {}): - sys.stderr.write("No quotas updated\n") - return - if parsed_args.project: project = utils.find_resource( identity_client.projects, diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 0a0d25c2de..bcf97c6955 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -279,9 +279,6 @@ def take_action(self, parsed_args): flavor = _find_flavor(compute_client, parsed_args.flavor) - if not parsed_args.property and not parsed_args.project: - raise exceptions.CommandError(_("Nothing specified to be set.")) - result = 0 if parsed_args.property: try: @@ -368,9 +365,6 @@ def take_action(self, parsed_args): flavor = _find_flavor(compute_client, parsed_args.flavor) - if not parsed_args.property and not parsed_args.project: - raise exceptions.CommandError(_("Nothing specified to be unset.")) - result = 0 if parsed_args.property: try: diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index 27467b0c28..e7b60e5ad5 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -691,10 +691,6 @@ def take_action(self, parsed_args): image.properties.update(kwargs['properties']) kwargs['properties'] = image.properties - if not kwargs: - LOG.warning(_('no arguments specified')) - return - image = image_client.images.update(image.id, **kwargs) finally: # Clean up open files - make sure data isn't a string diff --git a/openstackclient/tests/compute/v2/test_flavor.py b/openstackclient/tests/compute/v2/test_flavor.py index 4365a540bc..864a43271b 100644 --- a/openstackclient/tests/compute/v2/test_flavor.py +++ b/openstackclient/tests/compute/v2/test_flavor.py @@ -542,8 +542,12 @@ def test_flavor_set_nothing(self): ('flavor', self.flavor.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.assertRaises(exceptions.CommandError, self.cmd.take_action, - parsed_args) + result = self.cmd.take_action(parsed_args) + + self.flavors_mock.find.assert_called_with(name=parsed_args.flavor, + is_public=None) + self.flavor_access_mock.add_tenant_access.assert_not_called() + self.assertIsNone(result) class TestFlavorShow(TestFlavor): @@ -717,5 +721,8 @@ def test_flavor_unset_nothing(self): ('flavor', self.flavor.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.assertRaises(exceptions.CommandError, self.cmd.take_action, - parsed_args) + + result = self.cmd.take_action(parsed_args) + self.assertIsNone(result) + + self.flavor_access_mock.remove_tenant_access.assert_not_called() diff --git a/openstackclient/tests/image/v1/test_image.py b/openstackclient/tests/image/v1/test_image.py index 99c0b0eee7..14aa331fbc 100644 --- a/openstackclient/tests/image/v1/test_image.py +++ b/openstackclient/tests/image/v1/test_image.py @@ -475,8 +475,8 @@ def test_image_set_no_options(self): result = self.cmd.take_action(parsed_args) - # Verify update() was not called, if it was show the args - self.assertEqual(self.images_mock.update.call_args_list, []) + self.images_mock.update.assert_called_with(image_fakes.image_id, + **{}) self.assertIsNone(result) def test_image_set_options(self): diff --git a/releasenotes/notes/bug-1588588-39927ef06ca35730.yaml b/releasenotes/notes/bug-1588588-39927ef06ca35730.yaml index c745b6bef2..8621a81cb2 100644 --- a/releasenotes/notes/bug-1588588-39927ef06ca35730.yaml +++ b/releasenotes/notes/bug-1588588-39927ef06ca35730.yaml @@ -1,6 +1,6 @@ --- upgrade: - - All ``set`` and ``unset`` commands in network, identity, image, and volume now - return normally when nothing specified to modify. This will become the default - behavior of OSC ``set`` and ``unset`` commands. + - All ``set`` and ``unset`` commands now return normally when nothing + specified to modify. This will become the default behavior of OSC + ``set`` and ``unset`` commands. [Bug `1588588 `_] From 2d7a02f2ffd36e880a1e61394bd8bfce5a6b9f23 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Thu, 16 Jun 2016 19:44:57 +0800 Subject: [PATCH 1025/3095] Fix a missing i18n support in security_group_rule.py Change-Id: Id1459366ccf894275a11c2af840568d4fd114e18 --- openstackclient/network/v2/security_group_rule.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index 18863223ae..e3be44ece7 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -526,8 +526,8 @@ def take_action_compute(self, client, parsed_args): break if obj is None: - msg = _("Could not find security group rule with ID ") + \ - parsed_args.rule + msg = _("Could not find security group rule " + "with ID '%s'") % parsed_args.rule raise exceptions.CommandError(msg) # NOTE(rtheis): Format security group rule From 014835930d50130a2efca0786920876efc60a8eb Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Wed, 15 Jun 2016 13:34:15 +0800 Subject: [PATCH 1026/3095] Add "--project" option to the "flavor create" command. Add ``--project`` and ``--project-domain`` options to the ``flavor create`` command. We can use these options to add the flavor access to a givin project when we create the flavor. Change-Id: Ic1907272c1d1ae526f9c9e86f32ba06c6da147c0 --- doc/source/command-objects/flavor.rst | 12 ++++++++ openstackclient/compute/v2/flavor.py | 26 +++++++++++++++++ .../tests/compute/v2/test_flavor.py | 28 ++++++++++++++++++- ...-create-with-project-19d41bfa93e3c6d0.yaml | 5 ++++ 4 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/flavor-create-with-project-19d41bfa93e3c6d0.yaml diff --git a/doc/source/command-objects/flavor.rst b/doc/source/command-objects/flavor.rst index a30bedecb6..11efaa6f02 100644 --- a/doc/source/command-objects/flavor.rst +++ b/doc/source/command-objects/flavor.rst @@ -21,6 +21,8 @@ Create new flavor [--vcpus ] [--rxtx-factor ] [--public | --private] + [--project ] + [--project-domain ] .. option:: --id @@ -59,6 +61,16 @@ Create new flavor Flavor is not available to other projects +.. option:: --project + + Allow to access private flavor (name or ID) + (Must be used with :option:`--private` option) + +.. option:: --project-domain + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + .. _flavor_create-flavor-name: .. describe:: diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 0a0d25c2de..49cc4e367b 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -121,10 +121,22 @@ def get_parser(self, prog_name): action="store_false", help=_("Flavor is not available to other projects") ) + parser.add_argument( + '--project', + metavar='', + help=_("Allow to access private flavor (name or ID) " + "(Must be used with --private option)"), + ) + identity_common.add_project_domain_option_to_parser(parser) return parser def take_action(self, parsed_args): compute_client = self.app.client_manager.compute + identity_client = self.app.client_manager.identity + + if parsed_args.project and parsed_args.public: + msg = _("--project is only allowed with --private") + raise exceptions.CommandError(msg) args = ( parsed_args.name, @@ -141,6 +153,20 @@ def take_action(self, parsed_args): flavor = compute_client.flavors.create(*args)._info.copy() flavor.pop("links") + if parsed_args.project: + try: + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + compute_client.flavor_access.add_tenant_access( + parsed_args.id, project_id) + except Exception as e: + msg = _("Failed to add project %(project)s access to " + "flavor: %(e)s") + LOG.error(msg % {'project': parsed_args.project, 'e': e}) + return zip(*sorted(six.iteritems(flavor))) diff --git a/openstackclient/tests/compute/v2/test_flavor.py b/openstackclient/tests/compute/v2/test_flavor.py index 4365a540bc..c25d243133 100644 --- a/openstackclient/tests/compute/v2/test_flavor.py +++ b/openstackclient/tests/compute/v2/test_flavor.py @@ -75,6 +75,12 @@ class TestFlavorCreate(TestFlavor): def setUp(self): super(TestFlavorCreate, self).setUp() + # Return a project + self.projects_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.PROJECT), + loaded=True, + ) self.flavors_mock.create.return_value = self.flavor self.cmd = flavor.CreateFlavor(self.app, None) @@ -161,6 +167,7 @@ def test_flavor_create_other_options(self): '--vcpus', str(self.flavor.vcpus), '--rxtx-factor', str(self.flavor.rxtx_factor), '--private', + '--project', identity_fakes.project_id, ] verifylist = [ ('name', self.flavor.name), @@ -172,6 +179,7 @@ def test_flavor_create_other_options(self): ('vcpus', self.flavor.vcpus), ('rxtx_factor', self.flavor.rxtx_factor), ('public', False), + ('project', identity_fakes.project_id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -188,10 +196,28 @@ def test_flavor_create_other_options(self): ) columns, data = self.cmd.take_action(parsed_args) self.flavors_mock.create.assert_called_once_with(*args) - + self.flavor_access_mock.add_tenant_access.assert_called_with( + self.flavor.id, + identity_fakes.project_id, + ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_public_flavor_create_with_project(self): + arglist = [ + '--project', identity_fakes.project_id, + self.flavor.name, + ] + verifylist = [ + ('project', identity_fakes.project_id), + ('name', self.flavor.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, + parsed_args) + def test_flavor_create_no_options(self): arglist = [] verifylist = None diff --git a/releasenotes/notes/flavor-create-with-project-19d41bfa93e3c6d0.yaml b/releasenotes/notes/flavor-create-with-project-19d41bfa93e3c6d0.yaml new file mode 100644 index 0000000000..312dece847 --- /dev/null +++ b/releasenotes/notes/flavor-create-with-project-19d41bfa93e3c6d0.yaml @@ -0,0 +1,5 @@ +--- +features: + - Add ``--project`` and ``--project-domain`` options to the ``flavor create`` + command. We can use these options to add the flavor access to a given project + when we create the flavor. From f6a188c3e13bbd6092e7d6a06b98f225ec6c4f07 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Tue, 21 Jun 2016 18:05:37 +0000 Subject: [PATCH 1027/3095] Updated from global requirements Change-Id: I99fc95e55e3a8030ed9175658895335beef059c3 --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 50b3806881..cca81ec8ff 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -11,7 +11,7 @@ oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 reno>=1.6.2 # Apache2 requests-mock>=0.7.0 # Apache-2.0 -sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 # BSD +sphinx!=1.3b1,<1.3,>=1.2.1 # BSD os-testr>=0.7.0 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD testtools>=1.4.0 # MIT From cefe715031cc4b1b79a8a68fdb2b93d94fb128f0 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Tue, 21 Jun 2016 14:54:27 -0500 Subject: [PATCH 1028/3095] Fix token/endpoint auth plugin [This is not quite reduced from the original proposed fix as some changes have merged that complicate the switch to OSC_Config and v2 auth broke anyway.] Fix the --os-token --os-url breakage in the switch to ksa. Closes-bug: 1593664 Change-Id: I3ac23234fbf647fb145c7bd151d53c5c105462bf --- openstackclient/api/auth_plugin.py | 55 ++++++++++++++++-------------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/openstackclient/api/auth_plugin.py b/openstackclient/api/auth_plugin.py index 4434bc8ffc..56dc4de5d4 100644 --- a/openstackclient/api/auth_plugin.py +++ b/openstackclient/api/auth_plugin.py @@ -15,50 +15,55 @@ import logging -from oslo_config import cfg -from six.moves.urllib import parse as urlparse - -from keystoneauth1.loading._plugins import admin_token as token_endpoint +from keystoneauth1 import loading from keystoneauth1.loading._plugins.identity import generic as ksa_password +from keystoneauth1 import token_endpoint +from six.moves.urllib import parse as urlparse from openstackclient.i18n import _ LOG = logging.getLogger(__name__) -class TokenEndpoint(token_endpoint.AdminToken): +class TokenEndpoint(loading.BaseLoader): """Auth plugin to handle traditional token/endpoint usage - Implements the methods required to handle token authentication - with a user-specified token and service endpoint; no Identity calls - are made for re-scoping, service catalog lookups or the like. - - The purpose of this plugin is to get rid of the special-case paths - in the code to handle this authentication format. Its primary use - is for bootstrapping the Keystone database. + Keystoneauth contains a Token plugin class that now correctly + handles the token/endpoint auth compatible with OSC. However, + the AdminToken loader deprecates the 'url' argument, which breaks + OSC compatibility, so make one that works. """ + @property + def plugin_class(self): + return token_endpoint.Token + def load_from_options(self, url, token, **kwargs): """A plugin for static authentication with an existing token :param string url: Service endpoint :param string token: Existing token """ - return super(TokenEndpoint, self).load_from_options(endpoint=url, - token=token) - def get_options(self): - options = super(TokenEndpoint, self).get_options() - - options.extend([ - # Maintain name 'url' for compatibility - cfg.StrOpt('url', - help=_('Specific service endpoint to use')), - cfg.StrOpt('token', - secret=True, - help=_('Authentication token to use')), - ]) + return super(TokenEndpoint, self).load_from_options( + endpoint=url, + token=token, + ) + def get_options(self): + """Return the legacy options""" + + options = [ + loading.Opt( + 'url', + help=_('Specific service endpoint to use'), + ), + loading.Opt( + 'token', + secret=True, + help=_('Authentication token to use'), + ), + ] return options From 7177014fcc143eb98ffc7fdeafc23eade49c1bb8 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Mon, 20 Jun 2016 19:40:26 +0800 Subject: [PATCH 1029/3095] Fix errors for "host set" command "Host set" command cannot work. Because: 1.Host has no 'ID' attribute, so 'ID' attribute cannot be found in "host set" command. 2.value "True" and "Flase" are invalid in updata() method of host. 3.Some update functionalities is not supported in host API now. This patch solves the problems 1 and 2 in OSC. But the problem 3 is a API problem and can't be solved in OSC, only XenServer driver support to set enable/disable and maintenance host, it is a normal problem. After this patch the output of "host set" command is: The requested functionality is not supported. (HTTP 501) (Request-ID: req-14031fce-8c90-48a0-8492-dc8e3dd349f3) Just the same as the "host-update" command in novaclient. Change-Id: Ibe94c4d3d492d3d63355de803810edb988e1b4e9 Closes-Bug: #1594689 --- doc/source/command-objects/host.rst | 2 +- openstackclient/compute/v2/host.py | 22 ++++++++++--------- openstackclient/tests/compute/v2/fakes.py | 1 - openstackclient/tests/compute/v2/test_host.py | 14 ++++++------ 4 files changed, 20 insertions(+), 19 deletions(-) diff --git a/doc/source/command-objects/host.rst b/doc/source/command-objects/host.rst index ede62c562e..8c34d3fb97 100644 --- a/doc/source/command-objects/host.rst +++ b/doc/source/command-objects/host.rst @@ -54,7 +54,7 @@ Set host command .. describe:: - The host (name or ID) + Host to modify (name only) host show --------- diff --git a/openstackclient/compute/v2/host.py b/openstackclient/compute/v2/host.py index 764b22af0b..7856758933 100644 --- a/openstackclient/compute/v2/host.py +++ b/openstackclient/compute/v2/host.py @@ -54,7 +54,7 @@ def get_parser(self, prog_name): parser.add_argument( "host", metavar="", - help=_("The host to modify (name or ID)") + help=_("Host to modify (name only)") ) status = parser.add_mutually_exclusive_group() status.add_argument( @@ -84,22 +84,24 @@ def take_action(self, parsed_args): kwargs = {} if parsed_args.enable: - kwargs['status'] = True + kwargs['status'] = 'enable' if parsed_args.disable: - kwargs['status'] = False + kwargs['status'] = 'disable' if parsed_args.enable_maintenance: - kwargs['maintenance_mode'] = True + kwargs['maintenance_mode'] = 'enable' if parsed_args.disable_maintenance: - kwargs['maintenance_mode'] = False + kwargs['maintenance_mode'] = 'disable' compute_client = self.app.client_manager.compute - foundhost = utils.find_resource( - compute_client.hosts, - parsed_args.host - ) + + # More than one hosts will be returned by using find_resource() + # so that the return value cannot be used in host update() method. + # find_resource() is just used for checking existence of host and + # keeping the exception message consistent with other commands. + utils.find_resource(compute_client.hosts, parsed_args.host) compute_client.hosts.update( - foundhost.id, + parsed_args.host, kwargs ) diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 60abb8ef1e..97d0c96f0c 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -1022,7 +1022,6 @@ def create_one_host(attrs=None): # Set default attributes. host_info = { - "id": 1, "service_id": 1, "host": "host1", "uuid": 'host-id-' + uuid.uuid4().hex, diff --git a/openstackclient/tests/compute/v2/test_host.py b/openstackclient/tests/compute/v2/test_host.py index de5375771f..63ae1f6dbb 100644 --- a/openstackclient/tests/compute/v2/test_host.py +++ b/openstackclient/tests/compute/v2/test_host.py @@ -40,10 +40,10 @@ def setUp(self): def test_host_set_no_option(self): arglist = [ - str(self.host.id) + self.host.host ] verifylist = [ - ('host', str(self.host.id)) + ('host', self.host.host) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -52,18 +52,18 @@ def test_host_set_no_option(self): self.assertIsNone(result) body = {} - self.host_mock.update.assert_called_with(self.host.id, body) + self.host_mock.update.assert_called_with(self.host.host, body) def test_host_set(self): arglist = [ '--enable', '--disable-maintenance', - str(self.host.id) + self.host.host ] verifylist = [ ('enable', True), ('enable_maintenance', False), - ('host', str(self.host.id)) + ('host', self.host.host) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -71,5 +71,5 @@ def test_host_set(self): result = self.cmd.take_action(parsed_args) self.assertIsNone(result) - body = {'status': True, 'maintenance_mode': False} - self.host_mock.update.assert_called_with(self.host.id, body) + body = {'status': 'enable', 'maintenance_mode': 'disable'} + self.host_mock.update.assert_called_with(self.host.host, body) From 000c253d760fd978355c4f25d8f16db280af9b6c Mon Sep 17 00:00:00 2001 From: sunyajing Date: Wed, 22 Jun 2016 14:54:23 +0800 Subject: [PATCH 1030/3095] Add FakeObject classes to fakes.py, update unit tests in identity V2. Clean up fakes.py , use FakeCatalog, FakeProject, FakeService, FakeEndpoint, FakeRole classes instead, also update their unit tests. Change-Id: I510d175ec194165b0595ebd430e8cc596d363587 Partially-Implements: blueprint refactor-identity-unit-test --- openstackclient/tests/identity/v2_0/fakes.py | 248 +++++++++++++++++- .../tests/identity/v2_0/test_catalog.py | 39 +-- .../tests/identity/v2_0/test_endpoint.py | 154 +++++------ .../tests/identity/v2_0/test_project.py | 234 +++++++---------- .../tests/identity/v2_0/test_role.py | 22 +- 5 files changed, 422 insertions(+), 275 deletions(-) diff --git a/openstackclient/tests/identity/v2_0/fakes.py b/openstackclient/tests/identity/v2_0/fakes.py index c613ad82a3..10b8b49ed9 100644 --- a/openstackclient/tests/identity/v2_0/fakes.py +++ b/openstackclient/tests/identity/v2_0/fakes.py @@ -110,6 +110,8 @@ 'publicurl': endpoint_publicurl, 'service_id': endpoint_service_id, } +SERVICE_NAME = 'service-name-' + uuid.uuid4().hex +SERVICE_ID = 'service-id-' + uuid.uuid4().hex def fake_auth_ref(fake_token, fake_service=None): @@ -123,12 +125,12 @@ def fake_auth_ref(fake_token, fake_service=None): # Create a service catalog if fake_service: service = token.add_service( - fake_service['type'], - fake_service['name'], + fake_service.type, + fake_service.name, ) # TODO(dtroyer): Add an 'id' element to KSA's _Service fixure - service['id'] = fake_service['id'] - for e in fake_service['endpoints']: + service['id'] = fake_service.id + for e in fake_service.endpoints: # KSA's _Service fixture copies publicURL to internalURL and # adminURL if they do not exist. Soooo helpful... internal = e.get('internalURL', None) @@ -224,3 +226,241 @@ def create_one_extension(attrs=None): info=copy.deepcopy(extension_info), loaded=True) return extension + + +class FakeCatalog(object): + """Fake one or more catalog.""" + + @staticmethod + def create_catalog(attrs=None): + """Create a fake catalog. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object with id, name, type and so on. + """ + attrs = attrs or {} + + # Set default attributes. + catalog_info = { + 'id': SERVICE_ID, + 'type': 'compute', + 'name': 'supernova', + 'endpoints': [ + { + 'region': 'one', + 'publicURL': 'https://public.one.example.com', + 'internalURL': 'https://internal.one.example.com', + 'adminURL': 'https://admin.one.example.com', + }, + { + 'region': 'two', + 'publicURL': 'https://public.two.example.com', + 'internalURL': 'https://internal.two.example.com', + 'adminURL': 'https://admin.two.example.com', + }, + { + 'region': None, + 'publicURL': 'https://public.none.example.com', + 'internalURL': 'https://internal.none.example.com', + 'adminURL': 'https://admin.none.example.com', + }, + ], + } + # Overwrite default attributes. + catalog_info.update(attrs) + + catalog = fakes.FakeResource( + info=copy.deepcopy(catalog_info), + loaded=True) + + return catalog + + +class FakeProject(object): + """Fake one or more project.""" + + @staticmethod + def create_one_project(attrs=None): + """Create a fake project. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object, with id, name, and so on + """ + + attrs = attrs or {} + + # set default attributes. + project_info = { + 'id': 'project-id' + uuid.uuid4().hex, + 'name': 'project-name' + uuid.uuid4().hex, + 'description': 'project_description', + 'enabled': True, + } + project_info.update(attrs) + + project = fakes.FakeResource(info=copy.deepcopy(project_info), + loaded=True) + return project + + @staticmethod + def create_projects(attrs=None, count=2): + """Create multiple fake projects. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of projects to fake + :return: + A list of FakeResource objects faking the projects + """ + projects = [] + for i in range(0, count): + projects.append(FakeProject.create_one_project(attrs)) + + return projects + + +class FakeEndpoint(object): + """Fake one or more endpoint.""" + + @staticmethod + def create_one_endpoint(attrs=None): + """Create a fake agent. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object, with id, name, region, and so on + """ + + attrs = attrs or {} + + # set default attributes. + endpoint_info = { + 'service_name': SERVICE_NAME, + 'adminurl': 'http://endpoint_adminurl', + 'region': 'endpoint_region', + 'internalurl': 'http://endpoint_internalurl', + 'service_type': 'service_type', + 'id': 'endpoint-id-' + uuid.uuid4().hex, + 'publicurl': 'http://endpoint_publicurl', + 'service_id': SERVICE_ID, + + } + endpoint_info.update(attrs) + + endpoint = fakes.FakeResource(info=copy.deepcopy(endpoint_info), + loaded=True) + return endpoint + + @staticmethod + def create_endpoints(attrs=None, count=2): + """Create multiple fake endpoints. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of endpoints to fake + :return: + A list of FakeResource objects faking the endpoints + """ + endpoints = [] + for i in range(0, count): + endpoints.append(FakeEndpoint.create_one_endpoint(attrs)) + + return endpoints + + +class FakeService(object): + """Fake one or more service.""" + + @staticmethod + def create_one_service(attrs=None): + """Create a fake service. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object, with id, name, type, and so on + """ + + attrs = attrs or {} + + # set default attributes. + service_info = { + 'id': SERVICE_ID, + 'name': SERVICE_NAME, + 'description': 'service_description', + 'type': 'service_type', + + } + service_info.update(attrs) + + service = fakes.FakeResource(info=copy.deepcopy(service_info), + loaded=True) + return service + + @staticmethod + def create_services(attrs=None, count=2): + """Create multiple fake services. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of services to fake + :return: + A list of FakeResource objects faking the services + """ + services = [] + for i in range(0, count): + services.append(FakeService.create_one_service(attrs)) + + return services + + +class FakeRole(object): + """Fake one or more role.""" + + @staticmethod + def create_one_role(attrs=None): + """Create a fake role. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object, with id, name, and so on + """ + + attrs = attrs or {} + + # set default attributes. + role_info = { + 'id': 'role-id' + uuid.uuid4().hex, + 'name': 'role-name' + uuid.uuid4().hex, + } + role_info.update(attrs) + + role = fakes.FakeResource(info=copy.deepcopy(role_info), + loaded=True) + return role + + @staticmethod + def create_roles(attrs=None, count=2): + """Create multiple fake roles. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of roles to fake + :return: + A list of FakeResource objects faking the roles + """ + roles = [] + for i in range(0, count): + roles.append(FakeRole.create_one_role(attrs)) + + return roles diff --git a/openstackclient/tests/identity/v2_0/test_catalog.py b/openstackclient/tests/identity/v2_0/test_catalog.py index 2fdbafbea2..487d8f3161 100644 --- a/openstackclient/tests/identity/v2_0/test_catalog.py +++ b/openstackclient/tests/identity/v2_0/test_catalog.py @@ -20,38 +20,14 @@ class TestCatalog(utils.TestCommand): - fake_service = { - 'id': 'qwertyuiop', - 'type': 'compute', - 'name': 'supernova', - 'endpoints': [ - { - 'region': 'one', - 'publicURL': 'https://public.one.example.com', - 'internalURL': 'https://internal.one.example.com', - 'adminURL': 'https://admin.one.example.com', - }, - { - 'region': 'two', - 'publicURL': 'https://public.two.example.com', - 'internalURL': 'https://internal.two.example.com', - 'adminURL': 'https://admin.two.example.com', - }, - { - 'region': None, - 'publicURL': 'https://public.none.example.com', - 'internalURL': 'https://internal.none.example.com', - 'adminURL': 'https://admin.none.example.com', - }, - ], - } + service_catalog = identity_fakes.FakeCatalog.create_catalog() def setUp(self): super(TestCatalog, self).setUp() self.sc_mock = mock.MagicMock() self.sc_mock.service_catalog.catalog.return_value = [ - self.fake_service, + self.service_catalog, ] self.auth_mock = mock.MagicMock() @@ -77,7 +53,7 @@ def setUp(self): def test_catalog_list(self): auth_ref = identity_fakes.fake_auth_ref( identity_fakes.TOKEN, - fake_service=self.fake_service, + fake_service=self.service_catalog, ) self.ar_mock = mock.PropertyMock(return_value=auth_ref) type(self.app.client_manager).auth_ref = self.ar_mock @@ -108,7 +84,7 @@ def test_catalog_list(self): self.assertEqual(datalist, tuple(data)) def test_catalog_list_with_endpoint_url(self): - fake_service = { + attr = { 'id': 'qwertyuiop', 'type': 'compute', 'name': 'supernova', @@ -124,9 +100,10 @@ def test_catalog_list_with_endpoint_url(self): }, ], } + service_catalog = identity_fakes.FakeCatalog.create_catalog(attr) auth_ref = identity_fakes.fake_auth_ref( identity_fakes.TOKEN, - fake_service=fake_service, + fake_service=service_catalog, ) self.ar_mock = mock.PropertyMock(return_value=auth_ref) type(self.app.client_manager).auth_ref = self.ar_mock @@ -162,7 +139,7 @@ def setUp(self): def test_catalog_show(self): auth_ref = identity_fakes.fake_auth_ref( identity_fakes.UNSCOPED_TOKEN, - fake_service=self.fake_service, + fake_service=self.service_catalog, ) self.ar_mock = mock.PropertyMock(return_value=auth_ref) type(self.app.client_manager).auth_ref = self.ar_mock @@ -192,7 +169,7 @@ def test_catalog_show(self): '\n publicURL: https://public.none.example.com\n ' 'internalURL: https://internal.none.example.com\n ' 'adminURL: https://admin.none.example.com\n', - 'qwertyuiop', + self.service_catalog.id, 'supernova', 'compute', ) diff --git a/openstackclient/tests/identity/v2_0/test_endpoint.py b/openstackclient/tests/identity/v2_0/test_endpoint.py index 45ece45af9..e8fb926290 100644 --- a/openstackclient/tests/identity/v2_0/test_endpoint.py +++ b/openstackclient/tests/identity/v2_0/test_endpoint.py @@ -11,15 +11,15 @@ # under the License. # -import copy - from openstackclient.identity.v2_0 import endpoint -from openstackclient.tests import fakes from openstackclient.tests.identity.v2_0 import fakes as identity_fakes class TestEndpoint(identity_fakes.TestIdentityv2): + fake_endpoint = identity_fakes.FakeEndpoint.create_one_endpoint() + fake_service = identity_fakes.FakeService.create_one_service() + def setUp(self): super(TestEndpoint, self).setUp() @@ -37,35 +37,27 @@ class TestEndpointCreate(TestEndpoint): def setUp(self): super(TestEndpointCreate, self).setUp() - self.endpoints_mock.create.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.ENDPOINT), - loaded=True, - ) + self.endpoints_mock.create.return_value = self.fake_endpoint - self.services_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.SERVICE), - loaded=True, - ) + self.services_mock.get.return_value = self.fake_service # Get the command object to test self.cmd = endpoint.CreateEndpoint(self.app, None) def test_endpoint_create(self): arglist = [ - '--publicurl', identity_fakes.endpoint_publicurl, - '--internalurl', identity_fakes.endpoint_internalurl, - '--adminurl', identity_fakes.endpoint_adminurl, - '--region', identity_fakes.endpoint_region, - identity_fakes.endpoint_name, + '--publicurl', self.fake_endpoint.publicurl, + '--internalurl', self.fake_endpoint.internalurl, + '--adminurl', self.fake_endpoint.adminurl, + '--region', self.fake_endpoint.region, + self.fake_service.id, ] verifylist = [ - ('adminurl', identity_fakes.endpoint_adminurl), - ('internalurl', identity_fakes.endpoint_internalurl), - ('publicurl', identity_fakes.endpoint_publicurl), - ('region', identity_fakes.endpoint_region), - ('service', identity_fakes.service_name), + ('adminurl', self.fake_endpoint.adminurl), + ('internalurl', self.fake_endpoint.internalurl), + ('publicurl', self.fake_endpoint.publicurl), + ('region', self.fake_endpoint.region), + ('service', self.fake_service.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -77,25 +69,25 @@ def test_endpoint_create(self): # EndpointManager.create(region, service_id, publicurl, adminurl, # internalurl) self.endpoints_mock.create.assert_called_with( - identity_fakes.endpoint_region, - identity_fakes.service_id, - identity_fakes.endpoint_publicurl, - identity_fakes.endpoint_adminurl, - identity_fakes.endpoint_internalurl, + self.fake_endpoint.region, + self.fake_service.id, + self.fake_endpoint.publicurl, + self.fake_endpoint.adminurl, + self.fake_endpoint.internalurl, ) collist = ('adminurl', 'id', 'internalurl', 'publicurl', 'region', 'service_id', 'service_name', 'service_type') self.assertEqual(collist, columns) datalist = ( - identity_fakes.endpoint_adminurl, - identity_fakes.endpoint_id, - identity_fakes.endpoint_internalurl, - identity_fakes.endpoint_publicurl, - identity_fakes.endpoint_region, - identity_fakes.service_id, - identity_fakes.service_name, - identity_fakes.service_type, + self.fake_endpoint.adminurl, + self.fake_endpoint.id, + self.fake_endpoint.internalurl, + self.fake_endpoint.publicurl, + self.fake_endpoint.region, + self.fake_endpoint.service_id, + self.fake_endpoint.service_name, + self.fake_endpoint.service_type, ) self.assertEqual(datalist, data) @@ -106,17 +98,9 @@ class TestEndpointDelete(TestEndpoint): def setUp(self): super(TestEndpointDelete, self).setUp() - self.endpoints_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.ENDPOINT), - loaded=True, - ) + self.endpoints_mock.get.return_value = self.fake_endpoint - self.services_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.SERVICE), - loaded=True, - ) + self.services_mock.get.return_value = self.fake_service self.endpoints_mock.delete.return_value = None @@ -125,17 +109,17 @@ def setUp(self): def test_endpoint_delete_no_options(self): arglist = [ - identity_fakes.endpoint_id, + self.fake_endpoint.id, ] verifylist = [ - ('endpoint', identity_fakes.endpoint_id), + ('endpoint', self.fake_endpoint.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.endpoints_mock.delete.assert_called_with( - identity_fakes.endpoint_id, + self.fake_endpoint.id, ) self.assertIsNone(result) @@ -145,19 +129,9 @@ class TestEndpointList(TestEndpoint): def setUp(self): super(TestEndpointList, self).setUp() - self.endpoints_mock.list.return_value = [ - fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.ENDPOINT), - loaded=True, - ), - ] + self.endpoints_mock.list.return_value = [self.fake_endpoint] - self.services_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.SERVICE), - loaded=True, - ) + self.services_mock.get.return_value = self.fake_service # Get the command object to test self.cmd = endpoint.ListEndpoint(self.app, None) @@ -177,10 +151,10 @@ def test_endpoint_list_no_options(self): collist = ('ID', 'Region', 'Service Name', 'Service Type') self.assertEqual(collist, columns) datalist = (( - identity_fakes.endpoint_id, - identity_fakes.endpoint_region, - identity_fakes.service_name, - identity_fakes.service_type, + self.fake_endpoint.id, + self.fake_endpoint.region, + self.fake_endpoint.service_name, + self.fake_endpoint.service_type, ), ) self.assertEqual(datalist, tuple(data)) @@ -204,13 +178,13 @@ def test_endpoint_list_long(self): 'PublicURL', 'AdminURL', 'InternalURL') self.assertEqual(collist, columns) datalist = (( - identity_fakes.endpoint_id, - identity_fakes.endpoint_region, - identity_fakes.service_name, - identity_fakes.service_type, - identity_fakes.endpoint_publicurl, - identity_fakes.endpoint_adminurl, - identity_fakes.endpoint_internalurl, + self.fake_endpoint.id, + self.fake_endpoint.region, + self.fake_endpoint.service_name, + self.fake_endpoint.service_type, + self.fake_endpoint.publicurl, + self.fake_endpoint.adminurl, + self.fake_endpoint.internalurl, ), ) self.assertEqual(datalist, tuple(data)) @@ -220,29 +194,19 @@ class TestEndpointShow(TestEndpoint): def setUp(self): super(TestEndpointShow, self).setUp() - self.endpoints_mock.list.return_value = [ - fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.ENDPOINT), - loaded=True, - ), - ] + self.endpoints_mock.list.return_value = [self.fake_endpoint] - self.services_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.SERVICE), - loaded=True, - ) + self.services_mock.get.return_value = self.fake_service # Get the command object to test self.cmd = endpoint.ShowEndpoint(self.app, None) def test_endpoint_show(self): arglist = [ - identity_fakes.endpoint_name, + self.fake_endpoint.id, ] verifylist = [ - ('endpoint_or_service', identity_fakes.endpoint_name), + ('endpoint_or_service', self.fake_endpoint.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -255,20 +219,20 @@ def test_endpoint_show(self): self.endpoints_mock.list.assert_called_with() # ServiceManager.get(name) self.services_mock.get.assert_called_with( - identity_fakes.service_name, + self.fake_endpoint.service_id, ) collist = ('adminurl', 'id', 'internalurl', 'publicurl', 'region', 'service_id', 'service_name', 'service_type') self.assertEqual(collist, columns) datalist = ( - identity_fakes.endpoint_adminurl, - identity_fakes.endpoint_id, - identity_fakes.endpoint_internalurl, - identity_fakes.endpoint_publicurl, - identity_fakes.endpoint_region, - identity_fakes.service_id, - identity_fakes.service_name, - identity_fakes.service_type, + self.fake_endpoint.adminurl, + self.fake_endpoint.id, + self.fake_endpoint.internalurl, + self.fake_endpoint.publicurl, + self.fake_endpoint.region, + self.fake_endpoint.service_id, + self.fake_endpoint.service_name, + self.fake_endpoint.service_type, ) self.assertEqual(datalist, data) diff --git a/openstackclient/tests/identity/v2_0/test_project.py b/openstackclient/tests/identity/v2_0/test_project.py index 1eb1260419..96731c0c3e 100644 --- a/openstackclient/tests/identity/v2_0/test_project.py +++ b/openstackclient/tests/identity/v2_0/test_project.py @@ -13,27 +13,16 @@ # under the License. # -import copy - from keystoneauth1 import exceptions as ks_exc from osc_lib import exceptions from openstackclient.identity.v2_0 import project -from openstackclient.tests import fakes from openstackclient.tests.identity.v2_0 import fakes as identity_fakes class TestProject(identity_fakes.TestIdentityv2): - def setUp(self): - super(TestProject, self).setUp() - - # Get a shortcut to the TenantManager Mock - self.projects_mock = self.app.client_manager.identity.tenants - self.projects_mock.reset_mock() - - -class TestProjectCreate(TestProject): + fake_project = identity_fakes.FakeProject.create_one_project() columns = ( 'description', @@ -42,32 +31,38 @@ class TestProjectCreate(TestProject): 'name', ) datalist = ( - identity_fakes.project_description, + fake_project.description, True, - identity_fakes.project_id, - identity_fakes.project_name, + fake_project.id, + fake_project.name, ) + def setUp(self): + super(TestProject, self).setUp() + + # Get a shortcut to the TenantManager Mock + self.projects_mock = self.app.client_manager.identity.tenants + self.projects_mock.reset_mock() + + +class TestProjectCreate(TestProject): + def setUp(self): super(TestProjectCreate, self).setUp() - self.projects_mock.create.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) + self.projects_mock.create.return_value = self.fake_project # Get the command object to test self.cmd = project.CreateProject(self.app, None) def test_project_create_no_options(self): arglist = [ - identity_fakes.project_name, + self.fake_project.name, ] verifylist = [ ('enable', False), ('disable', False), - ('name', identity_fakes.project_name), + ('name', self.fake_project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -82,7 +77,7 @@ def test_project_create_no_options(self): 'enabled': True, } self.projects_mock.create.assert_called_with( - identity_fakes.project_name, + self.fake_project.name, **kwargs ) self.assertEqual(self.columns, columns) @@ -91,11 +86,11 @@ def test_project_create_no_options(self): def test_project_create_description(self): arglist = [ '--description', 'new desc', - identity_fakes.project_name, + self.fake_project.name, ] verifylist = [ ('description', 'new desc'), - ('name', identity_fakes.project_name), + ('name', self.fake_project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -110,7 +105,7 @@ def test_project_create_description(self): 'enabled': True, } self.projects_mock.create.assert_called_with( - identity_fakes.project_name, + self.fake_project.name, **kwargs ) @@ -120,12 +115,12 @@ def test_project_create_description(self): def test_project_create_enable(self): arglist = [ '--enable', - identity_fakes.project_name, + self.fake_project.name, ] verifylist = [ ('enable', True), ('disable', False), - ('name', identity_fakes.project_name), + ('name', self.fake_project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -140,7 +135,7 @@ def test_project_create_enable(self): 'enabled': True, } self.projects_mock.create.assert_called_with( - identity_fakes.project_name, + self.fake_project.name, **kwargs ) @@ -150,12 +145,12 @@ def test_project_create_enable(self): def test_project_create_disable(self): arglist = [ '--disable', - identity_fakes.project_name, + self.fake_project.name, ] verifylist = [ ('enable', False), ('disable', True), - ('name', identity_fakes.project_name), + ('name', self.fake_project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -170,7 +165,7 @@ def test_project_create_disable(self): 'enabled': False, } self.projects_mock.create.assert_called_with( - identity_fakes.project_name, + self.fake_project.name, **kwargs ) @@ -181,11 +176,11 @@ def test_project_create_property(self): arglist = [ '--property', 'fee=fi', '--property', 'fo=fum', - identity_fakes.project_name, + self.fake_project.name, ] verifylist = [ ('property', {'fee': 'fi', 'fo': 'fum'}), - ('name', identity_fakes.project_name), + ('name', self.fake_project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -202,7 +197,7 @@ def test_project_create_property(self): 'fo': 'fum', } self.projects_mock.create.assert_called_with( - identity_fakes.project_name, + self.fake_project.name, **kwargs ) @@ -216,19 +211,15 @@ def _raise_conflict(*args, **kwargs): # need to make this throw an exception... self.projects_mock.create.side_effect = _raise_conflict - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) + self.projects_mock.get.return_value = self.fake_project arglist = [ '--or-show', - identity_fakes.project_name, + self.fake_project.name, ] verifylist = [ - ('name', identity_fakes.project_name), ('or_show', True), + ('name', self.fake_project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -238,7 +229,7 @@ def _raise_conflict(*args, **kwargs): columns, data = self.cmd.take_action(parsed_args) # ProjectManager.create(name, description, enabled) - self.projects_mock.get.assert_called_with(identity_fakes.project_name) + self.projects_mock.get.assert_called_with(self.fake_project.name) # Set expected values kwargs = { @@ -246,7 +237,7 @@ def _raise_conflict(*args, **kwargs): 'enabled': True, } self.projects_mock.create.assert_called_with( - identity_fakes.project_name, + self.fake_project.name, **kwargs ) @@ -256,11 +247,11 @@ def _raise_conflict(*args, **kwargs): def test_project_create_or_show_not_exists(self): arglist = [ '--or-show', - identity_fakes.project_name, + self.fake_project.name, ] verifylist = [ - ('name', identity_fakes.project_name), ('or_show', True), + ('name', self.fake_project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -275,7 +266,7 @@ def test_project_create_or_show_not_exists(self): 'enabled': True, } self.projects_mock.create.assert_called_with( - identity_fakes.project_name, + self.fake_project.name, **kwargs ) @@ -289,11 +280,7 @@ def setUp(self): super(TestProjectDelete, self).setUp() # This is the return value for utils.find_resource() - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) + self.projects_mock.get.return_value = self.fake_project self.projects_mock.delete.return_value = None # Get the command object to test @@ -301,17 +288,17 @@ def setUp(self): def test_project_delete_no_options(self): arglist = [ - identity_fakes.project_id, + self.fake_project.id, ] verifylist = [ - ('projects', [identity_fakes.project_id]), + ('projects', [self.fake_project.id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.projects_mock.delete.assert_called_with( - identity_fakes.project_id, + self.fake_project.id, ) self.assertIsNone(result) @@ -321,13 +308,7 @@ class TestProjectList(TestProject): def setUp(self): super(TestProjectList, self).setUp() - self.projects_mock.list.return_value = [ - fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ), - ] + self.projects_mock.list.return_value = [self.fake_project] # Get the command object to test self.cmd = project.ListProject(self.app, None) @@ -346,8 +327,8 @@ def test_project_list_no_options(self): collist = ('ID', 'Name') self.assertEqual(collist, columns) datalist = (( - identity_fakes.project_id, - identity_fakes.project_name, + self.fake_project.id, + self.fake_project.name, ), ) self.assertEqual(datalist, tuple(data)) @@ -369,9 +350,9 @@ def test_project_list_long(self): collist = ('ID', 'Name', 'Description', 'Enabled') self.assertEqual(collist, columns) datalist = (( - identity_fakes.project_id, - identity_fakes.project_name, - identity_fakes.project_description, + self.fake_project.id, + self.fake_project.name, + self.fake_project.description, True, ), ) self.assertEqual(datalist, tuple(data)) @@ -382,26 +363,18 @@ class TestProjectSet(TestProject): def setUp(self): super(TestProjectSet, self).setUp() - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) - self.projects_mock.update.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) + self.projects_mock.get.return_value = self.fake_project + self.projects_mock.update.return_value = self.fake_project # Get the command object to test self.cmd = project.SetProject(self.app, None) def test_project_set_no_options(self): arglist = [ - identity_fakes.project_name, + self.fake_project.name, ] verifylist = [ - ('project', identity_fakes.project_name), + ('project', self.fake_project.name), ('enable', False), ('disable', False), ] @@ -433,14 +406,14 @@ def test_project_set_unexist_project(self): def test_project_set_name(self): arglist = [ - '--name', 'qwerty', - identity_fakes.project_name, + '--name', self.fake_project.name, + self.fake_project.name, ] verifylist = [ - ('name', 'qwerty'), + ('name', self.fake_project.name), ('enable', False), ('disable', False), - ('project', identity_fakes.project_name), + ('project', self.fake_project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -448,26 +421,26 @@ def test_project_set_name(self): # Set expected values kwargs = { - 'description': identity_fakes.project_description, + 'description': self.fake_project.description, 'enabled': True, - 'tenant_name': 'qwerty', + 'tenant_name': self.fake_project.name, } self.projects_mock.update.assert_called_with( - identity_fakes.project_id, + self.fake_project.id, **kwargs ) self.assertIsNone(result) def test_project_set_description(self): arglist = [ - '--description', 'new desc', - identity_fakes.project_name, + '--description', self.fake_project.description, + self.fake_project.name, ] verifylist = [ - ('description', 'new desc'), + ('description', self.fake_project.description), ('enable', False), ('disable', False), - ('project', identity_fakes.project_name), + ('project', self.fake_project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -475,12 +448,12 @@ def test_project_set_description(self): # Set expected values kwargs = { - 'description': 'new desc', + 'description': self.fake_project.description, 'enabled': True, - 'tenant_name': identity_fakes.project_name, + 'tenant_name': self.fake_project.name, } self.projects_mock.update.assert_called_with( - identity_fakes.project_id, + self.fake_project.id, **kwargs ) self.assertIsNone(result) @@ -488,12 +461,12 @@ def test_project_set_description(self): def test_project_set_enable(self): arglist = [ '--enable', - identity_fakes.project_name, + self.fake_project.name, ] verifylist = [ ('enable', True), ('disable', False), - ('project', identity_fakes.project_name), + ('project', self.fake_project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -501,12 +474,12 @@ def test_project_set_enable(self): # Set expected values kwargs = { - 'description': identity_fakes.project_description, + 'description': self.fake_project.description, 'enabled': True, - 'tenant_name': identity_fakes.project_name, + 'tenant_name': self.fake_project.name, } self.projects_mock.update.assert_called_with( - identity_fakes.project_id, + self.fake_project.id, **kwargs ) self.assertIsNone(result) @@ -514,12 +487,12 @@ def test_project_set_enable(self): def test_project_set_disable(self): arglist = [ '--disable', - identity_fakes.project_name, + self.fake_project.name, ] verifylist = [ ('enable', False), ('disable', True), - ('project', identity_fakes.project_name), + ('project', self.fake_project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -527,12 +500,12 @@ def test_project_set_disable(self): # Set expected values kwargs = { - 'description': identity_fakes.project_description, + 'description': self.fake_project.description, 'enabled': False, - 'tenant_name': identity_fakes.project_name, + 'tenant_name': self.fake_project.name, } self.projects_mock.update.assert_called_with( - identity_fakes.project_id, + self.fake_project.id, **kwargs ) self.assertIsNone(result) @@ -541,11 +514,11 @@ def test_project_set_property(self): arglist = [ '--property', 'fee=fi', '--property', 'fo=fum', - identity_fakes.project_name, + self.fake_project.name, ] verifylist = [ ('property', {'fee': 'fi', 'fo': 'fum'}), - ('project', identity_fakes.project_name), + ('project', self.fake_project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -553,14 +526,14 @@ def test_project_set_property(self): # Set expected values kwargs = { - 'description': identity_fakes.project_description, + 'description': self.fake_project.description, 'enabled': True, - 'tenant_name': identity_fakes.project_name, + 'tenant_name': self.fake_project.name, 'fee': 'fi', 'fo': 'fum', } self.projects_mock.update.assert_called_with( - identity_fakes.project_id, + self.fake_project.id, **kwargs ) self.assertIsNone(result) @@ -568,24 +541,22 @@ def test_project_set_property(self): class TestProjectShow(TestProject): + fake_proj_show = identity_fakes.FakeProject.create_one_project() + def setUp(self): super(TestProjectShow, self).setUp() - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) + self.projects_mock.get.return_value = self.fake_proj_show # Get the command object to test self.cmd = project.ShowProject(self.app, None) def test_project_show(self): arglist = [ - identity_fakes.project_id, + self.fake_proj_show.id, ] verifylist = [ - ('project', identity_fakes.project_id), + ('project', self.fake_proj_show.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -594,16 +565,16 @@ def test_project_show(self): # data to be shown. columns, data = self.cmd.take_action(parsed_args) self.projects_mock.get.assert_called_with( - identity_fakes.project_id, + self.fake_proj_show.id, ) collist = ('description', 'enabled', 'id', 'name', 'properties') self.assertEqual(collist, columns) datalist = ( - identity_fakes.project_description, + self.fake_proj_show.description, True, - identity_fakes.project_id, - identity_fakes.project_name, + self.fake_proj_show.id, + self.fake_proj_show.name, '', ) self.assertEqual(datalist, data) @@ -611,26 +582,23 @@ def test_project_show(self): class TestProjectUnset(TestProject): + attr = {'fee': 'fi', 'fo': 'fum'} + fake_proj = identity_fakes.FakeProject.create_one_project(attr) + def setUp(self): super(TestProjectUnset, self).setUp() - project_dict = {'fee': 'fi', 'fo': 'fum'} - project_dict.update(identity_fakes.PROJECT) - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(project_dict), - loaded=True, - ) + self.projects_mock.get.return_value = self.fake_proj # Get the command object to test self.cmd = project.UnsetProject(self.app, None) def test_project_unset_no_options(self): arglist = [ - identity_fakes.project_name, + self.fake_proj.name, ] verifylist = [ - ('project', identity_fakes.project_name), + ('project', self.fake_proj.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -642,7 +610,7 @@ def test_project_unset_key(self): arglist = [ '--property', 'fee', '--property', 'fo', - identity_fakes.project_name, + self.fake_proj.name, ] verifylist = [ ('property', ['fee', 'fo']), @@ -652,16 +620,16 @@ def test_project_unset_key(self): result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { - 'description': identity_fakes.project_description, + 'description': self.fake_proj.description, 'enabled': True, 'fee': None, 'fo': None, - 'id': identity_fakes.project_id, - 'name': identity_fakes.project_name, + 'id': self.fake_proj.id, + 'name': self.fake_proj.name, } self.projects_mock.update.assert_called_with( - identity_fakes.project_id, + self.fake_proj.id, **kwargs ) self.assertIsNone(result) diff --git a/openstackclient/tests/identity/v2_0/test_role.py b/openstackclient/tests/identity/v2_0/test_role.py index 7241c7bddf..74bd8f274f 100644 --- a/openstackclient/tests/identity/v2_0/test_role.py +++ b/openstackclient/tests/identity/v2_0/test_role.py @@ -26,12 +26,14 @@ class TestRole(identity_fakes.TestIdentityv2): - fake_service = copy.deepcopy(identity_fakes.SERVICE) - fake_service['endpoints'] = [ + attr = {} + attr['endpoints'] = [ { 'publicURL': identity_fakes.ENDPOINT['publicurl'], }, ] + fake_service = identity_fakes.FakeService.create_one_service(attr) + fake_role = identity_fakes.FakeRole.create_one_role() def setUp(self): super(TestRole, self).setUp() @@ -508,21 +510,17 @@ class TestRoleShow(TestRole): def setUp(self): super(TestRoleShow, self).setUp() - self.roles_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.ROLE), - loaded=True, - ) + self.roles_mock.get.return_value = self.fake_role # Get the command object to test self.cmd = role.ShowRole(self.app, None) def test_service_show(self): arglist = [ - identity_fakes.role_name, + self.fake_role.name, ] verifylist = [ - ('role', identity_fakes.role_name), + ('role', self.fake_role.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -533,13 +531,13 @@ def test_service_show(self): # RoleManager.get(role) self.roles_mock.get.assert_called_with( - identity_fakes.role_name, + self.fake_role.name, ) collist = ('id', 'name') self.assertEqual(collist, columns) datalist = ( - identity_fakes.role_id, - identity_fakes.role_name, + self.fake_role.id, + self.fake_role.name, ) self.assertEqual(datalist, data) From 70f8ae753467fb79e72969fb846c411f6c4d50d5 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Wed, 22 Jun 2016 18:43:42 +0800 Subject: [PATCH 1031/3095] Refactor unit test of "compute service list" command The unit test of "compute service list" only checked the "Disabled Reason" columns and its data. It is not enough. This patch change the test to check all datas in the list. Also, this patch modify the "Id" to "ID" in this command. Change-Id: I988fd6365f2652185dd96d9417f294eba9c31cd9 --- openstackclient/compute/v2/service.py | 4 +- openstackclient/tests/compute/v2/fakes.py | 6 ++- .../tests/compute/v2/test_service.py | 41 ++++++++++++++++--- 3 files changed, 42 insertions(+), 9 deletions(-) diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index 36917e2035..2e40dd7f47 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.py @@ -73,7 +73,7 @@ def take_action(self, parsed_args): compute_client = self.app.client_manager.compute if parsed_args.long: columns = ( - "Id", + "ID", "Binary", "Host", "Zone", @@ -84,7 +84,7 @@ def take_action(self, parsed_args): ) else: columns = ( - "Id", + "ID", "Binary", "Host", "Zone", diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 8416b630bf..94b040718f 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -648,15 +648,19 @@ def create_one_service(attrs=None): :param Dictionary attrs: A dictionary with all attributes :return: - A FakeResource object, with id, name, ram, vcpus, properties + A FakeResource object, with id, host, binary """ attrs = attrs or {} # Set default attributes. service_info = { + 'id': 'id-' + uuid.uuid4().hex, 'host': 'host-' + uuid.uuid4().hex, 'binary': 'binary-' + uuid.uuid4().hex, 'status': 'enabled', + 'zone': 'zone-' + uuid.uuid4().hex, + 'state': 'state-' + uuid.uuid4().hex, + 'updated_at': 'time-' + uuid.uuid4().hex, 'disabled_reason': 'earthquake', } diff --git a/openstackclient/tests/compute/v2/test_service.py b/openstackclient/tests/compute/v2/test_service.py index 3afe964f72..e41d633a62 100644 --- a/openstackclient/tests/compute/v2/test_service.py +++ b/openstackclient/tests/compute/v2/test_service.py @@ -62,11 +62,35 @@ def test_service_delete_no_options(self): class TestServiceList(TestService): + service = compute_fakes.FakeService.create_one_service() + + columns = ( + 'ID', + 'Binary', + 'Host', + 'Zone', + 'Status', + 'State', + 'Updated At', + ) + columns_long = columns + ( + 'Disabled Reason', + ) + + data = [( + service.id, + service.binary, + service.host, + service.zone, + service.status, + service.state, + service.updated_at, + )] + data_long = [data[0] + (service.disabled_reason, )] + def setUp(self): super(TestServiceList, self).setUp() - self.service = compute_fakes.FakeService.create_one_service() - self.service_mock.list.return_value = [self.service] # Get the command object to test @@ -93,8 +117,8 @@ def test_service_list(self): self.service.binary, ) - self.assertNotIn("Disabled Reason", columns) - self.assertNotIn(self.service.disabled_reason, list(data)[0]) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) def test_service_list_with_long_option(self): arglist = [ @@ -114,8 +138,13 @@ def test_service_list_with_long_option(self): # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.assertIn("Disabled Reason", columns) - self.assertIn(self.service.disabled_reason, list(data)[0]) + self.service_mock.list.assert_called_with( + self.service.host, + self.service.binary, + ) + + self.assertEqual(self.columns_long, columns) + self.assertEqual(self.data_long, list(data)) class TestServiceSet(TestService): From ccbb2dd1e8aaa5d2f34a30763f71125688c8dfac Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 22 Jun 2016 11:48:33 -0500 Subject: [PATCH 1032/3095] Remove OSCGenericPassword plugin The need for this has passed plus with 3.0 we can take the breakage hit, if any. Change-Id: Ic019842f00033d2cd67b75f036e7e817e4b7c075 --- doc/source/backwards-incompatible.rst | 21 ++++++++- openstackclient/api/auth.py | 4 +- openstackclient/api/auth_plugin.py | 44 ------------------- .../remove-osc_password-0767ac78267ef114.yaml | 7 +++ setup.cfg | 1 - 5 files changed, 28 insertions(+), 49 deletions(-) create mode 100644 releasenotes/notes/remove-osc_password-0767ac78267ef114.yaml diff --git a/doc/source/backwards-incompatible.rst b/doc/source/backwards-incompatible.rst index 00b314a53c..f86cfce4c2 100644 --- a/doc/source/backwards-incompatible.rst +++ b/doc/source/backwards-incompatible.rst @@ -13,8 +13,25 @@ deprecation warnings that indicate the new commands (or options) to use. Commands labeled as a beta according to :doc:`command-beta` are exempt from this backwards incompatible change handling. -List of Backwards Incompatible Changes -====================================== +Backwards Incompatible Changes +============================== + +Release 3.0 +----------- + +1. Remove the ``osc_password`` authentication plugin. + + This was the 'last-resort' plugin default that worked around an old default + Keystone configuration for the ``admin_endpoint`` and ``public_endpoint``. + + * In favor of: ``password`` + * As of: 3.0 + * Removed in: n/a + * Bug: n/a + * Commit: https://review.openstack.org/332938 + +Releases Before 3.0 +------------------- 1. Rename command `openstack project usage list` diff --git a/openstackclient/api/auth.py b/openstackclient/api/auth.py index d5412594a0..0c82fe9ba8 100644 --- a/openstackclient/api/auth.py +++ b/openstackclient/api/auth.py @@ -86,7 +86,7 @@ def select_auth_plugin(options): auth_plugin_name = 'v2password' else: # let keystoneclient figure it out itself - auth_plugin_name = 'osc_password' + auth_plugin_name = 'password' elif options.auth.get('token'): if options.identity_api_version == '3': auth_plugin_name = 'v3token' @@ -98,7 +98,7 @@ def select_auth_plugin(options): else: # The ultimate default is similar to the original behaviour, # but this time with version discovery - auth_plugin_name = 'osc_password' + auth_plugin_name = 'password' LOG.debug("Auth plugin %s selected", auth_plugin_name) return auth_plugin_name diff --git a/openstackclient/api/auth_plugin.py b/openstackclient/api/auth_plugin.py index 56dc4de5d4..dc47a688e6 100644 --- a/openstackclient/api/auth_plugin.py +++ b/openstackclient/api/auth_plugin.py @@ -16,9 +16,7 @@ import logging from keystoneauth1 import loading -from keystoneauth1.loading._plugins.identity import generic as ksa_password from keystoneauth1 import token_endpoint -from six.moves.urllib import parse as urlparse from openstackclient.i18n import _ @@ -65,45 +63,3 @@ def get_options(self): ), ] return options - - -class OSCGenericPassword(ksa_password.Password): - """Auth plugin hack to work around broken Keystone configurations - - The default Keystone configuration uses http://localhost:xxxx in - admin_endpoint and public_endpoint and are returned in the links.href - attribute by the version routes. Deployments that do not set these - are unusable with newer keystoneclient version discovery. - - """ - - def create_plugin(self, session, version, url, raw_status=None): - """Handle default Keystone endpoint configuration - - Build the actual API endpoint from the scheme, host and port of the - original auth URL and the rest from the returned version URL. - """ - - ver_u = urlparse.urlparse(url) - - # Only hack this if it is the default setting - if ver_u.netloc.startswith('localhost'): - auth_u = urlparse.urlparse(self.auth_url) - # from original auth_url: scheme, netloc - # from api_url: path, query (basically, the rest) - url = urlparse.urlunparse(( - auth_u.scheme, - auth_u.netloc, - ver_u.path, - ver_u.params, - ver_u.query, - ver_u.fragment, - )) - LOG.debug('Version URL updated: %s', url) - - return super(OSCGenericPassword, self).create_plugin( - session=session, - version=version, - url=url, - raw_status=raw_status, - ) diff --git a/releasenotes/notes/remove-osc_password-0767ac78267ef114.yaml b/releasenotes/notes/remove-osc_password-0767ac78267ef114.yaml new file mode 100644 index 0000000000..5d288bea68 --- /dev/null +++ b/releasenotes/notes/remove-osc_password-0767ac78267ef114.yaml @@ -0,0 +1,7 @@ +--- +upgrade: + - With the change to use keystoneauth plugins the OpenStackClient-specific + ``osc_password`` authentication plugin has been removed. The visible + difference should only be in the behaviour with poorly configured clouds + with old default Keystone values for admin_endpoint and public_endpoint + as seen in the version details returned in a GET to the root ('/') route. diff --git a/setup.cfg b/setup.cfg index 5e26d38dbd..44dd616acc 100644 --- a/setup.cfg +++ b/setup.cfg @@ -28,7 +28,6 @@ console_scripts = keystoneauth1.plugin = token_endpoint = openstackclient.api.auth_plugin:TokenEndpoint - osc_password = openstackclient.api.auth_plugin:OSCGenericPassword openstack.cli = command_list = openstackclient.common.module:ListCommand From 5534e293427e2ca89dc3d676226a0c2701c9612c Mon Sep 17 00:00:00 2001 From: Cao Xuan Hoang Date: Tue, 21 Jun 2016 16:30:55 +0700 Subject: [PATCH 1033/3095] Fix typo in openstackclient/network/v2 This patch adds the missing "." to some docstring/text. Trivial fix Change-Id: Iffb470dcce7200ddc9e439c465adc652a8322708 --- doc/source/command-objects/network.rst | 4 ++-- doc/source/command-objects/subnet.rst | 7 ++++--- openstackclient/network/v2/network.py | 2 +- openstackclient/network/v2/subnet.py | 8 ++++---- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/doc/source/command-objects/network.rst b/doc/source/command-objects/network.rst index 1cf442f222..0c472e7f18 100644 --- a/doc/source/command-objects/network.rst +++ b/doc/source/command-objects/network.rst @@ -108,7 +108,7 @@ Create new network .. option:: --provider-network-type The physical mechanism by which the virtual network is implemented. - The supported options are: flat, geneve, gre, local, vlan, vxlan + The supported options are: flat, geneve, gre, local, vlan, vxlan. *Network version 2 only* @@ -238,7 +238,7 @@ Set network properties .. option:: --provider-network-type The physical mechanism by which the virtual network is implemented. - The supported options are: flat, gre, local, vlan, vxlan + The supported options are: flat, gre, local, vlan, vxlan. .. option:: --provider-physical-network diff --git a/doc/source/command-objects/subnet.rst b/doc/source/command-objects/subnet.rst index fe77ccfde4..1137940f93 100644 --- a/doc/source/command-objects/subnet.rst +++ b/doc/source/command-objects/subnet.rst @@ -83,7 +83,7 @@ Create new subnet 'auto': Gateway address should automatically be chosen from within the subnet itself, 'none': This subnet will not use a gateway, e.g.: ``--gateway 192.168.9.1``, ``--gateway auto``, - ``--gateway none`` (default is 'auto') + ``--gateway none`` (default is 'auto'). .. option:: --host-route destination=,gateway= @@ -158,7 +158,8 @@ List subnets .. option:: --ip-version {4, 6} - List only subnets of given IP version in output + List only subnets of given IP version in output. + Allowed values for IP version are 4 and 6. subnet set ---------- @@ -200,7 +201,7 @@ Set subnet properties Specify a gateway for the subnet. The options are: : Specific IP address to use as the gateway, 'none': This subnet will not use a gateway, - e.g.: ``--gateway 192.168.9.1``, ``--gateway none`` + e.g.: ``--gateway 192.168.9.1``, ``--gateway none``. .. option:: --host-route destination=,gateway= diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 417355000e..31dfc7983b 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -108,7 +108,7 @@ def _add_additional_network_options(parser): 'vlan', 'vxlan'], help=_("The physical mechanism by which the virtual network " "is implemented. The supported options are: " - "flat, geneve, gre, local, vlan, vxlan")) + "flat, geneve, gre, local, vlan, vxlan.")) parser.add_argument( '--provider-physical-network', metavar='', diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 752923f73e..b076d82e24 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -235,7 +235,7 @@ def get_parser(self, prog_name): "'auto': Gateway address should automatically be chosen " "from within the subnet itself, 'none': This subnet will " "not use a gateway, e.g.: --gateway 192.168.9.1, " - "--gateway auto, --gateway none (default is 'auto')") + "--gateway auto, --gateway none (default is 'auto').") ) parser.add_argument( '--ip-version', @@ -244,7 +244,7 @@ def get_parser(self, prog_name): choices=[4, 6], help=_("IP version (default is 4). Note that when subnet pool is " "specified, IP version is determined from the subnet pool " - "and this option is ignored") + "and this option is ignored.") ) parser.add_argument( '--ipv6-ra-mode', @@ -334,7 +334,7 @@ def get_parser(self, prog_name): choices=[4, 6], metavar='', dest='ip_version', - help=_("List only subnets of given IP version in output" + help=_("List only subnets of given IP version in output." "Allowed values for IP version are 4 and 6."), ) return parser @@ -395,7 +395,7 @@ def get_parser(self, prog_name): help=_("Specify a gateway for the subnet. The options are: " ": Specific IP address to use as the gateway, " "'none': This subnet will not use a gateway, " - "e.g.: --gateway 192.168.9.1, --gateway none") + "e.g.: --gateway 192.168.9.1, --gateway none.") ) _get_common_parse_arguments(parser) return parser From 337d013c94378a4b3f0e8f90e4f5bd745448658f Mon Sep 17 00:00:00 2001 From: David Rosales Date: Fri, 29 Apr 2016 12:12:52 -0500 Subject: [PATCH 1034/3095] Use resource id when name given for identity show Currently a user is allowed to specify either a resource ID or name when running openstack identity comands. In some cases, when a name is specified instead of an ID, the command will return as not able to find the resource when it in fact does exist. The changes here are to check the client against the token on such requests and to extract the ID of the resource specified if enough information exists between the two. We then use the ID associated with the resource to complete the user requests. Change-Id: I40713b0ded42063b786dc21247e854224b9d2fe2 Closes-Bug: #1561599 --- openstackclient/identity/common.py | 33 ++++++++++++++++ openstackclient/identity/v3/domain.py | 7 +++- openstackclient/identity/v3/project.py | 7 +++- openstackclient/identity/v3/user.py | 6 ++- openstackclient/tests/identity/v3/fakes.py | 3 ++ .../tests/identity/v3/test_domain.py | 10 +++++ .../tests/identity/v3/test_project.py | 38 +++++++++++++++++++ .../tests/identity/v3/test_user.py | 11 ++++++ .../notes/bug-1561599-d5f541f08ae6274a.yaml | 7 ++++ 9 files changed, 117 insertions(+), 5 deletions(-) create mode 100644 releasenotes/notes/bug-1561599-d5f541f08ae6274a.yaml diff --git a/openstackclient/identity/common.py b/openstackclient/identity/common.py index cd767d2f46..379f4114e4 100644 --- a/openstackclient/identity/common.py +++ b/openstackclient/identity/common.py @@ -47,6 +47,39 @@ def find_service(identity_client, name_type_or_id): raise exceptions.CommandError(msg % name_type_or_id) +def _get_token_resource(client, resource, parsed_name): + """Peek into the user's auth token to get resource IDs + + Look into a user's token to try and find the ID of a domain, project or + user, when given the name. Typically non-admin users will interact with + the CLI using names. However, by default, keystone does not allow look up + by name since it would involve listing all entities. Instead opt to use + the correct ID (from the token) instead. + :param client: An identity client + :param resource: A resource to look at in the token, this may be `domain`, + `project_domain`, `user_domain`, `project`, or `user`. + :param parsed_name: This is input from parsed_args that the user is hoping + to find in the token. + + :returns: The ID of the resource from the token, or the original value from + parsed_args if it does not match. + """ + + try: + token = client.auth.client.get_token() + token_data = client.tokens.get_token_data(token) + token_dict = token_data['token'] + + # NOTE(stevemar): If domain is passed, just look at the project domain. + if resource == 'domain': + token_dict = token_dict['project'] + obj = token_dict[resource] + return obj['id'] if obj['name'] == parsed_name else parsed_name + # diaper defense in case parsing the token fails + except Exception: # noqa + return parsed_name + + def _get_domain_id_if_requested(identity_client, domain_name_or_id): if not domain_name_or_id: return None diff --git a/openstackclient/identity/v3/domain.py b/openstackclient/identity/v3/domain.py index 001d520167..8ba76c41b5 100644 --- a/openstackclient/identity/v3/domain.py +++ b/openstackclient/identity/v3/domain.py @@ -24,6 +24,7 @@ import six from openstackclient.i18n import _ +from openstackclient.identity import common LOG = logging.getLogger(__name__) @@ -187,8 +188,12 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity + + domain_str = common._get_token_resource(identity_client, 'domain', + parsed_args.domain) + domain = utils.find_resource(identity_client.domains, - parsed_args.domain) + domain_str) domain._info.pop('links') return zip(*sorted(six.iteritems(domain._info))) diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index 4db5bef132..56c1d41ada 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -321,18 +321,21 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity + project_str = common._get_token_resource(identity_client, 'project', + parsed_args.project) + if parsed_args.domain: domain = common.find_domain(identity_client, parsed_args.domain) project = utils.find_resource( identity_client.projects, - parsed_args.project, + project_str, domain_id=domain.id, parents_as_list=parsed_args.parents, subtree_as_list=parsed_args.children) else: project = utils.find_resource( identity_client.projects, - parsed_args.project, + project_str, parents_as_list=parsed_args.parents, subtree_as_list=parsed_args.children) diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index b0c80c14a3..dd5af06a31 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -444,14 +444,16 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity + user_str = common._get_token_resource(identity_client, 'user', + parsed_args.user) if parsed_args.domain: domain = common.find_domain(identity_client, parsed_args.domain) user = utils.find_resource(identity_client.users, - parsed_args.user, + user_str, domain_id=domain.id) else: user = utils.find_resource(identity_client.users, - parsed_args.user) + user_str) user._info.pop('links') return zip(*sorted(six.iteritems(user._info))) diff --git a/openstackclient/tests/identity/v3/fakes.py b/openstackclient/tests/identity/v3/fakes.py index cd1b4bd77d..dd918616ac 100644 --- a/openstackclient/tests/identity/v3/fakes.py +++ b/openstackclient/tests/identity/v3/fakes.py @@ -502,6 +502,9 @@ def __init__(self, **kwargs): self.role_assignments.resource_class = fakes.FakeResource(None, {}) self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] + self.auth = FakeAuth() + self.auth.client = mock.Mock() + self.auth.client.resource_class = fakes.FakeResource(None, {}) class FakeFederationManager(object): diff --git a/openstackclient/tests/identity/v3/test_domain.py b/openstackclient/tests/identity/v3/test_domain.py index f3777f12eb..e06e06812c 100644 --- a/openstackclient/tests/identity/v3/test_domain.py +++ b/openstackclient/tests/identity/v3/test_domain.py @@ -389,6 +389,16 @@ def test_domain_show(self): ('domain', identity_fakes.domain_id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.app.client_manager.identity.tokens.get_token_data.return_value = \ + {'token': + {'project': + {'domain': + {'id': 'd1', + 'name': 'd1' + } + } + } + } # 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 diff --git a/openstackclient/tests/identity/v3/test_project.py b/openstackclient/tests/identity/v3/test_project.py index 8fcada6e4d..93bf18afbc 100644 --- a/openstackclient/tests/identity/v3/test_project.py +++ b/openstackclient/tests/identity/v3/test_project.py @@ -749,6 +749,7 @@ def setUp(self): self.cmd = project.ShowProject(self.app, None) def test_project_show(self): + arglist = [ identity_fakes.project_id, ] @@ -757,6 +758,16 @@ def test_project_show(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.app.client_manager.identity.tokens.get_token_data.return_value = \ + {'token': + {'project': + {'domain': {}, + 'name': parsed_args.project, + 'id': parsed_args.project + } + } + } + # 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. @@ -797,6 +808,15 @@ def test_project_show_parents(self): ('children', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.app.client_manager.identity.tokens.get_token_data.return_value = \ + {'token': + {'project': + {'domain': {}, + 'name': parsed_args.project, + 'id': parsed_args.project + } + } + } columns, data = self.cmd.take_action(parsed_args) self.projects_mock.get.assert_called_with( @@ -845,6 +865,15 @@ def test_project_show_subtree(self): ('children', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.app.client_manager.identity.tokens.get_token_data.return_value = \ + {'token': + {'project': + {'domain': {}, + 'name': parsed_args.project, + 'id': parsed_args.project + } + } + } columns, data = self.cmd.take_action(parsed_args) self.projects_mock.get.assert_called_with( @@ -895,6 +924,15 @@ def test_project_show_parents_and_children(self): ('children', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.app.client_manager.identity.tokens.get_token_data.return_value = \ + {'token': + {'project': + {'domain': {}, + 'name': parsed_args.project, + 'id': parsed_args.project + } + } + } columns, data = self.cmd.take_action(parsed_args) self.projects_mock.get.assert_called_with( diff --git a/openstackclient/tests/identity/v3/test_user.py b/openstackclient/tests/identity/v3/test_user.py index 85522e577f..c4fb152108 100644 --- a/openstackclient/tests/identity/v3/test_user.py +++ b/openstackclient/tests/identity/v3/test_user.py @@ -1094,6 +1094,17 @@ def setUp(self): # Get the command object to test self.cmd = user.ShowUser(self.app, None) + self.app.client_manager.identity.auth.client.get_user_id.\ + return_value = 'bbbbbbb-aaaa-aaaa-aaaa-bbbbbbbaaaa' + self.app.client_manager.identity.tokens.get_token_data.return_value = \ + {'token': + {'user': + {'domain': {}, + 'id': 'bbbbbbb-aaaa-aaaa-aaaa-bbbbbbbaaaa', + 'name': 'bbbbbbb-aaaa-aaaa-aaaa-bbbbbbbaaaa' + } + } + } def test_user_show(self): arglist = [ diff --git a/releasenotes/notes/bug-1561599-d5f541f08ae6274a.yaml b/releasenotes/notes/bug-1561599-d5f541f08ae6274a.yaml new file mode 100644 index 0000000000..8624a95b39 --- /dev/null +++ b/releasenotes/notes/bug-1561599-d5f541f08ae6274a.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - When performing ``domain show``, ``project show`` or ``user show``, peek + into the user token to determine the ID or the resource (if supplied with + only a name). This should make finding information about the user and + their project easier for non-admin users. + [Bug `1561599 `_] From e8483c90229fbb40408358ff307402469e12c26f Mon Sep 17 00:00:00 2001 From: sunyajing Date: Thu, 23 Jun 2016 14:53:57 +0800 Subject: [PATCH 1035/3095] Standardize logger usage of catalog in identity Change-Id: I5307f949b3a350e41840a4a5c191ceacf1b3b291 Partially-Implements: blueprint log-usage --- openstackclient/identity/v2_0/catalog.py | 8 ++++++-- openstackclient/identity/v3/catalog.py | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/openstackclient/identity/v2_0/catalog.py b/openstackclient/identity/v2_0/catalog.py index 62e6b1f97a..7a15cf3a58 100644 --- a/openstackclient/identity/v2_0/catalog.py +++ b/openstackclient/identity/v2_0/catalog.py @@ -13,6 +13,8 @@ """Identity v2 Service Catalog action implementations""" +import logging + from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -21,6 +23,9 @@ from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + def _format_endpoints(eps=None): if not eps: return "" @@ -92,8 +97,7 @@ def take_action(self, parsed_args): break if not data: - self.app.log.error(_('service %s not found\n') % - parsed_args.service) + LOG.error(_('service %s not found\n'), parsed_args.service) return ((), ()) return zip(*sorted(six.iteritems(data))) diff --git a/openstackclient/identity/v3/catalog.py b/openstackclient/identity/v3/catalog.py index a6e141c945..a62d0a930d 100644 --- a/openstackclient/identity/v3/catalog.py +++ b/openstackclient/identity/v3/catalog.py @@ -13,6 +13,8 @@ """Identity v3 Service Catalog action implementations""" +import logging + from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -21,6 +23,9 @@ from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + def _format_endpoints(eps=None): if not eps: return "" @@ -87,8 +92,7 @@ def take_action(self, parsed_args): break if not data: - self.app.log.error(_('service %s not found\n') % - parsed_args.service) + LOG.error(_('service %s not found\n'), parsed_args.service) return ((), ()) return zip(*sorted(six.iteritems(data))) From d08cf31dc89a5ed09db491a832c252ac4f71501c Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Thu, 23 Jun 2016 08:27:28 -0500 Subject: [PATCH 1036/3095] Improve server functional tests Patch set [1] fixed timing issues related to the server functional tests. As part of the review, additional enhancements were suggested. This patch set provides those enhancements. In particular, the functional tests will now check for the cirros256 flavor and the cirros-*-uec image. [1] https://review.openstack.org/#/c/313870/ Change-Id: I7fe18e26b3d09db92bbe669ffafcd16618cae383 --- functional/tests/compute/v2/test_server.py | 28 ++++++++++++++-------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/functional/tests/compute/v2/test_server.py b/functional/tests/compute/v2/test_server.py index 96c1c1a50b..d08b003fbc 100644 --- a/functional/tests/compute/v2/test_server.py +++ b/functional/tests/compute/v2/test_server.py @@ -23,19 +23,27 @@ class ServerTests(test.TestCase): @classmethod def get_flavor(cls): - # NOTE(rtheis): Get m1.tiny flavor since functional tests may - # create other flavors. - raw_output = cls.openstack('flavor show m1.tiny -c id -f value') - return raw_output.strip('\n') + # NOTE(rtheis): Get cirros256 or m1.tiny flavors since functional + # tests may create other flavors. + flavors = cls.openstack('flavor list -c Name -f value').split('\n') + server_flavor = None + for flavor in flavors: + if flavor in ['m1.tiny', 'cirros256']: + server_flavor = flavor + break + return server_flavor @classmethod def get_image(cls): - # NOTE(rtheis): Get public images since functional tests may - # create private images. - raw_output = cls.openstack('image list --public -f value -c ID') - ray = raw_output.split('\n') - idx = int(len(ray) / 2) - return ray[idx] + # NOTE(rtheis): Get cirros image since functional tests may + # create other images. + images = cls.openstack('image list -c Name -f value').split('\n') + server_image = None + for image in images: + if image.startswith('cirros-') and image.endswith('-uec'): + server_image = image + break + return server_image @classmethod def get_network(cls): From 6828f5ce27dd5fb03bfb7fd9847bde0b942fd467 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Fri, 24 Jun 2016 03:17:41 +0000 Subject: [PATCH 1037/3095] Updated from global requirements Change-Id: I43d04f2cbb0543d55783252d8618ddafdda3a66c --- test-requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test-requirements.txt b/test-requirements.txt index cca81ec8ff..9e82a5fc2d 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -9,8 +9,8 @@ fixtures>=3.0.0 # Apache-2.0/BSD mock>=2.0 # BSD oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 -reno>=1.6.2 # Apache2 -requests-mock>=0.7.0 # Apache-2.0 +reno>=1.8.0 # Apache2 +requests-mock>=1.0 # Apache-2.0 sphinx!=1.3b1,<1.3,>=1.2.1 # BSD os-testr>=0.7.0 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD From ee156b5fe3ebb118e5cb8da9e9e002d8d0baccc7 Mon Sep 17 00:00:00 2001 From: SongmingYan Date: Thu, 2 Jun 2016 05:10:18 -0400 Subject: [PATCH 1038/3095] Fix the problem of router delete Change "Remove" to "Delete" when using it in route-interface-delete. Change-Id: I9ebc38c787acbcb42d2f8efeee41f6cb943e4b9b Related-bug: #1560446 --- doc/source/command-objects/router.rst | 2 +- openstackclient/network/v2/router.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/command-objects/router.rst b/doc/source/command-objects/router.rst index 9ca7661ede..f6d5500c65 100644 --- a/doc/source/command-objects/router.rst +++ b/doc/source/command-objects/router.rst @@ -150,7 +150,7 @@ Remove a port from a router .. describe:: - Port to be removed (name or ID) + Port to be removed and deleted (name or ID) router remove subnet -------------------- diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 6271a8784f..2f41838ffb 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -309,7 +309,7 @@ def get_parser(self, prog_name): parser.add_argument( 'port', metavar='', - help=_("Port to be removed (name or ID)") + help=_("Port to be removed and deleted (name or ID)") ) return parser From 6df09fd377f872388d4f855b001a6578ae6fba46 Mon Sep 17 00:00:00 2001 From: sunyajing Date: Thu, 23 Jun 2016 12:55:54 +0800 Subject: [PATCH 1039/3095] Support multi-delete for commands in identity V2 Commands are "ec2 credentials delete", "service delete", "endpoint delete". Also update their unit tests and functional tests. Partial-Bug: #1592906 Change-Id: I1a0b7160b803a523646d09d030e6f112c81c4c24 --- .../command-objects/ec2-credentials.rst | 4 +-- doc/source/command-objects/endpoint.rst | 6 ++-- doc/source/command-objects/service.rst | 6 ++-- .../tests/identity/v2/test_ec2_credentials.py | 8 +++++ functional/tests/identity/v2/test_endpoint.py | 7 +++++ functional/tests/identity/v2/test_service.py | 7 +++++ openstackclient/identity/v2_0/ec2creds.py | 27 +++++++++++++++-- openstackclient/identity/v2_0/endpoint.py | 30 ++++++++++++++++--- openstackclient/identity/v2_0/service.py | 26 ++++++++++++---- .../tests/identity/v2_0/test_endpoint.py | 2 +- .../tests/identity/v2_0/test_service.py | 2 +- .../notes/bug-1592906-a5604ec5abe77507.yaml | 5 ++++ 12 files changed, 108 insertions(+), 22 deletions(-) create mode 100644 releasenotes/notes/bug-1592906-a5604ec5abe77507.yaml diff --git a/doc/source/command-objects/ec2-credentials.rst b/doc/source/command-objects/ec2-credentials.rst index 6748422e14..caf7fd6901 100644 --- a/doc/source/command-objects/ec2-credentials.rst +++ b/doc/source/command-objects/ec2-credentials.rst @@ -55,7 +55,7 @@ Delete EC2 credentials os ec2 credentials delete [--user ] [--user-domain ] - + [ ...] .. option:: --user @@ -71,7 +71,7 @@ Delete EC2 credentials .. _ec2_credentials_delete-access-key: .. describe:: access-key - Credentials access key + Credentials access key(s) The :option:`--user` option is typically only useful for admin users, but may be allowed for other users depending on the policy of the cloud and diff --git a/doc/source/command-objects/endpoint.rst b/doc/source/command-objects/endpoint.rst index 817ad49143..c058c8444e 100644 --- a/doc/source/command-objects/endpoint.rst +++ b/doc/source/command-objects/endpoint.rst @@ -81,18 +81,18 @@ Create new endpoint endpoint delete --------------- -Delete endpoint +Delete endpoint(s) .. program:: endpoint delete .. code:: bash os endpoint delete - + [ ...] .. _endpoint_delete-endpoint: .. describe:: - Endpoint to delete (ID only) + Endpoint(s) to delete (ID only) endpoint list ------------- diff --git a/doc/source/command-objects/service.rst b/doc/source/command-objects/service.rst index 421a298b31..b5ec2d9962 100644 --- a/doc/source/command-objects/service.rst +++ b/doc/source/command-objects/service.rst @@ -46,18 +46,18 @@ Create new service service delete -------------- -Delete service +Delete service(s) .. program:: service delete .. code-block:: bash os service delete - + [ ...] .. _service_delete-type: .. describe:: - Service to delete (type, name or ID) + Service(s) to delete (type, name or ID) service list ------------ diff --git a/functional/tests/identity/v2/test_ec2_credentials.py b/functional/tests/identity/v2/test_ec2_credentials.py index 319bd11a64..7a8ee35d7e 100644 --- a/functional/tests/identity/v2/test_ec2_credentials.py +++ b/functional/tests/identity/v2/test_ec2_credentials.py @@ -25,6 +25,14 @@ def test_ec2_credentials_delete(self): ) self.assertEqual(0, len(raw_output)) + def test_ec2_credentials_multi_delete(self): + access_key_1 = self._create_dummy_ec2_credentials(add_clean_up=False) + access_key_2 = self._create_dummy_ec2_credentials(add_clean_up=False) + raw_output = self.openstack( + 'ec2 credentials delete ' + access_key_1 + ' ' + access_key_2 + ) + self.assertEqual(0, len(raw_output)) + def test_ec2_credentials_list(self): self._create_dummy_ec2_credentials() raw_output = self.openstack('ec2 credentials list') diff --git a/functional/tests/identity/v2/test_endpoint.py b/functional/tests/identity/v2/test_endpoint.py index 0682e6b476..34888c0b50 100644 --- a/functional/tests/identity/v2/test_endpoint.py +++ b/functional/tests/identity/v2/test_endpoint.py @@ -24,6 +24,13 @@ def test_endpoint_delete(self): 'endpoint delete %s' % endpoint_id) self.assertEqual(0, len(raw_output)) + def test_endpoint_multi_delete(self): + endpoint_id_1 = self._create_dummy_endpoint(add_clean_up=False) + endpoint_id_2 = self._create_dummy_endpoint(add_clean_up=False) + raw_output = self.openstack( + 'endpoint delete ' + endpoint_id_1 + ' ' + endpoint_id_2) + self.assertEqual(0, len(raw_output)) + def test_endpoint_list(self): endpoint_id = self._create_dummy_endpoint() raw_output = self.openstack('endpoint list') diff --git a/functional/tests/identity/v2/test_service.py b/functional/tests/identity/v2/test_service.py index 219ed33f07..9dcb6bea07 100644 --- a/functional/tests/identity/v2/test_service.py +++ b/functional/tests/identity/v2/test_service.py @@ -23,6 +23,13 @@ def test_service_delete(self): raw_output = self.openstack('service delete %s' % service_name) self.assertEqual(0, len(raw_output)) + def test_service_multi_delete(self): + service_name_1 = self._create_dummy_service(add_clean_up=False) + service_name_2 = self._create_dummy_service(add_clean_up=False) + raw_output = self.openstack( + 'service delete ' + service_name_1 + ' ' + service_name_2) + self.assertEqual(0, len(raw_output)) + def test_service_list(self): self._create_dummy_service() raw_output = self.openstack('service list') diff --git a/openstackclient/identity/v2_0/ec2creds.py b/openstackclient/identity/v2_0/ec2creds.py index 78f65824a6..058b5772d8 100644 --- a/openstackclient/identity/v2_0/ec2creds.py +++ b/openstackclient/identity/v2_0/ec2creds.py @@ -16,13 +16,19 @@ """Identity v2 EC2 Credentials 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 CreateEC2Creds(command.ShowOne): """Create EC2 credentials""" @@ -85,9 +91,10 @@ class DeleteEC2Creds(command.Command): def get_parser(self, prog_name): parser = super(DeleteEC2Creds, self).get_parser(prog_name) parser.add_argument( - 'access_key', + 'access_keys', metavar='', - help=_('Credentials access key'), + nargs='+', + help=_('Credentials access keys'), ) parser.add_argument( '--user', @@ -108,7 +115,21 @@ def take_action(self, parsed_args): # Get the user from the current auth user = self.app.client_manager.auth_ref.user_id - identity_client.ec2.delete(user, parsed_args.access_key) + result = 0 + for access_key in parsed_args.access_keys: + try: + identity_client.ec2.delete(user, access_key) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete EC2 keys with " + "access key '%(access_key)s': %(e)s") + % {'access_key': access_key, 'e': e}) + + if result > 0: + total = len(parsed_args.access_keys) + msg = (_("%(result)s of %(total)s EC2 keys failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListEC2Creds(command.Lister): diff --git a/openstackclient/identity/v2_0/endpoint.py b/openstackclient/identity/v2_0/endpoint.py index ee2bab6ff0..5a3b3186fb 100644 --- a/openstackclient/identity/v2_0/endpoint.py +++ b/openstackclient/identity/v2_0/endpoint.py @@ -15,7 +15,10 @@ """Endpoint action implementations""" +import logging + from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils import six @@ -23,6 +26,9 @@ from openstackclient.identity import common +LOG = logging.getLogger(__name__) + + class CreateEndpoint(command.ShowOne): """Create new endpoint""" @@ -74,20 +80,36 @@ def take_action(self, parsed_args): class DeleteEndpoint(command.Command): - """Delete endpoint""" + """Delete endpoint(s)""" def get_parser(self, prog_name): parser = super(DeleteEndpoint, self).get_parser(prog_name) parser.add_argument( - 'endpoint', + 'endpoints', metavar='', - help=_('Endpoint to delete (ID only)'), + nargs='+', + help=_('Endpoint(s) to delete (ID only)'), ) return parser def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - identity_client.endpoints.delete(parsed_args.endpoint) + + result = 0 + for endpoint in parsed_args.endpoints: + try: + identity_client.endpoints.delete(endpoint) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete endpoint with " + "ID '%(endpoint)s': %(e)s") + % {'endpoint': endpoint, 'e': e}) + + if result > 0: + total = len(parsed_args.endpoints) + msg = (_("%(result)s of %(total)s endpoints failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListEndpoint(command.Lister): diff --git a/openstackclient/identity/v2_0/service.py b/openstackclient/identity/v2_0/service.py index df332eb64d..e318643c17 100644 --- a/openstackclient/identity/v2_0/service.py +++ b/openstackclient/identity/v2_0/service.py @@ -91,21 +91,37 @@ def take_action(self, parsed_args): class DeleteService(command.Command): - """Delete service""" + """Delete service(s)""" def get_parser(self, prog_name): parser = super(DeleteService, self).get_parser(prog_name) parser.add_argument( - 'service', + 'services', metavar='', - help=_('Service to delete (type, name or ID)'), + nargs='+', + help=_('Service(s) to delete (type, name or ID)'), ) return parser def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - service = common.find_service(identity_client, parsed_args.service) - identity_client.services.delete(service.id) + + result = 0 + for service in parsed_args.services: + try: + service = common.find_service(identity_client, service) + identity_client.services.delete(service.id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete service with " + "name or ID '%(service)s': %(e)s") + % {'service': service, 'e': e}) + + if result > 0: + total = len(parsed_args.services) + msg = (_("%(result)s of %(total)s services failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListService(command.Lister): diff --git a/openstackclient/tests/identity/v2_0/test_endpoint.py b/openstackclient/tests/identity/v2_0/test_endpoint.py index e8fb926290..0b539702be 100644 --- a/openstackclient/tests/identity/v2_0/test_endpoint.py +++ b/openstackclient/tests/identity/v2_0/test_endpoint.py @@ -112,7 +112,7 @@ def test_endpoint_delete_no_options(self): self.fake_endpoint.id, ] verifylist = [ - ('endpoint', self.fake_endpoint.id), + ('endpoints', [self.fake_endpoint.id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/identity/v2_0/test_service.py b/openstackclient/tests/identity/v2_0/test_service.py index dc0fbcd122..ba976f4c85 100644 --- a/openstackclient/tests/identity/v2_0/test_service.py +++ b/openstackclient/tests/identity/v2_0/test_service.py @@ -190,7 +190,7 @@ def test_service_delete_no_options(self): identity_fakes.service_name, ] verifylist = [ - ('service', identity_fakes.service_name), + ('services', [identity_fakes.service_name]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/releasenotes/notes/bug-1592906-a5604ec5abe77507.yaml b/releasenotes/notes/bug-1592906-a5604ec5abe77507.yaml new file mode 100644 index 0000000000..a9b0606fda --- /dev/null +++ b/releasenotes/notes/bug-1592906-a5604ec5abe77507.yaml @@ -0,0 +1,5 @@ +--- +features: + - Support bulk deletion for ``ec2 credentials delete``, ``endpoint delete``, + ``service delete`` in identity V2.0 . + [Bug `1592906 `_] From 60f3cfa6ef21b5aca9a4b71b3f13f25db46b16ee Mon Sep 17 00:00:00 2001 From: Vincent Legoll Date: Fri, 24 Jun 2016 12:18:40 +0200 Subject: [PATCH 1040/3095] Remove code forgotten in cb28fb55884a9be7cd70c37343181116cf000a42 Change-Id: I2abc75c94c1b6450f56bfcf2d8b1135e5caa3598 Signed-off-by: Vincent Legoll --- functional/tests/identity/v2/common.py | 3 --- functional/tests/identity/v3/common.py | 3 --- 2 files changed, 6 deletions(-) diff --git a/functional/tests/identity/v2/common.py b/functional/tests/identity/v2/common.py index 9d6a7bb5b0..ca061a4dac 100644 --- a/functional/tests/identity/v2/common.py +++ b/functional/tests/identity/v2/common.py @@ -61,9 +61,6 @@ def setUpClass(cls): def tearDownClass(cls): cls.openstack('project delete %s' % cls.project_name) - if hasattr(super(IdentityTests, cls), 'tearDownClass'): - super(IdentityTests, cls).tearDownClass() - def _create_dummy_project(self, add_clean_up=True): project_name = data_utils.rand_name('TestProject') project_description = data_utils.rand_name('description') diff --git a/functional/tests/identity/v3/common.py b/functional/tests/identity/v3/common.py index a7cddfc2c9..c988d33638 100644 --- a/functional/tests/identity/v3/common.py +++ b/functional/tests/identity/v3/common.py @@ -87,9 +87,6 @@ def tearDownClass(cls): cls.openstack('domain set --disable %s' % cls.domain_name) cls.openstack('domain delete %s' % cls.domain_name) - if hasattr(super(IdentityTests, cls), 'tearDownClass'): - super(IdentityTests, cls).tearDownClass() - def _create_dummy_user(self, add_clean_up=True): username = data_utils.rand_name('TestUser') password = data_utils.rand_name('password') From ca9a6daba07969081572edee71bdf3438f41425b Mon Sep 17 00:00:00 2001 From: Kyrylo Romanenko Date: Fri, 24 Jun 2016 14:28:28 +0300 Subject: [PATCH 1041/3095] Make code more compact for get_list_opts function Do it like in get_show_opts done. Change-Id: If6b115c35a41997627660830fb8e5d299e8fb8e9 --- functional/common/test.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/functional/common/test.py b/functional/common/test.py index 436156151e..e882dd6498 100644 --- a/functional/common/test.py +++ b/functional/common/test.py @@ -63,9 +63,7 @@ def get_show_opts(cls, fields=[]): @classmethod def get_list_opts(cls, headers=[]): - opts = ' -f csv ' - opts = opts + ' '.join(['-c ' + it for it in headers]) - return opts + return ' -f csv ' + ' '.join(['-c ' + it for it in headers]) @classmethod def assertOutput(cls, expected, actual): From f2b73c2737037bc38e3608a9be98a2076fc8d777 Mon Sep 17 00:00:00 2001 From: Kyrylo Romanenko Date: Fri, 24 Jun 2016 14:45:19 +0300 Subject: [PATCH 1042/3095] Fix several flake8 code style issues in compute tests. Fix issues: H402 one line docstring needs punctuation, W503 line break before binary operator. Change-Id: Ie93430898403f137e7b647a97a28b0d7a4ebb463 --- functional/tests/compute/v2/test_agent.py | 18 +++++++++--------- functional/tests/compute/v2/test_aggregate.py | 2 +- functional/tests/compute/v2/test_flavor.py | 2 +- .../tests/compute/v2/test_server_group.py | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/functional/tests/compute/v2/test_agent.py b/functional/tests/compute/v2/test_agent.py index df7c21f264..2d7ea21615 100644 --- a/functional/tests/compute/v2/test_agent.py +++ b/functional/tests/compute/v2/test_agent.py @@ -16,7 +16,7 @@ class ComputeAgentTests(test.TestCase): - """Functional tests for compute agent. """ + """Functional tests for compute agent.""" ID = None MD5HASH = hashlib.md5().hexdigest() @@ -32,11 +32,11 @@ class ComputeAgentTests(test.TestCase): @classmethod def setUpClass(cls): opts = cls.get_show_opts(cls.HEADERS) - raw_output = cls.openstack('compute agent create ' - + cls.OS + ' ' + cls.ARCH + ' ' - + cls.VER + ' ' + cls.URL + ' ' - + cls.MD5HASH + ' ' + cls.HYPER + ' ' - + opts) + raw_output = cls.openstack('compute agent create ' + + cls.OS + ' ' + cls.ARCH + ' ' + + cls.VER + ' ' + cls.URL + ' ' + + cls.MD5HASH + ' ' + cls.HYPER + ' ' + + opts) # Get agent id because agent can only be deleted by ID output_list = raw_output.split('\n', 1) @@ -64,9 +64,9 @@ def test_agent_set(self): url = "http://openstack" md5hash = hashlib.md5().hexdigest() - raw_output = self.openstack('compute agent set ' - + self.ID + ' ' + ver + ' ' - + url + ' ' + md5hash) + raw_output = self.openstack('compute agent set ' + + self.ID + ' ' + ver + ' ' + + url + ' ' + md5hash) self.assertEqual('', raw_output) raw_output = self.openstack('compute agent list') diff --git a/functional/tests/compute/v2/test_aggregate.py b/functional/tests/compute/v2/test_aggregate.py index 2ad6559904..5bac585841 100644 --- a/functional/tests/compute/v2/test_aggregate.py +++ b/functional/tests/compute/v2/test_aggregate.py @@ -16,7 +16,7 @@ class AggregateTests(test.TestCase): - """Functional tests for aggregate. """ + """Functional tests for aggregate.""" NAME = uuid.uuid4().hex HEADERS = ['Name'] diff --git a/functional/tests/compute/v2/test_flavor.py b/functional/tests/compute/v2/test_flavor.py index d1f5f95dd4..2bb075bd59 100644 --- a/functional/tests/compute/v2/test_flavor.py +++ b/functional/tests/compute/v2/test_flavor.py @@ -16,7 +16,7 @@ class FlavorTests(test.TestCase): - """Functional tests for flavor. """ + """Functional tests for flavor.""" NAME = uuid.uuid4().hex HEADERS = ['Name'] diff --git a/functional/tests/compute/v2/test_server_group.py b/functional/tests/compute/v2/test_server_group.py index ce4f97a67b..c073b88e99 100644 --- a/functional/tests/compute/v2/test_server_group.py +++ b/functional/tests/compute/v2/test_server_group.py @@ -16,7 +16,7 @@ class ServerGroupTests(test.TestCase): - """Functional tests for servergroup. """ + """Functional tests for servergroup.""" NAME = uuid.uuid4().hex HEADERS = ['Name'] From 5951026bf28265a61c289d5148b73b485e242eaf Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Wed, 22 Jun 2016 19:37:35 +0800 Subject: [PATCH 1043/3095] Add unit tests for "host list" and "host show" commands Missing unit tests for "host list" and "host show" commands in computev2, this patch add them. Change-Id: Ib157920fa2267ec96d206cdf46213563a105501b --- openstackclient/tests/compute/v2/fakes.py | 8 +- openstackclient/tests/compute/v2/test_host.py | 104 ++++++++++++++++++ 2 files changed, 111 insertions(+), 1 deletion(-) diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index b7f17fbc93..9046e76b67 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -1088,7 +1088,13 @@ def create_one_host(attrs=None): "stats": "", "numa_topology": "", "ram_allocation_ratio": 1.0, - "cpu_allocation_ratio": 1.0 + "cpu_allocation_ratio": 1.0, + "zone": 'zone-' + uuid.uuid4().hex, + "host_name": 'name-' + uuid.uuid4().hex, + "service": 'service-' + uuid.uuid4().hex, + "cpu": 4, + "disk_gb": 100, + 'project': 'project-' + uuid.uuid4().hex, } host_info.update(attrs) host = fakes.FakeResource( diff --git a/openstackclient/tests/compute/v2/test_host.py b/openstackclient/tests/compute/v2/test_host.py index 63ae1f6dbb..9ebed68d45 100644 --- a/openstackclient/tests/compute/v2/test_host.py +++ b/openstackclient/tests/compute/v2/test_host.py @@ -15,6 +15,7 @@ from openstackclient.compute.v2 import host from openstackclient.tests.compute.v2 import fakes as compute_fakes +from openstackclient.tests import utils as tests_utils class TestHost(compute_fakes.TestComputev2): @@ -27,6 +28,58 @@ def setUp(self): self.host_mock.reset_mock() +class TestHostList(TestHost): + + host = compute_fakes.FakeHost.create_one_host() + + columns = ( + 'Host Name', + 'Service', + 'Zone', + ) + + data = [( + host.host_name, + host.service, + host.zone, + )] + + def setUp(self): + super(TestHostList, self).setUp() + + self.host_mock.list_all.return_value = [self.host] + + self.cmd = host.ListHost(self.app, None) + + def test_host_list_no_option(self): + arglist = [] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.host_mock.list_all.assert_called_with(None) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_host_list_with_option(self): + arglist = [ + '--zone', self.host.zone, + ] + verifylist = [ + ('zone', self.host.zone), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.host_mock.list_all.assert_called_with(self.host.zone) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + class TestHostSet(TestHost): def setUp(self): @@ -73,3 +126,54 @@ def test_host_set(self): body = {'status': 'enable', 'maintenance_mode': 'disable'} self.host_mock.update.assert_called_with(self.host.host, body) + + +class TestHostShow(TestHost): + + host = compute_fakes.FakeHost.create_one_host() + + columns = ( + 'Host', + 'Project', + 'CPU', + 'Memory MB', + 'Disk GB', + ) + data = [( + host.host, + host.project, + host.cpu, + host.memory_mb, + host.disk_gb, + )] + + def setUp(self): + super(TestHostShow, self).setUp() + + self.host_mock.get.return_value = [self.host] + + self.cmd = host.ShowHost(self.app, None) + + def test_host_show_no_option(self): + arglist = [] + verifylist = [] + + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_host_show_with_option(self): + arglist = [ + self.host.host_name, + ] + verifylist = [ + ('host', self.host.host_name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.host_mock.get.assert_called_with(self.host.host_name) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) From 6c7a30ab381e79f21767898f0a235276839f8ef2 Mon Sep 17 00:00:00 2001 From: ting wang Date: Thu, 26 May 2016 19:55:11 +0800 Subject: [PATCH 1044/3095] Implement rbac list and show command 1. implement "openstack network rbac list" 2. implement "openstack network rbac show" 3. also add FakeRBACPolicy to test "network rbac xxx" command The unit test class similar to FakeRouter, which is able to fake one or more rbac policies. It will be used by the rbac CRUD patches. Change-Id: I6c97bc8819698546895fd530464a2cbb347bf77d Co-Authored-By: Huanxuan Ao Partially-Implements: blueprint neutron-client-rbac Depends-On: I88f409a24947b67146c0f93ec8480834cef56d2f --- doc/source/command-objects/network-rbac.rst | 35 +++++ doc/source/commands.rst | 1 + openstackclient/network/v2/network_rbac.py | 75 +++++++++++ openstackclient/tests/network/v2/fakes.py | 51 ++++++++ .../tests/network/v2/test_network_rbac.py | 122 ++++++++++++++++++ ...-neutron-client-rbac-bbd7b545b50d2bdf.yaml | 4 + setup.cfg | 3 + 7 files changed, 291 insertions(+) create mode 100644 doc/source/command-objects/network-rbac.rst create mode 100644 openstackclient/network/v2/network_rbac.py create mode 100644 openstackclient/tests/network/v2/test_network_rbac.py create mode 100644 releasenotes/notes/bp-neutron-client-rbac-bbd7b545b50d2bdf.yaml diff --git a/doc/source/command-objects/network-rbac.rst b/doc/source/command-objects/network-rbac.rst new file mode 100644 index 0000000000..0cf2127fc2 --- /dev/null +++ b/doc/source/command-objects/network-rbac.rst @@ -0,0 +1,35 @@ +============ +network rbac +============ + +A **network rbac** is a Role-Based Access Control (RBAC) policy for +network resources. It enables both operators and users to grant access +to network resources for specific projects. + +Network v2 + +network rbac list +----------------- + +List network RBAC policies + +.. program:: network rbac list +.. code:: bash + + os network rbac list + +network rbac show +----------------- + +Display network RBAC policy details + +.. program:: network rbac show +.. code:: bash + + os network rbac show + + +.. _network_rbac_show-rbac-policy: +.. describe:: + + RBAC policy (ID only) diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 9fb0555d7c..a2e8712a9d 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -105,6 +105,7 @@ referring to both Compute and Volume quotas. * ``mapping``: (**Identity**) a definition to translate identity provider attributes to Identity concepts * ``module``: (**Internal**) - installed Python modules in the OSC process * ``network``: (**Compute**, **Network**) - a virtual network for connecting servers and other resources +* ``network rbac``: (**Network**) - an RBAC policy for network resources * ``network segment``: (**Network**) - a segment of a virtual network * ``object``: (**Object Storage**) a single file in the Object Storage * ``object store account``: (**Object Storage**) owns a group of Object Storage resources diff --git a/openstackclient/network/v2/network_rbac.py b/openstackclient/network/v2/network_rbac.py new file mode 100644 index 0000000000..7a759449b6 --- /dev/null +++ b/openstackclient/network/v2/network_rbac.py @@ -0,0 +1,75 @@ +# 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. +# + +"""RBAC action implementations""" + +from osc_lib.command import command +from osc_lib import utils + +from openstackclient.i18n import _ + + +def _get_columns(item): + columns = list(item.keys()) + if 'tenant_id' in columns: + columns.remove('tenant_id') + columns.append('project_id') + if 'target_tenant' in columns: + columns.remove('target_tenant') + columns.append('target_project') + return tuple(sorted(columns)) + + +class ListNetworkRBAC(command.Lister): + """List network RBAC policies""" + + def take_action(self, parsed_args): + client = self.app.client_manager.network + + columns = ( + 'id', + 'object_type', + 'object_id', + ) + column_headers = ( + 'ID', + 'Object Type', + 'Object ID', + ) + + data = client.rbac_policies() + return (column_headers, + (utils.get_item_properties( + s, columns, + ) for s in data)) + + +class ShowNetworkRBAC(command.ShowOne): + """Display network RBAC policy details""" + + def get_parser(self, prog_name): + parser = super(ShowNetworkRBAC, self).get_parser(prog_name) + parser.add_argument( + 'rbac_policy', + metavar="", + help=_("RBAC policy (ID only)") + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.find_rbac_policy(parsed_args.rbac_policy, + ignore_missing=False) + columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns) + return columns, data diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 50d9899cb0..786d0d59ca 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -480,6 +480,57 @@ def get_ports(ports=None, count=2): return mock.MagicMock(side_effect=ports) +class FakeNetworkRBAC(object): + """Fake one or more network rbac policies.""" + + @staticmethod + def create_one_network_rbac(attrs=None): + """Create a fake network rbac + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object, with id, action, target_tenant, + tenant_id, type + """ + attrs = attrs or {} + + # Set default attributes + rbac_attrs = { + 'id': 'rbac-id-' + uuid.uuid4().hex, + 'object_type': 'network', + 'object_id': 'object-id-' + uuid.uuid4().hex, + 'action': 'access_as_shared', + 'target_tenant': 'target-tenant-' + uuid.uuid4().hex, + 'tenant_id': 'tenant-id-' + uuid.uuid4().hex, + } + rbac_attrs.update(attrs) + rbac = fakes.FakeResource(info=copy.deepcopy(rbac_attrs), + loaded=True) + # Set attributes with special mapping in OpenStack SDK. + rbac.project_id = rbac_attrs['tenant_id'] + rbac.target_project = rbac_attrs['target_tenant'] + return rbac + + @staticmethod + def create_network_rbacs(attrs=None, count=2): + """Create multiple fake network rbac policies. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of rbac policies to fake + :return: + A list of FakeResource objects faking the rbac policies + """ + rbac_policies = [] + for i in range(0, count): + rbac_policies.append(FakeNetworkRBAC. + create_one_network_rbac(attrs)) + + return rbac_policies + + class FakeRouter(object): """Fake one or more routers.""" diff --git a/openstackclient/tests/network/v2/test_network_rbac.py b/openstackclient/tests/network/v2/test_network_rbac.py new file mode 100644 index 0000000000..057e0adc74 --- /dev/null +++ b/openstackclient/tests/network/v2/test_network_rbac.py @@ -0,0 +1,122 @@ +# 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_rbac +from openstackclient.tests.network.v2 import fakes as network_fakes +from openstackclient.tests import utils as tests_utils + + +class TestNetworkRBAC(network_fakes.TestNetworkV2): + + def setUp(self): + super(TestNetworkRBAC, self).setUp() + + # Get a shortcut to the network client + self.network = self.app.client_manager.network + + +class TestListNetworkRABC(TestNetworkRBAC): + + # The network rbac policies going to be listed up. + rbac_policies = network_fakes.FakeNetworkRBAC.create_network_rbacs(count=3) + + columns = ( + 'ID', + 'Object Type', + 'Object ID', + ) + + data = [] + for r in rbac_policies: + data.append(( + r.id, + r.object_type, + r.object_id, + )) + + def setUp(self): + super(TestListNetworkRABC, self).setUp() + + # Get the command object to test + self.cmd = network_rbac.ListNetworkRBAC(self.app, self.namespace) + + self.network.rbac_policies = mock.Mock(return_value=self.rbac_policies) + + def test_network_rbac_list(self): + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + self.network.rbac_policies.assert_called_with() + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + +class TestShowNetworkRBAC(TestNetworkRBAC): + + rbac_policy = network_fakes.FakeNetworkRBAC.create_one_network_rbac() + + columns = ( + 'action', + 'id', + 'object_id', + 'object_type', + 'project_id', + 'target_project', + ) + + data = [ + rbac_policy.action, + rbac_policy.id, + rbac_policy.object_id, + rbac_policy.object_type, + rbac_policy.tenant_id, + rbac_policy.target_tenant, + ] + + def setUp(self): + super(TestShowNetworkRBAC, self).setUp() + + # Get the command object to test + self.cmd = network_rbac.ShowNetworkRBAC(self.app, self.namespace) + + self.network.find_rbac_policy = mock.Mock( + return_value=self.rbac_policy) + + def test_show_no_options(self): + arglist = [] + verifylist = [] + + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_network_rbac_show_all_options(self): + arglist = [ + self.rbac_policy.object_id, + ] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + self.network.find_rbac_policy.assert_called_with( + self.rbac_policy.object_id, ignore_missing=False + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) diff --git a/releasenotes/notes/bp-neutron-client-rbac-bbd7b545b50d2bdf.yaml b/releasenotes/notes/bp-neutron-client-rbac-bbd7b545b50d2bdf.yaml new file mode 100644 index 0000000000..ad5c02bad3 --- /dev/null +++ b/releasenotes/notes/bp-neutron-client-rbac-bbd7b545b50d2bdf.yaml @@ -0,0 +1,4 @@ +--- +features: + - Add ``network rbac list`` and ``network rbac show`` commands. + [Blueprint `neutron-client-rbac `_] diff --git a/setup.cfg b/setup.cfg index 5e26d38dbd..c3d3f34e6c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -348,6 +348,9 @@ openstack.network.v2 = network_set = openstackclient.network.v2.network:SetNetwork network_show = openstackclient.network.v2.network:ShowNetwork + network_rbac_list = openstackclient.network.v2.network_rbac:ListNetworkRBAC + network_rbac_show = openstackclient.network.v2.network_rbac:ShowNetworkRBAC + network_segment_list = openstackclient.network.v2.network_segment:ListNetworkSegment network_segment_show = openstackclient.network.v2.network_segment:ShowNetworkSegment From c7b137e42153757d359966f54d50d742a35556e8 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Mon, 27 Jun 2016 15:27:34 +0800 Subject: [PATCH 1045/3095] Modify some unusual help messages in computev2 Some command help messages are unusual, for example: common help message for a list command: "list objects" unusual help message: "list obejcets command" I think we should keep help message consistent, so I modify the unusual help messages in computev2. Change-Id: Ic5f11eba1a4397949e85d91cc067519752e89bff --- doc/source/command-objects/compute-agent.rst | 4 ++-- doc/source/command-objects/compute-service.rst | 6 +++--- doc/source/command-objects/host.rst | 4 ++-- openstackclient/compute/v2/agent.py | 6 +++--- openstackclient/compute/v2/host.py | 4 ++-- openstackclient/compute/v2/service.py | 6 +++--- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/doc/source/command-objects/compute-agent.rst b/doc/source/command-objects/compute-agent.rst index b00a981d1f..ae057dc96e 100644 --- a/doc/source/command-objects/compute-agent.rst +++ b/doc/source/command-objects/compute-agent.rst @@ -59,7 +59,7 @@ Delete compute agent(s) compute agent list ------------------ -List compute agent command +List compute agents .. program:: compute agent list .. code:: bash @@ -74,7 +74,7 @@ List compute agent command compute agent set ----------------- -Set compute agent command +Set compute agent properties .. program:: agent set .. code:: bash diff --git a/doc/source/command-objects/compute-service.rst b/doc/source/command-objects/compute-service.rst index f43b5fa636..67d3eaf7c2 100644 --- a/doc/source/command-objects/compute-service.rst +++ b/doc/source/command-objects/compute-service.rst @@ -7,7 +7,7 @@ Compute v2 compute service delete ---------------------- -Delete service command +Delete compute service(s) .. program:: compute service delete .. code:: bash @@ -23,7 +23,7 @@ Delete service command compute service list -------------------- -List service command +List compute services .. program:: compute service list .. code:: bash @@ -50,7 +50,7 @@ List service command compute service set ------------------- -Set service command +Set compute service properties .. program:: compute service set .. code:: bash diff --git a/doc/source/command-objects/host.rst b/doc/source/command-objects/host.rst index 8c34d3fb97..409b834b8d 100644 --- a/doc/source/command-objects/host.rst +++ b/doc/source/command-objects/host.rst @@ -9,7 +9,7 @@ The physical computer running a hypervisor. host list --------- -List all hosts +List hosts .. program:: host list .. code:: bash @@ -24,7 +24,7 @@ List all hosts host set -------- -Set host command +Set host properties .. program:: host set .. code:: bash diff --git a/openstackclient/compute/v2/agent.py b/openstackclient/compute/v2/agent.py index 62be2424bf..4d92395511 100644 --- a/openstackclient/compute/v2/agent.py +++ b/openstackclient/compute/v2/agent.py @@ -29,7 +29,7 @@ class CreateAgent(command.ShowOne): - """Create compute agent command""" + """Create compute agent""" def get_parser(self, prog_name): parser = super(CreateAgent, self).get_parser(prog_name) @@ -112,7 +112,7 @@ def take_action(self, parsed_args): class ListAgent(command.Lister): - """List compute agent command""" + """List compute agents""" def get_parser(self, prog_name): parser = super(ListAgent, self).get_parser(prog_name) @@ -142,7 +142,7 @@ def take_action(self, parsed_args): class SetAgent(command.Command): - """Set compute agent command""" + """Set compute agent properties""" def get_parser(self, prog_name): parser = super(SetAgent, self).get_parser(prog_name) diff --git a/openstackclient/compute/v2/host.py b/openstackclient/compute/v2/host.py index 7856758933..4785377e27 100644 --- a/openstackclient/compute/v2/host.py +++ b/openstackclient/compute/v2/host.py @@ -22,7 +22,7 @@ class ListHost(command.Lister): - """List host command""" + """List hosts""" def get_parser(self, prog_name): parser = super(ListHost, self).get_parser(prog_name) @@ -107,7 +107,7 @@ def take_action(self, parsed_args): class ShowHost(command.Lister): - """Show host command""" + """Display host details""" def get_parser(self, prog_name): parser = super(ShowHost, self).get_parser(prog_name) diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index 2e40dd7f47..1441114baf 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.py @@ -29,7 +29,7 @@ class DeleteService(command.Command): - """Delete service command""" + """Delete compute service(s)""" def get_parser(self, prog_name): parser = super(DeleteService, self).get_parser(prog_name) @@ -47,7 +47,7 @@ def take_action(self, parsed_args): class ListService(command.Lister): - """List service command""" + """List compute services""" def get_parser(self, prog_name): parser = super(ListService, self).get_parser(prog_name) @@ -101,7 +101,7 @@ def take_action(self, parsed_args): class SetService(command.Command): - """Set service command""" + """Set compute service properties""" def get_parser(self, prog_name): parser = super(SetService, self).get_parser(prog_name) From 0106447cffa6cbfc3eea27e8a02ef677c69e97a2 Mon Sep 17 00:00:00 2001 From: sunyajing Date: Mon, 27 Jun 2016 16:09:16 +0800 Subject: [PATCH 1046/3095] Make set/unset command in volume pass normally when nothing specified Change-Id: Iac2d1b4ba9fa6358dc9317054af970eae8d49e84 --- openstackclient/volume/v2/volume.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index e54395fa01..be2388fb2f 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -457,6 +457,3 @@ def take_action(self, parsed_args): if parsed_args.image_property: volume_client.volumes.delete_image_metadata( volume.id, parsed_args.image_property) - - if (not parsed_args.image_property and not parsed_args.property): - LOG.error(_("No changes requested")) From fc719f998ce5f826d42f49aecf4f437e6d07857a Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Mon, 27 Jun 2016 16:59:51 +0800 Subject: [PATCH 1047/3095] Add "--force" option to "backup create" command in volumev2 Cinder V2 API supports creating volume backup with "--force" option. However, OSC doesn't support this argument. So this patch add the "--force" option to allow users to back up a in-use volume. Change-Id: I326f8d6172b2830da4cf1317348af50142cc5490 Closes-Bug: #1596443 --- doc/source/command-objects/backup.rst | 5 +++++ openstackclient/tests/volume/v2/test_backup.py | 8 ++++++-- openstackclient/volume/v2/backup.py | 9 ++++++++- releasenotes/notes/bug-1596443-9e2af267e91d1643.yaml | 6 ++++++ 4 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/bug-1596443-9e2af267e91d1643.yaml diff --git a/doc/source/command-objects/backup.rst b/doc/source/command-objects/backup.rst index 9d7fb95d68..abe1aa26f2 100644 --- a/doc/source/command-objects/backup.rst +++ b/doc/source/command-objects/backup.rst @@ -16,6 +16,7 @@ Create new backup [--container ] [--name ] [--description ] + [--force] .. option:: --container @@ -30,6 +31,10 @@ Create new backup Description of the backup +.. option:: --force + + Allow to back up an in-use volume + .. _backup_create-backup: .. describe:: diff --git a/openstackclient/tests/volume/v2/test_backup.py b/openstackclient/tests/volume/v2/test_backup.py index 8a151a9108..ba0f1c1811 100644 --- a/openstackclient/tests/volume/v2/test_backup.py +++ b/openstackclient/tests/volume/v2/test_backup.py @@ -72,12 +72,14 @@ def test_backup_create(self): "--name", self.new_backup.name, "--description", self.new_backup.description, "--container", self.new_backup.container, + "--force", self.new_backup.volume_id, ] verifylist = [ ("name", self.new_backup.name), ("description", self.new_backup.description), ("container", self.new_backup.container), + ("force", True), ("volume", self.new_backup.volume_id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -88,7 +90,8 @@ def test_backup_create(self): self.new_backup.volume_id, container=self.new_backup.container, name=self.new_backup.name, - description=self.new_backup.description + description=self.new_backup.description, + force=True, ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -112,7 +115,8 @@ def test_backup_create_without_name(self): self.new_backup.volume_id, container=self.new_backup.container, name=None, - description=self.new_backup.description + description=self.new_backup.description, + force=False, ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) diff --git a/openstackclient/volume/v2/backup.py b/openstackclient/volume/v2/backup.py index 6a44e30c57..e83fb136f6 100644 --- a/openstackclient/volume/v2/backup.py +++ b/openstackclient/volume/v2/backup.py @@ -48,6 +48,12 @@ def get_parser(self, prog_name): metavar="", help=_("Optional backup container name") ) + parser.add_argument( + '--force', + action='store_true', + default=False, + help=_("Allow to back up an in-use volume") + ) return parser def take_action(self, parsed_args): @@ -58,7 +64,8 @@ def take_action(self, parsed_args): volume_id, container=parsed_args.container, name=parsed_args.name, - description=parsed_args.description + description=parsed_args.description, + force=parsed_args.force, ) backup._info.pop("links", None) return zip(*sorted(six.iteritems(backup._info))) diff --git a/releasenotes/notes/bug-1596443-9e2af267e91d1643.yaml b/releasenotes/notes/bug-1596443-9e2af267e91d1643.yaml new file mode 100644 index 0000000000..cf4e85c0ff --- /dev/null +++ b/releasenotes/notes/bug-1596443-9e2af267e91d1643.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--force`` option to ``backup create`` command to allow users to + back up an in-use volume. + [Bug `1596443 `_] From ed64788cf1a247ca03174770d43d8c7816cc0ad1 Mon Sep 17 00:00:00 2001 From: reedip Date: Thu, 14 Apr 2016 18:11:52 +0900 Subject: [PATCH 1048/3095] Add command to unset information from Routers This patch introduces the ``router unset`` command to clear the routing information from the routers. Implements: blueprint network-property-unset Change-Id: Iac8d32ca42fb28878805b4b58ab411b67fa6555b --- doc/source/command-objects/router.rst | 24 +++++++++ openstackclient/network/v2/router.py | 43 ++++++++++++++++ .../tests/network/v2/test_router.py | 51 +++++++++++++++++++ .../notes/unset-router-7b0cbd9518bb1de6.yaml | 7 +++ setup.cfg | 1 + 5 files changed, 126 insertions(+) create mode 100644 releasenotes/notes/unset-router-7b0cbd9518bb1de6.yaml diff --git a/doc/source/command-objects/router.rst b/doc/source/command-objects/router.rst index 9ca7661ede..9d25dc6fac 100644 --- a/doc/source/command-objects/router.rst +++ b/doc/source/command-objects/router.rst @@ -240,3 +240,27 @@ Display router details .. describe:: Router to display (name or ID) + +router unset +------------ + +Unset router properties + +.. program:: router unset +.. code:: bash + + os router unset + [--route destination=,gateway=] + + +.. option:: --route destination=,gateway= + + Routes to be removed from the router + destination: destination subnet (in CIDR notation) + gateway: nexthop IP address + (repeat option to unset multiple routes) + +.. _router_unset-router: +.. describe:: + + Router to modify (name or ID) diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 6271a8784f..8a09543f98 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -14,6 +14,7 @@ """Router action implementations""" import argparse +import copy import json import logging @@ -462,3 +463,45 @@ def take_action(self, parsed_args): columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) return (columns, data) + + +class UnsetRouter(command.Command): + """Unset router properties""" + + def get_parser(self, prog_name): + parser = super(UnsetRouter, self).get_parser(prog_name) + parser.add_argument( + '--route', + metavar='destination=,gateway=', + action=parseractions.MultiKeyValueAction, + dest='routes', + default=None, + required_keys=['destination', 'gateway'], + help=_("Routes to be removed from the router " + "destination: destination subnet (in CIDR notation) " + "gateway: nexthop IP address " + "(repeat option to unset multiple routes)")) + parser.add_argument( + 'router', + metavar="", + help=_("Router to modify (name or ID)") + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.find_router(parsed_args.router, ignore_missing=False) + tmp_routes = copy.deepcopy(obj.routes) + attrs = {} + if parsed_args.routes: + try: + for route in parsed_args.routes: + tmp_routes.remove(route) + except ValueError: + msg = (_("Router does not contain route %s") % route) + raise exceptions.CommandError(msg) + for route in tmp_routes: + route['nexthop'] = route.pop('gateway') + attrs['routes'] = tmp_routes + if attrs: + client.update_router(obj, **attrs) diff --git a/openstackclient/tests/network/v2/test_router.py b/openstackclient/tests/network/v2/test_router.py index e3da253aa7..1ef4707b70 100644 --- a/openstackclient/tests/network/v2/test_router.py +++ b/openstackclient/tests/network/v2/test_router.py @@ -698,3 +698,54 @@ def test_show_all_options(self): self._router.name, ignore_missing=False) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + + +class TestUnsetRouter(TestRouter): + + def setUp(self): + super(TestUnsetRouter, self).setUp() + self._testrouter = network_fakes.FakeRouter.create_one_router( + {'routes': [{"destination": "192.168.101.1/24", + "gateway": "172.24.4.3"}, + {"destination": "192.168.101.2/24", + "gateway": "172.24.4.3"}], }) + self.fake_subnet = network_fakes.FakeSubnet.create_one_subnet() + self.network.find_router = mock.Mock(return_value=self._testrouter) + self.network.update_router = mock.Mock(return_value=None) + # Get the command object to test + self.cmd = router.UnsetRouter(self.app, self.namespace) + + def test_unset_router_params(self): + arglist = [ + '--route', 'destination=192.168.101.1/24,gateway=172.24.4.3', + self._testrouter.name, + ] + verifylist = [ + ('routes', [ + {"destination": "192.168.101.1/24", "gateway": "172.24.4.3"}]), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = { + 'routes': [{"destination": "192.168.101.2/24", + "nexthop": "172.24.4.3"}], + } + self.network.update_router.assert_called_once_with( + self._testrouter, **attrs) + self.assertIsNone(result) + + def test_unset_router_wrong_routes(self): + arglist = [ + '--route', 'destination=192.168.101.1/24,gateway=172.24.4.2', + self._testrouter.name, + ] + verifylist = [ + ('routes', [ + {"destination": "192.168.101.1/24", "gateway": "172.24.4.2"}]), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, parsed_args) diff --git a/releasenotes/notes/unset-router-7b0cbd9518bb1de6.yaml b/releasenotes/notes/unset-router-7b0cbd9518bb1de6.yaml new file mode 100644 index 0000000000..6811c84e57 --- /dev/null +++ b/releasenotes/notes/unset-router-7b0cbd9518bb1de6.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Add a new command ``router unset`` to clear the information + of routes from the router. + [ Blueprint `network-property-unset `_] + diff --git a/setup.cfg b/setup.cfg index 5e26d38dbd..d7e63b957b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -366,6 +366,7 @@ openstack.network.v2 = router_remove_subnet = openstackclient.network.v2.router:RemoveSubnetFromRouter router_set = openstackclient.network.v2.router:SetRouter router_show = openstackclient.network.v2.router:ShowRouter + router_unset = openstackclient.network.v2.router:UnsetRouter security_group_create = openstackclient.network.v2.security_group:CreateSecurityGroup security_group_delete = openstackclient.network.v2.security_group:DeleteSecurityGroup From 1d85a3f089121d035f1bcf5bcffb1b9c506c2e36 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Mon, 27 Jun 2016 19:53:52 +0800 Subject: [PATCH 1049/3095] Change "ID only" to "name or ID" for backup commands Tested "find_resulce()" can find a backup by name or ID. But some help messages and doc were "ID only", this patch modify them. Change-Id: I22dcc3a0b40f2bafabb6d26498158e2468037312 --- doc/source/command-objects/backup.rst | 6 +++--- openstackclient/volume/v1/backup.py | 6 +++--- openstackclient/volume/v2/backup.py | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/source/command-objects/backup.rst b/doc/source/command-objects/backup.rst index 9d7fb95d68..aed3c240ac 100644 --- a/doc/source/command-objects/backup.rst +++ b/doc/source/command-objects/backup.rst @@ -49,7 +49,7 @@ Delete backup(s) .. _backup_delete-backup: .. describe:: - Backup(s) to delete (ID only) + Backup(s) to delete (name or ID) backup list ----------- @@ -81,7 +81,7 @@ Restore backup .. _backup_restore-backup: .. describe:: - Backup to restore (ID only) + Backup to restore (name or ID) .. describe:: @@ -101,4 +101,4 @@ Display backup details .. _backup_show-backup: .. describe:: - Backup to display (ID only) + Backup to display (name or ID) diff --git a/openstackclient/volume/v1/backup.py b/openstackclient/volume/v1/backup.py index 212c74eff2..5f34a2c51f 100644 --- a/openstackclient/volume/v1/backup.py +++ b/openstackclient/volume/v1/backup.py @@ -76,7 +76,7 @@ def get_parser(self, prog_name): 'backups', metavar='', nargs="+", - help=_('Backup(s) to delete (ID only)'), + help=_('Backup(s) to delete (name or ID)'), ) return parser @@ -150,7 +150,7 @@ def get_parser(self, prog_name): parser.add_argument( 'backup', metavar='', - help=_('Backup to restore (ID only)') + help=_('Backup to restore (name or ID)') ) parser.add_argument( 'volume', @@ -177,7 +177,7 @@ def get_parser(self, prog_name): parser.add_argument( 'backup', metavar='', - help=_('Backup to display (ID only)') + help=_('Backup to display (name or ID)') ) return parser diff --git a/openstackclient/volume/v2/backup.py b/openstackclient/volume/v2/backup.py index 6a44e30c57..2cf8f3675b 100644 --- a/openstackclient/volume/v2/backup.py +++ b/openstackclient/volume/v2/backup.py @@ -147,7 +147,7 @@ def get_parser(self, prog_name): parser.add_argument( "backup", metavar="", - help=_("Backup to restore (ID only)") + help=_("Backup to restore (name or ID)") ) parser.add_argument( "volume", From 044a46ed5f040020cf80adc1ace987802344e87d Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Mon, 27 Jun 2016 11:26:18 -0400 Subject: [PATCH 1050/3095] skip image tag tests the command ``image set tag `` is now failing skip the test so our gate isn't broken. Change-Id: I05e415b212e76aef62c1b0b966863573eff338e9 Partial-Bug: #1596573 --- functional/tests/image/v2/test_image.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/functional/tests/image/v2/test_image.py b/functional/tests/image/v2/test_image.py index 6a33ad88e1..2e2b59bbbd 100644 --- a/functional/tests/image/v2/test_image.py +++ b/functional/tests/image/v2/test_image.py @@ -13,6 +13,8 @@ import os import uuid +import testtools + from functional.common import test @@ -66,6 +68,7 @@ def test_image_metadata(self): raw_output = self.openstack('image show ' + self.NAME + opts) self.assertEqual(self.NAME + "\na='b', c='d'\n", raw_output) + @testtools.skip("skip until bug 1596573 is resolved") def test_image_unset(self): opts = self.get_show_opts(["name", "tags", "properties"]) self.openstack('image set --tag 01 ' + self.NAME) From 1dce52ace8aaa2a6220baf9eb1716bcc0f05f676 Mon Sep 17 00:00:00 2001 From: "zhang.xiuhua" Date: Fri, 3 Jun 2016 16:20:42 +0800 Subject: [PATCH 1051/3095] Modify few words and change output format for command "ip floating list" According to OpenStack Documentation, preferred word choice: Link: http://docs.openstack.org/contributor-guide/writing-style/word-choice.html Change from 'etc.' to 'and so on' Change-Id: Id8a5cc99a8bba8ac7531636248edaca8f98da917 --- doc/source/backwards-incompatible.rst | 54 +++++++++++++++++---------- doc/source/commands.rst | 9 +++-- doc/source/humaninterfaceguide.rst | 2 +- 3 files changed, 40 insertions(+), 25 deletions(-) diff --git a/doc/source/backwards-incompatible.rst b/doc/source/backwards-incompatible.rst index f86cfce4c2..da3c1b6417 100644 --- a/doc/source/backwards-incompatible.rst +++ b/doc/source/backwards-incompatible.rst @@ -124,7 +124,7 @@ Releases Before 3.0 8. `region` commands no longer support `url` - The Keystone team removed support for thr `url` attribute from the client + The Keystone team removed support for the `url` attribute from the client and server side. Changes to the `create`, `set` and `list` commands for regions have been affected. @@ -184,27 +184,41 @@ Releases Before 3.0 14. Output of `ip floating list` command has changed. - When using Compute v2, the original output of `ip floating list` command is: - +----+--------+------------+----------+-------------+ - | ID | Pool | IP | Fixed IP | Instance ID | - +----+--------+-----------------------+-------------+ - | 1 | public | 172.24.4.1 | None | None | - +----+--------+------------+----------+-------------+ + When using Compute v2, the original output is: + + .. code-block:: bash + + # ip floating list + + +----+--------+------------+----------+-------------+ + | ID | Pool | IP | Fixed IP | Instance ID | + +----+--------+-----------------------+-------------+ + | 1 | public | 172.24.4.1 | None | None | + +----+--------+------------+----------+-------------+ Now it changes to: - +----+---------------------+------------------+-----------+--------+ - | ID | Floating IP Address | Fixed IP Address | Server ID | Pool | - +----+---------------------+------------------+-----------+--------+ - | 1 | 172.24.4.1 | None | None | public | - +----+---------------------+------------------+-----------+--------+ - - When using Network v2, the output of `ip floating list` command is: - +--------------------------------------+---------------------+------------------+------+ - | ID | Floating IP Address | Fixed IP Address | Port | - +--------------------------------------+---------------------+------------------+------+ - | 1976df86-e66a-4f96-81bd-c6ffee6407f1 | 172.24.4.3 | None | None | - +--------------------------------------+---------------------+------------------+------+ - which is different from Compute v2. + + .. code-block:: bash + + # ip floating list + + +----+---------------------+------------------+-----------+--------+ + | ID | Floating IP Address | Fixed IP Address | Server ID | Pool | + +----+---------------------+------------------+-----------+--------+ + | 1 | 172.24.4.1 | None | None | public | + +----+---------------------+------------------+-----------+--------+ + + When using Network v2, which is different from Compute v2. The output is: + + .. code-block:: bash + + # ip floating list + + +--------------------------------------+---------------------+------------------+------+ + | ID | Floating IP Address | Fixed IP Address | Port | + +--------------------------------------+---------------------+------------------+------+ + | 1976df86-e66a-4f96-81bd-c6ffee6407f1 | 172.24.4.3 | None | None | + +--------------------------------------+---------------------+------------------+------+ * In favor of: Use `ip floating list` command * As of: NA diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 9fb0555d7c..9d8ad6fd26 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -8,11 +8,12 @@ Commands take the form:: openstack [] [] [] -* All long options names begin with two dashes (``--``) and use a single dash +.. NOTE:: + + All long options names begin with two dashes (``--``) and use a single dash (``-``) internally between words (``--like-this``). Underscores (``_``) are not used in option names. - Global Options -------------- @@ -78,7 +79,7 @@ referring to both Compute and Volume quotas. * ``command``: (**Internal**) installed commands in the OSC process * ``compute agent``: (**Compute**) a cloud Compute agent available to a hypervisor * ``compute service``: (**Compute**) a cloud Compute process running on a host -* ``configuration``: (**Internal**) openstack client configuration +* ``configuration``: (**Internal**) OpenStack client configuration * ``console log``: (**Compute**) server console text dump * ``console url``: (**Compute**) server remote console URL * ``consumer``: (**Identity**) OAuth-based delegatee @@ -89,7 +90,7 @@ referring to both Compute and Volume quotas. * ``endpoint``: (**Identity**) the base URL used to contact a specific service * ``extension``: (**Compute**, **Identity**, **Network**, **Volume**) OpenStack server API extensions * ``federation protocol``: (**Identity**) the underlying protocol used while federating identities -* ``flavor``: (**Compute**) predefined server configurations: ram, root disk, etc +* ``flavor``: (**Compute**) predefined server configurations: ram, root disk and so on * ``group``: (**Identity**) a grouping of users * ``host``: (**Compute**) - the physical computer running compute services * ``hypervisor``: (**Compute**) the virtual machine manager diff --git a/doc/source/humaninterfaceguide.rst b/doc/source/humaninterfaceguide.rst index 9cca5aa758..5d3c48dcca 100644 --- a/doc/source/humaninterfaceguide.rst +++ b/doc/source/humaninterfaceguide.rst @@ -85,7 +85,7 @@ Simplicity To best support new users and create straight forward interactions, designs should be as simple as possible. When crafting new commands, designs should minimize the amount of noise present in output: large amounts of -nonessential data, overabundance of possible actions, etc. Designs should +nonessential data, overabundance of possible actions and so on. Designs should focus on the intent of the command, requiring only the necessary components and either removing superfluous elements or making them accessible through optional arguments. An example of this principle occurs From a9da91285f623171b3c25b58f68f121b364863ef Mon Sep 17 00:00:00 2001 From: sunyajing Date: Fri, 24 Jun 2016 12:32:29 +0800 Subject: [PATCH 1052/3095] Update Fakes.py and unit tests for commands in identity V2.0 Update remaining commands:role, service, user, token. Change-Id: I06eed60dd2f312bad6076c78b53cd07bcd4cd55c Partially-Implements: blueprint refactor-identity-unit-test --- openstackclient/tests/identity/v2_0/fakes.py | 70 ++++- .../tests/identity/v2_0/test_endpoint.py | 6 +- .../tests/identity/v2_0/test_role.py | 227 ++++++--------- .../tests/identity/v2_0/test_service.py | 121 ++++---- .../tests/identity/v2_0/test_token.py | 9 +- .../tests/identity/v2_0/test_user.py | 269 ++++++++---------- 6 files changed, 319 insertions(+), 383 deletions(-) diff --git a/openstackclient/tests/identity/v2_0/fakes.py b/openstackclient/tests/identity/v2_0/fakes.py index 10b8b49ed9..662d56b657 100644 --- a/openstackclient/tests/identity/v2_0/fakes.py +++ b/openstackclient/tests/identity/v2_0/fakes.py @@ -76,19 +76,19 @@ } token_expires = '2014-01-01T00:00:00Z' -token_id = 'tttttttt-tttt-tttt-tttt-tttttttttttt' +token_id = 'token-id-' + uuid.uuid4().hex TOKEN = { 'expires': token_expires, 'id': token_id, - 'tenant_id': project_id, - 'user_id': user_id, + 'tenant_id': 'project-id', + 'user_id': 'user-id', } UNSCOPED_TOKEN = { 'expires': token_expires, 'id': token_id, - 'user_id': user_id, + 'user_id': 'user-id', } endpoint_name = service_name @@ -110,8 +110,6 @@ 'publicurl': endpoint_publicurl, 'service_id': endpoint_service_id, } -SERVICE_NAME = 'service-name-' + uuid.uuid4().hex -SERVICE_ID = 'service-id-' + uuid.uuid4().hex def fake_auth_ref(fake_token, fake_service=None): @@ -244,7 +242,7 @@ def create_catalog(attrs=None): # Set default attributes. catalog_info = { - 'id': SERVICE_ID, + 'id': 'service-id-' + uuid.uuid4().hex, 'type': 'compute', 'name': 'supernova', 'endpoints': [ @@ -295,8 +293,8 @@ def create_one_project(attrs=None): # set default attributes. project_info = { - 'id': 'project-id' + uuid.uuid4().hex, - 'name': 'project-name' + uuid.uuid4().hex, + 'id': 'project-id-' + uuid.uuid4().hex, + 'name': 'project-name-' + uuid.uuid4().hex, 'description': 'project_description', 'enabled': True, } @@ -341,14 +339,14 @@ def create_one_endpoint(attrs=None): # set default attributes. endpoint_info = { - 'service_name': SERVICE_NAME, + 'service_name': 'service-name-' + uuid.uuid4().hex, 'adminurl': 'http://endpoint_adminurl', 'region': 'endpoint_region', 'internalurl': 'http://endpoint_internalurl', 'service_type': 'service_type', 'id': 'endpoint-id-' + uuid.uuid4().hex, 'publicurl': 'http://endpoint_publicurl', - 'service_id': SERVICE_ID, + 'service_id': 'service-name-' + uuid.uuid4().hex, } endpoint_info.update(attrs) @@ -392,8 +390,8 @@ def create_one_service(attrs=None): # set default attributes. service_info = { - 'id': SERVICE_ID, - 'name': SERVICE_NAME, + 'id': 'service-id-' + uuid.uuid4().hex, + 'name': 'service-name-' + uuid.uuid4().hex, 'description': 'service_description', 'type': 'service_type', @@ -464,3 +462,49 @@ def create_roles(attrs=None, count=2): roles.append(FakeRole.create_one_role(attrs)) return roles + + +class FakeUser(object): + """Fake one or more user.""" + + @staticmethod + def create_one_user(attrs=None): + """Create a fake user. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object, with id, name, and so on + """ + attrs = attrs or {} + + # set default attributes. + user_info = { + 'id': 'user-id-' + uuid.uuid4().hex, + 'name': 'user-name-' + uuid.uuid4().hex, + 'tenantId': 'project-id-' + uuid.uuid4().hex, + 'email': 'admin@openstack.org', + 'enabled': True, + } + user_info.update(attrs) + + user = fakes.FakeResource(info=copy.deepcopy(user_info), + loaded=True) + return user + + @staticmethod + def create_users(attrs=None, count=2): + """Create multiple fake users. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of users to fake + :return: + A list of FakeResource objects faking the users + """ + users = [] + for i in range(0, count): + users.append(FakeUser.create_one_user(attrs)) + + return users diff --git a/openstackclient/tests/identity/v2_0/test_endpoint.py b/openstackclient/tests/identity/v2_0/test_endpoint.py index 0b539702be..b2b6d0f180 100644 --- a/openstackclient/tests/identity/v2_0/test_endpoint.py +++ b/openstackclient/tests/identity/v2_0/test_endpoint.py @@ -17,8 +17,12 @@ class TestEndpoint(identity_fakes.TestIdentityv2): - fake_endpoint = identity_fakes.FakeEndpoint.create_one_endpoint() fake_service = identity_fakes.FakeService.create_one_service() + attr = { + 'service_name': fake_service.name, + 'service_id': fake_service.id, + } + fake_endpoint = identity_fakes.FakeEndpoint.create_one_endpoint(attr) def setUp(self): super(TestEndpoint, self).setUp() diff --git a/openstackclient/tests/identity/v2_0/test_role.py b/openstackclient/tests/identity/v2_0/test_role.py index 74bd8f274f..3d379356ef 100644 --- a/openstackclient/tests/identity/v2_0/test_role.py +++ b/openstackclient/tests/identity/v2_0/test_role.py @@ -13,14 +13,12 @@ # under the License. # -import copy import mock from keystoneauth1 import exceptions as ks_exc from osc_lib import exceptions from openstackclient.identity.v2_0 import role -from openstackclient.tests import fakes from openstackclient.tests.identity.v2_0 import fakes as identity_fakes @@ -34,6 +32,12 @@ class TestRole(identity_fakes.TestIdentityv2): ] fake_service = identity_fakes.FakeService.create_one_service(attr) fake_role = identity_fakes.FakeRole.create_one_role() + fake_project = identity_fakes.FakeProject.create_one_project() + attr = {} + attr = { + 'tenantId': fake_project.id, + } + fake_user = identity_fakes.FakeUser.create_one_user(attr) def setUp(self): super(TestRole, self).setUp() @@ -63,42 +67,26 @@ class TestRoleAdd(TestRole): def setUp(self): super(TestRoleAdd, self).setUp() - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) + self.projects_mock.get.return_value = self.fake_project - self.users_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.USER), - loaded=True, - ) + self.users_mock.get.return_value = self.fake_user - self.roles_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.ROLE), - loaded=True, - ) - self.roles_mock.add_user_role.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.ROLE), - loaded=True, - ) + self.roles_mock.get.return_value = self.fake_role + self.roles_mock.add_user_role.return_value = self.fake_role # Get the command object to test self.cmd = role.AddRole(self.app, None) def test_role_add(self): arglist = [ - '--project', identity_fakes.project_name, - '--user', identity_fakes.user_name, - identity_fakes.role_name, + '--project', self.fake_project.name, + '--user', self.fake_user.name, + self.fake_role.name, ] verifylist = [ - ('project', identity_fakes.project_name), - ('user', identity_fakes.user_name), - ('role', identity_fakes.role_name), + ('project', self.fake_project.name), + ('user', self.fake_user.name), + ('role', self.fake_role.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -109,49 +97,46 @@ def test_role_add(self): # RoleManager.add_user_role(user, role, tenant=None) self.roles_mock.add_user_role.assert_called_with( - identity_fakes.user_id, - identity_fakes.role_id, - identity_fakes.project_id, + self.fake_user.id, + self.fake_role.id, + self.fake_project.id, ) collist = ('id', 'name') self.assertEqual(collist, columns) datalist = ( - identity_fakes.role_id, - identity_fakes.role_name, + self.fake_role.id, + self.fake_role.name, ) self.assertEqual(datalist, data) class TestRoleCreate(TestRole): + fake_role_c = identity_fakes.FakeRole.create_one_role() columns = ( 'id', 'name' ) datalist = ( - identity_fakes.role_id, - identity_fakes.role_name, + fake_role_c.id, + fake_role_c.name, ) def setUp(self): super(TestRoleCreate, self).setUp() - self.roles_mock.create.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.ROLE), - loaded=True, - ) + self.roles_mock.create.return_value = self.fake_role_c # Get the command object to test self.cmd = role.CreateRole(self.app, None) def test_role_create_no_options(self): arglist = [ - identity_fakes.role_name, + self.fake_role_c.name, ] verifylist = [ - ('role_name', identity_fakes.role_name), + ('role_name', self.fake_role_c.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -162,7 +147,7 @@ def test_role_create_no_options(self): # RoleManager.create(name) self.roles_mock.create.assert_called_with( - identity_fakes.role_name, + self.fake_role_c.name, ) self.assertEqual(self.columns, columns) @@ -175,18 +160,14 @@ def _raise_conflict(*args, **kwargs): # need to make this throw an exception... self.roles_mock.create.side_effect = _raise_conflict - self.roles_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.ROLE), - loaded=True, - ) + self.roles_mock.get.return_value = self.fake_role_c arglist = [ '--or-show', - identity_fakes.role_name, + self.fake_role_c.name, ] verifylist = [ - ('role_name', identity_fakes.role_name), + ('role_name', self.fake_role_c.name), ('or_show', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -197,11 +178,11 @@ def _raise_conflict(*args, **kwargs): columns, data = self.cmd.take_action(parsed_args) # RoleManager.get(name, description, enabled) - self.roles_mock.get.assert_called_with(identity_fakes.role_name) + self.roles_mock.get.assert_called_with(self.fake_role_c.name) # RoleManager.create(name) self.roles_mock.create.assert_called_with( - identity_fakes.role_name, + self.fake_role_c.name, ) self.assertEqual(self.columns, columns) @@ -210,10 +191,10 @@ def _raise_conflict(*args, **kwargs): def test_role_create_or_show_not_exists(self): arglist = [ '--or-show', - identity_fakes.role_name, + self.fake_role_c.name, ] verifylist = [ - ('role_name', identity_fakes.role_name), + ('role_name', self.fake_role_c.name), ('or_show', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -225,7 +206,7 @@ def test_role_create_or_show_not_exists(self): # RoleManager.create(name) self.roles_mock.create.assert_called_with( - identity_fakes.role_name, + self.fake_role_c.name, ) self.assertEqual(self.columns, columns) @@ -237,11 +218,7 @@ class TestRoleDelete(TestRole): def setUp(self): super(TestRoleDelete, self).setUp() - self.roles_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.ROLE), - loaded=True, - ) + self.roles_mock.get.return_value = self.fake_role self.roles_mock.delete.return_value = None # Get the command object to test @@ -249,17 +226,17 @@ def setUp(self): def test_role_delete_no_options(self): arglist = [ - identity_fakes.role_name, + self.fake_role.name, ] verifylist = [ - ('roles', [identity_fakes.role_name]), + ('roles', [self.fake_role.name]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.roles_mock.delete.assert_called_with( - identity_fakes.role_id, + self.fake_role.id, ) self.assertIsNone(result) @@ -269,13 +246,7 @@ class TestRoleList(TestRole): def setUp(self): super(TestRoleList, self).setUp() - self.roles_mock.list.return_value = [ - fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.ROLE), - loaded=True, - ), - ] + self.roles_mock.list.return_value = [self.fake_role] # Get the command object to test self.cmd = role.ListRole(self.app, None) @@ -295,8 +266,8 @@ def test_role_list_no_options(self): collist = ('ID', 'Name') self.assertEqual(collist, columns) datalist = (( - identity_fakes.role_id, - identity_fakes.role_name, + self.fake_role.id, + self.fake_role.name, ), ) self.assertEqual(datalist, tuple(data)) @@ -313,25 +284,11 @@ class TestUserRoleList(TestRole): def setUp(self): super(TestUserRoleList, self).setUp() - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) + self.projects_mock.get.return_value = self.fake_project - self.users_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.USER), - loaded=True, - ) + self.users_mock.get.return_value = self.fake_user - self.roles_mock.roles_for_user.return_value = [ - fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.ROLE), - loaded=True, - ), - ] + self.roles_mock.roles_for_user.return_value = [self.fake_role] # Get the command object to test self.cmd = role.ListUserRole(self.app, None) @@ -366,17 +323,17 @@ def test_user_role_list_no_options_scoped_token(self): columns, data = self.cmd.take_action(parsed_args) self.roles_mock.roles_for_user.assert_called_with( - identity_fakes.user_id, - identity_fakes.project_id, + self.fake_user.id, + self.fake_project.id, ) collist = ('ID', 'Name', 'Project', 'User') self.assertEqual(collist, columns) datalist = (( - identity_fakes.role_id, - identity_fakes.role_name, - identity_fakes.project_name, - identity_fakes.user_name, + self.fake_role.id, + self.fake_role.name, + self.fake_project.name, + self.fake_user.name, ), ) self.assertEqual(datalist, tuple(data)) @@ -388,16 +345,12 @@ def test_user_role_list_project_unscoped_token(self): self.ar_mock = mock.PropertyMock(return_value=auth_ref) type(self.app.client_manager).auth_ref = self.ar_mock - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT_2), - loaded=True, - ) + self.projects_mock.get.return_value = self.fake_project arglist = [ - '--project', identity_fakes.PROJECT_2['name'], + '--project', self.fake_project.name, ] verifylist = [ - ('project', identity_fakes.PROJECT_2['name']), + ('project', self.fake_project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -407,30 +360,26 @@ def test_user_role_list_project_unscoped_token(self): columns, data = self.cmd.take_action(parsed_args) self.roles_mock.roles_for_user.assert_called_with( - identity_fakes.user_id, - identity_fakes.PROJECT_2['id'], + self.fake_user.id, + self.fake_project.id, ) self.assertEqual(columns, columns) datalist = (( - identity_fakes.role_id, - identity_fakes.role_name, - identity_fakes.PROJECT_2['name'], - identity_fakes.user_name, + self.fake_role.id, + self.fake_role.name, + self.fake_project.name, + self.fake_user.name, ), ) self.assertEqual(datalist, tuple(data)) def test_user_role_list_project_scoped_token(self): - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT_2), - loaded=True, - ) + self.projects_mock.get.return_value = self.fake_project arglist = [ - '--project', identity_fakes.PROJECT_2['name'], + '--project', self.fake_project.name, ] verifylist = [ - ('project', identity_fakes.PROJECT_2['name']), + ('project', self.fake_project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -440,16 +389,16 @@ def test_user_role_list_project_scoped_token(self): columns, data = self.cmd.take_action(parsed_args) self.roles_mock.roles_for_user.assert_called_with( - identity_fakes.user_id, - identity_fakes.PROJECT_2['id'], + self.fake_user.id, + self.fake_project.id, ) self.assertEqual(columns, columns) datalist = (( - identity_fakes.role_id, - identity_fakes.role_name, - identity_fakes.PROJECT_2['name'], - identity_fakes.user_name, + self.fake_role.id, + self.fake_role.name, + self.fake_project.name, + self.fake_user.name, ), ) self.assertEqual(datalist, tuple(data)) @@ -459,23 +408,11 @@ class TestRoleRemove(TestRole): def setUp(self): super(TestRoleRemove, self).setUp() - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) + self.projects_mock.get.return_value = self.fake_project - self.users_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.USER), - loaded=True, - ) + self.users_mock.get.return_value = self.fake_user - self.roles_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.ROLE), - loaded=True, - ) + self.roles_mock.get.return_value = self.fake_role self.roles_mock.remove_user_role.return_value = None # Get the command object to test @@ -483,14 +420,14 @@ def setUp(self): def test_role_remove(self): arglist = [ - '--project', identity_fakes.project_name, - '--user', identity_fakes.user_name, - identity_fakes.role_name, + '--project', self.fake_project.name, + '--user', self.fake_user.name, + self.fake_role.name, ] verifylist = [ - ('role', identity_fakes.role_name), - ('project', identity_fakes.project_name), - ('user', identity_fakes.user_name), + ('role', self.fake_role.name), + ('project', self.fake_project.name), + ('user', self.fake_user.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -498,9 +435,9 @@ def test_role_remove(self): # RoleManager.remove_user_role(user, role, tenant=None) self.roles_mock.remove_user_role.assert_called_with( - identity_fakes.user_id, - identity_fakes.role_id, - identity_fakes.project_id, + self.fake_user.id, + self.fake_role.id, + self.fake_project.id, ) self.assertIsNone(result) diff --git a/openstackclient/tests/identity/v2_0/test_service.py b/openstackclient/tests/identity/v2_0/test_service.py index ba976f4c85..318fa83d70 100644 --- a/openstackclient/tests/identity/v2_0/test_service.py +++ b/openstackclient/tests/identity/v2_0/test_service.py @@ -13,14 +13,12 @@ # under the License. # -import copy - from openstackclient.identity.v2_0 import service -from openstackclient.tests import fakes from openstackclient.tests.identity.v2_0 import fakes as identity_fakes class TestService(identity_fakes.TestIdentityv2): + fake_service = identity_fakes.FakeService.create_one_service() def setUp(self): super(TestService, self).setUp() @@ -32,6 +30,7 @@ def setUp(self): class TestServiceCreate(TestService): + fake_service_c = identity_fakes.FakeService.create_one_service() columns = ( 'description', 'id', @@ -39,30 +38,26 @@ class TestServiceCreate(TestService): 'type', ) datalist = ( - identity_fakes.service_description, - identity_fakes.service_id, - identity_fakes.service_name, - identity_fakes.service_type, + fake_service_c.description, + fake_service_c.id, + fake_service_c.name, + fake_service_c.type, ) def setUp(self): super(TestServiceCreate, self).setUp() - self.services_mock.create.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.SERVICE), - loaded=True, - ) + self.services_mock.create.return_value = self.fake_service_c # Get the command object to test self.cmd = service.CreateService(self.app, None) def test_service_create_with_type_positional(self): arglist = [ - identity_fakes.service_type, + self.fake_service_c.type, ] verifylist = [ - ('type_or_name', identity_fakes.service_type), + ('type_or_name', self.fake_service_c.type), ('type', None), ('description', None), ('name', None), @@ -77,7 +72,7 @@ def test_service_create_with_type_positional(self): # ServiceManager.create(name, service_type, description) self.services_mock.create.assert_called_with( None, - identity_fakes.service_type, + self.fake_service_c.type, None, ) @@ -86,12 +81,12 @@ def test_service_create_with_type_positional(self): def test_service_create_with_type_option(self): arglist = [ - '--type', identity_fakes.service_type, - identity_fakes.service_name, + '--type', self.fake_service_c.type, + self.fake_service_c.name, ] verifylist = [ - ('type_or_name', identity_fakes.service_name), - ('type', identity_fakes.service_type), + ('type_or_name', self.fake_service_c.name), + ('type', self.fake_service_c.type), ('description', None), ('name', None), ] @@ -104,8 +99,8 @@ def test_service_create_with_type_option(self): # ServiceManager.create(name, service_type, description) self.services_mock.create.assert_called_with( - identity_fakes.service_name, - identity_fakes.service_type, + self.fake_service_c.name, + self.fake_service_c.type, None, ) @@ -114,14 +109,14 @@ def test_service_create_with_type_option(self): def test_service_create_with_name_option(self): arglist = [ - '--name', identity_fakes.service_name, - identity_fakes.service_type, + '--name', self.fake_service_c.name, + self.fake_service_c.type, ] verifylist = [ - ('type_or_name', identity_fakes.service_type), + ('type_or_name', self.fake_service_c.type), ('type', None), ('description', None), - ('name', identity_fakes.service_name), + ('name', self.fake_service_c.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -132,8 +127,8 @@ def test_service_create_with_name_option(self): # ServiceManager.create(name, service_type, description) self.services_mock.create.assert_called_with( - identity_fakes.service_name, - identity_fakes.service_type, + self.fake_service_c.name, + self.fake_service_c.type, None, ) @@ -142,15 +137,15 @@ def test_service_create_with_name_option(self): def test_service_create_description(self): arglist = [ - '--name', identity_fakes.service_name, - '--description', identity_fakes.service_description, - identity_fakes.service_type, + '--name', self.fake_service_c.name, + '--description', self.fake_service_c.description, + self.fake_service_c.type, ] verifylist = [ - ('type_or_name', identity_fakes.service_type), + ('type_or_name', self.fake_service_c.type), ('type', None), - ('description', identity_fakes.service_description), - ('name', identity_fakes.service_name), + ('description', self.fake_service_c.description), + ('name', self.fake_service_c.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -161,9 +156,9 @@ def test_service_create_description(self): # ServiceManager.create(name, service_type, description) self.services_mock.create.assert_called_with( - identity_fakes.service_name, - identity_fakes.service_type, - identity_fakes.service_description, + self.fake_service_c.name, + self.fake_service_c.type, + self.fake_service_c.description, ) self.assertEqual(self.columns, columns) @@ -175,11 +170,7 @@ class TestServiceDelete(TestService): def setUp(self): super(TestServiceDelete, self).setUp() - self.services_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.SERVICE), - loaded=True, - ) + self.services_mock.get.return_value = self.fake_service self.services_mock.delete.return_value = None # Get the command object to test @@ -187,17 +178,17 @@ def setUp(self): def test_service_delete_no_options(self): arglist = [ - identity_fakes.service_name, + self.fake_service.name, ] verifylist = [ - ('services', [identity_fakes.service_name]), + ('services', [self.fake_service.name]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.services_mock.delete.assert_called_with( - identity_fakes.service_id, + self.fake_service.id, ) self.assertIsNone(result) @@ -207,13 +198,7 @@ class TestServiceList(TestService): def setUp(self): super(TestServiceList, self).setUp() - self.services_mock.list.return_value = [ - fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.SERVICE), - loaded=True, - ), - ] + self.services_mock.list.return_value = [self.fake_service] # Get the command object to test self.cmd = service.ListService(self.app, None) @@ -233,9 +218,9 @@ def test_service_list_no_options(self): collist = ('ID', 'Name', 'Type') self.assertEqual(collist, columns) datalist = (( - identity_fakes.service_id, - identity_fakes.service_name, - identity_fakes.service_type, + self.fake_service.id, + self.fake_service.name, + self.fake_service.type, ), ) self.assertEqual(datalist, tuple(data)) @@ -258,10 +243,10 @@ def test_service_list_long(self): collist = ('ID', 'Name', 'Type', 'Description') self.assertEqual(collist, columns) datalist = (( - identity_fakes.service_id, - identity_fakes.service_name, - identity_fakes.service_type, - identity_fakes.service_description, + self.fake_service.id, + self.fake_service.name, + self.fake_service.type, + self.fake_service.description, ), ) self.assertEqual(datalist, tuple(data)) @@ -271,21 +256,17 @@ class TestServiceShow(TestService): def setUp(self): super(TestServiceShow, self).setUp() - self.services_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.SERVICE), - loaded=True, - ) + self.services_mock.get.return_value = self.fake_service # Get the command object to test self.cmd = service.ShowService(self.app, None) def test_service_show(self): arglist = [ - identity_fakes.service_name, + self.fake_service.name, ] verifylist = [ - ('service', identity_fakes.service_name), + ('service', self.fake_service.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -296,15 +277,15 @@ def test_service_show(self): # ServiceManager.get(id) self.services_mock.get.assert_called_with( - identity_fakes.service_name, + self.fake_service.name, ) collist = ('description', 'id', 'name', 'type') self.assertEqual(collist, columns) datalist = ( - identity_fakes.service_description, - identity_fakes.service_id, - identity_fakes.service_name, - identity_fakes.service_type, + self.fake_service.description, + self.fake_service.id, + self.fake_service.name, + self.fake_service.type, ) self.assertEqual(datalist, data) diff --git a/openstackclient/tests/identity/v2_0/test_token.py b/openstackclient/tests/identity/v2_0/test_token.py index 96f08e8708..bb7767070f 100644 --- a/openstackclient/tests/identity/v2_0/test_token.py +++ b/openstackclient/tests/identity/v2_0/test_token.py @@ -21,6 +21,9 @@ class TestToken(identity_fakes.TestIdentityv2): + fake_user = identity_fakes.FakeUser.create_one_user() + fake_project = identity_fakes.FakeProject.create_one_project() + def setUp(self): super(TestToken, self).setUp() @@ -57,8 +60,8 @@ def test_token_issue(self): datalist = ( auth_ref.expires, identity_fakes.token_id, - identity_fakes.project_id, - identity_fakes.user_id, + 'project-id', + 'user-id', ) self.assertEqual(datalist, data) @@ -85,7 +88,7 @@ def test_token_issue_with_unscoped_token(self): datalist = ( auth_ref.expires, identity_fakes.token_id, - identity_fakes.user_id, + 'user-id', ) self.assertEqual(datalist, data) diff --git a/openstackclient/tests/identity/v2_0/test_user.py b/openstackclient/tests/identity/v2_0/test_user.py index f7a7b08c3f..ba87124755 100644 --- a/openstackclient/tests/identity/v2_0/test_user.py +++ b/openstackclient/tests/identity/v2_0/test_user.py @@ -13,19 +13,23 @@ # under the License. # -import copy import mock from keystoneauth1 import exceptions as ks_exc from osc_lib import exceptions from openstackclient.identity.v2_0 import user -from openstackclient.tests import fakes from openstackclient.tests.identity.v2_0 import fakes as identity_fakes class TestUser(identity_fakes.TestIdentityv2): + fake_project = identity_fakes.FakeProject.create_one_project() + attr = { + 'tenantId': fake_project.id, + } + fake_user = identity_fakes.FakeUser.create_one_user(attr) + def setUp(self): super(TestUser, self).setUp() @@ -40,6 +44,12 @@ def setUp(self): class TestUserCreate(TestUser): + fake_project_c = identity_fakes.FakeProject.create_one_project() + attr = { + 'tenantId': fake_project_c.id, + } + fake_user_c = identity_fakes.FakeUser.create_one_user(attr) + columns = ( 'email', 'enabled', @@ -48,39 +58,31 @@ class TestUserCreate(TestUser): 'project_id', ) datalist = ( - identity_fakes.user_email, + fake_user_c.email, True, - identity_fakes.user_id, - identity_fakes.user_name, - identity_fakes.project_id, + fake_user_c.id, + fake_user_c.name, + fake_project_c.id, ) def setUp(self): super(TestUserCreate, self).setUp() - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) + self.projects_mock.get.return_value = self.fake_project_c - self.users_mock.create.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.USER), - loaded=True, - ) + self.users_mock.create.return_value = self.fake_user_c # Get the command object to test self.cmd = user.CreateUser(self.app, None) def test_user_create_no_options(self): arglist = [ - identity_fakes.user_name, + self.fake_user_c.name, ] verifylist = [ ('enable', False), ('disable', False), - ('name', identity_fakes.user_name), + ('name', self.fake_user_c.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -96,7 +98,7 @@ def test_user_create_no_options(self): } # UserManager.create(name, password, email, tenant_id=, enabled=) self.users_mock.create.assert_called_with( - identity_fakes.user_name, + self.fake_user_c.name, None, None, **kwargs @@ -108,10 +110,10 @@ def test_user_create_no_options(self): def test_user_create_password(self): arglist = [ '--password', 'secret', - identity_fakes.user_name, + self.fake_user_c.name, ] verifylist = [ - ('name', identity_fakes.user_name), + ('name', self.fake_user_c.name), ('password_prompt', False), ('password', 'secret') ] @@ -129,7 +131,7 @@ def test_user_create_password(self): } # UserManager.create(name, password, email, tenant_id=, enabled=) self.users_mock.create.assert_called_with( - identity_fakes.user_name, + self.fake_user_c.name, 'secret', None, **kwargs @@ -140,10 +142,10 @@ def test_user_create_password(self): def test_user_create_password_prompt(self): arglist = [ '--password-prompt', - identity_fakes.user_name, + self.fake_user_c.name, ] verifylist = [ - ('name', identity_fakes.user_name), + ('name', self.fake_user_c.name), ('password_prompt', True) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -163,7 +165,7 @@ def test_user_create_password_prompt(self): } # UserManager.create(name, password, email, tenant_id=, enabled=) self.users_mock.create.assert_called_with( - identity_fakes.user_name, + self.fake_user_c.name, 'abc123', None, **kwargs @@ -175,10 +177,10 @@ def test_user_create_password_prompt(self): def test_user_create_email(self): arglist = [ '--email', 'barney@example.com', - identity_fakes.user_name, + self.fake_user_c.name, ] verifylist = [ - ('name', identity_fakes.user_name), + ('name', self.fake_user_c.name), ('email', 'barney@example.com'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -195,7 +197,7 @@ def test_user_create_email(self): } # UserManager.create(name, password, email, tenant_id=, enabled=) self.users_mock.create.assert_called_with( - identity_fakes.user_name, + self.fake_user_c.name, None, 'barney@example.com', **kwargs @@ -206,27 +208,22 @@ def test_user_create_email(self): def test_user_create_project(self): # Return the new project - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT_2), - loaded=True, - ) + self.projects_mock.get.return_value = self.fake_project_c + # Set up to return an updated user - USER_2 = copy.deepcopy(identity_fakes.USER) - USER_2['tenantId'] = identity_fakes.PROJECT_2['id'] - self.users_mock.create.return_value = fakes.FakeResource( - None, - USER_2, - loaded=True, - ) + attr = { + 'tenantId': self.fake_project_c.id, + } + user_2 = identity_fakes.FakeUser.create_one_user(attr) + self.users_mock.create.return_value = user_2 arglist = [ - '--project', identity_fakes.PROJECT_2['name'], - identity_fakes.user_name, + '--project', self.fake_project_c.name, + user_2.name, ] verifylist = [ - ('name', identity_fakes.user_name), - ('project', identity_fakes.PROJECT_2['name']), + ('name', user_2.name), + ('project', self.fake_project_c.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -238,11 +235,11 @@ def test_user_create_project(self): # Set expected values kwargs = { 'enabled': True, - 'tenant_id': identity_fakes.PROJECT_2['id'], + 'tenant_id': self.fake_project_c.id, } # UserManager.create(name, password, email, tenant_id=, enabled=) self.users_mock.create.assert_called_with( - identity_fakes.user_name, + user_2.name, None, None, **kwargs @@ -250,21 +247,21 @@ def test_user_create_project(self): self.assertEqual(self.columns, columns) datalist = ( - identity_fakes.user_email, + user_2.email, True, - identity_fakes.user_id, - identity_fakes.user_name, - identity_fakes.PROJECT_2['id'], + user_2.id, + user_2.name, + self.fake_project_c.id, ) self.assertEqual(datalist, data) def test_user_create_enable(self): arglist = [ '--enable', - identity_fakes.user_name, + self.fake_user_c.name, ] verifylist = [ - ('name', identity_fakes.user_name), + ('name', self.fake_user_c.name), ('enable', True), ('disable', False), ] @@ -282,7 +279,7 @@ def test_user_create_enable(self): } # UserManager.create(name, password, email, tenant_id=, enabled=) self.users_mock.create.assert_called_with( - identity_fakes.user_name, + self.fake_user_c.name, None, None, **kwargs @@ -294,10 +291,10 @@ def test_user_create_enable(self): def test_user_create_disable(self): arglist = [ '--disable', - identity_fakes.user_name, + self.fake_user_c.name, ] verifylist = [ - ('name', identity_fakes.user_name), + ('name', self.fake_user_c.name), ('enable', False), ('disable', True), ] @@ -315,7 +312,7 @@ def test_user_create_disable(self): } # UserManager.create(name, password, email, tenant_id=, enabled=) self.users_mock.create.assert_called_with( - identity_fakes.user_name, + self.fake_user_c.name, None, None, **kwargs @@ -331,18 +328,14 @@ def _raise_conflict(*args, **kwargs): # need to make this throw an exception... self.users_mock.create.side_effect = _raise_conflict - self.users_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.USER), - loaded=True, - ) + self.users_mock.get.return_value = self.fake_user_c arglist = [ '--or-show', - identity_fakes.user_name, + self.fake_user_c.name, ] verifylist = [ - ('name', identity_fakes.user_name), + ('name', self.fake_user_c.name), ('or_show', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -353,7 +346,7 @@ def _raise_conflict(*args, **kwargs): columns, data = self.cmd.take_action(parsed_args) # UserManager.create(name, password, email, tenant_id=, enabled=) - self.users_mock.get.assert_called_with(identity_fakes.user_name) + self.users_mock.get.assert_called_with(self.fake_user_c.name) self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, data) @@ -361,10 +354,10 @@ def _raise_conflict(*args, **kwargs): def test_user_create_or_show_not_exists(self): arglist = [ '--or-show', - identity_fakes.user_name, + self.fake_user_c.name, ] verifylist = [ - ('name', identity_fakes.user_name), + ('name', self.fake_user_c.name), ('or_show', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -381,7 +374,7 @@ def test_user_create_or_show_not_exists(self): } # UserManager.create(name, password, email, tenant_id=, enabled=) self.users_mock.create.assert_called_with( - identity_fakes.user_name, + self.fake_user_c.name, None, None, **kwargs @@ -396,11 +389,7 @@ def setUp(self): super(TestUserDelete, self).setUp() # This is the return value for utils.find_resource() - self.users_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.USER), - loaded=True, - ) + self.users_mock.get.return_value = self.fake_user self.users_mock.delete.return_value = None # Get the command object to test @@ -408,57 +397,47 @@ def setUp(self): def test_user_delete_no_options(self): arglist = [ - identity_fakes.user_id, + self.fake_user.id, ] verifylist = [ - ('users', [identity_fakes.user_id]), + ('users', [self.fake_user.id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.users_mock.delete.assert_called_with( - identity_fakes.user_id, + self.fake_user.id, ) self.assertIsNone(result) class TestUserList(TestUser): + fake_project_l = identity_fakes.FakeProject.create_one_project() + attr = { + 'tenantId': fake_project_l.id, + } + fake_user_l = identity_fakes.FakeUser.create_one_user(attr) + columns = ( 'ID', 'Name', ) datalist = ( ( - identity_fakes.user_id, - identity_fakes.user_name, + fake_user_l.id, + fake_user_l.name, ), ) def setUp(self): super(TestUserList, self).setUp() - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT_2), - loaded=True, - ) - self.projects_mock.list.return_value = [ - fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ), - ] + self.projects_mock.get.return_value = self.fake_project_l + self.projects_mock.list.return_value = [self.fake_project_l] - self.users_mock.list.return_value = [ - fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.USER), - loaded=True, - ), - ] + self.users_mock.list.return_value = [self.fake_user_l] # Get the command object to test self.cmd = user.ListUser(self.app, None) @@ -480,13 +459,13 @@ def test_user_list_no_options(self): def test_user_list_project(self): arglist = [ - '--project', identity_fakes.project_id, + '--project', self.fake_project_l.id, ] verifylist = [ - ('project', identity_fakes.project_id), + ('project', self.fake_project_l.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - project_id = identity_fakes.PROJECT_2['id'] + project_id = self.fake_project_l.id # In base command class Lister in cliff, abstract method take_action() # returns a tuple containing the column names and an iterable @@ -517,10 +496,10 @@ def test_user_list_long(self): collist = ('ID', 'Name', 'Project', 'Email', 'Enabled') self.assertEqual(collist, columns) datalist = (( - identity_fakes.user_id, - identity_fakes.user_name, - identity_fakes.project_name, - identity_fakes.user_email, + self.fake_user_l.id, + self.fake_user_l.name, + self.fake_project_l.name, + self.fake_user_l.email, True, ), ) self.assertEqual(datalist, tuple(data)) @@ -531,23 +510,15 @@ class TestUserSet(TestUser): def setUp(self): super(TestUserSet, self).setUp() - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) - self.users_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.USER), - loaded=True, - ) + self.projects_mock.get.return_value = self.fake_project + self.users_mock.get.return_value = self.fake_user # Get the command object to test self.cmd = user.SetUser(self.app, None) def test_user_set_no_options(self): arglist = [ - identity_fakes.user_name, + self.fake_user.name, ] verifylist = [ ('name', None), @@ -556,7 +527,7 @@ def test_user_set_no_options(self): ('project', None), ('enable', False), ('disable', False), - ('user', identity_fakes.user_name), + ('user', self.fake_user.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -588,7 +559,7 @@ def test_user_set_unexist_user(self): def test_user_set_name(self): arglist = [ '--name', 'qwerty', - identity_fakes.user_name, + self.fake_user.name, ] verifylist = [ ('name', 'qwerty'), @@ -597,7 +568,7 @@ def test_user_set_name(self): ('project', None), ('enable', False), ('disable', False), - ('user', identity_fakes.user_name), + ('user', self.fake_user.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -610,7 +581,7 @@ def test_user_set_name(self): } # UserManager.update(user, **kwargs) self.users_mock.update.assert_called_with( - identity_fakes.user_id, + self.fake_user.id, **kwargs ) self.assertIsNone(result) @@ -618,7 +589,7 @@ def test_user_set_name(self): def test_user_set_password(self): arglist = [ '--password', 'secret', - identity_fakes.user_name, + self.fake_user.name, ] verifylist = [ ('name', None), @@ -628,7 +599,7 @@ def test_user_set_password(self): ('project', None), ('enable', False), ('disable', False), - ('user', identity_fakes.user_name), + ('user', self.fake_user.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -636,7 +607,7 @@ def test_user_set_password(self): # UserManager.update_password(user, password) self.users_mock.update_password.assert_called_with( - identity_fakes.user_id, + self.fake_user.id, 'secret', ) self.assertIsNone(result) @@ -644,7 +615,7 @@ def test_user_set_password(self): def test_user_set_password_prompt(self): arglist = [ '--password-prompt', - identity_fakes.user_name, + self.fake_user.name, ] verifylist = [ ('name', None), @@ -654,7 +625,7 @@ def test_user_set_password_prompt(self): ('project', None), ('enable', False), ('disable', False), - ('user', identity_fakes.user_name), + ('user', self.fake_user.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -665,7 +636,7 @@ def test_user_set_password_prompt(self): # UserManager.update_password(user, password) self.users_mock.update_password.assert_called_with( - identity_fakes.user_id, + self.fake_user.id, 'abc123', ) self.assertIsNone(result) @@ -673,7 +644,7 @@ def test_user_set_password_prompt(self): def test_user_set_email(self): arglist = [ '--email', 'barney@example.com', - identity_fakes.user_name, + self.fake_user.name, ] verifylist = [ ('name', None), @@ -682,7 +653,7 @@ def test_user_set_email(self): ('project', None), ('enable', False), ('disable', False), - ('user', identity_fakes.user_name), + ('user', self.fake_user.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -695,24 +666,24 @@ def test_user_set_email(self): } # UserManager.update(user, **kwargs) self.users_mock.update.assert_called_with( - identity_fakes.user_id, + self.fake_user.id, **kwargs ) self.assertIsNone(result) def test_user_set_project(self): arglist = [ - '--project', identity_fakes.project_id, - identity_fakes.user_name, + '--project', self.fake_project.id, + self.fake_user.name, ] verifylist = [ ('name', None), ('password', None), ('email', None), - ('project', identity_fakes.project_id), + ('project', self.fake_project.id), ('enable', False), ('disable', False), - ('user', identity_fakes.user_name), + ('user', self.fake_user.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -720,15 +691,15 @@ def test_user_set_project(self): # UserManager.update_tenant(user, tenant) self.users_mock.update_tenant.assert_called_with( - identity_fakes.user_id, - identity_fakes.project_id, + self.fake_user.id, + self.fake_project.id, ) self.assertIsNone(result) def test_user_set_enable(self): arglist = [ '--enable', - identity_fakes.user_name, + self.fake_user.name, ] verifylist = [ ('name', None), @@ -737,7 +708,7 @@ def test_user_set_enable(self): ('project', None), ('enable', True), ('disable', False), - ('user', identity_fakes.user_name), + ('user', self.fake_user.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -749,7 +720,7 @@ def test_user_set_enable(self): } # UserManager.update(user, **kwargs) self.users_mock.update.assert_called_with( - identity_fakes.user_id, + self.fake_user.id, **kwargs ) self.assertIsNone(result) @@ -757,7 +728,7 @@ def test_user_set_enable(self): def test_user_set_disable(self): arglist = [ '--disable', - identity_fakes.user_name, + self.fake_user.name, ] verifylist = [ ('name', None), @@ -766,7 +737,7 @@ def test_user_set_disable(self): ('project', None), ('enable', False), ('disable', True), - ('user', identity_fakes.user_name), + ('user', self.fake_user.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -778,7 +749,7 @@ def test_user_set_disable(self): } # UserManager.update(user, **kwargs) self.users_mock.update.assert_called_with( - identity_fakes.user_id, + self.fake_user.id, **kwargs ) self.assertIsNone(result) @@ -789,21 +760,17 @@ class TestUserShow(TestUser): def setUp(self): super(TestUserShow, self).setUp() - self.users_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.USER), - loaded=True, - ) + self.users_mock.get.return_value = self.fake_user # Get the command object to test self.cmd = user.ShowUser(self.app, None) def test_user_show(self): arglist = [ - identity_fakes.user_id, + self.fake_user.id, ] verifylist = [ - ('user', identity_fakes.user_id), + ('user', self.fake_user.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -812,15 +779,15 @@ def test_user_show(self): # data to be shown. columns, data = self.cmd.take_action(parsed_args) - self.users_mock.get.assert_called_with(identity_fakes.user_id) + self.users_mock.get.assert_called_with(self.fake_user.id) collist = ('email', 'enabled', 'id', 'name', 'project_id') self.assertEqual(collist, columns) datalist = ( - identity_fakes.user_email, + self.fake_user.email, True, - identity_fakes.user_id, - identity_fakes.user_name, - identity_fakes.project_id, + self.fake_user.id, + self.fake_user.name, + self.fake_project.id, ) self.assertEqual(datalist, data) From af7ab03693a5708102cf6746563da289e4c1e3b7 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Mon, 27 Jun 2016 11:04:05 +0800 Subject: [PATCH 1053/3095] Support bulk deletion for delete commands in computev2 Support bulk deletion and error handling for "keypair delete" and "service delete" commands in computev2. Up to now, all the delete commands in computev2 support bulk deletion. Change-Id: I6d5c960e9716188e56615514d0921618a15a88ec Partially-Implements: blueprint multi-argument-compute Partial-Bug: #1592906 --- .../command-objects/compute-service.rst | 6 +- doc/source/command-objects/keypair.rst | 6 +- openstackclient/compute/v2/keypair.py | 27 +++++++- openstackclient/compute/v2/service.py | 20 +++++- openstackclient/tests/compute/v2/fakes.py | 19 ++++++ .../tests/compute/v2/test_keypair.py | 62 +++++++++++++++++-- .../tests/compute/v2/test_service.py | 55 ++++++++++++++-- ...lti-argument-compute-0bc4522f6edca355.yaml | 4 +- 8 files changed, 174 insertions(+), 25 deletions(-) diff --git a/doc/source/command-objects/compute-service.rst b/doc/source/command-objects/compute-service.rst index f43b5fa636..d886533e54 100644 --- a/doc/source/command-objects/compute-service.rst +++ b/doc/source/command-objects/compute-service.rst @@ -7,18 +7,18 @@ Compute v2 compute service delete ---------------------- -Delete service command +Delete compute service(s) .. program:: compute service delete .. code:: bash os compute service delete - + [ ...] .. _compute-service-delete: .. describe:: - Compute service to delete (ID only) + Compute service(s) to delete (ID only) compute service list -------------------- diff --git a/doc/source/command-objects/keypair.rst b/doc/source/command-objects/keypair.rst index 6638b8c93a..64cc20cd4c 100644 --- a/doc/source/command-objects/keypair.rst +++ b/doc/source/command-objects/keypair.rst @@ -30,17 +30,17 @@ Create new public key keypair delete -------------- -Delete public key +Delete public key(s) .. program:: keypair delete .. code:: bash os keypair delete - + [ ...] .. describe:: - Public key to delete (name only) + Public key(s) to delete (name only) keypair list ------------ diff --git a/openstackclient/compute/v2/keypair.py b/openstackclient/compute/v2/keypair.py index 13c0a99ca1..3725a3a889 100644 --- a/openstackclient/compute/v2/keypair.py +++ b/openstackclient/compute/v2/keypair.py @@ -16,6 +16,7 @@ """Keypair action implementations""" import io +import logging import os import sys @@ -27,6 +28,9 @@ from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + class CreateKeypair(command.ShowOne): """Create new public key""" @@ -78,20 +82,37 @@ def take_action(self, parsed_args): class DeleteKeypair(command.Command): - """Delete public key""" + """Delete public key(s)""" def get_parser(self, prog_name): parser = super(DeleteKeypair, self).get_parser(prog_name) parser.add_argument( 'name', metavar='', - help=_("Public key to delete (name only)") + nargs='+', + help=_("Public key(s) to delete (name only)") ) return parser def take_action(self, parsed_args): compute_client = self.app.client_manager.compute - compute_client.keypairs.delete(parsed_args.name) + result = 0 + for n in parsed_args.name: + try: + data = utils.find_resource( + compute_client.keypairs, n) + compute_client.keypairs.delete(data.name) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete public key with name " + "'%(name)s': %(e)s") + % {'name': n, 'e': e}) + + if result > 0: + total = len(parsed_args.name) + msg = (_("%(result)s of %(total)s public keys failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListKeypair(command.Lister): diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index 2e40dd7f47..0b5b1cfbf5 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.py @@ -29,21 +29,35 @@ class DeleteService(command.Command): - """Delete service command""" + """Delete compute service(s)""" def get_parser(self, prog_name): parser = super(DeleteService, self).get_parser(prog_name) parser.add_argument( "service", metavar="", - help=_("Compute service to delete (ID only)") + nargs='+', + help=_("Compute service(s) to delete (ID only)") ) return parser def take_action(self, parsed_args): compute_client = self.app.client_manager.compute + result = 0 + for s in parsed_args.service: + try: + compute_client.services.delete(s) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete compute service with " + "ID '%(service)s': %(e)s") + % {'service': s, 'e': e}) - compute_client.services.delete(parsed_args.service) + if result > 0: + total = len(parsed_args.service) + msg = (_("%(result)s of %(total)s compute services failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListService(command.Lister): diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index b7f17fbc93..882d848001 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -826,6 +826,25 @@ def create_keypairs(attrs=None, count=2): return keypairs + @staticmethod + def get_keypairs(keypairs=None, count=2): + """Get an iterable MagicMock object with a list of faked keypairs. + + If keypairs list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List keypairs: + A list of FakeResource objects faking keypairs + :param int count: + The number of keypairs to fake + :return: + An iterable Mock object with side_effect set to a list of faked + keypairs + """ + if keypairs is None: + keypairs = FakeKeypair.create_keypairs(count) + return mock.MagicMock(side_effect=keypairs) + class FakeAvailabilityZone(object): """Fake one or more compute availability zones (AZs).""" diff --git a/openstackclient/tests/compute/v2/test_keypair.py b/openstackclient/tests/compute/v2/test_keypair.py index a50a532392..25949e310b 100644 --- a/openstackclient/tests/compute/v2/test_keypair.py +++ b/openstackclient/tests/compute/v2/test_keypair.py @@ -14,6 +14,10 @@ # import mock +from mock import call + +from osc_lib import exceptions +from osc_lib import utils from openstackclient.compute.v2 import keypair from openstackclient.tests.compute.v2 import fakes as compute_fakes @@ -114,22 +118,23 @@ def test_keypair_create_public_key(self): class TestKeypairDelete(TestKeypair): - keypair = compute_fakes.FakeKeypair.create_one_keypair() + keypairs = compute_fakes.FakeKeypair.create_keypairs(count=2) def setUp(self): super(TestKeypairDelete, self).setUp() - self.keypairs_mock.get.return_value = self.keypair + self.keypairs_mock.get = compute_fakes.FakeKeypair.get_keypairs( + self.keypairs) self.keypairs_mock.delete.return_value = None self.cmd = keypair.DeleteKeypair(self.app, None) def test_keypair_delete(self): arglist = [ - self.keypair.name + self.keypairs[0].name ] verifylist = [ - ('name', self.keypair.name), + ('name', [self.keypairs[0].name]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -137,7 +142,54 @@ def test_keypair_delete(self): ret = self.cmd.take_action(parsed_args) self.assertIsNone(ret) - self.keypairs_mock.delete.assert_called_with(self.keypair.name) + self.keypairs_mock.delete.assert_called_with(self.keypairs[0].name) + + def test_delete_multiple_keypairs(self): + arglist = [] + for k in self.keypairs: + arglist.append(k.name) + verifylist = [ + ('name', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + calls = [] + for k in self.keypairs: + calls.append(call(k.name)) + self.keypairs_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_delete_multiple_keypairs_with_exception(self): + arglist = [ + self.keypairs[0].name, + 'unexist_keypair', + ] + verifylist = [ + ('name', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self.keypairs[0], exceptions.CommandError] + with mock.patch.object(utils, 'find_resource', + side_effect=find_mock_result) as find_mock: + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 public keys failed to delete.', + str(e)) + + find_mock.assert_any_call( + self.keypairs_mock, self.keypairs[0].name) + find_mock.assert_any_call(self.keypairs_mock, 'unexist_keypair') + + self.assertEqual(2, find_mock.call_count) + self.keypairs_mock.delete.assert_called_once_with( + self.keypairs[0].name + ) class TestKeypairList(TestKeypair): diff --git a/openstackclient/tests/compute/v2/test_service.py b/openstackclient/tests/compute/v2/test_service.py index e41d633a62..1599f466b7 100644 --- a/openstackclient/tests/compute/v2/test_service.py +++ b/openstackclient/tests/compute/v2/test_service.py @@ -14,6 +14,7 @@ # import mock +from mock import call from osc_lib import exceptions @@ -33,32 +34,74 @@ def setUp(self): class TestServiceDelete(TestService): + services = compute_fakes.FakeService.create_services(count=2) + def setUp(self): super(TestServiceDelete, self).setUp() - self.service = compute_fakes.FakeService.create_one_service() - self.service_mock.delete.return_value = None # Get the command object to test self.cmd = service.DeleteService(self.app, None) - def test_service_delete_no_options(self): + def test_service_delete(self): arglist = [ - self.service.binary, + self.services[0].binary, ] verifylist = [ - ('service', self.service.binary), + ('service', [self.services[0].binary]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.service_mock.delete.assert_called_with( - self.service.binary, + self.services[0].binary, ) self.assertIsNone(result) + def test_multi_services_delete(self): + arglist = [] + for s in self.services: + arglist.append(s.binary) + verifylist = [ + ('service', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + calls = [] + for s in self.services: + calls.append(call(s.binary)) + self.service_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_multi_services_delete_with_exception(self): + arglist = [ + self.services[0].binary, + 'unexist_service', + ] + verifylist = [ + ('service', arglist) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + delete_mock_result = [None, exceptions.CommandError] + self.service_mock.delete = ( + mock.MagicMock(side_effect=delete_mock_result) + ) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual( + '1 of 2 compute services failed to delete.', str(e)) + + self.service_mock.delete.assert_any_call(self.services[0].binary) + self.service_mock.delete.assert_any_call('unexist_service') + class TestServiceList(TestService): diff --git a/releasenotes/notes/bp-multi-argument-compute-0bc4522f6edca355.yaml b/releasenotes/notes/bp-multi-argument-compute-0bc4522f6edca355.yaml index 286dc7ea67..2e129dfa02 100644 --- a/releasenotes/notes/bp-multi-argument-compute-0bc4522f6edca355.yaml +++ b/releasenotes/notes/bp-multi-argument-compute-0bc4522f6edca355.yaml @@ -1,5 +1,5 @@ --- features: - - Support bulk deletion and error handling for ``aggregate delete`` and - ``flavor delete`` commands. + - Support bulk deletion and error handling for ``aggregate delete``, + ``flavor delete``, ``keypair delete`` and ``service delete`` commands. [Blueprint `multi-argument-compute `_] From 4e46c04f921c81927ded78f17fd28d5a55ebf9e2 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Tue, 28 Jun 2016 15:54:13 +0800 Subject: [PATCH 1054/3095] Add "--force" option to "volume qos delete" command Add ``--force`` option to ``volume qos delete`` command in volume v1 and v2 to allow users to delete in-use QoS specification(s). Change-Id: I46036e5f55ced8b8a1be54c521f2a5c242b89160 Closes-Bug: #1596821 --- doc/source/command-objects/volume-qos.rst | 5 +++++ .../tests/volume/v1/test_qos_specs.py | 20 +++++++++++++++++-- .../tests/volume/v2/test_qos_specs.py | 18 ++++++++++++++++- openstackclient/volume/v1/qos_specs.py | 8 +++++++- openstackclient/volume/v2/qos_specs.py | 8 +++++++- .../notes/bug-1596821-a07599eb4beb6342.yaml | 6 ++++++ 6 files changed, 60 insertions(+), 5 deletions(-) create mode 100644 releasenotes/notes/bug-1596821-a07599eb4beb6342.yaml diff --git a/doc/source/command-objects/volume-qos.rst b/doc/source/command-objects/volume-qos.rst index 2d9d14a4f0..f3c7ec2118 100644 --- a/doc/source/command-objects/volume-qos.rst +++ b/doc/source/command-objects/volume-qos.rst @@ -58,8 +58,13 @@ Delete QoS specification .. code:: bash os volume qos delete + [--force] [ ...] +.. option:: --force + + Allow to delete in-use QoS specification(s) + .. describe:: QoS specification(s) to delete (name or ID) diff --git a/openstackclient/tests/volume/v1/test_qos_specs.py b/openstackclient/tests/volume/v1/test_qos_specs.py index 392017c65d..4e1733fde5 100644 --- a/openstackclient/tests/volume/v1/test_qos_specs.py +++ b/openstackclient/tests/volume/v1/test_qos_specs.py @@ -211,7 +211,7 @@ def test_qos_delete_with_id(self): result = self.cmd.take_action(parsed_args) - self.qos_mock.delete.assert_called_with(volume_fakes.qos_id) + self.qos_mock.delete.assert_called_with(volume_fakes.qos_id, False) self.assertIsNone(result) def test_qos_delete_with_name(self): @@ -225,7 +225,23 @@ def test_qos_delete_with_name(self): result = self.cmd.take_action(parsed_args) - self.qos_mock.delete.assert_called_with(volume_fakes.qos_id) + self.qos_mock.delete.assert_called_with(volume_fakes.qos_id, False) + self.assertIsNone(result) + + def test_qos_delete_with_force(self): + arglist = [ + '--force', + volume_fakes.qos_id + ] + verifylist = [ + ('force', True), + ('qos_specs', [volume_fakes.qos_id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.qos_mock.delete.assert_called_with(volume_fakes.qos_id, True) self.assertIsNone(result) diff --git a/openstackclient/tests/volume/v2/test_qos_specs.py b/openstackclient/tests/volume/v2/test_qos_specs.py index 11047535cb..92ffca74ee 100644 --- a/openstackclient/tests/volume/v2/test_qos_specs.py +++ b/openstackclient/tests/volume/v2/test_qos_specs.py @@ -175,7 +175,23 @@ def test_qos_delete(self): result = self.cmd.take_action(parsed_args) - self.qos_mock.delete.assert_called_with(self.qos_spec.id) + self.qos_mock.delete.assert_called_with(self.qos_spec.id, False) + self.assertIsNone(result) + + def test_qos_delete_with_force(self): + arglist = [ + '--force', + self.qos_spec.id + ] + verifylist = [ + ('force', True), + ('qos_specs', [self.qos_spec.id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.qos_mock.delete.assert_called_with(self.qos_spec.id, True) self.assertIsNone(result) diff --git a/openstackclient/volume/v1/qos_specs.py b/openstackclient/volume/v1/qos_specs.py index 56a96256ac..c5850871e8 100644 --- a/openstackclient/volume/v1/qos_specs.py +++ b/openstackclient/volume/v1/qos_specs.py @@ -103,13 +103,19 @@ def get_parser(self, prog_name): nargs="+", help=_('QoS specification(s) to delete (name or ID)'), ) + parser.add_argument( + '--force', + action='store_true', + default=False, + help=_("Allow to delete in-use QoS specification(s)") + ) return parser def take_action(self, parsed_args): volume_client = self.app.client_manager.volume for qos in parsed_args.qos_specs: qos_spec = utils.find_resource(volume_client.qos_specs, qos) - volume_client.qos_specs.delete(qos_spec.id) + volume_client.qos_specs.delete(qos_spec.id, parsed_args.force) class DisassociateQos(command.Command): diff --git a/openstackclient/volume/v2/qos_specs.py b/openstackclient/volume/v2/qos_specs.py index 7ec272b38e..5ed1225bf0 100644 --- a/openstackclient/volume/v2/qos_specs.py +++ b/openstackclient/volume/v2/qos_specs.py @@ -103,13 +103,19 @@ def get_parser(self, prog_name): nargs="+", help=_('QoS specification(s) to delete (name or ID)'), ) + parser.add_argument( + '--force', + action='store_true', + default=False, + help=_("Allow to delete in-use QoS specification(s)") + ) return parser def take_action(self, parsed_args): volume_client = self.app.client_manager.volume for qos in parsed_args.qos_specs: qos_spec = utils.find_resource(volume_client.qos_specs, qos) - volume_client.qos_specs.delete(qos_spec.id) + volume_client.qos_specs.delete(qos_spec.id, parsed_args.force) class DisassociateQos(command.Command): diff --git a/releasenotes/notes/bug-1596821-a07599eb4beb6342.yaml b/releasenotes/notes/bug-1596821-a07599eb4beb6342.yaml new file mode 100644 index 0000000000..4843314b7e --- /dev/null +++ b/releasenotes/notes/bug-1596821-a07599eb4beb6342.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--force`` option to ``volume qos delete`` command to allow users to + delete in-use QoS specification(s). + [Bug `1596821 `_] From 45b026d7c8e4675a598192bcf4c88f22005cc1d2 Mon Sep 17 00:00:00 2001 From: reedip Date: Fri, 15 Apr 2016 13:21:08 +0900 Subject: [PATCH 1055/3095] Add command to unset information from Subnets This patch introduces the ``subnet unset`` command to clear the host-routes, allocation-pools and dns-nameservers from subnets. Implements: blueprint network-property-unset Change-Id: I31324a2423f6d2315eed27445dfdcfe863e0b550 --- doc/source/command-objects/subnet.rst | 38 +++++++ openstackclient/network/v2/subnet.py | 83 ++++++++++++++ .../tests/network/v2/test_subnet.py | 106 ++++++++++++++++++ .../notes/subnet-unset-5b458cdbaf93d766.yaml | 6 + setup.cfg | 1 + 5 files changed, 234 insertions(+) create mode 100644 releasenotes/notes/subnet-unset-5b458cdbaf93d766.yaml diff --git a/doc/source/command-objects/subnet.rst b/doc/source/command-objects/subnet.rst index fe77ccfde4..3051678977 100644 --- a/doc/source/command-objects/subnet.rst +++ b/doc/source/command-objects/subnet.rst @@ -235,3 +235,41 @@ Display subnet details .. describe:: Subnet to display (name or ID) + +subnet unset +------------ + +Unset subnet properties + +.. program:: subnet unset +.. code:: bash + + os subnet unset + [--allocation-pool start=,end= [...]] + [--dns-nameserver [...]] + [--host-route destination=,gateway= [...]] + + +.. option:: --dns-nameserver + + DNS server to be removed from this subnet + (repeat option to unset multiple DNS servers) + +.. option:: --allocation-pool start=,end= + + Allocation pool to be removed from this subnet e.g.: + ``start=192.168.199.2,end=192.168.199.254`` + (repeat option to unset multiple Allocation pools) + +.. option:: --host-route destination=,gateway= + + Route to be removed from this subnet e.g.: + ``destination=10.10.0.0/16,gateway=192.168.71.254`` + destination: destination subnet (in CIDR notation) + gateway: nexthop IP address + (repeat option to unset multiple host routes) + +.. _subnet_unset-subnet: +.. describe:: + + subnet to modify (name or ID) diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 752923f73e..357da065f4 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -28,6 +28,11 @@ LOG = logging.getLogger(__name__) +def _update_arguments(obj_list, parsed_args_list): + for item in parsed_args_list: + obj_list.remove(item) + + def _format_allocation_pools(data): pool_formatted = ['%s-%s' % (pool.get('start', ''), pool.get('end', '')) for pool in data] @@ -433,3 +438,81 @@ def take_action(self, parsed_args): columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) return (columns, data) + + +class UnsetSubnet(command.Command): + """Unset subnet properties""" + + def get_parser(self, prog_name): + parser = super(UnsetSubnet, self).get_parser(prog_name) + parser.add_argument( + '--allocation-pool', + metavar='start=,end=', + dest='allocation_pools', + action=parseractions.MultiKeyValueAction, + required_keys=['start', 'end'], + help=_('Allocation pool to be removed from this subnet ' + 'e.g.: start=192.168.199.2,end=192.168.199.254 ' + '(repeat option to unset multiple Allocation pools)') + ) + parser.add_argument( + '--dns-nameserver', + metavar='', + action='append', + dest='dns_nameservers', + help=_('DNS server to be removed from this subnet ' + '(repeat option to set multiple DNS servers)') + ) + parser.add_argument( + '--host-route', + metavar='destination=,gateway=', + dest='host_routes', + action=parseractions.MultiKeyValueAction, + required_keys=['destination', 'gateway'], + help=_('Route to be removed from this subnet ' + 'e.g.: destination=10.10.0.0/16,gateway=192.168.71.254 ' + 'destination: destination subnet (in CIDR notation) ' + 'gateway: nexthop IP address ' + '(repeat option to unset multiple host routes)') + ) + parser.add_argument( + 'subnet', + metavar="", + help=_("Subnet to modify (name or ID)") + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.find_subnet(parsed_args.subnet, ignore_missing=False) + tmp_obj = copy.deepcopy(obj) + attrs = {} + if parsed_args.dns_nameservers: + try: + _update_arguments(tmp_obj.dns_nameservers, + parsed_args.dns_nameservers) + except ValueError as error: + msg = (_("%s not in dns-nameservers") % str(error)) + raise exceptions.CommandError(msg) + attrs['dns_nameservers'] = tmp_obj.dns_nameservers + if parsed_args.host_routes: + try: + _update_arguments( + tmp_obj.host_routes, + convert_entries_to_nexthop(parsed_args.host_routes)) + except ValueError as error: + msg = (_("Subnet does not have %s in host-routes") % + str(error)) + raise exceptions.CommandError(msg) + attrs['host_routes'] = tmp_obj.host_routes + if parsed_args.allocation_pools: + try: + _update_arguments(tmp_obj.allocation_pools, + parsed_args.allocation_pools) + except ValueError as error: + msg = (_("Subnet does not have %s in allocation-pools") % + str(error)) + raise exceptions.CommandError(msg) + attrs['allocation_pools'] = tmp_obj.allocation_pools + if attrs: + client.update_subnet(obj, **attrs) diff --git a/openstackclient/tests/network/v2/test_subnet.py b/openstackclient/tests/network/v2/test_subnet.py index 99b558c070..82813d6c10 100644 --- a/openstackclient/tests/network/v2/test_subnet.py +++ b/openstackclient/tests/network/v2/test_subnet.py @@ -767,3 +767,109 @@ def test_show_all_options(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + + +class TestUnsetSubnet(TestSubnet): + + def setUp(self): + super(TestUnsetSubnet, self).setUp() + self._testsubnet = network_fakes.FakeSubnet.create_one_subnet( + {'dns_nameservers': ['8.8.8.8', + '8.8.8.4'], + 'host_routes': [{'destination': '10.20.20.0/24', + 'nexthop': '10.20.20.1'}, + {'destination': '10.30.30.30/24', + 'nexthop': '10.30.30.1'}], + 'allocation_pools': [{'start': '8.8.8.100', + 'end': '8.8.8.150'}, + {'start': '8.8.8.160', + 'end': '8.8.8.170'}], }) + self.network.find_subnet = mock.Mock(return_value=self._testsubnet) + self.network.update_subnet = mock.Mock(return_value=None) + # Get the command object to test + self.cmd = subnet_v2.UnsetSubnet(self.app, self.namespace) + + def test_unset_subnet_params(self): + arglist = [ + '--dns-nameserver', '8.8.8.8', + '--host-route', 'destination=10.30.30.30/24,gateway=10.30.30.1', + '--allocation-pool', 'start=8.8.8.100,end=8.8.8.150', + self._testsubnet.name, + ] + verifylist = [ + ('dns_nameservers', ['8.8.8.8']), + ('host_routes', [{ + "destination": "10.30.30.30/24", "gateway": "10.30.30.1"}]), + ('allocation_pools', [{ + 'start': '8.8.8.100', 'end': '8.8.8.150'}]), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = { + 'dns_nameservers': ['8.8.8.4'], + 'host_routes': [{ + "destination": "10.20.20.0/24", "nexthop": "10.20.20.1"}], + 'allocation_pools': [{'start': '8.8.8.160', 'end': '8.8.8.170'}], + } + self.network.update_subnet.assert_called_once_with( + self._testsubnet, **attrs) + self.assertIsNone(result) + + def test_unset_subnet_wrong_host_routes(self): + arglist = [ + '--dns-nameserver', '8.8.8.8', + '--host-route', 'destination=10.30.30.30/24,gateway=10.30.30.2', + '--allocation-pool', 'start=8.8.8.100,end=8.8.8.150', + self._testsubnet.name, + ] + verifylist = [ + ('dns_nameservers', ['8.8.8.8']), + ('host_routes', [{ + "destination": "10.30.30.30/24", "gateway": "10.30.30.2"}]), + ('allocation_pools', [{ + 'start': '8.8.8.100', 'end': '8.8.8.150'}]), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, parsed_args) + + def test_unset_subnet_wrong_allocation_pool(self): + arglist = [ + '--dns-nameserver', '8.8.8.8', + '--host-route', 'destination=10.30.30.30/24,gateway=10.30.30.1', + '--allocation-pool', 'start=8.8.8.100,end=8.8.8.156', + self._testsubnet.name, + ] + verifylist = [ + ('dns_nameservers', ['8.8.8.8']), + ('host_routes', [{ + "destination": "10.30.30.30/24", "gateway": "10.30.30.1"}]), + ('allocation_pools', [{ + 'start': '8.8.8.100', 'end': '8.8.8.156'}]), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, parsed_args) + + def test_unset_subnet_wrong_dns_nameservers(self): + arglist = [ + '--dns-nameserver', '8.8.8.1', + '--host-route', 'destination=10.30.30.30/24,gateway=10.30.30.1', + '--allocation-pool', 'start=8.8.8.100,end=8.8.8.150', + self._testsubnet.name, + ] + verifylist = [ + ('dns_nameservers', ['8.8.8.1']), + ('host_routes', [{ + "destination": "10.30.30.30/24", "gateway": "10.30.30.1"}]), + ('allocation_pools', [{ + 'start': '8.8.8.100', 'end': '8.8.8.150'}]), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, parsed_args) diff --git a/releasenotes/notes/subnet-unset-5b458cdbaf93d766.yaml b/releasenotes/notes/subnet-unset-5b458cdbaf93d766.yaml new file mode 100644 index 0000000000..9cb4bdc9ff --- /dev/null +++ b/releasenotes/notes/subnet-unset-5b458cdbaf93d766.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add a new command ``subnet unset`` to clear the information + of allocation-pools, host-routes or DNS servers from the subnet. + [ Blueprint `network-property-unset `_] diff --git a/setup.cfg b/setup.cfg index 44dd616acc..b2c20540f9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -382,6 +382,7 @@ openstack.network.v2 = subnet_list = openstackclient.network.v2.subnet:ListSubnet subnet_set = openstackclient.network.v2.subnet:SetSubnet subnet_show = openstackclient.network.v2.subnet:ShowSubnet + subnet_unset = openstackclient.network.v2.subnet:UnsetSubnet subnet_pool_create = openstackclient.network.v2.subnet_pool:CreateSubnetPool subnet_pool_delete = openstackclient.network.v2.subnet_pool:DeleteSubnetPool From dbed97a24df2fb74e4989fb15c912252f8a8bb07 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Tue, 28 Jun 2016 14:39:00 +0800 Subject: [PATCH 1056/3095] Add "--property" option to "flavor create" command Add "--property" option to "flavor create" command to support adding properties to a new falvor. Change-Id: I4f06b364375d5a81584fe41122d48e9568fa712a Closes-Bug: #1596798 --- doc/source/command-objects/flavor.rst | 5 +++++ functional/tests/compute/v2/test_flavor.py | 14 ++++++++----- openstackclient/compute/v2/flavor.py | 21 ++++++++++++++++--- openstackclient/tests/compute/v2/fakes.py | 1 + .../tests/compute/v2/test_flavor.py | 20 ++++++++++++++---- .../notes/bug-1596798-b22fd587bdca8b36.yaml | 4 ++++ 6 files changed, 53 insertions(+), 12 deletions(-) create mode 100644 releasenotes/notes/bug-1596798-b22fd587bdca8b36.yaml diff --git a/doc/source/command-objects/flavor.rst b/doc/source/command-objects/flavor.rst index a886b9ff1a..9ea504c1be 100644 --- a/doc/source/command-objects/flavor.rst +++ b/doc/source/command-objects/flavor.rst @@ -21,6 +21,7 @@ Create new flavor [--vcpus ] [--rxtx-factor ] [--public | --private] + [--property [...] ] [--project ] [--project-domain ] @@ -61,6 +62,10 @@ Create new flavor Flavor is not available to other projects +.. option:: --property + + Property to add for this flavor (repeat option to set multiple properties) + .. option:: --project Allow to access private flavor (name or ID) diff --git a/functional/tests/compute/v2/test_flavor.py b/functional/tests/compute/v2/test_flavor.py index 2bb075bd59..6edb1fdfec 100644 --- a/functional/tests/compute/v2/test_flavor.py +++ b/functional/tests/compute/v2/test_flavor.py @@ -25,7 +25,8 @@ class FlavorTests(test.TestCase): @classmethod def setUpClass(cls): opts = cls.get_show_opts(cls.FIELDS) - raw_output = cls.openstack('flavor create ' + cls.NAME + opts) + raw_output = cls.openstack( + 'flavor create --property a=b --property c=d ' + cls.NAME + opts) expected = cls.NAME + '\n' cls.assertOutput(expected, raw_output) @@ -47,19 +48,22 @@ def test_flavor_show(self): def test_flavor_properties(self): opts = self.get_show_opts(['properties']) + # check the properties we added in create command. + raw_output = self.openstack('flavor show ' + self.NAME + opts) + self.assertEqual("a='b', c='d'\n", raw_output) raw_output = self.openstack( - 'flavor set --property a=b --property c=d ' + self.NAME + 'flavor set --property e=f --property g=h ' + self.NAME ) self.assertEqual('', raw_output) raw_output = self.openstack('flavor show ' + self.NAME + opts) - self.assertEqual("a='b', c='d'\n", raw_output) + self.assertEqual("a='b', c='d', e='f', g='h'\n", raw_output) raw_output = self.openstack( - 'flavor unset --property a ' + self.NAME + 'flavor unset --property a --property c ' + self.NAME ) self.assertEqual('', raw_output) raw_output = self.openstack('flavor show ' + self.NAME + opts) - self.assertEqual("c='d'\n", raw_output) + self.assertEqual("e='f', g='h'\n", raw_output) diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 01d7da75ff..000df59899 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -121,6 +121,13 @@ def get_parser(self, prog_name): action="store_false", help=_("Flavor is not available to other projects") ) + parser.add_argument( + "--property", + metavar="", + action=parseractions.KeyValueAction, + help=_("Property to add for this flavor " + "(repeat option to set multiple properties)") + ) parser.add_argument( '--project', metavar='', @@ -150,8 +157,7 @@ def take_action(self, parsed_args): parsed_args.public ) - flavor = compute_client.flavors.create(*args)._info.copy() - flavor.pop("links") + flavor = compute_client.flavors.create(*args) if parsed_args.project: try: @@ -166,8 +172,17 @@ def take_action(self, parsed_args): msg = _("Failed to add project %(project)s access to " "flavor: %(e)s") LOG.error(msg % {'project': parsed_args.project, 'e': e}) + if parsed_args.property: + try: + flavor.set_keys(parsed_args.property) + except Exception as e: + LOG.error(_("Failed to set flavor property: %s"), e) - return zip(*sorted(six.iteritems(flavor))) + flavor_info = flavor._info.copy() + flavor_info.pop("links") + flavor_info['properties'] = utils.format_dict(flavor.get_keys()) + + return zip(*sorted(six.iteritems(flavor_info))) class DeleteFlavor(command.Command): diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index b7f17fbc93..a7a66d5e1f 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -716,6 +716,7 @@ def create_one_flavor(attrs=None): 'OS-FLV-DISABLED:disabled': False, 'os-flavor-access:is_public': True, 'OS-FLV-EXT-DATA:ephemeral': 0, + 'properties': {'property': 'value'}, } # Overwrite default attributes. diff --git a/openstackclient/tests/compute/v2/test_flavor.py b/openstackclient/tests/compute/v2/test_flavor.py index da76b6d706..20ae8706f6 100644 --- a/openstackclient/tests/compute/v2/test_flavor.py +++ b/openstackclient/tests/compute/v2/test_flavor.py @@ -56,6 +56,7 @@ class TestFlavorCreate(TestFlavor): 'id', 'name', 'os-flavor-access:is_public', + 'properties', 'ram', 'rxtx_factor', 'swap', @@ -68,6 +69,7 @@ class TestFlavorCreate(TestFlavor): flavor.id, flavor.name, flavor.is_public, + utils.format_dict(flavor.properties), flavor.ram, flavor.rxtx_factor, flavor.swap, @@ -116,7 +118,6 @@ def test_flavor_create_default_options(self): def test_flavor_create_all_options(self): arglist = [ - self.flavor.name, '--id', self.flavor.id, '--ram', str(self.flavor.ram), '--disk', str(self.flavor.disk), @@ -125,9 +126,10 @@ def test_flavor_create_all_options(self): '--vcpus', str(self.flavor.vcpus), '--rxtx-factor', str(self.flavor.rxtx_factor), '--public', + '--property', 'property=value', + self.flavor.name, ] verifylist = [ - ('name', self.flavor.name), ('id', self.flavor.id), ('ram', self.flavor.ram), ('disk', self.flavor.disk), @@ -136,6 +138,8 @@ def test_flavor_create_all_options(self): ('vcpus', self.flavor.vcpus), ('rxtx_factor', self.flavor.rxtx_factor), ('public', True), + ('property', {'property': 'value'}), + ('name', self.flavor.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -152,6 +156,8 @@ def test_flavor_create_all_options(self): ) columns, data = self.cmd.take_action(parsed_args) self.flavors_mock.create.assert_called_once_with(*args) + self.flavor.set_keys.assert_called_once_with({'property': 'value'}) + self.flavor.get_keys.assert_called_once_with() self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -160,7 +166,6 @@ def test_flavor_create_other_options(self): self.flavor.is_public = False arglist = [ - self.flavor.name, '--id', self.flavor.id, '--ram', str(self.flavor.ram), '--disk', str(self.flavor.disk), @@ -170,9 +175,11 @@ def test_flavor_create_other_options(self): '--rxtx-factor', str(self.flavor.rxtx_factor), '--private', '--project', identity_fakes.project_id, + '--property', 'key1=value1', + '--property', 'key2=value2', + self.flavor.name, ] verifylist = [ - ('name', self.flavor.name), ('id', self.flavor.id), ('ram', self.flavor.ram), ('disk', self.flavor.disk), @@ -182,6 +189,8 @@ def test_flavor_create_other_options(self): ('rxtx_factor', self.flavor.rxtx_factor), ('public', False), ('project', identity_fakes.project_id), + ('property', {'key1': 'value1', 'key2': 'value2'}), + ('name', self.flavor.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -202,6 +211,9 @@ def test_flavor_create_other_options(self): self.flavor.id, identity_fakes.project_id, ) + self.flavor.set_keys.assert_called_with( + {'key1': 'value1', 'key2': 'value2'}) + self.flavor.get_keys.assert_called_with() self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) diff --git a/releasenotes/notes/bug-1596798-b22fd587bdca8b36.yaml b/releasenotes/notes/bug-1596798-b22fd587bdca8b36.yaml new file mode 100644 index 0000000000..6ab833da16 --- /dev/null +++ b/releasenotes/notes/bug-1596798-b22fd587bdca8b36.yaml @@ -0,0 +1,4 @@ +--- +features: + - Add ``--property`` option to ``flavor create`` command. + [Bug `1596798 `_] From 92d0fbeafd146126897934a7ec57ebf2ccef8580 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Wed, 29 Jun 2016 09:48:04 -0500 Subject: [PATCH 1057/3095] Add port security option to network commands Add the "--enable-port-security" and "--disable-port-security" options to the "network create" and "network set" commands. This supports setting the default port security for ports created on a network. Change-Id: I1deb505bd77cef2e4bc3c2dbbb0c450665136f47 Implements: blueprint neutron-client --- doc/source/command-objects/network.rst | 26 ++++++++++++++++ openstackclient/network/v2/network.py | 30 +++++++++++++++++++ openstackclient/tests/network/v2/fakes.py | 3 ++ .../tests/network/v2/test_network.py | 18 +++++++++++ .../bp-neutron-client-a0552f8ca909b665.yaml | 5 ++++ 5 files changed, 82 insertions(+) diff --git a/doc/source/command-objects/network.rst b/doc/source/command-objects/network.rst index 0c472e7f18..5d9a5ca860 100644 --- a/doc/source/command-objects/network.rst +++ b/doc/source/command-objects/network.rst @@ -24,6 +24,7 @@ Create new network [--enable | --disable] [--share | --no-share] [--availability-zone-hint ] + [--enable-port-security | --disable-port-security] [--external [--default | --no-default] | --internal] [--provider-network-type ] [--provider-physical-network ] @@ -72,6 +73,20 @@ Create new network *Network version 2 only* +.. option:: --enable-port-security + + Enable port security by default for ports created on + this network (default) + + *Network version 2 only* + +.. option:: --disable-port-security + + Disable port security by default for ports created on + this network + + *Network version 2 only* + .. option:: --subnet IPv4 subnet for fixed IPs (in CIDR notation) @@ -191,6 +206,7 @@ Set network properties [--name ] [--enable | --disable] [--share | --no-share] + [--enable-port-security | --disable-port-security] [--external [--default | --no-default] | --internal] [--provider-network-type ] [--provider-physical-network ] @@ -218,6 +234,16 @@ Set network properties Do not share the network between projects +.. option:: --enable-port-security + + Enable port security by default for ports created on + this network + +.. option:: --disable-port-security + + Disable port security by default for ports created on + this network + .. option:: --external Set this network as an external network. diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 31dfc7983b..ccc02fd8e6 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -58,6 +58,10 @@ def _get_attrs(client_manager, parsed_args): attrs['shared'] = True if parsed_args.no_share: attrs['shared'] = False + if parsed_args.enable_port_security: + attrs['port_security_enabled'] = True + if parsed_args.disable_port_security: + attrs['port_security_enabled'] = False # "network set" command doesn't support setting project. if 'project' in parsed_args and parsed_args.project is not None: @@ -197,6 +201,19 @@ def update_parser_network(self, parser): "(Network Availability Zone extension required, " "repeat option to set multiple availability zones)") ) + port_security_group = parser.add_mutually_exclusive_group() + port_security_group.add_argument( + '--enable-port-security', + action='store_true', + help=_("Enable port security by default for ports created on " + "this network (default)") + ) + port_security_group.add_argument( + '--disable-port-security', + action='store_true', + help=_("Disable port security by default for ports created on " + "this network") + ) external_router_grp = parser.add_mutually_exclusive_group() external_router_grp.add_argument( '--external', @@ -403,6 +420,19 @@ def get_parser(self, prog_name): action='store_true', help=_("Do not share the network between projects") ) + port_security_group = parser.add_mutually_exclusive_group() + port_security_group.add_argument( + '--enable-port-security', + action='store_true', + help=_("Enable port security by default for ports created on " + "this network") + ) + port_security_group.add_argument( + '--disable-port-security', + action='store_true', + help=_("Disable port security by default for ports created on " + "this network") + ) external_router_grp = parser.add_mutually_exclusive_group() external_router_grp.add_argument( '--external', diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 50d9899cb0..8d5efe1456 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -285,6 +285,7 @@ def create_one_network(attrs=None): 'availability_zones': [], 'availability_zone_hints': [], 'is_default': False, + 'port_security_enabled': True, } # Overwrite default attributes. @@ -296,6 +297,8 @@ def create_one_network(attrs=None): # Set attributes with special mapping in OpenStack SDK. network.project_id = network_attrs['tenant_id'] network.is_router_external = network_attrs['router:external'] + network.is_port_security_enabled = \ + network_attrs['port_security_enabled'] return network diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index 8fc9dadf8f..ffe6c973dc 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -55,6 +55,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): 'id', 'is_default', 'name', + 'port_security_enabled', 'project_id', 'provider_network_type', 'router:external', @@ -70,6 +71,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): _network.id, _network.is_default, _network.name, + _network.is_port_security_enabled, _network.project_id, _network.provider_network_type, network._format_router_external(_network.is_router_external), @@ -151,6 +153,7 @@ def test_create_all_options(self): "--provider-physical-network", "physnet1", "--provider-segment", "400", "--transparent-vlan", + "--enable-port-security", self._network.name, ] verifylist = [ @@ -165,6 +168,7 @@ def test_create_all_options(self): ('physical_network', 'physnet1'), ('segmentation_id', '400'), ('transparent_vlan', True), + ('enable_port_security', True), ('name', self._network.name), ] @@ -183,6 +187,7 @@ def test_create_all_options(self): 'provider:physical_network': 'physnet1', 'provider:segmentation_id': '400', 'vlan_transparent': True, + 'port_security_enabled': True, }) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -191,6 +196,7 @@ def test_create_other_options(self): arglist = [ "--enable", "--no-share", + "--disable-port-security", self._network.name, ] verifylist = [ @@ -198,6 +204,7 @@ def test_create_other_options(self): ('no_share', True), ('name', self._network.name), ('external', False), + ('disable_port_security', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -207,6 +214,7 @@ def test_create_other_options(self): 'admin_state_up': True, 'name': self._network.name, 'shared': False, + 'port_security_enabled': False, }) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -226,6 +234,7 @@ class TestCreateNetworkIdentityV2(TestNetwork): 'id', 'is_default', 'name', + 'port_security_enabled', 'project_id', 'provider_network_type', 'router:external', @@ -241,6 +250,7 @@ class TestCreateNetworkIdentityV2(TestNetwork): _network.id, _network.is_default, _network.name, + _network.is_port_security_enabled, _network.project_id, _network.provider_network_type, network._format_router_external(_network.is_router_external), @@ -547,6 +557,7 @@ def test_set_this(self): '--provider-physical-network', 'physnet1', '--provider-segment', '400', '--no-transparent-vlan', + '--enable-port-security', ] verifylist = [ ('network', self._network.name), @@ -559,6 +570,7 @@ def test_set_this(self): ('physical_network', 'physnet1'), ('segmentation_id', '400'), ('no_transparent_vlan', True), + ('enable_port_security', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -574,6 +586,7 @@ def test_set_this(self): 'provider:physical_network': 'physnet1', 'provider:segmentation_id': '400', 'vlan_transparent': False, + 'port_security_enabled': True, } self.network.update_network.assert_called_once_with( self._network, **attrs) @@ -585,12 +598,14 @@ def test_set_that(self): '--disable', '--no-share', '--internal', + '--disable-port-security', ] verifylist = [ ('network', self._network.name), ('disable', True), ('no_share', True), ('internal', True), + ('disable_port_security', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -600,6 +615,7 @@ def test_set_that(self): 'admin_state_up': False, 'shared': False, 'router:external': False, + 'port_security_enabled': False, } self.network.update_network.assert_called_once_with( self._network, **attrs) @@ -630,6 +646,7 @@ class TestShowNetwork(TestNetwork): 'id', 'is_default', 'name', + 'port_security_enabled', 'project_id', 'provider_network_type', 'router:external', @@ -645,6 +662,7 @@ class TestShowNetwork(TestNetwork): _network.id, _network.is_default, _network.name, + _network.is_port_security_enabled, _network.project_id, _network.provider_network_type, network._format_router_external(_network.is_router_external), diff --git a/releasenotes/notes/bp-neutron-client-a0552f8ca909b665.yaml b/releasenotes/notes/bp-neutron-client-a0552f8ca909b665.yaml index 8402e2c9c3..9224a46240 100644 --- a/releasenotes/notes/bp-neutron-client-a0552f8ca909b665.yaml +++ b/releasenotes/notes/bp-neutron-client-a0552f8ca909b665.yaml @@ -4,6 +4,11 @@ features: ``port set`` commands to support JSON input for more advanced binding profile data. [Blueprint :oscbp:`neutron-client`] + - Add ``--enable-port-security`` and ``--disable-port-security`` + options on the ``network create`` and ``network set`` commands. + This supports setting the default port security for ports created + on a network. + [Blueprint :oscbp:`neutron-client`] - Add ``geneve`` choice to the ``network create`` command ``--provider-network-type`` option. [Blueprint :oscbp:`neutron-client`] From 3ddb9732923037ce1ca8761fe6e38475ada44cb0 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Thu, 30 Jun 2016 10:26:28 +0800 Subject: [PATCH 1058/3095] Fix doc issue for "compute agent list" command "--hypervisor" is a optional argument but in the doc it was wrong format, this patch modify it. Change-Id: I527feff73eca5834b2fee4efb2462562581210ed --- doc/source/command-objects/compute-agent.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/doc/source/command-objects/compute-agent.rst b/doc/source/command-objects/compute-agent.rst index ae057dc96e..55540c8d6b 100644 --- a/doc/source/command-objects/compute-agent.rst +++ b/doc/source/command-objects/compute-agent.rst @@ -66,10 +66,9 @@ List compute agents os compute agent list [--hypervisor ] -.. _compute_agent-list: -.. describe:: --hypervisor +.. option:: --hypervisor - Optional type of hypervisor + Type of hypervisor compute agent set ----------------- From 063c722a110031883e9615064092644de6df8da2 Mon Sep 17 00:00:00 2001 From: reedip Date: Fri, 15 Apr 2016 15:27:18 +0900 Subject: [PATCH 1059/3095] Add command to unset information from Subnet-pools This patch introduces the ``subnet pool unset`` command to clear the pool prefix information from the subnet-pools. Change-Id: I84b7259d6e26e695343d41cea6d807396faaf69a Implements: blueprint network-property-unset --- doc/source/command-objects/subnet-pool.rst | 22 ++++++++++ openstackclient/network/v2/subnet_pool.py | 41 +++++++++++++++++++ .../tests/network/v2/test_subnet_pool.py | 39 ++++++++++++++++++ .../unset-subnet-pool-333052dd85b95653.yaml | 6 +++ setup.cfg | 1 + 5 files changed, 109 insertions(+) create mode 100644 releasenotes/notes/unset-subnet-pool-333052dd85b95653.yaml diff --git a/doc/source/command-objects/subnet-pool.rst b/doc/source/command-objects/subnet-pool.rst index 516b9bf4a8..005b83579b 100644 --- a/doc/source/command-objects/subnet-pool.rst +++ b/doc/source/command-objects/subnet-pool.rst @@ -185,3 +185,25 @@ Display subnet pool details .. describe:: Subnet pool to display (name or ID) + +subnet pool unset +----------------- + +Unset subnet pool properties + +.. program:: subnet pool unset +.. code:: bash + + os subnet pool unset + [--pool-prefix [...]] + + +.. option:: --pool-prefix + + Remove subnet pool prefixes (in CIDR notation). + (repeat option to unset multiple prefixes). + +.. _subnet_pool_unset-subnet-pool: +.. describe:: + + Subnet pool to modify (name or ID) diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index 55dfed8392..ed2bb0ef05 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -12,6 +12,7 @@ # """Subnet pool action implementations""" +import copy import logging @@ -337,3 +338,43 @@ def take_action(self, parsed_args): columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) return (columns, data) + + +class UnsetSubnetPool(command.Command): + """Unset subnet pool properties""" + + def get_parser(self, prog_name): + parser = super(UnsetSubnetPool, self).get_parser(prog_name) + parser.add_argument( + '--pool-prefix', + metavar='', + action='append', + dest='prefixes', + help=_('Remove subnet pool prefixes (in CIDR notation). ' + '(repeat option to unset multiple prefixes).'), + ) + parser.add_argument( + 'subnet_pool', + metavar="", + help=_("Subnet pool to modify (name or ID)") + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.find_subnet_pool( + parsed_args.subnet_pool, ignore_missing=False) + tmp_prefixes = copy.deepcopy(obj.prefixes) + attrs = {} + if parsed_args.prefixes: + for prefix in parsed_args.prefixes: + try: + tmp_prefixes.remove(prefix) + except ValueError: + msg = _( + "Subnet pool does not " + "contain prefix %s") % prefix + raise exceptions.CommandError(msg) + attrs['prefixes'] = tmp_prefixes + if attrs: + client.update_subnet_pool(obj, **attrs) diff --git a/openstackclient/tests/network/v2/test_subnet_pool.py b/openstackclient/tests/network/v2/test_subnet_pool.py index 7a96b30f67..41b6170f42 100644 --- a/openstackclient/tests/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/network/v2/test_subnet_pool.py @@ -698,3 +698,42 @@ def test_show_all_options(self): ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + + +class TestUnsetSubnetPool(TestSubnetPool): + + def setUp(self): + super(TestUnsetSubnetPool, self).setUp() + self._subnetpool = network_fakes.FakeSubnetPool.create_one_subnet_pool( + {'prefixes': ['10.0.10.0/24', '10.1.10.0/24', + '10.2.10.0/24'], }) + self.network.find_subnet_pool = mock.Mock( + return_value=self._subnetpool) + self.network.update_subnet_pool = mock.Mock(return_value=None) + # Get the command object to test + self.cmd = subnet_pool.UnsetSubnetPool(self.app, self.namespace) + + def test_unset_subnet_pool(self): + arglist = [ + '--pool-prefix', '10.0.10.0/24', + '--pool-prefix', '10.1.10.0/24', + self._subnetpool.name, + ] + verifylist = [('prefixes', ['10.0.10.0/24', '10.1.10.0/24'])] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + attrs = {'prefixes': ['10.2.10.0/24']} + self.network.update_subnet_pool.assert_called_once_with( + self._subnetpool, **attrs) + self.assertIsNone(result) + + def test_unset_subnet_pool_prefix_not_existent(self): + arglist = [ + '--pool-prefix', '10.100.1.1/25', + self._subnetpool.name, + ] + verifylist = [('prefixes', ['10.100.1.1/25'])] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, + parsed_args) diff --git a/releasenotes/notes/unset-subnet-pool-333052dd85b95653.yaml b/releasenotes/notes/unset-subnet-pool-333052dd85b95653.yaml new file mode 100644 index 0000000000..7fbda240a1 --- /dev/null +++ b/releasenotes/notes/unset-subnet-pool-333052dd85b95653.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add a new command ``subnet pool unset`` to clear the information + of pool-prefixes from the subnet pools. + [ Blueprint `network-property-unset `_] diff --git a/setup.cfg b/setup.cfg index 356400e921..d41cdc0171 100644 --- a/setup.cfg +++ b/setup.cfg @@ -390,6 +390,7 @@ openstack.network.v2 = subnet_pool_list = openstackclient.network.v2.subnet_pool:ListSubnetPool subnet_pool_set = openstackclient.network.v2.subnet_pool:SetSubnetPool subnet_pool_show = openstackclient.network.v2.subnet_pool:ShowSubnetPool + subnet_pool_unset = openstackclient.network.v2.subnet_pool:UnsetSubnetPool openstack.object_store.v1 = object_store_account_set = openstackclient.object.v1.account:SetAccount From d324530532d5361e85e784c3df2f0d40a128b149 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 23 Jun 2016 15:39:48 -0500 Subject: [PATCH 1060/3095] osc-lib: api.auth Move auth plugin checking to osc-lib. Change-Id: I673d9c2d6e8bbf724c3000459a729e831d747814 --- examples/common.py | 3 +- examples/object_api.py | 4 +- examples/osc-lib.py | 3 +- openstackclient/api/auth.py | 230 +----------------- openstackclient/common/clientmanager.py | 2 +- openstackclient/identity/client.py | 2 +- .../tests/common/test_clientmanager.py | 4 +- 7 files changed, 15 insertions(+), 233 deletions(-) diff --git a/examples/common.py b/examples/common.py index 6d48a8cb27..d472fe6b91 100755 --- a/examples/common.py +++ b/examples/common.py @@ -38,8 +38,7 @@ import traceback from keystoneauth1 import session as ks_session - -from openstackclient.api import auth +from osc_lib.api import auth CONSOLE_MESSAGE_FORMAT = '%(levelname)s: %(name)s %(message)s' diff --git a/examples/object_api.py b/examples/object_api.py index 11b62da773..577fc052fa 100755 --- a/examples/object_api.py +++ b/examples/object_api.py @@ -25,13 +25,11 @@ import sys import common - +from os_client_config import config as cloud_config from openstackclient.api import object_store_v1 as object_store from openstackclient.identity import client as identity_client -from os_client_config import config as cloud_config - LOG = logging.getLogger('') diff --git a/examples/osc-lib.py b/examples/osc-lib.py index abaa18711f..cc04bc70fe 100755 --- a/examples/osc-lib.py +++ b/examples/osc-lib.py @@ -26,11 +26,10 @@ import sys import common +from os_client_config import config as cloud_config from openstackclient.common import clientmanager -from os_client_config import config as cloud_config - LOG = logging.getLogger('') diff --git a/openstackclient/api/auth.py b/openstackclient/api/auth.py index 0c82fe9ba8..d62a82dc43 100644 --- a/openstackclient/api/auth.py +++ b/openstackclient/api/auth.py @@ -11,229 +11,15 @@ # under the License. # -"""Authentication Library""" +# NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release +# or Jun 2017. -import argparse -import logging +import sys -from keystoneauth1.loading import base -from osc_lib import exceptions as exc -from osc_lib import utils +from osc_lib.api.auth import * # noqa -from openstackclient.i18n import _ -LOG = logging.getLogger(__name__) - -# Initialize the list of Authentication plugins early in order -# to get the command-line options -PLUGIN_LIST = None - -# List of plugin command line options -OPTIONS_LIST = {} - - -def get_plugin_list(): - """Gather plugin list and cache it""" - global PLUGIN_LIST - - if PLUGIN_LIST is None: - PLUGIN_LIST = base.get_available_plugin_names() - return PLUGIN_LIST - - -def get_options_list(): - """Gather plugin options so the help action has them available""" - - global OPTIONS_LIST - - if not OPTIONS_LIST: - for plugin_name in get_plugin_list(): - plugin_options = base.get_plugin_options(plugin_name) - for o in plugin_options: - os_name = o.dest.lower().replace('_', '-') - os_env_name = 'OS_' + os_name.upper().replace('-', '_') - OPTIONS_LIST.setdefault( - os_name, {'env': os_env_name, 'help': ''}, - ) - # TODO(mhu) simplistic approach, would be better to only add - # help texts if they vary from one auth plugin to another - # also the text rendering is ugly in the CLI ... - OPTIONS_LIST[os_name]['help'] += 'With %s: %s\n' % ( - plugin_name, - o.help, - ) - return OPTIONS_LIST - - -def select_auth_plugin(options): - """Pick an auth plugin based on --os-auth-type or other options""" - - auth_plugin_name = None - - # Do the token/url check first as this must override the default - # 'password' set by os-client-config - # Also, url and token are not copied into o-c-c's auth dict (yet?) - if options.auth.get('url') and options.auth.get('token'): - # service token authentication - auth_plugin_name = 'token_endpoint' - elif options.auth_type in PLUGIN_LIST: - # A direct plugin name was given, use it - auth_plugin_name = options.auth_type - elif options.auth.get('username'): - if options.identity_api_version == '3': - auth_plugin_name = 'v3password' - elif options.identity_api_version.startswith('2'): - auth_plugin_name = 'v2password' - else: - # let keystoneclient figure it out itself - auth_plugin_name = 'password' - elif options.auth.get('token'): - if options.identity_api_version == '3': - auth_plugin_name = 'v3token' - elif options.identity_api_version.startswith('2'): - auth_plugin_name = 'v2token' - else: - # let keystoneclient figure it out itself - auth_plugin_name = 'token' - else: - # The ultimate default is similar to the original behaviour, - # but this time with version discovery - auth_plugin_name = 'password' - LOG.debug("Auth plugin %s selected", auth_plugin_name) - return auth_plugin_name - - -def build_auth_params(auth_plugin_name, cmd_options): - - if auth_plugin_name: - LOG.debug('auth_type: %s', auth_plugin_name) - auth_plugin_loader = base.get_plugin_loader(auth_plugin_name) - auth_params = {opt.dest: opt.default - for opt in base.get_plugin_options(auth_plugin_name)} - auth_params.update(dict(cmd_options.auth)) - # grab tenant from project for v2.0 API compatibility - if auth_plugin_name.startswith("v2"): - if 'project_id' in auth_params: - auth_params['tenant_id'] = auth_params['project_id'] - del auth_params['project_id'] - if 'project_name' in auth_params: - auth_params['tenant_name'] = auth_params['project_name'] - del auth_params['project_name'] - else: - LOG.debug('no auth_type') - # delay the plugin choice, grab every option - auth_plugin_loader = None - auth_params = dict(cmd_options.auth) - plugin_options = set([o.replace('-', '_') for o in get_options_list()]) - for option in plugin_options: - LOG.debug('fetching option %s', option) - auth_params[option] = getattr(cmd_options.auth, option, None) - return (auth_plugin_loader, auth_params) - - -def check_valid_authorization_options(options, auth_plugin_name): - """Validate authorization options, and provide helpful error messages.""" - if (options.auth.get('project_id') and not - options.auth.get('domain_id') and not - options.auth.get('domain_name') and not - options.auth.get('project_name') and not - options.auth.get('tenant_id') and not - options.auth.get('tenant_name')): - raise exc.CommandError(_( - 'Missing parameter(s): ' - 'Set either a project or a domain scope, but not both. Set a ' - 'project scope with --os-project-name, OS_PROJECT_NAME, or ' - 'auth.project_name. Alternatively, set a domain scope with ' - '--os-domain-name, OS_DOMAIN_NAME or auth.domain_name.')) - - -def check_valid_authentication_options(options, auth_plugin_name): - """Validate authentication options, and provide helpful error messages.""" - - # Get all the options defined within the plugin. - plugin_opts = base.get_plugin_options(auth_plugin_name) - plugin_opts = {opt.dest: opt for opt in plugin_opts} - - # NOTE(aloga): this is an horrible hack. We need a way to specify the - # required options in the plugins. Using the "required" argument for - # the oslo_config.cfg.Opt does not work, as it is not possible to load the - # plugin if the option is not defined, so the error will simply be: - # "NoMatchingPlugin: The plugin foobar could not be found" - msgs = [] - if 'password' in plugin_opts and not options.auth.get('username'): - msgs.append(_('Set a username with --os-username, OS_USERNAME,' - ' or auth.username')) - if 'auth_url' in plugin_opts and not options.auth.get('auth_url'): - msgs.append(_('Set a service AUTH_URL, with --os-auth-url, ' - 'OS_AUTH_URL or auth.auth_url')) - if 'url' in plugin_opts and not options.auth.get('url'): - msgs.append(_('Set a service URL, with --os-url, ' - 'OS_URL or auth.url')) - if 'token' in plugin_opts and not options.auth.get('token'): - msgs.append(_('Set a token with --os-token, ' - 'OS_TOKEN or auth.token')) - if msgs: - raise exc.CommandError( - _('Missing parameter(s): \n%s') % '\n'.join(msgs)) - - -def build_auth_plugins_option_parser(parser): - """Auth plugins options builder - - Builds dynamically the list of options expected by each available - authentication plugin. - - """ - available_plugins = list(get_plugin_list()) - parser.add_argument( - '--os-auth-type', - metavar='', - dest='auth_type', - default=utils.env('OS_AUTH_TYPE'), - help=_('Select an authentication type. Available types: %s.' - ' Default: selected based on --os-username/--os-token' - ' (Env: OS_AUTH_TYPE)') % ', '.join(available_plugins), - choices=available_plugins - ) - # Maintain compatibility with old tenant env vars - envs = { - 'OS_PROJECT_NAME': utils.env( - 'OS_PROJECT_NAME', - default=utils.env('OS_TENANT_NAME') - ), - 'OS_PROJECT_ID': utils.env( - 'OS_PROJECT_ID', - default=utils.env('OS_TENANT_ID') - ), - } - for o in get_options_list(): - # Remove tenant options from KSC plugins and replace them below - if 'tenant' not in o: - parser.add_argument( - '--os-' + o, - metavar='' % o, - dest=o.replace('-', '_'), - default=envs.get( - OPTIONS_LIST[o]['env'], - utils.env(OPTIONS_LIST[o]['env']), - ), - help=_('%(help)s\n(Env: %(env)s)') % { - 'help': OPTIONS_LIST[o]['help'], - 'env': OPTIONS_LIST[o]['env'], - }, - ) - # add tenant-related options for compatibility - # this is deprecated but still used in some tempest tests... - parser.add_argument( - '--os-tenant-name', - metavar='', - dest='os_project_name', - help=argparse.SUPPRESS, - ) - parser.add_argument( - '--os-tenant-id', - metavar='', - dest='os_project_id', - help=argparse.SUPPRESS, - ) - return parser +sys.stderr.write( + "WARNING: %s is deprecated and will be removed after Jun 2017. " + "Please use osc_lib.api.auth\n" % __name__ +) diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index 3c35b52933..13c6b92cfa 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -20,12 +20,12 @@ import pkg_resources import sys +from osc_lib.api import auth from osc_lib import exceptions from oslo_utils import strutils import requests import six -from openstackclient.api import auth from openstackclient.common import session as osc_session from openstackclient.identity import client as identity_client diff --git a/openstackclient/identity/client.py b/openstackclient/identity/client.py index be7b643f5c..d932b97068 100644 --- a/openstackclient/identity/client.py +++ b/openstackclient/identity/client.py @@ -16,9 +16,9 @@ import logging from keystoneclient.v2_0 import client as identity_client_v2 +from osc_lib.api import auth from osc_lib import utils -from openstackclient.api import auth from openstackclient.i18n import _ LOG = logging.getLogger(__name__) diff --git a/openstackclient/tests/common/test_clientmanager.py b/openstackclient/tests/common/test_clientmanager.py index 0a9965e0f3..117c718429 100644 --- a/openstackclient/tests/common/test_clientmanager.py +++ b/openstackclient/tests/common/test_clientmanager.py @@ -19,10 +19,10 @@ from keystoneauth1.access import service_catalog from keystoneauth1.identity import v2 as auth_v2 from keystoneauth1 import token_endpoint +from osc_lib.api import auth from osc_lib import exceptions as exc from requests_mock.contrib import fixture -from openstackclient.api import auth from openstackclient.common import clientmanager from openstackclient.tests import fakes from openstackclient.tests import utils @@ -356,7 +356,7 @@ def test_client_manager_select_auth_plugin_failure(self): client_manager.setup_auth, ) - @mock.patch('openstackclient.api.auth.check_valid_authentication_options') + @mock.patch('osc_lib.api.auth.check_valid_authentication_options') def test_client_manager_auth_setup_once(self, check_authn_options_func): client_manager = clientmanager.ClientManager( cli_options=FakeOptions( From 46b14afc3f34935683bb582362b614adbe1349d9 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Thu, 30 Jun 2016 12:55:02 -0400 Subject: [PATCH 1061/3095] update plugin documentation - add watcher, gnocchi and aodh - organize sphinx docs based on alphabetical order of the library Change-Id: Ibdd500da0e488ce3256320d53b3354ee7c66468a --- doc/source/plugin-commands.rst | 57 ++++++++++++++++++++++++---------- doc/source/plugins.rst | 5 +-- test-requirements.txt | 1 + 3 files changed, 45 insertions(+), 18 deletions(-) diff --git a/doc/source/plugin-commands.rst b/doc/source/plugin-commands.rst index b23e7a338e..66e1d72c25 100644 --- a/doc/source/plugin-commands.rst +++ b/doc/source/plugin-commands.rst @@ -6,51 +6,76 @@ Note: To see the complete syntax for the plugin commands, see the `CLI_Ref`_ .. list-plugins:: openstack.cli.extension -.. list-plugins:: openstack.key_manager.v1 +# aodh +.. list-plugins:: openstack.alarming.v2 :detailed: -.. list-plugins:: openstack.baremetal.v1 +# barbican +.. list-plugins:: openstack.key_manager.v1 :detailed: +# congress .. list-plugins:: openstack.congressclient.v1 :detailed: -.. list-plugins:: openstack.workflow_engine.v2 - :detailed: +# cue +.. # cueclient is not in global-requirements +.. # list-plugins:: openstack.mb.v1 +.. # :detailed: -.. list-plugins:: openstack.data_processing.v1 +# designate +.. list-plugins:: openstack.dns.v1 :detailed: -.. list-plugins:: openstack.dns.v1 +# gnocchi +.. # gnocchiclient is not in global-requirements +.. # list-plugins:: openstack.metric.v1 +.. # :detailed: + +# heat +.. list-plugins:: openstack.orchestration.v1 :detailed: -.. list-plugins:: openstack.management.v1 +# ironic +.. list-plugins:: openstack.baremetal.v1 :detailed: -.. list-plugins:: openstack.messaging.v1 +# ironic-inspector +.. list-plugins:: openstack.baremetal_introspection.v1 :detailed: -.. list-plugins:: openstack.orchestration.v1 +# mistral +.. list-plugins:: openstack.workflow_engine.v2 :detailed: -.. list-plugins:: openstack.search.v1 +# murano +.. list-plugins:: openstack.application_catalog.v1 :detailed: -.. list-plugins:: openstack.baremetal_introspection.v1 +# sahara +.. list-plugins:: openstack.data_processing.v1 :detailed: -.. list-plugins:: openstack.application_catalog.v1 +# searchlight +.. list-plugins:: openstack.search.v1 :detailed: +# senlin .. list-plugins:: openstack.clustering.v1 :detailed: +# tripleo .. # tripleoclient is not in global-requirements -.. #.. list-plugins:: openstack.tripleoclient.v1 +.. # list-plugins:: openstack.tripleoclient.v1 .. # :detailed: -.. # cueclient is not in global-requirements -.. #.. list-plugins:: openstack.mb.v1 -.. # :detailed: +# watcher +.. # watcherclient is not in global-requirements +.. # list-plugins:: openstack.infra_optim.v1 +.. # :detailed: + +# zaqar +.. list-plugins:: openstack.messaging.v1 + :detailed: .. _CLI_Ref: http://docs.openstack.org/cli-reference/openstack.html \ No newline at end of file diff --git a/doc/source/plugins.rst b/doc/source/plugins.rst index 00aa5137ae..412edd104b 100644 --- a/doc/source/plugins.rst +++ b/doc/source/plugins.rst @@ -21,6 +21,8 @@ OpenStackClient. The following is a list of projects that are an OpenStackClient plugin. +- aodhclient +- gnocchiclient\*\* - python-barbicanclient - python-congressclient - python-cueclient\*\* @@ -34,14 +36,13 @@ The following is a list of projects that are an OpenStackClient plugin. - python-searchlightclient - python-senlinclient - python-tripleoclient\*\* +- python-watcherclient\*\* - python-zaqarclient \*\* Note that some clients are not listed in global-requirements The following is a list of projects that are not an OpenStackClient plugin. -- aodhclient -- gnocchiclient - python-troveclient - python-magnumclient - python-ceilometerclient diff --git a/test-requirements.txt b/test-requirements.txt index 9e82a5fc2d..b1413f66f0 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -20,6 +20,7 @@ osprofiler>=1.3.0 # Apache-2.0 bandit>=1.0.1 # Apache-2.0 # Install these to generate sphinx autodocs +aodhclient>=0.5.0 # Apache-2.0 python-barbicanclient>=4.0.0 # Apache-2.0 python-congressclient<2000,>=1.3.0 # Apache-2.0 python-designateclient>=1.5.0 # Apache-2.0 From 6364df4cbd33a20ea1c729e0031b1aebd3fcd6df Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Thu, 30 Jun 2016 15:06:58 +0800 Subject: [PATCH 1062/3095] Add "--snapshot" option to "backup create" command in volumev2 Add "--snapshot" option to "backup create" command to support backing up a snapshot. Change-Id: Ibecbf1902599875f422d372d529835f73211d3ec Closes-Bug: #1597184 --- doc/source/command-objects/backup.rst | 4 ++++ openstackclient/tests/volume/v2/fakes.py | 1 + openstackclient/tests/volume/v2/test_backup.py | 14 +++++++++++++- openstackclient/volume/v2/backup.py | 10 ++++++++++ .../notes/bug-1597184-f4fb687b3d4d99d9.yaml | 5 +++++ 5 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/bug-1597184-f4fb687b3d4d99d9.yaml diff --git a/doc/source/command-objects/backup.rst b/doc/source/command-objects/backup.rst index 8e657b32e5..9fff393d0c 100644 --- a/doc/source/command-objects/backup.rst +++ b/doc/source/command-objects/backup.rst @@ -31,6 +31,10 @@ Create new backup Description of the backup +.. option:: --snapshot + + Snapshot to backup (name or ID) + .. option:: --force Allow to back up an in-use volume diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py index 1cbbf68a1b..ae4c9f51ea 100644 --- a/openstackclient/tests/volume/v2/fakes.py +++ b/openstackclient/tests/volume/v2/fakes.py @@ -403,6 +403,7 @@ def create_one_backup(attrs=None): "id": 'backup-id-' + uuid.uuid4().hex, "name": 'backup-name-' + uuid.uuid4().hex, "volume_id": 'volume-id-' + uuid.uuid4().hex, + "snapshot_id": 'snapshot-id' + uuid.uuid4().hex, "description": 'description-' + uuid.uuid4().hex, "object_count": None, "container": 'container-' + uuid.uuid4().hex, diff --git a/openstackclient/tests/volume/v2/test_backup.py b/openstackclient/tests/volume/v2/test_backup.py index ba0f1c1811..b1e6594cb3 100644 --- a/openstackclient/tests/volume/v2/test_backup.py +++ b/openstackclient/tests/volume/v2/test_backup.py @@ -25,6 +25,8 @@ def setUp(self): self.backups_mock.reset_mock() self.volumes_mock = self.app.client_manager.volume.volumes self.volumes_mock.reset_mock() + self.snapshots_mock = self.app.client_manager.volume.volume_snapshots + self.snapshots_mock.reset_mock() self.restores_mock = self.app.client_manager.volume.restores self.restores_mock.reset_mock() @@ -32,8 +34,9 @@ def setUp(self): class TestBackupCreate(TestBackup): volume = volume_fakes.FakeVolume.create_one_volume() + snapshot = volume_fakes.FakeSnapshot.create_one_snapshot() new_backup = volume_fakes.FakeBackup.create_one_backup( - attrs={'volume_id': volume.id}) + attrs={'volume_id': volume.id, 'snapshot_id': snapshot.id}) columns = ( 'availability_zone', @@ -43,6 +46,7 @@ class TestBackupCreate(TestBackup): 'name', 'object_count', 'size', + 'snapshot_id', 'status', 'volume_id', ) @@ -54,6 +58,7 @@ class TestBackupCreate(TestBackup): new_backup.name, new_backup.object_count, new_backup.size, + new_backup.snapshot_id, new_backup.status, new_backup.volume_id, ) @@ -62,6 +67,7 @@ def setUp(self): super(TestBackupCreate, self).setUp() self.volumes_mock.get.return_value = self.volume + self.snapshots_mock.get.return_value = self.snapshot self.backups_mock.create.return_value = self.new_backup # Get the command object to test @@ -73,6 +79,7 @@ def test_backup_create(self): "--description", self.new_backup.description, "--container", self.new_backup.container, "--force", + "--snapshot", self.new_backup.snapshot_id, self.new_backup.volume_id, ] verifylist = [ @@ -80,6 +87,7 @@ def test_backup_create(self): ("description", self.new_backup.description), ("container", self.new_backup.container), ("force", True), + ("snapshot", self.new_backup.snapshot_id), ("volume", self.new_backup.volume_id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -92,6 +100,7 @@ def test_backup_create(self): name=self.new_backup.name, description=self.new_backup.description, force=True, + snapshot_id=self.new_backup.snapshot_id, ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -117,6 +126,7 @@ def test_backup_create_without_name(self): name=None, description=self.new_backup.description, force=False, + snapshot_id=None, ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -264,6 +274,7 @@ class TestBackupShow(TestBackup): 'name', 'object_count', 'size', + 'snapshot_id', 'status', 'volume_id', ) @@ -275,6 +286,7 @@ class TestBackupShow(TestBackup): backup.name, backup.object_count, backup.size, + backup.snapshot_id, backup.status, backup.volume_id, ) diff --git a/openstackclient/volume/v2/backup.py b/openstackclient/volume/v2/backup.py index 519913a937..a8da40800d 100644 --- a/openstackclient/volume/v2/backup.py +++ b/openstackclient/volume/v2/backup.py @@ -48,6 +48,11 @@ def get_parser(self, prog_name): metavar="", help=_("Optional backup container name") ) + parser.add_argument( + "--snapshot", + metavar="", + help=_("Snapshot to backup (name or ID)") + ) parser.add_argument( '--force', action='store_true', @@ -60,12 +65,17 @@ def take_action(self, parsed_args): volume_client = self.app.client_manager.volume volume_id = utils.find_resource( volume_client.volumes, parsed_args.volume).id + snapshot_id = None + if parsed_args.snapshot: + snapshot_id = utils.find_resource( + volume_client.volume_snapshots, parsed_args.snapshot).id backup = volume_client.backups.create( volume_id, container=parsed_args.container, name=parsed_args.name, description=parsed_args.description, force=parsed_args.force, + snapshot_id=snapshot_id, ) backup._info.pop("links", None) return zip(*sorted(six.iteritems(backup._info))) diff --git a/releasenotes/notes/bug-1597184-f4fb687b3d4d99d9.yaml b/releasenotes/notes/bug-1597184-f4fb687b3d4d99d9.yaml new file mode 100644 index 0000000000..638afb0ece --- /dev/null +++ b/releasenotes/notes/bug-1597184-f4fb687b3d4d99d9.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``--snapshot`` option to ``backup create`` command. + [Bug `1597184 `_] From 230d38fb4ca0235706a9cd7617628b84413075b7 Mon Sep 17 00:00:00 2001 From: reedip Date: Thu, 7 Apr 2016 15:22:11 +0900 Subject: [PATCH 1063/3095] Add command to unset information from ports This patch introduces the ``port unset`` command to clear the fixed-ip and binding:profile information from the ports. Implements: blueprint network-property-unset Change-Id: I9dba309234105af477e7618a8a437b7fa3b13cd7 --- doc/source/command-objects/port.rst | 29 +++++++ openstackclient/network/v2/port.py | 59 ++++++++++++++ openstackclient/tests/network/v2/test_port.py | 78 +++++++++++++++++++ ...d-port-unset-command-8bdaf1fa9c593374.yaml | 6 ++ setup.cfg | 1 + 5 files changed, 173 insertions(+) create mode 100644 releasenotes/notes/add-port-unset-command-8bdaf1fa9c593374.yaml diff --git a/doc/source/command-objects/port.rst b/doc/source/command-objects/port.rst index 9d93ee62c4..4d7b95b4e3 100644 --- a/doc/source/command-objects/port.rst +++ b/doc/source/command-objects/port.rst @@ -215,3 +215,32 @@ Display port details .. describe:: Port to display (name or ID) + +port unset +---------- + +Unset port properties + +.. program:: port unset +.. code:: bash + + os port unset + [--fixed-ip subnet=,ip-address= [...]] + [--binding-profile [...]] + + +.. option:: --fixed-ip subnet=,ip-address= + + Desired IP and/or subnet (name or ID) which should be removed + from this port: subnet=,ip-address= + (repeat option to unset multiple fixed IP addresses) + +.. option:: --binding-profile + + Desired key which should be removed from binding-profile + (repeat option to unset multiple binding:profile data) + +.. _port_unset-port: +.. describe:: + + Port to modify (name or ID) diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 2a069f0922..05c5a012f3 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -14,6 +14,7 @@ """Port action implementations""" import argparse +import copy import json import logging @@ -485,3 +486,61 @@ def take_action(self, parsed_args): columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) return (columns, data) + + +class UnsetPort(command.Command): + """Unset port properties""" + + def get_parser(self, prog_name): + parser = super(UnsetPort, self).get_parser(prog_name) + parser.add_argument( + '--fixed-ip', + metavar='subnet=,ip-address=', + action=parseractions.MultiKeyValueAction, + optional_keys=['subnet', 'ip-address'], + help=_("Desired IP and/or subnet (name or ID) which should be " + "removed from this port: subnet=," + "ip-address= (repeat option to unset multiple " + "fixed IP addresses)")) + + parser.add_argument( + '--binding-profile', + metavar='', + action='append', + help=_("Desired key which should be removed from binding:profile" + "(repeat option to unset multiple binding:profile data)")) + parser.add_argument( + 'port', + metavar="", + help=_("Port to modify (name or ID)") + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.find_port(parsed_args.port, ignore_missing=False) + # SDK ignores update() if it recieves a modified obj and attrs + # To handle the same tmp_obj is created in all take_action of + # Unset* classes + tmp_fixed_ips = copy.deepcopy(obj.fixed_ips) + tmp_binding_profile = copy.deepcopy(obj.binding_profile) + _prepare_fixed_ips(self.app.client_manager, parsed_args) + attrs = {} + if parsed_args.fixed_ip: + try: + for ip in parsed_args.fixed_ip: + tmp_fixed_ips.remove(ip) + except ValueError: + msg = _("Port does not contain fixed-ip %s") % ip + raise exceptions.CommandError(msg) + attrs['fixed_ips'] = tmp_fixed_ips + if parsed_args.binding_profile: + try: + for key in parsed_args.binding_profile: + del tmp_binding_profile[key] + except KeyError: + msg = _("Port does not contain binding-profile %s") % key + raise exceptions.CommandError(msg) + attrs['binding:profile'] = tmp_binding_profile + if attrs: + client.update_port(obj, **attrs) diff --git a/openstackclient/tests/network/v2/test_port.py b/openstackclient/tests/network/v2/test_port.py index 871fe87296..a1cecec8e7 100644 --- a/openstackclient/tests/network/v2/test_port.py +++ b/openstackclient/tests/network/v2/test_port.py @@ -616,3 +616,81 @@ def test_show_all_options(self): ref_columns, ref_data = self._get_common_cols_data(self._port) self.assertEqual(ref_columns, columns) self.assertEqual(ref_data, data) + + +class TestUnsetPort(TestPort): + + def setUp(self): + super(TestUnsetPort, self).setUp() + self._testport = network_fakes.FakePort.create_one_port( + {'fixed_ips': [{'subnet_id': '042eb10a-3a18-4658-ab-cf47c8d03152', + 'ip_address': '0.0.0.1'}, + {'subnet_id': '042eb10a-3a18-4658-ab-cf47c8d03152', + 'ip_address': '1.0.0.0'}], + 'binding:profile': {'batman': 'Joker', 'Superman': 'LexLuthor'}}) + self.fake_subnet = network_fakes.FakeSubnet.create_one_subnet( + {'id': '042eb10a-3a18-4658-ab-cf47c8d03152'}) + self.network.find_subnet = mock.Mock(return_value=self.fake_subnet) + self.network.find_port = mock.Mock(return_value=self._testport) + self.network.update_port = mock.Mock(return_value=None) + # Get the command object to test + self.cmd = port.UnsetPort(self.app, self.namespace) + + def test_unset_port_parameters(self): + arglist = [ + '--fixed-ip', + 'subnet=042eb10a-3a18-4658-ab-cf47c8d03152,ip-address=1.0.0.0', + '--binding-profile', 'Superman', + self._testport.name, + ] + verifylist = [ + ('fixed_ip', [{ + 'subnet': '042eb10a-3a18-4658-ab-cf47c8d03152', + 'ip-address': '1.0.0.0'}]), + ('binding_profile', ['Superman']), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = { + 'fixed_ips': [{ + 'subnet_id': '042eb10a-3a18-4658-ab-cf47c8d03152', + 'ip_address': '0.0.0.1'}], + 'binding:profile': {'batman': 'Joker'} + } + self.network.update_port.assert_called_once_with( + self._testport, **attrs) + self.assertIsNone(result) + + def test_unset_port_fixed_ip_not_existent(self): + arglist = [ + '--fixed-ip', 'ip-address=1.0.0.1', + '--binding-profile', 'Superman', + self._testport.name, + ] + verifylist = [ + ('fixed_ip', [{'ip-address': '1.0.0.1'}]), + ('binding_profile', ['Superman']), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + def test_unset_port_binding_profile_not_existent(self): + arglist = [ + '--fixed-ip', 'ip-address=1.0.0.0', + '--binding-profile', 'Neo', + self._testport.name, + ] + verifylist = [ + ('fixed_ip', [{'ip-address': '1.0.0.0'}]), + ('binding_profile', ['Neo']), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, + parsed_args) diff --git a/releasenotes/notes/add-port-unset-command-8bdaf1fa9c593374.yaml b/releasenotes/notes/add-port-unset-command-8bdaf1fa9c593374.yaml new file mode 100644 index 0000000000..f4e547b9e9 --- /dev/null +++ b/releasenotes/notes/add-port-unset-command-8bdaf1fa9c593374.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add a new command ``port unset`` to clear the information + of fixed-ip and binding-profile from the port. + [ Blueprint `network-property-unset `_] diff --git a/setup.cfg b/setup.cfg index 356400e921..b2569cdd6a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -355,6 +355,7 @@ openstack.network.v2 = port_list = openstackclient.network.v2.port:ListPort port_set = openstackclient.network.v2.port:SetPort port_show = openstackclient.network.v2.port:ShowPort + port_unset = openstackclient.network.v2.port:UnsetPort router_add_port = openstackclient.network.v2.router:AddPortToRouter router_add_subnet = openstackclient.network.v2.router:AddSubnetToRouter From 8a3b5d2b50c83b0708cee7d676d881faacb01de7 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Fri, 1 Jul 2016 04:24:41 +0000 Subject: [PATCH 1064/3095] Updated from global requirements Change-Id: I0954807ccd7be3256627eb330a66c9fb539d639f --- requirements.txt | 2 +- test-requirements.txt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index a9a5b0c633..0f7e292468 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ os-client-config>=1.13.1 # Apache-2.0 osc-lib>=0.1.0 # Apache-2.0 oslo.config>=3.10.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 -oslo.utils>=3.11.0 # Apache-2.0 +oslo.utils>=3.14.0 # Apache-2.0 python-glanceclient>=2.0.0 # Apache-2.0 python-keystoneclient!=1.8.0,!=2.1.0,>=1.7.0 # Apache-2.0 python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0 diff --git a/test-requirements.txt b/test-requirements.txt index b1413f66f0..34380080dc 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -15,12 +15,12 @@ sphinx!=1.3b1,<1.3,>=1.2.1 # BSD os-testr>=0.7.0 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD testtools>=1.4.0 # MIT -tempest>=11.0.0 # Apache-2.0 +tempest>=12.1.0 # Apache-2.0 osprofiler>=1.3.0 # Apache-2.0 bandit>=1.0.1 # Apache-2.0 # Install these to generate sphinx autodocs -aodhclient>=0.5.0 # Apache-2.0 +aodhclient>=0.5.0 # Apache-2.0 python-barbicanclient>=4.0.0 # Apache-2.0 python-congressclient<2000,>=1.3.0 # Apache-2.0 python-designateclient>=1.5.0 # Apache-2.0 From 50bd56db258a16199463afcdeb2f0579384cc711 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Fri, 17 Jun 2016 14:39:13 +0800 Subject: [PATCH 1065/3095] Transfer "ip floating pool list" to "floating ip pool list" This patch does the following things to transfer "ip floating pool list" to "floating ip pool list": * Add a new command "floating ip pool list" to deprecate "ip floating pool list". The source code is in network/v2 dir. * Add doc for "floating ip pool list". * Add floating ip pool unit tests. Change-Id: Id410f4e4a96cf589a6e8def209574da71395b55f Implements: blueprint rework-ip-commands Partial-bug: 1555990 Co-Authored-By: Dean Troyer --- .../command-objects/floating-ip-pool.rst | 15 +++ .../command-objects/ip-floating-pool.rst | 1 + doc/source/commands.rst | 1 + openstackclient/compute/v2/floatingippool.py | 36 ------- .../network/v2/floating_ip_pool.py | 66 +++++++++++++ openstackclient/tests/compute/v2/fakes.py | 51 ++++++++++ .../tests/network/v2/test_floating_ip_pool.py | 97 +++++++++++++++++++ .../ip-command-rework-8d3fe0858f51e6b8.yaml | 9 ++ setup.cfg | 5 +- 9 files changed, 244 insertions(+), 37 deletions(-) create mode 100644 doc/source/command-objects/floating-ip-pool.rst delete mode 100644 openstackclient/compute/v2/floatingippool.py create mode 100644 openstackclient/network/v2/floating_ip_pool.py create mode 100644 openstackclient/tests/network/v2/test_floating_ip_pool.py create mode 100644 releasenotes/notes/ip-command-rework-8d3fe0858f51e6b8.yaml diff --git a/doc/source/command-objects/floating-ip-pool.rst b/doc/source/command-objects/floating-ip-pool.rst new file mode 100644 index 0000000000..6f074d2d34 --- /dev/null +++ b/doc/source/command-objects/floating-ip-pool.rst @@ -0,0 +1,15 @@ +================ +floating ip pool +================ + +Compute v2, Network v2 + +floating ip pool list +--------------------- + +List pools of floating IP addresses + +.. program:: floating ip pool list +.. code:: bash + + os floating ip pool list diff --git a/doc/source/command-objects/ip-floating-pool.rst b/doc/source/command-objects/ip-floating-pool.rst index 63a450eb40..310974c619 100644 --- a/doc/source/command-objects/ip-floating-pool.rst +++ b/doc/source/command-objects/ip-floating-pool.rst @@ -8,6 +8,7 @@ ip floating pool list --------------------- List pools of floating IP addresses +(Deprecated, please use ``floating ip pool list`` instead) .. program:: ip floating pool list .. code:: bash diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 9d8ad6fd26..42268c25bd 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -91,6 +91,7 @@ referring to both Compute and Volume quotas. * ``extension``: (**Compute**, **Identity**, **Network**, **Volume**) OpenStack server API extensions * ``federation protocol``: (**Identity**) the underlying protocol used while federating identities * ``flavor``: (**Compute**) predefined server configurations: ram, root disk and so on +* ``floating ip pool``: (**Compute**, **Network**) - a pool of public IP addresses * ``group``: (**Identity**) a grouping of users * ``host``: (**Compute**) - the physical computer running compute services * ``hypervisor``: (**Compute**) the virtual machine manager diff --git a/openstackclient/compute/v2/floatingippool.py b/openstackclient/compute/v2/floatingippool.py deleted file mode 100644 index 0d46e32b12..0000000000 --- a/openstackclient/compute/v2/floatingippool.py +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright 2013 OpenStack Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - -"""Floating IP Pool action implementations""" - -from osc_lib.command import command -from osc_lib import utils - - -class ListFloatingIPPool(command.Lister): - """List pools of floating IP addresses""" - - def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - - columns = ('Name',) - - data = compute_client.floating_ip_pools.list() - - return (columns, - (utils.get_item_properties( - s, columns, - formatters={}, - ) for s in data)) diff --git a/openstackclient/network/v2/floating_ip_pool.py b/openstackclient/network/v2/floating_ip_pool.py new file mode 100644 index 0000000000..c78ca06a91 --- /dev/null +++ b/openstackclient/network/v2/floating_ip_pool.py @@ -0,0 +1,66 @@ +# 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. +# + +"""Floating IP Pool action implementations""" + +import logging + +from osc_lib import exceptions +from osc_lib import utils + +from openstackclient.i18n import _ +from openstackclient.network import common + + +class ListFloatingIPPool(common.NetworkAndComputeLister): + """List pools of floating IP addresses""" + + def take_action_network(self, client, parsed_args): + msg = _("Floating ip pool operations are only available for " + "Compute v2 network.") + raise exceptions.CommandError(msg) + + def take_action_compute(self, client, parsed_args): + columns = ( + 'Name', + ) + data = client.floating_ip_pools.list() + + return (columns, + (utils.get_item_properties( + s, columns, + ) for s in data)) + + +class ListIPFloatingPool(ListFloatingIPPool): + """List pools of floating IP addresses""" + + # TODO(tangchen): Remove this class and ``ip floating pool list`` command + # two cycles after Mitaka. + + # This notifies cliff to not display the help for this command + deprecated = True + + log = logging.getLogger('deprecated') + + def take_action_network(self, client, parsed_args): + self.log.warning(_('This command has been deprecated. ' + 'Please use "floating ip pool list" instead.')) + return super(ListIPFloatingPool, self).take_action_network( + client, parsed_args) + + def take_action_compute(self, client, parsed_args): + self.log.warning(_('This command has been deprecated. ' + 'Please use "floating ip pool list" instead.')) + return super(ListIPFloatingPool, self).take_action_compute( + client, parsed_args) diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index b9add2c8f9..f9b1f75f0c 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -178,6 +178,9 @@ def __init__(self, **kwargs): self.floating_ips = mock.Mock() self.floating_ips.resource_class = fakes.FakeResource(None, {}) + self.floating_ip_pools = mock.Mock() + self.floating_ip_pools.resource_class = fakes.FakeResource(None, {}) + self.networks = mock.Mock() self.networks.resource_class = fakes.FakeResource(None, {}) @@ -971,6 +974,54 @@ def get_floating_ips(floating_ips=None, count=2): return mock.MagicMock(side_effect=floating_ips) +class FakeFloatingIPPool(object): + """Fake one or more floating ip pools.""" + + @staticmethod + def create_one_floating_ip_pool(attrs=None): + """Create a fake floating ip pool. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object, with id, etc + """ + if attrs is None: + attrs = {} + + # Set default attributes. + floating_ip_pool_attrs = { + 'name': 'floating-ip-pool-name-' + uuid.uuid4().hex, + } + + # Overwrite default attributes. + floating_ip_pool_attrs.update(attrs) + + floating_ip_pool = fakes.FakeResource( + info=copy.deepcopy(floating_ip_pool_attrs), + loaded=True) + + return floating_ip_pool + + @staticmethod + def create_floating_ip_pools(attrs=None, count=2): + """Create multiple fake floating ip pools. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of floating ip pools to fake + :return: + A list of FakeResource objects faking the floating ip pools + """ + floating_ip_pools = [] + for i in range(0, count): + floating_ip_pools.append( + FakeFloatingIPPool.create_one_floating_ip_pool(attrs) + ) + return floating_ip_pools + + class FakeNetwork(object): """Fake one or more networks.""" diff --git a/openstackclient/tests/network/v2/test_floating_ip_pool.py b/openstackclient/tests/network/v2/test_floating_ip_pool.py new file mode 100644 index 0000000000..22d20d202d --- /dev/null +++ b/openstackclient/tests/network/v2/test_floating_ip_pool.py @@ -0,0 +1,97 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +from osc_lib import exceptions + +from openstackclient.network.v2 import floating_ip_pool +from openstackclient.tests.compute.v2 import fakes as compute_fakes +from openstackclient.tests.network.v2 import fakes as network_fakes + + +# Tests for Network API v2 +# +class TestFloatingIPPoolNetwork(network_fakes.TestNetworkV2): + + def setUp(self): + super(TestFloatingIPPoolNetwork, self).setUp() + + # Get a shortcut to the network client + self.network = self.app.client_manager.network + + +class TestListFloatingIPPoolNetwork(TestFloatingIPPoolNetwork): + + def setUp(self): + super(TestListFloatingIPPoolNetwork, self).setUp() + + # Get the command object to test + self.cmd = floating_ip_pool.ListFloatingIPPool(self.app, + self.namespace) + + def test_floating_ip_list(self): + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + + +# Tests for Compute network +# +class TestFloatingIPPoolCompute(compute_fakes.TestComputev2): + + def setUp(self): + super(TestFloatingIPPoolCompute, self).setUp() + + # Get a shortcut to the compute client + self.compute = self.app.client_manager.compute + + +class TestListFloatingIPPoolCompute(TestFloatingIPPoolCompute): + + # The floating ip pools to list up + floating_ip_pools = \ + compute_fakes.FakeFloatingIPPool.create_floating_ip_pools(count=3) + + columns = ( + 'Name', + ) + + data = [] + for pool in floating_ip_pools: + data.append(( + pool.name, + )) + + def setUp(self): + super(TestListFloatingIPPoolCompute, self).setUp() + + self.app.client_manager.network_endpoint_enabled = False + + self.compute.floating_ip_pools.list.return_value = \ + self.floating_ip_pools + + # Get the command object to test + self.cmd = floating_ip_pool.ListFloatingIPPool(self.app, None) + + def test_floating_ip_list(self): + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.compute.floating_ip_pools.list.assert_called_once_with() + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) diff --git a/releasenotes/notes/ip-command-rework-8d3fe0858f51e6b8.yaml b/releasenotes/notes/ip-command-rework-8d3fe0858f51e6b8.yaml new file mode 100644 index 0000000000..c5e36cfa08 --- /dev/null +++ b/releasenotes/notes/ip-command-rework-8d3fe0858f51e6b8.yaml @@ -0,0 +1,9 @@ +--- +features: + - Add new command ``floating ip pool list`` to list up all floating ip + pools. This command is used to replace the old command + ``ip floating pool list``. + [Blueprint rework-ip-commands ``_] +deprecations: + - Deprecate command ``ip floating pool list``. + [Blueprint rework-ip-commands ``_] diff --git a/setup.cfg b/setup.cfg index d41cdc0171..cf78baf798 100644 --- a/setup.cfg +++ b/setup.cfg @@ -92,7 +92,6 @@ openstack.compute.v2 = ip_floating_add = openstackclient.compute.v2.floatingip:AddFloatingIP ip_floating_remove = openstackclient.compute.v2.floatingip:RemoveFloatingIP - ip_floating_pool_list = openstackclient.compute.v2.floatingippool:ListFloatingIPPool keypair_create = openstackclient.compute.v2.keypair:CreateKeypair keypair_delete = openstackclient.compute.v2.keypair:DeleteKeypair @@ -333,6 +332,8 @@ openstack.network.v2 = address_scope_set = openstackclient.network.v2.address_scope:SetAddressScope address_scope_show = openstackclient.network.v2.address_scope:ShowAddressScope + floating_ip_pool_list = openstackclient.network.v2.floating_ip_pool:ListFloatingIPPool + ip_availability_list = openstackclient.network.v2.ip_availability:ListIPAvailability ip_availability_show = openstackclient.network.v2.ip_availability:ShowIPAvailability @@ -341,6 +342,8 @@ openstack.network.v2 = ip_floating_list = openstackclient.network.v2.floating_ip:ListFloatingIP ip_floating_show = openstackclient.network.v2.floating_ip:ShowFloatingIP + ip_floating_pool_list = openstackclient.network.v2.floating_ip_pool:ListIPFloatingPool + network_create = openstackclient.network.v2.network:CreateNetwork network_delete = openstackclient.network.v2.network:DeleteNetwork network_list = openstackclient.network.v2.network:ListNetwork From d1f9ea3f750bb4c1f440ebfa93d06a40673ec0aa Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Thu, 7 Apr 2016 14:10:37 +0800 Subject: [PATCH 1066/3095] Transfer "ip floating add/remove" to "server add/remove floating ip" This patch does the following things to transfer "ip floating add/remove" to "server add/remove floating ip": * Add new command "server add/remove floating ip", and unit tests and doc. * Deprecate "ip floating add/remove" command. compute/v2/floatingip.py is not removed because the arguments' positions are different between the new and old commands. * ip floating add server add floating ip * ip floating remove server remove floating ip Change-Id: Ic0dd22ca6fb7b7bc3e820fd5a14d7c551e7ab963 Implements: blueprint rework-ip-commands Partial-bug: 1555990 Co-Authored-By: Dean Troyer --- doc/source/command-objects/ip-floating.rst | 2 + doc/source/command-objects/server.rst | 40 +++++++++++ doc/source/commands.rst | 1 + openstackclient/compute/v2/floatingip.py | 34 ++++++++-- openstackclient/compute/v2/server.py | 55 +++++++++++++++ .../tests/compute/v2/test_server.py | 67 +++++++++++++++++++ .../ip-command-rework-8d3fe0858f51e6b8.yaml | 5 ++ setup.cfg | 2 + 8 files changed, 202 insertions(+), 4 deletions(-) diff --git a/doc/source/command-objects/ip-floating.rst b/doc/source/command-objects/ip-floating.rst index 8dd9478218..378812d123 100644 --- a/doc/source/command-objects/ip-floating.rst +++ b/doc/source/command-objects/ip-floating.rst @@ -8,6 +8,7 @@ ip floating add --------------- Add floating IP address to server +(Deprecated, please use ``server add floating ip`` instead) .. program:: ip floating add .. code:: bash @@ -92,6 +93,7 @@ ip floating remove ------------------ Remove floating IP address from server +(Deprecated, please use ``server remove floating ip`` instead) .. program:: ip floating remove .. code:: bash diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index f11355b6dc..3393e7fbd2 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -4,6 +4,26 @@ server Compute v2 +server add floating ip +---------------------- + +Add floating IP address to server + +.. program:: server add floating ip +.. code:: bash + + os server add floating ip + + + +.. describe:: + + Server (name or ID) to receive the floating IP address + +.. describe:: + + Floating IP address (IP address only) to assign to server + server add security group ------------------------- @@ -418,6 +438,26 @@ Rebuild server Server (name or ID) +server remove floating ip +------------------------- + +Remove floating IP address from server + +.. program:: server remove floating ip +.. code:: bash + + os server remove floating ip + + + +.. describe:: + + Server (name or ID) to remove the floating IP address from + +.. describe:: + + Floating IP address (IP address only) to remove from server + server remove security group ---------------------------- diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 42268c25bd..714cd518a8 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -91,6 +91,7 @@ referring to both Compute and Volume quotas. * ``extension``: (**Compute**, **Identity**, **Network**, **Volume**) OpenStack server API extensions * ``federation protocol``: (**Identity**) the underlying protocol used while federating identities * ``flavor``: (**Compute**) predefined server configurations: ram, root disk and so on +* ``floating ip``: (**Compute**, **Network**) - a public IP address that can be mapped to a server * ``floating ip pool``: (**Compute**, **Network**) - a pool of public IP addresses * ``group``: (**Identity**) a grouping of users * ``host``: (**Compute**) - the physical computer running compute services diff --git a/openstackclient/compute/v2/floatingip.py b/openstackclient/compute/v2/floatingip.py index 98079fbc38..8398ea57db 100644 --- a/openstackclient/compute/v2/floatingip.py +++ b/openstackclient/compute/v2/floatingip.py @@ -15,28 +15,43 @@ """Floating IP action implementations""" +import logging + from osc_lib.command import command from osc_lib import utils +from openstackclient.i18n import _ + class AddFloatingIP(command.Command): """Add floating IP address to server""" + # TODO(tangchen): Remove this class and ``ip floating add`` command + # two cycles after Mitaka. + + # This notifies cliff to not display the help for this command + deprecated = True + + log = logging.getLogger('deprecated') + def get_parser(self, prog_name): parser = super(AddFloatingIP, self).get_parser(prog_name) parser.add_argument( "ip_address", metavar="", - help="IP address to add to server (name only)", + help=_("IP address to add to server (name only)"), ) parser.add_argument( "server", metavar="", - help="Server to receive the IP address (name or ID)", + help=_("Server to receive the IP address (name or ID)"), ) return parser def take_action(self, parsed_args): + self.log.warning(_('This command has been deprecated. ' + 'Please use "server add floating ip" instead.')) + compute_client = self.app.client_manager.compute server = utils.find_resource( @@ -48,21 +63,32 @@ def take_action(self, parsed_args): class RemoveFloatingIP(command.Command): """Remove floating IP address from server""" + # TODO(tangchen): Remove this class and ``ip floating remove`` command + # two cycles after Mitaka. + + # This notifies cliff to not display the help for this command + deprecated = True + + log = logging.getLogger('deprecated') + def get_parser(self, prog_name): parser = super(RemoveFloatingIP, self).get_parser(prog_name) parser.add_argument( "ip_address", metavar="", - help="IP address to remove from server (name only)", + help=_("IP address to remove from server (name only)"), ) parser.add_argument( "server", metavar="", - help="Server to remove the IP address from (name or ID)", + help=_("Server to remove the IP address from (name or ID)"), ) return parser def take_action(self, parsed_args): + self.log.warning(_('This command has been deprecated. ' + 'Please use "server remove floating ip" instead.')) + compute_client = self.app.client_manager.compute server = utils.find_resource( diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 7e4b0dc1f7..d7c3a6566e 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -174,6 +174,33 @@ def _show_progress(progress): sys.stdout.flush() +class AddFloatingIP(command.Command): + """Add floating IP address to server""" + + def get_parser(self, prog_name): + parser = super(AddFloatingIP, self).get_parser(prog_name) + parser.add_argument( + "server", + metavar="", + help=_("Server (name or ID) to receive the floating IP address"), + ) + parser.add_argument( + "ip_address", + metavar="", + help=_("Floating IP address (IP address only) to assign " + "to server"), + ) + return parser + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + + server = utils.find_resource( + compute_client.servers, parsed_args.server) + + server.add_floating_ip(parsed_args.ip_address) + + class AddServerSecurityGroup(command.Command): """Add security group to server""" @@ -1081,6 +1108,34 @@ def take_action(self, parsed_args): return zip(*sorted(six.iteritems(details))) +class RemoveFloatingIP(command.Command): + """Remove floating IP address from server""" + + def get_parser(self, prog_name): + parser = super(RemoveFloatingIP, self).get_parser(prog_name) + parser.add_argument( + "server", + metavar="", + help=_("Server (name or ID) to remove the " + "floating IP address from"), + ) + parser.add_argument( + "ip_address", + metavar="", + help=_("Floating IP address (IP address only) " + "to remove from server"), + ) + return parser + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + + server = utils.find_resource( + compute_client.servers, parsed_args.server) + + server.remove_floating_ip(parsed_args.ip_address) + + class RemoveServerSecurityGroup(command.Command): """Remove security group from server""" diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 0f155601a8..81f2185698 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -88,6 +88,41 @@ def run_method_with_servers(self, method_name, server_count): self.assertIsNone(result) +class TestServerAddFloatingIP(TestServer): + + def setUp(self): + super(TestServerAddFloatingIP, self).setUp() + + # Get a shortcut to the compute client ServerManager Mock + self.networks_mock = self.app.client_manager.compute.networks + + # Get the command object to test + self.cmd = server.AddFloatingIP(self.app, None) + + # Set add_floating_ip method to be tested. + self.methods = { + 'add_floating_ip': None, + } + + def test_server_add_floating_ip(self): + servers = self.setup_servers_mock(count=1) + + arglist = [ + servers[0].id, + '1.2.3.4', + ] + verifylist = [ + ('server', servers[0].id), + ('ip_address', '1.2.3.4'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + servers[0].add_floating_ip.assert_called_once_with('1.2.3.4') + self.assertIsNone(result) + + class TestServerCreate(TestServer): columns = ( @@ -843,6 +878,38 @@ def test_rebuild_with_wait_fails(self, mock_wait_for_status): self.server.rebuild.assert_called_with(self.image, None) +class TestServerRemoveFloatingIP(TestServer): + + def setUp(self): + super(TestServerRemoveFloatingIP, self).setUp() + + # Get the command object to test + self.cmd = server.RemoveFloatingIP(self.app, None) + + # Set unshelve method to be tested. + self.methods = { + 'remove_floating_ip': None, + } + + def test_server_remove_floating_ip(self): + servers = self.setup_servers_mock(count=1) + + arglist = [ + servers[0].id, + '1.2.3.4', + ] + verifylist = [ + ('server', servers[0].id), + ('ip_address', '1.2.3.4'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + servers[0].remove_floating_ip.assert_called_once_with('1.2.3.4') + self.assertIsNone(result) + + class TestServerResize(TestServer): def setUp(self): diff --git a/releasenotes/notes/ip-command-rework-8d3fe0858f51e6b8.yaml b/releasenotes/notes/ip-command-rework-8d3fe0858f51e6b8.yaml index c5e36cfa08..3cfb11badd 100644 --- a/releasenotes/notes/ip-command-rework-8d3fe0858f51e6b8.yaml +++ b/releasenotes/notes/ip-command-rework-8d3fe0858f51e6b8.yaml @@ -4,6 +4,11 @@ features: pools. This command is used to replace the old command ``ip floating pool list``. [Blueprint rework-ip-commands ``_] + - Add new commands ``server add/remove floating ip``. They are used to + replace the old commands ``ip floating add/remove``. + [Blueprint rework-ip-commands ``_] deprecations: - Deprecate command ``ip floating pool list``. [Blueprint rework-ip-commands ``_] + - Deprecate commands ``ip floating add/remove``. + [Blueprint rework-ip-commands ``_] diff --git a/setup.cfg b/setup.cfg index cf78baf798..bab657ff2d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -98,6 +98,7 @@ openstack.compute.v2 = keypair_list = openstackclient.compute.v2.keypair:ListKeypair keypair_show = openstackclient.compute.v2.keypair:ShowKeypair + server_add_floating_ip = openstackclient.compute.v2.server:AddFloatingIP server_add_security_group = openstackclient.compute.v2.server:AddServerSecurityGroup server_add_volume = openstackclient.compute.v2.server:AddServerVolume server_create = openstackclient.compute.v2.server:CreateServer @@ -108,6 +109,7 @@ openstack.compute.v2 = server_pause = openstackclient.compute.v2.server:PauseServer server_reboot = openstackclient.compute.v2.server:RebootServer server_rebuild = openstackclient.compute.v2.server:RebuildServer + server_remove_floating_ip = openstackclient.compute.v2.server:RemoveFloatingIP server_remove_security_group = openstackclient.compute.v2.server:RemoveServerSecurityGroup server_remove_volume = openstackclient.compute.v2.server:RemoveServerVolume server_rescue = openstackclient.compute.v2.server:RescueServer From 179ebe6d648fc5cff514306076f1b0fa54e64b40 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Thu, 7 Apr 2016 14:30:06 +0800 Subject: [PATCH 1067/3095] Transfer "ip fixed add/remove" to "server add/remove fixed ip" This patch does the following things to transfer "ip fixed add/remove" to "server add/remove fixed ip": * Add new command "server add/remove fixed ip", and unit tests and doc. * Deprecate "ip fixed add/remove" command. compute/v2/fixedip.py is not removed because the arguments' positions are different between the new and old commands. * ip fixed add server add fixed ip * ip fixed remove server remove fixed ip Change-Id: Ica07ccf92a76c21fd5608ecaff86ff7c4d96f5a0 Implements: blueprint rework-ip-commands Partial-bug: 1555990 Co-Authored-By: Dean Troyer --- doc/source/command-objects/ip-fixed.rst | 2 + doc/source/command-objects/server.rst | 40 +++++++++++ doc/source/commands.rst | 1 + openstackclient/compute/v2/fixedip.py | 34 +++++++-- openstackclient/compute/v2/server.py | 57 +++++++++++++++ .../tests/compute/v2/test_server.py | 71 +++++++++++++++++++ .../ip-command-rework-8d3fe0858f51e6b8.yaml | 5 ++ setup.cfg | 2 + 8 files changed, 208 insertions(+), 4 deletions(-) diff --git a/doc/source/command-objects/ip-fixed.rst b/doc/source/command-objects/ip-fixed.rst index 3a55b9953c..c2447b2890 100644 --- a/doc/source/command-objects/ip-fixed.rst +++ b/doc/source/command-objects/ip-fixed.rst @@ -8,6 +8,7 @@ ip fixed add ------------ Add fixed IP address to server +(Deprecated, please use ``server add fixed ip`` instead) .. program:: ip fixed add .. code:: bash @@ -28,6 +29,7 @@ ip fixed remove --------------- Remove fixed IP address from server +(Deprecated, please use ``server remove fixed ip`` instead) .. program:: ip fixed remove .. code:: bash diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index 3393e7fbd2..49a5415337 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -4,6 +4,26 @@ server Compute v2 +server add fixed ip +------------------- + +Add fixed IP address to server + +.. program:: server add fixed ip +.. code:: bash + + os server add fixed ip + + + +.. describe:: + + Server (name or ID) to receive the fixed IP address + +.. describe:: + + Network (name or ID) to allocate the fixed IP address from + server add floating ip ---------------------- @@ -438,6 +458,26 @@ Rebuild server Server (name or ID) +server remove fixed ip +---------------------- + +Remove fixed IP address from server + +.. program:: server remove fixed ip +.. code:: bash + + os server remove fixed ip + + + +.. describe:: + + Server (name or ID) to remove the fixed IP address from + +.. describe:: + + Fixed IP address (IP address only) to remove from the server + server remove floating ip ------------------------- diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 714cd518a8..f6d213a072 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -91,6 +91,7 @@ referring to both Compute and Volume quotas. * ``extension``: (**Compute**, **Identity**, **Network**, **Volume**) OpenStack server API extensions * ``federation protocol``: (**Identity**) the underlying protocol used while federating identities * ``flavor``: (**Compute**) predefined server configurations: ram, root disk and so on +* ``fixed ip``: (**Compute**, **Network**) - an internal IP address assigned to a server * ``floating ip``: (**Compute**, **Network**) - a public IP address that can be mapped to a server * ``floating ip pool``: (**Compute**, **Network**) - a pool of public IP addresses * ``group``: (**Identity**) a grouping of users diff --git a/openstackclient/compute/v2/fixedip.py b/openstackclient/compute/v2/fixedip.py index 8bd72ca3d6..c14d29fa96 100644 --- a/openstackclient/compute/v2/fixedip.py +++ b/openstackclient/compute/v2/fixedip.py @@ -15,28 +15,43 @@ """Fixed IP action implementations""" +import logging + from osc_lib.command import command from osc_lib import utils +from openstackclient.i18n import _ + class AddFixedIP(command.Command): """Add fixed IP address to server""" + # TODO(tangchen): Remove this class and ``ip fixed add`` command + # two cycles after Mitaka. + + # This notifies cliff to not display the help for this command + deprecated = True + + log = logging.getLogger('deprecated') + def get_parser(self, prog_name): parser = super(AddFixedIP, self).get_parser(prog_name) parser.add_argument( "network", metavar="", - help="Network to fetch an IP address from (name or ID)", + help=_("Network to fetch an IP address from (name or ID)"), ) parser.add_argument( "server", metavar="", - help="Server to receive the IP address (name or ID)", + help=_("Server to receive the IP address (name or ID)"), ) return parser def take_action(self, parsed_args): + self.log.warning(_('This command has been deprecated. ' + 'Please use "server add fixed ip" instead.')) + compute_client = self.app.client_manager.compute network = utils.find_resource( @@ -51,21 +66,32 @@ def take_action(self, parsed_args): class RemoveFixedIP(command.Command): """Remove fixed IP address from server""" + # TODO(tangchen): Remove this class and ``ip fixed remove`` command + # two cycles after Mitaka. + + # This notifies cliff to not display the help for this command + deprecated = True + + log = logging.getLogger('deprecated') + def get_parser(self, prog_name): parser = super(RemoveFixedIP, self).get_parser(prog_name) parser.add_argument( "ip_address", metavar="", - help="IP address to remove from server (name only)", + help=_("IP address to remove from server (name only)"), ) parser.add_argument( "server", metavar="", - help="Server to remove the IP address from (name or ID)", + help=_("Server to remove the IP address from (name or ID)"), ) return parser def take_action(self, parsed_args): + self.log.warning(_('This command has been deprecated. ' + 'Please use "server remove fixed ip" instead.')) + compute_client = self.app.client_manager.compute server = utils.find_resource( diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index d7c3a6566e..7d04ffc3a0 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -174,6 +174,36 @@ def _show_progress(progress): sys.stdout.flush() +class AddFixedIP(command.Command): + """Add fixed IP address to server""" + + def get_parser(self, prog_name): + parser = super(AddFixedIP, self).get_parser(prog_name) + parser.add_argument( + "server", + metavar="", + help=_("Server (name or ID) to receive the fixed IP address"), + ) + parser.add_argument( + "network", + metavar="", + help=_("Network (name or ID) to allocate " + "the fixed IP address from"), + ) + return parser + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + + server = utils.find_resource( + compute_client.servers, parsed_args.server) + + network = utils.find_resource( + compute_client.networks, parsed_args.network) + + server.add_fixed_ip(network.id) + + class AddFloatingIP(command.Command): """Add floating IP address to server""" @@ -1108,6 +1138,33 @@ def take_action(self, parsed_args): return zip(*sorted(six.iteritems(details))) +class RemoveFixedIP(command.Command): + """Remove fixed IP address from server""" + + def get_parser(self, prog_name): + parser = super(RemoveFixedIP, self).get_parser(prog_name) + parser.add_argument( + "server", + metavar="", + help=_("Server (name or ID) to remove the fixed IP address from"), + ) + parser.add_argument( + "ip_address", + metavar="", + help=_("Fixed IP address (IP address only) to remove from the " + "server"), + ) + return parser + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + + server = utils.find_resource( + compute_client.servers, parsed_args.server) + + server.remove_fixed_ip(parsed_args.ip_address) + + class RemoveFloatingIP(command.Command): """Remove floating IP address from server""" diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 81f2185698..57babd2fed 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -88,6 +88,45 @@ def run_method_with_servers(self, method_name, server_count): self.assertIsNone(result) +class TestServerAddFixedIP(TestServer): + + def setUp(self): + super(TestServerAddFixedIP, self).setUp() + + # Get a shortcut to the compute client ServerManager Mock + self.networks_mock = self.app.client_manager.compute.networks + + # Get the command object to test + self.cmd = server.AddFixedIP(self.app, None) + + # Set add_fixed_ip method to be tested. + self.methods = { + 'add_fixed_ip': None, + } + + def test_server_add_fixed_ip(self): + servers = self.setup_servers_mock(count=1) + network = compute_fakes.FakeNetwork.create_one_network() + self.networks_mock.get.return_value = network + + arglist = [ + servers[0].id, + network.id, + ] + verifylist = [ + ('server', servers[0].id), + ('network', network.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + servers[0].add_fixed_ip.assert_called_once_with( + network.id, + ) + self.assertIsNone(result) + + class TestServerAddFloatingIP(TestServer): def setUp(self): @@ -878,6 +917,38 @@ def test_rebuild_with_wait_fails(self, mock_wait_for_status): self.server.rebuild.assert_called_with(self.image, None) +class TestServerRemoveFixedIP(TestServer): + + def setUp(self): + super(TestServerRemoveFixedIP, self).setUp() + + # Get the command object to test + self.cmd = server.RemoveFixedIP(self.app, None) + + # Set unshelve method to be tested. + self.methods = { + 'remove_fixed_ip': None, + } + + def test_server_remove_fixed_ip(self): + servers = self.setup_servers_mock(count=1) + + arglist = [ + servers[0].id, + '1.2.3.4', + ] + verifylist = [ + ('server', servers[0].id), + ('ip_address', '1.2.3.4'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + servers[0].remove_fixed_ip.assert_called_once_with('1.2.3.4') + self.assertIsNone(result) + + class TestServerRemoveFloatingIP(TestServer): def setUp(self): diff --git a/releasenotes/notes/ip-command-rework-8d3fe0858f51e6b8.yaml b/releasenotes/notes/ip-command-rework-8d3fe0858f51e6b8.yaml index 3cfb11badd..6e95eb08aa 100644 --- a/releasenotes/notes/ip-command-rework-8d3fe0858f51e6b8.yaml +++ b/releasenotes/notes/ip-command-rework-8d3fe0858f51e6b8.yaml @@ -7,8 +7,13 @@ features: - Add new commands ``server add/remove floating ip``. They are used to replace the old commands ``ip floating add/remove``. [Blueprint rework-ip-commands ``_] + - Add new commands ``server add/remove fixed ip``. They are used to + replace the old commands ``ip fixed add/remove``. + [Blueprint rework-ip-commands ``_] deprecations: - Deprecate command ``ip floating pool list``. [Blueprint rework-ip-commands ``_] - Deprecate commands ``ip floating add/remove``. [Blueprint rework-ip-commands ``_] + - Deprecate commands ``ip fixed add/remove``. + [Blueprint rework-ip-commands ``_] diff --git a/setup.cfg b/setup.cfg index bab657ff2d..5070ec9c6d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -98,6 +98,7 @@ openstack.compute.v2 = keypair_list = openstackclient.compute.v2.keypair:ListKeypair keypair_show = openstackclient.compute.v2.keypair:ShowKeypair + server_add_fixed_ip = openstackclient.compute.v2.server:AddFixedIP server_add_floating_ip = openstackclient.compute.v2.server:AddFloatingIP server_add_security_group = openstackclient.compute.v2.server:AddServerSecurityGroup server_add_volume = openstackclient.compute.v2.server:AddServerVolume @@ -109,6 +110,7 @@ openstack.compute.v2 = server_pause = openstackclient.compute.v2.server:PauseServer server_reboot = openstackclient.compute.v2.server:RebootServer server_rebuild = openstackclient.compute.v2.server:RebuildServer + server_remove_fixed_ip = openstackclient.compute.v2.server:RemoveFixedIP server_remove_floating_ip = openstackclient.compute.v2.server:RemoveFloatingIP server_remove_security_group = openstackclient.compute.v2.server:RemoveServerSecurityGroup server_remove_volume = openstackclient.compute.v2.server:RemoveServerVolume From 441e4e963a131b9cd323e22c9d0af17a1a033957 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Mon, 4 Jul 2016 09:42:14 +0800 Subject: [PATCH 1068/3095] Refactor unit tests for project and domain with fake classes in identityv3 Add FakeProject and FakeDomain classes and update unit tests for project and domain. Change-Id: Ifeed5dcba03155daa3b7b46b34d49c333ab19135 Partially-Implements: blueprint refactor-identity-unit-test --- openstackclient/tests/identity/v3/fakes.py | 64 +++ .../tests/identity/v3/test_domain.py | 136 +++--- .../tests/identity/v3/test_project.py | 441 +++++++++--------- 3 files changed, 343 insertions(+), 298 deletions(-) diff --git a/openstackclient/tests/identity/v3/fakes.py b/openstackclient/tests/identity/v3/fakes.py index dd918616ac..df532df401 100644 --- a/openstackclient/tests/identity/v3/fakes.py +++ b/openstackclient/tests/identity/v3/fakes.py @@ -15,6 +15,7 @@ import copy import mock +import uuid from keystoneauth1 import access from keystoneauth1 import fixture @@ -575,3 +576,66 @@ def setUp(self): endpoint=fakes.AUTH_URL, token=fakes.AUTH_TOKEN ) + + +class FakeProject(object): + """Fake one or more project.""" + + @staticmethod + def create_one_project(attrs=None): + """Create a fake project. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object, with id, name, and so on + """ + + attrs = attrs or {} + + # set default attributes. + project_info = { + 'id': 'project-id-' + uuid.uuid4().hex, + 'name': 'project-name-' + uuid.uuid4().hex, + 'description': 'project-description-' + uuid.uuid4().hex, + 'enabled': True, + 'is_domain': False, + 'domain_id': 'domain-id-' + uuid.uuid4().hex, + 'parent_id': 'parent-id-' + uuid.uuid4().hex, + 'links': 'links-' + uuid.uuid4().hex, + } + project_info.update(attrs) + + project = fakes.FakeResource(info=copy.deepcopy(project_info), + loaded=True) + return project + + +class FakeDomain(object): + """Fake one or more domain.""" + + @staticmethod + def create_one_domain(attrs=None): + """Create a fake domain. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object, with id, name, and so on + """ + + attrs = attrs or {} + + # set default attributes. + domain_info = { + 'id': 'domain-id-' + uuid.uuid4().hex, + 'name': 'domain-name-' + uuid.uuid4().hex, + 'description': 'domain-description-' + uuid.uuid4().hex, + 'enabled': True, + 'links': 'links-' + uuid.uuid4().hex, + } + domain_info.update(attrs) + + domain = fakes.FakeResource(info=copy.deepcopy(domain_info), + loaded=True) + return domain diff --git a/openstackclient/tests/identity/v3/test_domain.py b/openstackclient/tests/identity/v3/test_domain.py index e06e06812c..9229ddd0a9 100644 --- a/openstackclient/tests/identity/v3/test_domain.py +++ b/openstackclient/tests/identity/v3/test_domain.py @@ -10,10 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. -import copy - from openstackclient.identity.v3 import domain -from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes @@ -35,20 +32,17 @@ class TestDomainCreate(TestDomain): 'id', 'name', ) - datalist = ( - identity_fakes.domain_description, - True, - identity_fakes.domain_id, - identity_fakes.domain_name, - ) def setUp(self): super(TestDomainCreate, self).setUp() - self.domains_mock.create.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.DOMAIN), - loaded=True, + self.domain = identity_fakes.FakeDomain.create_one_domain() + self.domains_mock.create.return_value = self.domain + self.datalist = ( + self.domain.description, + True, + self.domain.id, + self.domain.name, ) # Get the command object to test @@ -56,10 +50,10 @@ def setUp(self): def test_domain_create_no_options(self): arglist = [ - identity_fakes.domain_name, + self.domain.name, ] verifylist = [ - ('name', identity_fakes.domain_name), + ('name', self.domain.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -70,7 +64,7 @@ def test_domain_create_no_options(self): # Set expected values kwargs = { - 'name': identity_fakes.domain_name, + 'name': self.domain.name, 'description': None, 'enabled': True, } @@ -84,11 +78,11 @@ def test_domain_create_no_options(self): def test_domain_create_description(self): arglist = [ '--description', 'new desc', - identity_fakes.domain_name, + self.domain.name, ] verifylist = [ ('description', 'new desc'), - ('name', identity_fakes.domain_name), + ('name', self.domain.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -99,7 +93,7 @@ def test_domain_create_description(self): # Set expected values kwargs = { - 'name': identity_fakes.domain_name, + 'name': self.domain.name, 'description': 'new desc', 'enabled': True, } @@ -113,11 +107,11 @@ def test_domain_create_description(self): def test_domain_create_enable(self): arglist = [ '--enable', - identity_fakes.domain_name, + self.domain.name, ] verifylist = [ ('enable', True), - ('name', identity_fakes.domain_name), + ('name', self.domain.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -128,7 +122,7 @@ def test_domain_create_enable(self): # Set expected values kwargs = { - 'name': identity_fakes.domain_name, + 'name': self.domain.name, 'description': None, 'enabled': True, } @@ -142,11 +136,11 @@ def test_domain_create_enable(self): def test_domain_create_disable(self): arglist = [ '--disable', - identity_fakes.domain_name, + self.domain.name, ] verifylist = [ ('disable', True), - ('name', identity_fakes.domain_name), + ('name', self.domain.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -157,7 +151,7 @@ def test_domain_create_disable(self): # Set expected values kwargs = { - 'name': identity_fakes.domain_name, + 'name': self.domain.name, 'description': None, 'enabled': False, } @@ -171,15 +165,13 @@ def test_domain_create_disable(self): class TestDomainDelete(TestDomain): + domain = identity_fakes.FakeDomain.create_one_domain() + def setUp(self): super(TestDomainDelete, self).setUp() # This is the return value for utils.find_resource() - self.domains_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.DOMAIN), - loaded=True, - ) + self.domains_mock.get.return_value = self.domain self.domains_mock.delete.return_value = None # Get the command object to test @@ -187,33 +179,29 @@ def setUp(self): def test_domain_delete(self): arglist = [ - identity_fakes.domain_id, + self.domain.id, ] verifylist = [ - ('domain', identity_fakes.domain_id), + ('domain', self.domain.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.domains_mock.delete.assert_called_with( - identity_fakes.domain_id, + self.domain.id, ) self.assertIsNone(result) class TestDomainList(TestDomain): + domain = identity_fakes.FakeDomain.create_one_domain() + def setUp(self): super(TestDomainList, self).setUp() - self.domains_mock.list.return_value = [ - fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.DOMAIN), - loaded=True, - ), - ] + self.domains_mock.list.return_value = [self.domain] # Get the command object to test self.cmd = domain.ListDomain(self.app, None) @@ -232,40 +220,34 @@ def test_domain_list_no_options(self): collist = ('ID', 'Name', 'Enabled', 'Description') self.assertEqual(collist, columns) datalist = (( - identity_fakes.domain_id, - identity_fakes.domain_name, + self.domain.id, + self.domain.name, True, - identity_fakes.domain_description, + self.domain.description, ), ) self.assertEqual(datalist, tuple(data)) class TestDomainSet(TestDomain): + domain = identity_fakes.FakeDomain.create_one_domain() + def setUp(self): super(TestDomainSet, self).setUp() - self.domains_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.DOMAIN), - loaded=True, - ) + self.domains_mock.get.return_value = self.domain - self.domains_mock.update.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.DOMAIN), - loaded=True, - ) + self.domains_mock.update.return_value = self.domain # Get the command object to test self.cmd = domain.SetDomain(self.app, None) def test_domain_set_no_options(self): arglist = [ - identity_fakes.domain_name, + self.domain.name, ] verifylist = [ - ('domain', identity_fakes.domain_name), + ('domain', self.domain.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -277,11 +259,11 @@ def test_domain_set_no_options(self): def test_domain_set_name(self): arglist = [ '--name', 'qwerty', - identity_fakes.domain_id, + self.domain.id, ] verifylist = [ ('name', 'qwerty'), - ('domain', identity_fakes.domain_id), + ('domain', self.domain.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -292,7 +274,7 @@ def test_domain_set_name(self): 'name': 'qwerty', } self.domains_mock.update.assert_called_with( - identity_fakes.domain_id, + self.domain.id, **kwargs ) self.assertIsNone(result) @@ -300,11 +282,11 @@ def test_domain_set_name(self): def test_domain_set_description(self): arglist = [ '--description', 'new desc', - identity_fakes.domain_id, + self.domain.id, ] verifylist = [ ('description', 'new desc'), - ('domain', identity_fakes.domain_id), + ('domain', self.domain.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -315,7 +297,7 @@ def test_domain_set_description(self): 'description': 'new desc', } self.domains_mock.update.assert_called_with( - identity_fakes.domain_id, + self.domain.id, **kwargs ) self.assertIsNone(result) @@ -323,11 +305,11 @@ def test_domain_set_description(self): def test_domain_set_enable(self): arglist = [ '--enable', - identity_fakes.domain_id, + self.domain.id, ] verifylist = [ ('enable', True), - ('domain', identity_fakes.domain_id), + ('domain', self.domain.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -338,7 +320,7 @@ def test_domain_set_enable(self): 'enabled': True, } self.domains_mock.update.assert_called_with( - identity_fakes.domain_id, + self.domain.id, **kwargs ) self.assertIsNone(result) @@ -346,11 +328,11 @@ def test_domain_set_enable(self): def test_domain_set_disable(self): arglist = [ '--disable', - identity_fakes.domain_id, + self.domain.id, ] verifylist = [ ('disable', True), - ('domain', identity_fakes.domain_id), + ('domain', self.domain.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -361,7 +343,7 @@ def test_domain_set_disable(self): 'enabled': False, } self.domains_mock.update.assert_called_with( - identity_fakes.domain_id, + self.domain.id, **kwargs ) self.assertIsNone(result) @@ -372,21 +354,17 @@ class TestDomainShow(TestDomain): def setUp(self): super(TestDomainShow, self).setUp() - self.domains_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.DOMAIN), - loaded=True, - ) - + self.domain = identity_fakes.FakeDomain.create_one_domain() + self.domains_mock.get.return_value = self.domain # Get the command object to test self.cmd = domain.ShowDomain(self.app, None) def test_domain_show(self): arglist = [ - identity_fakes.domain_id, + self.domain.id, ] verifylist = [ - ('domain', identity_fakes.domain_id), + ('domain', self.domain.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.app.client_manager.identity.tokens.get_token_data.return_value = \ @@ -405,15 +383,15 @@ def test_domain_show(self): # data to be shown. columns, data = self.cmd.take_action(parsed_args) self.domains_mock.get.assert_called_with( - identity_fakes.domain_id, + self.domain.id, ) collist = ('description', 'enabled', 'id', 'name') self.assertEqual(collist, columns) datalist = ( - identity_fakes.domain_description, + self.domain.description, True, - identity_fakes.domain_id, - identity_fakes.domain_name, + self.domain.id, + self.domain.name, ) self.assertEqual(datalist, data) diff --git a/openstackclient/tests/identity/v3/test_project.py b/openstackclient/tests/identity/v3/test_project.py index 93bf18afbc..4c122e3d90 100644 --- a/openstackclient/tests/identity/v3/test_project.py +++ b/openstackclient/tests/identity/v3/test_project.py @@ -13,13 +13,11 @@ # under the License. # -import copy import mock from osc_lib import exceptions from openstackclient.identity.v3 import project -from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes @@ -39,48 +37,46 @@ def setUp(self): class TestProjectCreate(TestProject): + domain = identity_fakes.FakeDomain.create_one_domain() + columns = ( 'description', 'domain_id', 'enabled', 'id', - 'name' - ) - datalist = ( - identity_fakes.project_description, - identity_fakes.domain_id, - True, - identity_fakes.project_id, - identity_fakes.project_name, + 'is_domain', + 'name', + 'parent_id', ) def setUp(self): super(TestProjectCreate, self).setUp() - self.domains_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.DOMAIN), - loaded=True, - ) - - self.projects_mock.create.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, + self.project = identity_fakes.FakeProject.create_one_project( + attrs={'domain_id': self.domain.id}) + self.domains_mock.get.return_value = self.domain + self.projects_mock.create.return_value = self.project + self.datalist = ( + self.project.description, + self.project.domain_id, + True, + self.project.id, + False, + self.project.name, + self.project.parent_id, ) - # Get the command object to test self.cmd = project.CreateProject(self.app, None) def test_project_create_no_options(self): arglist = [ - identity_fakes.project_name, + self.project.name, ] verifylist = [ ('parent', None), ('enable', False), ('disable', False), - ('name', identity_fakes.project_name), + ('name', self.project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -91,7 +87,7 @@ def test_project_create_no_options(self): # Set expected values kwargs = { - 'name': identity_fakes.project_name, + 'name': self.project.name, 'domain': None, 'description': None, 'enabled': True, @@ -103,27 +99,37 @@ def test_project_create_no_options(self): **kwargs ) - collist = ('description', 'domain_id', 'enabled', 'id', 'name') + collist = ( + 'description', + 'domain_id', + 'enabled', + 'id', + 'is_domain', + 'name', + 'parent_id', + ) self.assertEqual(collist, columns) datalist = ( - identity_fakes.project_description, - identity_fakes.domain_id, + self.project.description, + self.project.domain_id, True, - identity_fakes.project_id, - identity_fakes.project_name, + self.project.id, + False, + self.project.name, + self.project.parent_id, ) self.assertEqual(datalist, data) def test_project_create_description(self): arglist = [ '--description', 'new desc', - identity_fakes.project_name, + self.project.name, ] verifylist = [ ('description', 'new desc'), ('enable', False), ('disable', False), - ('name', identity_fakes.project_name), + ('name', self.project.name), ('parent', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -135,7 +141,7 @@ def test_project_create_description(self): # Set expected values kwargs = { - 'name': identity_fakes.project_name, + 'name': self.project.name, 'domain': None, 'description': 'new desc', 'enabled': True, @@ -152,14 +158,14 @@ def test_project_create_description(self): def test_project_create_domain(self): arglist = [ - '--domain', identity_fakes.domain_name, - identity_fakes.project_name, + '--domain', self.project.domain_id, + self.project.name, ] verifylist = [ - ('domain', identity_fakes.domain_name), + ('domain', self.project.domain_id), ('enable', False), ('disable', False), - ('name', identity_fakes.project_name), + ('name', self.project.name), ('parent', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -171,8 +177,8 @@ def test_project_create_domain(self): # Set expected values kwargs = { - 'name': identity_fakes.project_name, - 'domain': identity_fakes.domain_id, + 'name': self.project.name, + 'domain': self.project.domain_id, 'description': None, 'enabled': True, 'parent': None, @@ -188,14 +194,14 @@ def test_project_create_domain(self): def test_project_create_domain_no_perms(self): arglist = [ - '--domain', identity_fakes.domain_id, - identity_fakes.project_name, + '--domain', self.project.domain_id, + self.project.name, ] verifylist = [ - ('domain', identity_fakes.domain_id), + ('domain', self.project.domain_id), ('enable', False), ('disable', False), - ('name', identity_fakes.project_name), + ('name', self.project.name), ('parent', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -207,8 +213,8 @@ def test_project_create_domain_no_perms(self): # Set expected values kwargs = { - 'name': identity_fakes.project_name, - 'domain': identity_fakes.domain_id, + 'name': self.project.name, + 'domain': self.project.domain_id, 'description': None, 'enabled': True, 'parent': None, @@ -222,12 +228,12 @@ def test_project_create_domain_no_perms(self): def test_project_create_enable(self): arglist = [ '--enable', - identity_fakes.project_name, + self.project.name, ] verifylist = [ ('enable', True), ('disable', False), - ('name', identity_fakes.project_name), + ('name', self.project.name), ('parent', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -239,7 +245,7 @@ def test_project_create_enable(self): # Set expected values kwargs = { - 'name': identity_fakes.project_name, + 'name': self.project.name, 'domain': None, 'description': None, 'enabled': True, @@ -257,12 +263,12 @@ def test_project_create_enable(self): def test_project_create_disable(self): arglist = [ '--disable', - identity_fakes.project_name, + self.project.name, ] verifylist = [ ('enable', False), ('disable', True), - ('name', identity_fakes.project_name), + ('name', self.project.name), ('parent', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -274,7 +280,7 @@ def test_project_create_disable(self): # Set expected values kwargs = { - 'name': identity_fakes.project_name, + 'name': self.project.name, 'domain': None, 'description': None, 'enabled': False, @@ -293,11 +299,11 @@ def test_project_create_property(self): arglist = [ '--property', 'fee=fi', '--property', 'fo=fum', - identity_fakes.project_name, + self.project.name, ] verifylist = [ ('property', {'fee': 'fi', 'fo': 'fum'}), - ('name', identity_fakes.project_name), + ('name', self.project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -308,7 +314,7 @@ def test_project_create_property(self): # Set expected values kwargs = { - 'name': identity_fakes.project_name, + 'name': self.project.name, 'domain': None, 'description': None, 'enabled': True, @@ -326,37 +332,32 @@ def test_project_create_property(self): self.assertEqual(self.datalist, data) def test_project_create_parent(self): - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) - self.projects_mock.create.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT_WITH_PARENT), - loaded=True, - ) + self.parent = identity_fakes.FakeProject.create_one_project() + self.project = identity_fakes.FakeProject.create_one_project( + attrs={'domain_id': self.domain.id, 'parent_id': self.parent.id}) + self.projects_mock.get.return_value = self.parent + self.projects_mock.create.return_value = self.project arglist = [ - '--domain', identity_fakes.PROJECT_WITH_PARENT['domain_id'], - '--parent', identity_fakes.PROJECT['name'], - identity_fakes.PROJECT_WITH_PARENT['name'], + '--domain', self.project.domain_id, + '--parent', self.parent.name, + self.project.name, ] verifylist = [ - ('domain', identity_fakes.PROJECT_WITH_PARENT['domain_id']), - ('parent', identity_fakes.PROJECT['name']), + ('domain', self.project.domain_id), + ('parent', self.parent.name), ('enable', False), ('disable', False), - ('name', identity_fakes.PROJECT_WITH_PARENT['name']), + ('name', self.project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) kwargs = { - 'name': identity_fakes.PROJECT_WITH_PARENT['name'], - 'domain': identity_fakes.PROJECT_WITH_PARENT['domain_id'], - 'parent': identity_fakes.PROJECT['id'], + 'name': self.project.name, + 'domain': self.project.domain_id, + 'parent': self.parent.id, 'description': None, 'enabled': True, } @@ -370,17 +371,19 @@ def test_project_create_parent(self): 'domain_id', 'enabled', 'id', + 'is_domain', 'name', 'parent_id', ) self.assertEqual(columns, collist) datalist = ( - identity_fakes.PROJECT_WITH_PARENT['description'], - identity_fakes.PROJECT_WITH_PARENT['domain_id'], - identity_fakes.PROJECT_WITH_PARENT['enabled'], - identity_fakes.PROJECT_WITH_PARENT['id'], - identity_fakes.PROJECT_WITH_PARENT['name'], - identity_fakes.PROJECT['id'], + self.project.description, + self.project.domain_id, + self.project.enabled, + self.project.id, + self.project.is_domain, + self.project.name, + self.parent.id, ) self.assertEqual(data, datalist) @@ -392,16 +395,16 @@ def test_project_create_invalid_parent(self): 'Invalid parent') arglist = [ - '--domain', identity_fakes.domain_name, + '--domain', self.project.domain_id, '--parent', 'invalid', - identity_fakes.project_name, + self.project.name, ] verifylist = [ - ('domain', identity_fakes.domain_name), + ('domain', self.project.domain_id), ('parent', 'invalid'), ('enable', False), ('disable', False), - ('name', identity_fakes.project_name), + ('name', self.project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -414,15 +417,13 @@ def test_project_create_invalid_parent(self): class TestProjectDelete(TestProject): + project = identity_fakes.FakeProject.create_one_project() + def setUp(self): super(TestProjectDelete, self).setUp() # This is the return value for utils.find_resource() - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) + self.projects_mock.get.return_value = self.project self.projects_mock.delete.return_value = None # Get the command object to test @@ -430,44 +431,42 @@ def setUp(self): def test_project_delete_no_options(self): arglist = [ - identity_fakes.project_id, + self.project.id, ] verifylist = [ - ('projects', [identity_fakes.project_id]), + ('projects', [self.project.id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.projects_mock.delete.assert_called_with( - identity_fakes.project_id, + self.project.id, ) self.assertIsNone(result) class TestProjectList(TestProject): + domain = identity_fakes.FakeDomain.create_one_domain() + project = identity_fakes.FakeProject.create_one_project( + attrs={'domain_id': domain.id}) + columns = ( 'ID', 'Name', ) datalist = ( ( - identity_fakes.project_id, - identity_fakes.project_name, + project.id, + project.name, ), ) def setUp(self): super(TestProjectList, self).setUp() - self.projects_mock.list.return_value = [ - fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ), - ] + self.projects_mock.list.return_value = [self.project] # Get the command object to test self.cmd = project.ListProject(self.app, None) @@ -504,27 +503,23 @@ def test_project_list_long(self): collist = ('ID', 'Name', 'Domain ID', 'Description', 'Enabled') self.assertEqual(collist, columns) datalist = (( - identity_fakes.project_id, - identity_fakes.project_name, - identity_fakes.domain_id, - identity_fakes.project_description, + self.project.id, + self.project.name, + self.project.domain_id, + self.project.description, True, ), ) self.assertEqual(datalist, tuple(data)) def test_project_list_domain(self): arglist = [ - '--domain', identity_fakes.domain_name, + '--domain', self.project.domain_id, ] verifylist = [ - ('domain', identity_fakes.domain_name), + ('domain', self.project.domain_id), ] - self.domains_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.DOMAIN), - loaded=True, - ) + self.domains_mock.get.return_value = self.domain parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -533,17 +528,17 @@ def test_project_list_domain(self): # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) self.projects_mock.list.assert_called_with( - domain=identity_fakes.domain_id) + domain=self.project.domain_id) self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) def test_project_list_domain_no_perms(self): arglist = [ - '--domain', identity_fakes.domain_id, + '--domain', self.project.domain_id, ] verifylist = [ - ('domain', identity_fakes.domain_id), + ('domain', self.project.domain_id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) mocker = mock.Mock() @@ -553,42 +548,34 @@ def test_project_list_domain_no_perms(self): columns, data = self.cmd.take_action(parsed_args) self.projects_mock.list.assert_called_with( - domain=identity_fakes.domain_id) + domain=self.project.domain_id) self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) class TestProjectSet(TestProject): + domain = identity_fakes.FakeDomain.create_one_domain() + project = identity_fakes.FakeProject.create_one_project( + attrs={'domain_id': domain.id}) + def setUp(self): super(TestProjectSet, self).setUp() - self.domains_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.DOMAIN), - loaded=True, - ) + self.domains_mock.get.return_value = self.domain - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) - self.projects_mock.update.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) + self.projects_mock.get.return_value = self.project + self.projects_mock.update.return_value = self.project # Get the command object to test self.cmd = project.SetProject(self.app, None) def test_project_set_no_options(self): arglist = [ - identity_fakes.project_name, + self.project.name, ] verifylist = [ - ('project', identity_fakes.project_name), + ('project', self.project.name), ('enable', False), ('disable', False), ] @@ -601,15 +588,15 @@ def test_project_set_no_options(self): def test_project_set_name(self): arglist = [ '--name', 'qwerty', - '--domain', identity_fakes.domain_id, - identity_fakes.project_name, + '--domain', self.project.domain_id, + self.project.name, ] verifylist = [ ('name', 'qwerty'), - ('domain', identity_fakes.domain_id), + ('domain', self.project.domain_id), ('enable', False), ('disable', False), - ('project', identity_fakes.project_name), + ('project', self.project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -622,23 +609,23 @@ def test_project_set_name(self): # ProjectManager.update(project, name=, domain=, description=, # enabled=, **kwargs) self.projects_mock.update.assert_called_with( - identity_fakes.project_id, + self.project.id, **kwargs ) self.assertIsNone(result) def test_project_set_description(self): arglist = [ - '--domain', identity_fakes.domain_id, + '--domain', self.project.domain_id, '--description', 'new desc', - identity_fakes.project_name, + self.project.name, ] verifylist = [ - ('domain', identity_fakes.domain_id), + ('domain', self.project.domain_id), ('description', 'new desc'), ('enable', False), ('disable', False), - ('project', identity_fakes.project_name), + ('project', self.project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -649,22 +636,22 @@ def test_project_set_description(self): 'description': 'new desc', } self.projects_mock.update.assert_called_with( - identity_fakes.project_id, + self.project.id, **kwargs ) self.assertIsNone(result) def test_project_set_enable(self): arglist = [ - '--domain', identity_fakes.domain_id, + '--domain', self.project.domain_id, '--enable', - identity_fakes.project_name, + self.project.name, ] verifylist = [ - ('domain', identity_fakes.domain_id), + ('domain', self.project.domain_id), ('enable', True), ('disable', False), - ('project', identity_fakes.project_name), + ('project', self.project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -675,22 +662,22 @@ def test_project_set_enable(self): 'enabled': True, } self.projects_mock.update.assert_called_with( - identity_fakes.project_id, + self.project.id, **kwargs ) self.assertIsNone(result) def test_project_set_disable(self): arglist = [ - '--domain', identity_fakes.domain_id, + '--domain', self.project.domain_id, '--disable', - identity_fakes.project_name, + self.project.name, ] verifylist = [ - ('domain', identity_fakes.domain_id), + ('domain', self.project.domain_id), ('enable', False), ('disable', True), - ('project', identity_fakes.project_name), + ('project', self.project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -701,22 +688,22 @@ def test_project_set_disable(self): 'enabled': False, } self.projects_mock.update.assert_called_with( - identity_fakes.project_id, + self.project.id, **kwargs ) self.assertIsNone(result) def test_project_set_property(self): arglist = [ - '--domain', identity_fakes.domain_id, + '--domain', self.project.domain_id, '--property', 'fee=fi', '--property', 'fo=fum', - identity_fakes.project_name, + self.project.name, ] verifylist = [ - ('domain', identity_fakes.domain_id), + ('domain', self.project.domain_id), ('property', {'fee': 'fi', 'fo': 'fum'}), - ('project', identity_fakes.project_name), + ('project', self.project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -728,7 +715,7 @@ def test_project_set_property(self): 'fo': 'fum', } self.projects_mock.update.assert_called_with( - identity_fakes.project_id, + self.project.id, **kwargs ) self.assertIsNone(result) @@ -736,14 +723,14 @@ def test_project_set_property(self): class TestProjectShow(TestProject): + domain = identity_fakes.FakeDomain.create_one_domain() + def setUp(self): super(TestProjectShow, self).setUp() - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) + self.project = identity_fakes.FakeProject.create_one_project( + attrs={'domain_id': self.domain.id}) + self.projects_mock.get.return_value = self.project # Get the command object to test self.cmd = project.ShowProject(self.app, None) @@ -751,10 +738,10 @@ def setUp(self): def test_project_show(self): arglist = [ - identity_fakes.project_id, + self.project.id, ] verifylist = [ - ('project', identity_fakes.project_id), + ('project', self.project.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -773,37 +760,47 @@ def test_project_show(self): # data to be shown. columns, data = self.cmd.take_action(parsed_args) self.projects_mock.get.assert_called_with( - identity_fakes.project_id, + self.project.id, parents_as_list=False, subtree_as_list=False, ) - collist = ('description', 'domain_id', 'enabled', 'id', 'name') + collist = ( + 'description', + 'domain_id', + 'enabled', + 'id', + 'is_domain', + 'name', + 'parent_id', + ) self.assertEqual(collist, columns) datalist = ( - identity_fakes.project_description, - identity_fakes.domain_id, + self.project.description, + self.project.domain_id, True, - identity_fakes.project_id, - identity_fakes.project_name, + self.project.id, + False, + self.project.name, + self.project.parent_id, ) self.assertEqual(datalist, data) def test_project_show_parents(self): - project = copy.deepcopy(identity_fakes.PROJECT_WITH_GRANDPARENT) - project['parents'] = identity_fakes.grandparents - self.projects_mock.get.return_value = fakes.FakeResource( - None, - project, - loaded=True, + self.project = identity_fakes.FakeProject.create_one_project( + attrs={ + 'parent_id': self.project.parent_id, + 'parents': [{'project': {'id': self.project.parent_id}}] + } ) + self.projects_mock.get.return_value = self.project arglist = [ - identity_fakes.PROJECT_WITH_GRANDPARENT['id'], + self.project.id, '--parents', ] verifylist = [ - ('project', identity_fakes.PROJECT_WITH_GRANDPARENT['id']), + ('project', self.project.id), ('parents', True), ('children', False), ] @@ -820,7 +817,7 @@ def test_project_show_parents(self): columns, data = self.cmd.take_action(parsed_args) self.projects_mock.get.assert_called_with( - identity_fakes.PROJECT_WITH_GRANDPARENT['id'], + self.project.id, parents_as_list=True, subtree_as_list=False, ) @@ -830,37 +827,39 @@ def test_project_show_parents(self): 'domain_id', 'enabled', 'id', + 'is_domain', 'name', 'parent_id', 'parents', ) self.assertEqual(columns, collist) datalist = ( - identity_fakes.PROJECT_WITH_GRANDPARENT['description'], - identity_fakes.PROJECT_WITH_GRANDPARENT['domain_id'], - identity_fakes.PROJECT_WITH_GRANDPARENT['enabled'], - identity_fakes.PROJECT_WITH_GRANDPARENT['id'], - identity_fakes.PROJECT_WITH_GRANDPARENT['name'], - identity_fakes.PROJECT_WITH_GRANDPARENT['parent_id'], - identity_fakes.ids_for_parents_and_grandparents, + self.project.description, + self.project.domain_id, + self.project.enabled, + self.project.id, + self.project.is_domain, + self.project.name, + self.project.parent_id, + [self.project.parent_id], ) self.assertEqual(data, datalist) def test_project_show_subtree(self): - project = copy.deepcopy(identity_fakes.PROJECT_WITH_PARENT) - project['subtree'] = identity_fakes.children - self.projects_mock.get.return_value = fakes.FakeResource( - None, - project, - loaded=True, + self.project = identity_fakes.FakeProject.create_one_project( + attrs={ + 'parent_id': self.project.parent_id, + 'subtree': [{'project': {'id': 'children-id'}}] + } ) + self.projects_mock.get.return_value = self.project arglist = [ - identity_fakes.PROJECT_WITH_PARENT['id'], + self.project.id, '--children', ] verifylist = [ - ('project', identity_fakes.PROJECT_WITH_PARENT['id']), + ('project', self.project.id), ('parents', False), ('children', True), ] @@ -877,7 +876,7 @@ def test_project_show_subtree(self): columns, data = self.cmd.take_action(parsed_args) self.projects_mock.get.assert_called_with( - identity_fakes.PROJECT_WITH_PARENT['id'], + self.project.id, parents_as_list=False, subtree_as_list=True, ) @@ -887,39 +886,41 @@ def test_project_show_subtree(self): 'domain_id', 'enabled', 'id', + 'is_domain', 'name', 'parent_id', 'subtree', ) self.assertEqual(columns, collist) datalist = ( - identity_fakes.PROJECT_WITH_PARENT['description'], - identity_fakes.PROJECT_WITH_PARENT['domain_id'], - identity_fakes.PROJECT_WITH_PARENT['enabled'], - identity_fakes.PROJECT_WITH_PARENT['id'], - identity_fakes.PROJECT_WITH_PARENT['name'], - identity_fakes.PROJECT_WITH_PARENT['parent_id'], - identity_fakes.ids_for_children, + self.project.description, + self.project.domain_id, + self.project.enabled, + self.project.id, + self.project.is_domain, + self.project.name, + self.project.parent_id, + ['children-id'], ) self.assertEqual(data, datalist) def test_project_show_parents_and_children(self): - project = copy.deepcopy(identity_fakes.PROJECT_WITH_PARENT) - project['subtree'] = identity_fakes.children - project['parents'] = identity_fakes.parents - self.projects_mock.get.return_value = fakes.FakeResource( - None, - project, - loaded=True, + self.project = identity_fakes.FakeProject.create_one_project( + attrs={ + 'parent_id': self.project.parent_id, + 'parents': [{'project': {'id': self.project.parent_id}}], + 'subtree': [{'project': {'id': 'children-id'}}] + } ) + self.projects_mock.get.return_value = self.project arglist = [ - identity_fakes.PROJECT_WITH_PARENT['id'], + self.project.id, '--parents', '--children', ] verifylist = [ - ('project', identity_fakes.PROJECT_WITH_PARENT['id']), + ('project', self.project.id), ('parents', True), ('children', True), ] @@ -936,7 +937,7 @@ def test_project_show_parents_and_children(self): columns, data = self.cmd.take_action(parsed_args) self.projects_mock.get.assert_called_with( - identity_fakes.PROJECT_WITH_PARENT['id'], + self.project.id, parents_as_list=True, subtree_as_list=True, ) @@ -946,6 +947,7 @@ def test_project_show_parents_and_children(self): 'domain_id', 'enabled', 'id', + 'is_domain', 'name', 'parent_id', 'parents', @@ -953,13 +955,14 @@ def test_project_show_parents_and_children(self): ) self.assertEqual(columns, collist) datalist = ( - identity_fakes.PROJECT_WITH_PARENT['description'], - identity_fakes.PROJECT_WITH_PARENT['domain_id'], - identity_fakes.PROJECT_WITH_PARENT['enabled'], - identity_fakes.PROJECT_WITH_PARENT['id'], - identity_fakes.PROJECT_WITH_PARENT['name'], - identity_fakes.PROJECT_WITH_PARENT['parent_id'], - identity_fakes.ids_for_parents, - identity_fakes.ids_for_children, + self.project.description, + self.project.domain_id, + self.project.enabled, + self.project.id, + self.project.is_domain, + self.project.name, + self.project.parent_id, + [self.project.parent_id], + ['children-id'], ) self.assertEqual(data, datalist) From 43a07e133d0498dacc3c081c68508377d39e823b Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Mon, 4 Jul 2016 14:45:40 +0800 Subject: [PATCH 1069/3095] Add missing '(name only)' message for keypair in computev2 In 'keypair show' command. One help message had '(name only)' but the doc had not. And another had not '(name only)' but the doc had. This patch Fixs them and keeps the consistent between doc and help message. Change-Id: Ic88219ae25a6f545a1db39e6b585e1857ae9e2e0 --- doc/source/command-objects/keypair.rst | 2 +- openstackclient/compute/v2/keypair.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/command-objects/keypair.rst b/doc/source/command-objects/keypair.rst index 64cc20cd4c..af50a6511d 100644 --- a/doc/source/command-objects/keypair.rst +++ b/doc/source/command-objects/keypair.rst @@ -70,4 +70,4 @@ Display public key details .. describe:: - Public key to display + Public key to display (name only) diff --git a/openstackclient/compute/v2/keypair.py b/openstackclient/compute/v2/keypair.py index 3725a3a889..d30fd429e3 100644 --- a/openstackclient/compute/v2/keypair.py +++ b/openstackclient/compute/v2/keypair.py @@ -146,7 +146,7 @@ def get_parser(self, prog_name): '--public-key', action='store_true', default=False, - help=_("Show only bare public key") + help=_("Show only bare public key (name only)") ) return parser From 8aa00894efacc29185da660fb3ae7c42adefaa09 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Mon, 4 Jul 2016 16:42:42 +0800 Subject: [PATCH 1070/3095] Remove useless dest of option in "snapshot create" command The option is '--force' and the default argument is 'force', so the 'dest=force' is useless and unnecessary. This patch remove it. Change-Id: If8fd5270e4e36b5361127f4a91bdf347f29341c2 --- openstackclient/volume/v2/snapshot.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openstackclient/volume/v2/snapshot.py b/openstackclient/volume/v2/snapshot.py index 439904e744..5aba04aee3 100644 --- a/openstackclient/volume/v2/snapshot.py +++ b/openstackclient/volume/v2/snapshot.py @@ -46,7 +46,6 @@ def get_parser(self, prog_name): ) parser.add_argument( "--force", - dest="force", action="store_true", default=False, help=_("Create a snapshot attached to an instance. " From 6115dfe9f85e0972a01d6d50a91a19096540505e Mon Sep 17 00:00:00 2001 From: gecong1973 Date: Mon, 4 Jul 2016 16:36:43 +0800 Subject: [PATCH 1071/3095] fix a few spelling mistakes - overwriten should be overwritten - retrun should be return Change-Id: I1567402f4d5c7253e6a54d8753e3f201af7e6a54 --- openstackclient/tests/compute/v2/test_server.py | 4 ++-- openstackclient/tests/compute/v2/test_server_backup.py | 4 ++-- openstackclient/tests/compute/v2/test_server_image.py | 4 ++-- openstackclient/tests/image/v2/fakes.py | 2 +- openstackclient/tests/volume/v1/fakes.py | 4 ++-- openstackclient/tests/volume/v2/fakes.py | 6 +++--- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 0f155601a8..0bfe131019 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -51,10 +51,10 @@ def setUp(self): self.volumes_mock = self.app.client_manager.volume.volumes self.volumes_mock.reset_mock() - # Set object attributes to be tested. Could be overwriten in subclass. + # Set object attributes to be tested. Could be overwritten in subclass. self.attrs = {} - # Set object methods to be tested. Could be overwriten in subclass. + # Set object methods to be tested. Could be overwritten in subclass. self.methods = {} def setup_servers_mock(self, count): diff --git a/openstackclient/tests/compute/v2/test_server_backup.py b/openstackclient/tests/compute/v2/test_server_backup.py index b6802ff032..8eeb0dcabd 100644 --- a/openstackclient/tests/compute/v2/test_server_backup.py +++ b/openstackclient/tests/compute/v2/test_server_backup.py @@ -34,10 +34,10 @@ def setUp(self): self.images_mock = self.app.client_manager.image.images self.images_mock.reset_mock() - # Set object attributes to be tested. Could be overwriten in subclass. + # Set object attributes to be tested. Could be overwritten in subclass. self.attrs = {} - # Set object methods to be tested. Could be overwriten in subclass. + # Set object methods to be tested. Could be overwritten in subclass. self.methods = {} def setup_servers_mock(self, count): diff --git a/openstackclient/tests/compute/v2/test_server_image.py b/openstackclient/tests/compute/v2/test_server_image.py index 8a8bd9bc90..c3c52da05f 100644 --- a/openstackclient/tests/compute/v2/test_server_image.py +++ b/openstackclient/tests/compute/v2/test_server_image.py @@ -33,10 +33,10 @@ def setUp(self): self.images_mock = self.app.client_manager.image.images self.images_mock.reset_mock() - # Set object attributes to be tested. Could be overwriten in subclass. + # Set object attributes to be tested. Could be overwritten in subclass. self.attrs = {} - # Set object methods to be tested. Could be overwriten in subclass. + # Set object methods to be tested. Could be overwritten in subclass. self.methods = {} def setup_servers_mock(self, count): diff --git a/openstackclient/tests/image/v2/fakes.py b/openstackclient/tests/image/v2/fakes.py index 8e22fbb2e5..c2a8d7217b 100644 --- a/openstackclient/tests/image/v2/fakes.py +++ b/openstackclient/tests/image/v2/fakes.py @@ -190,7 +190,7 @@ def create_one_image(attrs=None): :param Dictionary attrs: A dictionary with all attrbutes of image - :retrun: + :return: A FakeResource object with id, name, owner, protected, visibility and tags attrs """ diff --git a/openstackclient/tests/volume/v1/fakes.py b/openstackclient/tests/volume/v1/fakes.py index 6c349866dd..2584d4b10d 100644 --- a/openstackclient/tests/volume/v1/fakes.py +++ b/openstackclient/tests/volume/v1/fakes.py @@ -157,7 +157,7 @@ def create_one_transfer(attrs=None): :param Dictionary attrs: A dictionary with all attributes of Transfer Request - :retrun: + :return: A FakeResource object with volume_id, name, id. """ # Set default attribute @@ -207,7 +207,7 @@ def create_one_service(attrs=None): :param Dictionary attrs: A dictionary with all attributes of service - :retrun: + :return: A FakeResource object with host, status, etc. """ # Set default attribute diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py index ae4c9f51ea..eff0faf940 100644 --- a/openstackclient/tests/volume/v2/fakes.py +++ b/openstackclient/tests/volume/v2/fakes.py @@ -53,7 +53,7 @@ def create_one_transfer(attrs=None): :param Dictionary attrs: A dictionary with all attributes of Transfer Request - :retrun: + :return: A FakeResource object with volume_id, name, id. """ # Set default attribute @@ -103,7 +103,7 @@ def create_one_service(attrs=None): :param Dictionary attrs: A dictionary with all attributes of service - :retrun: + :return: A FakeResource object with host, status, etc. """ # Set default attribute @@ -223,7 +223,7 @@ def create_one_volume(attrs=None): :param Dictionary attrs: A dictionary with all attributes of volume - :retrun: + :return: A FakeResource object with id, name, status, etc. """ attrs = attrs or {} From 578065b2c88a92b97e730682bdb636b6f8e6b42d Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Fri, 22 Apr 2016 12:30:19 -0500 Subject: [PATCH 1072/3095] Add python-neutronclient to OSC plugins Neutron is transitioning its CLI to OSC. Some CLIs will reside within python-openstackclient while others will be OSC plugins within python-neutronclient. Depends-On: I9a20bc7a3d8aa7b631fb0fd534fc5705c23326ce Change-Id: I2d8d369739d1df1597201368dd2158e07c8d2baf Related-Bug: #1521291 --- doc/source/plugin-commands.rst | 4 ++++ doc/source/plugins.rst | 9 ++++++--- test-requirements.txt | 1 + 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/doc/source/plugin-commands.rst b/doc/source/plugin-commands.rst index 66e1d72c25..614e28d924 100644 --- a/doc/source/plugin-commands.rst +++ b/doc/source/plugin-commands.rst @@ -52,6 +52,10 @@ Note: To see the complete syntax for the plugin commands, see the `CLI_Ref`_ .. list-plugins:: openstack.application_catalog.v1 :detailed: +# neutron +.. list-plugins:: openstack.neutronclient.v2 + :detailed: + # sahara .. list-plugins:: openstack.data_processing.v1 :detailed: diff --git a/doc/source/plugins.rst b/doc/source/plugins.rst index 412edd104b..9a05bb96de 100644 --- a/doc/source/plugins.rst +++ b/doc/source/plugins.rst @@ -12,8 +12,8 @@ Adoption OpenStackClient promises to provide first class support for the following OpenStack services: Compute, Identity, Image, Object Storage, Block Storage -and Network. These services are considered essential to any OpenStack -deployment. +and Network (core objects). These services are considered essential +to any OpenStack deployment. Other OpenStack services, such as Orchestration or Telemetry may create an OpenStackClient plugin. The source code will not be hosted by @@ -32,6 +32,7 @@ The following is a list of projects that are an OpenStackClient plugin. - python-ironic-inspector-client - python-mistralclient - python-muranoclient +- python-neutronclient\*\*\* - python-saharaclient - python-searchlightclient - python-senlinclient @@ -39,7 +40,9 @@ The following is a list of projects that are an OpenStackClient plugin. - python-watcherclient\*\* - python-zaqarclient -\*\* Note that some clients are not listed in global-requirements +\*\* Note that some clients are not listed in global-requirements. + +\*\*\* Project contains advanced network services. The following is a list of projects that are not an OpenStackClient plugin. diff --git a/test-requirements.txt b/test-requirements.txt index 34380080dc..dfd1077e51 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -29,6 +29,7 @@ python-ironicclient>=1.1.0 # Apache-2.0 python-ironic-inspector-client>=1.5.0 # Apache-2.0 python-mistralclient>=2.0.0 # Apache-2.0 python-muranoclient>=0.8.2 # Apache-2.0 +python-neutronclient>=4.2.0 # Apache-2.0 python-saharaclient>=0.13.0 # Apache-2.0 python-searchlightclient>=0.2.0 #Apache-2.0 python-senlinclient>=0.3.0 # Apache-2.0 From 259b4a14628b5eae5f9154a052381145e7e7ba1e Mon Sep 17 00:00:00 2001 From: Kyrylo Romanenko Date: Tue, 5 Jul 2016 12:16:18 +0300 Subject: [PATCH 1073/3095] Deduplicate get_opts methods One get_opts method can work instead of get_list_opts and get_show_opts both. Remove mutable default value. Change-Id: I9c5683d416f0f3ed4989abab6f152b0341e30a4f --- functional/common/test.py | 8 ++------ .../tests/common/test_availability_zone.py | 2 +- functional/tests/common/test_quota.py | 2 +- functional/tests/compute/v2/test_agent.py | 2 +- functional/tests/compute/v2/test_aggregate.py | 8 ++++---- functional/tests/compute/v2/test_flavor.py | 8 ++++---- functional/tests/compute/v2/test_server.py | 18 +++++++++--------- .../tests/compute/v2/test_server_group.py | 6 +++--- functional/tests/image/v1/test_image.py | 10 +++++----- functional/tests/image/v2/test_image.py | 12 ++++++------ .../tests/network/v2/test_address_scope.py | 8 ++++---- .../tests/network/v2/test_floating_ip.py | 6 +++--- .../tests/network/v2/test_ip_availability.py | 4 ++-- functional/tests/network/v2/test_network.py | 8 ++++---- .../tests/network/v2/test_network_segment.py | 8 ++++---- functional/tests/network/v2/test_port.py | 8 ++++---- functional/tests/network/v2/test_router.py | 8 ++++---- .../tests/network/v2/test_security_group.py | 8 ++++---- .../network/v2/test_security_group_rule.py | 8 ++++---- functional/tests/network/v2/test_subnet.py | 8 ++++---- .../tests/network/v2/test_subnet_pool.py | 8 ++++---- functional/tests/object/v1/test_container.py | 9 ++++----- functional/tests/volume/v1/test_qos.py | 8 ++++---- functional/tests/volume/v1/test_volume.py | 12 ++++++------ functional/tests/volume/v1/test_volume_type.py | 8 ++++---- functional/tests/volume/v2/test_qos.py | 10 +++++----- functional/tests/volume/v2/test_snapshot.py | 10 +++++----- functional/tests/volume/v2/test_volume.py | 16 ++++++++-------- functional/tests/volume/v2/test_volume_type.py | 8 ++++---- 29 files changed, 117 insertions(+), 122 deletions(-) diff --git a/functional/common/test.py b/functional/common/test.py index e882dd6498..fa523d7d33 100644 --- a/functional/common/test.py +++ b/functional/common/test.py @@ -54,17 +54,13 @@ def openstack(cls, cmd, fail_ok=False): @classmethod def get_openstack_configuration_value(cls, configuration): - opts = cls.get_show_opts([configuration]) + opts = cls.get_opts([configuration]) return cls.openstack('configuration show ' + opts) @classmethod - def get_show_opts(cls, fields=[]): + def get_opts(cls, fields): return ' -f value ' + ' '.join(['-c ' + it for it in fields]) - @classmethod - def get_list_opts(cls, headers=[]): - return ' -f csv ' + ' '.join(['-c ' + it for it in headers]) - @classmethod def assertOutput(cls, expected, actual): if expected != actual: diff --git a/functional/tests/common/test_availability_zone.py b/functional/tests/common/test_availability_zone.py index 9296db23d7..da8aad7ddd 100644 --- a/functional/tests/common/test_availability_zone.py +++ b/functional/tests/common/test_availability_zone.py @@ -20,6 +20,6 @@ class AvailabilityZoneTests(test.TestCase): DEFAULT_AZ_NAME = 'nova' def test_availability_zone_list(self): - opts = self.get_list_opts(self.HEADERS) + opts = self.get_opts(self.HEADERS) raw_output = self.openstack('availability zone list' + opts) self.assertIn(self.DEFAULT_AZ_NAME, raw_output) diff --git a/functional/tests/common/test_quota.py b/functional/tests/common/test_quota.py index 0bc93db283..0316de25a7 100644 --- a/functional/tests/common/test_quota.py +++ b/functional/tests/common/test_quota.py @@ -27,7 +27,7 @@ def setUpClass(cls): def test_quota_set(self): self.openstack('quota set --instances 11 --volumes 11 --networks 11 ' + self.PROJECT_NAME) - opts = self.get_show_opts(self.EXPECTED_FIELDS) + opts = self.get_opts(self.EXPECTED_FIELDS) raw_output = self.openstack('quota show ' + self.PROJECT_NAME + opts) self.assertEqual("11\n11\n11\n", raw_output) diff --git a/functional/tests/compute/v2/test_agent.py b/functional/tests/compute/v2/test_agent.py index 2d7ea21615..ad2e8253d0 100644 --- a/functional/tests/compute/v2/test_agent.py +++ b/functional/tests/compute/v2/test_agent.py @@ -31,7 +31,7 @@ class ComputeAgentTests(test.TestCase): @classmethod def setUpClass(cls): - opts = cls.get_show_opts(cls.HEADERS) + opts = cls.get_opts(cls.HEADERS) raw_output = cls.openstack('compute agent create ' + cls.OS + ' ' + cls.ARCH + ' ' + cls.VER + ' ' + cls.URL + ' ' + diff --git a/functional/tests/compute/v2/test_aggregate.py b/functional/tests/compute/v2/test_aggregate.py index 5bac585841..adb14e52ad 100644 --- a/functional/tests/compute/v2/test_aggregate.py +++ b/functional/tests/compute/v2/test_aggregate.py @@ -24,7 +24,7 @@ class AggregateTests(test.TestCase): @classmethod def setUpClass(cls): - opts = cls.get_show_opts(cls.FIELDS) + opts = cls.get_opts(cls.FIELDS) # Use the default 'nova' availability zone for the aggregate. raw_output = cls.openstack( 'aggregate create --zone nova ' + cls.NAME + opts @@ -38,17 +38,17 @@ def tearDownClass(cls): cls.assertOutput('', raw_output) def test_aggregate_list(self): - opts = self.get_list_opts(self.HEADERS) + opts = self.get_opts(self.HEADERS) raw_output = self.openstack('aggregate list' + opts) self.assertIn(self.NAME, raw_output) def test_aggregate_show(self): - opts = self.get_show_opts(self.FIELDS) + opts = self.get_opts(self.FIELDS) raw_output = self.openstack('aggregate show ' + self.NAME + opts) self.assertEqual(self.NAME + "\n", raw_output) def test_aggregate_properties(self): - opts = self.get_show_opts(['properties']) + opts = self.get_opts(['properties']) raw_output = self.openstack( 'aggregate set --property a=b --property c=d ' + self.NAME diff --git a/functional/tests/compute/v2/test_flavor.py b/functional/tests/compute/v2/test_flavor.py index 6edb1fdfec..ef0d2fe366 100644 --- a/functional/tests/compute/v2/test_flavor.py +++ b/functional/tests/compute/v2/test_flavor.py @@ -24,7 +24,7 @@ class FlavorTests(test.TestCase): @classmethod def setUpClass(cls): - opts = cls.get_show_opts(cls.FIELDS) + opts = cls.get_opts(cls.FIELDS) raw_output = cls.openstack( 'flavor create --property a=b --property c=d ' + cls.NAME + opts) expected = cls.NAME + '\n' @@ -36,18 +36,18 @@ def tearDownClass(cls): cls.assertOutput('', raw_output) def test_flavor_list(self): - opts = self.get_list_opts(self.HEADERS) + opts = self.get_opts(self.HEADERS) raw_output = self.openstack('flavor list' + opts) self.assertIn("small", raw_output) self.assertIn(self.NAME, raw_output) def test_flavor_show(self): - opts = self.get_show_opts(self.FIELDS) + opts = self.get_opts(self.FIELDS) raw_output = self.openstack('flavor show ' + self.NAME + opts) self.assertEqual(self.NAME + "\n", raw_output) def test_flavor_properties(self): - opts = self.get_show_opts(['properties']) + opts = self.get_opts(['properties']) # check the properties we added in create command. raw_output = self.openstack('flavor show ' + self.NAME + opts) self.assertEqual("a='b', c='d'\n", raw_output) diff --git a/functional/tests/compute/v2/test_server.py b/functional/tests/compute/v2/test_server.py index d08b003fbc..511ac37293 100644 --- a/functional/tests/compute/v2/test_server.py +++ b/functional/tests/compute/v2/test_server.py @@ -58,7 +58,7 @@ def get_network(cls): def server_create(self, name=None): """Create server. Add cleanup.""" name = name or data_utils.rand_uuid() - opts = self.get_show_opts(self.FIELDS) + opts = self.get_opts(self.FIELDS) flavor = self.get_flavor() image = self.get_image() network = self.get_network() @@ -72,7 +72,7 @@ def server_create(self, name=None): def server_list(self, params=[]): """List servers.""" - opts = self.get_list_opts(params) + opts = self.get_opts(params) return self.openstack('server list' + opts) def server_delete(self, name): @@ -114,7 +114,7 @@ def test_server_list(self): 2) List servers 3) Check output """ - opts = self.get_list_opts(self.HEADERS) + opts = self.get_opts(self.HEADERS) raw_output = self.openstack('server list' + opts) self.assertIn(self.NAME, raw_output) @@ -126,7 +126,7 @@ def test_server_show(self): 2) Show server 3) Check output """ - opts = self.get_show_opts(self.FIELDS) + opts = self.get_opts(self.FIELDS) raw_output = self.openstack('server show ' + self.NAME + opts) self.assertEqual(self.NAME + "\n", raw_output) @@ -144,13 +144,13 @@ def test_server_metadata(self): # metadata raw_output = self.openstack( 'server set --property a=b --property c=d ' + self.NAME) - opts = self.get_show_opts(["name", "properties"]) + opts = self.get_opts(["name", "properties"]) raw_output = self.openstack('server show ' + self.NAME + opts) self.assertEqual(self.NAME + "\na='b', c='d'\n", raw_output) raw_output = self.openstack( 'server unset --property a ' + self.NAME) - opts = self.get_show_opts(["name", "properties"]) + opts = self.get_opts(["name", "properties"]) raw_output = self.openstack('server show ' + self.NAME + opts) self.assertEqual(self.NAME + "\nc='d'\n", raw_output) @@ -224,7 +224,7 @@ def test_server_rescue_unrescue(self): """ self.wait_for_status("ACTIVE") # rescue - opts = self.get_show_opts(["adminPass"]) + opts = self.get_opts(["adminPass"]) raw_output = self.openstack('server rescue ' + self.NAME + opts) self.assertNotEqual("", raw_output) self.wait_for_status("RESCUE") @@ -248,7 +248,7 @@ def test_server_attach_detach_floating_ip(self): """ self.wait_for_status("ACTIVE") # attach ip - opts = self.get_show_opts(["id", "floating_ip_address"]) + opts = self.get_opts(["id", "floating_ip_address"]) raw_output = self.openstack('ip floating create ' + self.IP_POOL + opts) @@ -288,7 +288,7 @@ def wait_for_status(self, expected_status='ACTIVE', wait=900, interval=30): # TODO(thowe): Add a server wait command to osc failures = ['ERROR'] total_sleep = 0 - opts = self.get_show_opts(['status']) + opts = self.get_opts(['status']) while total_sleep < wait: status = self.openstack('server show ' + self.NAME + opts) status = status.rstrip() diff --git a/functional/tests/compute/v2/test_server_group.py b/functional/tests/compute/v2/test_server_group.py index c073b88e99..b91260523d 100644 --- a/functional/tests/compute/v2/test_server_group.py +++ b/functional/tests/compute/v2/test_server_group.py @@ -24,7 +24,7 @@ class ServerGroupTests(test.TestCase): @classmethod def setUpClass(cls): - opts = cls.get_show_opts(cls.FIELDS) + opts = cls.get_opts(cls.FIELDS) raw_output = cls.openstack('server group create --policy affinity ' + cls.NAME + opts) expected = cls.NAME + '\n' @@ -36,11 +36,11 @@ def tearDownClass(cls): cls.assertOutput('', raw_output) def test_server_group_list(self): - opts = self.get_list_opts(self.HEADERS) + opts = self.get_opts(self.HEADERS) raw_output = self.openstack('server group list' + opts) self.assertIn(self.NAME, raw_output) def test_server_group_show(self): - opts = self.get_show_opts(self.FIELDS) + opts = self.get_opts(self.FIELDS) raw_output = self.openstack('server group show ' + self.NAME + opts) self.assertEqual(self.NAME + "\n", raw_output) diff --git a/functional/tests/image/v1/test_image.py b/functional/tests/image/v1/test_image.py index fe61f83036..c64c0d986b 100644 --- a/functional/tests/image/v1/test_image.py +++ b/functional/tests/image/v1/test_image.py @@ -27,7 +27,7 @@ class ImageTests(test.TestCase): @classmethod def setUpClass(cls): os.environ['OS_IMAGE_API_VERSION'] = '1' - opts = cls.get_show_opts(cls.FIELDS) + opts = cls.get_opts(cls.FIELDS) raw_output = cls.openstack('image create ' + cls.NAME + opts) expected = cls.NAME + '\n' cls.assertOutput(expected, raw_output) @@ -43,17 +43,17 @@ def tearDownClass(cls): cls.assertOutput('', raw_output) def test_image_list(self): - opts = self.get_list_opts(self.HEADERS) + opts = self.get_opts(self.HEADERS) raw_output = self.openstack('image list' + opts) self.assertIn(self.NAME, raw_output) def test_image_show(self): - opts = self.get_show_opts(self.FIELDS) + opts = self.get_opts(self.FIELDS) raw_output = self.openstack('image show ' + self.NAME + opts) self.assertEqual(self.NAME + "\n", raw_output) def test_image_set(self): - opts = self.get_show_opts([ + opts = self.get_opts([ "disk_format", "is_public", "min_disk", "min_ram", "name"]) self.openstack('image set --min-disk 4 --min-ram 5 ' + '--disk-format qcow2 --public ' + self.NAME) @@ -61,7 +61,7 @@ def test_image_set(self): self.assertEqual("qcow2\nTrue\n4\n5\n" + self.NAME + '\n', raw_output) def test_image_metadata(self): - opts = self.get_show_opts(["name", "properties"]) + opts = self.get_opts(["name", "properties"]) self.openstack('image set --property a=b --property c=d ' + self.NAME) raw_output = self.openstack('image show ' + self.NAME + opts) self.assertEqual(self.NAME + "\na='b', c='d'\n", raw_output) diff --git a/functional/tests/image/v2/test_image.py b/functional/tests/image/v2/test_image.py index 2e2b59bbbd..c2524f8ad0 100644 --- a/functional/tests/image/v2/test_image.py +++ b/functional/tests/image/v2/test_image.py @@ -29,7 +29,7 @@ class ImageTests(test.TestCase): @classmethod def setUpClass(cls): os.environ['OS_IMAGE_API_VERSION'] = '2' - opts = cls.get_show_opts(cls.FIELDS) + opts = cls.get_opts(cls.FIELDS) raw_output = cls.openstack('image create ' + cls.NAME + opts) expected = cls.NAME + '\n' cls.assertOutput(expected, raw_output) @@ -45,17 +45,17 @@ def tearDownClass(cls): cls.assertOutput('', raw_output) def test_image_list(self): - opts = self.get_list_opts(self.HEADERS) + opts = self.get_opts(self.HEADERS) raw_output = self.openstack('image list' + opts) self.assertIn(self.NAME, raw_output) def test_image_show(self): - opts = self.get_show_opts(self.FIELDS) + opts = self.get_opts(self.FIELDS) raw_output = self.openstack('image show ' + self.NAME + opts) self.assertEqual(self.NAME + "\n", raw_output) def test_image_set(self): - opts = self.get_show_opts([ + opts = self.get_opts([ "disk_format", "visibility", "min_disk", "min_ram", "name"]) self.openstack('image set --min-disk 4 --min-ram 5 ' + '--public ' + self.NAME) @@ -63,14 +63,14 @@ def test_image_set(self): self.assertEqual("raw\n4\n5\n" + self.NAME + '\npublic\n', raw_output) def test_image_metadata(self): - opts = self.get_show_opts(["name", "properties"]) + opts = self.get_opts(["name", "properties"]) self.openstack('image set --property a=b --property c=d ' + self.NAME) raw_output = self.openstack('image show ' + self.NAME + opts) self.assertEqual(self.NAME + "\na='b', c='d'\n", raw_output) @testtools.skip("skip until bug 1596573 is resolved") def test_image_unset(self): - opts = self.get_show_opts(["name", "tags", "properties"]) + opts = self.get_opts(["name", "tags", "properties"]) self.openstack('image set --tag 01 ' + self.NAME) self.openstack('image unset --tag 01 ' + self.NAME) # test_image_metadata has set image properties "a" and "c" diff --git a/functional/tests/network/v2/test_address_scope.py b/functional/tests/network/v2/test_address_scope.py index 8e25e46aa9..3beab2338f 100644 --- a/functional/tests/network/v2/test_address_scope.py +++ b/functional/tests/network/v2/test_address_scope.py @@ -23,7 +23,7 @@ class AddressScopeTests(test.TestCase): @classmethod def setUpClass(cls): - opts = cls.get_show_opts(cls.FIELDS) + opts = cls.get_opts(cls.FIELDS) raw_output = cls.openstack('address scope create ' + cls.NAME + opts) cls.assertOutput(cls.NAME + "\n", raw_output) @@ -33,17 +33,17 @@ def tearDownClass(cls): cls.assertOutput('', raw_output) def test_address_scope_list(self): - opts = self.get_list_opts(self.HEADERS) + opts = self.get_opts(self.HEADERS) raw_output = self.openstack('address scope list' + opts) self.assertIn(self.NAME, raw_output) def test_address_scope_show(self): - opts = self.get_show_opts(self.FIELDS) + opts = self.get_opts(self.FIELDS) raw_output = self.openstack('address scope show ' + self.NAME + opts) self.assertEqual(self.NAME + "\n", raw_output) def test_address_scope_set(self): self.openstack('address scope set --share ' + self.NAME) - opts = self.get_show_opts(['shared']) + opts = self.get_opts(['shared']) raw_output = self.openstack('address scope show ' + self.NAME + opts) self.assertEqual("True\n", raw_output) diff --git a/functional/tests/network/v2/test_floating_ip.py b/functional/tests/network/v2/test_floating_ip.py index f9ecd92823..3b314b7050 100644 --- a/functional/tests/network/v2/test_floating_ip.py +++ b/functional/tests/network/v2/test_floating_ip.py @@ -33,7 +33,7 @@ def setUpClass(cls): ' --subnet-range 10.10.10.0/24 ' + cls.SUBNET_NAME ) - opts = cls.get_show_opts(cls.FIELDS) + opts = cls.get_opts(cls.FIELDS) raw_output = cls.openstack( 'ip floating create ' + cls.NETWORK_NAME + opts) cls.ID = raw_output.strip('\n') @@ -48,11 +48,11 @@ def tearDownClass(cls): cls.assertOutput('', raw_output) def test_floating_ip_list(self): - opts = self.get_list_opts(self.HEADERS) + opts = self.get_opts(self.HEADERS) raw_output = self.openstack('ip floating list' + opts) self.assertIn(self.ID, raw_output) def test_floating_ip_show(self): - opts = self.get_show_opts(self.FIELDS) + opts = self.get_opts(self.FIELDS) raw_output = self.openstack('ip floating show ' + self.ID + opts) self.assertEqual(self.ID + "\n", raw_output) diff --git a/functional/tests/network/v2/test_ip_availability.py b/functional/tests/network/v2/test_ip_availability.py index f1302d5f6f..e83010fded 100644 --- a/functional/tests/network/v2/test_ip_availability.py +++ b/functional/tests/network/v2/test_ip_availability.py @@ -25,7 +25,7 @@ class IPAvailabilityTests(test.TestCase): def setUpClass(cls): # Create a network for the subnet. cls.openstack('network create ' + cls.NETWORK_NAME) - opts = cls.get_show_opts(['name']) + opts = cls.get_opts(['name']) raw_output = cls.openstack( 'subnet create --network ' + cls.NETWORK_NAME + ' --subnet-range 10.10.10.0/24 ' + @@ -47,7 +47,7 @@ def test_ip_availability_list(self): self.assertIn(self.NETWORK_NAME, raw_output) def test_ip_availability_show(self): - opts = self.get_show_opts(self.FIELDS) + opts = self.get_opts(self.FIELDS) raw_output = self.openstack( 'ip availability show ' + self.NETWORK_NAME + opts) self.assertEqual(self.NETWORK_NAME + "\n", raw_output) diff --git a/functional/tests/network/v2/test_network.py b/functional/tests/network/v2/test_network.py index 5cc461b96c..f5c92faadc 100644 --- a/functional/tests/network/v2/test_network.py +++ b/functional/tests/network/v2/test_network.py @@ -23,7 +23,7 @@ class NetworkTests(test.TestCase): @classmethod def setUpClass(cls): - opts = cls.get_show_opts(cls.FIELDS) + opts = cls.get_opts(cls.FIELDS) raw_output = cls.openstack('network create ' + cls.NAME + opts) expected = cls.NAME + '\n' cls.assertOutput(expected, raw_output) @@ -34,17 +34,17 @@ def tearDownClass(cls): cls.assertOutput('', raw_output) def test_network_list(self): - opts = self.get_list_opts(self.HEADERS) + opts = self.get_opts(self.HEADERS) raw_output = self.openstack('network list' + opts) self.assertIn(self.NAME, raw_output) def test_network_set(self): raw_output = self.openstack('network set --disable ' + self.NAME) - opts = self.get_show_opts(['name', 'admin_state_up']) + opts = self.get_opts(['name', 'admin_state_up']) raw_output = self.openstack('network show ' + self.NAME + opts) self.assertEqual("DOWN\n" + self.NAME + "\n", raw_output) def test_network_show(self): - opts = self.get_show_opts(self.FIELDS) + opts = self.get_opts(self.FIELDS) raw_output = self.openstack('network show ' + self.NAME + opts) self.assertEqual(self.NAME + "\n", raw_output) diff --git a/functional/tests/network/v2/test_network_segment.py b/functional/tests/network/v2/test_network_segment.py index b5b5dcd9fe..4609973cf4 100644 --- a/functional/tests/network/v2/test_network_segment.py +++ b/functional/tests/network/v2/test_network_segment.py @@ -28,12 +28,12 @@ class NetworkSegmentTests(test.TestCase): @classmethod def setUpClass(cls): # Create a network for the segment. - opts = cls.get_show_opts(['id']) + opts = cls.get_opts(['id']) raw_output = cls.openstack('network create ' + cls.NETWORK_NAME + opts) cls.NETWORK_ID = raw_output.strip('\n') # Get the segment for the network. - opts = cls.get_show_opts(['ID', 'Network']) + opts = cls.get_opts(['ID', 'Network']) raw_output = cls.openstack('--os-beta-command ' 'network segment list ' ' --network ' + cls.NETWORK_NAME + @@ -47,13 +47,13 @@ def tearDownClass(cls): cls.assertOutput('', raw_output) def test_network_segment_list(self): - opts = self.get_list_opts(['ID']) + opts = self.get_opts(['ID']) raw_output = self.openstack('--os-beta-command ' 'network segment list' + opts) self.assertIn(self.NETWORK_SEGMENT_ID, raw_output) def test_network_segment_show(self): - opts = self.get_show_opts(['network_id']) + opts = self.get_opts(['network_id']) raw_output = self.openstack('--os-beta-command ' 'network segment show ' + self.NETWORK_SEGMENT_ID + opts) diff --git a/functional/tests/network/v2/test_port.py b/functional/tests/network/v2/test_port.py index 5b358a387a..a68019c45b 100644 --- a/functional/tests/network/v2/test_port.py +++ b/functional/tests/network/v2/test_port.py @@ -26,7 +26,7 @@ class PortTests(test.TestCase): def setUpClass(cls): # Create a network for the subnet. cls.openstack('network create ' + cls.NETWORK_NAME) - opts = cls.get_show_opts(cls.FIELDS) + opts = cls.get_opts(cls.FIELDS) raw_output = cls.openstack( 'port create --network ' + cls.NETWORK_NAME + ' ' + cls.NAME + opts @@ -42,17 +42,17 @@ def tearDownClass(cls): cls.assertOutput('', raw_output) def test_port_list(self): - opts = self.get_list_opts(self.HEADERS) + opts = self.get_opts(self.HEADERS) raw_output = self.openstack('port list' + opts) self.assertIn(self.NAME, raw_output) def test_port_set(self): self.openstack('port set --disable ' + self.NAME) - opts = self.get_show_opts(['name', 'admin_state_up']) + opts = self.get_opts(['name', 'admin_state_up']) raw_output = self.openstack('port show ' + self.NAME + opts) self.assertEqual("DOWN\n" + self.NAME + "\n", raw_output) def test_port_show(self): - opts = self.get_show_opts(self.FIELDS) + opts = self.get_opts(self.FIELDS) raw_output = self.openstack('port show ' + self.NAME + opts) self.assertEqual(self.NAME + "\n", raw_output) diff --git a/functional/tests/network/v2/test_router.py b/functional/tests/network/v2/test_router.py index ceb76255ed..e536c64ec7 100644 --- a/functional/tests/network/v2/test_router.py +++ b/functional/tests/network/v2/test_router.py @@ -23,7 +23,7 @@ class RouterTests(test.TestCase): @classmethod def setUpClass(cls): - opts = cls.get_show_opts(cls.FIELDS) + opts = cls.get_opts(cls.FIELDS) raw_output = cls.openstack('router create ' + cls.NAME + opts) expected = cls.NAME + '\n' cls.assertOutput(expected, raw_output) @@ -34,17 +34,17 @@ def tearDownClass(cls): cls.assertOutput('', raw_output) def test_router_list(self): - opts = self.get_list_opts(self.HEADERS) + opts = self.get_opts(self.HEADERS) raw_output = self.openstack('router list' + opts) self.assertIn(self.NAME, raw_output) def test_router_set(self): self.openstack('router set --disable ' + self.NAME) - opts = self.get_show_opts(['name', 'admin_state_up']) + opts = self.get_opts(['name', 'admin_state_up']) raw_output = self.openstack('router show ' + self.NAME + opts) self.assertEqual("DOWN\n" + self.NAME + "\n", raw_output) def test_router_show(self): - opts = self.get_show_opts(self.FIELDS) + opts = self.get_opts(self.FIELDS) raw_output = self.openstack('router show ' + self.NAME + opts) self.assertEqual(self.NAME + "\n", raw_output) diff --git a/functional/tests/network/v2/test_security_group.py b/functional/tests/network/v2/test_security_group.py index 4fc4d12df3..2a3b92a0d3 100644 --- a/functional/tests/network/v2/test_security_group.py +++ b/functional/tests/network/v2/test_security_group.py @@ -24,7 +24,7 @@ class SecurityGroupTests(test.TestCase): @classmethod def setUpClass(cls): - opts = cls.get_show_opts(cls.FIELDS) + opts = cls.get_opts(cls.FIELDS) raw_output = cls.openstack('security group create ' + cls.NAME + opts) expected = cls.NAME + '\n' cls.assertOutput(expected, raw_output) @@ -40,7 +40,7 @@ def tearDownClass(cls): cls.assertOutput('', raw_output) def test_security_group_list(self): - opts = self.get_list_opts(self.HEADERS) + opts = self.get_opts(self.HEADERS) raw_output = self.openstack('security group list' + opts) self.assertIn(self.NAME, raw_output) @@ -50,11 +50,11 @@ def test_security_group_set(self): ) self.assertEqual('', raw_output) - opts = self.get_show_opts(['description']) + opts = self.get_opts(['description']) raw_output = self.openstack('security group show ' + self.NAME + opts) self.assertEqual("NSA\n", raw_output) def test_security_group_show(self): - opts = self.get_show_opts(self.FIELDS) + opts = self.get_opts(self.FIELDS) raw_output = self.openstack('security group show ' + self.NAME + opts) self.assertEqual(self.NAME + "\n", raw_output) diff --git a/functional/tests/network/v2/test_security_group_rule.py b/functional/tests/network/v2/test_security_group_rule.py index 64e1fcdf6a..248d20b1fb 100644 --- a/functional/tests/network/v2/test_security_group_rule.py +++ b/functional/tests/network/v2/test_security_group_rule.py @@ -26,7 +26,7 @@ class SecurityGroupRuleTests(test.TestCase): @classmethod def setUpClass(cls): # Create the security group to hold the rule. - opts = cls.get_show_opts(cls.NAME_FIELD) + opts = cls.get_opts(cls.NAME_FIELD) raw_output = cls.openstack('security group create ' + cls.SECURITY_GROUP_NAME + opts) @@ -34,7 +34,7 @@ def setUpClass(cls): cls.assertOutput(expected, raw_output) # Create the security group rule. - opts = cls.get_show_opts(cls.ID_FIELD) + opts = cls.get_opts(cls.ID_FIELD) raw_output = cls.openstack('security group rule create ' + cls.SECURITY_GROUP_NAME + ' --protocol tcp --dst-port 80:80' + @@ -53,14 +53,14 @@ def tearDownClass(cls): cls.assertOutput('', raw_output) def test_security_group_rule_list(self): - opts = self.get_list_opts(self.ID_HEADER) + opts = self.get_opts(self.ID_HEADER) raw_output = self.openstack('security group rule list ' + self.SECURITY_GROUP_NAME + opts) self.assertIn(self.SECURITY_GROUP_RULE_ID, raw_output) def test_security_group_rule_show(self): - opts = self.get_show_opts(self.ID_FIELD) + opts = self.get_opts(self.ID_FIELD) raw_output = self.openstack('security group rule show ' + self.SECURITY_GROUP_RULE_ID + opts) diff --git a/functional/tests/network/v2/test_subnet.py b/functional/tests/network/v2/test_subnet.py index 7697e0f4e6..11ae6da19a 100644 --- a/functional/tests/network/v2/test_subnet.py +++ b/functional/tests/network/v2/test_subnet.py @@ -26,7 +26,7 @@ class SubnetTests(test.TestCase): def setUpClass(cls): # Create a network for the subnet. cls.openstack('network create ' + cls.NETWORK_NAME) - opts = cls.get_show_opts(cls.FIELDS) + opts = cls.get_opts(cls.FIELDS) raw_output = cls.openstack( 'subnet create --network ' + cls.NETWORK_NAME + ' --subnet-range 10.10.10.0/24 ' + @@ -43,17 +43,17 @@ def tearDownClass(cls): cls.assertOutput('', raw_output) def test_subnet_list(self): - opts = self.get_list_opts(self.HEADERS) + opts = self.get_opts(self.HEADERS) raw_output = self.openstack('subnet list' + opts) self.assertIn(self.NAME, raw_output) def test_subnet_set(self): self.openstack('subnet set --no-dhcp ' + self.NAME) - opts = self.get_show_opts(['name', 'enable_dhcp']) + opts = self.get_opts(['name', 'enable_dhcp']) raw_output = self.openstack('subnet show ' + self.NAME + opts) self.assertEqual("False\n" + self.NAME + "\n", raw_output) def test_subnet_show(self): - opts = self.get_show_opts(self.FIELDS) + opts = self.get_opts(self.FIELDS) raw_output = self.openstack('subnet show ' + self.NAME + opts) self.assertEqual(self.NAME + "\n", raw_output) diff --git a/functional/tests/network/v2/test_subnet_pool.py b/functional/tests/network/v2/test_subnet_pool.py index 1515487add..054188f714 100644 --- a/functional/tests/network/v2/test_subnet_pool.py +++ b/functional/tests/network/v2/test_subnet_pool.py @@ -25,7 +25,7 @@ class SubnetPoolTests(test.TestCase): @classmethod def setUpClass(cls): - opts = cls.get_show_opts(cls.FIELDS) + opts = cls.get_opts(cls.FIELDS) raw_output = cls.openstack('subnet pool create --pool-prefix ' + cls.CREATE_POOL_PREFIX + ' ' + cls.NAME + opts) @@ -37,19 +37,19 @@ def tearDownClass(cls): cls.assertOutput('', raw_output) def test_subnet_list(self): - opts = self.get_list_opts(self.HEADERS) + opts = self.get_opts(self.HEADERS) raw_output = self.openstack('subnet pool list' + opts) self.assertIn(self.NAME, raw_output) def test_subnet_set(self): self.openstack('subnet pool set --pool-prefix ' + self.SET_POOL_PREFIX + ' ' + self.NAME) - opts = self.get_show_opts(['prefixes', 'name']) + opts = self.get_opts(['prefixes', 'name']) raw_output = self.openstack('subnet pool show ' + self.NAME + opts) self.assertEqual(self.NAME + '\n' + self.SET_POOL_PREFIX + '\n', raw_output) def test_subnet_show(self): - opts = self.get_show_opts(self.FIELDS) + opts = self.get_opts(self.FIELDS) raw_output = self.openstack('subnet pool show ' + self.NAME + opts) self.assertEqual(self.NAME + '\n', raw_output) diff --git a/functional/tests/object/v1/test_container.py b/functional/tests/object/v1/test_container.py index 8721a4a79d..4f9e843b5c 100644 --- a/functional/tests/object/v1/test_container.py +++ b/functional/tests/object/v1/test_container.py @@ -21,10 +21,9 @@ class ContainerTests(test.TestCase): @classmethod def setUpClass(cls): - opts = cls.get_list_opts(['container']) + opts = cls.get_opts(['container']) raw_output = cls.openstack('container create ' + cls.NAME + opts) - expected = '"container"\n"' + cls.NAME + '"\n' - cls.assertOutput(expected, raw_output) + cls.assertOutput(cls.NAME + '\n', raw_output) @classmethod def tearDownClass(cls): @@ -32,11 +31,11 @@ def tearDownClass(cls): cls.assertOutput('', raw_output) def test_container_list(self): - opts = self.get_list_opts(['Name']) + opts = self.get_opts(['Name']) raw_output = self.openstack('container list' + opts) self.assertIn(self.NAME, raw_output) def test_container_show(self): - opts = self.get_show_opts(['container']) + opts = self.get_opts(['container']) raw_output = self.openstack('container show ' + self.NAME + opts) self.assertEqual(self.NAME + "\n", raw_output) diff --git a/functional/tests/volume/v1/test_qos.py b/functional/tests/volume/v1/test_qos.py index 9324830c8a..5aed4bd0b5 100644 --- a/functional/tests/volume/v1/test_qos.py +++ b/functional/tests/volume/v1/test_qos.py @@ -26,7 +26,7 @@ class QosTests(common.BaseVolumeTests): @classmethod def setUpClass(cls): super(QosTests, cls).setUpClass() - opts = cls.get_show_opts(cls.FIELDS) + opts = cls.get_opts(cls.FIELDS) raw_output = cls.openstack('volume qos create ' + cls.NAME + opts) cls.ID, name, rol = raw_output.split('\n') cls.assertOutput(cls.NAME, name) @@ -37,12 +37,12 @@ def tearDownClass(cls): cls.assertOutput('', raw_output) def test_volume_qos_list(self): - opts = self.get_list_opts(self.HEADERS) + opts = self.get_opts(self.HEADERS) raw_output = self.openstack('volume qos list' + opts) self.assertIn(self.NAME, raw_output) def test_volume_qos_show(self): - opts = self.get_show_opts(self.FIELDS) + opts = self.get_opts(self.FIELDS) raw_output = self.openstack('volume qos show ' + self.ID + opts) self.assertEqual(self.ID + "\n" + self.NAME + "\n", raw_output) @@ -50,6 +50,6 @@ def test_volume_qos_metadata(self): raw_output = self.openstack( 'volume qos set --property a=b --property c=d ' + self.ID) self.assertEqual("", raw_output) - opts = self.get_show_opts(['name', 'specs']) + opts = self.get_opts(['name', 'specs']) raw_output = self.openstack('volume qos show ' + self.ID + opts) self.assertEqual(self.NAME + "\na='b', c='d'\n", raw_output) diff --git a/functional/tests/volume/v1/test_volume.py b/functional/tests/volume/v1/test_volume.py index f574075d75..8275bf0a96 100644 --- a/functional/tests/volume/v1/test_volume.py +++ b/functional/tests/volume/v1/test_volume.py @@ -26,7 +26,7 @@ class VolumeTests(common.BaseVolumeTests): @classmethod def setUpClass(cls): super(VolumeTests, cls).setUpClass() - opts = cls.get_show_opts(cls.FIELDS) + opts = cls.get_opts(cls.FIELDS) raw_output = cls.openstack('volume create --size 1 ' + cls.NAME + opts) expected = cls.NAME + '\n' cls.assertOutput(expected, raw_output) @@ -42,12 +42,12 @@ def tearDownClass(cls): cls.assertOutput('', raw_output) def test_volume_list(self): - opts = self.get_list_opts(self.HEADERS) + opts = self.get_opts(self.HEADERS) raw_output = self.openstack('volume list' + opts) self.assertIn(self.NAME, raw_output) def test_volume_show(self): - opts = self.get_show_opts(self.FIELDS) + opts = self.get_opts(self.FIELDS) raw_output = self.openstack('volume show ' + self.NAME + opts) self.assertEqual(self.NAME + "\n", raw_output) @@ -55,7 +55,7 @@ def test_volume_properties(self): raw_output = self.openstack( 'volume set --property a=b --property c=d ' + self.NAME) self.assertEqual("", raw_output) - opts = self.get_show_opts(["properties"]) + opts = self.get_opts(["properties"]) raw_output = self.openstack('volume show ' + self.NAME + opts) self.assertEqual("a='b', c='d'\n", raw_output) @@ -66,12 +66,12 @@ def test_volume_properties(self): def test_volume_set(self): self.openstack('volume set --description RAMAC ' + self.NAME) - opts = self.get_show_opts(["display_description", "display_name"]) + opts = self.get_opts(["display_description", "display_name"]) raw_output = self.openstack('volume show ' + self.NAME + opts) self.assertEqual("RAMAC\n" + self.NAME + "\n", raw_output) def test_volume_set_size(self): self.openstack('volume set --size 2 ' + self.NAME) - opts = self.get_show_opts(["display_name", "size"]) + opts = self.get_opts(["display_name", "size"]) raw_output = self.openstack('volume show ' + self.NAME + opts) self.assertEqual(self.NAME + "\n2\n", raw_output) diff --git a/functional/tests/volume/v1/test_volume_type.py b/functional/tests/volume/v1/test_volume_type.py index 5f1f957e8c..ed44f3c3cc 100644 --- a/functional/tests/volume/v1/test_volume_type.py +++ b/functional/tests/volume/v1/test_volume_type.py @@ -26,7 +26,7 @@ class VolumeTypeTests(common.BaseVolumeTests): @classmethod def setUpClass(cls): super(VolumeTypeTests, cls).setUpClass() - opts = cls.get_show_opts(cls.FIELDS) + opts = cls.get_opts(cls.FIELDS) raw_output = cls.openstack('volume type create ' + cls.NAME + opts) expected = cls.NAME + '\n' cls.assertOutput(expected, raw_output) @@ -37,12 +37,12 @@ def tearDownClass(cls): cls.assertOutput('', raw_output) def test_volume_type_list(self): - opts = self.get_list_opts(self.HEADERS) + opts = self.get_opts(self.HEADERS) raw_output = self.openstack('volume type list' + opts) self.assertIn(self.NAME, raw_output) def test_volume_type_show(self): - opts = self.get_show_opts(self.FIELDS) + opts = self.get_opts(self.FIELDS) raw_output = self.openstack('volume type show ' + self.NAME + opts) self.assertEqual(self.NAME + "\n", raw_output) @@ -51,7 +51,7 @@ def test_volume_type_set_unset_properties(self): 'volume type set --property a=b --property c=d ' + self.NAME) self.assertEqual("", raw_output) - opts = self.get_show_opts(["properties"]) + opts = self.get_opts(["properties"]) raw_output = self.openstack('volume type show ' + self.NAME + opts) self.assertEqual("a='b', c='d'\n", raw_output) diff --git a/functional/tests/volume/v2/test_qos.py b/functional/tests/volume/v2/test_qos.py index 64c3b6c108..5311b478d3 100644 --- a/functional/tests/volume/v2/test_qos.py +++ b/functional/tests/volume/v2/test_qos.py @@ -26,7 +26,7 @@ class QosTests(common.BaseVolumeTests): @classmethod def setUpClass(cls): super(QosTests, cls).setUpClass() - opts = cls.get_show_opts(cls.FIELDS) + opts = cls.get_opts(cls.FIELDS) raw_output = cls.openstack('volume qos create ' + cls.NAME + opts) cls.ID, name, rol = raw_output.split('\n') cls.assertOutput(cls.NAME, name) @@ -37,12 +37,12 @@ def tearDownClass(cls): cls.assertOutput('', raw_output) def test_volume_qos_list(self): - opts = self.get_list_opts(self.HEADERS) + opts = self.get_opts(self.HEADERS) raw_output = self.openstack('volume qos list' + opts) self.assertIn(self.NAME, raw_output) def test_volume_qos_show(self): - opts = self.get_show_opts(self.FIELDS) + opts = self.get_opts(self.FIELDS) raw_output = self.openstack('volume qos show ' + self.ID + opts) self.assertEqual(self.ID + "\n" + self.NAME + "\n", raw_output) @@ -50,13 +50,13 @@ def test_volume_qos_metadata(self): raw_output = self.openstack( 'volume qos set --property a=b --property c=d ' + self.ID) self.assertEqual("", raw_output) - opts = self.get_show_opts(['name', 'specs']) + opts = self.get_opts(['name', 'specs']) raw_output = self.openstack('volume qos show ' + self.ID + opts) self.assertEqual(self.NAME + "\na='b', c='d'\n", raw_output) raw_output = self.openstack( 'volume qos unset --property a ' + self.ID) self.assertEqual("", raw_output) - opts = self.get_show_opts(['name', 'specs']) + opts = self.get_opts(['name', 'specs']) raw_output = self.openstack('volume qos show ' + self.ID + opts) self.assertEqual(self.NAME + "\nc='d'\n", raw_output) diff --git a/functional/tests/volume/v2/test_snapshot.py b/functional/tests/volume/v2/test_snapshot.py index 40f075323f..4f910830a7 100644 --- a/functional/tests/volume/v2/test_snapshot.py +++ b/functional/tests/volume/v2/test_snapshot.py @@ -26,7 +26,7 @@ class SnapshotTests(common.BaseVolumeTests): @classmethod def wait_for_status(cls, command, status, tries): - opts = cls.get_show_opts(['status']) + opts = cls.get_opts(['status']) for attempt in range(tries): time.sleep(1) raw_output = cls.openstack(command + opts) @@ -39,7 +39,7 @@ def setUpClass(cls): super(SnapshotTests, cls).setUpClass() cls.openstack('volume create --size 1 ' + cls.VOLLY) cls.wait_for_status('volume show ' + cls.VOLLY, 'available\n', 3) - opts = cls.get_show_opts(['status']) + opts = cls.get_opts(['status']) raw_output = cls.openstack('snapshot create --name ' + cls.NAME + ' ' + cls.VOLLY + opts) cls.assertOutput('creating\n', raw_output) @@ -57,7 +57,7 @@ def tearDownClass(cls): cls.openstack('volume delete --force ' + cls.VOLLY, fail_ok=True) def test_snapshot_list(self): - opts = self.get_list_opts(self.HEADERS) + opts = self.get_opts(self.HEADERS) raw_output = self.openstack('snapshot list' + opts) self.assertIn(self.NAME, raw_output) @@ -65,7 +65,7 @@ def test_snapshot_properties(self): raw_output = self.openstack( 'snapshot set --property a=b --property c=d ' + self.NAME) self.assertEqual("", raw_output) - opts = self.get_show_opts(["properties"]) + opts = self.get_opts(["properties"]) raw_output = self.openstack('snapshot show ' + self.NAME + opts) self.assertEqual("a='b', c='d'\n", raw_output) @@ -78,6 +78,6 @@ def test_snapshot_set(self): raw_output = self.openstack( 'snapshot set --description backup ' + self.NAME) self.assertEqual("", raw_output) - opts = self.get_show_opts(["description", "name"]) + opts = self.get_opts(["description", "name"]) raw_output = self.openstack('snapshot show ' + self.NAME + opts) self.assertEqual("backup\n" + self.NAME + "\n", raw_output) diff --git a/functional/tests/volume/v2/test_volume.py b/functional/tests/volume/v2/test_volume.py index e0c1219cc4..019f0c6f52 100644 --- a/functional/tests/volume/v2/test_volume.py +++ b/functional/tests/volume/v2/test_volume.py @@ -29,7 +29,7 @@ class VolumeTests(common.BaseVolumeTests): @classmethod def setUpClass(cls): super(VolumeTests, cls).setUpClass() - opts = cls.get_show_opts(cls.FIELDS) + opts = cls.get_opts(cls.FIELDS) # Create test volume raw_output = cls.openstack('volume create --size 1 ' + cls.NAME + opts) @@ -48,12 +48,12 @@ def tearDownClass(cls): cls.assertOutput('', raw_output) def test_volume_list(self): - opts = self.get_list_opts(self.HEADERS) + opts = self.get_opts(self.HEADERS) raw_output = self.openstack('volume list' + opts) self.assertIn(self.NAME, raw_output) def test_volume_show(self): - opts = self.get_show_opts(self.FIELDS) + opts = self.get_opts(self.FIELDS) raw_output = self.openstack('volume show ' + self.NAME + opts) self.assertEqual(self.NAME + "\n", raw_output) @@ -61,7 +61,7 @@ def test_volume_properties(self): raw_output = self.openstack( 'volume set --property a=b --property c=d ' + self.NAME) self.assertEqual("", raw_output) - opts = self.get_show_opts(["properties"]) + opts = self.get_opts(["properties"]) raw_output = self.openstack('volume show ' + self.NAME + opts) self.assertEqual("a='b', c='d'\n", raw_output) @@ -74,18 +74,18 @@ def test_volume_set(self): discription = uuid.uuid4().hex self.openstack('volume set --description ' + discription + ' ' + self.NAME) - opts = self.get_show_opts(["description", "name"]) + opts = self.get_opts(["description", "name"]) raw_output = self.openstack('volume show ' + self.NAME + opts) self.assertEqual(discription + "\n" + self.NAME + "\n", raw_output) def test_volume_set_size(self): self.openstack('volume set --size 2 ' + self.NAME) - opts = self.get_show_opts(["name", "size"]) + opts = self.get_opts(["name", "size"]) raw_output = self.openstack('volume show ' + self.NAME + opts) self.assertEqual(self.NAME + "\n2\n", raw_output) def test_volume_snapshot(self): - opts = self.get_show_opts(self.FIELDS) + opts = self.get_opts(self.FIELDS) # Create snapshot from test volume raw_output = self.openstack('snapshot create ' + self.NAME + @@ -116,7 +116,7 @@ def wait_for(self, check_type, check_name, desired_status, wait=120, interval=5, failures=['ERROR']): status = "notset" total_sleep = 0 - opts = self.get_show_opts(['status']) + opts = self.get_opts(['status']) while total_sleep < wait: status = self.openstack(check_type + ' show ' + check_name + opts) status = status.rstrip() diff --git a/functional/tests/volume/v2/test_volume_type.py b/functional/tests/volume/v2/test_volume_type.py index 114e429802..02f790ec50 100644 --- a/functional/tests/volume/v2/test_volume_type.py +++ b/functional/tests/volume/v2/test_volume_type.py @@ -26,7 +26,7 @@ class VolumeTypeTests(common.BaseVolumeTests): @classmethod def setUpClass(cls): super(VolumeTypeTests, cls).setUpClass() - opts = cls.get_show_opts(cls.FIELDS) + opts = cls.get_opts(cls.FIELDS) raw_output = cls.openstack( 'volume type create --private ' + cls.NAME + opts) expected = cls.NAME + '\n' @@ -38,12 +38,12 @@ def tearDownClass(cls): cls.assertOutput('', raw_output) def test_volume_type_list(self): - opts = self.get_list_opts(self.HEADERS) + opts = self.get_opts(self.HEADERS) raw_output = self.openstack('volume type list' + opts) self.assertIn(self.NAME, raw_output) def test_volume_type_show(self): - opts = self.get_show_opts(self.FIELDS) + opts = self.get_opts(self.FIELDS) raw_output = self.openstack('volume type show ' + self.NAME + opts) self.assertEqual(self.NAME + "\n", raw_output) @@ -52,7 +52,7 @@ def test_volume_type_set_unset_properties(self): 'volume type set --property a=b --property c=d ' + self.NAME) self.assertEqual("", raw_output) - opts = self.get_show_opts(["properties"]) + opts = self.get_opts(["properties"]) raw_output = self.openstack('volume type show ' + self.NAME + opts) self.assertEqual("a='b', c='d'\n", raw_output) From 72a82b2794912041173290fc71507f3768a62c0b Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Tue, 5 Jul 2016 20:24:31 -0400 Subject: [PATCH 1074/3095] skip failing tests due to bug 1599333 is fixed latest osc-lib breaks some of the tests, skip them for now so we can continue to merge patches that are already approved. Change-Id: I433190e9f763bea1df3135612b281d925745f884 Related-Bug: 1599333 --- openstackclient/tests/identity/v3/test_identity_provider.py | 2 ++ openstackclient/tests/identity/v3/test_project.py | 5 +++++ openstackclient/tests/identity/v3/test_service_provider.py | 3 +++ openstackclient/tests/volume/test_find_resource.py | 5 +++++ 4 files changed, 15 insertions(+) diff --git a/openstackclient/tests/identity/v3/test_identity_provider.py b/openstackclient/tests/identity/v3/test_identity_provider.py index 3ff7981253..aaf5b06d4e 100644 --- a/openstackclient/tests/identity/v3/test_identity_provider.py +++ b/openstackclient/tests/identity/v3/test_identity_provider.py @@ -15,6 +15,7 @@ import copy import mock +import testtools from openstackclient.identity.v3 import identity_provider from openstackclient.tests import fakes @@ -603,6 +604,7 @@ def setUp(self): # Get the command object to test self.cmd = identity_provider.ShowIdentityProvider(self.app, None) + @testtools.skip("skip until bug 1599333 is fixed") def test_identity_provider_show(self): arglist = [ identity_fakes.idp_id, diff --git a/openstackclient/tests/identity/v3/test_project.py b/openstackclient/tests/identity/v3/test_project.py index 93bf18afbc..0ec8f1e56b 100644 --- a/openstackclient/tests/identity/v3/test_project.py +++ b/openstackclient/tests/identity/v3/test_project.py @@ -17,6 +17,7 @@ import mock from osc_lib import exceptions +import testtools from openstackclient.identity.v3 import project from openstackclient.tests import fakes @@ -748,6 +749,7 @@ def setUp(self): # Get the command object to test self.cmd = project.ShowProject(self.app, None) + @testtools.skip("skip until bug 1599333 is fixed") def test_project_show(self): arglist = [ @@ -789,6 +791,7 @@ def test_project_show(self): ) self.assertEqual(datalist, data) + @testtools.skip("skip until bug 1599333 is fixed") def test_project_show_parents(self): project = copy.deepcopy(identity_fakes.PROJECT_WITH_GRANDPARENT) project['parents'] = identity_fakes.grandparents @@ -846,6 +849,7 @@ def test_project_show_parents(self): ) self.assertEqual(data, datalist) + @testtools.skip("skip until bug 1599333 is fixed") def test_project_show_subtree(self): project = copy.deepcopy(identity_fakes.PROJECT_WITH_PARENT) project['subtree'] = identity_fakes.children @@ -903,6 +907,7 @@ def test_project_show_subtree(self): ) self.assertEqual(data, datalist) + @testtools.skip("skip until bug 1599333 is fixed") def test_project_show_parents_and_children(self): project = copy.deepcopy(identity_fakes.PROJECT_WITH_PARENT) project['subtree'] = identity_fakes.children diff --git a/openstackclient/tests/identity/v3/test_service_provider.py b/openstackclient/tests/identity/v3/test_service_provider.py index 99ea1f75ce..62ef54683f 100644 --- a/openstackclient/tests/identity/v3/test_service_provider.py +++ b/openstackclient/tests/identity/v3/test_service_provider.py @@ -14,6 +14,8 @@ import copy +import testtools + from openstackclient.identity.v3 import service_provider from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as service_fakes @@ -395,6 +397,7 @@ def setUp(self): # Get the command object to test self.cmd = service_provider.ShowServiceProvider(self.app, None) + @testtools.skip("skip until bug 1599333 is fixed") def test_service_provider_show(self): arglist = [ service_fakes.sp_id, diff --git a/openstackclient/tests/volume/test_find_resource.py b/openstackclient/tests/volume/test_find_resource.py index 227d6ca76c..8f29ec3432 100644 --- a/openstackclient/tests/volume/test_find_resource.py +++ b/openstackclient/tests/volume/test_find_resource.py @@ -19,6 +19,7 @@ from cinderclient.v1 import volumes from osc_lib import exceptions from osc_lib import utils +import testtools from openstackclient.tests import utils as test_utils from openstackclient.volume import client # noqa @@ -47,11 +48,13 @@ def setUp(self): api.client.get.side_effect = [Exception("Not found"), (resp, body)] self.manager = volumes.VolumeManager(api) + @testtools.skip("skip until bug 1599333 is fixed") def test_find(self): result = utils.find_resource(self.manager, NAME) self.assertEqual(ID, result.id) self.assertEqual(NAME, result.display_name) + @testtools.skip("skip until bug 1599333 is fixed") def test_not_find(self): self.assertRaises(exceptions.CommandError, utils.find_resource, self.manager, 'GeorgeMartin') @@ -69,11 +72,13 @@ def setUp(self): api.client.get.side_effect = [Exception("Not found"), (resp, body)] self.manager = volume_snapshots.SnapshotManager(api) + @testtools.skip("skip until bug 1599333 is fixed") def test_find(self): result = utils.find_resource(self.manager, NAME) self.assertEqual(ID, result.id) self.assertEqual(NAME, result.display_name) + @testtools.skip("skip until bug 1599333 is fixed") def test_not_find(self): self.assertRaises(exceptions.CommandError, utils.find_resource, self.manager, 'GeorgeMartin') From 1b05a6dff9faaabac030794423d781b647818f0a Mon Sep 17 00:00:00 2001 From: "jqjiang.1@gmail.com" Date: Wed, 6 Jul 2016 16:09:21 +0800 Subject: [PATCH 1075/3095] fix some spelling mistakes in doc/ I check all the files under doc/ directory and find three spelling mistakes - exeuction should be execution - Fefora should be Fedora - opentackclient should be openstackclient Change-Id: If9e5d07b6558871bb3f8d55b52bf8f1d9db0897e --- doc/source/commands.rst | 2 +- doc/source/developing.rst | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 9d8ad6fd26..f79ca173e3 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -191,7 +191,7 @@ list check out :doc:`plugin-commands`. * ``stack resource``: (**Orchestration (Heat)**) * ``stack snapshot``: (**Orchestration (Heat)**) * ``stack template``: (**Orchestration (Heat)**) -* ``task exeuction``: (**Workflow Engine (Mistral)**) +* ``task execution``: (**Workflow Engine (Mistral)**) * ``tld``: (**DNS (Designate)**) * ``workbook``: (**Workflow Engine (Mistral)**) * ``workflow``: (**Workflow Engine (Mistral)**) diff --git a/doc/source/developing.rst b/doc/source/developing.rst index 399e4a5a43..95a04073fa 100644 --- a/doc/source/developing.rst +++ b/doc/source/developing.rst @@ -30,7 +30,7 @@ Install the prerequisites for Tox: You may need to use pip install for some packages. -* On RHEL or CentOS including Fefora: +* On RHEL or CentOS including Fedora: .. code-block:: bash @@ -103,7 +103,7 @@ only want to run the test that hits your breakpoint: .. code-block:: bash - $ tox -e debug opentackclient.tests.identity.v3.test_group + $ tox -e debug openstackclient.tests.identity.v3.test_group For reference, the `debug`_ ``tox`` environment implements the instructions From 3222ffc157b6e686249fb2e3d4375c89384bfb97 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Wed, 6 Jul 2016 16:54:13 +0800 Subject: [PATCH 1076/3095] Add "--property" option to "snapshot create" command in volumev2 Add "--property" option to "snapshot create" command in volumev2 (v2 only) to support adding properties to a new snapshot. Change-Id: Ie0e90c9ccc2ac89b3b7b0ac89751fd864aada9a4 Closes-Bug: #1597192 --- doc/source/command-objects/snapshot.rst | 7 +++++++ openstackclient/tests/volume/v2/test_snapshot.py | 9 +++++++-- openstackclient/volume/v2/snapshot.py | 10 +++++++++- releasenotes/notes/bug-1597192-52801f7520287309.yaml | 4 ++++ 4 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/bug-1597192-52801f7520287309.yaml diff --git a/doc/source/command-objects/snapshot.rst b/doc/source/command-objects/snapshot.rst index 9033011822..8ac0b6b869 100644 --- a/doc/source/command-objects/snapshot.rst +++ b/doc/source/command-objects/snapshot.rst @@ -16,6 +16,7 @@ Create new snapshot [--name ] [--description ] [--force] + [--property [...] ] .. option:: --name @@ -30,6 +31,12 @@ Create new snapshot Create a snapshot attached to an instance. Default is False +.. option:: --property + + Set a property to this snapshot (repeat option to set multiple properties) + + *Volume version 2 only* + .. _snapshot_create-snapshot: .. describe:: diff --git a/openstackclient/tests/volume/v2/test_snapshot.py b/openstackclient/tests/volume/v2/test_snapshot.py index ef199cbc7f..2e9bcc82b0 100644 --- a/openstackclient/tests/volume/v2/test_snapshot.py +++ b/openstackclient/tests/volume/v2/test_snapshot.py @@ -70,12 +70,15 @@ def test_snapshot_create(self): "--name", self.new_snapshot.name, "--description", self.new_snapshot.description, "--force", + '--property', 'Alpha=a', + '--property', 'Beta=b', self.new_snapshot.volume_id, ] verifylist = [ ("name", self.new_snapshot.name), ("description", self.new_snapshot.description), ("force", True), + ('property', {'Alpha': 'a', 'Beta': 'b'}), ("volume", self.new_snapshot.volume_id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -86,7 +89,8 @@ def test_snapshot_create(self): self.new_snapshot.volume_id, force=True, name=self.new_snapshot.name, - description=self.new_snapshot.description + description=self.new_snapshot.description, + metadata={'Alpha': 'a', 'Beta': 'b'}, ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -110,7 +114,8 @@ def test_snapshot_create_without_name(self): self.new_snapshot.volume_id, force=True, name=None, - description=self.new_snapshot.description + description=self.new_snapshot.description, + metadata=None, ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) diff --git a/openstackclient/volume/v2/snapshot.py b/openstackclient/volume/v2/snapshot.py index 5aba04aee3..5e6949d44e 100644 --- a/openstackclient/volume/v2/snapshot.py +++ b/openstackclient/volume/v2/snapshot.py @@ -51,6 +51,13 @@ def get_parser(self, prog_name): help=_("Create a snapshot attached to an instance. " "Default is False") ) + parser.add_argument( + "--property", + metavar="", + action=parseractions.KeyValueAction, + help=_("Set a property to this snapshot " + "(repeat option to set multiple properties)"), + ) return parser def take_action(self, parsed_args): @@ -61,7 +68,8 @@ def take_action(self, parsed_args): volume_id, force=parsed_args.force, name=parsed_args.name, - description=parsed_args.description + description=parsed_args.description, + metadata=parsed_args.property, ) snapshot._info.update( {'properties': utils.format_dict(snapshot._info.pop('metadata'))} diff --git a/releasenotes/notes/bug-1597192-52801f7520287309.yaml b/releasenotes/notes/bug-1597192-52801f7520287309.yaml new file mode 100644 index 0000000000..2069ca2b06 --- /dev/null +++ b/releasenotes/notes/bug-1597192-52801f7520287309.yaml @@ -0,0 +1,4 @@ +--- +features: + - Add ``--property`` option to ``snapshot create`` command. + [Bug `1597192 `_] From f5aef9ac36257cfd9808c0dbdb80c5adc412876b Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Wed, 6 Jul 2016 11:11:56 +0800 Subject: [PATCH 1077/3095] Add '--force' option to 'backup delete' command in volumev2 Add '--force' option to 'backup delete' command in volumev2 (v2 only) to allow delete in state other than error or available. Change-Id: I661ea0d465db227e374cbacdde0206fa1a6dd3d5 Closes-Bug: #1597188 --- doc/source/command-objects/backup.rst | 9 ++++++++- openstackclient/tests/volume/v2/test_backup.py | 18 +++++++++++++++++- openstackclient/volume/v2/backup.py | 8 +++++++- .../notes/bug-1597188-a2ff62b809865365.yaml | 6 ++++++ 4 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/bug-1597188-a2ff62b809865365.yaml diff --git a/doc/source/command-objects/backup.rst b/doc/source/command-objects/backup.rst index 9fff393d0c..d7d96c289c 100644 --- a/doc/source/command-objects/backup.rst +++ b/doc/source/command-objects/backup.rst @@ -2,7 +2,7 @@ backup ====== -Block Storage v1 +Block Storage v1, v2 backup create ------------- @@ -53,8 +53,15 @@ Delete backup(s) .. code:: bash os backup delete + [--force] [ ...] +.. option:: --force + + Allow delete in state other than error or available + + *Volume version 2 only* + .. _backup_delete-backup: .. describe:: diff --git a/openstackclient/tests/volume/v2/test_backup.py b/openstackclient/tests/volume/v2/test_backup.py index b1e6594cb3..ce26a2d6f0 100644 --- a/openstackclient/tests/volume/v2/test_backup.py +++ b/openstackclient/tests/volume/v2/test_backup.py @@ -156,7 +156,23 @@ def test_backup_delete(self): result = self.cmd.take_action(parsed_args) - self.backups_mock.delete.assert_called_with(self.backup.id) + self.backups_mock.delete.assert_called_with(self.backup.id, False) + self.assertIsNone(result) + + def test_backup_delete_with_force(self): + arglist = [ + '--force', + self.backup.id, + ] + verifylist = [ + ('force', True), + ("backups", [self.backup.id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.backups_mock.delete.assert_called_with(self.backup.id, True) self.assertIsNone(result) diff --git a/openstackclient/volume/v2/backup.py b/openstackclient/volume/v2/backup.py index a8da40800d..fed5f09e2d 100644 --- a/openstackclient/volume/v2/backup.py +++ b/openstackclient/volume/v2/backup.py @@ -92,6 +92,12 @@ def get_parser(self, prog_name): nargs="+", help=_("Backup(s) to delete (name or ID)") ) + parser.add_argument( + '--force', + action='store_true', + default=False, + help=_("Allow delete in state other than error or available") + ) return parser def take_action(self, parsed_args): @@ -99,7 +105,7 @@ def take_action(self, parsed_args): for backup in parsed_args.backups: backup_id = utils.find_resource( volume_client.backups, backup).id - volume_client.backups.delete(backup_id) + volume_client.backups.delete(backup_id, parsed_args.force) class ListBackup(command.Lister): diff --git a/releasenotes/notes/bug-1597188-a2ff62b809865365.yaml b/releasenotes/notes/bug-1597188-a2ff62b809865365.yaml new file mode 100644 index 0000000000..d1f23c416b --- /dev/null +++ b/releasenotes/notes/bug-1597188-a2ff62b809865365.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--force`` option to ``backup delete`` command to allow delete + in state other than error or available. + [Bug `1597188 `_] From b74be57300badc97b93f77ad6288b2a5a0b0523e Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Tue, 5 Jul 2016 14:58:51 -0700 Subject: [PATCH 1078/3095] "server list": "Image Name", "Image ID" columns The `Image Name` column is shown by default (i.e.: without passing `--long`). E.g.: ``` $ openstack server list WARNING: openstackclient.common.utils is deprecated and will be removed after Jun 2017. Please use osc_lib.utils +--------------------------------------+-----------------+---------+----------------------+----------------------+ | ID | Name | Status | Networks | Image Name | +--------------------------------------+-----------------+---------+----------------------+----------------------+ | abe67035-b14f-4541-b436-e0e778ec4227 | n9anonw302mgm1 | ACTIVE | mnky3-3000=10.3.0.55 | Koala.2016-07-01-175 | | 8f6a2d12-2bc3-4d89-ba94-8916ce9cdf92 | n9anonw301mgm1 | ACTIVE | mnky3-3000=10.3.0.37 | Koala.2016-07-01-175 | | b316d6d1-67cf-4f75-94a4-4c9a2b03f6a4 | n9dobby301mgm0 | ACTIVE | mnky3-3000=10.3.0.36 | Koala.2016-05-04-130 | +--------------------------------------+-----------------+---------+----------------------+----------------------+ ``` The `Image ID` column is only available with `--long`. E.g.: ``` $ openstack server list --long -c Name -c "Image Name" -c "Image ID" WARNING: openstackclient.common.utils is deprecated and will be removed after Jun 2017. Please use osc_lib.utils +-----------------+----------------------+--------------------------------------+ | Name | Image Name | Image ID | +-----------------+----------------------+--------------------------------------+ | n9anonw302mgm1 | Koala.2016-07-01-175 | f587c6fc-1df3-42cd-ac86-8cd2c995a8d9 | | n9anonw301mgm1 | Koala.2016-07-01-175 | f587c6fc-1df3-42cd-ac86-8cd2c995a8d9 | | n9dobby301mgm0 | Koala.2016-05-04-130 | 37ff47a6-3e51-4986-bfa5-62afbfad5dfc | +-----------------+----------------------+--------------------------------------+ ``` Closes-Bug: #1599304 Change-Id: I477995b840eb9520b285948926ebbfe1777dd86c --- openstackclient/compute/v2/server.py | 53 +++++++++++++++---- .../tests/compute/v2/test_server.py | 15 +++++- 2 files changed, 56 insertions(+), 12 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 7e4b0dc1f7..93d913a064 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -776,6 +776,8 @@ def take_action(self, parsed_args): 'OS-EXT-STS:task_state', 'OS-EXT-STS:power_state', 'Networks', + 'Image Name', + 'Image ID', 'OS-EXT-AZ:availability_zone', 'OS-EXT-SRV-ATTR:host', 'Metadata', @@ -787,6 +789,8 @@ def take_action(self, parsed_args): 'Task State', 'Power State', 'Networks', + 'Image Name', + 'Image ID', 'Availability Zone', 'Host', 'Properties', @@ -803,12 +807,14 @@ def take_action(self, parsed_args): 'Name', 'Status', 'Networks', + 'Image Name', ) column_headers = ( 'ID', 'Name', 'Status', 'Networks', + 'Image Name', ) mixed_case_fields = [] @@ -820,17 +826,42 @@ def take_action(self, parsed_args): data = compute_client.servers.list(search_opts=search_opts, marker=marker_id, limit=parsed_args.limit) - return (column_headers, - (utils.get_item_properties( - s, columns, - mixed_case_fields=mixed_case_fields, - formatters={ - 'OS-EXT-STS:power_state': - _format_servers_list_power_state, - 'Networks': _format_servers_list_networks, - 'Metadata': utils.format_dict, - }, - ) for s in data)) + + images = {} + # Create a dict that maps image_id to image object. + # Needed so that we can display the "Image Name" column. + # "Image Name" is not crucial, so we swallow any exceptions. + try: + images_list = self.app.client_manager.image.images.list() + for i in images_list: + images[i.id] = i + except Exception: + pass + + # Populate image_name and image_id attributes of server objects + # so that we can display "Image Name" and "Image ID" columns. + for s in data: + if 'id' in s.image: + image = images.get(s.image['id']) + if image: + s.image_name = image.name + s.image_id = s.image['id'] + else: + s.image_name = '' + s.image_id = '' + + table = (column_headers, + (utils.get_item_properties( + s, columns, + mixed_case_fields=mixed_case_fields, + formatters={ + 'OS-EXT-STS:power_state': + _format_servers_list_power_state, + 'Networks': _format_servers_list_networks, + 'Metadata': utils.format_dict, + }, + ) for s in data)) + return table class LockServer(command.Command): diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 0bfe131019..a737f07110 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -12,6 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. # +import collections import getpass import mock from mock import call @@ -519,6 +520,7 @@ class TestServerList(TestServer): 'Name', 'Status', 'Networks', + 'Image Name', ) columns_long = ( 'ID', @@ -527,6 +529,8 @@ class TestServerList(TestServer): 'Task State', 'Power State', 'Networks', + 'Image Name', + 'Image ID', 'Availability Zone', 'Host', 'Properties', @@ -589,12 +593,19 @@ def setUp(self): self.data = [] self.data_long = [] + Image = collections.namedtuple('Image', 'id name') + self.images_mock.list.return_value = [ + Image(id=s.image['id'], name=self.image.name) + for s in self.servers + ] + for s in self.servers: self.data.append(( s.id, s.name, s.status, server._format_servers_list_networks(s.networks), + self.image.name, )) self.data_long.append(( s.id, @@ -605,6 +616,8 @@ def setUp(self): getattr(s, 'OS-EXT-STS:power_state') ), server._format_servers_list_networks(s.networks), + self.image.name, + s.image['id'], getattr(s, 'OS-EXT-AZ:availability_zone'), getattr(s, 'OS-EXT-SRV-ATTR:host'), s.Metadata, @@ -652,7 +665,7 @@ def test_server_list_with_image(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.cimages_mock.get.assert_called_with(self.image.id) + self.cimages_mock.get.assert_any_call(self.image.id) self.search_opts['image'] = self.image.id self.servers_mock.list.assert_called_with(**self.kwargs) From 1e895f3286cdb433a72a2195aa3adb40b6ec9a5c Mon Sep 17 00:00:00 2001 From: zhengsenyan Date: Thu, 7 Jul 2016 14:02:13 +0800 Subject: [PATCH 1079/3095] fix one spelling mistake and two help messages I checked all the 70 rst files in doc/source/command-objects and found that: -one spelling mistake as:rescure should be rescue(server.rst) -two help messages:xpvnc should be xvpvnc(console-url.rst and console.py) Change-Id: I6f46c79983eaf5650bd3aa0ab448c19f5c6527a9 --- doc/source/command-objects/console-url.rst | 2 +- doc/source/command-objects/server.rst | 2 +- openstackclient/compute/v2/console.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/source/command-objects/console-url.rst b/doc/source/command-objects/console-url.rst index 9cab87981e..7993a7bfed 100644 --- a/doc/source/command-objects/console-url.rst +++ b/doc/source/command-objects/console-url.rst @@ -24,7 +24,7 @@ Show server's remote console URL .. option:: --xvpvnc - Show xpvnc console URL + Show xvpvnc console URL .. option:: --spice diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index 49a5415337..f099c271c9 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -543,7 +543,7 @@ server rescue Put server in rescue mode -.. program:: server rescure +.. program:: server rescue .. code:: bash os server rescue diff --git a/openstackclient/compute/v2/console.py b/openstackclient/compute/v2/console.py index 4179fa5c1e..74bed4417b 100644 --- a/openstackclient/compute/v2/console.py +++ b/openstackclient/compute/v2/console.py @@ -87,7 +87,7 @@ def get_parser(self, prog_name): dest='url_type', action='store_const', const='xvpvnc', - help=_("Show xpvnc console URL") + help=_("Show xvpvnc console URL") ) type_group.add_argument( '--spice', From cc9efea9e054515dc97ab86b3a030d54ca267e44 Mon Sep 17 00:00:00 2001 From: zheng yin Date: Thu, 7 Jul 2016 17:22:11 +0800 Subject: [PATCH 1080/3095] modify notes in the FakeHypervisorStats docstring The arugments should be: count and current_workload Change-Id: I445d1d72d1f1b86a626bb4c9512cdb8311b2ebc9 --- openstackclient/tests/compute/v2/fakes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index f9b1f75f0c..a62483d9b8 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -393,7 +393,7 @@ def create_one_hypervisor_stats(attrs=None): :param Dictionary attrs: A dictionary with all attributes :return: - A FakeResource object, with id, hypervisor_hostname, and so on + A FakeResource object, with count, current_workload, and so on """ attrs = attrs or {} From 6f36385cb87d04577d60032f6e74c732edab6d45 Mon Sep 17 00:00:00 2001 From: qinchunhua Date: Thu, 7 Jul 2016 07:42:01 -0400 Subject: [PATCH 1081/3095] Correct reraising of exception When an exception was caught and rethrown, it should call 'raise' without any arguments because it shows the place where an exception occured initially instead of place where the exception re-raised. Change-Id: I5fb6dea5da7fb6e1e2b339a713c7d37f8c99e407 --- openstackclient/common/availability_zone.py | 4 ++-- openstackclient/common/quota.py | 2 +- openstackclient/identity/v2_0/project.py | 8 ++++---- openstackclient/identity/v2_0/role.py | 4 ++-- openstackclient/identity/v2_0/user.py | 8 ++++---- openstackclient/identity/v3/domain.py | 4 ++-- openstackclient/identity/v3/group.py | 4 ++-- openstackclient/identity/v3/project.py | 4 ++-- openstackclient/identity/v3/role.py | 4 ++-- openstackclient/identity/v3/user.py | 4 ++-- openstackclient/image/v2/image.py | 8 ++++---- openstackclient/shell.py | 4 ++-- 12 files changed, 29 insertions(+), 29 deletions(-) mode change 100644 => 100755 openstackclient/common/availability_zone.py mode change 100644 => 100755 openstackclient/common/quota.py mode change 100644 => 100755 openstackclient/identity/v2_0/project.py mode change 100644 => 100755 openstackclient/identity/v2_0/role.py mode change 100644 => 100755 openstackclient/identity/v2_0/user.py mode change 100644 => 100755 openstackclient/identity/v3/domain.py mode change 100644 => 100755 openstackclient/identity/v3/group.py mode change 100644 => 100755 openstackclient/identity/v3/project.py mode change 100644 => 100755 openstackclient/identity/v3/role.py mode change 100644 => 100755 openstackclient/identity/v3/user.py mode change 100644 => 100755 openstackclient/image/v2/image.py mode change 100644 => 100755 openstackclient/shell.py diff --git a/openstackclient/common/availability_zone.py b/openstackclient/common/availability_zone.py old mode 100644 new mode 100755 index 89d77d1585..63c55370b0 --- a/openstackclient/common/availability_zone.py +++ b/openstackclient/common/availability_zone.py @@ -122,11 +122,11 @@ def _get_compute_availability_zones(self, parsed_args): compute_client = self.app.client_manager.compute try: data = compute_client.availability_zones.list() - except nova_exceptions.Forbidden as e: # policy doesn't allow + except nova_exceptions.Forbidden: # policy doesn't allow try: data = compute_client.availability_zones.list(detailed=False) except Exception: - raise e + raise # Argh, the availability zones are not iterable... result = [] diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py old mode 100644 new mode 100755 index 69415f0dc0..3c12c3665d --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -231,7 +231,7 @@ def get_compute_volume_quota(self, client, parsed_args): if type(e).__name__ == 'EndpointNotFound': return {} else: - raise e + raise return quota._info def get_network_quota(self, parsed_args): diff --git a/openstackclient/identity/v2_0/project.py b/openstackclient/identity/v2_0/project.py old mode 100644 new mode 100755 index 6c5db13c68..fc5c920175 --- a/openstackclient/identity/v2_0/project.py +++ b/openstackclient/identity/v2_0/project.py @@ -86,7 +86,7 @@ def take_action(self, parsed_args): enabled=enabled, **kwargs ) - except ks_exc.Conflict as e: + except ks_exc.Conflict: if parsed_args.or_show: project = utils.find_resource( identity_client.tenants, @@ -94,7 +94,7 @@ def take_action(self, parsed_args): ) LOG.info(_('Returning existing project %s'), project.name) else: - raise e + raise # TODO(stevemar): Remove the line below when we support multitenancy project._info.pop('parent_id', None) @@ -242,7 +242,7 @@ def take_action(self, parsed_args): parsed_args.project, ) info.update(project._info) - except ks_exc.Forbidden as e: + except ks_exc.Forbidden: auth_ref = self.app.client_manager.auth_ref if ( parsed_args.project == auth_ref.project_id or @@ -256,7 +256,7 @@ def take_action(self, parsed_args): 'enabled': True, } else: - raise e + raise # TODO(stevemar): Remove the line below when we support multitenancy info.pop('parent_id', None) diff --git a/openstackclient/identity/v2_0/role.py b/openstackclient/identity/v2_0/role.py old mode 100644 new mode 100755 index 6d06230c44..191cdaa3d1 --- a/openstackclient/identity/v2_0/role.py +++ b/openstackclient/identity/v2_0/role.py @@ -93,7 +93,7 @@ def take_action(self, parsed_args): identity_client = self.app.client_manager.identity try: role = identity_client.roles.create(parsed_args.role_name) - except ks_exc.Conflict as e: + except ks_exc.Conflict: if parsed_args.or_show: role = utils.find_resource( identity_client.roles, @@ -101,7 +101,7 @@ def take_action(self, parsed_args): ) LOG.info(_('Returning existing role %s'), role.name) else: - raise e + raise info = {} info.update(role._info) diff --git a/openstackclient/identity/v2_0/user.py b/openstackclient/identity/v2_0/user.py old mode 100644 new mode 100755 index 0f3278309c..d2075150d2 --- a/openstackclient/identity/v2_0/user.py +++ b/openstackclient/identity/v2_0/user.py @@ -102,7 +102,7 @@ def take_action(self, parsed_args): tenant_id=project_id, enabled=enabled, ) - except ks_exc.Conflict as e: + except ks_exc.Conflict: if parsed_args.or_show: user = utils.find_resource( identity_client.users, @@ -110,7 +110,7 @@ def take_action(self, parsed_args): ) LOG.info(_('Returning existing user %s'), user.name) else: - raise e + raise # NOTE(dtroyer): The users.create() method wants 'tenant_id' but # the returned resource has 'tenantId'. Sigh. @@ -349,7 +349,7 @@ def take_action(self, parsed_args): parsed_args.user, ) info.update(user._info) - except ks_exc.Forbidden as e: + except ks_exc.Forbidden: auth_ref = self.app.client_manager.auth_ref if ( parsed_args.user == auth_ref.user_id or @@ -364,7 +364,7 @@ def take_action(self, parsed_args): 'enabled': True, } else: - raise e + raise if 'tenantId' in info: info.update( diff --git a/openstackclient/identity/v3/domain.py b/openstackclient/identity/v3/domain.py old mode 100644 new mode 100755 index 8ba76c41b5..0546ac5217 --- a/openstackclient/identity/v3/domain.py +++ b/openstackclient/identity/v3/domain.py @@ -76,13 +76,13 @@ def take_action(self, parsed_args): description=parsed_args.description, enabled=enabled, ) - except ks_exc.Conflict as e: + except ks_exc.Conflict: if parsed_args.or_show: domain = utils.find_resource(identity_client.domains, parsed_args.name) LOG.info(_('Returning existing domain %s'), domain.name) else: - raise e + raise domain._info.pop('links') return zip(*sorted(six.iteritems(domain._info))) diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py old mode 100644 new mode 100755 index 8351fe640f..c79a64e782 --- a/openstackclient/identity/v3/group.py +++ b/openstackclient/identity/v3/group.py @@ -160,14 +160,14 @@ def take_action(self, parsed_args): name=parsed_args.name, domain=domain, description=parsed_args.description) - except ks_exc.Conflict as e: + except ks_exc.Conflict: if parsed_args.or_show: group = utils.find_resource(identity_client.groups, parsed_args.name, domain_id=domain) LOG.info(_('Returning existing group %s'), group.name) else: - raise e + raise group._info.pop('links') return zip(*sorted(six.iteritems(group._info))) diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py old mode 100644 new mode 100755 index 56c1d41ada..5398701704 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -111,14 +111,14 @@ def take_action(self, parsed_args): enabled=enabled, **kwargs ) - except ks_exc.Conflict as e: + except ks_exc.Conflict: if parsed_args.or_show: project = utils.find_resource(identity_client.projects, parsed_args.name, domain_id=domain) LOG.info(_('Returning existing project %s'), project.name) else: - raise e + raise project._info.pop('links') return zip(*sorted(six.iteritems(project._info))) diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py old mode 100644 new mode 100755 index 965ca3f59f..d3c530a502 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -165,13 +165,13 @@ def take_action(self, parsed_args): try: role = identity_client.roles.create(name=parsed_args.name) - except ks_exc.Conflict as e: + except ks_exc.Conflict: if parsed_args.or_show: role = utils.find_resource(identity_client.roles, parsed_args.name) LOG.info(_('Returning existing role %s'), role.name) else: - raise e + raise role._info.pop('links') return zip(*sorted(six.iteritems(role._info))) diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py old mode 100644 new mode 100755 index dd5af06a31..3e189ac1c6 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -121,14 +121,14 @@ def take_action(self, parsed_args): description=parsed_args.description, enabled=enabled ) - except ks_exc.Conflict as e: + except ks_exc.Conflict: if parsed_args.or_show: user = utils.find_resource(identity_client.users, parsed_args.name, domain_id=domain_id) LOG.info(_('Returning existing user %s'), user.name) else: - raise e + raise user._info.pop('links') return zip(*sorted(six.iteritems(user._info))) diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py old mode 100644 new mode 100755 index 309b1b6b18..64092fea74 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -339,7 +339,7 @@ def take_action(self, parsed_args): with fp: try: image_client.images.upload(image.id, fp) - except Exception as e: + except Exception: # If the upload fails for some reason attempt to remove the # dangling queued image made by the create() call above but # only if the user did not specify an id which indicates @@ -349,7 +349,7 @@ def take_action(self, parsed_args): image_client.images.delete(image.id) except Exception: pass # we don't care about this one - raise e # now, throw the upload exception again + raise # now, throw the upload exception again # update the image after the data has been uploaded image = image_client.images.get(image.id) @@ -834,11 +834,11 @@ def take_action(self, parsed_args): try: image = image_client.images.update(image.id, **kwargs) - except Exception as e: + except Exception: if activation_status is not None: LOG.info(_("Image %(id)s was %(status)s."), {'id': image.id, 'status': activation_status}) - raise e + raise class ShowImage(command.ShowOne): diff --git a/openstackclient/shell.py b/openstackclient/shell.py old mode 100644 new mode 100755 index 49a0604081..ed729e537c --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -331,10 +331,10 @@ def initialize_app(self, argv): 'auth_type': 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 # TODO(thowe): Change cliff so the default value for debug # can be set to None. From cde07464b53562011bd6604394e2d420c898915b Mon Sep 17 00:00:00 2001 From: Ryan Selden Date: Wed, 6 Jul 2016 20:27:31 +0000 Subject: [PATCH 1082/3095] Added a note on how to test changes It's confusing for new contributors to test the client. Added a quick note to the README explaining how to do so. Change-Id: I2a185f04bbec7ab01fdd2ab436a5f413f33819e7 --- README.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.rst b/README.rst index 340a0ea9eb..8ebc24b507 100644 --- a/README.rst +++ b/README.rst @@ -57,6 +57,15 @@ command:: openstack help openstack help server create +If you want to make changes to the OpenStackClient for testing and contribution, +make any changes and then run:: + + python setup.py develop + +or:: + + pip install -e . + Configuration ============= From 9f09d8c5d43141b683dd6b5258fa94799571a843 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Thu, 7 Jul 2016 22:41:07 -0400 Subject: [PATCH 1083/3095] Unskip tests caused by bug 1599333 There is now a second .get() call in osc_lib.utils.find_resources. These tests were failing because they only mocked a single access call to .get(). Ensure there are two calls to .get(), with the first one raising an exception. Change-Id: Idd2ad4a27a6db5bee633cc37a1042dbb0a57aa71 Closes-Bug: #1599333 --- .../tests/identity/v3/test_identity_provider.py | 6 ++++-- .../tests/identity/v3/test_project.py | 17 +++++++++++------ .../tests/identity/v3/test_service_provider.py | 6 +++--- .../tests/volume/test_find_resource.py | 13 ++++++------- 4 files changed, 24 insertions(+), 18 deletions(-) diff --git a/openstackclient/tests/identity/v3/test_identity_provider.py b/openstackclient/tests/identity/v3/test_identity_provider.py index aaf5b06d4e..161a1a1b0d 100644 --- a/openstackclient/tests/identity/v3/test_identity_provider.py +++ b/openstackclient/tests/identity/v3/test_identity_provider.py @@ -15,7 +15,6 @@ import copy import mock -import testtools from openstackclient.identity.v3 import identity_provider from openstackclient.tests import fakes @@ -600,11 +599,14 @@ def setUp(self): copy.deepcopy(identity_fakes.IDENTITY_PROVIDER), loaded=True, ) + + self.identity_providers_mock.get.side_effect = [Exception("Not found"), + ret] self.identity_providers_mock.get.return_value = ret + # Get the command object to test self.cmd = identity_provider.ShowIdentityProvider(self.app, None) - @testtools.skip("skip until bug 1599333 is fixed") def test_identity_provider_show(self): arglist = [ identity_fakes.idp_id, diff --git a/openstackclient/tests/identity/v3/test_project.py b/openstackclient/tests/identity/v3/test_project.py index 23b0c9a018..65874baae4 100644 --- a/openstackclient/tests/identity/v3/test_project.py +++ b/openstackclient/tests/identity/v3/test_project.py @@ -16,7 +16,6 @@ import mock from osc_lib import exceptions -import testtools from openstackclient.identity.v3 import project from openstackclient.tests.identity.v3 import fakes as identity_fakes @@ -731,14 +730,16 @@ def setUp(self): self.project = identity_fakes.FakeProject.create_one_project( attrs={'domain_id': self.domain.id}) - self.projects_mock.get.return_value = self.project # Get the command object to test self.cmd = project.ShowProject(self.app, None) - @testtools.skip("skip until bug 1599333 is fixed") def test_project_show(self): + self.projects_mock.get.side_effect = [Exception("Not found"), + self.project] + self.projects_mock.get.return_value = self.project + arglist = [ self.project.id, ] @@ -761,6 +762,7 @@ def test_project_show(self): # 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.projects_mock.get.assert_called_with( self.project.id, parents_as_list=False, @@ -788,7 +790,6 @@ def test_project_show(self): ) self.assertEqual(datalist, data) - @testtools.skip("skip until bug 1599333 is fixed") def test_project_show_parents(self): self.project = identity_fakes.FakeProject.create_one_project( attrs={ @@ -796,6 +797,8 @@ def test_project_show_parents(self): 'parents': [{'project': {'id': self.project.parent_id}}] } ) + self.projects_mock.get.side_effect = [Exception("Not found"), + self.project] self.projects_mock.get.return_value = self.project arglist = [ @@ -848,7 +851,6 @@ def test_project_show_parents(self): ) self.assertEqual(data, datalist) - @testtools.skip("skip until bug 1599333 is fixed") def test_project_show_subtree(self): self.project = identity_fakes.FakeProject.create_one_project( attrs={ @@ -856,6 +858,8 @@ def test_project_show_subtree(self): 'subtree': [{'project': {'id': 'children-id'}}] } ) + self.projects_mock.get.side_effect = [Exception("Not found"), + self.project] self.projects_mock.get.return_value = self.project arglist = [ @@ -908,7 +912,6 @@ def test_project_show_subtree(self): ) self.assertEqual(data, datalist) - @testtools.skip("skip until bug 1599333 is fixed") def test_project_show_parents_and_children(self): self.project = identity_fakes.FakeProject.create_one_project( attrs={ @@ -917,6 +920,8 @@ def test_project_show_parents_and_children(self): 'subtree': [{'project': {'id': 'children-id'}}] } ) + self.projects_mock.get.side_effect = [Exception("Not found"), + self.project] self.projects_mock.get.return_value = self.project arglist = [ diff --git a/openstackclient/tests/identity/v3/test_service_provider.py b/openstackclient/tests/identity/v3/test_service_provider.py index 62ef54683f..1d82cd1532 100644 --- a/openstackclient/tests/identity/v3/test_service_provider.py +++ b/openstackclient/tests/identity/v3/test_service_provider.py @@ -14,8 +14,6 @@ import copy -import testtools - from openstackclient.identity.v3 import service_provider from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as service_fakes @@ -393,11 +391,13 @@ def setUp(self): copy.deepcopy(service_fakes.SERVICE_PROVIDER), loaded=True, ) + self.service_providers_mock.get.side_effect = [Exception("Not found"), + ret] self.service_providers_mock.get.return_value = ret + # Get the command object to test self.cmd = service_provider.ShowServiceProvider(self.app, None) - @testtools.skip("skip until bug 1599333 is fixed") def test_service_provider_show(self): arglist = [ service_fakes.sp_id, diff --git a/openstackclient/tests/volume/test_find_resource.py b/openstackclient/tests/volume/test_find_resource.py index 8f29ec3432..982b02f03d 100644 --- a/openstackclient/tests/volume/test_find_resource.py +++ b/openstackclient/tests/volume/test_find_resource.py @@ -19,7 +19,6 @@ from cinderclient.v1 import volumes from osc_lib import exceptions from osc_lib import utils -import testtools from openstackclient.tests import utils as test_utils from openstackclient.volume import client # noqa @@ -45,16 +44,16 @@ def setUp(self): api.client.get = mock.Mock() resp = mock.Mock() body = {"volumes": [{"id": ID, 'display_name': NAME}]} - api.client.get.side_effect = [Exception("Not found"), (resp, body)] + api.client.get.side_effect = [Exception("Not found"), + Exception("Not found"), + (resp, body)] self.manager = volumes.VolumeManager(api) - @testtools.skip("skip until bug 1599333 is fixed") def test_find(self): result = utils.find_resource(self.manager, NAME) self.assertEqual(ID, result.id) self.assertEqual(NAME, result.display_name) - @testtools.skip("skip until bug 1599333 is fixed") def test_not_find(self): self.assertRaises(exceptions.CommandError, utils.find_resource, self.manager, 'GeorgeMartin') @@ -69,16 +68,16 @@ def setUp(self): api.client.get = mock.Mock() resp = mock.Mock() body = {"snapshots": [{"id": ID, 'display_name': NAME}]} - api.client.get.side_effect = [Exception("Not found"), (resp, body)] + api.client.get.side_effect = [Exception("Not found"), + Exception("Not found"), + (resp, body)] self.manager = volume_snapshots.SnapshotManager(api) - @testtools.skip("skip until bug 1599333 is fixed") def test_find(self): result = utils.find_resource(self.manager, NAME) self.assertEqual(ID, result.id) self.assertEqual(NAME, result.display_name) - @testtools.skip("skip until bug 1599333 is fixed") def test_not_find(self): self.assertRaises(exceptions.CommandError, utils.find_resource, self.manager, 'GeorgeMartin') From 2a5ab357930a318800b6b15bd0a9f4ffd2c460d4 Mon Sep 17 00:00:00 2001 From: dongwenshuai Date: Thu, 7 Jul 2016 05:25:55 -0400 Subject: [PATCH 1084/3095] Add network-topolopy support Add a new api about network-topolopy in openstackclient It is the intention for OSC to collect data from existing REST APIs Change-Id: I23709ee906555d773c3efeb094aef50587732a1b Related-Bug: #1586584 Implements: bp network-topology-support --- doc/source/specs/network-topology.rst | 44 +++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100755 doc/source/specs/network-topology.rst diff --git a/doc/source/specs/network-topology.rst b/doc/source/specs/network-topology.rst new file mode 100755 index 0000000000..6f9c8532d6 --- /dev/null +++ b/doc/source/specs/network-topology.rst @@ -0,0 +1,44 @@ +================ +network topology +================ + +A **network topology** shows a topological graph about +devices which connect to the specific network. Also, it +will return availability information for each individual +device within the network as well. One other thing to note +is that it is the intention for OSC to collect data from +existing REST APIs + +Network v2 + +network topology list +--------------------- + +List network topologies + +.. program:: network topology list +.. code:: bash + + os network topology list + [--project ] + +.. option:: --project + + List network topologies for given project + (name or ID) + +network topology show +--------------------- + +Show network topology details + +.. program:: network topology show +.. code:: bash + + os network topology show + + +.. _network_topology_show-network +.. describe:: + + Show network topology for a specific network (name or ID) From 527b2030fb4c73aa64d5815166dc326a44fae4ef Mon Sep 17 00:00:00 2001 From: zheng yin Date: Fri, 8 Jul 2016 18:36:11 +0800 Subject: [PATCH 1085/3095] Add notes, modify notes in fakes docstring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some code has no notes, then add them Some code notes are not perfect, then improve them Some code notes are incorrect, then modify them Change-Id: I43f9a8663ae138bdd494bc234c1fac00c2dd6c95 --- openstackclient/tests/compute/v2/fakes.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index a62483d9b8..7640247688 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -81,7 +81,10 @@ def create_one_aggregate(attrs=None): "availability_zone": "ag_zone", } } + + # Overwrite default attributes. aggregate_info.update(attrs) + aggregate = fakes.FakeResource( info=copy.deepcopy(aggregate_info), loaded=True) @@ -254,6 +257,8 @@ def create_one_agent(attrs=None): 'md5hash': 'agent-md5hash', 'hypervisor': 'hypervisor', } + + # Overwrite default attributes. agent_info.update(attrs) agent = fakes.FakeResource(info=copy.deepcopy(agent_info), @@ -412,6 +417,8 @@ def create_one_hypervisor_stats(attrs=None): 'vcpus': 8, 'vcpus_used': 3, } + + # Overwrite default attributes. stats_info.update(attrs) # Set default method. @@ -575,7 +582,7 @@ def create_one_server(attrs=None, methods=None): :param Dictionary methods: A dictionary with all methods :return: - A FakeResource object, with id, name, metadata + A FakeResource object, with id, name, metadata, and so on """ attrs = attrs or {} methods = methods or {} @@ -651,7 +658,7 @@ def create_one_service(attrs=None): :param Dictionary attrs: A dictionary with all attributes :return: - A FakeResource object, with id, host, binary + A FakeResource object, with id, host, binary, and so on """ attrs = attrs or {} @@ -703,7 +710,7 @@ def create_one_flavor(attrs=None): :param Dictionary attrs: A dictionary with all attributes :return: - A FakeResource object, with id, name, ram, vcpus, properties + A FakeResource object, with id, name, ram, vcpus, and so on """ attrs = attrs or {} @@ -790,7 +797,7 @@ def create_one_keypair(attrs=None, no_pri=False): :param Dictionary attrs: A dictionary with all attributes :return: - A FakeResource + A FakeResource object, name, fingerprint, and so on """ attrs = attrs or {} @@ -984,7 +991,7 @@ def create_one_floating_ip_pool(attrs=None): :param Dictionary attrs: A dictionary with all attributes :return: - A FakeResource object, with id, etc + A FakeResource object, with name, etc """ if attrs is None: attrs = {} @@ -1127,7 +1134,7 @@ def create_one_host(attrs=None): :param Dictionary attrs: A dictionary with all attributes :return: - A FakeResource object, with id and other attributes + A FakeResource object, with uuid and other attributes """ attrs = attrs or {} @@ -1183,6 +1190,7 @@ def create_one_server_group(attrs=None): if attrs is None: attrs = {} + # Set default attributes. server_group_info = { 'id': 'server-group-id-' + uuid.uuid4().hex, 'members': [], @@ -1192,7 +1200,10 @@ def create_one_server_group(attrs=None): 'project_id': 'server-group-project-id-' + uuid.uuid4().hex, 'user_id': 'server-group-user-id-' + uuid.uuid4().hex, } + + # Overwrite default attributes. server_group_info.update(attrs) + server_group = fakes.FakeResource( info=copy.deepcopy(server_group_info), loaded=True) From 94f654b010e15fadc6ad6f72e50a0f9cee4fb250 Mon Sep 17 00:00:00 2001 From: zheng yin Date: Sat, 9 Jul 2016 05:37:55 +0800 Subject: [PATCH 1086/3095] Remove FakeService.get_services FakeService.get_services is useless in other test files, so remove it Change-Id: If90b8742be97697f285750a05896b574c45f3504 --- openstackclient/tests/volume/v2/fakes.py | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py index eff0faf940..8a628ac97a 100644 --- a/openstackclient/tests/volume/v2/fakes.py +++ b/openstackclient/tests/volume/v2/fakes.py @@ -146,26 +146,6 @@ def create_services(attrs=None, count=2): return services - @staticmethod - def get_services(services=None, count=2): - """Get an iterable MagicMock object with a list of faked services. - - If services list is provided, then initialize the Mock object with the - list. Otherwise create one. - - :param List services: - A list of FakeResource objects faking services - :param Integer count: - The number of services to be faked - :return - An iterable Mock object with side_effect set to a list of faked - services - """ - if services is None: - services = FakeService.create_services(count) - - return mock.MagicMock(side_effect=services) - class FakeVolumeClient(object): From 746e1937c9ac884ce60497847b9294017cf30b94 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sat, 9 Jul 2016 03:17:22 +0000 Subject: [PATCH 1087/3095] Updated from global requirements Change-Id: Ie3f88ed6c0db05795ad6f46eb7596bb4f9ffcc13 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 0f7e292468..370e7d351a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ keystoneauth1>=2.7.0 # Apache-2.0 openstacksdk>=0.8.6 # Apache-2.0 os-client-config>=1.13.1 # Apache-2.0 osc-lib>=0.1.0 # Apache-2.0 -oslo.config>=3.10.0 # Apache-2.0 +oslo.config>=3.12.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 oslo.utils>=3.14.0 # Apache-2.0 python-glanceclient>=2.0.0 # Apache-2.0 From 17f177bb7131ef833c3574994c86ccb0cf092338 Mon Sep 17 00:00:00 2001 From: ji-xuepeng Date: Sat, 9 Jul 2016 23:17:58 +0800 Subject: [PATCH 1088/3095] remove unused LOG This is to remove unused LOG to keep code clean. Change-Id: Ic544157b19cefb4120381f3314334c60a9020baf --- openstackclient/api/auth_plugin.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/openstackclient/api/auth_plugin.py b/openstackclient/api/auth_plugin.py index dc47a688e6..a106b1ebc1 100644 --- a/openstackclient/api/auth_plugin.py +++ b/openstackclient/api/auth_plugin.py @@ -13,15 +13,11 @@ """Authentication Plugin Library""" -import logging - from keystoneauth1 import loading from keystoneauth1 import token_endpoint from openstackclient.i18n import _ -LOG = logging.getLogger(__name__) - class TokenEndpoint(loading.BaseLoader): """Auth plugin to handle traditional token/endpoint usage From 226766ae74a88f30d2a12383dd17b443de9a350b Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sat, 9 Jul 2016 19:27:08 +0000 Subject: [PATCH 1089/3095] Updated from global requirements Change-Id: I4be6137a0d6717702e495d391cd9cb404fcf26ce --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 370e7d351a..e3b550d1b2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ os-client-config>=1.13.1 # Apache-2.0 osc-lib>=0.1.0 # Apache-2.0 oslo.config>=3.12.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 -oslo.utils>=3.14.0 # Apache-2.0 +oslo.utils>=3.15.0 # Apache-2.0 python-glanceclient>=2.0.0 # Apache-2.0 python-keystoneclient!=1.8.0,!=2.1.0,>=1.7.0 # Apache-2.0 python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0 From cd189860e5cf7d9e85235b55dfa3f67e5a1f67d8 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Mon, 11 Jul 2016 10:10:59 +0800 Subject: [PATCH 1090/3095] Remove useless dest of option in volume v1&v2 some "dest" argument is useless because they are the same as the option name. So remove them. Change-Id: Idd37e8816cf8b0833c94a5e741fdfe56602551f9 --- openstackclient/volume/v1/volume.py | 1 - openstackclient/volume/v2/volume.py | 1 - openstackclient/volume/v2/volume_type.py | 2 -- 3 files changed, 4 deletions(-) diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index e11aa1a71c..820673bb93 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -175,7 +175,6 @@ def get_parser(self, prog_name): ) parser.add_argument( '--force', - dest='force', action='store_true', default=False, help=_('Attempt forced removal of volume(s), regardless of state ' diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index be2388fb2f..0ee7bd8f36 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -167,7 +167,6 @@ def get_parser(self, prog_name): ) parser.add_argument( "--force", - dest="force", action="store_true", default=False, help=_("Attempt forced removal of volume(s), regardless of state " diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index 87f4c5472a..91ac17a16f 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -47,14 +47,12 @@ def get_parser(self, prog_name): public_group = parser.add_mutually_exclusive_group() public_group.add_argument( "--public", - dest="public", action="store_true", default=False, help=_("Volume type is accessible to the public"), ) public_group.add_argument( "--private", - dest="private", action="store_true", default=False, help=_("Volume type is not accessible to the public"), From 59c9d508ef75da076a688327d0454858ba52c3d6 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Mon, 11 Jul 2016 09:56:55 +0800 Subject: [PATCH 1091/3095] Add missing "Volume version 2 only" message in backup.rst Former patchs add some volume v2 only options for backup but miss "Volume version 2 only" message. This patch adds them. Change-Id: Id50db25617776b6ee0d0ad6b4b82bd443b940343 --- doc/source/command-objects/backup.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/source/command-objects/backup.rst b/doc/source/command-objects/backup.rst index d7d96c289c..ee100b1b2d 100644 --- a/doc/source/command-objects/backup.rst +++ b/doc/source/command-objects/backup.rst @@ -16,6 +16,7 @@ Create new backup [--container ] [--name ] [--description ] + [--snapshot ] [--force] @@ -35,10 +36,14 @@ Create new backup Snapshot to backup (name or ID) + *Volume version 2 only* + .. option:: --force Allow to back up an in-use volume + *Volume version 2 only* + .. _backup_create-backup: .. describe:: From 6825263609be062649997f81d0f4fdde091eaf43 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Mon, 11 Jul 2016 14:46:13 +0800 Subject: [PATCH 1092/3095] Use FakeProject and FakeDomain classes in unit tests of networkv2 FakeProject and FakeDomain classes have been completed in identityv2_0 and v3, Now we can use these classes instead of old test data in networkv2. Change-Id: I2f698e54ff35e24ffbdcaba29da6f96eb263cc0a Partially-Implements: blueprint use-fake-project --- .../tests/network/v2/test_address_scope.py | 27 ++++------ .../tests/network/v2/test_ip_availability.py | 14 ++---- .../tests/network/v2/test_network.py | 50 ++++++++----------- .../tests/network/v2/test_security_group.py | 31 +++++------- .../network/v2/test_security_group_rule.py | 30 +++++------ .../tests/network/v2/test_subnet.py | 21 +++----- .../tests/network/v2/test_subnet_pool.py | 25 ++++------ 7 files changed, 77 insertions(+), 121 deletions(-) diff --git a/openstackclient/tests/network/v2/test_address_scope.py b/openstackclient/tests/network/v2/test_address_scope.py index 722371f9d9..16e74f46ec 100644 --- a/openstackclient/tests/network/v2/test_address_scope.py +++ b/openstackclient/tests/network/v2/test_address_scope.py @@ -11,7 +11,6 @@ # under the License. # -import copy import mock from mock import call @@ -35,11 +34,13 @@ def setUp(self): class TestCreateAddressScope(TestAddressScope): + project = identity_fakes_v3.FakeProject.create_one_project() + domain = identity_fakes_v3.FakeDomain.create_one_domain() # The new address scope created. new_address_scope = ( network_fakes.FakeAddressScope.create_one_address_scope( attrs={ - 'tenant_id': identity_fakes_v3.project_id, + 'tenant_id': project.id, } )) columns = ( @@ -75,19 +76,11 @@ def setUp(self): # Get a shortcut to the ProjectManager Mock self.projects_mock = self.identity.projects - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes_v3.PROJECT), - loaded=True, - ) + self.projects_mock.get.return_value = self.project # Get a shortcut to the DomainManager Mock self.domains_mock = self.identity.domains - self.domains_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes_v3.DOMAIN), - loaded=True, - ) + self.domains_mock.get.return_value = self.domain def test_create_no_options(self): arglist = [] @@ -121,15 +114,15 @@ def test_create_all_options(self): arglist = [ '--ip-version', str(self.new_address_scope.ip_version), '--share', - '--project', identity_fakes_v3.project_name, - '--project-domain', identity_fakes_v3.domain_name, + '--project', self.project.name, + '--project-domain', self.domain.name, self.new_address_scope.name, ] verifylist = [ ('ip_version', self.new_address_scope.ip_version), ('share', True), - ('project', identity_fakes_v3.project_name), - ('project_domain', identity_fakes_v3.domain_name), + ('project', self.project.name), + ('project_domain', self.domain.name), ('name', self.new_address_scope.name), ] @@ -139,7 +132,7 @@ def test_create_all_options(self): self.network.create_address_scope.assert_called_once_with(**{ 'ip_version': self.new_address_scope.ip_version, 'shared': True, - 'tenant_id': identity_fakes_v3.project_id, + 'tenant_id': self.project.id, 'name': self.new_address_scope.name, }) self.assertEqual(self.columns, columns) diff --git a/openstackclient/tests/network/v2/test_ip_availability.py b/openstackclient/tests/network/v2/test_ip_availability.py index c6ec2b0b06..21d44d0754 100644 --- a/openstackclient/tests/network/v2/test_ip_availability.py +++ b/openstackclient/tests/network/v2/test_ip_availability.py @@ -11,7 +11,6 @@ # under the License. # -import copy import mock from osc_lib import utils as common_utils @@ -41,11 +40,8 @@ def setUp(self): # Get a shortcut to the ProjectManager Mock self.projects_mock = self.identity.projects - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) + self.project = identity_fakes.FakeProject.create_one_project() + self.projects_mock.get.return_value = self.project class TestListIPAvailability(TestIPAvailability): @@ -109,16 +105,16 @@ def test_list_ip_version(self): def test_list_project(self): arglist = [ - '--project', identity_fakes.project_name + '--project', self.project.name ] verifylist = [ - ('project', identity_fakes.project_name) + ('project', self.project.name) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - filters = {'tenant_id': identity_fakes.project_id, + filters = {'tenant_id': self.project.id, 'ip_version': 4} self.network.network_ip_availabilities.assert_called_once_with( diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index 8fc9dadf8f..4322bf9a55 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -11,7 +11,6 @@ # under the License. # -import copy import mock from mock import call @@ -40,10 +39,12 @@ def setUp(self): class TestCreateNetworkIdentityV3(TestNetwork): + project = identity_fakes_v3.FakeProject.create_one_project() + domain = identity_fakes_v3.FakeDomain.create_one_domain() # The new network created. _network = network_fakes.FakeNetwork.create_one_network( attrs={ - 'tenant_id': identity_fakes_v3.project_id, + 'tenant_id': project.id, 'availability_zone_hints': ["nova"], } ) @@ -96,19 +97,11 @@ def setUp(self): # Get a shortcut to the ProjectManager Mock self.projects_mock = self.identity.projects - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes_v3.PROJECT), - loaded=True, - ) + self.projects_mock.get.return_value = self.project # Get a shortcut to the DomainManager Mock self.domains_mock = self.identity.domains - self.domains_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes_v3.DOMAIN), - loaded=True, - ) + self.domains_mock.get.return_value = self.domain def test_create_no_options(self): arglist = [] @@ -143,8 +136,8 @@ def test_create_all_options(self): arglist = [ "--disable", "--share", - "--project", identity_fakes_v3.project_name, - "--project-domain", identity_fakes_v3.domain_name, + "--project", self.project.name, + "--project-domain", self.domain.name, "--availability-zone-hint", "nova", "--external", "--default", "--provider-network-type", "vlan", @@ -156,8 +149,8 @@ def test_create_all_options(self): verifylist = [ ('disable', True), ('share', True), - ('project', identity_fakes_v3.project_name), - ('project_domain', identity_fakes_v3.domain_name), + ('project', self.project.name), + ('project_domain', self.domain.name), ('availability_zone_hints', ["nova"]), ('external', True), ('default', True), @@ -176,7 +169,7 @@ def test_create_all_options(self): 'availability_zone_hints': ["nova"], 'name': self._network.name, 'shared': True, - 'tenant_id': identity_fakes_v3.project_id, + 'tenant_id': self.project.id, 'is_default': True, 'router:external': True, 'provider:network_type': 'vlan', @@ -214,9 +207,10 @@ def test_create_other_options(self): class TestCreateNetworkIdentityV2(TestNetwork): + project = identity_fakes_v2.FakeProject.create_one_project() # The new network created. _network = network_fakes.FakeNetwork.create_one_network( - attrs={'tenant_id': identity_fakes_v2.project_id} + attrs={'tenant_id': project.id} ) columns = ( @@ -267,24 +261,20 @@ def setUp(self): # Get a shortcut to the ProjectManager Mock self.projects_mock = self.identity.tenants - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes_v2.PROJECT), - loaded=True, - ) + self.projects_mock.get.return_value = self.project # There is no DomainManager Mock in fake identity v2. def test_create_with_project_identityv2(self): arglist = [ - "--project", identity_fakes_v2.project_name, + "--project", self.project.name, self._network.name, ] verifylist = [ ('enable', True), ('share', None), ('name', self._network.name), - ('project', identity_fakes_v2.project_name), + ('project', self.project.name), ('external', False), ] @@ -294,22 +284,22 @@ def test_create_with_project_identityv2(self): self.network.create_network.assert_called_once_with(**{ 'admin_state_up': True, 'name': self._network.name, - 'tenant_id': identity_fakes_v2.project_id, + 'tenant_id': self.project.id, }) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) def test_create_with_domain_identityv2(self): arglist = [ - "--project", identity_fakes_v3.project_name, - "--project-domain", identity_fakes_v3.domain_name, + "--project", self.project.name, + "--project-domain", "domain-name", self._network.name, ] verifylist = [ ('enable', True), ('share', None), - ('project', identity_fakes_v3.project_name), - ('project_domain', identity_fakes_v3.domain_name), + ('project', self.project.name), + ('project_domain', "domain-name"), ('name', self._network.name), ('external', False), ] diff --git a/openstackclient/tests/network/v2/test_security_group.py b/openstackclient/tests/network/v2/test_security_group.py index b0c1498519..7ac925ac2f 100644 --- a/openstackclient/tests/network/v2/test_security_group.py +++ b/openstackclient/tests/network/v2/test_security_group.py @@ -11,7 +11,6 @@ # under the License. # -import copy import mock from mock import call @@ -45,6 +44,8 @@ def setUp(self): class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork): + project = identity_fakes.FakeProject.create_one_project() + domain = identity_fakes.FakeDomain.create_one_domain() # The security group to be created. _security_group = \ network_fakes.FakeSecurityGroup.create_one_security_group() @@ -81,19 +82,11 @@ def setUp(self): # Get a shortcut to the ProjectManager Mock self.projects_mock = self.identity.projects - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) + self.projects_mock.get.return_value = self.project # Get a shortcut to the DomainManager Mock self.domains_mock = self.identity.domains - self.domains_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.DOMAIN), - loaded=True, - ) + self.domains_mock.get.return_value = self.domain # Get the command object to test self.cmd = security_group.CreateSecurityGroup(self.app, self.namespace) @@ -123,15 +116,15 @@ def test_create_min_options(self): def test_create_all_options(self): arglist = [ '--description', self._security_group.description, - '--project', identity_fakes.project_name, - '--project-domain', identity_fakes.domain_name, + '--project', self.project.name, + '--project-domain', self.domain.name, self._security_group.name, ] verifylist = [ ('description', self._security_group.description), ('name', self._security_group.name), - ('project', identity_fakes.project_name), - ('project_domain', identity_fakes.domain_name), + ('project', self.project.name), + ('project_domain', self.domain.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -140,7 +133,7 @@ def test_create_all_options(self): self.network.create_security_group.assert_called_once_with(**{ 'description': self._security_group.description, 'name': self._security_group.name, - 'tenant_id': identity_fakes.project_id, + 'tenant_id': self.project.id, }) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -148,6 +141,8 @@ def test_create_all_options(self): class TestCreateSecurityGroupCompute(TestSecurityGroupCompute): + project = identity_fakes.FakeProject.create_one_project() + domain = identity_fakes.FakeDomain.create_one_domain() # The security group to be shown. _security_group = \ compute_fakes.FakeSecurityGroup.create_one_security_group() @@ -184,8 +179,8 @@ def test_create_no_options(self): def test_create_network_options(self): arglist = [ - '--project', identity_fakes.project_name, - '--project-domain', identity_fakes.domain_name, + '--project', self.project.name, + '--project-domain', self.domain.name, self._security_group.name, ] self.assertRaises(tests_utils.ParserException, diff --git a/openstackclient/tests/network/v2/test_security_group_rule.py b/openstackclient/tests/network/v2/test_security_group_rule.py index b2862679cb..67a123aa10 100644 --- a/openstackclient/tests/network/v2/test_security_group_rule.py +++ b/openstackclient/tests/network/v2/test_security_group_rule.py @@ -46,6 +46,8 @@ def setUp(self): class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): + project = identity_fakes.FakeProject.create_one_project() + domain = identity_fakes.FakeDomain.create_one_domain() # The security group rule to be created. _security_group_rule = None @@ -103,19 +105,11 @@ def setUp(self): # Get a shortcut to the ProjectManager Mock self.projects_mock = self.identity.projects - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) + self.projects_mock.get.return_value = self.project # Get a shortcut to the DomainManager Mock self.domains_mock = self.identity.domains - self.domains_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.DOMAIN), - loaded=True, - ) + self.domains_mock.get.return_value = self.domain # Get the command object to test self.cmd = security_group_rule.CreateSecurityGroupRule( @@ -306,8 +300,8 @@ def test_create_network_options(self): '--dst-port', str(self._security_group_rule.port_range_min), '--egress', '--ethertype', self._security_group_rule.ethertype, - '--project', identity_fakes.project_name, - '--project-domain', identity_fakes.domain_name, + '--project', self.project.name, + '--project-domain', self.domain.name, '--protocol', self._security_group_rule.protocol, self._security_group.id, ] @@ -316,8 +310,8 @@ def test_create_network_options(self): self._security_group_rule.port_range_max)), ('egress', True), ('ethertype', self._security_group_rule.ethertype), - ('project', identity_fakes.project_name), - ('project_domain', identity_fakes.domain_name), + ('project', self.project.name), + ('project_domain', self.domain.name), ('protocol', self._security_group_rule.protocol), ('group', self._security_group.id), ] @@ -332,7 +326,7 @@ def test_create_network_options(self): 'port_range_min': self._security_group_rule.port_range_min, 'protocol': self._security_group_rule.protocol, 'security_group_id': self._security_group.id, - 'tenant_id': identity_fakes.project_id, + 'tenant_id': self.project.id, }) self.assertEqual(self.expected_columns, columns) self.assertEqual(self.expected_data, data) @@ -470,6 +464,8 @@ def test_create_icmpv6_type(self): class TestCreateSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): + project = identity_fakes.FakeProject.create_one_project() + domain = identity_fakes.FakeDomain.create_one_domain() # The security group rule to be created. _security_group_rule = None @@ -534,8 +530,8 @@ def test_create_network_options(self): '--ethertype', 'IPv4', '--icmp-type', '3', '--icmp-code', '11', - '--project', identity_fakes.project_name, - '--project-domain', identity_fakes.domain_name, + '--project', self.project.name, + '--project-domain', self.domain.name, self._security_group.id, ] self.assertRaises(tests_utils.ParserException, diff --git a/openstackclient/tests/network/v2/test_subnet.py b/openstackclient/tests/network/v2/test_subnet.py index 82813d6c10..46af44fb5b 100644 --- a/openstackclient/tests/network/v2/test_subnet.py +++ b/openstackclient/tests/network/v2/test_subnet.py @@ -11,7 +11,6 @@ # under the License. # -import copy import mock from mock import call @@ -36,10 +35,12 @@ def setUp(self): class TestCreateSubnet(TestSubnet): + project = identity_fakes_v3.FakeProject.create_one_project() + domain = identity_fakes_v3.FakeDomain.create_one_domain() # An IPv4 subnet to be created with mostly default values _subnet = network_fakes.FakeSubnet.create_one_subnet( attrs={ - 'tenant_id': identity_fakes_v3.project_id, + 'tenant_id': project.id, } ) @@ -49,7 +50,7 @@ class TestCreateSubnet(TestSubnet): # An IPv4 subnet to be created using a specific subnet pool _subnet_from_pool = network_fakes.FakeSubnet.create_one_subnet( attrs={ - 'tenant_id': identity_fakes_v3.project_id, + 'tenant_id': project.id, 'subnetpool_id': _subnet_pool.id, 'dns_nameservers': ['8.8.8.8', '8.8.4.4'], @@ -63,7 +64,7 @@ class TestCreateSubnet(TestSubnet): # An IPv6 subnet to be created with most options specified _subnet_ipv6 = network_fakes.FakeSubnet.create_one_subnet( attrs={ - 'tenant_id': identity_fakes_v3.project_id, + 'tenant_id': project.id, 'cidr': 'fe80:0:0:a00a::/64', 'enable_dhcp': True, 'dns_nameservers': ['fe80:27ff:a00a:f00f::ffff', @@ -187,19 +188,11 @@ def setUp(self): # Get a shortcut to the ProjectManager Mock self.projects_mock = self.identity.projects - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes_v3.PROJECT), - loaded=True, - ) + self.projects_mock.get.return_value = self.project # Get a shortcut to the DomainManager Mock self.domains_mock = self.identity.domains - self.domains_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes_v3.DOMAIN), - loaded=True, - ) + self.domains_mock.get.return_value = self.domain # Mock SDK calls for all tests. self.network.find_network = mock.Mock(return_value=self._network) diff --git a/openstackclient/tests/network/v2/test_subnet_pool.py b/openstackclient/tests/network/v2/test_subnet_pool.py index 41b6170f42..8269af0be7 100644 --- a/openstackclient/tests/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/network/v2/test_subnet_pool.py @@ -12,7 +12,6 @@ # import argparse -import copy import mock from mock import call @@ -37,6 +36,8 @@ def setUp(self): class TestCreateSubnetPool(TestSubnetPool): + project = identity_fakes_v3.FakeProject.create_one_project() + domain = identity_fakes_v3.FakeDomain.create_one_domain() # The new subnet pool to create. _subnet_pool = network_fakes.FakeSubnetPool.create_one_subnet_pool() @@ -93,19 +94,11 @@ def setUp(self): # Get a shortcut to the ProjectManager Mock self.projects_mock = self.identity.projects - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes_v3.PROJECT), - loaded=True, - ) + self.projects_mock.get.return_value = self.project # Get a shortcut to the DomainManager Mock self.domains_mock = self.identity.domains - self.domains_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes_v3.DOMAIN), - loaded=True, - ) + self.domains_mock.get.return_value = self.domain def test_create_no_options(self): arglist = [] @@ -191,14 +184,14 @@ def test_create_len_negative(self): def test_create_project_domain(self): arglist = [ '--pool-prefix', '10.0.10.0/24', - "--project", identity_fakes_v3.project_name, - "--project-domain", identity_fakes_v3.domain_name, + "--project", self.project.name, + "--project-domain", self.domain.name, self._subnet_pool.name, ] verifylist = [ ('prefixes', ['10.0.10.0/24']), - ('project', identity_fakes_v3.project_name), - ('project_domain', identity_fakes_v3.domain_name), + ('project', self.project.name), + ('project_domain', self.domain.name), ('name', self._subnet_pool.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -207,7 +200,7 @@ def test_create_project_domain(self): self.network.create_subnet_pool.assert_called_once_with(**{ 'prefixes': ['10.0.10.0/24'], - 'tenant_id': identity_fakes_v3.project_id, + 'tenant_id': self.project.id, 'name': self._subnet_pool.name, }) self.assertEqual(self.columns, columns) From 25bdf6811c71413921777cad73b6d039444600ff Mon Sep 17 00:00:00 2001 From: sunyajing Date: Mon, 13 Jun 2016 10:37:10 +0800 Subject: [PATCH 1093/3095] Modify compute agent set command Migrate ``compute agent set`` arguments: version, url, md5hash to be optional. BackwardsIncompatibleImpact Change-Id: I092b7ed24274bafa548f0537c4586504be3a2825 Co-Authored-By: Huanxuan Ao --- doc/source/backwards-incompatible.rst | 13 ++++ doc/source/command-objects/compute-agent.rst | 21 +++--- functional/tests/compute/v2/test_agent.py | 9 ++- openstackclient/compute/v2/agent.py | 36 +++++++-- .../tests/compute/v2/test_agent.py | 73 ++++++++++++++++--- ...fy-compute-agent-set-77ff894ef62ebbc7.yaml | 3 + 6 files changed, 125 insertions(+), 30 deletions(-) create mode 100644 releasenotes/notes/modify-compute-agent-set-77ff894ef62ebbc7.yaml diff --git a/doc/source/backwards-incompatible.rst b/doc/source/backwards-incompatible.rst index da3c1b6417..4f38aa540d 100644 --- a/doc/source/backwards-incompatible.rst +++ b/doc/source/backwards-incompatible.rst @@ -170,6 +170,19 @@ Releases Before 3.0 * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1546065 * Commit: https://review.openstack.org/#/c/281088/ +12. should be optional for command `openstack compute agent set` + + Previously, the command was `openstack compute agent set `, + whereas now it is: `openstack compute agent set --version + --url + --md5hash `. + + * In favor of: making optional. + * As of: NA + * Removed in: NA + * Bug: NA + * Commit: https://review.openstack.org/#/c/328819/ + 13. `aggregate set` commands will no longer return the modified resource Previously, modifying an aggregate would result in the new aggregate being diff --git a/doc/source/command-objects/compute-agent.rst b/doc/source/command-objects/compute-agent.rst index ae057dc96e..562947705f 100644 --- a/doc/source/command-objects/compute-agent.rst +++ b/doc/source/command-objects/compute-agent.rst @@ -80,21 +80,24 @@ Set compute agent properties .. code:: bash os compute agent set - + [--agent-version ] + [--url ] + .. _compute_agent-set: -.. describe:: +.. option:: --agent-version - ID of the agent + Version of the agent -.. describe:: +.. option:: --url - Version of the agent + URL of the agent -.. describe:: +.. option:: --md5hash - URL + MD5 hash of the agent -.. describe:: +.. describe:: - MD5 hash + Agent to modify (ID only) diff --git a/functional/tests/compute/v2/test_agent.py b/functional/tests/compute/v2/test_agent.py index 2d7ea21615..9622c1bfb1 100644 --- a/functional/tests/compute/v2/test_agent.py +++ b/functional/tests/compute/v2/test_agent.py @@ -64,10 +64,11 @@ def test_agent_set(self): url = "http://openstack" md5hash = hashlib.md5().hexdigest() - raw_output = self.openstack('compute agent set ' + - self.ID + ' ' + ver + ' ' + - url + ' ' + md5hash) - self.assertEqual('', raw_output) + self.openstack('compute agent set ' + + self.ID + + ' --agent-version ' + ver + + ' --url ' + url + + ' --md5hash ' + md5hash) raw_output = self.openstack('compute agent list') self.assertIn(self.ID, raw_output) diff --git a/openstackclient/compute/v2/agent.py b/openstackclient/compute/v2/agent.py index 4d92395511..76c1b3b756 100644 --- a/openstackclient/compute/v2/agent.py +++ b/openstackclient/compute/v2/agent.py @@ -152,28 +152,48 @@ def get_parser(self, prog_name): help=_("ID of the agent") ) parser.add_argument( - "version", + "--agent-version", + dest="version", metavar="", help=_("Version of the agent") ) parser.add_argument( - "url", + "--url", metavar="", - help=_("URL") + help=_("URL of the agent") ) parser.add_argument( - "md5hash", + "--md5hash", metavar="", - help=_("MD5 hash") + help=_("MD5 hash of the agent") ) return parser def take_action(self, parsed_args): compute_client = self.app.client_manager.compute + data = compute_client.agents.list(hypervisor=None) + agent = {} + + for s in data: + if s.agent_id == int(parsed_args.id): + agent['version'] = s.version + agent['url'] = s.url + agent['md5hash'] = s.md5hash + if agent == {}: + msg = _("No agent with a ID of '%(id)s' exists.") + raise exceptions.CommandError(msg % parsed_args.id) + + if parsed_args.version: + agent['version'] = parsed_args.version + if parsed_args.url: + agent['url'] = parsed_args.url + if parsed_args.md5hash: + agent['md5hash'] = parsed_args.md5hash + args = ( parsed_args.id, - parsed_args.version, - parsed_args.url, - parsed_args.md5hash + agent['version'], + agent['url'], + agent['md5hash'], ) compute_client.agents.update(*args) diff --git a/openstackclient/tests/compute/v2/test_agent.py b/openstackclient/tests/compute/v2/test_agent.py index da32972890..7695ee4107 100644 --- a/openstackclient/tests/compute/v2/test_agent.py +++ b/openstackclient/tests/compute/v2/test_agent.py @@ -25,7 +25,9 @@ class TestAgent(compute_fakes.TestComputev2): - fake_agent = compute_fakes.FakeAgent.create_one_agent() + attr = {} + attr['agent_id'] = 1 + fake_agent = compute_fakes.FakeAgent.create_one_agent(attr) columns = ( 'agent_id', @@ -238,21 +240,34 @@ def setUp(self): super(TestAgentSet, self).setUp() self.agents_mock.update.return_value = self.fake_agent + self.agents_mock.list.return_value = [self.fake_agent] self.cmd = agent.SetAgent(self.app, None) - def test_agent_set(self): + def test_agent_set_nothing(self): arglist = [ - 'id', - 'new-version', - 'new-url', - 'new-md5hash', + '1', + ] + verifylist = [ + ('id', '1'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.agents_mock.update.assert_called_with(parsed_args.id, + self.fake_agent.version, + self.fake_agent.url, + self.fake_agent.md5hash) + self.assertIsNone(result) + + def test_agent_set_version(self): + arglist = [ + '1', + '--agent-version', 'new-version', ] verifylist = [ - ('id', 'id'), + ('id', '1'), ('version', 'new-version'), - ('url', 'new-url'), - ('md5hash', 'new-md5hash'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -260,6 +275,46 @@ def test_agent_set(self): self.agents_mock.update.assert_called_with(parsed_args.id, parsed_args.version, + self.fake_agent.url, + self.fake_agent.md5hash) + self.assertIsNone(result) + + def test_agent_set_url(self): + arglist = [ + '1', + '--url', 'new-url', + ] + + verifylist = [ + ('id', '1'), + ('url', 'new-url'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.agents_mock.update.assert_called_with(parsed_args.id, + self.fake_agent.version, parsed_args.url, + self.fake_agent.md5hash) + self.assertIsNone(result) + + def test_agent_set_md5hash(self): + arglist = [ + '1', + '--md5hash', 'new-md5hash', + ] + + verifylist = [ + ('id', '1'), + ('md5hash', 'new-md5hash'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.agents_mock.update.assert_called_with(parsed_args.id, + self.fake_agent.version, + self.fake_agent.url, parsed_args.md5hash) self.assertIsNone(result) diff --git a/releasenotes/notes/modify-compute-agent-set-77ff894ef62ebbc7.yaml b/releasenotes/notes/modify-compute-agent-set-77ff894ef62ebbc7.yaml new file mode 100644 index 0000000000..28ab36241d --- /dev/null +++ b/releasenotes/notes/modify-compute-agent-set-77ff894ef62ebbc7.yaml @@ -0,0 +1,3 @@ +--- +upgrade: + - Migrate command ``compute agent set`` arguments to be optional. From 393ecedc3b162ad166d2be92485ec985e8f7ae0e Mon Sep 17 00:00:00 2001 From: Thomas Goirand Date: Mon, 11 Jul 2016 14:53:37 +0200 Subject: [PATCH 1094/3095] Make the doc build reproducible This fixes Debian bug: https://bugs.debian.org/826676 Change-Id: I638488448a4d6e644ab0e6deaebd65c92fa7904a --- doc/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 792ba40a41..a244ea09a1 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -80,7 +80,7 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = [] +exclude_patterns = ['**tests**'] # The reST default role (used for this markup: `text`) to use for all # documents. From 8405db900ff25e21015351f74fa5c61a6a247a1e Mon Sep 17 00:00:00 2001 From: Rajasi Kulkarni Date: Sun, 3 Jul 2016 15:05:19 +0530 Subject: [PATCH 1095/3095] Pass security group id to novaclient In RemoveServerSecurityGroup we currently pass the entire security group object, which results in TypeError in novaclient. Added unit test case to test command 'openstack server remove security group -h ' Change-Id: I6d486403a83804c3a30d6f89d2cf7f64f09797c6 Closes-Bug: 1590883 --- openstackclient/compute/v2/server.py | 2 +- .../tests/compute/v2/test_server.py | 53 +++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 7d04ffc3a0..a317c11d11 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1222,7 +1222,7 @@ def take_action(self, parsed_args): parsed_args.group, ) - server.remove_security_group(security_group) + server.remove_security_group(security_group.id) class RemoveServerVolume(command.Command): diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 24f92e4676..1c5a5fe462 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -43,6 +43,11 @@ def setUp(self): self.flavors_mock = self.app.client_manager.compute.flavors self.flavors_mock.reset_mock() + # Get a shortcut to the compute client SecurityGroupManager Mock + self.security_groups_mock = \ + self.app.client_manager.compute.security_groups + self.security_groups_mock.reset_mock() + # Get a shortcut to the image client ImageManager Mock self.images_mock = self.app.client_manager.image.images self.images_mock.reset_mock() @@ -981,6 +986,54 @@ def test_server_remove_floating_ip(self): self.assertIsNone(result) +class TestServerRemoveSecurityGroup(TestServer): + + def setUp(self): + super(TestServerRemoveSecurityGroup, self).setUp() + + self.security_group = \ + compute_fakes.FakeSecurityGroup.create_one_security_group() + # This is the return value for utils.find_resource() for security group + self.security_groups_mock.get.return_value = self.security_group + + attrs = { + 'security_groups': [{'name': self.security_group.id}] + } + methods = { + 'remove_security_group': None, + } + + self.server = compute_fakes.FakeServer.create_one_server( + attrs=attrs, + methods=methods + ) + # This is the return value for utils.find_resource() for server + self.servers_mock.get.return_value = self.server + + # Get the command object to test + self.cmd = server.RemoveServerSecurityGroup(self.app, None) + + def test_server_remove_security_group(self): + arglist = [ + self.server.id, + self.security_group.id + ] + verifylist = [ + ('server', self.server.id), + ('group', self.security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.security_groups_mock.get.assert_called_with( + self.security_group.id, + ) + self.servers_mock.get.assert_called_with(self.server.id) + self.server.remove_security_group.assert_called_with( + self.security_group.id, + ) + self.assertIsNone(result) + + class TestServerResize(TestServer): def setUp(self): From 34812655a5b44623a04fc05f1a6c9110f4afbc25 Mon Sep 17 00:00:00 2001 From: Paul Bourke Date: Fri, 8 Jul 2016 11:25:25 +0000 Subject: [PATCH 1096/3095] Add "--incremental" option to "backup create" command in volume v2 Cinder V2 API supports creating volume backup with "--incremental" option. However, OSC doesn't support this argument. So this patch adds the "--incremental" option to allow users to create incremental style backups. Change-Id: Iefac5f1a6c9ef006ad9c22d4250ae6df50504781 Closes-Bug: 1600196 --- doc/source/command-objects/backup.rst | 7 +++++++ openstackclient/tests/volume/v2/test_backup.py | 4 ++++ openstackclient/volume/v2/backup.py | 7 +++++++ releasenotes/notes/bug-1600196-6a733dd4e3371df7.yaml | 6 ++++++ 4 files changed, 24 insertions(+) create mode 100644 releasenotes/notes/bug-1600196-6a733dd4e3371df7.yaml diff --git a/doc/source/command-objects/backup.rst b/doc/source/command-objects/backup.rst index ee100b1b2d..4abc155fe8 100644 --- a/doc/source/command-objects/backup.rst +++ b/doc/source/command-objects/backup.rst @@ -18,6 +18,7 @@ Create new backup [--description ] [--snapshot ] [--force] + [--incremental] .. option:: --container @@ -44,6 +45,12 @@ Create new backup *Volume version 2 only* +.. option:: --incremental + + Perform an incremental backup + + *Volume version 2 only* + .. _backup_create-backup: .. describe:: diff --git a/openstackclient/tests/volume/v2/test_backup.py b/openstackclient/tests/volume/v2/test_backup.py index ce26a2d6f0..31bfa64cab 100644 --- a/openstackclient/tests/volume/v2/test_backup.py +++ b/openstackclient/tests/volume/v2/test_backup.py @@ -79,6 +79,7 @@ def test_backup_create(self): "--description", self.new_backup.description, "--container", self.new_backup.container, "--force", + "--incremental", "--snapshot", self.new_backup.snapshot_id, self.new_backup.volume_id, ] @@ -87,6 +88,7 @@ def test_backup_create(self): ("description", self.new_backup.description), ("container", self.new_backup.container), ("force", True), + ("incremental", True), ("snapshot", self.new_backup.snapshot_id), ("volume", self.new_backup.volume_id), ] @@ -100,6 +102,7 @@ def test_backup_create(self): name=self.new_backup.name, description=self.new_backup.description, force=True, + incremental=True, snapshot_id=self.new_backup.snapshot_id, ) self.assertEqual(self.columns, columns) @@ -126,6 +129,7 @@ def test_backup_create_without_name(self): name=None, description=self.new_backup.description, force=False, + incremental=False, snapshot_id=None, ) self.assertEqual(self.columns, columns) diff --git a/openstackclient/volume/v2/backup.py b/openstackclient/volume/v2/backup.py index fed5f09e2d..49226bccca 100644 --- a/openstackclient/volume/v2/backup.py +++ b/openstackclient/volume/v2/backup.py @@ -59,6 +59,12 @@ def get_parser(self, prog_name): default=False, help=_("Allow to back up an in-use volume") ) + parser.add_argument( + '--incremental', + action='store_true', + default=False, + help=_("Perform an incremental backup") + ) return parser def take_action(self, parsed_args): @@ -75,6 +81,7 @@ def take_action(self, parsed_args): name=parsed_args.name, description=parsed_args.description, force=parsed_args.force, + incremental=parsed_args.incremental, snapshot_id=snapshot_id, ) backup._info.pop("links", None) diff --git a/releasenotes/notes/bug-1600196-6a733dd4e3371df7.yaml b/releasenotes/notes/bug-1600196-6a733dd4e3371df7.yaml new file mode 100644 index 0000000000..16c125fb5c --- /dev/null +++ b/releasenotes/notes/bug-1600196-6a733dd4e3371df7.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--incremental`` option to ``backup create`` command to allow users to + create incremental backups. + [Bug `1600196 `_] From e40e1f699085a1f8cd60300e96502d9eebc66725 Mon Sep 17 00:00:00 2001 From: zheng yin Date: Tue, 12 Jul 2016 03:01:37 +0800 Subject: [PATCH 1097/3095] Add Python3.5 to setup.cfg tox.ini Now that there is a passing gate job, we can claim support for Python 3.5 in the classifier. This patch also adds the convenience py35 venv. Change-Id: If93af96739741584c87913ba140d0c6cee2aa10d --- setup.cfg | 1 + tox.ini | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index dbe9c6f953..0dd368a381 100644 --- a/setup.cfg +++ b/setup.cfg @@ -17,6 +17,7 @@ classifier = Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.4 + Programming Language :: Python :: 3.5 [files] packages = diff --git a/tox.ini b/tox.ini index 6dfa126d1a..4afb8c2077 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] minversion = 1.6 -envlist = py34,py27,pep8 +envlist = py35,py34,py27,pep8 skipdist = True [testenv] From 8bbf30498eb34990cb9bc7c7af418216d47f0421 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Thu, 7 Jul 2016 11:18:50 -0700 Subject: [PATCH 1098/3095] image list: Add Checksum column The checksum could potentially be useful for spotting duplicated images or checking if images with the same name are identical or different. Closes-Bug: #1602073 Change-Id: Ia0c41970c846d550de14297e18bc738e847e5a3b --- openstackclient/image/v1/image.py | 2 ++ openstackclient/image/v2/image.py | 2 ++ openstackclient/tests/image/v1/test_image.py | 2 ++ openstackclient/tests/image/v2/test_image.py | 2 ++ releasenotes/notes/bug_1602073-5deb58deeafbc8be.yaml | 4 ++++ 5 files changed, 12 insertions(+) create mode 100644 releasenotes/notes/bug_1602073-5deb58deeafbc8be.yaml diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index e7b60e5ad5..5f669c6412 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -378,6 +378,7 @@ def take_action(self, parsed_args): 'Disk Format', 'Container Format', 'Size', + 'Checksum', 'Status', 'is_public', 'protected', @@ -390,6 +391,7 @@ def take_action(self, parsed_args): 'Disk Format', 'Container Format', 'Size', + 'Checksum', 'Status', 'Visibility', 'Protected', diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 64092fea74..0712e09c07 100755 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -490,6 +490,7 @@ def take_action(self, parsed_args): 'Disk Format', 'Container Format', 'Size', + 'Checksum', 'Status', 'visibility', 'protected', @@ -502,6 +503,7 @@ def take_action(self, parsed_args): 'Disk Format', 'Container Format', 'Size', + 'Checksum', 'Status', 'Visibility', 'Protected', diff --git a/openstackclient/tests/image/v1/test_image.py b/openstackclient/tests/image/v1/test_image.py index 14aa331fbc..cf08d138fa 100644 --- a/openstackclient/tests/image/v1/test_image.py +++ b/openstackclient/tests/image/v1/test_image.py @@ -363,6 +363,7 @@ def test_image_list_long_option(self): 'Disk Format', 'Container Format', 'Size', + 'Checksum', 'Status', 'Visibility', 'Protected', @@ -378,6 +379,7 @@ def test_image_list_long_option(self): '', '', '', + '', 'public', False, image_fakes.image_owner, diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index 592def21ef..763f71e290 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -643,6 +643,7 @@ def test_image_list_long_option(self): 'Disk Format', 'Container Format', 'Size', + 'Checksum', 'Status', 'Visibility', 'Protected', @@ -658,6 +659,7 @@ def test_image_list_long_option(self): '', '', '', + '', self._image.visibility, self._image.protected, self._image.owner, diff --git a/releasenotes/notes/bug_1602073-5deb58deeafbc8be.yaml b/releasenotes/notes/bug_1602073-5deb58deeafbc8be.yaml new file mode 100644 index 0000000000..be95966f8e --- /dev/null +++ b/releasenotes/notes/bug_1602073-5deb58deeafbc8be.yaml @@ -0,0 +1,4 @@ +--- +features: + - Add "Checksum" column to output of "image list --long" + [Bug `1602073 `_] From 536c0d9dea12b148b379d795c174a1eee8cdc0bd Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Tue, 12 Jul 2016 15:23:47 +0800 Subject: [PATCH 1099/3095] Modify some help and error messages in ec2creds identityv2 Usually we use "(s)" to show about multi deletion in help message. In addition, I think "EC2 credentials" is better than "EC2 keys" in the error message. Change-Id: I6a6461291542701d87a55d9ea0ea1fda6db04601 --- openstackclient/identity/v2_0/ec2creds.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openstackclient/identity/v2_0/ec2creds.py b/openstackclient/identity/v2_0/ec2creds.py index 058b5772d8..4873cd55fd 100644 --- a/openstackclient/identity/v2_0/ec2creds.py +++ b/openstackclient/identity/v2_0/ec2creds.py @@ -94,7 +94,7 @@ def get_parser(self, prog_name): 'access_keys', metavar='', nargs='+', - help=_('Credentials access keys'), + help=_('Credentials access key(s)'), ) parser.add_argument( '--user', @@ -121,7 +121,7 @@ def take_action(self, parsed_args): identity_client.ec2.delete(user, access_key) except Exception as e: result += 1 - LOG.error(_("Failed to delete EC2 keys with " + LOG.error(_("Failed to delete EC2 credentials with " "access key '%(access_key)s': %(e)s") % {'access_key': access_key, 'e': e}) From f79f6713384bbe5620faba40210850fb2b7f3972 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Tue, 12 Jul 2016 16:51:21 +0800 Subject: [PATCH 1100/3095] Change the wrong import order Change-Id: Ia48f0aae0250a5d29c0f6fa46386465e118f760f --- openstackclient/tests/volume/v2/test_qos_specs.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openstackclient/tests/volume/v2/test_qos_specs.py b/openstackclient/tests/volume/v2/test_qos_specs.py index 92ffca74ee..da0e8ba5b4 100644 --- a/openstackclient/tests/volume/v2/test_qos_specs.py +++ b/openstackclient/tests/volume/v2/test_qos_specs.py @@ -13,9 +13,10 @@ # under the License. # +from osc_lib import utils + from openstackclient.tests.volume.v2 import fakes as volume_fakes from openstackclient.volume.v2 import qos_specs -from osc_lib import utils class TestQos(volume_fakes.TestVolume): From e310682235810759c17278365fcb76fac438f582 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Tue, 12 Jul 2016 19:23:47 +0800 Subject: [PATCH 1101/3095] Add "--project" option to "volume type create" command Add "--project" and "--project-domain" options to "volume type create" command. We can use these options to add the type access to a given project when we create the volume type. Change-Id: I483a6b61dae137682c3d1f7527531b40e508ba92 Closes-Bug: #1602169 --- doc/source/command-objects/volume-type.rst | 16 +++++++++++ openstackclient/tests/volume/v2/test_type.py | 20 ++++++++++++++ openstackclient/volume/v2/volume_type.py | 27 ++++++++++++++++++- .../notes/bug-1602169-2750c9a6896d2825.yaml | 5 ++++ 4 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/bug-1602169-2750c9a6896d2825.yaml diff --git a/doc/source/command-objects/volume-type.rst b/doc/source/command-objects/volume-type.rst index dfc169cd14..e2a277b04a 100644 --- a/doc/source/command-objects/volume-type.rst +++ b/doc/source/command-objects/volume-type.rst @@ -16,6 +16,8 @@ Create new volume type [--description ] [--public | --private] [--property [...] ] + [--project ] + [--project-domain ] .. option:: --description @@ -40,6 +42,20 @@ Create new volume type Set a property on this volume type (repeat option to set multiple properties) +.. option:: --project + + Allow to access private type (name or ID) + (Must be used with :option:`--private` option) + + *Volume version 2 only* + +.. option:: --project-domain + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + + *Volume version 2 only* + .. _volume_type_create-name: .. describe:: diff --git a/openstackclient/tests/volume/v2/test_type.py b/openstackclient/tests/volume/v2/test_type.py index 174f33f2fb..a7db2e49e0 100644 --- a/openstackclient/tests/volume/v2/test_type.py +++ b/openstackclient/tests/volume/v2/test_type.py @@ -14,6 +14,7 @@ import copy +from osc_lib import exceptions from osc_lib import utils from openstackclient.tests import fakes @@ -41,6 +42,7 @@ def setUp(self): class TestTypeCreate(TestType): + project = identity_fakes.FakeProject.create_one_project() columns = ( 'description', 'id', @@ -58,6 +60,7 @@ def setUp(self): ) self.types_mock.create.return_value = self.new_volume_type + self.projects_mock.get.return_value = self.project # Get the command object to test self.cmd = volume_type.CreateVolumeType(self.app, None) @@ -89,12 +92,14 @@ def test_type_create_private(self): arglist = [ "--description", self.new_volume_type.description, "--private", + "--project", self.project.id, self.new_volume_type.name, ] verifylist = [ ("description", self.new_volume_type.description), ("public", False), ("private", True), + ("project", self.project.id), ("name", self.new_volume_type.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -109,6 +114,21 @@ def test_type_create_private(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_public_type_create_with_project(self): + arglist = [ + '--project', self.project.id, + self.new_volume_type.name, + ] + verifylist = [ + ('project', self.project.id), + ('name', self.new_volume_type.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, + parsed_args) + class TestTypeDelete(TestType): diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index 87f4c5472a..a1cd8bb5ac 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -66,12 +66,23 @@ def get_parser(self, prog_name): help=_('Set a property on this volume type ' '(repeat option to set multiple properties)'), ) + parser.add_argument( + '--project', + metavar='', + help=_("Allow to access private type (name or ID) " + "(Must be used with --private option)"), + ) + identity_common.add_project_domain_option_to_parser(parser) return parser def take_action(self, parsed_args): - + identity_client = self.app.client_manager.identity volume_client = self.app.client_manager.volume + if parsed_args.project and not parsed_args.private: + msg = _("--project is only allowed with --private") + raise exceptions.CommandError(msg) + kwargs = {} if parsed_args.public: kwargs['is_public'] = True @@ -84,6 +95,20 @@ def take_action(self, parsed_args): **kwargs ) volume_type._info.pop('extra_specs') + + if parsed_args.project: + try: + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + volume_client.volume_type_access.add_project_access( + volume_type.id, project_id) + except Exception as e: + msg = _("Failed to add project %(project)s access to " + "type: %(e)s") + LOG.error(msg % {'project': parsed_args.project, 'e': e}) if parsed_args.property: result = volume_type.set_keys(parsed_args.property) volume_type._info.update({'properties': utils.format_dict(result)}) diff --git a/releasenotes/notes/bug-1602169-2750c9a6896d2825.yaml b/releasenotes/notes/bug-1602169-2750c9a6896d2825.yaml new file mode 100644 index 0000000000..1ccd68fda8 --- /dev/null +++ b/releasenotes/notes/bug-1602169-2750c9a6896d2825.yaml @@ -0,0 +1,5 @@ +--- +features: + - Add ``--project`` and ``--project-domain`` options to ``volume type create`` + command. + [Bug `1602169 `_] From 067647b6a40b21e19f3faf3a4fc6188b8c3129b1 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 13 Jul 2016 10:30:51 -0500 Subject: [PATCH 1102/3095] Temp work around for missing select_auth_plugin() These were removed prematurely from osc-lib (by me) but the real fix in https://review.openstack.org/329189 is having racy functional test issues that may be related to osc-lib, so let's clear this up while we fix that... Change-Id: I8f67466967751fdf6fd24ae1b16ccee2aec52323 --- openstackclient/common/clientmanager.py | 76 ++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 2 deletions(-) diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index 13c6b92cfa..ec005dc092 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -20,6 +20,7 @@ import pkg_resources import sys +from keystoneauth1.loading import base from osc_lib.api import auth from osc_lib import exceptions from oslo_utils import strutils @@ -37,6 +38,77 @@ USER_AGENT = 'python-openstackclient' +# NOTE(dtroyer): Bringing back select_auth_plugin() and build_auth_params() +# temporarily because osc-lib 0.3.0 removed it a wee bit early +def select_auth_plugin(options): + """Pick an auth plugin based on --os-auth-type or other options""" + + auth_plugin_name = None + + # Do the token/url check first as this must override the default + # 'password' set by os-client-config + # Also, url and token are not copied into o-c-c's auth dict (yet?) + if options.auth.get('url') and options.auth.get('token'): + # service token authentication + auth_plugin_name = 'token_endpoint' + elif options.auth_type in auth.PLUGIN_LIST: + # A direct plugin name was given, use it + auth_plugin_name = options.auth_type + elif options.auth.get('username'): + if options.identity_api_version == '3': + auth_plugin_name = 'v3password' + elif options.identity_api_version.startswith('2'): + auth_plugin_name = 'v2password' + else: + # let keystoneauth figure it out itself + auth_plugin_name = 'password' + elif options.auth.get('token'): + if options.identity_api_version == '3': + auth_plugin_name = 'v3token' + elif options.identity_api_version.startswith('2'): + auth_plugin_name = 'v2token' + else: + # let keystoneauth figure it out itself + auth_plugin_name = 'token' + else: + # The ultimate default is similar to the original behaviour, + # but this time with version discovery + auth_plugin_name = 'password' + LOG.debug("Auth plugin %s selected", auth_plugin_name) + return auth_plugin_name + + +def build_auth_params(auth_plugin_name, cmd_options): + if auth_plugin_name: + LOG.debug('auth_type: %s', auth_plugin_name) + auth_plugin_loader = base.get_plugin_loader(auth_plugin_name) + auth_params = { + opt.dest: opt.default + for opt in base.get_plugin_options(auth_plugin_name) + } + auth_params.update(dict(cmd_options.auth)) + # grab tenant from project for v2.0 API compatibility + if auth_plugin_name.startswith("v2"): + if 'project_id' in auth_params: + auth_params['tenant_id'] = auth_params['project_id'] + del auth_params['project_id'] + if 'project_name' in auth_params: + auth_params['tenant_name'] = auth_params['project_name'] + del auth_params['project_name'] + else: + LOG.debug('no auth_type') + # delay the plugin choice, grab every option + auth_plugin_loader = None + auth_params = dict(cmd_options.auth) + plugin_options = set( + [o.replace('-', '_') for o in auth.get_options_list()] + ) + for option in plugin_options: + LOG.debug('fetching option %s', option) + auth_params[option] = getattr(cmd_options.auth, option, None) + return (auth_plugin_loader, auth_params) + + class ClientCache(object): """Descriptor class for caching created client handles.""" @@ -193,7 +265,7 @@ def setup_auth(self): # If no auth type is named by the user, select one based on # the supplied options - self.auth_plugin_name = auth.select_auth_plugin(self._cli_options) + self.auth_plugin_name = select_auth_plugin(self._cli_options) # Basic option checking to avoid unhelpful error messages auth.check_valid_authentication_options(self._cli_options, @@ -205,7 +277,7 @@ def setup_auth(self): not self._cli_options.auth.get('password')): self._cli_options.auth['password'] = self._pw_callback() - (auth_plugin, self._auth_params) = auth.build_auth_params( + (auth_plugin, self._auth_params) = build_auth_params( self.auth_plugin_name, self._cli_options, ) From e5a3c403e5982df8ab6a29d84c45aa2f8f3a10dc Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Wed, 13 Jul 2016 19:28:11 +0800 Subject: [PATCH 1103/3095] Make set/unset commands pass normally when nothing specified in identityv3 Change-Id: I554b41969f96b62a2c6d37024caa56b1441d5ed1 Partial-bug: #1588588 --- openstackclient/identity/v3/consumer.py | 6 ------ openstackclient/identity/v3/domain.py | 4 ---- openstackclient/identity/v3/endpoint.py | 8 -------- openstackclient/identity/v3/federation_protocol.py | 4 ---- openstackclient/identity/v3/group.py | 3 --- openstackclient/identity/v3/identity_provider.py | 8 -------- openstackclient/identity/v3/mapping.py | 4 ---- openstackclient/identity/v3/policy.py | 5 ----- openstackclient/identity/v3/project.py | 7 ------- openstackclient/identity/v3/region.py | 2 -- openstackclient/identity/v3/role.py | 4 ---- openstackclient/identity/v3/service.py | 10 ---------- openstackclient/identity/v3/service_provider.py | 9 --------- openstackclient/identity/v3/user.py | 13 ------------- openstackclient/tests/identity/v3/test_domain.py | 6 +++++- openstackclient/tests/identity/v3/test_endpoint.py | 12 +++++++++++- .../tests/identity/v3/test_identity_provider.py | 4 ++-- openstackclient/tests/identity/v3/test_region.py | 6 +++++- .../tests/identity/v3/test_service_provider.py | 11 +++++++++-- 19 files changed, 32 insertions(+), 94 deletions(-) diff --git a/openstackclient/identity/v3/consumer.py b/openstackclient/identity/v3/consumer.py index a4620bf9ef..65bf657feb 100644 --- a/openstackclient/identity/v3/consumer.py +++ b/openstackclient/identity/v3/consumer.py @@ -15,8 +15,6 @@ """Identity v3 Consumer action implementations""" -import sys - from osc_lib.command import command from osc_lib import utils import six @@ -102,10 +100,6 @@ def take_action(self, parsed_args): if parsed_args.description: kwargs['description'] = parsed_args.description - if not len(kwargs): - sys.stdout.write(_('Consumer not updated, no arguments present\n')) - return - consumer = identity_client.oauth1.consumers.update( consumer.id, **kwargs) diff --git a/openstackclient/identity/v3/domain.py b/openstackclient/identity/v3/domain.py index 0546ac5217..3e9bcf6344 100755 --- a/openstackclient/identity/v3/domain.py +++ b/openstackclient/identity/v3/domain.py @@ -16,7 +16,6 @@ """Identity v3 Domain action implementations""" import logging -import sys from keystoneauth1 import exceptions as ks_exc from osc_lib.command import command @@ -168,9 +167,6 @@ def take_action(self, parsed_args): if parsed_args.disable: kwargs['enabled'] = False - if not kwargs: - sys.stdout.write(_("Domain not updated, no arguments present\n")) - return identity_client.domains.update(domain.id, **kwargs) diff --git a/openstackclient/identity/v3/endpoint.py b/openstackclient/identity/v3/endpoint.py index 2f1cc9f3ce..bd2df361d1 100644 --- a/openstackclient/identity/v3/endpoint.py +++ b/openstackclient/identity/v3/endpoint.py @@ -15,8 +15,6 @@ """Identity v3 Endpoint action implementations""" -import sys - from osc_lib.command import command from osc_lib import utils import six @@ -212,12 +210,6 @@ def take_action(self, parsed_args): endpoint = utils.find_resource(identity_client.endpoints, parsed_args.endpoint) - if (not parsed_args.interface and not parsed_args.url - and not parsed_args.service and not parsed_args.region - and not parsed_args.enabled and not parsed_args.disabled): - sys.stdout.write(_("Endpoint not updated, no arguments present\n")) - return - service_id = None if parsed_args.service: service = common.find_service(identity_client, parsed_args.service) diff --git a/openstackclient/identity/v3/federation_protocol.py b/openstackclient/identity/v3/federation_protocol.py index 094802455a..0369bc3d06 100644 --- a/openstackclient/identity/v3/federation_protocol.py +++ b/openstackclient/identity/v3/federation_protocol.py @@ -149,10 +149,6 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - if not parsed_args.mapping: - LOG.error(_("No changes requested")) - return - protocol = identity_client.federation.protocols.update( parsed_args.identity_provider, parsed_args.federation_protocol, parsed_args.mapping) diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py index c79a64e782..f780810afa 100755 --- a/openstackclient/identity/v3/group.py +++ b/openstackclient/identity/v3/group.py @@ -343,9 +343,6 @@ def take_action(self, parsed_args): if parsed_args.description: kwargs['description'] = parsed_args.description - if not len(kwargs): - sys.stderr.write("Group not updated, no arguments present\n") - return identity_client.groups.update(group.id, **kwargs) diff --git a/openstackclient/identity/v3/identity_provider.py b/openstackclient/identity/v3/identity_provider.py index 5c638f9b6b..6fc9b13ce2 100644 --- a/openstackclient/identity/v3/identity_provider.py +++ b/openstackclient/identity/v3/identity_provider.py @@ -169,14 +169,6 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): federation_client = self.app.client_manager.identity.federation - # Basic argument checking - if (not parsed_args.enable and not parsed_args.disable and - not parsed_args.remote_id and - not parsed_args.remote_id_file and - not parsed_args.description): - LOG.error(_('No changes requested')) - return (None, None) - # Always set remote_ids if either is passed in if parsed_args.remote_id_file: file_content = utils.read_blob_file_contents( diff --git a/openstackclient/identity/v3/mapping.py b/openstackclient/identity/v3/mapping.py index 74ead2281a..69c141b128 100644 --- a/openstackclient/identity/v3/mapping.py +++ b/openstackclient/identity/v3/mapping.py @@ -162,10 +162,6 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - if not parsed_args.rules: - LOG.error(_("No changes requested")) - return - rules = self._read_rules(parsed_args.rules) mapping = identity_client.federation.mappings.update( diff --git a/openstackclient/identity/v3/policy.py b/openstackclient/identity/v3/policy.py index 68fb273820..79215cab42 100644 --- a/openstackclient/identity/v3/policy.py +++ b/openstackclient/identity/v3/policy.py @@ -15,8 +15,6 @@ """Identity v3 Policy action implementations""" -import sys - from osc_lib.command import command from osc_lib import utils import six @@ -136,9 +134,6 @@ def take_action(self, parsed_args): if parsed_args.type: kwargs['type'] = parsed_args.type - if not kwargs: - sys.stdout.write(_('Policy not updated, no arguments present\n')) - return identity_client.policies.update(parsed_args.policy, **kwargs) diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index 5398701704..56c4fbc8b2 100755 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -263,13 +263,6 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - if (not parsed_args.name - and not parsed_args.domain - and not parsed_args.description - and not parsed_args.enable - and not parsed_args.property - and not parsed_args.disable): - return project = common.find_project(identity_client, parsed_args.project, parsed_args.domain) diff --git a/openstackclient/identity/v3/region.py b/openstackclient/identity/v3/region.py index b5e46a9adb..d714cd05e4 100644 --- a/openstackclient/identity/v3/region.py +++ b/openstackclient/identity/v3/region.py @@ -132,8 +132,6 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - if not parsed_args.parent_region and not parsed_args.description: - return kwargs = {} if parsed_args.description: kwargs['description'] = parsed_args.description diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index d3c530a502..273801799d 100755 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -357,10 +357,6 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - if not parsed_args.name: - sys.stderr.write(_("Incorrect set of arguments provided. " - "See openstack --help for more details\n")) - return role = utils.find_resource( identity_client.roles, parsed_args.role, diff --git a/openstackclient/identity/v3/service.py b/openstackclient/identity/v3/service.py index 195b2701fb..7b23ae299c 100644 --- a/openstackclient/identity/v3/service.py +++ b/openstackclient/identity/v3/service.py @@ -15,8 +15,6 @@ """Identity v3 Service action implementations""" -import sys - from osc_lib.command import command from osc_lib import utils import six @@ -163,14 +161,6 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - if (not parsed_args.name - and not parsed_args.type - and not parsed_args.description - and not parsed_args.enable - and not parsed_args.disable): - sys.stderr.write(_("Incorrect set of arguments provided. " - "See openstack --help for more details\n")) - return service = common.find_service(identity_client, parsed_args.service) kwargs = {} diff --git a/openstackclient/identity/v3/service_provider.py b/openstackclient/identity/v3/service_provider.py index 0beea81379..1f95def81f 100644 --- a/openstackclient/identity/v3/service_provider.py +++ b/openstackclient/identity/v3/service_provider.py @@ -13,8 +13,6 @@ """Service Provider action implementations""" -import sys - from osc_lib.command import command from osc_lib import utils import six @@ -164,13 +162,6 @@ def take_action(self, parsed_args): elif parsed_args.disable is True: enabled = False - if not any((enabled is not None, parsed_args.description, - parsed_args.service_provider_url, - parsed_args.auth_url)): - sys.stdout.write(_("Service Provider not updated, no arguments " - "present\n")) - return (None, None) - service_provider = federation_client.service_providers.update( parsed_args.service_provider, enabled=enabled, description=parsed_args.description, diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index 3e189ac1c6..dc47ef8d54 100755 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -17,7 +17,6 @@ import copy import logging -import sys from keystoneauth1 import exceptions as ks_exc from osc_lib.command import command @@ -330,18 +329,6 @@ def take_action(self, parsed_args): if parsed_args.password_prompt: parsed_args.password = utils.get_password(self.app.stdin) - if (not parsed_args.name - and not parsed_args.name - and not parsed_args.password - and not parsed_args.email - and not parsed_args.project - and not parsed_args.description - and not parsed_args.enable - and not parsed_args.disable): - sys.stderr.write(_("Incorrect set of arguments provided. " - "See openstack --help for more details\n")) - return - user = utils.find_resource( identity_client.users, parsed_args.user, diff --git a/openstackclient/tests/identity/v3/test_domain.py b/openstackclient/tests/identity/v3/test_domain.py index 9229ddd0a9..17bcee065d 100644 --- a/openstackclient/tests/identity/v3/test_domain.py +++ b/openstackclient/tests/identity/v3/test_domain.py @@ -253,7 +253,11 @@ def test_domain_set_no_options(self): result = self.cmd.take_action(parsed_args) - self.assertNotCalled(self.domains_mock.update) + kwargs = {} + self.domains_mock.update.assert_called_with( + self.domain.id, + **kwargs + ) self.assertIsNone(result) def test_domain_set_name(self): diff --git a/openstackclient/tests/identity/v3/test_endpoint.py b/openstackclient/tests/identity/v3/test_endpoint.py index d953459cae..184e14a4c5 100644 --- a/openstackclient/tests/identity/v3/test_endpoint.py +++ b/openstackclient/tests/identity/v3/test_endpoint.py @@ -485,7 +485,17 @@ def test_endpoint_set_no_options(self): result = self.cmd.take_action(parsed_args) - self.assertNotCalled(self.endpoints_mock.update) + kwargs = { + 'enabled': None, + 'interface': None, + 'region': None, + 'service': None, + 'url': None, + } + self.endpoints_mock.update.assert_called_with( + identity_fakes.endpoint_id, + **kwargs + ) self.assertIsNone(result) def test_endpoint_set_interface(self): diff --git a/openstackclient/tests/identity/v3/test_identity_provider.py b/openstackclient/tests/identity/v3/test_identity_provider.py index 161a1a1b0d..8561fab99b 100644 --- a/openstackclient/tests/identity/v3/test_identity_provider.py +++ b/openstackclient/tests/identity/v3/test_identity_provider.py @@ -585,8 +585,8 @@ def prepare(self): # expect take_action() to return (None, None) as # neither --enable nor --disable was specified - self.assertIsNone(columns) - self.assertIsNone(data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) class TestIdentityProviderShow(TestIdentityProvider): diff --git a/openstackclient/tests/identity/v3/test_region.py b/openstackclient/tests/identity/v3/test_region.py index 02dec5681c..44e4814b01 100644 --- a/openstackclient/tests/identity/v3/test_region.py +++ b/openstackclient/tests/identity/v3/test_region.py @@ -253,7 +253,11 @@ def test_region_set_no_options(self): result = self.cmd.take_action(parsed_args) - self.assertNotCalled(self.regions_mock.update) + kwargs = {} + self.regions_mock.update.assert_called_with( + identity_fakes.region_id, + **kwargs + ) self.assertIsNone(result) def test_region_set_description(self): diff --git a/openstackclient/tests/identity/v3/test_service_provider.py b/openstackclient/tests/identity/v3/test_service_provider.py index 1d82cd1532..428057949b 100644 --- a/openstackclient/tests/identity/v3/test_service_provider.py +++ b/openstackclient/tests/identity/v3/test_service_provider.py @@ -377,8 +377,15 @@ def prepare(self): # expect take_action() to return (None, None) as none of --disabled, # --enabled, --description, --service-provider-url, --auth_url option # was set. - self.assertIsNone(columns) - self.assertIsNone(data) + self.assertEqual(self.columns, columns) + datalist = ( + service_fakes.sp_auth_url, + service_fakes.sp_description, + True, + service_fakes.sp_id, + service_fakes.service_provider_url + ) + self.assertEqual(datalist, data) class TestServiceProviderShow(TestServiceProvider): From 9b51127ecc85e9814b6256180ef612c857156559 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Tue, 12 Jul 2016 15:53:58 +0800 Subject: [PATCH 1104/3095] Support error handling for delete commands in volumev2 Some delete conmmands in volumev2 did not support error handling, this patch add them and also add the unit tests for bulk deletion Change-Id: I56ade6f9c7396c78fb989547476c4d94ccd76eae --- openstackclient/tests/volume/v2/fakes.py | 60 ++++++++++++++++ .../tests/volume/v2/test_backup.py | 70 ++++++++++++++++--- .../tests/volume/v2/test_qos_specs.py | 69 +++++++++++++++--- .../tests/volume/v2/test_snapshot.py | 63 +++++++++++++++-- .../tests/volume/v2/test_volume.py | 33 ++++++++- openstackclient/volume/v2/backup.py | 27 +++++-- openstackclient/volume/v2/qos_specs.py | 26 ++++++- openstackclient/volume/v2/snapshot.py | 27 +++++-- openstackclient/volume/v2/volume.py | 29 ++++++-- 9 files changed, 364 insertions(+), 40 deletions(-) diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py index 8a628ac97a..74e30a41dd 100644 --- a/openstackclient/tests/volume/v2/fakes.py +++ b/openstackclient/tests/volume/v2/fakes.py @@ -418,6 +418,26 @@ def create_backups(attrs=None, count=2): return backups + @staticmethod + def get_backups(backups=None, count=2): + """Get an iterable MagicMock object with a list of faked backups. + + If backups list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List volumes: + A list of FakeResource objects faking backups + :param Integer count: + The number of backups to be faked + :return + An iterable Mock object with side_effect set to a list of faked + backups + """ + if backups is None: + backups = FakeBackup.create_backups(count) + + return mock.MagicMock(side_effect=backups) + class FakeExtension(object): """Fake one or more extension.""" @@ -529,6 +549,26 @@ def create_qoses(attrs=None, count=2): return qoses + @staticmethod + def get_qoses(qoses=None, count=2): + """Get an iterable MagicMock object with a list of faked qoses. + + If qoses list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List volumes: + A list of FakeResource objects faking qoses + :param Integer count: + The number of qoses to be faked + :return + An iterable Mock object with side_effect set to a list of faked + qoses + """ + if qoses is None: + qoses = FakeQos.create_qoses(count) + + return mock.MagicMock(side_effect=qoses) + class FakeSnapshot(object): """Fake one or more snapshot.""" @@ -582,6 +622,26 @@ def create_snapshots(attrs=None, count=2): return snapshots + @staticmethod + def get_snapshots(snapshots=None, count=2): + """Get an iterable MagicMock object with a list of faked snapshots. + + If snapshots list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List volumes: + A list of FakeResource objects faking snapshots + :param Integer count: + The number of snapshots to be faked + :return + An iterable Mock object with side_effect set to a list of faked + snapshots + """ + if snapshots is None: + snapshots = FakeSnapshot.create_snapshots(count) + + return mock.MagicMock(side_effect=snapshots) + class FakeType(object): """Fake one or more type.""" diff --git a/openstackclient/tests/volume/v2/test_backup.py b/openstackclient/tests/volume/v2/test_backup.py index 31bfa64cab..3c2b39488a 100644 --- a/openstackclient/tests/volume/v2/test_backup.py +++ b/openstackclient/tests/volume/v2/test_backup.py @@ -12,6 +12,12 @@ # under the License. # +import mock +from mock import call + +from osc_lib import exceptions +from osc_lib import utils + from openstackclient.tests.volume.v2 import fakes as volume_fakes from openstackclient.volume.v2 import backup @@ -138,12 +144,13 @@ def test_backup_create_without_name(self): class TestBackupDelete(TestBackup): - backup = volume_fakes.FakeBackup.create_one_backup() + backups = volume_fakes.FakeBackup.create_backups(count=2) def setUp(self): super(TestBackupDelete, self).setUp() - self.backups_mock.get.return_value = self.backup + self.backups_mock.get = ( + volume_fakes.FakeBackup.get_backups(self.backups)) self.backups_mock.delete.return_value = None # Get the command object to mock @@ -151,34 +158,81 @@ def setUp(self): def test_backup_delete(self): arglist = [ - self.backup.id + self.backups[0].id ] verifylist = [ - ("backups", [self.backup.id]) + ("backups", [self.backups[0].id]) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.backups_mock.delete.assert_called_with(self.backup.id, False) + self.backups_mock.delete.assert_called_with( + self.backups[0].id, False) self.assertIsNone(result) def test_backup_delete_with_force(self): arglist = [ '--force', - self.backup.id, + self.backups[0].id, ] verifylist = [ ('force', True), - ("backups", [self.backup.id]) + ("backups", [self.backups[0].id]) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.backups_mock.delete.assert_called_with(self.backup.id, True) + self.backups_mock.delete.assert_called_with(self.backups[0].id, True) self.assertIsNone(result) + def test_delete_multiple_backups(self): + arglist = [] + for b in self.backups: + arglist.append(b.id) + verifylist = [ + ('backups', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + calls = [] + for b in self.backups: + calls.append(call(b.id, False)) + self.backups_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_delete_multiple_backups_with_exception(self): + arglist = [ + self.backups[0].id, + 'unexist_backup', + ] + verifylist = [ + ('backups', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self.backups[0], exceptions.CommandError] + with mock.patch.object(utils, 'find_resource', + side_effect=find_mock_result) as find_mock: + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 backups failed to delete.', + str(e)) + + find_mock.assert_any_call(self.backups_mock, self.backups[0].id) + find_mock.assert_any_call(self.backups_mock, 'unexist_backup') + + self.assertEqual(2, find_mock.call_count) + self.backups_mock.delete.assert_called_once_with( + self.backups[0].id, False + ) + class TestBackupList(TestBackup): diff --git a/openstackclient/tests/volume/v2/test_qos_specs.py b/openstackclient/tests/volume/v2/test_qos_specs.py index da0e8ba5b4..56b8ae03bb 100644 --- a/openstackclient/tests/volume/v2/test_qos_specs.py +++ b/openstackclient/tests/volume/v2/test_qos_specs.py @@ -13,6 +13,10 @@ # under the License. # +import mock +from mock import call + +from osc_lib import exceptions from osc_lib import utils from openstackclient.tests.volume.v2 import fakes as volume_fakes @@ -156,45 +160,94 @@ def test_qos_create_with_properties(self): class TestQosDelete(TestQos): - qos_spec = volume_fakes.FakeQos.create_one_qos() + qos_specs = volume_fakes.FakeQos.create_qoses(count=2) def setUp(self): super(TestQosDelete, self).setUp() - self.qos_mock.get.return_value = self.qos_spec + self.qos_mock.get = ( + volume_fakes.FakeQos.get_qoses(self.qos_specs)) # Get the command object to test self.cmd = qos_specs.DeleteQos(self.app, None) def test_qos_delete(self): arglist = [ - self.qos_spec.id + self.qos_specs[0].id ] verifylist = [ - ('qos_specs', [self.qos_spec.id]) + ('qos_specs', [self.qos_specs[0].id]) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.qos_mock.delete.assert_called_with(self.qos_spec.id, False) + self.qos_mock.delete.assert_called_with( + self.qos_specs[0].id, False) self.assertIsNone(result) def test_qos_delete_with_force(self): arglist = [ '--force', - self.qos_spec.id + self.qos_specs[0].id ] verifylist = [ ('force', True), - ('qos_specs', [self.qos_spec.id]) + ('qos_specs', [self.qos_specs[0].id]) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.qos_mock.delete.assert_called_with(self.qos_spec.id, True) + self.qos_mock.delete.assert_called_with( + self.qos_specs[0].id, True) self.assertIsNone(result) + def test_delete_multiple_qoses(self): + arglist = [] + for q in self.qos_specs: + arglist.append(q.id) + verifylist = [ + ('qos_specs', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + calls = [] + for q in self.qos_specs: + calls.append(call(q.id, False)) + self.qos_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_delete_multiple_qoses_with_exception(self): + arglist = [ + self.qos_specs[0].id, + 'unexist_qos', + ] + verifylist = [ + ('qos_specs', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self.qos_specs[0], exceptions.CommandError] + with mock.patch.object(utils, 'find_resource', + side_effect=find_mock_result) as find_mock: + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual( + '1 of 2 QoS specifications failed to delete.', str(e)) + + find_mock.assert_any_call(self.qos_mock, self.qos_specs[0].id) + find_mock.assert_any_call(self.qos_mock, 'unexist_qos') + + self.assertEqual(2, find_mock.call_count) + self.qos_mock.delete.assert_called_once_with( + self.qos_specs[0].id, False + ) + class TestQosDisassociate(TestQos): diff --git a/openstackclient/tests/volume/v2/test_snapshot.py b/openstackclient/tests/volume/v2/test_snapshot.py index 2e9bcc82b0..04e0285eda 100644 --- a/openstackclient/tests/volume/v2/test_snapshot.py +++ b/openstackclient/tests/volume/v2/test_snapshot.py @@ -12,6 +12,10 @@ # under the License. # +import mock +from mock import call + +from osc_lib import exceptions from osc_lib import utils from openstackclient.tests.volume.v2 import fakes as volume_fakes @@ -123,12 +127,13 @@ def test_snapshot_create_without_name(self): class TestSnapshotDelete(TestSnapshot): - snapshot = volume_fakes.FakeSnapshot.create_one_snapshot() + snapshots = volume_fakes.FakeSnapshot.create_snapshots(count=2) def setUp(self): super(TestSnapshotDelete, self).setUp() - self.snapshots_mock.get.return_value = self.snapshot + self.snapshots_mock.get = ( + volume_fakes.FakeSnapshot.get_snapshots(self.snapshots)) self.snapshots_mock.delete.return_value = None # Get the command object to mock @@ -136,18 +141,66 @@ def setUp(self): def test_snapshot_delete(self): arglist = [ - self.snapshot.id + self.snapshots[0].id ] verifylist = [ - ("snapshots", [self.snapshot.id]) + ("snapshots", [self.snapshots[0].id]) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.snapshots_mock.delete.assert_called_with(self.snapshot.id) + self.snapshots_mock.delete.assert_called_with( + self.snapshots[0].id) self.assertIsNone(result) + def test_delete_multiple_snapshots(self): + arglist = [] + for s in self.snapshots: + arglist.append(s.id) + verifylist = [ + ('snapshots', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + calls = [] + for s in self.snapshots: + calls.append(call(s.id)) + self.snapshots_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_delete_multiple_snapshots_with_exception(self): + arglist = [ + self.snapshots[0].id, + 'unexist_snapshot', + ] + verifylist = [ + ('snapshots', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self.snapshots[0], exceptions.CommandError] + with mock.patch.object(utils, 'find_resource', + side_effect=find_mock_result) as find_mock: + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 snapshots failed to delete.', + str(e)) + + find_mock.assert_any_call( + self.snapshots_mock, self.snapshots[0].id) + find_mock.assert_any_call(self.snapshots_mock, 'unexist_snapshot') + + self.assertEqual(2, find_mock.call_count) + self.snapshots_mock.delete.assert_called_once_with( + self.snapshots[0].id + ) + class TestSnapshotList(TestSnapshot): diff --git a/openstackclient/tests/volume/v2/test_volume.py b/openstackclient/tests/volume/v2/test_volume.py index 68158df0e0..db65c3bdd1 100644 --- a/openstackclient/tests/volume/v2/test_volume.py +++ b/openstackclient/tests/volume/v2/test_volume.py @@ -13,9 +13,10 @@ # import copy - +import mock from mock import call +from osc_lib import exceptions from osc_lib import utils from openstackclient.tests import fakes @@ -458,6 +459,36 @@ def test_volume_delete_multi_volumes(self): self.volumes_mock.delete.assert_has_calls(calls) self.assertIsNone(result) + def test_volume_delete_multi_volumes_with_exception(self): + volumes = self.setup_volumes_mock(count=2) + + arglist = [ + volumes[0].id, + 'unexist_volume', + ] + verifylist = [ + ('volumes', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [volumes[0], exceptions.CommandError] + with mock.patch.object(utils, 'find_resource', + side_effect=find_mock_result) as find_mock: + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 volumes failed to delete.', + str(e)) + + find_mock.assert_any_call(self.volumes_mock, volumes[0].id) + find_mock.assert_any_call(self.volumes_mock, 'unexist_volume') + + self.assertEqual(2, find_mock.call_count) + self.volumes_mock.delete.assert_called_once_with( + volumes[0].id + ) + class TestVolumeList(TestVolume): diff --git a/openstackclient/volume/v2/backup.py b/openstackclient/volume/v2/backup.py index 49226bccca..3d27c121ef 100644 --- a/openstackclient/volume/v2/backup.py +++ b/openstackclient/volume/v2/backup.py @@ -15,14 +15,19 @@ """Volume v2 Backup action implementations""" import copy +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 CreateBackup(command.ShowOne): """Create new backup""" @@ -109,10 +114,24 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): volume_client = self.app.client_manager.volume - for backup in parsed_args.backups: - backup_id = utils.find_resource( - volume_client.backups, backup).id - volume_client.backups.delete(backup_id, parsed_args.force) + result = 0 + + for i in parsed_args.backups: + try: + backup_id = utils.find_resource( + volume_client.backups, i).id + volume_client.backups.delete(backup_id, parsed_args.force) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete backup with " + "name or ID '%(backup)s': %(e)s") + % {'backup': i, 'e': e}) + + if result > 0: + total = len(parsed_args.backups) + msg = (_("%(result)s of %(total)s backups failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListBackup(command.Lister): diff --git a/openstackclient/volume/v2/qos_specs.py b/openstackclient/volume/v2/qos_specs.py index 5ed1225bf0..9797f1a69d 100644 --- a/openstackclient/volume/v2/qos_specs.py +++ b/openstackclient/volume/v2/qos_specs.py @@ -15,14 +15,20 @@ """Volume v2 QoS action implementations""" +import logging + from osc_lib.cli import parseractions 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 AssociateQos(command.Command): """Associate a QoS specification to a volume type""" @@ -113,9 +119,23 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): volume_client = self.app.client_manager.volume - for qos in parsed_args.qos_specs: - qos_spec = utils.find_resource(volume_client.qos_specs, qos) - volume_client.qos_specs.delete(qos_spec.id, parsed_args.force) + result = 0 + + for i in parsed_args.qos_specs: + try: + qos_spec = utils.find_resource(volume_client.qos_specs, i) + volume_client.qos_specs.delete(qos_spec.id, parsed_args.force) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete QoS specification with " + "name or ID '%(qos)s': %(e)s") + % {'qos': i, 'e': e}) + + if result > 0: + total = len(parsed_args.qos_specs) + msg = (_("%(result)s of %(total)s QoS specifications failed" + " to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class DisassociateQos(command.Command): diff --git a/openstackclient/volume/v2/snapshot.py b/openstackclient/volume/v2/snapshot.py index 5e6949d44e..ba692074a2 100644 --- a/openstackclient/volume/v2/snapshot.py +++ b/openstackclient/volume/v2/snapshot.py @@ -15,15 +15,20 @@ """Volume v2 snapshot action implementations""" import copy +import logging from osc_lib.cli import parseractions 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 CreateSnapshot(command.ShowOne): """Create new snapshot""" @@ -92,10 +97,24 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): volume_client = self.app.client_manager.volume - for snapshot in parsed_args.snapshots: - snapshot_id = utils.find_resource( - volume_client.volume_snapshots, snapshot).id - volume_client.volume_snapshots.delete(snapshot_id) + result = 0 + + for i in parsed_args.snapshots: + try: + snapshot_id = utils.find_resource( + volume_client.volume_snapshots, i).id + volume_client.volume_snapshots.delete(snapshot_id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete snapshot with " + "name or ID '%(snapshot)s': %(e)s") + % {'snapshot': i, 'e': e}) + + if result > 0: + total = len(parsed_args.snapshots) + msg = (_("%(result)s of %(total)s snapshots failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListSnapshot(command.Lister): diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index 0ee7bd8f36..85f267ef58 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -19,6 +19,7 @@ from osc_lib.cli import parseractions from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils import six @@ -176,13 +177,27 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): volume_client = self.app.client_manager.volume - for volume in parsed_args.volumes: - volume_obj = utils.find_resource( - volume_client.volumes, volume) - if parsed_args.force: - volume_client.volumes.force_delete(volume_obj.id) - else: - volume_client.volumes.delete(volume_obj.id) + result = 0 + + for i in parsed_args.volumes: + try: + volume_obj = utils.find_resource( + volume_client.volumes, i) + if parsed_args.force: + volume_client.volumes.force_delete(volume_obj.id) + else: + volume_client.volumes.delete(volume_obj.id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete volume with " + "name or ID '%(volume)s': %(e)s") + % {'volume': i, 'e': e}) + + if result > 0: + total = len(parsed_args.volumes) + msg = (_("%(result)s of %(total)s volumes failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListVolume(command.Lister): From 5e06d6a3a66d4f0e3115ce48f510fd81c5feee8c Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Thu, 14 Jul 2016 18:34:19 +0800 Subject: [PATCH 1105/3095] Change to plural form of object in multi delete error message in networkv2 Usually, the error message of multi delete is: "'result' of 'total' 'objects' failed to delete" the objects is a plural form. To match the other multi delete error messages in OSC, change the object in delete error message in networkv2 to a plural form. Just add a 's' in the message. Change-Id: I17e0735d025bb61014db709d2639813565015b3d --- openstackclient/network/common.py | 2 +- openstackclient/tests/network/v2/test_floating_ip.py | 4 ++-- openstackclient/tests/network/v2/test_security_group.py | 4 ++-- openstackclient/tests/network/v2/test_security_group_rule.py | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/openstackclient/network/common.py b/openstackclient/network/common.py index f62840fc04..2b1a5656f2 100644 --- a/openstackclient/network/common.py +++ b/openstackclient/network/common.py @@ -111,7 +111,7 @@ def take_action(self, parsed_args): if ret: total = len(resources) - msg = _("%(num)s of %(total)s %(resource)s failed to delete.") % { + msg = _("%(num)s of %(total)s %(resource)ss failed to delete.") % { "num": ret, "total": total, "resource": self.resource, diff --git a/openstackclient/tests/network/v2/test_floating_ip.py b/openstackclient/tests/network/v2/test_floating_ip.py index 5cd5279aff..234fe4465a 100644 --- a/openstackclient/tests/network/v2/test_floating_ip.py +++ b/openstackclient/tests/network/v2/test_floating_ip.py @@ -211,7 +211,7 @@ def test_multi_floating_ips_delete_with_exception(self): self.cmd.take_action(parsed_args) self.fail('CommandError should be raised.') except exceptions.CommandError as e: - self.assertEqual('1 of 2 floating_ip failed to delete.', str(e)) + self.assertEqual('1 of 2 floating_ips failed to delete.', str(e)) self.network.find_ip.assert_any_call( self.floating_ips[0].id, ignore_missing=False) @@ -462,7 +462,7 @@ def test_multi_floating_ips_delete_with_exception(self): self.cmd.take_action(parsed_args) self.fail('CommandError should be raised.') except exceptions.CommandError as e: - self.assertEqual('1 of 2 floating_ip failed to delete.', str(e)) + self.assertEqual('1 of 2 floating_ips failed to delete.', str(e)) self.compute.floating_ips.get.assert_any_call( self.floating_ips[0].id) diff --git a/openstackclient/tests/network/v2/test_security_group.py b/openstackclient/tests/network/v2/test_security_group.py index 7ac925ac2f..cea64897e6 100644 --- a/openstackclient/tests/network/v2/test_security_group.py +++ b/openstackclient/tests/network/v2/test_security_group.py @@ -296,7 +296,7 @@ def test_multi_security_groups_delete_with_exception(self): self.cmd.take_action(parsed_args) self.fail('CommandError should be raised.') except exceptions.CommandError as e: - self.assertEqual('1 of 2 group failed to delete.', str(e)) + self.assertEqual('1 of 2 groups failed to delete.', str(e)) self.network.find_security_group.assert_any_call( self._security_groups[0].name, ignore_missing=False) @@ -384,7 +384,7 @@ def test_multi_security_groups_delete_with_exception(self): self.cmd.take_action(parsed_args) self.fail('CommandError should be raised.') except exceptions.CommandError as e: - self.assertEqual('1 of 2 group failed to delete.', str(e)) + self.assertEqual('1 of 2 groups failed to delete.', str(e)) self.compute.security_groups.get.assert_any_call( self._security_groups[0].id) diff --git a/openstackclient/tests/network/v2/test_security_group_rule.py b/openstackclient/tests/network/v2/test_security_group_rule.py index 67a123aa10..34d35629e1 100644 --- a/openstackclient/tests/network/v2/test_security_group_rule.py +++ b/openstackclient/tests/network/v2/test_security_group_rule.py @@ -739,7 +739,7 @@ def test_multi_security_group_rules_delete_with_exception(self): self.cmd.take_action(parsed_args) self.fail('CommandError should be raised.') except exceptions.CommandError as e: - self.assertEqual('1 of 2 rule failed to delete.', str(e)) + self.assertEqual('1 of 2 rules failed to delete.', str(e)) self.network.find_security_group_rule.assert_any_call( self._security_group_rules[0].id, ignore_missing=False) @@ -819,7 +819,7 @@ def test_multi_security_group_rules_delete_with_exception(self): self.cmd.take_action(parsed_args) self.fail('CommandError should be raised.') except exceptions.CommandError as e: - self.assertEqual('1 of 2 rule failed to delete.', str(e)) + self.assertEqual('1 of 2 rules failed to delete.', str(e)) self.compute.security_group_rules.delete.assert_any_call( self._security_group_rules[0].id) From 9f346c3fb6eb25ed581139992380c53580c1cd42 Mon Sep 17 00:00:00 2001 From: Kyrylo Romanenko Date: Thu, 14 Jul 2016 14:09:18 +0300 Subject: [PATCH 1106/3095] Allow format selection in get_opts Default is "value". Change-Id: I244253f6cd53104a57ef12f6b14e1653c89d38bc --- functional/common/test.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/functional/common/test.py b/functional/common/test.py index fa523d7d33..f7f0361eea 100644 --- a/functional/common/test.py +++ b/functional/common/test.py @@ -58,8 +58,9 @@ def get_openstack_configuration_value(cls, configuration): return cls.openstack('configuration show ' + opts) @classmethod - def get_opts(cls, fields): - return ' -f value ' + ' '.join(['-c ' + it for it in fields]) + def get_opts(cls, fields, format='value'): + return ' -f {0} {1}'.format(format, + ' '.join(['-c ' + it for it in fields])) @classmethod def assertOutput(cls, expected, actual): From 17dc8526ab861e3eb0e80b228fa1ce756f63f5c4 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Fri, 15 Jul 2016 01:43:03 +0000 Subject: [PATCH 1107/3095] Updated from global requirements Change-Id: I51ce0df359d992153cbe0588c4f67d829a360032 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e3b550d1b2..8e85f964aa 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ cliff!=1.16.0,!=1.17.0,>=1.15.0 # Apache-2.0 keystoneauth1>=2.7.0 # Apache-2.0 openstacksdk>=0.8.6 # Apache-2.0 os-client-config>=1.13.1 # Apache-2.0 -osc-lib>=0.1.0 # Apache-2.0 +osc-lib>=0.3.0 # Apache-2.0 oslo.config>=3.12.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 oslo.utils>=3.15.0 # Apache-2.0 From c0467edc647bd3cce00829daf1af53bab330e040 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Wed, 13 Jul 2016 17:33:52 +0800 Subject: [PATCH 1108/3095] Update doc for credential in indentityv3 Change-Id: I5f49c038a75ea67674b6d9279a6e60d6ded8d12f --- doc/source/command-objects/credential.rst | 108 +++++++++++++++++++--- openstackclient/identity/v3/credential.py | 20 ++-- 2 files changed, 107 insertions(+), 21 deletions(-) diff --git a/doc/source/command-objects/credential.rst b/doc/source/command-objects/credential.rst index ed8a00dbd7..538362a864 100644 --- a/doc/source/command-objects/credential.rst +++ b/doc/source/command-objects/credential.rst @@ -1,27 +1,113 @@ -=========== +========== credential -=========== +========== Identity v3 credential create ------------------- +----------------- -.. ''[consider rolling the ec2 creds into this too]'' +Create new credential +.. program:: credential create .. code:: bash os credential create - --x509 - [] - [] + [--type ] + [--project ] + + +.. option:: --type + + New credential type + +.. option:: --project + + Project which limits the scope of the credential (name or ID) + +.. _credential_create: +.. describe:: + + User that owns the credential (name or ID) + +.. describe:: + + New credential data + +credential delete +----------------- + +Delete credential(s) + +.. program:: credential delete +.. code:: bash + + os credential delete + [ ...] + +.. _credential_delete: +.. describe:: + + ID(s) of credential to delete + +credential list +--------------- + +List credentials + +.. program:: credential list +.. code:: bash + + os credential list + +credential set +-------------- + +Set credential properties + +.. program:: credential set +.. code:: bash + + os credential set + [--user ] + [--type ] + [--data ] + [--project ] + + +.. option:: --user + + User that owns the credential (name or ID) + +.. option:: --type + + New credential type + +.. option:: --data + + New credential data + +.. option:: --project + + Project which limits the scope of the credential (name or ID) + +.. _credential_set: +.. describe:: + + ID of credential to change credential show ----------------- +--------------- + +Display credential details +.. program:: credential show .. code:: bash os credential show - [--token] - [--user] - [--x509 [--root]] + + +.. _credential_show: +.. describe:: + + ID of credential to display diff --git a/openstackclient/identity/v3/credential.py b/openstackclient/identity/v3/credential.py index eeeddfa554..7e5b040f4b 100644 --- a/openstackclient/identity/v3/credential.py +++ b/openstackclient/identity/v3/credential.py @@ -23,14 +23,14 @@ class CreateCredential(command.ShowOne): - """Create credential command""" + """Create new credential""" def get_parser(self, prog_name): parser = super(CreateCredential, self).get_parser(prog_name) parser.add_argument( 'user', metavar='', - help=_('Name or ID of user that owns the credential'), + help=_('user that owns the credential (name or ID)'), ) parser.add_argument( '--type', @@ -47,8 +47,8 @@ def get_parser(self, prog_name): parser.add_argument( '--project', metavar='', - help=_('Project name or ID which limits the ' - 'scope of the credential'), + help=_('Project which limits the scope of ' + 'the credential (name or ID)'), ) return parser @@ -89,7 +89,7 @@ def take_action(self, parsed_args): class ListCredential(command.Lister): - """List credential command""" + """List credentials""" def take_action(self, parsed_args): columns = ('ID', 'Type', 'User ID', 'Blob', 'Project ID') @@ -103,7 +103,7 @@ def take_action(self, parsed_args): class SetCredential(command.Command): - """Set credential command""" + """Set credential properties""" def get_parser(self, prog_name): parser = super(SetCredential, self).get_parser(prog_name) @@ -116,7 +116,7 @@ def get_parser(self, prog_name): '--user', metavar='', required=True, - help=_('Name or ID of user that owns the credential'), + help=_('User that owns the credential (name or ID)'), ) parser.add_argument( '--type', @@ -134,8 +134,8 @@ def get_parser(self, prog_name): parser.add_argument( '--project', metavar='', - help=_('Project name or ID which limits the ' - 'scope of the credential'), + help=_('Project which limits the scope of ' + 'the credential (name or ID)'), ) return parser @@ -159,7 +159,7 @@ def take_action(self, parsed_args): class ShowCredential(command.ShowOne): - """Show credential command""" + """Display credential details""" def get_parser(self, prog_name): parser = super(ShowCredential, self).get_parser(prog_name) From 50dd4a1a1e890f81cabce383ba41041e84127d5b Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Fri, 15 Jul 2016 19:58:04 +0800 Subject: [PATCH 1109/3095] Add create_one_image_member() in FakeImage class and update test Usually we use a fake object in unit test. To match the other test, add create_one_image_member() in FakeImage class in imagev2 to create a fake image member, delete the old data and update the unit test of image. Change-Id: I062a362b15db7e8bc89ec48c540d310199fd6c0b --- openstackclient/tests/image/v2/fakes.py | 33 +++++++++++++++----- openstackclient/tests/image/v2/test_image.py | 12 +++---- 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/openstackclient/tests/image/v2/fakes.py b/openstackclient/tests/image/v2/fakes.py index c2a8d7217b..d450dec17d 100644 --- a/openstackclient/tests/image/v2/fakes.py +++ b/openstackclient/tests/image/v2/fakes.py @@ -49,13 +49,6 @@ IMAGE_SHOW['tags'] = '' IMAGE_SHOW_data = tuple((IMAGE_SHOW[x] for x in sorted(IMAGE_SHOW))) -member_status = 'pending' -MEMBER = { - 'member_id': identity_fakes.project_id, - 'image_id': image_id, - 'status': member_status, -} - # Just enough v2 schema to do some testing IMAGE_schema = { "additionalProperties": { @@ -288,3 +281,29 @@ def get_image_data(image=None): else: data_list.append(getattr(image, x)) return tuple(data_list) + + @staticmethod + def create_one_image_member(attrs=None): + """Create a fake image member. + + :param Dictionary attrs: + A dictionary with all attrbutes of image member + :return: + A FakeResource object with member_id, image_id and so on + """ + attrs = attrs or {} + + # Set default attribute + image_member_info = { + 'member_id': 'member-id-' + uuid.uuid4().hex, + 'image_id': 'image-id-' + uuid.uuid4().hex, + 'status': 'pending', + } + + # Overwrite default attributes if there are some attributes set + image_member_info.update(attrs) + + image_member = fakes.FakeModel( + copy.deepcopy(image_member_info)) + + return image_member diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index 592def21ef..8bba1b011a 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -347,6 +347,10 @@ def test_image_create_dead_options(self): class TestAddProjectToImage(TestImage): _image = image_fakes.FakeImage.create_one_image() + new_member = image_fakes.FakeImage.create_one_image_member( + attrs={'image_id': _image.id, + 'member_id': identity_fakes.project_id} + ) columns = ( 'image_id', @@ -357,7 +361,7 @@ class TestAddProjectToImage(TestImage): datalist = ( _image.id, identity_fakes.project_id, - image_fakes.member_status + new_member.status ) def setUp(self): @@ -367,11 +371,7 @@ def setUp(self): self.images_mock.get.return_value = self._image # Update the image_id in the MEMBER dict - self.new_member = copy.deepcopy(image_fakes.MEMBER) - self.new_member['image_id'] = self._image.id - self.image_members_mock.create.return_value = fakes.FakeModel( - self.new_member, - ) + self.image_members_mock.create.return_value = self.new_member self.project_mock.get.return_value = fakes.FakeResource( None, copy.deepcopy(identity_fakes.PROJECT), From e2a9fd29c118351cd2c99e40233c9eb31c760154 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Sat, 16 Jul 2016 13:33:03 -0400 Subject: [PATCH 1110/3095] Follow upper constraints for all tox targets With the exception of releasenotes and cover, we should follow upper constraints. The tox_install file was copied over from python-neutronclient [1]. [1] http://git.openstack.org/cgit/openstack/python-neutronclient/tree/tools/tox_install.sh Change-Id: I633fa149820efafd7b2acec0388fa8bc8d06c988 --- tools/tox_install.sh | 55 ++++++++++++++++++++++++++++++++++++++++++++ tox.ini | 7 +++++- 2 files changed, 61 insertions(+), 1 deletion(-) create mode 100755 tools/tox_install.sh diff --git a/tools/tox_install.sh b/tools/tox_install.sh new file mode 100755 index 0000000000..c96b023c19 --- /dev/null +++ b/tools/tox_install.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash + +# Client constraint file contains this client version pin that is in conflict +# with installing the client from source. We should replace the version pin in +# the constraints file before applying it for from-source installation. + +ZUUL_CLONER=/usr/zuul-env/bin/zuul-cloner +BRANCH_NAME=master +CLIENT_NAME=python-openstackclient +requirements_installed=$(echo "import openstack_requirements" | python 2>/dev/null ; echo $?) + +set -e + +CONSTRAINTS_FILE=$1 +shift + +install_cmd="pip install" +if [ $CONSTRAINTS_FILE != "unconstrained" ]; then + + mydir=$(mktemp -dt "$CLIENT_NAME-tox_install-XXXXXXX") + localfile=$mydir/upper-constraints.txt + if [[ $CONSTRAINTS_FILE != http* ]]; then + CONSTRAINTS_FILE=file://$CONSTRAINTS_FILE + fi + curl $CONSTRAINTS_FILE -k -o $localfile + install_cmd="$install_cmd -c$localfile" + + if [ $requirements_installed -eq 0 ]; then + echo "ALREADY INSTALLED" > /tmp/tox_install.txt + echo "Requirements already installed; using existing package" + elif [ -x "$ZUUL_CLONER" ]; then + export ZUUL_BRANCH=${ZUUL_BRANCH-$BRANCH} + echo "ZUUL CLONER" > /tmp/tox_install.txt + pushd $mydir + $ZUUL_CLONER --cache-dir \ + /opt/git \ + --branch $BRANCH_NAME \ + git://git.openstack.org \ + openstack/requirements + cd openstack/requirements + $install_cmd -e . + popd + else + echo "PIP HARDCODE" > /tmp/tox_install.txt + if [ -z "$REQUIREMENTS_PIP_LOCATION" ]; then + REQUIREMENTS_PIP_LOCATION="git+https://git.openstack.org/openstack/requirements@$BRANCH_NAME#egg=requirements" + fi + $install_cmd -U -e ${REQUIREMENTS_PIP_LOCATION} + fi + + edit-constraints $localfile -- $CLIENT_NAME "-e file://$PWD#egg=$CLIENT_NAME" +fi + +$install_cmd -U $* +exit $? diff --git a/tox.ini b/tox.ini index 4afb8c2077..10b4978c29 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,8 @@ skipdist = True [testenv] usedevelop = True -install_command = pip install -U {opts} {packages} +install_command = + {toxinidir}/tools/tox_install.sh {env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages} setenv = VIRTUAL_ENV={envdir} deps = -r{toxinidir}/test-requirements.txt commands = ostestr {posargs} @@ -54,6 +55,8 @@ setenv = OS_TEST_PATH=./functional/tests passenv = OS_* [testenv:venv] +# TODO(ihrachys): remove once infra supports constraints for this target +install_command = pip install -U {opts} {packages} commands = {posargs} [testenv:cover] @@ -68,6 +71,8 @@ commands = oslo_debug_helper -t openstackclient/tests {posargs} commands = python setup.py build_sphinx [testenv:releasenotes] +# TODO(ihrachys): remove once infra supports constraints for this target +install_command = pip install -U {opts} {packages} commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html [flake8] From 5ce351c492b491a4a9a1edc02389bc184371050a Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Thu, 30 Jun 2016 13:30:01 +0000 Subject: [PATCH 1111/3095] Unskip the tests affected by warlock 1.3.0 1) This reverts commit 044a46ed5f040020cf80adc1ace987802344e87d which was broken by warlock 1.3.0 Change-Id: Ia286d9394586884018c4e62cf581e39a809f5545 --- functional/tests/image/v2/test_image.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/functional/tests/image/v2/test_image.py b/functional/tests/image/v2/test_image.py index c2524f8ad0..809451bb74 100644 --- a/functional/tests/image/v2/test_image.py +++ b/functional/tests/image/v2/test_image.py @@ -13,8 +13,6 @@ import os import uuid -import testtools - from functional.common import test @@ -68,7 +66,6 @@ def test_image_metadata(self): raw_output = self.openstack('image show ' + self.NAME + opts) self.assertEqual(self.NAME + "\na='b', c='d'\n", raw_output) - @testtools.skip("skip until bug 1596573 is resolved") def test_image_unset(self): opts = self.get_opts(["name", "tags", "properties"]) self.openstack('image set --tag 01 ' + self.NAME) From 9f6148132cc21a6f78b5ebb7c1282b30a309ef8a Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sun, 17 Jul 2016 23:59:46 +0000 Subject: [PATCH 1112/3095] Updated from global requirements Change-Id: I74744755891f65da2c1d7b7b475230d395a3a8ee --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 8e85f964aa..282402dcde 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,4 +18,4 @@ python-keystoneclient!=1.8.0,!=2.1.0,>=1.7.0 # Apache-2.0 python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0 python-cinderclient!=1.7.0,!=1.7.1,>=1.6.0 # Apache-2.0 requests>=2.10.0 # Apache-2.0 -stevedore>=1.10.0 # Apache-2.0 +stevedore>=1.16.0 # Apache-2.0 From 60639d76a742852e18f9e2889c480be95596c268 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Tue, 12 Jul 2016 12:44:55 +0800 Subject: [PATCH 1113/3095] Support bulk deletion for delete commands in identityv3 Support bulk deletion for delete commands in the list below identity/v3/consumer identity/v3/credential identity/v3/domain identity/v3/ec2creds identity/v3/endpoint identity/v3/federation_protocol identity/v3/identity_provider identity/v3/mapping identity/v3/policy identity/v3/region identity/v3/service_provider identity/v3/service The unit test in identityv3 need to be refactored, so I add some functional tests instead. I will add all unit tests at one time after the refactor completed. Change-Id: I82367570f59817b47c87b6c7bfeae95ccfe5c50e Closes-Bug: #1592906 --- doc/source/command-objects/consumer.rst | 6 ++-- doc/source/command-objects/domain.rst | 6 ++-- .../command-objects/federation-protocol.rst | 6 ++-- .../command-objects/identity-provider.rst | 6 ++-- doc/source/command-objects/mapping.rst | 6 ++-- doc/source/command-objects/policy.rst | 6 ++-- doc/source/command-objects/region.rst | 6 ++-- .../command-objects/service-provider.rst | 6 ++-- functional/tests/identity/v3/test_domain.py | 12 +++++++ functional/tests/identity/v3/test_endpoint.py | 7 +++++ functional/tests/identity/v3/test_idp.py | 7 +++++ functional/tests/identity/v3/test_region.py | 7 +++++ functional/tests/identity/v3/test_service.py | 7 +++++ .../identity/v3/test_service_provider.py | 7 +++++ openstackclient/identity/v3/consumer.py | 31 ++++++++++++++++--- openstackclient/identity/v3/credential.py | 27 ++++++++++++++-- openstackclient/identity/v3/domain.py | 25 ++++++++++++--- openstackclient/identity/v3/ec2creds.py | 25 +++++++++++++-- openstackclient/identity/v3/endpoint.py | 31 ++++++++++++++++--- .../identity/v3/federation_protocol.py | 24 +++++++++++--- .../identity/v3/identity_provider.py | 23 +++++++++++--- openstackclient/identity/v3/mapping.py | 22 ++++++++++--- openstackclient/identity/v3/policy.py | 27 ++++++++++++++-- openstackclient/identity/v3/region.py | 28 ++++++++++++++--- openstackclient/identity/v3/service.py | 31 +++++++++++++++---- .../identity/v3/service_provider.py | 28 ++++++++++++++--- .../tests/identity/v3/test_consumer.py | 2 +- .../tests/identity/v3/test_domain.py | 2 +- .../tests/identity/v3/test_endpoint.py | 2 +- .../identity/v3/test_identity_provider.py | 2 +- .../tests/identity/v3/test_mappings.py | 2 +- .../tests/identity/v3/test_protocol.py | 2 +- .../tests/identity/v3/test_region.py | 2 +- .../tests/identity/v3/test_service.py | 2 +- .../identity/v3/test_service_provider.py | 2 +- .../notes/bug-1592906-ad67ce8736f3cd48.yaml | 8 +++++ 36 files changed, 361 insertions(+), 82 deletions(-) create mode 100644 releasenotes/notes/bug-1592906-ad67ce8736f3cd48.yaml diff --git a/doc/source/command-objects/consumer.rst b/doc/source/command-objects/consumer.rst index 91294fa20b..335eaea32f 100644 --- a/doc/source/command-objects/consumer.rst +++ b/doc/source/command-objects/consumer.rst @@ -24,17 +24,17 @@ Create new consumer consumer delete --------------- -Delete consumer +Delete consumer(s) .. program:: consumer delete .. code:: bash os consumer delete - + [ ...] .. describe:: - Consumer to delete + Consumer(s) to delete consumer list ------------- diff --git a/doc/source/command-objects/domain.rst b/doc/source/command-objects/domain.rst index 94473570de..573a78cb27 100644 --- a/doc/source/command-objects/domain.rst +++ b/doc/source/command-objects/domain.rst @@ -43,17 +43,17 @@ Create new domain domain delete ------------- -Delete domain +Delete domain(s) .. program:: domain delete .. code:: bash os domain delete - + [ ...] .. describe:: - Domain to delete (name or ID) + Domain(s) to delete (name or ID) domain list ----------- diff --git a/doc/source/command-objects/federation-protocol.rst b/doc/source/command-objects/federation-protocol.rst index 5b4ea48ace..911d4e99c6 100644 --- a/doc/source/command-objects/federation-protocol.rst +++ b/doc/source/command-objects/federation-protocol.rst @@ -34,14 +34,14 @@ Create new federation protocol federation protocol delete -------------------------- -Delete federation protocol +Delete federation protocol(s) .. program:: federation protocol delete .. code:: bash os federation protocol delete --identity-provider - + [ ...] .. option:: --identity-provider @@ -49,7 +49,7 @@ Delete federation protocol .. describe:: - Federation protocol to delete (name or ID) + Federation protocol(s) to delete (name or ID) federation protocol list ------------------------ diff --git a/doc/source/command-objects/identity-provider.rst b/doc/source/command-objects/identity-provider.rst index ca773d811f..f772511d50 100644 --- a/doc/source/command-objects/identity-provider.rst +++ b/doc/source/command-objects/identity-provider.rst @@ -49,17 +49,17 @@ Create new identity provider identity provider delete ------------------------ -Delete identity provider +Delete identity provider(s) .. program:: identity provider delete .. code:: bash os identity provider delete - + [ ...] .. describe:: - Identity provider to delete + Identity provider(s) to delete identity provider list ---------------------- diff --git a/doc/source/command-objects/mapping.rst b/doc/source/command-objects/mapping.rst index 25af474061..7f61366e55 100644 --- a/doc/source/command-objects/mapping.rst +++ b/doc/source/command-objects/mapping.rst @@ -30,18 +30,18 @@ Create new mapping mapping delete -------------- -Delete mapping +Delete mapping(s) .. program:: mapping delete .. code:: bash os mapping delete - + [ ...] .. _mapping_delete-mapping: .. describe:: - Mapping to delete + Mapping(s) to delete mapping list ------------ diff --git a/doc/source/command-objects/policy.rst b/doc/source/command-objects/policy.rst index 195a89f25e..757b1c53ad 100644 --- a/doc/source/command-objects/policy.rst +++ b/doc/source/command-objects/policy.rst @@ -27,17 +27,17 @@ Create new policy policy delete ------------- -Delete policy +Delete policy(s) .. program:: policy delete .. code:: bash os policy delete - + [ ...] .. describe:: - Policy to delete + Policy(s) to delete policy list ----------- diff --git a/doc/source/command-objects/region.rst b/doc/source/command-objects/region.rst index 1892fc244f..b74821a9db 100644 --- a/doc/source/command-objects/region.rst +++ b/doc/source/command-objects/region.rst @@ -33,18 +33,18 @@ Create new region region delete ------------- -Delete region +Delete region(s) .. program:: region delete .. code:: bash os region delete - + [ ...] .. _region_delete-region-id: .. describe:: - Region ID to delete + Region ID(s) to delete region list ----------- diff --git a/doc/source/command-objects/service-provider.rst b/doc/source/command-objects/service-provider.rst index 963493b4a2..e0fbba2fe9 100644 --- a/doc/source/command-objects/service-provider.rst +++ b/doc/source/command-objects/service-provider.rst @@ -48,17 +48,17 @@ Create new service provider service provider delete ----------------------- -Delete service provider +Delete service provider(s) .. program:: service provider delete .. code:: bash os service provider delete - + [ ...] .. describe:: - Service provider to delete + Service provider(s) to delete service provider list --------------------- diff --git a/functional/tests/identity/v3/test_domain.py b/functional/tests/identity/v3/test_domain.py index 305ed58da5..3f514b580b 100644 --- a/functional/tests/identity/v3/test_domain.py +++ b/functional/tests/identity/v3/test_domain.py @@ -43,6 +43,18 @@ def test_domain_delete(self): raw_output = self.openstack('domain delete %s' % domain_name) self.assertEqual(0, len(raw_output)) + def test_domain_multi_delete(self): + domain_1 = self._create_dummy_domain(add_clean_up=False) + domain_2 = self._create_dummy_domain(add_clean_up=False) + # cannot delete enabled domain, disable it first + raw_output = self.openstack('domain set --disable %s' % domain_1) + self.assertEqual(0, len(raw_output)) + raw_output = self.openstack('domain set --disable %s' % domain_2) + self.assertEqual(0, len(raw_output)) + raw_output = self.openstack( + 'domain delete %s %s' % (domain_1, domain_2)) + self.assertEqual(0, len(raw_output)) + def test_domain_delete_failure(self): domain_name = self._create_dummy_domain() # cannot delete enabled domain diff --git a/functional/tests/identity/v3/test_endpoint.py b/functional/tests/identity/v3/test_endpoint.py index 162c9e58cd..e0afab2333 100644 --- a/functional/tests/identity/v3/test_endpoint.py +++ b/functional/tests/identity/v3/test_endpoint.py @@ -28,6 +28,13 @@ def test_endpoint_delete(self): 'endpoint delete %s' % endpoint_id) self.assertEqual(0, len(raw_output)) + def test_endpoint_multi_delete(self): + endpoint_1 = self._create_dummy_endpoint(add_clean_up=False) + endpoint_2 = self._create_dummy_endpoint(add_clean_up=False) + raw_output = self.openstack( + 'endpoint delete %s %s' % (endpoint_1, endpoint_2)) + self.assertEqual(0, len(raw_output)) + def test_endpoint_list(self): endpoint_id = self._create_dummy_endpoint() raw_output = self.openstack('endpoint list') diff --git a/functional/tests/identity/v3/test_idp.py b/functional/tests/identity/v3/test_idp.py index 8d86471245..bc9690f77e 100644 --- a/functional/tests/identity/v3/test_idp.py +++ b/functional/tests/identity/v3/test_idp.py @@ -26,6 +26,13 @@ def test_idp_delete(self): % identity_provider) self.assertEqual(0, len(raw_output)) + def test_idp_multi_delete(self): + idp_1 = self._create_dummy_idp(add_clean_up=False) + idp_2 = self._create_dummy_idp(add_clean_up=False) + raw_output = self.openstack( + 'identity provider delete %s %s' % (idp_1, idp_2)) + self.assertEqual(0, len(raw_output)) + def test_idp_show(self): identity_provider = self._create_dummy_idp(add_clean_up=True) raw_output = self.openstack('identity provider show %s' diff --git a/functional/tests/identity/v3/test_region.py b/functional/tests/identity/v3/test_region.py index 5ba0c231c6..2ebc0e597d 100644 --- a/functional/tests/identity/v3/test_region.py +++ b/functional/tests/identity/v3/test_region.py @@ -27,6 +27,13 @@ def test_region_delete(self): raw_output = self.openstack('region delete %s' % region_id) self.assertEqual(0, len(raw_output)) + def test_region_multi_delete(self): + region_1 = self._create_dummy_region(add_clean_up=False) + region_2 = self._create_dummy_region(add_clean_up=False) + raw_output = self.openstack( + 'region delete %s %s' % (region_1, region_2)) + self.assertEqual(0, len(raw_output)) + def test_region_list(self): raw_output = self.openstack('region list') items = self.parse_listing(raw_output) diff --git a/functional/tests/identity/v3/test_service.py b/functional/tests/identity/v3/test_service.py index c757d914ee..79a63dc8a8 100644 --- a/functional/tests/identity/v3/test_service.py +++ b/functional/tests/identity/v3/test_service.py @@ -25,6 +25,13 @@ def test_service_delete(self): raw_output = self.openstack('service delete %s' % service_name) self.assertEqual(0, len(raw_output)) + def test_service_multi_delete(self): + service_1 = self._create_dummy_service(add_clean_up=False) + service_2 = self._create_dummy_service(add_clean_up=False) + raw_output = self.openstack( + 'service delete %s %s' % (service_1, service_2)) + self.assertEqual(0, len(raw_output)) + def test_service_list(self): self._create_dummy_service() raw_output = self.openstack('service list') diff --git a/functional/tests/identity/v3/test_service_provider.py b/functional/tests/identity/v3/test_service_provider.py index 6cfa7e56db..458c2ae65e 100644 --- a/functional/tests/identity/v3/test_service_provider.py +++ b/functional/tests/identity/v3/test_service_provider.py @@ -26,6 +26,13 @@ def test_sp_delete(self): % service_provider) self.assertEqual(0, len(raw_output)) + def test_sp_multi_delete(self): + sp1 = self._create_dummy_sp(add_clean_up=False) + sp2 = self._create_dummy_sp(add_clean_up=False) + raw_output = self.openstack( + 'service provider delete %s %s' % (sp1, sp2)) + self.assertEqual(0, len(raw_output)) + def test_sp_show(self): service_provider = self._create_dummy_sp(add_clean_up=True) raw_output = self.openstack('service provider show %s' diff --git a/openstackclient/identity/v3/consumer.py b/openstackclient/identity/v3/consumer.py index 65bf657feb..b41a37ca2c 100644 --- a/openstackclient/identity/v3/consumer.py +++ b/openstackclient/identity/v3/consumer.py @@ -15,13 +15,19 @@ """Identity v3 Consumer 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 CreateConsumer(command.ShowOne): """Create new consumer""" @@ -44,22 +50,37 @@ def take_action(self, parsed_args): class DeleteConsumer(command.Command): - """Delete consumer""" + """Delete consumer(s)""" def get_parser(self, prog_name): parser = super(DeleteConsumer, self).get_parser(prog_name) parser.add_argument( 'consumer', metavar='', - help=_('Consumer to delete'), + nargs='+', + help=_('Consumer(s) to delete'), ) return parser def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - consumer = utils.find_resource( - identity_client.oauth1.consumers, parsed_args.consumer) - identity_client.oauth1.consumers.delete(consumer.id) + result = 0 + for i in parsed_args.consumer: + try: + consumer = utils.find_resource( + identity_client.oauth1.consumers, i) + identity_client.oauth1.consumers.delete(consumer.id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete consumer with name or " + "ID '%(consumer)s': %(e)s") + % {'consumer': i, 'e': e}) + + if result > 0: + total = len(parsed_args.consumer) + msg = (_("%(result)s of %(total)s consumers failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListConsumer(command.Lister): diff --git a/openstackclient/identity/v3/credential.py b/openstackclient/identity/v3/credential.py index eeeddfa554..0ea29abaaf 100644 --- a/openstackclient/identity/v3/credential.py +++ b/openstackclient/identity/v3/credential.py @@ -15,13 +15,19 @@ """Identity v3 Credential 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 CreateCredential(command.ShowOne): """Create credential command""" @@ -72,20 +78,35 @@ def take_action(self, parsed_args): class DeleteCredential(command.Command): - """Delete credential command""" + """Delete credential(s)""" def get_parser(self, prog_name): parser = super(DeleteCredential, self).get_parser(prog_name) parser.add_argument( 'credential', metavar='', - help=_('ID of credential to delete'), + nargs='+', + help=_('ID of credential(s) to delete'), ) return parser def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - identity_client.credentials.delete(parsed_args.credential) + result = 0 + for i in parsed_args.credential: + try: + identity_client.credentials.delete(i) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete credentials with " + "ID '%(credential)s': %(e)s") + % {'credential': i, 'e': e}) + + if result > 0: + total = len(parsed_args.credential) + msg = (_("%(result)s of %(total)s credential failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListCredential(command.Lister): diff --git a/openstackclient/identity/v3/domain.py b/openstackclient/identity/v3/domain.py index 3e9bcf6344..76e47d32e1 100755 --- a/openstackclient/identity/v3/domain.py +++ b/openstackclient/identity/v3/domain.py @@ -19,6 +19,7 @@ from keystoneauth1 import exceptions as ks_exc from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils import six @@ -88,22 +89,36 @@ def take_action(self, parsed_args): class DeleteDomain(command.Command): - """Delete domain""" + """Delete domain(s)""" def get_parser(self, prog_name): parser = super(DeleteDomain, self).get_parser(prog_name) parser.add_argument( 'domain', metavar='', - help=_('Domain to delete (name or ID)'), + nargs='+', + help=_('Domain(s) to delete (name or ID)'), ) return parser def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - domain = utils.find_resource(identity_client.domains, - parsed_args.domain) - identity_client.domains.delete(domain.id) + result = 0 + for i in parsed_args.domain: + try: + domain = utils.find_resource(identity_client.domains, i) + identity_client.domains.delete(domain.id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete domain with name or " + "ID '%(domain)s': %(e)s") + % {'domain': i, 'e': e}) + + if result > 0: + total = len(parsed_args.domain) + msg = (_("%(result)s of %(total)s domains failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListDomain(command.Lister): diff --git a/openstackclient/identity/v3/ec2creds.py b/openstackclient/identity/v3/ec2creds.py index 835fe96fd9..7ad0171906 100644 --- a/openstackclient/identity/v3/ec2creds.py +++ b/openstackclient/identity/v3/ec2creds.py @@ -12,7 +12,10 @@ """Identity v3 EC2 Credentials action implementations""" +import logging + from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils import six @@ -20,6 +23,9 @@ from openstackclient.identity import common +LOG = logging.getLogger(__name__) + + def _determine_ec2_user(parsed_args, client_manager): """Determine a user several different ways. @@ -113,7 +119,8 @@ def get_parser(self, prog_name): parser.add_argument( 'access_key', metavar='', - help=_('Credentials access key'), + nargs='+', + help=_('Credentials access key(s)'), ) parser.add_argument( '--user', @@ -126,7 +133,21 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): client_manager = self.app.client_manager user = _determine_ec2_user(parsed_args, client_manager) - client_manager.identity.ec2.delete(user, parsed_args.access_key) + result = 0 + for i in parsed_args.access_key: + try: + client_manager.identity.ec2.delete(user, i) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete EC2 credentials with " + "access key '%(access_key)s': %(e)s") + % {'access_key': i, 'e': e}) + + if result > 0: + total = len(parsed_args.access_key) + msg = (_("%(result)s of %(total)s EC2 keys failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListEC2Creds(command.Lister): diff --git a/openstackclient/identity/v3/endpoint.py b/openstackclient/identity/v3/endpoint.py index bd2df361d1..73b37a436c 100644 --- a/openstackclient/identity/v3/endpoint.py +++ b/openstackclient/identity/v3/endpoint.py @@ -15,7 +15,10 @@ """Identity v3 Endpoint action implementations""" +import logging + from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils import six @@ -23,6 +26,9 @@ from openstackclient.identity import common +LOG = logging.getLogger(__name__) + + def get_service_name(service): if hasattr(service, 'name'): return service.name @@ -93,22 +99,37 @@ def take_action(self, parsed_args): class DeleteEndpoint(command.Command): - """Delete endpoint""" + """Delete endpoint(s)""" def get_parser(self, prog_name): parser = super(DeleteEndpoint, self).get_parser(prog_name) parser.add_argument( 'endpoint', metavar='', - help=_('Endpoint to delete (ID only)'), + nargs='+', + help=_('Endpoint(s) to delete (ID only)'), ) return parser def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - endpoint_id = utils.find_resource(identity_client.endpoints, - parsed_args.endpoint).id - identity_client.endpoints.delete(endpoint_id) + result = 0 + for i in parsed_args.endpoint: + try: + endpoint_id = utils.find_resource( + identity_client.endpoints, i).id + identity_client.endpoints.delete(endpoint_id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete endpoint with " + "ID '%(endpoint)s': %(e)s") + % {'endpoint': i, 'e': e}) + + if result > 0: + total = len(parsed_args.endpoint) + msg = (_("%(result)s of %(total)s endpoints failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListEndpoint(command.Lister): diff --git a/openstackclient/identity/v3/federation_protocol.py b/openstackclient/identity/v3/federation_protocol.py index 0369bc3d06..3fde9027e7 100644 --- a/openstackclient/identity/v3/federation_protocol.py +++ b/openstackclient/identity/v3/federation_protocol.py @@ -17,6 +17,7 @@ import logging from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils import six @@ -71,14 +72,15 @@ def take_action(self, parsed_args): class DeleteProtocol(command.Command): - """Delete federation protocol""" + """Delete federation protocol(s)""" def get_parser(self, prog_name): parser = super(DeleteProtocol, self).get_parser(prog_name) parser.add_argument( 'federation_protocol', metavar='', - help=_('Federation protocol to delete (name or ID)'), + nargs='+', + help=_('Federation protocol(s) to delete (name or ID)'), ) parser.add_argument( '--identity-provider', @@ -92,8 +94,22 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - identity_client.federation.protocols.delete( - parsed_args.identity_provider, parsed_args.federation_protocol) + result = 0 + for i in parsed_args.federation_protocol: + try: + identity_client.federation.protocols.delete( + parsed_args.identity_provider, i) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete federation protocol " + "with name or ID '%(protocol)s': %(e)s") + % {'protocol': i, 'e': e}) + + if result > 0: + total = len(parsed_args.federation_protocol) + msg = (_("%(result)s of %(total)s federation protocols failed" + " to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListProtocols(command.Lister): diff --git a/openstackclient/identity/v3/identity_provider.py b/openstackclient/identity/v3/identity_provider.py index 6fc9b13ce2..0453e8883e 100644 --- a/openstackclient/identity/v3/identity_provider.py +++ b/openstackclient/identity/v3/identity_provider.py @@ -16,6 +16,7 @@ import logging from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils import six @@ -93,21 +94,35 @@ def take_action(self, parsed_args): class DeleteIdentityProvider(command.Command): - """Delete identity provider""" + """Delete identity provider(s)""" def get_parser(self, prog_name): parser = super(DeleteIdentityProvider, self).get_parser(prog_name) parser.add_argument( 'identity_provider', metavar='', - help=_('Identity provider to delete'), + nargs='+', + help=_('Identity provider(s) to delete'), ) return parser def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - identity_client.federation.identity_providers.delete( - parsed_args.identity_provider) + result = 0 + for i in parsed_args.identity_provider: + try: + identity_client.federation.identity_providers.delete(i) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete identity providers with " + "name or ID '%(provider)s': %(e)s") + % {'provider': i, 'e': e}) + + if result > 0: + total = len(parsed_args.identity_provider) + msg = (_("%(result)s of %(total)s identity providers failed" + " to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListIdentityProvider(command.Lister): diff --git a/openstackclient/identity/v3/mapping.py b/openstackclient/identity/v3/mapping.py index 69c141b128..09181a0bad 100644 --- a/openstackclient/identity/v3/mapping.py +++ b/openstackclient/identity/v3/mapping.py @@ -111,21 +111,35 @@ def take_action(self, parsed_args): class DeleteMapping(command.Command): - """Delete mapping""" + """Delete mapping(s)""" def get_parser(self, prog_name): parser = super(DeleteMapping, self).get_parser(prog_name) parser.add_argument( 'mapping', metavar='', - help=_('Mapping to delete'), + nargs='+', + help=_('Mapping(s) to delete'), ) return parser def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - - identity_client.federation.mappings.delete(parsed_args.mapping) + result = 0 + for i in parsed_args.mapping: + try: + identity_client.federation.mappings.delete(i) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete mapping with name or " + "ID '%(mapping)s': %(e)s") + % {'mapping': i, 'e': e}) + + if result > 0: + total = len(parsed_args.mapping) + msg = (_("%(result)s of %(total)s mappings failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListMapping(command.Lister): diff --git a/openstackclient/identity/v3/policy.py b/openstackclient/identity/v3/policy.py index 79215cab42..596eae018a 100644 --- a/openstackclient/identity/v3/policy.py +++ b/openstackclient/identity/v3/policy.py @@ -15,13 +15,19 @@ """Identity v3 Policy 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 CreatePolicy(command.ShowOne): """Create new policy""" @@ -55,20 +61,35 @@ def take_action(self, parsed_args): class DeletePolicy(command.Command): - """Delete policy""" + """Delete policy(s)""" def get_parser(self, prog_name): parser = super(DeletePolicy, self).get_parser(prog_name) parser.add_argument( 'policy', metavar='', - help=_('Policy to delete'), + nargs='+', + help=_('Policy(s) to delete'), ) return parser def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - identity_client.policies.delete(parsed_args.policy) + result = 0 + for i in parsed_args.policy: + try: + identity_client.policies.delete(i) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete policy with name or " + "ID '%(policy)s': %(e)s") + % {'policy': i, 'e': e}) + + if result > 0: + total = len(parsed_args.policy) + msg = (_("%(result)s of %(total)s policys failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListPolicy(command.Lister): diff --git a/openstackclient/identity/v3/region.py b/openstackclient/identity/v3/region.py index d714cd05e4..b7c51f9314 100644 --- a/openstackclient/identity/v3/region.py +++ b/openstackclient/identity/v3/region.py @@ -13,13 +13,19 @@ """Identity v3 Region 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 CreateRegion(command.ShowOne): """Create new region""" @@ -60,21 +66,35 @@ def take_action(self, parsed_args): class DeleteRegion(command.Command): - """Delete region""" + """Delete region(s)""" def get_parser(self, prog_name): parser = super(DeleteRegion, self).get_parser(prog_name) parser.add_argument( 'region', metavar='', - help=_('Region ID to delete'), + nargs='+', + help=_('Region ID(s) to delete'), ) return parser def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - - identity_client.regions.delete(parsed_args.region) + result = 0 + for i in parsed_args.region: + try: + identity_client.regions.delete(i) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete region with " + "ID '%(region)s': %(e)s") + % {'region': i, 'e': e}) + + if result > 0: + total = len(parsed_args.region) + msg = (_("%(result)s of %(total)s regions failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListRegion(command.Lister): diff --git a/openstackclient/identity/v3/service.py b/openstackclient/identity/v3/service.py index 7b23ae299c..97e64dc601 100644 --- a/openstackclient/identity/v3/service.py +++ b/openstackclient/identity/v3/service.py @@ -15,7 +15,10 @@ """Identity v3 Service action implementations""" +import logging + from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils import six @@ -23,6 +26,9 @@ from openstackclient.identity import common +LOG = logging.getLogger(__name__) + + class CreateService(command.ShowOne): """Create new service""" @@ -75,23 +81,36 @@ def take_action(self, parsed_args): class DeleteService(command.Command): - """Delete service""" + """Delete service(s)""" def get_parser(self, prog_name): parser = super(DeleteService, self).get_parser(prog_name) parser.add_argument( 'service', metavar='', - help=_('Service to delete (type, name or ID)'), + nargs='+', + help=_('Service(s) to delete (type, name or ID)'), ) return parser def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - - service = common.find_service(identity_client, parsed_args.service) - - identity_client.services.delete(service.id) + result = 0 + for i in parsed_args.service: + try: + service = common.find_service(identity_client, i) + identity_client.services.delete(service.id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete consumer with type, " + "name or ID '%(service)s': %(e)s") + % {'service': i, 'e': e}) + + if result > 0: + total = len(parsed_args.service) + msg = (_("%(result)s of %(total)s services failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListService(command.Lister): diff --git a/openstackclient/identity/v3/service_provider.py b/openstackclient/identity/v3/service_provider.py index 1f95def81f..440eba4090 100644 --- a/openstackclient/identity/v3/service_provider.py +++ b/openstackclient/identity/v3/service_provider.py @@ -13,13 +13,19 @@ """Service Provider 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 CreateServiceProvider(command.ShowOne): """Create new service provider""" @@ -81,21 +87,35 @@ def take_action(self, parsed_args): class DeleteServiceProvider(command.Command): - """Delete service provider""" + """Delete service provider(s)""" def get_parser(self, prog_name): parser = super(DeleteServiceProvider, self).get_parser(prog_name) parser.add_argument( 'service_provider', metavar='', - help=_('Service provider to delete'), + nargs='+', + help=_('Service provider(s) to delete'), ) return parser def take_action(self, parsed_args): service_client = self.app.client_manager.identity - service_client.federation.service_providers.delete( - parsed_args.service_provider) + result = 0 + for i in parsed_args.service_provider: + try: + service_client.federation.service_providers.delete(i) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete service provider with " + "name or ID '%(provider)s': %(e)s") + % {'provider': i, 'e': e}) + + if result > 0: + total = len(parsed_args.service_provider) + msg = (_("%(result)s of %(total)s service providers failed" + " to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListServiceProvider(command.Lister): diff --git a/openstackclient/tests/identity/v3/test_consumer.py b/openstackclient/tests/identity/v3/test_consumer.py index 4a8cf0871d..d90c7347cf 100644 --- a/openstackclient/tests/identity/v3/test_consumer.py +++ b/openstackclient/tests/identity/v3/test_consumer.py @@ -83,7 +83,7 @@ def test_delete_consumer(self): identity_fakes.consumer_id, ] verifylist = [ - ('consumer', identity_fakes.consumer_id), + ('consumer', [identity_fakes.consumer_id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/identity/v3/test_domain.py b/openstackclient/tests/identity/v3/test_domain.py index 17bcee065d..5e09402172 100644 --- a/openstackclient/tests/identity/v3/test_domain.py +++ b/openstackclient/tests/identity/v3/test_domain.py @@ -182,7 +182,7 @@ def test_domain_delete(self): self.domain.id, ] verifylist = [ - ('domain', self.domain.id), + ('domain', [self.domain.id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/identity/v3/test_endpoint.py b/openstackclient/tests/identity/v3/test_endpoint.py index 184e14a4c5..042763199e 100644 --- a/openstackclient/tests/identity/v3/test_endpoint.py +++ b/openstackclient/tests/identity/v3/test_endpoint.py @@ -273,7 +273,7 @@ def test_endpoint_delete(self): identity_fakes.endpoint_id, ] verifylist = [ - ('endpoint', identity_fakes.endpoint_id), + ('endpoint', [identity_fakes.endpoint_id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/identity/v3/test_identity_provider.py b/openstackclient/tests/identity/v3/test_identity_provider.py index 8561fab99b..1ec6105270 100644 --- a/openstackclient/tests/identity/v3/test_identity_provider.py +++ b/openstackclient/tests/identity/v3/test_identity_provider.py @@ -255,7 +255,7 @@ def test_delete_identity_provider(self): identity_fakes.idp_id, ] verifylist = [ - ('identity_provider', identity_fakes.idp_id), + ('identity_provider', [identity_fakes.idp_id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/identity/v3/test_mappings.py b/openstackclient/tests/identity/v3/test_mappings.py index af7b135d0e..6aa1a6e5db 100644 --- a/openstackclient/tests/identity/v3/test_mappings.py +++ b/openstackclient/tests/identity/v3/test_mappings.py @@ -91,7 +91,7 @@ def test_delete_mapping(self): identity_fakes.mapping_id ] verifylist = [ - ('mapping', identity_fakes.mapping_id) + ('mapping', [identity_fakes.mapping_id]) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/identity/v3/test_protocol.py b/openstackclient/tests/identity/v3/test_protocol.py index 238b0ff8f1..f718b27bd3 100644 --- a/openstackclient/tests/identity/v3/test_protocol.py +++ b/openstackclient/tests/identity/v3/test_protocol.py @@ -88,7 +88,7 @@ def test_delete_identity_provider(self): identity_fakes.protocol_id ] verifylist = [ - ('federation_protocol', identity_fakes.protocol_id), + ('federation_protocol', [identity_fakes.protocol_id]), ('identity_provider', identity_fakes.idp_id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/identity/v3/test_region.py b/openstackclient/tests/identity/v3/test_region.py index 44e4814b01..41ee5ce9a6 100644 --- a/openstackclient/tests/identity/v3/test_region.py +++ b/openstackclient/tests/identity/v3/test_region.py @@ -153,7 +153,7 @@ def test_region_delete_no_options(self): identity_fakes.region_id, ] verifylist = [ - ('region', identity_fakes.region_id), + ('region', [identity_fakes.region_id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/identity/v3/test_service.py b/openstackclient/tests/identity/v3/test_service.py index 1e70383f5a..a1f85adc7b 100644 --- a/openstackclient/tests/identity/v3/test_service.py +++ b/openstackclient/tests/identity/v3/test_service.py @@ -200,7 +200,7 @@ def test_service_delete_no_options(self): identity_fakes.service_name, ] verifylist = [ - ('service', identity_fakes.service_name), + ('service', [identity_fakes.service_name]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/identity/v3/test_service_provider.py b/openstackclient/tests/identity/v3/test_service_provider.py index 428057949b..f5270d839c 100644 --- a/openstackclient/tests/identity/v3/test_service_provider.py +++ b/openstackclient/tests/identity/v3/test_service_provider.py @@ -185,7 +185,7 @@ def test_delete_service_provider(self): service_fakes.sp_id, ] verifylist = [ - ('service_provider', service_fakes.sp_id), + ('service_provider', [service_fakes.sp_id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/releasenotes/notes/bug-1592906-ad67ce8736f3cd48.yaml b/releasenotes/notes/bug-1592906-ad67ce8736f3cd48.yaml new file mode 100644 index 0000000000..e674a2092e --- /dev/null +++ b/releasenotes/notes/bug-1592906-ad67ce8736f3cd48.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Support bulk deletion for identity v3 commands: ``consumer``, + ``credential``, ``domain``, ``ec2creds``, ``endpoint``, + ``federation_protocol``, ``identity_provider``, ``mapping``, + ``policy``, ``region``, ``service_provider`` and ``service``. + [Bug `1592906 `_] From 34435d9ca2e35ff02651a5fe715fc36694ecb8b1 Mon Sep 17 00:00:00 2001 From: qtang Date: Wed, 13 Jul 2016 17:28:45 +0800 Subject: [PATCH 1114/3095] Exchange the check order for the dhcp and no-dhcp The dhcp is setting with True by default and progress always jump into the first if check. So the no-dhcp option always ignored there. Check the no-dhcp option first and then the dhcp option value to avoid this. Change-Id: Ide640e2cab3936d419ca62105304ff5d4a8a2074 Closes-Bug: #1602588 --- openstackclient/network/v2/subnet.py | 3 +-- openstackclient/tests/network/v2/test_subnet.py | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 45d3442dcc..f26f680426 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -168,7 +168,7 @@ def _get_attrs(client_manager, parsed_args, is_create=True): attrs['allocation_pools'] = parsed_args.allocation_pools if parsed_args.dhcp: attrs['enable_dhcp'] = True - elif parsed_args.no_dhcp: + if parsed_args.no_dhcp: attrs['enable_dhcp'] = False if ('dns_nameservers' in parsed_args and parsed_args.dns_nameservers is not None): @@ -223,7 +223,6 @@ def get_parser(self, prog_name): dhcp_enable_group.add_argument( '--dhcp', action='store_true', - default=True, help=_("Enable DHCP (default)") ) dhcp_enable_group.add_argument( diff --git a/openstackclient/tests/network/v2/test_subnet.py b/openstackclient/tests/network/v2/test_subnet.py index 46af44fb5b..e24b49e803 100644 --- a/openstackclient/tests/network/v2/test_subnet.py +++ b/openstackclient/tests/network/v2/test_subnet.py @@ -236,7 +236,6 @@ def test_create_default_options(self): self.network.create_subnet.assert_called_once_with(**{ 'cidr': self._subnet.cidr, - 'enable_dhcp': self._subnet.enable_dhcp, 'ip_version': self._subnet.ip_version, 'name': self._subnet.name, 'network_id': self._subnet.network_id, @@ -410,7 +409,6 @@ def test_create_with_network_segment(self): self.network.create_subnet.assert_called_once_with(**{ 'cidr': self._subnet.cidr, - 'enable_dhcp': self._subnet.enable_dhcp, 'ip_version': self._subnet.ip_version, 'name': self._subnet.name, 'network_id': self._subnet.network_id, From 07e97b1a8ab76a253a483a5290bcf93fa41c7cc4 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Tue, 19 Jul 2016 21:14:19 +0000 Subject: [PATCH 1115/3095] Updated from global requirements Change-Id: I5aa5e608f41e0c2d900ed35ae06db1552a71ea6e --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 282402dcde..e6b3b1b467 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ six>=1.9.0 # MIT Babel>=2.3.4 # BSD cliff!=1.16.0,!=1.17.0,>=1.15.0 # Apache-2.0 keystoneauth1>=2.7.0 # Apache-2.0 -openstacksdk>=0.8.6 # Apache-2.0 +openstacksdk>=0.9.0 # Apache-2.0 os-client-config>=1.13.1 # Apache-2.0 osc-lib>=0.3.0 # Apache-2.0 oslo.config>=3.12.0 # Apache-2.0 From 0aa2304f38da0696576827fc4d71f2cf7cc13ad6 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Fri, 17 Jun 2016 15:06:40 +0800 Subject: [PATCH 1116/3095] Transfer "ip floating CRUD" to "floating ip CRUD" This patch does the following things to transfer "ip floating xxx" to "floating ip xxx": * Add new command "floating ip create/delete/list/show", and doc. * Deprecate "ip floating create/delete/list/show" command. Change-Id: Ib071acaac81988431244e858bddafa7f93403df5 Implements: blueprint rework-ip-commands Closes-bug: 1555990 Co-Authored-By: Dean Troyer --- doc/source/command-objects/floating-ip.rst | 82 +++++++++++++ doc/source/command-objects/ip-floating.rst | 4 + functional/tests/compute/v2/test_server.py | 11 +- .../tests/network/v2/test_floating_ip.py | 8 +- openstackclient/network/v2/floating_ip.py | 109 +++++++++++++++++- .../ip-command-rework-8d3fe0858f51e6b8.yaml | 5 + setup.cfg | 13 ++- 7 files changed, 214 insertions(+), 18 deletions(-) create mode 100644 doc/source/command-objects/floating-ip.rst diff --git a/doc/source/command-objects/floating-ip.rst b/doc/source/command-objects/floating-ip.rst new file mode 100644 index 0000000000..093bb8e481 --- /dev/null +++ b/doc/source/command-objects/floating-ip.rst @@ -0,0 +1,82 @@ +=========== +floating ip +=========== + +Compute v2, Network v2 + +floating ip create +------------------ + +Create floating IP + +.. program:: floating ip create +.. code:: bash + + os floating ip create + [--subnet ] + [--port ] + [--floating-ip-address ] + [--fixed-ip-address ] + + +.. option:: --subnet + + Subnet on which you want to create the floating IP (name or ID) + *Network version 2 only* + +.. option:: --port + + Port to be associated with the floating IP (name or ID) + *Network version 2 only* + +.. option:: --floating-ip-address + + Floating IP address + *Network version 2 only* + +.. option:: --fixed-ip-address + + Fixed IP address mapped to the floating IP + *Network version 2 only* + +.. describe:: + + Network to allocate floating IP from (name or ID) + +floating ip delete +------------------ + +Delete floating IP(s) + +.. program:: floating ip delete +.. code:: bash + + os floating ip delete [ ...] + +.. describe:: + + Floating IP(s) to delete (IP address or ID) + +floating ip list +---------------- + +List floating IP(s) + +.. program:: floating ip list +.. code:: bash + + os floating ip list + +floating ip show +---------------- + +Display floating IP details + +.. program:: floating ip show +.. code:: bash + + os floating ip show + +.. describe:: + + Floating IP to display (IP address or ID) diff --git a/doc/source/command-objects/ip-floating.rst b/doc/source/command-objects/ip-floating.rst index 378812d123..7b38a382d8 100644 --- a/doc/source/command-objects/ip-floating.rst +++ b/doc/source/command-objects/ip-floating.rst @@ -29,6 +29,7 @@ ip floating create ------------------ Create new floating IP address +(Deprecated, please use ``floating ip create`` instead) .. program:: ip floating create .. code:: bash @@ -68,6 +69,7 @@ ip floating delete ------------------ Delete floating IP(s) +(Deprecated, please use ``floating ip delete`` instead) .. program:: ip floating delete .. code:: bash @@ -83,6 +85,7 @@ ip floating list ---------------- List floating IP addresses +(Deprecated, please use ``floating ip list`` instead) .. program:: ip floating list .. code:: bash @@ -114,6 +117,7 @@ ip floating show ---------------- Display floating IP details +(Deprecated, please use ``floating ip show`` instead) .. program:: ip floating show .. code:: bash diff --git a/functional/tests/compute/v2/test_server.py b/functional/tests/compute/v2/test_server.py index 511ac37293..a9d0e9c132 100644 --- a/functional/tests/compute/v2/test_server.py +++ b/functional/tests/compute/v2/test_server.py @@ -249,24 +249,25 @@ def test_server_attach_detach_floating_ip(self): self.wait_for_status("ACTIVE") # attach ip opts = self.get_opts(["id", "floating_ip_address"]) - raw_output = self.openstack('ip floating create ' + + raw_output = self.openstack('floating ip create ' + self.IP_POOL + opts) ip, ipid, rol = tuple(raw_output.split('\n')) self.assertNotEqual("", ipid) self.assertNotEqual("", ip) - raw_output = self.openstack('ip floating add ' + ip + ' ' + self.NAME) + raw_output = self.openstack('server add floating ip ' + self.NAME + + ' ' + ip) self.assertEqual("", raw_output) raw_output = self.openstack('server show ' + self.NAME) self.assertIn(ip, raw_output) # detach ip - raw_output = self.openstack('ip floating remove ' + ip + ' ' + - self.NAME) + raw_output = self.openstack('server remove floating ip ' + self.NAME + + ' ' + ip) self.assertEqual("", raw_output) raw_output = self.openstack('server show ' + self.NAME) self.assertNotIn(ip, raw_output) - raw_output = self.openstack('ip floating delete ' + ipid) + raw_output = self.openstack('floating ip delete ' + ipid) self.assertEqual("", raw_output) def test_server_reboot(self): diff --git a/functional/tests/network/v2/test_floating_ip.py b/functional/tests/network/v2/test_floating_ip.py index 3b314b7050..7e7c63f4d4 100644 --- a/functional/tests/network/v2/test_floating_ip.py +++ b/functional/tests/network/v2/test_floating_ip.py @@ -35,12 +35,12 @@ def setUpClass(cls): ) opts = cls.get_opts(cls.FIELDS) raw_output = cls.openstack( - 'ip floating create ' + cls.NETWORK_NAME + opts) + 'floating ip create ' + cls.NETWORK_NAME + opts) cls.ID = raw_output.strip('\n') @classmethod def tearDownClass(cls): - raw_output = cls.openstack('ip floating delete ' + cls.ID) + raw_output = cls.openstack('floating ip delete ' + cls.ID) cls.assertOutput('', raw_output) raw_output = cls.openstack('subnet delete ' + cls.SUBNET_NAME) cls.assertOutput('', raw_output) @@ -49,10 +49,10 @@ def tearDownClass(cls): def test_floating_ip_list(self): opts = self.get_opts(self.HEADERS) - raw_output = self.openstack('ip floating list' + opts) + raw_output = self.openstack('floating ip list' + opts) self.assertIn(self.ID, raw_output) def test_floating_ip_show(self): opts = self.get_opts(self.FIELDS) - raw_output = self.openstack('ip floating show ' + self.ID + opts) + raw_output = self.openstack('floating ip show ' + self.ID + opts) self.assertEqual(self.ID + "\n", raw_output) diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index 8fbf049e3f..454335f1a5 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -13,6 +13,8 @@ """IP Floating action implementations""" +import logging + from osc_lib import utils from openstackclient.i18n import _ @@ -31,25 +33,26 @@ def _get_attrs(client_manager, parsed_args): attrs = {} network_client = client_manager.network + # Name of a network could be empty string. if parsed_args.network is not None: network = network_client.find_network(parsed_args.network, ignore_missing=False) attrs['floating_network_id'] = network.id - if parsed_args.subnet is not None: + if parsed_args.subnet: subnet = network_client.find_subnet(parsed_args.subnet, ignore_missing=False) attrs['subnet_id'] = subnet.id - if parsed_args.port is not None: + if parsed_args.port: port = network_client.find_port(parsed_args.port, ignore_missing=False) attrs['port_id'] = port.id - if parsed_args.floating_ip_address is not None: + if parsed_args.floating_ip_address: attrs['floating_ip_address'] = parsed_args.floating_ip_address - if parsed_args.fixed_ip_address is not None: + if parsed_args.fixed_ip_address: attrs['fixed_ip_address'] = parsed_args.fixed_ip_address return attrs @@ -110,6 +113,30 @@ def take_action_compute(self, client, parsed_args): return (columns, data) +class CreateIPFloating(CreateFloatingIP): + """Create floating IP""" + + # TODO(tangchen): Remove this class and ``ip floating create`` command + # two cycles after Mitaka. + + # This notifies cliff to not display the help for this command + deprecated = True + + log = logging.getLogger('deprecated') + + def take_action_network(self, client, parsed_args): + self.log.warning(_('This command has been deprecated. ' + 'Please use "floating ip create" instead.')) + return super(CreateIPFloating, self).take_action_network( + client, parsed_args) + + def take_action_compute(self, client, parsed_args): + self.log.warning(_('This command has been deprecated. ' + 'Please use "floating ip create" instead.')) + return super(CreateIPFloating, self).take_action_compute( + client, parsed_args) + + class DeleteFloatingIP(common.NetworkAndComputeDelete): """Delete floating IP(s)""" @@ -135,6 +162,30 @@ def take_action_compute(self, client, parsed_args): client.floating_ips.delete(obj.id) +class DeleteIPFloating(DeleteFloatingIP): + """Delete floating IP(s)""" + + # TODO(tangchen): Remove this class and ``ip floating delete`` command + # two cycles after Mitaka. + + # This notifies cliff to not display the help for this command + deprecated = True + + log = logging.getLogger('deprecated') + + def take_action_network(self, client, parsed_args): + self.log.warning(_('This command has been deprecated. ' + 'Please use "floating ip delete" instead.')) + return super(DeleteIPFloating, self).take_action_network( + client, parsed_args) + + def take_action_compute(self, client, parsed_args): + self.log.warning(_('This command has been deprecated. ' + 'Please use "floating ip delete" instead.')) + return super(DeleteIPFloating, self).take_action_compute( + client, parsed_args) + + class ListFloatingIP(common.NetworkAndComputeLister): """List floating IP(s)""" @@ -186,8 +237,32 @@ def take_action_compute(self, client, parsed_args): ) for s in data)) +class ListIPFloating(ListFloatingIP): + """List floating IP(s)""" + + # TODO(tangchen): Remove this class and ``ip floating list`` command + # two cycles after Mitaka. + + # This notifies cliff to not display the help for this command + deprecated = True + + log = logging.getLogger('deprecated') + + def take_action_network(self, client, parsed_args): + self.log.warning(_('This command has been deprecated. ' + 'Please use "floating ip list" instead.')) + return super(ListIPFloating, self).take_action_network( + client, parsed_args) + + def take_action_compute(self, client, parsed_args): + self.log.warning(_('This command has been deprecated. ' + 'Please use "floating ip list" instead.')) + return super(ListIPFloating, self).take_action_compute( + client, parsed_args) + + class ShowFloatingIP(common.NetworkAndComputeShowOne): - """Show floating IP details""" + """Display floating IP details""" def update_parser_common(self, parser): parser.add_argument( @@ -211,3 +286,27 @@ def take_action_compute(self, client, parsed_args): columns = _get_columns(obj._info) data = utils.get_dict_properties(obj._info, columns) return (columns, data) + + +class ShowIPFloating(ShowFloatingIP): + """Display floating IP details""" + + # TODO(tangchen): Remove this class and ``ip floating show`` command + # two cycles after Mitaka. + + # This notifies cliff to not display the help for this command + deprecated = True + + log = logging.getLogger('deprecated') + + def take_action_network(self, client, parsed_args): + self.log.warning(_('This command has been deprecated. ' + 'Please use "floating ip show" instead.')) + return super(ShowIPFloating, self).take_action_network( + client, parsed_args) + + def take_action_compute(self, client, parsed_args): + self.log.warning(_('This command has been deprecated. ' + 'Please use "floating ip show" instead.')) + return super(ShowIPFloating, self).take_action_compute( + client, parsed_args) diff --git a/releasenotes/notes/ip-command-rework-8d3fe0858f51e6b8.yaml b/releasenotes/notes/ip-command-rework-8d3fe0858f51e6b8.yaml index 6e95eb08aa..c5b7f22890 100644 --- a/releasenotes/notes/ip-command-rework-8d3fe0858f51e6b8.yaml +++ b/releasenotes/notes/ip-command-rework-8d3fe0858f51e6b8.yaml @@ -10,6 +10,9 @@ features: - Add new commands ``server add/remove fixed ip``. They are used to replace the old commands ``ip fixed add/remove``. [Blueprint rework-ip-commands ``_] + - Add new commands ``floating ip create/delete/list/show``. It is used to + replace the old commands ``ip floating create/delete/list/show``. + [Blueprint rework-ip-commands ``_] deprecations: - Deprecate command ``ip floating pool list``. [Blueprint rework-ip-commands ``_] @@ -17,3 +20,5 @@ deprecations: [Blueprint rework-ip-commands ``_] - Deprecate commands ``ip fixed add/remove``. [Blueprint rework-ip-commands ``_] + - Deprecate commands ``ip floating create/delete/list/show``. + [Blueprint rework-ip-commands ``_] diff --git a/setup.cfg b/setup.cfg index 0dd368a381..2c7c049c10 100644 --- a/setup.cfg +++ b/setup.cfg @@ -337,15 +337,20 @@ openstack.network.v2 = address_scope_set = openstackclient.network.v2.address_scope:SetAddressScope address_scope_show = openstackclient.network.v2.address_scope:ShowAddressScope + floating_ip_create = openstackclient.network.v2.floating_ip:CreateFloatingIP + floating_ip_delete = openstackclient.network.v2.floating_ip:DeleteFloatingIP + floating_ip_list = openstackclient.network.v2.floating_ip:ListFloatingIP + floating_ip_show = openstackclient.network.v2.floating_ip:ShowFloatingIP + floating_ip_pool_list = openstackclient.network.v2.floating_ip_pool:ListFloatingIPPool ip_availability_list = openstackclient.network.v2.ip_availability:ListIPAvailability ip_availability_show = openstackclient.network.v2.ip_availability:ShowIPAvailability - ip_floating_create = openstackclient.network.v2.floating_ip:CreateFloatingIP - ip_floating_delete = openstackclient.network.v2.floating_ip:DeleteFloatingIP - ip_floating_list = openstackclient.network.v2.floating_ip:ListFloatingIP - ip_floating_show = openstackclient.network.v2.floating_ip:ShowFloatingIP + ip_floating_create = openstackclient.network.v2.floating_ip:CreateIPFloating + ip_floating_delete = openstackclient.network.v2.floating_ip:DeleteIPFloating + ip_floating_list = openstackclient.network.v2.floating_ip:ListIPFloating + ip_floating_show = openstackclient.network.v2.floating_ip:ShowIPFloating ip_floating_pool_list = openstackclient.network.v2.floating_ip_pool:ListIPFloatingPool From 5e8957ef7f4acea1ece06378c050021b64ea3f6f Mon Sep 17 00:00:00 2001 From: Sheel Rana Date: Sun, 1 May 2016 12:07:46 +0530 Subject: [PATCH 1117/3095] Show project access for volume type OSC does not support to show project access details for private volume types. This patch will provide support for showing project access details for private volume types. Closes-Bug:#1554891 Implements: bp cinder-command-support Change-Id: I218fb07a6e69033e9f8570748eee1df8df9d6fdc --- doc/source/command-objects/volume-type.rst | 1 - openstackclient/tests/volume/v2/fakes.py | 33 +++++++++ openstackclient/tests/volume/v2/test_type.py | 72 +++++++++++++++++++ openstackclient/volume/v2/volume_type.py | 18 ++++- ...t_volume_type_access-f7d9aa6159f757ea.yaml | 10 +++ 5 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/list_volume_type_access-f7d9aa6159f757ea.yaml diff --git a/doc/source/command-objects/volume-type.rst b/doc/source/command-objects/volume-type.rst index e2a277b04a..ddc8933586 100644 --- a/doc/source/command-objects/volume-type.rst +++ b/doc/source/command-objects/volume-type.rst @@ -145,7 +145,6 @@ volume type show Display volume type details - .. program:: volume type show .. code:: bash diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py index 74e30a41dd..6809bebd3f 100644 --- a/openstackclient/tests/volume/v2/fakes.py +++ b/openstackclient/tests/volume/v2/fakes.py @@ -76,6 +76,38 @@ def create_one_transfer(attrs=None): return transfer +class FakeTypeAccess(object): + """Fake one or more volume type access.""" + + @staticmethod + def create_one_type_access(attrs=None): + """Create a fake volume type access for project. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object, with Volume_type_ID and Project_ID. + """ + if attrs is None: + attrs = {} + + # Set default attributes. + type_access_attrs = { + 'volume_type_id': 'volume-type-id-' + uuid.uuid4().hex, + 'project_id': 'project-id-' + uuid.uuid4().hex, + } + + # Overwrite default attributes. + type_access_attrs.update(attrs) + + type_access = fakes.FakeResource( + None, + type_access_attrs, + loaded=True) + + return type_access + + class FakeServiceClient(object): def __init__(self, **kwargs): @@ -666,6 +698,7 @@ def create_one_type(attrs=None, methods=None): "name": 'type-name-' + uuid.uuid4().hex, "description": 'type-description-' + uuid.uuid4().hex, "extra_specs": {"foo": "bar"}, + "is_public": True, } # Overwrite default attributes. diff --git a/openstackclient/tests/volume/v2/test_type.py b/openstackclient/tests/volume/v2/test_type.py index a7db2e49e0..e148bba420 100644 --- a/openstackclient/tests/volume/v2/test_type.py +++ b/openstackclient/tests/volume/v2/test_type.py @@ -13,6 +13,7 @@ # import copy +import mock from osc_lib import exceptions from osc_lib import utils @@ -46,6 +47,7 @@ class TestTypeCreate(TestType): columns = ( 'description', 'id', + 'is_public', 'name', ) @@ -56,6 +58,7 @@ def setUp(self): self.data = ( self.new_volume_type.description, self.new_volume_type.id, + True, self.new_volume_type.name, ) @@ -357,8 +360,10 @@ def test_type_set_project_access(self): class TestTypeShow(TestType): columns = ( + 'access_project_ids', 'description', 'id', + 'is_public', 'name', 'properties', ) @@ -368,8 +373,10 @@ def setUp(self): self.volume_type = volume_fakes.FakeType.create_one_type() self.data = ( + None, self.volume_type.description, self.volume_type.id, + True, self.volume_type.name, utils.format_dict(self.volume_type.extra_specs) ) @@ -394,6 +401,71 @@ def test_type_show(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_type_show_with_access(self): + arglist = [ + self.volume_type.id + ] + verifylist = [ + ("volume_type", self.volume_type.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + private_type = volume_fakes.FakeType.create_one_type( + attrs={'is_public': False}) + type_access_list = volume_fakes.FakeTypeAccess.create_one_type_access() + with mock.patch.object(self.types_mock, 'get', + return_value=private_type): + with mock.patch.object(self.types_access_mock, 'list', + return_value=[type_access_list]): + columns, data = self.cmd.take_action(parsed_args) + self.types_mock.get.assert_called_once_with( + self.volume_type.id) + self.types_access_mock.list.assert_called_once_with( + private_type.id) + + self.assertEqual(self.columns, columns) + private_type_data = ( + utils.format_list([type_access_list.project_id]), + private_type.description, + private_type.id, + private_type.is_public, + private_type.name, + utils.format_dict(private_type.extra_specs) + ) + self.assertEqual(private_type_data, data) + + def test_type_show_with_list_access_exec(self): + arglist = [ + self.volume_type.id + ] + verifylist = [ + ("volume_type", self.volume_type.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + private_type = volume_fakes.FakeType.create_one_type( + attrs={'is_public': False}) + with mock.patch.object(self.types_mock, 'get', + return_value=private_type): + with mock.patch.object(self.types_access_mock, 'list', + side_effect=Exception()): + columns, data = self.cmd.take_action(parsed_args) + self.types_mock.get.assert_called_once_with( + self.volume_type.id) + self.types_access_mock.list.assert_called_once_with( + private_type.id) + + self.assertEqual(self.columns, columns) + private_type_data = ( + None, + private_type.description, + private_type.id, + private_type.is_public, + private_type.name, + utils.format_dict(private_type.extra_specs) + ) + self.assertEqual(private_type_data, data) + class TestTypeUnset(TestType): diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index ac11785c26..62d619d086 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -282,8 +282,24 @@ def take_action(self, parsed_args): volume_client = self.app.client_manager.volume volume_type = utils.find_resource( volume_client.volume_types, parsed_args.volume_type) - properties = utils.format_dict(volume_type._info.pop('extra_specs')) + properties = utils.format_dict( + volume_type._info.pop('extra_specs', {})) volume_type._info.update({'properties': properties}) + access_project_ids = None + if not volume_type.is_public: + try: + volume_type_access = volume_client.volume_type_access.list( + volume_type.id) + project_ids = [utils.get_field(item, 'project_id') + for item in volume_type_access] + # TODO(Rui Chen): This format list case can be removed after + # patch https://review.openstack.org/#/c/330223/ merged. + access_project_ids = utils.format_list(project_ids) + except Exception as e: + msg = _('Failed to get access project list for volume type ' + '%(type)s: %(e)s') + LOG.error(msg % {'type': volume_type.id, 'e': e}) + volume_type._info.update({'access_project_ids': access_project_ids}) return zip(*sorted(six.iteritems(volume_type._info))) diff --git a/releasenotes/notes/list_volume_type_access-f7d9aa6159f757ea.yaml b/releasenotes/notes/list_volume_type_access-f7d9aa6159f757ea.yaml new file mode 100644 index 0000000000..aba3f75bb6 --- /dev/null +++ b/releasenotes/notes/list_volume_type_access-f7d9aa6159f757ea.yaml @@ -0,0 +1,10 @@ +--- +features: + - | + Show project access details for private volume type. + + An user can list projects which have access to + a specific private volume type by using + ``volume type show `` + + [Bug `1554891 `_] From e7fa8920d069f7625efc1312778818fd7b732b61 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Wed, 20 Jul 2016 19:12:02 +0800 Subject: [PATCH 1118/3095] Set identity v3 client in networkv2 fake Set identity v3 client in fake so that we needn't set it everytime when we use it in unit test. Change-Id: I9ed71056a357642e5aef7b670a7c85918aca59b9 --- openstackclient/tests/network/v2/fakes.py | 8 ++++++++ .../tests/network/v2/test_address_scope.py | 18 ++++-------------- .../tests/network/v2/test_ip_availability.py | 12 ++---------- .../tests/network/v2/test_network.py | 17 ++++------------- .../tests/network/v2/test_security_group.py | 18 ++++-------------- .../network/v2/test_security_group_rule.py | 17 ++++------------- .../tests/network/v2/test_subnet.py | 18 ++++-------------- .../tests/network/v2/test_subnet_pool.py | 18 ++++-------------- 8 files changed, 34 insertions(+), 92 deletions(-) diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 8d5efe1456..1bac90365c 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -17,6 +17,7 @@ import uuid from openstackclient.tests import fakes +from openstackclient.tests.identity.v3 import fakes as identity_fakes_v3 from openstackclient.tests import utils @@ -57,6 +58,13 @@ def setUp(self): token=fakes.AUTH_TOKEN, ) + self.app.client_manager.identity = ( + identity_fakes_v3.FakeIdentityv3Client( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN, + ) + ) + class FakeAddressScope(object): """Fake one or more address scopes.""" diff --git a/openstackclient/tests/network/v2/test_address_scope.py b/openstackclient/tests/network/v2/test_address_scope.py index 16e74f46ec..502516f984 100644 --- a/openstackclient/tests/network/v2/test_address_scope.py +++ b/openstackclient/tests/network/v2/test_address_scope.py @@ -17,7 +17,6 @@ from osc_lib import exceptions from openstackclient.network.v2 import address_scope -from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes_v3 from openstackclient.tests.network.v2 import fakes as network_fakes from openstackclient.tests import utils as tests_utils @@ -30,6 +29,10 @@ def setUp(self): # Get a shortcut to the network client self.network = self.app.client_manager.network + # Get a shortcut to the ProjectManager Mock + self.projects_mock = self.app.client_manager.identity.projects + # Get a shortcut to the DomainManager Mock + self.domains_mock = self.app.client_manager.identity.domains class TestCreateAddressScope(TestAddressScope): @@ -66,20 +69,7 @@ def setUp(self): # Get the command object to test self.cmd = address_scope.CreateAddressScope(self.app, self.namespace) - # Set identity client v3. And get a shortcut to Identity client. - identity_client = identity_fakes_v3.FakeIdentityv3Client( - endpoint=fakes.AUTH_URL, - token=fakes.AUTH_TOKEN, - ) - self.app.client_manager.identity = identity_client - self.identity = self.app.client_manager.identity - - # Get a shortcut to the ProjectManager Mock - self.projects_mock = self.identity.projects self.projects_mock.get.return_value = self.project - - # Get a shortcut to the DomainManager Mock - self.domains_mock = self.identity.domains self.domains_mock.get.return_value = self.domain def test_create_no_options(self): diff --git a/openstackclient/tests/network/v2/test_ip_availability.py b/openstackclient/tests/network/v2/test_ip_availability.py index 21d44d0754..f74bf8f72e 100644 --- a/openstackclient/tests/network/v2/test_ip_availability.py +++ b/openstackclient/tests/network/v2/test_ip_availability.py @@ -16,7 +16,6 @@ from osc_lib import utils as common_utils from openstackclient.network.v2 import ip_availability -from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes from openstackclient.tests.network.v2 import fakes as network_fakes from openstackclient.tests import utils as tests_utils @@ -30,16 +29,9 @@ def setUp(self): # Get a shortcut to the network client self.network = self.app.client_manager.network - # Set identity client v3. And get a shortcut to Identity client. - identity_client = identity_fakes.FakeIdentityv3Client( - endpoint=fakes.AUTH_URL, - token=fakes.AUTH_TOKEN, - ) - self.app.client_manager.identity = identity_client - self.identity = self.app.client_manager.identity - # Get a shortcut to the ProjectManager Mock - self.projects_mock = self.identity.projects + self.projects_mock = self.app.client_manager.identity.projects + self.project = identity_fakes.FakeProject.create_one_project() self.projects_mock.get.return_value = self.project diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index aa0164033d..bb606819e0 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -35,6 +35,10 @@ def setUp(self): # Get a shortcut to the network client self.network = self.app.client_manager.network + # Get a shortcut to the ProjectManager Mock + self.projects_mock = self.app.client_manager.identity.projects + # Get a shortcut to the DomainManager Mock + self.domains_mock = self.app.client_manager.identity.domains class TestCreateNetworkIdentityV3(TestNetwork): @@ -89,20 +93,7 @@ def setUp(self): # Get the command object to test self.cmd = network.CreateNetwork(self.app, self.namespace) - # Set identity client v3. And get a shortcut to Identity client. - identity_client = identity_fakes_v3.FakeIdentityv3Client( - endpoint=fakes.AUTH_URL, - token=fakes.AUTH_TOKEN, - ) - self.app.client_manager.identity = identity_client - self.identity = self.app.client_manager.identity - - # Get a shortcut to the ProjectManager Mock - self.projects_mock = self.identity.projects self.projects_mock.get.return_value = self.project - - # Get a shortcut to the DomainManager Mock - self.domains_mock = self.identity.domains self.domains_mock.get.return_value = self.domain def test_create_no_options(self): diff --git a/openstackclient/tests/network/v2/test_security_group.py b/openstackclient/tests/network/v2/test_security_group.py index cea64897e6..15f4cffe05 100644 --- a/openstackclient/tests/network/v2/test_security_group.py +++ b/openstackclient/tests/network/v2/test_security_group.py @@ -18,7 +18,6 @@ from openstackclient.network.v2 import security_group from openstackclient.tests.compute.v2 import fakes as compute_fakes -from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes from openstackclient.tests.network.v2 import fakes as network_fakes from openstackclient.tests import utils as tests_utils @@ -31,6 +30,10 @@ def setUp(self): # Get a shortcut to the network client self.network = self.app.client_manager.network + # Get a shortcut to the ProjectManager Mock + self.projects_mock = self.app.client_manager.identity.projects + # Get a shortcut to the DomainManager Mock + self.domains_mock = self.app.client_manager.identity.domains class TestSecurityGroupCompute(compute_fakes.TestComputev2): @@ -72,20 +75,7 @@ def setUp(self): self.network.create_security_group = mock.Mock( return_value=self._security_group) - # Set identity client v3. And get a shortcut to Identity client. - identity_client = identity_fakes.FakeIdentityv3Client( - endpoint=fakes.AUTH_URL, - token=fakes.AUTH_TOKEN, - ) - self.app.client_manager.identity = identity_client - self.identity = self.app.client_manager.identity - - # Get a shortcut to the ProjectManager Mock - self.projects_mock = self.identity.projects self.projects_mock.get.return_value = self.project - - # Get a shortcut to the DomainManager Mock - self.domains_mock = self.identity.domains self.domains_mock.get.return_value = self.domain # Get the command object to test diff --git a/openstackclient/tests/network/v2/test_security_group_rule.py b/openstackclient/tests/network/v2/test_security_group_rule.py index 34d35629e1..170989bf28 100644 --- a/openstackclient/tests/network/v2/test_security_group_rule.py +++ b/openstackclient/tests/network/v2/test_security_group_rule.py @@ -33,6 +33,10 @@ def setUp(self): # Get a shortcut to the network client self.network = self.app.client_manager.network + # Get a shortcut to the ProjectManager Mock + self.projects_mock = self.app.client_manager.identity.projects + # Get a shortcut to the DomainManager Mock + self.domains_mock = self.app.client_manager.identity.domains class TestSecurityGroupRuleCompute(compute_fakes.TestComputev2): @@ -95,20 +99,7 @@ def setUp(self): self.network.find_security_group = mock.Mock( return_value=self._security_group) - # Set identity client v3. And get a shortcut to Identity client. - identity_client = identity_fakes.FakeIdentityv3Client( - endpoint=fakes.AUTH_URL, - token=fakes.AUTH_TOKEN, - ) - self.app.client_manager.identity = identity_client - self.identity = self.app.client_manager.identity - - # Get a shortcut to the ProjectManager Mock - self.projects_mock = self.identity.projects self.projects_mock.get.return_value = self.project - - # Get a shortcut to the DomainManager Mock - self.domains_mock = self.identity.domains self.domains_mock.get.return_value = self.domain # Get the command object to test diff --git a/openstackclient/tests/network/v2/test_subnet.py b/openstackclient/tests/network/v2/test_subnet.py index e24b49e803..e7f3e7485a 100644 --- a/openstackclient/tests/network/v2/test_subnet.py +++ b/openstackclient/tests/network/v2/test_subnet.py @@ -18,7 +18,6 @@ from osc_lib import utils from openstackclient.network.v2 import subnet as subnet_v2 -from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes_v3 from openstackclient.tests.network.v2 import fakes as network_fakes from openstackclient.tests import utils as tests_utils @@ -31,6 +30,10 @@ def setUp(self): # Get a shortcut to the network client self.network = self.app.client_manager.network + # Get a shortcut to the ProjectManager Mock + self.projects_mock = self.app.client_manager.identity.projects + # Get a shortcut to the DomainManager Mock + self.domains_mock = self.app.client_manager.identity.domains class TestCreateSubnet(TestSubnet): @@ -178,20 +181,7 @@ def setUp(self): # Get the command object to test self.cmd = subnet_v2.CreateSubnet(self.app, self.namespace) - # Set identity client v3. And get a shortcut to Identity client. - identity_client = identity_fakes_v3.FakeIdentityv3Client( - endpoint=fakes.AUTH_URL, - token=fakes.AUTH_TOKEN, - ) - self.app.client_manager.identity = identity_client - self.identity = self.app.client_manager.identity - - # Get a shortcut to the ProjectManager Mock - self.projects_mock = self.identity.projects self.projects_mock.get.return_value = self.project - - # Get a shortcut to the DomainManager Mock - self.domains_mock = self.identity.domains self.domains_mock.get.return_value = self.domain # Mock SDK calls for all tests. diff --git a/openstackclient/tests/network/v2/test_subnet_pool.py b/openstackclient/tests/network/v2/test_subnet_pool.py index 8269af0be7..4cfecef7fb 100644 --- a/openstackclient/tests/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/network/v2/test_subnet_pool.py @@ -19,7 +19,6 @@ from osc_lib import utils from openstackclient.network.v2 import subnet_pool -from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes_v3 from openstackclient.tests.network.v2 import fakes as network_fakes from openstackclient.tests import utils as tests_utils @@ -32,6 +31,10 @@ def setUp(self): # Get a shortcut to the network client self.network = self.app.client_manager.network + # Get a shortcut to the ProjectManager Mock + self.projects_mock = self.app.client_manager.identity.projects + # Get a shortcut to the DomainManager Mock + self.domains_mock = self.app.client_manager.identity.domains class TestCreateSubnetPool(TestSubnetPool): @@ -84,20 +87,7 @@ def setUp(self): self.network.find_address_scope = mock.Mock( return_value=self._address_scope) - # Set identity client. And get a shortcut to Identity client. - identity_client = identity_fakes_v3.FakeIdentityv3Client( - endpoint=fakes.AUTH_URL, - token=fakes.AUTH_TOKEN, - ) - self.app.client_manager.identity = identity_client - self.identity = self.app.client_manager.identity - - # Get a shortcut to the ProjectManager Mock - self.projects_mock = self.identity.projects self.projects_mock.get.return_value = self.project - - # Get a shortcut to the DomainManager Mock - self.domains_mock = self.identity.domains self.domains_mock.get.return_value = self.domain def test_create_no_options(self): From ba34c592a717ce8d4f1d813838e7a7bb3528d09d Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 21 Jul 2016 04:10:27 +0000 Subject: [PATCH 1119/3095] Updated from global requirements Change-Id: I58e4387636739915dd90c93ce2ee6ee9522b634f --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e6b3b1b467..0847d4eb9f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ cliff!=1.16.0,!=1.17.0,>=1.15.0 # Apache-2.0 keystoneauth1>=2.7.0 # Apache-2.0 openstacksdk>=0.9.0 # Apache-2.0 os-client-config>=1.13.1 # Apache-2.0 -osc-lib>=0.3.0 # Apache-2.0 +osc-lib>=0.4.0 # Apache-2.0 oslo.config>=3.12.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 oslo.utils>=3.15.0 # Apache-2.0 From cf20225347766240a2ebb155e6e46b85f84a3ddc Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Thu, 21 Jul 2016 15:30:01 +0800 Subject: [PATCH 1120/3095] Support to get server rdp/serial/mks type console url The patch add the support to get server rdp/serial/mks type console url, that make osc capability equal with current nova server side feature. Change-Id: I3dee2531c68563725187c8251d5ea8d4c02cca0c Closes-Bug: #1605088 --- doc/source/command-objects/console-url.rst | 12 +++++ openstackclient/compute/v2/console.py | 27 +++++++++++ .../tests/compute/v2/test_console.py | 47 +++++++++++++++++++ .../notes/bug-1605088-fea9347336764469.yaml | 4 ++ 4 files changed, 90 insertions(+) create mode 100644 releasenotes/notes/bug-1605088-fea9347336764469.yaml diff --git a/doc/source/command-objects/console-url.rst b/doc/source/command-objects/console-url.rst index 7993a7bfed..166d0a9edd 100644 --- a/doc/source/command-objects/console-url.rst +++ b/doc/source/command-objects/console-url.rst @@ -30,6 +30,18 @@ Show server's remote console URL Show SPICE console URL +.. option:: --rdp + + Show RDP console URL + +.. option:: --serial + + Show serial console URL + +.. option:: --mks + + Show WebMKS console URL + .. describe:: Server to show URL (name or ID) diff --git a/openstackclient/compute/v2/console.py b/openstackclient/compute/v2/console.py index 74bed4417b..02be99d527 100644 --- a/openstackclient/compute/v2/console.py +++ b/openstackclient/compute/v2/console.py @@ -96,6 +96,27 @@ def get_parser(self, prog_name): const='spice-html5', help=_("Show SPICE console URL") ) + type_group.add_argument( + '--rdp', + dest='url_type', + action='store_const', + const='rdp-html5', + help=_("Show RDP console URL"), + ) + type_group.add_argument( + '--serial', + dest='url_type', + action='store_const', + const='serial', + help=_("Show serial console URL"), + ) + type_group.add_argument( + '--mks', + dest='url_type', + action='store_const', + const='webmks', + help=_("Show WebMKS console URL"), + ) return parser def take_action(self, parsed_args): @@ -110,6 +131,12 @@ def take_action(self, parsed_args): data = server.get_vnc_console(parsed_args.url_type) if parsed_args.url_type in ['spice-html5']: data = server.get_spice_console(parsed_args.url_type) + if parsed_args.url_type in ['rdp-html5']: + data = server.get_rdp_console(parsed_args.url_type) + if parsed_args.url_type in ['serial']: + data = server.get_serial_console(parsed_args.url_type) + if parsed_args.url_type in ['webmks']: + data = server.get_mks_console() if not data: return ({}, {}) diff --git a/openstackclient/tests/compute/v2/test_console.py b/openstackclient/tests/compute/v2/test_console.py index 6be081263c..fe4f26ae9a 100644 --- a/openstackclient/tests/compute/v2/test_console.py +++ b/openstackclient/tests/compute/v2/test_console.py @@ -147,3 +147,50 @@ def test_console_url_show_compatible(self): old_fake_server.get_vnc_console.assert_called_once_with('novnc') self.assertEqual(old_columns, columns) self.assertEqual(old_data, data) + + def test_console_url_show_with_rdp(self): + arglist = [ + '--rdp', + 'foo_vm', + ] + verifylist = [ + ('url_type', 'rdp-html5'), + ('server', 'foo_vm'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.fake_server.get_rdp_console.assert_called_once_with( + 'rdp-html5') + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_console_url_show_with_serial(self): + arglist = [ + '--serial', + 'foo_vm', + ] + verifylist = [ + ('url_type', 'serial'), + ('server', 'foo_vm'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.fake_server.get_serial_console.assert_called_once_with( + 'serial') + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_console_url_show_with_mks(self): + arglist = [ + '--mks', + 'foo_vm', + ] + verifylist = [ + ('url_type', 'webmks'), + ('server', 'foo_vm'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.fake_server.get_mks_console.assert_called_once_with() + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) diff --git a/releasenotes/notes/bug-1605088-fea9347336764469.yaml b/releasenotes/notes/bug-1605088-fea9347336764469.yaml new file mode 100644 index 0000000000..15e0bc455d --- /dev/null +++ b/releasenotes/notes/bug-1605088-fea9347336764469.yaml @@ -0,0 +1,4 @@ +--- +features: + - Support to get server ``rdp``, ``serial``, ``mks`` type console url. + [Bug `1605088 `_] From b0c317ebdd5e5f2626ce2fbd495149336fe5df7e Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Thu, 7 Jul 2016 21:27:22 +0800 Subject: [PATCH 1121/3095] Add Support for showing flavor access list Add a attribute "access_project_id" for flavor object to display the access project id list by using "flavor show" command. Change-Id: I7f0c152b816e0ca2e32e47f9b5c1aa7663d33b6d Closes-Bug:#1575461 --- openstackclient/compute/v2/flavor.py | 20 +++++++++ openstackclient/tests/compute/v2/fakes.py | 29 ++++++++++++ .../tests/compute/v2/test_flavor.py | 45 ++++++++++++++++++- .../notes/bug-1575461-3fed33e53795684a.yaml | 5 +++ 4 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/bug-1575461-3fed33e53795684a.yaml diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 000df59899..b3f09ce586 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -380,7 +380,27 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): compute_client = self.app.client_manager.compute resource_flavor = _find_flavor(compute_client, parsed_args.flavor) + + access_projects = None + # get access projects list of this flavor + if not resource_flavor.is_public: + try: + flavor_access = compute_client.flavor_access.list( + flavor=resource_flavor.id) + projects = [utils.get_field(access, 'tenant_id') + for access in flavor_access] + # TODO(Huanxuan Ao): This format case can be removed after + # patch https://review.openstack.org/#/c/330223/ merged. + access_projects = utils.format_list(projects) + except Exception as e: + msg = _("Failed to get access projects list " + "for flavor '%(flavor)s': %(e)s") + LOG.error(msg % {'flavor': parsed_args.flavor, 'e': e}) + flavor = resource_flavor._info.copy() + flavor.update({ + 'access_project_ids': access_projects + }) flavor.pop("links", None) flavor['properties'] = utils.format_dict(resource_flavor.get_keys()) diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 7640247688..e40e62a758 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -787,6 +787,35 @@ def get_flavors(flavors=None, count=2): return mock.MagicMock(side_effect=flavors) +class FakeFlavorAccess(object): + """Fake one or more flavor accesses.""" + + @staticmethod + def create_one_flavor_access(attrs=None): + """Create a fake flavor access. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object, with flavor_id, tenat_id + """ + attrs = attrs or {} + + # Set default attributes. + flavor_access_info = { + 'flavor_id': 'flavor-id-' + uuid.uuid4().hex, + 'tenant_id': 'tenant-id-' + uuid.uuid4().hex, + } + + # Overwrite default attributes. + flavor_access_info.update(attrs) + + flavor_access = fakes.FakeResource( + info=copy.deepcopy(flavor_access_info), loaded=True) + + return flavor_access + + class FakeKeypair(object): """Fake one or more keypairs.""" diff --git a/openstackclient/tests/compute/v2/test_flavor.py b/openstackclient/tests/compute/v2/test_flavor.py index 20ae8706f6..40cd17c118 100644 --- a/openstackclient/tests/compute/v2/test_flavor.py +++ b/openstackclient/tests/compute/v2/test_flavor.py @@ -619,11 +619,13 @@ def test_flavor_set_nothing(self): class TestFlavorShow(TestFlavor): # Return value of self.flavors_mock.find(). + flavor_access = compute_fakes.FakeFlavorAccess.create_one_flavor_access() flavor = compute_fakes.FakeFlavor.create_one_flavor() columns = ( 'OS-FLV-DISABLED:disabled', 'OS-FLV-EXT-DATA:ephemeral', + 'access_project_ids', 'disk', 'id', 'name', @@ -638,6 +640,7 @@ class TestFlavorShow(TestFlavor): data = ( flavor.disabled, flavor.ephemeral, + None, flavor.disk, flavor.id, flavor.name, @@ -655,6 +658,7 @@ def setUp(self): # Return value of _find_resource() self.flavors_mock.find.return_value = self.flavor self.flavors_mock.get.side_effect = exceptions.NotFound(None) + self.flavor_access_mock.list.return_value = [self.flavor_access] self.cmd = flavor.ShowFlavor(self.app, None) def test_show_no_options(self): @@ -665,7 +669,7 @@ def test_show_no_options(self): self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) - def test_flavor_show(self): + def test_public_flavor_show(self): arglist = [ self.flavor.name, ] @@ -680,6 +684,45 @@ def test_flavor_show(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_private_flavor_show(self): + private_flavor = compute_fakes.FakeFlavor.create_one_flavor( + attrs={ + 'os-flavor-access:is_public': False, + } + ) + self.flavors_mock.find.return_value = private_flavor + + arglist = [ + private_flavor.name, + ] + verifylist = [ + ('flavor', private_flavor.name), + ] + + data_with_project = ( + private_flavor.disabled, + private_flavor.ephemeral, + self.flavor_access.tenant_id, + private_flavor.disk, + private_flavor.id, + private_flavor.name, + private_flavor.is_public, + utils.format_dict(private_flavor.get_keys()), + private_flavor.ram, + private_flavor.rxtx_factor, + private_flavor.swap, + private_flavor.vcpus, + ) + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.flavor_access_mock.list.assert_called_with( + flavor=private_flavor.id) + self.assertEqual(self.columns, columns) + self.assertEqual(data_with_project, data) + class TestFlavorUnset(TestFlavor): diff --git a/releasenotes/notes/bug-1575461-3fed33e53795684a.yaml b/releasenotes/notes/bug-1575461-3fed33e53795684a.yaml new file mode 100644 index 0000000000..cd319454fa --- /dev/null +++ b/releasenotes/notes/bug-1575461-3fed33e53795684a.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add support for showing flavor access list by using ``flavor show`` command. + [Bug `1575461 `_] From d84393999a9136c821f0b7804f56f9a730cc94c9 Mon Sep 17 00:00:00 2001 From: "Swapnil Kulkarni (coolsvap)" Date: Fri, 22 Jul 2016 04:09:21 +0000 Subject: [PATCH 1122/3095] Remove discover from test-requirements It's only needed for python < 2.7 which is not supported Change-Id: If0e9fd59c47d27bc58917500c554819c6aa30b2c --- test-requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index dfd1077e51..5560da434c 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -4,7 +4,6 @@ hacking<0.11,>=0.10.0 coverage>=3.6 # Apache-2.0 -discover # BSD fixtures>=3.0.0 # Apache-2.0/BSD mock>=2.0 # BSD oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0 From f996138a0d0c1ea16a1c3e90e63eedcf8fa53148 Mon Sep 17 00:00:00 2001 From: shizhihui Date: Thu, 21 Jul 2016 12:14:45 +0800 Subject: [PATCH 1123/3095] Standardize import format According to the rule in http://docs.openstack.org/developer/hacking/#imports I modify some irregular import format. Change-Id: Ibf29ccaf3ddec4a956334cc3368ebee7a66e282c --- doc/source/developing.rst | 34 +++++++++++++++++++ openstackclient/api/object_store_v1.py | 3 +- .../compute/v2/hypervisor_stats.py | 3 +- openstackclient/identity/client.py | 1 + openstackclient/network/v2/subnet_pool.py | 1 - openstackclient/object/v1/container.py | 1 - openstackclient/tests/api/fakes.py | 2 +- openstackclient/tests/api/test_image_v1.py | 2 +- openstackclient/tests/api/test_image_v2.py | 2 +- .../tests/api/test_object_store_v1.py | 2 +- .../tests/common/test_availability_zone.py | 1 + .../tests/common/test_extension.py | 5 ++- openstackclient/tests/common/test_quota.py | 1 - .../tests/compute/v2/test_agent.py | 2 +- openstackclient/tests/fakes.py | 2 +- .../identity/v3/test_identity_provider.py | 1 - .../tests/identity/v3/test_mappings.py | 1 - .../tests/identity/v3/test_user.py | 1 - openstackclient/tests/image/v2/test_image.py | 3 +- .../tests/network/v2/test_address_scope.py | 2 +- openstackclient/tests/object/v1/fakes.py | 1 + openstackclient/tests/utils.py | 3 +- openstackclient/volume/client.py | 1 + 23 files changed, 51 insertions(+), 24 deletions(-) diff --git a/doc/source/developing.rst b/doc/source/developing.rst index 95a04073fa..c5092cc075 100644 --- a/doc/source/developing.rst +++ b/doc/source/developing.rst @@ -173,3 +173,37 @@ or .. code-block:: bash $ pip install -e . + +Standardize Import Format +========================= + +.. _`Import Order Guide`: http://docs.openstack.org/developer/hacking/#imports + +The import order shows below: + +* {{stdlib imports in human alphabetical order}} +* \n +* {{third-party lib imports in human alphabetical order}} +* \n +* {{project imports in human alphabetical order}} +* \n +* \n +* {{begin your code}} + +Example +~~~~~~~ + +.. code-block:: python + + import copy + import fixtures + import mock + import os + + from osc_lib.api import auth + from osc_lib import utils + import six + + from openstackclient import shell + from openstackclient.tests import utils + diff --git a/openstackclient/api/object_store_v1.py b/openstackclient/api/object_store_v1.py index ae49922ded..faa55118e9 100644 --- a/openstackclient/api/object_store_v1.py +++ b/openstackclient/api/object_store_v1.py @@ -17,11 +17,10 @@ import logging import os +from osc_lib import utils import six from six.moves import urllib -from osc_lib import utils - from openstackclient.api import api diff --git a/openstackclient/compute/v2/hypervisor_stats.py b/openstackclient/compute/v2/hypervisor_stats.py index a70f0ce1ca..c6fd2992c5 100644 --- a/openstackclient/compute/v2/hypervisor_stats.py +++ b/openstackclient/compute/v2/hypervisor_stats.py @@ -14,9 +14,8 @@ """Hypervisor Stats action implementations""" -import six - from osc_lib.command import command +import six class ShowHypervisorStats(command.ShowOne): diff --git a/openstackclient/identity/client.py b/openstackclient/identity/client.py index d932b97068..0c609313f9 100644 --- a/openstackclient/identity/client.py +++ b/openstackclient/identity/client.py @@ -21,6 +21,7 @@ from openstackclient.i18n import _ + LOG = logging.getLogger(__name__) DEFAULT_API_VERSION = '3' diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index ed2bb0ef05..d3fab8acba 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -13,7 +13,6 @@ """Subnet pool action implementations""" import copy - import logging from osc_lib.cli import parseractions diff --git a/openstackclient/object/v1/container.py b/openstackclient/object/v1/container.py index 2b021ec25f..f00cc15093 100644 --- a/openstackclient/object/v1/container.py +++ b/openstackclient/object/v1/container.py @@ -15,7 +15,6 @@ """Container v1 action implementations""" - from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import utils diff --git a/openstackclient/tests/api/fakes.py b/openstackclient/tests/api/fakes.py index e285a61c3c..8d1d88ffc8 100644 --- a/openstackclient/tests/api/fakes.py +++ b/openstackclient/tests/api/fakes.py @@ -13,9 +13,9 @@ """API Test Fakes""" +from keystoneauth1 import session from requests_mock.contrib import fixture -from keystoneauth1 import session from openstackclient.tests import utils diff --git a/openstackclient/tests/api/test_image_v1.py b/openstackclient/tests/api/test_image_v1.py index f347975637..f8ad669200 100644 --- a/openstackclient/tests/api/test_image_v1.py +++ b/openstackclient/tests/api/test_image_v1.py @@ -13,9 +13,9 @@ """Image v1 API Library Tests""" +from keystoneauth1 import session from requests_mock.contrib import fixture -from keystoneauth1 import session from openstackclient.api import image_v1 from openstackclient.tests import utils diff --git a/openstackclient/tests/api/test_image_v2.py b/openstackclient/tests/api/test_image_v2.py index 77063997e2..28b0d5803a 100644 --- a/openstackclient/tests/api/test_image_v2.py +++ b/openstackclient/tests/api/test_image_v2.py @@ -13,9 +13,9 @@ """Image v2 API Library Tests""" +from keystoneauth1 import session from requests_mock.contrib import fixture -from keystoneauth1 import session from openstackclient.api import image_v2 from openstackclient.tests import utils diff --git a/openstackclient/tests/api/test_object_store_v1.py b/openstackclient/tests/api/test_object_store_v1.py index 8cc3a92717..e6ac203d10 100644 --- a/openstackclient/tests/api/test_object_store_v1.py +++ b/openstackclient/tests/api/test_object_store_v1.py @@ -15,9 +15,9 @@ import mock +from keystoneauth1 import session from requests_mock.contrib import fixture -from keystoneauth1 import session from openstackclient.api import object_store_v1 as object_store from openstackclient.tests import utils diff --git a/openstackclient/tests/common/test_availability_zone.py b/openstackclient/tests/common/test_availability_zone.py index feecaf55b3..014ab8bced 100644 --- a/openstackclient/tests/common/test_availability_zone.py +++ b/openstackclient/tests/common/test_availability_zone.py @@ -12,6 +12,7 @@ # import mock + import six from openstackclient.common import availability_zone diff --git a/openstackclient/tests/common/test_extension.py b/openstackclient/tests/common/test_extension.py index 17a3b492dd..10023aa6ac 100644 --- a/openstackclient/tests/common/test_extension.py +++ b/openstackclient/tests/common/test_extension.py @@ -14,12 +14,11 @@ import mock from openstackclient.common import extension -from openstackclient.tests import fakes -from openstackclient.tests import utils - from openstackclient.tests.compute.v2 import fakes as compute_fakes +from openstackclient.tests import fakes from openstackclient.tests.identity.v2_0 import fakes as identity_fakes from openstackclient.tests.network.v2 import fakes as network_fakes +from openstackclient.tests import utils from openstackclient.tests.volume.v2 import fakes as volume_fakes diff --git a/openstackclient/tests/common/test_quota.py b/openstackclient/tests/common/test_quota.py index c9ec5599f1..6f001c8548 100644 --- a/openstackclient/tests/common/test_quota.py +++ b/openstackclient/tests/common/test_quota.py @@ -11,7 +11,6 @@ # under the License. import copy - import mock from openstackclient.common import quota diff --git a/openstackclient/tests/compute/v2/test_agent.py b/openstackclient/tests/compute/v2/test_agent.py index 7695ee4107..07265bb040 100644 --- a/openstackclient/tests/compute/v2/test_agent.py +++ b/openstackclient/tests/compute/v2/test_agent.py @@ -14,8 +14,8 @@ # import mock - from mock import call + from osc_lib import exceptions from openstackclient.compute.v2 import agent diff --git a/openstackclient/tests/fakes.py b/openstackclient/tests/fakes.py index d0cab0191a..786cd6d430 100644 --- a/openstackclient/tests/fakes.py +++ b/openstackclient/tests/fakes.py @@ -15,11 +15,11 @@ import json import mock -import six import sys from keystoneauth1 import fixture import requests +import six AUTH_TOKEN = "foobar" diff --git a/openstackclient/tests/identity/v3/test_identity_provider.py b/openstackclient/tests/identity/v3/test_identity_provider.py index 1ec6105270..b5d784ef32 100644 --- a/openstackclient/tests/identity/v3/test_identity_provider.py +++ b/openstackclient/tests/identity/v3/test_identity_provider.py @@ -13,7 +13,6 @@ # under the License. import copy - import mock from openstackclient.identity.v3 import identity_provider diff --git a/openstackclient/tests/identity/v3/test_mappings.py b/openstackclient/tests/identity/v3/test_mappings.py index 6aa1a6e5db..09a383ebda 100644 --- a/openstackclient/tests/identity/v3/test_mappings.py +++ b/openstackclient/tests/identity/v3/test_mappings.py @@ -13,7 +13,6 @@ # under the License. import copy - import mock from osc_lib import exceptions diff --git a/openstackclient/tests/identity/v3/test_user.py b/openstackclient/tests/identity/v3/test_user.py index c4fb152108..7b99a4cbbc 100644 --- a/openstackclient/tests/identity/v3/test_user.py +++ b/openstackclient/tests/identity/v3/test_user.py @@ -15,7 +15,6 @@ import contextlib import copy - import mock from openstackclient.identity.v3 import user diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index c6b83bc5fe..4d9904bb97 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -16,11 +16,10 @@ import copy import mock -import warlock - from glanceclient.v2 import schemas from osc_lib import exceptions from osc_lib import utils as common_utils +import warlock from openstackclient.image.v2 import image from openstackclient.tests import fakes diff --git a/openstackclient/tests/network/v2/test_address_scope.py b/openstackclient/tests/network/v2/test_address_scope.py index 502516f984..342cf49b11 100644 --- a/openstackclient/tests/network/v2/test_address_scope.py +++ b/openstackclient/tests/network/v2/test_address_scope.py @@ -12,8 +12,8 @@ # import mock - from mock import call + from osc_lib import exceptions from openstackclient.network.v2 import address_scope diff --git a/openstackclient/tests/object/v1/fakes.py b/openstackclient/tests/object/v1/fakes.py index b9e86db759..6c36711127 100644 --- a/openstackclient/tests/object/v1/fakes.py +++ b/openstackclient/tests/object/v1/fakes.py @@ -14,6 +14,7 @@ # from keystoneauth1 import session + from openstackclient.api import object_store_v1 as object_store from openstackclient.tests import utils diff --git a/openstackclient/tests/utils.py b/openstackclient/tests/utils.py index 8dead71840..f3ab5ea63b 100644 --- a/openstackclient/tests/utils.py +++ b/openstackclient/tests/utils.py @@ -14,9 +14,8 @@ # under the License. # -import os - import fixtures +import os import testtools from openstackclient.tests import fakes diff --git a/openstackclient/volume/client.py b/openstackclient/volume/client.py index ea0c441c69..11f20673da 100644 --- a/openstackclient/volume/client.py +++ b/openstackclient/volume/client.py @@ -19,6 +19,7 @@ from openstackclient.i18n import _ + LOG = logging.getLogger(__name__) DEFAULT_API_VERSION = '2' From c45b1d7b230e900d0416a4953607e5d4e1dc9cfd Mon Sep 17 00:00:00 2001 From: sunyajing Date: Fri, 24 Jun 2016 12:40:29 +0800 Subject: [PATCH 1124/3095] Fix error for find_service() in identity if there are more than one services be found with one name, a NoUniqueMatch exception should be raised but we can see a NotFound Exception raised instead. It is because in "find_service()", we use "find_resource()" first, if "find_resource()" return a exception, we just think it is a NotFound Exception and continue to find by type but ignore a NoUniqueMatch exception of "find_resource()". This patch refactor the "find_service()" method to solve this problem. Change-Id: Id4619092c57f276ae0698c89df0d5503b7423a4e Co-Authored-By: Huanxuan Ao Closes-Bug:#1597296 --- openstackclient/identity/common.py | 41 ++++++++++------ .../tests/identity/v2_0/test_endpoint.py | 3 -- .../tests/identity/v2_0/test_service.py | 47 ++++++++++++++----- .../tests/identity/v3/test_service.py | 34 ++++++++++++-- .../notes/bug-1597296-9735f33eacf5552e.yaml | 5 ++ 5 files changed, 96 insertions(+), 34 deletions(-) create mode 100644 releasenotes/notes/bug-1597296-9735f33eacf5552e.yaml diff --git a/openstackclient/identity/common.py b/openstackclient/identity/common.py index 379f4114e4..1e40f39687 100644 --- a/openstackclient/identity/common.py +++ b/openstackclient/identity/common.py @@ -30,21 +30,32 @@ def find_service(identity_client, name_type_or_id): """Find a service by id, name or type.""" try: - # search for the usual ID or name - return utils.find_resource(identity_client.services, name_type_or_id) - except exceptions.CommandError: - try: - # search for service type - return identity_client.services.find(type=name_type_or_id) - # FIXME(dtroyer): This exception should eventually come from - # common client exceptions - except identity_exc.NotFound: - msg = _("No service with a type, name or ID of '%s' exists.") - raise exceptions.CommandError(msg % name_type_or_id) - except identity_exc.NoUniqueMatch: - msg = _("Multiple service matches found for '%s', " - "use an ID to be more specific.") - raise exceptions.CommandError(msg % name_type_or_id) + # search for service id + return identity_client.services.get(name_type_or_id) + except identity_exc.NotFound: + # ignore NotFound exception, raise others + pass + + try: + # search for service name + return identity_client.services.find(name=name_type_or_id) + except identity_exc.NotFound: + pass + except identity_exc.NoUniqueMatch: + msg = _("Multiple service matches found for '%s', " + "use an ID to be more specific.") + raise exceptions.CommandError(msg % name_type_or_id) + + try: + # search for service type + return identity_client.services.find(type=name_type_or_id) + except identity_exc.NotFound: + msg = _("No service with a type, name or ID of '%s' exists.") + raise exceptions.CommandError(msg % name_type_or_id) + except identity_exc.NoUniqueMatch: + msg = _("Multiple service matches found for '%s', " + "use an ID to be more specific.") + raise exceptions.CommandError(msg % name_type_or_id) def _get_token_resource(client, resource, parsed_name): diff --git a/openstackclient/tests/identity/v2_0/test_endpoint.py b/openstackclient/tests/identity/v2_0/test_endpoint.py index b2b6d0f180..26ec654d8b 100644 --- a/openstackclient/tests/identity/v2_0/test_endpoint.py +++ b/openstackclient/tests/identity/v2_0/test_endpoint.py @@ -103,9 +103,6 @@ def setUp(self): super(TestEndpointDelete, self).setUp() self.endpoints_mock.get.return_value = self.fake_endpoint - - self.services_mock.get.return_value = self.fake_service - self.endpoints_mock.delete.return_value = None # Get the command object to test diff --git a/openstackclient/tests/identity/v2_0/test_service.py b/openstackclient/tests/identity/v2_0/test_service.py index 318fa83d70..7efd2a6047 100644 --- a/openstackclient/tests/identity/v2_0/test_service.py +++ b/openstackclient/tests/identity/v2_0/test_service.py @@ -13,6 +13,9 @@ # under the License. # +from keystoneclient import exceptions as identity_exc +from osc_lib import exceptions + from openstackclient.identity.v2_0 import service from openstackclient.tests.identity.v2_0 import fakes as identity_fakes @@ -170,7 +173,8 @@ class TestServiceDelete(TestService): def setUp(self): super(TestServiceDelete, self).setUp() - self.services_mock.get.return_value = self.fake_service + self.services_mock.get.side_effect = identity_exc.NotFound(None) + self.services_mock.find.return_value = self.fake_service self.services_mock.delete.return_value = None # Get the command object to test @@ -253,20 +257,23 @@ def test_service_list_long(self): class TestServiceShow(TestService): + fake_service_s = identity_fakes.FakeService.create_one_service() + def setUp(self): super(TestServiceShow, self).setUp() - self.services_mock.get.return_value = self.fake_service + self.services_mock.get.side_effect = identity_exc.NotFound(None) + self.services_mock.find.return_value = self.fake_service_s # Get the command object to test self.cmd = service.ShowService(self.app, None) def test_service_show(self): arglist = [ - self.fake_service.name, + self.fake_service_s.name, ] verifylist = [ - ('service', self.fake_service.name), + ('service', self.fake_service_s.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -275,17 +282,35 @@ def test_service_show(self): # data to be shown. columns, data = self.cmd.take_action(parsed_args) - # ServiceManager.get(id) - self.services_mock.get.assert_called_with( - self.fake_service.name, + # ServiceManager.find(id) + self.services_mock.find.assert_called_with( + name=self.fake_service_s.name, ) collist = ('description', 'id', 'name', 'type') self.assertEqual(collist, columns) datalist = ( - self.fake_service.description, - self.fake_service.id, - self.fake_service.name, - self.fake_service.type, + self.fake_service_s.description, + self.fake_service_s.id, + self.fake_service_s.name, + self.fake_service_s.type, ) self.assertEqual(datalist, data) + + def test_service_show_nounique(self): + self.services_mock.find.side_effect = identity_exc.NoUniqueMatch(None) + arglist = [ + 'nounique_service', + ] + verifylist = [ + ('service', 'nounique_service'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual( + "Multiple service matches found for 'nounique_service'," + " use an ID to be more specific.", str(e)) diff --git a/openstackclient/tests/identity/v3/test_service.py b/openstackclient/tests/identity/v3/test_service.py index a1f85adc7b..65d8ebd7de 100644 --- a/openstackclient/tests/identity/v3/test_service.py +++ b/openstackclient/tests/identity/v3/test_service.py @@ -15,6 +15,9 @@ import copy +from keystoneclient import exceptions as identity_exc +from osc_lib import exceptions + from openstackclient.identity.v3 import service from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes @@ -185,7 +188,8 @@ class TestServiceDelete(TestService): def setUp(self): super(TestServiceDelete, self).setUp() - self.services_mock.get.return_value = fakes.FakeResource( + self.services_mock.get.side_effect = identity_exc.NotFound(None) + self.services_mock.find.return_value = fakes.FakeResource( None, copy.deepcopy(identity_fakes.SERVICE), loaded=True, @@ -282,7 +286,8 @@ class TestServiceSet(TestService): def setUp(self): super(TestServiceSet, self).setUp() - self.services_mock.get.return_value = fakes.FakeResource( + self.services_mock.get.side_effect = identity_exc.NotFound(None) + self.services_mock.find.return_value = fakes.FakeResource( None, copy.deepcopy(identity_fakes.SERVICE), loaded=True, @@ -460,7 +465,8 @@ class TestServiceShow(TestService): def setUp(self): super(TestServiceShow, self).setUp() - self.services_mock.get.return_value = fakes.FakeResource( + self.services_mock.get.side_effect = identity_exc.NotFound(None) + self.services_mock.find.return_value = fakes.FakeResource( None, copy.deepcopy(identity_fakes.SERVICE), loaded=True, @@ -484,8 +490,8 @@ def test_service_show(self): columns, data = self.cmd.take_action(parsed_args) # ServiceManager.get(id) - self.services_mock.get.assert_called_with( - identity_fakes.service_name, + self.services_mock.find.assert_called_with( + name=identity_fakes.service_name ) collist = ('description', 'enabled', 'id', 'name', 'type') @@ -498,3 +504,21 @@ def test_service_show(self): identity_fakes.service_type, ) self.assertEqual(datalist, data) + + def test_service_show_nounique(self): + self.services_mock.find.side_effect = identity_exc.NoUniqueMatch(None) + arglist = [ + 'nounique_service', + ] + verifylist = [ + ('service', 'nounique_service'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual( + "Multiple service matches found for 'nounique_service'," + " use an ID to be more specific.", str(e)) diff --git a/releasenotes/notes/bug-1597296-9735f33eacf5552e.yaml b/releasenotes/notes/bug-1597296-9735f33eacf5552e.yaml new file mode 100644 index 0000000000..677f8d47ce --- /dev/null +++ b/releasenotes/notes/bug-1597296-9735f33eacf5552e.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - Fixed service name lookup in Identity commands to properly handle + multiple matches. + [Bug `1597296 `_] From 1b878b4efd5c6161439a2c9212a3a38d5a02026a Mon Sep 17 00:00:00 2001 From: SongmingYan Date: Fri, 22 Jul 2016 04:41:33 -0400 Subject: [PATCH 1125/3095] Remove execute permission on a few files Some files have execute permission unnecessarily. Change them from 755 to 644. Change-Id: I471ebd1c3d123ad4a7376f7f5996f53f8c2d9b0b --- openstackclient/common/availability_zone.py | 0 openstackclient/common/quota.py | 0 openstackclient/identity/v2_0/project.py | 0 openstackclient/identity/v2_0/role.py | 0 openstackclient/identity/v2_0/user.py | 0 openstackclient/identity/v3/domain.py | 0 openstackclient/identity/v3/group.py | 0 openstackclient/identity/v3/project.py | 0 openstackclient/identity/v3/role.py | 0 openstackclient/identity/v3/user.py | 0 openstackclient/image/v2/image.py | 0 openstackclient/shell.py | 0 12 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 openstackclient/common/availability_zone.py mode change 100755 => 100644 openstackclient/common/quota.py mode change 100755 => 100644 openstackclient/identity/v2_0/project.py mode change 100755 => 100644 openstackclient/identity/v2_0/role.py mode change 100755 => 100644 openstackclient/identity/v2_0/user.py mode change 100755 => 100644 openstackclient/identity/v3/domain.py mode change 100755 => 100644 openstackclient/identity/v3/group.py mode change 100755 => 100644 openstackclient/identity/v3/project.py mode change 100755 => 100644 openstackclient/identity/v3/role.py mode change 100755 => 100644 openstackclient/identity/v3/user.py mode change 100755 => 100644 openstackclient/image/v2/image.py mode change 100755 => 100644 openstackclient/shell.py diff --git a/openstackclient/common/availability_zone.py b/openstackclient/common/availability_zone.py old mode 100755 new mode 100644 diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py old mode 100755 new mode 100644 diff --git a/openstackclient/identity/v2_0/project.py b/openstackclient/identity/v2_0/project.py old mode 100755 new mode 100644 diff --git a/openstackclient/identity/v2_0/role.py b/openstackclient/identity/v2_0/role.py old mode 100755 new mode 100644 diff --git a/openstackclient/identity/v2_0/user.py b/openstackclient/identity/v2_0/user.py old mode 100755 new mode 100644 diff --git a/openstackclient/identity/v3/domain.py b/openstackclient/identity/v3/domain.py old mode 100755 new mode 100644 diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py old mode 100755 new mode 100644 diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py old mode 100755 new mode 100644 diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py old mode 100755 new mode 100644 diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py old mode 100755 new mode 100644 diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py old mode 100755 new mode 100644 diff --git a/openstackclient/shell.py b/openstackclient/shell.py old mode 100755 new mode 100644 From 75a1fcf70a7780691ad6d432e76d6f86933079ff Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 22 Jul 2016 12:58:24 -0500 Subject: [PATCH 1126/3095] Clarification of option name rules We never specifcally said anywhere that short names are global only and why. Change-Id: Ia2824cb7ebe7c2e1d116c0a9bc7760de24904c61 --- doc/source/command-options.rst | 6 ++++++ doc/source/humaninterfaceguide.rst | 22 ++++++++++++++-------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/doc/source/command-options.rst b/doc/source/command-options.rst index 5cb84684c9..6260ec3287 100644 --- a/doc/source/command-options.rst +++ b/doc/source/command-options.rst @@ -14,6 +14,12 @@ for defining and using options in all situations. The alternative of only using it when necessary leads to errors when copy-n-paste is used for a new command without understanding why or why not that instance is correct. +The :doc:`Human Interface Guide ` +describes the guildelines for option names and usage. In short: + * All option names shall be GNU-style long names (two leading dashes). + * Some global options may have short nmaes, generally limited to those defined + in support libraries such as ``cliff``. + General Command Options ======================= diff --git a/doc/source/humaninterfaceguide.rst b/doc/source/humaninterfaceguide.rst index 5d3c48dcca..400ccfcf6f 100644 --- a/doc/source/humaninterfaceguide.rst +++ b/doc/source/humaninterfaceguide.rst @@ -183,14 +183,6 @@ Output formats: * user-friendly tables with headers, etc * machine-parsable delimited -Notes: - -* All long options names shall begin with two dashes ('--') and use a single dash - ('-') internally between words (:code:`--like-this`). Underscores ('_') shall not - be used in option names. -* Authentication options conform to the common CLI authentication guidelines in - :doc:`authentication`. - Global Options ~~~~~~~~~~~~~~ @@ -202,6 +194,16 @@ the command-line option takes priority. The environment variable names are deri from the option name by dropping the leading dashes ('--'), converting each embedded dash ('-') to an underscore ('_'), and converting to upper case. +* Global options shall always have a long option name, certain common options may + also have short names. Short names should be reserved for global options to limit + the potential for duplication and multiple meanings between commands given the + limited set of available short names. +* All long options names shall begin with two dashes ('--') and use a single dash + ('-') internally between words (:code:`--like-this`). Underscores ('_') shall not + be used in option names. +* Authentication options conform to the common CLI authentication guidelines in + :doc:`authentication`. + For example, :code:`--os-username` can be set from the environment via :code:`OS_USERNAME`. @@ -245,6 +247,10 @@ Each command may have its own set of options distinct from the global options. They follow the same style as the global options and always appear between the command and any positional arguments the command requires. +Command options shall only have long names. The small range of available +short names makes it hard for a single short option name to have a consistent +meaning across multiple commands. + Option Forms ++++++++++++ From f38c51c1b90576e6b13ac6086386884c09f5813a Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 13 May 2016 16:53:44 -0500 Subject: [PATCH 1127/3095] Rework clientmanager * Add compatibility for plugin v2 interface removed from osc-lib * ClientManager.is_network_endpoint_enabled() is wrapper for new is_service_available() Change-Id: I6f26ce9e4d0702f50c7949bacfbeeb0f98cddb5d --- openstackclient/common/clientmanager.py | 276 ++----------- openstackclient/shell.py | 12 +- .../tests/common/test_clientmanager.py | 367 ++---------------- 3 files changed, 55 insertions(+), 600 deletions(-) diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index ec005dc092..9e4b9c937f 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -15,20 +15,13 @@ """Manage access to the clients, including authenticating when needed.""" -import copy import logging import pkg_resources import sys from keystoneauth1.loading import base from osc_lib.api import auth -from osc_lib import exceptions -from oslo_utils import strutils -import requests -import six - -from openstackclient.common import session as osc_session -from openstackclient.identity import client as identity_client +from osc_lib import clientmanager LOG = logging.getLogger(__name__) @@ -109,267 +102,44 @@ def build_auth_params(auth_plugin_name, cmd_options): return (auth_plugin_loader, auth_params) -class ClientCache(object): - """Descriptor class for caching created client handles.""" - - def __init__(self, factory): - self.factory = factory - self._handle = None - - def __get__(self, instance, owner): - # Tell the ClientManager to login to keystone - if self._handle is None: - try: - self._handle = self.factory(instance) - except AttributeError as err: - # Make sure the failure propagates. Otherwise, the plugin just - # quietly isn't there. - new_err = exceptions.PluginAttributeError(err) - six.reraise(new_err.__class__, new_err, sys.exc_info()[2]) - return self._handle +class ClientManager(clientmanager.ClientManager): + """Manages access to API clients, including authentication - -class ClientManager(object): - """Manages access to API clients, including authentication.""" + Wrap osc_lib's ClientManager to maintain compatibility for the existing + plugin V2 interface. Some currently private attributes become public + in osc-lib so we need to maintain a transition period. + """ # A simple incrementing version for the plugin to know what is available PLUGIN_INTERFACE_VERSION = "2" - identity = ClientCache(identity_client.make_client) - - def __getattr__(self, name): - # this is for the auth-related parameters. - if name in ['_' + o.replace('-', '_') - for o in auth.OPTIONS_LIST]: - return self._auth_params[name[1:]] - - raise AttributeError(name) - def __init__( self, cli_options=None, api_version=None, - verify=True, pw_func=None, ): - """Set up a ClientManager - - :param cli_options: - Options collected from the command-line, environment, or wherever - :param api_version: - Dict of API versions: key is API name, value is the version - :param verify: - TLS certificate verification; may be a boolean to enable or disable - server certificate verification, or a filename of a CA certificate - bundle to be used in verification (implies True) - :param pw_func: - Callback function for asking the user for a password. The function - takes an optional string for the prompt ('Password: ' on None) and - returns a string containing the password - """ - - self._cli_options = cli_options - self._api_version = api_version - self._pw_callback = pw_func - self._url = self._cli_options.auth.get('url') - self._region_name = self._cli_options.region_name - self._interface = self._cli_options.interface - - self.timing = self._cli_options.timing - - self._auth_ref = None - self.session = None - - # verify is the Requests-compatible form - self._verify = verify - # also store in the form used by the legacy client libs - self._cacert = None - if isinstance(verify, bool): - self._insecure = not verify - else: - self._cacert = verify - self._insecure = False - - # Set up client certificate and key - # NOTE(cbrandily): This converts client certificate/key to requests - # cert argument: None (no client certificate), a path - # to client certificate or a tuple with client - # certificate/key paths. - self._cert = self._cli_options.cert - if self._cert and self._cli_options.key: - self._cert = self._cert, self._cli_options.key - - # Get logging from root logger - root_logger = logging.getLogger('') - LOG.setLevel(root_logger.getEffectiveLevel()) - - # NOTE(gyee): use this flag to indicate whether auth setup has already - # been completed. If so, do not perform auth setup again. The reason - # we need this flag is that we want to be able to perform auth setup - # outside of auth_ref as auth_ref itself is a property. We can not - # retrofit auth_ref to optionally skip scope check. Some operations - # do not require a scoped token. In those cases, we call setup_auth - # prior to dereferrencing auth_ref. - self._auth_setup_completed = False - - def _set_default_scope_options(self): - # TODO(mordred): This is a usability improvement that's broadly useful - # We should port it back up into os-client-config. - default_domain = self._cli_options.default_domain - - # NOTE(hieulq): If USER_DOMAIN_NAME, USER_DOMAIN_ID, PROJECT_DOMAIN_ID - # or PROJECT_DOMAIN_NAME is present and API_VERSION is 2.0, then - # ignore all domain related configs. - if (self._api_version.get('identity') == '2.0' and - self.auth_plugin_name.endswith('password')): - domain_props = ['project_domain_name', 'project_domain_id', - 'user_domain_name', 'user_domain_id'] - for prop in domain_props: - if self._auth_params.pop(prop, None) is not None: - LOG.warning("Ignoring domain related configs " + - prop + " because identity API version is 2.0") - return - - # NOTE(aloga): The scope parameters below only apply to v3 and v3 - # related auth plugins, so we stop the parameter checking if v2 is - # being used. - if (self._api_version.get('identity') != '3' or - self.auth_plugin_name.startswith('v2')): - return - - # NOTE(stevemar): If PROJECT_DOMAIN_ID or PROJECT_DOMAIN_NAME is - # present, then do not change the behaviour. Otherwise, set the - # PROJECT_DOMAIN_ID to 'OS_DEFAULT_DOMAIN' for better usability. - if ('project_domain_id' in self._auth_params and - not self._auth_params.get('project_domain_id') and - not self._auth_params.get('project_domain_name')): - self._auth_params['project_domain_id'] = default_domain - - # NOTE(stevemar): If USER_DOMAIN_ID or USER_DOMAIN_NAME is present, - # then do not change the behaviour. Otherwise, set the - # USER_DOMAIN_ID to 'OS_DEFAULT_DOMAIN' for better usability. - if ('user_domain_id' in self._auth_params and - not self._auth_params.get('user_domain_id') and - not self._auth_params.get('user_domain_name')): - self._auth_params['user_domain_id'] = default_domain - - def setup_auth(self): - """Set up authentication - - This is deferred until authentication is actually attempted because - it gets in the way of things that do not require auth. - """ - - if self._auth_setup_completed: - return - - # If no auth type is named by the user, select one based on - # the supplied options - self.auth_plugin_name = select_auth_plugin(self._cli_options) - - # Basic option checking to avoid unhelpful error messages - auth.check_valid_authentication_options(self._cli_options, - self.auth_plugin_name) - - # Horrible hack alert...must handle prompt for null password if - # password auth is requested. - if (self.auth_plugin_name.endswith('password') and - not self._cli_options.auth.get('password')): - self._cli_options.auth['password'] = self._pw_callback() - - (auth_plugin, self._auth_params) = build_auth_params( - self.auth_plugin_name, - self._cli_options, + super(ClientManager, self).__init__( + cli_options=cli_options, + api_version=api_version, + pw_func=pw_func, ) - self._set_default_scope_options() - - # For compatibility until all clients can be updated - if 'project_name' in self._auth_params: - self._project_name = self._auth_params['project_name'] - elif 'tenant_name' in self._auth_params: - self._project_name = self._auth_params['tenant_name'] - - LOG.info('Using auth plugin: %s', self.auth_plugin_name) - LOG.debug('Using parameters %s', - strutils.mask_password(self._auth_params)) - self.auth = auth_plugin.load_from_options(**self._auth_params) - # needed by SAML authentication - request_session = requests.session() - self.session = osc_session.TimingSession( - auth=self.auth, - session=request_session, - verify=self._verify, - cert=self._cert, - user_agent=USER_AGENT, - ) - - self._auth_setup_completed = True - - def validate_scope(self): - if self._auth_ref.project_id is not None: - # We already have a project scope. - return - if self._auth_ref.domain_id is not None: - # We already have a domain scope. - return - - # We do not have a scoped token (and the user's default project scope - # was not implied), so the client needs to be explicitly configured - # with a scope. - auth.check_valid_authorization_options(self._cli_options, - self.auth_plugin_name) - - @property - def auth_ref(self): - """Dereference will trigger an auth if it hasn't already""" - if not self._auth_ref: - self.setup_auth() - LOG.debug("Get auth_ref") - self._auth_ref = self.auth.get_auth_ref(self.session) - return self._auth_ref + # TODO(dtroyer): For compatibility; mark this for removal when plugin + # interface v2 is removed + self._region_name = self.region_name + self._interface = self.interface + self._cacert = self.cacert + self._insecure = not self.verify def is_network_endpoint_enabled(self): """Check if the network endpoint is enabled""" - # Trigger authentication necessary to determine if the network - # endpoint is enabled. - if self.auth_ref: - service_catalog = self.auth_ref.service_catalog - else: - service_catalog = None - # Assume that the network endpoint is enabled. - network_endpoint_enabled = True - if service_catalog: - if 'network' in service_catalog.get_endpoints(): - LOG.debug("Network endpoint in service catalog") - else: - LOG.debug("No network endpoint in service catalog") - network_endpoint_enabled = False - else: - LOG.debug("No service catalog, assuming network endpoint enabled") - return network_endpoint_enabled - - def get_endpoint_for_service_type(self, service_type, region_name=None, - interface='public'): - """Return the endpoint URL for the service type.""" - if not interface: - interface = 'public' - # See if we are using password flow auth, i.e. we have a - # service catalog to select endpoints from - if self.auth_ref: - endpoint = self.auth_ref.service_catalog.url_for( - service_type=service_type, - region_name=region_name, - interface=interface, - ) - else: - # Get the passed endpoint directly from the auth plugin - endpoint = self.auth.get_endpoint(self.session, - interface=interface) - return endpoint - def get_configuration(self): - return copy.deepcopy(self._cli_options.config) + # NOTE(dtroyer): is_service_available() can also return None if + # there is no Service Catalog, callers here are + # not expecting that so fold None into True to + # use Network API by default + return self.is_service_available('network') is not False # Plugin Support @@ -391,7 +161,7 @@ def get_plugin_modules(group): setattr( ClientManager, module.API_NAME, - ClientCache( + clientmanager.ClientCache( getattr(sys.modules[ep.module_name], 'make_client', None) ), ) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index ed729e537c..b4f2df43b2 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -26,6 +26,7 @@ from cliff import command from cliff import complete from cliff import help +from osc_lib.cli import client_config as cloud_config from osc_lib.command import timing from osc_lib import exceptions as exc from osc_lib import logs @@ -38,8 +39,6 @@ from openstackclient.common import commandmanager from openstackclient.i18n import _ -from os_client_config import config as cloud_config - osprofiler_profiler = importutils.try_import("osprofiler.profiler") @@ -309,6 +308,9 @@ def initialize_app(self, argv): tenant_id = getattr(self.options, 'tenant_id', None) tenant_name = getattr(self.options, 'tenant_name', None) + # Save default domain + self.default_domain = self.options.default_domain + # handle some v2/v3 authentication inconsistencies by just acting like # both the project and tenant information are both present. This can # go away if we stop registering all the argparse options together. @@ -325,7 +327,7 @@ def initialize_app(self, argv): # Ignore the default value of interface. Only if it is set later # will it be used. try: - cc = cloud_config.OpenStackConfig( + cc = cloud_config.OSC_Config( override_defaults={ 'interface': None, 'auth_type': auth_type, @@ -368,9 +370,6 @@ def initialize_app(self, argv): if self.verify and self.cloud.cacert: self.verify = self.cloud.cacert - # Save default domain - self.default_domain = self.options.default_domain - # Loop through extensions to get API versions for mod in clientmanager.PLUGIN_MODULES: default_version = getattr(mod, 'DEFAULT_API_VERSION', None) @@ -429,7 +428,6 @@ def initialize_app(self, argv): self.client_manager = clientmanager.ClientManager( cli_options=self.cloud, - verify=self.verify, api_version=self.api_version, pw_func=prompt_for_password, ) diff --git a/openstackclient/tests/common/test_clientmanager.py b/openstackclient/tests/common/test_clientmanager.py index 117c718429..625e175a1d 100644 --- a/openstackclient/tests/common/test_clientmanager.py +++ b/openstackclient/tests/common/test_clientmanager.py @@ -13,111 +13,34 @@ # under the License. # -import json as jsonutils -import mock +import copy -from keystoneauth1.access import service_catalog -from keystoneauth1.identity import v2 as auth_v2 from keystoneauth1 import token_endpoint -from osc_lib.api import auth -from osc_lib import exceptions as exc -from requests_mock.contrib import fixture +from osc_lib.tests import utils as osc_lib_test_utils from openstackclient.common import clientmanager from openstackclient.tests import fakes -from openstackclient.tests import utils -API_VERSION = {"identity": "2.0"} -AUTH_REF = {'version': 'v2.0'} -AUTH_REF.update(fakes.TEST_RESPONSE_DICT['access']) -SERVICE_CATALOG = service_catalog.ServiceCatalogV2(AUTH_REF) +class TestClientManager(osc_lib_test_utils.TestClientManager): - -# This is deferred in api.auth but we need it here... -auth.get_options_list() - - -class Container(object): - attr = clientmanager.ClientCache(lambda x: object()) - buggy_attr = clientmanager.ClientCache(lambda x: x.foo) - - def __init__(self): - pass - - -class FakeOptions(object): - - def __init__(self, **kwargs): - for option in auth.OPTIONS_LIST: - setattr(self, option.replace('-', '_'), None) - self.auth_type = None - self.identity_api_version = '2.0' - self.timing = None - self.region_name = None - self.interface = None - self.url = None - self.auth = {} - self.cert = None - self.key = None - self.default_domain = 'default' - self.__dict__.update(kwargs) - - -class TestClientCache(utils.TestCase): - - def test_singleton(self): - # NOTE(dtroyer): Verify that the ClientCache descriptor only invokes - # the factory one time and always returns the same value after that. - c = Container() - self.assertEqual(c.attr, c.attr) - - def test_attribute_error_propagates(self): - c = Container() - err = self.assertRaises(exc.PluginAttributeError, - getattr, c, 'buggy_attr') - self.assertNotIsInstance(err, AttributeError) - self.assertEqual("'Container' object has no attribute 'foo'", str(err)) - - -class TestClientManager(utils.TestCase): - - def setUp(self): - super(TestClientManager, self).setUp() - self.mock = mock.Mock() - self.requests = self.useFixture(fixture.Fixture()) - # fake v2password token retrieval - self.stub_auth(json=fakes.TEST_RESPONSE_DICT) - # fake token and token_endpoint retrieval - self.stub_auth(json=fakes.TEST_RESPONSE_DICT, - url='/'.join([fakes.AUTH_URL, 'v2.0/tokens'])) - # fake v3password token retrieval - self.stub_auth(json=fakes.TEST_RESPONSE_DICT_V3, - url='/'.join([fakes.AUTH_URL, 'auth/tokens'])) - # fake password version endpoint discovery - self.stub_auth(json=fakes.TEST_VERSIONS, - url=fakes.AUTH_URL, - verb='GET') + def _clientmanager_class(self): + """Allow subclasses to override the ClientManager class""" + return clientmanager.ClientManager def test_client_manager_token_endpoint(self): - - client_manager = clientmanager.ClientManager( - cli_options=FakeOptions( - auth_type='token_endpoint', - auth=dict( - token=fakes.AUTH_TOKEN, - url=fakes.AUTH_URL, - ), - ), - api_version=API_VERSION, - verify=True + token_auth = { + 'url': fakes.AUTH_URL, + 'token': fakes.AUTH_TOKEN, + } + client_manager = self._make_clientmanager( + auth_args=token_auth, + auth_plugin_name='token_endpoint', ) - client_manager.setup_auth() - client_manager.auth_ref self.assertEqual( fakes.AUTH_URL, - client_manager._url, + client_manager._cli_options.config['auth']['url'], ) self.assertEqual( fakes.AUTH_TOKEN, @@ -127,256 +50,20 @@ def test_client_manager_token_endpoint(self): client_manager.auth, token_endpoint.Token, ) - self.assertFalse(client_manager._insecure) - self.assertTrue(client_manager._verify) - self.assertTrue(client_manager.is_network_endpoint_enabled()) - - def test_client_manager_token(self): - - client_manager = clientmanager.ClientManager( - cli_options=FakeOptions( - auth=dict( - token=fakes.AUTH_TOKEN, - auth_url=fakes.AUTH_URL, - ), - auth_type='v2token', - interface=fakes.INTERFACE, - region_name=fakes.REGION_NAME, - ), - api_version=API_VERSION, - verify=True - ) - client_manager.setup_auth() - client_manager.auth_ref - - self.assertEqual( - fakes.AUTH_URL, - client_manager._auth_url, - ) - self.assertIsInstance( - client_manager.auth, - auth_v2.Token, - ) - self.assertEqual( - fakes.INTERFACE, - client_manager._interface, - ) - self.assertEqual( - fakes.REGION_NAME, - client_manager._region_name, - ) - self.assertFalse(client_manager._insecure) - self.assertTrue(client_manager._verify) - self.assertTrue(client_manager.is_network_endpoint_enabled()) - - def test_client_manager_password(self): - - client_manager = clientmanager.ClientManager( - cli_options=FakeOptions( - auth=dict( - auth_url=fakes.AUTH_URL, - username=fakes.USERNAME, - password=fakes.PASSWORD, - project_name=fakes.PROJECT_NAME, - ), - ), - api_version=API_VERSION, - verify=False, - ) - client_manager.setup_auth() - client_manager.auth_ref - - self.assertEqual( - fakes.AUTH_URL, - client_manager._auth_url, - ) - self.assertEqual( - fakes.USERNAME, - client_manager._username, - ) - self.assertEqual( - fakes.PASSWORD, - client_manager._password, - ) - self.assertIsInstance( - client_manager.auth, - auth_v2.Password, - ) - self.assertTrue(client_manager._insecure) - self.assertFalse(client_manager._verify) - # These need to stick around until the old-style clients are gone - self.assertEqual( - AUTH_REF.pop('version'), - client_manager.auth_ref.version, - ) - self.assertEqual( - fakes.to_unicode_dict(AUTH_REF), - client_manager.auth_ref._data['access'], - ) - self.assertEqual( - dir(SERVICE_CATALOG), - dir(client_manager.auth_ref.service_catalog), - ) self.assertTrue(client_manager.is_network_endpoint_enabled()) def test_client_manager_network_endpoint_disabled(self): - - client_manager = clientmanager.ClientManager( - cli_options=FakeOptions( - auth=dict( - auth_url=fakes.AUTH_URL, - username=fakes.USERNAME, - password=fakes.PASSWORD, - project_name=fakes.PROJECT_NAME, - ), - auth_type='v3password', - ), - api_version={"identity": "3"}, - verify=False, - ) - client_manager.setup_auth() - client_manager.auth_ref - - # v3 fake doesn't have network endpoint. + auth_args = copy.deepcopy(self.default_password_auth) + auth_args.update({ + 'user_domain_name': 'default', + 'project_domain_name': 'default', + }) + # v3 fake doesn't have network endpoint + client_manager = self._make_clientmanager( + auth_args=auth_args, + identity_api_version='3', + auth_plugin_name='v3password', + ) + + self.assertFalse(client_manager.is_service_available('network')) self.assertFalse(client_manager.is_network_endpoint_enabled()) - - def stub_auth(self, json=None, url=None, verb=None, **kwargs): - subject_token = fakes.AUTH_TOKEN - base_url = fakes.AUTH_URL - if json: - text = jsonutils.dumps(json) - headers = {'X-Subject-Token': subject_token, - 'Content-Type': 'application/json'} - if not url: - url = '/'.join([base_url, 'tokens']) - url = url.replace("/?", "?") - if not verb: - verb = 'POST' - self.requests.register_uri(verb, - url, - headers=headers, - text=text) - - def test_client_manager_password_verify_ca(self): - - client_manager = clientmanager.ClientManager( - cli_options=FakeOptions( - auth=dict( - auth_url=fakes.AUTH_URL, - username=fakes.USERNAME, - password=fakes.PASSWORD, - project_name=fakes.PROJECT_NAME, - ), - auth_type='v2password', - ), - api_version=API_VERSION, - verify='cafile', - ) - client_manager.setup_auth() - client_manager.auth_ref - - self.assertFalse(client_manager._insecure) - self.assertTrue(client_manager._verify) - self.assertEqual('cafile', client_manager._cacert) - self.assertTrue(client_manager.is_network_endpoint_enabled()) - - def test_client_manager_password_no_cert(self): - client_manager = clientmanager.ClientManager( - cli_options=FakeOptions()) - self.assertIsNone(client_manager._cert) - - def test_client_manager_password_client_cert(self): - client_manager = clientmanager.ClientManager( - cli_options=FakeOptions(cert='cert')) - self.assertEqual('cert', client_manager._cert) - - def test_client_manager_password_client_cert_and_key(self): - client_manager = clientmanager.ClientManager( - cli_options=FakeOptions(cert='cert', key='key')) - self.assertEqual(('cert', 'key'), client_manager._cert) - - def _select_auth_plugin(self, auth_params, api_version, auth_plugin_name): - auth_params['auth_type'] = auth_plugin_name - auth_params['identity_api_version'] = api_version - - client_manager = clientmanager.ClientManager( - cli_options=FakeOptions(**auth_params), - api_version={"identity": api_version}, - verify=True - ) - client_manager.setup_auth() - client_manager.auth_ref - - self.assertEqual( - auth_plugin_name, - client_manager.auth_plugin_name, - ) - - def test_client_manager_select_auth_plugin(self): - # test token auth - params = dict( - auth=dict( - auth_url=fakes.AUTH_URL, - token=fakes.AUTH_TOKEN, - ), - ) - self._select_auth_plugin(params, '2.0', 'v2token') - self._select_auth_plugin(params, '3', 'v3token') - self._select_auth_plugin(params, 'XXX', 'token') - # test token/endpoint auth - params = dict( - auth_plugin='token_endpoint', - auth=dict( - url='test', - token=fakes.AUTH_TOKEN, - ), - ) - self._select_auth_plugin(params, 'XXX', 'token_endpoint') - # test password auth - params = dict( - auth=dict( - auth_url=fakes.AUTH_URL, - username=fakes.USERNAME, - password=fakes.PASSWORD, - project_name=fakes.PROJECT_NAME, - ), - ) - self._select_auth_plugin(params, '2.0', 'v2password') - self._select_auth_plugin(params, '3', 'v3password') - self._select_auth_plugin(params, 'XXX', 'password') - - def test_client_manager_select_auth_plugin_failure(self): - client_manager = clientmanager.ClientManager( - cli_options=FakeOptions(os_auth_plugin=''), - api_version=API_VERSION, - verify=True, - ) - self.assertRaises( - exc.CommandError, - client_manager.setup_auth, - ) - - @mock.patch('osc_lib.api.auth.check_valid_authentication_options') - def test_client_manager_auth_setup_once(self, check_authn_options_func): - client_manager = clientmanager.ClientManager( - cli_options=FakeOptions( - auth=dict( - auth_url=fakes.AUTH_URL, - username=fakes.USERNAME, - password=fakes.PASSWORD, - project_name=fakes.PROJECT_NAME, - ), - ), - api_version=API_VERSION, - verify=False, - ) - self.assertFalse(client_manager._auth_setup_completed) - client_manager.setup_auth() - self.assertTrue(check_authn_options_func.called) - self.assertTrue(client_manager._auth_setup_completed) - - # now make sure we don't do auth setup the second time around - # by checking whether check_valid_auth_options() gets called again - check_authn_options_func.reset_mock() - client_manager.auth_ref - check_authn_options_func.assert_not_called() From 713d92df4e53f74698a1ff2dfcb7514ff22f023b Mon Sep 17 00:00:00 2001 From: Henry Nash Date: Fri, 29 Apr 2016 23:59:27 +0100 Subject: [PATCH 1128/3095] Add assignment list to v2 identity and deprecate alternate listing The current identity role list command (both v2 and v3) is overloaded with listing roles as well as assignments (if you provide user, group, project or domain options). This is in addition to the v3 assignment list command designed for this purpose. This overloading complicates the fact that roles can now be domain specific (i.e. have a domain attribute), so the command 'role list --domain ] [--effective] [--inherited] + [--names] .. option:: --role Role to filter (name or ID) + .. versionadded:: 3 + .. option:: --user User to filter (name or ID) @@ -37,19 +40,27 @@ List role assignments Domain the user belongs to (name or ID). This can be used in case collisions between user names exist. + .. versionadded:: 3 + .. option:: --group Group to filter (name or ID) + .. versionadded:: 3 + .. option:: --group-domain Domain the group belongs to (name or ID). This can be used in case collisions between group names exist. + .. versionadded:: 3 + .. option:: --domain Domain to filter (name or ID) + .. versionadded:: 3 + .. option:: --project Project to filter (name or ID) @@ -59,14 +70,29 @@ List role assignments Domain the project belongs to (name or ID). This can be used in case collisions between project names exist. + .. versionadded:: 3 + .. option:: --effective Returns only effective role assignments (defaults to False) + .. versionadded:: 3 + .. option:: --inherited Specifies if the role grant is inheritable to the sub projects + .. versionadded:: 3 + .. option:: --names Returns role assignments with names instead of IDs + +.. option:: --auth-user + + Returns role assignments for the authenticated user. + +.. option:: --auth-project + + Returns role assignments for the project to which the authenticated user + is scoped. diff --git a/doc/source/command-objects/role.rst b/doc/source/command-objects/role.rst index 48751ed7bb..5542a35b9c 100644 --- a/doc/source/command-objects/role.rst +++ b/doc/source/command-objects/role.rst @@ -7,7 +7,7 @@ Identity v2, v3 role add -------- -Add role to a user or group in a project or domain +Add role assignment to a user or group in a project or domain .. program:: role add .. code:: bash @@ -123,31 +123,33 @@ List roles Filter roles by (name or ID) - .. versionadded:: 3 + (Deprecated, please use ``role assignment list`` instead) .. option:: --project Filter roles by (name or ID) - .. versionadded:: 3 + (Deprecated, please use ``role assignment list`` instead) .. option:: --user Filter roles by (name or ID) - .. versionadded:: 3 + (Deprecated, please use ``role assignment list`` instead) .. option:: --group Filter roles by (name or ID) - .. versionadded:: 3 + (Deprecated, please use ``role assignment list`` instead) .. option:: --user-domain Domain the user belongs to (name or ID). This can be used in case collisions between user names exist. + (Deprecated, please use ``role assignment list`` instead) + .. versionadded:: 3 .. option:: --group-domain @@ -155,6 +157,8 @@ List roles Domain the group belongs to (name or ID). This can be used in case collisions between group names exist. + (Deprecated, please use ``role assignment list`` instead) + .. versionadded:: 3 .. option:: --project-domain @@ -162,18 +166,22 @@ List roles Domain the project belongs to (name or ID). This can be used in case collisions between project names exist. + (Deprecated, please use ``role assignment list`` instead) + .. versionadded:: 3 .. option:: --inherited Specifies if the role grant is inheritable to the sub projects. + (Deprecated, please use ``role assignment list`` instead) + .. versionadded:: 3 role remove ----------- -Remove role from domain/project : user/group +Remove role assignment from domain/project : user/group .. program:: role remove .. code:: bash diff --git a/openstackclient/identity/v2_0/role.py b/openstackclient/identity/v2_0/role.py index 191cdaa3d1..b4b67bad29 100644 --- a/openstackclient/identity/v2_0/role.py +++ b/openstackclient/identity/v2_0/role.py @@ -150,6 +150,15 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): + + def _deprecated(): + # NOTE(henry-nash): Deprecated as of Newton, so we should remove + # this in the 'P' release. + self.log.warning(_('Listing assignments using role list is ' + 'deprecated as of the Newton release. Use role ' + 'assignment list --user --project ' + ' --names instead.')) + identity_client = self.app.client_manager.identity auth_ref = self.app.client_manager.auth_ref @@ -166,6 +175,7 @@ def take_action(self, parsed_args): identity_client.projects, parsed_args.project, ) + _deprecated() data = identity_client.roles.roles_for_user(user.id, project.id) elif parsed_args.user: @@ -181,6 +191,7 @@ def take_action(self, parsed_args): else: msg = _("Project must be specified") raise exceptions.CommandError(msg) + _deprecated() data = identity_client.roles.roles_for_user(user.id, project.id) elif parsed_args.project: project = utils.find_resource( @@ -195,6 +206,7 @@ def take_action(self, parsed_args): else: msg = _("User must be specified") raise exceptions.CommandError(msg) + _deprecated() data = identity_client.roles.roles_for_user(user.id, project.id) if parsed_args.user or parsed_args.project: @@ -249,6 +261,10 @@ def take_action(self, parsed_args): msg = _("User must be specified") raise exceptions.CommandError(msg) + self.log.warning(_('Listing assignments using user role list is ' + 'deprecated as of the Newton release. Use role ' + 'assignment list --user --project ' + ' --names instead.')) project = utils.find_resource( identity_client.tenants, parsed_args.project, diff --git a/openstackclient/identity/v2_0/role_assignment.py b/openstackclient/identity/v2_0/role_assignment.py new file mode 100644 index 0000000000..406508ac37 --- /dev/null +++ b/openstackclient/identity/v2_0/role_assignment.py @@ -0,0 +1,113 @@ +# 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. +# + +"""Identity v2 Assignment action implementations """ + +from openstackclient.common import command +from openstackclient.common import exceptions +from openstackclient.common import utils +from openstackclient.i18n import _ # noqa + + +class ListRoleAssignment(command.Lister): + """List role assignments""" + + def get_parser(self, prog_name): + parser = super(ListRoleAssignment, self).get_parser(prog_name) + parser.add_argument( + '--user', + metavar='', + help='User to filter (name or ID)', + ) + parser.add_argument( + '--project', + metavar='', + help='Project to filter (name or ID)', + ) + parser.add_argument( + '--names', + action="store_true", + help='Display names instead of IDs', + ) + parser.add_argument( + '--auth-user', + action="store_true", + dest='authuser', + help='Only list assignments for the authenticated user', + ) + parser.add_argument( + '--auth-project', + action="store_true", + dest='authproject', + help='Only list assignments for the project to which the ' + 'authenticated user\'s token is scoped', + ) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + auth_ref = self.app.client_manager.auth_ref + + include_names = True if parsed_args.names else False + + user = None + if parsed_args.user: + user = utils.find_resource( + identity_client.users, + parsed_args.user, + ) + elif parsed_args.authuser: + if auth_ref: + user = utils.find_resource( + identity_client.users, + auth_ref.user_id + ) + + project = None + if parsed_args.project: + project = utils.find_resource( + identity_client.projects, + parsed_args.project, + ) + elif parsed_args.authproject: + if auth_ref: + project = utils.find_resource( + identity_client.projects, + auth_ref.project_id + ) + + # If user or project is not specified, we would ideally list all + # relevant assignments in the system (to be compatible with v3). + # However, there is no easy way of doing that in v2. + if not user or not project: + msg = _("Project and User must be specified") + raise exceptions.CommandError(msg) + else: + data = identity_client.roles.roles_for_user(user.id, project.id) + + columns = ('Role', 'User', 'Project') + for user_role in data: + if include_names: + setattr(user_role, 'role', user_role.name) + user_role.user = user.name + user_role.project = project.name + else: + setattr(user_role, 'role', user_role.id) + user_role.user = user.id + user_role.project = project.id + + return (columns, + (utils.get_item_properties( + s, columns, + formatters={}, + ) for s in data)) diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index 273801799d..e8a03ff3db 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -251,6 +251,10 @@ def take_action(self, parsed_args): for user_role in data: user_role.user = user.name user_role.domain = domain.name + self.log.warning(_('Listing assignments using role list is ' + 'deprecated. Use role assignment list --user ' + ' --domain --names ' + 'instead.')) elif parsed_args.user and parsed_args.project: columns = ('ID', 'Name', 'Project', 'User') data = identity_client.roles.list( @@ -261,6 +265,10 @@ def take_action(self, parsed_args): for user_role in data: user_role.user = user.name user_role.project = project.name + self.log.warning(_('Listing assignments using role list is ' + 'deprecated. Use role assignment list --user ' + ' --project --names ' + 'instead.')) elif parsed_args.user: columns = ('ID', 'Name') data = identity_client.roles.list( @@ -268,6 +276,10 @@ def take_action(self, parsed_args): domain='default', os_inherit_extension_inherited=parsed_args.inherited ) + self.log.warning(_('Listing assignments using role list is ' + 'deprecated. Use role assignment list --user ' + ' --domain default --names ' + 'instead.')) elif parsed_args.group and parsed_args.domain: columns = ('ID', 'Name', 'Domain', 'Group') data = identity_client.roles.list( @@ -278,6 +290,10 @@ def take_action(self, parsed_args): for group_role in data: group_role.group = group.name group_role.domain = domain.name + self.log.warning(_('Listing assignments using role list is ' + 'deprecated. Use role assignment list --group ' + ' --domain --names ' + 'instead.')) elif parsed_args.group and parsed_args.project: columns = ('ID', 'Name', 'Project', 'Group') data = identity_client.roles.list( @@ -288,6 +304,10 @@ def take_action(self, parsed_args): for group_role in data: group_role.group = group.name group_role.project = project.name + self.log.warning(_('Listing assignments using role list is ' + 'deprecated. Use role assignment list --group ' + ' --project --names ' + 'instead.')) else: sys.stderr.write(_("Error: If a user or group is specified, " "either --domain or --project must also be " diff --git a/openstackclient/identity/v3/role_assignment.py b/openstackclient/identity/v3/role_assignment.py index 39e2336d6f..6177d1a5f6 100644 --- a/openstackclient/identity/v3/role_assignment.py +++ b/openstackclient/identity/v3/role_assignment.py @@ -67,6 +67,19 @@ def get_parser(self, prog_name): ) common.add_project_domain_option_to_parser(parser) common.add_inherited_option_to_parser(parser) + parser.add_argument( + '--auth-user', + action="store_true", + dest='authuser', + help='Only list assignments for the authenticated user', + ) + parser.add_argument( + '--auth-project', + action="store_true", + dest='authproject', + help='Only list assignments for the project to which the ' + 'authenticated user\'s token is scoped', + ) return parser def _as_tuple(self, assignment): @@ -75,6 +88,7 @@ def _as_tuple(self, assignment): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity + auth_ref = self.app.client_manager.auth_ref role = None if parsed_args.role: @@ -90,6 +104,12 @@ def take_action(self, parsed_args): parsed_args.user, parsed_args.user_domain, ) + elif parsed_args.authuser: + if auth_ref: + user = common.find_user( + identity_client, + auth_ref.user_id + ) domain = None if parsed_args.domain: @@ -105,6 +125,12 @@ def take_action(self, parsed_args): parsed_args.project, parsed_args.project_domain, ) + elif parsed_args.authproject: + if auth_ref: + project = common.find_project( + identity_client, + auth_ref.project_id + ) group = None if parsed_args.group: diff --git a/openstackclient/tests/identity/v2_0/fakes.py b/openstackclient/tests/identity/v2_0/fakes.py index 662d56b657..a715427cbb 100644 --- a/openstackclient/tests/identity/v2_0/fakes.py +++ b/openstackclient/tests/identity/v2_0/fakes.py @@ -50,6 +50,11 @@ 'name': role_name, } +ROLE_2 = { + 'id': '2', + 'name': 'bigboss', +} + service_id = '1925-10-11' service_name = 'elmore' service_description = 'Leonard, Elmore, rip' diff --git a/openstackclient/tests/identity/v2_0/test_role_assignment.py b/openstackclient/tests/identity/v2_0/test_role_assignment.py new file mode 100644 index 0000000000..ab48c2f418 --- /dev/null +++ b/openstackclient/tests/identity/v2_0/test_role_assignment.py @@ -0,0 +1,270 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +import copy +import mock + +from openstackclient.common import exceptions +from openstackclient.identity.v2_0 import role_assignment +from openstackclient.tests import fakes +from openstackclient.tests.identity.v2_0 import fakes as identity_fakes + + +class TestRoleAssignment(identity_fakes.TestIdentityv2): + + def setUp(self): + super(TestRoleAssignment, self).setUp() + + +class TestRoleAssignmentList(TestRoleAssignment): + + columns = ( + 'Role', + 'User', + 'Project', + ) + + def setUp(self): + super(TestRoleAssignment, self).setUp() + + # Get a shortcut to the UserManager Mock + self.users_mock = self.app.client_manager.identity.users + self.users_mock.reset_mock() + + # Get a shortcut to the ProjectManager Mock + self.projects_mock = self.app.client_manager.identity.projects + self.projects_mock.reset_mock() + + # Get a shortcut to the RoleManager Mock + self.roles_mock = self.app.client_manager.identity.roles + self.roles_mock.reset_mock() + + self.projects_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.PROJECT), + loaded=True, + ) + + self.users_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.USER), + loaded=True, + ) + + self.roles_mock.roles_for_user.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.ROLE), + loaded=True, + ), + ] + + # Get the command object to test + self.cmd = role_assignment.ListRoleAssignment(self.app, None) + + def test_role_assignment_list_no_filters(self): + + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # This argument combination should raise a CommandError + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args, + ) + + def test_role_assignment_list_only_project_filter(self): + + arglist = [ + '--project', identity_fakes.project_name, + ] + verifylist = [ + ('project', identity_fakes.project_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # This argument combination should raise a CommandError + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args, + ) + + def test_role_assignment_list_only_user_filter(self): + + arglist = [ + '--user', identity_fakes.user_name, + ] + verifylist = [ + ('user', identity_fakes.user_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # This argument combination should raise a CommandError + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args, + ) + + def test_role_assignment_list_project_and_user(self): + + self.roles_mock.roles_for_user.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy( + identity_fakes.ROLE), + loaded=True, + ), + fakes.FakeResource( + None, + copy.deepcopy( + identity_fakes.ROLE_2), + loaded=True, + ), + ] + + arglist = [ + '--project', identity_fakes.project_name, + '--user', identity_fakes.user_name, + ] + verifylist = [ + ('user', identity_fakes.user_name), + ('project', identity_fakes.project_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + self.roles_mock.roles_for_user.assert_called_with( + identity_fakes.user_id, + identity_fakes.project_id, + ) + + self.assertEqual(self.columns, columns) + datalist = (( + identity_fakes.role_id, + identity_fakes.user_id, + identity_fakes.project_id, + ), (identity_fakes.ROLE_2['id'], + identity_fakes.user_id, + identity_fakes.project_id, + ),) + self.assertEqual(datalist, tuple(data)) + + def test_role_assignment_list_def_creds(self): + + auth_ref = self.app.client_manager.auth_ref = mock.MagicMock() + auth_ref.project_id.return_value = identity_fakes.project_id + auth_ref.user_id.return_value = identity_fakes.user_id + + self.roles_mock.roles_for_user.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy( + identity_fakes.ROLE), + loaded=True, + ), + fakes.FakeResource( + None, + copy.deepcopy( + identity_fakes.ROLE_2), + loaded=True, + ), + ] + + arglist = [ + '--auth-user', + '--auth-project', + ] + verifylist = [ + ('authuser', True), + ('authproject', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + self.roles_mock.roles_for_user.assert_called_with( + identity_fakes.user_id, + identity_fakes.project_id, + ) + + self.assertEqual(self.columns, columns) + datalist = (( + identity_fakes.role_id, + identity_fakes.user_id, + identity_fakes.project_id, + ), (identity_fakes.ROLE_2['id'], + identity_fakes.user_id, + identity_fakes.project_id, + ),) + self.assertEqual(datalist, tuple(data)) + + def test_role_assignment_list_by_name_project_and_user(self): + + self.roles_mock.roles_for_user.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy( + identity_fakes.ROLE), + loaded=True, + ), + fakes.FakeResource( + None, + copy.deepcopy( + identity_fakes.ROLE_2), + loaded=True, + ), + ] + + arglist = [ + '--project', identity_fakes.project_name, + '--user', identity_fakes.user_name, + '--names' + ] + verifylist = [ + ('user', identity_fakes.user_name), + ('project', identity_fakes.project_name), + ('names', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + self.roles_mock.roles_for_user.assert_called_with( + identity_fakes.user_id, + identity_fakes.project_id, + ) + + self.assertEqual(self.columns, columns) + datalist = (( + identity_fakes.role_name, + identity_fakes.user_name, + identity_fakes.project_name, + ), (identity_fakes.ROLE_2['name'], + identity_fakes.user_name, + identity_fakes.project_name, + ),) + self.assertEqual(datalist, tuple(data)) diff --git a/openstackclient/tests/identity/v3/test_role_assignment.py b/openstackclient/tests/identity/v3/test_role_assignment.py index 8956b74fb3..0ae67c72e0 100644 --- a/openstackclient/tests/identity/v3/test_role_assignment.py +++ b/openstackclient/tests/identity/v3/test_role_assignment.py @@ -12,6 +12,7 @@ # import copy +import mock from openstackclient.identity.v3 import role_assignment from openstackclient.tests import fakes @@ -374,6 +375,65 @@ def test_role_assignment_list_project(self): ),) self.assertEqual(datalist, tuple(data)) + def test_role_assignment_list_def_creds(self): + + auth_ref = self.app.client_manager.auth_ref = mock.MagicMock() + auth_ref.project_id.return_value = identity_fakes.project_id + auth_ref.user_id.return_value = identity_fakes.user_id + + self.role_assignments_mock.list.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy( + identity_fakes.ASSIGNMENT_WITH_PROJECT_ID_AND_USER_ID), + loaded=True, + ), + ] + + arglist = [ + '--auth-user', + '--auth-project', + ] + verifylist = [ + ('user', None), + ('group', None), + ('domain', None), + ('project', None), + ('role', None), + ('effective', False), + ('inherited', False), + ('names', False), + ('authuser', True), + ('authproject', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + self.role_assignments_mock.list.assert_called_with( + domain=None, + user=self.users_mock.get(), + group=None, + project=self.projects_mock.get(), + role=None, + effective=False, + os_inherit_extension_inherited_to=None, + include_names=False) + + self.assertEqual(self.columns, columns) + datalist = (( + identity_fakes.role_id, + identity_fakes.user_id, + '', + identity_fakes.project_id, + '', + False + ),) + self.assertEqual(datalist, tuple(data)) + def test_role_assignment_list_effective(self): self.role_assignments_mock.list.return_value = [ diff --git a/releasenotes/notes/bug-1605774-28aec51f6ec4926e.yaml b/releasenotes/notes/bug-1605774-28aec51f6ec4926e.yaml new file mode 100644 index 0000000000..8c7958f4b9 --- /dev/null +++ b/releasenotes/notes/bug-1605774-28aec51f6ec4926e.yaml @@ -0,0 +1,4 @@ +--- +features: + - Deprecate ``role list`` arguments in favor of ``role assignment`` command. + [Bug `1605774 `_] diff --git a/setup.cfg b/setup.cfg index b8b866108f..f1abceb93d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -172,6 +172,7 @@ openstack.identity.v2 = role_list = openstackclient.identity.v2_0.role:ListRole role_remove = openstackclient.identity.v2_0.role:RemoveRole role_show = openstackclient.identity.v2_0.role:ShowRole + role_assignment_list = openstackclient.identity.v2_0.role_assignment:ListRoleAssignment service_create = openstackclient.identity.v2_0.service:CreateService service_delete = openstackclient.identity.v2_0.service:DeleteService From bca4571ab67b55b1a6d5c4ac09fe71dc687e4296 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Wed, 20 Jul 2016 17:26:45 +0800 Subject: [PATCH 1129/3095] Unit test of credential in identityv3 Add missing unit tests and refactor the older tests with fake classeds for credential in identity v3 Change-Id: I94d4f80a86806c6115178421bd481b7622065956 --- openstackclient/tests/identity/v3/fakes.py | 103 +++++- .../tests/identity/v3/test_credential.py | 292 ++++++++++++++++-- 2 files changed, 367 insertions(+), 28 deletions(-) diff --git a/openstackclient/tests/identity/v3/fakes.py b/openstackclient/tests/identity/v3/fakes.py index df532df401..bad15b6cde 100644 --- a/openstackclient/tests/identity/v3/fakes.py +++ b/openstackclient/tests/identity/v3/fakes.py @@ -198,8 +198,6 @@ 'links': base_url + 'services/' + service_id, } -credential_id = 'c-123' - endpoint_id = 'e-123' endpoint_url = 'http://127.0.0.1:35357' endpoint_region = 'RegionOne' @@ -639,3 +637,104 @@ def create_one_domain(attrs=None): domain = fakes.FakeResource(info=copy.deepcopy(domain_info), loaded=True) return domain + + +class FakeCredential(object): + """Fake one or more credential.""" + + @staticmethod + def create_one_credential(attrs=None): + """Create a fake credential. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object, with id, type, and so on + """ + + attrs = attrs or {} + + # set default attributes. + credential_info = { + 'id': 'credential-id-' + uuid.uuid4().hex, + 'type': 'cert', + 'user_id': 'user-id-' + uuid.uuid4().hex, + 'blob': 'credential-data-' + uuid.uuid4().hex, + 'project_id': 'project-id-' + uuid.uuid4().hex, + 'links': 'links-' + uuid.uuid4().hex, + } + credential_info.update(attrs) + + credential = fakes.FakeResource( + info=copy.deepcopy(credential_info), loaded=True) + return credential + + @staticmethod + def create_credentials(attrs=None, count=2): + """Create multiple fake credentials. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of credentials to fake + :return: + A list of FakeResource objects faking the credentials + """ + credentials = [] + for i in range(0, count): + credential = FakeCredential.create_one_credential(attrs) + credentials.append(credential) + + return credentials + + @staticmethod + def get_credentials(credentials=None, count=2): + """Get an iterable MagicMock object with a list of faked credentials. + + If credentials list is provided, then initialize the Mock object with + the list. Otherwise create one. + + :param List credentials: + A list of FakeResource objects faking credentials + :param Integer count: + The number of credentials to be faked + :return + An iterable Mock object with side_effect set to a list of faked + credentials + """ + if credentials is None: + credentials = FakeCredential.create_credentials(count) + + return mock.MagicMock(side_effect=credentials) + + +class FakeUser(object): + """Fake one or more user.""" + + @staticmethod + def create_one_user(attrs=None): + """Create a fake user. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object, with id, name, and so on + """ + + attrs = attrs or {} + + # set default attributes. + user_info = { + 'id': 'user-id-' + uuid.uuid4().hex, + 'name': 'user-name-' + uuid.uuid4().hex, + 'default_project_id': 'project-' + uuid.uuid4().hex, + 'email': 'user-email-' + uuid.uuid4().hex, + 'enabled': True, + 'domain_id': 'domain-id' + uuid.uuid4().hex, + 'links': 'links-' + uuid.uuid4().hex, + } + user_info.update(attrs) + + user = fakes.FakeResource(info=copy.deepcopy(user_info), + loaded=True) + return user diff --git a/openstackclient/tests/identity/v3/test_credential.py b/openstackclient/tests/identity/v3/test_credential.py index d8866124f9..b272087d10 100644 --- a/openstackclient/tests/identity/v3/test_credential.py +++ b/openstackclient/tests/identity/v3/test_credential.py @@ -10,7 +10,10 @@ # License for the specific language governing permissions and limitations # under the License. -import json +import mock +from mock import call + +from osc_lib import exceptions from openstackclient.identity.v3 import credential from openstackclient.tests.identity.v3 import fakes as identity_fakes @@ -18,16 +21,6 @@ class TestCredential(identity_fakes.TestIdentityv3): - data = { - "access": "abc123", - "secret": "hidden-message", - "trust_id": None - } - - def __init__(self, *args): - super(TestCredential, self).__init__(*args) - - self.json_data = json.dumps(self.data) def setUp(self): super(TestCredential, self).setUp() @@ -45,15 +38,221 @@ def setUp(self): self.projects_mock.reset_mock() +class TestCredentialCreate(TestCredential): + + user = identity_fakes.FakeUser.create_one_user() + project = identity_fakes.FakeProject.create_one_project() + columns = ( + 'blob', + 'id', + 'project_id', + 'type', + 'user_id', + ) + + def setUp(self): + super(TestCredentialCreate, self).setUp() + + self.credential = identity_fakes.FakeCredential.create_one_credential( + attrs={'user_id': self.user.id, 'project_id': self.project.id}) + self.credentials_mock.create.return_value = self.credential + self.users_mock.get.return_value = self.user + self.projects_mock.get.return_value = self.project + self.data = ( + self.credential.blob, + self.credential.id, + self.credential.project_id, + self.credential.type, + self.credential.user_id, + ) + + self.cmd = credential.CreateCredential(self.app, None) + + def test_credential_create_no_options(self): + arglist = [ + self.credential.user_id, + self.credential.blob, + ] + verifylist = [ + ('user', self.credential.user_id), + ('data', self.credential.blob), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + kwargs = { + 'user': self.credential.user_id, + 'type': self.credential.type, + 'blob': self.credential.blob, + 'project': None, + } + self.credentials_mock.create.assert_called_once_with( + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_credential_create_with_options(self): + arglist = [ + self.credential.user_id, + self.credential.blob, + '--type', self.credential.type, + '--project', self.credential.project_id, + ] + verifylist = [ + ('user', self.credential.user_id), + ('data', self.credential.blob), + ('type', self.credential.type), + ('project', self.credential.project_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + kwargs = { + 'user': self.credential.user_id, + 'type': self.credential.type, + 'blob': self.credential.blob, + 'project': self.credential.project_id, + } + self.credentials_mock.create.assert_called_once_with( + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_credential_create_with_invalid_type(self): + arglist = [ + self.credential.user_id, + self.credential.blob, + '--type', 'invalid_type', + ] + verifylist = [ + ('user', self.credential.user_id), + ('data', self.credential.blob), + ('type', 'invalid_type'), + ] + self.assertRaises(utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + +class TestCredentialDelete(TestCredential): + + credentials = identity_fakes.FakeCredential.create_credentials(count=2) + + def setUp(self): + super(TestCredentialDelete, self).setUp() + + self.credentials_mock.delete.return_value = None + + # Get the command object to test + self.cmd = credential.DeleteCredential(self.app, None) + + def test_credential_delete(self): + arglist = [ + self.credentials[0].id, + ] + verifylist = [ + ('credential', [self.credentials[0].id]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.credentials_mock.delete.assert_called_with( + self.credentials[0].id, + ) + self.assertIsNone(result) + + def test_credential_multi_delete(self): + arglist = [] + for c in self.credentials: + arglist.append(c.id) + verifylist = [ + ('credential', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + calls = [] + for c in self.credentials: + calls.append(call(c.id)) + self.credentials_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_credential_multi_delete_with_exception(self): + arglist = [ + self.credentials[0].id, + 'unexist_credential', + ] + verifylist = [ + ('credential', [self.credentials[0].id, 'unexist_credential']) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + delete_mock_result = [None, exceptions.CommandError] + self.credentials_mock.delete = ( + mock.MagicMock(side_effect=delete_mock_result) + ) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 credential failed to delete.', str(e)) + + self.credentials_mock.delete.assert_any_call(self.credentials[0].id) + self.credentials_mock.delete.assert_any_call('unexist_credential') + + +class TestCredentialList(TestCredential): + + credential = identity_fakes.FakeCredential.create_one_credential() + + columns = ('ID', 'Type', 'User ID', 'Data', 'Project ID') + data = (( + credential.id, + credential.type, + credential.user_id, + credential.blob, + credential.project_id, + ), ) + + def setUp(self): + super(TestCredentialList, self).setUp() + + self.credentials_mock.list.return_value = [self.credential] + + # Get the command object to test + self.cmd = credential.ListCredential(self.app, None) + + def test_domain_list_no_options(self): + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.credentials_mock.list.assert_called_with() + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, tuple(data)) + + class TestCredentialSet(TestCredential): + credential = identity_fakes.FakeCredential.create_one_credential() + def setUp(self): super(TestCredentialSet, self).setUp() self.cmd = credential.SetCredential(self.app, None) def test_credential_set_no_options(self): arglist = [ - identity_fakes.credential_id, + self.credential.id, ] self.assertRaises(utils.ParserException, @@ -62,8 +261,8 @@ def test_credential_set_no_options(self): def test_credential_set_missing_user(self): arglist = [ '--type', 'ec2', - '--data', self.json_data, - identity_fakes.credential_id, + '--data', self.credential.blob, + self.credential.id, ] self.assertRaises(utils.ParserException, @@ -71,9 +270,9 @@ def test_credential_set_missing_user(self): def test_credential_set_missing_type(self): arglist = [ - '--user', identity_fakes.user_name, - '--data', self.json_data, - identity_fakes.credential_id, + '--user', self.credential.user_id, + '--data', self.credential.blob, + self.credential.id, ] self.assertRaises(utils.ParserException, @@ -81,9 +280,9 @@ def test_credential_set_missing_type(self): def test_credential_set_missing_data(self): arglist = [ - '--user', identity_fakes.user_name, + '--user', self.credential.user_id, '--type', 'ec2', - identity_fakes.credential_id, + self.credential.id, ] self.assertRaises(utils.ParserException, @@ -91,10 +290,10 @@ def test_credential_set_missing_data(self): def test_credential_set_valid(self): arglist = [ - '--user', identity_fakes.user_name, + '--user', self.credential.user_id, '--type', 'ec2', - '--data', self.json_data, - identity_fakes.credential_id, + '--data', self.credential.blob, + self.credential.id, ] parsed_args = self.check_parser(self.cmd, arglist, []) @@ -104,14 +303,55 @@ def test_credential_set_valid(self): def test_credential_set_valid_with_project(self): arglist = [ - '--user', identity_fakes.user_name, + '--user', self.credential.user_id, '--type', 'ec2', - '--data', self.json_data, - '--project', identity_fakes.project_name, - identity_fakes.credential_id, + '--data', self.credential.blob, + '--project', self.credential.project_id, + self.credential.id, ] parsed_args = self.check_parser(self.cmd, arglist, []) result = self.cmd.take_action(parsed_args) self.assertIsNone(result) + + +class TestCredentialShow(TestCredential): + + columns = ( + 'blob', + 'id', + 'project_id', + 'type', + 'user_id', + ) + + def setUp(self): + super(TestCredentialShow, self).setUp() + + self.credential = identity_fakes.FakeCredential.create_one_credential() + self.credentials_mock.get.return_value = self.credential + self.data = ( + self.credential.blob, + self.credential.id, + self.credential.project_id, + self.credential.type, + self.credential.user_id, + ) + + self.cmd = credential.ShowCredential(self.app, None) + + def test_credential_show(self): + arglist = [ + self.credential.id, + ] + verifylist = [ + ('credential', self.credential.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.credentials_mock.get.assert_called_once_with(self.credential.id) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) From 61b9d9fe2d0c6c99d5e77361111f02a19d169782 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Fri, 22 Jul 2016 13:23:30 +0800 Subject: [PATCH 1130/3095] Add "--marker" and "--limit" options to "snapshot list" Add "--marker" and "--limit" options to "snapshot list" command in volume v2 (v2 only). Change-Id: Ib60840b9b83dfe5e599e4037e8ec308844a9448b Closes-Bug: #1605475 --- doc/source/command-objects/snapshot.rst | 15 ++++++++ .../tests/volume/v2/test_snapshot.py | 34 +++++++++++++++++-- openstackclient/volume/v2/snapshot.py | 17 +++++++++- .../notes/bug-1605475-84e649fb8c675737.yaml | 5 +++ 4 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/bug-1605475-84e649fb8c675737.yaml diff --git a/doc/source/command-objects/snapshot.rst b/doc/source/command-objects/snapshot.rst index 8ac0b6b869..ce2d9c8eab 100644 --- a/doc/source/command-objects/snapshot.rst +++ b/doc/source/command-objects/snapshot.rst @@ -68,6 +68,9 @@ List snapshots os snapshot list [--all-projects] + [--long] + [--limit ] + [--marker ] .. option:: --all-projects @@ -77,6 +80,18 @@ List snapshots List additional fields in output +.. option:: --limit + + Maximum number of snapshots to display + + *Volume version 2 only* + +.. option:: --marker + + The last snapshot ID of the previous page + + *Volume version 2 only* + snapshot set ------------ diff --git a/openstackclient/tests/volume/v2/test_snapshot.py b/openstackclient/tests/volume/v2/test_snapshot.py index 04e0285eda..3eb740ba6c 100644 --- a/openstackclient/tests/volume/v2/test_snapshot.py +++ b/openstackclient/tests/volume/v2/test_snapshot.py @@ -12,6 +12,7 @@ # under the License. # +import argparse import mock from mock import call @@ -260,16 +261,33 @@ def test_snapshot_list_without_options(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) + + self.snapshots_mock.list.assert_called_once_with( + limit=None, marker=None, search_opts={'all_tenants': False}) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) def test_snapshot_list_with_options(self): - arglist = ["--long"] - verifylist = [("long", True), ('all_projects', False)] + arglist = [ + "--long", + "--limit", "2", + "--marker", self.snapshots[0].id, + ] + verifylist = [ + ("long", True), + ("limit", 2), + ("marker", self.snapshots[0].id), + ('all_projects', False), + ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) + self.snapshots_mock.list.assert_called_once_with( + limit=2, + marker=self.snapshots[0].id, + search_opts={'all_tenants': False} + ) self.assertEqual(self.columns_long, columns) self.assertEqual(self.data_long, list(data)) @@ -285,9 +303,21 @@ def test_snapshot_list_all_projects(self): columns, data = self.cmd.take_action(parsed_args) + self.snapshots_mock.list.assert_called_once_with( + limit=None, marker=None, search_opts={'all_tenants': True}) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + def test_snapshot_list_negative_limit(self): + arglist = [ + "--limit", "-2", + ] + verifylist = [ + ("limit", -2), + ] + self.assertRaises(argparse.ArgumentTypeError, self.check_parser, + self.cmd, arglist, verifylist) + class TestSnapshotSet(TestSnapshot): diff --git a/openstackclient/volume/v2/snapshot.py b/openstackclient/volume/v2/snapshot.py index ba692074a2..8304a5eb46 100644 --- a/openstackclient/volume/v2/snapshot.py +++ b/openstackclient/volume/v2/snapshot.py @@ -134,6 +134,18 @@ def get_parser(self, prog_name): default=False, help=_('List additional fields in output'), ) + parser.add_argument( + '--marker', + metavar='', + help=_('The last snapshot ID of the previous page'), + ) + parser.add_argument( + '--limit', + type=int, + action=parseractions.NonNegativeAction, + metavar='', + help=_('Maximum number of snapshots to display'), + ) return parser def take_action(self, parsed_args): @@ -174,7 +186,10 @@ def _format_volume_id(volume_id): } data = self.app.client_manager.volume.volume_snapshots.list( - search_opts=search_opts) + search_opts=search_opts, + marker=parsed_args.marker, + limit=parsed_args.limit, + ) return (column_headers, (utils.get_item_properties( s, columns, diff --git a/releasenotes/notes/bug-1605475-84e649fb8c675737.yaml b/releasenotes/notes/bug-1605475-84e649fb8c675737.yaml new file mode 100644 index 0000000000..f9362bc3a9 --- /dev/null +++ b/releasenotes/notes/bug-1605475-84e649fb8c675737.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``--limit`` and ``--marker`` options to ``snapshot list`` command. + [Bug `1605475 `_] From e31408d2a4c524181c37ef565b021c0b729cbbe3 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Mon, 18 Jul 2016 15:14:51 +0800 Subject: [PATCH 1131/3095] Add options to "volume type list" command Add "--public" and "--private" options to "volume type command" in volumev2 (v2 only) to list optional volume types Change-Id: I8605990d62116c10d89ce192c14e550657dabee5 Closes-Bug: #1597198 --- doc/source/command-objects/volume-type.rst | 13 ++++++++ openstackclient/tests/volume/v2/test_type.py | 33 +++++++++++++++++-- openstackclient/volume/v2/volume_type.py | 20 ++++++++++- .../notes/bug-1597198-e36b55f3fd185a3a.yaml | 5 +++ 4 files changed, 67 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/bug-1597198-e36b55f3fd185a3a.yaml diff --git a/doc/source/command-objects/volume-type.rst b/doc/source/command-objects/volume-type.rst index dfc169cd14..9141c9ed59 100644 --- a/doc/source/command-objects/volume-type.rst +++ b/doc/source/command-objects/volume-type.rst @@ -71,11 +71,24 @@ List volume types os volume type list [--long] + [--public | --private] .. option:: --long List additional fields in output +.. option:: --public + + List only public types + + *Volume version 2 only* + +.. option:: --private + + List only private types (admin only) + + *Volume version 2 only* + volume type set --------------- diff --git a/openstackclient/tests/volume/v2/test_type.py b/openstackclient/tests/volume/v2/test_type.py index 174f33f2fb..e1c2c1dbc2 100644 --- a/openstackclient/tests/volume/v2/test_type.py +++ b/openstackclient/tests/volume/v2/test_type.py @@ -176,23 +176,50 @@ def setUp(self): def test_type_list_without_options(self): arglist = [] verifylist = [ - ("long", False) + ("long", False), + ("private", False), + ("public", False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) + self.types_mock.list.assert_called_once_with(is_public=None) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) def test_type_list_with_options(self): - arglist = ["--long"] - verifylist = [("long", True)] + arglist = [ + "--long", + "--public", + ] + verifylist = [ + ("long", True), + ("private", False), + ("public", True), + ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) + self.types_mock.list.assert_called_once_with(is_public=True) self.assertEqual(self.columns_long, columns) self.assertEqual(self.data_long, list(data)) + def test_type_list_with_private_option(self): + arglist = [ + "--private", + ] + verifylist = [ + ("long", False), + ("private", True), + ("public", False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.types_mock.list.assert_called_once_with(is_public=False) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + class TestTypeSet(TestType): diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index 91ac17a16f..05597bec62 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -135,6 +135,17 @@ def get_parser(self, prog_name): action='store_true', default=False, help=_('List additional fields in output')) + public_group = parser.add_mutually_exclusive_group() + public_group.add_argument( + "--public", + action="store_true", + help=_("List only public types") + ) + public_group.add_argument( + "--private", + action="store_true", + help=_("List only private types (admin only)") + ) return parser def take_action(self, parsed_args): @@ -144,7 +155,14 @@ def take_action(self, parsed_args): else: columns = ['ID', 'Name'] column_headers = columns - data = self.app.client_manager.volume.volume_types.list() + + is_public = None + if parsed_args.public: + is_public = True + if parsed_args.private: + is_public = False + data = self.app.client_manager.volume.volume_types.list( + is_public=is_public) return (column_headers, (utils.get_item_properties( s, columns, diff --git a/releasenotes/notes/bug-1597198-e36b55f3fd185a3a.yaml b/releasenotes/notes/bug-1597198-e36b55f3fd185a3a.yaml new file mode 100644 index 0000000000..a08caace84 --- /dev/null +++ b/releasenotes/notes/bug-1597198-e36b55f3fd185a3a.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``--public`` and ``--private`` options to ``volume type list`` command. + [Bug `1597198 `_] From bcaa5c2e0ea7d2f6390aba1debbe026c1599a0b2 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Mon, 25 Jul 2016 15:09:49 +0800 Subject: [PATCH 1132/3095] Use assertEqual() instead of assertDictEqual() In unittest2, assertDictEqual() is implemented by using != operator to compare two dicts. So is assertEqual() in testtools. assertEqual() in testtools is able to handle dict, list, set and so on. So we just call assertEqual() to make the unit tests simpler. Change-Id: Ice343b2ce468acae39d2ad79f7121503e3627656 --- openstackclient/tests/common/test_parseractions.py | 2 +- openstackclient/tests/compute/v2/test_server.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openstackclient/tests/common/test_parseractions.py b/openstackclient/tests/common/test_parseractions.py index 60d4a8cfa2..3038701f50 100644 --- a/openstackclient/tests/common/test_parseractions.py +++ b/openstackclient/tests/common/test_parseractions.py @@ -49,7 +49,7 @@ def test_good_values(self): actual = getattr(results, 'property', {}) # All should pass through unmolested expect = {'red': '', 'green': '100%', 'blue': '50%', 'format': '#rgb'} - self.assertDictEqual(expect, actual) + self.assertEqual(expect, actual) def test_error_values(self): self.assertRaises( diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 9c89c6af7d..e487d57c5b 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -1793,4 +1793,4 @@ def test_prep_server_detail(self, find_resource): server_detail.pop('networks') # Check the results. - self.assertDictEqual(info, server_detail) + self.assertEqual(info, server_detail) From 0bc2d83f685790c154e1c4acb824a55cf0db84b4 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Mon, 25 Jul 2016 14:29:08 -0500 Subject: [PATCH 1133/3095] Remove temporary code in ClientManager This effectively reverts https://review.openstack.org/#/c/341618/. Change-Id: Ic8e53e17b4a5352b0c00e39bcb5d248b057540a9 --- openstackclient/common/clientmanager.py | 73 ------------------------- 1 file changed, 73 deletions(-) diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index 9e4b9c937f..2105a49711 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -19,8 +19,6 @@ import pkg_resources import sys -from keystoneauth1.loading import base -from osc_lib.api import auth from osc_lib import clientmanager @@ -31,77 +29,6 @@ USER_AGENT = 'python-openstackclient' -# NOTE(dtroyer): Bringing back select_auth_plugin() and build_auth_params() -# temporarily because osc-lib 0.3.0 removed it a wee bit early -def select_auth_plugin(options): - """Pick an auth plugin based on --os-auth-type or other options""" - - auth_plugin_name = None - - # Do the token/url check first as this must override the default - # 'password' set by os-client-config - # Also, url and token are not copied into o-c-c's auth dict (yet?) - if options.auth.get('url') and options.auth.get('token'): - # service token authentication - auth_plugin_name = 'token_endpoint' - elif options.auth_type in auth.PLUGIN_LIST: - # A direct plugin name was given, use it - auth_plugin_name = options.auth_type - elif options.auth.get('username'): - if options.identity_api_version == '3': - auth_plugin_name = 'v3password' - elif options.identity_api_version.startswith('2'): - auth_plugin_name = 'v2password' - else: - # let keystoneauth figure it out itself - auth_plugin_name = 'password' - elif options.auth.get('token'): - if options.identity_api_version == '3': - auth_plugin_name = 'v3token' - elif options.identity_api_version.startswith('2'): - auth_plugin_name = 'v2token' - else: - # let keystoneauth figure it out itself - auth_plugin_name = 'token' - else: - # The ultimate default is similar to the original behaviour, - # but this time with version discovery - auth_plugin_name = 'password' - LOG.debug("Auth plugin %s selected", auth_plugin_name) - return auth_plugin_name - - -def build_auth_params(auth_plugin_name, cmd_options): - if auth_plugin_name: - LOG.debug('auth_type: %s', auth_plugin_name) - auth_plugin_loader = base.get_plugin_loader(auth_plugin_name) - auth_params = { - opt.dest: opt.default - for opt in base.get_plugin_options(auth_plugin_name) - } - auth_params.update(dict(cmd_options.auth)) - # grab tenant from project for v2.0 API compatibility - if auth_plugin_name.startswith("v2"): - if 'project_id' in auth_params: - auth_params['tenant_id'] = auth_params['project_id'] - del auth_params['project_id'] - if 'project_name' in auth_params: - auth_params['tenant_name'] = auth_params['project_name'] - del auth_params['project_name'] - else: - LOG.debug('no auth_type') - # delay the plugin choice, grab every option - auth_plugin_loader = None - auth_params = dict(cmd_options.auth) - plugin_options = set( - [o.replace('-', '_') for o in auth.get_options_list()] - ) - for option in plugin_options: - LOG.debug('fetching option %s', option) - auth_params[option] = getattr(cmd_options.auth, option, None) - return (auth_plugin_loader, auth_params) - - class ClientManager(clientmanager.ClientManager): """Manages access to API clients, including authentication From 1b2ea91420e297d51e66b985b8fb04c45059bd85 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Fri, 15 Jul 2016 19:02:18 +0800 Subject: [PATCH 1134/3095] Use identity fake classes instead of old unit tests data Use FakeProject, FakeDomain and FakeUser classes instead of old test data in image, compute and volume. (This work has done in network) Change-Id: Ic37d5d9db98f0f2acc6c714a9646063dcbde4ff3 Partially-Implements: blueprint use-fake-project --- .../tests/compute/v2/test_flavor.py | 62 ++++------ openstackclient/tests/image/v2/test_image.py | 114 +++++++----------- .../tests/volume/v1/test_volume.py | 51 +++----- openstackclient/tests/volume/v2/test_type.py | 28 ++--- .../tests/volume/v2/test_volume.py | 96 ++++++--------- 5 files changed, 136 insertions(+), 215 deletions(-) diff --git a/openstackclient/tests/compute/v2/test_flavor.py b/openstackclient/tests/compute/v2/test_flavor.py index 40cd17c118..d326ea8a88 100644 --- a/openstackclient/tests/compute/v2/test_flavor.py +++ b/openstackclient/tests/compute/v2/test_flavor.py @@ -13,7 +13,6 @@ # under the License. # -import copy import mock from mock import call @@ -22,7 +21,6 @@ from openstackclient.compute.v2 import flavor from openstackclient.tests.compute.v2 import fakes as compute_fakes -from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes from openstackclient.tests import utils as tests_utils @@ -48,7 +46,7 @@ class TestFlavorCreate(TestFlavor): flavor = compute_fakes.FakeFlavor.create_one_flavor( attrs={'links': 'flavor-links'}) - + project = identity_fakes.FakeProject.create_one_project() columns = ( 'OS-FLV-DISABLED:disabled', 'OS-FLV-EXT-DATA:ephemeral', @@ -80,11 +78,7 @@ def setUp(self): super(TestFlavorCreate, self).setUp() # Return a project - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) + self.projects_mock.get.return_value = self.project self.flavors_mock.create.return_value = self.flavor self.cmd = flavor.CreateFlavor(self.app, None) @@ -174,7 +168,7 @@ def test_flavor_create_other_options(self): '--vcpus', str(self.flavor.vcpus), '--rxtx-factor', str(self.flavor.rxtx_factor), '--private', - '--project', identity_fakes.project_id, + '--project', self.project.id, '--property', 'key1=value1', '--property', 'key2=value2', self.flavor.name, @@ -188,7 +182,7 @@ def test_flavor_create_other_options(self): ('vcpus', self.flavor.vcpus), ('rxtx_factor', self.flavor.rxtx_factor), ('public', False), - ('project', identity_fakes.project_id), + ('project', self.project.id), ('property', {'key1': 'value1', 'key2': 'value2'}), ('name', self.flavor.name), ] @@ -209,7 +203,7 @@ def test_flavor_create_other_options(self): self.flavors_mock.create.assert_called_once_with(*args) self.flavor_access_mock.add_tenant_access.assert_called_with( self.flavor.id, - identity_fakes.project_id, + self.project.id, ) self.flavor.set_keys.assert_called_with( {'key1': 'value1', 'key2': 'value2'}) @@ -219,11 +213,11 @@ def test_flavor_create_other_options(self): def test_public_flavor_create_with_project(self): arglist = [ - '--project', identity_fakes.project_id, + '--project', self.project.id, self.flavor.name, ] verifylist = [ - ('project', identity_fakes.project_id), + ('project', self.project.id), ('name', self.flavor.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -507,6 +501,7 @@ class TestFlavorSet(TestFlavor): # Return value of self.flavors_mock.find(). flavor = compute_fakes.FakeFlavor.create_one_flavor( attrs={'os-flavor-access:is_public': False}) + project = identity_fakes.FakeProject.create_one_project() def setUp(self): super(TestFlavorSet, self).setUp() @@ -514,11 +509,7 @@ def setUp(self): self.flavors_mock.find.return_value = self.flavor self.flavors_mock.get.side_effect = exceptions.NotFound(None) # Return a project - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) + self.projects_mock.get.return_value = self.project self.cmd = flavor.SetFlavor(self.app, None) def test_flavor_set_property(self): @@ -540,11 +531,11 @@ def test_flavor_set_property(self): def test_flavor_set_project(self): arglist = [ - '--project', identity_fakes.project_id, + '--project', self.project.id, self.flavor.id, ] verifylist = [ - ('project', identity_fakes.project_id), + ('project', self.project.id), ('flavor', self.flavor.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -555,7 +546,7 @@ def test_flavor_set_project(self): is_public=None) self.flavor_access_mock.add_tenant_access.assert_called_with( self.flavor.id, - identity_fakes.project_id, + self.project.id, ) self.flavor.set_keys.assert_not_called() self.assertIsNone(result) @@ -574,10 +565,10 @@ def test_flavor_set_no_project(self): def test_flavor_set_no_flavor(self): arglist = [ - '--project', identity_fakes.project_id, + '--project', self.project.id, ] verifylist = [ - ('project', identity_fakes.project_id), + ('project', self.project.id), ] self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) @@ -587,11 +578,11 @@ def test_flavor_set_with_unexist_flavor(self): self.flavors_mock.find.side_effect = exceptions.NotFound(None) arglist = [ - '--project', identity_fakes.project_id, + '--project', self.project.id, 'unexist_flavor', ] verifylist = [ - ('project', identity_fakes.project_id), + ('project', self.project.id), ('flavor', 'unexist_flavor'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -729,6 +720,7 @@ class TestFlavorUnset(TestFlavor): # Return value of self.flavors_mock.find(). flavor = compute_fakes.FakeFlavor.create_one_flavor( attrs={'os-flavor-access:is_public': False}) + project = identity_fakes.FakeProject.create_one_project() def setUp(self): super(TestFlavorUnset, self).setUp() @@ -736,11 +728,7 @@ def setUp(self): self.flavors_mock.find.return_value = self.flavor self.flavors_mock.get.side_effect = exceptions.NotFound(None) # Return a project - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) + self.projects_mock.get.return_value = self.project self.cmd = flavor.UnsetFlavor(self.app, None) def test_flavor_unset_property(self): @@ -763,11 +751,11 @@ def test_flavor_unset_property(self): def test_flavor_unset_project(self): arglist = [ - '--project', identity_fakes.project_id, + '--project', self.project.id, self.flavor.id, ] verifylist = [ - ('project', identity_fakes.project_id), + ('project', self.project.id), ('flavor', self.flavor.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -779,7 +767,7 @@ def test_flavor_unset_project(self): is_public=None) self.flavor_access_mock.remove_tenant_access.assert_called_with( self.flavor.id, - identity_fakes.project_id, + self.project.id, ) self.flavor.unset_keys.assert_not_called() self.assertIsNone(result) @@ -798,10 +786,10 @@ def test_flavor_unset_no_project(self): def test_flavor_unset_no_flavor(self): arglist = [ - '--project', identity_fakes.project_id, + '--project', self.project.id, ] verifylist = [ - ('project', identity_fakes.project_id), + ('project', self.project.id), ] self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) @@ -811,11 +799,11 @@ def test_flavor_unset_with_unexist_flavor(self): self.flavors_mock.find.side_effect = exceptions.NotFound(None) arglist = [ - '--project', identity_fakes.project_id, + '--project', self.project.id, 'unexist_flavor', ] verifylist = [ - ('project', identity_fakes.project_id), + ('project', self.project.id), ('flavor', 'unexist_flavor'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index c6b83bc5fe..d9cca8afeb 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -23,7 +23,6 @@ from osc_lib import utils as common_utils from openstackclient.image.v2 import image -from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes from openstackclient.tests.image.v2 import fakes as image_fakes @@ -58,23 +57,18 @@ def setup_images_mock(self, count): class TestImageCreate(TestImage): + project = identity_fakes.FakeProject.create_one_project() + domain = identity_fakes.FakeDomain.create_one_domain() + def setUp(self): super(TestImageCreate, self).setUp() self.new_image = image_fakes.FakeImage.create_one_image() self.images_mock.create.return_value = self.new_image - self.project_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) + self.project_mock.get.return_value = self.project - self.domain_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.DOMAIN), - loaded=True, - ) + self.domain_mock.get.return_value = self.domain # This is the return value for utils.find_resource() self.images_mock.get.return_value = copy.deepcopy( @@ -145,7 +139,7 @@ def test_image_reserve_options(self, mock_open): ('--private' if self.new_image.visibility == 'private' else '--public'), '--project', self.new_image.owner, - '--project-domain', identity_fakes.domain_id, + '--project-domain', self.domain.id, self.new_image.name, ] verifylist = [ @@ -158,7 +152,7 @@ def test_image_reserve_options(self, mock_open): ('public', self.new_image.visibility == 'public'), ('private', self.new_image.visibility == 'private'), ('project', self.new_image.owner), - ('project_domain', identity_fakes.domain_id), + ('project_domain', self.domain.id), ('name', self.new_image.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -175,7 +169,7 @@ def test_image_reserve_options(self, mock_open): disk_format='fs', min_disk=10, min_ram=4, - owner=identity_fakes.project_id, + owner=self.project.id, protected=self.new_image.protected, visibility=self.new_image.visibility, ) @@ -346,10 +340,12 @@ def test_image_create_dead_options(self): class TestAddProjectToImage(TestImage): + project = identity_fakes.FakeProject.create_one_project() + domain = identity_fakes.FakeDomain.create_one_domain() _image = image_fakes.FakeImage.create_one_image() new_member = image_fakes.FakeImage.create_one_image_member( attrs={'image_id': _image.id, - 'member_id': identity_fakes.project_id} + 'member_id': project.id} ) columns = ( @@ -360,8 +356,8 @@ class TestAddProjectToImage(TestImage): datalist = ( _image.id, - identity_fakes.project_id, - new_member.status + new_member.member_id, + new_member.status, ) def setUp(self): @@ -372,27 +368,19 @@ def setUp(self): # Update the image_id in the MEMBER dict self.image_members_mock.create.return_value = self.new_member - self.project_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) - self.domain_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.DOMAIN), - loaded=True, - ) + self.project_mock.get.return_value = self.project + self.domain_mock.get.return_value = self.domain # Get the command object to test self.cmd = image.AddProjectToImage(self.app, None) def test_add_project_to_image_no_option(self): arglist = [ self._image.id, - identity_fakes.project_id, + self.project.id, ] verifylist = [ ('image', self._image.id), - ('project', identity_fakes.project_id), + ('project', self.project.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -402,7 +390,7 @@ def test_add_project_to_image_no_option(self): columns, data = self.cmd.take_action(parsed_args) self.image_members_mock.create.assert_called_with( self._image.id, - identity_fakes.project_id + self.project.id ) self.assertEqual(self.columns, columns) @@ -411,13 +399,13 @@ def test_add_project_to_image_no_option(self): def test_add_project_to_image_with_option(self): arglist = [ self._image.id, - identity_fakes.project_id, - '--project-domain', identity_fakes.domain_id, + self.project.id, + '--project-domain', self.domain.id, ] verifylist = [ ('image', self._image.id), - ('project', identity_fakes.project_id), - ('project_domain', identity_fakes.domain_id), + ('project', self.project.id), + ('project_domain', self.domain.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -427,7 +415,7 @@ def test_add_project_to_image_with_option(self): columns, data = self.cmd.take_action(parsed_args) self.image_members_mock.create.assert_called_with( self._image.id, - identity_fakes.project_id + self.project.id ) self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, data) @@ -755,6 +743,9 @@ def test_image_list_marker_option(self, fr_mock): class TestRemoveProjectImage(TestImage): + project = identity_fakes.FakeProject.create_one_project() + domain = identity_fakes.FakeDomain.create_one_domain() + def setUp(self): super(TestRemoveProjectImage, self).setUp() @@ -762,16 +753,8 @@ def setUp(self): # This is the return value for utils.find_resource() self.images_mock.get.return_value = self._image - self.project_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) - self.domain_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.DOMAIN), - loaded=True, - ) + self.project_mock.get.return_value = self.project + self.domain_mock.get.return_value = self.domain self.image_members_mock.delete.return_value = None # Get the command object to test self.cmd = image.RemoveProjectImage(self.app, None) @@ -779,11 +762,11 @@ def setUp(self): def test_remove_project_image_no_options(self): arglist = [ self._image.id, - identity_fakes.project_id, + self.project.id, ] verifylist = [ ('image', self._image.id), - ('project', identity_fakes.project_id), + ('project', self.project.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -791,20 +774,20 @@ def test_remove_project_image_no_options(self): self.image_members_mock.delete.assert_called_with( self._image.id, - identity_fakes.project_id, + self.project.id, ) self.assertIsNone(result) def test_remove_project_image_with_options(self): arglist = [ self._image.id, - identity_fakes.project_id, - '--project-domain', identity_fakes.domain_id, + self.project.id, + '--project-domain', self.domain.id, ] verifylist = [ ('image', self._image.id), - ('project', identity_fakes.project_id), - ('project_domain', identity_fakes.domain_id), + ('project', self.project.id), + ('project_domain', self.domain.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -812,13 +795,16 @@ def test_remove_project_image_with_options(self): self.image_members_mock.delete.assert_called_with( self._image.id, - identity_fakes.project_id, + self.project.id, ) self.assertIsNone(result) class TestImageSet(TestImage): + project = identity_fakes.FakeProject.create_one_project() + domain = identity_fakes.FakeDomain.create_one_domain() + def setUp(self): super(TestImageSet, self).setUp() # Set up the schema @@ -827,17 +813,9 @@ def setUp(self): schemas.SchemaBasedModel, ) - self.project_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) + self.project_mock.get.return_value = self.project - self.domain_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.DOMAIN), - loaded=True, - ) + self.domain_mock.get.return_value = self.domain self.images_mock.get.return_value = self.model(**image_fakes.IMAGE) self.images_mock.update.return_value = self.model(**image_fakes.IMAGE) @@ -864,8 +842,8 @@ def test_image_set_options(self): '--min-ram', '4', '--container-format', 'ovf', '--disk-format', 'vmdk', - '--project', identity_fakes.project_name, - '--project-domain', identity_fakes.domain_id, + '--project', self.project.name, + '--project-domain', self.domain.id, image_fakes.image_id, ] verifylist = [ @@ -874,8 +852,8 @@ def test_image_set_options(self): ('min_ram', 4), ('container_format', 'ovf'), ('disk_format', 'vmdk'), - ('project', identity_fakes.project_name), - ('project_domain', identity_fakes.domain_id), + ('project', self.project.name), + ('project_domain', self.domain.id), ('image', image_fakes.image_id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -884,7 +862,7 @@ def test_image_set_options(self): kwargs = { 'name': 'new-name', - 'owner': identity_fakes.project_id, + 'owner': self.project.id, 'min_disk': 2, 'min_ram': 4, 'container_format': 'ovf', diff --git a/openstackclient/tests/volume/v1/test_volume.py b/openstackclient/tests/volume/v1/test_volume.py index 380bc632d6..e41c2a7266 100644 --- a/openstackclient/tests/volume/v1/test_volume.py +++ b/openstackclient/tests/volume/v1/test_volume.py @@ -50,6 +50,9 @@ def setUp(self): class TestVolumeCreate(TestVolume): + project = identity_fakes.FakeProject.create_one_project() + user = identity_fakes.FakeUser.create_one_user() + columns = ( 'attach_status', 'availability_zone', @@ -168,28 +171,20 @@ def test_volume_create_options(self): def test_volume_create_user_project_id(self): # Return a project - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) + self.projects_mock.get.return_value = self.project # Return a user - self.users_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.USER), - loaded=True, - ) + self.users_mock.get.return_value = self.user arglist = [ '--size', str(volume_fakes.volume_size), - '--project', identity_fakes.project_id, - '--user', identity_fakes.user_id, + '--project', self.project.id, + '--user', self.user.id, volume_fakes.volume_name, ] verifylist = [ ('size', volume_fakes.volume_size), - ('project', identity_fakes.project_id), - ('user', identity_fakes.user_id), + ('project', self.project.id), + ('user', self.user.id), ('name', volume_fakes.volume_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -211,8 +206,8 @@ def test_volume_create_user_project_id(self): volume_fakes.volume_name, None, None, - identity_fakes.user_id, - identity_fakes.project_id, + self.user.id, + self.project.id, None, None, None, @@ -223,28 +218,20 @@ def test_volume_create_user_project_id(self): def test_volume_create_user_project_name(self): # Return a project - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) + self.projects_mock.get.return_value = self.project # Return a user - self.users_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.USER), - loaded=True, - ) + self.users_mock.get.return_value = self.user arglist = [ '--size', str(volume_fakes.volume_size), - '--project', identity_fakes.project_name, - '--user', identity_fakes.user_name, + '--project', self.project.name, + '--user', self.user.name, volume_fakes.volume_name, ] verifylist = [ ('size', volume_fakes.volume_size), - ('project', identity_fakes.project_name), - ('user', identity_fakes.user_name), + ('project', self.project.name), + ('user', self.user.name), ('name', volume_fakes.volume_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -266,8 +253,8 @@ def test_volume_create_user_project_name(self): volume_fakes.volume_name, None, None, - identity_fakes.user_id, - identity_fakes.project_id, + self.user.id, + self.project.id, None, None, None, diff --git a/openstackclient/tests/volume/v2/test_type.py b/openstackclient/tests/volume/v2/test_type.py index e148bba420..238b098f15 100644 --- a/openstackclient/tests/volume/v2/test_type.py +++ b/openstackclient/tests/volume/v2/test_type.py @@ -12,13 +12,11 @@ # under the License. # -import copy import mock from osc_lib import exceptions from osc_lib import utils -from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes from openstackclient.tests import utils as tests_utils from openstackclient.tests.volume.v2 import fakes as volume_fakes @@ -219,6 +217,7 @@ def test_type_list_with_options(self): class TestTypeSet(TestType): + project = identity_fakes.FakeProject.create_one_project() volume_type = volume_fakes.FakeType.create_one_type( methods={'set_keys': None}) @@ -228,11 +227,7 @@ def setUp(self): self.types_mock.get.return_value = self.volume_type # Return a project - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) + self.projects_mock.get.return_value = self.project # Get the command object to test self.cmd = volume_type.SetVolumeType(self.app, None) @@ -339,11 +334,11 @@ def test_type_set_failed_with_missing_volume_type_argument(self): def test_type_set_project_access(self): arglist = [ - '--project', identity_fakes.project_id, + '--project', self.project.id, self.volume_type.id, ] verifylist = [ - ('project', identity_fakes.project_id), + ('project', self.project.id), ('volume_type', self.volume_type.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -353,7 +348,7 @@ def test_type_set_project_access(self): self.types_access_mock.add_project_access.assert_called_with( self.volume_type.id, - identity_fakes.project_id, + self.project.id, ) @@ -469,6 +464,7 @@ def test_type_show_with_list_access_exec(self): class TestTypeUnset(TestType): + project = identity_fakes.FakeProject.create_one_project() volume_type = volume_fakes.FakeType.create_one_type( methods={'unset_keys': None}) @@ -478,11 +474,7 @@ def setUp(self): self.types_mock.get.return_value = self.volume_type # Return a project - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) + self.projects_mock.get.return_value = self.project # Get the command object to test self.cmd = volume_type.UnsetVolumeType(self.app, None) @@ -507,11 +499,11 @@ def test_type_unset(self): def test_type_unset_project_access(self): arglist = [ - '--project', identity_fakes.project_id, + '--project', self.project.id, self.volume_type.id, ] verifylist = [ - ('project', identity_fakes.project_id), + ('project', self.project.id), ('volume_type', self.volume_type.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -521,7 +513,7 @@ def test_type_unset_project_access(self): self.types_access_mock.remove_project_access.assert_called_with( self.volume_type.id, - identity_fakes.project_id, + self.project.id, ) def test_type_unset_not_called_without_project_argument(self): diff --git a/openstackclient/tests/volume/v2/test_volume.py b/openstackclient/tests/volume/v2/test_volume.py index db65c3bdd1..25d0e92fef 100644 --- a/openstackclient/tests/volume/v2/test_volume.py +++ b/openstackclient/tests/volume/v2/test_volume.py @@ -12,14 +12,12 @@ # under the License. # -import copy import mock from mock import call from osc_lib import exceptions from osc_lib import utils -from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes from openstackclient.tests.image.v2 import fakes as image_fakes from openstackclient.tests.volume.v2 import fakes as volume_fakes @@ -57,6 +55,9 @@ def setup_volumes_mock(self, count): class TestVolumeCreate(TestVolume): + project = identity_fakes.FakeProject.create_one_project() + user = identity_fakes.FakeUser.create_one_user() + columns = ( 'attachments', 'availability_zone', @@ -168,28 +169,20 @@ def test_volume_create_options(self): def test_volume_create_user_project_id(self): # Return a project - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) + self.projects_mock.get.return_value = self.project # Return a user - self.users_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.USER), - loaded=True, - ) + self.users_mock.get.return_value = self.user arglist = [ '--size', str(self.new_volume.size), - '--project', identity_fakes.project_id, - '--user', identity_fakes.user_id, + '--project', self.project.id, + '--user', self.user.id, self.new_volume.name, ] verifylist = [ ('size', self.new_volume.size), - ('project', identity_fakes.project_id), - ('user', identity_fakes.user_id), + ('project', self.project.id), + ('user', self.user.id), ('name', self.new_volume.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -205,8 +198,8 @@ def test_volume_create_user_project_id(self): name=self.new_volume.name, description=None, volume_type=None, - user_id=identity_fakes.user_id, - project_id=identity_fakes.project_id, + user_id=self.user.id, + project_id=self.project.id, availability_zone=None, metadata=None, imageRef=None, @@ -218,28 +211,20 @@ def test_volume_create_user_project_id(self): def test_volume_create_user_project_name(self): # Return a project - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) + self.projects_mock.get.return_value = self.project # Return a user - self.users_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.USER), - loaded=True, - ) + self.users_mock.get.return_value = self.user arglist = [ '--size', str(self.new_volume.size), - '--project', identity_fakes.project_name, - '--user', identity_fakes.user_name, + '--project', self.project.name, + '--user', self.user.name, self.new_volume.name, ] verifylist = [ ('size', self.new_volume.size), - ('project', identity_fakes.project_name), - ('user', identity_fakes.user_name), + ('project', self.project.name), + ('user', self.user.name), ('name', self.new_volume.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -255,8 +240,8 @@ def test_volume_create_user_project_name(self): name=self.new_volume.name, description=None, volume_type=None, - user_id=identity_fakes.user_id, - project_id=identity_fakes.project_id, + user_id=self.user.id, + project_id=self.project.id, availability_zone=None, metadata=None, imageRef=None, @@ -492,6 +477,9 @@ def test_volume_delete_multi_volumes_with_exception(self): class TestVolumeList(TestVolume): + project = identity_fakes.FakeProject.create_one_project() + user = identity_fakes.FakeUser.create_one_user() + columns = [ 'ID', 'Display Name', @@ -506,21 +494,9 @@ def setUp(self): self.mock_volume = volume_fakes.FakeVolume.create_one_volume() self.volumes_mock.list.return_value = [self.mock_volume] - self.users_mock.get.return_value = [ - fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.USER), - loaded=True, - ), - ] + self.users_mock.get.return_value = self.user - self.projects_mock.get.return_value = [ - fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ), - ] + self.projects_mock.get.return_value = self.project # Get the command object to test self.cmd = volume.ListVolume(self.app, None) @@ -553,10 +529,10 @@ def test_volume_list_no_options(self): def test_volume_list_project(self): arglist = [ - '--project', identity_fakes.project_name, + '--project', self.project.name, ] verifylist = [ - ('project', identity_fakes.project_name), + ('project', self.project.name), ('long', False), ('all_projects', False), ('status', None), @@ -581,12 +557,12 @@ def test_volume_list_project(self): def test_volume_list_project_domain(self): arglist = [ - '--project', identity_fakes.project_name, - '--project-domain', identity_fakes.domain_name, + '--project', self.project.name, + '--project-domain', self.project.domain_id, ] verifylist = [ - ('project', identity_fakes.project_name), - ('project_domain', identity_fakes.domain_name), + ('project', self.project.name), + ('project_domain', self.project.domain_id), ('long', False), ('all_projects', False), ('status', None), @@ -611,10 +587,10 @@ def test_volume_list_project_domain(self): def test_volume_list_user(self): arglist = [ - '--user', identity_fakes.user_name, + '--user', self.user.name, ] verifylist = [ - ('user', identity_fakes.user_name), + ('user', self.user.name), ('long', False), ('all_projects', False), ('status', None), @@ -638,12 +614,12 @@ def test_volume_list_user(self): def test_volume_list_user_domain(self): arglist = [ - '--user', identity_fakes.user_name, - '--user-domain', identity_fakes.domain_name, + '--user', self.user.name, + '--user-domain', self.user.domain_id, ] verifylist = [ - ('user', identity_fakes.user_name), - ('user_domain', identity_fakes.domain_name), + ('user', self.user.name), + ('user_domain', self.user.domain_id), ('long', False), ('all_projects', False), ('status', None), From 756d2fac67b4128312e1d779648e62f1458b4ffc Mon Sep 17 00:00:00 2001 From: John Dennis Date: Fri, 15 Jul 2016 14:46:29 -0400 Subject: [PATCH 1135/3095] arguments are not locale decoded into Unicode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the openstackclient in Python2 passes command line arguments to a subcommand it fails to pass the arguments as text (e.g. Unicode). Instead it passes the arguments as binary data encoded using the current locales encoding. An easy way to see this is trying to pass a username with a non-ASCII character. % openstack user delete ñew No user with a name or ID of 'ñew' exists. What occurs internally is when the user data is retrieved it's it properly represented in a Unicode object. However the username pased from the command line is still a str object encoded in the locales encoding (typically UTF-8). A string comparison is attempted between the encoded data from the command line and the Unicode text found in the user representation. This seldom ends well, either the comparison fails to match or a codec error is raised. There is a hard and fast rule, all text data must be stored in Unicode objects and the conversion from binary encoded text to Unicode must occur as close to the I/O boundary as possible. Python3 enforces this behavior automatically but in Python2 it is the programmers job to do so. In the past there have been attempts to fix problems deep inside internal code by attempting to decode from UTF-8. There are two problems with this approach. First, internal code has no way to accurately know what encoding was used to encode the binary data. This is way it needs to be decoded as close to the I/O source as possible because that is the best place to know the actual encoding. Guessing UTF-8 is at best a heuristic. Second, there must be a canonical representation for data "inside" the program, you don't want dozens of individual modules, classes, methods, etc. performing conversions, instead they should be able to make the assumption in what format text is represented in, the format for text data must be Unicode. This is another reason to decode as close to the I/O as possible. In Python3 the argv strings are decoded from the locales encoding by the interpreter. By the time any Python3 code sees the argv strings they will be Unicode. However in Python2 there must be explicit code added to decode the argv strings into Unicode. The conversion of sys.argv into Unicode only occurs when argv is not passed to OpenStackShell.run(). If a caller of OpenStackShell.run() supplies their own arg it is their responsiblity to assure they are passing actual text objects. Consider this a requirement of the API. Note: This patch does not contain a unittest to exercise the behavior because it is difficult to construct a test that depends on command invocation from a shell. The general structure of the unit tests is to pass fake argv into OpenStackShell.run() as if it came from a shell. Because the new code only operates when argv is not passed and defaults to sys.argv it conflicts with the unittest design. Change-Id: I779d260744728eae8455ff9dedb6e5c09c165559 Closes-Bug: 1603494 Signed-off-by: John Dennis --- openstackclient/shell.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index b4f2df43b2..d6ce4ef661 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -18,7 +18,9 @@ import argparse import getpass +import locale import logging +import six import sys import traceback @@ -474,8 +476,17 @@ def clean_up(self, cmd, result, err): tcmd.run(targs) -def main(argv=sys.argv[1:]): +def main(argv=None): + if argv is None: + argv = sys.argv[1:] + if six.PY2: + # Emulate Py3, decode argv into Unicode based on locale so that + # commands always see arguments as text instead of binary data + encoding = locale.getpreferredencoding() + if encoding: + argv = map(lambda arg: arg.decode(encoding), argv) + return OpenStackShell().run(argv) if __name__ == "__main__": - sys.exit(main(sys.argv[1:])) + sys.exit(main()) From 954c28dfa21be76b0522af051d71fb9470877a1a Mon Sep 17 00:00:00 2001 From: wuyuting Date: Sun, 5 Jun 2016 22:55:43 -0400 Subject: [PATCH 1136/3095] Add support for deleting volumes with associated snapshots OSC doesn't support deleting volumes with associated snapshots. This patch provides support for deleting volumes with associated snapshots by adding an optional argument. Change-Id: I7e74f251574993ff13a38e508fd2f9debeda8d0a Closes-Bug: #1589332 Co-Authored-By: Rui Chen --- doc/source/command-objects/volume.rst | 8 ++- .../tests/volume/v2/test_volume.py | 55 +++++++++++++++++-- openstackclient/volume/v2/volume.py | 17 ++++-- .../notes/bug-1589332-2941f5286df7e5d4.yaml | 7 +++ 4 files changed, 76 insertions(+), 11 deletions(-) create mode 100644 releasenotes/notes/bug-1589332-2941f5286df7e5d4.yaml diff --git a/doc/source/command-objects/volume.rst b/doc/source/command-objects/volume.rst index a51d1117d1..021518be7f 100644 --- a/doc/source/command-objects/volume.rst +++ b/doc/source/command-objects/volume.rst @@ -88,13 +88,19 @@ Delete volume(s) .. code:: bash os volume delete - [--force] + [--force | --purge] [ ...] .. option:: --force Attempt forced removal of volume(s), regardless of state (defaults to False) +.. option:: --purge + + Remove any snapshots along with volume(s) (defaults to False) + + *Volume version 2 only* + .. _volume_delete-volume: .. describe:: diff --git a/openstackclient/tests/volume/v2/test_volume.py b/openstackclient/tests/volume/v2/test_volume.py index 25d0e92fef..6f552ad61b 100644 --- a/openstackclient/tests/volume/v2/test_volume.py +++ b/openstackclient/tests/volume/v2/test_volume.py @@ -420,13 +420,16 @@ def test_volume_delete_one_volume(self): volumes[0].id ] verifylist = [ - ("volumes", [volumes[0].id]) + ("force", False), + ("purge", False), + ("volumes", [volumes[0].id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.volumes_mock.delete.assert_called_with(volumes[0].id) + self.volumes_mock.delete.assert_called_once_with( + volumes[0].id, cascade=False) self.assertIsNone(result) def test_volume_delete_multi_volumes(self): @@ -434,13 +437,15 @@ def test_volume_delete_multi_volumes(self): arglist = [v.id for v in volumes] verifylist = [ + ('force', False), + ('purge', False), ('volumes', arglist), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - calls = [call(v.id) for v in volumes] + calls = [call(v.id, cascade=False) for v in volumes] self.volumes_mock.delete.assert_has_calls(calls) self.assertIsNone(result) @@ -452,6 +457,8 @@ def test_volume_delete_multi_volumes_with_exception(self): 'unexist_volume', ] verifylist = [ + ('force', False), + ('purge', False), ('volumes', arglist), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -471,8 +478,46 @@ def test_volume_delete_multi_volumes_with_exception(self): self.assertEqual(2, find_mock.call_count) self.volumes_mock.delete.assert_called_once_with( - volumes[0].id - ) + volumes[0].id, cascade=False) + + def test_volume_delete_with_purge(self): + volumes = self.setup_volumes_mock(count=1) + + arglist = [ + '--purge', + volumes[0].id, + ] + verifylist = [ + ('force', False), + ('purge', True), + ('volumes', [volumes[0].id]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.volumes_mock.delete.assert_called_once_with( + volumes[0].id, cascade=True) + self.assertIsNone(result) + + def test_volume_delete_with_force(self): + volumes = self.setup_volumes_mock(count=1) + + arglist = [ + '--force', + volumes[0].id, + ] + verifylist = [ + ('force', True), + ('purge', False), + ('volumes', [volumes[0].id]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.volumes_mock.force_delete.assert_called_once_with(volumes[0].id) + self.assertIsNone(result) class TestVolumeList(TestVolume): diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index 85f267ef58..6f055922f0 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -166,13 +166,19 @@ def get_parser(self, prog_name): nargs="+", help=_("Volume(s) to delete (name or ID)") ) - parser.add_argument( + group = parser.add_mutually_exclusive_group() + group.add_argument( "--force", action="store_true", - default=False, help=_("Attempt forced removal of volume(s), regardless of state " "(defaults to False)") ) + group.add_argument( + "--purge", + action="store_true", + help=_("Remove any snapshots along with volume(s) " + "(defaults to False)") + ) return parser def take_action(self, parsed_args): @@ -186,12 +192,13 @@ def take_action(self, parsed_args): if parsed_args.force: volume_client.volumes.force_delete(volume_obj.id) else: - volume_client.volumes.delete(volume_obj.id) + volume_client.volumes.delete(volume_obj.id, + cascade=parsed_args.purge) except Exception as e: result += 1 LOG.error(_("Failed to delete volume with " - "name or ID '%(volume)s': %(e)s") - % {'volume': i, 'e': e}) + "name or ID '%(volume)s': %(e)s"), + {'volume': i, 'e': e}) if result > 0: total = len(parsed_args.volumes) diff --git a/releasenotes/notes/bug-1589332-2941f5286df7e5d4.yaml b/releasenotes/notes/bug-1589332-2941f5286df7e5d4.yaml new file mode 100644 index 0000000000..0ac17c39ca --- /dev/null +++ b/releasenotes/notes/bug-1589332-2941f5286df7e5d4.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Add ``--purge`` option to ``volume delete`` command (Volume v2 only) in + order to removing any snapshots along with volume automatically when user + delete the volume. + [Bug `1589332 `_] From b50c2b6a8847219ec6d3916f429dec77cf2ba180 Mon Sep 17 00:00:00 2001 From: Elena Ezhova Date: Tue, 12 Jul 2016 14:38:48 +0300 Subject: [PATCH 1137/3095] Allow setting quotas for server groups and server group members Adds support of --server-groups and --server-group-members options to the "quota set" command. Change-Id: I178d1e267d010be7e908adefcf3b15abdafd9da4 Closes-Bug: #1602223 --- doc/source/command-objects/quota.rst | 12 ++++++++++++ openstackclient/common/quota.py | 2 ++ openstackclient/tests/common/test_quota.py | 6 ++++++ openstackclient/tests/compute/v2/fakes.py | 4 ++++ .../add-server-group-quotas-b67fcba98619f0c9.yaml | 6 ++++++ 5 files changed, 30 insertions(+) create mode 100644 releasenotes/notes/add-server-group-quotas-b67fcba98619f0c9.yaml diff --git a/doc/source/command-objects/quota.rst b/doc/source/command-objects/quota.rst index dc5e362355..381601db77 100644 --- a/doc/source/command-objects/quota.rst +++ b/doc/source/command-objects/quota.rst @@ -25,6 +25,8 @@ Set quotas for project [--key-pairs ] [--properties ] [--ram ] + [--server-groups ] + [--server-group-members ] # Block Storage settings [--gigabytes ] @@ -64,6 +66,8 @@ Set quotas for class [--key-pairs ] [--properties ] [--ram ] + [--server-groups ] + [--server-group-members ] # Block Storage settings [--gigabytes ] @@ -108,6 +112,14 @@ Set quotas for class New value for the injected-file-size quota +.. option:: --server-groups + + New value for the server-groups quota + +.. option:: --server-group-members + + New value for the server-group-members quota + .. option:: --floating-ips New value for the floating-ips quota diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index 3c12c3665d..5d53171c33 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -38,6 +38,8 @@ 'key_pairs': 'key-pairs', 'metadata_items': 'properties', 'ram': 'ram', + 'server_groups': 'server-groups', + 'server_group_members': 'server-group-members', } VOLUME_QUOTAS = { diff --git a/openstackclient/tests/common/test_quota.py b/openstackclient/tests/common/test_quota.py index 6f001c8548..16fa35f625 100644 --- a/openstackclient/tests/common/test_quota.py +++ b/openstackclient/tests/common/test_quota.py @@ -115,6 +115,8 @@ def test_quota_set(self): '--properties', str(compute_fakes.property_num), '--secgroup-rules', str(compute_fakes.secgroup_rule_num), '--secgroups', str(compute_fakes.secgroup_num), + '--server-groups', str(compute_fakes.servgroup_num), + '--server-group-members', str(compute_fakes.servgroup_members_num), identity_fakes.project_name, ] verifylist = [ @@ -131,6 +133,8 @@ def test_quota_set(self): ('metadata_items', compute_fakes.property_num), ('security_group_rules', compute_fakes.secgroup_rule_num), ('security_groups', compute_fakes.secgroup_num), + ('server_groups', compute_fakes.servgroup_num), + ('server_group_members', compute_fakes.servgroup_members_num), ('project', identity_fakes.project_name), ] @@ -153,6 +157,8 @@ def test_quota_set(self): 'metadata_items': compute_fakes.property_num, 'security_group_rules': compute_fakes.secgroup_rule_num, 'security_groups': compute_fakes.secgroup_num, + 'server_groups': compute_fakes.servgroup_num, + 'server_group_members': compute_fakes.servgroup_members_num, } self.quotas_mock.update.assert_called_with( diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index b4243a22bb..85c11c9479 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -36,6 +36,8 @@ property_num = 128 secgroup_rule_num = 20 secgroup_num = 10 +servgroup_num = 10 +servgroup_members_num = 10 project_name = 'project_test' QUOTA = { 'project': project_name, @@ -51,6 +53,8 @@ 'properties': property_num, 'secgroup_rules': secgroup_rule_num, 'secgroups': secgroup_num, + 'server-groups': servgroup_num, + 'server-group-members': servgroup_members_num } QUOTA_columns = tuple(sorted(QUOTA)) diff --git a/releasenotes/notes/add-server-group-quotas-b67fcba98619f0c9.yaml b/releasenotes/notes/add-server-group-quotas-b67fcba98619f0c9.yaml new file mode 100644 index 0000000000..9b62b3e6ea --- /dev/null +++ b/releasenotes/notes/add-server-group-quotas-b67fcba98619f0c9.yaml @@ -0,0 +1,6 @@ +--- +features: + - Added support of --server-groups --server-group-members options + to ``quota set`` command. + + [Bug `1602223 `_] From 13bc3793e0f0378db0151acb171dbe5f2d9c08dd Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Mon, 25 Jul 2016 17:37:41 +0800 Subject: [PATCH 1138/3095] Implement network rbac create and delete commands Add "network rbac create" and "network rbac delete" commands and also add unit tests, functional tests, docs and release note for them. Change-Id: I5fd58342f2deaa9bae7717412a942a21bbd7d045 Partially-Implements: blueprint neutron-client-rbac --- doc/source/command-objects/network-rbac.rst | 62 +++++ doc/source/specs/command-objects/example.rst | 2 +- .../tests/network/v2/test_network_rbac.py | 55 ++++ openstackclient/network/v2/network_rbac.py | 132 +++++++++ openstackclient/tests/network/v2/fakes.py | 19 ++ .../tests/network/v2/test_network_rbac.py | 258 +++++++++++++++++- ...-neutron-client-rbac-bbd7b545b50d2bdf.yaml | 3 +- setup.cfg | 2 + 8 files changed, 528 insertions(+), 5 deletions(-) create mode 100644 functional/tests/network/v2/test_network_rbac.py diff --git a/doc/source/command-objects/network-rbac.rst b/doc/source/command-objects/network-rbac.rst index 0cf2127fc2..ff929491ca 100644 --- a/doc/source/command-objects/network-rbac.rst +++ b/doc/source/command-objects/network-rbac.rst @@ -8,6 +8,68 @@ to network resources for specific projects. Network v2 +network rbac create +------------------- + +Create network RBAC policy + +.. program:: network rbac create +.. code:: bash + + os network rbac create + --type + --action + --target-project [--target-project-domain ] + [--project [--project-domain ]] + + +.. option:: --type + + Type of the object that RBAC policy affects ("qos_policy" or "network") (required) + +.. option:: --action + + Action for the RBAC policy ("access_as_external" or "access_as_shared") (required) + +.. option:: --target-project + + The project to which the RBAC policy will be enforced (name or ID) (required) + +.. option:: --target-project-domain + + Domain the target project belongs to (name or ID). + This can be used in case collisions between project names exist. + +.. option:: --project + + The owner project (name or ID) + +.. option:: --project-domain + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + +.. _network_rbac_create-rbac-policy: +.. describe:: + + The object to which this RBAC policy affects (name or ID for network objects, ID only for QoS policy objects) + +network rbac delete +------------------- + +Delete network RBAC policy(s) + +.. program:: network rbac delete +.. code:: bash + + os network rbac delete + [ ...] + +.. _network_rbac_delete-rbac-policy: +.. describe:: + + RBAC policy(s) to delete (ID only) + network rbac list ----------------- diff --git a/doc/source/specs/command-objects/example.rst b/doc/source/specs/command-objects/example.rst index 6f8b058844..f596978f41 100644 --- a/doc/source/specs/command-objects/example.rst +++ b/doc/source/specs/command-objects/example.rst @@ -38,7 +38,7 @@ Delete example(s) .. describe:: - Example to delete (name or ID) + Example(s) to delete (name or ID) example list ------------ diff --git a/functional/tests/network/v2/test_network_rbac.py b/functional/tests/network/v2/test_network_rbac.py new file mode 100644 index 0000000000..5d8cc1cc49 --- /dev/null +++ b/functional/tests/network/v2/test_network_rbac.py @@ -0,0 +1,55 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import uuid + +from functional.common import test + + +class NetworkRBACTests(test.TestCase): + """Functional tests for network rbac. """ + NET_NAME = uuid.uuid4().hex + OBJECT_ID = None + ID = None + HEADERS = ['ID'] + FIELDS = ['id'] + + @classmethod + def setUpClass(cls): + opts = cls.get_opts(cls.FIELDS) + raw_output = cls.openstack('network create ' + cls.NET_NAME + opts) + cls.OBJECT_ID = raw_output.strip('\n') + opts = cls.get_opts(['id', 'object_id']) + raw_output = cls.openstack('network rbac create ' + + cls.OBJECT_ID + + ' --action access_as_shared' + + ' --target-project admin' + + ' --type network' + opts) + cls.ID, object_id, rol = tuple(raw_output.split('\n')) + cls.assertOutput(cls.OBJECT_ID, object_id) + + @classmethod + def tearDownClass(cls): + raw_output = cls.openstack('network rbac delete ' + cls.ID) + cls.assertOutput('', raw_output) + raw_output = cls.openstack('network delete ' + cls.OBJECT_ID) + cls.assertOutput('', raw_output) + + def test_network_rbac_list(self): + opts = self.get_opts(self.HEADERS) + raw_output = self.openstack('network rbac list' + opts) + self.assertIn(self.ID, raw_output) + + def test_network_rbac_show(self): + opts = self.get_opts(self.FIELDS) + raw_output = self.openstack('network rbac show ' + self.ID + opts) + self.assertEqual(self.ID + "\n", raw_output) diff --git a/openstackclient/network/v2/network_rbac.py b/openstackclient/network/v2/network_rbac.py index 7a759449b6..62968376df 100644 --- a/openstackclient/network/v2/network_rbac.py +++ b/openstackclient/network/v2/network_rbac.py @@ -13,10 +13,17 @@ """RBAC action implementations""" +import logging + from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils from openstackclient.i18n import _ +from openstackclient.identity import common as identity_common + + +LOG = logging.getLogger(__name__) def _get_columns(item): @@ -30,6 +37,131 @@ def _get_columns(item): return tuple(sorted(columns)) +def _get_attrs(client_manager, parsed_args): + attrs = {} + attrs['object_type'] = parsed_args.type + attrs['action'] = parsed_args.action + + network_client = client_manager.network + if parsed_args.type == 'network': + object_id = network_client.find_network( + parsed_args.rbac_object, ignore_missing=False).id + if parsed_args.type == 'qos_policy': + # TODO(Huanxuan Ao): Support finding a object ID by obejct name + # after qos policy finding supported in SDK. + object_id = parsed_args.rbac_object + attrs['object_id'] = object_id + + identity_client = client_manager.identity + project_id = identity_common.find_project( + identity_client, + parsed_args.target_project, + parsed_args.target_project_domain, + ).id + attrs['target_tenant'] = project_id + if parsed_args.project is not None: + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + attrs['tenant_id'] = project_id + + return attrs + + +class CreateNetworkRBAC(command.ShowOne): + """Create network RBAC policy""" + + def get_parser(self, prog_name): + parser = super(CreateNetworkRBAC, self).get_parser(prog_name) + parser.add_argument( + 'rbac_object', + metavar="", + help=_("The object to which this RBAC policy affects (name or " + "ID for network objects, ID only for QoS policy objects)") + ) + parser.add_argument( + '--type', + metavar="", + required=True, + choices=['qos_policy', 'network'], + help=_('Type of the object that RBAC policy ' + 'affects ("qos_policy" or "network")') + ) + parser.add_argument( + '--action', + metavar="", + required=True, + choices=['access_as_external', 'access_as_shared'], + help=_('Action for the RBAC policy ' + '("access_as_external" or "access_as_shared")') + ) + parser.add_argument( + '--target-project', + required=True, + metavar="", + help=_('The project to which the RBAC policy ' + 'will be enforced (name or ID)') + ) + parser.add_argument( + '--target-project-domain', + metavar='', + help=_('Domain the target project belongs to (name or ID). ' + 'This can be used in case collisions between project names ' + 'exist.'), + ) + parser.add_argument( + '--project', + metavar="", + help=_('The owner project (name or ID)') + ) + identity_common.add_project_domain_option_to_parser(parser) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + attrs = _get_attrs(self.app.client_manager, parsed_args) + obj = client.create_rbac_policy(**attrs) + columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns) + return columns, data + + +class DeleteNetworkRBAC(command.Command): + """Delete network RBAC policy(s)""" + + def get_parser(self, prog_name): + parser = super(DeleteNetworkRBAC, self).get_parser(prog_name) + parser.add_argument( + 'rbac_policy', + metavar="", + nargs='+', + help=_("RBAC policy(s) to delete (ID only)") + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + result = 0 + + for rbac in parsed_args.rbac_policy: + try: + obj = client.find_rbac_policy(rbac, ignore_missing=False) + client.delete_rbac_policy(obj) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete RBAC policy with " + "ID '%(rbac)s': %(e)s"), + {'rbac': rbac, 'e': e}) + + if result > 0: + total = len(parsed_args.rbac_policy) + msg = (_("%(result)s of %(total)s RBAC policies failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) + + class ListNetworkRBAC(command.Lister): """List network RBAC policies""" diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 0575209447..9182fe553c 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -541,6 +541,25 @@ def create_network_rbacs(attrs=None, count=2): return rbac_policies + @staticmethod + def get_network_rbacs(rbac_policies=None, count=2): + """Get an iterable MagicMock object with a list of faked rbac policies. + + If rbac policies list is provided, then initialize the Mock object + with the list. Otherwise create one. + + :param List rbac_policies: + A list of FakeResource objects faking rbac policies + :param int count: + The number of rbac policies to fake + :return: + An iterable Mock object with side_effect set to a list of faked + rbac policies + """ + if rbac_policies is None: + rbac_policies = FakeNetworkRBAC.create_network_rbacs(count) + return mock.MagicMock(side_effect=rbac_policies) + class FakeRouter(object): """Fake one or more routers.""" diff --git a/openstackclient/tests/network/v2/test_network_rbac.py b/openstackclient/tests/network/v2/test_network_rbac.py index 057e0adc74..5d6e9cfa0b 100644 --- a/openstackclient/tests/network/v2/test_network_rbac.py +++ b/openstackclient/tests/network/v2/test_network_rbac.py @@ -12,8 +12,12 @@ # import mock +from mock import call + +from osc_lib import exceptions from openstackclient.network.v2 import network_rbac +from openstackclient.tests.identity.v3 import fakes as identity_fakes_v3 from openstackclient.tests.network.v2 import fakes as network_fakes from openstackclient.tests import utils as tests_utils @@ -25,6 +29,252 @@ def setUp(self): # Get a shortcut to the network client self.network = self.app.client_manager.network + # Get a shortcut to the ProjectManager Mock + self.projects_mock = self.app.client_manager.identity.projects + + +class TestCreateNetworkRBAC(TestNetworkRBAC): + + network_object = network_fakes.FakeNetwork.create_one_network() + project = identity_fakes_v3.FakeProject.create_one_project() + rbac_policy = network_fakes.FakeNetworkRBAC.create_one_network_rbac( + attrs={'tenant_id': project.id, + 'target_tenant': project.id, + 'object_id': network_object.id} + ) + + columns = ( + 'action', + 'id', + 'object_id', + 'object_type', + 'project_id', + 'target_project', + ) + + data = [ + rbac_policy.action, + rbac_policy.id, + rbac_policy.object_id, + rbac_policy.object_type, + rbac_policy.tenant_id, + rbac_policy.target_tenant, + ] + + def setUp(self): + super(TestCreateNetworkRBAC, self).setUp() + + # Get the command object to test + self.cmd = network_rbac.CreateNetworkRBAC(self.app, self.namespace) + + self.network.create_rbac_policy = mock.Mock( + return_value=self.rbac_policy) + self.network.find_network = mock.Mock( + return_value=self.network_object) + self.projects_mock.get.return_value = self.project + + def test_network_rbac_create_no_type(self): + arglist = [ + '--action', self.rbac_policy.action, + self.rbac_policy.object_id, + ] + verifylist = [ + ('action', self.rbac_policy.action), + ('rbac_policy', self.rbac_policy.id), + ] + + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_network_rbac_create_no_action(self): + arglist = [ + '--type', self.rbac_policy.object_type, + self.rbac_policy.object_id, + ] + verifylist = [ + ('type', self.rbac_policy.object_type), + ('rbac_policy', self.rbac_policy.id), + ] + + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_network_rbac_create_invalid_type(self): + arglist = [ + '--action', self.rbac_policy.action, + '--type', 'invalid_type', + '--target-project', self.rbac_policy.target_tenant, + self.rbac_policy.object_id, + ] + verifylist = [ + ('action', self.rbac_policy.action), + ('type', 'invalid_type'), + ('target-project', self.rbac_policy.target_tenant), + ('rbac_policy', self.rbac_policy.id), + ] + + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_network_rbac_create_invalid_action(self): + arglist = [ + '--type', self.rbac_policy.object_type, + '--action', 'invalid_action', + '--target-project', self.rbac_policy.target_tenant, + self.rbac_policy.object_id, + ] + verifylist = [ + ('type', self.rbac_policy.object_type), + ('action', 'invalid_action'), + ('target-project', self.rbac_policy.target_tenant), + ('rbac_policy', self.rbac_policy.id), + ] + + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_network_rbac_create(self): + arglist = [ + '--type', self.rbac_policy.object_type, + '--action', self.rbac_policy.action, + '--target-project', self.rbac_policy.target_tenant, + self.rbac_policy.object_id, + ] + verifylist = [ + ('type', self.rbac_policy.object_type), + ('action', self.rbac_policy.action), + ('target_project', self.rbac_policy.target_tenant), + ('rbac_object', self.rbac_policy.object_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_rbac_policy.assert_called_with(**{ + 'object_id': self.rbac_policy.object_id, + 'object_type': self.rbac_policy.object_type, + 'action': self.rbac_policy.action, + 'target_tenant': self.rbac_policy.target_tenant, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_network_rbac_create_all_options(self): + arglist = [ + '--type', self.rbac_policy.object_type, + '--action', self.rbac_policy.action, + '--target-project', self.rbac_policy.target_tenant, + '--project', self.rbac_policy.tenant_id, + '--project-domain', self.project.domain_id, + '--target-project-domain', self.project.domain_id, + self.rbac_policy.object_id, + ] + verifylist = [ + ('type', self.rbac_policy.object_type), + ('action', self.rbac_policy.action), + ('target_project', self.rbac_policy.target_tenant), + ('project', self.rbac_policy.tenant_id), + ('project_domain', self.project.domain_id), + ('target_project_domain', self.project.domain_id), + ('rbac_object', self.rbac_policy.object_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_rbac_policy.assert_called_with(**{ + 'object_id': self.rbac_policy.object_id, + 'object_type': self.rbac_policy.object_type, + 'action': self.rbac_policy.action, + 'target_tenant': self.rbac_policy.target_tenant, + 'tenant_id': self.rbac_policy.tenant_id, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + +class TestDeleteNetworkRBAC(TestNetworkRBAC): + + rbac_policies = network_fakes.FakeNetworkRBAC.create_network_rbacs(count=2) + + def setUp(self): + super(TestDeleteNetworkRBAC, self).setUp() + self.network.delete_rbac_policy = mock.Mock(return_value=None) + self.network.find_rbac_policy = ( + network_fakes.FakeNetworkRBAC.get_network_rbacs( + rbac_policies=self.rbac_policies) + ) + + # Get the command object to test + self.cmd = network_rbac.DeleteNetworkRBAC(self.app, self.namespace) + + def test_network_rbac_delete(self): + arglist = [ + self.rbac_policies[0].id, + ] + verifylist = [ + ('rbac_policy', [self.rbac_policies[0].id]), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.network.find_rbac_policy.assert_called_once_with( + self.rbac_policies[0].id, ignore_missing=False) + self.network.delete_rbac_policy.assert_called_once_with( + self.rbac_policies[0]) + self.assertIsNone(result) + + def test_multi_network_rbacs_delete(self): + arglist = [] + verifylist = [] + + for r in self.rbac_policies: + arglist.append(r.id) + verifylist = [ + ('rbac_policy', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [] + for r in self.rbac_policies: + calls.append(call(r)) + self.network.delete_rbac_policy.assert_has_calls(calls) + self.assertIsNone(result) + + def test_multi_network_policies_delete_with_exception(self): + arglist = [ + self.rbac_policies[0].id, + 'unexist_rbac_policy', + ] + verifylist = [ + ('rbac_policy', + [self.rbac_policies[0].id, 'unexist_rbac_policy']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self.rbac_policies[0], exceptions.CommandError] + self.network.find_rbac_policy = ( + mock.MagicMock(side_effect=find_mock_result) + ) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 RBAC policies failed to delete.', str(e)) + + self.network.find_rbac_policy.assert_any_call( + self.rbac_policies[0].id, ignore_missing=False) + self.network.find_rbac_policy.assert_any_call( + 'unexist_rbac_policy', ignore_missing=False) + self.network.delete_rbac_policy.assert_called_once_with( + self.rbac_policies[0] + ) class TestListNetworkRABC(TestNetworkRBAC): @@ -107,16 +357,18 @@ def test_show_no_options(self): def test_network_rbac_show_all_options(self): arglist = [ - self.rbac_policy.object_id, + self.rbac_policy.id, + ] + verifylist = [ + ('rbac_policy', self.rbac_policy.id), ] - verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) self.network.find_rbac_policy.assert_called_with( - self.rbac_policy.object_id, ignore_missing=False + self.rbac_policy.id, ignore_missing=False ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) diff --git a/releasenotes/notes/bp-neutron-client-rbac-bbd7b545b50d2bdf.yaml b/releasenotes/notes/bp-neutron-client-rbac-bbd7b545b50d2bdf.yaml index ad5c02bad3..7a00e587a0 100644 --- a/releasenotes/notes/bp-neutron-client-rbac-bbd7b545b50d2bdf.yaml +++ b/releasenotes/notes/bp-neutron-client-rbac-bbd7b545b50d2bdf.yaml @@ -1,4 +1,5 @@ --- features: - - Add ``network rbac list`` and ``network rbac show`` commands. + - Add ``network rbac list``, ``network rbac show``, ``network rbac create`` + and ``network rbac delete`` commands. [Blueprint `neutron-client-rbac `_] diff --git a/setup.cfg b/setup.cfg index f1abceb93d..c0bb221936 100644 --- a/setup.cfg +++ b/setup.cfg @@ -361,6 +361,8 @@ openstack.network.v2 = network_set = openstackclient.network.v2.network:SetNetwork network_show = openstackclient.network.v2.network:ShowNetwork + network_rbac_create = openstackclient.network.v2.network_rbac:CreateNetworkRBAC + network_rbac_delete = openstackclient.network.v2.network_rbac:DeleteNetworkRBAC network_rbac_list = openstackclient.network.v2.network_rbac:ListNetworkRBAC network_rbac_show = openstackclient.network.v2.network_rbac:ShowNetworkRBAC From e26eecc12f2dc659995fc0ef00b4ef12c714c46e Mon Sep 17 00:00:00 2001 From: Rajasi Kulkarni Date: Thu, 28 Jul 2016 21:39:02 +0530 Subject: [PATCH 1139/3095] Pass security group id to novaclient while adding security group to server In AddServerSecurityGroup, we currently pass the security group name to novaclient. If multiple security groups with same name exist, then even while passing secuity group using id to command 'openstack server add security group ' it results in error 'Multiple security_group matches found'. Added unit test case to test the command. Change-Id: I6097eb36e1545c85209cfd767c477e10f82c6999 Closes-Bug: 1604076 --- openstackclient/compute/v2/server.py | 2 +- .../tests/compute/v2/test_server.py | 48 +++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 16c86bd961..3e6903b701 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -260,7 +260,7 @@ def take_action(self, parsed_args): parsed_args.group, ) - server.add_security_group(security_group.name) + server.add_security_group(security_group.id) class AddServerVolume(command.Command): diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index e487d57c5b..a98398eed5 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -168,6 +168,54 @@ def test_server_add_floating_ip(self): self.assertIsNone(result) +class TestServerAddSecurityGroup(TestServer): + + def setUp(self): + super(TestServerAddSecurityGroup, self).setUp() + + self.security_group = \ + compute_fakes.FakeSecurityGroup.create_one_security_group() + # This is the return value for utils.find_resource() for security group + self.security_groups_mock.get.return_value = self.security_group + + attrs = { + 'security_groups': [{'name': self.security_group.id}] + } + methods = { + 'add_security_group': None, + } + + self.server = compute_fakes.FakeServer.create_one_server( + attrs=attrs, + methods=methods + ) + # This is the return value for utils.find_resource() for server + self.servers_mock.get.return_value = self.server + + # Get the command object to test + self.cmd = server.AddServerSecurityGroup(self.app, None) + + def test_server_add_security_group(self): + arglist = [ + self.server.id, + self.security_group.id + ] + verifylist = [ + ('server', self.server.id), + ('group', self.security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.security_groups_mock.get.assert_called_with( + self.security_group.id, + ) + self.servers_mock.get.assert_called_with(self.server.id) + self.server.add_security_group.assert_called_with( + self.security_group.id, + ) + self.assertIsNone(result) + + class TestServerCreate(TestServer): columns = ( From 62874a5307bbf3110c6dd37b4ccaf0f5b0551871 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Fri, 29 Jul 2016 02:34:59 +0000 Subject: [PATCH 1140/3095] Updated from global requirements Change-Id: If225aca4a8b366b6657f46bc9478a88f9e9b1964 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 0847d4eb9f..437884683a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ os-client-config>=1.13.1 # Apache-2.0 osc-lib>=0.4.0 # Apache-2.0 oslo.config>=3.12.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 -oslo.utils>=3.15.0 # Apache-2.0 +oslo.utils>=3.16.0 # Apache-2.0 python-glanceclient>=2.0.0 # Apache-2.0 python-keystoneclient!=1.8.0,!=2.1.0,>=1.7.0 # Apache-2.0 python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0 From 62c187df7a1e69493771c6a88d065f1f8e7fc372 Mon Sep 17 00:00:00 2001 From: qtang Date: Fri, 29 Jul 2016 11:15:31 +0800 Subject: [PATCH 1141/3095] Update the description of project in releasenotes. Change-Id: I1fe0e52d2e049d34666a6d94bac1d85b91a78b19 --- releasenotes/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py index 12ed68df29..d765e339b2 100644 --- a/releasenotes/source/conf.py +++ b/releasenotes/source/conf.py @@ -285,7 +285,7 @@ u'OpenStackclient Release Notes Documentation', u'OpenStackclient Developers', 'OpenStackClientReleaseNotes', - 'One line description of project.', + 'A unified command-line client for OpenStack.', 'Miscellaneous', )] From 82baba0c82f26c7cdd91bc38c8c516e3f4641a6c Mon Sep 17 00:00:00 2001 From: Abhishek Raut Date: Thu, 28 Jul 2016 17:45:18 -0700 Subject: [PATCH 1142/3095] Document network trunk commands Commands were added for network trunk object as part of change I6fe1dbd81813fae234801a61c0e3d89f9e7c791e. This patch adds documentation for the same. Change-Id: If89478bb4b8af08de11ad521669ba2ee91b76f69 Partial-implements: blueprint vlan-aware-vms --- doc/source/commands.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 025f42f905..32727b358c 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -178,6 +178,8 @@ list check out :doc:`plugin-commands`. * ``dataprocessing plugin``: (**Data Processing (Sahara)**) * ``message-broker cluster``: (**Message Broker (Cue)**) * ``message flavor``: (**Messaging (Zaqar)**) +* ``network subport``: (**Networking (Neutron)**) +* ``network trunk``: (**Networking (Neutron)**) * ``orchestration resource``: (**Orchestration (Heat)**) * ``orchestration template``: (**Orchestration (Heat)**) * ``pool``: (**Messaging (Zaqar)**) From 19bea1ca91166a2c7588b1ce7963fe73e8b6c406 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sat, 30 Jul 2016 01:24:17 +0000 Subject: [PATCH 1143/3095] Updated from global requirements Change-Id: Ie90dc3937e1991202a9af7aa38c5e510947cb219 --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 5560da434c..6a98478f09 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -29,7 +29,7 @@ python-ironic-inspector-client>=1.5.0 # Apache-2.0 python-mistralclient>=2.0.0 # Apache-2.0 python-muranoclient>=0.8.2 # Apache-2.0 python-neutronclient>=4.2.0 # Apache-2.0 -python-saharaclient>=0.13.0 # Apache-2.0 +python-saharaclient>=0.16.0 # Apache-2.0 python-searchlightclient>=0.2.0 #Apache-2.0 python-senlinclient>=0.3.0 # Apache-2.0 python-zaqarclient>=1.0.0 # Apache-2.0 From 27a55fd79663e20bb6139066be3d7734ec1acc62 Mon Sep 17 00:00:00 2001 From: Ghe Rivero Date: Wed, 15 Jun 2016 19:51:01 +0200 Subject: [PATCH 1144/3095] OS_DEFAULT_DOMAIN must be an ID Fix the documentation to indicate that the value of OS_DEFAULT_DOMAIN must be the domain ID. A domain name is not valid here Change-Id: Id6d42aa10ef346fa5124ef841c5ce408e34d6424 --- doc/source/authentication.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/authentication.rst b/doc/source/authentication.rst index a3986ee414..be16bd78ba 100644 --- a/doc/source/authentication.rst +++ b/doc/source/authentication.rst @@ -130,7 +130,7 @@ If using a domain as authorization scope, set either it's name or ID. * ``--os-domain-id`` or ``OS_DOMAIN_ID`` Note that if the user and project share the same domain, then simply setting -``os-default-domain`` or ``OS_DEFAULT_DOMAIN`` is sufficient. +``--os-default-domain`` or ``OS_DEFAULT_DOMAIN`` to the domain ID is sufficient. Thus, a minimal set of environment variables would be: From 1d2c9a79dfd5e6d8a348efd49877091effb4b5b0 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Mon, 1 Aug 2016 18:47:35 +0000 Subject: [PATCH 1145/3095] Updated from global requirements Change-Id: Iccc0deccc66dd82b101ca4fdd099cc55325ea516 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 437884683a..7cbebb88d0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ six>=1.9.0 # MIT Babel>=2.3.4 # BSD cliff!=1.16.0,!=1.17.0,>=1.15.0 # Apache-2.0 -keystoneauth1>=2.7.0 # Apache-2.0 +keystoneauth1>=2.10.0 # Apache-2.0 openstacksdk>=0.9.0 # Apache-2.0 os-client-config>=1.13.1 # Apache-2.0 osc-lib>=0.4.0 # Apache-2.0 From 5125b6f73b2345b89addfe16f336ed7f89b1284b Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Tue, 26 Jul 2016 18:05:36 +0800 Subject: [PATCH 1146/3095] Refactor identity v3 unit tests with fake class Refactor unit tests in identity v3 for "user", "endpoint", "group" and "service" with fake classes. Change-Id: I57316bbf762c805f8e9ae225b394bbe58ebdd416 Partially-Implements: blueprint refactor-identity-unit-test --- openstackclient/tests/identity/v3/fakes.py | 124 ++++- .../tests/identity/v3/test_endpoint.py | 424 ++++++++---------- .../tests/identity/v3/test_group.py | 57 +-- .../tests/identity/v3/test_service.py | 184 ++++---- .../tests/identity/v3/test_user.py | 392 +++++++--------- 5 files changed, 572 insertions(+), 609 deletions(-) diff --git a/openstackclient/tests/identity/v3/fakes.py b/openstackclient/tests/identity/v3/fakes.py index bad15b6cde..50e901c983 100644 --- a/openstackclient/tests/identity/v3/fakes.py +++ b/openstackclient/tests/identity/v3/fakes.py @@ -730,7 +730,7 @@ def create_one_user(attrs=None): 'default_project_id': 'project-' + uuid.uuid4().hex, 'email': 'user-email-' + uuid.uuid4().hex, 'enabled': True, - 'domain_id': 'domain-id' + uuid.uuid4().hex, + 'domain_id': 'domain-id-' + uuid.uuid4().hex, 'links': 'links-' + uuid.uuid4().hex, } user_info.update(attrs) @@ -738,3 +738,125 @@ def create_one_user(attrs=None): user = fakes.FakeResource(info=copy.deepcopy(user_info), loaded=True) return user + + +class FakeGroup(object): + """Fake one or more group.""" + + @staticmethod + def create_one_group(attrs=None): + """Create a fake group. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object, with id, name, and so on + """ + + attrs = attrs or {} + + # set default attributes. + group_info = { + 'id': 'group-id-' + uuid.uuid4().hex, + 'name': 'group-name-' + uuid.uuid4().hex, + 'links': 'links-' + uuid.uuid4().hex, + 'domain_id': 'domain-id-' + uuid.uuid4().hex, + 'description': 'group-description-' + uuid.uuid4().hex, + } + group_info.update(attrs) + + group = fakes.FakeResource(info=copy.deepcopy(group_info), + loaded=True) + return group + + +class FakeEndpoint(object): + """Fake one or more endpoint.""" + + @staticmethod + def create_one_endpoint(attrs=None): + """Create a fake endpoint. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object, with id, url, and so on + """ + + attrs = attrs or {} + + # set default attributes. + endpoint_info = { + 'id': 'endpoint-id-' + uuid.uuid4().hex, + 'url': 'url-' + uuid.uuid4().hex, + 'region': 'endpoint-region-' + uuid.uuid4().hex, + 'interface': 'admin', + 'service_id': 'service-id-' + uuid.uuid4().hex, + 'enabled': True, + 'links': 'links-' + uuid.uuid4().hex, + } + endpoint_info.update(attrs) + + endpoint = fakes.FakeResource(info=copy.deepcopy(endpoint_info), + loaded=True) + return endpoint + + +class FakeService(object): + """Fake one or more service.""" + + @staticmethod + def create_one_service(attrs=None): + """Create a fake service. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object, with id, name, and so on + """ + + attrs = attrs or {} + + # set default attributes. + service_info = { + 'id': 'service-id-' + uuid.uuid4().hex, + 'name': 'service-name-' + uuid.uuid4().hex, + 'type': 'service-type-' + uuid.uuid4().hex, + 'description': 'service-description-' + uuid.uuid4().hex, + 'enabled': True, + 'links': 'links-' + uuid.uuid4().hex, + } + service_info.update(attrs) + + service = fakes.FakeResource(info=copy.deepcopy(service_info), + loaded=True) + return service + + +class FakeRoleAssignment(object): + """Fake one or more role assignment.""" + + @staticmethod + def create_one_role_assignment(attrs=None): + """Create a fake role assignment. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object, with scope, user, and so on + """ + + attrs = attrs or {} + + # set default attributes. + role_assignment_info = { + 'scope': {'project': {'id': 'project-id-' + uuid.uuid4().hex}}, + 'user': {'id': 'user-id-' + uuid.uuid4().hex}, + 'role': {'id': 'role-id-' + uuid.uuid4().hex}, + } + role_assignment_info.update(attrs) + + role_assignment = fakes.FakeResource( + info=copy.deepcopy(role_assignment_info), loaded=True) + + return role_assignment diff --git a/openstackclient/tests/identity/v3/test_endpoint.py b/openstackclient/tests/identity/v3/test_endpoint.py index 042763199e..b2463a0d5e 100644 --- a/openstackclient/tests/identity/v3/test_endpoint.py +++ b/openstackclient/tests/identity/v3/test_endpoint.py @@ -10,11 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. - -import copy - from openstackclient.identity.v3 import endpoint -from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes @@ -31,12 +27,11 @@ def setUp(self): self.services_mock = self.app.client_manager.identity.services self.services_mock.reset_mock() - def get_fake_service_name(self): - return identity_fakes.service_name - class TestEndpointCreate(TestEndpoint): + service = identity_fakes.FakeService.create_one_service() + columns = ( 'enabled', 'id', @@ -51,33 +46,27 @@ class TestEndpointCreate(TestEndpoint): def setUp(self): super(TestEndpointCreate, self).setUp() - self.endpoints_mock.create.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.ENDPOINT), - loaded=True, - ) + self.endpoint = identity_fakes.FakeEndpoint.create_one_endpoint( + attrs={'service_id': self.service.id}) + self.endpoints_mock.create.return_value = self.endpoint # This is the return value for common.find_resource(service) - self.services_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.SERVICE), - loaded=True, - ) + self.services_mock.get.return_value = self.service # Get the command object to test self.cmd = endpoint.CreateEndpoint(self.app, None) def test_endpoint_create_no_options(self): arglist = [ - identity_fakes.service_id, - identity_fakes.endpoint_interface, - identity_fakes.endpoint_url, + self.service.id, + self.endpoint.interface, + self.endpoint.url, ] verifylist = [ ('enabled', True), - ('service', identity_fakes.service_id), - ('interface', identity_fakes.endpoint_interface), - ('url', identity_fakes.endpoint_url), + ('service', self.service.id), + ('interface', self.endpoint.interface), + ('url', self.endpoint.url), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -88,9 +77,9 @@ def test_endpoint_create_no_options(self): # Set expected values kwargs = { - 'service': identity_fakes.service_id, - 'url': identity_fakes.endpoint_url, - 'interface': identity_fakes.endpoint_interface, + 'service': self.service.id, + 'url': self.endpoint.url, + 'interface': self.endpoint.interface, 'enabled': True, 'region': None, } @@ -102,29 +91,29 @@ def test_endpoint_create_no_options(self): self.assertEqual(self.columns, columns) datalist = ( True, - identity_fakes.endpoint_id, - identity_fakes.endpoint_interface, - identity_fakes.endpoint_region, - identity_fakes.service_id, - self.get_fake_service_name(), - identity_fakes.service_type, - identity_fakes.endpoint_url, + self.endpoint.id, + self.endpoint.interface, + self.endpoint.region, + self.service.id, + self.service.name, + self.service.type, + self.endpoint.url, ) self.assertEqual(datalist, data) def test_endpoint_create_region(self): arglist = [ - identity_fakes.service_id, - identity_fakes.endpoint_interface, - identity_fakes.endpoint_url, - '--region', identity_fakes.endpoint_region, + self.service.id, + self.endpoint.interface, + self.endpoint.url, + '--region', self.endpoint.region, ] verifylist = [ ('enabled', True), - ('service', identity_fakes.service_id), - ('interface', identity_fakes.endpoint_interface), - ('url', identity_fakes.endpoint_url), - ('region', identity_fakes.endpoint_region), + ('service', self.service.id), + ('interface', self.endpoint.interface), + ('url', self.endpoint.url), + ('region', self.endpoint.region), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -135,11 +124,11 @@ def test_endpoint_create_region(self): # Set expected values kwargs = { - 'service': identity_fakes.service_id, - 'url': identity_fakes.endpoint_url, - 'interface': identity_fakes.endpoint_interface, + 'service': self.service.id, + 'url': self.endpoint.url, + 'interface': self.endpoint.interface, 'enabled': True, - 'region': identity_fakes.endpoint_region, + 'region': self.endpoint.region, } self.endpoints_mock.create.assert_called_with( @@ -149,28 +138,28 @@ def test_endpoint_create_region(self): self.assertEqual(self.columns, columns) datalist = ( True, - identity_fakes.endpoint_id, - identity_fakes.endpoint_interface, - identity_fakes.endpoint_region, - identity_fakes.service_id, - self.get_fake_service_name(), - identity_fakes.service_type, - identity_fakes.endpoint_url, + self.endpoint.id, + self.endpoint.interface, + self.endpoint.region, + self.service.id, + self.service.name, + self.service.type, + self.endpoint.url, ) self.assertEqual(datalist, data) def test_endpoint_create_enable(self): arglist = [ - identity_fakes.service_id, - identity_fakes.endpoint_interface, - identity_fakes.endpoint_url, + self.service.id, + self.endpoint.interface, + self.endpoint.url, '--enable' ] verifylist = [ ('enabled', True), - ('service', identity_fakes.service_id), - ('interface', identity_fakes.endpoint_interface), - ('url', identity_fakes.endpoint_url), + ('service', self.service.id), + ('interface', self.endpoint.interface), + ('url', self.endpoint.url), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -181,9 +170,9 @@ def test_endpoint_create_enable(self): # Set expected values kwargs = { - 'service': identity_fakes.service_id, - 'url': identity_fakes.endpoint_url, - 'interface': identity_fakes.endpoint_interface, + 'service': self.service.id, + 'url': self.endpoint.url, + 'interface': self.endpoint.interface, 'enabled': True, 'region': None, } @@ -195,28 +184,28 @@ def test_endpoint_create_enable(self): self.assertEqual(self.columns, columns) datalist = ( True, - identity_fakes.endpoint_id, - identity_fakes.endpoint_interface, - identity_fakes.endpoint_region, - identity_fakes.service_id, - self.get_fake_service_name(), - identity_fakes.service_type, - identity_fakes.endpoint_url, + self.endpoint.id, + self.endpoint.interface, + self.endpoint.region, + self.service.id, + self.service.name, + self.service.type, + self.endpoint.url, ) self.assertEqual(datalist, data) def test_endpoint_create_disable(self): arglist = [ - identity_fakes.service_id, - identity_fakes.endpoint_interface, - identity_fakes.endpoint_url, + self.service.id, + self.endpoint.interface, + self.endpoint.url, '--disable', ] verifylist = [ ('enabled', False), - ('service', identity_fakes.service_id), - ('interface', identity_fakes.endpoint_interface), - ('url', identity_fakes.endpoint_url), + ('service', self.service.id), + ('interface', self.endpoint.interface), + ('url', self.endpoint.url), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -227,9 +216,9 @@ def test_endpoint_create_disable(self): # Set expected values kwargs = { - 'service': identity_fakes.service_id, - 'url': identity_fakes.endpoint_url, - 'interface': identity_fakes.endpoint_interface, + 'service': self.service.id, + 'url': self.endpoint.url, + 'interface': self.endpoint.interface, 'enabled': False, 'region': None, } @@ -241,28 +230,26 @@ def test_endpoint_create_disable(self): self.assertEqual(self.columns, columns) datalist = ( True, - identity_fakes.endpoint_id, - identity_fakes.endpoint_interface, - identity_fakes.endpoint_region, - identity_fakes.service_id, - self.get_fake_service_name(), - identity_fakes.service_type, - identity_fakes.endpoint_url, + self.endpoint.id, + self.endpoint.interface, + self.endpoint.region, + self.service.id, + self.service.name, + self.service.type, + self.endpoint.url, ) self.assertEqual(datalist, data) class TestEndpointDelete(TestEndpoint): + endpoint = identity_fakes.FakeEndpoint.create_one_endpoint() + def setUp(self): super(TestEndpointDelete, self).setUp() # This is the return value for utils.find_resource(endpoint) - self.endpoints_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.ENDPOINT), - loaded=True, - ) + self.endpoints_mock.get.return_value = self.endpoint self.endpoints_mock.delete.return_value = None # Get the command object to test @@ -270,23 +257,27 @@ def setUp(self): def test_endpoint_delete(self): arglist = [ - identity_fakes.endpoint_id, + self.endpoint.id, ] verifylist = [ - ('endpoint', [identity_fakes.endpoint_id]), + ('endpoint', [self.endpoint.id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.endpoints_mock.delete.assert_called_with( - identity_fakes.endpoint_id, + self.endpoint.id, ) self.assertIsNone(result) class TestEndpointList(TestEndpoint): + service = identity_fakes.FakeService.create_one_service() + endpoint = identity_fakes.FakeEndpoint.create_one_endpoint( + attrs={'service_id': service.id}) + columns = ( 'ID', 'Region', @@ -300,20 +291,10 @@ class TestEndpointList(TestEndpoint): def setUp(self): super(TestEndpointList, self).setUp() - self.endpoints_mock.list.return_value = [ - fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.ENDPOINT), - loaded=True, - ), - ] + self.endpoints_mock.list.return_value = [self.endpoint] # This is the return value for common.find_resource(service) - self.services_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.SERVICE), - loaded=True, - ) + self.services_mock.get.return_value = self.service # Get the command object to test self.cmd = endpoint.ListEndpoint(self.app, None) @@ -332,23 +313,23 @@ def test_endpoint_list_no_options(self): self.assertEqual(self.columns, columns) datalist = ( ( - identity_fakes.endpoint_id, - identity_fakes.endpoint_region, - self.get_fake_service_name(), - identity_fakes.service_type, + self.endpoint.id, + self.endpoint.region, + self.service.name, + self.service.type, True, - identity_fakes.endpoint_interface, - identity_fakes.endpoint_url, + self.endpoint.interface, + self.endpoint.url, ), ) self.assertEqual(datalist, tuple(data)) def test_endpoint_list_service(self): arglist = [ - '--service', identity_fakes.service_id, + '--service', self.service.id, ] verifylist = [ - ('service', identity_fakes.service_id), + ('service', self.service.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -359,30 +340,30 @@ def test_endpoint_list_service(self): # Set expected values kwargs = { - 'service': identity_fakes.service_id, + 'service': self.service.id, } self.endpoints_mock.list.assert_called_with(**kwargs) self.assertEqual(self.columns, columns) datalist = ( ( - identity_fakes.endpoint_id, - identity_fakes.endpoint_region, - self.get_fake_service_name(), - identity_fakes.service_type, + self.endpoint.id, + self.endpoint.region, + self.service.name, + self.service.type, True, - identity_fakes.endpoint_interface, - identity_fakes.endpoint_url, + self.endpoint.interface, + self.endpoint.url, ), ) self.assertEqual(datalist, tuple(data)) def test_endpoint_list_interface(self): arglist = [ - '--interface', identity_fakes.endpoint_interface, + '--interface', self.endpoint.interface, ] verifylist = [ - ('interface', identity_fakes.endpoint_interface), + ('interface', self.endpoint.interface), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -393,30 +374,30 @@ def test_endpoint_list_interface(self): # Set expected values kwargs = { - 'interface': identity_fakes.endpoint_interface, + 'interface': self.endpoint.interface, } self.endpoints_mock.list.assert_called_with(**kwargs) self.assertEqual(self.columns, columns) datalist = ( ( - identity_fakes.endpoint_id, - identity_fakes.endpoint_region, - self.get_fake_service_name(), - identity_fakes.service_type, + self.endpoint.id, + self.endpoint.region, + self.service.name, + self.service.type, True, - identity_fakes.endpoint_interface, - identity_fakes.endpoint_url, + self.endpoint.interface, + self.endpoint.url, ), ) self.assertEqual(datalist, tuple(data)) def test_endpoint_list_region(self): arglist = [ - '--region', identity_fakes.endpoint_region, + '--region', self.endpoint.region, ] verifylist = [ - ('region', identity_fakes.endpoint_region), + ('region', self.endpoint.region), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -427,20 +408,20 @@ def test_endpoint_list_region(self): # Set expected values kwargs = { - 'region': identity_fakes.endpoint_region, + 'region': self.endpoint.region, } self.endpoints_mock.list.assert_called_with(**kwargs) self.assertEqual(self.columns, columns) datalist = ( ( - identity_fakes.endpoint_id, - identity_fakes.endpoint_region, - self.get_fake_service_name(), - identity_fakes.service_type, + self.endpoint.id, + self.endpoint.region, + self.service.name, + self.service.type, True, - identity_fakes.endpoint_interface, - identity_fakes.endpoint_url, + self.endpoint.interface, + self.endpoint.url, ), ) self.assertEqual(datalist, tuple(data)) @@ -448,38 +429,30 @@ def test_endpoint_list_region(self): class TestEndpointSet(TestEndpoint): + service = identity_fakes.FakeService.create_one_service() + endpoint = identity_fakes.FakeEndpoint.create_one_endpoint( + attrs={'service_id': service.id}) + def setUp(self): super(TestEndpointSet, self).setUp() # This is the return value for utils.find_resource(endpoint) - self.endpoints_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.ENDPOINT), - loaded=True, - ) + self.endpoints_mock.get.return_value = self.endpoint - self.endpoints_mock.update.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.ENDPOINT), - loaded=True, - ) + self.endpoints_mock.update.return_value = self.endpoint # This is the return value for common.find_resource(service) - self.services_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.SERVICE), - loaded=True, - ) + self.services_mock.get.return_value = self.service # Get the command object to test self.cmd = endpoint.SetEndpoint(self.app, None) def test_endpoint_set_no_options(self): arglist = [ - identity_fakes.endpoint_id, + self.endpoint.id, ] verifylist = [ - ('endpoint', identity_fakes.endpoint_id), + ('endpoint', self.endpoint.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -493,7 +466,7 @@ def test_endpoint_set_no_options(self): 'url': None, } self.endpoints_mock.update.assert_called_with( - identity_fakes.endpoint_id, + self.endpoint.id, **kwargs ) self.assertIsNone(result) @@ -501,11 +474,11 @@ def test_endpoint_set_no_options(self): def test_endpoint_set_interface(self): arglist = [ '--interface', 'public', - identity_fakes.endpoint_id + self.endpoint.id ] verifylist = [ ('interface', 'public'), - ('endpoint', identity_fakes.endpoint_id), + ('endpoint', self.endpoint.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -520,7 +493,7 @@ def test_endpoint_set_interface(self): 'service': None, } self.endpoints_mock.update.assert_called_with( - identity_fakes.endpoint_id, + self.endpoint.id, **kwargs ) self.assertIsNone(result) @@ -528,11 +501,11 @@ def test_endpoint_set_interface(self): def test_endpoint_set_url(self): arglist = [ '--url', 'http://localhost:5000', - identity_fakes.endpoint_id + self.endpoint.id ] verifylist = [ ('url', 'http://localhost:5000'), - ('endpoint', identity_fakes.endpoint_id), + ('endpoint', self.endpoint.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -547,19 +520,19 @@ def test_endpoint_set_url(self): 'service': None, } self.endpoints_mock.update.assert_called_with( - identity_fakes.endpoint_id, + self.endpoint.id, **kwargs ) self.assertIsNone(result) def test_endpoint_set_service(self): arglist = [ - '--service', identity_fakes.service_id, - identity_fakes.endpoint_id + '--service', self.service.id, + self.endpoint.id ] verifylist = [ - ('service', identity_fakes.service_id), - ('endpoint', identity_fakes.endpoint_id), + ('service', self.service.id), + ('endpoint', self.endpoint.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -571,10 +544,10 @@ def test_endpoint_set_service(self): 'interface': None, 'url': None, 'region': None, - 'service': identity_fakes.service_id, + 'service': self.service.id, } self.endpoints_mock.update.assert_called_with( - identity_fakes.endpoint_id, + self.endpoint.id, **kwargs ) self.assertIsNone(result) @@ -582,11 +555,11 @@ def test_endpoint_set_service(self): def test_endpoint_set_region(self): arglist = [ '--region', 'e-rzzz', - identity_fakes.endpoint_id + self.endpoint.id ] verifylist = [ ('region', 'e-rzzz'), - ('endpoint', identity_fakes.endpoint_id), + ('endpoint', self.endpoint.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -601,7 +574,7 @@ def test_endpoint_set_region(self): 'service': None, } self.endpoints_mock.update.assert_called_with( - identity_fakes.endpoint_id, + self.endpoint.id, **kwargs ) self.assertIsNone(result) @@ -609,11 +582,11 @@ def test_endpoint_set_region(self): def test_endpoint_set_enable(self): arglist = [ '--enable', - identity_fakes.endpoint_id + self.endpoint.id ] verifylist = [ ('enabled', True), - ('endpoint', identity_fakes.endpoint_id), + ('endpoint', self.endpoint.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -628,7 +601,7 @@ def test_endpoint_set_enable(self): 'service': None, } self.endpoints_mock.update.assert_called_with( - identity_fakes.endpoint_id, + self.endpoint.id, **kwargs ) self.assertIsNone(result) @@ -636,11 +609,11 @@ def test_endpoint_set_enable(self): def test_endpoint_set_disable(self): arglist = [ '--disable', - identity_fakes.endpoint_id + self.endpoint.id ] verifylist = [ ('disabled', True), - ('endpoint', identity_fakes.endpoint_id), + ('endpoint', self.endpoint.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -655,7 +628,7 @@ def test_endpoint_set_disable(self): 'service': None, } self.endpoints_mock.update.assert_called_with( - identity_fakes.endpoint_id, + self.endpoint.id, **kwargs ) self.assertIsNone(result) @@ -663,31 +636,27 @@ def test_endpoint_set_disable(self): class TestEndpointShow(TestEndpoint): + service = identity_fakes.FakeService.create_one_service() + endpoint = identity_fakes.FakeEndpoint.create_one_endpoint( + attrs={'service_id': service.id}) + def setUp(self): super(TestEndpointShow, self).setUp() - self.endpoints_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.ENDPOINT), - loaded=True, - ) + self.endpoints_mock.get.return_value = self.endpoint # This is the return value for common.find_resource(service) - self.services_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.SERVICE), - loaded=True, - ) + self.services_mock.get.return_value = self.service # Get the command object to test self.cmd = endpoint.ShowEndpoint(self.app, None) def test_endpoint_show(self): arglist = [ - identity_fakes.endpoint_id, + self.endpoint.id, ] verifylist = [ - ('endpoint', identity_fakes.endpoint_id), + ('endpoint', self.endpoint.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -696,7 +665,7 @@ def test_endpoint_show(self): # data to be shown. columns, data = self.cmd.take_action(parsed_args) self.endpoints_mock.get.assert_called_with( - identity_fakes.endpoint_id, + self.endpoint.id, ) collist = ( @@ -712,89 +681,70 @@ def test_endpoint_show(self): self.assertEqual(collist, columns) datalist = ( True, - identity_fakes.endpoint_id, - identity_fakes.endpoint_interface, - identity_fakes.endpoint_region, - identity_fakes.service_id, - self.get_fake_service_name(), - identity_fakes.service_type, - identity_fakes.endpoint_url, + self.endpoint.id, + self.endpoint.interface, + self.endpoint.region, + self.service.id, + self.service.name, + self.service.type, + self.endpoint.url, ) self.assertEqual(datalist, data) class TestEndpointCreateServiceWithoutName(TestEndpointCreate): + service = identity_fakes.FakeService.create_one_service( + attrs={'service_name': ''}) + def setUp(self): super(TestEndpointCreate, self).setUp() - self.endpoints_mock.create.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.ENDPOINT), - loaded=True, - ) + self.endpoint = identity_fakes.FakeEndpoint.create_one_endpoint( + attrs={'service_id': self.service.id}) + + self.endpoints_mock.create.return_value = self.endpoint # This is the return value for common.find_resource(service) - self.services_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.SERVICE_WITHOUT_NAME), - loaded=True, - ) + self.services_mock.get.return_value = self.service # Get the command object to test self.cmd = endpoint.CreateEndpoint(self.app, None) - def get_fake_service_name(self): - return '' - class TestEndpointListServiceWithoutName(TestEndpointList): + service = identity_fakes.FakeService.create_one_service( + attrs={'service_name': ''}) + endpoint = identity_fakes.FakeEndpoint.create_one_endpoint( + attrs={'service_id': service.id}) + def setUp(self): super(TestEndpointList, self).setUp() - self.endpoints_mock.list.return_value = [ - fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.ENDPOINT), - loaded=True, - ), - ] + self.endpoints_mock.list.return_value = [self.endpoint] # This is the return value for common.find_resource(service) - self.services_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.SERVICE_WITHOUT_NAME), - loaded=True, - ) + self.services_mock.get.return_value = self.service # Get the command object to test self.cmd = endpoint.ListEndpoint(self.app, None) - def get_fake_service_name(self): - return '' - class TestEndpointShowServiceWithoutName(TestEndpointShow): + service = identity_fakes.FakeService.create_one_service( + attrs={'service_name': ''}) + endpoint = identity_fakes.FakeEndpoint.create_one_endpoint( + attrs={'service_id': service.id}) + def setUp(self): super(TestEndpointShow, self).setUp() - self.endpoints_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.ENDPOINT), - loaded=True, - ) + self.endpoints_mock.get.return_value = self.endpoint # This is the return value for common.find_resource(service) - self.services_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.SERVICE_WITHOUT_NAME), - loaded=True, - ) + self.services_mock.get.return_value = self.service # Get the command object to test self.cmd = endpoint.ShowEndpoint(self.app, None) - - def get_fake_service_name(self): - return '' diff --git a/openstackclient/tests/identity/v3/test_group.py b/openstackclient/tests/identity/v3/test_group.py index c5f5dbca7b..112008d35e 100644 --- a/openstackclient/tests/identity/v3/test_group.py +++ b/openstackclient/tests/identity/v3/test_group.py @@ -11,10 +11,7 @@ # under the License. # -import copy - from openstackclient.identity.v3 import group -from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes @@ -38,44 +35,30 @@ def setUp(self): class TestGroupList(TestGroup): + domain = identity_fakes.FakeDomain.create_one_domain() + group = identity_fakes.FakeGroup.create_one_group() + user = identity_fakes.FakeUser.create_one_user() + columns = ( 'ID', 'Name', ) datalist = ( ( - identity_fakes.group_id, - identity_fakes.group_name, + group.id, + group.name, ), ) def setUp(self): super(TestGroupList, self).setUp() - self.groups_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.GROUP), - loaded=True, - ) - self.groups_mock.list.return_value = [ - fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.GROUP), - loaded=True, - ), - ] + self.groups_mock.get.return_value = self.group + self.groups_mock.list.return_value = [self.group] - self.domains_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.DOMAIN), - loaded=True, - ) + self.domains_mock.get.return_value = self.domain - self.users_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.USER), - loaded=True, - ) + self.users_mock.get.return_value = self.user # Get the command object to test self.cmd = group.ListGroup(self.app, None) @@ -105,10 +88,10 @@ def test_group_list_no_options(self): def test_group_list_domain(self): arglist = [ - '--domain', identity_fakes.domain_id, + '--domain', self.domain.id, ] verifylist = [ - ('domain', identity_fakes.domain_id), + ('domain', self.domain.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -119,7 +102,7 @@ def test_group_list_domain(self): # Set expected values kwargs = { - 'domain': identity_fakes.domain_id, + 'domain': self.domain.id, 'user': None, } @@ -132,10 +115,10 @@ def test_group_list_domain(self): def test_group_list_user(self): arglist = [ - '--user', identity_fakes.user_name, + '--user', self.user.name, ] verifylist = [ - ('user', identity_fakes.user_name), + ('user', self.user.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -147,7 +130,7 @@ def test_group_list_user(self): # Set expected values kwargs = { 'domain': None, - 'user': identity_fakes.user_id, + 'user': self.user.id, } self.groups_mock.list.assert_called_with( @@ -186,10 +169,10 @@ def test_group_list_long(self): 'Description', ) datalist = (( - identity_fakes.group_id, - identity_fakes.group_name, - '', - '', + self.group.id, + self.group.name, + self.group.domain_id, + self.group.description, ), ) self.assertEqual(columns, columns) self.assertEqual(datalist, tuple(data)) diff --git a/openstackclient/tests/identity/v3/test_service.py b/openstackclient/tests/identity/v3/test_service.py index 65d8ebd7de..76c66aa5e1 100644 --- a/openstackclient/tests/identity/v3/test_service.py +++ b/openstackclient/tests/identity/v3/test_service.py @@ -13,13 +13,10 @@ # under the License. # -import copy - from keystoneclient import exceptions as identity_exc from osc_lib import exceptions from openstackclient.identity.v3 import service -from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes @@ -42,37 +39,34 @@ class TestServiceCreate(TestService): 'name', 'type', ) - datalist = ( - identity_fakes.service_description, - True, - identity_fakes.service_id, - identity_fakes.service_name, - identity_fakes.service_type, - ) def setUp(self): super(TestServiceCreate, self).setUp() - self.services_mock.create.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.SERVICE), - loaded=True, + self.service = identity_fakes.FakeService.create_one_service() + self.datalist = ( + self.service.description, + True, + self.service.id, + self.service.name, + self.service.type, ) + self.services_mock.create.return_value = self.service # Get the command object to test self.cmd = service.CreateService(self.app, None) def test_service_create_name(self): arglist = [ - '--name', identity_fakes.service_name, - identity_fakes.service_type, + '--name', self.service.name, + self.service.type, ] verifylist = [ - ('name', identity_fakes.service_name), + ('name', self.service.name), ('description', None), ('enable', False), ('disable', False), - ('type', identity_fakes.service_type), + ('type', self.service.type), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -83,8 +77,8 @@ def test_service_create_name(self): # ServiceManager.create(name=, type=, enabled=, **kwargs) self.services_mock.create.assert_called_with( - name=identity_fakes.service_name, - type=identity_fakes.service_type, + name=self.service.name, + type=self.service.type, description=None, enabled=True, ) @@ -94,15 +88,15 @@ def test_service_create_name(self): def test_service_create_description(self): arglist = [ - '--description', identity_fakes.service_description, - identity_fakes.service_type, + '--description', self.service.description, + self.service.type, ] verifylist = [ ('name', None), - ('description', identity_fakes.service_description), + ('description', self.service.description), ('enable', False), ('disable', False), - ('type', identity_fakes.service_type), + ('type', self.service.type), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -114,8 +108,8 @@ def test_service_create_description(self): # ServiceManager.create(name=, type=, enabled=, **kwargs) self.services_mock.create.assert_called_with( name=None, - type=identity_fakes.service_type, - description=identity_fakes.service_description, + type=self.service.type, + description=self.service.description, enabled=True, ) @@ -125,14 +119,14 @@ def test_service_create_description(self): def test_service_create_enable(self): arglist = [ '--enable', - identity_fakes.service_type, + self.service.type, ] verifylist = [ ('name', None), ('description', None), ('enable', True), ('disable', False), - ('type', identity_fakes.service_type), + ('type', self.service.type), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -144,7 +138,7 @@ def test_service_create_enable(self): # ServiceManager.create(name=, type=, enabled=, **kwargs) self.services_mock.create.assert_called_with( name=None, - type=identity_fakes.service_type, + type=self.service.type, description=None, enabled=True, ) @@ -155,14 +149,14 @@ def test_service_create_enable(self): def test_service_create_disable(self): arglist = [ '--disable', - identity_fakes.service_type, + self.service.type, ] verifylist = [ ('name', None), ('description', None), ('enable', False), ('disable', True), - ('type', identity_fakes.service_type), + ('type', self.service.type), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -174,7 +168,7 @@ def test_service_create_disable(self): # ServiceManager.create(name=, type=, enabled=, **kwargs) self.services_mock.create.assert_called_with( name=None, - type=identity_fakes.service_type, + type=self.service.type, description=None, enabled=False, ) @@ -185,15 +179,13 @@ def test_service_create_disable(self): class TestServiceDelete(TestService): + service = identity_fakes.FakeService.create_one_service() + def setUp(self): super(TestServiceDelete, self).setUp() self.services_mock.get.side_effect = identity_exc.NotFound(None) - self.services_mock.find.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.SERVICE), - loaded=True, - ) + self.services_mock.find.return_value = self.service self.services_mock.delete.return_value = None # Get the command object to test @@ -201,33 +193,29 @@ def setUp(self): def test_service_delete_no_options(self): arglist = [ - identity_fakes.service_name, + self.service.name, ] verifylist = [ - ('service', [identity_fakes.service_name]), + ('service', [self.service.name]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.services_mock.delete.assert_called_with( - identity_fakes.service_id, + self.service.id, ) self.assertIsNone(result) class TestServiceList(TestService): + service = identity_fakes.FakeService.create_one_service() + def setUp(self): super(TestServiceList, self).setUp() - self.services_mock.list.return_value = [ - fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.SERVICE), - loaded=True, - ), - ] + self.services_mock.list.return_value = [self.service] # Get the command object to test self.cmd = service.ListService(self.app, None) @@ -247,9 +235,9 @@ def test_service_list_no_options(self): collist = ('ID', 'Name', 'Type') self.assertEqual(collist, columns) datalist = (( - identity_fakes.service_id, - identity_fakes.service_name, - identity_fakes.service_type, + self.service.id, + self.service.name, + self.service.type, ), ) self.assertEqual(datalist, tuple(data)) @@ -272,10 +260,10 @@ def test_service_list_long(self): collist = ('ID', 'Name', 'Type', 'Description', 'Enabled') self.assertEqual(collist, columns) datalist = (( - identity_fakes.service_id, - identity_fakes.service_name, - identity_fakes.service_type, - identity_fakes.service_description, + self.service.id, + self.service.name, + self.service.type, + self.service.description, True, ), ) self.assertEqual(datalist, tuple(data)) @@ -283,27 +271,21 @@ def test_service_list_long(self): class TestServiceSet(TestService): + service = identity_fakes.FakeService.create_one_service() + def setUp(self): super(TestServiceSet, self).setUp() self.services_mock.get.side_effect = identity_exc.NotFound(None) - self.services_mock.find.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.SERVICE), - loaded=True, - ) - self.services_mock.update.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.SERVICE), - loaded=True, - ) + self.services_mock.find.return_value = self.service + self.services_mock.update.return_value = self.service # Get the command object to test self.cmd = service.SetService(self.app, None) def test_service_set_no_options(self): arglist = [ - identity_fakes.service_name, + self.service.name, ] verifylist = [ ('type', None), @@ -311,7 +293,7 @@ def test_service_set_no_options(self): ('description', None), ('enable', False), ('disable', False), - ('service', identity_fakes.service_name), + ('service', self.service.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -321,16 +303,16 @@ def test_service_set_no_options(self): def test_service_set_type(self): arglist = [ - '--type', identity_fakes.service_type, - identity_fakes.service_name, + '--type', self.service.type, + self.service.name, ] verifylist = [ - ('type', identity_fakes.service_type), + ('type', self.service.type), ('name', None), ('description', None), ('enable', False), ('disable', False), - ('service', identity_fakes.service_name), + ('service', self.service.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -338,27 +320,27 @@ def test_service_set_type(self): # Set expected values kwargs = { - 'type': identity_fakes.service_type, + 'type': self.service.type, } # ServiceManager.update(service, name=, type=, enabled=, **kwargs) self.services_mock.update.assert_called_with( - identity_fakes.service_id, + self.service.id, **kwargs ) self.assertIsNone(result) def test_service_set_name(self): arglist = [ - '--name', identity_fakes.service_name, - identity_fakes.service_name, + '--name', self.service.name, + self.service.name, ] verifylist = [ ('type', None), - ('name', identity_fakes.service_name), + ('name', self.service.name), ('description', None), ('enable', False), ('disable', False), - ('service', identity_fakes.service_name), + ('service', self.service.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -366,27 +348,27 @@ def test_service_set_name(self): # Set expected values kwargs = { - 'name': identity_fakes.service_name, + 'name': self.service.name, } # ServiceManager.update(service, name=, type=, enabled=, **kwargs) self.services_mock.update.assert_called_with( - identity_fakes.service_id, + self.service.id, **kwargs ) self.assertIsNone(result) def test_service_set_description(self): arglist = [ - '--description', identity_fakes.service_description, - identity_fakes.service_name, + '--description', self.service.description, + self.service.name, ] verifylist = [ ('type', None), ('name', None), - ('description', identity_fakes.service_description), + ('description', self.service.description), ('enable', False), ('disable', False), - ('service', identity_fakes.service_name), + ('service', self.service.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -394,11 +376,11 @@ def test_service_set_description(self): # Set expected values kwargs = { - 'description': identity_fakes.service_description, + 'description': self.service.description, } # ServiceManager.update(service, name=, type=, enabled=, **kwargs) self.services_mock.update.assert_called_with( - identity_fakes.service_id, + self.service.id, **kwargs ) self.assertIsNone(result) @@ -406,7 +388,7 @@ def test_service_set_description(self): def test_service_set_enable(self): arglist = [ '--enable', - identity_fakes.service_name, + self.service.name, ] verifylist = [ ('type', None), @@ -414,7 +396,7 @@ def test_service_set_enable(self): ('description', None), ('enable', True), ('disable', False), - ('service', identity_fakes.service_name), + ('service', self.service.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -426,7 +408,7 @@ def test_service_set_enable(self): } # ServiceManager.update(service, name=, type=, enabled=, **kwargs) self.services_mock.update.assert_called_with( - identity_fakes.service_id, + self.service.id, **kwargs ) self.assertIsNone(result) @@ -434,7 +416,7 @@ def test_service_set_enable(self): def test_service_set_disable(self): arglist = [ '--disable', - identity_fakes.service_name, + self.service.name, ] verifylist = [ ('type', None), @@ -442,7 +424,7 @@ def test_service_set_disable(self): ('description', None), ('enable', False), ('disable', True), - ('service', identity_fakes.service_name), + ('service', self.service.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -454,7 +436,7 @@ def test_service_set_disable(self): } # ServiceManager.update(service, name=, type=, enabled=, **kwargs) self.services_mock.update.assert_called_with( - identity_fakes.service_id, + self.service.id, **kwargs ) self.assertIsNone(result) @@ -462,25 +444,23 @@ def test_service_set_disable(self): class TestServiceShow(TestService): + service = identity_fakes.FakeService.create_one_service() + def setUp(self): super(TestServiceShow, self).setUp() self.services_mock.get.side_effect = identity_exc.NotFound(None) - self.services_mock.find.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.SERVICE), - loaded=True, - ) + self.services_mock.find.return_value = self.service # Get the command object to test self.cmd = service.ShowService(self.app, None) def test_service_show(self): arglist = [ - identity_fakes.service_name, + self.service.name, ] verifylist = [ - ('service', identity_fakes.service_name), + ('service', self.service.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -491,17 +471,17 @@ def test_service_show(self): # ServiceManager.get(id) self.services_mock.find.assert_called_with( - name=identity_fakes.service_name + name=self.service.name ) collist = ('description', 'enabled', 'id', 'name', 'type') self.assertEqual(collist, columns) datalist = ( - identity_fakes.service_description, + self.service.description, True, - identity_fakes.service_id, - identity_fakes.service_name, - identity_fakes.service_type, + self.service.id, + self.service.name, + self.service.type, ) self.assertEqual(datalist, data) diff --git a/openstackclient/tests/identity/v3/test_user.py b/openstackclient/tests/identity/v3/test_user.py index 7b99a4cbbc..c3d9e749df 100644 --- a/openstackclient/tests/identity/v3/test_user.py +++ b/openstackclient/tests/identity/v3/test_user.py @@ -14,11 +14,9 @@ # import contextlib -import copy import mock from openstackclient.identity.v3 import user -from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes @@ -51,6 +49,9 @@ def setUp(self): class TestUserCreate(TestUser): + domain = identity_fakes.FakeDomain.create_one_domain() + project = identity_fakes.FakeProject.create_one_project() + columns = ( 'default_project_id', 'domain_id', @@ -59,47 +60,38 @@ class TestUserCreate(TestUser): 'id', 'name', ) - datalist = ( - identity_fakes.project_id, - identity_fakes.domain_id, - identity_fakes.user_email, - True, - identity_fakes.user_id, - identity_fakes.user_name, - ) def setUp(self): super(TestUserCreate, self).setUp() - self.domains_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.DOMAIN), - loaded=True, + self.user = identity_fakes.FakeUser.create_one_user( + attrs={'domain_id': self.domain.id, + 'default_project_id': self.project.id} ) - - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, + self.datalist = ( + self.project.id, + self.domain.id, + self.user.email, + True, + self.user.id, + self.user.name, ) - self.users_mock.create.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.USER), - loaded=True, - ) + self.domains_mock.get.return_value = self.domain + self.projects_mock.get.return_value = self.project + self.users_mock.create.return_value = self.user # Get the command object to test self.cmd = user.CreateUser(self.app, None) def test_user_create_no_options(self): arglist = [ - identity_fakes.user_name, + self.user.name, ] verifylist = [ ('enable', False), ('disable', False), - ('name', identity_fakes.user_name), + ('name', self.user.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -110,7 +102,7 @@ def test_user_create_no_options(self): # Set expected values kwargs = { - 'name': identity_fakes.user_name, + 'name': self.user.name, 'default_project': None, 'description': None, 'domain': None, @@ -131,14 +123,14 @@ def test_user_create_no_options(self): def test_user_create_password(self): arglist = [ '--password', 'secret', - identity_fakes.user_name, + self.user.name, ] verifylist = [ ('password', 'secret'), ('password_prompt', False), ('enable', False), ('disable', False), - ('name', identity_fakes.user_name), + ('name', self.user.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -149,7 +141,7 @@ def test_user_create_password(self): # Set expected values kwargs = { - 'name': identity_fakes.user_name, + 'name': self.user.name, 'default_project': None, 'description': None, 'domain': None, @@ -168,14 +160,14 @@ def test_user_create_password(self): def test_user_create_password_prompt(self): arglist = [ '--password-prompt', - identity_fakes.user_name, + self.user.name, ] verifylist = [ ('password', None), ('password_prompt', True), ('enable', False), ('disable', False), - ('name', identity_fakes.user_name), + ('name', self.user.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -189,7 +181,7 @@ def test_user_create_password_prompt(self): # Set expected values kwargs = { - 'name': identity_fakes.user_name, + 'name': self.user.name, 'default_project': None, 'description': None, 'domain': None, @@ -209,13 +201,13 @@ def test_user_create_password_prompt(self): def test_user_create_email(self): arglist = [ '--email', 'barney@example.com', - identity_fakes.user_name, + self.user.name, ] verifylist = [ ('email', 'barney@example.com'), ('enable', False), ('disable', False), - ('name', identity_fakes.user_name), + ('name', self.user.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -226,7 +218,7 @@ def test_user_create_email(self): # Set expected values kwargs = { - 'name': identity_fakes.user_name, + 'name': self.user.name, 'default_project': None, 'description': None, 'domain': None, @@ -244,30 +236,15 @@ def test_user_create_email(self): self.assertEqual(self.datalist, data) def test_user_create_project(self): - # Return the new project - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT_2), - loaded=True, - ) - # Set up to return an updated user - USER_2 = copy.deepcopy(identity_fakes.USER) - USER_2['default_project_id'] = identity_fakes.PROJECT_2['id'] - self.users_mock.create.return_value = fakes.FakeResource( - None, - USER_2, - loaded=True, - ) - arglist = [ - '--project', identity_fakes.PROJECT_2['name'], - identity_fakes.user_name, + '--project', self.project.name, + self.user.name, ] verifylist = [ - ('project', identity_fakes.PROJECT_2['name']), + ('project', self.project.name), ('enable', False), ('disable', False), - ('name', identity_fakes.user_name), + ('name', self.user.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -278,8 +255,8 @@ def test_user_create_project(self): # Set expected values kwargs = { - 'name': identity_fakes.user_name, - 'default_project': identity_fakes.PROJECT_2['id'], + 'name': self.user.name, + 'default_project': self.project.id, 'description': None, 'domain': None, 'email': None, @@ -294,42 +271,27 @@ def test_user_create_project(self): self.assertEqual(self.columns, columns) datalist = ( - identity_fakes.PROJECT_2['id'], - identity_fakes.domain_id, - identity_fakes.user_email, + self.project.id, + self.domain.id, + self.user.email, True, - identity_fakes.user_id, - identity_fakes.user_name, + self.user.id, + self.user.name, ) self.assertEqual(datalist, data) def test_user_create_project_domain(self): - # Return the new project - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT_2), - loaded=True, - ) - # Set up to return an updated user - USER_2 = copy.deepcopy(identity_fakes.USER) - USER_2['default_project_id'] = identity_fakes.PROJECT_2['id'] - self.users_mock.create.return_value = fakes.FakeResource( - None, - USER_2, - loaded=True, - ) - arglist = [ - '--project', identity_fakes.PROJECT_2['name'], - '--project-domain', identity_fakes.PROJECT_2['domain_id'], - identity_fakes.user_name, + '--project', self.project.name, + '--project-domain', self.project.domain_id, + self.user.name, ] verifylist = [ - ('project', identity_fakes.PROJECT_2['name']), - ('project_domain', identity_fakes.PROJECT_2['domain_id']), + ('project', self.project.name), + ('project_domain', self.project.domain_id), ('enable', False), ('disable', False), - ('name', identity_fakes.user_name), + ('name', self.user.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -340,8 +302,8 @@ def test_user_create_project_domain(self): # Set expected values kwargs = { - 'name': identity_fakes.user_name, - 'default_project': identity_fakes.PROJECT_2['id'], + 'name': self.user.name, + 'default_project': self.project.id, 'description': None, 'domain': None, 'email': None, @@ -356,25 +318,25 @@ def test_user_create_project_domain(self): self.assertEqual(self.columns, columns) datalist = ( - identity_fakes.PROJECT_2['id'], - identity_fakes.domain_id, - identity_fakes.user_email, + self.project.id, + self.domain.id, + self.user.email, True, - identity_fakes.user_id, - identity_fakes.user_name, + self.user.id, + self.user.name, ) self.assertEqual(datalist, data) def test_user_create_domain(self): arglist = [ - '--domain', identity_fakes.domain_name, - identity_fakes.user_name, + '--domain', self.domain.name, + self.user.name, ] verifylist = [ - ('domain', identity_fakes.domain_name), + ('domain', self.domain.name), ('enable', False), ('disable', False), - ('name', identity_fakes.user_name), + ('name', self.user.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -385,10 +347,10 @@ def test_user_create_domain(self): # Set expected values kwargs = { - 'name': identity_fakes.user_name, + 'name': self.user.name, 'default_project': None, 'description': None, - 'domain': identity_fakes.domain_id, + 'domain': self.domain.id, 'email': None, 'enabled': True, 'password': None, @@ -405,12 +367,12 @@ def test_user_create_domain(self): def test_user_create_enable(self): arglist = [ '--enable', - identity_fakes.user_name, + self.user.name, ] verifylist = [ ('enable', True), ('disable', False), - ('name', identity_fakes.user_name), + ('name', self.user.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -421,7 +383,7 @@ def test_user_create_enable(self): # Set expected values kwargs = { - 'name': identity_fakes.user_name, + 'name': self.user.name, 'default_project': None, 'description': None, 'domain': None, @@ -441,10 +403,10 @@ def test_user_create_enable(self): def test_user_create_disable(self): arglist = [ '--disable', - identity_fakes.user_name, + self.user.name, ] verifylist = [ - ('name', identity_fakes.user_name), + ('name', self.user.name), ('enable', False), ('disable', True), ] @@ -457,7 +419,7 @@ def test_user_create_disable(self): # Set expected values kwargs = { - 'name': identity_fakes.user_name, + 'name': self.user.name, 'default_project': None, 'description': None, 'domain': None, @@ -475,15 +437,13 @@ def test_user_create_disable(self): class TestUserDelete(TestUser): + user = identity_fakes.FakeUser.create_one_user() + def setUp(self): super(TestUserDelete, self).setUp() # This is the return value for utils.find_resource() - self.users_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.USER), - loaded=True, - ) + self.users_mock.get.return_value = self.user self.users_mock.delete.return_value = None # Get the command object to test @@ -491,76 +451,54 @@ def setUp(self): def test_user_delete_no_options(self): arglist = [ - identity_fakes.user_id, + self.user.id, ] verifylist = [ - ('users', [identity_fakes.user_id]), + ('users', [self.user.id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.users_mock.delete.assert_called_with( - identity_fakes.user_id, + self.user.id, ) self.assertIsNone(result) class TestUserList(TestUser): + domain = identity_fakes.FakeDomain.create_one_domain() + project = identity_fakes.FakeProject.create_one_project() + user = identity_fakes.FakeUser.create_one_user( + attrs={'domain_id': domain.id, + 'default_project_id': project.id} + ) + group = identity_fakes.FakeGroup.create_one_group() + role_assignment = ( + identity_fakes.FakeRoleAssignment.create_one_role_assignment( + attrs={'user': {'id': user.id}})) + columns = [ 'ID', 'Name' ] datalist = ( ( - identity_fakes.user_id, - identity_fakes.user_name, + user.id, + user.name, ), ) def setUp(self): super(TestUserList, self).setUp() - self.users_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.USER), - loaded=True, - ) - self.users_mock.list.return_value = [ - fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.USER), - loaded=True, - ), - ] - - self.domains_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.DOMAIN), - loaded=True, - ) - - self.groups_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.GROUP), - loaded=True, - ) - - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) - - self.role_assignments_mock.list.return_value = [ - fakes.FakeResource( - None, - copy.deepcopy( - identity_fakes.ASSIGNMENT_WITH_PROJECT_ID_AND_USER_ID), - loaded=True, - ) - ] + self.users_mock.get.return_value = self.user + self.users_mock.list.return_value = [self.user] + self.domains_mock.get.return_value = self.domain + self.groups_mock.get.return_value = self.group + self.projects_mock.get.return_value = self.project + self.role_assignments_mock.list.return_value = [self.role_assignment] # Get the command object to test self.cmd = user.ListUser(self.app, None) @@ -590,10 +528,10 @@ def test_user_list_no_options(self): def test_user_list_domain(self): arglist = [ - '--domain', identity_fakes.domain_id, + '--domain', self.domain.id, ] verifylist = [ - ('domain', identity_fakes.domain_id), + ('domain', self.domain.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -604,7 +542,7 @@ def test_user_list_domain(self): # Set expected values kwargs = { - 'domain': identity_fakes.domain_id, + 'domain': self.domain.id, 'group': None, } @@ -617,10 +555,10 @@ def test_user_list_domain(self): def test_user_list_group(self): arglist = [ - '--group', identity_fakes.group_name, + '--group', self.group.name, ] verifylist = [ - ('group', identity_fakes.group_name), + ('group', self.group.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -632,7 +570,7 @@ def test_user_list_group(self): # Set expected values kwargs = { 'domain': None, - 'group': identity_fakes.group_id, + 'group': self.group.id, } self.users_mock.list.assert_called_with( @@ -678,12 +616,12 @@ def test_user_list_long(self): self.assertEqual(collist, columns) datalist = ( ( - identity_fakes.user_id, - identity_fakes.user_name, - identity_fakes.project_id, - identity_fakes.domain_id, + self.user.id, + self.user.name, + self.project.id, + self.domain.id, '', - identity_fakes.user_email, + self.user.email, True, ), ) @@ -691,10 +629,10 @@ def test_user_list_long(self): def test_user_list_project(self): arglist = [ - '--project', identity_fakes.project_name, + '--project', self.project.name, ] verifylist = [ - ('project', identity_fakes.project_name), + ('project', self.project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -704,11 +642,11 @@ def test_user_list_project(self): columns, data = self.cmd.take_action(parsed_args) kwargs = { - 'project': identity_fakes.project_id, + 'project': self.project.id, } self.role_assignments_mock.list.assert_called_with(**kwargs) - self.users_mock.get.assert_called_with(identity_fakes.user_id) + self.users_mock.get.assert_called_with(self.user.id) self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) @@ -716,32 +654,24 @@ def test_user_list_project(self): class TestUserSet(TestUser): + project = identity_fakes.FakeProject.create_one_project() + user = identity_fakes.FakeUser.create_one_user( + attrs={'default_project_id': project.id} + ) + def setUp(self): super(TestUserSet, self).setUp() - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) - - self.users_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.USER), - loaded=True, - ) - self.users_mock.update.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.USER), - loaded=True, - ) + self.projects_mock.get.return_value = self.project + self.users_mock.get.return_value = self.user + self.users_mock.update.return_value = self.user # Get the command object to test self.cmd = user.SetUser(self.app, None) def test_user_set_no_options(self): arglist = [ - identity_fakes.user_name, + self.user.name, ] verifylist = [ ('name', None), @@ -750,7 +680,7 @@ def test_user_set_no_options(self): ('project', None), ('enable', False), ('disable', False), - ('user', identity_fakes.user_name), + ('user', self.user.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -761,7 +691,7 @@ def test_user_set_no_options(self): def test_user_set_name(self): arglist = [ '--name', 'qwerty', - identity_fakes.user_name, + self.user.name, ] verifylist = [ ('name', 'qwerty'), @@ -770,7 +700,7 @@ def test_user_set_name(self): ('project', None), ('enable', False), ('disable', False), - ('user', identity_fakes.user_name), + ('user', self.user.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -784,7 +714,7 @@ def test_user_set_name(self): # UserManager.update(user, name=, domain=, project=, password=, # email=, description=, enabled=, default_project=) self.users_mock.update.assert_called_with( - identity_fakes.user_id, + self.user.id, **kwargs ) self.assertIsNone(result) @@ -792,7 +722,7 @@ def test_user_set_name(self): def test_user_set_password(self): arglist = [ '--password', 'secret', - identity_fakes.user_name, + self.user.name, ] verifylist = [ ('name', None), @@ -802,7 +732,7 @@ def test_user_set_password(self): ('project', None), ('enable', False), ('disable', False), - ('user', identity_fakes.user_name), + ('user', self.user.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -816,7 +746,7 @@ def test_user_set_password(self): # UserManager.update(user, name=, domain=, project=, password=, # email=, description=, enabled=, default_project=) self.users_mock.update.assert_called_with( - identity_fakes.user_id, + self.user.id, **kwargs ) self.assertIsNone(result) @@ -824,7 +754,7 @@ def test_user_set_password(self): def test_user_set_password_prompt(self): arglist = [ '--password-prompt', - identity_fakes.user_name, + self.user.name, ] verifylist = [ ('name', None), @@ -834,7 +764,7 @@ def test_user_set_password_prompt(self): ('project', None), ('enable', False), ('disable', False), - ('user', identity_fakes.user_name), + ('user', self.user.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -851,7 +781,7 @@ def test_user_set_password_prompt(self): # UserManager.update(user, name=, domain=, project=, password=, # email=, description=, enabled=, default_project=) self.users_mock.update.assert_called_with( - identity_fakes.user_id, + self.user.id, **kwargs ) self.assertIsNone(result) @@ -859,7 +789,7 @@ def test_user_set_password_prompt(self): def test_user_set_email(self): arglist = [ '--email', 'barney@example.com', - identity_fakes.user_name, + self.user.name, ] verifylist = [ ('name', None), @@ -868,7 +798,7 @@ def test_user_set_email(self): ('project', None), ('enable', False), ('disable', False), - ('user', identity_fakes.user_name), + ('user', self.user.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -882,24 +812,24 @@ def test_user_set_email(self): # UserManager.update(user, name=, domain=, project=, password=, # email=, description=, enabled=, default_project=) self.users_mock.update.assert_called_with( - identity_fakes.user_id, + self.user.id, **kwargs ) self.assertIsNone(result) def test_user_set_project(self): arglist = [ - '--project', identity_fakes.project_id, - identity_fakes.user_name, + '--project', self.project.id, + self.user.name, ] verifylist = [ ('name', None), ('password', None), ('email', None), - ('project', identity_fakes.project_id), + ('project', self.project.id), ('enable', False), ('disable', False), - ('user', identity_fakes.user_name), + ('user', self.user.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -908,31 +838,31 @@ def test_user_set_project(self): # Set expected values kwargs = { 'enabled': True, - 'default_project': identity_fakes.project_id, + 'default_project': self.project.id, } # UserManager.update(user, name=, domain=, project=, password=, # email=, description=, enabled=, default_project=) self.users_mock.update.assert_called_with( - identity_fakes.user_id, + self.user.id, **kwargs ) self.assertIsNone(result) def test_user_set_project_domain(self): arglist = [ - '--project', identity_fakes.project_id, - '--project-domain', identity_fakes.domain_id, - identity_fakes.user_name, + '--project', self.project.id, + '--project-domain', self.project.domain_id, + self.user.name, ] verifylist = [ ('name', None), ('password', None), ('email', None), - ('project', identity_fakes.project_id), - ('project_domain', identity_fakes.domain_id), + ('project', self.project.id), + ('project_domain', self.project.domain_id), ('enable', False), ('disable', False), - ('user', identity_fakes.user_name), + ('user', self.user.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -941,12 +871,12 @@ def test_user_set_project_domain(self): # Set expected values kwargs = { 'enabled': True, - 'default_project': identity_fakes.project_id, + 'default_project': self.project.id, } # UserManager.update(user, name=, domain=, project=, password=, # email=, description=, enabled=, default_project=) self.users_mock.update.assert_called_with( - identity_fakes.user_id, + self.user.id, **kwargs ) self.assertIsNone(result) @@ -954,7 +884,7 @@ def test_user_set_project_domain(self): def test_user_set_enable(self): arglist = [ '--enable', - identity_fakes.user_name, + self.user.name, ] verifylist = [ ('name', None), @@ -963,7 +893,7 @@ def test_user_set_enable(self): ('project', None), ('enable', True), ('disable', False), - ('user', identity_fakes.user_name), + ('user', self.user.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -976,7 +906,7 @@ def test_user_set_enable(self): # UserManager.update(user, name=, domain=, project=, password=, # email=, description=, enabled=, default_project=) self.users_mock.update.assert_called_with( - identity_fakes.user_id, + self.user.id, **kwargs ) self.assertIsNone(result) @@ -984,7 +914,7 @@ def test_user_set_enable(self): def test_user_set_disable(self): arglist = [ '--disable', - identity_fakes.user_name, + self.user.name, ] verifylist = [ ('name', None), @@ -993,7 +923,7 @@ def test_user_set_disable(self): ('project', None), ('enable', False), ('disable', True), - ('user', identity_fakes.user_name), + ('user', self.user.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1006,7 +936,7 @@ def test_user_set_disable(self): # UserManager.update(user, name=, domain=, project=, password=, # email=, description=, enabled=, default_project=) self.users_mock.update.assert_called_with( - identity_fakes.user_id, + self.user.id, **kwargs ) self.assertIsNone(result) @@ -1082,35 +1012,33 @@ def test_user_password_change_no_prompt(self): class TestUserShow(TestUser): + user = identity_fakes.FakeUser.create_one_user() + def setUp(self): super(TestUserShow, self).setUp() - self.users_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.USER), - loaded=True, - ) + self.users_mock.get.return_value = self.user # Get the command object to test self.cmd = user.ShowUser(self.app, None) self.app.client_manager.identity.auth.client.get_user_id.\ - return_value = 'bbbbbbb-aaaa-aaaa-aaaa-bbbbbbbaaaa' + return_value = self.user.id self.app.client_manager.identity.tokens.get_token_data.return_value = \ {'token': {'user': {'domain': {}, - 'id': 'bbbbbbb-aaaa-aaaa-aaaa-bbbbbbbaaaa', - 'name': 'bbbbbbb-aaaa-aaaa-aaaa-bbbbbbbaaaa' + 'id': self.user.id, + 'name': self.user.name, } } } def test_user_show(self): arglist = [ - identity_fakes.user_id, + self.user.id, ] verifylist = [ - ('user', identity_fakes.user_id), + ('user', self.user.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1119,17 +1047,17 @@ def test_user_show(self): # data to be shown. columns, data = self.cmd.take_action(parsed_args) - self.users_mock.get.assert_called_with(identity_fakes.user_id) + self.users_mock.get.assert_called_with(self.user.id) collist = ('default_project_id', 'domain_id', 'email', 'enabled', 'id', 'name') self.assertEqual(collist, columns) datalist = ( - identity_fakes.project_id, - identity_fakes.domain_id, - identity_fakes.user_email, + self.user.default_project_id, + self.user.domain_id, + self.user.email, True, - identity_fakes.user_id, - identity_fakes.user_name, + self.user.id, + self.user.name, ) self.assertEqual(datalist, data) From c1a040f66d80c7f4c554938a71d985a41b1871bf Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Wed, 27 Jul 2016 15:35:58 +0800 Subject: [PATCH 1147/3095] Add a document for required options Some options are required in some commands, I think we need a document to state what should we do in this case. Change-Id: Id345ca1790e2125b333b271b74288e1a73d39ba3 --- doc/source/command-options.rst | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/doc/source/command-options.rst b/doc/source/command-options.rst index 6260ec3287..ebfeb5aa9c 100644 --- a/doc/source/command-options.rst +++ b/doc/source/command-options.rst @@ -211,6 +211,36 @@ An example handler in `take_action()` for `unset` action: if parsed_args.no_example_property: kwargs['example_property'] = [] +Required Options +---------------- + +Some options have no default value and the API does not allow them to be +`None`, then these options are always required when users use the command +to which these options belong. + +Required options must be validated by the CLI to aviod omissions. The CLI +validation may provide an error message for the user if a required option +is not specified. +(for example: ``error: argument --test is required``) + +.. option:: --test + + Test option (required) + +Implementation +~~~~~~~~~~~~~~ + +The parser declaration should look like this: + +.. code-block:: python + + parser.add_argument( + '--test', + metavar='', + required=True, + help=_('Test option (required)'), + ) + List Command Options ==================== From fac321458166cccffc0eb1d13a986a01c9e6d33e Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Tue, 2 Aug 2016 19:30:25 +0800 Subject: [PATCH 1148/3095] Implement "network rbac set" command Add "network rbac set" command which just supports setting a target project. Also, This patch adds the doc, unit test and functional test. But there is a bug of showing network RBAC https://bugs.launchpad.net/python-openstacksdk/+bug/1608903 We need to skip the functional test before this bug fixed. Change-Id: I756f448bb333cf1098a735e57a1c5dc4edf195d4 Partially-Implements: blueprint neutron-client-rbac --- doc/source/command-objects/network-rbac.rst | 26 +++++++++ .../tests/network/v2/test_network_rbac.py | 29 ++++++++-- openstackclient/network/v2/network_rbac.py | 41 ++++++++++++++ .../tests/network/v2/test_network_rbac.py | 56 +++++++++++++++++++ ...-neutron-client-rbac-bbd7b545b50d2bdf.yaml | 4 +- setup.cfg | 1 + 6 files changed, 151 insertions(+), 6 deletions(-) diff --git a/doc/source/command-objects/network-rbac.rst b/doc/source/command-objects/network-rbac.rst index ff929491ca..8acf097964 100644 --- a/doc/source/command-objects/network-rbac.rst +++ b/doc/source/command-objects/network-rbac.rst @@ -80,6 +80,32 @@ List network RBAC policies os network rbac list +network rbac set +---------------- + +Set network RBAC policy properties + +.. program:: network rbac set +.. code:: bash + + os network rbac set + [--target-project [--target-project-domain ]] + + +.. option:: --target-project + + The project to which the RBAC policy will be enforced (name or ID) + +.. option:: --target-project-domain + + Domain the target project belongs to (name or ID). + This can be used in case collisions between project names exist. + +.. _network_rbac_set-rbac-policy: +.. describe:: + + RBAC policy to be modified (ID only) + network rbac show ----------------- diff --git a/functional/tests/network/v2/test_network_rbac.py b/functional/tests/network/v2/test_network_rbac.py index 5d8cc1cc49..e7e358583d 100644 --- a/functional/tests/network/v2/test_network_rbac.py +++ b/functional/tests/network/v2/test_network_rbac.py @@ -12,12 +12,15 @@ import uuid +import testtools + from functional.common import test class NetworkRBACTests(test.TestCase): """Functional tests for network rbac. """ NET_NAME = uuid.uuid4().hex + PROJECT_NAME = uuid.uuid4().hex OBJECT_ID = None ID = None HEADERS = ['ID'] @@ -39,10 +42,10 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - raw_output = cls.openstack('network rbac delete ' + cls.ID) - cls.assertOutput('', raw_output) - raw_output = cls.openstack('network delete ' + cls.OBJECT_ID) - cls.assertOutput('', raw_output) + raw_output_rbac = cls.openstack('network rbac delete ' + cls.ID) + raw_output_network = cls.openstack('network delete ' + cls.OBJECT_ID) + cls.assertOutput('', raw_output_rbac) + cls.assertOutput('', raw_output_network) def test_network_rbac_list(self): opts = self.get_opts(self.HEADERS) @@ -53,3 +56,21 @@ def test_network_rbac_show(self): opts = self.get_opts(self.FIELDS) raw_output = self.openstack('network rbac show ' + self.ID + opts) self.assertEqual(self.ID + "\n", raw_output) + + # TODO(Huanxuan Ao): This test can pass after bug + # https://bugs.launchpad.net/python-openstackclient/+bug/1608903 fixed. + @testtools.skip( + 'Skip because of the bug ' + 'https://bugs.launchpad.net/python-openstackclient/+bug/1608903') + def test_network_rbac_set(self): + opts = self.get_opts(self.FIELDS) + project_id = self.openstack( + 'project create ' + self.PROJECT_NAME + opts) + self.openstack('network rbac set ' + self.ID + + ' --target-project ' + self.PROJECT_NAME) + opts = self.get_opts(['target_project']) + raw_output_rbac = self.openstack('network rbac show ' + self.ID + opts) + raw_output_project = self.openstack( + 'project delete ' + self.PROJECT_NAME) + self.assertEqual(project_id, raw_output_rbac) + self.assertOutput('', raw_output_project) diff --git a/openstackclient/network/v2/network_rbac.py b/openstackclient/network/v2/network_rbac.py index 62968376df..f4dfd4e734 100644 --- a/openstackclient/network/v2/network_rbac.py +++ b/openstackclient/network/v2/network_rbac.py @@ -186,6 +186,47 @@ def take_action(self, parsed_args): ) for s in data)) +class SetNetworkRBAC(command.Command): + """Set network RBAC policy properties""" + + def get_parser(self, prog_name): + parser = super(SetNetworkRBAC, self).get_parser(prog_name) + parser.add_argument( + 'rbac_policy', + metavar="", + help=_("RBAC policy to be modified (ID only)") + ) + parser.add_argument( + '--target-project', + metavar="", + help=_('The project to which the RBAC policy ' + 'will be enforced (name or ID)') + ) + parser.add_argument( + '--target-project-domain', + metavar='', + help=_('Domain the target project belongs to (name or ID). ' + 'This can be used in case collisions between project names ' + 'exist.'), + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.find_rbac_policy(parsed_args.rbac_policy, + ignore_missing=False) + attrs = {} + if parsed_args.target_project: + identity_client = self.app.client_manager.identity + project_id = identity_common.find_project( + identity_client, + parsed_args.target_project, + parsed_args.target_project_domain, + ).id + attrs['target_tenant'] = project_id + client.update_rbac_policy(obj, **attrs) + + class ShowNetworkRBAC(command.ShowOne): """Display network RBAC policy details""" diff --git a/openstackclient/tests/network/v2/test_network_rbac.py b/openstackclient/tests/network/v2/test_network_rbac.py index 5d6e9cfa0b..6255ada706 100644 --- a/openstackclient/tests/network/v2/test_network_rbac.py +++ b/openstackclient/tests/network/v2/test_network_rbac.py @@ -317,6 +317,62 @@ def test_network_rbac_list(self): self.assertEqual(self.data, list(data)) +class TestSetNetworkRBAC(TestNetworkRBAC): + + project = identity_fakes_v3.FakeProject.create_one_project() + rbac_policy = network_fakes.FakeNetworkRBAC.create_one_network_rbac( + attrs={'target_tenant': project.id}) + + def setUp(self): + super(TestSetNetworkRBAC, self).setUp() + + # Get the command object to test + self.cmd = network_rbac.SetNetworkRBAC(self.app, self.namespace) + + self.network.find_rbac_policy = mock.Mock( + return_value=self.rbac_policy) + self.network.update_rbac_policy = mock.Mock(return_value=None) + self.projects_mock.get.return_value = self.project + + def test_network_rbac_set_nothing(self): + arglist = [ + self.rbac_policy.id, + ] + verifylist = [ + ('rbac_policy', self.rbac_policy.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.network.find_rbac_policy.assert_called_once_with( + self.rbac_policy.id, ignore_missing=False + ) + attrs = {} + self.network.update_rbac_policy.assert_called_once_with( + self.rbac_policy, **attrs) + self.assertIsNone(result) + + def test_network_rbac_set(self): + arglist = [ + '--target-project', self.project.id, + self.rbac_policy.id, + ] + verifylist = [ + ('target_project', self.project.id), + ('rbac_policy', self.rbac_policy.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.network.find_rbac_policy.assert_called_once_with( + self.rbac_policy.id, ignore_missing=False + ) + attrs = {'target_tenant': self.project.id} + self.network.update_rbac_policy.assert_called_once_with( + self.rbac_policy, **attrs) + self.assertIsNone(result) + + class TestShowNetworkRBAC(TestNetworkRBAC): rbac_policy = network_fakes.FakeNetworkRBAC.create_one_network_rbac() diff --git a/releasenotes/notes/bp-neutron-client-rbac-bbd7b545b50d2bdf.yaml b/releasenotes/notes/bp-neutron-client-rbac-bbd7b545b50d2bdf.yaml index 7a00e587a0..eef92c93bd 100644 --- a/releasenotes/notes/bp-neutron-client-rbac-bbd7b545b50d2bdf.yaml +++ b/releasenotes/notes/bp-neutron-client-rbac-bbd7b545b50d2bdf.yaml @@ -1,5 +1,5 @@ --- features: - - Add ``network rbac list``, ``network rbac show``, ``network rbac create`` - and ``network rbac delete`` commands. + - Add ``network rbac list``, ``network rbac show``, ``network rbac create``, + ``network rbac delete`` and ``network rbac set`` commands. [Blueprint `neutron-client-rbac `_] diff --git a/setup.cfg b/setup.cfg index c0bb221936..fa2067a7ce 100644 --- a/setup.cfg +++ b/setup.cfg @@ -364,6 +364,7 @@ openstack.network.v2 = network_rbac_create = openstackclient.network.v2.network_rbac:CreateNetworkRBAC network_rbac_delete = openstackclient.network.v2.network_rbac:DeleteNetworkRBAC network_rbac_list = openstackclient.network.v2.network_rbac:ListNetworkRBAC + network_rbac_set = openstackclient.network.v2.network_rbac:SetNetworkRBAC network_rbac_show = openstackclient.network.v2.network_rbac:ShowNetworkRBAC network_segment_list = openstackclient.network.v2.network_segment:ListNetworkSegment From 20ae54045cef136a8d0665aab0d45698e12ed21c Mon Sep 17 00:00:00 2001 From: Xi Yang Date: Mon, 18 Jan 2016 15:47:23 +0800 Subject: [PATCH 1149/3095] Add support of setting volume's state OSC does not support to set volume's state, this patch is going to add this functionality. Closes-Bug:#1535213 Change-Id: I5bc1c7e81b8ba61c37f4bfd209fc86c5857fb050 Co-Authored-By: Huanxuan Ao --- doc/source/command-objects/volume.rst | 9 +++ functional/tests/volume/v2/test_volume.py | 7 ++ .../tests/volume/v2/test_volume.py | 77 +++++++++++-------- openstackclient/volume/v2/volume.py | 12 +++ .../notes/bug-1535213-c91133586e07d71c.yaml | 6 ++ 5 files changed, 81 insertions(+), 30 deletions(-) create mode 100644 releasenotes/notes/bug-1535213-c91133586e07d71c.yaml diff --git a/doc/source/command-objects/volume.rst b/doc/source/command-objects/volume.rst index 021518be7f..8efa8a6697 100644 --- a/doc/source/command-objects/volume.rst +++ b/doc/source/command-objects/volume.rst @@ -180,6 +180,7 @@ Set volume properties [--description ] [--property [...] ] [--image-property [...] ] + [--state ] .. option:: --name @@ -209,6 +210,14 @@ Set volume properties *Volume version 2 only* +.. option:: --state + + New volume state + ("available", "error", "creating", "deleting", "in-use", + "attaching", "detaching", "error_deleting" or "maintenance") + + *Volume version 2 only* + .. _volume_set-volume: .. describe:: diff --git a/functional/tests/volume/v2/test_volume.py b/functional/tests/volume/v2/test_volume.py index 019f0c6f52..02324a1e43 100644 --- a/functional/tests/volume/v2/test_volume.py +++ b/functional/tests/volume/v2/test_volume.py @@ -43,9 +43,16 @@ def tearDownClass(cls): 'volume set --name ' + cls.OTHER_NAME + ' ' + cls.NAME) cls.assertOutput('', raw_output) + # Set volume state + cls.openstack('volume set --state error ' + cls.OTHER_NAME) + opts = cls.get_opts(["status"]) + raw_output_status = cls.openstack( + 'volume show ' + cls.OTHER_NAME + opts) + # Delete test volume raw_output = cls.openstack('volume delete ' + cls.OTHER_NAME) cls.assertOutput('', raw_output) + cls.assertOutput('error\n', raw_output_status) def test_volume_list(self): opts = self.get_opts(self.HEADERS) diff --git a/openstackclient/tests/volume/v2/test_volume.py b/openstackclient/tests/volume/v2/test_volume.py index 6f552ad61b..c2740cab77 100644 --- a/openstackclient/tests/volume/v2/test_volume.py +++ b/openstackclient/tests/volume/v2/test_volume.py @@ -814,6 +814,53 @@ def test_volume_list_long(self): self.assertEqual(datalist, tuple(data)) +class TestVolumeSet(TestVolume): + + def setUp(self): + super(TestVolumeSet, self).setUp() + + self.new_volume = volume_fakes.FakeVolume.create_one_volume() + self.volumes_mock.get.return_value = self.new_volume + + # Get the command object to test + self.cmd = volume.SetVolume(self.app, None) + + def test_volume_set_image_property(self): + arglist = [ + '--image-property', 'Alpha=a', + '--image-property', 'Beta=b', + self.new_volume.id, + ] + verifylist = [ + ('image_property', {'Alpha': 'a', 'Beta': 'b'}), + ('volume', self.new_volume.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class ShowOne in cliff, abstract method take_action() + # returns nothing + self.cmd.take_action(parsed_args) + self.volumes_mock.set_image_metadata.assert_called_with( + self.volumes_mock.get().id, parsed_args.image_property) + + def test_volume_set_state(self): + arglist = [ + '--state', 'error', + self.new_volume.id + ] + verifylist = [ + ('state', 'error'), + ('volume', self.new_volume.id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.volumes_mock.reset_state.assert_called_with( + self.new_volume.id, 'error') + self.assertIsNone(result) + + class TestVolumeShow(TestVolume): def setUp(self): @@ -845,36 +892,6 @@ def test_volume_show(self): data) -class TestVolumeSet(TestVolume): - - def setUp(self): - super(TestVolumeSet, self).setUp() - - self.new_volume = volume_fakes.FakeVolume.create_one_volume() - self.volumes_mock.create.return_value = self.new_volume - - # Get the command object to test - self.cmd = volume.SetVolume(self.app, None) - - def test_volume_set_image_property(self): - arglist = [ - '--image-property', 'Alpha=a', - '--image-property', 'Beta=b', - self.new_volume.id, - ] - verifylist = [ - ('image_property', {'Alpha': 'a', 'Beta': 'b'}), - ('volume', self.new_volume.id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # In base command class ShowOne in cliff, abstract method take_action() - # returns nothing - self.cmd.take_action(parsed_args) - self.volumes_mock.set_image_metadata.assert_called_with( - self.volumes_mock.get().id, parsed_args.image_property) - - class TestVolumeUnset(TestVolume): def setUp(self): diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index 6f055922f0..aee18e4f26 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -378,6 +378,16 @@ def get_parser(self, prog_name): help=_('Set an image property on this volume ' '(repeat option to set multiple image properties)'), ) + parser.add_argument( + "--state", + metavar="", + choices=['available', 'error', 'creating', 'deleting', + 'in-use', 'attaching', 'detaching', 'error_deleting', + 'maintenance'], + help=_('New volume state ("available", "error", "creating", ' + '"deleting", "in-use", "attaching", "detaching", ' + '"error_deleting" or "maintenance")'), + ) return parser def take_action(self, parsed_args): @@ -400,6 +410,8 @@ def take_action(self, parsed_args): if parsed_args.image_property: volume_client.volumes.set_image_metadata( volume.id, parsed_args.image_property) + if parsed_args.state: + volume_client.volumes.reset_state(volume.id, parsed_args.state) kwargs = {} if parsed_args.name: diff --git a/releasenotes/notes/bug-1535213-c91133586e07d71c.yaml b/releasenotes/notes/bug-1535213-c91133586e07d71c.yaml new file mode 100644 index 0000000000..d3f36e3981 --- /dev/null +++ b/releasenotes/notes/bug-1535213-c91133586e07d71c.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Support a new ``--state`` option for ``volume set`` command that + changes the state of a volume. + [Bug `1535213 `_] From ab32f37e6715c15b9b6fb195f273c7672ef2a532 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Wed, 3 Aug 2016 13:33:19 +0800 Subject: [PATCH 1150/3095] Remove an outdated directory in tox.ini The directory openstack/common from the exclude list of flake8 in tox.ini was used to keep codes from oslo-incubator, but oslo-incubator was retired, so don't use this directory any more. Change-Id: If1ea5dc167cfe4a09aad413b9eab0af807ebe603 --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 10b4978c29..29854b6775 100644 --- a/tox.ini +++ b/tox.ini @@ -77,7 +77,7 @@ commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasen [flake8] show-source = True -exclude = .git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build,tools +exclude = .git,.tox,dist,doc,*lib/python*,*egg,build,tools # If 'ignore' is not set there are default errors and warnings that are set # Doc: http://flake8.readthedocs.org/en/latest/config.html#default ignore = __ From 06ea24725b1331fcf840a64f558e2d1f15a3c8c6 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 3 Aug 2016 16:15:20 +0000 Subject: [PATCH 1151/3095] Updated from global requirements Change-Id: I202897c2d66cc9e20b3d2ef1564a37db26f88d47 --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 6a98478f09..c952ec742d 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -24,7 +24,7 @@ python-barbicanclient>=4.0.0 # Apache-2.0 python-congressclient<2000,>=1.3.0 # Apache-2.0 python-designateclient>=1.5.0 # Apache-2.0 python-heatclient>=1.1.0 # Apache-2.0 -python-ironicclient>=1.1.0 # Apache-2.0 +python-ironicclient>=1.6.0 # Apache-2.0 python-ironic-inspector-client>=1.5.0 # Apache-2.0 python-mistralclient>=2.0.0 # Apache-2.0 python-muranoclient>=0.8.2 # Apache-2.0 From 5479ff323acd40d68cfc9a369289dbe724347082 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 4 Aug 2016 02:41:46 +0000 Subject: [PATCH 1152/3095] Updated from global requirements Change-Id: I61c1841fc28a9a493ee1e5383f14515840fe4337 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 7cbebb88d0..3cd361fc14 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ keystoneauth1>=2.10.0 # Apache-2.0 openstacksdk>=0.9.0 # Apache-2.0 os-client-config>=1.13.1 # Apache-2.0 osc-lib>=0.4.0 # Apache-2.0 -oslo.config>=3.12.0 # Apache-2.0 +oslo.config>=3.14.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 oslo.utils>=3.16.0 # Apache-2.0 python-glanceclient>=2.0.0 # Apache-2.0 From f58be9d4ddc4d395e209015e8735f6cb4258a566 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Thu, 4 Aug 2016 15:46:50 +0800 Subject: [PATCH 1153/3095] Add unit tests for group commands in identity v3 Add unit tests for commands below in identity v3: group create group delete group show group set group add user group remove user group contains user Change-Id: I02f3b49e93582245a2749492bba1dfc4c5e0258d --- openstackclient/tests/identity/v3/fakes.py | 38 ++ .../tests/identity/v3/test_group.py | 391 ++++++++++++++++++ 2 files changed, 429 insertions(+) diff --git a/openstackclient/tests/identity/v3/fakes.py b/openstackclient/tests/identity/v3/fakes.py index 50e901c983..8c138f7be8 100644 --- a/openstackclient/tests/identity/v3/fakes.py +++ b/openstackclient/tests/identity/v3/fakes.py @@ -769,6 +769,44 @@ def create_one_group(attrs=None): loaded=True) return group + @staticmethod + def create_groups(attrs=None, count=2): + """Create multiple fake groups. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of groups to fake + :return: + A list of FakeResource objects faking the groups + """ + groups = [] + for i in range(0, count): + group = FakeGroup.create_one_group(attrs) + groups.append(group) + + return groups + + @staticmethod + def get_groups(groups=None, count=2): + """Get an iterable MagicMock object with a list of faked groups. + + If groups list is provided, then initialize the Mock object with + the list. Otherwise create one. + + :param List groups: + A list of FakeResource objects faking groups + :param Integer count: + The number of groups to be faked + :return + An iterable Mock object with side_effect set to a list of faked + groups + """ + if groups is None: + groups = FakeGroup.create_groups(count) + + return mock.MagicMock(side_effect=groups) + class FakeEndpoint(object): """Fake one or more endpoint.""" diff --git a/openstackclient/tests/identity/v3/test_group.py b/openstackclient/tests/identity/v3/test_group.py index 112008d35e..a678dee9af 100644 --- a/openstackclient/tests/identity/v3/test_group.py +++ b/openstackclient/tests/identity/v3/test_group.py @@ -11,6 +11,12 @@ # under the License. # +import mock +from mock import call + +from keystoneauth1 import exceptions as ks_exc +from osc_lib import exceptions + from openstackclient.identity.v3 import group from openstackclient.tests.identity.v3 import fakes as identity_fakes @@ -33,6 +39,225 @@ def setUp(self): self.users_mock.reset_mock() +class TestGroupAddUser(TestGroup): + + group = identity_fakes.FakeGroup.create_one_group() + user = identity_fakes.FakeUser.create_one_user() + + def setUp(self): + super(TestGroupAddUser, self).setUp() + + self.groups_mock.get.return_value = self.group + self.users_mock.get.return_value = self.user + self.users_mock.add_to_group.return_value = None + + self.cmd = group.AddUserToGroup(self.app, None) + + def test_group_add_user(self): + arglist = [ + self.group.name, + self.user.name, + ] + verifylist = [ + ('group', self.group.name), + ('user', self.user.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.users_mock.add_to_group.assert_called_once_with( + self.user.id, self.group.id) + self.assertIsNone(result) + + +class TestGroupCheckUser(TestGroup): + + group = identity_fakes.FakeGroup.create_one_group() + user = identity_fakes.FakeUser.create_one_user() + + def setUp(self): + super(TestGroupCheckUser, self).setUp() + + self.groups_mock.get.return_value = self.group + self.users_mock.get.return_value = self.user + self.users_mock.check_in_group.return_value = None + + self.cmd = group.CheckUserInGroup(self.app, None) + + def test_group_check_user(self): + arglist = [ + self.group.name, + self.user.name, + ] + verifylist = [ + ('group', self.group.name), + ('user', self.user.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.users_mock.check_in_group.assert_called_once_with( + self.user.id, self.group.id) + self.assertIsNone(result) + + +class TestGroupCreate(TestGroup): + + domain = identity_fakes.FakeDomain.create_one_domain() + + columns = ( + 'description', + 'domain_id', + 'id', + 'name', + ) + + def setUp(self): + super(TestGroupCreate, self).setUp() + self.group = identity_fakes.FakeGroup.create_one_group( + attrs={'domain_id': self.domain.id}) + self.data = ( + self.group.description, + self.group.domain_id, + self.group.id, + self.group.name, + ) + + self.groups_mock.create.return_value = self.group + self.groups_mock.get.return_value = self.group + self.domains_mock.get.return_value = self.domain + + self.cmd = group.CreateGroup(self.app, None) + + def test_group_create(self): + arglist = [ + self.group.name, + ] + verifylist = [ + ('name', self.group.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.groups_mock.create.assert_called_once_with( + name=self.group.name, + domain=None, + description=None, + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_group_create_with_options(self): + arglist = [ + '--domain', self.domain.name, + '--description', self.group.description, + self.group.name, + ] + verifylist = [ + ('domain', self.domain.name), + ('description', self.group.description), + ('name', self.group.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.groups_mock.create.assert_called_once_with( + name=self.group.name, + domain=self.domain.id, + description=self.group.description, + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_group_create_or_show(self): + self.groups_mock.create.side_effect = ks_exc.Conflict() + arglist = [ + '--or-show', + self.group.name, + ] + verifylist = [ + ('or_show', True), + ('name', self.group.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.groups_mock.get.assert_called_once_with(self.group.name) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestGroupDelete(TestGroup): + + domain = identity_fakes.FakeDomain.create_one_domain() + groups = identity_fakes.FakeGroup.create_groups( + attrs={'domain_id': domain.id}, count=2) + + def setUp(self): + super(TestGroupDelete, self).setUp() + + self.groups_mock.get = ( + identity_fakes.FakeGroup.get_groups(self.groups)) + self.groups_mock.delete.return_value = None + self.domains_mock.get.return_value = self.domain + + self.cmd = group.DeleteGroup(self.app, None) + + def test_group_delete(self): + arglist = [ + self.groups[0].id, + ] + verifylist = [ + ('groups', [self.groups[0].id]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.groups_mock.get.assert_called_once_with(self.groups[0].id) + self.groups_mock.delete.assert_called_once_with(self.groups[0].id) + self.assertIsNone(result) + + def test_group_multi_delete(self): + arglist = [] + verifylist = [] + + for g in self.groups: + arglist.append(g.id) + verifylist = [ + ('groups', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [] + for g in self.groups: + calls.append(call(g.id)) + self.groups_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_group_delete_with_domain(self): + get_mock_result = [exceptions.CommandError, self.groups[0]] + self.groups_mock.get = ( + mock.MagicMock(side_effect=get_mock_result)) + + arglist = [ + '--domain', self.domain.id, + self.groups[0].id, + ] + verifylist = [ + ('domain', self.groups[0].domain_id), + ('groups', [self.groups[0].id]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.groups_mock.get.assert_any_call( + self.groups[0].id, domain_id=self.domain.id) + self.groups_mock.delete.assert_called_once_with(self.groups[0].id) + self.assertIsNone(result) + + class TestGroupList(TestGroup): domain = identity_fakes.FakeDomain.create_one_domain() @@ -176,3 +401,169 @@ def test_group_list_long(self): ), ) self.assertEqual(columns, columns) self.assertEqual(datalist, tuple(data)) + + +class TestGroupRemoveUser(TestGroup): + + group = identity_fakes.FakeGroup.create_one_group() + user = identity_fakes.FakeUser.create_one_user() + + def setUp(self): + super(TestGroupRemoveUser, self).setUp() + + self.groups_mock.get.return_value = self.group + self.users_mock.get.return_value = self.user + self.users_mock.remove_from_group.return_value = None + + self.cmd = group.RemoveUserFromGroup(self.app, None) + + def test_group_remove_user(self): + arglist = [ + self.group.id, + self.user.id, + ] + verifylist = [ + ('group', self.group.id), + ('user', self.user.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.users_mock.remove_from_group.assert_called_once_with( + self.user.id, self.group.id) + self.assertIsNone(result) + + +class TestGroupSet(TestGroup): + + domain = identity_fakes.FakeDomain.create_one_domain() + group = identity_fakes.FakeGroup.create_one_group( + attrs={'domain_id': domain.id}) + + def setUp(self): + super(TestGroupSet, self).setUp() + + self.groups_mock.get.return_value = self.group + self.domains_mock.get.return_value = self.domain + self.groups_mock.update.return_value = None + + self.cmd = group.SetGroup(self.app, None) + + def test_group_set_nothing(self): + arglist = [ + self.group.id, + ] + verifylist = [ + ('group', self.group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.groups_mock.update.assert_called_once_with(self.group.id) + self.assertIsNone(result) + + def test_group_set_name_and_description(self): + arglist = [ + '--name', 'new_name', + '--description', 'new_description', + self.group.id, + ] + verifylist = [ + ('name', 'new_name'), + ('description', 'new_description'), + ('group', self.group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + kwargs = { + 'name': 'new_name', + 'description': 'new_description', + } + self.groups_mock.update.assert_called_once_with( + self.group.id, **kwargs) + self.assertIsNone(result) + + def test_group_set_with_domain(self): + get_mock_result = [exceptions.CommandError, self.group] + self.groups_mock.get = ( + mock.MagicMock(side_effect=get_mock_result)) + + arglist = [ + '--domain', self.domain.id, + self.group.id, + ] + verifylist = [ + ('domain', self.domain.id), + ('group', self.group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.groups_mock.get.assert_any_call( + self.group.id, domain_id=self.domain.id) + self.groups_mock.update.assert_called_once_with(self.group.id) + self.assertIsNone(result) + + +class TestGroupShow(TestGroup): + + domain = identity_fakes.FakeDomain.create_one_domain() + + columns = ( + 'description', + 'domain_id', + 'id', + 'name', + ) + + def setUp(self): + super(TestGroupShow, self).setUp() + self.group = identity_fakes.FakeGroup.create_one_group( + attrs={'domain_id': self.domain.id}) + self.data = ( + self.group.description, + self.group.domain_id, + self.group.id, + self.group.name, + ) + + self.groups_mock.get.return_value = self.group + self.domains_mock.get.return_value = self.domain + + self.cmd = group.ShowGroup(self.app, None) + + def test_group_show(self): + arglist = [ + self.group.id, + ] + verifylist = [ + ('group', self.group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.groups_mock.get.assert_called_once_with(self.group.id) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_group_show_with_domain(self): + get_mock_result = [exceptions.CommandError, self.group] + self.groups_mock.get = ( + mock.MagicMock(side_effect=get_mock_result)) + + arglist = [ + '--domain', self.domain.id, + self.group.id, + ] + verifylist = [ + ('domain', self.domain.id), + ('group', self.group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.groups_mock.get.assert_any_call( + self.group.id, domain_id=self.domain.id) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) From 7f93d8cc888b5397614b52ebd26ffc742d6f7072 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Thu, 4 Aug 2016 11:20:44 +0800 Subject: [PATCH 1154/3095] Show "target_project_id" attribute properly for network rbac object According to the code in SDK: https://github.com/openstack/python-openstacksdk/blob/master/openstack/network/v2/rbac_policy.py#L34 we can see the conlumn of "target_tenant" should be "targer_project_id" but not "target_project". It is the reason why could not show the "target_project" in OSC, so this patch fix it. Before this change: (openstack) network rbac show b74fd644-e057-4d44-8ae1-7ca9967ea1e1 +----------------+--------------------------------------+ | Field | Value | +----------------+--------------------------------------+ | action | access_as_shared | | id | b74fd644-e057-4d44-8ae1-7ca9967ea1e1 | | object_id | 8735b57f-606a-4f65-9902-2052a6d2a66d | | object_type | network | | project_id | 01c0ba43101b4080a52a5f79a55c56ff | | target_project | | +----------------+--------------------------------------+ After this change: (openstack) network rbac show b74fd644-e057-4d44-8ae1-7ca9967ea1e1 +-------------------+--------------------------------------+ | Field | Value | +-------------------+--------------------------------------+ | action | access_as_shared | | id | b74fd644-e057-4d44-8ae1-7ca9967ea1e1 | | object_id | 8735b57f-606a-4f65-9902-2052a6d2a66d | | object_type | network | | project_id | 01c0ba43101b4080a52a5f79a55c56ff | | target_project_id | c7ab4d2ea9e1487095a8ca24ea44ef38 | +-------------------+--------------------------------------+ Change-Id: I53df127bfc3e43288c6afecdf872e6101b94a658 Closes-Bug: #1608903 --- functional/tests/network/v2/test_network_rbac.py | 9 +-------- openstackclient/network/v2/network_rbac.py | 2 +- openstackclient/tests/network/v2/fakes.py | 2 +- openstackclient/tests/network/v2/test_network_rbac.py | 4 ++-- 4 files changed, 5 insertions(+), 12 deletions(-) diff --git a/functional/tests/network/v2/test_network_rbac.py b/functional/tests/network/v2/test_network_rbac.py index e7e358583d..1dbc246bf2 100644 --- a/functional/tests/network/v2/test_network_rbac.py +++ b/functional/tests/network/v2/test_network_rbac.py @@ -12,8 +12,6 @@ import uuid -import testtools - from functional.common import test @@ -57,18 +55,13 @@ def test_network_rbac_show(self): raw_output = self.openstack('network rbac show ' + self.ID + opts) self.assertEqual(self.ID + "\n", raw_output) - # TODO(Huanxuan Ao): This test can pass after bug - # https://bugs.launchpad.net/python-openstackclient/+bug/1608903 fixed. - @testtools.skip( - 'Skip because of the bug ' - 'https://bugs.launchpad.net/python-openstackclient/+bug/1608903') def test_network_rbac_set(self): opts = self.get_opts(self.FIELDS) project_id = self.openstack( 'project create ' + self.PROJECT_NAME + opts) self.openstack('network rbac set ' + self.ID + ' --target-project ' + self.PROJECT_NAME) - opts = self.get_opts(['target_project']) + opts = self.get_opts(['target_project_id']) raw_output_rbac = self.openstack('network rbac show ' + self.ID + opts) raw_output_project = self.openstack( 'project delete ' + self.PROJECT_NAME) diff --git a/openstackclient/network/v2/network_rbac.py b/openstackclient/network/v2/network_rbac.py index f4dfd4e734..bb29579f95 100644 --- a/openstackclient/network/v2/network_rbac.py +++ b/openstackclient/network/v2/network_rbac.py @@ -33,7 +33,7 @@ def _get_columns(item): columns.append('project_id') if 'target_tenant' in columns: columns.remove('target_tenant') - columns.append('target_project') + columns.append('target_project_id') return tuple(sorted(columns)) diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 9182fe553c..ec329f4d0f 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -520,7 +520,7 @@ def create_one_network_rbac(attrs=None): loaded=True) # Set attributes with special mapping in OpenStack SDK. rbac.project_id = rbac_attrs['tenant_id'] - rbac.target_project = rbac_attrs['target_tenant'] + rbac.target_project_id = rbac_attrs['target_tenant'] return rbac @staticmethod diff --git a/openstackclient/tests/network/v2/test_network_rbac.py b/openstackclient/tests/network/v2/test_network_rbac.py index 6255ada706..9250e91b9e 100644 --- a/openstackclient/tests/network/v2/test_network_rbac.py +++ b/openstackclient/tests/network/v2/test_network_rbac.py @@ -49,7 +49,7 @@ class TestCreateNetworkRBAC(TestNetworkRBAC): 'object_id', 'object_type', 'project_id', - 'target_project', + 'target_project_id', ) data = [ @@ -383,7 +383,7 @@ class TestShowNetworkRBAC(TestNetworkRBAC): 'object_id', 'object_type', 'project_id', - 'target_project', + 'target_project_id', ) data = [ From 0736336a71db9baf3e768e4560a619ae6a875fa4 Mon Sep 17 00:00:00 2001 From: Michael Gugino Date: Thu, 12 May 2016 16:15:19 -0400 Subject: [PATCH 1155/3095] Implement network agents functionality python-neutronclient implements the following command set: agent-list, agent-show, agent-delete These commands display and modify various network agents and their information. python-openstacksdk has supported the api calls for these commands, but python-openstackclient does not implement these commands. This commit adds support for the following commands: openstack network agent list openstack network agent show openstack network agent delete Change-Id: I83ede6f89c37e7bdc38d7e9e7bb9d80e94c8becc Implements: blueprint implement-network-agents Depends-On: I9755637f76787d5fac8ff295ae273b308fcb98d0 Co-Authored-By: Huanxuan Ao --- doc/source/command-objects/network-agent.rst | 53 +++++ doc/source/commands.rst | 1 + .../tests/network/v2/test_network_agent.py | 32 +++ openstackclient/network/v2/network_agent.py | 119 ++++++++++ openstackclient/tests/network/v2/fakes.py | 67 ++++++ .../tests/network/v2/test_network_agent.py | 219 ++++++++++++++++++ ...ement-network-agents-5eba48796318f094.yaml | 5 + setup.cfg | 4 + 8 files changed, 500 insertions(+) create mode 100644 doc/source/command-objects/network-agent.rst create mode 100644 functional/tests/network/v2/test_network_agent.py create mode 100644 openstackclient/network/v2/network_agent.py create mode 100644 openstackclient/tests/network/v2/test_network_agent.py create mode 100644 releasenotes/notes/bp-implement-network-agents-5eba48796318f094.yaml diff --git a/doc/source/command-objects/network-agent.rst b/doc/source/command-objects/network-agent.rst new file mode 100644 index 0000000000..32cb371cc1 --- /dev/null +++ b/doc/source/command-objects/network-agent.rst @@ -0,0 +1,53 @@ +============= +network agent +============= + +A **network agent** is an agent that handles various tasks used to +implement virtual networks. These agents include neutron-dhcp-agent, +neutron-l3-agent, neutron-metering-agent, and neutron-lbaas-agent, +among others. The agent is available when the alive status of the +agent is "True". + +Network v2 + +network agent delete +-------------------- + +Delete network agent(s) + +.. program:: network agent delete +.. code:: bash + + os network agent delete + [ ...] + +.. _network_agent_delete-network-agent: +.. describe:: + + Network agent(s) to delete (ID only) + +network agent list +------------------ + +List network agents + +.. program:: network agent list +.. code:: bash + + os network agent list + +network agent show +------------------ + +Display network agent details + +.. program:: network agent show +.. code:: bash + + os network agent show + + +.. _network_agent_show-network-agent: +.. describe:: + + Network agent to display (ID only) diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 025f42f905..aff197f287 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -109,6 +109,7 @@ referring to both Compute and Volume quotas. * ``mapping``: (**Identity**) a definition to translate identity provider attributes to Identity concepts * ``module``: (**Internal**) - installed Python modules in the OSC process * ``network``: (**Compute**, **Network**) - a virtual network for connecting servers and other resources +* ``network agent``: (**Network**) - A network agent is an agent that handles various tasks used to implement virtual networks * ``network rbac``: (**Network**) - an RBAC policy for network resources * ``network segment``: (**Network**) - a segment of a virtual network * ``object``: (**Object Storage**) a single file in the Object Storage diff --git a/functional/tests/network/v2/test_network_agent.py b/functional/tests/network/v2/test_network_agent.py new file mode 100644 index 0000000000..e01ead4229 --- /dev/null +++ b/functional/tests/network/v2/test_network_agent.py @@ -0,0 +1,32 @@ +# 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 functional.common import test + + +class NetworkAgentTests(test.TestCase): + """Functional tests for network agent. """ + IDs = None + HEADERS = ['ID'] + FIELDS = ['id'] + + @classmethod + def test_network_agent_list(cls): + opts = cls.get_opts(cls.HEADERS) + raw_output = cls.openstack('network agent list' + opts) + # get the list of network agent IDs. + cls.IDs = raw_output.split('\n') + + def test_network_agent_show(self): + opts = self.get_opts(self.FIELDS) + raw_output = self.openstack('network agent show ' + self.IDs[0] + opts) + self.assertEqual(self.IDs[0] + "\n", raw_output) diff --git a/openstackclient/network/v2/network_agent.py b/openstackclient/network/v2/network_agent.py new file mode 100644 index 0000000000..1fb70a50a0 --- /dev/null +++ b/openstackclient/network/v2/network_agent.py @@ -0,0 +1,119 @@ +# 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 agent action implementations""" + +import logging + +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils + +from openstackclient.i18n import _ + + +LOG = logging.getLogger(__name__) + + +def _format_admin_state(state): + return 'UP' if state else 'DOWN' + + +_formatters = { + 'admin_state_up': _format_admin_state, + 'configurations': utils.format_dict, +} + + +class DeleteNetworkAgent(command.Command): + """Delete network agent(s)""" + + def get_parser(self, prog_name): + parser = super(DeleteNetworkAgent, self).get_parser(prog_name) + parser.add_argument( + 'network_agent', + metavar="", + nargs='+', + help=(_("Network agent(s) to delete (ID only)")) + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + result = 0 + + for agent in parsed_args.network_agent: + try: + obj = client.get_agent(agent, ignore_missing=False) + client.delete_agent(obj) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete network agent with " + "ID '%(agent)s': %(e)s"), + {'agent': agent, 'e': e}) + + if result > 0: + total = len(parsed_args.network_agent) + msg = (_("%(result)s of %(total)s network agents failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) + + +class ListNetworkAgent(command.Lister): + """List network agents""" + + def take_action(self, parsed_args): + client = self.app.client_manager.network + columns = ( + 'id', + 'agent_type', + 'host', + 'availability_zone', + 'alive', + 'admin_state_up', + 'binary' + ) + column_headers = ( + 'ID', + 'Agent Type', + 'Host', + 'Availability Zone', + 'Alive', + 'State', + 'Binary' + ) + data = client.agents() + return (column_headers, + (utils.get_item_properties( + s, columns, formatters=_formatters, + ) for s in data)) + + +class ShowNetworkAgent(command.ShowOne): + """Display network agent details""" + + def get_parser(self, prog_name): + parser = super(ShowNetworkAgent, self).get_parser(prog_name) + parser.add_argument( + 'network_agent', + metavar="", + help=(_("Network agent to display (ID only)")) + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.get_agent(parsed_args.network_agent, ignore_missing=False) + columns = tuple(sorted(list(obj.keys()))) + data = utils.get_item_properties(obj, columns, formatters=_formatters,) + return columns, data diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 0575209447..8c8f3a0000 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -491,6 +491,73 @@ def get_ports(ports=None, count=2): return mock.MagicMock(side_effect=ports) +class FakeNetworkAgent(object): + """Fake one or more network agents.""" + + @staticmethod + def create_one_network_agent(attrs=None): + """Create a fake network agent + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object, with id, agent_type, and so on. + """ + attrs = attrs or {} + + # Set default attributes + agent_attrs = { + 'id': 'agent-id-' + uuid.uuid4().hex, + 'agent_type': 'agent-type-' + uuid.uuid4().hex, + 'host': 'host-' + uuid.uuid4().hex, + 'availability_zone': 'zone-' + uuid.uuid4().hex, + 'alive': True, + 'admin_state_up': True, + 'binary': 'binary-' + uuid.uuid4().hex, + 'configurations': {'subnet': 2, 'networks': 1}, + } + agent_attrs.update(attrs) + agent = fakes.FakeResource(info=copy.deepcopy(agent_attrs), + loaded=True) + return agent + + @staticmethod + def create_network_agents(attrs=None, count=2): + """Create multiple fake network agents. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of network agents to fake + :return: + A list of FakeResource objects faking the network agents + """ + agents = [] + for i in range(0, count): + agents.append(FakeNetworkAgent.create_one_network_agent(attrs)) + + return agents + + @staticmethod + def get_network_agents(agents=None, count=2): + """Get an iterable MagicMock object with a list of faked network agents. + + If network agents list is provided, then initialize the Mock object + with the list. Otherwise create one. + + :param List agents: + A list of FakeResource objects faking network agents + :param int count: + The number of network agents to fake + :return: + An iterable Mock object with side_effect set to a list of faked + network agents + """ + if agents is None: + agents = FakeNetworkAgent.create_network_agents(count) + return mock.MagicMock(side_effect=agents) + + class FakeNetworkRBAC(object): """Fake one or more network rbac policies.""" diff --git a/openstackclient/tests/network/v2/test_network_agent.py b/openstackclient/tests/network/v2/test_network_agent.py new file mode 100644 index 0000000000..3cf9a53081 --- /dev/null +++ b/openstackclient/tests/network/v2/test_network_agent.py @@ -0,0 +1,219 @@ +# 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 mock import call + +from osc_lib import exceptions +from osc_lib import utils + +from openstackclient.network.v2 import network_agent +from openstackclient.tests.network.v2 import fakes as network_fakes +from openstackclient.tests import utils as tests_utils + + +class TestNetworkAgent(network_fakes.TestNetworkV2): + + def setUp(self): + super(TestNetworkAgent, self).setUp() + + # Get a shortcut to the network client + self.network = self.app.client_manager.network + + +class TestDeleteNetworkAgent(TestNetworkAgent): + + network_agents = ( + network_fakes.FakeNetworkAgent.create_network_agents(count=2)) + + def setUp(self): + super(TestDeleteNetworkAgent, self).setUp() + self.network.delete_agent = mock.Mock(return_value=None) + self.network.get_agent = ( + network_fakes.FakeNetworkAgent.get_network_agents( + agents=self.network_agents) + ) + + # Get the command object to test + self.cmd = network_agent.DeleteNetworkAgent(self.app, self.namespace) + + def test_network_agent_delete(self): + arglist = [ + self.network_agents[0].id, + ] + verifylist = [ + ('network_agent', [self.network_agents[0].id]), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.network.get_agent.assert_called_once_with( + self.network_agents[0].id, ignore_missing=False) + self.network.delete_agent.assert_called_once_with( + self.network_agents[0]) + self.assertIsNone(result) + + def test_multi_network_agents_delete(self): + arglist = [] + verifylist = [] + + for n in self.network_agents: + arglist.append(n.id) + verifylist = [ + ('network_agent', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [] + for n in self.network_agents: + calls.append(call(n)) + self.network.delete_agent.assert_has_calls(calls) + self.assertIsNone(result) + + def test_multi_network_agents_delete_with_exception(self): + arglist = [ + self.network_agents[0].id, + 'unexist_network_agent', + ] + verifylist = [ + ('network_agent', + [self.network_agents[0].id, 'unexist_network_agent']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self.network_agents[0], exceptions.CommandError] + self.network.get_agent = ( + mock.MagicMock(side_effect=find_mock_result) + ) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 network agents failed to delete.', str(e)) + + self.network.get_agent.assert_any_call( + self.network_agents[0].id, ignore_missing=False) + self.network.get_agent.assert_any_call( + 'unexist_network_agent', ignore_missing=False) + self.network.delete_agent.assert_called_once_with( + self.network_agents[0] + ) + + +class TestListNetworkAgent(TestNetworkAgent): + + network_agents = ( + network_fakes.FakeNetworkAgent.create_network_agents(count=3)) + + columns = ( + 'ID', + 'Agent Type', + 'Host', + 'Availability Zone', + 'Alive', + 'State', + 'Binary' + ) + data = [] + for agent in network_agents: + data.append(( + agent.id, + agent.agent_type, + agent.host, + agent.availability_zone, + agent.alive, + network_agent._format_admin_state(agent.admin_state_up), + agent.binary, + )) + + def setUp(self): + super(TestListNetworkAgent, self).setUp() + self.network.agents = mock.Mock( + return_value=self.network_agents) + + # Get the command object to test + self.cmd = network_agent.ListNetworkAgent(self.app, self.namespace) + + def test_network_agents_list(self): + arglist = [] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.agents.assert_called_once_with(**{}) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + +class TestShowNetworkAgent(TestNetworkAgent): + + _network_agent = ( + network_fakes.FakeNetworkAgent.create_one_network_agent()) + + columns = ( + 'admin_state_up', + 'agent_type', + 'alive', + 'availability_zone', + 'binary', + 'configurations', + 'host', + 'id', + ) + data = ( + network_agent._format_admin_state(_network_agent.admin_state_up), + _network_agent.agent_type, + _network_agent.alive, + _network_agent.availability_zone, + _network_agent.binary, + utils.format_dict(_network_agent.configurations), + _network_agent.host, + _network_agent.id, + ) + + def setUp(self): + super(TestShowNetworkAgent, self).setUp() + self.network.get_agent = mock.Mock( + return_value=self._network_agent) + + # Get the command object to test + self.cmd = network_agent.ShowNetworkAgent(self.app, self.namespace) + + def test_show_no_options(self): + arglist = [] + verifylist = [] + + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_show_all_options(self): + arglist = [ + self._network_agent.id, + ] + verifylist = [ + ('network_agent', self._network_agent.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.get_agent.assert_called_once_with( + self._network_agent.id, ignore_missing=False) + self.assertEqual(self.columns, columns) + self.assertEqual(list(self.data), list(data)) diff --git a/releasenotes/notes/bp-implement-network-agents-5eba48796318f094.yaml b/releasenotes/notes/bp-implement-network-agents-5eba48796318f094.yaml new file mode 100644 index 0000000000..4ca2510367 --- /dev/null +++ b/releasenotes/notes/bp-implement-network-agents-5eba48796318f094.yaml @@ -0,0 +1,5 @@ +--- +features: + - Add ``network agent delete``, ``network agent list`` and + ``network agent show`` commands. + [Blueprint `implement-network-agents `_] diff --git a/setup.cfg b/setup.cfg index f1abceb93d..fd6882854f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -355,6 +355,10 @@ openstack.network.v2 = ip_floating_pool_list = openstackclient.network.v2.floating_ip_pool:ListIPFloatingPool + network_agent_delete = openstackclient.network.v2.network_agent:DeleteNetworkAgent + network_agent_list = openstackclient.network.v2.network_agent:ListNetworkAgent + network_agent_show = openstackclient.network.v2.network_agent:ShowNetworkAgent + network_create = openstackclient.network.v2.network:CreateNetwork network_delete = openstackclient.network.v2.network:DeleteNetwork network_list = openstackclient.network.v2.network:ListNetwork From 6a15f90daed6514e30b4af0ebb52c7864acaafcc Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 23 Jun 2016 17:51:54 -0500 Subject: [PATCH 1156/3095] osc-lib: shell Convert to using ClientManager and OpenStackShell from osc-lib. * Change all internal uses of ClientManager private attributes that are now public in osc-lib's ClientManager. Leave back-compat copies in place in OSC's clientManager so we don't break plugins. * Put some work-arounds in place for changes in osc-lib that we need until a new release makes it through the g-r and u-c change process. * Add a test for Unicode decoding of argv in shell.main() to parallel the one in osc-lib. Change-Id: I85289740d4ca081f2aca8c9b40ec422ad25d302c --- openstackclient/common/clientmanager.py | 2 +- openstackclient/compute/client.py | 4 +- openstackclient/identity/client.py | 7 +- openstackclient/image/client.py | 12 +- openstackclient/network/client.py | 4 +- openstackclient/object/client.py | 4 +- openstackclient/shell.py | 389 +--------------- openstackclient/tests/test_shell.py | 588 +++++------------------- openstackclient/volume/client.py | 4 +- test-requirements.txt | 1 + 10 files changed, 147 insertions(+), 868 deletions(-) diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index 2105a49711..ccfde2d0d3 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -86,7 +86,7 @@ def get_plugin_modules(group): # Add the plugin to the ClientManager setattr( - ClientManager, + clientmanager.ClientManager, module.API_NAME, clientmanager.ClientCache( getattr(sys.modules[ep.module_name], 'make_client', None) diff --git a/openstackclient/compute/client.py b/openstackclient/compute/client.py index 1583676a8c..4cc3be9855 100644 --- a/openstackclient/compute/client.py +++ b/openstackclient/compute/client.py @@ -64,7 +64,7 @@ def make_client(instance): if ext.name == "list_extensions"] # Remember interface only if it is set - kwargs = utils.build_kwargs_dict('endpoint_type', instance._interface) + kwargs = utils.build_kwargs_dict('endpoint_type', instance.interface) client = nova_client.Client( version, @@ -72,7 +72,7 @@ def make_client(instance): extensions=extensions, http_log_debug=http_log_debug, timings=instance.timing, - region_name=instance._region_name, + region_name=instance.region_name, **kwargs ) diff --git a/openstackclient/identity/client.py b/openstackclient/identity/client.py index 0c609313f9..0292aac216 100644 --- a/openstackclient/identity/client.py +++ b/openstackclient/identity/client.py @@ -16,7 +16,6 @@ import logging from keystoneclient.v2_0 import client as identity_client_v2 -from osc_lib.api import auth from osc_lib import utils from openstackclient.i18n import _ @@ -50,11 +49,11 @@ def make_client(instance): LOG.debug('Instantiating identity client: %s', identity_client) # Remember interface only if interface is set - kwargs = utils.build_kwargs_dict('interface', instance._interface) + kwargs = utils.build_kwargs_dict('interface', instance.interface) client = identity_client( session=instance.session, - region_name=instance._region_name, + region_name=instance.region_name, **kwargs ) @@ -70,7 +69,7 @@ def build_option_parser(parser): help=_('Identity API version, default=%s ' '(Env: OS_IDENTITY_API_VERSION)') % DEFAULT_API_VERSION, ) - return auth.build_auth_plugins_option_parser(parser) + return parser class IdentityClientv2(identity_client_v2.Client): diff --git a/openstackclient/image/client.py b/openstackclient/image/client.py index 0ef694f46d..1be6c7654d 100644 --- a/openstackclient/image/client.py +++ b/openstackclient/image/client.py @@ -47,15 +47,15 @@ def make_client(instance): endpoint = instance.get_endpoint_for_service_type( API_NAME, - region_name=instance._region_name, - interface=instance._interface, + region_name=instance.region_name, + interface=instance.interface, ) client = image_client( endpoint, token=instance.auth.get_token(instance.session), - cacert=instance._cacert, - insecure=instance._insecure, + cacert=instance.cacert, + insecure=not instance.verify, ) # Create the low-level API @@ -70,8 +70,8 @@ def make_client(instance): session=instance.session, endpoint=instance.get_endpoint_for_service_type( IMAGE_API_TYPE, - region_name=instance._region_name, - interface=instance._interface, + region_name=instance.region_name, + interface=instance.interface, ) ) diff --git a/openstackclient/network/client.py b/openstackclient/network/client.py index d12987dd5d..c562058da5 100644 --- a/openstackclient/network/client.py +++ b/openstackclient/network/client.py @@ -34,9 +34,9 @@ def make_client(instance): """Returns a network proxy""" prof = profile.Profile() - prof.set_region(API_NAME, instance._region_name) + prof.set_region(API_NAME, instance.region_name) prof.set_version(API_NAME, instance._api_version[API_NAME]) - prof.set_interface(API_NAME, instance._interface) + prof.set_interface(API_NAME, instance.interface) conn = connection.Connection(authenticator=instance.session.auth, verify=instance.session.verify, cert=instance.session.cert, diff --git a/openstackclient/object/client.py b/openstackclient/object/client.py index cb3ddc288f..865f18f6de 100644 --- a/openstackclient/object/client.py +++ b/openstackclient/object/client.py @@ -32,8 +32,8 @@ def make_client(instance): endpoint = instance.get_endpoint_for_service_type( 'object-store', - region_name=instance._region_name, - interface=instance._interface, + region_name=instance.region_name, + interface=instance.interface, ) client = object_store_v1.APIv1( diff --git a/openstackclient/shell.py b/openstackclient/shell.py index d6ce4ef661..7a042e1ec7 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -16,30 +16,17 @@ """Command-line interface to the OpenStack APIs""" -import argparse -import getpass import locale -import logging -import six import sys -import traceback -from cliff import app -from cliff import command -from cliff import complete -from cliff import help -from osc_lib.cli import client_config as cloud_config -from osc_lib.command import timing -from osc_lib import exceptions as exc -from osc_lib import logs -from osc_lib import utils +from osc_lib.api import auth +from osc_lib import shell from oslo_utils import importutils -from oslo_utils import strutils +import six import openstackclient from openstackclient.common import clientmanager from openstackclient.common import commandmanager -from openstackclient.i18n import _ osprofiler_profiler = importutils.try_import("osprofiler.profiler") @@ -47,47 +34,9 @@ DEFAULT_DOMAIN = 'default' -def prompt_for_password(prompt=None): - """Prompt user for a password - - Prompt for a password if stdin is a tty. - """ - - if not prompt: - prompt = 'Password: ' - pw = None - # If stdin is a tty, try prompting for the password - if hasattr(sys.stdin, 'isatty') and sys.stdin.isatty(): - # Check for Ctl-D - try: - pw = getpass.getpass(prompt) - except EOFError: - pass - # No password because we did't have a tty or nothing was entered - if not pw: - raise exc.CommandError(_("No password entered, or found via" - " --os-password or OS_PASSWORD"),) - return pw - - -class OpenStackShell(app.App): - - CONSOLE_MESSAGE_FORMAT = '%(levelname)s: %(name)s %(message)s' - - log = logging.getLogger(__name__) - timing_data = [] +class OpenStackShell(shell.OpenStackShell): def __init__(self): - # Patch command.Command to add a default auth_required = True - command.Command.auth_required = True - - # Some commands do not need authentication - help.HelpCommand.auth_required = False - complete.CompleteCommand.auth_required = False - - # Slight change to the meaning of --debug - self.DEFAULT_DEBUG_VALUE = None - self.DEFAULT_DEBUG_HELP = 'Set debug logging and traceback on errors.' super(OpenStackShell, self).__init__( description=__doc__.strip(), @@ -97,281 +46,28 @@ def __init__(self): self.api_version = {} - # Until we have command line arguments parsed, dump any stack traces - self.dump_stack_trace = True - # Assume TLS host certificate verification is enabled self.verify = True - self.client_manager = None - self.command_options = None - - self.do_profile = False - - def configure_logging(self): - """Configure logging for the app.""" - self.log_configurator = logs.LogConfigurator(self.options) - self.dump_stack_trace = self.log_configurator.dump_trace - - def run(self, argv): - ret_val = 1 - self.command_options = argv - try: - ret_val = super(OpenStackShell, self).run(argv) - return ret_val - except Exception as e: - if not logging.getLogger('').handlers: - logging.basicConfig() - if self.dump_stack_trace: - self.log.error(traceback.format_exc()) - else: - self.log.error('Exception raised: ' + str(e)) - - return ret_val - - finally: - self.log.info("END return value: %s", ret_val) - - def init_profile(self): - # NOTE(dtroyer): Remove this 'if' block when the --profile global - # option is removed - if osprofiler_profiler and self.options.old_profile: - self.log.warning( - 'The --profile option is deprecated, ' - 'please use --os-profile instead' - ) - if not self.options.profile: - self.options.profile = self.options.old_profile - - self.do_profile = osprofiler_profiler and self.options.profile - if self.do_profile: - osprofiler_profiler.init(self.options.profile) - - def close_profile(self): - if self.do_profile: - trace_id = osprofiler_profiler.get().get_base_id() - - # NOTE(dbelova): let's use warning log level to see these messages - # printed. In fact we can define custom log level here with value - # bigger than most big default one (CRITICAL) or something like - # that (PROFILE = 60 for instance), but not sure we need it here. - self.log.warning("Trace ID: %s" % trace_id) - self.log.warning("Display trace with command:\n" - "osprofiler trace show --html %s " % trace_id) - - def run_subcommand(self, argv): - self.init_profile() - try: - ret_value = super(OpenStackShell, self).run_subcommand(argv) - finally: - self.close_profile() - return ret_value - - def interact(self): - self.init_profile() - try: - ret_value = super(OpenStackShell, self).interact() - finally: - self.close_profile() - return ret_value - def build_option_parser(self, description, version): parser = super(OpenStackShell, self).build_option_parser( description, version) + parser = clientmanager.build_plugin_option_parser(parser) + parser = auth.build_auth_plugins_option_parser(parser) + return parser - # service token auth argument - parser.add_argument( - '--os-cloud', - metavar='', - dest='cloud', - default=utils.env('OS_CLOUD'), - help=_('Cloud name in clouds.yaml (Env: OS_CLOUD)'), - ) - # Global arguments - parser.add_argument( - '--os-region-name', - metavar='', - dest='region_name', - default=utils.env('OS_REGION_NAME'), - help=_('Authentication region name (Env: OS_REGION_NAME)'), - ) - parser.add_argument( - '--os-cacert', - metavar='', - dest='cacert', - default=utils.env('OS_CACERT'), - help=_('CA certificate bundle file (Env: OS_CACERT)'), - ) - parser.add_argument( - '--os-cert', - metavar='', - dest='cert', - default=utils.env('OS_CERT'), - help=_('Client certificate bundle file (Env: OS_CERT)'), - ) - parser.add_argument( - '--os-key', - metavar='', - dest='key', - default=utils.env('OS_KEY'), - help=_('Client certificate key file (Env: OS_KEY)'), - ) - verify_group = parser.add_mutually_exclusive_group() - verify_group.add_argument( - '--verify', - action='store_true', - default=None, - help=_('Verify server certificate (default)'), - ) - verify_group.add_argument( - '--insecure', - action='store_true', - default=None, - help=_('Disable server certificate verification'), - ) - parser.add_argument( - '--os-default-domain', - metavar='', - dest='default_domain', - default=utils.env( - 'OS_DEFAULT_DOMAIN', - default=DEFAULT_DOMAIN), - help=_('Default domain ID, default=%s. ' - '(Env: OS_DEFAULT_DOMAIN)') % DEFAULT_DOMAIN, - ) - parser.add_argument( - '--os-interface', - metavar='', - dest='interface', - choices=['admin', 'public', 'internal'], - default=utils.env('OS_INTERFACE'), - help=_('Select an interface type.' - ' Valid interface types: [admin, public, internal].' - ' (Env: OS_INTERFACE)'), - ) - parser.add_argument( - '--timing', - default=False, - action='store_true', - help=_("Print API call timing info"), - ) - parser.add_argument( - '--os-beta-command', - action='store_true', - help=_("Enable beta commands which are subject to change"), - ) - - # osprofiler HMAC key argument - if osprofiler_profiler: - parser.add_argument( - '--os-profile', - metavar='hmac-key', - dest='profile', - help=_('HMAC key for encrypting profiling context data'), - ) - # NOTE(dtroyer): This global option should have been named - # --os-profile as --profile interferes with at - # least one existing command option. Deprecate - # --profile and remove after Apr 2017. - parser.add_argument( - '--profile', - metavar='hmac-key', - dest='old_profile', - help=argparse.SUPPRESS, - ) + def _final_defaults(self): + super(OpenStackShell, self)._final_defaults() - return clientmanager.build_plugin_option_parser(parser) + # Set default auth type to password + self._auth_type = 'password' - def initialize_app(self, argv): - """Global app init bits: + def _load_plugins(self): + """Load plugins via stevedore - * set up API versions - * validate authentication info - * authenticate against Identity if requested + osc-lib has no opinion on what plugins should be loaded """ - - # Parent __init__ parses argv into self.options - super(OpenStackShell, self).initialize_app(argv) - self.log.info("START with options: %s", - strutils.mask_password(self.command_options)) - self.log.debug("options: %s", - strutils.mask_password(self.options)) - - # Set the default plugin to token_endpoint if url and token are given - if (self.options.url and self.options.token): - # Use service token authentication - auth_type = 'token_endpoint' - else: - auth_type = 'password' - - project_id = getattr(self.options, 'project_id', None) - project_name = getattr(self.options, 'project_name', None) - tenant_id = getattr(self.options, 'tenant_id', None) - tenant_name = getattr(self.options, 'tenant_name', None) - - # Save default domain - self.default_domain = self.options.default_domain - - # handle some v2/v3 authentication inconsistencies by just acting like - # both the project and tenant information are both present. This can - # go away if we stop registering all the argparse options together. - if project_id and not tenant_id: - self.options.tenant_id = project_id - if project_name and not tenant_name: - self.options.tenant_name = project_name - if tenant_id and not project_id: - self.options.project_id = tenant_id - if tenant_name and not project_name: - self.options.project_name = tenant_name - - # Do configuration file handling - # Ignore the default value of interface. Only if it is set later - # will it be used. - try: - cc = cloud_config.OSC_Config( - override_defaults={ - 'interface': None, - 'auth_type': auth_type, - }, - ) - except (IOError, OSError): - self.log.critical("Could not read clouds.yaml configuration file") - self.print_help_if_requested() - raise - - # TODO(thowe): Change cliff so the default value for debug - # can be set to None. - if not self.options.debug: - self.options.debug = None - self.cloud = cc.get_one_cloud( - cloud=self.options.cloud, - argparse=self.options, - ) - - self.log_configurator.configure(self.cloud) - self.dump_stack_trace = self.log_configurator.dump_trace - self.log.debug("defaults: %s", cc.defaults) - self.log.debug("cloud cfg: %s", - strutils.mask_password(self.cloud.config)) - - # Set up client TLS - # NOTE(dtroyer): --insecure is the non-default condition that - # overrides any verify setting in clouds.yaml - # so check it first, then fall back to any verify - # setting provided. - self.verify = not self.cloud.config.get( - 'insecure', - not self.cloud.config.get('verify', True), - ) - - # NOTE(dtroyer): Per bug https://bugs.launchpad.net/bugs/1447784 - # --insecure now overrides any --os-cacert setting, - # where before --insecure was ignored if --os-cacert - # was set. - if self.verify and self.cloud.cacert: - self.verify = self.cloud.cacert - # Loop through extensions to get API versions for mod in clientmanager.PLUGIN_MODULES: default_version = getattr(mod, 'DEFAULT_API_VERSION', None) @@ -406,6 +102,11 @@ def initialize_app(self, argv): {'name': api, 'version': version_opt, 'group': cmd_group} ) + def _load_commands(self): + """Load commands via cliff/stevedore + + osc-lib has no opinion on what commands should be loaded + """ # Commands that span multiple APIs self.command_manager.add_command_group( 'openstack.common') @@ -422,59 +123,19 @@ def initialize_app(self, argv): # } self.command_manager.add_command_group( 'openstack.extension') - # call InitializeXxx() here - # set up additional clients to stuff in to client_manager?? - # Handle deferred help and exit - self.print_help_if_requested() + def initialize_app(self, argv): + super(OpenStackShell, self).initialize_app(argv) + # For now we need to build our own ClientManager so re-do what + # has already been done :( + # TODO(dtroyer): remove when osc-lib is fixed self.client_manager = clientmanager.ClientManager( cli_options=self.cloud, api_version=self.api_version, - pw_func=prompt_for_password, + pw_func=shell.prompt_for_password, ) - def prepare_to_run_command(self, cmd): - """Set up auth and API versions""" - self.log.info( - 'command: %s -> %s.%s', - getattr(cmd, 'cmd_name', ''), - cmd.__class__.__module__, - cmd.__class__.__name__, - ) - if cmd.auth_required: - self.client_manager.setup_auth() - if hasattr(cmd, 'required_scope') and cmd.required_scope: - # let the command decide whether we need a scoped token - self.client_manager.validate_scope() - # Trigger the Identity client to initialize - self.client_manager.auth_ref - - def clean_up(self, cmd, result, err): - self.log.debug('clean_up %s: %s', cmd.__class__.__name__, err or '') - - # Process collected timing data - if self.options.timing: - # Get session data - self.timing_data.extend( - self.client_manager.session.get_timings(), - ) - - # Use the Timing pseudo-command to generate the output - tcmd = timing.Timing(self, self.options) - tparser = tcmd.get_parser('Timing') - - # If anything other than prettytable is specified, force csv - format = 'table' - # Check the formatter used in the actual command - if hasattr(cmd, 'formatter') \ - and cmd.formatter != cmd._formatter_plugins['table'].obj: - format = 'csv' - - sys.stdout.write('\n') - targs = tparser.parse_args(['-f', format]) - tcmd.run(targs) - def main(argv=None): if argv is None: diff --git a/openstackclient/tests/test_shell.py b/openstackclient/tests/test_shell.py index 7d0bbd12e4..87cd7f5185 100644 --- a/openstackclient/tests/test_shell.py +++ b/openstackclient/tests/test_shell.py @@ -13,14 +13,15 @@ # under the License. # -import copy -import fixtures import mock import os -import testtools +import sys + +from osc_lib.tests import utils as osc_lib_test_utils +from oslo_utils import importutils +import wrapt from openstackclient import shell -from openstackclient.tests import utils DEFAULT_AUTH_URL = "http://127.0.0.1:5000/v2.0/" @@ -116,173 +117,97 @@ '--os-interface': (DEFAULT_INTERFACE, True, True) } -auth_options = { - '--os-auth-url': (DEFAULT_AUTH_URL, True, True), - '--os-project-id': (DEFAULT_PROJECT_ID, True, True), - '--os-project-name': (DEFAULT_PROJECT_NAME, True, True), - '--os-domain-id': (DEFAULT_DOMAIN_ID, True, True), - '--os-domain-name': (DEFAULT_DOMAIN_NAME, True, True), - '--os-user-domain-id': (DEFAULT_USER_DOMAIN_ID, True, True), - '--os-user-domain-name': (DEFAULT_USER_DOMAIN_NAME, True, True), - '--os-project-domain-id': (DEFAULT_PROJECT_DOMAIN_ID, True, True), - '--os-project-domain-name': (DEFAULT_PROJECT_DOMAIN_NAME, True, True), - '--os-username': (DEFAULT_USERNAME, True, True), - '--os-password': (DEFAULT_PASSWORD, True, True), - '--os-region-name': (DEFAULT_REGION_NAME, True, True), - '--os-trust-id': ("1234", True, True), - '--os-auth-type': ("v2password", True, True), - '--os-token': (DEFAULT_TOKEN, True, True), - '--os-url': (DEFAULT_SERVICE_URL, True, True), - '--os-interface': (DEFAULT_INTERFACE, True, True), -} - - -def opt2attr(opt): - if opt.startswith('--os-'): - attr = opt[5:] - elif opt.startswith('--'): - attr = opt[2:] - else: - attr = opt - return attr.lower().replace('-', '_') - - -def opt2env(opt): - return opt[2:].upper().replace('-', '_') - - -def make_shell(): - """Create a new command shell and mock out some bits.""" - _shell = shell.OpenStackShell() - _shell.command_manager = mock.Mock() - - return _shell +# Wrap the osc_lib make_shell() function to set the shell class since +# osc-lib's TestShell class doesn't allow us to specify it yet. +# TODO(dtroyer): remove this once the shell_class_patch patch is released +# in osc-lib +def make_shell_wrapper(func, inst, args, kwargs): + if 'shell_class' not in kwargs: + kwargs['shell_class'] = shell.OpenStackShell + return func(*args, **kwargs) -def fake_execute(shell, cmd): - """Pretend to execute shell commands.""" - return shell.run(cmd.split()) +wrapt.wrap_function_wrapper( + osc_lib_test_utils, + 'make_shell', + make_shell_wrapper, +) -class EnvFixture(fixtures.Fixture): - """Environment Fixture. - This fixture replaces os.environ with provided env or an empty env. - """ +class TestShell(osc_lib_test_utils.TestShell): - def __init__(self, env=None): - self.new_env = env or {} + # Full name of the OpenStackShell class to test (cliff.app.App subclass) + shell_class_name = "openstackclient.shell.OpenStackShell" - def _setUp(self): - self.orig_env, os.environ = os.environ, self.new_env - self.addCleanup(self.revert) - - def revert(self): - os.environ = self.orig_env - - -class TestShell(utils.TestCase): + # TODO(dtroyer): remove this once the shell_class_patch patch is released + # in osc-lib + app_patch = shell_class_name def setUp(self): super(TestShell, self).setUp() - patch = "openstackclient.shell.OpenStackShell.run_subcommand" - self.cmd_patch = mock.patch(patch) - self.cmd_save = self.cmd_patch.start() - self.addCleanup(self.cmd_patch.stop) - self.app = mock.Mock("Test Shell") - - def _assert_initialize_app_arg(self, cmd_options, default_args): - """Check the args passed to initialize_app() - - The argv argument to initialize_app() is the remainder from parsing - global options declared in both cliff.app and - openstackclient.OpenStackShell build_option_parser(). Any global - options passed on the commmad line should not be in argv but in - _shell.options. - """ + # TODO(dtroyer): remove this once the shell_class_patch patch is + # released in osc-lib + self.shell_class = importutils.import_class(self.shell_class_name) + def _assert_token_endpoint_auth(self, cmd_options, default_args): with mock.patch( - "openstackclient.shell.OpenStackShell.initialize_app", + self.shell_class_name + ".initialize_app", self.app, ): - _shell, _cmd = make_shell(), cmd_options + " list project" - fake_execute(_shell, _cmd) - - self.app.assert_called_with(["list", "project"]) - for k in default_args.keys(): - self.assertEqual( - default_args[k], - vars(_shell.options)[k], - "%s does not match" % k, - ) - - def _assert_cloud_config_arg(self, cmd_options, default_args): - """Check the args passed to cloud_config.get_one_cloud() - - The argparse argument to get_one_cloud() is an argparse.Namespace - object that contains all of the options processed to this point in - initialize_app(). - """ - - cloud = mock.Mock(name="cloudy") - cloud.config = {} - self.occ_get_one = mock.Mock(return_value=cloud) - with mock.patch( - "os_client_config.config.OpenStackConfig.get_one_cloud", - self.occ_get_one, - ): - _shell, _cmd = make_shell(), cmd_options + " list project" - fake_execute(_shell, _cmd) - - opts = self.occ_get_one.call_args[1]['argparse'] - for k in default_args.keys(): - self.assertEqual( - default_args[k], - vars(opts)[k], - "%s does not match" % k, - ) - - def _assert_token_auth(self, cmd_options, default_args): - with mock.patch("openstackclient.shell.OpenStackShell.initialize_app", - self.app): - _shell, _cmd = make_shell(), cmd_options + " list role" - fake_execute(_shell, _cmd) + _shell = osc_lib_test_utils.make_shell( + shell_class=self.shell_class, + ) + _cmd = cmd_options + " list role" + osc_lib_test_utils.fake_execute(_shell, _cmd) + print("_shell: %s" % _shell) self.app.assert_called_with(["list", "role"]) self.assertEqual( default_args.get("token", ''), _shell.options.token, - "token" + "token", ) self.assertEqual( - default_args.get("auth_url", ''), - _shell.options.auth_url, - "auth_url" + default_args.get("url", ''), + _shell.options.url, + "url", ) - def _assert_token_endpoint_auth(self, cmd_options, default_args): - with mock.patch("openstackclient.shell.OpenStackShell.initialize_app", - self.app): - _shell, _cmd = make_shell(), cmd_options + " list role" - fake_execute(_shell, _cmd) + def _assert_token_auth(self, cmd_options, default_args): + with mock.patch( + self.app_patch + ".initialize_app", + self.app, + ): + _shell = osc_lib_test_utils.make_shell( + shell_class=self.shell_class, + ) + _cmd = cmd_options + " list role" + osc_lib_test_utils.fake_execute(_shell, _cmd) + print("_shell: %s" % _shell) self.app.assert_called_with(["list", "role"]) self.assertEqual( default_args.get("token", ''), _shell.options.token, - "token", + "token" ) self.assertEqual( - default_args.get("url", ''), - _shell.options.url, - "url", + default_args.get("auth_url", ''), + _shell.options.auth_url, + "auth_url" ) def _assert_cli(self, cmd_options, default_args): - with mock.patch("openstackclient.shell.OpenStackShell.initialize_app", - self.app): - _shell, _cmd = make_shell(), cmd_options + " list server" - fake_execute(_shell, _cmd) + with mock.patch( + self.shell_class_name + ".initialize_app", + self.app, + ): + _shell = osc_lib_test_utils.make_shell( + shell_class=self.shell_class, + ) + _cmd = cmd_options + " list server" + osc_lib_test_utils.fake_execute(_shell, _cmd) self.app.assert_called_with(["list", "server"]) self.assertEqual(default_args["compute_api_version"], @@ -297,39 +222,17 @@ def _assert_cli(self, cmd_options, default_args): _shell.options.os_network_api_version) -class TestShellHelp(TestShell): - """Test the deferred help flag""" - - def setUp(self): - super(TestShellHelp, self).setUp() - self.useFixture(EnvFixture()) - - @testtools.skip("skip until bug 1444983 is resolved") - def test_help_options(self): - flag = "-h list server" - kwargs = { - "deferred_help": True, - } - with mock.patch("openstackclient.shell.OpenStackShell.initialize_app", - self.app): - _shell, _cmd = make_shell(), flag - fake_execute(_shell, _cmd) - - self.assertEqual(kwargs["deferred_help"], - _shell.options.deferred_help) - - class TestShellOptions(TestShell): def setUp(self): super(TestShellOptions, self).setUp() - self.useFixture(EnvFixture()) + self.useFixture(osc_lib_test_utils.EnvFixture()) def _test_options_init_app(self, test_opts): for opt in test_opts.keys(): if not test_opts[opt][1]: continue - key = opt2attr(opt) + key = osc_lib_test_utils.opt2attr(opt) if isinstance(test_opts[opt][0], str): cmd = opt + " " + test_opts[opt][0] else: @@ -343,7 +246,7 @@ def _test_options_get_one_cloud(self, test_opts): for opt in test_opts.keys(): if not test_opts[opt][1]: continue - key = opt2attr(opt) + key = osc_lib_test_utils.opt2attr(opt) if isinstance(test_opts[opt][0], str): cmd = opt + " " + test_opts[opt][0] else: @@ -357,12 +260,12 @@ def _test_env_init_app(self, test_opts): for opt in test_opts.keys(): if not test_opts[opt][2]: continue - key = opt2attr(opt) + key = osc_lib_test_utils.opt2attr(opt) kwargs = { key: test_opts[opt][0], } env = { - opt2env(opt): test_opts[opt][0], + osc_lib_test_utils.opt2env(opt): test_opts[opt][0], } os.environ = env.copy() self._assert_initialize_app_arg("", kwargs) @@ -371,37 +274,16 @@ def _test_env_get_one_cloud(self, test_opts): for opt in test_opts.keys(): if not test_opts[opt][2]: continue - key = opt2attr(opt) + key = osc_lib_test_utils.opt2attr(opt) kwargs = { key: test_opts[opt][0], } env = { - opt2env(opt): test_opts[opt][0], + osc_lib_test_utils.opt2env(opt): test_opts[opt][0], } os.environ = env.copy() self._assert_cloud_config_arg("", kwargs) - def test_empty_auth(self): - os.environ = {} - self._assert_initialize_app_arg("", {}) - self._assert_cloud_config_arg("", {}) - - def test_global_options(self): - self._test_options_init_app(global_options) - self._test_options_get_one_cloud(global_options) - - def test_auth_options(self): - self._test_options_init_app(auth_options) - self._test_options_get_one_cloud(auth_options) - - def test_global_env(self): - self._test_env_init_app(global_options) - self._test_env_get_one_cloud(global_options) - - def test_auth_env(self): - self._test_env_init_app(auth_options) - self._test_env_get_one_cloud(auth_options) - class TestShellTokenAuthEnv(TestShell): @@ -411,7 +293,7 @@ def setUp(self): "OS_TOKEN": DEFAULT_TOKEN, "OS_AUTH_URL": DEFAULT_AUTH_URL, } - self.useFixture(EnvFixture(env.copy())) + self.useFixture(osc_lib_test_utils.EnvFixture(env.copy())) def test_env(self): flag = "" @@ -455,7 +337,7 @@ def setUp(self): "OS_TOKEN": DEFAULT_TOKEN, "OS_URL": DEFAULT_SERVICE_URL, } - self.useFixture(EnvFixture(env.copy())) + self.useFixture(osc_lib_test_utils.EnvFixture(env.copy())) def test_env(self): flag = "" @@ -463,7 +345,7 @@ def test_env(self): "token": DEFAULT_TOKEN, "url": DEFAULT_SERVICE_URL, } - self._assert_token_auth(flag, kwargs) + self._assert_token_endpoint_auth(flag, kwargs) def test_only_token(self): flag = "--os-token xyzpdq" @@ -502,85 +384,7 @@ def setUp(self): "OS_VOLUME_API_VERSION": DEFAULT_VOLUME_API_VERSION, "OS_NETWORK_API_VERSION": DEFAULT_NETWORK_API_VERSION, } - self.useFixture(EnvFixture(env.copy())) - - def test_shell_args_no_options(self): - _shell = make_shell() - with mock.patch("openstackclient.shell.OpenStackShell.initialize_app", - self.app): - fake_execute(_shell, "list user") - self.app.assert_called_with(["list", "user"]) - - def test_shell_args_ca_options(self): - _shell = make_shell() - - # NOTE(dtroyer): The commented out asserts below are the desired - # behaviour and will be uncommented when the - # handling for --verify and --insecure is fixed. - - # Default - fake_execute(_shell, "list user") - self.assertIsNone(_shell.options.verify) - self.assertIsNone(_shell.options.insecure) - self.assertEqual('', _shell.options.cacert) - self.assertTrue(_shell.verify) - - # --verify - fake_execute(_shell, "--verify list user") - self.assertTrue(_shell.options.verify) - self.assertIsNone(_shell.options.insecure) - self.assertEqual('', _shell.options.cacert) - self.assertTrue(_shell.verify) - - # --insecure - fake_execute(_shell, "--insecure list user") - self.assertIsNone(_shell.options.verify) - self.assertTrue(_shell.options.insecure) - self.assertEqual('', _shell.options.cacert) - self.assertFalse(_shell.verify) - - # --os-cacert - fake_execute(_shell, "--os-cacert foo list user") - self.assertIsNone(_shell.options.verify) - self.assertIsNone(_shell.options.insecure) - self.assertEqual('foo', _shell.options.cacert) - self.assertTrue(_shell.verify) - - # --os-cacert and --verify - fake_execute(_shell, "--os-cacert foo --verify list user") - self.assertTrue(_shell.options.verify) - self.assertIsNone(_shell.options.insecure) - self.assertEqual('foo', _shell.options.cacert) - self.assertTrue(_shell.verify) - - # --os-cacert and --insecure - # NOTE(dtroyer): Per bug https://bugs.launchpad.net/bugs/1447784 - # in this combination --insecure now overrides any - # --os-cacert setting, where before --insecure - # was ignored if --os-cacert was set. - fake_execute(_shell, "--os-cacert foo --insecure list user") - self.assertIsNone(_shell.options.verify) - self.assertTrue(_shell.options.insecure) - self.assertEqual('foo', _shell.options.cacert) - self.assertFalse(_shell.verify) - - def test_shell_args_cert_options(self): - _shell = make_shell() - - # Default - fake_execute(_shell, "list user") - self.assertEqual('', _shell.options.cert) - self.assertEqual('', _shell.options.key) - - # --os-cert - fake_execute(_shell, "--os-cert mycert list user") - self.assertEqual('mycert', _shell.options.cert) - self.assertEqual('', _shell.options.key) - - # --os-key - fake_execute(_shell, "--os-key mickey list user") - self.assertEqual('', _shell.options.cert) - self.assertEqual('mickey', _shell.options.key) + self.useFixture(osc_lib_test_utils.EnvFixture(env.copy())) def test_default_env(self): flag = "" @@ -605,220 +409,34 @@ def test_empty_env(self): } self._assert_cli(flag, kwargs) - @mock.patch("os_client_config.config.OpenStackConfig._load_config_file") - def test_shell_args_cloud_no_vendor(self, config_mock): - config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_1)) - _shell = make_shell() - - fake_execute( - _shell, - "--os-cloud scc list user", - ) - self.assertEqual( - 'scc', - _shell.cloud.name, - ) - - # These come from clouds.yaml - self.assertEqual( - DEFAULT_AUTH_URL, - _shell.cloud.config['auth']['auth_url'], - ) - self.assertEqual( - DEFAULT_PROJECT_NAME, - _shell.cloud.config['auth']['project_name'], - ) - self.assertEqual( - 'zaphod', - _shell.cloud.config['auth']['username'], - ) - self.assertEqual( - 'occ-cloud', - _shell.cloud.config['region_name'], - ) - self.assertEqual( - 'glazed', - _shell.cloud.config['donut'], - ) - self.assertEqual( - 'public', - _shell.cloud.config['interface'], - ) - - @mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file") - @mock.patch("os_client_config.config.OpenStackConfig._load_config_file") - def test_shell_args_cloud_public(self, config_mock, public_mock): - config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_2)) - public_mock.return_value = ('file.yaml', copy.deepcopy(PUBLIC_1)) - _shell = make_shell() - - fake_execute( - _shell, - "--os-cloud megacloud list user", - ) - self.assertEqual( - 'megacloud', - _shell.cloud.name, - ) - - # These come from clouds-public.yaml - self.assertEqual( - DEFAULT_AUTH_URL, - _shell.cloud.config['auth']['auth_url'], - ) - self.assertEqual( - 'cake', - _shell.cloud.config['donut'], - ) - - # These come from clouds.yaml - self.assertEqual( - 'heart-o-gold', - _shell.cloud.config['auth']['project_name'], - ) - self.assertEqual( - 'zaphod', - _shell.cloud.config['auth']['username'], - ) - self.assertEqual( - 'occ-cloud', - _shell.cloud.config['region_name'], - ) - - self.assertEqual('mycert', _shell.cloud.config['cert']) - self.assertEqual('mickey', _shell.cloud.config['key']) - - @mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file") - @mock.patch("os_client_config.config.OpenStackConfig._load_config_file") - def test_shell_args_precedence(self, config_mock, vendor_mock): - config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_2)) - vendor_mock.return_value = ('file.yaml', copy.deepcopy(PUBLIC_1)) - _shell = make_shell() - - # Test command option overriding config file value - fake_execute( - _shell, - "--os-cloud megacloud --os-region-name krikkit list user", - ) - self.assertEqual( - 'megacloud', - _shell.cloud.name, - ) - - # These come from clouds-public.yaml - self.assertEqual( - DEFAULT_AUTH_URL, - _shell.cloud.config['auth']['auth_url'], - ) - self.assertEqual( - 'cake', - _shell.cloud.config['donut'], - ) - - # These come from clouds.yaml - self.assertEqual( - 'heart-o-gold', - _shell.cloud.config['auth']['project_name'], - ) - self.assertEqual( - 'zaphod', - _shell.cloud.config['auth']['username'], - ) - self.assertEqual( - 'krikkit', - _shell.cloud.config['region_name'], - ) - - -class TestShellCliEnv(TestShell): + +class TestShellArgV(TestShell): + """Test the deferred help flag""" def setUp(self): - super(TestShellCliEnv, self).setUp() - env = { - 'OS_REGION_NAME': 'occ-env', - } - self.useFixture(EnvFixture(env.copy())) - - @mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file") - @mock.patch("os_client_config.config.OpenStackConfig._load_config_file") - def test_shell_args_precedence_1(self, config_mock, vendor_mock): - config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_2)) - vendor_mock.return_value = ('file.yaml', copy.deepcopy(PUBLIC_1)) - _shell = make_shell() - - # Test env var - fake_execute( - _shell, - "--os-cloud megacloud list user", - ) - self.assertEqual( - 'megacloud', - _shell.cloud.name, - ) - - # These come from clouds-public.yaml - self.assertEqual( - DEFAULT_AUTH_URL, - _shell.cloud.config['auth']['auth_url'], - ) - self.assertEqual( - 'cake', - _shell.cloud.config['donut'], - ) - - # These come from clouds.yaml - self.assertEqual( - 'heart-o-gold', - _shell.cloud.config['auth']['project_name'], - ) - self.assertEqual( - 'zaphod', - _shell.cloud.config['auth']['username'], - ) - self.assertEqual( - 'occ-env', - _shell.cloud.config['region_name'], - ) - - @mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file") - @mock.patch("os_client_config.config.OpenStackConfig._load_config_file") - def test_shell_args_precedence_2(self, config_mock, vendor_mock): - config_mock.return_value = ('file.yaml', copy.deepcopy(CLOUD_2)) - vendor_mock.return_value = ('file.yaml', copy.deepcopy(PUBLIC_1)) - _shell = make_shell() - - # Test command option overriding config file value - fake_execute( - _shell, - "--os-cloud megacloud --os-region-name krikkit list user", - ) - self.assertEqual( - 'megacloud', - _shell.cloud.name, - ) - - # These come from clouds-public.yaml - self.assertEqual( - DEFAULT_AUTH_URL, - _shell.cloud.config['auth']['auth_url'], - ) - self.assertEqual( - 'cake', - _shell.cloud.config['donut'], - ) - - # These come from clouds.yaml - self.assertEqual( - 'heart-o-gold', - _shell.cloud.config['auth']['project_name'], - ) - self.assertEqual( - 'zaphod', - _shell.cloud.config['auth']['username'], - ) - - # These come from the command line - self.assertEqual( - 'krikkit', - _shell.cloud.config['region_name'], - ) + super(TestShellArgV, self).setUp() + + def test_shell_argv(self): + """Test argv decoding + + Python 2 does nothing with argv while Python 3 decodes it into + Unicode before we ever see it. We manually decode when running + under Python 2 so verify that we get the right argv types. + + Use the argv supplied by the test runner so we get actual Python + runtime behaviour; we only need to check the type of argv[0] + which will alwyas be present. + """ + + with mock.patch( + self.shell_class_name + ".run", + self.app, + ): + # Ensure type gets through unmolested through shell.main() + argv = sys.argv + shell.main(sys.argv) + self.assertEqual(type(argv[0]), type(self.app.call_args[0][0][0])) + + # When shell.main() gets sys.argv itself it should be decoded + shell.main() + self.assertEqual(type(u'x'), type(self.app.call_args[0][0][0])) diff --git a/openstackclient/volume/client.py b/openstackclient/volume/client.py index 11f20673da..ade5a95f55 100644 --- a/openstackclient/volume/client.py +++ b/openstackclient/volume/client.py @@ -57,13 +57,13 @@ def make_client(instance): extensions = [extension.Extension('list_extensions', list_extensions)] # Remember interface only if it is set - kwargs = utils.build_kwargs_dict('endpoint_type', instance._interface) + kwargs = utils.build_kwargs_dict('endpoint_type', instance.interface) client = volume_client( session=instance.session, extensions=extensions, http_log_debug=http_log_debug, - region_name=instance._region_name, + region_name=instance.region_name, **kwargs ) diff --git a/test-requirements.txt b/test-requirements.txt index 6a98478f09..271554d1d5 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -17,6 +17,7 @@ testtools>=1.4.0 # MIT tempest>=12.1.0 # Apache-2.0 osprofiler>=1.3.0 # Apache-2.0 bandit>=1.0.1 # Apache-2.0 +wrapt>=1.7.0 # BSD License # Install these to generate sphinx autodocs aodhclient>=0.5.0 # Apache-2.0 From 63f8018fe370f8908cd4d3da3d76ca0a4693a88d Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Fri, 5 Aug 2016 20:28:07 +0000 Subject: [PATCH 1157/3095] Updated from global requirements Change-Id: Ic33376d005aa07d377af183ba3537c6c1ee22c21 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3cd361fc14..5c840e27b3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ Babel>=2.3.4 # BSD cliff!=1.16.0,!=1.17.0,>=1.15.0 # Apache-2.0 keystoneauth1>=2.10.0 # Apache-2.0 openstacksdk>=0.9.0 # Apache-2.0 -os-client-config>=1.13.1 # Apache-2.0 +os-client-config!=1.19.0,>=1.13.1 # Apache-2.0 osc-lib>=0.4.0 # Apache-2.0 oslo.config>=3.14.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 From 3202fefc654bc32fd7e02b12b76a4ea55f7f53c0 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Fri, 5 Aug 2016 18:54:29 +0800 Subject: [PATCH 1158/3095] Support multi REST API calls error handling for "volume set" command Support the error handling follow the rule in doc/source/command-errors.rst Also add a unit test for testing the error handling Change-Id: I98064f4b8c1dc17eb3874f7b25c827a568463c0f --- .../tests/volume/v2/test_volume.py | 21 +++++++ openstackclient/volume/v2/volume.py | 55 ++++++++++++++----- 2 files changed, 62 insertions(+), 14 deletions(-) diff --git a/openstackclient/tests/volume/v2/test_volume.py b/openstackclient/tests/volume/v2/test_volume.py index c2740cab77..1bb5c19237 100644 --- a/openstackclient/tests/volume/v2/test_volume.py +++ b/openstackclient/tests/volume/v2/test_volume.py @@ -860,6 +860,27 @@ def test_volume_set_state(self): self.new_volume.id, 'error') self.assertIsNone(result) + def test_volume_set_state_failed(self): + self.volumes_mock.reset_state.side_effect = exceptions.CommandError() + arglist = [ + '--state', 'error', + self.new_volume.id + ] + verifylist = [ + ('state', 'error'), + ('volume', self.new_volume.id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('One or more of the set operations failed', + str(e)) + self.volumes_mock.reset_state.assert_called_with( + self.new_volume.id, 'error') + class TestVolumeShow(TestVolume): diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index aee18e4f26..bd201e0041 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -394,24 +394,42 @@ def take_action(self, parsed_args): volume_client = self.app.client_manager.volume volume = utils.find_resource(volume_client.volumes, parsed_args.volume) + result = 0 if parsed_args.size: - if volume.status != 'available': - LOG.error(_("Volume is in %s state, it must be available " - "before size can be extended"), volume.status) - return - if parsed_args.size <= volume.size: - LOG.error(_("New size must be greater than %s GB"), - volume.size) - return - volume_client.volumes.extend(volume.id, parsed_args.size) + try: + if volume.status != 'available': + msg = (_("Volume is in %s state, it must be available " + "before size can be extended"), volume.status) + raise exceptions.CommandError(msg) + if parsed_args.size <= volume.size: + msg = _("New size must be greater than %s GB"), volume.size + raise exceptions.CommandError(msg) + volume_client.volumes.extend(volume.id, parsed_args.size) + except Exception as e: + LOG.error(_("Failed to set volume size: %s"), e) + result += 1 if parsed_args.property: - volume_client.volumes.set_metadata(volume.id, parsed_args.property) + try: + volume_client.volumes.set_metadata( + volume.id, parsed_args.property) + except Exception as e: + LOG.error(_("Failed to set volume property: %s"), e) + result += 1 if parsed_args.image_property: - volume_client.volumes.set_image_metadata( - volume.id, parsed_args.image_property) + try: + volume_client.volumes.set_image_metadata( + volume.id, parsed_args.image_property) + except Exception as e: + LOG.error(_("Failed to set image property: %s"), e) + result += 1 if parsed_args.state: - volume_client.volumes.reset_state(volume.id, parsed_args.state) + try: + volume_client.volumes.reset_state( + volume.id, parsed_args.state) + except Exception as e: + LOG.error(_("Failed to set volume state: %s"), e) + result += 1 kwargs = {} if parsed_args.name: @@ -419,7 +437,16 @@ def take_action(self, parsed_args): if parsed_args.description: kwargs['display_description'] = parsed_args.description if kwargs: - volume_client.volumes.update(volume.id, **kwargs) + try: + volume_client.volumes.update(volume.id, **kwargs) + except Exception as e: + LOG.error(_("Failed to update volume display name " + "or display description: %s"), e) + result += 1 + + if result > 0: + raise exceptions.CommandError(_("One or more of the " + "set operations failed")) class ShowVolume(command.ShowOne): From 0b91368164acc596bf97fe4073083e26892f5b1a Mon Sep 17 00:00:00 2001 From: Cao Xuan Hoang Date: Mon, 8 Aug 2016 15:02:06 +0700 Subject: [PATCH 1159/3095] Add --ip-version filtering option to subnet.rst When executed "openstack subnet list --help" we can see this is supported for --ip-version filtering option. But this option is missing in the rst document. Change-Id: Ie5443f1da086a1ad455fbeaa848b50a0d9d4b290 Closes-Bug: #1610872 --- doc/source/command-objects/subnet.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/command-objects/subnet.rst b/doc/source/command-objects/subnet.rst index f0d5e90aa7..ba80037394 100644 --- a/doc/source/command-objects/subnet.rst +++ b/doc/source/command-objects/subnet.rst @@ -151,6 +151,7 @@ List subnets os subnet list [--long] + [--ip-version {4,6}] .. option:: --long From 831546fb9e4150074baecee9470a0b8d681e0f86 Mon Sep 17 00:00:00 2001 From: Cao Xuan Hoang Date: Tue, 9 Aug 2016 11:25:35 +0700 Subject: [PATCH 1160/3095] Add '--dhcp' and '--no-dhcp' options to os subnet list cmd This patch adds '--dhcp' and '--no-dhcp' options to filter subnets resulted by os subnet list command. Change-Id: Ib574cc54594845bc5c5afc38bf44e3b224d33b17 Partial-Bug: #1610883 --- doc/source/command-objects/subnet.rst | 9 ++++++ openstackclient/network/v2/subnet.py | 15 +++++++++ .../tests/network/v2/test_subnet.py | 32 +++++++++++++++++++ .../notes/bug-1610883-e6345c32a35cc290.yaml | 7 ++++ 4 files changed, 63 insertions(+) create mode 100644 releasenotes/notes/bug-1610883-e6345c32a35cc290.yaml diff --git a/doc/source/command-objects/subnet.rst b/doc/source/command-objects/subnet.rst index ba80037394..e8740e5348 100644 --- a/doc/source/command-objects/subnet.rst +++ b/doc/source/command-objects/subnet.rst @@ -152,6 +152,7 @@ List subnets os subnet list [--long] [--ip-version {4,6}] + [--dhcp | --no-dhcp] .. option:: --long @@ -162,6 +163,14 @@ List subnets List only subnets of given IP version in output. Allowed values for IP version are 4 and 6. +.. option:: --dhcp + + List subnets which have DHCP enabled + +.. option:: --no-dhcp + + List subnets which have DHCP disabled + subnet set ---------- diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index f26f680426..22452809cf 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -341,12 +341,27 @@ def get_parser(self, prog_name): help=_("List only subnets of given IP version in output." "Allowed values for IP version are 4 and 6."), ) + dhcp_enable_group = parser.add_mutually_exclusive_group() + dhcp_enable_group.add_argument( + '--dhcp', + action='store_true', + help=_("List subnets which have DHCP enabled") + ) + dhcp_enable_group.add_argument( + '--no-dhcp', + action='store_true', + help=_("List subnets which have DHCP disabled") + ) return parser def take_action(self, parsed_args): filters = {} if parsed_args.ip_version: filters['ip_version'] = parsed_args.ip_version + if parsed_args.dhcp: + filters['enable_dhcp'] = True + elif parsed_args.no_dhcp: + filters['enable_dhcp'] = False data = self.app.client_manager.network.subnets(**filters) headers = ('ID', 'Name', 'Network', 'Subnet') diff --git a/openstackclient/tests/network/v2/test_subnet.py b/openstackclient/tests/network/v2/test_subnet.py index e7f3e7485a..ba757c9835 100644 --- a/openstackclient/tests/network/v2/test_subnet.py +++ b/openstackclient/tests/network/v2/test_subnet.py @@ -584,6 +584,38 @@ def test_subnet_list_ip_version(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + def test_subnet_list_dhcp(self): + arglist = [ + '--dhcp', + ] + verifylist = [ + ('dhcp', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'enable_dhcp': True} + + self.network.subnets.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_subnet_list_no_dhcp(self): + arglist = [ + '--no-dhcp', + ] + verifylist = [ + ('no_dhcp', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'enable_dhcp': False} + + self.network.subnets.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + class TestSetSubnet(TestSubnet): diff --git a/releasenotes/notes/bug-1610883-e6345c32a35cc290.yaml b/releasenotes/notes/bug-1610883-e6345c32a35cc290.yaml new file mode 100644 index 0000000000..710ee39791 --- /dev/null +++ b/releasenotes/notes/bug-1610883-e6345c32a35cc290.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Make ``subnet list`` command supports listing up subnets with + dhcp enabled/disabled by adding ``--dhcp`` and ``--no-dhcp`` + options to the command. + [Bug `1610883 `_] From 722be75f9cffec7242d893cac20d40c570af32d6 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Wed, 10 Aug 2016 15:26:45 +0800 Subject: [PATCH 1161/3095] Implement "network agent set" command Add "network agent set" command in network v2 to set network agent properties. Also add the unit test, doc, functional test and release note. Change-Id: Iebaee4c60f8c6b43f538c1b82a38b93178b9ce53 Implements: bp implement-network-agents Co-Authored-By: Michael Gugino --- doc/source/command-objects/network-agent.rst | 30 ++++++++ .../tests/network/v2/test_network_agent.py | 9 +++ openstackclient/network/v2/network_agent.py | 41 ++++++++++ .../tests/network/v2/test_network_agent.py | 75 +++++++++++++++++++ ...ement-network-agents-5eba48796318f094.yaml | 4 +- setup.cfg | 1 + 6 files changed, 158 insertions(+), 2 deletions(-) diff --git a/doc/source/command-objects/network-agent.rst b/doc/source/command-objects/network-agent.rst index 32cb371cc1..15195a3398 100644 --- a/doc/source/command-objects/network-agent.rst +++ b/doc/source/command-objects/network-agent.rst @@ -36,6 +36,36 @@ List network agents os network agent list +network agent set +----------------- + +Set network agent properties + +.. program:: network agent set +.. code:: bash + + os network agent set + [--description ] + [--enable | --disable] + + +.. option:: --description + + Set network agent description + +.. option:: --enable + + Enable network agent + +.. option:: --disable + + Disable network agent + +.. _network_agent_set-network-agent: +.. describe:: + + Network agent to modify (ID only) + network agent show ------------------ diff --git a/functional/tests/network/v2/test_network_agent.py b/functional/tests/network/v2/test_network_agent.py index e01ead4229..f574c50c94 100644 --- a/functional/tests/network/v2/test_network_agent.py +++ b/functional/tests/network/v2/test_network_agent.py @@ -30,3 +30,12 @@ def test_network_agent_show(self): opts = self.get_opts(self.FIELDS) raw_output = self.openstack('network agent show ' + self.IDs[0] + opts) self.assertEqual(self.IDs[0] + "\n", raw_output) + + def test_network_agent_set(self): + opts = self.get_opts(['admin_state_up']) + self.openstack('network agent set --disable ' + self.IDs[0]) + raw_output = self.openstack('network agent show ' + self.IDs[0] + opts) + self.assertEqual("DOWN\n", raw_output) + self.openstack('network agent set --enable ' + self.IDs[0]) + raw_output = self.openstack('network agent show ' + self.IDs[0] + opts) + self.assertEqual("UP\n", raw_output) diff --git a/openstackclient/network/v2/network_agent.py b/openstackclient/network/v2/network_agent.py index 1fb70a50a0..fdb34bb79b 100644 --- a/openstackclient/network/v2/network_agent.py +++ b/openstackclient/network/v2/network_agent.py @@ -99,6 +99,47 @@ def take_action(self, parsed_args): ) for s in data)) +class SetNetworkAgent(command.Command): + """Set network agent properties""" + + def get_parser(self, prog_name): + parser = super(SetNetworkAgent, self).get_parser(prog_name) + parser.add_argument( + 'network_agent', + metavar="", + help=(_("Network agent to modify (ID only)")) + ) + parser.add_argument( + '--description', + metavar='', + help=_("Set network agent description") + ) + admin_group = parser.add_mutually_exclusive_group() + admin_group.add_argument( + '--enable', + action='store_true', + help=_("Enable network agent") + ) + admin_group.add_argument( + '--disable', + action='store_true', + help=_("Disable network agent") + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.get_agent(parsed_args.network_agent, ignore_missing=False) + attrs = {} + if parsed_args.description is not None: + attrs['description'] = str(parsed_args.description) + if parsed_args.enable: + attrs['admin_state_up'] = True + if parsed_args.disable: + attrs['admin_state_up'] = False + client.update_agent(obj, **attrs) + + class ShowNetworkAgent(command.ShowOne): """Display network agent details""" diff --git a/openstackclient/tests/network/v2/test_network_agent.py b/openstackclient/tests/network/v2/test_network_agent.py index 3cf9a53081..269d4e1d6d 100644 --- a/openstackclient/tests/network/v2/test_network_agent.py +++ b/openstackclient/tests/network/v2/test_network_agent.py @@ -160,6 +160,81 @@ def test_network_agents_list(self): self.assertEqual(self.data, list(data)) +class TestSetNetworkAgent(TestNetworkAgent): + + _network_agent = ( + network_fakes.FakeNetworkAgent.create_one_network_agent()) + + def setUp(self): + super(TestSetNetworkAgent, self).setUp() + self.network.update_agent = mock.Mock(return_value=None) + self.network.get_agent = mock.Mock(return_value=self._network_agent) + + # Get the command object to test + self.cmd = network_agent.SetNetworkAgent(self.app, self.namespace) + + def test_set_nothing(self): + arglist = [ + self._network_agent.id, + ] + verifylist = [ + ('network_agent', self._network_agent.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = {} + self.network.update_agent.assert_called_once_with( + self._network_agent, **attrs) + self.assertIsNone(result) + + def test_set_all(self): + arglist = [ + '--description', 'new_description', + '--enable', + self._network_agent.id, + ] + verifylist = [ + ('description', 'new_description'), + ('enable', True), + ('disable', False), + ('network_agent', self._network_agent.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = { + 'description': 'new_description', + 'admin_state_up': True, + } + self.network.update_agent.assert_called_once_with( + self._network_agent, **attrs) + self.assertIsNone(result) + + def test_set_with_disable(self): + arglist = [ + '--disable', + self._network_agent.id, + ] + verifylist = [ + ('enable', False), + ('disable', True), + ('network_agent', self._network_agent.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = { + 'admin_state_up': False, + } + self.network.update_agent.assert_called_once_with( + self._network_agent, **attrs) + self.assertIsNone(result) + + class TestShowNetworkAgent(TestNetworkAgent): _network_agent = ( diff --git a/releasenotes/notes/bp-implement-network-agents-5eba48796318f094.yaml b/releasenotes/notes/bp-implement-network-agents-5eba48796318f094.yaml index 4ca2510367..d9224c311f 100644 --- a/releasenotes/notes/bp-implement-network-agents-5eba48796318f094.yaml +++ b/releasenotes/notes/bp-implement-network-agents-5eba48796318f094.yaml @@ -1,5 +1,5 @@ --- features: - - Add ``network agent delete``, ``network agent list`` and - ``network agent show`` commands. + - Add ``network agent delete``, ``network agent list``, ``network agent show`` + and ``network agent set`` commands. [Blueprint `implement-network-agents `_] diff --git a/setup.cfg b/setup.cfg index d0c2648066..72a4589bea 100644 --- a/setup.cfg +++ b/setup.cfg @@ -357,6 +357,7 @@ openstack.network.v2 = network_agent_delete = openstackclient.network.v2.network_agent:DeleteNetworkAgent network_agent_list = openstackclient.network.v2.network_agent:ListNetworkAgent + network_agent_set = openstackclient.network.v2.network_agent:SetNetworkAgent network_agent_show = openstackclient.network.v2.network_agent:ShowNetworkAgent network_create = openstackclient.network.v2.network:CreateNetwork From b3248fb0bdf52905b4c241d561f9e8738fc56eba Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Wed, 10 Aug 2016 08:51:25 -0500 Subject: [PATCH 1162/3095] Fix OSC identity v3 functional tests The OSC identity v3 functional tests are failing due to [1] which added 'password_expires_at' to the user object. This patch set fixes the tests by updating user object fields list to include 'password_expires_at'. [1] https://review.openstack.org/#/c/333360/ Change-Id: Id4b060115d4270899ca0af2dc7b67ee723388e31 --- functional/tests/identity/v3/common.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/functional/tests/identity/v3/common.py b/functional/tests/identity/v3/common.py index c988d33638..47019c5fc8 100644 --- a/functional/tests/identity/v3/common.py +++ b/functional/tests/identity/v3/common.py @@ -27,7 +27,8 @@ class IdentityTests(test.TestCase): GROUP_FIELDS = ['description', 'domain_id', 'id', 'name', 'links'] TOKEN_FIELDS = ['expires', 'id', 'project_id', 'user_id'] USER_FIELDS = ['email', 'enabled', 'id', 'name', 'name', - 'domain_id', 'default_project_id', 'description'] + 'domain_id', 'default_project_id', 'description', + 'password_expires_at'] PROJECT_FIELDS = ['description', 'id', 'domain_id', 'is_domain', 'enabled', 'name', 'parent_id', 'links'] ROLE_FIELDS = ['id', 'name', 'links', 'domain_id'] From 5eb7e626b18b033f97f3cf10f2791529f9d75789 Mon Sep 17 00:00:00 2001 From: Henry Nash Date: Tue, 23 Feb 2016 11:42:40 +0000 Subject: [PATCH 1163/3095] Add support for domain specific roles A role entity can now be specified as domain specific. Closes-bug: #1606105 Change-Id: I564cf3da1d61f5bfcf85be591480d2f5c8d694a0 --- .../command-objects/role-assignment.rst | 8 + doc/source/command-objects/role.rst | 47 ++- openstackclient/identity/common.py | 10 + openstackclient/identity/v3/role.py | 103 ++++++- .../identity/v3/role_assignment.py | 12 + openstackclient/tests/identity/v3/fakes.py | 14 + .../tests/identity/v3/test_role.py | 272 +++++++++++++++++- .../tests/identity/v3/test_role_assignment.py | 53 ++++ .../notes/bug-1606105-ca06b230e22ab5c6.yaml | 4 + 9 files changed, 505 insertions(+), 18 deletions(-) create mode 100644 releasenotes/notes/bug-1606105-ca06b230e22ab5c6.yaml diff --git a/doc/source/command-objects/role-assignment.rst b/doc/source/command-objects/role-assignment.rst index ef1b22eeeb..dc970ed5b0 100644 --- a/doc/source/command-objects/role-assignment.rst +++ b/doc/source/command-objects/role-assignment.rst @@ -14,6 +14,7 @@ List role assignments os role assignment list [--role ] + [--role-domain ] [--user ] [--user-domain ] [--group ] @@ -31,6 +32,13 @@ List role assignments .. versionadded:: 3 +.. option:: --role-domain + + Domain the role belongs to (name or ID). + This can be used in case collisions between role names exist. + + .. versionadded:: 3 + .. option:: --user User to filter (name or ID) diff --git a/doc/source/command-objects/role.rst b/doc/source/command-objects/role.rst index 5542a35b9c..2ff1f13a89 100644 --- a/doc/source/command-objects/role.rst +++ b/doc/source/command-objects/role.rst @@ -15,6 +15,7 @@ Add role assignment to a user or group in a project or domain os role add --domain | --project [--project-domain ] --user [--user-domain ] | --group [--group-domain ] + --role-domain --inherited @@ -65,6 +66,13 @@ Add role assignment to a user or group in a project or domain .. versionadded:: 3 +.. option:: --role-domain + + Domain the role belongs to (name or ID). + This must be specified when the name of a domain specific role is used. + + .. versionadded:: 3 + .. describe:: Role to add to : (name or ID) @@ -79,8 +87,15 @@ Create new role os role create [--or-show] + [--domain ] +.. option:: --domain + + Domain the role belongs to (name or ID). + + .. versionadded:: 3 + .. option:: --or-show Return existing role @@ -101,11 +116,18 @@ Delete role(s) os role delete [ ...] + [--domain ] .. describe:: Role to delete (name or ID) +.. option:: --domain + + Domain the role belongs to (name or ID). + + .. versionadded:: 3 + role list --------- @@ -123,7 +145,8 @@ List roles Filter roles by (name or ID) - (Deprecated, please use ``role assignment list`` instead) + (Deprecated if being used to list assignments in conjunction with the + ``--user ``, option, please use ``role assignment list`` instead) .. option:: --project @@ -189,6 +212,7 @@ Remove role assignment from domain/project : user/group os role remove --domain | --project [--project-domain ] --user [--user-domain ] | --group [--group-domain ] + --role-domain --inherited @@ -239,6 +263,13 @@ Remove role assignment from domain/project : user/group .. versionadded:: 3 +.. option:: --role-domain + + Domain the role belongs to (name or ID). + This must be specified when the name of a domain specific role is used. + + .. versionadded:: 3 + .. describe:: Role to remove (name or ID) @@ -255,12 +286,19 @@ Set role properties os role set [--name ] + [--domain ] .. option:: --name Set role name +.. option:: --domain + + Domain the role belongs to (name or ID). + + .. versionadded:: 3 + .. describe:: Role to modify (name or ID) @@ -274,8 +312,15 @@ Display role details .. code:: bash os role show + [--domain ] +.. option:: --domain + + Domain the role belongs to (name or ID). + + .. versionadded:: 3 + .. describe:: Role to display (name or ID) diff --git a/openstackclient/identity/common.py b/openstackclient/identity/common.py index 1e40f39687..1f645b7d9c 100644 --- a/openstackclient/identity/common.py +++ b/openstackclient/identity/common.py @@ -201,6 +201,16 @@ def add_project_domain_option_to_parser(parser): ) +def add_role_domain_option_to_parser(parser): + parser.add_argument( + '--role-domain', + metavar='', + help=_('Domain the role belongs to (name or ID). ' + 'This must be specified when the name of a domain specific ' + 'role is used.'), + ) + + def add_inherited_option_to_parser(parser): parser.add_argument( '--inherited', diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index e8a03ff3db..8b91174626 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -109,7 +109,7 @@ def _process_identity_and_resource_options(parsed_args, class AddRole(command.Command): - """Adds a role to a user or group on a domain or project""" + """Adds a role assignment to a user or group on a domain or project""" def get_parser(self, prog_name): parser = super(AddRole, self).get_parser(prog_name) @@ -119,6 +119,7 @@ def get_parser(self, prog_name): help=_('Role to add to (name or ID)'), ) _add_identity_and_resource_options_to_parser(parser) + common.add_role_domain_option_to_parser(parser) return parser def take_action(self, parsed_args): @@ -127,9 +128,15 @@ def take_action(self, parsed_args): if (not parsed_args.user and not parsed_args.domain and not parsed_args.group and not parsed_args.project): return + + domain_id = None + if parsed_args.role_domain: + domain_id = common.find_domain(identity_client, + parsed_args.role_domain).id role = utils.find_resource( identity_client.roles, parsed_args.role, + domain_id=domain_id ) kwargs = _process_identity_and_resource_options( @@ -153,6 +160,11 @@ def get_parser(self, prog_name): metavar='', help=_('New role name'), ) + parser.add_argument( + '--domain', + metavar='', + help=_('Domain the role belongs to (name or ID)'), + ) parser.add_argument( '--or-show', action='store_true', @@ -163,12 +175,20 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity + domain_id = None + if parsed_args.domain: + domain_id = common.find_domain(identity_client, + parsed_args.domain).id + try: - role = identity_client.roles.create(name=parsed_args.name) + role = identity_client.roles.create( + name=parsed_args.name, domain=domain_id) + except ks_exc.Conflict: if parsed_args.or_show: role = utils.find_resource(identity_client.roles, - parsed_args.name) + parsed_args.name, + domain_id=domain_id) LOG.info(_('Returning existing role %s'), role.name) else: raise @@ -188,15 +208,26 @@ def get_parser(self, prog_name): nargs="+", help=_('Role(s) to delete (name or ID)'), ) + parser.add_argument( + '--domain', + metavar='', + help=_('Domain the role belongs to (name or ID)'), + ) return parser def take_action(self, parsed_args): identity_client = self.app.client_manager.identity + domain_id = None + if parsed_args.domain: + domain_id = common.find_domain(identity_client, + parsed_args.domain).id + for role in parsed_args.roles: role_obj = utils.find_resource( identity_client.roles, role, + domain_id=domain_id ) identity_client.roles.delete(role_obj.id) @@ -206,6 +237,18 @@ class ListRole(command.Lister): def get_parser(self, prog_name): parser = super(ListRole, self).get_parser(prog_name) + + # TODO(henry-nash): The use of the List Role command to list + # assignments (as well as roles) has been deprecated. In order + # to support domain specific roles, we are overriding the domain + # option to allow specification of the domain for the role. This does + # not conflict with any existing commands, since for the deprecated + # assignments listing you were never allowed to only specify a domain + # (you also needed to specify a user). + # + # Once we have removed the deprecated options entirely, we must + # replace the call to _add_identity_and_resource_options_to_parser() + # below with just adding the domain option into the parser. _add_identity_and_resource_options_to_parser(parser) return parser @@ -239,8 +282,14 @@ def take_action(self, parsed_args): # no user or group specified, list all roles in the system if not parsed_args.user and not parsed_args.group: - columns = ('ID', 'Name') - data = identity_client.roles.list() + if not parsed_args.domain: + columns = ('ID', 'Name') + data = identity_client.roles.list() + else: + columns = ('ID', 'Name', 'Domain') + data = identity_client.roles.list(domain_id=domain.id) + for role in data: + role.domain = domain.name elif parsed_args.user and parsed_args.domain: columns = ('ID', 'Name', 'Domain', 'User') data = identity_client.roles.list( @@ -322,7 +371,7 @@ def take_action(self, parsed_args): class RemoveRole(command.Command): - """Remove role from domain/project : user/group""" + """Removes a role assignment from domain/project : user/group""" def get_parser(self, prog_name): parser = super(RemoveRole, self).get_parser(prog_name) @@ -332,6 +381,8 @@ def get_parser(self, prog_name): help=_('Role to remove (name or ID)'), ) _add_identity_and_resource_options_to_parser(parser) + common.add_role_domain_option_to_parser(parser) + return parser def take_action(self, parsed_args): @@ -342,9 +393,15 @@ def take_action(self, parsed_args): sys.stderr.write(_("Incorrect set of arguments provided. " "See openstack --help for more details\n")) return + + domain_id = None + if parsed_args.role_domain: + domain_id = common.find_domain(identity_client, + parsed_args.role_domain).id role = utils.find_resource( identity_client.roles, parsed_args.role, + domain_id=domain_id ) kwargs = _process_identity_and_resource_options( @@ -367,6 +424,11 @@ def get_parser(self, prog_name): metavar='', help=_('Role to modify (name or ID)'), ) + parser.add_argument( + '--domain', + metavar='', + help=_('Domain the role belongs to (name or ID)'), + ) parser.add_argument( '--name', metavar='', @@ -377,10 +439,14 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - role = utils.find_resource( - identity_client.roles, - parsed_args.role, - ) + domain_id = None + if parsed_args.domain: + domain_id = common.find_domain(identity_client, + parsed_args.domain).id + + role = utils.find_resource(identity_client.roles, + parsed_args.role, + domain_id=domain_id) identity_client.roles.update(role.id, name=parsed_args.name) @@ -395,15 +461,24 @@ def get_parser(self, prog_name): metavar='', help=_('Role to display (name or ID)'), ) + parser.add_argument( + '--domain', + metavar='', + help=_('Domain the role belongs to (name or ID)'), + ) return parser def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - role = utils.find_resource( - identity_client.roles, - parsed_args.role, - ) + domain_id = None + if parsed_args.domain: + domain_id = common.find_domain(identity_client, + parsed_args.domain).id + + role = utils.find_resource(identity_client.roles, + parsed_args.role, + domain_id=domain_id) role._info.pop('links') return zip(*sorted(six.iteritems(role._info))) diff --git a/openstackclient/identity/v3/role_assignment.py b/openstackclient/identity/v3/role_assignment.py index 6177d1a5f6..d25cc6ce3d 100644 --- a/openstackclient/identity/v3/role_assignment.py +++ b/openstackclient/identity/v3/role_assignment.py @@ -36,6 +36,7 @@ def get_parser(self, prog_name): metavar='', help=_('Role to filter (name or ID)'), ) + common.add_role_domain_option_to_parser(parser) parser.add_argument( '--names', action="store_true", @@ -91,10 +92,15 @@ def take_action(self, parsed_args): auth_ref = self.app.client_manager.auth_ref role = None + role_domain_id = None + if parsed_args.role_domain: + role_domain_id = common.find_domain(identity_client, + parsed_args.role_domain).id if parsed_args.role: role = utils.find_resource( identity_client.roles, parsed_args.role, + domain_id=role_domain_id ) user = None @@ -205,6 +211,12 @@ def take_action(self, parsed_args): if hasattr(assignment, 'role'): if include_names: + # TODO(henry-nash): If this is a domain specific role it + # would be good show this as role@domain, although this + # domain info is not yet included in the response from the + # server. Although we could get it by re-reading the role + # from the ID, let's wait until the server does the right + # thing. setattr(assignment, 'role', assignment.role['name']) else: setattr(assignment, 'role', assignment.role['id']) diff --git a/openstackclient/tests/identity/v3/fakes.py b/openstackclient/tests/identity/v3/fakes.py index 8c138f7be8..c4d24d467e 100644 --- a/openstackclient/tests/identity/v3/fakes.py +++ b/openstackclient/tests/identity/v3/fakes.py @@ -173,9 +173,17 @@ ROLE = { 'id': role_id, 'name': role_name, + 'domain': None, 'links': base_url + 'roles/' + role_id, } +ROLE_2 = { + 'id': 'r2', + 'name': 'Rolls Royce', + 'domain': domain_id, + 'links': base_url + 'roles/' + 'r2', +} + service_id = 's-123' service_name = 'Texaco' service_type = 'gas' @@ -358,6 +366,12 @@ 'role': {'id': role_id}, } +ASSIGNMENT_WITH_DOMAIN_ROLE = { + 'scope': {'domain': {'id': domain_id}}, + 'user': {'id': user_id}, + 'role': {'id': ROLE_2['id']}, +} + ASSIGNMENT_WITH_DOMAIN_ID_AND_USER_ID_INCLUDE_NAMES = { 'scope': { 'domain': {'id': domain_id, diff --git a/openstackclient/tests/identity/v3/test_role.py b/openstackclient/tests/identity/v3/test_role.py index d2398e5d91..b4e76d9649 100644 --- a/openstackclient/tests/identity/v3/test_role.py +++ b/openstackclient/tests/identity/v3/test_role.py @@ -230,6 +230,45 @@ def test_role_add_group_project(self): ) self.assertIsNone(result) + def test_role_add_domain_role_on_user_project(self): + self.roles_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.ROLE_2), + loaded=True, + ) + arglist = [ + '--user', identity_fakes.user_name, + '--project', identity_fakes.project_name, + '--role-domain', identity_fakes.domain_name, + identity_fakes.ROLE_2['name'], + ] + if self._is_inheritance_testcase(): + arglist.append('--inherited') + verifylist = [ + ('user', identity_fakes.user_name), + ('group', None), + ('domain', None), + ('project', identity_fakes.project_name), + ('role', identity_fakes.ROLE_2['name']), + ('inherited', self._is_inheritance_testcase()), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'user': identity_fakes.user_id, + 'project': identity_fakes.project_id, + 'os_inherit_extension_inherited': self._is_inheritance_testcase(), + } + # RoleManager.grant(role, user=, group=, domain=, project=) + self.roles_mock.grant.assert_called_with( + identity_fakes.ROLE_2['id'], + **kwargs + ) + self.assertIsNone(result) + class TestRoleAddInherited(TestRoleAdd, TestRoleInherited): pass @@ -240,6 +279,12 @@ class TestRoleCreate(TestRole): def setUp(self): super(TestRoleCreate, self).setUp() + self.domains_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.DOMAIN), + loaded=True, + ) + self.roles_mock.create.return_value = fakes.FakeResource( None, copy.deepcopy(identity_fakes.ROLE), @@ -265,22 +310,67 @@ def test_role_create_no_options(self): # Set expected values kwargs = { + 'domain': None, 'name': identity_fakes.role_name, } - # RoleManager.create(name=) + # RoleManager.create(name=, domain=) self.roles_mock.create.assert_called_with( **kwargs ) - collist = ('id', 'name') + collist = ('domain', 'id', 'name') self.assertEqual(collist, columns) datalist = ( + None, identity_fakes.role_id, identity_fakes.role_name, ) self.assertEqual(datalist, data) + def test_role_create_with_domain(self): + + self.roles_mock.create.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.ROLE_2), + loaded=True, + ) + + arglist = [ + '--domain', identity_fakes.domain_name, + identity_fakes.ROLE_2['name'], + ] + verifylist = [ + ('domain', identity_fakes.domain_name), + ('name', identity_fakes.ROLE_2['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) + + # Set expected values + kwargs = { + 'domain': identity_fakes.domain_id, + 'name': identity_fakes.ROLE_2['name'], + } + + # RoleManager.create(name=, domain=) + self.roles_mock.create.assert_called_with( + **kwargs + ) + + collist = ('domain', 'id', 'name') + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.domain_id, + identity_fakes.ROLE_2['id'], + identity_fakes.ROLE_2['name'], + ) + self.assertEqual(datalist, data) + class TestRoleDelete(TestRole): @@ -313,6 +403,31 @@ def test_role_delete_no_options(self): ) self.assertIsNone(result) + def test_role_delete_with_domain(self): + self.roles_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.ROLE_2), + loaded=True, + ) + self.roles_mock.delete.return_value = None + + arglist = [ + '--domain', identity_fakes.domain_name, + identity_fakes.ROLE_2['name'], + ] + verifylist = [ + ('roles', [identity_fakes.ROLE_2['name']]), + ('domain', identity_fakes.domain_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.roles_mock.delete.assert_called_with( + identity_fakes.ROLE_2['id'], + ) + self.assertIsNone(result) + class TestRoleList(TestRole): @@ -583,6 +698,45 @@ def test_role_list_project_group(self): ), ) self.assertEqual(datalist, tuple(data)) + def test_role_list_domain_role(self): + self.roles_mock.list.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.ROLE_2), + loaded=True, + ), + ] + arglist = [ + '--domain', identity_fakes.domain_name, + ] + verifylist = [ + ('domain', identity_fakes.domain_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'domain_id': identity_fakes.domain_id + } + # RoleManager.list(user=, group=, domain=, project=, **kwargs) + self.roles_mock.list.assert_called_with( + **kwargs + ) + + collist = ('ID', 'Name', 'Domain') + self.assertEqual(collist, columns) + datalist = (( + identity_fakes.ROLE_2['id'], + identity_fakes.ROLE_2['name'], + identity_fakes.domain_name, + ), ) + self.assertEqual(datalist, tuple(data)) + class TestRoleRemove(TestRole): @@ -756,6 +910,44 @@ def test_role_remove_group_project(self): ) self.assertIsNone(result) + def test_role_remove_domain_role_on_group_domain(self): + self.roles_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.ROLE_2), + loaded=True, + ) + arglist = [ + '--group', identity_fakes.group_name, + '--domain', identity_fakes.domain_name, + identity_fakes.ROLE_2['name'], + ] + if self._is_inheritance_testcase(): + arglist.append('--inherited') + verifylist = [ + ('user', None), + ('group', identity_fakes.group_name), + ('domain', identity_fakes.domain_name), + ('project', None), + ('role', identity_fakes.ROLE_2['name']), + ('inherited', self._is_inheritance_testcase()), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'group': identity_fakes.group_id, + 'domain': identity_fakes.domain_id, + 'os_inherit_extension_inherited': self._is_inheritance_testcase(), + } + # RoleManager.revoke(role, user=, group=, domain=, project=) + self.roles_mock.revoke.assert_called_with( + identity_fakes.ROLE_2['id'], + **kwargs + ) + self.assertIsNone(result) + class TestRoleSet(TestRole): @@ -796,6 +988,37 @@ def test_role_set_no_options(self): ) self.assertIsNone(result) + def test_role_set_domain_role(self): + self.roles_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.ROLE_2), + loaded=True, + ) + arglist = [ + '--name', 'over', + '--domain', identity_fakes.domain_name, + identity_fakes.ROLE_2['name'], + ] + verifylist = [ + ('name', 'over'), + ('domain', identity_fakes.domain_name), + ('role', identity_fakes.ROLE_2['name']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'name': 'over', + } + # RoleManager.update(role, name=) + self.roles_mock.update.assert_called_with( + identity_fakes.ROLE_2['id'], + **kwargs + ) + self.assertIsNone(result) + class TestRoleShow(TestRole): @@ -830,10 +1053,53 @@ def test_role_show(self): identity_fakes.role_name, ) - collist = ('id', 'name') + collist = ('domain', 'id', 'name') self.assertEqual(collist, columns) datalist = ( + None, identity_fakes.role_id, identity_fakes.role_name, ) self.assertEqual(datalist, data) + + def test_role_show_domain_role(self): + self.roles_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.ROLE_2), + loaded=True, + ) + arglist = [ + '--domain', identity_fakes.domain_name, + identity_fakes.ROLE_2['name'], + ] + verifylist = [ + ('domain', identity_fakes.domain_name), + ('role', identity_fakes.ROLE_2['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) + + # RoleManager.get(role). This is called from utils.find_resource(). + # In fact, the current implementation calls the get(role) first with + # just the name, then with the name+domain_id. So technically we should + # mock this out with a call list, with the first call returning None + # and the second returning the object. However, if we did that we are + # then just testing the current sequencing within the utils method, and + # would become brittle to changes within that method. Hence we just + # check for the first call which is always lookup by name. + self.roles_mock.get.assert_called_with( + identity_fakes.ROLE_2['name'], + ) + + collist = ('domain', 'id', 'name') + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.domain_id, + identity_fakes.ROLE_2['id'], + identity_fakes.ROLE_2['name'], + ) + self.assertEqual(datalist, data) diff --git a/openstackclient/tests/identity/v3/test_role_assignment.py b/openstackclient/tests/identity/v3/test_role_assignment.py index 0ae67c72e0..113cc493ec 100644 --- a/openstackclient/tests/identity/v3/test_role_assignment.py +++ b/openstackclient/tests/identity/v3/test_role_assignment.py @@ -628,3 +628,56 @@ def test_role_assignment_list_include_names(self): False ),) self.assertEqual(tuple(data), datalist1) + + def test_role_assignment_list_domain_role(self): + + self.role_assignments_mock.list.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy( + identity_fakes.ASSIGNMENT_WITH_DOMAIN_ROLE), + loaded=True, + ), + ] + + arglist = [ + '--role', identity_fakes.ROLE_2['name'], + '--role-domain', identity_fakes.domain_name + ] + verifylist = [ + ('user', None), + ('group', None), + ('domain', None), + ('project', None), + ('role', identity_fakes.ROLE_2['name']), + ('effective', False), + ('inherited', False), + ('names', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + self.role_assignments_mock.list.assert_called_with( + domain=None, + user=None, + group=None, + project=None, + role=self.roles_mock.get(), + effective=False, + os_inherit_extension_inherited_to=None, + include_names=False) + + self.assertEqual(self.columns, columns) + datalist = (( + identity_fakes.ROLE_2['id'], + identity_fakes.user_id, + '', + '', + identity_fakes.domain_id, + False + ),) + self.assertEqual(datalist, tuple(data)) diff --git a/releasenotes/notes/bug-1606105-ca06b230e22ab5c6.yaml b/releasenotes/notes/bug-1606105-ca06b230e22ab5c6.yaml new file mode 100644 index 0000000000..a2c3ac82aa --- /dev/null +++ b/releasenotes/notes/bug-1606105-ca06b230e22ab5c6.yaml @@ -0,0 +1,4 @@ +--- +features: + - Add support for domain specific roles in ``role`` and``role assignment`` commands. + [Bug `1606105 `_] From 39c5eb9e3fe89bf177e513e447f22b62f5e4785c Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Tue, 9 Aug 2016 10:12:04 +0800 Subject: [PATCH 1164/3095] Rename backup commands in volume v1 and v2 Backup commands are used only in volume service now, but "backup" is too generic, users may not know the commands are used for volume from the commands name. By seeing the command name, users can only see the "backup" but do not know which object the backup commands work for. It may confuse users. I think rename "backup" to "volume backup" can depict resource relation and will be helpful for users to know the commands clearly. So add new commands ``volume backup create/delete/ list/show/restore`` to replace the old commands ``backup create/delete/list/show/restore``. And also deprecate old commands. Change-Id: I4f844d9bc48573eb4d17288ce6b8a90cea00d16a Implements: bp backup-snapshot-renamed-for-volume-resource Co-Authored-By: Sheel Rana --- doc/source/command-objects/backup.rst | 5 + doc/source/command-objects/volume-backup.rst | 132 ++++++++++++++++++ .../tests/volume/v2/test_backup.py | 10 +- openstackclient/volume/v1/backup.py | 116 +++++++++++++-- openstackclient/volume/v2/backup.py | 115 +++++++++++++-- ...-for-volume-resource-2d2d13ea8489a61f.yaml | 8 ++ setup.cfg | 12 ++ 7 files changed, 363 insertions(+), 35 deletions(-) create mode 100644 doc/source/command-objects/volume-backup.rst create mode 100644 releasenotes/notes/bp-backup-snapshot-renamed-for-volume-resource-2d2d13ea8489a61f.yaml diff --git a/doc/source/command-objects/backup.rst b/doc/source/command-objects/backup.rst index 4abc155fe8..ac88708636 100644 --- a/doc/source/command-objects/backup.rst +++ b/doc/source/command-objects/backup.rst @@ -8,6 +8,7 @@ backup create ------------- Create new backup +(Deprecated, please use ``volume backup create`` instead) .. program:: backup create .. code:: bash @@ -60,6 +61,7 @@ backup delete ------------- Delete backup(s) +(Deprecated, please use ``volume backup delete`` instead) .. program:: backup delete .. code:: bash @@ -83,6 +85,7 @@ backup list ----------- List backups +(Deprecated, please use ``volume backup list`` instead) .. program:: backup list .. code:: bash @@ -98,6 +101,7 @@ backup restore -------------- Restore backup +(Deprecated, please use ``volume backup restore`` instead) .. program:: backup restore .. code:: bash @@ -119,6 +123,7 @@ backup show ----------- Display backup details +(Deprecated, please use ``volume backup show`` instead) .. program:: backup show .. code:: bash diff --git a/doc/source/command-objects/volume-backup.rst b/doc/source/command-objects/volume-backup.rst new file mode 100644 index 0000000000..3a8f0213df --- /dev/null +++ b/doc/source/command-objects/volume-backup.rst @@ -0,0 +1,132 @@ +============= +volume backup +============= + +Block Storage v1, v2 + +volume backup create +-------------------- + +Create new volume backup + +.. program:: volume backup create +.. code:: bash + + os volume backup create + [--container ] + [--name ] + [--description ] + [--snapshot ] + [--force] + [--incremental] + + +.. option:: --container + + Optional backup container name + +.. option:: --name + + Name of the backup + +.. option:: --description + + Description of the backup + +.. option:: --snapshot + + Snapshot to backup (name or ID) + + *Volume version 2 only* + +.. option:: --force + + Allow to back up an in-use volume + + *Volume version 2 only* + +.. option:: --incremental + + Perform an incremental backup + + *Volume version 2 only* + +.. _volume_backup_create-backup: +.. describe:: + + Volume to backup (name or ID) + +volume backup delete +-------------------- + +Delete volume backup(s) + +.. program:: volume backup delete +.. code:: bash + + os volume backup delete + [--force] + [ ...] + +.. option:: --force + + Allow delete in state other than error or available + + *Volume version 2 only* + +.. _volume_backup_delete-backup: +.. describe:: + + Backup(s) to delete (name or ID) + +volume backup list +------------------ + +List volume backups + +.. program:: volume backup list +.. code:: bash + + os volume backup list + +.. _volume_backup_list-backup: +.. option:: --long + + List additional fields in output + +volume backup restore +--------------------- + +Restore volume backup + +.. program:: volume backup restore +.. code:: bash + + os volume backup restore + + + +.. _volume_backup_restore-backup: +.. describe:: + + Backup to restore (name or ID) + +.. describe:: + + Volume to restore to (name or ID) + +volume backup show +------------------ + +Display volume backup details + +.. program:: volume backup show +.. code:: bash + + os volume backup show + + +.. _volume_backup_show-backup: +.. describe:: + + Backup to display (name or ID) diff --git a/openstackclient/tests/volume/v2/test_backup.py b/openstackclient/tests/volume/v2/test_backup.py index 3c2b39488a..67064352b2 100644 --- a/openstackclient/tests/volume/v2/test_backup.py +++ b/openstackclient/tests/volume/v2/test_backup.py @@ -77,7 +77,7 @@ def setUp(self): self.backups_mock.create.return_value = self.new_backup # Get the command object to test - self.cmd = backup.CreateBackup(self.app, None) + self.cmd = backup.CreateVolumeBackup(self.app, None) def test_backup_create(self): arglist = [ @@ -154,7 +154,7 @@ def setUp(self): self.backups_mock.delete.return_value = None # Get the command object to mock - self.cmd = backup.DeleteBackup(self.app, None) + self.cmd = backup.DeleteVolumeBackup(self.app, None) def test_backup_delete(self): arglist = [ @@ -281,7 +281,7 @@ def setUp(self): self.volumes_mock.list.return_value = [self.volume] self.backups_mock.list.return_value = self.backups # Get the command to test - self.cmd = backup.ListBackup(self.app, None) + self.cmd = backup.ListVolumeBackup(self.app, None) def test_backup_list_without_options(self): arglist = [] @@ -317,7 +317,7 @@ def setUp(self): self.volumes_mock.get.return_value = self.volume self.restores_mock.restore.return_value = None # Get the command object to mock - self.cmd = backup.RestoreBackup(self.app, None) + self.cmd = backup.RestoreVolumeBackup(self.app, None) def test_backup_restore(self): arglist = [ @@ -370,7 +370,7 @@ def setUp(self): self.backups_mock.get.return_value = self.backup # Get the command object to test - self.cmd = backup.ShowBackup(self.app, None) + self.cmd = backup.ShowVolumeBackup(self.app, None) def test_backup_show(self): arglist = [ diff --git a/openstackclient/volume/v1/backup.py b/openstackclient/volume/v1/backup.py index 5f34a2c51f..539ed369e2 100644 --- a/openstackclient/volume/v1/backup.py +++ b/openstackclient/volume/v1/backup.py @@ -16,6 +16,7 @@ """Volume v1 Backup action implementations""" import copy +import logging from osc_lib.command import command from osc_lib import utils @@ -24,11 +25,11 @@ from openstackclient.i18n import _ -class CreateBackup(command.ShowOne): - """Create new backup""" +class CreateVolumeBackup(command.ShowOne): + """Create new volume backup""" def get_parser(self, prog_name): - parser = super(CreateBackup, self).get_parser(prog_name) + parser = super(CreateVolumeBackup, self).get_parser(prog_name) parser.add_argument( 'volume', metavar='', @@ -67,11 +68,28 @@ def take_action(self, parsed_args): return zip(*sorted(six.iteritems(backup._info))) -class DeleteBackup(command.Command): - """Delete backup(s)""" +class CreateBackup(CreateVolumeBackup): + """Create new backup""" + + # TODO(Huanxuan Ao): Remove this class and ``backup create`` command + # two cycles after Newton. + + # This notifies cliff to not display the help for this command + deprecated = True + + log = logging.getLogger('deprecated') + + def take_action(self, parsed_args): + self.log.warning(_('This command has been deprecated. ' + 'Please use "volume backup create" instead.')) + return super(CreateBackup, self).take_action(parsed_args) + + +class DeleteVolumeBackup(command.Command): + """Delete volume backup(s)""" def get_parser(self, prog_name): - parser = super(DeleteBackup, self).get_parser(prog_name) + parser = super(DeleteVolumeBackup, self).get_parser(prog_name) parser.add_argument( 'backups', metavar='', @@ -88,11 +106,28 @@ def take_action(self, parsed_args): volume_client.backups.delete(backup_id) -class ListBackup(command.Lister): - """List backups""" +class DeleteBackup(DeleteVolumeBackup): + """Delete backup(s)""" + + # TODO(Huanxuan Ao): Remove this class and ``backup delete`` command + # two cycles after Newton. + + # This notifies cliff to not display the help for this command + deprecated = True + + log = logging.getLogger('deprecated') + + def take_action(self, parsed_args): + self.log.warning(_('This command has been deprecated. ' + 'Please use "volume backup delete" instead.')) + return super(DeleteBackup, self).take_action(parsed_args) + + +class ListVolumeBackup(command.Lister): + """List volume backups""" def get_parser(self, prog_name): - parser = super(ListBackup, self).get_parser(prog_name) + parser = super(ListVolumeBackup, self).get_parser(prog_name) parser.add_argument( '--long', action='store_true', @@ -142,11 +177,28 @@ def _format_volume_id(volume_id): ) for s in data)) -class RestoreBackup(command.Command): - """Restore backup""" +class ListBackup(ListVolumeBackup): + """List backups""" + + # TODO(Huanxuan Ao): Remove this class and ``backup list`` command + # two cycles after Newton. + + # This notifies cliff to not display the help for this command + deprecated = True + + log = logging.getLogger('deprecated') + + def take_action(self, parsed_args): + self.log.warning(_('This command has been deprecated. ' + 'Please use "volume backup list" instead.')) + return super(ListBackup, self).take_action(parsed_args) + + +class RestoreVolumeBackup(command.Command): + """Restore volume backup""" def get_parser(self, prog_name): - parser = super(RestoreBackup, self).get_parser(prog_name) + parser = super(RestoreVolumeBackup, self).get_parser(prog_name) parser.add_argument( 'backup', metavar='', @@ -169,11 +221,28 @@ def take_action(self, parsed_args): destination_volume.id) -class ShowBackup(command.ShowOne): - """Display backup details""" +class RestoreBackup(RestoreVolumeBackup): + """Restore backup""" + + # TODO(Huanxuan Ao): Remove this class and ``backup restore`` command + # two cycles after Newton. + + # This notifies cliff to not display the help for this command + deprecated = True + + log = logging.getLogger('deprecated') + + def take_action(self, parsed_args): + self.log.warning(_('This command has been deprecated. ' + 'Please use "volume backup restore" instead.')) + return super(RestoreBackup, self).take_action(parsed_args) + + +class ShowVolumeBackup(command.ShowOne): + """Display volume backup details""" def get_parser(self, prog_name): - parser = super(ShowBackup, self).get_parser(prog_name) + parser = super(ShowVolumeBackup, self).get_parser(prog_name) parser.add_argument( 'backup', metavar='', @@ -187,3 +256,20 @@ def take_action(self, parsed_args): parsed_args.backup) backup._info.pop('links') return zip(*sorted(six.iteritems(backup._info))) + + +class ShowBackup(ShowVolumeBackup): + """Display backup details""" + + # TODO(Huanxuan Ao): Remove this class and ``backup show`` command + # two cycles after Newton. + + # This notifies cliff to not display the help for this command + deprecated = True + + log = logging.getLogger('deprecated') + + def take_action(self, parsed_args): + self.log.warning(_('This command has been deprecated. ' + 'Please use "volume backup show" instead.')) + return super(ShowBackup, self).take_action(parsed_args) diff --git a/openstackclient/volume/v2/backup.py b/openstackclient/volume/v2/backup.py index 3d27c121ef..07c1c94f75 100644 --- a/openstackclient/volume/v2/backup.py +++ b/openstackclient/volume/v2/backup.py @@ -28,11 +28,11 @@ LOG = logging.getLogger(__name__) -class CreateBackup(command.ShowOne): - """Create new backup""" +class CreateVolumeBackup(command.ShowOne): + """Create new volume backup""" def get_parser(self, prog_name): - parser = super(CreateBackup, self).get_parser(prog_name) + parser = super(CreateVolumeBackup, self).get_parser(prog_name) parser.add_argument( "volume", metavar="", @@ -93,11 +93,28 @@ def take_action(self, parsed_args): return zip(*sorted(six.iteritems(backup._info))) -class DeleteBackup(command.Command): - """Delete backup(s)""" +class CreateBackup(CreateVolumeBackup): + """Create new backup""" + + # TODO(Huanxuan Ao): Remove this class and ``backup create`` command + # two cycles after Newton. + + # This notifies cliff to not display the help for this command + deprecated = True + + log = logging.getLogger('deprecated') + + def take_action(self, parsed_args): + self.log.warning(_('This command has been deprecated. ' + 'Please use "volume backup create" instead.')) + return super(CreateBackup, self).take_action(parsed_args) + + +class DeleteVolumeBackup(command.Command): + """Delete volume backup(s)""" def get_parser(self, prog_name): - parser = super(DeleteBackup, self).get_parser(prog_name) + parser = super(DeleteVolumeBackup, self).get_parser(prog_name) parser.add_argument( "backups", metavar="", @@ -134,11 +151,28 @@ def take_action(self, parsed_args): raise exceptions.CommandError(msg) -class ListBackup(command.Lister): - """List backups""" +class DeleteBackup(DeleteVolumeBackup): + """Delete backup(s)""" + + # TODO(Huanxuan Ao): Remove this class and ``backup delete`` command + # two cycles after Newton. + + # This notifies cliff to not display the help for this command + deprecated = True + + log = logging.getLogger('deprecated') + + def take_action(self, parsed_args): + self.log.warning(_('This command has been deprecated. ' + 'Please use "volume backup delete" instead.')) + return super(DeleteBackup, self).take_action(parsed_args) + + +class ListVolumeBackup(command.Lister): + """List volume backups""" def get_parser(self, prog_name): - parser = super(ListBackup, self).get_parser(prog_name) + parser = super(ListVolumeBackup, self).get_parser(prog_name) parser.add_argument( "--long", action="store_true", @@ -188,11 +222,28 @@ def _format_volume_id(volume_id): ) for s in data)) -class RestoreBackup(command.ShowOne): - """Restore backup""" +class ListBackup(ListVolumeBackup): + """List backups""" + + # TODO(Huanxuan Ao): Remove this class and ``backup list`` command + # two cycles after Newton. + + # This notifies cliff to not display the help for this command + deprecated = True + + log = logging.getLogger('deprecated') + + def take_action(self, parsed_args): + self.log.warning(_('This command has been deprecated. ' + 'Please use "volume backup list" instead.')) + return super(ListBackup, self).take_action(parsed_args) + + +class RestoreVolumeBackup(command.ShowOne): + """Restore volume backup""" def get_parser(self, prog_name): - parser = super(RestoreBackup, self).get_parser(prog_name) + parser = super(RestoreVolumeBackup, self).get_parser(prog_name) parser.add_argument( "backup", metavar="", @@ -213,11 +264,28 @@ def take_action(self, parsed_args): return volume_client.restores.restore(backup.id, destination_volume.id) -class ShowBackup(command.ShowOne): - """Display backup details""" +class RestoreBackup(RestoreVolumeBackup): + """Restore backup""" + + # TODO(Huanxuan Ao): Remove this class and ``backup restore`` command + # two cycles after Newton. + + # This notifies cliff to not display the help for this command + deprecated = True + + log = logging.getLogger('deprecated') + + def take_action(self, parsed_args): + self.log.warning(_('This command has been deprecated. ' + 'Please use "volume backup restore" instead.')) + return super(RestoreBackup, self).take_action(parsed_args) + + +class ShowVolumeBackup(command.ShowOne): + """Display volume backup details""" def get_parser(self, prog_name): - parser = super(ShowBackup, self).get_parser(prog_name) + parser = super(ShowVolumeBackup, self).get_parser(prog_name) parser.add_argument( "backup", metavar="", @@ -231,3 +299,20 @@ def take_action(self, parsed_args): parsed_args.backup) backup._info.pop("links", None) return zip(*sorted(six.iteritems(backup._info))) + + +class ShowBackup(ShowVolumeBackup): + """Display backup details""" + + # TODO(Huanxuan Ao): Remove this class and ``backup show`` command + # two cycles after Newton. + + # This notifies cliff to not display the help for this command + deprecated = True + + log = logging.getLogger('deprecated') + + def take_action(self, parsed_args): + self.log.warning(_('This command has been deprecated. ' + 'Please use "volume backup show" instead.')) + return super(ShowBackup, self).take_action(parsed_args) diff --git a/releasenotes/notes/bp-backup-snapshot-renamed-for-volume-resource-2d2d13ea8489a61f.yaml b/releasenotes/notes/bp-backup-snapshot-renamed-for-volume-resource-2d2d13ea8489a61f.yaml new file mode 100644 index 0000000000..c985140972 --- /dev/null +++ b/releasenotes/notes/bp-backup-snapshot-renamed-for-volume-resource-2d2d13ea8489a61f.yaml @@ -0,0 +1,8 @@ +--- +features: + - Add new commands ``volume backup create/delete/list/show/restore``. It is + used to replace the old commands ``backup create/delete/list/show/restore``. + [Blueprint `backup-snapshot-renamed-for-volume-resource `_] +deprecations: + - Deprecate commands ``backup create/delete/list/show/restore``. + [Blueprint `backup-snapshot-renamed-for-volume-resource `_] diff --git a/setup.cfg b/setup.cfg index d0c2648066..414a582010 100644 --- a/setup.cfg +++ b/setup.cfg @@ -457,6 +457,12 @@ openstack.volume.v1 = volume_show = openstackclient.volume.v1.volume:ShowVolume volume_unset = openstackclient.volume.v1.volume:UnsetVolume + volume_backup_create = openstackclient.volume.v1.backup:CreateVolumeBackup + volume_backup_delete = openstackclient.volume.v1.backup:DeleteVolumeBackup + volume_backup_list = openstackclient.volume.v1.backup:ListVolumeBackup + volume_backup_restore = openstackclient.volume.v1.backup:RestoreVolumeBackup + volume_backup_show = openstackclient.volume.v1.backup:ShowVolumeBackup + volume_type_create = openstackclient.volume.v1.volume_type:CreateVolumeType volume_type_delete = openstackclient.volume.v1.volume_type:DeleteVolumeType volume_type_list = openstackclient.volume.v1.volume_type:ListVolumeType @@ -498,6 +504,12 @@ openstack.volume.v2 = volume_show = openstackclient.volume.v2.volume:ShowVolume volume_unset = openstackclient.volume.v2.volume:UnsetVolume + volume_backup_create = openstackclient.volume.v2.backup:CreateVolumeBackup + volume_backup_delete = openstackclient.volume.v2.backup:DeleteVolumeBackup + volume_backup_list = openstackclient.volume.v2.backup:ListVolumeBackup + volume_backup_restore = openstackclient.volume.v2.backup:RestoreVolumeBackup + volume_backup_show = openstackclient.volume.v2.backup:ShowVolumeBackup + volume_type_create = openstackclient.volume.v2.volume_type:CreateVolumeType volume_type_delete = openstackclient.volume.v2.volume_type:DeleteVolumeType volume_type_list = openstackclient.volume.v2.volume_type:ListVolumeType From 95f0e3dc366a94d7e47c5dc4178253c25b1aaa08 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Thu, 11 Aug 2016 11:38:16 +0800 Subject: [PATCH 1165/3095] Fix errors in volume set/unset image properties unit tests These errors may caused by copy-paste, we should specify a fake return value for get() method but not create() method. Create() mothod will not called in set/unset command. After specifing the return value of get(), we can use the fake volume in the assert. Change-Id: I1e0df4c28ece373168788c396e9082d565e36cc7 --- openstackclient/tests/volume/v2/test_volume.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openstackclient/tests/volume/v2/test_volume.py b/openstackclient/tests/volume/v2/test_volume.py index 1bb5c19237..74181a259f 100644 --- a/openstackclient/tests/volume/v2/test_volume.py +++ b/openstackclient/tests/volume/v2/test_volume.py @@ -841,7 +841,7 @@ def test_volume_set_image_property(self): # returns nothing self.cmd.take_action(parsed_args) self.volumes_mock.set_image_metadata.assert_called_with( - self.volumes_mock.get().id, parsed_args.image_property) + self.new_volume.id, parsed_args.image_property) def test_volume_set_state(self): arglist = [ @@ -919,7 +919,7 @@ def setUp(self): super(TestVolumeUnset, self).setUp() self.new_volume = volume_fakes.FakeVolume.create_one_volume() - self.volumes_mock.create.return_value = self.new_volume + self.volumes_mock.get.return_value = self.new_volume # Get the command object to set property self.cmd_set = volume.SetVolume(self.app, None) @@ -963,4 +963,4 @@ def test_volume_unset_image_property(self): self.cmd_unset.take_action(parsed_args_unset) self.volumes_mock.delete_image_metadata.assert_called_with( - self.volumes_mock.get().id, parsed_args_unset.image_property) + self.new_volume.id, parsed_args_unset.image_property) From 51cbe414e3ea9da59c1cf0b34e96fd3cef52b514 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Thu, 11 Aug 2016 00:40:23 -0700 Subject: [PATCH 1166/3095] update requirements and test requirements with the move to osc-lib we don't have to list some requirements. removed oslo.config since it was not being used, and moved a few to test-requirements since they are still used in examples and unit tests. Closes-Bug: 1593784 Change-Id: I39b610e6a49a4a346dddcedd231d9a7a81cab261 --- requirements.txt | 4 ---- test-requirements.txt | 3 +++ 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index 5c840e27b3..3f8ec2d61c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,14 +8,10 @@ Babel>=2.3.4 # BSD cliff!=1.16.0,!=1.17.0,>=1.15.0 # Apache-2.0 keystoneauth1>=2.10.0 # Apache-2.0 openstacksdk>=0.9.0 # Apache-2.0 -os-client-config!=1.19.0,>=1.13.1 # Apache-2.0 osc-lib>=0.4.0 # Apache-2.0 -oslo.config>=3.14.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 oslo.utils>=3.16.0 # Apache-2.0 python-glanceclient>=2.0.0 # Apache-2.0 python-keystoneclient!=1.8.0,!=2.1.0,>=1.7.0 # Apache-2.0 python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0 python-cinderclient!=1.7.0,!=1.7.1,>=1.6.0 # Apache-2.0 -requests>=2.10.0 # Apache-2.0 -stevedore>=1.16.0 # Apache-2.0 diff --git a/test-requirements.txt b/test-requirements.txt index 49bb4646db..efd236559b 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -9,8 +9,11 @@ mock>=2.0 # BSD oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 reno>=1.8.0 # Apache2 +requests>=2.10.0 # Apache-2.0 requests-mock>=1.0 # Apache-2.0 sphinx!=1.3b1,<1.3,>=1.2.1 # BSD +stevedore>=1.16.0 # Apache-2.0 +os-client-config!=1.19.0,>=1.13.1 # Apache-2.0 os-testr>=0.7.0 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD testtools>=1.4.0 # MIT From 81431d24a9f94f56c4c39cb12bf846871f6230d8 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Tue, 16 Aug 2016 18:46:55 +0800 Subject: [PATCH 1167/3095] Add "volume service set" command Add "volume service set" command in volume v1 and v2 (v1 is the same as v2) to disable or enable volume service. Change-Id: Ibb2db7e93b24cb2e0d2a7c28b6fd8bcc851b8d2f Closes-Bug: #1613597 --- doc/source/command-objects/volume-service.rst | 34 ++++ .../tests/volume/v1/test_service.py | 145 ++++++++++++++++++ .../tests/volume/v2/test_service.py | 145 ++++++++++++++++++ openstackclient/volume/v1/service.py | 56 +++++++ openstackclient/volume/v2/service.py | 56 +++++++ .../notes/bug-1613597-b1545148b0755e6f.yaml | 4 + setup.cfg | 2 + 7 files changed, 442 insertions(+) create mode 100644 releasenotes/notes/bug-1613597-b1545148b0755e6f.yaml diff --git a/doc/source/command-objects/volume-service.rst b/doc/source/command-objects/volume-service.rst index aa9fa6d262..d7495968fb 100644 --- a/doc/source/command-objects/volume-service.rst +++ b/doc/source/command-objects/volume-service.rst @@ -29,3 +29,37 @@ List volume service .. option:: --long List additional fields in output + +volume service set +------------------ + +Set volume service properties + +.. program:: volume service set +.. code:: bash + + os volume service set + [--enable | --disable] + [--disable-reason ] + + +.. option:: --enable + + Enable volume service + +.. option:: --disable + + Disable volume service + +.. option:: --disable-reason + + Reason for disabling the service (should be used with --disable option) + +.. _volume-service-set: +.. describe:: + + Name of host + +.. describe:: + + Name of service (Binary name) diff --git a/openstackclient/tests/volume/v1/test_service.py b/openstackclient/tests/volume/v1/test_service.py index 7168434496..2578d76b59 100644 --- a/openstackclient/tests/volume/v1/test_service.py +++ b/openstackclient/tests/volume/v1/test_service.py @@ -12,6 +12,7 @@ # under the License. # +from osc_lib import exceptions from openstackclient.tests.volume.v1 import fakes as service_fakes from openstackclient.volume.v1 import service @@ -139,3 +140,147 @@ def test_service_list_with_long_option(self): self.services.host, self.services.binary, ) + + +class TestServiceSet(TestService): + + service = service_fakes.FakeService.create_one_service() + + def setUp(self): + super(TestServiceSet, self).setUp() + + self.service_mock.enable.return_value = self.service + self.service_mock.disable.return_value = self.service + self.service_mock.disable_log_reason.return_value = self.service + + self.cmd = service.SetService(self.app, None) + + def test_service_set_nothing(self): + arglist = [ + self.service.host, + self.service.binary, + ] + verifylist = [ + ('host', self.service.host), + ('service', self.service.binary), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.service_mock.enable.assert_not_called() + self.service_mock.disable.assert_not_called() + self.service_mock.disable_log_reason.assert_not_called() + self.assertIsNone(result) + + def test_service_set_enable(self): + arglist = [ + '--enable', + self.service.host, + self.service.binary, + ] + verifylist = [ + ('enable', True), + ('host', self.service.host), + ('service', self.service.binary), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.service_mock.enable.assert_called_with( + self.service.host, + self.service.binary + ) + self.service_mock.disable.assert_not_called() + self.service_mock.disable_log_reason.assert_not_called() + self.assertIsNone(result) + + def test_service_set_disable(self): + arglist = [ + '--disable', + self.service.host, + self.service.binary, + ] + verifylist = [ + ('disable', True), + ('host', self.service.host), + ('service', self.service.binary), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.service_mock.disable.assert_called_with( + self.service.host, + self.service.binary + ) + self.service_mock.enable.assert_not_called() + self.service_mock.disable_log_reason.assert_not_called() + self.assertIsNone(result) + + def test_service_set_disable_with_reason(self): + reason = 'earthquake' + arglist = [ + '--disable', + '--disable-reason', reason, + self.service.host, + self.service.binary, + ] + verifylist = [ + ('disable', True), + ('disable_reason', reason), + ('host', self.service.host), + ('service', self.service.binary), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.service_mock.disable_log_reason.assert_called_with( + self.service.host, + self.service.binary, + reason + ) + self.assertIsNone(result) + + def test_service_set_only_with_disable_reason(self): + reason = 'earthquake' + arglist = [ + '--disable-reason', reason, + self.service.host, + self.service.binary, + ] + verifylist = [ + ('disable_reason', reason), + ('host', self.service.host), + ('service', self.service.binary), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + try: + self.cmd.take_action(parsed_args) + self.fail("CommandError should be raised.") + except exceptions.CommandError as e: + self.assertEqual("Cannot specify option --disable-reason without " + "--disable specified.", str(e)) + + def test_service_set_enable_with_disable_reason(self): + reason = 'earthquake' + arglist = [ + '--enable', + '--disable-reason', reason, + self.service.host, + self.service.binary, + ] + verifylist = [ + ('enable', True), + ('disable_reason', reason), + ('host', self.service.host), + ('service', self.service.binary), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + try: + self.cmd.take_action(parsed_args) + self.fail("CommandError should be raised.") + except exceptions.CommandError as e: + self.assertEqual("Cannot specify option --disable-reason without " + "--disable specified.", str(e)) diff --git a/openstackclient/tests/volume/v2/test_service.py b/openstackclient/tests/volume/v2/test_service.py index ba2e1b3217..2c432b2b2a 100644 --- a/openstackclient/tests/volume/v2/test_service.py +++ b/openstackclient/tests/volume/v2/test_service.py @@ -12,6 +12,7 @@ # under the License. # +from osc_lib import exceptions from openstackclient.tests.volume.v2 import fakes as service_fakes from openstackclient.volume.v2 import service @@ -139,3 +140,147 @@ def test_service_list_with_long_option(self): self.services.host, self.services.binary, ) + + +class TestServiceSet(TestService): + + service = service_fakes.FakeService.create_one_service() + + def setUp(self): + super(TestServiceSet, self).setUp() + + self.service_mock.enable.return_value = self.service + self.service_mock.disable.return_value = self.service + self.service_mock.disable_log_reason.return_value = self.service + + self.cmd = service.SetService(self.app, None) + + def test_service_set_nothing(self): + arglist = [ + self.service.host, + self.service.binary, + ] + verifylist = [ + ('host', self.service.host), + ('service', self.service.binary), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.service_mock.enable.assert_not_called() + self.service_mock.disable.assert_not_called() + self.service_mock.disable_log_reason.assert_not_called() + self.assertIsNone(result) + + def test_service_set_enable(self): + arglist = [ + '--enable', + self.service.host, + self.service.binary, + ] + verifylist = [ + ('enable', True), + ('host', self.service.host), + ('service', self.service.binary), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.service_mock.enable.assert_called_with( + self.service.host, + self.service.binary + ) + self.service_mock.disable.assert_not_called() + self.service_mock.disable_log_reason.assert_not_called() + self.assertIsNone(result) + + def test_service_set_disable(self): + arglist = [ + '--disable', + self.service.host, + self.service.binary, + ] + verifylist = [ + ('disable', True), + ('host', self.service.host), + ('service', self.service.binary), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.service_mock.disable.assert_called_with( + self.service.host, + self.service.binary + ) + self.service_mock.enable.assert_not_called() + self.service_mock.disable_log_reason.assert_not_called() + self.assertIsNone(result) + + def test_service_set_disable_with_reason(self): + reason = 'earthquake' + arglist = [ + '--disable', + '--disable-reason', reason, + self.service.host, + self.service.binary, + ] + verifylist = [ + ('disable', True), + ('disable_reason', reason), + ('host', self.service.host), + ('service', self.service.binary), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.service_mock.disable_log_reason.assert_called_with( + self.service.host, + self.service.binary, + reason + ) + self.assertIsNone(result) + + def test_service_set_only_with_disable_reason(self): + reason = 'earthquake' + arglist = [ + '--disable-reason', reason, + self.service.host, + self.service.binary, + ] + verifylist = [ + ('disable_reason', reason), + ('host', self.service.host), + ('service', self.service.binary), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + try: + self.cmd.take_action(parsed_args) + self.fail("CommandError should be raised.") + except exceptions.CommandError as e: + self.assertEqual("Cannot specify option --disable-reason without " + "--disable specified.", str(e)) + + def test_service_set_enable_with_disable_reason(self): + reason = 'earthquake' + arglist = [ + '--enable', + '--disable-reason', reason, + self.service.host, + self.service.binary, + ] + verifylist = [ + ('enable', True), + ('disable_reason', reason), + ('host', self.service.host), + ('service', self.service.binary), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + try: + self.cmd.take_action(parsed_args) + self.fail("CommandError should be raised.") + except exceptions.CommandError as e: + self.assertEqual("Cannot specify option --disable-reason without " + "--disable specified.", str(e)) diff --git a/openstackclient/volume/v1/service.py b/openstackclient/volume/v1/service.py index 2df38573d4..867c4b9c7f 100644 --- a/openstackclient/volume/v1/service.py +++ b/openstackclient/volume/v1/service.py @@ -15,6 +15,7 @@ """Service action implementations""" from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils from openstackclient.i18n import _ @@ -72,3 +73,58 @@ def take_action(self, parsed_args): (utils.get_item_properties( s, columns, ) for s in data)) + + +class SetService(command.Command): + """Set volume service properties""" + + def get_parser(self, prog_name): + parser = super(SetService, self).get_parser(prog_name) + parser.add_argument( + "host", + metavar="", + help=_("Name of host") + ) + parser.add_argument( + "service", + metavar="", + help=_("Name of service (Binary name)") + ) + enabled_group = parser.add_mutually_exclusive_group() + enabled_group.add_argument( + "--enable", + action="store_true", + help=_("Enable volume service") + ) + enabled_group.add_argument( + "--disable", + action="store_true", + help=_("Disable volume service") + ) + parser.add_argument( + "--disable-reason", + metavar="", + help=_("Reason for disabling the service " + "(should be used with --disable option)") + ) + return parser + + def take_action(self, parsed_args): + if parsed_args.disable_reason and not parsed_args.disable: + msg = _("Cannot specify option --disable-reason without " + "--disable specified.") + raise exceptions.CommandError(msg) + + service_client = self.app.client_manager.volume + if parsed_args.enable: + service_client.services.enable( + parsed_args.host, parsed_args.service) + if parsed_args.disable: + if parsed_args.disable_reason: + service_client.services.disable_log_reason( + parsed_args.host, + parsed_args.service, + parsed_args.disable_reason) + else: + service_client.services.disable( + parsed_args.host, parsed_args.service) diff --git a/openstackclient/volume/v2/service.py b/openstackclient/volume/v2/service.py index 2df38573d4..867c4b9c7f 100644 --- a/openstackclient/volume/v2/service.py +++ b/openstackclient/volume/v2/service.py @@ -15,6 +15,7 @@ """Service action implementations""" from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils from openstackclient.i18n import _ @@ -72,3 +73,58 @@ def take_action(self, parsed_args): (utils.get_item_properties( s, columns, ) for s in data)) + + +class SetService(command.Command): + """Set volume service properties""" + + def get_parser(self, prog_name): + parser = super(SetService, self).get_parser(prog_name) + parser.add_argument( + "host", + metavar="", + help=_("Name of host") + ) + parser.add_argument( + "service", + metavar="", + help=_("Name of service (Binary name)") + ) + enabled_group = parser.add_mutually_exclusive_group() + enabled_group.add_argument( + "--enable", + action="store_true", + help=_("Enable volume service") + ) + enabled_group.add_argument( + "--disable", + action="store_true", + help=_("Disable volume service") + ) + parser.add_argument( + "--disable-reason", + metavar="", + help=_("Reason for disabling the service " + "(should be used with --disable option)") + ) + return parser + + def take_action(self, parsed_args): + if parsed_args.disable_reason and not parsed_args.disable: + msg = _("Cannot specify option --disable-reason without " + "--disable specified.") + raise exceptions.CommandError(msg) + + service_client = self.app.client_manager.volume + if parsed_args.enable: + service_client.services.enable( + parsed_args.host, parsed_args.service) + if parsed_args.disable: + if parsed_args.disable_reason: + service_client.services.disable_log_reason( + parsed_args.host, + parsed_args.service, + parsed_args.disable_reason) + else: + service_client.services.disable( + parsed_args.host, parsed_args.service) diff --git a/releasenotes/notes/bug-1613597-b1545148b0755e6f.yaml b/releasenotes/notes/bug-1613597-b1545148b0755e6f.yaml new file mode 100644 index 0000000000..09b3538a23 --- /dev/null +++ b/releasenotes/notes/bug-1613597-b1545148b0755e6f.yaml @@ -0,0 +1,4 @@ +--- +features: + - Add ``volume service set`` commands in volume v1 and v2. + [Bug `1613597 `_] diff --git a/setup.cfg b/setup.cfg index 78eaf34424..7af6e0c7e2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -481,6 +481,7 @@ openstack.volume.v1 = volume_qos_unset = openstackclient.volume.v1.qos_specs:UnsetQos volume_service_list = openstackclient.volume.v1.service:ListService + volume_service_set = openstackclient.volume.v1.service:SetService volume_transfer_request_list = openstackclient.volume.v1.volume_transfer_request:ListTransferRequests @@ -528,6 +529,7 @@ openstack.volume.v2 = volume_qos_unset = openstackclient.volume.v2.qos_specs:UnsetQos volume_service_list = openstackclient.volume.v2.service:ListService + volume_service_set = openstackclient.volume.v2.service:SetService volume_transfer_request_list = openstackclient.volume.v2.volume_transfer_request:ListTransferRequests From 2a1a1740862c419e08284e50103d52e029f0e61e Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Tue, 16 Aug 2016 09:41:31 -0500 Subject: [PATCH 1168/3095] Gate-unbreaking combo review Fix argument precedence hack Working around issues in os-client-config <= 1.18.0 This is ugly because the issues in o-c-c 1.19.1 run even deeper than in 1.18.0, so we're going to use 1.19.0 get_one_cloud() that is known to work for OSC and fix o-c-c with an axe. Remove return values for set commands 'identity provider set' and 'service provider set' were still returning their show-like data, this is a fail for set commands now, don't know how this ever passed before... Constraints are ready to be used for tox.ini Per email[1] from Andreas, we don't need to hack at install_command any longer. [1] http://openstack.markmail.org/thread/a4l7tokbotwqvuoh Co-authorioed-by: Steve Martinelli Depends-On: I49313dc7d4f44ec897de7a375f25b7ed864226f1 Change-Id: I426548376fc7d3cdb36501310dafd8c44d22ae30 --- openstackclient/common/client_config.py | 186 ++++++++++++++++++ .../identity/v3/identity_provider.py | 9 +- .../identity/v3/service_provider.py | 9 +- openstackclient/shell.py | 31 ++- .../identity/v3/test_identity_provider.py | 52 +---- .../identity/v3/test_service_provider.py | 31 +-- tox.ini | 4 - 7 files changed, 232 insertions(+), 90 deletions(-) create mode 100644 openstackclient/common/client_config.py diff --git a/openstackclient/common/client_config.py b/openstackclient/common/client_config.py new file mode 100644 index 0000000000..cc6cad5899 --- /dev/null +++ b/openstackclient/common/client_config.py @@ -0,0 +1,186 @@ +# 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. +# + +"""OpenStackConfig subclass for argument compatibility""" + +import logging + +from os_client_config.config import OpenStackConfig + + +LOG = logging.getLogger(__name__) + + +# Sublcass OpenStackConfig in order to munge config values +# before auth plugins are loaded +class OSC_Config(OpenStackConfig): + + def _auth_select_default_plugin(self, config): + """Select a default plugin based on supplied arguments + + Migrated from auth.select_auth_plugin() + """ + + identity_version = config.get('identity_api_version', '') + + if config.get('username', None) and not config.get('auth_type', None): + if identity_version == '3': + config['auth_type'] = 'v3password' + elif identity_version.startswith('2'): + config['auth_type'] = 'v2password' + else: + # let keystoneauth figure it out itself + config['auth_type'] = 'password' + elif config.get('token', None) and not config.get('auth_type', None): + if identity_version == '3': + config['auth_type'] = 'v3token' + elif identity_version.startswith('2'): + config['auth_type'] = 'v2token' + else: + # let keystoneauth figure it out itself + config['auth_type'] = 'token' + else: + # The ultimate default is similar to the original behaviour, + # but this time with version discovery + if not config.get('auth_type', None): + config['auth_type'] = 'password' + + LOG.debug("Auth plugin %s selected" % config['auth_type']) + return config + + def _auth_v2_arguments(self, config): + """Set up v2-required arguments from v3 info + + Migrated from auth.build_auth_params() + """ + + if ('auth_type' in config and config['auth_type'].startswith("v2")): + if 'project_id' in config['auth']: + config['auth']['tenant_id'] = config['auth']['project_id'] + if 'project_name' in config['auth']: + config['auth']['tenant_name'] = config['auth']['project_name'] + return config + + def _auth_v2_ignore_v3(self, config): + """Remove v3 arguemnts if present for v2 plugin + + Migrated from clientmanager.setup_auth() + """ + + # NOTE(hieulq): If USER_DOMAIN_NAME, USER_DOMAIN_ID, PROJECT_DOMAIN_ID + # or PROJECT_DOMAIN_NAME is present and API_VERSION is 2.0, then + # ignore all domain related configs. + if (config.get('identity_api_version', '').startswith('2') and + config.get('auth_type', None).endswith('password')): + domain_props = [ + 'project_domain_id', + 'project_domain_name', + 'user_domain_id', + 'user_domain_name', + ] + for prop in domain_props: + if config['auth'].pop(prop, None) is not None: + LOG.warning("Ignoring domain related config " + + prop + " because identity API version is 2.0") + return config + + def _auth_default_domain(self, config): + """Set a default domain from available arguments + + Migrated from clientmanager.setup_auth() + """ + + identity_version = config.get('identity_api_version', '') + auth_type = config.get('auth_type', None) + + # TODO(mordred): This is a usability improvement that's broadly useful + # We should port it back up into os-client-config. + default_domain = config.get('default_domain', None) + if (identity_version == '3' and + not auth_type.startswith('v2') and + default_domain): + + # NOTE(stevemar): If PROJECT_DOMAIN_ID or PROJECT_DOMAIN_NAME is + # present, then do not change the behaviour. Otherwise, set the + # PROJECT_DOMAIN_ID to 'OS_DEFAULT_DOMAIN' for better usability. + if ( + not config['auth'].get('project_domain_id') and + not config['auth'].get('project_domain_name') + ): + config['auth']['project_domain_id'] = default_domain + + # NOTE(stevemar): If USER_DOMAIN_ID or USER_DOMAIN_NAME is present, + # then do not change the behaviour. Otherwise, set the + # USER_DOMAIN_ID to 'OS_DEFAULT_DOMAIN' for better usability. + # NOTE(aloga): this should only be set if there is a username. + # TODO(dtroyer): Move this to os-client-config after the plugin has + # been loaded so we can check directly if the options are accepted. + if ( + auth_type in ("password", "v3password", "v3totp") and + not config['auth'].get('user_domain_id') and + not config['auth'].get('user_domain_name') + ): + config['auth']['user_domain_id'] = default_domain + return config + + def auth_config_hook(self, config): + """Allow examination of config values before loading auth plugin + + OpenStackClient will override this to perform additional chacks + on auth_type. + """ + + config = self._auth_select_default_plugin(config) + config = self._auth_v2_arguments(config) + config = self._auth_v2_ignore_v3(config) + config = self._auth_default_domain(config) + + LOG.debug("auth_config_hook(): %s" % config) + return config + + def _validate_auth_ksc(self, config, cloud): + """Old compatibility hack for OSC, no longer needed/wanted""" + return config + + def _validate_auth(self, config, loader): + """Validate auth plugin arguments""" + # May throw a keystoneauth1.exceptions.NoMatchingPlugin + + plugin_options = loader.get_options() + + for p_opt in plugin_options: + # if it's in config, win, move it and kill it from config dict + # if it's in config.auth but not in config we're good + # deprecated loses to current + # provided beats default, deprecated or not + winning_value = self._find_winning_auth_value(p_opt, config) + if not winning_value: + winning_value = self._find_winning_auth_value( + p_opt, config['auth']) + + # Clean up after ourselves + for opt in [p_opt.name] + [o.name for o in p_opt.deprecated]: + opt = opt.replace('-', '_') + config.pop(opt, None) + config['auth'].pop(opt, None) + + if winning_value: + # Prefer the plugin configuration dest value if the value's key + # is marked as depreciated. + if p_opt.dest is None: + config['auth'][p_opt.name.replace('-', '_')] = ( + winning_value) + else: + config['auth'][p_opt.dest] = winning_value + + return config diff --git a/openstackclient/identity/v3/identity_provider.py b/openstackclient/identity/v3/identity_provider.py index 0453e8883e..b6b0318870 100644 --- a/openstackclient/identity/v3/identity_provider.py +++ b/openstackclient/identity/v3/identity_provider.py @@ -204,11 +204,10 @@ def take_action(self, parsed_args): if parsed_args.remote_id_file or parsed_args.remote_id: kwargs['remote_ids'] = remote_ids - identity_provider = federation_client.identity_providers.update( - parsed_args.identity_provider, **kwargs) - - identity_provider._info.pop('links', None) - return zip(*sorted(six.iteritems(identity_provider._info))) + federation_client.identity_providers.update( + parsed_args.identity_provider, + **kwargs + ) class ShowIdentityProvider(command.ShowOne): diff --git a/openstackclient/identity/v3/service_provider.py b/openstackclient/identity/v3/service_provider.py index 440eba4090..8548ae1fbd 100644 --- a/openstackclient/identity/v3/service_provider.py +++ b/openstackclient/identity/v3/service_provider.py @@ -182,12 +182,13 @@ def take_action(self, parsed_args): elif parsed_args.disable is True: enabled = False - service_provider = federation_client.service_providers.update( - parsed_args.service_provider, enabled=enabled, + federation_client.service_providers.update( + parsed_args.service_provider, + enabled=enabled, description=parsed_args.description, auth_url=parsed_args.auth_url, - sp_url=parsed_args.service_provider_url) - return zip(*sorted(six.iteritems(service_provider._info))) + sp_url=parsed_args.service_provider_url, + ) class ShowServiceProvider(command.ShowOne): diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 7a042e1ec7..67c51998c8 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -25,6 +25,7 @@ import six import openstackclient +from openstackclient.common import client_config as cloud_config from openstackclient.common import clientmanager from openstackclient.common import commandmanager @@ -127,9 +128,33 @@ def _load_commands(self): def initialize_app(self, argv): super(OpenStackShell, self).initialize_app(argv) - # For now we need to build our own ClientManager so re-do what - # has already been done :( - # TODO(dtroyer): remove when osc-lib is fixed + # Argument precedence is really broken in multiple places + # so we're just going to fix it here until o-c-c and osc-lib + # get sorted out. + # TODO(dtroyer): remove when os-client-config and osc-lib are fixed + + # First, throw away what has already been done with o-c-c and + # use our own. + try: + cc = cloud_config.OSC_Config( + override_defaults={ + 'interface': None, + 'auth_type': self._auth_type, + }, + ) + except (IOError, OSError) as e: + self.log.critical("Could not read clouds.yaml configuration file") + self.print_help_if_requested() + raise e + + if not self.options.debug: + self.options.debug = None + self.cloud = cc.get_one_cloud( + cloud=self.options.cloud, + argparse=self.options, + ) + + # Then, re-create the client_manager with the correct arguments self.client_manager = clientmanager.ClientManager( cli_options=self.cloud, api_version=self.api_version, diff --git a/openstackclient/tests/identity/v3/test_identity_provider.py b/openstackclient/tests/identity/v3/test_identity_provider.py index b5d784ef32..d86ac11e3f 100644 --- a/openstackclient/tests/identity/v3/test_identity_provider.py +++ b/openstackclient/tests/identity/v3/test_identity_provider.py @@ -356,19 +356,11 @@ def prepare(self): ('remote_id', None) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) + self.cmd.take_action(parsed_args) self.identity_providers_mock.update.assert_called_with( identity_fakes.idp_id, description=new_description, ) - self.assertEqual(self.columns, columns) - datalist = ( - identity_fakes.idp_description, - False, - identity_fakes.idp_id, - identity_fakes.idp_remote_ids - ) - self.assertEqual(datalist, data) def test_identity_provider_disable(self): """Disable Identity Provider @@ -402,22 +394,13 @@ def prepare(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) + self.cmd.take_action(parsed_args) self.identity_providers_mock.update.assert_called_with( identity_fakes.idp_id, enabled=False, remote_ids=identity_fakes.idp_remote_ids ) - self.assertEqual(self.columns, columns) - datalist = ( - identity_fakes.idp_description, - False, - identity_fakes.idp_id, - identity_fakes.idp_remote_ids - ) - self.assertEqual(datalist, data) - def test_identity_provider_enable(self): """Enable Identity Provider. @@ -448,12 +431,10 @@ def prepare(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) + self.cmd.take_action(parsed_args) self.identity_providers_mock.update.assert_called_with( identity_fakes.idp_id, enabled=True, remote_ids=identity_fakes.idp_remote_ids) - self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) def test_identity_provider_replace_remote_ids(self): """Enable Identity Provider. @@ -488,18 +469,10 @@ def prepare(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) + self.cmd.take_action(parsed_args) self.identity_providers_mock.update.assert_called_with( identity_fakes.idp_id, enabled=True, remote_ids=[self.new_remote_id]) - self.assertEqual(self.columns, columns) - datalist = ( - identity_fakes.idp_description, - True, - identity_fakes.idp_id, - [self.new_remote_id] - ) - self.assertEqual(datalist, data) def test_identity_provider_replace_remote_ids_file(self): """Enable Identity Provider. @@ -538,18 +511,10 @@ def prepare(self): mocker.return_value = self.new_remote_id with mock.patch("openstackclient.identity.v3.identity_provider." "utils.read_blob_file_contents", mocker): - columns, data = self.cmd.take_action(parsed_args) + self.cmd.take_action(parsed_args) self.identity_providers_mock.update.assert_called_with( identity_fakes.idp_id, enabled=True, remote_ids=[self.new_remote_id]) - self.assertEqual(self.columns, columns) - datalist = ( - identity_fakes.idp_description, - True, - identity_fakes.idp_id, - [self.new_remote_id] - ) - self.assertEqual(datalist, data) def test_identity_provider_no_options(self): def prepare(self): @@ -580,12 +545,7 @@ def prepare(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) - - # expect take_action() to return (None, None) as - # neither --enable nor --disable was specified - self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.cmd.take_action(parsed_args) class TestIdentityProviderShow(TestIdentityProvider): diff --git a/openstackclient/tests/identity/v3/test_service_provider.py b/openstackclient/tests/identity/v3/test_service_provider.py index f5270d839c..873ab1e759 100644 --- a/openstackclient/tests/identity/v3/test_service_provider.py +++ b/openstackclient/tests/identity/v3/test_service_provider.py @@ -289,7 +289,7 @@ def prepare(self): ('disable', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) + self.cmd.take_action(parsed_args) self.service_providers_mock.update.assert_called_with( service_fakes.sp_id, enabled=False, @@ -298,9 +298,6 @@ def prepare(self): sp_url=None ) - self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) - def test_service_provider_enable(self): """Enable Service Provider. @@ -327,19 +324,10 @@ def prepare(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) + self.cmd.take_action(parsed_args) self.service_providers_mock.update.assert_called_with( service_fakes.sp_id, enabled=True, description=None, auth_url=None, sp_url=None) - self.assertEqual(self.columns, columns) - datalist = ( - service_fakes.sp_auth_url, - service_fakes.sp_description, - True, - service_fakes.sp_id, - service_fakes.service_provider_url - ) - self.assertEqual(datalist, data) def test_service_provider_no_options(self): def prepare(self): @@ -372,20 +360,7 @@ def prepare(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) - - # expect take_action() to return (None, None) as none of --disabled, - # --enabled, --description, --service-provider-url, --auth_url option - # was set. - self.assertEqual(self.columns, columns) - datalist = ( - service_fakes.sp_auth_url, - service_fakes.sp_description, - True, - service_fakes.sp_id, - service_fakes.service_provider_url - ) - self.assertEqual(datalist, data) + self.cmd.take_action(parsed_args) class TestServiceProviderShow(TestServiceProvider): diff --git a/tox.ini b/tox.ini index 29854b6775..9eb0a1a0af 100644 --- a/tox.ini +++ b/tox.ini @@ -55,8 +55,6 @@ setenv = OS_TEST_PATH=./functional/tests passenv = OS_* [testenv:venv] -# TODO(ihrachys): remove once infra supports constraints for this target -install_command = pip install -U {opts} {packages} commands = {posargs} [testenv:cover] @@ -71,8 +69,6 @@ commands = oslo_debug_helper -t openstackclient/tests {posargs} commands = python setup.py build_sphinx [testenv:releasenotes] -# TODO(ihrachys): remove once infra supports constraints for this target -install_command = pip install -U {opts} {packages} commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html [flake8] From 44f6b769298ea0b8ad33e7f902acda08f83da04b Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 29 Jul 2016 16:31:12 -0500 Subject: [PATCH 1169/3095] Add shell integration test These run next to unit tests, but unlike unit tests, they test the dependent libraries also. This is to detect incompatible breakage in those dependencies. The tests provide CLI-level input and verify the API calls being made via requests using requests_mock so the entire stack is tested. It is possible we want to run these separate from the unit tests. They do not belong in the functional tests as they do not require a functional cloud for testing. Depends-on: I426548376fc7d3cdb36501310dafd8c44d22ae30 Change-Id: I356956fcc4ff35191a6ad6a085b75cf370434b09 --- openstackclient/tests/test_shell_integ.py | 561 ++++++++++++++++++++++ 1 file changed, 561 insertions(+) create mode 100644 openstackclient/tests/test_shell_integ.py diff --git a/openstackclient/tests/test_shell_integ.py b/openstackclient/tests/test_shell_integ.py new file mode 100644 index 0000000000..88e4ba7767 --- /dev/null +++ b/openstackclient/tests/test_shell_integ.py @@ -0,0 +1,561 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import copy +import mock + +from keystoneauth1 import fixture as ksa_fixture +from osc_lib.tests import utils as osc_lib_utils +from requests_mock.contrib import fixture + +from openstackclient import shell +from openstackclient.tests import test_shell +from openstackclient.tests import utils + +HOST = "192.168.5.41" +URL_BASE = "http://%s/identity" % HOST + +V2_AUTH_URL = URL_BASE + "/v2.0/" +V2_VERSION_RESP = { + "version": { + "status": "stable", + "updated": "2014-04-17T00:00:00Z", + "media-types": [ + { + "base": "application/json", + "type": "application/vnd.openstack.identity-v2.0+json", + }, + ], + "id": "v2.0", + "links": [ + { + "href": V2_AUTH_URL, + "rel": "self", + }, + { + "href": "http://docs.openstack.org/", + "type": "text/html", + "rel": "describedby", + }, + ], + }, +} + +V3_AUTH_URL = URL_BASE + "/v3/" +V3_VERSION_RESP = { + "version": { + "status": "stable", + "updated": "2016-04-04T00:00:00Z", + "media-types": [{ + "base": "application/json", + "type": "application/vnd.openstack.identity-v3+json", + }], + "id": "v3.6", + "links": [{ + "href": V3_AUTH_URL, + "rel": "self", + }] + } +} + + +class TestShellInteg(utils.TestCase): + + def setUp(self): + super(TestShellInteg, self).setUp() + + self.requests_mock = self.useFixture(fixture.Fixture()) + + +class TestShellCliV2Integ(TestShellInteg): + + def setUp(self): + super(TestShellCliV2Integ, self).setUp() + env = { + "OS_AUTH_URL": V2_AUTH_URL, + "OS_PROJECT_NAME": test_shell.DEFAULT_PROJECT_NAME, + "OS_USERNAME": test_shell.DEFAULT_USERNAME, + "OS_PASSWORD": test_shell.DEFAULT_PASSWORD, + "OS_IDENTITY_API_VERSION": "2", + } + self.useFixture(osc_lib_utils.EnvFixture(copy.deepcopy(env))) + + self.token = ksa_fixture.V2Token( + tenant_name=test_shell.DEFAULT_PROJECT_NAME, + user_name=test_shell.DEFAULT_USERNAME, + ) + + # Set up the v2 auth routes + self.requests_mock.register_uri( + 'GET', + V2_AUTH_URL, + json=V2_VERSION_RESP, + status_code=200, + ) + self.requests_mock.register_uri( + 'POST', + V2_AUTH_URL + 'tokens', + json=self.token, + status_code=200, + ) + + def test_shell_args_no_options(self): + _shell = shell.OpenStackShell() + _shell.run("configuration show".split()) + + # Check general calls + self.assertEqual(len(self.requests_mock.request_history), 2) + + # Check discovery request + self.assertEqual( + V2_AUTH_URL, + self.requests_mock.request_history[0].url, + ) + + # Check auth request + auth_req = self.requests_mock.request_history[1].json() + + self.assertEqual( + test_shell.DEFAULT_PROJECT_NAME, + auth_req['auth']['tenantName'], + ) + self.assertEqual( + test_shell.DEFAULT_USERNAME, + auth_req['auth']['passwordCredentials']['username'], + ) + self.assertEqual( + test_shell.DEFAULT_PASSWORD, + auth_req['auth']['passwordCredentials']['password'], + ) + + def test_shell_args_verify(self): + _shell = shell.OpenStackShell() + _shell.run("--verify configuration show".split()) + + # Check general calls + self.assertEqual(len(self.requests_mock.request_history), 2) + + # Check verify + self.assertTrue(self.requests_mock.request_history[0].verify) + + def test_shell_args_insecure(self): + _shell = shell.OpenStackShell() + _shell.run("--insecure configuration show".split()) + + # Check general calls + self.assertEqual(len(self.requests_mock.request_history), 2) + + # Check verify + self.assertFalse(self.requests_mock.request_history[0].verify) + + def test_shell_args_cacert(self): + _shell = shell.OpenStackShell() + _shell.run("--os-cacert xyzpdq configuration show".split()) + + # Check general calls + self.assertEqual(len(self.requests_mock.request_history), 2) + + # Check verify + self.assertEqual( + 'xyzpdq', + self.requests_mock.request_history[0].verify, + ) + + def test_shell_args_cacert_insecure(self): + _shell = shell.OpenStackShell() + _shell.run("--os-cacert xyzpdq --insecure configuration show".split()) + + # Check general calls + self.assertEqual(len(self.requests_mock.request_history), 2) + + # Check verify + self.assertFalse(self.requests_mock.request_history[0].verify) + + +class TestShellCliV2IgnoreInteg(TestShellInteg): + + def setUp(self): + super(TestShellCliV2IgnoreInteg, self).setUp() + env = { + "OS_AUTH_URL": V2_AUTH_URL, + "OS_PROJECT_NAME": test_shell.DEFAULT_PROJECT_NAME, + "OS_USERNAME": test_shell.DEFAULT_USERNAME, + "OS_PASSWORD": test_shell.DEFAULT_PASSWORD, + "OS_PROJECT_DOMAIN_ID": test_shell.DEFAULT_PROJECT_DOMAIN_ID, + "OS_USER_DOMAIN_ID": test_shell.DEFAULT_USER_DOMAIN_ID, + "OS_IDENTITY_API_VERSION": "2", + } + self.useFixture(osc_lib_utils.EnvFixture(copy.deepcopy(env))) + + self.token = ksa_fixture.V2Token( + tenant_name=test_shell.DEFAULT_PROJECT_NAME, + user_name=test_shell.DEFAULT_USERNAME, + ) + + # Set up the v2 auth routes + self.requests_mock.register_uri( + 'GET', + V2_AUTH_URL, + json=V2_VERSION_RESP, + status_code=200, + ) + self.requests_mock.register_uri( + 'POST', + V2_AUTH_URL + 'tokens', + json=self.token, + status_code=200, + ) + + def test_shell_args_ignore_v3(self): + _shell = shell.OpenStackShell() + _shell.run("configuration show".split()) + + # Check general calls + self.assertEqual(len(self.requests_mock.request_history), 2) + + # Check discovery request + self.assertEqual( + V2_AUTH_URL, + self.requests_mock.request_history[0].url, + ) + + # Check auth request + auth_req = self.requests_mock.request_history[1].json() + + self.assertEqual( + test_shell.DEFAULT_PROJECT_NAME, + auth_req['auth']['tenantName'], + ) + self.assertEqual( + test_shell.DEFAULT_USERNAME, + auth_req['auth']['passwordCredentials']['username'], + ) + self.assertEqual( + test_shell.DEFAULT_PASSWORD, + auth_req['auth']['passwordCredentials']['password'], + ) + + +class TestShellCliV3Integ(TestShellInteg): + + def setUp(self): + super(TestShellCliV3Integ, self).setUp() + env = { + "OS_AUTH_URL": V3_AUTH_URL, + "OS_PROJECT_DOMAIN_ID": test_shell.DEFAULT_PROJECT_DOMAIN_ID, + "OS_USER_DOMAIN_ID": test_shell.DEFAULT_USER_DOMAIN_ID, + "OS_USERNAME": test_shell.DEFAULT_USERNAME, + "OS_PASSWORD": test_shell.DEFAULT_PASSWORD, + "OS_IDENTITY_API_VERSION": "3", + } + self.useFixture(osc_lib_utils.EnvFixture(copy.deepcopy(env))) + + self.token = ksa_fixture.V3Token( + project_domain_id=test_shell.DEFAULT_PROJECT_DOMAIN_ID, + user_domain_id=test_shell.DEFAULT_USER_DOMAIN_ID, + user_name=test_shell.DEFAULT_USERNAME, + ) + + # Set up the v3 auth routes + self.requests_mock.register_uri( + 'GET', + V3_AUTH_URL, + json=V3_VERSION_RESP, + status_code=200, + ) + self.requests_mock.register_uri( + 'POST', + V3_AUTH_URL + 'auth/tokens', + json=self.token, + status_code=200, + ) + + def test_shell_args_no_options(self): + _shell = shell.OpenStackShell() + _shell.run("configuration show".split()) + + # Check general calls + self.assertEqual(len(self.requests_mock.request_history), 2) + + # Check discovery request + self.assertEqual( + V3_AUTH_URL, + self.requests_mock.request_history[0].url, + ) + + # Check auth request + auth_req = self.requests_mock.request_history[1].json() + + self.assertEqual( + test_shell.DEFAULT_PROJECT_DOMAIN_ID, + auth_req['auth']['identity']['password']['user']['domain']['id'], + ) + self.assertEqual( + test_shell.DEFAULT_USERNAME, + auth_req['auth']['identity']['password']['user']['name'], + ) + self.assertEqual( + test_shell.DEFAULT_PASSWORD, + auth_req['auth']['identity']['password']['user']['password'], + ) + + def test_shell_args_verify(self): + _shell = shell.OpenStackShell() + _shell.run("--verify configuration show".split()) + + # Check general calls + self.assertEqual(len(self.requests_mock.request_history), 2) + + # Check verify + self.assertTrue(self.requests_mock.request_history[0].verify) + + def test_shell_args_insecure(self): + _shell = shell.OpenStackShell() + _shell.run("--insecure configuration show".split()) + + # Check general calls + self.assertEqual(len(self.requests_mock.request_history), 2) + + # Check verify + self.assertFalse(self.requests_mock.request_history[0].verify) + + def test_shell_args_cacert(self): + _shell = shell.OpenStackShell() + _shell.run("--os-cacert xyzpdq configuration show".split()) + + # Check general calls + self.assertEqual(len(self.requests_mock.request_history), 2) + + # Check verify + self.assertEqual( + 'xyzpdq', + self.requests_mock.request_history[0].verify, + ) + + def test_shell_args_cacert_insecure(self): + # This test verifies the outcome of bug 1447784 + # https://bugs.launchpad.net/python-openstackclient/+bug/1447784 + _shell = shell.OpenStackShell() + _shell.run("--os-cacert xyzpdq --insecure configuration show".split()) + + # Check general calls + self.assertEqual(len(self.requests_mock.request_history), 2) + + # Check verify + self.assertFalse(self.requests_mock.request_history[0].verify) + + +class TestShellCliPrecedence(TestShellInteg): + """Validate option precedence rules + + Global option values may be set in three places: + * command line options + * environment variables + * clouds.yaml + + Verify that the above order is the precedence used, + i.e. a command line option overrides all others, etc + """ + + def setUp(self): + super(TestShellCliPrecedence, self).setUp() + env = { + "OS_CLOUD": "megacloud", + "OS_AUTH_URL": V3_AUTH_URL, + "OS_PROJECT_DOMAIN_ID": test_shell.DEFAULT_PROJECT_DOMAIN_ID, + "OS_USER_DOMAIN_ID": test_shell.DEFAULT_USER_DOMAIN_ID, + "OS_USERNAME": test_shell.DEFAULT_USERNAME, + "OS_IDENTITY_API_VERSION": "3", + "OS_CLOUD_NAME": "qaz", + } + self.useFixture(osc_lib_utils.EnvFixture(copy.deepcopy(env))) + + self.token = ksa_fixture.V3Token( + project_domain_id=test_shell.DEFAULT_PROJECT_DOMAIN_ID, + user_domain_id=test_shell.DEFAULT_USER_DOMAIN_ID, + user_name=test_shell.DEFAULT_USERNAME, + ) + + # Set up the v3 auth routes + self.requests_mock.register_uri( + 'GET', + V3_AUTH_URL, + json=V3_VERSION_RESP, + status_code=200, + ) + self.requests_mock.register_uri( + 'POST', + V3_AUTH_URL + 'auth/tokens', + json=self.token, + status_code=200, + ) + + # Patch a v3 auth URL into the o-c-c data + test_shell.PUBLIC_1['public-clouds']['megadodo']['auth']['auth_url'] \ + = V3_AUTH_URL + + # @mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file") + # @mock.patch("os_client_config.config.OpenStackConfig._load_config_file") + # def test_shell_args_options(self, config_mock, vendor_mock): + # """Verify command line options override environment variables""" + + # _shell = shell.OpenStackShell() + # _shell.run( + # "--os-username zarquon --os-password qaz " + # "configuration show".split(), + # ) + + # # Check general calls + # self.assertEqual(len(self.requests_mock.request_history), 2) + + # # Check discovery request + # self.assertEqual( + # V3_AUTH_URL, + # self.requests_mock.request_history[0].url, + # ) + + # # Check auth request + # auth_req = self.requests_mock.request_history[1].json() + + # # Environment var, no option + # self.assertEqual( + # test_shell.DEFAULT_PROJECT_DOMAIN_ID, + # auth_req['auth']['identity']['password']['user']['domain']['id'], + # ) + + # # Environment var, --os-username override + # self.assertEqual( + # 'zarquon', + # auth_req['auth']['identity']['password']['user']['name'], + # ) + + # # No environment var, --os-password override + # self.assertEqual( + # 'qaz', + # auth_req['auth']['identity']['password']['user']['password'], + # ) + + @mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file") + @mock.patch("os_client_config.config.OpenStackConfig._load_config_file") + def test_shell_args_precedence_1(self, config_mock, vendor_mock): + """Precedence run 1 + + Run 1 has --os-password on CLI + """ + + config_mock.return_value = ( + 'file.yaml', + copy.deepcopy(test_shell.CLOUD_2), + ) + vendor_mock.return_value = ( + 'file.yaml', + copy.deepcopy(test_shell.PUBLIC_1), + ) + _shell = shell.OpenStackShell() + _shell.run( + "--os-password qaz configuration show".split(), + ) + + # Check general calls + self.assertEqual(len(self.requests_mock.request_history), 2) + + # Check discovery request + self.assertEqual( + V3_AUTH_URL, + self.requests_mock.request_history[0].url, + ) + + # Check auth request + auth_req = self.requests_mock.request_history[1].json() + + # -env, -cli, -occ + # No test, everything not specified tests this + + # -env, -cli, +occ + self.assertEqual( + "heart-o-gold", + auth_req['auth']['scope']['project']['name'], + ) + + # -env, +cli, -occ + self.assertEqual( + 'qaz', + auth_req['auth']['identity']['password']['user']['password'], + ) + + # -env, +cli, +occ + + # +env, -cli, -occ + self.assertEqual( + test_shell.DEFAULT_USER_DOMAIN_ID, + auth_req['auth']['identity']['password']['user']['domain']['id'], + ) + + # +env, -cli, +occ + print("auth_req: %s" % auth_req['auth']) + self.assertEqual( + test_shell.DEFAULT_USERNAME, + auth_req['auth']['identity']['password']['user']['name'], + ) + + # +env, +cli, -occ + # see test_shell_args_precedence_2() + + # +env, +cli, +occ + # see test_shell_args_precedence_2() + + @mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file") + @mock.patch("os_client_config.config.OpenStackConfig._load_config_file") + def test_shell_args_precedence_2(self, config_mock, vendor_mock): + """Precedence run 2 + + Run 2 has --os-username, --os-password, --os-project-domain-id on CLI + """ + + config_mock.return_value = ( + 'file.yaml', + copy.deepcopy(test_shell.CLOUD_2), + ) + vendor_mock.return_value = ( + 'file.yaml', + copy.deepcopy(test_shell.PUBLIC_1), + ) + _shell = shell.OpenStackShell() + _shell.run( + "--os-username zarquon --os-password qaz " + "--os-project-domain-id 5678 configuration show".split(), + ) + + # Check general calls + self.assertEqual(len(self.requests_mock.request_history), 2) + + # Check discovery request + self.assertEqual( + V3_AUTH_URL, + self.requests_mock.request_history[0].url, + ) + + # Check auth request + auth_req = self.requests_mock.request_history[1].json() + + # +env, +cli, -occ + self.assertEqual( + '5678', + auth_req['auth']['scope']['project']['domain']['id'], + ) + + # +env, +cli, +occ + print("auth_req: %s" % auth_req['auth']) + self.assertEqual( + 'zarquon', + auth_req['auth']['identity']['password']['user']['name'], + ) From b8d6ceef869bbe8498689783657a34e89973f03b Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Sun, 31 Jul 2016 08:14:50 -0700 Subject: [PATCH 1170/3095] document locale and language support tips We have full support for various locales and languages, but often there are hiccups when setting up a terminal to use OSC in this manner. Document a few common questions so we don't have to individually support each bug report. Change-Id: I0f7c7efce3a2c00cc82ed8c67310e027b5e45a45 --- doc/source/configuration.rst | 56 ++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst index d80b3b3634..efef2b9724 100644 --- a/doc/source/configuration.rst +++ b/doc/source/configuration.rst @@ -214,3 +214,59 @@ The logging level for `--log-file` can be set by using following options. * `-v, --verbose` * `-q, --quiet` * `--debug` + +Locale and Language Support +--------------------------- + +Full support for languages is included as of OpenStackClient 3.0.0. Here are a +few tips to ensure you have a correct configuration. + +Verify preferred python encoding +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Please perform the following to diagnose ensure locale settings are correct. +Run python interactively and print the preferred encoding value, e.g.: + +:: + + $ python -c "import locale; print locale.getpreferredencoding()" + +If the value is ``ascii`` or ``ANSI_X3.4-1968`` or any other equivalent name for +ASCII the problem is in your environment. You most likely do not have your LANG +environment variable set correctly. + +Check the LANG environment variable +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``LANG`` should be of the form: `lang_code`_`[region_code]`.`encoding`. +For example, it may look like: ``en_US.UTF-8`` + +The critical part here is the `encoding` value of ``UTF-8``. Python will look +up locale information and if it finds an encoding value, it will set the +encoding property of stdin, stdout and stderr to the value found in your +environment, if it's not defined in your environment it defaults to ASCII. + +Redirecting output +~~~~~~~~~~~~~~~~~~ + +The above only occurs if stdin, stdout and stderr are attached to a TTY. If +redirecting data the encoding on these streams will default to the default +encoding which is set in the `site.py` of your Python distribution, which +defaults to ASCII. A workaround for this is to set ``PYTHONIOENCODING`` to UTF8. + +:: + + $ PYTHONIOENCODING=utf-8 + +A final note about DevStack +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A common post devstack operation is to source the ``openrc`` file to set up +environment variables. Doing so will unset the default ``LANG`` environment +variable in your terminal, which will cause the preferred python encoding to +be ``ascii``. We recommend either setting these environment variables +independently or using the ``devstack`` or ``devstack-admin`` os-cloud profile. + +:: + + $ openstack project list --os-cloud devstack-admin From cc3d46a47b62836fb97f6f49140a28668b8f212b Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 11 Aug 2016 11:26:25 -0500 Subject: [PATCH 1171/3095] Fix up last-minute imports to use osc-lib Change-Id: I1ed2983cf574ebd565eeac4f8199fbc3a2e29c8e --- openstackclient/identity/v2_0/role_assignment.py | 7 ++++--- .../tests/identity/v2_0/test_role_assignment.py | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/openstackclient/identity/v2_0/role_assignment.py b/openstackclient/identity/v2_0/role_assignment.py index 406508ac37..44f55c3b7a 100644 --- a/openstackclient/identity/v2_0/role_assignment.py +++ b/openstackclient/identity/v2_0/role_assignment.py @@ -13,9 +13,10 @@ """Identity v2 Assignment action implementations """ -from openstackclient.common import command -from openstackclient.common import exceptions -from openstackclient.common import utils +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils + from openstackclient.i18n import _ # noqa diff --git a/openstackclient/tests/identity/v2_0/test_role_assignment.py b/openstackclient/tests/identity/v2_0/test_role_assignment.py index ab48c2f418..a356ae0ad5 100644 --- a/openstackclient/tests/identity/v2_0/test_role_assignment.py +++ b/openstackclient/tests/identity/v2_0/test_role_assignment.py @@ -14,7 +14,8 @@ import copy import mock -from openstackclient.common import exceptions +from osc_lib import exceptions + from openstackclient.identity.v2_0 import role_assignment from openstackclient.tests import fakes from openstackclient.tests.identity.v2_0 import fakes as identity_fakes From 06721ea8895558b0000ca9e54f56ffa64657edf1 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 18 Aug 2016 17:37:24 -0500 Subject: [PATCH 1172/3095] Integ test cleanup Change-Id: Ie58a7bec569421097e92a7ddf3cb164fc3f07413 --- openstackclient/tests/test_shell_integ.py | 133 +++++++++++++++------- 1 file changed, 91 insertions(+), 42 deletions(-) diff --git a/openstackclient/tests/test_shell_integ.py b/openstackclient/tests/test_shell_integ.py index 88e4ba7767..bc5f1ae51a 100644 --- a/openstackclient/tests/test_shell_integ.py +++ b/openstackclient/tests/test_shell_integ.py @@ -355,7 +355,7 @@ def test_shell_args_cacert_insecure(self): class TestShellCliPrecedence(TestShellInteg): - """Validate option precedence rules + """Validate option precedence rules without clouds.yaml Global option values may be set in three places: * command line options @@ -368,6 +368,96 @@ class TestShellCliPrecedence(TestShellInteg): def setUp(self): super(TestShellCliPrecedence, self).setUp() + env = { + "OS_AUTH_URL": V3_AUTH_URL, + "OS_PROJECT_DOMAIN_ID": test_shell.DEFAULT_PROJECT_DOMAIN_ID, + "OS_USER_DOMAIN_ID": test_shell.DEFAULT_USER_DOMAIN_ID, + "OS_USERNAME": test_shell.DEFAULT_USERNAME, + "OS_IDENTITY_API_VERSION": "3", + } + self.useFixture(osc_lib_utils.EnvFixture(copy.deepcopy(env))) + + self.token = ksa_fixture.V3Token( + project_domain_id=test_shell.DEFAULT_PROJECT_DOMAIN_ID, + user_domain_id=test_shell.DEFAULT_USER_DOMAIN_ID, + user_name=test_shell.DEFAULT_USERNAME, + ) + + # Set up the v3 auth routes + self.requests_mock.register_uri( + 'GET', + V3_AUTH_URL, + json=V3_VERSION_RESP, + status_code=200, + ) + self.requests_mock.register_uri( + 'POST', + V3_AUTH_URL + 'auth/tokens', + json=self.token, + status_code=200, + ) + + # Patch a v3 auth URL into the o-c-c data + test_shell.PUBLIC_1['public-clouds']['megadodo']['auth']['auth_url'] \ + = V3_AUTH_URL + + def test_shell_args_options(self): + """Verify command line options override environment variables""" + + _shell = shell.OpenStackShell() + _shell.run( + "--os-username zarquon --os-password qaz " + "configuration show".split(), + ) + + # Check general calls + self.assertEqual(len(self.requests_mock.request_history), 2) + + # Check discovery request + self.assertEqual( + V3_AUTH_URL, + self.requests_mock.request_history[0].url, + ) + + # Check auth request + auth_req = self.requests_mock.request_history[1].json() + + # -env, -cli + # No test, everything not specified tests this + + # -env, +cli + self.assertEqual( + 'qaz', + auth_req['auth']['identity']['password']['user']['password'], + ) + + # +env, -cli + self.assertEqual( + test_shell.DEFAULT_PROJECT_DOMAIN_ID, + auth_req['auth']['identity']['password']['user']['domain']['id'], + ) + + # +env, +cli + self.assertEqual( + 'zarquon', + auth_req['auth']['identity']['password']['user']['name'], + ) + + +class TestShellCliPrecedenceOCC(TestShellInteg): + """Validate option precedence rules with clouds.yaml + + Global option values may be set in three places: + * command line options + * environment variables + * clouds.yaml + + Verify that the above order is the precedence used, + i.e. a command line option overrides all others, etc + """ + + def setUp(self): + super(TestShellCliPrecedenceOCC, self).setUp() env = { "OS_CLOUD": "megacloud", "OS_AUTH_URL": V3_AUTH_URL, @@ -403,47 +493,6 @@ def setUp(self): test_shell.PUBLIC_1['public-clouds']['megadodo']['auth']['auth_url'] \ = V3_AUTH_URL - # @mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file") - # @mock.patch("os_client_config.config.OpenStackConfig._load_config_file") - # def test_shell_args_options(self, config_mock, vendor_mock): - # """Verify command line options override environment variables""" - - # _shell = shell.OpenStackShell() - # _shell.run( - # "--os-username zarquon --os-password qaz " - # "configuration show".split(), - # ) - - # # Check general calls - # self.assertEqual(len(self.requests_mock.request_history), 2) - - # # Check discovery request - # self.assertEqual( - # V3_AUTH_URL, - # self.requests_mock.request_history[0].url, - # ) - - # # Check auth request - # auth_req = self.requests_mock.request_history[1].json() - - # # Environment var, no option - # self.assertEqual( - # test_shell.DEFAULT_PROJECT_DOMAIN_ID, - # auth_req['auth']['identity']['password']['user']['domain']['id'], - # ) - - # # Environment var, --os-username override - # self.assertEqual( - # 'zarquon', - # auth_req['auth']['identity']['password']['user']['name'], - # ) - - # # No environment var, --os-password override - # self.assertEqual( - # 'qaz', - # auth_req['auth']['identity']['password']['user']['password'], - # ) - @mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file") @mock.patch("os_client_config.config.OpenStackConfig._load_config_file") def test_shell_args_precedence_1(self, config_mock, vendor_mock): From cf9ad08ab6fd5e6ed5f68208a39b0014b0af5f8a Mon Sep 17 00:00:00 2001 From: Brian Haley Date: Fri, 15 Jul 2016 13:20:10 -0400 Subject: [PATCH 1173/3095] Add Subnet service-types to subnets Add '--service-type' to subnet arguments to support Subnet service-types. Change-Id: I215d83e4d4cf53e03fa35041c5e41a328641b3a9 Partially-implements: blueprint service-subnets --- doc/source/command-objects/subnet.rst | 31 ++++++ functional/tests/network/v2/test_subnet.py | 7 ++ openstackclient/network/v2/subnet.py | 50 +++++++++- openstackclient/tests/network/v2/fakes.py | 1 + .../tests/network/v2/test_subnet.py | 94 ++++++++++++++++++- .../subnet-service-type-8d9c414732e474a4.yaml | 6 ++ 6 files changed, 185 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/subnet-service-type-8d9c414732e474a4.yaml diff --git a/doc/source/command-objects/subnet.rst b/doc/source/command-objects/subnet.rst index e8740e5348..16092bcd27 100644 --- a/doc/source/command-objects/subnet.rst +++ b/doc/source/command-objects/subnet.rst @@ -29,6 +29,7 @@ Create new subnet [--ipv6-ra-mode {dhcpv6-stateful,dhcpv6-stateless,slaac}] [--ipv6-address-mode {dhcpv6-stateful,dhcpv6-stateless,slaac}] [--network-segment ] + [--service-type ] --network @@ -116,6 +117,13 @@ Create new subnet to change. Use global option ``--os-beta-command`` to enable this command option. +.. option:: --service-type + + Service type for this subnet e.g.: + ``network:floatingip_agent_gateway``. + Must be a valid device owner value for a network port + (repeat option to set multiple service types) + .. option:: --network Network this subnet belongs to (name or ID) @@ -171,6 +179,13 @@ List subnets List subnets which have DHCP disabled +.. option:: --service-type + + List only subnets of a given service type in output + e.g.: ``network:floatingip_agent_gateway``. + Must be a valid device owner value for a network port + (repeat option to list multiple service types) + subnet set ---------- @@ -185,6 +200,7 @@ Set subnet properties [--dns-nameserver ] [--gateway ] [--host-route destination=,gateway=] + [--service-type ] [--name ] @@ -221,6 +237,13 @@ Set subnet properties gateway: nexthop IP address (repeat option to add multiple routes) +.. option:: --service-type + + Service type for this subnet e.g.: + ``network:floatingip_agent_gateway``. + Must be a valid device owner value for a network port + (repeat option to set multiple service types) + .. option:: --name Updated name of the subnet @@ -259,6 +282,7 @@ Unset subnet properties [--allocation-pool start=,end= [...]] [--dns-nameserver [...]] [--host-route destination=,gateway= [...]] + [--service-type ] .. option:: --dns-nameserver @@ -280,6 +304,13 @@ Unset subnet properties gateway: nexthop IP address (repeat option to unset multiple host routes) +.. option:: --service-type + + Service type to be removed from this subnet e.g.: + ``network:floatingip_agent_gateway``. + Must be a valid device owner value for a network port + (repeat option to unset multiple service types) + .. _subnet_unset-subnet: .. describe:: diff --git a/functional/tests/network/v2/test_subnet.py b/functional/tests/network/v2/test_subnet.py index 11ae6da19a..7fb48437d9 100644 --- a/functional/tests/network/v2/test_subnet.py +++ b/functional/tests/network/v2/test_subnet.py @@ -53,6 +53,13 @@ def test_subnet_set(self): raw_output = self.openstack('subnet show ' + self.NAME + opts) self.assertEqual("False\n" + self.NAME + "\n", raw_output) + def test_subnet_set_service_type(self): + TYPE = 'network:floatingip_agent_gateway' + self.openstack('subnet set --service-type ' + TYPE + ' ' + self.NAME) + opts = self.get_opts(['name', 'service_types']) + raw_output = self.openstack('subnet show ' + self.NAME + opts) + self.assertEqual(self.NAME + "\n" + TYPE + "\n", raw_output) + def test_subnet_show(self): opts = self.get_opts(self.FIELDS) raw_output = self.openstack('subnet show ' + self.NAME + opts) diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 22452809cf..6feb8aa002 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -48,6 +48,7 @@ def _format_host_routes(data): 'allocation_pools': _format_allocation_pools, 'dns_nameservers': utils.format_list, 'host_routes': _format_host_routes, + 'service_types': utils.format_list, } @@ -82,6 +83,16 @@ def _get_common_parse_arguments(parser): "gateway: nexthop IP address " "(repeat option to add multiple routes)") ) + parser.add_argument( + '--service-type', + metavar='', + action='append', + dest='service_types', + help=_("Service type for this subnet " + "e.g.: network:floatingip_agent_gateway. " + "Must be a valid device owner value for a network port " + "(repeat option to set multiple service types)") + ) def _get_columns(item): @@ -177,6 +188,9 @@ def _get_attrs(client_manager, parsed_args, is_create=True): # Change 'gateway' entry to 'nexthop' to match the API attrs['host_routes'] = convert_entries_to_nexthop( parsed_args.host_routes) + if ('service_types' in parsed_args and + parsed_args.service_types is not None): + attrs['service_types'] = parsed_args.service_types return attrs @@ -352,6 +366,16 @@ def get_parser(self, prog_name): action='store_true', help=_("List subnets which have DHCP disabled") ) + parser.add_argument( + '--service-type', + metavar='', + action='append', + dest='service_types', + help=_("List only subnets of a given service type in output " + "e.g.: network:floatingip_agent_gateway. " + "Must be a valid device owner value for a network port " + "(repeat option to list multiple service types)") + ) return parser def take_action(self, parsed_args): @@ -362,6 +386,8 @@ def take_action(self, parsed_args): filters['enable_dhcp'] = True elif parsed_args.no_dhcp: filters['enable_dhcp'] = False + if parsed_args.service_types: + filters['service_types'] = parsed_args.service_types data = self.app.client_manager.network.subnets(**filters) headers = ('ID', 'Name', 'Network', 'Subnet') @@ -369,10 +395,10 @@ def take_action(self, parsed_args): if parsed_args.long: headers += ('Project', 'DHCP', 'Name Servers', 'Allocation Pools', 'Host Routes', 'IP Version', - 'Gateway') + 'Gateway', 'Service Types') columns += ('tenant_id', 'enable_dhcp', 'dns_nameservers', 'allocation_pools', 'host_routes', 'ip_version', - 'gateway_ip') + 'gateway_ip', 'service_types') return (headers, (utils.get_item_properties( @@ -430,6 +456,8 @@ def take_action(self, parsed_args): attrs['host_routes'] += obj.host_routes if 'allocation_pools' in attrs: attrs['allocation_pools'] += obj.allocation_pools + if 'service_types' in attrs: + attrs['service_types'] += obj.service_types client.update_subnet(obj, **attrs) return @@ -489,6 +517,16 @@ def get_parser(self, prog_name): 'gateway: nexthop IP address ' '(repeat option to unset multiple host routes)') ) + parser.add_argument( + '--service-type', + metavar='', + action='append', + dest='service_types', + help=_('Service type to be removed from this subnet ' + 'e.g.: network:floatingip_agent_gateway. ' + 'Must be a valid device owner value for a network port ' + '(repeat option to unset multiple service types)') + ) parser.add_argument( 'subnet', metavar="", @@ -528,5 +566,13 @@ def take_action(self, parsed_args): str(error)) raise exceptions.CommandError(msg) attrs['allocation_pools'] = tmp_obj.allocation_pools + if parsed_args.service_types: + try: + _update_arguments(tmp_obj.service_types, + parsed_args.service_types) + except ValueError as error: + msg = (_("%s not in service-types") % str(error)) + raise exceptions.CommandError(msg) + attrs['service_types'] = tmp_obj.service_types if attrs: client.update_subnet(obj, **attrs) diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index a6de75e246..33bc401798 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -889,6 +889,7 @@ def create_one_subnet(attrs=None): 'ipv6_address_mode': None, 'ipv6_ra_mode': None, 'segment_id': None, + 'service_types': [], 'subnetpool_id': None, } diff --git a/openstackclient/tests/network/v2/test_subnet.py b/openstackclient/tests/network/v2/test_subnet.py index ba757c9835..c117c6fdca 100644 --- a/openstackclient/tests/network/v2/test_subnet.py +++ b/openstackclient/tests/network/v2/test_subnet.py @@ -61,6 +61,8 @@ class TestCreateSubnet(TestSubnet): 'nexthop': '10.20.20.1'}, {'destination': '10.30.30.0/24', 'nexthop': '10.30.30.1'}], + 'service_types': ['network:router_gateway', + 'network:floatingip_agent_gateway'], } ) @@ -85,6 +87,8 @@ class TestCreateSubnet(TestSubnet): 'ipv6_address_mode': 'slaac', 'ipv6_ra_mode': 'slaac', 'subnetpool_id': 'None', + 'service_types': ['network:router_gateway', + 'network:floatingip_agent_gateway'], } ) @@ -118,6 +122,7 @@ class TestCreateSubnet(TestSubnet): 'network_id', 'project_id', 'segment_id', + 'service_types', 'subnetpool_id', ) @@ -136,6 +141,7 @@ class TestCreateSubnet(TestSubnet): _subnet.network_id, _subnet.project_id, _subnet.segment_id, + utils.format_list(_subnet.service_types), _subnet.subnetpool_id, ) @@ -154,6 +160,7 @@ class TestCreateSubnet(TestSubnet): _subnet_from_pool.network_id, _subnet_from_pool.project_id, _subnet_from_pool.segment_id, + utils.format_list(_subnet_from_pool.service_types), _subnet_from_pool.subnetpool_id, ) @@ -172,6 +179,7 @@ class TestCreateSubnet(TestSubnet): _subnet_ipv6.network_id, _subnet_ipv6.project_id, _subnet_ipv6.segment_id, + utils.format_list(_subnet_ipv6.service_types), _subnet_ipv6.subnetpool_id, ) @@ -259,6 +267,10 @@ def test_create_from_subnet_pool_options(self): ',destination=' + host_route.get('destination', '') arglist.append(value) + for service_type in self._subnet_from_pool.service_types: + arglist.append('--service-type') + arglist.append(service_type) + verifylist = [ ('name', self._subnet_from_pool.name), ('prefix_length', '24'), @@ -270,6 +282,7 @@ def test_create_from_subnet_pool_options(self): ('host_routes', subnet_v2.convert_entries_to_gateway( self._subnet_from_pool.host_routes)), ('subnet_pool', self._subnet_from_pool.subnetpool_id), + ('service_types', self._subnet_from_pool.service_types), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -285,6 +298,7 @@ def test_create_from_subnet_pool_options(self): 'network_id': self._subnet_from_pool.network_id, 'prefixlen': '24', 'subnetpool_id': self._subnet_from_pool.subnetpool_id, + 'service_types': self._subnet_from_pool.service_types, }) self.assertEqual(self.columns, columns) self.assertEqual(self.data_subnet_pool, data) @@ -321,6 +335,10 @@ def test_create_options_subnet_range_ipv6(self): ',end=' + pool.get('end', '') arglist.append(value) + for service_type in self._subnet_ipv6.service_types: + arglist.append('--service-type') + arglist.append(service_type) + verifylist = [ ('name', self._subnet_ipv6.name), ('subnet_range', self._subnet_ipv6.cidr), @@ -334,6 +352,7 @@ def test_create_options_subnet_range_ipv6(self): ('host_routes', subnet_v2.convert_entries_to_gateway( self._subnet_ipv6.host_routes)), ('allocation_pools', self._subnet_ipv6.allocation_pools), + ('service_types', self._subnet_ipv6.service_types), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -351,6 +370,7 @@ def test_create_options_subnet_range_ipv6(self): 'name': self._subnet_ipv6.name, 'network_id': self._subnet_ipv6.network_id, 'allocation_pools': self._subnet_ipv6.allocation_pools, + 'service_types': self._subnet_ipv6.service_types, }) self.assertEqual(self.columns, columns) self.assertEqual(self.data_ipv6, data) @@ -505,6 +525,7 @@ class TestListSubnet(TestSubnet): 'Host Routes', 'IP Version', 'Gateway', + 'Service Types', ) data = [] @@ -530,6 +551,7 @@ class TestListSubnet(TestSubnet): utils.format_list(subnet.host_routes), subnet.ip_version, subnet.gateway_ip, + utils.format_list(subnet.service_types), )) def setUp(self): @@ -616,6 +638,41 @@ def test_subnet_list_no_dhcp(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + def test_subnet_list_service_type(self): + arglist = [ + '--service-type', 'network:router_gateway', + ] + verifylist = [ + ('service_types', ['network:router_gateway']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'service_types': ['network:router_gateway']} + + self.network.subnets.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_subnet_list_service_type_multiple(self): + arglist = [ + '--service-type', 'network:router_gateway', + '--service-type', 'network:floatingip_agent_gateway', + ] + verifylist = [ + ('service_types', ['network:router_gateway', + 'network:floatingip_agent_gateway']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'service_types': ['network:router_gateway', + 'network:floatingip_agent_gateway']} + + self.network.subnets.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + class TestSetSubnet(TestSubnet): @@ -688,19 +745,24 @@ def test_set_nothing(self): def test_append_options(self): _testsubnet = network_fakes.FakeSubnet.create_one_subnet( - {'dns_nameservers': ["10.0.0.1"]}) + {'dns_nameservers': ["10.0.0.1"], + 'service_types': ["network:router_gateway"]}) self.network.find_subnet = mock.Mock(return_value=_testsubnet) arglist = [ '--dns-nameserver', '10.0.0.2', + '--service-type', 'network:floatingip_agent_gateway', _testsubnet.name, ] verifylist = [ ('dns_nameservers', ['10.0.0.2']), + ('service_types', ['network:floatingip_agent_gateway']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) attrs = { 'dns_nameservers': ['10.0.0.2', '10.0.0.1'], + 'service_types': ['network:floatingip_agent_gateway', + 'network:router_gateway'], } self.network.update_subnet.assert_called_once_with( _testsubnet, **attrs) @@ -726,6 +788,7 @@ class TestShowSubnet(TestSubnet): 'network_id', 'project_id', 'segment_id', + 'service_types', 'subnetpool_id', ) @@ -744,6 +807,7 @@ class TestShowSubnet(TestSubnet): _subnet.network_id, _subnet.tenant_id, _subnet.segment_id, + utils.format_list(_subnet.service_types), _subnet.subnetpool_id, ) @@ -796,7 +860,9 @@ def setUp(self): 'allocation_pools': [{'start': '8.8.8.100', 'end': '8.8.8.150'}, {'start': '8.8.8.160', - 'end': '8.8.8.170'}], }) + 'end': '8.8.8.170'}], + 'service_types': ['network:router_gateway', + 'network:floatingip_agent_gateway'], }) self.network.find_subnet = mock.Mock(return_value=self._testsubnet) self.network.update_subnet = mock.Mock(return_value=None) # Get the command object to test @@ -807,6 +873,7 @@ def test_unset_subnet_params(self): '--dns-nameserver', '8.8.8.8', '--host-route', 'destination=10.30.30.30/24,gateway=10.30.30.1', '--allocation-pool', 'start=8.8.8.100,end=8.8.8.150', + '--service-type', 'network:router_gateway', self._testsubnet.name, ] verifylist = [ @@ -815,6 +882,7 @@ def test_unset_subnet_params(self): "destination": "10.30.30.30/24", "gateway": "10.30.30.1"}]), ('allocation_pools', [{ 'start': '8.8.8.100', 'end': '8.8.8.150'}]), + ('service_types', ['network:router_gateway']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -825,6 +893,7 @@ def test_unset_subnet_params(self): 'host_routes': [{ "destination": "10.20.20.0/24", "nexthop": "10.20.20.1"}], 'allocation_pools': [{'start': '8.8.8.160', 'end': '8.8.8.170'}], + 'service_types': ['network:floatingip_agent_gateway'], } self.network.update_subnet.assert_called_once_with( self._testsubnet, **attrs) @@ -886,3 +955,24 @@ def test_unset_subnet_wrong_dns_nameservers(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) + + def test_unset_subnet_wrong_service_type(self): + arglist = [ + '--dns-nameserver', '8.8.8.8', + '--host-route', 'destination=10.30.30.30/24,gateway=10.30.30.1', + '--allocation-pool', 'start=8.8.8.100,end=8.8.8.150', + '--service-type', 'network:dhcp', + self._testsubnet.name, + ] + verifylist = [ + ('dns_nameservers', ['8.8.8.8']), + ('host_routes', [{ + "destination": "10.30.30.30/24", "gateway": "10.30.30.1"}]), + ('allocation_pools', [{ + 'start': '8.8.8.100', 'end': '8.8.8.150'}]), + ('service_types', ['network:dhcp']), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, parsed_args) diff --git a/releasenotes/notes/subnet-service-type-8d9c414732e474a4.yaml b/releasenotes/notes/subnet-service-type-8d9c414732e474a4.yaml new file mode 100644 index 0000000000..743c72576c --- /dev/null +++ b/releasenotes/notes/subnet-service-type-8d9c414732e474a4.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--service-type`` option to the ``subnet create``, + ``subnet set``, ``subnet unset``, and ``subnet list`` commands. + [ Blueprint `service-subnets `_] From 2f0d419e7c593233aaf9cc8fdf08a9d7886c2f33 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sun, 21 Aug 2016 00:10:10 +0000 Subject: [PATCH 1174/3095] Updated from global requirements Change-Id: Ia8340e97b90f7497244f57c7ab7e46d7a461b896 --- requirements.txt | 4 ++-- test-requirements.txt | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/requirements.txt b/requirements.txt index 3f8ec2d61c..be057a3b4f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ openstacksdk>=0.9.0 # Apache-2.0 osc-lib>=0.4.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 oslo.utils>=3.16.0 # Apache-2.0 -python-glanceclient>=2.0.0 # Apache-2.0 -python-keystoneclient!=1.8.0,!=2.1.0,>=1.7.0 # Apache-2.0 +python-glanceclient!=2.4.0,>=2.0.0 # Apache-2.0 +python-keystoneclient!=2.1.0,>=2.0.0 # Apache-2.0 python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0 python-cinderclient!=1.7.0,!=1.7.1,>=1.6.0 # Apache-2.0 diff --git a/test-requirements.txt b/test-requirements.txt index efd236559b..7db1d62383 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -13,13 +13,13 @@ requests>=2.10.0 # Apache-2.0 requests-mock>=1.0 # Apache-2.0 sphinx!=1.3b1,<1.3,>=1.2.1 # BSD stevedore>=1.16.0 # Apache-2.0 -os-client-config!=1.19.0,>=1.13.1 # Apache-2.0 +os-client-config!=1.19.0,!=1.19.1,!=1.20.0,>=1.13.1 # Apache-2.0 os-testr>=0.7.0 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD testtools>=1.4.0 # MIT tempest>=12.1.0 # Apache-2.0 -osprofiler>=1.3.0 # Apache-2.0 -bandit>=1.0.1 # Apache-2.0 +osprofiler>=1.4.0 # Apache-2.0 +bandit>=1.1.0 # Apache-2.0 wrapt>=1.7.0 # BSD License # Install these to generate sphinx autodocs @@ -32,7 +32,7 @@ python-ironicclient>=1.6.0 # Apache-2.0 python-ironic-inspector-client>=1.5.0 # Apache-2.0 python-mistralclient>=2.0.0 # Apache-2.0 python-muranoclient>=0.8.2 # Apache-2.0 -python-neutronclient>=4.2.0 # Apache-2.0 +python-neutronclient>=5.1.0 # Apache-2.0 python-saharaclient>=0.16.0 # Apache-2.0 python-searchlightclient>=0.2.0 #Apache-2.0 python-senlinclient>=0.3.0 # Apache-2.0 From ea5a8dd80be910663e50d62d8520a94b21d68d72 Mon Sep 17 00:00:00 2001 From: Andreas Jaeger Date: Mon, 22 Aug 2016 09:04:09 +0200 Subject: [PATCH 1175/3095] Fix post and periodic jobs The usage of zuul-cloner in tox_install breaks post and periodic jobs. Update the script, it does not need to handle unconstrainted installs anymore (see tox.ini). There's no need to set ZUUL_BRANCH explicitely - it's set via the environment and branch is passed in as well. Note that this script is needed for the edit-constraints call, add a comment. Change-Id: I0077c986a17d6bb92791474e03d1e77776e9382f Closes-Bug: #1615430 --- tools/tox_install.sh | 66 ++++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/tools/tox_install.sh b/tools/tox_install.sh index c96b023c19..53acc4d5ec 100755 --- a/tools/tox_install.sh +++ b/tools/tox_install.sh @@ -15,41 +15,41 @@ CONSTRAINTS_FILE=$1 shift install_cmd="pip install" -if [ $CONSTRAINTS_FILE != "unconstrained" ]; then - - mydir=$(mktemp -dt "$CLIENT_NAME-tox_install-XXXXXXX") - localfile=$mydir/upper-constraints.txt - if [[ $CONSTRAINTS_FILE != http* ]]; then - CONSTRAINTS_FILE=file://$CONSTRAINTS_FILE - fi - curl $CONSTRAINTS_FILE -k -o $localfile - install_cmd="$install_cmd -c$localfile" - - if [ $requirements_installed -eq 0 ]; then - echo "ALREADY INSTALLED" > /tmp/tox_install.txt - echo "Requirements already installed; using existing package" - elif [ -x "$ZUUL_CLONER" ]; then - export ZUUL_BRANCH=${ZUUL_BRANCH-$BRANCH} - echo "ZUUL CLONER" > /tmp/tox_install.txt - pushd $mydir - $ZUUL_CLONER --cache-dir \ - /opt/git \ - --branch $BRANCH_NAME \ - git://git.openstack.org \ - openstack/requirements - cd openstack/requirements - $install_cmd -e . - popd - else - echo "PIP HARDCODE" > /tmp/tox_install.txt - if [ -z "$REQUIREMENTS_PIP_LOCATION" ]; then - REQUIREMENTS_PIP_LOCATION="git+https://git.openstack.org/openstack/requirements@$BRANCH_NAME#egg=requirements" - fi - $install_cmd -U -e ${REQUIREMENTS_PIP_LOCATION} +mydir=$(mktemp -dt "$CLIENT_NAME-tox_install-XXXXXXX") +localfile=$mydir/upper-constraints.txt +if [[ $CONSTRAINTS_FILE != http* ]]; then + CONSTRAINTS_FILE=file://$CONSTRAINTS_FILE +fi +curl $CONSTRAINTS_FILE -k -o $localfile +install_cmd="$install_cmd -c$localfile" + +if [ $requirements_installed -eq 0 ]; then + echo "ALREADY INSTALLED" > /tmp/tox_install.txt + echo "Requirements already installed; using existing package" +elif [ -x "$ZUUL_CLONER" ]; then + # If this is called in a periodic job, these will not be set + echo "ZUUL CLONER" > /tmp/tox_install.txt + pushd $mydir + $ZUUL_CLONER --cache-dir \ + /opt/git \ + --branch $BRANCH_NAME \ + git://git.openstack.org \ + openstack/requirements + cd openstack/requirements + $install_cmd -e . + popd +else + echo "PIP HARDCODE" > /tmp/tox_install.txt + if [ -z "$REQUIREMENTS_PIP_LOCATION" ]; then + REQUIREMENTS_PIP_LOCATION="git+https://git.openstack.org/openstack/requirements@$BRANCH_NAME#egg=requirements" fi - - edit-constraints $localfile -- $CLIENT_NAME "-e file://$PWD#egg=$CLIENT_NAME" + $install_cmd -U -e ${REQUIREMENTS_PIP_LOCATION} fi +# This is the main purpose of the script: Allow local installation of +# the current repo. It is listed in constraints file and thus any +# install will be constrained and we need to unconstrain it. +edit-constraints $localfile -- $CLIENT_NAME "-e file://$PWD#egg=$CLIENT_NAME" + $install_cmd -U $* exit $? From 684412ca4cc0abad2c2a800d8247d12992b994e5 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Mon, 22 Aug 2016 08:29:29 -0500 Subject: [PATCH 1176/3095] Work around a version clash issue with os-client-config Need to add the fixed_arguments arg to _validate_auth() so os-client-config 1.19.1 and 1.20.0 can call our version properly. Change-Id: I328e47ba2f8115e6b18bf1482fd4aa35056907a4 --- openstackclient/common/client_config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openstackclient/common/client_config.py b/openstackclient/common/client_config.py index cc6cad5899..bbcb34eb7b 100644 --- a/openstackclient/common/client_config.py +++ b/openstackclient/common/client_config.py @@ -148,11 +148,11 @@ def auth_config_hook(self, config): LOG.debug("auth_config_hook(): %s" % config) return config - def _validate_auth_ksc(self, config, cloud): + def _validate_auth_ksc(self, config, cloud, fixed_argparse=None): """Old compatibility hack for OSC, no longer needed/wanted""" return config - def _validate_auth(self, config, loader): + def _validate_auth(self, config, loader, fixed_argparse=None): """Validate auth plugin arguments""" # May throw a keystoneauth1.exceptions.NoMatchingPlugin From 8f07fec3f2ccf510255f1ff2e4dab233be60d0f9 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Fri, 19 Aug 2016 21:56:24 +0800 Subject: [PATCH 1177/3095] Clean up FakeClient classes in volume fakes Put FakeTransferClient and FakeServiceClient classes into FakeVolumeClient class, I think we need not to create FakeClient classes for every objects, we can use only FakeVolumeClient for all volume objects. This can reduce repetition, unified codes and make codes look cleaner and easier. Change-Id: I2729ef0e80166f4d49ccd9a48b653e2f215e3bfe --- openstackclient/tests/volume/v1/fakes.py | 39 +----------------- .../tests/volume/v1/test_service.py | 2 +- .../tests/volume/v1/test_transfer_request.py | 2 +- openstackclient/tests/volume/v2/fakes.py | 41 ++----------------- .../tests/volume/v2/test_service.py | 2 +- .../tests/volume/v2/test_transfer_request.py | 2 +- 6 files changed, 10 insertions(+), 78 deletions(-) diff --git a/openstackclient/tests/volume/v1/fakes.py b/openstackclient/tests/volume/v1/fakes.py index 2584d4b10d..b96f925dce 100644 --- a/openstackclient/tests/volume/v1/fakes.py +++ b/openstackclient/tests/volume/v1/fakes.py @@ -129,25 +129,6 @@ } -class FakeTransferClient(object): - - def __init__(self, **kwargs): - - self.transfers = mock.Mock() - self.transfers.resource_class = fakes.FakeResource(None, {}) - - -class TestTransfer(utils.TestCommand): - - def setUp(self): - super(TestTransfer, self).setUp() - - self.app.client_manager.volume = FakeTransferClient( - endpoint=fakes.AUTH_URL, - token=fakes.AUTH_TOKEN - ) - - class FakeTransfer(object): """Fake one or more Transfer.""" @@ -180,24 +161,6 @@ def create_one_transfer(attrs=None): return transfer -class FakeServiceClient(object): - - def __init__(self, **kwargs): - self.services = mock.Mock() - self.services.resource_class = fakes.FakeResource(None, {}) - - -class TestService(utils.TestCommand): - - def setUp(self): - super(TestService, self).setUp() - - self.app.client_manager.volume = FakeServiceClient( - endpoint=fakes.AUTH_URL, - token=fakes.AUTH_TOKEN - ) - - class FakeService(object): """Fake one or more Services.""" @@ -290,6 +253,8 @@ def __init__(self, **kwargs): self.qos_specs.resource_class = fakes.FakeResource(None, {}) self.volume_types = mock.Mock() self.volume_types.resource_class = fakes.FakeResource(None, {}) + self.transfers = mock.Mock() + self.transfers.resource_class = fakes.FakeResource(None, {}) self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] diff --git a/openstackclient/tests/volume/v1/test_service.py b/openstackclient/tests/volume/v1/test_service.py index 7168434496..eb7118a6d4 100644 --- a/openstackclient/tests/volume/v1/test_service.py +++ b/openstackclient/tests/volume/v1/test_service.py @@ -17,7 +17,7 @@ from openstackclient.volume.v1 import service -class TestService(service_fakes.TestService): +class TestService(service_fakes.TestVolumev1): def setUp(self): super(TestService, self).setUp() diff --git a/openstackclient/tests/volume/v1/test_transfer_request.py b/openstackclient/tests/volume/v1/test_transfer_request.py index 94e02d62c9..91f5f4b26a 100644 --- a/openstackclient/tests/volume/v1/test_transfer_request.py +++ b/openstackclient/tests/volume/v1/test_transfer_request.py @@ -17,7 +17,7 @@ from openstackclient.volume.v1 import volume_transfer_request -class TestTransfer(transfer_fakes.TestTransfer): +class TestTransfer(transfer_fakes.TestVolumev1): def setUp(self): super(TestTransfer, self).setUp() diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py index 6809bebd3f..49384bd8ff 100644 --- a/openstackclient/tests/volume/v2/fakes.py +++ b/openstackclient/tests/volume/v2/fakes.py @@ -25,25 +25,6 @@ from openstackclient.tests import utils -class FakeTransferClient(object): - - def __init__(self, **kwargs): - - self.transfers = mock.Mock() - self.transfers.resource_class = fakes.FakeResource(None, {}) - - -class TestTransfer(utils.TestCommand): - - def setUp(self): - super(TestTransfer, self).setUp() - - self.app.client_manager.volume = FakeTransferClient( - endpoint=fakes.AUTH_URL, - token=fakes.AUTH_TOKEN - ) - - class FakeTransfer(object): """Fake one or more Transfer.""" @@ -108,24 +89,6 @@ def create_one_type_access(attrs=None): return type_access -class FakeServiceClient(object): - - def __init__(self, **kwargs): - self.services = mock.Mock() - self.services.resource_class = fakes.FakeResource(None, {}) - - -class TestService(utils.TestCommand): - - def setUp(self): - super(TestService, self).setUp() - - self.app.client_manager.volume = FakeServiceClient( - endpoint=fakes.AUTH_URL, - token=fakes.AUTH_TOKEN - ) - - class FakeService(object): """Fake one or more Services.""" @@ -200,6 +163,10 @@ def __init__(self, **kwargs): self.qos_specs.resource_class = fakes.FakeResource(None, {}) self.availability_zones = mock.Mock() self.availability_zones.resource_class = fakes.FakeResource(None, {}) + self.transfers = mock.Mock() + self.transfers.resource_class = fakes.FakeResource(None, {}) + self.services = mock.Mock() + self.services.resource_class = fakes.FakeResource(None, {}) self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] diff --git a/openstackclient/tests/volume/v2/test_service.py b/openstackclient/tests/volume/v2/test_service.py index ba2e1b3217..93ddf52879 100644 --- a/openstackclient/tests/volume/v2/test_service.py +++ b/openstackclient/tests/volume/v2/test_service.py @@ -17,7 +17,7 @@ from openstackclient.volume.v2 import service -class TestService(service_fakes.TestService): +class TestService(service_fakes.TestVolume): def setUp(self): super(TestService, self).setUp() diff --git a/openstackclient/tests/volume/v2/test_transfer_request.py b/openstackclient/tests/volume/v2/test_transfer_request.py index 945833c939..ea39caa729 100644 --- a/openstackclient/tests/volume/v2/test_transfer_request.py +++ b/openstackclient/tests/volume/v2/test_transfer_request.py @@ -17,7 +17,7 @@ from openstackclient.volume.v2 import volume_transfer_request -class TestTransfer(transfer_fakes.TestTransfer): +class TestTransfer(transfer_fakes.TestVolume): def setUp(self): super(TestTransfer, self).setUp() From 50a5c2a1636c58fb5fa608fa0c2493ab960ba0ff Mon Sep 17 00:00:00 2001 From: KATO Tomoyuki Date: Wed, 24 Aug 2016 16:35:06 +0900 Subject: [PATCH 1178/3095] [docs] fix incorrect rst markups The incorrect md-style markups break Sphinx builds, which do not properly generate web page. http://docs.openstack.org/developer/python-openstackclient/plugin-commands.html Change-Id: I36ae2cf922836cff42653283c0a683359bd91344 --- doc/source/plugin-commands.rst | 72 +++++++++++++++++++++++----------- 1 file changed, 50 insertions(+), 22 deletions(-) diff --git a/doc/source/plugin-commands.rst b/doc/source/plugin-commands.rst index 614e28d924..4cb13e83ea 100644 --- a/doc/source/plugin-commands.rst +++ b/doc/source/plugin-commands.rst @@ -1,85 +1,113 @@ -================= - Plugin Commands -================= +=============== +Plugin Commands +=============== Note: To see the complete syntax for the plugin commands, see the `CLI_Ref`_ .. list-plugins:: openstack.cli.extension -# aodh +aodh +---- + .. list-plugins:: openstack.alarming.v2 :detailed: -# barbican +barbican +-------- + .. list-plugins:: openstack.key_manager.v1 :detailed: -# congress +congress +-------- + .. list-plugins:: openstack.congressclient.v1 :detailed: -# cue +.. cue .. # cueclient is not in global-requirements .. # list-plugins:: openstack.mb.v1 .. # :detailed: -# designate +designate +--------- + .. list-plugins:: openstack.dns.v1 :detailed: -# gnocchi +.. gnocchi .. # gnocchiclient is not in global-requirements .. # list-plugins:: openstack.metric.v1 .. # :detailed: -# heat +heat +---- + .. list-plugins:: openstack.orchestration.v1 :detailed: -# ironic +ironic +------ + .. list-plugins:: openstack.baremetal.v1 :detailed: -# ironic-inspector +ironic-inspector +---------------- + .. list-plugins:: openstack.baremetal_introspection.v1 :detailed: -# mistral +mistral +------- + .. list-plugins:: openstack.workflow_engine.v2 :detailed: -# murano +murano +------ + .. list-plugins:: openstack.application_catalog.v1 :detailed: -# neutron +neutron +------- + .. list-plugins:: openstack.neutronclient.v2 :detailed: -# sahara +sahara +------ + .. list-plugins:: openstack.data_processing.v1 :detailed: -# searchlight +searchlight +----------- + .. list-plugins:: openstack.search.v1 :detailed: -# senlin +senlin +------ + .. list-plugins:: openstack.clustering.v1 :detailed: -# tripleo +.. tripleo .. # tripleoclient is not in global-requirements .. # list-plugins:: openstack.tripleoclient.v1 .. # :detailed: -# watcher +.. watcher .. # watcherclient is not in global-requirements .. # list-plugins:: openstack.infra_optim.v1 .. # :detailed: -# zaqar +zaqar +----- + .. list-plugins:: openstack.messaging.v1 :detailed: -.. _CLI_Ref: http://docs.openstack.org/cli-reference/openstack.html \ No newline at end of file +.. _CLI_Ref: http://docs.openstack.org/cli-reference/openstack.html From 3f86cc0d80228d80c7e3b866c60abedaa58fbbd4 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 24 Aug 2016 13:35:57 +0000 Subject: [PATCH 1179/3095] Updated from global requirements Change-Id: I53797c068d919027c7cfadcb245ab06eb9affc9e --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 7db1d62383..24b693c4ba 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -13,7 +13,7 @@ requests>=2.10.0 # Apache-2.0 requests-mock>=1.0 # Apache-2.0 sphinx!=1.3b1,<1.3,>=1.2.1 # BSD stevedore>=1.16.0 # Apache-2.0 -os-client-config!=1.19.0,!=1.19.1,!=1.20.0,>=1.13.1 # Apache-2.0 +os-client-config!=1.19.0,!=1.19.1,!=1.20.0,!=1.20.1,>=1.13.1 # Apache-2.0 os-testr>=0.7.0 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD testtools>=1.4.0 # MIT From 188709c6688b6baa0b9e3c09f4dda745ab1e700e Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 24 Aug 2016 15:26:39 -0500 Subject: [PATCH 1180/3095] Restore default auth-type for token/endpoint The split to osc-lib shell lost the detection of --os-token and --os-url to set --os-auth-type token_endpoint Closes-bug: 1615988 Change-Id: I248f776a3a7b276195c162818f41ba20760ee545 --- openstackclient/shell.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 67c51998c8..da58b63bf7 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -61,8 +61,12 @@ def build_option_parser(self, description, version): def _final_defaults(self): super(OpenStackShell, self)._final_defaults() - # Set default auth type to password - self._auth_type = 'password' + # Set the default plugin to token_endpoint if url and token are given + if (self.options.url and self.options.token): + # Use service token authentication + self._auth_type = 'token_endpoint' + else: + self._auth_type = 'password' def _load_plugins(self): """Load plugins via stevedore From afda6f846fc3abea47c83e6384974cb9bdb8db3b Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 25 Aug 2016 01:11:34 +0000 Subject: [PATCH 1181/3095] Updated from global requirements Change-Id: I14eec90a7bfe8301b8698dd4337f3748c3732b78 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index be057a3b4f..0851704277 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ Babel>=2.3.4 # BSD cliff!=1.16.0,!=1.17.0,>=1.15.0 # Apache-2.0 keystoneauth1>=2.10.0 # Apache-2.0 openstacksdk>=0.9.0 # Apache-2.0 -osc-lib>=0.4.0 # Apache-2.0 +osc-lib>=1.0.2 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 oslo.utils>=3.16.0 # Apache-2.0 python-glanceclient!=2.4.0,>=2.0.0 # Apache-2.0 From f854b7d6ea1c67cf7f313e039691c4cb40ff1236 Mon Sep 17 00:00:00 2001 From: Cao Xuan Hoang Date: Thu, 25 Aug 2016 13:50:38 +0700 Subject: [PATCH 1182/3095] Clean imports in code In some part in the code we import objects. In the Openstack style guidelines they recommend to import only modules. http://docs.openstack.org/developer/hacking/#imports Change-Id: I2eb35dc53f0fdb61c31022bb70293d1df8aaf482 --- openstackclient/common/client_config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openstackclient/common/client_config.py b/openstackclient/common/client_config.py index bbcb34eb7b..9e98adc652 100644 --- a/openstackclient/common/client_config.py +++ b/openstackclient/common/client_config.py @@ -15,7 +15,7 @@ import logging -from os_client_config.config import OpenStackConfig +from os_client_config import config LOG = logging.getLogger(__name__) @@ -23,7 +23,7 @@ # Sublcass OpenStackConfig in order to munge config values # before auth plugins are loaded -class OSC_Config(OpenStackConfig): +class OSC_Config(config.OpenStackConfig): def _auth_select_default_plugin(self, config): """Select a default plugin based on supplied arguments From 2774145e441757931a2ece90b308fb8cf7101c61 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 25 Aug 2016 09:31:05 +0000 Subject: [PATCH 1183/3095] Updated from global requirements Change-Id: I87cd3d1aaf2d7e8706b4e89ef679c03de69cf611 --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 0851704277..f065346eab 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,11 +7,11 @@ six>=1.9.0 # MIT Babel>=2.3.4 # BSD cliff!=1.16.0,!=1.17.0,>=1.15.0 # Apache-2.0 keystoneauth1>=2.10.0 # Apache-2.0 -openstacksdk>=0.9.0 # Apache-2.0 +openstacksdk>=0.9.4 # Apache-2.0 osc-lib>=1.0.2 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 oslo.utils>=3.16.0 # Apache-2.0 -python-glanceclient!=2.4.0,>=2.0.0 # Apache-2.0 +python-glanceclient!=2.4.0,>=2.3.0 # Apache-2.0 python-keystoneclient!=2.1.0,>=2.0.0 # Apache-2.0 python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0 python-cinderclient!=1.7.0,!=1.7.1,>=1.6.0 # Apache-2.0 From c97e8187fef91627a1c55236a45eb5b1b8d92fc2 Mon Sep 17 00:00:00 2001 From: Atsushi SAKAI Date: Thu, 25 Aug 2016 19:48:13 +0900 Subject: [PATCH 1184/3095] Fix six typos disassoiate => disassociate nmaes => names mutiually => mutually aviod => avoid CLustering => Clustering availble => available Change-Id: I84545bf46acfccc9dde3e85020700edb5a8375a6 --- doc/source/command-objects/volume-qos.rst | 2 +- doc/source/command-options.rst | 6 +++--- doc/source/commands.rst | 2 +- doc/source/plugins.rst | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/source/command-objects/volume-qos.rst b/doc/source/command-objects/volume-qos.rst index f3c7ec2118..54e509662d 100644 --- a/doc/source/command-objects/volume-qos.rst +++ b/doc/source/command-objects/volume-qos.rst @@ -74,7 +74,7 @@ volume qos disassociate Disassociate a QoS specification from a volume type -.. program:: volume qos disassoiate +.. program:: volume qos disassociate .. code:: bash os volume qos disassociate diff --git a/doc/source/command-options.rst b/doc/source/command-options.rst index ebfeb5aa9c..a833d1d5b7 100644 --- a/doc/source/command-options.rst +++ b/doc/source/command-options.rst @@ -17,7 +17,7 @@ new command without understanding why or why not that instance is correct. The :doc:`Human Interface Guide ` describes the guildelines for option names and usage. In short: * All option names shall be GNU-style long names (two leading dashes). - * Some global options may have short nmaes, generally limited to those defined + * Some global options may have short names, generally limited to those defined in support libraries such as ``cliff``. General Command Options @@ -36,7 +36,7 @@ prepended (such as in the traditional GNU option usage) like `--share` and In order to handle those APIs that behave differently when a field is set to `None` and when the field is not present in a passed argument list or dict, each of the boolean options shall set its own variable to `True` as part of -a mutiually exclusive group, rather than the more common configuration of +a mutually exclusive group, rather than the more common configuration of setting a single destination variable `True` or `False` directly. This allows us to detect the situation when neither option is present (both variables will be `False`) and act accordingly for those APIs where this matters. @@ -218,7 +218,7 @@ Some options have no default value and the API does not allow them to be `None`, then these options are always required when users use the command to which these options belong. -Required options must be validated by the CLI to aviod omissions. The CLI +Required options must be validated by the CLI to avoid omissions. The CLI validation may provide an error message for the user if a required option is not specified. (for example: ``error: argument --test is required``) diff --git a/doc/source/commands.rst b/doc/source/commands.rst index cf2a7aff6d..023be8791d 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -162,7 +162,7 @@ list check out :doc:`plugin-commands`. * ``cluster event``: (**Clustering (Senlin)**) * ``cluster members``: (**Clustering (Senlin)**) * ``cluster node``: (**Clustering (Senlin)**) -* ``cluster policy``: (**CLustering (Senlin)**) +* ``cluster policy``: (**Clustering (Senlin)**) * ``cluster policy binding``: (**Clustering (Senlin)**) * ``cluster policy type``: (**Clustering (Senlin)**) * ``cluster profile``: (**Clustering (Senlin)**) diff --git a/doc/source/plugins.rst b/doc/source/plugins.rst index 9a05bb96de..a19eea9ad4 100644 --- a/doc/source/plugins.rst +++ b/doc/source/plugins.rst @@ -169,7 +169,7 @@ the plugin commands: ... def take_action(self, parsed_args): - # Client manager interfaces are availble to plugins. + # Client manager interfaces are available to plugins. # This includes the OSC clients created. client_manager = self.app.client_manager From 9962403d3c52c96ef02e110a056c4a0c21bc5ab7 Mon Sep 17 00:00:00 2001 From: Andreas Jaeger Date: Sun, 28 Aug 2016 08:42:07 +0200 Subject: [PATCH 1185/3095] Cleanup after install Tox tests are run on developer machines and on long lived slaves. We should not leave lots of directories like /tmp/python-openstackclient-tox_install-sdnltRu lying around. Instead delete the temporary directory after our run. Remove also an obsolete comment. Change-Id: I939eae82dba3287fd4e4086128ebf4609a0e0770 --- tools/tox_install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/tox_install.sh b/tools/tox_install.sh index 53acc4d5ec..4af3be166d 100755 --- a/tools/tox_install.sh +++ b/tools/tox_install.sh @@ -16,6 +16,7 @@ shift install_cmd="pip install" mydir=$(mktemp -dt "$CLIENT_NAME-tox_install-XXXXXXX") +trap "rm -rf $mydir" EXIT localfile=$mydir/upper-constraints.txt if [[ $CONSTRAINTS_FILE != http* ]]; then CONSTRAINTS_FILE=file://$CONSTRAINTS_FILE @@ -27,7 +28,6 @@ if [ $requirements_installed -eq 0 ]; then echo "ALREADY INSTALLED" > /tmp/tox_install.txt echo "Requirements already installed; using existing package" elif [ -x "$ZUUL_CLONER" ]; then - # If this is called in a periodic job, these will not be set echo "ZUUL CLONER" > /tmp/tox_install.txt pushd $mydir $ZUUL_CLONER --cache-dir \ From 5b14741fcc8a27dbd886c647ecc474480ae2d1f0 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Mon, 29 Aug 2016 10:57:56 +0000 Subject: [PATCH 1186/3095] Updated from global requirements Change-Id: I90afe332e959ba8bbfb1f04cc84454d30a4cf4a8 --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 24b693c4ba..ff7b198123 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -27,7 +27,7 @@ aodhclient>=0.5.0 # Apache-2.0 python-barbicanclient>=4.0.0 # Apache-2.0 python-congressclient<2000,>=1.3.0 # Apache-2.0 python-designateclient>=1.5.0 # Apache-2.0 -python-heatclient>=1.1.0 # Apache-2.0 +python-heatclient>=1.4.0 # Apache-2.0 python-ironicclient>=1.6.0 # Apache-2.0 python-ironic-inspector-client>=1.5.0 # Apache-2.0 python-mistralclient>=2.0.0 # Apache-2.0 From bec206fa0a0214d856259661c5e32086f33d2f62 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Mon, 29 Aug 2016 11:07:49 -0500 Subject: [PATCH 1187/3095] Fix auth prompt brokenness We start by fixing this in the already-present OSC_Config class so OSC can move forward. This change needs to get ported down into os-client-config in the near future, maybe even soon enough to make the client library freeze this week. * Add the pw-func argument to the OSC_Config (or OpenStackConfig) __init__() * When looping through the auth options from the KSA plugin look for any that have a prompt defined and do not have a value already, so ask for one. Closes-bug: #1617384 Change-Id: Ic86d56b8a6844516292fb74513712b486fec4442 --- openstackclient/common/client_config.py | 43 +++++++++++++++++ openstackclient/common/clientmanager.py | 2 - openstackclient/shell.py | 2 +- openstackclient/tests/test_shell_integ.py | 58 +++++++++++++++++++++++ 4 files changed, 102 insertions(+), 3 deletions(-) diff --git a/openstackclient/common/client_config.py b/openstackclient/common/client_config.py index bbcb34eb7b..bc2314385b 100644 --- a/openstackclient/common/client_config.py +++ b/openstackclient/common/client_config.py @@ -25,6 +25,40 @@ # before auth plugins are loaded class OSC_Config(OpenStackConfig): + # TODO(dtroyer): Once os-client-config with pw_func argument is in + # global-requirements we can remove __init()__ + def __init__( + self, + config_files=None, + vendor_files=None, + override_defaults=None, + force_ipv4=None, + envvar_prefix=None, + secure_files=None, + pw_func=None, + ): + ret = super(OSC_Config, self).__init__( + config_files=config_files, + vendor_files=vendor_files, + override_defaults=override_defaults, + force_ipv4=force_ipv4, + envvar_prefix=envvar_prefix, + secure_files=secure_files, + ) + + # NOTE(dtroyer): This will be pushed down into os-client-config + # The default is there is no callback, the calling + # application must specify what to use, typically + # it will be osc_lib.shell.prompt_for_password() + if '_pw_callback' not in vars(self): + # Set the default if it doesn't already exist + self._pw_callback = None + if pw_func is not None: + # Set the passed in value + self._pw_callback = pw_func + + return ret + def _auth_select_default_plugin(self, config): """Select a default plugin based on supplied arguments @@ -183,4 +217,13 @@ def _validate_auth(self, config, loader, fixed_argparse=None): else: config['auth'][p_opt.dest] = winning_value + # See if this needs a prompting + if ( + 'prompt' in vars(p_opt) and + p_opt.prompt is not None and + p_opt.dest not in config['auth'] and + self._pw_callback is not None + ): + config['auth'][p_opt.dest] = self._pw_callback(p_opt.prompt) + return config diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index ccfde2d0d3..9097543b14 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -44,12 +44,10 @@ def __init__( self, cli_options=None, api_version=None, - pw_func=None, ): super(ClientManager, self).__init__( cli_options=cli_options, api_version=api_version, - pw_func=pw_func, ) # TODO(dtroyer): For compatibility; mark this for removal when plugin diff --git a/openstackclient/shell.py b/openstackclient/shell.py index da58b63bf7..26147be999 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -145,6 +145,7 @@ def initialize_app(self, argv): 'interface': None, 'auth_type': self._auth_type, }, + pw_func=shell.prompt_for_password, ) except (IOError, OSError) as e: self.log.critical("Could not read clouds.yaml configuration file") @@ -162,7 +163,6 @@ def initialize_app(self, argv): self.client_manager = clientmanager.ClientManager( cli_options=self.cloud, api_version=self.api_version, - pw_func=shell.prompt_for_password, ) diff --git a/openstackclient/tests/test_shell_integ.py b/openstackclient/tests/test_shell_integ.py index bc5f1ae51a..d50113b2ff 100644 --- a/openstackclient/tests/test_shell_integ.py +++ b/openstackclient/tests/test_shell_integ.py @@ -354,6 +354,64 @@ def test_shell_args_cacert_insecure(self): self.assertFalse(self.requests_mock.request_history[0].verify) +class TestShellCliV3Prompt(TestShellInteg): + + def setUp(self): + super(TestShellCliV3Prompt, self).setUp() + env = { + "OS_AUTH_URL": V3_AUTH_URL, + "OS_PROJECT_DOMAIN_ID": test_shell.DEFAULT_PROJECT_DOMAIN_ID, + "OS_USER_DOMAIN_ID": test_shell.DEFAULT_USER_DOMAIN_ID, + "OS_USERNAME": test_shell.DEFAULT_USERNAME, + "OS_IDENTITY_API_VERSION": "3", + } + self.useFixture(osc_lib_utils.EnvFixture(copy.deepcopy(env))) + + self.token = ksa_fixture.V3Token( + project_domain_id=test_shell.DEFAULT_PROJECT_DOMAIN_ID, + user_domain_id=test_shell.DEFAULT_USER_DOMAIN_ID, + user_name=test_shell.DEFAULT_USERNAME, + ) + + # Set up the v3 auth routes + self.requests_mock.register_uri( + 'GET', + V3_AUTH_URL, + json=V3_VERSION_RESP, + status_code=200, + ) + self.requests_mock.register_uri( + 'POST', + V3_AUTH_URL + 'auth/tokens', + json=self.token, + status_code=200, + ) + + @mock.patch("osc_lib.shell.prompt_for_password") + def test_shell_callback(self, mock_prompt): + mock_prompt.return_value = "qaz" + _shell = shell.OpenStackShell() + _shell.run("configuration show".split()) + + # Check general calls + self.assertEqual(len(self.requests_mock.request_history), 2) + + # Check password callback set correctly + self.assertEqual( + mock_prompt, + _shell.cloud._openstack_config._pw_callback + ) + + # Check auth request + auth_req = self.requests_mock.request_history[1].json() + + # Check returned password from prompt function + self.assertEqual( + "qaz", + auth_req['auth']['identity']['password']['user']['password'], + ) + + class TestShellCliPrecedence(TestShellInteg): """Validate option precedence rules without clouds.yaml From 84c83fc3aeebf8201a301f1864c3b8a1572111f1 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Mon, 29 Aug 2016 18:14:26 -0500 Subject: [PATCH 1188/3095] Provide fallback prompt function for current osc-lib Leaving the pw_func uninitialize in osc-lib turned out to be a bad idea as the test to prompt in setup_auth() doesn't check for a callback of None. Also, release note Change-Id: I8f875fa8a942d02a040238359ee22c603a4e5956 --- openstackclient/common/clientmanager.py | 3 +++ releasenotes/notes/bug-1617384-55c88207115e2a5b.yaml | 5 +++++ 2 files changed, 8 insertions(+) create mode 100644 releasenotes/notes/bug-1617384-55c88207115e2a5b.yaml diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index 9097543b14..57423aed6b 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -20,6 +20,7 @@ import sys from osc_lib import clientmanager +from osc_lib import shell LOG = logging.getLogger(__name__) @@ -48,6 +49,8 @@ def __init__( super(ClientManager, self).__init__( cli_options=cli_options, api_version=api_version, + # TODO(dtroyer): Remove this when osc-lib 1.2 is released + pw_func=shell.prompt_for_password, ) # TODO(dtroyer): For compatibility; mark this for removal when plugin diff --git a/releasenotes/notes/bug-1617384-55c88207115e2a5b.yaml b/releasenotes/notes/bug-1617384-55c88207115e2a5b.yaml new file mode 100644 index 0000000000..89b452be92 --- /dev/null +++ b/releasenotes/notes/bug-1617384-55c88207115e2a5b.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Fix prompting for password issue introduced in release 3.0.0 + [Bug `1617384 `_] From 110a62f277903b5bf233bfd746e8ce0989ca6df4 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Tue, 30 Aug 2016 09:20:59 -0500 Subject: [PATCH 1189/3095] Add importing file to import warnings Add the Python file doing the import to the warnings emitted by the modules moved to osc-lib. Users will at least have a hint as to which package is out-of-date. Change-Id: I633b440c30b2b15cfde7a9013e30dfa39ab200bc --- openstackclient/api/auth.py | 5 ++++- openstackclient/common/command.py | 5 ++++- openstackclient/common/exceptions.py | 5 ++++- openstackclient/common/logs.py | 5 ++++- openstackclient/common/parseractions.py | 5 ++++- openstackclient/common/timing.py | 5 ++++- openstackclient/common/utils.py | 5 ++++- 7 files changed, 28 insertions(+), 7 deletions(-) diff --git a/openstackclient/api/auth.py b/openstackclient/api/auth.py index d62a82dc43..7c520f4964 100644 --- a/openstackclient/api/auth.py +++ b/openstackclient/api/auth.py @@ -14,12 +14,15 @@ # NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release # or Jun 2017. +import inspect import sys from osc_lib.api.auth import * # noqa +parent_import = inspect.getouterframes(inspect.currentframe())[1][1] sys.stderr.write( "WARNING: %s is deprecated and will be removed after Jun 2017. " - "Please use osc_lib.api.auth\n" % __name__ + "Please use osc_lib.api.auth. This warning is caused by an " + "out-of-date import in %s\n" % (__name__, parent_import) ) diff --git a/openstackclient/common/command.py b/openstackclient/common/command.py index 29c1534d63..44954da337 100644 --- a/openstackclient/common/command.py +++ b/openstackclient/common/command.py @@ -15,12 +15,15 @@ # NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release # or Jun 2017. +import inspect import sys from osc_lib.command.command import * # noqa +parent_import = inspect.getouterframes(inspect.currentframe())[1][1] sys.stderr.write( "WARNING: %s is deprecated and will be removed after Jun 2017. " - "Please use osc_lib.command.command\n" % __name__ + "Please use osc_lib.command.command. This warning is caused by an " + "out-of-date import in %s\n" % (__name__, parent_import) ) diff --git a/openstackclient/common/exceptions.py b/openstackclient/common/exceptions.py index 7124074c99..ed497e7bac 100644 --- a/openstackclient/common/exceptions.py +++ b/openstackclient/common/exceptions.py @@ -14,12 +14,15 @@ # NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release # or Jun 2017. +import inspect import sys from osc_lib.exceptions import * # noqa +parent_import = inspect.getouterframes(inspect.currentframe())[1][1] sys.stderr.write( "WARNING: %s is deprecated and will be removed after Jun 2017. " - "Please use osc_lib.exceptions\n" % __name__ + "Please use osc_lib.exceptions. This warning is caused by an " + "out-of-date import in %s\n" % (__name__, parent_import) ) diff --git a/openstackclient/common/logs.py b/openstackclient/common/logs.py index 8aa97d5bae..24bf07eb39 100644 --- a/openstackclient/common/logs.py +++ b/openstackclient/common/logs.py @@ -14,13 +14,16 @@ # NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release # or Jun 2017. +import inspect import sys from osc_lib.logs import * # noqa from osc_lib.logs import _FileFormatter # noqa +parent_import = inspect.getouterframes(inspect.currentframe())[1][1] sys.stderr.write( "WARNING: %s is deprecated and will be removed after Jun 2017. " - "Please use osc_lib.logs\n" % __name__ + "Please use osc_lib.logs. This warning is caused by an " + "out-of-date import in %s\n" % (__name__, parent_import) ) diff --git a/openstackclient/common/parseractions.py b/openstackclient/common/parseractions.py index fa5148eca5..3af3a017f7 100644 --- a/openstackclient/common/parseractions.py +++ b/openstackclient/common/parseractions.py @@ -14,12 +14,15 @@ # NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release # or Jun 2017. +import inspect import sys from osc_lib.cli.parseractions import * # noqa +parent_import = inspect.getouterframes(inspect.currentframe())[1][1] sys.stderr.write( "WARNING: %s is deprecated and will be removed after Jun 2017. " - "Please use osc_lib.cli.parseractions\n" % __name__ + "Please use osc_lib.cli.parseractions. This warning is caused by an " + "out-of-date import in %s\n" % (__name__, parent_import) ) diff --git a/openstackclient/common/timing.py b/openstackclient/common/timing.py index facbec350d..444f0cb2ea 100644 --- a/openstackclient/common/timing.py +++ b/openstackclient/common/timing.py @@ -14,12 +14,15 @@ # NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release # or Jun 2017. +import inspect import sys from osc_lib.command.timing import * # noqa +parent_import = inspect.getouterframes(inspect.currentframe())[1][1] sys.stderr.write( "WARNING: %s is deprecated and will be removed after Jun 2017. " - "Please use osc_lib.command.timing\n" % __name__ + "Please use osc_lib.command.timing. This warning is caused by an " + "out-of-date import in %s\n" % (__name__, parent_import) ) diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index 73cd3dc9d3..aeb3aea7e3 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -14,12 +14,15 @@ # NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release # or Jun 2017. +import inspect import sys from osc_lib.utils import * # noqa +parent_import = inspect.getouterframes(inspect.currentframe())[1][1] sys.stderr.write( "WARNING: %s is deprecated and will be removed after Jun 2017. " - "Please use osc_lib.utils\n" % __name__ + "Please use osc_lib.utils. This warning is caused by an " + "out-of-date import in %s\n" % (__name__, parent_import) ) From 8241f08ee1a67bbdaabd1026b81cc494a53530f2 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Tue, 30 Aug 2016 14:53:39 +0000 Subject: [PATCH 1190/3095] Updated from global requirements Change-Id: I2eedd06457425f48353e416b4d5145ce1967d57c --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index ff7b198123..0ff0e2268f 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -13,7 +13,7 @@ requests>=2.10.0 # Apache-2.0 requests-mock>=1.0 # Apache-2.0 sphinx!=1.3b1,<1.3,>=1.2.1 # BSD stevedore>=1.16.0 # Apache-2.0 -os-client-config!=1.19.0,!=1.19.1,!=1.20.0,!=1.20.1,>=1.13.1 # Apache-2.0 +os-client-config!=1.19.0,!=1.19.1,!=1.20.0,!=1.20.1,!=1.21.0,>=1.13.1 # Apache-2.0 os-testr>=0.7.0 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD testtools>=1.4.0 # MIT From a24d6ba6059e3c2045ffccd7b9159d78d901c51c Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 31 Aug 2016 09:14:50 +0000 Subject: [PATCH 1191/3095] Updated from global requirements Change-Id: I87af564cffaf44669e1efe4ca45c75a6307f1286 --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 0ff0e2268f..6a4d3b8f13 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -33,7 +33,7 @@ python-ironic-inspector-client>=1.5.0 # Apache-2.0 python-mistralclient>=2.0.0 # Apache-2.0 python-muranoclient>=0.8.2 # Apache-2.0 python-neutronclient>=5.1.0 # Apache-2.0 -python-saharaclient>=0.16.0 # Apache-2.0 +python-saharaclient>=0.18.0 # Apache-2.0 python-searchlightclient>=0.2.0 #Apache-2.0 python-senlinclient>=0.3.0 # Apache-2.0 python-zaqarclient>=1.0.0 # Apache-2.0 From ca15cd434bcdcab330b8823b60e12d8886b655b3 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 31 Aug 2016 13:55:59 -0500 Subject: [PATCH 1192/3095] Rearrange integration tests Move the integration tests into their final home... * Create tests/integ to hold the integration tests * Split tests/test_shell_integ.py into tests/integ/base.py and tests/integ/cli/test_shell.py * Rename TestXXXXInteg classes to TestIntegXXXX * Adds tests/integ/cli/test_project.py for some simple project argument tests Change-Id: I2cdd340d1d446d61784eae35dd5aa09d40d5899d --- openstackclient/tests/integ/__init__.py | 0 openstackclient/tests/integ/base.py | 121 +++++++++ openstackclient/tests/integ/cli/__init__.py | 0 .../tests/integ/cli/test_project.py | 257 ++++++++++++++++++ .../cli/test_shell.py} | 228 +++------------- 5 files changed, 411 insertions(+), 195 deletions(-) create mode 100644 openstackclient/tests/integ/__init__.py create mode 100644 openstackclient/tests/integ/base.py create mode 100644 openstackclient/tests/integ/cli/__init__.py create mode 100644 openstackclient/tests/integ/cli/test_project.py rename openstackclient/tests/{test_shell_integ.py => integ/cli/test_shell.py} (71%) diff --git a/openstackclient/tests/integ/__init__.py b/openstackclient/tests/integ/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openstackclient/tests/integ/base.py b/openstackclient/tests/integ/base.py new file mode 100644 index 0000000000..9ee8489337 --- /dev/null +++ b/openstackclient/tests/integ/base.py @@ -0,0 +1,121 @@ +# 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 keystoneauth1 import fixture as ksa_fixture +from requests_mock.contrib import fixture + +from openstackclient.tests import test_shell +from openstackclient.tests import utils + + +HOST = "192.168.5.41" +URL_BASE = "http://%s/identity" % HOST + +V2_AUTH_URL = URL_BASE + "/v2.0/" +V2_VERSION_RESP = { + "version": { + "status": "stable", + "updated": "2014-04-17T00:00:00Z", + "media-types": [ + { + "base": "application/json", + "type": "application/vnd.openstack.identity-v2.0+json", + }, + ], + "id": "v2.0", + "links": [ + { + "href": V2_AUTH_URL, + "rel": "self", + }, + { + "href": "http://docs.openstack.org/", + "type": "text/html", + "rel": "describedby", + }, + ], + }, +} + +V3_AUTH_URL = URL_BASE + "/v3/" +V3_VERSION_RESP = { + "version": { + "status": "stable", + "updated": "2016-04-04T00:00:00Z", + "media-types": [{ + "base": "application/json", + "type": "application/vnd.openstack.identity-v3+json", + }], + "id": "v3.6", + "links": [{ + "href": V3_AUTH_URL, + "rel": "self", + }] + } +} + + +def make_v2_token(req_mock): + """Create an Identity v2 token and register the responses""" + + token = ksa_fixture.V2Token( + tenant_name=test_shell.DEFAULT_PROJECT_NAME, + user_name=test_shell.DEFAULT_USERNAME, + ) + + # Set up the v2 auth routes + req_mock.register_uri( + 'GET', + V2_AUTH_URL, + json=V2_VERSION_RESP, + status_code=200, + ) + req_mock.register_uri( + 'POST', + V2_AUTH_URL + 'tokens', + json=token, + status_code=200, + ) + return token + + +def make_v3_token(req_mock): + """Create an Identity v3 token and register the response""" + + token = ksa_fixture.V3Token( + # project_domain_id=test_shell.DEFAULT_PROJECT_DOMAIN_ID, + user_domain_id=test_shell.DEFAULT_USER_DOMAIN_ID, + user_name=test_shell.DEFAULT_USERNAME, + ) + + # Set up the v3 auth routes + req_mock.register_uri( + 'GET', + V3_AUTH_URL, + json=V3_VERSION_RESP, + status_code=200, + ) + req_mock.register_uri( + 'POST', + V3_AUTH_URL + 'auth/tokens', + json=token, + status_code=200, + ) + return token + + +class TestInteg(utils.TestCase): + + def setUp(self): + super(TestInteg, self).setUp() + + self.requests_mock = self.useFixture(fixture.Fixture()) diff --git a/openstackclient/tests/integ/cli/__init__.py b/openstackclient/tests/integ/cli/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openstackclient/tests/integ/cli/test_project.py b/openstackclient/tests/integ/cli/test_project.py new file mode 100644 index 0000000000..16d5f71713 --- /dev/null +++ b/openstackclient/tests/integ/cli/test_project.py @@ -0,0 +1,257 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import copy + +from osc_lib.tests import utils as osc_lib_utils + +from openstackclient import shell +from openstackclient.tests.integ import base as test_base +from openstackclient.tests import test_shell + + +class TestIntegV2ProjectID(test_base.TestInteg): + + def setUp(self): + super(TestIntegV2ProjectID, self).setUp() + env = { + "OS_AUTH_URL": test_base.V2_AUTH_URL, + "OS_PROJECT_ID": test_shell.DEFAULT_PROJECT_ID, + "OS_USERNAME": test_shell.DEFAULT_USERNAME, + "OS_PASSWORD": test_shell.DEFAULT_PASSWORD, + "OS_IDENTITY_API_VERSION": "2", + } + self.useFixture(osc_lib_utils.EnvFixture(copy.deepcopy(env))) + + self.token = test_base.make_v2_token(self.requests_mock) + + def test_project_id_env(self): + _shell = shell.OpenStackShell() + _shell.run("configuration show".split()) + + # Check general calls + self.assertEqual(len(self.requests_mock.request_history), 2) + + # Check discovery request + self.assertEqual( + test_base.V2_AUTH_URL, + self.requests_mock.request_history[0].url, + ) + + # Check auth request + auth_req = self.requests_mock.request_history[1].json() + + self.assertEqual( + test_shell.DEFAULT_PROJECT_ID, + auth_req['auth']['tenantId'], + ) + + def test_project_id_arg(self): + _shell = shell.OpenStackShell() + _shell.run("--os-project-id wsx configuration show".split()) + + # Check general calls + self.assertEqual(len(self.requests_mock.request_history), 2) + + # Check discovery request + self.assertEqual( + test_base.V2_AUTH_URL, + self.requests_mock.request_history[0].url, + ) + + # Check auth request + auth_req = self.requests_mock.request_history[1].json() + + self.assertEqual( + "wsx", + auth_req['auth']['tenantId'], + ) + + +class TestIntegV2ProjectName(test_base.TestInteg): + + def setUp(self): + super(TestIntegV2ProjectName, self).setUp() + env = { + "OS_AUTH_URL": test_base.V2_AUTH_URL, + "OS_PROJECT_NAME": test_shell.DEFAULT_PROJECT_NAME, + "OS_USERNAME": test_shell.DEFAULT_USERNAME, + "OS_PASSWORD": test_shell.DEFAULT_PASSWORD, + "OS_IDENTITY_API_VERSION": "2", + } + self.useFixture(osc_lib_utils.EnvFixture(copy.deepcopy(env))) + + self.token = test_base.make_v2_token(self.requests_mock) + + def test_project_name_env(self): + _shell = shell.OpenStackShell() + _shell.run("configuration show".split()) + + # Check general calls + self.assertEqual(len(self.requests_mock.request_history), 2) + + # Check discovery request + self.assertEqual( + test_base.V2_AUTH_URL, + self.requests_mock.request_history[0].url, + ) + + # Check auth request + auth_req = self.requests_mock.request_history[1].json() + + self.assertEqual( + test_shell.DEFAULT_PROJECT_NAME, + auth_req['auth']['tenantName'], + ) + + def test_project_name_arg(self): + _shell = shell.OpenStackShell() + _shell.run("--os-project-name qaz configuration show".split()) + + # Check general calls + self.assertEqual(len(self.requests_mock.request_history), 2) + + # Check discovery request + self.assertEqual( + test_base.V2_AUTH_URL, + self.requests_mock.request_history[0].url, + ) + + # Check auth request + auth_req = self.requests_mock.request_history[1].json() + + self.assertEqual( + "qaz", + auth_req['auth']['tenantName'], + ) + + +class TestIntegV3ProjectID(test_base.TestInteg): + + def setUp(self): + super(TestIntegV3ProjectID, self).setUp() + env = { + "OS_AUTH_URL": test_base.V3_AUTH_URL, + "OS_PROJECT_ID": test_shell.DEFAULT_PROJECT_NAME, + # "OS_PROJECT_DOMAIN_ID": test_shell.DEFAULT_PROJECT_DOMAIN_ID, + # "OS_USER_DOMAIN_ID": test_shell.DEFAULT_USER_DOMAIN_ID, + "OS_USERNAME": test_shell.DEFAULT_USERNAME, + "OS_PASSWORD": test_shell.DEFAULT_PASSWORD, + "OS_IDENTITY_API_VERSION": "3", + } + self.useFixture(osc_lib_utils.EnvFixture(copy.deepcopy(env))) + + self.token = test_base.make_v3_token(self.requests_mock) + + def test_project_id_env(self): + _shell = shell.OpenStackShell() + _shell.run("configuration show".split()) + + # Check general calls + self.assertEqual(len(self.requests_mock.request_history), 2) + + # Check discovery request + self.assertEqual( + test_base.V3_AUTH_URL, + self.requests_mock.request_history[0].url, + ) + + # Check auth request + auth_req = self.requests_mock.request_history[1].json() + + self.assertIsNone(auth_req['auth'].get('tenantId', None)) + self.assertIsNone(auth_req['auth'].get('tenantName', None)) + + def test_project_id_arg(self): + _shell = shell.OpenStackShell() + _shell.run("--os-project-id wsx configuration show".split()) + + # Check general calls + self.assertEqual(len(self.requests_mock.request_history), 2) + + # Check discovery request + self.assertEqual( + test_base.V3_AUTH_URL, + self.requests_mock.request_history[0].url, + ) + + # Check auth request + auth_req = self.requests_mock.request_history[1].json() + + self.assertIsNone(auth_req['auth'].get('tenantId', None)) + self.assertIsNone(auth_req['auth'].get('tenantName', None)) + + +class TestIntegV3ProjectName(test_base.TestInteg): + + def setUp(self): + super(TestIntegV3ProjectName, self).setUp() + env = { + "OS_AUTH_URL": test_base.V3_AUTH_URL, + "OS_PROJECT_NAME": test_shell.DEFAULT_PROJECT_NAME, + # "OS_PROJECT_DOMAIN_ID": test_shell.DEFAULT_PROJECT_DOMAIN_ID, + # "OS_USER_DOMAIN_ID": test_shell.DEFAULT_USER_DOMAIN_ID, + "OS_USERNAME": test_shell.DEFAULT_USERNAME, + "OS_PASSWORD": test_shell.DEFAULT_PASSWORD, + "OS_IDENTITY_API_VERSION": "3", + } + self.useFixture(osc_lib_utils.EnvFixture(copy.deepcopy(env))) + + self.token = test_base.make_v3_token(self.requests_mock) + + def test_project_name_env(self): + _shell = shell.OpenStackShell() + _shell.run("configuration show".split()) + + # Check general calls + self.assertEqual(len(self.requests_mock.request_history), 2) + + # Check discovery request + self.assertEqual( + test_base.V3_AUTH_URL, + self.requests_mock.request_history[0].url, + ) + + # Check auth request + auth_req = self.requests_mock.request_history[1].json() + + self.assertEqual( + test_shell.DEFAULT_PROJECT_NAME, + auth_req['auth']['scope']['project']['name'], + ) + + self.assertIsNone(auth_req['auth'].get('tenantId', None)) + self.assertIsNone(auth_req['auth'].get('tenantName', None)) + + def test_project_name_arg(self): + _shell = shell.OpenStackShell() + _shell.run("--os-project-name wsx configuration show".split()) + + # Check general calls + self.assertEqual(len(self.requests_mock.request_history), 2) + + # Check discovery request + self.assertEqual( + test_base.V3_AUTH_URL, + self.requests_mock.request_history[0].url, + ) + + # Check auth request + auth_req = self.requests_mock.request_history[1].json() + + self.assertEqual( + "wsx", + auth_req['auth']['scope']['project']['name'], + ) + + self.assertIsNone(auth_req['auth'].get('tenantId', None)) + self.assertIsNone(auth_req['auth'].get('tenantName', None)) diff --git a/openstackclient/tests/test_shell_integ.py b/openstackclient/tests/integ/cli/test_shell.py similarity index 71% rename from openstackclient/tests/test_shell_integ.py rename to openstackclient/tests/integ/cli/test_shell.py index d50113b2ff..dad58f8ffb 100644 --- a/openstackclient/tests/test_shell_integ.py +++ b/openstackclient/tests/integ/cli/test_shell.py @@ -13,75 +13,19 @@ import copy import mock -from keystoneauth1 import fixture as ksa_fixture from osc_lib.tests import utils as osc_lib_utils -from requests_mock.contrib import fixture from openstackclient import shell +from openstackclient.tests.integ import base as test_base from openstackclient.tests import test_shell -from openstackclient.tests import utils - -HOST = "192.168.5.41" -URL_BASE = "http://%s/identity" % HOST - -V2_AUTH_URL = URL_BASE + "/v2.0/" -V2_VERSION_RESP = { - "version": { - "status": "stable", - "updated": "2014-04-17T00:00:00Z", - "media-types": [ - { - "base": "application/json", - "type": "application/vnd.openstack.identity-v2.0+json", - }, - ], - "id": "v2.0", - "links": [ - { - "href": V2_AUTH_URL, - "rel": "self", - }, - { - "href": "http://docs.openstack.org/", - "type": "text/html", - "rel": "describedby", - }, - ], - }, -} - -V3_AUTH_URL = URL_BASE + "/v3/" -V3_VERSION_RESP = { - "version": { - "status": "stable", - "updated": "2016-04-04T00:00:00Z", - "media-types": [{ - "base": "application/json", - "type": "application/vnd.openstack.identity-v3+json", - }], - "id": "v3.6", - "links": [{ - "href": V3_AUTH_URL, - "rel": "self", - }] - } -} - - -class TestShellInteg(utils.TestCase): - def setUp(self): - super(TestShellInteg, self).setUp() - - self.requests_mock = self.useFixture(fixture.Fixture()) - -class TestShellCliV2Integ(TestShellInteg): +class TestIntegShellCliV2(test_base.TestInteg): def setUp(self): - super(TestShellCliV2Integ, self).setUp() + super(TestIntegShellCliV2, self).setUp() env = { - "OS_AUTH_URL": V2_AUTH_URL, + "OS_AUTH_URL": test_base.V2_AUTH_URL, "OS_PROJECT_NAME": test_shell.DEFAULT_PROJECT_NAME, "OS_USERNAME": test_shell.DEFAULT_USERNAME, "OS_PASSWORD": test_shell.DEFAULT_PASSWORD, @@ -89,24 +33,7 @@ def setUp(self): } self.useFixture(osc_lib_utils.EnvFixture(copy.deepcopy(env))) - self.token = ksa_fixture.V2Token( - tenant_name=test_shell.DEFAULT_PROJECT_NAME, - user_name=test_shell.DEFAULT_USERNAME, - ) - - # Set up the v2 auth routes - self.requests_mock.register_uri( - 'GET', - V2_AUTH_URL, - json=V2_VERSION_RESP, - status_code=200, - ) - self.requests_mock.register_uri( - 'POST', - V2_AUTH_URL + 'tokens', - json=self.token, - status_code=200, - ) + self.token = test_base.make_v2_token(self.requests_mock) def test_shell_args_no_options(self): _shell = shell.OpenStackShell() @@ -117,7 +44,7 @@ def test_shell_args_no_options(self): # Check discovery request self.assertEqual( - V2_AUTH_URL, + test_base.V2_AUTH_URL, self.requests_mock.request_history[0].url, ) @@ -181,12 +108,12 @@ def test_shell_args_cacert_insecure(self): self.assertFalse(self.requests_mock.request_history[0].verify) -class TestShellCliV2IgnoreInteg(TestShellInteg): +class TestIntegShellCliV2Ignore(test_base.TestInteg): def setUp(self): - super(TestShellCliV2IgnoreInteg, self).setUp() + super(TestIntegShellCliV2Ignore, self).setUp() env = { - "OS_AUTH_URL": V2_AUTH_URL, + "OS_AUTH_URL": test_base.V2_AUTH_URL, "OS_PROJECT_NAME": test_shell.DEFAULT_PROJECT_NAME, "OS_USERNAME": test_shell.DEFAULT_USERNAME, "OS_PASSWORD": test_shell.DEFAULT_PASSWORD, @@ -196,24 +123,7 @@ def setUp(self): } self.useFixture(osc_lib_utils.EnvFixture(copy.deepcopy(env))) - self.token = ksa_fixture.V2Token( - tenant_name=test_shell.DEFAULT_PROJECT_NAME, - user_name=test_shell.DEFAULT_USERNAME, - ) - - # Set up the v2 auth routes - self.requests_mock.register_uri( - 'GET', - V2_AUTH_URL, - json=V2_VERSION_RESP, - status_code=200, - ) - self.requests_mock.register_uri( - 'POST', - V2_AUTH_URL + 'tokens', - json=self.token, - status_code=200, - ) + self.token = test_base.make_v2_token(self.requests_mock) def test_shell_args_ignore_v3(self): _shell = shell.OpenStackShell() @@ -224,7 +134,7 @@ def test_shell_args_ignore_v3(self): # Check discovery request self.assertEqual( - V2_AUTH_URL, + test_base.V2_AUTH_URL, self.requests_mock.request_history[0].url, ) @@ -245,12 +155,12 @@ def test_shell_args_ignore_v3(self): ) -class TestShellCliV3Integ(TestShellInteg): +class TestIntegShellCliV3(test_base.TestInteg): def setUp(self): - super(TestShellCliV3Integ, self).setUp() + super(TestIntegShellCliV3, self).setUp() env = { - "OS_AUTH_URL": V3_AUTH_URL, + "OS_AUTH_URL": test_base.V3_AUTH_URL, "OS_PROJECT_DOMAIN_ID": test_shell.DEFAULT_PROJECT_DOMAIN_ID, "OS_USER_DOMAIN_ID": test_shell.DEFAULT_USER_DOMAIN_ID, "OS_USERNAME": test_shell.DEFAULT_USERNAME, @@ -259,25 +169,7 @@ def setUp(self): } self.useFixture(osc_lib_utils.EnvFixture(copy.deepcopy(env))) - self.token = ksa_fixture.V3Token( - project_domain_id=test_shell.DEFAULT_PROJECT_DOMAIN_ID, - user_domain_id=test_shell.DEFAULT_USER_DOMAIN_ID, - user_name=test_shell.DEFAULT_USERNAME, - ) - - # Set up the v3 auth routes - self.requests_mock.register_uri( - 'GET', - V3_AUTH_URL, - json=V3_VERSION_RESP, - status_code=200, - ) - self.requests_mock.register_uri( - 'POST', - V3_AUTH_URL + 'auth/tokens', - json=self.token, - status_code=200, - ) + self.token = test_base.make_v3_token(self.requests_mock) def test_shell_args_no_options(self): _shell = shell.OpenStackShell() @@ -288,7 +180,7 @@ def test_shell_args_no_options(self): # Check discovery request self.assertEqual( - V3_AUTH_URL, + test_base.V3_AUTH_URL, self.requests_mock.request_history[0].url, ) @@ -354,12 +246,12 @@ def test_shell_args_cacert_insecure(self): self.assertFalse(self.requests_mock.request_history[0].verify) -class TestShellCliV3Prompt(TestShellInteg): +class TestIntegShellCliV3Prompt(test_base.TestInteg): def setUp(self): - super(TestShellCliV3Prompt, self).setUp() + super(TestIntegShellCliV3Prompt, self).setUp() env = { - "OS_AUTH_URL": V3_AUTH_URL, + "OS_AUTH_URL": test_base.V3_AUTH_URL, "OS_PROJECT_DOMAIN_ID": test_shell.DEFAULT_PROJECT_DOMAIN_ID, "OS_USER_DOMAIN_ID": test_shell.DEFAULT_USER_DOMAIN_ID, "OS_USERNAME": test_shell.DEFAULT_USERNAME, @@ -367,25 +259,7 @@ def setUp(self): } self.useFixture(osc_lib_utils.EnvFixture(copy.deepcopy(env))) - self.token = ksa_fixture.V3Token( - project_domain_id=test_shell.DEFAULT_PROJECT_DOMAIN_ID, - user_domain_id=test_shell.DEFAULT_USER_DOMAIN_ID, - user_name=test_shell.DEFAULT_USERNAME, - ) - - # Set up the v3 auth routes - self.requests_mock.register_uri( - 'GET', - V3_AUTH_URL, - json=V3_VERSION_RESP, - status_code=200, - ) - self.requests_mock.register_uri( - 'POST', - V3_AUTH_URL + 'auth/tokens', - json=self.token, - status_code=200, - ) + self.token = test_base.make_v3_token(self.requests_mock) @mock.patch("osc_lib.shell.prompt_for_password") def test_shell_callback(self, mock_prompt): @@ -412,7 +286,7 @@ def test_shell_callback(self, mock_prompt): ) -class TestShellCliPrecedence(TestShellInteg): +class TestIntegShellCliPrecedence(test_base.TestInteg): """Validate option precedence rules without clouds.yaml Global option values may be set in three places: @@ -425,9 +299,9 @@ class TestShellCliPrecedence(TestShellInteg): """ def setUp(self): - super(TestShellCliPrecedence, self).setUp() + super(TestIntegShellCliPrecedence, self).setUp() env = { - "OS_AUTH_URL": V3_AUTH_URL, + "OS_AUTH_URL": test_base.V3_AUTH_URL, "OS_PROJECT_DOMAIN_ID": test_shell.DEFAULT_PROJECT_DOMAIN_ID, "OS_USER_DOMAIN_ID": test_shell.DEFAULT_USER_DOMAIN_ID, "OS_USERNAME": test_shell.DEFAULT_USERNAME, @@ -435,29 +309,11 @@ def setUp(self): } self.useFixture(osc_lib_utils.EnvFixture(copy.deepcopy(env))) - self.token = ksa_fixture.V3Token( - project_domain_id=test_shell.DEFAULT_PROJECT_DOMAIN_ID, - user_domain_id=test_shell.DEFAULT_USER_DOMAIN_ID, - user_name=test_shell.DEFAULT_USERNAME, - ) - - # Set up the v3 auth routes - self.requests_mock.register_uri( - 'GET', - V3_AUTH_URL, - json=V3_VERSION_RESP, - status_code=200, - ) - self.requests_mock.register_uri( - 'POST', - V3_AUTH_URL + 'auth/tokens', - json=self.token, - status_code=200, - ) + self.token = test_base.make_v3_token(self.requests_mock) # Patch a v3 auth URL into the o-c-c data test_shell.PUBLIC_1['public-clouds']['megadodo']['auth']['auth_url'] \ - = V3_AUTH_URL + = test_base.V3_AUTH_URL def test_shell_args_options(self): """Verify command line options override environment variables""" @@ -473,7 +329,7 @@ def test_shell_args_options(self): # Check discovery request self.assertEqual( - V3_AUTH_URL, + test_base.V3_AUTH_URL, self.requests_mock.request_history[0].url, ) @@ -502,7 +358,7 @@ def test_shell_args_options(self): ) -class TestShellCliPrecedenceOCC(TestShellInteg): +class TestIntegShellCliPrecedenceOCC(test_base.TestInteg): """Validate option precedence rules with clouds.yaml Global option values may be set in three places: @@ -515,10 +371,10 @@ class TestShellCliPrecedenceOCC(TestShellInteg): """ def setUp(self): - super(TestShellCliPrecedenceOCC, self).setUp() + super(TestIntegShellCliPrecedenceOCC, self).setUp() env = { "OS_CLOUD": "megacloud", - "OS_AUTH_URL": V3_AUTH_URL, + "OS_AUTH_URL": test_base.V3_AUTH_URL, "OS_PROJECT_DOMAIN_ID": test_shell.DEFAULT_PROJECT_DOMAIN_ID, "OS_USER_DOMAIN_ID": test_shell.DEFAULT_USER_DOMAIN_ID, "OS_USERNAME": test_shell.DEFAULT_USERNAME, @@ -527,29 +383,11 @@ def setUp(self): } self.useFixture(osc_lib_utils.EnvFixture(copy.deepcopy(env))) - self.token = ksa_fixture.V3Token( - project_domain_id=test_shell.DEFAULT_PROJECT_DOMAIN_ID, - user_domain_id=test_shell.DEFAULT_USER_DOMAIN_ID, - user_name=test_shell.DEFAULT_USERNAME, - ) - - # Set up the v3 auth routes - self.requests_mock.register_uri( - 'GET', - V3_AUTH_URL, - json=V3_VERSION_RESP, - status_code=200, - ) - self.requests_mock.register_uri( - 'POST', - V3_AUTH_URL + 'auth/tokens', - json=self.token, - status_code=200, - ) + self.token = test_base.make_v3_token(self.requests_mock) # Patch a v3 auth URL into the o-c-c data test_shell.PUBLIC_1['public-clouds']['megadodo']['auth']['auth_url'] \ - = V3_AUTH_URL + = test_base.V3_AUTH_URL @mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file") @mock.patch("os_client_config.config.OpenStackConfig._load_config_file") @@ -577,7 +415,7 @@ def test_shell_args_precedence_1(self, config_mock, vendor_mock): # Check discovery request self.assertEqual( - V3_AUTH_URL, + test_base.V3_AUTH_URL, self.requests_mock.request_history[0].url, ) @@ -647,7 +485,7 @@ def test_shell_args_precedence_2(self, config_mock, vendor_mock): # Check discovery request self.assertEqual( - V3_AUTH_URL, + test_base.V3_AUTH_URL, self.requests_mock.request_history[0].url, ) From 45d3e67b98d59016d9f3ce66d2c8519d1cdbe7b7 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 1 Sep 2016 18:50:33 +0000 Subject: [PATCH 1193/3095] Updated from global requirements Change-Id: I610346bca6a1a8ff9e0ab20fec664d3aff58c44c --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f065346eab..5d7636d0cf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ six>=1.9.0 # MIT Babel>=2.3.4 # BSD cliff!=1.16.0,!=1.17.0,>=1.15.0 # Apache-2.0 keystoneauth1>=2.10.0 # Apache-2.0 -openstacksdk>=0.9.4 # Apache-2.0 +openstacksdk>=0.9.5 # Apache-2.0 osc-lib>=1.0.2 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 oslo.utils>=3.16.0 # Apache-2.0 From 1626b63f5698918ac73ec407b1922fc006b0db66 Mon Sep 17 00:00:00 2001 From: Bin Zhou Date: Fri, 2 Sep 2016 12:05:58 +0800 Subject: [PATCH 1194/3095] Use assertIn(A, B) instead of assertTrue(A in B ) Developers should use assertIn(A, B) instead of assertTrue(A in B ). TrivialFix Change-Id: I0d7c542272315590536c427a8865e49660361e55 --- openstackclient/tests/common/test_module.py | 12 ++++++------ openstackclient/tests/image/v2/test_image.py | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/openstackclient/tests/common/test_module.py b/openstackclient/tests/common/test_module.py index 7d08dae7b7..8d5bb5f1f7 100644 --- a/openstackclient/tests/common/test_module.py +++ b/openstackclient/tests/common/test_module.py @@ -106,8 +106,8 @@ def test_module_list_no_options(self): columns, data = self.cmd.take_action(parsed_args) # Additional modules may be present, just check our additions - self.assertTrue(module_name_1 in columns) - self.assertTrue(module_version_1 in data) + self.assertIn(module_name_1, columns) + self.assertIn(module_version_1, data) def test_module_list_all(self): arglist = [ @@ -124,7 +124,7 @@ def test_module_list_all(self): columns, data = self.cmd.take_action(parsed_args) # Additional modules may be present, just check our additions - self.assertTrue(module_name_1 in columns) - self.assertTrue(module_name_2 in columns) - self.assertTrue(module_version_1 in data) - self.assertTrue(module_version_2 in data) + self.assertIn(module_name_1, columns) + self.assertIn(module_name_2, columns) + self.assertIn(module_version_1, data) + self.assertIn(module_version_2, data) diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index 2b116b4e82..830590c96a 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -1128,7 +1128,7 @@ def test_image_set_tag_merge(self): # ImageManager.update(image, **kwargs) a, k = self.images_mock.update.call_args self.assertEqual(image_fakes.image_id, a[0]) - self.assertTrue('tags' in k) + self.assertIn('tags', k) self.assertEqual(set(kwargs['tags']), set(k['tags'])) self.assertIsNone(result) @@ -1154,7 +1154,7 @@ def test_image_set_tag_merge_dupe(self): # ImageManager.update(image, **kwargs) a, k = self.images_mock.update.call_args self.assertEqual(image_fakes.image_id, a[0]) - self.assertTrue('tags' in k) + self.assertIn('tags', k) self.assertEqual(set(kwargs['tags']), set(k['tags'])) self.assertIsNone(result) From 3b75c9aae55356e8f7b2c04e0101ede99d860c1a Mon Sep 17 00:00:00 2001 From: Doug Hellmann Date: Fri, 2 Sep 2016 09:44:02 -0400 Subject: [PATCH 1195/3095] Update reno for stable/newton Change-Id: Ice2d2eb4cc922e911c1f92020e8f5a298383f7f3 --- releasenotes/source/index.rst | 1 + releasenotes/source/newton.rst | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 releasenotes/source/newton.rst diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index 4004e3574d..49f8ecda23 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -8,6 +8,7 @@ OpenStackClient Release Notes unreleased 20_releases pre_20_releases + newton OpenStack Releases ------------------ diff --git a/releasenotes/source/newton.rst b/releasenotes/source/newton.rst new file mode 100644 index 0000000000..97036ed251 --- /dev/null +++ b/releasenotes/source/newton.rst @@ -0,0 +1,6 @@ +=================================== + Newton Series Release Notes +=================================== + +.. release-notes:: + :branch: origin/stable/newton From 24c8b94baf4c95cd3a76159009badbc53573be09 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Mon, 5 Sep 2016 10:15:57 -0700 Subject: [PATCH 1196/3095] format token expires time to prevent json loading datetime data When output to shell, the token issue time is fine; however when selecting the json formatter (via the --format json) option, an exception is raised when formatting the dataetime data. Rather than pass in the datetime data, we should format the data with the ISO 8601 formatting. Closes-Bug: 1619937 Change-Id: Iffebb2d5413fabfd283dfa94fc560fc37270f9dd --- openstackclient/identity/v2_0/token.py | 4 +++- openstackclient/identity/v3/token.py | 4 +++- openstackclient/tests/identity/v2_0/fakes.py | 2 +- openstackclient/tests/identity/v2_0/test_token.py | 4 ++-- openstackclient/tests/identity/v3/fakes.py | 2 +- openstackclient/tests/identity/v3/test_token.py | 6 +++--- 6 files changed, 13 insertions(+), 9 deletions(-) diff --git a/openstackclient/identity/v2_0/token.py b/openstackclient/identity/v2_0/token.py index 56f920f313..4b7e988b6e 100644 --- a/openstackclient/identity/v2_0/token.py +++ b/openstackclient/identity/v2_0/token.py @@ -42,7 +42,9 @@ def take_action(self, parsed_args): if auth_ref.auth_token: data['id'] = auth_ref.auth_token if auth_ref.expires: - data['expires'] = auth_ref.expires + datetime_obj = auth_ref.expires + expires_str = datetime_obj.strftime('%Y-%m-%dT%H:%M:%S%z') + data['expires'] = expires_str if auth_ref.project_id: data['project_id'] = auth_ref.project_id if auth_ref.user_id: diff --git a/openstackclient/identity/v3/token.py b/openstackclient/identity/v3/token.py index ecf09693e0..2cd304e601 100644 --- a/openstackclient/identity/v3/token.py +++ b/openstackclient/identity/v3/token.py @@ -183,7 +183,9 @@ def take_action(self, parsed_args): if auth_ref.auth_token: data['id'] = auth_ref.auth_token if auth_ref.expires: - data['expires'] = auth_ref.expires + datetime_obj = auth_ref.expires + expires_str = datetime_obj.strftime('%Y-%m-%dT%H:%M:%S%z') + data['expires'] = expires_str if auth_ref.project_id: data['project_id'] = auth_ref.project_id if auth_ref.user_id: diff --git a/openstackclient/tests/identity/v2_0/fakes.py b/openstackclient/tests/identity/v2_0/fakes.py index a715427cbb..3e0b999364 100644 --- a/openstackclient/tests/identity/v2_0/fakes.py +++ b/openstackclient/tests/identity/v2_0/fakes.py @@ -80,7 +80,7 @@ 'enabled': True, } -token_expires = '2014-01-01T00:00:00Z' +token_expires = '2016-09-05T18:04:52+0000' token_id = 'token-id-' + uuid.uuid4().hex TOKEN = { diff --git a/openstackclient/tests/identity/v2_0/test_token.py b/openstackclient/tests/identity/v2_0/test_token.py index bb7767070f..17115e6b37 100644 --- a/openstackclient/tests/identity/v2_0/test_token.py +++ b/openstackclient/tests/identity/v2_0/test_token.py @@ -58,7 +58,7 @@ def test_token_issue(self): collist = ('expires', 'id', 'project_id', 'user_id') self.assertEqual(collist, columns) datalist = ( - auth_ref.expires, + identity_fakes.token_expires, identity_fakes.token_id, 'project-id', 'user-id', @@ -86,7 +86,7 @@ def test_token_issue_with_unscoped_token(self): ) self.assertEqual(collist, columns) datalist = ( - auth_ref.expires, + identity_fakes.token_expires, identity_fakes.token_id, 'user-id', ) diff --git a/openstackclient/tests/identity/v3/fakes.py b/openstackclient/tests/identity/v3/fakes.py index c4d24d467e..38f6724e85 100644 --- a/openstackclient/tests/identity/v3/fakes.py +++ b/openstackclient/tests/identity/v3/fakes.py @@ -252,7 +252,7 @@ 'trustor_user_id': user_id, } -token_expires = '2014-01-01T00:00:00Z' +token_expires = '2016-09-05T18:04:52+0000' token_id = 'tttttttt-tttt-tttt-tttt-tttttttttttt' UNSCOPED_TOKEN = { diff --git a/openstackclient/tests/identity/v3/test_token.py b/openstackclient/tests/identity/v3/test_token.py index 9728c6e134..d7225e6c3b 100644 --- a/openstackclient/tests/identity/v3/test_token.py +++ b/openstackclient/tests/identity/v3/test_token.py @@ -55,7 +55,7 @@ def test_token_issue_with_project_id(self): collist = ('expires', 'id', 'project_id', 'user_id') self.assertEqual(collist, columns) datalist = ( - auth_ref.expires, + identity_fakes.token_expires, identity_fakes.token_id, identity_fakes.project_id, identity_fakes.user_id, @@ -82,7 +82,7 @@ def test_token_issue_with_domain_id(self): self.assertEqual(collist, columns) datalist = ( identity_fakes.domain_id, - auth_ref.expires, + identity_fakes.token_expires, identity_fakes.token_id, identity_fakes.user_id, ) @@ -109,7 +109,7 @@ def test_token_issue_with_unscoped(self): ) self.assertEqual(collist, columns) datalist = ( - auth_ref.expires, + identity_fakes.token_expires, identity_fakes.token_id, identity_fakes.user_id, ) From eba1bc04f4f8d70d4b036e6a6aa3e5706526de1d Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Wed, 15 Jun 2016 14:25:51 -0400 Subject: [PATCH 1197/3095] better functional test collection straight up copied from glanceclient [1] [1] https://github.com/openstack/python-glanceclient/blob/master/glanceclient/tests/functional/hooks/post_test_hook.sh Change-Id: I9fad6d5c86831a2b872f3a61d4c7fa7383fc1266 --- post_test_hook.sh | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/post_test_hook.sh b/post_test_hook.sh index 7bb036f9cf..e555470d17 100755 --- a/post_test_hook.sh +++ b/post_test_hook.sh @@ -6,15 +6,38 @@ # For more information refer to: # http://docs.openstack.org/developer/python-openstackclient/ -set -xe +function generate_testr_results { + if [ -f .testrepository/0 ]; then + sudo .tox/functional/bin/testr last --subunit > $WORKSPACE/testrepository.subunit + sudo mv $WORKSPACE/testrepository.subunit $BASE/logs/testrepository.subunit + sudo .tox/functional/bin/subunit2html $BASE/logs/testrepository.subunit $BASE/logs/testr_results.html + sudo gzip -9 $BASE/logs/testrepository.subunit + sudo gzip -9 $BASE/logs/testr_results.html + sudo chown jenkins:jenkins $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz + sudo chmod a+r $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz + fi +} OPENSTACKCLIENT_DIR=$(cd $(dirname "$0") && pwd) +sudo chown -R jenkins:stack $OPENSTACKCLIENT_DIR +# Run tests echo "Running openstackclient functional test suite" -sudo -H -u stack -i < Date: Wed, 7 Sep 2016 16:23:45 +0800 Subject: [PATCH 1198/3095] Do not show "os-volume-type-access:is_public" property of volume type "os-volume-type-access:is_public" property is the same as "is_public" property in volume type object. So stop showing "os-volume-type-access:is_public" property and leave "is_public" property only. Change-Id: Ic78a9ee69b0ab356edff18cdb4c46fc24b495d2b Closes-Bug: #1620922 --- openstackclient/volume/v1/volume_type.py | 6 +++--- openstackclient/volume/v2/volume_type.py | 2 ++ releasenotes/notes/bug-1620922-7f27942dc00f7108.yaml | 5 +++++ 3 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/bug-1620922-7f27942dc00f7108.yaml diff --git a/openstackclient/volume/v1/volume_type.py b/openstackclient/volume/v1/volume_type.py index 3fe4fa05f1..625b34dc43 100644 --- a/openstackclient/volume/v1/volume_type.py +++ b/openstackclient/volume/v1/volume_type.py @@ -55,10 +55,9 @@ def take_action(self, parsed_args): if parsed_args.property: result = volume_type.set_keys(parsed_args.property) volume_type._info.update({'properties': utils.format_dict(result)}) + volume_type._info.pop("os-volume-type-access:is_public", None) - info = {} - info.update(volume_type._info) - return zip(*sorted(six.iteritems(info))) + return zip(*sorted(six.iteritems(volume_type._info))) class DeleteVolumeType(command.Command): @@ -171,6 +170,7 @@ def take_action(self, parsed_args): volume_client.volume_types, parsed_args.volume_type) properties = utils.format_dict(volume_type._info.pop('extra_specs')) volume_type._info.update({'properties': properties}) + volume_type._info.pop("os-volume-type-access:is_public", None) return zip(*sorted(six.iteritems(volume_type._info))) diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index e42fffe032..80a1f21b11 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -110,6 +110,7 @@ def take_action(self, parsed_args): if parsed_args.property: result = volume_type.set_keys(parsed_args.property) volume_type._info.update({'properties': utils.format_dict(result)}) + volume_type._info.pop("os-volume-type-access:is_public", None) return zip(*sorted(six.iteritems(volume_type._info))) @@ -318,6 +319,7 @@ def take_action(self, parsed_args): '%(type)s: %(e)s') LOG.error(msg % {'type': volume_type.id, 'e': e}) volume_type._info.update({'access_project_ids': access_project_ids}) + volume_type._info.pop("os-volume-type-access:is_public", None) return zip(*sorted(six.iteritems(volume_type._info))) diff --git a/releasenotes/notes/bug-1620922-7f27942dc00f7108.yaml b/releasenotes/notes/bug-1620922-7f27942dc00f7108.yaml new file mode 100644 index 0000000000..35a7f552ae --- /dev/null +++ b/releasenotes/notes/bug-1620922-7f27942dc00f7108.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - Do not show ``os-volume-type-access:is_public`` property which is the + same as ``is_public`` property of volume type object. + [Bug `1620922 `_] From d1de0bac88be0ed9fb0fe7cd804cda98ff2c9f66 Mon Sep 17 00:00:00 2001 From: Doug Hellmann Date: Wed, 7 Sep 2016 17:08:46 -0400 Subject: [PATCH 1199/3095] standardize release note page ordering In order to support automatically updating the release notes when we create stable branches, we want the pages to be in a standard order. This patch updates the order to be reverse chronological, so the most recent notes appear at the top. Change-Id: Ib364dcc8eb31275a31c83b68d7914263b183e393 Signed-off-by: Doug Hellmann --- releasenotes/source/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index 49f8ecda23..7739e4b4f3 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,9 +6,9 @@ OpenStackClient Release Notes :maxdepth: 1 unreleased + newton 20_releases pre_20_releases - newton OpenStack Releases ------------------ From 14dbfe44741b65c9e0514a34669f52de8629b1c0 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 1 Sep 2016 10:54:29 -0500 Subject: [PATCH 1200/3095] Defer auth prompting until it is actually needed Auth option prompting happens waaaay to early in the default os-client-config flow, we need to defer it until adter the commands have been parsed. This is why ClientManager.setup_auth() exists, as it is not called until the first attempt to connect to a server occurs. Commands that do not require authentication never hit this. Also, required options were not being enforced. By doing this we handle when no authentication info is present, we fail on missing auth-url rather than attempt to prompt for a password (default auth is password). Closes-Bug: 1619274 Change-Id: Ia4eae350e6904c9eb2c8507d9b3429fe52418726 --- openstackclient/common/client_config.py | 28 +++++++++++++++++++ openstackclient/common/clientmanager.py | 20 ++++++++++++++ openstackclient/shell.py | 36 ++++++++++++++++++++++--- 3 files changed, 81 insertions(+), 3 deletions(-) diff --git a/openstackclient/common/client_config.py b/openstackclient/common/client_config.py index 895909e97c..30286df8af 100644 --- a/openstackclient/common/client_config.py +++ b/openstackclient/common/client_config.py @@ -16,6 +16,7 @@ import logging from os_client_config import config +from os_client_config import exceptions as occ_exceptions LOG = logging.getLogger(__name__) @@ -182,6 +183,14 @@ def auth_config_hook(self, config): LOG.debug("auth_config_hook(): %s" % config) return config + def load_auth_plugin(self, config): + """Get auth plugin and validate args""" + + loader = self._get_auth_loader(config) + config = self._validate_auth(config, loader) + auth_plugin = loader.load_from_options(**config['auth']) + return auth_plugin + def _validate_auth_ksc(self, config, cloud, fixed_argparse=None): """Old compatibility hack for OSC, no longer needed/wanted""" return config @@ -192,6 +201,8 @@ def _validate_auth(self, config, loader, fixed_argparse=None): plugin_options = loader.get_options() + msgs = [] + prompt_options = [] for p_opt in plugin_options: # if it's in config, win, move it and kill it from config dict # if it's in config.auth but not in config we're good @@ -202,6 +213,16 @@ def _validate_auth(self, config, loader, fixed_argparse=None): winning_value = self._find_winning_auth_value( p_opt, config['auth']) + # if the plugin tells us that this value is required + # then error if it's doesn't exist now + if not winning_value and p_opt.required: + msgs.append( + 'Missing value {auth_key}' + ' required for auth plugin {plugin}'.format( + auth_key=p_opt.name, plugin=config.get('auth_type'), + ) + ) + # Clean up after ourselves for opt in [p_opt.name] + [o.name for o in p_opt.deprecated]: opt = opt.replace('-', '_') @@ -224,6 +245,13 @@ def _validate_auth(self, config, loader, fixed_argparse=None): p_opt.dest not in config['auth'] and self._pw_callback is not None ): + # Defer these until we know all required opts are present + prompt_options.append(p_opt) + + if msgs: + raise occ_exceptions.OpenStackConfigException('\n'.join(msgs)) + else: + for p_opt in prompt_options: config['auth'][p_opt.dest] = self._pw_callback(p_opt.prompt) return config diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index 57423aed6b..23c35a3b28 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -60,6 +60,26 @@ def __init__( self._cacert = self.cacert self._insecure = not self.verify + def setup_auth(self): + """Set up authentication""" + + if self._auth_setup_completed: + return + + # NOTE(dtroyer): Validate the auth args; this is protected with 'if' + # because openstack_config is an optional argument to + # CloudConfig.__init__() and we'll die if it was not + # passed. + if self._cli_options._openstack_config is not None: + self._cli_options._openstack_config._pw_callback = \ + shell.prompt_for_password + self._cli_options._auth = \ + self._cli_options._openstack_config.load_auth_plugin( + self._cli_options.config, + ) + + return super(ClientManager, self).setup_auth() + def is_network_endpoint_enabled(self): """Check if the network endpoint is enabled""" diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 26147be999..3971b6ef4a 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -140,12 +140,11 @@ def initialize_app(self, argv): # First, throw away what has already been done with o-c-c and # use our own. try: - cc = cloud_config.OSC_Config( + self.cloud_config = cloud_config.OSC_Config( override_defaults={ 'interface': None, 'auth_type': self._auth_type, }, - pw_func=shell.prompt_for_password, ) except (IOError, OSError) as e: self.log.critical("Could not read clouds.yaml configuration file") @@ -154,9 +153,13 @@ def initialize_app(self, argv): if not self.options.debug: self.options.debug = None - self.cloud = cc.get_one_cloud( + + # NOTE(dtroyer): Need to do this with validate=False to defer the + # auth plugin handling to ClientManager.setup_auth() + self.cloud = self.cloud_config.get_one_cloud( cloud=self.options.cloud, argparse=self.options, + validate=False, ) # Then, re-create the client_manager with the correct arguments @@ -165,6 +168,33 @@ def initialize_app(self, argv): api_version=self.api_version, ) + def prepare_to_run_command(self, cmd): + """Set up auth and API versions""" + + # TODO(dtroyer): Move this to osc-lib + # NOTE(dtroyer): If auth is not required for a command, force fake + # token auth so KSA plugins are happy + + kwargs = {} + if not cmd.auth_required: + # Build fake token creds to keep ksa and o-c-c hushed + kwargs['auth_type'] = 'token_endpoint' + kwargs['auth'] = {} + kwargs['auth']['token'] = 'x' + kwargs['auth']['url'] = 'x' + + # Validate auth options + self.cloud = self.cloud_config.get_one_cloud( + cloud=self.options.cloud, + argparse=self.options, + validate=True, + **kwargs + ) + # Push the updated args into ClientManager + self.client_manager._cli_options = self.cloud + + return super(OpenStackShell, self).prepare_to_run_command(cmd) + def main(argv=None): if argv is None: From 39839def2e356e8d145be89380c73a71423cf06d Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Mon, 5 Sep 2016 22:14:33 -0700 Subject: [PATCH 1201/3095] move unit tests to new "unit" test module this will better isolate the unit tests from the functional tests. unfortunately, the "integration" tests had to be lumped into the "unit" tests since we need the separation in testr.conf Change-Id: Ifd12198c1f90e4e3c951c73bfa1884ab300d8ded --- .testr.conf | 2 +- .../tests/{api => unit}/__init__.py | 0 .../tests/{common => unit/api}/__init__.py | 0 openstackclient/tests/{ => unit}/api/fakes.py | 2 +- .../tests/{ => unit}/api/test_api.py | 2 +- .../tests/{ => unit}/api/test_image_v1.py | 2 +- .../tests/{ => unit}/api/test_image_v2.py | 2 +- .../{ => unit}/api/test_object_store_v1.py | 2 +- .../tests/{ => unit}/api/test_utils.py | 2 +- .../{compute => unit/common}/__init__.py | 0 .../common/test_availability_zone.py | 10 +- .../{ => unit}/common/test_clientmanager.py | 2 +- .../tests/{ => unit}/common/test_command.py | 6 +- .../{ => unit}/common/test_commandmanager.py | 2 +- .../{ => unit}/common/test_configuration.py | 4 +- .../tests/{ => unit}/common/test_extension.py | 12 +- .../tests/{ => unit}/common/test_logs.py | 2 +- .../tests/{ => unit}/common/test_module.py | 4 +- .../{ => unit}/common/test_parseractions.py | 2 +- .../tests/{ => unit}/common/test_quota.py | 8 +- .../{compute/v2 => unit/compute}/__init__.py | 0 .../{identity => unit/compute/v2}/__init__.py | 0 .../tests/{ => unit}/compute/v2/fakes.py | 12 +- .../tests/{ => unit}/compute/v2/test_agent.py | 4 +- .../{ => unit}/compute/v2/test_aggregate.py | 4 +- .../{ => unit}/compute/v2/test_console.py | 2 +- .../{ => unit}/compute/v2/test_flavor.py | 6 +- .../tests/{ => unit}/compute/v2/test_host.py | 4 +- .../{ => unit}/compute/v2/test_hypervisor.py | 4 +- .../compute/v2/test_hypervisor_stats.py | 2 +- .../{ => unit}/compute/v2/test_keypair.py | 4 +- .../{ => unit}/compute/v2/test_server.py | 8 +- .../compute/v2/test_server_backup.py | 4 +- .../compute/v2/test_server_group.py | 4 +- .../compute/v2/test_server_image.py | 4 +- .../{ => unit}/compute/v2/test_service.py | 2 +- openstackclient/tests/{ => unit}/fakes.py | 0 .../v2_0 => unit/identity}/__init__.py | 0 .../v3 => unit/identity/v2_0}/__init__.py | 0 .../tests/{ => unit}/identity/v2_0/fakes.py | 4 +- .../{ => unit}/identity/v2_0/test_catalog.py | 4 +- .../{ => unit}/identity/v2_0/test_endpoint.py | 2 +- .../{ => unit}/identity/v2_0/test_project.py | 2 +- .../{ => unit}/identity/v2_0/test_role.py | 2 +- .../identity/v2_0/test_role_assignment.py | 4 +- .../{ => unit}/identity/v2_0/test_service.py | 2 +- .../{ => unit}/identity/v2_0/test_token.py | 2 +- .../{ => unit}/identity/v2_0/test_user.py | 2 +- .../{image => unit/identity/v3}/__init__.py | 0 .../tests/{ => unit}/identity/v3/fakes.py | 4 +- .../{ => unit}/identity/v3/test_catalog.py | 4 +- .../{ => unit}/identity/v3/test_consumer.py | 4 +- .../{ => unit}/identity/v3/test_credential.py | 4 +- .../{ => unit}/identity/v3/test_domain.py | 2 +- .../{ => unit}/identity/v3/test_endpoint.py | 2 +- .../{ => unit}/identity/v3/test_group.py | 2 +- .../identity/v3/test_identity_provider.py | 4 +- .../{ => unit}/identity/v3/test_mappings.py | 4 +- .../{ => unit}/identity/v3/test_oauth.py | 4 +- .../{ => unit}/identity/v3/test_project.py | 2 +- .../{ => unit}/identity/v3/test_protocol.py | 4 +- .../{ => unit}/identity/v3/test_region.py | 4 +- .../tests/{ => unit}/identity/v3/test_role.py | 4 +- .../identity/v3/test_role_assignment.py | 4 +- .../{ => unit}/identity/v3/test_service.py | 2 +- .../identity/v3/test_service_provider.py | 4 +- .../{ => unit}/identity/v3/test_token.py | 2 +- .../{ => unit}/identity/v3/test_trust.py | 4 +- .../identity/v3/test_unscoped_saml.py | 4 +- .../tests/{ => unit}/identity/v3/test_user.py | 2 +- .../{image/v1 => unit/image}/__init__.py | 0 .../{image/v2 => unit/image/v1}/__init__.py | 0 .../tests/{ => unit}/image/v1/fakes.py | 6 +- .../tests/{ => unit}/image/v1/test_image.py | 4 +- .../{integ => unit/image/v2}/__init__.py | 0 .../tests/{ => unit}/image/v2/fakes.py | 6 +- .../tests/{ => unit}/image/v2/test_image.py | 4 +- .../{integ/cli => unit/integ}/__init__.py | 0 .../tests/{ => unit}/integ/base.py | 4 +- .../{network => unit/integ/cli}/__init__.py | 0 .../{ => unit}/integ/cli/test_project.py | 4 +- .../tests/{ => unit}/integ/cli/test_shell.py | 4 +- .../{network/v2 => unit/network}/__init__.py | 0 .../tests/{ => unit}/network/test_common.py | 2 +- .../{object => unit/network/v2}/__init__.py | 0 .../tests/{ => unit}/network/v2/fakes.py | 6 +- .../network/v2/test_address_scope.py | 6 +- .../{ => unit}/network/v2/test_floating_ip.py | 6 +- .../network/v2/test_floating_ip_pool.py | 4 +- .../network/v2/test_ip_availability.py | 6 +- .../{ => unit}/network/v2/test_network.py | 12 +- .../network/v2/test_network_agent.py | 4 +- .../network/v2/test_network_rbac.py | 6 +- .../network/v2/test_network_segment.py | 4 +- .../tests/{ => unit}/network/v2/test_port.py | 4 +- .../{ => unit}/network/v2/test_router.py | 4 +- .../network/v2/test_security_group.py | 8 +- .../network/v2/test_security_group_rule.py | 10 +- .../{ => unit}/network/v2/test_subnet.py | 6 +- .../{ => unit}/network/v2/test_subnet_pool.py | 6 +- .../{object/v1 => unit/object}/__init__.py | 0 .../{volume => unit/object/v1}/__init__.py | 0 .../tests/{ => unit}/object/v1/fakes.py | 2 +- .../{ => unit}/object/v1/test_container.py | 2 +- .../object/v1/test_container_all.py | 2 +- .../tests/{ => unit}/object/v1/test_object.py | 2 +- .../{ => unit}/object/v1/test_object_all.py | 2 +- .../tests/{ => unit}/test_shell.py | 0 openstackclient/tests/{ => unit}/utils.py | 2 +- .../{volume/v1 => unit/volume}/__init__.py | 0 .../{ => unit}/volume/test_find_resource.py | 2 +- .../{volume/v2 => unit/volume/v1}/__init__.py | 0 .../tests/{ => unit}/volume/v1/fakes.py | 6 +- .../{ => unit}/volume/v1/test_qos_specs.py | 4 +- .../tests/unit/volume/v1/test_service.py | 286 ++++++++++++++++++ .../volume/v1/test_transfer_request.py | 2 +- .../tests/{ => unit}/volume/v1/test_volume.py | 6 +- .../tests/unit/volume/v2/__init__.py | 0 .../tests/{ => unit}/volume/v2/fakes.py | 8 +- .../tests/{ => unit}/volume/v2/test_backup.py | 2 +- .../{ => unit}/volume/v2/test_qos_specs.py | 2 +- .../{ => unit}/volume/v2/test_service.py | 2 +- .../{ => unit}/volume/v2/test_snapshot.py | 2 +- .../volume/v2/test_transfer_request.py | 2 +- .../tests/{ => unit}/volume/v2/test_type.py | 6 +- .../tests/{ => unit}/volume/v2/test_volume.py | 6 +- 126 files changed, 492 insertions(+), 206 deletions(-) rename openstackclient/tests/{api => unit}/__init__.py (100%) rename openstackclient/tests/{common => unit/api}/__init__.py (100%) rename openstackclient/tests/{ => unit}/api/fakes.py (96%) rename openstackclient/tests/{ => unit}/api/test_api.py (99%) rename openstackclient/tests/{ => unit}/api/test_image_v1.py (98%) rename openstackclient/tests/{ => unit}/api/test_image_v2.py (98%) rename openstackclient/tests/{ => unit}/api/test_object_store_v1.py (99%) rename openstackclient/tests/{ => unit}/api/test_utils.py (98%) rename openstackclient/tests/{compute => unit/common}/__init__.py (100%) rename openstackclient/tests/{ => unit}/common/test_availability_zone.py (96%) rename openstackclient/tests/{ => unit}/common/test_clientmanager.py (98%) rename openstackclient/tests/{ => unit}/common/test_command.py (88%) rename openstackclient/tests/{ => unit}/common/test_commandmanager.py (98%) rename openstackclient/tests/{ => unit}/common/test_configuration.py (96%) rename openstackclient/tests/{ => unit}/common/test_extension.py (95%) rename openstackclient/tests/{ => unit}/common/test_logs.py (99%) rename openstackclient/tests/{ => unit}/common/test_module.py (97%) rename openstackclient/tests/{ => unit}/common/test_parseractions.py (99%) rename openstackclient/tests/{ => unit}/common/test_quota.py (98%) rename openstackclient/tests/{compute/v2 => unit/compute}/__init__.py (100%) rename openstackclient/tests/{identity => unit/compute/v2}/__init__.py (100%) rename openstackclient/tests/{ => unit}/compute/v2/fakes.py (99%) rename openstackclient/tests/{ => unit}/compute/v2/test_agent.py (98%) rename openstackclient/tests/{ => unit}/compute/v2/test_aggregate.py (99%) rename openstackclient/tests/{ => unit}/compute/v2/test_console.py (98%) rename openstackclient/tests/{ => unit}/compute/v2/test_flavor.py (99%) rename openstackclient/tests/{ => unit}/compute/v2/test_host.py (97%) rename openstackclient/tests/{ => unit}/compute/v2/test_hypervisor.py (98%) rename openstackclient/tests/{ => unit}/compute/v2/test_hypervisor_stats.py (96%) rename openstackclient/tests/{ => unit}/compute/v2/test_keypair.py (98%) rename openstackclient/tests/{ => unit}/compute/v2/test_server.py (99%) rename openstackclient/tests/{ => unit}/compute/v2/test_server_backup.py (98%) rename openstackclient/tests/{ => unit}/compute/v2/test_server_group.py (98%) rename openstackclient/tests/{ => unit}/compute/v2/test_server_image.py (98%) rename openstackclient/tests/{ => unit}/compute/v2/test_service.py (99%) rename openstackclient/tests/{ => unit}/fakes.py (100%) rename openstackclient/tests/{identity/v2_0 => unit/identity}/__init__.py (100%) rename openstackclient/tests/{identity/v3 => unit/identity/v2_0}/__init__.py (100%) rename openstackclient/tests/{ => unit}/identity/v2_0/fakes.py (99%) rename openstackclient/tests/{ => unit}/identity/v2_0/test_catalog.py (98%) rename openstackclient/tests/{ => unit}/identity/v2_0/test_endpoint.py (99%) rename openstackclient/tests/{ => unit}/identity/v2_0/test_project.py (99%) rename openstackclient/tests/{ => unit}/identity/v2_0/test_role.py (99%) rename openstackclient/tests/{ => unit}/identity/v2_0/test_role_assignment.py (98%) rename openstackclient/tests/{ => unit}/identity/v2_0/test_service.py (99%) rename openstackclient/tests/{ => unit}/identity/v2_0/test_token.py (97%) rename openstackclient/tests/{ => unit}/identity/v2_0/test_user.py (99%) rename openstackclient/tests/{image => unit/identity/v3}/__init__.py (100%) rename openstackclient/tests/{ => unit}/identity/v3/fakes.py (99%) rename openstackclient/tests/{ => unit}/identity/v3/test_catalog.py (97%) rename openstackclient/tests/{ => unit}/identity/v3/test_consumer.py (98%) rename openstackclient/tests/{ => unit}/identity/v3/test_credential.py (98%) rename openstackclient/tests/{ => unit}/identity/v3/test_domain.py (99%) rename openstackclient/tests/{ => unit}/identity/v3/test_endpoint.py (99%) rename openstackclient/tests/{ => unit}/identity/v3/test_group.py (99%) rename openstackclient/tests/{ => unit}/identity/v3/test_identity_provider.py (99%) rename openstackclient/tests/{ => unit}/identity/v3/test_mappings.py (98%) rename openstackclient/tests/{ => unit}/identity/v3/test_oauth.py (98%) rename openstackclient/tests/{ => unit}/identity/v3/test_project.py (99%) rename openstackclient/tests/{ => unit}/identity/v3/test_protocol.py (98%) rename openstackclient/tests/{ => unit}/identity/v3/test_region.py (98%) rename openstackclient/tests/{ => unit}/identity/v3/test_role.py (99%) rename openstackclient/tests/{ => unit}/identity/v3/test_role_assignment.py (99%) rename openstackclient/tests/{ => unit}/identity/v3/test_service.py (99%) rename openstackclient/tests/{ => unit}/identity/v3/test_service_provider.py (99%) rename openstackclient/tests/{ => unit}/identity/v3/test_token.py (98%) rename openstackclient/tests/{ => unit}/identity/v3/test_trust.py (98%) rename openstackclient/tests/{ => unit}/identity/v3/test_unscoped_saml.py (97%) rename openstackclient/tests/{ => unit}/identity/v3/test_user.py (99%) rename openstackclient/tests/{image/v1 => unit/image}/__init__.py (100%) rename openstackclient/tests/{image/v2 => unit/image/v1}/__init__.py (100%) rename openstackclient/tests/{ => unit}/image/v1/fakes.py (92%) rename openstackclient/tests/{ => unit}/image/v1/test_image.py (99%) rename openstackclient/tests/{integ => unit/image/v2}/__init__.py (100%) rename openstackclient/tests/{ => unit}/image/v2/fakes.py (98%) rename openstackclient/tests/{ => unit}/image/v2/test_image.py (99%) rename openstackclient/tests/{integ/cli => unit/integ}/__init__.py (100%) rename openstackclient/tests/{ => unit}/integ/base.py (97%) rename openstackclient/tests/{network => unit/integ/cli}/__init__.py (100%) rename openstackclient/tests/{ => unit}/integ/cli/test_project.py (98%) rename openstackclient/tests/{ => unit}/integ/cli/test_shell.py (99%) rename openstackclient/tests/{network/v2 => unit/network}/__init__.py (100%) rename openstackclient/tests/{ => unit}/network/test_common.py (99%) rename openstackclient/tests/{object => unit/network/v2}/__init__.py (100%) rename openstackclient/tests/{ => unit}/network/v2/fakes.py (99%) rename openstackclient/tests/{ => unit}/network/v2/test_address_scope.py (98%) rename openstackclient/tests/{ => unit}/network/v2/test_floating_ip.py (98%) rename openstackclient/tests/{ => unit}/network/v2/test_floating_ip_pool.py (95%) rename openstackclient/tests/{ => unit}/network/v2/test_ip_availability.py (96%) rename openstackclient/tests/{ => unit}/network/v2/test_network.py (98%) rename openstackclient/tests/{ => unit}/network/v2/test_network_agent.py (98%) rename openstackclient/tests/{ => unit}/network/v2/test_network_rbac.py (98%) rename openstackclient/tests/{ => unit}/network/v2/test_network_segment.py (97%) rename openstackclient/tests/{ => unit}/network/v2/test_port.py (99%) rename openstackclient/tests/{ => unit}/network/v2/test_router.py (99%) rename openstackclient/tests/{ => unit}/network/v2/test_security_group.py (98%) rename openstackclient/tests/{ => unit}/network/v2/test_security_group_rule.py (99%) rename openstackclient/tests/{ => unit}/network/v2/test_subnet.py (99%) rename openstackclient/tests/{ => unit}/network/v2/test_subnet_pool.py (99%) rename openstackclient/tests/{object/v1 => unit/object}/__init__.py (100%) rename openstackclient/tests/{volume => unit/object/v1}/__init__.py (100%) rename openstackclient/tests/{ => unit}/object/v1/fakes.py (98%) rename openstackclient/tests/{ => unit}/object/v1/test_container.py (99%) rename openstackclient/tests/{ => unit}/object/v1/test_container_all.py (99%) rename openstackclient/tests/{ => unit}/object/v1/test_object.py (99%) rename openstackclient/tests/{ => unit}/object/v1/test_object_all.py (98%) rename openstackclient/tests/{ => unit}/test_shell.py (100%) rename openstackclient/tests/{ => unit}/utils.py (98%) rename openstackclient/tests/{volume/v1 => unit/volume}/__init__.py (100%) rename openstackclient/tests/{ => unit}/volume/test_find_resource.py (97%) rename openstackclient/tests/{volume/v2 => unit/volume/v1}/__init__.py (100%) rename openstackclient/tests/{ => unit}/volume/v1/fakes.py (97%) rename openstackclient/tests/{ => unit}/volume/v1/test_qos_specs.py (99%) create mode 100644 openstackclient/tests/unit/volume/v1/test_service.py rename openstackclient/tests/{ => unit}/volume/v1/test_transfer_request.py (98%) rename openstackclient/tests/{ => unit}/volume/v1/test_volume.py (99%) create mode 100644 openstackclient/tests/unit/volume/v2/__init__.py rename openstackclient/tests/{ => unit}/volume/v2/fakes.py (98%) rename openstackclient/tests/{ => unit}/volume/v2/test_backup.py (99%) rename openstackclient/tests/{ => unit}/volume/v2/test_qos_specs.py (99%) rename openstackclient/tests/{ => unit}/volume/v2/test_service.py (99%) rename openstackclient/tests/{ => unit}/volume/v2/test_snapshot.py (99%) rename openstackclient/tests/{ => unit}/volume/v2/test_transfer_request.py (98%) rename openstackclient/tests/{ => unit}/volume/v2/test_type.py (98%) rename openstackclient/tests/{ => unit}/volume/v2/test_volume.py (99%) diff --git a/.testr.conf b/.testr.conf index 90c80c1873..633aae1faf 100644 --- a/.testr.conf +++ b/.testr.conf @@ -2,7 +2,7 @@ test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \ OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \ OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \ - ${PYTHON:-python} -m subunit.run discover -t ./ ${OS_TEST_PATH:-./openstackclient/tests} $LISTOPT $IDOPTION + ${PYTHON:-python} -m subunit.run discover -t ./ ${OS_TEST_PATH:-./openstackclient/tests/unit} $LISTOPT $IDOPTION test_id_option=--load-list $IDFILE test_list_option=--list diff --git a/openstackclient/tests/api/__init__.py b/openstackclient/tests/unit/__init__.py similarity index 100% rename from openstackclient/tests/api/__init__.py rename to openstackclient/tests/unit/__init__.py diff --git a/openstackclient/tests/common/__init__.py b/openstackclient/tests/unit/api/__init__.py similarity index 100% rename from openstackclient/tests/common/__init__.py rename to openstackclient/tests/unit/api/__init__.py diff --git a/openstackclient/tests/api/fakes.py b/openstackclient/tests/unit/api/fakes.py similarity index 96% rename from openstackclient/tests/api/fakes.py rename to openstackclient/tests/unit/api/fakes.py index 8d1d88ffc8..26213a2f42 100644 --- a/openstackclient/tests/api/fakes.py +++ b/openstackclient/tests/unit/api/fakes.py @@ -16,7 +16,7 @@ from keystoneauth1 import session from requests_mock.contrib import fixture -from openstackclient.tests import utils +from openstackclient.tests.unit import utils RESP_ITEM_1 = { diff --git a/openstackclient/tests/api/test_api.py b/openstackclient/tests/unit/api/test_api.py similarity index 99% rename from openstackclient/tests/api/test_api.py rename to openstackclient/tests/unit/api/test_api.py index b444d9f1f3..5f4a0c1afa 100644 --- a/openstackclient/tests/api/test_api.py +++ b/openstackclient/tests/unit/api/test_api.py @@ -16,7 +16,7 @@ from osc_lib import exceptions from openstackclient.api import api -from openstackclient.tests.api import fakes as api_fakes +from openstackclient.tests.unit.api import fakes as api_fakes class TestKeystoneSession(api_fakes.TestSession): diff --git a/openstackclient/tests/api/test_image_v1.py b/openstackclient/tests/unit/api/test_image_v1.py similarity index 98% rename from openstackclient/tests/api/test_image_v1.py rename to openstackclient/tests/unit/api/test_image_v1.py index f8ad669200..e02ef3812b 100644 --- a/openstackclient/tests/api/test_image_v1.py +++ b/openstackclient/tests/unit/api/test_image_v1.py @@ -17,7 +17,7 @@ from requests_mock.contrib import fixture from openstackclient.api import image_v1 -from openstackclient.tests import utils +from openstackclient.tests.unit import utils FAKE_PROJECT = 'xyzpdq' diff --git a/openstackclient/tests/api/test_image_v2.py b/openstackclient/tests/unit/api/test_image_v2.py similarity index 98% rename from openstackclient/tests/api/test_image_v2.py rename to openstackclient/tests/unit/api/test_image_v2.py index 28b0d5803a..5dbb51e03c 100644 --- a/openstackclient/tests/api/test_image_v2.py +++ b/openstackclient/tests/unit/api/test_image_v2.py @@ -17,7 +17,7 @@ from requests_mock.contrib import fixture from openstackclient.api import image_v2 -from openstackclient.tests import utils +from openstackclient.tests.unit import utils FAKE_PROJECT = 'xyzpdq' diff --git a/openstackclient/tests/api/test_object_store_v1.py b/openstackclient/tests/unit/api/test_object_store_v1.py similarity index 99% rename from openstackclient/tests/api/test_object_store_v1.py rename to openstackclient/tests/unit/api/test_object_store_v1.py index e6ac203d10..acf955501c 100644 --- a/openstackclient/tests/api/test_object_store_v1.py +++ b/openstackclient/tests/unit/api/test_object_store_v1.py @@ -19,7 +19,7 @@ from requests_mock.contrib import fixture from openstackclient.api import object_store_v1 as object_store -from openstackclient.tests import utils +from openstackclient.tests.unit import utils FAKE_ACCOUNT = 'q12we34r' diff --git a/openstackclient/tests/api/test_utils.py b/openstackclient/tests/unit/api/test_utils.py similarity index 98% rename from openstackclient/tests/api/test_utils.py rename to openstackclient/tests/unit/api/test_utils.py index b87bdd13a0..1f52855864 100644 --- a/openstackclient/tests/api/test_utils.py +++ b/openstackclient/tests/unit/api/test_utils.py @@ -17,7 +17,7 @@ from openstackclient.api import api from openstackclient.api import utils as api_utils -from openstackclient.tests.api import fakes as api_fakes +from openstackclient.tests.unit.api import fakes as api_fakes class TestBaseAPIFilter(api_fakes.TestSession): diff --git a/openstackclient/tests/compute/__init__.py b/openstackclient/tests/unit/common/__init__.py similarity index 100% rename from openstackclient/tests/compute/__init__.py rename to openstackclient/tests/unit/common/__init__.py diff --git a/openstackclient/tests/common/test_availability_zone.py b/openstackclient/tests/unit/common/test_availability_zone.py similarity index 96% rename from openstackclient/tests/common/test_availability_zone.py rename to openstackclient/tests/unit/common/test_availability_zone.py index 014ab8bced..6c7adc43b6 100644 --- a/openstackclient/tests/common/test_availability_zone.py +++ b/openstackclient/tests/unit/common/test_availability_zone.py @@ -16,11 +16,11 @@ import six from openstackclient.common import availability_zone -from openstackclient.tests.compute.v2 import fakes as compute_fakes -from openstackclient.tests import fakes -from openstackclient.tests.network.v2 import fakes as network_fakes -from openstackclient.tests import utils -from openstackclient.tests.volume.v2 import fakes as volume_fakes +from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes +from openstackclient.tests.unit import fakes +from openstackclient.tests.unit.network.v2 import fakes as network_fakes +from openstackclient.tests.unit import utils +from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes def _build_compute_az_datalist(compute_az, long_datalist=False): diff --git a/openstackclient/tests/common/test_clientmanager.py b/openstackclient/tests/unit/common/test_clientmanager.py similarity index 98% rename from openstackclient/tests/common/test_clientmanager.py rename to openstackclient/tests/unit/common/test_clientmanager.py index 625e175a1d..7f82c35d94 100644 --- a/openstackclient/tests/common/test_clientmanager.py +++ b/openstackclient/tests/unit/common/test_clientmanager.py @@ -19,7 +19,7 @@ from osc_lib.tests import utils as osc_lib_test_utils from openstackclient.common import clientmanager -from openstackclient.tests import fakes +from openstackclient.tests.unit import fakes class TestClientManager(osc_lib_test_utils.TestClientManager): diff --git a/openstackclient/tests/common/test_command.py b/openstackclient/tests/unit/common/test_command.py similarity index 88% rename from openstackclient/tests/common/test_command.py rename to openstackclient/tests/unit/common/test_command.py index 658bc895c0..f24b290b62 100644 --- a/openstackclient/tests/common/test_command.py +++ b/openstackclient/tests/unit/common/test_command.py @@ -17,8 +17,8 @@ from osc_lib import exceptions from openstackclient.common import command -from openstackclient.tests import fakes as test_fakes -from openstackclient.tests import utils as test_utils +from openstackclient.tests.unit import fakes as test_fakes +from openstackclient.tests.unit import utils as test_utils class FakeCommand(command.Command): @@ -32,7 +32,7 @@ class TestCommand(test_utils.TestCase): def test_command_has_logger(self): cmd = FakeCommand(mock.Mock(), mock.Mock()) self.assertTrue(hasattr(cmd, 'log')) - self.assertEqual('openstackclient.tests.common.test_command.' + self.assertEqual('openstackclient.tests.unit.common.test_command.' 'FakeCommand', cmd.log.name) def test_validate_os_beta_command_enabled(self): diff --git a/openstackclient/tests/common/test_commandmanager.py b/openstackclient/tests/unit/common/test_commandmanager.py similarity index 98% rename from openstackclient/tests/common/test_commandmanager.py rename to openstackclient/tests/unit/common/test_commandmanager.py index e2b274dcb0..0c6c99c05d 100644 --- a/openstackclient/tests/common/test_commandmanager.py +++ b/openstackclient/tests/unit/common/test_commandmanager.py @@ -16,7 +16,7 @@ import mock from openstackclient.common import commandmanager -from openstackclient.tests import utils +from openstackclient.tests.unit import utils class FakeCommand(object): diff --git a/openstackclient/tests/common/test_configuration.py b/openstackclient/tests/unit/common/test_configuration.py similarity index 96% rename from openstackclient/tests/common/test_configuration.py rename to openstackclient/tests/unit/common/test_configuration.py index 915e5bd394..e10522b99c 100644 --- a/openstackclient/tests/common/test_configuration.py +++ b/openstackclient/tests/unit/common/test_configuration.py @@ -14,8 +14,8 @@ import mock from openstackclient.common import configuration -from openstackclient.tests import fakes -from openstackclient.tests import utils +from openstackclient.tests.unit import fakes +from openstackclient.tests.unit import utils class TestConfiguration(utils.TestCommand): diff --git a/openstackclient/tests/common/test_extension.py b/openstackclient/tests/unit/common/test_extension.py similarity index 95% rename from openstackclient/tests/common/test_extension.py rename to openstackclient/tests/unit/common/test_extension.py index 10023aa6ac..bf856ed1c3 100644 --- a/openstackclient/tests/common/test_extension.py +++ b/openstackclient/tests/unit/common/test_extension.py @@ -14,12 +14,12 @@ import mock from openstackclient.common import extension -from openstackclient.tests.compute.v2 import fakes as compute_fakes -from openstackclient.tests import fakes -from openstackclient.tests.identity.v2_0 import fakes as identity_fakes -from openstackclient.tests.network.v2 import fakes as network_fakes -from openstackclient.tests import utils -from openstackclient.tests.volume.v2 import fakes as volume_fakes +from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes +from openstackclient.tests.unit import fakes +from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes +from openstackclient.tests.unit.network.v2 import fakes as network_fakes +from openstackclient.tests.unit import utils +from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes class TestExtension(utils.TestCommand): diff --git a/openstackclient/tests/common/test_logs.py b/openstackclient/tests/unit/common/test_logs.py similarity index 99% rename from openstackclient/tests/common/test_logs.py rename to openstackclient/tests/unit/common/test_logs.py index 5091510cd3..4842c8d45c 100644 --- a/openstackclient/tests/common/test_logs.py +++ b/openstackclient/tests/unit/common/test_logs.py @@ -18,7 +18,7 @@ import mock from openstackclient.common import logs -from openstackclient.tests import utils +from openstackclient.tests.unit import utils class TestContext(utils.TestCase): diff --git a/openstackclient/tests/common/test_module.py b/openstackclient/tests/unit/common/test_module.py similarity index 97% rename from openstackclient/tests/common/test_module.py rename to openstackclient/tests/unit/common/test_module.py index 8d5bb5f1f7..eb54dbe04b 100644 --- a/openstackclient/tests/common/test_module.py +++ b/openstackclient/tests/unit/common/test_module.py @@ -18,8 +18,8 @@ import mock from openstackclient.common import module as osc_module -from openstackclient.tests import fakes -from openstackclient.tests import utils +from openstackclient.tests.unit import fakes +from openstackclient.tests.unit import utils # NOTE(dtroyer): module_1 must match the version list filter (not --all) diff --git a/openstackclient/tests/common/test_parseractions.py b/openstackclient/tests/unit/common/test_parseractions.py similarity index 99% rename from openstackclient/tests/common/test_parseractions.py rename to openstackclient/tests/unit/common/test_parseractions.py index 3038701f50..1212ad23d1 100644 --- a/openstackclient/tests/common/test_parseractions.py +++ b/openstackclient/tests/unit/common/test_parseractions.py @@ -19,7 +19,7 @@ import argparse from openstackclient.common import parseractions -from openstackclient.tests import utils +from openstackclient.tests.unit import utils class TestKeyValueAction(utils.TestCase): diff --git a/openstackclient/tests/common/test_quota.py b/openstackclient/tests/unit/common/test_quota.py similarity index 98% rename from openstackclient/tests/common/test_quota.py rename to openstackclient/tests/unit/common/test_quota.py index 16fa35f625..4a80a2b255 100644 --- a/openstackclient/tests/common/test_quota.py +++ b/openstackclient/tests/unit/common/test_quota.py @@ -14,10 +14,10 @@ import mock from openstackclient.common import quota -from openstackclient.tests.compute.v2 import fakes as compute_fakes -from openstackclient.tests import fakes -from openstackclient.tests.identity.v2_0 import fakes as identity_fakes -from openstackclient.tests.network.v2 import fakes as network_fakes +from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes +from openstackclient.tests.unit import fakes +from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes +from openstackclient.tests.unit.network.v2 import fakes as network_fakes class FakeQuotaResource(fakes.FakeResource): diff --git a/openstackclient/tests/compute/v2/__init__.py b/openstackclient/tests/unit/compute/__init__.py similarity index 100% rename from openstackclient/tests/compute/v2/__init__.py rename to openstackclient/tests/unit/compute/__init__.py diff --git a/openstackclient/tests/identity/__init__.py b/openstackclient/tests/unit/compute/v2/__init__.py similarity index 100% rename from openstackclient/tests/identity/__init__.py rename to openstackclient/tests/unit/compute/v2/__init__.py diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py similarity index 99% rename from openstackclient/tests/compute/v2/fakes.py rename to openstackclient/tests/unit/compute/v2/fakes.py index 85c11c9479..0e3d47ba84 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -17,12 +17,12 @@ import mock import uuid -from openstackclient.tests import fakes -from openstackclient.tests.identity.v2_0 import fakes as identity_fakes -from openstackclient.tests.image.v2 import fakes as image_fakes -from openstackclient.tests.network.v2 import fakes as network_fakes -from openstackclient.tests import utils -from openstackclient.tests.volume.v2 import fakes as volume_fakes +from openstackclient.tests.unit import fakes +from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes +from openstackclient.tests.unit.image.v2 import fakes as image_fakes +from openstackclient.tests.unit.network.v2 import fakes as network_fakes +from openstackclient.tests.unit import utils +from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes floating_ip_num = 100 fix_ip_num = 100 diff --git a/openstackclient/tests/compute/v2/test_agent.py b/openstackclient/tests/unit/compute/v2/test_agent.py similarity index 98% rename from openstackclient/tests/compute/v2/test_agent.py rename to openstackclient/tests/unit/compute/v2/test_agent.py index 07265bb040..169940e29c 100644 --- a/openstackclient/tests/compute/v2/test_agent.py +++ b/openstackclient/tests/unit/compute/v2/test_agent.py @@ -19,8 +19,8 @@ from osc_lib import exceptions from openstackclient.compute.v2 import agent -from openstackclient.tests.compute.v2 import fakes as compute_fakes -from openstackclient.tests import utils as tests_utils +from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes +from openstackclient.tests.unit import utils as tests_utils class TestAgent(compute_fakes.TestComputev2): diff --git a/openstackclient/tests/compute/v2/test_aggregate.py b/openstackclient/tests/unit/compute/v2/test_aggregate.py similarity index 99% rename from openstackclient/tests/compute/v2/test_aggregate.py rename to openstackclient/tests/unit/compute/v2/test_aggregate.py index 3ebca35f9c..c636d3de2f 100644 --- a/openstackclient/tests/compute/v2/test_aggregate.py +++ b/openstackclient/tests/unit/compute/v2/test_aggregate.py @@ -20,8 +20,8 @@ from osc_lib import utils from openstackclient.compute.v2 import aggregate -from openstackclient.tests.compute.v2 import fakes as compute_fakes -from openstackclient.tests import utils as tests_utils +from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes +from openstackclient.tests.unit import utils as tests_utils class TestAggregate(compute_fakes.TestComputev2): diff --git a/openstackclient/tests/compute/v2/test_console.py b/openstackclient/tests/unit/compute/v2/test_console.py similarity index 98% rename from openstackclient/tests/compute/v2/test_console.py rename to openstackclient/tests/unit/compute/v2/test_console.py index fe4f26ae9a..d53d241e36 100644 --- a/openstackclient/tests/compute/v2/test_console.py +++ b/openstackclient/tests/unit/compute/v2/test_console.py @@ -16,7 +16,7 @@ import mock from openstackclient.compute.v2 import console -from openstackclient.tests.compute.v2 import fakes as compute_fakes +from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes class TestConsole(compute_fakes.TestComputev2): diff --git a/openstackclient/tests/compute/v2/test_flavor.py b/openstackclient/tests/unit/compute/v2/test_flavor.py similarity index 99% rename from openstackclient/tests/compute/v2/test_flavor.py rename to openstackclient/tests/unit/compute/v2/test_flavor.py index d326ea8a88..ace650eb18 100644 --- a/openstackclient/tests/compute/v2/test_flavor.py +++ b/openstackclient/tests/unit/compute/v2/test_flavor.py @@ -20,9 +20,9 @@ from osc_lib import utils from openstackclient.compute.v2 import flavor -from openstackclient.tests.compute.v2 import fakes as compute_fakes -from openstackclient.tests.identity.v3 import fakes as identity_fakes -from openstackclient.tests import utils as tests_utils +from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes +from openstackclient.tests.unit import utils as tests_utils class TestFlavor(compute_fakes.TestComputev2): diff --git a/openstackclient/tests/compute/v2/test_host.py b/openstackclient/tests/unit/compute/v2/test_host.py similarity index 97% rename from openstackclient/tests/compute/v2/test_host.py rename to openstackclient/tests/unit/compute/v2/test_host.py index 9ebed68d45..a388172f9d 100644 --- a/openstackclient/tests/compute/v2/test_host.py +++ b/openstackclient/tests/unit/compute/v2/test_host.py @@ -14,8 +14,8 @@ # from openstackclient.compute.v2 import host -from openstackclient.tests.compute.v2 import fakes as compute_fakes -from openstackclient.tests import utils as tests_utils +from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes +from openstackclient.tests.unit import utils as tests_utils class TestHost(compute_fakes.TestComputev2): diff --git a/openstackclient/tests/compute/v2/test_hypervisor.py b/openstackclient/tests/unit/compute/v2/test_hypervisor.py similarity index 98% rename from openstackclient/tests/compute/v2/test_hypervisor.py rename to openstackclient/tests/unit/compute/v2/test_hypervisor.py index ee0f40ed94..d94a107c26 100644 --- a/openstackclient/tests/compute/v2/test_hypervisor.py +++ b/openstackclient/tests/unit/compute/v2/test_hypervisor.py @@ -18,8 +18,8 @@ from osc_lib import exceptions from openstackclient.compute.v2 import hypervisor -from openstackclient.tests.compute.v2 import fakes as compute_fakes -from openstackclient.tests import fakes +from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes +from openstackclient.tests.unit import fakes class TestHypervisor(compute_fakes.TestComputev2): diff --git a/openstackclient/tests/compute/v2/test_hypervisor_stats.py b/openstackclient/tests/unit/compute/v2/test_hypervisor_stats.py similarity index 96% rename from openstackclient/tests/compute/v2/test_hypervisor_stats.py rename to openstackclient/tests/unit/compute/v2/test_hypervisor_stats.py index ca5ce29b38..40086f9bf2 100644 --- a/openstackclient/tests/compute/v2/test_hypervisor_stats.py +++ b/openstackclient/tests/unit/compute/v2/test_hypervisor_stats.py @@ -14,7 +14,7 @@ # from openstackclient.compute.v2 import hypervisor_stats -from openstackclient.tests.compute.v2 import fakes as compute_fakes +from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes class TestHypervisorStats(compute_fakes.TestComputev2): diff --git a/openstackclient/tests/compute/v2/test_keypair.py b/openstackclient/tests/unit/compute/v2/test_keypair.py similarity index 98% rename from openstackclient/tests/compute/v2/test_keypair.py rename to openstackclient/tests/unit/compute/v2/test_keypair.py index 25949e310b..cb0085452d 100644 --- a/openstackclient/tests/compute/v2/test_keypair.py +++ b/openstackclient/tests/unit/compute/v2/test_keypair.py @@ -20,8 +20,8 @@ from osc_lib import utils from openstackclient.compute.v2 import keypair -from openstackclient.tests.compute.v2 import fakes as compute_fakes -from openstackclient.tests import utils as tests_utils +from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes +from openstackclient.tests.unit import utils as tests_utils class TestKeypair(compute_fakes.TestComputev2): diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py similarity index 99% rename from openstackclient/tests/compute/v2/test_server.py rename to openstackclient/tests/unit/compute/v2/test_server.py index a98398eed5..d4843f51c1 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -21,10 +21,10 @@ from osc_lib import utils as common_utils from openstackclient.compute.v2 import server -from openstackclient.tests.compute.v2 import fakes as compute_fakes -from openstackclient.tests.image.v2 import fakes as image_fakes -from openstackclient.tests import utils -from openstackclient.tests.volume.v2 import fakes as volume_fakes +from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes +from openstackclient.tests.unit.image.v2 import fakes as image_fakes +from openstackclient.tests.unit import utils +from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes class TestServer(compute_fakes.TestComputev2): diff --git a/openstackclient/tests/compute/v2/test_server_backup.py b/openstackclient/tests/unit/compute/v2/test_server_backup.py similarity index 98% rename from openstackclient/tests/compute/v2/test_server_backup.py rename to openstackclient/tests/unit/compute/v2/test_server_backup.py index 8eeb0dcabd..9aa63fc7ca 100644 --- a/openstackclient/tests/compute/v2/test_server_backup.py +++ b/openstackclient/tests/unit/compute/v2/test_server_backup.py @@ -17,8 +17,8 @@ from osc_lib import utils as common_utils from openstackclient.compute.v2 import server_backup -from openstackclient.tests.compute.v2 import fakes as compute_fakes -from openstackclient.tests.image.v2 import fakes as image_fakes +from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes +from openstackclient.tests.unit.image.v2 import fakes as image_fakes class TestServerBackup(compute_fakes.TestComputev2): diff --git a/openstackclient/tests/compute/v2/test_server_group.py b/openstackclient/tests/unit/compute/v2/test_server_group.py similarity index 98% rename from openstackclient/tests/compute/v2/test_server_group.py rename to openstackclient/tests/unit/compute/v2/test_server_group.py index bd5f84714c..d474f41d80 100644 --- a/openstackclient/tests/compute/v2/test_server_group.py +++ b/openstackclient/tests/unit/compute/v2/test_server_group.py @@ -19,8 +19,8 @@ from osc_lib import utils from openstackclient.compute.v2 import server_group -from openstackclient.tests.compute.v2 import fakes as compute_fakes -from openstackclient.tests import utils as tests_utils +from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes +from openstackclient.tests.unit import utils as tests_utils class TestServerGroup(compute_fakes.TestComputev2): diff --git a/openstackclient/tests/compute/v2/test_server_image.py b/openstackclient/tests/unit/compute/v2/test_server_image.py similarity index 98% rename from openstackclient/tests/compute/v2/test_server_image.py rename to openstackclient/tests/unit/compute/v2/test_server_image.py index c3c52da05f..f53f08e6e0 100644 --- a/openstackclient/tests/compute/v2/test_server_image.py +++ b/openstackclient/tests/unit/compute/v2/test_server_image.py @@ -16,8 +16,8 @@ from osc_lib import utils as common_utils from openstackclient.compute.v2 import server_image -from openstackclient.tests.compute.v2 import fakes as compute_fakes -from openstackclient.tests.image.v2 import fakes as image_fakes +from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes +from openstackclient.tests.unit.image.v2 import fakes as image_fakes class TestServerImage(compute_fakes.TestComputev2): diff --git a/openstackclient/tests/compute/v2/test_service.py b/openstackclient/tests/unit/compute/v2/test_service.py similarity index 99% rename from openstackclient/tests/compute/v2/test_service.py rename to openstackclient/tests/unit/compute/v2/test_service.py index 1599f466b7..1fd3b7d59f 100644 --- a/openstackclient/tests/compute/v2/test_service.py +++ b/openstackclient/tests/unit/compute/v2/test_service.py @@ -19,7 +19,7 @@ from osc_lib import exceptions from openstackclient.compute.v2 import service -from openstackclient.tests.compute.v2 import fakes as compute_fakes +from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes class TestService(compute_fakes.TestComputev2): diff --git a/openstackclient/tests/fakes.py b/openstackclient/tests/unit/fakes.py similarity index 100% rename from openstackclient/tests/fakes.py rename to openstackclient/tests/unit/fakes.py diff --git a/openstackclient/tests/identity/v2_0/__init__.py b/openstackclient/tests/unit/identity/__init__.py similarity index 100% rename from openstackclient/tests/identity/v2_0/__init__.py rename to openstackclient/tests/unit/identity/__init__.py diff --git a/openstackclient/tests/identity/v3/__init__.py b/openstackclient/tests/unit/identity/v2_0/__init__.py similarity index 100% rename from openstackclient/tests/identity/v3/__init__.py rename to openstackclient/tests/unit/identity/v2_0/__init__.py diff --git a/openstackclient/tests/identity/v2_0/fakes.py b/openstackclient/tests/unit/identity/v2_0/fakes.py similarity index 99% rename from openstackclient/tests/identity/v2_0/fakes.py rename to openstackclient/tests/unit/identity/v2_0/fakes.py index 3e0b999364..3d25cadfa6 100644 --- a/openstackclient/tests/identity/v2_0/fakes.py +++ b/openstackclient/tests/unit/identity/v2_0/fakes.py @@ -20,8 +20,8 @@ from keystoneauth1 import access from keystoneauth1 import fixture -from openstackclient.tests import fakes -from openstackclient.tests import utils +from openstackclient.tests.unit import fakes +from openstackclient.tests.unit import utils project_id = '8-9-64' diff --git a/openstackclient/tests/identity/v2_0/test_catalog.py b/openstackclient/tests/unit/identity/v2_0/test_catalog.py similarity index 98% rename from openstackclient/tests/identity/v2_0/test_catalog.py rename to openstackclient/tests/unit/identity/v2_0/test_catalog.py index 487d8f3161..c32f9fb856 100644 --- a/openstackclient/tests/identity/v2_0/test_catalog.py +++ b/openstackclient/tests/unit/identity/v2_0/test_catalog.py @@ -14,8 +14,8 @@ import mock from openstackclient.identity.v2_0 import catalog -from openstackclient.tests.identity.v2_0 import fakes as identity_fakes -from openstackclient.tests import utils +from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes +from openstackclient.tests.unit import utils class TestCatalog(utils.TestCommand): diff --git a/openstackclient/tests/identity/v2_0/test_endpoint.py b/openstackclient/tests/unit/identity/v2_0/test_endpoint.py similarity index 99% rename from openstackclient/tests/identity/v2_0/test_endpoint.py rename to openstackclient/tests/unit/identity/v2_0/test_endpoint.py index 26ec654d8b..915e04a500 100644 --- a/openstackclient/tests/identity/v2_0/test_endpoint.py +++ b/openstackclient/tests/unit/identity/v2_0/test_endpoint.py @@ -12,7 +12,7 @@ # from openstackclient.identity.v2_0 import endpoint -from openstackclient.tests.identity.v2_0 import fakes as identity_fakes +from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes class TestEndpoint(identity_fakes.TestIdentityv2): diff --git a/openstackclient/tests/identity/v2_0/test_project.py b/openstackclient/tests/unit/identity/v2_0/test_project.py similarity index 99% rename from openstackclient/tests/identity/v2_0/test_project.py rename to openstackclient/tests/unit/identity/v2_0/test_project.py index 96731c0c3e..c1f00762e4 100644 --- a/openstackclient/tests/identity/v2_0/test_project.py +++ b/openstackclient/tests/unit/identity/v2_0/test_project.py @@ -17,7 +17,7 @@ from osc_lib import exceptions from openstackclient.identity.v2_0 import project -from openstackclient.tests.identity.v2_0 import fakes as identity_fakes +from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes class TestProject(identity_fakes.TestIdentityv2): diff --git a/openstackclient/tests/identity/v2_0/test_role.py b/openstackclient/tests/unit/identity/v2_0/test_role.py similarity index 99% rename from openstackclient/tests/identity/v2_0/test_role.py rename to openstackclient/tests/unit/identity/v2_0/test_role.py index 3d379356ef..68ebf1418a 100644 --- a/openstackclient/tests/identity/v2_0/test_role.py +++ b/openstackclient/tests/unit/identity/v2_0/test_role.py @@ -19,7 +19,7 @@ from osc_lib import exceptions from openstackclient.identity.v2_0 import role -from openstackclient.tests.identity.v2_0 import fakes as identity_fakes +from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes class TestRole(identity_fakes.TestIdentityv2): diff --git a/openstackclient/tests/identity/v2_0/test_role_assignment.py b/openstackclient/tests/unit/identity/v2_0/test_role_assignment.py similarity index 98% rename from openstackclient/tests/identity/v2_0/test_role_assignment.py rename to openstackclient/tests/unit/identity/v2_0/test_role_assignment.py index a356ae0ad5..2730695936 100644 --- a/openstackclient/tests/identity/v2_0/test_role_assignment.py +++ b/openstackclient/tests/unit/identity/v2_0/test_role_assignment.py @@ -17,8 +17,8 @@ from osc_lib import exceptions from openstackclient.identity.v2_0 import role_assignment -from openstackclient.tests import fakes -from openstackclient.tests.identity.v2_0 import fakes as identity_fakes +from openstackclient.tests.unit import fakes +from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes class TestRoleAssignment(identity_fakes.TestIdentityv2): diff --git a/openstackclient/tests/identity/v2_0/test_service.py b/openstackclient/tests/unit/identity/v2_0/test_service.py similarity index 99% rename from openstackclient/tests/identity/v2_0/test_service.py rename to openstackclient/tests/unit/identity/v2_0/test_service.py index 7efd2a6047..1948bf4ab0 100644 --- a/openstackclient/tests/identity/v2_0/test_service.py +++ b/openstackclient/tests/unit/identity/v2_0/test_service.py @@ -17,7 +17,7 @@ from osc_lib import exceptions from openstackclient.identity.v2_0 import service -from openstackclient.tests.identity.v2_0 import fakes as identity_fakes +from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes class TestService(identity_fakes.TestIdentityv2): diff --git a/openstackclient/tests/identity/v2_0/test_token.py b/openstackclient/tests/unit/identity/v2_0/test_token.py similarity index 97% rename from openstackclient/tests/identity/v2_0/test_token.py rename to openstackclient/tests/unit/identity/v2_0/test_token.py index 17115e6b37..dd7f4f4ab4 100644 --- a/openstackclient/tests/identity/v2_0/test_token.py +++ b/openstackclient/tests/unit/identity/v2_0/test_token.py @@ -16,7 +16,7 @@ import mock from openstackclient.identity.v2_0 import token -from openstackclient.tests.identity.v2_0 import fakes as identity_fakes +from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes class TestToken(identity_fakes.TestIdentityv2): diff --git a/openstackclient/tests/identity/v2_0/test_user.py b/openstackclient/tests/unit/identity/v2_0/test_user.py similarity index 99% rename from openstackclient/tests/identity/v2_0/test_user.py rename to openstackclient/tests/unit/identity/v2_0/test_user.py index ba87124755..765f8559a8 100644 --- a/openstackclient/tests/identity/v2_0/test_user.py +++ b/openstackclient/tests/unit/identity/v2_0/test_user.py @@ -19,7 +19,7 @@ from osc_lib import exceptions from openstackclient.identity.v2_0 import user -from openstackclient.tests.identity.v2_0 import fakes as identity_fakes +from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes class TestUser(identity_fakes.TestIdentityv2): diff --git a/openstackclient/tests/image/__init__.py b/openstackclient/tests/unit/identity/v3/__init__.py similarity index 100% rename from openstackclient/tests/image/__init__.py rename to openstackclient/tests/unit/identity/v3/__init__.py diff --git a/openstackclient/tests/identity/v3/fakes.py b/openstackclient/tests/unit/identity/v3/fakes.py similarity index 99% rename from openstackclient/tests/identity/v3/fakes.py rename to openstackclient/tests/unit/identity/v3/fakes.py index 38f6724e85..7b76fa6087 100644 --- a/openstackclient/tests/identity/v3/fakes.py +++ b/openstackclient/tests/unit/identity/v3/fakes.py @@ -20,8 +20,8 @@ from keystoneauth1 import access from keystoneauth1 import fixture -from openstackclient.tests import fakes -from openstackclient.tests import utils +from openstackclient.tests.unit import fakes +from openstackclient.tests.unit import utils base_url = 'http://identity:5000/v3/' diff --git a/openstackclient/tests/identity/v3/test_catalog.py b/openstackclient/tests/unit/identity/v3/test_catalog.py similarity index 97% rename from openstackclient/tests/identity/v3/test_catalog.py rename to openstackclient/tests/unit/identity/v3/test_catalog.py index e3c5ed3d88..986c05f3ab 100644 --- a/openstackclient/tests/identity/v3/test_catalog.py +++ b/openstackclient/tests/unit/identity/v3/test_catalog.py @@ -14,8 +14,8 @@ import mock from openstackclient.identity.v3 import catalog -from openstackclient.tests.identity.v3 import fakes as identity_fakes -from openstackclient.tests import utils +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes +from openstackclient.tests.unit import utils class TestCatalog(utils.TestCommand): diff --git a/openstackclient/tests/identity/v3/test_consumer.py b/openstackclient/tests/unit/identity/v3/test_consumer.py similarity index 98% rename from openstackclient/tests/identity/v3/test_consumer.py rename to openstackclient/tests/unit/identity/v3/test_consumer.py index d90c7347cf..403250ef56 100644 --- a/openstackclient/tests/identity/v3/test_consumer.py +++ b/openstackclient/tests/unit/identity/v3/test_consumer.py @@ -13,8 +13,8 @@ import copy from openstackclient.identity.v3 import consumer -from openstackclient.tests import fakes -from openstackclient.tests.identity.v3 import fakes as identity_fakes +from openstackclient.tests.unit import fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes class TestOAuth1(identity_fakes.TestOAuth1): diff --git a/openstackclient/tests/identity/v3/test_credential.py b/openstackclient/tests/unit/identity/v3/test_credential.py similarity index 98% rename from openstackclient/tests/identity/v3/test_credential.py rename to openstackclient/tests/unit/identity/v3/test_credential.py index b272087d10..fd3ae6b262 100644 --- a/openstackclient/tests/identity/v3/test_credential.py +++ b/openstackclient/tests/unit/identity/v3/test_credential.py @@ -16,8 +16,8 @@ from osc_lib import exceptions from openstackclient.identity.v3 import credential -from openstackclient.tests.identity.v3 import fakes as identity_fakes -from openstackclient.tests import utils +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes +from openstackclient.tests.unit import utils class TestCredential(identity_fakes.TestIdentityv3): diff --git a/openstackclient/tests/identity/v3/test_domain.py b/openstackclient/tests/unit/identity/v3/test_domain.py similarity index 99% rename from openstackclient/tests/identity/v3/test_domain.py rename to openstackclient/tests/unit/identity/v3/test_domain.py index 5e09402172..36f13d3326 100644 --- a/openstackclient/tests/identity/v3/test_domain.py +++ b/openstackclient/tests/unit/identity/v3/test_domain.py @@ -11,7 +11,7 @@ # under the License. from openstackclient.identity.v3 import domain -from openstackclient.tests.identity.v3 import fakes as identity_fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes class TestDomain(identity_fakes.TestIdentityv3): diff --git a/openstackclient/tests/identity/v3/test_endpoint.py b/openstackclient/tests/unit/identity/v3/test_endpoint.py similarity index 99% rename from openstackclient/tests/identity/v3/test_endpoint.py rename to openstackclient/tests/unit/identity/v3/test_endpoint.py index b2463a0d5e..765fbedde1 100644 --- a/openstackclient/tests/identity/v3/test_endpoint.py +++ b/openstackclient/tests/unit/identity/v3/test_endpoint.py @@ -11,7 +11,7 @@ # under the License. from openstackclient.identity.v3 import endpoint -from openstackclient.tests.identity.v3 import fakes as identity_fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes class TestEndpoint(identity_fakes.TestIdentityv3): diff --git a/openstackclient/tests/identity/v3/test_group.py b/openstackclient/tests/unit/identity/v3/test_group.py similarity index 99% rename from openstackclient/tests/identity/v3/test_group.py rename to openstackclient/tests/unit/identity/v3/test_group.py index a678dee9af..d35e98c691 100644 --- a/openstackclient/tests/identity/v3/test_group.py +++ b/openstackclient/tests/unit/identity/v3/test_group.py @@ -18,7 +18,7 @@ from osc_lib import exceptions from openstackclient.identity.v3 import group -from openstackclient.tests.identity.v3 import fakes as identity_fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes class TestGroup(identity_fakes.TestIdentityv3): diff --git a/openstackclient/tests/identity/v3/test_identity_provider.py b/openstackclient/tests/unit/identity/v3/test_identity_provider.py similarity index 99% rename from openstackclient/tests/identity/v3/test_identity_provider.py rename to openstackclient/tests/unit/identity/v3/test_identity_provider.py index d86ac11e3f..cb672a92a6 100644 --- a/openstackclient/tests/identity/v3/test_identity_provider.py +++ b/openstackclient/tests/unit/identity/v3/test_identity_provider.py @@ -16,8 +16,8 @@ import mock from openstackclient.identity.v3 import identity_provider -from openstackclient.tests import fakes -from openstackclient.tests.identity.v3 import fakes as identity_fakes +from openstackclient.tests.unit import fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes class TestIdentityProvider(identity_fakes.TestFederatedIdentity): diff --git a/openstackclient/tests/identity/v3/test_mappings.py b/openstackclient/tests/unit/identity/v3/test_mappings.py similarity index 98% rename from openstackclient/tests/identity/v3/test_mappings.py rename to openstackclient/tests/unit/identity/v3/test_mappings.py index 09a383ebda..5086724c24 100644 --- a/openstackclient/tests/identity/v3/test_mappings.py +++ b/openstackclient/tests/unit/identity/v3/test_mappings.py @@ -18,8 +18,8 @@ from osc_lib import exceptions from openstackclient.identity.v3 import mapping -from openstackclient.tests import fakes -from openstackclient.tests.identity.v3 import fakes as identity_fakes +from openstackclient.tests.unit import fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes class TestMapping(identity_fakes.TestFederatedIdentity): diff --git a/openstackclient/tests/identity/v3/test_oauth.py b/openstackclient/tests/unit/identity/v3/test_oauth.py similarity index 98% rename from openstackclient/tests/identity/v3/test_oauth.py rename to openstackclient/tests/unit/identity/v3/test_oauth.py index d3cf36553a..3aabd9b8ae 100644 --- a/openstackclient/tests/identity/v3/test_oauth.py +++ b/openstackclient/tests/unit/identity/v3/test_oauth.py @@ -13,8 +13,8 @@ import copy from openstackclient.identity.v3 import token -from openstackclient.tests import fakes -from openstackclient.tests.identity.v3 import fakes as identity_fakes +from openstackclient.tests.unit import fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes class TestOAuth1(identity_fakes.TestOAuth1): diff --git a/openstackclient/tests/identity/v3/test_project.py b/openstackclient/tests/unit/identity/v3/test_project.py similarity index 99% rename from openstackclient/tests/identity/v3/test_project.py rename to openstackclient/tests/unit/identity/v3/test_project.py index 65874baae4..702d920975 100644 --- a/openstackclient/tests/identity/v3/test_project.py +++ b/openstackclient/tests/unit/identity/v3/test_project.py @@ -18,7 +18,7 @@ from osc_lib import exceptions from openstackclient.identity.v3 import project -from openstackclient.tests.identity.v3 import fakes as identity_fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes class TestProject(identity_fakes.TestIdentityv3): diff --git a/openstackclient/tests/identity/v3/test_protocol.py b/openstackclient/tests/unit/identity/v3/test_protocol.py similarity index 98% rename from openstackclient/tests/identity/v3/test_protocol.py rename to openstackclient/tests/unit/identity/v3/test_protocol.py index f718b27bd3..30b4aa4a11 100644 --- a/openstackclient/tests/identity/v3/test_protocol.py +++ b/openstackclient/tests/unit/identity/v3/test_protocol.py @@ -15,8 +15,8 @@ import copy from openstackclient.identity.v3 import federation_protocol -from openstackclient.tests import fakes -from openstackclient.tests.identity.v3 import fakes as identity_fakes +from openstackclient.tests.unit import fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes class TestProtocol(identity_fakes.TestFederatedIdentity): diff --git a/openstackclient/tests/identity/v3/test_region.py b/openstackclient/tests/unit/identity/v3/test_region.py similarity index 98% rename from openstackclient/tests/identity/v3/test_region.py rename to openstackclient/tests/unit/identity/v3/test_region.py index 41ee5ce9a6..e83a4e9f02 100644 --- a/openstackclient/tests/identity/v3/test_region.py +++ b/openstackclient/tests/unit/identity/v3/test_region.py @@ -14,8 +14,8 @@ import copy from openstackclient.identity.v3 import region -from openstackclient.tests import fakes -from openstackclient.tests.identity.v3 import fakes as identity_fakes +from openstackclient.tests.unit import fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes class TestRegion(identity_fakes.TestIdentityv3): diff --git a/openstackclient/tests/identity/v3/test_role.py b/openstackclient/tests/unit/identity/v3/test_role.py similarity index 99% rename from openstackclient/tests/identity/v3/test_role.py rename to openstackclient/tests/unit/identity/v3/test_role.py index b4e76d9649..448e18d372 100644 --- a/openstackclient/tests/identity/v3/test_role.py +++ b/openstackclient/tests/unit/identity/v3/test_role.py @@ -16,8 +16,8 @@ import copy from openstackclient.identity.v3 import role -from openstackclient.tests import fakes -from openstackclient.tests.identity.v3 import fakes as identity_fakes +from openstackclient.tests.unit import fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes class TestRole(identity_fakes.TestIdentityv3): diff --git a/openstackclient/tests/identity/v3/test_role_assignment.py b/openstackclient/tests/unit/identity/v3/test_role_assignment.py similarity index 99% rename from openstackclient/tests/identity/v3/test_role_assignment.py rename to openstackclient/tests/unit/identity/v3/test_role_assignment.py index 113cc493ec..7102a0cdae 100644 --- a/openstackclient/tests/identity/v3/test_role_assignment.py +++ b/openstackclient/tests/unit/identity/v3/test_role_assignment.py @@ -15,8 +15,8 @@ import mock from openstackclient.identity.v3 import role_assignment -from openstackclient.tests import fakes -from openstackclient.tests.identity.v3 import fakes as identity_fakes +from openstackclient.tests.unit import fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes class TestRoleAssignment(identity_fakes.TestIdentityv3): diff --git a/openstackclient/tests/identity/v3/test_service.py b/openstackclient/tests/unit/identity/v3/test_service.py similarity index 99% rename from openstackclient/tests/identity/v3/test_service.py rename to openstackclient/tests/unit/identity/v3/test_service.py index 76c66aa5e1..4cba445bfc 100644 --- a/openstackclient/tests/identity/v3/test_service.py +++ b/openstackclient/tests/unit/identity/v3/test_service.py @@ -17,7 +17,7 @@ from osc_lib import exceptions from openstackclient.identity.v3 import service -from openstackclient.tests.identity.v3 import fakes as identity_fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes class TestService(identity_fakes.TestIdentityv3): diff --git a/openstackclient/tests/identity/v3/test_service_provider.py b/openstackclient/tests/unit/identity/v3/test_service_provider.py similarity index 99% rename from openstackclient/tests/identity/v3/test_service_provider.py rename to openstackclient/tests/unit/identity/v3/test_service_provider.py index 873ab1e759..57473ef956 100644 --- a/openstackclient/tests/identity/v3/test_service_provider.py +++ b/openstackclient/tests/unit/identity/v3/test_service_provider.py @@ -15,8 +15,8 @@ import copy from openstackclient.identity.v3 import service_provider -from openstackclient.tests import fakes -from openstackclient.tests.identity.v3 import fakes as service_fakes +from openstackclient.tests.unit import fakes +from openstackclient.tests.unit.identity.v3 import fakes as service_fakes class TestServiceProvider(service_fakes.TestFederatedIdentity): diff --git a/openstackclient/tests/identity/v3/test_token.py b/openstackclient/tests/unit/identity/v3/test_token.py similarity index 98% rename from openstackclient/tests/identity/v3/test_token.py rename to openstackclient/tests/unit/identity/v3/test_token.py index d7225e6c3b..7321909f47 100644 --- a/openstackclient/tests/identity/v3/test_token.py +++ b/openstackclient/tests/unit/identity/v3/test_token.py @@ -16,7 +16,7 @@ import mock from openstackclient.identity.v3 import token -from openstackclient.tests.identity.v3 import fakes as identity_fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes class TestToken(identity_fakes.TestIdentityv3): diff --git a/openstackclient/tests/identity/v3/test_trust.py b/openstackclient/tests/unit/identity/v3/test_trust.py similarity index 98% rename from openstackclient/tests/identity/v3/test_trust.py rename to openstackclient/tests/unit/identity/v3/test_trust.py index 1ea2feb444..4eeb8bfe17 100644 --- a/openstackclient/tests/identity/v3/test_trust.py +++ b/openstackclient/tests/unit/identity/v3/test_trust.py @@ -14,8 +14,8 @@ import copy from openstackclient.identity.v3 import trust -from openstackclient.tests import fakes -from openstackclient.tests.identity.v3 import fakes as identity_fakes +from openstackclient.tests.unit import fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes class TestTrust(identity_fakes.TestIdentityv3): diff --git a/openstackclient/tests/identity/v3/test_unscoped_saml.py b/openstackclient/tests/unit/identity/v3/test_unscoped_saml.py similarity index 97% rename from openstackclient/tests/identity/v3/test_unscoped_saml.py rename to openstackclient/tests/unit/identity/v3/test_unscoped_saml.py index 62623902b1..9e4e1876ae 100644 --- a/openstackclient/tests/identity/v3/test_unscoped_saml.py +++ b/openstackclient/tests/unit/identity/v3/test_unscoped_saml.py @@ -15,8 +15,8 @@ from osc_lib import exceptions from openstackclient.identity.v3 import unscoped_saml -from openstackclient.tests import fakes -from openstackclient.tests.identity.v3 import fakes as identity_fakes +from openstackclient.tests.unit import fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes class TestUnscopedSAML(identity_fakes.TestFederatedIdentity): diff --git a/openstackclient/tests/identity/v3/test_user.py b/openstackclient/tests/unit/identity/v3/test_user.py similarity index 99% rename from openstackclient/tests/identity/v3/test_user.py rename to openstackclient/tests/unit/identity/v3/test_user.py index c3d9e749df..6150a5f3df 100644 --- a/openstackclient/tests/identity/v3/test_user.py +++ b/openstackclient/tests/unit/identity/v3/test_user.py @@ -17,7 +17,7 @@ import mock from openstackclient.identity.v3 import user -from openstackclient.tests.identity.v3 import fakes as identity_fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes class TestUser(identity_fakes.TestIdentityv3): diff --git a/openstackclient/tests/image/v1/__init__.py b/openstackclient/tests/unit/image/__init__.py similarity index 100% rename from openstackclient/tests/image/v1/__init__.py rename to openstackclient/tests/unit/image/__init__.py diff --git a/openstackclient/tests/image/v2/__init__.py b/openstackclient/tests/unit/image/v1/__init__.py similarity index 100% rename from openstackclient/tests/image/v2/__init__.py rename to openstackclient/tests/unit/image/v1/__init__.py diff --git a/openstackclient/tests/image/v1/fakes.py b/openstackclient/tests/unit/image/v1/fakes.py similarity index 92% rename from openstackclient/tests/image/v1/fakes.py rename to openstackclient/tests/unit/image/v1/fakes.py index 1e49f17325..a8e52fa32c 100644 --- a/openstackclient/tests/image/v1/fakes.py +++ b/openstackclient/tests/unit/image/v1/fakes.py @@ -15,9 +15,9 @@ import mock -from openstackclient.tests import fakes -from openstackclient.tests import utils -from openstackclient.tests.volume.v1 import fakes as volume_fakes +from openstackclient.tests.unit import fakes +from openstackclient.tests.unit import utils +from openstackclient.tests.unit.volume.v1 import fakes as volume_fakes image_id = 'im1' diff --git a/openstackclient/tests/image/v1/test_image.py b/openstackclient/tests/unit/image/v1/test_image.py similarity index 99% rename from openstackclient/tests/image/v1/test_image.py rename to openstackclient/tests/unit/image/v1/test_image.py index cf08d138fa..c62c1ff92d 100644 --- a/openstackclient/tests/image/v1/test_image.py +++ b/openstackclient/tests/unit/image/v1/test_image.py @@ -19,8 +19,8 @@ from osc_lib import exceptions from openstackclient.image.v1 import image -from openstackclient.tests import fakes -from openstackclient.tests.image.v1 import fakes as image_fakes +from openstackclient.tests.unit import fakes +from openstackclient.tests.unit.image.v1 import fakes as image_fakes class TestImage(image_fakes.TestImagev1): diff --git a/openstackclient/tests/integ/__init__.py b/openstackclient/tests/unit/image/v2/__init__.py similarity index 100% rename from openstackclient/tests/integ/__init__.py rename to openstackclient/tests/unit/image/v2/__init__.py diff --git a/openstackclient/tests/image/v2/fakes.py b/openstackclient/tests/unit/image/v2/fakes.py similarity index 98% rename from openstackclient/tests/image/v2/fakes.py rename to openstackclient/tests/unit/image/v2/fakes.py index d450dec17d..4d9f645833 100644 --- a/openstackclient/tests/image/v2/fakes.py +++ b/openstackclient/tests/unit/image/v2/fakes.py @@ -22,9 +22,9 @@ from osc_lib import utils as common_utils import warlock -from openstackclient.tests import fakes -from openstackclient.tests.identity.v3 import fakes as identity_fakes -from openstackclient.tests import utils +from openstackclient.tests.unit import fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes +from openstackclient.tests.unit import utils image_id = '0f41529e-7c12-4de8-be2d-181abb825b3c' image_name = 'graven' diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py similarity index 99% rename from openstackclient/tests/image/v2/test_image.py rename to openstackclient/tests/unit/image/v2/test_image.py index 830590c96a..ebc9c3a759 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -22,8 +22,8 @@ import warlock from openstackclient.image.v2 import image -from openstackclient.tests.identity.v3 import fakes as identity_fakes -from openstackclient.tests.image.v2 import fakes as image_fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes +from openstackclient.tests.unit.image.v2 import fakes as image_fakes class TestImage(image_fakes.TestImagev2): diff --git a/openstackclient/tests/integ/cli/__init__.py b/openstackclient/tests/unit/integ/__init__.py similarity index 100% rename from openstackclient/tests/integ/cli/__init__.py rename to openstackclient/tests/unit/integ/__init__.py diff --git a/openstackclient/tests/integ/base.py b/openstackclient/tests/unit/integ/base.py similarity index 97% rename from openstackclient/tests/integ/base.py rename to openstackclient/tests/unit/integ/base.py index 9ee8489337..caed4f8902 100644 --- a/openstackclient/tests/integ/base.py +++ b/openstackclient/tests/unit/integ/base.py @@ -13,8 +13,8 @@ from keystoneauth1 import fixture as ksa_fixture from requests_mock.contrib import fixture -from openstackclient.tests import test_shell -from openstackclient.tests import utils +from openstackclient.tests.unit import test_shell +from openstackclient.tests.unit import utils HOST = "192.168.5.41" diff --git a/openstackclient/tests/network/__init__.py b/openstackclient/tests/unit/integ/cli/__init__.py similarity index 100% rename from openstackclient/tests/network/__init__.py rename to openstackclient/tests/unit/integ/cli/__init__.py diff --git a/openstackclient/tests/integ/cli/test_project.py b/openstackclient/tests/unit/integ/cli/test_project.py similarity index 98% rename from openstackclient/tests/integ/cli/test_project.py rename to openstackclient/tests/unit/integ/cli/test_project.py index 16d5f71713..6a7c6d1bd6 100644 --- a/openstackclient/tests/integ/cli/test_project.py +++ b/openstackclient/tests/unit/integ/cli/test_project.py @@ -15,8 +15,8 @@ from osc_lib.tests import utils as osc_lib_utils from openstackclient import shell -from openstackclient.tests.integ import base as test_base -from openstackclient.tests import test_shell +from openstackclient.tests.unit.integ import base as test_base +from openstackclient.tests.unit import test_shell class TestIntegV2ProjectID(test_base.TestInteg): diff --git a/openstackclient/tests/integ/cli/test_shell.py b/openstackclient/tests/unit/integ/cli/test_shell.py similarity index 99% rename from openstackclient/tests/integ/cli/test_shell.py rename to openstackclient/tests/unit/integ/cli/test_shell.py index dad58f8ffb..9d819ed2e4 100644 --- a/openstackclient/tests/integ/cli/test_shell.py +++ b/openstackclient/tests/unit/integ/cli/test_shell.py @@ -16,8 +16,8 @@ from osc_lib.tests import utils as osc_lib_utils from openstackclient import shell -from openstackclient.tests.integ import base as test_base -from openstackclient.tests import test_shell +from openstackclient.tests.unit.integ import base as test_base +from openstackclient.tests.unit import test_shell class TestIntegShellCliV2(test_base.TestInteg): diff --git a/openstackclient/tests/network/v2/__init__.py b/openstackclient/tests/unit/network/__init__.py similarity index 100% rename from openstackclient/tests/network/v2/__init__.py rename to openstackclient/tests/unit/network/__init__.py diff --git a/openstackclient/tests/network/test_common.py b/openstackclient/tests/unit/network/test_common.py similarity index 99% rename from openstackclient/tests/network/test_common.py rename to openstackclient/tests/unit/network/test_common.py index 48608734a2..325aad2a86 100644 --- a/openstackclient/tests/network/test_common.py +++ b/openstackclient/tests/unit/network/test_common.py @@ -15,7 +15,7 @@ import mock from openstackclient.network import common -from openstackclient.tests import utils +from openstackclient.tests.unit import utils def _add_common_argument(parser): diff --git a/openstackclient/tests/object/__init__.py b/openstackclient/tests/unit/network/v2/__init__.py similarity index 100% rename from openstackclient/tests/object/__init__.py rename to openstackclient/tests/unit/network/v2/__init__.py diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py similarity index 99% rename from openstackclient/tests/network/v2/fakes.py rename to openstackclient/tests/unit/network/v2/fakes.py index 33bc401798..ed30bad343 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -16,9 +16,9 @@ import mock import uuid -from openstackclient.tests import fakes -from openstackclient.tests.identity.v3 import fakes as identity_fakes_v3 -from openstackclient.tests import utils +from openstackclient.tests.unit import fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3 +from openstackclient.tests.unit import utils QUOTA = { diff --git a/openstackclient/tests/network/v2/test_address_scope.py b/openstackclient/tests/unit/network/v2/test_address_scope.py similarity index 98% rename from openstackclient/tests/network/v2/test_address_scope.py rename to openstackclient/tests/unit/network/v2/test_address_scope.py index 342cf49b11..6d3f40110d 100644 --- a/openstackclient/tests/network/v2/test_address_scope.py +++ b/openstackclient/tests/unit/network/v2/test_address_scope.py @@ -17,9 +17,9 @@ from osc_lib import exceptions from openstackclient.network.v2 import address_scope -from openstackclient.tests.identity.v3 import fakes as identity_fakes_v3 -from openstackclient.tests.network.v2 import fakes as network_fakes -from openstackclient.tests import utils as tests_utils +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3 +from openstackclient.tests.unit.network.v2 import fakes as network_fakes +from openstackclient.tests.unit import utils as tests_utils class TestAddressScope(network_fakes.TestNetworkV2): diff --git a/openstackclient/tests/network/v2/test_floating_ip.py b/openstackclient/tests/unit/network/v2/test_floating_ip.py similarity index 98% rename from openstackclient/tests/network/v2/test_floating_ip.py rename to openstackclient/tests/unit/network/v2/test_floating_ip.py index 234fe4465a..a40e48f4ba 100644 --- a/openstackclient/tests/network/v2/test_floating_ip.py +++ b/openstackclient/tests/unit/network/v2/test_floating_ip.py @@ -17,9 +17,9 @@ from osc_lib import exceptions from openstackclient.network.v2 import floating_ip -from openstackclient.tests.compute.v2 import fakes as compute_fakes -from openstackclient.tests.network.v2 import fakes as network_fakes -from openstackclient.tests import utils as tests_utils +from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes +from openstackclient.tests.unit.network.v2 import fakes as network_fakes +from openstackclient.tests.unit import utils as tests_utils # Tests for Neutron network diff --git a/openstackclient/tests/network/v2/test_floating_ip_pool.py b/openstackclient/tests/unit/network/v2/test_floating_ip_pool.py similarity index 95% rename from openstackclient/tests/network/v2/test_floating_ip_pool.py rename to openstackclient/tests/unit/network/v2/test_floating_ip_pool.py index 22d20d202d..11d01d36e4 100644 --- a/openstackclient/tests/network/v2/test_floating_ip_pool.py +++ b/openstackclient/tests/unit/network/v2/test_floating_ip_pool.py @@ -14,8 +14,8 @@ from osc_lib import exceptions from openstackclient.network.v2 import floating_ip_pool -from openstackclient.tests.compute.v2 import fakes as compute_fakes -from openstackclient.tests.network.v2 import fakes as network_fakes +from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes +from openstackclient.tests.unit.network.v2 import fakes as network_fakes # Tests for Network API v2 diff --git a/openstackclient/tests/network/v2/test_ip_availability.py b/openstackclient/tests/unit/network/v2/test_ip_availability.py similarity index 96% rename from openstackclient/tests/network/v2/test_ip_availability.py rename to openstackclient/tests/unit/network/v2/test_ip_availability.py index f74bf8f72e..c929ab82c8 100644 --- a/openstackclient/tests/network/v2/test_ip_availability.py +++ b/openstackclient/tests/unit/network/v2/test_ip_availability.py @@ -16,9 +16,9 @@ from osc_lib import utils as common_utils from openstackclient.network.v2 import ip_availability -from openstackclient.tests.identity.v3 import fakes as identity_fakes -from openstackclient.tests.network.v2 import fakes as network_fakes -from openstackclient.tests import utils as tests_utils +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes +from openstackclient.tests.unit.network.v2 import fakes as network_fakes +from openstackclient.tests.unit import utils as tests_utils class TestIPAvailability(network_fakes.TestNetworkV2): diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/unit/network/v2/test_network.py similarity index 98% rename from openstackclient/tests/network/v2/test_network.py rename to openstackclient/tests/unit/network/v2/test_network.py index bb606819e0..84ead0938b 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/unit/network/v2/test_network.py @@ -18,12 +18,12 @@ from osc_lib import utils from openstackclient.network.v2 import network -from openstackclient.tests.compute.v2 import fakes as compute_fakes -from openstackclient.tests import fakes -from openstackclient.tests.identity.v2_0 import fakes as identity_fakes_v2 -from openstackclient.tests.identity.v3 import fakes as identity_fakes_v3 -from openstackclient.tests.network.v2 import fakes as network_fakes -from openstackclient.tests import utils as tests_utils +from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes +from openstackclient.tests.unit import fakes +from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes_v2 +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3 +from openstackclient.tests.unit.network.v2 import fakes as network_fakes +from openstackclient.tests.unit import utils as tests_utils # Tests for Neutron network diff --git a/openstackclient/tests/network/v2/test_network_agent.py b/openstackclient/tests/unit/network/v2/test_network_agent.py similarity index 98% rename from openstackclient/tests/network/v2/test_network_agent.py rename to openstackclient/tests/unit/network/v2/test_network_agent.py index 269d4e1d6d..2f17f41b04 100644 --- a/openstackclient/tests/network/v2/test_network_agent.py +++ b/openstackclient/tests/unit/network/v2/test_network_agent.py @@ -18,8 +18,8 @@ from osc_lib import utils from openstackclient.network.v2 import network_agent -from openstackclient.tests.network.v2 import fakes as network_fakes -from openstackclient.tests import utils as tests_utils +from openstackclient.tests.unit.network.v2 import fakes as network_fakes +from openstackclient.tests.unit import utils as tests_utils class TestNetworkAgent(network_fakes.TestNetworkV2): diff --git a/openstackclient/tests/network/v2/test_network_rbac.py b/openstackclient/tests/unit/network/v2/test_network_rbac.py similarity index 98% rename from openstackclient/tests/network/v2/test_network_rbac.py rename to openstackclient/tests/unit/network/v2/test_network_rbac.py index 9250e91b9e..1cd18a093f 100644 --- a/openstackclient/tests/network/v2/test_network_rbac.py +++ b/openstackclient/tests/unit/network/v2/test_network_rbac.py @@ -17,9 +17,9 @@ from osc_lib import exceptions from openstackclient.network.v2 import network_rbac -from openstackclient.tests.identity.v3 import fakes as identity_fakes_v3 -from openstackclient.tests.network.v2 import fakes as network_fakes -from openstackclient.tests import utils as tests_utils +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3 +from openstackclient.tests.unit.network.v2 import fakes as network_fakes +from openstackclient.tests.unit import utils as tests_utils class TestNetworkRBAC(network_fakes.TestNetworkV2): diff --git a/openstackclient/tests/network/v2/test_network_segment.py b/openstackclient/tests/unit/network/v2/test_network_segment.py similarity index 97% rename from openstackclient/tests/network/v2/test_network_segment.py rename to openstackclient/tests/unit/network/v2/test_network_segment.py index a635d84597..b9fce0784f 100644 --- a/openstackclient/tests/network/v2/test_network_segment.py +++ b/openstackclient/tests/unit/network/v2/test_network_segment.py @@ -16,8 +16,8 @@ from osc_lib import exceptions from openstackclient.network.v2 import network_segment -from openstackclient.tests.network.v2 import fakes as network_fakes -from openstackclient.tests import utils as tests_utils +from openstackclient.tests.unit.network.v2 import fakes as network_fakes +from openstackclient.tests.unit import utils as tests_utils class TestNetworkSegment(network_fakes.TestNetworkV2): diff --git a/openstackclient/tests/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py similarity index 99% rename from openstackclient/tests/network/v2/test_port.py rename to openstackclient/tests/unit/network/v2/test_port.py index a1cecec8e7..d5d7f3309b 100644 --- a/openstackclient/tests/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -19,8 +19,8 @@ from osc_lib import utils from openstackclient.network.v2 import port -from openstackclient.tests.network.v2 import fakes as network_fakes -from openstackclient.tests import utils as tests_utils +from openstackclient.tests.unit.network.v2 import fakes as network_fakes +from openstackclient.tests.unit import utils as tests_utils class TestPort(network_fakes.TestNetworkV2): diff --git a/openstackclient/tests/network/v2/test_router.py b/openstackclient/tests/unit/network/v2/test_router.py similarity index 99% rename from openstackclient/tests/network/v2/test_router.py rename to openstackclient/tests/unit/network/v2/test_router.py index 1ef4707b70..26fe655e30 100644 --- a/openstackclient/tests/network/v2/test_router.py +++ b/openstackclient/tests/unit/network/v2/test_router.py @@ -18,8 +18,8 @@ from osc_lib import utils as osc_utils from openstackclient.network.v2 import router -from openstackclient.tests.network.v2 import fakes as network_fakes -from openstackclient.tests import utils as tests_utils +from openstackclient.tests.unit.network.v2 import fakes as network_fakes +from openstackclient.tests.unit import utils as tests_utils class TestRouter(network_fakes.TestNetworkV2): diff --git a/openstackclient/tests/network/v2/test_security_group.py b/openstackclient/tests/unit/network/v2/test_security_group.py similarity index 98% rename from openstackclient/tests/network/v2/test_security_group.py rename to openstackclient/tests/unit/network/v2/test_security_group.py index 15f4cffe05..4c5b29721d 100644 --- a/openstackclient/tests/network/v2/test_security_group.py +++ b/openstackclient/tests/unit/network/v2/test_security_group.py @@ -17,10 +17,10 @@ from osc_lib import exceptions from openstackclient.network.v2 import security_group -from openstackclient.tests.compute.v2 import fakes as compute_fakes -from openstackclient.tests.identity.v3 import fakes as identity_fakes -from openstackclient.tests.network.v2 import fakes as network_fakes -from openstackclient.tests import utils as tests_utils +from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes +from openstackclient.tests.unit.network.v2 import fakes as network_fakes +from openstackclient.tests.unit import utils as tests_utils class TestSecurityGroupNetwork(network_fakes.TestNetworkV2): diff --git a/openstackclient/tests/network/v2/test_security_group_rule.py b/openstackclient/tests/unit/network/v2/test_security_group_rule.py similarity index 99% rename from openstackclient/tests/network/v2/test_security_group_rule.py rename to openstackclient/tests/unit/network/v2/test_security_group_rule.py index 170989bf28..51e18a6560 100644 --- a/openstackclient/tests/network/v2/test_security_group_rule.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_rule.py @@ -19,11 +19,11 @@ from openstackclient.network import utils as network_utils from openstackclient.network.v2 import security_group_rule -from openstackclient.tests.compute.v2 import fakes as compute_fakes -from openstackclient.tests import fakes -from openstackclient.tests.identity.v3 import fakes as identity_fakes -from openstackclient.tests.network.v2 import fakes as network_fakes -from openstackclient.tests import utils as tests_utils +from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes +from openstackclient.tests.unit import fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes +from openstackclient.tests.unit.network.v2 import fakes as network_fakes +from openstackclient.tests.unit import utils as tests_utils class TestSecurityGroupRuleNetwork(network_fakes.TestNetworkV2): diff --git a/openstackclient/tests/network/v2/test_subnet.py b/openstackclient/tests/unit/network/v2/test_subnet.py similarity index 99% rename from openstackclient/tests/network/v2/test_subnet.py rename to openstackclient/tests/unit/network/v2/test_subnet.py index c117c6fdca..e31db4693d 100644 --- a/openstackclient/tests/network/v2/test_subnet.py +++ b/openstackclient/tests/unit/network/v2/test_subnet.py @@ -18,9 +18,9 @@ from osc_lib import utils from openstackclient.network.v2 import subnet as subnet_v2 -from openstackclient.tests.identity.v3 import fakes as identity_fakes_v3 -from openstackclient.tests.network.v2 import fakes as network_fakes -from openstackclient.tests import utils as tests_utils +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3 +from openstackclient.tests.unit.network.v2 import fakes as network_fakes +from openstackclient.tests.unit import utils as tests_utils class TestSubnet(network_fakes.TestNetworkV2): diff --git a/openstackclient/tests/network/v2/test_subnet_pool.py b/openstackclient/tests/unit/network/v2/test_subnet_pool.py similarity index 99% rename from openstackclient/tests/network/v2/test_subnet_pool.py rename to openstackclient/tests/unit/network/v2/test_subnet_pool.py index 4cfecef7fb..0d9494c023 100644 --- a/openstackclient/tests/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/unit/network/v2/test_subnet_pool.py @@ -19,9 +19,9 @@ from osc_lib import utils from openstackclient.network.v2 import subnet_pool -from openstackclient.tests.identity.v3 import fakes as identity_fakes_v3 -from openstackclient.tests.network.v2 import fakes as network_fakes -from openstackclient.tests import utils as tests_utils +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3 +from openstackclient.tests.unit.network.v2 import fakes as network_fakes +from openstackclient.tests.unit import utils as tests_utils class TestSubnetPool(network_fakes.TestNetworkV2): diff --git a/openstackclient/tests/object/v1/__init__.py b/openstackclient/tests/unit/object/__init__.py similarity index 100% rename from openstackclient/tests/object/v1/__init__.py rename to openstackclient/tests/unit/object/__init__.py diff --git a/openstackclient/tests/volume/__init__.py b/openstackclient/tests/unit/object/v1/__init__.py similarity index 100% rename from openstackclient/tests/volume/__init__.py rename to openstackclient/tests/unit/object/v1/__init__.py diff --git a/openstackclient/tests/object/v1/fakes.py b/openstackclient/tests/unit/object/v1/fakes.py similarity index 98% rename from openstackclient/tests/object/v1/fakes.py rename to openstackclient/tests/unit/object/v1/fakes.py index 6c36711127..0ff594bce0 100644 --- a/openstackclient/tests/object/v1/fakes.py +++ b/openstackclient/tests/unit/object/v1/fakes.py @@ -16,7 +16,7 @@ from keystoneauth1 import session from openstackclient.api import object_store_v1 as object_store -from openstackclient.tests import utils +from openstackclient.tests.unit import utils ACCOUNT_ID = 'tqbfjotld' diff --git a/openstackclient/tests/object/v1/test_container.py b/openstackclient/tests/unit/object/v1/test_container.py similarity index 99% rename from openstackclient/tests/object/v1/test_container.py rename to openstackclient/tests/unit/object/v1/test_container.py index 41bc6e8c6d..37b8c705b7 100644 --- a/openstackclient/tests/object/v1/test_container.py +++ b/openstackclient/tests/unit/object/v1/test_container.py @@ -18,7 +18,7 @@ from openstackclient.api import object_store_v1 as object_store from openstackclient.object.v1 import container -from openstackclient.tests.object.v1 import fakes as object_fakes +from openstackclient.tests.unit.object.v1 import fakes as object_fakes AUTH_TOKEN = "foobar" diff --git a/openstackclient/tests/object/v1/test_container_all.py b/openstackclient/tests/unit/object/v1/test_container_all.py similarity index 99% rename from openstackclient/tests/object/v1/test_container_all.py rename to openstackclient/tests/unit/object/v1/test_container_all.py index 95e12f47f3..58c90e36d5 100644 --- a/openstackclient/tests/object/v1/test_container_all.py +++ b/openstackclient/tests/unit/object/v1/test_container_all.py @@ -16,7 +16,7 @@ from requests_mock.contrib import fixture from openstackclient.object.v1 import container as container_cmds -from openstackclient.tests.object.v1 import fakes as object_fakes +from openstackclient.tests.unit.object.v1 import fakes as object_fakes class TestContainerAll(object_fakes.TestObjectv1): diff --git a/openstackclient/tests/object/v1/test_object.py b/openstackclient/tests/unit/object/v1/test_object.py similarity index 99% rename from openstackclient/tests/object/v1/test_object.py rename to openstackclient/tests/unit/object/v1/test_object.py index e11f78a130..c0ac204dad 100644 --- a/openstackclient/tests/object/v1/test_object.py +++ b/openstackclient/tests/unit/object/v1/test_object.py @@ -18,7 +18,7 @@ from openstackclient.api import object_store_v1 as object_store from openstackclient.object.v1 import object as obj -from openstackclient.tests.object.v1 import fakes as object_fakes +from openstackclient.tests.unit.object.v1 import fakes as object_fakes AUTH_TOKEN = "foobar" diff --git a/openstackclient/tests/object/v1/test_object_all.py b/openstackclient/tests/unit/object/v1/test_object_all.py similarity index 98% rename from openstackclient/tests/object/v1/test_object_all.py rename to openstackclient/tests/unit/object/v1/test_object_all.py index 2a1bf05905..a0948b1bcf 100644 --- a/openstackclient/tests/object/v1/test_object_all.py +++ b/openstackclient/tests/unit/object/v1/test_object_all.py @@ -16,7 +16,7 @@ from requests_mock.contrib import fixture from openstackclient.object.v1 import object as object_cmds -from openstackclient.tests.object.v1 import fakes as object_fakes +from openstackclient.tests.unit.object.v1 import fakes as object_fakes class TestObjectAll(object_fakes.TestObjectv1): diff --git a/openstackclient/tests/test_shell.py b/openstackclient/tests/unit/test_shell.py similarity index 100% rename from openstackclient/tests/test_shell.py rename to openstackclient/tests/unit/test_shell.py diff --git a/openstackclient/tests/utils.py b/openstackclient/tests/unit/utils.py similarity index 98% rename from openstackclient/tests/utils.py rename to openstackclient/tests/unit/utils.py index f3ab5ea63b..3c5c8683f1 100644 --- a/openstackclient/tests/utils.py +++ b/openstackclient/tests/unit/utils.py @@ -18,7 +18,7 @@ import os import testtools -from openstackclient.tests import fakes +from openstackclient.tests.unit import fakes class ParserException(Exception): diff --git a/openstackclient/tests/volume/v1/__init__.py b/openstackclient/tests/unit/volume/__init__.py similarity index 100% rename from openstackclient/tests/volume/v1/__init__.py rename to openstackclient/tests/unit/volume/__init__.py diff --git a/openstackclient/tests/volume/test_find_resource.py b/openstackclient/tests/unit/volume/test_find_resource.py similarity index 97% rename from openstackclient/tests/volume/test_find_resource.py rename to openstackclient/tests/unit/volume/test_find_resource.py index 982b02f03d..d25093158c 100644 --- a/openstackclient/tests/volume/test_find_resource.py +++ b/openstackclient/tests/unit/volume/test_find_resource.py @@ -20,7 +20,7 @@ from osc_lib import exceptions from osc_lib import utils -from openstackclient.tests import utils as test_utils +from openstackclient.tests.unit import utils as test_utils from openstackclient.volume import client # noqa diff --git a/openstackclient/tests/volume/v2/__init__.py b/openstackclient/tests/unit/volume/v1/__init__.py similarity index 100% rename from openstackclient/tests/volume/v2/__init__.py rename to openstackclient/tests/unit/volume/v1/__init__.py diff --git a/openstackclient/tests/volume/v1/fakes.py b/openstackclient/tests/unit/volume/v1/fakes.py similarity index 97% rename from openstackclient/tests/volume/v1/fakes.py rename to openstackclient/tests/unit/volume/v1/fakes.py index b96f925dce..c6fee7d196 100644 --- a/openstackclient/tests/volume/v1/fakes.py +++ b/openstackclient/tests/unit/volume/v1/fakes.py @@ -15,9 +15,9 @@ import mock -from openstackclient.tests import fakes -from openstackclient.tests.identity.v2_0 import fakes as identity_fakes -from openstackclient.tests import utils +from openstackclient.tests.unit import fakes +from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes +from openstackclient.tests.unit import utils volume_id = 'vvvvvvvv-vvvv-vvvv-vvvvvvvv' diff --git a/openstackclient/tests/volume/v1/test_qos_specs.py b/openstackclient/tests/unit/volume/v1/test_qos_specs.py similarity index 99% rename from openstackclient/tests/volume/v1/test_qos_specs.py rename to openstackclient/tests/unit/volume/v1/test_qos_specs.py index 4e1733fde5..7b87ccb3d1 100644 --- a/openstackclient/tests/volume/v1/test_qos_specs.py +++ b/openstackclient/tests/unit/volume/v1/test_qos_specs.py @@ -17,8 +17,8 @@ from osc_lib import utils -from openstackclient.tests import fakes -from openstackclient.tests.volume.v1 import fakes as volume_fakes +from openstackclient.tests.unit import fakes +from openstackclient.tests.unit.volume.v1 import fakes as volume_fakes from openstackclient.volume.v1 import qos_specs diff --git a/openstackclient/tests/unit/volume/v1/test_service.py b/openstackclient/tests/unit/volume/v1/test_service.py new file mode 100644 index 0000000000..82d21bfc42 --- /dev/null +++ b/openstackclient/tests/unit/volume/v1/test_service.py @@ -0,0 +1,286 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +from osc_lib import exceptions + +from openstackclient.tests.unit.volume.v1 import fakes as service_fakes +from openstackclient.volume.v1 import service + + +class TestService(service_fakes.TestVolumev1): + + def setUp(self): + super(TestService, self).setUp() + + # Get a shortcut to the ServiceManager Mock + self.service_mock = self.app.client_manager.volume.services + self.service_mock.reset_mock() + + +class TestServiceList(TestService): + + # The service to be listed + services = service_fakes.FakeService.create_one_service() + + def setUp(self): + super(TestServiceList, self).setUp() + + self.service_mock.list.return_value = [self.services] + + # Get the command object to test + self.cmd = service.ListService(self.app, None) + + def test_service_list(self): + arglist = [ + '--host', self.services.host, + '--service', self.services.binary, + ] + verifylist = [ + ('host', self.services.host), + ('service', self.services.binary), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + expected_columns = [ + 'Binary', + 'Host', + 'Zone', + 'Status', + 'State', + 'Updated At', + ] + + # confirming if all expected columns are present in the result. + self.assertEqual(expected_columns, columns) + + datalist = (( + self.services.binary, + self.services.host, + self.services.zone, + self.services.status, + self.services.state, + self.services.updated_at, + ), ) + + # confirming if all expected values are present in the result. + self.assertEqual(datalist, tuple(data)) + + # checking if proper call was made to list services + self.service_mock.list.assert_called_with( + self.services.host, + self.services.binary, + ) + + # checking if prohibited columns are present in output + self.assertNotIn("Disabled Reason", columns) + self.assertNotIn(self.services.disabled_reason, + tuple(data)) + + def test_service_list_with_long_option(self): + arglist = [ + '--host', self.services.host, + '--service', self.services.binary, + '--long' + ] + verifylist = [ + ('host', self.services.host), + ('service', self.services.binary), + ('long', True) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + expected_columns = [ + 'Binary', + 'Host', + 'Zone', + 'Status', + 'State', + 'Updated At', + 'Disabled Reason' + ] + + # confirming if all expected columns are present in the result. + self.assertEqual(expected_columns, columns) + + datalist = (( + self.services.binary, + self.services.host, + self.services.zone, + self.services.status, + self.services.state, + self.services.updated_at, + self.services.disabled_reason, + ), ) + + # confirming if all expected values are present in the result. + self.assertEqual(datalist, tuple(data)) + + self.service_mock.list.assert_called_with( + self.services.host, + self.services.binary, + ) + + +class TestServiceSet(TestService): + + service = service_fakes.FakeService.create_one_service() + + def setUp(self): + super(TestServiceSet, self).setUp() + + self.service_mock.enable.return_value = self.service + self.service_mock.disable.return_value = self.service + self.service_mock.disable_log_reason.return_value = self.service + + self.cmd = service.SetService(self.app, None) + + def test_service_set_nothing(self): + arglist = [ + self.service.host, + self.service.binary, + ] + verifylist = [ + ('host', self.service.host), + ('service', self.service.binary), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.service_mock.enable.assert_not_called() + self.service_mock.disable.assert_not_called() + self.service_mock.disable_log_reason.assert_not_called() + self.assertIsNone(result) + + def test_service_set_enable(self): + arglist = [ + '--enable', + self.service.host, + self.service.binary, + ] + verifylist = [ + ('enable', True), + ('host', self.service.host), + ('service', self.service.binary), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.service_mock.enable.assert_called_with( + self.service.host, + self.service.binary + ) + self.service_mock.disable.assert_not_called() + self.service_mock.disable_log_reason.assert_not_called() + self.assertIsNone(result) + + def test_service_set_disable(self): + arglist = [ + '--disable', + self.service.host, + self.service.binary, + ] + verifylist = [ + ('disable', True), + ('host', self.service.host), + ('service', self.service.binary), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.service_mock.disable.assert_called_with( + self.service.host, + self.service.binary + ) + self.service_mock.enable.assert_not_called() + self.service_mock.disable_log_reason.assert_not_called() + self.assertIsNone(result) + + def test_service_set_disable_with_reason(self): + reason = 'earthquake' + arglist = [ + '--disable', + '--disable-reason', reason, + self.service.host, + self.service.binary, + ] + verifylist = [ + ('disable', True), + ('disable_reason', reason), + ('host', self.service.host), + ('service', self.service.binary), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.service_mock.disable_log_reason.assert_called_with( + self.service.host, + self.service.binary, + reason + ) + self.assertIsNone(result) + + def test_service_set_only_with_disable_reason(self): + reason = 'earthquake' + arglist = [ + '--disable-reason', reason, + self.service.host, + self.service.binary, + ] + verifylist = [ + ('disable_reason', reason), + ('host', self.service.host), + ('service', self.service.binary), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + try: + self.cmd.take_action(parsed_args) + self.fail("CommandError should be raised.") + except exceptions.CommandError as e: + self.assertEqual("Cannot specify option --disable-reason without " + "--disable specified.", str(e)) + + def test_service_set_enable_with_disable_reason(self): + reason = 'earthquake' + arglist = [ + '--enable', + '--disable-reason', reason, + self.service.host, + self.service.binary, + ] + verifylist = [ + ('enable', True), + ('disable_reason', reason), + ('host', self.service.host), + ('service', self.service.binary), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + try: + self.cmd.take_action(parsed_args) + self.fail("CommandError should be raised.") + except exceptions.CommandError as e: + self.assertEqual("Cannot specify option --disable-reason without " + "--disable specified.", str(e)) diff --git a/openstackclient/tests/volume/v1/test_transfer_request.py b/openstackclient/tests/unit/volume/v1/test_transfer_request.py similarity index 98% rename from openstackclient/tests/volume/v1/test_transfer_request.py rename to openstackclient/tests/unit/volume/v1/test_transfer_request.py index 91f5f4b26a..f7980c34aa 100644 --- a/openstackclient/tests/volume/v1/test_transfer_request.py +++ b/openstackclient/tests/unit/volume/v1/test_transfer_request.py @@ -13,7 +13,7 @@ # -from openstackclient.tests.volume.v1 import fakes as transfer_fakes +from openstackclient.tests.unit.volume.v1 import fakes as transfer_fakes from openstackclient.volume.v1 import volume_transfer_request diff --git a/openstackclient/tests/volume/v1/test_volume.py b/openstackclient/tests/unit/volume/v1/test_volume.py similarity index 99% rename from openstackclient/tests/volume/v1/test_volume.py rename to openstackclient/tests/unit/volume/v1/test_volume.py index e41c2a7266..f90566fd5e 100644 --- a/openstackclient/tests/volume/v1/test_volume.py +++ b/openstackclient/tests/unit/volume/v1/test_volume.py @@ -16,9 +16,9 @@ import copy import mock -from openstackclient.tests import fakes -from openstackclient.tests.identity.v2_0 import fakes as identity_fakes -from openstackclient.tests.volume.v1 import fakes as volume_fakes +from openstackclient.tests.unit import fakes +from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes +from openstackclient.tests.unit.volume.v1 import fakes as volume_fakes from openstackclient.volume.v1 import volume diff --git a/openstackclient/tests/unit/volume/v2/__init__.py b/openstackclient/tests/unit/volume/v2/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/unit/volume/v2/fakes.py similarity index 98% rename from openstackclient/tests/volume/v2/fakes.py rename to openstackclient/tests/unit/volume/v2/fakes.py index 49384bd8ff..a958c46803 100644 --- a/openstackclient/tests/volume/v2/fakes.py +++ b/openstackclient/tests/unit/volume/v2/fakes.py @@ -19,10 +19,10 @@ from osc_lib import utils as common_utils -from openstackclient.tests import fakes -from openstackclient.tests.identity.v3 import fakes as identity_fakes -from openstackclient.tests.image.v2 import fakes as image_fakes -from openstackclient.tests import utils +from openstackclient.tests.unit import 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 import utils class FakeTransfer(object): diff --git a/openstackclient/tests/volume/v2/test_backup.py b/openstackclient/tests/unit/volume/v2/test_backup.py similarity index 99% rename from openstackclient/tests/volume/v2/test_backup.py rename to openstackclient/tests/unit/volume/v2/test_backup.py index 67064352b2..4563387016 100644 --- a/openstackclient/tests/volume/v2/test_backup.py +++ b/openstackclient/tests/unit/volume/v2/test_backup.py @@ -18,7 +18,7 @@ from osc_lib import exceptions from osc_lib import utils -from openstackclient.tests.volume.v2 import fakes as volume_fakes +from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes from openstackclient.volume.v2 import backup diff --git a/openstackclient/tests/volume/v2/test_qos_specs.py b/openstackclient/tests/unit/volume/v2/test_qos_specs.py similarity index 99% rename from openstackclient/tests/volume/v2/test_qos_specs.py rename to openstackclient/tests/unit/volume/v2/test_qos_specs.py index 56b8ae03bb..7597e85280 100644 --- a/openstackclient/tests/volume/v2/test_qos_specs.py +++ b/openstackclient/tests/unit/volume/v2/test_qos_specs.py @@ -19,7 +19,7 @@ from osc_lib import exceptions from osc_lib import utils -from openstackclient.tests.volume.v2 import fakes as volume_fakes +from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes from openstackclient.volume.v2 import qos_specs diff --git a/openstackclient/tests/volume/v2/test_service.py b/openstackclient/tests/unit/volume/v2/test_service.py similarity index 99% rename from openstackclient/tests/volume/v2/test_service.py rename to openstackclient/tests/unit/volume/v2/test_service.py index 5959a5e3ec..3e9b2df91d 100644 --- a/openstackclient/tests/volume/v2/test_service.py +++ b/openstackclient/tests/unit/volume/v2/test_service.py @@ -14,7 +14,7 @@ from osc_lib import exceptions -from openstackclient.tests.volume.v2 import fakes as service_fakes +from openstackclient.tests.unit.volume.v2 import fakes as service_fakes from openstackclient.volume.v2 import service diff --git a/openstackclient/tests/volume/v2/test_snapshot.py b/openstackclient/tests/unit/volume/v2/test_snapshot.py similarity index 99% rename from openstackclient/tests/volume/v2/test_snapshot.py rename to openstackclient/tests/unit/volume/v2/test_snapshot.py index 3eb740ba6c..333d8d7248 100644 --- a/openstackclient/tests/volume/v2/test_snapshot.py +++ b/openstackclient/tests/unit/volume/v2/test_snapshot.py @@ -19,7 +19,7 @@ from osc_lib import exceptions from osc_lib import utils -from openstackclient.tests.volume.v2 import fakes as volume_fakes +from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes from openstackclient.volume.v2 import snapshot diff --git a/openstackclient/tests/volume/v2/test_transfer_request.py b/openstackclient/tests/unit/volume/v2/test_transfer_request.py similarity index 98% rename from openstackclient/tests/volume/v2/test_transfer_request.py rename to openstackclient/tests/unit/volume/v2/test_transfer_request.py index ea39caa729..32108c025d 100644 --- a/openstackclient/tests/volume/v2/test_transfer_request.py +++ b/openstackclient/tests/unit/volume/v2/test_transfer_request.py @@ -13,7 +13,7 @@ # -from openstackclient.tests.volume.v2 import fakes as transfer_fakes +from openstackclient.tests.unit.volume.v2 import fakes as transfer_fakes from openstackclient.volume.v2 import volume_transfer_request diff --git a/openstackclient/tests/volume/v2/test_type.py b/openstackclient/tests/unit/volume/v2/test_type.py similarity index 98% rename from openstackclient/tests/volume/v2/test_type.py rename to openstackclient/tests/unit/volume/v2/test_type.py index b0316aefca..84f87e3b19 100644 --- a/openstackclient/tests/volume/v2/test_type.py +++ b/openstackclient/tests/unit/volume/v2/test_type.py @@ -17,9 +17,9 @@ from osc_lib import exceptions from osc_lib import utils -from openstackclient.tests.identity.v3 import fakes as identity_fakes -from openstackclient.tests import utils as tests_utils -from openstackclient.tests.volume.v2 import fakes as volume_fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes +from openstackclient.tests.unit import utils as tests_utils +from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes from openstackclient.volume.v2 import volume_type diff --git a/openstackclient/tests/volume/v2/test_volume.py b/openstackclient/tests/unit/volume/v2/test_volume.py similarity index 99% rename from openstackclient/tests/volume/v2/test_volume.py rename to openstackclient/tests/unit/volume/v2/test_volume.py index 74181a259f..66f8f74d42 100644 --- a/openstackclient/tests/volume/v2/test_volume.py +++ b/openstackclient/tests/unit/volume/v2/test_volume.py @@ -18,9 +18,9 @@ from osc_lib import exceptions from osc_lib import utils -from openstackclient.tests.identity.v3 import fakes as identity_fakes -from openstackclient.tests.image.v2 import fakes as image_fakes -from openstackclient.tests.volume.v2 import fakes as volume_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.volume.v2 import fakes as volume_fakes from openstackclient.volume.v2 import volume From c14d3efe6162a58cb3cdcb2834ad2508e2525018 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Mon, 5 Sep 2016 20:35:06 -0700 Subject: [PATCH 1202/3095] move all functional tests to tests module functional tests should be grouped with other tests (unit and integration tests). as part of this commit the "common" module was renamed to just "base", this was done for simplicity. the post_test_hook.sh file was also copied to the functional module since it should live there. a separate change to the infra repo will be made to call the new location, once that is merged we can remove the old one (a new change will also be posted for that) Needed-By: I49d54f009021d65c1ae49faf6b3f0a7acdadd7b3 Change-Id: Ie8c334f6223373b8e06df8bd8466500d2a2c8ede --- functional/tests/volume/v1/__init__.py | 0 functional/tests/volume/v2/__init__.py | 0 .../tests/functional}/__init__.py | 0 .../tests/functional/base.py | 0 .../tests/functional}/common/__init__.py | 0 .../common/test_availability_zone.py | 4 +- .../functional}/common/test_configuration.py | 4 +- .../tests/functional}/common/test_help.py | 4 +- .../tests/functional}/common/test_quota.py | 4 +- .../tests/functional/compute}/__init__.py | 0 .../tests/functional/compute/v2}/__init__.py | 0 .../functional}/compute/v2/test_agent.py | 4 +- .../functional}/compute/v2/test_aggregate.py | 4 +- .../functional}/compute/v2/test_flavor.py | 4 +- .../functional}/compute/v2/test_keypair.py | 4 +- .../functional}/compute/v2/test_server.py | 4 +- .../compute/v2/test_server_group.py | 4 +- .../tests/functional/examples}/__init__.py | 0 .../functional}/examples/test_examples.py | 10 ++--- .../tests/functional/identity}/__init__.py | 0 .../tests/functional/identity/v2}/__init__.py | 0 .../tests/functional}/identity/v2/common.py | 4 +- .../functional}/identity/v2/test_catalog.py | 2 +- .../identity/v2/test_ec2_credentials.py | 2 +- .../functional}/identity/v2/test_endpoint.py | 2 +- .../functional}/identity/v2/test_project.py | 2 +- .../functional}/identity/v2/test_role.py | 2 +- .../functional}/identity/v2/test_service.py | 2 +- .../functional}/identity/v2/test_token.py | 2 +- .../functional}/identity/v2/test_user.py | 2 +- .../tests/functional/identity/v3}/__init__.py | 0 .../tests/functional}/identity/v3/common.py | 4 +- .../functional}/identity/v3/test_catalog.py | 2 +- .../functional}/identity/v3/test_domain.py | 2 +- .../functional}/identity/v3/test_endpoint.py | 2 +- .../functional}/identity/v3/test_group.py | 2 +- .../tests/functional}/identity/v3/test_idp.py | 2 +- .../functional}/identity/v3/test_project.py | 2 +- .../functional}/identity/v3/test_region.py | 2 +- .../functional}/identity/v3/test_role.py | 2 +- .../functional}/identity/v3/test_service.py | 2 +- .../identity/v3/test_service_provider.py | 2 +- .../functional}/identity/v3/test_token.py | 2 +- .../functional}/identity/v3/test_user.py | 2 +- .../tests/functional/image}/__init__.py | 0 .../tests/functional/image/v1}/__init__.py | 0 .../tests/functional}/image/v1/test_image.py | 4 +- .../tests/functional/image/v2}/__init__.py | 0 .../tests/functional}/image/v2/test_image.py | 4 +- .../tests/functional/network}/__init__.py | 0 .../tests/functional/network}/v2/__init__.py | 0 .../network/v2/test_address_scope.py | 4 +- .../network/v2/test_floating_ip.py | 4 +- .../network/v2/test_ip_availability.py | 4 +- .../functional}/network/v2/test_network.py | 4 +- .../network/v2/test_network_agent.py | 4 +- .../network/v2/test_network_rbac.py | 4 +- .../network/v2/test_network_segment.py | 4 +- .../tests/functional}/network/v2/test_port.py | 4 +- .../functional}/network/v2/test_router.py | 4 +- .../network/v2/test_security_group.py | 4 +- .../network/v2/test_security_group_rule.py | 4 +- .../functional}/network/v2/test_subnet.py | 4 +- .../network/v2/test_subnet_pool.py | 4 +- .../tests/functional/object}/__init__.py | 0 .../tests/functional/object/v1}/__init__.py | 0 .../functional}/object/v1/test_container.py | 4 +- .../functional}/object/v1/test_object.py | 4 +- .../tests/functional/post_test_hook.sh | 43 +++++++++++++++++++ .../tests/functional/volume}/__init__.py | 0 .../tests/functional/volume}/v1/__init__.py | 0 .../tests/functional}/volume/v1/common.py | 4 +- .../tests/functional}/volume/v1/test_qos.py | 2 +- .../functional}/volume/v1/test_volume.py | 2 +- .../functional}/volume/v1/test_volume_type.py | 2 +- .../tests/functional/volume/v2}/__init__.py | 0 .../tests/functional}/volume/v2/common.py | 4 +- .../tests/functional}/volume/v2/test_qos.py | 2 +- .../functional}/volume/v2/test_snapshot.py | 2 +- .../functional}/volume/v2/test_volume.py | 2 +- .../functional}/volume/v2/test_volume_type.py | 2 +- tox.ini | 2 +- 82 files changed, 138 insertions(+), 95 deletions(-) delete mode 100644 functional/tests/volume/v1/__init__.py delete mode 100644 functional/tests/volume/v2/__init__.py rename {functional => openstackclient/tests/functional}/__init__.py (100%) rename functional/common/test.py => openstackclient/tests/functional/base.py (100%) rename {functional => openstackclient/tests/functional}/common/__init__.py (100%) rename {functional/tests => openstackclient/tests/functional}/common/test_availability_zone.py (91%) rename {functional/tests => openstackclient/tests/functional}/common/test_configuration.py (94%) rename {functional/tests => openstackclient/tests/functional}/common/test_help.py (97%) rename {functional/tests => openstackclient/tests/functional}/common/test_quota.py (95%) rename {functional/tests => openstackclient/tests/functional/compute}/__init__.py (100%) rename {functional/tests/common => openstackclient/tests/functional/compute/v2}/__init__.py (100%) rename {functional/tests => openstackclient/tests/functional}/compute/v2/test_agent.py (96%) rename {functional/tests => openstackclient/tests/functional}/compute/v2/test_aggregate.py (96%) rename {functional/tests => openstackclient/tests/functional}/compute/v2/test_flavor.py (96%) rename {functional/tests => openstackclient/tests/functional}/compute/v2/test_keypair.py (98%) rename {functional/tests => openstackclient/tests/functional}/compute/v2/test_server.py (99%) rename {functional/tests => openstackclient/tests/functional}/compute/v2/test_server_group.py (94%) rename {functional/tests/compute => openstackclient/tests/functional/examples}/__init__.py (100%) rename {functional/tests => openstackclient/tests/functional}/examples/test_examples.py (75%) rename {functional/tests/compute/v2 => openstackclient/tests/functional/identity}/__init__.py (100%) rename {functional/tests/examples => openstackclient/tests/functional/identity/v2}/__init__.py (100%) rename {functional/tests => openstackclient/tests/functional}/identity/v2/common.py (98%) rename {functional/tests => openstackclient/tests/functional}/identity/v2/test_catalog.py (96%) rename {functional/tests => openstackclient/tests/functional}/identity/v2/test_ec2_credentials.py (96%) rename {functional/tests => openstackclient/tests/functional}/identity/v2/test_endpoint.py (96%) rename {functional/tests => openstackclient/tests/functional}/identity/v2/test_project.py (98%) rename {functional/tests => openstackclient/tests/functional}/identity/v2/test_role.py (98%) rename {functional/tests => openstackclient/tests/functional}/identity/v2/test_service.py (96%) rename {functional/tests => openstackclient/tests/functional}/identity/v2/test_token.py (93%) rename {functional/tests => openstackclient/tests/functional}/identity/v2/test_user.py (97%) rename {functional/tests/identity => openstackclient/tests/functional/identity/v3}/__init__.py (100%) rename {functional/tests => openstackclient/tests/functional}/identity/v3/common.py (99%) rename {functional/tests => openstackclient/tests/functional}/identity/v3/test_catalog.py (97%) rename {functional/tests => openstackclient/tests/functional}/identity/v3/test_domain.py (97%) rename {functional/tests => openstackclient/tests/functional}/identity/v3/test_endpoint.py (97%) rename {functional/tests => openstackclient/tests/functional}/identity/v3/test_group.py (99%) rename {functional/tests => openstackclient/tests/functional}/identity/v3/test_idp.py (97%) rename {functional/tests => openstackclient/tests/functional}/identity/v3/test_project.py (98%) rename {functional/tests => openstackclient/tests/functional}/identity/v3/test_region.py (97%) rename {functional/tests => openstackclient/tests/functional}/identity/v3/test_role.py (98%) rename {functional/tests => openstackclient/tests/functional}/identity/v3/test_service.py (97%) rename {functional/tests => openstackclient/tests/functional}/identity/v3/test_service_provider.py (97%) rename {functional/tests => openstackclient/tests/functional}/identity/v3/test_token.py (92%) rename {functional/tests => openstackclient/tests/functional}/identity/v3/test_user.py (98%) rename {functional/tests/identity/v2 => openstackclient/tests/functional/image}/__init__.py (100%) rename {functional/tests/identity/v3 => openstackclient/tests/functional/image/v1}/__init__.py (100%) rename {functional/tests => openstackclient/tests/functional}/image/v1/test_image.py (96%) rename {functional/tests/image => openstackclient/tests/functional/image/v2}/__init__.py (100%) rename {functional/tests => openstackclient/tests/functional}/image/v2/test_image.py (97%) rename {functional/tests/image/v1 => openstackclient/tests/functional/network}/__init__.py (100%) rename {functional/tests/image => openstackclient/tests/functional/network}/v2/__init__.py (100%) rename {functional/tests => openstackclient/tests/functional}/network/v2/test_address_scope.py (95%) rename {functional/tests => openstackclient/tests/functional}/network/v2/test_floating_ip.py (95%) rename {functional/tests => openstackclient/tests/functional}/network/v2/test_ip_availability.py (95%) rename {functional/tests => openstackclient/tests/functional}/network/v2/test_network.py (95%) rename {functional/tests => openstackclient/tests/functional}/network/v2/test_network_agent.py (94%) rename {functional/tests => openstackclient/tests/functional}/network/v2/test_network_rbac.py (96%) rename {functional/tests => openstackclient/tests/functional}/network/v2/test_network_segment.py (96%) rename {functional/tests => openstackclient/tests/functional}/network/v2/test_port.py (96%) rename {functional/tests => openstackclient/tests/functional}/network/v2/test_router.py (95%) rename {functional/tests => openstackclient/tests/functional}/network/v2/test_security_group.py (95%) rename {functional/tests => openstackclient/tests/functional}/network/v2/test_security_group_rule.py (96%) rename {functional/tests => openstackclient/tests/functional}/network/v2/test_subnet.py (96%) rename {functional/tests => openstackclient/tests/functional}/network/v2/test_subnet_pool.py (95%) rename {functional/tests/network => openstackclient/tests/functional/object}/__init__.py (100%) rename {functional/tests/network/v2 => openstackclient/tests/functional/object/v1}/__init__.py (100%) rename {functional/tests => openstackclient/tests/functional}/object/v1/test_container.py (94%) rename {functional/tests => openstackclient/tests/functional}/object/v1/test_object.py (97%) create mode 100755 openstackclient/tests/functional/post_test_hook.sh rename {functional/tests/object => openstackclient/tests/functional/volume}/__init__.py (100%) rename {functional/tests/object => openstackclient/tests/functional/volume}/v1/__init__.py (100%) rename {functional/tests => openstackclient/tests/functional}/volume/v1/common.py (89%) rename {functional/tests => openstackclient/tests/functional}/volume/v1/test_qos.py (96%) rename {functional/tests => openstackclient/tests/functional}/volume/v1/test_volume.py (97%) rename {functional/tests => openstackclient/tests/functional}/volume/v1/test_volume_type.py (97%) rename {functional/tests/volume => openstackclient/tests/functional/volume/v2}/__init__.py (100%) rename {functional/tests => openstackclient/tests/functional}/volume/v2/common.py (89%) rename {functional/tests => openstackclient/tests/functional}/volume/v2/test_qos.py (97%) rename {functional/tests => openstackclient/tests/functional}/volume/v2/test_snapshot.py (98%) rename {functional/tests => openstackclient/tests/functional}/volume/v2/test_volume.py (98%) rename {functional/tests => openstackclient/tests/functional}/volume/v2/test_volume_type.py (98%) diff --git a/functional/tests/volume/v1/__init__.py b/functional/tests/volume/v1/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/functional/tests/volume/v2/__init__.py b/functional/tests/volume/v2/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/functional/__init__.py b/openstackclient/tests/functional/__init__.py similarity index 100% rename from functional/__init__.py rename to openstackclient/tests/functional/__init__.py diff --git a/functional/common/test.py b/openstackclient/tests/functional/base.py similarity index 100% rename from functional/common/test.py rename to openstackclient/tests/functional/base.py diff --git a/functional/common/__init__.py b/openstackclient/tests/functional/common/__init__.py similarity index 100% rename from functional/common/__init__.py rename to openstackclient/tests/functional/common/__init__.py diff --git a/functional/tests/common/test_availability_zone.py b/openstackclient/tests/functional/common/test_availability_zone.py similarity index 91% rename from functional/tests/common/test_availability_zone.py rename to openstackclient/tests/functional/common/test_availability_zone.py index da8aad7ddd..f73e1ed956 100644 --- a/functional/tests/common/test_availability_zone.py +++ b/openstackclient/tests/functional/common/test_availability_zone.py @@ -10,10 +10,10 @@ # License for the specific language governing permissions and limitations # under the License. -from functional.common import test +from openstackclient.tests.functional import base -class AvailabilityZoneTests(test.TestCase): +class AvailabilityZoneTests(base.TestCase): """Functional tests for availability zone. """ HEADERS = ["'Zone Name'"] # So far, all components have the same default availability zone name. diff --git a/functional/tests/common/test_configuration.py b/openstackclient/tests/functional/common/test_configuration.py similarity index 94% rename from functional/tests/common/test_configuration.py rename to openstackclient/tests/functional/common/test_configuration.py index 801ee10dfc..f47d3b0078 100644 --- a/functional/tests/common/test_configuration.py +++ b/openstackclient/tests/functional/common/test_configuration.py @@ -12,14 +12,14 @@ import os -from functional.common import test from openstackclient.common import configuration +from openstackclient.tests.functional import base BASIC_CONFIG_HEADERS = ['Field', 'Value'] -class ConfigurationTests(test.TestCase): +class ConfigurationTests(base.TestCase): opts = "-f value -c auth.password" diff --git a/functional/tests/common/test_help.py b/openstackclient/tests/functional/common/test_help.py similarity index 97% rename from functional/tests/common/test_help.py rename to openstackclient/tests/functional/common/test_help.py index 7601c41bb9..bbc521970e 100644 --- a/functional/tests/common/test_help.py +++ b/openstackclient/tests/functional/common/test_help.py @@ -10,10 +10,10 @@ # License for the specific language governing permissions and limitations # under the License. -from functional.common import test +from openstackclient.tests.functional import base -class HelpTests(test.TestCase): +class HelpTests(base.TestCase): """Functional tests for openstackclient help output.""" SERVER_COMMANDS = [ diff --git a/functional/tests/common/test_quota.py b/openstackclient/tests/functional/common/test_quota.py similarity index 95% rename from functional/tests/common/test_quota.py rename to openstackclient/tests/functional/common/test_quota.py index 0316de25a7..fd45be38ab 100644 --- a/functional/tests/common/test_quota.py +++ b/openstackclient/tests/functional/common/test_quota.py @@ -10,10 +10,10 @@ # License for the specific language governing permissions and limitations # under the License. -from functional.common import test +from openstackclient.tests.functional import base -class QuotaTests(test.TestCase): +class QuotaTests(base.TestCase): """Functional tests for quota. """ # Test quota information for compute, network and volume. EXPECTED_FIELDS = ['instances', 'networks', 'volumes'] diff --git a/functional/tests/__init__.py b/openstackclient/tests/functional/compute/__init__.py similarity index 100% rename from functional/tests/__init__.py rename to openstackclient/tests/functional/compute/__init__.py diff --git a/functional/tests/common/__init__.py b/openstackclient/tests/functional/compute/v2/__init__.py similarity index 100% rename from functional/tests/common/__init__.py rename to openstackclient/tests/functional/compute/v2/__init__.py diff --git a/functional/tests/compute/v2/test_agent.py b/openstackclient/tests/functional/compute/v2/test_agent.py similarity index 96% rename from functional/tests/compute/v2/test_agent.py rename to openstackclient/tests/functional/compute/v2/test_agent.py index d432768d2c..7115db1f4f 100644 --- a/functional/tests/compute/v2/test_agent.py +++ b/openstackclient/tests/functional/compute/v2/test_agent.py @@ -12,10 +12,10 @@ import hashlib -from functional.common import test +from openstackclient.tests.functional import base -class ComputeAgentTests(test.TestCase): +class ComputeAgentTests(base.TestCase): """Functional tests for compute agent.""" ID = None diff --git a/functional/tests/compute/v2/test_aggregate.py b/openstackclient/tests/functional/compute/v2/test_aggregate.py similarity index 96% rename from functional/tests/compute/v2/test_aggregate.py rename to openstackclient/tests/functional/compute/v2/test_aggregate.py index adb14e52ad..2bc88e7b59 100644 --- a/functional/tests/compute/v2/test_aggregate.py +++ b/openstackclient/tests/functional/compute/v2/test_aggregate.py @@ -12,10 +12,10 @@ import uuid -from functional.common import test +from openstackclient.tests.functional import base -class AggregateTests(test.TestCase): +class AggregateTests(base.TestCase): """Functional tests for aggregate.""" NAME = uuid.uuid4().hex diff --git a/functional/tests/compute/v2/test_flavor.py b/openstackclient/tests/functional/compute/v2/test_flavor.py similarity index 96% rename from functional/tests/compute/v2/test_flavor.py rename to openstackclient/tests/functional/compute/v2/test_flavor.py index ef0d2fe366..794a6cc30f 100644 --- a/functional/tests/compute/v2/test_flavor.py +++ b/openstackclient/tests/functional/compute/v2/test_flavor.py @@ -12,10 +12,10 @@ import uuid -from functional.common import test +from openstackclient.tests.functional import base -class FlavorTests(test.TestCase): +class FlavorTests(base.TestCase): """Functional tests for flavor.""" NAME = uuid.uuid4().hex diff --git a/functional/tests/compute/v2/test_keypair.py b/openstackclient/tests/functional/compute/v2/test_keypair.py similarity index 98% rename from functional/tests/compute/v2/test_keypair.py rename to openstackclient/tests/functional/compute/v2/test_keypair.py index 6bc5cdb7c1..01078c6136 100644 --- a/functional/tests/compute/v2/test_keypair.py +++ b/openstackclient/tests/functional/compute/v2/test_keypair.py @@ -12,13 +12,13 @@ import tempfile -from functional.common import test +from openstackclient.tests.functional import base from tempest.lib.common.utils import data_utils from tempest.lib import exceptions -class KeypairBase(test.TestCase): +class KeypairBase(base.TestCase): """Methods for functional tests.""" def keypair_create(self, name=data_utils.rand_uuid()): diff --git a/functional/tests/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py similarity index 99% rename from functional/tests/compute/v2/test_server.py rename to openstackclient/tests/functional/compute/v2/test_server.py index a9d0e9c132..6eedf408a6 100644 --- a/functional/tests/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -14,11 +14,11 @@ from tempest.lib.common.utils import data_utils -from functional.common import test +from openstackclient.tests.functional import base from tempest.lib import exceptions -class ServerTests(test.TestCase): +class ServerTests(base.TestCase): """Functional tests for openstack server commands.""" @classmethod diff --git a/functional/tests/compute/v2/test_server_group.py b/openstackclient/tests/functional/compute/v2/test_server_group.py similarity index 94% rename from functional/tests/compute/v2/test_server_group.py rename to openstackclient/tests/functional/compute/v2/test_server_group.py index b91260523d..3f0a24e5f9 100644 --- a/functional/tests/compute/v2/test_server_group.py +++ b/openstackclient/tests/functional/compute/v2/test_server_group.py @@ -12,10 +12,10 @@ import uuid -from functional.common import test +from openstackclient.tests.functional import base -class ServerGroupTests(test.TestCase): +class ServerGroupTests(base.TestCase): """Functional tests for servergroup.""" NAME = uuid.uuid4().hex diff --git a/functional/tests/compute/__init__.py b/openstackclient/tests/functional/examples/__init__.py similarity index 100% rename from functional/tests/compute/__init__.py rename to openstackclient/tests/functional/examples/__init__.py diff --git a/functional/tests/examples/test_examples.py b/openstackclient/tests/functional/examples/test_examples.py similarity index 75% rename from functional/tests/examples/test_examples.py rename to openstackclient/tests/functional/examples/test_examples.py index 6e0e586724..031f036a98 100644 --- a/functional/tests/examples/test_examples.py +++ b/openstackclient/tests/functional/examples/test_examples.py @@ -10,19 +10,19 @@ # License for the specific language governing permissions and limitations # under the License. -from functional.common import test +from openstackclient.tests.functional import base -class ExampleTests(test.TestCase): +class ExampleTests(base.TestCase): """Functional tests for running examples.""" def test_common(self): # NOTE(stevemar): If an examples has a non-zero return # code, then execute will raise an error by default. - test.execute('python', test.EXAMPLE_DIR + '/common.py --debug') + base.execute('python', base.EXAMPLE_DIR + '/common.py --debug') def test_object_api(self): - test.execute('python', test.EXAMPLE_DIR + '/object_api.py --debug') + base.execute('python', base.EXAMPLE_DIR + '/object_api.py --debug') def test_osc_lib(self): - test.execute('python', test.EXAMPLE_DIR + '/osc-lib.py --debug') + base.execute('python', base.EXAMPLE_DIR + '/osc-lib.py --debug') diff --git a/functional/tests/compute/v2/__init__.py b/openstackclient/tests/functional/identity/__init__.py similarity index 100% rename from functional/tests/compute/v2/__init__.py rename to openstackclient/tests/functional/identity/__init__.py diff --git a/functional/tests/examples/__init__.py b/openstackclient/tests/functional/identity/v2/__init__.py similarity index 100% rename from functional/tests/examples/__init__.py rename to openstackclient/tests/functional/identity/v2/__init__.py diff --git a/functional/tests/identity/v2/common.py b/openstackclient/tests/functional/identity/v2/common.py similarity index 98% rename from functional/tests/identity/v2/common.py rename to openstackclient/tests/functional/identity/v2/common.py index ca061a4dac..b390c5bc15 100644 --- a/functional/tests/identity/v2/common.py +++ b/openstackclient/tests/functional/identity/v2/common.py @@ -14,12 +14,12 @@ from tempest.lib.common.utils import data_utils -from functional.common import test +from openstackclient.tests.functional import base BASIC_LIST_HEADERS = ['ID', 'Name'] -class IdentityTests(test.TestCase): +class IdentityTests(base.TestCase): """Functional tests for Identity commands. """ USER_FIELDS = ['email', 'enabled', 'id', 'name', 'project_id', diff --git a/functional/tests/identity/v2/test_catalog.py b/openstackclient/tests/functional/identity/v2/test_catalog.py similarity index 96% rename from functional/tests/identity/v2/test_catalog.py rename to openstackclient/tests/functional/identity/v2/test_catalog.py index b6291e05d1..f403fbfc7b 100644 --- a/functional/tests/identity/v2/test_catalog.py +++ b/openstackclient/tests/functional/identity/v2/test_catalog.py @@ -10,7 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. -from functional.tests.identity.v2 import common +from openstackclient.tests.functional.identity.v2 import common class CatalogTests(common.IdentityTests): diff --git a/functional/tests/identity/v2/test_ec2_credentials.py b/openstackclient/tests/functional/identity/v2/test_ec2_credentials.py similarity index 96% rename from functional/tests/identity/v2/test_ec2_credentials.py rename to openstackclient/tests/functional/identity/v2/test_ec2_credentials.py index 7a8ee35d7e..43dff91f2f 100644 --- a/functional/tests/identity/v2/test_ec2_credentials.py +++ b/openstackclient/tests/functional/identity/v2/test_ec2_credentials.py @@ -10,7 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. -from functional.tests.identity.v2 import common +from openstackclient.tests.functional.identity.v2 import common class EC2CredentialsTests(common.IdentityTests): diff --git a/functional/tests/identity/v2/test_endpoint.py b/openstackclient/tests/functional/identity/v2/test_endpoint.py similarity index 96% rename from functional/tests/identity/v2/test_endpoint.py rename to openstackclient/tests/functional/identity/v2/test_endpoint.py index 34888c0b50..9df5ca8aa3 100644 --- a/functional/tests/identity/v2/test_endpoint.py +++ b/openstackclient/tests/functional/identity/v2/test_endpoint.py @@ -10,7 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. -from functional.tests.identity.v2 import common +from openstackclient.tests.functional.identity.v2 import common class EndpointTests(common.IdentityTests): diff --git a/functional/tests/identity/v2/test_project.py b/openstackclient/tests/functional/identity/v2/test_project.py similarity index 98% rename from functional/tests/identity/v2/test_project.py rename to openstackclient/tests/functional/identity/v2/test_project.py index 7fb1a98de0..b6222a1bbf 100644 --- a/functional/tests/identity/v2/test_project.py +++ b/openstackclient/tests/functional/identity/v2/test_project.py @@ -12,7 +12,7 @@ from tempest.lib.common.utils import data_utils -from functional.tests.identity.v2 import common +from openstackclient.tests.functional.identity.v2 import common class ProjectTests(common.IdentityTests): diff --git a/functional/tests/identity/v2/test_role.py b/openstackclient/tests/functional/identity/v2/test_role.py similarity index 98% rename from functional/tests/identity/v2/test_role.py rename to openstackclient/tests/functional/identity/v2/test_role.py index 0f8d5ed4b8..82e19aaba9 100644 --- a/functional/tests/identity/v2/test_role.py +++ b/openstackclient/tests/functional/identity/v2/test_role.py @@ -10,7 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. -from functional.tests.identity.v2 import common +from openstackclient.tests.functional.identity.v2 import common class RoleTests(common.IdentityTests): diff --git a/functional/tests/identity/v2/test_service.py b/openstackclient/tests/functional/identity/v2/test_service.py similarity index 96% rename from functional/tests/identity/v2/test_service.py rename to openstackclient/tests/functional/identity/v2/test_service.py index 9dcb6bea07..d0e0380404 100644 --- a/functional/tests/identity/v2/test_service.py +++ b/openstackclient/tests/functional/identity/v2/test_service.py @@ -10,7 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. -from functional.tests.identity.v2 import common +from openstackclient.tests.functional.identity.v2 import common class ServiceTests(common.IdentityTests): diff --git a/functional/tests/identity/v2/test_token.py b/openstackclient/tests/functional/identity/v2/test_token.py similarity index 93% rename from functional/tests/identity/v2/test_token.py rename to openstackclient/tests/functional/identity/v2/test_token.py index ca9b7d683b..f856974484 100644 --- a/functional/tests/identity/v2/test_token.py +++ b/openstackclient/tests/functional/identity/v2/test_token.py @@ -10,7 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. -from functional.tests.identity.v2 import common +from openstackclient.tests.functional.identity.v2 import common class TokenTests(common.IdentityTests): diff --git a/functional/tests/identity/v2/test_user.py b/openstackclient/tests/functional/identity/v2/test_user.py similarity index 97% rename from functional/tests/identity/v2/test_user.py rename to openstackclient/tests/functional/identity/v2/test_user.py index ef4deface2..ac609b94c7 100644 --- a/functional/tests/identity/v2/test_user.py +++ b/openstackclient/tests/functional/identity/v2/test_user.py @@ -13,7 +13,7 @@ from tempest.lib.common.utils import data_utils from tempest.lib import exceptions -from functional.tests.identity.v2 import common +from openstackclient.tests.functional.identity.v2 import common class UserTests(common.IdentityTests): diff --git a/functional/tests/identity/__init__.py b/openstackclient/tests/functional/identity/v3/__init__.py similarity index 100% rename from functional/tests/identity/__init__.py rename to openstackclient/tests/functional/identity/v3/__init__.py diff --git a/functional/tests/identity/v3/common.py b/openstackclient/tests/functional/identity/v3/common.py similarity index 99% rename from functional/tests/identity/v3/common.py rename to openstackclient/tests/functional/identity/v3/common.py index 47019c5fc8..5dd42e70f8 100644 --- a/functional/tests/identity/v3/common.py +++ b/openstackclient/tests/functional/identity/v3/common.py @@ -14,13 +14,13 @@ from tempest.lib.common.utils import data_utils -from functional.common import test +from openstackclient.tests.functional import base BASIC_LIST_HEADERS = ['ID', 'Name'] -class IdentityTests(test.TestCase): +class IdentityTests(base.TestCase): """Functional tests for Identity commands. """ DOMAIN_FIELDS = ['description', 'enabled', 'id', 'name', 'links'] diff --git a/functional/tests/identity/v3/test_catalog.py b/openstackclient/tests/functional/identity/v3/test_catalog.py similarity index 97% rename from functional/tests/identity/v3/test_catalog.py rename to openstackclient/tests/functional/identity/v3/test_catalog.py index e33876b083..c8361406bb 100644 --- a/functional/tests/identity/v3/test_catalog.py +++ b/openstackclient/tests/functional/identity/v3/test_catalog.py @@ -10,7 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. -from functional.tests.identity.v3 import common +from openstackclient.tests.functional.identity.v3 import common class CatalogTests(common.IdentityTests): diff --git a/functional/tests/identity/v3/test_domain.py b/openstackclient/tests/functional/identity/v3/test_domain.py similarity index 97% rename from functional/tests/identity/v3/test_domain.py rename to openstackclient/tests/functional/identity/v3/test_domain.py index 3f514b580b..d8946d1ef6 100644 --- a/functional/tests/identity/v3/test_domain.py +++ b/openstackclient/tests/functional/identity/v3/test_domain.py @@ -13,7 +13,7 @@ from tempest.lib.common.utils import data_utils from tempest.lib import exceptions -from functional.tests.identity.v3 import common +from openstackclient.tests.functional.identity.v3 import common class DomainTests(common.IdentityTests): diff --git a/functional/tests/identity/v3/test_endpoint.py b/openstackclient/tests/functional/identity/v3/test_endpoint.py similarity index 97% rename from functional/tests/identity/v3/test_endpoint.py rename to openstackclient/tests/functional/identity/v3/test_endpoint.py index e0afab2333..22dc1b659e 100644 --- a/functional/tests/identity/v3/test_endpoint.py +++ b/openstackclient/tests/functional/identity/v3/test_endpoint.py @@ -12,7 +12,7 @@ from tempest.lib.common.utils import data_utils -from functional.tests.identity.v3 import common +from openstackclient.tests.functional.identity.v3 import common class EndpointTests(common.IdentityTests): diff --git a/functional/tests/identity/v3/test_group.py b/openstackclient/tests/functional/identity/v3/test_group.py similarity index 99% rename from functional/tests/identity/v3/test_group.py rename to openstackclient/tests/functional/identity/v3/test_group.py index 350756989f..7049118359 100644 --- a/functional/tests/identity/v3/test_group.py +++ b/openstackclient/tests/functional/identity/v3/test_group.py @@ -12,7 +12,7 @@ from tempest.lib.common.utils import data_utils -from functional.tests.identity.v3 import common +from openstackclient.tests.functional.identity.v3 import common class GroupTests(common.IdentityTests): diff --git a/functional/tests/identity/v3/test_idp.py b/openstackclient/tests/functional/identity/v3/test_idp.py similarity index 97% rename from functional/tests/identity/v3/test_idp.py rename to openstackclient/tests/functional/identity/v3/test_idp.py index bc9690f77e..f9d8cb8031 100644 --- a/functional/tests/identity/v3/test_idp.py +++ b/openstackclient/tests/functional/identity/v3/test_idp.py @@ -10,7 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. -from functional.tests.identity.v3 import common +from openstackclient.tests.functional.identity.v3 import common from tempest.lib.common.utils import data_utils diff --git a/functional/tests/identity/v3/test_project.py b/openstackclient/tests/functional/identity/v3/test_project.py similarity index 98% rename from functional/tests/identity/v3/test_project.py rename to openstackclient/tests/functional/identity/v3/test_project.py index a27c58fb49..7743884195 100644 --- a/functional/tests/identity/v3/test_project.py +++ b/openstackclient/tests/functional/identity/v3/test_project.py @@ -12,7 +12,7 @@ from tempest.lib.common.utils import data_utils -from functional.tests.identity.v3 import common +from openstackclient.tests.functional.identity.v3 import common class ProjectTests(common.IdentityTests): diff --git a/functional/tests/identity/v3/test_region.py b/openstackclient/tests/functional/identity/v3/test_region.py similarity index 97% rename from functional/tests/identity/v3/test_region.py rename to openstackclient/tests/functional/identity/v3/test_region.py index 2ebc0e597d..2a402bd1a8 100644 --- a/functional/tests/identity/v3/test_region.py +++ b/openstackclient/tests/functional/identity/v3/test_region.py @@ -10,7 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. -from functional.tests.identity.v3 import common +from openstackclient.tests.functional.identity.v3 import common class RegionTests(common.IdentityTests): diff --git a/functional/tests/identity/v3/test_role.py b/openstackclient/tests/functional/identity/v3/test_role.py similarity index 98% rename from functional/tests/identity/v3/test_role.py rename to openstackclient/tests/functional/identity/v3/test_role.py index 60aaf3f457..ab8af9c04e 100644 --- a/functional/tests/identity/v3/test_role.py +++ b/openstackclient/tests/functional/identity/v3/test_role.py @@ -12,7 +12,7 @@ from tempest.lib.common.utils import data_utils -from functional.tests.identity.v3 import common +from openstackclient.tests.functional.identity.v3 import common class RoleTests(common.IdentityTests): diff --git a/functional/tests/identity/v3/test_service.py b/openstackclient/tests/functional/identity/v3/test_service.py similarity index 97% rename from functional/tests/identity/v3/test_service.py rename to openstackclient/tests/functional/identity/v3/test_service.py index 79a63dc8a8..1ecda45af2 100644 --- a/functional/tests/identity/v3/test_service.py +++ b/openstackclient/tests/functional/identity/v3/test_service.py @@ -12,7 +12,7 @@ from tempest.lib.common.utils import data_utils -from functional.tests.identity.v3 import common +from openstackclient.tests.functional.identity.v3 import common class ServiceTests(common.IdentityTests): diff --git a/functional/tests/identity/v3/test_service_provider.py b/openstackclient/tests/functional/identity/v3/test_service_provider.py similarity index 97% rename from functional/tests/identity/v3/test_service_provider.py rename to openstackclient/tests/functional/identity/v3/test_service_provider.py index 458c2ae65e..e072bc93b1 100644 --- a/functional/tests/identity/v3/test_service_provider.py +++ b/openstackclient/tests/functional/identity/v3/test_service_provider.py @@ -10,7 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. -from functional.tests.identity.v3 import common +from openstackclient.tests.functional.identity.v3 import common from tempest.lib.common.utils import data_utils diff --git a/functional/tests/identity/v3/test_token.py b/openstackclient/tests/functional/identity/v3/test_token.py similarity index 92% rename from functional/tests/identity/v3/test_token.py rename to openstackclient/tests/functional/identity/v3/test_token.py index d8d3f43d66..62e90003fc 100644 --- a/functional/tests/identity/v3/test_token.py +++ b/openstackclient/tests/functional/identity/v3/test_token.py @@ -10,7 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. -from functional.tests.identity.v3 import common +from openstackclient.tests.functional.identity.v3 import common class TokenTests(common.IdentityTests): diff --git a/functional/tests/identity/v3/test_user.py b/openstackclient/tests/functional/identity/v3/test_user.py similarity index 98% rename from functional/tests/identity/v3/test_user.py rename to openstackclient/tests/functional/identity/v3/test_user.py index f3657064c9..9e9bde96c0 100644 --- a/functional/tests/identity/v3/test_user.py +++ b/openstackclient/tests/functional/identity/v3/test_user.py @@ -12,7 +12,7 @@ from tempest.lib.common.utils import data_utils -from functional.tests.identity.v3 import common +from openstackclient.tests.functional.identity.v3 import common class UserTests(common.IdentityTests): diff --git a/functional/tests/identity/v2/__init__.py b/openstackclient/tests/functional/image/__init__.py similarity index 100% rename from functional/tests/identity/v2/__init__.py rename to openstackclient/tests/functional/image/__init__.py diff --git a/functional/tests/identity/v3/__init__.py b/openstackclient/tests/functional/image/v1/__init__.py similarity index 100% rename from functional/tests/identity/v3/__init__.py rename to openstackclient/tests/functional/image/v1/__init__.py diff --git a/functional/tests/image/v1/test_image.py b/openstackclient/tests/functional/image/v1/test_image.py similarity index 96% rename from functional/tests/image/v1/test_image.py rename to openstackclient/tests/functional/image/v1/test_image.py index c64c0d986b..2a2b5734bc 100644 --- a/functional/tests/image/v1/test_image.py +++ b/openstackclient/tests/functional/image/v1/test_image.py @@ -13,10 +13,10 @@ import os import uuid -from functional.common import test +from openstackclient.tests.functional import base -class ImageTests(test.TestCase): +class ImageTests(base.TestCase): """Functional tests for image. """ NAME = uuid.uuid4().hex diff --git a/functional/tests/image/__init__.py b/openstackclient/tests/functional/image/v2/__init__.py similarity index 100% rename from functional/tests/image/__init__.py rename to openstackclient/tests/functional/image/v2/__init__.py diff --git a/functional/tests/image/v2/test_image.py b/openstackclient/tests/functional/image/v2/test_image.py similarity index 97% rename from functional/tests/image/v2/test_image.py rename to openstackclient/tests/functional/image/v2/test_image.py index 809451bb74..3f432b02ee 100644 --- a/functional/tests/image/v2/test_image.py +++ b/openstackclient/tests/functional/image/v2/test_image.py @@ -13,10 +13,10 @@ import os import uuid -from functional.common import test +from openstackclient.tests.functional import base -class ImageTests(test.TestCase): +class ImageTests(base.TestCase): """Functional tests for image. """ NAME = uuid.uuid4().hex diff --git a/functional/tests/image/v1/__init__.py b/openstackclient/tests/functional/network/__init__.py similarity index 100% rename from functional/tests/image/v1/__init__.py rename to openstackclient/tests/functional/network/__init__.py diff --git a/functional/tests/image/v2/__init__.py b/openstackclient/tests/functional/network/v2/__init__.py similarity index 100% rename from functional/tests/image/v2/__init__.py rename to openstackclient/tests/functional/network/v2/__init__.py diff --git a/functional/tests/network/v2/test_address_scope.py b/openstackclient/tests/functional/network/v2/test_address_scope.py similarity index 95% rename from functional/tests/network/v2/test_address_scope.py rename to openstackclient/tests/functional/network/v2/test_address_scope.py index 3beab2338f..ef4b575609 100644 --- a/functional/tests/network/v2/test_address_scope.py +++ b/openstackclient/tests/functional/network/v2/test_address_scope.py @@ -12,10 +12,10 @@ import uuid -from functional.common import test +from openstackclient.tests.functional import base -class AddressScopeTests(test.TestCase): +class AddressScopeTests(base.TestCase): """Functional tests for address scope. """ NAME = uuid.uuid4().hex HEADERS = ['Name'] diff --git a/functional/tests/network/v2/test_floating_ip.py b/openstackclient/tests/functional/network/v2/test_floating_ip.py similarity index 95% rename from functional/tests/network/v2/test_floating_ip.py rename to openstackclient/tests/functional/network/v2/test_floating_ip.py index 7e7c63f4d4..f3a1971f79 100644 --- a/functional/tests/network/v2/test_floating_ip.py +++ b/openstackclient/tests/functional/network/v2/test_floating_ip.py @@ -12,10 +12,10 @@ import uuid -from functional.common import test +from openstackclient.tests.functional import base -class FloatingIpTests(test.TestCase): +class FloatingIpTests(base.TestCase): """Functional tests for floating ip. """ SUBNET_NAME = uuid.uuid4().hex NETWORK_NAME = uuid.uuid4().hex diff --git a/functional/tests/network/v2/test_ip_availability.py b/openstackclient/tests/functional/network/v2/test_ip_availability.py similarity index 95% rename from functional/tests/network/v2/test_ip_availability.py rename to openstackclient/tests/functional/network/v2/test_ip_availability.py index e83010fded..b5c908f44d 100644 --- a/functional/tests/network/v2/test_ip_availability.py +++ b/openstackclient/tests/functional/network/v2/test_ip_availability.py @@ -12,10 +12,10 @@ import uuid -from functional.common import test +from openstackclient.tests.functional import base -class IPAvailabilityTests(test.TestCase): +class IPAvailabilityTests(base.TestCase): """Functional tests for IP availability. """ NAME = uuid.uuid4().hex NETWORK_NAME = uuid.uuid4().hex diff --git a/functional/tests/network/v2/test_network.py b/openstackclient/tests/functional/network/v2/test_network.py similarity index 95% rename from functional/tests/network/v2/test_network.py rename to openstackclient/tests/functional/network/v2/test_network.py index f5c92faadc..c77ff642c5 100644 --- a/functional/tests/network/v2/test_network.py +++ b/openstackclient/tests/functional/network/v2/test_network.py @@ -12,10 +12,10 @@ import uuid -from functional.common import test +from openstackclient.tests.functional import base -class NetworkTests(test.TestCase): +class NetworkTests(base.TestCase): """Functional tests for network. """ NAME = uuid.uuid4().hex HEADERS = ['Name'] diff --git a/functional/tests/network/v2/test_network_agent.py b/openstackclient/tests/functional/network/v2/test_network_agent.py similarity index 94% rename from functional/tests/network/v2/test_network_agent.py rename to openstackclient/tests/functional/network/v2/test_network_agent.py index f574c50c94..dd6112e72e 100644 --- a/functional/tests/network/v2/test_network_agent.py +++ b/openstackclient/tests/functional/network/v2/test_network_agent.py @@ -10,10 +10,10 @@ # License for the specific language governing permissions and limitations # under the License. -from functional.common import test +from openstackclient.tests.functional import base -class NetworkAgentTests(test.TestCase): +class NetworkAgentTests(base.TestCase): """Functional tests for network agent. """ IDs = None HEADERS = ['ID'] diff --git a/functional/tests/network/v2/test_network_rbac.py b/openstackclient/tests/functional/network/v2/test_network_rbac.py similarity index 96% rename from functional/tests/network/v2/test_network_rbac.py rename to openstackclient/tests/functional/network/v2/test_network_rbac.py index 1dbc246bf2..6f9f05e792 100644 --- a/functional/tests/network/v2/test_network_rbac.py +++ b/openstackclient/tests/functional/network/v2/test_network_rbac.py @@ -12,10 +12,10 @@ import uuid -from functional.common import test +from openstackclient.tests.functional import base -class NetworkRBACTests(test.TestCase): +class NetworkRBACTests(base.TestCase): """Functional tests for network rbac. """ NET_NAME = uuid.uuid4().hex PROJECT_NAME = uuid.uuid4().hex diff --git a/functional/tests/network/v2/test_network_segment.py b/openstackclient/tests/functional/network/v2/test_network_segment.py similarity index 96% rename from functional/tests/network/v2/test_network_segment.py rename to openstackclient/tests/functional/network/v2/test_network_segment.py index 4609973cf4..f871e88e18 100644 --- a/functional/tests/network/v2/test_network_segment.py +++ b/openstackclient/tests/functional/network/v2/test_network_segment.py @@ -13,12 +13,12 @@ import testtools import uuid -from functional.common import test +from openstackclient.tests.functional import base # NOTE(rtheis): Routed networks is still a WIP and not enabled by default. @testtools.skip("bp/routed-networks") -class NetworkSegmentTests(test.TestCase): +class NetworkSegmentTests(base.TestCase): """Functional tests for network segment. """ NETWORK_NAME = uuid.uuid4().hex PHYSICAL_NETWORK_NAME = uuid.uuid4().hex diff --git a/functional/tests/network/v2/test_port.py b/openstackclient/tests/functional/network/v2/test_port.py similarity index 96% rename from functional/tests/network/v2/test_port.py rename to openstackclient/tests/functional/network/v2/test_port.py index a68019c45b..decd9553dc 100644 --- a/functional/tests/network/v2/test_port.py +++ b/openstackclient/tests/functional/network/v2/test_port.py @@ -12,10 +12,10 @@ import uuid -from functional.common import test +from openstackclient.tests.functional import base -class PortTests(test.TestCase): +class PortTests(base.TestCase): """Functional tests for port. """ NAME = uuid.uuid4().hex NETWORK_NAME = uuid.uuid4().hex diff --git a/functional/tests/network/v2/test_router.py b/openstackclient/tests/functional/network/v2/test_router.py similarity index 95% rename from functional/tests/network/v2/test_router.py rename to openstackclient/tests/functional/network/v2/test_router.py index e536c64ec7..789c382548 100644 --- a/functional/tests/network/v2/test_router.py +++ b/openstackclient/tests/functional/network/v2/test_router.py @@ -12,10 +12,10 @@ import uuid -from functional.common import test +from openstackclient.tests.functional import base -class RouterTests(test.TestCase): +class RouterTests(base.TestCase): """Functional tests for router. """ NAME = uuid.uuid4().hex HEADERS = ['Name'] diff --git a/functional/tests/network/v2/test_security_group.py b/openstackclient/tests/functional/network/v2/test_security_group.py similarity index 95% rename from functional/tests/network/v2/test_security_group.py rename to openstackclient/tests/functional/network/v2/test_security_group.py index 2a3b92a0d3..debd81df6e 100644 --- a/functional/tests/network/v2/test_security_group.py +++ b/openstackclient/tests/functional/network/v2/test_security_group.py @@ -12,10 +12,10 @@ import uuid -from functional.common import test +from openstackclient.tests.functional import base -class SecurityGroupTests(test.TestCase): +class SecurityGroupTests(base.TestCase): """Functional tests for security group. """ NAME = uuid.uuid4().hex OTHER_NAME = uuid.uuid4().hex diff --git a/functional/tests/network/v2/test_security_group_rule.py b/openstackclient/tests/functional/network/v2/test_security_group_rule.py similarity index 96% rename from functional/tests/network/v2/test_security_group_rule.py rename to openstackclient/tests/functional/network/v2/test_security_group_rule.py index 248d20b1fb..c91de1a570 100644 --- a/functional/tests/network/v2/test_security_group_rule.py +++ b/openstackclient/tests/functional/network/v2/test_security_group_rule.py @@ -12,10 +12,10 @@ import uuid -from functional.common import test +from openstackclient.tests.functional import base -class SecurityGroupRuleTests(test.TestCase): +class SecurityGroupRuleTests(base.TestCase): """Functional tests for security group rule. """ SECURITY_GROUP_NAME = uuid.uuid4().hex SECURITY_GROUP_RULE_ID = None diff --git a/functional/tests/network/v2/test_subnet.py b/openstackclient/tests/functional/network/v2/test_subnet.py similarity index 96% rename from functional/tests/network/v2/test_subnet.py rename to openstackclient/tests/functional/network/v2/test_subnet.py index 7fb48437d9..231671f31d 100644 --- a/functional/tests/network/v2/test_subnet.py +++ b/openstackclient/tests/functional/network/v2/test_subnet.py @@ -12,10 +12,10 @@ import uuid -from functional.common import test +from openstackclient.tests.functional import base -class SubnetTests(test.TestCase): +class SubnetTests(base.TestCase): """Functional tests for subnet. """ NAME = uuid.uuid4().hex NETWORK_NAME = uuid.uuid4().hex diff --git a/functional/tests/network/v2/test_subnet_pool.py b/openstackclient/tests/functional/network/v2/test_subnet_pool.py similarity index 95% rename from functional/tests/network/v2/test_subnet_pool.py rename to openstackclient/tests/functional/network/v2/test_subnet_pool.py index 054188f714..e52f06fc5d 100644 --- a/functional/tests/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/functional/network/v2/test_subnet_pool.py @@ -12,10 +12,10 @@ import uuid -from functional.common import test +from openstackclient.tests.functional import base -class SubnetPoolTests(test.TestCase): +class SubnetPoolTests(base.TestCase): """Functional tests for subnet pool. """ NAME = uuid.uuid4().hex CREATE_POOL_PREFIX = '10.100.0.0/24' diff --git a/functional/tests/network/__init__.py b/openstackclient/tests/functional/object/__init__.py similarity index 100% rename from functional/tests/network/__init__.py rename to openstackclient/tests/functional/object/__init__.py diff --git a/functional/tests/network/v2/__init__.py b/openstackclient/tests/functional/object/v1/__init__.py similarity index 100% rename from functional/tests/network/v2/__init__.py rename to openstackclient/tests/functional/object/v1/__init__.py diff --git a/functional/tests/object/v1/test_container.py b/openstackclient/tests/functional/object/v1/test_container.py similarity index 94% rename from functional/tests/object/v1/test_container.py rename to openstackclient/tests/functional/object/v1/test_container.py index 4f9e843b5c..af76efd96c 100644 --- a/functional/tests/object/v1/test_container.py +++ b/openstackclient/tests/functional/object/v1/test_container.py @@ -12,10 +12,10 @@ import uuid -from functional.common import test +from openstackclient.tests.functional import base -class ContainerTests(test.TestCase): +class ContainerTests(base.TestCase): """Functional tests for object containers. """ NAME = uuid.uuid4().hex diff --git a/functional/tests/object/v1/test_object.py b/openstackclient/tests/functional/object/v1/test_object.py similarity index 97% rename from functional/tests/object/v1/test_object.py rename to openstackclient/tests/functional/object/v1/test_object.py index 8ea16da785..776cf47c2c 100644 --- a/functional/tests/object/v1/test_object.py +++ b/openstackclient/tests/functional/object/v1/test_object.py @@ -14,14 +14,14 @@ import tempfile import uuid -from functional.common import test +from openstackclient.tests.functional import base BASIC_LIST_HEADERS = ['Name'] CONTAINER_FIELDS = ['account', 'container', 'x-trans-id'] OBJECT_FIELDS = ['object', 'container', 'etag'] -class ObjectTests(test.TestCase): +class ObjectTests(base.TestCase): """Functional tests for Object commands. """ CONTAINER_NAME = uuid.uuid4().hex diff --git a/openstackclient/tests/functional/post_test_hook.sh b/openstackclient/tests/functional/post_test_hook.sh new file mode 100755 index 0000000000..e555470d17 --- /dev/null +++ b/openstackclient/tests/functional/post_test_hook.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +# This is a script that kicks off a series of functional tests against an +# OpenStack cloud. It will attempt to create an instance if one is not +# available. Do not run this script unless you know what you're doing. +# For more information refer to: +# http://docs.openstack.org/developer/python-openstackclient/ + +function generate_testr_results { + if [ -f .testrepository/0 ]; then + sudo .tox/functional/bin/testr last --subunit > $WORKSPACE/testrepository.subunit + sudo mv $WORKSPACE/testrepository.subunit $BASE/logs/testrepository.subunit + sudo .tox/functional/bin/subunit2html $BASE/logs/testrepository.subunit $BASE/logs/testr_results.html + sudo gzip -9 $BASE/logs/testrepository.subunit + sudo gzip -9 $BASE/logs/testr_results.html + sudo chown jenkins:jenkins $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz + sudo chmod a+r $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz + fi +} + +OPENSTACKCLIENT_DIR=$(cd $(dirname "$0") && pwd) +sudo chown -R jenkins:stack $OPENSTACKCLIENT_DIR + +# Run tests +echo "Running openstackclient functional test suite" +set +e + +# Go to the openstackclient dir +cd $OPENSTACKCLIENT_DIR + +# Source environment variables to kick things off +source ~stack/devstack/openrc admin admin +echo 'Running tests with:' +env | grep OS + +# Preserve env for OS_ credentials +sudo -E -H -u jenkins tox -efunctional +EXIT_CODE=$? +set -e + +# Collect and parse result +generate_testr_results +exit $EXIT_CODE diff --git a/functional/tests/object/__init__.py b/openstackclient/tests/functional/volume/__init__.py similarity index 100% rename from functional/tests/object/__init__.py rename to openstackclient/tests/functional/volume/__init__.py diff --git a/functional/tests/object/v1/__init__.py b/openstackclient/tests/functional/volume/v1/__init__.py similarity index 100% rename from functional/tests/object/v1/__init__.py rename to openstackclient/tests/functional/volume/v1/__init__.py diff --git a/functional/tests/volume/v1/common.py b/openstackclient/tests/functional/volume/v1/common.py similarity index 89% rename from functional/tests/volume/v1/common.py rename to openstackclient/tests/functional/volume/v1/common.py index 7d35ed5e6d..a442850daa 100644 --- a/functional/tests/volume/v1/common.py +++ b/openstackclient/tests/functional/volume/v1/common.py @@ -12,10 +12,10 @@ import os -from functional.common import test +from openstackclient.tests.functional import base -class BaseVolumeTests(test.TestCase): +class BaseVolumeTests(base.TestCase): """Base class for Volume functional tests. """ @classmethod diff --git a/functional/tests/volume/v1/test_qos.py b/openstackclient/tests/functional/volume/v1/test_qos.py similarity index 96% rename from functional/tests/volume/v1/test_qos.py rename to openstackclient/tests/functional/volume/v1/test_qos.py index 5aed4bd0b5..770d5acbd8 100644 --- a/functional/tests/volume/v1/test_qos.py +++ b/openstackclient/tests/functional/volume/v1/test_qos.py @@ -12,7 +12,7 @@ import uuid -from functional.tests.volume.v1 import common +from openstackclient.tests.functional.volume.v1 import common class QosTests(common.BaseVolumeTests): diff --git a/functional/tests/volume/v1/test_volume.py b/openstackclient/tests/functional/volume/v1/test_volume.py similarity index 97% rename from functional/tests/volume/v1/test_volume.py rename to openstackclient/tests/functional/volume/v1/test_volume.py index 8275bf0a96..6ac7f2bf71 100644 --- a/functional/tests/volume/v1/test_volume.py +++ b/openstackclient/tests/functional/volume/v1/test_volume.py @@ -12,7 +12,7 @@ import uuid -from functional.tests.volume.v1 import common +from openstackclient.tests.functional.volume.v1 import common class VolumeTests(common.BaseVolumeTests): diff --git a/functional/tests/volume/v1/test_volume_type.py b/openstackclient/tests/functional/volume/v1/test_volume_type.py similarity index 97% rename from functional/tests/volume/v1/test_volume_type.py rename to openstackclient/tests/functional/volume/v1/test_volume_type.py index ed44f3c3cc..538545abc6 100644 --- a/functional/tests/volume/v1/test_volume_type.py +++ b/openstackclient/tests/functional/volume/v1/test_volume_type.py @@ -13,7 +13,7 @@ import time import uuid -from functional.tests.volume.v1 import common +from openstackclient.tests.functional.volume.v1 import common class VolumeTypeTests(common.BaseVolumeTests): diff --git a/functional/tests/volume/__init__.py b/openstackclient/tests/functional/volume/v2/__init__.py similarity index 100% rename from functional/tests/volume/__init__.py rename to openstackclient/tests/functional/volume/v2/__init__.py diff --git a/functional/tests/volume/v2/common.py b/openstackclient/tests/functional/volume/v2/common.py similarity index 89% rename from functional/tests/volume/v2/common.py rename to openstackclient/tests/functional/volume/v2/common.py index 8652c2d182..e279a6f6ca 100644 --- a/functional/tests/volume/v2/common.py +++ b/openstackclient/tests/functional/volume/v2/common.py @@ -12,10 +12,10 @@ import os -from functional.common import test +from openstackclient.tests.functional import base -class BaseVolumeTests(test.TestCase): +class BaseVolumeTests(base.TestCase): """Base class for Volume functional tests. """ @classmethod diff --git a/functional/tests/volume/v2/test_qos.py b/openstackclient/tests/functional/volume/v2/test_qos.py similarity index 97% rename from functional/tests/volume/v2/test_qos.py rename to openstackclient/tests/functional/volume/v2/test_qos.py index 5311b478d3..a54acbfd47 100644 --- a/functional/tests/volume/v2/test_qos.py +++ b/openstackclient/tests/functional/volume/v2/test_qos.py @@ -12,7 +12,7 @@ import uuid -from functional.tests.volume.v2 import common +from openstackclient.tests.functional.volume.v2 import common class QosTests(common.BaseVolumeTests): diff --git a/functional/tests/volume/v2/test_snapshot.py b/openstackclient/tests/functional/volume/v2/test_snapshot.py similarity index 98% rename from functional/tests/volume/v2/test_snapshot.py rename to openstackclient/tests/functional/volume/v2/test_snapshot.py index 4f910830a7..4582b67dac 100644 --- a/functional/tests/volume/v2/test_snapshot.py +++ b/openstackclient/tests/functional/volume/v2/test_snapshot.py @@ -13,7 +13,7 @@ import time import uuid -from functional.tests.volume.v2 import common +from openstackclient.tests.functional.volume.v2 import common class SnapshotTests(common.BaseVolumeTests): diff --git a/functional/tests/volume/v2/test_volume.py b/openstackclient/tests/functional/volume/v2/test_volume.py similarity index 98% rename from functional/tests/volume/v2/test_volume.py rename to openstackclient/tests/functional/volume/v2/test_volume.py index 02324a1e43..73273573a9 100644 --- a/functional/tests/volume/v2/test_volume.py +++ b/openstackclient/tests/functional/volume/v2/test_volume.py @@ -13,7 +13,7 @@ import time import uuid -from functional.tests.volume.v2 import common +from openstackclient.tests.functional.volume.v2 import common class VolumeTests(common.BaseVolumeTests): diff --git a/functional/tests/volume/v2/test_volume_type.py b/openstackclient/tests/functional/volume/v2/test_volume_type.py similarity index 98% rename from functional/tests/volume/v2/test_volume_type.py rename to openstackclient/tests/functional/volume/v2/test_volume_type.py index 02f790ec50..b62cbb3908 100644 --- a/functional/tests/volume/v2/test_volume_type.py +++ b/openstackclient/tests/functional/volume/v2/test_volume_type.py @@ -13,7 +13,7 @@ import time import uuid -from functional.tests.volume.v2 import common +from openstackclient.tests.functional.volume.v2 import common class VolumeTypeTests(common.BaseVolumeTests): diff --git a/tox.ini b/tox.ini index 9eb0a1a0af..af7120e11e 100644 --- a/tox.ini +++ b/tox.ini @@ -51,7 +51,7 @@ commands = bandit -r openstackclient -x tests -s B105,B106,B107,B401,B404,B603,B606,B607,B110,B605,B101 [testenv:functional] -setenv = OS_TEST_PATH=./functional/tests +setenv = OS_TEST_PATH=./openstackclient/tests/functional passenv = OS_* [testenv:venv] From 4f23a77de04bfdfafd4bf8f16b0365df7663e9e5 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Tue, 8 Mar 2016 15:18:16 -0600 Subject: [PATCH 1203/3095] Add network segment create, delete and set support Add network segment create, delete and set in support of routed networks. This patch set includes documentation, unit tests and functional tests for the following new commands: - "os network segment create" - "os network segment delete" - "os network segment set" This patch set also includes support for the name and description properties. These new commands are currently marked as beta commands. Change-Id: I86bc223c4adc5b5fe1b1ee5c9253e43ba52fb5ed Depends-On: Ib194125162057fccb4e951587c2fa4ec2e2f098c Partially-Implements: blueprint routed-networks --- .../command-objects/network-segment.rst | 101 +++++- openstackclient/network/v2/network_segment.py | 161 ++++++++- openstackclient/tests/functional/base.py | 5 + .../network/v2/test_network_segment.py | 84 +++-- .../tests/unit/network/v2/fakes.py | 9 +- .../unit/network/v2/test_network_segment.py | 317 ++++++++++++++++++ .../bp-routed-networks-3b502faa5cd96807.yaml | 6 + setup.cfg | 3 + 8 files changed, 660 insertions(+), 26 deletions(-) create mode 100644 releasenotes/notes/bp-routed-networks-3b502faa5cd96807.yaml diff --git a/doc/source/command-objects/network-segment.rst b/doc/source/command-objects/network-segment.rst index 56a11e1ed4..242dcbe965 100644 --- a/doc/source/command-objects/network-segment.rst +++ b/doc/source/command-objects/network-segment.rst @@ -9,6 +9,75 @@ within a network may not be guaranteed. Network v2 +network segment create +---------------------- + +Create new network segment + +.. caution:: This is a beta command and subject to change. + Use global option ``--os-beta-command`` to + enable this command. + +.. program:: network segment create +.. code:: bash + + os network segment create + [--description ] + [--physical-network ] + [--segment ] + --network + --network-type + + +.. option:: --description + + Network segment description + +.. option:: --physical-network + + Physical network name of this network segment + +.. option:: --segment + + Segment identifier for this network segment which is + based on the network type, VLAN ID for vlan network + type and tunnel ID for geneve, gre and vxlan network + types + +.. option:: --network + + Network this network segment belongs to (name or ID) + +.. option:: --network-type + + Network type of this network segment + (flat, geneve, gre, local, vlan or vxlan) + +.. _network_segment_create-name: +.. describe:: + + New network segment name + +network segment delete +---------------------- + +Delete network segment(s) + +.. caution:: This is a beta command and subject to change. + Use global option ``--os-beta-command`` to + enable this command. + +.. program:: network segment delete +.. code:: bash + + os network segment delete + [ ...] + +.. _network_segment_delete-segment: +.. describe:: + + Network segment(s) to delete (name or ID) + network segment list -------------------- @@ -33,6 +102,36 @@ List network segments List network segments that belong to this network (name or ID) +network segment set +------------------- + +Set network segment properties + +.. caution:: This is a beta command and subject to change. + Use global option ``--os-beta-command`` to + enable this command. + +.. program:: network segment set +.. code:: bash + + os network segment set + [--description ] + [--name ] + + +.. option:: --description + + Set network segment description + +.. option:: --name + + Set network segment name + +.. _network_segment_set-segment: +.. describe:: + + Network segment to modify (name or ID) + network segment show -------------------- @@ -51,4 +150,4 @@ Display network segment details .. _network_segment_show-segment: .. describe:: - Network segment to display (ID only) + Network segment to display (name or ID) diff --git a/openstackclient/network/v2/network_segment.py b/openstackclient/network/v2/network_segment.py index bedf15f7d7..34cac0e02e 100644 --- a/openstackclient/network/v2/network_segment.py +++ b/openstackclient/network/v2/network_segment.py @@ -13,14 +13,129 @@ """Network segment action implementations""" -# TODO(rtheis): Add description and name properties when support is available. +import logging from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + +class CreateNetworkSegment(command.ShowOne): + """Create new network segment + + (Caution: This is a beta command and subject to change. + Use global option --os-beta-command to enable + this command) + """ + + def get_parser(self, prog_name): + parser = super(CreateNetworkSegment, self).get_parser(prog_name) + parser.add_argument( + 'name', + metavar='', + help=_('New network segment name') + ) + parser.add_argument( + '--description', + metavar='', + help=_('Network segment description'), + ) + parser.add_argument( + '--physical-network', + metavar='', + help=_('Physical network name of this network segment'), + ) + parser.add_argument( + '--segment', + metavar='', + type=int, + help=_('Segment identifier for this network segment which is ' + 'based on the network type, VLAN ID for vlan network ' + 'type and tunnel ID for geneve, gre and vxlan network ' + 'types'), + ) + parser.add_argument( + '--network', + metavar='', + required=True, + help=_('Network this network segment belongs to (name or ID)'), + ) + parser.add_argument( + '--network-type', + metavar='', + choices=['flat', 'geneve', 'gre', 'local', 'vlan', 'vxlan'], + required=True, + help=_('Network type of this network segment ' + '(flat, geneve, gre, local, vlan or vxlan)'), + ) + return parser + + def take_action(self, parsed_args): + self.validate_os_beta_command_enabled() + client = self.app.client_manager.network + attrs = {} + attrs['name'] = parsed_args.name + attrs['network_id'] = client.find_network(parsed_args.network, + ignore_missing=False).id + attrs['network_type'] = parsed_args.network_type + if parsed_args.description is not None: + attrs['description'] = parsed_args.description + if parsed_args.physical_network is not None: + attrs['physical_network'] = parsed_args.physical_network + if parsed_args.segment is not None: + attrs['segmentation_id'] = parsed_args.segment + obj = client.create_segment(**attrs) + columns = tuple(sorted(obj.keys())) + data = utils.get_item_properties(obj, columns) + return (columns, data) + + +class DeleteNetworkSegment(command.Command): + """Delete network segment(s) + + (Caution: This is a beta command and subject to change. + Use global option --os-beta-command to enable + this command) + """ + + def get_parser(self, prog_name): + parser = super(DeleteNetworkSegment, self).get_parser(prog_name) + parser.add_argument( + 'network_segment', + metavar='', + nargs='+', + help=_('Network segment(s) to delete (name or ID)'), + ) + return parser + + def take_action(self, parsed_args): + self.validate_os_beta_command_enabled() + client = self.app.client_manager.network + + result = 0 + for network_segment in parsed_args.network_segment: + try: + obj = client.find_segment(network_segment, + ignore_missing=False) + client.delete_segment(obj) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete network segment with " + "ID '%(network_segment)s': %(e)s") + % {'network_segment': network_segment, 'e': e}) + + if result > 0: + total = len(parsed_args.network_segment) + msg = (_("%(result)s of %(total)s network segments failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) + + class ListNetworkSegment(command.Lister): """List network segments @@ -61,12 +176,14 @@ def take_action(self, parsed_args): headers = ( 'ID', + 'Name', 'Network', 'Network Type', 'Segment', ) columns = ( 'id', + 'name', 'network_id', 'network_type', 'segmentation_id', @@ -86,6 +203,46 @@ def take_action(self, parsed_args): ) for s in data)) +class SetNetworkSegment(command.Command): + """Set network segment properties + + (Caution: This is a beta command and subject to change. + Use global option --os-beta-command to enable + this command) + """ + + def get_parser(self, prog_name): + parser = super(SetNetworkSegment, self).get_parser(prog_name) + parser.add_argument( + 'network_segment', + metavar='', + help=_('Network segment to modify (name or ID)'), + ) + parser.add_argument( + '--description', + metavar='', + help=_('Set network segment description'), + ) + parser.add_argument( + '--name', + metavar='', + help=_('Set network segment name'), + ) + return parser + + def take_action(self, parsed_args): + self.validate_os_beta_command_enabled() + client = self.app.client_manager.network + obj = client.find_segment(parsed_args.network_segment, + ignore_missing=False) + attrs = {} + if parsed_args.description is not None: + attrs['description'] = parsed_args.description + if parsed_args.name is not None: + attrs['name'] = parsed_args.name + client.update_segment(obj, **attrs) + + class ShowNetworkSegment(command.ShowOne): """Display network segment details @@ -99,7 +256,7 @@ def get_parser(self, prog_name): parser.add_argument( 'network_segment', metavar='', - help=_('Network segment to display (ID only)'), + help=_('Network segment to display (name or ID)'), ) return parser diff --git a/openstackclient/tests/functional/base.py b/openstackclient/tests/functional/base.py index f7f0361eea..298b24548b 100644 --- a/openstackclient/tests/functional/base.py +++ b/openstackclient/tests/functional/base.py @@ -57,6 +57,11 @@ def get_openstack_configuration_value(cls, configuration): opts = cls.get_opts([configuration]) return cls.openstack('configuration show ' + opts) + @classmethod + def get_openstack_extention_names(cls): + opts = cls.get_opts(['Name']) + return cls.openstack('extension list ' + opts) + @classmethod def get_opts(cls, fields, format='value'): return ' -f {0} {1}'.format(format, diff --git a/openstackclient/tests/functional/network/v2/test_network_segment.py b/openstackclient/tests/functional/network/v2/test_network_segment.py index f871e88e18..de5aef9650 100644 --- a/openstackclient/tests/functional/network/v2/test_network_segment.py +++ b/openstackclient/tests/functional/network/v2/test_network_segment.py @@ -10,20 +10,18 @@ # License for the specific language governing permissions and limitations # under the License. -import testtools import uuid from openstackclient.tests.functional import base -# NOTE(rtheis): Routed networks is still a WIP and not enabled by default. -@testtools.skip("bp/routed-networks") class NetworkSegmentTests(base.TestCase): """Functional tests for network segment. """ NETWORK_NAME = uuid.uuid4().hex PHYSICAL_NETWORK_NAME = uuid.uuid4().hex NETWORK_SEGMENT_ID = None NETWORK_ID = None + NETWORK_SEGMENT_EXTENSION = None @classmethod def setUpClass(cls): @@ -32,29 +30,75 @@ def setUpClass(cls): raw_output = cls.openstack('network create ' + cls.NETWORK_NAME + opts) cls.NETWORK_ID = raw_output.strip('\n') - # Get the segment for the network. - opts = cls.get_opts(['ID', 'Network']) - raw_output = cls.openstack('--os-beta-command ' - 'network segment list ' - ' --network ' + cls.NETWORK_NAME + - ' ' + opts) - raw_output_row = raw_output.split('\n')[0] - cls.NETWORK_SEGMENT_ID = raw_output_row.split(' ')[0] + # NOTE(rtheis): The segment extension is not yet enabled by default. + # Skip the tests if not enabled. + extensions = cls.get_openstack_extention_names() + if 'Segment' in extensions: + cls.NETWORK_SEGMENT_EXTENSION = 'Segment' + + if cls.NETWORK_SEGMENT_EXTENSION: + # Get the segment for the network. + opts = cls.get_opts(['ID', 'Network']) + raw_output = cls.openstack('--os-beta-command ' + 'network segment list ' + ' --network ' + cls.NETWORK_NAME + + ' ' + opts) + raw_output_row = raw_output.split('\n')[0] + cls.NETWORK_SEGMENT_ID = raw_output_row.split(' ')[0] @classmethod def tearDownClass(cls): raw_output = cls.openstack('network delete ' + cls.NETWORK_NAME) cls.assertOutput('', raw_output) + def test_network_segment_create_delete(self): + if self.NETWORK_SEGMENT_EXTENSION: + opts = self.get_opts(['id']) + raw_output = self.openstack( + '--os-beta-command' + + ' network segment create --network ' + self.NETWORK_ID + + ' --network-type geneve ' + + ' --segment 2055 test_segment ' + opts + ) + network_segment_id = raw_output.strip('\n') + raw_output = self.openstack('--os-beta-command ' + + 'network segment delete ' + + network_segment_id) + self.assertOutput('', raw_output) + else: + self.skipTest('Segment extension disabled') + def test_network_segment_list(self): - opts = self.get_opts(['ID']) - raw_output = self.openstack('--os-beta-command ' - 'network segment list' + opts) - self.assertIn(self.NETWORK_SEGMENT_ID, raw_output) + if self.NETWORK_SEGMENT_EXTENSION: + opts = self.get_opts(['ID']) + raw_output = self.openstack('--os-beta-command ' + 'network segment list' + opts) + self.assertIn(self.NETWORK_SEGMENT_ID, raw_output) + else: + self.skipTest('Segment extension disabled') + + def test_network_segment_set(self): + if self.NETWORK_SEGMENT_EXTENSION: + new_description = 'new_description' + raw_output = self.openstack('--os-beta-command ' + 'network segment set ' + + '--description ' + new_description + + ' ' + self.NETWORK_SEGMENT_ID) + self.assertOutput('', raw_output) + opts = self.get_opts(['description']) + raw_output = self.openstack('--os-beta-command ' + 'network segment show ' + + self.NETWORK_SEGMENT_ID + opts) + self.assertEqual(new_description + "\n", raw_output) + else: + self.skipTest('Segment extension disabled') def test_network_segment_show(self): - opts = self.get_opts(['network_id']) - raw_output = self.openstack('--os-beta-command ' - 'network segment show ' + - self.NETWORK_SEGMENT_ID + opts) - self.assertEqual(self.NETWORK_ID + "\n", raw_output) + if self.NETWORK_SEGMENT_EXTENSION: + opts = self.get_opts(['network_id']) + raw_output = self.openstack('--os-beta-command ' + 'network segment show ' + + self.NETWORK_SEGMENT_ID + opts) + self.assertEqual(self.NETWORK_ID + "\n", raw_output) + else: + self.skipTest('Segment extension disabled') diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index ed30bad343..d33b84c52d 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -362,11 +362,14 @@ def create_one_network_segment(attrs=None): attrs = attrs or {} # Set default attributes. + fake_uuid = uuid.uuid4().hex network_segment_attrs = { - 'id': 'network-segment-id-' + uuid.uuid4().hex, - 'network_id': 'network-id-' + uuid.uuid4().hex, + 'description': 'network-segment-description-' + fake_uuid, + 'id': 'network-segment-id-' + fake_uuid, + 'name': 'network-segment-name-' + fake_uuid, + 'network_id': 'network-id-' + fake_uuid, 'network_type': 'vlan', - 'physical_network': 'physical-network-name-' + uuid.uuid4().hex, + 'physical_network': 'physical-network-name-' + fake_uuid, 'segmentation_id': 1024, } diff --git a/openstackclient/tests/unit/network/v2/test_network_segment.py b/openstackclient/tests/unit/network/v2/test_network_segment.py index b9fce0784f..3e755e0730 100644 --- a/openstackclient/tests/unit/network/v2/test_network_segment.py +++ b/openstackclient/tests/unit/network/v2/test_network_segment.py @@ -12,6 +12,7 @@ # import mock +from mock import call from osc_lib import exceptions @@ -32,6 +33,243 @@ def setUp(self): self.network = self.app.client_manager.network +class TestCreateNetworkSegment(TestNetworkSegment): + + # The network segment to create along with associated network. + _network_segment = \ + network_fakes.FakeNetworkSegment.create_one_network_segment() + _network = network_fakes.FakeNetwork.create_one_network({ + 'id': _network_segment.network_id, + }) + + columns = ( + 'description', + 'id', + 'name', + 'network_id', + 'network_type', + 'physical_network', + 'segmentation_id', + ) + + data = ( + _network_segment.description, + _network_segment.id, + _network_segment.name, + _network_segment.network_id, + _network_segment.network_type, + _network_segment.physical_network, + _network_segment.segmentation_id, + ) + + def setUp(self): + super(TestCreateNetworkSegment, self).setUp() + + self.network.create_segment = mock.Mock( + return_value=self._network_segment + ) + self.network.find_network = mock.Mock(return_value=self._network) + + # Get the command object to test + self.cmd = network_segment.CreateNetworkSegment( + self.app, + self.namespace + ) + + def test_create_no_options(self): + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, [], []) + + def test_create_no_beta_commands(self): + arglist = [ + '--network', self._network_segment.network_id, + '--network-type', self._network_segment.network_type, + self._network_segment.name, + ] + verifylist = [ + ('network', self._network_segment.network_id), + ('network_type', self._network_segment.network_type), + ('name', self._network_segment.name), + ] + self.app.options.os_beta_command = False + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + + def test_create_invalid_network_type(self): + arglist = [ + '--network', self._network_segment.network_id, + '--network-type', 'foo', + self._network_segment.name, + ] + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, []) + + def test_create_minimum_options(self): + arglist = [ + '--network', self._network_segment.network_id, + '--network-type', self._network_segment.network_type, + self._network_segment.name, + ] + verifylist = [ + ('network', self._network_segment.network_id), + ('network_type', self._network_segment.network_type), + ('name', self._network_segment.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.find_network.assert_called_once_with( + self._network_segment.network_id, + ignore_missing=False + ) + self.network.create_segment.assert_called_once_with(**{ + 'network_id': self._network_segment.network_id, + 'network_type': self._network_segment.network_type, + 'name': self._network_segment.name, + }) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_all_options(self): + arglist = [ + '--description', self._network_segment.description, + '--network', self._network_segment.network_id, + '--network-type', self._network_segment.network_type, + '--physical-network', self._network_segment.physical_network, + '--segment', str(self._network_segment.segmentation_id), + self._network_segment.name, + ] + verifylist = [ + ('description', self._network_segment.description), + ('network', self._network_segment.network_id), + ('network_type', self._network_segment.network_type), + ('physical_network', self._network_segment.physical_network), + ('segment', self._network_segment.segmentation_id), + ('name', self._network_segment.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.find_network.assert_called_once_with( + self._network_segment.network_id, + ignore_missing=False + ) + self.network.create_segment.assert_called_once_with(**{ + 'description': self._network_segment.description, + 'network_id': self._network_segment.network_id, + 'network_type': self._network_segment.network_type, + 'physical_network': self._network_segment.physical_network, + 'segmentation_id': self._network_segment.segmentation_id, + 'name': self._network_segment.name, + }) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestDeleteNetworkSegment(TestNetworkSegment): + + # The network segments to delete. + _network_segments = \ + network_fakes.FakeNetworkSegment.create_network_segments() + + def setUp(self): + super(TestDeleteNetworkSegment, self).setUp() + + self.network.delete_segment = mock.Mock(return_value=None) + self.network.find_segment = mock.MagicMock( + side_effect=self._network_segments + ) + + # Get the command object to test + self.cmd = network_segment.DeleteNetworkSegment( + self.app, + self.namespace + ) + + def test_delete_no_beta_commands(self): + arglist = [ + self._network_segments[0].id, + ] + verifylist = [ + ('network_segment', [self._network_segments[0].id]), + ] + self.app.options.os_beta_command = False + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + + def test_delete(self): + arglist = [ + self._network_segments[0].id, + ] + verifylist = [ + ('network_segment', [self._network_segments[0].id]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.network.delete_segment.assert_called_once_with( + self._network_segments[0] + ) + self.assertIsNone(result) + + def test_delete_multiple(self): + arglist = [] + for _network_segment in self._network_segments: + arglist.append(_network_segment.id) + verifylist = [ + ('network_segment', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [] + for _network_segment in self._network_segments: + calls.append(call(_network_segment)) + self.network.delete_segment.assert_has_calls(calls) + self.assertIsNone(result) + + def test_delete_multiple_with_exception(self): + arglist = [ + self._network_segments[0].id, + 'doesnotexist' + ] + verifylist = [ + ('network_segment', [self._network_segments[0].id, + 'doesnotexist']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self._network_segments[0], + exceptions.CommandError] + self.network.find_segment = ( + mock.MagicMock(side_effect=find_mock_result) + ) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 network segments failed to delete.', + str(e)) + + self.network.find_segment.assert_any_call( + self._network_segments[0].id, ignore_missing=False) + self.network.find_segment.assert_any_call( + 'doesnotexist', ignore_missing=False) + self.network.delete_segment.assert_called_once_with( + self._network_segments[0] + ) + + class TestListNetworkSegment(TestNetworkSegment): _network = network_fakes.FakeNetwork.create_one_network() _network_segments = \ @@ -39,6 +277,7 @@ class TestListNetworkSegment(TestNetworkSegment): columns = ( 'ID', + 'Name', 'Network', 'Network Type', 'Segment', @@ -51,6 +290,7 @@ class TestListNetworkSegment(TestNetworkSegment): for _network_segment in _network_segments: data.append(( _network_segment.id, + _network_segment.name, _network_segment.network_id, _network_segment.network_type, _network_segment.segmentation_id, @@ -60,6 +300,7 @@ class TestListNetworkSegment(TestNetworkSegment): for _network_segment in _network_segments: data_long.append(( _network_segment.id, + _network_segment.name, _network_segment.network_id, _network_segment.network_type, _network_segment.segmentation_id, @@ -131,6 +372,78 @@ def test_list_network(self): self.assertEqual(self.data, list(data)) +class TestSetNetworkSegment(TestNetworkSegment): + + # The network segment to show. + _network_segment = \ + network_fakes.FakeNetworkSegment.create_one_network_segment() + + def setUp(self): + super(TestSetNetworkSegment, self).setUp() + + self.network.find_segment = mock.Mock( + return_value=self._network_segment + ) + self.network.update_segment = mock.Mock( + return_value=self._network_segment + ) + + # Get the command object to test + self.cmd = network_segment.SetNetworkSegment(self.app, self.namespace) + + def test_set_no_beta_commands(self): + arglist = [ + self._network_segment.id, + ] + verifylist = [ + ('network_segment', self._network_segment.id), + ] + self.app.options.os_beta_command = False + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + + def test_set_no_options(self): + arglist = [ + self._network_segment.id, + ] + verifylist = [ + ('network_segment', self._network_segment.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.network.update_segment.assert_called_once_with( + self._network_segment, **{} + ) + self.assertIsNone(result) + + def test_set_all_options(self): + arglist = [ + '--description', 'new description', + '--name', 'new name', + self._network_segment.id, + ] + verifylist = [ + ('description', 'new description'), + ('name', 'new name'), + ('network_segment', self._network_segment.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = { + 'description': 'new description', + 'name': 'new name', + } + self.network.update_segment.assert_called_once_with( + self._network_segment, **attrs + ) + self.assertIsNone(result) + + class TestShowNetworkSegment(TestNetworkSegment): # The network segment to show. @@ -138,7 +451,9 @@ class TestShowNetworkSegment(TestNetworkSegment): network_fakes.FakeNetworkSegment.create_one_network_segment() columns = ( + 'description', 'id', + 'name', 'network_id', 'network_type', 'physical_network', @@ -146,7 +461,9 @@ class TestShowNetworkSegment(TestNetworkSegment): ) data = ( + _network_segment.description, _network_segment.id, + _network_segment.name, _network_segment.network_id, _network_segment.network_type, _network_segment.physical_network, diff --git a/releasenotes/notes/bp-routed-networks-3b502faa5cd96807.yaml b/releasenotes/notes/bp-routed-networks-3b502faa5cd96807.yaml new file mode 100644 index 0000000000..b055479368 --- /dev/null +++ b/releasenotes/notes/bp-routed-networks-3b502faa5cd96807.yaml @@ -0,0 +1,6 @@ +--- +features: + - Add ``network segment create``, ``network segment delete`` and + ``network segment set`` commands. These are beta commands and subject to + change. Use global option ``--os-beta-command`` to enable these commands. + [Blueprint `routed-networks `_] diff --git a/setup.cfg b/setup.cfg index 7af6e0c7e2..663741d0ec 100644 --- a/setup.cfg +++ b/setup.cfg @@ -372,7 +372,10 @@ openstack.network.v2 = network_rbac_set = openstackclient.network.v2.network_rbac:SetNetworkRBAC network_rbac_show = openstackclient.network.v2.network_rbac:ShowNetworkRBAC + network_segment_create = openstackclient.network.v2.network_segment:CreateNetworkSegment + network_segment_delete = openstackclient.network.v2.network_segment:DeleteNetworkSegment network_segment_list = openstackclient.network.v2.network_segment:ListNetworkSegment + network_segment_set = openstackclient.network.v2.network_segment:SetNetworkSegment network_segment_show = openstackclient.network.v2.network_segment:ShowNetworkSegment port_create = openstackclient.network.v2.port:CreatePort From 676a0e9696ba7550132e4501bdbf3160608faed6 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Fri, 9 Sep 2016 22:23:20 -0400 Subject: [PATCH 1204/3095] unwedge the gate since changing the infra job to call the new bash script location, the functional tests now have a permission error: py.error.EACCES: [Permission denied]: mkdir('/opt/stack/new/python-openstackclient/.tox',) Change-Id: Ibe7057c99feac952d80156fb996d0193e1b0e497 --- openstackclient/tests/functional/post_test_hook.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openstackclient/tests/functional/post_test_hook.sh b/openstackclient/tests/functional/post_test_hook.sh index e555470d17..b7a39cfed9 100755 --- a/openstackclient/tests/functional/post_test_hook.sh +++ b/openstackclient/tests/functional/post_test_hook.sh @@ -18,16 +18,16 @@ function generate_testr_results { fi } -OPENSTACKCLIENT_DIR=$(cd $(dirname "$0") && pwd) +export OPENSTACKCLIENT_DIR="$BASE/new/python-openstackclient" sudo chown -R jenkins:stack $OPENSTACKCLIENT_DIR +# Go to the openstackclient dir +cd $OPENSTACKCLIENT_DIR + # Run tests echo "Running openstackclient functional test suite" set +e -# Go to the openstackclient dir -cd $OPENSTACKCLIENT_DIR - # Source environment variables to kick things off source ~stack/devstack/openrc admin admin echo 'Running tests with:' From 60e815a9899b1ed7065a7659acc3ba21c393d419 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Fri, 9 Sep 2016 14:35:47 -0700 Subject: [PATCH 1205/3095] remove duplicate unit test looks like this test snuck in during the refactor: $ diff openstackclient/tests/volume/v1/test_service.py \ openstackclient/tests/unit/volume/v1/test_service.py 17c17 < from openstackclient.tests.volume.v1 import fakes as service_fakes --- > from openstackclient.tests.unit.volume.v1 import fakes as service_fakes Change-Id: I769b2d39f28cfaf65e4027c785b0ddbbefa26aea --- .../tests/volume/v1/test_service.py | 286 ------------------ 1 file changed, 286 deletions(-) delete mode 100644 openstackclient/tests/volume/v1/test_service.py diff --git a/openstackclient/tests/volume/v1/test_service.py b/openstackclient/tests/volume/v1/test_service.py deleted file mode 100644 index 273a1156b5..0000000000 --- a/openstackclient/tests/volume/v1/test_service.py +++ /dev/null @@ -1,286 +0,0 @@ -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - -from osc_lib import exceptions - -from openstackclient.tests.volume.v1 import fakes as service_fakes -from openstackclient.volume.v1 import service - - -class TestService(service_fakes.TestVolumev1): - - def setUp(self): - super(TestService, self).setUp() - - # Get a shortcut to the ServiceManager Mock - self.service_mock = self.app.client_manager.volume.services - self.service_mock.reset_mock() - - -class TestServiceList(TestService): - - # The service to be listed - services = service_fakes.FakeService.create_one_service() - - def setUp(self): - super(TestServiceList, self).setUp() - - self.service_mock.list.return_value = [self.services] - - # Get the command object to test - self.cmd = service.ListService(self.app, None) - - def test_service_list(self): - arglist = [ - '--host', self.services.host, - '--service', self.services.binary, - ] - verifylist = [ - ('host', self.services.host), - ('service', self.services.binary), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # In base command class Lister in cliff, abstract method take_action() - # returns a tuple containing the column names and an iterable - # containing the data to be listed. - columns, data = self.cmd.take_action(parsed_args) - - expected_columns = [ - 'Binary', - 'Host', - 'Zone', - 'Status', - 'State', - 'Updated At', - ] - - # confirming if all expected columns are present in the result. - self.assertEqual(expected_columns, columns) - - datalist = (( - self.services.binary, - self.services.host, - self.services.zone, - self.services.status, - self.services.state, - self.services.updated_at, - ), ) - - # confirming if all expected values are present in the result. - self.assertEqual(datalist, tuple(data)) - - # checking if proper call was made to list services - self.service_mock.list.assert_called_with( - self.services.host, - self.services.binary, - ) - - # checking if prohibited columns are present in output - self.assertNotIn("Disabled Reason", columns) - self.assertNotIn(self.services.disabled_reason, - tuple(data)) - - def test_service_list_with_long_option(self): - arglist = [ - '--host', self.services.host, - '--service', self.services.binary, - '--long' - ] - verifylist = [ - ('host', self.services.host), - ('service', self.services.binary), - ('long', True) - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # In base command class Lister in cliff, abstract method take_action() - # returns a tuple containing the column names and an iterable - # containing the data to be listed. - columns, data = self.cmd.take_action(parsed_args) - - expected_columns = [ - 'Binary', - 'Host', - 'Zone', - 'Status', - 'State', - 'Updated At', - 'Disabled Reason' - ] - - # confirming if all expected columns are present in the result. - self.assertEqual(expected_columns, columns) - - datalist = (( - self.services.binary, - self.services.host, - self.services.zone, - self.services.status, - self.services.state, - self.services.updated_at, - self.services.disabled_reason, - ), ) - - # confirming if all expected values are present in the result. - self.assertEqual(datalist, tuple(data)) - - self.service_mock.list.assert_called_with( - self.services.host, - self.services.binary, - ) - - -class TestServiceSet(TestService): - - service = service_fakes.FakeService.create_one_service() - - def setUp(self): - super(TestServiceSet, self).setUp() - - self.service_mock.enable.return_value = self.service - self.service_mock.disable.return_value = self.service - self.service_mock.disable_log_reason.return_value = self.service - - self.cmd = service.SetService(self.app, None) - - def test_service_set_nothing(self): - arglist = [ - self.service.host, - self.service.binary, - ] - verifylist = [ - ('host', self.service.host), - ('service', self.service.binary), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) - - self.service_mock.enable.assert_not_called() - self.service_mock.disable.assert_not_called() - self.service_mock.disable_log_reason.assert_not_called() - self.assertIsNone(result) - - def test_service_set_enable(self): - arglist = [ - '--enable', - self.service.host, - self.service.binary, - ] - verifylist = [ - ('enable', True), - ('host', self.service.host), - ('service', self.service.binary), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - self.service_mock.enable.assert_called_with( - self.service.host, - self.service.binary - ) - self.service_mock.disable.assert_not_called() - self.service_mock.disable_log_reason.assert_not_called() - self.assertIsNone(result) - - def test_service_set_disable(self): - arglist = [ - '--disable', - self.service.host, - self.service.binary, - ] - verifylist = [ - ('disable', True), - ('host', self.service.host), - ('service', self.service.binary), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - self.service_mock.disable.assert_called_with( - self.service.host, - self.service.binary - ) - self.service_mock.enable.assert_not_called() - self.service_mock.disable_log_reason.assert_not_called() - self.assertIsNone(result) - - def test_service_set_disable_with_reason(self): - reason = 'earthquake' - arglist = [ - '--disable', - '--disable-reason', reason, - self.service.host, - self.service.binary, - ] - verifylist = [ - ('disable', True), - ('disable_reason', reason), - ('host', self.service.host), - ('service', self.service.binary), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - self.service_mock.disable_log_reason.assert_called_with( - self.service.host, - self.service.binary, - reason - ) - self.assertIsNone(result) - - def test_service_set_only_with_disable_reason(self): - reason = 'earthquake' - arglist = [ - '--disable-reason', reason, - self.service.host, - self.service.binary, - ] - verifylist = [ - ('disable_reason', reason), - ('host', self.service.host), - ('service', self.service.binary), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - try: - self.cmd.take_action(parsed_args) - self.fail("CommandError should be raised.") - except exceptions.CommandError as e: - self.assertEqual("Cannot specify option --disable-reason without " - "--disable specified.", str(e)) - - def test_service_set_enable_with_disable_reason(self): - reason = 'earthquake' - arglist = [ - '--enable', - '--disable-reason', reason, - self.service.host, - self.service.binary, - ] - verifylist = [ - ('enable', True), - ('disable_reason', reason), - ('host', self.service.host), - ('service', self.service.binary), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - try: - self.cmd.take_action(parsed_args) - self.fail("CommandError should be raised.") - except exceptions.CommandError as e: - self.assertEqual("Cannot specify option --disable-reason without " - "--disable specified.", str(e)) From 6adea682749f4068f47aae8c8356e2acc79554b0 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Mon, 5 Sep 2016 22:22:51 -0700 Subject: [PATCH 1206/3095] remove the old post_test_hook Use the new location in openstackclient.tests.functional. Depends-On: I49d54f009021d65c1ae49faf6b3f0a7acdadd7b3 Change-Id: I53b8fcc21c5f3638fd334245036c3d99bcaf9012 --- post_test_hook.sh | 43 ------------------------------------------- 1 file changed, 43 deletions(-) delete mode 100755 post_test_hook.sh diff --git a/post_test_hook.sh b/post_test_hook.sh deleted file mode 100755 index e555470d17..0000000000 --- a/post_test_hook.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash - -# This is a script that kicks off a series of functional tests against an -# OpenStack cloud. It will attempt to create an instance if one is not -# available. Do not run this script unless you know what you're doing. -# For more information refer to: -# http://docs.openstack.org/developer/python-openstackclient/ - -function generate_testr_results { - if [ -f .testrepository/0 ]; then - sudo .tox/functional/bin/testr last --subunit > $WORKSPACE/testrepository.subunit - sudo mv $WORKSPACE/testrepository.subunit $BASE/logs/testrepository.subunit - sudo .tox/functional/bin/subunit2html $BASE/logs/testrepository.subunit $BASE/logs/testr_results.html - sudo gzip -9 $BASE/logs/testrepository.subunit - sudo gzip -9 $BASE/logs/testr_results.html - sudo chown jenkins:jenkins $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz - sudo chmod a+r $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz - fi -} - -OPENSTACKCLIENT_DIR=$(cd $(dirname "$0") && pwd) -sudo chown -R jenkins:stack $OPENSTACKCLIENT_DIR - -# Run tests -echo "Running openstackclient functional test suite" -set +e - -# Go to the openstackclient dir -cd $OPENSTACKCLIENT_DIR - -# Source environment variables to kick things off -source ~stack/devstack/openrc admin admin -echo 'Running tests with:' -env | grep OS - -# Preserve env for OS_ credentials -sudo -E -H -u jenkins tox -efunctional -EXIT_CODE=$? -set -e - -# Collect and parse result -generate_testr_results -exit $EXIT_CODE From cb6c11b0a87ba99e01eff52204b2406e8517aeaa Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Tue, 6 Sep 2016 15:15:01 +0800 Subject: [PATCH 1207/3095] Multi REST API calls error handling of "volume unset" command Support multi REST API calls error handling for "volume unset" command follow the rule in doc/source/command-errors.rst. Also add a unit test for testing the error handling Change-Id: I2de7a7bd5a7a5e39817ed5cf6952abf4afba75e4 --- .../tests/unit/volume/v2/test_volume.py | 27 +++++++++++++++++++ openstackclient/volume/v2/volume.py | 22 ++++++++++++--- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/openstackclient/tests/unit/volume/v2/test_volume.py b/openstackclient/tests/unit/volume/v2/test_volume.py index 66f8f74d42..e9f1629e81 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume.py +++ b/openstackclient/tests/unit/volume/v2/test_volume.py @@ -964,3 +964,30 @@ def test_volume_unset_image_property(self): self.volumes_mock.delete_image_metadata.assert_called_with( self.new_volume.id, parsed_args_unset.image_property) + + def test_volume_unset_image_property_fail(self): + self.volumes_mock.delete_image_metadata.side_effect = ( + exceptions.CommandError()) + arglist = [ + '--image-property', 'Alpha', + '--property', 'Beta', + self.new_volume.id, + ] + verifylist = [ + ('image_property', ['Alpha']), + ('property', ['Beta']), + ('volume', self.new_volume.id), + ] + parsed_args = self.check_parser( + self.cmd_unset, arglist, verifylist) + + try: + self.cmd_unset.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('One or more of the unset operations failed', + str(e)) + self.volumes_mock.delete_image_metadata.assert_called_with( + self.new_volume.id, parsed_args.image_property) + self.volumes_mock.delete_metadata.assert_called_with( + self.new_volume.id, parsed_args.property) diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index bd201e0041..0f85b5d4a0 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -511,9 +511,23 @@ def take_action(self, parsed_args): volume = utils.find_resource( volume_client.volumes, parsed_args.volume) + result = 0 if parsed_args.property: - volume_client.volumes.delete_metadata( - volume.id, parsed_args.property) + try: + volume_client.volumes.delete_metadata( + volume.id, parsed_args.property) + except Exception as e: + LOG.error(_("Failed to unset volume property: %s"), e) + result += 1 + if parsed_args.image_property: - volume_client.volumes.delete_image_metadata( - volume.id, parsed_args.image_property) + try: + volume_client.volumes.delete_image_metadata( + volume.id, parsed_args.image_property) + except Exception as e: + LOG.error(_("Failed to unset image property: %s"), e) + result += 1 + + if result > 0: + raise exceptions.CommandError(_("One or more of the " + "unset operations failed")) From af81a92c373a5c4e1bec751b282eda8081cb3063 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Tue, 23 Aug 2016 16:09:52 +0800 Subject: [PATCH 1208/3095] Support error handling for delete commands in volume v1 Some delete commands in volume v1 support multi delete but do not support error handling, this patch fixes them, and this patch also refactor (or add new) unit tests for some delete commands in volume v1. Change-Id: Ia8177698f8733cfe75ea0ff00eee8fdc0820f62e --- openstackclient/tests/unit/volume/v1/fakes.py | 156 ++++++++++++++++++ .../tests/unit/volume/v1/test_qos_specs.py | 77 +++++++-- .../tests/unit/volume/v1/test_volume.py | 103 ++++++++++++ openstackclient/volume/v1/backup.py | 26 ++- openstackclient/volume/v1/qos_specs.py | 26 ++- openstackclient/volume/v1/snapshot.py | 27 ++- openstackclient/volume/v1/volume.py | 29 +++- 7 files changed, 411 insertions(+), 33 deletions(-) diff --git a/openstackclient/tests/unit/volume/v1/fakes.py b/openstackclient/tests/unit/volume/v1/fakes.py index c6fee7d196..5c1f3b12f5 100644 --- a/openstackclient/tests/unit/volume/v1/fakes.py +++ b/openstackclient/tests/unit/volume/v1/fakes.py @@ -13,7 +13,10 @@ # under the License. # +import copy import mock +import random +import uuid from openstackclient.tests.unit import fakes from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes @@ -234,6 +237,159 @@ def get_services(services=None, count=2): return mock.MagicMock(side_effect=services) +class FakeQos(object): + """Fake one or more Qos specification.""" + + @staticmethod + def create_one_qos(attrs=None): + """Create a fake Qos specification. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object with id, name, consumer, etc. + """ + attrs = attrs or {} + + # Set default attributes. + qos_info = { + "id": 'qos-id-' + uuid.uuid4().hex, + "name": 'qos-name-' + uuid.uuid4().hex, + "consumer": 'front-end', + "specs": {"foo": "bar", "iops": "9001"}, + } + + # Overwrite default attributes. + qos_info.update(attrs) + + qos = fakes.FakeResource( + info=copy.deepcopy(qos_info), + loaded=True) + return qos + + @staticmethod + def create_qoses(attrs=None, count=2): + """Create multiple fake Qos specifications. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of Qos specifications to fake + :return: + A list of FakeResource objects faking the Qos specifications + """ + qoses = [] + for i in range(0, count): + qos = FakeQos.create_one_qos(attrs) + qoses.append(qos) + + return qoses + + @staticmethod + def get_qoses(qoses=None, count=2): + """Get an iterable MagicMock object with a list of faked qoses. + + If qoses list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List volumes: + A list of FakeResource objects faking qoses + :param Integer count: + The number of qoses to be faked + :return + An iterable Mock object with side_effect set to a list of faked + qoses + """ + if qoses is None: + qoses = FakeQos.create_qoses(count) + + return mock.MagicMock(side_effect=qoses) + + +class FakeVolume(object): + """Fake one or more volumes.""" + + @staticmethod + def create_one_volume(attrs=None): + """Create a fake volume. + + :param Dictionary attrs: + A dictionary with all attributes of volume + :return: + A FakeResource object with id, name, status, etc. + """ + attrs = attrs or {} + + # Set default attribute + volume_info = { + 'id': 'volume-id' + uuid.uuid4().hex, + 'name': 'volume-name' + uuid.uuid4().hex, + 'description': 'description' + uuid.uuid4().hex, + 'status': random.choice(['available', 'in_use']), + 'size': random.randint(1, 20), + 'volume_type': + random.choice(['fake_lvmdriver-1', 'fake_lvmdriver-2']), + 'bootable': + random.randint(0, 1), + 'metadata': { + 'key' + uuid.uuid4().hex: 'val' + uuid.uuid4().hex, + 'key' + uuid.uuid4().hex: 'val' + uuid.uuid4().hex, + 'key' + uuid.uuid4().hex: 'val' + uuid.uuid4().hex}, + 'snapshot_id': random.randint(1, 5), + 'availability_zone': 'zone' + uuid.uuid4().hex, + 'attachments': [{ + 'device': '/dev/' + uuid.uuid4().hex, + 'server_id': uuid.uuid4().hex, + }, ], + } + + # Overwrite default attributes if there are some attributes set + volume_info.update(attrs) + + volume = fakes.FakeResource( + None, + volume_info, + loaded=True) + return volume + + @staticmethod + def create_volumes(attrs=None, count=2): + """Create multiple fake volumes. + + :param Dictionary attrs: + A dictionary with all attributes of volume + :param Integer count: + The number of volumes to be faked + :return: + A list of FakeResource objects + """ + volumes = [] + for n in range(0, count): + volumes.append(FakeVolume.create_one_volume(attrs)) + + return volumes + + @staticmethod + def get_volumes(volumes=None, count=2): + """Get an iterable MagicMock object with a list of faked volumes. + + If volumes list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List volumes: + A list of FakeResource objects faking volumes + :param Integer count: + The number of volumes to be faked + :return + An iterable Mock object with side_effect set to a list of faked + volumes + """ + if volumes is None: + volumes = FakeVolume.create_volumes(count) + + return mock.MagicMock(side_effect=volumes) + + class FakeImagev1Client(object): def __init__(self, **kwargs): diff --git a/openstackclient/tests/unit/volume/v1/test_qos_specs.py b/openstackclient/tests/unit/volume/v1/test_qos_specs.py index 7b87ccb3d1..81680ab4ef 100644 --- a/openstackclient/tests/unit/volume/v1/test_qos_specs.py +++ b/openstackclient/tests/unit/volume/v1/test_qos_specs.py @@ -14,7 +14,10 @@ # import copy +import mock +from mock import call +from osc_lib import exceptions from osc_lib import utils from openstackclient.tests.unit import fakes @@ -188,62 +191,106 @@ def test_qos_create_with_properties(self): class TestQosDelete(TestQos): + qos_specs = volume_fakes.FakeQos.create_qoses(count=2) + def setUp(self): super(TestQosDelete, self).setUp() - self.qos_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.QOS), - loaded=True, - ) - + self.qos_mock.get = ( + volume_fakes.FakeQos.get_qoses(self.qos_specs)) # Get the command object to test self.cmd = qos_specs.DeleteQos(self.app, None) def test_qos_delete_with_id(self): arglist = [ - volume_fakes.qos_id + self.qos_specs[0].id ] verifylist = [ - ('qos_specs', [volume_fakes.qos_id]) + ('qos_specs', [self.qos_specs[0].id]) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.qos_mock.delete.assert_called_with(volume_fakes.qos_id, False) + self.qos_mock.delete.assert_called_with(self.qos_specs[0].id, False) self.assertIsNone(result) def test_qos_delete_with_name(self): arglist = [ - volume_fakes.qos_name + self.qos_specs[0].name ] verifylist = [ - ('qos_specs', [volume_fakes.qos_name]) + ('qos_specs', [self.qos_specs[0].name]) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.qos_mock.delete.assert_called_with(volume_fakes.qos_id, False) + self.qos_mock.delete.assert_called_with(self.qos_specs[0].id, False) self.assertIsNone(result) def test_qos_delete_with_force(self): arglist = [ '--force', - volume_fakes.qos_id + self.qos_specs[0].id ] verifylist = [ ('force', True), - ('qos_specs', [volume_fakes.qos_id]) + ('qos_specs', [self.qos_specs[0].id]) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.qos_mock.delete.assert_called_with(volume_fakes.qos_id, True) + self.qos_mock.delete.assert_called_with(self.qos_specs[0].id, True) self.assertIsNone(result) + def test_delete_multiple_qoses(self): + arglist = [] + for q in self.qos_specs: + arglist.append(q.id) + verifylist = [ + ('qos_specs', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + calls = [] + for q in self.qos_specs: + calls.append(call(q.id, False)) + self.qos_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_delete_multiple_qoses_with_exception(self): + arglist = [ + self.qos_specs[0].id, + 'unexist_qos', + ] + verifylist = [ + ('qos_specs', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self.qos_specs[0], exceptions.CommandError] + with mock.patch.object(utils, 'find_resource', + side_effect=find_mock_result) as find_mock: + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual( + '1 of 2 QoS specifications failed to delete.', str(e)) + + find_mock.assert_any_call(self.qos_mock, self.qos_specs[0].id) + find_mock.assert_any_call(self.qos_mock, 'unexist_qos') + + self.assertEqual(2, find_mock.call_count) + self.qos_mock.delete.assert_called_once_with( + self.qos_specs[0].id, False + ) + class TestQosDisassociate(TestQos): diff --git a/openstackclient/tests/unit/volume/v1/test_volume.py b/openstackclient/tests/unit/volume/v1/test_volume.py index f90566fd5e..6a860fd0f8 100644 --- a/openstackclient/tests/unit/volume/v1/test_volume.py +++ b/openstackclient/tests/unit/volume/v1/test_volume.py @@ -15,6 +15,10 @@ import copy import mock +from mock import call + +from osc_lib import exceptions +from osc_lib import utils from openstackclient.tests.unit import fakes from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes @@ -43,6 +47,14 @@ def setUp(self): self.images_mock = self.app.client_manager.image.images self.images_mock.reset_mock() + def setup_volumes_mock(self, count): + volumes = volume_fakes.FakeVolume.create_volumes(count=count) + + self.volumes_mock.get = volume_fakes.FakeVolume.get_volumes( + volumes, + 0) + return volumes + # TODO(dtroyer): The volume create tests are incomplete, only the minimal # options and the options that require additional processing @@ -397,6 +409,97 @@ def test_volume_create_image_name(self): self.assertEqual(self.datalist, data) +class TestVolumeDelete(TestVolume): + + def setUp(self): + super(TestVolumeDelete, self).setUp() + + self.volumes_mock.delete.return_value = None + + # Get the command object to mock + self.cmd = volume.DeleteVolume(self.app, None) + + def test_volume_delete_one_volume(self): + volumes = self.setup_volumes_mock(count=1) + + arglist = [ + volumes[0].id + ] + verifylist = [ + ("force", False), + ("volumes", [volumes[0].id]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.volumes_mock.delete.assert_called_once_with(volumes[0].id) + self.assertIsNone(result) + + def test_volume_delete_multi_volumes(self): + volumes = self.setup_volumes_mock(count=3) + + arglist = [v.id for v in volumes] + verifylist = [ + ('force', False), + ('volumes', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [call(v.id) for v in volumes] + self.volumes_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_volume_delete_multi_volumes_with_exception(self): + volumes = self.setup_volumes_mock(count=2) + + arglist = [ + volumes[0].id, + 'unexist_volume', + ] + verifylist = [ + ('force', False), + ('volumes', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [volumes[0], exceptions.CommandError] + with mock.patch.object(utils, 'find_resource', + side_effect=find_mock_result) as find_mock: + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 volumes failed to delete.', + str(e)) + + find_mock.assert_any_call(self.volumes_mock, volumes[0].id) + find_mock.assert_any_call(self.volumes_mock, 'unexist_volume') + + self.assertEqual(2, find_mock.call_count) + self.volumes_mock.delete.assert_called_once_with(volumes[0].id) + + def test_volume_delete_with_force(self): + volumes = self.setup_volumes_mock(count=1) + + arglist = [ + '--force', + volumes[0].id, + ] + verifylist = [ + ('force', True), + ('volumes', [volumes[0].id]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.volumes_mock.force_delete.assert_called_once_with(volumes[0].id) + self.assertIsNone(result) + + class TestVolumeList(TestVolume): columns = ( diff --git a/openstackclient/volume/v1/backup.py b/openstackclient/volume/v1/backup.py index 539ed369e2..c9d0ca0d81 100644 --- a/openstackclient/volume/v1/backup.py +++ b/openstackclient/volume/v1/backup.py @@ -19,12 +19,16 @@ 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 CreateVolumeBackup(command.ShowOne): """Create new volume backup""" @@ -100,10 +104,24 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): volume_client = self.app.client_manager.volume - for backup in parsed_args.backups: - backup_id = utils.find_resource(volume_client.backups, - backup).id - volume_client.backups.delete(backup_id) + result = 0 + + for i in parsed_args.backups: + try: + backup_id = utils.find_resource( + volume_client.backups, i).id + volume_client.backups.delete(backup_id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete backup with " + "name or ID '%(backup)s': %(e)s"), + {'backup': i, 'e': e}) + + if result > 0: + total = len(parsed_args.backups) + msg = (_("%(result)s of %(total)s backups failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class DeleteBackup(DeleteVolumeBackup): diff --git a/openstackclient/volume/v1/qos_specs.py b/openstackclient/volume/v1/qos_specs.py index c5850871e8..b982c0e604 100644 --- a/openstackclient/volume/v1/qos_specs.py +++ b/openstackclient/volume/v1/qos_specs.py @@ -15,14 +15,20 @@ """Volume v1 QoS action implementations""" +import logging + from osc_lib.cli import parseractions 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 AssociateQos(command.Command): """Associate a QoS specification to a volume type""" @@ -113,9 +119,23 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): volume_client = self.app.client_manager.volume - for qos in parsed_args.qos_specs: - qos_spec = utils.find_resource(volume_client.qos_specs, qos) - volume_client.qos_specs.delete(qos_spec.id, parsed_args.force) + result = 0 + + for i in parsed_args.qos_specs: + try: + qos_spec = utils.find_resource(volume_client.qos_specs, i) + volume_client.qos_specs.delete(qos_spec.id, parsed_args.force) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete QoS specification with " + "name or ID '%(qos)s': %(e)s"), + {'qos': i, 'e': e}) + + if result > 0: + total = len(parsed_args.qos_specs) + msg = (_("%(result)s of %(total)s QoS specifications failed" + " to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class DisassociateQos(command.Command): diff --git a/openstackclient/volume/v1/snapshot.py b/openstackclient/volume/v1/snapshot.py index bb3a1fc354..e65475f0de 100644 --- a/openstackclient/volume/v1/snapshot.py +++ b/openstackclient/volume/v1/snapshot.py @@ -16,15 +16,20 @@ """Volume v1 Snapshot action implementations""" import copy +import logging from osc_lib.cli import parseractions 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 CreateSnapshot(command.ShowOne): """Create new snapshot""" @@ -88,10 +93,24 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): volume_client = self.app.client_manager.volume - for snapshot in parsed_args.snapshots: - snapshot_id = utils.find_resource(volume_client.volume_snapshots, - snapshot).id - volume_client.volume_snapshots.delete(snapshot_id) + result = 0 + + for i in parsed_args.snapshots: + try: + snapshot_id = utils.find_resource( + volume_client.volume_snapshots, i).id + volume_client.volume_snapshots.delete(snapshot_id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete snapshot with " + "name or ID '%(snapshot)s': %(e)s"), + {'snapshot': i, 'e': e}) + + if result > 0: + total = len(parsed_args.snapshots) + msg = (_("%(result)s of %(total)s snapshots failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListSnapshot(command.Lister): diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index 820673bb93..83158b97e5 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -20,6 +20,7 @@ from osc_lib.cli import parseractions from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils import six @@ -184,13 +185,27 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): volume_client = self.app.client_manager.volume - for volume in parsed_args.volumes: - volume_obj = utils.find_resource( - volume_client.volumes, volume) - if parsed_args.force: - volume_client.volumes.force_delete(volume_obj.id) - else: - volume_client.volumes.delete(volume_obj.id) + result = 0 + + for i in parsed_args.volumes: + try: + volume_obj = utils.find_resource( + volume_client.volumes, i) + if parsed_args.force: + volume_client.volumes.force_delete(volume_obj.id) + else: + volume_client.volumes.delete(volume_obj.id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete volume with " + "name or ID '%(volume)s': %(e)s"), + {'volume': i, 'e': e}) + + if result > 0: + total = len(parsed_args.volumes) + msg = (_("%(result)s of %(total)s volumes failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListVolume(command.Lister): From 6986a32e1cd6d0c0bdf973e5d4e4bcb3d1f45235 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Fri, 12 Aug 2016 12:29:45 +0800 Subject: [PATCH 1209/3095] Add "--limit" and "--marker" options to "volume list" command Add ``--limit`` option to ``volume list`` command in volume v1, add ``--limit`` and ``--marker`` options to ``volume list`` command in volume v2. Change-Id: I327a252aa83ed84166da99cf6aa80334e0e6dd44 Partial-Bug: #1612484 --- doc/source/command-objects/volume.rst | 12 ++++ .../tests/unit/volume/v1/test_volume.py | 41 +++++++++++ .../tests/unit/volume/v2/test_volume.py | 71 +++++++++++++++++++ openstackclient/volume/v1/volume.py | 12 +++- openstackclient/volume/v2/volume.py | 18 ++++- .../notes/bug-1612484-e8605ad8966a455e.yaml | 7 ++ 6 files changed, 159 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/bug-1612484-e8605ad8966a455e.yaml diff --git a/doc/source/command-objects/volume.rst b/doc/source/command-objects/volume.rst index 8efa8a6697..141c980abe 100644 --- a/doc/source/command-objects/volume.rst +++ b/doc/source/command-objects/volume.rst @@ -121,6 +121,8 @@ List volumes [--status ] [--all-projects] [--long] + [--limit ] + [--marker ] .. option:: --project @@ -166,6 +168,16 @@ List volumes List additional fields in output +.. option:: --limit + + Maximum number of volumes to display + +.. option:: --marker + + The last volume ID of the previous page + + *Volume version 2 only* + volume set ---------- diff --git a/openstackclient/tests/unit/volume/v1/test_volume.py b/openstackclient/tests/unit/volume/v1/test_volume.py index f90566fd5e..381928499e 100644 --- a/openstackclient/tests/unit/volume/v1/test_volume.py +++ b/openstackclient/tests/unit/volume/v1/test_volume.py @@ -13,6 +13,7 @@ # under the License. # +import argparse import copy import mock @@ -437,6 +438,7 @@ def test_volume_list_no_options(self): ('all_projects', False), ('name', None), ('status', None), + ('limit', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -454,6 +456,7 @@ def test_volume_list_name(self): ('all_projects', False), ('name', volume_fakes.volume_name), ('status', None), + ('limit', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -470,6 +473,7 @@ def test_volume_list_status(self): ('all_projects', False), ('name', None), ('status', volume_fakes.volume_status), + ('limit', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -486,6 +490,7 @@ def test_volume_list_all_projects(self): ('all_projects', True), ('name', None), ('status', None), + ('limit', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -502,6 +507,7 @@ def test_volume_list_long(self): ('all_projects', False), ('name', None), ('status', None), + ('limit', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -532,6 +538,41 @@ def test_volume_list_long(self): ), ) self.assertEqual(datalist, tuple(data)) + def test_volume_list_with_limit(self): + arglist = [ + '--limit', '2', + ] + verifylist = [ + ('long', False), + ('all_projects', False), + ('name', None), + ('status', None), + ('limit', 2), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volumes_mock.list.assert_called_once_with( + limit=2, + search_opts={ + 'status': None, + 'display_name': None, + 'all_tenants': False, } + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) + + def test_volume_list_negative_limit(self): + arglist = [ + "--limit", "-2", + ] + verifylist = [ + ("limit", -2), + ] + self.assertRaises(argparse.ArgumentTypeError, self.check_parser, + self.cmd, arglist, verifylist) + class TestVolumeSet(TestVolume): diff --git a/openstackclient/tests/unit/volume/v2/test_volume.py b/openstackclient/tests/unit/volume/v2/test_volume.py index 66f8f74d42..de059b1b5d 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume.py +++ b/openstackclient/tests/unit/volume/v2/test_volume.py @@ -12,6 +12,7 @@ # under the License. # +import argparse import mock from mock import call @@ -553,6 +554,8 @@ def test_volume_list_no_options(self): ('all_projects', False), ('name', None), ('status', None), + ('marker', None), + ('limit', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -581,6 +584,8 @@ def test_volume_list_project(self): ('long', False), ('all_projects', False), ('status', None), + ('marker', None), + ('limit', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -611,6 +616,8 @@ def test_volume_list_project_domain(self): ('long', False), ('all_projects', False), ('status', None), + ('marker', None), + ('limit', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -639,6 +646,8 @@ def test_volume_list_user(self): ('long', False), ('all_projects', False), ('status', None), + ('marker', None), + ('limit', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -668,6 +677,8 @@ def test_volume_list_user_domain(self): ('long', False), ('all_projects', False), ('status', None), + ('marker', None), + ('limit', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -696,6 +707,8 @@ def test_volume_list_name(self): ('all_projects', False), ('name', self.mock_volume.name), ('status', None), + ('marker', None), + ('limit', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -724,6 +737,8 @@ def test_volume_list_status(self): ('all_projects', False), ('name', None), ('status', self.mock_volume.status), + ('marker', None), + ('limit', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -752,6 +767,8 @@ def test_volume_list_all_projects(self): ('all_projects', True), ('name', None), ('status', None), + ('marker', None), + ('limit', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -780,6 +797,8 @@ def test_volume_list_long(self): ('all_projects', False), ('name', None), ('status', None), + ('marker', None), + ('limit', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -813,6 +832,58 @@ def test_volume_list_long(self): ), ) self.assertEqual(datalist, tuple(data)) + def test_volume_list_with_marker_and_limit(self): + arglist = [ + "--marker", self.mock_volume.id, + "--limit", "2", + ] + verifylist = [ + ('long', False), + ('all_projects', False), + ('name', None), + ('status', None), + ('marker', self.mock_volume.id), + ('limit', 2), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual(self.columns, columns) + + server = self.mock_volume.attachments[0]['server_id'] + device = self.mock_volume.attachments[0]['device'] + msg = 'Attached to %s on %s ' % (server, device) + datalist = (( + self.mock_volume.id, + self.mock_volume.name, + self.mock_volume.status, + self.mock_volume.size, + msg, + ), ) + + self.volumes_mock.list.assert_called_once_with( + marker=self.mock_volume.id, + limit=2, + search_opts={ + 'status': None, + 'project_id': None, + 'user_id': None, + 'display_name': None, + 'all_tenants': False, } + ) + self.assertEqual(datalist, tuple(data)) + + def test_volume_list_negative_limit(self): + arglist = [ + "--limit", "-2", + ] + verifylist = [ + ("limit", -2), + ] + self.assertRaises(argparse.ArgumentTypeError, self.check_parser, + self.cmd, arglist, verifylist) + class TestVolumeSet(TestVolume): diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index 820673bb93..6db6a5f24c 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -220,6 +220,13 @@ def get_parser(self, prog_name): default=False, help=_('List additional fields in output'), ) + parser.add_argument( + '--limit', + type=int, + action=parseractions.NonNegativeAction, + metavar='', + help=_('Maximum number of volumes to display'), + ) return parser def take_action(self, parsed_args): @@ -295,7 +302,10 @@ def _format_attach(attachments): 'status': parsed_args.status, } - data = volume_client.volumes.list(search_opts=search_opts) + data = volume_client.volumes.list( + search_opts=search_opts, + limit=parsed_args.limit, + ) return (column_headers, (utils.get_item_properties( diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index bd201e0041..28946a5f7f 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -246,6 +246,18 @@ def get_parser(self, prog_name): default=False, help=_('List additional fields in output'), ) + parser.add_argument( + '--marker', + metavar='', + help=_('The last volume ID of the previous page'), + ) + parser.add_argument( + '--limit', + type=int, + action=parseractions.NonNegativeAction, + metavar='', + help=_('Maximum number of volumes to display'), + ) return parser def take_action(self, parsed_args): @@ -328,7 +340,11 @@ def _format_attach(attachments): 'status': parsed_args.status, } - data = volume_client.volumes.list(search_opts=search_opts) + data = volume_client.volumes.list( + search_opts=search_opts, + marker=parsed_args.marker, + limit=parsed_args.limit, + ) return (column_headers, (utils.get_item_properties( diff --git a/releasenotes/notes/bug-1612484-e8605ad8966a455e.yaml b/releasenotes/notes/bug-1612484-e8605ad8966a455e.yaml new file mode 100644 index 0000000000..1020585e20 --- /dev/null +++ b/releasenotes/notes/bug-1612484-e8605ad8966a455e.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Add ``--limit`` option to ``volume list`` command in volume v1, + add ``--limit`` and ``--marker`` options to ``volume list`` + command in volume v2. + [Bug `1612484 `_] From 10e665a148efec0cf75bd5dd07decf23a36a52e0 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Mon, 15 Aug 2016 19:03:50 +0800 Subject: [PATCH 1210/3095] Error handling of multi REST API calls for "snapshot set" command Support multi REST API calls error handling for "snapshot set" command follow the rule in doc/source/command-errors.rst. Also add a unit test for testing the error handling Change-Id: I0c6214271bc54a25b051c0a62438c3344c8b51d7 --- .../tests/unit/volume/v2/test_snapshot.py | 49 +++++++++++++++++++ openstackclient/volume/v1/snapshot.py | 27 ++++++++-- openstackclient/volume/v2/snapshot.py | 35 ++++++++++--- 3 files changed, 100 insertions(+), 11 deletions(-) diff --git a/openstackclient/tests/unit/volume/v2/test_snapshot.py b/openstackclient/tests/unit/volume/v2/test_snapshot.py index 333d8d7248..d355662d8b 100644 --- a/openstackclient/tests/unit/volume/v2/test_snapshot.py +++ b/openstackclient/tests/unit/volume/v2/test_snapshot.py @@ -376,6 +376,55 @@ def test_snapshot_set_state_to_error(self): self.snapshot.id, "error") self.assertIsNone(result) + def test_volume_set_state_failed(self): + self.snapshots_mock.reset_state.side_effect = exceptions.CommandError() + arglist = [ + '--state', 'error', + self.snapshot.id + ] + verifylist = [ + ('state', 'error'), + ('snapshot', self.snapshot.id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('One or more of the set operations failed', + str(e)) + self.snapshots_mock.reset_state.assert_called_once_with( + self.snapshot.id, 'error') + + def test_volume_set_name_and_state_failed(self): + self.snapshots_mock.reset_state.side_effect = exceptions.CommandError() + arglist = [ + '--state', 'error', + "--name", "new_snapshot", + self.snapshot.id + ] + verifylist = [ + ('state', 'error'), + ("name", "new_snapshot"), + ('snapshot', self.snapshot.id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('One or more of the set operations failed', + str(e)) + kwargs = { + "name": "new_snapshot", + } + self.snapshots_mock.update.assert_called_once_with( + self.snapshot.id, **kwargs) + self.snapshots_mock.reset_state.assert_called_once_with( + self.snapshot.id, 'error') + class TestSnapshotShow(TestSnapshot): diff --git a/openstackclient/volume/v1/snapshot.py b/openstackclient/volume/v1/snapshot.py index bb3a1fc354..f8fa7c954b 100644 --- a/openstackclient/volume/v1/snapshot.py +++ b/openstackclient/volume/v1/snapshot.py @@ -16,15 +16,20 @@ """Volume v1 Snapshot action implementations""" import copy +import logging from osc_lib.cli import parseractions 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 CreateSnapshot(command.ShowOne): """Create new snapshot""" @@ -199,17 +204,31 @@ def take_action(self, parsed_args): snapshot = utils.find_resource(volume_client.volume_snapshots, parsed_args.snapshot) + result = 0 if parsed_args.property: - volume_client.volume_snapshots.set_metadata(snapshot.id, - parsed_args.property) + try: + volume_client.volume_snapshots.set_metadata( + snapshot.id, parsed_args.property) + except Exception as e: + LOG.error(_("Failed to set snapshot property: %s"), e) + result += 1 kwargs = {} if parsed_args.name: kwargs['display_name'] = parsed_args.name if parsed_args.description: kwargs['display_description'] = parsed_args.description - - snapshot.update(**kwargs) + if kwargs: + try: + snapshot.update(**kwargs) + except Exception as e: + LOG.error(_("Failed to update snapshot display name " + "or display description: %s"), e) + result += 1 + + if result > 0: + raise exceptions.CommandError(_("One or more of the " + "set operations failed")) class ShowSnapshot(command.ShowOne): diff --git a/openstackclient/volume/v2/snapshot.py b/openstackclient/volume/v2/snapshot.py index 8304a5eb46..0d82655108 100644 --- a/openstackclient/volume/v2/snapshot.py +++ b/openstackclient/volume/v2/snapshot.py @@ -240,19 +240,40 @@ def take_action(self, parsed_args): snapshot = utils.find_resource(volume_client.volume_snapshots, parsed_args.snapshot) + result = 0 + if parsed_args.property: + try: + volume_client.volume_snapshots.set_metadata( + snapshot.id, parsed_args.property) + except Exception as e: + LOG.error(_("Failed to set snapshot property: %s"), e) + result += 1 + + if parsed_args.state: + try: + volume_client.volume_snapshots.reset_state( + snapshot.id, parsed_args.state) + except Exception as e: + LOG.error(_("Failed to set snapshot state: %s"), e) + result += 1 + kwargs = {} if parsed_args.name: kwargs['name'] = parsed_args.name if parsed_args.description: kwargs['description'] = parsed_args.description + if kwargs: + try: + volume_client.volume_snapshots.update( + snapshot.id, **kwargs) + except Exception as e: + LOG.error(_("Failed to update snapshot name " + "or description: %s"), e) + result += 1 - if parsed_args.property: - volume_client.volume_snapshots.set_metadata(snapshot.id, - parsed_args.property) - if parsed_args.state: - volume_client.volume_snapshots.reset_state(snapshot.id, - parsed_args.state) - volume_client.volume_snapshots.update(snapshot.id, **kwargs) + if result > 0: + raise exceptions.CommandError(_("One or more of the " + "set operations failed")) class ShowSnapshot(command.ShowOne): From 6f4acc45c62310d925c8f33a33c14d37990b6636 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Tue, 9 Aug 2016 13:21:40 +0800 Subject: [PATCH 1211/3095] Implement "volume transfer request create" command Add "volume transfer request create" command in volume v1 and v2. Also add the unit tests, docs, release note and functional tests Change-Id: If362df1acf214efdf6ba129cd917d33eb54e1030 Implements: bp cinder-command-support Co-Authored-By: Sheel Rana --- .../volume-transfer-request.rst | 23 +++++- openstackclient/tests/unit/volume/v1/fakes.py | 7 +- .../unit/volume/v1/test_transfer_request.py | 72 ++++++++++++++++++- openstackclient/tests/unit/volume/v2/fakes.py | 7 +- .../unit/volume/v2/test_transfer_request.py | 72 ++++++++++++++++++- .../volume/v1/volume_transfer_request.py | 32 ++++++++- .../volume/v2/volume_transfer_request.py | 30 ++++++++ ...nder-command-support-cc8708c4395ce467.yaml | 4 ++ setup.cfg | 2 + 9 files changed, 241 insertions(+), 8 deletions(-) create mode 100644 releasenotes/notes/bp-cinder-command-support-cc8708c4395ce467.yaml diff --git a/doc/source/command-objects/volume-transfer-request.rst b/doc/source/command-objects/volume-transfer-request.rst index 79d84f7551..f9efb979fd 100644 --- a/doc/source/command-objects/volume-transfer-request.rst +++ b/doc/source/command-objects/volume-transfer-request.rst @@ -4,6 +4,27 @@ volume transfer request Block Storage v1, v2 +volume transfer request create +------------------------------ + +Create volume transfer request + +.. program:: volume transfer request create +.. code:: bash + + os volume transfer request create + [--name ] + + +.. option:: --name + + New transfer request name (default to None) + +.. _volume_transfer_request_create-volume: +.. describe:: + + Volume to transfer (name or ID) + volume transfer request list ---------------------------- @@ -18,4 +39,4 @@ Lists all volume transfer requests. .. option:: --all-projects Shows detail for all projects. Admin only. - (defaults to False) \ No newline at end of file + (defaults to False) diff --git a/openstackclient/tests/unit/volume/v1/fakes.py b/openstackclient/tests/unit/volume/v1/fakes.py index 5c1f3b12f5..f63553fe71 100644 --- a/openstackclient/tests/unit/volume/v1/fakes.py +++ b/openstackclient/tests/unit/volume/v1/fakes.py @@ -146,9 +146,12 @@ def create_one_transfer(attrs=None): """ # Set default attribute transfer_info = { - 'volume_id': 'ce26708d-a7f8-4b4b-9861-4a80256615a7', + 'auth_key': 'key-' + uuid.uuid4().hex, + 'created_at': 'time-' + uuid.uuid4().hex, + 'volume_id': 'volume-id-' + uuid.uuid4().hex, 'name': 'fake_transfer_name', - 'id': '731a7f53-aa92-4fbd-9de3-6f7d729c926b' + 'id': 'id-' + uuid.uuid4().hex, + 'links': 'links-' + uuid.uuid4().hex, } # Overwrite default attributes if there are some attributes set diff --git a/openstackclient/tests/unit/volume/v1/test_transfer_request.py b/openstackclient/tests/unit/volume/v1/test_transfer_request.py index f7980c34aa..a5c31f8d27 100644 --- a/openstackclient/tests/unit/volume/v1/test_transfer_request.py +++ b/openstackclient/tests/unit/volume/v1/test_transfer_request.py @@ -12,7 +12,6 @@ # under the License. # - from openstackclient.tests.unit.volume.v1 import fakes as transfer_fakes from openstackclient.volume.v1 import volume_transfer_request @@ -26,6 +25,77 @@ def setUp(self): self.transfer_mock = self.app.client_manager.volume.transfers self.transfer_mock.reset_mock() + # Get a shortcut to the VolumeManager Mock + self.volumes_mock = self.app.client_manager.volume.volumes + self.volumes_mock.reset_mock() + + +class TestTransferCreate(TestTransfer): + + volume = transfer_fakes.FakeVolume.create_one_volume() + + columns = ( + 'auth_key', + 'created_at', + 'id', + 'name', + 'volume_id', + ) + + def setUp(self): + super(TestTransferCreate, self).setUp() + + self.volume_transfer = transfer_fakes.FakeTransfer.create_one_transfer( + attrs={'volume_id': self.volume.id}) + self.data = ( + self.volume_transfer.auth_key, + self.volume_transfer.created_at, + self.volume_transfer.id, + self.volume_transfer.name, + self.volume_transfer.volume_id, + ) + + self.transfer_mock.create.return_value = self.volume_transfer + self.volumes_mock.get.return_value = self.volume + + # Get the command object to test + self.cmd = volume_transfer_request.CreateTransferRequest( + self.app, None) + + def test_transfer_create_without_name(self): + arglist = [ + self.volume.id, + ] + verifylist = [ + ('volume', self.volume.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.transfer_mock.create.assert_called_once_with( + self.volume.id, None) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_transfer_create_with_name(self): + arglist = [ + '--name', self.volume_transfer.name, + self.volume.id, + ] + verifylist = [ + ('name', self.volume_transfer.name), + ('volume', self.volume.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.transfer_mock.create.assert_called_once_with( + self.volume.id, self.volume_transfer.name,) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + class TestTransferList(TestTransfer): diff --git a/openstackclient/tests/unit/volume/v2/fakes.py b/openstackclient/tests/unit/volume/v2/fakes.py index a958c46803..51c952cdc6 100644 --- a/openstackclient/tests/unit/volume/v2/fakes.py +++ b/openstackclient/tests/unit/volume/v2/fakes.py @@ -39,9 +39,12 @@ def create_one_transfer(attrs=None): """ # Set default attribute transfer_info = { - 'volume_id': 'ce26708d-a7f8-4b4b-9861-4a80256615a7', + 'auth_key': 'key-' + uuid.uuid4().hex, + 'created_at': 'time-' + uuid.uuid4().hex, + 'volume_id': 'volume-id-' + uuid.uuid4().hex, 'name': 'fake_transfer_name', - 'id': '731a7f53-aa92-4fbd-9de3-6f7d729c926b' + 'id': 'id-' + uuid.uuid4().hex, + 'links': 'links-' + uuid.uuid4().hex, } # Overwrite default attributes if there are some attributes set diff --git a/openstackclient/tests/unit/volume/v2/test_transfer_request.py b/openstackclient/tests/unit/volume/v2/test_transfer_request.py index 32108c025d..8cc76bef4c 100644 --- a/openstackclient/tests/unit/volume/v2/test_transfer_request.py +++ b/openstackclient/tests/unit/volume/v2/test_transfer_request.py @@ -12,7 +12,6 @@ # under the License. # - from openstackclient.tests.unit.volume.v2 import fakes as transfer_fakes from openstackclient.volume.v2 import volume_transfer_request @@ -26,6 +25,77 @@ def setUp(self): self.transfer_mock = self.app.client_manager.volume.transfers self.transfer_mock.reset_mock() + # Get a shortcut to the VolumeManager Mock + self.volumes_mock = self.app.client_manager.volume.volumes + self.volumes_mock.reset_mock() + + +class TestTransferCreate(TestTransfer): + + volume = transfer_fakes.FakeVolume.create_one_volume() + + columns = ( + 'auth_key', + 'created_at', + 'id', + 'name', + 'volume_id', + ) + + def setUp(self): + super(TestTransferCreate, self).setUp() + + self.volume_transfer = transfer_fakes.FakeTransfer.create_one_transfer( + attrs={'volume_id': self.volume.id}) + self.data = ( + self.volume_transfer.auth_key, + self.volume_transfer.created_at, + self.volume_transfer.id, + self.volume_transfer.name, + self.volume_transfer.volume_id, + ) + + self.transfer_mock.create.return_value = self.volume_transfer + self.volumes_mock.get.return_value = self.volume + + # Get the command object to test + self.cmd = volume_transfer_request.CreateTransferRequest( + self.app, None) + + def test_transfer_create_without_name(self): + arglist = [ + self.volume.id, + ] + verifylist = [ + ('volume', self.volume.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.transfer_mock.create.assert_called_once_with( + self.volume.id, None) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_transfer_create_with_name(self): + arglist = [ + '--name', self.volume_transfer.name, + self.volume.id, + ] + verifylist = [ + ('name', self.volume_transfer.name), + ('volume', self.volume.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.transfer_mock.create.assert_called_once_with( + self.volume.id, self.volume_transfer.name,) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + class TestTransferList(TestTransfer): diff --git a/openstackclient/volume/v1/volume_transfer_request.py b/openstackclient/volume/v1/volume_transfer_request.py index 5d8ff6839c..b1167340fb 100644 --- a/openstackclient/volume/v1/volume_transfer_request.py +++ b/openstackclient/volume/v1/volume_transfer_request.py @@ -12,14 +12,44 @@ # under the License. # -"""Volume v2 transfer action implementations""" +"""Volume v1 transfer action implementations""" from osc_lib.command import command from osc_lib import utils +import six from openstackclient.i18n import _ +class CreateTransferRequest(command.ShowOne): + """Create volume transfer request.""" + + def get_parser(self, prog_name): + parser = super(CreateTransferRequest, self).get_parser(prog_name) + parser.add_argument( + '--name', + metavar="", + help=_('New transfer request name (default to None)') + ) + parser.add_argument( + 'volume', + metavar="", + help=_('Volume to transfer (name or ID)') + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + volume_id = utils.find_resource( + volume_client.volumes, parsed_args.volume).id + volume_transfer_request = volume_client.transfers.create( + volume_id, parsed_args.name, + ) + volume_transfer_request._info.pop("links", None) + + return zip(*sorted(six.iteritems(volume_transfer_request._info))) + + class ListTransferRequests(command.Lister): """Lists all volume transfer requests.""" diff --git a/openstackclient/volume/v2/volume_transfer_request.py b/openstackclient/volume/v2/volume_transfer_request.py index 5d8ff6839c..45581586e6 100644 --- a/openstackclient/volume/v2/volume_transfer_request.py +++ b/openstackclient/volume/v2/volume_transfer_request.py @@ -16,10 +16,40 @@ from osc_lib.command import command from osc_lib import utils +import six from openstackclient.i18n import _ +class CreateTransferRequest(command.ShowOne): + """Create volume transfer request.""" + + def get_parser(self, prog_name): + parser = super(CreateTransferRequest, self).get_parser(prog_name) + parser.add_argument( + '--name', + metavar="", + help=_('New transfer request name (default to None)'), + ) + parser.add_argument( + 'volume', + metavar="", + help=_('Volume to transfer (name or ID)'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + volume_id = utils.find_resource( + volume_client.volumes, parsed_args.volume).id + volume_transfer_request = volume_client.transfers.create( + volume_id, parsed_args.name, + ) + volume_transfer_request._info.pop("links", None) + + return zip(*sorted(six.iteritems(volume_transfer_request._info))) + + class ListTransferRequests(command.Lister): """Lists all volume transfer requests.""" diff --git a/releasenotes/notes/bp-cinder-command-support-cc8708c4395ce467.yaml b/releasenotes/notes/bp-cinder-command-support-cc8708c4395ce467.yaml new file mode 100644 index 0000000000..686ff25e4e --- /dev/null +++ b/releasenotes/notes/bp-cinder-command-support-cc8708c4395ce467.yaml @@ -0,0 +1,4 @@ +--- +features: + - Add ``volume transfer request create`` command in volume v1 and v2 + [Blueprint `cinder-command-support `_] diff --git a/setup.cfg b/setup.cfg index 7af6e0c7e2..262c276a7f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -483,6 +483,7 @@ openstack.volume.v1 = volume_service_list = openstackclient.volume.v1.service:ListService volume_service_set = openstackclient.volume.v1.service:SetService + volume_transfer_request_create = openstackclient.volume.v1.volume_transfer_request:CreateTransferRequest volume_transfer_request_list = openstackclient.volume.v1.volume_transfer_request:ListTransferRequests openstack.volume.v2 = @@ -531,6 +532,7 @@ openstack.volume.v2 = volume_service_list = openstackclient.volume.v2.service:ListService volume_service_set = openstackclient.volume.v2.service:SetService + volume_transfer_request_create = openstackclient.volume.v2.volume_transfer_request:CreateTransferRequest volume_transfer_request_list = openstackclient.volume.v2.volume_transfer_request:ListTransferRequests [build_sphinx] From d2273ecea5d540f4dacc89772870722355f2492f Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Mon, 8 Aug 2016 12:09:19 +0800 Subject: [PATCH 1212/3095] Implement "volume transfer request delete" command Add "volume transfer request delete" command in volume v1 and v2. Also add the unit tests, docs, release note and functional tests Change-Id: Ic3d375bc8df3312fac53c1800d75f48376b8c91c Implements: bp cinder-command-support Co-Authored-By: Sheel Rana --- .../volume-transfer-request.rst | 16 ++++ .../volume/v1/test_transfer_request.py | 53 ++++++++++++ .../volume/v2/test_transfer_request.py | 53 ++++++++++++ openstackclient/tests/unit/volume/v1/fakes.py | 37 ++++++++ .../unit/volume/v1/test_transfer_request.py | 84 +++++++++++++++++++ openstackclient/tests/unit/volume/v2/fakes.py | 37 ++++++++ .../unit/volume/v2/test_transfer_request.py | 84 +++++++++++++++++++ .../volume/v1/volume_transfer_request.py | 41 +++++++++ .../volume/v2/volume_transfer_request.py | 41 +++++++++ ...nder-command-support-cc8708c4395ce467.yaml | 3 +- setup.cfg | 2 + 11 files changed, 450 insertions(+), 1 deletion(-) create mode 100644 openstackclient/tests/functional/volume/v1/test_transfer_request.py create mode 100644 openstackclient/tests/functional/volume/v2/test_transfer_request.py diff --git a/doc/source/command-objects/volume-transfer-request.rst b/doc/source/command-objects/volume-transfer-request.rst index f9efb979fd..4729999574 100644 --- a/doc/source/command-objects/volume-transfer-request.rst +++ b/doc/source/command-objects/volume-transfer-request.rst @@ -25,6 +25,22 @@ Create volume transfer request Volume to transfer (name or ID) +volume transfer request delete +------------------------------ + +Delete volume transfer request(s) + +.. program:: volume transfer request delete +.. code:: bash + + os volume transfer request delete + [ ...] + +.. _volume_transfer_request_delete-transfer-request: +.. describe:: + + Volume transfer request(s) to delete (name or ID) + volume transfer request list ---------------------------- diff --git a/openstackclient/tests/functional/volume/v1/test_transfer_request.py b/openstackclient/tests/functional/volume/v1/test_transfer_request.py new file mode 100644 index 0000000000..d8406b0295 --- /dev/null +++ b/openstackclient/tests/functional/volume/v1/test_transfer_request.py @@ -0,0 +1,53 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import uuid + +from openstackclient.tests.functional.volume.v1 import common + + +class TransferRequestTests(common.BaseVolumeTests): + """Functional tests for transfer request. """ + + NAME = uuid.uuid4().hex + VOLUME_NAME = uuid.uuid4().hex + HEADERS = ['Name'] + FIELDS = ['name'] + + @classmethod + def setUpClass(cls): + super(TransferRequestTests, cls).setUpClass() + opts = cls.get_opts(['display_name']) + raw_output = cls.openstack( + 'volume create --size 1 ' + cls.VOLUME_NAME + opts) + cls.assertOutput(cls.VOLUME_NAME + '\n', raw_output) + + opts = cls.get_opts(cls.FIELDS) + raw_output = cls.openstack( + 'volume transfer request create ' + + cls.VOLUME_NAME + + ' --name ' + cls.NAME + opts) + cls.assertOutput(cls.NAME + '\n', raw_output) + + @classmethod + def tearDownClass(cls): + raw_output_transfer = cls.openstack( + 'volume transfer request delete ' + cls.NAME) + raw_output_volume = cls.openstack( + 'volume delete ' + cls.VOLUME_NAME) + cls.assertOutput('', raw_output_transfer) + cls.assertOutput('', raw_output_volume) + + def test_volume_transfer_request_list(self): + opts = self.get_opts(self.HEADERS) + raw_output = self.openstack('volume transfer request list' + opts) + self.assertIn(self.NAME, raw_output) diff --git a/openstackclient/tests/functional/volume/v2/test_transfer_request.py b/openstackclient/tests/functional/volume/v2/test_transfer_request.py new file mode 100644 index 0000000000..4f02238bc9 --- /dev/null +++ b/openstackclient/tests/functional/volume/v2/test_transfer_request.py @@ -0,0 +1,53 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import uuid + +from openstackclient.tests.functional.volume.v2 import common + + +class TransferRequestTests(common.BaseVolumeTests): + """Functional tests for transfer request. """ + + NAME = uuid.uuid4().hex + VOLUME_NAME = uuid.uuid4().hex + HEADERS = ['Name'] + FIELDS = ['name'] + + @classmethod + def setUpClass(cls): + super(TransferRequestTests, cls).setUpClass() + opts = cls.get_opts(cls.FIELDS) + + raw_output = cls.openstack( + 'volume create --size 1 ' + cls.VOLUME_NAME + opts) + cls.assertOutput(cls.VOLUME_NAME + '\n', raw_output) + + raw_output = cls.openstack( + 'volume transfer request create ' + + cls.VOLUME_NAME + + ' --name ' + cls.NAME + opts) + cls.assertOutput(cls.NAME + '\n', raw_output) + + @classmethod + def tearDownClass(cls): + raw_output_transfer = cls.openstack( + 'volume transfer request delete ' + cls.NAME) + raw_output_volume = cls.openstack( + 'volume delete ' + cls.VOLUME_NAME) + cls.assertOutput('', raw_output_transfer) + cls.assertOutput('', raw_output_volume) + + def test_volume_transfer_request_list(self): + opts = self.get_opts(self.HEADERS) + raw_output = self.openstack('volume transfer request list' + opts) + self.assertIn(self.NAME, raw_output) diff --git a/openstackclient/tests/unit/volume/v1/fakes.py b/openstackclient/tests/unit/volume/v1/fakes.py index f63553fe71..c765a3c730 100644 --- a/openstackclient/tests/unit/volume/v1/fakes.py +++ b/openstackclient/tests/unit/volume/v1/fakes.py @@ -166,6 +166,43 @@ def create_one_transfer(attrs=None): return transfer + @staticmethod + def create_transfers(attrs=None, count=2): + """Create multiple fake transfers. + + :param Dictionary attrs: + A dictionary with all attributes of transfer + :param Integer count: + The number of transfers to be faked + :return: + A list of FakeResource objects + """ + transfers = [] + for n in range(0, count): + transfers.append(FakeTransfer.create_one_transfer(attrs)) + + return transfers + + @staticmethod + def get_transfers(transfers=None, count=2): + """Get an iterable MagicMock object with a list of faked transfers. + + If transfers list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List transfers: + A list of FakeResource objects faking transfers + :param Integer count: + The number of transfers to be faked + :return + An iterable Mock object with side_effect set to a list of faked + transfers + """ + if transfers is None: + transfers = FakeTransfer.create_transfers(count) + + return mock.MagicMock(side_effect=transfers) + class FakeService(object): """Fake one or more Services.""" diff --git a/openstackclient/tests/unit/volume/v1/test_transfer_request.py b/openstackclient/tests/unit/volume/v1/test_transfer_request.py index a5c31f8d27..c3b8dbcea2 100644 --- a/openstackclient/tests/unit/volume/v1/test_transfer_request.py +++ b/openstackclient/tests/unit/volume/v1/test_transfer_request.py @@ -12,6 +12,12 @@ # under the License. # +import mock +from mock import call + +from osc_lib import exceptions +from osc_lib import utils + from openstackclient.tests.unit.volume.v1 import fakes as transfer_fakes from openstackclient.volume.v1 import volume_transfer_request @@ -97,6 +103,84 @@ def test_transfer_create_with_name(self): self.assertEqual(self.data, data) +class TestTransferDelete(TestTransfer): + + volume_transfers = transfer_fakes.FakeTransfer.create_transfers(count=2) + + def setUp(self): + super(TestTransferDelete, self).setUp() + + self.transfer_mock.get = ( + transfer_fakes.FakeTransfer.get_transfers(self.volume_transfers)) + self.transfer_mock.delete.return_value = None + + # Get the command object to mock + self.cmd = volume_transfer_request.DeleteTransferRequest( + self.app, None) + + def test_transfer_delete(self): + arglist = [ + self.volume_transfers[0].id + ] + verifylist = [ + ("transfer_request", [self.volume_transfers[0].id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.transfer_mock.delete.assert_called_with( + self.volume_transfers[0].id) + self.assertIsNone(result) + + def test_delete_multiple_transfers(self): + arglist = [] + for v in self.volume_transfers: + arglist.append(v.id) + verifylist = [ + ('transfer_request', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + calls = [] + for v in self.volume_transfers: + calls.append(call(v.id)) + self.transfer_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_delete_multiple_transfers_with_exception(self): + arglist = [ + self.volume_transfers[0].id, + 'unexist_transfer', + ] + verifylist = [ + ('transfer_request', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self.volume_transfers[0], exceptions.CommandError] + with mock.patch.object(utils, 'find_resource', + side_effect=find_mock_result) as find_mock: + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 volume transfer requests failed ' + 'to delete.', str(e)) + + find_mock.assert_any_call( + self.transfer_mock, self.volume_transfers[0].id) + find_mock.assert_any_call(self.transfer_mock, 'unexist_transfer') + + self.assertEqual(2, find_mock.call_count) + self.transfer_mock.delete.assert_called_once_with( + self.volume_transfers[0].id, + ) + + class TestTransferList(TestTransfer): # The Transfers to be listed diff --git a/openstackclient/tests/unit/volume/v2/fakes.py b/openstackclient/tests/unit/volume/v2/fakes.py index 51c952cdc6..e79a9407f9 100644 --- a/openstackclient/tests/unit/volume/v2/fakes.py +++ b/openstackclient/tests/unit/volume/v2/fakes.py @@ -59,6 +59,43 @@ def create_one_transfer(attrs=None): return transfer + @staticmethod + def create_transfers(attrs=None, count=2): + """Create multiple fake transfers. + + :param Dictionary attrs: + A dictionary with all attributes of transfer + :param Integer count: + The number of transfers to be faked + :return: + A list of FakeResource objects + """ + transfers = [] + for n in range(0, count): + transfers.append(FakeTransfer.create_one_transfer(attrs)) + + return transfers + + @staticmethod + def get_transfers(transfers=None, count=2): + """Get an iterable MagicMock object with a list of faked transfers. + + If transfers list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List transfers: + A list of FakeResource objects faking transfers + :param Integer count: + The number of transfers to be faked + :return + An iterable Mock object with side_effect set to a list of faked + transfers + """ + if transfers is None: + transfers = FakeTransfer.create_transfers(count) + + return mock.MagicMock(side_effect=transfers) + class FakeTypeAccess(object): """Fake one or more volume type access.""" diff --git a/openstackclient/tests/unit/volume/v2/test_transfer_request.py b/openstackclient/tests/unit/volume/v2/test_transfer_request.py index 8cc76bef4c..a18e4c52dd 100644 --- a/openstackclient/tests/unit/volume/v2/test_transfer_request.py +++ b/openstackclient/tests/unit/volume/v2/test_transfer_request.py @@ -12,6 +12,12 @@ # under the License. # +import mock +from mock import call + +from osc_lib import exceptions +from osc_lib import utils + from openstackclient.tests.unit.volume.v2 import fakes as transfer_fakes from openstackclient.volume.v2 import volume_transfer_request @@ -97,6 +103,84 @@ def test_transfer_create_with_name(self): self.assertEqual(self.data, data) +class TestTransferDelete(TestTransfer): + + volume_transfers = transfer_fakes.FakeTransfer.create_transfers(count=2) + + def setUp(self): + super(TestTransferDelete, self).setUp() + + self.transfer_mock.get = ( + transfer_fakes.FakeTransfer.get_transfers(self.volume_transfers)) + self.transfer_mock.delete.return_value = None + + # Get the command object to mock + self.cmd = volume_transfer_request.DeleteTransferRequest( + self.app, None) + + def test_transfer_delete(self): + arglist = [ + self.volume_transfers[0].id + ] + verifylist = [ + ("transfer_request", [self.volume_transfers[0].id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.transfer_mock.delete.assert_called_with( + self.volume_transfers[0].id) + self.assertIsNone(result) + + def test_delete_multiple_transfers(self): + arglist = [] + for v in self.volume_transfers: + arglist.append(v.id) + verifylist = [ + ('transfer_request', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + calls = [] + for v in self.volume_transfers: + calls.append(call(v.id)) + self.transfer_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_delete_multiple_transfers_with_exception(self): + arglist = [ + self.volume_transfers[0].id, + 'unexist_transfer', + ] + verifylist = [ + ('transfer_request', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self.volume_transfers[0], exceptions.CommandError] + with mock.patch.object(utils, 'find_resource', + side_effect=find_mock_result) as find_mock: + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 volume transfer requests failed ' + 'to delete.', str(e)) + + find_mock.assert_any_call( + self.transfer_mock, self.volume_transfers[0].id) + find_mock.assert_any_call(self.transfer_mock, 'unexist_transfer') + + self.assertEqual(2, find_mock.call_count) + self.transfer_mock.delete.assert_called_once_with( + self.volume_transfers[0].id, + ) + + class TestTransferList(TestTransfer): # The Transfers to be listed diff --git a/openstackclient/volume/v1/volume_transfer_request.py b/openstackclient/volume/v1/volume_transfer_request.py index b1167340fb..8c5055282a 100644 --- a/openstackclient/volume/v1/volume_transfer_request.py +++ b/openstackclient/volume/v1/volume_transfer_request.py @@ -14,13 +14,19 @@ """Volume v1 transfer 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 CreateTransferRequest(command.ShowOne): """Create volume transfer request.""" @@ -50,6 +56,41 @@ def take_action(self, parsed_args): return zip(*sorted(six.iteritems(volume_transfer_request._info))) +class DeleteTransferRequest(command.Command): + """Delete volume transfer request(s).""" + + def get_parser(self, prog_name): + parser = super(DeleteTransferRequest, self).get_parser(prog_name) + parser.add_argument( + 'transfer_request', + metavar="", + nargs="+", + help=_('Volume transfer request(s) to delete (name or ID)'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + result = 0 + + for t in parsed_args.transfer_request: + try: + transfer_request_id = utils.find_resource( + volume_client.transfers, t).id + volume_client.transfers.delete(transfer_request_id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete volume transfer request " + "with name or ID '%(transfer)s': %(e)s") + % {'transfer': t, 'e': e}) + + if result > 0: + total = len(parsed_args.transfer_request) + msg = (_("%(result)s of %(total)s volume transfer requests failed" + " to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) + + class ListTransferRequests(command.Lister): """Lists all volume transfer requests.""" diff --git a/openstackclient/volume/v2/volume_transfer_request.py b/openstackclient/volume/v2/volume_transfer_request.py index 45581586e6..f5de8d7ae0 100644 --- a/openstackclient/volume/v2/volume_transfer_request.py +++ b/openstackclient/volume/v2/volume_transfer_request.py @@ -14,13 +14,19 @@ """Volume v2 transfer 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 CreateTransferRequest(command.ShowOne): """Create volume transfer request.""" @@ -50,6 +56,41 @@ def take_action(self, parsed_args): return zip(*sorted(six.iteritems(volume_transfer_request._info))) +class DeleteTransferRequest(command.Command): + """Delete volume transfer request(s).""" + + def get_parser(self, prog_name): + parser = super(DeleteTransferRequest, self).get_parser(prog_name) + parser.add_argument( + 'transfer_request', + metavar="", + nargs="+", + help=_('Volume transfer request(s) to delete (name or ID)'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + result = 0 + + for t in parsed_args.transfer_request: + try: + transfer_request_id = utils.find_resource( + volume_client.transfers, t).id + volume_client.transfers.delete(transfer_request_id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete volume transfer request " + "with name or ID '%(transfer)s': %(e)s") + % {'transfer': t, 'e': e}) + + if result > 0: + total = len(parsed_args.transfer_request) + msg = (_("%(result)s of %(total)s volume transfer requests failed" + " to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) + + class ListTransferRequests(command.Lister): """Lists all volume transfer requests.""" diff --git a/releasenotes/notes/bp-cinder-command-support-cc8708c4395ce467.yaml b/releasenotes/notes/bp-cinder-command-support-cc8708c4395ce467.yaml index 686ff25e4e..5f94fd6709 100644 --- a/releasenotes/notes/bp-cinder-command-support-cc8708c4395ce467.yaml +++ b/releasenotes/notes/bp-cinder-command-support-cc8708c4395ce467.yaml @@ -1,4 +1,5 @@ --- features: - - Add ``volume transfer request create`` command in volume v1 and v2 + - Add ``volume transfer request create`` and ``volume transfer request delete`` + commands in volume v1 and v2. [Blueprint `cinder-command-support `_] diff --git a/setup.cfg b/setup.cfg index 262c276a7f..81232ef183 100644 --- a/setup.cfg +++ b/setup.cfg @@ -484,6 +484,7 @@ openstack.volume.v1 = volume_service_set = openstackclient.volume.v1.service:SetService volume_transfer_request_create = openstackclient.volume.v1.volume_transfer_request:CreateTransferRequest + volume_transfer_request_delete = openstackclient.volume.v1.volume_transfer_request:DeleteTransferRequest volume_transfer_request_list = openstackclient.volume.v1.volume_transfer_request:ListTransferRequests openstack.volume.v2 = @@ -533,6 +534,7 @@ openstack.volume.v2 = volume_service_set = openstackclient.volume.v2.service:SetService volume_transfer_request_create = openstackclient.volume.v2.volume_transfer_request:CreateTransferRequest + volume_transfer_request_delete = openstackclient.volume.v2.volume_transfer_request:DeleteTransferRequest volume_transfer_request_list = openstackclient.volume.v2.volume_transfer_request:ListTransferRequests [build_sphinx] From 9966412c2d8ce53c54bf08a95726b36ab6572ab3 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Thu, 8 Sep 2016 17:10:15 +0800 Subject: [PATCH 1213/3095] Add unit test for volume type in volume v1 Add unit test for volume type commands (create/delete/show/list/set/unset) in volume v1 Change-Id: I6ff1f1c7482bd0b4bfec5b4a1496807b722fa047 --- openstackclient/tests/unit/volume/v1/fakes.py | 74 ++++ .../tests/unit/volume/v1/test_type.py | 347 ++++++++++++++++++ 2 files changed, 421 insertions(+) create mode 100644 openstackclient/tests/unit/volume/v1/test_type.py diff --git a/openstackclient/tests/unit/volume/v1/fakes.py b/openstackclient/tests/unit/volume/v1/fakes.py index 5c1f3b12f5..0f3c3a0f75 100644 --- a/openstackclient/tests/unit/volume/v1/fakes.py +++ b/openstackclient/tests/unit/volume/v1/fakes.py @@ -434,3 +434,77 @@ def setUp(self): endpoint=fakes.AUTH_URL, token=fakes.AUTH_TOKEN, ) + + +class FakeType(object): + """Fake one or more type.""" + + @staticmethod + def create_one_type(attrs=None, methods=None): + """Create a fake type. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :return: + A FakeResource object with id, name, description, etc. + """ + attrs = attrs or {} + methods = methods or {} + + # Set default attributes. + type_info = { + "id": 'type-id-' + uuid.uuid4().hex, + "name": 'type-name-' + uuid.uuid4().hex, + "description": 'type-description-' + uuid.uuid4().hex, + "extra_specs": {"foo": "bar"}, + "is_public": True, + } + + # Overwrite default attributes. + type_info.update(attrs) + + volume_type = fakes.FakeResource( + info=copy.deepcopy(type_info), + methods=methods, + loaded=True) + return volume_type + + @staticmethod + def create_types(attrs=None, count=2): + """Create multiple fake types. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of types to fake + :return: + A list of FakeResource objects faking the types + """ + volume_types = [] + for i in range(0, count): + volume_type = FakeType.create_one_type(attrs) + volume_types.append(volume_type) + + return volume_types + + @staticmethod + def get_types(types=None, count=2): + """Get an iterable MagicMock object with a list of faked types. + + If types list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List types: + A list of FakeResource objects faking types + :param Integer count: + The number of types to be faked + :return + An iterable Mock object with side_effect set to a list of faked + types + """ + if types is None: + types = FakeType.create_types(count) + + return mock.MagicMock(side_effect=types) diff --git a/openstackclient/tests/unit/volume/v1/test_type.py b/openstackclient/tests/unit/volume/v1/test_type.py new file mode 100644 index 0000000000..35016dc6b6 --- /dev/null +++ b/openstackclient/tests/unit/volume/v1/test_type.py @@ -0,0 +1,347 @@ +# +# 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 mock import call + +from osc_lib import exceptions +from osc_lib import utils + +from openstackclient.tests.unit import utils as tests_utils +from openstackclient.tests.unit.volume.v1 import fakes as volume_fakes +from openstackclient.volume.v1 import volume_type + + +class TestType(volume_fakes.TestVolumev1): + + def setUp(self): + super(TestType, self).setUp() + + self.types_mock = self.app.client_manager.volume.volume_types + self.types_mock.reset_mock() + + +class TestTypeCreate(TestType): + + columns = ( + 'description', + 'id', + 'is_public', + 'name', + ) + + def setUp(self): + super(TestTypeCreate, self).setUp() + + self.new_volume_type = volume_fakes.FakeType.create_one_type( + methods={'set_keys': {'myprop': 'myvalue'}} + ) + self.data = ( + self.new_volume_type.description, + self.new_volume_type.id, + True, + self.new_volume_type.name, + ) + + self.types_mock.create.return_value = self.new_volume_type + # Get the command object to test + self.cmd = volume_type.CreateVolumeType(self.app, None) + + def test_type_create(self): + arglist = [ + self.new_volume_type.name, + ] + verifylist = [ + ("name", self.new_volume_type.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.types_mock.create.assert_called_with( + self.new_volume_type.name, + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestTypeDelete(TestType): + + volume_types = volume_fakes.FakeType.create_types(count=2) + + def setUp(self): + super(TestTypeDelete, self).setUp() + + self.types_mock.get = volume_fakes.FakeType.get_types( + self.volume_types) + self.types_mock.delete.return_value = None + + # Get the command object to mock + self.cmd = volume_type.DeleteVolumeType(self.app, None) + + def test_type_delete(self): + arglist = [ + self.volume_types[0].id + ] + verifylist = [ + ("volume_types", [self.volume_types[0].id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.types_mock.delete.assert_called_with(self.volume_types[0]) + self.assertIsNone(result) + + def test_delete_multiple_types(self): + arglist = [] + for t in self.volume_types: + arglist.append(t.id) + verifylist = [ + ('volume_types', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + calls = [] + for t in self.volume_types: + calls.append(call(t)) + self.types_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_delete_multiple_types_with_exception(self): + arglist = [ + self.volume_types[0].id, + 'unexist_type', + ] + verifylist = [ + ('volume_types', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self.volume_types[0], exceptions.CommandError] + with mock.patch.object(utils, 'find_resource', + side_effect=find_mock_result) as find_mock: + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 volume types failed to delete.', + str(e)) + + find_mock.assert_any_call( + self.types_mock, self.volume_types[0].id) + find_mock.assert_any_call(self.types_mock, 'unexist_type') + + self.assertEqual(2, find_mock.call_count) + self.types_mock.delete.assert_called_once_with( + self.volume_types[0] + ) + + +class TestTypeList(TestType): + + volume_types = volume_fakes.FakeType.create_types() + + columns = ( + "ID", + "Name" + ) + columns_long = ( + "ID", + "Name", + "Properties" + ) + + data = [] + for t in volume_types: + data.append(( + t.id, + t.name, + )) + data_long = [] + for t in volume_types: + data_long.append(( + t.id, + t.name, + utils.format_dict(t.extra_specs), + )) + + def setUp(self): + super(TestTypeList, self).setUp() + + self.types_mock.list.return_value = self.volume_types + # get the command to test + self.cmd = volume_type.ListVolumeType(self.app, None) + + def test_type_list_without_options(self): + arglist = [] + verifylist = [ + ("long", False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.types_mock.list.assert_called_once_with() + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_type_list_with_options(self): + arglist = [ + "--long", + ] + verifylist = [ + ("long", True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.types_mock.list.assert_called_once_with() + self.assertEqual(self.columns_long, columns) + self.assertEqual(self.data_long, list(data)) + + +class TestTypeSet(TestType): + + volume_type = volume_fakes.FakeType.create_one_type( + methods={'set_keys': None}) + + def setUp(self): + super(TestTypeSet, self).setUp() + + self.types_mock.get.return_value = self.volume_type + + # Get the command object to test + self.cmd = volume_type.SetVolumeType(self.app, None) + + def test_type_set_nothing(self): + arglist = [ + self.volume_type.id, + ] + verifylist = [ + ('volume_type', self.volume_type.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.assertIsNone(result) + + def test_type_set_property(self): + arglist = [ + '--property', 'myprop=myvalue', + self.volume_type.id, + ] + verifylist = [ + ('property', {'myprop': 'myvalue'}), + ('volume_type', self.volume_type.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.volume_type.set_keys.assert_called_once_with( + {'myprop': 'myvalue'}) + self.assertIsNone(result) + + +class TestTypeShow(TestType): + + columns = ( + 'description', + 'id', + 'is_public', + 'name', + 'properties', + ) + + def setUp(self): + super(TestTypeShow, self).setUp() + + self.volume_type = volume_fakes.FakeType.create_one_type() + self.data = ( + self.volume_type.description, + self.volume_type.id, + True, + self.volume_type.name, + utils.format_dict(self.volume_type.extra_specs) + ) + + self.types_mock.get.return_value = self.volume_type + + # Get the command object to test + self.cmd = volume_type.ShowVolumeType(self.app, None) + + def test_type_show(self): + arglist = [ + self.volume_type.id + ] + verifylist = [ + ("volume_type", self.volume_type.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.types_mock.get.assert_called_with(self.volume_type.id) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestTypeUnset(TestType): + + volume_type = volume_fakes.FakeType.create_one_type( + methods={'unset_keys': None}) + + def setUp(self): + super(TestTypeUnset, self).setUp() + + self.types_mock.get.return_value = self.volume_type + + # Get the command object to test + self.cmd = volume_type.UnsetVolumeType(self.app, None) + + def test_type_unset(self): + arglist = [ + '--property', 'property', + '--property', 'multi_property', + self.volume_type.id, + ] + verifylist = [ + ('property', ['property', 'multi_property']), + ('volume_type', self.volume_type.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.volume_type.unset_keys.assert_called_once_with( + ['property', 'multi_property']) + self.assertIsNone(result) + + def test_type_unset_failed_with_missing_volume_type_argument(self): + arglist = [ + '--property', 'property', + '--property', 'multi_property', + ] + verifylist = [ + ('property', ['property', 'multi_property']), + ] + + self.assertRaises(tests_utils.ParserException, + self.check_parser, + self.cmd, + arglist, + verifylist) From d6f99b721d3e3978fd6a5438bdacefd5840d3b02 Mon Sep 17 00:00:00 2001 From: Hironori Shiina Date: Thu, 11 Aug 2016 00:02:29 +0900 Subject: [PATCH 1214/3095] Fix regular expression for uptime in hypervisor show Hypervisor show command has a few bugs as follows. - It doesn't trim an extra whitespace in the head of uptime information. - It doesn't display uptime information when the number of user is 1. This patch fixes the regular expression to match uptime information. Change-Id: Ic2f7fd9a9274466717084a0886f95f78e98a9007 Closes-Bug: 1611809 --- openstackclient/compute/v2/hypervisor.py | 5 +++-- openstackclient/tests/unit/compute/v2/test_hypervisor.py | 8 ++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/openstackclient/compute/v2/hypervisor.py b/openstackclient/compute/v2/hypervisor.py index 00625050a7..f9051919e7 100644 --- a/openstackclient/compute/v2/hypervisor.py +++ b/openstackclient/compute/v2/hypervisor.py @@ -98,8 +98,9 @@ def take_action(self, parsed_args): # Extract data from uptime value # format: 0 up 0, 0 users, load average: 0, 0, 0 # example: 17:37:14 up 2:33, 3 users, load average: 0.33, 0.36, 0.34 - m = re.match("(.+)\sup\s+(.+),\s+(.+)\susers,\s+load average:\s(.+)", - uptime['uptime']) + m = re.match( + "\s*(.+)\sup\s+(.+),\s+(.+)\susers?,\s+load average:\s(.+)", + uptime['uptime']) if m: hypervisor["host_time"] = m.group(1) hypervisor["uptime"] = m.group(2) diff --git a/openstackclient/tests/unit/compute/v2/test_hypervisor.py b/openstackclient/tests/unit/compute/v2/test_hypervisor.py index d94a107c26..02ac6ba36e 100644 --- a/openstackclient/tests/unit/compute/v2/test_hypervisor.py +++ b/openstackclient/tests/unit/compute/v2/test_hypervisor.py @@ -159,10 +159,12 @@ def setUp(self): 'free_disk_gb', 'free_ram_mb', 'host_ip', + 'host_time', 'hypervisor_hostname', 'hypervisor_type', 'hypervisor_version', 'id', + 'load_average', 'local_gb', 'local_gb_used', 'memory_mb', @@ -172,6 +174,8 @@ def setUp(self): 'service_id', 'state', 'status', + 'uptime', + 'users', 'vcpus', 'vcpus_used', ) @@ -183,10 +187,12 @@ def setUp(self): 50, 1024, '192.168.0.10', + '01:28:24', self.hypervisor.hypervisor_hostname, 'QEMU', 2004001, self.hypervisor.id, + '0.94, 0.62, 0.50', 50, 0, 1024, @@ -196,6 +202,8 @@ def setUp(self): 1, 'up', 'enabled', + '3 days, 11:15', + '1', 4, 0, ) From 98bafda7e919b57d473f283f96ebb39219cfd2b6 Mon Sep 17 00:00:00 2001 From: Hironori Shiina Date: Thu, 11 Aug 2016 14:57:57 +0900 Subject: [PATCH 1215/3095] Display hypervisor information without uptime Some virt drivers such as ironic virt driver doesn't implement a method to get host uptime. For such drivers, hypervisor show command displays no information although these drivers provides other host information. This patch fixes the command to display hypervisor information in case where a virt driver doesn't provide host uptime by ignoring a HTTPNotImplemented exception. Change-Id: I7bcca5862cd9c05aadaf6192cb80aa651cd77cad Closes-Bug: 1612065 --- openstackclient/compute/v2/hypervisor.py | 29 ++++---- .../tests/unit/compute/v2/test_hypervisor.py | 70 +++++++++++++++++++ 2 files changed, 87 insertions(+), 12 deletions(-) diff --git a/openstackclient/compute/v2/hypervisor.py b/openstackclient/compute/v2/hypervisor.py index f9051919e7..0222e89969 100644 --- a/openstackclient/compute/v2/hypervisor.py +++ b/openstackclient/compute/v2/hypervisor.py @@ -17,6 +17,7 @@ import re +from novaclient import exceptions as nova_exceptions from osc_lib.command import command from osc_lib import utils import six @@ -94,18 +95,22 @@ def take_action(self, parsed_args): if service_host in aggregate.hosts] hypervisor["aggregates"] = member_of - uptime = compute_client.hypervisors.uptime(hypervisor['id'])._info - # Extract data from uptime value - # format: 0 up 0, 0 users, load average: 0, 0, 0 - # example: 17:37:14 up 2:33, 3 users, load average: 0.33, 0.36, 0.34 - m = re.match( - "\s*(.+)\sup\s+(.+),\s+(.+)\susers?,\s+load average:\s(.+)", - uptime['uptime']) - if m: - hypervisor["host_time"] = m.group(1) - hypervisor["uptime"] = m.group(2) - hypervisor["users"] = m.group(3) - hypervisor["load_average"] = m.group(4) + try: + uptime = compute_client.hypervisors.uptime(hypervisor['id'])._info + # Extract data from uptime value + # format: 0 up 0, 0 users, load average: 0, 0, 0 + # example: 17:37:14 up 2:33, 3 users, + # load average: 0.33, 0.36, 0.34 + m = re.match( + "\s*(.+)\sup\s+(.+),\s+(.+)\susers?,\s+load average:\s(.+)", + uptime['uptime']) + if m: + hypervisor["host_time"] = m.group(1) + hypervisor["uptime"] = m.group(2) + hypervisor["users"] = m.group(3) + hypervisor["load_average"] = m.group(4) + except nova_exceptions.HTTPNotImplemented: + pass hypervisor["service_id"] = hypervisor["service"]["id"] hypervisor["service_host"] = hypervisor["service"]["host"] diff --git a/openstackclient/tests/unit/compute/v2/test_hypervisor.py b/openstackclient/tests/unit/compute/v2/test_hypervisor.py index 02ac6ba36e..e39570afc6 100644 --- a/openstackclient/tests/unit/compute/v2/test_hypervisor.py +++ b/openstackclient/tests/unit/compute/v2/test_hypervisor.py @@ -15,6 +15,7 @@ import copy +from novaclient import exceptions as nova_exceptions from osc_lib import exceptions from openstackclient.compute.v2 import hypervisor @@ -227,3 +228,72 @@ def test_hypervisor_show(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + + def test_hyprvisor_show_uptime_not_implemented(self): + arglist = [ + self.hypervisor.hypervisor_hostname, + ] + verifylist = [ + ('hypervisor', self.hypervisor.hypervisor_hostname), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.hypervisors_mock.uptime.side_effect = ( + nova_exceptions.HTTPNotImplemented(501)) + + # 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) + + expected_columns = ( + 'aggregates', + 'cpu_info', + 'current_workload', + 'disk_available_least', + 'free_disk_gb', + 'free_ram_mb', + 'host_ip', + 'hypervisor_hostname', + 'hypervisor_type', + 'hypervisor_version', + 'id', + 'local_gb', + 'local_gb_used', + 'memory_mb', + 'memory_mb_used', + 'running_vms', + 'service_host', + 'service_id', + 'state', + 'status', + 'vcpus', + 'vcpus_used', + ) + expected_data = ( + [], + {'aaa': 'aaa'}, + 0, + 50, + 50, + 1024, + '192.168.0.10', + self.hypervisor.hypervisor_hostname, + 'QEMU', + 2004001, + self.hypervisor.id, + 50, + 0, + 1024, + 512, + 0, + 'aaa', + 1, + 'up', + 'enabled', + 4, + 0, + ) + + self.assertEqual(expected_columns, columns) + self.assertEqual(expected_data, data) From 1a78c76250806340c32e816e5a900abf2c600954 Mon Sep 17 00:00:00 2001 From: Tang Chen Date: Tue, 30 Aug 2016 14:03:22 +0800 Subject: [PATCH 1216/3095] Trivial: Rename ListTransferRequests to ListTransferRequest The class name should use plural. Change-Id: I2de2489ff9aa60c2d1bf12743cbd41f2091739ca --- openstackclient/tests/unit/volume/v1/test_transfer_request.py | 2 +- openstackclient/tests/unit/volume/v2/test_transfer_request.py | 2 +- openstackclient/volume/v1/volume_transfer_request.py | 4 ++-- openstackclient/volume/v2/volume_transfer_request.py | 4 ++-- setup.cfg | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/openstackclient/tests/unit/volume/v1/test_transfer_request.py b/openstackclient/tests/unit/volume/v1/test_transfer_request.py index c3b8dbcea2..e89c505647 100644 --- a/openstackclient/tests/unit/volume/v1/test_transfer_request.py +++ b/openstackclient/tests/unit/volume/v1/test_transfer_request.py @@ -192,7 +192,7 @@ def setUp(self): self.transfer_mock.list.return_value = [self.volume_transfers] # Get the command object to test - self.cmd = volume_transfer_request.ListTransferRequests(self.app, None) + self.cmd = volume_transfer_request.ListTransferRequest(self.app, None) def test_transfer_list_without_argument(self): arglist = [] diff --git a/openstackclient/tests/unit/volume/v2/test_transfer_request.py b/openstackclient/tests/unit/volume/v2/test_transfer_request.py index a18e4c52dd..b4f890896c 100644 --- a/openstackclient/tests/unit/volume/v2/test_transfer_request.py +++ b/openstackclient/tests/unit/volume/v2/test_transfer_request.py @@ -192,7 +192,7 @@ def setUp(self): self.transfer_mock.list.return_value = [self.volume_transfers] # Get the command object to test - self.cmd = volume_transfer_request.ListTransferRequests(self.app, None) + self.cmd = volume_transfer_request.ListTransferRequest(self.app, None) def test_transfer_list_without_argument(self): arglist = [] diff --git a/openstackclient/volume/v1/volume_transfer_request.py b/openstackclient/volume/v1/volume_transfer_request.py index 8c5055282a..a985f8e598 100644 --- a/openstackclient/volume/v1/volume_transfer_request.py +++ b/openstackclient/volume/v1/volume_transfer_request.py @@ -91,11 +91,11 @@ def take_action(self, parsed_args): raise exceptions.CommandError(msg) -class ListTransferRequests(command.Lister): +class ListTransferRequest(command.Lister): """Lists all volume transfer requests.""" def get_parser(self, prog_name): - parser = super(ListTransferRequests, self).get_parser(prog_name) + parser = super(ListTransferRequest, self).get_parser(prog_name) parser.add_argument( '--all-projects', dest='all_projects', diff --git a/openstackclient/volume/v2/volume_transfer_request.py b/openstackclient/volume/v2/volume_transfer_request.py index f5de8d7ae0..8e79807cab 100644 --- a/openstackclient/volume/v2/volume_transfer_request.py +++ b/openstackclient/volume/v2/volume_transfer_request.py @@ -91,11 +91,11 @@ def take_action(self, parsed_args): raise exceptions.CommandError(msg) -class ListTransferRequests(command.Lister): +class ListTransferRequest(command.Lister): """Lists all volume transfer requests.""" def get_parser(self, prog_name): - parser = super(ListTransferRequests, self).get_parser(prog_name) + parser = super(ListTransferRequest, self).get_parser(prog_name) parser.add_argument( '--all-projects', dest='all_projects', diff --git a/setup.cfg b/setup.cfg index 81232ef183..3e6486a23d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -485,7 +485,7 @@ openstack.volume.v1 = volume_transfer_request_create = openstackclient.volume.v1.volume_transfer_request:CreateTransferRequest volume_transfer_request_delete = openstackclient.volume.v1.volume_transfer_request:DeleteTransferRequest - volume_transfer_request_list = openstackclient.volume.v1.volume_transfer_request:ListTransferRequests + volume_transfer_request_list = openstackclient.volume.v1.volume_transfer_request:ListTransferRequest openstack.volume.v2 = backup_create = openstackclient.volume.v2.backup:CreateBackup @@ -535,7 +535,7 @@ openstack.volume.v2 = volume_transfer_request_create = openstackclient.volume.v2.volume_transfer_request:CreateTransferRequest volume_transfer_request_delete = openstackclient.volume.v2.volume_transfer_request:DeleteTransferRequest - volume_transfer_request_list = openstackclient.volume.v2.volume_transfer_request:ListTransferRequests + volume_transfer_request_list = openstackclient.volume.v2.volume_transfer_request:ListTransferRequest [build_sphinx] source-dir = doc/source From 4e71e9da6b729212516eceb0aa556ecbc4dfdf36 Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Mon, 8 Aug 2016 15:56:51 +0800 Subject: [PATCH 1217/3095] Unit tests of quota command refactor Do refactor for quota command related unit tests, remove useless code, add new tests to cover '--volume-type' and '--class' option in "quota set" command and add the volume quota fake object. Change-Id: Iaf214740e98db1bfb4c739e810bac1c5ba6e9625 --- .../tests/unit/common/test_quota.py | 139 ++++++++++++------ openstackclient/tests/unit/volume/v2/fakes.py | 17 +++ 2 files changed, 112 insertions(+), 44 deletions(-) diff --git a/openstackclient/tests/unit/common/test_quota.py b/openstackclient/tests/unit/common/test_quota.py index 4a80a2b255..7edd4186b0 100644 --- a/openstackclient/tests/unit/common/test_quota.py +++ b/openstackclient/tests/unit/common/test_quota.py @@ -18,6 +18,7 @@ from openstackclient.tests.unit import fakes from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes from openstackclient.tests.unit.network.v2 import fakes as network_fakes +from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes class FakeQuotaResource(fakes.FakeResource): @@ -43,10 +44,7 @@ def setUp(self): self.quotas_mock.reset_mock() self.quotas_class_mock = self.app.client_manager.compute.quota_classes self.quotas_class_mock.reset_mock() - volume_mock = mock.Mock() - volume_mock.quotas = mock.Mock() - self.app.client_manager.volume = volume_mock - self.volume_quotas_mock = volume_mock.quotas + self.volume_quotas_mock = self.app.client_manager.volume.quotas self.volume_quotas_mock.reset_mock() self.volume_quotas_class_mock = \ self.app.client_manager.volume.quota_classes @@ -66,24 +64,12 @@ class TestQuotaSet(TestQuota): def setUp(self): super(TestQuotaSet, self).setUp() - self.quotas_mock.find.return_value = FakeQuotaResource( - None, - copy.deepcopy(compute_fakes.QUOTA), - loaded=True, - ) - self.quotas_mock.update.return_value = FakeQuotaResource( None, copy.deepcopy(compute_fakes.QUOTA), loaded=True, ) - self.volume_quotas_mock.find.return_value = FakeQuotaResource( - None, - copy.deepcopy(compute_fakes.QUOTA), - loaded=True, - ) - self.volume_quotas_mock.update.return_value = FakeQuotaResource( None, copy.deepcopy(compute_fakes.QUOTA), @@ -141,7 +127,7 @@ def test_quota_set(self): self.app.client_manager.network_endpoint_enabled = False parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) kwargs = { 'floating_ips': compute_fakes.floating_ip_num, @@ -161,38 +147,72 @@ def test_quota_set(self): 'server_group_members': compute_fakes.servgroup_members_num, } - self.quotas_mock.update.assert_called_with( + self.quotas_mock.update.assert_called_once_with( identity_fakes.project_id, **kwargs ) + self.assertIsNone(result) def test_quota_set_volume(self): arglist = [ - '--gigabytes', str(compute_fakes.floating_ip_num), - '--snapshots', str(compute_fakes.fix_ip_num), - '--volumes', str(compute_fakes.injected_file_num), + '--gigabytes', str(volume_fakes.QUOTA['gigabytes']), + '--snapshots', str(volume_fakes.QUOTA['snapshots']), + '--volumes', str(volume_fakes.QUOTA['volumes']), identity_fakes.project_name, ] verifylist = [ - ('gigabytes', compute_fakes.floating_ip_num), - ('snapshots', compute_fakes.fix_ip_num), - ('volumes', compute_fakes.injected_file_num), + ('gigabytes', volume_fakes.QUOTA['gigabytes']), + ('snapshots', volume_fakes.QUOTA['snapshots']), + ('volumes', volume_fakes.QUOTA['volumes']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) + + kwargs = { + 'gigabytes': volume_fakes.QUOTA['gigabytes'], + 'snapshots': volume_fakes.QUOTA['snapshots'], + 'volumes': volume_fakes.QUOTA['volumes'], + } + + self.volume_quotas_mock.update.assert_called_once_with( + identity_fakes.project_id, + **kwargs + ) + + self.assertIsNone(result) + + def test_quota_set_volume_with_volume_type(self): + arglist = [ + '--gigabytes', str(volume_fakes.QUOTA['gigabytes']), + '--snapshots', str(volume_fakes.QUOTA['snapshots']), + '--volumes', str(volume_fakes.QUOTA['volumes']), + '--volume-type', 'volume_type_backend', + identity_fakes.project_name, + ] + verifylist = [ + ('gigabytes', volume_fakes.QUOTA['gigabytes']), + ('snapshots', volume_fakes.QUOTA['snapshots']), + ('volumes', volume_fakes.QUOTA['volumes']), + ('volume_type', 'volume_type_backend'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) kwargs = { - 'gigabytes': compute_fakes.floating_ip_num, - 'snapshots': compute_fakes.fix_ip_num, - 'volumes': compute_fakes.injected_file_num, + 'gigabytes_volume_type_backend': volume_fakes.QUOTA['gigabytes'], + 'snapshots_volume_type_backend': volume_fakes.QUOTA['snapshots'], + 'volumes_volume_type_backend': volume_fakes.QUOTA['volumes'], } - self.volume_quotas_mock.update.assert_called_with( + self.volume_quotas_mock.update.assert_called_once_with( identity_fakes.project_id, **kwargs ) + self.assertIsNone(result) def test_quota_set_network(self): arglist = [ @@ -228,7 +248,7 @@ def test_quota_set_network(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) kwargs = { 'subnet': network_fakes.QUOTA['subnet'], 'network': network_fakes.QUOTA['network'], @@ -244,10 +264,38 @@ def test_quota_set_network(self): 'member': network_fakes.QUOTA['member'], 'health_monitor': network_fakes.QUOTA['health_monitor'], } - self.network_mock.update_quota.assert_called_with( + self.network_mock.update_quota.assert_called_once_with( identity_fakes.project_id, **kwargs ) + self.assertIsNone(result) + + def test_quota_set_with_class(self): + arglist = [ + '--instances', str(compute_fakes.instance_num), + '--volumes', str(volume_fakes.QUOTA['volumes']), + '--network', str(network_fakes.QUOTA['network']), + '--class', + identity_fakes.project_name, + ] + verifylist = [ + ('instances', compute_fakes.instance_num), + ('volumes', volume_fakes.QUOTA['volumes']), + ('network', network_fakes.QUOTA['network']), + ('quota_class', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.quotas_class_mock.update.assert_called_once_with( + identity_fakes.project_id, + **{'instances': compute_fakes.instance_num} + ) + self.volume_quotas_class_mock.update.assert_called_once_with( + identity_fakes.project_id, + **{'volumes': volume_fakes.QUOTA['volumes']} + ) + self.assertNotCalled(self.network_mock.update_quota) + self.assertIsNone(result) class TestQuotaShow(TestQuota): @@ -269,13 +317,13 @@ def setUp(self): self.volume_quotas_mock.get.return_value = FakeQuotaResource( None, - copy.deepcopy(compute_fakes.QUOTA), + copy.deepcopy(volume_fakes.QUOTA), loaded=True, ) self.volume_quotas_mock.defaults.return_value = FakeQuotaResource( None, - copy.deepcopy(compute_fakes.QUOTA), + copy.deepcopy(volume_fakes.QUOTA), loaded=True, ) @@ -297,7 +345,7 @@ def setUp(self): self.volume_quotas_class_mock.get.return_value = FakeQuotaResource( None, - copy.deepcopy(compute_fakes.QUOTA), + copy.deepcopy(volume_fakes.QUOTA), loaded=True, ) @@ -328,10 +376,11 @@ def test_quota_show(self): self.cmd.take_action(parsed_args) - self.quotas_mock.get.assert_called_with(identity_fakes.project_id) - self.volume_quotas_mock.get.assert_called_with( + self.quotas_mock.get.assert_called_once_with(identity_fakes.project_id) + self.volume_quotas_mock.get.assert_called_once_with( + identity_fakes.project_id) + self.network.get_quota.assert_called_once_with( identity_fakes.project_id) - self.network.get_quota.assert_called_with(identity_fakes.project_id) def test_quota_show_with_default(self): arglist = [ @@ -347,8 +396,9 @@ def test_quota_show_with_default(self): self.cmd.take_action(parsed_args) - self.quotas_mock.defaults.assert_called_with(identity_fakes.project_id) - self.volume_quotas_mock.defaults.assert_called_with( + self.quotas_mock.defaults.assert_called_once_with( + identity_fakes.project_id) + self.volume_quotas_mock.defaults.assert_called_once_with( identity_fakes.project_id) def test_quota_show_with_class(self): @@ -365,9 +415,9 @@ def test_quota_show_with_class(self): self.cmd.take_action(parsed_args) - self.quotas_class_mock.get.assert_called_with( + self.quotas_class_mock.get.assert_called_once_with( identity_fakes.project_id) - self.volume_quotas_class_mock.get.assert_called_with( + self.volume_quotas_class_mock.get.assert_called_once_with( identity_fakes.project_id) def test_quota_show_no_project(self): @@ -375,7 +425,8 @@ def test_quota_show_no_project(self): self.cmd.take_action(parsed_args) - self.quotas_mock.get.assert_called_with(identity_fakes.project_id) - self.volume_quotas_mock.get.assert_called_with( + self.quotas_mock.get.assert_called_once_with(identity_fakes.project_id) + self.volume_quotas_mock.get.assert_called_once_with( + identity_fakes.project_id) + self.network.get_quota.assert_called_once_with( identity_fakes.project_id) - self.network.get_quota.assert_called_with(identity_fakes.project_id) diff --git a/openstackclient/tests/unit/volume/v2/fakes.py b/openstackclient/tests/unit/volume/v2/fakes.py index e79a9407f9..babdc1f7c6 100644 --- a/openstackclient/tests/unit/volume/v2/fakes.py +++ b/openstackclient/tests/unit/volume/v2/fakes.py @@ -25,6 +25,19 @@ from openstackclient.tests.unit import utils +QUOTA = { + "gigabytes": 1000, + "volumes": 11, + "snapshots": 10, + "backups": 10, + "backup_gigabytes": 1000, + "per_volume_gigabytes": -1, + "gigabytes_volume_type_backend": -1, + "volumes_volume_type_backend": -1, + "snapshots_volume_type_backend": -1, +} + + class FakeTransfer(object): """Fake one or more Transfer.""" @@ -207,6 +220,10 @@ def __init__(self, **kwargs): self.transfers.resource_class = fakes.FakeResource(None, {}) self.services = mock.Mock() self.services.resource_class = fakes.FakeResource(None, {}) + self.quotas = mock.Mock() + self.quotas.resource_class = fakes.FakeResource(None, {}) + self.quota_classes = mock.Mock() + self.quota_classes.resource_class = fakes.FakeResource(None, {}) self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] From 6fba7163e85a436d1fe0660d9932a53d06b1a343 Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Fri, 5 Aug 2016 12:07:13 +0800 Subject: [PATCH 1218/3095] Set quota "per_volume_gigabytes", "backup_gigabytes" and "backups" "per_volume_gigabytes", "backup_gigabytes" and "backups" items can be shown in "openstack quota show" command, but can't be updated by "openstack quota set". This patch fix the issue. Change-Id: I47db5a69d4e4ef6e140f2735257c83e1fb052760 Closes-Bug: #1609767 --- doc/source/command-objects/quota.rst | 26 ++++++++++++++++--- openstackclient/common/quota.py | 12 ++++++++- .../tests/unit/common/test_quota.py | 22 ++++++++++++++++ .../notes/bug-1609767-0602edc4408c2dc6.yaml | 5 ++++ 4 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/bug-1609767-0602edc4408c2dc6.yaml diff --git a/doc/source/command-objects/quota.rst b/doc/source/command-objects/quota.rst index 381601db77..4dc5b17150 100644 --- a/doc/source/command-objects/quota.rst +++ b/doc/source/command-objects/quota.rst @@ -2,9 +2,10 @@ quota ===== -Resource quotas appear in multiple APIs, OpenStackClient presents them as a single object with multiple properties. +Resource quotas appear in multiple APIs, OpenStackClient presents them as a +single object with multiple properties. -Block Storage v1, Compute v2, Network v2 +Block Storage v1, v2, Compute v2, Network v2 quota set --------- @@ -29,7 +30,10 @@ Set quotas for project [--server-group-members ] # Block Storage settings + [--backups ] + [--backup-gigabytes ] [--gigabytes ] + [--per-volume-gigabytes ] [--snapshots ] [--volumes ] [--volume-type ] @@ -70,7 +74,10 @@ Set quotas for class [--server-group-members ] # Block Storage settings + [--backups ] + [--backup-gigabytes ] [--gigabytes ] + [--per-volume-gigabytes ] [--snapshots ] [--volumes ] @@ -136,10 +143,22 @@ Set quotas for class New value for the injected-path-size quota +.. option:: --backups + + New value for the backups quota + +.. option:: --backup-gigabytes + + New value for the backup gigabytes quota + .. option:: --gigabytes New value for the gigabytes quota +.. option:: --per-volume-gigabytes + + New value for the gigabytes quota of per volume + .. option:: --volumes New value for the volumes quota @@ -150,7 +169,8 @@ Set quotas for class .. option:: --volume-type - Set quotas for a specific + Set quotas for a specific . The supported quotas are: + gigabytes, snapshots, volumes. .. option:: --networks diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index 5d53171c33..8ae6f7d485 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -43,11 +43,20 @@ } VOLUME_QUOTAS = { + 'backups': 'backups', + 'backup_gigabytes': 'backup-gigabytes', 'gigabytes': 'gigabytes', + 'per_volume_gigabytes': 'per-volume-gigabytes', 'snapshots': 'snapshots', 'volumes': 'volumes', } +IMPACT_VOLUME_TYPE_QUOTAS = [ + 'gigabytes', + 'snapshots', + 'volumes', +] + NOVA_NETWORK_QUOTAS = { 'floating_ips': 'floating-ips', 'security_group_rules': 'secgroup-rules', @@ -128,7 +137,8 @@ def take_action(self, parsed_args): for k, v in VOLUME_QUOTAS.items(): value = getattr(parsed_args, k, None) if value is not None: - if parsed_args.volume_type: + if (parsed_args.volume_type and + k in IMPACT_VOLUME_TYPE_QUOTAS): k = k + '_%s' % parsed_args.volume_type volume_kwargs[k] = value diff --git a/openstackclient/tests/unit/common/test_quota.py b/openstackclient/tests/unit/common/test_quota.py index 7edd4186b0..cbe4cb80ec 100644 --- a/openstackclient/tests/unit/common/test_quota.py +++ b/openstackclient/tests/unit/common/test_quota.py @@ -158,12 +158,20 @@ def test_quota_set_volume(self): '--gigabytes', str(volume_fakes.QUOTA['gigabytes']), '--snapshots', str(volume_fakes.QUOTA['snapshots']), '--volumes', str(volume_fakes.QUOTA['volumes']), + '--backups', str(volume_fakes.QUOTA['backups']), + '--backup-gigabytes', str(volume_fakes.QUOTA['backup_gigabytes']), + '--per-volume-gigabytes', + str(volume_fakes.QUOTA['per_volume_gigabytes']), identity_fakes.project_name, ] verifylist = [ ('gigabytes', volume_fakes.QUOTA['gigabytes']), ('snapshots', volume_fakes.QUOTA['snapshots']), ('volumes', volume_fakes.QUOTA['volumes']), + ('backups', volume_fakes.QUOTA['backups']), + ('backup_gigabytes', volume_fakes.QUOTA['backup_gigabytes']), + ('per_volume_gigabytes', + volume_fakes.QUOTA['per_volume_gigabytes']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -174,6 +182,9 @@ def test_quota_set_volume(self): 'gigabytes': volume_fakes.QUOTA['gigabytes'], 'snapshots': volume_fakes.QUOTA['snapshots'], 'volumes': volume_fakes.QUOTA['volumes'], + 'backups': volume_fakes.QUOTA['backups'], + 'backup_gigabytes': volume_fakes.QUOTA['backup_gigabytes'], + 'per_volume_gigabytes': volume_fakes.QUOTA['per_volume_gigabytes'] } self.volume_quotas_mock.update.assert_called_once_with( @@ -188,6 +199,10 @@ def test_quota_set_volume_with_volume_type(self): '--gigabytes', str(volume_fakes.QUOTA['gigabytes']), '--snapshots', str(volume_fakes.QUOTA['snapshots']), '--volumes', str(volume_fakes.QUOTA['volumes']), + '--backups', str(volume_fakes.QUOTA['backups']), + '--backup-gigabytes', str(volume_fakes.QUOTA['backup_gigabytes']), + '--per-volume-gigabytes', + str(volume_fakes.QUOTA['per_volume_gigabytes']), '--volume-type', 'volume_type_backend', identity_fakes.project_name, ] @@ -195,6 +210,10 @@ def test_quota_set_volume_with_volume_type(self): ('gigabytes', volume_fakes.QUOTA['gigabytes']), ('snapshots', volume_fakes.QUOTA['snapshots']), ('volumes', volume_fakes.QUOTA['volumes']), + ('backups', volume_fakes.QUOTA['backups']), + ('backup_gigabytes', volume_fakes.QUOTA['backup_gigabytes']), + ('per_volume_gigabytes', + volume_fakes.QUOTA['per_volume_gigabytes']), ('volume_type', 'volume_type_backend'), ] @@ -206,6 +225,9 @@ def test_quota_set_volume_with_volume_type(self): 'gigabytes_volume_type_backend': volume_fakes.QUOTA['gigabytes'], 'snapshots_volume_type_backend': volume_fakes.QUOTA['snapshots'], 'volumes_volume_type_backend': volume_fakes.QUOTA['volumes'], + 'backups': volume_fakes.QUOTA['backups'], + 'backup_gigabytes': volume_fakes.QUOTA['backup_gigabytes'], + 'per_volume_gigabytes': volume_fakes.QUOTA['per_volume_gigabytes'] } self.volume_quotas_mock.update.assert_called_once_with( diff --git a/releasenotes/notes/bug-1609767-0602edc4408c2dc6.yaml b/releasenotes/notes/bug-1609767-0602edc4408c2dc6.yaml new file mode 100644 index 0000000000..edb241fbe9 --- /dev/null +++ b/releasenotes/notes/bug-1609767-0602edc4408c2dc6.yaml @@ -0,0 +1,5 @@ +--- +features: + - Support to update ``per_volume_gigabytes``, ``backup_gigabytes`` and + ``backups`` quota in ``quota set`` command. + [Bug `1609767 `_] From 8d59b31c72468d82cbe79a6cf24df28fb3b6cf06 Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Wed, 29 Jun 2016 14:43:38 +0800 Subject: [PATCH 1219/3095] Add functional tests for unset multiple volume type props CinderClient bug/1596511 cause that OSC can't unset multiple properties of volume type, obviously no functional test case cover the part of logic in OSC, that make the issue raising, the patch add functional test cases for this scenario. Change-Id: I42cf9ac8cc72ccc2f1208926d8faf7b80ee2d288 Partial-Bug: #1596511 Depends-On: I60378a32cdc52aacdf869d69b246dec7eb6cdb77 --- .../functional/volume/v1/test_volume_type.py | 15 +++++++++++++++ .../functional/volume/v2/test_volume_type.py | 15 +++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/openstackclient/tests/functional/volume/v1/test_volume_type.py b/openstackclient/tests/functional/volume/v1/test_volume_type.py index 538545abc6..955759b6b2 100644 --- a/openstackclient/tests/functional/volume/v1/test_volume_type.py +++ b/openstackclient/tests/functional/volume/v1/test_volume_type.py @@ -61,6 +61,21 @@ def test_volume_type_set_unset_properties(self): raw_output = self.openstack('volume type show ' + self.NAME + opts) self.assertEqual("c='d'\n", raw_output) + def test_volume_type_set_unset_multiple_properties(self): + raw_output = self.openstack( + 'volume type set --property a=b --property c=d ' + self.NAME) + self.assertEqual("", raw_output) + + opts = self.get_opts(["properties"]) + raw_output = self.openstack('volume type show ' + self.NAME + opts) + self.assertEqual("a='b', c='d'\n", raw_output) + + raw_output = self.openstack( + 'volume type unset --property a --property c ' + self.NAME) + self.assertEqual("", raw_output) + raw_output = self.openstack('volume type show ' + self.NAME + opts) + self.assertEqual("\n", raw_output) + def test_multi_delete(self): vol_type1 = uuid.uuid4().hex vol_type2 = uuid.uuid4().hex diff --git a/openstackclient/tests/functional/volume/v2/test_volume_type.py b/openstackclient/tests/functional/volume/v2/test_volume_type.py index b62cbb3908..d8bd3a96c1 100644 --- a/openstackclient/tests/functional/volume/v2/test_volume_type.py +++ b/openstackclient/tests/functional/volume/v2/test_volume_type.py @@ -62,6 +62,21 @@ def test_volume_type_set_unset_properties(self): raw_output = self.openstack('volume type show ' + self.NAME + opts) self.assertEqual("c='d'\n", raw_output) + def test_volume_type_set_unset_multiple_properties(self): + raw_output = self.openstack( + 'volume type set --property a=b --property c=d ' + self.NAME) + self.assertEqual("", raw_output) + + opts = self.get_opts(["properties"]) + raw_output = self.openstack('volume type show ' + self.NAME + opts) + self.assertEqual("a='b', c='d'\n", raw_output) + + raw_output = self.openstack( + 'volume type unset --property a --property c ' + self.NAME) + self.assertEqual("", raw_output) + raw_output = self.openstack('volume type show ' + self.NAME + opts) + self.assertEqual("\n", raw_output) + def test_volume_type_set_unset_project(self): raw_output = self.openstack( 'volume type set --project admin ' + self.NAME) From 6f326acd260d035cb024f0c5e3ef2237277d8b37 Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Mon, 11 Jul 2016 09:55:22 +0800 Subject: [PATCH 1220/3095] Support fetching network project default quota Neutron server and openstacksdk had supported to fetch network project default quota, this patch add the CLI support in openstackclient. Change-Id: If0ef74c268c41a866c62156da0603a40ae4e6e31 Closes-Bug: #1204956 Depends-On: I6a4e2a146351dd1e7d652442511f1ef2c279da42 --- openstackclient/common/quota.py | 9 +++++++-- openstackclient/tests/functional/common/test_quota.py | 11 +++++++++++ openstackclient/tests/unit/common/test_quota.py | 9 +++++++++ releasenotes/notes/bug-1204956-af47c7f34ecc19c3.yaml | 5 +++++ 4 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/bug-1204956-af47c7f34ecc19c3.yaml diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index 8ae6f7d485..aabfa5d54b 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -247,11 +247,16 @@ def get_compute_volume_quota(self, client, parsed_args): return quota._info def get_network_quota(self, parsed_args): - if parsed_args.quota_class or parsed_args.default: + if parsed_args.quota_class: return {} if self.app.client_manager.is_network_endpoint_enabled(): project = self._get_project(parsed_args) - return self.app.client_manager.network.get_quota(project) + client = self.app.client_manager.network + if parsed_args.default: + network_quota = client.get_quota_default(project) + else: + network_quota = client.get_quota(project) + return network_quota else: return {} diff --git a/openstackclient/tests/functional/common/test_quota.py b/openstackclient/tests/functional/common/test_quota.py index fd45be38ab..9687cdb0d5 100644 --- a/openstackclient/tests/functional/common/test_quota.py +++ b/openstackclient/tests/functional/common/test_quota.py @@ -17,6 +17,7 @@ class QuotaTests(base.TestCase): """Functional tests for quota. """ # Test quota information for compute, network and volume. EXPECTED_FIELDS = ['instances', 'networks', 'volumes'] + EXPECTED_CLASS_FIELDS = ['instances', 'volumes'] PROJECT_NAME = None @classmethod @@ -40,3 +41,13 @@ def test_quota_show_default_project(self): raw_output = self.openstack('quota show') for expected_field in self.EXPECTED_FIELDS: self.assertIn(expected_field, raw_output) + + def test_quota_show_with_default_option(self): + raw_output = self.openstack('quota show --default') + for expected_field in self.EXPECTED_FIELDS: + self.assertIn(expected_field, raw_output) + + def test_quota_show_with_class_option(self): + raw_output = self.openstack('quota show --class') + for expected_field in self.EXPECTED_CLASS_FIELDS: + self.assertIn(expected_field, raw_output) diff --git a/openstackclient/tests/unit/common/test_quota.py b/openstackclient/tests/unit/common/test_quota.py index cbe4cb80ec..ac03cb6048 100644 --- a/openstackclient/tests/unit/common/test_quota.py +++ b/openstackclient/tests/unit/common/test_quota.py @@ -383,6 +383,8 @@ def setUp(self): ) self.network = self.app.client_manager.network self.network.get_quota = mock.Mock(return_value=network_fakes.QUOTA) + self.network.get_quota_default = mock.Mock( + return_value=network_fakes.QUOTA) self.cmd = quota.ShowQuota(self.app, None) @@ -403,6 +405,7 @@ def test_quota_show(self): identity_fakes.project_id) self.network.get_quota.assert_called_once_with( identity_fakes.project_id) + self.assertNotCalled(self.network.get_quota_default) def test_quota_show_with_default(self): arglist = [ @@ -422,6 +425,9 @@ def test_quota_show_with_default(self): identity_fakes.project_id) self.volume_quotas_mock.defaults.assert_called_once_with( identity_fakes.project_id) + self.network.get_quota_default.assert_called_once_with( + identity_fakes.project_id) + self.assertNotCalled(self.network.get_quota) def test_quota_show_with_class(self): arglist = [ @@ -441,6 +447,8 @@ def test_quota_show_with_class(self): identity_fakes.project_id) self.volume_quotas_class_mock.get.assert_called_once_with( identity_fakes.project_id) + self.assertNotCalled(self.network.get_quota) + self.assertNotCalled(self.network.get_quota_default) def test_quota_show_no_project(self): parsed_args = self.check_parser(self.cmd, [], []) @@ -452,3 +460,4 @@ def test_quota_show_no_project(self): identity_fakes.project_id) self.network.get_quota.assert_called_once_with( identity_fakes.project_id) + self.assertNotCalled(self.network.get_quota_default) diff --git a/releasenotes/notes/bug-1204956-af47c7f34ecc19c3.yaml b/releasenotes/notes/bug-1204956-af47c7f34ecc19c3.yaml new file mode 100644 index 0000000000..65def078b7 --- /dev/null +++ b/releasenotes/notes/bug-1204956-af47c7f34ecc19c3.yaml @@ -0,0 +1,5 @@ +--- +features: + - Supported to fetch network project default quota with command + ``quota show --default``. + [Bug `1204956 `_] From 2a01ede6b65b20484776aa02d4c1ad9a7a82c240 Mon Sep 17 00:00:00 2001 From: qtang Date: Fri, 5 Aug 2016 16:49:08 +0800 Subject: [PATCH 1221/3095] Fix openstack quota set/show --class not work identity_client should not be used for quota class operation. Update code to fix the qutoa class set/show issue. Change-Id: I71c59c08a0d5da29982497f589e1efe131997f21 Closes-Bug: #1609233 --- openstackclient/common/quota.py | 32 ++++---- .../tests/functional/common/test_quota.py | 13 +++- .../tests/unit/common/test_quota.py | 77 ++++++++++++++++--- .../notes/bug-1609233-90b2ddf8d941050e.yaml | 4 + 4 files changed, 97 insertions(+), 29 deletions(-) create mode 100644 releasenotes/notes/bug-1609233-90b2ddf8d941050e.yaml diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index aabfa5d54b..96a50ad57e 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -154,36 +154,34 @@ def take_action(self, parsed_args): if value is not None: compute_kwargs[k] = value - if parsed_args.project: - project = utils.find_resource( - identity_client.projects, - parsed_args.project, - ) - if parsed_args.quota_class: if compute_kwargs: compute_client.quota_classes.update( - project.id, + parsed_args.project, **compute_kwargs) if volume_kwargs: volume_client.quota_classes.update( - project.id, + parsed_args.project, **volume_kwargs) if network_kwargs: sys.stderr.write("Network quotas are ignored since quota class" "is not supported.") else: + project = utils.find_resource( + identity_client.projects, + parsed_args.project, + ).id if compute_kwargs: compute_client.quotas.update( - project.id, + project, **compute_kwargs) if volume_kwargs: volume_client.quotas.update( - project.id, + project, **volume_kwargs) if network_kwargs: network_client.update_quota( - project.id, + project, **network_kwargs) @@ -230,15 +228,15 @@ def _get_project(self, parsed_args): return project def get_compute_volume_quota(self, client, parsed_args): - project = self._get_project(parsed_args) - try: if parsed_args.quota_class: - quota = client.quota_classes.get(project) - elif parsed_args.default: - quota = client.quotas.defaults(project) + quota = client.quota_classes.get(parsed_args.project) else: - quota = client.quotas.get(project) + project = self._get_project(parsed_args) + if parsed_args.default: + quota = client.quotas.defaults(project) + else: + quota = client.quotas.get(project) except Exception as e: if type(e).__name__ == 'EndpointNotFound': return {} diff --git a/openstackclient/tests/functional/common/test_quota.py b/openstackclient/tests/functional/common/test_quota.py index 9687cdb0d5..c1de9aa92d 100644 --- a/openstackclient/tests/functional/common/test_quota.py +++ b/openstackclient/tests/functional/common/test_quota.py @@ -26,8 +26,8 @@ def setUpClass(cls): cls.get_openstack_configuration_value('auth.project_name') def test_quota_set(self): - self.openstack('quota set --instances 11 --volumes 11 --networks 11 ' - + self.PROJECT_NAME) + self.openstack('quota set --instances 11 --volumes 11 --networks 11 ' + + self.PROJECT_NAME) opts = self.get_opts(self.EXPECTED_FIELDS) raw_output = self.openstack('quota show ' + self.PROJECT_NAME + opts) self.assertEqual("11\n11\n11\n", raw_output) @@ -51,3 +51,12 @@ def test_quota_show_with_class_option(self): raw_output = self.openstack('quota show --class') for expected_field in self.EXPECTED_CLASS_FIELDS: self.assertIn(expected_field, raw_output) + + def test_quota_class_set(self): + class_name = 'default' + class_expected_fields = ['instances', 'volumes'] + self.openstack('quota set --instances 11 --volumes 11 --class ' + + class_name) + opts = self.get_opts(class_expected_fields) + raw_output = self.openstack('quota show --class ' + class_name + opts) + self.assertEqual("11\n11\n", raw_output) diff --git a/openstackclient/tests/unit/common/test_quota.py b/openstackclient/tests/unit/common/test_quota.py index ac03cb6048..7e242b8811 100644 --- a/openstackclient/tests/unit/common/test_quota.py +++ b/openstackclient/tests/unit/common/test_quota.py @@ -82,6 +82,18 @@ def setUp(self): loaded=True, ) + self.quotas_class_mock.update.return_value = FakeQuotaResource( + None, + copy.deepcopy(compute_fakes.QUOTA), + loaded=True, + ) + + self.volume_quotas_class_mock.update.return_value = FakeQuotaResource( + None, + copy.deepcopy(compute_fakes.QUOTA), + loaded=True, + ) + self.network_mock = self.app.client_manager.network self.network_mock.update_quota = mock.Mock() @@ -294,27 +306,72 @@ def test_quota_set_network(self): def test_quota_set_with_class(self): arglist = [ + '--injected-files', str(compute_fakes.injected_file_num), + '--injected-file-size', str(compute_fakes.injected_file_size_num), + '--injected-path-size', str(compute_fakes.injected_path_size_num), + '--key-pairs', str(compute_fakes.key_pair_num), + '--cores', str(compute_fakes.core_num), + '--ram', str(compute_fakes.ram_num), '--instances', str(compute_fakes.instance_num), + '--properties', str(compute_fakes.property_num), + '--server-groups', str(compute_fakes.servgroup_num), + '--server-group-members', str(compute_fakes.servgroup_members_num), + '--gigabytes', str(compute_fakes.floating_ip_num), + '--snapshots', str(compute_fakes.fix_ip_num), '--volumes', str(volume_fakes.QUOTA['volumes']), '--network', str(network_fakes.QUOTA['network']), - '--class', - identity_fakes.project_name, + '--class', identity_fakes.project_name, ] verifylist = [ + ('injected_files', compute_fakes.injected_file_num), + ('injected_file_content_bytes', + compute_fakes.injected_file_size_num), + ('injected_file_path_bytes', compute_fakes.injected_path_size_num), + ('key_pairs', compute_fakes.key_pair_num), + ('cores', compute_fakes.core_num), + ('ram', compute_fakes.ram_num), ('instances', compute_fakes.instance_num), + ('metadata_items', compute_fakes.property_num), + ('server_groups', compute_fakes.servgroup_num), + ('server_group_members', compute_fakes.servgroup_members_num), + ('gigabytes', compute_fakes.floating_ip_num), + ('snapshots', compute_fakes.fix_ip_num), ('volumes', volume_fakes.QUOTA['volumes']), ('network', network_fakes.QUOTA['network']), + ('project', identity_fakes.project_name), ('quota_class', True), ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) - self.quotas_class_mock.update.assert_called_once_with( - identity_fakes.project_id, - **{'instances': compute_fakes.instance_num} + + kwargs_compute = { + 'injected_files': compute_fakes.injected_file_num, + 'injected_file_content_bytes': + compute_fakes.injected_file_size_num, + 'injected_file_path_bytes': compute_fakes.injected_path_size_num, + 'key_pairs': compute_fakes.key_pair_num, + 'cores': compute_fakes.core_num, + 'ram': compute_fakes.ram_num, + 'instances': compute_fakes.instance_num, + 'metadata_items': compute_fakes.property_num, + 'server_groups': compute_fakes.servgroup_num, + 'server_group_members': compute_fakes.servgroup_members_num, + } + kwargs_volume = { + 'gigabytes': compute_fakes.floating_ip_num, + 'snapshots': compute_fakes.fix_ip_num, + 'volumes': volume_fakes.QUOTA['volumes'], + } + + self.quotas_class_mock.update.assert_called_with( + identity_fakes.project_name, + **kwargs_compute ) - self.volume_quotas_class_mock.update.assert_called_once_with( - identity_fakes.project_id, - **{'volumes': volume_fakes.QUOTA['volumes']} + self.volume_quotas_class_mock.update.assert_called_with( + identity_fakes.project_name, + **kwargs_volume ) self.assertNotCalled(self.network_mock.update_quota) self.assertIsNone(result) @@ -444,9 +501,9 @@ def test_quota_show_with_class(self): self.cmd.take_action(parsed_args) self.quotas_class_mock.get.assert_called_once_with( - identity_fakes.project_id) + identity_fakes.project_name) self.volume_quotas_class_mock.get.assert_called_once_with( - identity_fakes.project_id) + identity_fakes.project_name) self.assertNotCalled(self.network.get_quota) self.assertNotCalled(self.network.get_quota_default) diff --git a/releasenotes/notes/bug-1609233-90b2ddf8d941050e.yaml b/releasenotes/notes/bug-1609233-90b2ddf8d941050e.yaml new file mode 100644 index 0000000000..671a39b1f3 --- /dev/null +++ b/releasenotes/notes/bug-1609233-90b2ddf8d941050e.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - Fix the ``--class`` option not work when we set/show the quota class. + [Bug `1609233 `_] \ No newline at end of file From 96a8ed435c7e55633c00dbb1283477ff11cf35f9 Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Thu, 18 Aug 2016 15:37:13 +0800 Subject: [PATCH 1222/3095] Support listing specified server's ports Add new option "--server" for "port list" command to list all of the ports that are attached on the specified server. Change-Id: I8b5550ea5068405b163711303465b704b5207410 Closes-Bug: #1614385 --- doc/source/command-objects/port.rst | 6 ++++- openstackclient/network/v2/port.py | 21 +++++++++++++----- .../tests/unit/network/v2/test_port.py | 22 +++++++++++++++++++ .../notes/bug-1614385-460b5034ba372463.yaml | 5 +++++ 4 files changed, 48 insertions(+), 6 deletions(-) create mode 100644 releasenotes/notes/bug-1614385-460b5034ba372463.yaml diff --git a/doc/source/command-objects/port.rst b/doc/source/command-objects/port.rst index 4d7b95b4e3..771f2f8d48 100644 --- a/doc/source/command-objects/port.rst +++ b/doc/source/command-objects/port.rst @@ -115,7 +115,7 @@ List ports os port list [--device-owner ] - [--router ] + [--router | --server ] .. option:: --device-owner @@ -126,6 +126,10 @@ List ports List only ports attached to this router (name or ID) +.. option:: --server + + List only ports attached to this server (name or ID) + port set -------- diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 05c5a012f3..d7268573dc 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -345,16 +345,23 @@ def get_parser(self, prog_name): "This is the entity that uses the port (for example, " "network:dhcp).") ) - parser.add_argument( + device_group = parser.add_mutually_exclusive_group() + device_group.add_argument( '--router', metavar='', dest='router', help=_("List only ports attached to this router (name or ID)") ) + device_group.add_argument( + '--server', + metavar='', + help=_("List only ports attached to this server (name or ID)"), + ) return parser def take_action(self, parsed_args): - client = self.app.client_manager.network + network_client = self.app.client_manager.network + compute_client = self.app.client_manager.compute columns = ( 'id', @@ -373,11 +380,15 @@ def take_action(self, parsed_args): if parsed_args.device_owner is not None: filters['device_owner'] = parsed_args.device_owner if parsed_args.router: - _router = client.find_router(parsed_args.router, - ignore_missing=False) + _router = network_client.find_router(parsed_args.router, + ignore_missing=False) filters['device_id'] = _router.id + if parsed_args.server: + server = utils.find_resource(compute_client.servers, + parsed_args.server) + filters['device_id'] = server.id - data = client.ports(**filters) + data = network_client.ports(**filters) return (column_headers, (utils.get_item_properties( diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index d5d7f3309b..43a78f8c88 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -19,6 +19,7 @@ from osc_lib import utils from openstackclient.network.v2 import port +from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes from openstackclient.tests.unit.network.v2 import fakes as network_fakes from openstackclient.tests.unit import utils as tests_utils @@ -337,6 +338,7 @@ def setUp(self): 'id': 'fake-router-id', }) self.network.find_router = mock.Mock(return_value=fake_router) + self.app.client_manager.compute = mock.Mock() def test_port_list_no_options(self): arglist = [] @@ -369,6 +371,26 @@ def test_port_list_router_opt(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + @mock.patch.object(utils, 'find_resource') + def test_port_list_with_server_option(self, mock_find): + fake_server = compute_fakes.FakeServer.create_one_server() + mock_find.return_value = fake_server + + arglist = [ + '--server', 'fake-server-name', + ] + verifylist = [ + ('server', 'fake-server-name'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.network.ports.assert_called_once_with( + device_id=fake_server.id) + mock_find.assert_called_once_with(mock.ANY, 'fake-server-name') + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + def test_port_list_device_owner_opt(self): arglist = [ '--device-owner', self._ports[0].device_owner, diff --git a/releasenotes/notes/bug-1614385-460b5034ba372463.yaml b/releasenotes/notes/bug-1614385-460b5034ba372463.yaml new file mode 100644 index 0000000000..ef416a3fb3 --- /dev/null +++ b/releasenotes/notes/bug-1614385-460b5034ba372463.yaml @@ -0,0 +1,5 @@ +--- +features: + - Support listing the specified server's ports by new option ``--server`` of + ``port list`` command. + [Bug `1614385 `_] From bc222e97b155152be3c8cced987ef1d443076d10 Mon Sep 17 00:00:00 2001 From: qtang Date: Wed, 14 Sep 2016 13:27:31 +0800 Subject: [PATCH 1223/3095] Replace 'MagicMock' with 'Mock' Change-Id: I7aeceede6bd3cb88cf04f398454f9758dbee20f1 Closes-Bug: #1475722 --- .../tests/unit/compute/v2/fakes.py | 12 +++++----- .../tests/unit/compute/v2/test_flavor.py | 2 +- .../tests/unit/compute/v2/test_server.py | 2 +- .../unit/compute/v2/test_server_backup.py | 6 ++--- .../unit/compute/v2/test_server_image.py | 4 ++-- .../tests/unit/compute/v2/test_service.py | 2 +- openstackclient/tests/unit/fakes.py | 2 +- .../tests/unit/identity/v2_0/test_catalog.py | 4 ++-- .../identity/v2_0/test_role_assignment.py | 2 +- .../tests/unit/identity/v3/fakes.py | 4 ++-- .../tests/unit/identity/v3/test_catalog.py | 4 ++-- .../tests/unit/identity/v3/test_credential.py | 2 +- .../tests/unit/identity/v3/test_group.py | 6 ++--- .../unit/identity/v3/test_role_assignment.py | 2 +- .../tests/unit/image/v1/test_image.py | 2 +- openstackclient/tests/unit/image/v2/fakes.py | 2 +- .../tests/unit/network/v2/fakes.py | 22 +++++++++---------- .../unit/network/v2/test_address_scope.py | 2 +- .../tests/unit/network/v2/test_floating_ip.py | 4 ++-- .../unit/network/v2/test_network_agent.py | 2 +- .../unit/network/v2/test_network_rbac.py | 2 +- .../tests/unit/network/v2/test_port.py | 2 +- .../tests/unit/network/v2/test_router.py | 2 +- .../unit/network/v2/test_security_group.py | 4 ++-- .../network/v2/test_security_group_rule.py | 4 ++-- .../tests/unit/network/v2/test_subnet.py | 2 +- .../tests/unit/network/v2/test_subnet_pool.py | 2 +- openstackclient/tests/unit/volume/v1/fakes.py | 10 ++++----- openstackclient/tests/unit/volume/v2/fakes.py | 10 ++++----- 29 files changed, 63 insertions(+), 63 deletions(-) diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index 0e3d47ba84..3c82977333 100644 --- a/openstackclient/tests/unit/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -128,7 +128,7 @@ def get_aggregates(aggregates=None, count=2): """ if aggregates is None: aggregates = FakeAggregate.create_aggregates(count) - return mock.MagicMock(side_effect=aggregates) + return mock.Mock(side_effect=aggregates) class FakeComputev2Client(object): @@ -519,7 +519,7 @@ def get_security_groups(security_groups=None, count=2): """ if security_groups is None: security_groups = FakeSecurityGroup.create_security_groups(count) - return mock.MagicMock(side_effect=security_groups) + return mock.Mock(side_effect=security_groups) class FakeSecurityGroupRule(object): @@ -649,7 +649,7 @@ def get_servers(servers=None, count=2): """ if servers is None: servers = FakeServer.create_servers(count) - return mock.MagicMock(side_effect=servers) + return mock.Mock(side_effect=servers) class FakeService(object): @@ -788,7 +788,7 @@ def get_flavors(flavors=None, count=2): """ if flavors is None: flavors = FakeFlavor.create_flavors(count) - return mock.MagicMock(side_effect=flavors) + return mock.Mock(side_effect=flavors) class FakeFlavorAccess(object): @@ -887,7 +887,7 @@ def get_keypairs(keypairs=None, count=2): """ if keypairs is None: keypairs = FakeKeypair.create_keypairs(count) - return mock.MagicMock(side_effect=keypairs) + return mock.Mock(side_effect=keypairs) class FakeAvailabilityZone(object): @@ -1011,7 +1011,7 @@ def get_floating_ips(floating_ips=None, count=2): """ if floating_ips is None: floating_ips = FakeFloatingIP.create_floating_ips(count) - return mock.MagicMock(side_effect=floating_ips) + return mock.Mock(side_effect=floating_ips) class FakeFloatingIPPool(object): diff --git a/openstackclient/tests/unit/compute/v2/test_flavor.py b/openstackclient/tests/unit/compute/v2/test_flavor.py index ace650eb18..93ad9d14c9 100644 --- a/openstackclient/tests/unit/compute/v2/test_flavor.py +++ b/openstackclient/tests/unit/compute/v2/test_flavor.py @@ -292,7 +292,7 @@ def test_multi_flavors_delete_with_exception(self): find_mock_result = [self.flavors[0], exceptions.CommandError] self.flavors_mock.get = ( - mock.MagicMock(side_effect=find_mock_result) + mock.Mock(side_effect=find_mock_result) ) self.flavors_mock.find.side_effect = exceptions.NotFound(None) diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index d4843f51c1..1081b9a3c5 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -417,7 +417,7 @@ def test_server_create_with_network(self): @mock.patch('openstackclient.compute.v2.server.io.open') def test_server_create_userdata(self, mock_open): - mock_file = mock.MagicMock(name='File') + mock_file = mock.Mock(name='File') mock_open.return_value = mock_file mock_open.read.return_value = '#!/bin/sh' diff --git a/openstackclient/tests/unit/compute/v2/test_server_backup.py b/openstackclient/tests/unit/compute/v2/test_server_backup.py index 9aa63fc7ca..9a481e0a67 100644 --- a/openstackclient/tests/unit/compute/v2/test_server_backup.py +++ b/openstackclient/tests/unit/compute/v2/test_server_backup.py @@ -101,7 +101,7 @@ def setup_images_mock(self, count, servers=None): count=count, ) - self.images_mock.get = mock.MagicMock(side_effect=images) + self.images_mock.get = mock.Mock(side_effect=images) return images def test_server_backup_defaults(self): @@ -181,7 +181,7 @@ def test_server_backup_wait_fail(self, mock_wait_for_status): count=5, ) - self.images_mock.get = mock.MagicMock( + self.images_mock.get = mock.Mock( side_effect=images, ) @@ -230,7 +230,7 @@ def test_server_backup_wait_ok(self, mock_wait_for_status): count=5, ) - self.images_mock.get = mock.MagicMock( + self.images_mock.get = mock.Mock( side_effect=images, ) diff --git a/openstackclient/tests/unit/compute/v2/test_server_image.py b/openstackclient/tests/unit/compute/v2/test_server_image.py index f53f08e6e0..47bd682f17 100644 --- a/openstackclient/tests/unit/compute/v2/test_server_image.py +++ b/openstackclient/tests/unit/compute/v2/test_server_image.py @@ -99,8 +99,8 @@ def setup_images_mock(self, count, servers=None): count=count, ) - self.images_mock.get = mock.MagicMock(side_effect=images) - self.servers_mock.create_image = mock.MagicMock( + self.images_mock.get = mock.Mock(side_effect=images) + self.servers_mock.create_image = mock.Mock( return_value=images[0].id, ) return images diff --git a/openstackclient/tests/unit/compute/v2/test_service.py b/openstackclient/tests/unit/compute/v2/test_service.py index 1fd3b7d59f..8403efc9c8 100644 --- a/openstackclient/tests/unit/compute/v2/test_service.py +++ b/openstackclient/tests/unit/compute/v2/test_service.py @@ -89,7 +89,7 @@ def test_multi_services_delete_with_exception(self): delete_mock_result = [None, exceptions.CommandError] self.service_mock.delete = ( - mock.MagicMock(side_effect=delete_mock_result) + mock.Mock(side_effect=delete_mock_result) ) try: diff --git a/openstackclient/tests/unit/fakes.py b/openstackclient/tests/unit/fakes.py index 786cd6d430..f259836627 100644 --- a/openstackclient/tests/unit/fakes.py +++ b/openstackclient/tests/unit/fakes.py @@ -200,7 +200,7 @@ def _add_methods(self, methods): returned, which looks like a function call. """ for (name, ret) in six.iteritems(methods): - method = mock.MagicMock(return_value=ret) + method = mock.Mock(return_value=ret) setattr(self, name, method) def __repr__(self): diff --git a/openstackclient/tests/unit/identity/v2_0/test_catalog.py b/openstackclient/tests/unit/identity/v2_0/test_catalog.py index c32f9fb856..65af58a24e 100644 --- a/openstackclient/tests/unit/identity/v2_0/test_catalog.py +++ b/openstackclient/tests/unit/identity/v2_0/test_catalog.py @@ -25,12 +25,12 @@ class TestCatalog(utils.TestCommand): def setUp(self): super(TestCatalog, self).setUp() - self.sc_mock = mock.MagicMock() + self.sc_mock = mock.Mock() self.sc_mock.service_catalog.catalog.return_value = [ self.service_catalog, ] - self.auth_mock = mock.MagicMock() + self.auth_mock = mock.Mock() self.app.client_manager.session = self.auth_mock self.auth_mock.auth.get_auth_ref.return_value = self.sc_mock diff --git a/openstackclient/tests/unit/identity/v2_0/test_role_assignment.py b/openstackclient/tests/unit/identity/v2_0/test_role_assignment.py index 2730695936..87643f128d 100644 --- a/openstackclient/tests/unit/identity/v2_0/test_role_assignment.py +++ b/openstackclient/tests/unit/identity/v2_0/test_role_assignment.py @@ -170,7 +170,7 @@ def test_role_assignment_list_project_and_user(self): def test_role_assignment_list_def_creds(self): - auth_ref = self.app.client_manager.auth_ref = mock.MagicMock() + auth_ref = self.app.client_manager.auth_ref = mock.Mock() auth_ref.project_id.return_value = identity_fakes.project_id auth_ref.user_id.return_value = identity_fakes.user_id diff --git a/openstackclient/tests/unit/identity/v3/fakes.py b/openstackclient/tests/unit/identity/v3/fakes.py index 7b76fa6087..75065e657e 100644 --- a/openstackclient/tests/unit/identity/v3/fakes.py +++ b/openstackclient/tests/unit/identity/v3/fakes.py @@ -719,7 +719,7 @@ def get_credentials(credentials=None, count=2): if credentials is None: credentials = FakeCredential.create_credentials(count) - return mock.MagicMock(side_effect=credentials) + return mock.Mock(side_effect=credentials) class FakeUser(object): @@ -819,7 +819,7 @@ def get_groups(groups=None, count=2): if groups is None: groups = FakeGroup.create_groups(count) - return mock.MagicMock(side_effect=groups) + return mock.Mock(side_effect=groups) class FakeEndpoint(object): diff --git a/openstackclient/tests/unit/identity/v3/test_catalog.py b/openstackclient/tests/unit/identity/v3/test_catalog.py index 986c05f3ab..53008e8cbc 100644 --- a/openstackclient/tests/unit/identity/v3/test_catalog.py +++ b/openstackclient/tests/unit/identity/v3/test_catalog.py @@ -50,12 +50,12 @@ class TestCatalog(utils.TestCommand): def setUp(self): super(TestCatalog, self).setUp() - self.sc_mock = mock.MagicMock() + self.sc_mock = mock.Mock() self.sc_mock.service_catalog.catalog.return_value = [ self.fake_service, ] - self.auth_mock = mock.MagicMock() + self.auth_mock = mock.Mock() self.app.client_manager.session = self.auth_mock self.auth_mock.auth.get_auth_ref.return_value = self.sc_mock diff --git a/openstackclient/tests/unit/identity/v3/test_credential.py b/openstackclient/tests/unit/identity/v3/test_credential.py index fd3ae6b262..ce0fb5ae24 100644 --- a/openstackclient/tests/unit/identity/v3/test_credential.py +++ b/openstackclient/tests/unit/identity/v3/test_credential.py @@ -196,7 +196,7 @@ def test_credential_multi_delete_with_exception(self): delete_mock_result = [None, exceptions.CommandError] self.credentials_mock.delete = ( - mock.MagicMock(side_effect=delete_mock_result) + mock.Mock(side_effect=delete_mock_result) ) try: diff --git a/openstackclient/tests/unit/identity/v3/test_group.py b/openstackclient/tests/unit/identity/v3/test_group.py index d35e98c691..eb50adb526 100644 --- a/openstackclient/tests/unit/identity/v3/test_group.py +++ b/openstackclient/tests/unit/identity/v3/test_group.py @@ -239,7 +239,7 @@ def test_group_multi_delete(self): def test_group_delete_with_domain(self): get_mock_result = [exceptions.CommandError, self.groups[0]] self.groups_mock.get = ( - mock.MagicMock(side_effect=get_mock_result)) + mock.Mock(side_effect=get_mock_result)) arglist = [ '--domain', self.domain.id, @@ -487,7 +487,7 @@ def test_group_set_name_and_description(self): def test_group_set_with_domain(self): get_mock_result = [exceptions.CommandError, self.group] self.groups_mock.get = ( - mock.MagicMock(side_effect=get_mock_result)) + mock.Mock(side_effect=get_mock_result)) arglist = [ '--domain', self.domain.id, @@ -550,7 +550,7 @@ def test_group_show(self): def test_group_show_with_domain(self): get_mock_result = [exceptions.CommandError, self.group] self.groups_mock.get = ( - mock.MagicMock(side_effect=get_mock_result)) + mock.Mock(side_effect=get_mock_result)) arglist = [ '--domain', self.domain.id, diff --git a/openstackclient/tests/unit/identity/v3/test_role_assignment.py b/openstackclient/tests/unit/identity/v3/test_role_assignment.py index 7102a0cdae..32fbb7f1d4 100644 --- a/openstackclient/tests/unit/identity/v3/test_role_assignment.py +++ b/openstackclient/tests/unit/identity/v3/test_role_assignment.py @@ -377,7 +377,7 @@ def test_role_assignment_list_project(self): def test_role_assignment_list_def_creds(self): - auth_ref = self.app.client_manager.auth_ref = mock.MagicMock() + auth_ref = self.app.client_manager.auth_ref = mock.Mock() auth_ref.project_id.return_value = identity_fakes.project_id auth_ref.user_id.return_value = identity_fakes.user_id diff --git a/openstackclient/tests/unit/image/v1/test_image.py b/openstackclient/tests/unit/image/v1/test_image.py index c62c1ff92d..a6bc80a08e 100644 --- a/openstackclient/tests/unit/image/v1/test_image.py +++ b/openstackclient/tests/unit/image/v1/test_image.py @@ -149,7 +149,7 @@ def test_image_reserve_options(self): @mock.patch('openstackclient.image.v1.image.io.open', name='Open') def test_image_create_file(self, mock_open): - mock_file = mock.MagicMock(name='File') + mock_file = mock.Mock(name='File') mock_open.return_value = mock_file mock_open.read.return_value = image_fakes.image_data mock_exception = { diff --git a/openstackclient/tests/unit/image/v2/fakes.py b/openstackclient/tests/unit/image/v2/fakes.py index 4d9f645833..8e2f587d29 100644 --- a/openstackclient/tests/unit/image/v2/fakes.py +++ b/openstackclient/tests/unit/image/v2/fakes.py @@ -245,7 +245,7 @@ def get_images(images=None, count=2): if images is None: images = FakeImage.create_images(count) - return mock.MagicMock(side_effect=images) + return mock.Mock(side_effect=images) @staticmethod def get_image_columns(image=None): diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index ed30bad343..e3adb435cb 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -136,7 +136,7 @@ def get_address_scopes(address_scopes=None, count=2): """ if address_scopes is None: address_scopes = FakeAddressScope.create_address_scopes(count) - return mock.MagicMock(side_effect=address_scopes) + return mock.Mock(side_effect=address_scopes) class FakeAvailabilityZone(object): @@ -344,7 +344,7 @@ def get_networks(networks=None, count=2): """ if networks is None: networks = FakeNetwork.create_networks(count) - return mock.MagicMock(side_effect=networks) + return mock.Mock(side_effect=networks) class FakeNetworkSegment(object): @@ -488,7 +488,7 @@ def get_ports(ports=None, count=2): """ if ports is None: ports = FakePort.create_ports(count) - return mock.MagicMock(side_effect=ports) + return mock.Mock(side_effect=ports) class FakeNetworkAgent(object): @@ -555,7 +555,7 @@ def get_network_agents(agents=None, count=2): """ if agents is None: agents = FakeNetworkAgent.create_network_agents(count) - return mock.MagicMock(side_effect=agents) + return mock.Mock(side_effect=agents) class FakeNetworkRBAC(object): @@ -625,7 +625,7 @@ def get_network_rbacs(rbac_policies=None, count=2): """ if rbac_policies is None: rbac_policies = FakeNetworkRBAC.create_network_rbacs(count) - return mock.MagicMock(side_effect=rbac_policies) + return mock.Mock(side_effect=rbac_policies) class FakeRouter(object): @@ -703,7 +703,7 @@ def get_routers(routers=None, count=2): """ if routers is None: routers = FakeRouter.create_routers(count) - return mock.MagicMock(side_effect=routers) + return mock.Mock(side_effect=routers) class FakeSecurityGroup(object): @@ -776,7 +776,7 @@ def get_security_groups(security_groups=None, count=2): """ if security_groups is None: security_groups = FakeSecurityGroup.create_security_groups(count) - return mock.MagicMock(side_effect=security_groups) + return mock.Mock(side_effect=security_groups) class FakeSecurityGroupRule(object): @@ -855,7 +855,7 @@ def get_security_group_rules(security_group_rules=None, count=2): if security_group_rules is None: security_group_rules = ( FakeSecurityGroupRule.create_security_group_rules(count)) - return mock.MagicMock(side_effect=security_group_rules) + return mock.Mock(side_effect=security_group_rules) class FakeSubnet(object): @@ -938,7 +938,7 @@ def get_subnets(subnets=None, count=2): """ if subnets is None: subnets = FakeSubnet.create_subnets(count) - return mock.MagicMock(side_effect=subnets) + return mock.Mock(side_effect=subnets) class FakeFloatingIP(object): @@ -1015,7 +1015,7 @@ def get_floating_ips(floating_ips=None, count=2): """ if floating_ips is None: floating_ips = FakeFloatingIP.create_floating_ips(count) - return mock.MagicMock(side_effect=floating_ips) + return mock.Mock(side_effect=floating_ips) class FakeSubnetPool(object): @@ -1097,4 +1097,4 @@ def get_subnet_pools(subnet_pools=None, count=2): """ if subnet_pools is None: subnet_pools = FakeSubnetPool.create_subnet_pools(count) - return mock.MagicMock(side_effect=subnet_pools) + return mock.Mock(side_effect=subnet_pools) diff --git a/openstackclient/tests/unit/network/v2/test_address_scope.py b/openstackclient/tests/unit/network/v2/test_address_scope.py index 6d3f40110d..12c3f1d638 100644 --- a/openstackclient/tests/unit/network/v2/test_address_scope.py +++ b/openstackclient/tests/unit/network/v2/test_address_scope.py @@ -216,7 +216,7 @@ def test_multi_address_scopes_delete_with_exception(self): find_mock_result = [self._address_scopes[0], exceptions.CommandError] self.network.find_address_scope = ( - mock.MagicMock(side_effect=find_mock_result) + mock.Mock(side_effect=find_mock_result) ) try: diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip.py b/openstackclient/tests/unit/network/v2/test_floating_ip.py index a40e48f4ba..a77fb24b5c 100644 --- a/openstackclient/tests/unit/network/v2/test_floating_ip.py +++ b/openstackclient/tests/unit/network/v2/test_floating_ip.py @@ -204,7 +204,7 @@ def test_multi_floating_ips_delete_with_exception(self): find_mock_result = [self.floating_ips[0], exceptions.CommandError] self.network.find_ip = ( - mock.MagicMock(side_effect=find_mock_result) + mock.Mock(side_effect=find_mock_result) ) try: @@ -454,7 +454,7 @@ def test_multi_floating_ips_delete_with_exception(self): find_mock_result = [self.floating_ips[0], exceptions.CommandError] self.compute.floating_ips.get = ( - mock.MagicMock(side_effect=find_mock_result) + mock.Mock(side_effect=find_mock_result) ) self.compute.floating_ips.find.side_effect = exceptions.NotFound(None) diff --git a/openstackclient/tests/unit/network/v2/test_network_agent.py b/openstackclient/tests/unit/network/v2/test_network_agent.py index 2f17f41b04..9f5b442a82 100644 --- a/openstackclient/tests/unit/network/v2/test_network_agent.py +++ b/openstackclient/tests/unit/network/v2/test_network_agent.py @@ -96,7 +96,7 @@ def test_multi_network_agents_delete_with_exception(self): find_mock_result = [self.network_agents[0], exceptions.CommandError] self.network.get_agent = ( - mock.MagicMock(side_effect=find_mock_result) + mock.Mock(side_effect=find_mock_result) ) try: diff --git a/openstackclient/tests/unit/network/v2/test_network_rbac.py b/openstackclient/tests/unit/network/v2/test_network_rbac.py index 1cd18a093f..c526ae4e4b 100644 --- a/openstackclient/tests/unit/network/v2/test_network_rbac.py +++ b/openstackclient/tests/unit/network/v2/test_network_rbac.py @@ -259,7 +259,7 @@ def test_multi_network_policies_delete_with_exception(self): find_mock_result = [self.rbac_policies[0], exceptions.CommandError] self.network.find_rbac_policy = ( - mock.MagicMock(side_effect=find_mock_result) + mock.Mock(side_effect=find_mock_result) ) try: diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index d5d7f3309b..92ad716eca 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -289,7 +289,7 @@ def test_multi_ports_delete_with_exception(self): find_mock_result = [self._ports[0], exceptions.CommandError] self.network.find_port = ( - mock.MagicMock(side_effect=find_mock_result) + mock.Mock(side_effect=find_mock_result) ) try: diff --git a/openstackclient/tests/unit/network/v2/test_router.py b/openstackclient/tests/unit/network/v2/test_router.py index 26fe655e30..de070eb25d 100644 --- a/openstackclient/tests/unit/network/v2/test_router.py +++ b/openstackclient/tests/unit/network/v2/test_router.py @@ -263,7 +263,7 @@ def test_multi_routers_delete_with_exception(self): find_mock_result = [self._routers[0], exceptions.CommandError] self.network.find_router = ( - mock.MagicMock(side_effect=find_mock_result) + mock.Mock(side_effect=find_mock_result) ) try: diff --git a/openstackclient/tests/unit/network/v2/test_security_group.py b/openstackclient/tests/unit/network/v2/test_security_group.py index 4c5b29721d..2615b77a26 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group.py +++ b/openstackclient/tests/unit/network/v2/test_security_group.py @@ -279,7 +279,7 @@ def test_multi_security_groups_delete_with_exception(self): find_mock_result = [self._security_groups[0], exceptions.CommandError] self.network.find_security_group = ( - mock.MagicMock(side_effect=find_mock_result) + mock.Mock(side_effect=find_mock_result) ) try: @@ -365,7 +365,7 @@ def test_multi_security_groups_delete_with_exception(self): find_mock_result = [self._security_groups[0], exceptions.CommandError] self.compute.security_groups.get = ( - mock.MagicMock(side_effect=find_mock_result) + mock.Mock(side_effect=find_mock_result) ) self.compute.security_groups.find.side_effect = ( exceptions.NotFound(None)) diff --git a/openstackclient/tests/unit/network/v2/test_security_group_rule.py b/openstackclient/tests/unit/network/v2/test_security_group_rule.py index 51e18a6560..96d58e5c00 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_rule.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_rule.py @@ -723,7 +723,7 @@ def test_multi_security_group_rules_delete_with_exception(self): find_mock_result = [ self._security_group_rules[0], exceptions.CommandError] self.network.find_security_group_rule = ( - mock.MagicMock(side_effect=find_mock_result) + mock.Mock(side_effect=find_mock_result) ) try: @@ -803,7 +803,7 @@ def test_multi_security_group_rules_delete_with_exception(self): find_mock_result = [None, exceptions.CommandError] self.compute.security_group_rules.delete = ( - mock.MagicMock(side_effect=find_mock_result) + mock.Mock(side_effect=find_mock_result) ) try: diff --git a/openstackclient/tests/unit/network/v2/test_subnet.py b/openstackclient/tests/unit/network/v2/test_subnet.py index e31db4693d..6d7623443d 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet.py +++ b/openstackclient/tests/unit/network/v2/test_subnet.py @@ -489,7 +489,7 @@ def test_multi_subnets_delete_with_exception(self): find_mock_result = [self._subnets[0], exceptions.CommandError] self.network.find_subnet = ( - mock.MagicMock(side_effect=find_mock_result) + mock.Mock(side_effect=find_mock_result) ) try: diff --git a/openstackclient/tests/unit/network/v2/test_subnet_pool.py b/openstackclient/tests/unit/network/v2/test_subnet_pool.py index 0d9494c023..e0e1969baf 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/unit/network/v2/test_subnet_pool.py @@ -310,7 +310,7 @@ def test_multi_subnet_pools_delete_with_exception(self): find_mock_result = [self._subnet_pools[0], exceptions.CommandError] self.network.find_subnet_pool = ( - mock.MagicMock(side_effect=find_mock_result) + mock.Mock(side_effect=find_mock_result) ) try: diff --git a/openstackclient/tests/unit/volume/v1/fakes.py b/openstackclient/tests/unit/volume/v1/fakes.py index 9fb6fb9b2f..2a4f62c520 100644 --- a/openstackclient/tests/unit/volume/v1/fakes.py +++ b/openstackclient/tests/unit/volume/v1/fakes.py @@ -201,7 +201,7 @@ def get_transfers(transfers=None, count=2): if transfers is None: transfers = FakeTransfer.create_transfers(count) - return mock.MagicMock(side_effect=transfers) + return mock.Mock(side_effect=transfers) class FakeService(object): @@ -274,7 +274,7 @@ def get_services(services=None, count=2): if services is None: services = FakeService.create_services(count) - return mock.MagicMock(side_effect=services) + return mock.Mock(side_effect=services) class FakeQos(object): @@ -343,7 +343,7 @@ def get_qoses(qoses=None, count=2): if qoses is None: qoses = FakeQos.create_qoses(count) - return mock.MagicMock(side_effect=qoses) + return mock.Mock(side_effect=qoses) class FakeVolume(object): @@ -427,7 +427,7 @@ def get_volumes(volumes=None, count=2): if volumes is None: volumes = FakeVolume.create_volumes(count) - return mock.MagicMock(side_effect=volumes) + return mock.Mock(side_effect=volumes) class FakeImagev1Client(object): @@ -547,4 +547,4 @@ def get_types(types=None, count=2): if types is None: types = FakeType.create_types(count) - return mock.MagicMock(side_effect=types) + return mock.Mock(side_effect=types) diff --git a/openstackclient/tests/unit/volume/v2/fakes.py b/openstackclient/tests/unit/volume/v2/fakes.py index babdc1f7c6..8d7ac83139 100644 --- a/openstackclient/tests/unit/volume/v2/fakes.py +++ b/openstackclient/tests/unit/volume/v2/fakes.py @@ -107,7 +107,7 @@ def get_transfers(transfers=None, count=2): if transfers is None: transfers = FakeTransfer.create_transfers(count) - return mock.MagicMock(side_effect=transfers) + return mock.Mock(side_effect=transfers) class FakeTypeAccess(object): @@ -331,7 +331,7 @@ def get_volumes(volumes=None, count=2): if volumes is None: volumes = FakeVolume.create_volumes(count) - return mock.MagicMock(side_effect=volumes) + return mock.Mock(side_effect=volumes) @staticmethod def get_volume_columns(volume=None): @@ -492,7 +492,7 @@ def get_backups(backups=None, count=2): if backups is None: backups = FakeBackup.create_backups(count) - return mock.MagicMock(side_effect=backups) + return mock.Mock(side_effect=backups) class FakeExtension(object): @@ -623,7 +623,7 @@ def get_qoses(qoses=None, count=2): if qoses is None: qoses = FakeQos.create_qoses(count) - return mock.MagicMock(side_effect=qoses) + return mock.Mock(side_effect=qoses) class FakeSnapshot(object): @@ -696,7 +696,7 @@ def get_snapshots(snapshots=None, count=2): if snapshots is None: snapshots = FakeSnapshot.create_snapshots(count) - return mock.MagicMock(side_effect=snapshots) + return mock.Mock(side_effect=snapshots) class FakeType(object): From 7cba0ed671a1771d082da969a95ca0b4f4b9dafe Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Thu, 18 Aug 2016 17:06:22 +0100 Subject: [PATCH 1224/3095] Doc, help and message updates for port unset Update port unset based on review comments in [1]. [1] https://review.openstack.org/#/c/306236/ Change-Id: Icae6e3869516f2d53f90ad46efc03f897c00b0e5 Implements: blueprint network-property-unset --- doc/source/command-objects/subnet.rst | 8 ++-- openstackclient/network/v2/subnet.py | 57 +++++++++++---------------- 2 files changed, 28 insertions(+), 37 deletions(-) diff --git a/doc/source/command-objects/subnet.rst b/doc/source/command-objects/subnet.rst index 16092bcd27..4566d74d7b 100644 --- a/doc/source/command-objects/subnet.rst +++ b/doc/source/command-objects/subnet.rst @@ -292,9 +292,9 @@ Unset subnet properties .. option:: --allocation-pool start=,end= - Allocation pool to be removed from this subnet e.g.: - ``start=192.168.199.2,end=192.168.199.254`` - (repeat option to unset multiple Allocation pools) + Allocation pool IP addresses to be removed from this + subnet e.g.: ``start=192.168.199.2,end=192.168.199.254`` + (repeat option to unset multiple allocation pools) .. option:: --host-route destination=,gateway= @@ -314,4 +314,4 @@ Unset subnet properties .. _subnet_unset-subnet: .. describe:: - subnet to modify (name or ID) + Subnet to modify (name or ID) diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 6feb8aa002..f1c7d15d55 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -28,9 +28,14 @@ LOG = logging.getLogger(__name__) -def _update_arguments(obj_list, parsed_args_list): +def _update_arguments(obj_list, parsed_args_list, option): for item in parsed_args_list: - obj_list.remove(item) + try: + obj_list.remove(item) + except ValueError: + msg = (_("Subnet does not contain %(option)s %(value)s") % + {'option': option, 'value': item}) + raise exceptions.CommandError(msg) def _format_allocation_pools(data): @@ -493,9 +498,9 @@ def get_parser(self, prog_name): dest='allocation_pools', action=parseractions.MultiKeyValueAction, required_keys=['start', 'end'], - help=_('Allocation pool to be removed from this subnet ' - 'e.g.: start=192.168.199.2,end=192.168.199.254 ' - '(repeat option to unset multiple Allocation pools)') + help=_('Allocation pool IP addresses to be removed from this ' + 'subnet e.g.: start=192.168.199.2,end=192.168.199.254 ' + '(repeat option to unset multiple allocation pools)') ) parser.add_argument( '--dns-nameserver', @@ -503,7 +508,7 @@ def get_parser(self, prog_name): action='append', dest='dns_nameservers', help=_('DNS server to be removed from this subnet ' - '(repeat option to set multiple DNS servers)') + '(repeat option to unset multiple DNS servers)') ) parser.add_argument( '--host-route', @@ -540,39 +545,25 @@ def take_action(self, parsed_args): tmp_obj = copy.deepcopy(obj) attrs = {} if parsed_args.dns_nameservers: - try: - _update_arguments(tmp_obj.dns_nameservers, - parsed_args.dns_nameservers) - except ValueError as error: - msg = (_("%s not in dns-nameservers") % str(error)) - raise exceptions.CommandError(msg) + _update_arguments(tmp_obj.dns_nameservers, + parsed_args.dns_nameservers, + 'dns-nameserver') attrs['dns_nameservers'] = tmp_obj.dns_nameservers if parsed_args.host_routes: - try: - _update_arguments( - tmp_obj.host_routes, - convert_entries_to_nexthop(parsed_args.host_routes)) - except ValueError as error: - msg = (_("Subnet does not have %s in host-routes") % - str(error)) - raise exceptions.CommandError(msg) + _update_arguments( + tmp_obj.host_routes, + convert_entries_to_nexthop(parsed_args.host_routes), + 'host-route') attrs['host_routes'] = tmp_obj.host_routes if parsed_args.allocation_pools: - try: - _update_arguments(tmp_obj.allocation_pools, - parsed_args.allocation_pools) - except ValueError as error: - msg = (_("Subnet does not have %s in allocation-pools") % - str(error)) - raise exceptions.CommandError(msg) + _update_arguments(tmp_obj.allocation_pools, + parsed_args.allocation_pools, + 'allocation-pool') attrs['allocation_pools'] = tmp_obj.allocation_pools if parsed_args.service_types: - try: - _update_arguments(tmp_obj.service_types, - parsed_args.service_types) - except ValueError as error: - msg = (_("%s not in service-types") % str(error)) - raise exceptions.CommandError(msg) + _update_arguments(tmp_obj.service_types, + parsed_args.service_types, + 'service-type') attrs['service_types'] = tmp_obj.service_types if attrs: client.update_subnet(obj, **attrs) From 6a914d0056e810e1ef37eaf4f01cd5c85217aba6 Mon Sep 17 00:00:00 2001 From: qtang Date: Thu, 25 Aug 2016 10:56:58 +0800 Subject: [PATCH 1225/3095] Support mark volume as bootable in volume set Add --bootable | --non-bootable option in volume set to mark or unmark volume as bootable. Change-Id: Ifa6c2dd1642202f55b6d50e3b8614d3513d488f6 Closes-Bug:#1535704 --- doc/source/command-objects/volume.rst | 9 +++++++ .../tests/functional/volume/v1/test_volume.py | 11 ++++++++ .../tests/functional/volume/v2/test_volume.py | 11 ++++++++ .../tests/unit/volume/v1/test_volume.py | 27 +++++++++++++++++++ .../tests/unit/volume/v2/test_volume.py | 27 +++++++++++++++++++ openstackclient/volume/v1/volume.py | 18 ++++++++++++- openstackclient/volume/v2/volume.py | 18 +++++++++++++ .../notes/bug-1535704-d6f013bfa22ab668.yaml | 6 +++++ 8 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/bug-1535704-d6f013bfa22ab668.yaml diff --git a/doc/source/command-objects/volume.rst b/doc/source/command-objects/volume.rst index 141c980abe..26d4ead6f0 100644 --- a/doc/source/command-objects/volume.rst +++ b/doc/source/command-objects/volume.rst @@ -193,6 +193,7 @@ Set volume properties [--property [...] ] [--image-property [...] ] [--state ] + [--bootable | --non-bootable] .. option:: --name @@ -211,6 +212,14 @@ Set volume properties Set a property on this volume (repeat option to set multiple properties) +.. option:: --bootable + + Mark volume as bootable + +.. option:: --non-bootable + + Mark volume as non-bootable + .. option:: --image-property Set an image property on this volume diff --git a/openstackclient/tests/functional/volume/v1/test_volume.py b/openstackclient/tests/functional/volume/v1/test_volume.py index 6ac7f2bf71..5e4bcbea9a 100644 --- a/openstackclient/tests/functional/volume/v1/test_volume.py +++ b/openstackclient/tests/functional/volume/v1/test_volume.py @@ -75,3 +75,14 @@ def test_volume_set_size(self): opts = self.get_opts(["display_name", "size"]) raw_output = self.openstack('volume show ' + self.NAME + opts) self.assertEqual(self.NAME + "\n2\n", raw_output) + + def test_volume_set_bootable(self): + self.openstack('volume set --bootable ' + self.NAME) + opts = self.get_opts(["bootable"]) + raw_output = self.openstack('volume show ' + self.NAME + opts) + self.assertEqual("true\n", raw_output) + + self.openstack('volume set --non-bootable ' + self.NAME) + opts = self.get_opts(["bootable"]) + raw_output = self.openstack('volume show ' + self.NAME + opts) + self.assertEqual("false\n", raw_output) diff --git a/openstackclient/tests/functional/volume/v2/test_volume.py b/openstackclient/tests/functional/volume/v2/test_volume.py index 73273573a9..fb880578c9 100644 --- a/openstackclient/tests/functional/volume/v2/test_volume.py +++ b/openstackclient/tests/functional/volume/v2/test_volume.py @@ -91,6 +91,17 @@ def test_volume_set_size(self): raw_output = self.openstack('volume show ' + self.NAME + opts) self.assertEqual(self.NAME + "\n2\n", raw_output) + def test_volume_set_bootable(self): + self.openstack('volume set --bootable ' + self.NAME) + opts = self.get_opts(["bootable"]) + raw_output = self.openstack('volume show ' + self.NAME + opts) + self.assertEqual("true\n", raw_output) + + self.openstack('volume set --non-bootable ' + self.NAME) + opts = self.get_opts(["bootable"]) + raw_output = self.openstack('volume show ' + self.NAME + opts) + self.assertEqual("false\n", raw_output) + def test_volume_snapshot(self): opts = self.get_opts(self.FIELDS) diff --git a/openstackclient/tests/unit/volume/v1/test_volume.py b/openstackclient/tests/unit/volume/v1/test_volume.py index 54ec9e7ee3..e95f42d037 100644 --- a/openstackclient/tests/unit/volume/v1/test_volume.py +++ b/openstackclient/tests/unit/volume/v1/test_volume.py @@ -844,6 +844,8 @@ def test_volume_set_property(self): ('size', None), ('property', {'myprop': 'myvalue'}), ('volume', volume_fakes.volume_name), + ('bootable', False), + ('non_bootable', False) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -858,3 +860,28 @@ def test_volume_set_property(self): metadata ) self.assertIsNone(result) + + def test_volume_set_bootable(self): + arglist = [ + ['--bootable', volume_fakes.volume_id], + ['--non-bootable', volume_fakes.volume_id] + ] + verifylist = [ + [ + ('bootable', True), + ('non_bootable', False), + ('volume', volume_fakes.volume_id) + ], + [ + ('bootable', False), + ('non_bootable', True), + ('volume', volume_fakes.volume_id) + ] + ] + for index in range(len(arglist)): + parsed_args = self.check_parser( + self.cmd, arglist[index], verifylist[index]) + + self.cmd.take_action(parsed_args) + self.volumes_mock.set_bootable.assert_called_with( + volume_fakes.volume_id, verifylist[index][0][1]) diff --git a/openstackclient/tests/unit/volume/v2/test_volume.py b/openstackclient/tests/unit/volume/v2/test_volume.py index de059b1b5d..a42ce22bf8 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume.py +++ b/openstackclient/tests/unit/volume/v2/test_volume.py @@ -905,6 +905,8 @@ def test_volume_set_image_property(self): verifylist = [ ('image_property', {'Alpha': 'a', 'Beta': 'b'}), ('volume', self.new_volume.id), + ('bootable', False), + ('non_bootable', False) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -952,6 +954,31 @@ def test_volume_set_state_failed(self): self.volumes_mock.reset_state.assert_called_with( self.new_volume.id, 'error') + def test_volume_set_bootable(self): + arglist = [ + ['--bootable', self.new_volume.id], + ['--non-bootable', self.new_volume.id] + ] + verifylist = [ + [ + ('bootable', True), + ('non_bootable', False), + ('volume', self.new_volume.id) + ], + [ + ('bootable', False), + ('non_bootable', True), + ('volume', self.new_volume.id) + ] + ] + for index in range(len(arglist)): + parsed_args = self.check_parser( + self.cmd, arglist[index], verifylist[index]) + + self.cmd.take_action(parsed_args) + self.volumes_mock.set_bootable.assert_called_with( + self.new_volume.id, verifylist[index][0][1]) + class TestVolumeShow(TestVolume): diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index 16f0d6dbac..69bf380348 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -363,6 +363,17 @@ def get_parser(self, prog_name): help=_('Set a property on this volume ' '(repeat option to set multiple properties)'), ) + bootable_group = parser.add_mutually_exclusive_group() + bootable_group.add_argument( + "--bootable", + action="store_true", + help=_("Mark volume as bootable") + ) + bootable_group.add_argument( + "--non-bootable", + action="store_true", + help=_("Mark volume as non-bootable") + ) return parser def take_action(self, parsed_args): @@ -382,7 +393,12 @@ def take_action(self, parsed_args): if parsed_args.property: volume_client.volumes.set_metadata(volume.id, parsed_args.property) - + if parsed_args.bootable or parsed_args.non_bootable: + try: + volume_client.volumes.set_bootable( + volume.id, parsed_args.bootable) + except Exception as e: + LOG.error(_("Failed to set volume bootable property: %s"), e) kwargs = {} if parsed_args.name: kwargs['display_name'] = parsed_args.name diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index 28946a5f7f..804e233728 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -404,6 +404,17 @@ def get_parser(self, prog_name): '"deleting", "in-use", "attaching", "detaching", ' '"error_deleting" or "maintenance")'), ) + bootable_group = parser.add_mutually_exclusive_group() + bootable_group.add_argument( + "--bootable", + action="store_true", + help=_("Mark volume as bootable") + ) + bootable_group.add_argument( + "--non-bootable", + action="store_true", + help=_("Mark volume as non-bootable") + ) return parser def take_action(self, parsed_args): @@ -446,6 +457,13 @@ def take_action(self, parsed_args): except Exception as e: LOG.error(_("Failed to set volume state: %s"), e) result += 1 + if parsed_args.bootable or parsed_args.non_bootable: + try: + volume_client.volumes.set_bootable( + volume.id, parsed_args.bootable) + except Exception as e: + LOG.error(_("Failed to set volume bootable property: %s"), e) + result += 1 kwargs = {} if parsed_args.name: diff --git a/releasenotes/notes/bug-1535704-d6f013bfa22ab668.yaml b/releasenotes/notes/bug-1535704-d6f013bfa22ab668.yaml new file mode 100644 index 0000000000..5d9077010c --- /dev/null +++ b/releasenotes/notes/bug-1535704-d6f013bfa22ab668.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Add ``--bootable`` and ``--non-bootable`` options to ``os volume set`` + command to mark volume as bootable or non-bootable. + [Bug `1535704 `_] \ No newline at end of file From e04e389da2e9d8ec96398356ceda2ab2388cdad5 Mon Sep 17 00:00:00 2001 From: Ukesh Kumar Vasudevan Date: Thu, 15 Sep 2016 12:51:23 +0530 Subject: [PATCH 1226/3095] router list if availability_zone ext not enabled when calling router list with --long OSC expects the availability_zone field in the response, even if this extension is not loaded: As per the document, http://developer.openstack.org/api-ref/networking/ v2/?expanded=list-routers-detail, The availability zone(s) for the router is available when router_availability_zone extension is enabled. Added testcase to test router list without availability zone Change-Id: Ic9abb06140eb310b797ade8b0463a876d8bea1b8 Closes-bug: #1622565 --- openstackclient/network/v2/router.py | 11 ++++-- .../tests/unit/network/v2/test_router.py | 36 +++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index f6d96d03bb..d30197cc41 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -281,13 +281,20 @@ def take_action(self, parsed_args): columns = columns + ( 'routes', 'external_gateway_info', - 'availability_zones' ) column_headers = column_headers + ( 'Routes', 'External gateway info', - 'Availability zones' ) + # availability zone will be available only when + # router_availability_zone extension is enabled + if client.find_extension("router_availability_zone"): + columns = columns + ( + 'availability_zones', + ) + column_headers = column_headers + ( + 'Availability zones', + ) data = client.routers() return (column_headers, diff --git a/openstackclient/tests/unit/network/v2/test_router.py b/openstackclient/tests/unit/network/v2/test_router.py index 26fe655e30..9d0342228e 100644 --- a/openstackclient/tests/unit/network/v2/test_router.py +++ b/openstackclient/tests/unit/network/v2/test_router.py @@ -285,6 +285,7 @@ class TestListRouter(TestRouter): # The routers going to be listed up. routers = network_fakes.FakeRouter.create_routers(count=3) + _extensions = network_fakes.FakeExtension.create_one_extension() columns = ( 'ID', @@ -300,6 +301,10 @@ class TestListRouter(TestRouter): 'External gateway info', 'Availability zones' ) + columns_long_no_az = columns + ( + 'Routes', + 'External gateway info', + ) data = [] for r in routers: @@ -322,6 +327,15 @@ class TestListRouter(TestRouter): osc_utils.format_list(r.availability_zones), ) ) + data_long_no_az = [] + for i in range(0, len(routers)): + r = routers[i] + data_long_no_az.append( + data[i] + ( + router._format_routes(r.routes), + router._format_external_gateway_info(r.external_gateway_info), + ) + ) def setUp(self): super(TestListRouter, self).setUp() @@ -330,6 +344,7 @@ def setUp(self): self.cmd = router.ListRouter(self.app, self.namespace) self.network.routers = mock.Mock(return_value=self.routers) + self.network.find_extension = mock.Mock(return_value=self._extensions) def test_router_list_no_options(self): arglist = [] @@ -365,6 +380,27 @@ def test_router_list_long(self): self.assertEqual(self.columns_long, columns) self.assertEqual(self.data_long, list(data)) + def test_router_list_long_no_az(self): + arglist = [ + '--long', + ] + verifylist = [ + ('long', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # to mock, that no availability zone + self.network.find_extension = mock.Mock(return_value=None) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + self.network.routers.assert_called_once_with() + self.assertEqual(self.columns_long_no_az, columns) + self.assertEqual(self.data_long_no_az, list(data)) + class TestRemovePortFromRouter(TestRouter): '''Remove port from a Router ''' From e6b09eef0c02f2671ef263df61d193533e65d024 Mon Sep 17 00:00:00 2001 From: Cedric Brandily Date: Thu, 15 Sep 2016 21:57:17 +0200 Subject: [PATCH 1227/3095] Correct login name deduction in SshServer Currently SshServer ("server ssh" command) raises an AttributeError if no explicit login is provided because it uses an attribute which no more exists. Change-Id: I86f68230037d51efb41aca62e07e058733ecd67a Closes-Bug: #1624085 --- openstackclient/compute/v2/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 3e6903b701..1ca3149798 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1677,7 +1677,7 @@ def take_action(self, parsed_args): if parsed_args.login: login = parsed_args.login else: - login = self.app.client_manager._username + login = self.app.client_manager.auth_ref.username if parsed_args.verbose: cmd += " -v" From d3e9361796b4395fc86ea85adb5c9df20cd555d2 Mon Sep 17 00:00:00 2001 From: Reedip Date: Fri, 16 Sep 2016 12:46:45 +0530 Subject: [PATCH 1228/3095] Fix Quota Support for HMs Health Monitors could not be updated using the `openstack quota update` CLI. This patch fixes the same. Change-Id: Ic5a4fa5dce5767e40139137131114834d564f89a Closes-Bug: #1624225 --- openstackclient/common/quota.py | 2 +- openstackclient/tests/unit/common/test_quota.py | 6 +++--- openstackclient/tests/unit/network/v2/fakes.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index aabfa5d54b..4668e193e7 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -75,7 +75,7 @@ 'vip': 'vips', 'subnetpool': 'subnetpools', 'member': 'members', - 'health_monitor': 'health-monitors', + 'healthmonitor': 'health-monitors', } diff --git a/openstackclient/tests/unit/common/test_quota.py b/openstackclient/tests/unit/common/test_quota.py index ac03cb6048..c43755bd05 100644 --- a/openstackclient/tests/unit/common/test_quota.py +++ b/openstackclient/tests/unit/common/test_quota.py @@ -250,7 +250,7 @@ def test_quota_set_network(self): '--ports', str(network_fakes.QUOTA['port']), '--vips', str(network_fakes.QUOTA['vip']), '--members', str(network_fakes.QUOTA['member']), - '--health-monitors', str(network_fakes.QUOTA['health_monitor']), + '--health-monitors', str(network_fakes.QUOTA['healthmonitor']), identity_fakes.project_name, ] verifylist = [ @@ -266,7 +266,7 @@ def test_quota_set_network(self): ('port', network_fakes.QUOTA['port']), ('vip', network_fakes.QUOTA['vip']), ('member', network_fakes.QUOTA['member']), - ('health_monitor', network_fakes.QUOTA['health_monitor']), + ('healthmonitor', network_fakes.QUOTA['healthmonitor']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -284,7 +284,7 @@ def test_quota_set_network(self): 'port': network_fakes.QUOTA['port'], 'vip': network_fakes.QUOTA['vip'], 'member': network_fakes.QUOTA['member'], - 'health_monitor': network_fakes.QUOTA['health_monitor'], + 'healthmonitor': network_fakes.QUOTA['healthmonitor'], } self.network_mock.update_quota.assert_called_once_with( identity_fakes.project_id, diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index e3adb435cb..ec032c929d 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -33,7 +33,7 @@ "port": 50, "vip": 10, "member": 10, - "health_monitor": 10, + "healthmonitor": 10, } From 91c4509afe1bdf89f89a40dfc0a3cb808fd2004a Mon Sep 17 00:00:00 2001 From: Reedip Date: Fri, 16 Sep 2016 13:39:54 +0530 Subject: [PATCH 1229/3095] Fix quota-update issue in LBaaS Currently L7Policies cannot be updated( it was missing in implementation in neutronclient). The same has been taken care in the current patch. Also, currently quota doesnt support updating the members in an LBaaS pool. This patch temporarily removes it, till it is not confirmed that LBaaS v2 needs to support quotas for members or not. Change-Id: I25a54a57debb762a32a280ece8c081fc52365f0f Closes-Bug: #1624097 --- openstackclient/common/quota.py | 2 +- openstackclient/tests/unit/common/test_quota.py | 6 +++--- openstackclient/tests/unit/network/v2/fakes.py | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index 4668e193e7..ebbf7ddf5b 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -74,8 +74,8 @@ 'rbac_policy': 'rbac-policies', 'vip': 'vips', 'subnetpool': 'subnetpools', - 'member': 'members', 'healthmonitor': 'health-monitors', + 'l7policy': 'l7policies', } diff --git a/openstackclient/tests/unit/common/test_quota.py b/openstackclient/tests/unit/common/test_quota.py index c43755bd05..294d772d1a 100644 --- a/openstackclient/tests/unit/common/test_quota.py +++ b/openstackclient/tests/unit/common/test_quota.py @@ -249,8 +249,8 @@ def test_quota_set_network(self): '--rbac-policies', str(network_fakes.QUOTA['rbac_policy']), '--ports', str(network_fakes.QUOTA['port']), '--vips', str(network_fakes.QUOTA['vip']), - '--members', str(network_fakes.QUOTA['member']), '--health-monitors', str(network_fakes.QUOTA['healthmonitor']), + '--l7policies', str(network_fakes.QUOTA['l7policy']), identity_fakes.project_name, ] verifylist = [ @@ -265,8 +265,8 @@ def test_quota_set_network(self): ('rbac_policy', network_fakes.QUOTA['rbac_policy']), ('port', network_fakes.QUOTA['port']), ('vip', network_fakes.QUOTA['vip']), - ('member', network_fakes.QUOTA['member']), ('healthmonitor', network_fakes.QUOTA['healthmonitor']), + ('l7policy', network_fakes.QUOTA['l7policy']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -283,8 +283,8 @@ def test_quota_set_network(self): 'rbac_policy': network_fakes.QUOTA['rbac_policy'], 'port': network_fakes.QUOTA['port'], 'vip': network_fakes.QUOTA['vip'], - 'member': network_fakes.QUOTA['member'], 'healthmonitor': network_fakes.QUOTA['healthmonitor'], + 'l7policy': network_fakes.QUOTA['l7policy'], } self.network_mock.update_quota.assert_called_once_with( identity_fakes.project_id, diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index ec032c929d..4cc3512e9d 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -34,6 +34,7 @@ "vip": 10, "member": 10, "healthmonitor": 10, + "l7policy": 5, } From d44782bc089d6f3092cc2ee22ee4a13ea0c40ce9 Mon Sep 17 00:00:00 2001 From: Cao Xuan Hoang Date: Fri, 16 Sep 2016 14:31:26 +0700 Subject: [PATCH 1230/3095] Add filtering options to os subnet list command This patch adds the following filtering options: '--project' and '--project-domain', '--network', '--gateway', '--name', '--subnet-range' to the command. Change-Id: I575739486b9548492bd00f50130181b825534226 Partially-Implements: blueprint network-commands-options Closes-Bug: #1610883 --- doc/source/command-objects/subnet.rst | 31 +++++ openstackclient/network/v2/subnet.py | 51 +++++++- .../tests/unit/network/v2/test_subnet.py | 110 +++++++++++++++++- .../notes/bug-1610883-38929f6fc2eefc9a.yaml | 8 ++ 4 files changed, 198 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/bug-1610883-38929f6fc2eefc9a.yaml diff --git a/doc/source/command-objects/subnet.rst b/doc/source/command-objects/subnet.rst index 4566d74d7b..d2ea6132c5 100644 --- a/doc/source/command-objects/subnet.rst +++ b/doc/source/command-objects/subnet.rst @@ -161,6 +161,11 @@ List subnets [--long] [--ip-version {4,6}] [--dhcp | --no-dhcp] + [--project [--project-domain ]] + [--network ] + [--gateway ] + [--name ] + [--subnet-range ] .. option:: --long @@ -186,6 +191,32 @@ List subnets Must be a valid device owner value for a network port (repeat option to list multiple service types) +.. option:: --project + + List only subnets which belong to a given project (name or ID) in output + +.. option:: --project-domain + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + +.. option:: --network + + List only subnets which belong to a given network (name or ID) in output + +.. option:: --gateway + + List only subnets of given gateway IP in output + +.. option:: --name + + List only subnets of given name in output + +.. option:: --subnet-range + + List only subnets of given subnet range (in CIDR notation) in output + e.g.: ``--subnet-range 10.10.0.0/16`` + subnet set ---------- diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index f1c7d15d55..8a3f229a2d 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -381,9 +381,41 @@ def get_parser(self, prog_name): "Must be a valid device owner value for a network port " "(repeat option to list multiple service types)") ) + parser.add_argument( + '--project', + metavar='', + help=_("List only subnets which belong to a given project " + "(name or ID) in output") + ) + identity_common.add_project_domain_option_to_parser(parser) + parser.add_argument( + '--network', + metavar='', + help=_("List only subnets which belong to a given network " + "(name or ID) in output") + ) + parser.add_argument( + '--gateway', + metavar='', + help=_("List only subnets of given gateway IP in output") + ) + parser.add_argument( + '--name', + metavar='', + help=_("List only subnets of given name in output") + ) + parser.add_argument( + '--subnet-range', + metavar='', + help=_("List only subnets of given subnet range " + "(in CIDR notation) in output " + "e.g.: --subnet-range 10.10.0.0/16") + ) return parser def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + network_client = self.app.client_manager.network filters = {} if parsed_args.ip_version: filters['ip_version'] = parsed_args.ip_version @@ -393,7 +425,24 @@ def take_action(self, parsed_args): filters['enable_dhcp'] = False if parsed_args.service_types: filters['service_types'] = parsed_args.service_types - data = self.app.client_manager.network.subnets(**filters) + if parsed_args.project: + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + filters['tenant_id'] = project_id + if parsed_args.network: + network_id = network_client.find_network(parsed_args.network, + ignore_missing=False).id + filters['network_id'] = network_id + if parsed_args.gateway: + filters['gateway_ip'] = parsed_args.gateway + if parsed_args.name: + filters['name'] = parsed_args.name + if parsed_args.subnet_range: + filters['cidr'] = parsed_args.subnet_range + data = network_client.subnets(**filters) headers = ('ID', 'Name', 'Network', 'Subnet') columns = ('id', 'name', 'network_id', 'cidr') diff --git a/openstackclient/tests/unit/network/v2/test_subnet.py b/openstackclient/tests/unit/network/v2/test_subnet.py index 6d7623443d..f09fe4faa0 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet.py +++ b/openstackclient/tests/unit/network/v2/test_subnet.py @@ -646,7 +646,6 @@ def test_subnet_list_service_type(self): ('service_types', ['network:router_gateway']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) filters = {'service_types': ['network:router_gateway']} @@ -654,6 +653,24 @@ def test_subnet_list_service_type(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + def test_subnet_list_project(self): + project = identity_fakes_v3.FakeProject.create_one_project() + self.projects_mock.get.return_value = project + arglist = [ + '--project', project.id, + ] + verifylist = [ + ('project', project.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'tenant_id': project.id} + + self.network.subnets.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + def test_subnet_list_service_type_multiple(self): arglist = [ '--service-type', 'network:router_gateway', @@ -668,6 +685,97 @@ def test_subnet_list_service_type_multiple(self): columns, data = self.cmd.take_action(parsed_args) filters = {'service_types': ['network:router_gateway', 'network:floatingip_agent_gateway']} + self.network.subnets.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_subnet_list_project_domain(self): + project = identity_fakes_v3.FakeProject.create_one_project() + self.projects_mock.get.return_value = project + arglist = [ + '--project', project.id, + '--project-domain', project.domain_id, + ] + verifylist = [ + ('project', project.id), + ('project_domain', project.domain_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'tenant_id': project.id} + + self.network.subnets.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_subnet_list_network(self): + network = network_fakes.FakeNetwork.create_one_network() + self.network.find_network = mock.Mock(return_value=network) + arglist = [ + '--network', network.id, + ] + verifylist = [ + ('network', network.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'network_id': network.id} + + self.network.subnets.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_subnet_list_gateway(self): + subnet = network_fakes.FakeSubnet.create_one_subnet() + self.network.find_network = mock.Mock(return_value=subnet) + arglist = [ + '--gateway', subnet.gateway_ip, + ] + verifylist = [ + ('gateway', subnet.gateway_ip), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'gateway_ip': subnet.gateway_ip} + + self.network.subnets.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_subnet_list_name(self): + subnet = network_fakes.FakeSubnet.create_one_subnet() + self.network.find_network = mock.Mock(return_value=subnet) + arglist = [ + '--name', subnet.name, + ] + verifylist = [ + ('name', subnet.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'name': subnet.name} + + self.network.subnets.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_subnet_list_subnet_range(self): + subnet = network_fakes.FakeSubnet.create_one_subnet() + self.network.find_network = mock.Mock(return_value=subnet) + arglist = [ + '--subnet-range', subnet.cidr, + ] + verifylist = [ + ('subnet_range', subnet.cidr), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'cidr': subnet.cidr} self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) diff --git a/releasenotes/notes/bug-1610883-38929f6fc2eefc9a.yaml b/releasenotes/notes/bug-1610883-38929f6fc2eefc9a.yaml new file mode 100644 index 0000000000..215c24f438 --- /dev/null +++ b/releasenotes/notes/bug-1610883-38929f6fc2eefc9a.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Make ``subnet list`` command supports listing up subnets with + some filtering options by adding ``--project`` and ``--project-domain``, + ``--network``, ``--gateway``,``--name``, ``--subnet-range`` + options to the command. + [Bug `1610883 `_] From 1a6ae06ad5e33dd03e4f25ea094e6ae2a15ad874 Mon Sep 17 00:00:00 2001 From: Cao Xuan Hoang Date: Thu, 18 Aug 2016 13:15:58 +0700 Subject: [PATCH 1231/3095] Add filtering options to os subnet pool list command This patch adds the following filtering options: '--share' and '--no-share', '--default' and '--no-default', '--project' and '--project-domain', '--name', '--address-scope' options to the command. Change-Id: I7ad9ed6842c6b3475ebf316fb5b954c7f5a7d7e3 Closes-Bug: #1613926 Partially-Implements: blueprint network-commands-options --- doc/source/command-objects/subnet-pool.rst | 38 +++++ openstackclient/network/v2/subnet_pool.py | 103 +++++++++---- .../tests/unit/network/v2/test_subnet_pool.py | 138 ++++++++++++++++++ .../notes/bug-1613926-2d0e405831c0b5a9.yaml | 8 + 4 files changed, 256 insertions(+), 31 deletions(-) create mode 100644 releasenotes/notes/bug-1613926-2d0e405831c0b5a9.yaml diff --git a/doc/source/command-objects/subnet-pool.rst b/doc/source/command-objects/subnet-pool.rst index 005b83579b..3330d62d5a 100644 --- a/doc/source/command-objects/subnet-pool.rst +++ b/doc/source/command-objects/subnet-pool.rst @@ -104,11 +104,49 @@ List subnet pools os subnet pool list [--long] + [--share | --no-share] + [--default | --no-default] + [--project [--project-domain ]] + [--name ] + [--address-scope ] .. option:: --long List additional fields in output +.. option:: --share + + List subnets shared between projects + +.. option:: --no-share + + List subnets not shared between projects + +.. option:: --default + + List subnets used as the default external subnet pool + +.. option:: --no-default + + List subnets not used as the default external subnet pool + +.. option:: --project + + List subnets according to their project (name or ID) + +.. option:: --project-domain + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + +.. option:: --name + + List only subnets of given name in output + +.. option:: --address-scope + + List only subnets of given address scope (name or ID) in output + subnet pool set --------------- diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index d3fab8acba..6852ca27a0 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -226,41 +226,82 @@ def get_parser(self, prog_name): default=False, help=_("List additional fields in output") ) + shared_group = parser.add_mutually_exclusive_group() + shared_group.add_argument( + '--share', + action='store_true', + help=_("List subnets shared between projects"), + ) + shared_group.add_argument( + '--no-share', + action='store_true', + help=_("List subnets not shared between projects"), + ) + default_group = parser.add_mutually_exclusive_group() + default_group.add_argument( + '--default', + action='store_true', + help=_("List subnets used as the default external subnet pool"), + ) + default_group.add_argument( + '--no-default', + action='store_true', + help=_("List subnets not used as the default external subnet pool") + ) + parser.add_argument( + '--project', + metavar='', + help=_("List subnets according to their project (name or ID)") + ) + identity_common.add_project_domain_option_to_parser(parser) + parser.add_argument( + '--name', + metavar='', + help=_("List only subnets of given name in output") + ) + parser.add_argument( + '--address-scope', + metavar='', + help=_("List only subnets of given address scope (name or ID) " + "in output") + ) return parser def take_action(self, parsed_args): - data = self.app.client_manager.network.subnet_pools() - + identity_client = self.app.client_manager.identity + network_client = self.app.client_manager.network + filters = {} + if parsed_args.share: + filters['shared'] = True + elif parsed_args.no_share: + filters['shared'] = False + if parsed_args.default: + filters['is_default'] = True + elif parsed_args.no_default: + filters['is_default'] = False + if parsed_args.project: + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + filters['tenant_id'] = project_id + if parsed_args.name is not None: + filters['name'] = parsed_args.name + if parsed_args.address_scope: + address_scope = network_client.find_address_scope( + parsed_args.address_scope, + ignore_missing=False) + filters['address_scope_id'] = address_scope.id + data = network_client.subnet_pools(**filters) + + headers = ('ID', 'Name', 'Prefixes') + columns = ('id', 'name', 'prefixes') if parsed_args.long: - headers = ( - 'ID', - 'Name', - 'Prefixes', - 'Default Prefix Length', - 'Address Scope', - 'Default Subnet Pool', - 'Shared', - ) - columns = ( - 'id', - 'name', - 'prefixes', - 'default_prefixlen', - 'address_scope_id', - 'is_default', - 'shared', - ) - else: - headers = ( - 'ID', - 'Name', - 'Prefixes', - ) - columns = ( - 'id', - 'name', - 'prefixes', - ) + headers += ('Default Prefix Length', 'Address Scope', + 'Default Subnet Pool', 'Shared') + columns += ('default_prefixlen', 'address_scope_id', + 'is_default', 'shared') return (headers, (utils.get_item_properties( diff --git a/openstackclient/tests/unit/network/v2/test_subnet_pool.py b/openstackclient/tests/unit/network/v2/test_subnet_pool.py index e0e1969baf..251323d94c 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/unit/network/v2/test_subnet_pool.py @@ -400,6 +400,144 @@ def test_subnet_pool_list_long(self): self.assertEqual(self.columns_long, columns) self.assertEqual(self.data_long, list(data)) + def test_subnet_pool_list_no_share(self): + arglist = [ + '--no-share', + ] + verifylist = [ + ('share', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'shared': False} + + self.network.subnet_pools.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_subnet_pool_list_share(self): + arglist = [ + '--share', + ] + verifylist = [ + ('share', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'shared': True} + + self.network.subnet_pools.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_subnet_pool_list_no_default(self): + arglist = [ + '--no-default', + ] + verifylist = [ + ('default', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'is_default': False} + + self.network.subnet_pools.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_subnet_pool_list_default(self): + arglist = [ + '--default', + ] + verifylist = [ + ('default', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'is_default': True} + + self.network.subnet_pools.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_subnet_pool_list_project(self): + project = identity_fakes_v3.FakeProject.create_one_project() + self.projects_mock.get.return_value = project + arglist = [ + '--project', project.id, + ] + verifylist = [ + ('project', project.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'tenant_id': project.id} + + self.network.subnet_pools.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_subnet_pool_list_project_domain(self): + project = identity_fakes_v3.FakeProject.create_one_project() + self.projects_mock.get.return_value = project + arglist = [ + '--project', project.id, + '--project-domain', project.domain_id, + ] + verifylist = [ + ('project', project.id), + ('project_domain', project.domain_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'tenant_id': project.id} + + self.network.subnet_pools.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_subnet_pool_list_name(self): + subnet_pool = network_fakes.FakeSubnetPool.create_one_subnet_pool() + self.network.find_network = mock.Mock(return_value=subnet_pool) + arglist = [ + '--name', subnet_pool.name, + ] + verifylist = [ + ('name', subnet_pool.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'name': subnet_pool.name} + + self.network.subnet_pools.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_subnet_pool_list_address_scope(self): + addr_scope = network_fakes.FakeAddressScope.create_one_address_scope() + self.network.find_address_scope = mock.Mock(return_value=addr_scope) + arglist = [ + '--address-scope', addr_scope.id, + ] + verifylist = [ + ('address_scope', addr_scope.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'address_scope_id': addr_scope.id} + + self.network.subnet_pools.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + class TestSetSubnetPool(TestSubnetPool): diff --git a/releasenotes/notes/bug-1613926-2d0e405831c0b5a9.yaml b/releasenotes/notes/bug-1613926-2d0e405831c0b5a9.yaml new file mode 100644 index 0000000000..fca316c162 --- /dev/null +++ b/releasenotes/notes/bug-1613926-2d0e405831c0b5a9.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Make ``subnet pool list`` command supports listing up subnets with + some filtering options by adding ``--share`` and ``--no-share``, + ``--project`` and ``--project-domain``, ``--default`` and ``--no-default``, + ``--name``, ``--address-scope`` options to the command. + [Bug `1613926 `_] From 554607eb3dab879da8e172eacb72930e54f0acf4 Mon Sep 17 00:00:00 2001 From: Reedip Date: Sat, 17 Sep 2016 13:09:42 +0530 Subject: [PATCH 1232/3095] Provide support to list ports by network The new --network option provides a list of ports connected with the current network. Change-Id: I6a45184887b3e65f792391cb7e91f4ad85f29e03 Partial-Bug:#1562067 Partially-Implements: blueprint network-commands-options --- doc/source/command-objects/port.rst | 5 +++++ openstackclient/network/v2/port.py | 8 ++++++++ openstackclient/tests/unit/network/v2/test_port.py | 11 +++++++++-- ...network-list-option-to-ports-9d101344ddeb3e64.yaml | 7 +++++++ 4 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/add-network-list-option-to-ports-9d101344ddeb3e64.yaml diff --git a/doc/source/command-objects/port.rst b/doc/source/command-objects/port.rst index 771f2f8d48..04cf59a2c0 100644 --- a/doc/source/command-objects/port.rst +++ b/doc/source/command-objects/port.rst @@ -116,6 +116,7 @@ List ports os port list [--device-owner ] [--router | --server ] + [--network ] .. option:: --device-owner @@ -130,6 +131,10 @@ List ports List only ports attached to this server (name or ID) +.. option:: --network + + List only ports attached to this network (name or ID) + port set -------- diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index d7268573dc..0df78e43b3 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -345,6 +345,10 @@ def get_parser(self, prog_name): "This is the entity that uses the port (for example, " "network:dhcp).") ) + parser.add_argument( + '--network', + metavar='', + help=_("List only ports connected to this network (name or ID)")) device_group = parser.add_mutually_exclusive_group() device_group.add_argument( '--router', @@ -387,6 +391,10 @@ def take_action(self, parsed_args): server = utils.find_resource(compute_client.servers, parsed_args.server) filters['device_id'] = server.id + if parsed_args.network: + network = network_client.find_network(parsed_args.network, + ignore_missing=False) + filters['network_id'] = network.id data = network_client.ports(**filters) diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index afa67bbadb..271e816078 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -337,7 +337,11 @@ def setUp(self): fake_router = network_fakes.FakeRouter.create_one_router({ 'id': 'fake-router-id', }) + fake_network = network_fakes.FakeNetwork.create_one_network({ + 'id': 'fake-network-id', + }) self.network.find_router = mock.Mock(return_value=fake_router) + self.network.find_network = mock.Mock(return_value=fake_network) self.app.client_manager.compute = mock.Mock() def test_port_list_no_options(self): @@ -414,11 +418,13 @@ def test_port_list_all_opt(self): arglist = [ '--device-owner', self._ports[0].device_owner, '--router', 'fake-router-name', + '--network', 'fake-network-name', ] verifylist = [ ('device_owner', self._ports[0].device_owner), - ('router', 'fake-router-name') + ('router', 'fake-router-name'), + ('network', 'fake-network-name') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -427,7 +433,8 @@ def test_port_list_all_opt(self): self.network.ports.assert_called_once_with(**{ 'device_owner': self._ports[0].device_owner, - 'device_id': 'fake-router-id' + 'device_id': 'fake-router-id', + 'network_id': 'fake-network-id' }) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) diff --git a/releasenotes/notes/add-network-list-option-to-ports-9d101344ddeb3e64.yaml b/releasenotes/notes/add-network-list-option-to-ports-9d101344ddeb3e64.yaml new file mode 100644 index 0000000000..478b29a359 --- /dev/null +++ b/releasenotes/notes/add-network-list-option-to-ports-9d101344ddeb3e64.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Ports can now be listed as per the networks they are + connected to by using the ``--network`` option with + the ``port list`` CLI. + [ Blueprint `network-commands-options `_] From 5ec435e706d137afb714cfd5c5ddbd40d8107a9e Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 16 Sep 2016 10:01:39 -0500 Subject: [PATCH 1233/3095] Clean up docs and notes for 3.3.0 release Change-Id: I432dcb5e85819d22e6b30758da80d31c9a0db654 --- doc/source/command-objects/volume-service.rst | 3 ++- ...58deeafbc8be.yaml => bug-1602073-5deb58deeafbc8be.yaml} | 0 releasenotes/notes/bug-1610883-38929f6fc2eefc9a.yaml | 6 ++---- releasenotes/notes/bug-1613926-2d0e405831c0b5a9.yaml | 7 +++---- releasenotes/notes/bug-1622565-2e715aff8b054401.yaml | 6 ++++++ releasenotes/notes/bug-1624085-7cf296649277f405.yaml | 4 ++++ 6 files changed, 17 insertions(+), 9 deletions(-) rename releasenotes/notes/{bug_1602073-5deb58deeafbc8be.yaml => bug-1602073-5deb58deeafbc8be.yaml} (100%) create mode 100644 releasenotes/notes/bug-1622565-2e715aff8b054401.yaml create mode 100644 releasenotes/notes/bug-1624085-7cf296649277f405.yaml diff --git a/doc/source/command-objects/volume-service.rst b/doc/source/command-objects/volume-service.rst index d7495968fb..4fea41fe26 100644 --- a/doc/source/command-objects/volume-service.rst +++ b/doc/source/command-objects/volume-service.rst @@ -41,7 +41,8 @@ Set volume service properties os volume service set [--enable | --disable] [--disable-reason ] - + + .. option:: --enable diff --git a/releasenotes/notes/bug_1602073-5deb58deeafbc8be.yaml b/releasenotes/notes/bug-1602073-5deb58deeafbc8be.yaml similarity index 100% rename from releasenotes/notes/bug_1602073-5deb58deeafbc8be.yaml rename to releasenotes/notes/bug-1602073-5deb58deeafbc8be.yaml diff --git a/releasenotes/notes/bug-1610883-38929f6fc2eefc9a.yaml b/releasenotes/notes/bug-1610883-38929f6fc2eefc9a.yaml index 215c24f438..a1bde63eae 100644 --- a/releasenotes/notes/bug-1610883-38929f6fc2eefc9a.yaml +++ b/releasenotes/notes/bug-1610883-38929f6fc2eefc9a.yaml @@ -1,8 +1,6 @@ --- features: - | - Make ``subnet list`` command supports listing up subnets with - some filtering options by adding ``--project`` and ``--project-domain``, - ``--network``, ``--gateway``,``--name``, ``--subnet-range`` - options to the command. + Add ``--project``, ``--project-domain``, ``--network``, ``--gateway``, + ``--name`` and ``--subnet-range`` options to the ``subnet list`` command. [Bug `1610883 `_] diff --git a/releasenotes/notes/bug-1613926-2d0e405831c0b5a9.yaml b/releasenotes/notes/bug-1613926-2d0e405831c0b5a9.yaml index fca316c162..2959a268c7 100644 --- a/releasenotes/notes/bug-1613926-2d0e405831c0b5a9.yaml +++ b/releasenotes/notes/bug-1613926-2d0e405831c0b5a9.yaml @@ -1,8 +1,7 @@ --- features: - | - Make ``subnet pool list`` command supports listing up subnets with - some filtering options by adding ``--share`` and ``--no-share``, - ``--project`` and ``--project-domain``, ``--default`` and ``--no-default``, - ``--name``, ``--address-scope`` options to the command. + Add ``--share``, ``--no-share``, ``--project``, ``--project-domain``, + ``--default``, ``--no-default``, ``--name`` and ``--address-scope`` + options to the ``subnet pool list`` command. [Bug `1613926 `_] diff --git a/releasenotes/notes/bug-1622565-2e715aff8b054401.yaml b/releasenotes/notes/bug-1622565-2e715aff8b054401.yaml new file mode 100644 index 0000000000..9e63a5f3d2 --- /dev/null +++ b/releasenotes/notes/bug-1622565-2e715aff8b054401.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Fix ``--long`` option in ``router list`` command for deployments without + the ``router_availability_zone`` extension is not enabled. + [Bug `1622565 `_] diff --git a/releasenotes/notes/bug-1624085-7cf296649277f405.yaml b/releasenotes/notes/bug-1624085-7cf296649277f405.yaml new file mode 100644 index 0000000000..40b019a17b --- /dev/null +++ b/releasenotes/notes/bug-1624085-7cf296649277f405.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - Fix missing ``_username`` attribute error in ``server ssh`` command. + [Bug `1624085 `_] From 88be7ddd388c440d3f80564c7f1f4f2aed1a8e36 Mon Sep 17 00:00:00 2001 From: Ankur Gupta Date: Mon, 19 Sep 2016 16:43:28 -0500 Subject: [PATCH 1234/3095] Add --description to Create/Set Network Added simple option for network description to create and set network. Change-Id: I90ce4db4e365a56ecddb00d59f4c5aa2ebbd49a3 Partially-Implements: blueprint network-command-options --- doc/source/command-objects/network.rst | 10 ++++++++++ openstackclient/network/v2/network.py | 14 ++++++++++++++ openstackclient/tests/unit/network/v2/fakes.py | 1 + .../tests/unit/network/v2/test_network.py | 12 ++++++++++++ 4 files changed, 37 insertions(+) diff --git a/doc/source/command-objects/network.rst b/doc/source/command-objects/network.rst index 5d9a5ca860..8075d50919 100644 --- a/doc/source/command-objects/network.rst +++ b/doc/source/command-objects/network.rst @@ -23,6 +23,7 @@ Create new network [--project [--project-domain ]] [--enable | --disable] [--share | --no-share] + [--description ] [--availability-zone-hint ] [--enable-port-security | --disable-port-security] [--external [--default | --no-default] | --internal] @@ -65,6 +66,10 @@ Create new network Do not share the network between projects +.. option:: --description + + Set network description + .. option:: --availability-zone-hint Availability Zone in which to create this network @@ -206,6 +211,7 @@ Set network properties [--name ] [--enable | --disable] [--share | --no-share] + [--description ] [--enable-port-security | --disable-port-security] [--external [--default | --no-default] | --internal] [--provider-network-type ] @@ -234,6 +240,10 @@ Set network properties Do not share the network between projects +.. option:: --description + + Set network description + .. option:: --enable-port-security Enable port security by default for ports created on diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index ccc02fd8e6..31b173e138 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -78,6 +78,10 @@ def _get_attrs(client_manager, parsed_args): parsed_args.availability_zone_hints is not None: attrs['availability_zone_hints'] = parsed_args.availability_zone_hints + # set description + if parsed_args.description: + attrs['description'] = parsed_args.description + # update_external_network_options if parsed_args.internal: attrs['router:external'] = False @@ -191,6 +195,11 @@ def update_parser_network(self, parser): metavar='', help=_("Owner's project (name or ID)") ) + parser.add_argument( + '--description', + metavar='', + help=_("Set network description") + ) identity_common.add_project_domain_option_to_parser(parser) parser.add_argument( '--availability-zone-hint', @@ -420,6 +429,11 @@ def get_parser(self, prog_name): action='store_true', help=_("Do not share the network between projects") ) + parser.add_argument( + '--description', + metavar=" Date: Thu, 18 Aug 2016 16:50:52 +0700 Subject: [PATCH 1235/3095] Add 'description' option to os subnet (pool) create/set cmd This patch adds '--description' option to the commands. Change-Id: Ifc2828670c3c48a87a0493d98686a5babf9b2ae7 Closes-Bug: #1614458 Closes-Bug: #1614823 Partially-Implements: blueprint network-commands-options --- doc/source/command-objects/subnet-pool.rst | 10 +++ doc/source/command-objects/subnet.rst | 9 +++ openstackclient/network/v2/subnet.py | 12 ++++ openstackclient/network/v2/subnet_pool.py | 13 ++++ .../tests/unit/network/v2/fakes.py | 2 + .../tests/unit/network/v2/test_subnet.py | 64 +++++++++++++++++++ .../tests/unit/network/v2/test_subnet_pool.py | 47 ++++++++++++++ .../notes/bug-1614458-c42be5738f447db8.yaml | 6 ++ .../notes/bug-1614823-e89080342f25f2c0.yaml | 6 ++ 9 files changed, 169 insertions(+) create mode 100644 releasenotes/notes/bug-1614458-c42be5738f447db8.yaml create mode 100644 releasenotes/notes/bug-1614823-e89080342f25f2c0.yaml diff --git a/doc/source/command-objects/subnet-pool.rst b/doc/source/command-objects/subnet-pool.rst index 005b83579b..b59cd286ef 100644 --- a/doc/source/command-objects/subnet-pool.rst +++ b/doc/source/command-objects/subnet-pool.rst @@ -19,6 +19,7 @@ Create subnet pool [--default-prefix-length ] [--min-prefix-length ] [--max-prefix-length ] + [--description ] [--project [--project-domain ]] [--address-scope ] [--default | --no-default] @@ -38,6 +39,10 @@ Create subnet pool Set subnet pool maximum prefix length +.. option:: --description + + Set subnet pool description + .. option:: --project Owner's project (name or ID) @@ -125,6 +130,7 @@ Set subnet pool properties [--max-prefix-length ] [--address-scope | --no-address-scope] [--default | --no-default] + [--description ] .. option:: --name @@ -165,6 +171,10 @@ Set subnet pool properties Set this as a non-default subnet pool +.. option:: --description + + Set subnet pool description + .. _subnet_pool_set-subnet-pool: .. describe:: diff --git a/doc/source/command-objects/subnet.rst b/doc/source/command-objects/subnet.rst index 4566d74d7b..35a7655398 100644 --- a/doc/source/command-objects/subnet.rst +++ b/doc/source/command-objects/subnet.rst @@ -26,6 +26,7 @@ Create new subnet [--gateway ] [--host-route destination=,gateway=] [--ip-version {4,6}] + [--description ] [--ipv6-ra-mode {dhcpv6-stateful,dhcpv6-stateless,slaac}] [--ipv6-address-mode {dhcpv6-stateful,dhcpv6-stateless,slaac}] [--network-segment ] @@ -100,6 +101,10 @@ Create new subnet IP version is determined from the subnet pool and this option is ignored. +.. option:: --description + + Set subnet description + .. option:: --ipv6-ra-mode {dhcpv6-stateful,dhcpv6-stateless,slaac} IPv6 RA (Router Advertisement) mode, @@ -202,6 +207,7 @@ Set subnet properties [--host-route destination=,gateway=] [--service-type ] [--name ] + [--description ] .. option:: --allocation-pool start=,end= @@ -243,6 +249,9 @@ Set subnet properties ``network:floatingip_agent_gateway``. Must be a valid device owner value for a network port (repeat option to set multiple service types) +.. option:: --description + + Set subnet description .. option:: --name diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index f1c7d15d55..85babf1658 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -196,6 +196,8 @@ def _get_attrs(client_manager, parsed_args, is_create=True): if ('service_types' in parsed_args and parsed_args.service_types is not None): attrs['service_types'] = parsed_args.service_types + if parsed_args.description is not None: + attrs['description'] = parsed_args.description return attrs @@ -294,6 +296,11 @@ def get_parser(self, prog_name): metavar='', help=_("Network this subnet belongs to (name or ID)") ) + parser.add_argument( + '--description', + metavar='', + help=_("Set subnet description") + ) _get_common_parse_arguments(parser) return parser @@ -447,6 +454,11 @@ def get_parser(self, prog_name): "'none': This subnet will not use a gateway, " "e.g.: --gateway 192.168.9.1, --gateway none.") ) + parser.add_argument( + '--description', + metavar='', + help=_("Set subnet description") + ) _get_common_parse_arguments(parser) return parser diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index d3fab8acba..6578c94bb0 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -81,6 +81,9 @@ def _get_attrs(client_manager, parsed_args): ).id attrs['tenant_id'] = project_id + if parsed_args.description is not None: + attrs['description'] = parsed_args.description + return attrs @@ -167,6 +170,11 @@ def get_parser(self, prog_name): action='store_true', help=_("Set this subnet pool as not shared"), ) + parser.add_argument( + '--description', + metavar='', + help=_("Set subnet pool description") + ) return parser def take_action(self, parsed_args): @@ -299,6 +307,11 @@ def get_parser(self, prog_name): help=_("Remove address scope associated with the subnet pool") ) _add_default_options(parser) + parser.add_argument( + '--description', + metavar='', + help=_("Set subnet pool description") + ) return parser diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index e3adb435cb..7559811c62 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -891,6 +891,7 @@ def create_one_subnet(attrs=None): 'segment_id': None, 'service_types': [], 'subnetpool_id': None, + 'description': 'subnet-description-' + uuid.uuid4().hex, } # Overwrite default attributes. @@ -1046,6 +1047,7 @@ def create_one_subnet_pool(attrs=None): 'min_prefixlen': '8', 'default_quota': None, 'ip_version': '4', + 'description': 'subnet-pool-description-' + uuid.uuid4().hex, } # Overwrite default attributes. diff --git a/openstackclient/tests/unit/network/v2/test_subnet.py b/openstackclient/tests/unit/network/v2/test_subnet.py index 6d7623443d..38613c0049 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet.py +++ b/openstackclient/tests/unit/network/v2/test_subnet.py @@ -110,6 +110,7 @@ class TestCreateSubnet(TestSubnet): columns = ( 'allocation_pools', 'cidr', + 'description', 'dns_nameservers', 'enable_dhcp', 'gateway_ip', @@ -129,6 +130,7 @@ class TestCreateSubnet(TestSubnet): data = ( subnet_v2._format_allocation_pools(_subnet.allocation_pools), _subnet.cidr, + _subnet.description, utils.format_list(_subnet.dns_nameservers), _subnet.enable_dhcp, _subnet.gateway_ip, @@ -148,6 +150,7 @@ class TestCreateSubnet(TestSubnet): data_subnet_pool = ( subnet_v2._format_allocation_pools(_subnet_from_pool.allocation_pools), _subnet_from_pool.cidr, + _subnet_from_pool.description, utils.format_list(_subnet_from_pool.dns_nameservers), _subnet_from_pool.enable_dhcp, _subnet_from_pool.gateway_ip, @@ -167,6 +170,7 @@ class TestCreateSubnet(TestSubnet): data_ipv6 = ( subnet_v2._format_allocation_pools(_subnet_ipv6.allocation_pools), _subnet_ipv6.cidr, + _subnet_ipv6.description, utils.format_list(_subnet_ipv6.dns_nameservers), _subnet_ipv6.enable_dhcp, _subnet_ipv6.gateway_ip, @@ -427,6 +431,40 @@ def test_create_with_network_segment(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_create_with_description(self): + # Mock SDK calls for this test. + self.network.create_subnet = mock.Mock(return_value=self._subnet) + self._network.id = self._subnet.network_id + + arglist = [ + "--subnet-range", self._subnet.cidr, + "--network", self._subnet.network_id, + "--description", self._subnet.description, + self._subnet.name, + ] + verifylist = [ + ('name', self._subnet.name), + ('description', self._subnet.description), + ('subnet_range', self._subnet.cidr), + ('network', self._subnet.network_id), + ('ip_version', self._subnet.ip_version), + ('gateway', 'auto'), + + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_subnet.assert_called_once_with(**{ + 'cidr': self._subnet.cidr, + 'ip_version': self._subnet.ip_version, + 'name': self._subnet.name, + 'network_id': self._subnet.network_id, + 'description': self._subnet.description, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + class TestDeleteSubnet(TestSubnet): @@ -768,6 +806,30 @@ def test_append_options(self): _testsubnet, **attrs) self.assertIsNone(result) + def test_set_non_append_options(self): + arglist = [ + "--description", "new_description", + "--dhcp", + "--gateway", self._subnet.gateway_ip, + self._subnet.name, + ] + verifylist = [ + ('description', "new_description"), + ('dhcp', True), + ('gateway', self._subnet.gateway_ip), + ('subnet', self._subnet.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + attrs = { + 'enable_dhcp': True, + 'gateway_ip': self._subnet.gateway_ip, + 'description': "new_description", + } + self.network.update_subnet.assert_called_with(self._subnet, **attrs) + self.assertIsNone(result) + class TestShowSubnet(TestSubnet): # The subnets to be shown @@ -776,6 +838,7 @@ class TestShowSubnet(TestSubnet): columns = ( 'allocation_pools', 'cidr', + 'description', 'dns_nameservers', 'enable_dhcp', 'gateway_ip', @@ -795,6 +858,7 @@ class TestShowSubnet(TestSubnet): data = ( subnet_v2._format_allocation_pools(_subnet.allocation_pools), _subnet.cidr, + _subnet.description, utils.format_list(_subnet.dns_nameservers), _subnet.enable_dhcp, _subnet.gateway_ip, diff --git a/openstackclient/tests/unit/network/v2/test_subnet_pool.py b/openstackclient/tests/unit/network/v2/test_subnet_pool.py index e0e1969baf..c78dc6fb8b 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/unit/network/v2/test_subnet_pool.py @@ -50,6 +50,7 @@ class TestCreateSubnetPool(TestSubnetPool): 'address_scope_id', 'default_prefixlen', 'default_quota', + 'description', 'id', 'ip_version', 'is_default', @@ -64,6 +65,7 @@ class TestCreateSubnetPool(TestSubnetPool): _subnet_pool.address_scope_id, _subnet_pool.default_prefixlen, _subnet_pool.default_quota, + _subnet_pool.description, _subnet_pool.id, _subnet_pool.ip_version, _subnet_pool.is_default, @@ -245,6 +247,29 @@ def test_create_default_and_shared_options(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_create_with_description(self): + arglist = [ + '--pool-prefix', '10.0.10.0/24', + '--description', self._subnet_pool.description, + self._subnet_pool.name, + ] + verifylist = [ + ('prefixes', ['10.0.10.0/24']), + ('description', self._subnet_pool.description), + ('name', self._subnet_pool.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_subnet_pool.assert_called_once_with(**{ + 'name': self._subnet_pool.name, + 'prefixes': ['10.0.10.0/24'], + 'description': self._subnet_pool.description, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + class TestDeleteSubnetPool(TestSubnetPool): @@ -611,6 +636,26 @@ def test_set_no_default_conflict(self): self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) + def test_set_description(self): + arglist = [ + '--description', 'new_description', + self._subnet_pool.name, + ] + verifylist = [ + ('description', "new_description"), + ('subnet_pool', self._subnet_pool.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + attrs = { + 'description': "new_description", + } + self.network.update_subnet_pool.assert_called_once_with( + self._subnet_pool, **attrs) + self.assertIsNone(result) + class TestShowSubnetPool(TestSubnetPool): @@ -621,6 +666,7 @@ class TestShowSubnetPool(TestSubnetPool): 'address_scope_id', 'default_prefixlen', 'default_quota', + 'description', 'id', 'ip_version', 'is_default', @@ -636,6 +682,7 @@ class TestShowSubnetPool(TestSubnetPool): _subnet_pool.address_scope_id, _subnet_pool.default_prefixlen, _subnet_pool.default_quota, + _subnet_pool.description, _subnet_pool.id, _subnet_pool.ip_version, _subnet_pool.is_default, diff --git a/releasenotes/notes/bug-1614458-c42be5738f447db8.yaml b/releasenotes/notes/bug-1614458-c42be5738f447db8.yaml new file mode 100644 index 0000000000..671882b0a8 --- /dev/null +++ b/releasenotes/notes/bug-1614458-c42be5738f447db8.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Adds ``description`` option to ``subnet create`` and + ``subnet set`` commands. + [Bug `1614458 `_] diff --git a/releasenotes/notes/bug-1614823-e89080342f25f2c0.yaml b/releasenotes/notes/bug-1614823-e89080342f25f2c0.yaml new file mode 100644 index 0000000000..96c6661989 --- /dev/null +++ b/releasenotes/notes/bug-1614823-e89080342f25f2c0.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Adds ``description`` option to ``subnet pool create`` + and ``subnet pool set`` commands. + [Bug `1614823 `_] From 6f0462732a999f43ca0a602e1f323c833debaa5c Mon Sep 17 00:00:00 2001 From: zhufl Date: Wed, 21 Sep 2016 10:45:39 +0800 Subject: [PATCH 1236/3095] Remove unnecessary setUp setUp will be automatically called around each testcase, so this is to remove setUp that doing nothing additional than super to keep code clean. Change-Id: I47be2be6f0e9785eb166f89d411582d6292babc1 --- openstackclient/tests/unit/test_shell.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/openstackclient/tests/unit/test_shell.py b/openstackclient/tests/unit/test_shell.py index 87cd7f5185..3d91da9bb7 100644 --- a/openstackclient/tests/unit/test_shell.py +++ b/openstackclient/tests/unit/test_shell.py @@ -413,9 +413,6 @@ def test_empty_env(self): class TestShellArgV(TestShell): """Test the deferred help flag""" - def setUp(self): - super(TestShellArgV, self).setUp() - def test_shell_argv(self): """Test argv decoding From b38be94a5d82eb88d27c81e697152ad064854466 Mon Sep 17 00:00:00 2001 From: reedip Date: Wed, 20 Apr 2016 17:31:52 +0900 Subject: [PATCH 1237/3095] Introduce overwrite functionality in ``osc port set`` The overwrite functionality allows user to overwrite either the binding-profile or the fixed-ips of a specific port. Change-Id: I8ec3d04eeaf28972ee545fcdda4d5f7bd9deb915 partially-implements: blueprint allow-overwrite-set-options --- doc/source/command-objects/port.rst | 14 ++++-- openstackclient/network/v2/port.py | 29 +++++++----- .../tests/unit/network/v2/test_port.py | 44 +++++++++++++++++++ ...add-overwrite-option-190a9c6904d53dab.yaml | 6 +++ 4 files changed, 78 insertions(+), 15 deletions(-) create mode 100644 releasenotes/notes/add-overwrite-option-190a9c6904d53dab.yaml diff --git a/doc/source/command-objects/port.rst b/doc/source/command-objects/port.rst index 4d7b95b4e3..49e6840719 100644 --- a/doc/source/command-objects/port.rst +++ b/doc/source/command-objects/port.rst @@ -135,11 +135,13 @@ Set port properties .. code:: bash os port set - [--fixed-ip subnet=,ip-address= | --no-fixed-ip] + [--fixed-ip subnet=,ip-address=] + [--no-fixed-ip] [--device ] [--device-owner ] [--vnic-type ] - [--binding-profile | --no-binding-profile] + [--binding-profile ] + [--no-binding-profile] [--host ] [--enable | --disable] [--name ] @@ -153,7 +155,9 @@ Set port properties .. option:: --no-fixed-ip - Clear existing information of fixed IP addresses + Clear existing information of fixed IP addresses. + Specify both --fixed-ip and --no-fixed-ip + to overwrite the current fixed IP addresses. .. option:: --device @@ -177,7 +181,9 @@ Set port properties .. option:: --no-binding-profile - Clear existing information of binding:profile + Clear existing information of binding:profile. + Specify both --binding-profile and --no-binding-profile + to overwrite the current binding:profile information. .. option:: --host diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 05c5a012f3..b3634eb744 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -109,7 +109,6 @@ def _get_attrs(client_manager, parsed_args): 'The --host-id option is deprecated, ' 'please use --host instead.' )) - if parsed_args.fixed_ip is not None: attrs['fixed_ips'] = parsed_args.fixed_ip if parsed_args.device: @@ -409,8 +408,7 @@ def get_parser(self, prog_name): metavar="", help=_("Set port name") ) - fixed_ip = parser.add_mutually_exclusive_group() - fixed_ip.add_argument( + parser.add_argument( '--fixed-ip', metavar='subnet=,ip-address=', action=parseractions.MultiKeyValueAction, @@ -419,13 +417,14 @@ def get_parser(self, prog_name): "subnet=,ip-address= " "(repeat option to set multiple fixed IP addresses)") ) - fixed_ip.add_argument( + parser.add_argument( '--no-fixed-ip', action='store_true', - help=_("Clear existing information of fixed IP addresses") + help=_("Clear existing information of fixed IP addresses." + "Specify both --fixed-ip and --no-fixed-ip " + "to overwrite the current fixed IP addresses.") ) - binding_profile = parser.add_mutually_exclusive_group() - binding_profile.add_argument( + parser.add_argument( '--binding-profile', metavar='', action=JSONKeyValueAction, @@ -433,10 +432,12 @@ def get_parser(self, prog_name): "be passed as = or JSON. " "(repeat option to set multiple binding:profile data)") ) - binding_profile.add_argument( + parser.add_argument( '--no-binding-profile', action='store_true', - help=_("Clear existing information of binding:profile") + help=_("Clear existing information of binding:profile." + "Specify both --binding-profile and --no-binding-profile " + "to overwrite the current binding:profile information.") ) parser.add_argument( 'port', @@ -452,7 +453,11 @@ def take_action(self, parsed_args): attrs = _get_attrs(self.app.client_manager, parsed_args) obj = client.find_port(parsed_args.port, ignore_missing=False) if 'binding:profile' in attrs: - attrs['binding:profile'].update(obj.binding_profile) + # Do not modify attrs if both binding_profile/no_binding given + if not parsed_args.no_binding_profile: + tmp_binding_profile = copy.deepcopy(obj.binding_profile) + tmp_binding_profile.update(attrs['binding:profile']) + attrs['binding:profile'] = tmp_binding_profile elif parsed_args.no_binding_profile: attrs['binding:profile'] = {} if 'fixed_ips' in attrs: @@ -461,7 +466,9 @@ def take_action(self, parsed_args): # would therefore add an empty dictionary, while we need # to append the attrs['fixed_ips'] iff there is some info # in the obj.fixed_ips. Therefore I have opted for this `for` loop - attrs['fixed_ips'] += [ip for ip in obj.fixed_ips if ip] + # Do not modify attrs if fixed_ip/no_fixed_ip given + if not parsed_args.no_fixed_ip: + attrs['fixed_ips'] += [ip for ip in obj.fixed_ips if ip] elif parsed_args.no_fixed_ip: attrs['fixed_ips'] = [] diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index d5d7f3309b..4f68deba2f 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -465,6 +465,50 @@ def test_append_fixed_ip(self): self.network.update_port.assert_called_once_with(_testport, **attrs) self.assertIsNone(result) + def test_overwrite_binding_profile(self): + _testport = network_fakes.FakePort.create_one_port( + {'binding_profile': {'lok_i': 'visi_on'}}) + self.network.find_port = mock.Mock(return_value=_testport) + arglist = [ + '--binding-profile', 'lok_i=than_os', + '--no-binding-profile', + _testport.name, + ] + verifylist = [ + ('binding_profile', {'lok_i': 'than_os'}), + ('no_binding_profile', True) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + attrs = { + 'binding:profile': + {'lok_i': 'than_os'}, + } + self.network.update_port.assert_called_once_with(_testport, **attrs) + self.assertIsNone(result) + + def test_overwrite_fixed_ip(self): + _testport = network_fakes.FakePort.create_one_port( + {'fixed_ips': [{'ip_address': '0.0.0.1'}]}) + self.network.find_port = mock.Mock(return_value=_testport) + arglist = [ + '--fixed-ip', 'ip-address=10.0.0.12', + '--no-fixed-ip', + _testport.name, + ] + verifylist = [ + ('fixed_ip', [{'ip-address': '10.0.0.12'}]), + ('no_fixed_ip', True) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + attrs = { + 'fixed_ips': [ + {'ip_address': '10.0.0.12'}], + } + self.network.update_port.assert_called_once_with(_testport, **attrs) + self.assertIsNone(result) + def test_set_this(self): arglist = [ '--disable', diff --git a/releasenotes/notes/add-overwrite-option-190a9c6904d53dab.yaml b/releasenotes/notes/add-overwrite-option-190a9c6904d53dab.yaml new file mode 100644 index 0000000000..fdd61b523d --- /dev/null +++ b/releasenotes/notes/add-overwrite-option-190a9c6904d53dab.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + ``port set`` command now allows the user to overwrite fixed-ips or binding-profile + of a port. + [ Blueprint `allow-overwrite-set-options ` _] \ No newline at end of file From 7f12b745ce509218f1ea38bb313d433688bcbf6f Mon Sep 17 00:00:00 2001 From: Reedip Date: Sat, 10 Sep 2016 16:59:19 +0530 Subject: [PATCH 1238/3095] Overwrite/Clear support for subnets This patch adds the overwrite/clear functionality for allocation-pool and host-routes in subnets. Change-Id: Idfa41173d0c054c5bfb4eda8c5f614928012555a implements: blueprint allow-overwrite-set-options --- doc/source/command-objects/subnet.rst | 15 ++++++++- openstackclient/network/v2/subnet.py | 30 ++++++++++++++--- .../tests/unit/network/v2/test_subnet.py | 33 +++++++++++++++++++ ...e-options-for-subnet-76476127dcf321ad.yaml | 6 ++++ 4 files changed, 79 insertions(+), 5 deletions(-) create mode 100644 releasenotes/notes/overwrite-options-for-subnet-76476127dcf321ad.yaml diff --git a/doc/source/command-objects/subnet.rst b/doc/source/command-objects/subnet.rst index 64130ee19e..00401ddab3 100644 --- a/doc/source/command-objects/subnet.rst +++ b/doc/source/command-objects/subnet.rst @@ -232,10 +232,12 @@ Set subnet properties os subnet set [--allocation-pool start=,end=] + [--no-allocation-pool] [--dhcp | --no-dhcp] [--dns-nameserver ] [--gateway ] [--host-route destination=,gateway=] + [--no-host-route] [--service-type ] [--name ] [--description ] @@ -247,6 +249,12 @@ Set subnet properties ``start=192.168.199.2,end=192.168.199.254`` (repeat option to add multiple IP addresses) +.. option:: --no-allocation-pool + + Clear associated allocation pools from this subnet. + Specify both --allocation-pool and --no-allocation-pool + to overwrite the current allocation pool information. + .. option:: --dhcp Enable DHCP @@ -272,7 +280,12 @@ Set subnet properties ``destination=10.10.0.0/16,gateway=192.168.71.254`` destination: destination subnet (in CIDR notation) gateway: nexthop IP address - (repeat option to add multiple routes) + +.. option:: --no-host-route + + Clear associated host routes from this subnet. + Specify both --host-route and --no-host-route + to overwrite the current host route information. .. option:: --service-type diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 7645348743..2021d9f03c 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -57,7 +57,7 @@ def _format_host_routes(data): } -def _get_common_parse_arguments(parser): +def _get_common_parse_arguments(parser, is_create=True): parser.add_argument( '--allocation-pool', metavar='start=,end=', @@ -68,6 +68,14 @@ def _get_common_parse_arguments(parser): "e.g.: start=192.168.199.2,end=192.168.199.254 " "(repeat option to add multiple IP addresses)") ) + if not is_create: + parser.add_argument( + '--no-allocation-pool', + action='store_true', + help=_("Clear associated allocation-pools from the subnet. " + "Specify both --allocation-pool and --no-allocation-pool " + "to overwrite the current allocation pool information.") + ) parser.add_argument( '--dns-nameserver', metavar='', @@ -88,6 +96,14 @@ def _get_common_parse_arguments(parser): "gateway: nexthop IP address " "(repeat option to add multiple routes)") ) + if not is_create: + parser.add_argument( + '--no-host-route', + action='store_true', + help=_("Clear associated host-routes from the subnet. " + "Specify both --host-route and --no-host-route " + "to overwrite the current host route information.") + ) parser.add_argument( '--service-type', metavar='', @@ -508,7 +524,7 @@ def get_parser(self, prog_name): metavar='', help=_("Set subnet description") ) - _get_common_parse_arguments(parser) + _get_common_parse_arguments(parser, is_create=False) return parser def take_action(self, parsed_args): @@ -519,9 +535,15 @@ def take_action(self, parsed_args): if 'dns_nameservers' in attrs: attrs['dns_nameservers'] += obj.dns_nameservers if 'host_routes' in attrs: - attrs['host_routes'] += obj.host_routes + if not parsed_args.no_host_route: + attrs['host_routes'] += obj.host_routes + elif parsed_args.no_host_route: + attrs['host_routes'] = '' if 'allocation_pools' in attrs: - attrs['allocation_pools'] += obj.allocation_pools + if not parsed_args.no_allocation_pool: + attrs['allocation_pools'] += obj.allocation_pools + elif parsed_args.no_allocation_pool: + attrs['allocation_pools'] = '' if 'service_types' in attrs: attrs['service_types'] += obj.service_types client.update_subnet(obj, **attrs) diff --git a/openstackclient/tests/unit/network/v2/test_subnet.py b/openstackclient/tests/unit/network/v2/test_subnet.py index 5850639184..9c468f3924 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet.py +++ b/openstackclient/tests/unit/network/v2/test_subnet.py @@ -938,6 +938,39 @@ def test_set_non_append_options(self): self.network.update_subnet.assert_called_with(self._subnet, **attrs) self.assertIsNone(result) + def test_overwrite_options(self): + _testsubnet = network_fakes.FakeSubnet.create_one_subnet( + {'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'}], }) + 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', + _testsubnet.name, + ] + verifylist = [ + ('host_routes', [{ + "destination": "10.30.30.30/24", "gateway": "10.30.30.1"}]), + ('allocation_pools', [{ + 'start': '8.8.8.100', 'end': '8.8.8.150'}]), + ('no_host_route', True), + ('no_allocation_pool', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + attrs = { + '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'}], + } + self.network.update_subnet.assert_called_once_with( + _testsubnet, **attrs) + self.assertIsNone(result) + class TestShowSubnet(TestSubnet): # The subnets to be shown diff --git a/releasenotes/notes/overwrite-options-for-subnet-76476127dcf321ad.yaml b/releasenotes/notes/overwrite-options-for-subnet-76476127dcf321ad.yaml new file mode 100644 index 0000000000..263cbdc923 --- /dev/null +++ b/releasenotes/notes/overwrite-options-for-subnet-76476127dcf321ad.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + ``subnet set`` command now allows the user to clear and overwrite + allocation-pool or host-route of a subnet. + [ Blueprint `allow-overwrite-set-options ` _] From b30a7b795f346e7ccc5575602550ca430bfb71a0 Mon Sep 17 00:00:00 2001 From: Shu Yingya Date: Wed, 21 Sep 2016 12:16:35 +0800 Subject: [PATCH 1239/3095] replace metavar "volume-id" with "volume" to avoid ambiguity The help message of command "openstack volume show" accepts either volume-name or volume-ID. But the metavar is "volume-id" as below. It can easily lead to misunderstanding. usage: openstack volume show [-h] [-f {json,shell,table,value,yaml}] [-c COLUMN] [--max-width ] [--noindent][--prefix PREFIX] Change-Id: I57576ea23868b1026cf268be69b39e98a53aafd4 --- openstackclient/volume/v2/volume.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index 5089aed1e6..0805b2be67 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -490,7 +490,7 @@ def get_parser(self, prog_name): parser = super(ShowVolume, self).get_parser(prog_name) parser.add_argument( 'volume', - metavar="", + metavar="", help=_("Volume to display (name or ID)") ) return parser From 4a8b802d6b1b4de4979260234b0fda4b18a01699 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Wed, 21 Sep 2016 14:26:30 +0800 Subject: [PATCH 1240/3095] Refactor volume unit test with FakeVolume class in volume v1 FakeVolume class has been added in the fake.py in volume v1, this patch refactors the volume command unit tests with the FakeVolume class. Change-Id: Ic3c1a46d5bff9048d0095f5739ae9e5a34ca6b5b --- openstackclient/tests/unit/volume/v1/fakes.py | 14 +- .../tests/unit/volume/v1/test_volume.py | 253 +++++++++--------- 2 files changed, 131 insertions(+), 136 deletions(-) diff --git a/openstackclient/tests/unit/volume/v1/fakes.py b/openstackclient/tests/unit/volume/v1/fakes.py index 2a4f62c520..3337449937 100644 --- a/openstackclient/tests/unit/volume/v1/fakes.py +++ b/openstackclient/tests/unit/volume/v1/fakes.py @@ -363,24 +363,24 @@ def create_one_volume(attrs=None): # Set default attribute volume_info = { 'id': 'volume-id' + uuid.uuid4().hex, - 'name': 'volume-name' + uuid.uuid4().hex, - 'description': 'description' + uuid.uuid4().hex, - 'status': random.choice(['available', 'in_use']), - 'size': random.randint(1, 20), + 'display_name': 'volume-name' + uuid.uuid4().hex, + 'display_description': 'description' + uuid.uuid4().hex, + 'status': 'available', + 'size': 10, 'volume_type': random.choice(['fake_lvmdriver-1', 'fake_lvmdriver-2']), - 'bootable': - random.randint(0, 1), + 'bootable': 'true', 'metadata': { 'key' + uuid.uuid4().hex: 'val' + uuid.uuid4().hex, 'key' + uuid.uuid4().hex: 'val' + uuid.uuid4().hex, 'key' + uuid.uuid4().hex: 'val' + uuid.uuid4().hex}, - 'snapshot_id': random.randint(1, 5), + 'snapshot_id': 'snapshot-id-' + uuid.uuid4().hex, 'availability_zone': 'zone' + uuid.uuid4().hex, 'attachments': [{ 'device': '/dev/' + uuid.uuid4().hex, 'server_id': uuid.uuid4().hex, }, ], + 'created_at': 'time-' + uuid.uuid4().hex, } # Overwrite default attributes if there are some attributes set diff --git a/openstackclient/tests/unit/volume/v1/test_volume.py b/openstackclient/tests/unit/volume/v1/test_volume.py index e95f42d037..6fe6394101 100644 --- a/openstackclient/tests/unit/volume/v1/test_volume.py +++ b/openstackclient/tests/unit/volume/v1/test_volume.py @@ -67,48 +67,50 @@ class TestVolumeCreate(TestVolume): user = identity_fakes.FakeUser.create_one_user() columns = ( - 'attach_status', + 'attachments', 'availability_zone', + 'bootable', + 'created_at', 'display_description', 'display_name', 'id', 'properties', 'size', + 'snapshot_id', 'status', 'type', ) - datalist = ( - 'detached', - volume_fakes.volume_zone, - volume_fakes.volume_description, - volume_fakes.volume_name, - volume_fakes.volume_id, - volume_fakes.volume_metadata_str, - volume_fakes.volume_size, - volume_fakes.volume_status, - volume_fakes.volume_type, - ) def setUp(self): super(TestVolumeCreate, self).setUp() - - self.volumes_mock.create.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.VOLUME), - loaded=True, + self.new_volume = volume_fakes.FakeVolume.create_one_volume() + self.datalist = ( + self.new_volume.attachments, + self.new_volume.availability_zone, + self.new_volume.bootable, + self.new_volume.created_at, + self.new_volume.display_description, + self.new_volume.display_name, + self.new_volume.id, + utils.format_dict(self.new_volume.metadata), + self.new_volume.size, + self.new_volume.snapshot_id, + self.new_volume.status, + self.new_volume.volume_type, ) + self.volumes_mock.create.return_value = self.new_volume # Get the command object to test self.cmd = volume.CreateVolume(self.app, None) def test_volume_create_min_options(self): arglist = [ - '--size', str(volume_fakes.volume_size), - volume_fakes.volume_name, + '--size', str(self.new_volume.size), + self.new_volume.display_name, ] verifylist = [ - ('size', volume_fakes.volume_size), - ('name', volume_fakes.volume_name), + ('size', self.new_volume.size), + ('name', self.new_volume.display_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -123,10 +125,10 @@ def test_volume_create_min_options(self): # project_id=, availability_zone=, # metadata=, imageRef=) self.volumes_mock.create.assert_called_with( - volume_fakes.volume_size, + self.new_volume.size, None, None, - volume_fakes.volume_name, + self.new_volume.display_name, None, None, None, @@ -140,18 +142,18 @@ def test_volume_create_min_options(self): def test_volume_create_options(self): arglist = [ - '--size', str(volume_fakes.volume_size), - '--description', volume_fakes.volume_description, - '--type', volume_fakes.volume_type, - '--availability-zone', volume_fakes.volume_zone, - volume_fakes.volume_name, + '--size', str(self.new_volume.size), + '--description', self.new_volume.display_description, + '--type', self.new_volume.volume_type, + '--availability-zone', self.new_volume.availability_zone, + self.new_volume.display_name, ] verifylist = [ - ('size', volume_fakes.volume_size), - ('description', volume_fakes.volume_description), - ('type', volume_fakes.volume_type), - ('availability_zone', volume_fakes.volume_zone), - ('name', volume_fakes.volume_name), + ('size', self.new_volume.size), + ('description', self.new_volume.display_description), + ('type', self.new_volume.volume_type), + ('availability_zone', self.new_volume.availability_zone), + ('name', self.new_volume.display_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -166,15 +168,15 @@ def test_volume_create_options(self): # project_id=, availability_zone=, # metadata=, imageRef=) self.volumes_mock.create.assert_called_with( - volume_fakes.volume_size, + self.new_volume.size, None, None, - volume_fakes.volume_name, - volume_fakes.volume_description, - volume_fakes.volume_type, + self.new_volume.display_name, + self.new_volume.display_description, + self.new_volume.volume_type, None, None, - volume_fakes.volume_zone, + self.new_volume.availability_zone, None, None, ) @@ -189,16 +191,16 @@ def test_volume_create_user_project_id(self): self.users_mock.get.return_value = self.user arglist = [ - '--size', str(volume_fakes.volume_size), + '--size', str(self.new_volume.size), '--project', self.project.id, '--user', self.user.id, - volume_fakes.volume_name, + self.new_volume.display_name, ] verifylist = [ - ('size', volume_fakes.volume_size), + ('size', self.new_volume.size), ('project', self.project.id), ('user', self.user.id), - ('name', volume_fakes.volume_name), + ('name', self.new_volume.display_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -213,10 +215,10 @@ def test_volume_create_user_project_id(self): # project_id=, availability_zone=, # metadata=, imageRef=) self.volumes_mock.create.assert_called_with( - volume_fakes.volume_size, + self.new_volume.size, None, None, - volume_fakes.volume_name, + self.new_volume.display_name, None, None, self.user.id, @@ -236,16 +238,16 @@ def test_volume_create_user_project_name(self): self.users_mock.get.return_value = self.user arglist = [ - '--size', str(volume_fakes.volume_size), + '--size', str(self.new_volume.size), '--project', self.project.name, '--user', self.user.name, - volume_fakes.volume_name, + self.new_volume.display_name, ] verifylist = [ - ('size', volume_fakes.volume_size), + ('size', self.new_volume.size), ('project', self.project.name), ('user', self.user.name), - ('name', volume_fakes.volume_name), + ('name', self.new_volume.display_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -260,10 +262,10 @@ def test_volume_create_user_project_name(self): # project_id=, availability_zone=, # metadata=, imageRef=) self.volumes_mock.create.assert_called_with( - volume_fakes.volume_size, + self.new_volume.size, None, None, - volume_fakes.volume_name, + self.new_volume.display_name, None, None, self.user.id, @@ -280,13 +282,13 @@ def test_volume_create_properties(self): arglist = [ '--property', 'Alpha=a', '--property', 'Beta=b', - '--size', str(volume_fakes.volume_size), - volume_fakes.volume_name, + '--size', str(self.new_volume.size), + self.new_volume.display_name, ] verifylist = [ ('property', {'Alpha': 'a', 'Beta': 'b'}), - ('size', volume_fakes.volume_size), - ('name', volume_fakes.volume_name), + ('size', self.new_volume.size), + ('name', self.new_volume.display_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -301,10 +303,10 @@ def test_volume_create_properties(self): # project_id=, availability_zone=, # metadata=, imageRef=) self.volumes_mock.create.assert_called_with( - volume_fakes.volume_size, + self.new_volume.size, None, None, - volume_fakes.volume_name, + self.new_volume.display_name, None, None, None, @@ -326,13 +328,13 @@ def test_volume_create_image_id(self): arglist = [ '--image', volume_fakes.image_id, - '--size', str(volume_fakes.volume_size), - volume_fakes.volume_name, + '--size', str(self.new_volume.size), + self.new_volume.display_name, ] verifylist = [ ('image', volume_fakes.image_id), - ('size', volume_fakes.volume_size), - ('name', volume_fakes.volume_name), + ('size', self.new_volume.size), + ('name', self.new_volume.display_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -347,10 +349,10 @@ def test_volume_create_image_id(self): # project_id=, availability_zone=, # metadata=, imageRef=) self.volumes_mock.create.assert_called_with( - volume_fakes.volume_size, + self.new_volume.size, None, None, - volume_fakes.volume_name, + self.new_volume.display_name, None, None, None, @@ -372,13 +374,13 @@ def test_volume_create_image_name(self): arglist = [ '--image', volume_fakes.image_name, - '--size', str(volume_fakes.volume_size), - volume_fakes.volume_name, + '--size', str(self.new_volume.size), + self.new_volume.display_name, ] verifylist = [ ('image', volume_fakes.image_name), - ('size', volume_fakes.volume_size), - ('name', volume_fakes.volume_name), + ('size', self.new_volume.size), + ('name', self.new_volume.display_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -393,10 +395,10 @@ def test_volume_create_image_name(self): # project_id=, availability_zone=, # metadata=, imageRef=) self.volumes_mock.create.assert_called_with( - volume_fakes.volume_size, + self.new_volume.size, None, None, - volume_fakes.volume_name, + self.new_volume.display_name, None, None, None, @@ -503,6 +505,7 @@ def test_volume_delete_with_force(self): class TestVolumeList(TestVolume): + _volume = volume_fakes.FakeVolume.create_one_volume() columns = ( 'ID', 'Display Name', @@ -510,26 +513,23 @@ class TestVolumeList(TestVolume): 'Size', 'Attached to', ) + server = _volume.attachments[0]['server_id'] + device = _volume.attachments[0]['device'] + msg = 'Attached to %s on %s ' % (server, device) datalist = ( ( - volume_fakes.volume_id, - volume_fakes.volume_name, - volume_fakes.volume_status, - volume_fakes.volume_size, - '', + _volume.id, + _volume.display_name, + _volume.status, + _volume.size, + msg, ), ) def setUp(self): super(TestVolumeList, self).setUp() - self.volumes_mock.list.return_value = [ - fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.VOLUME), - loaded=True, - ), - ] + self.volumes_mock.list.return_value = [self._volume] # Get the command object to test self.cmd = volume.ListVolume(self.app, None) @@ -552,12 +552,12 @@ def test_volume_list_no_options(self): def test_volume_list_name(self): arglist = [ - '--name', volume_fakes.volume_name, + '--name', self._volume.display_name, ] verifylist = [ ('long', False), ('all_projects', False), - ('name', volume_fakes.volume_name), + ('name', self._volume.display_name), ('status', None), ('limit', None), ] @@ -569,13 +569,13 @@ def test_volume_list_name(self): def test_volume_list_status(self): arglist = [ - '--status', volume_fakes.volume_status, + '--status', self._volume.status, ] verifylist = [ ('long', False), ('all_projects', False), ('name', None), - ('status', volume_fakes.volume_status), + ('status', self._volume.status), ('limit', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -630,14 +630,14 @@ def test_volume_list_long(self): self.assertEqual(collist, columns) datalist = (( - volume_fakes.volume_id, - volume_fakes.volume_name, - volume_fakes.volume_status, - volume_fakes.volume_size, - volume_fakes.volume_type, - '', - '', - "Alpha='a', Beta='b', Gamma='g'", + self._volume.id, + self._volume.display_name, + self._volume.status, + self._volume.size, + self._volume.volume_type, + self._volume.bootable, + self.msg, + utils.format_dict(self._volume.metadata), ), ) self.assertEqual(datalist, tuple(data)) @@ -679,33 +679,27 @@ def test_volume_list_negative_limit(self): class TestVolumeSet(TestVolume): + _volume = volume_fakes.FakeVolume.create_one_volume() + def setUp(self): super(TestVolumeSet, self).setUp() - self.volumes_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.VOLUME), - loaded=True, - ) + self.volumes_mock.get.return_value = self._volume - self.volumes_mock.update.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.VOLUME), - loaded=True, - ) + self.volumes_mock.update.return_value = self._volume # Get the command object to test self.cmd = volume.SetVolume(self.app, None) def test_volume_set_no_options(self): arglist = [ - volume_fakes.volume_name, + self._volume.display_name, ] verifylist = [ ('name', None), ('description', None), ('size', None), ('property', None), - ('volume', volume_fakes.volume_name), + ('volume', self._volume.display_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -715,14 +709,14 @@ def test_volume_set_no_options(self): def test_volume_set_name(self): arglist = [ '--name', 'qwerty', - volume_fakes.volume_name, + self._volume.display_name, ] verifylist = [ ('name', 'qwerty'), ('description', None), ('size', None), ('property', None), - ('volume', volume_fakes.volume_name), + ('volume', self._volume.display_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -733,7 +727,7 @@ def test_volume_set_name(self): 'display_name': 'qwerty', } self.volumes_mock.update.assert_called_with( - volume_fakes.volume_id, + self._volume.id, **kwargs ) self.assertIsNone(result) @@ -741,14 +735,14 @@ def test_volume_set_name(self): def test_volume_set_description(self): arglist = [ '--description', 'new desc', - volume_fakes.volume_name, + self._volume.display_name, ] verifylist = [ ('name', None), ('description', 'new desc'), ('size', None), ('property', None), - ('volume', volume_fakes.volume_name), + ('volume', self._volume.display_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -759,7 +753,7 @@ def test_volume_set_description(self): 'display_description': 'new desc', } self.volumes_mock.update.assert_called_with( - volume_fakes.volume_id, + self._volume.id, **kwargs ) self.assertIsNone(result) @@ -767,14 +761,14 @@ def test_volume_set_description(self): def test_volume_set_size(self): arglist = [ '--size', '130', - volume_fakes.volume_name, + self._volume.display_name, ] verifylist = [ ('name', None), ('description', None), ('size', 130), ('property', None), - ('volume', volume_fakes.volume_name), + ('volume', self._volume.display_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -783,23 +777,24 @@ def test_volume_set_size(self): # Set expected values size = 130 self.volumes_mock.extend.assert_called_with( - volume_fakes.volume_id, + self._volume.id, size ) self.assertIsNone(result) @mock.patch.object(volume.LOG, 'error') def test_volume_set_size_smaller(self, mock_log_error): + self._volume.status = 'available' arglist = [ - '--size', '100', - volume_fakes.volume_name, + '--size', '1', + self._volume.display_name, ] verifylist = [ ('name', None), ('description', None), - ('size', 100), + ('size', 1), ('property', None), - ('volume', volume_fakes.volume_name), + ('volume', self._volume.display_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -807,22 +802,22 @@ def test_volume_set_size_smaller(self, mock_log_error): mock_log_error.assert_called_with("New size must be greater " "than %s GB", - volume_fakes.volume_size) + self._volume.size) self.assertIsNone(result) @mock.patch.object(volume.LOG, 'error') def test_volume_set_size_not_available(self, mock_log_error): - self.volumes_mock.get.return_value.status = 'error' + self._volume.status = 'error' arglist = [ '--size', '130', - volume_fakes.volume_name, + self._volume.display_name, ] verifylist = [ ('name', None), ('description', None), ('size', 130), ('property', None), - ('volume', volume_fakes.volume_name), + ('volume', self._volume.display_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -836,14 +831,14 @@ def test_volume_set_size_not_available(self, mock_log_error): def test_volume_set_property(self): arglist = [ '--property', 'myprop=myvalue', - volume_fakes.volume_name, + self._volume.display_name, ] verifylist = [ ('name', None), ('description', None), ('size', None), ('property', {'myprop': 'myvalue'}), - ('volume', volume_fakes.volume_name), + ('volume', self._volume.display_name), ('bootable', False), ('non_bootable', False) ] @@ -856,26 +851,26 @@ def test_volume_set_property(self): 'myprop': 'myvalue' } self.volumes_mock.set_metadata.assert_called_with( - volume_fakes.volume_id, + self._volume.id, metadata ) self.assertIsNone(result) def test_volume_set_bootable(self): arglist = [ - ['--bootable', volume_fakes.volume_id], - ['--non-bootable', volume_fakes.volume_id] + ['--bootable', self._volume.id], + ['--non-bootable', self._volume.id] ] verifylist = [ [ ('bootable', True), ('non_bootable', False), - ('volume', volume_fakes.volume_id) + ('volume', self._volume.id) ], [ ('bootable', False), ('non_bootable', True), - ('volume', volume_fakes.volume_id) + ('volume', self._volume.id) ] ] for index in range(len(arglist)): @@ -884,4 +879,4 @@ def test_volume_set_bootable(self): self.cmd.take_action(parsed_args) self.volumes_mock.set_bootable.assert_called_with( - volume_fakes.volume_id, verifylist[index][0][1]) + self._volume.id, verifylist[index][0][1]) From 47b782687f4c23d03e872fb2b2b69b57ba3f9aec Mon Sep 17 00:00:00 2001 From: rabi Date: Wed, 21 Sep 2016 12:58:50 +0530 Subject: [PATCH 1241/3095] Use correct router add/remove interface methods Use the correct openstacksdk method names. Change-Id: I07b463753eedf41f79c6e00e9224d0f7697a1bd3 Closes-Bug: #1625954 --- openstackclient/network/v2/router.py | 8 ++++---- .../tests/unit/network/v2/test_router.py | 19 +++++++++---------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index d30197cc41..03134b8caa 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -119,7 +119,7 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): client = self.app.client_manager.network port = client.find_port(parsed_args.port, ignore_missing=False) - client.router_add_interface(client.find_router( + client.add_interface_to_router(client.find_router( parsed_args.router, ignore_missing=False), port_id=port.id) @@ -144,7 +144,7 @@ def take_action(self, parsed_args): client = self.app.client_manager.network subnet = client.find_subnet(parsed_args.subnet, ignore_missing=False) - client.router_add_interface( + client.add_interface_to_router( client.find_router(parsed_args.router, ignore_missing=False), subnet_id=subnet.id) @@ -324,7 +324,7 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): client = self.app.client_manager.network port = client.find_port(parsed_args.port, ignore_missing=False) - client.router_remove_interface(client.find_router( + client.remove_interface_from_router(client.find_router( parsed_args.router, ignore_missing=False), port_id=port.id) @@ -349,7 +349,7 @@ def take_action(self, parsed_args): client = self.app.client_manager.network subnet = client.find_subnet(parsed_args.subnet, ignore_missing=False) - client.router_remove_interface( + client.remove_interface_from_router( client.find_router(parsed_args.router, ignore_missing=False), subnet_id=subnet.id) diff --git a/openstackclient/tests/unit/network/v2/test_router.py b/openstackclient/tests/unit/network/v2/test_router.py index 6898a046bf..5ed969b415 100644 --- a/openstackclient/tests/unit/network/v2/test_router.py +++ b/openstackclient/tests/unit/network/v2/test_router.py @@ -40,7 +40,7 @@ class TestAddPortToRouter(TestRouter): def setUp(self): super(TestAddPortToRouter, self).setUp() - self.network.router_add_interface = mock.Mock() + self.network.add_interface_to_router = mock.Mock() self.cmd = router.AddPortToRouter(self.app, self.namespace) self.network.find_router = mock.Mock(return_value=self._router) self.network.find_port = mock.Mock(return_value=self._port) @@ -65,9 +65,8 @@ def test_add_port_required_options(self): result = self.cmd.take_action(parsed_args) - self.network.router_add_interface.assert_called_with(self._router, **{ - 'port_id': self._router.port, - }) + self.network.add_interface_to_router.assert_called_with( + self._router, **{'port_id': self._router.port, }) self.assertIsNone(result) @@ -80,7 +79,7 @@ class TestAddSubnetToRouter(TestRouter): def setUp(self): super(TestAddSubnetToRouter, self).setUp() - self.network.router_add_interface = mock.Mock() + self.network.add_interface_to_router = mock.Mock() self.cmd = router.AddSubnetToRouter(self.app, self.namespace) self.network.find_router = mock.Mock(return_value=self._router) self.network.find_subnet = mock.Mock(return_value=self._subnet) @@ -104,7 +103,7 @@ def test_add_subnet_required_options(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.network.router_add_interface.assert_called_with( + self.network.add_interface_to_router.assert_called_with( self._router, **{'subnet_id': self._router.subnet}) self.assertIsNone(result) @@ -411,7 +410,7 @@ class TestRemovePortFromRouter(TestRouter): def setUp(self): super(TestRemovePortFromRouter, self).setUp() - self.network.router_remove_interface = mock.Mock() + self.network.remove_interface_from_router = mock.Mock() self.cmd = router.RemovePortFromRouter(self.app, self.namespace) self.network.find_router = mock.Mock(return_value=self._router) self.network.find_port = mock.Mock(return_value=self._port) @@ -436,7 +435,7 @@ def test_remove_port_required_options(self): result = self.cmd.take_action(parsed_args) - self.network.router_remove_interface.assert_called_with( + self.network.remove_interface_from_router.assert_called_with( self._router, **{'port_id': self._router.port}) self.assertIsNone(result) @@ -450,7 +449,7 @@ class TestRemoveSubnetFromRouter(TestRouter): def setUp(self): super(TestRemoveSubnetFromRouter, self).setUp() - self.network.router_remove_interface = mock.Mock() + self.network.remove_interface_from_router = mock.Mock() self.cmd = router.RemoveSubnetFromRouter(self.app, self.namespace) self.network.find_router = mock.Mock(return_value=self._router) self.network.find_subnet = mock.Mock(return_value=self._subnet) @@ -474,7 +473,7 @@ def test_remove_subnet_required_options(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.network.router_remove_interface.assert_called_with( + self.network.remove_interface_from_router.assert_called_with( self._router, **{'subnet_id': self._router.subnet}) self.assertIsNone(result) From bba5c9047f81439a18e4e8be434f51abab83a24a Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Wed, 21 Sep 2016 15:08:14 +0800 Subject: [PATCH 1242/3095] Fix "volume unset" command pass normally when nothing specified When nothing specified in "volume unset" command, there will be an error message says that the "--properties" option is required, it is unusual behaviour, this patch fix it and also add unit test for it. Also, this patch add unit test for "volume show" command by the way. Change-Id: I5b5d587670acf0af4262b8521292455bf9f60fe5 Partial-bug: #1588588 --- .../tests/unit/volume/v1/test_volume.py | 99 +++++++++++++++++++ openstackclient/volume/v1/volume.py | 2 - 2 files changed, 99 insertions(+), 2 deletions(-) diff --git a/openstackclient/tests/unit/volume/v1/test_volume.py b/openstackclient/tests/unit/volume/v1/test_volume.py index 6fe6394101..895f1f876b 100644 --- a/openstackclient/tests/unit/volume/v1/test_volume.py +++ b/openstackclient/tests/unit/volume/v1/test_volume.py @@ -880,3 +880,102 @@ def test_volume_set_bootable(self): self.cmd.take_action(parsed_args) self.volumes_mock.set_bootable.assert_called_with( self._volume.id, verifylist[index][0][1]) + + +class TestVolumeShow(TestVolume): + + columns = ( + 'attachments', + 'availability_zone', + 'bootable', + 'created_at', + 'display_description', + 'display_name', + 'id', + 'properties', + 'size', + 'snapshot_id', + 'status', + 'type', + ) + + def setUp(self): + super(TestVolumeShow, self).setUp() + self._volume = volume_fakes.FakeVolume.create_one_volume() + self.datalist = ( + self._volume.attachments, + self._volume.availability_zone, + self._volume.bootable, + self._volume.created_at, + self._volume.display_description, + self._volume.display_name, + self._volume.id, + utils.format_dict(self._volume.metadata), + self._volume.size, + self._volume.snapshot_id, + self._volume.status, + self._volume.volume_type, + ) + self.volumes_mock.get.return_value = self._volume + # Get the command object to test + self.cmd = volume.ShowVolume(self.app, None) + + def test_volume_show(self): + arglist = [ + self._volume.id + ] + verifylist = [ + ("volume", self._volume.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.volumes_mock.get.assert_called_with(self._volume.id) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) + + +class TestVolumeUnset(TestVolume): + + _volume = volume_fakes.FakeVolume.create_one_volume() + + def setUp(self): + super(TestVolumeUnset, self).setUp() + + self.volumes_mock.get.return_value = self._volume + + self.volumes_mock.delete_metadata.return_value = None + # Get the command object to test + self.cmd = volume.UnsetVolume(self.app, None) + + def test_volume_unset_no_options(self): + arglist = [ + self._volume.display_name, + ] + verifylist = [ + ('property', None), + ('volume', self._volume.display_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.assertIsNone(result) + + def test_volume_unset_property(self): + arglist = [ + '--property', 'myprop', + self._volume.display_name, + ] + verifylist = [ + ('property', ['myprop']), + ('volume', self._volume.display_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.volumes_mock.delete_metadata.assert_called_with( + self._volume.id, ['myprop'] + ) + self.assertIsNone(result) diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index 69bf380348..89fa2014ae 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -452,10 +452,8 @@ def get_parser(self, prog_name): '--property', metavar='', action='append', - default=[], help=_('Remove a property from volume ' '(repeat option to remove multiple properties)'), - required=True, ) return parser From 274e400f5a033da073a8a790eb4895fe7faced86 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 21 Sep 2016 11:56:44 +0000 Subject: [PATCH 1243/3095] Updated from global requirements Change-Id: I6f4e6002d66342afda1821902625b90476d193b4 --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 5d7636d0cf..069a737408 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,11 +7,11 @@ six>=1.9.0 # MIT Babel>=2.3.4 # BSD cliff!=1.16.0,!=1.17.0,>=1.15.0 # Apache-2.0 keystoneauth1>=2.10.0 # Apache-2.0 -openstacksdk>=0.9.5 # Apache-2.0 +openstacksdk>=0.9.6 # Apache-2.0 osc-lib>=1.0.2 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 oslo.utils>=3.16.0 # Apache-2.0 -python-glanceclient!=2.4.0,>=2.3.0 # Apache-2.0 +python-glanceclient>=2.5.0 # Apache-2.0 python-keystoneclient!=2.1.0,>=2.0.0 # Apache-2.0 python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0 python-cinderclient!=1.7.0,!=1.7.1,>=1.6.0 # Apache-2.0 From 9c473f475d2bb7adadc65a7525b93db84f10bef9 Mon Sep 17 00:00:00 2001 From: Ha Van Tu Date: Thu, 18 Aug 2016 15:07:47 +0700 Subject: [PATCH 1244/3095] Add filtering options to os network list command This patch adds the following filtering options: '--name', '--internal', '--share' and '--no-share', '--enable' and '--disable', '--project' and '--project-domain', '--status'. Change-Id: I7e9dd372ee572c6ee8cdba7fac3182f9dc0a137b Partially-Implements: blueprint network-commands-options Closes-Bug: #1578819 --- doc/source/command-objects/network.rst | 45 ++++- openstackclient/network/v2/network.py | 83 ++++++++- .../tests/unit/network/v2/test_network.py | 162 ++++++++++++++++++ .../notes/bug-1578819-d1efccfefb18356d.yaml | 7 + 4 files changed, 290 insertions(+), 7 deletions(-) create mode 100644 releasenotes/notes/bug-1578819-d1efccfefb18356d.yaml diff --git a/doc/source/command-objects/network.rst b/doc/source/command-objects/network.rst index 5d9a5ca860..8cc250d542 100644 --- a/doc/source/command-objects/network.rst +++ b/doc/source/command-objects/network.rst @@ -181,17 +181,60 @@ List networks .. code:: bash os network list - [--external] + [--external | --internal] [--long] + [--name ] + [--enable | --disable] + [--project [--project-domain ]] + [--share | --no-share] + [--status ] .. option:: --external List external networks +.. option:: --internal + + List internal networks + .. option:: --long List additional fields in output +.. option:: --name + + List networks according to their name + +.. option:: --enable + + List enabled networks + +.. option:: --disable + + List disabled networks + +.. option:: --project + + List networks according to their project (name or ID) + +.. option:: --project-domain + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + +.. option:: --share + + List networks shared between projects + +.. option:: --no-share + + List networks not shared between projects + +.. option:: --status + + List networks according to their status + ('ACTIVE', 'BUILD', 'DOWN', 'ERROR') + network set ----------- diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index ccc02fd8e6..6529f8699b 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -295,21 +295,66 @@ class ListNetwork(common.NetworkAndComputeLister): """List networks""" def update_parser_common(self, parser): - parser.add_argument( + router_ext_group = parser.add_mutually_exclusive_group() + router_ext_group.add_argument( '--external', action='store_true', - default=False, help=_("List external networks") ) + router_ext_group.add_argument( + '--internal', + action='store_true', + help=_("List internal networks") + ) parser.add_argument( '--long', action='store_true', - default=False, help=_("List additional fields in output") ) + parser.add_argument( + '--name', + metavar='', + help=_("List networks according to their name") + ) + admin_state_group = parser.add_mutually_exclusive_group() + admin_state_group.add_argument( + '--enable', + action='store_true', + help=_("List enabled networks") + ) + admin_state_group.add_argument( + '--disable', + action='store_true', + help=_("List disabled networks") + ) + parser.add_argument( + '--project', + metavar='', + help=_("List networks according to their project (name or ID)") + ) + identity_common.add_project_domain_option_to_parser(parser) + shared_group = parser.add_mutually_exclusive_group() + shared_group.add_argument( + '--share', + action='store_true', + help=_("List networks shared between projects") + ) + shared_group.add_argument( + '--no-share', + action='store_true', + help=_("List networks not shared between projects") + ) + parser.add_argument( + '--status', + metavar='', + choices=['ACTIVE', 'BUILD', 'DOWN', 'ERROR'], + help=_("List networks according to their status " + "('ACTIVE', 'BUILD', 'DOWN', 'ERROR')") + ) return parser def take_action_network(self, client, parsed_args): + identity_client = self.app.client_manager.identity if parsed_args.long: columns = ( 'id', @@ -347,10 +392,36 @@ def take_action_network(self, client, parsed_args): 'Subnets', ) + args = {} + if parsed_args.external: - args = {'router:external': True} - else: - args = {} + args['router:external'] = True + elif parsed_args.internal: + args['router:external'] = False + + if parsed_args.name is not None: + args['name'] = parsed_args.name + + if parsed_args.enable: + args['admin_state_up'] = True + elif parsed_args.disable: + args['admin_state_up'] = False + + if parsed_args.project: + project = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ) + args['tenant_id'] = project.id + + if parsed_args.share: + args['shared'] = True + elif parsed_args.no_share: + args['shared'] = False + + if parsed_args.status: + args['status'] = parsed_args.status data = client.networks(**args) diff --git a/openstackclient/tests/unit/network/v2/test_network.py b/openstackclient/tests/unit/network/v2/test_network.py index 84ead0938b..9a37564567 100644 --- a/openstackclient/tests/unit/network/v2/test_network.py +++ b/openstackclient/tests/unit/network/v2/test_network.py @@ -13,6 +13,7 @@ import mock from mock import call +import random from osc_lib import exceptions from osc_lib import utils @@ -491,6 +492,23 @@ def test_list_external(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + def test_list_internal(self): + arglist = [ + '--internal', + ] + verifylist = [ + ('internal', True), + ('long', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.networks.assert_called_once_with( + **{'router:external': False} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + def test_network_list_long(self): arglist = [ '--long', @@ -510,6 +528,150 @@ def test_network_list_long(self): self.assertEqual(self.columns_long, columns) self.assertEqual(self.data_long, list(data)) + def test_list_name(self): + test_name = "fakename" + arglist = [ + '--name', test_name, + ] + verifylist = [ + ('external', False), + ('long', False), + ('name', test_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.networks.assert_called_once_with( + **{'name': test_name} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_network_list_enable(self): + arglist = [ + '--enable', + ] + verifylist = [ + ('long', False), + ('external', False), + ('enable', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.networks.assert_called_once_with( + **{'admin_state_up': True} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_network_list_disable(self): + arglist = [ + '--disable', + ] + verifylist = [ + ('long', False), + ('external', False), + ('disable', True) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.networks.assert_called_once_with( + **{'admin_state_up': False} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_network_list_project(self): + project = identity_fakes_v3.FakeProject.create_one_project() + self.projects_mock.get.return_value = project + arglist = [ + '--project', project.id, + ] + verifylist = [ + ('project', project.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.network.networks.assert_called_once_with( + **{'tenant_id': project.id} + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_networ_list_project_domain(self): + project = identity_fakes_v3.FakeProject.create_one_project() + self.projects_mock.get.return_value = project + arglist = [ + '--project', project.id, + '--project-domain', project.domain_id, + ] + verifylist = [ + ('project', project.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'tenant_id': project.id} + + self.network.networks.assert_called_once_with(**filters) + + def test_network_list_share(self): + arglist = [ + '--share', + ] + verifylist = [ + ('long', False), + ('share', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.networks.assert_called_once_with( + **{'shared': True} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_network_list_no_share(self): + arglist = [ + '--no-share', + ] + verifylist = [ + ('long', False), + ('no_share', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.networks.assert_called_once_with( + **{'shared': False} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_network_list_status(self): + choices = ['ACTIVE', 'BUILD', 'DOWN', 'ERROR'] + test_status = random.choice(choices) + arglist = [ + '--status', test_status, + ] + verifylist = [ + ('long', False), + ('status', test_status), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.networks.assert_called_once_with( + **{'status': test_status} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + class TestSetNetwork(TestNetwork): diff --git a/releasenotes/notes/bug-1578819-d1efccfefb18356d.yaml b/releasenotes/notes/bug-1578819-d1efccfefb18356d.yaml new file mode 100644 index 0000000000..47c870d660 --- /dev/null +++ b/releasenotes/notes/bug-1578819-d1efccfefb18356d.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Add ``--internal``, ``--name``, ``--project`` and ``--project-domain``, + ``--enable`` and ``--disable``, ``--share`` and ``--no share``, ``--status`` + options to the ``network list`` command. + [Bug `1578819 `_] From ddf84429f297b34ce7067250d834ea897e37f37c Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Tue, 16 Aug 2016 10:52:26 +0800 Subject: [PATCH 1245/3095] Add "volume backup set" command in volume v2 Add "volume backup set" command in volume v2 (v2 only) to set backup name, description and state Change-Id: If17e8457db9a4704fb5bb9c75921ed82fd0069cf Closes-Bug: #1613261 --- doc/source/command-objects/volume-backup.rst | 31 +++++++++ .../tests/unit/volume/v2/test_backup.py | 69 +++++++++++++++++++ openstackclient/volume/v2/backup.py | 59 ++++++++++++++++ .../notes/bug-1613261-290a64080fead6c0.yaml | 4 ++ setup.cfg | 1 + 5 files changed, 164 insertions(+) create mode 100644 releasenotes/notes/bug-1613261-290a64080fead6c0.yaml diff --git a/doc/source/command-objects/volume-backup.rst b/doc/source/command-objects/volume-backup.rst index 3a8f0213df..05b02345ce 100644 --- a/doc/source/command-objects/volume-backup.rst +++ b/doc/source/command-objects/volume-backup.rst @@ -115,6 +115,37 @@ Restore volume backup Volume to restore to (name or ID) +volume backup set +----------------- + +Set volume backup properties + +.. program:: volume backup set +.. code:: bash + + os volume backup set + [--name ] + [--description ] + [--state ] + + +.. option:: --name + + New backup name + +.. option:: --description + + New backup description + +.. option:: --state + + New backup state ("available" or "error") (admin only) + +.. _backup_set-volume-backup: +.. describe:: + + Backup to modify (name or ID) + volume backup show ------------------ diff --git a/openstackclient/tests/unit/volume/v2/test_backup.py b/openstackclient/tests/unit/volume/v2/test_backup.py index 4563387016..306c9eb3e8 100644 --- a/openstackclient/tests/unit/volume/v2/test_backup.py +++ b/openstackclient/tests/unit/volume/v2/test_backup.py @@ -336,6 +336,75 @@ def test_backup_restore(self): self.assertIsNone(result) +class TestBackupSet(TestBackup): + + backup = volume_fakes.FakeBackup.create_one_backup() + + def setUp(self): + super(TestBackupSet, self).setUp() + + self.backups_mock.get.return_value = self.backup + + # Get the command object to test + self.cmd = backup.SetVolumeBackup(self.app, None) + + def test_backup_set_name(self): + arglist = [ + '--name', 'new_name', + self.backup.id, + ] + verifylist = [ + ('name', 'new_name'), + ('backup', self.backup.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class ShowOne in cliff, abstract method take_action() + # returns nothing + result = self.cmd.take_action(parsed_args) + self.backups_mock.update.assert_called_once_with( + self.backup.id, **{'name': 'new_name'}) + self.assertIsNone(result) + + def test_backup_set_state(self): + arglist = [ + '--state', 'error', + self.backup.id + ] + verifylist = [ + ('state', 'error'), + ('backup', self.backup.id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.backups_mock.reset_state.assert_called_once_with( + self.backup.id, 'error') + self.assertIsNone(result) + + def test_backup_set_state_failed(self): + self.backups_mock.reset_state.side_effect = exceptions.CommandError() + arglist = [ + '--state', 'error', + self.backup.id + ] + verifylist = [ + ('state', 'error'), + ('backup', self.backup.id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('One or more of the set operations failed', + str(e)) + self.backups_mock.reset_state.assert_called_with( + self.backup.id, 'error') + + class TestBackupShow(TestBackup): backup = volume_fakes.FakeBackup.create_one_backup() diff --git a/openstackclient/volume/v2/backup.py b/openstackclient/volume/v2/backup.py index 07c1c94f75..4a133d3644 100644 --- a/openstackclient/volume/v2/backup.py +++ b/openstackclient/volume/v2/backup.py @@ -281,6 +281,65 @@ def take_action(self, parsed_args): return super(RestoreBackup, self).take_action(parsed_args) +class SetVolumeBackup(command.Command): + """Set volume backup properties""" + + def get_parser(self, prog_name): + parser = super(SetVolumeBackup, self).get_parser(prog_name) + parser.add_argument( + "backup", + metavar="", + help=_("Backup to modify (name or ID)") + ) + parser.add_argument( + '--name', + metavar='', + help=_('New backup name') + ) + parser.add_argument( + '--description', + metavar='', + help=_('New backup description') + ) + parser.add_argument( + '--state', + metavar='', + choices=['available', 'error'], + help=_('New backup state ("available" or "error") (admin only)'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + backup = utils.find_resource(volume_client.backups, + parsed_args.backup) + result = 0 + if parsed_args.state: + try: + volume_client.backups.reset_state( + backup.id, parsed_args.state) + except Exception as e: + LOG.error(_("Failed to set backup state: %s"), e) + result += 1 + + kwargs = {} + if parsed_args.name: + kwargs['name'] = parsed_args.name + if parsed_args.description: + kwargs['description'] = parsed_args.description + if kwargs: + try: + volume_client.backups.update(backup.id, **kwargs) + except Exception as e: + LOG.error(_("Failed to update backup name " + "or description: %s"), e) + result += 1 + + if result > 0: + raise exceptions.CommandError(_("One or more of the " + "set operations failed")) + + class ShowVolumeBackup(command.ShowOne): """Display volume backup details""" diff --git a/releasenotes/notes/bug-1613261-290a64080fead6c0.yaml b/releasenotes/notes/bug-1613261-290a64080fead6c0.yaml new file mode 100644 index 0000000000..7d239be35c --- /dev/null +++ b/releasenotes/notes/bug-1613261-290a64080fead6c0.yaml @@ -0,0 +1,4 @@ +--- +features: + - Add ``volume backup set`` commands in volume v2. + [Bug `1613261 `_] diff --git a/setup.cfg b/setup.cfg index 3e6486a23d..166cd694c8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -512,6 +512,7 @@ openstack.volume.v2 = volume_backup_delete = openstackclient.volume.v2.backup:DeleteVolumeBackup volume_backup_list = openstackclient.volume.v2.backup:ListVolumeBackup volume_backup_restore = openstackclient.volume.v2.backup:RestoreVolumeBackup + volume_backup_set = openstackclient.volume.v2.backup:SetVolumeBackup volume_backup_show = openstackclient.volume.v2.backup:ShowVolumeBackup volume_type_create = openstackclient.volume.v2.volume_type:CreateVolumeType From 1d5297ca074a7615474eda5c682835befc662243 Mon Sep 17 00:00:00 2001 From: qtang Date: Thu, 22 Sep 2016 16:13:25 +0800 Subject: [PATCH 1246/3095] Align '=' for image.rst Change-Id: Iae074a9b6f5ef91738d35e41602c234aab5bed56 --- doc/source/command-objects/image.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/command-objects/image.rst b/doc/source/command-objects/image.rst index d6451af76e..842eab8d8a 100644 --- a/doc/source/command-objects/image.rst +++ b/doc/source/command-objects/image.rst @@ -1,6 +1,6 @@ -====== +===== image -====== +===== Image v1, v2 From 3ef7e29dd01a848ad08ce1b66deb9c5c3b1a4b1e Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Mon, 29 Aug 2016 19:55:28 +0800 Subject: [PATCH 1247/3095] Implement "volume transfer request show/accept" command Add "volume transfer request show" and "volume transfer accept" commands in volume v1 and v2. Also add the unit tests, docs, release note and functional tests Implements: bp cinder-command-support Co-Authored-By: Sheel Rana Change-Id: I5787fc486b3401307125caa316f517b9c96a95a5 --- .../volume-transfer-request.rst | 37 +++++++ .../volume/v1/test_transfer_request.py | 38 +++++++ .../volume/v2/test_transfer_request.py | 38 +++++++ openstackclient/tests/unit/volume/v1/fakes.py | 2 - .../unit/volume/v1/test_transfer_request.py | 98 ++++++++++++++++++- openstackclient/tests/unit/volume/v2/fakes.py | 2 - .../unit/volume/v2/test_transfer_request.py | 98 ++++++++++++++++++- .../volume/v1/volume_transfer_request.py | 49 ++++++++++ .../volume/v2/volume_transfer_request.py | 49 ++++++++++ ...nder-command-support-cc8708c4395ce467.yaml | 3 +- setup.cfg | 4 + 11 files changed, 411 insertions(+), 7 deletions(-) diff --git a/doc/source/command-objects/volume-transfer-request.rst b/doc/source/command-objects/volume-transfer-request.rst index 4729999574..b89289a81e 100644 --- a/doc/source/command-objects/volume-transfer-request.rst +++ b/doc/source/command-objects/volume-transfer-request.rst @@ -4,6 +4,27 @@ volume transfer request Block Storage v1, v2 +volume transfer request accept +------------------------------ + +Accept volume transfer request + +.. program:: volume transfer request accept +.. code:: bash + + os volume transfer request accept + + + +.. _volume_transfer_request_accept: +.. describe:: + + Volume transfer request to accept (name or ID) + +.. describe:: + + Authentication key of transfer request + volume transfer request create ------------------------------ @@ -56,3 +77,19 @@ Lists all volume transfer requests. Shows detail for all projects. Admin only. (defaults to False) + +volume transfer request show +---------------------------- + +Show volume transfer request details + +.. program:: volume transfer request show +.. code:: bash + + os volume transfer request show + + +.. _volume_transfer_request_show-transfer-request: +.. describe:: + + Volume transfer request to display (name or ID) diff --git a/openstackclient/tests/functional/volume/v1/test_transfer_request.py b/openstackclient/tests/functional/volume/v1/test_transfer_request.py index d8406b0295..e03cd717cd 100644 --- a/openstackclient/tests/functional/volume/v1/test_transfer_request.py +++ b/openstackclient/tests/functional/volume/v1/test_transfer_request.py @@ -47,7 +47,45 @@ def tearDownClass(cls): cls.assertOutput('', raw_output_transfer) cls.assertOutput('', raw_output_volume) + def test_volume_transfer_request_accept(self): + volume_name = uuid.uuid4().hex + name = uuid.uuid4().hex + + # create a volume + opts = self.get_opts(['display_name']) + raw_output = self.openstack( + 'volume create --size 1 ' + volume_name + opts) + self.assertEqual(volume_name + '\n', raw_output) + + # create volume transfer request for the volume + # and get the auth_key of the new transfer request + opts = self.get_opts(['auth_key']) + auth_key = self.openstack( + 'volume transfer request create ' + + volume_name + + ' --name ' + name + opts) + self.assertNotEqual('', auth_key) + + # accept the volume transfer request + opts = self.get_opts(self.FIELDS) + raw_output = self.openstack( + 'volume transfer request accept ' + name + + ' ' + auth_key + opts) + self.assertEqual(name + '\n', raw_output) + + # the volume transfer will be removed by default after accepted + # so just need to delete the volume here + raw_output = self.openstack( + 'volume delete ' + volume_name) + self.assertEqual('', raw_output) + def test_volume_transfer_request_list(self): opts = self.get_opts(self.HEADERS) raw_output = self.openstack('volume transfer request list' + opts) self.assertIn(self.NAME, raw_output) + + def test_volume_transfer_request_show(self): + opts = self.get_opts(self.FIELDS) + raw_output = self.openstack( + 'volume transfer request show ' + self.NAME + opts) + self.assertEqual(self.NAME + '\n', raw_output) diff --git a/openstackclient/tests/functional/volume/v2/test_transfer_request.py b/openstackclient/tests/functional/volume/v2/test_transfer_request.py index 4f02238bc9..1791f8ac1f 100644 --- a/openstackclient/tests/functional/volume/v2/test_transfer_request.py +++ b/openstackclient/tests/functional/volume/v2/test_transfer_request.py @@ -47,7 +47,45 @@ def tearDownClass(cls): cls.assertOutput('', raw_output_transfer) cls.assertOutput('', raw_output_volume) + def test_volume_transfer_request_accept(self): + volume_name = uuid.uuid4().hex + name = uuid.uuid4().hex + + # create a volume + opts = self.get_opts(self.FIELDS) + raw_output = self.openstack( + 'volume create --size 1 ' + volume_name + opts) + self.assertEqual(volume_name + '\n', raw_output) + + # create volume transfer request for the volume + # and get the auth_key of the new transfer request + opts = self.get_opts(['auth_key']) + auth_key = self.openstack( + 'volume transfer request create ' + + volume_name + + ' --name ' + name + opts) + self.assertNotEqual('', auth_key) + + # accept the volume transfer request + opts = self.get_opts(self.FIELDS) + raw_output = self.openstack( + 'volume transfer request accept ' + name + + ' ' + auth_key + opts) + self.assertEqual(name + '\n', raw_output) + + # the volume transfer will be removed by default after accepted + # so just need to delete the volume here + raw_output = self.openstack( + 'volume delete ' + volume_name) + self.assertEqual('', raw_output) + def test_volume_transfer_request_list(self): opts = self.get_opts(self.HEADERS) raw_output = self.openstack('volume transfer request list' + opts) self.assertIn(self.NAME, raw_output) + + def test_volume_transfer_request_show(self): + opts = self.get_opts(self.FIELDS) + raw_output = self.openstack( + 'volume transfer request show ' + self.NAME + opts) + self.assertEqual(self.NAME + '\n', raw_output) diff --git a/openstackclient/tests/unit/volume/v1/fakes.py b/openstackclient/tests/unit/volume/v1/fakes.py index 3337449937..ef52e4b008 100644 --- a/openstackclient/tests/unit/volume/v1/fakes.py +++ b/openstackclient/tests/unit/volume/v1/fakes.py @@ -146,8 +146,6 @@ def create_one_transfer(attrs=None): """ # Set default attribute transfer_info = { - 'auth_key': 'key-' + uuid.uuid4().hex, - 'created_at': 'time-' + uuid.uuid4().hex, 'volume_id': 'volume-id-' + uuid.uuid4().hex, 'name': 'fake_transfer_name', 'id': 'id-' + uuid.uuid4().hex, diff --git a/openstackclient/tests/unit/volume/v1/test_transfer_request.py b/openstackclient/tests/unit/volume/v1/test_transfer_request.py index e89c505647..b3788d6ec8 100644 --- a/openstackclient/tests/unit/volume/v1/test_transfer_request.py +++ b/openstackclient/tests/unit/volume/v1/test_transfer_request.py @@ -36,6 +36,53 @@ def setUp(self): self.volumes_mock.reset_mock() +class TestTransferAccept(TestTransfer): + + columns = ( + 'id', + 'name', + 'volume_id', + ) + + def setUp(self): + super(TestTransferAccept, self).setUp() + + self.volume_transfer = ( + transfer_fakes.FakeTransfer.create_one_transfer()) + self.data = ( + self.volume_transfer.id, + self.volume_transfer.name, + self.volume_transfer.volume_id, + ) + + self.transfer_mock.get.return_value = self.volume_transfer + self.transfer_mock.accept.return_value = self.volume_transfer + + # Get the command object to test + self.cmd = volume_transfer_request.AcceptTransferRequest( + self.app, None) + + def test_transfer_accept(self): + arglist = [ + self.volume_transfer.id, + 'auth_key', + ] + verifylist = [ + ('transfer_request', self.volume_transfer.id), + ('auth_key', 'auth_key'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.transfer_mock.get.assert_called_once_with( + self.volume_transfer.id) + self.transfer_mock.accept.assert_called_once_with( + self.volume_transfer.id, 'auth_key') + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + class TestTransferCreate(TestTransfer): volume = transfer_fakes.FakeVolume.create_one_volume() @@ -52,7 +99,10 @@ def setUp(self): super(TestTransferCreate, self).setUp() self.volume_transfer = transfer_fakes.FakeTransfer.create_one_transfer( - attrs={'volume_id': self.volume.id}) + attrs={'volume_id': self.volume.id, + 'auth_key': 'key', + 'created_at': 'time'} + ) self.data = ( self.volume_transfer.auth_key, self.volume_transfer.created_at, @@ -266,3 +316,49 @@ def test_transfer_list_with_argument(self): detailed=True, search_opts={'all_tenants': 1} ) + + +class TestTransferShow(TestTransfer): + + columns = ( + 'created_at', + 'id', + 'name', + 'volume_id', + ) + + def setUp(self): + super(TestTransferShow, self).setUp() + + self.volume_transfer = ( + transfer_fakes.FakeTransfer.create_one_transfer( + attrs={'created_at': 'time'}) + ) + self.data = ( + self.volume_transfer.created_at, + self.volume_transfer.id, + self.volume_transfer.name, + self.volume_transfer.volume_id, + ) + + self.transfer_mock.get.return_value = self.volume_transfer + + # Get the command object to test + self.cmd = volume_transfer_request.ShowTransferRequest( + self.app, None) + + def test_transfer_show(self): + arglist = [ + self.volume_transfer.id, + ] + verifylist = [ + ('transfer_request', self.volume_transfer.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.transfer_mock.get.assert_called_once_with( + self.volume_transfer.id) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) diff --git a/openstackclient/tests/unit/volume/v2/fakes.py b/openstackclient/tests/unit/volume/v2/fakes.py index 8d7ac83139..2aeea60ab5 100644 --- a/openstackclient/tests/unit/volume/v2/fakes.py +++ b/openstackclient/tests/unit/volume/v2/fakes.py @@ -52,8 +52,6 @@ def create_one_transfer(attrs=None): """ # Set default attribute transfer_info = { - 'auth_key': 'key-' + uuid.uuid4().hex, - 'created_at': 'time-' + uuid.uuid4().hex, 'volume_id': 'volume-id-' + uuid.uuid4().hex, 'name': 'fake_transfer_name', 'id': 'id-' + uuid.uuid4().hex, diff --git a/openstackclient/tests/unit/volume/v2/test_transfer_request.py b/openstackclient/tests/unit/volume/v2/test_transfer_request.py index b4f890896c..8cd6534ba7 100644 --- a/openstackclient/tests/unit/volume/v2/test_transfer_request.py +++ b/openstackclient/tests/unit/volume/v2/test_transfer_request.py @@ -36,6 +36,53 @@ def setUp(self): self.volumes_mock.reset_mock() +class TestTransferAccept(TestTransfer): + + columns = ( + 'id', + 'name', + 'volume_id', + ) + + def setUp(self): + super(TestTransferAccept, self).setUp() + + self.volume_transfer = ( + transfer_fakes.FakeTransfer.create_one_transfer()) + self.data = ( + self.volume_transfer.id, + self.volume_transfer.name, + self.volume_transfer.volume_id, + ) + + self.transfer_mock.get.return_value = self.volume_transfer + self.transfer_mock.accept.return_value = self.volume_transfer + + # Get the command object to test + self.cmd = volume_transfer_request.AcceptTransferRequest( + self.app, None) + + def test_transfer_accept(self): + arglist = [ + self.volume_transfer.id, + 'auth_key', + ] + verifylist = [ + ('transfer_request', self.volume_transfer.id), + ('auth_key', 'auth_key'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.transfer_mock.get.assert_called_once_with( + self.volume_transfer.id) + self.transfer_mock.accept.assert_called_once_with( + self.volume_transfer.id, 'auth_key') + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + class TestTransferCreate(TestTransfer): volume = transfer_fakes.FakeVolume.create_one_volume() @@ -52,7 +99,10 @@ def setUp(self): super(TestTransferCreate, self).setUp() self.volume_transfer = transfer_fakes.FakeTransfer.create_one_transfer( - attrs={'volume_id': self.volume.id}) + attrs={'volume_id': self.volume.id, + 'auth_key': 'key', + 'created_at': 'time'} + ) self.data = ( self.volume_transfer.auth_key, self.volume_transfer.created_at, @@ -266,3 +316,49 @@ def test_transfer_list_with_argument(self): detailed=True, search_opts={'all_tenants': 1} ) + + +class TestTransferShow(TestTransfer): + + columns = ( + 'created_at', + 'id', + 'name', + 'volume_id', + ) + + def setUp(self): + super(TestTransferShow, self).setUp() + + self.volume_transfer = ( + transfer_fakes.FakeTransfer.create_one_transfer( + attrs={'created_at': 'time'}) + ) + self.data = ( + self.volume_transfer.created_at, + self.volume_transfer.id, + self.volume_transfer.name, + self.volume_transfer.volume_id, + ) + + self.transfer_mock.get.return_value = self.volume_transfer + + # Get the command object to test + self.cmd = volume_transfer_request.ShowTransferRequest( + self.app, None) + + def test_transfer_show(self): + arglist = [ + self.volume_transfer.id, + ] + verifylist = [ + ('transfer_request', self.volume_transfer.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.transfer_mock.get.assert_called_once_with( + self.volume_transfer.id) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) diff --git a/openstackclient/volume/v1/volume_transfer_request.py b/openstackclient/volume/v1/volume_transfer_request.py index a985f8e598..4d6f216132 100644 --- a/openstackclient/volume/v1/volume_transfer_request.py +++ b/openstackclient/volume/v1/volume_transfer_request.py @@ -27,6 +27,34 @@ LOG = logging.getLogger(__name__) +class AcceptTransferRequest(command.ShowOne): + """Accept volume transfer request.""" + + def get_parser(self, prog_name): + parser = super(AcceptTransferRequest, self).get_parser(prog_name) + parser.add_argument( + 'transfer_request', + metavar="", + help=_('Volume transfer request to accept (name or ID)'), + ) + parser.add_argument( + 'auth_key', + metavar="", + help=_('Authentication key of transfer request'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + transfer_request_id = utils.find_resource( + volume_client.transfers, parsed_args.transfer_request).id + transfer_accept = volume_client.transfers.accept( + transfer_request_id, parsed_args.auth_key) + transfer_accept._info.pop("links", None) + + return zip(*sorted(six.iteritems(transfer_accept._info))) + + class CreateTransferRequest(command.ShowOne): """Create volume transfer request.""" @@ -120,3 +148,24 @@ def take_action(self, parsed_args): return (column_headers, ( utils.get_item_properties(s, columns) for s in volume_transfer_result)) + + +class ShowTransferRequest(command.ShowOne): + """Show volume transfer request details.""" + + def get_parser(self, prog_name): + parser = super(ShowTransferRequest, self).get_parser(prog_name) + parser.add_argument( + 'transfer_request', + metavar="", + help=_('Volume transfer request to display (name or ID)'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + volume_transfer_request = utils.find_resource( + volume_client.transfers, parsed_args.transfer_request) + volume_transfer_request._info.pop("links", None) + + return zip(*sorted(six.iteritems(volume_transfer_request._info))) diff --git a/openstackclient/volume/v2/volume_transfer_request.py b/openstackclient/volume/v2/volume_transfer_request.py index 8e79807cab..9008fe3c24 100644 --- a/openstackclient/volume/v2/volume_transfer_request.py +++ b/openstackclient/volume/v2/volume_transfer_request.py @@ -27,6 +27,34 @@ LOG = logging.getLogger(__name__) +class AcceptTransferRequest(command.ShowOne): + """Accept volume transfer request.""" + + def get_parser(self, prog_name): + parser = super(AcceptTransferRequest, self).get_parser(prog_name) + parser.add_argument( + 'transfer_request', + metavar="", + help=_('Volume transfer request to accept (name or ID)'), + ) + parser.add_argument( + 'auth_key', + metavar="", + help=_('Authentication key of transfer request'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + transfer_request_id = utils.find_resource( + volume_client.transfers, parsed_args.transfer_request).id + transfer_accept = volume_client.transfers.accept( + transfer_request_id, parsed_args.auth_key) + transfer_accept._info.pop("links", None) + + return zip(*sorted(six.iteritems(transfer_accept._info))) + + class CreateTransferRequest(command.ShowOne): """Create volume transfer request.""" @@ -120,3 +148,24 @@ def take_action(self, parsed_args): return (column_headers, ( utils.get_item_properties(s, columns) for s in volume_transfer_result)) + + +class ShowTransferRequest(command.ShowOne): + """Show volume transfer request details.""" + + def get_parser(self, prog_name): + parser = super(ShowTransferRequest, self).get_parser(prog_name) + parser.add_argument( + 'transfer_request', + metavar="", + help=_('Volume transfer request to display (name or ID)'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + volume_transfer_request = utils.find_resource( + volume_client.transfers, parsed_args.transfer_request) + volume_transfer_request._info.pop("links", None) + + return zip(*sorted(six.iteritems(volume_transfer_request._info))) diff --git a/releasenotes/notes/bp-cinder-command-support-cc8708c4395ce467.yaml b/releasenotes/notes/bp-cinder-command-support-cc8708c4395ce467.yaml index 5f94fd6709..4c858a94c8 100644 --- a/releasenotes/notes/bp-cinder-command-support-cc8708c4395ce467.yaml +++ b/releasenotes/notes/bp-cinder-command-support-cc8708c4395ce467.yaml @@ -1,5 +1,6 @@ --- features: - - Add ``volume transfer request create`` and ``volume transfer request delete`` + - Add ``volume transfer request create``, ``volume transfer request delete``, + ``volume transfer request show`` and ``volume transfer request accept`` commands in volume v1 and v2. [Blueprint `cinder-command-support `_] diff --git a/setup.cfg b/setup.cfg index 3e6486a23d..1635b5597b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -483,9 +483,11 @@ openstack.volume.v1 = volume_service_list = openstackclient.volume.v1.service:ListService volume_service_set = openstackclient.volume.v1.service:SetService + volume_transfer_request_accept = openstackclient.volume.v1.volume_transfer_request:AcceptTransferRequest volume_transfer_request_create = openstackclient.volume.v1.volume_transfer_request:CreateTransferRequest volume_transfer_request_delete = openstackclient.volume.v1.volume_transfer_request:DeleteTransferRequest volume_transfer_request_list = openstackclient.volume.v1.volume_transfer_request:ListTransferRequest + volume_transfer_request_show = openstackclient.volume.v1.volume_transfer_request:ShowTransferRequest openstack.volume.v2 = backup_create = openstackclient.volume.v2.backup:CreateBackup @@ -533,9 +535,11 @@ openstack.volume.v2 = volume_service_list = openstackclient.volume.v2.service:ListService volume_service_set = openstackclient.volume.v2.service:SetService + volume_transfer_request_accept = openstackclient.volume.v2.volume_transfer_request:AcceptTransferRequest volume_transfer_request_create = openstackclient.volume.v2.volume_transfer_request:CreateTransferRequest volume_transfer_request_delete = openstackclient.volume.v2.volume_transfer_request:DeleteTransferRequest volume_transfer_request_list = openstackclient.volume.v2.volume_transfer_request:ListTransferRequest + volume_transfer_request_show = openstackclient.volume.v2.volume_transfer_request:ShowTransferRequest [build_sphinx] source-dir = doc/source From 41f7de22d6f99b25d49187bf532197a20f640c7c Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 22 Sep 2016 13:15:54 +0000 Subject: [PATCH 1248/3095] Updated from global requirements Change-Id: I9f23736d106b4b6fcc37b98f03387ea041c5828d --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 069a737408..db9334179c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ six>=1.9.0 # MIT Babel>=2.3.4 # BSD cliff!=1.16.0,!=1.17.0,>=1.15.0 # Apache-2.0 keystoneauth1>=2.10.0 # Apache-2.0 -openstacksdk>=0.9.6 # Apache-2.0 +openstacksdk!=0.9.6,>=0.9.5 # Apache-2.0 osc-lib>=1.0.2 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 oslo.utils>=3.16.0 # Apache-2.0 From 07dfd05ddae88d64d012097fb4e275231b2d9312 Mon Sep 17 00:00:00 2001 From: zheng yin Date: Fri, 23 Sep 2016 11:14:15 +0800 Subject: [PATCH 1249/3095] Add square bracket to option parameter Because option parameter is not required. I add [] to option parameter. Change-Id: I27c14c42bf576decff90a4a290dcaad7fab00524 --- doc/source/command-objects/usage.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/source/command-objects/usage.rst b/doc/source/command-objects/usage.rst index 0c866086dd..589123a65b 100644 --- a/doc/source/command-objects/usage.rst +++ b/doc/source/command-objects/usage.rst @@ -13,8 +13,8 @@ List resource usage per project .. code:: bash os usage list - --start - --end + [--start ] + [--end ] .. option:: --start @@ -33,9 +33,9 @@ Show resource usage for a single project .. code:: bash os usage show - --project - --start - --end + [--project ] + [--start ] + [--end ] .. option:: --project From fd876e4cc699d7e6da7c5439a8452c4a8b395139 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Fri, 23 Sep 2016 13:02:13 +0800 Subject: [PATCH 1250/3095] Fix unset commands to pass normally when nothing specified After I found this problem appear in "volume unset", I checked all the volume command and also found some same problems. This patch fix them all. The main reason of we ignored this problem before is there was not any tests for it. So I add tests for "nothing unset" for them all to test and aviod this problem. Also, I add unit tests for all snapshot commands in volume v1 by the way in this patch. We will need more tests to avoid some ignored problem. Change-Id: I46775f24643d715e168b30785b8b531c0431a55b Partial-bug: #1588588 --- openstackclient/tests/unit/volume/v1/fakes.py | 78 +++ .../tests/unit/volume/v1/test_qos_specs.py | 13 + .../tests/unit/volume/v1/test_snapshot.py | 467 ++++++++++++++++++ .../tests/unit/volume/v1/test_type.py | 13 + openstackclient/volume/v1/qos_specs.py | 1 - openstackclient/volume/v1/snapshot.py | 2 - openstackclient/volume/v1/volume_type.py | 2 - 7 files changed, 571 insertions(+), 5 deletions(-) create mode 100644 openstackclient/tests/unit/volume/v1/test_snapshot.py diff --git a/openstackclient/tests/unit/volume/v1/fakes.py b/openstackclient/tests/unit/volume/v1/fakes.py index ef52e4b008..ced7b3b6b5 100644 --- a/openstackclient/tests/unit/volume/v1/fakes.py +++ b/openstackclient/tests/unit/volume/v1/fakes.py @@ -449,6 +449,8 @@ def __init__(self, **kwargs): self.volume_types.resource_class = fakes.FakeResource(None, {}) self.transfers = mock.Mock() self.transfers.resource_class = fakes.FakeResource(None, {}) + self.volume_snapshots = mock.Mock() + self.volume_snapshots.resource_class = fakes.FakeResource(None, {}) self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] @@ -546,3 +548,79 @@ def get_types(types=None, count=2): types = FakeType.create_types(count) return mock.Mock(side_effect=types) + + +class FakeSnapshot(object): + """Fake one or more snapshot.""" + + @staticmethod + def create_one_snapshot(attrs=None): + """Create a fake snapshot. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object with id, name, description, etc. + """ + attrs = attrs or {} + + # Set default attributes. + snapshot_info = { + "id": 'snapshot-id-' + uuid.uuid4().hex, + "display_name": 'snapshot-name-' + uuid.uuid4().hex, + "display_description": 'snapshot-description-' + uuid.uuid4().hex, + "size": 10, + "status": "available", + "metadata": {"foo": "bar"}, + "created_at": "2015-06-03T18:49:19.000000", + "volume_id": 'vloume-id-' + uuid.uuid4().hex, + } + + # Overwrite default attributes. + snapshot_info.update(attrs) + + snapshot_method = {'update': None} + + snapshot = fakes.FakeResource( + info=copy.deepcopy(snapshot_info), + methods=copy.deepcopy(snapshot_method), + loaded=True) + return snapshot + + @staticmethod + def create_snapshots(attrs=None, count=2): + """Create multiple fake snapshots. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of snapshots to fake + :return: + A list of FakeResource objects faking the snapshots + """ + snapshots = [] + for i in range(0, count): + snapshot = FakeSnapshot.create_one_snapshot(attrs) + snapshots.append(snapshot) + + return snapshots + + @staticmethod + def get_snapshots(snapshots=None, count=2): + """Get an iterable MagicMock object with a list of faked snapshots. + + If snapshots list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List volumes: + A list of FakeResource objects faking snapshots + :param Integer count: + The number of snapshots to be faked + :return + An iterable Mock object with side_effect set to a list of faked + snapshots + """ + if snapshots is None: + snapshots = FakeSnapshot.create_snapshots(count) + + return mock.Mock(side_effect=snapshots) diff --git a/openstackclient/tests/unit/volume/v1/test_qos_specs.py b/openstackclient/tests/unit/volume/v1/test_qos_specs.py index 81680ab4ef..1982980a32 100644 --- a/openstackclient/tests/unit/volume/v1/test_qos_specs.py +++ b/openstackclient/tests/unit/volume/v1/test_qos_specs.py @@ -517,3 +517,16 @@ def test_qos_unset_with_properties(self): ['iops', 'foo'] ) self.assertIsNone(result) + + def test_qos_unset_nothing(self): + arglist = [ + volume_fakes.qos_id, + ] + + verifylist = [ + ('qos_spec', volume_fakes.qos_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.assertIsNone(result) diff --git a/openstackclient/tests/unit/volume/v1/test_snapshot.py b/openstackclient/tests/unit/volume/v1/test_snapshot.py new file mode 100644 index 0000000000..edfbdc190c --- /dev/null +++ b/openstackclient/tests/unit/volume/v1/test_snapshot.py @@ -0,0 +1,467 @@ +# +# 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 mock import call + +from osc_lib import exceptions +from osc_lib import utils + +from openstackclient.tests.unit.volume.v1 import fakes as volume_fakes +from openstackclient.volume.v1 import snapshot + + +class TestSnapshot(volume_fakes.TestVolumev1): + + def setUp(self): + super(TestSnapshot, self).setUp() + + self.snapshots_mock = self.app.client_manager.volume.volume_snapshots + self.snapshots_mock.reset_mock() + self.volumes_mock = self.app.client_manager.volume.volumes + self.volumes_mock.reset_mock() + + +class TestSnapshotCreate(TestSnapshot): + + columns = ( + 'created_at', + 'display_description', + 'display_name', + 'id', + 'properties', + 'size', + 'status', + 'volume_id', + ) + + def setUp(self): + super(TestSnapshotCreate, self).setUp() + + self.volume = volume_fakes.FakeVolume.create_one_volume() + self.new_snapshot = volume_fakes.FakeSnapshot.create_one_snapshot( + attrs={'volume_id': self.volume.id}) + + self.data = ( + self.new_snapshot.created_at, + self.new_snapshot.display_description, + self.new_snapshot.display_name, + self.new_snapshot.id, + utils.format_dict(self.new_snapshot.metadata), + self.new_snapshot.size, + self.new_snapshot.status, + self.new_snapshot.volume_id, + ) + + self.volumes_mock.get.return_value = self.volume + self.snapshots_mock.create.return_value = self.new_snapshot + # Get the command object to test + self.cmd = snapshot.CreateSnapshot(self.app, None) + + def test_snapshot_create(self): + arglist = [ + "--name", self.new_snapshot.display_name, + "--description", self.new_snapshot.display_description, + "--force", + self.new_snapshot.volume_id, + ] + verifylist = [ + ("name", self.new_snapshot.display_name), + ("description", self.new_snapshot.display_description), + ("force", True), + ("volume", self.new_snapshot.volume_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.snapshots_mock.create.assert_called_with( + self.new_snapshot.volume_id, + True, + self.new_snapshot.display_name, + self.new_snapshot.display_description, + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_snapshot_create_without_name(self): + arglist = [ + self.new_snapshot.volume_id, + "--description", self.new_snapshot.display_description, + "--force" + ] + verifylist = [ + ("volume", self.new_snapshot.volume_id), + ("description", self.new_snapshot.display_description), + ("force", True) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.snapshots_mock.create.assert_called_with( + self.new_snapshot.volume_id, + True, + None, + self.new_snapshot.display_description, + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestSnapshotDelete(TestSnapshot): + + snapshots = volume_fakes.FakeSnapshot.create_snapshots(count=2) + + def setUp(self): + super(TestSnapshotDelete, self).setUp() + + self.snapshots_mock.get = ( + volume_fakes.FakeSnapshot.get_snapshots(self.snapshots)) + self.snapshots_mock.delete.return_value = None + + # Get the command object to mock + self.cmd = snapshot.DeleteSnapshot(self.app, None) + + def test_snapshot_delete(self): + arglist = [ + self.snapshots[0].id + ] + verifylist = [ + ("snapshots", [self.snapshots[0].id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.snapshots_mock.delete.assert_called_with( + self.snapshots[0].id) + self.assertIsNone(result) + + def test_delete_multiple_snapshots(self): + arglist = [] + for s in self.snapshots: + arglist.append(s.id) + verifylist = [ + ('snapshots', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + calls = [] + for s in self.snapshots: + calls.append(call(s.id)) + self.snapshots_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_delete_multiple_snapshots_with_exception(self): + arglist = [ + self.snapshots[0].id, + 'unexist_snapshot', + ] + verifylist = [ + ('snapshots', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self.snapshots[0], exceptions.CommandError] + with mock.patch.object(utils, 'find_resource', + side_effect=find_mock_result) as find_mock: + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 snapshots failed to delete.', + str(e)) + + find_mock.assert_any_call( + self.snapshots_mock, self.snapshots[0].id) + find_mock.assert_any_call(self.snapshots_mock, 'unexist_snapshot') + + self.assertEqual(2, find_mock.call_count) + self.snapshots_mock.delete.assert_called_once_with( + self.snapshots[0].id + ) + + +class TestSnapshotList(TestSnapshot): + + volume = volume_fakes.FakeVolume.create_one_volume() + snapshots = volume_fakes.FakeSnapshot.create_snapshots( + attrs={'volume_id': volume.display_name}, count=3) + + columns = [ + "ID", + "Name", + "Description", + "Status", + "Size" + ] + columns_long = columns + [ + "Created At", + "Volume", + "Properties" + ] + + data = [] + for s in snapshots: + data.append(( + s.id, + s.display_name, + s.display_description, + s.status, + s.size, + )) + data_long = [] + for s in snapshots: + data_long.append(( + s.id, + s.display_name, + s.display_description, + s.status, + s.size, + s.created_at, + s.volume_id, + utils.format_dict(s.metadata), + )) + + def setUp(self): + super(TestSnapshotList, self).setUp() + + self.volumes_mock.list.return_value = [self.volume] + self.snapshots_mock.list.return_value = self.snapshots + # Get the command to test + self.cmd = snapshot.ListSnapshot(self.app, None) + + def test_snapshot_list_without_options(self): + arglist = [] + verifylist = [ + ('all_projects', False), + ("long", False) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.snapshots_mock.list.assert_called_once_with( + search_opts={'all_tenants': False}) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_snapshot_list_with_long(self): + arglist = [ + "--long", + ] + verifylist = [ + ("long", True), + ('all_projects', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.snapshots_mock.list.assert_called_once_with( + search_opts={'all_tenants': False} + ) + self.assertEqual(self.columns_long, columns) + self.assertEqual(self.data_long, list(data)) + + def test_snapshot_list_all_projects(self): + arglist = [ + '--all-projects', + ] + verifylist = [ + ('long', False), + ('all_projects', True) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.snapshots_mock.list.assert_called_once_with( + search_opts={'all_tenants': True}) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + +class TestSnapshotSet(TestSnapshot): + + snapshot = volume_fakes.FakeSnapshot.create_one_snapshot() + + def setUp(self): + super(TestSnapshotSet, self).setUp() + + self.snapshots_mock.get.return_value = self.snapshot + self.snapshots_mock.set_metadata.return_value = None + # Get the command object to mock + self.cmd = snapshot.SetSnapshot(self.app, None) + + def test_snapshot_set_all(self): + arglist = [ + "--name", "new_snapshot", + "--description", "new_description", + "--property", "x=y", + "--property", "foo=foo", + self.snapshot.id, + ] + new_property = {"x": "y", "foo": "foo"} + verifylist = [ + ("name", "new_snapshot"), + ("description", "new_description"), + ("property", new_property), + ("snapshot", self.snapshot.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + kwargs = { + "display_name": "new_snapshot", + "display_description": "new_description", + } + self.snapshot.update.assert_called_with(**kwargs) + self.snapshots_mock.set_metadata.assert_called_with( + self.snapshot.id, new_property + ) + self.assertIsNone(result) + + def test_snapshot_set_nothing(self): + arglist = [ + self.snapshot.id, + ] + verifylist = [ + ("snapshot", self.snapshot.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.assertIsNone(result) + + def test_snapshot_set_fail(self): + self.snapshots_mock.set_metadata.side_effect = ( + exceptions.CommandError()) + arglist = [ + "--name", "new_snapshot", + "--description", "new_description", + "--property", "x=y", + "--property", "foo=foo", + self.snapshot.id, + ] + new_property = {"x": "y", "foo": "foo"} + verifylist = [ + ("name", "new_snapshot"), + ("description", "new_description"), + ("property", new_property), + ("snapshot", self.snapshot.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, parsed_args) + + +class TestSnapshotShow(TestSnapshot): + + columns = ( + 'created_at', + 'display_description', + 'display_name', + 'id', + 'properties', + 'size', + 'status', + 'volume_id', + ) + + def setUp(self): + super(TestSnapshotShow, self).setUp() + + self.snapshot = volume_fakes.FakeSnapshot.create_one_snapshot() + + self.data = ( + self.snapshot.created_at, + self.snapshot.display_description, + self.snapshot.display_name, + self.snapshot.id, + utils.format_dict(self.snapshot.metadata), + self.snapshot.size, + self.snapshot.status, + self.snapshot.volume_id, + ) + + self.snapshots_mock.get.return_value = self.snapshot + # Get the command object to test + self.cmd = snapshot.ShowSnapshot(self.app, None) + + def test_snapshot_show(self): + arglist = [ + self.snapshot.id + ] + verifylist = [ + ("snapshot", self.snapshot.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.snapshots_mock.get.assert_called_with(self.snapshot.id) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestSnapshotUnset(TestSnapshot): + + snapshot = volume_fakes.FakeSnapshot.create_one_snapshot() + + def setUp(self): + super(TestSnapshotUnset, self).setUp() + + self.snapshots_mock.get.return_value = self.snapshot + self.snapshots_mock.delete_metadata.return_value = None + # Get the command object to mock + self.cmd = snapshot.UnsetSnapshot(self.app, None) + + def test_snapshot_unset(self): + arglist = [ + "--property", "foo", + self.snapshot.id, + ] + verifylist = [ + ("property", ["foo"]), + ("snapshot", self.snapshot.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.snapshots_mock.delete_metadata.assert_called_with( + self.snapshot.id, ["foo"] + ) + self.assertIsNone(result) + + def test_snapshot_unset_nothing(self): + arglist = [ + self.snapshot.id, + ] + verifylist = [ + ("snapshot", self.snapshot.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.assertIsNone(result) diff --git a/openstackclient/tests/unit/volume/v1/test_type.py b/openstackclient/tests/unit/volume/v1/test_type.py index 35016dc6b6..23a1186dad 100644 --- a/openstackclient/tests/unit/volume/v1/test_type.py +++ b/openstackclient/tests/unit/volume/v1/test_type.py @@ -345,3 +345,16 @@ def test_type_unset_failed_with_missing_volume_type_argument(self): self.cmd, arglist, verifylist) + + def test_type_unset_nothing(self): + arglist = [ + self.volume_type.id, + ] + verifylist = [ + ('volume_type', self.volume_type.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.assertIsNone(result) diff --git a/openstackclient/volume/v1/qos_specs.py b/openstackclient/volume/v1/qos_specs.py index b982c0e604..93c24a212a 100644 --- a/openstackclient/volume/v1/qos_specs.py +++ b/openstackclient/volume/v1/qos_specs.py @@ -273,7 +273,6 @@ def get_parser(self, prog_name): '--property', metavar='', action='append', - default=[], help=_('Property to remove from the QoS specification. ' '(repeat option to unset multiple properties)'), ) diff --git a/openstackclient/volume/v1/snapshot.py b/openstackclient/volume/v1/snapshot.py index c4d113a31a..bc92c0f56d 100644 --- a/openstackclient/volume/v1/snapshot.py +++ b/openstackclient/volume/v1/snapshot.py @@ -283,8 +283,6 @@ def get_parser(self, prog_name): '--property', metavar='', action='append', - default=[], - required=True, help=_('Property to remove from snapshot ' '(repeat option to remove multiple properties)'), ) diff --git a/openstackclient/volume/v1/volume_type.py b/openstackclient/volume/v1/volume_type.py index 625b34dc43..61e9f7fca1 100644 --- a/openstackclient/volume/v1/volume_type.py +++ b/openstackclient/volume/v1/volume_type.py @@ -188,10 +188,8 @@ def get_parser(self, prog_name): '--property', metavar='', action='append', - default=[], help=_('Remove a property from this volume type ' '(repeat option to remove multiple properties)'), - required=True, ) return parser From 2e5d6a67f628aa00aa4edcc8568a2a6c30c4c7aa Mon Sep 17 00:00:00 2001 From: zheng yin Date: Thu, 22 Sep 2016 19:35:05 +0800 Subject: [PATCH 1251/3095] Use assertGreater replace assertTrue we can use assertGreater(a,b) to replace assertTrue(a>b) Change-Id: Idd92a99bf5fd25785a47645d46bcfcad23ed870a --- openstackclient/tests/functional/identity/v3/test_project.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/tests/functional/identity/v3/test_project.py b/openstackclient/tests/functional/identity/v3/test_project.py index 7743884195..5639dc167c 100644 --- a/openstackclient/tests/functional/identity/v3/test_project.py +++ b/openstackclient/tests/functional/identity/v3/test_project.py @@ -66,7 +66,7 @@ def test_project_list_with_domain(self): items = self.parse_listing(raw_output) self.assert_table_structure(items, common.BASIC_LIST_HEADERS) self.assertIn(project_name, raw_output) - self.assertTrue(len(items) > 0) + self.assertGreater(len(items), 0) def test_project_set(self): project_name = self._create_dummy_project() From df8ef606036a1865ec19954398db8440ee839618 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Fri, 23 Sep 2016 17:22:44 +0800 Subject: [PATCH 1252/3095] Add unit tests for backup commands in volume v1 There was not any unit tests for backup commands in volume v1 so that sometimes some small bugs maybe ignored, this patch add unit tests for them. Change-Id: Ic67c1b80243f7b3d15dabd25e4e4a1b1517a8b59 --- openstackclient/tests/unit/volume/v1/fakes.py | 80 ++++ .../tests/unit/volume/v1/test_backup.py | 355 ++++++++++++++++++ 2 files changed, 435 insertions(+) create mode 100644 openstackclient/tests/unit/volume/v1/test_backup.py diff --git a/openstackclient/tests/unit/volume/v1/fakes.py b/openstackclient/tests/unit/volume/v1/fakes.py index ced7b3b6b5..3999543c5b 100644 --- a/openstackclient/tests/unit/volume/v1/fakes.py +++ b/openstackclient/tests/unit/volume/v1/fakes.py @@ -451,6 +451,10 @@ def __init__(self, **kwargs): self.transfers.resource_class = fakes.FakeResource(None, {}) self.volume_snapshots = mock.Mock() self.volume_snapshots.resource_class = fakes.FakeResource(None, {}) + self.backups = mock.Mock() + self.backups.resource_class = fakes.FakeResource(None, {}) + self.restores = mock.Mock() + self.restores.resource_class = fakes.FakeResource(None, {}) self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] @@ -624,3 +628,79 @@ def get_snapshots(snapshots=None, count=2): snapshots = FakeSnapshot.create_snapshots(count) return mock.Mock(side_effect=snapshots) + + +class FakeBackup(object): + """Fake one or more backup.""" + + @staticmethod + def create_one_backup(attrs=None): + """Create a fake backup. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object with id, name, volume_id, etc. + """ + attrs = attrs or {} + + # Set default attributes. + backup_info = { + "id": 'backup-id-' + uuid.uuid4().hex, + "name": 'backup-name-' + uuid.uuid4().hex, + "volume_id": 'volume-id-' + uuid.uuid4().hex, + "snapshot_id": 'snapshot-id' + uuid.uuid4().hex, + "description": 'description-' + uuid.uuid4().hex, + "object_count": None, + "container": 'container-' + uuid.uuid4().hex, + "size": random.randint(1, 20), + "status": "error", + "availability_zone": 'zone' + uuid.uuid4().hex, + "links": 'links-' + uuid.uuid4().hex, + } + + # Overwrite default attributes. + backup_info.update(attrs) + + backup = fakes.FakeResource( + info=copy.deepcopy(backup_info), + loaded=True) + return backup + + @staticmethod + def create_backups(attrs=None, count=2): + """Create multiple fake backups. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of backups to fake + :return: + A list of FakeResource objects faking the backups + """ + backups = [] + for i in range(0, count): + backup = FakeBackup.create_one_backup(attrs) + backups.append(backup) + + return backups + + @staticmethod + def get_backups(backups=None, count=2): + """Get an iterable MagicMock object with a list of faked backups. + + If backups list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List volumes: + A list of FakeResource objects faking backups + :param Integer count: + The number of backups to be faked + :return + An iterable Mock object with side_effect set to a list of faked + backups + """ + if backups is None: + backups = FakeBackup.create_backups(count) + + return mock.Mock(side_effect=backups) diff --git a/openstackclient/tests/unit/volume/v1/test_backup.py b/openstackclient/tests/unit/volume/v1/test_backup.py new file mode 100644 index 0000000000..32c2fd2242 --- /dev/null +++ b/openstackclient/tests/unit/volume/v1/test_backup.py @@ -0,0 +1,355 @@ +# +# 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 mock import call + +from osc_lib import exceptions +from osc_lib import utils + +from openstackclient.tests.unit.volume.v1 import fakes as volume_fakes +from openstackclient.volume.v1 import backup + + +class TestBackup(volume_fakes.TestVolumev1): + + def setUp(self): + super(TestBackup, self).setUp() + + self.backups_mock = self.app.client_manager.volume.backups + self.backups_mock.reset_mock() + self.volumes_mock = self.app.client_manager.volume.volumes + self.volumes_mock.reset_mock() + self.snapshots_mock = self.app.client_manager.volume.volume_snapshots + self.snapshots_mock.reset_mock() + self.restores_mock = self.app.client_manager.volume.restores + self.restores_mock.reset_mock() + + +class TestBackupCreate(TestBackup): + + volume = volume_fakes.FakeVolume.create_one_volume() + + columns = ( + 'availability_zone', + 'container', + 'description', + 'id', + 'name', + 'object_count', + 'size', + 'snapshot_id', + 'status', + 'volume_id', + ) + + def setUp(self): + super(TestBackupCreate, self).setUp() + self.new_backup = volume_fakes.FakeBackup.create_one_backup( + attrs={'volume_id': self.volume.id}) + self.data = ( + self.new_backup.availability_zone, + self.new_backup.container, + self.new_backup.description, + self.new_backup.id, + self.new_backup.name, + self.new_backup.object_count, + self.new_backup.size, + self.new_backup.snapshot_id, + self.new_backup.status, + self.new_backup.volume_id, + ) + self.volumes_mock.get.return_value = self.volume + self.backups_mock.create.return_value = self.new_backup + + # Get the command object to test + self.cmd = backup.CreateVolumeBackup(self.app, None) + + def test_backup_create(self): + arglist = [ + "--name", self.new_backup.name, + "--description", self.new_backup.description, + "--container", self.new_backup.container, + self.new_backup.volume_id, + ] + verifylist = [ + ("name", self.new_backup.name), + ("description", self.new_backup.description), + ("container", self.new_backup.container), + ("volume", self.new_backup.volume_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.backups_mock.create.assert_called_with( + self.new_backup.volume_id, + self.new_backup.container, + self.new_backup.name, + self.new_backup.description, + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_backup_create_without_name(self): + arglist = [ + "--description", self.new_backup.description, + "--container", self.new_backup.container, + self.new_backup.volume_id, + ] + verifylist = [ + ("description", self.new_backup.description), + ("container", self.new_backup.container), + ("volume", self.new_backup.volume_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.backups_mock.create.assert_called_with( + self.new_backup.volume_id, + self.new_backup.container, + None, + self.new_backup.description, + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestBackupDelete(TestBackup): + + backups = volume_fakes.FakeBackup.create_backups(count=2) + + def setUp(self): + super(TestBackupDelete, self).setUp() + + self.backups_mock.get = ( + volume_fakes.FakeBackup.get_backups(self.backups)) + self.backups_mock.delete.return_value = None + + # Get the command object to mock + self.cmd = backup.DeleteVolumeBackup(self.app, None) + + def test_backup_delete(self): + arglist = [ + self.backups[0].id + ] + verifylist = [ + ("backups", [self.backups[0].id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.backups_mock.delete.assert_called_with( + self.backups[0].id) + self.assertIsNone(result) + + def test_delete_multiple_backups(self): + arglist = [] + for b in self.backups: + arglist.append(b.id) + verifylist = [ + ('backups', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + calls = [] + for b in self.backups: + calls.append(call(b.id)) + self.backups_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_delete_multiple_backups_with_exception(self): + arglist = [ + self.backups[0].id, + 'unexist_backup', + ] + verifylist = [ + ('backups', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self.backups[0], exceptions.CommandError] + with mock.patch.object(utils, 'find_resource', + side_effect=find_mock_result) as find_mock: + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 backups failed to delete.', + str(e)) + + find_mock.assert_any_call(self.backups_mock, self.backups[0].id) + find_mock.assert_any_call(self.backups_mock, 'unexist_backup') + + self.assertEqual(2, find_mock.call_count) + self.backups_mock.delete.assert_called_once_with( + self.backups[0].id, + ) + + +class TestBackupList(TestBackup): + + volume = volume_fakes.FakeVolume.create_one_volume() + backups = volume_fakes.FakeBackup.create_backups( + attrs={'volume_id': volume.display_name}, count=3) + + columns = [ + 'ID', + 'Name', + 'Description', + 'Status', + 'Size', + ] + columns_long = columns + [ + 'Availability Zone', + 'Volume', + 'Container', + ] + + data = [] + for b in backups: + data.append(( + b.id, + b.name, + b.description, + b.status, + b.size, + )) + data_long = [] + for b in backups: + data_long.append(( + b.id, + b.name, + b.description, + b.status, + b.size, + b.availability_zone, + b.volume_id, + b.container, + )) + + def setUp(self): + super(TestBackupList, self).setUp() + + self.volumes_mock.list.return_value = [self.volume] + self.backups_mock.list.return_value = self.backups + # Get the command to test + self.cmd = backup.ListVolumeBackup(self.app, None) + + def test_backup_list_without_options(self): + arglist = [] + verifylist = [("long", False)] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_backup_list_with_options(self): + arglist = ["--long"] + verifylist = [("long", True)] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual(self.columns_long, columns) + self.assertEqual(self.data_long, list(data)) + + +class TestBackupRestore(TestBackup): + + volume = volume_fakes.FakeVolume.create_one_volume() + backup = volume_fakes.FakeBackup.create_one_backup( + attrs={'volume_id': volume.id}) + + def setUp(self): + super(TestBackupRestore, self).setUp() + + self.backups_mock.get.return_value = self.backup + self.volumes_mock.get.return_value = self.volume + self.restores_mock.restore.return_value = None + # Get the command object to mock + self.cmd = backup.RestoreVolumeBackup(self.app, None) + + def test_backup_restore(self): + arglist = [ + self.backup.id, + self.backup.volume_id + ] + verifylist = [ + ("backup", self.backup.id), + ("volume", self.backup.volume_id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.restores_mock.restore.assert_called_with(self.backup.id, + self.backup.volume_id) + self.assertIsNone(result) + + +class TestBackupShow(TestBackup): + + columns = ( + 'availability_zone', + 'container', + 'description', + 'id', + 'name', + 'object_count', + 'size', + 'snapshot_id', + 'status', + 'volume_id', + ) + + def setUp(self): + super(TestBackupShow, self).setUp() + self.backup = volume_fakes.FakeBackup.create_one_backup() + self.data = ( + self.backup.availability_zone, + self.backup.container, + self.backup.description, + self.backup.id, + self.backup.name, + self.backup.object_count, + self.backup.size, + self.backup.snapshot_id, + self.backup.status, + self.backup.volume_id, + ) + self.backups_mock.get.return_value = self.backup + # Get the command object to test + self.cmd = backup.ShowVolumeBackup(self.app, None) + + def test_backup_show(self): + arglist = [ + self.backup.id + ] + verifylist = [ + ("backup", self.backup.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.backups_mock.get.assert_called_with(self.backup.id) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) From 0981931628cdea07793850631595cf7299badeaa Mon Sep 17 00:00:00 2001 From: Ankur Gupta Date: Tue, 20 Sep 2016 12:29:08 -0500 Subject: [PATCH 1253/3095] Add --description to Neutron commands Added simple option for floating ip description when creating a floating ip. Added option for create and set router as well. Partially Implements: blueprint neutron-client-descriptions Partially Implements: blueprint network-commands-options Change-Id: Ib8cd8af449e9ff33b980017b2527a6b196894a00 --- doc/source/command-objects/floating-ip.rst | 6 ++++++ doc/source/command-objects/router.rst | 10 ++++++++++ openstackclient/network/v2/floating_ip.py | 8 ++++++++ openstackclient/network/v2/router.py | 13 ++++++++++++- openstackclient/tests/unit/network/v2/fakes.py | 2 ++ .../tests/unit/network/v2/test_floating_ip.py | 9 ++++++++- .../tests/unit/network/v2/test_router.py | 7 +++++++ ...eutron-client-descriptions-a80902b4295843cf.yaml | 7 +++++++ 8 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/bp-neutron-client-descriptions-a80902b4295843cf.yaml diff --git a/doc/source/command-objects/floating-ip.rst b/doc/source/command-objects/floating-ip.rst index 093bb8e481..2ab21f36eb 100644 --- a/doc/source/command-objects/floating-ip.rst +++ b/doc/source/command-objects/floating-ip.rst @@ -17,6 +17,7 @@ Create floating IP [--port ] [--floating-ip-address ] [--fixed-ip-address ] + [--description ] .. option:: --subnet @@ -39,6 +40,11 @@ Create floating IP Fixed IP address mapped to the floating IP *Network version 2 only* +.. option:: --description + + Set floating IP description + *Network version 2 only* + .. describe:: Network to allocate floating IP from (name or ID) diff --git a/doc/source/command-objects/router.rst b/doc/source/command-objects/router.rst index bbdca054eb..13a75158f4 100644 --- a/doc/source/command-objects/router.rst +++ b/doc/source/command-objects/router.rst @@ -64,6 +64,7 @@ Create new router [--project [--project-domain ]] [--enable | --disable] [--distributed] + [--description ] [--availability-zone-hint ] @@ -88,6 +89,10 @@ Create new router Create a distributed router +.. option:: --description + + Set router description + .. option:: --availability-zone-hint Availability Zone in which to create this router @@ -186,6 +191,7 @@ Set router properties [--name ] [--enable | --disable] [--distributed | --centralized] + [--description ] [--route destination=,gateway= | --no-route] @@ -209,6 +215,10 @@ Set router properties Set router to centralized mode (disabled router only) +.. option:: --description + + Set router description + .. option:: --route destination=,gateway= Routes associated with the router diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index 454335f1a5..bb75540cc6 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -55,6 +55,9 @@ def _get_attrs(client_manager, parsed_args): if parsed_args.fixed_ip_address: attrs['fixed_ip_address'] = parsed_args.fixed_ip_address + if parsed_args.description is not None: + attrs['description'] = parsed_args.description + return attrs @@ -97,6 +100,11 @@ def update_parser_network(self, parser): dest='fixed_ip_address', help=_("Fixed IP address mapped to the floating IP") ) + parser.add_argument( + '--description', + metavar='', + help=_('Set floating IP description') + ) return parser def take_action_network(self, client, parsed_args): diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index d30197cc41..ceebe66c5f 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -82,7 +82,8 @@ def _get_attrs(client_manager, parsed_args): if ('availability_zone_hints' in parsed_args and parsed_args.availability_zone_hints is not None): attrs['availability_zone_hints'] = parsed_args.availability_zone_hints - + if parsed_args.description is not None: + attrs['description'] = parsed_args.description # "router set" command doesn't support setting project. if 'project' in parsed_args and parsed_args.project is not None: identity_client = client_manager.identity @@ -179,6 +180,11 @@ def get_parser(self, prog_name): default=False, help=_("Create a distributed router") ) + parser.add_argument( + '--description', + metavar='', + help=_('Set router description') + ) parser.add_argument( '--project', metavar='', @@ -370,6 +376,11 @@ def get_parser(self, prog_name): metavar='', help=_("Set router name") ) + parser.add_argument( + '--description', + metavar='', + help=_('Set router description') + ) admin_group = parser.add_mutually_exclusive_group() admin_group.add_argument( '--enable', diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index 89b128ee78..6bb35ed064 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -651,6 +651,7 @@ def create_one_router(attrs=None): 'name': 'router-name-' + uuid.uuid4().hex, 'status': 'ACTIVE', 'admin_state_up': True, + 'description': 'router-description-' + uuid.uuid4().hex, 'distributed': False, 'ha': False, 'tenant_id': 'project-id-' + uuid.uuid4().hex, @@ -970,6 +971,7 @@ def create_one_floating_ip(attrs=None): 'router_id': 'router-id-' + uuid.uuid4().hex, 'port_id': 'port-id-' + uuid.uuid4().hex, 'tenant_id': 'project-id-' + uuid.uuid4().hex, + 'description': 'floating-ip-description-' + uuid.uuid4().hex, } # Overwrite default attributes. diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip.py b/openstackclient/tests/unit/network/v2/test_floating_ip.py index a77fb24b5c..1f30f2e923 100644 --- a/openstackclient/tests/unit/network/v2/test_floating_ip.py +++ b/openstackclient/tests/unit/network/v2/test_floating_ip.py @@ -49,6 +49,7 @@ class TestCreateFloatingIPNetwork(TestFloatingIPNetwork): ) columns = ( + 'description', 'dns_domain', 'dns_name', 'fixed_ip_address', @@ -62,6 +63,7 @@ class TestCreateFloatingIPNetwork(TestFloatingIPNetwork): ) data = ( + floating_ip.description, floating_ip.dns_domain, floating_ip.dns_name, floating_ip.fixed_ip_address, @@ -117,14 +119,16 @@ def test_create_all_options(self): '--port', self.floating_ip.port_id, '--floating-ip-address', self.floating_ip.floating_ip_address, '--fixed-ip-address', self.floating_ip.fixed_ip_address, + '--description', self.floating_ip.description, self.floating_ip.floating_network_id, ] verifylist = [ ('subnet', self.subnet.id), ('port', self.floating_ip.port_id), - ('floating_ip_address', self.floating_ip.floating_ip_address), ('fixed_ip_address', self.floating_ip.fixed_ip_address), ('network', self.floating_ip.floating_network_id), + ('description', self.floating_ip.description), + ('floating_ip_address', self.floating_ip.floating_ip_address), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -136,6 +140,7 @@ def test_create_all_options(self): 'floating_ip_address': self.floating_ip.floating_ip_address, 'fixed_ip_address': self.floating_ip.fixed_ip_address, 'floating_network_id': self.floating_ip.floating_network_id, + 'description': self.floating_ip.description, }) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -269,6 +274,7 @@ class TestShowFloatingIPNetwork(TestFloatingIPNetwork): floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip() columns = ( + 'description', 'dns_domain', 'dns_name', 'fixed_ip_address', @@ -282,6 +288,7 @@ class TestShowFloatingIPNetwork(TestFloatingIPNetwork): ) data = ( + floating_ip.description, floating_ip.dns_domain, floating_ip.dns_name, floating_ip.fixed_ip_address, diff --git a/openstackclient/tests/unit/network/v2/test_router.py b/openstackclient/tests/unit/network/v2/test_router.py index 6898a046bf..8c1645e03f 100644 --- a/openstackclient/tests/unit/network/v2/test_router.py +++ b/openstackclient/tests/unit/network/v2/test_router.py @@ -119,6 +119,7 @@ class TestCreateRouter(TestRouter): 'admin_state_up', 'availability_zone_hints', 'availability_zones', + 'description', 'distributed', 'external_gateway_info', 'ha', @@ -132,6 +133,7 @@ class TestCreateRouter(TestRouter): router._format_admin_state(new_router.admin_state_up), osc_utils.format_list(new_router.availability_zone_hints), osc_utils.format_list(new_router.availability_zones), + new_router.description, new_router.distributed, router._format_external_gateway_info(new_router.external_gateway_info), new_router.ha, @@ -503,12 +505,14 @@ def test_set_this(self): '--enable', '--distributed', '--name', 'noob', + '--description', 'router', ] verifylist = [ ('router', self._router.name), ('enable', True), ('distributed', True), ('name', 'noob'), + ('description', 'router'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -518,6 +522,7 @@ def test_set_this(self): 'admin_state_up': True, 'distributed': True, 'name': 'noob', + 'description': 'router', } self.network.update_router.assert_called_once_with( self._router, **attrs) @@ -681,6 +686,7 @@ class TestShowRouter(TestRouter): 'admin_state_up', 'availability_zone_hints', 'availability_zones', + 'description', 'distributed', 'external_gateway_info', 'ha', @@ -694,6 +700,7 @@ class TestShowRouter(TestRouter): router._format_admin_state(_router.admin_state_up), osc_utils.format_list(_router.availability_zone_hints), osc_utils.format_list(_router.availability_zones), + _router.description, _router.distributed, router._format_external_gateway_info(_router.external_gateway_info), _router.ha, diff --git a/releasenotes/notes/bp-neutron-client-descriptions-a80902b4295843cf.yaml b/releasenotes/notes/bp-neutron-client-descriptions-a80902b4295843cf.yaml new file mode 100644 index 0000000000..3c4f4a504c --- /dev/null +++ b/releasenotes/notes/bp-neutron-client-descriptions-a80902b4295843cf.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Add ``--description`` option for core network resources. Allows users to + set a description for: ``floating ip create`` and ``router set/create``. + [Blueprint :oscbp:`neutron-client-descriptions`] + [Blueprint :oscbp:`network-commands-options`] From 2731fc3912aa8392179ca2d870e9daa76e1410e4 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Fri, 23 Sep 2016 23:53:20 +0000 Subject: [PATCH 1254/3095] Updated from global requirements Change-Id: Ic4aed24a86fbce3c20ab96bd98edf9e249f8d247 --- requirements.txt | 2 +- test-requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index db9334179c..e662587066 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ six>=1.9.0 # MIT Babel>=2.3.4 # BSD cliff!=1.16.0,!=1.17.0,>=1.15.0 # Apache-2.0 keystoneauth1>=2.10.0 # Apache-2.0 -openstacksdk!=0.9.6,>=0.9.5 # Apache-2.0 +openstacksdk>=0.9.7 # Apache-2.0 osc-lib>=1.0.2 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 oslo.utils>=3.16.0 # Apache-2.0 diff --git a/test-requirements.txt b/test-requirements.txt index 6a4d3b8f13..c13ebe5da3 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -34,6 +34,6 @@ python-mistralclient>=2.0.0 # Apache-2.0 python-muranoclient>=0.8.2 # Apache-2.0 python-neutronclient>=5.1.0 # Apache-2.0 python-saharaclient>=0.18.0 # Apache-2.0 -python-searchlightclient>=0.2.0 #Apache-2.0 +python-searchlightclient>=1.0.0 #Apache-2.0 python-senlinclient>=0.3.0 # Apache-2.0 python-zaqarclient>=1.0.0 # Apache-2.0 From 63715569160785ffeac05e34b604136440a9f865 Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Tue, 26 Apr 2016 19:34:13 +0800 Subject: [PATCH 1255/3095] Support "--no" option in aggregate set Supporting "--no-property" option will apply user a convenience way to clean all properties of aggregate in a short command, and this kind of behavior is the recommended way to devref. The patch add "--no-property" option in "aggregate set" command, and update related test cases and devref document. Change-Id: I7614a23c0db05144562330dc600dbab7d003d5d8 Implements: blueprint support-no-property-in-aggregate --- doc/source/command-objects/aggregate.rst | 7 ++ openstackclient/compute/v2/aggregate.py | 33 ++++++- .../tests/unit/compute/v2/fakes.py | 1 + .../tests/unit/compute/v2/test_aggregate.py | 98 +++++++++++++++++-- ...roperty-in-aggregate-b74a42e00a65d14a.yaml | 7 ++ 5 files changed, 131 insertions(+), 15 deletions(-) create mode 100644 releasenotes/notes/support-no-property-in-aggregate-b74a42e00a65d14a.yaml diff --git a/doc/source/command-objects/aggregate.rst b/doc/source/command-objects/aggregate.rst index 25c7041a48..642942d457 100644 --- a/doc/source/command-objects/aggregate.rst +++ b/doc/source/command-objects/aggregate.rst @@ -116,6 +116,7 @@ Set aggregate properties [--name ] [--zone ] [--property [...] ] + [--no-property] .. option:: --name @@ -131,6 +132,12 @@ Set aggregate properties Property to set on :ref:`\ ` (repeat option to set multiple properties) +.. option:: --no-property + + Remove all properties from :ref:`\ ` + (specify both --property and --no-property to + overwrite the current properties) + .. _aggregate_set-aggregate: .. describe:: diff --git a/openstackclient/compute/v2/aggregate.py b/openstackclient/compute/v2/aggregate.py index 2e2838c54c..58d529e9f8 100644 --- a/openstackclient/compute/v2/aggregate.py +++ b/openstackclient/compute/v2/aggregate.py @@ -248,6 +248,14 @@ def get_parser(self, prog_name): help=_("Property to set on " "(repeat option to set multiple properties)") ) + parser.add_argument( + "--no-property", + dest="no_property", + action="store_true", + help=_("Remove all properties from " + "(specify both --property and --no-property to " + "overwrite the current properties)"), + ) return parser def take_action(self, parsed_args): @@ -269,10 +277,23 @@ def take_action(self, parsed_args): kwargs ) + set_property = {} + if parsed_args.no_property: + # NOTE(RuiChen): "availability_zone" is removed from response of + # aggregate show and create commands, don't see it + # anywhere, so pop it, avoid the unexpected server + # exception(can't unset the availability zone from + # aggregate metadata in nova). + set_property.update({key: None + for key in aggregate.metadata.keys() + if key != 'availability_zone'}) if parsed_args.property: + set_property.update(parsed_args.property) + + if set_property: compute_client.aggregates.set_metadata( aggregate, - parsed_args.property + set_property ) @@ -326,7 +347,6 @@ def get_parser(self, prog_name): "--property", metavar="", action='append', - required=True, help=_("Property to remove from aggregate " "(repeat option to remove multiple properties)") ) @@ -338,6 +358,9 @@ def take_action(self, parsed_args): compute_client.aggregates, parsed_args.aggregate) - unset_property = {key: None for key in parsed_args.property} - compute_client.aggregates.set_metadata(aggregate, - unset_property) + unset_property = {} + if parsed_args.property: + unset_property.update({key: None for key in parsed_args.property}) + if unset_property: + compute_client.aggregates.set_metadata(aggregate, + unset_property) diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index 3c82977333..985ce5e2ed 100644 --- a/openstackclient/tests/unit/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -83,6 +83,7 @@ def create_one_aggregate(attrs=None): "id": "aggregate-id-" + uuid.uuid4().hex, "metadata": { "availability_zone": "ag_zone", + "key1": "value1", } } diff --git a/openstackclient/tests/unit/compute/v2/test_aggregate.py b/openstackclient/tests/unit/compute/v2/test_aggregate.py index c636d3de2f..3efe0dbd36 100644 --- a/openstackclient/tests/unit/compute/v2/test_aggregate.py +++ b/openstackclient/tests/unit/compute/v2/test_aggregate.py @@ -21,7 +21,6 @@ from openstackclient.compute.v2 import aggregate from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes -from openstackclient.tests.unit import utils as tests_utils class TestAggregate(compute_fakes.TestComputev2): @@ -235,7 +234,8 @@ class TestAggregateList(TestAggregate): TestAggregate.fake_ag.id, TestAggregate.fake_ag.name, TestAggregate.fake_ag.availability_zone, - {}, + {key: value for key, value in TestAggregate.fake_ag.metadata.items() + if key != 'availability_zone'}, ), ) def setUp(self): @@ -371,6 +371,62 @@ def test_aggregate_set_with_property(self): self.fake_ag, parsed_args.property) self.assertIsNone(result) + def test_aggregate_set_with_no_property_and_property(self): + arglist = [ + '--no-property', + '--property', 'key2=value2', + 'ag1', + ] + verifylist = [ + ('no_property', True), + ('property', {'key2': 'value2'}), + ('aggregate', 'ag1'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate) + self.assertNotCalled(self.aggregate_mock.update) + self.aggregate_mock.set_metadata.assert_called_once_with( + self.fake_ag, {'key1': None, 'key2': 'value2'}) + self.assertIsNone(result) + + def test_aggregate_set_with_no_property(self): + arglist = [ + '--no-property', + 'ag1', + ] + verifylist = [ + ('no_property', True), + ('aggregate', 'ag1'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate) + self.assertNotCalled(self.aggregate_mock.update) + self.aggregate_mock.set_metadata.assert_called_once_with( + self.fake_ag, {'key1': None}) + self.assertIsNone(result) + + def test_aggregate_set_with_zone_and_no_property(self): + arglist = [ + '--zone', 'new_zone', + '--no-property', + 'ag1', + ] + verifylist = [ + ('zone', 'new_zone'), + ('no_property', True), + ('aggregate', 'ag1'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate) + self.aggregate_mock.update.assert_called_once_with( + self.fake_ag, {'availability_zone': parsed_args.zone}) + self.aggregate_mock.set_metadata.assert_called_once_with( + self.fake_ag, {'key1': None}) + self.assertIsNone(result) + class TestAggregateShow(TestAggregate): @@ -387,7 +443,10 @@ class TestAggregateShow(TestAggregate): TestAggregate.fake_ag.hosts, TestAggregate.fake_ag.id, TestAggregate.fake_ag.name, - '', + utils.format_dict( + {key: value + for key, value in TestAggregate.fake_ag.metadata.items() + if key != 'availability_zone'}), ) def setUp(self): @@ -435,13 +494,32 @@ def test_aggregate_unset(self): self.fake_ag, {'unset_key': None}) self.assertIsNone(result) - def test_aggregate_unset_no_property(self): + def test_aggregate_unset_multiple_properties(self): arglist = [ + '--property', 'unset_key1', + '--property', 'unset_key2', 'ag1', ] - verifylist = None - self.assertRaises(tests_utils.ParserException, - self.check_parser, - self.cmd, - arglist, - verifylist) + verifylist = [ + ('property', ['unset_key1', 'unset_key2']), + ('aggregate', 'ag1'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.aggregate_mock.set_metadata.assert_called_once_with( + self.fake_ag, {'unset_key1': None, 'unset_key2': None}) + self.assertIsNone(result) + + def test_aggregate_unset_no_option(self): + arglist = [ + 'ag1', + ] + verifylist = [ + ('property', None), + ('aggregate', 'ag1'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.assertNotCalled(self.aggregate_mock.set_metadata) + self.assertIsNone(result) diff --git a/releasenotes/notes/support-no-property-in-aggregate-b74a42e00a65d14a.yaml b/releasenotes/notes/support-no-property-in-aggregate-b74a42e00a65d14a.yaml new file mode 100644 index 0000000000..5a785e4ae6 --- /dev/null +++ b/releasenotes/notes/support-no-property-in-aggregate-b74a42e00a65d14a.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Add ``--no-property`` option in ``aggregate set``. + Supporting ``--no-property`` option will apply user a convenience way to + clean all properties of aggregate in a short command. + [Blueprint `support-no-property-in-aggregate `_] From 52eaec1189be98664f39d7e2e07b23cbea585f10 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Thu, 22 Sep 2016 10:56:45 +0800 Subject: [PATCH 1256/3095] Add functional test for snapshot in volume v1 Add functional test for snapshot commands in volume v1. Tests can always help to find or avoid bugs. Change-Id: Ieb0ab9c763d381a6343b4c4a8a5874f3e682f24f --- .../functional/volume/v1/test_snapshot.py | 86 +++++++++++++++++++ .../functional/volume/v2/test_snapshot.py | 9 +- 2 files changed, 92 insertions(+), 3 deletions(-) create mode 100644 openstackclient/tests/functional/volume/v1/test_snapshot.py diff --git a/openstackclient/tests/functional/volume/v1/test_snapshot.py b/openstackclient/tests/functional/volume/v1/test_snapshot.py new file mode 100644 index 0000000000..c6d65ccc60 --- /dev/null +++ b/openstackclient/tests/functional/volume/v1/test_snapshot.py @@ -0,0 +1,86 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import time +import uuid + +from openstackclient.tests.functional.volume.v1 import common + + +class SnapshotTests(common.BaseVolumeTests): + """Functional tests for snapshot. """ + + VOLLY = uuid.uuid4().hex + NAME = uuid.uuid4().hex + OTHER_NAME = uuid.uuid4().hex + HEADERS = ['"Name"'] + + @classmethod + def wait_for_status(cls, command, status, tries): + opts = cls.get_opts(['status']) + for attempt in range(tries): + time.sleep(1) + raw_output = cls.openstack(command + opts) + if (raw_output == status): + return + cls.assertOutput(status, raw_output) + + @classmethod + def setUpClass(cls): + super(SnapshotTests, cls).setUpClass() + cls.openstack('volume create --size 1 ' + cls.VOLLY) + cls.wait_for_status('volume show ' + cls.VOLLY, 'available\n', 3) + opts = cls.get_opts(['status']) + raw_output = cls.openstack('snapshot create --name ' + cls.NAME + + ' ' + cls.VOLLY + opts) + cls.assertOutput('creating\n', raw_output) + cls.wait_for_status('snapshot show ' + cls.NAME, 'available\n', 3) + + @classmethod + def tearDownClass(cls): + # Rename test + raw_output = cls.openstack( + 'snapshot set --name ' + cls.OTHER_NAME + ' ' + cls.NAME) + cls.assertOutput('', raw_output) + # Delete test + raw_output_snapshot = cls.openstack( + 'snapshot delete ' + cls.OTHER_NAME) + cls.wait_for_status('volume show ' + cls.VOLLY, 'available\n', 6) + raw_output_volume = cls.openstack('volume delete --force ' + cls.VOLLY) + cls.assertOutput('', raw_output_snapshot) + cls.assertOutput('', raw_output_volume) + + def test_snapshot_list(self): + opts = self.get_opts(self.HEADERS) + raw_output = self.openstack('snapshot list' + opts) + self.assertIn(self.NAME, raw_output) + + def test_snapshot_set_unset_properties(self): + raw_output = self.openstack( + 'snapshot set --property a=b --property c=d ' + self.NAME) + self.assertEqual("", raw_output) + opts = self.get_opts(["properties"]) + raw_output = self.openstack('snapshot show ' + self.NAME + opts) + self.assertEqual("a='b', c='d'\n", raw_output) + + raw_output = self.openstack('snapshot unset --property a ' + self.NAME) + self.assertEqual("", raw_output) + raw_output = self.openstack('snapshot show ' + self.NAME + opts) + self.assertEqual("c='d'\n", raw_output) + + def test_snapshot_set_description(self): + raw_output = self.openstack( + 'snapshot set --description backup ' + self.NAME) + self.assertEqual("", raw_output) + opts = self.get_opts(["display_description", "display_name"]) + raw_output = self.openstack('snapshot show ' + self.NAME + opts) + self.assertEqual("backup\n" + self.NAME + "\n", raw_output) diff --git a/openstackclient/tests/functional/volume/v2/test_snapshot.py b/openstackclient/tests/functional/volume/v2/test_snapshot.py index 4582b67dac..fcbc31cbec 100644 --- a/openstackclient/tests/functional/volume/v2/test_snapshot.py +++ b/openstackclient/tests/functional/volume/v2/test_snapshot.py @@ -52,9 +52,12 @@ def tearDownClass(cls): 'snapshot set --name ' + cls.OTHER_NAME + ' ' + cls.NAME) cls.assertOutput('', raw_output) # Delete test - raw_output = cls.openstack('snapshot delete ' + cls.OTHER_NAME) - cls.assertOutput('', raw_output) - cls.openstack('volume delete --force ' + cls.VOLLY, fail_ok=True) + raw_output_snapshot = cls.openstack( + 'snapshot delete ' + cls.OTHER_NAME) + cls.wait_for_status('volume show ' + cls.VOLLY, 'available\n', 6) + raw_output_volume = cls.openstack('volume delete --force ' + cls.VOLLY) + cls.assertOutput('', raw_output_snapshot) + cls.assertOutput('', raw_output_volume) def test_snapshot_list(self): opts = self.get_opts(self.HEADERS) From 02b6d32bfb3342507c5009f18e9cef966d8d1b74 Mon Sep 17 00:00:00 2001 From: LiuNanke Date: Sun, 25 Sep 2016 10:35:02 +0800 Subject: [PATCH 1257/3095] Fix typo in osc doc Change-Id: I8df73b36378e3de88ebe6d1ec125546c83f707c7 --- doc/source/command-objects/network-agent.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/command-objects/network-agent.rst b/doc/source/command-objects/network-agent.rst index 15195a3398..9d158ba094 100644 --- a/doc/source/command-objects/network-agent.rst +++ b/doc/source/command-objects/network-agent.rst @@ -49,7 +49,7 @@ Set network agent properties [--enable | --disable] -.. option:: --description +.. option:: --description Set network agent description From 084e4fbf1b5efeab34f84053edb7f829488afac7 Mon Sep 17 00:00:00 2001 From: KATO Tomoyuki Date: Sun, 25 Sep 2016 14:10:20 +0900 Subject: [PATCH 1258/3095] Add option markup in osc doc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To avoid unintended hyphen concatenation: "openstack –help" Change-Id: I6e8a1c453c5964bea4adc47d167904ab8ce8abda --- doc/source/man/openstack.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/man/openstack.rst b/doc/source/man/openstack.rst index 7af380a31c..e03b48e5c8 100644 --- a/doc/source/man/openstack.rst +++ b/doc/source/man/openstack.rst @@ -12,7 +12,7 @@ SYNOPSIS :program:`openstack help` -:program:`openstack` --help +:program:`openstack` :option:`--help` DESCRIPTION From 50ad04df973e6dcc96915190fa86f58daa969dec Mon Sep 17 00:00:00 2001 From: KATO Tomoyuki Date: Sun, 25 Sep 2016 14:17:09 +0900 Subject: [PATCH 1259/3095] Remove reference to CLI Ref in osc doc Since we decide to unify the osc doc into the osc repo, CLI Reference does not provide osc content. Change-Id: I0d530143105b5411a7a38e9e9d2297a1e8c69071 --- doc/source/plugin-commands.rst | 4 ---- 1 file changed, 4 deletions(-) diff --git a/doc/source/plugin-commands.rst b/doc/source/plugin-commands.rst index 4cb13e83ea..616d2f0f62 100644 --- a/doc/source/plugin-commands.rst +++ b/doc/source/plugin-commands.rst @@ -2,8 +2,6 @@ Plugin Commands =============== -Note: To see the complete syntax for the plugin commands, see the `CLI_Ref`_ - .. list-plugins:: openstack.cli.extension aodh @@ -109,5 +107,3 @@ zaqar .. list-plugins:: openstack.messaging.v1 :detailed: - -.. _CLI_Ref: http://docs.openstack.org/cli-reference/openstack.html From f2f12d91f7dd1c1f55bb86b0e0c9acf21820612e Mon Sep 17 00:00:00 2001 From: zheng yin Date: Sun, 25 Sep 2016 15:46:18 +0800 Subject: [PATCH 1260/3095] remove square backet to required parameter Required parameter don't need to square backet, therefore, I remove them. Change-Id: I36517fb782da53b3b973f70f292f25a4f6784077 --- doc/source/command-objects/container.rst | 6 +++--- doc/source/command-objects/object.rst | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/source/command-objects/container.rst b/doc/source/command-objects/container.rst index 130a6b3dad..adcf0961da 100644 --- a/doc/source/command-objects/container.rst +++ b/doc/source/command-objects/container.rst @@ -104,7 +104,7 @@ Set container properties os container set [--property [...] ] - [] + .. option:: --property @@ -123,7 +123,7 @@ Display container details .. code:: bash os container show - [] + .. describe:: @@ -139,7 +139,7 @@ Unset container properties os container unset [--property ] - [] + .. option:: --property diff --git a/doc/source/command-objects/object.rst b/doc/source/command-objects/object.rst index 5aaad8a526..3c8333abec 100644 --- a/doc/source/command-objects/object.rst +++ b/doc/source/command-objects/object.rst @@ -60,7 +60,7 @@ List objects [--limit ] [--long] [--all] - ] + .. option:: --prefix @@ -104,8 +104,8 @@ Save object locally os object save [--file ] - [] - [] + + .. option:: --file @@ -130,7 +130,7 @@ Set object properties os object set [--property [...] ] - [] + .. option:: --property @@ -175,7 +175,7 @@ Unset object properties os object unset [--property ] - [] + .. option:: --property From 567ba7bb203d2ed7f5c6c1bf00548e8413a23a80 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sun, 25 Sep 2016 10:02:35 +0000 Subject: [PATCH 1261/3095] Updated from global requirements Change-Id: Id079bf205bf61f3d26dfec526c98718fe05874f6 --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index c13ebe5da3..adcd1927e7 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -27,7 +27,7 @@ aodhclient>=0.5.0 # Apache-2.0 python-barbicanclient>=4.0.0 # Apache-2.0 python-congressclient<2000,>=1.3.0 # Apache-2.0 python-designateclient>=1.5.0 # Apache-2.0 -python-heatclient>=1.4.0 # Apache-2.0 +python-heatclient>=1.5.0 # Apache-2.0 python-ironicclient>=1.6.0 # Apache-2.0 python-ironic-inspector-client>=1.5.0 # Apache-2.0 python-mistralclient>=2.0.0 # Apache-2.0 From 4fd00f04f2093a99c955007d1263b84f1e66b2be Mon Sep 17 00:00:00 2001 From: "guangpei.liu" Date: Sun, 25 Sep 2016 16:33:49 +0800 Subject: [PATCH 1262/3095] Align '=' for developing.rst and newton.rst Change-Id: I2d9b4cbaf65a0fa1c3184f52540b90d32b906fa3 --- doc/source/developing.rst | 4 ++-- releasenotes/source/newton.rst | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/source/developing.rst b/doc/source/developing.rst index c5092cc075..2981ba4d16 100644 --- a/doc/source/developing.rst +++ b/doc/source/developing.rst @@ -6,7 +6,7 @@ Communication ------------- Meetings -========= +======== The OpenStackClient team meets regularly on every Thursday. For details please refer to the `OpenStack IRC meetings`_ page. @@ -16,7 +16,7 @@ Testing ------- Tox prerequisites and installation -=================================== +================================== Install the prerequisites for Tox: diff --git a/releasenotes/source/newton.rst b/releasenotes/source/newton.rst index 97036ed251..be218598f4 100644 --- a/releasenotes/source/newton.rst +++ b/releasenotes/source/newton.rst @@ -1,6 +1,6 @@ -=================================== +============================ Newton Series Release Notes -=================================== +============================ .. release-notes:: :branch: origin/stable/newton From 3ebc7520a6ecdf6260757daf7ad913a1bff4e342 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Sun, 25 Sep 2016 15:44:03 +0800 Subject: [PATCH 1263/3095] Add warning message for --state option of set command in volume There are some set commands can set object state, it maybe a danger behavor for users, so add explanation and warning in the help message of the "--state" option to talk users be caution when using (cinderclient have done this too) Change-Id: I6a902887ea98879999c9972f36b1b7ef332173c3 --- doc/source/command-objects/snapshot.rst | 5 +++-- doc/source/command-objects/volume-backup.rst | 2 ++ doc/source/command-objects/volume.rst | 4 +++- openstackclient/volume/v2/backup.py | 5 ++++- openstackclient/volume/v2/snapshot.py | 7 +++++-- openstackclient/volume/v2/volume.py | 5 ++++- 6 files changed, 21 insertions(+), 7 deletions(-) diff --git a/doc/source/command-objects/snapshot.rst b/doc/source/command-objects/snapshot.rst index ce2d9c8eab..a2709adbe5 100644 --- a/doc/source/command-objects/snapshot.rst +++ b/doc/source/command-objects/snapshot.rst @@ -123,8 +123,9 @@ Set snapshot properties .. option:: --state New snapshot state. - Valid values are "available", "error", "creating", - "deleting", and "error_deleting". + ("available", "error", "creating", "deleting", or "error_deleting") (admin only) + (This option simply changes the state of the snapshot in the database with + no regard to actual status, exercise caution when using) *Volume version 2 only* diff --git a/doc/source/command-objects/volume-backup.rst b/doc/source/command-objects/volume-backup.rst index 05b02345ce..246fd38eb2 100644 --- a/doc/source/command-objects/volume-backup.rst +++ b/doc/source/command-objects/volume-backup.rst @@ -140,6 +140,8 @@ Set volume backup properties .. option:: --state New backup state ("available" or "error") (admin only) + (This option simply changes the state of the backup in the database with + no regard to actual status, exercise caution when using) .. _backup_set-volume-backup: .. describe:: diff --git a/doc/source/command-objects/volume.rst b/doc/source/command-objects/volume.rst index 26d4ead6f0..f772557fc6 100644 --- a/doc/source/command-objects/volume.rst +++ b/doc/source/command-objects/volume.rst @@ -235,7 +235,9 @@ Set volume properties New volume state ("available", "error", "creating", "deleting", "in-use", - "attaching", "detaching", "error_deleting" or "maintenance") + "attaching", "detaching", "error_deleting" or "maintenance") (admin only) + (This option simply changes the state of the volume in the database with + no regard to actual status, exercise caution when using) *Volume version 2 only* diff --git a/openstackclient/volume/v2/backup.py b/openstackclient/volume/v2/backup.py index 4a133d3644..2ca35b24b2 100644 --- a/openstackclient/volume/v2/backup.py +++ b/openstackclient/volume/v2/backup.py @@ -305,7 +305,10 @@ def get_parser(self, prog_name): '--state', metavar='', choices=['available', 'error'], - help=_('New backup state ("available" or "error") (admin only)'), + help=_('New backup state ("available" or "error") (admin only) ' + '(This option simply changes the state of the backup ' + 'in the database with no regard to actual status, ' + 'exercise caution when using)'), ) return parser diff --git a/openstackclient/volume/v2/snapshot.py b/openstackclient/volume/v2/snapshot.py index 0d82655108..4994e0da3c 100644 --- a/openstackclient/volume/v2/snapshot.py +++ b/openstackclient/volume/v2/snapshot.py @@ -230,8 +230,11 @@ def get_parser(self, prog_name): metavar='', choices=['available', 'error', 'creating', 'deleting', 'error-deleting'], - help=_('New snapshot state. Valid values are available, ' - 'error, creating, deleting, and error-deleting.'), + help=_('New snapshot state. ("available", "error", "creating", ' + '"deleting", or "error_deleting") (admin only) ' + '(This option simply changes the state of the snapshot ' + 'in the database with no regard to actual status, ' + 'exercise caution when using)'), ) return parser diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index 0805b2be67..b006718996 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -402,7 +402,10 @@ def get_parser(self, prog_name): 'maintenance'], help=_('New volume state ("available", "error", "creating", ' '"deleting", "in-use", "attaching", "detaching", ' - '"error_deleting" or "maintenance")'), + '"error_deleting" or "maintenance") (admin only) ' + '(This option simply changes the state of the volume ' + 'in the database with no regard to actual status, ' + 'exercise caution when using)'), ) bootable_group = parser.add_mutually_exclusive_group() bootable_group.add_argument( From 5cc3efb004b3d9e9c447524f556946b4ce9c2195 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Mon, 26 Sep 2016 04:27:11 +0000 Subject: [PATCH 1264/3095] Updated from global requirements Change-Id: I9989b7e0b6aa2062283451831d74c7852701acd2 --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index adcd1927e7..0db070174a 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -10,7 +10,7 @@ oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 reno>=1.8.0 # Apache2 requests>=2.10.0 # Apache-2.0 -requests-mock>=1.0 # Apache-2.0 +requests-mock>=1.1 # Apache-2.0 sphinx!=1.3b1,<1.3,>=1.2.1 # BSD stevedore>=1.16.0 # Apache-2.0 os-client-config!=1.19.0,!=1.19.1,!=1.20.0,!=1.20.1,!=1.21.0,>=1.13.1 # Apache-2.0 From 34eba23c7a843dfa571541d26124c757d1ffc14f Mon Sep 17 00:00:00 2001 From: zheng yin Date: Mon, 26 Sep 2016 15:43:25 +0800 Subject: [PATCH 1265/3095] Add command option parameter in console-url.rst There are options (--rdp,--serial,--mks) in console-url.rst, but it has no these options after "os console url show", therefore, I add them. Change-Id: I4fd6b14f37cb5751021b8d33d4a65dae17e4e4e5 --- doc/source/command-objects/console-url.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/command-objects/console-url.rst b/doc/source/command-objects/console-url.rst index 166d0a9edd..08c8333248 100644 --- a/doc/source/command-objects/console-url.rst +++ b/doc/source/command-objects/console-url.rst @@ -16,6 +16,7 @@ Show server's remote console URL os console url show [--novnc | --xvpvnc | --spice] + [--rdp | --serial | --mks] .. option:: --novnc From 9912fdd7ff037462d295e77ac58aadc3d7f8f972 Mon Sep 17 00:00:00 2001 From: zheng yin Date: Fri, 23 Sep 2016 19:42:40 +0800 Subject: [PATCH 1266/3095] Add default limit for container/object Default container name length less than or equal to 256 in link[1], as the same time,default object name length less than or equal to 1024 in link[2]. Thereforce, I check the length of container and object in take_action. and if it's greater than 256/1024 I warn the user. [1] https://github.com/openstack/swift/blob/master/swift/common/constraints.py#L39 [2] https://github.com/openstack/swift/blob/master/swift/common/constraints.py#L35 Change-Id: I304b77cbc464eaba041321654cc29248cbe4b9a6 --- openstackclient/object/v1/container.py | 11 +++++++++++ openstackclient/object/v1/object.py | 10 ++++++++++ 2 files changed, 21 insertions(+) diff --git a/openstackclient/object/v1/container.py b/openstackclient/object/v1/container.py index f00cc15093..2f0d4ac2e0 100644 --- a/openstackclient/object/v1/container.py +++ b/openstackclient/object/v1/container.py @@ -15,11 +15,18 @@ """Container v1 action implementations""" +import logging + from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import utils import six +from openstackclient.i18n import _ + + +LOG = logging.getLogger(__name__) + class CreateContainer(command.Lister): """Create new container""" @@ -38,6 +45,10 @@ def take_action(self, parsed_args): results = [] for container in parsed_args.containers: + if len(container) > 256: + LOG.warning( + _('Container name is %s characters long, the default limit' + ' is 256'), len(container)) data = self.app.client_manager.object_store.container_create( container=container, ) diff --git a/openstackclient/object/v1/object.py b/openstackclient/object/v1/object.py index 39dba3d5c7..db61d638e4 100644 --- a/openstackclient/object/v1/object.py +++ b/openstackclient/object/v1/object.py @@ -15,12 +15,18 @@ """Object v1 action implementations""" +import logging from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import utils import six +from openstackclient.i18n import _ + + +LOG = logging.getLogger(__name__) + class CreateObject(command.Lister): """Upload object to container""" @@ -44,6 +50,10 @@ def take_action(self, parsed_args): results = [] for obj in parsed_args.objects: + if len(obj) > 1024: + LOG.warning( + _('Object name is %s characters long, default limit' + ' is 1024'), len(obj)) data = self.app.client_manager.object_store.object_create( container=parsed_args.container, object=obj, From 78312ca9afea22f6511f2421dccb0736f394e9c8 Mon Sep 17 00:00:00 2001 From: Rajasi Kulkarni Date: Mon, 22 Aug 2016 00:26:46 +0530 Subject: [PATCH 1267/3095] Add option "--name" to command "openstack object create" Option "--name" can be used to set as the object name of the file to be uploaded in the container. Similar to option "--object-name" in command "swift upload". Added unit test case to ensure an exception is raised when using option "--name" for uploading multiple objects. Change-Id: Ied7827841f6ca1cf9d4b48e304cbe5d62eda38ab Closes-Bug: #1607972 --- doc/source/command-objects/object.rst | 5 +++++ openstackclient/api/object_store_v1.py | 11 ++++++++-- openstackclient/object/v1/object.py | 14 +++++++++++- openstackclient/tests/unit/object/v1/fakes.py | 2 ++ .../tests/unit/object/v1/test_object_all.py | 22 +++++++++++++++++++ .../notes/bug-1607972-a910a9fbdb81da57.yaml | 5 +++++ 6 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/bug-1607972-a910a9fbdb81da57.yaml diff --git a/doc/source/command-objects/object.rst b/doc/source/command-objects/object.rst index 5aaad8a526..11dfd30e98 100644 --- a/doc/source/command-objects/object.rst +++ b/doc/source/command-objects/object.rst @@ -13,9 +13,14 @@ Upload object to container .. code:: bash os object create + [--name ] [ ...] +.. option:: --name + + Upload a file and rename it. Can only be used when uploading a single object + .. describe:: Container for new object diff --git a/openstackclient/api/object_store_v1.py b/openstackclient/api/object_store_v1.py index faa55118e9..184814c610 100644 --- a/openstackclient/api/object_store_v1.py +++ b/openstackclient/api/object_store_v1.py @@ -214,6 +214,7 @@ def object_create( self, container=None, object=None, + name=None, ): """Create an object inside a container @@ -221,6 +222,8 @@ def object_create( name of container to store object :param string object: local path to object + :param string name: + name of object to create :returns: dict of returned headers """ @@ -229,8 +232,12 @@ def object_create( # TODO(dtroyer): What exception to raise here? return {} + # For uploading a file, if name is provided then set it as the + # object's name in the container. + object_name_str = name if name else object + full_url = "%s/%s" % (urllib.parse.quote(container), - urllib.parse.quote(object)) + urllib.parse.quote(object_name_str)) with io.open(object, 'rb') as f: response = self.create( full_url, @@ -240,7 +247,7 @@ def object_create( data = { 'account': self._find_account_id(), 'container': container, - 'object': object, + 'object': object_name_str, 'x-trans-id': response.headers.get('X-Trans-Id'), 'etag': response.headers.get('Etag'), } diff --git a/openstackclient/object/v1/object.py b/openstackclient/object/v1/object.py index db61d638e4..88f6815e80 100644 --- a/openstackclient/object/v1/object.py +++ b/openstackclient/object/v1/object.py @@ -19,6 +19,7 @@ from osc_lib.cli import parseractions from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils import six @@ -44,10 +45,20 @@ def get_parser(self, prog_name): nargs="+", help='Local filename(s) to upload', ) + parser.add_argument( + '--name', + metavar='', + help='Upload a file and rename it. ' + 'Can only be used when uploading a single object' + ) return parser def take_action(self, parsed_args): - + if parsed_args.name: + if len(parsed_args.objects) > 1: + msg = _('Attempting to upload multiple objects and ' + 'using --name is not permitted') + raise exceptions.CommandError(msg) results = [] for obj in parsed_args.objects: if len(obj) > 1024: @@ -57,6 +68,7 @@ def take_action(self, parsed_args): data = self.app.client_manager.object_store.object_create( container=parsed_args.container, object=obj, + name=parsed_args.name, ) results.append(data) diff --git a/openstackclient/tests/unit/object/v1/fakes.py b/openstackclient/tests/unit/object/v1/fakes.py index 0ff594bce0..72646d25bb 100644 --- a/openstackclient/tests/unit/object/v1/fakes.py +++ b/openstackclient/tests/unit/object/v1/fakes.py @@ -75,6 +75,8 @@ 'last_modified': object_modified_2, } +object_upload_name = 'test-object-name' + class TestObjectv1(utils.TestCommand): diff --git a/openstackclient/tests/unit/object/v1/test_object_all.py b/openstackclient/tests/unit/object/v1/test_object_all.py index a0948b1bcf..f215836ed0 100644 --- a/openstackclient/tests/unit/object/v1/test_object_all.py +++ b/openstackclient/tests/unit/object/v1/test_object_all.py @@ -13,6 +13,7 @@ import copy +from osc_lib import exceptions from requests_mock.contrib import fixture from openstackclient.object.v1 import object as object_cmds @@ -35,6 +36,27 @@ def setUp(self): # Get the command object to test self.cmd = object_cmds.CreateObject(self.app, None) + def test_multiple_object_create_with_object_name(self): + arglist = [ + object_fakes.container_name, + object_fakes.object_name_1, + object_fakes.object_name_2, + '--name', object_fakes.object_upload_name, + ] + + verifylist = [ + ('container', object_fakes.container_name), + ('objects', [object_fakes.object_name_1, + object_fakes.object_name_2]), + ('name', object_fakes.object_upload_name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, + parsed_args) + class TestObjectList(TestObjectAll): diff --git a/releasenotes/notes/bug-1607972-a910a9fbdb81da57.yaml b/releasenotes/notes/bug-1607972-a910a9fbdb81da57.yaml new file mode 100644 index 0000000000..0e3872ea0e --- /dev/null +++ b/releasenotes/notes/bug-1607972-a910a9fbdb81da57.yaml @@ -0,0 +1,5 @@ +--- +features: + - Add ``--name`` option to command ``object create`` + for uploading a file and renaming it. + [Bug `1607972 `_] From 28b66459dba0357e91e8f0035db04f817b2272c2 Mon Sep 17 00:00:00 2001 From: Rajasi Kulkarni Date: Tue, 20 Sep 2016 20:10:09 +0530 Subject: [PATCH 1268/3095] Add translation markers for object commands None of the help messages for the object commands are marked for translation. This patch adds the necessary support. Co-Authored-By: Steve Martinelli Change-Id: Ibf472d8f7d5ab6c876f60cddcab8833b28f042e0 --- openstackclient/object/v1/account.py | 10 +++-- openstackclient/object/v1/container.py | 34 ++++++++-------- openstackclient/object/v1/object.py | 54 +++++++++++++------------- 3 files changed, 50 insertions(+), 48 deletions(-) diff --git a/openstackclient/object/v1/account.py b/openstackclient/object/v1/account.py index 801fe4504a..2fe00ecbc3 100644 --- a/openstackclient/object/v1/account.py +++ b/openstackclient/object/v1/account.py @@ -18,6 +18,8 @@ from osc_lib import utils import six +from openstackclient.i18n import _ + class SetAccount(command.Command): """Set account properties""" @@ -29,8 +31,8 @@ def get_parser(self, prog_name): metavar="", required=True, action=parseractions.KeyValueAction, - help="Set a property on this account " - "(repeat option to set multiple properties)" + help=_("Set a property on this account " + "(repeat option to set multiple properties)") ) return parser @@ -61,8 +63,8 @@ def get_parser(self, prog_name): required=True, action='append', default=[], - help='Property to remove from account ' - '(repeat option to remove multiple properties)', + help=_('Property to remove from account ' + '(repeat option to remove multiple properties)'), ) return parser diff --git a/openstackclient/object/v1/container.py b/openstackclient/object/v1/container.py index 2f0d4ac2e0..01964d0c0b 100644 --- a/openstackclient/object/v1/container.py +++ b/openstackclient/object/v1/container.py @@ -37,7 +37,7 @@ def get_parser(self, prog_name): 'containers', metavar='', nargs="+", - help='New container name(s)', + help=_('New container name(s)'), ) return parser @@ -71,13 +71,13 @@ def get_parser(self, prog_name): '--recursive', '-r', action='store_true', default=False, - help='Recursively delete objects and container', + help=_('Recursively delete objects and container'), ) parser.add_argument( 'containers', metavar='', nargs="+", - help='Container(s) to delete', + help=_('Container(s) to delete'), ) return parser @@ -105,35 +105,35 @@ def get_parser(self, prog_name): parser.add_argument( "--prefix", metavar="", - help="Filter list using ", + help=_("Filter list using "), ) parser.add_argument( "--marker", metavar="", - help="Anchor for paging", + help=_("Anchor for paging"), ) parser.add_argument( "--end-marker", metavar="", - help="End anchor for paging", + help=_("End anchor for paging"), ) parser.add_argument( "--limit", metavar="", type=int, - help="Limit the number of containers returned", + help=_("Limit the number of containers returned"), ) parser.add_argument( '--long', action='store_true', default=False, - help='List additional fields in output', + help=_('List additional fields in output'), ) parser.add_argument( '--all', action='store_true', default=False, - help='List all containers (default is 10000)', + help=_('List all containers (default is 10000)'), ) return parser @@ -175,7 +175,7 @@ def get_parser(self, prog_name): parser.add_argument( 'container', metavar='', - help='Container to save', + help=_('Container to save'), ) return parser @@ -193,15 +193,15 @@ def get_parser(self, prog_name): parser.add_argument( 'container', metavar='', - help='Container to modify', + help=_('Container to modify'), ) parser.add_argument( "--property", metavar="", required=True, action=parseractions.KeyValueAction, - help="Set a property on this container " - "(repeat option to set multiple properties)" + help=_("Set a property on this container " + "(repeat option to set multiple properties)") ) return parser @@ -220,7 +220,7 @@ def get_parser(self, prog_name): parser.add_argument( 'container', metavar='', - help='Container to display', + help=_('Container to display'), ) return parser @@ -243,7 +243,7 @@ def get_parser(self, prog_name): parser.add_argument( 'container', metavar='', - help='Container to modify', + help=_('Container to modify'), ) parser.add_argument( '--property', @@ -251,8 +251,8 @@ def get_parser(self, prog_name): required=True, action='append', default=[], - help='Property to remove from container ' - '(repeat option to remove multiple properties)', + help=_('Property to remove from container ' + '(repeat option to remove multiple properties)'), ) return parser diff --git a/openstackclient/object/v1/object.py b/openstackclient/object/v1/object.py index 88f6815e80..3c47ee0428 100644 --- a/openstackclient/object/v1/object.py +++ b/openstackclient/object/v1/object.py @@ -37,19 +37,19 @@ def get_parser(self, prog_name): parser.add_argument( 'container', metavar='', - help='Container for new object', + help=_('Container for new object'), ) parser.add_argument( 'objects', metavar='', nargs="+", - help='Local filename(s) to upload', + help=_('Local filename(s) to upload'), ) parser.add_argument( '--name', metavar='', - help='Upload a file and rename it. ' - 'Can only be used when uploading a single object' + help=_('Upload a file and rename it. ' + 'Can only be used when uploading a single object') ) return parser @@ -88,13 +88,13 @@ def get_parser(self, prog_name): parser.add_argument( 'container', metavar='', - help='Delete object(s) from ', + help=_('Delete object(s) from '), ) parser.add_argument( 'objects', metavar='', nargs="+", - help='Object(s) to delete', + help=_('Object(s) to delete'), ) return parser @@ -115,45 +115,45 @@ def get_parser(self, prog_name): parser.add_argument( "container", metavar="", - help="Container to list", + help=_("Container to list"), ) parser.add_argument( "--prefix", metavar="", - help="Filter list using ", + help=_("Filter list using "), ) parser.add_argument( "--delimiter", metavar="", - help="Roll up items with ", + help=_("Roll up items with "), ) parser.add_argument( "--marker", metavar="", - help="Anchor for paging", + help=_("Anchor for paging"), ) parser.add_argument( "--end-marker", metavar="", - help="End anchor for paging", + help=_("End anchor for paging"), ) parser.add_argument( "--limit", metavar="", type=int, - help="Limit the number of objects returned", + help=_("Limit the number of objects returned"), ) parser.add_argument( '--long', action='store_true', default=False, - help='List additional fields in output', + help=_('List additional fields in output'), ) parser.add_argument( '--all', action='store_true', default=False, - help='List all objects in container (default is 10000)', + help=_('List all objects in container (default is 10000)'), ) return parser @@ -204,17 +204,17 @@ def get_parser(self, prog_name): parser.add_argument( "--file", metavar="", - help="Destination filename (defaults to object name)", + help=_("Destination filename (defaults to object name)"), ) parser.add_argument( 'container', metavar='', - help='Download from ', + help=_('Download from '), ) parser.add_argument( "object", metavar="", - help="Object to save", + help=_("Object to save"), ) return parser @@ -234,20 +234,20 @@ def get_parser(self, prog_name): parser.add_argument( 'container', metavar='', - help='Modify from ', + help=_('Modify from '), ) parser.add_argument( 'object', metavar='', - help='Object to modify', + help=_('Object to modify'), ) parser.add_argument( "--property", metavar="", required=True, action=parseractions.KeyValueAction, - help="Set a property on this object " - "(repeat option to set multiple properties)" + help=_("Set a property on this object " + "(repeat option to set multiple properties)") ) return parser @@ -267,12 +267,12 @@ def get_parser(self, prog_name): parser.add_argument( 'container', metavar='', - help='Display from ', + help=_('Display from '), ) parser.add_argument( 'object', metavar='', - help='Object to display', + help=_('Object to display'), ) return parser @@ -296,12 +296,12 @@ def get_parser(self, prog_name): parser.add_argument( 'container', metavar='', - help='Modify from ', + help=_('Modify from '), ) parser.add_argument( 'object', metavar='', - help='Object to modify', + help=_('Object to modify'), ) parser.add_argument( '--property', @@ -309,8 +309,8 @@ def get_parser(self, prog_name): required=True, action='append', default=[], - help='Property to remove from object ' - '(repeat option to remove multiple properties)', + help=_('Property to remove from object ' + '(repeat option to remove multiple properties)'), ) return parser From 3e9749149b8a589ce4bc1a4911934336b427eaeb Mon Sep 17 00:00:00 2001 From: Nguyen Phuong An Date: Tue, 27 Sep 2016 09:52:41 +0700 Subject: [PATCH 1269/3095] Replace 'MagicMock' with 'Mock' Change-Id: I35ebb2f0c83ec4300e26a72d9217ed0208f526eb Closes-Bug: #1475722 --- .../tests/unit/network/v2/fakes.py | 22 +++++++++---------- .../unit/network/v2/test_network_segment.py | 4 ++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index d7ebd0bc26..cea0028218 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -122,7 +122,7 @@ def create_address_scopes(attrs=None, count=2): @staticmethod def get_address_scopes(address_scopes=None, count=2): - """Get an iterable MagicMock object with a list of faked address scopes. + """Get an iterable Mock object with a list of faked address scopes. If address scopes list is provided, then initialize the Mock object with the list. Otherwise create one. @@ -331,7 +331,7 @@ def create_networks(attrs=None, count=2): @staticmethod def get_networks(networks=None, count=2): - """Get an iterable MagicMock object with a list of faked networks. + """Get an iterable Mock object with a list of faked networks. If networks list is provided, then initialize the Mock object with the list. Otherwise create one. @@ -478,7 +478,7 @@ def create_ports(attrs=None, count=2): @staticmethod def get_ports(ports=None, count=2): - """Get an iterable MagicMock object with a list of faked ports. + """Get an iterable Mock object with a list of faked ports. If ports list is provided, then initialize the Mock object with the list. Otherwise create one. @@ -545,7 +545,7 @@ def create_network_agents(attrs=None, count=2): @staticmethod def get_network_agents(agents=None, count=2): - """Get an iterable MagicMock object with a list of faked network agents. + """Get an iterable Mock object with a list of faked network agents. If network agents list is provided, then initialize the Mock object with the list. Otherwise create one. @@ -615,7 +615,7 @@ def create_network_rbacs(attrs=None, count=2): @staticmethod def get_network_rbacs(rbac_policies=None, count=2): - """Get an iterable MagicMock object with a list of faked rbac policies. + """Get an iterable Mock object with a list of faked rbac policies. If rbac policies list is provided, then initialize the Mock object with the list. Otherwise create one. @@ -694,7 +694,7 @@ def create_routers(attrs=None, count=2): @staticmethod def get_routers(routers=None, count=2): - """Get an iterable MagicMock object with a list of faked routers. + """Get an iterable Mock object with a list of faked routers. If routers list is provided, then initialize the Mock object with the list. Otherwise create one. @@ -767,7 +767,7 @@ def create_security_groups(attrs=None, count=2): @staticmethod def get_security_groups(security_groups=None, count=2): - """Get an iterable MagicMock object with a list of faked security groups. + """Get an iterable Mock object with a list of faked security groups. If security groups list is provided, then initialize the Mock object with the list. Otherwise create one. @@ -845,7 +845,7 @@ def create_security_group_rules(attrs=None, count=2): @staticmethod def get_security_group_rules(security_group_rules=None, count=2): - """Get an iterable MagicMock object with a list of faked security group rules. + """Get an iterable Mock object with a list of faked security group rules. If security group rules list is provided, then initialize the Mock object with the list. Otherwise create one. @@ -930,7 +930,7 @@ def create_subnets(attrs=None, count=2): @staticmethod def get_subnets(subnets=None, count=2): - """Get an iterable MagicMock object with a list of faked subnets. + """Get an iterable Mock object with a list of faked subnets. If subnets list is provided, then initialize the Mock object with the list. Otherwise create one. @@ -1008,7 +1008,7 @@ def create_floating_ips(attrs=None, count=2): @staticmethod def get_floating_ips(floating_ips=None, count=2): - """Get an iterable MagicMock object with a list of faked floating ips. + """Get an iterable Mock object with a list of faked floating ips. If floating_ips list is provided, then initialize the Mock object with the list. Otherwise create one. @@ -1091,7 +1091,7 @@ def create_subnet_pools(attrs=None, count=2): @staticmethod def get_subnet_pools(subnet_pools=None, count=2): - """Get an iterable MagicMock object with a list of faked subnet pools. + """Get an iterable Mock object with a list of faked subnet pools. If subnet_pools list is provided, then initialize the Mock object with the list. Otherwise create one. diff --git a/openstackclient/tests/unit/network/v2/test_network_segment.py b/openstackclient/tests/unit/network/v2/test_network_segment.py index 3e755e0730..7457a8c224 100644 --- a/openstackclient/tests/unit/network/v2/test_network_segment.py +++ b/openstackclient/tests/unit/network/v2/test_network_segment.py @@ -182,7 +182,7 @@ def setUp(self): super(TestDeleteNetworkSegment, self).setUp() self.network.delete_segment = mock.Mock(return_value=None) - self.network.find_segment = mock.MagicMock( + self.network.find_segment = mock.Mock( side_effect=self._network_segments ) @@ -251,7 +251,7 @@ def test_delete_multiple_with_exception(self): find_mock_result = [self._network_segments[0], exceptions.CommandError] self.network.find_segment = ( - mock.MagicMock(side_effect=find_mock_result) + mock.Mock(side_effect=find_mock_result) ) try: From c4ab086195f65e128ff175758cb38909a795e2a1 Mon Sep 17 00:00:00 2001 From: zheng yin Date: Tue, 27 Sep 2016 11:21:08 +0800 Subject: [PATCH 1270/3095] fix doc information in the limit.rst I check files in compute/v2, and I find there is a mistake, then I fix it. Change-Id: Ic17b9d5e64e4ed8b1d8e1476c73dd6d9365167fc --- doc/source/command-objects/limits.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/source/command-objects/limits.rst b/doc/source/command-objects/limits.rst index 87a5b33c98..ef4171ac7d 100644 --- a/doc/source/command-objects/limits.rst +++ b/doc/source/command-objects/limits.rst @@ -15,7 +15,8 @@ Show compute and block storage limits .. code:: bash os limits show - --absolute [--reserved] | --rate + --absolute | --rate + [--reserved] [--project ] [--domain ] From 7d2d9fd84ce2b73b739bb6e9616bd41d58ca449a Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Tue, 27 Sep 2016 10:07:38 +0000 Subject: [PATCH 1271/3095] Updated from global requirements Change-Id: Idd9c9a1c114001a3628ec69098495f9875537ec2 --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 0db070174a..bf2ffb9147 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -6,7 +6,7 @@ hacking<0.11,>=0.10.0 coverage>=3.6 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD mock>=2.0 # BSD -oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0 +oslosphinx>=4.7.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 reno>=1.8.0 # Apache2 requests>=2.10.0 # Apache-2.0 From 8d63b8b263ca4011761b062331d53d9b53b5031d Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Wed, 17 Aug 2016 16:31:40 +0800 Subject: [PATCH 1272/3095] Implement "consistency group list" command Add "consistency group" object in volume v2 (v2 only) and implement "consistency group list" command Change-Id: Ifa90d97f4b223f9a5b00708cff07fa2c5e2635f0 Implements: bp cinder-command-support Partial-Bug: #1613964 Co-Authored-By: Sheel Rana --- .../command-objects/consistency-group.rst | 26 ++++ doc/source/commands.rst | 1 + openstackclient/tests/unit/volume/v2/fakes.py | 55 ++++++++ .../unit/volume/v2/test_consistency_group.py | 122 ++++++++++++++++++ .../volume/v2/consistency_group.py | 57 ++++++++ .../notes/bug-1613964-e5760f4825f1e043.yaml | 4 + setup.cfg | 2 + 7 files changed, 267 insertions(+) create mode 100644 doc/source/command-objects/consistency-group.rst create mode 100644 openstackclient/tests/unit/volume/v2/test_consistency_group.py create mode 100644 openstackclient/volume/v2/consistency_group.py create mode 100644 releasenotes/notes/bug-1613964-e5760f4825f1e043.yaml diff --git a/doc/source/command-objects/consistency-group.rst b/doc/source/command-objects/consistency-group.rst new file mode 100644 index 0000000000..f24df0d11c --- /dev/null +++ b/doc/source/command-objects/consistency-group.rst @@ -0,0 +1,26 @@ +================= +consistency group +================= + +Block Storage v2 + +consistency group list +---------------------- + +List consistency groups. + +.. program:: consistency group list +.. code:: bash + + os consistency group list + [--all-projects] + [--long] + +.. option:: --all-projects + + Show detail for all projects. Admin only. + (defaults to False) + +.. option:: --long + + List additional fields in output diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 023be8791d..bf2c322a21 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -80,6 +80,7 @@ referring to both Compute and Volume quotas. * ``compute agent``: (**Compute**) a cloud Compute agent available to a hypervisor * ``compute service``: (**Compute**) a cloud Compute process running on a host * ``configuration``: (**Internal**) OpenStack client configuration +* ``consistency group``: (**Volume**) a consistency group of volumes * ``console log``: (**Compute**) server console text dump * ``console url``: (**Compute**) server remote console URL * ``consumer``: (**Identity**) OAuth-based delegatee diff --git a/openstackclient/tests/unit/volume/v2/fakes.py b/openstackclient/tests/unit/volume/v2/fakes.py index 2aeea60ab5..5e1d16e1db 100644 --- a/openstackclient/tests/unit/volume/v2/fakes.py +++ b/openstackclient/tests/unit/volume/v2/fakes.py @@ -222,6 +222,8 @@ def __init__(self, **kwargs): self.quotas.resource_class = fakes.FakeResource(None, {}) self.quota_classes = mock.Mock() self.quota_classes.resource_class = fakes.FakeResource(None, {}) + self.consistencygroups = mock.Mock() + self.consistencygroups.resource_class = fakes.FakeResource(None, {}) self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] @@ -493,6 +495,59 @@ def get_backups(backups=None, count=2): return mock.Mock(side_effect=backups) +class FakeConsistencyGroup(object): + """Fake one or more consistency group.""" + + @staticmethod + def create_one_consistency_group(attrs=None): + """Create a fake consistency group. + + :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_info = { + "id": 'backup-id-' + uuid.uuid4().hex, + "name": 'backup-name-' + uuid.uuid4().hex, + "description": 'description-' + uuid.uuid4().hex, + "status": "error", + "availability_zone": 'zone' + uuid.uuid4().hex, + "created_at": 'time-' + uuid.uuid4().hex, + "volume_types": ['volume-type1'], + } + + # Overwrite default attributes. + consistency_group_info.update(attrs) + + consistency_group = fakes.FakeResource( + info=copy.deepcopy(consistency_group_info), + loaded=True) + return consistency_group + + @staticmethod + def create_consistency_groups(attrs=None, count=2): + """Create multiple fake consistency groups. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of consistency groups to fake + :return: + A list of FakeResource objects faking the consistency groups + """ + consistency_groups = [] + for i in range(0, count): + consistency_group = ( + FakeConsistencyGroup.create_one_consistency_group(attrs)) + consistency_groups.append(consistency_group) + + return consistency_groups + + 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 new file mode 100644 index 0000000000..00e1b60e88 --- /dev/null +++ b/openstackclient/tests/unit/volume/v2/test_consistency_group.py @@ -0,0 +1,122 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +from osc_lib import utils + +from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes +from openstackclient.volume.v2 import consistency_group + + +class TestConsistencyGroup(volume_fakes.TestVolume): + + def setUp(self): + super(TestConsistencyGroup, self).setUp() + + # Get a shortcut to the TransferManager Mock + self.consistencygroups_mock = ( + self.app.client_manager.volume.consistencygroups) + self.consistencygroups_mock.reset_mock() + + +class TestConsistencyGroupList(TestConsistencyGroup): + + consistency_groups = ( + volume_fakes.FakeConsistencyGroup.create_consistency_groups(count=2)) + + columns = [ + 'ID', + 'Status', + 'Name', + ] + columns_long = [ + 'ID', + 'Status', + 'Availability Zone', + 'Name', + 'Description', + 'Volume Types', + ] + data = [] + for c in consistency_groups: + data.append(( + c.id, + c.status, + c.name, + )) + data_long = [] + for c in consistency_groups: + data_long.append(( + c.id, + c.status, + c.availability_zone, + c.name, + c.description, + utils.format_list(c.volume_types) + )) + + def setUp(self): + super(TestConsistencyGroupList, self).setUp() + + self.consistencygroups_mock.list.return_value = self.consistency_groups + # Get the command to test + self.cmd = consistency_group.ListConsistencyGroup(self.app, None) + + def test_consistency_group_list_without_options(self): + arglist = [] + verifylist = [ + ("all_projects", False), + ("long", False), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.consistencygroups_mock.list.assert_called_once_with( + detailed=True, search_opts={'all_tenants': False}) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_consistency_group_list_with_all_project(self): + arglist = [ + "--all-projects" + ] + verifylist = [ + ("all_projects", True), + ("long", False), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.consistencygroups_mock.list.assert_called_once_with( + detailed=True, search_opts={'all_tenants': True}) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_consistency_group_list_with_long(self): + arglist = [ + "--long", + ] + verifylist = [ + ("all_projects", False), + ("long", True), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.consistencygroups_mock.list.assert_called_once_with( + detailed=True, search_opts={'all_tenants': False}) + self.assertEqual(self.columns_long, columns) + self.assertEqual(self.data_long, list(data)) diff --git a/openstackclient/volume/v2/consistency_group.py b/openstackclient/volume/v2/consistency_group.py new file mode 100644 index 0000000000..39f2d57726 --- /dev/null +++ b/openstackclient/volume/v2/consistency_group.py @@ -0,0 +1,57 @@ +# +# 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 action implementations""" + +from osc_lib.command import command +from osc_lib import utils + +from openstackclient.i18n import _ + + +class ListConsistencyGroup(command.Lister): + """List consistency groups.""" + + def get_parser(self, prog_name): + parser = super(ListConsistencyGroup, 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') + ) + return parser + + def take_action(self, parsed_args): + if parsed_args.long: + columns = ['ID', 'Status', 'Availability Zone', + 'Name', 'Description', 'Volume Types'] + else: + columns = ['ID', 'Status', 'Name'] + volume_client = self.app.client_manager.volume + consistency_groups = volume_client.consistencygroups.list( + detailed=True, + search_opts={'all_tenants': parsed_args.all_projects} + ) + + return (columns, ( + utils.get_item_properties( + s, columns, + formatters={'Volume Types': utils.format_list}) + for s in consistency_groups)) diff --git a/releasenotes/notes/bug-1613964-e5760f4825f1e043.yaml b/releasenotes/notes/bug-1613964-e5760f4825f1e043.yaml new file mode 100644 index 0000000000..53738e273c --- /dev/null +++ b/releasenotes/notes/bug-1613964-e5760f4825f1e043.yaml @@ -0,0 +1,4 @@ +--- +features: + - Add ``consistency group list`` command in volume v2. + [Bug `1613964 `_] diff --git a/setup.cfg b/setup.cfg index 574d67d189..2aa8874019 100644 --- a/setup.cfg +++ b/setup.cfg @@ -499,6 +499,8 @@ openstack.volume.v2 = backup_restore = openstackclient.volume.v2.backup:RestoreBackup backup_show = openstackclient.volume.v2.backup:ShowBackup + consistency_group_list = openstackclient.volume.v2.consistency_group:ListConsistencyGroup + snapshot_create = openstackclient.volume.v2.snapshot:CreateSnapshot snapshot_delete = openstackclient.volume.v2.snapshot:DeleteSnapshot snapshot_list = openstackclient.volume.v2.snapshot:ListSnapshot From c9e0c01f67a00e63bb5d8b5781d7e8e87b39136c Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Tue, 27 Sep 2016 12:27:05 +0800 Subject: [PATCH 1273/3095] Add and modify options for "volume create" command 1.Add mutually exclusive options into a mutually exclusive group. 2.Add "--source-replicated", "--consistency-group", "--hint" and "multi-attach" options 3.Make --size option to be optional under some cases Closes-Bug: #1568005 Closes-Bug: #1627913 Implements: bp implement-cinder-features Co-Authored-By: Roman Vasilets Change-Id: I2c4c3073195d33774e477f4d7f22e383b14b41dd --- doc/source/command-objects/volume.rst | 31 ++++- .../tests/unit/volume/v1/test_volume.py | 62 +++++++++ .../tests/unit/volume/v2/test_volume.py | 131 ++++++++++++++++-- openstackclient/volume/v1/volume.py | 30 ++-- openstackclient/volume/v2/volume.py | 67 ++++++++- .../notes/bug-1627913-2adf4182977e5926.yaml | 5 + 6 files changed, 296 insertions(+), 30 deletions(-) create mode 100644 releasenotes/notes/bug-1627913-2adf4182977e5926.yaml diff --git a/doc/source/command-objects/volume.rst b/doc/source/command-objects/volume.rst index f772557fc6..8f1233614c 100644 --- a/doc/source/command-objects/volume.rst +++ b/doc/source/command-objects/volume.rst @@ -13,21 +13,23 @@ Create new volume .. code:: bash os volume create - --size + [--size ] [--type ] - [--image ] - [--snapshot ] - [--source ] + [--image | --snapshot | --source | --source-replicated ] [--description ] [--user ] [--project ] [--availability-zone ] + [--consistency-group ] [--property [...] ] + [--hint [...] ] + [--multi-attach] -.. option:: --size (required) +.. option:: --size Volume size in GB + (Required unless --snapshot or --source or --source-replicated is specified) .. option:: --type @@ -46,10 +48,14 @@ Create new volume Use :option:`\` as source of volume (name or ID) -.. option:: --source +.. option:: --source Volume to clone (name or ID) +.. option:: --source-replicated + + Replicated volume to clone (name or ID) + .. option:: --description Volume description @@ -66,10 +72,23 @@ Create new volume Create volume in :option:`\` +.. option:: --consistency-group + + Consistency group where the new volume belongs to + .. option:: --property Set a property on this volume (repeat option to set multiple properties) +.. option:: --hint + + Arbitrary scheduler hint key-value pairs to help boot an instance + (repeat option to set multiple hints) + +.. option:: --multi-attach + + Allow volume to be attached more than once (default to False) + .. _volume_create-name: .. describe:: diff --git a/openstackclient/tests/unit/volume/v1/test_volume.py b/openstackclient/tests/unit/volume/v1/test_volume.py index 895f1f876b..73c00844e8 100644 --- a/openstackclient/tests/unit/volume/v1/test_volume.py +++ b/openstackclient/tests/unit/volume/v1/test_volume.py @@ -23,6 +23,7 @@ from openstackclient.tests.unit import fakes from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes +from openstackclient.tests.unit import utils as tests_utils from openstackclient.tests.unit.volume.v1 import fakes as volume_fakes from openstackclient.volume.v1 import volume @@ -411,6 +412,67 @@ def test_volume_create_image_name(self): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, data) + def test_volume_create_with_source(self): + self.volumes_mock.get.return_value = self.new_volume + arglist = [ + '--source', self.new_volume.id, + self.new_volume.display_name, + ] + verifylist = [ + ('source', self.new_volume.id), + ('name', self.new_volume.display_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volumes_mock.create.assert_called_with( + None, + None, + self.new_volume.id, + self.new_volume.display_name, + None, + None, + None, + None, + None, + None, + None, + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) + + def test_volume_create_without_size(self): + arglist = [ + self.new_volume.display_name, + ] + verifylist = [ + ('name', self.new_volume.display_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + + def test_volume_create_with_multi_source(self): + arglist = [ + '--image', 'source_image', + '--source', 'source_volume', + '--snapshot', 'source_snapshot', + '--size', str(self.new_volume.size), + self.new_volume.display_name, + ] + verifylist = [ + ('image', 'source_image'), + ('source', 'source_volume'), + ('snapshot', 'source_snapshot'), + ('size', self.new_volume.size), + ('name', self.new_volume.display_name), + ] + + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + class TestVolumeDelete(TestVolume): diff --git a/openstackclient/tests/unit/volume/v2/test_volume.py b/openstackclient/tests/unit/volume/v2/test_volume.py index 5bdde9deba..f4a7c14283 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume.py +++ b/openstackclient/tests/unit/volume/v2/test_volume.py @@ -21,6 +21,7 @@ from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes from openstackclient.tests.unit.image.v2 import fakes as image_fakes +from openstackclient.tests.unit import utils as tests_utils from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes from openstackclient.volume.v2 import volume @@ -45,6 +46,10 @@ def setUp(self): self.snapshots_mock = self.app.client_manager.volume.volume_snapshots self.snapshots_mock.reset_mock() + self.consistencygroups_mock = ( + self.app.client_manager.volume.consistencygroups) + self.consistencygroups_mock.reset_mock() + def setup_volumes_mock(self, count): volumes = volume_fakes.FakeVolume.create_volumes(count=count) @@ -123,18 +128,28 @@ def test_volume_create_min_options(self): availability_zone=None, metadata=None, imageRef=None, - source_volid=None + source_volid=None, + consistencygroup_id=None, + source_replica=None, + multiattach=False, + scheduler_hints=None, ) self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, data) def test_volume_create_options(self): + consistency_group = ( + volume_fakes.FakeConsistencyGroup.create_one_consistency_group()) + self.consistencygroups_mock.get.return_value = consistency_group arglist = [ '--size', str(self.new_volume.size), '--description', self.new_volume.description, '--type', self.new_volume.volume_type, '--availability-zone', self.new_volume.availability_zone, + '--consistency-group', consistency_group.id, + '--hint', 'k=v', + '--multi-attach', self.new_volume.name, ] verifylist = [ @@ -142,6 +157,9 @@ def test_volume_create_options(self): ('description', self.new_volume.description), ('type', self.new_volume.volume_type), ('availability_zone', self.new_volume.availability_zone), + ('consistency_group', consistency_group.id), + ('hint', {'k': 'v'}), + ('multi_attach', True), ('name', self.new_volume.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -162,7 +180,11 @@ def test_volume_create_options(self): availability_zone=self.new_volume.availability_zone, metadata=None, imageRef=None, - source_volid=None + source_volid=None, + consistencygroup_id=consistency_group.id, + source_replica=None, + multiattach=True, + scheduler_hints={'k': 'v'}, ) self.assertEqual(self.columns, columns) @@ -204,7 +226,11 @@ def test_volume_create_user_project_id(self): availability_zone=None, metadata=None, imageRef=None, - source_volid=None + source_volid=None, + consistencygroup_id=None, + source_replica=None, + multiattach=False, + scheduler_hints=None, ) self.assertEqual(self.columns, columns) @@ -246,7 +272,11 @@ def test_volume_create_user_project_name(self): availability_zone=None, metadata=None, imageRef=None, - source_volid=None + source_volid=None, + consistencygroup_id=None, + source_replica=None, + multiattach=False, + scheduler_hints=None, ) self.assertEqual(self.columns, columns) @@ -282,7 +312,11 @@ def test_volume_create_properties(self): availability_zone=None, metadata={'Alpha': 'a', 'Beta': 'b'}, imageRef=None, - source_volid=None + source_volid=None, + consistencygroup_id=None, + source_replica=None, + multiattach=False, + scheduler_hints=None, ) self.assertEqual(self.columns, columns) @@ -321,6 +355,10 @@ def test_volume_create_image_id(self): metadata=None, imageRef=image.id, source_volid=None, + consistencygroup_id=None, + source_replica=None, + multiattach=False, + scheduler_hints=None, ) self.assertEqual(self.columns, columns) @@ -358,7 +396,11 @@ def test_volume_create_image_name(self): availability_zone=None, metadata=None, imageRef=image.id, - source_volid=None + source_volid=None, + consistencygroup_id=None, + source_replica=None, + multiattach=False, + scheduler_hints=None, ) self.assertEqual(self.columns, columns) @@ -368,12 +410,10 @@ def test_volume_create_with_snapshot(self): snapshot = volume_fakes.FakeSnapshot.create_one_snapshot() self.new_volume.snapshot_id = snapshot.id arglist = [ - '--size', str(self.new_volume.size), '--snapshot', self.new_volume.snapshot_id, self.new_volume.name, ] verifylist = [ - ('size', self.new_volume.size), ('snapshot', self.new_volume.snapshot_id), ('name', self.new_volume.name), ] @@ -387,7 +427,7 @@ def test_volume_create_with_snapshot(self): columns, data = self.cmd.take_action(parsed_args) self.volumes_mock.create.assert_called_once_with( - size=self.new_volume.size, + size=None, snapshot_id=snapshot.id, name=self.new_volume.name, description=None, @@ -397,12 +437,83 @@ def test_volume_create_with_snapshot(self): availability_zone=None, metadata=None, imageRef=None, - source_volid=None + source_volid=None, + consistencygroup_id=None, + source_replica=None, + multiattach=False, + scheduler_hints=None, ) self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, data) + def test_volume_create_with_source_replicated(self): + self.volumes_mock.get.return_value = self.new_volume + arglist = [ + '--source-replicated', self.new_volume.id, + self.new_volume.name, + ] + verifylist = [ + ('source_replicated', self.new_volume.id), + ('name', self.new_volume.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.volumes_mock.create.assert_called_once_with( + size=None, + snapshot_id=None, + name=self.new_volume.name, + description=None, + volume_type=None, + user_id=None, + project_id=None, + availability_zone=None, + metadata=None, + imageRef=None, + source_volid=None, + consistencygroup_id=None, + source_replica=self.new_volume.id, + multiattach=False, + scheduler_hints=None, + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) + + def test_volume_create_without_size(self): + arglist = [ + self.new_volume.name, + ] + verifylist = [ + ('name', self.new_volume.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + + def test_volume_create_with_multi_source(self): + arglist = [ + '--image', 'source_image', + '--source', 'source_volume', + '--snapshot', 'source_snapshot', + '--source-replicated', 'source_replicated_volume', + '--size', str(self.new_volume.size), + self.new_volume.name, + ] + verifylist = [ + ('image', 'source_image'), + ('source', 'source_volume'), + ('snapshot', 'source_snapshot'), + ('source-replicated', 'source_replicated_volume'), + ('size', self.new_volume.size), + ('name', self.new_volume.name), + ] + + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + class TestVolumeDelete(TestVolume): diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index 89fa2014ae..cafe8ce6ab 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -30,6 +30,20 @@ LOG = logging.getLogger(__name__) +def _check_size_arg(args): + """Check whether --size option is required or not. + + Require size parameter only in case when snapshot or source + volume is not specified. + """ + + if ((args.snapshot or args.source) + is None and args.size is None): + msg = _("--size is a required option if snapshot " + "or source volume is not specified.") + raise exceptions.CommandError(msg) + + class CreateVolume(command.ShowOne): """Create new volume""" @@ -43,32 +57,32 @@ def get_parser(self, prog_name): parser.add_argument( '--size', metavar='', - required=True, type=int, - help=_('Volume size in GB'), + help=_("Volume size in GB (Required unless --snapshot or " + "--source is specified)"), ) parser.add_argument( '--type', metavar='', help=_("Set the type of volume"), ) - parser.add_argument( + source_group = parser.add_mutually_exclusive_group() + source_group.add_argument( '--image', metavar='', help=_('Use as source of volume (name or ID)'), ) - snapshot_group = parser.add_mutually_exclusive_group() - snapshot_group.add_argument( + source_group.add_argument( '--snapshot', metavar='', help=_('Use as source of volume (name or ID)'), ) - snapshot_group.add_argument( + source_group.add_argument( '--snapshot-id', metavar='', help=argparse.SUPPRESS, ) - parser.add_argument( + source_group.add_argument( '--source', metavar='', help=_('Volume to clone (name or ID)'), @@ -104,7 +118,7 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - + _check_size_arg(parsed_args) identity_client = self.app.client_manager.identity image_client = self.app.client_manager.image volume_client = self.app.client_manager.volume diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index b006718996..e74051145b 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -30,6 +30,20 @@ LOG = logging.getLogger(__name__) +def _check_size_arg(args): + """Check whether --size option is required or not. + + Require size parameter only in case when snapshot or source + volume is not specified. + """ + + if ((args.snapshot or args.source or args.source_replicated) + is None and args.size is None): + msg = _("--size is a required option if snapshot " + "or source volume is not specified.") + raise exceptions.CommandError(msg) + + class CreateVolume(command.ShowOne): """Create new volume""" @@ -44,29 +58,35 @@ def get_parser(self, prog_name): "--size", metavar="", type=int, - required=True, - help=_("Volume size in GB"), + help=_("Volume size in GB (Required unless --snapshot or " + "--source or --source-replicated is specified)"), ) parser.add_argument( "--type", metavar="", help=_("Set the type of volume"), ) - parser.add_argument( + source_group = parser.add_mutually_exclusive_group() + source_group.add_argument( "--image", metavar="", help=_("Use as source of volume (name or ID)"), ) - parser.add_argument( + source_group.add_argument( "--snapshot", metavar="", help=_("Use as source of volume (name or ID)"), ) - parser.add_argument( + source_group.add_argument( "--source", metavar="", help=_("Volume to clone (name or ID)"), ) + source_group.add_argument( + "--source-replicated", + metavar="", + help=_("Replicated volume to clone (name or ID)"), + ) parser.add_argument( "--description", metavar="", @@ -87,6 +107,11 @@ def get_parser(self, prog_name): metavar="", help=_("Create volume in "), ) + parser.add_argument( + "--consistency-group", + metavar="consistency-group>", + help=_("Consistency group where the new volume belongs to"), + ) parser.add_argument( "--property", metavar="", @@ -94,9 +119,23 @@ def get_parser(self, prog_name): help=_("Set a property to this volume " "(repeat option to set multiple properties)"), ) + parser.add_argument( + "--hint", + metavar="", + action=parseractions.KeyValueAction, + help=_("Arbitrary scheduler hint key-value pairs to help boot " + "an instance (repeat option to set multiple hints)"), + ) + parser.add_argument( + "--multi-attach", + action="store_true", + help=_("Allow volume to be attached more than once " + "(default to False)") + ) return parser def take_action(self, parsed_args): + _check_size_arg(parsed_args) identity_client = self.app.client_manager.identity volume_client = self.app.client_manager.volume image_client = self.app.client_manager.image @@ -107,6 +146,18 @@ def take_action(self, parsed_args): volume_client.volumes, parsed_args.source).id + replicated_source_volume = None + if parsed_args.source_replicated: + replicated_source_volume = utils.find_resource( + volume_client.volumes, + parsed_args.source_replicated).id + + consistency_group = None + if parsed_args.consistency_group: + consistency_group = utils.find_resource( + volume_client.consistencygroups, + parsed_args.consistency_group).id + image = None if parsed_args.image: image = utils.find_resource( @@ -142,7 +193,11 @@ def take_action(self, parsed_args): availability_zone=parsed_args.availability_zone, metadata=parsed_args.property, imageRef=image, - source_volid=source_volume + source_volid=source_volume, + consistencygroup_id=consistency_group, + source_replica=replicated_source_volume, + multiattach=parsed_args.multi_attach, + scheduler_hints=parsed_args.hint, ) # Remove key links from being displayed volume._info.update( diff --git a/releasenotes/notes/bug-1627913-2adf4182977e5926.yaml b/releasenotes/notes/bug-1627913-2adf4182977e5926.yaml new file mode 100644 index 0000000000..454d4c8958 --- /dev/null +++ b/releasenotes/notes/bug-1627913-2adf4182977e5926.yaml @@ -0,0 +1,5 @@ +--- +features: + - Add ``--source-replicated``, ``--consistency-group``, ``--hint`` and + ``--multi-attach`` options to ``volume create`` command in volume v2. + [Bug `1627913 `_] From 256ec66f79f98b41497e8937911604bac7ddeabb Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Wed, 28 Sep 2016 08:16:22 -0500 Subject: [PATCH 1274/3095] Remove beta label for network segment resource With Ocata development opened, neutron network segments are no longer beta resources. Change-Id: I25be51c30df17c746136471b88840f64a03187b9 Partially-Implements: blueprint routed-networks --- .../command-objects/network-segment.rst | 20 ------ doc/source/command-objects/subnet.rst | 6 +- openstackclient/network/v2/network_segment.py | 42 ++----------- openstackclient/network/v2/subnet.py | 15 +++-- .../network/v2/test_network_segment.py | 19 ++---- .../unit/network/v2/test_network_segment.py | 61 ------------------- .../tests/unit/network/v2/test_subnet.py | 18 ------ .../bp-routed-networks-3b502faa5cd96807.yaml | 6 +- 8 files changed, 23 insertions(+), 164 deletions(-) diff --git a/doc/source/command-objects/network-segment.rst b/doc/source/command-objects/network-segment.rst index 242dcbe965..d0434651fd 100644 --- a/doc/source/command-objects/network-segment.rst +++ b/doc/source/command-objects/network-segment.rst @@ -14,10 +14,6 @@ network segment create Create new network segment -.. caution:: This is a beta command and subject to change. - Use global option ``--os-beta-command`` to - enable this command. - .. program:: network segment create .. code:: bash @@ -63,10 +59,6 @@ network segment delete Delete network segment(s) -.. caution:: This is a beta command and subject to change. - Use global option ``--os-beta-command`` to - enable this command. - .. program:: network segment delete .. code:: bash @@ -83,10 +75,6 @@ network segment list List network segments -.. caution:: This is a beta command and subject to change. - Use global option ``--os-beta-command`` to - enable this command. - .. program:: network segment list .. code:: bash @@ -107,10 +95,6 @@ network segment set Set network segment properties -.. caution:: This is a beta command and subject to change. - Use global option ``--os-beta-command`` to - enable this command. - .. program:: network segment set .. code:: bash @@ -137,10 +121,6 @@ network segment show Display network segment details -.. caution:: This is a beta command and subject to change. - Use global option ``--os-beta-command`` to - enable this command. - .. program:: network segment show .. code:: bash diff --git a/doc/source/command-objects/subnet.rst b/doc/source/command-objects/subnet.rst index 00401ddab3..e2059875ba 100644 --- a/doc/source/command-objects/subnet.rst +++ b/doc/source/command-objects/subnet.rst @@ -116,11 +116,7 @@ Create new subnet .. option:: --network-segment - Network segment to associate with this subnet (ID only) - - .. caution:: This is a beta command option and subject - to change. Use global option ``--os-beta-command`` - to enable this command option. + Network segment to associate with this subnet (name or ID) .. option:: --service-type diff --git a/openstackclient/network/v2/network_segment.py b/openstackclient/network/v2/network_segment.py index 34cac0e02e..94722f1e68 100644 --- a/openstackclient/network/v2/network_segment.py +++ b/openstackclient/network/v2/network_segment.py @@ -26,12 +26,7 @@ class CreateNetworkSegment(command.ShowOne): - """Create new network segment - - (Caution: This is a beta command and subject to change. - Use global option --os-beta-command to enable - this command) - """ + """Create new network segment""" def get_parser(self, prog_name): parser = super(CreateNetworkSegment, self).get_parser(prog_name) @@ -76,7 +71,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.validate_os_beta_command_enabled() client = self.app.client_manager.network attrs = {} attrs['name'] = parsed_args.name @@ -96,12 +90,7 @@ def take_action(self, parsed_args): class DeleteNetworkSegment(command.Command): - """Delete network segment(s) - - (Caution: This is a beta command and subject to change. - Use global option --os-beta-command to enable - this command) - """ + """Delete network segment(s)""" def get_parser(self, prog_name): parser = super(DeleteNetworkSegment, self).get_parser(prog_name) @@ -114,7 +103,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.validate_os_beta_command_enabled() client = self.app.client_manager.network result = 0 @@ -137,12 +125,7 @@ def take_action(self, parsed_args): class ListNetworkSegment(command.Lister): - """List network segments - - (Caution: This is a beta command and subject to change. - Use global option --os-beta-command to enable - this command) - """ + """List network segments""" def get_parser(self, prog_name): parser = super(ListNetworkSegment, self).get_parser(prog_name) @@ -161,8 +144,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.validate_os_beta_command_enabled() - network_client = self.app.client_manager.network filters = {} @@ -204,12 +185,7 @@ def take_action(self, parsed_args): class SetNetworkSegment(command.Command): - """Set network segment properties - - (Caution: This is a beta command and subject to change. - Use global option --os-beta-command to enable - this command) - """ + """Set network segment properties""" def get_parser(self, prog_name): parser = super(SetNetworkSegment, self).get_parser(prog_name) @@ -231,7 +207,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.validate_os_beta_command_enabled() client = self.app.client_manager.network obj = client.find_segment(parsed_args.network_segment, ignore_missing=False) @@ -244,12 +219,7 @@ def take_action(self, parsed_args): class ShowNetworkSegment(command.ShowOne): - """Display network segment details - - (Caution: This is a beta command and subject to change. - Use global option --os-beta-command to enable - this command) - """ + """Display network segment details""" def get_parser(self, prog_name): parser = super(ShowNetworkSegment, self).get_parser(prog_name) @@ -261,8 +231,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - self.validate_os_beta_command_enabled() - client = self.app.client_manager.network obj = client.find_segment( parsed_args.network_segment, diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 2021d9f03c..1b778c9114 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -179,7 +179,7 @@ def _get_attrs(client_manager, parsed_args, is_create=True): attrs['ipv6_ra_mode'] = parsed_args.ipv6_ra_mode if parsed_args.ipv6_address_mode is not None: attrs['ipv6_address_mode'] = parsed_args.ipv6_address_mode - if 'network_segment' in parsed_args: + if parsed_args.network_segment is not None: attrs['segment_id'] = client.find_segment( parsed_args.network_segment, ignore_missing=False).id @@ -299,13 +299,12 @@ def get_parser(self, prog_name): help=_("IPv6 address mode, " "valid modes: [dhcpv6-stateful, dhcpv6-stateless, slaac]") ) - if self.app.options.os_beta_command: - parser.add_argument( - '--network-segment', - metavar='', - help=_("Network segment to associate with this subnet " - "(ID only)") - ) + parser.add_argument( + '--network-segment', + metavar='', + help=_("Network segment to associate with this subnet " + "(name or ID)") + ) parser.add_argument( '--network', required=True, diff --git a/openstackclient/tests/functional/network/v2/test_network_segment.py b/openstackclient/tests/functional/network/v2/test_network_segment.py index de5aef9650..b6f19ac4bf 100644 --- a/openstackclient/tests/functional/network/v2/test_network_segment.py +++ b/openstackclient/tests/functional/network/v2/test_network_segment.py @@ -39,8 +39,7 @@ def setUpClass(cls): if cls.NETWORK_SEGMENT_EXTENSION: # Get the segment for the network. opts = cls.get_opts(['ID', 'Network']) - raw_output = cls.openstack('--os-beta-command ' - 'network segment list ' + raw_output = cls.openstack('network segment list ' ' --network ' + cls.NETWORK_NAME + ' ' + opts) raw_output_row = raw_output.split('\n')[0] @@ -55,14 +54,12 @@ def test_network_segment_create_delete(self): if self.NETWORK_SEGMENT_EXTENSION: opts = self.get_opts(['id']) raw_output = self.openstack( - '--os-beta-command' + ' network segment create --network ' + self.NETWORK_ID + ' --network-type geneve ' + ' --segment 2055 test_segment ' + opts ) network_segment_id = raw_output.strip('\n') - raw_output = self.openstack('--os-beta-command ' + - 'network segment delete ' + + raw_output = self.openstack('network segment delete ' + network_segment_id) self.assertOutput('', raw_output) else: @@ -71,8 +68,7 @@ def test_network_segment_create_delete(self): def test_network_segment_list(self): if self.NETWORK_SEGMENT_EXTENSION: opts = self.get_opts(['ID']) - raw_output = self.openstack('--os-beta-command ' - 'network segment list' + opts) + raw_output = self.openstack('network segment list' + opts) self.assertIn(self.NETWORK_SEGMENT_ID, raw_output) else: self.skipTest('Segment extension disabled') @@ -80,14 +76,12 @@ def test_network_segment_list(self): def test_network_segment_set(self): if self.NETWORK_SEGMENT_EXTENSION: new_description = 'new_description' - raw_output = self.openstack('--os-beta-command ' - 'network segment set ' + + raw_output = self.openstack('network segment set ' + '--description ' + new_description + ' ' + self.NETWORK_SEGMENT_ID) self.assertOutput('', raw_output) opts = self.get_opts(['description']) - raw_output = self.openstack('--os-beta-command ' - 'network segment show ' + + raw_output = self.openstack('network segment show ' + self.NETWORK_SEGMENT_ID + opts) self.assertEqual(new_description + "\n", raw_output) else: @@ -96,8 +90,7 @@ def test_network_segment_set(self): def test_network_segment_show(self): if self.NETWORK_SEGMENT_EXTENSION: opts = self.get_opts(['network_id']) - raw_output = self.openstack('--os-beta-command ' - 'network segment show ' + + raw_output = self.openstack('network segment show ' + self.NETWORK_SEGMENT_ID + opts) self.assertEqual(self.NETWORK_ID + "\n", raw_output) else: diff --git a/openstackclient/tests/unit/network/v2/test_network_segment.py b/openstackclient/tests/unit/network/v2/test_network_segment.py index 3e755e0730..b9f0c3cff4 100644 --- a/openstackclient/tests/unit/network/v2/test_network_segment.py +++ b/openstackclient/tests/unit/network/v2/test_network_segment.py @@ -26,9 +26,6 @@ class TestNetworkSegment(network_fakes.TestNetworkV2): def setUp(self): super(TestNetworkSegment, self).setUp() - # Enable beta commands. - self.app.options.os_beta_command = True - # Get a shortcut to the network client self.network = self.app.client_manager.network @@ -81,22 +78,6 @@ def test_create_no_options(self): self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, [], []) - def test_create_no_beta_commands(self): - arglist = [ - '--network', self._network_segment.network_id, - '--network-type', self._network_segment.network_type, - self._network_segment.name, - ] - verifylist = [ - ('network', self._network_segment.network_id), - ('network_type', self._network_segment.network_type), - ('name', self._network_segment.name), - ] - self.app.options.os_beta_command = False - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.assertRaises(exceptions.CommandError, self.cmd.take_action, - parsed_args) - def test_create_invalid_network_type(self): arglist = [ '--network', self._network_segment.network_id, @@ -192,18 +173,6 @@ def setUp(self): self.namespace ) - def test_delete_no_beta_commands(self): - arglist = [ - self._network_segments[0].id, - ] - verifylist = [ - ('network_segment', [self._network_segments[0].id]), - ] - self.app.options.os_beta_command = False - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.assertRaises(exceptions.CommandError, self.cmd.take_action, - parsed_args) - def test_delete(self): arglist = [ self._network_segments[0].id, @@ -330,12 +299,6 @@ def test_list_no_option(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) - def test_list_no_beta_commands(self): - self.app.options.os_beta_command = False - parsed_args = self.check_parser(self.cmd, [], []) - self.assertRaises(exceptions.CommandError, self.cmd.take_action, - parsed_args) - def test_list_long(self): arglist = [ '--long', @@ -391,18 +354,6 @@ def setUp(self): # Get the command object to test self.cmd = network_segment.SetNetworkSegment(self.app, self.namespace) - def test_set_no_beta_commands(self): - arglist = [ - self._network_segment.id, - ] - verifylist = [ - ('network_segment', self._network_segment.id), - ] - self.app.options.os_beta_command = False - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.assertRaises(exceptions.CommandError, self.cmd.take_action, - parsed_args) - def test_set_no_options(self): arglist = [ self._network_segment.id, @@ -485,18 +436,6 @@ def test_show_no_options(self): self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, [], []) - def test_show_no_beta_commands(self): - arglist = [ - self._network_segment.id, - ] - verifylist = [ - ('network_segment', self._network_segment.id), - ] - self.app.options.os_beta_command = False - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.assertRaises(exceptions.CommandError, self.cmd.take_action, - parsed_args) - def test_show_all_options(self): arglist = [ self._network_segment.id, diff --git a/openstackclient/tests/unit/network/v2/test_subnet.py b/openstackclient/tests/unit/network/v2/test_subnet.py index 9c468f3924..2d51aa4ad9 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet.py +++ b/openstackclient/tests/unit/network/v2/test_subnet.py @@ -379,23 +379,6 @@ def test_create_options_subnet_range_ipv6(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data_ipv6, data) - def test_create_no_beta_command_options(self): - arglist = [ - "--subnet-range", self._subnet.cidr, - "--network-segment", self._network_segment.id, - "--network", self._subnet.network_id, - self._subnet.name, - ] - verifylist = [ - ('name', self._subnet.name), - ('subnet_range', self._subnet.cidr), - ('network-segment', self._network_segment.id), - ('network', self._subnet.network_id), - ] - self.app.options.os_beta_command = False - self.assertRaises(tests_utils.ParserException, - self.check_parser, self.cmd, arglist, verifylist) - def test_create_with_network_segment(self): # Mock SDK calls for this test. self.network.create_subnet = mock.Mock(return_value=self._subnet) @@ -417,7 +400,6 @@ def test_create_with_network_segment(self): ] - self.app.options.os_beta_command = True parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) diff --git a/releasenotes/notes/bp-routed-networks-3b502faa5cd96807.yaml b/releasenotes/notes/bp-routed-networks-3b502faa5cd96807.yaml index b055479368..6da2f7afca 100644 --- a/releasenotes/notes/bp-routed-networks-3b502faa5cd96807.yaml +++ b/releasenotes/notes/bp-routed-networks-3b502faa5cd96807.yaml @@ -1,6 +1,8 @@ --- features: - Add ``network segment create``, ``network segment delete`` and - ``network segment set`` commands. These are beta commands and subject to - change. Use global option ``--os-beta-command`` to enable these commands. + ``network segment set`` commands. In addition, the ``network segment list`` + and ``network segment show`` commands are no longer beta commands and + the ``--network-segment`` option on the ``subnet create`` command is + no longer a beta command option. [Blueprint `routed-networks `_] From c9fd35a49675027eea430b72e1c1f756da093b21 Mon Sep 17 00:00:00 2001 From: qtang Date: Wed, 28 Sep 2016 16:49:59 +0800 Subject: [PATCH 1275/3095] Update default nova api version to 2.1 Ref:https://review.openstack.org/#/c/311653/ We should use 2.1 as the default nova version now The API_MIN_VERSION of novaclient has already changed to 2.1 Change-Id: I9ff16cf052556e5d3756f81e02a8e76e8f315df5 Closes-bug: #1588171 --- openstackclient/compute/client.py | 3 ++- openstackclient/tests/unit/fakes.py | 2 +- releasenotes/notes/bug-1588171-61214d0ea482988c.yaml | 5 +++++ 3 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/bug-1588171-61214d0ea482988c.yaml diff --git a/openstackclient/compute/client.py b/openstackclient/compute/client.py index 4cc3be9855..c5b364b238 100644 --- a/openstackclient/compute/client.py +++ b/openstackclient/compute/client.py @@ -23,11 +23,12 @@ LOG = logging.getLogger(__name__) -DEFAULT_API_VERSION = '2' +DEFAULT_API_VERSION = '2.1' API_VERSION_OPTION = 'os_compute_api_version' API_NAME = 'compute' API_VERSIONS = { "2": "novaclient.client", + "2.1": "novaclient.client", } # Save the microversion if in use diff --git a/openstackclient/tests/unit/fakes.py b/openstackclient/tests/unit/fakes.py index f259836627..f7cb567644 100644 --- a/openstackclient/tests/unit/fakes.py +++ b/openstackclient/tests/unit/fakes.py @@ -38,7 +38,7 @@ _s = TEST_RESPONSE_DICT.add_service('network', name='neutron') _s.add_endpoint(AUTH_URL + ':9696') _s = TEST_RESPONSE_DICT.add_service('compute', name='nova') -_s.add_endpoint(AUTH_URL + ':8774/v2') +_s.add_endpoint(AUTH_URL + ':8774/v2.1') _s = TEST_RESPONSE_DICT.add_service('image', name='glance') _s.add_endpoint(AUTH_URL + ':9292') _s = TEST_RESPONSE_DICT.add_service('object', name='swift') diff --git a/releasenotes/notes/bug-1588171-61214d0ea482988c.yaml b/releasenotes/notes/bug-1588171-61214d0ea482988c.yaml new file mode 100644 index 0000000000..29b9168cca --- /dev/null +++ b/releasenotes/notes/bug-1588171-61214d0ea482988c.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Update novaclient DEFAULT_API_VERSION to 2.1 from 2.0 + [Bug `1588171 `_] \ No newline at end of file From f41e27ec3ab60241ffc94969c47b16f84c2293da Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 28 Sep 2016 17:33:31 +0000 Subject: [PATCH 1276/3095] Updated from global requirements Change-Id: I0895da779013a3fc586a36d4bbd6d1daf55bde20 --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index bf2ffb9147..b32ca05967 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -11,7 +11,7 @@ oslotest>=1.10.0 # Apache-2.0 reno>=1.8.0 # Apache2 requests>=2.10.0 # Apache-2.0 requests-mock>=1.1 # Apache-2.0 -sphinx!=1.3b1,<1.3,>=1.2.1 # BSD +sphinx!=1.3b1,<1.4,>=1.2.1 # BSD stevedore>=1.16.0 # Apache-2.0 os-client-config!=1.19.0,!=1.19.1,!=1.20.0,!=1.20.1,!=1.21.0,>=1.13.1 # Apache-2.0 os-testr>=0.7.0 # Apache-2.0 From 0ae68f55fc59eca0326fb8d249d7004d737486be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20GEORGEL?= Date: Thu, 29 Sep 2016 10:36:26 +0200 Subject: [PATCH 1277/3095] Add example in the doc when using Identity v3 Add example doc with user_domain_name and project_domain_name Change-Id: I5f454fcf705c8af1f0a1b85385bd7d8587234a14 --- doc/source/configuration.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst index efef2b9724..a49f093a45 100644 --- a/doc/source/configuration.rst +++ b/doc/source/configuration.rst @@ -111,6 +111,22 @@ The selection of ``interface`` (as seen above in the ``rackspace`` entry) is optional. For this configuration to work, every service for this cloud instance must already be configured to support this type of interface. +If you are using Identity v3 you need to specify the user and the project +domain name as shown in the example below: + +:: + + clouds: + devstack: + auth: + auth_url: http://192.168.122.10:35357/ + project_name: demo + username: demo + password: 0penstack + user_domain_name: Default + project_domain_name: Default + region_name: RegionOne + clouds-public.yaml ~~~~~~~~~~~~~~~~~~ From d0a0ff77a8b59371ba9181f55955822b141709ec Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Fri, 30 Sep 2016 01:16:22 -0400 Subject: [PATCH 1278/3095] Document `openstack complete` help and usage Currently we have no documentation for the `complete` command. Change-Id: I9faf38341bb57a61869ddbef406a022219a7c62c Closes-Bug: 1623860 --- doc/source/command-objects/complete.rst | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 doc/source/command-objects/complete.rst diff --git a/doc/source/command-objects/complete.rst b/doc/source/command-objects/complete.rst new file mode 100644 index 0000000000..8f84eec10a --- /dev/null +++ b/doc/source/command-objects/complete.rst @@ -0,0 +1,25 @@ +======== +complete +======== + +The ``complete`` command is inherited from the `python-cliff` library, it can +be used to generate a bash-completion script. Currently, the command will +generate a script for bash versions 3 or 4. The bash-completion script is +printed directly to standard out. + +Typical usage for this command is:: + + openstack complete | sudo tee /etc/bash_completion.d/osc.bash_completion > /dev/null + +If installing ``python-openstackclient`` from a package (``apt-get`` or ``yum``), +then this command will likely be run for you. + +complete +-------- + +print bash completion command + +.. program:: complete +.. code:: bash + + os complete From eadbeb39797b0f714d5d54fc171c12727bf1c98a Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Fri, 30 Sep 2016 10:24:22 +0000 Subject: [PATCH 1279/3095] Updated from global requirements Change-Id: I065a55b5fb0fdf01d47434012a4ea4ea3d67c2ba --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index b32ca05967..e9b747d671 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -14,7 +14,7 @@ requests-mock>=1.1 # Apache-2.0 sphinx!=1.3b1,<1.4,>=1.2.1 # BSD stevedore>=1.16.0 # Apache-2.0 os-client-config!=1.19.0,!=1.19.1,!=1.20.0,!=1.20.1,!=1.21.0,>=1.13.1 # Apache-2.0 -os-testr>=0.7.0 # Apache-2.0 +os-testr>=0.8.0 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD testtools>=1.4.0 # MIT tempest>=12.1.0 # Apache-2.0 From e2fc436d53f53d0993fc0b9dd29f402e6c7f8bc1 Mon Sep 17 00:00:00 2001 From: Nam Nguyen Hoai Date: Tue, 9 Aug 2016 14:31:34 +0700 Subject: [PATCH 1280/3095] Add --ha option to os router create command This patch added --ha option which the 'os router create' command was missed. Change-Id: I77635fb17af32beb0d8ed9aa080ef79285719fdc Closes-Bug: #1610161 --- doc/source/command-objects/router.rst | 6 ++++- openstackclient/network/v2/router.py | 10 ++++++-- .../tests/unit/network/v2/test_router.py | 25 +++++++++++++++++++ .../notes/bug-1610161-7c34c7b735701bd4.yaml | 5 ++++ 4 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/bug-1610161-7c34c7b735701bd4.yaml diff --git a/doc/source/command-objects/router.rst b/doc/source/command-objects/router.rst index 13a75158f4..335de17999 100644 --- a/doc/source/command-objects/router.rst +++ b/doc/source/command-objects/router.rst @@ -63,7 +63,7 @@ Create new router os router create [--project [--project-domain ]] [--enable | --disable] - [--distributed] + [--distributed] [--ha] [--description ] [--availability-zone-hint ] @@ -89,6 +89,10 @@ Create new router Create a distributed router +.. option:: --ha + + Create a highly available router + .. option:: --description Set router description diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index cb40d7741b..48a3a92cd3 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -94,7 +94,6 @@ def _get_attrs(client_manager, parsed_args): ).id attrs['tenant_id'] = project_id - # TODO(tangchen): Support getting 'ha' property. # TODO(tangchen): Support getting 'external_gateway_info' property. return attrs @@ -180,10 +179,15 @@ def get_parser(self, prog_name): default=False, help=_("Create a distributed router") ) + parser.add_argument( + '--ha', + action='store_true', + help=_("Create a highly available router") + ) parser.add_argument( '--description', metavar='', - help=_('Set router description') + help=_("Set router description") ) parser.add_argument( '--project', @@ -207,6 +211,8 @@ def take_action(self, parsed_args): client = self.app.client_manager.network attrs = _get_attrs(self.app.client_manager, parsed_args) + if parsed_args.ha: + attrs['ha'] = parsed_args.ha obj = client.create_router(**attrs) columns = _get_columns(obj) diff --git a/openstackclient/tests/unit/network/v2/test_router.py b/openstackclient/tests/unit/network/v2/test_router.py index d12289e15f..6a44586221 100644 --- a/openstackclient/tests/unit/network/v2/test_router.py +++ b/openstackclient/tests/unit/network/v2/test_router.py @@ -166,6 +166,7 @@ def test_create_default_options(self): ('name', self.new_router.name), ('enable', True), ('distributed', False), + ('ha', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -178,6 +179,29 @@ def test_create_default_options(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_create_with_ha_option(self): + arglist = [ + '--ha', + self.new_router.name, + ] + verifylist = [ + ('name', self.new_router.name), + ('enable', True), + ('distributed', False), + ('ha', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_router.assert_called_once_with(**{ + 'admin_state_up': True, + 'name': self.new_router.name, + 'ha': True, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + def test_create_with_AZ_hints(self): arglist = [ self.new_router.name, @@ -189,6 +213,7 @@ def test_create_with_AZ_hints(self): ('availability_zone_hints', ['fake-az', 'fake-az2']), ('enable', True), ('distributed', False), + ('ha', False) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/releasenotes/notes/bug-1610161-7c34c7b735701bd4.yaml b/releasenotes/notes/bug-1610161-7c34c7b735701bd4.yaml new file mode 100644 index 0000000000..9cdbba49f6 --- /dev/null +++ b/releasenotes/notes/bug-1610161-7c34c7b735701bd4.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``--ha`` option to ``router create`` command. + [Bug `1610161 `_] \ No newline at end of file From ea7f28fb4acd39345bb95809a952e03ed802a9ff Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Tue, 4 Oct 2016 22:55:49 +0800 Subject: [PATCH 1281/3095] Refactor qos spec unit test in volume v1 Refactor qos spec unit test with FakeQos class in volume v1. Change-Id: Ia76bf41a911457282b293dfaf0af311b1a9194ce --- openstackclient/tests/unit/volume/v1/fakes.py | 26 ++ .../tests/unit/volume/v1/test_qos_specs.py | 257 +++++++----------- 2 files changed, 124 insertions(+), 159 deletions(-) diff --git a/openstackclient/tests/unit/volume/v1/fakes.py b/openstackclient/tests/unit/volume/v1/fakes.py index 3999543c5b..a11ea49137 100644 --- a/openstackclient/tests/unit/volume/v1/fakes.py +++ b/openstackclient/tests/unit/volume/v1/fakes.py @@ -305,6 +305,32 @@ def create_one_qos(attrs=None): loaded=True) return qos + @staticmethod + def create_one_qos_association(attrs=None): + """Create a fake Qos specification association. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object with id, name, association_type, etc. + """ + attrs = attrs or {} + + # Set default attributes. + qos_association_info = { + "id": 'type-id-' + uuid.uuid4().hex, + "name": 'type-name-' + uuid.uuid4().hex, + "association_type": 'volume_type', + } + + # Overwrite default attributes. + qos_association_info.update(attrs) + + qos_association = fakes.FakeResource( + info=copy.deepcopy(qos_association_info), + loaded=True) + return qos_association + @staticmethod def create_qoses(attrs=None, count=2): """Create multiple fake Qos specifications. diff --git a/openstackclient/tests/unit/volume/v1/test_qos_specs.py b/openstackclient/tests/unit/volume/v1/test_qos_specs.py index 1982980a32..464038e733 100644 --- a/openstackclient/tests/unit/volume/v1/test_qos_specs.py +++ b/openstackclient/tests/unit/volume/v1/test_qos_specs.py @@ -13,14 +13,12 @@ # under the License. # -import copy import mock from mock import call from osc_lib import exceptions from osc_lib import utils -from openstackclient.tests.unit import fakes from openstackclient.tests.unit.volume.v1 import fakes as volume_fakes from openstackclient.volume.v1 import qos_specs @@ -39,154 +37,124 @@ def setUp(self): class TestQosAssociate(TestQos): + volume_type = volume_fakes.FakeType.create_one_type() + qos_spec = volume_fakes.FakeQos.create_one_qos() + def setUp(self): super(TestQosAssociate, self).setUp() + self.qos_mock.get.return_value = self.qos_spec + self.types_mock.get.return_value = self.volume_type # Get the command object to test self.cmd = qos_specs.AssociateQos(self.app, None) def test_qos_associate(self): - self.qos_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.QOS), - loaded=True - ) - self.types_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.TYPE), - loaded=True - ) arglist = [ - volume_fakes.qos_id, - volume_fakes.type_id + self.qos_spec.id, + self.volume_type.id ] verifylist = [ - ('qos_spec', volume_fakes.qos_id), - ('volume_type', volume_fakes.type_id) + ('qos_spec', self.qos_spec.id), + ('volume_type', self.volume_type.id) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.qos_mock.associate.assert_called_with( - volume_fakes.qos_id, - volume_fakes.type_id + self.qos_spec.id, + self.volume_type.id ) self.assertIsNone(result) class TestQosCreate(TestQos): + new_qos_spec = volume_fakes.FakeQos.create_one_qos() columns = ( 'consumer', 'id', 'name', + 'specs' ) datalist = ( - volume_fakes.qos_consumer, - volume_fakes.qos_id, - volume_fakes.qos_name + new_qos_spec.consumer, + new_qos_spec.id, + new_qos_spec.name, + new_qos_spec.specs ) def setUp(self): super(TestQosCreate, self).setUp() + self.qos_mock.create.return_value = self.new_qos_spec # Get the command object to test self.cmd = qos_specs.CreateQos(self.app, None) def test_qos_create_without_properties(self): - self.qos_mock.create.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.QOS_DEFAULT_CONSUMER), - loaded=True - ) - arglist = [ - volume_fakes.qos_name, + self.new_qos_spec.name, ] verifylist = [ - ('name', volume_fakes.qos_name), + ('name', self.new_qos_spec.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.qos_mock.create.assert_called_with( - volume_fakes.qos_name, - {'consumer': volume_fakes.qos_default_consumer} + self.new_qos_spec.name, + {'consumer': 'both'} ) self.assertEqual(self.columns, columns) - datalist = ( - volume_fakes.qos_default_consumer, - volume_fakes.qos_id, - volume_fakes.qos_name - ) - self.assertEqual(datalist, data) + self.assertEqual(self.datalist, data) def test_qos_create_with_consumer(self): - self.qos_mock.create.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.QOS), - loaded=True - ) - arglist = [ - volume_fakes.qos_name, - '--consumer', volume_fakes.qos_consumer + '--consumer', self.new_qos_spec.consumer, + self.new_qos_spec.name, ] verifylist = [ - ('name', volume_fakes.qos_name), - ('consumer', volume_fakes.qos_consumer) + ('consumer', self.new_qos_spec.consumer), + ('name', self.new_qos_spec.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.qos_mock.create.assert_called_with( - volume_fakes.qos_name, - {'consumer': volume_fakes.qos_consumer} + self.new_qos_spec.name, + {'consumer': self.new_qos_spec.consumer} ) self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, data) def test_qos_create_with_properties(self): - self.qos_mock.create.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.QOS_WITH_SPECS), - loaded=True - ) - arglist = [ - volume_fakes.qos_name, - '--consumer', volume_fakes.qos_consumer, + '--consumer', self.new_qos_spec.consumer, '--property', 'foo=bar', - '--property', 'iops=9001' + '--property', 'iops=9001', + self.new_qos_spec.name, ] verifylist = [ - ('name', volume_fakes.qos_name), - ('consumer', volume_fakes.qos_consumer), - ('property', volume_fakes.qos_specs) + ('consumer', self.new_qos_spec.consumer), + ('property', self.new_qos_spec.specs), + ('name', self.new_qos_spec.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - specs = volume_fakes.qos_specs.copy() - specs.update({'consumer': volume_fakes.qos_consumer}) + self.new_qos_spec.specs.update( + {'consumer': self.new_qos_spec.consumer}) self.qos_mock.create.assert_called_with( - volume_fakes.qos_name, - specs + self.new_qos_spec.name, + self.new_qos_spec.specs ) - columns = self.columns + ( - 'specs', - ) - self.assertEqual(columns, columns) - datalist = self.datalist + ( - volume_fakes.qos_specs, - ) - self.assertEqual(datalist, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) class TestQosDelete(TestQos): @@ -294,79 +262,62 @@ def test_delete_multiple_qoses_with_exception(self): class TestQosDisassociate(TestQos): + volume_type = volume_fakes.FakeType.create_one_type() + qos_spec = volume_fakes.FakeQos.create_one_qos() + def setUp(self): super(TestQosDisassociate, self).setUp() + self.qos_mock.get.return_value = self.qos_spec + self.types_mock.get.return_value = self.volume_type # Get the command object to test self.cmd = qos_specs.DisassociateQos(self.app, None) def test_qos_disassociate_with_volume_type(self): - self.qos_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.QOS), - loaded=True - ) - self.types_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.TYPE), - loaded=True - ) arglist = [ - volume_fakes.qos_id, - '--volume-type', volume_fakes.type_id + '--volume-type', self.volume_type.id, + self.qos_spec.id, ] verifylist = [ - ('qos_spec', volume_fakes.qos_id), - ('volume_type', volume_fakes.type_id) + ('volume_type', self.volume_type.id), + ('qos_spec', self.qos_spec.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.qos_mock.disassociate.assert_called_with( - volume_fakes.qos_id, - volume_fakes.type_id + self.qos_spec.id, + self.volume_type.id ) self.assertIsNone(result) def test_qos_disassociate_with_all_volume_types(self): - self.qos_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.QOS), - loaded=True - ) - arglist = [ - volume_fakes.qos_id, - '--all' + '--all', + self.qos_spec.id, ] verifylist = [ - ('qos_spec', volume_fakes.qos_id) + ('qos_spec', self.qos_spec.id) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.qos_mock.disassociate_all.assert_called_with(volume_fakes.qos_id) + self.qos_mock.disassociate_all.assert_called_with(self.qos_spec.id) self.assertIsNone(result) class TestQosList(TestQos): + qos_spec = volume_fakes.FakeQos.create_one_qos() + qos_association = volume_fakes.FakeQos.create_one_qos_association() + def setUp(self): super(TestQosList, self).setUp() - self.qos_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.QOS_WITH_ASSOCIATIONS), - loaded=True, - ) - self.qos_mock.list.return_value = [self.qos_mock.get.return_value] - self.qos_mock.get_associations.return_value = [fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.qos_association), - loaded=True, - )] + self.qos_mock.list.return_value = [self.qos_spec] + self.qos_mock.get_associations.return_value = [self.qos_association] # Get the command object to test self.cmd = qos_specs.ListQos(self.app, None) @@ -389,81 +340,72 @@ def test_qos_list(self): ) self.assertEqual(collist, columns) datalist = (( - volume_fakes.qos_id, - volume_fakes.qos_name, - volume_fakes.qos_consumer, - volume_fakes.type_name, - utils.format_dict(volume_fakes.qos_specs), + self.qos_spec.id, + self.qos_spec.name, + self.qos_spec.consumer, + self.qos_association.name, + utils.format_dict(self.qos_spec.specs), ), ) self.assertEqual(datalist, tuple(data)) class TestQosSet(TestQos): + qos_spec = volume_fakes.FakeQos.create_one_qos() + def setUp(self): super(TestQosSet, self).setUp() + self.qos_mock.get.return_value = self.qos_spec # Get the command object to test self.cmd = qos_specs.SetQos(self.app, None) def test_qos_set_with_properties_with_id(self): - self.qos_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.QOS_WITH_SPECS), - loaded=True - ) arglist = [ - volume_fakes.qos_id, '--property', 'foo=bar', - '--property', 'iops=9001' + '--property', 'iops=9001', + self.qos_spec.id, ] verifylist = [ - ('qos_spec', volume_fakes.qos_id), - ('property', volume_fakes.qos_specs) + ('property', self.qos_spec.specs), + ('qos_spec', self.qos_spec.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.qos_mock.set_keys.assert_called_with( - volume_fakes.qos_id, - volume_fakes.qos_specs + self.qos_spec.id, + self.qos_spec.specs ) self.assertIsNone(result) class TestQosShow(TestQos): + qos_spec = volume_fakes.FakeQos.create_one_qos() + qos_association = volume_fakes.FakeQos.create_one_qos_association() + def setUp(self): super(TestQosShow, self).setUp() - - self.qos_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.QOS_WITH_ASSOCIATIONS), - loaded=True, - ) - self.qos_mock.get_associations.return_value = [fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.qos_association), - loaded=True, - )] - + self.qos_mock.get.return_value = self.qos_spec + self.qos_mock.get_associations.return_value = [self.qos_association] # Get the command object to test self.cmd = qos_specs.ShowQos(self.app, None) def test_qos_show(self): arglist = [ - volume_fakes.qos_id + self.qos_spec.id ] verifylist = [ - ('qos_spec', volume_fakes.qos_id) + ('qos_spec', self.qos_spec.id) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.qos_mock.get.assert_called_with( - volume_fakes.qos_id + self.qos_spec.id ) collist = ( @@ -475,56 +417,53 @@ def test_qos_show(self): ) self.assertEqual(collist, columns) datalist = ( - volume_fakes.type_name, - volume_fakes.qos_consumer, - volume_fakes.qos_id, - volume_fakes.qos_name, - utils.format_dict(volume_fakes.qos_specs), + self.qos_association.name, + self.qos_spec.consumer, + self.qos_spec.id, + self.qos_spec.name, + utils.format_dict(self.qos_spec.specs), ) self.assertEqual(datalist, tuple(data)) class TestQosUnset(TestQos): + qos_spec = volume_fakes.FakeQos.create_one_qos() + def setUp(self): super(TestQosUnset, self).setUp() + self.qos_mock.get.return_value = self.qos_spec # Get the command object to test self.cmd = qos_specs.UnsetQos(self.app, None) def test_qos_unset_with_properties(self): - self.qos_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.QOS), - loaded=True - ) arglist = [ - volume_fakes.qos_id, '--property', 'iops', - '--property', 'foo' + '--property', 'foo', + self.qos_spec.id, ] - verifylist = [ - ('qos_spec', volume_fakes.qos_id), - ('property', ['iops', 'foo']) + ('property', ['iops', 'foo']), + ('qos_spec', self.qos_spec.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.qos_mock.unset_keys.assert_called_with( - volume_fakes.qos_id, + self.qos_spec.id, ['iops', 'foo'] ) self.assertIsNone(result) def test_qos_unset_nothing(self): arglist = [ - volume_fakes.qos_id, + self.qos_spec.id, ] verifylist = [ - ('qos_spec', volume_fakes.qos_id), + ('qos_spec', self.qos_spec.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) From bfeecd50fdb8f1475d51ac662eb09514a1754345 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 5 Oct 2016 01:36:09 +0000 Subject: [PATCH 1282/3095] Updated from global requirements Change-Id: Ib5e40955446ef8edfb2e461b6284a7fdf655ea76 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e662587066..ac7323fca8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ pbr>=1.6 # Apache-2.0 six>=1.9.0 # MIT Babel>=2.3.4 # BSD -cliff!=1.16.0,!=1.17.0,>=1.15.0 # Apache-2.0 +cliff>=2.2.0 # Apache-2.0 keystoneauth1>=2.10.0 # Apache-2.0 openstacksdk>=0.9.7 # Apache-2.0 osc-lib>=1.0.2 # Apache-2.0 From 70cb628278642e0e275c3e8d294bb73c17a97f5e Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Mon, 3 Oct 2016 16:24:39 -0500 Subject: [PATCH 1283/3095] SDK Refactor: Prepare address scope commands Prepare the OSC "address scope" commands for the SDK refactor. See [1] for details. [1] https://etherpad.openstack.org/p/osc-network-command-sdk-support Change-Id: I4e253e01f9b0b10452354f4e4152468090c76958 Partially-Implements: blueprint network-command-sdk-support --- openstackclient/network/sdk_utils.py | 40 +++++++++++++ openstackclient/network/v2/address_scope.py | 28 +++++---- .../tests/unit/network/test_sdk_utils.py | 59 +++++++++++++++++++ .../tests/unit/network/v2/fakes.py | 1 + 4 files changed, 116 insertions(+), 12 deletions(-) create mode 100644 openstackclient/network/sdk_utils.py create mode 100644 openstackclient/tests/unit/network/test_sdk_utils.py diff --git a/openstackclient/network/sdk_utils.py b/openstackclient/network/sdk_utils.py new file mode 100644 index 0000000000..7bd54e4615 --- /dev/null +++ b/openstackclient/network/sdk_utils.py @@ -0,0 +1,40 @@ +# 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 six + + +# Get the OSC show command display and attribute columns for an SDK resource. +def get_osc_show_columns_for_sdk_resource(sdk_resource, osc_column_map): + if getattr(sdk_resource, 'allow_get', None) is not None: + resource_dict = sdk_resource.to_dict( + body=True, headers=False, ignore_none=False) + else: + resource_dict = sdk_resource + + # Build the OSC column names to display for the SDK resource. + attr_map = {} + display_columns = list(resource_dict.keys()) + for sdk_attr, osc_attr in six.iteritems(osc_column_map): + if sdk_attr in display_columns: + attr_map[osc_attr] = sdk_attr + display_columns.remove(sdk_attr) + if osc_attr not in display_columns: + display_columns.append(osc_attr) + sorted_display_columns = sorted(display_columns) + + # Build the SDK attribute names for the OSC column names. + attr_columns = [] + for column in sorted_display_columns: + new_column = attr_map[column] if column in attr_map else column + attr_columns.append(new_column) + return tuple(sorted_display_columns), tuple(attr_columns) diff --git a/openstackclient/network/v2/address_scope.py b/openstackclient/network/v2/address_scope.py index 6cd13f8ce7..95f6c94702 100644 --- a/openstackclient/network/v2/address_scope.py +++ b/openstackclient/network/v2/address_scope.py @@ -21,18 +21,18 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import sdk_utils LOG = logging.getLogger(__name__) def _get_columns(item): - columns = list(item.keys()) - if 'tenant_id' in columns: - columns.remove('tenant_id') - columns.append('project_id') - - return tuple(sorted(columns)) + column_map = { + 'is_shared': 'shared', + 'tenant_id': 'project_id', + } + return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) def _get_attrs(client_manager, parsed_args): @@ -55,6 +55,8 @@ def _get_attrs(client_manager, parsed_args): return attrs +# TODO(rtheis): Use the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. class CreateAddressScope(command.ShowOne): """Create a new Address Scope""" @@ -97,10 +99,10 @@ def take_action(self, parsed_args): client = self.app.client_manager.network attrs = _get_attrs(self.app.client_manager, parsed_args) obj = client.create_address_scope(**attrs) - columns = _get_columns(obj) + display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters={}) - return (columns, data) + return (display_columns, data) class DeleteAddressScope(command.Command): @@ -147,8 +149,8 @@ def take_action(self, parsed_args): 'id', 'name', 'ip_version', - 'shared', - 'tenant_id', + 'is_shared', + 'project_id', ) column_headers = ( 'ID', @@ -165,6 +167,8 @@ def take_action(self, parsed_args): ) for s in data)) +# TODO(rtheis): Use the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. class SetAddressScope(command.Command): """Set address scope properties""" @@ -227,7 +231,7 @@ def take_action(self, parsed_args): obj = client.find_address_scope( parsed_args.address_scope, ignore_missing=False) - columns = _get_columns(obj) + display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters={}) - return (columns, data) + return (display_columns, data) diff --git a/openstackclient/tests/unit/network/test_sdk_utils.py b/openstackclient/tests/unit/network/test_sdk_utils.py new file mode 100644 index 0000000000..d1efa7e40a --- /dev/null +++ b/openstackclient/tests/unit/network/test_sdk_utils.py @@ -0,0 +1,59 @@ +# 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.network import sdk_utils +from openstackclient.tests.unit import utils as tests_utils + + +class TestSDKUtils(tests_utils.TestCase): + + def setUp(self): + super(TestSDKUtils, self).setUp() + + def _test_get_osc_show_columns_for_sdk_resource( + self, sdk_resource, column_map, + expected_display_columns, expected_attr_columns): + display_columns, attr_columns = \ + sdk_utils.get_osc_show_columns_for_sdk_resource( + sdk_resource, column_map) + self.assertEqual(expected_display_columns, display_columns) + self.assertEqual(expected_attr_columns, attr_columns) + + def test_get_osc_show_columns_for_sdk_resource_empty(self): + self._test_get_osc_show_columns_for_sdk_resource( + {}, {}, tuple(), tuple()) + + def test_get_osc_show_columns_for_sdk_resource_empty_map(self): + self._test_get_osc_show_columns_for_sdk_resource( + {'foo': 'foo1'}, {}, + ('foo',), ('foo',)) + + def test_get_osc_show_columns_for_sdk_resource_empty_data(self): + self._test_get_osc_show_columns_for_sdk_resource( + {}, {'foo': 'foo_map'}, + ('foo_map',), ('foo_map',)) + + def test_get_osc_show_columns_for_sdk_resource_map(self): + self._test_get_osc_show_columns_for_sdk_resource( + {'foo': 'foo1'}, {'foo': 'foo_map'}, + ('foo_map',), ('foo',)) + + def test_get_osc_show_columns_for_sdk_resource_map_dup(self): + self._test_get_osc_show_columns_for_sdk_resource( + {'foo': 'foo1', 'foo_map': 'foo1'}, {'foo': 'foo_map'}, + ('foo_map',), ('foo',)) + + def test_get_osc_show_columns_for_sdk_resource_map_full(self): + self._test_get_osc_show_columns_for_sdk_resource( + {'foo': 'foo1', 'bar': 'bar1'}, + {'foo': 'foo_map', 'new': 'bar'}, + ('bar', 'foo_map'), ('bar', 'foo')) diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index cea0028218..ed3579b724 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -98,6 +98,7 @@ def create_one_address_scope(attrs=None): loaded=True) # Set attributes with special mapping in OpenStack SDK. + address_scope.is_shared = address_scope_attrs['shared'] address_scope.project_id = address_scope_attrs['tenant_id'] return address_scope From 368a2503213753fdc39cbc3ff5fe59da57c7d048 Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Wed, 5 Oct 2016 07:33:31 -0500 Subject: [PATCH 1284/3095] SDK Refactor: Prepare network segment commands Prepare the OSC "network segment" commands for the SDK refactor. See [1] for details. [1] https://etherpad.openstack.org/p/osc-network-command-sdk-support Change-Id: I426ecdad0f9d95b89e80fb1c5ba81ffe5fe274c9 Partially-Implements: blueprint network-command-sdk-support --- openstackclient/network/v2/network_segment.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/openstackclient/network/v2/network_segment.py b/openstackclient/network/v2/network_segment.py index 94722f1e68..ce1350028f 100644 --- a/openstackclient/network/v2/network_segment.py +++ b/openstackclient/network/v2/network_segment.py @@ -20,11 +20,16 @@ from osc_lib import utils from openstackclient.i18n import _ +from openstackclient.network import sdk_utils LOG = logging.getLogger(__name__) +def _get_columns(item): + return sdk_utils.get_osc_show_columns_for_sdk_resource(item, {}) + + class CreateNetworkSegment(command.ShowOne): """Create new network segment""" @@ -84,9 +89,9 @@ def take_action(self, parsed_args): if parsed_args.segment is not None: attrs['segmentation_id'] = parsed_args.segment obj = client.create_segment(**attrs) - columns = tuple(sorted(obj.keys())) + display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns) - return (columns, data) + return (display_columns, data) class DeleteNetworkSegment(command.Command): @@ -236,6 +241,6 @@ def take_action(self, parsed_args): parsed_args.network_segment, ignore_missing=False ) - columns = tuple(sorted(obj.keys())) + display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns) - return (columns, data) + return (display_columns, data) From d6cc50f0daac71fbe2a18059be94f6d01a26d78d Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Wed, 5 Oct 2016 08:09:16 -0500 Subject: [PATCH 1285/3095] SDK Refactor: Prepare subnet pool commands Prepare the OSC "subnet pool" commands for the SDK refactor. See [1] for details. [1] https://etherpad.openstack.org/p/osc-network-command-sdk-support Change-Id: I6d8cb7a079cf115ee25d48d9175e31f0f995c502 Partially-Implements: blueprint network-command-sdk-support --- openstackclient/network/v2/subnet_pool.py | 35 +++++++++++++------ .../tests/unit/network/v2/fakes.py | 5 +++ .../tests/unit/network/v2/test_subnet_pool.py | 8 ++--- 3 files changed, 33 insertions(+), 15 deletions(-) diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index a01d2f7ba2..2e0a32c240 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -22,17 +22,21 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import sdk_utils LOG = logging.getLogger(__name__) def _get_columns(item): - columns = list(item.keys()) - if 'tenant_id' in columns: - columns.remove('tenant_id') - columns.append('project_id') - return tuple(sorted(columns)) + column_map = { + 'default_prefix_length': 'default_prefixlen', + 'is_shared': 'shared', + 'maximum_prefix_length': 'max_prefixlen', + 'minimum_prefix_length': 'min_prefixlen', + 'tenant_id': 'project_id', + } + return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) _formatters = { @@ -134,6 +138,8 @@ def _add_default_options(parser): ) +# TODO(rtheis): Use the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. class CreateSubnetPool(command.ShowOne): """Create subnet pool""" @@ -184,9 +190,9 @@ def take_action(self, parsed_args): if "prefixes" not in attrs: attrs['prefixes'] = [] obj = client.create_subnet_pool(**attrs) - columns = _get_columns(obj) + display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) - return (columns, data) + return (display_columns, data) class DeleteSubnetPool(command.Command): @@ -223,6 +229,8 @@ def take_action(self, parsed_args): raise exceptions.CommandError(msg) +# TODO(rtheis): Use only the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. class ListSubnetPool(command.Lister): """List subnet pools""" @@ -281,8 +289,10 @@ def take_action(self, parsed_args): filters = {} if parsed_args.share: filters['shared'] = True + filters['is_shared'] = True elif parsed_args.no_share: filters['shared'] = False + filters['is_shared'] = False if parsed_args.default: filters['is_default'] = True elif parsed_args.no_default: @@ -294,6 +304,7 @@ def take_action(self, parsed_args): parsed_args.project_domain, ).id filters['tenant_id'] = project_id + filters['project_id'] = project_id if parsed_args.name is not None: filters['name'] = parsed_args.name if parsed_args.address_scope: @@ -308,8 +319,8 @@ def take_action(self, parsed_args): if parsed_args.long: headers += ('Default Prefix Length', 'Address Scope', 'Default Subnet Pool', 'Shared') - columns += ('default_prefixlen', 'address_scope_id', - 'is_default', 'shared') + columns += ('default_prefix_length', 'address_scope_id', + 'is_default', 'is_shared') return (headers, (utils.get_item_properties( @@ -318,6 +329,8 @@ def take_action(self, parsed_args): ) for s in data)) +# TODO(rtheis): Use the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. class SetSubnetPool(command.Command): """Set subnet pool properties""" @@ -388,9 +401,9 @@ def take_action(self, parsed_args): parsed_args.subnet_pool, ignore_missing=False ) - columns = _get_columns(obj) + display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) - return (columns, data) + return (display_columns, data) class UnsetSubnetPool(command.Command): diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index ed3579b724..667f76b8df 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -1067,6 +1067,11 @@ def create_one_subnet_pool(attrs=None): ) # Set attributes with special mapping in OpenStack SDK. + subnet_pool.default_prefix_length = \ + subnet_pool_attrs['default_prefixlen'] + subnet_pool.is_shared = subnet_pool_attrs['shared'] + subnet_pool.maximum_prefix_length = subnet_pool_attrs['max_prefixlen'] + subnet_pool.minimum_prefix_length = subnet_pool_attrs['min_prefixlen'] subnet_pool.project_id = subnet_pool_attrs['tenant_id'] return subnet_pool diff --git a/openstackclient/tests/unit/network/v2/test_subnet_pool.py b/openstackclient/tests/unit/network/v2/test_subnet_pool.py index fa6ffff304..f12537e7aa 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/unit/network/v2/test_subnet_pool.py @@ -435,7 +435,7 @@ def test_subnet_pool_list_no_share(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - filters = {'shared': False} + filters = {'shared': False, 'is_shared': False} self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) @@ -451,7 +451,7 @@ def test_subnet_pool_list_share(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - filters = {'shared': True} + filters = {'shared': True, 'is_shared': True} self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) @@ -501,7 +501,7 @@ def test_subnet_pool_list_project(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - filters = {'tenant_id': project.id} + filters = {'tenant_id': project.id, 'project_id': project.id} self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) @@ -521,7 +521,7 @@ def test_subnet_pool_list_project_domain(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - filters = {'tenant_id': project.id} + filters = {'tenant_id': project.id, 'project_id': project.id} self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) From cd1a412408f068aeef97c1ee368400307fce7733 Mon Sep 17 00:00:00 2001 From: Matt Riedemann Date: Wed, 5 Oct 2016 21:11:16 -0400 Subject: [PATCH 1286/3095] Mask passwords in debug logs for auth_config_hook The auth config hook can have credentials in it so we have to mask the config before logging it. To avoid doing the work of masking the password if we aren't going to log it, there is a conditional put around the actual debug statement. Change-Id: I8e626672ec94fc837610216bccb4354dbdedca17 Closes-Bug: #1630822 --- openstackclient/common/client_config.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openstackclient/common/client_config.py b/openstackclient/common/client_config.py index 30286df8af..d6297753b4 100644 --- a/openstackclient/common/client_config.py +++ b/openstackclient/common/client_config.py @@ -17,6 +17,8 @@ from os_client_config import config from os_client_config import exceptions as occ_exceptions +from oslo_utils import strutils +import six LOG = logging.getLogger(__name__) @@ -180,7 +182,9 @@ def auth_config_hook(self, config): config = self._auth_v2_ignore_v3(config) config = self._auth_default_domain(config) - LOG.debug("auth_config_hook(): %s" % config) + if LOG.isEnabledFor(logging.DEBUG): + LOG.debug("auth_config_hook(): %s", + strutils.mask_password(six.text_type(config))) return config def load_auth_plugin(self, config): From 55c4290403874ed8d05b40b90350e05f84b9f8e0 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 6 Oct 2016 17:02:13 +0000 Subject: [PATCH 1287/3095] Updated from global requirements Change-Id: Iacadedbfcf1500ae67f7c401bf5a3e22aa07c366 --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index e9b747d671..d799f5a669 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -12,7 +12,7 @@ reno>=1.8.0 # Apache2 requests>=2.10.0 # Apache-2.0 requests-mock>=1.1 # Apache-2.0 sphinx!=1.3b1,<1.4,>=1.2.1 # BSD -stevedore>=1.16.0 # Apache-2.0 +stevedore>=1.17.1 # Apache-2.0 os-client-config!=1.19.0,!=1.19.1,!=1.20.0,!=1.20.1,!=1.21.0,>=1.13.1 # Apache-2.0 os-testr>=0.8.0 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD From d695e3b1579661d6682766b95121bc1a10d871a3 Mon Sep 17 00:00:00 2001 From: Andreas Jaeger Date: Thu, 6 Oct 2016 20:51:51 +0200 Subject: [PATCH 1288/3095] Enable release notes translation Releasenote translation publishing is being prepared. 'locale_dirs' needs to be defined in conf.py to generate translated version of the release notes. Note that this repository might not get translated release notes - or no translations at all - but we add the entry here nevertheless to prepare for it. Change-Id: Ib265f919e176f048f6e56bde47db3d99b3dd993e --- releasenotes/source/conf.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py index d765e339b2..38f0107cc4 100644 --- a/releasenotes/source/conf.py +++ b/releasenotes/source/conf.py @@ -300,3 +300,6 @@ # If true, do not generate a @detailmenu in the "Top" node's menu. # texinfo_no_detailmenu = False + +# -- Options for Internationalization output ------------------------------ +locale_dirs = ['locale/'] From 42f9317360ac4a20daa2c5ef329d9fc99e8df680 Mon Sep 17 00:00:00 2001 From: Kyrylo Romanenko Date: Thu, 6 Oct 2016 16:02:09 +0300 Subject: [PATCH 1289/3095] Improve output of supported API versions Sort supported versions properly for better look. Change-Id: I6c2f5ecc04cf14ea5bf1b214cb303fcc9783af3f Closes-Bug: #1630962 --- openstackclient/shell.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 3971b6ef4a..be4b52834a 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -93,10 +93,12 @@ def _load_plugins(self): mod_versions = getattr(mod, 'API_VERSIONS', None) if not skip_old_check and mod_versions: if version_opt not in mod_versions: + sorted_versions = sorted( + mod.API_VERSIONS.keys(), + key=lambda s: list(map(int, s.split('.')))) self.log.warning( - "%s version %s is not in supported versions %s" - % (api, version_opt, - ', '.join(list(mod.API_VERSIONS.keys())))) + "%s version %s is not in supported versions: %s" + % (api, version_opt, ', '.join(sorted_versions))) # Command groups deal only with major versions version = '.v' + version_opt.replace('.', '_').split('_')[0] From 762f2f2c34814b8bfc615696918d8cb49b93a1dd Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 7 Oct 2016 14:52:34 -0500 Subject: [PATCH 1290/3095] More 3.3.0 release notes Plus minor doc formatting updates Change-Id: I849794e954cdeb582f7a0b104c62ec6688ead628 --- doc/source/command-objects/router.rst | 3 ++- .../add-overwrite-option-190a9c6904d53dab.yaml | 6 ------ ...w-overwrite-set-options-190a9c6904d53dab.yaml | 16 ++++++++++++++++ ...twork-command-options-2-e7b13a6a09f5d21e.yaml | 6 ++++++ ...ron-client-descriptions-a80902b4295843cf.yaml | 6 ++++-- ...o-property-in-aggregate-b74a42e00a65d14a.yaml | 7 +++++++ .../notes/bug-1588171-61214d0ea482988c.yaml | 4 ++-- .../notes/bug-1609233-90b2ddf8d941050e.yaml | 6 ++++-- .../notes/bug-1614458-c42be5738f447db8.yaml | 2 +- .../notes/bug-1614823-e89080342f25f2c0.yaml | 2 +- .../notes/bug-1627913-2adf4182977e5926.yaml | 5 ++++- ...-mask-password-on-debug-20dcdf1c54e84fa1.yaml | 5 +++++ ...rite-options-for-subnet-76476127dcf321ad.yaml | 6 ------ ...o-property-in-aggregate-b74a42e00a65d14a.yaml | 7 ------- releasenotes/source/index.rst | 1 + 15 files changed, 53 insertions(+), 29 deletions(-) delete mode 100644 releasenotes/notes/add-overwrite-option-190a9c6904d53dab.yaml create mode 100644 releasenotes/notes/bp-allow-overwrite-set-options-190a9c6904d53dab.yaml create mode 100644 releasenotes/notes/bp-network-command-options-2-e7b13a6a09f5d21e.yaml create mode 100644 releasenotes/notes/bp-support-no-property-in-aggregate-b74a42e00a65d14a.yaml create mode 100644 releasenotes/notes/bug-1630822-mask-password-on-debug-20dcdf1c54e84fa1.yaml delete mode 100644 releasenotes/notes/overwrite-options-for-subnet-76476127dcf321ad.yaml delete mode 100644 releasenotes/notes/support-no-property-in-aggregate-b74a42e00a65d14a.yaml diff --git a/doc/source/command-objects/router.rst b/doc/source/command-objects/router.rst index 335de17999..151639802c 100644 --- a/doc/source/command-objects/router.rst +++ b/doc/source/command-objects/router.rst @@ -63,7 +63,8 @@ Create new router os router create [--project [--project-domain ]] [--enable | --disable] - [--distributed] [--ha] + [--distributed] + [--ha] [--description ] [--availability-zone-hint ] diff --git a/releasenotes/notes/add-overwrite-option-190a9c6904d53dab.yaml b/releasenotes/notes/add-overwrite-option-190a9c6904d53dab.yaml deleted file mode 100644 index fdd61b523d..0000000000 --- a/releasenotes/notes/add-overwrite-option-190a9c6904d53dab.yaml +++ /dev/null @@ -1,6 +0,0 @@ ---- -features: - - | - ``port set`` command now allows the user to overwrite fixed-ips or binding-profile - of a port. - [ Blueprint `allow-overwrite-set-options ` _] \ No newline at end of file diff --git a/releasenotes/notes/bp-allow-overwrite-set-options-190a9c6904d53dab.yaml b/releasenotes/notes/bp-allow-overwrite-set-options-190a9c6904d53dab.yaml new file mode 100644 index 0000000000..bd7a7d73ac --- /dev/null +++ b/releasenotes/notes/bp-allow-overwrite-set-options-190a9c6904d53dab.yaml @@ -0,0 +1,16 @@ +--- +features: + - | + Allow ``--no-fixed-ip`` and ``--no-binding-profile`` options to + ``port set`` command to be specified when ``--fixed-ip`` and + ``--binding-profile`` are present. This allows the list of fixed + IPs and binding profiles to be cleared and replaced with new values + in a single command. + [Blueprint `allow-overwrite-set-options `_] + - | + Add ``--no-allocation-pool`` and ``--no-host-route`` options to + ``subnet set`` command that clears the respective values in the + specified subnet. This allows new values to replace the entire + list of existing values in a single command for allocation pools + and host routes. + [Blueprint `allow-overwrite-set-options `_] diff --git a/releasenotes/notes/bp-network-command-options-2-e7b13a6a09f5d21e.yaml b/releasenotes/notes/bp-network-command-options-2-e7b13a6a09f5d21e.yaml new file mode 100644 index 0000000000..d0ba7ff3b1 --- /dev/null +++ b/releasenotes/notes/bp-network-command-options-2-e7b13a6a09f5d21e.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--description`` option to ``network create`` and + ``network set`` commands. + [Blueprint `network-commands-options `_] diff --git a/releasenotes/notes/bp-neutron-client-descriptions-a80902b4295843cf.yaml b/releasenotes/notes/bp-neutron-client-descriptions-a80902b4295843cf.yaml index 3c4f4a504c..83a0090251 100644 --- a/releasenotes/notes/bp-neutron-client-descriptions-a80902b4295843cf.yaml +++ b/releasenotes/notes/bp-neutron-client-descriptions-a80902b4295843cf.yaml @@ -1,7 +1,9 @@ --- features: - | - Add ``--description`` option for core network resources. Allows users to - set a description for: ``floating ip create`` and ``router set/create``. + Add ``--description`` option to ``floating ip create`` command. [Blueprint :oscbp:`neutron-client-descriptions`] + - | + Add ``--description`` option to ``router set`` and + ``router create`` commands. [Blueprint :oscbp:`network-commands-options`] diff --git a/releasenotes/notes/bp-support-no-property-in-aggregate-b74a42e00a65d14a.yaml b/releasenotes/notes/bp-support-no-property-in-aggregate-b74a42e00a65d14a.yaml new file mode 100644 index 0000000000..5f9c1c40c0 --- /dev/null +++ b/releasenotes/notes/bp-support-no-property-in-aggregate-b74a42e00a65d14a.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Add ``--no-property`` option to ``aggregate set`` command. + This allows the property list to be cleared and replaced with + new values in a single command. + [Blueprint `support-no-property-in-aggregate `_] diff --git a/releasenotes/notes/bug-1588171-61214d0ea482988c.yaml b/releasenotes/notes/bug-1588171-61214d0ea482988c.yaml index 29b9168cca..7c32419bd6 100644 --- a/releasenotes/notes/bug-1588171-61214d0ea482988c.yaml +++ b/releasenotes/notes/bug-1588171-61214d0ea482988c.yaml @@ -1,5 +1,5 @@ --- fixes: - | - Update novaclient DEFAULT_API_VERSION to 2.1 from 2.0 - [Bug `1588171 `_] \ No newline at end of file + Update novaclient ``DEFAULT_API_VERSION`` from 2.0 to 2.1 + [Bug `1588171 `_] diff --git a/releasenotes/notes/bug-1609233-90b2ddf8d941050e.yaml b/releasenotes/notes/bug-1609233-90b2ddf8d941050e.yaml index 671a39b1f3..0f61edf10e 100644 --- a/releasenotes/notes/bug-1609233-90b2ddf8d941050e.yaml +++ b/releasenotes/notes/bug-1609233-90b2ddf8d941050e.yaml @@ -1,4 +1,6 @@ --- fixes: - - Fix the ``--class`` option not work when we set/show the quota class. - [Bug `1609233 `_] \ No newline at end of file + - | + Fix the ``--class`` option in ``quota set`` and ``quota show`` + commands to not perform a project lookup in Identity. + [Bug `1609233 `_] diff --git a/releasenotes/notes/bug-1614458-c42be5738f447db8.yaml b/releasenotes/notes/bug-1614458-c42be5738f447db8.yaml index 671882b0a8..15e0464f05 100644 --- a/releasenotes/notes/bug-1614458-c42be5738f447db8.yaml +++ b/releasenotes/notes/bug-1614458-c42be5738f447db8.yaml @@ -1,6 +1,6 @@ --- features: - | - Adds ``description`` option to ``subnet create`` and + Adds ``--description`` option to ``subnet create`` and ``subnet set`` commands. [Bug `1614458 `_] diff --git a/releasenotes/notes/bug-1614823-e89080342f25f2c0.yaml b/releasenotes/notes/bug-1614823-e89080342f25f2c0.yaml index 96c6661989..9e4057fcc6 100644 --- a/releasenotes/notes/bug-1614823-e89080342f25f2c0.yaml +++ b/releasenotes/notes/bug-1614823-e89080342f25f2c0.yaml @@ -1,6 +1,6 @@ --- features: - | - Adds ``description`` option to ``subnet pool create`` + Adds ``--description`` option to ``subnet pool create`` and ``subnet pool set`` commands. [Bug `1614823 `_] diff --git a/releasenotes/notes/bug-1627913-2adf4182977e5926.yaml b/releasenotes/notes/bug-1627913-2adf4182977e5926.yaml index 454d4c8958..f23c8d1f56 100644 --- a/releasenotes/notes/bug-1627913-2adf4182977e5926.yaml +++ b/releasenotes/notes/bug-1627913-2adf4182977e5926.yaml @@ -1,5 +1,8 @@ --- features: - - Add ``--source-replicated``, ``--consistency-group``, ``--hint`` and + - | + Add ``--source-replicated``, ``--consistency-group``, ``--hint`` and ``--multi-attach`` options to ``volume create`` command in volume v2. + Make ``--size`` optional when ``--snapshot``, ``--source`` or + ``source-replicated`` options are present. [Bug `1627913 `_] diff --git a/releasenotes/notes/bug-1630822-mask-password-on-debug-20dcdf1c54e84fa1.yaml b/releasenotes/notes/bug-1630822-mask-password-on-debug-20dcdf1c54e84fa1.yaml new file mode 100644 index 0000000000..1edb3a2e0c --- /dev/null +++ b/releasenotes/notes/bug-1630822-mask-password-on-debug-20dcdf1c54e84fa1.yaml @@ -0,0 +1,5 @@ +--- +security: + - | + Mask passwords when ``--debug`` or ``-vv`` options are used. + [Bug `1630822 `_] diff --git a/releasenotes/notes/overwrite-options-for-subnet-76476127dcf321ad.yaml b/releasenotes/notes/overwrite-options-for-subnet-76476127dcf321ad.yaml deleted file mode 100644 index 263cbdc923..0000000000 --- a/releasenotes/notes/overwrite-options-for-subnet-76476127dcf321ad.yaml +++ /dev/null @@ -1,6 +0,0 @@ ---- -features: - - | - ``subnet set`` command now allows the user to clear and overwrite - allocation-pool or host-route of a subnet. - [ Blueprint `allow-overwrite-set-options ` _] diff --git a/releasenotes/notes/support-no-property-in-aggregate-b74a42e00a65d14a.yaml b/releasenotes/notes/support-no-property-in-aggregate-b74a42e00a65d14a.yaml deleted file mode 100644 index 5a785e4ae6..0000000000 --- a/releasenotes/notes/support-no-property-in-aggregate-b74a42e00a65d14a.yaml +++ /dev/null @@ -1,7 +0,0 @@ ---- -features: - - | - Add ``--no-property`` option in ``aggregate set``. - Supporting ``--no-property`` option will apply user a convenience way to - clean all properties of aggregate in a short command. - [Blueprint `support-no-property-in-aggregate `_] diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index 7739e4b4f3..0941837950 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -21,6 +21,7 @@ OpenStack release was made is shown below: ================= ======================= OpenStack Release OpenStackClient Release ================= ======================= +Newton 3.2.0 Mitaka 2.3.0 Liberty 1.7.3 Kilo 1.0.6 From 407e164c9554ebb2249b0656eafd42e5a6874a41 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Sun, 9 Oct 2016 15:44:52 +0800 Subject: [PATCH 1291/3095] Refactor image v1 unit tests with FakeImage class Add FakeImage class, and refactor the unit tests with it in image v1. Change-Id: I9024ca5eca5c604e7588c1d905562bf6838309f1 Implements: bp improve-image-unittest-framework --- openstackclient/tests/unit/image/v1/fakes.py | 44 ++++ .../tests/unit/image/v1/test_image.py | 236 +++++++++++------- 2 files changed, 185 insertions(+), 95 deletions(-) diff --git a/openstackclient/tests/unit/image/v1/fakes.py b/openstackclient/tests/unit/image/v1/fakes.py index a8e52fa32c..080356ee6c 100644 --- a/openstackclient/tests/unit/image/v1/fakes.py +++ b/openstackclient/tests/unit/image/v1/fakes.py @@ -13,7 +13,9 @@ # under the License. # +import copy import mock +import uuid from openstackclient.tests.unit import fakes from openstackclient.tests.unit import utils @@ -74,3 +76,45 @@ def setUp(self): endpoint=fakes.AUTH_URL, token=fakes.AUTH_TOKEN, ) + + +class FakeImage(object): + """Fake one or more images.""" + + @staticmethod + def create_one_image(attrs=None): + """Create a fake image. + + :param Dictionary attrs: + A dictionary with all attrbutes of image + :return: + A FakeResource object with id, name, owner, protected, + visibility and tags attrs + """ + attrs = attrs or {} + + # Set default attribute + image_info = { + 'id': str(uuid.uuid4()), + 'name': 'image-name' + uuid.uuid4().hex, + 'owner': 'image-owner' + uuid.uuid4().hex, + 'container_format': '', + 'disk_format': '', + 'min_disk': 0, + 'min_ram': 0, + 'is_public': True, + 'protected': False, + 'properties': { + 'Alpha': 'a', + 'Beta': 'b', + 'Gamma': 'g'}, + } + + # Overwrite default attributes if there are some attributes set + image_info.update(attrs) + + image = fakes.FakeResource( + info=copy.deepcopy(image_info), + loaded=True) + + return image diff --git a/openstackclient/tests/unit/image/v1/test_image.py b/openstackclient/tests/unit/image/v1/test_image.py index a6bc80a08e..aef74f0467 100644 --- a/openstackclient/tests/unit/image/v1/test_image.py +++ b/openstackclient/tests/unit/image/v1/test_image.py @@ -17,6 +17,7 @@ import mock from osc_lib import exceptions +from osc_lib import utils from openstackclient.image.v1 import image from openstackclient.tests.unit import fakes @@ -35,25 +36,39 @@ def setUp(self): class TestImageCreate(TestImage): + new_image = image_fakes.FakeImage.create_one_image() + columns = ( + 'container_format', + 'disk_format', + 'id', + 'is_public', + 'min_disk', + 'min_ram', + 'name', + 'owner', + 'properties', + 'protected', + ) + data = ( + new_image.container_format, + new_image.disk_format, + new_image.id, + new_image.is_public, + new_image.min_disk, + new_image.min_ram, + new_image.name, + new_image.owner, + utils.format_dict(new_image.properties), + new_image.protected, + ) + def setUp(self): super(TestImageCreate, self).setUp() - self.images_mock.create.return_value = fakes.FakeResource( - None, - copy.deepcopy(image_fakes.IMAGE), - loaded=True, - ) + self.images_mock.create.return_value = self.new_image # This is the return value for utils.find_resource() - self.images_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(image_fakes.IMAGE), - loaded=True, - ) - self.images_mock.update.return_value = fakes.FakeResource( - None, - copy.deepcopy(image_fakes.IMAGE), - loaded=True, - ) + self.images_mock.get.return_value = self.new_image + self.images_mock.update.return_value = self.new_image # Get the command object to test self.cmd = image.CreateImage(self.app, None) @@ -65,12 +80,12 @@ def test_image_reserve_no_options(self): } self.images_mock.configure_mock(**mock_exception) arglist = [ - image_fakes.image_name, + self.new_image.name, ] verifylist = [ ('container_format', image.DEFAULT_CONTAINER_FORMAT), ('disk_format', image.DEFAULT_DISK_FORMAT), - ('name', image_fakes.image_name), + ('name', self.new_image.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -81,7 +96,7 @@ def test_image_reserve_no_options(self): # ImageManager.create(name=, **) self.images_mock.create.assert_called_with( - name=image_fakes.image_name, + name=self.new_image.name, container_format=image.DEFAULT_CONTAINER_FORMAT, disk_format=image.DEFAULT_DISK_FORMAT, data=mock.ANY, @@ -90,8 +105,8 @@ def test_image_reserve_no_options(self): # Verify update() was not called, if it was show the args self.assertEqual(self.images_mock.update.call_args_list, []) - self.assertEqual(image_fakes.IMAGE_columns, columns) - self.assertEqual(image_fakes.IMAGE_data, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) def test_image_reserve_options(self): mock_exception = { @@ -107,7 +122,7 @@ def test_image_reserve_options(self): '--protected', '--private', '--project', 'q', - image_fakes.image_name, + self.new_image.name, ] verifylist = [ ('container_format', 'ovf'), @@ -119,7 +134,7 @@ def test_image_reserve_options(self): ('public', False), ('private', True), ('project', 'q'), - ('name', image_fakes.image_name), + ('name', self.new_image.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -130,7 +145,7 @@ def test_image_reserve_options(self): # ImageManager.create(name=, **) self.images_mock.create.assert_called_with( - name=image_fakes.image_name, + name=self.new_image.name, container_format='ovf', disk_format='fs', min_disk=10, @@ -144,14 +159,14 @@ def test_image_reserve_options(self): # Verify update() was not called, if it was show the args self.assertEqual(self.images_mock.update.call_args_list, []) - self.assertEqual(image_fakes.IMAGE_columns, columns) - self.assertEqual(image_fakes.IMAGE_data, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) @mock.patch('openstackclient.image.v1.image.io.open', name='Open') def test_image_create_file(self, mock_open): mock_file = mock.Mock(name='File') mock_open.return_value = mock_file - mock_open.read.return_value = image_fakes.image_data + mock_open.read.return_value = self.data mock_exception = { 'find.side_effect': exceptions.CommandError('x'), 'get.side_effect': exceptions.CommandError('x'), @@ -164,7 +179,7 @@ def test_image_create_file(self, mock_open): '--public', '--property', 'Alpha=1', '--property', 'Beta=2', - image_fakes.image_name, + self.new_image.name, ] verifylist = [ ('file', 'filer'), @@ -173,7 +188,7 @@ def test_image_create_file(self, mock_open): ('public', True), ('private', False), ('properties', {'Alpha': '1', 'Beta': '2'}), - ('name', image_fakes.image_name), + ('name', self.new_image.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -193,7 +208,7 @@ def test_image_create_file(self, mock_open): # ImageManager.create(name=, **) self.images_mock.create.assert_called_with( - name=image_fakes.image_name, + name=self.new_image.name, container_format=image.DEFAULT_CONTAINER_FORMAT, disk_format=image.DEFAULT_DISK_FORMAT, protected=False, @@ -208,21 +223,19 @@ def test_image_create_file(self, mock_open): # Verify update() was not called, if it was show the args self.assertEqual(self.images_mock.update.call_args_list, []) - self.assertEqual(image_fakes.IMAGE_columns, columns) - self.assertEqual(image_fakes.IMAGE_data, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) class TestImageDelete(TestImage): + _image = image_fakes.FakeImage.create_one_image() + def setUp(self): super(TestImageDelete, self).setUp() # This is the return value for utils.find_resource() - self.images_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(image_fakes.IMAGE), - loaded=True, - ) + self.images_mock.get.return_value = self._image self.images_mock.delete.return_value = None # Get the command object to test @@ -230,21 +243,23 @@ def setUp(self): def test_image_delete_no_options(self): arglist = [ - image_fakes.image_id, + self._image.id, ] verifylist = [ - ('images', [image_fakes.image_id]), + ('images', [self._image.id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.images_mock.delete.assert_called_with(image_fakes.image_id) + self.images_mock.delete.assert_called_with(self._image.id) self.assertIsNone(result) class TestImageList(TestImage): + _image = image_fakes.FakeImage.create_one_image() + columns = ( 'ID', 'Name', @@ -252,18 +267,33 @@ class TestImageList(TestImage): ) datalist = ( ( - image_fakes.image_id, - image_fakes.image_name, + _image.id, + _image.name, '', ), ) + # create a image_info as the side_effect of the fake image_list() + info = { + 'id': _image.id, + 'name': _image.name, + 'owner': _image.owner, + 'container_format': _image.container_format, + 'disk_format': _image.disk_format, + 'min_disk': _image.min_disk, + 'min_ram': _image.min_ram, + 'is_public': _image.is_public, + 'protected': _image.protected, + 'properties': _image.properties, + } + image_info = copy.deepcopy(info) + def setUp(self): super(TestImageList, self).setUp() self.api_mock = mock.Mock() self.api_mock.image_list.side_effect = [ - [copy.deepcopy(image_fakes.IMAGE)], [], + [self.image_info], [], ] self.app.client_manager.image.api = self.api_mock @@ -285,7 +315,7 @@ def test_image_list_no_options(self): columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( detailed=True, - marker=image_fakes.image_id, + marker=self._image.id, ) self.assertEqual(self.columns, columns) @@ -309,7 +339,7 @@ def test_image_list_public_option(self): self.api_mock.image_list.assert_called_with( detailed=True, public=True, - marker=image_fakes.image_id, + marker=self._image.id, ) self.assertEqual(self.columns, columns) @@ -333,7 +363,7 @@ def test_image_list_private_option(self): self.api_mock.image_list.assert_called_with( detailed=True, private=True, - marker=image_fakes.image_id, + marker=self._image.id, ) self.assertEqual(self.columns, columns) @@ -354,7 +384,7 @@ def test_image_list_long_option(self): columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( detailed=True, - marker=image_fakes.image_id, + marker=self._image.id, ) collist = ( @@ -373,8 +403,8 @@ def test_image_list_long_option(self): self.assertEqual(collist, columns) datalist = (( - image_fakes.image_id, - image_fakes.image_name, + self._image.id, + self._image.name, '', '', '', @@ -382,7 +412,7 @@ def test_image_list_long_option(self): '', 'public', False, - image_fakes.image_owner, + self._image.owner, "Alpha='a', Beta='b', Gamma='g'", ), ) self.assertEqual(datalist, tuple(data)) @@ -390,7 +420,7 @@ def test_image_list_long_option(self): @mock.patch('openstackclient.api.utils.simple_filter') def test_image_list_property_option(self, sf_mock): sf_mock.side_effect = [ - [copy.deepcopy(image_fakes.IMAGE)], [], + [self.image_info], [], ] arglist = [ @@ -407,10 +437,10 @@ def test_image_list_property_option(self, sf_mock): columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( detailed=True, - marker=image_fakes.image_id, + marker=self._image.id, ) sf_mock.assert_called_with( - [image_fakes.IMAGE], + [self.image_info], attr='a', value='1', property_field='properties', @@ -422,7 +452,7 @@ def test_image_list_property_option(self, sf_mock): @mock.patch('osc_lib.utils.sort_items') def test_image_list_sort_option(self, si_mock): si_mock.side_effect = [ - [copy.deepcopy(image_fakes.IMAGE)], [], + [self.image_info], [], ] arglist = ['--sort', 'name:asc'] @@ -435,10 +465,10 @@ def test_image_list_sort_option(self, si_mock): columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( detailed=True, - marker=image_fakes.image_id, + marker=self._image.id, ) si_mock.assert_called_with( - [image_fakes.IMAGE], + [self.image_info], 'name:asc' ) @@ -448,36 +478,30 @@ def test_image_list_sort_option(self, si_mock): class TestImageSet(TestImage): + _image = image_fakes.FakeImage.create_one_image() + def setUp(self): super(TestImageSet, self).setUp() # This is the return value for utils.find_resource() - self.images_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(image_fakes.IMAGE), - loaded=True, - ) - self.images_mock.update.return_value = fakes.FakeResource( - None, - copy.deepcopy(image_fakes.IMAGE), - loaded=True, - ) + self.images_mock.get.return_value = self._image + self.images_mock.update.return_value = self._image # Get the command object to test self.cmd = image.SetImage(self.app, None) def test_image_set_no_options(self): arglist = [ - image_fakes.image_name, + self._image.name, ] verifylist = [ - ('image', image_fakes.image_name), + ('image', self._image.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.images_mock.update.assert_called_with(image_fakes.image_id, + self.images_mock.update.assert_called_with(self._image.id, **{}) self.assertIsNone(result) @@ -490,7 +514,7 @@ def test_image_set_options(self): '--disk-format', 'vmdk', '--size', '35165824', '--project', 'new-owner', - image_fakes.image_name, + self._image.name, ] verifylist = [ ('name', 'new-name'), @@ -500,7 +524,7 @@ def test_image_set_options(self): ('disk_format', 'vmdk'), ('size', 35165824), ('project', 'new-owner'), - ('image', image_fakes.image_name), + ('image', self._image.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -517,7 +541,7 @@ def test_image_set_options(self): } # ImageManager.update(image, **kwargs) self.images_mock.update.assert_called_with( - image_fakes.image_id, + self._image.id, **kwargs ) self.assertIsNone(result) @@ -526,14 +550,14 @@ def test_image_set_bools1(self): arglist = [ '--protected', '--private', - image_fakes.image_name, + self._image.name, ] verifylist = [ ('protected', True), ('unprotected', False), ('public', False), ('private', True), - ('image', image_fakes.image_name), + ('image', self._image.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -545,7 +569,7 @@ def test_image_set_bools1(self): } # ImageManager.update(image, **kwargs) self.images_mock.update.assert_called_with( - image_fakes.image_id, + self._image.id, **kwargs ) self.assertIsNone(result) @@ -554,14 +578,14 @@ def test_image_set_bools2(self): arglist = [ '--unprotected', '--public', - image_fakes.image_name, + self._image.name, ] verifylist = [ ('protected', False), ('unprotected', True), ('public', True), ('private', False), - ('image', image_fakes.image_name), + ('image', self._image.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -573,7 +597,7 @@ def test_image_set_bools2(self): } # ImageManager.update(image, **kwargs) self.images_mock.update.assert_called_with( - image_fakes.image_id, + self._image.id, **kwargs ) self.assertIsNone(result) @@ -582,11 +606,11 @@ def test_image_set_properties(self): arglist = [ '--property', 'Alpha=1', '--property', 'Beta=2', - image_fakes.image_name, + self._image.name, ] verifylist = [ ('properties', {'Alpha': '1', 'Beta': '2'}), - ('image', image_fakes.image_name), + ('image', self._image.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -601,7 +625,7 @@ def test_image_set_properties(self): } # ImageManager.update(image, **kwargs) self.images_mock.update.assert_called_with( - image_fakes.image_id, + self._image.id, **kwargs ) self.assertIsNone(result) @@ -624,7 +648,7 @@ def test_image_update_volume(self): "volume_type": 'volume_type', "container_format": image.DEFAULT_CONTAINER_FORMAT, "disk_format": image.DEFAULT_DISK_FORMAT, - "image": image_fakes.image_name, + "image": self._image.name, } full_response = {"os-volume_upload_image": response} volumes_mock.upload_to_image.return_value = (201, full_response) @@ -632,7 +656,7 @@ def test_image_update_volume(self): arglist = [ '--volume', 'volly', '--name', 'updated_image', - image_fakes.image_name, + self._image.name, ] verifylist = [ ('private', False), @@ -642,7 +666,7 @@ def test_image_update_volume(self): ('volume', 'volly'), ('force', False), ('name', 'updated_image'), - ('image', image_fakes.image_name), + ('image', self._image.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -653,13 +677,13 @@ def test_image_update_volume(self): volumes_mock.upload_to_image.assert_called_with( 'vol1', False, - image_fakes.image_name, + self._image.name, '', '', ) # ImageManager.update(image_id, remove_props=, **) self.images_mock.update.assert_called_with( - image_fakes.image_id, + self._image.id, name='updated_image', volume='volly', ) @@ -668,24 +692,46 @@ def test_image_update_volume(self): class TestImageShow(TestImage): + _image = image_fakes.FakeImage.create_one_image() + columns = ( + 'container_format', + 'disk_format', + 'id', + 'is_public', + 'min_disk', + 'min_ram', + 'name', + 'owner', + 'properties', + 'protected', + ) + data = ( + _image.container_format, + _image.disk_format, + _image.id, + _image.is_public, + _image.min_disk, + _image.min_ram, + _image.name, + _image.owner, + utils.format_dict(_image.properties), + _image.protected, + ) + def setUp(self): super(TestImageShow, self).setUp() - self.images_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(image_fakes.IMAGE), - loaded=True, - ) + self.images_mock.get.return_value = self._image # Get the command object to test self.cmd = image.ShowImage(self.app, None) def test_image_show(self): arglist = [ - image_fakes.image_id, + self._image.id, ] verifylist = [ - ('image', image_fakes.image_id), + ('image', self._image.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -694,8 +740,8 @@ def test_image_show(self): # data to be shown. columns, data = self.cmd.take_action(parsed_args) self.images_mock.get.assert_called_with( - image_fakes.image_id, + self._image.id, ) - self.assertEqual(image_fakes.IMAGE_columns, columns) - self.assertEqual(image_fakes.IMAGE_data, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) From 29f78500dba1aa258e4e94e331a7a48c38ce58e2 Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Fri, 23 Sep 2016 15:26:48 +0800 Subject: [PATCH 1292/3095] Redefine set/unset command devref Now we have some use cases about set/unset properties, try to redefine devref to find out a best and easiest solution to keep commands simple and clearly. Five use cases exist in property action, "append", "update", "remove", "clean", "override", the following rules can cover all these use cases: 1. append ==> "set --property new-key=value" 2. update ==> "set --property existed-key=new-value" 3. remove ==> "unset --property existed-key" 4. clean ==> "set --no-property" 5. clean ==> "unset --all-property" 6. override ==> "set --no-property --property new-key=value" Related blueprint support-no-property-in-aggregate and blueprint allow-overwrite-set-options. Change-Id: If86daf6989d8e0ad0dc6e590d7636be7d5203a18 --- doc/source/command-options.rst | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/doc/source/command-options.rst b/doc/source/command-options.rst index a833d1d5b7..c850b000cf 100644 --- a/doc/source/command-options.rst +++ b/doc/source/command-options.rst @@ -118,12 +118,12 @@ Some options can be repeated to build a collection of values for a property. Adding a value to the collection must be provided via the ``set`` action. Removing a value from the collection must be provided via an ``unset`` action. As a convenience, removing all values from the collection may be provided via a -``--no`` option on the ``set`` and ``unset`` actions. If both ``--no`` option -and option are specified, the values specified on the command would overwrite -the collection property instead of appending on the ``set`` action. The -``--no`` option must be part of a mutually exclusive group with the related -property option on the ``unset`` action, overwrite case don't exist in -``unset`` action. +``--no`` option on the ``set`` action and a ``--all`` option on ``unset`` +action. If both ``--no`` option and option are specified, the values specified +on the command would overwrite the collection property instead of appending on +the ``set`` action. The ``--all`` option must be part of a mutually exclusive +group with the related property option on the ``unset`` action, overwrite case +don't exist in ``unset`` action. An example behavior for ``set`` action: @@ -165,7 +165,9 @@ An example parser declaration for `set` action: '--no-example-property', dest='no_example_property', action='store_true', - help=_('Remove all example properties for this '), + help=_('Remove all example properties for this ' + '(specify both --example-property and --no-example-property' + ' to overwrite the current example properties)'), ) An example handler in `take_action()` for `set` action: @@ -194,8 +196,8 @@ An example parser declaration for `unset` action: '(repeat option to remove multiple properties)'), ) example_property_group.add_argument( - '--no-example-property', - dest='no_example_property', + '--all-example-property', + dest='all_example_property', action='store_true', help=_('Remove all example properties for this '), ) @@ -208,7 +210,7 @@ An example handler in `take_action()` for `unset` action: kwargs['example_property'] = \ list(set(resource_example_property) - \ set(parsed_args.example_property)) - if parsed_args.no_example_property: + if parsed_args.all_example_property: kwargs['example_property'] = [] Required Options From 2bbb482106d9ec97537d4604096a3fca1ddae2d4 Mon Sep 17 00:00:00 2001 From: "Choe, Cheng-Dae" Date: Sat, 8 Oct 2016 02:50:25 +0900 Subject: [PATCH 1293/3095] Fix router unset --route option Fix the "--route" option one the "os route unset" command. The option did not convert gateway to nexthop which results "Router does not contain route" error. Change-Id: Ia57bc7ea77ad7c6030535180a6ce42b4928c9e56 Closes-Bug: 1631471 --- openstackclient/network/v2/router.py | 3 +-- openstackclient/tests/unit/network/v2/test_router.py | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 48a3a92cd3..64bb881911 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -520,12 +520,11 @@ def take_action(self, parsed_args): if parsed_args.routes: try: for route in parsed_args.routes: + route['nexthop'] = route.pop('gateway') tmp_routes.remove(route) except ValueError: msg = (_("Router does not contain route %s") % route) raise exceptions.CommandError(msg) - for route in tmp_routes: - route['nexthop'] = route.pop('gateway') attrs['routes'] = tmp_routes if attrs: client.update_router(obj, **attrs) diff --git a/openstackclient/tests/unit/network/v2/test_router.py b/openstackclient/tests/unit/network/v2/test_router.py index 6a44586221..d85561bc12 100644 --- a/openstackclient/tests/unit/network/v2/test_router.py +++ b/openstackclient/tests/unit/network/v2/test_router.py @@ -773,9 +773,9 @@ def setUp(self): super(TestUnsetRouter, self).setUp() self._testrouter = network_fakes.FakeRouter.create_one_router( {'routes': [{"destination": "192.168.101.1/24", - "gateway": "172.24.4.3"}, + "nexthop": "172.24.4.3"}, {"destination": "192.168.101.2/24", - "gateway": "172.24.4.3"}], }) + "nexthop": "172.24.4.3"}], }) self.fake_subnet = network_fakes.FakeSubnet.create_one_subnet() self.network.find_router = mock.Mock(return_value=self._testrouter) self.network.update_router = mock.Mock(return_value=None) From 3205dad161c6c7bcaf16f985121217b8cf320af0 Mon Sep 17 00:00:00 2001 From: Rodolfo Alonso Hernandez Date: Tue, 2 Aug 2016 09:42:21 +0100 Subject: [PATCH 1294/3095] Add network support for Network QoS policies Added following commands: - network qos policy create - network qos policy delete - network qos policy set - network qos policy show - network qos policy list Closes-Bug: 1609037 Depends-On: I33bafeca979410d329ae10a82772ccdb48c10daa Change-Id: I63a8f63702514ff5814481bb021e2aa9d5f3d4b1 --- .../command-objects/network-qos-policy.rst | 122 ++++++ doc/source/commands.rst | 1 + .../network/v2/network_qos_policy.py | 231 +++++++++++ .../network/v2/test_network_qos_policy.py | 55 +++ .../tests/unit/network/v2/fakes.py | 150 +++++++ .../network/v2/test_network_qos_policy.py | 380 ++++++++++++++++++ ...d-network-qos-policy-b8ad1e408d73c279.yaml | 8 + setup.cfg | 6 + 8 files changed, 953 insertions(+) create mode 100644 doc/source/command-objects/network-qos-policy.rst create mode 100644 openstackclient/network/v2/network_qos_policy.py create mode 100644 openstackclient/tests/functional/network/v2/test_network_qos_policy.py create mode 100644 openstackclient/tests/unit/network/v2/test_network_qos_policy.py create mode 100644 releasenotes/notes/add-network-qos-policy-b8ad1e408d73c279.yaml diff --git a/doc/source/command-objects/network-qos-policy.rst b/doc/source/command-objects/network-qos-policy.rst new file mode 100644 index 0000000000..f1e549766b --- /dev/null +++ b/doc/source/command-objects/network-qos-policy.rst @@ -0,0 +1,122 @@ +================== +network qos policy +================== + +A **Network QoS policy** groups a number of Network QoS rules, applied to a +network or a port. + +Network v2 + +network qos policy create +------------------------- + +Create new Network QoS policy + +.. program:: network qos policy create +.. code:: bash + + os network qos policy create + [--description ] + [--share | --no-share] + [--project ] + [--project-domain ] + + +.. option:: --description + + Description of the QoS policy + +.. option:: --share + + Make the QoS policy accessible by other projects + +.. option:: --no-share + + Make the QoS policy not accessible by other projects (default) + +.. option:: --project + + Owner's project (name or ID) + +.. option:: --project-domain ] + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + +.. describe:: + + New QoS policy specification name + +network qos policy delete +------------------------- + +Delete Network QoS policy + +.. program:: network qos policy delete +.. code:: bash + + os network qos policy delete + [ ...] + +.. describe:: + + Network QoS policy(s) to delete (name or ID) + +network qos policy list +----------------------- + +List Network QoS policies + +.. program:: network qos policy list +.. code:: bash + + os network qos policy list + +network qos policy set +---------------------- + +Set Network QoS policy properties + +.. program:: network qos policy set +.. code:: bash + + os network qos policy set + [--name ] + [--description ] + [--share | --no-share] + + +.. option:: --name + + Name of the QoS policy + +.. option:: --description + + Description of the QoS policy + +.. option:: --share + + Make the QoS policy accessible by other projects + +.. option:: --no-share + + Make the QoS policy not accessible by other projects + +.. describe:: + + Network QoS policy(s) to delete (name or ID) + +network qos policy show +----------------------- + +Display Network QoS policy details + +.. program:: network qos policy show +.. code:: bash + + os network qos policy show + + +.. describe:: + + Network QoS policy(s) to show (name or ID) diff --git a/doc/source/commands.rst b/doc/source/commands.rst index bf2c322a21..53272f68ea 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -112,6 +112,7 @@ referring to both Compute and Volume quotas. * ``network``: (**Compute**, **Network**) - a virtual network for connecting servers and other resources * ``network agent``: (**Network**) - A network agent is an agent that handles various tasks used to implement virtual networks * ``network rbac``: (**Network**) - an RBAC policy for network resources +* ``network qos policy``: (**Network**) - a QoS policy for network resources * ``network segment``: (**Network**) - a segment of a virtual network * ``object``: (**Object Storage**) a single file in the Object Storage * ``object store account``: (**Object Storage**) owns a group of Object Storage resources diff --git a/openstackclient/network/v2/network_qos_policy.py b/openstackclient/network/v2/network_qos_policy.py new file mode 100644 index 0000000000..a8fcfc592a --- /dev/null +++ b/openstackclient/network/v2/network_qos_policy.py @@ -0,0 +1,231 @@ +# 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 logging + +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils + +from openstackclient.i18n import _ +from openstackclient.identity import common as identity_common + + +LOG = logging.getLogger(__name__) + + +def _get_columns(item): + columns = list(item.keys()) + if 'tenant_id' in columns: + columns.remove('tenant_id') + columns.append('project_id') + return tuple(sorted(columns)) + + +def _get_attrs(client_manager, parsed_args): + attrs = {} + if parsed_args.name is not None: + attrs['name'] = str(parsed_args.name) + if parsed_args.description is not None: + attrs['description'] = parsed_args.description + if parsed_args.share: + attrs['shared'] = True + if parsed_args.no_share: + attrs['shared'] = False + if parsed_args.project is not None: + identity_client = client_manager.identity + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + attrs['tenant_id'] = project_id + + return attrs + + +class CreateNetworkQosPolicy(command.ShowOne): + """Create a QoS policy""" + + def get_parser(self, prog_name): + parser = super(CreateNetworkQosPolicy, self).get_parser(prog_name) + parser.add_argument( + 'name', + metavar='', + help=_("Name of QoS policy to create") + ) + parser.add_argument( + '--description', + metavar='', + help=_("Description of the QoS policy") + ) + share_group = parser.add_mutually_exclusive_group() + share_group.add_argument( + '--share', + action='store_true', + default=None, + help=_("Make the QoS policy accessible by other projects") + ) + share_group.add_argument( + '--no-share', + action='store_true', + help=_("Make the QoS policy not accessible by other projects " + "(default)") + ) + parser.add_argument( + '--project', + metavar='', + help=_("Owner's project (name or ID)") + ) + identity_common.add_project_domain_option_to_parser(parser) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + attrs = _get_attrs(self.app.client_manager, parsed_args) + obj = client.create_qos_policy(**attrs) + columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns, formatters={}) + return columns, data + + +class DeleteNetworkQosPolicy(command.Command): + """Delete Qos Policy(s)""" + + def get_parser(self, prog_name): + parser = super(DeleteNetworkQosPolicy, self).get_parser(prog_name) + parser.add_argument( + 'policy', + metavar="", + nargs="+", + help=_("QoS policy(s) to delete (name or ID)") + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + result = 0 + + for policy in parsed_args.policy: + try: + obj = client.find_qos_policy(policy, ignore_missing=False) + client.delete_qos_policy(obj) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete QoS policy " + "name or ID '%(qos_policy)s': %(e)s"), + {'qos_policy': policy, 'e': e}) + + if result > 0: + total = len(parsed_args.policy) + msg = (_("%(result)s of %(total)s QoS policies failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) + + +class ListNetworkQosPolicy(command.Lister): + """List QoS policies""" + + def take_action(self, parsed_args): + client = self.app.client_manager.network + columns = ( + 'id', + 'name', + 'shared', + 'tenant_id', + ) + column_headers = ( + 'ID', + 'Name', + 'Shared', + 'Project', + ) + data = client.qos_policies() + + return (column_headers, + (utils.get_item_properties( + s, columns, formatters={}, + ) for s in data)) + + +class SetNetworkQosPolicy(command.Command): + """Set QoS policy properties""" + + def get_parser(self, prog_name): + parser = super(SetNetworkQosPolicy, self).get_parser(prog_name) + parser.add_argument( + 'policy', + metavar="", + help=_("QoS policy to modify (name or ID)") + ) + parser.add_argument( + '--name', + metavar="", + help=_('Set QoS policy name') + ) + parser.add_argument( + '--description', + metavar='', + help=_("Description of the QoS policy") + ) + enable_group = parser.add_mutually_exclusive_group() + enable_group.add_argument( + '--share', + action='store_true', + help=_('Make the QoS policy accessible by other projects'), + ) + enable_group.add_argument( + '--no-share', + action='store_true', + help=_('Make the QoS policy not accessible by other projects'), + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.find_qos_policy( + parsed_args.policy, + ignore_missing=False) + attrs = {} + if parsed_args.name is not None: + attrs['name'] = parsed_args.name + if parsed_args.share: + attrs['shared'] = True + if parsed_args.no_share: + attrs['shared'] = False + if parsed_args.description is not None: + attrs['description'] = parsed_args.description + client.update_qos_policy(obj, **attrs) + + +class ShowNetworkQosPolicy(command.ShowOne): + """Display QoS policy details""" + + def get_parser(self, prog_name): + parser = super(ShowNetworkQosPolicy, self).get_parser(prog_name) + parser.add_argument( + 'policy', + metavar="", + help=_("QoS policy to display (name or ID)") + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.find_qos_policy(parsed_args.policy, + ignore_missing=False) + columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns) + return columns, data diff --git a/openstackclient/tests/functional/network/v2/test_network_qos_policy.py b/openstackclient/tests/functional/network/v2/test_network_qos_policy.py new file mode 100644 index 0000000000..07dea31b76 --- /dev/null +++ b/openstackclient/tests/functional/network/v2/test_network_qos_policy.py @@ -0,0 +1,55 @@ +# 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 uuid + +from openstackclient.tests.functional import base + + +class QosPolicyTests(base.TestCase): + """Functional tests for QoS policy. """ + NAME = uuid.uuid4().hex + HEADERS = ['Name'] + FIELDS = ['name'] + + @classmethod + def setUpClass(cls): + opts = cls.get_opts(cls.FIELDS) + raw_output = cls.openstack('network qos policy create ' + cls.NAME + + opts) + cls.assertOutput(cls.NAME + "\n", raw_output) + + @classmethod + def tearDownClass(cls): + raw_output = cls.openstack('network qos policy delete ' + cls.NAME) + cls.assertOutput('', raw_output) + + def test_qos_policy_list(self): + opts = self.get_opts(self.HEADERS) + raw_output = self.openstack('network qos policy list' + opts) + self.assertIn(self.NAME, raw_output) + + def test_qos_policy_show(self): + opts = self.get_opts(self.FIELDS) + raw_output = self.openstack('network qos policy show ' + self.NAME + + opts) + self.assertEqual(self.NAME + "\n", raw_output) + + def test_qos_policy_set(self): + self.openstack('network qos policy set --share ' + self.NAME) + opts = self.get_opts(['shared']) + raw_output = self.openstack('network qos policy show ' + self.NAME + + opts) + self.assertEqual("True\n", raw_output) diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index cea0028218..94727ae3ca 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -633,6 +633,156 @@ def get_network_rbacs(rbac_policies=None, count=2): return mock.Mock(side_effect=rbac_policies) +class FakeNetworkQosBandwidthLimitRule(object): + """Fake one or more QoS bandwidth limit rules.""" + + @staticmethod + def create_one_qos_bandwidth_limit_rule(attrs=None): + """Create a fake QoS bandwidth limit rule. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object with id, qos_policy_id, max_kbps and + max_burst_kbps attributes. + """ + attrs = attrs or {} + + # Set default attributes. + qos_bandwidth_limit_rule_attrs = { + 'id': 'qos-bandwidth-limit-rule-id-' + uuid.uuid4().hex, + 'qos_policy_id': 'qos-policy-id-' + uuid.uuid4().hex, + 'max_kbps': 1500, + 'max_burst_kbps': 1200, + } + + # Overwrite default attributes. + qos_bandwidth_limit_rule_attrs.update(attrs) + + qos_bandwidth_limit_rule = fakes.FakeResource( + info=copy.deepcopy(qos_bandwidth_limit_rule_attrs), + loaded=True) + + return qos_bandwidth_limit_rule + + @staticmethod + def create_qos_bandwidth_limit_rules(attrs=None, count=2): + """Create multiple fake QoS bandwidth limit rules. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of QoS bandwidth limit rules to fake + :return: + A list of FakeResource objects faking the QoS bandwidth limit rules + """ + qos_policies = [] + for i in range(0, count): + qos_policies.append(FakeNetworkQosBandwidthLimitRule. + create_one_qos_bandwidth_limit_rule(attrs)) + + return qos_policies + + @staticmethod + def get_qos_bandwidth_limit_rules(qos_rules=None, count=2): + """Get a list of faked QoS bandwidth limit rules. + + If QoS bandwidth limit rules list is provided, then initialize the + Mock object with the list. Otherwise create one. + + :param List address scopes: + A list of FakeResource objects faking QoS bandwidth limit rules + :param int count: + The number of QoS bandwidth limit rules to fake + :return: + An iterable Mock object with side_effect set to a list of faked + qos bandwidth limit rules + """ + if qos_rules is None: + qos_rules = (FakeNetworkQosBandwidthLimitRule. + create_qos_bandwidth_limit_rules(count)) + return mock.Mock(side_effect=qos_rules) + + +class FakeNetworkQosPolicy(object): + """Fake one or more QoS policies.""" + + @staticmethod + def create_one_qos_policy(attrs=None): + """Create a fake QoS policy. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object with name, id, etc. + """ + attrs = attrs or {} + qos_id = attrs.get('id') or 'qos-policy-id-' + uuid.uuid4().hex + rule_attrs = {'qos_policy_id': qos_id} + rules = [ + FakeNetworkQosBandwidthLimitRule. + create_one_qos_bandwidth_limit_rule(rule_attrs)] + + # Set default attributes. + qos_policy_attrs = { + 'name': 'qos-policy-name-' + uuid.uuid4().hex, + 'id': qos_id, + 'tenant_id': 'project-id-' + uuid.uuid4().hex, + 'shared': False, + 'description': 'qos-policy-description-' + uuid.uuid4().hex, + 'rules': rules, + } + + # Overwrite default attributes. + qos_policy_attrs.update(attrs) + + qos_policy = fakes.FakeResource( + info=copy.deepcopy(qos_policy_attrs), + loaded=True) + + # Set attributes with special mapping in OpenStack SDK. + qos_policy.project_id = qos_policy_attrs['tenant_id'] + + return qos_policy + + @staticmethod + def create_qos_policies(attrs=None, count=2): + """Create multiple fake QoS policies. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of QoS policies to fake + :return: + A list of FakeResource objects faking the QoS policies + """ + qos_policies = [] + for i in range(0, count): + qos_policies.append( + FakeNetworkQosPolicy.create_one_qos_policy(attrs)) + + return qos_policies + + @staticmethod + def get_qos_policies(qos_policies=None, count=2): + """Get an iterable MagicMock object with a list of faked QoS policies. + + If qos policies list is provided, then initialize the Mock object + with the list. Otherwise create one. + + :param List address scopes: + A list of FakeResource objects faking qos policies + :param int count: + The number of QoS policies to fake + :return: + An iterable Mock object with side_effect set to a list of faked + QoS policies + """ + if qos_policies is None: + qos_policies = FakeNetworkQosPolicy.create_qos_policies(count) + return mock.Mock(side_effect=qos_policies) + + class FakeRouter(object): """Fake one or more routers.""" diff --git a/openstackclient/tests/unit/network/v2/test_network_qos_policy.py b/openstackclient/tests/unit/network/v2/test_network_qos_policy.py new file mode 100644 index 0000000000..bd30579af7 --- /dev/null +++ b/openstackclient/tests/unit/network/v2/test_network_qos_policy.py @@ -0,0 +1,380 @@ +# 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 mock import call + +from osc_lib import exceptions + +from openstackclient.network.v2 import network_qos_policy +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3 +from openstackclient.tests.unit.network.v2 import fakes as network_fakes +from openstackclient.tests.unit import utils as tests_utils + + +class TestQosPolicy(network_fakes.TestNetworkV2): + + def setUp(self): + super(TestQosPolicy, self).setUp() + # Get a shortcut to the network client + self.network = self.app.client_manager.network + # Get a shortcut to the ProjectManager Mock + self.projects_mock = self.app.client_manager.identity.projects + + +class TestCreateNetworkQosPolicy(TestQosPolicy): + + project = identity_fakes_v3.FakeProject.create_one_project() + + # The new qos policy created. + new_qos_policy = ( + network_fakes.FakeNetworkQosPolicy.create_one_qos_policy( + attrs={ + 'tenant_id': project.id, + } + )) + columns = ( + 'description', + 'id', + 'name', + 'project_id', + 'rules', + 'shared', + ) + + data = ( + new_qos_policy.description, + new_qos_policy.id, + new_qos_policy.name, + new_qos_policy.project_id, + new_qos_policy.rules, + new_qos_policy.shared, + ) + + def setUp(self): + super(TestCreateNetworkQosPolicy, self).setUp() + self.network.create_qos_policy = mock.Mock( + return_value=self.new_qos_policy) + + # Get the command object to test + self.cmd = network_qos_policy.CreateNetworkQosPolicy( + self.app, self.namespace) + + self.projects_mock.get.return_value = self.project + + def test_create_no_options(self): + arglist = [] + verifylist = [] + + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_create_default_options(self): + arglist = [ + self.new_qos_policy.name, + ] + verifylist = [ + ('project', None), + ('name', self.new_qos_policy.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_qos_policy.assert_called_once_with(**{ + 'name': self.new_qos_policy.name + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_all_options(self): + arglist = [ + '--share', + '--project', self.project.name, + self.new_qos_policy.name, + '--description', 'QoS policy description', + ] + verifylist = [ + ('share', True), + ('project', self.project.name), + ('name', self.new_qos_policy.name), + ('description', 'QoS policy description'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_qos_policy.assert_called_once_with(**{ + 'shared': True, + 'tenant_id': self.project.id, + 'name': self.new_qos_policy.name, + 'description': 'QoS policy description', + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestDeleteNetworkQosPolicy(TestQosPolicy): + + # The address scope to delete. + _qos_policies = ( + network_fakes.FakeNetworkQosPolicy.create_qos_policies(count=2)) + + def setUp(self): + super(TestDeleteNetworkQosPolicy, self).setUp() + self.network.delete_qos_policy = mock.Mock(return_value=None) + self.network.find_qos_policy = ( + network_fakes.FakeNetworkQosPolicy.get_qos_policies( + qos_policies=self._qos_policies) + ) + + # Get the command object to test + self.cmd = network_qos_policy.DeleteNetworkQosPolicy( + self.app, self.namespace) + + def test_qos_policy_delete(self): + arglist = [ + self._qos_policies[0].name, + ] + verifylist = [ + ('policy', [self._qos_policies[0].name]), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.network.find_qos_policy.assert_called_once_with( + self._qos_policies[0].name, ignore_missing=False) + self.network.delete_qos_policy.assert_called_once_with( + self._qos_policies[0]) + self.assertIsNone(result) + + def test_multi_qos_policies_delete(self): + arglist = [] + + for a in self._qos_policies: + arglist.append(a.name) + verifylist = [ + ('policy', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [] + for a in self._qos_policies: + calls.append(call(a)) + self.network.delete_qos_policy.assert_has_calls(calls) + self.assertIsNone(result) + + def test_multi_qos_policies_delete_with_exception(self): + arglist = [ + self._qos_policies[0].name, + 'unexist_qos_policy', + ] + verifylist = [ + ('policy', + [self._qos_policies[0].name, 'unexist_qos_policy']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self._qos_policies[0], exceptions.CommandError] + self.network.find_qos_policy = ( + mock.MagicMock(side_effect=find_mock_result) + ) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 QoS policies failed to delete.', str(e)) + + self.network.find_qos_policy.assert_any_call( + self._qos_policies[0].name, ignore_missing=False) + self.network.find_qos_policy.assert_any_call( + 'unexist_qos_policy', ignore_missing=False) + self.network.delete_qos_policy.assert_called_once_with( + self._qos_policies[0] + ) + + +class TestListNetworkQosPolicy(TestQosPolicy): + + # The QoS policies to list up. + qos_policies = ( + network_fakes.FakeNetworkQosPolicy.create_qos_policies(count=3)) + columns = ( + 'ID', + 'Name', + 'Shared', + 'Project', + ) + data = [] + for qos_policy in qos_policies: + data.append(( + qos_policy.id, + qos_policy.name, + qos_policy.shared, + qos_policy.project_id, + )) + + def setUp(self): + super(TestListNetworkQosPolicy, self).setUp() + self.network.qos_policies = mock.Mock(return_value=self.qos_policies) + + # Get the command object to test + self.cmd = network_qos_policy.ListNetworkQosPolicy(self.app, + self.namespace) + + def test_qos_policy_list(self): + arglist = [] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.qos_policies.assert_called_once_with(**{}) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + +class TestSetNetworkQosPolicy(TestQosPolicy): + + # The QoS policy to set. + _qos_policy = network_fakes.FakeNetworkQosPolicy.create_one_qos_policy() + + def setUp(self): + super(TestSetNetworkQosPolicy, self).setUp() + self.network.update_qos_policy = mock.Mock(return_value=None) + self.network.find_qos_policy = mock.Mock( + return_value=self._qos_policy) + + # Get the command object to test + self.cmd = network_qos_policy.SetNetworkQosPolicy(self.app, + self.namespace) + + def test_set_nothing(self): + arglist = [self._qos_policy.name, ] + verifylist = [ + ('policy', self._qos_policy.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = {} + self.network.update_qos_policy.assert_called_with( + self._qos_policy, **attrs) + self.assertIsNone(result) + + def test_set_name_share_description(self): + arglist = [ + '--name', 'new_qos_policy', + '--share', + '--description', 'QoS policy description', + self._qos_policy.name, + ] + verifylist = [ + ('name', 'new_qos_policy'), + ('share', True), + ('description', 'QoS policy description'), + ('policy', self._qos_policy.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + attrs = { + 'name': 'new_qos_policy', + 'description': 'QoS policy description', + 'shared': True, + } + self.network.update_qos_policy.assert_called_with( + self._qos_policy, **attrs) + self.assertIsNone(result) + + def test_set_no_share(self): + arglist = [ + '--no-share', + self._qos_policy.name, + ] + verifylist = [ + ('no_share', True), + ('policy', self._qos_policy.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + attrs = { + 'shared': False + } + self.network.update_qos_policy.assert_called_with( + self._qos_policy, **attrs) + self.assertIsNone(result) + + +class TestShowNetworkQosPolicy(TestQosPolicy): + + # The QoS policy to show. + _qos_policy = ( + network_fakes.FakeNetworkQosPolicy.create_one_qos_policy()) + columns = ( + 'description', + 'id', + 'name', + 'project_id', + 'rules', + 'shared', + ) + data = ( + _qos_policy.description, + _qos_policy.id, + _qos_policy.name, + _qos_policy.project_id, + _qos_policy.rules, + _qos_policy.shared, + ) + + def setUp(self): + super(TestShowNetworkQosPolicy, self).setUp() + self.network.find_qos_policy = mock.Mock(return_value=self._qos_policy) + + # Get the command object to test + self.cmd = network_qos_policy.ShowNetworkQosPolicy(self.app, + self.namespace) + + def test_show_no_options(self): + arglist = [] + verifylist = [] + + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_show_all_options(self): + arglist = [ + self._qos_policy.name, + ] + verifylist = [ + ('policy', self._qos_policy.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.find_qos_policy.assert_called_once_with( + self._qos_policy.name, ignore_missing=False) + self.assertEqual(self.columns, columns) + self.assertEqual(list(self.data), list(data)) diff --git a/releasenotes/notes/add-network-qos-policy-b8ad1e408d73c279.yaml b/releasenotes/notes/add-network-qos-policy-b8ad1e408d73c279.yaml new file mode 100644 index 0000000000..baff14ebf3 --- /dev/null +++ b/releasenotes/notes/add-network-qos-policy-b8ad1e408d73c279.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Add support for Network QoS policies commands: + ``network qos policy create``, ``network qos policy delete``, + ``network qos policy list``, ``network qos policy show`` and + ``network qos policy set`` + [Bug `1609037 `_] diff --git a/setup.cfg b/setup.cfg index 2aa8874019..a4abec1bc5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -366,6 +366,12 @@ openstack.network.v2 = network_set = openstackclient.network.v2.network:SetNetwork network_show = openstackclient.network.v2.network:ShowNetwork + network_qos_policy_create = openstackclient.network.v2.network_qos_policy:CreateNetworkQosPolicy + network_qos_policy_delete = openstackclient.network.v2.network_qos_policy:DeleteNetworkQosPolicy + network_qos_policy_list = openstackclient.network.v2.network_qos_policy:ListNetworkQosPolicy + network_qos_policy_set = openstackclient.network.v2.network_qos_policy:SetNetworkQosPolicy + network_qos_policy_show = openstackclient.network.v2.network_qos_policy:ShowNetworkQosPolicy + network_rbac_create = openstackclient.network.v2.network_rbac:CreateNetworkRBAC network_rbac_delete = openstackclient.network.v2.network_rbac:DeleteNetworkRBAC network_rbac_list = openstackclient.network.v2.network_rbac:ListNetworkRBAC From 694a24c3093f5b2595b63ccf988da7972e532084 Mon Sep 17 00:00:00 2001 From: Cedric Brandily Date: Tue, 11 Oct 2016 22:01:16 +0200 Subject: [PATCH 1295/3095] Fix --shared/block-migration options in server migrate command Currently, --shared-migration and --block-migration options effects are reversed: --block-migration requests a migration with share, --shared-migration a block-migration. This change corrects OSC implementation and clarifies arguments passed to novaclient (the root cause of the bug). Change-Id: Ib682cff0c44d3b1304670f8606907b1762d8b1e7 Closes-Bug: #1518059 --- openstackclient/compute/v2/server.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 1ca3149798..df46c7df64 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -973,15 +973,15 @@ def get_parser(self, prog_name): migration_group = parser.add_mutually_exclusive_group() migration_group.add_argument( '--shared-migration', - dest='shared_migration', - action='store_true', - default=True, + dest='block_migration', + action='store_false', + default=False, help=_('Perform a shared live migration (default)'), ) migration_group.add_argument( '--block-migration', - dest='shared_migration', - action='store_false', + dest='block_migration', + action='store_true', help=_('Perform a block live migration'), ) disk_group = parser.add_mutually_exclusive_group() @@ -1016,9 +1016,9 @@ def take_action(self, parsed_args): ) if parsed_args.live: server.live_migrate( - parsed_args.live, - parsed_args.shared_migration, - parsed_args.disk_overcommit, + host=parsed_args.live, + block_migration=parsed_args.block_migration, + disk_over_commit=parsed_args.disk_overcommit, ) else: server.migrate() From 66a04abd581e03e5e71a6b755e1b4dce1856ef41 Mon Sep 17 00:00:00 2001 From: Nguyen Phuong An Date: Tue, 16 Aug 2016 15:07:01 +0700 Subject: [PATCH 1296/3095] Add security groups options to "port create/set/unset" This patch adds '--security-group' and '--no-security-group' options to "port create", "port set" and "port unset" commands. Change-Id: Iff60d8f29227017b0a3966efca6cdecba69abcea Partial-Bug: #1612136 Partially-Implements: blueprint network-commands-options --- doc/source/command-objects/port.rst | 27 +++ openstackclient/network/v2/port.py | 73 +++++- .../tests/unit/network/v2/test_port.py | 220 ++++++++++++++++++ .../notes/bug-1612136-051b5f94796e3b51.yaml | 6 + 4 files changed, 325 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/bug-1612136-051b5f94796e3b51.yaml diff --git a/doc/source/command-objects/port.rst b/doc/source/command-objects/port.rst index e3e783ada8..29ce344c18 100644 --- a/doc/source/command-objects/port.rst +++ b/doc/source/command-objects/port.rst @@ -26,6 +26,7 @@ Create new port [--host ] [--enable | --disable] [--mac-address ] + [--security-group | --no-security-group] [--project [--project-domain ]] @@ -75,6 +76,15 @@ Create new port MAC address of this port +.. option:: --security-group + + Security group to associate with this port (name or ID) + (repeat option to set multiple security groups) + +.. option:: --no-security-group + + Associate no security groups with this port + .. option:: --project Owner's project (name or ID) @@ -154,6 +164,8 @@ Set port properties [--host ] [--enable | --disable] [--name ] + [--security-group ] + [--no-security-group] .. option:: --fixed-ip subnet=,ip-address= @@ -210,6 +222,15 @@ Set port properties Set port name +.. option:: --security-group + + Security group to associate with this port (name or ID) + (repeat option to set multiple security groups) + +.. option:: --no-security-group + + Clear existing security groups associated with this port + .. _port_set-port: .. describe:: @@ -242,6 +263,7 @@ Unset port properties os port unset [--fixed-ip subnet=,ip-address= [...]] [--binding-profile [...]] + [--security-group [...]] .. option:: --fixed-ip subnet=,ip-address= @@ -255,6 +277,11 @@ Unset port properties Desired key which should be removed from binding-profile (repeat option to unset multiple binding:profile data) +.. option:: --security-group + + Security group which should be removed from this port (name or ID) + (repeat option to unset multiple security groups) + .. _port_unset-port: .. describe:: diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 92b286a9e8..80d52862a8 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -281,7 +281,23 @@ def get_parser(self, prog_name): help=_("Name of this port") ) # TODO(singhj): Add support for extended options: - # qos,security groups,dhcp, address pairs + # qos,dhcp, address pairs + secgroups = parser.add_mutually_exclusive_group() + secgroups.add_argument( + '--security-group', + metavar='', + action='append', + dest='security_groups', + help=_("Security group to associate with this port (name or ID) " + "(repeat option to set multiple security groups)") + ) + secgroups.add_argument( + '--no-security-group', + dest='no_security_group', + action='store_true', + help=_("Associate no security groups with this port") + ) + return parser def take_action(self, parsed_args): @@ -291,6 +307,14 @@ def take_action(self, parsed_args): parsed_args.network = _network.id _prepare_fixed_ips(self.app.client_manager, parsed_args) attrs = _get_attrs(self.app.client_manager, parsed_args) + + if parsed_args.security_groups: + attrs['security_groups'] = [client.find_security_group( + sg, ignore_missing=False).id + for sg in parsed_args.security_groups] + if parsed_args.no_security_group: + attrs['security_groups'] = [] + obj = client.create_port(**attrs) columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) @@ -463,6 +487,21 @@ def get_parser(self, prog_name): metavar="", help=_("Port to modify (name or ID)") ) + parser.add_argument( + '--security-group', + metavar='', + action='append', + dest='security_groups', + help=_("Security group to associate with this port (name or ID) " + "(repeat option to set multiple security groups)") + ) + parser.add_argument( + '--no-security-group', + dest='no_security_group', + action='store_true', + help=_("Clear existing security groups associated with this port") + ) + return parser def take_action(self, parsed_args): @@ -490,6 +529,17 @@ def take_action(self, parsed_args): attrs['fixed_ips'] += [ip for ip in obj.fixed_ips if ip] elif parsed_args.no_fixed_ip: attrs['fixed_ips'] = [] + if parsed_args.security_groups and parsed_args.no_security_group: + attrs['security_groups'] = [client.find_security_group(sg, + ignore_missing=False).id + for sg in parsed_args.security_groups] + elif parsed_args.security_groups: + attrs['security_groups'] = obj.security_groups + for sg in parsed_args.security_groups: + sg_id = client.find_security_group(sg, ignore_missing=False).id + attrs['security_groups'].append(sg_id) + elif parsed_args.no_security_group: + attrs['security_groups'] = [] client.update_port(obj, **attrs) @@ -535,6 +585,15 @@ def get_parser(self, prog_name): action='append', help=_("Desired key which should be removed from binding:profile" "(repeat option to unset multiple binding:profile data)")) + parser.add_argument( + '--security-group', + metavar='', + action='append', + dest='security_groups', + help=_("Security group which should be removed this port (name " + "or ID) (repeat option to unset multiple security groups)") + ) + parser.add_argument( 'port', metavar="", @@ -550,6 +609,7 @@ def take_action(self, parsed_args): # Unset* classes tmp_fixed_ips = copy.deepcopy(obj.fixed_ips) tmp_binding_profile = copy.deepcopy(obj.binding_profile) + tmp_secgroups = copy.deepcopy(obj.security_groups) _prepare_fixed_ips(self.app.client_manager, parsed_args) attrs = {} if parsed_args.fixed_ip: @@ -568,5 +628,16 @@ def take_action(self, parsed_args): msg = _("Port does not contain binding-profile %s") % key raise exceptions.CommandError(msg) attrs['binding:profile'] = tmp_binding_profile + if parsed_args.security_groups: + try: + for sg in parsed_args.security_groups: + sg_id = client.find_security_group( + sg, ignore_missing=False).id + tmp_secgroups.remove(sg_id) + except ValueError: + msg = _("Port does not contain security group %s") % sg + raise exceptions.CommandError(msg) + attrs['security_groups'] = tmp_secgroups + if attrs: client.update_port(obj, **attrs) diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index a2aceab173..b27e71b5de 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -228,6 +228,93 @@ def test_create_json_binding_profile(self): self.assertEqual(ref_columns, columns) self.assertEqual(ref_data, data) + def test_create_with_security_group(self): + secgroup = network_fakes.FakeSecurityGroup.create_one_security_group() + self.network.find_security_group = mock.Mock(return_value=secgroup) + arglist = [ + '--network', self._port.network_id, + '--security-group', secgroup.id, + 'test-port', + ] + verifylist = [ + ('network', self._port.network_id,), + ('enable', True), + ('security_groups', [secgroup.id]), + ('name', 'test-port'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_port.assert_called_once_with(**{ + 'admin_state_up': True, + 'network_id': self._port.network_id, + 'security_groups': [secgroup.id], + 'name': 'test-port', + }) + + ref_columns, ref_data = self._get_common_cols_data(self._port) + self.assertEqual(ref_columns, columns) + self.assertEqual(ref_data, data) + + def test_create_with_security_groups(self): + sg_1 = network_fakes.FakeSecurityGroup.create_one_security_group() + sg_2 = network_fakes.FakeSecurityGroup.create_one_security_group() + self.network.find_security_group = mock.Mock(side_effect=[sg_1, sg_2]) + arglist = [ + '--network', self._port.network_id, + '--security-group', sg_1.id, + '--security-group', sg_2.id, + 'test-port', + ] + verifylist = [ + ('network', self._port.network_id,), + ('enable', True), + ('security_groups', [sg_1.id, sg_2.id]), + ('name', 'test-port'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_port.assert_called_once_with(**{ + 'admin_state_up': True, + 'network_id': self._port.network_id, + 'security_groups': [sg_1.id, sg_2.id], + 'name': 'test-port', + }) + + ref_columns, ref_data = self._get_common_cols_data(self._port) + self.assertEqual(ref_columns, columns) + self.assertEqual(ref_data, data) + + def test_create_with_no_secuirty_groups(self): + arglist = [ + '--network', self._port.network_id, + '--no-security-group', + 'test-port', + ] + verifylist = [ + ('network', self._port.network_id), + ('enable', True), + ('no_security_group', True), + ('name', 'test-port'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_port.assert_called_once_with(**{ + 'admin_state_up': True, + 'network_id': self._port.network_id, + 'security_groups': [], + 'name': 'test-port', + }) + + ref_columns, ref_data = self._get_common_cols_data(self._port) + self.assertEqual(ref_columns, columns) + self.assertEqual(ref_data, data) + class TestDeletePort(TestPort): @@ -651,6 +738,95 @@ def test_set_mixed_binding_profile(self): self.network.update_port.assert_called_once_with(self._port, **attrs) self.assertIsNone(result) + def test_set_security_group(self): + sg = network_fakes.FakeSecurityGroup.create_one_security_group() + self.network.find_security_group = mock.Mock(return_value=sg) + arglist = [ + '--security-group', sg.id, + self._port.name, + ] + verifylist = [ + ('security_groups', [sg.id]), + ('port', self._port.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = { + 'security_groups': [sg.id], + } + self.network.update_port.assert_called_once_with(self._port, **attrs) + self.assertIsNone(result) + + def test_append_security_group(self): + sg_1 = network_fakes.FakeSecurityGroup.create_one_security_group() + sg_2 = network_fakes.FakeSecurityGroup.create_one_security_group() + sg_3 = network_fakes.FakeSecurityGroup.create_one_security_group() + self.network.find_security_group = mock.Mock(side_effect=[sg_2, sg_3]) + _testport = network_fakes.FakePort.create_one_port( + {'security_groups': [sg_1.id]}) + self.network.find_port = mock.Mock(return_value=_testport) + arglist = [ + '--security-group', sg_2.id, + '--security-group', sg_3.id, + _testport.name, + ] + verifylist = [ + ('security_groups', [sg_2.id, sg_3.id]), + ('port', _testport.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + attrs = { + 'security_groups': [sg_1.id, sg_2.id, sg_3.id], + } + self.network.update_port.assert_called_once_with(_testport, **attrs) + self.assertIsNone(result) + + def test_set_no_security_groups(self): + arglist = [ + '--no-security-group', + self._port.name, + ] + verifylist = [ + ('no_security_group', True), + ('port', self._port.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = { + 'security_groups': [], + } + self.network.update_port.assert_called_once_with(self._port, **attrs) + self.assertIsNone(result) + + def test_overwrite_security_group(self): + sg1 = network_fakes.FakeSecurityGroup.create_one_security_group() + sg2 = network_fakes.FakeSecurityGroup.create_one_security_group() + _testport = network_fakes.FakePort.create_one_port( + {'security_groups': [sg1.id]}) + self.network.find_port = mock.Mock(return_value=_testport) + self.network.find_security_group = mock.Mock(return_value=sg2) + arglist = [ + '--security-group', sg2.id, + '--no-security-group', + _testport.name, + ] + verifylist = [ + ('security_groups', [sg2.id]), + ('no_security_group', True) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + attrs = { + 'security_groups': [sg2.id], + } + self.network.update_port.assert_called_once_with(_testport, **attrs) + self.assertIsNone(result) + class TestShowPort(TestPort): @@ -767,3 +943,47 @@ def test_unset_port_binding_profile_not_existent(self): self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) + + def test_unset_security_group(self): + _fake_sg1 = network_fakes.FakeSecurityGroup.create_one_security_group() + _fake_sg2 = network_fakes.FakeSecurityGroup.create_one_security_group() + _fake_port = network_fakes.FakePort.create_one_port( + {'security_groups': [_fake_sg1.id, _fake_sg2.id]}) + self.network.find_port = mock.Mock(return_value=_fake_port) + self.network.find_security_group = mock.Mock(return_value=_fake_sg2) + arglist = [ + '--security-group', _fake_sg2.id, + _fake_port.name, + ] + verifylist = [ + ('security_groups', [_fake_sg2.id]), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = { + 'security_groups': [_fake_sg1.id] + } + self.network.update_port.assert_called_once_with( + _fake_port, **attrs) + self.assertIsNone(result) + + def test_unset_port_security_group_not_existent(self): + _fake_sg1 = network_fakes.FakeSecurityGroup.create_one_security_group() + _fake_sg2 = network_fakes.FakeSecurityGroup.create_one_security_group() + _fake_port = network_fakes.FakePort.create_one_port( + {'security_groups': [_fake_sg1.id]}) + self.network.find_security_group = mock.Mock(return_value=_fake_sg2) + arglist = [ + '--security-group', _fake_sg2.id, + _fake_port.name, + ] + verifylist = [ + ('security_groups', [_fake_sg2.id]), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, + parsed_args) diff --git a/releasenotes/notes/bug-1612136-051b5f94796e3b51.yaml b/releasenotes/notes/bug-1612136-051b5f94796e3b51.yaml new file mode 100644 index 0000000000..423947beb1 --- /dev/null +++ b/releasenotes/notes/bug-1612136-051b5f94796e3b51.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--security-group`` and ``--no-security-group`` options to + ``port create``, ``port set`` and ``port unset`` commands. + [Bug `1612136 `_] From 2c1282cecf33162b5483f5cb558f98395945380b Mon Sep 17 00:00:00 2001 From: Nam Nguyen Hoai Date: Thu, 18 Aug 2016 15:24:51 +0700 Subject: [PATCH 1297/3095] Add a new column and a new option the 'os port list' cmd This patch will add a new column called status to the result of the 'os port list' command and --long option to 'os port list' command. Co-Authored-By: Ha Van Tu Change-Id: I4f942414e969687304b578ed7f003dd219c0f2f8 Closes-Bug: #1613995 Closes-Bug: #1614321 Partially-Implements: blueprint network-commands-options --- doc/source/command-objects/port.rst | 5 +++ openstackclient/network/v2/port.py | 11 +++++ .../tests/unit/network/v2/test_port.py | 41 +++++++++++++++++++ .../notes/bug-1613995-10bb3895d702c063.yaml | 7 ++++ 4 files changed, 64 insertions(+) create mode 100644 releasenotes/notes/bug-1613995-10bb3895d702c063.yaml diff --git a/doc/source/command-objects/port.rst b/doc/source/command-objects/port.rst index e3e783ada8..980f77b9a1 100644 --- a/doc/source/command-objects/port.rst +++ b/doc/source/command-objects/port.rst @@ -117,6 +117,7 @@ List ports [--device-owner ] [--router | --server ] [--network ] + [--long] .. option:: --device-owner @@ -135,6 +136,10 @@ List ports List only ports attached to this network (name or ID) +.. option:: --long + + List additional fields in output + port set -------- diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 92b286a9e8..abdc5f0592 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -360,6 +360,12 @@ def get_parser(self, prog_name): metavar='', help=_("List only ports attached to this server (name or ID)"), ) + parser.add_argument( + '--long', + action='store_true', + default=False, + help=_("List additional fields in output") + ) return parser def take_action(self, parsed_args): @@ -371,15 +377,20 @@ def take_action(self, parsed_args): 'name', 'mac_address', 'fixed_ips', + 'status', ) column_headers = ( 'ID', 'Name', 'MAC Address', 'Fixed IP Addresses', + 'Status', ) filters = {} + if parsed_args.long: + columns += ('security_groups', 'device_owner',) + column_headers += ('Security Groups', 'Device Owner',) if parsed_args.device_owner is not None: filters['device_owner'] = parsed_args.device_owner if parsed_args.router: diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index a2aceab173..bfc26bf42d 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -317,6 +317,17 @@ class TestListPort(TestPort): 'Name', 'MAC Address', 'Fixed IP Addresses', + 'Status', + ) + + columns_long = ( + 'ID', + 'Name', + 'MAC Address', + 'Fixed IP Addresses', + 'Status', + 'Security Groups', + 'Device Owner', ) data = [] @@ -326,6 +337,19 @@ class TestListPort(TestPort): prt.name, prt.mac_address, utils.format_list_of_dicts(prt.fixed_ips), + prt.status, + )) + + data_long = [] + for prt in _ports: + data_long.append(( + prt.id, + prt.name, + prt.mac_address, + utils.format_list_of_dicts(prt.fixed_ips), + prt.status, + utils.format_list(prt.security_groups), + prt.device_owner, )) def setUp(self): @@ -439,6 +463,23 @@ def test_port_list_all_opt(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + def test_list_port_with_long(self): + arglist = [ + '--long', + ] + + verifylist = [ + ('long', True), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.ports.assert_called_once_with() + self.assertEqual(self.columns_long, columns) + self.assertEqual(self.data_long, list(data)) + class TestSetPort(TestPort): diff --git a/releasenotes/notes/bug-1613995-10bb3895d702c063.yaml b/releasenotes/notes/bug-1613995-10bb3895d702c063.yaml new file mode 100644 index 0000000000..466f950a5a --- /dev/null +++ b/releasenotes/notes/bug-1613995-10bb3895d702c063.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Add a new column ``status`` and ``--long`` option to the result of the + ``os port list`` command. + [Bug `1613995 `_] + [Bug `1614321 `_] From 43d1646058a5faa917f2e7ed073c710180da8bd3 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 13 Oct 2016 05:28:24 +0000 Subject: [PATCH 1298/3095] Updated from global requirements Change-Id: I24283bbbc5e1717b9824ad8f62859a3053910950 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ac7323fca8..29c2bd16c8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ Babel>=2.3.4 # BSD cliff>=2.2.0 # Apache-2.0 keystoneauth1>=2.10.0 # Apache-2.0 openstacksdk>=0.9.7 # Apache-2.0 -osc-lib>=1.0.2 # Apache-2.0 +osc-lib>=1.2.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 oslo.utils>=3.16.0 # Apache-2.0 python-glanceclient>=2.5.0 # Apache-2.0 From 291b66e983dd4652839be0dd765616b65fd4c54b Mon Sep 17 00:00:00 2001 From: Nam Nguyen Hoai Date: Wed, 17 Aug 2016 13:40:52 +0700 Subject: [PATCH 1299/3095] Add direction and protocol options to os security group rule list cmd This patch added direction options (--ingress, --egress) and protocol option (--protocol) to filter rules by os security group rule list command. Change-Id: I56ace3f97eb927fd2a868f728c7347a29d028b67 Closes-Bug: #1613533 Partially-Implements: blueprint network-commands-options --- .../command-objects/security-group-rule.rst | 24 +++++++++ .../network/v2/security_group_rule.py | 30 +++++++++++ .../network/v2/test_security_group_rule.py | 54 +++++++++++++++++++ .../notes/bug-1613533-93279179c6f70117.yaml | 6 +++ 4 files changed, 114 insertions(+) create mode 100644 releasenotes/notes/bug-1613533-93279179c6f70117.yaml diff --git a/doc/source/command-objects/security-group-rule.rst b/doc/source/command-objects/security-group-rule.rst index 5284b2dc22..69869dc15e 100644 --- a/doc/source/command-objects/security-group-rule.rst +++ b/doc/source/command-objects/security-group-rule.rst @@ -126,6 +126,8 @@ List security group rules os security group rule list [--all-projects] + [--protocol ] + [--ingress | --egress] [--long] [] @@ -142,6 +144,28 @@ List security group rules *Compute version 2 does not have additional fields to display.* + +.. option:: --protocol + + List rules by the IP protocol (ah, dhcp, egp, esp, gre, icmp, igmp, + ipv6-encap, ipv6-frag, ipv6-icmp, ipv6-nonxt,ipv6-opts, ipv6-route, + ospf, pgm, rsvp, sctp, tcp, udp, udplite, vrrp and integer + representations [0-255]) + + *Network version 2* + +.. option:: --ingress + + List rules applied to incoming network traffic + + *Network version 2 only* + +.. option:: --egress + + List rules applied to outgoing network traffic + + *Network version 2 only* + .. describe:: List all rules in this security group (name or ID) diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index e3be44ece7..3b9055df9c 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -379,6 +379,28 @@ def update_parser_network(self, parser): default=False, help=argparse.SUPPRESS ) + parser.add_argument( + '--protocol', + metavar='', + type=_convert_to_lowercase, + help=_("List rules by the IP protocol (" + "ah, dhcp, egp, esp, gre, icmp, igmp, " + "ipv6-encap, ipv6-frag, ipv6-icmp, ipv6-nonxt, " + "ipv6-opts, ipv6-route, ospf, pgm, rsvp, sctp, tcp, " + "udp, udplite, vrrp and integer representations [0-255])." + ) + ) + direction_group = parser.add_mutually_exclusive_group() + direction_group.add_argument( + '--ingress', + action='store_true', + help=_("List rules applied to incoming network traffic") + ) + direction_group.add_argument( + '--egress', + action='store_true', + help=_("List rules applied to outgoing network traffic") + ) parser.add_argument( '--long', action='store_true', @@ -443,6 +465,14 @@ def take_action_network(self, client, parsed_args): query = {'security_group_id': security_group_id} else: columns = columns + ('security_group_id',) + + if parsed_args.ingress: + query['direction'] = 'ingress' + if parsed_args.egress: + query['direction'] = 'egress' + if parsed_args.protocol is not None: + query['protocol'] = parsed_args.protocol + rules = list(client.security_group_rules(**query)) # Reformat the rules to display a port range instead diff --git a/openstackclient/tests/unit/network/v2/test_security_group_rule.py b/openstackclient/tests/unit/network/v2/test_security_group_rule.py index 96d58e5c00..f91314d667 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_rule.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_rule.py @@ -942,6 +942,60 @@ def test_list_with_ignored_options(self): self.assertEqual(self.expected_columns_no_group, columns) self.assertEqual(self.expected_data_no_group, list(data)) + def test_list_with_protocol(self): + self._security_group_rule_tcp.port_range_min = 80 + arglist = [ + '--protocol', 'tcp', + ] + verifylist = [ + ('protocol', 'tcp'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.security_group_rules.assert_called_once_with(**{ + 'protocol': 'tcp', + }) + self.assertEqual(self.expected_columns_no_group, columns) + self.assertEqual(self.expected_data_no_group, list(data)) + + def test_list_with_ingress(self): + self._security_group_rule_tcp.port_range_min = 80 + arglist = [ + '--ingress', + ] + verifylist = [ + ('ingress', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.security_group_rules.assert_called_once_with(**{ + 'direction': 'ingress', + }) + self.assertEqual(self.expected_columns_no_group, columns) + self.assertEqual(self.expected_data_no_group, list(data)) + + def test_list_with_wrong_egress(self): + self._security_group_rule_tcp.port_range_min = 80 + arglist = [ + '--egress', + ] + verifylist = [ + ('egress', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.security_group_rules.assert_called_once_with(**{ + 'direction': 'egress', + }) + self.assertEqual(self.expected_columns_no_group, columns) + self.assertEqual(self.expected_data_no_group, list(data)) + class TestListSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): diff --git a/releasenotes/notes/bug-1613533-93279179c6f70117.yaml b/releasenotes/notes/bug-1613533-93279179c6f70117.yaml new file mode 100644 index 0000000000..8907041a56 --- /dev/null +++ b/releasenotes/notes/bug-1613533-93279179c6f70117.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--ingress``, ``--egress`` and ``--protocol`` options to + ``security group rule list`` command. + [Bug `1613533 `_] \ No newline at end of file From 3a2716362820f47e9d9ff0ba7dcb50c191092192 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Fri, 14 Oct 2016 05:44:06 +0000 Subject: [PATCH 1300/3095] Updated from global requirements Change-Id: Ia68a75e6cfbcfc267d68822a04e528605701672f --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 29c2bd16c8..235e40ed9f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ six>=1.9.0 # MIT Babel>=2.3.4 # BSD cliff>=2.2.0 # Apache-2.0 -keystoneauth1>=2.10.0 # Apache-2.0 +keystoneauth1>=2.13.0 # Apache-2.0 openstacksdk>=0.9.7 # Apache-2.0 osc-lib>=1.2.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 From 928fecd134d9e328a3d4e7f91797b11503e8a70a Mon Sep 17 00:00:00 2001 From: wangxiyuan Date: Fri, 14 Oct 2016 15:37:52 +0800 Subject: [PATCH 1301/3095] Update the doc for Zaqar v2 Zaqar v1 and v1.1 is deprecated now, and the V2 is the default API version. Update the plugin doc for it. Change-Id: I26db823852ba543607b250a09aae63bb439a8959 --- doc/source/commands.rst | 5 ++++- doc/source/plugin-commands.rst | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 53272f68ea..de473a063d 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -159,6 +159,7 @@ list check out :doc:`plugin-commands`. * ``action definition``: (**Workflow Engine (Mistral)**) * ``action execution``: (**Workflow Engine (Mistral)**) * ``baremetal``: (**Baremetal (Ironic)**) +* ``claim``: (**Messaging (Zaqar)**) * ``cluster``: (**Clustering (Senlin)**) * ``cluster action``: (**Clustering (Senlin)**) * ``cluster event``: (**Clustering (Senlin)**) @@ -180,7 +181,8 @@ list check out :doc:`plugin-commands`. * ``dataprocessing image tags``: (**Data Processing (Sahara)**) * ``dataprocessing plugin``: (**Data Processing (Sahara)**) * ``message-broker cluster``: (**Message Broker (Cue)**) -* ``message flavor``: (**Messaging (Zaqar)**) +* ``messaging``: (**Messaging (Zaqar)**) +* ``messaging flavor``: (**Messaging (Zaqar)**) * ``network subport``: (**Networking (Neutron)**) * ``network trunk``: (**Networking (Neutron)**) * ``orchestration resource``: (**Orchestration (Heat)**) @@ -200,6 +202,7 @@ list check out :doc:`plugin-commands`. * ``stack resource``: (**Orchestration (Heat)**) * ``stack snapshot``: (**Orchestration (Heat)**) * ``stack template``: (**Orchestration (Heat)**) +* ``subscription``: (**Messaging (Zaqar)**) * ``task execution``: (**Workflow Engine (Mistral)**) * ``tld``: (**DNS (Designate)**) * ``workbook``: (**Workflow Engine (Mistral)**) diff --git a/doc/source/plugin-commands.rst b/doc/source/plugin-commands.rst index 616d2f0f62..7428b34d01 100644 --- a/doc/source/plugin-commands.rst +++ b/doc/source/plugin-commands.rst @@ -105,5 +105,5 @@ senlin zaqar ----- -.. list-plugins:: openstack.messaging.v1 +.. list-plugins:: openstack.messaging.v2 :detailed: From a58bacc619a92e6dcc3977ae9aff979e584dae2f Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sat, 15 Oct 2016 00:12:29 +0000 Subject: [PATCH 1302/3095] Updated from global requirements Change-Id: Iff006bdedb4d7eefef9fc55e7bb3c0f37abbf784 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 235e40ed9f..8cd7cef175 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ six>=1.9.0 # MIT Babel>=2.3.4 # BSD cliff>=2.2.0 # Apache-2.0 -keystoneauth1>=2.13.0 # Apache-2.0 +keystoneauth1>=2.14.0 # Apache-2.0 openstacksdk>=0.9.7 # Apache-2.0 osc-lib>=1.2.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 From 82af038bb035008f4fb8928944710abedbf7694c Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Sat, 15 Oct 2016 20:28:19 -0700 Subject: [PATCH 1303/3095] properly format error messages for volume resizing The error messages are tuples and not substituting variables. Before: $ openstack volume set vol1 --size 1 Failed to set volume size: (u'New size must be greater than %s GB', 2) $ openstack volume set vol1 --size 1 Failed to set volume size: (u'Volume is in %s state, it must be available before size can be extended', u'error') After: $ openstack volume set vol2 --size 3 Failed to set volume size: New size must be greater than 4 GB $ openstack volume set vol2 --size 3 Failed to set volume size: Volume is in error state, it must be available before size can be extended Change-Id: Ide6e644b1c6d1c11a9dc2f3f53c1a1837380b8d5 --- openstackclient/volume/v2/volume.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index e74051145b..cb409711b5 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -484,10 +484,11 @@ def take_action(self, parsed_args): try: if volume.status != 'available': msg = (_("Volume is in %s state, it must be available " - "before size can be extended"), volume.status) + "before size can be extended") % volume.status) raise exceptions.CommandError(msg) if parsed_args.size <= volume.size: - msg = _("New size must be greater than %s GB"), volume.size + msg = (_("New size must be greater than %s GB") + % volume.size) raise exceptions.CommandError(msg) volume_client.volumes.extend(volume.id, parsed_args.size) except Exception as e: From 8f8a8448a4adc1d250c253738688ff46e6456616 Mon Sep 17 00:00:00 2001 From: Reedip Date: Tue, 4 Oct 2016 16:25:07 +0530 Subject: [PATCH 1304/3095] Add necessary info to Floating IP list This patch adds the Network ID and Project ID for the listed floating IPs when Neutron is enabled for OpenstackClient. Change-Id: I823090f2e6d30acd72247d30956f48f4d7672a50 Closes-Bug:#1566090 --- openstackclient/network/v2/floating_ip.py | 4 ++++ openstackclient/tests/unit/network/v2/test_floating_ip.py | 4 ++++ releasenotes/notes/bug-1566090_64726dc7df5b1572.yaml | 7 +++++++ 3 files changed, 15 insertions(+) create mode 100644 releasenotes/notes/bug-1566090_64726dc7df5b1572.yaml diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index bb75540cc6..e805762849 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -203,12 +203,16 @@ def take_action_network(self, client, parsed_args): 'floating_ip_address', 'fixed_ip_address', 'port_id', + 'floating_network_id', + 'project_id', ) headers = ( 'ID', 'Floating IP Address', 'Fixed IP Address', 'Port', + 'Floating Network', + 'Project', ) query = {} diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip.py b/openstackclient/tests/unit/network/v2/test_floating_ip.py index 1f30f2e923..578c615432 100644 --- a/openstackclient/tests/unit/network/v2/test_floating_ip.py +++ b/openstackclient/tests/unit/network/v2/test_floating_ip.py @@ -237,6 +237,8 @@ class TestListFloatingIPNetwork(TestFloatingIPNetwork): 'Floating IP Address', 'Fixed IP Address', 'Port', + 'Floating Network', + 'Project', ) data = [] @@ -246,6 +248,8 @@ class TestListFloatingIPNetwork(TestFloatingIPNetwork): ip.floating_ip_address, ip.fixed_ip_address, ip.port_id, + ip.floating_network_id, + ip.tenant_id, )) def setUp(self): diff --git a/releasenotes/notes/bug-1566090_64726dc7df5b1572.yaml b/releasenotes/notes/bug-1566090_64726dc7df5b1572.yaml new file mode 100644 index 0000000000..464e57f032 --- /dev/null +++ b/releasenotes/notes/bug-1566090_64726dc7df5b1572.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + ``openstack floating ip`` now provides ``Floating Network`` and + ``Project`` to identify to which network and project the + floating-ip belongs to. + [Bug `1566090 `_] From bae09c3c3fac210f4839a8a51dfb51e2dad69aa7 Mon Sep 17 00:00:00 2001 From: Reedip Date: Sat, 8 Oct 2016 13:39:36 +0530 Subject: [PATCH 1305/3095] Add support make a router HA Currently router set CLI does not provide the support make a router highly available. The following patch enables the same. Checking for setting a router as HA is left on the neutron server itself. Partially-Implements: blueprint network-commands-options Change-Id: I0d0548cf037a14e5ccb2f732918ee9d1f63f43b4 Closes-Bug:#1631492 --- doc/source/command-objects/router.rst | 9 ++++++++ openstackclient/network/v2/router.py | 22 ++++++++++++++----- .../tests/unit/network/v2/test_router.py | 6 +++++ ...-ha-to-router-update-6a38a73cc112b2fc.yaml | 7 ++++++ 4 files changed, 39 insertions(+), 5 deletions(-) create mode 100644 releasenotes/notes/add-ha-to-router-update-6a38a73cc112b2fc.yaml diff --git a/doc/source/command-objects/router.rst b/doc/source/command-objects/router.rst index 151639802c..0eaae655d2 100644 --- a/doc/source/command-objects/router.rst +++ b/doc/source/command-objects/router.rst @@ -198,6 +198,7 @@ Set router properties [--distributed | --centralized] [--description ] [--route destination=,gateway= | --no-route] + [--ha | --no-ha] .. option:: --name @@ -235,6 +236,14 @@ Set router properties Clear routes associated with the router +.. option:: --ha + + Set the router as highly available (disabled router only) + +.. option:: --no-ha + + Clear high availablability attribute of the router (disabled router only) + .. _router_set-router: .. describe:: diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 64bb881911..193bf6e9bd 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -433,11 +433,19 @@ def get_parser(self, prog_name): action='store_true', help=argparse.SUPPRESS, ) - - # TODO(tangchen): Support setting 'ha' property in 'router set' - # command. It appears that changing the ha state is supported by - # neutron under certain conditions. - + routes_ha = parser.add_mutually_exclusive_group() + routes_ha.add_argument( + '--ha', + action='store_true', + help=_("Set the router as highly available " + "(disabled router only)") + ) + routes_ha.add_argument( + '--no-ha', + action='store_true', + help=_("Clear high availablability attribute of the router " + "(disabled router only)") + ) # TODO(tangchen): Support setting 'external_gateway_info' property in # 'router set' command. @@ -451,6 +459,10 @@ def take_action(self, parsed_args): attrs = _get_attrs(self.app.client_manager, parsed_args) # Get the route attributes. + if parsed_args.ha: + attrs['ha'] = True + elif parsed_args.no_ha: + attrs['ha'] = False if parsed_args.no_route: attrs['routes'] = [] elif parsed_args.clear_routes: diff --git a/openstackclient/tests/unit/network/v2/test_router.py b/openstackclient/tests/unit/network/v2/test_router.py index d85561bc12..bc448d137e 100644 --- a/openstackclient/tests/unit/network/v2/test_router.py +++ b/openstackclient/tests/unit/network/v2/test_router.py @@ -529,6 +529,7 @@ def test_set_this(self): '--enable', '--distributed', '--name', 'noob', + '--no-ha', '--description', 'router', ] verifylist = [ @@ -537,6 +538,7 @@ def test_set_this(self): ('distributed', True), ('name', 'noob'), ('description', 'router'), + ('no_ha', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -546,6 +548,7 @@ def test_set_this(self): 'admin_state_up': True, 'distributed': True, 'name': 'noob', + 'ha': False, 'description': 'router', } self.network.update_router.assert_called_once_with( @@ -557,11 +560,13 @@ def test_set_that(self): self._router.name, '--disable', '--centralized', + '--ha', ] verifylist = [ ('router', self._router.name), ('disable', True), ('centralized', True), + ('ha', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -570,6 +575,7 @@ def test_set_that(self): attrs = { 'admin_state_up': False, 'distributed': False, + 'ha': True, } self.network.update_router.assert_called_once_with( self._router, **attrs) diff --git a/releasenotes/notes/add-ha-to-router-update-6a38a73cc112b2fc.yaml b/releasenotes/notes/add-ha-to-router-update-6a38a73cc112b2fc.yaml new file mode 100644 index 0000000000..905a1fdd50 --- /dev/null +++ b/releasenotes/notes/add-ha-to-router-update-6a38a73cc112b2fc.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Add support to update high-availability property of + a router by adding ``--ha`` and ``--no-ha`` option + to ``router set`` CLI. + [Bug `1631492 `_] From c99ec284db181c7f9c72ce1163ba1ea45fe369d0 Mon Sep 17 00:00:00 2001 From: Aradhana Singh Date: Thu, 6 Oct 2016 21:51:01 +0000 Subject: [PATCH 1306/3095] Add description field port create & port set This patchset 1. adds description field to openstack port create and openstack port set. 2. updates method _add_updatable_args with 4 spaces instead of existing 8 spaces Partially Implements: blueprint neutron-client-descriptions Partially Implements: blueprint network-commands-options Change-Id: I4598e555722b1de7bc47f3a9be0fd81eacfcb572 --- doc/source/command-objects/port.rst | 10 +++ openstackclient/network/v2/port.py | 87 ++++++++++--------- .../tests/unit/network/v2/fakes.py | 1 + .../tests/unit/network/v2/test_port.py | 8 ++ ...-client-descriptions-b65dd776f78b5a73.yaml | 6 ++ 5 files changed, 72 insertions(+), 40 deletions(-) create mode 100644 releasenotes/notes/bp-neutron-client-descriptions-b65dd776f78b5a73.yaml diff --git a/doc/source/command-objects/port.rst b/doc/source/command-objects/port.rst index e3e783ada8..6b318faf74 100644 --- a/doc/source/command-objects/port.rst +++ b/doc/source/command-objects/port.rst @@ -18,6 +18,7 @@ Create new port os port create --network + [--description ] [--fixed-ip subnet=,ip-address=] [--device ] [--device-owner ] @@ -33,6 +34,10 @@ Create new port Network this port belongs to (name or ID) +.. option:: --description + + Description of this port + .. option:: --fixed-ip subnet=,ip-address= Desired IP and/or subnet (name or ID) for this port: @@ -144,6 +149,7 @@ Set port properties .. code:: bash os port set + [--description ] [--fixed-ip subnet=,ip-address=] [--no-fixed-ip] [--device ] @@ -156,6 +162,10 @@ Set port properties [--name ] +.. option:: --description + + Description of this port + .. option:: --fixed-ip subnet=,ip-address= Desired IP and/or subnet (name or ID) for this port: diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 92b286a9e8..bd777d717c 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -109,6 +109,8 @@ def _get_attrs(client_manager, parsed_args): 'The --host-id option is deprecated, ' 'please use --host instead.' )) + if parsed_args.description is not None: + attrs['description'] = parsed_args.description if parsed_args.fixed_ip is not None: attrs['fixed_ips'] = parsed_args.fixed_ip if parsed_args.device: @@ -180,46 +182,51 @@ def _prepare_fixed_ips(client_manager, parsed_args): def _add_updatable_args(parser): - # NOTE(dtroyer): --device-id is deprecated in Mar 2016. Do not - # remove before 3.x release or Mar 2017. - device_group = parser.add_mutually_exclusive_group() - device_group.add_argument( - '--device', - metavar='', - help=_("Port device ID") - ) - device_group.add_argument( - '--device-id', - metavar='', - help=argparse.SUPPRESS, - ) - parser.add_argument( - '--device-owner', - metavar='', - help=_("Device owner of this port. This is the entity that uses " - "the port (for example, network:dhcp).") - ) - parser.add_argument( - '--vnic-type', - metavar='', - choices=['direct', 'direct-physical', 'macvtap', - 'normal', 'baremetal'], - help=_("VNIC type for this port (direct | direct-physical | " - "macvtap | normal | baremetal, default: normal)") - ) - # NOTE(dtroyer): --host-id is deprecated in Mar 2016. Do not - # remove before 3.x release or Mar 2017. - host_group = parser.add_mutually_exclusive_group() - host_group.add_argument( - '--host', - metavar='', - help=_("Allocate port on host (ID only)") - ) - host_group.add_argument( - '--host-id', - metavar='', - help=argparse.SUPPRESS, - ) + parser.add_argument( + '--description', + metavar='', + help=_("Description of this port") + ) + # NOTE(dtroyer): --device-id is deprecated in Mar 2016. Do not + # remove before 3.x release or Mar 2017. + device_group = parser.add_mutually_exclusive_group() + device_group.add_argument( + '--device', + metavar='', + help=_("Port device ID") + ) + device_group.add_argument( + '--device-id', + metavar='', + help=argparse.SUPPRESS, + ) + parser.add_argument( + '--device-owner', + metavar='', + help=_("Device owner of this port. This is the entity that uses " + "the port (for example, network:dhcp).") + ) + parser.add_argument( + '--vnic-type', + metavar='', + choices=['direct', 'direct-physical', 'macvtap', + 'normal', 'baremetal'], + help=_("VNIC type for this port (direct | direct-physical | " + "macvtap | normal | baremetal, default: normal)") + ) + # NOTE(dtroyer): --host-id is deprecated in Mar 2016. Do not + # remove before 3.x release or Mar 2017. + host_group = parser.add_mutually_exclusive_group() + host_group.add_argument( + '--host', + metavar='', + help=_("Allocate port on host (ID only)") + ) + host_group.add_argument( + '--host-id', + metavar='', + help=argparse.SUPPRESS, + ) class CreatePort(command.ShowOne): diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index 94727ae3ca..8994517e4e 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -427,6 +427,7 @@ def create_one_port(attrs=None): 'binding:vif_details': {}, 'binding:vif_type': 'ovs', 'binding:vnic_type': 'normal', + 'description': 'description-' + uuid.uuid4().hex, 'device_id': 'device-id-' + uuid.uuid4().hex, 'device_owner': 'compute:nova', 'dns_assignment': [{}], diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index a2aceab173..110f713e67 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -41,6 +41,7 @@ def _get_common_cols_data(self, fake_port): 'binding_vif_details', 'binding_vif_type', 'binding_vnic_type', + 'description', 'device_id', 'device_owner', 'dns_assignment', @@ -65,6 +66,7 @@ def _get_common_cols_data(self, fake_port): utils.format_dict(fake_port.binding_vif_details), fake_port.binding_vif_type, fake_port.binding_vnic_type, + fake_port.description, fake_port.device_id, fake_port.device_owner, utils.format_list_of_dicts(fake_port.dns_assignment), @@ -130,6 +132,7 @@ def test_create_full_options(self): '--mac-address', 'aa:aa:aa:aa:aa:aa', '--fixed-ip', 'subnet=%s,ip-address=10.0.0.2' % self.fake_subnet.id, + '--description', self._port.description, '--device', 'deviceid', '--device-owner', 'fakeowner', '--disable', @@ -146,6 +149,7 @@ def test_create_full_options(self): 'fixed_ip', [{'subnet': self.fake_subnet.id, 'ip-address': '10.0.0.2'}] ), + ('description', self._port.description), ('device', 'deviceid'), ('device_owner', 'fakeowner'), ('disable', True), @@ -163,6 +167,7 @@ def test_create_full_options(self): 'mac_address': 'aa:aa:aa:aa:aa:aa', 'fixed_ips': [{'subnet_id': self.fake_subnet.id, 'ip_address': '10.0.0.2'}], + 'description': self._port.description, 'device_id': 'deviceid', 'device_owner': 'fakeowner', 'admin_state_up': False, @@ -565,6 +570,7 @@ def test_set_this(self): def test_set_that(self): arglist = [ + '--description', 'newDescription', '--enable', '--vnic-type', 'macvtap', '--binding-profile', 'foo=bar', @@ -573,6 +579,7 @@ def test_set_that(self): self._port.name, ] verifylist = [ + ('description', 'newDescription'), ('enable', True), ('vnic_type', 'macvtap'), ('binding_profile', {'foo': 'bar'}), @@ -589,6 +596,7 @@ def test_set_that(self): 'binding:vnic_type': 'macvtap', 'binding:profile': {'foo': 'bar'}, 'binding:host_id': 'binding-host-id-xxxx', + 'description': 'newDescription', 'name': 'newName', } self.network.update_port.assert_called_once_with(self._port, **attrs) diff --git a/releasenotes/notes/bp-neutron-client-descriptions-b65dd776f78b5a73.yaml b/releasenotes/notes/bp-neutron-client-descriptions-b65dd776f78b5a73.yaml new file mode 100644 index 0000000000..625a4e76db --- /dev/null +++ b/releasenotes/notes/bp-neutron-client-descriptions-b65dd776f78b5a73.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--description`` option to ``port set`` and + ``port create`` commands. + [Blueprint :oscbp:`neutron-client-descriptions`] From d7c8bb88e4a117d8ab6a53c3a7d14cc7a4105eda Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Sat, 1 Oct 2016 18:38:29 +0800 Subject: [PATCH 1307/3095] Add "volume migrate" command Add "volume migrate" command in volume v1 and v2 to support migrating volume to a new host Change-Id: Ie4e6037171a31a872006a13f9fd1e15eaa627c26 Implements: bp cinder-command-support --- doc/source/command-objects/volume.rst | 42 +++++++++ doc/source/commands.rst | 4 +- .../tests/unit/volume/v1/test_volume.py | 62 +++++++++++++ .../tests/unit/volume/v2/test_volume.py | 90 +++++++++++++++++++ openstackclient/volume/v1/volume.py | 31 +++++++ openstackclient/volume/v2/volume.py | 47 ++++++++++ ...lume-migrate-command-52cf6edd62fe17a7.yaml | 4 + setup.cfg | 2 + 8 files changed, 280 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/volume-migrate-command-52cf6edd62fe17a7.yaml diff --git a/doc/source/command-objects/volume.rst b/doc/source/command-objects/volume.rst index 8f1233614c..703a5c7692 100644 --- a/doc/source/command-objects/volume.rst +++ b/doc/source/command-objects/volume.rst @@ -197,6 +197,48 @@ List volumes *Volume version 2 only* +volume migrate +-------------- + +Migrate volume to a new host + +.. program:: volume migrate +.. code:: bash + + os volume migrate + --host + [--force-host-copy] + [--lock-volume | --unlock-volume] + + +.. option:: --host + + Destination host (takes the form: host@backend-name#pool) (required) + +.. option:: --force-host-copy + + Enable generic host-based force-migration, + which bypasses driver optimizations + +.. option:: --lock-volume + + If specified, the volume state will be locked and will not allow + a migration to be aborted (possibly by another operation) + + *Volume version 2 only* + +.. option:: --unlock-volume + + If specified, the volume state will not be locked and the a + migration can be aborted (default) (possibly by another operation) + + *Volume version 2 only* + +.. _volume_migrate-volume: +.. describe:: + + Volume to migrate (name or ID) + volume set ---------- diff --git a/doc/source/commands.rst b/doc/source/commands.rst index de473a063d..072767a4c8 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -230,8 +230,8 @@ Those actions with an opposite action are noted in parens if applicable. * ``issue`` (``revoke``) - issue a token * ``list`` - display summary information about multiple objects * ``lock`` (``unlock``) - lock one or more servers so that non-admin user won't be able to execute actions -* ``migrate`` - move a server to a different host; ``--live`` performs a - live migration if possible +* ``migrate`` - move a server or a volume to a different host; ``--live`` performs a + live server migration if possible * ``pause`` (``unpause``) - stop one or more servers and leave them in memory * ``reboot`` - forcibly reboot a server * ``rebuild`` - rebuild a server using (most of) the same arguments as in the original create diff --git a/openstackclient/tests/unit/volume/v1/test_volume.py b/openstackclient/tests/unit/volume/v1/test_volume.py index 73c00844e8..8e79ecbba5 100644 --- a/openstackclient/tests/unit/volume/v1/test_volume.py +++ b/openstackclient/tests/unit/volume/v1/test_volume.py @@ -739,6 +739,68 @@ def test_volume_list_negative_limit(self): self.cmd, arglist, verifylist) +class TestVolumeMigrate(TestVolume): + + _volume = volume_fakes.FakeVolume.create_one_volume() + + def setUp(self): + super(TestVolumeMigrate, self).setUp() + + self.volumes_mock.get.return_value = self._volume + self.volumes_mock.migrate_volume.return_value = None + # Get the command object to test + self.cmd = volume.MigrateVolume(self.app, None) + + def test_volume_migrate(self): + arglist = [ + "--host", "host@backend-name#pool", + self._volume.id, + ] + verifylist = [ + ("force_host_copy", False), + ("host", "host@backend-name#pool"), + ("volume", self._volume.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.volumes_mock.get.assert_called_once_with(self._volume.id) + self.volumes_mock.migrate_volume.assert_called_once_with( + self._volume.id, "host@backend-name#pool", False) + self.assertIsNone(result) + + def test_volume_migrate_with_option(self): + arglist = [ + "--force-host-copy", + "--host", "host@backend-name#pool", + self._volume.id, + ] + verifylist = [ + ("force_host_copy", True), + ("host", "host@backend-name#pool"), + ("volume", self._volume.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.volumes_mock.get.assert_called_once_with(self._volume.id) + self.volumes_mock.migrate_volume.assert_called_once_with( + self._volume.id, "host@backend-name#pool", True) + self.assertIsNone(result) + + def test_volume_migrate_without_host(self): + arglist = [ + self._volume.id, + ] + verifylist = [ + ("force_host_copy", False), + ("volume", self._volume.id), + ] + + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + class TestVolumeSet(TestVolume): _volume = volume_fakes.FakeVolume.create_one_volume() diff --git a/openstackclient/tests/unit/volume/v2/test_volume.py b/openstackclient/tests/unit/volume/v2/test_volume.py index f4a7c14283..0a436e61c2 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume.py +++ b/openstackclient/tests/unit/volume/v2/test_volume.py @@ -996,6 +996,96 @@ def test_volume_list_negative_limit(self): self.cmd, arglist, verifylist) +class TestVolumeMigrate(TestVolume): + + _volume = volume_fakes.FakeVolume.create_one_volume() + + def setUp(self): + super(TestVolumeMigrate, self).setUp() + + self.volumes_mock.get.return_value = self._volume + self.volumes_mock.migrate_volume.return_value = None + # Get the command object to test + self.cmd = volume.MigrateVolume(self.app, None) + + def test_volume_migrate(self): + arglist = [ + "--host", "host@backend-name#pool", + self._volume.id, + ] + verifylist = [ + ("force_host_copy", False), + ("lock_volume", False), + ("unlock_volume", False), + ("host", "host@backend-name#pool"), + ("volume", self._volume.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.volumes_mock.get.assert_called_once_with(self._volume.id) + self.volumes_mock.migrate_volume.assert_called_once_with( + self._volume.id, "host@backend-name#pool", False, False) + self.assertIsNone(result) + + def test_volume_migrate_with_option(self): + arglist = [ + "--force-host-copy", + "--lock-volume", + "--host", "host@backend-name#pool", + self._volume.id, + ] + verifylist = [ + ("force_host_copy", True), + ("lock_volume", True), + ("unlock_volume", False), + ("host", "host@backend-name#pool"), + ("volume", self._volume.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.volumes_mock.get.assert_called_once_with(self._volume.id) + self.volumes_mock.migrate_volume.assert_called_once_with( + self._volume.id, "host@backend-name#pool", True, True) + self.assertIsNone(result) + + def test_volume_migrate_with_unlock_volume(self): + arglist = [ + "--unlock-volume", + "--host", "host@backend-name#pool", + self._volume.id, + ] + verifylist = [ + ("force_host_copy", False), + ("lock_volume", False), + ("unlock_volume", True), + ("host", "host@backend-name#pool"), + ("volume", self._volume.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.volumes_mock.get.assert_called_once_with(self._volume.id) + self.volumes_mock.migrate_volume.assert_called_once_with( + self._volume.id, "host@backend-name#pool", False, False) + self.assertIsNone(result) + + def test_volume_migrate_without_host(self): + arglist = [ + self._volume.id, + ] + verifylist = [ + ("force_host_copy", False), + ("lock_volume", False), + ("unlock_volume", False), + ("volume", self._volume.id), + ] + + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + class TestVolumeSet(TestVolume): def setUp(self): diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index cafe8ce6ab..63bcfbb8bb 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -344,6 +344,37 @@ def _format_attach(attachments): ) for s in data)) +class MigrateVolume(command.Command): + """Migrate volume to a new host""" + + def get_parser(self, prog_name): + parser = super(MigrateVolume, self).get_parser(prog_name) + parser.add_argument( + 'volume', + metavar="", + help=_("Volume to migrate (name or ID)") + ) + parser.add_argument( + '--host', + metavar="", + required=True, + help=_("Destination host (takes the form: host@backend-name#pool)") + ) + parser.add_argument( + '--force-host-copy', + action="store_true", + help=_("Enable generic host-based force-migration, " + "which bypasses driver optimizations") + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + volume = utils.find_resource(volume_client.volumes, parsed_args.volume) + volume_client.volumes.migrate_volume(volume.id, parsed_args.host, + parsed_args.force_host_copy,) + + class SetVolume(command.Command): """Set volume properties""" diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index cb409711b5..0e4071fbf4 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -409,6 +409,53 @@ def _format_attach(attachments): ) for s in data)) +class MigrateVolume(command.Command): + """Migrate volume to a new host""" + + def get_parser(self, prog_name): + parser = super(MigrateVolume, self).get_parser(prog_name) + parser.add_argument( + 'volume', + metavar="", + help=_("Volume to migrate (name or ID)") + ) + parser.add_argument( + '--host', + metavar="", + required=True, + help=_("Destination host (takes the form: host@backend-name#pool)") + ) + parser.add_argument( + '--force-host-copy', + action="store_true", + help=_("Enable generic host-based force-migration, " + "which bypasses driver optimizations") + ) + lock_group = parser.add_mutually_exclusive_group() + lock_group.add_argument( + '--lock-volume', + action="store_true", + help=_("If specified, the volume state will be locked " + "and will not allow a migration to be aborted " + "(possibly by another operation)") + ) + lock_group.add_argument( + '--unlock-volume', + action="store_true", + help=_("If specified, the volume state will not be " + "locked and the a migration can be aborted " + "(default) (possibly by another operation)") + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + volume = utils.find_resource(volume_client.volumes, parsed_args.volume) + volume_client.volumes.migrate_volume(volume.id, parsed_args.host, + parsed_args.force_host_copy, + parsed_args.lock_volume,) + + class SetVolume(command.Command): """Set volume properties""" diff --git a/releasenotes/notes/volume-migrate-command-52cf6edd62fe17a7.yaml b/releasenotes/notes/volume-migrate-command-52cf6edd62fe17a7.yaml new file mode 100644 index 0000000000..634f008242 --- /dev/null +++ b/releasenotes/notes/volume-migrate-command-52cf6edd62fe17a7.yaml @@ -0,0 +1,4 @@ +--- +features: + - Add ``volume migrate`` command. + [Blueprint `cinder-command-support `_] diff --git a/setup.cfg b/setup.cfg index a4abec1bc5..c94437393e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -463,6 +463,7 @@ openstack.volume.v1 = volume_create = openstackclient.volume.v1.volume:CreateVolume volume_delete = openstackclient.volume.v1.volume:DeleteVolume volume_list = openstackclient.volume.v1.volume:ListVolume + volume_migrate = openstackclient.volume.v1.volume:MigrateVolume volume_set = openstackclient.volume.v1.volume:SetVolume volume_show = openstackclient.volume.v1.volume:ShowVolume volume_unset = openstackclient.volume.v1.volume:UnsetVolume @@ -517,6 +518,7 @@ openstack.volume.v2 = volume_create = openstackclient.volume.v2.volume:CreateVolume volume_delete = openstackclient.volume.v2.volume:DeleteVolume volume_list = openstackclient.volume.v2.volume:ListVolume + volume_migrate = openstackclient.volume.v2.volume:MigrateVolume volume_set = openstackclient.volume.v2.volume:SetVolume volume_show = openstackclient.volume.v2.volume:ShowVolume volume_unset = openstackclient.volume.v2.volume:UnsetVolume From daffce3a6a31ac59ee10e3cc8fe421320da1704a Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Wed, 28 Sep 2016 09:27:32 +0800 Subject: [PATCH 1308/3095] Add "--read-only" and "--read-write" options in "volume set" Add "--read-only" and "--read-write" options in "volume set" command to set volume access mode. Implements: bp cinder-command-support Change-Id: I76ba85c7d3ff0eb026a9cbd794368d8b2b0d17fe --- doc/source/command-objects/volume.rst | 9 +++ .../tests/unit/volume/v1/test_volume.py | 65 ++++++++++++++----- .../tests/unit/volume/v2/test_volume.py | 41 ++++++++++++ openstackclient/volume/v1/volume.py | 64 ++++++++++++++---- openstackclient/volume/v2/volume.py | 20 ++++++ ...nder-command-support-3db775ba331e113d.yaml | 5 ++ 6 files changed, 176 insertions(+), 28 deletions(-) create mode 100644 releasenotes/notes/bp-cinder-command-support-3db775ba331e113d.yaml diff --git a/doc/source/command-objects/volume.rst b/doc/source/command-objects/volume.rst index 8f1233614c..cc530c5bb2 100644 --- a/doc/source/command-objects/volume.rst +++ b/doc/source/command-objects/volume.rst @@ -213,6 +213,7 @@ Set volume properties [--image-property [...] ] [--state ] [--bootable | --non-bootable] + [--read-only | --read-write] .. option:: --name @@ -239,6 +240,14 @@ Set volume properties Mark volume as non-bootable +.. option:: --read-only + + Set volume to read-only access mode + +.. option:: --read-write + + Set volume to read-write access mode + .. option:: --image-property Set an image property on this volume diff --git a/openstackclient/tests/unit/volume/v1/test_volume.py b/openstackclient/tests/unit/volume/v1/test_volume.py index 73c00844e8..58de6cbd11 100644 --- a/openstackclient/tests/unit/volume/v1/test_volume.py +++ b/openstackclient/tests/unit/volume/v1/test_volume.py @@ -844,8 +844,7 @@ def test_volume_set_size(self): ) self.assertIsNone(result) - @mock.patch.object(volume.LOG, 'error') - def test_volume_set_size_smaller(self, mock_log_error): + def test_volume_set_size_smaller(self): self._volume.status = 'available' arglist = [ '--size', '1', @@ -860,15 +859,11 @@ def test_volume_set_size_smaller(self, mock_log_error): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) - - mock_log_error.assert_called_with("New size must be greater " - "than %s GB", - self._volume.size) - self.assertIsNone(result) + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, + parsed_args) - @mock.patch.object(volume.LOG, 'error') - def test_volume_set_size_not_available(self, mock_log_error): + def test_volume_set_size_not_available(self): self._volume.status = 'error' arglist = [ '--size', '130', @@ -883,12 +878,9 @@ def test_volume_set_size_not_available(self, mock_log_error): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) - - mock_log_error.assert_called_with("Volume is in %s state, it must be " - "available before size can be " - "extended", 'error') - self.assertIsNone(result) + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, + parsed_args) def test_volume_set_property(self): arglist = [ @@ -896,6 +888,8 @@ def test_volume_set_property(self): self._volume.display_name, ] verifylist = [ + ('read_only', False), + ('read_write', False), ('name', None), ('description', None), ('size', None), @@ -916,6 +910,7 @@ def test_volume_set_property(self): self._volume.id, metadata ) + self.volumes_mock.update_readonly_flag.assert_not_called() self.assertIsNone(result) def test_volume_set_bootable(self): @@ -943,6 +938,44 @@ def test_volume_set_bootable(self): self.volumes_mock.set_bootable.assert_called_with( self._volume.id, verifylist[index][0][1]) + def test_volume_set_readonly(self): + arglist = [ + '--read-only', + self._volume.id + ] + verifylist = [ + ('read_only', True), + ('read_write', False), + ('volume', self._volume.id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.volumes_mock.update_readonly_flag.assert_called_once_with( + self._volume.id, + True) + self.assertIsNone(result) + + def test_volume_set_read_write(self): + arglist = [ + '--read-write', + self._volume.id + ] + verifylist = [ + ('read_only', False), + ('read_write', True), + ('volume', self._volume.id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.volumes_mock.update_readonly_flag.assert_called_once_with( + self._volume.id, + False) + self.assertIsNone(result) + class TestVolumeShow(TestVolume): diff --git a/openstackclient/tests/unit/volume/v2/test_volume.py b/openstackclient/tests/unit/volume/v2/test_volume.py index f4a7c14283..4e4c233b20 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume.py +++ b/openstackclient/tests/unit/volume/v2/test_volume.py @@ -1033,6 +1033,8 @@ def test_volume_set_state(self): self.new_volume.id ] verifylist = [ + ('read_only', False), + ('read_write', False), ('state', 'error'), ('volume', self.new_volume.id) ] @@ -1042,6 +1044,7 @@ def test_volume_set_state(self): result = self.cmd.take_action(parsed_args) self.volumes_mock.reset_state.assert_called_with( self.new_volume.id, 'error') + self.volumes_mock.update_readonly_flag.assert_not_called() self.assertIsNone(result) def test_volume_set_state_failed(self): @@ -1090,6 +1093,44 @@ def test_volume_set_bootable(self): self.volumes_mock.set_bootable.assert_called_with( self.new_volume.id, verifylist[index][0][1]) + def test_volume_set_readonly(self): + arglist = [ + '--read-only', + self.new_volume.id + ] + verifylist = [ + ('read_only', True), + ('read_write', False), + ('volume', self.new_volume.id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.volumes_mock.update_readonly_flag.assert_called_once_with( + self.new_volume.id, + True) + self.assertIsNone(result) + + def test_volume_set_read_write(self): + arglist = [ + '--read-write', + self.new_volume.id + ] + verifylist = [ + ('read_only', False), + ('read_write', True), + ('volume', self.new_volume.id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.volumes_mock.update_readonly_flag.assert_called_once_with( + self.new_volume.id, + False) + self.assertIsNone(result) + class TestVolumeShow(TestVolume): diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index cafe8ce6ab..5b8219cad6 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -388,38 +388,78 @@ def get_parser(self, prog_name): action="store_true", help=_("Mark volume as non-bootable") ) + readonly_group = parser.add_mutually_exclusive_group() + readonly_group.add_argument( + "--read-only", + action="store_true", + help=_("Set volume to read-only access mode") + ) + readonly_group.add_argument( + "--read-write", + action="store_true", + help=_("Set volume to read-write access mode") + ) return parser def take_action(self, parsed_args): volume_client = self.app.client_manager.volume volume = utils.find_resource(volume_client.volumes, parsed_args.volume) + result = 0 if parsed_args.size: - if volume.status != 'available': - LOG.error(_("Volume is in %s state, it must be available " - "before size can be extended"), volume.status) - return - if parsed_args.size <= volume.size: - LOG.error(_("New size must be greater than %s GB"), - volume.size) - return - volume_client.volumes.extend(volume.id, parsed_args.size) - + try: + if volume.status != 'available': + msg = (_("Volume is in %s state, it must be available " + "before size can be extended") % volume.status) + raise exceptions.CommandError(msg) + if parsed_args.size <= volume.size: + msg = (_("New size must be greater than %s GB") + % volume.size) + raise exceptions.CommandError(msg) + volume_client.volumes.extend(volume.id, parsed_args.size) + except Exception as e: + LOG.error(_("Failed to set volume size: %s"), e) + result += 1 if parsed_args.property: - volume_client.volumes.set_metadata(volume.id, parsed_args.property) + try: + volume_client.volumes.set_metadata( + volume.id, + parsed_args.property) + except Exception as e: + LOG.error(_("Failed to set volume property: %s"), e) + result += 1 if parsed_args.bootable or parsed_args.non_bootable: try: volume_client.volumes.set_bootable( volume.id, parsed_args.bootable) except Exception as e: LOG.error(_("Failed to set volume bootable property: %s"), e) + result += 1 + if parsed_args.read_only or parsed_args.read_write: + try: + volume_client.volumes.update_readonly_flag( + volume.id, + parsed_args.read_only) + except Exception as e: + LOG.error(_("Failed to set volume read-only access " + "mode flag: %s"), e) + result += 1 kwargs = {} if parsed_args.name: kwargs['display_name'] = parsed_args.name if parsed_args.description: kwargs['display_description'] = parsed_args.description if kwargs: - volume_client.volumes.update(volume.id, **kwargs) + try: + volume_client.volumes.update(volume.id, **kwargs) + except Exception as e: + LOG.error(_("Failed to update volume display name " + "or display description: %s"), e) + result += 1 + + if result > 0: + raise exceptions.CommandError(_("One or more of the " + "set operations failed")) class ShowVolume(command.ShowOne): diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index cb409711b5..695139bd51 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -473,6 +473,17 @@ def get_parser(self, prog_name): action="store_true", help=_("Mark volume as non-bootable") ) + readonly_group = parser.add_mutually_exclusive_group() + readonly_group.add_argument( + "--read-only", + action="store_true", + help=_("Set volume to read-only access mode") + ) + readonly_group.add_argument( + "--read-write", + action="store_true", + help=_("Set volume to read-write access mode") + ) return parser def take_action(self, parsed_args): @@ -523,6 +534,15 @@ def take_action(self, parsed_args): except Exception as e: LOG.error(_("Failed to set volume bootable property: %s"), e) result += 1 + if parsed_args.read_only or parsed_args.read_write: + try: + volume_client.volumes.update_readonly_flag( + volume.id, + parsed_args.read_only) + except Exception as e: + LOG.error(_("Failed to set volume read-only access " + "mode flag: %s"), e) + result += 1 kwargs = {} if parsed_args.name: diff --git a/releasenotes/notes/bp-cinder-command-support-3db775ba331e113d.yaml b/releasenotes/notes/bp-cinder-command-support-3db775ba331e113d.yaml new file mode 100644 index 0000000000..823201121d --- /dev/null +++ b/releasenotes/notes/bp-cinder-command-support-3db775ba331e113d.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``--read-only`` and ``--read-write`` options to ``volume set`` command. + [Blueprint `cinder-command-support `_] From acc39673abfc6cd953628dbea94eb65ad2c9b361 Mon Sep 17 00:00:00 2001 From: Kyrylo Romanenko Date: Tue, 18 Oct 2016 19:09:06 +0300 Subject: [PATCH 1309/3095] Rename variable to avoid shadowing of built-in name Change-Id: I06e2617db1d5508723bc343072a15586af89b390 --- openstackclient/tests/functional/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openstackclient/tests/functional/base.py b/openstackclient/tests/functional/base.py index 298b24548b..885abc02eb 100644 --- a/openstackclient/tests/functional/base.py +++ b/openstackclient/tests/functional/base.py @@ -63,8 +63,8 @@ def get_openstack_extention_names(cls): return cls.openstack('extension list ' + opts) @classmethod - def get_opts(cls, fields, format='value'): - return ' -f {0} {1}'.format(format, + def get_opts(cls, fields, output_format='value'): + return ' -f {0} {1}'.format(output_format, ' '.join(['-c ' + it for it in fields])) @classmethod From d373d76d1dc60a604d7750219be5573bbf903600 Mon Sep 17 00:00:00 2001 From: Carl Baldwin Date: Thu, 13 Oct 2016 20:29:04 +0000 Subject: [PATCH 1310/3095] Reset allocation pools to [] instead of '' the sdk expects subnet allocation pools to be reset to an empty array, not an empty string. Currently this results in an error message: "Invalid input for allocation_pools. Reason: Invalid data format for IP pool" Change-Id: I7cc84b9c8e4abdbd2c91e5d591ad31f0849c1a83 Closes-Bug: #1634672 --- openstackclient/network/v2/subnet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 1b778c9114..f1ecb5a727 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -542,7 +542,7 @@ def take_action(self, parsed_args): if not parsed_args.no_allocation_pool: attrs['allocation_pools'] += obj.allocation_pools elif parsed_args.no_allocation_pool: - attrs['allocation_pools'] = '' + attrs['allocation_pools'] = [] if 'service_types' in attrs: attrs['service_types'] += obj.service_types client.update_subnet(obj, **attrs) From 3770ad08b21b5ad7dd5430e810f1618435b269d5 Mon Sep 17 00:00:00 2001 From: qtang Date: Thu, 29 Sep 2016 15:00:17 +0800 Subject: [PATCH 1311/3095] Warning for empty password set for user create/set Raise warning when empty password set for user Change-Id: If03516f3f1290e4c329fe3d1277dee0512de0410 Closes-Bug: #1607959 --- openstackclient/identity/v2_0/user.py | 8 ++++++++ openstackclient/identity/v3/user.py | 12 ++++++++++++ releasenotes/notes/bug-1607959-a52aa93e3793f28a.yaml | 6 ++++++ 3 files changed, 26 insertions(+) create mode 100644 releasenotes/notes/bug-1607959-a52aa93e3793f28a.yaml diff --git a/openstackclient/identity/v2_0/user.py b/openstackclient/identity/v2_0/user.py index d2075150d2..bc091ce7a3 100644 --- a/openstackclient/identity/v2_0/user.py +++ b/openstackclient/identity/v2_0/user.py @@ -94,6 +94,10 @@ def take_action(self, parsed_args): if parsed_args.password_prompt: parsed_args.password = utils.get_password(self.app.stdin) + if not parsed_args.password: + LOG.warning(_("No password was supplied, authentication will fail " + "when a user does not have a password.")) + try: user = identity_client.users.create( parsed_args.name, @@ -292,6 +296,10 @@ def take_action(self, parsed_args): if parsed_args.password_prompt: parsed_args.password = utils.get_password(self.app.stdin) + if '' == parsed_args.password: + LOG.warning(_("No password was supplied, authentication will fail " + "when a user does not have a password.")) + user = utils.find_resource( identity_client.users, parsed_args.user, diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index dc47ef8d54..1e086fb6aa 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -110,6 +110,10 @@ def take_action(self, parsed_args): if parsed_args.password_prompt: parsed_args.password = utils.get_password(self.app.stdin) + if not parsed_args.password: + LOG.warning(_("No password was supplied, authentication will fail " + "when a user does not have a password.")) + try: user = identity_client.users.create( name=parsed_args.name, @@ -329,6 +333,10 @@ def take_action(self, parsed_args): if parsed_args.password_prompt: parsed_args.password = utils.get_password(self.app.stdin) + if '' == parsed_args.password: + LOG.warning(_("No password was supplied, authentication will fail " + "when a user does not have a password.")) + user = utils.find_resource( identity_client.users, parsed_args.user, @@ -408,6 +416,10 @@ def take_action(self, parsed_args): password = utils.get_password( self.app.stdin, prompt="New Password:") + if '' == password: + LOG.warning(_("No password was supplied, authentication will fail " + "when a user does not have a password.")) + identity_client.users.update_password(current_password, password) diff --git a/releasenotes/notes/bug-1607959-a52aa93e3793f28a.yaml b/releasenotes/notes/bug-1607959-a52aa93e3793f28a.yaml new file mode 100644 index 0000000000..172b1301d9 --- /dev/null +++ b/releasenotes/notes/bug-1607959-a52aa93e3793f28a.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + A warning message will be shown when an empty password is used + for ``user create`` and ``user set`` operations. + [Bug `1607959 `_] \ No newline at end of file From 6eff90c3d6058af6d2acd70a615fa7ef3379899f Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 19 Oct 2016 03:58:36 +0000 Subject: [PATCH 1312/3095] Updated from global requirements Change-Id: I8345847ce7c5987de4446d5a4c2c275fc376a3bb --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 8cd7cef175..682527279a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,6 +12,6 @@ osc-lib>=1.2.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 oslo.utils>=3.16.0 # Apache-2.0 python-glanceclient>=2.5.0 # Apache-2.0 -python-keystoneclient!=2.1.0,>=2.0.0 # Apache-2.0 +python-keystoneclient>=3.6.0 # Apache-2.0 python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0 python-cinderclient!=1.7.0,!=1.7.1,>=1.6.0 # Apache-2.0 From a1e305641430af48b72c941f87c7ffcc182b3f9a Mon Sep 17 00:00:00 2001 From: Yan Xing'an Date: Mon, 17 Oct 2016 22:33:35 -0700 Subject: [PATCH 1313/3095] Add option to allow filtering by mac-address on port list Added support to allow filtering ports via --mac-address option to the port list command. Change-Id: I903e443f2f9057571d46520c4cafb88d1972c0cb Partial-bug: #1634333 Partially-Implements: blueprint network-commands-options --- doc/source/command-objects/port.rst | 5 ++++ openstackclient/network/v2/port.py | 7 +++++ .../tests/unit/network/v2/test_port.py | 26 +++++++++++++++++-- .../notes/bug-1634333-a2b04d33ca39440e.yaml | 6 +++++ 4 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/bug-1634333-a2b04d33ca39440e.yaml diff --git a/doc/source/command-objects/port.rst b/doc/source/command-objects/port.rst index fe256d0940..f4dbe26393 100644 --- a/doc/source/command-objects/port.rst +++ b/doc/source/command-objects/port.rst @@ -127,6 +127,7 @@ List ports [--device-owner ] [--router | --server ] [--network ] + [--mac-address ] [--long] .. option:: --device-owner @@ -146,6 +147,10 @@ List ports List only ports attached to this network (name or ID) +.. option:: --mac-address + + List only ports with this MAC address + .. option:: --long List additional fields in output diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 86174d535a..8916fd7304 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -384,6 +384,11 @@ def get_parser(self, prog_name): metavar='', help=_("List only ports attached to this server (name or ID)"), ) + parser.add_argument( + '--mac-address', + metavar='', + help=_("List only ports with this MAC address") + ) parser.add_argument( '--long', action='store_true', @@ -429,6 +434,8 @@ def take_action(self, parsed_args): network = network_client.find_network(parsed_args.network, ignore_missing=False) filters['network_id'] = network.id + if parsed_args.mac_address: + filters['mac_address'] = parsed_args.mac_address data = network_client.ports(**filters) diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index 4ff278a994..4f6eac53e4 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -530,12 +530,14 @@ def test_port_list_all_opt(self): '--device-owner', self._ports[0].device_owner, '--router', 'fake-router-name', '--network', 'fake-network-name', + '--mac-address', self._ports[0].mac_address, ] verifylist = [ ('device_owner', self._ports[0].device_owner), ('router', 'fake-router-name'), - ('network', 'fake-network-name') + ('network', 'fake-network-name'), + ('mac_address', self._ports[0].mac_address) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -545,7 +547,27 @@ def test_port_list_all_opt(self): self.network.ports.assert_called_once_with(**{ 'device_owner': self._ports[0].device_owner, 'device_id': 'fake-router-id', - 'network_id': 'fake-network-id' + 'network_id': 'fake-network-id', + 'mac_address': self._ports[0].mac_address + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_port_list_mac_address_opt(self): + arglist = [ + '--mac-address', self._ports[0].mac_address, + ] + + verifylist = [ + ('mac_address', self._ports[0].mac_address) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.ports.assert_called_once_with(**{ + 'mac_address': self._ports[0].mac_address }) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) diff --git a/releasenotes/notes/bug-1634333-a2b04d33ca39440e.yaml b/releasenotes/notes/bug-1634333-a2b04d33ca39440e.yaml new file mode 100644 index 0000000000..4ecee30aa2 --- /dev/null +++ b/releasenotes/notes/bug-1634333-a2b04d33ca39440e.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add support to allow filtering ports via ``--mac-address`` + option to the ``port list`` command. + [Bug `1634333 `_] From fee1a31296cb47de105d0fa70a509570151f9d81 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 19 Oct 2016 17:46:01 +0000 Subject: [PATCH 1314/3095] Updated from global requirements Change-Id: I54c524ba67b4f481fe3e262dcc4bc6c5601608a8 --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index d799f5a669..87c82b46f7 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -13,7 +13,7 @@ requests>=2.10.0 # Apache-2.0 requests-mock>=1.1 # Apache-2.0 sphinx!=1.3b1,<1.4,>=1.2.1 # BSD stevedore>=1.17.1 # Apache-2.0 -os-client-config!=1.19.0,!=1.19.1,!=1.20.0,!=1.20.1,!=1.21.0,>=1.13.1 # Apache-2.0 +os-client-config>=1.22.0 # Apache-2.0 os-testr>=0.8.0 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD testtools>=1.4.0 # MIT From 57d5f945402681e5f7d62b9ca99fc229927cc784 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Thu, 20 Oct 2016 10:55:50 +0800 Subject: [PATCH 1315/3095] Allow input the QoS policy name in network rbac create command We could input a QoS policy ID for the "rbac_object" parameter in "network rbac create" command but not name before. After this change, "rbac_object" parameter can be both QoS policy name or ID. Change-Id: I0fd6b5b5ae410074d85475ef49e5a0a9a52bf86f --- doc/source/command-objects/network-rbac.rst | 2 +- openstackclient/network/v2/network_rbac.py | 9 ++--- .../unit/network/v2/test_network_rbac.py | 40 +++++++++++++++++++ ...-network-rbac-create-d1f4de77ad2dd421.yaml | 4 ++ 4 files changed, 49 insertions(+), 6 deletions(-) create mode 100644 releasenotes/notes/fix-network-rbac-create-d1f4de77ad2dd421.yaml diff --git a/doc/source/command-objects/network-rbac.rst b/doc/source/command-objects/network-rbac.rst index 8acf097964..113242f471 100644 --- a/doc/source/command-objects/network-rbac.rst +++ b/doc/source/command-objects/network-rbac.rst @@ -52,7 +52,7 @@ Create network RBAC policy .. _network_rbac_create-rbac-policy: .. describe:: - The object to which this RBAC policy affects (name or ID for network objects, ID only for QoS policy objects) + The object to which this RBAC policy affects (name or ID) network rbac delete ------------------- diff --git a/openstackclient/network/v2/network_rbac.py b/openstackclient/network/v2/network_rbac.py index bb29579f95..b1e0413f71 100644 --- a/openstackclient/network/v2/network_rbac.py +++ b/openstackclient/network/v2/network_rbac.py @@ -47,9 +47,9 @@ def _get_attrs(client_manager, parsed_args): object_id = network_client.find_network( parsed_args.rbac_object, ignore_missing=False).id if parsed_args.type == 'qos_policy': - # TODO(Huanxuan Ao): Support finding a object ID by obejct name - # after qos policy finding supported in SDK. - object_id = parsed_args.rbac_object + object_id = network_client.find_qos_policy( + parsed_args.rbac_object, + ignore_missing=False).id attrs['object_id'] = object_id identity_client = client_manager.identity @@ -78,8 +78,7 @@ def get_parser(self, prog_name): parser.add_argument( 'rbac_object', metavar="", - help=_("The object to which this RBAC policy affects (name or " - "ID for network objects, ID only for QoS policy objects)") + help=_("The object to which this RBAC policy affects (name or ID)") ) parser.add_argument( '--type', diff --git a/openstackclient/tests/unit/network/v2/test_network_rbac.py b/openstackclient/tests/unit/network/v2/test_network_rbac.py index c526ae4e4b..b884dbc02d 100644 --- a/openstackclient/tests/unit/network/v2/test_network_rbac.py +++ b/openstackclient/tests/unit/network/v2/test_network_rbac.py @@ -36,6 +36,7 @@ def setUp(self): class TestCreateNetworkRBAC(TestNetworkRBAC): network_object = network_fakes.FakeNetwork.create_one_network() + qos_object = network_fakes.FakeNetworkQosPolicy.create_one_qos_policy() project = identity_fakes_v3.FakeProject.create_one_project() rbac_policy = network_fakes.FakeNetworkRBAC.create_one_network_rbac( attrs={'tenant_id': project.id, @@ -71,6 +72,8 @@ def setUp(self): return_value=self.rbac_policy) self.network.find_network = mock.Mock( return_value=self.network_object) + self.network.find_qos_policy = mock.Mock( + return_value=self.qos_object) self.projects_mock.get.return_value = self.project def test_network_rbac_create_no_type(self): @@ -194,6 +197,43 @@ def test_network_rbac_create_all_options(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + def test_network_rbac_create_qos_object(self): + self.rbac_policy.object_type = 'qos_policy' + self.rbac_policy.object_id = self.qos_object.id + arglist = [ + '--type', 'qos_policy', + '--action', self.rbac_policy.action, + '--target-project', self.rbac_policy.target_tenant, + self.qos_object.name, + ] + verifylist = [ + ('type', 'qos_policy'), + ('action', self.rbac_policy.action), + ('target_project', self.rbac_policy.target_tenant), + ('rbac_object', self.qos_object.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_rbac_policy.assert_called_with(**{ + 'object_id': self.qos_object.id, + 'object_type': 'qos_policy', + 'action': self.rbac_policy.action, + 'target_tenant': self.rbac_policy.target_tenant, + }) + self.data = [ + self.rbac_policy.action, + self.rbac_policy.id, + self.qos_object.id, + 'qos_policy', + self.rbac_policy.tenant_id, + self.rbac_policy.target_tenant, + ] + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + class TestDeleteNetworkRBAC(TestNetworkRBAC): diff --git a/releasenotes/notes/fix-network-rbac-create-d1f4de77ad2dd421.yaml b/releasenotes/notes/fix-network-rbac-create-d1f4de77ad2dd421.yaml new file mode 100644 index 0000000000..964828ca19 --- /dev/null +++ b/releasenotes/notes/fix-network-rbac-create-d1f4de77ad2dd421.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + ``rbac_object`` parameter in ``network rbac create`` command now can be a QoS policy name. From e6fb6586063195188e185260786e8f4f1fd94d7f Mon Sep 17 00:00:00 2001 From: Yan Xing'an Date: Wed, 19 Oct 2016 21:21:25 -0700 Subject: [PATCH 1316/3095] Correct help string of the subnet pool list options Correct help string of subnet pool list options, from "subnets" to "subnet pools". Change-Id: Ic6dd6649f411748cd0c6e6b08f0451c8a0fa220f Partially-Implements: blueprint network-commands-options --- doc/source/command-objects/subnet-pool.rst | 14 +++++++------- openstackclient/network/v2/subnet_pool.py | 18 ++++++++++-------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/doc/source/command-objects/subnet-pool.rst b/doc/source/command-objects/subnet-pool.rst index 28a735d613..bd1356f394 100644 --- a/doc/source/command-objects/subnet-pool.rst +++ b/doc/source/command-objects/subnet-pool.rst @@ -121,23 +121,23 @@ List subnet pools .. option:: --share - List subnets shared between projects + List subnet pools shared between projects .. option:: --no-share - List subnets not shared between projects + List subnet pools not shared between projects .. option:: --default - List subnets used as the default external subnet pool + List subnet pools used as the default external subnet pool .. option:: --no-default - List subnets not used as the default external subnet pool + List subnet pools not used as the default external subnet pool .. option:: --project - List subnets according to their project (name or ID) + List subnet pools according to their project (name or ID) .. option:: --project-domain @@ -146,11 +146,11 @@ List subnet pools .. option:: --name - List only subnets of given name in output + List only subnet pools of given name in output .. option:: --address-scope - List only subnets of given address scope (name or ID) in output + List only subnet pools of given address scope (name or ID) in output subnet pool set --------------- diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index a01d2f7ba2..a29c451859 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -238,40 +238,42 @@ def get_parser(self, prog_name): shared_group.add_argument( '--share', action='store_true', - help=_("List subnets shared between projects"), + help=_("List subnet pools shared between projects"), ) shared_group.add_argument( '--no-share', action='store_true', - help=_("List subnets not shared between projects"), + help=_("List subnet pools not shared between projects"), ) default_group = parser.add_mutually_exclusive_group() default_group.add_argument( '--default', action='store_true', - help=_("List subnets used as the default external subnet pool"), + help=_("List subnet pools used as the default external " + "subnet pool"), ) default_group.add_argument( '--no-default', action='store_true', - help=_("List subnets not used as the default external subnet pool") + help=_("List subnet pools not used as the default external " + "subnet pool") ) parser.add_argument( '--project', metavar='', - help=_("List subnets according to their project (name or ID)") + help=_("List subnet pools according to their project (name or ID)") ) identity_common.add_project_domain_option_to_parser(parser) parser.add_argument( '--name', metavar='', - help=_("List only subnets of given name in output") + help=_("List only subnet pools of given name in output") ) parser.add_argument( '--address-scope', metavar='', - help=_("List only subnets of given address scope (name or ID) " - "in output") + help=_("List only subnet pools of given address scope " + "(name or ID) in output") ) return parser From e0aa1acbaee0538b97cbf379d0daa1f22a4e0b72 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Fri, 21 Oct 2016 00:50:36 +0000 Subject: [PATCH 1317/3095] Updated from global requirements Change-Id: Iaa10a45834aef38a432c6cf84a7766252116773a --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 682527279a..a33e9aada0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ keystoneauth1>=2.14.0 # Apache-2.0 openstacksdk>=0.9.7 # Apache-2.0 osc-lib>=1.2.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 -oslo.utils>=3.16.0 # Apache-2.0 +oslo.utils>=3.17.0 # Apache-2.0 python-glanceclient>=2.5.0 # Apache-2.0 python-keystoneclient>=3.6.0 # Apache-2.0 python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0 From 292608dc08b7cbc792f83ae55862e482f5324a97 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Thu, 20 Oct 2016 14:11:33 +0800 Subject: [PATCH 1318/3095] Use FakeImage class to replace duplicated image info in volume test FakeImage class has been added in image v1 so that we can reuse it in volume v1 unit test to replace the duplicated image info in volume v1 fake. Change-Id: I0f96e568bd65e59241b57704fc2a379319a386d8 --- openstackclient/tests/unit/volume/v1/fakes.py | 13 --------- .../tests/unit/volume/v1/test_volume.py | 29 +++++++------------ 2 files changed, 11 insertions(+), 31 deletions(-) diff --git a/openstackclient/tests/unit/volume/v1/fakes.py b/openstackclient/tests/unit/volume/v1/fakes.py index a11ea49137..434e637a0a 100644 --- a/openstackclient/tests/unit/volume/v1/fakes.py +++ b/openstackclient/tests/unit/volume/v1/fakes.py @@ -69,19 +69,6 @@ 'links': extension_links, } -# NOTE(dtroyer): duplicating here the minimum image info needed to test -# volume create --image until circular references can be -# avoided by refactoring the test fakes. - -image_id = 'im1' -image_name = 'graven' - - -IMAGE = { - 'id': image_id, - 'name': image_name, -} - type_id = "5520dc9e-6f9b-4378-a719-729911c0f407" type_name = "fake-lvmdriver-1" diff --git a/openstackclient/tests/unit/volume/v1/test_volume.py b/openstackclient/tests/unit/volume/v1/test_volume.py index 73c00844e8..cd3cfd5d08 100644 --- a/openstackclient/tests/unit/volume/v1/test_volume.py +++ b/openstackclient/tests/unit/volume/v1/test_volume.py @@ -14,15 +14,14 @@ # import argparse -import copy import mock from mock import call from osc_lib import exceptions from osc_lib import utils -from openstackclient.tests.unit import fakes from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes +from openstackclient.tests.unit.image.v1 import fakes as image_fakes from openstackclient.tests.unit import utils as tests_utils from openstackclient.tests.unit.volume.v1 import fakes as volume_fakes from openstackclient.volume.v1 import volume @@ -321,19 +320,16 @@ def test_volume_create_properties(self): self.assertEqual(self.datalist, data) def test_volume_create_image_id(self): - self.images_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.IMAGE), - loaded=True, - ) + image = image_fakes.FakeImage.create_one_image() + self.images_mock.get.return_value = image arglist = [ - '--image', volume_fakes.image_id, + '--image', image.id, '--size', str(self.new_volume.size), self.new_volume.display_name, ] verifylist = [ - ('image', volume_fakes.image_id), + ('image', image.id), ('size', self.new_volume.size), ('name', self.new_volume.display_name), ] @@ -360,26 +356,23 @@ def test_volume_create_image_id(self): None, None, None, - volume_fakes.image_id, + image.id, ) self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, data) def test_volume_create_image_name(self): - self.images_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.IMAGE), - loaded=True, - ) + image = image_fakes.FakeImage.create_one_image() + self.images_mock.get.return_value = image arglist = [ - '--image', volume_fakes.image_name, + '--image', image.name, '--size', str(self.new_volume.size), self.new_volume.display_name, ] verifylist = [ - ('image', volume_fakes.image_name), + ('image', image.name), ('size', self.new_volume.size), ('name', self.new_volume.display_name), ] @@ -406,7 +399,7 @@ def test_volume_create_image_name(self): None, None, None, - volume_fakes.image_id, + image.id, ) self.assertEqual(self.columns, columns) From 65a08d6af6cdf023e36443dfa89d01dd3f48c336 Mon Sep 17 00:00:00 2001 From: Yan Xing'an Date: Thu, 20 Oct 2016 21:56:00 -0700 Subject: [PATCH 1319/3095] Improve a network testcase Fix a typo networ to network, and add assertEqual lines. Change-Id: I13c0c775c1d5f7a8f579d387cd9bea1bdd74aebc --- openstackclient/tests/unit/network/v2/test_network.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openstackclient/tests/unit/network/v2/test_network.py b/openstackclient/tests/unit/network/v2/test_network.py index 50a60c2d47..828da4a26f 100644 --- a/openstackclient/tests/unit/network/v2/test_network.py +++ b/openstackclient/tests/unit/network/v2/test_network.py @@ -609,7 +609,7 @@ def test_network_list_project(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) - def test_networ_list_project_domain(self): + def test_network_list_project_domain(self): project = identity_fakes_v3.FakeProject.create_one_project() self.projects_mock.get.return_value = project arglist = [ @@ -625,6 +625,8 @@ def test_networ_list_project_domain(self): filters = {'tenant_id': project.id} self.network.networks.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) def test_network_list_share(self): arglist = [ From 586ac0205826a44937ab1a0382d92c9f8e002696 Mon Sep 17 00:00:00 2001 From: liujunpeng Date: Wed, 19 Oct 2016 04:50:02 -0700 Subject: [PATCH 1320/3095] update volume and zone commands -add volume backup -add zone export/import Change-Id: Ied99ba78e9a45d323e68656ada318ed2984bdc02 --- doc/source/commands.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/source/commands.rst b/doc/source/commands.rst index de473a063d..3e0f932eeb 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -143,12 +143,12 @@ referring to both Compute and Volume quotas. * ``user``: (**Identity**) individual cloud resources users * ``user role``: (**Identity**) roles assigned to a user * ``volume``: (**Volume**) block volumes +* ``volume backup``: (**Volume**) backup for volumes * ``volume qos``: (**Volume**) quality-of-service (QoS) specification for volumes * ``volume type``: (**Volume**) deployment-specific types of volumes available * ``volume service``: (**Volume**) services to manage block storage operations * ``volume transfer request``: (**Volume**) volume owner transfer request - Plugin Objects -------------- @@ -210,6 +210,8 @@ list check out :doc:`plugin-commands`. * ``workflow execution``: (**Workflow Engine (Mistral)**) * ``zone``: (**DNS (Designate)**) * ``zone blacklist``: (**DNS (Designate)**) +* ``zone export``: (**DNS (Designate)**) +* ``zone import``: (**DNS (Designate)**) * ``zone transfer``: (**DNS (Designate)**) From 09eb81674ba3470415a54310d07a6090ee9aed75 Mon Sep 17 00:00:00 2001 From: liujunpeng Date: Fri, 21 Oct 2016 01:34:03 -0700 Subject: [PATCH 1321/3095] update openstackclient page url The wiki url has been deprecated. Change-Id: I4101b357d59661dce06519f158181982c5f96db5 --- doc/source/man/openstack.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/man/openstack.rst b/doc/source/man/openstack.rst index e03b48e5c8..aeed2cadba 100644 --- a/doc/source/man/openstack.rst +++ b/doc/source/man/openstack.rst @@ -417,8 +417,8 @@ http://www.apache.org/licenses/LICENSE-2.0 SEE ALSO ======== -The `OpenStackClient page `_ -in the `OpenStack Wiki `_ contains further +The `OpenStackClient page `_ +in the `OpenStack Docs `_ contains further documentation. The individual OpenStack project CLIs, the OpenStack API references. From be9306f1d4ad8aedbbe2470163ff3dd9566e7a00 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sat, 22 Oct 2016 01:27:27 +0000 Subject: [PATCH 1322/3095] Updated from global requirements Change-Id: I76fa121b61b50403af404c8d6e5d9a390231230c --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 87c82b46f7..4006314dec 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -3,7 +3,7 @@ # process, which may cause wedges in the gate later. hacking<0.11,>=0.10.0 -coverage>=3.6 # Apache-2.0 +coverage>=4.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD mock>=2.0 # BSD oslosphinx>=4.7.0 # Apache-2.0 From 151c161a5eaa2ef98bdd9fa291c1947ef1dd9487 Mon Sep 17 00:00:00 2001 From: judy-yu Date: Mon, 24 Oct 2016 17:16:48 +0800 Subject: [PATCH 1323/3095] Avoid duplicated project_id when show network Project_id appear twice when show network. This patch check and not append if it already has one. Change-Id: I4400239f454522101b639a0412050dd60eb6a612 Closes-Bug: #1636123 Partially-Implements: blueprint duplicated-project-id --- openstackclient/network/v2/network.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index dbf1b6010d..40183b73fb 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -42,6 +42,7 @@ def _get_columns(item): columns = list(item.keys()) if 'tenant_id' in columns: columns.remove('tenant_id') + if 'project_id' not in columns: columns.append('project_id') return tuple(sorted(columns)) From f4f1a516928f274dc85ee85cbaa2cd4b5f8ff901 Mon Sep 17 00:00:00 2001 From: Iswarya_Vakati Date: Mon, 24 Oct 2016 17:52:59 +0530 Subject: [PATCH 1324/3095] Updated coverage configuration file removed unneccassary directories in .coveragerc file openstackclient/openstack/* it is no longer valid, we no longer use content from oslo-incubator Change-Id: I0b361617e855aae322009b97697dcc13a394ceba --- .coveragerc | 1 - 1 file changed, 1 deletion(-) diff --git a/.coveragerc b/.coveragerc index 2651b2ffef..3685187b80 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,7 +1,6 @@ [run] branch = True source = openstackclient -omit = openstackclient/openstack/* [report] ignore_errors = True From 5055074db00f75285034c056b631d85f42cd9086 Mon Sep 17 00:00:00 2001 From: Anne Gentle Date: Mon, 24 Oct 2016 10:55:10 +0200 Subject: [PATCH 1325/3095] Adds information about private key generation for instance access - Also updated the help text in the command itself. Change-Id: Ib3d4f94ef415a3f12024d0d7c000d2de20de001b Partial-Bug: 1549410 --- doc/source/command-objects/keypair.rst | 22 ++++++++++-------- openstackclient/compute/v2/keypair.py | 23 ++++++++++--------- .../tests/unit/compute/v2/test_keypair.py | 3 +-- 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/doc/source/command-objects/keypair.rst b/doc/source/command-objects/keypair.rst index af50a6511d..c9bf085b44 100644 --- a/doc/source/command-objects/keypair.rst +++ b/doc/source/command-objects/keypair.rst @@ -3,14 +3,16 @@ keypair ======= The badly named keypair is really the public key of an OpenSSH key pair to be -used for access to created servers. +used for access to created servers. You can also create a private key for +access to a created server by not passing any argument to the keypair create +command. Compute v2 keypair create -------------- -Create new public key +Create new public or private key for server ssh access .. program:: keypair create .. code:: bash @@ -21,16 +23,16 @@ Create new public key .. option:: --public-key - Filename for public key to add + Filename for public key to add. If not used, creates a private key. .. describe:: - New public key name + New public or private key name keypair delete -------------- -Delete public key(s) +Delete public or private key(s) .. program:: keypair delete .. code:: bash @@ -40,12 +42,12 @@ Delete public key(s) .. describe:: - Public key(s) to delete (name only) + Name of key(s) to delete (name only) keypair list ------------ -List public key fingerprints +List key fingerprints .. program:: keypair list .. code:: bash @@ -55,7 +57,7 @@ List public key fingerprints keypair show ------------ -Display public key details +Display key details .. program:: keypair show .. code:: bash @@ -66,8 +68,8 @@ Display public key details .. option:: --public-key - Show only bare public key (name only) + Show only bare public key paired with the generated key .. describe:: - Public key to display (name only) + Public or private key to display (name only) diff --git a/openstackclient/compute/v2/keypair.py b/openstackclient/compute/v2/keypair.py index d30fd429e3..d5c682f455 100644 --- a/openstackclient/compute/v2/keypair.py +++ b/openstackclient/compute/v2/keypair.py @@ -32,19 +32,20 @@ class CreateKeypair(command.ShowOne): - """Create new public key""" + """Create new public or private key for server ssh access""" def get_parser(self, prog_name): parser = super(CreateKeypair, self).get_parser(prog_name) parser.add_argument( 'name', metavar='', - help=_("New public key name") + help=_("New public or private key name") ) parser.add_argument( '--public-key', metavar='', - help=_("Filename for public key to add") + help=_("Filename for public key to add. If not used, " + "creates a private key.") ) return parser @@ -82,7 +83,7 @@ def take_action(self, parsed_args): class DeleteKeypair(command.Command): - """Delete public key(s)""" + """Delete public or private key(s)""" def get_parser(self, prog_name): parser = super(DeleteKeypair, self).get_parser(prog_name) @@ -90,7 +91,7 @@ def get_parser(self, prog_name): 'name', metavar='', nargs='+', - help=_("Public key(s) to delete (name only)") + help=_("Name of key(s) to delete (name only)") ) return parser @@ -104,19 +105,19 @@ def take_action(self, parsed_args): compute_client.keypairs.delete(data.name) except Exception as e: result += 1 - LOG.error(_("Failed to delete public key with name " + LOG.error(_("Failed to delete key with name " "'%(name)s': %(e)s") % {'name': n, 'e': e}) if result > 0: total = len(parsed_args.name) - msg = (_("%(result)s of %(total)s public keys failed " + msg = (_("%(result)s of %(total)s keys failed " "to delete.") % {'result': result, 'total': total}) raise exceptions.CommandError(msg) class ListKeypair(command.Lister): - """List public key fingerprints""" + """List key fingerprints""" def take_action(self, parsed_args): compute_client = self.app.client_manager.compute @@ -133,20 +134,20 @@ def take_action(self, parsed_args): class ShowKeypair(command.ShowOne): - """Display public key details""" + """Display key details""" def get_parser(self, prog_name): parser = super(ShowKeypair, self).get_parser(prog_name) parser.add_argument( 'name', metavar='', - help=_("Public key to display (name only)") + help=_("Public or private key to display (name only)") ) parser.add_argument( '--public-key', action='store_true', default=False, - help=_("Show only bare public key (name only)") + help=_("Show only bare public key paired with the generated key") ) return parser diff --git a/openstackclient/tests/unit/compute/v2/test_keypair.py b/openstackclient/tests/unit/compute/v2/test_keypair.py index cb0085452d..efc5463cc6 100644 --- a/openstackclient/tests/unit/compute/v2/test_keypair.py +++ b/openstackclient/tests/unit/compute/v2/test_keypair.py @@ -179,8 +179,7 @@ def test_delete_multiple_keypairs_with_exception(self): self.cmd.take_action(parsed_args) self.fail('CommandError should be raised.') except exceptions.CommandError as e: - self.assertEqual('1 of 2 public keys failed to delete.', - str(e)) + self.assertEqual('1 of 2 keys failed to delete.', str(e)) find_mock.assert_any_call( self.keypairs_mock, self.keypairs[0].name) From 8ca1cc637013972491744b8318d30e9256bc4165 Mon Sep 17 00:00:00 2001 From: Yan Xing'an Date: Wed, 26 Oct 2016 23:48:58 -0700 Subject: [PATCH 1326/3095] Add --long option and more columns to the hypervisor list command Support --long option and more columns in output of hypervisor list command, including 'Hypervisor Type', 'Host IP', 'State', and 'vCPU Used', 'vCPUs', 'Memory MB Used', 'Memory MB' with --long option. Change-Id: I0c790c7835309dded03e230cf497168e19404537 Closes-Bug: #1637074 --- doc/source/command-objects/hypervisor.rst | 5 ++ openstackclient/compute/v2/hypervisor.py | 12 +++- .../tests/unit/compute/v2/test_hypervisor.py | 67 ++++++++++++++++++- .../notes/bug-1637074-1b0e409f30f715ca.yaml | 5 ++ 4 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/bug-1637074-1b0e409f30f715ca.yaml diff --git a/doc/source/command-objects/hypervisor.rst b/doc/source/command-objects/hypervisor.rst index d6d0469b8e..3053a7584b 100644 --- a/doc/source/command-objects/hypervisor.rst +++ b/doc/source/command-objects/hypervisor.rst @@ -14,11 +14,16 @@ List hypervisors os hypervisor list [--matching ] + [--long] .. option:: --matching Filter hypervisors using substring +.. option:: --long + + List additional fields in output + hypervisor show --------------- diff --git a/openstackclient/compute/v2/hypervisor.py b/openstackclient/compute/v2/hypervisor.py index 0222e89969..69b5d1370b 100644 --- a/openstackclient/compute/v2/hypervisor.py +++ b/openstackclient/compute/v2/hypervisor.py @@ -35,14 +35,24 @@ def get_parser(self, prog_name): metavar="", help=_("Filter hypervisors using substring") ) + parser.add_argument( + '--long', + action='store_true', + help=_("List additional fields in output") + ) return parser def take_action(self, parsed_args): compute_client = self.app.client_manager.compute columns = ( "ID", - "Hypervisor Hostname" + "Hypervisor Hostname", + "Hypervisor Type", + "Host IP", + "State" ) + if parsed_args.long: + columns += ("vCPUs Used", "vCPUs", "Memory MB Used", "Memory MB") if parsed_args.matching: data = compute_client.hypervisors.search(parsed_args.matching) diff --git a/openstackclient/tests/unit/compute/v2/test_hypervisor.py b/openstackclient/tests/unit/compute/v2/test_hypervisor.py index e39570afc6..7200d04ed8 100644 --- a/openstackclient/tests/unit/compute/v2/test_hypervisor.py +++ b/openstackclient/tests/unit/compute/v2/test_hypervisor.py @@ -48,19 +48,63 @@ def setUp(self): self.columns = ( "ID", - "Hypervisor Hostname" + "Hypervisor Hostname", + "Hypervisor Type", + "Host IP", + "State" + ) + self.columns_long = ( + "ID", + "Hypervisor Hostname", + "Hypervisor Type", + "Host IP", + "State", + "vCPUs Used", + "vCPUs", + "Memory MB Used", + "Memory MB" ) self.data = ( ( self.hypervisors[0].id, self.hypervisors[0].hypervisor_hostname, + self.hypervisors[0].hypervisor_type, + self.hypervisors[0].host_ip, + self.hypervisors[0].state ), ( self.hypervisors[1].id, self.hypervisors[1].hypervisor_hostname, + self.hypervisors[1].hypervisor_type, + self.hypervisors[1].host_ip, + self.hypervisors[1].state ), ) + self.data_long = ( + ( + self.hypervisors[0].id, + self.hypervisors[0].hypervisor_hostname, + self.hypervisors[0].hypervisor_type, + self.hypervisors[0].host_ip, + self.hypervisors[0].state, + self.hypervisors[0].vcpus_used, + self.hypervisors[0].vcpus, + self.hypervisors[0].memory_mb_used, + self.hypervisors[0].memory_mb + ), + ( + self.hypervisors[1].id, + self.hypervisors[1].hypervisor_hostname, + self.hypervisors[1].hypervisor_type, + self.hypervisors[1].host_ip, + self.hypervisors[1].state, + self.hypervisors[1].vcpus_used, + self.hypervisors[1].vcpus, + self.hypervisors[1].memory_mb_used, + self.hypervisors[1].memory_mb + ), + ) # Get the command object to test self.cmd = hypervisor.ListHypervisor(self.app, None) @@ -93,6 +137,9 @@ def test_hypervisor_list_matching_option_found(self): ( self.hypervisors[0].id, self.hypervisors[0].hypervisor_hostname, + self.hypervisors[1].hypervisor_type, + self.hypervisors[1].host_ip, + self.hypervisors[1].state, ), ) @@ -123,6 +170,24 @@ def test_hypervisor_list_matching_option_not_found(self): self.cmd.take_action, parsed_args) + def test_hypervisor_list_long_option(self): + arglist = [ + '--long', + ] + verifylist = [ + ('long', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + self.hypervisors_mock.list.assert_called_with() + self.assertEqual(self.columns_long, columns) + self.assertEqual(self.data_long, tuple(data)) + class TestHypervisorShow(TestHypervisor): diff --git a/releasenotes/notes/bug-1637074-1b0e409f30f715ca.yaml b/releasenotes/notes/bug-1637074-1b0e409f30f715ca.yaml new file mode 100644 index 0000000000..705542b1c7 --- /dev/null +++ b/releasenotes/notes/bug-1637074-1b0e409f30f715ca.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``--long`` option and more columns to the ``hypervisor list`` command. + [Bug `1637074 `_] From 960b2658dc6ab1f82b78dbc531386544d302f09e Mon Sep 17 00:00:00 2001 From: Yan Xing'an Date: Thu, 20 Oct 2016 20:56:32 -0700 Subject: [PATCH 1327/3095] Support --provider-* options in the network list command Add --provider-network-type, --provider-physical-network, --provider-segment options into network list command. Change-Id: I02546170211fb3e7e55d5dc7e7cdc6d387fd26e5 Closes-Bug: #1635580 Partially-Implements: blueprint network-commands-options --- doc/source/command-objects/network.rst | 43 ++++++++++++ openstackclient/network/v2/network.py | 32 ++++++++- .../tests/unit/network/v2/fakes.py | 2 + .../tests/unit/network/v2/test_network.py | 68 +++++++++++++++++-- .../notes/bug-1635580-54e0039b469ad5a6.yaml | 6 ++ 5 files changed, 146 insertions(+), 5 deletions(-) create mode 100644 releasenotes/notes/bug-1635580-54e0039b469ad5a6.yaml diff --git a/doc/source/command-objects/network.rst b/doc/source/command-objects/network.rst index d133674f9e..4b72d5e068 100644 --- a/doc/source/command-objects/network.rst +++ b/doc/source/command-objects/network.rst @@ -193,53 +193,96 @@ List networks [--project [--project-domain ]] [--share | --no-share] [--status ] + [--provider-network-type ] + [--provider-physical-network ] + [--provider-segment ] .. option:: --external List external networks + *Network version 2 only* + .. option:: --internal List internal networks + *Network version 2 only* + .. option:: --long List additional fields in output + *Network version 2 only* + .. option:: --name List networks according to their name + *Network version 2 only* + .. option:: --enable List enabled networks + *Network version 2 only* + .. option:: --disable List disabled networks + *Network version 2 only* + .. option:: --project List networks according to their project (name or ID) + *Network version 2 only* + .. option:: --project-domain Domain the project belongs to (name or ID). This can be used in case collisions between project names exist. + *Network version 2 only* + .. option:: --share List networks shared between projects + *Network version 2 only* + .. option:: --no-share List networks not shared between projects + *Network version 2 only* + .. option:: --status List networks according to their status ('ACTIVE', 'BUILD', 'DOWN', 'ERROR') +.. option:: --provider-network-type + + List networks according to their physical mechanisms. + The supported options are: flat, geneve, gre, local, vlan, vxlan. + + *Network version 2 only* + +.. option:: --provider-physical-network + + List networks according to name of the physical network + + *Network version 2 only* + +.. option:: --provider-segment + + List networks according to VLAN ID for VLAN networks + or Tunnel ID for GENEVE/GRE/VXLAN networks + + *Network version 2 only* + network set ----------- diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 40183b73fb..397139e20a 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -304,7 +304,7 @@ def take_action_compute(self, client, parsed_args): class ListNetwork(common.NetworkAndComputeLister): """List networks""" - def update_parser_common(self, parser): + def update_parser_network(self, parser): router_ext_group = parser.add_mutually_exclusive_group() router_ext_group.add_argument( '--external', @@ -361,6 +361,29 @@ def update_parser_common(self, parser): help=_("List networks according to their status " "('ACTIVE', 'BUILD', 'DOWN', 'ERROR')") ) + parser.add_argument( + '--provider-network-type', + metavar='', + choices=['flat', 'geneve', 'gre', 'local', + 'vlan', 'vxlan'], + help=_("List networks according to their physical mechanisms. " + "The supported options are: flat, geneve, gre, local, " + "vlan, vxlan.") + ) + parser.add_argument( + '--provider-physical-network', + metavar='', + dest='physical_network', + help=_("List networks according to name of the physical network") + ) + parser.add_argument( + '--provider-segment', + metavar='', + dest='segmentation_id', + help=_("List networks according to VLAN ID for VLAN networks " + "or Tunnel ID for GENEVE/GRE/VXLAN networks") + ) + return parser def take_action_network(self, client, parsed_args): @@ -433,6 +456,13 @@ def take_action_network(self, client, parsed_args): if parsed_args.status: args['status'] = parsed_args.status + if parsed_args.provider_network_type: + args['provider:network_type'] = parsed_args.provider_network_type + if parsed_args.physical_network: + args['provider:physical_network'] = parsed_args.physical_network + if parsed_args.segmentation_id: + args['provider:segmentation_id'] = parsed_args.segmentation_id + data = client.networks(**args) return (column_headers, diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index 94727ae3ca..02ef037e03 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -291,6 +291,8 @@ def create_one_network(attrs=None): 'shared': False, 'subnets': ['a', 'b'], 'provider_network_type': 'vlan', + 'provider_physical_network': 'physnet1', + 'provider_segmentation_id': "400", 'router:external': True, 'availability_zones': [], 'availability_zone_hints': [], diff --git a/openstackclient/tests/unit/network/v2/test_network.py b/openstackclient/tests/unit/network/v2/test_network.py index 828da4a26f..c0de864049 100644 --- a/openstackclient/tests/unit/network/v2/test_network.py +++ b/openstackclient/tests/unit/network/v2/test_network.py @@ -65,6 +65,8 @@ class TestCreateNetworkIdentityV3(TestNetwork): 'port_security_enabled', 'project_id', 'provider_network_type', + 'provider_physical_network', + 'provider_segmentation_id', 'router:external', 'shared', 'status', @@ -82,6 +84,8 @@ class TestCreateNetworkIdentityV3(TestNetwork): _network.is_port_security_enabled, _network.project_id, _network.provider_network_type, + _network.provider_physical_network, + _network.provider_segmentation_id, network._format_router_external(_network.is_router_external), _network.shared, _network.status, @@ -229,6 +233,8 @@ class TestCreateNetworkIdentityV2(TestNetwork): 'port_security_enabled', 'project_id', 'provider_network_type', + 'provider_physical_network', + 'provider_segmentation_id', 'router:external', 'shared', 'status', @@ -246,6 +252,8 @@ class TestCreateNetworkIdentityV2(TestNetwork): _network.is_port_security_enabled, _network.project_id, _network.provider_network_type, + _network.provider_physical_network, + _network.provider_segmentation_id, network._format_router_external(_network.is_router_external), _network.shared, _network.status, @@ -681,6 +689,57 @@ def test_network_list_status(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + def test_network_list_provider_network_type(self): + network_type = self._network[0].provider_network_type + arglist = [ + '--provider-network-type', network_type, + ] + verifylist = [ + ('provider_network_type', network_type), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.networks.assert_called_once_with( + **{'provider:network_type': network_type} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_network_list_provider_physical_network(self): + physical_network = self._network[0].provider_physical_network + arglist = [ + '--provider-physical-network', physical_network, + ] + verifylist = [ + ('physical_network', physical_network), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.networks.assert_called_once_with( + **{'provider:physical_network': physical_network} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_network_list_provider_segment(self): + segmentation_id = self._network[0].provider_segmentation_id + arglist = [ + '--provider-segment', segmentation_id, + ] + verifylist = [ + ('segmentation_id', segmentation_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.networks.assert_called_once_with( + **{'provider:segmentation_id': segmentation_id} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + class TestSetNetwork(TestNetwork): @@ -805,6 +864,8 @@ class TestShowNetwork(TestNetwork): 'port_security_enabled', 'project_id', 'provider_network_type', + 'provider_physical_network', + 'provider_segmentation_id', 'router:external', 'shared', 'status', @@ -822,6 +883,8 @@ class TestShowNetwork(TestNetwork): _network.is_port_security_enabled, _network.project_id, _network.provider_network_type, + _network.provider_physical_network, + _network.provider_segmentation_id, network._format_router_external(_network.is_router_external), _network.shared, _network.status, @@ -1111,10 +1174,7 @@ def setUp(self): def test_network_list_no_options(self): arglist = [] - verifylist = [ - ('external', False), - ('long', False), - ] + verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) # In base command class Lister in cliff, abstract method take_action() diff --git a/releasenotes/notes/bug-1635580-54e0039b469ad5a6.yaml b/releasenotes/notes/bug-1635580-54e0039b469ad5a6.yaml new file mode 100644 index 0000000000..afedabcddc --- /dev/null +++ b/releasenotes/notes/bug-1635580-54e0039b469ad5a6.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--provider-network-type``, ``--provider-physical-network``, and + ``--provider-segment`` options to the ``network list`` command. + [Bug `1635580 `_] From 84beac59948629be9ea065386c5b5bc330d2c609 Mon Sep 17 00:00:00 2001 From: Yi Zhao Date: Mon, 31 Oct 2016 16:47:30 +0800 Subject: [PATCH 1328/3095] Add filtering options --name,--enable,--disable to router list Change-Id: I171b6be4501b02c3df66589c45177200919117db Closes-Bug: #1637945 Partially-Implements: blueprint network-commands-options --- doc/source/command-objects/router.rst | 14 +++++ openstackclient/network/v2/router.py | 29 ++++++++++- .../tests/unit/network/v2/test_router.py | 52 +++++++++++++++++++ .../notes/bug-1637945-f361c834381d409c.yaml | 5 ++ 4 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/bug-1637945-f361c834381d409c.yaml diff --git a/doc/source/command-objects/router.rst b/doc/source/command-objects/router.rst index 0eaae655d2..56b95ffa68 100644 --- a/doc/source/command-objects/router.rst +++ b/doc/source/command-objects/router.rst @@ -134,12 +134,26 @@ List routers .. code:: bash os router list + [--name ] + [--enable | --disable] [--long] .. option:: --long List additional fields in output +.. option:: --name + + List routers according to their name + +.. option:: --enable + + List enabled routers + +.. option:: --disable + + List disabled routers + router remove port ------------------ diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 193bf6e9bd..d96c314a9f 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -260,6 +260,22 @@ class ListRouter(command.Lister): def get_parser(self, prog_name): parser = super(ListRouter, self).get_parser(prog_name) + parser.add_argument( + '--name', + metavar='', + help=_("List routers according to their name") + ) + admin_state_group = parser.add_mutually_exclusive_group() + admin_state_group.add_argument( + '--enable', + action='store_true', + help=_("List enabled routers") + ) + admin_state_group.add_argument( + '--disable', + action='store_true', + help=_("List disabled routers") + ) parser.add_argument( '--long', action='store_true', @@ -289,6 +305,17 @@ def take_action(self, parsed_args): 'HA', 'Project', ) + + args = {} + + if parsed_args.name is not None: + args['name'] = parsed_args.name + + if parsed_args.enable: + args['admin_state_up'] = True + elif parsed_args.disable: + args['admin_state_up'] = False + if parsed_args.long: columns = columns + ( 'routes', @@ -308,7 +335,7 @@ def take_action(self, parsed_args): 'Availability zones', ) - data = client.routers() + data = client.routers(**args) return (column_headers, (utils.get_item_properties( s, columns, diff --git a/openstackclient/tests/unit/network/v2/test_router.py b/openstackclient/tests/unit/network/v2/test_router.py index bc448d137e..24984e47b0 100644 --- a/openstackclient/tests/unit/network/v2/test_router.py +++ b/openstackclient/tests/unit/network/v2/test_router.py @@ -427,6 +427,58 @@ def test_router_list_long_no_az(self): self.assertEqual(self.columns_long_no_az, columns) self.assertEqual(self.data_long_no_az, list(data)) + def test_list_name(self): + test_name = "fakename" + arglist = [ + '--name', test_name, + ] + verifylist = [ + ('long', False), + ('name', test_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.routers.assert_called_once_with( + **{'name': test_name} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_router_list_enable(self): + arglist = [ + '--enable', + ] + verifylist = [ + ('long', False), + ('enable', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.routers.assert_called_once_with( + **{'admin_state_up': True} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_router_list_disable(self): + arglist = [ + '--disable', + ] + verifylist = [ + ('long', False), + ('disable', True) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.routers.assert_called_once_with( + **{'admin_state_up': False} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + class TestRemovePortFromRouter(TestRouter): '''Remove port from a Router ''' diff --git a/releasenotes/notes/bug-1637945-f361c834381d409c.yaml b/releasenotes/notes/bug-1637945-f361c834381d409c.yaml new file mode 100644 index 0000000000..da61f48a85 --- /dev/null +++ b/releasenotes/notes/bug-1637945-f361c834381d409c.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``--name``, ``--enable``, ``--disable`` to ``os router list`` + [Bug `1637945 `_] From 970b0e00053f81a313c18cc9b50bf964aa86fc20 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 2 Nov 2016 15:40:40 +0000 Subject: [PATCH 1329/3095] Updated from global requirements Change-Id: I2cfff831e1ef85249bb6ec19637ac75f2ee9f899 --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 4006314dec..b7a04c4d00 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -8,7 +8,7 @@ fixtures>=3.0.0 # Apache-2.0/BSD mock>=2.0 # BSD oslosphinx>=4.7.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 -reno>=1.8.0 # Apache2 +reno>=1.8.0 # Apache-2.0 requests>=2.10.0 # Apache-2.0 requests-mock>=1.1 # Apache-2.0 sphinx!=1.3b1,<1.4,>=1.2.1 # BSD From 23ee2fd8f060ed312a84eb03c99494e8af2ffb6f Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Wed, 14 Sep 2016 14:54:29 +0800 Subject: [PATCH 1330/3095] Refactor "snapshot" commands 1.change the command name ``snapshot create/delete/list/ show/set/unset`` to ``volume snapshot create/delete/list/ show/set/unset``. 2.change the optional parameter "--name " to a positional parameter ""; Change the positional parameter "" to a optional parameter "--volume " Change-Id: If03276ecdf6f0d96893d5ecf91c2aaa64929cff3 Implements: bp backup-snapshot-renamed-for-volume-resource Co-Authored-By: Sheel Rana --- doc/source/command-objects/snapshot.rst | 6 + .../command-objects/volume-snapshot.rst | 171 +++++++++ doc/source/commands.rst | 3 +- .../functional/volume/v1/test_snapshot.py | 32 +- .../functional/volume/v2/test_snapshot.py | 32 +- .../tests/functional/volume/v2/test_volume.py | 10 +- .../tests/unit/volume/v1/test_snapshot.py | 50 ++- .../tests/unit/volume/v2/test_snapshot.py | 51 ++- openstackclient/volume/v1/snapshot.py | 18 + openstackclient/volume/v1/volume_snapshot.py | 305 ++++++++++++++++ openstackclient/volume/v2/snapshot.py | 18 + openstackclient/volume/v2/volume_snapshot.py | 338 ++++++++++++++++++ ...me-snapshot-commands-e0937f7143a4ef55.yaml | 8 + setup.cfg | 14 + 14 files changed, 996 insertions(+), 60 deletions(-) create mode 100644 doc/source/command-objects/volume-snapshot.rst create mode 100644 openstackclient/volume/v1/volume_snapshot.py create mode 100644 openstackclient/volume/v2/volume_snapshot.py create mode 100644 releasenotes/notes/rename-snapshot-commands-e0937f7143a4ef55.yaml diff --git a/doc/source/command-objects/snapshot.rst b/doc/source/command-objects/snapshot.rst index a2709adbe5..e75693ca18 100644 --- a/doc/source/command-objects/snapshot.rst +++ b/doc/source/command-objects/snapshot.rst @@ -8,6 +8,7 @@ snapshot create --------------- Create new snapshot +(Deprecated, please use ``volume snapshot create`` instead) .. program:: snapshot create .. code:: bash @@ -46,6 +47,7 @@ snapshot delete --------------- Delete snapshot(s) +(Deprecated, please use ``volume snapshot delete`` instead) .. program:: snapshot delete .. code:: bash @@ -62,6 +64,7 @@ snapshot list ------------- List snapshots +(Deprecated, please use ``volume snapshot list`` instead) .. program:: snapshot list .. code:: bash @@ -96,6 +99,7 @@ snapshot set ------------ Set snapshot properties +(Deprecated, please use ``volume snapshot set`` instead) .. program:: snapshot set .. code:: bash @@ -137,6 +141,7 @@ snapshot show ------------- Display snapshot details +(Deprecated, please use ``volume snapshot show`` instead) .. program:: snapshot show .. code:: bash @@ -153,6 +158,7 @@ snapshot unset -------------- Unset snapshot properties +(Deprecated, please use ``volume snapshot unset`` instead) .. program:: snapshot unset .. code:: bash diff --git a/doc/source/command-objects/volume-snapshot.rst b/doc/source/command-objects/volume-snapshot.rst new file mode 100644 index 0000000000..b84601f464 --- /dev/null +++ b/doc/source/command-objects/volume-snapshot.rst @@ -0,0 +1,171 @@ +=============== +volume snapshot +=============== + +Block Storage v1, v2 + +volume snapshot create +---------------------- + +Create new volume snapshot + +.. program:: volume snapshot create +.. code:: bash + + os volume snapshot create + [--volume ] + [--description ] + [--force] + [--property [...] ] + + +.. option:: --volume + + Volume to snapshot (name or ID) (default is ) + +.. option:: --description + + Description of the snapshot + +.. option:: --force + + Create a snapshot attached to an instance. Default is False + +.. option:: --property + + Set a property to this snapshot (repeat option to set multiple properties) + + *Volume version 2 only* + +.. _volume_snapshot_create-snapshot-name: +.. describe:: + + Name of the new snapshot (default to None) + +volume snapshot delete +---------------------- + +Delete volume snapshot(s) + +.. program:: volume snapshot delete +.. code:: bash + + os volume snapshot delete + [ ...] + +.. _volume_snapshot_delete-snapshot: +.. describe:: + + Snapshot(s) to delete (name or ID) + +volume snapshot list +-------------------- + +List volume snapshots + +.. program:: volume snapshot list +.. code:: bash + + os volume snapshot list + [--all-projects] + [--long] + [--limit ] + [--marker ] + +.. option:: --all-projects + + Include all projects (admin only) + +.. option:: --long + + List additional fields in output + +.. option:: --limit + + Maximum number of snapshots to display + + *Volume version 2 only* + +.. option:: --marker + + The last snapshot ID of the previous page + + *Volume version 2 only* + +volume snapshot set +------------------- + +Set volume snapshot properties + +.. program:: volume snapshot set +.. code:: bash + + os volume snapshot set + [--name ] + [--description ] + [--property [...] ] + [--state ] + + +.. option:: --name + + New snapshot name + +.. option:: --description + + New snapshot description + +.. option:: --property + + Property to add or modify for this snapshot (repeat option to set multiple properties) + +.. option:: --state + + New snapshot state. + ("available", "error", "creating", "deleting", or "error_deleting") (admin only) + (This option simply changes the state of the snapshot in the database with + no regard to actual status, exercise caution when using) + + *Volume version 2 only* + +.. _volume_snapshot_set-snapshot: +.. describe:: + + Snapshot to modify (name or ID) + +volume snapshot show +-------------------- + +Display volume snapshot details + +.. program:: volume snapshot show +.. code:: bash + + os volume snapshot show + + +.. _volume_snapshot_show-snapshot: +.. describe:: + + Snapshot to display (name or ID) + +volume snapshot unset +--------------------- + +Unset volume snapshot properties + +.. program:: volume snapshot unset +.. code:: bash + + os volume snapshot unset + [--property ] + + +.. option:: --property + + Property to remove from snapshot (repeat option to remove multiple properties) + +.. _volume_snapshot_unset-snapshot: +.. describe:: + + Snapshot to modify (name or ID) diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 3e0f932eeb..36c48b0e2b 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -74,7 +74,6 @@ referring to both Compute and Volume quotas. * ``address scope``: (**Network**) a scope of IPv4 or IPv6 addresses * ``aggregate``: (**Compute**) a grouping of compute hosts * ``availability zone``: (**Compute**, **Network**, **Volume**) a logical partition of hosts or block storage or network services -* ``backup``: (**Volume**) a volume copy * ``catalog``: (**Identity**) service catalog * ``command``: (**Internal**) installed commands in the OSC process * ``compute agent``: (**Compute**) a cloud Compute agent available to a hypervisor @@ -134,7 +133,6 @@ referring to both Compute and Volume quotas. * ``server image``: (**Compute**) saved server disk image * ``service``: (**Identity**) a cloud service * ``service provider``: (**Identity**) a resource that consumes assertions from an ``identity provider`` -* ``snapshot``: (**Volume**) a point-in-time copy of a volume * ``subnet``: (**Network**) - a contiguous range of IP addresses assigned to a network * ``subnet pool``: (**Network**) - a pool of subnets * ``token``: (**Identity**) a bearer token managed by Identity service @@ -145,6 +143,7 @@ referring to both Compute and Volume quotas. * ``volume``: (**Volume**) block volumes * ``volume backup``: (**Volume**) backup for volumes * ``volume qos``: (**Volume**) quality-of-service (QoS) specification for volumes +* ``volume snapshot``: (**Volume**) a point-in-time copy of a volume * ``volume type``: (**Volume**) deployment-specific types of volumes available * ``volume service``: (**Volume**) services to manage block storage operations * ``volume transfer request``: (**Volume**) volume owner transfer request diff --git a/openstackclient/tests/functional/volume/v1/test_snapshot.py b/openstackclient/tests/functional/volume/v1/test_snapshot.py index c6d65ccc60..1e1c6b2144 100644 --- a/openstackclient/tests/functional/volume/v1/test_snapshot.py +++ b/openstackclient/tests/functional/volume/v1/test_snapshot.py @@ -16,8 +16,8 @@ from openstackclient.tests.functional.volume.v1 import common -class SnapshotTests(common.BaseVolumeTests): - """Functional tests for snapshot. """ +class VolumeSnapshotTests(common.BaseVolumeTests): + """Functional tests for volume snapshot. """ VOLLY = uuid.uuid4().hex NAME = uuid.uuid4().hex @@ -36,24 +36,25 @@ def wait_for_status(cls, command, status, tries): @classmethod def setUpClass(cls): - super(SnapshotTests, cls).setUpClass() + super(VolumeSnapshotTests, cls).setUpClass() cls.openstack('volume create --size 1 ' + cls.VOLLY) cls.wait_for_status('volume show ' + cls.VOLLY, 'available\n', 3) opts = cls.get_opts(['status']) - raw_output = cls.openstack('snapshot create --name ' + cls.NAME + - ' ' + cls.VOLLY + opts) + raw_output = cls.openstack('volume snapshot create --volume ' + + cls.VOLLY + ' ' + cls.NAME + opts) cls.assertOutput('creating\n', raw_output) - cls.wait_for_status('snapshot show ' + cls.NAME, 'available\n', 3) + cls.wait_for_status( + 'volume snapshot show ' + cls.NAME, 'available\n', 3) @classmethod def tearDownClass(cls): # Rename test raw_output = cls.openstack( - 'snapshot set --name ' + cls.OTHER_NAME + ' ' + cls.NAME) + 'volume snapshot set --name ' + cls.OTHER_NAME + ' ' + cls.NAME) cls.assertOutput('', raw_output) # Delete test raw_output_snapshot = cls.openstack( - 'snapshot delete ' + cls.OTHER_NAME) + 'volume snapshot delete ' + cls.OTHER_NAME) cls.wait_for_status('volume show ' + cls.VOLLY, 'available\n', 6) raw_output_volume = cls.openstack('volume delete --force ' + cls.VOLLY) cls.assertOutput('', raw_output_snapshot) @@ -61,26 +62,27 @@ def tearDownClass(cls): def test_snapshot_list(self): opts = self.get_opts(self.HEADERS) - raw_output = self.openstack('snapshot list' + opts) + raw_output = self.openstack('volume snapshot list' + opts) self.assertIn(self.NAME, raw_output) def test_snapshot_set_unset_properties(self): raw_output = self.openstack( - 'snapshot set --property a=b --property c=d ' + self.NAME) + 'volume snapshot set --property a=b --property c=d ' + self.NAME) self.assertEqual("", raw_output) opts = self.get_opts(["properties"]) - raw_output = self.openstack('snapshot show ' + self.NAME + opts) + raw_output = self.openstack('volume snapshot show ' + self.NAME + opts) self.assertEqual("a='b', c='d'\n", raw_output) - raw_output = self.openstack('snapshot unset --property a ' + self.NAME) + raw_output = self.openstack( + 'volume snapshot unset --property a ' + self.NAME) self.assertEqual("", raw_output) - raw_output = self.openstack('snapshot show ' + self.NAME + opts) + raw_output = self.openstack('volume snapshot show ' + self.NAME + opts) self.assertEqual("c='d'\n", raw_output) def test_snapshot_set_description(self): raw_output = self.openstack( - 'snapshot set --description backup ' + self.NAME) + 'volume snapshot set --description backup ' + self.NAME) self.assertEqual("", raw_output) opts = self.get_opts(["display_description", "display_name"]) - raw_output = self.openstack('snapshot show ' + self.NAME + opts) + raw_output = self.openstack('volume snapshot show ' + self.NAME + opts) self.assertEqual("backup\n" + self.NAME + "\n", raw_output) diff --git a/openstackclient/tests/functional/volume/v2/test_snapshot.py b/openstackclient/tests/functional/volume/v2/test_snapshot.py index fcbc31cbec..4eb69e9d7f 100644 --- a/openstackclient/tests/functional/volume/v2/test_snapshot.py +++ b/openstackclient/tests/functional/volume/v2/test_snapshot.py @@ -16,8 +16,8 @@ from openstackclient.tests.functional.volume.v2 import common -class SnapshotTests(common.BaseVolumeTests): - """Functional tests for snapshot. """ +class VolumeSnapshotTests(common.BaseVolumeTests): + """Functional tests for volume snapshot. """ VOLLY = uuid.uuid4().hex NAME = uuid.uuid4().hex @@ -36,24 +36,25 @@ def wait_for_status(cls, command, status, tries): @classmethod def setUpClass(cls): - super(SnapshotTests, cls).setUpClass() + super(VolumeSnapshotTests, cls).setUpClass() cls.openstack('volume create --size 1 ' + cls.VOLLY) cls.wait_for_status('volume show ' + cls.VOLLY, 'available\n', 3) opts = cls.get_opts(['status']) - raw_output = cls.openstack('snapshot create --name ' + cls.NAME + - ' ' + cls.VOLLY + opts) + raw_output = cls.openstack('volume snapshot create --volume ' + + cls.VOLLY + ' ' + cls.NAME + opts) cls.assertOutput('creating\n', raw_output) - cls.wait_for_status('snapshot show ' + cls.NAME, 'available\n', 3) + cls.wait_for_status( + 'volume snapshot show ' + cls.NAME, 'available\n', 3) @classmethod def tearDownClass(cls): # Rename test raw_output = cls.openstack( - 'snapshot set --name ' + cls.OTHER_NAME + ' ' + cls.NAME) + 'volume snapshot set --name ' + cls.OTHER_NAME + ' ' + cls.NAME) cls.assertOutput('', raw_output) # Delete test raw_output_snapshot = cls.openstack( - 'snapshot delete ' + cls.OTHER_NAME) + 'volume snapshot delete ' + cls.OTHER_NAME) cls.wait_for_status('volume show ' + cls.VOLLY, 'available\n', 6) raw_output_volume = cls.openstack('volume delete --force ' + cls.VOLLY) cls.assertOutput('', raw_output_snapshot) @@ -61,26 +62,27 @@ def tearDownClass(cls): def test_snapshot_list(self): opts = self.get_opts(self.HEADERS) - raw_output = self.openstack('snapshot list' + opts) + raw_output = self.openstack('volume snapshot list' + opts) self.assertIn(self.NAME, raw_output) def test_snapshot_properties(self): raw_output = self.openstack( - 'snapshot set --property a=b --property c=d ' + self.NAME) + 'volume snapshot set --property a=b --property c=d ' + self.NAME) self.assertEqual("", raw_output) opts = self.get_opts(["properties"]) - raw_output = self.openstack('snapshot show ' + self.NAME + opts) + raw_output = self.openstack('volume snapshot show ' + self.NAME + opts) self.assertEqual("a='b', c='d'\n", raw_output) - raw_output = self.openstack('snapshot unset --property a ' + self.NAME) + raw_output = self.openstack( + 'volume snapshot unset --property a ' + self.NAME) self.assertEqual("", raw_output) - raw_output = self.openstack('snapshot show ' + self.NAME + opts) + raw_output = self.openstack('volume snapshot show ' + self.NAME + opts) self.assertEqual("c='d'\n", raw_output) def test_snapshot_set(self): raw_output = self.openstack( - 'snapshot set --description backup ' + self.NAME) + 'volume snapshot set --description backup ' + self.NAME) self.assertEqual("", raw_output) opts = self.get_opts(["description", "name"]) - raw_output = self.openstack('snapshot show ' + self.NAME + opts) + raw_output = self.openstack('volume snapshot show ' + self.NAME + opts) self.assertEqual("backup\n" + self.NAME + "\n", raw_output) diff --git a/openstackclient/tests/functional/volume/v2/test_volume.py b/openstackclient/tests/functional/volume/v2/test_volume.py index fb880578c9..ea891cba55 100644 --- a/openstackclient/tests/functional/volume/v2/test_volume.py +++ b/openstackclient/tests/functional/volume/v2/test_volume.py @@ -106,11 +106,12 @@ def test_volume_snapshot(self): opts = self.get_opts(self.FIELDS) # Create snapshot from test volume - raw_output = self.openstack('snapshot create ' + self.NAME + - ' --name ' + self.SNAPSHOT_NAME + opts) + raw_output = self.openstack('volume snapshot create ' + + self.SNAPSHOT_NAME + + ' --volume ' + self.NAME + opts) expected = self.SNAPSHOT_NAME + '\n' self.assertOutput(expected, raw_output) - self.wait_for("snapshot", self.SNAPSHOT_NAME, "available") + self.wait_for("volume snapshot", self.SNAPSHOT_NAME, "available") # Create volume from snapshot raw_output = self.openstack('volume create --size 2 --snapshot ' + @@ -126,7 +127,8 @@ def test_volume_snapshot(self): self.assertOutput('', raw_output) # Delete test snapshot - raw_output = self.openstack('snapshot delete ' + self.SNAPSHOT_NAME) + raw_output = self.openstack( + 'volume snapshot delete ' + self.SNAPSHOT_NAME) self.assertOutput('', raw_output) self.wait_for("volume", self.NAME, "available") diff --git a/openstackclient/tests/unit/volume/v1/test_snapshot.py b/openstackclient/tests/unit/volume/v1/test_snapshot.py index edfbdc190c..8e30d6a9d5 100644 --- a/openstackclient/tests/unit/volume/v1/test_snapshot.py +++ b/openstackclient/tests/unit/volume/v1/test_snapshot.py @@ -19,7 +19,7 @@ from osc_lib import utils from openstackclient.tests.unit.volume.v1 import fakes as volume_fakes -from openstackclient.volume.v1 import snapshot +from openstackclient.volume.v1 import volume_snapshot class TestSnapshot(volume_fakes.TestVolumev1): @@ -67,20 +67,20 @@ def setUp(self): self.volumes_mock.get.return_value = self.volume self.snapshots_mock.create.return_value = self.new_snapshot # Get the command object to test - self.cmd = snapshot.CreateSnapshot(self.app, None) + self.cmd = volume_snapshot.CreateVolumeSnapshot(self.app, None) def test_snapshot_create(self): arglist = [ - "--name", self.new_snapshot.display_name, + "--volume", self.new_snapshot.volume_id, "--description", self.new_snapshot.display_description, "--force", - self.new_snapshot.volume_id, + self.new_snapshot.display_name, ] verifylist = [ - ("name", self.new_snapshot.display_name), + ("volume", self.new_snapshot.volume_id), ("description", self.new_snapshot.display_description), ("force", True), - ("volume", self.new_snapshot.volume_id), + ("snapshot_name", self.new_snapshot.display_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -97,7 +97,7 @@ def test_snapshot_create(self): def test_snapshot_create_without_name(self): arglist = [ - self.new_snapshot.volume_id, + "--volume", self.new_snapshot.volume_id, "--description", self.new_snapshot.display_description, "--force" ] @@ -119,6 +119,32 @@ def test_snapshot_create_without_name(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_snapshot_create_without_volume(self): + arglist = [ + "--description", self.new_snapshot.display_description, + "--force", + self.new_snapshot.display_name + ] + verifylist = [ + ("description", self.new_snapshot.display_description), + ("force", True), + ("snapshot_name", self.new_snapshot.display_name) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volumes_mock.get.assert_called_once_with( + self.new_snapshot.display_name) + self.snapshots_mock.create.assert_called_once_with( + self.new_snapshot.volume_id, + True, + self.new_snapshot.display_name, + self.new_snapshot.display_description, + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + class TestSnapshotDelete(TestSnapshot): @@ -132,7 +158,7 @@ def setUp(self): self.snapshots_mock.delete.return_value = None # Get the command object to mock - self.cmd = snapshot.DeleteSnapshot(self.app, None) + self.cmd = volume_snapshot.DeleteVolumeSnapshot(self.app, None) def test_snapshot_delete(self): arglist = [ @@ -244,7 +270,7 @@ def setUp(self): self.volumes_mock.list.return_value = [self.volume] self.snapshots_mock.list.return_value = self.snapshots # Get the command to test - self.cmd = snapshot.ListSnapshot(self.app, None) + self.cmd = volume_snapshot.ListVolumeSnapshot(self.app, None) def test_snapshot_list_without_options(self): arglist = [] @@ -307,7 +333,7 @@ def setUp(self): self.snapshots_mock.get.return_value = self.snapshot self.snapshots_mock.set_metadata.return_value = None # Get the command object to mock - self.cmd = snapshot.SetSnapshot(self.app, None) + self.cmd = volume_snapshot.SetVolumeSnapshot(self.app, None) def test_snapshot_set_all(self): arglist = [ @@ -404,7 +430,7 @@ def setUp(self): self.snapshots_mock.get.return_value = self.snapshot # Get the command object to test - self.cmd = snapshot.ShowSnapshot(self.app, None) + self.cmd = volume_snapshot.ShowVolumeSnapshot(self.app, None) def test_snapshot_show(self): arglist = [ @@ -432,7 +458,7 @@ def setUp(self): self.snapshots_mock.get.return_value = self.snapshot self.snapshots_mock.delete_metadata.return_value = None # Get the command object to mock - self.cmd = snapshot.UnsetSnapshot(self.app, None) + self.cmd = volume_snapshot.UnsetVolumeSnapshot(self.app, None) def test_snapshot_unset(self): arglist = [ diff --git a/openstackclient/tests/unit/volume/v2/test_snapshot.py b/openstackclient/tests/unit/volume/v2/test_snapshot.py index d355662d8b..b67dd6ebf5 100644 --- a/openstackclient/tests/unit/volume/v2/test_snapshot.py +++ b/openstackclient/tests/unit/volume/v2/test_snapshot.py @@ -20,7 +20,7 @@ from osc_lib import utils from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes -from openstackclient.volume.v2 import snapshot +from openstackclient.volume.v2 import volume_snapshot class TestSnapshot(volume_fakes.TestVolume): @@ -68,23 +68,23 @@ def setUp(self): self.volumes_mock.get.return_value = self.volume self.snapshots_mock.create.return_value = self.new_snapshot # Get the command object to test - self.cmd = snapshot.CreateSnapshot(self.app, None) + self.cmd = volume_snapshot.CreateVolumeSnapshot(self.app, None) def test_snapshot_create(self): arglist = [ - "--name", self.new_snapshot.name, + "--volume", self.new_snapshot.volume_id, "--description", self.new_snapshot.description, "--force", '--property', 'Alpha=a', '--property', 'Beta=b', - self.new_snapshot.volume_id, + self.new_snapshot.name, ] verifylist = [ - ("name", self.new_snapshot.name), + ("volume", self.new_snapshot.volume_id), ("description", self.new_snapshot.description), ("force", True), ('property', {'Alpha': 'a', 'Beta': 'b'}), - ("volume", self.new_snapshot.volume_id), + ("snapshot_name", self.new_snapshot.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -102,7 +102,7 @@ def test_snapshot_create(self): def test_snapshot_create_without_name(self): arglist = [ - self.new_snapshot.volume_id, + "--volume", self.new_snapshot.volume_id, "--description", self.new_snapshot.description, "--force" ] @@ -125,6 +125,33 @@ def test_snapshot_create_without_name(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_snapshot_create_without_volume(self): + arglist = [ + "--description", self.new_snapshot.description, + "--force", + self.new_snapshot.name + ] + verifylist = [ + ("description", self.new_snapshot.description), + ("force", True), + ("snapshot_name", self.new_snapshot.name) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volumes_mock.get.assert_called_once_with( + self.new_snapshot.name) + self.snapshots_mock.create.assert_called_once_with( + self.new_snapshot.volume_id, + force=True, + name=self.new_snapshot.name, + description=self.new_snapshot.description, + metadata=None, + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + class TestSnapshotDelete(TestSnapshot): @@ -138,7 +165,7 @@ def setUp(self): self.snapshots_mock.delete.return_value = None # Get the command object to mock - self.cmd = snapshot.DeleteSnapshot(self.app, None) + self.cmd = volume_snapshot.DeleteVolumeSnapshot(self.app, None) def test_snapshot_delete(self): arglist = [ @@ -250,7 +277,7 @@ def setUp(self): self.volumes_mock.list.return_value = [self.volume] self.snapshots_mock.list.return_value = self.snapshots # Get the command to test - self.cmd = snapshot.ListSnapshot(self.app, None) + self.cmd = volume_snapshot.ListVolumeSnapshot(self.app, None) def test_snapshot_list_without_options(self): arglist = [] @@ -330,7 +357,7 @@ def setUp(self): self.snapshots_mock.set_metadata.return_value = None self.snapshots_mock.update.return_value = None # Get the command object to mock - self.cmd = snapshot.SetSnapshot(self.app, None) + self.cmd = volume_snapshot.SetVolumeSnapshot(self.app, None) def test_snapshot_set(self): arglist = [ @@ -457,7 +484,7 @@ def setUp(self): self.snapshots_mock.get.return_value = self.snapshot # Get the command object to test - self.cmd = snapshot.ShowSnapshot(self.app, None) + self.cmd = volume_snapshot.ShowVolumeSnapshot(self.app, None) def test_snapshot_show(self): arglist = [ @@ -485,7 +512,7 @@ def setUp(self): self.snapshots_mock.get.return_value = self.snapshot self.snapshots_mock.delete_metadata.return_value = None # Get the command object to mock - self.cmd = snapshot.UnsetSnapshot(self.app, None) + self.cmd = volume_snapshot.UnsetVolumeSnapshot(self.app, None) def test_snapshot_unset(self): arglist = [ diff --git a/openstackclient/volume/v1/snapshot.py b/openstackclient/volume/v1/snapshot.py index bc92c0f56d..1c0c0bc7c5 100644 --- a/openstackclient/volume/v1/snapshot.py +++ b/openstackclient/volume/v1/snapshot.py @@ -13,6 +13,10 @@ # under the License. # +# TODO(Huanxuan Ao): Remove this file and "snapshot create", "snapshot delete", +# "snapshot set", "snapshot show" and "snapshot unset" +# commands two cycles after Ocata. + """Volume v1 Snapshot action implementations""" import copy @@ -27,6 +31,8 @@ from openstackclient.i18n import _ +deprecated = True +LOG_DEP = logging.getLogger('deprecated') LOG = logging.getLogger(__name__) @@ -61,6 +67,8 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): + LOG_DEP.warning(_('This command has been deprecated. ' + 'Please use "volume snapshot create" instead.')) volume_client = self.app.client_manager.volume volume_id = utils.find_resource(volume_client.volumes, parsed_args.volume).id @@ -92,6 +100,8 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): + LOG_DEP.warning(_('This command has been deprecated. ' + 'Please use "volume snapshot delete" instead.')) volume_client = self.app.client_manager.volume result = 0 @@ -133,6 +143,8 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): + LOG_DEP.warning(_('This command has been deprecated. ' + 'Please use "volume snapshot list" instead.')) def _format_volume_id(volume_id): """Return a volume name if available @@ -214,6 +226,8 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): + LOG_DEP.warning(_('This command has been deprecated. ' + 'Please use "volume snapshot set" instead.')) volume_client = self.app.client_manager.volume snapshot = utils.find_resource(volume_client.volume_snapshots, parsed_args.snapshot) @@ -258,6 +272,8 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): + LOG_DEP.warning(_('This command has been deprecated. ' + 'Please use "volume snapshot show" instead.')) volume_client = self.app.client_manager.volume snapshot = utils.find_resource(volume_client.volume_snapshots, parsed_args.snapshot) @@ -289,6 +305,8 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): + LOG_DEP.warning(_('This command has been deprecated. ' + 'Please use "volume snapshot unset" instead.')) volume_client = self.app.client_manager.volume snapshot = utils.find_resource( volume_client.volume_snapshots, parsed_args.snapshot) diff --git a/openstackclient/volume/v1/volume_snapshot.py b/openstackclient/volume/v1/volume_snapshot.py new file mode 100644 index 0000000000..c2ecf75b5b --- /dev/null +++ b/openstackclient/volume/v1/volume_snapshot.py @@ -0,0 +1,305 @@ +# Copyright 2012-2013 OpenStack Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +"""Volume v1 Snapshot action implementations""" + +import copy +import logging + +from osc_lib.cli import parseractions +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 CreateVolumeSnapshot(command.ShowOne): + """Create new volume snapshot""" + + def get_parser(self, prog_name): + parser = super(CreateVolumeSnapshot, self).get_parser(prog_name) + parser.add_argument( + 'snapshot_name', + metavar='', + nargs="?", + help=_('Name of the snapshot (default to None)'), + ) + parser.add_argument( + '--volume', + metavar='', + help=_('Volume to snapshot (name or ID) ' + '(default is )'), + ) + parser.add_argument( + '--description', + metavar='', + help=_('Description of the snapshot'), + ) + parser.add_argument( + '--force', + dest='force', + action='store_true', + default=False, + help=_('Create a snapshot attached to an instance. ' + 'Default is False'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + volume = parsed_args.volume + if not parsed_args.volume: + volume = parsed_args.snapshot_name + volume_id = utils.find_resource(volume_client.volumes, + volume).id + snapshot = volume_client.volume_snapshots.create( + volume_id, + parsed_args.force, + parsed_args.snapshot_name, + parsed_args.description + ) + + snapshot._info.update( + {'properties': utils.format_dict(snapshot._info.pop('metadata'))} + ) + + return zip(*sorted(six.iteritems(snapshot._info))) + + +class DeleteVolumeSnapshot(command.Command): + """Delete volume snapshot(s)""" + + def get_parser(self, prog_name): + parser = super(DeleteVolumeSnapshot, self).get_parser(prog_name) + parser.add_argument( + 'snapshots', + metavar='', + nargs="+", + help=_('Snapshot(s) to delete (name or ID)'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + result = 0 + + for i in parsed_args.snapshots: + try: + snapshot_id = utils.find_resource( + volume_client.volume_snapshots, i).id + volume_client.volume_snapshots.delete(snapshot_id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete snapshot with " + "name or ID '%(snapshot)s': %(e)s"), + {'snapshot': i, 'e': e}) + + if result > 0: + total = len(parsed_args.snapshots) + msg = (_("%(result)s of %(total)s snapshots failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) + + +class ListVolumeSnapshot(command.Lister): + """List volume snapshots""" + + def get_parser(self, prog_name): + parser = super(ListVolumeSnapshot, self).get_parser(prog_name) + parser.add_argument( + '--all-projects', + action='store_true', + default=False, + help=_('Include all projects (admin only)'), + ) + parser.add_argument( + '--long', + action='store_true', + default=False, + help=_('List additional fields in output'), + ) + return parser + + def take_action(self, parsed_args): + + def _format_volume_id(volume_id): + """Return a volume name if available + + :param volume_id: a volume ID + :rtype: either the volume ID or name + """ + + volume = volume_id + if volume_id in volume_cache.keys(): + volume = volume_cache[volume_id].display_name + return volume + + if parsed_args.long: + columns = ['ID', 'Display Name', 'Display Description', 'Status', + 'Size', 'Created At', 'Volume ID', 'Metadata'] + column_headers = copy.deepcopy(columns) + column_headers[6] = 'Volume' + column_headers[7] = 'Properties' + else: + columns = ['ID', 'Display Name', 'Display Description', 'Status', + 'Size'] + column_headers = copy.deepcopy(columns) + + # Always update Name and Description + column_headers[1] = 'Name' + column_headers[2] = 'Description' + + # Cache the volume list + volume_cache = {} + try: + for s in self.app.client_manager.volume.volumes.list(): + volume_cache[s.id] = s + except Exception: + # Just forget it if there's any trouble + pass + + search_opts = { + 'all_tenants': parsed_args.all_projects, + } + + data = self.app.client_manager.volume.volume_snapshots.list( + search_opts=search_opts) + return (column_headers, + (utils.get_item_properties( + s, columns, + formatters={'Metadata': utils.format_dict, + 'Volume ID': _format_volume_id}, + ) for s in data)) + + +class SetVolumeSnapshot(command.Command): + """Set volume snapshot properties""" + + def get_parser(self, prog_name): + parser = super(SetVolumeSnapshot, self).get_parser(prog_name) + parser.add_argument( + 'snapshot', + metavar='', + help=_('Snapshot to modify (name or ID)') + ) + parser.add_argument( + '--name', + metavar='', + help=_('New snapshot name') + ) + parser.add_argument( + '--description', + metavar='', + help=_('New snapshot description') + ) + parser.add_argument( + '--property', + metavar='', + action=parseractions.KeyValueAction, + help=_('Property to add/change for this snapshot ' + '(repeat option to set multiple properties)'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + snapshot = utils.find_resource(volume_client.volume_snapshots, + parsed_args.snapshot) + + result = 0 + if parsed_args.property: + try: + volume_client.volume_snapshots.set_metadata( + snapshot.id, parsed_args.property) + except Exception as e: + LOG.error(_("Failed to set snapshot property: %s"), e) + result += 1 + + kwargs = {} + if parsed_args.name: + kwargs['display_name'] = parsed_args.name + if parsed_args.description: + kwargs['display_description'] = parsed_args.description + if kwargs: + try: + snapshot.update(**kwargs) + except Exception as e: + LOG.error(_("Failed to update snapshot display name " + "or display description: %s"), e) + result += 1 + + if result > 0: + raise exceptions.CommandError(_("One or more of the " + "set operations failed")) + + +class ShowVolumeSnapshot(command.ShowOne): + """Display volume snapshot details""" + + def get_parser(self, prog_name): + parser = super(ShowVolumeSnapshot, self).get_parser(prog_name) + parser.add_argument( + 'snapshot', + metavar='', + help=_('Snapshot to display (name or ID)') + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + snapshot = utils.find_resource(volume_client.volume_snapshots, + parsed_args.snapshot) + + snapshot._info.update( + {'properties': utils.format_dict(snapshot._info.pop('metadata'))} + ) + + return zip(*sorted(six.iteritems(snapshot._info))) + + +class UnsetVolumeSnapshot(command.Command): + """Unset volume snapshot properties""" + + def get_parser(self, prog_name): + parser = super(UnsetVolumeSnapshot, self).get_parser(prog_name) + parser.add_argument( + 'snapshot', + metavar='', + help=_('Snapshot to modify (name or ID)'), + ) + parser.add_argument( + '--property', + metavar='', + action='append', + help=_('Property to remove from snapshot ' + '(repeat option to remove multiple properties)'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + snapshot = utils.find_resource( + volume_client.volume_snapshots, parsed_args.snapshot) + + if parsed_args.property: + volume_client.volume_snapshots.delete_metadata( + snapshot.id, + parsed_args.property, + ) diff --git a/openstackclient/volume/v2/snapshot.py b/openstackclient/volume/v2/snapshot.py index 4994e0da3c..2f3de2119a 100644 --- a/openstackclient/volume/v2/snapshot.py +++ b/openstackclient/volume/v2/snapshot.py @@ -12,6 +12,10 @@ # under the License. # +# TODO(Huanxuan Ao): Remove this file and "snapshot create", "snapshot delete", +# "snapshot set", "snapshot show" and "snapshot unset" +# commands two cycles after Ocata. + """Volume v2 snapshot action implementations""" import copy @@ -26,6 +30,8 @@ from openstackclient.i18n import _ +deprecated = True +LOG_DEP = logging.getLogger('deprecated') LOG = logging.getLogger(__name__) @@ -66,6 +72,8 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): + LOG_DEP.warning(_('This command has been deprecated. ' + 'Please use "volume snapshot create" instead.')) volume_client = self.app.client_manager.volume volume_id = utils.find_resource( volume_client.volumes, parsed_args.volume).id @@ -96,6 +104,8 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): + LOG_DEP.warning(_('This command has been deprecated. ' + 'Please use "volume snapshot delete" instead.')) volume_client = self.app.client_manager.volume result = 0 @@ -149,6 +159,8 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): + LOG_DEP.warning(_('This command has been deprecated. ' + 'Please use "volume snapshot list" instead.')) def _format_volume_id(volume_id): """Return a volume name if available @@ -239,6 +251,8 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): + LOG_DEP.warning(_('This command has been deprecated. ' + 'Please use "volume snapshot set" instead.')) volume_client = self.app.client_manager.volume snapshot = utils.find_resource(volume_client.volume_snapshots, parsed_args.snapshot) @@ -292,6 +306,8 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): + LOG_DEP.warning(_('This command has been deprecated. ' + 'Please use "volume snapshot show" instead.')) volume_client = self.app.client_manager.volume snapshot = utils.find_resource( volume_client.volume_snapshots, parsed_args.snapshot) @@ -322,6 +338,8 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): + LOG_DEP.warning(_('This command has been deprecated. ' + 'Please use "volume snapshot unset" instead.')) volume_client = self.app.client_manager.volume snapshot = utils.find_resource( volume_client.volume_snapshots, parsed_args.snapshot) diff --git a/openstackclient/volume/v2/volume_snapshot.py b/openstackclient/volume/v2/volume_snapshot.py new file mode 100644 index 0000000000..43f30326ed --- /dev/null +++ b/openstackclient/volume/v2/volume_snapshot.py @@ -0,0 +1,338 @@ +# +# 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 snapshot action implementations""" + +import copy +import logging + +from osc_lib.cli import parseractions +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 CreateVolumeSnapshot(command.ShowOne): + """Create new volume snapshot""" + + def get_parser(self, prog_name): + parser = super(CreateVolumeSnapshot, self).get_parser(prog_name) + parser.add_argument( + "snapshot_name", + metavar="", + nargs="?", + help=_("Name of the new snapshot (default to None)") + ) + parser.add_argument( + "--volume", + metavar="", + help=_("Volume to snapshot (name or ID) " + "(default is )") + ) + parser.add_argument( + "--description", + metavar="", + help=_("Description of the snapshot") + ) + parser.add_argument( + "--force", + action="store_true", + default=False, + help=_("Create a snapshot attached to an instance. " + "Default is False") + ) + parser.add_argument( + "--property", + metavar="", + action=parseractions.KeyValueAction, + help=_("Set a property to this snapshot " + "(repeat option to set multiple properties)"), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + volume = parsed_args.volume + if not parsed_args.volume: + volume = parsed_args.snapshot_name + volume_id = utils.find_resource( + volume_client.volumes, volume).id + snapshot = volume_client.volume_snapshots.create( + volume_id, + force=parsed_args.force, + name=parsed_args.snapshot_name, + description=parsed_args.description, + metadata=parsed_args.property, + ) + snapshot._info.update( + {'properties': utils.format_dict(snapshot._info.pop('metadata'))} + ) + return zip(*sorted(six.iteritems(snapshot._info))) + + +class DeleteVolumeSnapshot(command.Command): + """Delete volume snapshot(s)""" + + def get_parser(self, prog_name): + parser = super(DeleteVolumeSnapshot, self).get_parser(prog_name) + parser.add_argument( + "snapshots", + metavar="", + nargs="+", + help=_("Snapshot(s) to delete (name or ID)") + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + result = 0 + + for i in parsed_args.snapshots: + try: + snapshot_id = utils.find_resource( + volume_client.volume_snapshots, i).id + volume_client.volume_snapshots.delete(snapshot_id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete snapshot with " + "name or ID '%(snapshot)s': %(e)s") + % {'snapshot': i, 'e': e}) + + if result > 0: + total = len(parsed_args.snapshots) + msg = (_("%(result)s of %(total)s snapshots failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) + + +class ListVolumeSnapshot(command.Lister): + """List volume snapshots""" + + def get_parser(self, prog_name): + parser = super(ListVolumeSnapshot, self).get_parser(prog_name) + parser.add_argument( + '--all-projects', + action='store_true', + default=False, + help=_('Include all projects (admin only)'), + ) + parser.add_argument( + '--long', + action='store_true', + default=False, + help=_('List additional fields in output'), + ) + parser.add_argument( + '--marker', + metavar='', + help=_('The last snapshot ID of the previous page'), + ) + parser.add_argument( + '--limit', + type=int, + action=parseractions.NonNegativeAction, + metavar='', + help=_('Maximum number of snapshots to display'), + ) + return parser + + def take_action(self, parsed_args): + + def _format_volume_id(volume_id): + """Return a volume name if available + + :param volume_id: a volume ID + :rtype: either the volume ID or name + """ + + volume = volume_id + if volume_id in volume_cache.keys(): + volume = volume_cache[volume_id].name + return volume + + if parsed_args.long: + columns = ['ID', 'Name', 'Description', 'Status', + 'Size', 'Created At', 'Volume ID', 'Metadata'] + column_headers = copy.deepcopy(columns) + column_headers[6] = 'Volume' + column_headers[7] = 'Properties' + else: + columns = ['ID', 'Name', 'Description', 'Status', 'Size'] + column_headers = copy.deepcopy(columns) + + # Cache the volume list + volume_cache = {} + try: + for s in self.app.client_manager.volume.volumes.list(): + volume_cache[s.id] = s + except Exception: + # Just forget it if there's any trouble + pass + + search_opts = { + 'all_tenants': parsed_args.all_projects, + } + + data = self.app.client_manager.volume.volume_snapshots.list( + search_opts=search_opts, + marker=parsed_args.marker, + limit=parsed_args.limit, + ) + return (column_headers, + (utils.get_item_properties( + s, columns, + formatters={'Metadata': utils.format_dict, + 'Volume ID': _format_volume_id}, + ) for s in data)) + + +class SetVolumeSnapshot(command.Command): + """Set volume snapshot properties""" + + def get_parser(self, prog_name): + parser = super(SetVolumeSnapshot, self).get_parser(prog_name) + parser.add_argument( + 'snapshot', + metavar='', + help=_('Snapshot to modify (name or ID)') + ) + parser.add_argument( + '--name', + metavar='', + help=_('New snapshot name') + ) + parser.add_argument( + '--description', + metavar='', + help=_('New snapshot description') + ) + parser.add_argument( + '--property', + metavar='', + action=parseractions.KeyValueAction, + help=_('Property to add/change for this snapshot ' + '(repeat option to set multiple properties)'), + ) + parser.add_argument( + '--state', + metavar='', + choices=['available', 'error', 'creating', 'deleting', + 'error-deleting'], + help=_('New snapshot state. ("available", "error", "creating", ' + '"deleting", or "error_deleting") (admin only) ' + '(This option simply changes the state of the snapshot ' + 'in the database with no regard to actual status, ' + 'exercise caution when using)'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + snapshot = utils.find_resource(volume_client.volume_snapshots, + parsed_args.snapshot) + + result = 0 + if parsed_args.property: + try: + volume_client.volume_snapshots.set_metadata( + snapshot.id, parsed_args.property) + except Exception as e: + LOG.error(_("Failed to set snapshot property: %s"), e) + result += 1 + + if parsed_args.state: + try: + volume_client.volume_snapshots.reset_state( + snapshot.id, parsed_args.state) + except Exception as e: + LOG.error(_("Failed to set snapshot state: %s"), e) + result += 1 + + kwargs = {} + if parsed_args.name: + kwargs['name'] = parsed_args.name + if parsed_args.description: + kwargs['description'] = parsed_args.description + if kwargs: + try: + volume_client.volume_snapshots.update( + snapshot.id, **kwargs) + except Exception as e: + LOG.error(_("Failed to update snapshot name " + "or description: %s"), e) + result += 1 + + if result > 0: + raise exceptions.CommandError(_("One or more of the " + "set operations failed")) + + +class ShowVolumeSnapshot(command.ShowOne): + """Display volume snapshot details""" + + def get_parser(self, prog_name): + parser = super(ShowVolumeSnapshot, self).get_parser(prog_name) + parser.add_argument( + "snapshot", + metavar="", + help=_("Snapshot to display (name or ID)") + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + snapshot = utils.find_resource( + volume_client.volume_snapshots, parsed_args.snapshot) + snapshot._info.update( + {'properties': utils.format_dict(snapshot._info.pop('metadata'))} + ) + return zip(*sorted(six.iteritems(snapshot._info))) + + +class UnsetVolumeSnapshot(command.Command): + """Unset volume snapshot properties""" + + def get_parser(self, prog_name): + parser = super(UnsetVolumeSnapshot, self).get_parser(prog_name) + parser.add_argument( + 'snapshot', + metavar='', + help=_('Snapshot to modify (name or ID)'), + ) + parser.add_argument( + '--property', + metavar='', + action='append', + default=[], + help=_('Property to remove from snapshot ' + '(repeat option to remove multiple properties)'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + snapshot = utils.find_resource( + volume_client.volume_snapshots, parsed_args.snapshot) + + if parsed_args.property: + volume_client.volume_snapshots.delete_metadata( + snapshot.id, + parsed_args.property, + ) diff --git a/releasenotes/notes/rename-snapshot-commands-e0937f7143a4ef55.yaml b/releasenotes/notes/rename-snapshot-commands-e0937f7143a4ef55.yaml new file mode 100644 index 0000000000..4228207f43 --- /dev/null +++ b/releasenotes/notes/rename-snapshot-commands-e0937f7143a4ef55.yaml @@ -0,0 +1,8 @@ +--- +features: + - Add new commands ``volume snapshot create/delete/list/show/set/unset``. they are + used to replace the old commands ``snapshot create/delete/list/show/set/unset``. + [Blueprint `backup-snapshot-renamed-for-volume-resource `_] +deprecations: + - Deprecate commands ``snapshot create/delete/list/show/set/unset``. + [Blueprint `backup-snapshot-renamed-for-volume-resource `_] diff --git a/setup.cfg b/setup.cfg index a4abec1bc5..c76e8e3ca9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -473,6 +473,13 @@ openstack.volume.v1 = volume_backup_restore = openstackclient.volume.v1.backup:RestoreVolumeBackup volume_backup_show = openstackclient.volume.v1.backup:ShowVolumeBackup + volume_snapshot_create = openstackclient.volume.v1.volume_snapshot:CreateVolumeSnapshot + volume_snapshot_delete = openstackclient.volume.v1.volume_snapshot:DeleteVolumeSnapshot + volume_snapshot_list = openstackclient.volume.v1.volume_snapshot:ListVolumeSnapshot + volume_snapshot_set = openstackclient.volume.v1.volume_snapshot:SetVolumeSnapshot + volume_snapshot_show = openstackclient.volume.v1.volume_snapshot:ShowVolumeSnapshot + volume_snapshot_unset = openstackclient.volume.v1.volume_snapshot:UnsetVolumeSnapshot + volume_type_create = openstackclient.volume.v1.volume_type:CreateVolumeType volume_type_delete = openstackclient.volume.v1.volume_type:DeleteVolumeType volume_type_list = openstackclient.volume.v1.volume_type:ListVolumeType @@ -528,6 +535,13 @@ openstack.volume.v2 = volume_backup_set = openstackclient.volume.v2.backup:SetVolumeBackup volume_backup_show = openstackclient.volume.v2.backup:ShowVolumeBackup + volume_snapshot_create = openstackclient.volume.v2.volume_snapshot:CreateVolumeSnapshot + volume_snapshot_delete = openstackclient.volume.v2.volume_snapshot:DeleteVolumeSnapshot + volume_snapshot_list = openstackclient.volume.v2.volume_snapshot:ListVolumeSnapshot + volume_snapshot_set = openstackclient.volume.v2.volume_snapshot:SetVolumeSnapshot + volume_snapshot_show = openstackclient.volume.v2.volume_snapshot:ShowVolumeSnapshot + volume_snapshot_unset = openstackclient.volume.v2.volume_snapshot:UnsetVolumeSnapshot + volume_type_create = openstackclient.volume.v2.volume_type:CreateVolumeType volume_type_delete = openstackclient.volume.v2.volume_type:DeleteVolumeType volume_type_list = openstackclient.volume.v2.volume_type:ListVolumeType From 86a3c7a4159b8e978c5a90c66ded1f30944c1f45 Mon Sep 17 00:00:00 2001 From: Sindhu Devale Date: Wed, 2 Nov 2016 10:11:54 -0500 Subject: [PATCH 1331/3095] SDK refactor: Prepare floating ip commands Prepare the OSC "floating ip" commands for the SDK refactor. Change-Id: I02052185b3ce0b053acdcf76a0f68d49e6f7e608 Partially-Implements: blueprint network-command-sdk-support --- openstackclient/network/v2/floating_ip.py | 16 ++++++++++++---- .../tests/unit/network/v2/test_floating_ip.py | 2 +- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index bb75540cc6..12a4754d3e 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -19,6 +19,14 @@ from openstackclient.i18n import _ from openstackclient.network import common +from openstackclient.network import sdk_utils + + +def _get_network_columns(item): + column_map = { + 'tenant_id': 'project_id', + } + return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) def _get_columns(item): @@ -110,9 +118,9 @@ def update_parser_network(self, parser): def take_action_network(self, client, parsed_args): attrs = _get_attrs(self.app.client_manager, parsed_args) obj = client.create_ip(**attrs) - columns = _get_columns(obj) + display_columns, columns = _get_network_columns(obj) data = utils.get_item_properties(obj, columns) - return (columns, data) + return (display_columns, data) def take_action_compute(self, client, parsed_args): obj = client.floating_ips.create(parsed_args.network) @@ -282,9 +290,9 @@ def update_parser_common(self, parser): def take_action_network(self, client, parsed_args): obj = client.find_ip(parsed_args.floating_ip, ignore_missing=False) - columns = _get_columns(obj) + display_columns, columns = _get_network_columns(obj) data = utils.get_item_properties(obj, columns) - return (columns, data) + return (display_columns, data) def take_action_compute(self, client, parsed_args): obj = utils.find_resource( diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip.py b/openstackclient/tests/unit/network/v2/test_floating_ip.py index 1f30f2e923..10aaca34ef 100644 --- a/openstackclient/tests/unit/network/v2/test_floating_ip.py +++ b/openstackclient/tests/unit/network/v2/test_floating_ip.py @@ -296,7 +296,7 @@ class TestShowFloatingIPNetwork(TestFloatingIPNetwork): floating_ip.floating_network_id, floating_ip.id, floating_ip.port_id, - floating_ip.tenant_id, + floating_ip.project_id, floating_ip.router_id, floating_ip.status, ) From 3a915b5b5fc8f7f5dc2618eeedbef7517f1557c3 Mon Sep 17 00:00:00 2001 From: judy-yu Date: Fri, 4 Nov 2016 17:23:46 +0800 Subject: [PATCH 1332/3095] Not appropriate name sg rule attribute For ingress rules set ip-prefix means src-ip- prefix, but for egress rules set ip-prefix means dst-ip-prefix. It is not appropriate to name src-ip directly. So as to src-group. Change-Id: I03fd0e14e470e7272930ac2651e73263b83bd4e1 Closes-bug: #1637365 --- .../command-objects/security-group-rule.rst | 10 +- .../network/v2/security_group_rule.py | 54 +++++-- .../network/v2/test_security_group_rule.py | 141 +++++++++++++++++- .../notes/bug-1637365-b90cdcc05ffc7d3a.yaml | 7 + 4 files changed, 190 insertions(+), 22 deletions(-) create mode 100644 releasenotes/notes/bug-1637365-b90cdcc05ffc7d3a.yaml diff --git a/doc/source/command-objects/security-group-rule.rst b/doc/source/command-objects/security-group-rule.rst index 5284b2dc22..5bd144e988 100644 --- a/doc/source/command-objects/security-group-rule.rst +++ b/doc/source/command-objects/security-group-rule.rst @@ -16,7 +16,7 @@ Create a new security group rule .. code:: bash os security group rule create - [--src-ip | --src-group ] + [--remote-ip | --remote-group ] [--dst-port | [--icmp-type [--icmp-code ]]] [--protocol ] [--ingress | --egress] @@ -24,14 +24,14 @@ Create a new security group rule [--project [--project-domain ]] -.. option:: --src-ip +.. option:: --remote-ip - Source IP address block + Remote IP address block (may use CIDR notation; default for IPv4 rule: 0.0.0.0/0) -.. option:: --src-group +.. option:: --remote-group - Source security group (name or ID) + Remote security group (name or ID) .. option:: --dst-port diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index e3be44ece7..49435db673 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -94,14 +94,31 @@ def update_parser_common(self, parser): metavar='', help=_("Create rule in this security group (name or ID)") ) - source_group = parser.add_mutually_exclusive_group() - source_group.add_argument( + # NOTE(yujie): Support either remote-ip option name for now. + # However, consider deprecating and then removing --src-ip in + # a future release. + remote_group = parser.add_mutually_exclusive_group() + remote_group.add_argument( + "--remote-ip", + metavar="", + help=_("Remote IP address block (may use CIDR notation; " + "default for IPv4 rule: 0.0.0.0/0)") + ) + remote_group.add_argument( "--src-ip", metavar="", help=_("Source IP address block (may use CIDR notation; " "default for IPv4 rule: 0.0.0.0/0)") ) - source_group.add_argument( + # NOTE(yujie): Support either remote-group option name for now. + # However, consider deprecating and then removing --src-group in + # a future release. + remote_group.add_argument( + "--remote-group", + metavar="", + help=_("Remote security group (name or ID)") + ) + remote_group.add_argument( "--src-group", metavar="", help=_("Source security group (name or ID)") @@ -277,13 +294,16 @@ def take_action_network(self, client, parsed_args): if parsed_args.icmp_code: attrs['port_range_max'] = parsed_args.icmp_code - if parsed_args.src_group is not None: + if not (parsed_args.remote_group is None and + parsed_args.src_group is None): attrs['remote_group_id'] = client.find_security_group( - parsed_args.src_group, + parsed_args.remote_group or parsed_args.src_group, ignore_missing=False ).id - elif parsed_args.src_ip is not None: - attrs['remote_ip_prefix'] = parsed_args.src_ip + elif not (parsed_args.remote_ip is None and + parsed_args.src_ip is None): + attrs['remote_ip_prefix'] = (parsed_args.remote_ip or + parsed_args.src_ip) elif attrs['ethertype'] == 'IPv4': attrs['remote_ip_prefix'] = '0.0.0.0/0' attrs['security_group_id'] = security_group_id @@ -312,23 +332,25 @@ def take_action_compute(self, client, parsed_args): from_port, to_port = -1, -1 else: from_port, to_port = parsed_args.dst_port - src_ip = None - if parsed_args.src_group is not None: - parsed_args.src_group = utils.find_resource( + remote_ip = None + if not (parsed_args.remote_group is None and + parsed_args.src_group is None): + parsed_args.remote_group = utils.find_resource( client.security_groups, - parsed_args.src_group, + parsed_args.remote_group or parsed_args.src_group, ).id - if parsed_args.src_ip is not None: - src_ip = parsed_args.src_ip + if not (parsed_args.remote_ip is None and + parsed_args.src_ip is None): + remote_ip = parsed_args.remote_ip or parsed_args.src_ip else: - src_ip = '0.0.0.0/0' + remote_ip = '0.0.0.0/0' obj = client.security_group_rules.create( group.id, protocol, from_port, to_port, - src_ip, - parsed_args.src_group, + remote_ip, + parsed_args.remote_group, ) return _format_security_group_rule_show(obj._info) diff --git a/openstackclient/tests/unit/network/v2/test_security_group_rule.py b/openstackclient/tests/unit/network/v2/test_security_group_rule.py index 96d58e5c00..a6093ffe03 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_rule.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_rule.py @@ -119,6 +119,15 @@ def test_create_all_source_options(self): self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, []) + def test_create_all_remote_options(self): + arglist = [ + '--remote-ip', '10.10.0.0/24', + '--remote-group', self._security_group.id, + self._security_group.id, + ] + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, arglist, []) + def test_create_bad_ethertype(self): arglist = [ '--ethertype', 'foo', @@ -213,7 +222,7 @@ def test_create_proto_option(self): self.assertEqual(self.expected_columns, columns) self.assertEqual(self.expected_data, data) - def test_create_source_group(self): + def test_create_remote_group(self): self._setup_security_group_rule({ 'port_range_max': 22, 'port_range_min': 22, @@ -248,6 +257,34 @@ def test_create_source_group(self): self.assertEqual(self.expected_columns, columns) self.assertEqual(self.expected_data, data) + def test_create_source_group(self): + self._setup_security_group_rule({ + 'remote_group_id': self._security_group.id, + }) + arglist = [ + '--ingress', + '--src-group', self._security_group.name, + self._security_group.id, + ] + verifylist = [ + ('ingress', True), + ('src_group', self._security_group.name), + ('group', self._security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_security_group_rule.assert_called_once_with(**{ + 'direction': self._security_group_rule.direction, + 'ethertype': self._security_group_rule.ethertype, + 'protocol': self._security_group_rule.protocol, + 'remote_group_id': self._security_group_rule.remote_group_id, + 'security_group_id': self._security_group.id, + }) + self.assertEqual(self.expected_columns, columns) + self.assertEqual(self.expected_data, data) + def test_create_source_ip(self): self._setup_security_group_rule({ 'protocol': 'icmp', @@ -277,6 +314,35 @@ def test_create_source_ip(self): self.assertEqual(self.expected_columns, columns) self.assertEqual(self.expected_data, data) + def test_create_remote_ip(self): + self._setup_security_group_rule({ + 'protocol': 'icmp', + 'remote_ip_prefix': '10.0.2.0/24', + }) + arglist = [ + '--protocol', self._security_group_rule.protocol, + '--remote-ip', self._security_group_rule.remote_ip_prefix, + self._security_group.id, + ] + verifylist = [ + ('protocol', self._security_group_rule.protocol), + ('remote_ip', self._security_group_rule.remote_ip_prefix), + ('group', self._security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_security_group_rule.assert_called_once_with(**{ + 'direction': self._security_group_rule.direction, + 'ethertype': self._security_group_rule.ethertype, + 'protocol': self._security_group_rule.protocol, + 'remote_ip_prefix': self._security_group_rule.remote_ip_prefix, + 'security_group_id': self._security_group.id, + }) + self.assertEqual(self.expected_columns, columns) + self.assertEqual(self.expected_data, data) + def test_create_network_options(self): self._setup_security_group_rule({ 'direction': 'egress', @@ -498,6 +564,15 @@ def test_create_all_source_options(self): self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, []) + def test_create_all_remote_options(self): + arglist = [ + '--remote-ip', '10.10.0.0/24', + '--remote-group', self._security_group.id, + self._security_group.id, + ] + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, arglist, []) + def test_create_bad_protocol(self): arglist = [ '--protocol', 'foo', @@ -588,6 +663,38 @@ def test_create_source_group(self): self.assertEqual(expected_columns, columns) self.assertEqual(expected_data, data) + def test_create_remote_group(self): + expected_columns, expected_data = self._setup_security_group_rule({ + 'from_port': 22, + 'to_port': 22, + 'group': {'name': self._security_group.name}, + }) + arglist = [ + '--dst-port', str(self._security_group_rule.from_port), + '--remote-group', self._security_group.name, + self._security_group.id, + ] + verifylist = [ + ('dst_port', (self._security_group_rule.from_port, + self._security_group_rule.to_port)), + ('remote_group', self._security_group.name), + ('group', self._security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.compute.security_group_rules.create.assert_called_once_with( + self._security_group.id, + self._security_group_rule.ip_protocol, + self._security_group_rule.from_port, + self._security_group_rule.to_port, + self._security_group_rule.ip_range['cidr'], + self._security_group.id, + ) + self.assertEqual(expected_columns, columns) + self.assertEqual(expected_data, data) + def test_create_source_ip(self): expected_columns, expected_data = self._setup_security_group_rule({ 'ip_protocol': 'icmp', @@ -620,6 +727,38 @@ def test_create_source_ip(self): self.assertEqual(expected_columns, columns) self.assertEqual(expected_data, data) + def test_create_remote_ip(self): + expected_columns, expected_data = self._setup_security_group_rule({ + 'ip_protocol': 'icmp', + 'from_port': -1, + 'to_port': -1, + 'ip_range': {'cidr': '10.0.2.0/24'}, + }) + arglist = [ + '--protocol', self._security_group_rule.ip_protocol, + '--remote-ip', self._security_group_rule.ip_range['cidr'], + self._security_group.id, + ] + verifylist = [ + ('protocol', self._security_group_rule.ip_protocol), + ('remote_ip', self._security_group_rule.ip_range['cidr']), + ('group', self._security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.compute.security_group_rules.create.assert_called_once_with( + self._security_group.id, + self._security_group_rule.ip_protocol, + self._security_group_rule.from_port, + self._security_group_rule.to_port, + self._security_group_rule.ip_range['cidr'], + None, + ) + self.assertEqual(expected_columns, columns) + self.assertEqual(expected_data, data) + def test_create_proto_option(self): expected_columns, expected_data = self._setup_security_group_rule({ 'ip_protocol': 'icmp', diff --git a/releasenotes/notes/bug-1637365-b90cdcc05ffc7d3a.yaml b/releasenotes/notes/bug-1637365-b90cdcc05ffc7d3a.yaml new file mode 100644 index 0000000000..2b8e6c16bc --- /dev/null +++ b/releasenotes/notes/bug-1637365-b90cdcc05ffc7d3a.yaml @@ -0,0 +1,7 @@ +upgrade: + - + Changed the ``security group rule create`` command ``--src-ip`` + option to ``--remote-ip``, ``--src-group`` option to ``--remote-group``. + Using the ``--src-ip`` ``--src-group`` option is still supported, but + is no longer documented and may be deprecated in a future release. + [Bug `1637365 `_] From 0e3e05098c5f0e1a4c8dfe90f84940a9e6b9374b Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sun, 6 Nov 2016 02:07:15 +0000 Subject: [PATCH 1333/3095] Updated from global requirements Change-Id: I6e4dbe7ad08d749aa153eb30887802c6fb129a13 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a33e9aada0..2fcc7ff3b1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ keystoneauth1>=2.14.0 # Apache-2.0 openstacksdk>=0.9.7 # Apache-2.0 osc-lib>=1.2.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 -oslo.utils>=3.17.0 # Apache-2.0 +oslo.utils>=3.18.0 # Apache-2.0 python-glanceclient>=2.5.0 # Apache-2.0 python-keystoneclient>=3.6.0 # Apache-2.0 python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0 From e07b0e0919784b48dc47ae9cd8836342b8c13480 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Mon, 7 Nov 2016 17:19:24 +0800 Subject: [PATCH 1334/3095] Add options to "volume backup list" command Add "--name", "--status", "--volume", "--marker" (v2 only) and "--limit" (v2 only) options to "volume backup list" command Change-Id: If20cb7650f2359d393ee314d9e055a8659c73009 Closes-Bug: #1612484 Closes-Bug: #1639712 --- doc/source/command-objects/volume-backup.rst | 31 +++++++++++ .../tests/unit/volume/v1/test_backup.py | 40 +++++++++++++- .../tests/unit/volume/v2/test_backup.py | 53 +++++++++++++++++- openstackclient/volume/v1/backup.py | 36 +++++++++++- openstackclient/volume/v2/backup.py | 55 ++++++++++++++++++- .../notes/bug-1639712-a7b9d1a35a042049.yaml | 6 ++ 6 files changed, 211 insertions(+), 10 deletions(-) create mode 100644 releasenotes/notes/bug-1639712-a7b9d1a35a042049.yaml diff --git a/doc/source/command-objects/volume-backup.rst b/doc/source/command-objects/volume-backup.rst index 246fd38eb2..7ce8a4eb3e 100644 --- a/doc/source/command-objects/volume-backup.rst +++ b/doc/source/command-objects/volume-backup.rst @@ -88,12 +88,43 @@ List volume backups .. code:: bash os volume backup list + [--long] + [--name ] + [--status ] + [--volume ] + [--marker ] + [--limit ] .. _volume_backup_list-backup: .. option:: --long List additional fields in output +.. options:: --name + + Filters results by the backup name + +.. options:: --status + + Filters results by the backup status + ('creating', 'available', 'deleting', 'error', 'restoring' or 'error_restoring') + +.. options:: --volume + + Filters results by the volume which they backup (name or ID)" + +.. options:: --marker + + The last backup of the previous page (name or ID) + + *Volume version 2 only* + +.. options:: --limit + + Maximum number of backups to display + + *Volume version 2 only* + volume backup restore --------------------- diff --git a/openstackclient/tests/unit/volume/v1/test_backup.py b/openstackclient/tests/unit/volume/v1/test_backup.py index 32c2fd2242..f4a3b5e5cd 100644 --- a/openstackclient/tests/unit/volume/v1/test_backup.py +++ b/openstackclient/tests/unit/volume/v1/test_backup.py @@ -249,26 +249,60 @@ def setUp(self): self.volumes_mock.list.return_value = [self.volume] self.backups_mock.list.return_value = self.backups + self.volumes_mock.get.return_value = self.volume # Get the command to test self.cmd = backup.ListVolumeBackup(self.app, None) def test_backup_list_without_options(self): arglist = [] - verifylist = [("long", False)] + verifylist = [ + ("long", False), + ("name", None), + ("status", None), + ("volume", None), + ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) + search_opts = { + "name": None, + "status": None, + "volume_id": None, + } + self.volumes_mock.get.assert_not_called + self.backups_mock.list.assert_called_with( + search_opts=search_opts, + ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) def test_backup_list_with_options(self): - arglist = ["--long"] - verifylist = [("long", True)] + arglist = [ + "--long", + "--name", self.backups[0].name, + "--status", "error", + "--volume", self.volume.id, + ] + verifylist = [ + ("long", True), + ("name", self.backups[0].name), + ("status", "error"), + ("volume", self.volume.id), + ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) + search_opts = { + "name": self.backups[0].name, + "status": "error", + "volume_id": self.volume.id, + } + self.volumes_mock.get.assert_called_once_with(self.volume.id) + self.backups_mock.list.assert_called_with( + search_opts=search_opts, + ) self.assertEqual(self.columns_long, columns) self.assertEqual(self.data_long, list(data)) diff --git a/openstackclient/tests/unit/volume/v2/test_backup.py b/openstackclient/tests/unit/volume/v2/test_backup.py index 306c9eb3e8..dd2b268916 100644 --- a/openstackclient/tests/unit/volume/v2/test_backup.py +++ b/openstackclient/tests/unit/volume/v2/test_backup.py @@ -280,26 +280,73 @@ def setUp(self): self.volumes_mock.list.return_value = [self.volume] self.backups_mock.list.return_value = self.backups + self.volumes_mock.get.return_value = self.volume + self.backups_mock.get.return_value = self.backups[0] # Get the command to test self.cmd = backup.ListVolumeBackup(self.app, None) def test_backup_list_without_options(self): arglist = [] - verifylist = [("long", False)] + verifylist = [ + ("long", False), + ("name", None), + ("status", None), + ("volume", None), + ("marker", None), + ("limit", None), + ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) + search_opts = { + "name": None, + "status": None, + "volume_id": None, + } + self.volumes_mock.get.assert_not_called + self.backups_mock.get.assert_not_called + self.backups_mock.list.assert_called_with( + search_opts=search_opts, + marker=None, + limit=None, + ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) def test_backup_list_with_options(self): - arglist = ["--long"] - verifylist = [("long", True)] + arglist = [ + "--long", + "--name", self.backups[0].name, + "--status", "error", + "--volume", self.volume.id, + "--marker", self.backups[0].id, + "--limit", "3", + ] + verifylist = [ + ("long", True), + ("name", self.backups[0].name), + ("status", "error"), + ("volume", self.volume.id), + ("marker", self.backups[0].id), + ("limit", 3), + ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) + search_opts = { + "name": self.backups[0].name, + "status": "error", + "volume_id": self.volume.id, + } + self.volumes_mock.get.assert_called_once_with(self.volume.id) + self.backups_mock.get.assert_called_once_with(self.backups[0].id) + self.backups_mock.list.assert_called_with( + search_opts=search_opts, + marker=self.backups[0].id, + limit=3, + ) self.assertEqual(self.columns_long, columns) self.assertEqual(self.data_long, list(data)) diff --git a/openstackclient/volume/v1/backup.py b/openstackclient/volume/v1/backup.py index c9d0ca0d81..bf4e8625bd 100644 --- a/openstackclient/volume/v1/backup.py +++ b/openstackclient/volume/v1/backup.py @@ -152,9 +152,30 @@ def get_parser(self, prog_name): default=False, help=_('List additional fields in output'), ) + parser.add_argument( + "--name", + metavar="", + help=_("Filters results by the backup name") + ) + parser.add_argument( + "--status", + metavar="", + choices=['creating', 'available', 'deleting', + 'error', 'restoring', 'error_restoring'], + help=_("Filters results by the backup status " + "('creating', 'available', 'deleting', " + "'error', 'restoring' or 'error_restoring')") + ) + parser.add_argument( + "--volume", + metavar="", + help=_("Filters results by the volume which they " + "backup (name or ID)") + ) return parser def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume def _format_volume_id(volume_id): """Return a volume name if available @@ -180,13 +201,24 @@ def _format_volume_id(volume_id): # Cache the volume list volume_cache = {} try: - for s in self.app.client_manager.volume.volumes.list(): + for s in volume_client.volumes.list(): volume_cache[s.id] = s except Exception: # Just forget it if there's any trouble pass - data = self.app.client_manager.volume.backups.list() + filter_volume_id = None + if parsed_args.volume: + filter_volume_id = utils.find_resource(volume_client.volumes, + parsed_args.volume).id + search_opts = { + 'name': parsed_args.name, + 'status': parsed_args.status, + 'volume_id': filter_volume_id, + } + data = volume_client.backups.list( + search_opts=search_opts, + ) return (column_headers, (utils.get_item_properties( diff --git a/openstackclient/volume/v2/backup.py b/openstackclient/volume/v2/backup.py index 2ca35b24b2..e674ef2bb0 100644 --- a/openstackclient/volume/v2/backup.py +++ b/openstackclient/volume/v2/backup.py @@ -17,6 +17,7 @@ import copy import logging +from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -179,9 +180,42 @@ def get_parser(self, prog_name): default=False, help=_("List additional fields in output") ) + parser.add_argument( + "--name", + metavar="", + help=_("Filters results by the backup name") + ) + parser.add_argument( + "--status", + metavar="", + choices=['creating', 'available', 'deleting', + 'error', 'restoring', 'error_restoring'], + help=_("Filters results by the backup status " + "('creating', 'available', 'deleting', " + "'error', 'restoring' or 'error_restoring')") + ) + parser.add_argument( + "--volume", + metavar="", + help=_("Filters results by the volume which they " + "backup (name or ID)") + ) + parser.add_argument( + '--marker', + metavar='', + help=_('The last backup of the previous page (name or ID)'), + ) + parser.add_argument( + '--limit', + type=int, + action=parseractions.NonNegativeAction, + metavar='', + help=_('Maximum number of backups to display'), + ) return parser def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume def _format_volume_id(volume_id): """Return a volume name if available @@ -207,13 +241,30 @@ def _format_volume_id(volume_id): # Cache the volume list volume_cache = {} try: - for s in self.app.client_manager.volume.volumes.list(): + for s in volume_client.volumes.list(): volume_cache[s.id] = s except Exception: # Just forget it if there's any trouble pass - data = self.app.client_manager.volume.backups.list() + filter_volume_id = None + if parsed_args.volume: + filter_volume_id = utils.find_resource(volume_client.volumes, + parsed_args.volume).id + marker_backup_id = None + if parsed_args.marker: + marker_backup_id = utils.find_resource(volume_client.backups, + parsed_args.marker).id + search_opts = { + 'name': parsed_args.name, + 'status': parsed_args.status, + 'volume_id': filter_volume_id, + } + data = volume_client.backups.list( + search_opts=search_opts, + marker=marker_backup_id, + limit=parsed_args.limit, + ) return (column_headers, (utils.get_item_properties( diff --git a/releasenotes/notes/bug-1639712-a7b9d1a35a042049.yaml b/releasenotes/notes/bug-1639712-a7b9d1a35a042049.yaml new file mode 100644 index 0000000000..f9c09c4b4d --- /dev/null +++ b/releasenotes/notes/bug-1639712-a7b9d1a35a042049.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--name``, ``--status``, ``--volume``, ``--marker`` and ``--limit`` options + to ``volume backup list`` command + [Bug `1639712 `_] From 55669b90c041815c1862d468fe8eb76172a212d8 Mon Sep 17 00:00:00 2001 From: Jordan Pittier Date: Fri, 4 Nov 2016 13:13:20 +0100 Subject: [PATCH 1335/3095] Add 'all-projects' option to 'volume backup list' Similar to what 'volume list --all-projects' does, 'volume backup list --all-projects' list volume backups accross all projects. Change-Id: Id5dda9b5adc822c4ddfb2dda339946d3322858e2 --- doc/source/command-objects/volume-backup.rst | 5 +++++ openstackclient/tests/unit/volume/v1/test_backup.py | 7 ++++++- openstackclient/tests/unit/volume/v2/test_backup.py | 9 +++++++-- openstackclient/volume/v1/backup.py | 7 +++++++ openstackclient/volume/v2/backup.py | 7 +++++++ ...backup_list_all-projects_option-4bb23e0b9b075cac.yaml | 4 ++++ 6 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/backup_list_all-projects_option-4bb23e0b9b075cac.yaml diff --git a/doc/source/command-objects/volume-backup.rst b/doc/source/command-objects/volume-backup.rst index 7ce8a4eb3e..2cccffae93 100644 --- a/doc/source/command-objects/volume-backup.rst +++ b/doc/source/command-objects/volume-backup.rst @@ -94,6 +94,7 @@ List volume backups [--volume ] [--marker ] [--limit ] + [--all-projects] .. _volume_backup_list-backup: .. option:: --long @@ -125,6 +126,10 @@ List volume backups *Volume version 2 only* +.. option:: --all-projects + + Include all projects (admin only) + volume backup restore --------------------- diff --git a/openstackclient/tests/unit/volume/v1/test_backup.py b/openstackclient/tests/unit/volume/v1/test_backup.py index f4a3b5e5cd..1097d3f121 100644 --- a/openstackclient/tests/unit/volume/v1/test_backup.py +++ b/openstackclient/tests/unit/volume/v1/test_backup.py @@ -260,6 +260,7 @@ def test_backup_list_without_options(self): ("name", None), ("status", None), ("volume", None), + ('all_projects', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -269,8 +270,9 @@ def test_backup_list_without_options(self): "name": None, "status": None, "volume_id": None, + "all_tenants": False, } - self.volumes_mock.get.assert_not_called + self.volumes_mock.get.assert_not_called() self.backups_mock.list.assert_called_with( search_opts=search_opts, ) @@ -283,12 +285,14 @@ def test_backup_list_with_options(self): "--name", self.backups[0].name, "--status", "error", "--volume", self.volume.id, + "--all-projects" ] verifylist = [ ("long", True), ("name", self.backups[0].name), ("status", "error"), ("volume", self.volume.id), + ('all_projects', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -298,6 +302,7 @@ def test_backup_list_with_options(self): "name": self.backups[0].name, "status": "error", "volume_id": self.volume.id, + "all_tenants": True, } self.volumes_mock.get.assert_called_once_with(self.volume.id) self.backups_mock.list.assert_called_with( diff --git a/openstackclient/tests/unit/volume/v2/test_backup.py b/openstackclient/tests/unit/volume/v2/test_backup.py index dd2b268916..10e7aac5cf 100644 --- a/openstackclient/tests/unit/volume/v2/test_backup.py +++ b/openstackclient/tests/unit/volume/v2/test_backup.py @@ -294,6 +294,7 @@ def test_backup_list_without_options(self): ("volume", None), ("marker", None), ("limit", None), + ('all_projects', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -303,9 +304,10 @@ def test_backup_list_without_options(self): "name": None, "status": None, "volume_id": None, + 'all_tenants': False, } - self.volumes_mock.get.assert_not_called - self.backups_mock.get.assert_not_called + self.volumes_mock.get.assert_not_called() + self.backups_mock.get.assert_not_called() self.backups_mock.list.assert_called_with( search_opts=search_opts, marker=None, @@ -321,6 +323,7 @@ def test_backup_list_with_options(self): "--status", "error", "--volume", self.volume.id, "--marker", self.backups[0].id, + "--all-projects", "--limit", "3", ] verifylist = [ @@ -329,6 +332,7 @@ def test_backup_list_with_options(self): ("status", "error"), ("volume", self.volume.id), ("marker", self.backups[0].id), + ('all_projects', True), ("limit", 3), ] @@ -339,6 +343,7 @@ def test_backup_list_with_options(self): "name": self.backups[0].name, "status": "error", "volume_id": self.volume.id, + 'all_tenants': True, } self.volumes_mock.get.assert_called_once_with(self.volume.id) self.backups_mock.get.assert_called_once_with(self.backups[0].id) diff --git a/openstackclient/volume/v1/backup.py b/openstackclient/volume/v1/backup.py index bf4e8625bd..a02cdccb22 100644 --- a/openstackclient/volume/v1/backup.py +++ b/openstackclient/volume/v1/backup.py @@ -172,6 +172,12 @@ def get_parser(self, prog_name): help=_("Filters results by the volume which they " "backup (name or ID)") ) + parser.add_argument( + '--all-projects', + action='store_true', + default=False, + help=_('Include all projects (admin only)'), + ) return parser def take_action(self, parsed_args): @@ -215,6 +221,7 @@ def _format_volume_id(volume_id): 'name': parsed_args.name, 'status': parsed_args.status, 'volume_id': filter_volume_id, + 'all_tenants': parsed_args.all_projects, } data = volume_client.backups.list( search_opts=search_opts, diff --git a/openstackclient/volume/v2/backup.py b/openstackclient/volume/v2/backup.py index e674ef2bb0..c41cffda38 100644 --- a/openstackclient/volume/v2/backup.py +++ b/openstackclient/volume/v2/backup.py @@ -212,6 +212,12 @@ def get_parser(self, prog_name): metavar='', help=_('Maximum number of backups to display'), ) + parser.add_argument( + '--all-projects', + action='store_true', + default=False, + help=_('Include all projects (admin only)'), + ) return parser def take_action(self, parsed_args): @@ -259,6 +265,7 @@ def _format_volume_id(volume_id): 'name': parsed_args.name, 'status': parsed_args.status, 'volume_id': filter_volume_id, + 'all_tenants': parsed_args.all_projects, } data = volume_client.backups.list( search_opts=search_opts, diff --git a/releasenotes/notes/backup_list_all-projects_option-4bb23e0b9b075cac.yaml b/releasenotes/notes/backup_list_all-projects_option-4bb23e0b9b075cac.yaml new file mode 100644 index 0000000000..5d379f64ea --- /dev/null +++ b/releasenotes/notes/backup_list_all-projects_option-4bb23e0b9b075cac.yaml @@ -0,0 +1,4 @@ +--- +features: + - The ``openstack volume backup list`` command now supports the + ``all-projects`` option to list volume backups accross all projects. From 52c4a55d43248d87737613509930242244b6ff3c Mon Sep 17 00:00:00 2001 From: Sindhu Devale Date: Sun, 6 Nov 2016 22:56:00 -0600 Subject: [PATCH 1336/3095] Add 'description' option This patch adds '--description' option to os security group rule create cmd. Change-Id: I604bcdeb4658d2dcc4d860a87e704e186cca5225 Partially-Implements: blueprint network-commands-options Partially-Implements: blueprint neutron-client-descriptions --- .../command-objects/security-group-rule.rst | 7 +++++ .../network/v2/security_group_rule.py | 8 +++++ .../tests/unit/network/v2/fakes.py | 2 ++ .../network/v2/test_security_group_rule.py | 31 +++++++++++++++++++ ...-client-descriptions-a80902b4295843cf.yaml | 3 ++ 5 files changed, 51 insertions(+) diff --git a/doc/source/command-objects/security-group-rule.rst b/doc/source/command-objects/security-group-rule.rst index 5284b2dc22..69c9fabc54 100644 --- a/doc/source/command-objects/security-group-rule.rst +++ b/doc/source/command-objects/security-group-rule.rst @@ -22,6 +22,7 @@ Create a new security group rule [--ingress | --egress] [--ethertype ] [--project [--project-domain ]] + [--description ] .. option:: --src-ip @@ -97,6 +98,12 @@ Create a new security group rule *Network version 2 only* +.. option:: --description + + Set security group rule description + + *Network version 2 only* + .. describe:: Create rule in this security group (name or ID) diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index e3be44ece7..0f3bad3d3b 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -109,6 +109,11 @@ def update_parser_common(self, parser): return parser def update_parser_network(self, parser): + parser.add_argument( + '--description', + metavar='', + help=_("Set security group rule description") + ) parser.add_argument( '--dst-port', metavar='', @@ -235,6 +240,9 @@ def take_action_network(self, client, parsed_args): attrs = {} attrs['protocol'] = self._get_protocol(parsed_args) + if parsed_args.description is not None: + attrs['description'] = parsed_args.description + # NOTE(rtheis): A direction must be specified and ingress # is the default. if parsed_args.ingress or not parsed_args.egress: diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index 94727ae3ca..31f3be7563 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -951,6 +951,8 @@ def create_one_security_group_rule(attrs=None): # Set default attributes. security_group_rule_attrs = { + 'description': 'security-group-rule-description-' + + uuid.uuid4().hex, 'direction': 'ingress', 'ethertype': 'IPv4', 'id': 'security-group-rule-id-' + uuid.uuid4().hex, diff --git a/openstackclient/tests/unit/network/v2/test_security_group_rule.py b/openstackclient/tests/unit/network/v2/test_security_group_rule.py index 96d58e5c00..401e042f35 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_rule.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_rule.py @@ -60,6 +60,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): network_fakes.FakeSecurityGroup.create_one_security_group() expected_columns = ( + 'description', 'direction', 'ethertype', 'id', @@ -81,6 +82,7 @@ def _setup_security_group_rule(self, attrs=None): self.network.create_security_group_rule = mock.Mock( return_value=self._security_group_rule) self.expected_data = ( + self._security_group_rule.description, self._security_group_rule.direction, self._security_group_rule.ethertype, self._security_group_rule.id, @@ -452,6 +454,33 @@ def test_create_icmpv6_type(self): self.assertEqual(self.expected_columns, columns) self.assertEqual(self.expected_data, data) + def test_create_with_description(self): + self._setup_security_group_rule({ + 'description': 'Setting SGR', + }) + arglist = [ + '--description', self._security_group_rule.description, + self._security_group.id, + ] + verifylist = [ + ('description', self._security_group_rule.description), + ('group', self._security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_security_group_rule.assert_called_once_with(**{ + 'description': self._security_group_rule.description, + 'direction': self._security_group_rule.direction, + 'ethertype': self._security_group_rule.ethertype, + 'protocol': self._security_group_rule.protocol, + 'remote_ip_prefix': self._security_group_rule.remote_ip_prefix, + 'security_group_id': self._security_group.id, + }) + self.assertEqual(self.expected_columns, columns) + self.assertEqual(self.expected_data, data) + class TestCreateSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): @@ -1075,6 +1104,7 @@ class TestShowSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): network_fakes.FakeSecurityGroupRule.create_one_security_group_rule() columns = ( + 'description', 'direction', 'ethertype', 'id', @@ -1088,6 +1118,7 @@ class TestShowSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): ) data = ( + _security_group_rule.description, _security_group_rule.direction, _security_group_rule.ethertype, _security_group_rule.id, diff --git a/releasenotes/notes/bp-neutron-client-descriptions-a80902b4295843cf.yaml b/releasenotes/notes/bp-neutron-client-descriptions-a80902b4295843cf.yaml index 83a0090251..3135d0d73c 100644 --- a/releasenotes/notes/bp-neutron-client-descriptions-a80902b4295843cf.yaml +++ b/releasenotes/notes/bp-neutron-client-descriptions-a80902b4295843cf.yaml @@ -7,3 +7,6 @@ features: Add ``--description`` option to ``router set`` and ``router create`` commands. [Blueprint :oscbp:`network-commands-options`] + | + Adds ``--description`` option to ``security group rule create``. + [Blueprint :oscbp:`network-commands-options`] From a1ed3752924e38fae8829bec22b7d2006a3e8e1b Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 9 Nov 2016 04:24:18 +0000 Subject: [PATCH 1337/3095] Updated from global requirements Change-Id: I27e6899434ac47018a6ebd140cd326e7c6cd00d9 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 2fcc7ff3b1..05a000f857 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ # The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -pbr>=1.6 # Apache-2.0 +pbr>=1.8 # Apache-2.0 six>=1.9.0 # MIT Babel>=2.3.4 # BSD From 52279b1b04924284576966eb33192dd1301683bb Mon Sep 17 00:00:00 2001 From: Jordan Pittier Date: Thu, 10 Nov 2016 16:19:01 +0100 Subject: [PATCH 1338/3095] network.common.NetworkAndComputeShowOne: catch HttpException Problem: if a user issue the cmd 'openstack floating ip create public' and has already maxed his quota for FIP, OSC exits with a not so useful message: >jordan@jordan-XPS13-9333:~ $ openstack floating ip create public >HttpException: Conflict This patches catch the HttpException earlier and prints a more verbose message: > jordan@jordan-XPS13-9333:~ $ openstack floating ip create public > Error while executing command: Quota exceeded for resources: ['floatingip'] Change-Id: I7c87524d871d230d92f007c32e06439b34c7194a --- openstackclient/network/common.py | 17 +++++++++++------ .../tests/unit/network/test_common.py | 14 ++++++++++++++ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/openstackclient/network/common.py b/openstackclient/network/common.py index 2b1a5656f2..a2e700be2f 100644 --- a/openstackclient/network/common.py +++ b/openstackclient/network/common.py @@ -14,6 +14,7 @@ import abc import logging +import openstack.exceptions from osc_lib.command import command from osc_lib import exceptions import six @@ -181,12 +182,16 @@ class NetworkAndComputeShowOne(command.ShowOne): """ def take_action(self, parsed_args): - if self.app.client_manager.is_network_endpoint_enabled(): - return self.take_action_network(self.app.client_manager.network, - parsed_args) - else: - return self.take_action_compute(self.app.client_manager.compute, - parsed_args) + try: + if self.app.client_manager.is_network_endpoint_enabled(): + return self.take_action_network( + self.app.client_manager.network, parsed_args) + else: + return self.take_action_compute( + self.app.client_manager.compute, parsed_args) + except openstack.exceptions.HttpException as exc: + msg = _("Error while executing command: %s") % exc.message + raise exceptions.CommandError(msg) def get_parser(self, prog_name): LOG.debug('get_parser(%s)', prog_name) diff --git a/openstackclient/tests/unit/network/test_common.py b/openstackclient/tests/unit/network/test_common.py index 325aad2a86..4b9a754b34 100644 --- a/openstackclient/tests/unit/network/test_common.py +++ b/openstackclient/tests/unit/network/test_common.py @@ -14,6 +14,8 @@ import argparse import mock +import openstack +from openstackclient.common import exceptions from openstackclient.network import common from openstackclient.tests.unit import utils @@ -172,3 +174,15 @@ class TestNetworkAndComputeShowOne(TestNetworkAndCompute): def setUp(self): super(TestNetworkAndComputeShowOne, self).setUp() self.cmd = FakeNetworkAndComputeShowOne(self.app, self.namespace) + + def test_take_action_with_http_exception(self): + with mock.patch.object(self.cmd, 'take_action_network') as m_action: + m_action.side_effect = openstack.exceptions.HttpException("bar") + self.assertRaisesRegex(exceptions.CommandError, "bar", + self.cmd.take_action, mock.Mock()) + + self.app.client_manager.network_endpoint_enabled = False + with mock.patch.object(self.cmd, 'take_action_compute') as m_action: + m_action.side_effect = openstack.exceptions.HttpException("bar") + self.assertRaisesRegex(exceptions.CommandError, "bar", + self.cmd.take_action, mock.Mock()) From ff7fda061e4f89744b2c6d237ab913225b2ee14e Mon Sep 17 00:00:00 2001 From: Jaspreet Singh Rawel Date: Thu, 10 Nov 2016 02:05:54 +0530 Subject: [PATCH 1339/3095] Show disk format vhdx in help Currently disk format type vhdx is missing in help. Closes-Bug: 1635518 Change-Id: Ibe5976f722c4eb966b12a7b1e1c2702fd08ce84b --- openstackclient/image/v1/image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index 5f669c6412..fc54cc896c 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -503,7 +503,7 @@ def get_parser(self, prog_name): choices=container_choices ) disk_choices = ["ami", "ari", "aki", "vhd", "vmdk", "raw", "qcow2", - "vdi", "iso"] + "vhdx", "vdi", "iso"] parser.add_argument( "--disk-format", metavar="", From 839c5f7a84b48cecbc80606e894b7906fa01d66b Mon Sep 17 00:00:00 2001 From: songminglong Date: Sun, 13 Nov 2016 10:11:03 +0800 Subject: [PATCH 1340/3095] Add '--network' and other options to floating ip list The patch adds filtering '--network', '--port', '--fixed-ip-address' options to floating ip list command Partial-Bug: #1614379 Change-Id: I82319d0985d9e864431097c90264a20bf88167cc --- doc/source/command-objects/floating-ip.rst | 21 +++++++ openstackclient/network/v2/floating_ip.py | 36 +++++++++++ .../tests/unit/network/v2/test_floating_ip.py | 61 ++++++++++++++++++- .../notes/bug-1614379-da92ded6d19f5ad5.yaml | 6 ++ 4 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/bug-1614379-da92ded6d19f5ad5.yaml diff --git a/doc/source/command-objects/floating-ip.rst b/doc/source/command-objects/floating-ip.rst index 2ab21f36eb..b2cc8af0ef 100644 --- a/doc/source/command-objects/floating-ip.rst +++ b/doc/source/command-objects/floating-ip.rst @@ -72,6 +72,27 @@ List floating IP(s) .. code:: bash os floating ip list + [--network ] + [--port ] + [--fixed-ip-address ] + +.. option:: --network + + List floating IP(s) according to given network (name or ID) + + *Network version 2 only* + +.. option:: --port + + List floating IP(s) according to given port (name or ID) + + *Network version 2 only* + +.. option:: --fixed-ip-address + + List floating IP(s) according to given fixed IP address + + *Network version 2 only* floating ip show ---------------- diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index 7ae5d44816..2aac8e7bb7 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -205,7 +205,31 @@ def take_action_compute(self, client, parsed_args): class ListFloatingIP(common.NetworkAndComputeLister): """List floating IP(s)""" + def update_parser_network(self, parser): + parser.add_argument( + '--network', + metavar='', + help=_("List floating IP(s) according to " + "given network (name or ID)") + ) + parser.add_argument( + '--port', + metavar='', + help=_("List floating IP(s) according to " + "given port (name or ID)") + ) + parser.add_argument( + '--fixed-ip-address', + metavar='', + help=_("List floating IP(s) according to " + "given fixed IP address") + ) + + return parser + def take_action_network(self, client, parsed_args): + network_client = self.app.client_manager.network + columns = ( 'id', 'floating_ip_address', @@ -224,6 +248,18 @@ def take_action_network(self, client, parsed_args): ) query = {} + + if parsed_args.network is not None: + network = network_client.find_network(parsed_args.network, + ignore_missing=False) + query['floating_network_id'] = network.id + if parsed_args.port is not None: + port = network_client.find_port(parsed_args.port, + ignore_missing=False) + query['port_id'] = port.id + if parsed_args.fixed_ip_address is not None: + query['fixed_ip_address'] = parsed_args.fixed_ip_address + data = client.ips(**query) return (headers, diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip.py b/openstackclient/tests/unit/network/v2/test_floating_ip.py index 0454733b4d..10f3067d9a 100644 --- a/openstackclient/tests/unit/network/v2/test_floating_ip.py +++ b/openstackclient/tests/unit/network/v2/test_floating_ip.py @@ -231,6 +231,12 @@ class TestListFloatingIPNetwork(TestFloatingIPNetwork): # The floating ips to list up floating_ips = network_fakes.FakeFloatingIP.create_floating_ips(count=3) + fake_network = network_fakes.FakeNetwork.create_one_network({ + 'id': 'fake_network_id', + }) + fake_port = network_fakes.FakePort.create_one_port({ + 'id': 'fake_port_id', + }) columns = ( 'ID', @@ -256,6 +262,8 @@ def setUp(self): super(TestListFloatingIPNetwork, self).setUp() self.network.ips = mock.Mock(return_value=self.floating_ips) + self.network.find_network = mock.Mock(return_value=self.fake_network) + self.network.find_port = mock.Mock(return_value=self.fake_port) # Get the command object to test self.cmd = floating_ip.ListFloatingIP(self.app, self.namespace) @@ -267,7 +275,58 @@ def test_floating_ip_list(self): columns, data = self.cmd.take_action(parsed_args) - self.network.ips.assert_called_once_with(**{}) + self.network.ips.assert_called_once_with() + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_floating_ip_list_network(self): + arglist = [ + '--network', 'fake_network_id', + ] + verifylist = [ + ('network', 'fake_network_id'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.ips.assert_called_once_with(**{ + 'floating_network_id': 'fake_network_id', + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_floating_ip_list_port(self): + arglist = [ + '--port', 'fake_port_id', + ] + verifylist = [ + ('port', 'fake_port_id'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.ips.assert_called_once_with(**{ + 'port_id': 'fake_port_id', + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_floating_ip_list_fixed_ip_address(self): + arglist = [ + '--fixed-ip-address', self.floating_ips[0].fixed_ip_address, + ] + verifylist = [ + ('fixed_ip_address', self.floating_ips[0].fixed_ip_address), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.ips.assert_called_once_with(**{ + 'fixed_ip_address': self.floating_ips[0].fixed_ip_address, + }) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) diff --git a/releasenotes/notes/bug-1614379-da92ded6d19f5ad5.yaml b/releasenotes/notes/bug-1614379-da92ded6d19f5ad5.yaml new file mode 100644 index 0000000000..a5ac74f131 --- /dev/null +++ b/releasenotes/notes/bug-1614379-da92ded6d19f5ad5.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--port``, ``--fixed-ip-address``, ``--network``, + options to ``floating ip list`` command + [Bug `1614379 `_] \ No newline at end of file From 0b5655fae87b9cf278231fac8febe3114175ff9c Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Tue, 15 Nov 2016 10:14:28 +0000 Subject: [PATCH 1341/3095] Updated from global requirements Change-Id: I1896f594f692e43e7c986594fc0afcf6a345b54e --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 05a000f857..d84b44c8bc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ six>=1.9.0 # MIT Babel>=2.3.4 # BSD cliff>=2.2.0 # Apache-2.0 keystoneauth1>=2.14.0 # Apache-2.0 -openstacksdk>=0.9.7 # Apache-2.0 +openstacksdk!=0.9.9,>=0.9.7 # Apache-2.0 osc-lib>=1.2.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 oslo.utils>=3.18.0 # Apache-2.0 From 4cd336b1285fdd0455ce1fcc4544ae81a7756e0b Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 16 Nov 2016 00:21:32 +0000 Subject: [PATCH 1342/3095] Updated from global requirements Change-Id: I96c212312bca547dd5a27b6af8cfabad46d6e1da --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index d84b44c8bc..e72f687473 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ six>=1.9.0 # MIT Babel>=2.3.4 # BSD cliff>=2.2.0 # Apache-2.0 keystoneauth1>=2.14.0 # Apache-2.0 -openstacksdk!=0.9.9,>=0.9.7 # Apache-2.0 +openstacksdk>=0.9.10 # Apache-2.0 osc-lib>=1.2.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 oslo.utils>=3.18.0 # Apache-2.0 From 76c7a0af574f4af5fcd93e4f5102200e8a34df70 Mon Sep 17 00:00:00 2001 From: Reedip Date: Wed, 16 Nov 2016 14:08:11 +0530 Subject: [PATCH 1343/3095] TrivialFix: Insert blank space for readability Change-Id: Ia4612f65332d788e186c801edb98486862f98e56 --- doc/source/command-objects/subnet.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/command-objects/subnet.rst b/doc/source/command-objects/subnet.rst index e2059875ba..c4c980f628 100644 --- a/doc/source/command-objects/subnet.rst +++ b/doc/source/command-objects/subnet.rst @@ -289,6 +289,7 @@ Set subnet properties ``network:floatingip_agent_gateway``. Must be a valid device owner value for a network port (repeat option to set multiple service types) + .. option:: --description Set subnet description From 7684ab4a297acd8f756e78f71a7e85dc70079d09 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Wed, 16 Nov 2016 20:07:50 +0800 Subject: [PATCH 1344/3095] Outdated test data clean up in volume Now all the volume unit tests (both v1 and v2) are using fake classes. All the old fake data and code can be removed. Change-Id: Ib35ad4b6c94c42a21215e012f80f8978a74a3d18 --- openstackclient/tests/unit/volume/v1/fakes.py | 96 ------------------- 1 file changed, 96 deletions(-) diff --git a/openstackclient/tests/unit/volume/v1/fakes.py b/openstackclient/tests/unit/volume/v1/fakes.py index 434e637a0a..78a8227e44 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 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.""" From 0ac4370c09567b80cda84d022068642047576e32 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 16 Nov 2016 07:37:40 -0600 Subject: [PATCH 1345/3095] Do proper deprecation for security group rule create Review I03fd0e14e470e7272930ac2651e73263b83bd4e1 renamed the --src-group and --src-ip options to --remote-group and --remote-ip but did not properly deprecate the old option names. Add deprecation warnings when the old option names are used. Also, format the warnings using the new proposed translation guideline for marking substrings to not be translated, such as literal names and option names. Change-Id: I63d085d190fc28b8637e7686016eda4efbdda1be --- .../network/v2/security_group_rule.py | 64 ++++++++++++++----- .../notes/bug-1637365-b90cdcc05ffc7d3a.yaml | 11 ++-- 2 files changed, 53 insertions(+), 22 deletions(-) diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index f71edce80f..b7ca0eafec 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -14,6 +14,7 @@ """Security Group Rule action implementations""" import argparse +import logging try: from novaclient.v2 import security_group_rules as compute_secgroup_rules @@ -31,6 +32,9 @@ from openstackclient.network import utils as network_utils +LOG = logging.getLogger(__name__) + + def _format_security_group_rule_show(obj): data = network_utils.transform_compute_security_group_rule(obj) return zip(*sorted(six.iteritems(data))) @@ -94,34 +98,30 @@ def update_parser_common(self, parser): metavar='', help=_("Create rule in this security group (name or ID)") ) - # NOTE(yujie): Support either remote-ip option name for now. - # However, consider deprecating and then removing --src-ip in - # a future release. remote_group = parser.add_mutually_exclusive_group() remote_group.add_argument( "--remote-ip", metavar="", help=_("Remote IP address block (may use CIDR notation; " - "default for IPv4 rule: 0.0.0.0/0)") - ) - remote_group.add_argument( - "--src-ip", - metavar="", - help=_("Source IP address block (may use CIDR notation; " - "default for IPv4 rule: 0.0.0.0/0)") + "default for IPv4 rule: 0.0.0.0/0)"), ) - # NOTE(yujie): Support either remote-group option name for now. - # However, consider deprecating and then removing --src-group in - # a future release. remote_group.add_argument( "--remote-group", metavar="", - help=_("Remote security group (name or ID)") + help=_("Remote security group (name or ID)"), + ) + # Handle deprecated options + # NOTE(dtroyer): --src-ip and --src-group were deprecated in Nov 2016. + # Do not remove before 4.x release or Nov 2017. + remote_group.add_argument( + "--src-ip", + metavar="", + help=argparse.SUPPRESS, ) remote_group.add_argument( "--src-group", metavar="", - help=_("Source security group (name or ID)") + help=argparse.SUPPRESS, ) return parser @@ -302,16 +302,31 @@ def take_action_network(self, client, parsed_args): if parsed_args.icmp_code: attrs['port_range_max'] = parsed_args.icmp_code + # NOTE(dtroyer): --src-ip and --src-group were deprecated in Nov 2016. + # Do not remove before 4.x release or Nov 2017. if not (parsed_args.remote_group is None and parsed_args.src_group is None): attrs['remote_group_id'] = client.find_security_group( parsed_args.remote_group or parsed_args.src_group, ignore_missing=False ).id + if parsed_args.src_group: + LOG.warning( + _("The %(old)s option is deprecated, " + "please use %(new)s instead.") % + {'old': '--src-group', 'new': '--remote-group'}, + ) elif not (parsed_args.remote_ip is None and parsed_args.src_ip is None): - attrs['remote_ip_prefix'] = (parsed_args.remote_ip or - parsed_args.src_ip) + attrs['remote_ip_prefix'] = ( + parsed_args.remote_ip or parsed_args.src_ip + ) + if parsed_args.src_ip: + LOG.warning( + _("The %(old)s option is deprecated, " + "please use %(new)s instead.") % + {'old': '--src-ip', 'new': '--remote-ip'}, + ) elif attrs['ethertype'] == 'IPv4': attrs['remote_ip_prefix'] = '0.0.0.0/0' attrs['security_group_id'] = security_group_id @@ -340,6 +355,9 @@ def take_action_compute(self, client, parsed_args): from_port, to_port = -1, -1 else: from_port, to_port = parsed_args.dst_port + + # NOTE(dtroyer): --src-ip and --src-group were deprecated in Nov 2016. + # Do not remove before 4.x release or Nov 2017. remote_ip = None if not (parsed_args.remote_group is None and parsed_args.src_group is None): @@ -347,9 +365,21 @@ def take_action_compute(self, client, parsed_args): client.security_groups, parsed_args.remote_group or parsed_args.src_group, ).id + if parsed_args.src_group: + LOG.warning( + _("The %(old)s option is deprecated, " + "please use %(new)s instead.") % + {'old': '--src-group', 'new': '--remote-group'}, + ) if not (parsed_args.remote_ip is None and parsed_args.src_ip is None): remote_ip = parsed_args.remote_ip or parsed_args.src_ip + if parsed_args.src_ip: + LOG.warning( + _("The %(old)s option is deprecated, " + "please use %(new)s instead.") % + {'old': '--src-ip', 'new': '--remote-ip'}, + ) else: remote_ip = '0.0.0.0/0' obj = client.security_group_rules.create( diff --git a/releasenotes/notes/bug-1637365-b90cdcc05ffc7d3a.yaml b/releasenotes/notes/bug-1637365-b90cdcc05ffc7d3a.yaml index 2b8e6c16bc..b2a169d71c 100644 --- a/releasenotes/notes/bug-1637365-b90cdcc05ffc7d3a.yaml +++ b/releasenotes/notes/bug-1637365-b90cdcc05ffc7d3a.yaml @@ -1,7 +1,8 @@ upgrade: - - - Changed the ``security group rule create`` command ``--src-ip`` - option to ``--remote-ip``, ``--src-group`` option to ``--remote-group``. - Using the ``--src-ip`` ``--src-group`` option is still supported, but - is no longer documented and may be deprecated in a future release. + - | + Rename the ``--src-group`` and ``--src-ip`` options in the + ``security group rule create`` command to ``--remote-group`` + and ``--remote-ip``. + The ``--src-group`` and ``--src-ip`` options are deprecated but still + supported, and will be removed in a future release. [Bug `1637365 `_] From ad5ae83a34e555dabeb2761d3d8f3c7f1dbac2f8 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Tue, 15 Nov 2016 15:27:52 -0600 Subject: [PATCH 1346/3095] 3.4.0 release note cleanup Change-Id: I4023e80da43e0cb28150db2892f301d141d63ef8 --- .../backup_list_all-projects_option-4bb23e0b9b075cac.yaml | 4 ++-- .../bp-neutron-client-descriptions-a80902b4295843cf.yaml | 3 --- .../bp-neutron-client-descriptions-b65dd776f78b5a73.yaml | 3 +++ releasenotes/notes/bug-1518059-e2dbe6e4b2473f10.yaml | 6 ++++++ releasenotes/notes/bug-1613533-93279179c6f70117.yaml | 2 +- releasenotes/notes/bug-1631471-beb0a1c9b4a932cb.yaml | 5 +++++ releasenotes/notes/bug-1634672-7ce577f3adc34eed.yaml | 6 ++++++ releasenotes/notes/bug-1637945-f361c834381d409c.yaml | 3 ++- releasenotes/notes/cliff-2.3.0-7ead18fae9ceea80.yaml | 7 +++++++ 9 files changed, 32 insertions(+), 7 deletions(-) create mode 100644 releasenotes/notes/bug-1518059-e2dbe6e4b2473f10.yaml create mode 100644 releasenotes/notes/bug-1631471-beb0a1c9b4a932cb.yaml create mode 100644 releasenotes/notes/bug-1634672-7ce577f3adc34eed.yaml create mode 100644 releasenotes/notes/cliff-2.3.0-7ead18fae9ceea80.yaml diff --git a/releasenotes/notes/backup_list_all-projects_option-4bb23e0b9b075cac.yaml b/releasenotes/notes/backup_list_all-projects_option-4bb23e0b9b075cac.yaml index 5d379f64ea..5a7defaf85 100644 --- a/releasenotes/notes/backup_list_all-projects_option-4bb23e0b9b075cac.yaml +++ b/releasenotes/notes/backup_list_all-projects_option-4bb23e0b9b075cac.yaml @@ -1,4 +1,4 @@ --- features: - - The ``openstack volume backup list`` command now supports the - ``all-projects`` option to list volume backups accross all projects. + - Add ``--all-projects`` option to the ``volume backup list`` command + to list volume backups accross all projects. diff --git a/releasenotes/notes/bp-neutron-client-descriptions-a80902b4295843cf.yaml b/releasenotes/notes/bp-neutron-client-descriptions-a80902b4295843cf.yaml index 3135d0d73c..83a0090251 100644 --- a/releasenotes/notes/bp-neutron-client-descriptions-a80902b4295843cf.yaml +++ b/releasenotes/notes/bp-neutron-client-descriptions-a80902b4295843cf.yaml @@ -7,6 +7,3 @@ features: Add ``--description`` option to ``router set`` and ``router create`` commands. [Blueprint :oscbp:`network-commands-options`] - | - Adds ``--description`` option to ``security group rule create``. - [Blueprint :oscbp:`network-commands-options`] diff --git a/releasenotes/notes/bp-neutron-client-descriptions-b65dd776f78b5a73.yaml b/releasenotes/notes/bp-neutron-client-descriptions-b65dd776f78b5a73.yaml index 625a4e76db..6b41ae2791 100644 --- a/releasenotes/notes/bp-neutron-client-descriptions-b65dd776f78b5a73.yaml +++ b/releasenotes/notes/bp-neutron-client-descriptions-b65dd776f78b5a73.yaml @@ -1,5 +1,8 @@ --- features: + - | + Add ``--description`` option to ``security group rule create`` command. + [Blueprint :oscbp:`network-commands-options`] - | Add ``--description`` option to ``port set`` and ``port create`` commands. diff --git a/releasenotes/notes/bug-1518059-e2dbe6e4b2473f10.yaml b/releasenotes/notes/bug-1518059-e2dbe6e4b2473f10.yaml new file mode 100644 index 0000000000..2da9208845 --- /dev/null +++ b/releasenotes/notes/bug-1518059-e2dbe6e4b2473f10.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Fix the ``--block-migration`` and ``--shared-migration`` options for + ``server migrate`` to send the correct values to the Compute API. + [Bug `1518059 `_]: diff --git a/releasenotes/notes/bug-1613533-93279179c6f70117.yaml b/releasenotes/notes/bug-1613533-93279179c6f70117.yaml index 8907041a56..d4c850cd10 100644 --- a/releasenotes/notes/bug-1613533-93279179c6f70117.yaml +++ b/releasenotes/notes/bug-1613533-93279179c6f70117.yaml @@ -3,4 +3,4 @@ features: - | Add ``--ingress``, ``--egress`` and ``--protocol`` options to ``security group rule list`` command. - [Bug `1613533 `_] \ No newline at end of file + [Bug `1613533 `_] diff --git a/releasenotes/notes/bug-1631471-beb0a1c9b4a932cb.yaml b/releasenotes/notes/bug-1631471-beb0a1c9b4a932cb.yaml new file mode 100644 index 0000000000..39145bd656 --- /dev/null +++ b/releasenotes/notes/bug-1631471-beb0a1c9b4a932cb.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Fix ``router unset --route`` to correctly removed routes. + [Bug `1631471 `_] diff --git a/releasenotes/notes/bug-1634672-7ce577f3adc34eed.yaml b/releasenotes/notes/bug-1634672-7ce577f3adc34eed.yaml new file mode 100644 index 0000000000..904512a286 --- /dev/null +++ b/releasenotes/notes/bug-1634672-7ce577f3adc34eed.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Fix ``--no-allocation-pool`` option for ``subnet set`` command to + send the correct value to the Network API. + [Bug `1518059 `_]: diff --git a/releasenotes/notes/bug-1637945-f361c834381d409c.yaml b/releasenotes/notes/bug-1637945-f361c834381d409c.yaml index da61f48a85..203c2ee919 100644 --- a/releasenotes/notes/bug-1637945-f361c834381d409c.yaml +++ b/releasenotes/notes/bug-1637945-f361c834381d409c.yaml @@ -1,5 +1,6 @@ --- features: - | - Add ``--name``, ``--enable``, ``--disable`` to ``os router list`` + Add ``--name``, ``--enable``, ``--disable`` options to + ``router list`` command. [Bug `1637945 `_] diff --git a/releasenotes/notes/cliff-2.3.0-7ead18fae9ceea80.yaml b/releasenotes/notes/cliff-2.3.0-7ead18fae9ceea80.yaml new file mode 100644 index 0000000000..bcff8893cd --- /dev/null +++ b/releasenotes/notes/cliff-2.3.0-7ead18fae9ceea80.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + Cliff 2.3.0: The shell formatter would emit invalid shell variable + names for field names that contain colons ('``:``') and dashes ('``-``'), + these are now replaced by underscores ('``_``'). + [Bug `1616323 `_] From 6eef3277f5b3ebcceded9963627b78b2307621a9 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 16 Nov 2016 22:16:41 +0000 Subject: [PATCH 1347/3095] Updated from global requirements Change-Id: Ie6b85f717a7fe5bce2f6a711168dba894940cde9 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e72f687473..c31670bea7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ pbr>=1.8 # Apache-2.0 six>=1.9.0 # MIT Babel>=2.3.4 # BSD -cliff>=2.2.0 # Apache-2.0 +cliff>=2.3.0 # Apache-2.0 keystoneauth1>=2.14.0 # Apache-2.0 openstacksdk>=0.9.10 # Apache-2.0 osc-lib>=1.2.0 # Apache-2.0 From 0ef8535036c3739d798fd5627ae142d121f20d42 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Sun, 13 Nov 2016 09:42:09 -0500 Subject: [PATCH 1348/3095] translate all command help strings Leverage the new cliff command class attribute (_description) to get the help of a command, this allows us to mark strings for translation. We could not do this before since the help was grabbed from the docstring. This also depends on a new release of cliff and a bump to the minimum level in osc's requirements. Closes-Bug: 1636209 Depends-On: Id915f6aa7d95a0ff3dc6e2ceaac5decb3f3bf0da Change-Id: I8673080bb5625e8e3c499feaefd42dfc7121e96f --- openstackclient/common/availability_zone.py | 2 +- openstackclient/common/configuration.py | 2 +- openstackclient/common/extension.py | 2 +- openstackclient/common/limits.py | 2 +- openstackclient/common/module.py | 4 +- openstackclient/common/quota.py | 4 +- openstackclient/compute/v2/agent.py | 8 +-- openstackclient/compute/v2/aggregate.py | 16 ++--- openstackclient/compute/v2/console.py | 4 +- openstackclient/compute/v2/fixedip.py | 4 +- openstackclient/compute/v2/flavor.py | 12 ++-- openstackclient/compute/v2/floatingip.py | 4 +- openstackclient/compute/v2/host.py | 7 +- openstackclient/compute/v2/hypervisor.py | 4 +- .../compute/v2/hypervisor_stats.py | 4 +- openstackclient/compute/v2/keypair.py | 8 +-- openstackclient/compute/v2/server.py | 65 ++++++++++--------- openstackclient/compute/v2/server_backup.py | 2 +- openstackclient/compute/v2/server_group.py | 8 +-- openstackclient/compute/v2/server_image.py | 2 +- openstackclient/compute/v2/service.py | 6 +- openstackclient/compute/v2/usage.py | 4 +- openstackclient/identity/v2_0/catalog.py | 4 +- openstackclient/identity/v2_0/ec2creds.py | 8 +-- openstackclient/identity/v2_0/endpoint.py | 8 +-- openstackclient/identity/v2_0/project.py | 12 ++-- openstackclient/identity/v2_0/role.py | 14 ++-- .../identity/v2_0/role_assignment.py | 2 +- openstackclient/identity/v2_0/service.py | 8 +-- openstackclient/identity/v2_0/token.py | 4 +- openstackclient/identity/v2_0/user.py | 10 +-- openstackclient/identity/v3/catalog.py | 4 +- openstackclient/identity/v3/consumer.py | 10 +-- openstackclient/identity/v3/credential.py | 10 +-- openstackclient/identity/v3/domain.py | 10 +-- openstackclient/identity/v3/ec2creds.py | 8 +-- openstackclient/identity/v3/endpoint.py | 10 +-- .../identity/v3/federation_protocol.py | 10 +-- openstackclient/identity/v3/group.py | 16 ++--- .../identity/v3/identity_provider.py | 10 +-- openstackclient/identity/v3/mapping.py | 12 ++-- openstackclient/identity/v3/policy.py | 10 +-- openstackclient/identity/v3/project.py | 10 +-- openstackclient/identity/v3/region.py | 10 +-- openstackclient/identity/v3/role.py | 16 +++-- .../identity/v3/role_assignment.py | 4 +- openstackclient/identity/v3/service.py | 10 +-- .../identity/v3/service_provider.py | 10 +-- openstackclient/identity/v3/token.py | 10 +-- openstackclient/identity/v3/trust.py | 8 +-- openstackclient/identity/v3/unscoped_saml.py | 4 +- openstackclient/identity/v3/user.py | 12 ++-- openstackclient/image/v1/image.py | 12 ++-- openstackclient/image/v2/image.py | 20 +++--- openstackclient/network/v2/address_scope.py | 10 +-- openstackclient/network/v2/floating_ip.py | 16 ++--- .../network/v2/floating_ip_pool.py | 4 +- openstackclient/network/v2/ip_availability.py | 4 +- openstackclient/network/v2/network.py | 10 +-- openstackclient/network/v2/network_agent.py | 8 +-- .../network/v2/network_qos_policy.py | 10 +-- openstackclient/network/v2/network_rbac.py | 10 +-- openstackclient/network/v2/network_segment.py | 10 +-- openstackclient/network/v2/port.py | 12 ++-- openstackclient/network/v2/router.py | 20 +++--- openstackclient/network/v2/security_group.py | 10 +-- .../network/v2/security_group_rule.py | 8 +-- openstackclient/network/v2/subnet.py | 12 ++-- openstackclient/network/v2/subnet_pool.py | 13 ++-- openstackclient/object/v1/account.py | 6 +- openstackclient/object/v1/container.py | 14 ++-- openstackclient/object/v1/object.py | 14 ++-- openstackclient/volume/v1/backup.py | 20 +++--- openstackclient/volume/v1/qos_specs.py | 16 ++--- openstackclient/volume/v1/service.py | 4 +- openstackclient/volume/v1/snapshot.py | 12 ++-- openstackclient/volume/v1/volume.py | 14 ++-- .../volume/v1/volume_transfer_request.py | 10 +-- openstackclient/volume/v1/volume_type.py | 12 ++-- openstackclient/volume/v2/backup.py | 22 +++---- .../volume/v2/consistency_group.py | 2 +- openstackclient/volume/v2/qos_specs.py | 16 ++--- openstackclient/volume/v2/service.py | 4 +- openstackclient/volume/v2/snapshot.py | 12 ++-- openstackclient/volume/v2/volume.py | 14 ++-- .../volume/v2/volume_transfer_request.py | 10 +-- openstackclient/volume/v2/volume_type.py | 12 ++-- 87 files changed, 429 insertions(+), 422 deletions(-) diff --git a/openstackclient/common/availability_zone.py b/openstackclient/common/availability_zone.py index 63c55370b0..b2385ef743 100644 --- a/openstackclient/common/availability_zone.py +++ b/openstackclient/common/availability_zone.py @@ -88,7 +88,7 @@ def _xform_network_availability_zone(az): class ListAvailabilityZone(command.Lister): - """List availability zones and their status""" + _description = _("List availability zones and their status") def get_parser(self, prog_name): parser = super(ListAvailabilityZone, self).get_parser(prog_name) diff --git a/openstackclient/common/configuration.py b/openstackclient/common/configuration.py index 016e91917f..57825bb056 100644 --- a/openstackclient/common/configuration.py +++ b/openstackclient/common/configuration.py @@ -23,7 +23,7 @@ class ShowConfiguration(command.ShowOne): - """Display configuration details""" + _description = _("Display configuration details") def get_parser(self, prog_name): parser = super(ShowConfiguration, self).get_parser(prog_name) diff --git a/openstackclient/common/extension.py b/openstackclient/common/extension.py index de48001609..991f3afcd5 100644 --- a/openstackclient/common/extension.py +++ b/openstackclient/common/extension.py @@ -28,7 +28,7 @@ class ListExtension(command.Lister): - """List API extensions""" + _description = _("List API extensions") def get_parser(self, prog_name): parser = super(ListExtension, self).get_parser(prog_name) diff --git a/openstackclient/common/limits.py b/openstackclient/common/limits.py index f7aa82f62c..957f1d0212 100644 --- a/openstackclient/common/limits.py +++ b/openstackclient/common/limits.py @@ -25,7 +25,7 @@ class ShowLimits(command.Lister): - """Show compute and block storage limits""" + _description = _("Show compute and block storage limits") def get_parser(self, prog_name): parser = super(ShowLimits, self).get_parser(prog_name) diff --git a/openstackclient/common/module.py b/openstackclient/common/module.py index 7c5fcd55a2..15719a30e8 100644 --- a/openstackclient/common/module.py +++ b/openstackclient/common/module.py @@ -25,7 +25,7 @@ class ListCommand(command.Lister): - """List recognized commands by group""" + _description = _("List recognized commands by group") auth_required = False @@ -53,7 +53,7 @@ def take_action(self, parsed_args): class ListModule(command.ShowOne): - """List module versions""" + _description = _("List module versions") auth_required = False diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index 8f099cc927..58368c5620 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -80,7 +80,7 @@ class SetQuota(command.Command): - """Set quotas for project or class""" + _description = _("Set quotas for project or class") def _build_options_list(self): if self.app.client_manager.is_network_endpoint_enabled(): @@ -186,7 +186,7 @@ def take_action(self, parsed_args): class ShowQuota(command.ShowOne): - """Show quotas for project or class""" + _description = _("Show quotas for project or class") def get_parser(self, prog_name): parser = super(ShowQuota, self).get_parser(prog_name) diff --git a/openstackclient/compute/v2/agent.py b/openstackclient/compute/v2/agent.py index 76c1b3b756..151dcc1e82 100644 --- a/openstackclient/compute/v2/agent.py +++ b/openstackclient/compute/v2/agent.py @@ -29,7 +29,7 @@ class CreateAgent(command.ShowOne): - """Create compute agent""" + _description = _("Create compute agent") def get_parser(self, prog_name): parser = super(CreateAgent, self).get_parser(prog_name) @@ -81,7 +81,7 @@ def take_action(self, parsed_args): class DeleteAgent(command.Command): - """Delete compute agent(s)""" + _description = _("Delete compute agent(s)") def get_parser(self, prog_name): parser = super(DeleteAgent, self).get_parser(prog_name) @@ -112,7 +112,7 @@ def take_action(self, parsed_args): class ListAgent(command.Lister): - """List compute agents""" + _description = _("List compute agents") def get_parser(self, prog_name): parser = super(ListAgent, self).get_parser(prog_name) @@ -142,7 +142,7 @@ def take_action(self, parsed_args): class SetAgent(command.Command): - """Set compute agent properties""" + _description = _("Set compute agent properties") def get_parser(self, prog_name): parser = super(SetAgent, self).get_parser(prog_name) diff --git a/openstackclient/compute/v2/aggregate.py b/openstackclient/compute/v2/aggregate.py index 58d529e9f8..76ba5cc662 100644 --- a/openstackclient/compute/v2/aggregate.py +++ b/openstackclient/compute/v2/aggregate.py @@ -31,7 +31,7 @@ class AddAggregateHost(command.ShowOne): - """Add host to aggregate""" + _description = _("Add host to aggregate") def get_parser(self, prog_name): parser = super(AddAggregateHost, self).get_parser(prog_name) @@ -62,7 +62,7 @@ def take_action(self, parsed_args): class CreateAggregate(command.ShowOne): - """Create a new aggregate""" + _description = _("Create a new aggregate") def get_parser(self, prog_name): parser = super(CreateAggregate, self).get_parser(prog_name) @@ -105,7 +105,7 @@ def take_action(self, parsed_args): class DeleteAggregate(command.Command): - """Delete existing aggregate(s)""" + _description = _("Delete existing aggregate(s)") def get_parser(self, prog_name): parser = super(DeleteAggregate, self).get_parser(prog_name) @@ -139,7 +139,7 @@ def take_action(self, parsed_args): class ListAggregate(command.Lister): - """List all aggregates""" + _description = _("List all aggregates") def get_parser(self, prog_name): parser = super(ListAggregate, self).get_parser(prog_name) @@ -188,7 +188,7 @@ def take_action(self, parsed_args): class RemoveAggregateHost(command.ShowOne): - """Remove host from aggregate""" + _description = _("Remove host from aggregate") def get_parser(self, prog_name): parser = super(RemoveAggregateHost, self).get_parser(prog_name) @@ -222,7 +222,7 @@ def take_action(self, parsed_args): class SetAggregate(command.Command): - """Set aggregate properties""" + _description = _("Set aggregate properties") def get_parser(self, prog_name): parser = super(SetAggregate, self).get_parser(prog_name) @@ -298,7 +298,7 @@ def take_action(self, parsed_args): class ShowAggregate(command.ShowOne): - """Display aggregate details""" + _description = _("Display aggregate details") def get_parser(self, prog_name): parser = super(ShowAggregate, self).get_parser(prog_name) @@ -334,7 +334,7 @@ def take_action(self, parsed_args): class UnsetAggregate(command.Command): - """Unset aggregate properties""" + _description = _("Unset aggregate properties") def get_parser(self, prog_name): parser = super(UnsetAggregate, self).get_parser(prog_name) diff --git a/openstackclient/compute/v2/console.py b/openstackclient/compute/v2/console.py index 02be99d527..358df50182 100644 --- a/openstackclient/compute/v2/console.py +++ b/openstackclient/compute/v2/console.py @@ -26,7 +26,7 @@ class ShowConsoleLog(command.Command): - """Show server's console output""" + _description = _("Show server's console output") def get_parser(self, prog_name): parser = super(ShowConsoleLog, self).get_parser(prog_name) @@ -64,7 +64,7 @@ def take_action(self, parsed_args): class ShowConsoleURL(command.ShowOne): - """Show server's remote console URL""" + _description = _("Show server's remote console URL") def get_parser(self, prog_name): parser = super(ShowConsoleURL, self).get_parser(prog_name) diff --git a/openstackclient/compute/v2/fixedip.py b/openstackclient/compute/v2/fixedip.py index c14d29fa96..0c0b619e8e 100644 --- a/openstackclient/compute/v2/fixedip.py +++ b/openstackclient/compute/v2/fixedip.py @@ -24,7 +24,7 @@ class AddFixedIP(command.Command): - """Add fixed IP address to server""" + _description = _("Add fixed IP address to server") # TODO(tangchen): Remove this class and ``ip fixed add`` command # two cycles after Mitaka. @@ -64,7 +64,7 @@ def take_action(self, parsed_args): class RemoveFixedIP(command.Command): - """Remove fixed IP address from server""" + _description = _("Remove fixed IP address from server") # TODO(tangchen): Remove this class and ``ip fixed remove`` command # two cycles after Mitaka. diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index b3f09ce586..f20d154b27 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -49,7 +49,7 @@ def _find_flavor(compute_client, flavor): class CreateFlavor(command.ShowOne): - """Create new flavor""" + _description = _("Create new flavor") def get_parser(self, prog_name): parser = super(CreateFlavor, self).get_parser(prog_name) @@ -186,7 +186,7 @@ def take_action(self, parsed_args): class DeleteFlavor(command.Command): - """Delete flavor(s)""" + _description = _("Delete flavor(s)") def get_parser(self, prog_name): parser = super(DeleteFlavor, self).get_parser(prog_name) @@ -219,7 +219,7 @@ def take_action(self, parsed_args): class ListFlavor(command.Lister): - """List flavors""" + _description = _("List flavors") def get_parser(self, prog_name): parser = super(ListFlavor, self).get_parser(prog_name) @@ -303,7 +303,7 @@ def take_action(self, parsed_args): class SetFlavor(command.Command): - """Set flavor properties""" + _description = _("Set flavor properties") def get_parser(self, prog_name): parser = super(SetFlavor, self).get_parser(prog_name) @@ -366,7 +366,7 @@ def take_action(self, parsed_args): class ShowFlavor(command.ShowOne): - """Display flavor details""" + _description = _("Display flavor details") def get_parser(self, prog_name): parser = super(ShowFlavor, self).get_parser(prog_name) @@ -409,7 +409,7 @@ def take_action(self, parsed_args): class UnsetFlavor(command.Command): - """Unset flavor properties""" + _description = _("Unset flavor properties") def get_parser(self, prog_name): parser = super(UnsetFlavor, self).get_parser(prog_name) diff --git a/openstackclient/compute/v2/floatingip.py b/openstackclient/compute/v2/floatingip.py index 8398ea57db..69595bed2f 100644 --- a/openstackclient/compute/v2/floatingip.py +++ b/openstackclient/compute/v2/floatingip.py @@ -24,7 +24,7 @@ class AddFloatingIP(command.Command): - """Add floating IP address to server""" + _description = _("Add floating IP address to server") # TODO(tangchen): Remove this class and ``ip floating add`` command # two cycles after Mitaka. @@ -61,7 +61,7 @@ def take_action(self, parsed_args): class RemoveFloatingIP(command.Command): - """Remove floating IP address from server""" + _description = _("Remove floating IP address from server") # TODO(tangchen): Remove this class and ``ip floating remove`` command # two cycles after Mitaka. diff --git a/openstackclient/compute/v2/host.py b/openstackclient/compute/v2/host.py index 4785377e27..a495b367aa 100644 --- a/openstackclient/compute/v2/host.py +++ b/openstackclient/compute/v2/host.py @@ -22,7 +22,7 @@ class ListHost(command.Lister): - """List hosts""" + _description = _("List hosts") def get_parser(self, prog_name): parser = super(ListHost, self).get_parser(prog_name) @@ -48,7 +48,8 @@ def take_action(self, parsed_args): class SetHost(command.Command): - """Set host properties""" + _description = _("Set host properties") + def get_parser(self, prog_name): parser = super(SetHost, self).get_parser(prog_name) parser.add_argument( @@ -107,7 +108,7 @@ def take_action(self, parsed_args): class ShowHost(command.Lister): - """Display host details""" + _description = _("Display host details") def get_parser(self, prog_name): parser = super(ShowHost, self).get_parser(prog_name) diff --git a/openstackclient/compute/v2/hypervisor.py b/openstackclient/compute/v2/hypervisor.py index 69b5d1370b..406aa9175b 100644 --- a/openstackclient/compute/v2/hypervisor.py +++ b/openstackclient/compute/v2/hypervisor.py @@ -26,7 +26,7 @@ class ListHypervisor(command.Lister): - """List hypervisors""" + _description = _("List hypervisors") def get_parser(self, prog_name): parser = super(ListHypervisor, self).get_parser(prog_name) @@ -66,7 +66,7 @@ def take_action(self, parsed_args): class ShowHypervisor(command.ShowOne): - """Display hypervisor details""" + _description = _("Display hypervisor details") def get_parser(self, prog_name): parser = super(ShowHypervisor, self).get_parser(prog_name) diff --git a/openstackclient/compute/v2/hypervisor_stats.py b/openstackclient/compute/v2/hypervisor_stats.py index c6fd2992c5..b0413005b6 100644 --- a/openstackclient/compute/v2/hypervisor_stats.py +++ b/openstackclient/compute/v2/hypervisor_stats.py @@ -17,9 +17,11 @@ from osc_lib.command import command import six +from openstackclient.i18n import _ + class ShowHypervisorStats(command.ShowOne): - """Display hypervisor stats details""" + _description = _("Display hypervisor stats details") def take_action(self, parsed_args): compute_client = self.app.client_manager.compute diff --git a/openstackclient/compute/v2/keypair.py b/openstackclient/compute/v2/keypair.py index d5c682f455..a63cbfecc0 100644 --- a/openstackclient/compute/v2/keypair.py +++ b/openstackclient/compute/v2/keypair.py @@ -32,7 +32,7 @@ class CreateKeypair(command.ShowOne): - """Create new public or private key for server ssh access""" + _description = _("Create new public or private key for server ssh access") def get_parser(self, prog_name): parser = super(CreateKeypair, self).get_parser(prog_name) @@ -83,7 +83,7 @@ def take_action(self, parsed_args): class DeleteKeypair(command.Command): - """Delete public or private key(s)""" + _description = _("Delete public or private key(s)") def get_parser(self, prog_name): parser = super(DeleteKeypair, self).get_parser(prog_name) @@ -117,7 +117,7 @@ def take_action(self, parsed_args): class ListKeypair(command.Lister): - """List key fingerprints""" + _description = _("List key fingerprints") def take_action(self, parsed_args): compute_client = self.app.client_manager.compute @@ -134,7 +134,7 @@ def take_action(self, parsed_args): class ShowKeypair(command.ShowOne): - """Display key details""" + _description = _("Display key details") def get_parser(self, prog_name): parser = super(ShowKeypair, self).get_parser(prog_name) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index df46c7df64..48d8b2d0d9 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -175,7 +175,7 @@ def _show_progress(progress): class AddFixedIP(command.Command): - """Add fixed IP address to server""" + _description = _("Add fixed IP address to server") def get_parser(self, prog_name): parser = super(AddFixedIP, self).get_parser(prog_name) @@ -205,7 +205,7 @@ def take_action(self, parsed_args): class AddFloatingIP(command.Command): - """Add floating IP address to server""" + _description = _("Add floating IP address to server") def get_parser(self, prog_name): parser = super(AddFloatingIP, self).get_parser(prog_name) @@ -232,7 +232,7 @@ def take_action(self, parsed_args): class AddServerSecurityGroup(command.Command): - """Add security group to server""" + _description = _("Add security group to server") def get_parser(self, prog_name): parser = super(AddServerSecurityGroup, self).get_parser(prog_name) @@ -264,7 +264,7 @@ def take_action(self, parsed_args): class AddServerVolume(command.Command): - """Add volume to server""" + _description = _("Add volume to server") def get_parser(self, prog_name): parser = super(AddServerVolume, self).get_parser(prog_name) @@ -306,7 +306,7 @@ def take_action(self, parsed_args): class CreateServer(command.ShowOne): - """Create a new server""" + _description = _("Create a new server") def get_parser(self, prog_name): parser = super(CreateServer, self).get_parser(prog_name) @@ -642,7 +642,7 @@ def take_action(self, parsed_args): class DeleteServer(command.Command): - """Delete server(s)""" + _description = _("Delete server(s)") def get_parser(self, prog_name): parser = super(DeleteServer, self).get_parser(prog_name) @@ -680,7 +680,7 @@ def take_action(self, parsed_args): class ListServer(command.Lister): - """List servers""" + _description = _("List servers") def get_parser(self, prog_name): parser = super(ListServer, self).get_parser(prog_name) @@ -923,7 +923,8 @@ def take_action(self, parsed_args): class LockServer(command.Command): - """Lock server(s). A non-admin user will not be able to execute actions""" + _description = _("Lock server(s). A non-admin user will not be able to " + "execute actions") def get_parser(self, prog_name): parser = super(LockServer, self).get_parser(prog_name) @@ -956,7 +957,7 @@ def take_action(self, parsed_args): # then adding the groups doesn't seem to work class MigrateServer(command.Command): - """Migrate server to different host""" + _description = _("Migrate server to different host") def get_parser(self, prog_name): parser = super(MigrateServer, self).get_parser(prog_name) @@ -1038,7 +1039,7 @@ def take_action(self, parsed_args): class PauseServer(command.Command): - """Pause server(s)""" + _description = _("Pause server(s)") def get_parser(self, prog_name): parser = super(PauseServer, self).get_parser(prog_name) @@ -1060,7 +1061,7 @@ def take_action(self, parsed_args): class RebootServer(command.Command): - """Perform a hard or soft server reboot""" + _description = _("Perform a hard or soft server reboot") def get_parser(self, prog_name): parser = super(RebootServer, self).get_parser(prog_name) @@ -1114,7 +1115,7 @@ def take_action(self, parsed_args): class RebuildServer(command.ShowOne): - """Rebuild server""" + _description = _("Rebuild server") def get_parser(self, prog_name): parser = super(RebuildServer, self).get_parser(prog_name) @@ -1170,7 +1171,7 @@ def take_action(self, parsed_args): class RemoveFixedIP(command.Command): - """Remove fixed IP address from server""" + _description = _("Remove fixed IP address from server") def get_parser(self, prog_name): parser = super(RemoveFixedIP, self).get_parser(prog_name) @@ -1197,7 +1198,7 @@ def take_action(self, parsed_args): class RemoveFloatingIP(command.Command): - """Remove floating IP address from server""" + _description = _("Remove floating IP address from server") def get_parser(self, prog_name): parser = super(RemoveFloatingIP, self).get_parser(prog_name) @@ -1225,7 +1226,7 @@ def take_action(self, parsed_args): class RemoveServerSecurityGroup(command.Command): - """Remove security group from server""" + _description = _("Remove security group from server") def get_parser(self, prog_name): parser = super(RemoveServerSecurityGroup, self).get_parser(prog_name) @@ -1257,7 +1258,7 @@ def take_action(self, parsed_args): class RemoveServerVolume(command.Command): - """Remove volume from server""" + _description = _("Remove volume from server") def get_parser(self, prog_name): parser = super(RemoveServerVolume, self).get_parser(prog_name) @@ -1293,7 +1294,7 @@ def take_action(self, parsed_args): class RescueServer(command.ShowOne): - """Put server in rescue mode""" + _description = _("Put server in rescue mode") def get_parser(self, prog_name): parser = super(RescueServer, self).get_parser(prog_name) @@ -1315,7 +1316,7 @@ def take_action(self, parsed_args): class ResizeServer(command.Command): - """Scale server to a new flavor""" + _description = _("Scale server to a new flavor") def get_parser(self, prog_name): parser = super(ResizeServer, self).get_parser(prog_name) @@ -1380,7 +1381,7 @@ def take_action(self, parsed_args): class RestoreServer(command.Command): - """Restore server(s)""" + _description = _("Restore server(s)") def get_parser(self, prog_name): parser = super(RestoreServer, self).get_parser(prog_name) @@ -1402,7 +1403,7 @@ def take_action(self, parsed_args): class ResumeServer(command.Command): - """Resume server(s)""" + _description = _("Resume server(s)") def get_parser(self, prog_name): parser = super(ResumeServer, self).get_parser(prog_name) @@ -1425,7 +1426,7 @@ def take_action(self, parsed_args): class SetServer(command.Command): - """Set server properties""" + _description = _("Set server properties") def get_parser(self, prog_name): parser = super(SetServer, self).get_parser(prog_name) @@ -1490,7 +1491,7 @@ def take_action(self, parsed_args): class ShelveServer(command.Command): - """Shelve server(s)""" + _description = _("Shelve server(s)") def get_parser(self, prog_name): parser = super(ShelveServer, self).get_parser(prog_name) @@ -1512,7 +1513,7 @@ def take_action(self, parsed_args): class ShowServer(command.ShowOne): - """Show server details""" + _description = _("Show server details") def get_parser(self, prog_name): parser = super(ShowServer, self).get_parser(prog_name) @@ -1546,7 +1547,7 @@ def take_action(self, parsed_args): class SshServer(command.Command): - """SSH to server""" + _description = _("SSH to server") def get_parser(self, prog_name): parser = super(SshServer, self).get_parser(prog_name) @@ -1690,7 +1691,7 @@ def take_action(self, parsed_args): class StartServer(command.Command): - """Start server(s).""" + _description = _("Start server(s).") def get_parser(self, prog_name): parser = super(StartServer, self).get_parser(prog_name) @@ -1712,7 +1713,7 @@ def take_action(self, parsed_args): class StopServer(command.Command): - """Stop server(s).""" + _description = _("Stop server(s).") def get_parser(self, prog_name): parser = super(StopServer, self).get_parser(prog_name) @@ -1734,7 +1735,7 @@ def take_action(self, parsed_args): class SuspendServer(command.Command): - """Suspend server(s)""" + _description = _("Suspend server(s)") def get_parser(self, prog_name): parser = super(SuspendServer, self).get_parser(prog_name) @@ -1757,7 +1758,7 @@ def take_action(self, parsed_args): class UnlockServer(command.Command): - """Unlock server(s)""" + _description = _("Unlock server(s)") def get_parser(self, prog_name): parser = super(UnlockServer, self).get_parser(prog_name) @@ -1780,7 +1781,7 @@ def take_action(self, parsed_args): class UnpauseServer(command.Command): - """Unpause server(s)""" + _description = _("Unpause server(s)") def get_parser(self, prog_name): parser = super(UnpauseServer, self).get_parser(prog_name) @@ -1803,7 +1804,7 @@ def take_action(self, parsed_args): class UnrescueServer(command.Command): - """Restore server from rescue mode""" + _description = _("Restore server from rescue mode") def get_parser(self, prog_name): parser = super(UnrescueServer, self).get_parser(prog_name) @@ -1824,7 +1825,7 @@ def take_action(self, parsed_args): class UnsetServer(command.Command): - """Unset server properties""" + _description = _("Unset server properties") def get_parser(self, prog_name): parser = super(UnsetServer, self).get_parser(prog_name) @@ -1858,7 +1859,7 @@ def take_action(self, parsed_args): class UnshelveServer(command.Command): - """Unshelve server(s)""" + _description = _("Unshelve server(s)") def get_parser(self, prog_name): parser = super(UnshelveServer, self).get_parser(prog_name) diff --git a/openstackclient/compute/v2/server_backup.py b/openstackclient/compute/v2/server_backup.py index c0e2e5ee4c..ddcf91010e 100644 --- a/openstackclient/compute/v2/server_backup.py +++ b/openstackclient/compute/v2/server_backup.py @@ -33,7 +33,7 @@ def _show_progress(progress): class CreateServerBackup(command.ShowOne): - """Create a server backup image""" + _description = _("Create a server backup image") IMAGE_API_VERSIONS = { "1": "openstackclient.image.v1.image", diff --git a/openstackclient/compute/v2/server_group.py b/openstackclient/compute/v2/server_group.py index d51b1ec23e..6bcfc6aef7 100644 --- a/openstackclient/compute/v2/server_group.py +++ b/openstackclient/compute/v2/server_group.py @@ -43,7 +43,7 @@ def _get_columns(info): class CreateServerGroup(command.ShowOne): - """Create a new server group.""" + _description = _("Create a new server group.") def get_parser(self, prog_name): parser = super(CreateServerGroup, self).get_parser(prog_name) @@ -77,7 +77,7 @@ def take_action(self, parsed_args): class DeleteServerGroup(command.Command): - """Delete existing server group(s).""" + _description = _("Delete existing server group(s).") def get_parser(self, prog_name): parser = super(DeleteServerGroup, self).get_parser(prog_name) @@ -112,7 +112,7 @@ def take_action(self, parsed_args): class ListServerGroup(command.Lister): - """List all server groups.""" + _description = _("List all server groups.") def get_parser(self, prog_name): parser = super(ListServerGroup, self).get_parser(prog_name) @@ -161,7 +161,7 @@ def take_action(self, parsed_args): class ShowServerGroup(command.ShowOne): - """Display server group details.""" + _description = _("Display server group details.") def get_parser(self, prog_name): parser = super(ShowServerGroup, self).get_parser(prog_name) diff --git a/openstackclient/compute/v2/server_image.py b/openstackclient/compute/v2/server_image.py index 285c7fd21d..c66e06747f 100644 --- a/openstackclient/compute/v2/server_image.py +++ b/openstackclient/compute/v2/server_image.py @@ -37,7 +37,7 @@ def _show_progress(progress): class CreateServerImage(command.ShowOne): - """Create a new server disk image from an existing server""" + _description = _("Create a new server disk image from an existing server") IMAGE_API_VERSIONS = { "1": "openstackclient.image.v1.image", diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index 53624f5571..9c384f0589 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.py @@ -29,7 +29,7 @@ class DeleteService(command.Command): - """Delete compute service(s)""" + _description = _("Delete compute service(s)") def get_parser(self, prog_name): parser = super(DeleteService, self).get_parser(prog_name) @@ -61,7 +61,7 @@ def take_action(self, parsed_args): class ListService(command.Lister): - """List compute services""" + _description = _("List compute services") def get_parser(self, prog_name): parser = super(ListService, self).get_parser(prog_name) @@ -115,7 +115,7 @@ def take_action(self, parsed_args): class SetService(command.Command): - """Set compute service properties""" + _description = _("Set compute service properties") def get_parser(self, prog_name): parser = super(SetService, self).get_parser(prog_name) diff --git a/openstackclient/compute/v2/usage.py b/openstackclient/compute/v2/usage.py index 2f35b01b13..89601ae341 100644 --- a/openstackclient/compute/v2/usage.py +++ b/openstackclient/compute/v2/usage.py @@ -26,7 +26,7 @@ class ListUsage(command.Lister): - """List resource usage per project""" + _description = _("List resource usage per project") def get_parser(self, prog_name): parser = super(ListUsage, self).get_parser(prog_name) @@ -115,7 +115,7 @@ def _format_project(project): class ShowUsage(command.ShowOne): - """Show resource usage for a single project""" + _description = _("Show resource usage for a single project") def get_parser(self, prog_name): parser = super(ShowUsage, self).get_parser(prog_name) diff --git a/openstackclient/identity/v2_0/catalog.py b/openstackclient/identity/v2_0/catalog.py index 7a15cf3a58..993bdd5343 100644 --- a/openstackclient/identity/v2_0/catalog.py +++ b/openstackclient/identity/v2_0/catalog.py @@ -43,7 +43,7 @@ def _format_endpoints(eps=None): class ListCatalog(command.Lister): - """List services in the service catalog""" + _description = _("List services in the service catalog") def take_action(self, parsed_args): @@ -66,7 +66,7 @@ def take_action(self, parsed_args): class ShowCatalog(command.ShowOne): - """Display service catalog details""" + _description = _("Display service catalog details") def get_parser(self, prog_name): parser = super(ShowCatalog, self).get_parser(prog_name) diff --git a/openstackclient/identity/v2_0/ec2creds.py b/openstackclient/identity/v2_0/ec2creds.py index 4873cd55fd..2572c4f0bd 100644 --- a/openstackclient/identity/v2_0/ec2creds.py +++ b/openstackclient/identity/v2_0/ec2creds.py @@ -30,7 +30,7 @@ class CreateEC2Creds(command.ShowOne): - """Create EC2 credentials""" + _description = _("Create EC2 credentials") def get_parser(self, prog_name): parser = super(CreateEC2Creds, self).get_parser(prog_name) @@ -86,7 +86,7 @@ def take_action(self, parsed_args): class DeleteEC2Creds(command.Command): - """Delete EC2 credentials""" + _description = _("Delete EC2 credentials") def get_parser(self, prog_name): parser = super(DeleteEC2Creds, self).get_parser(prog_name) @@ -133,7 +133,7 @@ def take_action(self, parsed_args): class ListEC2Creds(command.Lister): - """List EC2 credentials""" + _description = _("List EC2 credentials") def get_parser(self, prog_name): parser = super(ListEC2Creds, self).get_parser(prog_name) @@ -168,7 +168,7 @@ def take_action(self, parsed_args): class ShowEC2Creds(command.ShowOne): - """Display EC2 credentials details""" + _description = _("Display EC2 credentials details") def get_parser(self, prog_name): parser = super(ShowEC2Creds, self).get_parser(prog_name) diff --git a/openstackclient/identity/v2_0/endpoint.py b/openstackclient/identity/v2_0/endpoint.py index 5a3b3186fb..7e0751a7ef 100644 --- a/openstackclient/identity/v2_0/endpoint.py +++ b/openstackclient/identity/v2_0/endpoint.py @@ -30,7 +30,7 @@ class CreateEndpoint(command.ShowOne): - """Create new endpoint""" + _description = _("Create new endpoint") def get_parser(self, prog_name): parser = super(CreateEndpoint, self).get_parser(prog_name) @@ -80,7 +80,7 @@ def take_action(self, parsed_args): class DeleteEndpoint(command.Command): - """Delete endpoint(s)""" + _description = _("Delete endpoint(s)") def get_parser(self, prog_name): parser = super(DeleteEndpoint, self).get_parser(prog_name) @@ -113,7 +113,7 @@ def take_action(self, parsed_args): class ListEndpoint(command.Lister): - """List endpoints""" + _description = _("List endpoints") def get_parser(self, prog_name): parser = super(ListEndpoint, self).get_parser(prog_name) @@ -146,7 +146,7 @@ def take_action(self, parsed_args): class ShowEndpoint(command.ShowOne): - """Display endpoint details""" + _description = _("Display endpoint details") def get_parser(self, prog_name): parser = super(ShowEndpoint, self).get_parser(prog_name) diff --git a/openstackclient/identity/v2_0/project.py b/openstackclient/identity/v2_0/project.py index fc5c920175..8526d6bdf6 100644 --- a/openstackclient/identity/v2_0/project.py +++ b/openstackclient/identity/v2_0/project.py @@ -30,7 +30,7 @@ class CreateProject(command.ShowOne): - """Create new project""" + _description = _("Create new project") def get_parser(self, prog_name): parser = super(CreateProject, self).get_parser(prog_name) @@ -102,7 +102,7 @@ def take_action(self, parsed_args): class DeleteProject(command.Command): - """Delete project(s)""" + _description = _("Delete project(s)") def get_parser(self, prog_name): parser = super(DeleteProject, self).get_parser(prog_name) @@ -126,7 +126,7 @@ def take_action(self, parsed_args): class ListProject(command.Lister): - """List projects""" + _description = _("List projects") def get_parser(self, prog_name): parser = super(ListProject, self).get_parser(prog_name) @@ -152,7 +152,7 @@ def take_action(self, parsed_args): class SetProject(command.Command): - """Set project properties""" + _description = _("Set project properties") def get_parser(self, prog_name): parser = super(SetProject, self).get_parser(prog_name) @@ -221,7 +221,7 @@ def take_action(self, parsed_args): class ShowProject(command.ShowOne): - """Display project details""" + _description = _("Display project details") def get_parser(self, prog_name): parser = super(ShowProject, self).get_parser(prog_name) @@ -279,7 +279,7 @@ def take_action(self, parsed_args): class UnsetProject(command.Command): - """Unset project properties""" + _description = _("Unset project properties") def get_parser(self, prog_name): parser = super(UnsetProject, self).get_parser(prog_name) diff --git a/openstackclient/identity/v2_0/role.py b/openstackclient/identity/v2_0/role.py index b4b67bad29..0a28a70a8f 100644 --- a/openstackclient/identity/v2_0/role.py +++ b/openstackclient/identity/v2_0/role.py @@ -30,7 +30,7 @@ class AddRole(command.ShowOne): - """Add role to project:user""" + _description = _("Add role to project:user") def get_parser(self, prog_name): parser = super(AddRole, self).get_parser(prog_name) @@ -73,7 +73,7 @@ def take_action(self, parsed_args): class CreateRole(command.ShowOne): - """Create new role""" + _description = _("Create new role") def get_parser(self, prog_name): parser = super(CreateRole, self).get_parser(prog_name) @@ -109,7 +109,7 @@ def take_action(self, parsed_args): class DeleteRole(command.Command): - """Delete role(s)""" + _description = _("Delete role(s)") def get_parser(self, prog_name): parser = super(DeleteRole, self).get_parser(prog_name) @@ -133,7 +133,7 @@ def take_action(self, parsed_args): class ListRole(command.Lister): - """List roles""" + _description = _("List roles") def get_parser(self, prog_name): parser = super(ListRole, self).get_parser(prog_name) @@ -223,7 +223,7 @@ def _deprecated(): class ListUserRole(command.Lister): - """List user-role assignments""" + _description = _("List user-role assignments") def get_parser(self, prog_name): parser = super(ListUserRole, self).get_parser(prog_name) @@ -293,7 +293,7 @@ def take_action(self, parsed_args): class RemoveRole(command.Command): - """Remove role from project : user""" + _description = _("Remove role from project : user") def get_parser(self, prog_name): parser = super(RemoveRole, self).get_parser(prog_name) @@ -331,7 +331,7 @@ def take_action(self, parsed_args): class ShowRole(command.ShowOne): - """Display role details""" + _description = _("Display role details") def get_parser(self, prog_name): parser = super(ShowRole, self).get_parser(prog_name) diff --git a/openstackclient/identity/v2_0/role_assignment.py b/openstackclient/identity/v2_0/role_assignment.py index 44f55c3b7a..8236bbfc68 100644 --- a/openstackclient/identity/v2_0/role_assignment.py +++ b/openstackclient/identity/v2_0/role_assignment.py @@ -21,7 +21,7 @@ class ListRoleAssignment(command.Lister): - """List role assignments""" + _description = _("List role assignments") def get_parser(self, prog_name): parser = super(ListRoleAssignment, self).get_parser(prog_name) diff --git a/openstackclient/identity/v2_0/service.py b/openstackclient/identity/v2_0/service.py index e318643c17..f70f0fa9db 100644 --- a/openstackclient/identity/v2_0/service.py +++ b/openstackclient/identity/v2_0/service.py @@ -31,7 +31,7 @@ class CreateService(command.ShowOne): - """Create new service""" + _description = _("Create new service") def get_parser(self, prog_name): parser = super(CreateService, self).get_parser(prog_name) @@ -91,7 +91,7 @@ def take_action(self, parsed_args): class DeleteService(command.Command): - """Delete service(s)""" + _description = _("Delete service(s)") def get_parser(self, prog_name): parser = super(DeleteService, self).get_parser(prog_name) @@ -125,7 +125,7 @@ def take_action(self, parsed_args): class ListService(command.Lister): - """List services""" + _description = _("List services") def get_parser(self, prog_name): parser = super(ListService, self).get_parser(prog_name) @@ -151,7 +151,7 @@ def take_action(self, parsed_args): class ShowService(command.ShowOne): - """Display service details""" + _description = _("Display service details") def get_parser(self, prog_name): parser = super(ShowService, self).get_parser(prog_name) diff --git a/openstackclient/identity/v2_0/token.py b/openstackclient/identity/v2_0/token.py index 4b7e988b6e..3b08b4750d 100644 --- a/openstackclient/identity/v2_0/token.py +++ b/openstackclient/identity/v2_0/token.py @@ -23,7 +23,7 @@ class IssueToken(command.ShowOne): - """Issue new token""" + _description = _("Issue new token") # scoped token is optional required_scope = False @@ -53,7 +53,7 @@ def take_action(self, parsed_args): class RevokeToken(command.Command): - """Revoke existing token""" + _description = _("Revoke existing token") def get_parser(self, prog_name): parser = super(RevokeToken, self).get_parser(prog_name) diff --git a/openstackclient/identity/v2_0/user.py b/openstackclient/identity/v2_0/user.py index bc091ce7a3..ddd5b981e0 100644 --- a/openstackclient/identity/v2_0/user.py +++ b/openstackclient/identity/v2_0/user.py @@ -29,7 +29,7 @@ class CreateUser(command.ShowOne): - """Create new user""" + _description = _("Create new user") def get_parser(self, prog_name): parser = super(CreateUser, self).get_parser(prog_name) @@ -130,7 +130,7 @@ def take_action(self, parsed_args): class DeleteUser(command.Command): - """Delete user(s)""" + _description = _("Delete user(s)") def get_parser(self, prog_name): parser = super(DeleteUser, self).get_parser(prog_name) @@ -154,7 +154,7 @@ def take_action(self, parsed_args): class ListUser(command.Lister): - """List users""" + _description = _("List users") def get_parser(self, prog_name): parser = super(ListUser, self).get_parser(prog_name) @@ -242,7 +242,7 @@ def _format_project(project): class SetUser(command.Command): - """Set user properties""" + _description = _("Set user properties") def get_parser(self, prog_name): parser = super(SetUser, self).get_parser(prog_name) @@ -336,7 +336,7 @@ def take_action(self, parsed_args): class ShowUser(command.ShowOne): - """Display user details""" + _description = _("Display user details") def get_parser(self, prog_name): parser = super(ShowUser, self).get_parser(prog_name) diff --git a/openstackclient/identity/v3/catalog.py b/openstackclient/identity/v3/catalog.py index a62d0a930d..28f4fadad5 100644 --- a/openstackclient/identity/v3/catalog.py +++ b/openstackclient/identity/v3/catalog.py @@ -38,7 +38,7 @@ def _format_endpoints(eps=None): class ListCatalog(command.Lister): - """List services in the service catalog""" + _description = _("List services in the service catalog") def take_action(self, parsed_args): @@ -61,7 +61,7 @@ def take_action(self, parsed_args): class ShowCatalog(command.ShowOne): - """Display service catalog details""" + _description = _("Display service catalog details") def get_parser(self, prog_name): parser = super(ShowCatalog, self).get_parser(prog_name) diff --git a/openstackclient/identity/v3/consumer.py b/openstackclient/identity/v3/consumer.py index b41a37ca2c..bcb29db4dc 100644 --- a/openstackclient/identity/v3/consumer.py +++ b/openstackclient/identity/v3/consumer.py @@ -29,7 +29,7 @@ class CreateConsumer(command.ShowOne): - """Create new consumer""" + _description = _("Create new consumer") def get_parser(self, prog_name): parser = super(CreateConsumer, self).get_parser(prog_name) @@ -50,7 +50,7 @@ def take_action(self, parsed_args): class DeleteConsumer(command.Command): - """Delete consumer(s)""" + _description = _("Delete consumer(s)") def get_parser(self, prog_name): parser = super(DeleteConsumer, self).get_parser(prog_name) @@ -84,7 +84,7 @@ def take_action(self, parsed_args): class ListConsumer(command.Lister): - """List consumers""" + _description = _("List consumers") def take_action(self, parsed_args): columns = ('ID', 'Description') @@ -97,7 +97,7 @@ def take_action(self, parsed_args): class SetConsumer(command.Command): - """Set consumer properties""" + _description = _("Set consumer properties") def get_parser(self, prog_name): parser = super(SetConsumer, self).get_parser(prog_name) @@ -126,7 +126,7 @@ def take_action(self, parsed_args): class ShowConsumer(command.ShowOne): - """Display consumer details""" + _description = _("Display consumer details") def get_parser(self, prog_name): parser = super(ShowConsumer, self).get_parser(prog_name) diff --git a/openstackclient/identity/v3/credential.py b/openstackclient/identity/v3/credential.py index 0ef94cf40f..68287f2a39 100644 --- a/openstackclient/identity/v3/credential.py +++ b/openstackclient/identity/v3/credential.py @@ -29,7 +29,7 @@ class CreateCredential(command.ShowOne): - """Create new credential""" + _description = _("Create new credential") def get_parser(self, prog_name): parser = super(CreateCredential, self).get_parser(prog_name) @@ -78,7 +78,7 @@ def take_action(self, parsed_args): class DeleteCredential(command.Command): - """Delete credential(s)""" + _description = _("Delete credential(s)") def get_parser(self, prog_name): parser = super(DeleteCredential, self).get_parser(prog_name) @@ -110,7 +110,7 @@ def take_action(self, parsed_args): class ListCredential(command.Lister): - """List credentials""" + _description = _("List credentials") def take_action(self, parsed_args): columns = ('ID', 'Type', 'User ID', 'Blob', 'Project ID') @@ -124,7 +124,7 @@ def take_action(self, parsed_args): class SetCredential(command.Command): - """Set credential properties""" + _description = _("Set credential properties") def get_parser(self, prog_name): parser = super(SetCredential, self).get_parser(prog_name) @@ -180,7 +180,7 @@ def take_action(self, parsed_args): class ShowCredential(command.ShowOne): - """Display credential details""" + _description = _("Display credential details") def get_parser(self, prog_name): parser = super(ShowCredential, self).get_parser(prog_name) diff --git a/openstackclient/identity/v3/domain.py b/openstackclient/identity/v3/domain.py index 76e47d32e1..59ab0f0772 100644 --- a/openstackclient/identity/v3/domain.py +++ b/openstackclient/identity/v3/domain.py @@ -31,7 +31,7 @@ class CreateDomain(command.ShowOne): - """Create new domain""" + _description = _("Create new domain") def get_parser(self, prog_name): parser = super(CreateDomain, self).get_parser(prog_name) @@ -89,7 +89,7 @@ def take_action(self, parsed_args): class DeleteDomain(command.Command): - """Delete domain(s)""" + _description = _("Delete domain(s)") def get_parser(self, prog_name): parser = super(DeleteDomain, self).get_parser(prog_name) @@ -122,7 +122,7 @@ def take_action(self, parsed_args): class ListDomain(command.Lister): - """List domains""" + _description = _("List domains") def take_action(self, parsed_args): columns = ('ID', 'Name', 'Enabled', 'Description') @@ -135,7 +135,7 @@ def take_action(self, parsed_args): class SetDomain(command.Command): - """Set domain properties""" + _description = _("Set domain properties") def get_parser(self, prog_name): parser = super(SetDomain, self).get_parser(prog_name) @@ -186,7 +186,7 @@ def take_action(self, parsed_args): class ShowDomain(command.ShowOne): - """Display domain details""" + _description = _("Display domain details") def get_parser(self, prog_name): parser = super(ShowDomain, self).get_parser(prog_name) diff --git a/openstackclient/identity/v3/ec2creds.py b/openstackclient/identity/v3/ec2creds.py index 7ad0171906..9854efda70 100644 --- a/openstackclient/identity/v3/ec2creds.py +++ b/openstackclient/identity/v3/ec2creds.py @@ -55,7 +55,7 @@ def _determine_ec2_user(parsed_args, client_manager): class CreateEC2Creds(command.ShowOne): - """Create EC2 credentials""" + _description = _("Create EC2 credentials") def get_parser(self, prog_name): parser = super(CreateEC2Creds, self).get_parser(prog_name) @@ -112,7 +112,7 @@ def take_action(self, parsed_args): class DeleteEC2Creds(command.Command): - """Delete EC2 credentials""" + _description = _("Delete EC2 credentials") def get_parser(self, prog_name): parser = super(DeleteEC2Creds, self).get_parser(prog_name) @@ -151,7 +151,7 @@ def take_action(self, parsed_args): class ListEC2Creds(command.Lister): - """List EC2 credentials""" + _description = _("List EC2 credentials") def get_parser(self, prog_name): parser = super(ListEC2Creds, self).get_parser(prog_name) @@ -179,7 +179,7 @@ def take_action(self, parsed_args): class ShowEC2Creds(command.ShowOne): - """Display EC2 credentials details""" + _description = _("Display EC2 credentials details") def get_parser(self, prog_name): parser = super(ShowEC2Creds, self).get_parser(prog_name) diff --git a/openstackclient/identity/v3/endpoint.py b/openstackclient/identity/v3/endpoint.py index 73b37a436c..39fd49efd1 100644 --- a/openstackclient/identity/v3/endpoint.py +++ b/openstackclient/identity/v3/endpoint.py @@ -37,7 +37,7 @@ def get_service_name(service): class CreateEndpoint(command.ShowOne): - """Create new endpoint""" + _description = _("Create new endpoint") def get_parser(self, prog_name): parser = super(CreateEndpoint, self).get_parser(prog_name) @@ -99,7 +99,7 @@ def take_action(self, parsed_args): class DeleteEndpoint(command.Command): - """Delete endpoint(s)""" + _description = _("Delete endpoint(s)") def get_parser(self, prog_name): parser = super(DeleteEndpoint, self).get_parser(prog_name) @@ -133,7 +133,7 @@ def take_action(self, parsed_args): class ListEndpoint(command.Lister): - """List endpoints""" + _description = _("List endpoints") def get_parser(self, prog_name): parser = super(ListEndpoint, self).get_parser(prog_name) @@ -181,7 +181,7 @@ def take_action(self, parsed_args): class SetEndpoint(command.Command): - """Set endpoint properties""" + _description = _("Set endpoint properties") def get_parser(self, prog_name): parser = super(SetEndpoint, self).get_parser(prog_name) @@ -252,7 +252,7 @@ def take_action(self, parsed_args): class ShowEndpoint(command.ShowOne): - """Display endpoint details""" + _description = _("Display endpoint details") def get_parser(self, prog_name): parser = super(ShowEndpoint, self).get_parser(prog_name) diff --git a/openstackclient/identity/v3/federation_protocol.py b/openstackclient/identity/v3/federation_protocol.py index 3fde9027e7..0752e8f6f2 100644 --- a/openstackclient/identity/v3/federation_protocol.py +++ b/openstackclient/identity/v3/federation_protocol.py @@ -28,7 +28,7 @@ class CreateProtocol(command.ShowOne): - """Create new federation protocol""" + _description = _("Create new federation protocol") def get_parser(self, prog_name): parser = super(CreateProtocol, self).get_parser(prog_name) @@ -72,7 +72,7 @@ def take_action(self, parsed_args): class DeleteProtocol(command.Command): - """Delete federation protocol(s)""" + _description = _("Delete federation protocol(s)") def get_parser(self, prog_name): parser = super(DeleteProtocol, self).get_parser(prog_name) @@ -113,7 +113,7 @@ def take_action(self, parsed_args): class ListProtocols(command.Lister): - """List federation protocols""" + _description = _("List federation protocols") def get_parser(self, prog_name): parser = super(ListProtocols, self).get_parser(prog_name) @@ -139,7 +139,7 @@ def take_action(self, parsed_args): class SetProtocol(command.Command): - """Set federation protocol properties""" + _description = _("Set federation protocol properties") def get_parser(self, prog_name): parser = super(SetProtocol, self).get_parser(prog_name) @@ -179,7 +179,7 @@ def take_action(self, parsed_args): class ShowProtocol(command.ShowOne): - """Display federation protocol details""" + _description = _("Display federation protocol details") def get_parser(self, prog_name): parser = super(ShowProtocol, self).get_parser(prog_name) diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py index f780810afa..df684c129b 100644 --- a/openstackclient/identity/v3/group.py +++ b/openstackclient/identity/v3/group.py @@ -31,7 +31,7 @@ class AddUserToGroup(command.Command): - """Add user to group""" + _description = _("Add user to group") def get_parser(self, prog_name): parser = super(AddUserToGroup, self).get_parser(prog_name) @@ -76,7 +76,7 @@ def take_action(self, parsed_args): class CheckUserInGroup(command.Command): - """Check user membership in group""" + _description = _("Check user membership in group") def get_parser(self, prog_name): parser = super(CheckUserInGroup, self).get_parser(prog_name) @@ -121,7 +121,7 @@ def take_action(self, parsed_args): class CreateGroup(command.ShowOne): - """Create new group""" + _description = _("Create new group") def get_parser(self, prog_name): parser = super(CreateGroup, self).get_parser(prog_name) @@ -174,7 +174,7 @@ def take_action(self, parsed_args): class DeleteGroup(command.Command): - """Delete group(s)""" + _description = _("Delete group(s)") def get_parser(self, prog_name): parser = super(DeleteGroup, self).get_parser(prog_name) @@ -202,7 +202,7 @@ def take_action(self, parsed_args): class ListGroup(command.Lister): - """List groups""" + _description = _("List groups") def get_parser(self, prog_name): parser = super(ListGroup, self).get_parser(prog_name) @@ -262,7 +262,7 @@ def take_action(self, parsed_args): class RemoveUserFromGroup(command.Command): - """Remove user from group""" + _description = _("Remove user from group") def get_parser(self, prog_name): parser = super(RemoveUserFromGroup, self).get_parser(prog_name) @@ -307,7 +307,7 @@ def take_action(self, parsed_args): class SetGroup(command.Command): - """Set group properties""" + _description = _("Set group properties") def get_parser(self, prog_name): parser = super(SetGroup, self).get_parser(prog_name) @@ -347,7 +347,7 @@ def take_action(self, parsed_args): class ShowGroup(command.ShowOne): - """Display group details""" + _description = _("Display group details") def get_parser(self, prog_name): parser = super(ShowGroup, self).get_parser(prog_name) diff --git a/openstackclient/identity/v3/identity_provider.py b/openstackclient/identity/v3/identity_provider.py index b6b0318870..163dcb5f07 100644 --- a/openstackclient/identity/v3/identity_provider.py +++ b/openstackclient/identity/v3/identity_provider.py @@ -27,7 +27,7 @@ class CreateIdentityProvider(command.ShowOne): - """Create new identity provider""" + _description = _("Create new identity provider") def get_parser(self, prog_name): parser = super(CreateIdentityProvider, self).get_parser(prog_name) @@ -94,7 +94,7 @@ def take_action(self, parsed_args): class DeleteIdentityProvider(command.Command): - """Delete identity provider(s)""" + _description = _("Delete identity provider(s)") def get_parser(self, prog_name): parser = super(DeleteIdentityProvider, self).get_parser(prog_name) @@ -126,7 +126,7 @@ def take_action(self, parsed_args): class ListIdentityProvider(command.Lister): - """List identity providers""" + _description = _("List identity providers") def take_action(self, parsed_args): columns = ('ID', 'Enabled', 'Description') @@ -140,7 +140,7 @@ def take_action(self, parsed_args): class SetIdentityProvider(command.Command): - """Set identity provider properties""" + _description = _("Set identity provider properties") def get_parser(self, prog_name): parser = super(SetIdentityProvider, self).get_parser(prog_name) @@ -211,7 +211,7 @@ def take_action(self, parsed_args): class ShowIdentityProvider(command.ShowOne): - """Display identity provider details""" + _description = _("Display identity provider details") def get_parser(self, prog_name): parser = super(ShowIdentityProvider, self).get_parser(prog_name) diff --git a/openstackclient/identity/v3/mapping.py b/openstackclient/identity/v3/mapping.py index 09181a0bad..dbb1b06870 100644 --- a/openstackclient/identity/v3/mapping.py +++ b/openstackclient/identity/v3/mapping.py @@ -30,7 +30,7 @@ class _RulesReader(object): - """Helper class capable of reading rules from files""" + _description = _("Helper class capable of reading rules from files") def _read_rules(self, path): """Read and parse rules from path @@ -82,7 +82,7 @@ def _read_rules(self, path): class CreateMapping(command.ShowOne, _RulesReader): - """Create new mapping""" + _description = _("Create new mapping") def get_parser(self, prog_name): parser = super(CreateMapping, self).get_parser(prog_name) @@ -111,7 +111,7 @@ def take_action(self, parsed_args): class DeleteMapping(command.Command): - """Delete mapping(s)""" + _description = _("Delete mapping(s)") def get_parser(self, prog_name): parser = super(DeleteMapping, self).get_parser(prog_name) @@ -143,7 +143,7 @@ def take_action(self, parsed_args): class ListMapping(command.Lister): - """List mappings""" + _description = _("List mappings") def take_action(self, parsed_args): # NOTE(marek-denis): Since rules can be long and tedious I have decided @@ -157,7 +157,7 @@ def take_action(self, parsed_args): class SetMapping(command.Command, _RulesReader): - """Set mapping properties""" + _description = _("Set mapping properties") def get_parser(self, prog_name): parser = super(SetMapping, self).get_parser(prog_name) @@ -187,7 +187,7 @@ def take_action(self, parsed_args): class ShowMapping(command.ShowOne): - """Display mapping details""" + _description = _("Display mapping details") def get_parser(self, prog_name): parser = super(ShowMapping, self).get_parser(prog_name) diff --git a/openstackclient/identity/v3/policy.py b/openstackclient/identity/v3/policy.py index 596eae018a..c511652ede 100644 --- a/openstackclient/identity/v3/policy.py +++ b/openstackclient/identity/v3/policy.py @@ -29,7 +29,7 @@ class CreatePolicy(command.ShowOne): - """Create new policy""" + _description = _("Create new policy") def get_parser(self, prog_name): parser = super(CreatePolicy, self).get_parser(prog_name) @@ -61,7 +61,7 @@ def take_action(self, parsed_args): class DeletePolicy(command.Command): - """Delete policy(s)""" + _description = _("Delete policy(s)") def get_parser(self, prog_name): parser = super(DeletePolicy, self).get_parser(prog_name) @@ -93,7 +93,7 @@ def take_action(self, parsed_args): class ListPolicy(command.Lister): - """List policies""" + _description = _("List policies") def get_parser(self, prog_name): parser = super(ListPolicy, self).get_parser(prog_name) @@ -121,7 +121,7 @@ def take_action(self, parsed_args): class SetPolicy(command.Command): - """Set policy properties""" + _description = _("Set policy properties") def get_parser(self, prog_name): parser = super(SetPolicy, self).get_parser(prog_name) @@ -159,7 +159,7 @@ def take_action(self, parsed_args): class ShowPolicy(command.ShowOne): - """Display policy details""" + _description = _("Display policy details") def get_parser(self, prog_name): parser = super(ShowPolicy, self).get_parser(prog_name) diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index 56c4fbc8b2..a634865911 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -31,7 +31,7 @@ class CreateProject(command.ShowOne): - """Create new project""" + _description = _("Create new project") def get_parser(self, prog_name): parser = super(CreateProject, self).get_parser(prog_name) @@ -125,7 +125,7 @@ def take_action(self, parsed_args): class DeleteProject(command.Command): - """Delete project(s)""" + _description = _("Delete project(s)") def get_parser(self, prog_name): parser = super(DeleteProject, self).get_parser(prog_name) @@ -160,7 +160,7 @@ def take_action(self, parsed_args): class ListProject(command.Lister): - """List projects""" + _description = _("List projects") def get_parser(self, prog_name): parser = super(ListProject, self).get_parser(prog_name) @@ -216,7 +216,7 @@ def take_action(self, parsed_args): class SetProject(command.Command): - """Set project properties""" + _description = _("Set project properties") def get_parser(self, prog_name): parser = super(SetProject, self).get_parser(prog_name) @@ -283,7 +283,7 @@ def take_action(self, parsed_args): class ShowProject(command.ShowOne): - """Display project details""" + _description = _("Display project details") def get_parser(self, prog_name): parser = super(ShowProject, self).get_parser(prog_name) diff --git a/openstackclient/identity/v3/region.py b/openstackclient/identity/v3/region.py index b7c51f9314..d3e712e375 100644 --- a/openstackclient/identity/v3/region.py +++ b/openstackclient/identity/v3/region.py @@ -27,7 +27,7 @@ class CreateRegion(command.ShowOne): - """Create new region""" + _description = _("Create new region") def get_parser(self, prog_name): parser = super(CreateRegion, self).get_parser(prog_name) @@ -66,7 +66,7 @@ def take_action(self, parsed_args): class DeleteRegion(command.Command): - """Delete region(s)""" + _description = _("Delete region(s)") def get_parser(self, prog_name): parser = super(DeleteRegion, self).get_parser(prog_name) @@ -98,7 +98,7 @@ def take_action(self, parsed_args): class ListRegion(command.Lister): - """List regions""" + _description = _("List regions") def get_parser(self, prog_name): parser = super(ListRegion, self).get_parser(prog_name) @@ -128,7 +128,7 @@ def take_action(self, parsed_args): class SetRegion(command.Command): - """Set region properties""" + _description = _("Set region properties") def get_parser(self, prog_name): parser = super(SetRegion, self).get_parser(prog_name) @@ -162,7 +162,7 @@ def take_action(self, parsed_args): class ShowRegion(command.ShowOne): - """Display region details""" + _description = _("Display region details") def get_parser(self, prog_name): parser = super(ShowRegion, self).get_parser(prog_name) diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index 8b91174626..c9d0fbf305 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -109,7 +109,8 @@ def _process_identity_and_resource_options(parsed_args, class AddRole(command.Command): - """Adds a role assignment to a user or group on a domain or project""" + _description = _("Adds a role assignment to a user or group on a domain " + "or project") def get_parser(self, prog_name): parser = super(AddRole, self).get_parser(prog_name) @@ -151,7 +152,7 @@ def take_action(self, parsed_args): class CreateRole(command.ShowOne): - """Create new role""" + _description = _("Create new role") def get_parser(self, prog_name): parser = super(CreateRole, self).get_parser(prog_name) @@ -198,7 +199,7 @@ def take_action(self, parsed_args): class DeleteRole(command.Command): - """Delete role(s)""" + _description = _("Delete role(s)") def get_parser(self, prog_name): parser = super(DeleteRole, self).get_parser(prog_name) @@ -233,7 +234,7 @@ def take_action(self, parsed_args): class ListRole(command.Lister): - """List roles""" + _description = _("List roles") def get_parser(self, prog_name): parser = super(ListRole, self).get_parser(prog_name) @@ -371,7 +372,8 @@ def take_action(self, parsed_args): class RemoveRole(command.Command): - """Removes a role assignment from domain/project : user/group""" + _description = _("Removes a role assignment from domain/project : " + "user/group") def get_parser(self, prog_name): parser = super(RemoveRole, self).get_parser(prog_name) @@ -415,7 +417,7 @@ def take_action(self, parsed_args): class SetRole(command.Command): - """Set role properties""" + _description = _("Set role properties") def get_parser(self, prog_name): parser = super(SetRole, self).get_parser(prog_name) @@ -452,7 +454,7 @@ def take_action(self, parsed_args): class ShowRole(command.ShowOne): - """Display role details""" + _description = _("Display role details") def get_parser(self, prog_name): parser = super(ShowRole, self).get_parser(prog_name) diff --git a/openstackclient/identity/v3/role_assignment.py b/openstackclient/identity/v3/role_assignment.py index d25cc6ce3d..9da050ded2 100644 --- a/openstackclient/identity/v3/role_assignment.py +++ b/openstackclient/identity/v3/role_assignment.py @@ -11,7 +11,7 @@ # under the License. # -"""Identity v3 Assignment action implementations """ +"""Identity v3 Assignment action implementations""" from osc_lib.command import command from osc_lib import utils @@ -21,7 +21,7 @@ class ListRoleAssignment(command.Lister): - """List role assignments""" + _description = _("List role assignments") def get_parser(self, prog_name): parser = super(ListRoleAssignment, self).get_parser(prog_name) diff --git a/openstackclient/identity/v3/service.py b/openstackclient/identity/v3/service.py index 97e64dc601..7daf8919d3 100644 --- a/openstackclient/identity/v3/service.py +++ b/openstackclient/identity/v3/service.py @@ -30,7 +30,7 @@ class CreateService(command.ShowOne): - """Create new service""" + _description = _("Create new service") def get_parser(self, prog_name): parser = super(CreateService, self).get_parser(prog_name) @@ -81,7 +81,7 @@ def take_action(self, parsed_args): class DeleteService(command.Command): - """Delete service(s)""" + _description = _("Delete service(s)") def get_parser(self, prog_name): parser = super(DeleteService, self).get_parser(prog_name) @@ -114,7 +114,7 @@ def take_action(self, parsed_args): class ListService(command.Lister): - """List services""" + _description = _("List services") def get_parser(self, prog_name): parser = super(ListService, self).get_parser(prog_name) @@ -140,7 +140,7 @@ def take_action(self, parsed_args): class SetService(command.Command): - """Set service properties""" + _description = _("Set service properties") def get_parser(self, prog_name): parser = super(SetService, self).get_parser(prog_name) @@ -201,7 +201,7 @@ def take_action(self, parsed_args): class ShowService(command.ShowOne): - """Display service details""" + _description = _("Display service details") def get_parser(self, prog_name): parser = super(ShowService, self).get_parser(prog_name) diff --git a/openstackclient/identity/v3/service_provider.py b/openstackclient/identity/v3/service_provider.py index 8548ae1fbd..459dc00b03 100644 --- a/openstackclient/identity/v3/service_provider.py +++ b/openstackclient/identity/v3/service_provider.py @@ -27,7 +27,7 @@ class CreateServiceProvider(command.ShowOne): - """Create new service provider""" + _description = _("Create new service provider") def get_parser(self, prog_name): parser = super(CreateServiceProvider, self).get_parser(prog_name) @@ -87,7 +87,7 @@ def take_action(self, parsed_args): class DeleteServiceProvider(command.Command): - """Delete service provider(s)""" + _description = _("Delete service provider(s)") def get_parser(self, prog_name): parser = super(DeleteServiceProvider, self).get_parser(prog_name) @@ -119,7 +119,7 @@ def take_action(self, parsed_args): class ListServiceProvider(command.Lister): - """List service providers""" + _description = _("List service providers") def take_action(self, parsed_args): service_client = self.app.client_manager.identity @@ -134,7 +134,7 @@ def take_action(self, parsed_args): class SetServiceProvider(command.Command): - """Set service provider properties""" + _description = _("Set service provider properties") def get_parser(self, prog_name): parser = super(SetServiceProvider, self).get_parser(prog_name) @@ -192,7 +192,7 @@ def take_action(self, parsed_args): class ShowServiceProvider(command.ShowOne): - """Display service provider details""" + _description = _("Display service provider details") def get_parser(self, prog_name): parser = super(ShowServiceProvider, self).get_parser(prog_name) diff --git a/openstackclient/identity/v3/token.py b/openstackclient/identity/v3/token.py index 2cd304e601..7a66f23b75 100644 --- a/openstackclient/identity/v3/token.py +++ b/openstackclient/identity/v3/token.py @@ -25,7 +25,7 @@ class AuthorizeRequestToken(command.ShowOne): - """Authorize a request token""" + _description = _("Authorize a request token") def get_parser(self, prog_name): parser = super(AuthorizeRequestToken, self).get_parser(prog_name) @@ -66,7 +66,7 @@ def take_action(self, parsed_args): class CreateAccessToken(command.ShowOne): - """Create an access token""" + _description = _("Create an access token") def get_parser(self, prog_name): parser = super(CreateAccessToken, self).get_parser(prog_name) @@ -112,7 +112,7 @@ def take_action(self, parsed_args): class CreateRequestToken(command.ShowOne): - """Create a request token""" + _description = _("Create a request token") def get_parser(self, prog_name): parser = super(CreateRequestToken, self).get_parser(prog_name) @@ -164,7 +164,7 @@ def take_action(self, parsed_args): class IssueToken(command.ShowOne): - """Issue new token""" + _description = _("Issue new token") # scoped token is optional required_scope = False @@ -196,7 +196,7 @@ def take_action(self, parsed_args): class RevokeToken(command.Command): - """Revoke existing token""" + _description = _("Revoke existing token") def get_parser(self, prog_name): parser = super(RevokeToken, self).get_parser(prog_name) diff --git a/openstackclient/identity/v3/trust.py b/openstackclient/identity/v3/trust.py index bbc86adb90..62d72ea142 100644 --- a/openstackclient/identity/v3/trust.py +++ b/openstackclient/identity/v3/trust.py @@ -24,7 +24,7 @@ class CreateTrust(command.ShowOne): - """Create new trust""" + _description = _("Create new trust") def get_parser(self, prog_name): parser = super(CreateTrust, self).get_parser(prog_name) @@ -131,7 +131,7 @@ def take_action(self, parsed_args): class DeleteTrust(command.Command): - """Delete trust(s)""" + _description = _("Delete trust(s)") def get_parser(self, prog_name): parser = super(DeleteTrust, self).get_parser(prog_name) @@ -151,7 +151,7 @@ def take_action(self, parsed_args): class ListTrust(command.Lister): - """List trusts""" + _description = _("List trusts") def take_action(self, parsed_args): columns = ('ID', 'Expires At', 'Impersonation', 'Project ID', @@ -165,7 +165,7 @@ def take_action(self, parsed_args): class ShowTrust(command.ShowOne): - """Display trust details""" + _description = _("Display trust details") def get_parser(self, prog_name): parser = super(ShowTrust, self).get_parser(prog_name) diff --git a/openstackclient/identity/v3/unscoped_saml.py b/openstackclient/identity/v3/unscoped_saml.py index f116174b32..5940534a0a 100644 --- a/openstackclient/identity/v3/unscoped_saml.py +++ b/openstackclient/identity/v3/unscoped_saml.py @@ -44,7 +44,7 @@ def _decorated(self, parsed_args): class ListAccessibleDomains(command.Lister): - """List accessible domains""" + _description = _("List accessible domains") @auth_with_unscoped_saml def take_action(self, parsed_args): @@ -59,7 +59,7 @@ def take_action(self, parsed_args): class ListAccessibleProjects(command.Lister): - """List accessible projects""" + _description = _("List accessible projects") @auth_with_unscoped_saml def take_action(self, parsed_args): diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index 1e086fb6aa..796cf28c22 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -31,7 +31,7 @@ class CreateUser(command.ShowOne): - """Create new user""" + _description = _("Create new user") def get_parser(self, prog_name): parser = super(CreateUser, self).get_parser(prog_name) @@ -138,7 +138,7 @@ def take_action(self, parsed_args): class DeleteUser(command.Command): - """Delete user(s)""" + _description = _("Delete user(s)") def get_parser(self, prog_name): parser = super(DeleteUser, self).get_parser(prog_name) @@ -173,7 +173,7 @@ def take_action(self, parsed_args): class ListUser(command.Lister): - """List users""" + _description = _("List users") def get_parser(self, prog_name): parser = super(ListUser, self).get_parser(prog_name) @@ -273,7 +273,7 @@ def take_action(self, parsed_args): class SetUser(command.Command): - """Set user properties""" + _description = _("Set user properties") def get_parser(self, prog_name): parser = super(SetUser, self).get_parser(prog_name) @@ -365,7 +365,7 @@ def take_action(self, parsed_args): class SetPasswordUser(command.Command): - """Change current user password""" + _description = _("Change current user password") required_scope = False @@ -424,7 +424,7 @@ def take_action(self, parsed_args): class ShowUser(command.ShowOne): - """Display user details""" + _description = _("Display user details") def get_parser(self, prog_name): parser = super(ShowUser, self).get_parser(prog_name) diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index 5f669c6412..d9ac8c08aa 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -59,7 +59,7 @@ def _format_visibility(data): class CreateImage(command.ShowOne): - """Create/upload an image""" + _description = _("Create/upload an image") def get_parser(self, prog_name): parser = super(CreateImage, self).get_parser(prog_name) @@ -277,7 +277,7 @@ def take_action(self, parsed_args): class DeleteImage(command.Command): - """Delete image(s)""" + _description = _("Delete image(s)") def get_parser(self, prog_name): parser = super(DeleteImage, self).get_parser(prog_name) @@ -300,7 +300,7 @@ def take_action(self, parsed_args): class ListImage(command.Lister): - """List available images""" + _description = _("List available images") def get_parser(self, prog_name): parser = super(ListImage, self).get_parser(prog_name) @@ -440,7 +440,7 @@ def take_action(self, parsed_args): class SaveImage(command.Command): - """Save an image locally""" + _description = _("Save an image locally") def get_parser(self, prog_name): parser = super(SaveImage, self).get_parser(prog_name) @@ -468,7 +468,7 @@ def take_action(self, parsed_args): class SetImage(command.Command): - """Set image properties""" + _description = _("Set image properties") def get_parser(self, prog_name): parser = super(SetImage, self).get_parser(prog_name) @@ -702,7 +702,7 @@ def take_action(self, parsed_args): class ShowImage(command.ShowOne): - """Display image details""" + _description = _("Display image details") def get_parser(self, prog_name): parser = super(ShowImage, self).get_parser(prog_name) diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 0712e09c07..657277c0dc 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -38,7 +38,7 @@ def _format_image(image): - """Format an image to make it more consistent with OSC operations. """ + """Format an image to make it more consistent with OSC operations.""" info = {} properties = {} @@ -69,7 +69,7 @@ def _format_image(image): class AddProjectToImage(command.ShowOne): - """Associate project with image""" + _description = _("Associate project with image") def get_parser(self, prog_name): parser = super(AddProjectToImage, self).get_parser(prog_name) @@ -107,7 +107,7 @@ def take_action(self, parsed_args): class CreateImage(command.ShowOne): - """Create/upload an image""" + _description = _("Create/upload an image") deadopts = ('size', 'location', 'copy-from', 'checksum', 'store') @@ -361,7 +361,7 @@ def take_action(self, parsed_args): class DeleteImage(command.Command): - """Delete image(s)""" + _description = _("Delete image(s)") def get_parser(self, prog_name): parser = super(DeleteImage, self).get_parser(prog_name) @@ -398,7 +398,7 @@ def take_action(self, parsed_args): class ListImage(command.Lister): - """List available images""" + _description = _("List available images") def get_parser(self, prog_name): parser = super(ListImage, self).get_parser(prog_name) @@ -542,7 +542,7 @@ def take_action(self, parsed_args): class RemoveProjectImage(command.Command): - """Disassociate project with image""" + _description = _("Disassociate project with image") def get_parser(self, prog_name): parser = super(RemoveProjectImage, self).get_parser(prog_name) @@ -575,7 +575,7 @@ def take_action(self, parsed_args): class SaveImage(command.Command): - """Save an image locally""" + _description = _("Save an image locally") def get_parser(self, prog_name): parser = super(SaveImage, self).get_parser(prog_name) @@ -603,7 +603,7 @@ def take_action(self, parsed_args): class SetImage(command.Command): - """Set image properties""" + _description = _("Set image properties") deadopts = ('visibility',) @@ -844,7 +844,7 @@ def take_action(self, parsed_args): class ShowImage(command.ShowOne): - """Display image details""" + _description = _("Display image details") def get_parser(self, prog_name): parser = super(ShowImage, self).get_parser(prog_name) @@ -867,7 +867,7 @@ def take_action(self, parsed_args): class UnsetImage(command.Command): - """Unset image tags and properties""" + _description = _("Unset image tags and properties") def get_parser(self, prog_name): parser = super(UnsetImage, self).get_parser(prog_name) diff --git a/openstackclient/network/v2/address_scope.py b/openstackclient/network/v2/address_scope.py index 95f6c94702..0d8f80d025 100644 --- a/openstackclient/network/v2/address_scope.py +++ b/openstackclient/network/v2/address_scope.py @@ -58,7 +58,7 @@ def _get_attrs(client_manager, parsed_args): # TODO(rtheis): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. class CreateAddressScope(command.ShowOne): - """Create a new Address Scope""" + _description = _("Create a new Address Scope") def get_parser(self, prog_name): parser = super(CreateAddressScope, self).get_parser(prog_name) @@ -106,7 +106,7 @@ def take_action(self, parsed_args): class DeleteAddressScope(command.Command): - """Delete address scope(s)""" + _description = _("Delete address scope(s)") def get_parser(self, prog_name): parser = super(DeleteAddressScope, self).get_parser(prog_name) @@ -141,7 +141,7 @@ def take_action(self, parsed_args): class ListAddressScope(command.Lister): - """List address scopes""" + _description = _("List address scopes") def take_action(self, parsed_args): client = self.app.client_manager.network @@ -170,7 +170,7 @@ def take_action(self, parsed_args): # TODO(rtheis): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. class SetAddressScope(command.Command): - """Set address scope properties""" + _description = _("Set address scope properties") def get_parser(self, prog_name): parser = super(SetAddressScope, self).get_parser(prog_name) @@ -214,7 +214,7 @@ def take_action(self, parsed_args): class ShowAddressScope(command.ShowOne): - """Display address scope details""" + _description = _("Display address scope details") def get_parser(self, prog_name): parser = super(ShowAddressScope, self).get_parser(prog_name) diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index 7ae5d44816..9b9c38bcfc 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -70,7 +70,7 @@ def _get_attrs(client_manager, parsed_args): class CreateFloatingIP(common.NetworkAndComputeShowOne): - """Create floating IP""" + _description = _("Create floating IP") def update_parser_common(self, parser): # In Compute v2 network, floating IPs could be allocated from floating @@ -130,7 +130,7 @@ def take_action_compute(self, client, parsed_args): class CreateIPFloating(CreateFloatingIP): - """Create floating IP""" + _description = _("Create floating IP") # TODO(tangchen): Remove this class and ``ip floating create`` command # two cycles after Mitaka. @@ -154,7 +154,7 @@ def take_action_compute(self, client, parsed_args): class DeleteFloatingIP(common.NetworkAndComputeDelete): - """Delete floating IP(s)""" + _description = _("Delete floating IP(s)") # Used by base class to find resources in parsed_args. resource = 'floating_ip' @@ -179,7 +179,7 @@ def take_action_compute(self, client, parsed_args): class DeleteIPFloating(DeleteFloatingIP): - """Delete floating IP(s)""" + _description = _("Delete floating IP(s)") # TODO(tangchen): Remove this class and ``ip floating delete`` command # two cycles after Mitaka. @@ -203,7 +203,7 @@ def take_action_compute(self, client, parsed_args): class ListFloatingIP(common.NetworkAndComputeLister): - """List floating IP(s)""" + _description = _("List floating IP(s)") def take_action_network(self, client, parsed_args): columns = ( @@ -258,7 +258,7 @@ def take_action_compute(self, client, parsed_args): class ListIPFloating(ListFloatingIP): - """List floating IP(s)""" + _description = _("List floating IP(s)") # TODO(tangchen): Remove this class and ``ip floating list`` command # two cycles after Mitaka. @@ -282,7 +282,7 @@ def take_action_compute(self, client, parsed_args): class ShowFloatingIP(common.NetworkAndComputeShowOne): - """Display floating IP details""" + _description = _("Display floating IP details") def update_parser_common(self, parser): parser.add_argument( @@ -309,7 +309,7 @@ def take_action_compute(self, client, parsed_args): class ShowIPFloating(ShowFloatingIP): - """Display floating IP details""" + _description = _("Display floating IP details") # TODO(tangchen): Remove this class and ``ip floating show`` command # two cycles after Mitaka. diff --git a/openstackclient/network/v2/floating_ip_pool.py b/openstackclient/network/v2/floating_ip_pool.py index c78ca06a91..73e94ead8b 100644 --- a/openstackclient/network/v2/floating_ip_pool.py +++ b/openstackclient/network/v2/floating_ip_pool.py @@ -23,7 +23,7 @@ class ListFloatingIPPool(common.NetworkAndComputeLister): - """List pools of floating IP addresses""" + _description = _("List pools of floating IP addresses") def take_action_network(self, client, parsed_args): msg = _("Floating ip pool operations are only available for " @@ -43,7 +43,7 @@ def take_action_compute(self, client, parsed_args): class ListIPFloatingPool(ListFloatingIPPool): - """List pools of floating IP addresses""" + _description = _("List pools of floating IP addresses") # TODO(tangchen): Remove this class and ``ip floating pool list`` command # two cycles after Mitaka. diff --git a/openstackclient/network/v2/ip_availability.py b/openstackclient/network/v2/ip_availability.py index 1d7b2aed87..5960e2faee 100644 --- a/openstackclient/network/v2/ip_availability.py +++ b/openstackclient/network/v2/ip_availability.py @@ -34,7 +34,7 @@ def _get_columns(item): class ListIPAvailability(command.Lister): - """List IP availability for network""" + _description = _("List IP availability for network") def get_parser(self, prog_name): parser = super(ListIPAvailability, self).get_parser(prog_name) @@ -92,7 +92,7 @@ def take_action(self, parsed_args): class ShowIPAvailability(command.ShowOne): - """Show network IP availability details""" + _description = _("Show network IP availability details") def get_parser(self, prog_name): parser = super(ShowIPAvailability, self).get_parser(prog_name) diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 397139e20a..37775e6ced 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -156,7 +156,7 @@ def _get_attrs_compute(client_manager, parsed_args): class CreateNetwork(common.NetworkAndComputeShowOne): - """Create new network""" + _description = _("Create new network") def update_parser_common(self, parser): parser.add_argument( @@ -276,7 +276,7 @@ def take_action_compute(self, client, parsed_args): class DeleteNetwork(common.NetworkAndComputeDelete): - """Delete network(s)""" + _description = _("Delete network(s)") # Used by base class to find resources in parsed_args. resource = 'network' @@ -302,7 +302,7 @@ def take_action_compute(self, client, parsed_args): class ListNetwork(common.NetworkAndComputeLister): - """List networks""" + _description = _("List networks") def update_parser_network(self, parser): router_ext_group = parser.add_mutually_exclusive_group() @@ -493,7 +493,7 @@ def take_action_compute(self, client, parsed_args): class SetNetwork(command.Command): - """Set network properties""" + _description = _("Set network properties") def get_parser(self, prog_name): parser = super(SetNetwork, self).get_parser(prog_name) @@ -584,7 +584,7 @@ def take_action(self, parsed_args): class ShowNetwork(common.NetworkAndComputeShowOne): - """Show network details""" + _description = _("Show network details") def update_parser_common(self, parser): parser.add_argument( diff --git a/openstackclient/network/v2/network_agent.py b/openstackclient/network/v2/network_agent.py index fdb34bb79b..6570849962 100644 --- a/openstackclient/network/v2/network_agent.py +++ b/openstackclient/network/v2/network_agent.py @@ -36,7 +36,7 @@ def _format_admin_state(state): class DeleteNetworkAgent(command.Command): - """Delete network agent(s)""" + _description = _("Delete network agent(s)") def get_parser(self, prog_name): parser = super(DeleteNetworkAgent, self).get_parser(prog_name) @@ -70,7 +70,7 @@ def take_action(self, parsed_args): class ListNetworkAgent(command.Lister): - """List network agents""" + _description = _("List network agents") def take_action(self, parsed_args): client = self.app.client_manager.network @@ -100,7 +100,7 @@ def take_action(self, parsed_args): class SetNetworkAgent(command.Command): - """Set network agent properties""" + _description = _("Set network agent properties") def get_parser(self, prog_name): parser = super(SetNetworkAgent, self).get_parser(prog_name) @@ -141,7 +141,7 @@ def take_action(self, parsed_args): class ShowNetworkAgent(command.ShowOne): - """Display network agent details""" + _description = _("Display network agent details") def get_parser(self, prog_name): parser = super(ShowNetworkAgent, self).get_parser(prog_name) diff --git a/openstackclient/network/v2/network_qos_policy.py b/openstackclient/network/v2/network_qos_policy.py index a8fcfc592a..75cb1d9155 100644 --- a/openstackclient/network/v2/network_qos_policy.py +++ b/openstackclient/network/v2/network_qos_policy.py @@ -57,7 +57,7 @@ def _get_attrs(client_manager, parsed_args): class CreateNetworkQosPolicy(command.ShowOne): - """Create a QoS policy""" + _description = _("Create a QoS policy") def get_parser(self, prog_name): parser = super(CreateNetworkQosPolicy, self).get_parser(prog_name) @@ -102,7 +102,7 @@ def take_action(self, parsed_args): class DeleteNetworkQosPolicy(command.Command): - """Delete Qos Policy(s)""" + _description = _("Delete Qos Policy(s)") def get_parser(self, prog_name): parser = super(DeleteNetworkQosPolicy, self).get_parser(prog_name) @@ -136,7 +136,7 @@ def take_action(self, parsed_args): class ListNetworkQosPolicy(command.Lister): - """List QoS policies""" + _description = _("List QoS policies") def take_action(self, parsed_args): client = self.app.client_manager.network @@ -161,7 +161,7 @@ def take_action(self, parsed_args): class SetNetworkQosPolicy(command.Command): - """Set QoS policy properties""" + _description = _("Set QoS policy properties") def get_parser(self, prog_name): parser = super(SetNetworkQosPolicy, self).get_parser(prog_name) @@ -211,7 +211,7 @@ def take_action(self, parsed_args): class ShowNetworkQosPolicy(command.ShowOne): - """Display QoS policy details""" + _description = _("Display QoS policy details") def get_parser(self, prog_name): parser = super(ShowNetworkQosPolicy, self).get_parser(prog_name) diff --git a/openstackclient/network/v2/network_rbac.py b/openstackclient/network/v2/network_rbac.py index b1e0413f71..2bcdd811b0 100644 --- a/openstackclient/network/v2/network_rbac.py +++ b/openstackclient/network/v2/network_rbac.py @@ -71,7 +71,7 @@ def _get_attrs(client_manager, parsed_args): class CreateNetworkRBAC(command.ShowOne): - """Create network RBAC policy""" + _description = _("Create network RBAC policy") def get_parser(self, prog_name): parser = super(CreateNetworkRBAC, self).get_parser(prog_name) @@ -128,7 +128,7 @@ def take_action(self, parsed_args): class DeleteNetworkRBAC(command.Command): - """Delete network RBAC policy(s)""" + _description = _("Delete network RBAC policy(s)") def get_parser(self, prog_name): parser = super(DeleteNetworkRBAC, self).get_parser(prog_name) @@ -162,7 +162,7 @@ def take_action(self, parsed_args): class ListNetworkRBAC(command.Lister): - """List network RBAC policies""" + _description = _("List network RBAC policies") def take_action(self, parsed_args): client = self.app.client_manager.network @@ -186,7 +186,7 @@ def take_action(self, parsed_args): class SetNetworkRBAC(command.Command): - """Set network RBAC policy properties""" + _description = _("Set network RBAC policy properties") def get_parser(self, prog_name): parser = super(SetNetworkRBAC, self).get_parser(prog_name) @@ -227,7 +227,7 @@ def take_action(self, parsed_args): class ShowNetworkRBAC(command.ShowOne): - """Display network RBAC policy details""" + _description = _("Display network RBAC policy details") def get_parser(self, prog_name): parser = super(ShowNetworkRBAC, self).get_parser(prog_name) diff --git a/openstackclient/network/v2/network_segment.py b/openstackclient/network/v2/network_segment.py index ce1350028f..709dc2965a 100644 --- a/openstackclient/network/v2/network_segment.py +++ b/openstackclient/network/v2/network_segment.py @@ -31,7 +31,7 @@ def _get_columns(item): class CreateNetworkSegment(command.ShowOne): - """Create new network segment""" + _description = _("Create new network segment") def get_parser(self, prog_name): parser = super(CreateNetworkSegment, self).get_parser(prog_name) @@ -95,7 +95,7 @@ def take_action(self, parsed_args): class DeleteNetworkSegment(command.Command): - """Delete network segment(s)""" + _description = _("Delete network segment(s)") def get_parser(self, prog_name): parser = super(DeleteNetworkSegment, self).get_parser(prog_name) @@ -130,7 +130,7 @@ def take_action(self, parsed_args): class ListNetworkSegment(command.Lister): - """List network segments""" + _description = _("List network segments") def get_parser(self, prog_name): parser = super(ListNetworkSegment, self).get_parser(prog_name) @@ -190,7 +190,7 @@ def take_action(self, parsed_args): class SetNetworkSegment(command.Command): - """Set network segment properties""" + _description = _("Set network segment properties") def get_parser(self, prog_name): parser = super(SetNetworkSegment, self).get_parser(prog_name) @@ -224,7 +224,7 @@ def take_action(self, parsed_args): class ShowNetworkSegment(command.ShowOne): - """Display network segment details""" + _description = _("Display network segment details") def get_parser(self, prog_name): parser = super(ShowNetworkSegment, self).get_parser(prog_name) diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index b0d41d2bd1..64b30c7c1c 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -230,7 +230,7 @@ def _add_updatable_args(parser): class CreatePort(command.ShowOne): - """Create a new port""" + _description = _("Create a new port") def get_parser(self, prog_name): parser = super(CreatePort, self).get_parser(prog_name) @@ -330,7 +330,7 @@ def take_action(self, parsed_args): class DeletePort(command.Command): - """Delete port(s)""" + _description = _("Delete port(s)") def get_parser(self, prog_name): parser = super(DeletePort, self).get_parser(prog_name) @@ -364,7 +364,7 @@ def take_action(self, parsed_args): class ListPort(command.Lister): - """List ports""" + _description = _("List ports") def get_parser(self, prog_name): parser = super(ListPort, self).get_parser(prog_name) @@ -454,7 +454,7 @@ def take_action(self, parsed_args): class SetPort(command.Command): - """Set port properties""" + _description = _("Set port properties") def get_parser(self, prog_name): parser = super(SetPort, self).get_parser(prog_name) @@ -570,7 +570,7 @@ def take_action(self, parsed_args): class ShowPort(command.ShowOne): - """Display port details""" + _description = _("Display port details") def get_parser(self, prog_name): parser = super(ShowPort, self).get_parser(prog_name) @@ -590,7 +590,7 @@ def take_action(self, parsed_args): class UnsetPort(command.Command): - """Unset port properties""" + _description = _("Unset port properties") def get_parser(self, prog_name): parser = super(UnsetPort, self).get_parser(prog_name) diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index d96c314a9f..cbd412b585 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -100,7 +100,7 @@ def _get_attrs(client_manager, parsed_args): class AddPortToRouter(command.Command): - """Add a port to a router""" + _description = _("Add a port to a router") def get_parser(self, prog_name): parser = super(AddPortToRouter, self).get_parser(prog_name) @@ -124,7 +124,7 @@ def take_action(self, parsed_args): class AddSubnetToRouter(command.Command): - """Add a subnet to a router""" + _description = _("Add a subnet to a router") def get_parser(self, prog_name): parser = super(AddSubnetToRouter, self).get_parser(prog_name) @@ -151,7 +151,7 @@ def take_action(self, parsed_args): class CreateRouter(command.ShowOne): - """Create a new router""" + _description = _("Create a new router") def get_parser(self, prog_name): parser = super(CreateRouter, self).get_parser(prog_name) @@ -222,7 +222,7 @@ def take_action(self, parsed_args): class DeleteRouter(command.Command): - """Delete router(s)""" + _description = _("Delete router(s)") def get_parser(self, prog_name): parser = super(DeleteRouter, self).get_parser(prog_name) @@ -256,7 +256,7 @@ def take_action(self, parsed_args): class ListRouter(command.Lister): - """List routers""" + _description = _("List routers") def get_parser(self, prog_name): parser = super(ListRouter, self).get_parser(prog_name) @@ -344,7 +344,7 @@ def take_action(self, parsed_args): class RemovePortFromRouter(command.Command): - """Remove a port from a router""" + _description = _("Remove a port from a router") def get_parser(self, prog_name): parser = super(RemovePortFromRouter, self).get_parser(prog_name) @@ -368,7 +368,7 @@ def take_action(self, parsed_args): class RemoveSubnetFromRouter(command.Command): - """Remove a subnet from a router""" + _description = _("Remove a subnet from a router") def get_parser(self, prog_name): parser = super(RemoveSubnetFromRouter, self).get_parser(prog_name) @@ -395,7 +395,7 @@ def take_action(self, parsed_args): class SetRouter(command.Command): - """Set router properties""" + _description = _("Set router properties") def get_parser(self, prog_name): parser = super(SetRouter, self).get_parser(prog_name) @@ -509,7 +509,7 @@ def take_action(self, parsed_args): class ShowRouter(command.ShowOne): - """Display router details""" + _description = _("Display router details") def get_parser(self, prog_name): parser = super(ShowRouter, self).get_parser(prog_name) @@ -529,7 +529,7 @@ def take_action(self, parsed_args): class UnsetRouter(command.Command): - """Unset router properties""" + _description = _("Unset router properties") def get_parser(self, prog_name): parser = super(UnsetRouter, self).get_parser(prog_name) diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py index f832f721ef..554dd61d6f 100644 --- a/openstackclient/network/v2/security_group.py +++ b/openstackclient/network/v2/security_group.py @@ -95,7 +95,7 @@ def _get_columns(item): class CreateSecurityGroup(common.NetworkAndComputeShowOne): - """Create a new security group""" + _description = _("Create a new security group") def update_parser_common(self, parser): parser.add_argument( @@ -165,7 +165,7 @@ def take_action_compute(self, client, parsed_args): class DeleteSecurityGroup(common.NetworkAndComputeDelete): - """Delete security group(s)""" + _description = _("Delete security group(s)") # Used by base class to find resources in parsed_args. resource = 'group' @@ -190,7 +190,7 @@ def take_action_compute(self, client, parsed_args): class ListSecurityGroup(common.NetworkAndComputeLister): - """List security groups""" + _description = _("List security groups") def update_parser_network(self, parser): # Maintain and hide the argument for backwards compatibility. @@ -238,7 +238,7 @@ def take_action_compute(self, client, parsed_args): class SetSecurityGroup(common.NetworkAndComputeCommand): - """Set security group properties""" + _description = _("Set security group properties") def update_parser_common(self, parser): parser.add_argument( @@ -293,7 +293,7 @@ def take_action_compute(self, client, parsed_args): class ShowSecurityGroup(common.NetworkAndComputeShowOne): - """Display security group details""" + _description = _("Display security group details") def update_parser_common(self, parser): parser.add_argument( diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index b7ca0eafec..05fafc0de8 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -90,7 +90,7 @@ def _is_icmp_protocol(protocol): class CreateSecurityGroupRule(common.NetworkAndComputeShowOne): - """Create a new security group rule""" + _description = _("Create a new security group rule") def update_parser_common(self, parser): parser.add_argument( @@ -394,7 +394,7 @@ def take_action_compute(self, client, parsed_args): class DeleteSecurityGroupRule(common.NetworkAndComputeDelete): - """Delete security group rule(s)""" + _description = _("Delete security group rule(s)") # Used by base class to find resources in parsed_args. resource = 'rule' @@ -419,7 +419,7 @@ def take_action_compute(self, client, parsed_args): class ListSecurityGroupRule(common.NetworkAndComputeLister): - """List security group rules""" + _description = _("List security group rules") def update_parser_common(self, parser): parser.add_argument( @@ -584,7 +584,7 @@ def take_action_compute(self, client, parsed_args): class ShowSecurityGroupRule(common.NetworkAndComputeShowOne): - """Display security group rule details""" + _description = _("Display security group rule details") def update_parser_common(self, parser): parser.add_argument( diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index f1ecb5a727..eda9294809 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -218,7 +218,7 @@ def _get_attrs(client_manager, parsed_args, is_create=True): class CreateSubnet(command.ShowOne): - """Create a subnet""" + _description = _("Create a subnet") def get_parser(self, prog_name): parser = super(CreateSubnet, self).get_parser(prog_name) @@ -329,7 +329,7 @@ def take_action(self, parsed_args): class DeleteSubnet(command.Command): - """Delete subnet(s)""" + _description = _("Delete subnet(s)") def get_parser(self, prog_name): parser = super(DeleteSubnet, self).get_parser(prog_name) @@ -363,7 +363,7 @@ def take_action(self, parsed_args): class ListSubnet(command.Lister): - """List subnets""" + _description = _("List subnets") def get_parser(self, prog_name): parser = super(ListSubnet, self).get_parser(prog_name) @@ -484,7 +484,7 @@ def take_action(self, parsed_args): class SetSubnet(command.Command): - """Set subnet properties""" + _description = _("Set subnet properties") def get_parser(self, prog_name): parser = super(SetSubnet, self).get_parser(prog_name) @@ -550,7 +550,7 @@ def take_action(self, parsed_args): class ShowSubnet(command.ShowOne): - """Display subnet details""" + _description = _("Display subnet details") def get_parser(self, prog_name): parser = super(ShowSubnet, self).get_parser(prog_name) @@ -570,7 +570,7 @@ def take_action(self, parsed_args): class UnsetSubnet(command.Command): - """Unset subnet properties""" + _description = _("Unset subnet properties") def get_parser(self, prog_name): parser = super(UnsetSubnet, self).get_parser(prog_name) diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index 5ebdea024e..a5a244240e 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -12,6 +12,7 @@ # """Subnet pool action implementations""" + import copy import logging @@ -141,7 +142,7 @@ def _add_default_options(parser): # TODO(rtheis): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. class CreateSubnetPool(command.ShowOne): - """Create subnet pool""" + _description = _("Create subnet pool") def get_parser(self, prog_name): parser = super(CreateSubnetPool, self).get_parser(prog_name) @@ -196,7 +197,7 @@ def take_action(self, parsed_args): class DeleteSubnetPool(command.Command): - """Delete subnet pool(s)""" + _description = _("Delete subnet pool(s)") def get_parser(self, prog_name): parser = super(DeleteSubnetPool, self).get_parser(prog_name) @@ -232,7 +233,7 @@ def take_action(self, parsed_args): # TODO(rtheis): Use only the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. class ListSubnetPool(command.Lister): - """List subnet pools""" + _description = _("List subnet pools") def get_parser(self, prog_name): parser = super(ListSubnetPool, self).get_parser(prog_name) @@ -334,7 +335,7 @@ def take_action(self, parsed_args): # TODO(rtheis): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. class SetSubnetPool(command.Command): - """Set subnet pool properties""" + _description = _("Set subnet pool properties") def get_parser(self, prog_name): parser = super(SetSubnetPool, self).get_parser(prog_name) @@ -386,7 +387,7 @@ def take_action(self, parsed_args): class ShowSubnetPool(command.ShowOne): - """Display subnet pool details""" + _description = _("Display subnet pool details") def get_parser(self, prog_name): parser = super(ShowSubnetPool, self).get_parser(prog_name) @@ -409,7 +410,7 @@ def take_action(self, parsed_args): class UnsetSubnetPool(command.Command): - """Unset subnet pool properties""" + _description = _("Unset subnet pool properties") def get_parser(self, prog_name): parser = super(UnsetSubnetPool, self).get_parser(prog_name) diff --git a/openstackclient/object/v1/account.py b/openstackclient/object/v1/account.py index 2fe00ecbc3..4847f8bbc2 100644 --- a/openstackclient/object/v1/account.py +++ b/openstackclient/object/v1/account.py @@ -22,7 +22,7 @@ class SetAccount(command.Command): - """Set account properties""" + _description = _("Set account properties") def get_parser(self, prog_name): parser = super(SetAccount, self).get_parser(prog_name) @@ -43,7 +43,7 @@ def take_action(self, parsed_args): class ShowAccount(command.ShowOne): - """Display account details""" + _description = _("Display account details") def take_action(self, parsed_args): data = self.app.client_manager.object_store.account_show() @@ -53,7 +53,7 @@ def take_action(self, parsed_args): class UnsetAccount(command.Command): - """Unset account properties""" + _description = _("Unset account properties") def get_parser(self, prog_name): parser = super(UnsetAccount, self).get_parser(prog_name) diff --git a/openstackclient/object/v1/container.py b/openstackclient/object/v1/container.py index 01964d0c0b..88fb860244 100644 --- a/openstackclient/object/v1/container.py +++ b/openstackclient/object/v1/container.py @@ -29,7 +29,7 @@ class CreateContainer(command.Lister): - """Create new container""" + _description = _("Create new container") def get_parser(self, prog_name): parser = super(CreateContainer, self).get_parser(prog_name) @@ -63,7 +63,7 @@ def take_action(self, parsed_args): class DeleteContainer(command.Command): - """Delete container""" + _description = _("Delete container") def get_parser(self, prog_name): parser = super(DeleteContainer, self).get_parser(prog_name) @@ -98,7 +98,7 @@ def take_action(self, parsed_args): class ListContainer(command.Lister): - """List containers""" + _description = _("List containers") def get_parser(self, prog_name): parser = super(ListContainer, self).get_parser(prog_name) @@ -168,7 +168,7 @@ def take_action(self, parsed_args): class SaveContainer(command.Command): - """Save container contents locally""" + _description = _("Save container contents locally") def get_parser(self, prog_name): parser = super(SaveContainer, self).get_parser(prog_name) @@ -186,7 +186,7 @@ def take_action(self, parsed_args): class SetContainer(command.Command): - """Set container properties""" + _description = _("Set container properties") def get_parser(self, prog_name): parser = super(SetContainer, self).get_parser(prog_name) @@ -213,7 +213,7 @@ def take_action(self, parsed_args): class ShowContainer(command.ShowOne): - """Display container details""" + _description = _("Display container details") def get_parser(self, prog_name): parser = super(ShowContainer, self).get_parser(prog_name) @@ -236,7 +236,7 @@ def take_action(self, parsed_args): class UnsetContainer(command.Command): - """Unset container properties""" + _description = _("Unset container properties") def get_parser(self, prog_name): parser = super(UnsetContainer, self).get_parser(prog_name) diff --git a/openstackclient/object/v1/object.py b/openstackclient/object/v1/object.py index 3c47ee0428..71b6f52015 100644 --- a/openstackclient/object/v1/object.py +++ b/openstackclient/object/v1/object.py @@ -30,7 +30,7 @@ class CreateObject(command.Lister): - """Upload object to container""" + _description = _("Upload object to container") def get_parser(self, prog_name): parser = super(CreateObject, self).get_parser(prog_name) @@ -81,7 +81,7 @@ def take_action(self, parsed_args): class DeleteObject(command.Command): - """Delete object from container""" + _description = _("Delete object from container") def get_parser(self, prog_name): parser = super(DeleteObject, self).get_parser(prog_name) @@ -108,7 +108,7 @@ def take_action(self, parsed_args): class ListObject(command.Lister): - """List objects""" + _description = _("List objects") def get_parser(self, prog_name): parser = super(ListObject, self).get_parser(prog_name) @@ -197,7 +197,7 @@ def take_action(self, parsed_args): class SaveObject(command.Command): - """Save object locally""" + _description = _("Save object locally") def get_parser(self, prog_name): parser = super(SaveObject, self).get_parser(prog_name) @@ -227,7 +227,7 @@ def take_action(self, parsed_args): class SetObject(command.Command): - """Set object properties""" + _description = _("Set object properties") def get_parser(self, prog_name): parser = super(SetObject, self).get_parser(prog_name) @@ -260,7 +260,7 @@ def take_action(self, parsed_args): class ShowObject(command.ShowOne): - """Display object details""" + _description = _("Display object details") def get_parser(self, prog_name): parser = super(ShowObject, self).get_parser(prog_name) @@ -289,7 +289,7 @@ def take_action(self, parsed_args): class UnsetObject(command.Command): - """Unset object properties""" + _description = _("Unset object properties") def get_parser(self, prog_name): parser = super(UnsetObject, self).get_parser(prog_name) diff --git a/openstackclient/volume/v1/backup.py b/openstackclient/volume/v1/backup.py index a02cdccb22..9ac1302a55 100644 --- a/openstackclient/volume/v1/backup.py +++ b/openstackclient/volume/v1/backup.py @@ -30,7 +30,7 @@ class CreateVolumeBackup(command.ShowOne): - """Create new volume backup""" + _description = _("Create new volume backup") def get_parser(self, prog_name): parser = super(CreateVolumeBackup, self).get_parser(prog_name) @@ -73,7 +73,7 @@ def take_action(self, parsed_args): class CreateBackup(CreateVolumeBackup): - """Create new backup""" + _description = _("Create new backup") # TODO(Huanxuan Ao): Remove this class and ``backup create`` command # two cycles after Newton. @@ -90,7 +90,7 @@ def take_action(self, parsed_args): class DeleteVolumeBackup(command.Command): - """Delete volume backup(s)""" + _description = _("Delete volume backup(s)") def get_parser(self, prog_name): parser = super(DeleteVolumeBackup, self).get_parser(prog_name) @@ -125,7 +125,7 @@ def take_action(self, parsed_args): class DeleteBackup(DeleteVolumeBackup): - """Delete backup(s)""" + _description = _("Delete backup(s)") # TODO(Huanxuan Ao): Remove this class and ``backup delete`` command # two cycles after Newton. @@ -142,7 +142,7 @@ def take_action(self, parsed_args): class ListVolumeBackup(command.Lister): - """List volume backups""" + _description = _("List volume backups") def get_parser(self, prog_name): parser = super(ListVolumeBackup, self).get_parser(prog_name) @@ -235,7 +235,7 @@ def _format_volume_id(volume_id): class ListBackup(ListVolumeBackup): - """List backups""" + _description = _("List backups") # TODO(Huanxuan Ao): Remove this class and ``backup list`` command # two cycles after Newton. @@ -252,7 +252,7 @@ def take_action(self, parsed_args): class RestoreVolumeBackup(command.Command): - """Restore volume backup""" + _description = _("Restore volume backup") def get_parser(self, prog_name): parser = super(RestoreVolumeBackup, self).get_parser(prog_name) @@ -279,7 +279,7 @@ def take_action(self, parsed_args): class RestoreBackup(RestoreVolumeBackup): - """Restore backup""" + _description = _("Restore backup") # TODO(Huanxuan Ao): Remove this class and ``backup restore`` command # two cycles after Newton. @@ -296,7 +296,7 @@ def take_action(self, parsed_args): class ShowVolumeBackup(command.ShowOne): - """Display volume backup details""" + _description = _("Display volume backup details") def get_parser(self, prog_name): parser = super(ShowVolumeBackup, self).get_parser(prog_name) @@ -316,7 +316,7 @@ def take_action(self, parsed_args): class ShowBackup(ShowVolumeBackup): - """Display backup details""" + _description = _("Display backup details") # TODO(Huanxuan Ao): Remove this class and ``backup show`` command # two cycles after Newton. diff --git a/openstackclient/volume/v1/qos_specs.py b/openstackclient/volume/v1/qos_specs.py index 93c24a212a..b824b35179 100644 --- a/openstackclient/volume/v1/qos_specs.py +++ b/openstackclient/volume/v1/qos_specs.py @@ -30,7 +30,7 @@ class AssociateQos(command.Command): - """Associate a QoS specification to a volume type""" + _description = _("Associate a QoS specification to a volume type") def get_parser(self, prog_name): parser = super(AssociateQos, self).get_parser(prog_name) @@ -57,7 +57,7 @@ def take_action(self, parsed_args): class CreateQos(command.ShowOne): - """Create new QoS specification""" + _description = _("Create new QoS specification") def get_parser(self, prog_name): parser = super(CreateQos, self).get_parser(prog_name) @@ -99,7 +99,7 @@ def take_action(self, parsed_args): class DeleteQos(command.Command): - """Delete QoS specification""" + _description = _("Delete QoS specification") def get_parser(self, prog_name): parser = super(DeleteQos, self).get_parser(prog_name) @@ -139,7 +139,7 @@ def take_action(self, parsed_args): class DisassociateQos(command.Command): - """Disassociate a QoS specification from a volume type""" + _description = _("Disassociate a QoS specification from a volume type") def get_parser(self, prog_name): parser = super(DisassociateQos, self).get_parser(prog_name) @@ -177,7 +177,7 @@ def take_action(self, parsed_args): class ListQos(command.Lister): - """List QoS specifications""" + _description = _("List QoS specifications") def take_action(self, parsed_args): volume_client = self.app.client_manager.volume @@ -202,7 +202,7 @@ def take_action(self, parsed_args): class SetQos(command.Command): - """Set QoS specification properties""" + _description = _("Set QoS specification properties") def get_parser(self, prog_name): parser = super(SetQos, self).get_parser(prog_name) @@ -231,7 +231,7 @@ def take_action(self, parsed_args): class ShowQos(command.ShowOne): - """Display QoS specification details""" + _description = _("Display QoS specification details") def get_parser(self, prog_name): parser = super(ShowQos, self).get_parser(prog_name) @@ -260,7 +260,7 @@ def take_action(self, parsed_args): class UnsetQos(command.Command): - """Unset QoS specification properties""" + _description = _("Unset QoS specification properties") def get_parser(self, prog_name): parser = super(UnsetQos, self).get_parser(prog_name) diff --git a/openstackclient/volume/v1/service.py b/openstackclient/volume/v1/service.py index 867c4b9c7f..d468c6ff1e 100644 --- a/openstackclient/volume/v1/service.py +++ b/openstackclient/volume/v1/service.py @@ -22,7 +22,7 @@ class ListService(command.Lister): - """List service command""" + _description = _("List service command") def get_parser(self, prog_name): parser = super(ListService, self).get_parser(prog_name) @@ -76,7 +76,7 @@ def take_action(self, parsed_args): class SetService(command.Command): - """Set volume service properties""" + _description = _("Set volume service properties") def get_parser(self, prog_name): parser = super(SetService, self).get_parser(prog_name) diff --git a/openstackclient/volume/v1/snapshot.py b/openstackclient/volume/v1/snapshot.py index bc92c0f56d..0f91ee7251 100644 --- a/openstackclient/volume/v1/snapshot.py +++ b/openstackclient/volume/v1/snapshot.py @@ -31,7 +31,7 @@ class CreateSnapshot(command.ShowOne): - """Create new snapshot""" + _description = _("Create new snapshot") def get_parser(self, prog_name): parser = super(CreateSnapshot, self).get_parser(prog_name) @@ -79,7 +79,7 @@ def take_action(self, parsed_args): class DeleteSnapshot(command.Command): - """Delete snapshot(s)""" + _description = _("Delete snapshot(s)") def get_parser(self, prog_name): parser = super(DeleteSnapshot, self).get_parser(prog_name) @@ -114,7 +114,7 @@ def take_action(self, parsed_args): class ListSnapshot(command.Lister): - """List snapshots""" + _description = _("List snapshots") def get_parser(self, prog_name): parser = super(ListSnapshot, self).get_parser(prog_name) @@ -185,7 +185,7 @@ def _format_volume_id(volume_id): class SetSnapshot(command.Command): - """Set snapshot properties""" + _description = _("Set snapshot properties") def get_parser(self, prog_name): parser = super(SetSnapshot, self).get_parser(prog_name) @@ -246,7 +246,7 @@ def take_action(self, parsed_args): class ShowSnapshot(command.ShowOne): - """Display snapshot details""" + _description = _("Display snapshot details") def get_parser(self, prog_name): parser = super(ShowSnapshot, self).get_parser(prog_name) @@ -270,7 +270,7 @@ def take_action(self, parsed_args): class UnsetSnapshot(command.Command): - """Unset snapshot properties""" + _description = _("Unset snapshot properties") def get_parser(self, prog_name): parser = super(UnsetSnapshot, self).get_parser(prog_name) diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index bfdc9d806f..0087bad480 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -45,7 +45,7 @@ def _check_size_arg(args): class CreateVolume(command.ShowOne): - """Create new volume""" + _description = _("Create new volume") def get_parser(self, prog_name): parser = super(CreateVolume, self).get_parser(prog_name) @@ -178,7 +178,7 @@ def take_action(self, parsed_args): class DeleteVolume(command.Command): - """Delete volume(s)""" + _description = _("Delete volume(s)") def get_parser(self, prog_name): parser = super(DeleteVolume, self).get_parser(prog_name) @@ -223,7 +223,7 @@ def take_action(self, parsed_args): class ListVolume(command.Lister): - """List volumes""" + _description = _("List volumes") def get_parser(self, prog_name): parser = super(ListVolume, self).get_parser(prog_name) @@ -345,7 +345,7 @@ def _format_attach(attachments): class MigrateVolume(command.Command): - """Migrate volume to a new host""" + _description = _("Migrate volume to a new host") def get_parser(self, prog_name): parser = super(MigrateVolume, self).get_parser(prog_name) @@ -376,7 +376,7 @@ def take_action(self, parsed_args): class SetVolume(command.Command): - """Set volume properties""" + _description = _("Set volume properties") def get_parser(self, prog_name): parser = super(SetVolume, self).get_parser(prog_name) @@ -494,7 +494,7 @@ def take_action(self, parsed_args): class ShowVolume(command.ShowOne): - """Show volume details""" + _description = _("Show volume details") def get_parser(self, prog_name): parser = super(ShowVolume, self).get_parser(prog_name) @@ -524,7 +524,7 @@ def take_action(self, parsed_args): class UnsetVolume(command.Command): - """Unset volume properties""" + _description = _("Unset volume properties") def get_parser(self, prog_name): parser = super(UnsetVolume, self).get_parser(prog_name) diff --git a/openstackclient/volume/v1/volume_transfer_request.py b/openstackclient/volume/v1/volume_transfer_request.py index 4d6f216132..f24d5a5667 100644 --- a/openstackclient/volume/v1/volume_transfer_request.py +++ b/openstackclient/volume/v1/volume_transfer_request.py @@ -28,7 +28,7 @@ class AcceptTransferRequest(command.ShowOne): - """Accept volume transfer request.""" + _description = _("Accept volume transfer request.") def get_parser(self, prog_name): parser = super(AcceptTransferRequest, self).get_parser(prog_name) @@ -56,7 +56,7 @@ def take_action(self, parsed_args): class CreateTransferRequest(command.ShowOne): - """Create volume transfer request.""" + _description = _("Create volume transfer request.") def get_parser(self, prog_name): parser = super(CreateTransferRequest, self).get_parser(prog_name) @@ -85,7 +85,7 @@ def take_action(self, parsed_args): class DeleteTransferRequest(command.Command): - """Delete volume transfer request(s).""" + _description = _("Delete volume transfer request(s).") def get_parser(self, prog_name): parser = super(DeleteTransferRequest, self).get_parser(prog_name) @@ -120,7 +120,7 @@ def take_action(self, parsed_args): class ListTransferRequest(command.Lister): - """Lists all volume transfer requests.""" + _description = _("Lists all volume transfer requests.") def get_parser(self, prog_name): parser = super(ListTransferRequest, self).get_parser(prog_name) @@ -151,7 +151,7 @@ def take_action(self, parsed_args): class ShowTransferRequest(command.ShowOne): - """Show volume transfer request details.""" + _description = _("Show volume transfer request details.") def get_parser(self, prog_name): parser = super(ShowTransferRequest, self).get_parser(prog_name) diff --git a/openstackclient/volume/v1/volume_type.py b/openstackclient/volume/v1/volume_type.py index 61e9f7fca1..4f159239b5 100644 --- a/openstackclient/volume/v1/volume_type.py +++ b/openstackclient/volume/v1/volume_type.py @@ -30,7 +30,7 @@ class CreateVolumeType(command.ShowOne): - """Create new volume type""" + _description = _("Create new volume type") def get_parser(self, prog_name): parser = super(CreateVolumeType, self).get_parser(prog_name) @@ -61,7 +61,7 @@ def take_action(self, parsed_args): class DeleteVolumeType(command.Command): - """Delete volume type(s)""" + _description = _("Delete volume type(s)") def get_parser(self, prog_name): parser = super(DeleteVolumeType, self).get_parser(prog_name) @@ -97,7 +97,7 @@ def take_action(self, parsed_args): class ListVolumeType(command.Lister): - """List volume types""" + _description = _("List volume types") def get_parser(self, prog_name): parser = super(ListVolumeType, self).get_parser(prog_name) @@ -125,7 +125,7 @@ def take_action(self, parsed_args): class SetVolumeType(command.Command): - """Set volume type properties""" + _description = _("Set volume type properties") def get_parser(self, prog_name): parser = super(SetVolumeType, self).get_parser(prog_name) @@ -153,7 +153,7 @@ def take_action(self, parsed_args): class ShowVolumeType(command.ShowOne): - """Display volume type details""" + _description = _("Display volume type details") def get_parser(self, prog_name): parser = super(ShowVolumeType, self).get_parser(prog_name) @@ -175,7 +175,7 @@ def take_action(self, parsed_args): class UnsetVolumeType(command.Command): - """Unset volume type properties""" + _description = _("Unset volume type properties") def get_parser(self, prog_name): parser = super(UnsetVolumeType, self).get_parser(prog_name) diff --git a/openstackclient/volume/v2/backup.py b/openstackclient/volume/v2/backup.py index c41cffda38..00389fcb86 100644 --- a/openstackclient/volume/v2/backup.py +++ b/openstackclient/volume/v2/backup.py @@ -30,7 +30,7 @@ class CreateVolumeBackup(command.ShowOne): - """Create new volume backup""" + _description = _("Create new volume backup") def get_parser(self, prog_name): parser = super(CreateVolumeBackup, self).get_parser(prog_name) @@ -95,7 +95,7 @@ def take_action(self, parsed_args): class CreateBackup(CreateVolumeBackup): - """Create new backup""" + _description = _("Create new backup") # TODO(Huanxuan Ao): Remove this class and ``backup create`` command # two cycles after Newton. @@ -112,7 +112,7 @@ def take_action(self, parsed_args): class DeleteVolumeBackup(command.Command): - """Delete volume backup(s)""" + _description = _("Delete volume backup(s)") def get_parser(self, prog_name): parser = super(DeleteVolumeBackup, self).get_parser(prog_name) @@ -153,7 +153,7 @@ def take_action(self, parsed_args): class DeleteBackup(DeleteVolumeBackup): - """Delete backup(s)""" + _description = _("Delete backup(s)") # TODO(Huanxuan Ao): Remove this class and ``backup delete`` command # two cycles after Newton. @@ -170,7 +170,7 @@ def take_action(self, parsed_args): class ListVolumeBackup(command.Lister): - """List volume backups""" + _description = _("List volume backups") def get_parser(self, prog_name): parser = super(ListVolumeBackup, self).get_parser(prog_name) @@ -281,7 +281,7 @@ def _format_volume_id(volume_id): class ListBackup(ListVolumeBackup): - """List backups""" + _description = _("List backups") # TODO(Huanxuan Ao): Remove this class and ``backup list`` command # two cycles after Newton. @@ -298,7 +298,7 @@ def take_action(self, parsed_args): class RestoreVolumeBackup(command.ShowOne): - """Restore volume backup""" + _description = _("Restore volume backup") def get_parser(self, prog_name): parser = super(RestoreVolumeBackup, self).get_parser(prog_name) @@ -323,7 +323,7 @@ def take_action(self, parsed_args): class RestoreBackup(RestoreVolumeBackup): - """Restore backup""" + _description = _("Restore backup") # TODO(Huanxuan Ao): Remove this class and ``backup restore`` command # two cycles after Newton. @@ -340,7 +340,7 @@ def take_action(self, parsed_args): class SetVolumeBackup(command.Command): - """Set volume backup properties""" + _description = _("Set volume backup properties") def get_parser(self, prog_name): parser = super(SetVolumeBackup, self).get_parser(prog_name) @@ -402,7 +402,7 @@ def take_action(self, parsed_args): class ShowVolumeBackup(command.ShowOne): - """Display volume backup details""" + _description = _("Display volume backup details") def get_parser(self, prog_name): parser = super(ShowVolumeBackup, self).get_parser(prog_name) @@ -422,7 +422,7 @@ def take_action(self, parsed_args): class ShowBackup(ShowVolumeBackup): - """Display backup details""" + _description = _("Display backup details") # TODO(Huanxuan Ao): Remove this class and ``backup show`` command # two cycles after Newton. diff --git a/openstackclient/volume/v2/consistency_group.py b/openstackclient/volume/v2/consistency_group.py index 39f2d57726..0754fdc705 100644 --- a/openstackclient/volume/v2/consistency_group.py +++ b/openstackclient/volume/v2/consistency_group.py @@ -21,7 +21,7 @@ class ListConsistencyGroup(command.Lister): - """List consistency groups.""" + _description = _("List consistency groups.") def get_parser(self, prog_name): parser = super(ListConsistencyGroup, self).get_parser(prog_name) diff --git a/openstackclient/volume/v2/qos_specs.py b/openstackclient/volume/v2/qos_specs.py index 9797f1a69d..b7f49eca14 100644 --- a/openstackclient/volume/v2/qos_specs.py +++ b/openstackclient/volume/v2/qos_specs.py @@ -30,7 +30,7 @@ class AssociateQos(command.Command): - """Associate a QoS specification to a volume type""" + _description = _("Associate a QoS specification to a volume type") def get_parser(self, prog_name): parser = super(AssociateQos, self).get_parser(prog_name) @@ -57,7 +57,7 @@ def take_action(self, parsed_args): class CreateQos(command.ShowOne): - """Create new QoS specification""" + _description = _("Create new QoS specification") def get_parser(self, prog_name): parser = super(CreateQos, self).get_parser(prog_name) @@ -99,7 +99,7 @@ def take_action(self, parsed_args): class DeleteQos(command.Command): - """Delete QoS specification""" + _description = _("Delete QoS specification") def get_parser(self, prog_name): parser = super(DeleteQos, self).get_parser(prog_name) @@ -139,7 +139,7 @@ def take_action(self, parsed_args): class DisassociateQos(command.Command): - """Disassociate a QoS specification from a volume type""" + _description = _("Disassociate a QoS specification from a volume type") def get_parser(self, prog_name): parser = super(DisassociateQos, self).get_parser(prog_name) @@ -177,7 +177,7 @@ def take_action(self, parsed_args): class ListQos(command.Lister): - """List QoS specifications""" + _description = _("List QoS specifications") def take_action(self, parsed_args): volume_client = self.app.client_manager.volume @@ -202,7 +202,7 @@ def take_action(self, parsed_args): class SetQos(command.Command): - """Set QoS specification properties""" + _description = _("Set QoS specification properties") def get_parser(self, prog_name): parser = super(SetQos, self).get_parser(prog_name) @@ -231,7 +231,7 @@ def take_action(self, parsed_args): class ShowQos(command.ShowOne): - """Display QoS specification details""" + _description = _("Display QoS specification details") def get_parser(self, prog_name): parser = super(ShowQos, self).get_parser(prog_name) @@ -260,7 +260,7 @@ def take_action(self, parsed_args): class UnsetQos(command.Command): - """Unset QoS specification properties""" + _description = _("Unset QoS specification properties") def get_parser(self, prog_name): parser = super(UnsetQos, self).get_parser(prog_name) diff --git a/openstackclient/volume/v2/service.py b/openstackclient/volume/v2/service.py index 867c4b9c7f..d468c6ff1e 100644 --- a/openstackclient/volume/v2/service.py +++ b/openstackclient/volume/v2/service.py @@ -22,7 +22,7 @@ class ListService(command.Lister): - """List service command""" + _description = _("List service command") def get_parser(self, prog_name): parser = super(ListService, self).get_parser(prog_name) @@ -76,7 +76,7 @@ def take_action(self, parsed_args): class SetService(command.Command): - """Set volume service properties""" + _description = _("Set volume service properties") def get_parser(self, prog_name): parser = super(SetService, self).get_parser(prog_name) diff --git a/openstackclient/volume/v2/snapshot.py b/openstackclient/volume/v2/snapshot.py index 4994e0da3c..8cda112ac0 100644 --- a/openstackclient/volume/v2/snapshot.py +++ b/openstackclient/volume/v2/snapshot.py @@ -30,7 +30,7 @@ class CreateSnapshot(command.ShowOne): - """Create new snapshot""" + _description = _("Create new snapshot") def get_parser(self, prog_name): parser = super(CreateSnapshot, self).get_parser(prog_name) @@ -83,7 +83,7 @@ def take_action(self, parsed_args): class DeleteSnapshot(command.Command): - """Delete volume snapshot(s)""" + _description = _("Delete volume snapshot(s)") def get_parser(self, prog_name): parser = super(DeleteSnapshot, self).get_parser(prog_name) @@ -118,7 +118,7 @@ def take_action(self, parsed_args): class ListSnapshot(command.Lister): - """List snapshots""" + _description = _("List snapshots") def get_parser(self, prog_name): parser = super(ListSnapshot, self).get_parser(prog_name) @@ -199,7 +199,7 @@ def _format_volume_id(volume_id): class SetSnapshot(command.Command): - """Set snapshot properties""" + _description = _("Set snapshot properties") def get_parser(self, prog_name): parser = super(SetSnapshot, self).get_parser(prog_name) @@ -280,7 +280,7 @@ def take_action(self, parsed_args): class ShowSnapshot(command.ShowOne): - """Display snapshot details""" + _description = _("Display snapshot details") def get_parser(self, prog_name): parser = super(ShowSnapshot, self).get_parser(prog_name) @@ -302,7 +302,7 @@ def take_action(self, parsed_args): class UnsetSnapshot(command.Command): - """Unset snapshot properties""" + _description = _("Unset snapshot properties") def get_parser(self, prog_name): parser = super(UnsetSnapshot, self).get_parser(prog_name) diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index 4a9192a0c4..0531e0aa08 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -45,7 +45,7 @@ def _check_size_arg(args): class CreateVolume(command.ShowOne): - """Create new volume""" + _description = _("Create new volume") def get_parser(self, prog_name): parser = super(CreateVolume, self).get_parser(prog_name) @@ -211,7 +211,7 @@ def take_action(self, parsed_args): class DeleteVolume(command.Command): - """Delete volume(s)""" + _description = _("Delete volume(s)") def get_parser(self, prog_name): parser = super(DeleteVolume, self).get_parser(prog_name) @@ -263,7 +263,7 @@ def take_action(self, parsed_args): class ListVolume(command.Lister): - """List volumes""" + _description = _("List volumes") def get_parser(self, prog_name): parser = super(ListVolume, self).get_parser(prog_name) @@ -410,7 +410,7 @@ def _format_attach(attachments): class MigrateVolume(command.Command): - """Migrate volume to a new host""" + _description = _("Migrate volume to a new host") def get_parser(self, prog_name): parser = super(MigrateVolume, self).get_parser(prog_name) @@ -457,7 +457,7 @@ def take_action(self, parsed_args): class SetVolume(command.Command): - """Set volume properties""" + _description = _("Set volume properties") def get_parser(self, prog_name): parser = super(SetVolume, self).get_parser(prog_name) @@ -610,7 +610,7 @@ def take_action(self, parsed_args): class ShowVolume(command.ShowOne): - """Display volume details""" + _description = _("Display volume details") def get_parser(self, prog_name): parser = super(ShowVolume, self).get_parser(prog_name) @@ -641,7 +641,7 @@ def take_action(self, parsed_args): class UnsetVolume(command.Command): - """Unset volume properties""" + _description = _("Unset volume properties") def get_parser(self, prog_name): parser = super(UnsetVolume, self).get_parser(prog_name) diff --git a/openstackclient/volume/v2/volume_transfer_request.py b/openstackclient/volume/v2/volume_transfer_request.py index 9008fe3c24..aefe594a31 100644 --- a/openstackclient/volume/v2/volume_transfer_request.py +++ b/openstackclient/volume/v2/volume_transfer_request.py @@ -28,7 +28,7 @@ class AcceptTransferRequest(command.ShowOne): - """Accept volume transfer request.""" + _description = _("Accept volume transfer request.") def get_parser(self, prog_name): parser = super(AcceptTransferRequest, self).get_parser(prog_name) @@ -56,7 +56,7 @@ def take_action(self, parsed_args): class CreateTransferRequest(command.ShowOne): - """Create volume transfer request.""" + _description = _("Create volume transfer request.") def get_parser(self, prog_name): parser = super(CreateTransferRequest, self).get_parser(prog_name) @@ -85,7 +85,7 @@ def take_action(self, parsed_args): class DeleteTransferRequest(command.Command): - """Delete volume transfer request(s).""" + _description = _("Delete volume transfer request(s).") def get_parser(self, prog_name): parser = super(DeleteTransferRequest, self).get_parser(prog_name) @@ -120,7 +120,7 @@ def take_action(self, parsed_args): class ListTransferRequest(command.Lister): - """Lists all volume transfer requests.""" + _description = _("Lists all volume transfer requests.") def get_parser(self, prog_name): parser = super(ListTransferRequest, self).get_parser(prog_name) @@ -151,7 +151,7 @@ def take_action(self, parsed_args): class ShowTransferRequest(command.ShowOne): - """Show volume transfer request details.""" + _description = _("Show volume transfer request details.") def get_parser(self, prog_name): parser = super(ShowTransferRequest, self).get_parser(prog_name) diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index 80a1f21b11..0fcfaf7e8d 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -30,7 +30,7 @@ class CreateVolumeType(command.ShowOne): - """Create new volume type""" + _description = _("Create new volume type") def get_parser(self, prog_name): parser = super(CreateVolumeType, self).get_parser(prog_name) @@ -116,7 +116,7 @@ def take_action(self, parsed_args): class DeleteVolumeType(command.Command): - """Delete volume type(s)""" + _description = _("Delete volume type(s)") def get_parser(self, prog_name): parser = super(DeleteVolumeType, self).get_parser(prog_name) @@ -152,7 +152,7 @@ def take_action(self, parsed_args): class ListVolumeType(command.Lister): - """List volume types""" + _description = _("List volume types") def get_parser(self, prog_name): parser = super(ListVolumeType, self).get_parser(prog_name) @@ -197,7 +197,7 @@ def take_action(self, parsed_args): class SetVolumeType(command.Command): - """Set volume type properties""" + _description = _("Set volume type properties") def get_parser(self, prog_name): parser = super(SetVolumeType, self).get_parser(prog_name) @@ -286,7 +286,7 @@ def take_action(self, parsed_args): class ShowVolumeType(command.ShowOne): - """Display volume type details""" + _description = _("Display volume type details") def get_parser(self, prog_name): parser = super(ShowVolumeType, self).get_parser(prog_name) @@ -324,7 +324,7 @@ def take_action(self, parsed_args): class UnsetVolumeType(command.Command): - """Unset volume type properties""" + _description = _("Unset volume type properties") def get_parser(self, prog_name): parser = super(UnsetVolumeType, self).get_parser(prog_name) From 98b9bc10d1ab6c205dee7ca3813034ccd1de1005 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Thu, 10 Nov 2016 21:26:19 +0800 Subject: [PATCH 1349/3095] Add "consistency group create" command Add "consistency group create" command in volume v2 (v2 only). Change-Id: I2e9affe390b1012aa18459e64d04afcdfc15e27d Implements: bp cinder-command-support Partial-Bug: #1613964 --- .../command-objects/consistency-group.rst | 36 +++++ .../unit/volume/v2/test_consistency_group.py | 132 ++++++++++++++++++ .../volume/v2/consistency_group.py | 77 ++++++++++ .../notes/bug-1613964-837196399be16b3d.yaml | 4 + setup.cfg | 1 + 5 files changed, 250 insertions(+) create mode 100644 releasenotes/notes/bug-1613964-837196399be16b3d.yaml diff --git a/doc/source/command-objects/consistency-group.rst b/doc/source/command-objects/consistency-group.rst index f24df0d11c..c8c4577ccf 100644 --- a/doc/source/command-objects/consistency-group.rst +++ b/doc/source/command-objects/consistency-group.rst @@ -4,6 +4,42 @@ consistency group Block Storage v2 +consistency group create +------------------------ + +Create new consistency group. + +.. program:: consistency group create +.. code:: bash + + os consistency group create + --volume-type | --consistency-group-source + [--description ] + [--availability-zone ] + [] + +.. option:: --volume-type + + Volume type of this consistency group (name or ID) + +.. option:: --consistency-group-source + + Existing consistency group (name or ID) + +.. option:: --description + + Description of this consistency group + +.. option:: --availability-zone + + Availability zone for this consistency group + (not available if creating consistency group from source) + +.. _consistency_group_create-name: +.. option:: + + Name of new consistency group (default to None) + consistency group list ---------------------- diff --git a/openstackclient/tests/unit/volume/v2/test_consistency_group.py b/openstackclient/tests/unit/volume/v2/test_consistency_group.py index 00e1b60e88..e005642169 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 @@ def setUp(self): 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/volume/v2/consistency_group.py b/openstackclient/volume/v2/consistency_group.py index 0754fdc705..9316f28737 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="", + 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="", + help=_("Volume type of this consistency group (name or ID)") + ) + exclusive_group.add_argument( + "--consistency-group-source", + metavar="", + help=_("Existing consistency group (name or ID)") + ) + parser.add_argument( + "--description", + metavar="", + help=_("Description of this consistency group") + ) + parser.add_argument( + "--availability-zone", + metavar="", + 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/releasenotes/notes/bug-1613964-837196399be16b3d.yaml b/releasenotes/notes/bug-1613964-837196399be16b3d.yaml new file mode 100644 index 0000000000..0f17930ebb --- /dev/null +++ b/releasenotes/notes/bug-1613964-837196399be16b3d.yaml @@ -0,0 +1,4 @@ +--- +features: + - Add ``consistency group create`` command in volume v2. + [Bug `1613964 `_] diff --git a/setup.cfg b/setup.cfg index c94437393e..e86eba4d82 100644 --- a/setup.cfg +++ b/setup.cfg @@ -506,6 +506,7 @@ openstack.volume.v2 = backup_restore = openstackclient.volume.v2.backup:RestoreBackup backup_show = openstackclient.volume.v2.backup:ShowBackup + consistency_group_create = openstackclient.volume.v2.consistency_group:CreateConsistencyGroup consistency_group_list = openstackclient.volume.v2.consistency_group:ListConsistencyGroup snapshot_create = openstackclient.volume.v2.snapshot:CreateSnapshot From 4bce7167338908b082eb8ea946adcd60109e0907 Mon Sep 17 00:00:00 2001 From: Fei Long Wang Date: Thu, 17 Nov 2016 16:59:57 +1300 Subject: [PATCH 1350/3095] Using v2 as the default version of Glance Glance API v1 has been deprecated, so it's better to use v2 as the default API version in openstackclient. Closes-Bug: 1642772 Change-Id: I7d9e3228a2f3a3d0da437b7ee6f23e528de27fd3 --- openstackclient/image/client.py | 2 +- releasenotes/notes/bug-1642772-19f53765bef8ee91.yaml | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/bug-1642772-19f53765bef8ee91.yaml diff --git a/openstackclient/image/client.py b/openstackclient/image/client.py index 1be6c7654d..b67c291fd0 100644 --- a/openstackclient/image/client.py +++ b/openstackclient/image/client.py @@ -22,7 +22,7 @@ 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/releasenotes/notes/bug-1642772-19f53765bef8ee91.yaml b/releasenotes/notes/bug-1642772-19f53765bef8ee91.yaml new file mode 100644 index 0000000000..0e1a7f110c --- /dev/null +++ b/releasenotes/notes/bug-1642772-19f53765bef8ee91.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - Changed the default version of ``OS_IMAGE_API_VERSION`` to ``2``. Image v1 + has been deprecated for more than six months and other projects, such as + `shade` and `os-client-config` are using Image v2 by default as well. + [Bug `1642772 `_] From e51a8d63747932f2ee4ffab02dfb0cd43e4a103d Mon Sep 17 00:00:00 2001 From: Boris Bobrov Date: Thu, 17 Nov 2016 13:46:21 +0300 Subject: [PATCH 1351/3095] Use project_domain_id only in password auth The method being changed constructs domain-related parameters that will further be passed to the auth plugin. If project domain is not passed, the method sets it to the default domain. token_endpoint does not expect any information about domain, because it uses only a token and URL. Passing it to auth plugin causes an exception. Construct domain-related parameters only for specific plugins, such as password or totp. Change-Id: I13db3bbe31a0ed843e9f4528d37c768546e2bee9 Closes-Bug: 1642301 --- openstackclient/common/client_config.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openstackclient/common/client_config.py b/openstackclient/common/client_config.py index d6297753b4..1ac53f7fce 100644 --- a/openstackclient/common/client_config.py +++ b/openstackclient/common/client_config.py @@ -151,6 +151,7 @@ def _auth_default_domain(self, config): # present, then do not change the behaviour. Otherwise, set the # PROJECT_DOMAIN_ID to 'OS_DEFAULT_DOMAIN' for better usability. if ( + auth_type in ("password", "v3password", "v3totp") and not config['auth'].get('project_domain_id') and not config['auth'].get('project_domain_name') ): From 61cc5464750e03a44536642cdc493cf81ded2366 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 18 Nov 2016 11:08:40 -0600 Subject: [PATCH 1352/3095] Add relnote for release 3.4.1 Change-Id: If7d0237c4ed032be9d225cd0a05972caf5946e2d --- releasenotes/notes/bug-1642301-18b08e0cd4b11687.yaml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 releasenotes/notes/bug-1642301-18b08e0cd4b11687.yaml diff --git a/releasenotes/notes/bug-1642301-18b08e0cd4b11687.yaml b/releasenotes/notes/bug-1642301-18b08e0cd4b11687.yaml new file mode 100644 index 0000000000..48aae4625a --- /dev/null +++ b/releasenotes/notes/bug-1642301-18b08e0cd4b11687.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Fix ``TypeError: __init__() got an unexpected keyword argument 'project_domain_id'`` + error with non-password authentication types. + [Bug `1642301 `_] \ No newline at end of file From 3907137f5824e359bcdcfcdd8ab3d15a83d10bca Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Wed, 16 Nov 2016 20:55:21 +0800 Subject: [PATCH 1353/3095] Add commands for "consistency group snapshot" Add commands: consistency group snapshot create consistency group snapshot delete consistency group snapshot list consistency group snapshot show in volume v2 (v2 only) Change-Id: Ib4115f8ff00fb5aa8194588223032657eb1346b5 Closes-Bug: #1642238 Implements: bp cinder-command-support --- .../consistency-group-snapshot.rst | 96 +++++ doc/source/commands.rst | 1 + openstackclient/tests/unit/volume/v2/fakes.py | 78 ++++ .../v2/test_consistency_group_snapshot.py | 351 ++++++++++++++++++ .../volume/v2/consistency_group_snapshot.py | 190 ++++++++++ .../notes/bug-1642238-3032c7fe7f0ce29d.yaml | 6 + setup.cfg | 5 + 7 files changed, 727 insertions(+) create mode 100644 doc/source/command-objects/consistency-group-snapshot.rst create mode 100644 openstackclient/tests/unit/volume/v2/test_consistency_group_snapshot.py create mode 100644 openstackclient/volume/v2/consistency_group_snapshot.py create mode 100644 releasenotes/notes/bug-1642238-3032c7fe7f0ce29d.yaml diff --git a/doc/source/command-objects/consistency-group-snapshot.rst b/doc/source/command-objects/consistency-group-snapshot.rst new file mode 100644 index 0000000000..b7b1423e8a --- /dev/null +++ b/doc/source/command-objects/consistency-group-snapshot.rst @@ -0,0 +1,96 @@ +========================== +consistency group snapshot +========================== + +Block Storage v2 + +consistency group snapshot create +--------------------------------- + +Create new consistency group snapshot. + +.. program:: consistency group snapshot create +.. code:: bash + + os consistency group snapshot create + [--consistency-group ] + [--description ] + [] + +.. option:: --consistency-group + + Consistency group to snapshot (name or ID) + (default to be the same as ) + +.. option:: --description + + Description of this consistency group snapshot + +.. _consistency_group_snapshot_create-snapshot-name: +.. option:: + + Name of new consistency group snapshot (default to None) + +consistency group snapshot delete +--------------------------------- + +Delete consistency group snapshot(s) + +.. program:: consistency group snapshot delete +.. code:: bash + + os consistency group snapshot delete + [ ...] + +.. _consistency_group_snapshot_delete-consistency-group-snapshot: +.. describe:: + + Consistency group snapshot(s) to delete (name or ID) + +consistency group snapshot list +------------------------------- + +List consistency group snapshots. + +.. program:: consistency group snapshot list +.. code:: bash + + os consistency group snapshot list + [--all-projects] + [--long] + [--status ] + [--consistency-group ] + +.. option:: --all-projects + + Show detail for all projects. Admin only. + (defaults to False) + +.. option:: --long + + List additional fields in output + +.. option:: --status + + Filters results by a status + ("available", "error", "creating", "deleting" or "error_deleting") + +.. option:: --consistency-group + + Filters results by a consistency group (name or ID) + +consistency group snapshot show +------------------------------- + +Display consistency group snapshot details. + +.. program:: consistency group snapshot show +.. code:: bash + + os consistency group snapshot show + + +.. _consistency_group_snapshot_show-consistency-group-snapshot: +.. describe:: + + Consistency group snapshot to display (name or ID) diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 6a4d999056..d8fb48bdd5 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -81,6 +81,7 @@ referring to both Compute and Volume quotas. * ``compute service``: (**Compute**) a cloud Compute process running on a host * ``configuration``: (**Internal**) OpenStack client configuration * ``consistency group``: (**Volume**) a consistency group of volumes +* ``consistency group snapshot``: (**Volume**) a point-in-time copy of a consistency group * ``console log``: (**Compute**) server console text dump * ``console url``: (**Compute**) server remote console URL * ``consumer``: (**Identity**) OAuth-based delegatee diff --git a/openstackclient/tests/unit/volume/v2/fakes.py b/openstackclient/tests/unit/volume/v2/fakes.py index 5e1d16e1db..70234f43f0 100644 --- a/openstackclient/tests/unit/volume/v2/fakes.py +++ b/openstackclient/tests/unit/volume/v2/fakes.py @@ -224,6 +224,8 @@ def __init__(self, **kwargs): 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'] @@ -548,6 +550,82 @@ def create_consistency_groups(attrs=None, count=2): 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_snapshot.py b/openstackclient/tests/unit/volume/v2/test_consistency_group_snapshot.py new file mode 100644 index 0000000000..3bfe93df04 --- /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_snapshot.py b/openstackclient/volume/v2/consistency_group_snapshot.py new file mode 100644 index 0000000000..540deb0187 --- /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="", + nargs="?", + help=_("Name of new consistency group snapshot (default to None)") + ) + parser.add_argument( + "--consistency-group", + metavar="", + help=_("Consistency group to snapshot (name or ID) " + "(default to be the same as )") + ) + parser.add_argument( + "--description", + metavar="", + 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="", + 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="", + 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="", + 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="", + 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/releasenotes/notes/bug-1642238-3032c7fe7f0ce29d.yaml b/releasenotes/notes/bug-1642238-3032c7fe7f0ce29d.yaml new file mode 100644 index 0000000000..bb8d3f11ff --- /dev/null +++ b/releasenotes/notes/bug-1642238-3032c7fe7f0ce29d.yaml @@ -0,0 +1,6 @@ +--- +features: + - Add ``consistency group snapshot create``, ``consistency group snapshot delete``, + ``consistency group snapshot list`` and ``consistency group snapshot show`` commands + in volume v2. + [Bug `1642238 `_] diff --git a/setup.cfg b/setup.cfg index c94437393e..cfe9ddb01b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -508,6 +508,11 @@ openstack.volume.v2 = consistency_group_list = openstackclient.volume.v2.consistency_group:ListConsistencyGroup + consistency_group_snapshot_create = openstackclient.volume.v2.consistency_group_snapshot:CreateConsistencyGroupSnapshot + consistency_group_snapshot_delete = openstackclient.volume.v2.consistency_group_snapshot:DeleteConsistencyGroupSnapshot + consistency_group_snapshot_list = openstackclient.volume.v2.consistency_group_snapshot:ListConsistencyGroupSnapshot + consistency_group_snapshot_show = openstackclient.volume.v2.consistency_group_snapshot:ShowConsistencyGroupSnapshot + snapshot_create = openstackclient.volume.v2.snapshot:CreateSnapshot snapshot_delete = openstackclient.volume.v2.snapshot:DeleteSnapshot snapshot_list = openstackclient.volume.v2.snapshot:ListSnapshot From 11560a0527f4533a65942b7291c7c76a81602a00 Mon Sep 17 00:00:00 2001 From: Abhishek Raut Date: Thu, 17 Nov 2016 16:14:40 -0800 Subject: [PATCH 1354/3095] SDK Refactor: Prepare subnet commands Prepare the OSC "subnet" commands for the SDK refactor. See [1] for details. [1] https://etherpad.openstack.org/p/osc-network-command-sdk-support Partially-Implements: blueprint network-command-sdk-support Change-Id: I5d58b189e822b2ee61c9c95ccf909113ff59de46 --- openstackclient/network/v2/subnet.py | 31 +++++++++++++------ .../tests/unit/network/v2/fakes.py | 2 ++ .../tests/unit/network/v2/test_subnet.py | 8 ++--- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index eda9294809..3a7b5248ca 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -23,6 +23,7 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import sdk_utils LOG = logging.getLogger(__name__) @@ -117,11 +118,12 @@ def _get_common_parse_arguments(parser, is_create=True): def _get_columns(item): - columns = list(item.keys()) - if 'tenant_id' in columns: - columns.remove('tenant_id') - columns.append('project_id') - return tuple(sorted(columns)) + column_map = { + 'is_dhcp_enabled': 'enable_dhcp', + 'subnet_pool_id': 'subnetpool_id', + 'tenant_id': 'project_id', + } + return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) def convert_entries_to_nexthop(entries): @@ -217,6 +219,8 @@ def _get_attrs(client_manager, parsed_args, is_create=True): return attrs +# TODO(abhiraut): Use the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. class CreateSubnet(command.ShowOne): _description = _("Create a subnet") @@ -323,9 +327,9 @@ def take_action(self, parsed_args): client = self.app.client_manager.network attrs = _get_attrs(self.app.client_manager, parsed_args) obj = client.create_subnet(**attrs) - columns = _get_columns(obj) + display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) - return (columns, data) + return (display_columns, data) class DeleteSubnet(command.Command): @@ -362,6 +366,8 @@ def take_action(self, parsed_args): raise exceptions.CommandError(msg) +# TODO(abhiraut): Use only the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. class ListSubnet(command.Lister): _description = _("List subnets") @@ -443,8 +449,10 @@ def take_action(self, parsed_args): filters['ip_version'] = parsed_args.ip_version if parsed_args.dhcp: filters['enable_dhcp'] = True + filters['is_dhcp_enabled'] = True elif parsed_args.no_dhcp: filters['enable_dhcp'] = False + filters['is_dhcp_enabled'] = False if parsed_args.service_types: filters['service_types'] = parsed_args.service_types if parsed_args.project: @@ -454,6 +462,7 @@ def take_action(self, parsed_args): parsed_args.project_domain, ).id filters['tenant_id'] = project_id + filters['project_id'] = project_id if parsed_args.network: network_id = network_client.find_network(parsed_args.network, ignore_missing=False).id @@ -472,7 +481,7 @@ def take_action(self, parsed_args): headers += ('Project', 'DHCP', 'Name Servers', 'Allocation Pools', 'Host Routes', 'IP Version', 'Gateway', 'Service Types') - columns += ('tenant_id', 'enable_dhcp', 'dns_nameservers', + columns += ('project_id', 'is_dhcp_enabled', 'dns_nameservers', 'allocation_pools', 'host_routes', 'ip_version', 'gateway_ip', 'service_types') @@ -483,6 +492,8 @@ def take_action(self, parsed_args): ) for s in data)) +# TODO(abhiraut): Use the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. class SetSubnet(command.Command): _description = _("Set subnet properties") @@ -564,9 +575,9 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): obj = self.app.client_manager.network.find_subnet(parsed_args.subnet, ignore_missing=False) - columns = _get_columns(obj) + display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) - return (columns, data) + return (display_columns, data) class UnsetSubnet(command.Command): diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index 37f10147f6..797da4f9e1 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -1064,6 +1064,8 @@ def create_one_subnet(attrs=None): loaded=True) # Set attributes with special mappings in OpenStack SDK. + subnet.is_dhcp_enabled = subnet_attrs['enable_dhcp'] + subnet.subnet_pool_id = subnet_attrs['subnetpool_id'] subnet.project_id = subnet_attrs['tenant_id'] return subnet diff --git a/openstackclient/tests/unit/network/v2/test_subnet.py b/openstackclient/tests/unit/network/v2/test_subnet.py index 2d51aa4ad9..d477c4a466 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet.py +++ b/openstackclient/tests/unit/network/v2/test_subnet.py @@ -636,7 +636,7 @@ def test_subnet_list_dhcp(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - filters = {'enable_dhcp': True} + filters = {'enable_dhcp': True, 'is_dhcp_enabled': True} self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) @@ -652,7 +652,7 @@ def test_subnet_list_no_dhcp(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - filters = {'enable_dhcp': False} + filters = {'enable_dhcp': False, 'is_dhcp_enabled': False} self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) @@ -685,7 +685,7 @@ def test_subnet_list_project(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - filters = {'tenant_id': project.id} + filters = {'tenant_id': project.id, 'project_id': project.id} self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) @@ -723,7 +723,7 @@ def test_subnet_list_project_domain(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - filters = {'tenant_id': project.id} + filters = {'tenant_id': project.id, 'project_id': project.id} self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) From 77ea8baafe69cd7da6e0dfa0075da9f6cf12c164 Mon Sep 17 00:00:00 2001 From: Abhishek Raut Date: Sat, 19 Nov 2016 17:14:58 -0800 Subject: [PATCH 1355/3095] SDK Refactor: Prepare network qos policy commands Prepare the OSC "network qos policy" commands for the SDK refactor. See [1] for details. [1] https://etherpad.openstack.org/p/osc-network-command-sdk-support Change-Id: I12dd8bda40801c26176a73646ed87aea66f09fcc Partially-Implements: blueprint network-command-sdk-support --- .../network/v2/network_qos_policy.py | 29 ++++++++++++------- .../tests/unit/network/v2/fakes.py | 1 + 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/openstackclient/network/v2/network_qos_policy.py b/openstackclient/network/v2/network_qos_policy.py index 75cb1d9155..5ccbe36b03 100644 --- a/openstackclient/network/v2/network_qos_policy.py +++ b/openstackclient/network/v2/network_qos_policy.py @@ -21,17 +21,18 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import sdk_utils LOG = logging.getLogger(__name__) def _get_columns(item): - columns = list(item.keys()) - if 'tenant_id' in columns: - columns.remove('tenant_id') - columns.append('project_id') - return tuple(sorted(columns)) + column_map = { + 'is_shared': 'shared', + 'tenant_id': 'project_id', + } + return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) def _get_attrs(client_manager, parsed_args): @@ -56,6 +57,8 @@ def _get_attrs(client_manager, parsed_args): return attrs +# TODO(abhiraut): Use the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. class CreateNetworkQosPolicy(command.ShowOne): _description = _("Create a QoS policy") @@ -96,9 +99,9 @@ def take_action(self, parsed_args): client = self.app.client_manager.network attrs = _get_attrs(self.app.client_manager, parsed_args) obj = client.create_qos_policy(**attrs) - columns = _get_columns(obj) + display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters={}) - return columns, data + return (display_columns, data) class DeleteNetworkQosPolicy(command.Command): @@ -135,6 +138,8 @@ def take_action(self, parsed_args): raise exceptions.CommandError(msg) +# TODO(abhiraut): Use only the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. class ListNetworkQosPolicy(command.Lister): _description = _("List QoS policies") @@ -143,8 +148,8 @@ def take_action(self, parsed_args): columns = ( 'id', 'name', - 'shared', - 'tenant_id', + 'is_shared', + 'project_id', ) column_headers = ( 'ID', @@ -160,6 +165,8 @@ def take_action(self, parsed_args): ) for s in data)) +# TODO(abhiraut): Use the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. class SetNetworkQosPolicy(command.Command): _description = _("Set QoS policy properties") @@ -226,6 +233,6 @@ def take_action(self, parsed_args): client = self.app.client_manager.network obj = client.find_qos_policy(parsed_args.policy, ignore_missing=False) - columns = _get_columns(obj) + display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns) - return columns, data + return (display_columns, data) diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index 37f10147f6..8fa66a2e46 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -746,6 +746,7 @@ def create_one_qos_policy(attrs=None): loaded=True) # Set attributes with special mapping in OpenStack SDK. + qos_policy.is_shared = qos_policy_attrs['shared'] qos_policy.project_id = qos_policy_attrs['tenant_id'] return qos_policy From 1256aee49134c537b1132852ba8ed7549777d0d9 Mon Sep 17 00:00:00 2001 From: Abhishek Raut Date: Sat, 19 Nov 2016 17:31:52 -0800 Subject: [PATCH 1356/3095] SDK Refactor: Prepare network rbac commands Prepare the OSC "network rbac" commands for the SDK refactor. See [1] for details. [1] https://etherpad.openstack.org/p/osc-network-command-sdk-support Change-Id: I6f25e167d7f933667173b04a4b0ad55baf3c56f2 Partially-Implements: blueprint network-command-sdk-support --- openstackclient/network/v2/network_rbac.py | 26 ++++++++++++---------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/openstackclient/network/v2/network_rbac.py b/openstackclient/network/v2/network_rbac.py index 2bcdd811b0..e837af3a62 100644 --- a/openstackclient/network/v2/network_rbac.py +++ b/openstackclient/network/v2/network_rbac.py @@ -21,20 +21,18 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import sdk_utils LOG = logging.getLogger(__name__) def _get_columns(item): - columns = list(item.keys()) - if 'tenant_id' in columns: - columns.remove('tenant_id') - columns.append('project_id') - if 'target_tenant' in columns: - columns.remove('target_tenant') - columns.append('target_project_id') - return tuple(sorted(columns)) + column_map = { + 'target_tenant': 'target_project_id', + 'tenant_id': 'project_id', + } + return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) def _get_attrs(client_manager, parsed_args): @@ -70,6 +68,8 @@ def _get_attrs(client_manager, parsed_args): return attrs +# TODO(abhiraut): Use the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. class CreateNetworkRBAC(command.ShowOne): _description = _("Create network RBAC policy") @@ -122,9 +122,9 @@ def take_action(self, parsed_args): client = self.app.client_manager.network attrs = _get_attrs(self.app.client_manager, parsed_args) obj = client.create_rbac_policy(**attrs) - columns = _get_columns(obj) + display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns) - return columns, data + return display_columns, data class DeleteNetworkRBAC(command.Command): @@ -185,6 +185,8 @@ def take_action(self, parsed_args): ) for s in data)) +# TODO(abhiraut): Use the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. class SetNetworkRBAC(command.Command): _description = _("Set network RBAC policy properties") @@ -242,6 +244,6 @@ def take_action(self, parsed_args): client = self.app.client_manager.network obj = client.find_rbac_policy(parsed_args.rbac_policy, ignore_missing=False) - columns = _get_columns(obj) + display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns) - return columns, data + return display_columns, data From c76e631a2cd19c78950a1c0ed1a4feb10815b25b Mon Sep 17 00:00:00 2001 From: Abhishek Raut Date: Sat, 19 Nov 2016 17:45:01 -0800 Subject: [PATCH 1357/3095] SDK Refactor: Prepare security group rule commands Prepare the OSC "security group rule" commands for the SDK refactor. See [1] for details. [1] https://etherpad.openstack.org/p/osc-network-command-sdk-support Change-Id: I5553e40fe90f3a26d3f462a69f1a424032479d49 Partially-Implements: blueprint network-command-sdk-support --- .../network/v2/security_group_rule.py | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index 05fafc0de8..b878d8751b 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -29,6 +29,7 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common +from openstackclient.network import sdk_utils from openstackclient.network import utils as network_utils @@ -67,11 +68,10 @@ def _format_network_port_range(rule): def _get_columns(item): - columns = list(item.keys()) - if 'tenant_id' in columns: - columns.remove('tenant_id') - columns.append('project_id') - return tuple(sorted(columns)) + column_map = { + 'tenant_id': 'project_id', + } + return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) def _convert_to_lowercase(string): @@ -89,6 +89,8 @@ def _is_icmp_protocol(protocol): return False +# TODO(abhiraut): Use the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. class CreateSecurityGroupRule(common.NetworkAndComputeShowOne): _description = _("Create a new security group rule") @@ -341,9 +343,9 @@ def take_action_network(self, client, parsed_args): # Create and show the security group rule. obj = client.create_security_group_rule(**attrs) - columns = _get_columns(obj) + display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns) - return (columns, data) + return (display_columns, data) def take_action_compute(self, client, parsed_args): group = utils.find_resource( @@ -597,9 +599,9 @@ def update_parser_common(self, parser): def take_action_network(self, client, parsed_args): obj = client.find_security_group_rule(parsed_args.rule, ignore_missing=False) - columns = _get_columns(obj) + display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns) - return (columns, data) + return (display_columns, data) def take_action_compute(self, client, parsed_args): # NOTE(rtheis): Unfortunately, compute does not have an API From a7a0d0c61a6a88e044b781bbc4903cdcfc919e55 Mon Sep 17 00:00:00 2001 From: Vijendra Soni Date: Fri, 11 Nov 2016 20:01:05 +0530 Subject: [PATCH 1358/3095] Sort list images by name by default Set the default key and dir for openstack image list, if no command line argument passed Changing sort_key to name(old value: created_at) Change-Id: I6c61f6e5a04824d655ccf43477afcec9652101df Closes-Bug: #1639231 --- doc/source/command-objects/image.rst | 2 +- openstackclient/image/v1/image.py | 3 ++- openstackclient/image/v2/image.py | 3 ++- releasenotes/notes/bug-1639231-21823768bd54170a.yaml | 6 ++++++ 4 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/bug-1639231-21823768bd54170a.yaml diff --git a/doc/source/command-objects/image.rst b/doc/source/command-objects/image.rst index 842eab8d8a..84e6baa500 100644 --- a/doc/source/command-objects/image.rst +++ b/doc/source/command-objects/image.rst @@ -221,7 +221,7 @@ List available images .. option:: --sort [:] - Sort output by selected keys and directions(asc or desc) (default: asc), + Sort output by selected keys and directions(asc or desc) (default: name:asc), multiple keys and directions can be specified separated by comma .. option:: --limit diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index 5f669c6412..26ce1aac90 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -350,8 +350,9 @@ def get_parser(self, prog_name): parser.add_argument( '--sort', metavar="[:]", + default='name:asc', help=_("Sort output by selected keys and directions(asc or desc) " - "(default: asc), multiple keys and directions can be " + "(default: name:asc), multiple keys and directions can be " "specified separated by comma"), ) return parser diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 0712e09c07..2edec9db2d 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -447,8 +447,9 @@ def get_parser(self, prog_name): parser.add_argument( '--sort', metavar="[:]", + default='name:asc', help=_("Sort output by selected keys and directions(asc or desc) " - "(default: asc), multiple keys and directions can be " + "(default: name:asc), multiple keys and directions can be " "specified separated by comma"), ) parser.add_argument( diff --git a/releasenotes/notes/bug-1639231-21823768bd54170a.yaml b/releasenotes/notes/bug-1639231-21823768bd54170a.yaml new file mode 100644 index 0000000000..740a0dd1b8 --- /dev/null +++ b/releasenotes/notes/bug-1639231-21823768bd54170a.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + The ``image list`` command will now sort by name in ascending order by + default. ``--sort`` option will have the default value of ``name:asc``. + [Bug `1639231 `_] From 55195cec46fadd88f6151783b1e17557d5e94940 Mon Sep 17 00:00:00 2001 From: "zhiyong.dai" Date: Wed, 14 Dec 2016 21:14:17 +0800 Subject: [PATCH 1359/3095] Add "volume host failover" command Add "volume host failover" command in volume v2 (v2 only). Change-Id: Ia39e6d20bf5c9d3096e46f3432804a240827548d Implements: bp cinder-command-support --- doc/source/command-objects/volume-host.rst | 26 ++++++++++++++-- doc/source/commands.rst | 1 + .../tests/unit/volume/v2/test_volume_host.py | 31 +++++++++++++++++++ openstackclient/volume/v2/volume_host.py | 29 +++++++++++++++-- ...volume-host-failover-8fc77b24533b7fed.yaml | 5 +++ setup.cfg | 1 + 6 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/add-volume-host-failover-8fc77b24533b7fed.yaml diff --git a/doc/source/command-objects/volume-host.rst b/doc/source/command-objects/volume-host.rst index a225e53e58..1e513cb716 100644 --- a/doc/source/command-objects/volume-host.rst +++ b/doc/source/command-objects/volume-host.rst @@ -4,6 +4,28 @@ volume host Volume v2 +volume host failover +-------------------- + +Failover volume host to different backend + +.. program:: volume host failover +.. code:: bash + + openstack volume host failover + --volume-backend + + +.. option:: --volume-backend + + The ID of the volume backend replication + target where the host will failover to (required) + +.. _volume_host_failover-host-name: +.. describe:: + + Name of volume host + volume host set --------------- @@ -18,13 +40,13 @@ Set volume host properties .. option:: --enable - Thaw and enable the specified volume host + Thaw and enable the specified volume host. .. option:: --disable Freeze and disable the specified volume host -.. _volume-host-set: +.. _volume_host_set-host-name: .. describe:: Name of volume host diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 206b18af3a..815a29bb3a 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -234,6 +234,7 @@ Those actions with an opposite action are noted in parens if applicable. * ``create`` (``delete``) - create a new occurrence of the specified object * ``delete`` (``create``) - delete specific occurrences of the specified objects * ``expand`` (``shrink``) - increase the capacity of a cluster +* ``failover`` - failover volume host to different backend * ``issue`` (``revoke``) - issue a token * ``list`` - display summary information about multiple objects * ``lock`` (``unlock``) - lock one or more servers so that non-admin user won't be able to execute actions diff --git a/openstackclient/tests/unit/volume/v2/test_volume_host.py b/openstackclient/tests/unit/volume/v2/test_volume_host.py index aad7bb0bf4..b024329a1b 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume_host.py +++ b/openstackclient/tests/unit/volume/v2/test_volume_host.py @@ -35,6 +35,7 @@ def setUp(self): self.host_mock.freeze_host.return_value = None self.host_mock.thaw_host.return_value = None + # Get the command object to mock self.cmd = volume_host.SetVolumeHost(self.app, None) def test_volume_host_set_nothing(self): @@ -84,3 +85,33 @@ def test_volume_host_set_disable(self): self.host_mock.freeze_host.assert_called_with(self.service.host) self.host_mock.thaw_host.assert_not_called() self.assertIsNone(result) + + +class TestVolumeHostFailover(TestVolumeHost): + + service = host_fakes.FakeService.create_one_service() + + def setUp(self): + super(TestVolumeHostFailover, self).setUp() + + self.host_mock.failover_host.return_value = None + + # Get the command object to mock + self.cmd = volume_host.FailoverVolumeHost(self.app, None) + + def test_volume_host_failover(self): + arglist = [ + '--volume-backend', 'backend_test', + self.service.host, + ] + verifylist = [ + ('volume_backend', 'backend_test'), + ('host', self.service.host), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.host_mock.failover_host.assert_called_with( + self.service.host, 'backend_test') + self.assertIsNone(result) diff --git a/openstackclient/volume/v2/volume_host.py b/openstackclient/volume/v2/volume_host.py index 376e502491..2fdeb9684b 100644 --- a/openstackclient/volume/v2/volume_host.py +++ b/openstackclient/volume/v2/volume_host.py @@ -19,6 +19,31 @@ from openstackclient.i18n import _ +class FailoverVolumeHost(command.Command): + _description = _("Failover volume host to different backend") + + def get_parser(self, prog_name): + parser = super(FailoverVolumeHost, self).get_parser(prog_name) + parser.add_argument( + "host", + metavar="", + help=_("Name of volume host") + ) + parser.add_argument( + "--volume-backend", + metavar="", + required=True, + help=_("The ID of the volume backend replication " + "target where the host will failover to (required)") + ) + return parser + + def take_action(self, parsed_args): + service_client = self.app.client_manager.volume + service_client.services.failover_host(parsed_args.host, + parsed_args.volume_backend) + + class SetVolumeHost(command.Command): _description = _("Set volume host properties") @@ -33,12 +58,12 @@ def get_parser(self, prog_name): enabled_group.add_argument( "--disable", action="store_true", - help=_("Freeze and disable the specified volume host.") + help=_("Freeze and disable the specified volume host") ) enabled_group.add_argument( "--enable", action="store_true", - help=_("Thaw and enable the specified volume host.") + help=_("Thaw and enable the specified volume host") ) return parser diff --git a/releasenotes/notes/add-volume-host-failover-8fc77b24533b7fed.yaml b/releasenotes/notes/add-volume-host-failover-8fc77b24533b7fed.yaml new file mode 100644 index 0000000000..8630f74251 --- /dev/null +++ b/releasenotes/notes/add-volume-host-failover-8fc77b24533b7fed.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``volume host failover`` command. + [Blueprint `cinder-command-support `_] diff --git a/setup.cfg b/setup.cfg index 8c5c663055..f184d03870 100644 --- a/setup.cfg +++ b/setup.cfg @@ -548,6 +548,7 @@ openstack.volume.v2 = volume_backup_set = openstackclient.volume.v2.backup:SetVolumeBackup volume_backup_show = openstackclient.volume.v2.backup:ShowVolumeBackup + volume_host_failover = openstackclient.volume.v2.volume_host:FailoverVolumeHost volume_host_set = openstackclient.volume.v2.volume_host:SetVolumeHost volume_snapshot_create = openstackclient.volume.v2.volume_snapshot:CreateVolumeSnapshot From 3544b4d89c45b3f3b28212c4433555c5ce83041a Mon Sep 17 00:00:00 2001 From: "zhiyong.dai" Date: Tue, 22 Nov 2016 04:07:28 +0800 Subject: [PATCH 1360/3095] Fill the missing colon This patch fills the missing colon in ip-availability.rst Change-Id: Icc7c8cc04fdfeb00051392f560bedcde62cfcee7 --- doc/source/command-objects/ip-availability.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/command-objects/ip-availability.rst b/doc/source/command-objects/ip-availability.rst index 73d0599a43..dd39e649fe 100644 --- a/doc/source/command-objects/ip-availability.rst +++ b/doc/source/command-objects/ip-availability.rst @@ -54,7 +54,7 @@ subnet within the network as well. openstack ip availability show -.. _ip_availability_show-network +.. _ip_availability_show-network: .. describe:: Show IP availability for a specific network (name or ID) From b6b9a524f9fe056df4e55d6589e1a4e95cd558bc Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Tue, 22 Nov 2016 19:35:20 +0800 Subject: [PATCH 1361/3095] Remove outdated comments in volume unit test Checked the volume create unit test has beed completed, so remove the TODO. Checked the FakeVolume class has been added in volume v1, so remove the note. Change-Id: I82b5f775d013e5e301bc1c18481ef516ca5baa7e --- openstackclient/tests/unit/volume/v1/test_volume.py | 4 ---- openstackclient/tests/unit/volume/v2/fakes.py | 5 +---- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/openstackclient/tests/unit/volume/v1/test_volume.py b/openstackclient/tests/unit/volume/v1/test_volume.py index 2570728808..7a44dea85c 100644 --- a/openstackclient/tests/unit/volume/v1/test_volume.py +++ b/openstackclient/tests/unit/volume/v1/test_volume.py @@ -57,10 +57,6 @@ def setup_volumes_mock(self, count): return volumes -# TODO(dtroyer): The volume create tests are incomplete, only the minimal -# options and the options that require additional processing -# are implemented at this time. - class TestVolumeCreate(TestVolume): project = identity_fakes.FakeProject.create_one_project() diff --git a/openstackclient/tests/unit/volume/v2/fakes.py b/openstackclient/tests/unit/volume/v2/fakes.py index 5e1d16e1db..bd7e597378 100644 --- a/openstackclient/tests/unit/volume/v2/fakes.py +++ b/openstackclient/tests/unit/volume/v2/fakes.py @@ -248,10 +248,7 @@ def setUp(self): class FakeVolume(object): - """Fake one or more volumes. - - TODO(xiexs): Currently, only volume API v2 is supported by this class. - """ + """Fake one or more volumes.""" @staticmethod def create_one_volume(attrs=None): From 5e070c36a176edf47ec0832ef90636ee6720def7 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Tue, 22 Nov 2016 20:35:27 +0800 Subject: [PATCH 1362/3095] Add "Is Public" column in volume type list I think admin users will like to see the "Is Public" column in volume type list since they can know the volume type is public or private, so add "Is Public" column in volume type list Change-Id: I3f6e297a7adf82a275debbd87d2c4da415aa1360 Closes-Bug: #1643861 --- openstackclient/tests/unit/volume/v1/test_type.py | 6 +++++- openstackclient/tests/unit/volume/v2/test_type.py | 5 ++++- openstackclient/volume/v1/volume_type.py | 6 +++--- openstackclient/volume/v2/volume_type.py | 7 ++++--- releasenotes/notes/bug-1643861-b17ad5dfcb4304ff.yaml | 4 ++++ 5 files changed, 20 insertions(+), 8 deletions(-) create mode 100644 releasenotes/notes/bug-1643861-b17ad5dfcb4304ff.yaml diff --git a/openstackclient/tests/unit/volume/v1/test_type.py b/openstackclient/tests/unit/volume/v1/test_type.py index 23a1186dad..81ad8301e6 100644 --- a/openstackclient/tests/unit/volume/v1/test_type.py +++ b/openstackclient/tests/unit/volume/v1/test_type.py @@ -158,11 +158,13 @@ class TestTypeList(TestType): columns = ( "ID", - "Name" + "Name", + "Is Public", ) columns_long = ( "ID", "Name", + "Is Public", "Properties" ) @@ -171,12 +173,14 @@ class TestTypeList(TestType): data.append(( t.id, t.name, + t.is_public, )) data_long = [] for t in volume_types: data_long.append(( t.id, t.name, + t.is_public, utils.format_dict(t.extra_specs), )) diff --git a/openstackclient/tests/unit/volume/v2/test_type.py b/openstackclient/tests/unit/volume/v2/test_type.py index 84f87e3b19..325872d734 100644 --- a/openstackclient/tests/unit/volume/v2/test_type.py +++ b/openstackclient/tests/unit/volume/v2/test_type.py @@ -165,7 +165,8 @@ class TestTypeList(TestType): columns = [ "ID", - "Name" + "Name", + "Is Public", ] columns_long = columns + [ "Description", @@ -177,12 +178,14 @@ class TestTypeList(TestType): data.append(( t.id, t.name, + t.is_public, )) data_long = [] for t in volume_types: data_long.append(( t.id, t.name, + t.is_public, t.description, utils.format_dict(t.extra_specs), )) diff --git a/openstackclient/volume/v1/volume_type.py b/openstackclient/volume/v1/volume_type.py index 4f159239b5..8adce3221c 100644 --- a/openstackclient/volume/v1/volume_type.py +++ b/openstackclient/volume/v1/volume_type.py @@ -111,10 +111,10 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): if parsed_args.long: - columns = ('ID', 'Name', 'Extra Specs') - column_headers = ('ID', 'Name', 'Properties') + columns = ('ID', 'Name', 'Is Public', 'Extra Specs') + column_headers = ('ID', 'Name', 'Is Public', 'Properties') else: - columns = ('ID', 'Name') + columns = ('ID', 'Name', 'Is Public') column_headers = columns data = self.app.client_manager.volume.volume_types.list() return (column_headers, diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index 0fcfaf7e8d..a35af62331 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -176,10 +176,11 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): if parsed_args.long: - columns = ['ID', 'Name', 'Description', 'Extra Specs'] - column_headers = ['ID', 'Name', 'Description', 'Properties'] + columns = ['ID', 'Name', 'Is Public', 'Description', 'Extra Specs'] + column_headers = [ + 'ID', 'Name', 'Is Public', 'Description', 'Properties'] else: - columns = ['ID', 'Name'] + columns = ['ID', 'Name', 'Is Public'] column_headers = columns is_public = None diff --git a/releasenotes/notes/bug-1643861-b17ad5dfcb4304ff.yaml b/releasenotes/notes/bug-1643861-b17ad5dfcb4304ff.yaml new file mode 100644 index 0000000000..f69cbb6a43 --- /dev/null +++ b/releasenotes/notes/bug-1643861-b17ad5dfcb4304ff.yaml @@ -0,0 +1,4 @@ +--- +features: + - Add ``Is Public`` column to ``volume type list``. + [Bug `1643861 `_] From 3a7bb859a67ed82b30be23f626e271da85970590 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Tue, 22 Nov 2016 17:59:10 +0000 Subject: [PATCH 1363/3095] Updated from global requirements Change-Id: Iec7883cfbc38d44aa1ad39c3a2879ad400b7ebb9 --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index b7a04c4d00..8b15c292e0 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -27,7 +27,7 @@ aodhclient>=0.5.0 # Apache-2.0 python-barbicanclient>=4.0.0 # Apache-2.0 python-congressclient<2000,>=1.3.0 # Apache-2.0 python-designateclient>=1.5.0 # Apache-2.0 -python-heatclient>=1.5.0 # Apache-2.0 +python-heatclient!=1.6.0,>=1.5.0 # Apache-2.0 python-ironicclient>=1.6.0 # Apache-2.0 python-ironic-inspector-client>=1.5.0 # Apache-2.0 python-mistralclient>=2.0.0 # Apache-2.0 From 82b96f57db0ab1c2d9aa1f3fa1e5025b291509ea Mon Sep 17 00:00:00 2001 From: "zhiyong.dai" Date: Wed, 23 Nov 2016 02:54:21 +0800 Subject: [PATCH 1364/3095] TrivialFix in helpMessage for readability Change-Id: Ic5fae89455470585f1bd79539f99a9c04e0c68a0 --- doc/source/command-objects/request-token.rst | 3 ++- openstackclient/identity/v3/token.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/source/command-objects/request-token.rst b/doc/source/command-objects/request-token.rst index ec72dd146b..3c80780f8e 100644 --- a/doc/source/command-objects/request-token.rst +++ b/doc/source/command-objects/request-token.rst @@ -24,7 +24,8 @@ Authorize a request token .. option:: --role - Roles to authorize (name or ID) (repeat to set multiple values) (required) + Roles to authorize (name or ID) + (repeat option to set multiple values) (required) request token create -------------------- diff --git a/openstackclient/identity/v3/token.py b/openstackclient/identity/v3/token.py index 7a66f23b75..effb9e3525 100644 --- a/openstackclient/identity/v3/token.py +++ b/openstackclient/identity/v3/token.py @@ -42,7 +42,7 @@ def get_parser(self, prog_name): default=[], required=True, help=_('Roles to authorize (name or ID) ' - '(repeat option to set multiple values, required)'), + '(repeat option to set multiple values) (required)'), ) return parser From 4b14f3d0cb26dc89d7b62c70302eefaabc7cf2e3 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Tue, 18 Oct 2016 14:59:28 +0800 Subject: [PATCH 1365/3095] Add "--type" and "--retype-policy" options to "volume set" command Add "--type" and "--retype-policy" options to "volume set" command in volume v2 (v2 only) to support changing the volume type for a volume Change-Id: I0153abdb967aee790586a57cef31930e32005c1b Implements: bp cinder-command-support --- doc/source/command-objects/volume.rst | 16 +++++ .../tests/unit/volume/v2/test_volume.py | 66 +++++++++++++++++++ openstackclient/volume/v2/volume.py | 35 ++++++++++ ...nder-command-support-413b6d80232f8ece.yaml | 5 ++ 4 files changed, 122 insertions(+) create mode 100644 releasenotes/notes/bp-cinder-command-support-413b6d80232f8ece.yaml diff --git a/doc/source/command-objects/volume.rst b/doc/source/command-objects/volume.rst index abd433b6f3..df4d68806f 100644 --- a/doc/source/command-objects/volume.rst +++ b/doc/source/command-objects/volume.rst @@ -254,6 +254,8 @@ Set volume properties [--property [...] ] [--image-property [...] ] [--state ] + [--type ] + [--retype-policy ] [--bootable | --non-bootable] [--read-only | --read-write] @@ -274,6 +276,20 @@ Set volume properties Set a property on this volume (repeat option to set multiple properties) +.. option:: --type + + New volume type (name or ID) + + *Volume version 2 only* + +.. option:: --retype-policy + + Migration policy while re-typing volume + ("never" or "on-demand", default is "never" ) + (available only when "--type" option is specified) + + *Volume version 2 only* + .. option:: --bootable Mark volume as bootable diff --git a/openstackclient/tests/unit/volume/v2/test_volume.py b/openstackclient/tests/unit/volume/v2/test_volume.py index fc99bf6e3d..417283425a 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume.py +++ b/openstackclient/tests/unit/volume/v2/test_volume.py @@ -46,6 +46,9 @@ def setUp(self): self.snapshots_mock = self.app.client_manager.volume.volume_snapshots self.snapshots_mock.reset_mock() + self.types_mock = self.app.client_manager.volume.volume_types + self.types_mock.reset_mock() + self.consistencygroups_mock = ( self.app.client_manager.volume.consistencygroups) self.consistencygroups_mock.reset_mock() @@ -1088,11 +1091,14 @@ def test_volume_migrate_without_host(self): class TestVolumeSet(TestVolume): + volume_type = volume_fakes.FakeType.create_one_type() + def setUp(self): super(TestVolumeSet, self).setUp() self.new_volume = volume_fakes.FakeVolume.create_one_volume() self.volumes_mock.get.return_value = self.new_volume + self.types_mock.get.return_value = self.volume_type # Get the command object to test self.cmd = volume.SetVolume(self.app, None) @@ -1221,6 +1227,66 @@ def test_volume_set_read_write(self): False) self.assertIsNone(result) + def test_volume_set_type(self): + arglist = [ + '--type', self.volume_type.id, + self.new_volume.id + ] + verifylist = [ + ('retype_policy', None), + ('type', self.volume_type.id), + ('volume', self.new_volume.id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.volumes_mock.retype.assert_called_once_with( + self.new_volume.id, + self.volume_type.id, + 'never') + self.assertIsNone(result) + + def test_volume_set_type_with_policy(self): + arglist = [ + '--retype-policy', 'on-demand', + '--type', self.volume_type.id, + self.new_volume.id + ] + verifylist = [ + ('retype_policy', 'on-demand'), + ('type', self.volume_type.id), + ('volume', self.new_volume.id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.volumes_mock.retype.assert_called_once_with( + self.new_volume.id, + self.volume_type.id, + 'on-demand') + self.assertIsNone(result) + + @mock.patch.object(volume.LOG, 'warning') + def test_volume_set_with_only_retype_policy(self, mock_warning): + arglist = [ + '--retype-policy', 'on-demand', + self.new_volume.id + ] + verifylist = [ + ('retype_policy', 'on-demand'), + ('volume', self.new_volume.id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.volumes_mock.retype.assert_not_called() + mock_warning.assert_called_with("'--retype-policy' option will " + "not work without '--type' option") + self.assertIsNone(result) + class TestVolumeShow(TestVolume): diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index 4a9192a0c4..b93af02e62 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -509,6 +509,19 @@ def get_parser(self, prog_name): 'in the database with no regard to actual status, ' 'exercise caution when using)'), ) + parser.add_argument( + '--type', + metavar='', + help=_('New volume type (name or ID)'), + ) + parser.add_argument( + '--retype-policy', + metavar='', + choices=['never', 'on-demand'], + help=_('Migration policy while re-typing volume ' + '("never" or "on-demand", default is "never" ) ' + '(available only when "--type" option is specified)'), + ) bootable_group = parser.add_mutually_exclusive_group() bootable_group.add_argument( "--bootable", @@ -590,6 +603,28 @@ def take_action(self, parsed_args): LOG.error(_("Failed to set volume read-only access " "mode flag: %s"), e) result += 1 + if parsed_args.type: + # get the migration policy + migration_policy = 'never' + if parsed_args.retype_policy: + migration_policy = parsed_args.retype_policy + try: + # find the volume type + volume_type = utils.find_resource( + volume_client.volume_types, + parsed_args.type) + # reset to the new volume type + volume_client.volumes.retype( + volume.id, + volume_type.id, + migration_policy) + except Exception as e: + LOG.error(_("Failed to set volume type: %s"), e) + result += 1 + elif parsed_args.retype_policy: + # If the "--retype-policy" is specified without "--type" + LOG.warning(_("'--retype-policy' option will not work " + "without '--type' option")) kwargs = {} if parsed_args.name: diff --git a/releasenotes/notes/bp-cinder-command-support-413b6d80232f8ece.yaml b/releasenotes/notes/bp-cinder-command-support-413b6d80232f8ece.yaml new file mode 100644 index 0000000000..174f037551 --- /dev/null +++ b/releasenotes/notes/bp-cinder-command-support-413b6d80232f8ece.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``--type`` and ``--retype-policy`` options to ``volume set`` command. + [Blueprint `cinder-command-support `_] From f15352f8613d73b003abc5fe4da9ff23229895c8 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Wed, 9 Nov 2016 16:01:33 -0500 Subject: [PATCH 1366/3095] clean up image choices and help text Use choices for image set and image create commands, this aligns with our use of choices in networking commands. Also update the help text to match that of the networking commands, where we iterate through the options. Related-Bug: 1635518 Change-Id: Ib4c66b06e07f1d4e5bfe1b74053f2215cccad890 --- doc/source/command-objects/image.rst | 6 ++++-- openstackclient/image/v1/image.py | 15 ++++++++------- openstackclient/image/v2/image.py | 12 ++++++++---- openstackclient/tests/unit/image/v1/test_image.py | 6 +++--- openstackclient/tests/unit/image/v2/test_image.py | 14 +++++++------- 5 files changed, 30 insertions(+), 23 deletions(-) diff --git a/doc/source/command-objects/image.rst b/doc/source/command-objects/image.rst index 842eab8d8a..5184f44698 100644 --- a/doc/source/command-objects/image.rst +++ b/doc/source/command-objects/image.rst @@ -78,7 +78,8 @@ Create/upload an image .. option:: --disk-format - Image disk format (default: raw) + Image disk format. The supported options are: ami, ari, aki, vhd, vmdk, + raw, qcow2, vhdx, vdi, and iso. The default format is: raw .. option:: --size @@ -337,7 +338,8 @@ Set image properties .. option:: --disk-format - Image disk format (default: raw) + Image disk format. The supported options are: ami, ari, aki, vhd, vmdk, + raw, qcow2, vhdx, vdi, and iso. .. option:: --size diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index cf389d17b7..0d54e339e1 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -38,6 +38,8 @@ 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 @@ def get_parser(self, prog_name): "--disk-format", default=DEFAULT_DISK_FORMAT, metavar="", - 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 @@ def get_parser(self, prog_name): container_choices, choices=container_choices ) - disk_choices = ["ami", "ari", "aki", "vhd", "vmdk", "raw", "qcow2", - "vhdx", "vdi", "iso"] parser.add_argument( "--disk-format", metavar="", - 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 657277c0dc..4031952bc4 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -32,6 +32,8 @@ 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 @@ def get_parser(self, prog_name): parser.add_argument( "--disk-format", default=DEFAULT_DISK_FORMAT, + choices=DISK_CHOICES, metavar="", - 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 @@ def get_parser(self, prog_name): parser.add_argument( "--disk-format", metavar="", - 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/tests/unit/image/v1/test_image.py b/openstackclient/tests/unit/image/v1/test_image.py index aef74f0467..036c833651 100644 --- a/openstackclient/tests/unit/image/v1/test_image.py +++ b/openstackclient/tests/unit/image/v1/test_image.py @@ -116,7 +116,7 @@ def test_image_reserve_options(self): 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 @@ def test_image_reserve_options(self): ] verifylist = [ ('container_format', 'ovf'), - ('disk_format', 'fs'), + ('disk_format', 'ami'), ('min_disk', 10), ('min_ram', 4), ('protected', True), @@ -147,7 +147,7 @@ def test_image_reserve_options(self): 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 ebc9c3a759..2f2212e4c3 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -130,7 +130,7 @@ def test_image_reserve_options(self, mock_open): 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 @@ def test_image_reserve_options(self, mock_open): ] 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 @@ def test_image_reserve_options(self, mock_open): 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 @@ def test_image_create_with_unexist_owner(self): arglist = [ '--container-format', 'ovf', - '--disk-format', 'fs', + '--disk-format', 'ami', '--min-disk', '10', '--min-ram', '4', '--owner', 'unexist_owner', @@ -203,7 +203,7 @@ def test_image_create_with_unexist_owner(self): ] verifylist = [ ('container_format', 'ovf'), - ('disk_format', 'fs'), + ('disk_format', 'ami'), ('min_disk', 10), ('min_ram', 4), ('owner', 'unexist_owner'), @@ -227,7 +227,7 @@ def test_image_create_with_unexist_project(self): arglist = [ '--container-format', 'ovf', - '--disk-format', 'fs', + '--disk-format', 'ami', '--min-disk', '10', '--min-ram', '4', '--protected', @@ -237,7 +237,7 @@ def test_image_create_with_unexist_project(self): ] verifylist = [ ('container_format', 'ovf'), - ('disk_format', 'fs'), + ('disk_format', 'ami'), ('min_disk', 10), ('min_ram', 4), ('protected', True), From c8ab58bf61afb7c1f16018ba79bb102d399c6466 Mon Sep 17 00:00:00 2001 From: "zhiyong.dai" Date: Thu, 24 Nov 2016 22:45:21 +0800 Subject: [PATCH 1367/3095] Functional test for server group Rework functional tests to remove resource create/delete from setupClass() and teardownClass() methods. Change-Id: Ia852e48d3bcf706eefa56b03ba1f02b3fd7605cd --- .../compute/v2/test_server_group.py | 112 ++++++++++++++---- 1 file changed, 86 insertions(+), 26 deletions(-) diff --git a/openstackclient/tests/functional/compute/v2/test_server_group.py b/openstackclient/tests/functional/compute/v2/test_server_group.py index 3f0a24e5f9..44ecda1de7 100644 --- a/openstackclient/tests/functional/compute/v2/test_server_group.py +++ b/openstackclient/tests/functional/compute/v2/test_server_group.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import json import uuid from openstackclient.tests.functional import base @@ -18,29 +19,88 @@ class ServerGroupTests(base.TestCase): """Functional tests for servergroup.""" - NAME = uuid.uuid4().hex - HEADERS = ['Name'] - FIELDS = ['name'] - - @classmethod - def setUpClass(cls): - opts = cls.get_opts(cls.FIELDS) - raw_output = cls.openstack('server group create --policy affinity ' + - cls.NAME + opts) - expected = cls.NAME + '\n' - cls.assertOutput(expected, raw_output) - - @classmethod - def tearDownClass(cls): - raw_output = cls.openstack('server group delete ' + cls.NAME) - cls.assertOutput('', raw_output) - - def test_server_group_list(self): - opts = self.get_opts(self.HEADERS) - raw_output = self.openstack('server group list' + opts) - self.assertIn(self.NAME, raw_output) - - def test_server_group_show(self): - opts = self.get_opts(self.FIELDS) - raw_output = self.openstack('server group show ' + self.NAME + opts) - self.assertEqual(self.NAME + "\n", raw_output) + def test_server_group_delete(self): + """Test create, delete multiple""" + name1 = uuid.uuid4().hex + name2 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'server group create -f json ' + + '--policy affinity ' + + name1 + )) + self.assertEqual( + name1, + cmd_output['name'] + ) + self.assertEqual( + 'affinity', + cmd_output['policies'] + ) + + cmd_output = json.loads(self.openstack( + 'server group create -f json ' + + '--policy anti-affinity ' + + name2 + )) + self.assertEqual( + name2, + cmd_output['name'] + ) + self.assertEqual( + 'anti-affinity', + cmd_output['policies'] + ) + + del_output = self.openstack( + 'server group delete ' + name1 + ' ' + name2) + self.assertOutput('', del_output) + + def test_server_group_show_and_list(self): + """Test server group create, show, and list""" + name1 = uuid.uuid4().hex + name2 = uuid.uuid4().hex + + # test server gorup show + cmd_output = json.loads(self.openstack( + 'server group create -f json ' + + '--policy affinity ' + + name1 + )) + self.addCleanup(self.openstack, 'server group delete ' + name1) + cmd_output = json.loads(self.openstack( + 'server group show -f json ' + name1)) + self.assertEqual( + name1, + cmd_output['name'] + ) + self.assertEqual( + 'affinity', + cmd_output['policies'] + ) + + cmd_output = json.loads(self.openstack( + 'server group create -f json ' + + '--policy anti-affinity ' + + name2 + )) + self.addCleanup(self.openstack, 'server group delete ' + name2) + cmd_output = json.loads(self.openstack( + 'server group show -f json ' + name2)) + self.assertEqual( + name2, + cmd_output['name'] + ) + self.assertEqual( + 'anti-affinity', + cmd_output['policies'] + ) + + # test server group list + cmd_output = json.loads(self.openstack( + 'server group list -f json')) + names = [x["Name"] for x in cmd_output] + self.assertIn(name1, names) + self.assertIn(name2, names) + policies = [x["Policies"] for x in cmd_output] + self.assertIn('affinity', policies) + self.assertIn('anti-affinity', policies) From da03f6e17de0d720beba1c5b14cd18ca6699d8be Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 24 Nov 2016 18:50:36 +0000 Subject: [PATCH 1368/3095] Updated from global requirements Change-Id: I517d93ce1a2fcdb6b674bd5617902c4daf078031 --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 8b15c292e0..d323c422e3 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -27,7 +27,7 @@ aodhclient>=0.5.0 # Apache-2.0 python-barbicanclient>=4.0.0 # Apache-2.0 python-congressclient<2000,>=1.3.0 # Apache-2.0 python-designateclient>=1.5.0 # Apache-2.0 -python-heatclient!=1.6.0,>=1.5.0 # Apache-2.0 +python-heatclient>=1.6.1 # Apache-2.0 python-ironicclient>=1.6.0 # Apache-2.0 python-ironic-inspector-client>=1.5.0 # Apache-2.0 python-mistralclient>=2.0.0 # Apache-2.0 From 71e6d444767a29664821ab190e0736088d8ccfba Mon Sep 17 00:00:00 2001 From: "jiahui.qiang" Date: Mon, 21 Nov 2016 05:47:48 +0800 Subject: [PATCH 1369/3095] Add "volume host set" command Add "volume host set" command in volume v2 to support freeze(disable) and thaw(enable) the specified cinder-volume host Change-Id: Iee1604d72f9eccd9e327b0ef8d345909a733a647 Implements: bp cinder-command-support --- doc/source/command-objects/volume-host.rst | 30 +++++++ doc/source/commands.rst | 1 + .../tests/unit/volume/v2/test_volume_host.py | 86 +++++++++++++++++++ openstackclient/volume/v2/volume_host.py | 50 +++++++++++ ...nder-command-support-7e3ae1fb4cd90407.yaml | 5 ++ setup.cfg | 2 + 6 files changed, 174 insertions(+) create mode 100644 doc/source/command-objects/volume-host.rst create mode 100644 openstackclient/tests/unit/volume/v2/test_volume_host.py create mode 100644 openstackclient/volume/v2/volume_host.py create mode 100644 releasenotes/notes/bp-cinder-command-support-7e3ae1fb4cd90407.yaml diff --git a/doc/source/command-objects/volume-host.rst b/doc/source/command-objects/volume-host.rst new file mode 100644 index 0000000000..956fce525b --- /dev/null +++ b/doc/source/command-objects/volume-host.rst @@ -0,0 +1,30 @@ +=========== +volume host +=========== + +Volume v2 + +volume host set +--------------- + +Set volume host properties + +.. program:: volume host set +.. code:: bash + + os volume host set + [--enable | --disable] + + +.. option:: --enable + + Thaw and enable the specified volume host + +.. option:: --disable + + Freeze and disable the specified volume host + +.. _volume-host-set: +.. describe:: + + Name of volume host diff --git a/doc/source/commands.rst b/doc/source/commands.rst index f7ef3eaabb..795dfd530b 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -144,6 +144,7 @@ referring to both Compute and Volume quotas. * ``user role``: (**Identity**) roles assigned to a user * ``volume``: (**Volume**) block volumes * ``volume backup``: (**Volume**) backup for volumes +* ``volume host``: (**Volume**) the physical computer for volumes * ``volume qos``: (**Volume**) quality-of-service (QoS) specification for volumes * ``volume snapshot``: (**Volume**) a point-in-time copy of a volume * ``volume type``: (**Volume**) deployment-specific types of volumes available diff --git a/openstackclient/tests/unit/volume/v2/test_volume_host.py b/openstackclient/tests/unit/volume/v2/test_volume_host.py new file mode 100644 index 0000000000..aad7bb0bf4 --- /dev/null +++ b/openstackclient/tests/unit/volume/v2/test_volume_host.py @@ -0,0 +1,86 @@ +# +# 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.unit.volume.v2 import fakes as host_fakes +from openstackclient.volume.v2 import volume_host + + +class TestVolumeHost(host_fakes.TestVolume): + + def setUp(self): + super(TestVolumeHost, self).setUp() + + self.host_mock = self.app.client_manager.volume.services + self.host_mock.reset_mock() + + +class TestVolumeHostSet(TestVolumeHost): + + service = host_fakes.FakeService.create_one_service() + + def setUp(self): + super(TestVolumeHostSet, self).setUp() + + self.host_mock.freeze_host.return_value = None + self.host_mock.thaw_host.return_value = None + + self.cmd = volume_host.SetVolumeHost(self.app, None) + + def test_volume_host_set_nothing(self): + arglist = [ + self.service.host, + ] + verifylist = [ + ('host', self.service.host), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.host_mock.freeze_host.assert_not_called() + self.host_mock.thaw_host.assert_not_called() + self.assertIsNone(result) + + def test_volume_host_set_enable(self): + arglist = [ + '--enable', + self.service.host, + ] + verifylist = [ + ('enable', True), + ('host', self.service.host), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.host_mock.thaw_host.assert_called_with(self.service.host) + self.host_mock.freeze_host.assert_not_called() + self.assertIsNone(result) + + def test_volume_host_set_disable(self): + arglist = [ + '--disable', + self.service.host, + ] + verifylist = [ + ('disable', True), + ('host', self.service.host), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.host_mock.freeze_host.assert_called_with(self.service.host) + self.host_mock.thaw_host.assert_not_called() + self.assertIsNone(result) diff --git a/openstackclient/volume/v2/volume_host.py b/openstackclient/volume/v2/volume_host.py new file mode 100644 index 0000000000..376e502491 --- /dev/null +++ b/openstackclient/volume/v2/volume_host.py @@ -0,0 +1,50 @@ +# +# 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 host action implementations""" + +from osc_lib.command import command + +from openstackclient.i18n import _ + + +class SetVolumeHost(command.Command): + _description = _("Set volume host properties") + + def get_parser(self, prog_name): + parser = super(SetVolumeHost, self).get_parser(prog_name) + parser.add_argument( + "host", + metavar="", + help=_("Name of volume host") + ) + enabled_group = parser.add_mutually_exclusive_group() + enabled_group.add_argument( + "--disable", + action="store_true", + help=_("Freeze and disable the specified volume host.") + ) + enabled_group.add_argument( + "--enable", + action="store_true", + help=_("Thaw and enable the specified volume host.") + ) + return parser + + def take_action(self, parsed_args): + service_client = self.app.client_manager.volume + if parsed_args.enable: + service_client.services.thaw_host(parsed_args.host) + if parsed_args.disable: + service_client.services.freeze_host(parsed_args.host) diff --git a/releasenotes/notes/bp-cinder-command-support-7e3ae1fb4cd90407.yaml b/releasenotes/notes/bp-cinder-command-support-7e3ae1fb4cd90407.yaml new file mode 100644 index 0000000000..1b0ca18de3 --- /dev/null +++ b/releasenotes/notes/bp-cinder-command-support-7e3ae1fb4cd90407.yaml @@ -0,0 +1,5 @@ +--- +features: + - Add ``volume host set`` command, it allows a user to enable or disable a volume host. + [Blueprint `cinder-command-support `_] + diff --git a/setup.cfg b/setup.cfg index 2bbbb5ae72..ee1fc2ecb6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -547,6 +547,8 @@ openstack.volume.v2 = volume_backup_set = openstackclient.volume.v2.backup:SetVolumeBackup volume_backup_show = openstackclient.volume.v2.backup:ShowVolumeBackup + volume_host_set = openstackclient.volume.v2.volume_host:SetVolumeHost + volume_snapshot_create = openstackclient.volume.v2.volume_snapshot:CreateVolumeSnapshot volume_snapshot_delete = openstackclient.volume.v2.volume_snapshot:DeleteVolumeSnapshot volume_snapshot_list = openstackclient.volume.v2.volume_snapshot:ListVolumeSnapshot From ff3566ef48f1a68d5267cd758d42fdc2140d14b0 Mon Sep 17 00:00:00 2001 From: "zhiyong.dai" Date: Sat, 26 Nov 2016 02:13:30 +0800 Subject: [PATCH 1370/3095] Functional test for agent Using json format output in compute agent functional tests. Remove resource create/delete from setupClass() and teardownClass() methods Change-Id: Ic7c6c268dfccca097709378c0473eb82cddf7bc6 --- .../tests/functional/compute/v2/test_agent.py | 233 +++++++++++++----- 1 file changed, 176 insertions(+), 57 deletions(-) diff --git a/openstackclient/tests/functional/compute/v2/test_agent.py b/openstackclient/tests/functional/compute/v2/test_agent.py index 7115db1f4f..1a112e8212 100644 --- a/openstackclient/tests/functional/compute/v2/test_agent.py +++ b/openstackclient/tests/functional/compute/v2/test_agent.py @@ -11,6 +11,7 @@ # under the License. import hashlib +import json from openstackclient.tests.functional import base @@ -18,60 +19,178 @@ class ComputeAgentTests(base.TestCase): """Functional tests for compute agent.""" - ID = None - MD5HASH = hashlib.md5().hexdigest() - URL = "http://localhost" - VER = "v1" - OS = "TEST_OS" - ARCH = "x86_64" - HYPER = "kvm" - - HEADERS = ['agent_id', 'md5hash'] - FIELDS = ['agent_id', 'md5hash'] - - @classmethod - def setUpClass(cls): - opts = cls.get_opts(cls.HEADERS) - raw_output = cls.openstack('compute agent create ' + - cls.OS + ' ' + cls.ARCH + ' ' + - cls.VER + ' ' + cls.URL + ' ' + - cls.MD5HASH + ' ' + cls.HYPER + ' ' + - opts) - - # Get agent id because agent can only be deleted by ID - output_list = raw_output.split('\n', 1) - cls.ID = output_list[0] - - cls.assertOutput(cls.MD5HASH + '\n', output_list[1]) - - @classmethod - def tearDownClass(cls): - raw_output = cls.openstack('compute agent delete ' + cls.ID) - cls.assertOutput('', raw_output) - - def test_agent_list(self): - raw_output = self.openstack('compute agent list') - self.assertIn(self.ID, raw_output) - self.assertIn(self.OS, raw_output) - self.assertIn(self.ARCH, raw_output) - self.assertIn(self.VER, raw_output) - self.assertIn(self.URL, raw_output) - self.assertIn(self.MD5HASH, raw_output) - self.assertIn(self.HYPER, raw_output) - - def test_agent_set(self): - ver = 'v2' - url = "http://openstack" - md5hash = hashlib.md5().hexdigest() - - self.openstack('compute agent set ' - + self.ID - + ' --agent-version ' + ver - + ' --url ' + url - + ' --md5hash ' + md5hash) - - raw_output = self.openstack('compute agent list') - self.assertIn(self.ID, raw_output) - self.assertIn(ver, raw_output) - self.assertIn(url, raw_output) - self.assertIn(md5hash, raw_output) + # Generate two different md5hash + MD5HASH1 = hashlib.md5() + MD5HASH1.update('agent_1') + MD5HASH1 = MD5HASH1.hexdigest() + MD5HASH2 = hashlib.md5() + MD5HASH2.update('agent_2') + MD5HASH2 = MD5HASH2.hexdigest() + + def test_compute_agent_delete(self): + """Test compute agent create, delete multiple""" + os1 = "os_1" + arch1 = "x86_64" + ver1 = "v1" + url1 = "http://localhost" + md5hash1 = self.MD5HASH1 + hyper1 = "kvm" + cmd1 = ' '.join((os1, arch1, ver1, url1, md5hash1, hyper1)) + + cmd_output = json.loads(self.openstack( + 'compute agent create -f json ' + + cmd1 + )) + agent_id1 = str(cmd_output["agent_id"]) + + os2 = "os_2" + arch2 = "x86" + ver2 = "v2" + url2 = "http://openstack" + md5hash2 = self.MD5HASH2 + hyper2 = "xen" + cmd2 = ' '.join((os2, arch2, ver2, url2, md5hash2, hyper2)) + + cmd_output = json.loads(self.openstack( + 'compute agent create -f json ' + + cmd2 + )) + agent_id2 = str(cmd_output["agent_id"]) + + # Test compute agent delete + del_output = self.openstack( + 'compute agent delete ' + + agent_id1 + ' ' + agent_id2 + ) + self.assertOutput('', del_output) + + def test_compute_agent_list(self): + """Test compute agent create and list""" + os1 = "os_1" + arch1 = "x86_64" + ver1 = "v1" + url1 = "http://localhost" + md5hash1 = self.MD5HASH1 + hyper1 = "kvm" + cmd1 = ' '.join((os1, arch1, ver1, url1, md5hash1, hyper1)) + + cmd_output = json.loads(self.openstack( + 'compute agent create -f json ' + + cmd1 + )) + agent_id1 = str(cmd_output["agent_id"]) + self.addCleanup(self.openstack, 'compute agent delete ' + agent_id1) + + os2 = "os_2" + arch2 = "x86" + ver2 = "v2" + url2 = "http://openstack" + md5hash2 = self.MD5HASH2 + hyper2 = "xen" + cmd2 = ' '.join((os2, arch2, ver2, url2, md5hash2, hyper2)) + + cmd_output = json.loads(self.openstack( + 'compute agent create -f json ' + + cmd2 + )) + agent_id2 = str(cmd_output["agent_id"]) + self.addCleanup(self.openstack, 'compute agent delete ' + agent_id2) + + # Test compute agent list + cmd_output = json.loads(self.openstack( + 'compute agent list -f json' + )) + + hypervisors = [x["Hypervisor"] for x in cmd_output] + self.assertIn(hyper1, hypervisors) + self.assertIn(hyper2, hypervisors) + + os = [x['OS'] for x in cmd_output] + self.assertIn(os1, os) + self.assertIn(os2, os) + + archs = [x['Architecture'] for x in cmd_output] + self.assertIn(arch1, archs) + self.assertIn(arch2, archs) + + versions = [x['Version'] for x in cmd_output] + self.assertIn(ver1, versions) + self.assertIn(ver2, versions) + + md5hashes = [x['Md5Hash'] for x in cmd_output] + self.assertIn(md5hash1, md5hashes) + self.assertIn(md5hash2, md5hashes) + + urls = [x['URL'] for x in cmd_output] + self.assertIn(url1, urls) + self.assertIn(url2, urls) + + # Test compute agent list --hypervisor + cmd_output = json.loads(self.openstack( + 'compute agent list -f json ' + + '--hypervisor kvm' + )) + + hypervisors = [x["Hypervisor"] for x in cmd_output] + self.assertIn(hyper1, hypervisors) + self.assertNotIn(hyper2, hypervisors) + + os = [x['OS'] for x in cmd_output] + self.assertIn(os1, os) + self.assertNotIn(os2, os) + + archs = [x['Architecture'] for x in cmd_output] + self.assertIn(arch1, archs) + self.assertNotIn(arch2, archs) + + versions = [x['Version'] for x in cmd_output] + self.assertIn(ver1, versions) + self.assertNotIn(ver2, versions) + + md5hashes = [x['Md5Hash'] for x in cmd_output] + self.assertIn(md5hash1, md5hashes) + self.assertNotIn(md5hash2, md5hashes) + + urls = [x['URL'] for x in cmd_output] + self.assertIn(url1, urls) + self.assertNotIn(url2, urls) + + def test_compute_agent_set(self): + """Test compute agent set""" + os1 = "os_1" + arch1 = "x86_64" + ver1 = "v1" + ver2 = "v2" + url1 = "http://localhost" + url2 = "http://openstack" + md5hash1 = self.MD5HASH1 + md5hash2 = self.MD5HASH2 + hyper1 = "kvm" + cmd = ' '.join((os1, arch1, ver1, url1, md5hash1, hyper1)) + + cmd_output = json.loads(self.openstack( + 'compute agent create -f json ' + + cmd + )) + agent_id = str(cmd_output["agent_id"]) + self.assertEqual(ver1, cmd_output["version"]) + self.assertEqual(url1, cmd_output["url"]) + self.assertEqual(md5hash1, cmd_output["md5hash"]) + + self.addCleanup(self.openstack, 'compute agent delete ' + agent_id) + + raw_output = self.openstack( + 'compute agent set ' + + agent_id + ' ' + + '--agent-version ' + ver2 + ' ' + + '--url ' + url2 + ' ' + + '--md5hash ' + md5hash2 + ) + self.assertOutput('', raw_output) + + cmd_output = json.loads(self.openstack( + 'compute agent list -f json' + )) + self.assertEqual(ver2, cmd_output[0]["Version"]) + self.assertEqual(url2, cmd_output[0]["URL"]) + self.assertEqual(md5hash2, cmd_output[0]["Md5Hash"]) From 1f8485b94f4a89f033683e7e9b982a5634ffc015 Mon Sep 17 00:00:00 2001 From: Flavio Percoco Date: Fri, 25 Nov 2016 10:55:35 +0100 Subject: [PATCH 1371/3095] Show team and repo badges on README This patch adds the team's and repository's badges to the README file. The motivation behind this is to communicate the project status and features at first glance. For more information about this effort, please read this email thread: http://lists.openstack.org/pipermail/openstack-dev/2016-October/105562.html To see an example of how this would look like check: https://gist.github.com/30af7c8ad287756d38d131d1f5dceae0 Change-Id: I2d906fa0675463e21f2786d93f464c465f2bc3b1 --- README.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.rst b/README.rst index 8ebc24b507..86f6be136a 100644 --- a/README.rst +++ b/README.rst @@ -1,3 +1,12 @@ +======================== +Team and repository tags +======================== + +.. image:: http://governance.openstack.org/badges/python-openstackclient.svg + :target: http://governance.openstack.org/reference/tags/index.html + +.. Change things from this point on + =============== OpenStackClient =============== From 6ca4dc3533d009866f82515c34cb3881f993c750 Mon Sep 17 00:00:00 2001 From: "jiahui.qiang" Date: Tue, 22 Nov 2016 17:27:34 +0800 Subject: [PATCH 1372/3095] Add options to "volume snapshot list" command Add "--name", "--status" and "--volume" options to "volume snapshot list" command for filtering results. Change-Id: I72db1abce7701f31598deec34801a4d1f5713870 Closes-Bug:#1645252 --- .../command-objects/volume-snapshot.rst | 16 +++ .../tests/unit/volume/v1/test_snapshot.py | 96 +++++++++++++++- .../tests/unit/volume/v2/test_snapshot.py | 103 +++++++++++++++++- openstackclient/volume/v1/volume_snapshot.py | 34 +++++- openstackclient/volume/v2/volume_snapshot.py | 34 +++++- .../notes/bug-1645252-219bfd50c8f04846.yaml | 6 + 6 files changed, 278 insertions(+), 11 deletions(-) create mode 100644 releasenotes/notes/bug-1645252-219bfd50c8f04846.yaml diff --git a/doc/source/command-objects/volume-snapshot.rst b/doc/source/command-objects/volume-snapshot.rst index b84601f464..2d9406b630 100644 --- a/doc/source/command-objects/volume-snapshot.rst +++ b/doc/source/command-objects/volume-snapshot.rst @@ -71,6 +71,9 @@ List volume snapshots [--long] [--limit ] [--marker ] + [--name ] + [--status ] + [--volume ] .. option:: --all-projects @@ -80,6 +83,19 @@ List volume snapshots List additional fields in output +.. option:: --status + + Filters results by a status. + ('available', 'error', 'creating', 'deleting' or 'error-deleting') + +.. option:: --name + + Filters results by a name. + +.. option:: --volume + + Filters results by a volume (name or ID). + .. option:: --limit Maximum number of snapshots to display diff --git a/openstackclient/tests/unit/volume/v1/test_snapshot.py b/openstackclient/tests/unit/volume/v1/test_snapshot.py index 8e30d6a9d5..fd878f4531 100644 --- a/openstackclient/tests/unit/volume/v1/test_snapshot.py +++ b/openstackclient/tests/unit/volume/v1/test_snapshot.py @@ -268,6 +268,7 @@ def setUp(self): super(TestSnapshotList, self).setUp() self.volumes_mock.list.return_value = [self.volume] + self.volumes_mock.get.return_value = self.volume self.snapshots_mock.list.return_value = self.snapshots # Get the command to test self.cmd = volume_snapshot.ListVolumeSnapshot(self.app, None) @@ -283,7 +284,13 @@ def test_snapshot_list_without_options(self): columns, data = self.cmd.take_action(parsed_args) self.snapshots_mock.list.assert_called_once_with( - search_opts={'all_tenants': False}) + search_opts={ + 'all_tenants': False, + 'display_name': None, + 'status': None, + 'volume_id': None + } + ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) @@ -300,11 +307,88 @@ def test_snapshot_list_with_long(self): columns, data = self.cmd.take_action(parsed_args) self.snapshots_mock.list.assert_called_once_with( - search_opts={'all_tenants': False} + search_opts={ + 'all_tenants': False, + 'display_name': None, + 'status': None, + 'volume_id': None + } ) self.assertEqual(self.columns_long, columns) self.assertEqual(self.data_long, list(data)) + def test_snapshot_list_name_option(self): + arglist = [ + '--name', self.snapshots[0].display_name, + ] + verifylist = [ + ('all_projects', False), + ('long', False), + ('name', self.snapshots[0].display_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.snapshots_mock.list.assert_called_once_with( + search_opts={ + 'all_tenants': False, + 'display_name': self.snapshots[0].display_name, + 'status': None, + 'volume_id': None + } + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_snapshot_list_status_option(self): + arglist = [ + '--status', self.snapshots[0].status, + ] + verifylist = [ + ('all_projects', False), + ('long', False), + ('status', self.snapshots[0].status), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.snapshots_mock.list.assert_called_once_with( + search_opts={ + 'all_tenants': False, + 'display_name': None, + 'status': self.snapshots[0].status, + 'volume_id': None + } + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_snapshot_list_volumeid_option(self): + arglist = [ + '--volume', self.volume.id, + ] + verifylist = [ + ('all_projects', False), + ('long', False), + ('volume', self.volume.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.snapshots_mock.list.assert_called_once_with( + search_opts={ + 'all_tenants': False, + 'display_name': None, + 'status': None, + 'volume_id': self.volume.id + } + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + def test_snapshot_list_all_projects(self): arglist = [ '--all-projects', @@ -318,7 +402,13 @@ def test_snapshot_list_all_projects(self): columns, data = self.cmd.take_action(parsed_args) self.snapshots_mock.list.assert_called_once_with( - search_opts={'all_tenants': True}) + search_opts={ + 'all_tenants': True, + 'display_name': None, + 'status': None, + 'volume_id': None + } + ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) diff --git a/openstackclient/tests/unit/volume/v2/test_snapshot.py b/openstackclient/tests/unit/volume/v2/test_snapshot.py index b67dd6ebf5..bb238135fc 100644 --- a/openstackclient/tests/unit/volume/v2/test_snapshot.py +++ b/openstackclient/tests/unit/volume/v2/test_snapshot.py @@ -275,6 +275,7 @@ def setUp(self): super(TestSnapshotList, self).setUp() self.volumes_mock.list.return_value = [self.volume] + self.volumes_mock.get.return_value = self.volume self.snapshots_mock.list.return_value = self.snapshots # Get the command to test self.cmd = volume_snapshot.ListVolumeSnapshot(self.app, None) @@ -283,14 +284,21 @@ def test_snapshot_list_without_options(self): arglist = [] verifylist = [ ('all_projects', False), - ("long", False) + ('long', False) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.snapshots_mock.list.assert_called_once_with( - limit=None, marker=None, search_opts={'all_tenants': False}) + limit=None, marker=None, + search_opts={ + 'all_tenants': False, + 'name': None, + 'status': None, + 'volume_id': None + } + ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) @@ -313,7 +321,12 @@ def test_snapshot_list_with_options(self): self.snapshots_mock.list.assert_called_once_with( limit=2, marker=self.snapshots[0].id, - search_opts={'all_tenants': False} + search_opts={ + 'all_tenants': False, + 'name': None, + 'status': None, + 'volume_id': None + } ) self.assertEqual(self.columns_long, columns) self.assertEqual(self.data_long, list(data)) @@ -331,7 +344,89 @@ def test_snapshot_list_all_projects(self): columns, data = self.cmd.take_action(parsed_args) self.snapshots_mock.list.assert_called_once_with( - limit=None, marker=None, search_opts={'all_tenants': True}) + limit=None, marker=None, + search_opts={ + 'all_tenants': True, + 'name': None, + 'status': None, + 'volume_id': None + } + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_snapshot_list_name_option(self): + arglist = [ + '--name', self.snapshots[0].name, + ] + verifylist = [ + ('all_projects', False), + ('long', False), + ('name', self.snapshots[0].name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.snapshots_mock.list.assert_called_once_with( + limit=None, marker=None, + search_opts={ + 'all_tenants': False, + 'name': self.snapshots[0].name, + 'status': None, + 'volume_id': None + } + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_snapshot_list_status_option(self): + arglist = [ + '--status', self.snapshots[0].status, + ] + verifylist = [ + ('all_projects', False), + ('long', False), + ('status', self.snapshots[0].status), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.snapshots_mock.list.assert_called_once_with( + limit=None, marker=None, + search_opts={ + 'all_tenants': False, + 'name': None, + 'status': self.snapshots[0].status, + 'volume_id': None + } + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_snapshot_list_volumeid_option(self): + arglist = [ + '--volume', self.volume.id, + ] + verifylist = [ + ('all_projects', False), + ('long', False), + ('volume', self.volume.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.snapshots_mock.list.assert_called_once_with( + limit=None, marker=None, + search_opts={ + 'all_tenants': False, + 'name': None, + 'status': None, + 'volume_id': self.volume.id + } + ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) diff --git a/openstackclient/volume/v1/volume_snapshot.py b/openstackclient/volume/v1/volume_snapshot.py index c2ecf75b5b..77c93ad4fb 100644 --- a/openstackclient/volume/v1/volume_snapshot.py +++ b/openstackclient/volume/v1/volume_snapshot.py @@ -135,9 +135,31 @@ def get_parser(self, prog_name): default=False, help=_('List additional fields in output'), ) + parser.add_argument( + '--name', + metavar='', + default=None, + help=_('Filters results by a name.') + ) + parser.add_argument( + '--status', + metavar='', + choices=['available', 'error', 'creating', 'deleting', + 'error-deleting'], + help=_("Filters results by a status. " + "('available', 'error', 'creating', 'deleting'" + " or 'error-deleting')") + ) + parser.add_argument( + '--volume', + metavar='', + default=None, + help=_('Filters results by a volume (name or ID).') + ) return parser def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume def _format_volume_id(volume_id): """Return a volume name if available @@ -169,17 +191,25 @@ def _format_volume_id(volume_id): # Cache the volume list volume_cache = {} try: - for s in self.app.client_manager.volume.volumes.list(): + for s in volume_client.volumes.list(): volume_cache[s.id] = s except Exception: # Just forget it if there's any trouble pass + volume_id = None + if parsed_args.volume: + volume_id = utils.find_resource( + volume_client.volumes, parsed_args.volume).id + search_opts = { 'all_tenants': parsed_args.all_projects, + 'display_name': parsed_args.name, + 'status': parsed_args.status, + 'volume_id': volume_id, } - data = self.app.client_manager.volume.volume_snapshots.list( + data = volume_client.volume_snapshots.list( search_opts=search_opts) return (column_headers, (utils.get_item_properties( diff --git a/openstackclient/volume/v2/volume_snapshot.py b/openstackclient/volume/v2/volume_snapshot.py index 43f30326ed..86af6d8c43 100644 --- a/openstackclient/volume/v2/volume_snapshot.py +++ b/openstackclient/volume/v2/volume_snapshot.py @@ -151,9 +151,31 @@ def get_parser(self, prog_name): metavar='', help=_('Maximum number of snapshots to display'), ) + parser.add_argument( + '--name', + metavar='', + default=None, + help=_('Filters results by a name.') + ) + parser.add_argument( + '--status', + metavar='', + choices=['available', 'error', 'creating', 'deleting', + 'error-deleting'], + help=_("Filters results by a status. " + "('available', 'error', 'creating', 'deleting'" + " or 'error-deleting')") + ) + parser.add_argument( + '--volume', + metavar='', + default=None, + help=_('Filters results by a volume (name or ID).') + ) return parser def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume def _format_volume_id(volume_id): """Return a volume name if available @@ -180,17 +202,25 @@ def _format_volume_id(volume_id): # Cache the volume list volume_cache = {} try: - for s in self.app.client_manager.volume.volumes.list(): + for s in volume_client.volumes.list(): volume_cache[s.id] = s except Exception: # Just forget it if there's any trouble pass + volume_id = None + if parsed_args.volume: + volume_id = utils.find_resource( + volume_client.volumes, parsed_args.volume).id + search_opts = { 'all_tenants': parsed_args.all_projects, + 'name': parsed_args.name, + 'status': parsed_args.status, + 'volume_id': volume_id, } - data = self.app.client_manager.volume.volume_snapshots.list( + data = volume_client.volume_snapshots.list( search_opts=search_opts, marker=parsed_args.marker, limit=parsed_args.limit, diff --git a/releasenotes/notes/bug-1645252-219bfd50c8f04846.yaml b/releasenotes/notes/bug-1645252-219bfd50c8f04846.yaml new file mode 100644 index 0000000000..12b1e42deb --- /dev/null +++ b/releasenotes/notes/bug-1645252-219bfd50c8f04846.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--name``, ``--status`` and ``--volume`` options + to ``volume snapshot list`` command + [Bug `1645252 `_] From 4fafd837ef60c8d12f6a4ed019ed7214f98509f0 Mon Sep 17 00:00:00 2001 From: "zhiyong.dai" Date: Sat, 26 Nov 2016 22:18:57 +0800 Subject: [PATCH 1373/3095] DevRef fix for network qos policy Several help messages are not proper. This patch will do the following work: * Remove redundant square bracket ']' * Modify the wrong message Change-Id: I2f05d1dc86838dbe77169074f93e467a959d0122 --- doc/source/command-objects/network-qos-policy.rst | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/doc/source/command-objects/network-qos-policy.rst b/doc/source/command-objects/network-qos-policy.rst index cf594497a8..7ec6776c97 100644 --- a/doc/source/command-objects/network-qos-policy.rst +++ b/doc/source/command-objects/network-qos-policy.rst @@ -38,11 +38,12 @@ Create new Network QoS policy Owner's project (name or ID) -.. option:: --project-domain ] +.. option:: --project-domain Domain the project belongs to (name or ID). This can be used in case collisions between project names exist. +.. _network_qos_policy_create-name: .. describe:: New QoS policy specification name @@ -58,6 +59,7 @@ Delete Network QoS policy openstack network qos policy delete [ ...] +.. _network_qos_policy_delete-qos-policy: .. describe:: Network QoS policy(s) to delete (name or ID) @@ -102,9 +104,10 @@ Set Network QoS policy properties Make the QoS policy not accessible by other projects +.. _network_qos_policy_set-qos-policy: .. describe:: - Network QoS policy(s) to delete (name or ID) + Network QoS policy to modify (name or ID) network qos policy show ----------------------- @@ -117,6 +120,7 @@ Display Network QoS policy details openstack network qos policy show +.. _network_qos_policy_show-qos-policy: .. describe:: - Network QoS policy(s) to show (name or ID) + Network QoS policy to display (name or ID) From dc1d510350172b182720eff8d60b579a6d59d016 Mon Sep 17 00:00:00 2001 From: "jiahui.qiang" Date: Sun, 27 Nov 2016 06:18:00 +0800 Subject: [PATCH 1374/3095] Fix description errors in volume fakes In volumev2 fakes.py. Description of some 'get_*' methods is incorrect. the error will mislead the user. this patch has already fixed it. Change-Id: I048e3c2b625c7b54ab19f2b923d4970427f277ff --- openstackclient/tests/unit/volume/v2/fakes.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openstackclient/tests/unit/volume/v2/fakes.py b/openstackclient/tests/unit/volume/v2/fakes.py index 3137bfb012..d5cd72ecce 100644 --- a/openstackclient/tests/unit/volume/v2/fakes.py +++ b/openstackclient/tests/unit/volume/v2/fakes.py @@ -480,7 +480,7 @@ def get_backups(backups=None, count=2): If backups list is provided, then initialize the Mock object with the list. Otherwise create one. - :param List volumes: + :param List backups: A list of FakeResource objects faking backups :param Integer count: The number of backups to be faked @@ -764,7 +764,7 @@ def get_qoses(qoses=None, count=2): If qoses list is provided, then initialize the Mock object with the list. Otherwise create one. - :param List volumes: + :param List qoses: A list of FakeResource objects faking qoses :param Integer count: The number of qoses to be faked @@ -837,7 +837,7 @@ def get_snapshots(snapshots=None, count=2): If snapshots list is provided, then initialize the Mock object with the list. Otherwise create one. - :param List volumes: + :param List snapshots: A list of FakeResource objects faking snapshots :param Integer count: The number of snapshots to be faked From 26a74d4596101fcd371ebf0a401265a0e0b4e1e0 Mon Sep 17 00:00:00 2001 From: "zhiyong.dai" Date: Fri, 25 Nov 2016 01:04:34 +0800 Subject: [PATCH 1375/3095] Add choices for option '--policy' When creating server group, the '--policy' option is required, but valid choice is 'affinity' or 'anti-affinity', adding multiple policies will produce an error. This patch add choices and default value for option '--policy', and remove the former help message. Change-Id: I312680af384363b3e9a7de9aa1f0946643e193e2 Closes-Bug: #1662771 --- doc/source/command-objects/server-group.rst | 4 +- openstackclient/compute/v2/server_group.py | 9 +++-- .../unit/compute/v2/test_server_group.py | 37 ++----------------- 3 files changed, 10 insertions(+), 40 deletions(-) diff --git a/doc/source/command-objects/server-group.rst b/doc/source/command-objects/server-group.rst index 77c6431c1e..9769635073 100644 --- a/doc/source/command-objects/server-group.rst +++ b/doc/source/command-objects/server-group.rst @@ -15,13 +15,13 @@ Create a new server group .. code-block:: bash openstack server group create - --policy [--policy ] ... + --policy .. option:: --policy Add a policy to :ref:`\ ` - (repeat option to add multiple policies) + ('affinity' or 'anti-affinity', default to 'affinity') .. _server_group_create-name: .. describe:: diff --git a/openstackclient/compute/v2/server_group.py b/openstackclient/compute/v2/server_group.py index 6bcfc6aef7..c6e2161f60 100644 --- a/openstackclient/compute/v2/server_group.py +++ b/openstackclient/compute/v2/server_group.py @@ -55,10 +55,11 @@ def get_parser(self, prog_name): parser.add_argument( '--policy', metavar='', - action='append', - required=True, + choices=['affinity', 'anti-affinity'], + default='affinity', help=_("Add a policy to " - "(repeat option to add multiple policies)") + "('affinity' or 'anti-affinity', " + "default to 'affinity')") ) return parser @@ -67,7 +68,7 @@ def take_action(self, parsed_args): info = {} server_group = compute_client.server_groups.create( name=parsed_args.name, - policies=parsed_args.policy) + policies=[parsed_args.policy]) info.update(server_group._info) columns = _get_columns(info) diff --git a/openstackclient/tests/unit/compute/v2/test_server_group.py b/openstackclient/tests/unit/compute/v2/test_server_group.py index d474f41d80..088497da30 100644 --- a/openstackclient/tests/unit/compute/v2/test_server_group.py +++ b/openstackclient/tests/unit/compute/v2/test_server_group.py @@ -63,54 +63,23 @@ def setUp(self): def test_server_group_create(self): arglist = [ - '--policy', 'affinity', + '--policy', 'anti-affinity', 'affinity_group', ] verifylist = [ - ('policy', ['affinity']), + ('policy', 'anti-affinity'), ('name', 'affinity_group'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.server_groups_mock.create.assert_called_once_with( name=parsed_args.name, - policies=parsed_args.policy, + policies=[parsed_args.policy], ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) - def test_server_group_create_with_multiple_policies(self): - arglist = [ - '--policy', 'affinity', - '--policy', 'soft-affinity', - 'affinity_group', - ] - verifylist = [ - ('policy', ['affinity', 'soft-affinity']), - ('name', 'affinity_group'), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) - self.server_groups_mock.create.assert_called_once_with( - name=parsed_args.name, - policies=parsed_args.policy, - ) - - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) - - def test_server_group_create_no_policy(self): - arglist = [ - 'affinity_group', - ] - verifylist = None - self.assertRaises(tests_utils.ParserException, - self.check_parser, - self.cmd, - arglist, - verifylist) - class TestServerGroupDelete(TestServerGroup): From 3258664c7a2ab7a4fda3ed5e46ec4768ca6c56e8 Mon Sep 17 00:00:00 2001 From: "zhiyong.dai" Date: Sun, 27 Nov 2016 21:07:34 +0800 Subject: [PATCH 1376/3095] Refactor module functional test Combine "test_module_list_no_options" and "test_module_list_with_all_option" into one test, and modify the test for "--all' option. Change-Id: If4e7a73502a888c50de17ec19ef7e8d02dd23f1d --- .../tests/functional/common/test_module.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/openstackclient/tests/functional/common/test_module.py b/openstackclient/tests/functional/common/test_module.py index f56c1627bb..b56c83ad24 100644 --- a/openstackclient/tests/functional/common/test_module.py +++ b/openstackclient/tests/functional/common/test_module.py @@ -29,12 +29,15 @@ class ModuleTest(base.TestCase): 'os_client_config', 'keystoneauth1'] - def test_module_list_no_options(self): - json_output = json.loads(self.openstack('module list -f json')) + def test_module_list(self): + # Test module list + cmd_output = json.loads(self.openstack('module list -f json')) for one_module in self.CLIENTS: - self.assertIn(one_module, json_output.keys()) - - def test_module_list_with_all_option(self): - json_output = json.loads(self.openstack('module list --all -f json')) - for one_module in (self.CLIENTS + self.LIBS): - self.assertIn(one_module, json_output.keys()) + self.assertIn(one_module, cmd_output.keys()) + for one_module in self.LIBS: + self.assertNotIn(one_module, cmd_output.keys()) + + # Test module list --all + cmd_output = json.loads(self.openstack('module list --all -f json')) + for one_module in self.CLIENTS + self.LIBS: + self.assertIn(one_module, cmd_output.keys()) From 7d8602c9bc86e16ec23b3fedaee112202d4f6632 Mon Sep 17 00:00:00 2001 From: "zhiyong.dai" Date: Fri, 25 Nov 2016 19:44:23 +0800 Subject: [PATCH 1377/3095] Functional test for aggregate Using json format output in aggregate functional tests. Remove resource create/delete from setupClass() and teardownClass() methods Change-Id: I6494ca63bfe8a51de0f65570fddcaf38f6c42dbb --- .../functional/compute/v2/test_aggregate.py | 209 ++++++++++++------ 1 file changed, 142 insertions(+), 67 deletions(-) diff --git a/openstackclient/tests/functional/compute/v2/test_aggregate.py b/openstackclient/tests/functional/compute/v2/test_aggregate.py index 383681034a..95068fc231 100644 --- a/openstackclient/tests/functional/compute/v2/test_aggregate.py +++ b/openstackclient/tests/functional/compute/v2/test_aggregate.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import json import uuid from openstackclient.tests.functional import base @@ -18,90 +19,164 @@ class AggregateTests(base.TestCase): """Functional tests for aggregate.""" - NAME = uuid.uuid4().hex - HEADERS = ['Name'] - FIELDS = ['name'] + def test_aggregate_create_and_delete(self): + """Test create, delete multiple""" + name1 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'aggregate create -f json ' + + '--zone nova ' + + name1)) + self.assertEqual( + name1, + cmd_output['name'] + ) + self.assertEqual( + 'nova', + cmd_output['availability_zone'] + ) - @classmethod - def setUpClass(cls): - opts = cls.get_opts(cls.FIELDS) - # Use the default 'nova' availability zone for the aggregate. - raw_output = cls.openstack( - 'aggregate create --zone nova ' + cls.NAME + opts + name2 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'aggregate create -f json ' + + '--zone nova ' + + name2)) + self.assertEqual( + name2, + cmd_output['name'] + ) + self.assertEqual( + 'nova', + cmd_output['availability_zone'] ) - expected = cls.NAME + '\n' - cls.assertOutput(expected, raw_output) - @classmethod - def tearDownClass(cls): - raw_output = cls.openstack('aggregate delete ' + cls.NAME) - cls.assertOutput('', raw_output) + del_output = self.openstack( + 'aggregate delete ' + name1 + ' ' + name2) + self.assertOutput('', del_output) def test_aggregate_list(self): - opts = self.get_opts(self.HEADERS) - raw_output = self.openstack('aggregate list' + opts) - self.assertIn(self.NAME, raw_output) - - def test_aggregate_show(self): - opts = self.get_opts(self.FIELDS) - raw_output = self.openstack('aggregate show ' + self.NAME + opts) - self.assertEqual(self.NAME + "\n", raw_output) + """Test aggregate list""" + name1 = uuid.uuid4().hex + self.openstack( + 'aggregate create ' + + '--zone nova ' + + '--property a=b ' + + name1) + self.addCleanup(self.openstack, 'aggregate delete ' + name1) - def test_aggregate_properties(self): - opts = self.get_opts(['name', 'properties']) + name2 = uuid.uuid4().hex + self.openstack( + 'aggregate create ' + + '--zone internal ' + + '--property c=d ' + + name2) + self.addCleanup(self.openstack, 'aggregate delete ' + name2) + + cmd_output = json.loads(self.openstack( + 'aggregate list -f json')) + names = [x['Name'] for x in cmd_output] + self.assertIn(name1, names) + self.assertIn(name2, names) + zones = [x['Availability Zone'] for x in cmd_output] + self.assertIn('nova', zones) + self.assertIn('internal', zones) + + # Test aggregate list --long + cmd_output = json.loads(self.openstack( + 'aggregate list --long -f json')) + names = [x['Name'] for x in cmd_output] + self.assertIn(name1, names) + self.assertIn(name2, names) + zones = [x['Availability Zone'] for x in cmd_output] + self.assertIn('nova', zones) + self.assertIn('internal', zones) + properties = [x['Properties'] for x in cmd_output] + self.assertIn({'a': 'b'}, properties) + self.assertIn({'c': 'd'}, properties) + + def test_aggregate_set_and_unset(self): + """Test aggregate set, show and unset""" + name1 = uuid.uuid4().hex + name2 = uuid.uuid4().hex + self.openstack( + 'aggregate create ' + + '--zone nova ' + + '--property a=b ' + + name1) + self.addCleanup(self.openstack, 'aggregate delete ' + name2) raw_output = self.openstack( - 'aggregate set --property a=b --property c=d ' + self.NAME + 'aggregate set --name ' + + name2 + + ' --zone internal ' + + '--no-property ' + + '--property c=d ' + + name1 ) - self.assertEqual('', raw_output) - - raw_output = self.openstack('aggregate show ' + self.NAME + opts) - self.assertIn(self.NAME + "\na='b', c='d'\n", raw_output) + self.assertOutput('', raw_output) - raw_output = self.openstack( - 'aggregate unset --property a ' + self.NAME + cmd_output = json.loads(self.openstack( + 'aggregate show -f json ' + name2)) + self.assertEqual( + name2, + cmd_output['name'] ) - self.assertEqual('', raw_output) - - raw_output = self.openstack('aggregate show ' + self.NAME + opts) - self.assertIn(self.NAME + "\nc='d'\n", raw_output) - - raw_output = self.openstack( - 'aggregate set --property a=b --property c=d ' + self.NAME + self.assertEqual( + 'internal', + cmd_output['availability_zone'] ) - self.assertEqual('', raw_output) - - raw_output = self.openstack( - 'aggregate set --no-property ' + self.NAME + self.assertIn( + "c='d'", + cmd_output['properties'] + ) + self.assertNotIn( + "a='b'", + cmd_output['properties'] ) - self.assertEqual('', raw_output) - - raw_output = self.openstack('aggregate show ' + self.NAME + opts) - self.assertNotIn("a='b', c='d'", raw_output) - - def test_aggregate_set(self): - opts = self.get_opts(["name", "availability_zone"]) + # Test unset raw_output = self.openstack( - 'aggregate set --zone Zone_1 ' + self.NAME) - self.assertEqual("", raw_output) + 'aggregate unset --property c ' + + name2 + ) + self.assertOutput('', raw_output) - raw_output = self.openstack('aggregate show ' + self.NAME + opts) - self.assertEqual("Zone_1\n" + self.NAME + "\n", raw_output) + cmd_output = json.loads(self.openstack( + 'aggregate show -f json ' + name2)) + self.assertNotIn( + "c='d'", + cmd_output['properties'] + ) def test_aggregate_add_and_remove_host(self): - opts = self.get_opts(["hosts", "name"]) - - raw_output = self.openstack('host list -f value -c "Host Name"') - host_name = raw_output.split()[0] - + """Test aggregate add and remove host""" + name = uuid.uuid4().hex self.openstack( - 'aggregate add host ' + self.NAME + ' ' + host_name) - raw_output = self.openstack('aggregate show ' + self.NAME + opts) - self.assertEqual("[u'" + host_name + "']" + "\n" + self.NAME + "\n", - raw_output) + 'aggregate create ' + name) + self.addCleanup(self.openstack, 'aggregate delete ' + name) + + # Get a host + cmd_output = json.loads(self.openstack( + 'host list -f json')) + host_name = cmd_output[0]['Host Name'] + + # Test add host + cmd_output = json.loads(self.openstack( + 'aggregate add host -f json ' + + name + ' ' + + host_name + )) + self.assertIn( + host_name, + cmd_output['hosts'] + ) - self.openstack( - 'aggregate remove host ' + self.NAME + ' ' + host_name) - raw_output = self.openstack('aggregate show ' + self.NAME + opts) - self.assertEqual("[]\n" + self.NAME + "\n", raw_output) + # Test remove host + cmd_output = json.loads(self.openstack( + 'aggregate remove host -f json ' + + name + ' ' + + host_name + )) + self.assertNotIn( + host_name, + cmd_output['hosts'] + ) From d7bf1592c58b688476632ce44e20614b3333a067 Mon Sep 17 00:00:00 2001 From: "zhiyong.dai" Date: Sat, 26 Nov 2016 19:29:30 +0800 Subject: [PATCH 1378/3095] Functional test for configuration Using json format output in configuration show functional test. Change-Id: I005b361ae70ced3f6cef77291db1d39dafb0793c --- .../functional/common/test_configuration.py | 43 ++++++++++++++----- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/openstackclient/tests/functional/common/test_configuration.py b/openstackclient/tests/functional/common/test_configuration.py index f47d3b0078..63a17d0e77 100644 --- a/openstackclient/tests/functional/common/test_configuration.py +++ b/openstackclient/tests/functional/common/test_configuration.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import json import os from openstackclient.common import configuration @@ -20,25 +21,47 @@ class ConfigurationTests(base.TestCase): - - opts = "-f value -c auth.password" + """Functional test for configuration.""" def test_configuration_show(self): + + # Test show without option raw_output = self.openstack('configuration show') items = self.parse_listing(raw_output) self.assert_table_structure(items, BASIC_CONFIG_HEADERS) - def test_configuration_show_unmask(self): - raw_output = self.openstack('configuration show --unmask ' + self.opts) + cmd_output = json.loads(self.openstack( + 'configuration show -f json' + )) + self.assertEqual( + configuration.REDACTED, + cmd_output['auth.password'] + ) + + # Test show --mask + cmd_output = json.loads(self.openstack( + 'configuration show --mask -f json' + )) + self.assertEqual( + configuration.REDACTED, + cmd_output['auth.password'] + ) + + # Test show --unmask + cmd_output = json.loads(self.openstack( + 'configuration show --unmask -f json' + )) # If we are using os-client-config, this will not be set. Rather than # parse clouds.yaml to get the right value, just make sure # we are not getting redacted. passwd = os.environ.get('OS_PASSWORD') if passwd: - self.assertEqual(passwd + '\n', raw_output) + self.assertEqual( + passwd, + cmd_output['auth.password'] + ) else: - self.assertNotEqual(configuration.REDACTED + '\n', raw_output) - - def test_configuration_show_mask(self): - raw_output = self.openstack('configuration show --mask ' + self.opts) - self.assertEqual(configuration.REDACTED + '\n', raw_output) + self.assertNotEqual( + configuration.REDACTED, + cmd_output['auth.password'] + ) From abfcd7810cb5937060fd9ae290f07a48226c9ced Mon Sep 17 00:00:00 2001 From: Reedip Date: Wed, 16 Nov 2016 14:22:44 +0530 Subject: [PATCH 1379/3095] Introduce overwrite functionality in ``osc subnet set`` The overwrite functionality allows user to overwrite the dns-nameservers of a specific subnet. Change-Id: I421808a3bdeb4565668f627b7929c4762cf40212 partially-implements: blueprint allow-overwrite-set-options partially-implements: blueprint network-commands-options --- doc/source/command-objects/subnet.rst | 7 +++++++ openstackclient/network/v2/subnet.py | 14 +++++++++++++- .../tests/unit/network/v2/test_subnet.py | 8 +++++++- ...meserver-overwrite-option-b866baeae12f9460.yaml | 7 +++++++ 4 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/add-dns-nameserver-overwrite-option-b866baeae12f9460.yaml diff --git a/doc/source/command-objects/subnet.rst b/doc/source/command-objects/subnet.rst index e2059875ba..a88b810330 100644 --- a/doc/source/command-objects/subnet.rst +++ b/doc/source/command-objects/subnet.rst @@ -231,6 +231,7 @@ Set subnet properties [--no-allocation-pool] [--dhcp | --no-dhcp] [--dns-nameserver ] + [--no-dns-nameserver] [--gateway ] [--host-route destination=,gateway=] [--no-host-route] @@ -263,6 +264,12 @@ Set subnet properties DNS server for this subnet (repeat option to set multiple DNS servers) +.. option:: --no-dns-nameservers + + Clear existing information of DNS servers. + Specify both --dns-nameserver and --no-dns-nameservers + to overwrite the current DNS server information. + .. option:: --gateway Specify a gateway for the subnet. The options are: diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index f1ecb5a727..dc504b32e7 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=,gateway=', @@ -532,7 +541,10 @@ def take_action(self, parsed_args): 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/tests/unit/network/v2/test_subnet.py b/openstackclient/tests/unit/network/v2/test_subnet.py index 2d51aa4ad9..e74065758a 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet.py +++ b/openstackclient/tests/unit/network/v2/test_subnet.py @@ -925,13 +925,16 @@ def test_overwrite_options(self): {'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 @@ def test_overwrite_options(self): "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 @@ def test_overwrite_options(self): '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/releasenotes/notes/add-dns-nameserver-overwrite-option-b866baeae12f9460.yaml b/releasenotes/notes/add-dns-nameserver-overwrite-option-b866baeae12f9460.yaml new file mode 100644 index 0000000000..04f0638d69 --- /dev/null +++ b/releasenotes/notes/add-dns-nameserver-overwrite-option-b866baeae12f9460.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + ``subnet set`` command now allows the user to overwrite/clear dns-nameserver information + of a subnet by using the option ``no-dns-nameserver``. + [ Blueprint `allow-overwrite-set-options ` _] + From 4132392c2fd8337d18296c33f07c4a89b8a58bda Mon Sep 17 00:00:00 2001 From: Rodolfo Alonso Hernandez Date: Mon, 26 Sep 2016 11:12:07 +0100 Subject: [PATCH 1380/3095] Add QoS support to Network object. Added "qos_policy" parameter to Network class. Change-Id: Idc00f2792eef5b1f0910084d95cf9a8e83fe818c Closes-Bug: 1627069 --- doc/source/command-objects/network.rst | 16 +++++++++++++ openstackclient/network/v2/network.py | 23 +++++++++++++++++++ .../tests/unit/network/v2/fakes.py | 1 + .../tests/unit/network/v2/test_network.py | 21 +++++++++++++++++ ...twork-add-qos-policy-a25e868e67142f90.yaml | 8 +++++++ 5 files changed, 69 insertions(+) create mode 100644 releasenotes/notes/network-add-qos-policy-a25e868e67142f90.yaml diff --git a/doc/source/command-objects/network.rst b/doc/source/command-objects/network.rst index 4b72d5e068..06a5247a87 100644 --- a/doc/source/command-objects/network.rst +++ b/doc/source/command-objects/network.rst @@ -30,6 +30,7 @@ Create new network [--provider-network-type ] [--provider-physical-network ] [--provider-segment ] + [--qos-policy ] [--transparent-vlan | --no-transparent-vlan] @@ -144,6 +145,12 @@ Create new network *Network version 2 only* +.. option:: --qos-policy + + QoS policy to attach to this network (name or ID) + + *Network version 2 only* + .. option:: --transparent-vlan Make the network VLAN transparent @@ -303,6 +310,7 @@ Set network properties [--provider-network-type ] [--provider-physical-network ] [--provider-segment ] + [--qos-policy | --no-qos-policy] [--transparent-vlan | --no-transparent-vlan] @@ -370,6 +378,14 @@ Set network properties VLAN ID for VLAN networks or Tunnel ID for GRE/VXLAN networks +.. option:: --qos-policy + + QoS policy to attach to this network (name or ID) + +.. option:: --no-qos-policy + + Remove the QoS policy attached to this network + .. option:: --transparent-vlan Make the network VLAN transparent diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 37775e6ced..1c06c462e0 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 @@ def update_parser_network(self, parser): help=_("Do not use the network as the default external network " "(default)") ) + parser.add_argument( + '--qos-policy', + metavar='', + help=_("QoS policy to attach to this network (name or ID)") + ) _add_additional_network_options(parser) return parser @@ -572,6 +584,17 @@ def get_parser(self, prog_name): 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='', + 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/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index e96abc0977..11e3873d30 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -299,6 +299,7 @@ def create_one_network(attrs=None): 'availability_zone_hints': [], 'is_default': False, 'port_security_enabled': True, + 'qos_policy_id': 'qos-policy-id-' + uuid.uuid4().hex, } # Overwrite default attributes. diff --git a/openstackclient/tests/unit/network/v2/test_network.py b/openstackclient/tests/unit/network/v2/test_network.py index c0de864049..96b1b1021d 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 @@ def setUp(self): 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 @@ def test_create_all_options(self): "--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 @@ def test_create_all_options(self): ('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 @@ def test_create_all_options(self): '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 @@ def setUp(self): 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 @@ def test_set_this(self): '--provider-segment', '400', '--no-transparent-vlan', '--enable-port-security', + '--qos-policy', self.qos_policy.name, ] verifylist = [ ('network', self._network.name), @@ -784,6 +798,7 @@ def test_set_this(self): ('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 @@ def test_set_this(self): '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 @@ def test_set_that(self): '--no-share', '--internal', '--disable-port-security', + '--no-qos-policy', ] verifylist = [ ('network', self._network.name), @@ -820,6 +837,7 @@ def test_set_that(self): ('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 @@ def test_set_that(self): '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/releasenotes/notes/network-add-qos-policy-a25e868e67142f90.yaml b/releasenotes/notes/network-add-qos-policy-a25e868e67142f90.yaml new file mode 100644 index 0000000000..2d2836d72a --- /dev/null +++ b/releasenotes/notes/network-add-qos-policy-a25e868e67142f90.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Add QoS support for Network commands. + The new parameter ``qos-policy`` is added to ``network create`` and + ``network set`` commands. This parameter is the name or the ID of the + network QoS policy to attach to this network. + [Bug `1627069 `_] From c286722965ce7f5ea9acc201aa9cf289cfe16105 Mon Sep 17 00:00:00 2001 From: "zhiyong.dai" Date: Sun, 27 Nov 2016 20:32:36 +0800 Subject: [PATCH 1381/3095] Refactor availability zone functional test Using json format output in availability zone list functional test Change-Id: I7098b1c3bee680e47e414dcb4fa272628cdec1eb --- .../common/test_availability_zone.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/openstackclient/tests/functional/common/test_availability_zone.py b/openstackclient/tests/functional/common/test_availability_zone.py index f73e1ed956..025da95c24 100644 --- a/openstackclient/tests/functional/common/test_availability_zone.py +++ b/openstackclient/tests/functional/common/test_availability_zone.py @@ -10,16 +10,23 @@ # License for the specific language governing permissions and limitations # under the License. +import json + from openstackclient.tests.functional import base class AvailabilityZoneTests(base.TestCase): """Functional tests for availability zone. """ - HEADERS = ["'Zone Name'"] - # So far, all components have the same default availability zone name. - DEFAULT_AZ_NAME = 'nova' def test_availability_zone_list(self): - opts = self.get_opts(self.HEADERS) - raw_output = self.openstack('availability zone list' + opts) - self.assertIn(self.DEFAULT_AZ_NAME, raw_output) + cmd_output = json.loads(self.openstack( + 'availability zone list -f json')) + zones = [x['Zone Name'] for x in cmd_output] + self.assertIn( + 'internal', + zones + ) + self.assertIn( + 'nova', + zones + ) From fca81526ffd6b7ad74d6f414545c531b3e9f75ad Mon Sep 17 00:00:00 2001 From: "zhiyong.dai" Date: Sun, 27 Nov 2016 18:48:03 +0800 Subject: [PATCH 1382/3095] Trivial:modify one parameter This patch modifies a wrong parameter to make the metavar consistent with the optional argument. Change-Id: I27ed30fdbc3adbc19d2f5662d6952cfe15dc52b1 --- openstackclient/volume/v2/volume_type.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index 0fcfaf7e8d..d0df677dcf 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -213,7 +213,7 @@ def get_parser(self, prog_name): ) parser.add_argument( '--description', - metavar='', + metavar='', help=_('Set volume type description'), ) parser.add_argument( From 747ec5b8979418f5e5592c8c401a6cc1052e8236 Mon Sep 17 00:00:00 2001 From: "jiahui.qiang" Date: Mon, 28 Nov 2016 07:34:56 +0800 Subject: [PATCH 1383/3095] Add unit tests for server create in computev2 Unit tests for server create is not completed. Some of the code in take_action has not been tested. Change-Id: Ifd3f42400408d3437e0bc6cd42acbb6db861b1f3 --- .../tests/unit/compute/v2/test_server.py | 151 ++++++++++++++++++ 1 file changed, 151 insertions(+) diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 1081b9a3c5..5722118989 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -328,6 +328,61 @@ def test_server_create_minimal(self): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist(), data) + def test_server_create_with_options(self): + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--key-name', 'keyname', + '--property', 'Beta=b', + '--security-group', 'securitygroup', + '--hint', 'a=b', + '--hint', 'a=c', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('key_name', 'keyname'), + ('property', {'Beta': 'b'}), + ('security_group', ['securitygroup']), + ('hint', ['a=b', 'a=c']), + ('config_drive', False), + ('server_name', self.new_server.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) + + # Set expected values + kwargs = dict( + meta={'Beta': 'b'}, + files={}, + reservation_id=None, + min_count=1, + max_count=1, + security_groups=['securitygroup'], + userdata=None, + key_name='keyname', + availability_zone=None, + block_device_mapping={}, + nics=[], + scheduler_hints={'a': ['b', 'c']}, + config_drive=None, + ) + # ServerManager.create(name, image, flavor, **kwargs) + self.servers_mock.create.assert_called_with( + self.new_server.name, + self.image, + self.flavor, + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) + def test_server_create_with_network(self): arglist = [ '--image', 'image1', @@ -415,6 +470,102 @@ def test_server_create_with_network(self): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist(), data) + @mock.patch.object(common_utils, 'wait_for_status', return_value=True) + def test_server_create_with_wait_ok(self, mock_wait_for_status): + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--wait', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('config_drive', False), + ('wait', True), + ('server_name', self.new_server.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + mock_wait_for_status.assert_called_once_with( + self.servers_mock.get, + self.new_server.id, + callback=server._show_progress, + ) + + kwargs = dict( + meta=None, + files={}, + reservation_id=None, + min_count=1, + max_count=1, + security_groups=[], + userdata=None, + key_name=None, + availability_zone=None, + block_device_mapping={}, + nics=[], + scheduler_hints={}, + config_drive=None, + ) + self.servers_mock.create.assert_called_with( + self.new_server.name, + self.image, + self.flavor, + **kwargs + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) + + @mock.patch.object(common_utils, 'wait_for_status', return_value=False) + def test_server_create_with_wait_fails(self, mock_wait_for_status): + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--wait', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('config_drive', False), + ('wait', True), + ('server_name', self.new_server.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises(SystemExit, self.cmd.take_action, parsed_args) + + mock_wait_for_status.assert_called_once_with( + self.servers_mock.get, + self.new_server.id, + callback=server._show_progress, + ) + + kwargs = dict( + meta=None, + files={}, + reservation_id=None, + min_count=1, + max_count=1, + security_groups=[], + userdata=None, + key_name=None, + availability_zone=None, + block_device_mapping={}, + nics=[], + scheduler_hints={}, + config_drive=None, + ) + self.servers_mock.create.assert_called_with( + self.new_server.name, + self.image, + self.flavor, + **kwargs + ) + @mock.patch('openstackclient.compute.v2.server.io.open') def test_server_create_userdata(self, mock_open): mock_file = mock.Mock(name='File') From 246f60ab13b57df0cdff2190ebaca316a946f3e7 Mon Sep 17 00:00:00 2001 From: Sindhu Devale Date: Wed, 9 Nov 2016 16:30:55 -0600 Subject: [PATCH 1384/3095] Add `--enable/disable-port-security` option to `port set` and `port create` This patch adds the currently missing options `--enable-port-security` and `--disable-port-security` in the `os port set` and `os port create` commands. Partially-Implements: blueprint network-commands-options Change-Id: I4dc11cdf32bf482a5937f5464fe8a3b418644ec3 --- doc/source/command-objects/port.rst | 18 ++++ openstackclient/network/v2/port.py | 28 +++++++ .../tests/unit/network/v2/test_port.py | 84 +++++++++++++++++++ ...-enabled-to-port-set-82b801d21d45e715.yaml | 6 ++ 4 files changed, 136 insertions(+) create mode 100644 releasenotes/notes/add-port-security-enabled-to-port-set-82b801d21d45e715.yaml diff --git a/doc/source/command-objects/port.rst b/doc/source/command-objects/port.rst index fe256d0940..3fa24b2976 100644 --- a/doc/source/command-objects/port.rst +++ b/doc/source/command-objects/port.rst @@ -28,6 +28,7 @@ Create new port [--mac-address ] [--security-group | --no-security-group] [--project [--project-domain ]] + [--enable-port-security | --disable-port-security] .. option:: --network @@ -94,6 +95,14 @@ Create new port Domain the project belongs to (name or ID). This can be used in case collisions between project names exist. +.. option:: --enable-port-security + + Enable port security for this port (Default) + +.. option:: --disable-port-security + + Disable port security for this port + .. _port_create-name: .. describe:: @@ -171,6 +180,7 @@ Set port properties [--name ] [--security-group ] [--no-security-group] + [--enable-port-security | --disable-port-security] .. option:: --fixed-ip subnet=,ip-address= @@ -236,6 +246,14 @@ Set port properties Clear existing security groups associated with this port +.. option:: --enable-port-security + + Enable port security for this port + +.. option:: --disable-port-security + + Disable port security for this port + .. _port_set-port: .. describe:: diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 86174d535a..784adf19cb 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -146,6 +146,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 @@ -297,6 +303,17 @@ def get_parser(self, prog_name): 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 @@ -512,6 +529,17 @@ def get_parser(self, prog_name): 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/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index 4ff278a994..955df4dcf9 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -315,6 +315,54 @@ def test_create_with_no_secuirty_groups(self): 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): @@ -868,6 +916,42 @@ def test_overwrite_security_group(self): 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/releasenotes/notes/add-port-security-enabled-to-port-set-82b801d21d45e715.yaml b/releasenotes/notes/add-port-security-enabled-to-port-set-82b801d21d45e715.yaml new file mode 100644 index 0000000000..5bc3952139 --- /dev/null +++ b/releasenotes/notes/add-port-security-enabled-to-port-set-82b801d21d45e715.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Added ``--enable-port-security`` and ``--disable-port-security`` + options to ``port set`` and ``port create`` commands. + [Blueprint :oscbp:`network-commands-options`] From ac7d27adc6ee9b035a57c21a0ed84596ae65a449 Mon Sep 17 00:00:00 2001 From: Ankur Gupta Date: Mon, 3 Oct 2016 18:39:00 -0500 Subject: [PATCH 1385/3095] Add network service provider list to OSC Add network service providers commands to OSC Change-Id: Iea8bbe460061d67e36a4346b349e07612112c732 Depends-On: Ie0be92e0717b8b13e31068276a12b5fbf784e374 --- .../network-service-provider.rst | 18 +++++ doc/source/commands.rst | 1 + .../network/v2/network_service_provider.py | 41 +++++++++++ .../v2/test_network_service_provider.py | 26 +++++++ .../tests/unit/network/v2/fakes.py | 35 +++++++++ .../v2/test_network_service_provider.py | 71 +++++++++++++++++++ ...ork-service-provider-c161a4a328a8a408.yaml | 4 ++ setup.cfg | 2 + 8 files changed, 198 insertions(+) create mode 100644 doc/source/command-objects/network-service-provider.rst create mode 100644 openstackclient/network/v2/network_service_provider.py create mode 100644 openstackclient/tests/functional/network/v2/test_network_service_provider.py create mode 100644 openstackclient/tests/unit/network/v2/test_network_service_provider.py create mode 100644 releasenotes/notes/add-network-service-provider-c161a4a328a8a408.yaml diff --git a/doc/source/command-objects/network-service-provider.rst b/doc/source/command-objects/network-service-provider.rst new file mode 100644 index 0000000000..344bb88019 --- /dev/null +++ b/doc/source/command-objects/network-service-provider.rst @@ -0,0 +1,18 @@ +======================== +network service provider +======================== + +A **network service provider** is a particular driver that implements a +networking service + +Network v2 + +network service provider list +----------------------------- + +List service providers + +.. program:: network service provider list +.. code:: bash + + os network service provider list diff --git a/doc/source/commands.rst b/doc/source/commands.rst index bf2c322a21..fbda059787 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -113,6 +113,7 @@ referring to both Compute and Volume quotas. * ``network agent``: (**Network**) - A network agent is an agent that handles various tasks used to implement virtual networks * ``network rbac``: (**Network**) - an RBAC policy for network resources * ``network segment``: (**Network**) - a segment of a virtual network +* ``network service provider``: (**Network**) - a driver providing a network service * ``object``: (**Object Storage**) a single file in the Object Storage * ``object store account``: (**Object Storage**) owns a group of Object Storage resources * ``policy``: (**Identity**) determines authorization diff --git a/openstackclient/network/v2/network_service_provider.py b/openstackclient/network/v2/network_service_provider.py new file mode 100644 index 0000000000..3aa33c23bd --- /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/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 0000000000..379de4305a --- /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/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index cea0028218..e296ba7266 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -1107,3 +1107,38 @@ def get_subnet_pools(subnet_pools=None, count=2): 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_service_provider.py b/openstackclient/tests/unit/network/v2/test_network_service_provider.py new file mode 100644 index 0000000000..5ba85ddb9a --- /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/releasenotes/notes/add-network-service-provider-c161a4a328a8a408.yaml b/releasenotes/notes/add-network-service-provider-c161a4a328a8a408.yaml new file mode 100644 index 0000000000..0841dd0854 --- /dev/null +++ b/releasenotes/notes/add-network-service-provider-c161a4a328a8a408.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Add support for the ``network service provider`` command. diff --git a/setup.cfg b/setup.cfg index 2aa8874019..973504d6bd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -378,6 +378,8 @@ openstack.network.v2 = network_segment_set = openstackclient.network.v2.network_segment:SetNetworkSegment network_segment_show = openstackclient.network.v2.network_segment:ShowNetworkSegment + network_service_provider_list = openstackclient.network.v2.network_service_provider:ListNetworkServiceProvider + port_create = openstackclient.network.v2.port:CreatePort port_delete = openstackclient.network.v2.port:DeletePort port_list = openstackclient.network.v2.port:ListPort From 9f297853ce768c243d9c411b42786954e991cd91 Mon Sep 17 00:00:00 2001 From: "zhiyong.dai" Date: Mon, 28 Nov 2016 01:53:22 +0800 Subject: [PATCH 1386/3095] Add functional test for volume service Add functional test for volume service, v1 and v2 Change-Id: If226c82ef8df339e4ae63d8241e0bd15b69264d2 --- .../functional/volume/v1/test_service.py | 93 +++++++++++++++++++ .../functional/volume/v2/test_service.py | 93 +++++++++++++++++++ 2 files changed, 186 insertions(+) create mode 100644 openstackclient/tests/functional/volume/v1/test_service.py create mode 100644 openstackclient/tests/functional/volume/v2/test_service.py diff --git a/openstackclient/tests/functional/volume/v1/test_service.py b/openstackclient/tests/functional/volume/v1/test_service.py new file mode 100644 index 0000000000..c921c46aaa --- /dev/null +++ b/openstackclient/tests/functional/volume/v1/test_service.py @@ -0,0 +1,93 @@ +# 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 json + +from openstackclient.tests.functional.volume.v1 import common + + +class VolumeServiceTests(common.BaseVolumeTests): + """Functional tests for volume service.""" + + def test_volume_service_list(self): + cmd_output = json.loads(self.openstack( + 'volume service list -f json')) + + # Get the nonredundant services and hosts + services = list(set([x['Binary'] for x in cmd_output])) + + # Test volume service list --service + cmd_output = json.loads(self.openstack( + 'volume service list -f json ' + + '--service ' + + services[0] + )) + for x in cmd_output: + self.assertEqual( + services[0], + x['Binary'] + ) + + # TODO(zhiyong.dai): test volume service list --host after solving + # https://bugs.launchpad.net/python-openstackclient/+bug/1664451 + + def test_volume_service_set(self): + + # Get a service and host + cmd_output = json.loads(self.openstack( + 'volume service list -f json' + )) + service_1 = cmd_output[0]['Binary'] + host_1 = cmd_output[0]['Host'] + + # Test volume service set --enable + raw_output = self.openstack( + 'volume service set --enable ' + + host_1 + ' ' + + service_1 + ) + self.assertOutput('', raw_output) + + cmd_output = json.loads(self.openstack( + 'volume service list -f json --long' + )) + self.assertEqual( + 'enabled', + cmd_output[0]['Status'] + ) + self.assertEqual( + None, + cmd_output[0]['Disabled Reason'] + ) + + # Test volume service set --disable and --disable-reason + disable_reason = 'disable_reason' + raw_output = self.openstack( + 'volume service set --disable ' + + '--disable-reason ' + + disable_reason + ' ' + + host_1 + ' ' + + service_1 + ) + self.assertOutput('', raw_output) + + cmd_output = json.loads(self.openstack( + 'volume service list -f json --long' + )) + self.assertEqual( + 'disabled', + cmd_output[0]['Status'] + ) + self.assertEqual( + disable_reason, + cmd_output[0]['Disabled Reason'] + ) diff --git a/openstackclient/tests/functional/volume/v2/test_service.py b/openstackclient/tests/functional/volume/v2/test_service.py new file mode 100644 index 0000000000..8d1944e404 --- /dev/null +++ b/openstackclient/tests/functional/volume/v2/test_service.py @@ -0,0 +1,93 @@ +# 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 json + +from openstackclient.tests.functional.volume.v2 import common + + +class VolumeServiceTests(common.BaseVolumeTests): + """Functional tests for volume service.""" + + def test_volume_service_list(self): + cmd_output = json.loads(self.openstack( + 'volume service list -f json')) + + # Get the nonredundant services and hosts + services = list(set([x['Binary'] for x in cmd_output])) + + # Test volume service list --service + cmd_output = json.loads(self.openstack( + 'volume service list -f json ' + + '--service ' + + services[0] + )) + for x in cmd_output: + self.assertEqual( + services[0], + x['Binary'] + ) + + # TODO(zhiyong.dai): test volume service list --host after solving + # https://bugs.launchpad.net/python-openstackclient/+bug/1664451 + + def test_volume_service_set(self): + + # Get a service and host + cmd_output = json.loads(self.openstack( + 'volume service list -f json' + )) + service_1 = cmd_output[0]['Binary'] + host_1 = cmd_output[0]['Host'] + + # Test volume service set --enable + raw_output = self.openstack( + 'volume service set --enable ' + + host_1 + ' ' + + service_1 + ) + self.assertOutput('', raw_output) + + cmd_output = json.loads(self.openstack( + 'volume service list -f json --long' + )) + self.assertEqual( + 'enabled', + cmd_output[0]['Status'] + ) + self.assertEqual( + None, + cmd_output[0]['Disabled Reason'] + ) + + # Test volume service set --disable and --disable-reason + disable_reason = 'disable_reason' + raw_output = self.openstack( + 'volume service set --disable ' + + '--disable-reason ' + + disable_reason + ' ' + + host_1 + ' ' + + service_1 + ) + self.assertOutput('', raw_output) + + cmd_output = json.loads(self.openstack( + 'volume service list -f json --long' + )) + self.assertEqual( + 'disabled', + cmd_output[0]['Status'] + ) + self.assertEqual( + disable_reason, + cmd_output[0]['Disabled Reason'] + ) From ce079d22617f3129bfdf5165b9f124d7cc7dbc2e Mon Sep 17 00:00:00 2001 From: Nam Nguyen Hoai Date: Tue, 16 Aug 2016 16:00:32 +0700 Subject: [PATCH 1387/3095] Add '--project' and '--project-domain' options to os cmds This patch added '--project' and '--project-domain' options to filter subnets resulted by 'os subnet list', 'os floating ip create' and 'os security group list' commands. Co-Authored-By: Ha Van Tu Change-Id: I727663d49ffa6aa042fdeb60797f18bb753b0372 Closes-Bug: #1613231 Closes-Bug: #1610909 Closes-Bug: #1613629 Partially-Implements: blueprint network-commands-options --- doc/source/command-objects/floating-ip.rst | 14 ++++++ doc/source/command-objects/router.rst | 10 ++++ doc/source/command-objects/security-group.rst | 14 ++++++ openstackclient/network/v2/floating_ip.py | 16 ++++++ openstackclient/network/v2/router.py | 15 +++++- openstackclient/network/v2/security_group.py | 18 ++++++- .../tests/unit/network/v2/test_floating_ip.py | 50 +++++++++++++++++++ .../tests/unit/network/v2/test_router.py | 41 +++++++++++++++ .../unit/network/v2/test_security_group.py | 38 ++++++++++++++ .../notes/bug-1613231-386b2b1373662052.yaml | 8 +++ 10 files changed, 222 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/bug-1613231-386b2b1373662052.yaml diff --git a/doc/source/command-objects/floating-ip.rst b/doc/source/command-objects/floating-ip.rst index b2cc8af0ef..6a5e38b063 100644 --- a/doc/source/command-objects/floating-ip.rst +++ b/doc/source/command-objects/floating-ip.rst @@ -18,6 +18,7 @@ Create floating IP [--floating-ip-address ] [--fixed-ip-address ] [--description ] + [--project [--project-domain ]] .. option:: --subnet @@ -45,6 +46,19 @@ Create floating IP Set floating IP description *Network version 2 only* +.. option:: --project + + Owner's project (name or ID) + + *Network version 2 only* + +.. option:: --project-domain + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + + *Network version 2 only* + .. describe:: Network to allocate floating IP from (name or ID) diff --git a/doc/source/command-objects/router.rst b/doc/source/command-objects/router.rst index 56b95ffa68..059d1a3fe0 100644 --- a/doc/source/command-objects/router.rst +++ b/doc/source/command-objects/router.rst @@ -137,6 +137,7 @@ List routers [--name ] [--enable | --disable] [--long] + [--project [--project-domain ]] .. option:: --long @@ -154,6 +155,15 @@ List routers List disabled routers +.. option:: --project + + List routers according to their project (name or ID) + +.. option:: --project-domain + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + router remove port ------------------ diff --git a/doc/source/command-objects/security-group.rst b/doc/source/command-objects/security-group.rst index ba054554df..5157f53096 100644 --- a/doc/source/command-objects/security-group.rst +++ b/doc/source/command-objects/security-group.rst @@ -67,6 +67,7 @@ List security groups os security group list [--all-projects] + [--project [--project-domain ]] .. option:: --all-projects @@ -75,6 +76,19 @@ List security groups *Network version 2 ignores this option and will always display information* *for all projects (admin only).* +.. option:: --project + + List security groups according to the project (name or ID) + + *Network version 2 only* + +.. option:: --project-domain + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + + *Network version 2 only* + security group set ------------------ diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index c787cd2f9d..7b8374e2ae 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -18,6 +18,7 @@ from osc_lib import utils from openstackclient.i18n import _ +from openstackclient.identity import common as identity_common from openstackclient.network import common from openstackclient.network import sdk_utils @@ -66,6 +67,15 @@ def _get_attrs(client_manager, parsed_args): if parsed_args.description is not None: attrs['description'] = parsed_args.description + if parsed_args.project: + identity_client = client_manager.identity + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + attrs['tenant_id'] = project_id + return attrs @@ -113,6 +123,12 @@ def update_parser_network(self, parser): metavar='', help=_('Set floating IP description') ) + parser.add_argument( + '--project', + metavar='', + help=_("Owner's project (name or ID)") + ) + identity_common.add_project_domain_option_to_parser(parser) return parser def take_action_network(self, client, parsed_args): diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index cbd412b585..50af80bb47 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -282,11 +282,17 @@ def get_parser(self, prog_name): default=False, help=_("List additional fields in output") ) + parser.add_argument( + '--project', + metavar='', + help=_("List routers according to their project (name or ID)") + ) + identity_common.add_project_domain_option_to_parser(parser) return parser def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity client = self.app.client_manager.network - columns = ( 'id', 'name', @@ -316,6 +322,13 @@ def take_action(self, parsed_args): elif parsed_args.disable: args['admin_state_up'] = False + if parsed_args.project: + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + args['tenant_id'] = project_id if parsed_args.long: columns = columns + ( 'routes', diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py index 554dd61d6f..a02d73bbe5 100644 --- a/openstackclient/network/v2/security_group.py +++ b/openstackclient/network/v2/security_group.py @@ -201,6 +201,13 @@ def update_parser_network(self, parser): default=False, help=argparse.SUPPRESS, ) + parser.add_argument( + '--project', + metavar='', + help=_("List security groups according to the project " + "(name or ID)") + ) + identity_common.add_project_domain_option_to_parser(parser) return parser def update_parser_compute(self, parser): @@ -228,7 +235,16 @@ def _get_return_data(self, data, include_project=True): ) for s in data)) def take_action_network(self, client, parsed_args): - return self._get_return_data(client.security_groups()) + filters = {} + if parsed_args.project: + identity_client = self.app.client_manager.identity + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + filters['tenant_id'] = project_id + return self._get_return_data(client.security_groups(**filters)) def take_action_compute(self, client, parsed_args): search = {'all_tenants': parsed_args.all_projects} diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip.py b/openstackclient/tests/unit/network/v2/test_floating_ip.py index 10f3067d9a..b3d211ba34 100644 --- a/openstackclient/tests/unit/network/v2/test_floating_ip.py +++ b/openstackclient/tests/unit/network/v2/test_floating_ip.py @@ -18,6 +18,7 @@ from openstackclient.network.v2 import floating_ip from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3 from openstackclient.tests.unit.network.v2 import fakes as network_fakes from openstackclient.tests.unit import utils as tests_utils @@ -31,6 +32,7 @@ def setUp(self): # Get a shortcut to the network client self.network = self.app.client_manager.network + self.projects_mock = self.app.client_manager.identity.projects class TestCreateFloatingIPNetwork(TestFloatingIPNetwork): @@ -145,6 +147,54 @@ def test_create_all_options(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_floating_ip_create_project(self): + project = identity_fakes_v3.FakeProject.create_one_project() + self.projects_mock.get.return_value = project + arglist = [ + '--project', project.id, + self.floating_ip.floating_network_id, + ] + verifylist = [ + ('network', self.floating_ip.floating_network_id), + ('project', project.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_ip.assert_called_once_with(**{ + 'floating_network_id': self.floating_ip.floating_network_id, + 'tenant_id': project.id, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_floating_ip_create_project_domain(self): + project = identity_fakes_v3.FakeProject.create_one_project() + domain = identity_fakes_v3.FakeDomain.create_one_domain() + self.projects_mock.get.return_value = project + arglist = [ + "--project", project.name, + "--project-domain", domain.name, + self.floating_ip.floating_network_id, + ] + verifylist = [ + ('network', self.floating_ip.floating_network_id), + ('project', project.name), + ('project_domain', domain.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_ip.assert_called_once_with(**{ + 'floating_network_id': self.floating_ip.floating_network_id, + 'tenant_id': project.id, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + class TestDeleteFloatingIPNetwork(TestFloatingIPNetwork): diff --git a/openstackclient/tests/unit/network/v2/test_router.py b/openstackclient/tests/unit/network/v2/test_router.py index 24984e47b0..b0409447dd 100644 --- a/openstackclient/tests/unit/network/v2/test_router.py +++ b/openstackclient/tests/unit/network/v2/test_router.py @@ -18,6 +18,7 @@ from osc_lib import utils as osc_utils from openstackclient.network.v2 import router +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3 from openstackclient.tests.unit.network.v2 import fakes as network_fakes from openstackclient.tests.unit import utils as tests_utils @@ -29,6 +30,7 @@ def setUp(self): # Get a shortcut to the network client self.network = self.app.client_manager.network + self.projects_mock = self.app.client_manager.identity.projects class TestAddPortToRouter(TestRouter): @@ -476,6 +478,45 @@ def test_router_list_disable(self): self.network.routers.assert_called_once_with( **{'admin_state_up': False} ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_router_list_project(self): + project = identity_fakes_v3.FakeProject.create_one_project() + self.projects_mock.get.return_value = project + arglist = [ + '--project', project.id, + ] + verifylist = [ + ('project', project.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'tenant_id': project.id} + + self.network.routers.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_router_list_project_domain(self): + project = identity_fakes_v3.FakeProject.create_one_project() + self.projects_mock.get.return_value = project + arglist = [ + '--project', project.id, + '--project-domain', project.domain_id, + ] + verifylist = [ + ('project', project.id), + ('project_domain', project.domain_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'tenant_id': project.id} + + self.network.routers.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) diff --git a/openstackclient/tests/unit/network/v2/test_security_group.py b/openstackclient/tests/unit/network/v2/test_security_group.py index 2615b77a26..43aa07ccbe 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group.py +++ b/openstackclient/tests/unit/network/v2/test_security_group.py @@ -444,6 +444,44 @@ def test_security_group_list_all_projects(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + def test_security_group_list_project(self): + project = identity_fakes.FakeProject.create_one_project() + self.projects_mock.get.return_value = project + arglist = [ + '--project', project.id, + ] + verifylist = [ + ('project', project.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'tenant_id': project.id} + + self.network.security_groups.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_security_group_list_project_domain(self): + project = identity_fakes.FakeProject.create_one_project() + self.projects_mock.get.return_value = project + arglist = [ + '--project', project.id, + '--project-domain', project.domain_id, + ] + verifylist = [ + ('project', project.id), + ('project_domain', project.domain_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'tenant_id': project.id} + + self.network.security_groups.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + class TestListSecurityGroupCompute(TestSecurityGroupCompute): diff --git a/releasenotes/notes/bug-1613231-386b2b1373662052.yaml b/releasenotes/notes/bug-1613231-386b2b1373662052.yaml new file mode 100644 index 0000000000..a55af0dd4c --- /dev/null +++ b/releasenotes/notes/bug-1613231-386b2b1373662052.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Add ``--project`` and ``--project-domain`` options to the ``router list``, + ``floating ip create`` and ``security group list`` commands. + [Bug `1613231 `_] + [Bug `1613629 `_] + [Bug `1610909 `_] From 40d73a0b585fde9c065fb069d7a31c4717c8887d Mon Sep 17 00:00:00 2001 From: gengchc2 Date: Fri, 2 Dec 2016 09:56:27 +0800 Subject: [PATCH 1388/3095] Correct reraising of exception When an exception was caught and rethrown, it should call 'raise' without any arguments because it shows the place where an exception occured initially instead of place where the exception re-raised Change-Id: I3ec3680debbfad7c06f2251396e0e8e4e3df6c50 --- openstackclient/shell.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index be4b52834a..e08eee61df 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -148,10 +148,10 @@ def initialize_app(self, argv): '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 From 2cb0f85288f56e901ae2e67847bd1ee8fb39ff01 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Fri, 2 Dec 2016 05:13:51 +0000 Subject: [PATCH 1389/3095] Updated from global requirements Change-Id: I807f29306f262cf5c7deceb240e0a257b96af456 --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index d323c422e3..3eb018f5f4 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -9,7 +9,7 @@ mock>=2.0 # BSD oslosphinx>=4.7.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 reno>=1.8.0 # Apache-2.0 -requests>=2.10.0 # Apache-2.0 +requests!=2.12.2,>=2.10.0 # Apache-2.0 requests-mock>=1.1 # Apache-2.0 sphinx!=1.3b1,<1.4,>=1.2.1 # BSD stevedore>=1.17.1 # Apache-2.0 From d5b69c6269507e313bdb3589f2eb1a8716e29eab Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Fri, 2 Dec 2016 17:18:11 +0000 Subject: [PATCH 1390/3095] Updated from global requirements Change-Id: I8f9828e5e3a266a1ba52f9c09ddad4ed8d928e1b --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c31670bea7..2d8aadc603 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,6 +12,6 @@ osc-lib>=1.2.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 oslo.utils>=3.18.0 # Apache-2.0 python-glanceclient>=2.5.0 # Apache-2.0 -python-keystoneclient>=3.6.0 # Apache-2.0 +python-keystoneclient>=3.8.0 # Apache-2.0 python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0 python-cinderclient!=1.7.0,!=1.7.1,>=1.6.0 # Apache-2.0 From 094e5189b7bd4a84b124d17a7c70e4f9aaf7ebc7 Mon Sep 17 00:00:00 2001 From: "zhiyong.dai" Date: Thu, 24 Nov 2016 21:09:55 +0800 Subject: [PATCH 1391/3095] Add "consistency group delete" command Add "consistency group delete" command in volume v2 (v2 only). Change-Id: Ieebc2417df0d45a578d5617bad245d7863f09190 Implements: bp cinder-command-support Partial-Bug: #1613964 --- .../command-objects/consistency-group.rst | 21 ++++ openstackclient/tests/unit/volume/v2/fakes.py | 24 +++++ .../unit/volume/v2/test_consistency_group.py | 101 ++++++++++++++++++ .../volume/v2/consistency_group.py | 45 ++++++++ .../notes/bug-1613964-837196399be16b3d.yaml | 2 + setup.cfg | 1 + 6 files changed, 194 insertions(+) diff --git a/doc/source/command-objects/consistency-group.rst b/doc/source/command-objects/consistency-group.rst index c8c4577ccf..3ab68e90d7 100644 --- a/doc/source/command-objects/consistency-group.rst +++ b/doc/source/command-objects/consistency-group.rst @@ -40,6 +40,27 @@ Create new consistency group. Name of new consistency group (default to None) +consistency group delete +------------------------ + +Delete consistency group(s). + +.. program:: consistency group delete +.. code:: bash + + os consistency group delete + [--force] + [ ...] + +.. option:: --force + + Allow delete in state other than error or available + +.. _consistency_group_delete-consistency-group: +.. describe:: + + Consistency group(s) to delete (name or ID) + consistency group list ---------------------- diff --git a/openstackclient/tests/unit/volume/v2/fakes.py b/openstackclient/tests/unit/volume/v2/fakes.py index 41d8e79475..3137bfb012 100644 --- a/openstackclient/tests/unit/volume/v2/fakes.py +++ b/openstackclient/tests/unit/volume/v2/fakes.py @@ -546,6 +546,30 @@ def create_consistency_groups(attrs=None, count=2): return consistency_groups + @staticmethod + def get_consistency_groups(consistency_groups=None, count=2): + """Note: + + Get an iterable MagicMock object with a list of faked + consistency_groups. + + If consistency_groups list is provided, then initialize + the Mock object with the list. Otherwise create one. + + :param List consistency_groups: + A list of FakeResource objects faking consistency_groups + :param Integer count: + The number of consistency_groups to be faked + :return + An iterable Mock object with side_effect set to a list of faked + consistency_groups + """ + if consistency_groups is None: + consistency_groups = (FakeConsistencyGroup. + create_consistency_groups(count)) + + return mock.Mock(side_effect=consistency_groups) + class FakeConsistencyGroupSnapshot(object): """Fake one or more consistency group snapshot.""" diff --git a/openstackclient/tests/unit/volume/v2/test_consistency_group.py b/openstackclient/tests/unit/volume/v2/test_consistency_group.py index e005642169..835d996024 100644 --- a/openstackclient/tests/unit/volume/v2/test_consistency_group.py +++ b/openstackclient/tests/unit/volume/v2/test_consistency_group.py @@ -12,6 +12,10 @@ # under the License. # +import mock +from mock import call + +from osc_lib import exceptions from osc_lib import utils from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes @@ -161,6 +165,103 @@ def test_consistency_group_create_from_source(self): self.assertEqual(self.data, data) +class TestConsistencyGroupDelete(TestConsistencyGroup): + + consistency_groups =\ + volume_fakes.FakeConsistencyGroup.create_consistency_groups(count=2) + + def setUp(self): + super(TestConsistencyGroupDelete, self).setUp() + + self.consistencygroups_mock.get = volume_fakes.FakeConsistencyGroup.\ + get_consistency_groups(self.consistency_groups) + self.consistencygroups_mock.delete.return_value = None + + # Get the command object to mock + self.cmd = consistency_group.DeleteConsistencyGroup(self.app, None) + + def test_consistency_group_delete(self): + arglist = [ + self.consistency_groups[0].id + ] + verifylist = [ + ("consistency_groups", [self.consistency_groups[0].id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.consistencygroups_mock.delete.assert_called_with( + self.consistency_groups[0].id, False) + self.assertIsNone(result) + + def test_consistency_group_delete_with_force(self): + arglist = [ + '--force', + self.consistency_groups[0].id, + ] + verifylist = [ + ('force', True), + ("consistency_groups", [self.consistency_groups[0].id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.consistencygroups_mock.delete.assert_called_with( + self.consistency_groups[0].id, True) + self.assertIsNone(result) + + def test_delete_multiple_consistency_groups(self): + arglist = [] + for b in self.consistency_groups: + arglist.append(b.id) + verifylist = [ + ('consistency_groups', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + calls = [] + for b in self.consistency_groups: + calls.append(call(b.id, False)) + self.consistencygroups_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_delete_multiple_consistency_groups_with_exception(self): + arglist = [ + self.consistency_groups[0].id, + 'unexist_consistency_group', + ] + verifylist = [ + ('consistency_groups', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self.consistency_groups[0], + exceptions.CommandError] + with mock.patch.object(utils, 'find_resource', + side_effect=find_mock_result) as find_mock: + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 consistency groups failed to delete.', + str(e)) + + find_mock.assert_any_call(self.consistencygroups_mock, + self.consistency_groups[0].id) + find_mock.assert_any_call(self.consistencygroups_mock, + 'unexist_consistency_group') + + self.assertEqual(2, find_mock.call_count) + self.consistencygroups_mock.delete.assert_called_once_with( + self.consistency_groups[0].id, False + ) + + class TestConsistencyGroupList(TestConsistencyGroup): consistency_groups = ( diff --git a/openstackclient/volume/v2/consistency_group.py b/openstackclient/volume/v2/consistency_group.py index 9316f28737..a90091a69b 100644 --- a/openstackclient/volume/v2/consistency_group.py +++ b/openstackclient/volume/v2/consistency_group.py @@ -17,11 +17,56 @@ 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 DeleteConsistencyGroup(command.Command): + _description = _("Delete consistency group(s).") + + def get_parser(self, prog_name): + parser = super(DeleteConsistencyGroup, self).get_parser(prog_name) + parser.add_argument( + 'consistency_groups', + metavar='', + nargs="+", + help=_('Consistency group(s) to delete (name or ID)'), + ) + parser.add_argument( + '--force', + action='store_true', + default=False, + help=_("Allow delete in state other than error or available"), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + result = 0 + + for i in parsed_args.consistency_groups: + try: + consistency_group_id = utils.find_resource( + volume_client.consistencygroups, i).id + volume_client.consistencygroups.delete( + consistency_group_id, parsed_args.force) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete consistency group with " + "name or ID '%(consistency_group)s':%(e)s") + % {'consistency_group': i, 'e': e}) + + if result > 0: + total = len(parsed_args.consistency_groups) + msg = (_("%(result)s of %(total)s consistency groups failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) + LOG = logging.getLogger(__name__) diff --git a/releasenotes/notes/bug-1613964-837196399be16b3d.yaml b/releasenotes/notes/bug-1613964-837196399be16b3d.yaml index 0f17930ebb..1d404af5b2 100644 --- a/releasenotes/notes/bug-1613964-837196399be16b3d.yaml +++ b/releasenotes/notes/bug-1613964-837196399be16b3d.yaml @@ -2,3 +2,5 @@ features: - Add ``consistency group create`` command in volume v2. [Bug `1613964 `_] + - Add ``consistency group delete`` command in volume v2. + [Bug `1613964 `_] diff --git a/setup.cfg b/setup.cfg index 871a3dbe00..a42af75fb3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -509,6 +509,7 @@ openstack.volume.v2 = backup_show = openstackclient.volume.v2.backup:ShowBackup consistency_group_create = openstackclient.volume.v2.consistency_group:CreateConsistencyGroup + consistency_group_delete = openstackclient.volume.v2.consistency_group:DeleteConsistencyGroup consistency_group_list = openstackclient.volume.v2.consistency_group:ListConsistencyGroup consistency_group_snapshot_create = openstackclient.volume.v2.consistency_group_snapshot:CreateConsistencyGroupSnapshot From 1907220113efe9425a6cc7f52ce87dd4e82233c7 Mon Sep 17 00:00:00 2001 From: daizhiyong Date: Wed, 16 Nov 2016 15:39:14 +0800 Subject: [PATCH 1392/3095] Add "consistency group show" command Add "consistency group show" command in volume v2 (v2 only). Change-Id: If496eba2955c0aacd52600bb6fba39690ddd90cb Implements: bp cinder-command-support Partial-Bug: #1613964 --- .../command-objects/consistency-group.rst | 17 ++++++++ .../unit/volume/v2/test_consistency_group.py | 43 +++++++++++++++++++ .../volume/v2/consistency_group.py | 21 +++++++++ .../notes/bug-1613964-837196399be16b3d.yaml | 2 + setup.cfg | 1 + 5 files changed, 84 insertions(+) diff --git a/doc/source/command-objects/consistency-group.rst b/doc/source/command-objects/consistency-group.rst index 3ab68e90d7..46682a56e4 100644 --- a/doc/source/command-objects/consistency-group.rst +++ b/doc/source/command-objects/consistency-group.rst @@ -81,3 +81,20 @@ List consistency groups. .. option:: --long List additional fields in output + +consistency group show +---------------------- + +Display consistency group details. + +.. program:: consistency group show +.. code:: bash + + os consistency group show + + +.. _consistency_group_show-consistency-group: +.. describe:: + + Consistency group to display (name or ID) + diff --git a/openstackclient/tests/unit/volume/v2/test_consistency_group.py b/openstackclient/tests/unit/volume/v2/test_consistency_group.py index 835d996024..5beb6ef28d 100644 --- a/openstackclient/tests/unit/volume/v2/test_consistency_group.py +++ b/openstackclient/tests/unit/volume/v2/test_consistency_group.py @@ -353,3 +353,46 @@ def test_consistency_group_list_with_long(self): detailed=True, search_opts={'all_tenants': False}) self.assertEqual(self.columns_long, columns) self.assertEqual(self.data_long, list(data)) + + +class TestConsistencyGroupShow(TestConsistencyGroup): + columns = ( + 'availability_zone', + 'created_at', + 'description', + 'id', + 'name', + 'status', + 'volume_types', + ) + + def setUp(self): + super(TestConsistencyGroupShow, self).setUp() + + self.consistency_group = ( + volume_fakes.FakeConsistencyGroup.create_one_consistency_group()) + self.data = ( + self.consistency_group.availability_zone, + self.consistency_group.created_at, + self.consistency_group.description, + self.consistency_group.id, + self.consistency_group.name, + self.consistency_group.status, + self.consistency_group.volume_types, + ) + self.consistencygroups_mock.get.return_value = self.consistency_group + self.cmd = consistency_group.ShowConsistencyGroup(self.app, None) + + def test_consistency_group_show(self): + arglist = [ + self.consistency_group.id + ] + verifylist = [ + ("consistency_group", self.consistency_group.id) + ] + 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.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 a90091a69b..661bcbe51f 100644 --- a/openstackclient/volume/v2/consistency_group.py +++ b/openstackclient/volume/v2/consistency_group.py @@ -23,6 +23,7 @@ from openstackclient.i18n import _ + LOG = logging.getLogger(__name__) @@ -177,3 +178,23 @@ def take_action(self, parsed_args): s, columns, formatters={'Volume Types': utils.format_list}) for s in consistency_groups)) + + +class ShowConsistencyGroup(command.ShowOne): + _description = _("Display consistency group details.") + + def get_parser(self, prog_name): + parser = super(ShowConsistencyGroup, self).get_parser(prog_name) + parser.add_argument( + "consistency_group", + metavar="", + help=_("Consistency group to display (name or ID)") + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + consistency_group = utils.find_resource( + volume_client.consistencygroups, + parsed_args.consistency_group) + return zip(*sorted(six.iteritems(consistency_group._info))) diff --git a/releasenotes/notes/bug-1613964-837196399be16b3d.yaml b/releasenotes/notes/bug-1613964-837196399be16b3d.yaml index 1d404af5b2..4097509453 100644 --- a/releasenotes/notes/bug-1613964-837196399be16b3d.yaml +++ b/releasenotes/notes/bug-1613964-837196399be16b3d.yaml @@ -4,3 +4,5 @@ features: [Bug `1613964 `_] - Add ``consistency group delete`` command in volume v2. [Bug `1613964 `_] + - Add ``consistency group show`` command in volume v2. + [Bug `1613964 `_] diff --git a/setup.cfg b/setup.cfg index a42af75fb3..3bd487b35c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -511,6 +511,7 @@ openstack.volume.v2 = consistency_group_create = openstackclient.volume.v2.consistency_group:CreateConsistencyGroup consistency_group_delete = openstackclient.volume.v2.consistency_group:DeleteConsistencyGroup consistency_group_list = openstackclient.volume.v2.consistency_group:ListConsistencyGroup + consistency_group_show = openstackclient.volume.v2.consistency_group:ShowConsistencyGroup consistency_group_snapshot_create = openstackclient.volume.v2.consistency_group_snapshot:CreateConsistencyGroupSnapshot consistency_group_snapshot_delete = openstackclient.volume.v2.consistency_group_snapshot:DeleteConsistencyGroupSnapshot From df5f12b135f273e3916e7bf300fa7688a180ea02 Mon Sep 17 00:00:00 2001 From: Nam Nguyen Hoai Date: Fri, 12 Aug 2016 16:00:52 +0700 Subject: [PATCH 1393/3095] Add "dns-name" option to "os port create" and "os port set" This patch added a "dns-name" option to "os port create" and "os port set" command. Change-Id: I360e2c9a1970e64fe17e4561d7618f860b937373 Co-Authored-By: Ha Van Tu Partial-Bug: #1612136 Partially-Implements: blueprint network-commands-options --- doc/source/command-objects/port.rst | 12 +++++ openstackclient/network/v2/port.py | 8 +++ .../tests/unit/network/v2/test_port.py | 50 +++++++++++++++++++ .../notes/bug-1612136-63aac6377209db38.yaml | 5 ++ 4 files changed, 75 insertions(+) create mode 100644 releasenotes/notes/bug-1612136-63aac6377209db38.yaml diff --git a/doc/source/command-objects/port.rst b/doc/source/command-objects/port.rst index 3aff2f77be..73c53290be 100644 --- a/doc/source/command-objects/port.rst +++ b/doc/source/command-objects/port.rst @@ -28,6 +28,7 @@ Create new port [--enable | --disable] [--mac-address ] [--security-group | --no-security-group] + [--dns-name ] [--project [--project-domain ]] [--enable-port-security | --disable-port-security] @@ -91,6 +92,11 @@ Create new port Associate no security groups with this port +.. option:: --dns-name + + Set DNS name to this port + (requires DNS integration extension) + .. option:: --project Owner's project (name or ID) @@ -192,6 +198,7 @@ Set port properties [--security-group ] [--no-security-group] [--enable-port-security | --disable-port-security] + [--dns-name ] .. option:: --description @@ -269,6 +276,11 @@ Set port properties Disable port security for this port +.. option:: --dns-name + + Set DNS name to this port + (requires DNS integration extension) + .. _port_set-port: .. describe:: diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 6cae87ee18..c960ba2528 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -128,6 +128,8 @@ def _get_attrs(client_manager, parsed_args): if parsed_args.host: attrs['binding:host_id'] = parsed_args.host + if parsed_args.dns_name is not None: + attrs['dns_name'] = parsed_args.dns_name # It is possible that name is not updated during 'port set' if parsed_args.name is not None: attrs['name'] = str(parsed_args.name) @@ -233,6 +235,12 @@ def _add_updatable_args(parser): metavar='', help=argparse.SUPPRESS, ) + parser.add_argument( + '--dns-name', + metavar='dns-name', + help=_("Set DNS name to this port " + "(requires DNS integration extension)") + ) class CreatePort(command.ShowOne): diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index 9312a8976d..aeb9884a42 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -140,6 +140,7 @@ def test_create_full_options(self): '--binding-profile', 'foo=bar', '--binding-profile', 'foo2=bar2', '--network', self._port.network_id, + '--dns-name', '8.8.8.8', 'test-port', ] @@ -156,6 +157,7 @@ def test_create_full_options(self): ('vnic_type', 'macvtap'), ('binding_profile', {'foo': 'bar', 'foo2': 'bar2'}), ('network', self._port.network_id), + ('dns_name', '8.8.8.8'), ('name', 'test-port'), ] @@ -174,6 +176,7 @@ def test_create_full_options(self): 'binding:vnic_type': 'macvtap', 'binding:profile': {'foo': 'bar', 'foo2': 'bar2'}, 'network_id': self._port.network_id, + 'dns_name': '8.8.8.8', 'name': 'test-port', }) @@ -241,6 +244,7 @@ def test_create_with_security_group(self): '--security-group', secgroup.id, 'test-port', ] + verifylist = [ ('network', self._port.network_id,), ('enable', True), @@ -262,6 +266,33 @@ def test_create_with_security_group(self): self.assertEqual(ref_columns, columns) self.assertEqual(ref_data, data) + def test_create_port_with_dns_name(self): + arglist = [ + '--network', self._port.network_id, + '--dns-name', '8.8.8.8', + 'test-port', + ] + verifylist = [ + ('network', self._port.network_id,), + ('enable', True), + ('dns_name', '8.8.8.8'), + ('name', 'test-port'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_port.assert_called_once_with(**{ + 'admin_state_up': True, + 'network_id': self._port.network_id, + 'dns_name': '8.8.8.8', + 'name': 'test-port', + }) + + ref_columns, ref_data = self._get_common_cols_data(self._port) + self.assertEqual(ref_columns, columns) + self.assertEqual(ref_data, data) + def test_create_with_security_groups(self): sg_1 = network_fakes.FakeSecurityGroup.create_one_security_group() sg_2 = network_fakes.FakeSecurityGroup.create_one_security_group() @@ -676,6 +707,25 @@ def test_set_fixed_ip(self): self.network.update_port.assert_called_once_with(self._port, **attrs) self.assertIsNone(result) + def test_set_dns_name(self): + arglist = [ + '--dns-name', '8.8.8.8', + self._port.name, + ] + verifylist = [ + ('dns_name', '8.8.8.8'), + ('port', self._port.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = { + 'dns_name': '8.8.8.8', + } + self.network.update_port.assert_called_once_with(self._port, **attrs) + self.assertIsNone(result) + def test_append_fixed_ip(self): _testport = network_fakes.FakePort.create_one_port( {'fixed_ips': [{'ip_address': '0.0.0.1'}]}) diff --git a/releasenotes/notes/bug-1612136-63aac6377209db38.yaml b/releasenotes/notes/bug-1612136-63aac6377209db38.yaml new file mode 100644 index 0000000000..51eaa76dd1 --- /dev/null +++ b/releasenotes/notes/bug-1612136-63aac6377209db38.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``--dns-name`` option to ``os port create`` and ``os port set`` commands. + [Bug `1612136 `_] From 83675e9ee080d82b7c927180847d27fae6903e5a Mon Sep 17 00:00:00 2001 From: judy-yu Date: Sun, 4 Dec 2016 18:03:33 +0800 Subject: [PATCH 1394/3095] Avoid duplicated project_id when show network resources Project_id appear twice when show network resources. This patch check and not append if it already has one. Change-Id: I744988f3f52d4a744e397a6a82fefdc4c17eacbf Closes-Bug: #1636123 Partially-Implements: blueprint duplicated-project-id --- openstackclient/network/v2/port.py | 3 ++- openstackclient/network/v2/router.py | 19 ++++++++++--------- openstackclient/network/v2/security_group.py | 3 ++- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 6cae87ee18..0b636308e3 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -50,7 +50,8 @@ def _get_columns(item): columns = list(item.keys()) if 'tenant_id' in columns: columns.remove('tenant_id') - columns.append('project_id') + if 'project_id' not in columns: + columns.append('project_id') binding_columns = [ 'binding:host_id', 'binding:profile', diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index cbd412b585..c387831aab 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -25,6 +25,7 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import sdk_utils LOG = logging.getLogger(__name__) @@ -59,11 +60,10 @@ def _format_routes(routes): def _get_columns(item): - columns = list(item.keys()) - if 'tenant_id' in columns: - columns.remove('tenant_id') - columns.append('project_id') - return tuple(sorted(columns)) + column_map = { + 'tenant_id': 'project_id', + } + return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) def _get_attrs(client_manager, parsed_args): @@ -215,10 +215,10 @@ def take_action(self, parsed_args): attrs['ha'] = parsed_args.ha obj = client.create_router(**attrs) - columns = _get_columns(obj) + display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) - return (columns, data) + return (display_columns, data) class DeleteRouter(command.Command): @@ -523,9 +523,10 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): client = self.app.client_manager.network obj = client.find_router(parsed_args.router, ignore_missing=False) - columns = _get_columns(obj) + display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) - return (columns, data) + + return (display_columns, data) class UnsetRouter(command.Command): diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py index 554dd61d6f..8c5c7fee1e 100644 --- a/openstackclient/network/v2/security_group.py +++ b/openstackclient/network/v2/security_group.py @@ -81,8 +81,9 @@ def _get_columns(item): columns.remove('security_group_rules') property_column_mappings.append(('rules', 'security_group_rules')) if 'tenant_id' in columns: - columns.append('project_id') columns.remove('tenant_id') + if 'project_id' not in columns: + columns.append('project_id') property_column_mappings.append(('project_id', 'tenant_id')) display_columns = sorted(columns) From 99ba4f86b0e2a4072f3a3754a5e3a48be29cdfd7 Mon Sep 17 00:00:00 2001 From: "zhiyong.dai" Date: Sun, 4 Dec 2016 18:15:55 +0800 Subject: [PATCH 1395/3095] Adjust the code sequence in consistency_group.py Place "CreateConsistencyGroup" above "DeleteConsistencyGroup" Change-Id: I554a8e445fee0760450b2da3b5c4a4f3b2434d60 --- .../volume/v2/consistency_group.py | 87 +++++++++---------- 1 file changed, 42 insertions(+), 45 deletions(-) diff --git a/openstackclient/volume/v2/consistency_group.py b/openstackclient/volume/v2/consistency_group.py index 661bcbe51f..9b0975dd17 100644 --- a/openstackclient/volume/v2/consistency_group.py +++ b/openstackclient/volume/v2/consistency_group.py @@ -27,51 +27,6 @@ LOG = logging.getLogger(__name__) -class DeleteConsistencyGroup(command.Command): - _description = _("Delete consistency group(s).") - - def get_parser(self, prog_name): - parser = super(DeleteConsistencyGroup, self).get_parser(prog_name) - parser.add_argument( - 'consistency_groups', - metavar='', - nargs="+", - help=_('Consistency group(s) to delete (name or ID)'), - ) - parser.add_argument( - '--force', - action='store_true', - default=False, - help=_("Allow delete in state other than error or available"), - ) - return parser - - def take_action(self, parsed_args): - volume_client = self.app.client_manager.volume - result = 0 - - for i in parsed_args.consistency_groups: - try: - consistency_group_id = utils.find_resource( - volume_client.consistencygroups, i).id - volume_client.consistencygroups.delete( - consistency_group_id, parsed_args.force) - except Exception as e: - result += 1 - LOG.error(_("Failed to delete consistency group with " - "name or ID '%(consistency_group)s':%(e)s") - % {'consistency_group': i, 'e': e}) - - if result > 0: - total = len(parsed_args.consistency_groups) - msg = (_("%(result)s of %(total)s consistency groups failed " - "to delete.") % {'result': result, 'total': total}) - raise exceptions.CommandError(msg) - - -LOG = logging.getLogger(__name__) - - class CreateConsistencyGroup(command.ShowOne): _description = _("Create new consistency group.") @@ -143,6 +98,48 @@ def take_action(self, parsed_args): return zip(*sorted(six.iteritems(consistency_group._info))) +class DeleteConsistencyGroup(command.Command): + _description = _("Delete consistency group(s).") + + def get_parser(self, prog_name): + parser = super(DeleteConsistencyGroup, self).get_parser(prog_name) + parser.add_argument( + 'consistency_groups', + metavar='', + nargs="+", + help=_('Consistency group(s) to delete (name or ID)'), + ) + parser.add_argument( + '--force', + action='store_true', + default=False, + help=_("Allow delete in state other than error or available"), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + result = 0 + + for i in parsed_args.consistency_groups: + try: + consistency_group_id = utils.find_resource( + volume_client.consistencygroups, i).id + volume_client.consistencygroups.delete( + consistency_group_id, parsed_args.force) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete consistency group with " + "name or ID '%(consistency_group)s':%(e)s") + % {'consistency_group': i, 'e': e}) + + if result > 0: + total = len(parsed_args.consistency_groups) + msg = (_("%(result)s of %(total)s consistency groups failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) + + class ListConsistencyGroup(command.Lister): _description = _("List consistency groups.") From 8c71a35eebb25a4930c7d0c2cc4cf0d57b1f6c72 Mon Sep 17 00:00:00 2001 From: Ankur Gupta Date: Wed, 12 Oct 2016 13:41:12 -0500 Subject: [PATCH 1396/3095] SDK Refactor: Prepare ip availability commands Prepared the OSC "ip availability" commands for the SDK refactor. See [1] for details. [1] https://etherpad.openstack.org/p/osc-network-command-sdk-support Change-Id: Ia22e9fafec0a91ba1b0f1ce825fcaf1d8cbbbf88 Partially-Implements: blueprint network-command-sdk-support --- openstackclient/network/v2/ip_availability.py | 18 ++++++++++-------- openstackclient/tests/unit/network/v2/fakes.py | 12 +++++++++--- .../unit/network/v2/test_ip_availability.py | 1 + 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/openstackclient/network/v2/ip_availability.py b/openstackclient/network/v2/ip_availability.py index 1d7b2aed87..839e1d0084 100644 --- a/openstackclient/network/v2/ip_availability.py +++ b/openstackclient/network/v2/ip_availability.py @@ -18,7 +18,7 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common - +from openstackclient.network import sdk_utils _formatters = { 'subnet_ip_availability': utils.format_list_of_dicts, @@ -26,13 +26,14 @@ def _get_columns(item): - columns = list(item.keys()) - if 'tenant_id' in columns: - columns.remove('tenant_id') - columns.append('project_id') - return tuple(sorted(columns)) + column_map = { + 'tenant_id': 'project_id', + } + return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) +# TODO(ankur-gupta-f): Use the SDK resource mapped attribute names once +# the OSC minimum requirements include SDK 1.0. class ListIPAvailability(command.Lister): """List IP availability for network""" @@ -84,6 +85,7 @@ def take_action(self, parsed_args): parsed_args.project_domain, ).id filters['tenant_id'] = project_id + filters['project_id'] = project_id data = client.network_ip_availabilities(**filters) return (column_headers, (utils.get_item_properties( @@ -107,6 +109,6 @@ def take_action(self, parsed_args): client = self.app.client_manager.network obj = client.find_network_ip_availability(parsed_args.network, ignore_missing=False) - columns = _get_columns(obj) + display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) - return (columns, data) + return (display_columns, data) diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index ed3579b724..527649b802 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -194,15 +194,18 @@ class FakeIPAvailability(object): """Fake one or more network ip availabilities.""" @staticmethod - def create_one_ip_availability(): + def create_one_ip_availability(attrs=None): """Create a fake list with ip availability stats of a network. + :param Dictionary attrs: + A dictionary with all attributes :return: A FakeResource object with network_name, network_id, etc. """ + attrs = attrs or {} # Set default attributes. - network_ip_availability = { + network_ip_attrs = { 'network_id': 'network-id-' + uuid.uuid4().hex, 'network_name': 'network-name-' + uuid.uuid4().hex, 'tenant_id': '', @@ -210,10 +213,13 @@ def create_one_ip_availability(): 'total_ips': 254, 'used_ips': 6, } + network_ip_attrs.update(attrs) network_ip_availability = fakes.FakeResource( - info=copy.deepcopy(network_ip_availability), + info=copy.deepcopy(network_ip_attrs), loaded=True) + network_ip_availability.project_id = network_ip_attrs['tenant_id'] + return network_ip_availability @staticmethod diff --git a/openstackclient/tests/unit/network/v2/test_ip_availability.py b/openstackclient/tests/unit/network/v2/test_ip_availability.py index c929ab82c8..4bdbddc47f 100644 --- a/openstackclient/tests/unit/network/v2/test_ip_availability.py +++ b/openstackclient/tests/unit/network/v2/test_ip_availability.py @@ -107,6 +107,7 @@ def test_list_project(self): columns, data = self.cmd.take_action(parsed_args) filters = {'tenant_id': self.project.id, + 'project_id': self.project.id, 'ip_version': 4} self.network.network_ip_availabilities.assert_called_once_with( From 5203cc970762953e72d4745452d6ed5a2283ad9f Mon Sep 17 00:00:00 2001 From: Colleen Murphy Date: Mon, 28 Nov 2016 15:37:14 +0100 Subject: [PATCH 1397/3095] Remove auth_with_unscoped_saml decorator The auth_with_unscoped_saml decorator existed to make sure the user selected the right auth plugin before trying to call either a 'federation domain' or 'federation project' command. This is outdated, because openstackclient now uses keystoneauth[1] and keystoneauth removed its entrypoints for the federation plugins[2] since its _Rescoped class no longer needs them. This patch removes the decorator since that validation check was the only thing standing in the way of the commands working correctly. Also removed the '*_list_wrong_auth' tests since those only existed to test the decorator, and stopped setting the plugin in the positive tests since the automatically-determined token plugin should now be fine. [1] http://git.openstack.org/cgit/openstack/python-openstackclient/commit/?id=6ae0d2e8a54fd5139e63a990ab4bdce634e73c5e [2] http://git.openstack.org/cgit/openstack/keystoneauth/commit/?id=d9e4d26bb86f8d48e43188b88bab9d7fe778d2c1 Change-Id: Id981739663113447a7bba8ddba81ba9394a19e07 Closes-bug: #1624115 --- openstackclient/identity/v3/unscoped_saml.py | 22 ---------------- .../unit/identity/v3/test_unscoped_saml.py | 26 ------------------- 2 files changed, 48 deletions(-) diff --git a/openstackclient/identity/v3/unscoped_saml.py b/openstackclient/identity/v3/unscoped_saml.py index 5940534a0a..f7598f178f 100644 --- a/openstackclient/identity/v3/unscoped_saml.py +++ b/openstackclient/identity/v3/unscoped_saml.py @@ -18,35 +18,14 @@ a scoped token.""" from osc_lib.command import command -from osc_lib import exceptions from osc_lib import utils from openstackclient.i18n import _ -UNSCOPED_AUTH_PLUGINS = ['v3unscopedsaml', 'v3unscopedadfs', 'v3oidc'] - - -def auth_with_unscoped_saml(func): - """Check the unscoped federated context""" - - def _decorated(self, parsed_args): - auth_plugin_name = self.app.client_manager.auth_plugin_name - if auth_plugin_name in UNSCOPED_AUTH_PLUGINS: - return func(self, parsed_args) - else: - msg = (_('This command requires the use of an unscoped SAML ' - 'authentication plugin. Please use argument ' - '--os-auth-type with one of the following ' - 'plugins: %s') % ', '.join(UNSCOPED_AUTH_PLUGINS)) - raise exceptions.CommandError(msg) - return _decorated - - class ListAccessibleDomains(command.Lister): _description = _("List accessible domains") - @auth_with_unscoped_saml def take_action(self, parsed_args): columns = ('ID', 'Enabled', 'Name', 'Description') identity_client = self.app.client_manager.identity @@ -61,7 +40,6 @@ def take_action(self, parsed_args): class ListAccessibleProjects(command.Lister): _description = _("List accessible projects") - @auth_with_unscoped_saml def take_action(self, parsed_args): columns = ('ID', 'Domain ID', 'Enabled', 'Name') identity_client = self.app.client_manager.identity diff --git a/openstackclient/tests/unit/identity/v3/test_unscoped_saml.py b/openstackclient/tests/unit/identity/v3/test_unscoped_saml.py index 9e4e1876ae..34655263f7 100644 --- a/openstackclient/tests/unit/identity/v3/test_unscoped_saml.py +++ b/openstackclient/tests/unit/identity/v3/test_unscoped_saml.py @@ -12,8 +12,6 @@ import copy -from osc_lib import exceptions - from openstackclient.identity.v3 import unscoped_saml from openstackclient.tests.unit import fakes from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes @@ -48,7 +46,6 @@ def setUp(self): self.cmd = unscoped_saml.ListAccessibleDomains(self.app, None) def test_accessible_domains_list(self): - self.app.client_manager.auth_plugin_name = 'v3unscopedsaml' arglist = [] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -70,17 +67,6 @@ def test_accessible_domains_list(self): ), ) self.assertEqual(datalist, tuple(data)) - def test_accessible_domains_list_wrong_auth(self): - auth = identity_fakes.FakeAuth("wrong auth") - self.app.client_manager.identity.session.auth = auth - arglist = [] - verifylist = [] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - self.assertRaises(exceptions.CommandError, - self.cmd.take_action, - parsed_args) - class TestProjectList(TestUnscopedSAML): @@ -99,7 +85,6 @@ def setUp(self): self.cmd = unscoped_saml.ListAccessibleProjects(self.app, None) def test_accessible_projects_list(self): - self.app.client_manager.auth_plugin_name = 'v3unscopedsaml' arglist = [] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -120,14 +105,3 @@ def test_accessible_projects_list(self): identity_fakes.project_name, ), ) self.assertEqual(datalist, tuple(data)) - - def test_accessible_projects_list_wrong_auth(self): - auth = identity_fakes.FakeAuth("wrong auth") - self.app.client_manager.identity.session.auth = auth - arglist = [] - verifylist = [] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - self.assertRaises(exceptions.CommandError, - self.cmd.take_action, - parsed_args) From 42f33435ed36319552842674a96823b7c6e2164f Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Fri, 2 Dec 2016 16:14:42 +0000 Subject: [PATCH 1398/3095] Revert "Remove marker and loop from "image list" command" This reverts commit 0b6fdcbe4c3f3142fdd18bfd827653d894607941. Adapt "image list" to not loop when --marker is specified on command line. Update tests to work with current state of code. Change-Id: I8af58adf8637a9e34371c6280db40935d22bc3c3 --- openstackclient/image/v2/image.py | 15 +++++++++++-- .../tests/unit/image/v2/test_image.py | 21 ++++++++++++++----- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 4031952bc4..ac29da9cc5 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -485,7 +485,6 @@ def take_action(self, parsed_args): if parsed_args.marker: kwargs['marker'] = utils.find_resource(image_client.images, parsed_args.marker).id - if parsed_args.long: columns = ( 'ID', @@ -518,7 +517,19 @@ def take_action(self, parsed_args): column_headers = columns # List of image data received - data = image_client.api.image_list(**kwargs) + data = [] + if 'marker' in kwargs: + data = image_client.api.image_list(**kwargs) + else: + # No pages received yet, so start the page marker at None. + marker = None + while True: + page = image_client.api.image_list(marker=marker, **kwargs) + if not page: + break + data.extend(page) + # Set the marker to the id of the last item we received + marker = page[-1]['id'] if parsed_args.property: # NOTE(dtroyer): coerce to a list to subscript it in py3 diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py index 2f2212e4c3..a054e513d9 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -535,7 +535,9 @@ def test_image_list_no_options(self): # returns a tuple containing the column names and an iterable # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.api_mock.image_list.assert_called_with() + self.api_mock.image_list.assert_called_with( + marker=self._image.id, + ) self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) @@ -558,6 +560,7 @@ def test_image_list_public_option(self): columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( public=True, + marker=self._image.id, ) self.assertEqual(self.columns, columns) @@ -581,6 +584,7 @@ def test_image_list_private_option(self): columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( private=True, + marker=self._image.id, ) self.assertEqual(self.columns, columns) @@ -604,6 +608,7 @@ def test_image_list_shared_option(self): columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( shared=True, + marker=self._image.id, ) self.assertEqual(self.columns, columns) @@ -622,7 +627,9 @@ def test_image_list_long_option(self): # returns a tuple containing the column names and an iterable # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.api_mock.image_list.assert_called_with() + self.api_mock.image_list.assert_called_with( + marker=self._image.id, + ) collist = ( 'ID', @@ -670,7 +677,9 @@ def test_image_list_property_option(self, sf_mock): # returns a tuple containing the column names and an iterable # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.api_mock.image_list.assert_called_with() + self.api_mock.image_list.assert_called_with( + marker=self._image.id, + ) sf_mock.assert_called_with( [self._image], attr='a', @@ -693,7 +702,9 @@ def test_image_list_sort_option(self, si_mock): # returns a tuple containing the column names and an iterable # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.api_mock.image_list.assert_called_with() + self.api_mock.image_list.assert_called_with( + marker=self._image.id, + ) si_mock.assert_called_with( [self._image], 'name:asc' @@ -712,7 +723,7 @@ def test_image_list_limit_option(self): columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( - limit=1, + limit=1, marker=self._image.id ) self.assertEqual(self.columns, columns) From 7e5a98bca91e0734f3440fdbeb12cd59273cd6a4 Mon Sep 17 00:00:00 2001 From: "jiahui.qiang" Date: Sun, 4 Dec 2016 00:29:03 +0800 Subject: [PATCH 1399/3095] Add some options to "volume create" command Add "--bootable", "--non-bootable", "--read-only" and "--read-write" options to "volume create" command for setting some attributes at the time of crration. Change-Id: I71b4e9fccb4ee0ab1a90e7179d6d2d34dbbae909 Implements: bp cinder-command-support --- doc/source/command-objects/volume.rst | 18 +++ .../tests/unit/volume/v1/test_volume.py | 136 ++++++++++++++++ .../tests/unit/volume/v2/test_volume.py | 148 ++++++++++++++++++ openstackclient/volume/v1/volume.py | 38 +++++ openstackclient/volume/v2/volume.py | 38 +++++ ...nder-command-support-ff7acc531baae8c3.yaml | 5 + 6 files changed, 383 insertions(+) create mode 100644 releasenotes/notes/bp-cinder-command-support-ff7acc531baae8c3.yaml diff --git a/doc/source/command-objects/volume.rst b/doc/source/command-objects/volume.rst index df4d68806f..5383386f5b 100644 --- a/doc/source/command-objects/volume.rst +++ b/doc/source/command-objects/volume.rst @@ -24,6 +24,8 @@ Create new volume [--property [...] ] [--hint [...] ] [--multi-attach] + [--bootable | --non-bootable] + [--read-only | --read-write] .. option:: --size @@ -89,6 +91,22 @@ Create new volume Allow volume to be attached more than once (default to False) +.. option:: --bootable + + Mark volume as bootable + +.. option:: --non-bootable + + Mark volume as non-bootable (default) + +.. option:: --read-only + + Set volume to read-only access mode + +.. option:: --read-write + + Set volume to read-write access mode (default) + .. _volume_create-name: .. describe:: diff --git a/openstackclient/tests/unit/volume/v1/test_volume.py b/openstackclient/tests/unit/volume/v1/test_volume.py index 7a44dea85c..6c6d9a1d6f 100644 --- a/openstackclient/tests/unit/volume/v1/test_volume.py +++ b/openstackclient/tests/unit/volume/v1/test_volume.py @@ -431,6 +431,142 @@ def test_volume_create_with_source(self): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, data) + def test_volume_create_with_bootable_and_readonly(self): + arglist = [ + '--bootable', + '--read-only', + '--size', str(self.new_volume.size), + self.new_volume.display_name, + ] + verifylist = [ + ('bootable', True), + ('non_bootable', False), + ('read_only', True), + ('read_write', False), + ('size', self.new_volume.size), + ('name', self.new_volume.display_name), + ] + + parsed_args = self.check_parser( + self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volumes_mock.create.assert_called_with( + self.new_volume.size, + None, + None, + self.new_volume.display_name, + None, + None, + None, + None, + None, + None, + None, + ) + + self.assertEqual(self.columns, columns) + 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) + + def test_volume_create_with_nonbootable_and_readwrite(self): + arglist = [ + '--non-bootable', + '--read-write', + '--size', str(self.new_volume.size), + self.new_volume.display_name, + ] + verifylist = [ + ('bootable', False), + ('non_bootable', True), + ('read_only', False), + ('read_write', True), + ('size', self.new_volume.size), + ('name', self.new_volume.display_name), + ] + + parsed_args = self.check_parser( + self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volumes_mock.create.assert_called_with( + self.new_volume.size, + None, + None, + self.new_volume.display_name, + None, + None, + None, + None, + None, + None, + None, + ) + + self.assertEqual(self.columns, columns) + 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') + def test_volume_create_with_bootable_and_readonly_fail( + self, mock_error): + + self.volumes_mock.set_bootable.side_effect = ( + exceptions.CommandError()) + + self.volumes_mock.update_readonly_flag.side_effect = ( + exceptions.CommandError()) + + arglist = [ + '--bootable', + '--read-only', + '--size', str(self.new_volume.size), + self.new_volume.display_name, + ] + verifylist = [ + ('bootable', True), + ('non_bootable', False), + ('read_only', True), + ('read_write', False), + ('size', self.new_volume.size), + ('name', self.new_volume.display_name), + ] + + parsed_args = self.check_parser( + self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volumes_mock.create.assert_called_with( + self.new_volume.size, + None, + None, + self.new_volume.display_name, + None, + None, + None, + None, + None, + None, + None, + ) + + self.assertEqual(2, mock_error.call_count) + self.assertEqual(self.columns, columns) + 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) + def test_volume_create_without_size(self): arglist = [ self.new_volume.display_name, diff --git a/openstackclient/tests/unit/volume/v2/test_volume.py b/openstackclient/tests/unit/volume/v2/test_volume.py index 417283425a..1529c2e25b 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume.py +++ b/openstackclient/tests/unit/volume/v2/test_volume.py @@ -450,6 +450,154 @@ def test_volume_create_with_snapshot(self): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, data) + def test_volume_create_with_bootable_and_readonly(self): + arglist = [ + '--bootable', + '--read-only', + '--size', str(self.new_volume.size), + self.new_volume.name, + ] + verifylist = [ + ('bootable', True), + ('non_bootable', False), + ('read_only', True), + ('read_write', False), + ('size', self.new_volume.size), + ('name', self.new_volume.name), + ] + + parsed_args = self.check_parser( + self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volumes_mock.create.assert_called_with( + size=self.new_volume.size, + snapshot_id=None, + name=self.new_volume.name, + description=None, + volume_type=None, + user_id=None, + project_id=None, + availability_zone=None, + metadata=None, + imageRef=None, + source_volid=None, + consistencygroup_id=None, + source_replica=None, + multiattach=False, + scheduler_hints=None, + ) + + self.assertEqual(self.columns, columns) + 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) + + def test_volume_create_with_nonbootable_and_readwrite(self): + arglist = [ + '--non-bootable', + '--read-write', + '--size', str(self.new_volume.size), + self.new_volume.name, + ] + verifylist = [ + ('bootable', False), + ('non_bootable', True), + ('read_only', False), + ('read_write', True), + ('size', self.new_volume.size), + ('name', self.new_volume.name), + ] + + parsed_args = self.check_parser( + self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volumes_mock.create.assert_called_with( + size=self.new_volume.size, + snapshot_id=None, + name=self.new_volume.name, + description=None, + volume_type=None, + user_id=None, + project_id=None, + availability_zone=None, + metadata=None, + imageRef=None, + source_volid=None, + consistencygroup_id=None, + source_replica=None, + multiattach=False, + scheduler_hints=None, + ) + + self.assertEqual(self.columns, columns) + 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') + def test_volume_create_with_bootable_and_readonly_fail( + self, mock_error): + + self.volumes_mock.set_bootable.side_effect = ( + exceptions.CommandError()) + + self.volumes_mock.update_readonly_flag.side_effect = ( + exceptions.CommandError()) + + arglist = [ + '--bootable', + '--read-only', + '--size', str(self.new_volume.size), + self.new_volume.name, + ] + verifylist = [ + ('bootable', True), + ('non_bootable', False), + ('read_only', True), + ('read_write', False), + ('size', self.new_volume.size), + ('name', self.new_volume.name), + ] + + parsed_args = self.check_parser( + self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volumes_mock.create.assert_called_with( + size=self.new_volume.size, + snapshot_id=None, + name=self.new_volume.name, + description=None, + volume_type=None, + user_id=None, + project_id=None, + availability_zone=None, + metadata=None, + imageRef=None, + source_volid=None, + consistencygroup_id=None, + source_replica=None, + multiattach=False, + scheduler_hints=None, + ) + + self.assertEqual(2, mock_error.call_count) + self.assertEqual(self.columns, columns) + 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) + def test_volume_create_with_source_replicated(self): self.volumes_mock.get.return_value = self.new_volume arglist = [ diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index 0087bad480..739484dfc0 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -114,6 +114,28 @@ def get_parser(self, prog_name): help=_('Set a property on this volume ' '(repeat option to set multiple properties)'), ) + bootable_group = parser.add_mutually_exclusive_group() + bootable_group.add_argument( + "--bootable", + action="store_true", + help=_("Mark volume as bootable") + ) + bootable_group.add_argument( + "--non-bootable", + action="store_true", + help=_("Mark volume as non-bootable (default)") + ) + readonly_group = parser.add_mutually_exclusive_group() + readonly_group.add_argument( + "--read-only", + action="store_true", + help=_("Set volume to read-only access mode") + ) + readonly_group.add_argument( + "--read-write", + action="store_true", + help=_("Set volume to read-write access mode (default)") + ) return parser @@ -166,6 +188,22 @@ def take_action(self, parsed_args): parsed_args.property, image, ) + + if parsed_args.bootable or parsed_args.non_bootable: + try: + volume_client.volumes.set_bootable( + volume.id, parsed_args.bootable) + except Exception as e: + LOG.error(_("Failed to set volume bootable property: %s"), e) + if parsed_args.read_only or parsed_args.read_write: + try: + volume_client.volumes.update_readonly_flag( + volume.id, + parsed_args.read_only) + except Exception as e: + LOG.error(_("Failed to set volume read-only access " + "mode flag: %s"), e) + # Map 'metadata' column to 'properties' volume._info.update( { diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index 80abfb5501..301bf5e41f 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -132,6 +132,28 @@ def get_parser(self, prog_name): help=_("Allow volume to be attached more than once " "(default to False)") ) + bootable_group = parser.add_mutually_exclusive_group() + bootable_group.add_argument( + "--bootable", + action="store_true", + help=_("Mark volume as bootable") + ) + bootable_group.add_argument( + "--non-bootable", + action="store_true", + help=_("Mark volume as non-bootable (default)") + ) + readonly_group = parser.add_mutually_exclusive_group() + readonly_group.add_argument( + "--read-only", + action="store_true", + help=_("Set volume to read-only access mode") + ) + readonly_group.add_argument( + "--read-write", + action="store_true", + help=_("Set volume to read-write access mode (default)") + ) return parser def take_action(self, parsed_args): @@ -199,6 +221,22 @@ def take_action(self, parsed_args): multiattach=parsed_args.multi_attach, scheduler_hints=parsed_args.hint, ) + + if parsed_args.bootable or parsed_args.non_bootable: + try: + volume_client.volumes.set_bootable( + volume.id, parsed_args.bootable) + except Exception as e: + LOG.error(_("Failed to set volume bootable property: %s"), e) + if parsed_args.read_only or parsed_args.read_write: + try: + volume_client.volumes.update_readonly_flag( + volume.id, + parsed_args.read_only) + except Exception as e: + LOG.error(_("Failed to set volume read-only access " + "mode flag: %s"), e) + # Remove key links from being displayed volume._info.update( { diff --git a/releasenotes/notes/bp-cinder-command-support-ff7acc531baae8c3.yaml b/releasenotes/notes/bp-cinder-command-support-ff7acc531baae8c3.yaml new file mode 100644 index 0000000000..d8126b1e8c --- /dev/null +++ b/releasenotes/notes/bp-cinder-command-support-ff7acc531baae8c3.yaml @@ -0,0 +1,5 @@ +--- +features: + - Add ``--bootable``, ``--non-bootable``, ``--read-only`` and ``--read-write`` + options to ``volume create`` command. + [Blueprint `cinder-command-support `_] From d083ddb12f4b8eb0d72bb4ff60113cd0904d8c1d Mon Sep 17 00:00:00 2001 From: "jiahui.qiang" Date: Sun, 13 Nov 2016 06:15:13 +0800 Subject: [PATCH 1400/3095] Add --default option to "volume type list" Add "--default" option to volume v2's "type list" command, it will show which volume type the volume service has set as default. Implements: bp cinder-command-support Change-Id: Iae7ebc633ebe5554cc88390a84361887ec211fb2 --- doc/source/command-objects/volume-type.rst | 8 ++++- .../functional/volume/v2/test_volume_type.py | 5 ++++ .../tests/unit/volume/v2/test_type.py | 27 ++++++++++++++++- openstackclient/volume/v2/volume_type.py | 29 ++++++++++++------- ...nder-command-support-82dbadef47454c18.yaml | 7 +++++ 5 files changed, 64 insertions(+), 12 deletions(-) create mode 100644 releasenotes/notes/bp-cinder-command-support-82dbadef47454c18.yaml diff --git a/doc/source/command-objects/volume-type.rst b/doc/source/command-objects/volume-type.rst index d93532c731..72422b5c25 100644 --- a/doc/source/command-objects/volume-type.rst +++ b/doc/source/command-objects/volume-type.rst @@ -87,7 +87,7 @@ List volume types os volume type list [--long] - [--public | --private] + [--default | --public | --private] .. option:: --long @@ -105,6 +105,12 @@ List volume types *Volume version 2 only* +.. option:: --default + + List the default volume type + + *Volume version 2 only* + volume type set --------------- diff --git a/openstackclient/tests/functional/volume/v2/test_volume_type.py b/openstackclient/tests/functional/volume/v2/test_volume_type.py index d8bd3a96c1..b4df5b2d59 100644 --- a/openstackclient/tests/functional/volume/v2/test_volume_type.py +++ b/openstackclient/tests/functional/volume/v2/test_volume_type.py @@ -42,6 +42,11 @@ def test_volume_type_list(self): raw_output = self.openstack('volume type list' + opts) self.assertIn(self.NAME, raw_output) + def test_volume_type_list_default(self): + opts = self.get_opts(self.HEADERS) + raw_output = self.openstack('volume type list --default' + opts) + self.assertEqual("lvmdriver-1\n", raw_output) + def test_volume_type_show(self): opts = self.get_opts(self.FIELDS) raw_output = self.openstack('volume type show ' + self.NAME + opts) diff --git a/openstackclient/tests/unit/volume/v2/test_type.py b/openstackclient/tests/unit/volume/v2/test_type.py index 325872d734..0d556e13f7 100644 --- a/openstackclient/tests/unit/volume/v2/test_type.py +++ b/openstackclient/tests/unit/volume/v2/test_type.py @@ -172,7 +172,11 @@ class TestTypeList(TestType): "Description", "Properties" ] - + data_with_default_type = [( + volume_types[0].id, + volume_types[0].name, + True + )] data = [] for t in volume_types: data.append(( @@ -194,6 +198,7 @@ def setUp(self): super(TestTypeList, self).setUp() self.types_mock.list.return_value = self.volume_types + self.types_mock.default.return_value = self.volume_types[0] # get the command to test self.cmd = volume_type.ListVolumeType(self.app, None) @@ -203,6 +208,7 @@ def test_type_list_without_options(self): ("long", False), ("private", False), ("public", False), + ("default", False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -220,6 +226,7 @@ def test_type_list_with_options(self): ("long", True), ("private", False), ("public", True), + ("default", False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -236,6 +243,7 @@ def test_type_list_with_private_option(self): ("long", False), ("private", True), ("public", False), + ("default", False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -244,6 +252,23 @@ def test_type_list_with_private_option(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + def test_type_list_with_default_option(self): + arglist = [ + "--default", + ] + verifylist = [ + ("long", False), + ("private", False), + ("public", False), + ("default", True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.types_mock.default.assert_called_once_with() + self.assertEqual(self.columns, columns) + self.assertEqual(self.data_with_default_type, list(data)) + class TestTypeSet(TestType): diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index 42ebb53e99..46466783f8 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -160,8 +160,15 @@ def get_parser(self, prog_name): '--long', action='store_true', default=False, - help=_('List additional fields in output')) + help=_('List additional fields in output') + ) public_group = parser.add_mutually_exclusive_group() + public_group.add_argument( + "--default", + action='store_true', + default=False, + help=_('List the default volume type') + ) public_group.add_argument( "--public", action="store_true", @@ -175,6 +182,7 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume if parsed_args.long: columns = ['ID', 'Name', 'Is Public', 'Description', 'Extra Specs'] column_headers = [ @@ -182,14 +190,16 @@ def take_action(self, parsed_args): else: columns = ['ID', 'Name', 'Is Public'] column_headers = columns - - is_public = None - if parsed_args.public: - is_public = True - if parsed_args.private: - is_public = False - data = self.app.client_manager.volume.volume_types.list( - is_public=is_public) + if parsed_args.default: + data = [volume_client.volume_types.default()] + else: + is_public = None + if parsed_args.public: + is_public = True + if parsed_args.private: + is_public = False + data = volume_client.volume_types.list( + is_public=is_public) return (column_headers, (utils.get_item_properties( s, columns, @@ -240,7 +250,6 @@ def take_action(self, parsed_args): volume_type = utils.find_resource( volume_client.volume_types, parsed_args.volume_type) - result = 0 kwargs = {} if parsed_args.name: diff --git a/releasenotes/notes/bp-cinder-command-support-82dbadef47454c18.yaml b/releasenotes/notes/bp-cinder-command-support-82dbadef47454c18.yaml new file mode 100644 index 0000000000..0ced2ce227 --- /dev/null +++ b/releasenotes/notes/bp-cinder-command-support-82dbadef47454c18.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Add ``--default`` option to ``volume type list`` command, in + order to show which volume type the volume sets as it's + default. + [Blueprint :oscbp:`cinder-command-support`] From 4dc78e4265f60356ea453c7f52fcdaeecec58fcb Mon Sep 17 00:00:00 2001 From: "zhiyong.dai" Date: Sun, 4 Dec 2016 13:11:24 +0800 Subject: [PATCH 1401/3095] Add "consistency group set" command Add "consistency group set" command in volume v2 (v2 only). Change-Id: I53116015388b7a4b0e15813f52c1246166bb0fc1 Implements: bp cinder-command-support Partial-Bug: #1613964 --- .../command-objects/consistency-group.rst | 27 +++++++- .../unit/volume/v2/test_consistency_group.py | 64 +++++++++++++++++++ .../volume/v2/consistency_group.py | 39 ++++++++++- .../notes/bug-1613964-86e0afe0e012a758.yaml | 4 ++ setup.cfg | 1 + 5 files changed, 133 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/bug-1613964-86e0afe0e012a758.yaml diff --git a/doc/source/command-objects/consistency-group.rst b/doc/source/command-objects/consistency-group.rst index 46682a56e4..cd972035f3 100644 --- a/doc/source/command-objects/consistency-group.rst +++ b/doc/source/command-objects/consistency-group.rst @@ -82,6 +82,32 @@ List consistency groups. List additional fields in output +consistency group set +--------------------- + +Set consistency group properties. + +.. program:: consistency group set + .. code:: bash + + os consistency group set + [--name ] + [--description ] + + +.. option:: --name + + New consistency group name + +.. option:: --description + + New consistency group description + +.. _consistency_group_set-consistency-group: +.. describe:: + + Consistency group to modify (name or ID) + consistency group show ---------------------- @@ -97,4 +123,3 @@ Display consistency group details. .. describe:: Consistency group to display (name or ID) - diff --git a/openstackclient/tests/unit/volume/v2/test_consistency_group.py b/openstackclient/tests/unit/volume/v2/test_consistency_group.py index 5beb6ef28d..8a997e18fb 100644 --- a/openstackclient/tests/unit/volume/v2/test_consistency_group.py +++ b/openstackclient/tests/unit/volume/v2/test_consistency_group.py @@ -355,6 +355,70 @@ def test_consistency_group_list_with_long(self): self.assertEqual(self.data_long, list(data)) +class TestConsistencyGroupSet(TestConsistencyGroup): + + consistency_group = ( + volume_fakes.FakeConsistencyGroup.create_one_consistency_group()) + + def setUp(self): + super(TestConsistencyGroupSet, self).setUp() + + self.consistencygroups_mock.get.return_value = ( + self.consistency_group) + # Get the command object to test + self.cmd = consistency_group.SetConsistencyGroup(self.app, None) + + def test_consistency_group_set_name(self): + new_name = 'new_name' + arglist = [ + '--name', new_name, + self.consistency_group.id, + ] + verifylist = [ + ('name', new_name), + ('description', None), + ('consistency_group', self.consistency_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'name': new_name, + } + self.consistencygroups_mock.update.assert_called_once_with( + self.consistency_group.id, + **kwargs + ) + self.assertIsNone(result) + + def test_consistency_group_set_description(self): + new_description = 'new_description' + arglist = [ + '--description', new_description, + self.consistency_group.id, + ] + verifylist = [ + ('name', None), + ('description', new_description), + ('consistency_group', self.consistency_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'description': new_description, + } + self.consistencygroups_mock.update.assert_called_once_with( + self.consistency_group.id, + **kwargs + ) + self.assertIsNone(result) + + class TestConsistencyGroupShow(TestConsistencyGroup): columns = ( 'availability_zone', diff --git a/openstackclient/volume/v2/consistency_group.py b/openstackclient/volume/v2/consistency_group.py index 661bcbe51f..f77da59b91 100644 --- a/openstackclient/volume/v2/consistency_group.py +++ b/openstackclient/volume/v2/consistency_group.py @@ -151,7 +151,7 @@ def get_parser(self, prog_name): parser.add_argument( '--all-projects', action="store_true", - help=_('Show detail for all projects. Admin only. ' + help=_('Show details for all projects. Admin only. ' '(defaults to False)') ) parser.add_argument( @@ -180,6 +180,43 @@ def take_action(self, parsed_args): for s in consistency_groups)) +class SetConsistencyGroup(command.Command): + _description = _("Set consistency group properties") + + def get_parser(self, prog_name): + parser = super(SetConsistencyGroup, self).get_parser(prog_name) + parser.add_argument( + 'consistency_group', + metavar='', + help=_('Consistency group to modify (name or ID)') + ) + parser.add_argument( + '--name', + metavar='', + help=_('New consistency group name'), + ) + parser.add_argument( + '--description', + metavar='', + help=_('New consistency group description'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + kwargs = {} + if parsed_args.name: + kwargs['name'] = parsed_args.name + if parsed_args.description: + kwargs['description'] = parsed_args.description + if kwargs: + consistency_group_id = utils.find_resource( + volume_client.consistencygroups, + parsed_args.consistency_group).id + volume_client.consistencygroups.update( + consistency_group_id, **kwargs) + + class ShowConsistencyGroup(command.ShowOne): _description = _("Display consistency group details.") diff --git a/releasenotes/notes/bug-1613964-86e0afe0e012a758.yaml b/releasenotes/notes/bug-1613964-86e0afe0e012a758.yaml new file mode 100644 index 0000000000..461c2f6896 --- /dev/null +++ b/releasenotes/notes/bug-1613964-86e0afe0e012a758.yaml @@ -0,0 +1,4 @@ +--- +features: + - Add ``consistency group set`` command in volume v2. + [Bug `1613964 `_] diff --git a/setup.cfg b/setup.cfg index 2bbbb5ae72..7f76cd44a2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -518,6 +518,7 @@ openstack.volume.v2 = consistency_group_create = openstackclient.volume.v2.consistency_group:CreateConsistencyGroup consistency_group_delete = openstackclient.volume.v2.consistency_group:DeleteConsistencyGroup consistency_group_list = openstackclient.volume.v2.consistency_group:ListConsistencyGroup + consistency_group_set = openstackclient.volume.v2.consistency_group:SetConsistencyGroup consistency_group_show = openstackclient.volume.v2.consistency_group:ShowConsistencyGroup consistency_group_snapshot_create = openstackclient.volume.v2.consistency_group_snapshot:CreateConsistencyGroupSnapshot From 3e9109bc7c9511b45568b299da897d977852d76d Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Wed, 7 Dec 2016 11:32:44 +0800 Subject: [PATCH 1402/3095] Add "consistency-group-snapshot" option to consistency group create Add "consistency-group-snapshot" option to "consistency group create" command to support for creating consistency group from existing consistency group snapshot Implements: bp cinder-command-support Partial-Bug: #1613964 Change-Id: I54c265d38299f4973945ba99e30042bcf47859c0 --- .../command-objects/consistency-group.rst | 6 ++- .../unit/volume/v2/test_consistency_group.py | 39 +++++++++++++++++++ .../volume/v2/consistency_group.py | 23 ++++++++--- 3 files changed, 61 insertions(+), 7 deletions(-) diff --git a/doc/source/command-objects/consistency-group.rst b/doc/source/command-objects/consistency-group.rst index 46682a56e4..05b52dd66e 100644 --- a/doc/source/command-objects/consistency-group.rst +++ b/doc/source/command-objects/consistency-group.rst @@ -13,7 +13,7 @@ Create new consistency group. .. code:: bash os consistency group create - --volume-type | --consistency-group-source + --volume-type | --consistency-group-source | --consistency-group-snapshot [--description ] [--availability-zone ] [] @@ -26,6 +26,10 @@ Create new consistency group. Existing consistency group (name or ID) +.. option:: --consistency-group-snapshot + + Existing consistency group snapshot (name or ID) + .. option:: --description Description of this consistency group diff --git a/openstackclient/tests/unit/volume/v2/test_consistency_group.py b/openstackclient/tests/unit/volume/v2/test_consistency_group.py index 5beb6ef28d..0e2f162e98 100644 --- a/openstackclient/tests/unit/volume/v2/test_consistency_group.py +++ b/openstackclient/tests/unit/volume/v2/test_consistency_group.py @@ -32,6 +32,10 @@ def setUp(self): self.app.client_manager.volume.consistencygroups) self.consistencygroups_mock.reset_mock() + self.cgsnapshots_mock = ( + self.app.client_manager.volume.cgsnapshots) + self.cgsnapshots_mock.reset_mock() + self.types_mock = self.app.client_manager.volume.volume_types self.types_mock.reset_mock() @@ -41,6 +45,11 @@ class TestConsistencyGroupCreate(TestConsistencyGroup): volume_type = volume_fakes.FakeType.create_one_type() new_consistency_group = ( volume_fakes.FakeConsistencyGroup.create_one_consistency_group()) + consistency_group_snapshot = ( + volume_fakes. + FakeConsistencyGroupSnapshot. + create_one_consistency_group_snapshot() + ) columns = ( 'availability_zone', @@ -70,6 +79,8 @@ def setUp(self): self.consistencygroups_mock.get.return_value = ( self.new_consistency_group) self.types_mock.get.return_value = self.volume_type + self.cgsnapshots_mock.get.return_value = ( + self.consistency_group_snapshot) # Get the command object to test self.cmd = consistency_group.CreateConsistencyGroup(self.app, None) @@ -164,6 +175,34 @@ def test_consistency_group_create_from_source(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_consistency_group_create_from_snapshot(self): + arglist = [ + '--consistency-group-snapshot', self.consistency_group_snapshot.id, + '--description', self.new_consistency_group.description, + self.new_consistency_group.name, + ] + verifylist = [ + ('consistency_group_snapshot', self.consistency_group_snapshot.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.cgsnapshots_mock.get.assert_called_once_with( + self.consistency_group_snapshot.id) + self.consistencygroups_mock.create_from_src.assert_called_with( + self.consistency_group_snapshot.id, + None, + name=self.new_consistency_group.name, + description=self.new_consistency_group.description, + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + class TestConsistencyGroupDelete(TestConsistencyGroup): diff --git a/openstackclient/volume/v2/consistency_group.py b/openstackclient/volume/v2/consistency_group.py index 661bcbe51f..fbeae745ea 100644 --- a/openstackclient/volume/v2/consistency_group.py +++ b/openstackclient/volume/v2/consistency_group.py @@ -94,6 +94,11 @@ def get_parser(self, prog_name): metavar="", help=_("Existing consistency group (name or ID)") ) + exclusive_group.add_argument( + "--consistency-group-snapshot", + metavar="", + help=_("Existing consistency group snapshot (name or ID)") + ) parser.add_argument( "--description", metavar="", @@ -120,17 +125,23 @@ def take_action(self, parsed_args): description=parsed_args.description, availability_zone=parsed_args.availability_zone ) - elif parsed_args.consistency_group_source: + else: 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_id = None consistency_group_snapshot = None - # TODO(Huanxuan Ao): Support for creating from consistency group - # snapshot after adding "consistency_group_snapshot" resource + if parsed_args.consistency_group_source: + consistency_group_id = utils.find_resource( + volume_client.consistencygroups, + parsed_args.consistency_group_source).id + elif parsed_args.consistency_group_snapshot: + consistency_group_snapshot = utils.find_resource( + volume_client.cgsnapshots, + parsed_args.consistency_group_snapshot).id + consistency_group = ( volume_client.consistencygroups.create_from_src( consistency_group_snapshot, From 6da1f97acd4aeb3b8b7589f478e48d0ab8f32dfe Mon Sep 17 00:00:00 2001 From: "zhiyong.dai" Date: Wed, 7 Dec 2016 18:33:34 +0800 Subject: [PATCH 1403/3095] Trivial: update volume-qos.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before the ".. describe",some codes are left out. I add them in this patch. In addition, I change "[--property ] to [--property [...] ] Change-Id: I37ae0ba53e2a2d43a4806b318c7776ff2260fd1d --- doc/source/command-objects/volume-qos.rst | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/doc/source/command-objects/volume-qos.rst b/doc/source/command-objects/volume-qos.rst index 54e509662d..620c26bafb 100644 --- a/doc/source/command-objects/volume-qos.rst +++ b/doc/source/command-objects/volume-qos.rst @@ -16,6 +16,7 @@ Associate a QoS specification to a volume type +.. _volume_qos_associate: .. describe:: QoS specification to modify (name or ID) @@ -45,6 +46,7 @@ Create new QoS Specification Set a property on this QoS specification (repeat option to set multiple properties) +.. _volume_qos_create-name: .. describe:: New QoS specification name @@ -65,6 +67,7 @@ Delete QoS specification Allow to delete in-use QoS specification(s) +.. _volume_qos_delete-qos-spec: .. describe:: QoS specification(s) to delete (name or ID) @@ -89,6 +92,7 @@ Disassociate a QoS specification from a volume type Disassociate the QoS from every volume type +.. _volume_qos_disassociate-qos-spec: .. describe:: QoS specification to modify (name or ID) @@ -119,6 +123,7 @@ Set QoS specification properties Property to add or modify for this QoS specification (repeat option to set multiple properties) +.. _volume_qos_set-qos-spec: .. describe:: QoS specification to modify (name or ID) @@ -134,6 +139,7 @@ Display QoS specification details os volume qos show +.. _volume_qos_show-qos-spec: .. describe:: QoS specification to display (name or ID) @@ -147,13 +153,14 @@ Unset QoS specification properties .. code:: bash os volume qos unset - [--property ] + [--property [...] ] .. option:: --property Property to remove from QoS specification (repeat option to remove multiple properties) +.. _volume_qos_unset-qos-spec: .. describe:: QoS specification to modify (name or ID) From 035a87051c73eb5e6c4de3a12a4d50bd6acd1714 Mon Sep 17 00:00:00 2001 From: "jiahui.qiang" Date: Thu, 8 Dec 2016 03:00:45 +0800 Subject: [PATCH 1404/3095] Modified API calls in os usage We usually call v3 keystoneclient APIs for V2 OSC, this patch modified 'tenants' to 'projects'. Change-Id: Idbf74f098cd1386aa675c081480b89bbc11c8534 --- openstackclient/compute/v2/usage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/compute/v2/usage.py b/openstackclient/compute/v2/usage.py index 89601ae341..3edcffe460 100644 --- a/openstackclient/compute/v2/usage.py +++ b/openstackclient/compute/v2/usage.py @@ -89,7 +89,7 @@ def _format_project(project): # Cache the project list project_cache = {} try: - for p in self.app.client_manager.identity.tenants.list(): + for p in self.app.client_manager.identity.projects.list(): project_cache[p.id] = p except Exception: # Just forget it if there's any trouble From bbfd8cb46bc7493971173f1cbc774ef4697ddfe4 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Mon, 4 Jul 2016 10:18:45 +0800 Subject: [PATCH 1405/3095] Add '--force' option to 'volume snapshot delete' command Add '--force' option to 'volume snapshot delete' command in volume v2 (v2 only) to allow delete in state other than error or available. Change-Id: Ie8991e9a630d7c7e9ac6c6870aed787bbcebacf2 Closes-Bug: #1597195 --- .../command-objects/volume-snapshot.rst | 5 ++++ .../tests/unit/volume/v2/test_snapshot.py | 23 ++++++++++++++++--- openstackclient/volume/v2/volume_snapshot.py | 9 +++++++- .../notes/bug-1597195-54ff1ecf381899f6.yaml | 6 +++++ 4 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/bug-1597195-54ff1ecf381899f6.yaml diff --git a/doc/source/command-objects/volume-snapshot.rst b/doc/source/command-objects/volume-snapshot.rst index b84601f464..af93217726 100644 --- a/doc/source/command-objects/volume-snapshot.rst +++ b/doc/source/command-objects/volume-snapshot.rst @@ -51,8 +51,13 @@ Delete volume snapshot(s) .. code:: bash os volume snapshot delete + [--force] [ ...] +.. option:: --force + + Attempt forced removal of snapshot(s), regardless of state (defaults to False) + .. _volume_snapshot_delete-snapshot: .. describe:: diff --git a/openstackclient/tests/unit/volume/v2/test_snapshot.py b/openstackclient/tests/unit/volume/v2/test_snapshot.py index b67dd6ebf5..1d1693addb 100644 --- a/openstackclient/tests/unit/volume/v2/test_snapshot.py +++ b/openstackclient/tests/unit/volume/v2/test_snapshot.py @@ -179,7 +179,24 @@ def test_snapshot_delete(self): result = self.cmd.take_action(parsed_args) self.snapshots_mock.delete.assert_called_with( - self.snapshots[0].id) + self.snapshots[0].id, False) + self.assertIsNone(result) + + def test_snapshot_delete_with_force(self): + arglist = [ + '--force', + self.snapshots[0].id + ] + verifylist = [ + ('force', True), + ("snapshots", [self.snapshots[0].id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.snapshots_mock.delete.assert_called_with( + self.snapshots[0].id, True) self.assertIsNone(result) def test_delete_multiple_snapshots(self): @@ -195,7 +212,7 @@ def test_delete_multiple_snapshots(self): calls = [] for s in self.snapshots: - calls.append(call(s.id)) + calls.append(call(s.id, False)) self.snapshots_mock.delete.assert_has_calls(calls) self.assertIsNone(result) @@ -226,7 +243,7 @@ def test_delete_multiple_snapshots_with_exception(self): self.assertEqual(2, find_mock.call_count) self.snapshots_mock.delete.assert_called_once_with( - self.snapshots[0].id + self.snapshots[0].id, False ) diff --git a/openstackclient/volume/v2/volume_snapshot.py b/openstackclient/volume/v2/volume_snapshot.py index 43f30326ed..5fdeca3acd 100644 --- a/openstackclient/volume/v2/volume_snapshot.py +++ b/openstackclient/volume/v2/volume_snapshot.py @@ -98,6 +98,12 @@ def get_parser(self, prog_name): nargs="+", help=_("Snapshot(s) to delete (name or ID)") ) + parser.add_argument( + '--force', + action='store_true', + help=_("Attempt forced removal of snapshot(s), " + "regardless of state (defaults to False)") + ) return parser def take_action(self, parsed_args): @@ -108,7 +114,8 @@ def take_action(self, parsed_args): try: snapshot_id = utils.find_resource( volume_client.volume_snapshots, i).id - volume_client.volume_snapshots.delete(snapshot_id) + volume_client.volume_snapshots.delete( + snapshot_id, parsed_args.force) except Exception as e: result += 1 LOG.error(_("Failed to delete snapshot with " diff --git a/releasenotes/notes/bug-1597195-54ff1ecf381899f6.yaml b/releasenotes/notes/bug-1597195-54ff1ecf381899f6.yaml new file mode 100644 index 0000000000..ad45762027 --- /dev/null +++ b/releasenotes/notes/bug-1597195-54ff1ecf381899f6.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--force`` option to ``volume snapshot delete`` command to allow delete + in state other than error or available. + [Bug `1597195 `_] From 841616f729836230c0762566ba5c2bfceb14a2a0 Mon Sep 17 00:00:00 2001 From: Dougal Matthews Date: Thu, 8 Dec 2016 13:15:14 +0000 Subject: [PATCH 1406/3095] Correct missspellings of secret Change-Id: Ia00d5cf3a32f534d1c0f40d5b295610f88a777d4 --- doc/source/humaninterfaceguide.rst | 6 +++--- doc/source/man/openstack.rst | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/source/humaninterfaceguide.rst b/doc/source/humaninterfaceguide.rst index 400ccfcf6f..b43c71a9e9 100644 --- a/doc/source/humaninterfaceguide.rst +++ b/doc/source/humaninterfaceguide.rst @@ -2,7 +2,7 @@ Human Interface Guide ===================== -*Note: This page covers the OpenStackClient CLI only but looks familiar +*Note: This page covers the OpenStackClient CLI only but looks familiar because it was derived from the Horizon HIG.* Overview @@ -297,7 +297,7 @@ Using global options: .. code-block:: bash - $ openstack --os-tenant-name ExampleCo --os-username demo --os-password secrete --os-auth-url http://localhost:5000:/v2.0 server show appweb01 + $ openstack --os-tenant-name ExampleCo --os-username demo --os-password secret --os-auth-url http://localhost:5000:/v2.0 server show appweb01 +------------------------+-----------------------------------------------------+ | Property | Value | +------------------------+-----------------------------------------------------+ @@ -319,7 +319,7 @@ Using environment variables: $ export OS_TENANT_NAME=ExampleCo $ export OS_USERNAME=demo - $ export OS_PASSWORD=secrete + $ export OS_PASSWORD=secret $ export OS_AUTH_URL=http://localhost:5000:/v2.0 $ openstack server show appweb01 +------------------------+-----------------------------------------------------+ diff --git a/doc/source/man/openstack.rst b/doc/source/man/openstack.rst index aeed2cadba..5793c6ee85 100644 --- a/doc/source/man/openstack.rst +++ b/doc/source/man/openstack.rst @@ -288,7 +288,7 @@ Show the detailed information for server ``appweb01``:: openstack \ --os-project-name ExampleCo \ - --os-username demo --os-password secrete \ + --os-username demo --os-password secret \ --os-auth-url http://localhost:5000:/v2.0 \ server show appweb01 From 47716d1ad32b7c879f6b5736687be1553f6fbfb6 Mon Sep 17 00:00:00 2001 From: songminglong Date: Fri, 9 Dec 2016 14:23:26 +0800 Subject: [PATCH 1407/3095] Add extra filtering options to floating ip list The patch adds filtering '--long', 'status', '--project', '--project-domain' and '--router' options to floating ip list command. Closes-Bug: #1614379 Partially-Implements: blueprint network-commands-options Change-Id: I2a02cf23845ff435927d8b481f77249915bd94dc --- doc/source/command-objects/floating-ip.rst | 35 ++++++ openstackclient/network/v2/floating_ip.py | 54 +++++++++ .../tests/unit/network/v2/test_floating_ip.py | 112 ++++++++++++++++++ .../notes/bug-1614379-d8e2815804d53cef.yaml | 6 + 4 files changed, 207 insertions(+) create mode 100644 releasenotes/notes/bug-1614379-d8e2815804d53cef.yaml diff --git a/doc/source/command-objects/floating-ip.rst b/doc/source/command-objects/floating-ip.rst index 6a5e38b063..57dc0ccbab 100644 --- a/doc/source/command-objects/floating-ip.rst +++ b/doc/source/command-objects/floating-ip.rst @@ -89,6 +89,10 @@ List floating IP(s) [--network ] [--port ] [--fixed-ip-address ] + [--long] + [--status ] + [--project [--project-domain ]] + [--router ] .. option:: --network @@ -108,6 +112,37 @@ List floating IP(s) *Network version 2 only* +.. option:: --long + + List additional fields in output + + *Network version 2 only* + +.. option:: --status + + List floating IP(s) according to given status ('ACTIVE', 'DOWN') + + *Network version 2 only* + +.. option:: --project + + List floating IP(s) according to given project (name or ID) + + *Network version 2 only* + +.. option:: --project-domain + + Domain the project belongs to (name or ID). This can + be used in case collisions between project names exist. + + *Network version 2 only* + +.. option:: --router + + List floating IP(s) according to given router (name or ID) + + *Network version 2 only* + floating ip show ---------------- diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index 7b8374e2ae..8202b3fae2 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -219,6 +219,8 @@ def take_action_compute(self, client, parsed_args): class ListFloatingIP(common.NetworkAndComputeLister): + # TODO(songminglong): Use SDK resource mapped attribute names once + # the OSC minimum requirements include SDK 1.0 _description = _("List floating IP(s)") def update_parser_network(self, parser): @@ -240,11 +242,38 @@ def update_parser_network(self, parser): help=_("List floating IP(s) according to " "given fixed IP address") ) + parser.add_argument( + '--long', + action='store_true', + default=False, + help=_("List additional fields in output") + ) + parser.add_argument( + '--status', + metavar='', + choices=['ACTIVE', 'DOWN'], + help=_("List floating IP(s) according to " + "given status ('ACTIVE', 'DOWN')") + ) + parser.add_argument( + '--project', + metavar='', + help=_("List floating IP(s) according to " + "given project (name or ID)") + ) + identity_common.add_project_domain_option_to_parser(parser) + parser.add_argument( + '--router', + metavar='', + help=_("List floating IP(s) according to " + "given router (name or ID)") + ) return parser def take_action_network(self, client, parsed_args): network_client = self.app.client_manager.network + identity_client = self.app.client_manager.identity columns = ( 'id', @@ -262,6 +291,17 @@ def take_action_network(self, client, parsed_args): 'Floating Network', 'Project', ) + if parsed_args.long: + columns = columns + ( + 'router_id', + 'status', + 'description', + ) + headers = headers + ( + 'Router', + 'Status', + 'Description', + ) query = {} @@ -275,6 +315,20 @@ def take_action_network(self, client, parsed_args): query['port_id'] = port.id if parsed_args.fixed_ip_address is not None: query['fixed_ip_address'] = parsed_args.fixed_ip_address + if parsed_args.status: + query['status'] = parsed_args.status + if parsed_args.project is not None: + project = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ) + query['tenant_id'] = project.id + query['project_id'] = project.id + if parsed_args.router is not None: + router = network_client.find_router(parsed_args.router, + ignore_missing=False) + query['router_id'] = router.id data = client.ips(**query) diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip.py b/openstackclient/tests/unit/network/v2/test_floating_ip.py index b3d211ba34..63d22bf84c 100644 --- a/openstackclient/tests/unit/network/v2/test_floating_ip.py +++ b/openstackclient/tests/unit/network/v2/test_floating_ip.py @@ -32,7 +32,10 @@ def setUp(self): # Get a shortcut to the network client self.network = self.app.client_manager.network + # Get a shortcut to the ProjectManager Mock self.projects_mock = self.app.client_manager.identity.projects + # Get a shortcut to the DomainManager Mock + self.domains_mock = self.app.client_manager.identity.domains class TestCreateFloatingIPNetwork(TestFloatingIPNetwork): @@ -287,6 +290,9 @@ class TestListFloatingIPNetwork(TestFloatingIPNetwork): fake_port = network_fakes.FakePort.create_one_port({ 'id': 'fake_port_id', }) + fake_router = network_fakes.FakeRouter.create_one_router({ + 'id': 'fake_router_id', + }) columns = ( 'ID', @@ -296,8 +302,14 @@ class TestListFloatingIPNetwork(TestFloatingIPNetwork): 'Floating Network', 'Project', ) + columns_long = columns + ( + 'Router', + 'Status', + 'Description', + ) data = [] + data_long = [] for ip in floating_ips: data.append(( ip.id, @@ -307,6 +319,17 @@ class TestListFloatingIPNetwork(TestFloatingIPNetwork): ip.floating_network_id, ip.tenant_id, )) + data_long.append(( + ip.id, + ip.floating_ip_address, + ip.fixed_ip_address, + ip.port_id, + ip.floating_network_id, + ip.tenant_id, + ip.router_id, + ip.status, + ip.description, + )) def setUp(self): super(TestListFloatingIPNetwork, self).setUp() @@ -314,6 +337,7 @@ def setUp(self): self.network.ips = mock.Mock(return_value=self.floating_ips) self.network.find_network = mock.Mock(return_value=self.fake_network) self.network.find_port = mock.Mock(return_value=self.fake_port) + self.network.find_router = mock.Mock(return_value=self.fake_router) # Get the command object to test self.cmd = floating_ip.ListFloatingIP(self.app, self.namespace) @@ -380,6 +404,94 @@ def test_floating_ip_list_fixed_ip_address(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + def test_floating_ip_list_long(self): + arglist = ['--long', ] + verifylist = [('long', True), ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.ips.assert_called_once_with() + self.assertEqual(self.columns_long, columns) + self.assertEqual(self.data_long, list(data)) + + def test_floating_ip_list_status(self): + arglist = [ + '--status', 'ACTIVE', + '--long', + ] + verifylist = [ + ('status', 'ACTIVE'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.ips.assert_called_once_with(**{ + 'status': 'ACTIVE', + }) + self.assertEqual(self.columns_long, columns) + self.assertEqual(self.data_long, list(data)) + + def test_floating_ip_list_project(self): + project = identity_fakes_v3.FakeProject.create_one_project() + self.projects_mock.get.return_value = project + arglist = [ + '--project', project.id, + ] + verifylist = [ + ('project', project.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'tenant_id': project.id, + 'project_id': project.id, } + + self.network.ips.assert_called_once_with(**filters) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_floating_ip_list_project_domain(self): + project = identity_fakes_v3.FakeProject.create_one_project() + self.projects_mock.get.return_value = project + arglist = [ + '--project', project.id, + '--project-domain', project.domain_id, + ] + verifylist = [ + ('project', project.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'tenant_id': project.id, + 'project_id': project.id, } + + self.network.ips.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_floating_ip_list_router(self): + arglist = [ + '--router', 'fake_router_id', + '--long', + ] + verifylist = [ + ('router', 'fake_router_id'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.ips.assert_called_once_with(**{ + 'router_id': 'fake_router_id', + }) + self.assertEqual(self.columns_long, columns) + self.assertEqual(self.data_long, list(data)) + class TestShowFloatingIPNetwork(TestFloatingIPNetwork): diff --git a/releasenotes/notes/bug-1614379-d8e2815804d53cef.yaml b/releasenotes/notes/bug-1614379-d8e2815804d53cef.yaml new file mode 100644 index 0000000000..e9ca443d7c --- /dev/null +++ b/releasenotes/notes/bug-1614379-d8e2815804d53cef.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--long``, ``--status``, ``--project``, ``--project-domain``, + and ``--router`` options to ``floating ip list`` command. + [Bug `1614379 `_] \ No newline at end of file From 5bc2cf231d756331325bb520d1e6f28b407c493e Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Sun, 11 Dec 2016 00:26:51 -0500 Subject: [PATCH 1408/3095] update plugins documentation - remove cueclient from list of supported plugins, it seems like a dead project to me, see [1] for more details. - remove the ** from watcherclient, it is now listed in global requirements [2]. [1] https://review.openstack.org/#/c/409497/ [2] https://github.com/openstack/requirements/blob/master/global-requirements.txt#L232 Change-Id: Ia49436ccdbdf5d84060062b57e4a6286b5906468 --- doc/source/plugins.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/source/plugins.rst b/doc/source/plugins.rst index a19eea9ad4..0965c12f74 100644 --- a/doc/source/plugins.rst +++ b/doc/source/plugins.rst @@ -25,7 +25,6 @@ The following is a list of projects that are an OpenStackClient plugin. - gnocchiclient\*\* - python-barbicanclient - python-congressclient -- python-cueclient\*\* - python-designateclient - python-heatclient - python-ironicclient @@ -37,7 +36,7 @@ The following is a list of projects that are an OpenStackClient plugin. - python-searchlightclient - python-senlinclient - python-tripleoclient\*\* -- python-watcherclient\*\* +- python-watcherclient - python-zaqarclient \*\* Note that some clients are not listed in global-requirements. From 7357b24d3a63be612aa32c901e15424ff92beca0 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Sun, 25 Sep 2016 11:49:47 +0800 Subject: [PATCH 1409/3095] Add "--remote-source" option to "volume snapshot create" command Add "--remote-source" option to "volume snapshot create" command to support creating snapshot from an existing remote snapshot in volume v2 (v2 only), also add the doc, unit tests and release note. Change-Id: I9e5fad4f0db5b44d528eb6b930edbc816e392c3a Implements: bp cinder-command-support Closes-Bug: #1618676 Co-Authored-By: Sheel Rana --- .../command-objects/volume-snapshot.rst | 9 +++++ .../tests/unit/volume/v2/test_snapshot.py | 28 +++++++++++++ openstackclient/volume/v2/volume_snapshot.py | 39 +++++++++++++++---- .../notes/bug-1618676-04ff0f335b670567.yaml | 5 +++ 4 files changed, 74 insertions(+), 7 deletions(-) create mode 100644 releasenotes/notes/bug-1618676-04ff0f335b670567.yaml diff --git a/doc/source/command-objects/volume-snapshot.rst b/doc/source/command-objects/volume-snapshot.rst index 141e9f78bf..1744903515 100644 --- a/doc/source/command-objects/volume-snapshot.rst +++ b/doc/source/command-objects/volume-snapshot.rst @@ -17,6 +17,7 @@ Create new volume snapshot [--description ] [--force] [--property [...] ] + [--remote-source [...]] .. option:: --volume @@ -37,6 +38,14 @@ Create new volume snapshot *Volume version 2 only* +.. option:: --remote-source + + The attribute(s) of the exsiting remote volume snapshot + (admin required) (repeat option to specify multiple attributes) + e.g.: '--remote-source source-name=test_name --remote-source source-id=test_id' + + *Volume version 2 only* + .. _volume_snapshot_create-snapshot-name: .. describe:: diff --git a/openstackclient/tests/unit/volume/v2/test_snapshot.py b/openstackclient/tests/unit/volume/v2/test_snapshot.py index cedf21a916..8ce356aea8 100644 --- a/openstackclient/tests/unit/volume/v2/test_snapshot.py +++ b/openstackclient/tests/unit/volume/v2/test_snapshot.py @@ -67,6 +67,7 @@ def setUp(self): self.volumes_mock.get.return_value = self.volume self.snapshots_mock.create.return_value = self.new_snapshot + self.snapshots_mock.manage.return_value = self.new_snapshot # Get the command object to test self.cmd = volume_snapshot.CreateVolumeSnapshot(self.app, None) @@ -152,6 +153,33 @@ def test_snapshot_create_without_volume(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_snapshot_create_without_remote_source(self): + arglist = [ + '--remote-source', 'source-name=test_source_name', + '--remote-source', 'source-id=test_source_id', + '--volume', self.new_snapshot.volume_id, + ] + ref_dict = {'source-name': 'test_source_name', + 'source-id': 'test_source_id'} + verifylist = [ + ('remote_source', ref_dict), + ('volume', self.new_snapshot.volume_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.snapshots_mock.manage.assert_called_with( + volume_id=self.new_snapshot.volume_id, + ref=ref_dict, + name=None, + description=None, + metadata=None, + ) + self.snapshots_mock.create.assert_not_called() + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + class TestSnapshotDelete(TestSnapshot): diff --git a/openstackclient/volume/v2/volume_snapshot.py b/openstackclient/volume/v2/volume_snapshot.py index d95a49a448..34b8fb82c2 100644 --- a/openstackclient/volume/v2/volume_snapshot.py +++ b/openstackclient/volume/v2/volume_snapshot.py @@ -65,6 +65,15 @@ def get_parser(self, prog_name): help=_("Set a property to this snapshot " "(repeat option to set multiple properties)"), ) + parser.add_argument( + "--remote-source", + metavar="", + action=parseractions.KeyValueAction, + help=_("The attribute(s) of the exsiting remote volume snapshot " + "(admin required) (repeat option to specify multiple " + "attributes) e.g.: '--remote-source source-name=test_name " + "--remote-source source-id=test_id'"), + ) return parser def take_action(self, parsed_args): @@ -74,13 +83,29 @@ def take_action(self, parsed_args): volume = parsed_args.snapshot_name volume_id = utils.find_resource( volume_client.volumes, volume).id - snapshot = volume_client.volume_snapshots.create( - volume_id, - force=parsed_args.force, - name=parsed_args.snapshot_name, - description=parsed_args.description, - metadata=parsed_args.property, - ) + if parsed_args.remote_source: + # Create a new snapshot from an existing remote snapshot source + if parsed_args.force: + msg = (_("'--force' option will not work when you create " + "new volume snapshot from an existing remote " + "volume snapshot")) + LOG.warning(msg) + snapshot = volume_client.volume_snapshots.manage( + volume_id=volume_id, + ref=parsed_args.remote_source, + name=parsed_args.snapshot_name, + description=parsed_args.description, + metadata=parsed_args.property, + ) + else: + # create a new snapshot from scratch + snapshot = volume_client.volume_snapshots.create( + volume_id, + force=parsed_args.force, + name=parsed_args.snapshot_name, + description=parsed_args.description, + metadata=parsed_args.property, + ) snapshot._info.update( {'properties': utils.format_dict(snapshot._info.pop('metadata'))} ) diff --git a/releasenotes/notes/bug-1618676-04ff0f335b670567.yaml b/releasenotes/notes/bug-1618676-04ff0f335b670567.yaml new file mode 100644 index 0000000000..69282252bb --- /dev/null +++ b/releasenotes/notes/bug-1618676-04ff0f335b670567.yaml @@ -0,0 +1,5 @@ +--- +features: + - Add ``--remote-source`` option to ``volume snapshot create`` command to support + creating volume snapshot from an existing remote volume snapshot in volume v2. + [Bug `1618676 `_] From 083e155ae52408b1e70da66c8fbc06e3d2fbbf6e Mon Sep 17 00:00:00 2001 From: Yan Xing'an Date: Mon, 14 Nov 2016 01:23:39 -0800 Subject: [PATCH 1410/3095] SDK Refactor: Prepare router commands Prepare the OSC "router" commands for the SDK refactor. See [1] for details. [1] https://etherpad.openstack.org/p/osc-network-command-sdk-support Change-Id: I2fa12943a65e3981b924e6cea9ed041682ec54b2 Partially-Implements: blueprint network-command-sdk-support --- openstackclient/network/v2/router.py | 21 +++++++++++++++---- .../tests/unit/network/v2/fakes.py | 3 +++ .../tests/unit/network/v2/test_router.py | 8 +++---- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index cdd634d0eb..61a005e68c 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -52,6 +52,7 @@ def _format_routes(routes): _formatters = { 'admin_state_up': _format_admin_state, + 'is_admin_state_up': _format_admin_state, 'external_gateway_info': _format_external_gateway_info, 'availability_zones': utils.format_list, 'availability_zone_hints': utils.format_list, @@ -62,6 +63,9 @@ def _format_routes(routes): def _get_columns(item): column_map = { 'tenant_id': 'project_id', + 'is_ha': 'ha', + 'is_distributed': 'distributed', + 'is_admin_state_up': 'admin_state_up', } return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) @@ -150,6 +154,8 @@ def take_action(self, parsed_args): subnet_id=subnet.id) +# TODO(yanxing'an): Use the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. class CreateRouter(command.ShowOne): _description = _("Create a new router") @@ -255,6 +261,8 @@ def take_action(self, parsed_args): raise exceptions.CommandError(msg) +# TODO(yanxing'an): Use the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. class ListRouter(command.Lister): _description = _("List routers") @@ -297,10 +305,10 @@ def take_action(self, parsed_args): 'id', 'name', 'status', - 'admin_state_up', - 'distributed', - 'ha', - 'tenant_id', + 'is_admin_state_up', + 'is_distributed', + 'is_ha', + 'project_id', ) column_headers = ( 'ID', @@ -319,8 +327,10 @@ def take_action(self, parsed_args): if parsed_args.enable: args['admin_state_up'] = True + args['is_admin_state_up'] = True elif parsed_args.disable: args['admin_state_up'] = False + args['is_admin_state_up'] = False if parsed_args.project: project_id = identity_common.find_project( @@ -329,6 +339,7 @@ def take_action(self, parsed_args): parsed_args.project_domain, ).id args['tenant_id'] = project_id + args['project_id'] = project_id if parsed_args.long: columns = columns + ( 'routes', @@ -407,6 +418,8 @@ def take_action(self, parsed_args): subnet_id=subnet.id) +# TODO(yanxing'an): Use the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. class SetRouter(command.Command): _description = _("Set router properties") diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index 97d0707636..678ab2266e 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -834,6 +834,9 @@ def create_one_router(attrs=None): # Set attributes with special mapping in OpenStack SDK. router.project_id = router_attrs['tenant_id'] + router.is_admin_state_up = router_attrs['admin_state_up'] + router.is_distributed = router_attrs['distributed'] + router.is_ha = router_attrs['ha'] return router diff --git a/openstackclient/tests/unit/network/v2/test_router.py b/openstackclient/tests/unit/network/v2/test_router.py index b0409447dd..8c1d580479 100644 --- a/openstackclient/tests/unit/network/v2/test_router.py +++ b/openstackclient/tests/unit/network/v2/test_router.py @@ -459,7 +459,7 @@ def test_router_list_enable(self): columns, data = self.cmd.take_action(parsed_args) self.network.routers.assert_called_once_with( - **{'admin_state_up': True} + **{'admin_state_up': True, 'is_admin_state_up': True} ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) @@ -476,7 +476,7 @@ def test_router_list_disable(self): columns, data = self.cmd.take_action(parsed_args) self.network.routers.assert_called_once_with( - **{'admin_state_up': False} + **{'admin_state_up': False, 'is_admin_state_up': False} ) self.assertEqual(self.columns, columns) @@ -494,7 +494,7 @@ def test_router_list_project(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - filters = {'tenant_id': project.id} + filters = {'tenant_id': project.id, 'project_id': project.id} self.network.routers.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) @@ -514,7 +514,7 @@ def test_router_list_project_domain(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - filters = {'tenant_id': project.id} + filters = {'tenant_id': project.id, 'project_id': project.id} self.network.routers.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) From 2f2603d90896d0765e1bb2bb1cfb223fdba75835 Mon Sep 17 00:00:00 2001 From: "zhiyong.dai" Date: Tue, 6 Dec 2016 19:08:49 +0800 Subject: [PATCH 1411/3095] Add two consistency group commands Add commands: consistency group add volume consistency group remove volume in volume v2 (v2 only) Change-Id: I70ff287d3b5df78196b8f4b9e50402c471aef284 Implements: bp cinder-command-support Closes-Bug: #1613964 --- .../command-objects/consistency-group.rst | 46 +++- .../unit/volume/v2/test_consistency_group.py | 206 ++++++++++++++++++ .../volume/v2/consistency_group.py | 92 ++++++++ .../notes/bug-1613964-b3e8d9d828a3822c.yaml | 6 + setup.cfg | 2 + 5 files changed, 351 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/bug-1613964-b3e8d9d828a3822c.yaml diff --git a/doc/source/command-objects/consistency-group.rst b/doc/source/command-objects/consistency-group.rst index 910fbba9a1..b3af60b420 100644 --- a/doc/source/command-objects/consistency-group.rst +++ b/doc/source/command-objects/consistency-group.rst @@ -4,6 +4,28 @@ consistency group Block Storage v2 +consistency group add volume +---------------------------- + +Add volume(s) to consistency group. + +.. program:: consistency group add volume +.. code:: bash + + os consistency group add volume + + [ ...] + +.. _consistency_group_add_volume: +.. describe:: + + Consistency group to contain (name or ID) + +.. describe:: + + Volume(s) to add to (name or ID) + (repeat option to add multiple volumes) + consistency group create ------------------------ @@ -86,13 +108,35 @@ List consistency groups. List additional fields in output +consistency group remove volume +------------------------------- + +Remove volume(s) from consistency group. + +.. program:: consistency group remove volume +.. code:: bash + + os consistency group remove volume + + [ ...] + +.. _consistency_group_remove_volume: +.. describe:: + + Consistency group containing (name or ID) + +.. describe:: + + Volume(s) to remove from (name or ID) + (repeat option to remove multiple volumes) + consistency group set --------------------- Set consistency group properties. .. program:: consistency group set - .. code:: bash +.. code:: bash os consistency group set [--name ] diff --git a/openstackclient/tests/unit/volume/v2/test_consistency_group.py b/openstackclient/tests/unit/volume/v2/test_consistency_group.py index bc99ca8dce..6eeeae393e 100644 --- a/openstackclient/tests/unit/volume/v2/test_consistency_group.py +++ b/openstackclient/tests/unit/volume/v2/test_consistency_group.py @@ -36,10 +36,115 @@ def setUp(self): self.app.client_manager.volume.cgsnapshots) self.cgsnapshots_mock.reset_mock() + self.volumes_mock = ( + self.app.client_manager.volume.volumes) + self.volumes_mock.reset_mock() + self.types_mock = self.app.client_manager.volume.volume_types self.types_mock.reset_mock() +class TestConsistencyGroupAddVolume(TestConsistencyGroup): + + _consistency_group = ( + volume_fakes.FakeConsistencyGroup.create_one_consistency_group()) + + def setUp(self): + super(TestConsistencyGroupAddVolume, self).setUp() + + self.consistencygroups_mock.get.return_value = ( + self._consistency_group) + # Get the command object to test + self.cmd = \ + consistency_group.AddVolumeToConsistencyGroup(self.app, None) + + def test_add_one_volume_to_consistency_group(self): + volume = volume_fakes.FakeVolume.create_one_volume() + self.volumes_mock.get.return_value = volume + arglist = [ + self._consistency_group.id, + volume.id, + ] + verifylist = [ + ('consistency_group', self._consistency_group.id), + ('volumes', [volume.id]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'add_volumes': volume.id, + } + self.consistencygroups_mock.update.assert_called_once_with( + self._consistency_group.id, + **kwargs + ) + self.assertIsNone(result) + + def test_add_multiple_volumes_to_consistency_group(self): + volumes = volume_fakes.FakeVolume.create_volumes(count=2) + self.volumes_mock.get = volume_fakes.FakeVolume.get_volumes(volumes) + arglist = [ + self._consistency_group.id, + volumes[0].id, + volumes[1].id, + ] + verifylist = [ + ('consistency_group', self._consistency_group.id), + ('volumes', [volumes[0].id, volumes[1].id]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'add_volumes': volumes[0].id + ',' + volumes[1].id, + } + self.consistencygroups_mock.update.assert_called_once_with( + self._consistency_group.id, + **kwargs + ) + self.assertIsNone(result) + + @mock.patch.object(consistency_group.LOG, 'error') + def test_add_multiple_volumes_to_consistency_group_with_exception( + self, mock_error): + volume = volume_fakes.FakeVolume.create_one_volume() + arglist = [ + self._consistency_group.id, + volume.id, + 'unexist_volume', + ] + verifylist = [ + ('consistency_group', self._consistency_group.id), + ('volumes', [volume.id, 'unexist_volume']), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [volume, + exceptions.CommandError, + self._consistency_group] + with mock.patch.object(utils, 'find_resource', + side_effect=find_mock_result) as find_mock: + result = self.cmd.take_action(parsed_args) + mock_error.assert_called_with("1 of 2 volumes failed to add.") + self.assertIsNone(result) + find_mock.assert_any_call(self.consistencygroups_mock, + self._consistency_group.id) + find_mock.assert_any_call(self.volumes_mock, + volume.id) + find_mock.assert_any_call(self.volumes_mock, + 'unexist_volume') + self.assertEqual(3, find_mock.call_count) + self.consistencygroups_mock.update.assert_called_once_with( + self._consistency_group.id, add_volumes=volume.id + ) + + class TestConsistencyGroupCreate(TestConsistencyGroup): volume_type = volume_fakes.FakeType.create_one_type() @@ -394,6 +499,107 @@ def test_consistency_group_list_with_long(self): self.assertEqual(self.data_long, list(data)) +class TestConsistencyGroupRemoveVolume(TestConsistencyGroup): + + _consistency_group = ( + volume_fakes.FakeConsistencyGroup.create_one_consistency_group()) + + def setUp(self): + super(TestConsistencyGroupRemoveVolume, self).setUp() + + self.consistencygroups_mock.get.return_value = ( + self._consistency_group) + # Get the command object to test + self.cmd = \ + consistency_group.RemoveVolumeFromConsistencyGroup(self.app, None) + + def test_remove_one_volume_from_consistency_group(self): + volume = volume_fakes.FakeVolume.create_one_volume() + self.volumes_mock.get.return_value = volume + arglist = [ + self._consistency_group.id, + volume.id, + ] + verifylist = [ + ('consistency_group', self._consistency_group.id), + ('volumes', [volume.id]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'remove_volumes': volume.id, + } + self.consistencygroups_mock.update.assert_called_once_with( + self._consistency_group.id, + **kwargs + ) + self.assertIsNone(result) + + def test_remove_multi_volumes_from_consistency_group(self): + volumes = volume_fakes.FakeVolume.create_volumes(count=2) + self.volumes_mock.get = volume_fakes.FakeVolume.get_volumes(volumes) + arglist = [ + self._consistency_group.id, + volumes[0].id, + volumes[1].id, + ] + verifylist = [ + ('consistency_group', self._consistency_group.id), + ('volumes', [volumes[0].id, volumes[1].id]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'remove_volumes': volumes[0].id + ',' + volumes[1].id, + } + self.consistencygroups_mock.update.assert_called_once_with( + self._consistency_group.id, + **kwargs + ) + self.assertIsNone(result) + + @mock.patch.object(consistency_group.LOG, 'error') + def test_remove_multiple_volumes_from_consistency_group_with_exception( + self, mock_error): + volume = volume_fakes.FakeVolume.create_one_volume() + arglist = [ + self._consistency_group.id, + volume.id, + 'unexist_volume', + ] + verifylist = [ + ('consistency_group', self._consistency_group.id), + ('volumes', [volume.id, 'unexist_volume']), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [volume, + exceptions.CommandError, + self._consistency_group] + with mock.patch.object(utils, 'find_resource', + side_effect=find_mock_result) as find_mock: + result = self.cmd.take_action(parsed_args) + mock_error.assert_called_with("1 of 2 volumes failed to remove.") + self.assertIsNone(result) + find_mock.assert_any_call(self.consistencygroups_mock, + self._consistency_group.id) + find_mock.assert_any_call(self.volumes_mock, + volume.id) + find_mock.assert_any_call(self.volumes_mock, + 'unexist_volume') + self.assertEqual(3, find_mock.call_count) + self.consistencygroups_mock.update.assert_called_once_with( + self._consistency_group.id, remove_volumes=volume.id + ) + + class TestConsistencyGroupSet(TestConsistencyGroup): consistency_group = ( diff --git a/openstackclient/volume/v2/consistency_group.py b/openstackclient/volume/v2/consistency_group.py index 2f4f3c9582..0a932f8454 100644 --- a/openstackclient/volume/v2/consistency_group.py +++ b/openstackclient/volume/v2/consistency_group.py @@ -27,6 +27,60 @@ LOG = logging.getLogger(__name__) +def _find_volumes(parsed_args_volumes, volume_client): + result = 0 + uuid = '' + for volume in parsed_args_volumes: + try: + volume_id = utils.find_resource( + volume_client.volumes, volume).id + uuid += volume_id + ',' + except Exception as e: + result += 1 + LOG.error(_("Failed to find volume with " + "name or ID '%(volume)s':%(e)s") + % {'volume': volume, 'e': e}) + + return result, uuid + + +class AddVolumeToConsistencyGroup(command.Command): + _description = _("Add volume(s) to consistency group") + + def get_parser(self, prog_name): + parser = super(AddVolumeToConsistencyGroup, self).get_parser(prog_name) + parser.add_argument( + 'consistency_group', + metavar="", + help=_('Consistency group to contain (name or ID)'), + ) + parser.add_argument( + 'volumes', + metavar='', + nargs='+', + help=_('Volume(s) to add to (name or ID) ' + '(repeat option to add multiple volumes)'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + result, add_uuid = _find_volumes(parsed_args.volumes, volume_client) + + if result > 0: + total = len(parsed_args.volumes) + LOG.error(_("%(result)s of %(total)s volumes failed " + "to add.") % {'result': result, 'total': total}) + + if add_uuid: + add_uuid = add_uuid.rstrip(',') + consistency_group_id = utils.find_resource( + volume_client.consistencygroups, + parsed_args.consistency_group).id + volume_client.consistencygroups.update( + consistency_group_id, add_volumes=add_uuid) + + class CreateConsistencyGroup(command.ShowOne): _description = _("Create new consistency group.") @@ -188,6 +242,44 @@ def take_action(self, parsed_args): for s in consistency_groups)) +class RemoveVolumeFromConsistencyGroup(command.Command): + _description = _("Remove volume(s) from consistency group") + + def get_parser(self, prog_name): + parser = \ + super(RemoveVolumeFromConsistencyGroup, self).get_parser(prog_name) + parser.add_argument( + 'consistency_group', + metavar="", + help=_('Consistency group containing (name or ID)'), + ) + parser.add_argument( + 'volumes', + metavar='', + nargs='+', + help=_('Volume(s) to remove from (name or ID) ' + '(repeat option to remove multiple volumes)'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + result, remove_uuid = _find_volumes(parsed_args.volumes, volume_client) + + if result > 0: + total = len(parsed_args.volumes) + LOG.error(_("%(result)s of %(total)s volumes failed " + "to remove.") % {'result': result, 'total': total}) + + if remove_uuid: + remove_uuid = remove_uuid.rstrip(',') + consistency_group_id = utils.find_resource( + volume_client.consistencygroups, + parsed_args.consistency_group).id + volume_client.consistencygroups.update( + consistency_group_id, remove_volumes=remove_uuid) + + class SetConsistencyGroup(command.Command): _description = _("Set consistency group properties") diff --git a/releasenotes/notes/bug-1613964-b3e8d9d828a3822c.yaml b/releasenotes/notes/bug-1613964-b3e8d9d828a3822c.yaml new file mode 100644 index 0000000000..b11d0c1f28 --- /dev/null +++ b/releasenotes/notes/bug-1613964-b3e8d9d828a3822c.yaml @@ -0,0 +1,6 @@ +--- +features: + - Add ``consistency group add volume`` and ``consistency group remove volume`` commands + in volume v2. + [Bug `1642238 `_] + diff --git a/setup.cfg b/setup.cfg index 8c5c663055..69ae4cf29a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -515,9 +515,11 @@ openstack.volume.v2 = backup_restore = openstackclient.volume.v2.backup:RestoreBackup backup_show = openstackclient.volume.v2.backup:ShowBackup + consistency_group_add_volume = openstackclient.volume.v2.consistency_group:AddVolumeToConsistencyGroup consistency_group_create = openstackclient.volume.v2.consistency_group:CreateConsistencyGroup consistency_group_delete = openstackclient.volume.v2.consistency_group:DeleteConsistencyGroup consistency_group_list = openstackclient.volume.v2.consistency_group:ListConsistencyGroup + consistency_group_remove_volume = openstackclient.volume.v2.consistency_group:RemoveVolumeFromConsistencyGroup consistency_group_set = openstackclient.volume.v2.consistency_group:SetConsistencyGroup consistency_group_show = openstackclient.volume.v2.consistency_group:ShowConsistencyGroup From bd63da55473979297d67799c98381301cfc00e64 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Tue, 13 Dec 2016 21:49:48 +0000 Subject: [PATCH 1412/3095] Updated from global requirements Change-Id: I1b677f708a7c598fc68891226d3ff31aa4cb731a --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 3eb018f5f4..3b07d3c0da 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -23,7 +23,7 @@ bandit>=1.1.0 # Apache-2.0 wrapt>=1.7.0 # BSD License # Install these to generate sphinx autodocs -aodhclient>=0.5.0 # Apache-2.0 +aodhclient>=0.7.0 # Apache-2.0 python-barbicanclient>=4.0.0 # Apache-2.0 python-congressclient<2000,>=1.3.0 # Apache-2.0 python-designateclient>=1.5.0 # Apache-2.0 From 11a762e03c3d53b45888387f3c5472a37d9d9f68 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 9 Dec 2016 11:55:48 -0600 Subject: [PATCH 1413/3095] 3.5.0 release note cleanup Change-Id: Ibe5e5a9a212ac483d0cb38bdb3a607d21f32e96d --- ...dd-dns-nameserver-overwrite-option-b866baeae12f9460.yaml | 6 ++---- .../add-network-service-provider-c161a4a328a8a408.yaml | 2 +- ...-port-security-enabled-to-port-set-82b801d21d45e715.yaml | 2 +- releasenotes/notes/bug-1619274-e78afd7c12ea2c3d.yaml | 6 ++++++ releasenotes/notes/bug-1642301-ad04424c80e8fe50.yaml | 6 ++++++ .../notes/rename-snapshot-commands-e0937f7143a4ef55.yaml | 2 +- 6 files changed, 17 insertions(+), 7 deletions(-) create mode 100644 releasenotes/notes/bug-1619274-e78afd7c12ea2c3d.yaml create mode 100644 releasenotes/notes/bug-1642301-ad04424c80e8fe50.yaml diff --git a/releasenotes/notes/add-dns-nameserver-overwrite-option-b866baeae12f9460.yaml b/releasenotes/notes/add-dns-nameserver-overwrite-option-b866baeae12f9460.yaml index 04f0638d69..0f902f0af9 100644 --- a/releasenotes/notes/add-dns-nameserver-overwrite-option-b866baeae12f9460.yaml +++ b/releasenotes/notes/add-dns-nameserver-overwrite-option-b866baeae12f9460.yaml @@ -1,7 +1,5 @@ --- features: - | - ``subnet set`` command now allows the user to overwrite/clear dns-nameserver information - of a subnet by using the option ``no-dns-nameserver``. - [ Blueprint `allow-overwrite-set-options ` _] - + Add ``--no-dns-nameserver`` option to ``subnet set`` command. + [Blueprint `allow-overwrite-set-options `_] diff --git a/releasenotes/notes/add-network-service-provider-c161a4a328a8a408.yaml b/releasenotes/notes/add-network-service-provider-c161a4a328a8a408.yaml index 0841dd0854..a8d3adae67 100644 --- a/releasenotes/notes/add-network-service-provider-c161a4a328a8a408.yaml +++ b/releasenotes/notes/add-network-service-provider-c161a4a328a8a408.yaml @@ -1,4 +1,4 @@ --- features: - | - Add support for the ``network service provider`` command. + Add ``network service provider list`` command. diff --git a/releasenotes/notes/add-port-security-enabled-to-port-set-82b801d21d45e715.yaml b/releasenotes/notes/add-port-security-enabled-to-port-set-82b801d21d45e715.yaml index 5bc3952139..e392c679ae 100644 --- a/releasenotes/notes/add-port-security-enabled-to-port-set-82b801d21d45e715.yaml +++ b/releasenotes/notes/add-port-security-enabled-to-port-set-82b801d21d45e715.yaml @@ -1,6 +1,6 @@ --- features: - | - Added ``--enable-port-security`` and ``--disable-port-security`` + Add ``--enable-port-security`` and ``--disable-port-security`` options to ``port set`` and ``port create`` commands. [Blueprint :oscbp:`network-commands-options`] diff --git a/releasenotes/notes/bug-1619274-e78afd7c12ea2c3d.yaml b/releasenotes/notes/bug-1619274-e78afd7c12ea2c3d.yaml new file mode 100644 index 0000000000..1abd944d89 --- /dev/null +++ b/releasenotes/notes/bug-1619274-e78afd7c12ea2c3d.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - Skip password prompt when running commands that do not require auth + and user auth values are present except for password. + [Bug `1619274 `_] + *Fixed in release 3.3.0* diff --git a/releasenotes/notes/bug-1642301-ad04424c80e8fe50.yaml b/releasenotes/notes/bug-1642301-ad04424c80e8fe50.yaml new file mode 100644 index 0000000000..a4370236e1 --- /dev/null +++ b/releasenotes/notes/bug-1642301-ad04424c80e8fe50.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - Fix problem with ``--os-auth-type token_endpoint`` that caused exceptions + when recent os-client-config version 1.23.0 or newer is installed. + [Bug `1642301 `_] + *Fixed in release 3.4.1* diff --git a/releasenotes/notes/rename-snapshot-commands-e0937f7143a4ef55.yaml b/releasenotes/notes/rename-snapshot-commands-e0937f7143a4ef55.yaml index 4228207f43..32dd9caa80 100644 --- a/releasenotes/notes/rename-snapshot-commands-e0937f7143a4ef55.yaml +++ b/releasenotes/notes/rename-snapshot-commands-e0937f7143a4ef55.yaml @@ -1,6 +1,6 @@ --- features: - - Add new commands ``volume snapshot create/delete/list/show/set/unset``. they are + - Add new commands ``volume snapshot create/delete/list/show/set/unset``. They are used to replace the old commands ``snapshot create/delete/list/show/set/unset``. [Blueprint `backup-snapshot-renamed-for-volume-resource `_] deprecations: From 7832ea357c29fcf8b1e1ad0d29cede3637893ac6 Mon Sep 17 00:00:00 2001 From: Abhishek Raut Date: Sat, 19 Nov 2016 04:52:19 -0800 Subject: [PATCH 1414/3095] SDK Refactor: Prepare port commands Prepare the OSC "port" commands for the SDK refactor. See [1] for details. Also fixed a typo in the UT. [1] https://etherpad.openstack.org/p/osc-network-command-sdk-support Change-Id: I0e37d6c04f3d8e81fdfd50ac26eea9b5a5fb2ff9 Partially-Implements: blueprint network-command-sdk-support --- openstackclient/network/v2/port.py | 52 +++++++++++-------- .../tests/unit/network/v2/fakes.py | 5 +- .../tests/unit/network/v2/test_port.py | 2 +- 3 files changed, 35 insertions(+), 24 deletions(-) diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index bce3e2d303..2ffea50148 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -25,6 +25,7 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import sdk_utils LOG = logging.getLogger(__name__) @@ -36,34 +37,33 @@ def _format_admin_state(state): _formatters = { 'admin_state_up': _format_admin_state, + 'is_admin_state_up': _format_admin_state, 'allowed_address_pairs': utils.format_list_of_dicts, 'binding_profile': utils.format_dict, 'binding_vif_details': utils.format_dict, + 'binding:profile': utils.format_dict, + 'binding:vif_details': utils.format_dict, 'dns_assignment': utils.format_list_of_dicts, 'extra_dhcp_opts': utils.format_list_of_dicts, 'fixed_ips': utils.format_list_of_dicts, + 'security_group_ids': utils.format_list, 'security_groups': utils.format_list, } def _get_columns(item): - columns = list(item.keys()) - if 'tenant_id' in columns: - columns.remove('tenant_id') - if 'project_id' not in columns: - columns.append('project_id') - binding_columns = [ - 'binding:host_id', - 'binding:profile', - 'binding:vif_details', - 'binding:vif_type', - 'binding:vnic_type', - ] - for binding_column in binding_columns: - if binding_column in columns: - columns.remove(binding_column) - columns.append(binding_column.replace('binding:', 'binding_', 1)) - return tuple(sorted(columns)) + column_map = { + 'binding:host_id': 'binding_host_id', + 'binding:profile': 'binding_profile', + 'binding:vif_details': 'binding_vif_details', + 'binding:vif_type': 'binding_vif_type', + 'binding:vnic_type': 'binding_vnic_type', + 'is_admin_state_up': 'admin_state_up', + 'is_port_security_enabled': 'port_security_enabled', + 'security_group_ids': 'security_groups', + 'tenant_id': 'project_id', + } + return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) class JSONKeyValueAction(argparse.Action): @@ -244,6 +244,8 @@ def _add_updatable_args(parser): ) +# TODO(abhiraut): Use the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. class CreatePort(command.ShowOne): _description = _("Create a new port") @@ -349,10 +351,10 @@ def take_action(self, parsed_args): attrs['security_groups'] = [] obj = client.create_port(**attrs) - columns = _get_columns(obj) + display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) - return (columns, data) + return (display_columns, data) class DeletePort(command.Command): @@ -389,6 +391,8 @@ def take_action(self, parsed_args): raise exceptions.CommandError(msg) +# TODO(abhiraut): Use only the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. class ListPort(command.Lister): _description = _("List ports") @@ -451,7 +455,7 @@ def take_action(self, parsed_args): filters = {} if parsed_args.long: - columns += ('security_groups', 'device_owner',) + columns += ('security_group_ids', 'device_owner',) column_headers += ('Security Groups', 'Device Owner',) if parsed_args.device_owner is not None: filters['device_owner'] = parsed_args.device_owner @@ -479,6 +483,8 @@ def take_action(self, parsed_args): ) for s in data)) +# TODO(abhiraut): Use the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. class SetPort(command.Command): _description = _("Set port properties") @@ -621,11 +627,13 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): client = self.app.client_manager.network obj = client.find_port(parsed_args.port, ignore_missing=False) - columns = _get_columns(obj) + display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) - return (columns, data) + return (display_columns, data) +# TODO(abhiraut): Use the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. class UnsetPort(command.Command): _description = _("Unset port properties") diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index 97d0707636..ece6369afd 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -461,12 +461,15 @@ def create_one_port(attrs=None): loaded=True) # Set attributes with special mappings in OpenStack SDK. - port.project_id = port_attrs['tenant_id'] port.binding_host_id = port_attrs['binding:host_id'] port.binding_profile = port_attrs['binding:profile'] port.binding_vif_details = port_attrs['binding:vif_details'] port.binding_vif_type = port_attrs['binding:vif_type'] port.binding_vnic_type = port_attrs['binding:vnic_type'] + port.is_admin_state_up = port_attrs['admin_state_up'] + port.is_port_security_enabled = port_attrs['port_security_enabled'] + port.project_id = port_attrs['tenant_id'] + port.security_group_ids = port_attrs['security_groups'] return port diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index aeb9884a42..255e81166e 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -324,7 +324,7 @@ def test_create_with_security_groups(self): self.assertEqual(ref_columns, columns) self.assertEqual(ref_data, data) - def test_create_with_no_secuirty_groups(self): + def test_create_with_no_security_groups(self): arglist = [ '--network', self._port.network_id, '--no-security-group', From 74360e00f51bd6be275ae5e7e5fc0e1ee765b54f Mon Sep 17 00:00:00 2001 From: jeckxie Date: Wed, 14 Dec 2016 14:39:24 +0800 Subject: [PATCH 1415/3095] [TrivialFix] Fix typo error Change-Id: Iaba9ba22de044b34b4b77f81bcdccda13af81405 --- openstackclient/network/v2/port.py | 2 +- openstackclient/tests/unit/test_shell.py | 2 +- .../notes/backup_list_all-projects_option-4bb23e0b9b075cac.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index bce3e2d303..b660e698b2 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -666,7 +666,7 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): client = self.app.client_manager.network obj = client.find_port(parsed_args.port, ignore_missing=False) - # SDK ignores update() if it recieves a modified obj and attrs + # SDK ignores update() if it receives a modified obj and attrs # To handle the same tmp_obj is created in all take_action of # Unset* classes tmp_fixed_ips = copy.deepcopy(obj.fixed_ips) diff --git a/openstackclient/tests/unit/test_shell.py b/openstackclient/tests/unit/test_shell.py index 3d91da9bb7..b9fac684c4 100644 --- a/openstackclient/tests/unit/test_shell.py +++ b/openstackclient/tests/unit/test_shell.py @@ -422,7 +422,7 @@ def test_shell_argv(self): Use the argv supplied by the test runner so we get actual Python runtime behaviour; we only need to check the type of argv[0] - which will alwyas be present. + which will always be present. """ with mock.patch( diff --git a/releasenotes/notes/backup_list_all-projects_option-4bb23e0b9b075cac.yaml b/releasenotes/notes/backup_list_all-projects_option-4bb23e0b9b075cac.yaml index 5a7defaf85..cbc7593a1d 100644 --- a/releasenotes/notes/backup_list_all-projects_option-4bb23e0b9b075cac.yaml +++ b/releasenotes/notes/backup_list_all-projects_option-4bb23e0b9b075cac.yaml @@ -1,4 +1,4 @@ --- features: - Add ``--all-projects`` option to the ``volume backup list`` command - to list volume backups accross all projects. + to list volume backups across all projects. From 5e03b9246488d8e9c0a538b5ae278b36d9e2f1b7 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 15 Dec 2016 03:55:33 +0000 Subject: [PATCH 1416/3095] Updated from global requirements Change-Id: Id1468c3882547d908b92a598cbb53196c139cf8a --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 2d8aadc603..ddbf13a949 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ six>=1.9.0 # MIT Babel>=2.3.4 # BSD cliff>=2.3.0 # Apache-2.0 -keystoneauth1>=2.14.0 # Apache-2.0 +keystoneauth1>=2.16.0 # Apache-2.0 openstacksdk>=0.9.10 # Apache-2.0 osc-lib>=1.2.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 From 22d639b0b61380f715f72142e4c61526f621a707 Mon Sep 17 00:00:00 2001 From: Sindhu Devale Date: Mon, 5 Dec 2016 22:54:49 -0600 Subject: [PATCH 1417/3095] SDK refactor: Prepare network commands Prepare the OSC "network" commands for the SDK refactor. Change-Id: I50680f6675905f2147fee94cce8c1ed9c81dac0a Partially-Implements: blueprint network-command-sdk-support --- openstackclient/network/v2/network.py | 57 +++++++++++++++---- .../tests/unit/network/v2/fakes.py | 21 ++++++- .../tests/unit/network/v2/test_network.py | 55 +++++++++++------- 3 files changed, 100 insertions(+), 33 deletions(-) diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 1c06c462e0..26cd02c6df 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -19,6 +19,7 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common +from openstackclient.network import sdk_utils def _format_admin_state(item): @@ -31,13 +32,33 @@ def _format_router_external(item): _formatters = { 'subnets': utils.format_list, + 'subnet_ids': utils.format_list, 'admin_state_up': _format_admin_state, + 'is_admin_state_up': _format_admin_state, 'router:external': _format_router_external, + 'is_router_external': _format_router_external, 'availability_zones': utils.format_list, 'availability_zone_hints': utils.format_list, } +def _get_network_columns(item): + column_map = { + 'subnet_ids': 'subnets', + 'is_admin_state_up': 'admin_state_up', + 'is_router_external': 'router:external', + 'is_port_security_enabled': 'port_security_enabled', + 'provider_network_type': 'provider:network_type', + 'provider_physical_network': 'provider:physical_network', + 'provider_segmentation_id': 'provider:segmentation_id', + 'is_shared': 'shared', + 'ipv4_address_scope_id': 'ipv4_address_scope', + 'ipv6_address_scope_id': 'ipv6_address_scope', + 'tenant_id': 'project_id', + } + return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + + def _get_columns(item): columns = list(item.keys()) if 'tenant_id' in columns: @@ -162,6 +183,8 @@ def _get_attrs_compute(client_manager, parsed_args): return attrs +# TODO(sindhu): Use the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. class CreateNetwork(common.NetworkAndComputeShowOne): _description = _("Create new network") @@ -275,9 +298,9 @@ def update_parser_compute(self, parser): def take_action_network(self, client, parsed_args): attrs = _get_attrs(self.app.client_manager, parsed_args) obj = client.create_network(**attrs) - columns = _get_columns(obj) + display_columns, columns = _get_network_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) - return (columns, data) + return (display_columns, data) def take_action_compute(self, client, parsed_args): attrs = _get_attrs_compute(self.app.client_manager, parsed_args) @@ -313,6 +336,8 @@ def take_action_compute(self, client, parsed_args): client.networks.delete(network.id) +# TODO(sindhu): Use the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. class ListNetwork(common.NetworkAndComputeLister): _description = _("List networks") @@ -405,12 +430,12 @@ def take_action_network(self, client, parsed_args): 'id', 'name', 'status', - 'tenant_id', - 'admin_state_up', - 'shared', - 'subnets', + 'project_id', + 'is_admin_state_up', + 'is_shared', + 'subnet_ids', 'provider_network_type', - 'router:external', + 'is_router_external', 'availability_zones', ) column_headers = ( @@ -429,7 +454,7 @@ def take_action_network(self, client, parsed_args): columns = ( 'id', 'name', - 'subnets' + 'subnet_ids' ) column_headers = ( 'ID', @@ -441,16 +466,20 @@ def take_action_network(self, client, parsed_args): if parsed_args.external: args['router:external'] = True + args['is_router_external'] = True elif parsed_args.internal: args['router:external'] = False + args['is_router_external'] = False if parsed_args.name is not None: args['name'] = parsed_args.name if parsed_args.enable: args['admin_state_up'] = True + args['is_admin_state_up'] = True elif parsed_args.disable: args['admin_state_up'] = False + args['is_admin_state_up'] = False if parsed_args.project: project = identity_common.find_project( @@ -459,21 +488,27 @@ def take_action_network(self, client, parsed_args): parsed_args.project_domain, ) args['tenant_id'] = project.id + args['project_id'] = project.id if parsed_args.share: args['shared'] = True + args['is_shared'] = True elif parsed_args.no_share: args['shared'] = False + args['is_shared'] = False if parsed_args.status: args['status'] = parsed_args.status if parsed_args.provider_network_type: args['provider:network_type'] = parsed_args.provider_network_type + args['provider_network_type'] = parsed_args.provider_network_type if parsed_args.physical_network: args['provider:physical_network'] = parsed_args.physical_network + args['provider_physical_network'] = parsed_args.physical_network if parsed_args.segmentation_id: args['provider:segmentation_id'] = parsed_args.segmentation_id + args['provider_segmentation_id'] = parsed_args.segmentation_id data = client.networks(**args) @@ -504,6 +539,8 @@ def take_action_compute(self, client, parsed_args): ) for s in data)) +# TODO(sindhu): Use the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. class SetNetwork(command.Command): _description = _("Set network properties") @@ -619,9 +656,9 @@ def update_parser_common(self, parser): def take_action_network(self, client, parsed_args): obj = client.find_network(parsed_args.network, ignore_missing=False) - columns = _get_columns(obj) + display_columns, columns = _get_network_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) - return (columns, data) + return (display_columns, data) def take_action_compute(self, client, parsed_args): obj = utils.find_resource( diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index c18511f702..deaa46589f 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -291,15 +291,17 @@ def create_one_network(attrs=None): 'admin_state_up': True, 'shared': False, 'subnets': ['a', 'b'], - 'provider_network_type': 'vlan', - 'provider_physical_network': 'physnet1', - 'provider_segmentation_id': "400", + 'provider:network_type': 'vlan', + 'provider:physical_network': 'physnet1', + 'provider:segmentation_id': "400", 'router:external': True, 'availability_zones': [], 'availability_zone_hints': [], 'is_default': False, 'port_security_enabled': True, 'qos_policy_id': 'qos-policy-id-' + uuid.uuid4().hex, + 'ipv4_address_scope': 'ipv4' + uuid.uuid4().hex, + 'ipv6_address_scope': 'ipv6' + uuid.uuid4().hex, } # Overwrite default attributes. @@ -311,8 +313,21 @@ def create_one_network(attrs=None): # Set attributes with special mapping in OpenStack SDK. network.project_id = network_attrs['tenant_id'] network.is_router_external = network_attrs['router:external'] + network.is_admin_state_up = network_attrs['admin_state_up'] network.is_port_security_enabled = \ network_attrs['port_security_enabled'] + network.subnet_ids = network_attrs['subnets'] + network.is_shared = network_attrs['shared'] + network.provider_network_type = \ + network_attrs['provider:network_type'] + network.provider_physical_network = \ + network_attrs['provider:physical_network'] + network.provider_segmentation_id = \ + network_attrs['provider:segmentation_id'] + network.ipv4_address_scope_id = \ + network_attrs['ipv4_address_scope'] + network.ipv6_address_scope_id = \ + network_attrs['ipv6_address_scope'] return network diff --git a/openstackclient/tests/unit/network/v2/test_network.py b/openstackclient/tests/unit/network/v2/test_network.py index 96b1b1021d..c5283443e6 100644 --- a/openstackclient/tests/unit/network/v2/test_network.py +++ b/openstackclient/tests/unit/network/v2/test_network.py @@ -62,13 +62,15 @@ class TestCreateNetworkIdentityV3(TestNetwork): 'availability_zones', 'description', 'id', + 'ipv4_address_scope', + 'ipv6_address_scope', 'is_default', 'name', 'port_security_enabled', 'project_id', - 'provider_network_type', - 'provider_physical_network', - 'provider_segmentation_id', + 'provider:network_type', + 'provider:physical_network', + 'provider:segmentation_id', 'qos_policy_id', 'router:external', 'shared', @@ -82,6 +84,8 @@ class TestCreateNetworkIdentityV3(TestNetwork): utils.format_list(_network.availability_zones), _network.description, _network.id, + _network.ipv4_address_scope_id, + _network.ipv6_address_scope_id, _network.is_default, _network.name, _network.is_port_security_enabled, @@ -236,13 +240,15 @@ class TestCreateNetworkIdentityV2(TestNetwork): 'availability_zones', 'description', 'id', + 'ipv4_address_scope', + 'ipv6_address_scope', 'is_default', 'name', 'port_security_enabled', 'project_id', - 'provider_network_type', - 'provider_physical_network', - 'provider_segmentation_id', + 'provider:network_type', + 'provider:physical_network', + 'provider:segmentation_id', 'qos_policy_id', 'router:external', 'shared', @@ -256,6 +262,8 @@ class TestCreateNetworkIdentityV2(TestNetwork): utils.format_list(_network.availability_zones), _network.description, _network.id, + _network.ipv4_address_scope_id, + _network.ipv6_address_scope_id, _network.is_default, _network.name, _network.is_port_security_enabled, @@ -512,7 +520,7 @@ def test_list_external(self): columns, data = self.cmd.take_action(parsed_args) self.network.networks.assert_called_once_with( - **{'router:external': True} + **{'router:external': True, 'is_router_external': True} ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) @@ -529,7 +537,7 @@ def test_list_internal(self): columns, data = self.cmd.take_action(parsed_args) self.network.networks.assert_called_once_with( - **{'router:external': False} + **{'router:external': False, 'is_router_external': False} ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) @@ -585,7 +593,7 @@ def test_network_list_enable(self): columns, data = self.cmd.take_action(parsed_args) self.network.networks.assert_called_once_with( - **{'admin_state_up': True} + **{'admin_state_up': True, 'is_admin_state_up': True} ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) @@ -603,7 +611,7 @@ def test_network_list_disable(self): columns, data = self.cmd.take_action(parsed_args) self.network.networks.assert_called_once_with( - **{'admin_state_up': False} + **{'admin_state_up': False, 'is_admin_state_up': False} ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) @@ -621,7 +629,7 @@ def test_network_list_project(self): columns, data = self.cmd.take_action(parsed_args) self.network.networks.assert_called_once_with( - **{'tenant_id': project.id} + **{'tenant_id': project.id, 'project_id': project.id} ) self.assertEqual(self.columns, columns) @@ -640,7 +648,7 @@ def test_network_list_project_domain(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - filters = {'tenant_id': project.id} + filters = {'tenant_id': project.id, 'project_id': project.id} self.network.networks.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) @@ -658,7 +666,7 @@ def test_network_list_share(self): columns, data = self.cmd.take_action(parsed_args) self.network.networks.assert_called_once_with( - **{'shared': True} + **{'shared': True, 'is_shared': True} ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) @@ -675,7 +683,7 @@ def test_network_list_no_share(self): columns, data = self.cmd.take_action(parsed_args) self.network.networks.assert_called_once_with( - **{'shared': False} + **{'shared': False, 'is_shared': False} ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) @@ -711,7 +719,8 @@ def test_network_list_provider_network_type(self): columns, data = self.cmd.take_action(parsed_args) self.network.networks.assert_called_once_with( - **{'provider:network_type': network_type} + **{'provider:network_type': network_type, + 'provider_network_type': network_type} ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) @@ -728,7 +737,8 @@ def test_network_list_provider_physical_network(self): columns, data = self.cmd.take_action(parsed_args) self.network.networks.assert_called_once_with( - **{'provider:physical_network': physical_network} + **{'provider:physical_network': physical_network, + 'provider_physical_network': physical_network} ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) @@ -745,7 +755,8 @@ def test_network_list_provider_segment(self): columns, data = self.cmd.take_action(parsed_args) self.network.networks.assert_called_once_with( - **{'provider:segmentation_id': segmentation_id} + **{'provider:segmentation_id': segmentation_id, + 'provider_segmentation_id': segmentation_id} ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) @@ -878,13 +889,15 @@ class TestShowNetwork(TestNetwork): 'availability_zones', 'description', 'id', + 'ipv4_address_scope', + 'ipv6_address_scope', 'is_default', 'name', 'port_security_enabled', 'project_id', - 'provider_network_type', - 'provider_physical_network', - 'provider_segmentation_id', + 'provider:network_type', + 'provider:physical_network', + 'provider:segmentation_id', 'qos_policy_id', 'router:external', 'shared', @@ -898,6 +911,8 @@ class TestShowNetwork(TestNetwork): utils.format_list(_network.availability_zones), _network.description, _network.id, + _network.ipv4_address_scope_id, + _network.ipv6_address_scope_id, _network.is_default, _network.name, _network.is_port_security_enabled, From 63377f25fc7dfae151bab2f6e82002a9a1944e93 Mon Sep 17 00:00:00 2001 From: Mikhail Feoktistov Date: Thu, 15 Dec 2016 19:29:55 +0300 Subject: [PATCH 1418/3095] Add ploop to supported disk formats This format is used for containers for Virtuozzo hypervisor Closes-Bug: 1650342 Change-Id: Ic79f29a1fe9ea5016d3d5520c2b06e39da01ff61 --- doc/source/command-objects/image.rst | 4 ++-- openstackclient/image/v1/image.py | 2 +- openstackclient/image/v2/image.py | 2 +- releasenotes/notes/bug-1650342-22cb88ef37a41872.yaml | 5 +++++ 4 files changed, 9 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/bug-1650342-22cb88ef37a41872.yaml diff --git a/doc/source/command-objects/image.rst b/doc/source/command-objects/image.rst index 7fc33c697c..9564f099e6 100644 --- a/doc/source/command-objects/image.rst +++ b/doc/source/command-objects/image.rst @@ -79,7 +79,7 @@ Create/upload an image .. option:: --disk-format Image disk format. The supported options are: ami, ari, aki, vhd, vmdk, - raw, qcow2, vhdx, vdi, and iso. The default format is: raw + raw, qcow2, vhdx, vdi, iso, and ploop. The default format is: raw .. option:: --size @@ -339,7 +339,7 @@ Set image properties .. option:: --disk-format Image disk format. The supported options are: ami, ari, aki, vhd, vmdk, - raw, qcow2, vhdx, vdi, and iso. + raw, qcow2, vhdx, vdi, iso, and ploop. .. option:: --size diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index eb79cd2fed..1f239b6701 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -39,7 +39,7 @@ DEFAULT_CONTAINER_FORMAT = 'bare' DEFAULT_DISK_FORMAT = 'raw' DISK_CHOICES = ["ami", "ari", "aki", "vhd", "vmdk", "raw", "qcow2", "vhdx", - "vdi", "iso"] + "vdi", "iso", "ploop"] LOG = logging.getLogger(__name__) diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 1d16760556..55eb7eb104 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -33,7 +33,7 @@ DEFAULT_CONTAINER_FORMAT = 'bare' DEFAULT_DISK_FORMAT = 'raw' DISK_CHOICES = ["ami", "ari", "aki", "vhd", "vmdk", "raw", "qcow2", "vhdx", - "vdi", "iso"] + "vdi", "iso", "ploop"] LOG = logging.getLogger(__name__) diff --git a/releasenotes/notes/bug-1650342-22cb88ef37a41872.yaml b/releasenotes/notes/bug-1650342-22cb88ef37a41872.yaml new file mode 100644 index 0000000000..345d683125 --- /dev/null +++ b/releasenotes/notes/bug-1650342-22cb88ef37a41872.yaml @@ -0,0 +1,5 @@ +--- +features: + - Add ``ploop`` as a valid disk format choice for ``image create`` + and ``image set`` commands. + [Bug `1650342 `_] From 841d9d8dbd313b3dcf604466786d6b9a5c918156 Mon Sep 17 00:00:00 2001 From: Reedip Date: Fri, 19 Aug 2016 21:02:35 +0530 Subject: [PATCH 1419/3095] Add support for setting router gateway This patch adds the support to set the gateway information for a router. Implements: blueprint neutron-client-advanced-router Partially-Implements: blueprint network-commands-options Change-Id: Ifb5a4d1965cd7e75c0c8cf2cfb677e0628b699dc Depends-On: I2bda0dd40afd64b6cecca5f64ef2326bda4fac92 --- doc/source/command-objects/router.rst | 19 +++ openstackclient/network/v2/router.py | 58 ++++++++- .../tests/unit/network/v2/test_router.py | 112 +++++++++++++++++- .../router-gateway-set-01d9c7ffe4461daf.yaml | 7 ++ 4 files changed, 189 insertions(+), 7 deletions(-) create mode 100644 releasenotes/notes/router-gateway-set-01d9c7ffe4461daf.yaml diff --git a/doc/source/command-objects/router.rst b/doc/source/command-objects/router.rst index 56b95ffa68..22c4149032 100644 --- a/doc/source/command-objects/router.rst +++ b/doc/source/command-objects/router.rst @@ -213,6 +213,7 @@ Set router properties [--description ] [--route destination=,gateway= | --no-route] [--ha | --no-ha] + [--external-gateway [--enable-snat|--disable-snat] [--fixed-ip subnet=,ip-address=]] .. option:: --name @@ -258,6 +259,24 @@ Set router properties Clear high availablability attribute of the router (disabled router only) +.. option:: --external-gateway + + External Network used as router's gateway (name or ID) + +.. option:: --enable-snat + + Enable Source NAT on external gateway + +.. option:: --disable-snat + + Disable Source NAT on external gateway + +.. option:: --fixed-ip subnet=,ip-address= + + Desired IP and/or subnet (name or ID) on external gateway: + subnet=,ip-address= + (repeat option to set multiple fixed IP addresses) + .. _router_set-router: .. describe:: diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index cbd412b585..bf60cf6fa9 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -473,9 +473,32 @@ def get_parser(self, prog_name): help=_("Clear high availablability attribute of the router " "(disabled router only)") ) - # TODO(tangchen): Support setting 'external_gateway_info' property in - # 'router set' command. - + parser.add_argument( + '--external-gateway', + metavar="", + help=_("External Network used as router's gateway (name or ID)") + ) + parser.add_argument( + '--fixed-ip', + metavar='subnet=,ip-address=', + action=parseractions.MultiKeyValueAction, + optional_keys=['subnet', 'ip-address'], + help=_("Desired IP and/or subnet (name or ID)" + "on external gateway: " + "subnet=,ip-address= " + "(repeat option to set multiple fixed IP addresses)") + ) + snat_group = parser.add_mutually_exclusive_group() + snat_group.add_argument( + '--enable-snat', + action='store_true', + help=_("Enable Source NAT on external gateway") + ) + snat_group.add_argument( + '--disable-snat', + action='store_true', + help=_("Disable Source NAT on external gateway") + ) return parser def take_action(self, parsed_args): @@ -504,7 +527,34 @@ def take_action(self, parsed_args): for route in parsed_args.routes: route['nexthop'] = route.pop('gateway') attrs['routes'] = obj.routes + parsed_args.routes - + if (parsed_args.disable_snat or parsed_args.enable_snat or + parsed_args.fixed_ip) and not parsed_args.external_gateway: + msg = (_("You must specify '--external-gateway' in order" + "to update the SNAT or fixed-ip values")) + raise exceptions.CommandError(msg) + if parsed_args.external_gateway: + gateway_info = {} + network = client.find_network( + parsed_args.external_gateway, ignore_missing=False) + gateway_info['network_id'] = network.id + if parsed_args.disable_snat: + gateway_info['enable_snat'] = False + if parsed_args.enable_snat: + gateway_info['enable_snat'] = True + if parsed_args.fixed_ip: + ips = [] + for ip_spec in parsed_args.fixed_ip: + if ip_spec.get('subnet', False): + subnet_name_id = ip_spec.pop('subnet') + if subnet_name_id: + subnet = client.find_subnet(subnet_name_id, + ignore_missing=False) + ip_spec['subnet_id'] = subnet.id + if ip_spec.get('ip-address', False): + ip_spec['ip_address'] = ip_spec.pop('ip-address') + ips.append(ip_spec) + gateway_info['external_fixed_ips'] = ips + attrs['external_gateway_info'] = gateway_info client.update_router(obj, **attrs) diff --git a/openstackclient/tests/unit/network/v2/test_router.py b/openstackclient/tests/unit/network/v2/test_router.py index 24984e47b0..f5f2c3255b 100644 --- a/openstackclient/tests/unit/network/v2/test_router.py +++ b/openstackclient/tests/unit/network/v2/test_router.py @@ -561,17 +561,19 @@ class TestSetRouter(TestRouter): # The router to set. _default_route = {'destination': '10.20.20.0/24', 'nexthop': '10.20.30.1'} + _network = network_fakes.FakeNetwork.create_one_network() + _subnet = network_fakes.FakeSubnet.create_one_subnet() _router = network_fakes.FakeRouter.create_one_router( attrs={'routes': [_default_route]} ) def setUp(self): super(TestSetRouter, self).setUp() - + self.network.router_add_gateway = mock.Mock() self.network.update_router = mock.Mock(return_value=None) - self.network.find_router = mock.Mock(return_value=self._router) - + self.network.find_network = mock.Mock(return_value=self._network) + self.network.find_subnet = mock.Mock(return_value=self._subnet) # Get the command object to test self.cmd = router.SetRouter(self.app, self.namespace) @@ -758,6 +760,110 @@ def test_set_nothing(self): self._router, **attrs) self.assertIsNone(result) + def test_wrong_gateway_params(self): + arglist = [ + "--fixed-ip", "subnet='abc'", + self._router.id, + ] + verifylist = [ + ('fixed_ip', [{'subnet': "'abc'"}]), + ('router', self._router.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, parsed_args) + + def test_set_gateway_network_only(self): + arglist = [ + "--external-gateway", self._network.id, + self._router.id, + ] + verifylist = [ + ('external_gateway', self._network.id), + ('router', self._router.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.network.update_router.assert_called_with( + self._router, **{'external_gateway_info': { + 'network_id': self._network.id}}) + self.assertIsNone(result) + + def test_set_gateway_options_subnet_only(self): + arglist = [ + "--external-gateway", self._network.id, + "--fixed-ip", "subnet='abc'", + self._router.id, + '--enable-snat', + ] + verifylist = [ + ('router', self._router.id), + ('external_gateway', self._network.id), + ('fixed_ip', [{'subnet': "'abc'"}]), + ('enable_snat', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.network.update_router.assert_called_with( + self._router, **{'external_gateway_info': { + 'network_id': self._network.id, + 'external_fixed_ips': [{ + 'subnet_id': self._subnet.id, }], + 'enable_snat': True, }}) + self.assertIsNone(result) + + def test_set_gateway_option_ipaddress_only(self): + arglist = [ + "--external-gateway", self._network.id, + "--fixed-ip", "ip-address=10.0.1.1", + self._router.id, + '--enable-snat', + ] + verifylist = [ + ('router', self._router.id), + ('external_gateway', self._network.id), + ('fixed_ip', [{'ip-address': "10.0.1.1"}]), + ('enable_snat', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.network.update_router.assert_called_with( + self._router, **{'external_gateway_info': { + 'network_id': self._network.id, + 'external_fixed_ips': [{ + 'ip_address': "10.0.1.1", }], + 'enable_snat': True, }}) + self.assertIsNone(result) + + def test_set_gateway_options_subnet_ipaddress(self): + arglist = [ + "--external-gateway", self._network.id, + "--fixed-ip", "subnet='abc',ip-address=10.0.1.1", + self._router.id, + '--enable-snat', + ] + verifylist = [ + ('router', self._router.id), + ('external_gateway', self._network.id), + ('fixed_ip', [{'subnet': "'abc'", + 'ip-address': "10.0.1.1"}]), + ('enable_snat', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.network.update_router.assert_called_with( + self._router, **{'external_gateway_info': { + 'network_id': self._network.id, + 'external_fixed_ips': [{ + 'subnet_id': self._subnet.id, + 'ip_address': "10.0.1.1", }], + 'enable_snat': True, }}) + self.assertIsNone(result) + class TestShowRouter(TestRouter): diff --git a/releasenotes/notes/router-gateway-set-01d9c7ffe4461daf.yaml b/releasenotes/notes/router-gateway-set-01d9c7ffe4461daf.yaml new file mode 100644 index 0000000000..dec9d09663 --- /dev/null +++ b/releasenotes/notes/router-gateway-set-01d9c7ffe4461daf.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Add support for setting the gateway information in a router, + by introducing the new option ``--external-gateway`` in + ``router set`` CLI. + [ Blueprint `neutron-client-advanced-router `_] From d12aa86f7c4cadb423553d607ae4078f2cb8a962 Mon Sep 17 00:00:00 2001 From: Yan Xing'an Date: Sun, 23 Oct 2016 01:40:20 -0700 Subject: [PATCH 1420/3095] Add filtering options to the address scope list command Add --name, --ip-version, --project, --project-domain, --share, --no-share options to the address scope list command. Change-Id: I8ece8da473d07dfc21dfb5b17de47624241f0142 Closes-Bug: #1636046 Partially-Implements: blueprint network-commands-options --- doc/source/command-objects/address-scope.rst | 29 ++++++ openstackclient/network/v2/address_scope.py | 61 +++++++++++- .../unit/network/v2/test_address_scope.py | 98 +++++++++++++++++++ .../notes/bug-1636046-98dc0e69a4e44850.yaml | 6 ++ 4 files changed, 193 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/bug-1636046-98dc0e69a4e44850.yaml diff --git a/doc/source/command-objects/address-scope.rst b/doc/source/command-objects/address-scope.rst index 7481ed53de..f7572d97da 100644 --- a/doc/source/command-objects/address-scope.rst +++ b/doc/source/command-objects/address-scope.rst @@ -72,6 +72,35 @@ List address scopes .. code:: bash os address scope list + [--name ] + [--ip-version ] + [--project [--project-domain ]] + [--share | --no-share] + +.. option:: --name + + List only address scopes of given name in output + +.. option:: --ip-version + + List address scopes of given IP version networks (4 or 6) + +.. option:: --project + + List address scopes according to their project (name or ID) + +.. option:: --project-domain + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + +.. option:: --share + + List address scopes shared between projects + +.. option:: --no-share + + List address scopes not shared between projects address scope set ----------------- diff --git a/openstackclient/network/v2/address_scope.py b/openstackclient/network/v2/address_scope.py index 0d8f80d025..9f77aed685 100644 --- a/openstackclient/network/v2/address_scope.py +++ b/openstackclient/network/v2/address_scope.py @@ -140,9 +140,48 @@ def take_action(self, parsed_args): raise exceptions.CommandError(msg) +# TODO(yanxing'an): Use the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. class ListAddressScope(command.Lister): _description = _("List address scopes") + def get_parser(self, prog_name): + parser = super(ListAddressScope, self).get_parser(prog_name) + + parser.add_argument( + '--name', + metavar='', + help=_("List only address scopes of given name in output") + ) + parser.add_argument( + '--ip-version', + type=int, + choices=[4, 6], + metavar='', + dest='ip_version', + help=_("List address scopes of given IP version networks (4 or 6)") + ) + parser.add_argument( + '--project', + metavar="", + help=_("List address scopes according to their project " + "(name or ID)") + ) + identity_common.add_project_domain_option_to_parser(parser) + + shared_group = parser.add_mutually_exclusive_group() + shared_group.add_argument( + '--share', + action='store_true', + help=_("List address scopes shared between projects") + ) + shared_group.add_argument( + '--no-share', + action='store_true', + help=_("List address scopes not shared between projects") + ) + return parser + def take_action(self, parsed_args): client = self.app.client_manager.network columns = ( @@ -159,7 +198,27 @@ def take_action(self, parsed_args): 'Shared', 'Project', ) - data = client.address_scopes() + attrs = {} + if parsed_args.name: + attrs['name'] = parsed_args.name + if parsed_args.ip_version: + attrs['ip_version'] = parsed_args.ip_version + if parsed_args.share: + attrs['shared'] = True + attrs['is_shared'] = True + if parsed_args.no_share: + attrs['shared'] = False + attrs['is_shared'] = False + if 'project' in parsed_args and parsed_args.project is not None: + identity_client = self.app.client_manager.identity + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + attrs['tenant_id'] = project_id + attrs['project_id'] = project_id + data = client.address_scopes(**attrs) return (column_headers, (utils.get_item_properties( diff --git a/openstackclient/tests/unit/network/v2/test_address_scope.py b/openstackclient/tests/unit/network/v2/test_address_scope.py index 12c3f1d638..516b879589 100644 --- a/openstackclient/tests/unit/network/v2/test_address_scope.py +++ b/openstackclient/tests/unit/network/v2/test_address_scope.py @@ -275,6 +275,104 @@ def test_address_scope_list(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + def test_address_scope_list_name(self): + arglist = [ + '--name', self.address_scopes[0].name, + ] + verifylist = [ + ('name', self.address_scopes[0].name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.address_scopes.assert_called_once_with( + **{'name': self.address_scopes[0].name}) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_address_scope_list_ip_version(self): + arglist = [ + '--ip-version', str(4), + ] + verifylist = [ + ('ip_version', 4), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.address_scopes.assert_called_once_with( + **{'ip_version': 4}) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_address_scope_list_project(self): + project = identity_fakes_v3.FakeProject.create_one_project() + self.projects_mock.get.return_value = project + arglist = [ + '--project', project.id, + ] + verifylist = [ + ('project', project.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.address_scopes.assert_called_once_with( + **{'tenant_id': project.id, 'project_id': project.id}) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_address_scope_project_domain(self): + project = identity_fakes_v3.FakeProject.create_one_project() + self.projects_mock.get.return_value = project + arglist = [ + '--project', project.id, + '--project-domain', project.domain_id, + ] + verifylist = [ + ('project', project.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'tenant_id': project.id, 'project_id': project.id} + + self.network.address_scopes.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_address_scope_list_share(self): + arglist = [ + '--share', + ] + verifylist = [ + ('share', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.address_scopes.assert_called_once_with( + **{'shared': True, 'is_shared': True} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_address_scope_list_no_share(self): + arglist = [ + '--no-share', + ] + verifylist = [ + ('no_share', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.address_scopes.assert_called_once_with( + **{'shared': False, 'is_shared': False} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + class TestSetAddressScope(TestAddressScope): diff --git a/releasenotes/notes/bug-1636046-98dc0e69a4e44850.yaml b/releasenotes/notes/bug-1636046-98dc0e69a4e44850.yaml new file mode 100644 index 0000000000..7b6c7d833c --- /dev/null +++ b/releasenotes/notes/bug-1636046-98dc0e69a4e44850.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--name``, ``--ip-version``, ``--project``, ``--project-domain``, + ``--share``, ``--no-share`` options to the ``address scope list`` command. + [Bug `1636046 `_] From 25c563d0d8e1b9cc5d9bcafa93b33628e29aed58 Mon Sep 17 00:00:00 2001 From: "zhiyong.dai" Date: Sun, 18 Dec 2016 02:34:07 +0800 Subject: [PATCH 1421/3095] Tivial:update the description format in volume_snapshot.py Replace the old description format with "_description = _( )" Change-Id: I36d61621309cfceefbd1ab1b930fa94b4bad2036 --- openstackclient/volume/v2/volume_snapshot.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openstackclient/volume/v2/volume_snapshot.py b/openstackclient/volume/v2/volume_snapshot.py index 34b8fb82c2..af29b777dc 100644 --- a/openstackclient/volume/v2/volume_snapshot.py +++ b/openstackclient/volume/v2/volume_snapshot.py @@ -30,7 +30,7 @@ class CreateVolumeSnapshot(command.ShowOne): - """Create new volume snapshot""" + _description = _("Create new volume snapshot") def get_parser(self, prog_name): parser = super(CreateVolumeSnapshot, self).get_parser(prog_name) @@ -113,7 +113,7 @@ def take_action(self, parsed_args): class DeleteVolumeSnapshot(command.Command): - """Delete volume snapshot(s)""" + _description = _("Delete volume snapshot(s)") def get_parser(self, prog_name): parser = super(DeleteVolumeSnapshot, self).get_parser(prog_name) @@ -155,7 +155,7 @@ def take_action(self, parsed_args): class ListVolumeSnapshot(command.Lister): - """List volume snapshots""" + _description = _("List volume snapshots") def get_parser(self, prog_name): parser = super(ListVolumeSnapshot, self).get_parser(prog_name) @@ -266,7 +266,7 @@ def _format_volume_id(volume_id): class SetVolumeSnapshot(command.Command): - """Set volume snapshot properties""" + _description = _("Set volume snapshot properties") def get_parser(self, prog_name): parser = super(SetVolumeSnapshot, self).get_parser(prog_name) @@ -347,7 +347,7 @@ def take_action(self, parsed_args): class ShowVolumeSnapshot(command.ShowOne): - """Display volume snapshot details""" + _description = _("Display volume snapshot details") def get_parser(self, prog_name): parser = super(ShowVolumeSnapshot, self).get_parser(prog_name) @@ -369,7 +369,7 @@ def take_action(self, parsed_args): class UnsetVolumeSnapshot(command.Command): - """Unset volume snapshot properties""" + _description = _("Unset volume snapshot properties") def get_parser(self, prog_name): parser = super(UnsetVolumeSnapshot, self).get_parser(prog_name) From 194a8529d900da340e6976a1e128b129b428acfc Mon Sep 17 00:00:00 2001 From: Kevin_Zheng Date: Mon, 19 Dec 2016 17:23:53 +0800 Subject: [PATCH 1422/3095] Add doc for Searchlight client The doc information for python-searchlight client is not good and this patch add some information. Change-Id: I47da1665f5108a4d945b927c134904ab479c144d --- doc/source/commands.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 795dfd530b..206b18af3a 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -193,6 +193,9 @@ list check out :doc:`plugin-commands`. * ``ptr record``: (**DNS (Designate)**) * ``queue``: (**Messaging (Zaqar)**) * ``recordset``: (**DNS (Designate)**) +* ``search`` (**Search (Searchlight)**) +* ``search facet`` (**Search (Searchlight)**) +* ``search resource type`` (**Search (Searchlight)**) * ``secret``: (**Key Manager (Barbican)**) * ``secret container``: (**Key Manager (Barbican)**) * ``secret order``: (**Key Manager (Barbican)**) @@ -237,6 +240,7 @@ Those actions with an opposite action are noted in parens if applicable. * ``migrate`` - move a server or a volume to a different host; ``--live`` performs a live server migration if possible * ``pause`` (``unpause``) - stop one or more servers and leave them in memory +* ``query`` - Query resources by Elasticsearch query string or json format DSL. * ``reboot`` - forcibly reboot a server * ``rebuild`` - rebuild a server using (most of) the same arguments as in the original create * ``remove`` (``add``) - remove an object from a group of objects From 20b0b71809e7d9aaf82b07acac10f1d3f08c48a3 Mon Sep 17 00:00:00 2001 From: "zhiyong.dai" Date: Mon, 19 Dec 2016 18:50:08 +0800 Subject: [PATCH 1423/3095] Add one test for "backup set" command The former tests for "backup set" command miss a test for '--description' option. In this patch, one relative test is added. Change-Id: Ie755d56a68a666d48751ab1ad20c8edb50e69b94 --- .../tests/unit/volume/v2/test_backup.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/openstackclient/tests/unit/volume/v2/test_backup.py b/openstackclient/tests/unit/volume/v2/test_backup.py index 10e7aac5cf..a8e81c7eb8 100644 --- a/openstackclient/tests/unit/volume/v2/test_backup.py +++ b/openstackclient/tests/unit/volume/v2/test_backup.py @@ -418,6 +418,30 @@ def test_backup_set_name(self): self.backup.id, **{'name': 'new_name'}) self.assertIsNone(result) + def test_backup_set_description(self): + arglist = [ + '--description', 'new_description', + self.backup.id, + ] + verifylist = [ + ('name', None), + ('description', 'new_description'), + ('backup', self.backup.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'description': 'new_description' + } + self.backups_mock.update.assert_called_once_with( + self.backup.id, + **kwargs + ) + self.assertIsNone(result) + def test_backup_set_state(self): arglist = [ '--state', 'error', From 25104c7d4a23092e6173bb76171d2d7ee6c4694b Mon Sep 17 00:00:00 2001 From: Reedip Date: Wed, 16 Nov 2016 15:56:57 +0530 Subject: [PATCH 1424/3095] Introduce overwrite functionality in ``osc router set`` The overwrite functionality allows user to overwrite the routes of a specific router. Change-Id: I8d3cfe5cab2ffbfa046371c3adcd2cf446c91cbc partially-implements: blueprint allow-overwrite-set-options --- doc/source/command-objects/router.rst | 4 +- openstackclient/network/v2/router.py | 27 +++++----- .../tests/unit/network/v2/test_router.py | 50 +++++++++++++------ ...ite-option-to-router-7c50c8031dab6bae.yaml | 7 +++ 4 files changed, 60 insertions(+), 28 deletions(-) create mode 100644 releasenotes/notes/add-overwrite-option-to-router-7c50c8031dab6bae.yaml diff --git a/doc/source/command-objects/router.rst b/doc/source/command-objects/router.rst index 16a8258dfd..d2190ba4b4 100644 --- a/doc/source/command-objects/router.rst +++ b/doc/source/command-objects/router.rst @@ -259,7 +259,9 @@ Set router properties .. option:: --no-route - Clear routes associated with the router + Clear routes associated with the router. + Specify both --route and --no-route to overwrite + current value of route. .. option:: --ha diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index fea294dadb..4d779c3767 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -98,8 +98,6 @@ def _get_attrs(client_manager, parsed_args): ).id attrs['tenant_id'] = project_id - # TODO(tangchen): Support getting 'external_gateway_info' property. - return attrs @@ -464,7 +462,8 @@ def get_parser(self, prog_name): help=_("Set router to centralized mode (disabled router only)") ) routes_group = parser.add_mutually_exclusive_group() - routes_group.add_argument( + # ToDo(Reedip):Remove mutual exclusiveness once clear-routes is removed + parser.add_argument( '--route', metavar='destination=,gateway=', action=parseractions.MultiKeyValueAction, @@ -479,7 +478,9 @@ def get_parser(self, prog_name): routes_group.add_argument( '--no-route', action='store_true', - help=_("Clear routes associated with the router") + help=_("Clear routes associated with the router. " + "Specify both --route and --no-route to overwrite " + "current value of route.") ) routes_group.add_argument( '--clear-routes', @@ -539,20 +540,22 @@ def take_action(self, parsed_args): attrs['ha'] = True elif parsed_args.no_ha: attrs['ha'] = False - if parsed_args.no_route: - attrs['routes'] = [] - elif parsed_args.clear_routes: - attrs['routes'] = [] + if parsed_args.clear_routes: LOG.warning(_( 'The --clear-routes option is deprecated, ' 'please use --no-route instead.' )) - elif parsed_args.routes is not None: - # Map the route keys and append to the current routes. - # The REST API will handle route validation and duplicates. + + if parsed_args.routes is not None: for route in parsed_args.routes: route['nexthop'] = route.pop('gateway') - attrs['routes'] = obj.routes + parsed_args.routes + attrs['routes'] = parsed_args.routes + if not (parsed_args.no_route or parsed_args.clear_routes): + # Map the route keys and append to the current routes. + # The REST API will handle route validation and duplicates. + attrs['routes'] += obj.routes + elif parsed_args.no_route or parsed_args.clear_routes: + attrs['routes'] = [] if (parsed_args.disable_snat or parsed_args.enable_snat or parsed_args.fixed_ip) and not parsed_args.external_gateway: msg = (_("You must specify '--external-gateway' in order" diff --git a/openstackclient/tests/unit/network/v2/test_router.py b/openstackclient/tests/unit/network/v2/test_router.py index 9183cb6364..faca7479c4 100644 --- a/openstackclient/tests/unit/network/v2/test_router.py +++ b/openstackclient/tests/unit/network/v2/test_router.py @@ -704,10 +704,10 @@ def test_set_route(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - + routes = [{'destination': '10.20.30.0/24', + 'nexthop': '10.20.30.1'}] attrs = { - 'routes': self._router.routes + [{'destination': '10.20.30.0/24', - 'nexthop': '10.20.30.1'}], + 'routes': routes + self._router.routes } self.network.update_router.assert_called_once_with( self._router, **attrs) @@ -733,21 +733,31 @@ def test_set_no_route(self): self._router, **attrs) self.assertIsNone(result) - def test_set_route_no_route(self): + def test_set_route_overwrite_route(self): + _testrouter = network_fakes.FakeRouter.create_one_router( + {'routes': [{"destination": "10.0.0.2", + "nexthop": "1.1.1.1"}]}) + self.network.find_router = mock.Mock(return_value=_testrouter) arglist = [ - self._router.name, + _testrouter.name, '--route', 'destination=10.20.30.0/24,gateway=10.20.30.1', '--no-route', ] verifylist = [ - ('router', self._router.name), + ('router', _testrouter.name), ('routes', [{'destination': '10.20.30.0/24', 'gateway': '10.20.30.1'}]), ('no_route', True), ] - - self.assertRaises(tests_utils.ParserException, self.check_parser, - self.cmd, arglist, verifylist) + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + attrs = { + 'routes': [{'destination': '10.20.30.0/24', + 'nexthop': '10.20.30.1'}] + } + self.network.update_router.assert_called_once_with( + _testrouter, **attrs) + self.assertIsNone(result) def test_set_clear_routes(self): arglist = [ @@ -769,21 +779,31 @@ def test_set_clear_routes(self): self._router, **attrs) self.assertIsNone(result) - def test_set_route_clear_routes(self): + def test_overwrite_route_clear_routes(self): + _testrouter = network_fakes.FakeRouter.create_one_router( + {'routes': [{"destination": "10.0.0.2", + "nexthop": "1.1.1.1"}]}) + self.network.find_router = mock.Mock(return_value=_testrouter) arglist = [ - self._router.name, + _testrouter.name, '--route', 'destination=10.20.30.0/24,gateway=10.20.30.1', '--clear-routes', ] verifylist = [ - ('router', self._router.name), + ('router', _testrouter.name), ('routes', [{'destination': '10.20.30.0/24', 'gateway': '10.20.30.1'}]), ('clear_routes', True), ] - - self.assertRaises(tests_utils.ParserException, self.check_parser, - self.cmd, arglist, verifylist) + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + attrs = { + 'routes': [{'destination': '10.20.30.0/24', + 'nexthop': '10.20.30.1'}] + } + self.network.update_router.assert_called_once_with( + _testrouter, **attrs) + self.assertIsNone(result) def test_set_nothing(self): arglist = [ diff --git a/releasenotes/notes/add-overwrite-option-to-router-7c50c8031dab6bae.yaml b/releasenotes/notes/add-overwrite-option-to-router-7c50c8031dab6bae.yaml new file mode 100644 index 0000000000..ac83b7e84e --- /dev/null +++ b/releasenotes/notes/add-overwrite-option-to-router-7c50c8031dab6bae.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Add support to overwrite routes in a router instance, using ``--router`` + and ``--no-router`` option in ``osc router set``. + [ Blueprint `allow-overwrite-set-options `_] + From 4a5bf8d2a58fde1d6cbbd2bb27c3eb6fabe59c3a Mon Sep 17 00:00:00 2001 From: Reedip Date: Tue, 29 Nov 2016 07:18:47 -0500 Subject: [PATCH 1425/3095] Add support for clearing router gateway This patch adds the support to clear the gateway information from a router. Change-Id: I446c556750f080a6fc21fea8f531fd71838d648a Implements: blueprint neutron-client-advanced-router Partially-Implements: blueprint network-commands-options --- doc/source/command-objects/router.rst | 5 +++++ openstackclient/network/v2/router.py | 7 +++++++ .../tests/unit/network/v2/test_router.py | 13 +++++++++++++ .../notes/router-gateway-set-01d9c7ffe4461daf.yaml | 3 ++- 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/doc/source/command-objects/router.rst b/doc/source/command-objects/router.rst index 16a8258dfd..ee89466105 100644 --- a/doc/source/command-objects/router.rst +++ b/doc/source/command-objects/router.rst @@ -318,6 +318,7 @@ Unset router properties os router unset [--route destination=,gateway=] + [--external-gateway] .. option:: --route destination=,gateway= @@ -327,6 +328,10 @@ Unset router properties gateway: nexthop IP address (repeat option to unset multiple routes) +.. option:: --external-gateway + + Remove external gateway information from the router + .. _router_unset-router: .. describe:: diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index fea294dadb..45507b530c 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -621,6 +621,11 @@ def get_parser(self, prog_name): "destination: destination subnet (in CIDR notation) " "gateway: nexthop IP address " "(repeat option to unset multiple routes)")) + parser.add_argument( + '--external-gateway', + action='store_true', + default=False, + help=_("Remove external gateway information from the router")) parser.add_argument( 'router', metavar="", @@ -642,5 +647,7 @@ def take_action(self, parsed_args): msg = (_("Router does not contain route %s") % route) raise exceptions.CommandError(msg) attrs['routes'] = tmp_routes + if parsed_args.external_gateway: + attrs['external_gateway_info'] = {} if attrs: client.update_router(obj, **attrs) diff --git a/openstackclient/tests/unit/network/v2/test_router.py b/openstackclient/tests/unit/network/v2/test_router.py index 9183cb6364..a24a34c521 100644 --- a/openstackclient/tests/unit/network/v2/test_router.py +++ b/openstackclient/tests/unit/network/v2/test_router.py @@ -1021,3 +1021,16 @@ def test_unset_router_wrong_routes(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) + + def test_unset_router_external_gateway(self): + arglist = [ + '--external-gateway', + self._testrouter.name, + ] + verifylist = [('external_gateway', True)] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + attrs = {'external_gateway_info': {}} + self.network.update_router.assert_called_once_with( + self._testrouter, **attrs) + self.assertIsNone(result) diff --git a/releasenotes/notes/router-gateway-set-01d9c7ffe4461daf.yaml b/releasenotes/notes/router-gateway-set-01d9c7ffe4461daf.yaml index dec9d09663..9e3f19597c 100644 --- a/releasenotes/notes/router-gateway-set-01d9c7ffe4461daf.yaml +++ b/releasenotes/notes/router-gateway-set-01d9c7ffe4461daf.yaml @@ -3,5 +3,6 @@ features: - | Add support for setting the gateway information in a router, by introducing the new option ``--external-gateway`` in - ``router set`` CLI. + ``router set`` command and clearing the gateway information in a router + by introducing ``--external-gateway`` option in ``router unset`` command. [ Blueprint `neutron-client-advanced-router `_] From 9663424c7444e0f5ae54dda499a96f179d8512fd Mon Sep 17 00:00:00 2001 From: Kevin_Zheng Date: Tue, 20 Dec 2016 09:03:29 +0800 Subject: [PATCH 1426/3095] change os in command example to openstack In the current doc, the command examples are like "os server create" but the acutal command should be started with "openstack" instead of "os", it is misleading to first time users. Change-Id: Ic7686257725e1aa8e4e0d65a712eff1c079927a8 --- doc/source/command-objects/access-token.rst | 2 +- doc/source/command-objects/address-scope.rst | 10 +++++----- doc/source/command-objects/aggregate.rst | 16 ++++++++-------- doc/source/command-objects/availability-zone.rst | 2 +- doc/source/command-objects/backup.rst | 10 +++++----- doc/source/command-objects/catalog.rst | 4 ++-- doc/source/command-objects/command.rst | 2 +- doc/source/command-objects/complete.rst | 2 +- doc/source/command-objects/compute-agent.rst | 8 ++++---- doc/source/command-objects/compute-service.rst | 6 +++--- doc/source/command-objects/configuration.rst | 2 +- .../consistency-group-snapshot.rst | 8 ++++---- doc/source/command-objects/consistency-group.rst | 10 +++++----- doc/source/command-objects/console-log.rst | 2 +- doc/source/command-objects/console-url.rst | 2 +- doc/source/command-objects/consumer.rst | 10 +++++----- doc/source/command-objects/container.rst | 14 +++++++------- doc/source/command-objects/credential.rst | 10 +++++----- doc/source/command-objects/domain.rst | 10 +++++----- doc/source/command-objects/ec2-credentials.rst | 8 ++++---- doc/source/command-objects/endpoint.rst | 12 ++++++------ doc/source/command-objects/extension.rst | 2 +- 22 files changed, 76 insertions(+), 76 deletions(-) diff --git a/doc/source/command-objects/access-token.rst b/doc/source/command-objects/access-token.rst index fd22e761f8..b1400412ff 100644 --- a/doc/source/command-objects/access-token.rst +++ b/doc/source/command-objects/access-token.rst @@ -14,7 +14,7 @@ Create an access token .. program:: access token create .. code:: bash - os access token create + openstack access token create --consumer-key --consumer-secret --request-key diff --git a/doc/source/command-objects/address-scope.rst b/doc/source/command-objects/address-scope.rst index f7572d97da..9155d09ed1 100644 --- a/doc/source/command-objects/address-scope.rst +++ b/doc/source/command-objects/address-scope.rst @@ -15,7 +15,7 @@ Create new address scope .. program:: address scope create .. code:: bash - os address scope create + openstack address scope create [--project [--project-domain ]] [--ip-version ] [--share | --no-share] @@ -55,7 +55,7 @@ Delete address scope(s) .. program:: address scope delete .. code:: bash - os address scope delete + openstack address scope delete [ ...] .. _address_scope_delete-address-scope: @@ -71,7 +71,7 @@ List address scopes .. program:: address scope list .. code:: bash - os address scope list + openstack address scope list [--name ] [--ip-version ] [--project [--project-domain ]] @@ -110,7 +110,7 @@ Set address scope properties .. program:: address scope set .. code:: bash - os address scope set + openstack address scope set [--name ] [--share | --no-share] @@ -140,7 +140,7 @@ Display address scope details .. program:: address scope show .. code:: bash - os address scope show + openstack address scope show .. _address_scope_show-address-scope: diff --git a/doc/source/command-objects/aggregate.rst b/doc/source/command-objects/aggregate.rst index 642942d457..0d2926db7d 100644 --- a/doc/source/command-objects/aggregate.rst +++ b/doc/source/command-objects/aggregate.rst @@ -15,7 +15,7 @@ Add host to aggregate .. program:: aggregate add host .. code:: bash - os aggregate add host + openstack aggregate add host @@ -36,7 +36,7 @@ Create a new aggregate .. program:: aggregate create .. code:: bash - os aggregate create + openstack aggregate create [--zone ] [--property [...] ] @@ -61,7 +61,7 @@ Delete existing aggregate(s) .. program:: aggregate delete .. code:: bash - os aggregate delete + openstack aggregate delete [ ...] .. describe:: @@ -76,7 +76,7 @@ List all aggregates .. program:: aggregate list .. code:: bash - os aggregate list + openstack aggregate list [--long] .. option:: --long @@ -91,7 +91,7 @@ Remove host from aggregate .. program:: aggregate remove host .. code:: bash - os aggregate remove host + openstack aggregate remove host @@ -112,7 +112,7 @@ Set aggregate properties .. program:: aggregate set .. code:: bash - os aggregate set + openstack aggregate set [--name ] [--zone ] [--property [...] ] @@ -151,7 +151,7 @@ Display aggregate details .. program:: aggregate show .. code:: bash - os aggregate show + openstack aggregate show .. describe:: @@ -166,7 +166,7 @@ Unset aggregate properties .. program:: aggregate unset .. code-block:: bash - os aggregate unset + openstack aggregate unset [--property [...] ] diff --git a/doc/source/command-objects/availability-zone.rst b/doc/source/command-objects/availability-zone.rst index 149e10811d..d4c117a0f8 100644 --- a/doc/source/command-objects/availability-zone.rst +++ b/doc/source/command-objects/availability-zone.rst @@ -15,7 +15,7 @@ List availability zones and their status .. program availability zone list .. code:: bash - os availability zone list + openstack availability zone list [--compute] [--network] [--volume] diff --git a/doc/source/command-objects/backup.rst b/doc/source/command-objects/backup.rst index ac88708636..f892327695 100644 --- a/doc/source/command-objects/backup.rst +++ b/doc/source/command-objects/backup.rst @@ -13,7 +13,7 @@ Create new backup .. program:: backup create .. code:: bash - os backup create + openstack backup create [--container ] [--name ] [--description ] @@ -66,7 +66,7 @@ Delete backup(s) .. program:: backup delete .. code:: bash - os backup delete + openstack backup delete [--force] [ ...] @@ -90,7 +90,7 @@ List backups .. program:: backup list .. code:: bash - os backup list + openstack backup list .. _backup_list-backup: .. option:: --long @@ -106,7 +106,7 @@ Restore backup .. program:: backup restore .. code:: bash - os backup restore + openstack backup restore @@ -128,7 +128,7 @@ Display backup details .. program:: backup show .. code:: bash - os backup show + openstack backup show .. _backup_show-backup: diff --git a/doc/source/command-objects/catalog.rst b/doc/source/command-objects/catalog.rst index 4319fb111f..dccf780121 100644 --- a/doc/source/command-objects/catalog.rst +++ b/doc/source/command-objects/catalog.rst @@ -12,7 +12,7 @@ List services in the service catalog .. program:: catalog list .. code:: bash - os catalog list + openstack catalog list catalog show ------------ @@ -22,7 +22,7 @@ Display service catalog details .. program:: catalog show .. code:: bash - os catalog show + openstack catalog show .. describe:: diff --git a/doc/source/command-objects/command.rst b/doc/source/command-objects/command.rst index ac4f851438..34c07da625 100644 --- a/doc/source/command-objects/command.rst +++ b/doc/source/command-objects/command.rst @@ -14,4 +14,4 @@ List recognized commands by group .. program:: command list .. code:: bash - os command list + openstack command list diff --git a/doc/source/command-objects/complete.rst b/doc/source/command-objects/complete.rst index 8f84eec10a..20e5c41da4 100644 --- a/doc/source/command-objects/complete.rst +++ b/doc/source/command-objects/complete.rst @@ -22,4 +22,4 @@ print bash completion command .. program:: complete .. code:: bash - os complete + openstack complete diff --git a/doc/source/command-objects/compute-agent.rst b/doc/source/command-objects/compute-agent.rst index 239a24e830..e8317b4825 100644 --- a/doc/source/command-objects/compute-agent.rst +++ b/doc/source/command-objects/compute-agent.rst @@ -12,7 +12,7 @@ Create compute agent .. program:: compute agent create .. code:: bash - os compute agent create + openstack compute agent create @@ -49,7 +49,7 @@ Delete compute agent(s) .. program:: compute agent delete .. code:: bash - os compute agent delete [ ...] + openstack compute agent delete [ ...] .. _compute_agent-delete: .. describe:: @@ -64,7 +64,7 @@ List compute agents .. program:: compute agent list .. code:: bash - os compute agent list [--hypervisor ] + openstack compute agent list [--hypervisor ] .. option:: --hypervisor @@ -78,7 +78,7 @@ Set compute agent properties .. program:: agent set .. code:: bash - os compute agent set + openstack compute agent set [--agent-version ] [--url ] diff --git a/doc/source/command-objects/compute-service.rst b/doc/source/command-objects/compute-service.rst index 57dd2ba556..66347214e4 100644 --- a/doc/source/command-objects/compute-service.rst +++ b/doc/source/command-objects/compute-service.rst @@ -12,7 +12,7 @@ Delete compute service(s) .. program:: compute service delete .. code:: bash - os compute service delete + openstack compute service delete [ ...] .. _compute-service-delete: @@ -28,7 +28,7 @@ List compute services .. program:: compute service list .. code:: bash - os compute service list + openstack compute service list [--host ] [--service ] [--long] @@ -55,7 +55,7 @@ Set compute service properties .. program:: compute service set .. code:: bash - os compute service set + openstack compute service set [--enable | --disable] [--disable-reason ] [--up | --down] diff --git a/doc/source/command-objects/configuration.rst b/doc/source/command-objects/configuration.rst index 7bf054c072..0e00bbe9a0 100644 --- a/doc/source/command-objects/configuration.rst +++ b/doc/source/command-objects/configuration.rst @@ -15,7 +15,7 @@ show different configurations. .. program:: configuration show .. code:: bash - os configuration show + openstack configuration show [--mask | --unmask] .. option:: --mask diff --git a/doc/source/command-objects/consistency-group-snapshot.rst b/doc/source/command-objects/consistency-group-snapshot.rst index b7b1423e8a..3d455038ec 100644 --- a/doc/source/command-objects/consistency-group-snapshot.rst +++ b/doc/source/command-objects/consistency-group-snapshot.rst @@ -12,7 +12,7 @@ Create new consistency group snapshot. .. program:: consistency group snapshot create .. code:: bash - os consistency group snapshot create + openstack consistency group snapshot create [--consistency-group ] [--description ] [] @@ -39,7 +39,7 @@ Delete consistency group snapshot(s) .. program:: consistency group snapshot delete .. code:: bash - os consistency group snapshot delete + openstack consistency group snapshot delete [ ...] .. _consistency_group_snapshot_delete-consistency-group-snapshot: @@ -55,7 +55,7 @@ List consistency group snapshots. .. program:: consistency group snapshot list .. code:: bash - os consistency group snapshot list + openstack consistency group snapshot list [--all-projects] [--long] [--status ] @@ -87,7 +87,7 @@ Display consistency group snapshot details. .. program:: consistency group snapshot show .. code:: bash - os consistency group snapshot show + openstack consistency group snapshot show .. _consistency_group_snapshot_show-consistency-group-snapshot: diff --git a/doc/source/command-objects/consistency-group.rst b/doc/source/command-objects/consistency-group.rst index 910fbba9a1..658d942502 100644 --- a/doc/source/command-objects/consistency-group.rst +++ b/doc/source/command-objects/consistency-group.rst @@ -12,7 +12,7 @@ Create new consistency group. .. program:: consistency group create .. code:: bash - os consistency group create + openstack consistency group create --volume-type | --consistency-group-source | --consistency-group-snapshot [--description ] [--availability-zone ] @@ -52,7 +52,7 @@ Delete consistency group(s). .. program:: consistency group delete .. code:: bash - os consistency group delete + openstack consistency group delete [--force] [ ...] @@ -73,7 +73,7 @@ List consistency groups. .. program:: consistency group list .. code:: bash - os consistency group list + openstack consistency group list [--all-projects] [--long] @@ -94,7 +94,7 @@ Set consistency group properties. .. program:: consistency group set .. code:: bash - os consistency group set + openstack consistency group set [--name ] [--description ] @@ -120,7 +120,7 @@ Display consistency group details. .. program:: consistency group show .. code:: bash - os consistency group show + openstack consistency group show .. _consistency_group_show-consistency-group: diff --git a/doc/source/command-objects/console-log.rst b/doc/source/command-objects/console-log.rst index 9eafb61ae2..bcb23e7009 100644 --- a/doc/source/command-objects/console-log.rst +++ b/doc/source/command-objects/console-log.rst @@ -14,7 +14,7 @@ Show server's console output .. program:: console log show .. code:: bash - os console log show + openstack console log show [--lines ] diff --git a/doc/source/command-objects/console-url.rst b/doc/source/command-objects/console-url.rst index 08c8333248..8a5807b6a3 100644 --- a/doc/source/command-objects/console-url.rst +++ b/doc/source/command-objects/console-url.rst @@ -14,7 +14,7 @@ Show server's remote console URL .. program:: console url show .. code:: bash - os console url show + openstack console url show [--novnc | --xvpvnc | --spice] [--rdp | --serial | --mks] diff --git a/doc/source/command-objects/consumer.rst b/doc/source/command-objects/consumer.rst index 335eaea32f..d4ce5dc5b2 100644 --- a/doc/source/command-objects/consumer.rst +++ b/doc/source/command-objects/consumer.rst @@ -14,7 +14,7 @@ Create new consumer .. program:: consumer create .. code:: bash - os consumer create + openstack consumer create [--description ] .. option:: --description @@ -29,7 +29,7 @@ Delete consumer(s) .. program:: consumer delete .. code:: bash - os consumer delete + openstack consumer delete [ ...] .. describe:: @@ -44,7 +44,7 @@ List consumers .. program:: consumer list .. code:: bash - os consumer list + openstack consumer list consumer set ------------ @@ -54,7 +54,7 @@ Set consumer properties .. program:: consumer set .. code:: bash - os consumer set + openstack consumer set [--description ] @@ -74,7 +74,7 @@ Display consumer details .. program:: consumer show .. code:: bash - os consumer show + openstack consumer show .. _consumer_show-consumer: diff --git a/doc/source/command-objects/container.rst b/doc/source/command-objects/container.rst index adcf0961da..c431047293 100644 --- a/doc/source/command-objects/container.rst +++ b/doc/source/command-objects/container.rst @@ -12,7 +12,7 @@ Create new container .. program:: container create .. code:: bash - os container create + openstack container create [ ...] .. describe:: @@ -27,7 +27,7 @@ Delete container .. program:: container delete .. code:: bash - os container delete + openstack container delete [-r] | [--recursive] [ ...] @@ -47,7 +47,7 @@ List containers .. program:: container list .. code:: bash - os container list + openstack container list [--prefix ] [--marker ] [--end-marker ] @@ -87,7 +87,7 @@ Save container contents locally .. program:: container save .. code:: bash - os container save + openstack container save .. describe:: @@ -102,7 +102,7 @@ Set container properties .. program:: container set .. code:: bash - os container set + openstack container set [--property [...] ] @@ -122,7 +122,7 @@ Display container details .. program:: container show .. code:: bash - os container show + openstack container show .. describe:: @@ -137,7 +137,7 @@ Unset container properties .. program:: container unset .. code:: bash - os container unset + openstack container unset [--property ] diff --git a/doc/source/command-objects/credential.rst b/doc/source/command-objects/credential.rst index 538362a864..0b835c73f1 100644 --- a/doc/source/command-objects/credential.rst +++ b/doc/source/command-objects/credential.rst @@ -12,7 +12,7 @@ Create new credential .. program:: credential create .. code:: bash - os credential create + openstack credential create [--type ] [--project ] @@ -42,7 +42,7 @@ Delete credential(s) .. program:: credential delete .. code:: bash - os credential delete + openstack credential delete [ ...] .. _credential_delete: @@ -58,7 +58,7 @@ List credentials .. program:: credential list .. code:: bash - os credential list + openstack credential list credential set -------------- @@ -68,7 +68,7 @@ Set credential properties .. program:: credential set .. code:: bash - os credential set + openstack credential set [--user ] [--type ] [--data ] @@ -104,7 +104,7 @@ Display credential details .. program:: credential show .. code:: bash - os credential show + openstack credential show .. _credential_show: diff --git a/doc/source/command-objects/domain.rst b/doc/source/command-objects/domain.rst index 573a78cb27..70a3e73309 100644 --- a/doc/source/command-objects/domain.rst +++ b/doc/source/command-objects/domain.rst @@ -12,7 +12,7 @@ Create new domain .. program:: domain create .. code:: bash - os domain create + openstack domain create [--description ] [--enable | --disable] [--or-show] @@ -48,7 +48,7 @@ Delete domain(s) .. program:: domain delete .. code:: bash - os domain delete + openstack domain delete [ ...] .. describe:: @@ -63,7 +63,7 @@ List domains .. program:: domain list .. code:: bash - os domain list + openstack domain list domain set ---------- @@ -73,7 +73,7 @@ Set domain properties .. program:: domain set .. code:: bash - os domain set + openstack domain set [--name ] [--description ] [--enable | --disable] @@ -107,7 +107,7 @@ Display domain details .. program:: domain show .. code:: bash - os domain show + openstack domain show .. describe:: diff --git a/doc/source/command-objects/ec2-credentials.rst b/doc/source/command-objects/ec2-credentials.rst index caf7fd6901..9174b0413e 100644 --- a/doc/source/command-objects/ec2-credentials.rst +++ b/doc/source/command-objects/ec2-credentials.rst @@ -12,7 +12,7 @@ Create EC2 credentials .. program:: ec2 credentials create .. code-block:: bash - os ec2 credentials create + openstack ec2 credentials create [--project ] [--user ] [--user-domain ] @@ -52,7 +52,7 @@ Delete EC2 credentials .. program:: ec2 credentials delete .. code-block:: bash - os ec2 credentials delete + openstack ec2 credentials delete [--user ] [--user-domain ] [ ...] @@ -85,7 +85,7 @@ List EC2 credentials .. program:: ec2 credentials list .. code-block:: bash - os ec2 credentials list + openstack ec2 credentials list [--user ] [--user-domain ] @@ -112,7 +112,7 @@ Display EC2 credentials details .. program:: ec2 credentials show .. code-block:: bash - os ec2 credentials show + openstack ec2 credentials show [--user ] [--user-domain ] diff --git a/doc/source/command-objects/endpoint.rst b/doc/source/command-objects/endpoint.rst index c058c8444e..d6eb362af5 100644 --- a/doc/source/command-objects/endpoint.rst +++ b/doc/source/command-objects/endpoint.rst @@ -14,7 +14,7 @@ Create new endpoint .. program:: endpoint create .. code:: bash - os endpoint create + openstack endpoint create --publicurl [--adminurl ] [--internalurl ] @@ -47,7 +47,7 @@ Create new endpoint .. program:: endpoint create .. code:: bash - os endpoint create + openstack endpoint create [--region ] [--enable | --disable] @@ -86,7 +86,7 @@ Delete endpoint(s) .. program:: endpoint delete .. code:: bash - os endpoint delete + openstack endpoint delete [ ...] .. _endpoint_delete-endpoint: @@ -102,7 +102,7 @@ List endpoints .. program:: endpoint list .. code:: bash - os endpoint list + openstack endpoint list [--service ] [--region ] @@ -142,7 +142,7 @@ Set endpoint properties .. program:: endpoint set .. code:: bash - os endpoint set + openstack endpoint set [--region ] [--interface ] [--url ] @@ -187,7 +187,7 @@ Display endpoint details .. program:: endpoint show .. code:: bash - os endpoint show + openstack endpoint show .. _endpoint_show-endpoint: diff --git a/doc/source/command-objects/extension.rst b/doc/source/command-objects/extension.rst index dff30fa137..b7ba5c0a42 100644 --- a/doc/source/command-objects/extension.rst +++ b/doc/source/command-objects/extension.rst @@ -13,7 +13,7 @@ List API extensions .. program:: extension list .. code:: bash - os extension list + openstack extension list [--compute] [--identity] [--network] From 847da51deaffcbb4e0f1c3825e60d8181b31b120 Mon Sep 17 00:00:00 2001 From: Kevin_Zheng Date: Tue, 20 Dec 2016 09:18:55 +0800 Subject: [PATCH 1427/3095] change os in command example to openstack(2) In the current doc, the command examples are like "os server create" but the acutal command should be started with "openstack" instead of "os", it is misleading to first time users. Change-Id: I80c96f0938cec382f2f714a500138bd5a2c81aa0 --- .../command-objects/federation-protocol.rst | 10 +++++----- doc/source/command-objects/flavor.rst | 12 +++++------ .../command-objects/floating-ip-pool.rst | 2 +- doc/source/command-objects/floating-ip.rst | 8 ++++---- doc/source/command-objects/group.rst | 14 ++++++------- doc/source/command-objects/host.rst | 6 +++--- .../command-objects/hypervisor-stats.rst | 2 +- doc/source/command-objects/hypervisor.rst | 4 ++-- .../command-objects/identity-provider.rst | 10 +++++----- doc/source/command-objects/image.rst | 18 ++++++++--------- .../command-objects/ip-availability.rst | 4 ++-- doc/source/command-objects/ip-fixed.rst | 4 ++-- .../command-objects/ip-floating-pool.rst | 2 +- doc/source/command-objects/ip-floating.rst | 12 +++++------ doc/source/command-objects/keypair.rst | 8 ++++---- doc/source/command-objects/limits.rst | 2 +- doc/source/command-objects/mapping.rst | 10 +++++----- doc/source/command-objects/module.rst | 2 +- doc/source/command-objects/network-agent.rst | 8 ++++---- .../command-objects/network-qos-policy.rst | 10 +++++----- doc/source/command-objects/network-rbac.rst | 10 +++++----- .../command-objects/network-segment.rst | 10 +++++----- .../network-service-provider.rst | 2 +- doc/source/command-objects/network.rst | 10 +++++----- .../command-objects/object-store-account.rst | 6 +++--- doc/source/command-objects/object.rst | 14 ++++++------- doc/source/command-objects/policy.rst | 10 +++++----- doc/source/command-objects/port.rst | 12 +++++------ doc/source/command-objects/project.rst | 12 +++++------ doc/source/command-objects/quota.rst | 8 ++++---- doc/source/command-objects/region.rst | 10 +++++----- doc/source/command-objects/request-token.rst | 4 ++-- .../command-objects/role-assignment.rst | 2 +- doc/source/command-objects/role.rst | 14 ++++++------- doc/source/command-objects/router.rst | 20 +++++++++---------- 35 files changed, 146 insertions(+), 146 deletions(-) diff --git a/doc/source/command-objects/federation-protocol.rst b/doc/source/command-objects/federation-protocol.rst index 911d4e99c6..e1f98174c0 100644 --- a/doc/source/command-objects/federation-protocol.rst +++ b/doc/source/command-objects/federation-protocol.rst @@ -14,7 +14,7 @@ Create new federation protocol .. program:: federation protocol create .. code:: bash - os federation protocol create + openstack federation protocol create --identity-provider --mapping @@ -39,7 +39,7 @@ Delete federation protocol(s) .. program:: federation protocol delete .. code:: bash - os federation protocol delete + openstack federation protocol delete --identity-provider [ ...] @@ -59,7 +59,7 @@ List federation protocols .. program:: federation protocol list .. code:: bash - os federation protocol list + openstack federation protocol list --identity-provider .. option:: --identity-provider @@ -74,7 +74,7 @@ Set federation protocol properties .. program:: federation protocol set .. code:: bash - os federation protocol set + openstack federation protocol set --identity-provider [--mapping ] @@ -99,7 +99,7 @@ Display federation protocol details .. program:: federation protocol show .. code:: bash - os federation protocol show + openstack federation protocol show --identity-provider diff --git a/doc/source/command-objects/flavor.rst b/doc/source/command-objects/flavor.rst index 9ea504c1be..0900f2edf7 100644 --- a/doc/source/command-objects/flavor.rst +++ b/doc/source/command-objects/flavor.rst @@ -12,7 +12,7 @@ Create new flavor .. program:: flavor create .. code:: bash - os flavor create + openstack flavor create [--id ] [--ram ] [--disk ] @@ -89,7 +89,7 @@ Delete flavor(s) .. program:: flavor delete .. code:: bash - os flavor delete + openstack flavor delete [ ...] .. _flavor_delete-flavor: @@ -105,7 +105,7 @@ List flavors .. program:: flavor list .. code:: bash - os flavor list + openstack flavor list [--public | --private | --all] [--long] [--marker ] @@ -143,7 +143,7 @@ Set flavor properties .. program:: flavor set .. code:: bash - os flavor set + openstack flavor set [--property [...] ] [--project ] [--project-domain ] @@ -174,7 +174,7 @@ Display flavor details .. program:: flavor show .. code:: bash - os flavor show + openstack flavor show .. _flavor_show-flavor: @@ -190,7 +190,7 @@ Unset flavor properties .. program:: flavor unset .. code:: bash - os flavor unset + openstack flavor unset [--property [...] ] [--project ] [--project-domain ] diff --git a/doc/source/command-objects/floating-ip-pool.rst b/doc/source/command-objects/floating-ip-pool.rst index 6f074d2d34..9213b86dd0 100644 --- a/doc/source/command-objects/floating-ip-pool.rst +++ b/doc/source/command-objects/floating-ip-pool.rst @@ -12,4 +12,4 @@ List pools of floating IP addresses .. program:: floating ip pool list .. code:: bash - os floating ip pool list + openstack floating ip pool list diff --git a/doc/source/command-objects/floating-ip.rst b/doc/source/command-objects/floating-ip.rst index 57dc0ccbab..5a38bc1b04 100644 --- a/doc/source/command-objects/floating-ip.rst +++ b/doc/source/command-objects/floating-ip.rst @@ -12,7 +12,7 @@ Create floating IP .. program:: floating ip create .. code:: bash - os floating ip create + openstack floating ip create [--subnet ] [--port ] [--floating-ip-address ] @@ -71,7 +71,7 @@ Delete floating IP(s) .. program:: floating ip delete .. code:: bash - os floating ip delete [ ...] + openstack floating ip delete [ ...] .. describe:: @@ -85,7 +85,7 @@ List floating IP(s) .. program:: floating ip list .. code:: bash - os floating ip list + openstack floating ip list [--network ] [--port ] [--fixed-ip-address ] @@ -151,7 +151,7 @@ Display floating IP details .. program:: floating ip show .. code:: bash - os floating ip show + openstack floating ip show .. describe:: diff --git a/doc/source/command-objects/group.rst b/doc/source/command-objects/group.rst index 0f2c5cd10b..a4b266352e 100644 --- a/doc/source/command-objects/group.rst +++ b/doc/source/command-objects/group.rst @@ -48,7 +48,7 @@ Check user membership in group .. program:: group contains user .. code:: bash - os group contains user + openstack group contains user [--group-domain ] [--user-domain ] @@ -84,7 +84,7 @@ Create new group .. program:: group create .. code:: bash - os group create + openstack group create [--domain ] [--description ] [--or-show] @@ -116,7 +116,7 @@ Delete group .. program:: group delete .. code:: bash - os group delete + openstack group delete [--domain ] [ ...] @@ -136,7 +136,7 @@ List groups .. program:: group list .. code:: bash - os group list + openstack group list [--domain ] [--user [--user-domain ]] [--long] @@ -168,7 +168,7 @@ Remove user from group .. program:: group remove user .. code:: bash - os group remove user + openstack group remove user [--group-domain ] [--user-domain ] @@ -204,7 +204,7 @@ Set group properties .. program:: group set .. code:: bash - os group set + openstack group set [--domain ] [--name ] [--description ] @@ -234,7 +234,7 @@ Display group details .. program:: group show .. code:: bash - os group show + openstack group show [--domain ] diff --git a/doc/source/command-objects/host.rst b/doc/source/command-objects/host.rst index 409b834b8d..cbf3439827 100644 --- a/doc/source/command-objects/host.rst +++ b/doc/source/command-objects/host.rst @@ -14,7 +14,7 @@ List hosts .. program:: host list .. code:: bash - os host list + openstack host list [--zone ] .. option:: --zone @@ -29,7 +29,7 @@ Set host properties .. program:: host set .. code:: bash - os host set + openstack host set [--enable | --disable] [--enable-maintenance | --disable-maintenance] @@ -64,7 +64,7 @@ Display host details .. program:: host show .. code:: bash - os host show + openstack host show .. describe:: diff --git a/doc/source/command-objects/hypervisor-stats.rst b/doc/source/command-objects/hypervisor-stats.rst index 06edf7a1e9..89faf135f4 100644 --- a/doc/source/command-objects/hypervisor-stats.rst +++ b/doc/source/command-objects/hypervisor-stats.rst @@ -12,5 +12,5 @@ Display hypervisor stats details .. program:: hypervisor stats show .. code:: bash - os hypervisor stats show + openstack hypervisor stats show diff --git a/doc/source/command-objects/hypervisor.rst b/doc/source/command-objects/hypervisor.rst index 3053a7584b..9db384a247 100644 --- a/doc/source/command-objects/hypervisor.rst +++ b/doc/source/command-objects/hypervisor.rst @@ -12,7 +12,7 @@ List hypervisors .. program:: hypervisor list .. code:: bash - os hypervisor list + openstack hypervisor list [--matching ] [--long] @@ -32,7 +32,7 @@ Display hypervisor details .. program:: hypervisor show .. code:: bash - os hypervisor show + openstack hypervisor show .. _hypervisor_show-flavor: diff --git a/doc/source/command-objects/identity-provider.rst b/doc/source/command-objects/identity-provider.rst index f772511d50..0c2d02bd27 100644 --- a/doc/source/command-objects/identity-provider.rst +++ b/doc/source/command-objects/identity-provider.rst @@ -14,7 +14,7 @@ Create new identity provider .. program:: identity provider create .. code:: bash - os identity provider create + openstack identity provider create [--remote-id [...] | --remote-id-file ] [--description ] [--enable | --disable] @@ -54,7 +54,7 @@ Delete identity provider(s) .. program:: identity provider delete .. code:: bash - os identity provider delete + openstack identity provider delete [ ...] .. describe:: @@ -69,7 +69,7 @@ List identity providers .. program:: identity provider list .. code:: bash - os identity provider list + openstack identity provider list identity provider set --------------------- @@ -79,7 +79,7 @@ Set identity provider properties .. program:: identity provider set .. code:: bash - os identity provider set + openstack identity provider set [--remote-id [...] | --remote-id-file ] [--description ] [--enable | --disable] @@ -119,7 +119,7 @@ Display identity provider details .. program:: identity provider show .. code:: bash - os identity provider show + openstack identity provider show .. describe:: diff --git a/doc/source/command-objects/image.rst b/doc/source/command-objects/image.rst index 9564f099e6..1913355ebf 100644 --- a/doc/source/command-objects/image.rst +++ b/doc/source/command-objects/image.rst @@ -14,7 +14,7 @@ Associate project with image .. program:: image add project .. code:: bash - os image add project + openstack image add project [--project-domain ] @@ -41,7 +41,7 @@ Create/upload an image .. program:: image create .. code:: bash - os image create + openstack image create [--id ] [--store ] [--container-format ] @@ -175,7 +175,7 @@ Delete image(s) .. program:: image delete .. code:: bash - os image delete + openstack image delete .. describe:: @@ -190,7 +190,7 @@ List available images .. program:: image list .. code:: bash - os image list + openstack image list [--public | --private | --shared] [--property ] [--long] @@ -244,7 +244,7 @@ Disassociate project with image .. program:: image remove project .. code:: bash - os image remove remove + openstack image remove remove [--project-domain ] @@ -269,7 +269,7 @@ Save an image locally .. program:: image save .. code:: bash - os image save + openstack image save --file @@ -291,7 +291,7 @@ Set image properties .. program:: image set .. code:: bash - os image set + openstack image set [--name ] [--min-disk ] [--min-ram ] @@ -495,7 +495,7 @@ Display image details .. program:: image show .. code:: bash - os image show + openstack image show .. describe:: @@ -512,7 +512,7 @@ Unset image tags or properties .. program:: image unset .. code:: bash - os image set + openstack image set [--tag ] [--property ] diff --git a/doc/source/command-objects/ip-availability.rst b/doc/source/command-objects/ip-availability.rst index f200ab13a6..73d0599a43 100644 --- a/doc/source/command-objects/ip-availability.rst +++ b/doc/source/command-objects/ip-availability.rst @@ -18,7 +18,7 @@ number of allocated IP addresses from that pool. .. program:: ip availability list .. code:: bash - os ip availability list + openstack ip availability list [--ip-version {4,6}] [--project ] @@ -51,7 +51,7 @@ subnet within the network as well. .. program:: ip availability show .. code:: bash - os ip availability show + openstack ip availability show .. _ip_availability_show-network diff --git a/doc/source/command-objects/ip-fixed.rst b/doc/source/command-objects/ip-fixed.rst index c2447b2890..f5b11dc61b 100644 --- a/doc/source/command-objects/ip-fixed.rst +++ b/doc/source/command-objects/ip-fixed.rst @@ -13,7 +13,7 @@ Add fixed IP address to server .. program:: ip fixed add .. code:: bash - os ip fixed add + openstack ip fixed add @@ -34,7 +34,7 @@ Remove fixed IP address from server .. program:: ip fixed remove .. code:: bash - os ip fixed remove + openstack ip fixed remove diff --git a/doc/source/command-objects/ip-floating-pool.rst b/doc/source/command-objects/ip-floating-pool.rst index 310974c619..6d00355ab4 100644 --- a/doc/source/command-objects/ip-floating-pool.rst +++ b/doc/source/command-objects/ip-floating-pool.rst @@ -13,4 +13,4 @@ List pools of floating IP addresses .. program:: ip floating pool list .. code:: bash - os ip floating pool list + openstack ip floating pool list diff --git a/doc/source/command-objects/ip-floating.rst b/doc/source/command-objects/ip-floating.rst index 7b38a382d8..4e5f7b008a 100644 --- a/doc/source/command-objects/ip-floating.rst +++ b/doc/source/command-objects/ip-floating.rst @@ -13,7 +13,7 @@ Add floating IP address to server .. program:: ip floating add .. code:: bash - os ip floating add + openstack ip floating add @@ -34,7 +34,7 @@ Create new floating IP address .. program:: ip floating create .. code:: bash - os ip floating create + openstack ip floating create [--subnet ] [--port ] [--floating-ip-address ] @@ -74,7 +74,7 @@ Delete floating IP(s) .. program:: ip floating delete .. code:: bash - os ip floating delete + openstack ip floating delete [ ...] .. describe:: @@ -90,7 +90,7 @@ List floating IP addresses .. program:: ip floating list .. code:: bash - os ip floating list + openstack ip floating list ip floating remove ------------------ @@ -101,7 +101,7 @@ Remove floating IP address from server .. program:: ip floating remove .. code:: bash - os ip floating remove + openstack ip floating remove @@ -122,7 +122,7 @@ Display floating IP details .. program:: ip floating show .. code:: bash - os ip floating show + openstack ip floating show .. describe:: diff --git a/doc/source/command-objects/keypair.rst b/doc/source/command-objects/keypair.rst index c9bf085b44..47acf7f2c1 100644 --- a/doc/source/command-objects/keypair.rst +++ b/doc/source/command-objects/keypair.rst @@ -17,7 +17,7 @@ Create new public or private key for server ssh access .. program:: keypair create .. code:: bash - os keypair create + openstack keypair create [--public-key ] @@ -37,7 +37,7 @@ Delete public or private key(s) .. program:: keypair delete .. code:: bash - os keypair delete + openstack keypair delete [ ...] .. describe:: @@ -52,7 +52,7 @@ List key fingerprints .. program:: keypair list .. code:: bash - os keypair list + openstack keypair list keypair show ------------ @@ -62,7 +62,7 @@ Display key details .. program:: keypair show .. code:: bash - os keypair show + openstack keypair show [--public-key] diff --git a/doc/source/command-objects/limits.rst b/doc/source/command-objects/limits.rst index ef4171ac7d..9a302fbfd9 100644 --- a/doc/source/command-objects/limits.rst +++ b/doc/source/command-objects/limits.rst @@ -14,7 +14,7 @@ Show compute and block storage limits .. program:: limits show .. code:: bash - os limits show + openstack limits show --absolute | --rate [--reserved] [--project ] diff --git a/doc/source/command-objects/mapping.rst b/doc/source/command-objects/mapping.rst index 7f61366e55..1f657ed282 100644 --- a/doc/source/command-objects/mapping.rst +++ b/doc/source/command-objects/mapping.rst @@ -14,7 +14,7 @@ Create new mapping .. program:: mapping create .. code:: bash - os mapping create + openstack mapping create --rules @@ -35,7 +35,7 @@ Delete mapping(s) .. program:: mapping delete .. code:: bash - os mapping delete + openstack mapping delete [ ...] .. _mapping_delete-mapping: @@ -51,7 +51,7 @@ List mappings .. program:: mapping list .. code:: bash - os mapping list + openstack mapping list mapping set ----------- @@ -61,7 +61,7 @@ Set mapping properties .. program:: mapping set .. code:: bash - os mapping set + openstack mapping set [--rules ] @@ -82,7 +82,7 @@ Display mapping details .. program:: mapping show .. code:: bash - os mapping show + openstack mapping show .. _mapping_show-mapping: diff --git a/doc/source/command-objects/module.rst b/doc/source/command-objects/module.rst index c3bc137287..f4b32e7589 100644 --- a/doc/source/command-objects/module.rst +++ b/doc/source/command-objects/module.rst @@ -14,7 +14,7 @@ List module versions .. program:: module list .. code:: bash - os module list + openstack module list [--all] .. option:: --all diff --git a/doc/source/command-objects/network-agent.rst b/doc/source/command-objects/network-agent.rst index 9d158ba094..b95f2e5074 100644 --- a/doc/source/command-objects/network-agent.rst +++ b/doc/source/command-objects/network-agent.rst @@ -18,7 +18,7 @@ Delete network agent(s) .. program:: network agent delete .. code:: bash - os network agent delete + openstack network agent delete [ ...] .. _network_agent_delete-network-agent: @@ -34,7 +34,7 @@ List network agents .. program:: network agent list .. code:: bash - os network agent list + openstack network agent list network agent set ----------------- @@ -44,7 +44,7 @@ Set network agent properties .. program:: network agent set .. code:: bash - os network agent set + openstack network agent set [--description ] [--enable | --disable] @@ -74,7 +74,7 @@ Display network agent details .. program:: network agent show .. code:: bash - os network agent show + openstack network agent show .. _network_agent_show-network-agent: diff --git a/doc/source/command-objects/network-qos-policy.rst b/doc/source/command-objects/network-qos-policy.rst index f1e549766b..cf594497a8 100644 --- a/doc/source/command-objects/network-qos-policy.rst +++ b/doc/source/command-objects/network-qos-policy.rst @@ -15,7 +15,7 @@ Create new Network QoS policy .. program:: network qos policy create .. code:: bash - os network qos policy create + openstack network qos policy create [--description ] [--share | --no-share] [--project ] @@ -55,7 +55,7 @@ Delete Network QoS policy .. program:: network qos policy delete .. code:: bash - os network qos policy delete + openstack network qos policy delete [ ...] .. describe:: @@ -70,7 +70,7 @@ List Network QoS policies .. program:: network qos policy list .. code:: bash - os network qos policy list + openstack network qos policy list network qos policy set ---------------------- @@ -80,7 +80,7 @@ Set Network QoS policy properties .. program:: network qos policy set .. code:: bash - os network qos policy set + openstack network qos policy set [--name ] [--description ] [--share | --no-share] @@ -114,7 +114,7 @@ Display Network QoS policy details .. program:: network qos policy show .. code:: bash - os network qos policy show + openstack network qos policy show .. describe:: diff --git a/doc/source/command-objects/network-rbac.rst b/doc/source/command-objects/network-rbac.rst index 113242f471..33fa6af40e 100644 --- a/doc/source/command-objects/network-rbac.rst +++ b/doc/source/command-objects/network-rbac.rst @@ -16,7 +16,7 @@ Create network RBAC policy .. program:: network rbac create .. code:: bash - os network rbac create + openstack network rbac create --type --action --target-project [--target-project-domain ] @@ -62,7 +62,7 @@ Delete network RBAC policy(s) .. program:: network rbac delete .. code:: bash - os network rbac delete + openstack network rbac delete [ ...] .. _network_rbac_delete-rbac-policy: @@ -78,7 +78,7 @@ List network RBAC policies .. program:: network rbac list .. code:: bash - os network rbac list + openstack network rbac list network rbac set ---------------- @@ -88,7 +88,7 @@ Set network RBAC policy properties .. program:: network rbac set .. code:: bash - os network rbac set + openstack network rbac set [--target-project [--target-project-domain ]] @@ -114,7 +114,7 @@ Display network RBAC policy details .. program:: network rbac show .. code:: bash - os network rbac show + openstack network rbac show .. _network_rbac_show-rbac-policy: diff --git a/doc/source/command-objects/network-segment.rst b/doc/source/command-objects/network-segment.rst index d0434651fd..d6a66aa0b9 100644 --- a/doc/source/command-objects/network-segment.rst +++ b/doc/source/command-objects/network-segment.rst @@ -17,7 +17,7 @@ Create new network segment .. program:: network segment create .. code:: bash - os network segment create + openstack network segment create [--description ] [--physical-network ] [--segment ] @@ -62,7 +62,7 @@ Delete network segment(s) .. program:: network segment delete .. code:: bash - os network segment delete + openstack network segment delete [ ...] .. _network_segment_delete-segment: @@ -78,7 +78,7 @@ List network segments .. program:: network segment list .. code:: bash - os network segment list + openstack network segment list [--long] [--network ] @@ -98,7 +98,7 @@ Set network segment properties .. program:: network segment set .. code:: bash - os network segment set + openstack network segment set [--description ] [--name ] @@ -124,7 +124,7 @@ Display network segment details .. program:: network segment show .. code:: bash - os network segment show + openstack network segment show .. _network_segment_show-segment: diff --git a/doc/source/command-objects/network-service-provider.rst b/doc/source/command-objects/network-service-provider.rst index 344bb88019..8db2ab448b 100644 --- a/doc/source/command-objects/network-service-provider.rst +++ b/doc/source/command-objects/network-service-provider.rst @@ -15,4 +15,4 @@ List service providers .. program:: network service provider list .. code:: bash - os network service provider list + openstack network service provider list diff --git a/doc/source/command-objects/network.rst b/doc/source/command-objects/network.rst index 06a5247a87..dc597443ce 100644 --- a/doc/source/command-objects/network.rst +++ b/doc/source/command-objects/network.rst @@ -19,7 +19,7 @@ Create new network .. program:: network create .. code:: bash - os network create + openstack network create [--project [--project-domain ]] [--enable | --disable] [--share | --no-share] @@ -176,7 +176,7 @@ Delete network(s) .. program:: network delete .. code:: bash - os network delete + openstack network delete [ ...] .. _network_delete-network: @@ -192,7 +192,7 @@ List networks .. program:: network list .. code:: bash - os network list + openstack network list [--external | --internal] [--long] [--name ] @@ -300,7 +300,7 @@ Set network properties .. program:: network set .. code:: bash - os network set + openstack network set [--name ] [--enable | --disable] [--share | --no-share] @@ -407,7 +407,7 @@ Display network details .. program:: network show .. code:: bash - os network show + openstack network show .. _network_show-network: diff --git a/doc/source/command-objects/object-store-account.rst b/doc/source/command-objects/object-store-account.rst index ba37078ed6..e8f09d458c 100644 --- a/doc/source/command-objects/object-store-account.rst +++ b/doc/source/command-objects/object-store-account.rst @@ -12,7 +12,7 @@ Set account properties .. program:: object store account set .. code:: bash - os object store account set + openstack object store account set [--property [...] ] .. option:: --property @@ -27,7 +27,7 @@ Display account details .. program:: object store account show .. code:: bash - os object store account show + openstack object store account show object store account unset -------------------------- @@ -37,7 +37,7 @@ Unset account properties .. program:: object store account unset .. code:: bash - os object store account unset + openstack object store account unset [--property ] .. option:: --property diff --git a/doc/source/command-objects/object.rst b/doc/source/command-objects/object.rst index d02ac9bc07..0a32a827b6 100644 --- a/doc/source/command-objects/object.rst +++ b/doc/source/command-objects/object.rst @@ -12,7 +12,7 @@ Upload object to container .. program:: object create .. code:: bash - os object create + openstack object create [--name ] [ ...] @@ -37,7 +37,7 @@ Delete object from container .. program:: object delete .. code:: bash - os object delete + openstack object delete [ ...] @@ -57,7 +57,7 @@ List objects .. program object list .. code:: bash - os object list + openstack object list [--prefix ] [--delimiter ] [--marker ] @@ -107,7 +107,7 @@ Save object locally .. program:: object save .. code:: bash - os object save + openstack object save [--file ] @@ -132,7 +132,7 @@ Set object properties .. program:: object set .. code:: bash - os object set + openstack object set [--property [...] ] @@ -157,7 +157,7 @@ Display object details .. program:: object show .. code:: bash - os object show + openstack object show @@ -177,7 +177,7 @@ Unset object properties .. program:: object unset .. code:: bash - os object unset + openstack object unset [--property ] diff --git a/doc/source/command-objects/policy.rst b/doc/source/command-objects/policy.rst index 757b1c53ad..deddf2c438 100644 --- a/doc/source/command-objects/policy.rst +++ b/doc/source/command-objects/policy.rst @@ -12,7 +12,7 @@ Create new policy .. program:: policy create .. code:: bash - os policy create + openstack policy create [--type ] @@ -32,7 +32,7 @@ Delete policy(s) .. program:: policy delete .. code:: bash - os policy delete + openstack policy delete [ ...] .. describe:: @@ -47,7 +47,7 @@ List policies .. program:: policy list .. code:: bash - os policy list + openstack policy list [--long] .. option:: --long @@ -62,7 +62,7 @@ Set policy properties .. program:: policy set .. code:: bash - os policy set + openstack policy set [--type ] [--rules ] @@ -87,7 +87,7 @@ Display policy details .. program:: policy show .. code:: bash - os policy show + openstack policy show .. describe:: diff --git a/doc/source/command-objects/port.rst b/doc/source/command-objects/port.rst index 73c53290be..3f6e3fc02c 100644 --- a/doc/source/command-objects/port.rst +++ b/doc/source/command-objects/port.rst @@ -16,7 +16,7 @@ Create new port .. program:: port create .. code:: bash - os port create + openstack port create --network [--description ] [--fixed-ip subnet=,ip-address=] @@ -127,7 +127,7 @@ Delete port(s) .. program:: port delete .. code:: bash - os port delete + openstack port delete [ ...] .. _port_delete-port: @@ -143,7 +143,7 @@ List ports .. program:: port list .. code:: bash - os port list + openstack port list [--device-owner ] [--router | --server ] [--network ] @@ -183,7 +183,7 @@ Set port properties .. program:: port set .. code:: bash - os port set + openstack port set [--description ] [--fixed-ip subnet=,ip-address=] [--no-fixed-ip] @@ -294,7 +294,7 @@ Display port details .. program:: port show .. code:: bash - os port show + openstack port show .. _port_show-port: @@ -310,7 +310,7 @@ Unset port properties .. program:: port unset .. code:: bash - os port unset + openstack port unset [--fixed-ip subnet=,ip-address= [...]] [--binding-profile [...]] [--security-group [...]] diff --git a/doc/source/command-objects/project.rst b/doc/source/command-objects/project.rst index f76f79ff53..1111fa59bf 100644 --- a/doc/source/command-objects/project.rst +++ b/doc/source/command-objects/project.rst @@ -12,7 +12,7 @@ Create new project .. program:: project create .. code:: bash - os project create + openstack project create [--domain ] [--parent ] [--description ] @@ -69,7 +69,7 @@ Delete project(s) .. program:: project delete .. code:: bash - os project delete + openstack project delete [--domain ] [ ...] @@ -92,7 +92,7 @@ List projects .. program:: project list .. code:: bash - os project list + openstack project list [--domain ] [--user ] [--long] @@ -121,7 +121,7 @@ Set project properties .. program:: project set .. code:: bash - os project set + openstack project set [--name ] [--domain ] [--description ] @@ -171,7 +171,7 @@ Display project details .. program:: project show .. code:: bash - os project show + openstack project show [--domain ] @@ -208,7 +208,7 @@ Unset project properties .. program:: project unset .. code:: bash - os project unset + openstack project unset --property [--property ...] diff --git a/doc/source/command-objects/quota.rst b/doc/source/command-objects/quota.rst index 4dc5b17150..70e28dd8cf 100644 --- a/doc/source/command-objects/quota.rst +++ b/doc/source/command-objects/quota.rst @@ -15,7 +15,7 @@ Set quotas for project .. program:: quota set .. code:: bash - os quota set + openstack quota set # Compute settings [--cores ] [--fixed-ips ] @@ -58,7 +58,7 @@ Set quotas for class .. code:: bash - os quota set + openstack quota set --class # Compute settings [--cores ] @@ -216,7 +216,7 @@ Show quotas for project or class .. program:: quota show .. code:: bash - os quota show + openstack quota show [--default] [] @@ -232,7 +232,7 @@ Show quotas for project or class .. code:: bash - os quota show + openstack quota show --class [] diff --git a/doc/source/command-objects/region.rst b/doc/source/command-objects/region.rst index b74821a9db..d2c63c1bd8 100644 --- a/doc/source/command-objects/region.rst +++ b/doc/source/command-objects/region.rst @@ -12,7 +12,7 @@ Create new region .. program:: region create .. code:: bash - os region create + openstack region create [--parent-region ] [--description ] @@ -38,7 +38,7 @@ Delete region(s) .. program:: region delete .. code:: bash - os region delete + openstack region delete [ ...] .. _region_delete-region-id: @@ -54,7 +54,7 @@ List regions .. program:: region list .. code:: bash - os region list + openstack region list [--parent-region ] .. option:: --parent-region @@ -69,7 +69,7 @@ Set region properties .. program:: region set .. code:: bash - os region set + openstack region set [--parent-region ] [--description ] @@ -95,7 +95,7 @@ Display region details .. program:: region show .. code:: bash - os region show + openstack region show .. _region_show-region-id: diff --git a/doc/source/command-objects/request-token.rst b/doc/source/command-objects/request-token.rst index 84081cb17b..ec72dd146b 100644 --- a/doc/source/command-objects/request-token.rst +++ b/doc/source/command-objects/request-token.rst @@ -14,7 +14,7 @@ Authorize a request token .. program:: request token authorize .. code:: bash - os request token authorize + openstack request token authorize --request-key --role @@ -34,7 +34,7 @@ Create a request token .. program:: request token create .. code:: bash - os request token create + openstack request token create --consumer-key --consumer-secret --project diff --git a/doc/source/command-objects/role-assignment.rst b/doc/source/command-objects/role-assignment.rst index dc970ed5b0..b29f32c690 100644 --- a/doc/source/command-objects/role-assignment.rst +++ b/doc/source/command-objects/role-assignment.rst @@ -12,7 +12,7 @@ List role assignments .. program:: role assignment list .. code:: bash - os role assignment list + openstack role assignment list [--role ] [--role-domain ] [--user ] diff --git a/doc/source/command-objects/role.rst b/doc/source/command-objects/role.rst index 2ff1f13a89..fe3126c0f7 100644 --- a/doc/source/command-objects/role.rst +++ b/doc/source/command-objects/role.rst @@ -12,7 +12,7 @@ Add role assignment to a user or group in a project or domain .. program:: role add .. code:: bash - os role add + openstack role add --domain | --project [--project-domain ] --user [--user-domain ] | --group [--group-domain ] --role-domain @@ -85,7 +85,7 @@ Create new role .. program:: role create .. code:: bash - os role create + openstack role create [--or-show] [--domain ] @@ -114,7 +114,7 @@ Delete role(s) .. program:: role delete .. code:: bash - os role delete + openstack role delete [ ...] [--domain ] @@ -136,7 +136,7 @@ List roles .. program:: role list .. code:: bash - os role list + openstack role list --domain | --project [--project-domain ] --user [--user-domain ] | --group [--group-domain ] --inherited @@ -209,7 +209,7 @@ Remove role assignment from domain/project : user/group .. program:: role remove .. code:: bash - os role remove + openstack role remove --domain | --project [--project-domain ] --user [--user-domain ] | --group [--group-domain ] --role-domain @@ -284,7 +284,7 @@ Set role properties .. program:: role set .. code:: bash - os role set + openstack role set [--name ] [--domain ] @@ -311,7 +311,7 @@ Display role details .. program:: role show .. code:: bash - os role show + openstack role show [--domain ] diff --git a/doc/source/command-objects/router.rst b/doc/source/command-objects/router.rst index 16a8258dfd..dfade677e0 100644 --- a/doc/source/command-objects/router.rst +++ b/doc/source/command-objects/router.rst @@ -16,7 +16,7 @@ Add a port to a router .. program:: router add port .. code:: bash - os router add port + openstack router add port @@ -38,7 +38,7 @@ Add a subnet to a router .. program:: router add subnet .. code:: bash - os router add subnet + openstack router add subnet @@ -60,7 +60,7 @@ Create new router .. program:: router create .. code:: bash - os router create + openstack router create [--project [--project-domain ]] [--enable | --disable] [--distributed] @@ -117,7 +117,7 @@ Delete router(s) .. program:: router delete .. code:: bash - os router delete + openstack router delete [ ...] .. _router_delete-router: @@ -133,7 +133,7 @@ List routers .. program:: router list .. code:: bash - os router list + openstack router list [--name ] [--enable | --disable] [--long] @@ -172,7 +172,7 @@ Remove a port from a router .. program:: router remove port .. code:: bash - os router remove port + openstack router remove port @@ -194,7 +194,7 @@ Remove a subnet from a router .. program:: router remove subnet .. code:: bash - os router remove subnet + openstack router remove subnet @@ -216,7 +216,7 @@ Set router properties .. program:: router set .. code:: bash - os router set + openstack router set [--name ] [--enable | --disable] [--distributed | --centralized] @@ -300,7 +300,7 @@ Display router details .. program:: router show .. code:: bash - os router show + openstack router show .. _router_show-router: @@ -316,7 +316,7 @@ Unset router properties .. program:: router unset .. code:: bash - os router unset + openstack router unset [--route destination=,gateway=] From 446e6f2fa8a14efd49b42e09c13a45e9e1eec22c Mon Sep 17 00:00:00 2001 From: Kevin_Zheng Date: Tue, 20 Dec 2016 09:26:15 +0800 Subject: [PATCH 1428/3095] change os in command example to openstack(3) In the current doc, the command examples are like "os server create" but the acutal command should be started with "openstack" instead of "os", it is misleading to first time users. Change-Id: Ie67c0152d8ff8b7c456f91dc8b9a9164437ee9d1 --- .../command-objects/security-group-rule.rst | 8 +-- doc/source/command-objects/security-group.rst | 10 +-- doc/source/command-objects/server-backup.rst | 2 +- doc/source/command-objects/server-group.rst | 8 +-- doc/source/command-objects/server-image.rst | 2 +- doc/source/command-objects/server.rst | 66 +++++++++---------- .../command-objects/service-provider.rst | 10 +-- doc/source/command-objects/service.rst | 10 +-- doc/source/command-objects/snapshot.rst | 10 +-- doc/source/command-objects/subnet-pool.rst | 12 ++-- doc/source/command-objects/subnet.rst | 12 ++-- doc/source/command-objects/token.rst | 4 +- doc/source/command-objects/trust.rst | 8 +-- doc/source/command-objects/usage.rst | 4 +- doc/source/command-objects/user-role.rst | 2 +- doc/source/command-objects/user.rst | 10 +-- doc/source/command-objects/volume-backup.rst | 10 +-- doc/source/command-objects/volume-host.rst | 2 +- doc/source/command-objects/volume-qos.rst | 16 ++--- doc/source/command-objects/volume-service.rst | 4 +- .../command-objects/volume-snapshot.rst | 10 +-- .../volume-transfer-request.rst | 10 +-- doc/source/command-objects/volume-type.rst | 12 ++-- doc/source/command-objects/volume.rst | 14 ++-- doc/source/specs/command-objects/example.rst | 10 +-- doc/source/specs/network-topology.rst | 4 +- 26 files changed, 135 insertions(+), 135 deletions(-) diff --git a/doc/source/command-objects/security-group-rule.rst b/doc/source/command-objects/security-group-rule.rst index f53389cc05..1dbf16d22b 100644 --- a/doc/source/command-objects/security-group-rule.rst +++ b/doc/source/command-objects/security-group-rule.rst @@ -15,7 +15,7 @@ Create a new security group rule .. program:: security group rule create .. code:: bash - os security group rule create + openstack security group rule create [--remote-ip | --remote-group ] [--dst-port | [--icmp-type [--icmp-code ]]] [--protocol ] @@ -116,7 +116,7 @@ Delete security group rule(s) .. program:: security group rule delete .. code:: bash - os security group rule delete + openstack security group rule delete [ ...] .. describe:: @@ -131,7 +131,7 @@ List security group rules .. program:: security group rule list .. code:: bash - os security group rule list + openstack security group rule list [--all-projects] [--protocol ] [--ingress | --egress] @@ -185,7 +185,7 @@ Display security group rule details .. program:: security group rule show .. code:: bash - os security group rule show + openstack security group rule show .. describe:: diff --git a/doc/source/command-objects/security-group.rst b/doc/source/command-objects/security-group.rst index 5157f53096..a95a96f49f 100644 --- a/doc/source/command-objects/security-group.rst +++ b/doc/source/command-objects/security-group.rst @@ -16,7 +16,7 @@ Create a new security group .. program:: security group create .. code:: bash - os security group create + openstack security group create [--description ] [--project [--project-domain ]] @@ -50,7 +50,7 @@ Delete security group(s) .. program:: security group delete .. code:: bash - os security group delete + openstack security group delete [ ...] .. describe:: @@ -65,7 +65,7 @@ List security groups .. program:: security group list .. code:: bash - os security group list + openstack security group list [--all-projects] [--project [--project-domain ]] @@ -97,7 +97,7 @@ Set security group properties .. program:: security group set .. code:: bash - os security group set + openstack security group set [--name ] [--description ] @@ -122,7 +122,7 @@ Display security group details .. program:: security group show .. code:: bash - os security group show + openstack security group show .. describe:: diff --git a/doc/source/command-objects/server-backup.rst b/doc/source/command-objects/server-backup.rst index 23e17d5f5d..9ddb2cdfab 100644 --- a/doc/source/command-objects/server-backup.rst +++ b/doc/source/command-objects/server-backup.rst @@ -15,7 +15,7 @@ Create a server backup image .. program:: server create .. code:: bash - os server backup create + openstack server backup create [--name ] [--type ] [--rotate ] diff --git a/doc/source/command-objects/server-group.rst b/doc/source/command-objects/server-group.rst index 414f49b1aa..77c6431c1e 100644 --- a/doc/source/command-objects/server-group.rst +++ b/doc/source/command-objects/server-group.rst @@ -14,7 +14,7 @@ Create a new server group .. program:: server group create .. code-block:: bash - os server group create + openstack server group create --policy [--policy ] ... @@ -36,7 +36,7 @@ Delete existing server group(s) .. program:: server group delete .. code-block:: bash - os server group delete + openstack server group delete [ ...] .. describe:: @@ -52,7 +52,7 @@ List all server groups .. program:: server group list .. code-block:: bash - os server group list + openstack server group list [--all-projects] [--long] @@ -72,7 +72,7 @@ Display server group details .. program:: server group show .. code-block:: bash - os server group show + openstack server group show .. describe:: diff --git a/doc/source/command-objects/server-image.rst b/doc/source/command-objects/server-image.rst index eb44e47e07..7e625d9e42 100644 --- a/doc/source/command-objects/server-image.rst +++ b/doc/source/command-objects/server-image.rst @@ -15,7 +15,7 @@ Create a new server disk image from an existing server .. program:: server image create .. code:: bash - os server image create + openstack server image create [--name ] [--wait] diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index f099c271c9..c61153feaa 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -32,7 +32,7 @@ Add floating IP address to server .. program:: server add floating ip .. code:: bash - os server add floating ip + openstack server add floating ip @@ -52,7 +52,7 @@ Add security group to server .. program:: server add security group .. code:: bash - os server add security group + openstack server add security group @@ -72,7 +72,7 @@ Add volume to server .. program:: server add volume .. code:: bash - os server add volume + openstack server add volume [--device ] @@ -97,7 +97,7 @@ Create a new server .. program:: server create .. code:: bash - os server create + openstack server create --image | --volume --flavor [--security-group [...] ] @@ -199,7 +199,7 @@ Delete server(s) .. program:: server delete .. code:: bash - os server delete + openstack server delete [ ...] [--wait] .. option:: --wait @@ -222,7 +222,7 @@ resource. .. program:: server dump create .. code:: bash - os server dump create + openstack server dump create [ ...] .. describe:: @@ -236,7 +236,7 @@ List servers .. code:: bash - os server list + openstack server list [--reservation-id ] [--ip ] [--ip6 ] @@ -333,7 +333,7 @@ Lock server(s). A non-admin user will not be able to execute actions .. program:: server lock .. code:: bash - os server lock + openstack server lock [ ...] .. describe:: @@ -348,7 +348,7 @@ Migrate server to different host .. program:: server migrate .. code:: bash - os server migrate + openstack server migrate --live [--shared-migration | --block-migration] [--disk-overcommit | --no-disk-overcommit] @@ -391,7 +391,7 @@ Pause server(s) .. program:: server pause .. code:: bash - os server pause + openstack server pause [ ...] .. describe:: @@ -406,7 +406,7 @@ Perform a hard or soft server reboot .. program:: server reboot .. code:: bash - os server reboot + openstack server reboot [--hard | --soft] [--wait] @@ -435,7 +435,7 @@ Rebuild server .. program:: server rebuild .. code:: bash - os server rebuild + openstack server rebuild [--image ] [--password ] [--wait] @@ -466,7 +466,7 @@ Remove fixed IP address from server .. program:: server remove fixed ip .. code:: bash - os server remove fixed ip + openstack server remove fixed ip @@ -486,7 +486,7 @@ Remove floating IP address from server .. program:: server remove floating ip .. code:: bash - os server remove floating ip + openstack server remove floating ip @@ -506,7 +506,7 @@ Remove security group from server .. program:: server remove security group .. code:: bash - os server remove security group + openstack server remove security group @@ -526,7 +526,7 @@ Remove volume from server .. program:: server remove volume .. code:: bash - os server remove volume + openstack server remove volume @@ -546,7 +546,7 @@ Put server in rescue mode .. program:: server rescue .. code:: bash - os server rescue + openstack server rescue .. describe:: @@ -561,12 +561,12 @@ Scale server to a new flavor .. program:: server resize .. code:: bash - os server resize + openstack server resize --flavor [--wait] - os server resize + openstack server resize --confirm | --revert @@ -604,7 +604,7 @@ Restore server(s) from soft-deleted state .. program:: server restore .. code:: bash - os server restore + openstack server restore [ ...] .. describe:: @@ -619,7 +619,7 @@ Resume server(s) .. program:: server resume .. code:: bash - os server resume + openstack server resume [ ...] .. describe:: @@ -634,7 +634,7 @@ Set server properties .. program:: server set .. code:: bash - os server set + openstack server set --name --property [--property ] ... @@ -671,7 +671,7 @@ Shelve server(s) .. program:: server shelve .. code:: bash - os server shelve + openstack server shelve [ ...] .. describe:: @@ -686,7 +686,7 @@ Show server details .. program:: server show .. code:: bash - os server show + openstack server show [--diagnostics] @@ -706,7 +706,7 @@ SSH to server .. program:: server ssh .. code:: bash - os server ssh + openstack server ssh [--login ] [--port ] [--identity ] @@ -754,7 +754,7 @@ Start server(s) .. program:: server start .. code:: bash - os server start + openstack server start [ ...] .. describe:: @@ -769,7 +769,7 @@ Stop server(s) .. program:: server stop .. code:: bash - os server stop + openstack server stop [ ...] .. describe:: @@ -784,7 +784,7 @@ Suspend server(s) .. program:: server suspend .. code:: bash - os server suspend + openstack server suspend [ ...] .. describe:: @@ -799,7 +799,7 @@ Unlock server(s) .. program:: server unlock .. code:: bash - os server unlock + openstack server unlock [ ...] .. describe:: @@ -814,7 +814,7 @@ Unpause server(s) .. program:: server unpause .. code:: bash - os server unpause + openstack server unpause [ ...] .. describe:: @@ -829,7 +829,7 @@ Restore server from rescue mode .. program:: server unrescue .. code:: bash - os server unrescue + openstack server unrescue .. describe:: @@ -844,7 +844,7 @@ Unset server properties .. program:: server unset .. code:: bash - os server unset + openstack server unset --property [--property ] ... @@ -866,7 +866,7 @@ Unshelve server(s) .. program:: server unshelve .. code:: bash - os server unshelve + openstack server unshelve [ ...] .. describe:: diff --git a/doc/source/command-objects/service-provider.rst b/doc/source/command-objects/service-provider.rst index e0fbba2fe9..63ef44e1f2 100644 --- a/doc/source/command-objects/service-provider.rst +++ b/doc/source/command-objects/service-provider.rst @@ -14,7 +14,7 @@ Create new service provider .. program:: service provider create .. code:: bash - os service provider create + openstack service provider create [--description ] [--enable | --disable] --auth-url @@ -53,7 +53,7 @@ Delete service provider(s) .. program:: service provider delete .. code:: bash - os service provider delete + openstack service provider delete [ ...] .. describe:: @@ -68,7 +68,7 @@ List service providers .. program:: service provider list .. code:: bash - os service provider list + openstack service provider list service provider set -------------------- @@ -78,7 +78,7 @@ Set service provider properties .. program:: service provider set .. code:: bash - os service provider set + openstack service provider set [--enable | --disable] [--description ] [--auth-url ] @@ -117,7 +117,7 @@ Display service provider details .. program:: service provider show .. code:: bash - os service provider show + openstack service provider show .. describe:: diff --git a/doc/source/command-objects/service.rst b/doc/source/command-objects/service.rst index b5ec2d9962..250d19ec3d 100644 --- a/doc/source/command-objects/service.rst +++ b/doc/source/command-objects/service.rst @@ -12,7 +12,7 @@ Create new service .. program:: service create .. code-block:: bash - os service create + openstack service create [--name ] [--description ] [--enable | --disable] @@ -51,7 +51,7 @@ Delete service(s) .. program:: service delete .. code-block:: bash - os service delete + openstack service delete [ ...] .. _service_delete-type: @@ -67,7 +67,7 @@ List services .. program:: service list .. code-block:: bash - os service list + openstack service list [--long] .. option:: --long @@ -87,7 +87,7 @@ Set service properties .. program:: service set .. code-block:: bash - os service set + openstack service set [--type ] [--name ] [--description ] @@ -127,7 +127,7 @@ Display service details .. program:: service show .. code-block:: bash - os service show + openstack service show [--catalog] diff --git a/doc/source/command-objects/snapshot.rst b/doc/source/command-objects/snapshot.rst index e75693ca18..e4a5be6d10 100644 --- a/doc/source/command-objects/snapshot.rst +++ b/doc/source/command-objects/snapshot.rst @@ -52,7 +52,7 @@ Delete snapshot(s) .. program:: snapshot delete .. code:: bash - os snapshot delete + openstack snapshot delete [ ...] .. _snapshot_delete-snapshot: @@ -69,7 +69,7 @@ List snapshots .. program:: snapshot list .. code:: bash - os snapshot list + openstack snapshot list [--all-projects] [--long] [--limit ] @@ -104,7 +104,7 @@ Set snapshot properties .. program:: snapshot set .. code:: bash - os snapshot set + openstack snapshot set [--name ] [--description ] [--property [...] ] @@ -146,7 +146,7 @@ Display snapshot details .. program:: snapshot show .. code:: bash - os snapshot show + openstack snapshot show .. _snapshot_show-snapshot: @@ -163,7 +163,7 @@ Unset snapshot properties .. program:: snapshot unset .. code:: bash - os snapshot unset + openstack snapshot unset [--property ] diff --git a/doc/source/command-objects/subnet-pool.rst b/doc/source/command-objects/subnet-pool.rst index bd1356f394..8b59bf8a65 100644 --- a/doc/source/command-objects/subnet-pool.rst +++ b/doc/source/command-objects/subnet-pool.rst @@ -15,7 +15,7 @@ Create subnet pool .. program:: subnet pool create .. code:: bash - os subnet pool create + openstack subnet pool create [--default-prefix-length ] [--min-prefix-length ] [--max-prefix-length ] @@ -91,7 +91,7 @@ Delete subnet pool(s) .. program:: subnet pool delete .. code:: bash - os subnet pool delete + openstack subnet pool delete [ ...] .. _subnet_pool_delete-subnet-pool: @@ -107,7 +107,7 @@ List subnet pools .. program:: subnet pool list .. code:: bash - os subnet pool list + openstack subnet pool list [--long] [--share | --no-share] [--default | --no-default] @@ -160,7 +160,7 @@ Set subnet pool properties .. program:: subnet pool set .. code:: bash - os subnet pool set + openstack subnet pool set [--name ] [--pool-prefix [...]] [--default-prefix-length ] @@ -226,7 +226,7 @@ Display subnet pool details .. program:: subnet pool show .. code:: bash - os subnet pool show + openstack subnet pool show .. _subnet_pool_show-subnet-pool: @@ -242,7 +242,7 @@ Unset subnet pool properties .. program:: subnet pool unset .. code:: bash - os subnet pool unset + openstack subnet pool unset [--pool-prefix [...]] diff --git a/doc/source/command-objects/subnet.rst b/doc/source/command-objects/subnet.rst index 6152619292..fb28793e76 100644 --- a/doc/source/command-objects/subnet.rst +++ b/doc/source/command-objects/subnet.rst @@ -16,7 +16,7 @@ Create new subnet .. program:: subnet create .. code:: bash - os subnet create + openstack subnet create [--project [--project-domain ]] [--subnet-pool | --use-default-subnet-pool [--prefix-length ]] [--subnet-range ] @@ -142,7 +142,7 @@ Delete subnet(s) .. program:: subnet delete .. code:: bash - os subnet delete + openstack subnet delete [ ...] .. _subnet_delete-subnet: @@ -158,7 +158,7 @@ List subnets .. program:: subnet list .. code:: bash - os subnet list + openstack subnet list [--long] [--ip-version {4,6}] [--dhcp | --no-dhcp] @@ -226,7 +226,7 @@ Set subnet properties .. program:: subnet set .. code:: bash - os subnet set + openstack subnet set [--allocation-pool start=,end=] [--no-allocation-pool] [--dhcp | --no-dhcp] @@ -319,7 +319,7 @@ Display subnet details .. program:: subnet show .. code:: bash - os subnet show + openstack subnet show .. _subnet_show-subnet: @@ -335,7 +335,7 @@ Unset subnet properties .. program:: subnet unset .. code:: bash - os subnet unset + openstack subnet unset [--allocation-pool start=,end= [...]] [--dns-nameserver [...]] [--host-route destination=,gateway= [...]] diff --git a/doc/source/command-objects/token.rst b/doc/source/command-objects/token.rst index 5e7c7b2686..b4b14cd9fd 100644 --- a/doc/source/command-objects/token.rst +++ b/doc/source/command-objects/token.rst @@ -12,7 +12,7 @@ Issue new token .. program:: token issue .. code:: bash - os token issue + openstack token issue token revoke ------------ @@ -22,7 +22,7 @@ Revoke existing token .. program:: token revoke .. code:: bash - os token revoke + openstack token revoke .. describe:: diff --git a/doc/source/command-objects/trust.rst b/doc/source/command-objects/trust.rst index cb44c6b6cd..28459bcaf6 100644 --- a/doc/source/command-objects/trust.rst +++ b/doc/source/command-objects/trust.rst @@ -12,7 +12,7 @@ Create new trust .. program:: trust create .. code:: bash - os trust create + openstack trust create --project --role [--impersonate] @@ -69,7 +69,7 @@ Delete trust(s) .. program:: trust delete .. code:: bash - os trust delete + openstack trust delete [ ...] .. describe:: @@ -84,7 +84,7 @@ List trusts .. program:: trust list .. code:: bash - os trust list + openstack trust list trust show ---------- @@ -94,7 +94,7 @@ Display trust details .. program:: trust show .. code:: bash - os trust show + openstack trust show .. describe:: diff --git a/doc/source/command-objects/usage.rst b/doc/source/command-objects/usage.rst index 589123a65b..9cd0f70eb3 100644 --- a/doc/source/command-objects/usage.rst +++ b/doc/source/command-objects/usage.rst @@ -12,7 +12,7 @@ List resource usage per project .. program:: usage list .. code:: bash - os usage list + openstack usage list [--start ] [--end ] @@ -32,7 +32,7 @@ Show resource usage for a single project .. program:: usage show .. code:: bash - os usage show + openstack usage show [--project ] [--start ] [--end ] diff --git a/doc/source/command-objects/user-role.rst b/doc/source/command-objects/user-role.rst index 8283f91110..4f443f3126 100644 --- a/doc/source/command-objects/user-role.rst +++ b/doc/source/command-objects/user-role.rst @@ -14,7 +14,7 @@ List user-role assignments .. program:: user role list .. code:: bash - os user role list + openstack user role list [--project ] [] diff --git a/doc/source/command-objects/user.rst b/doc/source/command-objects/user.rst index a9fee72811..7e0ee21b0f 100644 --- a/doc/source/command-objects/user.rst +++ b/doc/source/command-objects/user.rst @@ -12,7 +12,7 @@ Create new user .. program:: user create .. code:: bash - os user create + openstack user create [--domain ] [--project [--project-domain ]] [--password ] @@ -82,7 +82,7 @@ Delete user(s) .. program:: user delete .. code:: bash - os user delete + openstack user delete [--domain ] [ ...] @@ -105,7 +105,7 @@ List users .. program:: user list .. code:: bash - os user list + openstack user list [--project ] [--domain ] [--group | --project ] @@ -139,7 +139,7 @@ Set user properties .. program:: user set .. code:: bash - os user set + openstack user set [--name ] [--project [--project-domain ]] [--password ] @@ -200,7 +200,7 @@ Display user details .. program:: user show .. code:: bash - os user show + openstack user show [--domain ] diff --git a/doc/source/command-objects/volume-backup.rst b/doc/source/command-objects/volume-backup.rst index 2cccffae93..3c329b94df 100644 --- a/doc/source/command-objects/volume-backup.rst +++ b/doc/source/command-objects/volume-backup.rst @@ -64,7 +64,7 @@ Delete volume backup(s) .. program:: volume backup delete .. code:: bash - os volume backup delete + openstack volume backup delete [--force] [ ...] @@ -87,7 +87,7 @@ List volume backups .. program:: volume backup list .. code:: bash - os volume backup list + openstack volume backup list [--long] [--name ] [--status ] @@ -138,7 +138,7 @@ Restore volume backup .. program:: volume backup restore .. code:: bash - os volume backup restore + openstack volume backup restore @@ -159,7 +159,7 @@ Set volume backup properties .. program:: volume backup set .. code:: bash - os volume backup set + openstack volume backup set [--name ] [--description ] [--state ] @@ -192,7 +192,7 @@ Display volume backup details .. program:: volume backup show .. code:: bash - os volume backup show + openstack volume backup show .. _volume_backup_show-backup: diff --git a/doc/source/command-objects/volume-host.rst b/doc/source/command-objects/volume-host.rst index 956fce525b..a225e53e58 100644 --- a/doc/source/command-objects/volume-host.rst +++ b/doc/source/command-objects/volume-host.rst @@ -12,7 +12,7 @@ Set volume host properties .. program:: volume host set .. code:: bash - os volume host set + openstack volume host set [--enable | --disable] diff --git a/doc/source/command-objects/volume-qos.rst b/doc/source/command-objects/volume-qos.rst index 620c26bafb..8fdbc12284 100644 --- a/doc/source/command-objects/volume-qos.rst +++ b/doc/source/command-objects/volume-qos.rst @@ -12,7 +12,7 @@ Associate a QoS specification to a volume type .. program:: volume qos associate .. code:: bash - os volume qos associate + openstack volume qos associate @@ -33,7 +33,7 @@ Create new QoS Specification .. program:: volume qos create .. code:: bash - os volume qos create + openstack volume qos create [--consumer ] [--property [...] ] @@ -59,7 +59,7 @@ Delete QoS specification .. program:: volume qos delete .. code:: bash - os volume qos delete + openstack volume qos delete [--force] [ ...] @@ -80,7 +80,7 @@ Disassociate a QoS specification from a volume type .. program:: volume qos disassociate .. code:: bash - os volume qos disassociate + openstack volume qos disassociate --volume-type | --all @@ -105,7 +105,7 @@ List QoS specifications .. program:: volume qos list .. code:: bash - os volume qos list + openstack volume qos list volume qos set -------------- @@ -115,7 +115,7 @@ Set QoS specification properties .. program:: volume qos set .. code:: bash - os volume qos set + openstack volume qos set [--property [...] ] @@ -136,7 +136,7 @@ Display QoS specification details .. program:: volume qos show .. code:: bash - os volume qos show + openstack volume qos show .. _volume_qos_show-qos-spec: @@ -152,7 +152,7 @@ Unset QoS specification properties .. program:: volume qos unset .. code:: bash - os volume qos unset + openstack volume qos unset [--property [...] ] diff --git a/doc/source/command-objects/volume-service.rst b/doc/source/command-objects/volume-service.rst index 4fea41fe26..842d9cb23a 100644 --- a/doc/source/command-objects/volume-service.rst +++ b/doc/source/command-objects/volume-service.rst @@ -12,7 +12,7 @@ List volume service .. program:: volume service list .. code:: bash - os volume service list + openstack volume service list [--host ] [--service ] [--long] @@ -38,7 +38,7 @@ Set volume service properties .. program:: volume service set .. code:: bash - os volume service set + openstack volume service set [--enable | --disable] [--disable-reason ] diff --git a/doc/source/command-objects/volume-snapshot.rst b/doc/source/command-objects/volume-snapshot.rst index 1744903515..e34bad0042 100644 --- a/doc/source/command-objects/volume-snapshot.rst +++ b/doc/source/command-objects/volume-snapshot.rst @@ -59,7 +59,7 @@ Delete volume snapshot(s) .. program:: volume snapshot delete .. code:: bash - os volume snapshot delete + openstack volume snapshot delete [--force] [ ...] @@ -80,7 +80,7 @@ List volume snapshots .. program:: volume snapshot list .. code:: bash - os volume snapshot list + openstack volume snapshot list [--all-projects] [--long] [--limit ] @@ -130,7 +130,7 @@ Set volume snapshot properties .. program:: volume snapshot set .. code:: bash - os volume snapshot set + openstack volume snapshot set [--name ] [--description ] [--property [...] ] @@ -171,7 +171,7 @@ Display volume snapshot details .. program:: volume snapshot show .. code:: bash - os volume snapshot show + openstack volume snapshot show .. _volume_snapshot_show-snapshot: @@ -187,7 +187,7 @@ Unset volume snapshot properties .. program:: volume snapshot unset .. code:: bash - os volume snapshot unset + openstack volume snapshot unset [--property ] diff --git a/doc/source/command-objects/volume-transfer-request.rst b/doc/source/command-objects/volume-transfer-request.rst index b89289a81e..77c3642964 100644 --- a/doc/source/command-objects/volume-transfer-request.rst +++ b/doc/source/command-objects/volume-transfer-request.rst @@ -12,7 +12,7 @@ Accept volume transfer request .. program:: volume transfer request accept .. code:: bash - os volume transfer request accept + openstack volume transfer request accept @@ -33,7 +33,7 @@ Create volume transfer request .. program:: volume transfer request create .. code:: bash - os volume transfer request create + openstack volume transfer request create [--name ] @@ -54,7 +54,7 @@ Delete volume transfer request(s) .. program:: volume transfer request delete .. code:: bash - os volume transfer request delete + openstack volume transfer request delete [ ...] .. _volume_transfer_request_delete-transfer-request: @@ -70,7 +70,7 @@ Lists all volume transfer requests. .. program:: volume transfer request list .. code:: bash - os volume transfer request list + openstack volume transfer request list --all-projects .. option:: --all-projects @@ -86,7 +86,7 @@ Show volume transfer request details .. program:: volume transfer request show .. code:: bash - os volume transfer request show + openstack volume transfer request show .. _volume_transfer_request_show-transfer-request: diff --git a/doc/source/command-objects/volume-type.rst b/doc/source/command-objects/volume-type.rst index 72422b5c25..40e9825c87 100644 --- a/doc/source/command-objects/volume-type.rst +++ b/doc/source/command-objects/volume-type.rst @@ -12,7 +12,7 @@ Create new volume type .. program:: volume type create .. code:: bash - os volume type create + openstack volume type create [--description ] [--public | --private] [--property [...] ] @@ -69,7 +69,7 @@ Delete volume type(s) .. program:: volume type delete .. code:: bash - os volume type delete + openstack volume type delete [ ...] .. _volume_type_delete-volume-type: @@ -85,7 +85,7 @@ List volume types .. program:: volume type list .. code:: bash - os volume type list + openstack volume type list [--long] [--default | --public | --private] @@ -119,7 +119,7 @@ Set volume type properties .. program:: volume type set .. code:: bash - os volume type set + openstack volume type set [--name ] [--description ] [--property [...] ] @@ -167,7 +167,7 @@ Display volume type details .. program:: volume type show .. code:: bash - os volume type show + openstack volume type show .. _volume_type_show-volume-type: @@ -183,7 +183,7 @@ Unset volume type properties .. program:: volume type unset .. code:: bash - os volume type unset + openstack volume type unset [--property [...] ] [--project ] [--project-domain ] diff --git a/doc/source/command-objects/volume.rst b/doc/source/command-objects/volume.rst index 5383386f5b..56c5455283 100644 --- a/doc/source/command-objects/volume.rst +++ b/doc/source/command-objects/volume.rst @@ -12,7 +12,7 @@ Create new volume .. program:: volume create .. code:: bash - os volume create + openstack volume create [--size ] [--type ] [--image | --snapshot | --source | --source-replicated ] @@ -124,7 +124,7 @@ Delete volume(s) .. program:: volume delete .. code:: bash - os volume delete + openstack volume delete [--force | --purge] [ ...] @@ -151,7 +151,7 @@ List volumes .. program:: volume list .. code:: bash - os volume list + openstack volume list [--project [--project-domain ]] [--user [--user-domain ]] [--name ] @@ -223,7 +223,7 @@ Migrate volume to a new host .. program:: volume migrate .. code:: bash - os volume migrate + openstack volume migrate --host [--force-host-copy] [--lock-volume | --unlock-volume] @@ -265,7 +265,7 @@ Set volume properties .. program:: volume set .. code:: bash - os volume set + openstack volume set [--name ] [--size ] [--description ] @@ -358,7 +358,7 @@ Show volume details .. program:: volume show .. code:: bash - os volume show + openstack volume show .. _volume_show-volume: @@ -374,7 +374,7 @@ Unset volume properties .. program:: volume unset .. code:: bash - os volume unset + openstack volume unset [--property ] [--image-property ] diff --git a/doc/source/specs/command-objects/example.rst b/doc/source/specs/command-objects/example.rst index f596978f41..fa559433e2 100644 --- a/doc/source/specs/command-objects/example.rst +++ b/doc/source/specs/command-objects/example.rst @@ -18,7 +18,7 @@ Create new example .. program:: example create .. code:: bash - os example create + openstack example create .. describe:: @@ -33,7 +33,7 @@ Delete example(s) .. program:: example delete .. code:: bash - os example delete + openstack example delete [ ...] .. describe:: @@ -48,7 +48,7 @@ List examples .. program:: example list .. code:: bash - os example list + openstack example list example set ----------- @@ -58,7 +58,7 @@ Set example properties .. program:: example set .. code:: bash - os example set + openstack example set [--name ] @@ -78,7 +78,7 @@ Display example details .. program:: example show .. code:: bash - os example show + openstack example show .. describe:: diff --git a/doc/source/specs/network-topology.rst b/doc/source/specs/network-topology.rst index 6f9c8532d6..a0218a1e1b 100755 --- a/doc/source/specs/network-topology.rst +++ b/doc/source/specs/network-topology.rst @@ -19,7 +19,7 @@ List network topologies .. program:: network topology list .. code:: bash - os network topology list + openstack network topology list [--project ] .. option:: --project @@ -35,7 +35,7 @@ Show network topology details .. program:: network topology show .. code:: bash - os network topology show + openstack network topology show .. _network_topology_show-network From 8bcfb824c8f2978c9348968d3da1345c45d7b764 Mon Sep 17 00:00:00 2001 From: Nguyen Phuong An Date: Wed, 17 Aug 2016 11:25:13 +0700 Subject: [PATCH 1429/3095] Add 'allowed address pairs' option to 'port create/set/unset' This patch adds '--allowed-addres-pair' and '--no-allowed-address-pair' options to 'port create', 'port set' and 'port unset' commands. Partial-Bug: #1612136 Closes-Bug: #1638265 Partially-Implements: blueprint network-commands-options Co-Authored-By: Ha Van Tu Change-Id: I08d2269950467a8972a0d0110ed61f5cc7f5ca45 --- doc/source/command-objects/port.rst | 29 +++ openstackclient/network/v2/port.py | 84 +++++++- .../tests/unit/network/v2/test_port.py | 194 +++++++++++++++++- .../notes/bug-1612136-6111b944569b9351.yaml | 7 + 4 files changed, 310 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/bug-1612136-6111b944569b9351.yaml diff --git a/doc/source/command-objects/port.rst b/doc/source/command-objects/port.rst index 73c53290be..a4bbc28535 100644 --- a/doc/source/command-objects/port.rst +++ b/doc/source/command-objects/port.rst @@ -29,6 +29,7 @@ Create new port [--mac-address ] [--security-group | --no-security-group] [--dns-name ] + [--allowed-address ip-address=[,mac-address=]] [--project [--project-domain ]] [--enable-port-security | --disable-port-security] @@ -97,6 +98,12 @@ Create new port Set DNS name to this port (requires DNS integration extension) +.. option:: --allowed-address ip-address=[,mac-address=] + + Add allowed-address pair associated with this port: + ip-address=[,mac-address=] + (repeat option to set multiple allowed-address pairs) + .. option:: --project Owner's project (name or ID) @@ -199,6 +206,8 @@ Set port properties [--no-security-group] [--enable-port-security | --disable-port-security] [--dns-name ] + [--allowed-address ip-address=[,mac-address=]] + [--no-allowed-address] .. option:: --description @@ -281,6 +290,19 @@ Set port properties Set DNS name to this port (requires DNS integration extension) +.. option:: --allowed-address ip-address=[,mac-address=] + + Add allowed-address pair associated with this port: + ip-address=[,mac-address=] + (repeat option to set multiple allowed-address pairs) + +.. option:: --no-allowed-address + + Clear existing allowed-address pairs associated + with this port. + (Specify both --allowed-address and --no-allowed-address + to overwrite the current allowed-address pairs) + .. _port_set-port: .. describe:: @@ -314,6 +336,7 @@ Unset port properties [--fixed-ip subnet=,ip-address= [...]] [--binding-profile [...]] [--security-group [...]] + [--allowed-address ip-address=[,mac-address=] [...]] .. option:: --fixed-ip subnet=,ip-address= @@ -332,6 +355,12 @@ Unset port properties Security group which should be removed from this port (name or ID) (repeat option to unset multiple security groups) +.. option:: --allowed-address ip-address=[,mac-address=] + + Desired allowed-address pair which should be removed from this port: + ip-address=[,mac-address=] + (repeat option to unset multiple allowed-address pairs) + .. _port_unset-port: .. describe:: diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 4525da1800..eb699ab89c 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -246,6 +246,17 @@ def _add_updatable_args(parser): # TODO(abhiraut): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. +def _convert_address_pairs(parsed_args): + ops = [] + for opt in parsed_args.allowed_address_pairs: + addr = {} + addr['ip_address'] = opt['ip-address'] + if 'mac-address' in opt: + addr['mac_address'] = opt['mac-address'] + ops.append(addr) + return ops + + class CreatePort(command.ShowOne): _description = _("Create a new port") @@ -305,7 +316,7 @@ def get_parser(self, prog_name): help=_("Name of this port") ) # TODO(singhj): Add support for extended options: - # qos,dhcp, address pairs + # qos,dhcp secgroups = parser.add_mutually_exclusive_group() secgroups.add_argument( '--security-group', @@ -332,7 +343,17 @@ def get_parser(self, prog_name): action='store_true', help=_("Disable port security for this port") ) - + parser.add_argument( + '--allowed-address', + metavar='ip-address=[,mac-address=]', + action=parseractions.MultiKeyValueAction, + dest='allowed_address_pairs', + required_keys=['ip-address'], + optional_keys=['mac-address'], + help=_("Add allowed-address pair associated with this port: " + "ip-address=[,mac-address=] " + "(repeat option to set multiple allowed-address pairs)") + ) return parser def take_action(self, parsed_args): @@ -349,6 +370,9 @@ def take_action(self, parsed_args): for sg in parsed_args.security_groups] if parsed_args.no_security_group: attrs['security_groups'] = [] + if parsed_args.allowed_address_pairs: + attrs['allowed_address_pairs'] = ( + _convert_address_pairs(parsed_args)) obj = client.create_port(**attrs) display_columns, columns = _get_columns(obj) @@ -569,7 +593,26 @@ def get_parser(self, prog_name): action='store_true', help=_("Disable port security for this port") ) - + parser.add_argument( + '--allowed-address', + metavar='ip-address=[,mac-address=]', + action=parseractions.MultiKeyValueAction, + dest='allowed_address_pairs', + required_keys=['ip-address'], + optional_keys=['mac-address'], + help=_("Add allowed-address pair associated with this port: " + "ip-address=[,mac-address=] " + "(repeat option to set multiple allowed-address pairs)") + ) + parser.add_argument( + '--no-allowed-address', + dest='no_allowed_address_pair', + action='store_true', + help=_("Clear existing allowed-address pairs associated" + "with this port." + "(Specify both --allowed-address and --no-allowed-address" + "to overwrite the current allowed-address pairs)") + ) return parser def take_action(self, parsed_args): @@ -609,6 +652,19 @@ def take_action(self, parsed_args): elif parsed_args.no_security_group: attrs['security_groups'] = [] + if (parsed_args.allowed_address_pairs and + parsed_args.no_allowed_address_pair): + attrs['allowed_address_pairs'] = ( + _convert_address_pairs(parsed_args)) + + elif parsed_args.allowed_address_pairs: + attrs['allowed_address_pairs'] = ( + [addr for addr in obj.allowed_address_pairs if addr] + + _convert_address_pairs(parsed_args)) + + elif parsed_args.no_allowed_address_pair: + attrs['allowed_address_pairs'] = [] + client.update_port(obj, **attrs) @@ -669,6 +725,19 @@ def get_parser(self, prog_name): metavar="", help=_("Port to modify (name or ID)") ) + parser.add_argument( + '--allowed-address', + metavar='ip-address=[,mac-address=]', + action=parseractions.MultiKeyValueAction, + dest='allowed_address_pairs', + required_keys=['ip-address'], + optional_keys=['mac-address'], + help=_("Desired allowed-address pair which should be removed " + "from this port: ip-address= " + "[,mac-address=] (repeat option to set " + "multiple allowed-address pairs)") + ) + return parser def take_action(self, parsed_args): @@ -680,6 +749,7 @@ def take_action(self, parsed_args): tmp_fixed_ips = copy.deepcopy(obj.fixed_ips) tmp_binding_profile = copy.deepcopy(obj.binding_profile) tmp_secgroups = copy.deepcopy(obj.security_groups) + tmp_addr_pairs = copy.deepcopy(obj.allowed_address_pairs) _prepare_fixed_ips(self.app.client_manager, parsed_args) attrs = {} if parsed_args.fixed_ip: @@ -708,6 +778,14 @@ def take_action(self, parsed_args): msg = _("Port does not contain security group %s") % sg raise exceptions.CommandError(msg) attrs['security_groups'] = tmp_secgroups + if parsed_args.allowed_address_pairs: + try: + for addr in _convert_address_pairs(parsed_args): + tmp_addr_pairs.remove(addr) + except ValueError: + msg = _("Port does not contain allowed-address-pair %s") % addr + raise exceptions.CommandError(msg) + attrs['allowed_address_pairs'] = tmp_addr_pairs if attrs: client.update_port(obj, **attrs) diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index 255e81166e..9deb77acae 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -216,7 +216,7 @@ def test_create_json_binding_profile(self): 'test-port', ] verifylist = [ - ('network', self._port.network_id,), + ('network', self._port.network_id), ('enable', True), ('binding_profile', {'parent_name': 'fake_parent', 'tag': 42}), ('name', 'test-port'), @@ -351,6 +351,74 @@ def test_create_with_no_security_groups(self): self.assertEqual(ref_columns, columns) self.assertEqual(ref_data, data) + def test_create_port_with_allowed_address_pair_ipaddr(self): + pairs = [{'ip_address': '192.168.1.123'}, + {'ip_address': '192.168.1.45'}] + arglist = [ + '--network', self._port.network_id, + '--allowed-address', 'ip-address=192.168.1.123', + '--allowed-address', 'ip-address=192.168.1.45', + 'test-port', + ] + verifylist = [ + ('network', self._port.network_id), + ('enable', True), + ('allowed_address_pairs', [{'ip-address': '192.168.1.123'}, + {'ip-address': '192.168.1.45'}]), + ('name', 'test-port'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_port.assert_called_once_with(**{ + 'admin_state_up': True, + 'network_id': self._port.network_id, + 'allowed_address_pairs': pairs, + 'name': 'test-port', + }) + + ref_columns, ref_data = self._get_common_cols_data(self._port) + self.assertEqual(ref_columns, columns) + self.assertEqual(ref_data, data) + + def test_create_port_with_allowed_address_pair(self): + pairs = [{'ip_address': '192.168.1.123', + 'mac_address': 'aa:aa:aa:aa:aa:aa'}, + {'ip_address': '192.168.1.45', + 'mac_address': 'aa:aa:aa:aa:aa:b1'}] + arglist = [ + '--network', self._port.network_id, + '--allowed-address', + 'ip-address=192.168.1.123,mac-address=aa:aa:aa:aa:aa:aa', + '--allowed-address', + 'ip-address=192.168.1.45,mac-address=aa:aa:aa:aa:aa:b1', + 'test-port', + ] + verifylist = [ + ('network', self._port.network_id), + ('enable', True), + ('allowed_address_pairs', [{'ip-address': '192.168.1.123', + 'mac-address': 'aa:aa:aa:aa:aa:aa'}, + {'ip-address': '192.168.1.45', + 'mac-address': 'aa:aa:aa:aa:aa:b1'}]), + ('name', 'test-port'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_port.assert_called_once_with(**{ + 'admin_state_up': True, + 'network_id': self._port.network_id, + 'allowed_address_pairs': pairs, + 'name': 'test-port', + }) + + ref_columns, ref_data = self._get_common_cols_data(self._port) + self.assertEqual(ref_columns, columns) + self.assertEqual(ref_data, data) + def test_create_port_security_enabled(self): arglist = [ '--network', self._port.network_id, @@ -996,6 +1064,91 @@ def test_overwrite_security_group(self): self.network.update_port.assert_called_once_with(_testport, **attrs) self.assertIsNone(result) + def test_set_allowed_address_pair(self): + arglist = [ + '--allowed-address', 'ip-address=192.168.1.123', + self._port.name, + ] + verifylist = [ + ('allowed_address_pairs', [{'ip-address': '192.168.1.123'}]), + ('port', self._port.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = { + 'allowed_address_pairs': [{'ip_address': '192.168.1.123'}], + } + self.network.update_port.assert_called_once_with(self._port, **attrs) + self.assertIsNone(result) + + def test_append_allowed_address_pair(self): + _testport = network_fakes.FakePort.create_one_port( + {'allowed_address_pairs': [{'ip_address': '192.168.1.123'}]}) + self.network.find_port = mock.Mock(return_value=_testport) + arglist = [ + '--allowed-address', 'ip-address=192.168.1.45', + _testport.name, + ] + verifylist = [ + ('allowed_address_pairs', [{'ip-address': '192.168.1.45'}]), + ('port', _testport.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = { + 'allowed_address_pairs': [{'ip_address': '192.168.1.123'}, + {'ip_address': '192.168.1.45'}], + } + self.network.update_port.assert_called_once_with(_testport, **attrs) + self.assertIsNone(result) + + def test_overwrite_allowed_address_pair(self): + _testport = network_fakes.FakePort.create_one_port( + {'allowed_address_pairs': [{'ip_address': '192.168.1.123'}]}) + self.network.find_port = mock.Mock(return_value=_testport) + arglist = [ + '--allowed-address', 'ip-address=192.168.1.45', + '--no-allowed-address', + _testport.name, + ] + verifylist = [ + ('allowed_address_pairs', [{'ip-address': '192.168.1.45'}]), + ('no_allowed_address_pair', True), + ('port', _testport.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = { + 'allowed_address_pairs': [{'ip_address': '192.168.1.45'}], + } + self.network.update_port.assert_called_once_with(_testport, **attrs) + self.assertIsNone(result) + + def test_set_no_allowed_address_pairs(self): + arglist = [ + '--no-allowed-address', + self._port.name, + ] + verifylist = [ + ('no_allowed_address_pair', True), + ('port', self._port.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = { + 'allowed_address_pairs': [], + } + self.network.update_port.assert_called_once_with(self._port, **attrs) + self.assertIsNone(result) + def test_port_security_enabled(self): arglist = [ '--enable-port-security', @@ -1192,3 +1345,42 @@ def test_unset_port_security_group_not_existent(self): self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) + + def test_unset_port_allowed_address_pair(self): + _fake_port = network_fakes.FakePort.create_one_port( + {'allowed_address_pairs': [{'ip_address': '192.168.1.123'}]}) + self.network.find_port = mock.Mock(return_value=_fake_port) + arglist = [ + '--allowed-address', 'ip-address=192.168.1.123', + _fake_port.name, + ] + verifylist = [ + ('allowed_address_pairs', [{'ip-address': '192.168.1.123'}]), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = { + 'allowed_address_pairs': [], + } + + self.network.update_port.assert_called_once_with(_fake_port, **attrs) + self.assertIsNone(result) + + def test_unset_port_allowed_address_pair_not_existent(self): + _fake_port = network_fakes.FakePort.create_one_port( + {'allowed_address_pairs': [{'ip_address': '192.168.1.123'}]}) + self.network.find_port = mock.Mock(return_value=_fake_port) + arglist = [ + '--allowed-address', 'ip-address=192.168.1.45', + _fake_port.name, + ] + verifylist = [ + ('allowed_address_pairs', [{'ip-address': '192.168.1.45'}]), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, + parsed_args) diff --git a/releasenotes/notes/bug-1612136-6111b944569b9351.yaml b/releasenotes/notes/bug-1612136-6111b944569b9351.yaml new file mode 100644 index 0000000000..2d30de3eb1 --- /dev/null +++ b/releasenotes/notes/bug-1612136-6111b944569b9351.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Add ``--allowed-address`` option to ``port create``, ``port set`` and + ``port unset`` commands. Also add ``--no-allowed-address`` option to + ``port create`` and ``port set`` commands. + [Bug `1612136 `_] From 3816b4b90a84ed3917d07af4c95a46cce0519ea7 Mon Sep 17 00:00:00 2001 From: ZhaoBo Date: Tue, 20 Dec 2016 23:32:32 +0800 Subject: [PATCH 1430/3095] Fix the missing os in command in example This patch correct the missing one 'os example'. Change-Id: I7c2cb01082f1eff1a7a607508f166dd54a450f44 --- doc/source/command-beta.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/command-beta.rst b/doc/source/command-beta.rst index bc8c04c5e7..669ef2011a 100644 --- a/doc/source/command-beta.rst +++ b/doc/source/command-beta.rst @@ -45,7 +45,7 @@ List examples .. program:: example list .. code:: bash - os example list + openstack example list Help ~~~~ From af7129cda33dcd4ac784097ddb4f119e145c40d5 Mon Sep 17 00:00:00 2001 From: guiyanxing Date: Thu, 8 Dec 2016 15:17:53 +0800 Subject: [PATCH 1431/3095] Add '--type'and other options to network rbac list This patch adds '--type','--action','--long' filtering options to network rbac list command Change-Id: I21846820ab223bb7832e89eb2d7658bbec271aec Closes-Bug: #1648307 Partially-Implements: blueprint network-commands-options --- doc/source/command-objects/network-rbac.rst | 15 +++++ openstackclient/network/v2/network_rbac.py | 36 ++++++++++- .../unit/network/v2/test_network_rbac.py | 64 ++++++++++++++++++- .../notes/bug-1648307-a2c6d7698e927449.yaml | 6 ++ 4 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/bug-1648307-a2c6d7698e927449.yaml diff --git a/doc/source/command-objects/network-rbac.rst b/doc/source/command-objects/network-rbac.rst index 33fa6af40e..c49f29bb37 100644 --- a/doc/source/command-objects/network-rbac.rst +++ b/doc/source/command-objects/network-rbac.rst @@ -79,6 +79,21 @@ List network RBAC policies .. code:: bash openstack network rbac list + [--type ] + [--action ] + [--long] + +.. option:: --type + + List network RBAC policies according to given object type ("qos_policy" or "network") + +.. option:: --action + + List network RBAC policies according to given action ("access_as_external" or "access_as_shared") + +.. option:: --long + + List additional fields in output network rbac set ---------------- diff --git a/openstackclient/network/v2/network_rbac.py b/openstackclient/network/v2/network_rbac.py index e837af3a62..9075473726 100644 --- a/openstackclient/network/v2/network_rbac.py +++ b/openstackclient/network/v2/network_rbac.py @@ -164,6 +164,30 @@ def take_action(self, parsed_args): class ListNetworkRBAC(command.Lister): _description = _("List network RBAC policies") + def get_parser(self, prog_name): + parser = super(ListNetworkRBAC, self).get_parser(prog_name) + parser.add_argument( + '--type', + metavar='', + choices=['qos_policy', 'network'], + help=_('List network RBAC policies according to ' + 'given object type ("qos_policy" or "network")') + ) + parser.add_argument( + '--action', + metavar='', + choices=['access_as_external', 'access_as_shared'], + help=_('List network RBAC policies according to given ' + 'action ("access_as_external" or "access_as_shared")') + ) + parser.add_argument( + '--long', + action='store_true', + default=False, + help=_("List additional fields in output") + ) + return parser + def take_action(self, parsed_args): client = self.app.client_manager.network @@ -178,7 +202,17 @@ def take_action(self, parsed_args): 'Object ID', ) - data = client.rbac_policies() + query = {} + if parsed_args.long: + columns += ('action',) + column_headers += ('Action',) + if parsed_args.type is not None: + query['object_type'] = parsed_args.type + if parsed_args.action is not None: + query['action'] = parsed_args.action + + data = client.rbac_policies(**query) + return (column_headers, (utils.get_item_properties( s, columns, diff --git a/openstackclient/tests/unit/network/v2/test_network_rbac.py b/openstackclient/tests/unit/network/v2/test_network_rbac.py index b884dbc02d..935ce0758c 100644 --- a/openstackclient/tests/unit/network/v2/test_network_rbac.py +++ b/openstackclient/tests/unit/network/v2/test_network_rbac.py @@ -327,7 +327,12 @@ class TestListNetworkRABC(TestNetworkRBAC): 'Object Type', 'Object ID', ) - + columns_long = ( + 'ID', + 'Object Type', + 'Object ID', + 'Action', + ) data = [] for r in rbac_policies: data.append(( @@ -335,6 +340,14 @@ class TestListNetworkRABC(TestNetworkRBAC): r.object_type, r.object_id, )) + data_long = [] + for r in rbac_policies: + data_long.append(( + r.id, + r.object_type, + r.object_id, + r.action, + )) def setUp(self): super(TestListNetworkRABC, self).setUp() @@ -356,6 +369,55 @@ def test_network_rbac_list(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + def test_network_rbac_list_type_opt(self): + arglist = [ + '--type', self.rbac_policies[0].object_type, ] + verifylist = [ + ('type', self.rbac_policies[0].object_type)] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + self.network.rbac_policies.assert_called_with(**{ + 'object_type': self.rbac_policies[0].object_type + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_network_rbac_list_action_opt(self): + arglist = [ + '--action', self.rbac_policies[0].action, ] + verifylist = [ + ('action', self.rbac_policies[0].action)] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + self.network.rbac_policies.assert_called_with(**{ + 'action': self.rbac_policies[0].action + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_network_rbac_list_with_long(self): + arglist = [ + '--long', + ] + + verifylist = [ + ('long', True), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.rbac_policies.assert_called_with() + self.assertEqual(self.columns_long, columns) + self.assertEqual(self.data_long, list(data)) + class TestSetNetworkRBAC(TestNetworkRBAC): diff --git a/releasenotes/notes/bug-1648307-a2c6d7698e927449.yaml b/releasenotes/notes/bug-1648307-a2c6d7698e927449.yaml new file mode 100644 index 0000000000..2e426948a3 --- /dev/null +++ b/releasenotes/notes/bug-1648307-a2c6d7698e927449.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--type``, ``--action``, ``--long`` options + to ``network rbac list`` command + [Bug `1648307 `_] From 97c0b4bf15d8990d9f2d7790f61c4e36ced32061 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Wed, 21 Dec 2016 10:29:38 +0800 Subject: [PATCH 1432/3095] Fix all missing "os to openstack" transformation Checked and fix all missing of the review: https://review.openstack.org/#/c/412669/ https://review.openstack.org/#/c/412673/ https://review.openstack.org/#/c/412678/ Change-Id: Ice85958874121eeb574d0b4cfa58e1590b014ed6 --- doc/source/command-objects/group.rst | 2 +- doc/source/command-objects/server.rst | 2 +- doc/source/command-objects/snapshot.rst | 2 +- doc/source/command-objects/volume-backup.rst | 2 +- doc/source/command-objects/volume-snapshot.rst | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/source/command-objects/group.rst b/doc/source/command-objects/group.rst index a4b266352e..3c3199cfeb 100644 --- a/doc/source/command-objects/group.rst +++ b/doc/source/command-objects/group.rst @@ -12,7 +12,7 @@ Add user to group .. program:: group add user .. code:: bash - os group add user + openstack group add user [--group-domain ] [--user-domain ] diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index c61153feaa..5ad29a0fde 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -12,7 +12,7 @@ Add fixed IP address to server .. program:: server add fixed ip .. code:: bash - os server add fixed ip + openstack server add fixed ip diff --git a/doc/source/command-objects/snapshot.rst b/doc/source/command-objects/snapshot.rst index e4a5be6d10..c5c431d0c7 100644 --- a/doc/source/command-objects/snapshot.rst +++ b/doc/source/command-objects/snapshot.rst @@ -13,7 +13,7 @@ Create new snapshot .. program:: snapshot create .. code:: bash - os snapshot create + openstack snapshot create [--name ] [--description ] [--force] diff --git a/doc/source/command-objects/volume-backup.rst b/doc/source/command-objects/volume-backup.rst index 3c329b94df..5e41e69596 100644 --- a/doc/source/command-objects/volume-backup.rst +++ b/doc/source/command-objects/volume-backup.rst @@ -12,7 +12,7 @@ Create new volume backup .. program:: volume backup create .. code:: bash - os volume backup create + openstack volume backup create [--container ] [--name ] [--description ] diff --git a/doc/source/command-objects/volume-snapshot.rst b/doc/source/command-objects/volume-snapshot.rst index e34bad0042..37a5088a59 100644 --- a/doc/source/command-objects/volume-snapshot.rst +++ b/doc/source/command-objects/volume-snapshot.rst @@ -12,7 +12,7 @@ Create new volume snapshot .. program:: volume snapshot create .. code:: bash - os volume snapshot create + openstack volume snapshot create [--volume ] [--description ] [--force] From ff18e3d0e9307fb505113b467e58bdb62992fb85 Mon Sep 17 00:00:00 2001 From: Kevin_Zheng Date: Tue, 20 Dec 2016 10:17:05 +0800 Subject: [PATCH 1433/3095] Should support 'auto' and 'none' as network parameter when boot instances Nova added support using 'auto' and 'none' as network parameters since microversion 2.37: http://git.openstack.org/cgit/openstack/nova/tree/nova/api/openstack/rest_api_version_history.rst#n389 we should also add support for this in OSC. Change-Id: I6e5f616dfa48895ebd13144effe9fda7cb94c649 Closes-bug: #1651288 --- doc/source/command-objects/server.rst | 6 +- openstackclient/compute/v2/server.py | 66 ++++++++++--------- ...one-as-nic-parameter-ed23a6e7f99f250d.yaml | 9 +++ 3 files changed, 49 insertions(+), 32 deletions(-) create mode 100644 releasenotes/notes/add-auto-and-none-as-nic-parameter-ed23a6e7f99f250d.yaml diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index 5ad29a0fde..88f22baf03 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -107,7 +107,7 @@ Create a new server [--user-data ] [--availability-zone ] [--block-device-mapping [...] ] - [--nic [...] ] + [--nic [...] ] [--hint [...] ] [--config-drive |True ] [--min ] @@ -158,7 +158,7 @@ Create a new server Map block devices; map is ::: (optional extension) -.. option:: --nic +.. option:: --nic Create a NIC on the server. Specify option multiple times to create multiple NICs. Either net-id or port-id must be provided, but not both. @@ -166,6 +166,8 @@ Create a new server port-id: attach NIC to port with this UUID, v4-fixed-ip: IPv4 fixed address for NIC (optional), v6-fixed-ip: IPv6 fixed address for NIC (optional). + none: (v2.37+) no network is attached. + auto: (v2.37+) the compute service will automatically allocate a network. .. option:: --hint diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 48d8b2d0d9..938c8c06ae 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -393,7 +393,10 @@ def get_parser(self, prog_name): "net-id: attach NIC to network with this UUID, " "port-id: attach NIC to port with this UUID, " "v4-fixed-ip: IPv4 fixed address for NIC (optional), " - "v6-fixed-ip: IPv6 fixed address for NIC (optional)."), + "v6-fixed-ip: IPv6 fixed address for NIC (optional), " + "none: (v2.37+) no network is attached, " + "auto: (v2.37+) the compute service will automatically " + "allocate a network."), ) parser.add_argument( '--hint', @@ -513,36 +516,39 @@ def take_action(self, parsed_args): block_device_mapping.update({dev_key: block_volume}) nics = [] - for nic_str in parsed_args.nic: - nic_info = {"net-id": "", "v4-fixed-ip": "", - "v6-fixed-ip": "", "port-id": ""} - nic_info.update(dict(kv_str.split("=", 1) - for kv_str in nic_str.split(","))) - if bool(nic_info["net-id"]) == bool(nic_info["port-id"]): - msg = _("either net-id or port-id should be specified " - "but not both") - raise exceptions.CommandError(msg) - if self.app.client_manager.is_network_endpoint_enabled(): - network_client = self.app.client_manager.network - if nic_info["net-id"]: - net = network_client.find_network( - nic_info["net-id"], ignore_missing=False) - nic_info["net-id"] = net.id - if nic_info["port-id"]: - port = network_client.find_port( - nic_info["port-id"], ignore_missing=False) - nic_info["port-id"] = port.id - else: - if nic_info["net-id"]: - nic_info["net-id"] = utils.find_resource( - compute_client.networks, - nic_info["net-id"] - ).id - if nic_info["port-id"]: - msg = _("can't create server with port specified " - "since network endpoint not enabled") + if parsed_args.nic in ('auto', 'none'): + nics = [parsed_args.nic] + else: + for nic_str in parsed_args.nic: + nic_info = {"net-id": "", "v4-fixed-ip": "", + "v6-fixed-ip": "", "port-id": ""} + nic_info.update(dict(kv_str.split("=", 1) + for kv_str in nic_str.split(","))) + if bool(nic_info["net-id"]) == bool(nic_info["port-id"]): + msg = _("either net-id or port-id should be specified " + "but not both") raise exceptions.CommandError(msg) - nics.append(nic_info) + if self.app.client_manager.is_network_endpoint_enabled(): + network_client = self.app.client_manager.network + if nic_info["net-id"]: + net = network_client.find_network( + nic_info["net-id"], ignore_missing=False) + nic_info["net-id"] = net.id + if nic_info["port-id"]: + port = network_client.find_port( + nic_info["port-id"], ignore_missing=False) + nic_info["port-id"] = port.id + else: + if nic_info["net-id"]: + nic_info["net-id"] = utils.find_resource( + compute_client.networks, + nic_info["net-id"] + ).id + if nic_info["port-id"]: + msg = _("can't create server with port specified " + "since network endpoint not enabled") + raise exceptions.CommandError(msg) + nics.append(nic_info) hints = {} for hint in parsed_args.hint: diff --git a/releasenotes/notes/add-auto-and-none-as-nic-parameter-ed23a6e7f99f250d.yaml b/releasenotes/notes/add-auto-and-none-as-nic-parameter-ed23a6e7f99f250d.yaml new file mode 100644 index 0000000000..9c871f78ef --- /dev/null +++ b/releasenotes/notes/add-auto-and-none-as-nic-parameter-ed23a6e7f99f250d.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + Added ``auto`` and ``none`` as values for ``--nic`` to + the``server create`` command. Specifying ``none`` will not + attach a network to the server. Specifying ``auto`` + will automatically attach a network. Note, v2.37 (or newer) + of the Compute API is required for these options. + [Bug `1650342 `_] From 5084ce14b0b99cb79286d3dd57ebb7c5ea10ac1c Mon Sep 17 00:00:00 2001 From: Rodolfo Alonso Hernandez Date: Thu, 22 Dec 2016 13:49:27 +0000 Subject: [PATCH 1434/3095] Update earliest-version number format in release notes Change-Id: I67deca8d2ff1f63d272b8d5b78cb31efd6e6d4df Closes-Bug: #1652054 --- releasenotes/source/unreleased.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/releasenotes/source/unreleased.rst b/releasenotes/source/unreleased.rst index 62d0c014d5..220d13c2cd 100644 --- a/releasenotes/source/unreleased.rst +++ b/releasenotes/source/unreleased.rst @@ -3,4 +3,4 @@ Current Release Notes ===================== .. release-notes:: - :earliest-version: 3.0 + :earliest-version: 3.0.0 From 9e1e7e1c9fde2e60b2f95f3bd000c599a9e1c72a Mon Sep 17 00:00:00 2001 From: Rodolfo Alonso Hernandez Date: Fri, 12 Aug 2016 16:51:35 +0100 Subject: [PATCH 1435/3095] Add support for QoS rule type commands Added following commands: - network qos rule type list Closes-Bug: 1612194 Depends-On: Iecf7bc7acd244a842aae963993f37a64a26b43b9 Change-Id: I38af823c726ceaba9d0b45488fa48e2d93971c92 --- .../command-objects/network-qos-rule-type.rst | 18 ++++++ doc/source/commands.rst | 1 + .../network/v2/network_qos_rule_type.py | 41 ++++++++++++ .../network/v2/test_network_qos_rule_type.py | 29 +++++++++ .../tests/unit/network/v2/fakes.py | 45 ++++++++++++++ .../network/v2/test_network_qos_rule_type.py | 62 +++++++++++++++++++ ...twork-qos-rule-types-337e464c6e81f29c.yaml | 6 ++ setup.cfg | 2 + 8 files changed, 204 insertions(+) create mode 100644 doc/source/command-objects/network-qos-rule-type.rst create mode 100644 openstackclient/network/v2/network_qos_rule_type.py create mode 100644 openstackclient/tests/functional/network/v2/test_network_qos_rule_type.py create mode 100644 openstackclient/tests/unit/network/v2/test_network_qos_rule_type.py create mode 100644 releasenotes/notes/add-network-qos-rule-types-337e464c6e81f29c.yaml diff --git a/doc/source/command-objects/network-qos-rule-type.rst b/doc/source/command-objects/network-qos-rule-type.rst new file mode 100644 index 0000000000..020a05cd5f --- /dev/null +++ b/doc/source/command-objects/network-qos-rule-type.rst @@ -0,0 +1,18 @@ +===================== +network qos rule type +===================== + +A **Network QoS rule type** is a specific Network QoS rule type available to be +used. + +Network v2 + +network qos rule type list +-------------------------- + +List Network QoS rule types + +.. program:: network qos rule type list +.. code:: bash + + os network qos rule type list diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 206b18af3a..52e05d7e09 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -113,6 +113,7 @@ referring to both Compute and Volume quotas. * ``network agent``: (**Network**) - A network agent is an agent that handles various tasks used to implement virtual networks * ``network rbac``: (**Network**) - an RBAC policy for network resources * ``network qos policy``: (**Network**) - a QoS policy for network resources +* ``network qos rule type``: (**Network**) - list of QoS available rule types * ``network segment``: (**Network**) - a segment of a virtual network * ``network service provider``: (**Network**) - a driver providing a network service * ``object``: (**Object Storage**) a single file in the Object Storage diff --git a/openstackclient/network/v2/network_qos_rule_type.py b/openstackclient/network/v2/network_qos_rule_type.py new file mode 100644 index 0000000000..82a265b8c9 --- /dev/null +++ b/openstackclient/network/v2/network_qos_rule_type.py @@ -0,0 +1,41 @@ +# 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 logging + +from osc_lib.command import command +from osc_lib import utils + + +LOG = logging.getLogger(__name__) + + +class ListNetworkQosRuleType(command.Lister): + """List QoS rule types""" + + def take_action(self, parsed_args): + client = self.app.client_manager.network + columns = ( + 'type', + ) + column_headers = ( + 'Type', + ) + data = client.qos_rule_types() + + return (column_headers, + (utils.get_item_properties( + s, columns, formatters={}, + ) for s in data)) diff --git a/openstackclient/tests/functional/network/v2/test_network_qos_rule_type.py b/openstackclient/tests/functional/network/v2/test_network_qos_rule_type.py new file mode 100644 index 0000000000..4d0dbab06d --- /dev/null +++ b/openstackclient/tests/functional/network/v2/test_network_qos_rule_type.py @@ -0,0 +1,29 @@ +# 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 NetworkQosRuleTypeTests(base.TestCase): + """Functional tests for Network QoS rule type. """ + + AVAILABLE_RULE_TYPES = ['dscp_marking', + 'bandwidth_limit', + 'minimum_bandwidth'] + + def test_qos_rule_type_list(self): + raw_output = self.openstack('network qos rule type list') + for rule_type in self.AVAILABLE_RULE_TYPES: + self.assertIn(rule_type, raw_output) diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index 84f145fbe1..88e67f43ff 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -813,6 +813,51 @@ def get_qos_policies(qos_policies=None, count=2): return mock.Mock(side_effect=qos_policies) +class FakeNetworkQosRuleType(object): + """Fake one or more Network QoS rule types.""" + + @staticmethod + def create_one_qos_rule_type(attrs=None): + """Create a fake Network QoS rule type. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object with name, id, etc. + """ + attrs = attrs or {} + + # Set default attributes. + qos_rule_type_attrs = { + 'type': 'rule-type-' + uuid.uuid4().hex, + } + + # Overwrite default attributes. + qos_rule_type_attrs.update(attrs) + + return fakes.FakeResource( + info=copy.deepcopy(qos_rule_type_attrs), + loaded=True) + + @staticmethod + def create_qos_rule_types(attrs=None, count=2): + """Create multiple fake Network QoS rule types. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of QoS rule types to fake + :return: + A list of FakeResource objects faking the QoS rule types + """ + qos_rule_types = [] + for i in range(0, count): + qos_rule_types.append( + FakeNetworkQosRuleType.create_one_qos_rule_type(attrs)) + + return qos_rule_types + + class FakeRouter(object): """Fake one or more routers.""" diff --git a/openstackclient/tests/unit/network/v2/test_network_qos_rule_type.py b/openstackclient/tests/unit/network/v2/test_network_qos_rule_type.py new file mode 100644 index 0000000000..b93abe8017 --- /dev/null +++ b/openstackclient/tests/unit/network/v2/test_network_qos_rule_type.py @@ -0,0 +1,62 @@ +# 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_qos_rule_type as _qos_rule_type +from openstackclient.tests.unit.network.v2 import fakes as network_fakes + + +class TestNetworkQosRuleType(network_fakes.TestNetworkV2): + + def setUp(self): + super(TestNetworkQosRuleType, self).setUp() + # Get a shortcut to the network client + self.network = self.app.client_manager.network + + +class TestListNetworkQosRuleType(TestNetworkQosRuleType): + + # The QoS policies to list up. + qos_rule_types = ( + network_fakes.FakeNetworkQosRuleType.create_qos_rule_types(count=3)) + columns = ( + 'Type', + ) + data = [] + for qos_rule_type in qos_rule_types: + data.append(( + qos_rule_type.type, + )) + + def setUp(self): + super(TestListNetworkQosRuleType, self).setUp() + self.network.qos_rule_types = mock.Mock( + return_value=self.qos_rule_types) + + # Get the command object to test + self.cmd = _qos_rule_type.ListNetworkQosRuleType(self.app, + self.namespace) + + def test_qos_rule_type_list(self): + arglist = [] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.qos_rule_types.assert_called_once_with(**{}) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) diff --git a/releasenotes/notes/add-network-qos-rule-types-337e464c6e81f29c.yaml b/releasenotes/notes/add-network-qos-rule-types-337e464c6e81f29c.yaml new file mode 100644 index 0000000000..2cff599ff5 --- /dev/null +++ b/releasenotes/notes/add-network-qos-rule-types-337e464c6e81f29c.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add support for Network QoS rule type commands: + ``network qos rule type list``, + [Bug `1612194 `_] diff --git a/setup.cfg b/setup.cfg index 8c5c663055..7e1bfc1735 100644 --- a/setup.cfg +++ b/setup.cfg @@ -372,6 +372,8 @@ openstack.network.v2 = network_qos_policy_set = openstackclient.network.v2.network_qos_policy:SetNetworkQosPolicy network_qos_policy_show = openstackclient.network.v2.network_qos_policy:ShowNetworkQosPolicy + network_qos_rule_type_list = openstackclient.network.v2.network_qos_rule_type:ListNetworkQosRuleType + network_rbac_create = openstackclient.network.v2.network_rbac:CreateNetworkRBAC network_rbac_delete = openstackclient.network.v2.network_rbac:DeleteNetworkRBAC network_rbac_list = openstackclient.network.v2.network_rbac:ListNetworkRBAC From 780ce07459f8c196dbf289909c390ff88a380e3f Mon Sep 17 00:00:00 2001 From: Badhmapriya Boopalan Date: Thu, 8 Dec 2016 14:48:25 +0000 Subject: [PATCH 1436/3095] To support '--project' and '--project-domain' options Include '--project' and '--project-domain' filtering options to 'port list' command. Change-Id: Ic7deae348f737c754b2f3e2113bd76d71a442400 Closes-Bug: #1648087 --- doc/source/command-objects/port.rst | 10 +++++ openstackclient/network/v2/port.py | 15 +++++++ .../tests/unit/network/v2/test_port.py | 41 +++++++++++++++++++ .../notes/bug-1648087-21dfb7250abfdbe9.yaml | 6 +++ 4 files changed, 72 insertions(+) create mode 100644 releasenotes/notes/bug-1648087-21dfb7250abfdbe9.yaml diff --git a/doc/source/command-objects/port.rst b/doc/source/command-objects/port.rst index 3f6e3fc02c..522cb3ac5e 100644 --- a/doc/source/command-objects/port.rst +++ b/doc/source/command-objects/port.rst @@ -149,6 +149,7 @@ List ports [--network ] [--mac-address ] [--long] + [--project [--project-domain ]] .. option:: --device-owner @@ -175,6 +176,15 @@ List ports List additional fields in output +.. option:: --project + + List ports according to their project (name or ID) + +.. option:: --project-domain + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + port set -------- diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 4525da1800..b1d456410b 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -432,11 +432,18 @@ def get_parser(self, prog_name): default=False, help=_("List additional fields in output") ) + parser.add_argument( + '--project', + metavar='', + help=_("List ports according to their project (name or ID)") + ) + identity_common.add_project_domain_option_to_parser(parser) return parser def take_action(self, parsed_args): network_client = self.app.client_manager.network compute_client = self.app.client_manager.compute + identity_client = self.app.client_manager.identity columns = ( 'id', @@ -473,6 +480,14 @@ def take_action(self, parsed_args): filters['network_id'] = network.id if parsed_args.mac_address: filters['mac_address'] = parsed_args.mac_address + if parsed_args.project: + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + filters['tenant_id'] = project_id + filters['project_id'] = project_id data = network_client.ports(**filters) diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index 255e81166e..6f61eabc34 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -20,6 +20,7 @@ from openstackclient.network.v2 import port from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes from openstackclient.tests.unit.network.v2 import fakes as network_fakes from openstackclient.tests.unit import utils as tests_utils @@ -31,6 +32,8 @@ def setUp(self): # Get a shortcut to the network client self.network = self.app.client_manager.network + # Get a shortcut to the ProjectManager Mock + self.projects_mock = self.app.client_manager.identity.projects def _get_common_cols_data(self, fake_port): columns = ( @@ -673,6 +676,44 @@ def test_list_port_with_long(self): self.assertEqual(self.columns_long, columns) self.assertEqual(self.data_long, list(data)) + def test_port_list_project(self): + project = identity_fakes.FakeProject.create_one_project() + self.projects_mock.get.return_value = project + arglist = [ + '--project', project.id, + ] + verifylist = [ + ('project', project.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'tenant_id': project.id, 'project_id': project.id} + + self.network.ports.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_port_list_project_domain(self): + project = identity_fakes.FakeProject.create_one_project() + self.projects_mock.get.return_value = project + arglist = [ + '--project', project.id, + '--project-domain', project.domain_id, + ] + verifylist = [ + ('project', project.id), + ('project_domain', project.domain_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'tenant_id': project.id, 'project_id': project.id} + + self.network.ports.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + class TestSetPort(TestPort): diff --git a/releasenotes/notes/bug-1648087-21dfb7250abfdbe9.yaml b/releasenotes/notes/bug-1648087-21dfb7250abfdbe9.yaml new file mode 100644 index 0000000000..fad99aee9c --- /dev/null +++ b/releasenotes/notes/bug-1648087-21dfb7250abfdbe9.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--project`` and ``--project-domain`` filtering options + to ``port list`` command. + [Bug `1648087 `_] From 188e32f9e642fc5eaaec83d5c231aa890354671a Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 23 Dec 2016 16:09:45 -0600 Subject: [PATCH 1437/3095] WIP: Skip broken functional tests... ...so we don't have to fix them all in one review. Do not merge this until https://review.openstack.org/#/c/414649 is merged and funtional tests pass. Change-Id: I4f187111d3e4d8c4a613c20a946d6b5d3562e879 --- openstackclient/tests/functional/common/test_quota.py | 6 ++++++ .../tests/functional/network/v2/test_ip_availability.py | 2 ++ .../tests/functional/network/v2/test_network_agent.py | 4 ++++ .../tests/functional/network/v2/test_network_qos_policy.py | 2 ++ .../functional/network/v2/test_network_qos_rule_type.py | 3 +++ .../functional/network/v2/test_network_service_provider.py | 3 +++ openstackclient/tests/functional/network/v2/test_port.py | 2 ++ .../tests/functional/network/v2/test_security_group.py | 2 ++ .../tests/functional/network/v2/test_security_group_rule.py | 4 ++++ 9 files changed, 28 insertions(+) diff --git a/openstackclient/tests/functional/common/test_quota.py b/openstackclient/tests/functional/common/test_quota.py index c1de9aa92d..fbb8e56367 100644 --- a/openstackclient/tests/functional/common/test_quota.py +++ b/openstackclient/tests/functional/common/test_quota.py @@ -10,6 +10,8 @@ # License for the specific language governing permissions and limitations # under the License. +import testtools + from openstackclient.tests.functional import base @@ -25,6 +27,7 @@ def setUpClass(cls): cls.PROJECT_NAME =\ cls.get_openstack_configuration_value('auth.project_name') + @testtools.skip('broken SDK testing') def test_quota_set(self): self.openstack('quota set --instances 11 --volumes 11 --networks 11 ' + self.PROJECT_NAME) @@ -32,16 +35,19 @@ def test_quota_set(self): raw_output = self.openstack('quota show ' + self.PROJECT_NAME + opts) self.assertEqual("11\n11\n11\n", raw_output) + @testtools.skip('broken SDK testing') def test_quota_show(self): raw_output = self.openstack('quota show ' + self.PROJECT_NAME) for expected_field in self.EXPECTED_FIELDS: self.assertIn(expected_field, raw_output) + @testtools.skip('broken SDK testing') def test_quota_show_default_project(self): raw_output = self.openstack('quota show') for expected_field in self.EXPECTED_FIELDS: self.assertIn(expected_field, raw_output) + @testtools.skip('broken SDK testing') def test_quota_show_with_default_option(self): raw_output = self.openstack('quota show --default') for expected_field in self.EXPECTED_FIELDS: diff --git a/openstackclient/tests/functional/network/v2/test_ip_availability.py b/openstackclient/tests/functional/network/v2/test_ip_availability.py index b5c908f44d..edbe7e3c3e 100644 --- a/openstackclient/tests/functional/network/v2/test_ip_availability.py +++ b/openstackclient/tests/functional/network/v2/test_ip_availability.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import testtools import uuid from openstackclient.tests.functional import base @@ -46,6 +47,7 @@ def test_ip_availability_list(self): raw_output = self.openstack('ip availability list' + opts) self.assertIn(self.NETWORK_NAME, raw_output) + @testtools.skip('broken SDK testing') def test_ip_availability_show(self): opts = self.get_opts(self.FIELDS) raw_output = self.openstack( diff --git a/openstackclient/tests/functional/network/v2/test_network_agent.py b/openstackclient/tests/functional/network/v2/test_network_agent.py index dd6112e72e..e99dcef6e7 100644 --- a/openstackclient/tests/functional/network/v2/test_network_agent.py +++ b/openstackclient/tests/functional/network/v2/test_network_agent.py @@ -10,6 +10,8 @@ # License for the specific language governing permissions and limitations # under the License. +import testtools + from openstackclient.tests.functional import base @@ -26,11 +28,13 @@ def test_network_agent_list(cls): # get the list of network agent IDs. cls.IDs = raw_output.split('\n') + @testtools.skip('broken SDK testing') def test_network_agent_show(self): opts = self.get_opts(self.FIELDS) raw_output = self.openstack('network agent show ' + self.IDs[0] + opts) self.assertEqual(self.IDs[0] + "\n", raw_output) + @testtools.skip('broken SDK testing') def test_network_agent_set(self): opts = self.get_opts(['admin_state_up']) self.openstack('network agent set --disable ' + self.IDs[0]) diff --git a/openstackclient/tests/functional/network/v2/test_network_qos_policy.py b/openstackclient/tests/functional/network/v2/test_network_qos_policy.py index 07dea31b76..6cf6440160 100644 --- a/openstackclient/tests/functional/network/v2/test_network_qos_policy.py +++ b/openstackclient/tests/functional/network/v2/test_network_qos_policy.py @@ -13,6 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. +import testtools import uuid from openstackclient.tests.functional import base @@ -25,6 +26,7 @@ class QosPolicyTests(base.TestCase): FIELDS = ['name'] @classmethod + @testtools.skip('broken SDK testing') def setUpClass(cls): opts = cls.get_opts(cls.FIELDS) raw_output = cls.openstack('network qos policy create ' + cls.NAME + diff --git a/openstackclient/tests/functional/network/v2/test_network_qos_rule_type.py b/openstackclient/tests/functional/network/v2/test_network_qos_rule_type.py index 4d0dbab06d..2bb04a9d40 100644 --- a/openstackclient/tests/functional/network/v2/test_network_qos_rule_type.py +++ b/openstackclient/tests/functional/network/v2/test_network_qos_rule_type.py @@ -13,6 +13,8 @@ # License for the specific language governing permissions and limitations # under the License. +import testtools + from openstackclient.tests.functional import base @@ -23,6 +25,7 @@ class NetworkQosRuleTypeTests(base.TestCase): 'bandwidth_limit', 'minimum_bandwidth'] + @testtools.skip('broken SDK testing') def test_qos_rule_type_list(self): raw_output = self.openstack('network qos rule type list') for rule_type in self.AVAILABLE_RULE_TYPES: diff --git a/openstackclient/tests/functional/network/v2/test_network_service_provider.py b/openstackclient/tests/functional/network/v2/test_network_service_provider.py index 379de4305a..82420ea320 100644 --- a/openstackclient/tests/functional/network/v2/test_network_service_provider.py +++ b/openstackclient/tests/functional/network/v2/test_network_service_provider.py @@ -13,6 +13,8 @@ # License for the specific language governing permissions and limitations # under the License. +import testtools + from openstackclient.tests.functional import base @@ -21,6 +23,7 @@ class TestNetworkServiceProvider(base.TestCase): SERVICE_TYPE = ['L3_ROUTER_NAT'] + @testtools.skip('broken SDK testing') 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/functional/network/v2/test_port.py b/openstackclient/tests/functional/network/v2/test_port.py index decd9553dc..a4a0a94b37 100644 --- a/openstackclient/tests/functional/network/v2/test_port.py +++ b/openstackclient/tests/functional/network/v2/test_port.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import testtools import uuid from openstackclient.tests.functional import base @@ -23,6 +24,7 @@ class PortTests(base.TestCase): FIELDS = ['name'] @classmethod + @testtools.skip('broken SDK testing') def setUpClass(cls): # Create a network for the subnet. cls.openstack('network create ' + cls.NETWORK_NAME) diff --git a/openstackclient/tests/functional/network/v2/test_security_group.py b/openstackclient/tests/functional/network/v2/test_security_group.py index debd81df6e..7c6338ea44 100644 --- a/openstackclient/tests/functional/network/v2/test_security_group.py +++ b/openstackclient/tests/functional/network/v2/test_security_group.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import testtools import uuid from openstackclient.tests.functional import base @@ -23,6 +24,7 @@ class SecurityGroupTests(base.TestCase): FIELDS = ['name'] @classmethod + @testtools.skip('broken SDK testing') def setUpClass(cls): opts = cls.get_opts(cls.FIELDS) raw_output = cls.openstack('security group create ' + cls.NAME + opts) diff --git a/openstackclient/tests/functional/network/v2/test_security_group_rule.py b/openstackclient/tests/functional/network/v2/test_security_group_rule.py index c91de1a570..5b12e1bc9c 100644 --- a/openstackclient/tests/functional/network/v2/test_security_group_rule.py +++ b/openstackclient/tests/functional/network/v2/test_security_group_rule.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import testtools import uuid from openstackclient.tests.functional import base @@ -24,6 +25,7 @@ class SecurityGroupRuleTests(base.TestCase): ID_HEADER = ['ID'] @classmethod + @testtools.skip('broken SDK testing') def setUpClass(cls): # Create the security group to hold the rule. opts = cls.get_opts(cls.NAME_FIELD) @@ -52,6 +54,7 @@ def tearDownClass(cls): cls.SECURITY_GROUP_NAME) cls.assertOutput('', raw_output) + @testtools.skip('broken SDK testing') def test_security_group_rule_list(self): opts = self.get_opts(self.ID_HEADER) raw_output = self.openstack('security group rule list ' + @@ -59,6 +62,7 @@ def test_security_group_rule_list(self): opts) self.assertIn(self.SECURITY_GROUP_RULE_ID, raw_output) + @testtools.skip('broken SDK testing') def test_security_group_rule_show(self): opts = self.get_opts(self.ID_FIELD) raw_output = self.openstack('security group rule show ' + From a3fbbd923b1fdd6ddbc0c320811a05c9a7670d47 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sat, 24 Dec 2016 09:22:57 +0000 Subject: [PATCH 1438/3095] Updated from global requirements Change-Id: I673f91955be4dfde1eea54b48cc7e9a554447fcd --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ddbf13a949..81a129e0dc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ six>=1.9.0 # MIT Babel>=2.3.4 # BSD cliff>=2.3.0 # Apache-2.0 keystoneauth1>=2.16.0 # Apache-2.0 -openstacksdk>=0.9.10 # Apache-2.0 +openstacksdk!=0.9.11,>=0.9.10 # Apache-2.0 osc-lib>=1.2.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 oslo.utils>=3.18.0 # Apache-2.0 From b7ad35c0079870341cda6a8d3c903bcd90f1c600 Mon Sep 17 00:00:00 2001 From: Jordan Pittier Date: Mon, 19 Dec 2016 17:30:13 +0100 Subject: [PATCH 1439/3095] Test-requirements.txt: Bump Hacking to 0.12 We already pass all the checks. Change-Id: I9af2eb26dfb1ecf296753dcfc505768d534929ea --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 3b07d3c0da..050db6c292 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,7 +1,7 @@ # The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -hacking<0.11,>=0.10.0 +hacking>=0.12.0,!=0.13.0,<0.14 # Apache-2.0 coverage>=4.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD From 7b1febf47f775ca6f0024cd7f1eaebe72a73d6fe Mon Sep 17 00:00:00 2001 From: "jiahui.qiang" Date: Thu, 8 Dec 2016 10:03:21 +0800 Subject: [PATCH 1440/3095] Add unit tests for usage commands in compute v2 Add unit tests and fakes for command below in compute v2: usage list usage show Change-Id: Ie533e23375ca6b8ba4cb7e865d39fac652cc0195 --- .../tests/unit/compute/v2/fakes.py | 65 +++++++ .../tests/unit/compute/v2/test_usage.py | 179 ++++++++++++++++++ 2 files changed, 244 insertions(+) create mode 100644 openstackclient/tests/unit/compute/v2/test_usage.py diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index 985ce5e2ed..4fe735b6b4 100644 --- a/openstackclient/tests/unit/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -168,6 +168,9 @@ def __init__(self, **kwargs): self.quota_classes = mock.Mock() self.quota_classes.resource_class = fakes.FakeResource(None, {}) + self.usage = mock.Mock() + self.usage.resource_class = fakes.FakeResource(None, {}) + self.volumes = mock.Mock() self.volumes.resource_class = fakes.FakeResource(None, {}) @@ -1248,3 +1251,65 @@ def create_one_server_group(attrs=None): info=copy.deepcopy(server_group_info), loaded=True) return server_group + + +class FakeUsage(object): + """Fake one or more usage.""" + + @staticmethod + def create_one_usage(attrs=None): + """Create a fake usage. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object, with tenant_id and other attributes + """ + if attrs is None: + attrs = {} + + # Set default attributes. + usage_info = { + 'tenant_id': 'usage-tenant-id-' + uuid.uuid4().hex, + 'total_memory_mb_usage': 512.0, + 'total_vcpus_usage': 1.0, + 'total_local_gb_usage': 1.0, + 'server_usages': [ + { + 'ended_at': None, + 'flavor': 'usage-flavor-' + uuid.uuid4().hex, + 'hours': 1.0, + 'local_gb': 1, + 'memory_mb': 512, + 'name': 'usage-name-' + uuid.uuid4().hex, + 'state': 'active', + 'uptime': 3600, + 'vcpus': 1 + } + ] + } + + # Overwrite default attributes. + usage_info.update(attrs) + + usage = fakes.FakeResource(info=copy.deepcopy(usage_info), + loaded=True) + + return usage + + @staticmethod + def create_usages(attrs=None, count=2): + """Create multiple fake services. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of services to fake + :return: + A list of FakeResource objects faking the services + """ + usages = [] + for i in range(0, count): + usages.append(FakeUsage.create_one_usage(attrs)) + + return usages diff --git a/openstackclient/tests/unit/compute/v2/test_usage.py b/openstackclient/tests/unit/compute/v2/test_usage.py new file mode 100644 index 0000000000..a383e9036d --- /dev/null +++ b/openstackclient/tests/unit/compute/v2/test_usage.py @@ -0,0 +1,179 @@ +# 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 datetime +import mock + +from openstackclient.compute.v2 import usage +from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes + + +class TestUsage(compute_fakes.TestComputev2): + + def setUp(self): + super(TestUsage, self).setUp() + + self.usage_mock = self.app.client_manager.compute.usage + self.usage_mock.reset_mock() + + self.projects_mock = self.app.client_manager.identity.projects + self.projects_mock.reset_mock() + + +class TestUsageList(TestUsage): + + project = identity_fakes.FakeProject.create_one_project() + # Return value of self.usage_mock.list(). + usages = compute_fakes.FakeUsage.create_usages( + attrs={'tenant_id': project.name}, count=1) + + columns = ( + "Project", + "Servers", + "RAM MB-Hours", + "CPU Hours", + "Disk GB-Hours" + ) + + data = [( + usages[0].tenant_id, + len(usages[0].server_usages), + float("%.2f" % usages[0].total_memory_mb_usage), + float("%.2f" % usages[0].total_vcpus_usage), + float("%.2f" % usages[0].total_local_gb_usage), + )] + + def setUp(self): + super(TestUsageList, self).setUp() + + self.usage_mock.list.return_value = self.usages + + self.projects_mock.list.return_value = [self.project] + # Get the command object to test + self.cmd = usage.ListUsage(self.app, None) + + def test_usage_list_no_options(self): + + arglist = [] + verifylist = [ + ('start', None), + ('end', None), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.projects_mock.list.assert_called_with() + + self.assertEqual(self.columns, columns) + self.assertEqual(tuple(self.data), tuple(data)) + + def test_usage_list_with_options(self): + arglist = [ + '--start', '2016-11-11', + '--end', '2016-12-20', + ] + verifylist = [ + ('start', '2016-11-11'), + ('end', '2016-12-20'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.projects_mock.list.assert_called_with() + self.usage_mock.list.assert_called_with( + datetime.datetime(2016, 11, 11, 0, 0), + datetime.datetime(2016, 12, 20, 0, 0), + detailed=True) + + self.assertEqual(self.columns, columns) + self.assertEqual(tuple(self.data), tuple(data)) + + +class TestUsageShow(TestUsage): + + project = identity_fakes.FakeProject.create_one_project() + # Return value of self.usage_mock.list(). + usage = compute_fakes.FakeUsage.create_one_usage( + attrs={'tenant_id': project.name}) + + columns = ( + 'CPU Hours', + 'Disk GB-Hours', + 'RAM MB-Hours', + 'Servers', + ) + + data = ( + float("%.2f" % usage.total_vcpus_usage), + float("%.2f" % usage.total_local_gb_usage), + float("%.2f" % usage.total_memory_mb_usage), + len(usage.server_usages), + ) + + def setUp(self): + super(TestUsageShow, self).setUp() + + self.usage_mock.get.return_value = self.usage + + self.projects_mock.get.return_value = self.project + # Get the command object to test + self.cmd = usage.ShowUsage(self.app, None) + + def test_usage_show_no_options(self): + + self.app.client_manager.auth_ref = mock.Mock() + self.app.client_manager.auth_ref.project_id = self.project.id + + arglist = [] + verifylist = [ + ('project', None), + ('start', None), + ('end', None), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_usage_show_with_options(self): + + arglist = [ + '--project', self.project.id, + '--start', '2016-11-11', + '--end', '2016-12-20', + ] + verifylist = [ + ('project', self.project.id), + ('start', '2016-11-11'), + ('end', '2016-12-20'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.usage_mock.get.assert_called_with( + self.project.id, + datetime.datetime(2016, 11, 11, 0, 0), + datetime.datetime(2016, 12, 20, 0, 0)) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) From 80c703702a74ecdc6cd5a341319c33767a241a12 Mon Sep 17 00:00:00 2001 From: Ha Van Tu Date: Mon, 26 Dec 2016 15:30:53 +0700 Subject: [PATCH 1441/3095] Fix typo in release notes This patch replace a typo "proprty" with "property". Change-Id: Idf228afd8827e6b33d6eb657c1f3e0cb35286a45 --- releasenotes/source/pre_20_releases.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/releasenotes/source/pre_20_releases.rst b/releasenotes/source/pre_20_releases.rst index a2af7a9a23..06e7c667c2 100644 --- a/releasenotes/source/pre_20_releases.rst +++ b/releasenotes/source/pre_20_releases.rst @@ -194,7 +194,7 @@ Pre-2.0 Releases * catalog list fails in identity v2 Bug `1474656 `_ -* openstack flavor unset NoneType error when used without --proprty +* openstack flavor unset NoneType error when used without --property Bug `1474237 `_ * TypeError: 'NoneType' object does not support item assignment with latest os-client-config From 6929c50a13c59070d3a4f292a06263f57fcb094c Mon Sep 17 00:00:00 2001 From: JingLiu Date: Mon, 26 Dec 2016 17:01:48 +0800 Subject: [PATCH 1442/3095] Fix a spelling error Change-Id: Ic14c769d99986d5c51d26fc276f4d47d6fba3265 --- openstackclient/network/v2/router.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index cdd634d0eb..a0b2e92bb3 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -483,7 +483,7 @@ def get_parser(self, prog_name): routes_ha.add_argument( '--no-ha', action='store_true', - help=_("Clear high availablability attribute of the router " + help=_("Clear high availability attribute of the router " "(disabled router only)") ) # TODO(tangchen): Support setting 'external_gateway_info' property in From 17a249c5ddc22403486f32575e91da7db10ff8e2 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 28 Dec 2016 09:16:21 +0000 Subject: [PATCH 1443/3095] Updated from global requirements Change-Id: I9fbd59f515a49e881fa8927c880087e3c814c362 --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 050db6c292..b84e21e4cc 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,7 +1,7 @@ # The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -hacking>=0.12.0,!=0.13.0,<0.14 # Apache-2.0 +hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0 coverage>=4.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD From 13c8a1c7aaa099c22279043d23c7a5a9424763a8 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 28 Dec 2016 16:25:52 +0000 Subject: [PATCH 1444/3095] Revert "WIP: Skip broken functional tests..." SDK 0.9.11 caused some failures, its blocked now. Revert those breakages that occurred in 0.9.10 -> 0.9.11. This partially reverts commit 188e32f9e642fc5eaaec83d5c231aa890354671a. Change-Id: I8b350250dbdcbf5c4599dfb55f6685c0db18bb30 --- openstackclient/tests/functional/common/test_quota.py | 6 ------ .../tests/functional/network/v2/test_network_agent.py | 4 ---- .../tests/functional/network/v2/test_network_qos_policy.py | 2 -- openstackclient/tests/functional/network/v2/test_port.py | 2 -- .../tests/functional/network/v2/test_security_group.py | 2 -- .../tests/functional/network/v2/test_security_group_rule.py | 4 ---- 6 files changed, 20 deletions(-) diff --git a/openstackclient/tests/functional/common/test_quota.py b/openstackclient/tests/functional/common/test_quota.py index fbb8e56367..c1de9aa92d 100644 --- a/openstackclient/tests/functional/common/test_quota.py +++ b/openstackclient/tests/functional/common/test_quota.py @@ -10,8 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -import testtools - from openstackclient.tests.functional import base @@ -27,7 +25,6 @@ def setUpClass(cls): cls.PROJECT_NAME =\ cls.get_openstack_configuration_value('auth.project_name') - @testtools.skip('broken SDK testing') def test_quota_set(self): self.openstack('quota set --instances 11 --volumes 11 --networks 11 ' + self.PROJECT_NAME) @@ -35,19 +32,16 @@ def test_quota_set(self): raw_output = self.openstack('quota show ' + self.PROJECT_NAME + opts) self.assertEqual("11\n11\n11\n", raw_output) - @testtools.skip('broken SDK testing') def test_quota_show(self): raw_output = self.openstack('quota show ' + self.PROJECT_NAME) for expected_field in self.EXPECTED_FIELDS: self.assertIn(expected_field, raw_output) - @testtools.skip('broken SDK testing') def test_quota_show_default_project(self): raw_output = self.openstack('quota show') for expected_field in self.EXPECTED_FIELDS: self.assertIn(expected_field, raw_output) - @testtools.skip('broken SDK testing') def test_quota_show_with_default_option(self): raw_output = self.openstack('quota show --default') for expected_field in self.EXPECTED_FIELDS: diff --git a/openstackclient/tests/functional/network/v2/test_network_agent.py b/openstackclient/tests/functional/network/v2/test_network_agent.py index e99dcef6e7..dd6112e72e 100644 --- a/openstackclient/tests/functional/network/v2/test_network_agent.py +++ b/openstackclient/tests/functional/network/v2/test_network_agent.py @@ -10,8 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -import testtools - from openstackclient.tests.functional import base @@ -28,13 +26,11 @@ def test_network_agent_list(cls): # get the list of network agent IDs. cls.IDs = raw_output.split('\n') - @testtools.skip('broken SDK testing') def test_network_agent_show(self): opts = self.get_opts(self.FIELDS) raw_output = self.openstack('network agent show ' + self.IDs[0] + opts) self.assertEqual(self.IDs[0] + "\n", raw_output) - @testtools.skip('broken SDK testing') def test_network_agent_set(self): opts = self.get_opts(['admin_state_up']) self.openstack('network agent set --disable ' + self.IDs[0]) diff --git a/openstackclient/tests/functional/network/v2/test_network_qos_policy.py b/openstackclient/tests/functional/network/v2/test_network_qos_policy.py index 6cf6440160..07dea31b76 100644 --- a/openstackclient/tests/functional/network/v2/test_network_qos_policy.py +++ b/openstackclient/tests/functional/network/v2/test_network_qos_policy.py @@ -13,7 +13,6 @@ # License for the specific language governing permissions and limitations # under the License. -import testtools import uuid from openstackclient.tests.functional import base @@ -26,7 +25,6 @@ class QosPolicyTests(base.TestCase): FIELDS = ['name'] @classmethod - @testtools.skip('broken SDK testing') def setUpClass(cls): opts = cls.get_opts(cls.FIELDS) raw_output = cls.openstack('network qos policy create ' + cls.NAME + diff --git a/openstackclient/tests/functional/network/v2/test_port.py b/openstackclient/tests/functional/network/v2/test_port.py index a4a0a94b37..decd9553dc 100644 --- a/openstackclient/tests/functional/network/v2/test_port.py +++ b/openstackclient/tests/functional/network/v2/test_port.py @@ -10,7 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -import testtools import uuid from openstackclient.tests.functional import base @@ -24,7 +23,6 @@ class PortTests(base.TestCase): FIELDS = ['name'] @classmethod - @testtools.skip('broken SDK testing') def setUpClass(cls): # Create a network for the subnet. cls.openstack('network create ' + cls.NETWORK_NAME) diff --git a/openstackclient/tests/functional/network/v2/test_security_group.py b/openstackclient/tests/functional/network/v2/test_security_group.py index 7c6338ea44..debd81df6e 100644 --- a/openstackclient/tests/functional/network/v2/test_security_group.py +++ b/openstackclient/tests/functional/network/v2/test_security_group.py @@ -10,7 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -import testtools import uuid from openstackclient.tests.functional import base @@ -24,7 +23,6 @@ class SecurityGroupTests(base.TestCase): FIELDS = ['name'] @classmethod - @testtools.skip('broken SDK testing') def setUpClass(cls): opts = cls.get_opts(cls.FIELDS) raw_output = cls.openstack('security group create ' + cls.NAME + opts) diff --git a/openstackclient/tests/functional/network/v2/test_security_group_rule.py b/openstackclient/tests/functional/network/v2/test_security_group_rule.py index 5b12e1bc9c..c91de1a570 100644 --- a/openstackclient/tests/functional/network/v2/test_security_group_rule.py +++ b/openstackclient/tests/functional/network/v2/test_security_group_rule.py @@ -10,7 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -import testtools import uuid from openstackclient.tests.functional import base @@ -25,7 +24,6 @@ class SecurityGroupRuleTests(base.TestCase): ID_HEADER = ['ID'] @classmethod - @testtools.skip('broken SDK testing') def setUpClass(cls): # Create the security group to hold the rule. opts = cls.get_opts(cls.NAME_FIELD) @@ -54,7 +52,6 @@ def tearDownClass(cls): cls.SECURITY_GROUP_NAME) cls.assertOutput('', raw_output) - @testtools.skip('broken SDK testing') def test_security_group_rule_list(self): opts = self.get_opts(self.ID_HEADER) raw_output = self.openstack('security group rule list ' + @@ -62,7 +59,6 @@ def test_security_group_rule_list(self): opts) self.assertIn(self.SECURITY_GROUP_RULE_ID, raw_output) - @testtools.skip('broken SDK testing') def test_security_group_rule_show(self): opts = self.get_opts(self.ID_FIELD) raw_output = self.openstack('security group rule show ' + From 0948aa6aeb573900bb8b80b0d0b1f91202222715 Mon Sep 17 00:00:00 2001 From: licanwei Date: Fri, 30 Dec 2016 14:16:32 +0800 Subject: [PATCH 1445/3095] update server migrate '--wait' description 'Wait for resize to complete' ==> 'Wait for migrate to complete' Change-Id: I0e08968c0132f0a8e1ef1ce2e6179001315372c3 --- doc/source/command-objects/server.rst | 2 +- openstackclient/compute/v2/server.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index 88f22baf03..40ecc047a1 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -379,7 +379,7 @@ Migrate server to different host .. option:: --wait - Wait for resize to complete + Wait for migrate to complete .. describe:: diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 938c8c06ae..d92218734f 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1009,7 +1009,7 @@ def get_parser(self, prog_name): parser.add_argument( '--wait', action='store_true', - help=_('Wait for resize to complete'), + help=_('Wait for migrate to complete'), ) return parser From 22cee104ed4967bcdf718f9e3c6eed984b9fddfa Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 30 Dec 2016 12:57:02 -0600 Subject: [PATCH 1446/3095] Beef up network functional tests We need to get more thorough in our functional testing, so start by adding tests for all create and set options, check return values. This also removes most of the setupClass() and teardownClass() methods as they held common state that was subject to race conditions when running tests in parallel. Change-Id: I4179f493cea971b7c576ffbf501330b5c57f52f3 --- .../functional/network/v2/test_network.py | 259 ++++++++++++++++-- 1 file changed, 235 insertions(+), 24 deletions(-) diff --git a/openstackclient/tests/functional/network/v2/test_network.py b/openstackclient/tests/functional/network/v2/test_network.py index c77ff642c5..01fb401a44 100644 --- a/openstackclient/tests/functional/network/v2/test_network.py +++ b/openstackclient/tests/functional/network/v2/test_network.py @@ -10,41 +10,252 @@ # License for the specific language governing permissions and limitations # under the License. +import re import uuid from openstackclient.tests.functional import base class NetworkTests(base.TestCase): - """Functional tests for network. """ - NAME = uuid.uuid4().hex - HEADERS = ['Name'] - FIELDS = ['name'] + """Functional tests for network""" @classmethod def setUpClass(cls): - opts = cls.get_opts(cls.FIELDS) - raw_output = cls.openstack('network create ' + cls.NAME + opts) - expected = cls.NAME + '\n' - cls.assertOutput(expected, raw_output) + # Set up some regex for matching below + cls.re_id = re.compile("id\s+\|\s+(\S+)") + cls.re_description = re.compile("description\s+\|\s+([^|]+?)\s+\|") + cls.re_enabled = re.compile("admin_state_up\s+\|\s+(\S+)") + cls.re_shared = re.compile("shared\s+\|\s+(\S+)") + cls.re_external = re.compile("router:external\s+\|\s+(\S+)") + cls.re_default = re.compile("is_default\s+\|\s+(\S+)") + cls.re_port_security = re.compile( + "port_security_enabled\s+\|\s+(\S+)" + ) - @classmethod - def tearDownClass(cls): - raw_output = cls.openstack('network delete ' + cls.NAME) - cls.assertOutput('', raw_output) + def test_network_delete(self): + """Test create, delete multiple""" + name1 = uuid.uuid4().hex + raw_output = self.openstack( + 'network create ' + + '--description aaaa ' + + name1 + ) + self.assertEqual( + 'aaaa', + re.search(self.re_description, raw_output).group(1), + ) + name2 = uuid.uuid4().hex + raw_output = self.openstack( + 'network create ' + + '--description bbbb ' + + name2 + ) + self.assertEqual( + 'bbbb', + re.search(self.re_description, raw_output).group(1), + ) + + del_output = self.openstack('network delete ' + name1 + ' ' + name2) + self.assertOutput('', del_output) def test_network_list(self): - opts = self.get_opts(self.HEADERS) - raw_output = self.openstack('network list' + opts) - self.assertIn(self.NAME, raw_output) + """Test create defaults, list filters, delete""" + name1 = uuid.uuid4().hex + raw_output = self.openstack( + 'network create ' + + '--description aaaa ' + + name1 + ) + self.addCleanup(self.openstack, 'network delete ' + name1) + self.assertEqual( + 'aaaa', + re.search(self.re_description, raw_output).group(1), + ) + # Check the default values + self.assertEqual( + 'UP', + re.search(self.re_enabled, raw_output).group(1), + ) + self.assertEqual( + 'False', + re.search(self.re_shared, raw_output).group(1), + ) + self.assertEqual( + 'Internal', + re.search(self.re_external, raw_output).group(1), + ) + # NOTE(dtroyer): is_default is not present in the create output + # so make sure it stays that way. + self.assertIsNone(re.search(self.re_default, raw_output)) + self.assertEqual( + 'True', + re.search(self.re_port_security, raw_output).group(1), + ) + + name2 = uuid.uuid4().hex + raw_output = self.openstack( + 'network create ' + + '--description bbbb ' + + '--disable ' + + '--share ' + + name2 + ) + self.addCleanup(self.openstack, 'network delete ' + name2) + self.assertEqual( + 'bbbb', + re.search(self.re_description, raw_output).group(1), + ) + self.assertEqual( + 'DOWN', + re.search(self.re_enabled, raw_output).group(1), + ) + self.assertEqual( + 'True', + re.search(self.re_shared, raw_output).group(1), + ) + + # Test list --long + raw_output = self.openstack('network list --long') + self.assertIsNotNone( + re.search("\|\s+" + name1 + "\s+\|\s+ACTIVE", raw_output) + ) + self.assertIsNotNone( + re.search("\|\s+" + name2 + "\s+\|\s+ACTIVE", raw_output) + ) + + # Test list --long --enable + raw_output = self.openstack('network list --long --enable') + self.assertIsNotNone( + re.search("\|\s+" + name1 + "\s+\|\s+ACTIVE", raw_output) + ) + self.assertIsNone( + re.search("\|\s+" + name2 + "\s+\|\s+ACTIVE", raw_output) + ) + + # Test list --long --disable + raw_output = self.openstack('network list --long --disable') + self.assertIsNone( + re.search("\|\s+" + name1 + "\s+\|\s+ACTIVE", raw_output) + ) + self.assertIsNotNone( + re.search("\|\s+" + name2 + "\s+\|\s+ACTIVE", raw_output) + ) + + # Test list --long --share + raw_output = self.openstack('network list --long --share') + self.assertIsNone( + re.search("\|\s+" + name1 + "\s+\|\s+ACTIVE", raw_output) + ) + self.assertIsNotNone( + re.search("\|\s+" + name2 + "\s+\|\s+ACTIVE", raw_output) + ) + + # Test list --long --no-share + raw_output = self.openstack('network list --long --no-share') + self.assertIsNotNone( + re.search("\|\s+" + name1 + "\s+\|\s+ACTIVE", raw_output) + ) + self.assertIsNone( + re.search("\|\s+" + name2 + "\s+\|\s+ACTIVE", raw_output) + ) def test_network_set(self): - raw_output = self.openstack('network set --disable ' + self.NAME) - opts = self.get_opts(['name', 'admin_state_up']) - raw_output = self.openstack('network show ' + self.NAME + opts) - self.assertEqual("DOWN\n" + self.NAME + "\n", raw_output) - - def test_network_show(self): - opts = self.get_opts(self.FIELDS) - raw_output = self.openstack('network show ' + self.NAME + opts) - self.assertEqual(self.NAME + "\n", raw_output) + """Tests create options, set, show, delete""" + name = uuid.uuid4().hex + raw_output = self.openstack( + 'network create ' + + '--description aaaa ' + + '--enable ' + + '--no-share ' + + '--internal ' + + '--no-default ' + + '--enable-port-security ' + + name + ) + self.addCleanup(self.openstack, 'network delete ' + name) + self.assertEqual( + 'aaaa', + re.search(self.re_description, raw_output).group(1), + ) + self.assertEqual( + 'UP', + re.search(self.re_enabled, raw_output).group(1), + ) + self.assertEqual( + 'False', + re.search(self.re_shared, raw_output).group(1), + ) + self.assertEqual( + 'Internal', + re.search(self.re_external, raw_output).group(1), + ) + # NOTE(dtroyer): is_default is not present in the create output + # so make sure it stays that way. + self.assertIsNone(re.search(self.re_default, raw_output)) + self.assertEqual( + 'True', + re.search(self.re_port_security, raw_output).group(1), + ) + + raw_output = self.openstack( + 'network set ' + + '--description cccc ' + + '--disable ' + + '--share ' + + '--external ' + + '--disable-port-security ' + + name + ) + self.assertOutput('', raw_output) + + raw_output = self.openstack('network show ' + name) + + self.assertEqual( + 'cccc', + re.search(self.re_description, raw_output).group(1), + ) + self.assertEqual( + 'DOWN', + re.search(self.re_enabled, raw_output).group(1), + ) + self.assertEqual( + 'True', + re.search(self.re_shared, raw_output).group(1), + ) + self.assertEqual( + 'External', + re.search(self.re_external, raw_output).group(1), + ) + # why not 'None' like above?? + self.assertEqual( + 'False', + re.search(self.re_default, raw_output).group(1), + ) + self.assertEqual( + 'False', + re.search(self.re_port_security, raw_output).group(1), + ) + + # NOTE(dtroyer): There is ambiguity around is_default in that + # it is not in the API docs and apparently can + # not be set when the network is --external, + # although the option handling code only looks at + # the value of is_default when external is True. + raw_output = self.openstack( + 'network set ' + + '--default ' + + name + ) + self.assertOutput('', raw_output) + + raw_output = self.openstack('network show ' + name) + + self.assertEqual( + 'cccc', + re.search(self.re_description, raw_output).group(1), + ) + # NOTE(dtroyer): This should be 'True' + self.assertEqual( + 'False', + re.search(self.re_default, raw_output).group(1), + ) From 241e0ec1cd27626ee7f77a94d1fce134de3fc872 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 29 Dec 2016 14:51:35 -0600 Subject: [PATCH 1447/3095] Beef up address scope functional tests We need to get more thorough in our functional testing, so start by adding tests for all create and set options, check return values. This also removes most of the setupClass() and teardownClass() methods as they held common state that was subject to race conditions when running tests in parallel. Change-Id: Ib337f9e9d16b4183bb319b58cbe943045f365ff2 --- .../network/v2/test_address_scope.py | 148 +++++++++++++++--- 1 file changed, 127 insertions(+), 21 deletions(-) diff --git a/openstackclient/tests/functional/network/v2/test_address_scope.py b/openstackclient/tests/functional/network/v2/test_address_scope.py index ef4b575609..75f843445b 100644 --- a/openstackclient/tests/functional/network/v2/test_address_scope.py +++ b/openstackclient/tests/functional/network/v2/test_address_scope.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import re import uuid from openstackclient.tests.functional import base @@ -17,33 +18,138 @@ class AddressScopeTests(base.TestCase): """Functional tests for address scope. """ - NAME = uuid.uuid4().hex - HEADERS = ['Name'] - FIELDS = ['name'] + + # NOTE(dtroyer): Do not normalize the setup and teardown of the resource + # creation and deletion. Little is gained when each test + # has its own needs and there are collisions when running + # tests in parallel. @classmethod def setUpClass(cls): - opts = cls.get_opts(cls.FIELDS) - raw_output = cls.openstack('address scope create ' + cls.NAME + opts) - cls.assertOutput(cls.NAME + "\n", raw_output) + # Set up some regex for matching below + cls.re_name = re.compile("name\s+\|\s+([^|]+?)\s+\|") + cls.re_ip_version = re.compile("ip_version\s+\|\s+(\S+)") + cls.re_shared = re.compile("shared\s+\|\s+(\S+)") - @classmethod - def tearDownClass(cls): - raw_output = cls.openstack('address scope delete ' + cls.NAME) - cls.assertOutput('', raw_output) + def test_address_scope_delete(self): + """Test create, delete multiple""" + name1 = uuid.uuid4().hex + raw_output = self.openstack( + 'address scope create ' + name1, + ) + self.assertEqual( + name1, + re.search(self.re_name, raw_output).group(1), + ) + # Check the default values + self.assertEqual( + 'False', + re.search(self.re_shared, raw_output).group(1), + ) + + name2 = uuid.uuid4().hex + raw_output = self.openstack( + 'address scope create ' + name2, + ) + self.assertEqual( + name2, + re.search(self.re_name, raw_output).group(1), + ) + + raw_output = self.openstack( + 'address scope delete ' + name1 + ' ' + name2, + ) + self.assertOutput('', raw_output) def test_address_scope_list(self): - opts = self.get_opts(self.HEADERS) - raw_output = self.openstack('address scope list' + opts) - self.assertIn(self.NAME, raw_output) + """Test create defaults, list filters, delete""" + name1 = uuid.uuid4().hex + raw_output = self.openstack( + 'address scope create --ip-version 4 --share ' + name1, + ) + self.addCleanup(self.openstack, 'address scope delete ' + name1) + self.assertEqual( + '4', + re.search(self.re_ip_version, raw_output).group(1), + ) + self.assertEqual( + 'True', + re.search(self.re_shared, raw_output).group(1), + ) + + name2 = uuid.uuid4().hex + raw_output = self.openstack( + 'address scope create --ip-version 6 --no-share ' + name2, + ) + self.addCleanup(self.openstack, 'address scope delete ' + name2) + self.assertEqual( + '6', + re.search(self.re_ip_version, raw_output).group(1), + ) + self.assertEqual( + 'False', + re.search(self.re_shared, raw_output).group(1), + ) - def test_address_scope_show(self): - opts = self.get_opts(self.FIELDS) - raw_output = self.openstack('address scope show ' + self.NAME + opts) - self.assertEqual(self.NAME + "\n", raw_output) + # Test list + raw_output = self.openstack('address scope list') + self.assertIsNotNone(re.search(name1 + "\s+\|\s+4", raw_output)) + self.assertIsNotNone(re.search(name2 + "\s+\|\s+6", raw_output)) + + # Test list --share + # TODO(dtroyer): returns 'HttpException: Bad Request' + # raw_output = self.openstack('address scope list --share') + # self.assertIsNotNone(re.search(name1 + "\s+\|\s+4", raw_output)) + # self.assertIsNotNone(re.search(name2 + "\s+\|\s+6", raw_output)) + + # Test list --no-share + # TODO(dtroyer): returns 'HttpException: Bad Request' + # raw_output = self.openstack('address scope list --no-share') + # self.assertIsNotNone(re.search(name1 + "\s+\|\s+4", raw_output)) + # self.assertIsNotNone(re.search(name2 + "\s+\|\s+6", raw_output)) def test_address_scope_set(self): - self.openstack('address scope set --share ' + self.NAME) - opts = self.get_opts(['shared']) - raw_output = self.openstack('address scope show ' + self.NAME + opts) - self.assertEqual("True\n", raw_output) + """Tests create options, set, show, delete""" + name = uuid.uuid4().hex + newname = name + "_" + raw_output = self.openstack( + 'address scope create ' + + '--ip-version 4 ' + + '--no-share ' + + name, + ) + self.addCleanup(self.openstack, 'address scope delete ' + newname) + self.assertEqual( + name, + re.search(self.re_name, raw_output).group(1), + ) + self.assertEqual( + '4', + re.search(self.re_ip_version, raw_output).group(1), + ) + self.assertEqual( + 'False', + re.search(self.re_shared, raw_output).group(1), + ) + + raw_output = self.openstack( + 'address scope set ' + + '--name ' + newname + + ' --share ' + + name, + ) + self.assertOutput('', raw_output) + + raw_output = self.openstack('address scope show ' + newname) + self.assertEqual( + newname, + re.search(self.re_name, raw_output).group(1), + ) + self.assertEqual( + '4', + re.search(self.re_ip_version, raw_output).group(1), + ) + self.assertEqual( + 'True', + re.search(self.re_shared, raw_output).group(1), + ) From 3da71c819d1ecda6a629ccd75d26237d8ce8f470 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 29 Dec 2016 22:13:36 -0600 Subject: [PATCH 1448/3095] Beef up floating IP functional tests We need to get more thorough in our functional testing, so start by adding tests for create options. This also removes the parts of the setupClass() and teardownClass() methods that do not pertain to the static prereqs for testing. Change-Id: I0a090a8abc41613d8970343d1b67d101b4c82c65 --- .../functional/network/v2/test_floating_ip.py | 143 +++++++++++++++--- 1 file changed, 121 insertions(+), 22 deletions(-) diff --git a/openstackclient/tests/functional/network/v2/test_floating_ip.py b/openstackclient/tests/functional/network/v2/test_floating_ip.py index f3a1971f79..f0b12bc7a8 100644 --- a/openstackclient/tests/functional/network/v2/test_floating_ip.py +++ b/openstackclient/tests/functional/network/v2/test_floating_ip.py @@ -10,49 +10,148 @@ # License for the specific language governing permissions and limitations # under the License. +import random +import re import uuid from openstackclient.tests.functional import base class FloatingIpTests(base.TestCase): - """Functional tests for floating ip. """ + """Functional tests for floating ip""" SUBNET_NAME = uuid.uuid4().hex NETWORK_NAME = uuid.uuid4().hex - ID = None - HEADERS = ['ID'] - FIELDS = ['id'] @classmethod def setUpClass(cls): - # Create a network for the floating ip. - cls.openstack('network create --external ' + cls.NETWORK_NAME) - # Create a subnet for the network. - cls.openstack( - 'subnet create --network ' + cls.NETWORK_NAME + - ' --subnet-range 10.10.10.0/24 ' + - cls.SUBNET_NAME + # Set up some regex for matching below + cls.re_id = re.compile("id\s+\|\s+(\S+)") + cls.re_floating_ip = re.compile("floating_ip_address\s+\|\s+(\S+)") + cls.re_fixed_ip = re.compile("fixed_ip_address\s+\|\s+(\S+)") + cls.re_description = re.compile("description\s+\|\s+([^|]+?)\s+\|") + cls.re_network_id = re.compile("floating_network_id\s+\|\s+(\S+)") + + # Make a random subnet + cls.subnet = ".".join(map( + str, + (random.randint(0, 255) for _ in range(3)) + )) + ".0/26" + + # Create a network for the floating ip + raw_output = cls.openstack( + 'network create --external ' + cls.NETWORK_NAME ) - opts = cls.get_opts(cls.FIELDS) + cls.network_id = re.search(cls.re_id, raw_output).group(1) + + # Create a subnet for the network raw_output = cls.openstack( - 'floating ip create ' + cls.NETWORK_NAME + opts) - cls.ID = raw_output.strip('\n') + 'subnet create ' + + '--network ' + cls.NETWORK_NAME + ' ' + + '--subnet-range ' + cls.subnet + ' ' + + cls.SUBNET_NAME + ) + cls.subnet_id = re.search(cls.re_id, raw_output).group(1) @classmethod def tearDownClass(cls): - raw_output = cls.openstack('floating ip delete ' + cls.ID) - cls.assertOutput('', raw_output) raw_output = cls.openstack('subnet delete ' + cls.SUBNET_NAME) cls.assertOutput('', raw_output) raw_output = cls.openstack('network delete ' + cls.NETWORK_NAME) cls.assertOutput('', raw_output) + def test_floating_ip_delete(self): + """Test create, delete multiple""" + raw_output = self.openstack( + 'floating ip create ' + + '--description aaaa ' + + self.NETWORK_NAME + ) + re_ip = re.search(self.re_floating_ip, raw_output) + self.assertIsNotNone(re_ip) + ip1 = re_ip.group(1) + self.assertEqual( + 'aaaa', + re.search(self.re_description, raw_output).group(1), + ) + + raw_output = self.openstack( + 'floating ip create ' + + '--description bbbb ' + + self.NETWORK_NAME + ) + ip2 = re.search(self.re_floating_ip, raw_output).group(1) + self.assertEqual( + 'bbbb', + re.search(self.re_description, raw_output).group(1), + ) + + # Clean up after ourselves + raw_output = self.openstack('floating ip delete ' + ip1 + ' ' + ip2) + self.assertOutput('', raw_output) + def test_floating_ip_list(self): - opts = self.get_opts(self.HEADERS) - raw_output = self.openstack('floating ip list' + opts) - self.assertIn(self.ID, raw_output) + """Test create defaults, list filters, delete""" + raw_output = self.openstack( + 'floating ip create ' + + '--description aaaa ' + + self.NETWORK_NAME + ) + re_ip = re.search(self.re_floating_ip, raw_output) + self.assertIsNotNone(re_ip) + ip1 = re_ip.group(1) + self.addCleanup(self.openstack, 'floating ip delete ' + ip1) + self.assertEqual( + 'aaaa', + re.search(self.re_description, raw_output).group(1), + ) + self.assertIsNotNone(re.search(self.re_network_id, raw_output)) + + raw_output = self.openstack( + 'floating ip create ' + + '--description bbbb ' + + self.NETWORK_NAME + ) + ip2 = re.search(self.re_floating_ip, raw_output).group(1) + self.addCleanup(self.openstack, 'floating ip delete ' + ip2) + self.assertEqual( + 'bbbb', + re.search(self.re_description, raw_output).group(1), + ) + + # Test list + raw_output = self.openstack('floating ip list') + self.assertIsNotNone(re.search("\|\s+" + ip1 + "\s+\|", raw_output)) + self.assertIsNotNone(re.search("\|\s+" + ip2 + "\s+\|", raw_output)) + + # Test list --long + raw_output = self.openstack('floating ip list --long') + self.assertIsNotNone(re.search("\|\s+" + ip1 + "\s+\|", raw_output)) + self.assertIsNotNone(re.search("\|\s+" + ip2 + "\s+\|", raw_output)) + + # TODO(dtroyer): add more filter tests def test_floating_ip_show(self): - opts = self.get_opts(self.FIELDS) - raw_output = self.openstack('floating ip show ' + self.ID + opts) - self.assertEqual(self.ID + "\n", raw_output) + """Test show""" + raw_output = self.openstack( + 'floating ip create ' + + '--description shosho ' + + # '--fixed-ip-address 1.2.3.4 ' + + self.NETWORK_NAME + ) + re_ip = re.search(self.re_floating_ip, raw_output) + self.assertIsNotNone(re_ip) + ip = re_ip.group(1) + + raw_output = self.openstack('floating ip show ' + ip) + self.addCleanup(self.openstack, 'floating ip delete ' + ip) + + self.assertEqual( + 'shosho', + re.search(self.re_description, raw_output).group(1), + ) + # TODO(dtroyer): not working??? + # self.assertEqual( + # '1.2.3.4', + # re.search(self.re_floating_ip, raw_output).group(1), + # ) + self.assertIsNotNone(re.search(self.re_network_id, raw_output)) From 0fb1378c6ca7a5ea717ee651d64603b0246f6737 Mon Sep 17 00:00:00 2001 From: Ankur Gupta Date: Fri, 23 Sep 2016 14:59:51 -0500 Subject: [PATCH 1449/3095] OSC Network Meter Implement Neutron feature of Metering into OpenStack Client. Meter Rules will be implemented in seperate patchset. Partially Implements: blueprint neutron-client-metering Change-Id: Ie82d42759504cbdb1c991c5183c1f0adf59e60fe --- doc/source/command-objects/meter.rst | 91 ++++++ doc/source/commands.rst | 1 + openstackclient/network/v2/meter.py | 190 +++++++++++ .../tests/functional/network/v2/test_meter.py | 102 ++++++ .../tests/unit/network/v2/fakes.py | 45 +++ .../tests/unit/network/v2/test_meter.py | 304 ++++++++++++++++++ ...tron-client-metering-1ee703a48343ece1.yaml | 7 + setup.cfg | 5 + 8 files changed, 745 insertions(+) create mode 100644 doc/source/command-objects/meter.rst create mode 100644 openstackclient/network/v2/meter.py create mode 100644 openstackclient/tests/functional/network/v2/test_meter.py create mode 100644 openstackclient/tests/unit/network/v2/test_meter.py create mode 100644 releasenotes/notes/bp-neutron-client-metering-1ee703a48343ece1.yaml diff --git a/doc/source/command-objects/meter.rst b/doc/source/command-objects/meter.rst new file mode 100644 index 0000000000..6077ce9226 --- /dev/null +++ b/doc/source/command-objects/meter.rst @@ -0,0 +1,91 @@ +============= +network meter +============= + +A **network meter** allows operators to measure +traffic for a specific IP range. The following commands +are specific to the L3 metering extension. + +Network v2 + +network meter create +-------------------- + +Create network meter + +.. program:: network meter create +.. code:: bash + + openstack network meter create + [--project [--project-domain ]] + [--description ] + [--share | --no-share] + + +.. option:: --project + + Owner's project (name of ID) + + *Network version 2 only* + +.. option:: --description + + Description of meter + + *Network version 2 only* + +.. option:: --share + + Share the meter between projects + +.. option:: --no-share + + Do not share the meter between projects (Default) + +.. _network_meter_create: +.. describe:: + + New meter name + +network meter delete +-------------------- + +Delete network meter(s) + +.. program:: network meter delete +.. code:: bash + + openstack network meter delete + [ ...] + +.. _network_meter_delete: +.. describe:: + + Meter(s) to delete (name or ID) + +network meter list +------------------ + +List network meters + +.. program:: network meter list +.. code:: bash + + openstack network meter list + + +network meter show +------------------ + +Show network meter + +.. program:: network meter show +.. code:: bash + + openstack network meter show + + +.. _network_meter_show: +.. describe:: + + Meter to display (name or ID) diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 52e05d7e09..b81c121bef 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -111,6 +111,7 @@ referring to both Compute and Volume quotas. * ``module``: (**Internal**) - installed Python modules in the OSC process * ``network``: (**Compute**, **Network**) - a virtual network for connecting servers and other resources * ``network agent``: (**Network**) - A network agent is an agent that handles various tasks used to implement virtual networks +* ``network meter``: (**Network**) - allow traffic metering in a network * ``network rbac``: (**Network**) - an RBAC policy for network resources * ``network qos policy``: (**Network**) - a QoS policy for network resources * ``network qos rule type``: (**Network**) - list of QoS available rule types diff --git a/openstackclient/network/v2/meter.py b/openstackclient/network/v2/meter.py new file mode 100644 index 0000000000..df0e1da119 --- /dev/null +++ b/openstackclient/network/v2/meter.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. +# + +"""Metering Label Implementations""" + +import logging + +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils + +from openstackclient.i18n import _ +from openstackclient.identity import common as identity_common +from openstackclient.network import sdk_utils + +LOG = logging.getLogger(__name__) + + +def _get_columns(item): + column_map = { + 'is_shared': 'shared', + 'tenant_id': 'project_id', + } + return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + + +def _get_attrs(client_manager, parsed_args): + attrs = {} + + if parsed_args.description is not None: + attrs['description'] = parsed_args.description + if parsed_args.project is not None and 'project' in parsed_args: + identity_client = client_manager.identity + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + attrs['tenant_id'] = project_id + if parsed_args.share: + attrs['shared'] = True + if parsed_args.no_share: + attrs['shared'] = False + if parsed_args.name is not None: + attrs['name'] = parsed_args.name + + return attrs + + +# TODO(ankur-gupta-f): Use the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. +class CreateMeter(command.ShowOne): + _description = _("Create network meter") + + def get_parser(self, prog_name): + parser = super(CreateMeter, self).get_parser(prog_name) + + parser.add_argument( + '--description', + metavar='', + help=_("Create description for meter") + ) + parser.add_argument( + '--project', + metavar='', + help=_("Owner's project (name or ID)") + ) + + identity_common.add_project_domain_option_to_parser(parser) + share_group = parser.add_mutually_exclusive_group() + share_group.add_argument( + '--share', + action='store_true', + default=None, + help=_("Share meter between projects") + ) + share_group.add_argument( + '--no-share', + action='store_true', + help=_("Do not share meter between projects") + ) + parser.add_argument( + 'name', + metavar='', + help=_('Name of meter'), + ) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + attrs = _get_attrs(self.app.client_manager, parsed_args) + obj = client.create_metering_label(**attrs) + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns, formatters={}) + + return (display_columns, data) + + +# TODO(ankur-gupta-f): Use the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. +class DeleteMeter(command.Command): + _description = _("Delete network meter") + + def get_parser(self, prog_name): + parser = super(DeleteMeter, self).get_parser(prog_name) + + parser.add_argument( + 'meter', + metavar='', + nargs='+', + help=_('Meter to delete (name or ID)') + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + result = 0 + + for meter in parsed_args.meter: + try: + obj = client.find_metering_label(meter, ignore_missing=False) + client.delete_metering_label(obj) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete meter with " + "ID '%(meter)s': %(e)s"), + {"meter": meter, "e": e}) + if result > 0: + total = len(parsed_args.meter) + msg = (_("%(result)s of %(total)s meters failed " + "to delete.") % {"result": result, "total": total}) + raise exceptions.CommandError(msg) + + +class ListMeter(command.Lister): + _description = _("List network meters") + + def take_action(self, parsed_args): + client = self.app.client_manager.network + + columns = ( + 'id', + 'name', + 'description', + 'shared', + ) + column_headers = ( + 'ID', + 'Name', + 'Description', + 'Shared', + ) + + data = client.metering_labels() + return (column_headers, + (utils.get_item_properties( + s, columns, + ) for s in data)) + + +class ShowMeter(command.ShowOne): + _description = _("Show network meter") + + def get_parser(self, prog_name): + parser = super(ShowMeter, self).get_parser(prog_name) + parser.add_argument( + 'meter', + metavar='', + help=_('Meter to display (name or ID)') + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.find_metering_label(parsed_args.meter, + ignore_missing=False) + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns) + return display_columns, data diff --git a/openstackclient/tests/functional/network/v2/test_meter.py b/openstackclient/tests/functional/network/v2/test_meter.py new file mode 100644 index 0000000000..7dce34e7e3 --- /dev/null +++ b/openstackclient/tests/functional/network/v2/test_meter.py @@ -0,0 +1,102 @@ +# 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 re +import uuid + +from openstackclient.tests.functional import base + + +class TestMeter(base.TestCase): + """Functional tests for network meter.""" + + # NOTE(dtroyer): Do not normalize the setup and teardown of the resource + # creation and deletion. Little is gained when each test + # has its own needs and there are collisions when running + # tests in parallel. + + @classmethod + def setUpClass(cls): + # Set up some regex for matching below + cls.re_name = re.compile("name\s+\|\s+([^|]+?)\s+\|") + cls.re_shared = re.compile("shared\s+\|\s+(\S+)") + cls.re_description = re.compile("description\s+\|\s+([^|]+?)\s+\|") + + def test_meter_delete(self): + """Test create, delete multiple""" + name1 = uuid.uuid4().hex + name2 = uuid.uuid4().hex + + raw_output = self.openstack( + 'network meter create ' + name1, + ) + self.assertEqual( + name1, + re.search(self.re_name, raw_output).group(1), + ) + # Check if default shared values + self.assertEqual( + 'False', + re.search(self.re_shared, raw_output).group(1) + ) + + raw_output = self.openstack( + 'network meter create ' + name2, + ) + self.assertEqual( + name2, + re.search(self.re_name, raw_output).group(1), + ) + + raw_output = self.openstack( + 'network meter delete ' + name1 + ' ' + name2, + ) + self.assertOutput('', raw_output) + + def test_meter_list(self): + """Test create, list filters, delete""" + name1 = uuid.uuid4().hex + raw_output = self.openstack( + 'network meter create --description Test1 --share ' + name1, + ) + self.addCleanup(self.openstack, 'network meter delete ' + name1) + + self.assertEqual( + 'Test1', + re.search(self.re_description, raw_output).group(1), + ) + self.assertEqual( + 'True', + re.search(self.re_shared, raw_output).group(1), + ) + + name2 = uuid.uuid4().hex + raw_output = self.openstack( + 'network meter create --description Test2 --no-share ' + name2, + ) + self.addCleanup(self.openstack, 'network meter delete ' + name2) + + self.assertEqual( + 'Test2', + re.search(self.re_description, raw_output).group(1), + ) + self.assertEqual( + 'False', + re.search(self.re_shared, raw_output).group(1), + ) + + raw_output = self.openstack('network meter list') + self.assertIsNotNone(re.search(name1 + "\s+\|\s+Test1", raw_output)) + self.assertIsNotNone(re.search(name2 + "\s+\|\s+Test2", raw_output)) diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index 88e67f43ff..b931cb557d 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -1258,6 +1258,51 @@ def get_floating_ips(floating_ips=None, count=2): return mock.Mock(side_effect=floating_ips) +class FakeNetworkMeter(object): + """Fake network meter""" + + @staticmethod + def create_one_meter(attrs=None): + """Create metering pool""" + attrs = attrs or {} + + meter_attrs = { + 'id': 'meter-id-' + uuid.uuid4().hex, + 'name': 'meter-name-' + uuid.uuid4().hex, + 'description': 'meter-description-' + uuid.uuid4().hex, + 'tenant_id': 'project-id-' + uuid.uuid4().hex, + 'shared': False + } + + meter_attrs.update(attrs) + + meter = fakes.FakeResource( + info=copy.deepcopy(meter_attrs), + loaded=True) + + meter.project_id = meter_attrs['tenant_id'] + + return meter + + @staticmethod + def create_meter(attrs=None, count=2): + """Create multiple meters""" + + meters = [] + for i in range(0, count): + meters.append(FakeNetworkMeter. + create_one_meter(attrs)) + return meters + + @staticmethod + def get_meter(meter=None, count=2): + """Get a list of meters""" + if meter is None: + meter = (FakeNetworkMeter. + create_meter(count)) + return mock.Mock(side_effect=meter) + + class FakeSubnetPool(object): """Fake one or more subnet pools.""" diff --git a/openstackclient/tests/unit/network/v2/test_meter.py b/openstackclient/tests/unit/network/v2/test_meter.py new file mode 100644 index 0000000000..b393f7fa65 --- /dev/null +++ b/openstackclient/tests/unit/network/v2/test_meter.py @@ -0,0 +1,304 @@ +# 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 mock import call + +from osc_lib import exceptions + +from openstackclient.network.v2 import meter +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3 +from openstackclient.tests.unit.network.v2 import fakes as network_fakes +from openstackclient.tests.unit import utils as tests_utils + + +class TestMeter(network_fakes.TestNetworkV2): + + def setUp(self): + super(TestMeter, self).setUp() + self.network = self.app.client_manager.network + self.projects_mock = self.app.client_manager.identity.projects + self.domains_mock = self.app.client_manager.identity.domains + + +class TestCreateMeter(TestMeter): + project = identity_fakes_v3.FakeProject.create_one_project() + domain = identity_fakes_v3.FakeDomain.create_one_domain() + + new_meter = ( + network_fakes.FakeNetworkMeter. + create_one_meter() + ) + columns = ( + 'description', + 'id', + 'name', + 'project_id', + 'shared', + ) + + data = ( + new_meter.description, + new_meter.id, + new_meter.name, + new_meter.project_id, + new_meter.shared, + ) + + def setUp(self): + super(TestCreateMeter, self).setUp() + self.network.create_metering_label = mock.Mock( + return_value=self.new_meter) + self.projects_mock.get.return_value = self.project + self.cmd = meter.CreateMeter(self.app, self.namespace) + + def test_create_no_options(self): + arglist = [] + verifylist = [] + + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_create_default_options(self): + arglist = [ + self.new_meter.name, + ] + + verifylist = [ + ('name', self.new_meter.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_metering_label.assert_called_once_with( + **{'name': self.new_meter.name} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_all_options(self): + arglist = [ + "--description", self.new_meter.description, + "--project", self.new_meter.project_id, + "--project-domain", self.domain.name, + "--share", + self.new_meter.name, + ] + + verifylist = [ + ('description', self.new_meter.description), + ('name', self.new_meter.name), + ('project', self.new_meter.project_id), + ('project_domain', self.domain.name), + ('share', True), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_metering_label.assert_called_once_with( + **{'description': self.new_meter.description, + 'name': self.new_meter.name, + 'tenant_id': self.project.id, + 'shared': True, } + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestDeleteMeter(TestMeter): + + def setUp(self): + super(TestDeleteMeter, self).setUp() + + self.meter_list = \ + network_fakes.FakeNetworkMeter.create_meter(count=2) + + self.network.delete_metering_label = mock.Mock(return_value=None) + + self.network.find_metering_label = network_fakes \ + .FakeNetworkMeter.get_meter( + meter=self.meter_list + ) + + self.cmd = meter.DeleteMeter(self.app, self.namespace) + + def test_delete_one_meter(self): + arglist = [ + self.meter_list[0].name, + ] + verifylist = [ + ('meter', [self.meter_list[0].name]), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.network.delete_metering_label.assert_called_once_with( + self.meter_list[0] + ) + self.assertIsNone(result) + + def test_delete_multiple_meters(self): + arglist = [] + for n in self.meter_list: + arglist.append(n.id) + verifylist = [ + ('meter', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [] + for n in self.meter_list: + calls.append(call(n)) + self.network.delete_metering_label.assert_has_calls(calls) + self.assertIsNone(result) + + def test_delete_multiple_meter_exception(self): + arglist = [ + self.meter_list[0].id, + 'xxxx-yyyy-zzzz', + self.meter_list[1].id, + ] + verifylist = [ + ('meter', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + return_find = [ + self.meter_list[0], + exceptions.NotFound('404'), + self.meter_list[1], + ] + self.network.find_meter = mock.Mock(side_effect=return_find) + + ret_delete = [ + None, + exceptions.NotFound('404'), + ] + self.network.delete_metering_label = mock.Mock(side_effect=ret_delete) + + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + + calls = [ + call(self.meter_list[0]), + call(self.meter_list[1]), + ] + self.network.delete_metering_label.assert_has_calls(calls) + + +class TestListMeter(TestMeter): + + meter_list = \ + network_fakes.FakeNetworkMeter.create_meter(count=2) + + columns = ( + 'ID', + 'Name', + 'Description', + 'Shared', + ) + + data = [] + + for meters in meter_list: + data.append(( + meters.id, + meters.name, + meters.description, + meters.shared, + )) + + def setUp(self): + super(TestListMeter, self).setUp() + + self.network.metering_labels = mock.Mock( + return_value=self.meter_list + ) + + self.cmd = meter.ListMeter(self.app, self.namespace) + + def test_meter_list(self): + arglist = [] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.metering_labels.assert_called_with() + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + +class TestShowMeter(TestMeter): + new_meter = ( + network_fakes.FakeNetworkMeter. + create_one_meter() + ) + columns = ( + 'description', + 'id', + 'name', + 'project_id', + 'shared', + ) + + data = ( + new_meter.description, + new_meter.id, + new_meter.name, + new_meter.project_id, + new_meter.shared, + ) + + def setUp(self): + super(TestShowMeter, self).setUp() + + self.cmd = meter.ShowMeter(self.app, self.namespace) + + self.network.find_metering_label = \ + mock.Mock(return_value=self.new_meter) + + def test_show_no_options(self): + arglist = [] + verifylist = [] + + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_meter_show_option(self): + arglist = [ + self.new_meter.name, + ] + verifylist = [ + ('meter', self.new_meter.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.find_metering_label.assert_called_with( + self.new_meter.name, ignore_missing=False + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) diff --git a/releasenotes/notes/bp-neutron-client-metering-1ee703a48343ece1.yaml b/releasenotes/notes/bp-neutron-client-metering-1ee703a48343ece1.yaml new file mode 100644 index 0000000000..d5b047cb82 --- /dev/null +++ b/releasenotes/notes/bp-neutron-client-metering-1ee703a48343ece1.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Add support for network metering commands: + ``network meter create``, ``network meter delete``, + ``network meter show``, ``network meter list`` + [Blueprint :oscbp:`neutron-client-metering`] diff --git a/setup.cfg b/setup.cfg index 7e1bfc1735..c88a4cb57b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -355,6 +355,11 @@ openstack.network.v2 = ip_floating_pool_list = openstackclient.network.v2.floating_ip_pool:ListIPFloatingPool + network_meter_create = openstackclient.network.v2.meter:CreateMeter + network_meter_delete = openstackclient.network.v2.meter:DeleteMeter + network_meter_list = openstackclient.network.v2.meter:ListMeter + network_meter_show = openstackclient.network.v2.meter:ShowMeter + network_agent_delete = openstackclient.network.v2.network_agent:DeleteNetworkAgent network_agent_list = openstackclient.network.v2.network_agent:ListNetworkAgent network_agent_set = openstackclient.network.v2.network_agent:SetNetworkAgent From 75f2875a6e2be5c7fef56ebc1f3c235aa5ae1521 Mon Sep 17 00:00:00 2001 From: "zhiyong.dai" Date: Mon, 19 Dec 2016 17:23:35 +0800 Subject: [PATCH 1450/3095] Add ":option:` `" in the help message This patch mainly adds ":option:` `" in several help messages in the doc. This modification will create one link which helps the readers find the relative option quickly and conveniently in website. Change-Id: Ia047e15c3b2064e4822ee7df7922d4774d862602 --- doc/source/command-objects/aggregate.rst | 7 ++++- .../command-objects/compute-service.rst | 9 ++---- doc/source/command-objects/image.rst | 28 +++++++++++++------ doc/source/command-objects/limits.rst | 4 +-- doc/source/command-objects/port.rst | 4 +-- doc/source/command-objects/service.rst | 2 +- doc/source/command-objects/subnet.rst | 10 +++---- doc/source/command-objects/volume-service.rst | 6 ++-- 8 files changed, 41 insertions(+), 29 deletions(-) diff --git a/doc/source/command-objects/aggregate.rst b/doc/source/command-objects/aggregate.rst index 0d2926db7d..2029a6c8cf 100644 --- a/doc/source/command-objects/aggregate.rst +++ b/doc/source/command-objects/aggregate.rst @@ -24,6 +24,7 @@ Add host to aggregate Aggregate (name or ID) +.. _aggregate_add_host-host: .. describe:: Host to add to :ref:`\ ` @@ -49,6 +50,7 @@ Create a new aggregate Property to add to this aggregate (repeat option to set multiple properties) +.. _aggregate_create-name: .. describe:: New aggregate name @@ -64,6 +66,7 @@ Delete existing aggregate(s) openstack aggregate delete [ ...] +.. _aggregate_delete-aggregate: .. describe:: Aggregate(s) to delete (name or ID) @@ -100,6 +103,7 @@ Remove host from aggregate Aggregate (name or ID) +.. _aggregate_remove_host-host: .. describe:: Host to remove from :ref:`\ ` @@ -135,7 +139,7 @@ Set aggregate properties .. option:: --no-property Remove all properties from :ref:`\ ` - (specify both --property and --no-property to + (specify both :option:`--property` and :option:`--no-property` to overwrite the current properties) .. _aggregate_set-aggregate: @@ -154,6 +158,7 @@ Display aggregate details openstack aggregate show +.. _aggregate_show-aggregate: .. describe:: Aggregate to display (name or ID) diff --git a/doc/source/command-objects/compute-service.rst b/doc/source/command-objects/compute-service.rst index 66347214e4..ba624ea0a4 100644 --- a/doc/source/command-objects/compute-service.rst +++ b/doc/source/command-objects/compute-service.rst @@ -15,7 +15,7 @@ Delete compute service(s) openstack compute service delete [ ...] -.. _compute-service-delete: +.. _compute_service_delete-service: .. describe:: Compute service(s) to delete (ID only) @@ -33,7 +33,6 @@ List compute services [--service ] [--long] -.. _compute-service-list: .. option:: --host List services on specified host (name only) @@ -46,7 +45,6 @@ List compute services List additional fields in output - compute service set ------------------- @@ -61,7 +59,6 @@ Set compute service properties [--up | --down] -.. _compute-service-set: .. option:: --enable Enable service @@ -72,7 +69,7 @@ Set compute service properties .. option:: --disable-reason - Reason for disabling the service (in quotes). Should be used with --disable option. + Reason for disabling the service (in quotes). Should be used with :option:`--disable` option. .. option:: --up @@ -82,6 +79,7 @@ Set compute service properties Force down service +.. _compute_service_set-host: .. describe:: Name of host @@ -89,4 +87,3 @@ Set compute service properties .. describe:: Name of service (Binary name) - diff --git a/doc/source/command-objects/image.rst b/doc/source/command-objects/image.rst index 1913355ebf..7ebcb54ad2 100644 --- a/doc/source/command-objects/image.rst +++ b/doc/source/command-objects/image.rst @@ -23,10 +23,12 @@ Associate project with image Domain the project belongs to (name or ID). This can be used in case collisions between project names exist. +.. _image_add_project-image: .. describe:: Image to share (name or ID). +.. _image_add_project-project: .. describe:: Project to associate with image (name or ID) @@ -83,7 +85,7 @@ Create/upload an image .. option:: --size - Image size, in bytes (only used with --location and --copy-from) + Image size, in bytes (only used with :option:`--location` and :option:`--copy-from`) *Image version 1 only.* @@ -91,7 +93,7 @@ Create/upload an image Minimum disk size needed to boot image, in gigabytes -.. option:: --min-ram +.. option:: --min-ram Minimum RAM size needed to boot image, in megabytes @@ -103,7 +105,7 @@ Create/upload an image .. option:: --copy-from - Copy image from the data store (similar to --location) + Copy image from the data store (similar to :option:`--location`) *Image version 1 only.* @@ -117,7 +119,7 @@ Create/upload an image .. option:: --force - Force image creation if volume is in use (only meaningful with --volume) + Force image creation if volume is in use (only meaningful with :option:`--volume`) .. option:: --checksum @@ -163,6 +165,7 @@ Create/upload an image .. versionadded:: 2 +.. _image_create-image-name: .. describe:: New image name @@ -178,6 +181,7 @@ Delete image(s) openstack image delete +.. _image_delete-image: .. describe:: Image(s) to delete (name or ID) @@ -246,13 +250,15 @@ Disassociate project with image openstack image remove remove [--project-domain ] - + + .. option:: --project-domain Domain the project belongs to (name or ID). This can be used in case collisions between project names exist. +.. _image_remove_project: .. describe:: Image to unshare (name or ID). @@ -277,6 +283,7 @@ Save an image locally Downloaded image save filename (default: stdout) +.. _image_save-image: .. describe:: Image to save (name or ID) @@ -294,7 +301,7 @@ Set image properties openstack image set [--name ] [--min-disk ] - [--min-ram ] + [--min-ram ] [--container-format ] [--disk-format ] [--size ] @@ -328,7 +335,7 @@ Set image properties Minimum disk size needed to boot image, in gigabytes -.. option:: --min-ram +.. option:: --min-ram Minimum RAM size needed to boot image, in megabytes @@ -377,7 +384,7 @@ Set image properties .. option:: --copy-from - Copy image from the data store (similar to --location) + Copy image from the data store (similar to :option:`--location`) *Image version 1 only.* @@ -395,7 +402,7 @@ Set image properties .. option:: --force - Force image update if volume is in use (only meaningful with --volume) + Force image update if volume is in use (only meaningful with :option:`--volume`) *Image version 1 only.* @@ -483,6 +490,7 @@ Set image properties .. versionadded:: 2 +.. _image_set-image: .. describe:: Image to modify (name or ID) @@ -498,6 +506,7 @@ Display image details openstack image show +.. _image_show-image: .. describe:: Image to display (name or ID) @@ -525,6 +534,7 @@ Unset image tags or properties Unset a property on this image (repeat option to unset multiple properties) +.. _image_unset-image: .. describe:: Image to modify (name or ID) diff --git a/doc/source/command-objects/limits.rst b/doc/source/command-objects/limits.rst index 9a302fbfd9..9261420951 100644 --- a/doc/source/command-objects/limits.rst +++ b/doc/source/command-objects/limits.rst @@ -34,8 +34,8 @@ Show compute and block storage limits .. option:: --project - Show limits for a specific project (name or ID) [only valid with --absolute] + Show limits for a specific project (name or ID) [only valid with :option:`--absolute`] .. option:: --domain - Domain the project belongs to (name or ID) [only valid with --absolute] + Domain the project belongs to (name or ID) [only valid with :option:`--absolute`] diff --git a/doc/source/command-objects/port.rst b/doc/source/command-objects/port.rst index 3f6e3fc02c..34656f32b4 100644 --- a/doc/source/command-objects/port.rst +++ b/doc/source/command-objects/port.rst @@ -214,7 +214,7 @@ Set port properties .. option:: --no-fixed-ip Clear existing information of fixed IP addresses. - Specify both --fixed-ip and --no-fixed-ip + Specify both :option:`--fixed-ip` and :option:`--no-fixed-ip` to overwrite the current fixed IP addresses. .. option:: --device @@ -240,7 +240,7 @@ Set port properties .. option:: --no-binding-profile Clear existing information of binding:profile. - Specify both --binding-profile and --no-binding-profile + Specify both :option:`--binding-profile` and :option:`--no-binding-profile` to overwrite the current binding:profile information. .. option:: --host diff --git a/doc/source/command-objects/service.rst b/doc/source/command-objects/service.rst index 250d19ec3d..a69c69504b 100644 --- a/doc/source/command-objects/service.rst +++ b/doc/source/command-objects/service.rst @@ -54,7 +54,7 @@ Delete service(s) openstack service delete [ ...] -.. _service_delete-type: +.. _service_delete-service: .. describe:: Service(s) to delete (type, name or ID) diff --git a/doc/source/command-objects/subnet.rst b/doc/source/command-objects/subnet.rst index fb28793e76..5eb55c23c8 100644 --- a/doc/source/command-objects/subnet.rst +++ b/doc/source/command-objects/subnet.rst @@ -49,7 +49,7 @@ Create new subnet .. option:: --use-default-subnet-pool - Use default subnet pool for ``--ip-version`` + Use default subnet pool for :option:`--ip-version` .. option:: --prefix-length @@ -58,7 +58,7 @@ Create new subnet .. option:: --subnet-range Subnet range in CIDR notation - (required if ``--subnet-pool`` is not specified, optional otherwise) + (required if :option:`--subnet-pool` is not specified, optional otherwise) .. option:: --allocation-pool start=,end= @@ -249,7 +249,7 @@ Set subnet properties .. option:: --no-allocation-pool Clear associated allocation pools from this subnet. - Specify both --allocation-pool and --no-allocation-pool + Specify both :option:`--allocation-pool` and :option:`--no-allocation-pool` to overwrite the current allocation pool information. .. option:: --dhcp @@ -267,7 +267,7 @@ Set subnet properties .. option:: --no-dns-nameservers Clear existing information of DNS servers. - Specify both --dns-nameserver and --no-dns-nameservers + Specify both :option:`--dns-nameserver` and :option:`--no-dns-nameservers` to overwrite the current DNS server information. .. option:: --gateway @@ -287,7 +287,7 @@ Set subnet properties .. option:: --no-host-route Clear associated host routes from this subnet. - Specify both --host-route and --no-host-route + Specify both :option:`--host-route` and :option:`--no-host-route` to overwrite the current host route information. .. option:: --service-type diff --git a/doc/source/command-objects/volume-service.rst b/doc/source/command-objects/volume-service.rst index 842d9cb23a..2ad23240d3 100644 --- a/doc/source/command-objects/volume-service.rst +++ b/doc/source/command-objects/volume-service.rst @@ -17,7 +17,6 @@ List volume service [--service ] [--long] -.. _volume-service-list: .. option:: --host List services on specified host (name only) @@ -54,9 +53,10 @@ Set volume service properties .. option:: --disable-reason - Reason for disabling the service (should be used with --disable option) + Reason for disabling the service + (should be used with :option:`--disable` option) -.. _volume-service-set: +.. _volume_service_set-host: .. describe:: Name of host From be9e60be4b34ed65206cc0fc69e1b43e3217c2f7 Mon Sep 17 00:00:00 2001 From: "zhiyong.dai" Date: Tue, 3 Jan 2017 18:45:49 +0800 Subject: [PATCH 1451/3095] Update the description format In network_qos_rule_type.py, network_service_provider.py, server.py and volume_snapshot.py, the description format is using """ """. In this patch, the former format is replaced with "_description = _( )". Change-Id: I6863b01d3534e033df745070037ee45286745c92 --- openstackclient/network/v2/network_qos_rule_type.py | 4 +++- .../network/v2/network_service_provider.py | 4 +++- openstackclient/volume/v1/volume_snapshot.py | 12 ++++++------ 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/openstackclient/network/v2/network_qos_rule_type.py b/openstackclient/network/v2/network_qos_rule_type.py index 82a265b8c9..657eb54b95 100644 --- a/openstackclient/network/v2/network_qos_rule_type.py +++ b/openstackclient/network/v2/network_qos_rule_type.py @@ -18,12 +18,14 @@ from osc_lib.command import command from osc_lib import utils +from openstackclient.i18n import _ + LOG = logging.getLogger(__name__) class ListNetworkQosRuleType(command.Lister): - """List QoS rule types""" + _description = _("List QoS rule types") def take_action(self, parsed_args): client = self.app.client_manager.network diff --git a/openstackclient/network/v2/network_service_provider.py b/openstackclient/network/v2/network_service_provider.py index 3aa33c23bd..157948cc4b 100644 --- a/openstackclient/network/v2/network_service_provider.py +++ b/openstackclient/network/v2/network_service_provider.py @@ -16,9 +16,11 @@ from osc_lib.command import command from osc_lib import utils +from openstackclient.i18n import _ + class ListNetworkServiceProvider(command.Lister): - """List Service Providers""" + _description = _("List Service Providers") def take_action(self, parsed_args): client = self.app.client_manager.network diff --git a/openstackclient/volume/v1/volume_snapshot.py b/openstackclient/volume/v1/volume_snapshot.py index 77c93ad4fb..45bd30c044 100644 --- a/openstackclient/volume/v1/volume_snapshot.py +++ b/openstackclient/volume/v1/volume_snapshot.py @@ -31,7 +31,7 @@ class CreateVolumeSnapshot(command.ShowOne): - """Create new volume snapshot""" + _description = _("Create new volume snapshot") def get_parser(self, prog_name): parser = super(CreateVolumeSnapshot, self).get_parser(prog_name) @@ -84,7 +84,7 @@ def take_action(self, parsed_args): class DeleteVolumeSnapshot(command.Command): - """Delete volume snapshot(s)""" + _description = _("Delete volume snapshot(s)") def get_parser(self, prog_name): parser = super(DeleteVolumeSnapshot, self).get_parser(prog_name) @@ -119,7 +119,7 @@ def take_action(self, parsed_args): class ListVolumeSnapshot(command.Lister): - """List volume snapshots""" + _description = _("List volume snapshots") def get_parser(self, prog_name): parser = super(ListVolumeSnapshot, self).get_parser(prog_name) @@ -220,7 +220,7 @@ def _format_volume_id(volume_id): class SetVolumeSnapshot(command.Command): - """Set volume snapshot properties""" + _description = _("Set volume snapshot properties") def get_parser(self, prog_name): parser = super(SetVolumeSnapshot, self).get_parser(prog_name) @@ -281,7 +281,7 @@ def take_action(self, parsed_args): class ShowVolumeSnapshot(command.ShowOne): - """Display volume snapshot details""" + _description = _("Display volume snapshot details") def get_parser(self, prog_name): parser = super(ShowVolumeSnapshot, self).get_parser(prog_name) @@ -305,7 +305,7 @@ def take_action(self, parsed_args): class UnsetVolumeSnapshot(command.Command): - """Unset volume snapshot properties""" + _description = _("Unset volume snapshot properties") def get_parser(self, prog_name): parser = super(UnsetVolumeSnapshot, self).get_parser(prog_name) From 96578cb8ab9a4b95144c33d0af38863fce8d8892 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Fri, 30 Dec 2016 13:22:07 +0800 Subject: [PATCH 1452/3095] Error handling for delete commands in identity Add missing multi deletion error handling for identity delete commands. All delete commands in identity support error handling now. Change-Id: I05626dcb5e516a423d610906347b02236ba7eeaf --- openstackclient/identity/v2_0/project.py | 24 ++++++++++--- openstackclient/identity/v2_0/role.py | 23 ++++++++++--- openstackclient/identity/v2_0/user.py | 24 ++++++++++--- openstackclient/identity/v3/group.py | 22 +++++++++--- openstackclient/identity/v3/project.py | 30 +++++++++++----- openstackclient/identity/v3/role.py | 27 +++++++++++---- openstackclient/identity/v3/trust.py | 26 ++++++++++++-- openstackclient/identity/v3/user.py | 30 +++++++++++----- .../tests/unit/identity/v2_0/test_project.py | 29 ++++++++++++++++ .../tests/unit/identity/v2_0/test_role.py | 27 +++++++++++++++ .../tests/unit/identity/v2_0/test_user.py | 27 +++++++++++++++ .../tests/unit/identity/v3/test_group.py | 27 +++++++++++++++ .../tests/unit/identity/v3/test_project.py | 27 +++++++++++++++ .../tests/unit/identity/v3/test_role.py | 34 +++++++++++++++++++ .../tests/unit/identity/v3/test_trust.py | 31 +++++++++++++++++ .../tests/unit/identity/v3/test_user.py | 29 ++++++++++++++++ 16 files changed, 392 insertions(+), 45 deletions(-) diff --git a/openstackclient/identity/v2_0/project.py b/openstackclient/identity/v2_0/project.py index 8526d6bdf6..ca565d4dd6 100644 --- a/openstackclient/identity/v2_0/project.py +++ b/openstackclient/identity/v2_0/project.py @@ -20,6 +20,7 @@ from keystoneauth1 import exceptions as ks_exc from osc_lib.cli import parseractions from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils import six @@ -117,12 +118,25 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity + errors = 0 for project in parsed_args.projects: - project_obj = utils.find_resource( - identity_client.tenants, - project, - ) - identity_client.tenants.delete(project_obj.id) + try: + project_obj = utils.find_resource( + identity_client.tenants, + project, + ) + identity_client.tenants.delete(project_obj.id) + except Exception as e: + errors += 1 + LOG.error(_("Failed to delete project with " + "name or ID '%(project)s': %(e)s"), + {'project': project, 'e': e}) + + if errors > 0: + total = len(parsed_args.projects) + msg = (_("%(errors)s of %(total)s projects failed " + "to delete.") % {'errors': errors, 'total': total}) + raise exceptions.CommandError(msg) class ListProject(command.Lister): diff --git a/openstackclient/identity/v2_0/role.py b/openstackclient/identity/v2_0/role.py index 0a28a70a8f..e254e05fd8 100644 --- a/openstackclient/identity/v2_0/role.py +++ b/openstackclient/identity/v2_0/role.py @@ -124,12 +124,25 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity + errors = 0 for role in parsed_args.roles: - role_obj = utils.find_resource( - identity_client.roles, - role, - ) - identity_client.roles.delete(role_obj.id) + try: + role_obj = utils.find_resource( + identity_client.roles, + role, + ) + identity_client.roles.delete(role_obj.id) + except Exception as e: + errors += 1 + LOG.error(_("Failed to delete role with " + "name or ID '%(role)s': %(e)s"), + {'role': role, 'e': e}) + + if errors > 0: + total = len(parsed_args.roles) + msg = (_("%(errors)s of %(total)s roles failed " + "to delete.") % {'errors': errors, 'total': total}) + raise exceptions.CommandError(msg) class ListRole(command.Lister): diff --git a/openstackclient/identity/v2_0/user.py b/openstackclient/identity/v2_0/user.py index ddd5b981e0..2a3dde6b52 100644 --- a/openstackclient/identity/v2_0/user.py +++ b/openstackclient/identity/v2_0/user.py @@ -19,6 +19,7 @@ from keystoneauth1 import exceptions as ks_exc from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils import six @@ -145,12 +146,25 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity + errors = 0 for user in parsed_args.users: - user_obj = utils.find_resource( - identity_client.users, - user, - ) - identity_client.users.delete(user_obj.id) + try: + user_obj = utils.find_resource( + identity_client.users, + user, + ) + identity_client.users.delete(user_obj.id) + except Exception as e: + errors += 1 + LOG.error(_("Failed to delete user with " + "name or ID '%(user)s': %(e)s"), + {'user': user, 'e': e}) + + if errors > 0: + total = len(parsed_args.users) + msg = (_("%(errors)s of %(total)s users failed " + "to delete.") % {'errors': errors, 'total': total}) + raise exceptions.CommandError(msg) class ListUser(command.Lister): diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py index df684c129b..a03a86ebd2 100644 --- a/openstackclient/identity/v3/group.py +++ b/openstackclient/identity/v3/group.py @@ -20,6 +20,7 @@ from keystoneauth1 import exceptions as ks_exc from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils import six @@ -194,11 +195,24 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity + errors = 0 for group in parsed_args.groups: - group_obj = common.find_group(identity_client, - group, - parsed_args.domain) - identity_client.groups.delete(group_obj.id) + try: + group_obj = common.find_group(identity_client, + group, + parsed_args.domain) + identity_client.groups.delete(group_obj.id) + except Exception as e: + errors += 1 + LOG.error(_("Failed to delete group with " + "name or ID '%(group)s': %(e)s"), + {'group': group, 'e': e}) + + if errors > 0: + total = len(parsed_args.groups) + msg = (_("%(errors)s of %(total)s groups failed " + "to delete.") % {'errors': errors, 'total': total}) + raise exceptions.CommandError(msg) class ListGroup(command.Lister): diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index a634865911..12197cdde1 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -20,6 +20,7 @@ from keystoneauth1 import exceptions as ks_exc from osc_lib.cli import parseractions from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils import six @@ -148,15 +149,28 @@ def take_action(self, parsed_args): domain = None if parsed_args.domain: domain = common.find_domain(identity_client, parsed_args.domain) + errors = 0 for project in parsed_args.projects: - if domain is not None: - project_obj = utils.find_resource(identity_client.projects, - project, - domain_id=domain.id) - else: - project_obj = utils.find_resource(identity_client.projects, - project) - identity_client.projects.delete(project_obj.id) + try: + if domain is not None: + project_obj = utils.find_resource(identity_client.projects, + project, + domain_id=domain.id) + else: + project_obj = utils.find_resource(identity_client.projects, + project) + identity_client.projects.delete(project_obj.id) + except Exception as e: + errors += 1 + LOG.error(_("Failed to delete project with " + "name or ID '%(project)s': %(e)s"), + {'project': project, 'e': e}) + + if errors > 0: + total = len(parsed_args.projects) + msg = (_("%(errors)s of %(total)s projects failed " + "to delete.") % {'errors': errors, 'total': total}) + raise exceptions.CommandError(msg) class ListProject(command.Lister): diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index c9d0fbf305..994ecc9c8b 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -20,6 +20,7 @@ from keystoneauth1 import exceptions as ks_exc from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils import six @@ -223,14 +224,26 @@ def take_action(self, parsed_args): if parsed_args.domain: domain_id = common.find_domain(identity_client, parsed_args.domain).id - + errors = 0 for role in parsed_args.roles: - role_obj = utils.find_resource( - identity_client.roles, - role, - domain_id=domain_id - ) - identity_client.roles.delete(role_obj.id) + try: + role_obj = utils.find_resource( + identity_client.roles, + role, + domain_id=domain_id + ) + identity_client.roles.delete(role_obj.id) + except Exception as e: + errors += 1 + LOG.error(_("Failed to delete role with " + "name or ID '%(role)s': %(e)s"), + {'role': role, 'e': e}) + + if errors > 0: + total = len(parsed_args.roles) + msg = (_("%(errors)s of %(total)s roles failed " + "to delete.") % {'errors': errors, 'total': total}) + raise exceptions.CommandError(msg) class ListRole(command.Lister): diff --git a/openstackclient/identity/v3/trust.py b/openstackclient/identity/v3/trust.py index 62d72ea142..04ee4dce5a 100644 --- a/openstackclient/identity/v3/trust.py +++ b/openstackclient/identity/v3/trust.py @@ -14,8 +14,10 @@ """Identity v3 Trust action implementations""" import datetime +import logging from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils import six @@ -23,6 +25,9 @@ from openstackclient.identity import common +LOG = logging.getLogger(__name__) + + class CreateTrust(command.ShowOne): _description = _("Create new trust") @@ -145,9 +150,24 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - for t in parsed_args.trust: - trust_obj = utils.find_resource(identity_client.trusts, t) - identity_client.trusts.delete(trust_obj.id) + + errors = 0 + for trust in parsed_args.trust: + try: + trust_obj = utils.find_resource(identity_client.trusts, + trust) + identity_client.trusts.delete(trust_obj.id) + except Exception as e: + errors += 1 + LOG.error(_("Failed to delete trust with " + "name or ID '%(trust)s': %(e)s"), + {'trust': trust, 'e': e}) + + if errors > 0: + total = len(parsed_args.trust) + msg = (_("%(errors)s of %(total)s trusts failed " + "to delete.") % {'errors': errors, 'total': total}) + raise exceptions.CommandError(msg) class ListTrust(command.Lister): diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index 796cf28c22..19a4c29875 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -20,6 +20,7 @@ from keystoneauth1 import exceptions as ks_exc from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils import six @@ -161,15 +162,28 @@ def take_action(self, parsed_args): domain = None if parsed_args.domain: domain = common.find_domain(identity_client, parsed_args.domain) + errors = 0 for user in parsed_args.users: - if domain is not None: - user_obj = utils.find_resource(identity_client.users, - user, - domain_id=domain.id) - else: - user_obj = utils.find_resource(identity_client.users, - user) - identity_client.users.delete(user_obj.id) + try: + if domain is not None: + user_obj = utils.find_resource(identity_client.users, + user, + domain_id=domain.id) + else: + user_obj = utils.find_resource(identity_client.users, + user) + identity_client.users.delete(user_obj.id) + except Exception as e: + errors += 1 + LOG.error(_("Failed to delete user with " + "name or ID '%(user)s': %(e)s"), + {'user': user, 'e': e}) + + if errors > 0: + total = len(parsed_args.users) + msg = (_("%(errors)s of %(total)s users failed " + "to delete.") % {'errors': errors, 'total': total}) + raise exceptions.CommandError(msg) class ListUser(command.Lister): diff --git a/openstackclient/tests/unit/identity/v2_0/test_project.py b/openstackclient/tests/unit/identity/v2_0/test_project.py index c1f00762e4..4e1077db0d 100644 --- a/openstackclient/tests/unit/identity/v2_0/test_project.py +++ b/openstackclient/tests/unit/identity/v2_0/test_project.py @@ -13,8 +13,11 @@ # under the License. # +import mock + from keystoneauth1 import exceptions as ks_exc from osc_lib import exceptions +from osc_lib import utils from openstackclient.identity.v2_0 import project from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes @@ -302,6 +305,32 @@ def test_project_delete_no_options(self): ) self.assertIsNone(result) + @mock.patch.object(utils, 'find_resource') + def test_delete_multi_projects_with_exception(self, find_mock): + find_mock.side_effect = [self.fake_project, + exceptions.CommandError] + arglist = [ + self.fake_project.id, + 'unexist_project', + ] + verifylist = [ + ('projects', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 projects failed to delete.', + str(e)) + + find_mock.assert_any_call(self.projects_mock, self.fake_project.id) + find_mock.assert_any_call(self.projects_mock, 'unexist_project') + + self.assertEqual(2, find_mock.call_count) + self.projects_mock.delete.assert_called_once_with(self.fake_project.id) + class TestProjectList(TestProject): diff --git a/openstackclient/tests/unit/identity/v2_0/test_role.py b/openstackclient/tests/unit/identity/v2_0/test_role.py index 68ebf1418a..684ce803a2 100644 --- a/openstackclient/tests/unit/identity/v2_0/test_role.py +++ b/openstackclient/tests/unit/identity/v2_0/test_role.py @@ -17,6 +17,7 @@ from keystoneauth1 import exceptions as ks_exc from osc_lib import exceptions +from osc_lib import utils from openstackclient.identity.v2_0 import role from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes @@ -240,6 +241,32 @@ def test_role_delete_no_options(self): ) self.assertIsNone(result) + @mock.patch.object(utils, 'find_resource') + def test_delete_multi_roles_with_exception(self, find_mock): + find_mock.side_effect = [self.fake_role, + exceptions.CommandError] + arglist = [ + self.fake_role.id, + 'unexist_role', + ] + verifylist = [ + ('roles', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 roles failed to delete.', + str(e)) + + find_mock.assert_any_call(self.roles_mock, self.fake_role.id) + find_mock.assert_any_call(self.roles_mock, 'unexist_role') + + self.assertEqual(2, find_mock.call_count) + self.roles_mock.delete.assert_called_once_with(self.fake_role.id) + class TestRoleList(TestRole): diff --git a/openstackclient/tests/unit/identity/v2_0/test_user.py b/openstackclient/tests/unit/identity/v2_0/test_user.py index 765f8559a8..a8b9497ecf 100644 --- a/openstackclient/tests/unit/identity/v2_0/test_user.py +++ b/openstackclient/tests/unit/identity/v2_0/test_user.py @@ -17,6 +17,7 @@ from keystoneauth1 import exceptions as ks_exc from osc_lib import exceptions +from osc_lib import utils from openstackclient.identity.v2_0 import user from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes @@ -411,6 +412,32 @@ def test_user_delete_no_options(self): ) self.assertIsNone(result) + @mock.patch.object(utils, 'find_resource') + def test_delete_multi_users_with_exception(self, find_mock): + find_mock.side_effect = [self.fake_user, + exceptions.CommandError] + arglist = [ + self.fake_user.id, + 'unexist_user', + ] + verifylist = [ + ('users', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 users failed to delete.', + str(e)) + + find_mock.assert_any_call(self.users_mock, self.fake_user.id) + find_mock.assert_any_call(self.users_mock, 'unexist_user') + + self.assertEqual(2, find_mock.call_count) + self.users_mock.delete.assert_called_once_with(self.fake_user.id) + class TestUserList(TestUser): diff --git a/openstackclient/tests/unit/identity/v3/test_group.py b/openstackclient/tests/unit/identity/v3/test_group.py index eb50adb526..8558de950a 100644 --- a/openstackclient/tests/unit/identity/v3/test_group.py +++ b/openstackclient/tests/unit/identity/v3/test_group.py @@ -16,6 +16,7 @@ from keystoneauth1 import exceptions as ks_exc from osc_lib import exceptions +from osc_lib import utils from openstackclient.identity.v3 import group from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes @@ -257,6 +258,32 @@ def test_group_delete_with_domain(self): self.groups_mock.delete.assert_called_once_with(self.groups[0].id) self.assertIsNone(result) + @mock.patch.object(utils, 'find_resource') + def test_delete_multi_groups_with_exception(self, find_mock): + find_mock.side_effect = [self.groups[0], + exceptions.CommandError] + arglist = [ + self.groups[0].id, + 'unexist_group', + ] + verifylist = [ + ('groups', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 groups failed to delete.', + str(e)) + + find_mock.assert_any_call(self.groups_mock, self.groups[0].id) + find_mock.assert_any_call(self.groups_mock, 'unexist_group') + + self.assertEqual(2, find_mock.call_count) + self.groups_mock.delete.assert_called_once_with(self.groups[0].id) + class TestGroupList(TestGroup): diff --git a/openstackclient/tests/unit/identity/v3/test_project.py b/openstackclient/tests/unit/identity/v3/test_project.py index 702d920975..2b89809004 100644 --- a/openstackclient/tests/unit/identity/v3/test_project.py +++ b/openstackclient/tests/unit/identity/v3/test_project.py @@ -16,6 +16,7 @@ import mock from osc_lib import exceptions +from osc_lib import utils from openstackclient.identity.v3 import project from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes @@ -445,6 +446,32 @@ def test_project_delete_no_options(self): ) self.assertIsNone(result) + @mock.patch.object(utils, 'find_resource') + def test_delete_multi_projects_with_exception(self, find_mock): + find_mock.side_effect = [self.project, + exceptions.CommandError] + arglist = [ + self.project.id, + 'unexist_project', + ] + verifylist = [ + ('projects', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 projects failed to delete.', + str(e)) + + find_mock.assert_any_call(self.projects_mock, self.project.id) + find_mock.assert_any_call(self.projects_mock, 'unexist_project') + + self.assertEqual(2, find_mock.call_count) + self.projects_mock.delete.assert_called_once_with(self.project.id) + class TestProjectList(TestProject): diff --git a/openstackclient/tests/unit/identity/v3/test_role.py b/openstackclient/tests/unit/identity/v3/test_role.py index 448e18d372..c0b68bdfad 100644 --- a/openstackclient/tests/unit/identity/v3/test_role.py +++ b/openstackclient/tests/unit/identity/v3/test_role.py @@ -14,6 +14,10 @@ # import copy +import mock + +from osc_lib import exceptions +from osc_lib import utils from openstackclient.identity.v3 import role from openstackclient.tests.unit import fakes @@ -428,6 +432,36 @@ def test_role_delete_with_domain(self): ) self.assertIsNone(result) + @mock.patch.object(utils, 'find_resource') + def test_delete_multi_roles_with_exception(self, find_mock): + find_mock.side_effect = [self.roles_mock.get.return_value, + exceptions.CommandError] + arglist = [ + identity_fakes.role_name, + 'unexist_role', + ] + verifylist = [ + ('roles', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 roles failed to delete.', + str(e)) + + find_mock.assert_any_call(self.roles_mock, + identity_fakes.role_name, + domain_id=None) + find_mock.assert_any_call(self.roles_mock, + 'unexist_role', + domain_id=None) + + self.assertEqual(2, find_mock.call_count) + self.roles_mock.delete.assert_called_once_with(identity_fakes.role_id) + class TestRoleList(TestRole): diff --git a/openstackclient/tests/unit/identity/v3/test_trust.py b/openstackclient/tests/unit/identity/v3/test_trust.py index 4eeb8bfe17..93e8f63da1 100644 --- a/openstackclient/tests/unit/identity/v3/test_trust.py +++ b/openstackclient/tests/unit/identity/v3/test_trust.py @@ -12,6 +12,10 @@ # import copy +import mock + +from osc_lib import exceptions +from osc_lib import utils from openstackclient.identity.v3 import trust from openstackclient.tests.unit import fakes @@ -148,6 +152,33 @@ def test_trust_delete(self): ) self.assertIsNone(result) + @mock.patch.object(utils, 'find_resource') + def test_delete_multi_trusts_with_exception(self, find_mock): + find_mock.side_effect = [self.trusts_mock.get.return_value, + exceptions.CommandError] + arglist = [ + identity_fakes.trust_id, + 'unexist_trust', + ] + verifylist = [ + ('trust', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 trusts failed to delete.', + str(e)) + + find_mock.assert_any_call(self.trusts_mock, identity_fakes.trust_id) + find_mock.assert_any_call(self.trusts_mock, 'unexist_trust') + + self.assertEqual(2, find_mock.call_count) + self.trusts_mock.delete.assert_called_once_with( + identity_fakes.trust_id) + class TestTrustList(TestTrust): diff --git a/openstackclient/tests/unit/identity/v3/test_user.py b/openstackclient/tests/unit/identity/v3/test_user.py index 6150a5f3df..3c1f49a680 100644 --- a/openstackclient/tests/unit/identity/v3/test_user.py +++ b/openstackclient/tests/unit/identity/v3/test_user.py @@ -16,6 +16,9 @@ import contextlib import mock +from osc_lib import exceptions +from osc_lib import utils + from openstackclient.identity.v3 import user from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes @@ -465,6 +468,32 @@ def test_user_delete_no_options(self): ) self.assertIsNone(result) + @mock.patch.object(utils, 'find_resource') + def test_delete_multi_users_with_exception(self, find_mock): + find_mock.side_effect = [self.user, + exceptions.CommandError] + arglist = [ + self.user.id, + 'unexist_user', + ] + verifylist = [ + ('users', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 users failed to delete.', + str(e)) + + find_mock.assert_any_call(self.users_mock, self.user.id) + find_mock.assert_any_call(self.users_mock, 'unexist_user') + + self.assertEqual(2, find_mock.call_count) + self.users_mock.delete.assert_called_once_with(self.user.id) + class TestUserList(TestUser): From c6aceb78b71590a13bd5ff33b2b44b4ddbfe27b8 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 4 Jan 2017 11:30:17 -0600 Subject: [PATCH 1453/3095] Remove unneeded methods from OS_Config class These are in the minimum required version of osc-lib (1.2.0). A few methods remain that need to be released in osc-lib, expect them in the 1.3.0 release soon. Change-Id: I2333946da9a73e73377b646e4c06e99597990945 --- openstackclient/common/client_config.py | 96 ++----------------------- 1 file changed, 6 insertions(+), 90 deletions(-) diff --git a/openstackclient/common/client_config.py b/openstackclient/common/client_config.py index 1ac53f7fce..ad86e9f878 100644 --- a/openstackclient/common/client_config.py +++ b/openstackclient/common/client_config.py @@ -15,10 +15,8 @@ import logging -from os_client_config import config from os_client_config import exceptions as occ_exceptions -from oslo_utils import strutils -import six +from osc_lib.cli import client_config LOG = logging.getLogger(__name__) @@ -26,7 +24,7 @@ # Sublcass OpenStackConfig in order to munge config values # before auth plugins are loaded -class OSC_Config(config.OpenStackConfig): +class OSC_Config(client_config.OSC_Config): # TODO(dtroyer): Once os-client-config with pw_func argument is in # global-requirements we can remove __init()__ @@ -62,75 +60,8 @@ def __init__( return ret - def _auth_select_default_plugin(self, config): - """Select a default plugin based on supplied arguments - - Migrated from auth.select_auth_plugin() - """ - - identity_version = config.get('identity_api_version', '') - - if config.get('username', None) and not config.get('auth_type', None): - if identity_version == '3': - config['auth_type'] = 'v3password' - elif identity_version.startswith('2'): - config['auth_type'] = 'v2password' - else: - # let keystoneauth figure it out itself - config['auth_type'] = 'password' - elif config.get('token', None) and not config.get('auth_type', None): - if identity_version == '3': - config['auth_type'] = 'v3token' - elif identity_version.startswith('2'): - config['auth_type'] = 'v2token' - else: - # let keystoneauth figure it out itself - config['auth_type'] = 'token' - else: - # The ultimate default is similar to the original behaviour, - # but this time with version discovery - if not config.get('auth_type', None): - config['auth_type'] = 'password' - - LOG.debug("Auth plugin %s selected" % config['auth_type']) - return config - - def _auth_v2_arguments(self, config): - """Set up v2-required arguments from v3 info - - Migrated from auth.build_auth_params() - """ - - if ('auth_type' in config and config['auth_type'].startswith("v2")): - if 'project_id' in config['auth']: - config['auth']['tenant_id'] = config['auth']['project_id'] - if 'project_name' in config['auth']: - config['auth']['tenant_name'] = config['auth']['project_name'] - return config - - def _auth_v2_ignore_v3(self, config): - """Remove v3 arguemnts if present for v2 plugin - - Migrated from clientmanager.setup_auth() - """ - - # NOTE(hieulq): If USER_DOMAIN_NAME, USER_DOMAIN_ID, PROJECT_DOMAIN_ID - # or PROJECT_DOMAIN_NAME is present and API_VERSION is 2.0, then - # ignore all domain related configs. - if (config.get('identity_api_version', '').startswith('2') and - config.get('auth_type', None).endswith('password')): - domain_props = [ - 'project_domain_id', - 'project_domain_name', - 'user_domain_id', - 'user_domain_name', - ] - for prop in domain_props: - if config['auth'].pop(prop, None) is not None: - LOG.warning("Ignoring domain related config " + - prop + " because identity API version is 2.0") - return config - + # TODO(dtroyer): Remove _auth_default_domain when the v3otp fix is + # backported to osc-lib, should be in release 1.3.0 def _auth_default_domain(self, config): """Set a default domain from available arguments @@ -171,23 +102,6 @@ def _auth_default_domain(self, config): config['auth']['user_domain_id'] = default_domain return config - def auth_config_hook(self, config): - """Allow examination of config values before loading auth plugin - - OpenStackClient will override this to perform additional chacks - on auth_type. - """ - - config = self._auth_select_default_plugin(config) - config = self._auth_v2_arguments(config) - config = self._auth_v2_ignore_v3(config) - config = self._auth_default_domain(config) - - if LOG.isEnabledFor(logging.DEBUG): - LOG.debug("auth_config_hook(): %s", - strutils.mask_password(six.text_type(config))) - return config - def load_auth_plugin(self, config): """Get auth plugin and validate args""" @@ -196,10 +110,12 @@ def load_auth_plugin(self, config): auth_plugin = loader.load_from_options(**config['auth']) return auth_plugin + # TODO(dtroyer): Remove _validate_auth_ksc when it is in osc-lib 1.3.0 def _validate_auth_ksc(self, config, cloud, fixed_argparse=None): """Old compatibility hack for OSC, no longer needed/wanted""" return config + # TODO(dtroyer): Remove _validate_auth when it is in osc-lib 1.3.0 def _validate_auth(self, config, loader, fixed_argparse=None): """Validate auth plugin arguments""" # May throw a keystoneauth1.exceptions.NoMatchingPlugin From 1bd2bf67dab86bce06da89db65f2b532edf8e35e Mon Sep 17 00:00:00 2001 From: Imtiaz Chowdhury Date: Tue, 27 Dec 2016 14:04:29 -0800 Subject: [PATCH 1454/3095] Fixes image api URL endpoint for certain scenario openstackclient fails to get image list when the image api endpoint has 'v2' substring in the URL. Instead of checking whether the api endpoint URL terminates with '/v2', the current logic is checking whether 'v2' appears anywhere in the endpoint string. This issue was discovered on a production setup where certain server names had 'v2' in their names. For example, when a hostname is gopher.dev20.com, the image list APIs fail. This commit updates the unit test to reflect this scenario. Without the change in openstackclient/api/image_v2.py, all the unit tests fail. Co-Authored-By: sergio.carvalho@workday.com Change-Id: I26b85afd646938272dbabe8e045b337b7df58c7d Closes-Bug: 1652827 --- openstackclient/api/image_v1.py | 6 +++--- openstackclient/api/image_v2.py | 6 +++--- openstackclient/tests/unit/api/test_image_v1.py | 2 +- openstackclient/tests/unit/api/test_image_v2.py | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/openstackclient/api/image_v1.py b/openstackclient/api/image_v1.py index 534c775069..e15d825a31 100644 --- a/openstackclient/api/image_v1.py +++ b/openstackclient/api/image_v1.py @@ -19,7 +19,7 @@ class APIv1(api.BaseAPI): """Image v1 API""" - _endpoint_suffix = 'v1' + _endpoint_suffix = '/v1' def __init__(self, endpoint=None, **kwargs): super(APIv1, self).__init__(endpoint=endpoint, **kwargs) @@ -29,8 +29,8 @@ def __init__(self, endpoint=None, **kwargs): def _munge_url(self): # Hack this until discovery is up - if self._endpoint_suffix not in self.endpoint.split('/')[-1]: - self.endpoint = '/'.join([self.endpoint, self._endpoint_suffix]) + if not self.endpoint.endswith(self._endpoint_suffix): + self.endpoint = self.endpoint + self._endpoint_suffix def image_list( self, diff --git a/openstackclient/api/image_v2.py b/openstackclient/api/image_v2.py index 026498fa1d..c36281212c 100644 --- a/openstackclient/api/image_v2.py +++ b/openstackclient/api/image_v2.py @@ -19,12 +19,12 @@ class APIv2(image_v1.APIv1): """Image v2 API""" - _endpoint_suffix = 'v2' + _endpoint_suffix = '/v2' def _munge_url(self): # Hack this until discovery is up, and ignore parent endpoint setting - if 'v2' not in self.endpoint.split('/')[-1]: - self.endpoint = '/'.join([self.endpoint, 'v2']) + if not self.endpoint.endswith(self._endpoint_suffix): + self.endpoint = self.endpoint + self._endpoint_suffix def image_list( self, diff --git a/openstackclient/tests/unit/api/test_image_v1.py b/openstackclient/tests/unit/api/test_image_v1.py index e02ef3812b..6ce3ddeac0 100644 --- a/openstackclient/tests/unit/api/test_image_v1.py +++ b/openstackclient/tests/unit/api/test_image_v1.py @@ -21,7 +21,7 @@ FAKE_PROJECT = 'xyzpdq' -FAKE_URL = 'http://gopher.com' +FAKE_URL = 'http://gopher.dev10.com' class TestImageAPIv1(utils.TestCase): diff --git a/openstackclient/tests/unit/api/test_image_v2.py b/openstackclient/tests/unit/api/test_image_v2.py index 5dbb51e03c..22490e4632 100644 --- a/openstackclient/tests/unit/api/test_image_v2.py +++ b/openstackclient/tests/unit/api/test_image_v2.py @@ -21,7 +21,7 @@ FAKE_PROJECT = 'xyzpdq' -FAKE_URL = 'http://gopher.com' +FAKE_URL = 'http://gopher.dev20.com' class TestImageAPIv2(utils.TestCase): From f055fe67c11fff020ae959b1672844aaff382491 Mon Sep 17 00:00:00 2001 From: Jordan Pittier Date: Thu, 15 Dec 2016 22:20:30 +0100 Subject: [PATCH 1455/3095] Add support for Glance 'update image members' feature This patch adds 3 new options to the "image set" command: --accept, --reject and --pending. This updates the membership status for an image. Closes-Bug: 1620481 Change-Id: I13b8c067aad68ece9ff636fbdd83bcb3663c91b2 --- doc/source/command-objects/image.rst | 31 ++++++++++++++++ openstackclient/image/v2/image.py | 36 ++++++++++++++++++- .../tests/functional/image/v2/test_image.py | 22 ++++++++++++ .../tests/unit/image/v2/test_image.py | 33 +++++++++++++++++ ...ate-image-membership-68221f226ca3b6e0.yaml | 4 +++ 5 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/image-set-to-update-image-membership-68221f226ca3b6e0.yaml diff --git a/doc/source/command-objects/image.rst b/doc/source/command-objects/image.rst index 7ebcb54ad2..999842af21 100644 --- a/doc/source/command-objects/image.rst +++ b/doc/source/command-objects/image.rst @@ -325,6 +325,7 @@ Set image properties [--ramdisk-id ] [--activate|--deactivate] [--project [--project-domain ]] + [--accept | --reject | --pending] .. option:: --name @@ -490,6 +491,36 @@ Set image properties .. versionadded:: 2 +.. option:: --accept + + Accept the image membership. + + If `--project` is passed, this will update the membership status for the + given project, otherwise `--project` will default to the project the user + is authenticated to. + + .. versionadded:: 2 + +.. option:: --reject + + Reject the image membership. + + If `--project` is passed, this will update the membership status for the + given project, otherwise `--project` will default to the project the user + is authenticated to. + + .. versionadded:: 2 + +.. option:: --pending + + Reset the image membership to 'pending'. + + If `--project` is passed, this will update the membership status for the + given project, otherwise `--project` will default to the project the user + is authenticated to. + + .. versionadded:: 2 + .. _image_set-image: .. describe:: diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 55eb7eb104..418cc3973f 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -777,6 +777,23 @@ def get_parser(self, prog_name): dest=deadopt.replace('-', '_'), help=argparse.SUPPRESS, ) + + membership_group = parser.add_mutually_exclusive_group() + membership_group.add_argument( + "--accept", + action="store_true", + help=_("Accept the image membership"), + ) + membership_group.add_argument( + "--reject", + action="store_true", + help=_("Reject the image membership"), + ) + membership_group.add_argument( + "--pending", + action="store_true", + help=_("Reset the image membership to 'pending'"), + ) return parser def take_action(self, parsed_args): @@ -828,12 +845,14 @@ def take_action(self, parsed_args): project_arg = parsed_args.owner LOG.warning(_('The --owner option is deprecated, ' 'please use --project instead.')) + project_id = None if project_arg: - kwargs['owner'] = common.find_project( + project_id = common.find_project( identity_client, project_arg, parsed_args.project_domain, ).id + kwargs['owner'] = project_id image = utils.find_resource( image_client.images, parsed_args.image) @@ -846,6 +865,21 @@ def take_action(self, parsed_args): image_client.images.reactivate(image.id) activation_status = "activated" + membership_group_args = ('accept', 'reject', 'pending') + membership_status = [status for status in membership_group_args + if getattr(parsed_args, status)] + if membership_status: + # If a specific project is not passed, assume we want to update + # our own membership + if not project_id: + project_id = self.app.client_manager.auth_ref.project_id + # The mutually exclusive group of the arg parser ensure we have at + # most one item in the membership_status list. + if membership_status[0] != 'pending': + membership_status[0] += 'ed' # Glance expects the past form + image_client.image_members.update( + image.id, project_id, membership_status[0]) + if parsed_args.tags: # Tags should be extended, but duplicates removed kwargs['tags'] = list(set(image.tags).union(set(parsed_args.tags))) diff --git a/openstackclient/tests/functional/image/v2/test_image.py b/openstackclient/tests/functional/image/v2/test_image.py index 3f432b02ee..6faff94a32 100644 --- a/openstackclient/tests/functional/image/v2/test_image.py +++ b/openstackclient/tests/functional/image/v2/test_image.py @@ -74,3 +74,25 @@ def test_image_unset(self): self.openstack('image unset --property a --property c ' + self.NAME) raw_output = self.openstack('image show ' + self.NAME + opts) self.assertEqual(self.NAME + "\n\n", raw_output) + + def test_image_members(self): + opts = self.get_opts(['project_id']) + my_project_id = self.openstack('token issue' + opts).strip() + self.openstack( + 'image add project {} {}'.format(self.NAME, my_project_id)) + + self.openstack( + 'image set --accept ' + self.NAME) + shared_img_list = self.parse_listing( + self.openstack('image list --shared', self.get_opts(['name'])) + ) + self.assertIn(self.NAME, [img['Name'] for img in shared_img_list]) + + self.openstack( + 'image set --reject ' + self.NAME) + shared_img_list = self.parse_listing( + self.openstack('image list --shared', self.get_opts(['name'])) + ) + + self.openstack( + 'image remove project {} {}'.format(self.NAME, my_project_id)) diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py index a054e513d9..a15131190f 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -845,6 +845,39 @@ def test_image_set_no_options(self): self.assertIsNone(result) + self.image_members_mock.update.assert_not_called() + + def test_image_set_membership_option(self): + membership = image_fakes.FakeImage.create_one_image_member( + attrs={'image_id': image_fakes.image_id, + 'member_id': self.project.id} + ) + self.image_members_mock.update.return_value = membership + + for status in ('accept', 'reject', 'pending'): + arglist = [ + '--%s' % status, + image_fakes.image_id, + ] + verifylist = [ + (status, True), + ('image', image_fakes.image_id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.image_members_mock.update.assert_called_once_with( + image_fakes.image_id, + self.app.client_manager.auth_ref.project_id, + status if status == 'pending' else status + 'ed' + ) + self.image_members_mock.update.reset_mock() + + # Assert that the 'update image" route is also called, in addition to + # the 'update membership' route. + self.images_mock.update.assert_called_with(image_fakes.image_id) + def test_image_set_options(self): arglist = [ '--name', 'new-name', diff --git a/releasenotes/notes/image-set-to-update-image-membership-68221f226ca3b6e0.yaml b/releasenotes/notes/image-set-to-update-image-membership-68221f226ca3b6e0.yaml new file mode 100644 index 0000000000..599216c47b --- /dev/null +++ b/releasenotes/notes/image-set-to-update-image-membership-68221f226ca3b6e0.yaml @@ -0,0 +1,4 @@ +--- +features: + - Add support to update image membership with the `--accept`, + ``--reject`` and ``--pending`` options of the ``image set command``. From e0813cc54eaf15a553a8273236482e92dc3bf85c Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 4 Jan 2017 19:24:49 -0600 Subject: [PATCH 1456/3095] Functional tests - port * Rework functional tests to remove resource create/delete from setupClass() and teardownClass() methods. * Add tests for more command options Change-Id: Ic77df94fe5980e60c6a67cbf061b9a9dc601518f --- .../tests/functional/network/v2/test_port.py | 164 +++++++++++++++--- 1 file changed, 141 insertions(+), 23 deletions(-) diff --git a/openstackclient/tests/functional/network/v2/test_port.py b/openstackclient/tests/functional/network/v2/test_port.py index decd9553dc..e100bd8296 100644 --- a/openstackclient/tests/functional/network/v2/test_port.py +++ b/openstackclient/tests/functional/network/v2/test_port.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import re import uuid from openstackclient.tests.functional import base @@ -24,35 +25,152 @@ class PortTests(base.TestCase): @classmethod def setUpClass(cls): - # Create a network for the subnet. - cls.openstack('network create ' + cls.NETWORK_NAME) - opts = cls.get_opts(cls.FIELDS) - raw_output = cls.openstack( - 'port create --network ' + cls.NETWORK_NAME + ' ' + - cls.NAME + opts - ) - expected = cls.NAME + '\n' - cls.assertOutput(expected, raw_output) + # Set up some regex for matching below + cls.re_id = re.compile("\s+id\s+\|\s+(\S+)") + cls.re_name = re.compile("\s+name\s+\|\s+([^|]+?)\s+\|") + cls.re_description = re.compile("\s+description\s+\|\s+([^|]+?)\s+\|") + cls.re_mac_address = re.compile("\s+mac_address\s+\|\s+([^|]+?)\s+\|") + cls.re_state = re.compile("\s+admin_state_up\s+\|\s+([^|]+?)\s+\|") + + # Create a network for the port + raw_output = cls.openstack('network create ' + cls.NETWORK_NAME) + cls.network_id = re.search(cls.re_id, raw_output).group(1) @classmethod def tearDownClass(cls): - raw_output = cls.openstack('port delete ' + cls.NAME) - cls.assertOutput('', raw_output) raw_output = cls.openstack('network delete ' + cls.NETWORK_NAME) cls.assertOutput('', raw_output) + def test_port_delete(self): + """Test create, delete multiple""" + raw_output = self.openstack( + 'port create --network ' + self.NETWORK_NAME + ' ' + self.NAME + ) + re_id1 = re.search(self.re_id, raw_output) + self.assertIsNotNone(re_id1) + id1 = re_id1.group(1) + self.assertIsNotNone( + re.search(self.re_mac_address, raw_output).group(1), + ) + self.assertEqual( + self.NAME, + re.search(self.re_name, raw_output).group(1), + ) + + raw_output = self.openstack( + 'port create ' + + '--network ' + self.NETWORK_NAME + ' ' + + self.NAME + 'x' + ) + id2 = re.search(self.re_id, raw_output).group(1) + self.assertIsNotNone( + re.search(self.re_mac_address, raw_output).group(1), + ) + self.assertEqual( + self.NAME + 'x', + re.search(self.re_name, raw_output).group(1), + ) + + # Clean up after ourselves + raw_output = self.openstack('port delete ' + id1 + ' ' + id2) + self.assertOutput('', raw_output) + def test_port_list(self): - opts = self.get_opts(self.HEADERS) - raw_output = self.openstack('port list' + opts) - self.assertIn(self.NAME, raw_output) + """Test create defaults, list, delete""" + raw_output = self.openstack( + 'port create --network ' + self.NETWORK_NAME + ' ' + self.NAME + ) + re_id1 = re.search(self.re_id, raw_output) + self.assertIsNotNone(re_id1) + id1 = re_id1.group(1) + mac1 = re.search(self.re_mac_address, raw_output).group(1) + self.addCleanup(self.openstack, 'port delete ' + id1) + self.assertEqual( + self.NAME, + re.search(self.re_name, raw_output).group(1), + ) + + raw_output = self.openstack( + 'port create ' + + '--network ' + self.NETWORK_NAME + ' ' + + self.NAME + 'x' + ) + id2 = re.search(self.re_id, raw_output).group(1) + mac2 = re.search(self.re_mac_address, raw_output).group(1) + self.addCleanup(self.openstack, 'port delete ' + id2) + self.assertEqual( + self.NAME + 'x', + re.search(self.re_name, raw_output).group(1), + ) + + # Test list + raw_output = self.openstack('port list') + self.assertIsNotNone(re.search("\|\s+" + id1 + "\s+\|", raw_output)) + self.assertIsNotNone(re.search("\|\s+" + id2 + "\s+\|", raw_output)) + self.assertIsNotNone(re.search("\|\s+" + mac1 + "\s+\|", raw_output)) + self.assertIsNotNone(re.search("\|\s+" + mac2 + "\s+\|", raw_output)) + + # Test list --long + raw_output = self.openstack('port list --long') + self.assertIsNotNone(re.search("\|\s+" + id1 + "\s+\|", raw_output)) + self.assertIsNotNone(re.search("\|\s+" + id2 + "\s+\|", raw_output)) + + # Test list --mac-address + raw_output = self.openstack('port list --mac-address ' + mac2) + self.assertIsNone(re.search("\|\s+" + id1 + "\s+\|", raw_output)) + self.assertIsNotNone(re.search("\|\s+" + id2 + "\s+\|", raw_output)) + self.assertIsNone(re.search("\|\s+" + mac1 + "\s+\|", raw_output)) + self.assertIsNotNone(re.search("\|\s+" + mac2 + "\s+\|", raw_output)) def test_port_set(self): - self.openstack('port set --disable ' + self.NAME) - opts = self.get_opts(['name', 'admin_state_up']) - raw_output = self.openstack('port show ' + self.NAME + opts) - self.assertEqual("DOWN\n" + self.NAME + "\n", raw_output) - - def test_port_show(self): - opts = self.get_opts(self.FIELDS) - raw_output = self.openstack('port show ' + self.NAME + opts) - self.assertEqual(self.NAME + "\n", raw_output) + """Test create, set, show, delete""" + raw_output = self.openstack( + 'port create ' + + '--network ' + self.NETWORK_NAME + ' ' + + '--description xyzpdq ' + '--disable ' + + self.NAME + ) + re_id = re.search(self.re_id, raw_output) + self.assertIsNotNone(re_id) + id = re_id.group(1) + self.addCleanup(self.openstack, 'port delete ' + id) + self.assertEqual( + self.NAME, + re.search(self.re_name, raw_output).group(1), + ) + self.assertEqual( + 'xyzpdq', + re.search(self.re_description, raw_output).group(1), + ) + self.assertEqual( + 'DOWN', + re.search(self.re_state, raw_output).group(1), + ) + + raw_output = self.openstack( + 'port set ' + + '--enable ' + + self.NAME + ) + self.assertOutput('', raw_output) + + raw_output = self.openstack( + 'port show ' + + self.NAME + ) + self.assertEqual( + self.NAME, + re.search(self.re_name, raw_output).group(1), + ) + self.assertEqual( + 'xyzpdq', + re.search(self.re_description, raw_output).group(1), + ) + self.assertEqual( + 'UP', + re.search(self.re_state, raw_output).group(1), + ) + self.assertIsNotNone( + re.search(self.re_mac_address, raw_output).group(1), + ) From d8749f9148f2a78f28e91c58e698779735eae4dc Mon Sep 17 00:00:00 2001 From: Jens Rosenboom Date: Thu, 5 Jan 2017 12:34:43 +0100 Subject: [PATCH 1457/3095] Fix creating a private flavor with ID auto When a private flavor is created with ID auto (=default) and a project is specified for it, instead of trying to add the project to the flavor called "auto" the ID of the newly created project should be used. Change-Id: I19f7a0ec26bd5d147f00ecba3312240e3601567e Closes-Bug: 1654221 --- openstackclient/compute/v2/flavor.py | 2 +- openstackclient/tests/unit/compute/v2/test_flavor.py | 5 ++--- releasenotes/notes/bug-1654221-a564ab75a6afc332.yaml | 6 ++++++ 3 files changed, 9 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/bug-1654221-a564ab75a6afc332.yaml diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index f20d154b27..e562cd40ca 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -167,7 +167,7 @@ def take_action(self, parsed_args): parsed_args.project_domain, ).id compute_client.flavor_access.add_tenant_access( - parsed_args.id, project_id) + flavor.id, project_id) except Exception as e: msg = _("Failed to add project %(project)s access to " "flavor: %(e)s") diff --git a/openstackclient/tests/unit/compute/v2/test_flavor.py b/openstackclient/tests/unit/compute/v2/test_flavor.py index 93ad9d14c9..632fcda129 100644 --- a/openstackclient/tests/unit/compute/v2/test_flavor.py +++ b/openstackclient/tests/unit/compute/v2/test_flavor.py @@ -160,7 +160,7 @@ def test_flavor_create_other_options(self): self.flavor.is_public = False arglist = [ - '--id', self.flavor.id, + '--id', 'auto', '--ram', str(self.flavor.ram), '--disk', str(self.flavor.disk), '--ephemeral', str(self.flavor.ephemeral), @@ -174,7 +174,6 @@ def test_flavor_create_other_options(self): self.flavor.name, ] verifylist = [ - ('id', self.flavor.id), ('ram', self.flavor.ram), ('disk', self.flavor.disk), ('ephemeral', self.flavor.ephemeral), @@ -193,7 +192,7 @@ def test_flavor_create_other_options(self): self.flavor.ram, self.flavor.vcpus, self.flavor.disk, - self.flavor.id, + 'auto', self.flavor.ephemeral, self.flavor.swap, self.flavor.rxtx_factor, diff --git a/releasenotes/notes/bug-1654221-a564ab75a6afc332.yaml b/releasenotes/notes/bug-1654221-a564ab75a6afc332.yaml new file mode 100644 index 0000000000..5f7132ff84 --- /dev/null +++ b/releasenotes/notes/bug-1654221-a564ab75a6afc332.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Fix ``--project`` option for ``flavor create`` command when the + ID for the new flavor is auto generated. + [Bug `1654221 `_] From 4d15a2a8fc50e64c73001a56d15763778f6dda5b Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 5 Jan 2017 20:30:36 +0000 Subject: [PATCH 1458/3095] Updated from global requirements Change-Id: Ie7b85846889a74026130c89ef37fe38bf8727ce3 --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index b84e21e4cc..08b49c9880 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -35,5 +35,5 @@ python-muranoclient>=0.8.2 # Apache-2.0 python-neutronclient>=5.1.0 # Apache-2.0 python-saharaclient>=0.18.0 # Apache-2.0 python-searchlightclient>=1.0.0 #Apache-2.0 -python-senlinclient>=0.3.0 # Apache-2.0 +python-senlinclient>=1.1.0 # Apache-2.0 python-zaqarclient>=1.0.0 # Apache-2.0 From 99aeff4967c9cd1cb01408993eff72c4fcbada7d Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Fri, 6 Jan 2017 12:33:41 +0800 Subject: [PATCH 1459/3095] Fix "ip availability show" command SDK refactor broken ip availability show functional test, We can not find a ip availability by network name but only network ID, so we find network and get the ID first, then find the ip availability by the network ID. Closes-Bug: 1653139 Change-Id: I246163fb875e2cdb9e5b091bac500a94268e8aa9 --- openstackclient/network/v2/ip_availability.py | 4 +++- .../functional/network/v2/test_ip_availability.py | 2 -- .../tests/unit/network/v2/test_ip_availability.py | 10 ++++++++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/openstackclient/network/v2/ip_availability.py b/openstackclient/network/v2/ip_availability.py index a80fe1c410..1d96358054 100644 --- a/openstackclient/network/v2/ip_availability.py +++ b/openstackclient/network/v2/ip_availability.py @@ -107,7 +107,9 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): client = self.app.client_manager.network - obj = client.find_network_ip_availability(parsed_args.network, + network_id = client.find_network(parsed_args.network, + ignore_missing=False).id + obj = client.find_network_ip_availability(network_id, ignore_missing=False) display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) diff --git a/openstackclient/tests/functional/network/v2/test_ip_availability.py b/openstackclient/tests/functional/network/v2/test_ip_availability.py index edbe7e3c3e..b5c908f44d 100644 --- a/openstackclient/tests/functional/network/v2/test_ip_availability.py +++ b/openstackclient/tests/functional/network/v2/test_ip_availability.py @@ -10,7 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -import testtools import uuid from openstackclient.tests.functional import base @@ -47,7 +46,6 @@ def test_ip_availability_list(self): raw_output = self.openstack('ip availability list' + opts) self.assertIn(self.NETWORK_NAME, raw_output) - @testtools.skip('broken SDK testing') def test_ip_availability_show(self): opts = self.get_opts(self.FIELDS) raw_output = self.openstack( diff --git a/openstackclient/tests/unit/network/v2/test_ip_availability.py b/openstackclient/tests/unit/network/v2/test_ip_availability.py index 4bdbddc47f..c7c5a9b49e 100644 --- a/openstackclient/tests/unit/network/v2/test_ip_availability.py +++ b/openstackclient/tests/unit/network/v2/test_ip_availability.py @@ -118,8 +118,10 @@ def test_list_project(self): class TestShowIPAvailability(TestIPAvailability): + _network = network_fakes.FakeNetwork.create_one_network() _ip_availability = \ - network_fakes.FakeIPAvailability.create_one_ip_availability() + network_fakes.FakeIPAvailability.create_one_ip_availability( + attrs={'network_id': _network.id}) columns = ( 'network_id', @@ -144,6 +146,8 @@ def setUp(self): self.network.find_network_ip_availability = mock.Mock( return_value=self._ip_availability) + self.network.find_network = mock.Mock( + return_value=self._network) # Get the command object to test self.cmd = ip_availability.ShowIPAvailability( @@ -166,8 +170,10 @@ def test_show_all_options(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.network.find_network_ip_availability.assert_called_once_with( + self._ip_availability.network_id, + ignore_missing=False) + self.network.find_network.assert_called_once_with( self._ip_availability.network_name, ignore_missing=False) - self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) From d80b1465e6ea6019531a2bd1df4599e28afdebf4 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Fri, 6 Jan 2017 13:14:37 +0800 Subject: [PATCH 1460/3095] Fix network service provider functional test SDK refactor broken network service provider functional test, tested this command works, but there is a error in the funtional test, so fix it. Change-Id: I783c58cedd39a05b665e47709b2b5321871e558b Closes-Bug: 1653138 --- .../functional/network/v2/test_network_service_provider.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/openstackclient/tests/functional/network/v2/test_network_service_provider.py b/openstackclient/tests/functional/network/v2/test_network_service_provider.py index 82420ea320..6fbff6c8d4 100644 --- a/openstackclient/tests/functional/network/v2/test_network_service_provider.py +++ b/openstackclient/tests/functional/network/v2/test_network_service_provider.py @@ -13,17 +13,14 @@ # License for the specific language governing permissions and limitations # under the License. -import testtools - from openstackclient.tests.functional import base class TestNetworkServiceProvider(base.TestCase): """Functional tests for network service provider""" - SERVICE_TYPE = ['L3_ROUTER_NAT'] + SERVICE_TYPE = 'L3_ROUTER_NAT' - @testtools.skip('broken SDK testing') def test_network_service_provider_list(self): raw_output = self.openstack('network service provider list') self.assertIn(self.SERVICE_TYPE, raw_output) From 2e78c11c8d485195b9ae40b9d00cf3a557aebd6d Mon Sep 17 00:00:00 2001 From: Zhou Zhihong Date: Wed, 21 Dec 2016 18:27:26 -0800 Subject: [PATCH 1461/3095] Add options to allow filtering on agent list Add options to allow filtering via --agent-type and --host on agent list Change-Id: I1800f0777aa92a76b4b95f64f8acc18454809e81 Closes-Bug: #1641868 Partially-Implements: blueprint network-commands-options --- doc/source/command-objects/network-agent.rst | 13 ++++++ openstackclient/network/v2/network_agent.py | 41 ++++++++++++++++++- .../unit/network/v2/test_network_agent.py | 35 ++++++++++++++++ .../notes/bug-1641868-97c284e33f944c2d.yaml | 6 +++ 4 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/bug-1641868-97c284e33f944c2d.yaml diff --git a/doc/source/command-objects/network-agent.rst b/doc/source/command-objects/network-agent.rst index b95f2e5074..947e07cf6c 100644 --- a/doc/source/command-objects/network-agent.rst +++ b/doc/source/command-objects/network-agent.rst @@ -35,6 +35,19 @@ List network agents .. code:: bash openstack network agent list + [--agent-type ] + [--host ] + +.. option:: --agent-type + + List only agents with the specified agent type. + The supported agent types are: dhcp, open-vswitch, + linux-bridge, ofa, l3, loadbalancer, metering, + metadata, macvtap, nic. + +.. option:: --host + + List only agents running on the specified host network agent set ----------------- diff --git a/openstackclient/network/v2/network_agent.py b/openstackclient/network/v2/network_agent.py index 6570849962..b3411166fa 100644 --- a/openstackclient/network/v2/network_agent.py +++ b/openstackclient/network/v2/network_agent.py @@ -72,6 +72,25 @@ def take_action(self, parsed_args): class ListNetworkAgent(command.Lister): _description = _("List network agents") + def get_parser(self, prog_name): + parser = super(ListNetworkAgent, self).get_parser(prog_name) + parser.add_argument( + '--agent-type', + metavar='', + choices=["dhcp", "open-vswitch", "linux-bridge", "ofa", "l3", + "loadbalancer", "metering", "metadata", "macvtap", "nic"], + help=_("List only agents with the specified agent type. " + "The supported agent types are: dhcp, open-vswitch, " + "linux-bridge, ofa, l3, loadbalancer, metering, " + "metadata, macvtap, nic.") + ) + parser.add_argument( + '--host', + metavar='', + help=_("List only agents running on the specified host") + ) + return parser + def take_action(self, parsed_args): client = self.app.client_manager.network columns = ( @@ -92,7 +111,27 @@ def take_action(self, parsed_args): 'State', 'Binary' ) - data = client.agents() + + key_value = { + 'dhcp': 'DHCP agent', + 'open-vswitch': 'Open vSwitch agent', + 'linux-bridge': 'Linux bridge agent', + 'ofa': 'OFA driver agent', + 'l3': 'L3 agent', + 'loadbalancer': 'Loadbalancer agent', + 'metering': 'Metering agent', + 'metadata': 'Metadata agent', + 'macvtap': 'Macvtap agent', + 'nic': 'NIC Switch agent' + } + + filters = {} + if parsed_args.agent_type is not None: + filters['agent_type'] = key_value[parsed_args.agent_type] + if parsed_args.host is not None: + filters['host'] = parsed_args.host + + data = client.agents(**filters) return (column_headers, (utils.get_item_properties( s, columns, formatters=_formatters, diff --git a/openstackclient/tests/unit/network/v2/test_network_agent.py b/openstackclient/tests/unit/network/v2/test_network_agent.py index 9f5b442a82..9fd395b48b 100644 --- a/openstackclient/tests/unit/network/v2/test_network_agent.py +++ b/openstackclient/tests/unit/network/v2/test_network_agent.py @@ -130,6 +130,7 @@ class TestListNetworkAgent(TestNetworkAgent): ) data = [] for agent in network_agents: + agent.agent_type = 'DHCP agent' data.append(( agent.id, agent.agent_type, @@ -159,6 +160,40 @@ def test_network_agents_list(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + def test_network_agents_list_agent_type(self): + arglist = [ + '--agent-type', 'dhcp', + ] + verifylist = [ + ('agent_type', 'dhcp'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.agents.assert_called_once_with(**{ + 'agent_type': self.network_agents[0].agent_type, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_network_agents_list_host(self): + arglist = [ + '--host', self.network_agents[0].host, + ] + verifylist = [ + ('host', self.network_agents[0].host), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.agents.assert_called_once_with(**{ + 'host': self.network_agents[0].host, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + class TestSetNetworkAgent(TestNetworkAgent): diff --git a/releasenotes/notes/bug-1641868-97c284e33f944c2d.yaml b/releasenotes/notes/bug-1641868-97c284e33f944c2d.yaml new file mode 100644 index 0000000000..0eb7e11da9 --- /dev/null +++ b/releasenotes/notes/bug-1641868-97c284e33f944c2d.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add filters ``--agent-type`` and ``--host`` + to ``network agent list`` command + [Bug `1641868 `_] From f825c9b81bb0aca7a623a6106fba01397cc6ebe8 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 5 Jan 2017 12:32:12 -0600 Subject: [PATCH 1462/3095] Functional tests - flavor * Rework functional tests to remove resource create/delete from setupClass() and teardownClass() methods. * Add tests for more command options * Use JSON output Change-Id: Ib99ef954fe8e1170c7445940180d80b8b9c0a92c --- .../functional/compute/v2/test_flavor.py | 227 +++++++++++++++--- 1 file changed, 200 insertions(+), 27 deletions(-) diff --git a/openstackclient/tests/functional/compute/v2/test_flavor.py b/openstackclient/tests/functional/compute/v2/test_flavor.py index 794a6cc30f..0b01da5172 100644 --- a/openstackclient/tests/functional/compute/v2/test_flavor.py +++ b/openstackclient/tests/functional/compute/v2/test_flavor.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import json import uuid from openstackclient.tests.functional import base @@ -18,52 +19,224 @@ class FlavorTests(base.TestCase): """Functional tests for flavor.""" - NAME = uuid.uuid4().hex - HEADERS = ['Name'] - FIELDS = ['name'] + PROJECT_NAME = uuid.uuid4().hex @classmethod def setUpClass(cls): - opts = cls.get_opts(cls.FIELDS) - raw_output = cls.openstack( - 'flavor create --property a=b --property c=d ' + cls.NAME + opts) - expected = cls.NAME + '\n' - cls.assertOutput(expected, raw_output) + # Make a project + cmd_output = json.loads(cls.openstack( + "project create -f json --enable " + cls.PROJECT_NAME + )) + cls.project_id = cmd_output["id"] @classmethod def tearDownClass(cls): - raw_output = cls.openstack('flavor delete ' + cls.NAME) + raw_output = cls.openstack("project delete " + cls.PROJECT_NAME) cls.assertOutput('', raw_output) + def test_flavor_delete(self): + """Test create w/project, delete multiple""" + name1 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + "flavor create -f json " + + "--project " + self.PROJECT_NAME + " " + + "--private " + + name1 + )) + self.assertIsNotNone(cmd_output["id"]) + + name2 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + "flavor create -f json " + + "--id qaz " + + "--project " + self.PROJECT_NAME + " " + + "--private " + + name2 + )) + self.assertIsNotNone(cmd_output["id"]) + self.assertEqual( + "qaz", + cmd_output["id"], + ) + + raw_output = self.openstack( + "flavor delete " + name1 + " " + name2, + ) + self.assertOutput('', raw_output) + def test_flavor_list(self): - opts = self.get_opts(self.HEADERS) - raw_output = self.openstack('flavor list' + opts) - self.assertIn("small", raw_output) - self.assertIn(self.NAME, raw_output) + """Test create defaults, list filters, delete""" + name1 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + "flavor create -f json " + + "--property a=b " + + "--property c=d " + + name1 + )) + self.addCleanup(self.openstack, "flavor delete " + name1) + self.assertIsNotNone(cmd_output["id"]) + self.assertEqual( + name1, + cmd_output["name"], + ) + + name2 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + "flavor create -f json " + + "--id qaz " + + "--ram 123 " + + "--private " + + "--property a=b2 " + + "--property b=d2 " + + name2 + )) + self.addCleanup(self.openstack, "flavor delete " + name2) + self.assertIsNotNone(cmd_output["id"]) + self.assertEqual( + "qaz", + cmd_output["id"], + ) + self.assertEqual( + name2, + cmd_output["name"], + ) + self.assertEqual( + 123, + cmd_output["ram"], + ) + self.assertEqual( + 0, + cmd_output["disk"], + ) + self.assertEqual( + False, + cmd_output["os-flavor-access:is_public"], + ) + self.assertEqual( + "a='b2', b='d2'", + cmd_output["properties"], + ) - def test_flavor_show(self): - opts = self.get_opts(self.FIELDS) - raw_output = self.openstack('flavor show ' + self.NAME + opts) - self.assertEqual(self.NAME + "\n", raw_output) + # Test list + cmd_output = json.loads(self.openstack( + "flavor list -f json" + )) + col_name = [x["Name"] for x in cmd_output] + self.assertIn(name1, col_name) + self.assertNotIn(name2, col_name) + + # Test list --long + cmd_output = json.loads(self.openstack( + "flavor list -f json " + + "--long" + )) + col_name = [x["Name"] for x in cmd_output] + col_properties = [x['Properties'] for x in cmd_output] + self.assertIn(name1, col_name) + self.assertIn("a='b', c='d'", col_properties) + self.assertNotIn(name2, col_name) + self.assertNotIn("b2', b='d2'", col_properties) + + # Test list --public + cmd_output = json.loads(self.openstack( + "flavor list -f json " + + "--public" + )) + col_name = [x["Name"] for x in cmd_output] + self.assertIn(name1, col_name) + self.assertNotIn(name2, col_name) + + # Test list --private + cmd_output = json.loads(self.openstack( + "flavor list -f json " + + "--private" + )) + col_name = [x["Name"] for x in cmd_output] + self.assertNotIn(name1, col_name) + self.assertIn(name2, col_name) + + # Test list --all + cmd_output = json.loads(self.openstack( + "flavor list -f json " + + "--all" + )) + col_name = [x["Name"] for x in cmd_output] + self.assertIn(name1, col_name) + self.assertIn(name2, col_name) def test_flavor_properties(self): - opts = self.get_opts(['properties']) - # check the properties we added in create command. - raw_output = self.openstack('flavor show ' + self.NAME + opts) - self.assertEqual("a='b', c='d'\n", raw_output) + """Test create defaults, list filters, delete""" + name1 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + "flavor create -f json " + + "--id qaz " + + "--ram 123 " + + "--disk 20 " + + "--private " + + "--property a=first " + + "--property b=second " + + name1 + )) + self.addCleanup(self.openstack, "flavor delete " + name1) + self.assertIsNotNone(cmd_output["id"]) + self.assertEqual( + "qaz", + cmd_output["id"], + ) + self.assertEqual( + name1, + cmd_output["name"], + ) + self.assertEqual( + 123, + cmd_output["ram"], + ) + self.assertEqual( + 20, + cmd_output["disk"], + ) + self.assertEqual( + False, + cmd_output["os-flavor-access:is_public"], + ) + self.assertEqual( + "a='first', b='second'", + cmd_output["properties"], + ) raw_output = self.openstack( - 'flavor set --property e=f --property g=h ' + self.NAME + "flavor set " + + "--property a='third and 10' " + + "--property g=fourth " + + name1 ) self.assertEqual('', raw_output) - raw_output = self.openstack('flavor show ' + self.NAME + opts) - self.assertEqual("a='b', c='d', e='f', g='h'\n", raw_output) + cmd_output = json.loads(self.openstack( + "flavor show -f json " + + name1 + )) + self.assertEqual( + "qaz", + cmd_output["id"], + ) + self.assertEqual( + "a='third and 10', b='second', g='fourth'", + cmd_output['properties'], + ) raw_output = self.openstack( - 'flavor unset --property a --property c ' + self.NAME + "flavor unset " + + "--property b " + + name1 ) self.assertEqual('', raw_output) - raw_output = self.openstack('flavor show ' + self.NAME + opts) - self.assertEqual("e='f', g='h'\n", raw_output) + cmd_output = json.loads(self.openstack( + "flavor show -f json " + + name1 + )) + self.assertEqual( + "a='third and 10', g='fourth'", + cmd_output["properties"], + ) From a6bfea9c6fa16a241b006c9d2c3e714ab8e5c7c3 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Fri, 6 Jan 2017 23:22:19 -0500 Subject: [PATCH 1463/3095] skip tests related to SDK 0912 and keystone IdP change the gate is super wedged, i will skip tests and unskip them as necessary. Change-Id: Ia4469738c876ec1293f91b96dcc7d15365f4f37d --- openstackclient/tests/functional/common/test_quota.py | 6 ++++++ .../tests/functional/identity/v3/test_idp.py | 10 +++++++++- .../tests/functional/network/v2/test_floating_ip.py | 6 ++++++ .../tests/functional/network/v2/test_network.py | 4 ++++ .../tests/functional/network/v2/test_network_agent.py | 4 ++++ .../tests/functional/network/v2/test_port.py | 3 +++ .../tests/functional/network/v2/test_security_group.py | 3 +++ .../functional/network/v2/test_security_group_rule.py | 3 +++ 8 files changed, 38 insertions(+), 1 deletion(-) diff --git a/openstackclient/tests/functional/common/test_quota.py b/openstackclient/tests/functional/common/test_quota.py index c1de9aa92d..fbb8e56367 100644 --- a/openstackclient/tests/functional/common/test_quota.py +++ b/openstackclient/tests/functional/common/test_quota.py @@ -10,6 +10,8 @@ # License for the specific language governing permissions and limitations # under the License. +import testtools + from openstackclient.tests.functional import base @@ -25,6 +27,7 @@ def setUpClass(cls): cls.PROJECT_NAME =\ cls.get_openstack_configuration_value('auth.project_name') + @testtools.skip('broken SDK testing') def test_quota_set(self): self.openstack('quota set --instances 11 --volumes 11 --networks 11 ' + self.PROJECT_NAME) @@ -32,16 +35,19 @@ def test_quota_set(self): raw_output = self.openstack('quota show ' + self.PROJECT_NAME + opts) self.assertEqual("11\n11\n11\n", raw_output) + @testtools.skip('broken SDK testing') def test_quota_show(self): raw_output = self.openstack('quota show ' + self.PROJECT_NAME) for expected_field in self.EXPECTED_FIELDS: self.assertIn(expected_field, raw_output) + @testtools.skip('broken SDK testing') def test_quota_show_default_project(self): raw_output = self.openstack('quota show') for expected_field in self.EXPECTED_FIELDS: self.assertIn(expected_field, raw_output) + @testtools.skip('broken SDK testing') def test_quota_show_with_default_option(self): raw_output = self.openstack('quota show --default') for expected_field in self.EXPECTED_FIELDS: diff --git a/openstackclient/tests/functional/identity/v3/test_idp.py b/openstackclient/tests/functional/identity/v3/test_idp.py index f9d8cb8031..a81cafa9fb 100644 --- a/openstackclient/tests/functional/identity/v3/test_idp.py +++ b/openstackclient/tests/functional/identity/v3/test_idp.py @@ -10,22 +10,27 @@ # License for the specific language governing permissions and limitations # under the License. -from openstackclient.tests.functional.identity.v3 import common from tempest.lib.common.utils import data_utils +import testtools + +from openstackclient.tests.functional.identity.v3 import common class IdentityProviderTests(common.IdentityTests): # Introduce functional test case for command 'Identity Provider' + @testtools.skip('domain resource changed') def test_idp_create(self): self._create_dummy_idp() + @testtools.skip('domain resource changed') def test_idp_delete(self): identity_provider = self._create_dummy_idp(add_clean_up=False) raw_output = self.openstack('identity provider delete %s' % identity_provider) self.assertEqual(0, len(raw_output)) + @testtools.skip('domain resource changed') def test_idp_multi_delete(self): idp_1 = self._create_dummy_idp(add_clean_up=False) idp_2 = self._create_dummy_idp(add_clean_up=False) @@ -33,6 +38,7 @@ def test_idp_multi_delete(self): 'identity provider delete %s %s' % (idp_1, idp_2)) self.assertEqual(0, len(raw_output)) + @testtools.skip('domain resource changed') def test_idp_show(self): identity_provider = self._create_dummy_idp(add_clean_up=True) raw_output = self.openstack('identity provider show %s' @@ -40,12 +46,14 @@ def test_idp_show(self): items = self.parse_show(raw_output) self.assert_show_fields(items, self.IDENTITY_PROVIDER_FIELDS) + @testtools.skip('domain resource changed') def test_idp_list(self): self._create_dummy_idp(add_clean_up=True) raw_output = self.openstack('identity provider list') items = self.parse_listing(raw_output) self.assert_table_structure(items, self.IDENTITY_PROVIDER_LIST_HEADERS) + @testtools.skip('domain resource changed') def test_idp_set(self): identity_provider = self._create_dummy_idp(add_clean_up=True) new_remoteid = data_utils.rand_name('newRemoteId') diff --git a/openstackclient/tests/functional/network/v2/test_floating_ip.py b/openstackclient/tests/functional/network/v2/test_floating_ip.py index f0b12bc7a8..5f642f0415 100644 --- a/openstackclient/tests/functional/network/v2/test_floating_ip.py +++ b/openstackclient/tests/functional/network/v2/test_floating_ip.py @@ -14,6 +14,8 @@ import re import uuid +import testtools + from openstackclient.tests.functional import base @@ -23,6 +25,7 @@ class FloatingIpTests(base.TestCase): NETWORK_NAME = uuid.uuid4().hex @classmethod + @testtools.skip('broken SDK testing') def setUpClass(cls): # Set up some regex for matching below cls.re_id = re.compile("id\s+\|\s+(\S+)") @@ -59,6 +62,7 @@ def tearDownClass(cls): raw_output = cls.openstack('network delete ' + cls.NETWORK_NAME) cls.assertOutput('', raw_output) + @testtools.skip('broken SDK testing') def test_floating_ip_delete(self): """Test create, delete multiple""" raw_output = self.openstack( @@ -89,6 +93,7 @@ def test_floating_ip_delete(self): raw_output = self.openstack('floating ip delete ' + ip1 + ' ' + ip2) self.assertOutput('', raw_output) + @testtools.skip('broken SDK testing') def test_floating_ip_list(self): """Test create defaults, list filters, delete""" raw_output = self.openstack( @@ -130,6 +135,7 @@ def test_floating_ip_list(self): # TODO(dtroyer): add more filter tests + @testtools.skip('broken SDK testing') def test_floating_ip_show(self): """Test show""" raw_output = self.openstack( diff --git a/openstackclient/tests/functional/network/v2/test_network.py b/openstackclient/tests/functional/network/v2/test_network.py index 01fb401a44..ef42dcceef 100644 --- a/openstackclient/tests/functional/network/v2/test_network.py +++ b/openstackclient/tests/functional/network/v2/test_network.py @@ -13,6 +13,8 @@ import re import uuid +import testtools + from openstackclient.tests.functional import base @@ -58,6 +60,7 @@ def test_network_delete(self): del_output = self.openstack('network delete ' + name1 + ' ' + name2) self.assertOutput('', del_output) + @testtools.skip('broken SDK testing') def test_network_list(self): """Test create defaults, list filters, delete""" name1 = uuid.uuid4().hex @@ -159,6 +162,7 @@ def test_network_list(self): re.search("\|\s+" + name2 + "\s+\|\s+ACTIVE", raw_output) ) + @testtools.skip('broken SDK testing') def test_network_set(self): """Tests create options, set, show, delete""" name = uuid.uuid4().hex diff --git a/openstackclient/tests/functional/network/v2/test_network_agent.py b/openstackclient/tests/functional/network/v2/test_network_agent.py index dd6112e72e..e99dcef6e7 100644 --- a/openstackclient/tests/functional/network/v2/test_network_agent.py +++ b/openstackclient/tests/functional/network/v2/test_network_agent.py @@ -10,6 +10,8 @@ # License for the specific language governing permissions and limitations # under the License. +import testtools + from openstackclient.tests.functional import base @@ -26,11 +28,13 @@ def test_network_agent_list(cls): # get the list of network agent IDs. cls.IDs = raw_output.split('\n') + @testtools.skip('broken SDK testing') def test_network_agent_show(self): opts = self.get_opts(self.FIELDS) raw_output = self.openstack('network agent show ' + self.IDs[0] + opts) self.assertEqual(self.IDs[0] + "\n", raw_output) + @testtools.skip('broken SDK testing') def test_network_agent_set(self): opts = self.get_opts(['admin_state_up']) self.openstack('network agent set --disable ' + self.IDs[0]) diff --git a/openstackclient/tests/functional/network/v2/test_port.py b/openstackclient/tests/functional/network/v2/test_port.py index decd9553dc..976fbedb8d 100644 --- a/openstackclient/tests/functional/network/v2/test_port.py +++ b/openstackclient/tests/functional/network/v2/test_port.py @@ -12,6 +12,8 @@ import uuid +import testtools + from openstackclient.tests.functional import base @@ -23,6 +25,7 @@ class PortTests(base.TestCase): FIELDS = ['name'] @classmethod + @testtools.skip('broken SDK testing') def setUpClass(cls): # Create a network for the subnet. cls.openstack('network create ' + cls.NETWORK_NAME) diff --git a/openstackclient/tests/functional/network/v2/test_security_group.py b/openstackclient/tests/functional/network/v2/test_security_group.py index debd81df6e..73f6deb3e9 100644 --- a/openstackclient/tests/functional/network/v2/test_security_group.py +++ b/openstackclient/tests/functional/network/v2/test_security_group.py @@ -12,6 +12,8 @@ import uuid +import testtools + from openstackclient.tests.functional import base @@ -23,6 +25,7 @@ class SecurityGroupTests(base.TestCase): FIELDS = ['name'] @classmethod + @testtools.skip('broken SDK testing') def setUpClass(cls): opts = cls.get_opts(cls.FIELDS) raw_output = cls.openstack('security group create ' + cls.NAME + opts) diff --git a/openstackclient/tests/functional/network/v2/test_security_group_rule.py b/openstackclient/tests/functional/network/v2/test_security_group_rule.py index c91de1a570..5d2e584369 100644 --- a/openstackclient/tests/functional/network/v2/test_security_group_rule.py +++ b/openstackclient/tests/functional/network/v2/test_security_group_rule.py @@ -12,6 +12,8 @@ import uuid +import testtools + from openstackclient.tests.functional import base @@ -24,6 +26,7 @@ class SecurityGroupRuleTests(base.TestCase): ID_HEADER = ['ID'] @classmethod + @testtools.skip('broken SDK testing') def setUpClass(cls): # Create the security group to hold the rule. opts = cls.get_opts(cls.NAME_FIELD) From 29146ab684a4cb4fccb647a6b8aa7b0561f15f8b Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Fri, 6 Jan 2017 14:01:05 -0800 Subject: [PATCH 1464/3095] add domain id to expected IdP fields, unskip tests commit Id18b8b2fe853b97631bc990df8188ed64a6e1275 added domain IDs to an Identity provider, our functional tests have a hard match on what to expect when 'showing' an idp, the domain ID was missing. Change-Id: I87a1fd762918551c533668a9aa94f7c6268b79d6 --- openstackclient/tests/functional/identity/v3/common.py | 3 ++- openstackclient/tests/functional/identity/v3/test_idp.py | 7 ------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/openstackclient/tests/functional/identity/v3/common.py b/openstackclient/tests/functional/identity/v3/common.py index 5dd42e70f8..3b6fc27b97 100644 --- a/openstackclient/tests/functional/identity/v3/common.py +++ b/openstackclient/tests/functional/identity/v3/common.py @@ -42,7 +42,8 @@ class IdentityTests(base.TestCase): ENDPOINT_LIST_HEADERS = ['ID', 'Region', 'Service Name', 'Service Type', 'Enabled', 'Interface', 'URL'] - IDENTITY_PROVIDER_FIELDS = ['description', 'enabled', 'id', 'remote_ids'] + IDENTITY_PROVIDER_FIELDS = ['description', 'enabled', 'id', 'remote_ids', + 'domain_id'] IDENTITY_PROVIDER_LIST_HEADERS = ['ID', 'Enabled', 'Description'] SERVICE_PROVIDER_FIELDS = ['auth_url', 'description', 'enabled', diff --git a/openstackclient/tests/functional/identity/v3/test_idp.py b/openstackclient/tests/functional/identity/v3/test_idp.py index a81cafa9fb..5db3610a5b 100644 --- a/openstackclient/tests/functional/identity/v3/test_idp.py +++ b/openstackclient/tests/functional/identity/v3/test_idp.py @@ -11,7 +11,6 @@ # under the License. from tempest.lib.common.utils import data_utils -import testtools from openstackclient.tests.functional.identity.v3 import common @@ -19,18 +18,15 @@ class IdentityProviderTests(common.IdentityTests): # Introduce functional test case for command 'Identity Provider' - @testtools.skip('domain resource changed') def test_idp_create(self): self._create_dummy_idp() - @testtools.skip('domain resource changed') def test_idp_delete(self): identity_provider = self._create_dummy_idp(add_clean_up=False) raw_output = self.openstack('identity provider delete %s' % identity_provider) self.assertEqual(0, len(raw_output)) - @testtools.skip('domain resource changed') def test_idp_multi_delete(self): idp_1 = self._create_dummy_idp(add_clean_up=False) idp_2 = self._create_dummy_idp(add_clean_up=False) @@ -38,7 +34,6 @@ def test_idp_multi_delete(self): 'identity provider delete %s %s' % (idp_1, idp_2)) self.assertEqual(0, len(raw_output)) - @testtools.skip('domain resource changed') def test_idp_show(self): identity_provider = self._create_dummy_idp(add_clean_up=True) raw_output = self.openstack('identity provider show %s' @@ -46,14 +41,12 @@ def test_idp_show(self): items = self.parse_show(raw_output) self.assert_show_fields(items, self.IDENTITY_PROVIDER_FIELDS) - @testtools.skip('domain resource changed') def test_idp_list(self): self._create_dummy_idp(add_clean_up=True) raw_output = self.openstack('identity provider list') items = self.parse_listing(raw_output) self.assert_table_structure(items, self.IDENTITY_PROVIDER_LIST_HEADERS) - @testtools.skip('domain resource changed') def test_idp_set(self): identity_provider = self._create_dummy_idp(add_clean_up=True) new_remoteid = data_utils.rand_name('newRemoteId') From 27e0be051714fe11a3b9b5306f2e0a72d95fe2c3 Mon Sep 17 00:00:00 2001 From: "jiahui.qiang" Date: Fri, 6 Jan 2017 22:49:41 +0800 Subject: [PATCH 1465/3095] Add --project and --project-domain option to "volume snapshot list" Add "--project" and "--project-domain" option to volume v2's "volume snapshot list" command, it will filter list result by different project. Change-Id: I7dccd6d8d9f1889fa9cb0c2d04a42d77975c645b --- .../command-objects/volume-snapshot.rst | 15 +++++++++++++ .../tests/unit/volume/v2/test_snapshot.py | 15 ++++++++++++- openstackclient/volume/v2/volume_snapshot.py | 22 ++++++++++++++++++- ...napshot_list_project-e7dcc07f98d44182.yaml | 6 +++++ 4 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/volume_snapshot_list_project-e7dcc07f98d44182.yaml diff --git a/doc/source/command-objects/volume-snapshot.rst b/doc/source/command-objects/volume-snapshot.rst index 37a5088a59..3cf46ad552 100644 --- a/doc/source/command-objects/volume-snapshot.rst +++ b/doc/source/command-objects/volume-snapshot.rst @@ -82,6 +82,7 @@ List volume snapshots openstack volume snapshot list [--all-projects] + [--project [--project-domain ]] [--long] [--limit ] [--marker ] @@ -93,6 +94,20 @@ List volume snapshots Include all projects (admin only) +.. option:: --project + + Filter results by project (name or ID) (admin only) + + *Volume version 2 only* + +.. option:: --project-domain + + Domain the project belongs to (name or ID). + + This can be used in case collisions between project names exist. + + *Volume version 2 only* + .. option:: --long List additional fields in output diff --git a/openstackclient/tests/unit/volume/v2/test_snapshot.py b/openstackclient/tests/unit/volume/v2/test_snapshot.py index 8ce356aea8..12d1e3906d 100644 --- a/openstackclient/tests/unit/volume/v2/test_snapshot.py +++ b/openstackclient/tests/unit/volume/v2/test_snapshot.py @@ -19,6 +19,7 @@ from osc_lib import exceptions from osc_lib import utils +from openstackclient.tests.unit.identity.v3 import fakes as project_fakes from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes from openstackclient.volume.v2 import volume_snapshot @@ -32,6 +33,8 @@ def setUp(self): self.snapshots_mock.reset_mock() self.volumes_mock = self.app.client_manager.volume.volumes self.volumes_mock.reset_mock() + self.project_mock = self.app.client_manager.identity.projects + self.project_mock.reset_mock() class TestSnapshotCreate(TestSnapshot): @@ -278,6 +281,7 @@ def test_delete_multiple_snapshots_with_exception(self): class TestSnapshotList(TestSnapshot): volume = volume_fakes.FakeVolume.create_one_volume() + project = project_fakes.FakeProject.create_one_project() snapshots = volume_fakes.FakeSnapshot.create_snapshots( attrs={'volume_id': volume.name}, count=3) @@ -321,6 +325,7 @@ def setUp(self): self.volumes_mock.list.return_value = [self.volume] self.volumes_mock.get.return_value = self.volume + self.project_mock.get.return_value = self.project self.snapshots_mock.list.return_value = self.snapshots # Get the command to test self.cmd = volume_snapshot.ListVolumeSnapshot(self.app, None) @@ -341,6 +346,7 @@ def test_snapshot_list_without_options(self): 'all_tenants': False, 'name': None, 'status': None, + 'project_id': None, 'volume_id': None } ) @@ -351,11 +357,13 @@ def test_snapshot_list_with_options(self): arglist = [ "--long", "--limit", "2", + "--project", self.project.id, "--marker", self.snapshots[0].id, ] verifylist = [ ("long", True), ("limit", 2), + ("project", self.project.id), ("marker", self.snapshots[0].id), ('all_projects', False), ] @@ -367,7 +375,8 @@ def test_snapshot_list_with_options(self): limit=2, marker=self.snapshots[0].id, search_opts={ - 'all_tenants': False, + 'all_tenants': True, + 'project_id': self.project.id, 'name': None, 'status': None, 'volume_id': None @@ -394,6 +403,7 @@ def test_snapshot_list_all_projects(self): 'all_tenants': True, 'name': None, 'status': None, + 'project_id': None, 'volume_id': None } ) @@ -419,6 +429,7 @@ def test_snapshot_list_name_option(self): 'all_tenants': False, 'name': self.snapshots[0].name, 'status': None, + 'project_id': None, 'volume_id': None } ) @@ -444,6 +455,7 @@ def test_snapshot_list_status_option(self): 'all_tenants': False, 'name': None, 'status': self.snapshots[0].status, + 'project_id': None, 'volume_id': None } ) @@ -469,6 +481,7 @@ def test_snapshot_list_volumeid_option(self): 'all_tenants': False, 'name': None, 'status': None, + 'project_id': None, 'volume_id': self.volume.id } ) diff --git a/openstackclient/volume/v2/volume_snapshot.py b/openstackclient/volume/v2/volume_snapshot.py index af29b777dc..3283bb5355 100644 --- a/openstackclient/volume/v2/volume_snapshot.py +++ b/openstackclient/volume/v2/volume_snapshot.py @@ -24,6 +24,7 @@ import six from openstackclient.i18n import _ +from openstackclient.identity import common as identity_common LOG = logging.getLogger(__name__) @@ -165,6 +166,12 @@ def get_parser(self, prog_name): default=False, help=_('Include all projects (admin only)'), ) + parser.add_argument( + '--project', + metavar='', + help=_('Filter results by project (name or ID) (admin only)') + ) + identity_common.add_project_domain_option_to_parser(parser) parser.add_argument( '--long', action='store_true', @@ -208,6 +215,7 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): volume_client = self.app.client_manager.volume + identity_client = self.app.client_manager.identity def _format_volume_id(volume_id): """Return a volume name if available @@ -245,8 +253,20 @@ def _format_volume_id(volume_id): volume_id = utils.find_resource( volume_client.volumes, parsed_args.volume).id + project_id = None + if parsed_args.project: + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain).id + + # set value of 'all_tenants' when using project option + all_projects = True if parsed_args.project else \ + parsed_args.all_projects + search_opts = { - 'all_tenants': parsed_args.all_projects, + 'all_tenants': all_projects, + 'project_id': project_id, 'name': parsed_args.name, 'status': parsed_args.status, 'volume_id': volume_id, diff --git a/releasenotes/notes/volume_snapshot_list_project-e7dcc07f98d44182.yaml b/releasenotes/notes/volume_snapshot_list_project-e7dcc07f98d44182.yaml new file mode 100644 index 0000000000..6cedb545f1 --- /dev/null +++ b/releasenotes/notes/volume_snapshot_list_project-e7dcc07f98d44182.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--project`` and ``--project-domain`` option to + ``volume snapshot list`` command, in order to filter list result + by different project. From 51ea68ae948da5d69b262827961ca9ae9118edbc Mon Sep 17 00:00:00 2001 From: "jiahui.qiang" Date: Thu, 5 Jan 2017 14:26:16 +0800 Subject: [PATCH 1466/3095] Fix filter error in os volume list This patch fixed a bug of unable to filter volume list by '--project', '--user' in "openstack volume list". Modify uint test for 'volume list' to check parameter of list method. Change-Id: I1fc4296c4c7eca0f7a803dbfd5e15e3bc0d6403f --- .../tests/unit/volume/v2/test_volume.py | 117 ++++++++++++++++++ openstackclient/volume/v2/volume.py | 9 +- ...me-list-with-project-2dc867c5e8346a66.yaml | 5 + 3 files changed, 128 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/bug-volume-list-with-project-2dc867c5e8346a66.yaml diff --git a/openstackclient/tests/unit/volume/v2/test_volume.py b/openstackclient/tests/unit/volume/v2/test_volume.py index 1529c2e25b..4fef9dd9a6 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume.py +++ b/openstackclient/tests/unit/volume/v2/test_volume.py @@ -823,6 +823,19 @@ def test_volume_list_no_options(self): columns, data = self.cmd.take_action(parsed_args) + search_opts = { + 'all_tenants': False, + 'project_id': None, + 'user_id': None, + 'display_name': None, + 'status': None, + } + self.volumes_mock.list.assert_called_once_with( + search_opts=search_opts, + marker=None, + limit=None, + ) + self.assertEqual(self.columns, columns) server = self.mock_volume.attachments[0]['server_id'] @@ -853,6 +866,19 @@ def test_volume_list_project(self): columns, data = self.cmd.take_action(parsed_args) + search_opts = { + 'all_tenants': True, + 'project_id': self.project.id, + 'user_id': None, + 'display_name': None, + 'status': None, + } + self.volumes_mock.list.assert_called_once_with( + search_opts=search_opts, + marker=None, + limit=None, + ) + self.assertEqual(self.columns, columns) server = self.mock_volume.attachments[0]['server_id'] @@ -885,6 +911,19 @@ def test_volume_list_project_domain(self): columns, data = self.cmd.take_action(parsed_args) + search_opts = { + 'all_tenants': True, + 'project_id': self.project.id, + 'user_id': None, + 'display_name': None, + 'status': None, + } + self.volumes_mock.list.assert_called_once_with( + search_opts=search_opts, + marker=None, + limit=None, + ) + self.assertEqual(self.columns, columns) server = self.mock_volume.attachments[0]['server_id'] @@ -915,6 +954,19 @@ def test_volume_list_user(self): columns, data = self.cmd.take_action(parsed_args) + search_opts = { + 'all_tenants': False, + 'project_id': None, + 'user_id': self.user.id, + 'display_name': None, + 'status': None, + } + self.volumes_mock.list.assert_called_once_with( + search_opts=search_opts, + marker=None, + limit=None, + ) + self.assertEqual(self.columns, columns) server = self.mock_volume.attachments[0]['server_id'] device = self.mock_volume.attachments[0]['device'] @@ -946,6 +998,19 @@ def test_volume_list_user_domain(self): columns, data = self.cmd.take_action(parsed_args) + search_opts = { + 'all_tenants': False, + 'project_id': None, + 'user_id': self.user.id, + 'display_name': None, + 'status': None, + } + self.volumes_mock.list.assert_called_once_with( + search_opts=search_opts, + marker=None, + limit=None, + ) + self.assertEqual(self.columns, columns) server = self.mock_volume.attachments[0]['server_id'] @@ -976,6 +1041,19 @@ def test_volume_list_name(self): columns, data = self.cmd.take_action(parsed_args) + search_opts = { + 'all_tenants': False, + 'project_id': None, + 'user_id': None, + 'display_name': self.mock_volume.name, + 'status': None, + } + self.volumes_mock.list.assert_called_once_with( + search_opts=search_opts, + marker=None, + limit=None, + ) + self.assertEqual(self.columns, columns) server = self.mock_volume.attachments[0]['server_id'] @@ -1006,6 +1084,19 @@ def test_volume_list_status(self): columns, data = self.cmd.take_action(parsed_args) + search_opts = { + 'all_tenants': False, + 'project_id': None, + 'user_id': None, + 'display_name': None, + 'status': self.mock_volume.status, + } + self.volumes_mock.list.assert_called_once_with( + search_opts=search_opts, + marker=None, + limit=None, + ) + self.assertEqual(self.columns, columns) server = self.mock_volume.attachments[0]['server_id'] @@ -1036,6 +1127,19 @@ def test_volume_list_all_projects(self): columns, data = self.cmd.take_action(parsed_args) + search_opts = { + 'all_tenants': True, + 'project_id': None, + 'user_id': None, + 'display_name': None, + 'status': None, + } + self.volumes_mock.list.assert_called_once_with( + search_opts=search_opts, + marker=None, + limit=None, + ) + self.assertEqual(self.columns, columns) server = self.mock_volume.attachments[0]['server_id'] @@ -1067,6 +1171,19 @@ def test_volume_list_long(self): columns, data = self.cmd.take_action(parsed_args) + search_opts = { + 'all_tenants': False, + 'project_id': None, + 'user_id': None, + 'display_name': None, + 'status': None, + } + self.volumes_mock.list.assert_called_once_with( + search_opts=search_opts, + marker=None, + limit=None, + ) + collist = [ 'ID', 'Display Name', diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index 301bf5e41f..78db261bd1 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -417,16 +417,19 @@ def _format_attach(attachments): project_id = identity_common.find_project( identity_client, parsed_args.project, - parsed_args.project_domain) + parsed_args.project_domain).id user_id = None if parsed_args.user: user_id = identity_common.find_user(identity_client, parsed_args.user, - parsed_args.user_domain) + parsed_args.user_domain).id + + # set value of 'all_tenants' when using project option + all_projects = bool(parsed_args.project) or parsed_args.all_projects search_opts = { - 'all_tenants': parsed_args.all_projects, + 'all_tenants': all_projects, 'project_id': project_id, 'user_id': user_id, 'display_name': parsed_args.name, diff --git a/releasenotes/notes/bug-volume-list-with-project-2dc867c5e8346a66.yaml b/releasenotes/notes/bug-volume-list-with-project-2dc867c5e8346a66.yaml new file mode 100644 index 0000000000..f2245201b6 --- /dev/null +++ b/releasenotes/notes/bug-volume-list-with-project-2dc867c5e8346a66.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Fix a bug of unable to filter volume list by ``--project`` + and ``--user`` options in the ``openstack volume list``. From 8e277c64fb86dc1ca432e02b82accdcaf42a2779 Mon Sep 17 00:00:00 2001 From: "jiahui.qiang" Date: Mon, 9 Jan 2017 10:43:27 +0800 Subject: [PATCH 1467/3095] Fix port command for SDK >0.9.10 The port functional test can not be passed in my local environment. When 'dns_assignment' is None, the port create, show command will fail because parameter for 'utils.format_list_of_dicts' can not be None. Change-Id: Iebf16fb7ca681660c2b9ee7839a0629f38c6a38a --- openstackclient/network/v2/port.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 4525da1800..20f3ad75ec 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -35,6 +35,10 @@ def _format_admin_state(state): return 'UP' if state else 'DOWN' +def _format_dns_assignment(dns_assignment): + return utils.format_list_of_dicts(dns_assignment) \ + if dns_assignment else None + _formatters = { 'admin_state_up': _format_admin_state, 'is_admin_state_up': _format_admin_state, @@ -43,7 +47,7 @@ def _format_admin_state(state): 'binding_vif_details': utils.format_dict, 'binding:profile': utils.format_dict, 'binding:vif_details': utils.format_dict, - 'dns_assignment': utils.format_list_of_dicts, + 'dns_assignment': _format_dns_assignment, 'extra_dhcp_opts': utils.format_list_of_dicts, 'fixed_ips': utils.format_list_of_dicts, 'security_group_ids': utils.format_list, From 07ade2266c80e47541f9e621e90e337cff69d56b Mon Sep 17 00:00:00 2001 From: "jiahui.qiang" Date: Fri, 6 Jan 2017 18:35:28 +0800 Subject: [PATCH 1468/3095] functional test for volume Rework functional tests to remove resource create/delete from setupClass() and teardownClass() methods. Add test for more command options Change-Id: I2b6ad1fce26f04c11ed43f8d73515fde4a7d09af --- .../tests/functional/volume/v2/test_volume.py | 305 ++++++++++++------ 1 file changed, 204 insertions(+), 101 deletions(-) diff --git a/openstackclient/tests/functional/volume/v2/test_volume.py b/openstackclient/tests/functional/volume/v2/test_volume.py index ea891cba55..203ca819a5 100644 --- a/openstackclient/tests/functional/volume/v2/test_volume.py +++ b/openstackclient/tests/functional/volume/v2/test_volume.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import json import time import uuid @@ -19,118 +20,220 @@ class VolumeTests(common.BaseVolumeTests): """Functional tests for volume. """ - NAME = uuid.uuid4().hex - SNAPSHOT_NAME = uuid.uuid4().hex - VOLUME_FROM_SNAPSHOT_NAME = uuid.uuid4().hex - OTHER_NAME = uuid.uuid4().hex - HEADERS = ['"Display Name"'] - FIELDS = ['name'] - - @classmethod - def setUpClass(cls): - super(VolumeTests, cls).setUpClass() - opts = cls.get_opts(cls.FIELDS) - - # Create test volume - raw_output = cls.openstack('volume create --size 1 ' + cls.NAME + opts) - expected = cls.NAME + '\n' - cls.assertOutput(expected, raw_output) - - @classmethod - def tearDownClass(cls): - # Rename test volume - raw_output = cls.openstack( - 'volume set --name ' + cls.OTHER_NAME + ' ' + cls.NAME) - cls.assertOutput('', raw_output) - - # Set volume state - cls.openstack('volume set --state error ' + cls.OTHER_NAME) - opts = cls.get_opts(["status"]) - raw_output_status = cls.openstack( - 'volume show ' + cls.OTHER_NAME + opts) - - # Delete test volume - raw_output = cls.openstack('volume delete ' + cls.OTHER_NAME) - cls.assertOutput('', raw_output) - cls.assertOutput('error\n', raw_output_status) + def test_volume_delete(self): + """Test create, delete multiple""" + name1 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume create -f json ' + + '--size 1 ' + + name1 + )) + self.assertEqual( + 1, + cmd_output["size"], + ) + + name2 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume create -f json ' + + '--size 2 ' + + name2 + )) + self.assertEqual( + 2, + cmd_output["size"], + ) + + self.wait_for("volume", name1, "available") + self.wait_for("volume", name2, "available") + del_output = self.openstack('volume delete ' + name1 + ' ' + name2) + self.assertOutput('', del_output) def test_volume_list(self): - opts = self.get_opts(self.HEADERS) - raw_output = self.openstack('volume list' + opts) - self.assertIn(self.NAME, raw_output) - - def test_volume_show(self): - opts = self.get_opts(self.FIELDS) - raw_output = self.openstack('volume show ' + self.NAME + opts) - self.assertEqual(self.NAME + "\n", raw_output) - - def test_volume_properties(self): + """Test create, list filter""" + name1 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume create -f json ' + + '--size 1 ' + + name1 + )) + self.addCleanup(self.openstack, 'volume delete ' + name1) + self.assertEqual( + 1, + cmd_output["size"], + ) + self.wait_for("volume", name1, "available") + + name2 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume create -f json ' + + '--size 2 ' + + name2 + )) + self.addCleanup(self.openstack, 'volume delete ' + name2) + self.assertEqual( + 2, + cmd_output["size"], + ) + self.wait_for("volume", name2, "available") raw_output = self.openstack( - 'volume set --property a=b --property c=d ' + self.NAME) - self.assertEqual("", raw_output) - opts = self.get_opts(["properties"]) - raw_output = self.openstack('volume show ' + self.NAME + opts) - self.assertEqual("a='b', c='d'\n", raw_output) + 'volume set ' + + '--state error ' + + name2 + ) + self.assertOutput('', raw_output) - raw_output = self.openstack('volume unset --property a ' + self.NAME) - self.assertEqual("", raw_output) - raw_output = self.openstack('volume show ' + self.NAME + opts) - self.assertEqual("c='d'\n", raw_output) + # Test list --long + cmd_output = json.loads(self.openstack( + 'volume list -f json ' + + '--long' + )) + names = [x["Display Name"] for x in cmd_output] + self.assertIn(name1, names) + self.assertIn(name2, names) + + # Test list --status + cmd_output = json.loads(self.openstack( + 'volume list -f json ' + + '--status error' + )) + names = [x["Display Name"] for x in cmd_output] + self.assertNotIn(name1, names) + self.assertIn(name2, names) + + # TODO(qiangjiahui): Add project option to filter tests when we can + # specify volume with project def test_volume_set(self): - discription = uuid.uuid4().hex - self.openstack('volume set --description ' + discription + ' ' + - self.NAME) - opts = self.get_opts(["description", "name"]) - raw_output = self.openstack('volume show ' + self.NAME + opts) - self.assertEqual(discription + "\n" + self.NAME + "\n", raw_output) - - def test_volume_set_size(self): - self.openstack('volume set --size 2 ' + self.NAME) - opts = self.get_opts(["name", "size"]) - raw_output = self.openstack('volume show ' + self.NAME + opts) - self.assertEqual(self.NAME + "\n2\n", raw_output) - - def test_volume_set_bootable(self): - self.openstack('volume set --bootable ' + self.NAME) - opts = self.get_opts(["bootable"]) - raw_output = self.openstack('volume show ' + self.NAME + opts) - self.assertEqual("true\n", raw_output) - - self.openstack('volume set --non-bootable ' + self.NAME) - opts = self.get_opts(["bootable"]) - raw_output = self.openstack('volume show ' + self.NAME + opts) - self.assertEqual("false\n", raw_output) + """Tests create volume, set, unset, show, delete""" + name = uuid.uuid4().hex + new_name = name + "_" + cmd_output = json.loads(self.openstack( + 'volume create -f json ' + + '--size 1 ' + + '--description aaaa ' + + '--property Alpha=a ' + + name + )) + self.addCleanup(self.openstack, 'volume delete ' + new_name) + self.assertEqual( + name, + cmd_output["name"], + ) + self.assertEqual( + 1, + cmd_output["size"], + ) + self.assertEqual( + 'aaaa', + cmd_output["description"], + ) + self.assertEqual( + "Alpha='a'", + cmd_output["properties"], + ) + self.assertEqual( + 'false', + cmd_output["bootable"], + ) + self.wait_for("volume", name, "available") + + # Test volume set + raw_output = self.openstack( + 'volume set ' + + '--name ' + new_name + + ' --size 2 ' + + '--description bbbb ' + + '--property Alpha=c ' + + '--property Beta=b ' + + '--bootable ' + + name, + ) + self.assertOutput('', raw_output) - def test_volume_snapshot(self): - opts = self.get_opts(self.FIELDS) - - # Create snapshot from test volume - raw_output = self.openstack('volume snapshot create ' + - self.SNAPSHOT_NAME + - ' --volume ' + self.NAME + opts) - expected = self.SNAPSHOT_NAME + '\n' - self.assertOutput(expected, raw_output) - self.wait_for("volume snapshot", self.SNAPSHOT_NAME, "available") - - # Create volume from snapshot - raw_output = self.openstack('volume create --size 2 --snapshot ' + - self.SNAPSHOT_NAME + ' ' + - self.VOLUME_FROM_SNAPSHOT_NAME + opts) - expected = self.VOLUME_FROM_SNAPSHOT_NAME + '\n' - self.assertOutput(expected, raw_output) - self.wait_for("volume", self.VOLUME_FROM_SNAPSHOT_NAME, "available") - - # Delete volume that create from snapshot - raw_output = self.openstack('volume delete ' + - self.VOLUME_FROM_SNAPSHOT_NAME) + cmd_output = json.loads(self.openstack( + 'volume show -f json ' + + new_name + )) + self.assertEqual( + new_name, + cmd_output["name"], + ) + self.assertEqual( + 2, + cmd_output["size"], + ) + self.assertEqual( + 'bbbb', + cmd_output["description"], + ) + self.assertEqual( + "Alpha='c', Beta='b'", + cmd_output["properties"], + ) + self.assertEqual( + 'true', + cmd_output["bootable"], + ) + + # Test volume unset + raw_output = self.openstack( + 'volume unset ' + + '--property Alpha ' + + new_name, + ) self.assertOutput('', raw_output) - # Delete test snapshot + cmd_output = json.loads(self.openstack( + 'volume show -f json ' + + new_name + )) + self.assertEqual( + "Beta='b'", + cmd_output["properties"], + ) + + def test_volume_snapshot(self): + """Tests volume create from snapshot""" + + volume_name = uuid.uuid4().hex + snapshot_name = uuid.uuid4().hex + # Make a snapshot + cmd_output = json.loads(self.openstack( + 'volume create -f json ' + + '--size 1 ' + + volume_name + )) + self.wait_for("volume", volume_name, "available") + self.assertEqual( + volume_name, + cmd_output["name"], + ) + cmd_output = json.loads(self.openstack( + 'volume snapshot create -f json ' + + snapshot_name + + ' --volume ' + volume_name + )) + self.wait_for("volume snapshot", snapshot_name, "available") + + name = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume create -f json ' + + '--snapshot ' + snapshot_name + + ' ' + name + )) + self.addCleanup(self.openstack, 'volume delete ' + name) + self.addCleanup(self.openstack, 'volume delete ' + volume_name) + self.assertEqual( + name, + cmd_output["name"], + ) + self.wait_for("volume", name, "available") + + # Delete snapshot raw_output = self.openstack( - 'volume snapshot delete ' + self.SNAPSHOT_NAME) + 'volume snapshot delete ' + snapshot_name) self.assertOutput('', raw_output) - self.wait_for("volume", self.NAME, "available") def wait_for(self, check_type, check_name, desired_status, wait=120, interval=5, failures=['ERROR']): From 142c5faae38c0619d218e0113644d6fa7bea76a8 Mon Sep 17 00:00:00 2001 From: Nir Magnezi Date: Mon, 9 Jan 2017 12:31:47 +0200 Subject: [PATCH 1469/3095] Fix Octavia gate breakage caused by _get_columns() The above mentioned function tries to extract keys() from an item which is type class. This patch fixes the issue by converting item to dict by using to_dict(). Change-Id: Ida520ae9fe64171d105f486ba06eda127a24547b Closes-Bug: #1654887 --- openstackclient/network/v2/security_group.py | 6 +++--- .../tests/functional/network/v2/test_security_group.py | 3 --- .../tests/functional/network/v2/test_security_group_rule.py | 2 +- openstackclient/tests/unit/fakes.py | 3 +++ 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py index 5420bc8ba7..df9e320b10 100644 --- a/openstackclient/network/v2/security_group.py +++ b/openstackclient/network/v2/security_group.py @@ -74,7 +74,7 @@ def _format_compute_security_group_rules(sg_rules): def _get_columns(item): # Build the display columns and a list of the property columns # that need to be mapped (display column name, property name). - columns = list(item.keys()) + columns = list(item.to_dict().keys()) property_column_mappings = [] if 'security_group_rules' in columns: columns.append('rules') @@ -156,7 +156,7 @@ def take_action_compute(self, client, parsed_args): parsed_args.name, description, ) - display_columns, property_columns = _get_columns(obj._info) + display_columns, property_columns = _get_columns(obj) data = utils.get_dict_properties( obj._info, property_columns, @@ -336,7 +336,7 @@ def take_action_compute(self, client, parsed_args): client.security_groups, parsed_args.group, ) - display_columns, property_columns = _get_columns(obj._info) + display_columns, property_columns = _get_columns(obj) data = utils.get_dict_properties( obj._info, property_columns, diff --git a/openstackclient/tests/functional/network/v2/test_security_group.py b/openstackclient/tests/functional/network/v2/test_security_group.py index 73f6deb3e9..debd81df6e 100644 --- a/openstackclient/tests/functional/network/v2/test_security_group.py +++ b/openstackclient/tests/functional/network/v2/test_security_group.py @@ -12,8 +12,6 @@ import uuid -import testtools - from openstackclient.tests.functional import base @@ -25,7 +23,6 @@ class SecurityGroupTests(base.TestCase): FIELDS = ['name'] @classmethod - @testtools.skip('broken SDK testing') def setUpClass(cls): opts = cls.get_opts(cls.FIELDS) raw_output = cls.openstack('security group create ' + cls.NAME + opts) diff --git a/openstackclient/tests/functional/network/v2/test_security_group_rule.py b/openstackclient/tests/functional/network/v2/test_security_group_rule.py index 5d2e584369..ec3731eb9a 100644 --- a/openstackclient/tests/functional/network/v2/test_security_group_rule.py +++ b/openstackclient/tests/functional/network/v2/test_security_group_rule.py @@ -26,7 +26,6 @@ class SecurityGroupRuleTests(base.TestCase): ID_HEADER = ['ID'] @classmethod - @testtools.skip('broken SDK testing') def setUpClass(cls): # Create the security group to hold the rule. opts = cls.get_opts(cls.NAME_FIELD) @@ -55,6 +54,7 @@ def tearDownClass(cls): cls.SECURITY_GROUP_NAME) cls.assertOutput('', raw_output) + @testtools.skip('broken SDK testing') def test_security_group_rule_list(self): opts = self.get_opts(self.ID_HEADER) raw_output = self.openstack('security group rule list ' + diff --git a/openstackclient/tests/unit/fakes.py b/openstackclient/tests/unit/fakes.py index f7cb567644..ca6b1d31f1 100644 --- a/openstackclient/tests/unit/fakes.py +++ b/openstackclient/tests/unit/fakes.py @@ -212,6 +212,9 @@ def __repr__(self): def keys(self): return self._info.keys() + def to_dict(self): + return self._info + @property def info(self): return self._info From 1cdc1319d6cbfc4087551e5bf0a9875c016eca1c Mon Sep 17 00:00:00 2001 From: Jordan Pittier Date: Mon, 9 Jan 2017 15:40:59 +0100 Subject: [PATCH 1470/3095] Make 'object save' fast again 'openstack object save' is critically slow to download big objects. While we 'stream' (chunked download) the data, the default chunks_size is 1 byte [1], which is terribly inefficient. [1] : http://docs.python-requests.org/en/master/api/#requests.Response.iter_content Closes-Bug: 1654645 Change-Id: I2223e5897346acd2f2c1fae638d1193cff833c19 --- openstackclient/api/object_store_v1.py | 2 +- releasenotes/notes/speedup-object-save-6bd59e678a31c3e8.yaml | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/speedup-object-save-6bd59e678a31c3e8.yaml diff --git a/openstackclient/api/object_store_v1.py b/openstackclient/api/object_store_v1.py index 184814c610..74c4a46f20 100644 --- a/openstackclient/api/object_store_v1.py +++ b/openstackclient/api/object_store_v1.py @@ -380,7 +380,7 @@ def object_save( if len(os.path.dirname(file)) > 0: os.makedirs(os.path.dirname(file)) with open(file, 'wb') as f: - for chunk in response.iter_content(): + for chunk in response.iter_content(64 * 1024): f.write(chunk) def object_set( diff --git a/releasenotes/notes/speedup-object-save-6bd59e678a31c3e8.yaml b/releasenotes/notes/speedup-object-save-6bd59e678a31c3e8.yaml new file mode 100644 index 0000000000..b7ad094023 --- /dev/null +++ b/releasenotes/notes/speedup-object-save-6bd59e678a31c3e8.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Makes ``openstack object save`` much faster when saving an object to disk. + [Bug `1654645 `_] From 6b114cd98f4a1bc95cb8702db02eeb625be6b3e7 Mon Sep 17 00:00:00 2001 From: Rodolfo Alonso Hernandez Date: Fri, 5 Aug 2016 09:57:24 +0100 Subject: [PATCH 1471/3095] Add support for Network QoS rule commands Added following commands: - network qos rule create --type minimum-bandwidth dscp-marking limit-bandwidth - network qos rule delete - network qos rule list - network qos rule set - network qos rule show Closes-Bug: 1609472 Depends-On: I2e8869750024a8ccbc7777b95fe8ef6e26ec0885 Depends-On: Ife549ff6499217ca65e2554be8ef86ea7866b2d8 Change-Id: Ib3e1951f0917f5f23c8d9e0a380d19da2b3af5f0 --- .../command-objects/network-qos-rule.rst | 165 +++ doc/source/commands.rst | 1 + .../network/v2/network_qos_rule.py | 356 +++++++ openstackclient/tests/functional/base.py | 5 + .../network/v2/test_network_qos_rule.py | 181 ++++ openstackclient/tests/unit/fakes.py | 6 + .../tests/unit/network/v2/fakes.py | 162 +-- .../unit/network/v2/test_network_qos_rule.py | 997 ++++++++++++++++++ ...add-network-qos-rule-22cc1ddd509941db.yaml | 8 + setup.cfg | 6 + 10 files changed, 1814 insertions(+), 73 deletions(-) create mode 100644 doc/source/command-objects/network-qos-rule.rst create mode 100644 openstackclient/network/v2/network_qos_rule.py create mode 100644 openstackclient/tests/functional/network/v2/test_network_qos_rule.py create mode 100644 openstackclient/tests/unit/network/v2/test_network_qos_rule.py create mode 100644 releasenotes/notes/add-network-qos-rule-22cc1ddd509941db.yaml diff --git a/doc/source/command-objects/network-qos-rule.rst b/doc/source/command-objects/network-qos-rule.rst new file mode 100644 index 0000000000..b98244b1c3 --- /dev/null +++ b/doc/source/command-objects/network-qos-rule.rst @@ -0,0 +1,165 @@ +================ +network qos rule +================ + +A **Network QoS rule** specifies a rule defined in a Network QoS policy; its +type is defined by the parameter 'type'. Can be assigned, within a Network QoS +policy, to a port or a network. Each Network QoS policy can contain several +rules, each of them + +Network v2 + +network qos rule create +----------------------- + +Create new Network QoS rule + +.. program:: network qos rule create +.. code:: bash + + os network qos rule create + --type + [--max-kbps ] + [--max-burst-kbits ] + [--dscp-marks ] + [--min-kbps ] + [--ingress | --egress] + + +.. option:: --type + + QoS rule type (minimum-bandwidth, dscp-marking, bandwidth-limit) + +.. option:: --max-kbps + + Maximum bandwidth in kbps + +.. option:: --max-burst-kbits + + Maximum burst in kilobits, 0 means automatic + +.. option:: --dscp-mark + + DSCP mark: value can be 0, even numbers from 8-56, excluding 42, 44, 50, + 52, and 54 + +.. option:: --min-kbps + + Minimum guaranteed bandwidth in kbps + +.. option:: --ingress + + Ingress traffic direction from the project point of view + +.. option:: --egress + + Egress traffic direction from the project point of view + +.. describe:: + + QoS policy that contains the rule (name or ID) + +network qos rule delete +----------------------- + +Delete Network QoS rule + +.. program:: network qos rule delete +.. code:: bash + + os network qos rule delete + + + +.. describe:: + + QoS policy that contains the rule (name or ID) + +.. describe:: + + Network QoS rule to delete (ID) + +network qos rule list +--------------------- + +List Network QoS rules + +.. program:: network qos rule list +.. code:: bash + + os network qos rule list + + +.. describe:: + + QoS policy that contains the rule (name or ID) + +network qos rule set +-------------------- + +Set Network QoS rule properties + +.. program:: network qos rule set +.. code:: bash + + os network qos rule set + [--max-kbps ] + [--max-burst-kbits ] + [--dscp-marks ] + [--min-kbps ] + [--ingress | --egress] + + + +.. option:: --max-kbps + + Maximum bandwidth in kbps + +.. option:: --max-burst-kbits + + Maximum burst in kilobits, 0 means automatic + +.. option:: --dscp-mark + + DSCP mark: value can be 0, even numbers from 8-56, excluding 42, 44, 50, + 52, and 54 + +.. option:: --min-kbps + + Minimum guaranteed bandwidth in kbps + +.. option:: --ingress + + Ingress traffic direction from the project point of view + +.. option:: --egress + + Egress traffic direction from the project point of view + +.. describe:: + + QoS policy that contains the rule (name or ID) + +.. describe:: + + Network QoS rule to delete (ID) + +network qos rule show +--------------------- + +Display Network QoS rule details + +.. program:: network qos rule show +.. code:: bash + + os network qos rule show + + + +.. describe:: + + QoS policy that contains the rule (name or ID) + +.. describe:: + + Network QoS rule to delete (ID) diff --git a/doc/source/commands.rst b/doc/source/commands.rst index b81c121bef..78668a0f22 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -113,6 +113,7 @@ referring to both Compute and Volume quotas. * ``network agent``: (**Network**) - A network agent is an agent that handles various tasks used to implement virtual networks * ``network meter``: (**Network**) - allow traffic metering in a network * ``network rbac``: (**Network**) - an RBAC policy for network resources +* ``network qos rule``: (**Network**) - a QoS rule for network resources * ``network qos policy``: (**Network**) - a QoS policy for network resources * ``network qos rule type``: (**Network**) - list of QoS available rule types * ``network segment``: (**Network**) - a segment of a virtual network diff --git a/openstackclient/network/v2/network_qos_rule.py b/openstackclient/network/v2/network_qos_rule.py new file mode 100644 index 0000000000..054d16b4ab --- /dev/null +++ b/openstackclient/network/v2/network_qos_rule.py @@ -0,0 +1,356 @@ +# 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 itertools +import logging +import six + +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils + +from openstackclient.i18n import _ +from openstackclient.network import sdk_utils + + +LOG = logging.getLogger(__name__) + +RULE_TYPE_BANDWIDTH_LIMIT = 'bandwidth-limit' +RULE_TYPE_DSCP_MARKING = 'dscp-marking' +RULE_TYPE_MINIMUM_BANDWIDTH = 'minimum-bandwidth' +REQUIRED_PARAMETERS = { + RULE_TYPE_MINIMUM_BANDWIDTH: ['min_kbps', 'direction'], + RULE_TYPE_DSCP_MARKING: ['dscp_mark'], + RULE_TYPE_BANDWIDTH_LIMIT: ['max_kbps', 'max_burst_kbps']} +DIRECTION_EGRESS = 'egress' +DIRECTION_INGRESS = 'ingress' +DSCP_VALID_MARKS = [0, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, + 34, 36, 38, 40, 46, 48, 56] + +ACTION_CREATE = 'create' +ACTION_DELETE = 'delete' +ACTION_FIND = 'find' +ACTION_SET = 'update' +ACTION_SHOW = 'get' + + +def _get_columns(item): + column_map = { + 'tenant_id': 'project_id', + } + return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + + +def _check_type_parameters(attrs, type, is_create): + req_params = REQUIRED_PARAMETERS[type] + notreq_params = list(itertools.chain( + *[v for k, v in six.iteritems(REQUIRED_PARAMETERS) if k != type])) + if is_create and None in map(attrs.get, req_params): + msg = (_('"Create" rule command for type "%(rule_type)s" requires ' + 'arguments %(args)s') % {'rule_type': type, + 'args': ", ".join(req_params)}) + raise exceptions.CommandError(msg) + if set(six.iterkeys(attrs)) & set(notreq_params): + msg = (_('Rule type "%(rule_type)s" only requires arguments %(args)s') + % {'rule_type': type, 'args': ", ".join(req_params)}) + raise exceptions.CommandError(msg) + + +def _get_attrs(network_client, parsed_args, is_create=False): + attrs = {} + qos = network_client.find_qos_policy(parsed_args.qos_policy, + ignore_missing=False) + attrs['qos_policy_id'] = qos.id + if not is_create: + attrs['id'] = parsed_args.id + rule_type = _find_rule_type(qos, parsed_args.id) + if not rule_type: + msg = (_('Rule ID %(rule_id)s not found') % + {'rule_id': parsed_args.id}) + raise exceptions.CommandError(msg) + else: + if not parsed_args.type: + msg = _('"Create" rule command requires argument "type"') + raise exceptions.CommandError(msg) + rule_type = parsed_args.type + if parsed_args.max_kbps: + attrs['max_kbps'] = parsed_args.max_kbps + if parsed_args.max_burst_kbits: + # NOTE(ralonsoh): this parameter must be changed in SDK and then in + # Neutron API, from 'max_burst_kbps' to + # 'max_burst_kbits' + attrs['max_burst_kbps'] = parsed_args.max_burst_kbits + if parsed_args.dscp_mark: + attrs['dscp_mark'] = parsed_args.dscp_mark + if parsed_args.min_kbps: + attrs['min_kbps'] = parsed_args.min_kbps + if parsed_args.ingress: + attrs['direction'] = 'ingress' + if parsed_args.egress: + attrs['direction'] = 'egress' + _check_type_parameters(attrs, rule_type, is_create) + return attrs + + +def _get_item_properties(item, fields): + """Return a tuple containing the item properties.""" + row = [] + for field in fields: + row.append(item.get(field, '')) + return tuple(row) + + +def _rule_action_call(client, action, rule_type): + rule_type = rule_type.replace('-', '_') + func_name = '%(action)s_qos_%(rule_type)s_rule' % {'action': action, + 'rule_type': rule_type} + return getattr(client, func_name) + + +def _find_rule_type(qos, rule_id): + for rule in (r for r in qos.rules if r['id'] == rule_id): + return rule['type'].replace('_', '-') + return None + + +def _add_rule_arguments(parser): + parser.add_argument( + '--max-kbps', + dest='max_kbps', + metavar='', + type=int, + help=_('Maximum bandwidth in kbps') + ) + parser.add_argument( + '--max-burst-kbits', + dest='max_burst_kbits', + metavar='', + type=int, + help=_('Maximum burst in kilobits, 0 means automatic') + ) + parser.add_argument( + '--dscp-mark', + dest='dscp_mark', + metavar='', + type=int, + help=_('DSCP mark: value can be 0, even numbers from 8-56, ' + 'excluding 42, 44, 50, 52, and 54') + ) + parser.add_argument( + '--min-kbps', + dest='min_kbps', + metavar='', + type=int, + help=_('Minimum guaranteed bandwidth in kbps') + ) + direction_group = parser.add_mutually_exclusive_group() + direction_group.add_argument( + '--ingress', + action='store_true', + help=_("Ingress traffic direction from the project point of view") + ) + direction_group.add_argument( + '--egress', + action='store_true', + help=_("Egress traffic direction from the project point of view") + ) + + +class CreateNetworkQosRule(command.ShowOne): + _description = _("Create new Network QoS rule") + + def get_parser(self, prog_name): + parser = super(CreateNetworkQosRule, self).get_parser( + prog_name) + parser.add_argument( + 'qos_policy', + metavar='', + help=_('QoS policy that contains the rule (name or ID)') + ) + parser.add_argument( + '--type', + metavar='', + choices=[RULE_TYPE_MINIMUM_BANDWIDTH, + RULE_TYPE_DSCP_MARKING, + RULE_TYPE_BANDWIDTH_LIMIT], + help=(_('QoS rule type (%s)') % + ", ".join(six.iterkeys(REQUIRED_PARAMETERS))) + ) + _add_rule_arguments(parser) + return parser + + def take_action(self, parsed_args): + network_client = self.app.client_manager.network + attrs = _get_attrs(network_client, parsed_args, is_create=True) + try: + obj = _rule_action_call( + network_client, ACTION_CREATE, parsed_args.type)( + attrs.pop('qos_policy_id'), **attrs) + except Exception as e: + msg = (_('Failed to create Network QoS rule: %(e)s') % {'e': e}) + raise exceptions.CommandError(msg) + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns) + return display_columns, data + + +class DeleteNetworkQosRule(command.Command): + _description = _("Delete Network QoS rule") + + def get_parser(self, prog_name): + parser = super(DeleteNetworkQosRule, self).get_parser(prog_name) + parser.add_argument( + 'qos_policy', + metavar='', + help=_('QoS policy that contains the rule (name or ID)') + ) + parser.add_argument( + 'id', + metavar='', + help=_('Network QoS rule to delete (ID)') + ) + return parser + + def take_action(self, parsed_args): + network_client = self.app.client_manager.network + rule_id = parsed_args.id + try: + qos = network_client.find_qos_policy(parsed_args.qos_policy, + ignore_missing=False) + rule_type = _find_rule_type(qos, rule_id) + if not rule_type: + raise Exception('Rule %s not found' % rule_id) + _rule_action_call(network_client, ACTION_DELETE, rule_type)( + rule_id, qos.id) + except Exception as e: + msg = (_('Failed to delete Network QoS rule ID "%(rule)s": %(e)s') + % {'rule': rule_id, 'e': e}) + raise exceptions.CommandError(msg) + + +class ListNetworkQosRule(command.Lister): + _description = _("List Network QoS rules") + + def get_parser(self, prog_name): + parser = super(ListNetworkQosRule, self).get_parser(prog_name) + parser.add_argument( + 'qos_policy', + metavar='', + help=_('QoS policy that contains the rule (name or ID)') + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + columns = ( + 'id', + 'qos_policy_id', + 'type', + 'max_kbps', + 'max_burst_kbps', + 'min_kbps', + 'dscp_mark', + 'direction', + ) + column_headers = ( + 'ID', + 'QoS Policy ID', + 'Type', + 'Max Kbps', + 'Max Burst Kbits', + 'Min Kbps', + 'DSCP mark', + 'Direction', + ) + qos = client.find_qos_policy(parsed_args.qos_policy, + ignore_missing=False) + data = qos.rules + return (column_headers, + (_get_item_properties(s, columns) for s in data)) + + +class SetNetworkQosRule(command.Command): + _description = _("Set Network QoS rule properties") + + def get_parser(self, prog_name): + parser = super(SetNetworkQosRule, self).get_parser(prog_name) + parser.add_argument( + 'qos_policy', + metavar='', + help=_('QoS policy that contains the rule (name or ID)') + ) + parser.add_argument( + 'id', + metavar='', + help=_('Network QoS rule to delete (ID)') + ) + _add_rule_arguments(parser) + return parser + + def take_action(self, parsed_args): + network_client = self.app.client_manager.network + try: + qos = network_client.find_qos_policy(parsed_args.qos_policy, + ignore_missing=False) + rule_type = _find_rule_type(qos, parsed_args.id) + if not rule_type: + raise Exception('Rule not found') + attrs = _get_attrs(network_client, parsed_args) + qos_id = attrs.pop('qos_policy_id') + qos_rule = _rule_action_call(network_client, ACTION_FIND, + rule_type)(attrs.pop('id'), qos_id) + _rule_action_call(network_client, ACTION_SET, rule_type)( + qos_rule, qos_id, **attrs) + except Exception as e: + msg = (_('Failed to set Network QoS rule ID "%(rule)s": %(e)s') % + {'rule': parsed_args.id, 'e': e}) + raise exceptions.CommandError(msg) + + +class ShowNetworkQosRule(command.ShowOne): + _description = _("Display Network QoS rule details") + + def get_parser(self, prog_name): + parser = super(ShowNetworkQosRule, self).get_parser(prog_name) + parser.add_argument( + 'qos_policy', + metavar='', + help=_('QoS policy that contains the rule (name or ID)') + ) + parser.add_argument( + 'id', + metavar='', + help=_('Network QoS rule to delete (ID)') + ) + return parser + + def take_action(self, parsed_args): + network_client = self.app.client_manager.network + rule_id = parsed_args.id + try: + qos = network_client.find_qos_policy(parsed_args.qos_policy, + ignore_missing=False) + rule_type = _find_rule_type(qos, rule_id) + if not rule_type: + raise Exception('Rule not found') + obj = _rule_action_call(network_client, ACTION_SHOW, rule_type)( + rule_id, qos.id) + except Exception as e: + msg = (_('Failed to set Network QoS rule ID "%(rule)s": %(e)s') % + {'rule': rule_id, 'e': e}) + raise exceptions.CommandError(msg) + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns) + return display_columns, data diff --git a/openstackclient/tests/functional/base.py b/openstackclient/tests/functional/base.py index 885abc02eb..fb78ea6250 100644 --- a/openstackclient/tests/functional/base.py +++ b/openstackclient/tests/functional/base.py @@ -77,6 +77,11 @@ def assertInOutput(cls, expected, actual): if expected not in actual: raise Exception(expected + ' not in ' + actual) + @classmethod + def assertsOutputNotNone(cls, observed): + if observed is None: + raise Exception('No output observed') + def assert_table_structure(self, items, field_names): """Verify that all items have keys listed in field_names.""" for item in items: diff --git a/openstackclient/tests/functional/network/v2/test_network_qos_rule.py b/openstackclient/tests/functional/network/v2/test_network_qos_rule.py new file mode 100644 index 0000000000..af0c9baca7 --- /dev/null +++ b/openstackclient/tests/functional/network/v2/test_network_qos_rule.py @@ -0,0 +1,181 @@ +# 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 uuid + +from openstackclient.tests.functional import base + + +class NetworkQosRuleTestsMinimumBandwidth(base.TestCase): + """Functional tests for QoS minimum bandwidth rule.""" + RULE_ID = None + QOS_POLICY_NAME = 'qos_policy_' + uuid.uuid4().hex + MIN_KBPS = 2800 + MIN_KBPS_MODIFIED = 7500 + DIRECTION = '--egress' + HEADERS = ['ID'] + FIELDS = ['id'] + TYPE = 'minimum-bandwidth' + + @classmethod + def setUpClass(cls): + opts = cls.get_opts(cls.FIELDS) + cls.openstack('network qos policy create ' + cls.QOS_POLICY_NAME) + cls.RULE_ID = cls.openstack('network qos rule create --type ' + + cls.TYPE + ' --min-kbps ' + + str(cls.MIN_KBPS) + ' ' + cls.DIRECTION + + ' ' + cls.QOS_POLICY_NAME + opts) + cls.assertsOutputNotNone(cls.RULE_ID) + + @classmethod + def tearDownClass(cls): + raw_output = cls.openstack('network qos rule delete ' + + cls.QOS_POLICY_NAME + ' ' + cls.RULE_ID) + cls.openstack('network qos policy delete ' + cls.QOS_POLICY_NAME) + cls.assertOutput('', raw_output) + + def test_qos_policy_list(self): + opts = self.get_opts(self.HEADERS) + raw_output = self.openstack('network qos rule list ' + + self.QOS_POLICY_NAME + opts) + self.assertIn(self.RULE_ID, raw_output) + + def test_qos_policy_show(self): + opts = self.get_opts(self.FIELDS) + raw_output = self.openstack('network qos rule show ' + + self.QOS_POLICY_NAME + ' ' + self.RULE_ID + + opts) + self.assertEqual(self.RULE_ID, raw_output) + + def test_qos_policy_set(self): + self.openstack('network qos rule set --min-kbps ' + + str(self.MIN_KBPS_MODIFIED) + ' ' + + self.QOS_POLICY_NAME + ' ' + self.RULE_ID) + opts = self.get_opts(['min_kbps']) + raw_output = self.openstack('network qos rule show ' + + self.QOS_POLICY_NAME + ' ' + self.RULE_ID + + opts) + self.assertEqual(str(self.MIN_KBPS_MODIFIED) + "\n", raw_output) + + +class NetworkQosRuleTestsDSCPMarking(base.TestCase): + """Functional tests for QoS DSCP marking rule.""" + RULE_ID = None + QOS_POLICY_NAME = 'qos_policy_' + uuid.uuid4().hex + DSCP_MARK = 8 + DSCP_MARK_MODIFIED = 32 + HEADERS = ['ID'] + FIELDS = ['id'] + TYPE = 'dscp-marking' + + @classmethod + def setUpClass(cls): + opts = cls.get_opts(cls.FIELDS) + cls.openstack('network qos policy create ' + cls.QOS_POLICY_NAME) + cls.RULE_ID = cls.openstack('network qos rule create --type ' + + cls.TYPE + ' --dscp-mark ' + + str(cls.DSCP_MARK) + ' ' + + cls.QOS_POLICY_NAME + opts) + cls.assertsOutputNotNone(cls.RULE_ID) + + @classmethod + def tearDownClass(cls): + raw_output = cls.openstack('network qos rule delete ' + + cls.QOS_POLICY_NAME + ' ' + cls.RULE_ID) + cls.openstack('network qos policy delete ' + cls.QOS_POLICY_NAME) + cls.assertOutput('', raw_output) + + def test_qos_policy_list(self): + opts = self.get_opts(self.HEADERS) + raw_output = self.openstack('network qos rule list ' + + self.QOS_POLICY_NAME + opts) + self.assertIn(self.RULE_ID, raw_output) + + def test_qos_policy_show(self): + opts = self.get_opts(self.FIELDS) + raw_output = self.openstack('network qos rule show ' + + self.QOS_POLICY_NAME + ' ' + self.RULE_ID + + opts) + self.assertEqual(self.RULE_ID, raw_output) + + def test_qos_policy_set(self): + self.openstack('network qos rule set --dscp-mark ' + + str(self.DSCP_MARK_MODIFIED) + ' ' + + self.QOS_POLICY_NAME + ' ' + self.RULE_ID) + opts = self.get_opts(['dscp_mark']) + raw_output = self.openstack('network qos rule show ' + + self.QOS_POLICY_NAME + ' ' + self.RULE_ID + + opts) + self.assertEqual(str(self.DSCP_MARK_MODIFIED) + "\n", raw_output) + + +class NetworkQosRuleTestsBandwidthLimit(base.TestCase): + """Functional tests for QoS bandwidth limit rule.""" + RULE_ID = None + QOS_POLICY_NAME = 'qos_policy_' + uuid.uuid4().hex + MAX_KBPS = 10000 + MAX_KBPS_MODIFIED = 15000 + MAX_BURST_KBITS = 1400 + MAX_BURST_KBITS_MODIFIED = 1800 + HEADERS = ['ID'] + FIELDS = ['id'] + TYPE = 'bandwidth-limit' + + @classmethod + def setUpClass(cls): + opts = cls.get_opts(cls.FIELDS) + cls.openstack('network qos policy create ' + cls.QOS_POLICY_NAME) + cls.RULE_ID = cls.openstack('network qos rule create --type ' + + cls.TYPE + ' --max-kbps ' + + str(cls.MAX_KBPS) + ' --max-burst-kbits ' + + str(cls.MAX_BURST_KBITS) + ' ' + + cls.QOS_POLICY_NAME + opts) + cls.assertsOutputNotNone(cls.RULE_ID) + + @classmethod + def tearDownClass(cls): + raw_output = cls.openstack('network qos rule delete ' + + cls.QOS_POLICY_NAME + ' ' + cls.RULE_ID) + cls.openstack('network qos policy delete ' + cls.QOS_POLICY_NAME) + cls.assertOutput('', raw_output) + + def test_qos_policy_list(self): + opts = self.get_opts(self.HEADERS) + raw_output = self.openstack('network qos rule list ' + + self.QOS_POLICY_NAME + opts) + self.assertIn(self.RULE_ID, raw_output) + + def test_qos_policy_show(self): + opts = self.get_opts(self.FIELDS) + raw_output = self.openstack('network qos rule show ' + + self.QOS_POLICY_NAME + ' ' + self.RULE_ID + + opts) + self.assertEqual(self.RULE_ID, raw_output) + + def test_qos_policy_set(self): + self.openstack('network qos rule set --max-kbps ' + + str(self.MAX_KBPS_MODIFIED) + ' --max-burst-kbits ' + + str(self.MAX_BURST_KBITS_MODIFIED) + ' ' + + self.QOS_POLICY_NAME + ' ' + self.RULE_ID) + opts = self.get_opts(['max_kbps']) + raw_output = self.openstack('network qos rule show ' + + self.QOS_POLICY_NAME + ' ' + self.RULE_ID + + opts) + self.assertEqual(str(self.MAX_KBPS_MODIFIED) + "\n", raw_output) + opts = self.get_opts(['max_burst_kbps']) + raw_output = self.openstack('network qos rule show ' + + self.QOS_POLICY_NAME + ' ' + self.RULE_ID + + opts) + self.assertEqual(str(self.MAX_BURST_KBITS_MODIFIED) + "\n", raw_output) diff --git a/openstackclient/tests/unit/fakes.py b/openstackclient/tests/unit/fakes.py index f7cb567644..0732a1ec3e 100644 --- a/openstackclient/tests/unit/fakes.py +++ b/openstackclient/tests/unit/fakes.py @@ -216,6 +216,12 @@ def keys(self): def info(self): return self._info + def __getitem__(self, item): + return self._info.get(item) + + def get(self, item, default=None): + return self._info.get(item, default) + class FakeResponse(requests.Response): diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index b931cb557d..fe0422face 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -14,6 +14,8 @@ import argparse import copy import mock +from random import choice +from random import randint import uuid from openstackclient.tests.unit import fakes @@ -37,6 +39,15 @@ "l7policy": 5, } +RULE_TYPE_BANDWIDTH_LIMIT = 'bandwidth-limit' +RULE_TYPE_DSCP_MARKING = 'dscp-marking' +RULE_TYPE_MINIMUM_BANDWIDTH = 'minimum-bandwidth' +VALID_QOS_RULES = [RULE_TYPE_BANDWIDTH_LIMIT, + RULE_TYPE_DSCP_MARKING, + RULE_TYPE_MINIMUM_BANDWIDTH] +VALID_DSCP_MARKS = [0, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, + 34, 36, 38, 40, 46, 48, 56] + class FakeNetworkV2Client(object): @@ -662,83 +673,90 @@ def get_network_rbacs(rbac_policies=None, count=2): return mock.Mock(side_effect=rbac_policies) -class FakeNetworkQosBandwidthLimitRule(object): - """Fake one or more QoS bandwidth limit rules.""" +class FakeNetworkQosPolicy(object): + """Fake one or more QoS policies.""" @staticmethod - def create_one_qos_bandwidth_limit_rule(attrs=None): - """Create a fake QoS bandwidth limit rule. + def create_one_qos_policy(attrs=None): + """Create a fake QoS policy. :param Dictionary attrs: A dictionary with all attributes :return: - A FakeResource object with id, qos_policy_id, max_kbps and - max_burst_kbps attributes. + A FakeResource object with name, id, etc. """ attrs = attrs or {} + qos_id = attrs.get('id') or 'qos-policy-id-' + uuid.uuid4().hex + rule_attrs = {'qos_policy_id': qos_id} + rules = [FakeNetworkQosRule.create_one_qos_rule(rule_attrs)] # Set default attributes. - qos_bandwidth_limit_rule_attrs = { - 'id': 'qos-bandwidth-limit-rule-id-' + uuid.uuid4().hex, - 'qos_policy_id': 'qos-policy-id-' + uuid.uuid4().hex, - 'max_kbps': 1500, - 'max_burst_kbps': 1200, + qos_policy_attrs = { + 'name': 'qos-policy-name-' + uuid.uuid4().hex, + 'id': qos_id, + 'tenant_id': 'project-id-' + uuid.uuid4().hex, + 'shared': False, + 'description': 'qos-policy-description-' + uuid.uuid4().hex, + 'rules': rules, } # Overwrite default attributes. - qos_bandwidth_limit_rule_attrs.update(attrs) + qos_policy_attrs.update(attrs) - qos_bandwidth_limit_rule = fakes.FakeResource( - info=copy.deepcopy(qos_bandwidth_limit_rule_attrs), + qos_policy = fakes.FakeResource( + info=copy.deepcopy(qos_policy_attrs), loaded=True) - return qos_bandwidth_limit_rule + # Set attributes with special mapping in OpenStack SDK. + qos_policy.is_shared = qos_policy_attrs['shared'] + qos_policy.project_id = qos_policy_attrs['tenant_id'] + + return qos_policy @staticmethod - def create_qos_bandwidth_limit_rules(attrs=None, count=2): - """Create multiple fake QoS bandwidth limit rules. + def create_qos_policies(attrs=None, count=2): + """Create multiple fake QoS policies. :param Dictionary attrs: A dictionary with all attributes :param int count: - The number of QoS bandwidth limit rules to fake + The number of QoS policies to fake :return: - A list of FakeResource objects faking the QoS bandwidth limit rules + A list of FakeResource objects faking the QoS policies """ qos_policies = [] for i in range(0, count): - qos_policies.append(FakeNetworkQosBandwidthLimitRule. - create_one_qos_bandwidth_limit_rule(attrs)) + qos_policies.append( + FakeNetworkQosPolicy.create_one_qos_policy(attrs)) return qos_policies @staticmethod - def get_qos_bandwidth_limit_rules(qos_rules=None, count=2): - """Get a list of faked QoS bandwidth limit rules. + def get_qos_policies(qos_policies=None, count=2): + """Get an iterable MagicMock object with a list of faked QoS policies. - If QoS bandwidth limit rules list is provided, then initialize the - Mock object with the list. Otherwise create one. + If qos policies list is provided, then initialize the Mock object + with the list. Otherwise create one. :param List address scopes: - A list of FakeResource objects faking QoS bandwidth limit rules + A list of FakeResource objects faking qos policies :param int count: - The number of QoS bandwidth limit rules to fake + The number of QoS policies to fake :return: An iterable Mock object with side_effect set to a list of faked - qos bandwidth limit rules + QoS policies """ - if qos_rules is None: - qos_rules = (FakeNetworkQosBandwidthLimitRule. - create_qos_bandwidth_limit_rules(count)) - return mock.Mock(side_effect=qos_rules) + if qos_policies is None: + qos_policies = FakeNetworkQosPolicy.create_qos_policies(count) + return mock.Mock(side_effect=qos_policies) -class FakeNetworkQosPolicy(object): - """Fake one or more QoS policies.""" +class FakeNetworkQosRule(object): + """Fake one or more Network QoS rules.""" @staticmethod - def create_one_qos_policy(attrs=None): - """Create a fake QoS policy. + def create_one_qos_rule(attrs=None): + """Create a fake Network QoS rule. :param Dictionary attrs: A dictionary with all attributes @@ -746,71 +764,69 @@ def create_one_qos_policy(attrs=None): A FakeResource object with name, id, etc. """ attrs = attrs or {} - qos_id = attrs.get('id') or 'qos-policy-id-' + uuid.uuid4().hex - rule_attrs = {'qos_policy_id': qos_id} - rules = [ - FakeNetworkQosBandwidthLimitRule. - create_one_qos_bandwidth_limit_rule(rule_attrs)] # Set default attributes. - qos_policy_attrs = { - 'name': 'qos-policy-name-' + uuid.uuid4().hex, - 'id': qos_id, + type = attrs.get('type') or choice(VALID_QOS_RULES) + qos_rule_attrs = { + 'id': 'qos-rule-id-' + uuid.uuid4().hex, + 'qos_policy_id': 'qos-policy-id-' + uuid.uuid4().hex, 'tenant_id': 'project-id-' + uuid.uuid4().hex, - 'shared': False, - 'description': 'qos-policy-description-' + uuid.uuid4().hex, - 'rules': rules, + 'type': type, } + if type == RULE_TYPE_BANDWIDTH_LIMIT: + qos_rule_attrs['max_kbps'] = randint(1, 10000) + qos_rule_attrs['max_burst_kbits'] = randint(1, 10000) + elif type == RULE_TYPE_DSCP_MARKING: + qos_rule_attrs['dscp_mark'] = choice(VALID_DSCP_MARKS) + elif type == RULE_TYPE_MINIMUM_BANDWIDTH: + qos_rule_attrs['min_kbps'] = randint(1, 10000) + qos_rule_attrs['direction'] = 'egress' # Overwrite default attributes. - qos_policy_attrs.update(attrs) + qos_rule_attrs.update(attrs) - qos_policy = fakes.FakeResource( - info=copy.deepcopy(qos_policy_attrs), - loaded=True) + qos_rule = fakes.FakeResource(info=copy.deepcopy(qos_rule_attrs), + loaded=True) # Set attributes with special mapping in OpenStack SDK. - qos_policy.is_shared = qos_policy_attrs['shared'] - qos_policy.project_id = qos_policy_attrs['tenant_id'] + qos_rule.project_id = qos_rule['tenant_id'] - return qos_policy + return qos_rule @staticmethod - def create_qos_policies(attrs=None, count=2): - """Create multiple fake QoS policies. + def create_qos_rules(attrs=None, count=2): + """Create multiple fake Network QoS rules. :param Dictionary attrs: A dictionary with all attributes :param int count: - The number of QoS policies to fake + The number of Network QoS rule to fake :return: - A list of FakeResource objects faking the QoS policies + A list of FakeResource objects faking the Network QoS rules """ - qos_policies = [] + qos_rules = [] for i in range(0, count): - qos_policies.append( - FakeNetworkQosPolicy.create_one_qos_policy(attrs)) - - return qos_policies + qos_rules.append(FakeNetworkQosRule.create_one_qos_rule(attrs)) + return qos_rules @staticmethod - def get_qos_policies(qos_policies=None, count=2): - """Get an iterable MagicMock object with a list of faked QoS policies. + def get_qos_rules(qos_rules=None, count=2): + """Get a list of faked Network QoS rules. - If qos policies list is provided, then initialize the Mock object - with the list. Otherwise create one. + If Network QoS rules list is provided, then initialize the Mock + object with the list. Otherwise create one. :param List address scopes: - A list of FakeResource objects faking qos policies + A list of FakeResource objects faking Network QoS rules :param int count: - The number of QoS policies to fake + The number of QoS minimum bandwidth rules to fake :return: An iterable Mock object with side_effect set to a list of faked - QoS policies + qos minimum bandwidth rules """ - if qos_policies is None: - qos_policies = FakeNetworkQosPolicy.create_qos_policies(count) - return mock.Mock(side_effect=qos_policies) + if qos_rules is None: + qos_rules = (FakeNetworkQosRule.create_qos_rules(count)) + return mock.Mock(side_effect=qos_rules) class FakeNetworkQosRuleType(object): diff --git a/openstackclient/tests/unit/network/v2/test_network_qos_rule.py b/openstackclient/tests/unit/network/v2/test_network_qos_rule.py new file mode 100644 index 0000000000..e66f25b700 --- /dev/null +++ b/openstackclient/tests/unit/network/v2/test_network_qos_rule.py @@ -0,0 +1,997 @@ +# 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 osc_lib import exceptions + +from openstackclient.network.v2 import network_qos_rule +from openstackclient.tests.unit.network.v2 import fakes as network_fakes +from openstackclient.tests.unit import utils as tests_utils + + +RULE_TYPE_BANDWIDTH_LIMIT = 'bandwidth-limit' +RULE_TYPE_DSCP_MARKING = 'dscp-marking' +RULE_TYPE_MINIMUM_BANDWIDTH = 'minimum-bandwidth' +DSCP_VALID_MARKS = [0, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, + 34, 36, 38, 40, 46, 48, 56] + + +class TestNetworkQosRule(network_fakes.TestNetworkV2): + + def setUp(self): + super(TestNetworkQosRule, self).setUp() + # Get a shortcut to the network client + self.network = self.app.client_manager.network + self.qos_policy = (network_fakes.FakeNetworkQosPolicy. + create_one_qos_policy()) + self.network.find_qos_policy = mock.Mock(return_value=self.qos_policy) + + +class TestCreateNetworkQosRuleMinimumBandwidth(TestNetworkQosRule): + + def test_check_type_parameters(self): + pass + + def setUp(self): + super(TestCreateNetworkQosRuleMinimumBandwidth, self).setUp() + attrs = {'qos_policy_id': self.qos_policy.id, + 'type': RULE_TYPE_MINIMUM_BANDWIDTH} + self.new_rule = network_fakes.FakeNetworkQosRule.create_one_qos_rule( + attrs) + self.columns = ( + 'direction', + 'id', + 'min_kbps', + 'project_id', + 'qos_policy_id', + 'type' + ) + + self.data = ( + self.new_rule.direction, + self.new_rule.id, + self.new_rule.min_kbps, + self.new_rule.project_id, + self.new_rule.qos_policy_id, + self.new_rule.type, + ) + self.network.create_qos_minimum_bandwidth_rule = mock.Mock( + return_value=self.new_rule) + + # Get the command object to test + self.cmd = network_qos_rule.CreateNetworkQosRule(self.app, + self.namespace) + + def test_create_no_options(self): + arglist = [] + verifylist = [] + + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_create_default_options(self): + arglist = [ + '--type', RULE_TYPE_MINIMUM_BANDWIDTH, + '--min-kbps', str(self.new_rule.min_kbps), + '--egress', + self.new_rule.qos_policy_id, + ] + + verifylist = [ + ('type', RULE_TYPE_MINIMUM_BANDWIDTH), + ('min_kbps', self.new_rule.min_kbps), + ('egress', True), + ('qos_policy', self.new_rule.qos_policy_id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_qos_minimum_bandwidth_rule.assert_called_once_with( + self.qos_policy.id, + **{'min_kbps': self.new_rule.min_kbps, + 'direction': self.new_rule.direction} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_wrong_options(self): + arglist = [ + '--type', RULE_TYPE_MINIMUM_BANDWIDTH, + '--max-kbps', '10000', + self.new_rule.qos_policy_id, + ] + + verifylist = [ + ('type', RULE_TYPE_MINIMUM_BANDWIDTH), + ('max_kbps', 10000), + ('qos_policy', self.new_rule.qos_policy_id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + try: + self.cmd.take_action(parsed_args) + except exceptions.CommandError as e: + msg = ('"Create" rule command for type "minimum-bandwidth" ' + 'requires arguments min_kbps, direction') + self.assertEqual(msg, str(e)) + + +class TestCreateNetworkQosRuleDSCPMarking(TestNetworkQosRule): + + def test_check_type_parameters(self): + pass + + def setUp(self): + super(TestCreateNetworkQosRuleDSCPMarking, self).setUp() + attrs = {'qos_policy_id': self.qos_policy.id, + 'type': RULE_TYPE_DSCP_MARKING} + self.new_rule = network_fakes.FakeNetworkQosRule.create_one_qos_rule( + attrs) + self.columns = ( + 'dscp_mark', + 'id', + 'project_id', + 'qos_policy_id', + 'type' + ) + + self.data = ( + self.new_rule.dscp_mark, + self.new_rule.id, + self.new_rule.project_id, + self.new_rule.qos_policy_id, + self.new_rule.type, + ) + self.network.create_qos_dscp_marking_rule = mock.Mock( + return_value=self.new_rule) + + # Get the command object to test + self.cmd = network_qos_rule.CreateNetworkQosRule(self.app, + self.namespace) + + def test_create_no_options(self): + arglist = [] + verifylist = [] + + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_create_default_options(self): + arglist = [ + '--type', RULE_TYPE_DSCP_MARKING, + '--dscp-mark', str(self.new_rule.dscp_mark), + self.new_rule.qos_policy_id, + ] + + verifylist = [ + ('type', RULE_TYPE_DSCP_MARKING), + ('dscp_mark', self.new_rule.dscp_mark), + ('qos_policy', self.new_rule.qos_policy_id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_qos_dscp_marking_rule.assert_called_once_with( + self.qos_policy.id, + **{'dscp_mark': self.new_rule.dscp_mark} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_wrong_options(self): + arglist = [ + '--type', RULE_TYPE_DSCP_MARKING, + '--max-kbps', '10000', + self.new_rule.qos_policy_id, + ] + + verifylist = [ + ('type', RULE_TYPE_DSCP_MARKING), + ('max_kbps', 10000), + ('qos_policy', self.new_rule.qos_policy_id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + try: + self.cmd.take_action(parsed_args) + except exceptions.CommandError as e: + msg = ('"Create" rule command for type "dscp-marking" ' + 'requires arguments dscp_mark') + self.assertEqual(msg, str(e)) + + +class TestCreateNetworkQosRuleBandwidtLimit(TestNetworkQosRule): + + def test_check_type_parameters(self): + pass + + def setUp(self): + super(TestCreateNetworkQosRuleBandwidtLimit, self).setUp() + attrs = {'qos_policy_id': self.qos_policy.id, + 'type': RULE_TYPE_BANDWIDTH_LIMIT} + self.new_rule = network_fakes.FakeNetworkQosRule.create_one_qos_rule( + attrs) + self.columns = ( + 'id', + 'max_burst_kbits', + 'max_kbps', + 'project_id', + 'qos_policy_id', + 'type' + ) + + self.data = ( + self.new_rule.id, + self.new_rule.max_burst_kbits, + self.new_rule.max_kbps, + self.new_rule.project_id, + self.new_rule.qos_policy_id, + self.new_rule.type, + ) + self.network.create_qos_bandwidth_limit_rule = mock.Mock( + return_value=self.new_rule) + + # Get the command object to test + self.cmd = network_qos_rule.CreateNetworkQosRule(self.app, + self.namespace) + + def test_create_no_options(self): + arglist = [] + verifylist = [] + + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_create_default_options(self): + arglist = [ + '--type', RULE_TYPE_BANDWIDTH_LIMIT, + '--max-kbps', str(self.new_rule.max_kbps), + '--max-burst-kbits', str(self.new_rule.max_burst_kbits), + self.new_rule.qos_policy_id, + ] + + verifylist = [ + ('type', RULE_TYPE_BANDWIDTH_LIMIT), + ('max_kbps', self.new_rule.max_kbps), + ('max_burst_kbits', self.new_rule.max_burst_kbits), + ('qos_policy', self.new_rule.qos_policy_id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_qos_bandwidth_limit_rule.assert_called_once_with( + self.qos_policy.id, + **{'max_kbps': self.new_rule.max_kbps, + 'max_burst_kbps': self.new_rule.max_burst_kbits} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_wrong_options(self): + arglist = [ + '--type', RULE_TYPE_BANDWIDTH_LIMIT, + '--min-kbps', '10000', + self.new_rule.qos_policy_id, + ] + + verifylist = [ + ('type', RULE_TYPE_BANDWIDTH_LIMIT), + ('min_kbps', 10000), + ('qos_policy', self.new_rule.qos_policy_id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + try: + self.cmd.take_action(parsed_args) + except exceptions.CommandError as e: + msg = ('"Create" rule command for type "bandwidth-limit" ' + 'requires arguments max_kbps, max_burst_kbps') + self.assertEqual(msg, str(e)) + + +class TestDeleteNetworkQosRuleMinimumBandwidth(TestNetworkQosRule): + + def setUp(self): + super(TestDeleteNetworkQosRuleMinimumBandwidth, self).setUp() + attrs = {'qos_policy_id': self.qos_policy.id, + 'type': RULE_TYPE_MINIMUM_BANDWIDTH} + self.new_rule = network_fakes.FakeNetworkQosRule.create_one_qos_rule( + attrs) + self.qos_policy.rules = [self.new_rule] + self.network.delete_qos_minimum_bandwidth_rule = mock.Mock( + return_value=None) + self.network.find_qos_minimum_bandwidth_rule = ( + network_fakes.FakeNetworkQosRule.get_qos_rules( + qos_rules=self.new_rule) + ) + + # Get the command object to test + self.cmd = network_qos_rule.DeleteNetworkQosRule(self.app, + self.namespace) + + def test_qos_policy_delete(self): + arglist = [ + self.new_rule.qos_policy_id, + self.new_rule.id, + ] + verifylist = [ + ('qos_policy', self.new_rule.qos_policy_id), + ('id', self.new_rule.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.network.find_qos_policy.assert_called_once_with( + self.qos_policy.id, ignore_missing=False) + self.network.delete_qos_minimum_bandwidth_rule.assert_called_once_with( + self.new_rule.id, self.qos_policy.id) + self.assertIsNone(result) + + def test_qos_policy_delete_error(self): + arglist = [ + self.new_rule.qos_policy_id, + self.new_rule.id, + ] + verifylist = [ + ('qos_policy', self.new_rule.qos_policy_id), + ('id', self.new_rule.id), + ] + + self.network.delete_qos_minimum_bandwidth_rule.side_effect = \ + Exception('Error message') + try: + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + except exceptions.CommandError as e: + msg = ('Failed to delete Network QoS rule ID "%(rule)s": %(e)s' % + {'rule': self.new_rule.id, 'e': 'Error message'}) + self.assertEqual(msg, str(e)) + + +class TestDeleteNetworkQosRuleDSCPMarking(TestNetworkQosRule): + + def setUp(self): + super(TestDeleteNetworkQosRuleDSCPMarking, self).setUp() + attrs = {'qos_policy_id': self.qos_policy.id, + 'type': RULE_TYPE_DSCP_MARKING} + self.new_rule = network_fakes.FakeNetworkQosRule.create_one_qos_rule( + attrs) + self.qos_policy.rules = [self.new_rule] + self.network.delete_qos_dscp_marking_rule = mock.Mock( + return_value=None) + self.network.find_qos_dscp_marking_rule = ( + network_fakes.FakeNetworkQosRule.get_qos_rules( + qos_rules=self.new_rule) + ) + + # Get the command object to test + self.cmd = network_qos_rule.DeleteNetworkQosRule(self.app, + self.namespace) + + def test_qos_policy_delete(self): + arglist = [ + self.new_rule.qos_policy_id, + self.new_rule.id, + ] + verifylist = [ + ('qos_policy', self.new_rule.qos_policy_id), + ('id', self.new_rule.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.network.find_qos_policy.assert_called_once_with( + self.qos_policy.id, ignore_missing=False) + self.network.delete_qos_dscp_marking_rule.assert_called_once_with( + self.new_rule.id, self.qos_policy.id) + self.assertIsNone(result) + + def test_qos_policy_delete_error(self): + arglist = [ + self.new_rule.qos_policy_id, + self.new_rule.id, + ] + verifylist = [ + ('qos_policy', self.new_rule.qos_policy_id), + ('id', self.new_rule.id), + ] + + self.network.delete_qos_dscp_marking_rule.side_effect = \ + Exception('Error message') + try: + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + except exceptions.CommandError as e: + msg = ('Failed to delete Network QoS rule ID "%(rule)s": %(e)s' % + {'rule': self.new_rule.id, 'e': 'Error message'}) + self.assertEqual(msg, str(e)) + + +class TestDeleteNetworkQosRuleBandwidthLimit(TestNetworkQosRule): + + def setUp(self): + super(TestDeleteNetworkQosRuleBandwidthLimit, self).setUp() + attrs = {'qos_policy_id': self.qos_policy.id, + 'type': RULE_TYPE_BANDWIDTH_LIMIT} + self.new_rule = network_fakes.FakeNetworkQosRule.create_one_qos_rule( + attrs) + self.qos_policy.rules = [self.new_rule] + self.network.delete_qos_bandwidth_limit_rule = mock.Mock( + return_value=None) + self.network.find_qos_bandwidth_limit_rule = ( + network_fakes.FakeNetworkQosRule.get_qos_rules( + qos_rules=self.new_rule) + ) + + # Get the command object to test + self.cmd = network_qos_rule.DeleteNetworkQosRule(self.app, + self.namespace) + + def test_qos_policy_delete(self): + arglist = [ + self.new_rule.qos_policy_id, + self.new_rule.id, + ] + verifylist = [ + ('qos_policy', self.new_rule.qos_policy_id), + ('id', self.new_rule.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.network.find_qos_policy.assert_called_once_with( + self.qos_policy.id, ignore_missing=False) + self.network.delete_qos_bandwidth_limit_rule.assert_called_once_with( + self.new_rule.id, self.qos_policy.id) + self.assertIsNone(result) + + def test_qos_policy_delete_error(self): + arglist = [ + self.new_rule.qos_policy_id, + self.new_rule.id, + ] + verifylist = [ + ('qos_policy', self.new_rule.qos_policy_id), + ('id', self.new_rule.id), + ] + + self.network.delete_qos_bandwidth_limit_rule.side_effect = \ + Exception('Error message') + try: + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + except exceptions.CommandError as e: + msg = ('Failed to delete Network QoS rule ID "%(rule)s": %(e)s' % + {'rule': self.new_rule.id, 'e': 'Error message'}) + self.assertEqual(msg, str(e)) + + +class TestSetNetworkQosRuleMinimumBandwidth(TestNetworkQosRule): + + def setUp(self): + super(TestSetNetworkQosRuleMinimumBandwidth, self).setUp() + attrs = {'qos_policy_id': self.qos_policy.id, + 'type': RULE_TYPE_MINIMUM_BANDWIDTH} + self.new_rule = network_fakes.FakeNetworkQosRule.create_one_qos_rule( + attrs=attrs) + self.qos_policy.rules = [self.new_rule] + self.network.update_qos_minimum_bandwidth_rule = mock.Mock( + return_value=None) + self.network.find_qos_minimum_bandwidth_rule = mock.Mock( + return_value=self.new_rule) + self.network.find_qos_policy = mock.Mock( + return_value=self.qos_policy) + + # Get the command object to test + self.cmd = (network_qos_rule.SetNetworkQosRule(self.app, + self.namespace)) + + def test_set_nothing(self): + arglist = [ + self.new_rule.qos_policy_id, + self.new_rule.id, + ] + verifylist = [ + ('qos_policy', self.new_rule.qos_policy_id), + ('id', self.new_rule.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.network.update_qos_minimum_bandwidth_rule.assert_called_with( + self.new_rule, self.qos_policy.id) + self.assertIsNone(result) + + def test_set_min_kbps(self): + arglist = [ + '--min-kbps', str(self.new_rule.min_kbps), + self.new_rule.qos_policy_id, + self.new_rule.id, + ] + verifylist = [ + ('min_kbps', self.new_rule.min_kbps), + ('qos_policy', self.new_rule.qos_policy_id), + ('id', self.new_rule.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = { + 'min_kbps': self.new_rule.min_kbps, + } + self.network.update_qos_minimum_bandwidth_rule.assert_called_with( + self.new_rule, self.qos_policy.id, **attrs) + self.assertIsNone(result) + + def test_set_wrong_options(self): + arglist = [ + '--max-kbps', str(10000), + self.new_rule.qos_policy_id, + self.new_rule.id, + ] + verifylist = [ + ('max_kbps', 10000), + ('qos_policy', self.new_rule.qos_policy_id), + ('id', self.new_rule.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + try: + self.cmd.take_action(parsed_args) + except exceptions.CommandError as e: + msg = ('Failed to set Network QoS rule ID "%(rule)s": Rule type ' + '"minimum-bandwidth" only requires arguments min_kbps, ' + 'direction' % {'rule': self.new_rule.id}) + self.assertEqual(msg, str(e)) + + +class TestSetNetworkQosRuleDSCPMarking(TestNetworkQosRule): + + def setUp(self): + super(TestSetNetworkQosRuleDSCPMarking, self).setUp() + attrs = {'qos_policy_id': self.qos_policy.id, + 'type': RULE_TYPE_DSCP_MARKING} + self.new_rule = network_fakes.FakeNetworkQosRule.create_one_qos_rule( + attrs=attrs) + self.qos_policy.rules = [self.new_rule] + self.network.update_qos_dscp_marking_rule = mock.Mock( + return_value=None) + self.network.find_qos_dscp_marking_rule = mock.Mock( + return_value=self.new_rule) + self.network.find_qos_policy = mock.Mock( + return_value=self.qos_policy) + + # Get the command object to test + self.cmd = (network_qos_rule.SetNetworkQosRule(self.app, + self.namespace)) + + def test_set_nothing(self): + arglist = [ + self.new_rule.qos_policy_id, + self.new_rule.id, + ] + verifylist = [ + ('qos_policy', self.new_rule.qos_policy_id), + ('id', self.new_rule.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.network.update_qos_dscp_marking_rule.assert_called_with( + self.new_rule, self.qos_policy.id) + self.assertIsNone(result) + + def test_set_dscp_mark(self): + arglist = [ + '--dscp-mark', str(self.new_rule.dscp_mark), + self.new_rule.qos_policy_id, + self.new_rule.id, + ] + verifylist = [ + ('dscp_mark', self.new_rule.dscp_mark), + ('qos_policy', self.new_rule.qos_policy_id), + ('id', self.new_rule.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = { + 'dscp_mark': self.new_rule.dscp_mark, + } + self.network.update_qos_dscp_marking_rule.assert_called_with( + self.new_rule, self.qos_policy.id, **attrs) + self.assertIsNone(result) + + def test_set_wrong_options(self): + arglist = [ + '--max-kbps', str(10000), + self.new_rule.qos_policy_id, + self.new_rule.id, + ] + verifylist = [ + ('max_kbps', 10000), + ('qos_policy', self.new_rule.qos_policy_id), + ('id', self.new_rule.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + try: + self.cmd.take_action(parsed_args) + except exceptions.CommandError as e: + msg = ('Failed to set Network QoS rule ID "%(rule)s": Rule type ' + '"dscp-marking" only requires arguments dscp_mark' % + {'rule': self.new_rule.id}) + self.assertEqual(msg, str(e)) + + +class TestSetNetworkQosRuleBandwidthLimit(TestNetworkQosRule): + + def setUp(self): + super(TestSetNetworkQosRuleBandwidthLimit, self).setUp() + attrs = {'qos_policy_id': self.qos_policy.id, + 'type': RULE_TYPE_BANDWIDTH_LIMIT} + self.new_rule = network_fakes.FakeNetworkQosRule.create_one_qos_rule( + attrs=attrs) + self.qos_policy.rules = [self.new_rule] + self.network.update_qos_bandwidth_limit_rule = mock.Mock( + return_value=None) + self.network.find_qos_bandwidth_limit_rule = mock.Mock( + return_value=self.new_rule) + self.network.find_qos_policy = mock.Mock( + return_value=self.qos_policy) + + # Get the command object to test + self.cmd = (network_qos_rule.SetNetworkQosRule(self.app, + self.namespace)) + + def test_set_nothing(self): + arglist = [ + self.new_rule.qos_policy_id, + self.new_rule.id, + ] + verifylist = [ + ('qos_policy', self.new_rule.qos_policy_id), + ('id', self.new_rule.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.network.update_qos_bandwidth_limit_rule.assert_called_with( + self.new_rule, self.qos_policy.id) + self.assertIsNone(result) + + def test_set_max_kbps(self): + arglist = [ + '--max-kbps', str(self.new_rule.max_kbps), + self.new_rule.qos_policy_id, + self.new_rule.id, + ] + verifylist = [ + ('max_kbps', self.new_rule.max_kbps), + ('qos_policy', self.new_rule.qos_policy_id), + ('id', self.new_rule.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = { + 'max_kbps': self.new_rule.max_kbps, + } + self.network.update_qos_bandwidth_limit_rule.assert_called_with( + self.new_rule, self.qos_policy.id, **attrs) + self.assertIsNone(result) + + def test_set_max_burst_kbits(self): + arglist = [ + '--max-burst-kbits', str(self.new_rule.max_burst_kbits), + self.new_rule.qos_policy_id, + self.new_rule.id, + ] + verifylist = [ + ('max_burst_kbits', self.new_rule.max_burst_kbits), + ('qos_policy', self.new_rule.qos_policy_id), + ('id', self.new_rule.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = { + 'max_burst_kbps': self.new_rule.max_burst_kbits, + } + self.network.update_qos_bandwidth_limit_rule.assert_called_with( + self.new_rule, self.qos_policy.id, **attrs) + self.assertIsNone(result) + + def test_set_wrong_options(self): + arglist = [ + '--min-kbps', str(10000), + self.new_rule.qos_policy_id, + self.new_rule.id, + ] + verifylist = [ + ('min_kbps', 10000), + ('qos_policy', self.new_rule.qos_policy_id), + ('id', self.new_rule.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + try: + self.cmd.take_action(parsed_args) + except exceptions.CommandError as e: + msg = ('Failed to set Network QoS rule ID "%(rule)s": Rule type ' + '"bandwidth-limit" only requires arguments max_kbps, ' + 'max_burst_kbps' % {'rule': self.new_rule.id}) + self.assertEqual(msg, str(e)) + + +class TestListNetworkQosRule(TestNetworkQosRule): + + def setUp(self): + super(TestListNetworkQosRule, self).setUp() + attrs = {'qos_policy_id': self.qos_policy.id, + 'type': RULE_TYPE_MINIMUM_BANDWIDTH} + self.new_rule_min_bw = (network_fakes.FakeNetworkQosRule. + create_one_qos_rule(attrs=attrs)) + attrs['type'] = RULE_TYPE_DSCP_MARKING + self.new_rule_dscp_mark = (network_fakes.FakeNetworkQosRule. + create_one_qos_rule(attrs=attrs)) + attrs['type'] = RULE_TYPE_BANDWIDTH_LIMIT + self.new_rule_max_bw = (network_fakes.FakeNetworkQosRule. + create_one_qos_rule(attrs=attrs)) + self.qos_policy.rules = [self.new_rule_min_bw, + self.new_rule_dscp_mark, + self.new_rule_max_bw] + self.network.find_qos_minimum_bandwidth_rule = mock.Mock( + return_value=self.new_rule_min_bw) + self.network.find_qos_dscp_marking_rule = mock.Mock( + return_value=self.new_rule_dscp_mark) + self.network.find_qos_bandwidth_limit_rule = mock.Mock( + return_value=self.new_rule_max_bw) + self.columns = ( + 'ID', + 'QoS Policy ID', + 'Type', + 'Max Kbps', + 'Max Burst Kbits', + 'Min Kbps', + 'DSCP mark', + 'Direction', + ) + self.data = [] + for index in range(len(self.qos_policy.rules)): + self.data.append(( + self.qos_policy.rules[index].id, + self.qos_policy.rules[index].qos_policy_id, + self.qos_policy.rules[index].type, + getattr(self.qos_policy.rules[index], 'max_kbps', ''), + getattr(self.qos_policy.rules[index], 'max_burst_kbps', ''), + getattr(self.qos_policy.rules[index], 'min_kbps', ''), + getattr(self.qos_policy.rules[index], 'dscp_mark', ''), + getattr(self.qos_policy.rules[index], 'direction', ''), + )) + # Get the command object to test + self.cmd = network_qos_rule.ListNetworkQosRule(self.app, + self.namespace) + + def test_qos_rule_list(self): + arglist = [ + self.qos_policy.id + ] + verifylist = [ + ('qos_policy', self.qos_policy.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.find_qos_policy.assert_called_once_with( + self.qos_policy.id, ignore_missing=False) + self.assertEqual(self.columns, columns) + list_data = list(data) + self.assertEqual(len(self.data), len(list_data)) + for index in range(len(list_data)): + self.assertEqual(self.data[index], list_data[index]) + + +class TestShowNetworkQosRuleMinimumBandwidth(TestNetworkQosRule): + + def setUp(self): + super(TestShowNetworkQosRuleMinimumBandwidth, self).setUp() + attrs = {'qos_policy_id': self.qos_policy.id, + 'type': RULE_TYPE_MINIMUM_BANDWIDTH} + self.new_rule = network_fakes.FakeNetworkQosRule.create_one_qos_rule( + attrs) + self.qos_policy.rules = [self.new_rule] + self.columns = ( + 'direction', + 'id', + 'min_kbps', + 'project_id', + 'qos_policy_id', + 'type' + ) + self.data = ( + self.new_rule.direction, + self.new_rule.id, + self.new_rule.min_kbps, + self.new_rule.project_id, + self.new_rule.qos_policy_id, + self.new_rule.type, + ) + + self.network.get_qos_minimum_bandwidth_rule = mock.Mock( + return_value=self.new_rule) + + # Get the command object to test + self.cmd = network_qos_rule.ShowNetworkQosRule(self.app, + self.namespace) + + def test_show_no_options(self): + arglist = [] + verifylist = [] + + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_show_all_options(self): + arglist = [ + self.new_rule.qos_policy_id, + self.new_rule.id, + ] + verifylist = [ + ('qos_policy', self.new_rule.qos_policy_id), + ('id', self.new_rule.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.get_qos_minimum_bandwidth_rule.assert_called_once_with( + self.new_rule.id, self.qos_policy.id) + self.assertEqual(self.columns, columns) + self.assertEqual(list(self.data), list(data)) + + +class TestShowNetworkQosDSCPMarking(TestNetworkQosRule): + + def setUp(self): + super(TestShowNetworkQosDSCPMarking, self).setUp() + attrs = {'qos_policy_id': self.qos_policy.id, + 'type': RULE_TYPE_DSCP_MARKING} + self.new_rule = network_fakes.FakeNetworkQosRule.create_one_qos_rule( + attrs) + self.qos_policy.rules = [self.new_rule] + self.columns = ( + 'dscp_mark', + 'id', + 'project_id', + 'qos_policy_id', + 'type' + ) + self.data = ( + self.new_rule.dscp_mark, + self.new_rule.id, + self.new_rule.project_id, + self.new_rule.qos_policy_id, + self.new_rule.type, + ) + + self.network.get_qos_dscp_marking_rule = mock.Mock( + return_value=self.new_rule) + + # Get the command object to test + self.cmd = network_qos_rule.ShowNetworkQosRule(self.app, + self.namespace) + + def test_show_no_options(self): + arglist = [] + verifylist = [] + + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_show_all_options(self): + arglist = [ + self.new_rule.qos_policy_id, + self.new_rule.id, + ] + verifylist = [ + ('qos_policy', self.new_rule.qos_policy_id), + ('id', self.new_rule.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.get_qos_dscp_marking_rule.assert_called_once_with( + self.new_rule.id, self.qos_policy.id) + self.assertEqual(self.columns, columns) + self.assertEqual(list(self.data), list(data)) + + +class TestShowNetworkQosBandwidthLimit(TestNetworkQosRule): + + def setUp(self): + super(TestShowNetworkQosBandwidthLimit, self).setUp() + attrs = {'qos_policy_id': self.qos_policy.id, + 'type': RULE_TYPE_BANDWIDTH_LIMIT} + self.new_rule = network_fakes.FakeNetworkQosRule.create_one_qos_rule( + attrs) + self.qos_policy.rules = [self.new_rule] + self.columns = ( + 'id', + 'max_burst_kbits', + 'max_kbps', + 'project_id', + 'qos_policy_id', + 'type' + ) + self.data = ( + self.new_rule.id, + self.new_rule.max_burst_kbits, + self.new_rule.max_kbps, + self.new_rule.project_id, + self.new_rule.qos_policy_id, + self.new_rule.type, + ) + + self.network.get_qos_bandwidth_limit_rule = mock.Mock( + return_value=self.new_rule) + + # Get the command object to test + self.cmd = network_qos_rule.ShowNetworkQosRule(self.app, + self.namespace) + + def test_show_no_options(self): + arglist = [] + verifylist = [] + + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_show_all_options(self): + arglist = [ + self.new_rule.qos_policy_id, + self.new_rule.id, + ] + verifylist = [ + ('qos_policy', self.new_rule.qos_policy_id), + ('id', self.new_rule.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.get_qos_bandwidth_limit_rule.assert_called_once_with( + self.new_rule.id, self.qos_policy.id) + self.assertEqual(self.columns, columns) + self.assertEqual(list(self.data), list(data)) diff --git a/releasenotes/notes/add-network-qos-rule-22cc1ddd509941db.yaml b/releasenotes/notes/add-network-qos-rule-22cc1ddd509941db.yaml new file mode 100644 index 0000000000..812d510fac --- /dev/null +++ b/releasenotes/notes/add-network-qos-rule-22cc1ddd509941db.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Add support for Network QoS rule commands: + ``network qos rule create``, ``network qos rule delete``, + ``network qos rule list``, ``network qos rule show`` and + ``network qos rule set`` + [Bug `1609472 `_] diff --git a/setup.cfg b/setup.cfg index c88a4cb57b..2f22208b59 100644 --- a/setup.cfg +++ b/setup.cfg @@ -377,6 +377,12 @@ openstack.network.v2 = network_qos_policy_set = openstackclient.network.v2.network_qos_policy:SetNetworkQosPolicy network_qos_policy_show = openstackclient.network.v2.network_qos_policy:ShowNetworkQosPolicy + network_qos_rule_create = openstackclient.network.v2.network_qos_rule:CreateNetworkQosRule + network_qos_rule_delete = openstackclient.network.v2.network_qos_rule:DeleteNetworkQosRule + network_qos_rule_list = openstackclient.network.v2.network_qos_rule:ListNetworkQosRule + network_qos_rule_set = openstackclient.network.v2.network_qos_rule:SetNetworkQosRule + network_qos_rule_show = openstackclient.network.v2.network_qos_rule:ShowNetworkQosRule + network_qos_rule_type_list = openstackclient.network.v2.network_qos_rule_type:ListNetworkQosRuleType network_rbac_create = openstackclient.network.v2.network_rbac:CreateNetworkRBAC From 89b7488d2f96ff179bba4b95a0c63dda3ed1b249 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Tue, 10 Jan 2017 09:42:23 +0000 Subject: [PATCH 1472/3095] Updated from global requirements Change-Id: I3f06e0a50c4d8232796f2e42ce07eb40a139d1c4 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 81a129e0dc..26086364d7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ six>=1.9.0 # MIT Babel>=2.3.4 # BSD cliff>=2.3.0 # Apache-2.0 keystoneauth1>=2.16.0 # Apache-2.0 -openstacksdk!=0.9.11,>=0.9.10 # Apache-2.0 +openstacksdk!=0.9.11,!=0.9.12,>=0.9.10 # Apache-2.0 osc-lib>=1.2.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 oslo.utils>=3.18.0 # Apache-2.0 From 6962cc963e6e17e709524ecf6a395e2d0c8b8370 Mon Sep 17 00:00:00 2001 From: Badhmapriya Boopalan Date: Tue, 15 Nov 2016 19:55:46 +0000 Subject: [PATCH 1473/3095] To display image size in human friendly format Include option '--human-readable' to 'image show' command. This option displays image size in human readable format (such as K, M, G, T,..) Related Commit: I0ef74c2ec978483fe49156c88acf5c369a8fa5c2 Closes-Bug: #1640086 Change-Id: I28cd5702925d51303d0607ed8dccf12c56434682 --- openstackclient/image/v1/image.py | 9 +++++++ openstackclient/image/v2/image.py | 8 ++++++ openstackclient/tests/unit/image/v1/fakes.py | 2 ++ .../tests/unit/image/v1/test_image.py | 27 ++++++++++++++++++- openstackclient/tests/unit/image/v2/fakes.py | 9 ++++--- .../tests/unit/image/v2/test_image.py | 26 ++++++++++++++++++ .../notes/bug-1640086-21d7e5f2ce18f53c.yaml | 6 +++++ 7 files changed, 83 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/bug-1640086-21d7e5f2ce18f53c.yaml diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index cf389d17b7..48c90eb0cf 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -706,6 +706,12 @@ class ShowImage(command.ShowOne): def get_parser(self, prog_name): parser = super(ShowImage, self).get_parser(prog_name) + parser.add_argument( + "--human-readable", + default=False, + action='store_true', + help=_("Print image size in a human-friendly format."), + ) parser.add_argument( "image", metavar="", @@ -722,5 +728,8 @@ def take_action(self, parsed_args): info = {} info.update(image._info) + if parsed_args.human_readable: + if 'size' in info: + info['size'] = utils.format_size(info['size']) info['properties'] = utils.format_dict(info.get('properties', {})) return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 657277c0dc..cb3537b623 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -848,6 +848,12 @@ class ShowImage(command.ShowOne): def get_parser(self, prog_name): parser = super(ShowImage, self).get_parser(prog_name) + parser.add_argument( + "--human-readable", + default=False, + action='store_true', + help=_("Print image size in a human-friendly format."), + ) parser.add_argument( "image", metavar="", @@ -861,6 +867,8 @@ def take_action(self, parsed_args): image_client.images, parsed_args.image, ) + if parsed_args.human_readable: + image['size'] = utils.format_size(image['size']) info = _format_image(image) return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/tests/unit/image/v1/fakes.py b/openstackclient/tests/unit/image/v1/fakes.py index 080356ee6c..4b6d278c68 100644 --- a/openstackclient/tests/unit/image/v1/fakes.py +++ b/openstackclient/tests/unit/image/v1/fakes.py @@ -34,6 +34,7 @@ } image_properties_str = "Alpha='a', Beta='b', Gamma='g'" image_data = 'line 1\nline 2\n' +image_size = 0 IMAGE = { 'id': image_id, @@ -46,6 +47,7 @@ 'is_public': image_public, 'protected': image_protected, 'properties': image_properties, + 'size': image_size, } IMAGE_columns = tuple(sorted(IMAGE)) diff --git a/openstackclient/tests/unit/image/v1/test_image.py b/openstackclient/tests/unit/image/v1/test_image.py index aef74f0467..6b7560aa40 100644 --- a/openstackclient/tests/unit/image/v1/test_image.py +++ b/openstackclient/tests/unit/image/v1/test_image.py @@ -692,7 +692,8 @@ def test_image_update_volume(self): class TestImageShow(TestImage): - _image = image_fakes.FakeImage.create_one_image() + _image = image_fakes.FakeImage.create_one_image( + attrs={'size': 2000}) columns = ( 'container_format', 'disk_format', @@ -704,6 +705,7 @@ class TestImageShow(TestImage): 'owner', 'properties', 'protected', + 'size', ) data = ( _image.container_format, @@ -716,6 +718,7 @@ class TestImageShow(TestImage): _image.owner, utils.format_dict(_image.properties), _image.protected, + _image.size, ) def setUp(self): @@ -745,3 +748,25 @@ def test_image_show(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + + def test_image_show_human_readable(self): + arglist = [ + '--human-readable', + self._image.id, + ] + verifylist = [ + ('human_readable', True), + ('image', self._image.id), + ] + 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.images_mock.get.assert_called_with( + self._image.id, + ) + + size_index = columns.index('size') + self.assertEqual(data[size_index], '2K') diff --git a/openstackclient/tests/unit/image/v2/fakes.py b/openstackclient/tests/unit/image/v2/fakes.py index 8e2f587d29..3f02f62d46 100644 --- a/openstackclient/tests/unit/image/v2/fakes.py +++ b/openstackclient/tests/unit/image/v2/fakes.py @@ -32,6 +32,7 @@ image_protected = False image_visibility = 'public' image_tags = [] +image_size = 0 IMAGE = { 'id': image_id, @@ -39,7 +40,8 @@ 'owner': image_owner, 'protected': image_protected, 'visibility': image_visibility, - 'tags': image_tags + 'tags': image_tags, + 'size': image_size } IMAGE_columns = tuple(sorted(IMAGE)) @@ -106,7 +108,8 @@ "size": { "type": [ "null", - "integer" + "integer", + "string" ], "description": "Size of image file in bytes (READ-ONLY)" }, @@ -185,7 +188,7 @@ def create_one_image(attrs=None): A dictionary with all attrbutes of image :return: A FakeResource object with id, name, owner, protected, - visibility and tags attrs + visibility, tags and size attrs """ attrs = attrs or {} diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py index ebc9c3a759..a69a42de27 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -1177,6 +1177,9 @@ def test_image_set_dead_options(self): class TestImageShow(TestImage): + new_image = image_fakes.FakeImage.create_one_image( + attrs={'size': 1000}) + def setUp(self): super(TestImageShow, self).setUp() @@ -1211,6 +1214,29 @@ def test_image_show(self): self.assertEqual(image_fakes.IMAGE_columns, columns) self.assertEqual(image_fakes.IMAGE_SHOW_data, data) + def test_image_show_human_readable(self): + self.images_mock.get.return_value = self.new_image + arglist = [ + '--human-readable', + self.new_image.id, + ] + verifylist = [ + ('human_readable', True), + ('image', self.new_image.id), + ] + 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.images_mock.get.assert_called_with( + self.new_image.id, + ) + + size_index = columns.index('size') + self.assertEqual(data[size_index], '1K') + class TestImageUnset(TestImage): diff --git a/releasenotes/notes/bug-1640086-21d7e5f2ce18f53c.yaml b/releasenotes/notes/bug-1640086-21d7e5f2ce18f53c.yaml new file mode 100644 index 0000000000..83d6f7bd47 --- /dev/null +++ b/releasenotes/notes/bug-1640086-21d7e5f2ce18f53c.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--human-readable`` option to ``image show`` to display + image size in human readable format (such as K, M, G, T,..) + [Bug `1640086 `_] From d98b72c245a71ce289a7e18069fabfc433893914 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Wed, 11 Jan 2017 11:21:21 -0500 Subject: [PATCH 1474/3095] add support for running tests with master dependencies we can create a new infra job that points to the new test hook, which should install the master version of the SDK, osc-lib and os-client-config. Change-Id: Ib6391893b2302bdc514525d5ddda886fe8c60100 --- .../tests/functional/post_test_hook_tips.sh | 46 +++++++++++++++++++ tox.ini | 9 ++++ 2 files changed, 55 insertions(+) create mode 100755 openstackclient/tests/functional/post_test_hook_tips.sh diff --git a/openstackclient/tests/functional/post_test_hook_tips.sh b/openstackclient/tests/functional/post_test_hook_tips.sh new file mode 100755 index 0000000000..994142d883 --- /dev/null +++ b/openstackclient/tests/functional/post_test_hook_tips.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +# This is a script that kicks off a series of functional tests against an +# OpenStack cloud. It will attempt to create an instance if one is not +# available. Do not run this script unless you know what you're doing. +# For more information refer to: +# http://docs.openstack.org/developer/python-openstackclient/ + +# This particular script differs from the normal post_test_hook because +# it installs the master (tip) version of osc-lib and openstacksdk + +function generate_testr_results { + if [ -f .testrepository/0 ]; then + sudo .tox/functional/bin/testr last --subunit > $WORKSPACE/testrepository.subunit + sudo mv $WORKSPACE/testrepository.subunit $BASE/logs/testrepository.subunit + sudo .tox/functional/bin/subunit2html $BASE/logs/testrepository.subunit $BASE/logs/testr_results.html + sudo gzip -9 $BASE/logs/testrepository.subunit + sudo gzip -9 $BASE/logs/testr_results.html + sudo chown jenkins:jenkins $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz + sudo chmod a+r $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz + fi +} + +export OPENSTACKCLIENT_DIR="$BASE/new/python-openstackclient" +sudo chown -R jenkins:stack $OPENSTACKCLIENT_DIR + +# Go to the openstackclient dir +cd $OPENSTACKCLIENT_DIR + +# Run tests +echo "Running openstackclient functional test suite" +set +e + +# Source environment variables to kick things off +source ~stack/devstack/openrc admin admin +echo 'Running tests with:' +env | grep OS + +# Preserve env for OS_ credentials +sudo -E -H -u jenkins tox -e functional-tips +EXIT_CODE=$? +set -e + +# Collect and parse result +generate_testr_results +exit $EXIT_CODE diff --git a/tox.ini b/tox.ini index af7120e11e..e7bb652762 100644 --- a/tox.ini +++ b/tox.ini @@ -54,6 +54,15 @@ commands = setenv = OS_TEST_PATH=./openstackclient/tests/functional passenv = OS_* +[testenv:functional-tips] +setenv = OS_TEST_PATH=./openstackclient/tests/functional +passenv = OS_* +commands = + pip install -q -U -e "git+https://git.openstack.org/openstack/osc-lib.git#egg=osc_lib" + pip install -q -U -e "git+https://git.openstack.org/openstack/python-openstacksdk.git#egg=python_openstacksdk" + pip install -q -U -e "git+https://git.openstack.org/openstack/os-client-config.git#egg=os_client_config" + ostestr {posargs} + [testenv:venv] commands = {posargs} From 28f9a9c6211839c0f3320dcbb0e8288d6627fa43 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Fri, 2 Dec 2016 14:26:35 +0800 Subject: [PATCH 1475/3095] Add unit test for multi volume types delete Missing unit test for multi volume types delete in volume v2 (v1 has been done), this patch add it. Change-Id: I5fe67196408157f8bdfe6399ba1e559cea3dc559 --- openstackclient/tests/unit/volume/v2/fakes.py | 20 +++++++ .../tests/unit/volume/v2/test_type.py | 58 +++++++++++++++++-- 2 files changed, 73 insertions(+), 5 deletions(-) diff --git a/openstackclient/tests/unit/volume/v2/fakes.py b/openstackclient/tests/unit/volume/v2/fakes.py index d5cd72ecce..a667640347 100644 --- a/openstackclient/tests/unit/volume/v2/fakes.py +++ b/openstackclient/tests/unit/volume/v2/fakes.py @@ -903,3 +903,23 @@ def create_types(attrs=None, count=2): volume_types.append(volume_type) return volume_types + + @staticmethod + def get_types(types=None, count=2): + """Get an iterable MagicMock object with a list of faked types. + + If types list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List types: + A list of FakeResource objects faking types + :param Integer count: + The number of types to be faked + :return + An iterable Mock object with side_effect set to a list of faked + types + """ + if types is None: + types = FakeType.create_types(count) + + return mock.Mock(side_effect=types) diff --git a/openstackclient/tests/unit/volume/v2/test_type.py b/openstackclient/tests/unit/volume/v2/test_type.py index 0d556e13f7..cec01bd8ef 100644 --- a/openstackclient/tests/unit/volume/v2/test_type.py +++ b/openstackclient/tests/unit/volume/v2/test_type.py @@ -13,6 +13,7 @@ # import mock +from mock import call from osc_lib import exceptions from osc_lib import utils @@ -133,12 +134,13 @@ def test_public_type_create_with_project(self): class TestTypeDelete(TestType): - volume_type = volume_fakes.FakeType.create_one_type() + volume_types = volume_fakes.FakeType.create_types(count=2) def setUp(self): super(TestTypeDelete, self).setUp() - self.types_mock.get.return_value = self.volume_type + self.types_mock.get = volume_fakes.FakeType.get_types( + self.volume_types) self.types_mock.delete.return_value = None # Get the command object to mock @@ -146,18 +148,64 @@ def setUp(self): def test_type_delete(self): arglist = [ - self.volume_type.id + self.volume_types[0].id ] verifylist = [ - ("volume_types", [self.volume_type.id]) + ("volume_types", [self.volume_types[0].id]) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.types_mock.delete.assert_called_with(self.volume_type) + self.types_mock.delete.assert_called_with(self.volume_types[0]) self.assertIsNone(result) + def test_delete_multiple_types(self): + arglist = [] + for t in self.volume_types: + arglist.append(t.id) + verifylist = [ + ('volume_types', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + calls = [] + for t in self.volume_types: + calls.append(call(t)) + self.types_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_delete_multiple_types_with_exception(self): + arglist = [ + self.volume_types[0].id, + 'unexist_type', + ] + verifylist = [ + ('volume_types', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self.volume_types[0], exceptions.CommandError] + with mock.patch.object(utils, 'find_resource', + side_effect=find_mock_result) as find_mock: + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 volume types failed to delete.', + str(e)) + find_mock.assert_any_call( + self.types_mock, self.volume_types[0].id) + find_mock.assert_any_call(self.types_mock, 'unexist_type') + + self.assertEqual(2, find_mock.call_count) + self.types_mock.delete.assert_called_once_with( + self.volume_types[0] + ) + class TestTypeList(TestType): From 4b2355b3e3cdbdb4e1a395e313147decf6221bc4 Mon Sep 17 00:00:00 2001 From: Rodolfo Alonso Hernandez Date: Thu, 12 Jan 2017 13:21:07 +0000 Subject: [PATCH 1476/3095] Fix Network QoS rule CLI attrs parsing Allows to set zero values in the CLI parameters for Network QoS rule. Change-Id: Ie0e045ff4888615d68804fd739d5b995ca11e9a1 Closes-Bug: #1655947 --- .../network/v2/network_qos_rule.py | 8 +-- .../unit/network/v2/test_network_qos_rule.py | 52 +++++++++++++++++++ 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/openstackclient/network/v2/network_qos_rule.py b/openstackclient/network/v2/network_qos_rule.py index 054d16b4ab..a662ca18a3 100644 --- a/openstackclient/network/v2/network_qos_rule.py +++ b/openstackclient/network/v2/network_qos_rule.py @@ -85,16 +85,16 @@ def _get_attrs(network_client, parsed_args, is_create=False): msg = _('"Create" rule command requires argument "type"') raise exceptions.CommandError(msg) rule_type = parsed_args.type - if parsed_args.max_kbps: + if parsed_args.max_kbps is not None: attrs['max_kbps'] = parsed_args.max_kbps - if parsed_args.max_burst_kbits: + if parsed_args.max_burst_kbits is not None: # NOTE(ralonsoh): this parameter must be changed in SDK and then in # Neutron API, from 'max_burst_kbps' to # 'max_burst_kbits' attrs['max_burst_kbps'] = parsed_args.max_burst_kbits - if parsed_args.dscp_mark: + if parsed_args.dscp_mark is not None: attrs['dscp_mark'] = parsed_args.dscp_mark - if parsed_args.min_kbps: + if parsed_args.min_kbps is not None: attrs['min_kbps'] = parsed_args.min_kbps if parsed_args.ingress: attrs['direction'] = 'ingress' diff --git a/openstackclient/tests/unit/network/v2/test_network_qos_rule.py b/openstackclient/tests/unit/network/v2/test_network_qos_rule.py index e66f25b700..41ccae32a7 100644 --- a/openstackclient/tests/unit/network/v2/test_network_qos_rule.py +++ b/openstackclient/tests/unit/network/v2/test_network_qos_rule.py @@ -523,6 +523,16 @@ def test_set_nothing(self): self.assertIsNone(result) def test_set_min_kbps(self): + self._set_min_kbps() + + def test_set_min_kbps_to_zero(self): + self._set_min_kbps(min_kbps=0) + + def _set_min_kbps(self, min_kbps=None): + if min_kbps: + previous_min_kbps = self.new_rule.min_kbps + self.new_rule.min_kbps = min_kbps + arglist = [ '--min-kbps', str(self.new_rule.min_kbps), self.new_rule.qos_policy_id, @@ -544,6 +554,9 @@ def test_set_min_kbps(self): self.new_rule, self.qos_policy.id, **attrs) self.assertIsNone(result) + if min_kbps: + self.new_rule.min_kbps = previous_min_kbps + def test_set_wrong_options(self): arglist = [ '--max-kbps', str(10000), @@ -604,6 +617,16 @@ def test_set_nothing(self): self.assertIsNone(result) def test_set_dscp_mark(self): + self._set_dscp_mark() + + def test_set_dscp_mark_to_zero(self): + self._set_dscp_mark(dscp_mark=0) + + def _set_dscp_mark(self, dscp_mark=None): + if dscp_mark: + previous_dscp_mark = self.new_rule.dscp_mark + self.new_rule.dscp_mark = dscp_mark + arglist = [ '--dscp-mark', str(self.new_rule.dscp_mark), self.new_rule.qos_policy_id, @@ -625,6 +648,9 @@ def test_set_dscp_mark(self): self.new_rule, self.qos_policy.id, **attrs) self.assertIsNone(result) + if dscp_mark: + self.new_rule.dscp_mark = previous_dscp_mark + def test_set_wrong_options(self): arglist = [ '--max-kbps', str(10000), @@ -685,6 +711,16 @@ def test_set_nothing(self): self.assertIsNone(result) def test_set_max_kbps(self): + self._set_max_kbps() + + def test_set_max_kbps_to_zero(self): + self._set_max_kbps(max_kbps=0) + + def _set_max_kbps(self, max_kbps=None): + if max_kbps: + previous_max_kbps = self.new_rule.max_kbps + self.new_rule.max_kbps = max_kbps + arglist = [ '--max-kbps', str(self.new_rule.max_kbps), self.new_rule.qos_policy_id, @@ -706,7 +742,20 @@ def test_set_max_kbps(self): self.new_rule, self.qos_policy.id, **attrs) self.assertIsNone(result) + if max_kbps: + self.new_rule.max_kbps = previous_max_kbps + def test_set_max_burst_kbits(self): + self._set_max_burst_kbits() + + def test_set_max_burst_kbits_to_zero(self): + self._set_max_burst_kbits(max_burst_kbits=0) + + def _set_max_burst_kbits(self, max_burst_kbits=None): + if max_burst_kbits: + previous_max_burst_kbits = self.new_rule.max_burst_kbits + self.new_rule.max_burst_kbits = max_burst_kbits + arglist = [ '--max-burst-kbits', str(self.new_rule.max_burst_kbits), self.new_rule.qos_policy_id, @@ -728,6 +777,9 @@ def test_set_max_burst_kbits(self): self.new_rule, self.qos_policy.id, **attrs) self.assertIsNone(result) + if max_burst_kbits: + self.new_rule.max_burst_kbits = previous_max_burst_kbits + def test_set_wrong_options(self): arglist = [ '--min-kbps', str(10000), From e637e9c6c8782bce0ae654f01cacf6d486460728 Mon Sep 17 00:00:00 2001 From: Pavlo Shchelokovskyy Date: Thu, 12 Jan 2017 18:19:01 +0200 Subject: [PATCH 1477/3095] Fix flavor create help re swap size units nova actually expects and uses swap size in MB, while in openstackclient currently help states that swap must be specified in GB and passes this value to nova without changes. Fix the help string. Change-Id: I95f46246c072961ce77f818d80d75e6a51f728d0 Closes-Bug: #1656018 --- doc/source/command-objects/flavor.rst | 4 ++-- openstackclient/compute/v2/flavor.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/source/command-objects/flavor.rst b/doc/source/command-objects/flavor.rst index 0900f2edf7..971628d720 100644 --- a/doc/source/command-objects/flavor.rst +++ b/doc/source/command-objects/flavor.rst @@ -42,9 +42,9 @@ Create new flavor Ephemeral disk size in GB (default 0G) -.. option:: --swap +.. option:: --swap - Swap space size in GB (default 0G) + Swap space size in MB (default 0M) .. option:: --vcpus diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index e562cd40ca..7cd22ed7a9 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -89,9 +89,9 @@ def get_parser(self, prog_name): parser.add_argument( "--swap", type=int, - metavar="", + metavar="", default=0, - help=_("Swap space size in GB (default 0G)") + help=_("Swap space size in MB (default 0M)") ) parser.add_argument( "--vcpus", From 96f3c7e78315e77ca4b550cff71b104095f8dbc3 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 12 Jan 2017 09:41:43 -0600 Subject: [PATCH 1478/3095] Fix image member unit tests occasional failures Let's try just unrolling the member status tests and not getting too fancy here. Change-Id: I30022ebd2da6d6cf1abba424d7d7fa679285f291 --- .../tests/unit/image/v2/test_image.py | 107 ++++++++++++++---- 1 file changed, 87 insertions(+), 20 deletions(-) diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py index a15131190f..164185dfda 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -829,6 +829,11 @@ def setUp(self): self.images_mock.get.return_value = self.model(**image_fakes.IMAGE) self.images_mock.update.return_value = self.model(**image_fakes.IMAGE) + + self.app.client_manager.auth_ref = mock.Mock( + project_id=self.project.id, + ) + # Get the command object to test self.cmd = image.SetImage(self.app, None) @@ -847,32 +852,94 @@ def test_image_set_no_options(self): self.image_members_mock.update.assert_not_called() - def test_image_set_membership_option(self): + def test_image_set_membership_option_accept(self): + membership = image_fakes.FakeImage.create_one_image_member( + attrs={'image_id': image_fakes.image_id, + 'member_id': self.project.id} + ) + self.image_members_mock.update.return_value = membership + + arglist = [ + '--accept', + image_fakes.image_id, + ] + verifylist = [ + ('accept', True), + ('reject', False), + ('pending', False), + ('image', image_fakes.image_id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.image_members_mock.update.assert_called_once_with( + image_fakes.image_id, + self.app.client_manager.auth_ref.project_id, + 'accepted', + ) + + # Assert that the 'update image" route is also called, in addition to + # the 'update membership' route. + self.images_mock.update.assert_called_with(image_fakes.image_id) + + def test_image_set_membership_option_reject(self): + membership = image_fakes.FakeImage.create_one_image_member( + attrs={'image_id': image_fakes.image_id, + 'member_id': self.project.id} + ) + self.image_members_mock.update.return_value = membership + + arglist = [ + '--reject', + image_fakes.image_id, + ] + verifylist = [ + ('accept', False), + ('reject', True), + ('pending', False), + ('image', image_fakes.image_id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.image_members_mock.update.assert_called_once_with( + image_fakes.image_id, + self.app.client_manager.auth_ref.project_id, + 'rejected', + ) + + # Assert that the 'update image" route is also called, in addition to + # the 'update membership' route. + self.images_mock.update.assert_called_with(image_fakes.image_id) + + def test_image_set_membership_option_pending(self): membership = image_fakes.FakeImage.create_one_image_member( attrs={'image_id': image_fakes.image_id, 'member_id': self.project.id} ) self.image_members_mock.update.return_value = membership - for status in ('accept', 'reject', 'pending'): - arglist = [ - '--%s' % status, - image_fakes.image_id, - ] - verifylist = [ - (status, True), - ('image', image_fakes.image_id) - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) - - self.image_members_mock.update.assert_called_once_with( - image_fakes.image_id, - self.app.client_manager.auth_ref.project_id, - status if status == 'pending' else status + 'ed' - ) - self.image_members_mock.update.reset_mock() + arglist = [ + '--pending', + image_fakes.image_id, + ] + verifylist = [ + ('accept', False), + ('reject', False), + ('pending', True), + ('image', image_fakes.image_id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.image_members_mock.update.assert_called_once_with( + image_fakes.image_id, + self.app.client_manager.auth_ref.project_id, + 'pending', + ) # Assert that the 'update image" route is also called, in addition to # the 'update membership' route. From 511b7880ea0dae6042426aac3743ac9941fc8db3 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Wed, 11 Jan 2017 15:45:19 -0500 Subject: [PATCH 1479/3095] unskip network qos rule type functional test with the SDK at 0.9.12 the below test fails with: 'minimum_bandwidth' not in +-----------------+ | Type | +-----------------+ | dscp_marking | | bandwidth_limit | +-----------------+ So remove 'minimum_bandwidth' from the asserted fields. Change-Id: I24ff691ae5a946d901afa763973305025829280b Closes-Bug: 1653137 --- .../functional/network/v2/test_network_qos_rule_type.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/openstackclient/tests/functional/network/v2/test_network_qos_rule_type.py b/openstackclient/tests/functional/network/v2/test_network_qos_rule_type.py index 2bb04a9d40..7dff0cbdad 100644 --- a/openstackclient/tests/functional/network/v2/test_network_qos_rule_type.py +++ b/openstackclient/tests/functional/network/v2/test_network_qos_rule_type.py @@ -13,8 +13,6 @@ # License for the specific language governing permissions and limitations # under the License. -import testtools - from openstackclient.tests.functional import base @@ -22,10 +20,8 @@ class NetworkQosRuleTypeTests(base.TestCase): """Functional tests for Network QoS rule type. """ AVAILABLE_RULE_TYPES = ['dscp_marking', - 'bandwidth_limit', - 'minimum_bandwidth'] + 'bandwidth_limit'] - @testtools.skip('broken SDK testing') def test_qos_rule_type_list(self): raw_output = self.openstack('network qos rule type list') for rule_type in self.AVAILABLE_RULE_TYPES: From 024bd3bd660490e458456c28674dcd0ad4ee13c8 Mon Sep 17 00:00:00 2001 From: Reedip Date: Wed, 11 Jan 2017 12:34:48 -0500 Subject: [PATCH 1480/3095] Fix quota show output Currently Quota Show expects dictionary to be returned for Network client, similar to Volume and Compute clients, but Network Object is being returned, causing the "openstack quota show" to fail. This patch takes care of this issue. Depends-On: Ie0e045ff4888615d68804fd739d5b995ca11e9a1 Change-Id: Ic507997cba09fcfa84dd1151d6922f56a7c5187b Closes-Bug:#1655537 --- openstackclient/common/quota.py | 4 ++++ releasenotes/notes/bug-1655537-20b0eb676afa278f.yaml | 6 ++++++ 2 files changed, 10 insertions(+) create mode 100644 releasenotes/notes/bug-1655537-20b0eb676afa278f.yaml diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index 58368c5620..afc6195f93 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -273,6 +273,10 @@ def take_action(self, parsed_args): volume_quota_info = self.get_compute_volume_quota(volume_client, parsed_args) network_quota_info = self.get_network_quota(parsed_args) + # NOTE(reedip): Remove the below check once requirement for + # Openstack SDK is fixed to version 0.9.12 and above + if type(network_quota_info) is not dict: + network_quota_info = network_quota_info.to_dict() info = {} info.update(compute_quota_info) diff --git a/releasenotes/notes/bug-1655537-20b0eb676afa278f.yaml b/releasenotes/notes/bug-1655537-20b0eb676afa278f.yaml new file mode 100644 index 0000000000..77cb3df073 --- /dev/null +++ b/releasenotes/notes/bug-1655537-20b0eb676afa278f.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Fixed a ``'Quota' object is not iterable`` error in the ``quota show`` command + that appeared with the initial release of openstacksdk v0.9.11 and v0.9.12. + [Bug `1655537 `_] From 64385002eaa6192bcbcb27b696ebac74e6b35fd7 Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Thu, 12 Jan 2017 17:14:48 -0500 Subject: [PATCH 1481/3095] Install from /opt/stack/new instead of git.o.o Installing directly from the git url will bypass any depends-on processing zuul may have done on the dependent repos. Change-Id: I3dadacf7a855cc4efad701f0a6275d6cd60efd72 --- tox.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tox.ini b/tox.ini index e7bb652762..22d394c439 100644 --- a/tox.ini +++ b/tox.ini @@ -58,9 +58,9 @@ passenv = OS_* setenv = OS_TEST_PATH=./openstackclient/tests/functional passenv = OS_* commands = - pip install -q -U -e "git+https://git.openstack.org/openstack/osc-lib.git#egg=osc_lib" - pip install -q -U -e "git+https://git.openstack.org/openstack/python-openstacksdk.git#egg=python_openstacksdk" - pip install -q -U -e "git+https://git.openstack.org/openstack/os-client-config.git#egg=os_client_config" + pip install -q -U -e /opt/stack/new/osc-lib + pip install -q -U -e /opt/stack/new/python-openstacksdk + pip install -q -U -e /opt/stack/new/os-client-config ostestr {posargs} [testenv:venv] From 5988ee61d894417813450e5ecc8fdf704b015240 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 13 Jan 2017 11:56:34 -0600 Subject: [PATCH 1482/3095] Fix security group rule list for NEtwork v2 Fix the formatting of Port Range in the security group rule list command for Network v2 to handle SDK changes. Change-Id: Id954cbfaedbb74f60125ebda91f80db751759933 --- .../network/v2/security_group_rule.py | 45 +++++++++++-------- .../network/v2/test_security_group_rule.py | 3 -- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index b878d8751b..4fb62c7bff 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -51,17 +51,17 @@ def _format_network_port_range(rule): # - Single port: '80:80' # - No port range: '' port_range = '' - if _is_icmp_protocol(rule.protocol): - if rule.port_range_min: - port_range += 'type=' + str(rule.port_range_min) - if rule.port_range_max: - port_range += ':code=' + str(rule.port_range_max) - elif rule.port_range_min or rule.port_range_max: - port_range_min = str(rule.port_range_min) - port_range_max = str(rule.port_range_max) - if rule.port_range_min is None: + if _is_icmp_protocol(rule['protocol']): + if rule['port_range_min']: + port_range += 'type=' + str(rule['port_range_min']) + if rule['port_range_max']: + port_range += ':code=' + str(rule['port_range_max']) + elif rule['port_range_min'] or rule['port_range_max']: + port_range_min = str(rule['port_range_min']) + port_range_max = str(rule['port_range_max']) + if rule['port_range_min'] is None: port_range_min = port_range_max - if rule.port_range_max is None: + if rule['port_range_max'] is None: port_range_max = port_range_min port_range = port_range_min + ':' + port_range_max return port_range @@ -423,6 +423,16 @@ def take_action_compute(self, client, parsed_args): class ListSecurityGroupRule(common.NetworkAndComputeLister): _description = _("List security group rules") + def _format_network_security_group_rule(self, rule): + """Transform the SDK SecurityGroupRule object to a dict + + The SDK object gets in the way of reformatting columns... + Create port_range column from port_range_min and port_range_max + """ + rule = rule.to_dict() + rule['port_range'] = _format_network_port_range(rule) + return rule + def update_parser_common(self, parser): parser.add_argument( 'group', @@ -508,7 +518,7 @@ def take_action_network(self, client, parsed_args): 'id', 'protocol', 'remote_ip_prefix', - 'port_range_min', + 'port_range', ) if parsed_args.long: columns = columns + ('direction', 'ethertype',) @@ -535,16 +545,13 @@ def take_action_network(self, client, parsed_args): if parsed_args.protocol is not None: query['protocol'] = parsed_args.protocol - rules = list(client.security_group_rules(**query)) - - # Reformat the rules to display a port range instead - # of just the port range minimum. This maintains - # output compatibility with compute. - for rule in rules: - rule.port_range_min = _format_network_port_range(rule) + rules = [ + self._format_network_security_group_rule(r) + for r in client.security_group_rules(**query) + ] return (column_headers, - (utils.get_item_properties( + (utils.get_dict_properties( s, columns, ) for s in rules)) diff --git a/openstackclient/tests/functional/network/v2/test_security_group_rule.py b/openstackclient/tests/functional/network/v2/test_security_group_rule.py index ec3731eb9a..c91de1a570 100644 --- a/openstackclient/tests/functional/network/v2/test_security_group_rule.py +++ b/openstackclient/tests/functional/network/v2/test_security_group_rule.py @@ -12,8 +12,6 @@ import uuid -import testtools - from openstackclient.tests.functional import base @@ -54,7 +52,6 @@ def tearDownClass(cls): cls.SECURITY_GROUP_NAME) cls.assertOutput('', raw_output) - @testtools.skip('broken SDK testing') def test_security_group_rule_list(self): opts = self.get_opts(self.ID_HEADER) raw_output = self.openstack('security group rule list ' + From 14ff3ba19e2cb5e6b7b92b15cf6a33474c3adde3 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Fri, 13 Jan 2017 12:35:49 -0500 Subject: [PATCH 1483/3095] fix functional tests for network agents As of SDK v0.9.11 the get_agent method no longer supports the "ignore_missing" parameter. Change-Id: Id655bf8499ed1a102a6bf583927cf66139581ab0 --- openstackclient/network/v2/network_agent.py | 4 ++-- .../tests/functional/network/v2/test_network_agent.py | 4 ---- openstackclient/tests/unit/network/v2/test_network_agent.py | 2 +- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/openstackclient/network/v2/network_agent.py b/openstackclient/network/v2/network_agent.py index b3411166fa..7d36e5e9f0 100644 --- a/openstackclient/network/v2/network_agent.py +++ b/openstackclient/network/v2/network_agent.py @@ -168,7 +168,7 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): client = self.app.client_manager.network - obj = client.get_agent(parsed_args.network_agent, ignore_missing=False) + obj = client.get_agent(parsed_args.network_agent) attrs = {} if parsed_args.description is not None: attrs['description'] = str(parsed_args.description) @@ -193,7 +193,7 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): client = self.app.client_manager.network - obj = client.get_agent(parsed_args.network_agent, ignore_missing=False) + obj = client.get_agent(parsed_args.network_agent) columns = tuple(sorted(list(obj.keys()))) data = utils.get_item_properties(obj, columns, formatters=_formatters,) return columns, data diff --git a/openstackclient/tests/functional/network/v2/test_network_agent.py b/openstackclient/tests/functional/network/v2/test_network_agent.py index e99dcef6e7..dd6112e72e 100644 --- a/openstackclient/tests/functional/network/v2/test_network_agent.py +++ b/openstackclient/tests/functional/network/v2/test_network_agent.py @@ -10,8 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -import testtools - from openstackclient.tests.functional import base @@ -28,13 +26,11 @@ def test_network_agent_list(cls): # get the list of network agent IDs. cls.IDs = raw_output.split('\n') - @testtools.skip('broken SDK testing') def test_network_agent_show(self): opts = self.get_opts(self.FIELDS) raw_output = self.openstack('network agent show ' + self.IDs[0] + opts) self.assertEqual(self.IDs[0] + "\n", raw_output) - @testtools.skip('broken SDK testing') def test_network_agent_set(self): opts = self.get_opts(['admin_state_up']) self.openstack('network agent set --disable ' + self.IDs[0]) diff --git a/openstackclient/tests/unit/network/v2/test_network_agent.py b/openstackclient/tests/unit/network/v2/test_network_agent.py index 9fd395b48b..9964f14da5 100644 --- a/openstackclient/tests/unit/network/v2/test_network_agent.py +++ b/openstackclient/tests/unit/network/v2/test_network_agent.py @@ -324,6 +324,6 @@ def test_show_all_options(self): columns, data = self.cmd.take_action(parsed_args) self.network.get_agent.assert_called_once_with( - self._network_agent.id, ignore_missing=False) + self._network_agent.id) self.assertEqual(self.columns, columns) self.assertEqual(list(self.data), list(data)) From 7329e640db78eed23297da9fea3b28e3b194c2ff Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Sat, 14 Jan 2017 20:20:09 +0800 Subject: [PATCH 1484/3095] Fix subnet creating failure in functional test Noticed sometimes floating ip func test failed[1]. The first mumber of the address seems like cannot bigger than 223. So specify subnet ranges as the random number between 0 to 233, maybe it will safer for our functional tests. [1]: http://logs.openstack.org/50/418650/12/check/gate-osc-dsvm-functional-ubuntu-xenial/e163f68/console.html#_2017-01-14_06_58_35_930306 Change-Id: I44a23bce851dcf2009c8d77059cf75ed80145fb0 --- openstackclient/tests/functional/network/v2/test_floating_ip.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/tests/functional/network/v2/test_floating_ip.py b/openstackclient/tests/functional/network/v2/test_floating_ip.py index 5f642f0415..17fec501b0 100644 --- a/openstackclient/tests/functional/network/v2/test_floating_ip.py +++ b/openstackclient/tests/functional/network/v2/test_floating_ip.py @@ -37,7 +37,7 @@ def setUpClass(cls): # Make a random subnet cls.subnet = ".".join(map( str, - (random.randint(0, 255) for _ in range(3)) + (random.randint(0, 223) for _ in range(3)) )) + ".0/26" # Create a network for the floating ip From b2fd8ba869cd4b8e927118f7712d0ed7fb60309f Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Thu, 22 Dec 2016 23:27:26 +0800 Subject: [PATCH 1485/3095] Add "encryption-*" options in volume type commands Add "--encryption-provider", "--encryption-cipher", "--encryption-key-size" and "--encryption-control-location" options to "volume type set" and "volume type create" commands. Add "--encryption-type" option to "volume type unset", "volume type list" and "volume type show" commands. Change-Id: I3572635d5913d971a723a62d7790ffe0f20ec39a Implements: bp cinder-command-support Closes-Bug: #1651117 --- doc/source/command-objects/volume-type.rst | 81 +++++ .../functional/volume/v1/test_volume_type.py | 71 +++++ .../functional/volume/v2/test_volume_type.py | 87 ++++++ openstackclient/tests/unit/volume/v1/fakes.py | 31 ++ .../tests/unit/volume/v1/test_type.py | 237 ++++++++++++++- openstackclient/tests/unit/volume/v2/fakes.py | 31 ++ .../tests/unit/volume/v2/test_type.py | 276 +++++++++++++++++- openstackclient/volume/v1/volume_type.py | 221 +++++++++++++- openstackclient/volume/v2/volume_type.py | 210 ++++++++++++- .../notes/bug-1651117-a1df37e7ea939ba4.yaml | 9 + 10 files changed, 1235 insertions(+), 19 deletions(-) create mode 100644 releasenotes/notes/bug-1651117-a1df37e7ea939ba4.yaml diff --git a/doc/source/command-objects/volume-type.rst b/doc/source/command-objects/volume-type.rst index 40e9825c87..afa293d7b8 100644 --- a/doc/source/command-objects/volume-type.rst +++ b/doc/source/command-objects/volume-type.rst @@ -18,6 +18,10 @@ Create new volume type [--property [...] ] [--project ] [--project-domain ] + [--encryption-provider ] + [--encryption-cipher ] + [--encryption-key-size ] + [--encryption-control-location ] .. option:: --description @@ -56,6 +60,34 @@ Create new volume type *Volume version 2 only* +.. option:: --encryption-provider + + Set the class that provides encryption support for this volume type + (e.g "LuksEncryptor") (admin only) + + This option is required when setting encryption type of a volume. + Consider using other encryption options such as: :option:`--encryption-cipher`, + :option:`--encryption-key-size` and :option:`--encryption-control-location` + +.. option:: --encryption-cipher + + Set the encryption algorithm or mode for this volume type + (e.g "aes-xts-plain64") (admin only) + +.. option:: --encryption-key-size + + Set the size of the encryption key of this volume type + (e.g "128" or "256") (admin only) + +.. option:: --encryption-control-location + + Set the notional service where the encryption is performed + ("front-end" or "back-end") (admin only) + + The default value for this option is "front-end" when setting encryption type of + a volume. Consider using other encryption options such as: :option:`--encryption-cipher`, + :option:`--encryption-key-size` and :option:`--encryption-provider` + .. _volume_type_create-name: .. describe:: @@ -88,6 +120,7 @@ List volume types openstack volume type list [--long] [--default | --public | --private] + [--encryption-type] .. option:: --long @@ -111,6 +144,10 @@ List volume types *Volume version 2 only* +.. option:: --encryption-type + + Display encryption information for each volume type (admin only) + volume type set --------------- @@ -125,6 +162,10 @@ Set volume type properties [--property [...] ] [--project ] [--project-domain ] + [--encryption-provider ] + [--encryption-cipher ] + [--encryption-key-size ] + [--encryption-control-location ] .. option:: --name @@ -154,6 +195,34 @@ Set volume type properties Set a property on this volume type (repeat option to set multiple properties) +.. option:: --encryption-provider + + Set the class that provides encryption support for this volume type + (e.g "LuksEncryptor") (admin only) + + This option is required when setting encryption type of a volume for the first time. + Consider using other encryption options such as: :option:`--encryption-cipher`, + :option:`--encryption-key-size` and :option:`--encryption-control-location` + +.. option:: --encryption-cipher + + Set the encryption algorithm or mode for this volume type + (e.g "aes-xts-plain64") (admin only) + +.. option:: --encryption-key-size + + Set the size of the encryption key of this volume type + (e.g "128" or "256") (admin only) + +.. option:: --encryption-control-location + + Set the notional service where the encryption is performed + ("front-end" or "back-end") (admin only) + + The default value for this option is "front-end" when setting encryption type of + a volume for the first time. Consider using other encryption options such as: + :option:`--encryption-cipher`, :option:`--encryption-key-size` and :option:`--encryption-provider` + .. _volume_type_set-volume-type: .. describe:: @@ -168,8 +237,13 @@ Display volume type details .. code:: bash openstack volume type show + [--encryption-type] +.. option:: --encryption-type + + Display encryption information of this volume type (admin only) + .. _volume_type_show-volume-type: .. describe:: @@ -187,6 +261,7 @@ Unset volume type properties [--property [...] ] [--project ] [--project-domain ] + [--encryption-type] .. option:: --property @@ -204,6 +279,12 @@ Unset volume type properties Domain the project belongs to (name or ID). This can be used in case collisions between project names exist. + *Volume version 2 only* + +.. option:: --encryption-type + + Remove the encryption type for this volume type (admin only) + .. _volume_type_unset-volume-type: .. describe:: diff --git a/openstackclient/tests/functional/volume/v1/test_volume_type.py b/openstackclient/tests/functional/volume/v1/test_volume_type.py index 955759b6b2..d1842795df 100644 --- a/openstackclient/tests/functional/volume/v1/test_volume_type.py +++ b/openstackclient/tests/functional/volume/v1/test_volume_type.py @@ -87,3 +87,74 @@ def test_multi_delete(self): time.sleep(5) raw_output = self.openstack(cmd) self.assertOutput('', raw_output) + + # NOTE: Add some basic funtional tests with the old format to + # make sure the command works properly, need to change + # these to new test format when beef up all tests for + # volume tye commands. + def test_encryption_type(self): + encryption_type = uuid.uuid4().hex + # test create new encryption type + opts = self.get_opts(['encryption']) + raw_output = self.openstack( + 'volume type create ' + '--encryption-provider LuksEncryptor ' + '--encryption-cipher aes-xts-plain64 ' + '--encryption-key-size 128 ' + '--encryption-control-location front-end ' + + encryption_type + opts) + expected = ["provider='LuksEncryptor'", + "cipher='aes-xts-plain64'", + "key_size='128'", + "control_location='front-end'"] + for attr in expected: + self.assertIn(attr, raw_output) + # test show encryption type + opts = self.get_opts(['encryption']) + raw_output = self.openstack( + 'volume type show --encryption-type ' + encryption_type + opts) + expected = ["provider='LuksEncryptor'", + "cipher='aes-xts-plain64'", + "key_size='128'", + "control_location='front-end'"] + for attr in expected: + self.assertIn(attr, raw_output) + # test list encryption type + opts = self.get_opts(['Encryption']) + raw_output = self.openstack( + 'volume type list --encryption-type ' + opts) + expected = ["provider='LuksEncryptor'", + "cipher='aes-xts-plain64'", + "key_size='128'", + "control_location='front-end'"] + for attr in expected: + self.assertIn(attr, raw_output) + # test set new encryption type + raw_output = self.openstack( + 'volume type set ' + '--encryption-provider LuksEncryptor ' + '--encryption-cipher aes-xts-plain64 ' + '--encryption-key-size 128 ' + '--encryption-control-location front-end ' + + self.NAME) + self.assertEqual('', raw_output) + opts = self.get_opts(['encryption']) + raw_output = self.openstack( + 'volume type show --encryption-type ' + self.NAME + opts) + expected = ["provider='LuksEncryptor'", + "cipher='aes-xts-plain64'", + "key_size='128'", + "control_location='front-end'"] + for attr in expected: + self.assertIn(attr, raw_output) + # test unset encryption type + raw_output = self.openstack( + 'volume type unset --encryption-type ' + self.NAME) + self.assertEqual('', raw_output) + opts = self.get_opts(['encryption']) + raw_output = self.openstack( + 'volume type show --encryption-type ' + self.NAME + opts) + self.assertEqual('\n', raw_output) + # test delete encryption type + raw_output = self.openstack('volume type delete ' + encryption_type) + self.assertEqual('', raw_output) diff --git a/openstackclient/tests/functional/volume/v2/test_volume_type.py b/openstackclient/tests/functional/volume/v2/test_volume_type.py index b4df5b2d59..a5d0a767c9 100644 --- a/openstackclient/tests/functional/volume/v2/test_volume_type.py +++ b/openstackclient/tests/functional/volume/v2/test_volume_type.py @@ -102,3 +102,90 @@ def test_multi_delete(self): time.sleep(5) raw_output = self.openstack(cmd) self.assertOutput('', raw_output) + + # NOTE: Add some basic funtional tests with the old format to + # make sure the command works properly, need to change + # these to new test format when beef up all tests for + # volume tye commands. + def test_encryption_type(self): + encryption_type = uuid.uuid4().hex + # test create new encryption type + opts = self.get_opts(['encryption']) + raw_output = self.openstack( + 'volume type create ' + '--encryption-provider LuksEncryptor ' + '--encryption-cipher aes-xts-plain64 ' + '--encryption-key-size 128 ' + '--encryption-control-location front-end ' + + encryption_type + opts) + expected = ["provider='LuksEncryptor'", + "cipher='aes-xts-plain64'", + "key_size='128'", + "control_location='front-end'"] + for attr in expected: + self.assertIn(attr, raw_output) + # test show encryption type + opts = self.get_opts(['encryption']) + raw_output = self.openstack( + 'volume type show --encryption-type ' + encryption_type + opts) + expected = ["provider='LuksEncryptor'", + "cipher='aes-xts-plain64'", + "key_size='128'", + "control_location='front-end'"] + for attr in expected: + self.assertIn(attr, raw_output) + # test list encryption type + opts = self.get_opts(['Encryption']) + raw_output = self.openstack( + 'volume type list --encryption-type ' + opts) + expected = ["provider='LuksEncryptor'", + "cipher='aes-xts-plain64'", + "key_size='128'", + "control_location='front-end'"] + for attr in expected: + self.assertIn(attr, raw_output) + # test set existing encryption type + raw_output = self.openstack( + 'volume type set ' + '--encryption-key-size 256 ' + '--encryption-control-location back-end ' + + encryption_type) + self.assertEqual('', raw_output) + opts = self.get_opts(['encryption']) + raw_output = self.openstack( + 'volume type show --encryption-type ' + encryption_type + opts) + expected = ["provider='LuksEncryptor'", + "cipher='aes-xts-plain64'", + "key_size='256'", + "control_location='back-end'"] + for attr in expected: + self.assertIn(attr, raw_output) + # test set new encryption type + raw_output = self.openstack( + 'volume type set ' + '--encryption-provider LuksEncryptor ' + '--encryption-cipher aes-xts-plain64 ' + '--encryption-key-size 128 ' + '--encryption-control-location front-end ' + + self.NAME) + self.assertEqual('', raw_output) + opts = self.get_opts(['encryption']) + raw_output = self.openstack( + 'volume type show --encryption-type ' + self.NAME + opts) + expected = ["provider='LuksEncryptor'", + "cipher='aes-xts-plain64'", + "key_size='128'", + "control_location='front-end'"] + for attr in expected: + self.assertIn(attr, raw_output) + # test unset encryption type + raw_output = self.openstack( + 'volume type unset --encryption-type ' + self.NAME) + self.assertEqual('', raw_output) + opts = self.get_opts(['encryption']) + raw_output = self.openstack( + 'volume type show --encryption-type ' + self.NAME + opts) + self.assertEqual('\n', raw_output) + # test delete encryption type + raw_output = self.openstack('volume type delete ' + encryption_type) + self.assertEqual('', raw_output) diff --git a/openstackclient/tests/unit/volume/v1/fakes.py b/openstackclient/tests/unit/volume/v1/fakes.py index 78a8227e44..fff5181dc4 100644 --- a/openstackclient/tests/unit/volume/v1/fakes.py +++ b/openstackclient/tests/unit/volume/v1/fakes.py @@ -364,6 +364,9 @@ def __init__(self, **kwargs): self.qos_specs.resource_class = fakes.FakeResource(None, {}) self.volume_types = mock.Mock() self.volume_types.resource_class = fakes.FakeResource(None, {}) + self.volume_encryption_types = mock.Mock() + self.volume_encryption_types.resource_class = ( + fakes.FakeResource(None, {})) self.transfers = mock.Mock() self.transfers.resource_class = fakes.FakeResource(None, {}) self.volume_snapshots = mock.Mock() @@ -470,6 +473,34 @@ def get_types(types=None, count=2): return mock.Mock(side_effect=types) + @staticmethod + def create_one_encryption_type(attrs=None): + """Create a fake encryption type. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object with volume_type_id etc. + """ + attrs = attrs or {} + + # Set default attributes. + encryption_info = { + "volume_type_id": 'type-id-' + uuid.uuid4().hex, + 'provider': 'LuksEncryptor', + 'cipher': None, + 'key_size': None, + 'control_location': 'front-end', + } + + # Overwrite default attributes. + encryption_info.update(attrs) + + encryption_type = fakes.FakeResource( + info=copy.deepcopy(encryption_info), + loaded=True) + return encryption_type + class FakeSnapshot(object): """Fake one or more snapshot.""" diff --git a/openstackclient/tests/unit/volume/v1/test_type.py b/openstackclient/tests/unit/volume/v1/test_type.py index 81ad8301e6..dcdd3d56dd 100644 --- a/openstackclient/tests/unit/volume/v1/test_type.py +++ b/openstackclient/tests/unit/volume/v1/test_type.py @@ -31,6 +31,10 @@ def setUp(self): self.types_mock = self.app.client_manager.volume.volume_types self.types_mock.reset_mock() + self.encryption_types_mock = ( + self.app.client_manager.volume.volume_encryption_types) + self.encryption_types_mock.reset_mock() + class TestTypeCreate(TestType): @@ -75,6 +79,67 @@ def test_type_create(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_type_create_with_encryption(self): + encryption_info = { + 'provider': 'LuksEncryptor', + 'cipher': 'aes-xts-plain64', + 'key_size': '128', + 'control_location': 'front-end', + } + encryption_type = volume_fakes.FakeType.create_one_encryption_type( + attrs=encryption_info + ) + self.new_volume_type = volume_fakes.FakeType.create_one_type( + attrs={'encryption': encryption_info}) + self.types_mock.create.return_value = self.new_volume_type + self.encryption_types_mock.create.return_value = encryption_type + encryption_columns = ( + 'description', + 'encryption', + 'id', + 'is_public', + 'name', + ) + encryption_data = ( + self.new_volume_type.description, + utils.format_dict(encryption_info), + self.new_volume_type.id, + True, + self.new_volume_type.name, + ) + arglist = [ + '--encryption-provider', 'LuksEncryptor', + '--encryption-cipher', 'aes-xts-plain64', + '--encryption-key-size', '128', + '--encryption-control-location', 'front-end', + self.new_volume_type.name, + ] + verifylist = [ + ('encryption_provider', 'LuksEncryptor'), + ('encryption_cipher', 'aes-xts-plain64'), + ('encryption_key_size', 128), + ('encryption_control_location', 'front-end'), + ('name', self.new_volume_type.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.types_mock.create.assert_called_with( + self.new_volume_type.name, + ) + body = { + 'provider': 'LuksEncryptor', + 'cipher': 'aes-xts-plain64', + 'key_size': 128, + 'control_location': 'front-end', + } + self.encryption_types_mock.create.assert_called_with( + self.new_volume_type, + body, + ) + self.assertEqual(encryption_columns, columns) + self.assertEqual(encryption_data, data) + class TestTypeDelete(TestType): @@ -156,17 +221,17 @@ class TestTypeList(TestType): volume_types = volume_fakes.FakeType.create_types() - columns = ( + columns = [ "ID", "Name", "Is Public", - ) - columns_long = ( + ] + columns_long = [ "ID", "Name", "Is Public", "Properties" - ) + ] data = [] for t in volume_types: @@ -188,6 +253,8 @@ def setUp(self): super(TestTypeList, self).setUp() self.types_mock.list.return_value = self.volume_types + self.encryption_types_mock.create.return_value = None + self.encryption_types_mock.update.return_value = None # get the command to test self.cmd = volume_type.ListVolumeType(self.app, None) @@ -195,6 +262,7 @@ def test_type_list_without_options(self): arglist = [] verifylist = [ ("long", False), + ("encryption_type", False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -217,6 +285,47 @@ def test_type_list_with_options(self): self.assertEqual(self.columns_long, columns) self.assertEqual(self.data_long, list(data)) + def test_type_list_with_encryption(self): + encryption_type = volume_fakes.FakeType.create_one_encryption_type( + attrs={'volume_type_id': self.volume_types[0].id}) + encryption_info = { + 'provider': 'LuksEncryptor', + 'cipher': None, + 'key_size': None, + 'control_location': 'front-end', + } + encryption_columns = self.columns + [ + "Encryption", + ] + encryption_data = [] + encryption_data.append(( + self.volume_types[0].id, + self.volume_types[0].name, + self.volume_types[0].is_public, + utils.format_dict(encryption_info), + )) + encryption_data.append(( + self.volume_types[1].id, + self.volume_types[1].name, + self.volume_types[1].is_public, + '-', + )) + + self.encryption_types_mock.list.return_value = [encryption_type] + arglist = [ + "--encryption-type", + ] + verifylist = [ + ("encryption_type", True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.encryption_types_mock.list.assert_called_once_with() + self.types_mock.list.assert_called_once_with() + self.assertEqual(encryption_columns, columns) + self.assertEqual(encryption_data, list(data)) + class TestTypeSet(TestType): @@ -260,6 +369,60 @@ def test_type_set_property(self): {'myprop': 'myvalue'}) self.assertIsNone(result) + def test_type_set_new_encryption(self): + arglist = [ + '--encryption-provider', 'LuksEncryptor', + '--encryption-cipher', 'aes-xts-plain64', + '--encryption-key-size', '128', + '--encryption-control-location', 'front-end', + self.volume_type.id, + ] + verifylist = [ + ('encryption_provider', 'LuksEncryptor'), + ('encryption_cipher', 'aes-xts-plain64'), + ('encryption_key_size', 128), + ('encryption_control_location', 'front-end'), + ('volume_type', self.volume_type.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + body = { + 'provider': 'LuksEncryptor', + 'cipher': 'aes-xts-plain64', + 'key_size': 128, + 'control_location': 'front-end', + } + self.encryption_types_mock.create.assert_called_with( + self.volume_type, + body, + ) + self.assertIsNone(result) + + def test_type_set_new_encryption_without_provider(self): + arglist = [ + '--encryption-cipher', 'aes-xts-plain64', + '--encryption-key-size', '128', + '--encryption-control-location', 'front-end', + self.volume_type.id, + ] + verifylist = [ + ('encryption_cipher', 'aes-xts-plain64'), + ('encryption_key_size', 128), + ('encryption_control_location', 'front-end'), + ('volume_type', self.volume_type.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual("Command Failed: One or more of" + " the operations failed", + str(e)) + self.encryption_types_mock.create.assert_not_called() + self.encryption_types_mock.update.assert_not_called() + class TestTypeShow(TestType): @@ -293,7 +456,8 @@ def test_type_show(self): self.volume_type.id ] verifylist = [ - ("volume_type", self.volume_type.id) + ("volume_type", self.volume_type.id), + ("encryption_type", False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -303,6 +467,50 @@ def test_type_show(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_type_show_with_encryption(self): + encryption_type = volume_fakes.FakeType.create_one_encryption_type() + encryption_info = { + 'provider': 'LuksEncryptor', + 'cipher': None, + 'key_size': None, + 'control_location': 'front-end', + } + self.volume_type = volume_fakes.FakeType.create_one_type( + attrs={'encryption': encryption_info}) + self.types_mock.get.return_value = self.volume_type + self.encryption_types_mock.get.return_value = encryption_type + encryption_columns = ( + 'description', + 'encryption', + 'id', + 'is_public', + 'name', + 'properties', + ) + encryption_data = ( + self.volume_type.description, + utils.format_dict(encryption_info), + self.volume_type.id, + True, + self.volume_type.name, + utils.format_dict(self.volume_type.extra_specs) + ) + arglist = [ + '--encryption-type', + self.volume_type.id + ] + verifylist = [ + ('encryption_type', True), + ("volume_type", self.volume_type.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.types_mock.get.assert_called_with(self.volume_type.id) + self.encryption_types_mock.get.assert_called_with(self.volume_type.id) + self.assertEqual(encryption_columns, columns) + self.assertEqual(encryption_data, data) + class TestTypeUnset(TestType): @@ -317,13 +525,14 @@ def setUp(self): # Get the command object to test self.cmd = volume_type.UnsetVolumeType(self.app, None) - def test_type_unset(self): + def test_type_unset_property(self): arglist = [ '--property', 'property', '--property', 'multi_property', self.volume_type.id, ] verifylist = [ + ('encryption_type', False), ('property', ['property', 'multi_property']), ('volume_type', self.volume_type.id), ] @@ -333,6 +542,7 @@ def test_type_unset(self): result = self.cmd.take_action(parsed_args) self.volume_type.unset_keys.assert_called_once_with( ['property', 'multi_property']) + self.encryption_types_mock.delete.assert_not_called() self.assertIsNone(result) def test_type_unset_failed_with_missing_volume_type_argument(self): @@ -362,3 +572,18 @@ def test_type_unset_nothing(self): result = self.cmd.take_action(parsed_args) self.assertIsNone(result) + + def test_type_unset_encryption_type(self): + arglist = [ + '--encryption-type', + self.volume_type.id, + ] + verifylist = [ + ('encryption_type', True), + ('volume_type', self.volume_type.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.encryption_types_mock.delete.assert_called_with(self.volume_type) + self.assertIsNone(result) diff --git a/openstackclient/tests/unit/volume/v2/fakes.py b/openstackclient/tests/unit/volume/v2/fakes.py index a667640347..d54faec7b3 100644 --- a/openstackclient/tests/unit/volume/v2/fakes.py +++ b/openstackclient/tests/unit/volume/v2/fakes.py @@ -208,6 +208,9 @@ def __init__(self, **kwargs): self.volume_types.resource_class = fakes.FakeResource(None, {}) self.volume_type_access = mock.Mock() self.volume_type_access.resource_class = fakes.FakeResource(None, {}) + self.volume_encryption_types = mock.Mock() + self.volume_encryption_types.resource_class = ( + fakes.FakeResource(None, {})) self.restores = mock.Mock() self.restores.resource_class = fakes.FakeResource(None, {}) self.qos_specs = mock.Mock() @@ -923,3 +926,31 @@ def get_types(types=None, count=2): types = FakeType.create_types(count) return mock.Mock(side_effect=types) + + @staticmethod + def create_one_encryption_type(attrs=None): + """Create a fake encryption type. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object with volume_type_id etc. + """ + attrs = attrs or {} + + # Set default attributes. + encryption_info = { + "volume_type_id": 'type-id-' + uuid.uuid4().hex, + 'provider': 'LuksEncryptor', + 'cipher': None, + 'key_size': None, + 'control_location': 'front-end', + } + + # Overwrite default attributes. + encryption_info.update(attrs) + + encryption_type = fakes.FakeResource( + info=copy.deepcopy(encryption_info), + loaded=True) + return encryption_type diff --git a/openstackclient/tests/unit/volume/v2/test_type.py b/openstackclient/tests/unit/volume/v2/test_type.py index cec01bd8ef..4023d55b3d 100644 --- a/openstackclient/tests/unit/volume/v2/test_type.py +++ b/openstackclient/tests/unit/volume/v2/test_type.py @@ -36,6 +36,10 @@ def setUp(self): self.app.client_manager.volume.volume_type_access) self.types_access_mock.reset_mock() + self.encryption_types_mock = ( + self.app.client_manager.volume.volume_encryption_types) + self.encryption_types_mock.reset_mock() + self.projects_mock = self.app.client_manager.identity.projects self.projects_mock.reset_mock() @@ -131,6 +135,68 @@ def test_public_type_create_with_project(self): self.cmd.take_action, parsed_args) + def test_type_create_with_encryption(self): + encryption_info = { + 'provider': 'LuksEncryptor', + 'cipher': 'aes-xts-plain64', + 'key_size': '128', + 'control_location': 'front-end', + } + encryption_type = volume_fakes.FakeType.create_one_encryption_type( + attrs=encryption_info + ) + self.new_volume_type = volume_fakes.FakeType.create_one_type( + attrs={'encryption': encryption_info}) + self.types_mock.create.return_value = self.new_volume_type + self.encryption_types_mock.create.return_value = encryption_type + encryption_columns = ( + 'description', + 'encryption', + 'id', + 'is_public', + 'name', + ) + encryption_data = ( + self.new_volume_type.description, + utils.format_dict(encryption_info), + self.new_volume_type.id, + True, + self.new_volume_type.name, + ) + arglist = [ + '--encryption-provider', 'LuksEncryptor', + '--encryption-cipher', 'aes-xts-plain64', + '--encryption-key-size', '128', + '--encryption-control-location', 'front-end', + self.new_volume_type.name, + ] + verifylist = [ + ('encryption_provider', 'LuksEncryptor'), + ('encryption_cipher', 'aes-xts-plain64'), + ('encryption_key_size', 128), + ('encryption_control_location', 'front-end'), + ('name', self.new_volume_type.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.types_mock.create.assert_called_with( + self.new_volume_type.name, + description=None, + ) + body = { + 'provider': 'LuksEncryptor', + 'cipher': 'aes-xts-plain64', + 'key_size': 128, + 'control_location': 'front-end', + } + self.encryption_types_mock.create.assert_called_with( + self.new_volume_type, + body, + ) + self.assertEqual(encryption_columns, columns) + self.assertEqual(encryption_data, data) + class TestTypeDelete(TestType): @@ -305,6 +371,7 @@ def test_type_list_with_default_option(self): "--default", ] verifylist = [ + ("encryption_type", False), ("long", False), ("private", False), ("public", False), @@ -317,6 +384,47 @@ def test_type_list_with_default_option(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data_with_default_type, list(data)) + def test_type_list_with_encryption(self): + encryption_type = volume_fakes.FakeType.create_one_encryption_type( + attrs={'volume_type_id': self.volume_types[0].id}) + encryption_info = { + 'provider': 'LuksEncryptor', + 'cipher': None, + 'key_size': None, + 'control_location': 'front-end', + } + encryption_columns = self.columns + [ + "Encryption", + ] + encryption_data = [] + encryption_data.append(( + self.volume_types[0].id, + self.volume_types[0].name, + self.volume_types[0].is_public, + utils.format_dict(encryption_info), + )) + encryption_data.append(( + self.volume_types[1].id, + self.volume_types[1].name, + self.volume_types[1].is_public, + '-', + )) + + self.encryption_types_mock.list.return_value = [encryption_type] + arglist = [ + "--encryption-type", + ] + verifylist = [ + ("encryption_type", True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.encryption_types_mock.list.assert_called_once_with() + self.types_mock.list.assert_called_once_with(is_public=None) + self.assertEqual(encryption_columns, columns) + self.assertEqual(encryption_data, list(data)) + class TestTypeSet(TestType): @@ -331,6 +439,8 @@ def setUp(self): # Return a project self.projects_mock.get.return_value = self.project + self.encryption_types_mock.create.return_value = None + self.encryption_types_mock.update.return_value = None # Get the command object to test self.cmd = volume_type.SetVolumeType(self.app, None) @@ -454,6 +564,107 @@ def test_type_set_project_access(self): self.project.id, ) + def test_type_set_new_encryption(self): + self.encryption_types_mock.update.side_effect = ( + exceptions.NotFound('NotFound')) + arglist = [ + '--encryption-provider', 'LuksEncryptor', + '--encryption-cipher', 'aes-xts-plain64', + '--encryption-key-size', '128', + '--encryption-control-location', 'front-end', + self.volume_type.id, + ] + verifylist = [ + ('encryption_provider', 'LuksEncryptor'), + ('encryption_cipher', 'aes-xts-plain64'), + ('encryption_key_size', 128), + ('encryption_control_location', 'front-end'), + ('volume_type', self.volume_type.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + body = { + 'provider': 'LuksEncryptor', + 'cipher': 'aes-xts-plain64', + 'key_size': 128, + 'control_location': 'front-end', + } + self.encryption_types_mock.update.assert_called_with( + self.volume_type, + body, + ) + self.encryption_types_mock.create.assert_called_with( + self.volume_type, + body, + ) + self.assertIsNone(result) + + @mock.patch.object(utils, 'find_resource') + def test_type_set_existing_encryption(self, mock_find): + mock_find.side_effect = [self.volume_type, + "existing_encryption_type"] + arglist = [ + '--encryption-provider', 'LuksEncryptor', + '--encryption-cipher', 'aes-xts-plain64', + '--encryption-control-location', 'front-end', + self.volume_type.id, + ] + verifylist = [ + ('encryption_provider', 'LuksEncryptor'), + ('encryption_cipher', 'aes-xts-plain64'), + ('encryption_control_location', 'front-end'), + ('volume_type', self.volume_type.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + body = { + 'provider': 'LuksEncryptor', + 'cipher': 'aes-xts-plain64', + 'control_location': 'front-end', + } + self.encryption_types_mock.update.assert_called_with( + self.volume_type, + body, + ) + self.encryption_types_mock.create.assert_not_called() + self.assertIsNone(result) + + def test_type_set_new_encryption_without_provider(self): + self.encryption_types_mock.update.side_effect = ( + exceptions.NotFound('NotFound')) + arglist = [ + '--encryption-cipher', 'aes-xts-plain64', + '--encryption-key-size', '128', + '--encryption-control-location', 'front-end', + self.volume_type.id, + ] + verifylist = [ + ('encryption_cipher', 'aes-xts-plain64'), + ('encryption_key_size', 128), + ('encryption_control_location', 'front-end'), + ('volume_type', self.volume_type.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual("Command Failed: One or more of" + " the operations failed", + str(e)) + body = { + 'cipher': 'aes-xts-plain64', + 'key_size': 128, + 'control_location': 'front-end', + } + self.encryption_types_mock.update.assert_called_with( + self.volume_type, + body, + ) + self.encryption_types_mock.create.assert_not_called() + class TestTypeShow(TestType): @@ -489,6 +700,7 @@ def test_type_show(self): self.volume_type.id ] verifylist = [ + ("encryption_type", False), ("volume_type", self.volume_type.id) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -564,6 +776,52 @@ def test_type_show_with_list_access_exec(self): ) self.assertEqual(private_type_data, data) + def test_type_show_with_encryption(self): + encryption_type = volume_fakes.FakeType.create_one_encryption_type() + encryption_info = { + 'provider': 'LuksEncryptor', + 'cipher': None, + 'key_size': None, + 'control_location': 'front-end', + } + self.volume_type = volume_fakes.FakeType.create_one_type( + attrs={'encryption': encryption_info}) + self.types_mock.get.return_value = self.volume_type + self.encryption_types_mock.get.return_value = encryption_type + encryption_columns = ( + 'access_project_ids', + 'description', + 'encryption', + 'id', + 'is_public', + 'name', + 'properties', + ) + encryption_data = ( + None, + self.volume_type.description, + utils.format_dict(encryption_info), + self.volume_type.id, + True, + self.volume_type.name, + utils.format_dict(self.volume_type.extra_specs) + ) + arglist = [ + '--encryption-type', + self.volume_type.id + ] + verifylist = [ + ('encryption_type', True), + ("volume_type", self.volume_type.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.types_mock.get.assert_called_with(self.volume_type.id) + self.encryption_types_mock.get.assert_called_with(self.volume_type.id) + self.assertEqual(encryption_columns, columns) + self.assertEqual(encryption_data, data) + class TestTypeUnset(TestType): @@ -625,6 +883,7 @@ def test_type_unset_not_called_without_project_argument(self): self.volume_type.id, ] verifylist = [ + ('encryption_type', False), ('project', ''), ('volume_type', self.volume_type.id), ] @@ -633,7 +892,7 @@ def test_type_unset_not_called_without_project_argument(self): result = self.cmd.take_action(parsed_args) self.assertIsNone(result) - + self.encryption_types_mock.delete.assert_not_called() self.assertFalse(self.types_access_mock.remove_project_access.called) def test_type_unset_failed_with_missing_volume_type_argument(self): @@ -649,3 +908,18 @@ def test_type_unset_failed_with_missing_volume_type_argument(self): self.cmd, arglist, verifylist) + + def test_type_unset_encryption_type(self): + arglist = [ + '--encryption-type', + self.volume_type.id, + ] + verifylist = [ + ('encryption_type', True), + ('volume_type', self.volume_type.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.encryption_types_mock.delete.assert_called_with(self.volume_type) + self.assertIsNone(result) diff --git a/openstackclient/volume/v1/volume_type.py b/openstackclient/volume/v1/volume_type.py index 8adce3221c..f9baa5be5d 100644 --- a/openstackclient/volume/v1/volume_type.py +++ b/openstackclient/volume/v1/volume_type.py @@ -29,6 +29,26 @@ LOG = logging.getLogger(__name__) +def _create_encryption_type(volume_client, volume_type, parsed_args): + if not parsed_args.encryption_provider: + msg = _("'--encryption-provider' should be specified while " + "creating a new encryption type") + raise exceptions.CommandError(msg) + # set the default of control location while creating + control_location = 'front-end' + if parsed_args.encryption_control_location: + control_location = parsed_args.encryption_control_location + body = { + 'provider': parsed_args.encryption_provider, + 'cipher': parsed_args.encryption_cipher, + 'key_size': parsed_args.encryption_key_size, + 'control_location': control_location + } + encryption = volume_client.volume_encryption_types.create( + volume_type, body) + return encryption + + class CreateVolumeType(command.ShowOne): _description = _("Create new volume type") @@ -46,6 +66,42 @@ def get_parser(self, prog_name): help=_('Set a property on this volume type ' '(repeat option to set multiple properties)'), ) + # TODO(Huanxuan Ao): Add choices for each "--encryption-*" option. + parser.add_argument( + '--encryption-provider', + metavar='', + help=_('Set the class that provides encryption support for ' + 'this volume type (e.g "LuksEncryptor") (admin only) ' + '(This option is required when setting encryption type ' + 'of a volume. Consider using other encryption options ' + 'such as: "--encryption-cipher", "--encryption-key-size" ' + 'and "--encryption-control-location")'), + ) + parser.add_argument( + '--encryption-cipher', + metavar='', + help=_('Set the encryption algorithm or mode for this ' + 'volume type (e.g "aes-xts-plain64") (admin only)'), + ) + parser.add_argument( + '--encryption-key-size', + metavar='', + type=int, + help=_('Set the size of the encryption key of this ' + 'volume type (e.g "128" or "256") (admin only)'), + ) + parser.add_argument( + '--encryption-control-location', + metavar='', + choices=['front-end', 'back-end'], + help=_('Set the notional service where the encryption is ' + 'performed ("front-end" or "back-end") (admin only) ' + '(The default value for this option is "front-end" ' + 'when setting encryption type of a volume. Consider ' + 'using other encryption options such as: ' + '"--encryption-cipher", "--encryption-key-size" and ' + '"--encryption-provider")'), + ) return parser def take_action(self, parsed_args): @@ -55,6 +111,21 @@ def take_action(self, parsed_args): if parsed_args.property: result = volume_type.set_keys(parsed_args.property) volume_type._info.update({'properties': utils.format_dict(result)}) + if (parsed_args.encryption_provider or + parsed_args.encryption_cipher or + parsed_args.encryption_key_size or + parsed_args.encryption_control_location): + try: + # create new encryption + encryption = _create_encryption_type( + volume_client, volume_type, parsed_args) + except Exception as e: + LOG.error(_("Failed to set encryption information for this " + "volume type: %s"), e) + # add encryption info in result + encryption._info.pop("volume_type_id", None) + volume_type._info.update( + {'encryption': utils.format_dict(encryption._info)}) volume_type._info.pop("os-volume-type-access:is_public", None) return zip(*sorted(six.iteritems(volume_type._info))) @@ -107,20 +178,58 @@ def get_parser(self, prog_name): default=False, help=_('List additional fields in output') ) + parser.add_argument( + "--encryption-type", + action="store_true", + help=_("Display encryption information for each volume type " + "(admin only)"), + ) return parser def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume if parsed_args.long: - columns = ('ID', 'Name', 'Is Public', 'Extra Specs') - column_headers = ('ID', 'Name', 'Is Public', 'Properties') + columns = ['ID', 'Name', 'Is Public', 'Extra Specs'] + column_headers = ['ID', 'Name', 'Is Public', 'Properties'] else: - columns = ('ID', 'Name', 'Is Public') - column_headers = columns - data = self.app.client_manager.volume.volume_types.list() + columns = ['ID', 'Name', 'Is Public'] + column_headers = ['ID', 'Name', 'Is Public'] + data = volume_client.volume_types.list() + + def _format_encryption_info(type_id, encryption_data=None): + encryption_data = encryption + encryption_info = '-' + if type_id in encryption_data.keys(): + encryption_info = encryption_data[type_id] + return encryption_info + + if parsed_args.encryption_type: + encryption = {} + for d in volume_client.volume_encryption_types.list(): + volume_type_id = d._info['volume_type_id'] + # remove some redundant information + del_key = [ + 'deleted', + 'created_at', + 'updated_at', + 'deleted_at', + 'volume_type_id' + ] + for key in del_key: + d._info.pop(key, None) + # save the encryption information with their volume type ID + encryption[volume_type_id] = utils.format_dict(d._info) + # We need to get volume type ID, then show encryption + # information according to the ID, so use "id" to keep + # difference to the real "ID" column. + columns += ['id'] + column_headers += ['Encryption'] + return (column_headers, (utils.get_item_properties( s, columns, - formatters={'Extra Specs': utils.format_dict}, + formatters={'Extra Specs': utils.format_dict, + 'id': _format_encryption_info}, ) for s in data)) @@ -141,6 +250,42 @@ def get_parser(self, prog_name): help=_('Set a property on this volume type ' '(repeat option to set multiple properties)'), ) + # TODO(Huanxuan Ao): Add choices for each "--encryption-*" option. + parser.add_argument( + '--encryption-provider', + metavar='', + help=_('Set the class that provides encryption support for ' + 'this volume type (e.g "LuksEncryptor") (admin only) ' + '(This option is required when setting encryption type ' + 'of a volume. Consider using other encryption options ' + 'such as: "--encryption-cipher", "--encryption-key-size" ' + 'and "--encryption-control-location")'), + ) + parser.add_argument( + '--encryption-cipher', + metavar='', + help=_('Set the encryption algorithm or mode for this ' + 'volume type (e.g "aes-xts-plain64") (admin only)'), + ) + parser.add_argument( + '--encryption-key-size', + metavar='', + type=int, + help=_('Set the size of the encryption key of this ' + 'volume type (e.g "128" or "256") (admin only)'), + ) + parser.add_argument( + '--encryption-control-location', + metavar='', + choices=['front-end', 'back-end'], + help=_('Set the notional service where the encryption is ' + 'performed ("front-end" or "back-end") (admin only) ' + '(The default value for this option is "front-end" ' + 'when setting encryption type of a volume. Consider ' + 'using other encryption options such as: ' + '"--encryption-cipher", "--encryption-key-size" and ' + '"--encryption-provider")'), + ) return parser def take_action(self, parsed_args): @@ -148,8 +293,29 @@ def take_action(self, parsed_args): volume_type = utils.find_resource( volume_client.volume_types, parsed_args.volume_type) + result = 0 if parsed_args.property: - volume_type.set_keys(parsed_args.property) + try: + volume_type.set_keys(parsed_args.property) + except Exception as e: + LOG.error(_("Failed to set volume type property: %s"), e) + result += 1 + + if (parsed_args.encryption_provider or + parsed_args.encryption_cipher or + parsed_args.encryption_key_size or + parsed_args.encryption_control_location): + try: + _create_encryption_type( + volume_client, volume_type, parsed_args) + except Exception as e: + LOG.error(_("Failed to set encryption information for this " + "volume type: %s"), e) + result += 1 + + if result > 0: + raise exceptions.CommandError(_("Command Failed: One or more of" + " the operations failed")) class ShowVolumeType(command.ShowOne): @@ -162,6 +328,12 @@ def get_parser(self, prog_name): metavar="", help=_("Volume type to display (name or ID)") ) + parser.add_argument( + "--encryption-type", + action="store_true", + help=_("Display encryption information of this volume type " + "(admin only)"), + ) return parser def take_action(self, parsed_args): @@ -170,6 +342,17 @@ def take_action(self, parsed_args): volume_client.volume_types, parsed_args.volume_type) properties = utils.format_dict(volume_type._info.pop('extra_specs')) volume_type._info.update({'properties': properties}) + if parsed_args.encryption_type: + # show encryption type information for this volume type + try: + encryption = volume_client.volume_encryption_types.get( + volume_type.id) + encryption._info.pop("volume_type_id", None) + volume_type._info.update( + {'encryption': utils.format_dict(encryption._info)}) + except Exception as e: + LOG.error(_("Failed to display the encryption information " + "of this volume type: %s"), e) volume_type._info.pop("os-volume-type-access:is_public", None) return zip(*sorted(six.iteritems(volume_type._info))) @@ -191,6 +374,12 @@ def get_parser(self, prog_name): help=_('Remove a property from this volume type ' '(repeat option to remove multiple properties)'), ) + parser.add_argument( + "--encryption-type", + action="store_true", + help=_("Remove the encryption type for this volume type " + "(admin oly)"), + ) return parser def take_action(self, parsed_args): @@ -200,5 +389,21 @@ def take_action(self, parsed_args): parsed_args.volume_type, ) + result = 0 if parsed_args.property: - volume_type.unset_keys(parsed_args.property) + try: + volume_type.unset_keys(parsed_args.property) + except Exception as e: + LOG.error(_("Failed to unset volume type property: %s"), e) + result += 1 + if parsed_args.encryption_type: + try: + volume_client.volume_encryption_types.delete(volume_type) + except Exception as e: + LOG.error(_("Failed to remove the encryption type for this " + "volume type: %s"), e) + result += 1 + + if result > 0: + raise exceptions.CommandError(_("Command Failed: One or more of" + " the operations failed")) diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index 46466783f8..8d2901f29c 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -29,6 +29,44 @@ LOG = logging.getLogger(__name__) +def _create_encryption_type(volume_client, volume_type, parsed_args): + if not parsed_args.encryption_provider: + msg = _("'--encryption-provider' should be specified while " + "creating a new encryption type") + raise exceptions.CommandError(msg) + # set the default of control location while creating + control_location = 'front-end' + if parsed_args.encryption_control_location: + control_location = parsed_args.encryption_control_location + body = { + 'provider': parsed_args.encryption_provider, + 'cipher': parsed_args.encryption_cipher, + 'key_size': parsed_args.encryption_key_size, + 'control_location': control_location + } + encryption = volume_client.volume_encryption_types.create( + volume_type, body) + return encryption + + +def _set_encryption_type(volume_client, volume_type, parsed_args): + # update the existing encryption type + body = {} + for attr in ['provider', 'cipher', 'key_size', 'control_location']: + info = getattr(parsed_args, 'encryption_' + attr, None) + if info is not None: + body[attr] = info + try: + volume_client.volume_encryption_types.update(volume_type, body) + except Exception as e: + if type(e).__name__ == 'NotFound': + # create new encryption type + LOG.warning(_("No existing encryption type found, creating " + "new encryption type for this volume type ...")) + _create_encryption_type( + volume_client, volume_type, parsed_args) + + class CreateVolumeType(command.ShowOne): _description = _("Create new volume type") @@ -70,6 +108,42 @@ def get_parser(self, prog_name): help=_("Allow to access private type (name or ID) " "(Must be used with --private option)"), ) + # TODO(Huanxuan Ao): Add choices for each "--encryption-*" option. + parser.add_argument( + '--encryption-provider', + metavar='', + help=_('Set the class that provides encryption support for ' + 'this volume type (e.g "LuksEncryptor") (admin only) ' + '(This option is required when setting encryption type ' + 'of a volume. Consider using other encryption options ' + 'such as: "--encryption-cipher", "--encryption-key-size" ' + 'and "--encryption-control-location")'), + ) + parser.add_argument( + '--encryption-cipher', + metavar='', + help=_('Set the encryption algorithm or mode for this ' + 'volume type (e.g "aes-xts-plain64") (admin only)'), + ) + parser.add_argument( + '--encryption-key-size', + metavar='', + type=int, + help=_('Set the size of the encryption key of this ' + 'volume type (e.g "128" or "256") (admin only)'), + ) + parser.add_argument( + '--encryption-control-location', + metavar='', + choices=['front-end', 'back-end'], + help=_('Set the notional service where the encryption is ' + 'performed ("front-end" or "back-end") (admin only) ' + '(The default value for this option is "front-end" ' + 'when setting encryption type of a volume. Consider ' + 'using other encryption options such as: ' + '"--encryption-cipher", "--encryption-key-size" and ' + '"--encryption-provider")'), + ) identity_common.add_project_domain_option_to_parser(parser) return parser @@ -110,6 +184,21 @@ def take_action(self, parsed_args): if parsed_args.property: result = volume_type.set_keys(parsed_args.property) volume_type._info.update({'properties': utils.format_dict(result)}) + if (parsed_args.encryption_provider or + parsed_args.encryption_cipher or + parsed_args.encryption_key_size or + parsed_args.encryption_control_location): + try: + # create new encryption + encryption = _create_encryption_type( + volume_client, volume_type, parsed_args) + except Exception as e: + LOG.error(_("Failed to set encryption information for this " + "volume type: %s"), e) + # add encryption info in result + encryption._info.pop("volume_type_id", None) + volume_type._info.update( + {'encryption': utils.format_dict(encryption._info)}) volume_type._info.pop("os-volume-type-access:is_public", None) return zip(*sorted(six.iteritems(volume_type._info))) @@ -179,6 +268,12 @@ def get_parser(self, prog_name): action="store_true", help=_("List only private types (admin only)") ) + parser.add_argument( + "--encryption-type", + action="store_true", + help=_("Display encryption information for each volume type " + "(admin only)"), + ) return parser def take_action(self, parsed_args): @@ -189,7 +284,7 @@ def take_action(self, parsed_args): 'ID', 'Name', 'Is Public', 'Description', 'Properties'] else: columns = ['ID', 'Name', 'Is Public'] - column_headers = columns + column_headers = ['ID', 'Name', 'Is Public'] if parsed_args.default: data = [volume_client.volume_types.default()] else: @@ -200,10 +295,41 @@ def take_action(self, parsed_args): is_public = False data = volume_client.volume_types.list( is_public=is_public) + + def _format_encryption_info(type_id, encryption_data=None): + encryption_data = encryption + encryption_info = '-' + if type_id in encryption_data.keys(): + encryption_info = encryption_data[type_id] + return encryption_info + + if parsed_args.encryption_type: + encryption = {} + for d in volume_client.volume_encryption_types.list(): + volume_type_id = d._info['volume_type_id'] + # remove some redundant information + del_key = [ + 'deleted', + 'created_at', + 'updated_at', + 'deleted_at', + 'volume_type_id' + ] + for key in del_key: + d._info.pop(key, None) + # save the encryption information with their volume type ID + encryption[volume_type_id] = utils.format_dict(d._info) + # We need to get volume type ID, then show encryption + # information according to the ID, so use "id" to keep + # difference to the real "ID" column. + columns += ['id'] + column_headers += ['Encryption'] + return (column_headers, (utils.get_item_properties( s, columns, - formatters={'Extra Specs': utils.format_dict}, + formatters={'Extra Specs': utils.format_dict, + 'id': _format_encryption_info}, ) for s in data)) @@ -241,7 +367,43 @@ def get_parser(self, prog_name): '(admin only)'), ) identity_common.add_project_domain_option_to_parser(parser) - + # TODO(Huanxuan Ao): Add choices for each "--encryption-*" option. + parser.add_argument( + '--encryption-provider', + metavar='', + help=_('Set the class that provides encryption support for ' + 'this volume type (e.g "LuksEncryptor") (admin only) ' + '(This option is required when setting encryption type ' + 'of a volume for the first time. Consider using other ' + 'encryption options such as: "--encryption-cipher", ' + '"--encryption-key-size" and ' + '"--encryption-control-location")'), + ) + parser.add_argument( + '--encryption-cipher', + metavar='', + help=_('Set the encryption algorithm or mode for this ' + 'volume type (e.g "aes-xts-plain64") (admin only)'), + ) + parser.add_argument( + '--encryption-key-size', + metavar='', + type=int, + help=_('Set the size of the encryption key of this ' + 'volume type (e.g "128" or "256") (admin only)'), + ) + parser.add_argument( + '--encryption-control-location', + metavar='', + choices=['front-end', 'back-end'], + help=_('Set the notional service where the encryption is ' + 'performed ("front-end" or "back-end") (admin only) ' + '(The default value for this option is "front-end" ' + 'when setting encryption type of a volume for the ' + 'first time. Consider using other encryption options ' + 'such as: "--encryption-cipher", "--encryption-key-size" ' + 'and "--encryption-provider")'), + ) return parser def take_action(self, parsed_args): @@ -290,6 +452,17 @@ def take_action(self, parsed_args): "project: %s"), e) result += 1 + if (parsed_args.encryption_provider or + parsed_args.encryption_cipher or + parsed_args.encryption_key_size or + parsed_args.encryption_control_location): + try: + _set_encryption_type(volume_client, volume_type, parsed_args) + except Exception as e: + LOG.error(_("Failed to set encryption information for this " + "volume type: %s"), e) + result += 1 + if result > 0: raise exceptions.CommandError(_("Command Failed: One or more of" " the operations failed")) @@ -305,6 +478,12 @@ def get_parser(self, prog_name): metavar="", help=_("Volume type to display (name or ID)") ) + parser.add_argument( + "--encryption-type", + action="store_true", + help=_("Display encryption information of this volume type " + "(admin only)"), + ) return parser def take_action(self, parsed_args): @@ -329,6 +508,17 @@ def take_action(self, parsed_args): '%(type)s: %(e)s') LOG.error(msg % {'type': volume_type.id, 'e': e}) volume_type._info.update({'access_project_ids': access_project_ids}) + if parsed_args.encryption_type: + # show encryption type information for this volume type + try: + encryption = volume_client.volume_encryption_types.get( + volume_type.id) + encryption._info.pop("volume_type_id", None) + volume_type._info.update( + {'encryption': utils.format_dict(encryption._info)}) + except Exception as e: + LOG.error(_("Failed to display the encryption information " + "of this volume type: %s"), e) volume_type._info.pop("os-volume-type-access:is_public", None) return zip(*sorted(six.iteritems(volume_type._info))) @@ -357,7 +547,12 @@ def get_parser(self, prog_name): ' (admin only)'), ) identity_common.add_project_domain_option_to_parser(parser) - + parser.add_argument( + "--encryption-type", + action="store_true", + help=_("Remove the encryption type for this volume type " + "(admin only)"), + ) return parser def take_action(self, parsed_args): @@ -391,6 +586,13 @@ def take_action(self, parsed_args): LOG.error(_("Failed to remove volume type access from " "project: %s"), e) result += 1 + if parsed_args.encryption_type: + try: + volume_client.volume_encryption_types.delete(volume_type) + except Exception as e: + LOG.error(_("Failed to remove the encryption type for this " + "volume type: %s"), e) + result += 1 if result > 0: raise exceptions.CommandError(_("Command Failed: One or more of" diff --git a/releasenotes/notes/bug-1651117-a1df37e7ea939ba4.yaml b/releasenotes/notes/bug-1651117-a1df37e7ea939ba4.yaml new file mode 100644 index 0000000000..d175e4faa0 --- /dev/null +++ b/releasenotes/notes/bug-1651117-a1df37e7ea939ba4.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + Add ``--encryption-provider``, ``--encryption-cipher``, ``--encryption-key-size`` + and ``--encryption-control-location`` options to ``volume type set`` and + ``volume type create`` commands. + Add ``--encryption-type`` option to ``volume type unset``, ``volume type list`` + and ``volume type show`` commands. + [Bug `1651117 `_] From 339af2c20bfe44a772a1e39fc8b769db112b75ce Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 13 Jan 2017 14:05:04 -0600 Subject: [PATCH 1486/3095] Fix floating IP delete and show by IP The floating IP delete and show commands did not work using IP addresses as the selector, only ID. The SDK floating_ip resource does not support but OSC does, so we have to do it ourselves. Now with more SDK 0.9.10 support! Change-Id: Iea1b57cded6b16a56a06af87ab8f1fa001a3485e Closes-bug: 1656402 --- openstackclient/network/v2/floating_ip.py | 81 ++++++++++++++++- .../functional/network/v2/test_floating_ip.py | 6 -- .../tests/unit/network/v2/fakes.py | 1 + .../tests/unit/network/v2/test_floating_ip.py | 90 +++++++++++++++---- .../notes/bug-1656402-88b12760fb2d4ef9.yaml | 6 ++ 5 files changed, 157 insertions(+), 27 deletions(-) create mode 100644 releasenotes/notes/bug-1656402-88b12760fb2d4ef9.yaml diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index 8202b3fae2..980c41c716 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -15,6 +15,8 @@ import logging +from openstack import exceptions as sdk_exceptions +from openstack.network.v2 import floating_ip as _floating_ip from osc_lib import utils from openstackclient.i18n import _ @@ -79,6 +81,58 @@ def _get_attrs(client_manager, parsed_args): return attrs +def _find_floating_ip( + session, + ip_cache, + name_or_id, + ignore_missing=True, + **params +): + """Find a floating IP by IP or ID + + The SDK's find_ip() can only locate a floating IP by ID so we have + to do this ourselves. + """ + + def _get_one_match(name_or_id): + """Given a list of results, return the match""" + the_result = None + for maybe_result in ip_cache: + id_value = maybe_result.id + ip_value = maybe_result.floating_ip_address + + if (id_value == name_or_id) or (ip_value == name_or_id): + # Only allow one resource to be found. If we already + # found a match, raise an exception to show it. + if the_result is None: + the_result = maybe_result + else: + msg = "More than one %s exists with the name '%s'." + msg = (msg % (_floating_ip.FloatingIP, name_or_id)) + raise sdk_exceptions.DuplicateResource(msg) + + return the_result + + # Try to short-circuit by looking directly for a matching ID. + try: + match = _floating_ip.FloatingIP.existing(id=name_or_id, **params) + return (match.get(session), ip_cache) + except sdk_exceptions.NotFoundException: + pass + + if len(ip_cache) == 0: + ip_cache = list(_floating_ip.FloatingIP.list(session, **params)) + + result = _get_one_match(name_or_id) + if result is not None: + return (result, ip_cache) + + if ignore_missing: + return (None, ip_cache) + raise sdk_exceptions.ResourceNotFound( + "No %s found for %s" % (_floating_ip.FloatingIP.__name__, name_or_id)) + + class CreateFloatingIP(common.NetworkAndComputeShowOne): _description = _("Create floating IP") @@ -186,13 +240,28 @@ def update_parser_common(self, parser): return parser def take_action_network(self, client, parsed_args): - obj = client.find_ip(self.r, ignore_missing=False) + (obj, self.ip_cache) = _find_floating_ip( + client.session, + self.ip_cache, + self.r, + ignore_missing=False, + ) client.delete_ip(obj) def take_action_compute(self, client, parsed_args): obj = utils.find_resource(client.floating_ips, self.r) client.floating_ips.delete(obj.id) + def take_action(self, parsed_args): + """Implements a naive cache for the list of floating IPs""" + + # NOTE(dtroyer): This really only prevents multiple list() + # calls when performing multiple resource deletes + # in a single command. In an interactive session + # each delete command will call list(). + self.ip_cache = [] + super(DeleteFloatingIP, self).take_action(parsed_args) + class DeleteIPFloating(DeleteFloatingIP): _description = _("Delete floating IP(s)") @@ -390,6 +459,9 @@ def take_action_compute(self, client, parsed_args): class ShowFloatingIP(common.NetworkAndComputeShowOne): _description = _("Display floating IP details") + # ip_cache is unused here but is a side effect of _find_floating_ip() + ip_cache = [] + def update_parser_common(self, parser): parser.add_argument( 'floating_ip', @@ -399,7 +471,12 @@ def update_parser_common(self, parser): return parser def take_action_network(self, client, parsed_args): - obj = client.find_ip(parsed_args.floating_ip, ignore_missing=False) + (obj, self.ip_cache) = _find_floating_ip( + client.session, + [], + parsed_args.floating_ip, + ignore_missing=False, + ) display_columns, columns = _get_network_columns(obj) data = utils.get_item_properties(obj, columns) return (display_columns, data) diff --git a/openstackclient/tests/functional/network/v2/test_floating_ip.py b/openstackclient/tests/functional/network/v2/test_floating_ip.py index 5f642f0415..f0b12bc7a8 100644 --- a/openstackclient/tests/functional/network/v2/test_floating_ip.py +++ b/openstackclient/tests/functional/network/v2/test_floating_ip.py @@ -14,8 +14,6 @@ import re import uuid -import testtools - from openstackclient.tests.functional import base @@ -25,7 +23,6 @@ class FloatingIpTests(base.TestCase): NETWORK_NAME = uuid.uuid4().hex @classmethod - @testtools.skip('broken SDK testing') def setUpClass(cls): # Set up some regex for matching below cls.re_id = re.compile("id\s+\|\s+(\S+)") @@ -62,7 +59,6 @@ def tearDownClass(cls): raw_output = cls.openstack('network delete ' + cls.NETWORK_NAME) cls.assertOutput('', raw_output) - @testtools.skip('broken SDK testing') def test_floating_ip_delete(self): """Test create, delete multiple""" raw_output = self.openstack( @@ -93,7 +89,6 @@ def test_floating_ip_delete(self): raw_output = self.openstack('floating ip delete ' + ip1 + ' ' + ip2) self.assertOutput('', raw_output) - @testtools.skip('broken SDK testing') def test_floating_ip_list(self): """Test create defaults, list filters, delete""" raw_output = self.openstack( @@ -135,7 +130,6 @@ def test_floating_ip_list(self): # TODO(dtroyer): add more filter tests - @testtools.skip('broken SDK testing') def test_floating_ip_show(self): """Test show""" raw_output = self.openstack( diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index fe0422face..98d5dea37b 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -52,6 +52,7 @@ class FakeNetworkV2Client(object): def __init__(self, **kwargs): + self.session = mock.Mock() self.extensions = mock.Mock() self.extensions.resource_class = fakes.FakeResource(None, {}) diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip.py b/openstackclient/tests/unit/network/v2/test_floating_ip.py index 63d22bf84c..e395300d99 100644 --- a/openstackclient/tests/unit/network/v2/test_floating_ip.py +++ b/openstackclient/tests/unit/network/v2/test_floating_ip.py @@ -208,13 +208,19 @@ def setUp(self): super(TestDeleteFloatingIPNetwork, self).setUp() self.network.delete_ip = mock.Mock(return_value=None) - self.network.find_ip = ( - network_fakes.FakeFloatingIP.get_floating_ips(self.floating_ips)) # Get the command object to test self.cmd = floating_ip.DeleteFloatingIP(self.app, self.namespace) - def test_floating_ip_delete(self): + @mock.patch( + "openstackclient.tests.unit.network.v2.test_floating_ip." + + "floating_ip._find_floating_ip" + ) + def test_floating_ip_delete(self, find_floating_ip_mock): + find_floating_ip_mock.side_effect = [ + (self.floating_ips[0], []), + (self.floating_ips[1], []), + ] arglist = [ self.floating_ips[0].id, ] @@ -225,12 +231,24 @@ def test_floating_ip_delete(self): result = self.cmd.take_action(parsed_args) - self.network.find_ip.assert_called_once_with( - self.floating_ips[0].id, ignore_missing=False) + find_floating_ip_mock.assert_called_once_with( + mock.ANY, + [], + self.floating_ips[0].id, + ignore_missing=False, + ) self.network.delete_ip.assert_called_once_with(self.floating_ips[0]) self.assertIsNone(result) - def test_multi_floating_ips_delete(self): + @mock.patch( + "openstackclient.tests.unit.network.v2.test_floating_ip." + + "floating_ip._find_floating_ip" + ) + def test_floating_ip_delete_multi(self, find_floating_ip_mock): + find_floating_ip_mock.side_effect = [ + (self.floating_ips[0], []), + (self.floating_ips[1], []), + ] arglist = [] verifylist = [] @@ -243,13 +261,37 @@ def test_multi_floating_ips_delete(self): result = self.cmd.take_action(parsed_args) + calls = [ + call( + mock.ANY, + [], + self.floating_ips[0].id, + ignore_missing=False, + ), + call( + mock.ANY, + [], + self.floating_ips[1].id, + ignore_missing=False, + ), + ] + find_floating_ip_mock.assert_has_calls(calls) + calls = [] for f in self.floating_ips: calls.append(call(f)) self.network.delete_ip.assert_has_calls(calls) self.assertIsNone(result) - def test_multi_floating_ips_delete_with_exception(self): + @mock.patch( + "openstackclient.tests.unit.network.v2.test_floating_ip." + + "floating_ip._find_floating_ip" + ) + def test_floating_ip_delete_multi_exception(self, find_floating_ip_mock): + find_floating_ip_mock.side_effect = [ + (self.floating_ips[0], []), + exceptions.CommandError, + ] arglist = [ self.floating_ips[0].id, 'unexist_floating_ip', @@ -260,21 +302,24 @@ def test_multi_floating_ips_delete_with_exception(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - find_mock_result = [self.floating_ips[0], exceptions.CommandError] - self.network.find_ip = ( - mock.Mock(side_effect=find_mock_result) - ) - try: self.cmd.take_action(parsed_args) self.fail('CommandError should be raised.') except exceptions.CommandError as e: self.assertEqual('1 of 2 floating_ips failed to delete.', str(e)) - self.network.find_ip.assert_any_call( - self.floating_ips[0].id, ignore_missing=False) - self.network.find_ip.assert_any_call( - 'unexist_floating_ip', ignore_missing=False) + find_floating_ip_mock.assert_any_call( + mock.ANY, + [], + self.floating_ips[0].id, + ignore_missing=False, + ) + find_floating_ip_mock.assert_any_call( + mock.ANY, + [], + 'unexist_floating_ip', + ignore_missing=False, + ) self.network.delete_ip.assert_called_once_with( self.floating_ips[0] ) @@ -534,7 +579,12 @@ def setUp(self): # Get the command object to test self.cmd = floating_ip.ShowFloatingIP(self.app, self.namespace) - def test_floating_ip_show(self): + @mock.patch( + "openstackclient.tests.unit.network.v2.test_floating_ip." + + "floating_ip._find_floating_ip" + ) + def test_floating_ip_show(self, find_floating_ip_mock): + find_floating_ip_mock.return_value = (self.floating_ip, []) arglist = [ self.floating_ip.id, ] @@ -545,9 +595,11 @@ def test_floating_ip_show(self): columns, data = self.cmd.take_action(parsed_args) - self.network.find_ip.assert_called_once_with( + find_floating_ip_mock.assert_called_once_with( + mock.ANY, + [], self.floating_ip.id, - ignore_missing=False + ignore_missing=False, ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) diff --git a/releasenotes/notes/bug-1656402-88b12760fb2d4ef9.yaml b/releasenotes/notes/bug-1656402-88b12760fb2d4ef9.yaml new file mode 100644 index 0000000000..c8a302982a --- /dev/null +++ b/releasenotes/notes/bug-1656402-88b12760fb2d4ef9.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Fix ``floating ip delete`` and ``floating ip show`` to accept IP addresses + in addition to IDs to select floating IPs to delete or show. + [Bug `1656402 `_ From a76d38119befe654bd9ef906c55de0d334ac950a Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Fri, 13 Jan 2017 12:00:31 -0800 Subject: [PATCH 1487/3095] Fix network functional tests for sdk 0.9.12 it seems the SDK returns "is_default", but doesn't actually set it based on --default or --is-default I already had these functioanl tests mostly converted to JSON, so I dumped that in here too to make the is_default stuff cleaner. Change-Id: I47ca62f57c73aa7fa984ca54742fc86b6bb837c4 --- .../functional/network/v2/test_network.py | 229 ++++++++++-------- 1 file changed, 125 insertions(+), 104 deletions(-) diff --git a/openstackclient/tests/functional/network/v2/test_network.py b/openstackclient/tests/functional/network/v2/test_network.py index ef42dcceef..c55d70f9af 100644 --- a/openstackclient/tests/functional/network/v2/test_network.py +++ b/openstackclient/tests/functional/network/v2/test_network.py @@ -10,164 +10,173 @@ # License for the specific language governing permissions and limitations # under the License. -import re +import json import uuid -import testtools - from openstackclient.tests.functional import base class NetworkTests(base.TestCase): """Functional tests for network""" - @classmethod - def setUpClass(cls): - # Set up some regex for matching below - cls.re_id = re.compile("id\s+\|\s+(\S+)") - cls.re_description = re.compile("description\s+\|\s+([^|]+?)\s+\|") - cls.re_enabled = re.compile("admin_state_up\s+\|\s+(\S+)") - cls.re_shared = re.compile("shared\s+\|\s+(\S+)") - cls.re_external = re.compile("router:external\s+\|\s+(\S+)") - cls.re_default = re.compile("is_default\s+\|\s+(\S+)") - cls.re_port_security = re.compile( - "port_security_enabled\s+\|\s+(\S+)" - ) - def test_network_delete(self): """Test create, delete multiple""" name1 = uuid.uuid4().hex - raw_output = self.openstack( - 'network create ' + + cmd_output = json.loads(self.openstack( + 'network create -f json ' + '--description aaaa ' + name1 - ) + )) + self.assertIsNotNone(cmd_output["id"]) self.assertEqual( 'aaaa', - re.search(self.re_description, raw_output).group(1), + cmd_output["description"], ) + name2 = uuid.uuid4().hex - raw_output = self.openstack( - 'network create ' + + cmd_output = json.loads(self.openstack( + 'network create -f json ' + '--description bbbb ' + name2 - ) + )) + self.assertIsNotNone(cmd_output["id"]) self.assertEqual( 'bbbb', - re.search(self.re_description, raw_output).group(1), + cmd_output["description"], ) del_output = self.openstack('network delete ' + name1 + ' ' + name2) self.assertOutput('', del_output) - @testtools.skip('broken SDK testing') def test_network_list(self): """Test create defaults, list filters, delete""" name1 = uuid.uuid4().hex - raw_output = self.openstack( - 'network create ' + + cmd_output = json.loads(self.openstack( + 'network create -f json ' + '--description aaaa ' + name1 - ) + )) self.addCleanup(self.openstack, 'network delete ' + name1) + self.assertIsNotNone(cmd_output["id"]) self.assertEqual( 'aaaa', - re.search(self.re_description, raw_output).group(1), + cmd_output["description"], ) # Check the default values self.assertEqual( 'UP', - re.search(self.re_enabled, raw_output).group(1), + cmd_output["admin_state_up"], ) self.assertEqual( - 'False', - re.search(self.re_shared, raw_output).group(1), + False, + cmd_output["shared"], ) self.assertEqual( 'Internal', - re.search(self.re_external, raw_output).group(1), + cmd_output["router:external"], ) # NOTE(dtroyer): is_default is not present in the create output # so make sure it stays that way. - self.assertIsNone(re.search(self.re_default, raw_output)) + # NOTE(stevemar): is_default *is* present in SDK 0.9.11 and newer, + # but the value seems to always be None, regardless + # of the --default or --no-default value. + # self.assertEqual('x', cmd_output) + if ('is_default' in cmd_output): + self.assertEqual( + None, + cmd_output["is_default"], + ) self.assertEqual( - 'True', - re.search(self.re_port_security, raw_output).group(1), + True, + cmd_output["port_security_enabled"], ) name2 = uuid.uuid4().hex - raw_output = self.openstack( - 'network create ' + + cmd_output = json.loads(self.openstack( + 'network create -f json ' + '--description bbbb ' + '--disable ' + '--share ' + name2 - ) + )) self.addCleanup(self.openstack, 'network delete ' + name2) + self.assertIsNotNone(cmd_output["id"]) self.assertEqual( 'bbbb', - re.search(self.re_description, raw_output).group(1), + cmd_output["description"], ) self.assertEqual( 'DOWN', - re.search(self.re_enabled, raw_output).group(1), + cmd_output["admin_state_up"], ) self.assertEqual( - 'True', - re.search(self.re_shared, raw_output).group(1), + True, + cmd_output["shared"], + ) + if ('is_default' in cmd_output): + self.assertEqual( + None, + cmd_output["is_default"], + ) + self.assertEqual( + True, + cmd_output["port_security_enabled"], ) # Test list --long - raw_output = self.openstack('network list --long') - self.assertIsNotNone( - re.search("\|\s+" + name1 + "\s+\|\s+ACTIVE", raw_output) - ) - self.assertIsNotNone( - re.search("\|\s+" + name2 + "\s+\|\s+ACTIVE", raw_output) - ) + cmd_output = json.loads(self.openstack( + "network list -f json " + + "--long" + )) + col_name = [x["Name"] for x in cmd_output] + self.assertIn(name1, col_name) + self.assertIn(name2, col_name) # Test list --long --enable - raw_output = self.openstack('network list --long --enable') - self.assertIsNotNone( - re.search("\|\s+" + name1 + "\s+\|\s+ACTIVE", raw_output) - ) - self.assertIsNone( - re.search("\|\s+" + name2 + "\s+\|\s+ACTIVE", raw_output) - ) + cmd_output = json.loads(self.openstack( + "network list -f json " + + "--enable " + + "--long" + )) + col_name = [x["Name"] for x in cmd_output] + self.assertIn(name1, col_name) + self.assertNotIn(name2, col_name) # Test list --long --disable - raw_output = self.openstack('network list --long --disable') - self.assertIsNone( - re.search("\|\s+" + name1 + "\s+\|\s+ACTIVE", raw_output) - ) - self.assertIsNotNone( - re.search("\|\s+" + name2 + "\s+\|\s+ACTIVE", raw_output) - ) + cmd_output = json.loads(self.openstack( + "network list -f json " + + "--disable " + + "--long" + )) + col_name = [x["Name"] for x in cmd_output] + self.assertNotIn(name1, col_name) + self.assertIn(name2, col_name) # Test list --long --share - raw_output = self.openstack('network list --long --share') - self.assertIsNone( - re.search("\|\s+" + name1 + "\s+\|\s+ACTIVE", raw_output) - ) - self.assertIsNotNone( - re.search("\|\s+" + name2 + "\s+\|\s+ACTIVE", raw_output) - ) + cmd_output = json.loads(self.openstack( + "network list -f json " + + "--share " + + "--long" + )) + col_name = [x["Name"] for x in cmd_output] + self.assertNotIn(name1, col_name) + self.assertIn(name2, col_name) # Test list --long --no-share - raw_output = self.openstack('network list --long --no-share') - self.assertIsNotNone( - re.search("\|\s+" + name1 + "\s+\|\s+ACTIVE", raw_output) - ) - self.assertIsNone( - re.search("\|\s+" + name2 + "\s+\|\s+ACTIVE", raw_output) - ) + cmd_output = json.loads(self.openstack( + "network list -f json " + + "--no-share " + + "--long" + )) + col_name = [x["Name"] for x in cmd_output] + self.assertIn(name1, col_name) + self.assertNotIn(name2, col_name) - @testtools.skip('broken SDK testing') def test_network_set(self): """Tests create options, set, show, delete""" name = uuid.uuid4().hex - raw_output = self.openstack( - 'network create ' + + cmd_output = json.loads(self.openstack( + 'network create -f json ' + '--description aaaa ' + '--enable ' + '--no-share ' + @@ -175,30 +184,38 @@ def test_network_set(self): '--no-default ' + '--enable-port-security ' + name - ) + )) self.addCleanup(self.openstack, 'network delete ' + name) + self.assertIsNotNone(cmd_output["id"]) self.assertEqual( 'aaaa', - re.search(self.re_description, raw_output).group(1), + cmd_output["description"], ) self.assertEqual( 'UP', - re.search(self.re_enabled, raw_output).group(1), + cmd_output["admin_state_up"], ) self.assertEqual( - 'False', - re.search(self.re_shared, raw_output).group(1), + False, + cmd_output["shared"], ) self.assertEqual( 'Internal', - re.search(self.re_external, raw_output).group(1), + cmd_output["router:external"], ) # NOTE(dtroyer): is_default is not present in the create output # so make sure it stays that way. - self.assertIsNone(re.search(self.re_default, raw_output)) + # NOTE(stevemar): is_default *is* present in SDK 0.9.11 and newer, + # but the value seems to always be None, regardless + # of the --default or --no-default value. + if ('is_default' in cmd_output): + self.assertEqual( + None, + cmd_output["is_default"], + ) self.assertEqual( - 'True', - re.search(self.re_port_security, raw_output).group(1), + True, + cmd_output["port_security_enabled"], ) raw_output = self.openstack( @@ -212,32 +229,34 @@ def test_network_set(self): ) self.assertOutput('', raw_output) - raw_output = self.openstack('network show ' + name) + cmd_output = json.loads(self.openstack( + 'network show -f json ' + name + )) self.assertEqual( 'cccc', - re.search(self.re_description, raw_output).group(1), + cmd_output["description"], ) self.assertEqual( 'DOWN', - re.search(self.re_enabled, raw_output).group(1), + cmd_output["admin_state_up"], ) self.assertEqual( - 'True', - re.search(self.re_shared, raw_output).group(1), + True, + cmd_output["shared"], ) self.assertEqual( 'External', - re.search(self.re_external, raw_output).group(1), + cmd_output["router:external"], ) # why not 'None' like above?? self.assertEqual( - 'False', - re.search(self.re_default, raw_output).group(1), + False, + cmd_output["is_default"], ) self.assertEqual( - 'False', - re.search(self.re_port_security, raw_output).group(1), + False, + cmd_output["port_security_enabled"], ) # NOTE(dtroyer): There is ambiguity around is_default in that @@ -252,14 +271,16 @@ def test_network_set(self): ) self.assertOutput('', raw_output) - raw_output = self.openstack('network show ' + name) + cmd_output = json.loads(self.openstack( + 'network show -f json ' + name + )) self.assertEqual( 'cccc', - re.search(self.re_description, raw_output).group(1), + cmd_output["description"], ) # NOTE(dtroyer): This should be 'True' self.assertEqual( - 'False', - re.search(self.re_default, raw_output).group(1), + False, + cmd_output["port_security_enabled"], ) From 819526591ee2cdbf7f138a08f9c38b9c804e5d31 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Sat, 14 Jan 2017 12:57:09 -0600 Subject: [PATCH 1488/3095] Fix quota show --default command Work around a bug in OpenStack SDK 0.9.11 and 0.9.12 that causes quota show --default to fail. This can be removed when the proposed SDK fix (https://review.openstack.org/420301) is reelased and in the minimum SDK version in global requirements. quota set --network is still broken, I can't fix it at the moment... Closes-bug: 1656572 Change-Id: Ice77e14782c33e672176afbab36bba95b73d7a11 --- openstackclient/common/quota.py | 36 ++++++++++++++++++- .../tests/functional/common/test_quota.py | 3 -- .../notes/bug-1656572-b40303ae50a41000.yaml | 6 ++++ 3 files changed, 41 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/bug-1656572-b40303ae50a41000.yaml diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index afc6195f93..fa6c576552 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -18,6 +18,8 @@ import itertools import sys +from openstack import exceptions as sdk_exceptions +from openstack.network.v2 import quota as _quota from osc_lib.command import command from osc_lib import utils import six @@ -251,7 +253,39 @@ def get_network_quota(self, parsed_args): project = self._get_project(parsed_args) client = self.app.client_manager.network if parsed_args.default: - network_quota = client.get_quota_default(project) + # TODO(dtroyer): Remove the top of this if block once the + # fixed SDK QuotaDefault class is the minimum + # required version. This is expected to be + # SDK release 0.9.13 + if hasattr(_quota.QuotaDefault, 'project'): + # hack 0.9.11+ + quotadef_obj = client._get_resource( + _quota.QuotaDefault, + project, + ) + quotadef_obj.base_path = quotadef_obj.base_path % { + 'project': project, + } + try: + network_quota = quotadef_obj.get( + client.session, + requires_id=False, + ) + except sdk_exceptions.NotFoundException as e: + raise sdk_exceptions.ResourceNotFound( + message="No %s found for %s" % + (_quota.QuotaDefault.__name__, project), + details=e.details, + response=e.response, + request_id=e.request_id, + url=e.url, + method=e.method, + http_status=e.http_status, + cause=e.cause, + ) + # end hack-around + else: + network_quota = client.get_quota_default(project) else: network_quota = client.get_quota(project) return network_quota diff --git a/openstackclient/tests/functional/common/test_quota.py b/openstackclient/tests/functional/common/test_quota.py index fbb8e56367..b2b198afb4 100644 --- a/openstackclient/tests/functional/common/test_quota.py +++ b/openstackclient/tests/functional/common/test_quota.py @@ -35,19 +35,16 @@ def test_quota_set(self): raw_output = self.openstack('quota show ' + self.PROJECT_NAME + opts) self.assertEqual("11\n11\n11\n", raw_output) - @testtools.skip('broken SDK testing') def test_quota_show(self): raw_output = self.openstack('quota show ' + self.PROJECT_NAME) for expected_field in self.EXPECTED_FIELDS: self.assertIn(expected_field, raw_output) - @testtools.skip('broken SDK testing') def test_quota_show_default_project(self): raw_output = self.openstack('quota show') for expected_field in self.EXPECTED_FIELDS: self.assertIn(expected_field, raw_output) - @testtools.skip('broken SDK testing') def test_quota_show_with_default_option(self): raw_output = self.openstack('quota show --default') for expected_field in self.EXPECTED_FIELDS: diff --git a/releasenotes/notes/bug-1656572-b40303ae50a41000.yaml b/releasenotes/notes/bug-1656572-b40303ae50a41000.yaml new file mode 100644 index 0000000000..e6d0bcc593 --- /dev/null +++ b/releasenotes/notes/bug-1656572-b40303ae50a41000.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Work around a bug in OpenStackSDK 0.9.11 and 0.9.12 that causes + ``quota show --default`` to fail. + [Bug `1656572 `_] From 63cdf079b102a0b575ab760435130e23cdbc0db5 Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Fri, 13 Jan 2017 14:18:32 -0600 Subject: [PATCH 1489/3095] Use git+file urls instead of directories The /opt/stack/new directories are owned by the wrong user, so python setup.py egg_info fails because it can't create the egg_info dir. Changing the invocation to use git+file:// urls solves the problem. Additionally, make a correction to test collection. Change-Id: I39da0b26417dce1a72b15dedc02d10284329307f --- openstackclient/tests/functional/post_test_hook_tips.sh | 9 +++++---- tox.ini | 7 ++++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/openstackclient/tests/functional/post_test_hook_tips.sh b/openstackclient/tests/functional/post_test_hook_tips.sh index 994142d883..28ab958006 100755 --- a/openstackclient/tests/functional/post_test_hook_tips.sh +++ b/openstackclient/tests/functional/post_test_hook_tips.sh @@ -7,13 +7,14 @@ # http://docs.openstack.org/developer/python-openstackclient/ # This particular script differs from the normal post_test_hook because -# it installs the master (tip) version of osc-lib and openstacksdk +# it installs the master (tip) version of osc-lib, os-client-config +# and openstacksdk, OSCs most important dependencies. function generate_testr_results { if [ -f .testrepository/0 ]; then - sudo .tox/functional/bin/testr last --subunit > $WORKSPACE/testrepository.subunit + sudo .tox/functional-tips/bin/testr last --subunit > $WORKSPACE/testrepository.subunit sudo mv $WORKSPACE/testrepository.subunit $BASE/logs/testrepository.subunit - sudo .tox/functional/bin/subunit2html $BASE/logs/testrepository.subunit $BASE/logs/testr_results.html + sudo .tox/functional-tips/bin/subunit2html $BASE/logs/testrepository.subunit $BASE/logs/testr_results.html sudo gzip -9 $BASE/logs/testrepository.subunit sudo gzip -9 $BASE/logs/testr_results.html sudo chown jenkins:jenkins $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz @@ -28,7 +29,7 @@ sudo chown -R jenkins:stack $OPENSTACKCLIENT_DIR cd $OPENSTACKCLIENT_DIR # Run tests -echo "Running openstackclient functional test suite" +echo "Running openstackclient functional-tips test suite" set +e # Source environment variables to kick things off diff --git a/tox.ini b/tox.ini index 22d394c439..130b32fe83 100644 --- a/tox.ini +++ b/tox.ini @@ -58,9 +58,10 @@ passenv = OS_* setenv = OS_TEST_PATH=./openstackclient/tests/functional passenv = OS_* commands = - pip install -q -U -e /opt/stack/new/osc-lib - pip install -q -U -e /opt/stack/new/python-openstacksdk - pip install -q -U -e /opt/stack/new/os-client-config + pip install -q -U -e "git+file:///opt/stack/new/osc-lib#egg=osc_lib" + pip install -q -U -e "git+file:///opt/stack/new/python-openstacksdk#egg=openstacksdk" + pip install -q -U -e "git+file:///opt/stack/new/os-client-config#egg=os_client_config" + pip freeze ostestr {posargs} [testenv:venv] From 4c5cea46bed0812c090cb6732fc9bc4a8dead387 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Sat, 14 Jan 2017 18:36:28 -0500 Subject: [PATCH 1490/3095] unskip port test seems like we fixed this one when fixing other tests but never unskipped it. Change-Id: Id1a3d11b581b6f72ad62a8699899b6163c3870bb --- openstackclient/tests/functional/network/v2/test_port.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/openstackclient/tests/functional/network/v2/test_port.py b/openstackclient/tests/functional/network/v2/test_port.py index 976fbedb8d..decd9553dc 100644 --- a/openstackclient/tests/functional/network/v2/test_port.py +++ b/openstackclient/tests/functional/network/v2/test_port.py @@ -12,8 +12,6 @@ import uuid -import testtools - from openstackclient.tests.functional import base @@ -25,7 +23,6 @@ class PortTests(base.TestCase): FIELDS = ['name'] @classmethod - @testtools.skip('broken SDK testing') def setUpClass(cls): # Create a network for the subnet. cls.openstack('network create ' + cls.NETWORK_NAME) From d9361cbb704b721829fa88408a7b1b58bcbdc6f4 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sun, 15 Jan 2017 09:28:59 +0000 Subject: [PATCH 1491/3095] Updated from global requirements Change-Id: Idb49f1e126d1dab070894e2fc70c78b9984f988a --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 08b49c9880..b53d08dd71 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -28,7 +28,7 @@ python-barbicanclient>=4.0.0 # Apache-2.0 python-congressclient<2000,>=1.3.0 # Apache-2.0 python-designateclient>=1.5.0 # Apache-2.0 python-heatclient>=1.6.1 # Apache-2.0 -python-ironicclient>=1.6.0 # Apache-2.0 +python-ironicclient>=1.9.0 # Apache-2.0 python-ironic-inspector-client>=1.5.0 # Apache-2.0 python-mistralclient>=2.0.0 # Apache-2.0 python-muranoclient>=0.8.2 # Apache-2.0 From b860ba0e42b942a1a5fb01b55d35a2edf062bf65 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Sat, 14 Jan 2017 19:13:04 +0800 Subject: [PATCH 1492/3095] SDK refactor: Prepare network agent commands Prepare the OSC "network agent" commands for the SDK refactor. Partially-Implements: blueprint network-command-sdk-support Closes-bug: #1656542 Change-Id: I6745f5ffb04b009487e6cb36d1807dce9b248b4c --- openstackclient/network/v2/network_agent.py | 24 +++++++++++++++---- .../tests/unit/network/v2/fakes.py | 2 ++ .../unit/network/v2/test_network_agent.py | 2 ++ 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/openstackclient/network/v2/network_agent.py b/openstackclient/network/v2/network_agent.py index 7d36e5e9f0..d429fa0830 100644 --- a/openstackclient/network/v2/network_agent.py +++ b/openstackclient/network/v2/network_agent.py @@ -20,6 +20,7 @@ from osc_lib import utils from openstackclient.i18n import _ +from openstackclient.network import sdk_utils LOG = logging.getLogger(__name__) @@ -31,10 +32,19 @@ def _format_admin_state(state): _formatters = { 'admin_state_up': _format_admin_state, + 'is_admin_state_up': _format_admin_state, 'configurations': utils.format_dict, } +def _get_network_columns(item): + column_map = { + 'is_admin_state_up': 'admin_state_up', + 'is_alive': 'alive', + } + return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + + class DeleteNetworkAgent(command.Command): _description = _("Delete network agent(s)") @@ -69,6 +79,8 @@ def take_action(self, parsed_args): raise exceptions.CommandError(msg) +# TODO(huanxuan): Use the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. class ListNetworkAgent(command.Lister): _description = _("List network agents") @@ -98,8 +110,8 @@ def take_action(self, parsed_args): 'agent_type', 'host', 'availability_zone', - 'alive', - 'admin_state_up', + 'is_alive', + 'is_admin_state_up', 'binary' ) column_headers = ( @@ -138,6 +150,8 @@ def take_action(self, parsed_args): ) for s in data)) +# TODO(huanxuan): Use the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. class SetNetworkAgent(command.Command): _description = _("Set network agent properties") @@ -172,6 +186,8 @@ def take_action(self, parsed_args): attrs = {} if parsed_args.description is not None: attrs['description'] = str(parsed_args.description) + # TODO(huanxuan): Also update by the new attribute name + # "is_admin_state_up" after sdk 0.9.12 if parsed_args.enable: attrs['admin_state_up'] = True if parsed_args.disable: @@ -194,6 +210,6 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): client = self.app.client_manager.network obj = client.get_agent(parsed_args.network_agent) - columns = tuple(sorted(list(obj.keys()))) + display_columns, columns = _get_network_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters,) - return columns, data + return display_columns, data diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index 98d5dea37b..524285ab18 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -565,6 +565,8 @@ def create_one_network_agent(attrs=None): agent_attrs.update(attrs) agent = fakes.FakeResource(info=copy.deepcopy(agent_attrs), loaded=True) + agent.is_admin_state_up = agent_attrs['admin_state_up'] + agent.is_alive = agent_attrs['alive'] return agent @staticmethod diff --git a/openstackclient/tests/unit/network/v2/test_network_agent.py b/openstackclient/tests/unit/network/v2/test_network_agent.py index 9964f14da5..2fc0c04328 100644 --- a/openstackclient/tests/unit/network/v2/test_network_agent.py +++ b/openstackclient/tests/unit/network/v2/test_network_agent.py @@ -195,6 +195,8 @@ def test_network_agents_list_host(self): self.assertEqual(self.data, list(data)) +# TODO(huanxuan): Also update by the new attribute name +# "is_admin_state_up" after sdk 0.9.12 class TestSetNetworkAgent(TestNetworkAgent): _network_agent = ( From 0340275fa9b48cda5a5f4015534ca8cbca23b3d2 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Sun, 15 Jan 2017 14:37:49 +0800 Subject: [PATCH 1493/3095] Fix quota set command error for SDK > 0.9.10 A bug in OpenStack SDK 0.9.11 and 0.9.12 that causes quota set command to fail. This can be removed when the proposed SDK fix (https://review.openstack.org/#/c/419911/) is released and in the minimum SDK version in global requirements. Closes-Bug: #1655445 Change-Id: I63132f5f762f0120282f8b92e72512763063e3c6 --- openstackclient/common/quota.py | 33 ++++++++- .../tests/functional/common/test_quota.py | 3 - .../tests/unit/common/test_quota.py | 72 +++++++++++-------- .../notes/bug-1655445-96c787e3a51226e0.yaml | 6 ++ 4 files changed, 78 insertions(+), 36 deletions(-) create mode 100644 releasenotes/notes/bug-1655445-96c787e3a51226e0.yaml diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index fa6c576552..d86aec58fb 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -182,9 +182,36 @@ def take_action(self, parsed_args): project, **volume_kwargs) if network_kwargs: - network_client.update_quota( - project, - **network_kwargs) + if hasattr(_quota.Quota, 'allow_get'): + # TODO(huanxuan): Remove this block once the fixed + # SDK Quota class is the minimum required version. + # This is expected to be SDK release 0.9.13 + res = network_client._get_resource( + _quota.Quota, project, **network_kwargs) + if any([res._body.dirty, res._header.dirty]): + request = res._prepare_request(prepend_key=True) + # remove the id in the body + if 'id' in request.body[res.resource_key]: + del request.body[res.resource_key]['id'] + if res.patch_update: + response = network_client.session.patch( + request.uri, + endpoint_filter=res.service, + json=request.body, + headers=request.headers + ) + else: + response = network_client.session.put( + request.uri, + endpoint_filter=res.service, + json=request.body, + headers=request.headers + ) + res._translate_response(response, has_body=True) + else: + network_client.update_quota( + project, + **network_kwargs) class ShowQuota(command.ShowOne): diff --git a/openstackclient/tests/functional/common/test_quota.py b/openstackclient/tests/functional/common/test_quota.py index b2b198afb4..c1de9aa92d 100644 --- a/openstackclient/tests/functional/common/test_quota.py +++ b/openstackclient/tests/functional/common/test_quota.py @@ -10,8 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -import testtools - from openstackclient.tests.functional import base @@ -27,7 +25,6 @@ def setUpClass(cls): cls.PROJECT_NAME =\ cls.get_openstack_configuration_value('auth.project_name') - @testtools.skip('broken SDK testing') def test_quota_set(self): self.openstack('quota set --instances 11 --volumes 11 --networks 11 ' + self.PROJECT_NAME) diff --git a/openstackclient/tests/unit/common/test_quota.py b/openstackclient/tests/unit/common/test_quota.py index 7dd2337321..244d74d25d 100644 --- a/openstackclient/tests/unit/common/test_quota.py +++ b/openstackclient/tests/unit/common/test_quota.py @@ -13,6 +13,8 @@ import copy import mock +from openstack.network.v2 import quota as _quota + from openstackclient.common import quota from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes from openstackclient.tests.unit import fakes @@ -282,27 +284,32 @@ def test_quota_set_network(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) - kwargs = { - 'subnet': network_fakes.QUOTA['subnet'], - 'network': network_fakes.QUOTA['network'], - 'floatingip': network_fakes.QUOTA['floatingip'], - 'subnetpool': network_fakes.QUOTA['subnetpool'], - 'security_group_rule': - network_fakes.QUOTA['security_group_rule'], - 'security_group': network_fakes.QUOTA['security_group'], - 'router': network_fakes.QUOTA['router'], - 'rbac_policy': network_fakes.QUOTA['rbac_policy'], - 'port': network_fakes.QUOTA['port'], - 'vip': network_fakes.QUOTA['vip'], - 'healthmonitor': network_fakes.QUOTA['healthmonitor'], - 'l7policy': network_fakes.QUOTA['l7policy'], - } - self.network_mock.update_quota.assert_called_once_with( - identity_fakes.project_id, - **kwargs - ) - self.assertIsNone(result) + # TODO(huanxuan): Remove this if condition once the fixed + # SDK Quota class is the minimum required version. + # This is expected to be SDK release 0.9.13 + if not hasattr(_quota.Quota, 'allow_get'): + # Just run this when sdk <= 0.9.10 + result = self.cmd.take_action(parsed_args) + kwargs = { + 'subnet': network_fakes.QUOTA['subnet'], + 'network': network_fakes.QUOTA['network'], + 'floatingip': network_fakes.QUOTA['floatingip'], + 'subnetpool': network_fakes.QUOTA['subnetpool'], + 'security_group_rule': + network_fakes.QUOTA['security_group_rule'], + 'security_group': network_fakes.QUOTA['security_group'], + 'router': network_fakes.QUOTA['router'], + 'rbac_policy': network_fakes.QUOTA['rbac_policy'], + 'port': network_fakes.QUOTA['port'], + 'vip': network_fakes.QUOTA['vip'], + 'healthmonitor': network_fakes.QUOTA['healthmonitor'], + 'l7policy': network_fakes.QUOTA['l7policy'], + } + self.network_mock.update_quota.assert_called_once_with( + identity_fakes.project_id, + **kwargs + ) + self.assertIsNone(result) def test_quota_set_with_class(self): arglist = [ @@ -476,15 +483,20 @@ def test_quota_show_with_default(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) - - self.quotas_mock.defaults.assert_called_once_with( - identity_fakes.project_id) - self.volume_quotas_mock.defaults.assert_called_once_with( - identity_fakes.project_id) - self.network.get_quota_default.assert_called_once_with( - identity_fakes.project_id) - self.assertNotCalled(self.network.get_quota) + # TODO(huanxuan): Remove this if condition once the fixed + # SDK QuotaDefault class is the minimum required version. + # This is expected to be SDK release 0.9.13 + if not hasattr(_quota.QuotaDefault, 'project'): + # Just run this when sdk <= 0.9.10 + self.cmd.take_action(parsed_args) + + self.quotas_mock.defaults.assert_called_once_with( + identity_fakes.project_id) + self.volume_quotas_mock.defaults.assert_called_once_with( + identity_fakes.project_id) + self.network.get_quota_default.assert_called_once_with( + identity_fakes.project_id) + self.assertNotCalled(self.network.get_quota) def test_quota_show_with_class(self): arglist = [ diff --git a/releasenotes/notes/bug-1655445-96c787e3a51226e0.yaml b/releasenotes/notes/bug-1655445-96c787e3a51226e0.yaml new file mode 100644 index 0000000000..0f8f1ad18f --- /dev/null +++ b/releasenotes/notes/bug-1655445-96c787e3a51226e0.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Work around a bug in OpenStackSDK 0.9.11 and 0.9.12 that causes + ``quota set --network`` to fail. + [Bug `1655445 `_] From 4d3cfb9142be8884fa74a6a8b324df869e32ba30 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 12 Jan 2017 16:01:30 -0600 Subject: [PATCH 1494/3095] Release 3.7.0 cleanup Change-Id: I75d9e2473461e316086cfad8c886c6b7d22ecd5e --- .../add-overwrite-option-to-router-7c50c8031dab6bae.yaml | 5 ++--- releasenotes/notes/bug-1652827-f59bbd1b64df958d.yaml | 6 ++++++ ...age-set-to-update-image-membership-68221f226ca3b6e0.yaml | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/bug-1652827-f59bbd1b64df958d.yaml diff --git a/releasenotes/notes/add-overwrite-option-to-router-7c50c8031dab6bae.yaml b/releasenotes/notes/add-overwrite-option-to-router-7c50c8031dab6bae.yaml index ac83b7e84e..1b022cf131 100644 --- a/releasenotes/notes/add-overwrite-option-to-router-7c50c8031dab6bae.yaml +++ b/releasenotes/notes/add-overwrite-option-to-router-7c50c8031dab6bae.yaml @@ -1,7 +1,6 @@ --- features: - | - Add support to overwrite routes in a router instance, using ``--router`` - and ``--no-router`` option in ``osc router set``. + Add ``--router`` and ``--no-router`` options to ``osc router set`` command to + modify routes in a router instance. [ Blueprint `allow-overwrite-set-options `_] - diff --git a/releasenotes/notes/bug-1652827-f59bbd1b64df958d.yaml b/releasenotes/notes/bug-1652827-f59bbd1b64df958d.yaml new file mode 100644 index 0000000000..24991cbd1c --- /dev/null +++ b/releasenotes/notes/bug-1652827-f59bbd1b64df958d.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Fix an endpoint version problem with Image endpoints that contained the + substring 'v2'. + [Bug `1652827 `_] diff --git a/releasenotes/notes/image-set-to-update-image-membership-68221f226ca3b6e0.yaml b/releasenotes/notes/image-set-to-update-image-membership-68221f226ca3b6e0.yaml index 599216c47b..6db63f871a 100644 --- a/releasenotes/notes/image-set-to-update-image-membership-68221f226ca3b6e0.yaml +++ b/releasenotes/notes/image-set-to-update-image-membership-68221f226ca3b6e0.yaml @@ -1,4 +1,4 @@ --- features: - - Add support to update image membership with the `--accept`, + - Add support to update image membership with the ``--accept``, ``--reject`` and ``--pending`` options of the ``image set command``. From 603543bb71db27a46689cfb45a82637080c29c09 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Mon, 16 Jan 2017 17:28:09 +0000 Subject: [PATCH 1495/3095] Updated from global requirements Change-Id: Ic8eef0aa955d747ae7edf19761f8d682cf95d63f --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 26086364d7..e5ca3c7ba4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ six>=1.9.0 # MIT Babel>=2.3.4 # BSD cliff>=2.3.0 # Apache-2.0 -keystoneauth1>=2.16.0 # Apache-2.0 +keystoneauth1>=2.17.0 # Apache-2.0 openstacksdk!=0.9.11,!=0.9.12,>=0.9.10 # Apache-2.0 osc-lib>=1.2.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 From 2476a26d995a1415357eeffb78a1a832be8ed470 Mon Sep 17 00:00:00 2001 From: "zhiyong.dai" Date: Tue, 22 Nov 2016 02:19:12 +0800 Subject: [PATCH 1496/3095] Update functional test for aggregate. Add the following functional tests : option: "--no-property" command: "aggregate set --zone", "aggregate add host", "aggregate remove host". Change-Id: Ia9c31facb5f0f5b92b8df950fd4021b8ecc924c5 --- .../functional/compute/v2/test_aggregate.py | 46 +++++++++++++++++-- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/openstackclient/tests/functional/compute/v2/test_aggregate.py b/openstackclient/tests/functional/compute/v2/test_aggregate.py index 2bc88e7b59..383681034a 100644 --- a/openstackclient/tests/functional/compute/v2/test_aggregate.py +++ b/openstackclient/tests/functional/compute/v2/test_aggregate.py @@ -48,7 +48,7 @@ def test_aggregate_show(self): self.assertEqual(self.NAME + "\n", raw_output) def test_aggregate_properties(self): - opts = self.get_opts(['properties']) + opts = self.get_opts(['name', 'properties']) raw_output = self.openstack( 'aggregate set --property a=b --property c=d ' + self.NAME @@ -56,7 +56,7 @@ def test_aggregate_properties(self): self.assertEqual('', raw_output) raw_output = self.openstack('aggregate show ' + self.NAME + opts) - self.assertIn("a='b', c='d'\n", raw_output) + self.assertIn(self.NAME + "\na='b', c='d'\n", raw_output) raw_output = self.openstack( 'aggregate unset --property a ' + self.NAME @@ -64,4 +64,44 @@ def test_aggregate_properties(self): self.assertEqual('', raw_output) raw_output = self.openstack('aggregate show ' + self.NAME + opts) - self.assertIn("c='d'\n", raw_output) + self.assertIn(self.NAME + "\nc='d'\n", raw_output) + + raw_output = self.openstack( + 'aggregate set --property a=b --property c=d ' + self.NAME + ) + self.assertEqual('', raw_output) + + raw_output = self.openstack( + 'aggregate set --no-property ' + self.NAME + ) + self.assertEqual('', raw_output) + + raw_output = self.openstack('aggregate show ' + self.NAME + opts) + self.assertNotIn("a='b', c='d'", raw_output) + + def test_aggregate_set(self): + opts = self.get_opts(["name", "availability_zone"]) + + raw_output = self.openstack( + 'aggregate set --zone Zone_1 ' + self.NAME) + self.assertEqual("", raw_output) + + raw_output = self.openstack('aggregate show ' + self.NAME + opts) + self.assertEqual("Zone_1\n" + self.NAME + "\n", raw_output) + + def test_aggregate_add_and_remove_host(self): + opts = self.get_opts(["hosts", "name"]) + + raw_output = self.openstack('host list -f value -c "Host Name"') + host_name = raw_output.split()[0] + + self.openstack( + 'aggregate add host ' + self.NAME + ' ' + host_name) + raw_output = self.openstack('aggregate show ' + self.NAME + opts) + self.assertEqual("[u'" + host_name + "']" + "\n" + self.NAME + "\n", + raw_output) + + self.openstack( + 'aggregate remove host ' + self.NAME + ' ' + host_name) + raw_output = self.openstack('aggregate show ' + self.NAME + opts) + self.assertEqual("[]\n" + self.NAME + "\n", raw_output) From dc3b83590abb7f0c45e97fae2d096d635eb9132d Mon Sep 17 00:00:00 2001 From: "jiahui.qiang" Date: Tue, 17 Jan 2017 08:33:12 +0800 Subject: [PATCH 1497/3095] Functional test for volume snapshot Refactor functional tests in volume snapshot. Change-Id: I2fcc468096b3a26c83b8af1e379a62c80eb9c63e --- .../functional/volume/v2/test_snapshot.py | 239 +++++++++++++++--- 1 file changed, 198 insertions(+), 41 deletions(-) diff --git a/openstackclient/tests/functional/volume/v2/test_snapshot.py b/openstackclient/tests/functional/volume/v2/test_snapshot.py index 4eb69e9d7f..c83e79f877 100644 --- a/openstackclient/tests/functional/volume/v2/test_snapshot.py +++ b/openstackclient/tests/functional/volume/v2/test_snapshot.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import json import time import uuid @@ -20,9 +21,6 @@ class VolumeSnapshotTests(common.BaseVolumeTests): """Functional tests for volume snapshot. """ VOLLY = uuid.uuid4().hex - NAME = uuid.uuid4().hex - OTHER_NAME = uuid.uuid4().hex - HEADERS = ['"Name"'] @classmethod def wait_for_status(cls, command, status, tries): @@ -37,52 +35,211 @@ def wait_for_status(cls, command, status, tries): @classmethod def setUpClass(cls): super(VolumeSnapshotTests, cls).setUpClass() - cls.openstack('volume create --size 1 ' + cls.VOLLY) - cls.wait_for_status('volume show ' + cls.VOLLY, 'available\n', 3) - opts = cls.get_opts(['status']) - raw_output = cls.openstack('volume snapshot create --volume ' + - cls.VOLLY + ' ' + cls.NAME + opts) - cls.assertOutput('creating\n', raw_output) - cls.wait_for_status( - 'volume snapshot show ' + cls.NAME, 'available\n', 3) + # create a volume for all tests to create snapshot + cmd_output = json.loads(cls.openstack( + 'volume create -f json ' + + '--size 1 ' + + cls.VOLLY + )) + cls.wait_for_status('volume show ' + cls.VOLLY, 'available\n', 6) + cls.VOLUME_ID = cmd_output['id'] @classmethod def tearDownClass(cls): - # Rename test - raw_output = cls.openstack( - 'volume snapshot set --name ' + cls.OTHER_NAME + ' ' + cls.NAME) - cls.assertOutput('', raw_output) - # Delete test - raw_output_snapshot = cls.openstack( - 'volume snapshot delete ' + cls.OTHER_NAME) cls.wait_for_status('volume show ' + cls.VOLLY, 'available\n', 6) - raw_output_volume = cls.openstack('volume delete --force ' + cls.VOLLY) - cls.assertOutput('', raw_output_snapshot) - cls.assertOutput('', raw_output_volume) + raw_output = cls.openstack('volume delete --force ' + cls.VOLLY) + cls.assertOutput('', raw_output) - def test_snapshot_list(self): - opts = self.get_opts(self.HEADERS) - raw_output = self.openstack('volume snapshot list' + opts) - self.assertIn(self.NAME, raw_output) + def test_volume_snapshot__delete(self): + """Test create, delete multiple""" + name1 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume snapshot create -f json ' + + name1 + + ' --volume ' + self.VOLLY + )) + self.assertEqual( + name1, + cmd_output["name"], + ) - def test_snapshot_properties(self): - raw_output = self.openstack( - 'volume snapshot set --property a=b --property c=d ' + self.NAME) - self.assertEqual("", raw_output) - opts = self.get_opts(["properties"]) - raw_output = self.openstack('volume snapshot show ' + self.NAME + opts) - self.assertEqual("a='b', c='d'\n", raw_output) + name2 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume snapshot create -f json ' + + name2 + + ' --volume ' + self.VOLLY + )) + self.assertEqual( + name2, + cmd_output["name"], + ) + + self.wait_for_status( + 'volume snapshot show ' + name1, 'available\n', 6) + self.wait_for_status( + 'volume snapshot show ' + name2, 'available\n', 6) + + del_output = self.openstack( + 'volume snapshot delete ' + name1 + ' ' + name2) + self.assertOutput('', del_output) + + def test_volume_snapshot_list(self): + """Test create, list filter""" + name1 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume snapshot create -f json ' + + name1 + + ' --volume ' + self.VOLLY + )) + self.addCleanup(self.openstack, 'volume snapshot delete ' + name1) + self.assertEqual( + name1, + cmd_output["name"], + ) + self.assertEqual( + self.VOLUME_ID, + cmd_output["volume_id"], + ) + self.assertEqual( + 1, + cmd_output["size"], + ) + self.wait_for_status( + 'volume snapshot show ' + name1, 'available\n', 6) + name2 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume snapshot create -f json ' + + name2 + + ' --volume ' + self.VOLLY + )) + self.addCleanup(self.openstack, 'volume snapshot delete ' + name2) + self.assertEqual( + name2, + cmd_output["name"], + ) + self.assertEqual( + self.VOLUME_ID, + cmd_output["volume_id"], + ) + self.assertEqual( + 1, + cmd_output["size"], + ) + self.wait_for_status( + 'volume snapshot show ' + name2, 'available\n', 6) raw_output = self.openstack( - 'volume snapshot unset --property a ' + self.NAME) - self.assertEqual("", raw_output) - raw_output = self.openstack('volume snapshot show ' + self.NAME + opts) - self.assertEqual("c='d'\n", raw_output) + 'volume snapshot set ' + + '--state error ' + + name2 + ) + self.assertOutput('', raw_output) + + # Test list --long, --status + cmd_output = json.loads(self.openstack( + 'volume snapshot list -f json ' + + '--long ' + + '--status error' + )) + names = [x["Name"] for x in cmd_output] + self.assertNotIn(name1, names) + self.assertIn(name2, names) + + # Test list --volume + cmd_output = json.loads(self.openstack( + 'volume snapshot list -f json ' + + '--volume ' + self.VOLLY + )) + names = [x["Name"] for x in cmd_output] + self.assertIn(name1, names) + self.assertIn(name2, names) + + # Test list --name + cmd_output = json.loads(self.openstack( + 'volume snapshot list -f json ' + + '--name ' + name1 + )) + names = [x["Name"] for x in cmd_output] + self.assertIn(name1, names) + self.assertNotIn(name2, names) def test_snapshot_set(self): + """Test create, set, unset, show, delete volume snapshot""" + name = uuid.uuid4().hex + new_name = name + "_" + cmd_output = json.loads(self.openstack( + 'volume snapshot create -f json ' + + '--volume ' + self.VOLLY + + ' --description aaaa ' + + '--property Alpha=a ' + + name + )) + self.addCleanup(self.openstack, 'volume snapshot delete ' + new_name) + self.assertEqual( + name, + cmd_output["name"], + ) + self.assertEqual( + 1, + cmd_output["size"], + ) + self.assertEqual( + 'aaaa', + cmd_output["description"], + ) + self.assertEqual( + "Alpha='a'", + cmd_output["properties"], + ) + self.wait_for_status( + 'volume snapshot show ' + name, 'available\n', 6) + + # Test volume snapshot set raw_output = self.openstack( - 'volume snapshot set --description backup ' + self.NAME) - self.assertEqual("", raw_output) - opts = self.get_opts(["description", "name"]) - raw_output = self.openstack('volume snapshot show ' + self.NAME + opts) - self.assertEqual("backup\n" + self.NAME + "\n", raw_output) + 'volume snapshot set ' + + '--name ' + new_name + + ' --description bbbb ' + + '--property Alpha=c ' + + '--property Beta=b ' + + name, + ) + self.assertOutput('', raw_output) + + # Show snapshot set result + cmd_output = json.loads(self.openstack( + 'volume snapshot show -f json ' + + new_name + )) + self.assertEqual( + new_name, + cmd_output["name"], + ) + self.assertEqual( + 1, + cmd_output["size"], + ) + self.assertEqual( + 'bbbb', + cmd_output["description"], + ) + self.assertEqual( + "Alpha='c', Beta='b'", + cmd_output["properties"], + ) + + # Test volume unset + raw_output = self.openstack( + 'volume snapshot unset ' + + '--property Alpha ' + + new_name, + ) + self.assertOutput('', raw_output) + + cmd_output = json.loads(self.openstack( + 'volume snapshot show -f json ' + + new_name + )) + self.assertEqual( + "Beta='b'", + cmd_output["properties"], + ) From f353253122ca39aeb092656cea011a06e70103a4 Mon Sep 17 00:00:00 2001 From: "jiahui.qiang" Date: Tue, 17 Jan 2017 02:42:53 +0800 Subject: [PATCH 1498/3095] Error in the return of command 'volume qos create' This patch fixed a 'volume qos create' display mistake in argument of 'specs'[1]. For command such as: $ openstack volume qos create hello [1]https://bugs.launchpad.net/python-openstackclient/+bug/1656767 Closes-bug:#1656767 Change-Id: Ia9fce833d318d9b52b97c12cfb89e2d3c5465fbe --- .../tests/functional/volume/v2/test_qos.py | 4 +-- .../tests/unit/volume/v2/test_qos_specs.py | 28 ++++++++++--------- openstackclient/volume/v2/qos_specs.py | 11 ++++++-- .../notes/bug-1656767-36a3d4b9fac335c9.yaml | 5 ++++ 4 files changed, 31 insertions(+), 17 deletions(-) create mode 100644 releasenotes/notes/bug-1656767-36a3d4b9fac335c9.yaml diff --git a/openstackclient/tests/functional/volume/v2/test_qos.py b/openstackclient/tests/functional/volume/v2/test_qos.py index a54acbfd47..1558c216bf 100644 --- a/openstackclient/tests/functional/volume/v2/test_qos.py +++ b/openstackclient/tests/functional/volume/v2/test_qos.py @@ -50,13 +50,13 @@ def test_volume_qos_metadata(self): raw_output = self.openstack( 'volume qos set --property a=b --property c=d ' + self.ID) self.assertEqual("", raw_output) - opts = self.get_opts(['name', 'specs']) + opts = self.get_opts(['name', 'properties']) raw_output = self.openstack('volume qos show ' + self.ID + opts) self.assertEqual(self.NAME + "\na='b', c='d'\n", raw_output) raw_output = self.openstack( 'volume qos unset --property a ' + self.ID) self.assertEqual("", raw_output) - opts = self.get_opts(['name', 'specs']) + opts = self.get_opts(['name', 'properties']) raw_output = self.openstack('volume qos show ' + self.ID + opts) self.assertEqual(self.NAME + "\nc='d'\n", raw_output) diff --git a/openstackclient/tests/unit/volume/v2/test_qos_specs.py b/openstackclient/tests/unit/volume/v2/test_qos_specs.py index 7597e85280..35d9a34575 100644 --- a/openstackclient/tests/unit/volume/v2/test_qos_specs.py +++ b/openstackclient/tests/unit/volume/v2/test_qos_specs.py @@ -70,24 +70,26 @@ def test_qos_associate(self): class TestQosCreate(TestQos): - new_qos_spec = volume_fakes.FakeQos.create_one_qos() columns = ( 'consumer', 'id', 'name', - 'specs' - ) - data = ( - new_qos_spec.consumer, - new_qos_spec.id, - new_qos_spec.name, - new_qos_spec.specs + 'properties' ) def setUp(self): super(TestQosCreate, self).setUp() + self.new_qos_spec = volume_fakes.FakeQos.create_one_qos() self.qos_mock.create.return_value = self.new_qos_spec + + self.data = ( + self.new_qos_spec.consumer, + self.new_qos_spec.id, + self.new_qos_spec.name, + utils.format_dict(self.new_qos_spec.specs) + ) + # Get the command object to test self.cmd = qos_specs.CreateQos(self.app, None) @@ -147,11 +149,11 @@ def test_qos_create_with_properties(self): columns, data = self.cmd.take_action(parsed_args) - self.new_qos_spec.specs.update( - {'consumer': self.new_qos_spec.consumer}) self.qos_mock.create.assert_called_with( self.new_qos_spec.name, - self.new_qos_spec.specs + {'consumer': self.new_qos_spec.consumer, + 'foo': 'bar', + 'iops': '9001'} ) self.assertEqual(self.columns, columns) @@ -307,7 +309,7 @@ class TestQosList(TestQos): 'Name', 'Consumer', 'Associations', - 'Specs', + 'Properties', ) data = [] for q in qos_specs: @@ -383,7 +385,7 @@ class TestQosShow(TestQos): 'consumer', 'id', 'name', - 'specs' + 'properties' ) data = ( qos_association.name, diff --git a/openstackclient/volume/v2/qos_specs.py b/openstackclient/volume/v2/qos_specs.py index b7f49eca14..8e1d67b5c3 100644 --- a/openstackclient/volume/v2/qos_specs.py +++ b/openstackclient/volume/v2/qos_specs.py @@ -95,6 +95,9 @@ def take_action(self, parsed_args): qos_spec = volume_client.qos_specs.create(parsed_args.name, specs) + qos_spec._info.update( + {'properties': utils.format_dict(qos_spec._info.pop('specs'))} + ) return zip(*sorted(six.iteritems(qos_spec._info))) @@ -190,8 +193,11 @@ def take_action(self, parsed_args): for association in qos_associations] qos._info.update({'associations': associations}) + display_columns = ( + 'ID', 'Name', 'Consumer', 'Associations', 'Properties') + columns = ('ID', 'Name', 'Consumer', 'Associations', 'Specs') - return (columns, + return (display_columns, (utils.get_dict_properties( s._info, columns, formatters={ @@ -254,7 +260,8 @@ def take_action(self, parsed_args): qos_spec._info.update({ 'associations': utils.format_list(associations) }) - qos_spec._info.update({'specs': utils.format_dict(qos_spec.specs)}) + qos_spec._info.update( + {'properties': utils.format_dict(qos_spec._info.pop('specs'))}) return zip(*sorted(six.iteritems(qos_spec._info))) diff --git a/releasenotes/notes/bug-1656767-36a3d4b9fac335c9.yaml b/releasenotes/notes/bug-1656767-36a3d4b9fac335c9.yaml new file mode 100644 index 0000000000..0c5b270fb4 --- /dev/null +++ b/releasenotes/notes/bug-1656767-36a3d4b9fac335c9.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Fixed a ``volume qos create`` display mistake in argument of ``specs``. + [Bug `1656767 `_] From 89d9449dcfd4679e8d786c73df4be67af8e3b04a Mon Sep 17 00:00:00 2001 From: "jiahui.qiang" Date: Tue, 17 Jan 2017 17:42:15 +0800 Subject: [PATCH 1499/3095] Error in the return of command server show, create The raw output in the command 'openstack server create'(also in show) is used in display table directily. the item like os-extended-volumes:volumes_attached and security_groups needs to convert. the worry output: os-extended-volumes:volumes_attached | [{u'id': u'c3525de9-1cbf-4ac8-8b7a-ca295c46633b'}] security_groups | [{u'name': u'default'}] Change-Id: Id9db251c315f989e1dc5b1b6231ab328014213e3 --- openstackclient/compute/v2/server.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index d92218734f..798d3d3fcf 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -144,6 +144,20 @@ def _prep_server_detail(compute_client, server): except Exception: info['flavor'] = flavor_id + if 'os-extended-volumes:volumes_attached' in info: + info.update( + { + 'volumes_attached': utils.format_list_of_dicts( + info.pop('os-extended-volumes:volumes_attached')) + } + ) + if 'security_groups' in info: + info.update( + { + 'security_groups': utils.format_list_of_dicts( + info.pop('security_groups')) + } + ) # NOTE(dtroyer): novaclient splits these into separate entries... # Format addresses in a useful way info['addresses'] = _format_servers_list_networks(server.networks) From ab1f637f55275a92033838a98bd7ddbcb37beb5d Mon Sep 17 00:00:00 2001 From: Trevor McCasland Date: Tue, 17 Jan 2017 09:50:40 -0600 Subject: [PATCH 1500/3095] Add plugin adoption for trove With I308a6c6f3f5ce7dbb814ec0fd8ecb1734a2f137f merged in the python-troveclient project, trove can now say it has adopted the plugin. What commands that actually get implemented for this cycle, is to be determined. Another patch adding more commands to the docs will be proposed later when they get merged. Change-Id: If2c2545dd5d1510cc6eece698e34ad0f8c1b970f --- doc/source/commands.rst | 1 + doc/source/plugin-commands.rst | 6 ++++++ doc/source/plugins.rst | 2 +- test-requirements.txt | 1 + 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 78668a0f22..1f1ece4fba 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -181,6 +181,7 @@ list check out :doc:`plugin-commands`. * ``congress policy``: (**Policy (Congress)**) * ``congress policy rule``: (**Policy (Congress)**) * ``cron trigger``: (**Workflow Engine (Mistral)**) +* ``database flavor``: (**Database (Trove)**) * ``dataprocessing data source``: (**Data Processing (Sahara)**) * ``dataprocessing image``: (**Data Processing (Sahara)**) * ``dataprocessing image tags``: (**Data Processing (Sahara)**) diff --git a/doc/source/plugin-commands.rst b/doc/source/plugin-commands.rst index 7428b34d01..844c48a72a 100644 --- a/doc/source/plugin-commands.rst +++ b/doc/source/plugin-commands.rst @@ -97,6 +97,12 @@ senlin .. # list-plugins:: openstack.tripleoclient.v1 .. # :detailed: +trove +------ + +.. list-plugins:: openstack.database.v1 + :detailed: + .. watcher .. # watcherclient is not in global-requirements .. # list-plugins:: openstack.infra_optim.v1 diff --git a/doc/source/plugins.rst b/doc/source/plugins.rst index 0965c12f74..eafe5e8fa0 100644 --- a/doc/source/plugins.rst +++ b/doc/source/plugins.rst @@ -36,6 +36,7 @@ The following is a list of projects that are an OpenStackClient plugin. - python-searchlightclient - python-senlinclient - python-tripleoclient\*\* +- python-troveclient - python-watcherclient - python-zaqarclient @@ -45,7 +46,6 @@ The following is a list of projects that are an OpenStackClient plugin. The following is a list of projects that are not an OpenStackClient plugin. -- python-troveclient - python-magnumclient - python-ceilometerclient - python-solumclient diff --git a/test-requirements.txt b/test-requirements.txt index b53d08dd71..62e41b83c2 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -36,4 +36,5 @@ python-neutronclient>=5.1.0 # Apache-2.0 python-saharaclient>=0.18.0 # Apache-2.0 python-searchlightclient>=1.0.0 #Apache-2.0 python-senlinclient>=1.1.0 # Apache-2.0 +python-troveclient>=2.2.0 # Apache-2.0 python-zaqarclient>=1.0.0 # Apache-2.0 From 61a22a285ab204a866b375eec5274b567afc61ee Mon Sep 17 00:00:00 2001 From: YAMAMOTO Takashi Date: Wed, 18 Jan 2017 09:36:55 +0900 Subject: [PATCH 1501/3095] Remove the fixed set of choices for network_type So that it can specify out-of-tree ML2 type drivers. Closes-Bug: #1657311 Change-Id: I2445fb165b86cf5937d8aa09ba0fd5564eb1f8f4 --- openstackclient/network/v2/network.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 26cd02c6df..17ce0f7fb4 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -141,10 +141,8 @@ def _add_additional_network_options(parser): parser.add_argument( '--provider-network-type', metavar='', - choices=['flat', 'geneve', 'gre', 'local', - 'vlan', 'vxlan'], help=_("The physical mechanism by which the virtual network " - "is implemented. The supported options are: " + "is implemented. For example: " "flat, geneve, gre, local, vlan, vxlan.")) parser.add_argument( '--provider-physical-network', From 9fd145edbf149345edee35b43c7958d3821c9ff3 Mon Sep 17 00:00:00 2001 From: "jiahui.qiang" Date: Tue, 17 Jan 2017 02:58:06 +0800 Subject: [PATCH 1502/3095] Functional test for volume qos Refactor functional tests for testing more command options. Change-Id: I6d5a82eb0e84f72e8da76b17c952e4daae9dbc08 --- .../tests/functional/volume/v1/test_qos.py | 134 ++++++++++++----- .../tests/functional/volume/v2/test_qos.py | 141 +++++++++++++----- 2 files changed, 202 insertions(+), 73 deletions(-) diff --git a/openstackclient/tests/functional/volume/v1/test_qos.py b/openstackclient/tests/functional/volume/v1/test_qos.py index 770d5acbd8..9ca32b0a08 100644 --- a/openstackclient/tests/functional/volume/v1/test_qos.py +++ b/openstackclient/tests/functional/volume/v1/test_qos.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import json import uuid from openstackclient.tests.functional.volume.v1 import common @@ -18,38 +19,103 @@ class QosTests(common.BaseVolumeTests): """Functional tests for volume qos. """ - NAME = uuid.uuid4().hex - HEADERS = ['Name'] - FIELDS = ['id', 'name'] - ID = None - - @classmethod - def setUpClass(cls): - super(QosTests, cls).setUpClass() - opts = cls.get_opts(cls.FIELDS) - raw_output = cls.openstack('volume qos create ' + cls.NAME + opts) - cls.ID, name, rol = raw_output.split('\n') - cls.assertOutput(cls.NAME, name) - - @classmethod - def tearDownClass(cls): - raw_output = cls.openstack('volume qos delete ' + cls.ID) - cls.assertOutput('', raw_output) - - def test_volume_qos_list(self): - opts = self.get_opts(self.HEADERS) - raw_output = self.openstack('volume qos list' + opts) - self.assertIn(self.NAME, raw_output) - - def test_volume_qos_show(self): - opts = self.get_opts(self.FIELDS) - raw_output = self.openstack('volume qos show ' + self.ID + opts) - self.assertEqual(self.ID + "\n" + self.NAME + "\n", raw_output) - - def test_volume_qos_metadata(self): + def test_volume_qos_create_list(self): + """Test create, list, delete multiple""" + name1 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume qos create -f json ' + + name1 + )) + self.assertEqual( + name1, + cmd_output['name'] + ) + + name2 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume qos create -f json ' + + name2 + )) + self.assertEqual( + name2, + cmd_output['name'] + ) + + # Test list + cmd_output = json.loads(self.openstack( + 'volume qos list -f json' + )) + names = [x["Name"] for x in cmd_output] + self.assertIn(name1, names) + self.assertIn(name2, names) + + # Test delete multiple + del_output = self.openstack('volume qos delete ' + name1 + ' ' + name2) + self.assertOutput('', del_output) + + def test_volume_qos_set_show_unset(self): + """Tests create volume qos, set, unset, show, delete""" + + name = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume qos create -f json ' + + '--consumer front-end ' + '--property Alpha=a ' + + name + )) + self.addCleanup(self.openstack, 'volume qos delete ' + name) + self.assertEqual( + name, + cmd_output['name'] + ) + + self.assertEqual( + "front-end", + cmd_output['consumer'] + ) + + # Test volume qos set + raw_output = self.openstack( + 'volume qos set ' + + '--property Alpha=c ' + + '--property Beta=b ' + + name, + ) + self.assertOutput('', raw_output) + + # Test volume qos show + cmd_output = json.loads(self.openstack( + 'volume qos show -f json ' + + name + )) + self.assertEqual( + name, + cmd_output['name'] + ) + self.assertEqual( + "Alpha='c', Beta='b'", + cmd_output['specs'] + ) + + # Test volume qos unset raw_output = self.openstack( - 'volume qos set --property a=b --property c=d ' + self.ID) - self.assertEqual("", raw_output) - opts = self.get_opts(['name', 'specs']) - raw_output = self.openstack('volume qos show ' + self.ID + opts) - self.assertEqual(self.NAME + "\na='b', c='d'\n", raw_output) + 'volume qos unset ' + + '--property Alpha ' + + name, + ) + self.assertOutput('', raw_output) + + cmd_output = json.loads(self.openstack( + 'volume qos show -f json ' + + name + )) + self.assertEqual( + name, + cmd_output['name'] + ) + self.assertEqual( + "Beta='b'", + cmd_output['specs'] + ) + + # TODO(qiangjiahui): Add tests for associate and disassociate volume type diff --git a/openstackclient/tests/functional/volume/v2/test_qos.py b/openstackclient/tests/functional/volume/v2/test_qos.py index 1558c216bf..aee10dcaed 100644 --- a/openstackclient/tests/functional/volume/v2/test_qos.py +++ b/openstackclient/tests/functional/volume/v2/test_qos.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import json import uuid from openstackclient.tests.functional.volume.v2 import common @@ -18,45 +19,107 @@ class QosTests(common.BaseVolumeTests): """Functional tests for volume qos. """ - NAME = uuid.uuid4().hex - HEADERS = ['Name'] - FIELDS = ['id', 'name'] - ID = None - - @classmethod - def setUpClass(cls): - super(QosTests, cls).setUpClass() - opts = cls.get_opts(cls.FIELDS) - raw_output = cls.openstack('volume qos create ' + cls.NAME + opts) - cls.ID, name, rol = raw_output.split('\n') - cls.assertOutput(cls.NAME, name) - - @classmethod - def tearDownClass(cls): - raw_output = cls.openstack('volume qos delete ' + cls.ID) - cls.assertOutput('', raw_output) - - def test_volume_qos_list(self): - opts = self.get_opts(self.HEADERS) - raw_output = self.openstack('volume qos list' + opts) - self.assertIn(self.NAME, raw_output) - - def test_volume_qos_show(self): - opts = self.get_opts(self.FIELDS) - raw_output = self.openstack('volume qos show ' + self.ID + opts) - self.assertEqual(self.ID + "\n" + self.NAME + "\n", raw_output) - - def test_volume_qos_metadata(self): + def test_volume_qos_create_delete_list(self): + """Test create, list, delete multiple""" + name1 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume qos create -f json ' + + name1 + )) + self.assertEqual( + name1, + cmd_output['name'] + ) + + name2 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume qos create -f json ' + + name2 + )) + self.assertEqual( + name2, + cmd_output['name'] + ) + + # Test list + cmd_output = json.loads(self.openstack( + 'volume qos list -f json' + )) + names = [x["Name"] for x in cmd_output] + self.assertIn(name1, names) + self.assertIn(name2, names) + + # Test delete multiple + del_output = self.openstack('volume qos delete ' + name1 + ' ' + name2) + self.assertOutput('', del_output) + + def test_volume_qos_set_show_unset(self): + """Tests create volume qos, set, unset, show, delete""" + + name = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume qos create -f json ' + + '--consumer front-end ' + '--property Alpha=a ' + + name + )) + self.addCleanup(self.openstack, 'volume qos delete ' + name) + self.assertEqual( + name, + cmd_output['name'] + ) + + self.assertEqual( + "front-end", + cmd_output['consumer'] + ) + self.assertEqual( + "Alpha='a'", + cmd_output['properties'] + ) + + # Test volume qos set raw_output = self.openstack( - 'volume qos set --property a=b --property c=d ' + self.ID) - self.assertEqual("", raw_output) - opts = self.get_opts(['name', 'properties']) - raw_output = self.openstack('volume qos show ' + self.ID + opts) - self.assertEqual(self.NAME + "\na='b', c='d'\n", raw_output) + 'volume qos set ' + + '--property Alpha=c ' + + '--property Beta=b ' + + name, + ) + self.assertOutput('', raw_output) + + # Test volume qos show + cmd_output = json.loads(self.openstack( + 'volume qos show -f json ' + + name + )) + self.assertEqual( + name, + cmd_output['name'] + ) + self.assertEqual( + "Alpha='c', Beta='b'", + cmd_output['properties'] + ) + # Test volume qos unset raw_output = self.openstack( - 'volume qos unset --property a ' + self.ID) - self.assertEqual("", raw_output) - opts = self.get_opts(['name', 'properties']) - raw_output = self.openstack('volume qos show ' + self.ID + opts) - self.assertEqual(self.NAME + "\nc='d'\n", raw_output) + 'volume qos unset ' + + '--property Alpha ' + + name, + ) + self.assertOutput('', raw_output) + + cmd_output = json.loads(self.openstack( + 'volume qos show -f json ' + + name + )) + self.assertEqual( + name, + cmd_output['name'] + ) + self.assertEqual( + "Beta='b'", + cmd_output['properties'] + ) + + # TODO(qiangjiahui): Add tests for associate and disassociate volume type From b201a11b4231fd5cf29fa55f8380e8f8e5a317e5 Mon Sep 17 00:00:00 2001 From: "jiahui.qiang" Date: Thu, 19 Jan 2017 21:54:54 +0800 Subject: [PATCH 1503/3095] Functional test for subnet Refactor functional tests for testing more command options. Change-Id: I030652f6f3ab3dada12252599b20969f03a5df02 --- .../functional/network/v2/test_subnet.py | 268 +++++++++++++++--- 1 file changed, 231 insertions(+), 37 deletions(-) diff --git a/openstackclient/tests/functional/network/v2/test_subnet.py b/openstackclient/tests/functional/network/v2/test_subnet.py index 231671f31d..995a497983 100644 --- a/openstackclient/tests/functional/network/v2/test_subnet.py +++ b/openstackclient/tests/functional/network/v2/test_subnet.py @@ -10,6 +10,8 @@ # License for the specific language governing permissions and limitations # under the License. +import json +import random import uuid from openstackclient.tests.functional import base @@ -17,50 +19,242 @@ class SubnetTests(base.TestCase): """Functional tests for subnet. """ - NAME = uuid.uuid4().hex - NETWORK_NAME = uuid.uuid4().hex - HEADERS = ['Name'] - FIELDS = ['name'] @classmethod def setUpClass(cls): - # Create a network for the subnet. - cls.openstack('network create ' + cls.NETWORK_NAME) - opts = cls.get_opts(cls.FIELDS) - raw_output = cls.openstack( - 'subnet create --network ' + cls.NETWORK_NAME + - ' --subnet-range 10.10.10.0/24 ' + - cls.NAME + opts - ) - expected = cls.NAME + '\n' - cls.assertOutput(expected, raw_output) + # Create a network for the all subnet tests. + cls.NETWORK_NAME = uuid.uuid4().hex + cmd_output = json.loads(cls.openstack( + 'network create -f json ' + + cls.NETWORK_NAME + )) + # Get network_id for assertEqual + cls.NETWORK_ID = cmd_output["id"] @classmethod def tearDownClass(cls): - raw_output = cls.openstack('subnet delete ' + cls.NAME) - cls.assertOutput('', raw_output) raw_output = cls.openstack('network delete ' + cls.NETWORK_NAME) cls.assertOutput('', raw_output) + def test_subnet_create_and_delete(self): + """Test create, delete""" + name1 = uuid.uuid4().hex + cmd = ('subnet create -f json --network ' + + self.NETWORK_NAME + + ' --subnet-range') + cmd_output = self._subnet_create(cmd, name1) + self.assertEqual( + name1, + cmd_output["name"], + ) + self.assertEqual( + self.NETWORK_ID, + cmd_output["network_id"], + ) + + del_output = self.openstack( + 'subnet delete ' + name1) + self.assertOutput('', del_output) + def test_subnet_list(self): - opts = self.get_opts(self.HEADERS) - raw_output = self.openstack('subnet list' + opts) - self.assertIn(self.NAME, raw_output) - - def test_subnet_set(self): - self.openstack('subnet set --no-dhcp ' + self.NAME) - opts = self.get_opts(['name', 'enable_dhcp']) - raw_output = self.openstack('subnet show ' + self.NAME + opts) - self.assertEqual("False\n" + self.NAME + "\n", raw_output) - - def test_subnet_set_service_type(self): - TYPE = 'network:floatingip_agent_gateway' - self.openstack('subnet set --service-type ' + TYPE + ' ' + self.NAME) - opts = self.get_opts(['name', 'service_types']) - raw_output = self.openstack('subnet show ' + self.NAME + opts) - self.assertEqual(self.NAME + "\n" + TYPE + "\n", raw_output) - - def test_subnet_show(self): - opts = self.get_opts(self.FIELDS) - raw_output = self.openstack('subnet show ' + self.NAME + opts) - self.assertEqual(self.NAME + "\n", raw_output) + """Test create, list filter""" + name1 = uuid.uuid4().hex + name2 = uuid.uuid4().hex + cmd = ('subnet create -f json ' + + '--network ' + self.NETWORK_NAME + + ' --dhcp --subnet-range') + cmd_output = self._subnet_create(cmd, name1) + self.assertEqual( + name1, + cmd_output["name"], + ) + self.assertEqual( + True, + cmd_output["enable_dhcp"], + ) + self.assertEqual( + self.NETWORK_ID, + cmd_output["network_id"], + ) + self.assertEqual( + 4, + cmd_output["ip_version"], + ) + + cmd = ('subnet create -f json ' + + '--network ' + self.NETWORK_NAME + + ' --ip-version 6 --no-dhcp ' + + '--subnet-range') + cmd_output = self._subnet_create(cmd, name2, is_type_ipv4=False) + self.assertEqual( + name2, + cmd_output["name"], + ) + self.assertEqual( + False, + cmd_output["enable_dhcp"], + ) + self.assertEqual( + self.NETWORK_ID, + cmd_output["network_id"], + ) + self.assertEqual( + 6, + cmd_output["ip_version"], + ) + + # Test list --long + cmd_output = json.loads(self.openstack( + 'subnet list -f json ' + + '--long ' + )) + names = [x["Name"] for x in cmd_output] + self.assertIn(name1, names) + self.assertIn(name2, names) + + # Test list --name + cmd_output = json.loads(self.openstack( + 'subnet list -f json ' + + '--name ' + name1 + )) + names = [x["Name"] for x in cmd_output] + self.assertIn(name1, names) + self.assertNotIn(name2, names) + + # Test list --ip-version + cmd_output = json.loads(self.openstack( + 'subnet list -f json ' + + '--ip-version 6' + )) + names = [x["Name"] for x in cmd_output] + self.assertNotIn(name1, names) + self.assertIn(name2, names) + + # Test list --network + cmd_output = json.loads(self.openstack( + 'subnet list -f json ' + + '--network ' + self.NETWORK_ID + )) + names = [x["Name"] for x in cmd_output] + self.assertIn(name1, names) + self.assertIn(name2, names) + + # Test list --no-dhcp + cmd_output = json.loads(self.openstack( + 'subnet list -f json ' + + '--no-dhcp ' + )) + names = [x["Name"] for x in cmd_output] + self.assertNotIn(name1, names) + self.assertIn(name2, names) + + del_output = self.openstack( + 'subnet delete ' + name1 + ' ' + name2) + self.assertOutput('', del_output) + + def test_subnet_set_show_unset(self): + """Test create subnet, set, unset, show, delete""" + + name = uuid.uuid4().hex + new_name = name + "_" + cmd = ('subnet create -f json ' + + '--network ' + self.NETWORK_NAME + + ' --description aaaa --subnet-range') + cmd_output = self._subnet_create(cmd, name) + self.assertEqual( + name, + cmd_output["name"], + ) + self.assertEqual( + 'aaaa', + cmd_output["description"], + ) + + # Test set --no-dhcp --name --gateway --description + cmd_output = self.openstack( + 'subnet set ' + + '--name ' + new_name + + ' --description bbbb ' + + '--no-dhcp ' + + '--gateway 10.10.11.1 ' + + '--service-type network:floatingip_agent_gateway ' + + name + ) + self.assertOutput('', cmd_output) + + cmd_output = json.loads(self.openstack( + 'subnet show -f json ' + + new_name + )) + self.assertEqual( + new_name, + cmd_output["name"], + ) + self.assertEqual( + 'bbbb', + cmd_output["description"], + ) + self.assertEqual( + False, + cmd_output["enable_dhcp"], + ) + self.assertEqual( + '10.10.11.1', + cmd_output["gateway_ip"], + ) + self.assertEqual( + 'network:floatingip_agent_gateway', + cmd_output["service_types"], + ) + + # Test unset + cmd_output = self.openstack( + 'subnet unset ' + + '--service-type network:floatingip_agent_gateway ' + + new_name + ) + self.assertOutput('', cmd_output) + + cmd_output = json.loads(self.openstack( + 'subnet show -f json ' + + new_name + )) + self.assertEqual( + '', + cmd_output["service_types"], + ) + + del_output = self.openstack( + 'subnet delete ' + new_name) + self.assertOutput('', del_output) + + def _subnet_create(self, cmd, name, is_type_ipv4=True): + # Try random subnet range for subnet creating + # Because we can not determine ahead of time what subnets are already + # in use, possibly by another test running in parallel, try 4 times + for i in range(4): + # Make a random subnet + if is_type_ipv4: + subnet = ".".join(map( + str, + (random.randint(0, 223) for _ in range(3)) + )) + ".0/26" + else: + subnet = ":".join(map( + str, + (hex(random.randint(0, 65535))[2:] for _ in range(7)) + )) + ":0/112" + try: + cmd_output = json.loads(self.openstack( + cmd + ' ' + subnet + ' ' + + name + )) + except Exception: + if (i == 3): + # raise the exception at the last time + raise + pass + else: + # break and no longer retry if create sucessfully + break + return cmd_output From f96cff1a6dc40ea78a5d530b95626abf6f0e2467 Mon Sep 17 00:00:00 2001 From: "jiahui.qiang" Date: Wed, 18 Jan 2017 21:27:35 +0800 Subject: [PATCH 1504/3095] Functional test for router Refactor functional tests for testing more command options. Change-Id: I6200045c6228e245fc48a4d48d4b3796dede61b5 --- .../functional/network/v2/test_router.py | 235 +++++++++++++++--- 1 file changed, 206 insertions(+), 29 deletions(-) diff --git a/openstackclient/tests/functional/network/v2/test_router.py b/openstackclient/tests/functional/network/v2/test_router.py index 789c382548..443f68b20d 100644 --- a/openstackclient/tests/functional/network/v2/test_router.py +++ b/openstackclient/tests/functional/network/v2/test_router.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import json import uuid from openstackclient.tests.functional import base @@ -17,34 +18,210 @@ class RouterTests(base.TestCase): """Functional tests for router. """ - NAME = uuid.uuid4().hex - HEADERS = ['Name'] - FIELDS = ['name'] - - @classmethod - def setUpClass(cls): - opts = cls.get_opts(cls.FIELDS) - raw_output = cls.openstack('router create ' + cls.NAME + opts) - expected = cls.NAME + '\n' - cls.assertOutput(expected, raw_output) - - @classmethod - def tearDownClass(cls): - raw_output = cls.openstack('router delete ' + cls.NAME) - cls.assertOutput('', raw_output) + + def test_router_create_and_delete(self): + """Test create options, delete""" + name1 = uuid.uuid4().hex + name2 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'router create -f json ' + + name1 + )) + self.assertEqual( + name1, + cmd_output["name"], + ) + cmd_output = json.loads(self.openstack( + 'router create -f json ' + + name2 + )) + self.assertEqual( + name2, + cmd_output["name"], + ) + + del_output = self.openstack( + 'router delete ' + name1 + ' ' + name2) + self.assertOutput('', del_output) def test_router_list(self): - opts = self.get_opts(self.HEADERS) - raw_output = self.openstack('router list' + opts) - self.assertIn(self.NAME, raw_output) - - def test_router_set(self): - self.openstack('router set --disable ' + self.NAME) - opts = self.get_opts(['name', 'admin_state_up']) - raw_output = self.openstack('router show ' + self.NAME + opts) - self.assertEqual("DOWN\n" + self.NAME + "\n", raw_output) - - def test_router_show(self): - opts = self.get_opts(self.FIELDS) - raw_output = self.openstack('router show ' + self.NAME + opts) - self.assertEqual(self.NAME + "\n", raw_output) + """Test create, list filter""" + # Get project IDs + cmd_output = json.loads(self.openstack('token issue -f json ')) + auth_project_id = cmd_output['project_id'] + + cmd_output = json.loads(self.openstack('project list -f json ')) + admin_project_id = None + demo_project_id = None + for p in cmd_output: + if p['Name'] == 'admin': + admin_project_id = p['ID'] + if p['Name'] == 'demo': + demo_project_id = p['ID'] + + # Verify assumptions: + # * admin and demo projects are present + # * demo and admin are distinct projects + # * tests run as admin + self.assertIsNotNone(admin_project_id) + self.assertIsNotNone(demo_project_id) + self.assertNotEqual(admin_project_id, demo_project_id) + self.assertEqual(admin_project_id, auth_project_id) + + name1 = uuid.uuid4().hex + name2 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'router create -f json ' + + '--disable ' + + name1 + )) + self.assertEqual( + name1, + cmd_output["name"], + ) + self.assertEqual( + "DOWN", + cmd_output["admin_state_up"], + ) + self.assertEqual( + admin_project_id, + cmd_output["project_id"], + ) + cmd_output = json.loads(self.openstack( + 'router create -f json ' + + '--project ' + demo_project_id + + ' ' + name2 + )) + self.assertEqual( + name2, + cmd_output["name"], + ) + self.assertEqual( + "UP", + cmd_output["admin_state_up"], + ) + self.assertEqual( + demo_project_id, + cmd_output["project_id"], + ) + + # Test list --project + cmd_output = json.loads(self.openstack( + 'router list -f json ' + + '--project ' + demo_project_id + )) + names = [x["Name"] for x in cmd_output] + self.assertNotIn(name1, names) + self.assertIn(name2, names) + + # Test list --disable + cmd_output = json.loads(self.openstack( + 'router list -f json ' + + '--disable ' + )) + names = [x["Name"] for x in cmd_output] + self.assertIn(name1, names) + self.assertNotIn(name2, names) + + # Test list --name + cmd_output = json.loads(self.openstack( + 'router list -f json ' + + '--name ' + name1 + )) + names = [x["Name"] for x in cmd_output] + self.assertIn(name1, names) + self.assertNotIn(name2, names) + + # Test list --long + cmd_output = json.loads(self.openstack( + 'router list -f json ' + + '--long ' + )) + names = [x["Name"] for x in cmd_output] + self.assertIn(name1, names) + self.assertIn(name2, names) + + del_output = self.openstack( + 'router delete ' + name1 + ' ' + name2) + self.assertOutput('', del_output) + + def test_router_set_show_unset(self): + """Tests create router, set, unset, show, delete""" + + name = uuid.uuid4().hex + new_name = name + "_" + cmd_output = json.loads(self.openstack( + 'router create -f json ' + + '--description aaaa ' + + name + )) + self.assertEqual( + name, + cmd_output["name"], + ) + self.assertEqual( + 'aaaa', + cmd_output["description"], + ) + + # Test set --disable + cmd_output = self.openstack( + 'router set ' + + '--name ' + new_name + + ' --description bbbb ' + + '--disable ' + + name + ) + self.assertOutput('', cmd_output) + + cmd_output = json.loads(self.openstack( + 'router show -f json ' + + new_name + )) + self.assertEqual( + new_name, + cmd_output["name"], + ) + self.assertEqual( + 'bbbb', + cmd_output["description"], + ) + self.assertEqual( + 'DOWN', + cmd_output["admin_state_up"], + ) + + # Test set --ha --distributed + cmd_output = self.openstack( + 'router set ' + + '--distributed ' + + '--external-gateway public ' + + new_name + ) + self.assertOutput('', cmd_output) + + cmd_output = json.loads(self.openstack( + 'router show -f json ' + + new_name + )) + self.assertEqual( + True, + cmd_output["distributed"], + ) + self.assertIsNotNone(cmd_output["external_gateway_info"]) + + # Test unset + cmd_output = self.openstack( + 'router unset ' + + '--external-gateway ' + + new_name + ) + cmd_output = json.loads(self.openstack( + 'router show -f json ' + + new_name + )) + self.assertIsNone(cmd_output["external_gateway_info"]) + + del_output = self.openstack( + 'router delete ' + new_name) + self.assertOutput('', del_output) From 4a8e7dbe6b19e589e277e13d565578d98161572f Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Tue, 17 Jan 2017 22:33:08 +0800 Subject: [PATCH 1505/3095] Fix functional test for creating subnet subnet create failed by some bad random subnet range, so retry it with new random range when the test failed. Change-Id: If528ff419b51dd5c5232f81d4b26abae542bd820 --- .../functional/network/v2/test_floating_ip.py | 39 ++++++++++++------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/openstackclient/tests/functional/network/v2/test_floating_ip.py b/openstackclient/tests/functional/network/v2/test_floating_ip.py index fa9607a074..8fbec3d5f7 100644 --- a/openstackclient/tests/functional/network/v2/test_floating_ip.py +++ b/openstackclient/tests/functional/network/v2/test_floating_ip.py @@ -31,25 +31,38 @@ def setUpClass(cls): cls.re_description = re.compile("description\s+\|\s+([^|]+?)\s+\|") cls.re_network_id = re.compile("floating_network_id\s+\|\s+(\S+)") - # Make a random subnet - cls.subnet = ".".join(map( - str, - (random.randint(0, 223) for _ in range(3)) - )) + ".0/26" - # Create a network for the floating ip raw_output = cls.openstack( 'network create --external ' + cls.NETWORK_NAME ) cls.network_id = re.search(cls.re_id, raw_output).group(1) - # Create a subnet for the network - raw_output = cls.openstack( - 'subnet create ' + - '--network ' + cls.NETWORK_NAME + ' ' + - '--subnet-range ' + cls.subnet + ' ' + - cls.SUBNET_NAME - ) + # Try random subnet range for subnet creating + # Because we can not determine ahead of time what subnets are already + # in use, possibly by another test running in parallel, try 4 times + for i in range(4): + # Make a random subnet + cls.subnet = ".".join(map( + str, + (random.randint(0, 223) for _ in range(3)) + )) + ".0/26" + try: + # Create a subnet for the network + raw_output = cls.openstack( + 'subnet create ' + + '--network ' + cls.NETWORK_NAME + ' ' + + '--subnet-range ' + cls.subnet + ' ' + + cls.SUBNET_NAME + ) + except Exception: + if (i == 3): + # raise the exception at the last time + raise + pass + else: + # break and no longer retry if create sucessfully + break + cls.subnet_id = re.search(cls.re_id, raw_output).group(1) @classmethod From 4f4af3d67df0ca67bb1bd59857dbbd27f059a22a Mon Sep 17 00:00:00 2001 From: "jiahui.qiang" Date: Sat, 21 Jan 2017 18:22:26 +0800 Subject: [PATCH 1506/3095] Switch to use cleanup method in functional test Switch the delete command in subnet and router functional tests. Because addCleanup will also work when an assertion fails ,except the test is for testing delete command. Change-Id: I33634f5148c4895c7cf4d4773a9c33c8368850c7 --- .../functional/network/v2/test_router.py | 17 ++++------ .../functional/network/v2/test_subnet.py | 33 ++++++++++++------- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/openstackclient/tests/functional/network/v2/test_router.py b/openstackclient/tests/functional/network/v2/test_router.py index 443f68b20d..aa708e0a6f 100644 --- a/openstackclient/tests/functional/network/v2/test_router.py +++ b/openstackclient/tests/functional/network/v2/test_router.py @@ -20,7 +20,7 @@ class RouterTests(base.TestCase): """Functional tests for router. """ def test_router_create_and_delete(self): - """Test create options, delete""" + """Test create options, delete multiple""" name1 = uuid.uuid4().hex name2 = uuid.uuid4().hex cmd_output = json.loads(self.openstack( @@ -75,6 +75,8 @@ def test_router_list(self): '--disable ' + name1 )) + + self.addCleanup(self.openstack, 'router delete ' + name1) self.assertEqual( name1, cmd_output["name"], @@ -92,6 +94,8 @@ def test_router_list(self): '--project ' + demo_project_id + ' ' + name2 )) + + self.addCleanup(self.openstack, 'router delete ' + name2) self.assertEqual( name2, cmd_output["name"], @@ -141,12 +145,8 @@ def test_router_list(self): self.assertIn(name1, names) self.assertIn(name2, names) - del_output = self.openstack( - 'router delete ' + name1 + ' ' + name2) - self.assertOutput('', del_output) - def test_router_set_show_unset(self): - """Tests create router, set, unset, show, delete""" + """Tests create router, set, unset, show""" name = uuid.uuid4().hex new_name = name + "_" @@ -155,6 +155,7 @@ def test_router_set_show_unset(self): '--description aaaa ' + name )) + self.addCleanup(self.openstack, 'router delete ' + new_name) self.assertEqual( name, cmd_output["name"], @@ -221,7 +222,3 @@ def test_router_set_show_unset(self): new_name )) self.assertIsNone(cmd_output["external_gateway_info"]) - - del_output = self.openstack( - 'router delete ' + new_name) - self.assertOutput('', del_output) diff --git a/openstackclient/tests/functional/network/v2/test_subnet.py b/openstackclient/tests/functional/network/v2/test_subnet.py index 995a497983..61cffcde4d 100644 --- a/openstackclient/tests/functional/network/v2/test_subnet.py +++ b/openstackclient/tests/functional/network/v2/test_subnet.py @@ -37,7 +37,7 @@ def tearDownClass(cls): cls.assertOutput('', raw_output) def test_subnet_create_and_delete(self): - """Test create, delete""" + """Test create, delete multiple""" name1 = uuid.uuid4().hex cmd = ('subnet create -f json --network ' + self.NETWORK_NAME + @@ -51,9 +51,22 @@ def test_subnet_create_and_delete(self): self.NETWORK_ID, cmd_output["network_id"], ) + name2 = uuid.uuid4().hex + cmd = ('subnet create -f json --network ' + + self.NETWORK_NAME + + ' --subnet-range') + cmd_output = self._subnet_create(cmd, name2) + self.assertEqual( + name2, + cmd_output["name"], + ) + self.assertEqual( + self.NETWORK_ID, + cmd_output["network_id"], + ) del_output = self.openstack( - 'subnet delete ' + name1) + 'subnet delete ' + name1 + ' ' + name2) self.assertOutput('', del_output) def test_subnet_list(self): @@ -64,6 +77,8 @@ def test_subnet_list(self): '--network ' + self.NETWORK_NAME + ' --dhcp --subnet-range') cmd_output = self._subnet_create(cmd, name1) + + self.addCleanup(self.openstack, 'subnet delete ' + name1) self.assertEqual( name1, cmd_output["name"], @@ -86,6 +101,8 @@ def test_subnet_list(self): ' --ip-version 6 --no-dhcp ' + '--subnet-range') cmd_output = self._subnet_create(cmd, name2, is_type_ipv4=False) + + self.addCleanup(self.openstack, 'subnet delete ' + name2) self.assertEqual( name2, cmd_output["name"], @@ -148,12 +165,8 @@ def test_subnet_list(self): self.assertNotIn(name1, names) self.assertIn(name2, names) - del_output = self.openstack( - 'subnet delete ' + name1 + ' ' + name2) - self.assertOutput('', del_output) - def test_subnet_set_show_unset(self): - """Test create subnet, set, unset, show, delete""" + """Test create subnet, set, unset, show""" name = uuid.uuid4().hex new_name = name + "_" @@ -161,6 +174,8 @@ def test_subnet_set_show_unset(self): '--network ' + self.NETWORK_NAME + ' --description aaaa --subnet-range') cmd_output = self._subnet_create(cmd, name) + + self.addCleanup(self.openstack, 'subnet delete ' + new_name) self.assertEqual( name, cmd_output["name"], @@ -224,10 +239,6 @@ def test_subnet_set_show_unset(self): cmd_output["service_types"], ) - del_output = self.openstack( - 'subnet delete ' + new_name) - self.assertOutput('', del_output) - def _subnet_create(self, cmd, name, is_type_ipv4=True): # Try random subnet range for subnet creating # Because we can not determine ahead of time what subnets are already From fd0ac815de9a8ca528434029d4e41c99e3b5984e Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Fri, 20 Jan 2017 17:48:11 +0800 Subject: [PATCH 1507/3095] Functional tests debug support Pass OS_* options into tox debug venv in order to debug functional tests in local, and install ipdb to make code-debug more convenient. Change-Id: Ib926948f2b9a52921cf7487fe16ef716481e3ceb --- tox.ini | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 130b32fe83..9d18cb4e73 100644 --- a/tox.ini +++ b/tox.ini @@ -73,7 +73,10 @@ commands = coverage report [testenv:debug] -commands = oslo_debug_helper -t openstackclient/tests {posargs} +passenv = OS_* +commands = + pip install -q -U ipdb + oslo_debug_helper -t openstackclient/tests {posargs} [testenv:docs] commands = python setup.py build_sphinx From 4d9da2c40ae02086258cfde852b297754d8085fa Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Tue, 10 Jan 2017 15:13:47 +0800 Subject: [PATCH 1508/3095] Fix OSC networking commands help errors OSC networking commands need to authenticate to get service catalog, then decide to show nova-network or neutron command help message. Fake token and fake auth_type in prepare_to_run_command() casue os-cloud-config use AdminToken auth plugin, but pass all the auth information (include: username, password and so on) to it, that casue the class initialization error. Pop the fake token and url, then try to load auth plugin again to fix the issue. Change-Id: I8b140f0b0a60681fc2a35a013bb0c84ff8cb9589 Closes-Bug: #1650026 --- openstackclient/common/clientmanager.py | 27 +++++++++++++++++-- .../tests/functional/common/test_help.py | 7 +++++ .../notes/bug-1650026-0ce6a77e69d24424.yaml | 6 +++++ 3 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/bug-1650026-0ce6a77e69d24424.yaml diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index 23c35a3b28..3e1a50e3e6 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -59,6 +59,8 @@ def __init__( self._interface = self.interface self._cacert = self.cacert self._insecure = not self.verify + # store original auth_type + self._original_auth_type = cli_options.auth_type def setup_auth(self): """Set up authentication""" @@ -73,12 +75,33 @@ def setup_auth(self): if self._cli_options._openstack_config is not None: self._cli_options._openstack_config._pw_callback = \ shell.prompt_for_password + try: + self._cli_options._auth = \ + self._cli_options._openstack_config.load_auth_plugin( + self._cli_options.config, + ) + except TypeError as e: + self._fallback_load_auth_plugin(e) + + return super(ClientManager, self).setup_auth() + + def _fallback_load_auth_plugin(self, e): + # NOTES(RuiChen): Hack to avoid auth plugins choking on data they don't + # expect, delete fake token and endpoint, then try to + # load auth plugin again with user specified options. + # We know it looks ugly, but it's necessary. + if self._cli_options.config['auth']['token'] == 'x': + # restore original auth_type + self._cli_options.config['auth_type'] = \ + self._original_auth_type + del self._cli_options.config['auth']['token'] + del self._cli_options.config['auth']['endpoint'] self._cli_options._auth = \ self._cli_options._openstack_config.load_auth_plugin( self._cli_options.config, ) - - return super(ClientManager, self).setup_auth() + else: + raise e def is_network_endpoint_enabled(self): """Check if the network endpoint is enabled""" diff --git a/openstackclient/tests/functional/common/test_help.py b/openstackclient/tests/functional/common/test_help.py index bbc521970e..211c52b1de 100644 --- a/openstackclient/tests/functional/common/test_help.py +++ b/openstackclient/tests/functional/common/test_help.py @@ -64,3 +64,10 @@ def test_server_only_help(self): raw_output = self.openstack('help server') for command in [row[0] for row in self.SERVER_COMMANDS]: self.assertIn(command, raw_output) + + def test_networking_commands_help(self): + """Check networking related commands in help message.""" + raw_output = self.openstack('help network list') + self.assertIn('List networks', raw_output) + raw_output = self.openstack('network create --help') + self.assertIn('Create new network', raw_output) diff --git a/releasenotes/notes/bug-1650026-0ce6a77e69d24424.yaml b/releasenotes/notes/bug-1650026-0ce6a77e69d24424.yaml new file mode 100644 index 0000000000..bb92c873a5 --- /dev/null +++ b/releasenotes/notes/bug-1650026-0ce6a77e69d24424.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Fixed a ``__init__() got an unexpected keyword argument 'project_name'`` + error in various networking commands when ``help`` or ``--help`` was used. + [Bug `1650026 `_] From 274687d85279b95140e35104eba593309ab7980a Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Tue, 17 Jan 2017 17:09:57 +0800 Subject: [PATCH 1509/3095] Update devref about "--no-property" Update the example about "--no-property" and "--property" to make help message order more reasonable, that help make users aware of the processing order, and update the help details when both "--no-property" and "--property" appear in the same command. Change-Id: I998cdaf2f8c881dce219581ff328a639e8e358ee Implements: blueprint allow-overwrite-set-options --- doc/source/command-options.rst | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/doc/source/command-options.rst b/doc/source/command-options.rst index c850b000cf..faa82bee21 100644 --- a/doc/source/command-options.rst +++ b/doc/source/command-options.rst @@ -153,6 +153,15 @@ An example parser declaration for `set` action: .. code-block:: python + parser.add_argument( + '--no-example-property', + dest='no_example_property', + action='store_true', + help=_('Remove all example properties for this ' + '(specify both --no-example-property and --example-property' + ' to remove the current properties before setting' + ' new properties.)'), + ) parser.add_argument( '--example-property', metavar='', @@ -161,26 +170,21 @@ An example parser declaration for `set` action: help=_('Example property for this ' '(repeat option to set multiple properties)'), ) - parser.add_argument( - '--no-example-property', - dest='no_example_property', - action='store_true', - help=_('Remove all example properties for this ' - '(specify both --example-property and --no-example-property' - ' to overwrite the current example properties)'), - ) + +Please make `--no-example-property` be shown in front of `--example-property` +in the help, like above, that help make users aware of the processing order. An example handler in `take_action()` for `set` action: .. code-block:: python - if parsed_args.example_property and parsed_args.no_example_property: + if parsed_args.no_example_property and parsed_args.example_property: kwargs['example_property'] = parsed_args.example_property + elif parsed_args.no_example_property: + kwargs['example_property'] = [] elif parsed_args.example_property: kwargs['example_property'] = \ resource_example_property + parsed_args.example_property - elif parsed_args.no_example_property: - kwargs['example_property'] = [] An example parser declaration for `unset` action: From 5caac0e5634fd65e2f635b724f00461eaebd58bf Mon Sep 17 00:00:00 2001 From: Ankur Gupta Date: Tue, 27 Sep 2016 17:00:43 -0500 Subject: [PATCH 1510/3095] Add meter rule to OSC Implement network feature meter label rules into OpenStack Client. Allows for creation of rules to meter network traffic. Partially Implements: blueprint neutron-client-metering Change-Id: If18c078d7e80c122583417669f820f02c84d6237 --- .../command-objects/network-meter-rule.rst | 103 ++++++ doc/source/commands.rst | 1 + .../network/v2/network_meter_rule.py | 203 +++++++++++ .../network/v2/test_network_meter_rule.py | 108 ++++++ .../tests/unit/network/v2/fakes.py | 46 +++ .../network/v2/test_network_meter_rule.py | 321 ++++++++++++++++++ setup.cfg | 15 +- 7 files changed, 792 insertions(+), 5 deletions(-) create mode 100644 doc/source/command-objects/network-meter-rule.rst create mode 100644 openstackclient/network/v2/network_meter_rule.py create mode 100644 openstackclient/tests/functional/network/v2/test_network_meter_rule.py create mode 100644 openstackclient/tests/unit/network/v2/test_network_meter_rule.py diff --git a/doc/source/command-objects/network-meter-rule.rst b/doc/source/command-objects/network-meter-rule.rst new file mode 100644 index 0000000000..83f8fd4fb4 --- /dev/null +++ b/doc/source/command-objects/network-meter-rule.rst @@ -0,0 +1,103 @@ +================== +network meter rule +================== + +A **meter rule** sets the rule for +a meter to measure traffic for a specific IP range. +The following uses **meter** and requires the L3 +metering extension. + +Network v2 + +network meter rule create +------------------------- + +Create meter rule + +.. program:: network meter rule create +.. code:: bash + + openstack network meter rule create + [--project [--project-domain ]] + [--ingress | --egress] + [--exclude | --include] + --remote-ip-prefix + + +.. option:: --project + + Owner's project (name or ID) + + *Network version 2 only* + +.. option:: --project-domain + + Domain the project belongs to (name of ID). + This can be used in case collisions between project names exist. + +.. option:: --ingress + + Rule is applied to incoming traffic (default) + +.. option:: --egress + + Rule is applied to outgoing traffic + +.. option:: --exclude + + Exclude remote_ip_prefix from count of the traffic of IP addresses + +.. option:: --include + + Include remote_ip_prefix into count of the traffic of IP addresses + (default) + +.. option:: --remote-ip-prefix + + The remote IP prefix to associate with this metering rule packet + +.. _network_meter_rule_create: +.. describe:: + + Meter to associate with this meter rule (name or ID) + + +network meter rule delete +------------------------- + +Delete meter rule(s) + +.. program:: network meter rule delete +.. code:: bash + + openstack network meter rule delete [ ...] + +.. _network_meter_rule_delete: +.. describe:: + + ID of meter rule(s) to delete + +network meter rule list +----------------------- + +List meter rules + +.. program:: network meter rule list +.. code:: bash + + openstack network meter rule list + +network meter rule show +----------------------- + +Show meter rule + +.. program:: network meter rule show +.. code:: bash + + openstack network meter rule show + +.. _network_meter_show: +.. describe:: + + Meter rule to display (ID only) diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 1f1ece4fba..ece0b6e29a 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -112,6 +112,7 @@ referring to both Compute and Volume quotas. * ``network``: (**Compute**, **Network**) - a virtual network for connecting servers and other resources * ``network agent``: (**Network**) - A network agent is an agent that handles various tasks used to implement virtual networks * ``network meter``: (**Network**) - allow traffic metering in a network +* ``network meter rule``: (**Network**) - rules for network traffic metering * ``network rbac``: (**Network**) - an RBAC policy for network resources * ``network qos rule``: (**Network**) - a QoS rule for network resources * ``network qos policy``: (**Network**) - a QoS policy for network resources diff --git a/openstackclient/network/v2/network_meter_rule.py b/openstackclient/network/v2/network_meter_rule.py new file mode 100644 index 0000000000..49ff9e1b0e --- /dev/null +++ b/openstackclient/network/v2/network_meter_rule.py @@ -0,0 +1,203 @@ +# 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. +# + +"""Meter Rule Implementations""" + +import logging + +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils + +from openstackclient.i18n import _ +from openstackclient.identity import common as identity_common +from openstackclient.network import sdk_utils + +LOG = logging.getLogger(__name__) + + +def _get_columns(item): + column_map = { + 'tenant_id': 'project_id', + } + return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + + +def _get_attrs(client_manager, parsed_args): + attrs = {} + + if parsed_args.exclude: + attrs['excluded'] = True + if parsed_args.include: + attrs['excluded'] = False + if parsed_args.ingress or not parsed_args.egress: + attrs['direction'] = 'ingress' + if parsed_args.egress: + attrs['direction'] = 'egress' + if parsed_args.remote_ip_prefix is not None: + attrs['remote_ip_prefix'] = parsed_args.remote_ip_prefix + if parsed_args.meter is not None: + attrs['metering_label_id'] = parsed_args.meter + if parsed_args.project is not None: + identity_client = client_manager.identity + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + attrs['tenant_id'] = project_id + + return attrs + + +class CreateMeterRule(command.ShowOne): + _description = _("Create a new meter rule") + + def get_parser(self, prog_name): + parser = super(CreateMeterRule, self).get_parser(prog_name) + + parser.add_argument( + '--project', + metavar='', + help=_("Owner's project (name or ID)") + ) + identity_common.add_project_domain_option_to_parser(parser) + exclude_group = parser.add_mutually_exclusive_group() + exclude_group.add_argument( + '--exclude', + action='store_true', + help=_("Exclude remote IP prefix from traffic count") + ) + exclude_group.add_argument( + '--include', + action='store_true', + help=_("Include remote IP prefix from traffic count (default)") + ) + direction_group = parser.add_mutually_exclusive_group() + direction_group.add_argument( + '--ingress', + action='store_true', + help=_("Apply rule to incoming network traffic (default)") + ) + direction_group.add_argument( + '--egress', + action='store_true', + help=_('Apply rule to outgoing network traffic') + ) + parser.add_argument( + '--remote-ip-prefix', + metavar='', + required=True, + help=_('The remote IP prefix to associate with this rule'), + ) + parser.add_argument( + 'meter', + metavar='', + help=_('Label to associate with this metering rule (name or ID)'), + ) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + _meter = client.find_metering_label(parsed_args.meter, + ignore_missing=False) + parsed_args.meter = _meter.id + attrs = _get_attrs(self.app.client_manager, parsed_args) + obj = client.create_metering_label_rule(**attrs) + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns, formatters={}) + + return (display_columns, data) + + +class DeleteMeterRule(command.Command): + _description = _("Delete meter rule(s)") + + def get_parser(self, prog_name): + parser = super(DeleteMeterRule, self).get_parser(prog_name) + + parser.add_argument( + 'meter_rule_id', + metavar='', + nargs='+', + help=_('Meter rule to delete (ID only)') + ) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + result = 0 + + for id in parsed_args.meter_rule_id: + try: + obj = client.find_metering_label_rule(id, ignore_missing=False) + client.delete_metering_label_rule(obj) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete meter rule with " + "ID '%(id)s': %(e)s"), + {"id": id, "e": e}) + + if result > 0: + total = len(parsed_args.meter_rule_id) + msg = (_("%(result)s of %(total)s meter rules failed " + "to delete.") % {"result": result, "total": total}) + raise exceptions.CommandError(msg) + + +class ListMeterRule(command.Lister): + _description = _("List meter rules") + + def take_action(self, parsed_args): + client = self.app.client_manager.network + + columns = ( + 'id', + 'excluded', + 'direction', + 'remote_ip_prefix', + ) + column_headers = ( + 'ID', + 'Excluded', + 'Direction', + 'Remote IP Prefix', + ) + data = client.metering_label_rules() + return (column_headers, + (utils.get_item_properties( + s, columns, + ) for s in data)) + + +class ShowMeterRule(command.ShowOne): + _description = _("Display meter rules details") + + def get_parser(self, prog_name): + parser = super(ShowMeterRule, self).get_parser(prog_name) + parser.add_argument( + 'meter_rule_id', + metavar='', + help=_('Meter rule (ID only)') + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.find_metering_label_rule(parsed_args.meter_rule_id, + ignore_missing=False) + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns) + return display_columns, data diff --git a/openstackclient/tests/functional/network/v2/test_network_meter_rule.py b/openstackclient/tests/functional/network/v2/test_network_meter_rule.py new file mode 100644 index 0000000000..4f079e3ce2 --- /dev/null +++ b/openstackclient/tests/functional/network/v2/test_network_meter_rule.py @@ -0,0 +1,108 @@ +# 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 re +import uuid + +from openstackclient.tests.functional import base + + +class TestMeterRule(base.TestCase): + """Functional tests for meter rule""" + METER_NAME = uuid.uuid4().hex + METER_ID = None + METER_RULE_ID = None + + @classmethod + def setUpClass(cls): + # Set up some regex for matching below + cls.re_id = re.compile("id\s+\|\s+(\S+)") + cls.re_direction = re.compile("direction\s+\|\s+(\S+)") + cls.re_ip_prefix = re.compile( + "remote_ip_prefix\s+\|\s+([^|]+?)\s+\|" + ) + cls.re_meter_id = re.compile("metering_label_id\s+\|\s+(\S+)") + + raw_output = cls.openstack( + 'network meter create ' + cls.METER_NAME + ) + + cls.METER_ID = re.search(cls.re_id, raw_output).group(1) + + @classmethod + def tearDownClass(cls): + raw_output = cls.openstack('network meter delete ' + cls.METER_ID) + cls.assertOutput('', raw_output) + + def test_meter_rule_delete(self): + """test create, delete""" + + raw_output = self.openstack( + 'network meter rule create ' + + '--remote-ip-prefix 10.0.0.0/8 ' + + self.METER_ID + ) + rule_id = re.search(self.re_id, raw_output).group(1) + re_ip = re.search(self.re_ip_prefix, raw_output) + + self.addCleanup(self.openstack, + 'network meter rule delete ' + rule_id) + self.assertIsNotNone(re_ip) + self.assertIsNotNone(rule_id) + + def test_meter_rule_list(self): + """Test create, list, delete""" + raw_output = self.openstack( + 'network meter rule create ' + + '--remote-ip-prefix 10.0.0.0/8 ' + + self.METER_ID + ) + rule_id = re.search(self.re_id, raw_output).group(1) + self.addCleanup(self.openstack, + 'network meter rule delete ' + rule_id) + self.assertEqual( + '10.0.0.0/8', + re.search(self.re_ip_prefix, raw_output).group(1) + ) + + raw_output = self.openstack('network meter rule list') + self.assertIsNotNone(re.search(rule_id + "|\s+\|\s+\|\s+10.0.0.0/8", + raw_output)) + + def test_meter_rule_show(self): + """Test create, show, delete""" + raw_output = self.openstack( + 'network meter rule create ' + + '--remote-ip-prefix 10.0.0.0/8 ' + + '--egress ' + + self.METER_ID + ) + rule_id = re.search(self.re_id, raw_output).group(1) + + self.assertEqual( + 'egress', + re.search(self.re_direction, raw_output).group(1) + ) + + raw_output = self.openstack('network meter rule show ' + rule_id) + + self.assertEqual( + '10.0.0.0/8', + re.search(self.re_ip_prefix, raw_output).group(1) + ) + self.assertIsNotNone(rule_id) + + self.addCleanup(self.openstack, + 'network meter rule delete ' + rule_id) diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index 524285ab18..eb96533934 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -1322,6 +1322,52 @@ def get_meter(meter=None, count=2): return mock.Mock(side_effect=meter) +class FakeNetworkMeterRule(object): + """Fake metering rule""" + + @staticmethod + def create_one_rule(attrs=None): + """Create one meter rule""" + attrs = attrs or {} + + meter_rule_attrs = { + 'id': 'meter-label-rule-id-' + uuid.uuid4().hex, + 'direction': 'ingress', + 'excluded': False, + 'metering_label_id': 'meter-label-id-' + uuid.uuid4().hex, + 'remote_ip_prefix': '10.0.0.0/24', + 'tenant_id': 'project-id-' + uuid.uuid4().hex, + } + + meter_rule_attrs.update(attrs) + + meter_rule = fakes.FakeResource( + info=copy.deepcopy(meter_rule_attrs), + loaded=True) + + meter_rule.project_id = meter_rule_attrs['tenant_id'] + + return meter_rule + + @staticmethod + def create_meter_rule(attrs=None, count=2): + """Create multiple meter rules""" + + meter_rules = [] + for i in range(0, count): + meter_rules.append(FakeNetworkMeterRule. + create_one_rule(attrs)) + return meter_rules + + @staticmethod + def get_meter_rule(meter_rule=None, count=2): + """Get a list of meter rules""" + if meter_rule is None: + meter_rule = (FakeNetworkMeterRule. + create_meter_rule(count)) + return mock.Mock(side_effect=meter_rule) + + class FakeSubnetPool(object): """Fake one or more subnet pools.""" diff --git a/openstackclient/tests/unit/network/v2/test_network_meter_rule.py b/openstackclient/tests/unit/network/v2/test_network_meter_rule.py new file mode 100644 index 0000000000..af481793d7 --- /dev/null +++ b/openstackclient/tests/unit/network/v2/test_network_meter_rule.py @@ -0,0 +1,321 @@ +# 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 mock import call + +from osc_lib import exceptions + +from openstackclient.network.v2 import network_meter_rule +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3 +from openstackclient.tests.unit.network.v2 import fakes as network_fakes +from openstackclient.tests.unit import utils as tests_utils + + +class TestMeterRule(network_fakes.TestNetworkV2): + def setUp(self): + super(TestMeterRule, self).setUp() + self.network = self.app.client_manager.network + self.projects_mock = self.app.client_manager.identity.projects + self.domains_mock = self.app.client_manager.identity.domains + + +class TestCreateMeterRule(TestMeterRule): + project = identity_fakes_v3.FakeProject.create_one_project() + domain = identity_fakes_v3.FakeDomain.create_one_domain() + + new_rule = ( + network_fakes.FakeNetworkMeterRule. + create_one_rule() + ) + + columns = ( + 'direction', + 'excluded', + 'id', + 'metering_label_id', + 'project_id', + 'remote_ip_prefix', + ) + data = ( + new_rule.direction, + new_rule.excluded, + new_rule.id, + new_rule.metering_label_id, + new_rule.project_id, + new_rule.remote_ip_prefix, + ) + + def setUp(self): + super(TestCreateMeterRule, self).setUp() + fake_meter = network_fakes.FakeNetworkMeter.create_one_meter({ + 'id': self.new_rule.metering_label_id}) + + self.network.create_metering_label_rule = mock.Mock( + return_value=self.new_rule) + self.projects_mock.get.return_value = self.project + self.cmd = network_meter_rule.CreateMeterRule(self.app, + self.namespace) + self.network.find_metering_label = mock.Mock( + return_value=fake_meter) + + def test_create_no_options(self): + arglist = [] + verifylist = [] + + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_create_default_options(self): + arglist = [ + self.new_rule.metering_label_id, + "--remote-ip-prefix", self.new_rule.remote_ip_prefix, + ] + verifylist = [ + ('meter', self.new_rule.metering_label_id), + ('remote_ip_prefix', self.new_rule.remote_ip_prefix), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_metering_label_rule.assert_called_once_with( + **{'direction': 'ingress', + 'metering_label_id': self.new_rule.metering_label_id, + 'remote_ip_prefix': self.new_rule.remote_ip_prefix, } + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_all_options(self): + arglist = [ + "--ingress", + "--include", + self.new_rule.metering_label_id, + "--remote-ip-prefix", self.new_rule.remote_ip_prefix, + ] + verifylist = [ + ('ingress', True), + ('include', True), + ('meter', self.new_rule.metering_label_id), + ('remote_ip_prefix', self.new_rule.remote_ip_prefix), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_metering_label_rule.assert_called_once_with( + **{'direction': self.new_rule.direction, + 'excluded': self.new_rule.excluded, + 'metering_label_id': self.new_rule.metering_label_id, + 'remote_ip_prefix': self.new_rule.remote_ip_prefix, } + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestDeleteMeterRule(TestMeterRule): + def setUp(self): + super(TestDeleteMeterRule, self).setUp() + self.rule_list = \ + network_fakes.FakeNetworkMeterRule.create_meter_rule( + count=2 + ) + self.network.delete_metering_label_rule = mock.Mock(return_value=None) + + self.network.find_metering_label_rule = network_fakes \ + .FakeNetworkMeterRule.get_meter_rule( + meter_rule=self.rule_list + ) + + self.cmd = network_meter_rule.DeleteMeterRule(self.app, + self.namespace) + + def test_delete_one_rule(self): + arglist = [ + self.rule_list[0].id, + ] + verifylist = [ + ('meter_rule_id', [self.rule_list[0].id]), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.network.delete_metering_label_rule.assert_called_once_with( + self.rule_list[0] + ) + self.assertIsNone(result) + + def test_delete_multiple_rules(self): + arglist = [] + for rule in self.rule_list: + arglist.append(rule.id) + verifylist = [ + ('meter_rule_id', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [] + for rule in self.rule_list: + calls.append(call(rule)) + self.network.delete_metering_label_rule.assert_has_calls(calls) + self.assertIsNone(result) + + def test_delete_multiple_rules_exception(self): + arglist = [ + self.rule_list[0].id, + 'xxxx-yyyy-zzzz', + self.rule_list[1].id, + ] + verifylist = [ + ('meter_rule_id', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + return_find = [ + self.rule_list[0], + exceptions.NotFound('404'), + self.rule_list[1], + ] + self.network.find_metering_label_rule = mock.Mock( + side_effect=return_find + ) + + ret_delete = [ + None, + exceptions.NotFound('404'), + ] + self.network.delete_metering_label_rule = mock.Mock( + side_effect=ret_delete + ) + + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + + calls = [ + call(self.rule_list[0]), + call(self.rule_list[1]), + ] + self.network.delete_metering_label_rule.assert_has_calls(calls) + + +class TestListMeterRule(TestMeterRule): + rule_list = \ + network_fakes.FakeNetworkMeterRule.create_meter_rule( + count=2 + ) + + columns = ( + 'ID', + 'Excluded', + 'Direction', + 'Remote IP Prefix', + ) + + data = [] + + for rule in rule_list: + data.append(( + rule.id, + rule.excluded, + rule.direction, + rule.remote_ip_prefix, + )) + + def setUp(self): + super(TestListMeterRule, self).setUp() + + self.network.metering_label_rules = mock.Mock( + return_value=self.rule_list + ) + + self.cmd = network_meter_rule.ListMeterRule(self.app, + self.namespace) + + def test_rule_list(self): + arglist = [] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.metering_label_rules.assert_called_with() + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + +class TestShowMeterRule(TestMeterRule): + new_rule = ( + network_fakes.FakeNetworkMeterRule. + create_one_rule() + ) + + columns = ( + 'direction', + 'excluded', + 'id', + 'metering_label_id', + 'project_id', + 'remote_ip_prefix', + ) + + data = ( + new_rule.direction, + new_rule.excluded, + new_rule.id, + new_rule.metering_label_id, + new_rule.project_id, + new_rule.remote_ip_prefix, + ) + + def setUp(self): + super(TestShowMeterRule, self).setUp() + + self.cmd = network_meter_rule.ShowMeterRule(self.app, + self.namespace) + + self.network.find_metering_label_rule = \ + mock.Mock(return_value=self.new_rule) + + def test_show_no_options(self): + arglist = [] + verifylist = [] + + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_label_rule_show_option(self): + arglist = [ + self.new_rule.id, + ] + verifylist = [ + ('meter_rule_id', self.new_rule.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.find_metering_label_rule.assert_called_with( + self.new_rule.id, ignore_missing=False + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) diff --git a/setup.cfg b/setup.cfg index 7aa3f1985a..dbe921aa4a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -355,11 +355,6 @@ openstack.network.v2 = ip_floating_pool_list = openstackclient.network.v2.floating_ip_pool:ListIPFloatingPool - network_meter_create = openstackclient.network.v2.meter:CreateMeter - network_meter_delete = openstackclient.network.v2.meter:DeleteMeter - network_meter_list = openstackclient.network.v2.meter:ListMeter - network_meter_show = openstackclient.network.v2.meter:ShowMeter - network_agent_delete = openstackclient.network.v2.network_agent:DeleteNetworkAgent network_agent_list = openstackclient.network.v2.network_agent:ListNetworkAgent network_agent_set = openstackclient.network.v2.network_agent:SetNetworkAgent @@ -371,6 +366,16 @@ openstack.network.v2 = network_set = openstackclient.network.v2.network:SetNetwork network_show = openstackclient.network.v2.network:ShowNetwork + network_meter_create = openstackclient.network.v2.meter:CreateMeter + network_meter_delete = openstackclient.network.v2.meter:DeleteMeter + network_meter_list = openstackclient.network.v2.meter:ListMeter + network_meter_show = openstackclient.network.v2.meter:ShowMeter + + network_meter_rule_create = openstackclient.network.v2.network_meter_rule:CreateMeterRule + network_meter_rule_delete = openstackclient.network.v2.network_meter_rule:DeleteMeterRule + network_meter_rule_list = openstackclient.network.v2.network_meter_rule:ListMeterRule + network_meter_rule_show = openstackclient.network.v2.network_meter_rule:ShowMeterRule + network_qos_policy_create = openstackclient.network.v2.network_qos_policy:CreateNetworkQosPolicy network_qos_policy_delete = openstackclient.network.v2.network_qos_policy:DeleteNetworkQosPolicy network_qos_policy_list = openstackclient.network.v2.network_qos_policy:ListNetworkQosPolicy From ab88573ebbbdfdb8b351a9aee4670a9a31b478b2 Mon Sep 17 00:00:00 2001 From: Abhishek Raut Date: Thu, 5 Jan 2017 09:12:07 -0800 Subject: [PATCH 1511/3095] SDK refactor: Prepare security group commands Prepare the OSC "security group" commands for the SDK refactor. Change-Id: If9918fad2474f9b4d68424f2806f0de61fd58b2e Partially-Implements: blueprint network-command-sdk-support --- openstackclient/network/v2/security_group.py | 33 +++++++------------ .../unit/network/v2/test_security_group.py | 4 +-- 2 files changed, 14 insertions(+), 23 deletions(-) diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py index df9e320b10..c6d9ede7f8 100644 --- a/openstackclient/network/v2/security_group.py +++ b/openstackclient/network/v2/security_group.py @@ -21,6 +21,7 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common +from openstackclient.network import sdk_utils from openstackclient.network import utils as network_utils @@ -34,6 +35,7 @@ def _format_network_security_group_rules(sg_rules): sg_rule.pop(key) sg_rule.pop('security_group_id', None) sg_rule.pop('tenant_id', None) + sg_rule.pop('project_id', None) return utils.format_list_of_dicts(sg_rules) @@ -72,29 +74,15 @@ def _format_compute_security_group_rules(sg_rules): def _get_columns(item): - # Build the display columns and a list of the property columns - # that need to be mapped (display column name, property name). - columns = list(item.to_dict().keys()) - property_column_mappings = [] - if 'security_group_rules' in columns: - columns.append('rules') - columns.remove('security_group_rules') - property_column_mappings.append(('rules', 'security_group_rules')) - if 'tenant_id' in columns: - columns.remove('tenant_id') - if 'project_id' not in columns: - columns.append('project_id') - property_column_mappings.append(('project_id', 'tenant_id')) - display_columns = sorted(columns) - - # Build the property columns and apply any column mappings. - property_columns = sorted(columns) - for property_column_mapping in property_column_mappings: - property_index = property_columns.index(property_column_mapping[0]) - property_columns[property_index] = property_column_mapping[1] - return tuple(display_columns), property_columns + column_map = { + 'security_group_rules': 'rules', + 'tenant_id': 'project_id', + } + return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) +# TODO(abhiraut): Use the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. class CreateSecurityGroup(common.NetworkAndComputeShowOne): _description = _("Create a new security group") @@ -190,6 +178,8 @@ def take_action_compute(self, client, parsed_args): client.security_groups.delete(data.id) +# TODO(rauta): Use the SDK resource mapped attribute names once +# the OSC minimum requirements include SDK 1.0. class ListSecurityGroup(common.NetworkAndComputeLister): _description = _("List security groups") @@ -245,6 +235,7 @@ def take_action_network(self, client, parsed_args): parsed_args.project_domain, ).id filters['tenant_id'] = project_id + filters['project_id'] = project_id return self._get_return_data(client.security_groups(**filters)) def take_action_compute(self, client, parsed_args): diff --git a/openstackclient/tests/unit/network/v2/test_security_group.py b/openstackclient/tests/unit/network/v2/test_security_group.py index 43aa07ccbe..9a30267ebd 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group.py +++ b/openstackclient/tests/unit/network/v2/test_security_group.py @@ -456,7 +456,7 @@ def test_security_group_list_project(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - filters = {'tenant_id': project.id} + filters = {'tenant_id': project.id, 'project_id': project.id} self.network.security_groups.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) @@ -476,7 +476,7 @@ def test_security_group_list_project_domain(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - filters = {'tenant_id': project.id} + filters = {'tenant_id': project.id, 'project_id': project.id} self.network.security_groups.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) From 5cf77bb672eeb28327cac8bc0a8227c8b7137819 Mon Sep 17 00:00:00 2001 From: Hongbin Lu Date: Mon, 23 Jan 2017 00:04:02 -0600 Subject: [PATCH 1512/3095] Handle 403 error on creating trust Currently, creating trust requires permission to list roles, but non-admin users don't have permission to do that by default. This commit adds exception handling on listing roles, and continue to create trust if server returns 403. Closes-Bug: #1658582 Change-Id: I4f016b76cb46ae07ef65ed54780881bbcd6210d3 --- openstackclient/identity/v3/trust.py | 12 ++++++++---- releasenotes/notes/bug-1658582-80a76f6b0af0ca12.yaml | 6 ++++++ 2 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/bug-1658582-80a76f6b0af0ca12.yaml diff --git a/openstackclient/identity/v3/trust.py b/openstackclient/identity/v3/trust.py index 04ee4dce5a..52daeb4d16 100644 --- a/openstackclient/identity/v3/trust.py +++ b/openstackclient/identity/v3/trust.py @@ -16,6 +16,7 @@ import datetime import logging +from keystoneclient import exceptions as identity_exc from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -105,10 +106,13 @@ def take_action(self, parsed_args): role_names = [] for role in parsed_args.role: - role_name = utils.find_resource( - identity_client.roles, - role, - ).name + try: + role_name = utils.find_resource( + identity_client.roles, + role, + ).name + except identity_exc.Forbidden: + role_name = role role_names.append(role_name) expires_at = None diff --git a/releasenotes/notes/bug-1658582-80a76f6b0af0ca12.yaml b/releasenotes/notes/bug-1658582-80a76f6b0af0ca12.yaml new file mode 100644 index 0000000000..ee8b25c5c8 --- /dev/null +++ b/releasenotes/notes/bug-1658582-80a76f6b0af0ca12.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Correctly handle non-admin in ``create trust`` command when looking + up role names. + [Bug `1658582 `_] From 80c62021c829ceb878b8d51cedeca6fd6c688dd0 Mon Sep 17 00:00:00 2001 From: Brian Rosmaita Date: Fri, 20 Jan 2017 23:15:12 +0000 Subject: [PATCH 1513/3095] Update container format choices The choices for valid container formats is out of date in the image v1 and v2 help text. This patch adds 'ova' and 'docker' and does some minor refactoring to align the container_format option with the disk_format option. Change-Id: Icdea5fab801abd651baf45cf96c920c7df79f11b Closes-bug: #1658138 --- doc/source/command-objects/image.rst | 6 ++++-- openstackclient/image/v1/image.py | 16 ++++++++++------ openstackclient/image/v2/image.py | 14 ++++++++++---- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/doc/source/command-objects/image.rst b/doc/source/command-objects/image.rst index 999842af21..7a9ead5c61 100644 --- a/doc/source/command-objects/image.rst +++ b/doc/source/command-objects/image.rst @@ -76,7 +76,8 @@ Create/upload an image .. option:: --container-format - Image container format (default: bare) + Image container format. The supported options are: ami, ari, aki, + bare, docker, ova, ovf. The default format is: bare .. option:: --disk-format @@ -342,7 +343,8 @@ Set image properties .. option:: --container-format - Image container format (default: bare) + Image container format. The supported options are: ami, ari, aki, + bare, docker, ova, ovf. .. option:: --disk-format diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index 1f239b6701..f4944afa6f 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -36,6 +36,7 @@ from openstackclient.i18n import _ +CONTAINER_CHOICES = ["ami", "ari", "aki", "bare", "docker", "ova", "ovf"] DEFAULT_CONTAINER_FORMAT = 'bare' DEFAULT_DISK_FORMAT = 'raw' DISK_CHOICES = ["ami", "ari", "aki", "vhd", "vmdk", "raw", "qcow2", "vhdx", @@ -84,8 +85,12 @@ def get_parser(self, prog_name): "--container-format", default=DEFAULT_CONTAINER_FORMAT, metavar="", - help=_("Image container format " - "(default: %s)") % DEFAULT_CONTAINER_FORMAT, + choices=CONTAINER_CHOICES, + help=(_("Image container format. " + "The supported options are: %(option_list)s. " + "The default format is: %(default_opt)s") % + {'option_list': ', '.join(CONTAINER_CHOICES), + 'default_opt': DEFAULT_CONTAINER_FORMAT}) ) parser.add_argument( "--disk-format", @@ -498,13 +503,12 @@ def get_parser(self, prog_name): type=int, help=_("Minimum RAM size needed to boot image, in megabytes"), ) - container_choices = ["ami", "ari", "aki", "bare", "ovf"] parser.add_argument( "--container-format", metavar="", - help=_("Container format of image. Acceptable formats: %s") % - container_choices, - choices=container_choices + choices=CONTAINER_CHOICES, + help=_("Image container format. The supported options are: %s") % + ', '.join(CONTAINER_CHOICES) ) parser.add_argument( "--disk-format", diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 418cc3973f..29499ec2c8 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -30,6 +30,7 @@ from openstackclient.identity import common +CONTAINER_CHOICES = ["ami", "ari", "aki", "bare", "docker", "ova", "ovf"] DEFAULT_CONTAINER_FORMAT = 'bare' DEFAULT_DISK_FORMAT = 'raw' DISK_CHOICES = ["ami", "ari", "aki", "vhd", "vmdk", "raw", "qcow2", "vhdx", @@ -135,9 +136,13 @@ def get_parser(self, prog_name): parser.add_argument( "--container-format", default=DEFAULT_CONTAINER_FORMAT, + choices=CONTAINER_CHOICES, metavar="", - help=_("Image container format " - "(default: %s)") % DEFAULT_CONTAINER_FORMAT, + help=(_("Image container format. " + "The supported options are: %(option_list)s. " + "The default format is: %(default_opt)s") % + {'option_list': ', '.join(CONTAINER_CHOICES), + 'default_opt': DEFAULT_CONTAINER_FORMAT}) ) parser.add_argument( "--disk-format", @@ -659,8 +664,9 @@ def get_parser(self, prog_name): parser.add_argument( "--container-format", metavar="", - help=_("Image container format " - "(default: %s)") % DEFAULT_CONTAINER_FORMAT, + choices=CONTAINER_CHOICES, + help=_("Image container format. The supported options are: %s") % + ', '.join(CONTAINER_CHOICES) ) parser.add_argument( "--disk-format", From 98f803e0f0e622e7770cfab52e99168f6a68f427 Mon Sep 17 00:00:00 2001 From: Artom Lifshitz Date: Tue, 24 Jan 2017 15:02:38 +0000 Subject: [PATCH 1514/3095] Use image client for images instead of compute With the deprecation of the Nova proxy APIs in microversion 2.36 [1], any operation that uses a microversion higher than 2.36 and works with images will fail because the /images endpoint will return 404. This patch updates openstackclient to query images using the image client in places where previously the compute client was used. [1] http://docs.openstack.org/developer/nova/api_microversion_history.html#id33 Change-Id: Ia66e44e530799ce6531922dcf6a84e38528c8725 Closes-bug: 1630161 --- openstackclient/compute/v2/server.py | 20 +++++++++------- .../tests/unit/compute/v2/test_server.py | 23 ++++++++----------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 798d3d3fcf..a1330e0195 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -113,7 +113,7 @@ def _get_ip_address(addresses, address_type, ip_address_family): ) -def _prep_server_detail(compute_client, server): +def _prep_server_detail(compute_client, image_client, server): """Prepare the detailed server dict for printing :param compute_client: a compute client instance @@ -130,7 +130,7 @@ def _prep_server_detail(compute_client, server): if image_info: image_id = image_info.get('id', '') try: - image = utils.find_resource(compute_client.images, image_id) + image = utils.find_resource(image_client.images, image_id) info['image'] = "%s (%s)" % (image.name, image_id) except Exception: info['image'] = image_id @@ -450,12 +450,13 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): compute_client = self.app.client_manager.compute volume_client = self.app.client_manager.volume + image_client = self.app.client_manager.image # Lookup parsed_args.image image = None if parsed_args.image: image = utils.find_resource( - compute_client.images, + image_client.images, parsed_args.image, ) @@ -629,7 +630,7 @@ def take_action(self, parsed_args): sys.stdout.write(_('Error creating server\n')) raise SystemExit - details = _prep_server_detail(compute_client, server) + details = _prep_server_detail(compute_client, image_client, server) return zip(*sorted(six.iteritems(details))) @@ -797,6 +798,7 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): compute_client = self.app.client_manager.compute identity_client = self.app.client_manager.identity + image_client = self.app.client_manager.image project_id = None if parsed_args.project: @@ -826,7 +828,7 @@ def take_action(self, parsed_args): # image name is given, map it to ID. image_id = None if parsed_args.image: - image_id = utils.find_resource(compute_client.images, + image_id = utils.find_resource(image_client.images, parsed_args.image).id search_opts = { @@ -1164,13 +1166,14 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): compute_client = self.app.client_manager.compute + image_client = self.app.client_manager.image server = utils.find_resource( compute_client.servers, parsed_args.server) # If parsed_args.image is not set, default to the currently used one. image_id = parsed_args.image or server._info.get('image', {}).get('id') - image = utils.find_resource(compute_client.images, image_id) + image = utils.find_resource(image_client.images, image_id) server = server.rebuild(image, parsed_args.password) if parsed_args.wait: @@ -1186,7 +1189,7 @@ def take_action(self, parsed_args): sys.stdout.write(_('Error rebuilding server\n')) raise SystemExit - details = _prep_server_detail(compute_client, server) + details = _prep_server_detail(compute_client, image_client, server) return zip(*sorted(six.iteritems(details))) @@ -1561,7 +1564,8 @@ def take_action(self, parsed_args): sys.stderr.write(_("Error retrieving diagnostics data\n")) return ({}, {}) else: - data = _prep_server_detail(compute_client, server) + data = _prep_server_detail(compute_client, + self.app.client_manager.image, server) return zip(*sorted(six.iteritems(data))) diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 5722118989..54f36209c0 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -36,10 +36,6 @@ def setUp(self): self.servers_mock = self.app.client_manager.compute.servers self.servers_mock.reset_mock() - # Get a shortcut to the compute client ImageManager Mock - self.cimages_mock = self.app.client_manager.compute.images - self.cimages_mock.reset_mock() - # Get a shortcut to the compute client FlavorManager Mock self.flavors_mock = self.app.client_manager.compute.flavors self.flavors_mock.reset_mock() @@ -259,7 +255,7 @@ def setUp(self): self.servers_mock.create.return_value = self.new_server self.image = image_fakes.FakeImage.create_one_image() - self.cimages_mock.get.return_value = self.image + self.images_mock.get.return_value = self.image self.flavor = compute_fakes.FakeFlavor.create_one_flavor() self.flavors_mock.get.return_value = self.flavor @@ -859,7 +855,7 @@ def setUp(self): self.servers_mock.list.return_value = self.servers self.image = image_fakes.FakeImage.create_one_image() - self.cimages_mock.get.return_value = self.image + self.images_mock.get.return_value = self.image self.flavor = compute_fakes.FakeFlavor.create_one_flavor() self.flavors_mock.get.return_value = self.flavor @@ -943,7 +939,7 @@ def test_server_list_with_image(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.cimages_mock.get.assert_any_call(self.image.id) + self.images_mock.get.assert_any_call(self.image.id) self.search_opts['image'] = self.image.id self.servers_mock.list.assert_called_with(**self.kwargs) @@ -1019,7 +1015,7 @@ def setUp(self): # Return value for utils.find_resource for image self.image = image_fakes.FakeImage.create_one_image() - self.cimages_mock.get.return_value = self.image + self.images_mock.get.return_value = self.image # Fake the rebuilt new server. new_server = compute_fakes.FakeServer.create_one_server() @@ -1059,7 +1055,7 @@ def test_rebuild_with_current_image(self): self.cmd.take_action(parsed_args) self.servers_mock.get.assert_called_with(self.server.id) - self.cimages_mock.get.assert_called_with(self.image.id) + self.images_mock.get.assert_called_with(self.image.id) self.server.rebuild.assert_called_with(self.image, None) def test_rebuild_with_current_image_and_password(self): @@ -1078,7 +1074,7 @@ def test_rebuild_with_current_image_and_password(self): self.cmd.take_action(parsed_args) self.servers_mock.get.assert_called_with(self.server.id) - self.cimages_mock.get.assert_called_with(self.image.id) + self.images_mock.get.assert_called_with(self.image.id) self.server.rebuild.assert_called_with(self.image, password) @mock.patch.object(common_utils, 'wait_for_status', return_value=True) @@ -1106,7 +1102,7 @@ def test_rebuild_with_wait_ok(self, mock_wait_for_status): ) self.servers_mock.get.assert_called_with(self.server.id) - self.cimages_mock.get.assert_called_with(self.image.id) + self.images_mock.get.assert_called_with(self.image.id) self.server.rebuild.assert_called_with(self.image, None) @mock.patch.object(common_utils, 'wait_for_status', return_value=False) @@ -1130,7 +1126,7 @@ def test_rebuild_with_wait_fails(self, mock_wait_for_status): ) self.servers_mock.get.assert_called_with(self.server.id) - self.cimages_mock.get.assert_called_with(self.image.id) + self.images_mock.get.assert_called_with(self.image.id) self.server.rebuild.assert_called_with(self.image, None) @@ -1628,7 +1624,7 @@ def setUp(self): # This is the return value for utils.find_resource() self.servers_mock.get.return_value = self.server - self.cimages_mock.get.return_value = self.image + self.images_mock.get.return_value = self.image self.flavors_mock.get.return_value = self.flavor # Get the command object to test @@ -1986,6 +1982,7 @@ def test_prep_server_detail(self, find_resource): # Call _prep_server_detail(). server_detail = server._prep_server_detail( self.app.client_manager.compute, + self.app.client_manager.image, _server ) # 'networks' is used to create _server. Remove it. From b69b539a422860bfb402093ff9d93a1b6e338b26 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 25 Jan 2017 00:55:54 +0000 Subject: [PATCH 1515/3095] Updated from global requirements Change-Id: I7b0e46de18f10cde110e1957c12210bc3271e7f9 --- requirements.txt | 4 ++-- test-requirements.txt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index e5ca3c7ba4..c537100674 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,12 +6,12 @@ six>=1.9.0 # MIT Babel>=2.3.4 # BSD cliff>=2.3.0 # Apache-2.0 -keystoneauth1>=2.17.0 # Apache-2.0 +keystoneauth1>=2.18.0 # Apache-2.0 openstacksdk!=0.9.11,!=0.9.12,>=0.9.10 # Apache-2.0 osc-lib>=1.2.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 oslo.utils>=3.18.0 # Apache-2.0 python-glanceclient>=2.5.0 # Apache-2.0 python-keystoneclient>=3.8.0 # Apache-2.0 -python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0 +python-novaclient!=7.0.0,>=6.0.0 # Apache-2.0 python-cinderclient!=1.7.0,!=1.7.1,>=1.6.0 # Apache-2.0 diff --git a/test-requirements.txt b/test-requirements.txt index 62e41b83c2..032d191a36 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -28,12 +28,12 @@ python-barbicanclient>=4.0.0 # Apache-2.0 python-congressclient<2000,>=1.3.0 # Apache-2.0 python-designateclient>=1.5.0 # Apache-2.0 python-heatclient>=1.6.1 # Apache-2.0 -python-ironicclient>=1.9.0 # Apache-2.0 +python-ironicclient>=1.10.0 # Apache-2.0 python-ironic-inspector-client>=1.5.0 # Apache-2.0 python-mistralclient>=2.0.0 # Apache-2.0 python-muranoclient>=0.8.2 # Apache-2.0 python-neutronclient>=5.1.0 # Apache-2.0 -python-saharaclient>=0.18.0 # Apache-2.0 +python-saharaclient>=1.1.0 # Apache-2.0 python-searchlightclient>=1.0.0 #Apache-2.0 python-senlinclient>=1.1.0 # Apache-2.0 python-troveclient>=2.2.0 # Apache-2.0 From 9ada3b529d60c174b7daa8db2a2fe5ebe619cced Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Tue, 24 Jan 2017 17:41:00 -0600 Subject: [PATCH 1516/3095] Add server test for image and flavor lookups Review Ia66e44e530799ce6531922dcf6a84e38528c8725 changes OSC's server commands to use the image client rather than compute clirnt (yay!) but we never really tested any of this in functional tests. This review adds a simple functional test (in the new style) to watch the client change; it passes locally for me without the client change, due to timing we went ahead and merged that first. Change-Id: I5529f412578c50090e70d17aa0129217bf803fed --- .../functional/compute/v2/test_server.py | 64 ++++++++++++++----- 1 file changed, 49 insertions(+), 15 deletions(-) diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py index 6eedf408a6..ffdfa6c812 100644 --- a/openstackclient/tests/functional/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import json import time from tempest.lib.common.utils import data_utils @@ -59,12 +60,10 @@ def server_create(self, name=None): """Create server. Add cleanup.""" name = name or data_utils.rand_uuid() opts = self.get_opts(self.FIELDS) - flavor = self.get_flavor() - image = self.get_image() - network = self.get_network() raw_output = self.openstack('--debug server create --flavor ' + - flavor + - ' --image ' + image + network + ' ' + + self.flavor_name + + ' --image ' + self.image_name + + self.network_arg + ' ' + name + opts) if not raw_output: self.fail('Server has not been created!') @@ -82,6 +81,10 @@ def server_delete(self, name): def setUp(self): """Set necessary variables and create server.""" super(ServerTests, self).setUp() + self.flavor_name = self.get_flavor() + self.image_name = self.get_image() + self.network_arg = self.get_network() + self.NAME = data_utils.rand_name('TestServer') self.OTHER_NAME = data_utils.rand_name('TestServer') self.HEADERS = ['"Name"'] @@ -119,16 +122,47 @@ def test_server_list(self): self.assertIn(self.NAME, raw_output) def test_server_show(self): - """Test server show command. - - Test steps: - 1) Boot server in setUp - 2) Show server - 3) Check output - """ - opts = self.get_opts(self.FIELDS) - raw_output = self.openstack('server show ' + self.NAME + opts) - self.assertEqual(self.NAME + "\n", raw_output) + """Test server create, server delete commands""" + name1 = data_utils.rand_name('TestServer') + cmd_output = json.loads(self.openstack( + 'server create -f json ' + + '--flavor ' + self.flavor_name + ' ' + + '--image ' + self.image_name + ' ' + + self.network_arg + ' ' + + name1 + )) + self.assertIsNotNone(cmd_output["id"]) + self.addCleanup(self.openstack, 'server delete ' + name1) + self.assertEqual( + name1, + cmd_output["name"], + ) + + # Have a look at some other fields + flavor = json.loads(self.openstack( + 'flavor show -f json ' + + self.flavor_name + )) + self.assertEqual( + self.flavor_name, + flavor['name'], + ) + self.assertEqual( + '%s (%s)' % (flavor['name'], flavor['id']), + cmd_output["flavor"], + ) + image = json.loads(self.openstack( + 'image show -f json ' + + self.image_name + )) + self.assertEqual( + self.image_name, + image['name'], + ) + self.assertEqual( + '%s (%s)' % (image['name'], image['id']), + cmd_output["image"], + ) def test_server_metadata(self): """Test command to set server metadata. From 365d839a5b4e685d9a12f81386ddfaab07c56d83 Mon Sep 17 00:00:00 2001 From: Colleen Murphy Date: Wed, 25 Jan 2017 18:07:17 +0100 Subject: [PATCH 1517/3095] Fix 'mapping set' return value Without this patch, the command 'openstack mapping set ' will, upon success, print the rules for the updated mapping and exit with return code 1 (failure). This is a problem for scripts and config management tools that depend on the return code to validate whether the operation was successful, since even upon success the command returns a failing error code. Moreover, the behavior of printing the new value is completely unlike the behavior of any of the 'set' subcommands for other entities. This patch normalizes the 'mapping set' command by omitting any return value in the SetMapping take_action() method. This way the client will only exit with an error code if an exception is raised, and not upon normal operation. Change-Id: I610ec3b2fa7561072346d46e49cfc1ae82130e0d --- openstackclient/identity/v3/mapping.py | 1 - openstackclient/tests/unit/identity/v3/test_mappings.py | 8 ++------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/openstackclient/identity/v3/mapping.py b/openstackclient/identity/v3/mapping.py index dbb1b06870..28080f8957 100644 --- a/openstackclient/identity/v3/mapping.py +++ b/openstackclient/identity/v3/mapping.py @@ -183,7 +183,6 @@ def take_action(self, parsed_args): rules=rules) mapping._info.pop('links', None) - return zip(*sorted(six.iteritems(mapping._info))) class ShowMapping(command.ShowOne): diff --git a/openstackclient/tests/unit/identity/v3/test_mappings.py b/openstackclient/tests/unit/identity/v3/test_mappings.py index 5086724c24..93fe1196c9 100644 --- a/openstackclient/tests/unit/identity/v3/test_mappings.py +++ b/openstackclient/tests/unit/identity/v3/test_mappings.py @@ -181,16 +181,12 @@ def test_set_new_rules(self): mocker.return_value = identity_fakes.MAPPING_RULES_2 with mock.patch("openstackclient.identity.v3.mapping." "SetMapping._read_rules", mocker): - columns, data = self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) self.mapping_mock.update.assert_called_with( mapping=identity_fakes.mapping_id, rules=identity_fakes.MAPPING_RULES_2) - collist = ('id', 'rules') - self.assertEqual(collist, columns) - datalist = (identity_fakes.mapping_id, - identity_fakes.MAPPING_RULES_2) - self.assertEqual(datalist, data) + self.assertIsNone(result) def test_set_rules_wrong_file_path(self): arglist = [ From 5ecb353f891cf6fc0a132b7cc232e51ea39be348 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 25 Jan 2017 11:19:36 -0600 Subject: [PATCH 1518/3095] Add server_boot_from_volume() test Plucked this test out of I5529f412578c50090e70d17aa0129217bf803fed in order to validate the current behaviour before applying that change. It was converted to the new JSON-style. Change-Id: Ie51b1c375c5940856ec76a5770df3c6bd18a3eba --- .../functional/compute/v2/test_server.py | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py index ffdfa6c812..b6ef85875e 100644 --- a/openstackclient/tests/functional/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -16,6 +16,7 @@ from tempest.lib.common.utils import data_utils from openstackclient.tests.functional import base +from openstackclient.tests.functional.volume.v2 import test_volume from tempest.lib import exceptions @@ -318,6 +319,120 @@ def test_server_reboot(self): self.assertEqual("", raw_output) self.wait_for_status("ACTIVE") + def test_server_boot_from_volume(self): + """Test server create from volume, server delete + + Test steps: + 1) Create volume from image + 2) Create empty volume + 3) Create server from new volumes + 4) Check for ACTIVE new server status + 5) Check volumes attached to server + """ + # server_image = self.get_image() + # get volume status wait function + volume_wait_for = test_volume.VolumeTests( + methodName='wait_for', + ).wait_for + + # get image size + cmd_output = json.loads(self.openstack( + 'image show -f json ' + + self.image_name + )) + try: + image_size = cmd_output['min_disk'] + if image_size < 1: + image_size = 1 + except ValueError: + image_size = 1 + + # create volume from image + volume_name = data_utils.rand_name('volume', self.image_name) + cmd_output = json.loads(self.openstack( + 'volume create -f json ' + + '--image ' + self.image_name + ' ' + + '--size ' + str(image_size) + ' ' + + volume_name + )) + self.assertIsNotNone(cmd_output["id"]) + self.addCleanup(self.openstack, 'volume delete ' + volume_name) + self.assertEqual( + volume_name, + cmd_output['name'], + ) + volume_wait_for("volume", volume_name, "available") + + # create empty volume + empty_volume_name = data_utils.rand_name('TestVolume') + cmd_output = json.loads(self.openstack( + 'volume create -f json ' + + '--size ' + str(image_size) + ' ' + + empty_volume_name + )) + self.assertIsNotNone(cmd_output["id"]) + self.addCleanup(self.openstack, 'volume delete ' + empty_volume_name) + self.assertEqual( + empty_volume_name, + cmd_output['name'], + ) + volume_wait_for("volume", empty_volume_name, "available") + + # create server + server_name = data_utils.rand_name('TestServer') + server = json.loads(self.openstack( + 'server create -f json ' + + '--flavor ' + self.flavor_name + ' ' + + '--volume ' + volume_name + ' ' + + '--block-device-mapping vdb=' + empty_volume_name + ' ' + + self.network_arg + ' ' + + server_name + )) + self.assertIsNotNone(server["id"]) + self.addCleanup(self.openstack, 'server delete --wait ' + server_name) + self.assertEqual( + server_name, + server['name'], + ) + volume_wait_for("server", server_name, "ACTIVE") + + # check volumes + cmd_output = json.loads(self.openstack( + 'volume show -f json ' + + volume_name + )) + attachments = cmd_output['attachments'] + self.assertEqual( + 1, + len(attachments), + ) + self.assertEqual( + server['id'], + attachments[0]['server_id'], + ) + self.assertEqual( + "in-use", + cmd_output['status'], + ) + + # NOTE(dtroyer): Prior to https://review.openstack.org/#/c/407111 + # --block-device-mapping was ignored if --volume + # present on the command line, so this volume should + # not be attached. + cmd_output = json.loads(self.openstack( + 'volume show -f json ' + + empty_volume_name + )) + attachments = cmd_output['attachments'] + self.assertEqual( + 0, + len(attachments), + ) + self.assertEqual( + "available", + cmd_output['status'], + ) + def wait_for_status(self, expected_status='ACTIVE', wait=900, interval=30): """Wait until server reaches expected status.""" # TODO(thowe): Add a server wait command to osc From 42ac82b1a4970da688ea8fe6c1eb35c87bae8652 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Tue, 24 Jan 2017 00:10:51 -0500 Subject: [PATCH 1519/3095] change assert_show_fields to not fail on new fields whenever a resource adds a field (which is allowed in our API guidelines), OSC functional tests fail, because we validate the resource keys to a hardcoded list. instead, this change proposes that the logic of assert_show_fields is flipped around, so our hardcoded list acts as a minimum set of values that must appear in the resource. as part of this change, some fields were remove from the constants since they were not actually in the returned data. also, delete unused code `assert_show_structure`. Change-Id: I8c0f0e80ea472f9c7f93c5f1f0ae52048e6cd7da --- openstackclient/tests/functional/base.py | 27 +++++++------------ .../tests/functional/identity/v2/common.py | 9 +++---- .../tests/functional/identity/v3/common.py | 8 +++--- 3 files changed, 18 insertions(+), 26 deletions(-) diff --git a/openstackclient/tests/functional/base.py b/openstackclient/tests/functional/base.py index fb78ea6250..8574329621 100644 --- a/openstackclient/tests/functional/base.py +++ b/openstackclient/tests/functional/base.py @@ -16,7 +16,6 @@ import subprocess import testtools -import six from tempest.lib.cli import output_parser from tempest.lib import exceptions @@ -88,23 +87,17 @@ def assert_table_structure(self, items, field_names): for field in field_names: self.assertIn(field, item) - def assert_show_fields(self, items, field_names): + def assert_show_fields(self, show_output, field_names): """Verify that all items have keys listed in field_names.""" - for item in items: - for key in six.iterkeys(item): - self.assertIn(key, field_names) - - def assert_show_structure(self, items, field_names): - """Verify that all field_names listed in keys of all items.""" - if isinstance(items, list): - o = {} - for d in items: - o.update(d) - else: - o = items - item_keys = o.keys() - for field in field_names: - self.assertIn(field, item_keys) + + # field_names = ['name', 'description'] + # show_output = [{'name': 'fc2b98d8faed4126b9e371eda045ade2'}, + # {'description': 'description-821397086'}] + # this next line creates a flattened list of all 'keys' (like 'name', + # and 'description' out of the output + all_headers = [item for sublist in show_output for item in sublist] + for field_name in field_names: + self.assertIn(field_name, all_headers) def parse_show_as_object(self, raw_output): """Return a dict with values parsed from cli output.""" diff --git a/openstackclient/tests/functional/identity/v2/common.py b/openstackclient/tests/functional/identity/v2/common.py index b390c5bc15..ad02f7791b 100644 --- a/openstackclient/tests/functional/identity/v2/common.py +++ b/openstackclient/tests/functional/identity/v2/common.py @@ -22,14 +22,13 @@ class IdentityTests(base.TestCase): """Functional tests for Identity commands. """ - USER_FIELDS = ['email', 'enabled', 'id', 'name', 'project_id', - 'username', 'domain_id', 'default_project_id'] - PROJECT_FIELDS = ['enabled', 'id', 'name', 'description', 'domain_id'] + USER_FIELDS = ['email', 'enabled', 'id', 'name', 'project_id', 'username'] + PROJECT_FIELDS = ['enabled', 'id', 'name', 'description'] TOKEN_FIELDS = ['expires', 'id', 'project_id', 'user_id'] - ROLE_FIELDS = ['id', 'name', 'links', 'domain_id'] + ROLE_FIELDS = ['id', 'name', 'domain_id'] SERVICE_FIELDS = ['id', 'enabled', 'name', 'type', 'description'] ENDPOINT_FIELDS = ['id', 'region', 'service_id', 'service_name', - 'service_type', 'enabled', 'publicurl', + 'service_type', 'publicurl', 'adminurl', 'internalurl'] EC2_CREDENTIALS_FIELDS = ['access', 'project_id', 'secret', diff --git a/openstackclient/tests/functional/identity/v3/common.py b/openstackclient/tests/functional/identity/v3/common.py index 3b6fc27b97..a509574cb7 100644 --- a/openstackclient/tests/functional/identity/v3/common.py +++ b/openstackclient/tests/functional/identity/v3/common.py @@ -23,15 +23,15 @@ class IdentityTests(base.TestCase): """Functional tests for Identity commands. """ - DOMAIN_FIELDS = ['description', 'enabled', 'id', 'name', 'links'] - GROUP_FIELDS = ['description', 'domain_id', 'id', 'name', 'links'] + DOMAIN_FIELDS = ['description', 'enabled', 'id', 'name'] + GROUP_FIELDS = ['description', 'domain_id', 'id', 'name'] TOKEN_FIELDS = ['expires', 'id', 'project_id', 'user_id'] USER_FIELDS = ['email', 'enabled', 'id', 'name', 'name', 'domain_id', 'default_project_id', 'description', 'password_expires_at'] PROJECT_FIELDS = ['description', 'id', 'domain_id', 'is_domain', - 'enabled', 'name', 'parent_id', 'links'] - ROLE_FIELDS = ['id', 'name', 'links', 'domain_id'] + 'enabled', 'name', 'parent_id'] + ROLE_FIELDS = ['id', 'name', 'domain_id'] SERVICE_FIELDS = ['id', 'enabled', 'name', 'type', 'description'] REGION_FIELDS = ['description', 'enabled', 'parent_region', 'region'] ENDPOINT_FIELDS = ['id', 'region', 'region_id', 'service_id', From d780e9e91fae608a8e810dc070b67299fd0b324c Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 25 Jan 2017 15:25:28 -0600 Subject: [PATCH 1520/3095] Fix Identity functional tests to not require OS_AUTH_URL The Identity functional tests had an assumption that OS_AUTH_URL would always be present, but when running the functional tests and only setting OS_CLOUD (using clouds.yaml for creds) this fell down. Change-Id: Ie589d301f866b06d9f8be8deeb953e03bc01cf09 --- openstackclient/tests/functional/identity/v2/common.py | 4 ++-- openstackclient/tests/functional/identity/v3/common.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openstackclient/tests/functional/identity/v2/common.py b/openstackclient/tests/functional/identity/v2/common.py index b390c5bc15..fe49a22554 100644 --- a/openstackclient/tests/functional/identity/v2/common.py +++ b/openstackclient/tests/functional/identity/v2/common.py @@ -44,8 +44,8 @@ def setUpClass(cls): # prepare v2 env os.environ['OS_IDENTITY_API_VERSION'] = '2.0' auth_url = os.environ.get('OS_AUTH_URL') - auth_url = auth_url.replace('v3', 'v2.0') - os.environ['OS_AUTH_URL'] = auth_url + if auth_url: + os.environ['OS_AUTH_URL'] = auth_url.replace('v3', 'v2.0') # create dummy project cls.project_name = data_utils.rand_name('TestProject') diff --git a/openstackclient/tests/functional/identity/v3/common.py b/openstackclient/tests/functional/identity/v3/common.py index 3b6fc27b97..eabf96d51c 100644 --- a/openstackclient/tests/functional/identity/v3/common.py +++ b/openstackclient/tests/functional/identity/v3/common.py @@ -56,8 +56,8 @@ def setUpClass(cls): # prepare v3 env os.environ['OS_IDENTITY_API_VERSION'] = '3' auth_url = os.environ.get('OS_AUTH_URL') - auth_url = auth_url.replace('v2.0', 'v3') - os.environ['OS_AUTH_URL'] = auth_url + if auth_url: + os.environ['OS_AUTH_URL'] = auth_url.replace('v2.0', 'v3') # create dummy domain cls.domain_name = data_utils.rand_name('TestDomain') From c46f9dc501441ef449f41e726ec3cfbbe9f3de9d Mon Sep 17 00:00:00 2001 From: "jiahui.qiang" Date: Thu, 24 Nov 2016 17:05:25 +0800 Subject: [PATCH 1521/3095] Add options to "server list" command Add "--deleted" and "--changes-since" options to "server list" command. Change-Id: Id94f6e5831a60b172b6cfcfca29b1d89de8db621 Closes-Bug:#1647242 --- doc/source/command-objects/server.rst | 11 +++++ openstackclient/compute/v2/server.py | 25 ++++++++++ .../tests/unit/compute/v2/test_server.py | 46 +++++++++++++++++++ .../notes/bug-1647242-fdc39e564372857b.yaml | 6 +++ 4 files changed, 88 insertions(+) create mode 100644 releasenotes/notes/bug-1647242-fdc39e564372857b.yaml diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index 40ecc047a1..f18d091855 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -253,6 +253,8 @@ List servers [--long] [--marker ] [--limit ] + [--deleted] + [--changes-since ] .. option:: --reservation-id @@ -327,6 +329,15 @@ List servers be displayed. If limit is greater than 'osapi_max_limit' option of Nova API, 'osapi_max_limit' will be used instead. +.. option:: --deleted + + Only display deleted servers (Admin only). + +.. option:: --changes-since + + List only servers changed after a certain point of time. The provided time + should be an ISO 8061 formatted time. ex 2016-03-04T06:27:59Z. + server lock ----------- diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index a1330e0195..dc32add5b2 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -26,6 +26,7 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from oslo_utils import timeutils import six try: @@ -793,6 +794,20 @@ def get_parser(self, prog_name): " 'osapi_max_limit' option of Nova API," " 'osapi_max_limit' will be used instead."), ) + parser.add_argument( + '--deleted', + action="store_true", + default=False, + help=_('Only display deleted servers (Admin only).') + ) + parser.add_argument( + '--changes-since', + metavar='', + default=None, + help=_("List only servers changed after a certain point of time." + " The provided time should be an ISO 8061 formatted time." + " ex 2016-03-04T06:27:59Z .") + ) return parser def take_action(self, parsed_args): @@ -844,9 +859,19 @@ def take_action(self, parsed_args): 'tenant_id': project_id, 'all_tenants': parsed_args.all_projects, 'user_id': user_id, + 'deleted': parsed_args.deleted, + 'changes_since': parsed_args.changes_since, } LOG.debug('search options: %s', search_opts) + if search_opts['changes_since']: + try: + timeutils.parse_isotime(search_opts['changes_since']) + except ValueError: + raise exceptions.CommandError(_('Invalid changes-since value:' + ' %s') % search_opts['changes' + '_since']) + if parsed_args.long: columns = ( 'ID', diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 54f36209c0..deba4435a1 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -19,6 +19,7 @@ from osc_lib import exceptions from osc_lib import utils as common_utils +from oslo_utils import timeutils from openstackclient.compute.v2 import server from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes @@ -826,6 +827,8 @@ def setUp(self): 'tenant_id': None, 'all_tenants': False, 'user_id': None, + 'deleted': False, + 'changes_since': None, } # Default params of the core function of the command in the case of no @@ -902,6 +905,7 @@ def test_server_list_no_option(self): verifylist = [ ('all_projects', False), ('long', False), + ('deleted', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -967,6 +971,48 @@ def test_server_list_with_flavor(self): self.assertEqual(self.columns, columns) self.assertEqual(tuple(self.data), tuple(data)) + def test_server_list_with_changes_since(self): + + arglist = [ + '--changes-since', '2016-03-04T06:27:59Z', + '--deleted' + ] + verifylist = [ + ('changes_since', '2016-03-04T06:27:59Z'), + ('deleted', True), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.search_opts['changes_since'] = '2016-03-04T06:27:59Z' + self.search_opts['deleted'] = True + self.servers_mock.list.assert_called_with(**self.kwargs) + + self.assertEqual(self.columns, columns) + self.assertEqual(tuple(self.data), tuple(data)) + + @mock.patch.object(timeutils, 'parse_isotime', side_effect=ValueError) + def test_server_list_with_invalid_changes_since(self, mock_parse_isotime): + + arglist = [ + '--changes-since', 'Invalid time value', + ] + verifylist = [ + ('changes_since', 'Invalid time value'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('Invalid changes-since value: Invalid time ' + 'value', str(e)) + mock_parse_isotime.assert_called_once_with( + 'Invalid time value' + ) + class TestServerLock(TestServer): diff --git a/releasenotes/notes/bug-1647242-fdc39e564372857b.yaml b/releasenotes/notes/bug-1647242-fdc39e564372857b.yaml new file mode 100644 index 0000000000..d4b3bdaa5d --- /dev/null +++ b/releasenotes/notes/bug-1647242-fdc39e564372857b.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--deleted`` and ``--changes-since`` options to ``server list`` + command. + [Bug `1647242 `_] From 4cb56269ad30d0bd59f7685040ab0585f38c3b0f Mon Sep 17 00:00:00 2001 From: Samuel Pilla Date: Wed, 25 Jan 2017 10:40:49 -0600 Subject: [PATCH 1522/3095] Adds domain specification for SetUser This patch adds the ability to specify the domain context for making changes to a user with `--domain` flag. Example: $ openstack user set test_user --domain test_domain --enable Change-Id: I2b3241785c22e72e19181394acff650422299b0e Closes-Bug: #1658147 --- doc/source/command-objects/user.rst | 7 ++++ openstackclient/identity/v3/user.py | 23 +++++++++--- .../tests/unit/identity/v3/test_user.py | 36 +++++++++++++++++++ .../notes/bug-1658147-9de9ae222f9db9ae.yaml | 6 ++++ 4 files changed, 68 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/bug-1658147-9de9ae222f9db9ae.yaml diff --git a/doc/source/command-objects/user.rst b/doc/source/command-objects/user.rst index 7e0ee21b0f..632d0e2566 100644 --- a/doc/source/command-objects/user.rst +++ b/doc/source/command-objects/user.rst @@ -153,6 +153,13 @@ Set user properties Set user name +.. option:: --domain + + Domain the user belongs to (name or ID). + This can be used in case collisions between user names exist. + + .. versionadded:: 3 + .. option:: --project Set default project (name or ID) diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index 19a4c29875..9c289a6d8a 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -301,6 +301,12 @@ def get_parser(self, prog_name): metavar='', help=_('Set user name'), ) + parser.add_argument( + '--domain', + metavar='', + help=_('Domain the user belongs to (name or ID). This can be ' + 'used in case collisions between user names exist.'), + ) parser.add_argument( '--project', metavar='', @@ -351,10 +357,19 @@ def take_action(self, parsed_args): LOG.warning(_("No password was supplied, authentication will fail " "when a user does not have a password.")) - user = utils.find_resource( - identity_client.users, - parsed_args.user, - ) + user_str = common._get_token_resource(identity_client, 'user', + parsed_args.user) + if parsed_args.domain: + domain = common.find_domain(identity_client, parsed_args.domain) + user = utils.find_resource(identity_client.users, + user_str, + domain_id=domain.id) + else: + user = utils.find_resource( + identity_client.users, + parsed_args.user, + ) + kwargs = {} if parsed_args.name: kwargs['name'] = parsed_args.name diff --git a/openstackclient/tests/unit/identity/v3/test_user.py b/openstackclient/tests/unit/identity/v3/test_user.py index 3c1f49a680..2ce66e94a2 100644 --- a/openstackclient/tests/unit/identity/v3/test_user.py +++ b/openstackclient/tests/unit/identity/v3/test_user.py @@ -684,9 +684,14 @@ def test_user_list_project(self): class TestUserSet(TestUser): project = identity_fakes.FakeProject.create_one_project() + domain = identity_fakes.FakeDomain.create_one_domain() user = identity_fakes.FakeUser.create_one_user( attrs={'default_project_id': project.id} ) + user2 = identity_fakes.FakeUser.create_one_user( + attrs={'default_project_id': project.id, + 'domain_id': domain.id} + ) def setUp(self): super(TestUserSet, self).setUp() @@ -748,6 +753,37 @@ def test_user_set_name(self): ) self.assertIsNone(result) + def test_user_set_specify_domain(self): + arglist = [ + '--name', 'qwerty', + '--domain', self.domain.id, + self.user2.name + ] + verifylist = [ + ('name', 'qwerty'), + ('password', None), + ('domain', self.domain.id), + ('email', None), + ('project', None), + ('enable', False), + ('disable', False), + ('user', self.user2.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + kwargs = { + 'enabled': True, + 'name': 'qwerty' + } + + self.users_mock.update.assert_called_with( + self.user.id, + **kwargs + ) + self.assertIsNone(result) + def test_user_set_password(self): arglist = [ '--password', 'secret', diff --git a/releasenotes/notes/bug-1658147-9de9ae222f9db9ae.yaml b/releasenotes/notes/bug-1658147-9de9ae222f9db9ae.yaml new file mode 100644 index 0000000000..f46305abfa --- /dev/null +++ b/releasenotes/notes/bug-1658147-9de9ae222f9db9ae.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--domain`` options to the ``user set`` command. + Allows specification of domain context when changing users. + [Bug `1658147 `_] From 26d50be79a401322f4e74b94c066257ddf0f287c Mon Sep 17 00:00:00 2001 From: "zhiyong.dai" Date: Mon, 2 Jan 2017 17:55:32 +0800 Subject: [PATCH 1523/3095] Support "--no-property" option in volume snapshot set Supporting "--no-property" option will apply user a convenient way to clean all properties of volume snapshot in a short command, and this kind of behavior is the recommended way to devref. The patch adds "--no-property" option in "volume snapshot set" command, and update related test cases and devref document. Change-Id: I5f10cc2b5814553699920c4343995b2e11416e4e Implements: blueprint allow-overwrite-set-options --- .../command-objects/volume-snapshot.rst | 7 + .../functional/volume/v1/test_snapshot.py | 250 +++++++++++++++--- .../functional/volume/v2/test_snapshot.py | 36 ++- .../tests/unit/volume/v1/test_snapshot.py | 13 +- .../tests/unit/volume/v2/test_snapshot.py | 63 ++++- openstackclient/volume/v1/volume_snapshot.py | 20 ++ openstackclient/volume/v2/volume_snapshot.py | 20 ++ ...y-in-volume-snapshot-0af3fcb31a3cfc2b.yaml | 7 + 8 files changed, 357 insertions(+), 59 deletions(-) create mode 100644 releasenotes/notes/support-no-property-in-volume-snapshot-0af3fcb31a3cfc2b.yaml diff --git a/doc/source/command-objects/volume-snapshot.rst b/doc/source/command-objects/volume-snapshot.rst index 37a5088a59..8aed1d830f 100644 --- a/doc/source/command-objects/volume-snapshot.rst +++ b/doc/source/command-objects/volume-snapshot.rst @@ -133,6 +133,7 @@ Set volume snapshot properties openstack volume snapshot set [--name ] [--description ] + [--no-property] [--property [...] ] [--state ] @@ -145,6 +146,12 @@ Set volume snapshot properties New snapshot description +.. option:: --no-property + + Remove all properties from :ref:`\ ` + (specify both :option:`--no-property` and :option:`--property` to + remove the current properties before setting new properties.) + .. option:: --property Property to add or modify for this snapshot (repeat option to set multiple properties) diff --git a/openstackclient/tests/functional/volume/v1/test_snapshot.py b/openstackclient/tests/functional/volume/v1/test_snapshot.py index 1e1c6b2144..89a98661f4 100644 --- a/openstackclient/tests/functional/volume/v1/test_snapshot.py +++ b/openstackclient/tests/functional/volume/v1/test_snapshot.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import json import time import uuid @@ -20,9 +21,6 @@ class VolumeSnapshotTests(common.BaseVolumeTests): """Functional tests for volume snapshot. """ VOLLY = uuid.uuid4().hex - NAME = uuid.uuid4().hex - OTHER_NAME = uuid.uuid4().hex - HEADERS = ['"Name"'] @classmethod def wait_for_status(cls, command, status, tries): @@ -30,59 +28,223 @@ def wait_for_status(cls, command, status, tries): for attempt in range(tries): time.sleep(1) raw_output = cls.openstack(command + opts) - if (raw_output == status): + if (raw_output.rstrip() == status): return cls.assertOutput(status, raw_output) @classmethod def setUpClass(cls): super(VolumeSnapshotTests, cls).setUpClass() - cls.openstack('volume create --size 1 ' + cls.VOLLY) - cls.wait_for_status('volume show ' + cls.VOLLY, 'available\n', 3) - opts = cls.get_opts(['status']) - raw_output = cls.openstack('volume snapshot create --volume ' + - cls.VOLLY + ' ' + cls.NAME + opts) - cls.assertOutput('creating\n', raw_output) - cls.wait_for_status( - 'volume snapshot show ' + cls.NAME, 'available\n', 3) + # create a volume for all tests to create snapshot + cmd_output = json.loads(cls.openstack( + 'volume create -f json ' + + '--size 1 ' + + cls.VOLLY + )) + cls.wait_for_status('volume show ' + cls.VOLLY, 'available', 6) + cls.VOLUME_ID = cmd_output['id'] @classmethod def tearDownClass(cls): - # Rename test - raw_output = cls.openstack( - 'volume snapshot set --name ' + cls.OTHER_NAME + ' ' + cls.NAME) + cls.wait_for_status('volume show ' + cls.VOLLY, 'available', 6) + raw_output = cls.openstack('volume delete --force ' + cls.VOLLY) cls.assertOutput('', raw_output) - # Delete test - raw_output_snapshot = cls.openstack( - 'volume snapshot delete ' + cls.OTHER_NAME) - cls.wait_for_status('volume show ' + cls.VOLLY, 'available\n', 6) - raw_output_volume = cls.openstack('volume delete --force ' + cls.VOLLY) - cls.assertOutput('', raw_output_snapshot) - cls.assertOutput('', raw_output_volume) - - def test_snapshot_list(self): - opts = self.get_opts(self.HEADERS) - raw_output = self.openstack('volume snapshot list' + opts) - self.assertIn(self.NAME, raw_output) - - def test_snapshot_set_unset_properties(self): + + def test_volume_snapshot__delete(self): + """Test create, delete multiple""" + name1 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume snapshot create -f json ' + + name1 + + ' --volume ' + self.VOLLY + )) + self.assertEqual( + name1, + cmd_output["display_name"], + ) + + name2 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume snapshot create -f json ' + + name2 + + ' --volume ' + self.VOLLY + )) + self.assertEqual( + name2, + cmd_output["display_name"], + ) + + self.wait_for_status( + 'volume snapshot show ' + name1, 'available', 6) + self.wait_for_status( + 'volume snapshot show ' + name2, 'available', 6) + + del_output = self.openstack( + 'volume snapshot delete ' + name1 + ' ' + name2) + self.assertOutput('', del_output) + + def test_volume_snapshot_list(self): + """Test create, list filter""" + name1 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume snapshot create -f json ' + + name1 + + ' --volume ' + self.VOLLY + )) + self.addCleanup(self.openstack, 'volume snapshot delete ' + name1) + self.assertEqual( + name1, + cmd_output["display_name"], + ) + self.assertEqual( + self.VOLUME_ID, + cmd_output["volume_id"], + ) + self.assertEqual( + 1, + cmd_output["size"], + ) + self.wait_for_status( + 'volume snapshot show ' + name1, 'available', 6) + + name2 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume snapshot create -f json ' + + name2 + + ' --volume ' + self.VOLLY + )) + self.addCleanup(self.openstack, 'volume snapshot delete ' + name2) + self.assertEqual( + name2, + cmd_output["display_name"], + ) + self.assertEqual( + self.VOLUME_ID, + cmd_output["volume_id"], + ) + self.assertEqual( + 1, + cmd_output["size"], + ) + self.wait_for_status( + 'volume snapshot show ' + name2, 'available', 6) + + # Test list --long, --status + cmd_output = json.loads(self.openstack( + 'volume snapshot list -f json ' + + '--long ' + + '--status error' + )) + names = [x["Name"] for x in cmd_output] + self.assertNotIn(name1, names) + self.assertNotIn(name2, names) + + # Test list --volume + cmd_output = json.loads(self.openstack( + 'volume snapshot list -f json ' + + '--volume ' + self.VOLLY + )) + names = [x["Name"] for x in cmd_output] + self.assertIn(name1, names) + self.assertIn(name2, names) + + # Test list --name + cmd_output = json.loads(self.openstack( + 'volume snapshot list -f json ' + + '--name ' + name1 + )) + names = [x["Name"] for x in cmd_output] + self.assertIn(name1, names) + self.assertNotIn(name2, names) + + def test_snapshot_set(self): + """Test create, set, unset, show, delete volume snapshot""" + name = uuid.uuid4().hex + new_name = name + "_" + cmd_output = json.loads(self.openstack( + 'volume snapshot create -f json ' + + '--volume ' + self.VOLLY + + ' --description aaaa ' + + name + )) + self.addCleanup(self.openstack, 'volume snapshot delete ' + new_name) + self.assertEqual( + name, + cmd_output["display_name"], + ) + self.assertEqual( + 1, + cmd_output["size"], + ) + self.assertEqual( + 'aaaa', + cmd_output["display_description"], + ) + self.wait_for_status( + 'volume snapshot show ' + name, 'available', 6) + + # Test volume snapshot set raw_output = self.openstack( - 'volume snapshot set --property a=b --property c=d ' + self.NAME) - self.assertEqual("", raw_output) - opts = self.get_opts(["properties"]) - raw_output = self.openstack('volume snapshot show ' + self.NAME + opts) - self.assertEqual("a='b', c='d'\n", raw_output) + 'volume snapshot set ' + + '--name ' + new_name + + ' --description bbbb ' + + '--property Alpha=a ' + + '--property Beta=b ' + + name, + ) + self.assertOutput('', raw_output) + + # Show snapshot set result + cmd_output = json.loads(self.openstack( + 'volume snapshot show -f json ' + + new_name + )) + self.assertEqual( + new_name, + cmd_output["display_name"], + ) + self.assertEqual( + 1, + cmd_output["size"], + ) + self.assertEqual( + 'bbbb', + cmd_output["display_description"], + ) + self.assertEqual( + "Alpha='a', Beta='b'", + cmd_output["properties"], + ) + # Test volume unset raw_output = self.openstack( - 'volume snapshot unset --property a ' + self.NAME) - self.assertEqual("", raw_output) - raw_output = self.openstack('volume snapshot show ' + self.NAME + opts) - self.assertEqual("c='d'\n", raw_output) + 'volume snapshot unset ' + + '--property Alpha ' + + new_name, + ) + self.assertOutput('', raw_output) + + cmd_output = json.loads(self.openstack( + 'volume snapshot show -f json ' + + new_name + )) + self.assertEqual( + "Beta='b'", + cmd_output["properties"], + ) - def test_snapshot_set_description(self): + # Test volume snapshot set --no-property raw_output = self.openstack( - 'volume snapshot set --description backup ' + self.NAME) - self.assertEqual("", raw_output) - opts = self.get_opts(["display_description", "display_name"]) - raw_output = self.openstack('volume snapshot show ' + self.NAME + opts) - self.assertEqual("backup\n" + self.NAME + "\n", raw_output) + 'volume snapshot set ' + + '--no-property ' + + new_name, + ) + self.assertOutput('', raw_output) + cmd_output = json.loads(self.openstack( + 'volume snapshot show -f json ' + + new_name + )) + self.assertNotIn( + "Beta='b'", + cmd_output["properties"], + ) diff --git a/openstackclient/tests/functional/volume/v2/test_snapshot.py b/openstackclient/tests/functional/volume/v2/test_snapshot.py index c83e79f877..422e5b7ceb 100644 --- a/openstackclient/tests/functional/volume/v2/test_snapshot.py +++ b/openstackclient/tests/functional/volume/v2/test_snapshot.py @@ -28,7 +28,7 @@ def wait_for_status(cls, command, status, tries): for attempt in range(tries): time.sleep(1) raw_output = cls.openstack(command + opts) - if (raw_output == status): + if (raw_output.rstrip() == status): return cls.assertOutput(status, raw_output) @@ -41,12 +41,12 @@ def setUpClass(cls): '--size 1 ' + cls.VOLLY )) - cls.wait_for_status('volume show ' + cls.VOLLY, 'available\n', 6) + cls.wait_for_status('volume show ' + cls.VOLLY, 'available', 6) cls.VOLUME_ID = cmd_output['id'] @classmethod def tearDownClass(cls): - cls.wait_for_status('volume show ' + cls.VOLLY, 'available\n', 6) + cls.wait_for_status('volume show ' + cls.VOLLY, 'available', 6) raw_output = cls.openstack('volume delete --force ' + cls.VOLLY) cls.assertOutput('', raw_output) @@ -75,9 +75,9 @@ def test_volume_snapshot__delete(self): ) self.wait_for_status( - 'volume snapshot show ' + name1, 'available\n', 6) + 'volume snapshot show ' + name1, 'available', 6) self.wait_for_status( - 'volume snapshot show ' + name2, 'available\n', 6) + 'volume snapshot show ' + name2, 'available', 6) del_output = self.openstack( 'volume snapshot delete ' + name1 + ' ' + name2) @@ -105,7 +105,7 @@ def test_volume_snapshot_list(self): cmd_output["size"], ) self.wait_for_status( - 'volume snapshot show ' + name1, 'available\n', 6) + 'volume snapshot show ' + name1, 'available', 6) name2 = uuid.uuid4().hex cmd_output = json.loads(self.openstack( @@ -127,7 +127,7 @@ def test_volume_snapshot_list(self): cmd_output["size"], ) self.wait_for_status( - 'volume snapshot show ' + name2, 'available\n', 6) + 'volume snapshot show ' + name2, 'available', 6) raw_output = self.openstack( 'volume snapshot set ' + '--state error ' + @@ -163,7 +163,7 @@ def test_volume_snapshot_list(self): self.assertIn(name1, names) self.assertNotIn(name2, names) - def test_snapshot_set(self): + def test_volume_snapshot_set(self): """Test create, set, unset, show, delete volume snapshot""" name = uuid.uuid4().hex new_name = name + "_" @@ -192,7 +192,7 @@ def test_snapshot_set(self): cmd_output["properties"], ) self.wait_for_status( - 'volume snapshot show ' + name, 'available\n', 6) + 'volume snapshot show ' + name, 'available', 6) # Test volume snapshot set raw_output = self.openstack( @@ -227,7 +227,7 @@ def test_snapshot_set(self): cmd_output["properties"], ) - # Test volume unset + # Test volume snapshot unset raw_output = self.openstack( 'volume snapshot unset ' + '--property Alpha ' + @@ -243,3 +243,19 @@ def test_snapshot_set(self): "Beta='b'", cmd_output["properties"], ) + + # Test volume snapshot set --no-property + raw_output = self.openstack( + 'volume snapshot set ' + + '--no-property ' + + new_name, + ) + self.assertOutput('', raw_output) + cmd_output = json.loads(self.openstack( + 'volume snapshot show -f json ' + + new_name + )) + self.assertNotIn( + "Beta='b'", + cmd_output["properties"], + ) diff --git a/openstackclient/tests/unit/volume/v1/test_snapshot.py b/openstackclient/tests/unit/volume/v1/test_snapshot.py index fd878f4531..87a62b0a9b 100644 --- a/openstackclient/tests/unit/volume/v1/test_snapshot.py +++ b/openstackclient/tests/unit/volume/v1/test_snapshot.py @@ -429,15 +429,17 @@ def test_snapshot_set_all(self): arglist = [ "--name", "new_snapshot", "--description", "new_description", - "--property", "x=y", - "--property", "foo=foo", + "--property", "foo_1=foo_1", + "--property", "foo_2=foo_2", + "--no-property", self.snapshot.id, ] - new_property = {"x": "y", "foo": "foo"} + new_property = {"foo_1": "foo_1", "foo_2": "foo_2"} verifylist = [ ("name", "new_snapshot"), ("description", "new_description"), ("property", new_property), + ("no_property", True), ("snapshot", self.snapshot.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -449,8 +451,11 @@ def test_snapshot_set_all(self): "display_description": "new_description", } self.snapshot.update.assert_called_with(**kwargs) + self.snapshots_mock.delete_metadata.assert_called_with( + self.snapshot.id, ["foo"] + ) self.snapshots_mock.set_metadata.assert_called_with( - self.snapshot.id, new_property + self.snapshot.id, {"foo_2": "foo_2", "foo_1": "foo_1"} ) self.assertIsNone(result) diff --git a/openstackclient/tests/unit/volume/v2/test_snapshot.py b/openstackclient/tests/unit/volume/v2/test_snapshot.py index 8ce356aea8..f3a6ed3ce5 100644 --- a/openstackclient/tests/unit/volume/v2/test_snapshot.py +++ b/openstackclient/tests/unit/volume/v2/test_snapshot.py @@ -499,7 +499,23 @@ def setUp(self): # Get the command object to mock self.cmd = volume_snapshot.SetVolumeSnapshot(self.app, None) - def test_snapshot_set(self): + def test_snapshot_set_no_option(self): + arglist = [ + self.snapshot.id, + ] + verifylist = [ + ("snapshot", self.snapshot.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.snapshots_mock.get.assert_called_once_with(parsed_args.snapshot) + self.assertNotCalled(self.snapshots_mock.reset_state) + self.assertNotCalled(self.snapshots_mock.update) + self.assertNotCalled(self.snapshots_mock.set_metadata) + self.assertIsNone(result) + + def test_snapshot_set_name_and_property(self): arglist = [ "--name", "new_snapshot", "--property", "x=y", @@ -526,6 +542,51 @@ def test_snapshot_set(self): ) self.assertIsNone(result) + def test_snapshot_set_with_no_property(self): + arglist = [ + "--no-property", + self.snapshot.id, + ] + verifylist = [ + ("no_property", True), + ("snapshot", self.snapshot.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.snapshots_mock.get.assert_called_once_with(parsed_args.snapshot) + self.assertNotCalled(self.snapshots_mock.reset_state) + self.assertNotCalled(self.snapshots_mock.update) + self.assertNotCalled(self.snapshots_mock.set_metadata) + self.snapshots_mock.delete_metadata.assert_called_with( + self.snapshot.id, ["foo"] + ) + self.assertIsNone(result) + + def test_snapshot_set_with_no_property_and_property(self): + arglist = [ + "--no-property", + "--property", "foo_1=bar_1", + self.snapshot.id, + ] + verifylist = [ + ("no_property", True), + ("property", {"foo_1": "bar_1"}), + ("snapshot", self.snapshot.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.snapshots_mock.get.assert_called_once_with(parsed_args.snapshot) + self.assertNotCalled(self.snapshots_mock.reset_state) + self.assertNotCalled(self.snapshots_mock.update) + self.snapshots_mock.delete_metadata.assert_called_with( + self.snapshot.id, ["foo"] + ) + self.snapshots_mock.set_metadata.assert_called_once_with( + self.snapshot.id, {"foo_1": "bar_1"}) + self.assertIsNone(result) + def test_snapshot_set_state_to_error(self): arglist = [ "--state", "error", diff --git a/openstackclient/volume/v1/volume_snapshot.py b/openstackclient/volume/v1/volume_snapshot.py index 45bd30c044..f22c338b80 100644 --- a/openstackclient/volume/v1/volume_snapshot.py +++ b/openstackclient/volume/v1/volume_snapshot.py @@ -239,6 +239,15 @@ def get_parser(self, prog_name): metavar='', help=_('New snapshot description') ) + parser.add_argument( + "--no-property", + dest="no_property", + action="store_true", + help=_("Remove all properties from " + "(specify both --no-property and --property to " + "remove the current properties before setting " + "new properties.)"), + ) parser.add_argument( '--property', metavar='', @@ -254,6 +263,17 @@ def take_action(self, parsed_args): parsed_args.snapshot) result = 0 + if parsed_args.no_property: + try: + key_list = snapshot.metadata.keys() + volume_client.volume_snapshots.delete_metadata( + snapshot.id, + list(key_list), + ) + except Exception as e: + LOG.error(_("Failed to clean snapshot properties: %s"), e) + result += 1 + if parsed_args.property: try: volume_client.volume_snapshots.set_metadata( diff --git a/openstackclient/volume/v2/volume_snapshot.py b/openstackclient/volume/v2/volume_snapshot.py index af29b777dc..3c06fa5ac7 100644 --- a/openstackclient/volume/v2/volume_snapshot.py +++ b/openstackclient/volume/v2/volume_snapshot.py @@ -285,6 +285,15 @@ def get_parser(self, prog_name): metavar='', help=_('New snapshot description') ) + parser.add_argument( + "--no-property", + dest="no_property", + action="store_true", + help=_("Remove all properties from " + "(specify both --no-property and --property to " + "remove the current properties before setting " + "new properties.)"), + ) parser.add_argument( '--property', metavar='', @@ -311,6 +320,17 @@ def take_action(self, parsed_args): parsed_args.snapshot) result = 0 + if parsed_args.no_property: + try: + key_list = snapshot.metadata.keys() + volume_client.volume_snapshots.delete_metadata( + snapshot.id, + list(key_list), + ) + except Exception as e: + LOG.error(_("Failed to clean snapshot properties: %s"), e) + result += 1 + if parsed_args.property: try: volume_client.volume_snapshots.set_metadata( diff --git a/releasenotes/notes/support-no-property-in-volume-snapshot-0af3fcb31a3cfc2b.yaml b/releasenotes/notes/support-no-property-in-volume-snapshot-0af3fcb31a3cfc2b.yaml new file mode 100644 index 0000000000..6a3220b248 --- /dev/null +++ b/releasenotes/notes/support-no-property-in-volume-snapshot-0af3fcb31a3cfc2b.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Add ``--no-property`` option in ``volume snapshot set``. + Supporting ``--no-property`` option will apply user a convenient way to + clean all properties of volume snapshot in a short command. + [ Blueprint `allow-overwrite-set-options ` _] From 95c8661f86e74c9d5217869a740da11350f1f0eb Mon Sep 17 00:00:00 2001 From: Nikita Gerasimov Date: Mon, 5 Dec 2016 20:47:40 +0300 Subject: [PATCH 1524/3095] Switch server create to block_device_mapping_v2 Current compute_client.servers.create() relies on block_device_mapping arg which is legacy[1]. "block_device_mapping" format require device_name which is leads to hard-coded hack in --volume key handler to KVM specific. "block_device_mapping_v2" format is more friendly to hypervisiors. Support of block_device_mapping_v2 appear in python-novaclient 2.16.0, openstackclient require at least 2.29.0 Makes options --volume and --block-device-mapping work simultaneously. Appends --block-device-mapping data even if --volume used. After bug 1383338 only --volume was taken when both are used. [1]http://docs.openstack.org/developer/nova/block_device_mapping.html NOTE(dtroyer): I moved the new test_boot_from_volume() functional test to Ie51b1c375c5940856ec76a5770df3c6bd18a3eba to test our previous behaviour. The only changes required to support the new behaviour should be that the empty_volume is now attached in that test. Change-Id: I7bac3d870dd9ca404093142f8bce22a62e49180d Closes-Bug: 1647406 Closes-Bug: 1497845 --- openstackclient/compute/v2/server.py | 52 ++++++++++++------- .../functional/compute/v2/test_server.py | 12 +++-- .../tests/unit/compute/v2/test_server.py | 23 ++++---- .../notes/bug-1647406-c936581034a1b6e4.yaml | 16 ++++++ 4 files changed, 70 insertions(+), 33 deletions(-) create mode 100644 releasenotes/notes/bug-1647406-c936581034a1b6e4.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index a1330e0195..cd4500367c 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -507,28 +507,40 @@ def take_action(self, parsed_args): "exception": e} ) - block_device_mapping = {} + block_device_mapping_v2 = [] if volume: - # When booting from volume, for now assume no other mappings - # This device value is likely KVM-specific - block_device_mapping = {'vda': volume} - else: - for dev_map in parsed_args.block_device_mapping: - dev_key, dev_vol = dev_map.split('=', 1) - block_volume = None - if dev_vol: - vol = dev_vol.split(':', 1)[0] - if vol: - vol_id = utils.find_resource( + block_device_mapping_v2 = [{'uuid': volume, + 'boot_index': '0', + 'source_type': 'volume', + 'destination_type': 'volume' + }] + for dev_map in parsed_args.block_device_mapping: + dev_name, dev_map = dev_map.split('=', 1) + if dev_map: + dev_map = dev_map.split(':') + if len(dev_map) > 0: + mapping = { + 'device_name': dev_name, + 'uuid': utils.find_resource( volume_client.volumes, - vol, - ).id - block_volume = dev_vol.replace(vol, vol_id) + dev_map[0], + ).id} + # Block device mapping v1 compatibility + if len(dev_map) > 1 and \ + dev_map[1] in ('volume', 'snapshot'): + mapping['source_type'] = dev_map[1] else: - msg = _("Volume name or ID must be specified if " - "--block-device-mapping is specified") - raise exceptions.CommandError(msg) - block_device_mapping.update({dev_key: block_volume}) + mapping['source_type'] = 'volume' + mapping['destination_type'] = 'volume' + if len(dev_map) > 2: + mapping['volume_size'] = dev_map[2] + if len(dev_map) > 3: + mapping['delete_on_termination'] = dev_map[3] + else: + msg = _("Volume name or ID must be specified if " + "--block-device-mapping is specified") + raise exceptions.CommandError(msg) + block_device_mapping_v2.append(mapping) nics = [] if parsed_args.nic in ('auto', 'none'): @@ -598,7 +610,7 @@ def take_action(self, parsed_args): userdata=userdata, key_name=parsed_args.key_name, availability_zone=parsed_args.availability_zone, - block_device_mapping=block_device_mapping, + block_device_mapping_v2=block_device_mapping_v2, nics=nics, scheduler_hints=hints, config_drive=config_drive) diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py index b6ef85875e..119ef05c5d 100644 --- a/openstackclient/tests/functional/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -417,19 +417,23 @@ def test_server_boot_from_volume(self): # NOTE(dtroyer): Prior to https://review.openstack.org/#/c/407111 # --block-device-mapping was ignored if --volume - # present on the command line, so this volume should - # not be attached. + # present on the command line. Now we should see the + # attachment. cmd_output = json.loads(self.openstack( 'volume show -f json ' + empty_volume_name )) attachments = cmd_output['attachments'] self.assertEqual( - 0, + 1, len(attachments), ) self.assertEqual( - "available", + server['id'], + attachments[0]['server_id'], + ) + self.assertEqual( + "in-use", cmd_output['status'], ) diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 54f36209c0..53189aa2d3 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -308,7 +308,7 @@ def test_server_create_minimal(self): userdata=None, key_name=None, availability_zone=None, - block_device_mapping={}, + block_device_mapping_v2=[], nics=[], scheduler_hints={}, config_drive=None, @@ -363,7 +363,7 @@ def test_server_create_with_options(self): userdata=None, key_name='keyname', availability_zone=None, - block_device_mapping={}, + block_device_mapping_v2=[], nics=[], scheduler_hints={'a': ['b', 'c']}, config_drive=None, @@ -443,7 +443,7 @@ def test_server_create_with_network(self): userdata=None, key_name=None, availability_zone=None, - block_device_mapping={}, + block_device_mapping_v2=[], nics=[{'net-id': 'net1_uuid', 'v4-fixed-ip': '', 'v6-fixed-ip': '', @@ -500,7 +500,7 @@ def test_server_create_with_wait_ok(self, mock_wait_for_status): userdata=None, key_name=None, availability_zone=None, - block_device_mapping={}, + block_device_mapping_v2=[], nics=[], scheduler_hints={}, config_drive=None, @@ -550,7 +550,7 @@ def test_server_create_with_wait_fails(self, mock_wait_for_status): userdata=None, key_name=None, availability_zone=None, - block_device_mapping={}, + block_device_mapping_v2=[], nics=[], scheduler_hints={}, config_drive=None, @@ -605,7 +605,7 @@ def test_server_create_userdata(self, mock_open): userdata=mock_file, key_name=None, availability_zone=None, - block_device_mapping={}, + block_device_mapping_v2=[], nics=[], scheduler_hints={}, config_drive=None, @@ -656,9 +656,14 @@ def test_server_create_with_block_device_mapping(self): userdata=None, key_name=None, availability_zone=None, - block_device_mapping={ - 'vda': real_volume_mapping - }, + block_device_mapping_v2=[{ + 'device_name': 'vda', + 'uuid': real_volume_mapping.split(':', 1)[0], + 'destination_type': 'volume', + 'source_type': 'volume', + 'delete_on_termination': '0', + 'volume_size': '' + }], nics=[], scheduler_hints={}, config_drive=None, diff --git a/releasenotes/notes/bug-1647406-c936581034a1b6e4.yaml b/releasenotes/notes/bug-1647406-c936581034a1b6e4.yaml new file mode 100644 index 0000000000..2f327517de --- /dev/null +++ b/releasenotes/notes/bug-1647406-c936581034a1b6e4.yaml @@ -0,0 +1,16 @@ +--- +fixes: + - | + Allow ``--block-device-mapping`` option to work correctly with + ``--volume`` option in ``server create`` command. + After :lpbug:`1383338` ``--block-device-mapping`` was ignored if + ``--volume`` was present. Block device mappings are now appended + to the mapping created by the ``--volume`` option if it is present. + The device name of the boot volume specificed in the ``--volume`` option + is no longer assumed to be *'vda'* but now uses the hypervisor's boot + index to obtain the device name. This maintains the status quo for + **QEMU/KVM** hypervisors but **XEN**, **parallels** and others + *virt types* that have device naming is different from ``vd*`` + should now also work correctly. + [:lpbug:`1497845`] + [:lpbug:`1647406`] From 16aeee430308fd1863d2f9892f09e0d928a0e6f1 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 26 Jan 2017 09:52:49 -0600 Subject: [PATCH 1525/3095] Cleanup for 3.8.0 release Release notes and proofreading cleanups. Change-Id: Iddc6a64e6ea3082aa220a2465793e6f7f2aecaa9 --- doc/source/command-objects/network-meter-rule.rst | 6 ++---- doc/source/command-objects/server.rst | 9 +++++++-- openstackclient/compute/v2/server.py | 7 +++++-- .../bp-neutron-client-metering-6f8f9527c2a797fd.yaml | 7 +++++++ ...-no-property-in-volume-snapshot-0af3fcb31a3cfc2b.yaml | 4 +--- 5 files changed, 22 insertions(+), 11 deletions(-) create mode 100644 releasenotes/notes/bp-neutron-client-metering-6f8f9527c2a797fd.yaml diff --git a/doc/source/command-objects/network-meter-rule.rst b/doc/source/command-objects/network-meter-rule.rst index 83f8fd4fb4..22d50aa907 100644 --- a/doc/source/command-objects/network-meter-rule.rst +++ b/doc/source/command-objects/network-meter-rule.rst @@ -18,18 +18,16 @@ Create meter rule .. code:: bash openstack network meter rule create - [--project [--project-domain ]] + --remote-ip-prefix [--ingress | --egress] [--exclude | --include] - --remote-ip-prefix + [--project [--project-domain ]] .. option:: --project Owner's project (name or ID) - *Network version 2 only* - .. option:: --project-domain Domain the project belongs to (name of ID). diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index f18d091855..a5d22f81c2 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -117,11 +117,16 @@ Create a new server .. option:: --image - Create server from this image (name or ID) + Create server boot disk from this image (name or ID) .. option:: --volume - Create server from this volume (name or ID) + Create server using this volume as the boot disk (name or ID) + + This option automatically creates a block device mapping with a boot + index of 0. On many hypervisors (libvirt/kvm for example) this will + be device ``vda``. Do not create a duplicate mapping using + :option:`--block-device-mapping` for this volume. .. option:: --flavor diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index ccda1c519b..f9f0df4f2f 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -336,12 +336,15 @@ def get_parser(self, prog_name): disk_group.add_argument( '--image', metavar='', - help=_('Create server from this image (name or ID)'), + help=_('Create server boot disk from this image (name or ID)'), ) disk_group.add_argument( '--volume', metavar='', - help=_('Create server from this volume (name or ID)'), + help=_( + 'Create server using this volume as the boot disk ' + '(name or ID)' + ), ) parser.add_argument( '--flavor', diff --git a/releasenotes/notes/bp-neutron-client-metering-6f8f9527c2a797fd.yaml b/releasenotes/notes/bp-neutron-client-metering-6f8f9527c2a797fd.yaml new file mode 100644 index 0000000000..37ecdcb8de --- /dev/null +++ b/releasenotes/notes/bp-neutron-client-metering-6f8f9527c2a797fd.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Add meter rules commands for ``network meter rule create``, + ``network meter rule delete``, ``network meter rule list``, + and ``network meter rule show``. + [Blueprint :oscbp:`neutron-client-metering`] diff --git a/releasenotes/notes/support-no-property-in-volume-snapshot-0af3fcb31a3cfc2b.yaml b/releasenotes/notes/support-no-property-in-volume-snapshot-0af3fcb31a3cfc2b.yaml index 6a3220b248..42e06179b4 100644 --- a/releasenotes/notes/support-no-property-in-volume-snapshot-0af3fcb31a3cfc2b.yaml +++ b/releasenotes/notes/support-no-property-in-volume-snapshot-0af3fcb31a3cfc2b.yaml @@ -2,6 +2,4 @@ features: - | Add ``--no-property`` option in ``volume snapshot set``. - Supporting ``--no-property`` option will apply user a convenient way to - clean all properties of volume snapshot in a short command. - [ Blueprint `allow-overwrite-set-options ` _] + [Blueprint `allow-overwrite-set-options `_] From dd7837a5e2730d5962fc22894cbd92433951fa7d Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 26 Jan 2017 18:52:09 +0000 Subject: [PATCH 1526/3095] Updated from global requirements Change-Id: I7d572ca8c0f2608180343e2ec9d41378cf15474f --- requirements.txt | 2 +- test-requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index c537100674..422f782d86 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ six>=1.9.0 # MIT Babel>=2.3.4 # BSD cliff>=2.3.0 # Apache-2.0 keystoneauth1>=2.18.0 # Apache-2.0 -openstacksdk!=0.9.11,!=0.9.12,>=0.9.10 # Apache-2.0 +openstacksdk>=0.9.13 # Apache-2.0 osc-lib>=1.2.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 oslo.utils>=3.18.0 # Apache-2.0 diff --git a/test-requirements.txt b/test-requirements.txt index 032d191a36..707df3ca72 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -28,7 +28,7 @@ python-barbicanclient>=4.0.0 # Apache-2.0 python-congressclient<2000,>=1.3.0 # Apache-2.0 python-designateclient>=1.5.0 # Apache-2.0 python-heatclient>=1.6.1 # Apache-2.0 -python-ironicclient>=1.10.0 # Apache-2.0 +python-ironicclient>=1.11.0 # Apache-2.0 python-ironic-inspector-client>=1.5.0 # Apache-2.0 python-mistralclient>=2.0.0 # Apache-2.0 python-muranoclient>=0.8.2 # Apache-2.0 From f58ae51ee25df09f92d5705881371b55175b6153 Mon Sep 17 00:00:00 2001 From: Andreas Jaeger Date: Thu, 26 Jan 2017 19:52:31 +0100 Subject: [PATCH 1527/3095] Fix sphinx errors It's :option: instead of :options:, fix typo. Change-Id: I273d592f38db07fef7633fe833b1c0ab9e52e6a8 --- doc/source/command-objects/volume-backup.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/source/command-objects/volume-backup.rst b/doc/source/command-objects/volume-backup.rst index 5e41e69596..f2f814c5d3 100644 --- a/doc/source/command-objects/volume-backup.rst +++ b/doc/source/command-objects/volume-backup.rst @@ -101,26 +101,26 @@ List volume backups List additional fields in output -.. options:: --name +.. option:: --name Filters results by the backup name -.. options:: --status +.. option:: --status Filters results by the backup status ('creating', 'available', 'deleting', 'error', 'restoring' or 'error_restoring') -.. options:: --volume +.. option:: --volume Filters results by the volume which they backup (name or ID)" -.. options:: --marker +.. option:: --marker The last backup of the previous page (name or ID) *Volume version 2 only* -.. options:: --limit +.. option:: --limit Maximum number of backups to display From 4a12280999eed4f35e9e2d61a475ffc7c2b001b2 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 27 Jan 2017 19:26:34 -0600 Subject: [PATCH 1528/3095] Fix address scope list --share Remove the 'shared' key from the attrs passed in to the SDK with 0.9.13. Also convert the functional tests to the JSON-style (that's how I found this). Closes-bug: 1659993 Change-Id: I614fbce967cdd07fe7360242547dbf52e7677939 --- openstackclient/network/v2/address_scope.py | 2 - .../network/v2/test_address_scope.py | 135 ++++++++++-------- .../unit/network/v2/test_address_scope.py | 4 +- 3 files changed, 79 insertions(+), 62 deletions(-) diff --git a/openstackclient/network/v2/address_scope.py b/openstackclient/network/v2/address_scope.py index 9f77aed685..71c1a9afb7 100644 --- a/openstackclient/network/v2/address_scope.py +++ b/openstackclient/network/v2/address_scope.py @@ -204,10 +204,8 @@ def take_action(self, parsed_args): if parsed_args.ip_version: attrs['ip_version'] = parsed_args.ip_version if parsed_args.share: - attrs['shared'] = True attrs['is_shared'] = True if parsed_args.no_share: - attrs['shared'] = False attrs['is_shared'] = False if 'project' in parsed_args and parsed_args.project is not None: identity_client = self.app.client_manager.identity diff --git a/openstackclient/tests/functional/network/v2/test_address_scope.py b/openstackclient/tests/functional/network/v2/test_address_scope.py index 75f843445b..eaf88969ad 100644 --- a/openstackclient/tests/functional/network/v2/test_address_scope.py +++ b/openstackclient/tests/functional/network/v2/test_address_scope.py @@ -10,7 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. -import re +import json import uuid from openstackclient.tests.functional import base @@ -24,36 +24,31 @@ class AddressScopeTests(base.TestCase): # has its own needs and there are collisions when running # tests in parallel. - @classmethod - def setUpClass(cls): - # Set up some regex for matching below - cls.re_name = re.compile("name\s+\|\s+([^|]+?)\s+\|") - cls.re_ip_version = re.compile("ip_version\s+\|\s+(\S+)") - cls.re_shared = re.compile("shared\s+\|\s+(\S+)") - def test_address_scope_delete(self): """Test create, delete multiple""" name1 = uuid.uuid4().hex - raw_output = self.openstack( - 'address scope create ' + name1, - ) + cmd_output = json.loads(self.openstack( + 'address scope create -f json ' + + name1 + )) self.assertEqual( name1, - re.search(self.re_name, raw_output).group(1), + cmd_output['name'], ) # Check the default values self.assertEqual( - 'False', - re.search(self.re_shared, raw_output).group(1), + False, + cmd_output['shared'], ) name2 = uuid.uuid4().hex - raw_output = self.openstack( - 'address scope create ' + name2, - ) + cmd_output = json.loads(self.openstack( + 'address scope create -f json ' + + name2 + )) self.assertEqual( name2, - re.search(self.re_name, raw_output).group(1), + cmd_output['name'], ) raw_output = self.openstack( @@ -64,72 +59,93 @@ def test_address_scope_delete(self): def test_address_scope_list(self): """Test create defaults, list filters, delete""" name1 = uuid.uuid4().hex - raw_output = self.openstack( - 'address scope create --ip-version 4 --share ' + name1, - ) + cmd_output = json.loads(self.openstack( + 'address scope create -f json ' + + '--ip-version 4 ' + + '--share ' + + name1 + )) self.addCleanup(self.openstack, 'address scope delete ' + name1) self.assertEqual( - '4', - re.search(self.re_ip_version, raw_output).group(1), + name1, + cmd_output['name'], + ) + self.assertEqual( + 4, + cmd_output['ip_version'], ) self.assertEqual( - 'True', - re.search(self.re_shared, raw_output).group(1), + True, + cmd_output['shared'], ) name2 = uuid.uuid4().hex - raw_output = self.openstack( - 'address scope create --ip-version 6 --no-share ' + name2, - ) + cmd_output = json.loads(self.openstack( + 'address scope create -f json ' + + '--ip-version 6 ' + + '--no-share ' + + name2 + )) self.addCleanup(self.openstack, 'address scope delete ' + name2) self.assertEqual( - '6', - re.search(self.re_ip_version, raw_output).group(1), + name2, + cmd_output['name'], + ) + self.assertEqual( + 6, + cmd_output['ip_version'], ) self.assertEqual( - 'False', - re.search(self.re_shared, raw_output).group(1), + False, + cmd_output['shared'], ) # Test list - raw_output = self.openstack('address scope list') - self.assertIsNotNone(re.search(name1 + "\s+\|\s+4", raw_output)) - self.assertIsNotNone(re.search(name2 + "\s+\|\s+6", raw_output)) + cmd_output = json.loads(self.openstack( + 'address scope list -f json ', + )) + col_data = [x["IP Version"] for x in cmd_output] + self.assertIn(4, col_data) + self.assertIn(6, col_data) # Test list --share - # TODO(dtroyer): returns 'HttpException: Bad Request' - # raw_output = self.openstack('address scope list --share') - # self.assertIsNotNone(re.search(name1 + "\s+\|\s+4", raw_output)) - # self.assertIsNotNone(re.search(name2 + "\s+\|\s+6", raw_output)) + cmd_output = json.loads(self.openstack( + 'address scope list -f json --share', + )) + col_data = [x["Shared"] for x in cmd_output] + self.assertIn(True, col_data) + self.assertNotIn(False, col_data) # Test list --no-share - # TODO(dtroyer): returns 'HttpException: Bad Request' - # raw_output = self.openstack('address scope list --no-share') - # self.assertIsNotNone(re.search(name1 + "\s+\|\s+4", raw_output)) - # self.assertIsNotNone(re.search(name2 + "\s+\|\s+6", raw_output)) + cmd_output = json.loads(self.openstack( + 'address scope list -f json --no-share', + )) + col_data = [x["Shared"] for x in cmd_output] + self.assertIn(False, col_data) + self.assertNotIn(True, col_data) def test_address_scope_set(self): """Tests create options, set, show, delete""" name = uuid.uuid4().hex newname = name + "_" - raw_output = self.openstack( - 'address scope create ' + + cmd_output = json.loads(self.openstack( + 'address scope create -f json ' + '--ip-version 4 ' + '--no-share ' + - name, - ) + name + )) self.addCleanup(self.openstack, 'address scope delete ' + newname) self.assertEqual( name, - re.search(self.re_name, raw_output).group(1), + cmd_output['name'], ) self.assertEqual( - '4', - re.search(self.re_ip_version, raw_output).group(1), + 4, + cmd_output['ip_version'], ) self.assertEqual( - 'False', - re.search(self.re_shared, raw_output).group(1), + False, + cmd_output['shared'], ) raw_output = self.openstack( @@ -140,16 +156,19 @@ def test_address_scope_set(self): ) self.assertOutput('', raw_output) - raw_output = self.openstack('address scope show ' + newname) + cmd_output = json.loads(self.openstack( + 'address scope show -f json ' + + newname, + )) self.assertEqual( newname, - re.search(self.re_name, raw_output).group(1), + cmd_output['name'], ) self.assertEqual( - '4', - re.search(self.re_ip_version, raw_output).group(1), + 4, + cmd_output['ip_version'], ) self.assertEqual( - 'True', - re.search(self.re_shared, raw_output).group(1), + True, + cmd_output['shared'], ) diff --git a/openstackclient/tests/unit/network/v2/test_address_scope.py b/openstackclient/tests/unit/network/v2/test_address_scope.py index 516b879589..400671881f 100644 --- a/openstackclient/tests/unit/network/v2/test_address_scope.py +++ b/openstackclient/tests/unit/network/v2/test_address_scope.py @@ -352,7 +352,7 @@ def test_address_scope_list_share(self): columns, data = self.cmd.take_action(parsed_args) self.network.address_scopes.assert_called_once_with( - **{'shared': True, 'is_shared': True} + **{'is_shared': True} ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) @@ -368,7 +368,7 @@ def test_address_scope_list_no_share(self): columns, data = self.cmd.take_action(parsed_args) self.network.address_scopes.assert_called_once_with( - **{'shared': False, 'is_shared': False} + **{'is_shared': False} ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) From fab4fcc397ce8d0ea1c817d14e5787689d0ef7ac Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 27 Jan 2017 20:05:00 -0600 Subject: [PATCH 1529/3095] Fix network create --project SDK 0.9.13 needs to see project_id in the attributes, not tenant_id. Closes-bug: 1659878 Change-Id: Iff7abe8bd00cbe087c07579596c40af8b0a73302 --- openstackclient/network/v2/network.py | 2 ++ openstackclient/tests/unit/network/v2/test_network.py | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 17ce0f7fb4..655147d95f 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -93,7 +93,9 @@ def _get_attrs(client_manager, parsed_args): parsed_args.project, parsed_args.project_domain, ).id + # TODO(dtroyer): Remove tenant_id when we clean up the SDK refactor attrs['tenant_id'] = project_id + attrs['project_id'] = project_id # "network set" command doesn't support setting availability zone hints. if 'availability_zone_hints' in parsed_args and \ diff --git a/openstackclient/tests/unit/network/v2/test_network.py b/openstackclient/tests/unit/network/v2/test_network.py index c5283443e6..b405bef9a8 100644 --- a/openstackclient/tests/unit/network/v2/test_network.py +++ b/openstackclient/tests/unit/network/v2/test_network.py @@ -185,7 +185,9 @@ def test_create_all_options(self): 'name': self._network.name, 'shared': True, 'description': self._network.description, + # TODO(dtroyer): Remove tenant_id when we clean up the SDK refactor 'tenant_id': self.project.id, + 'project_id': self.project.id, 'is_default': True, 'router:external': True, 'provider:network_type': 'vlan', @@ -319,7 +321,9 @@ def test_create_with_project_identityv2(self): self.network.create_network.assert_called_once_with(**{ 'admin_state_up': True, 'name': self._network.name, + # TODO(dtroyer): Remove tenant_id when we clean up the SDK refactor 'tenant_id': self.project.id, + 'project_id': self.project.id, }) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) From 1e3dc48c64304eb378660ceb531aab3d42ac0710 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Sat, 28 Jan 2017 07:18:45 -0600 Subject: [PATCH 1530/3095] Add relnotes for the two recent bug fixes Also add a functional test for network create --project Change-Id: Idbfdf82f1ea6c84fb6a51df88e746e5ddb896b4f --- .../functional/network/v2/test_network.py | 72 +++++++++++++++++++ .../notes/bug-1659878-f6a55b7166d99ca8.yaml | 7 ++ .../notes/bug-1659993-a5fe43bef587e490.yaml | 6 ++ 3 files changed, 85 insertions(+) create mode 100644 releasenotes/notes/bug-1659878-f6a55b7166d99ca8.yaml create mode 100644 releasenotes/notes/bug-1659993-a5fe43bef587e490.yaml diff --git a/openstackclient/tests/functional/network/v2/test_network.py b/openstackclient/tests/functional/network/v2/test_network.py index c55d70f9af..1f7b7c9e6e 100644 --- a/openstackclient/tests/functional/network/v2/test_network.py +++ b/openstackclient/tests/functional/network/v2/test_network.py @@ -19,6 +19,78 @@ class NetworkTests(base.TestCase): """Functional tests for network""" + def test_network_create(self): + """Test create options, delete""" + # Get project IDs + cmd_output = json.loads(self.openstack('token issue -f json ')) + auth_project_id = cmd_output['project_id'] + + cmd_output = json.loads(self.openstack('project list -f json ')) + admin_project_id = None + demo_project_id = None + for p in cmd_output: + if p['Name'] == 'admin': + admin_project_id = p['ID'] + if p['Name'] == 'demo': + demo_project_id = p['ID'] + + # Verify assumptions: + # * admin and demo projects are present + # * demo and admin are distinct projects + # * tests run as admin + self.assertIsNotNone(admin_project_id) + self.assertIsNotNone(demo_project_id) + self.assertNotEqual(admin_project_id, demo_project_id) + self.assertEqual(admin_project_id, auth_project_id) + + # network create with no options + name1 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'network create -f json ' + + name1 + )) + self.addCleanup(self.openstack, 'network delete ' + name1) + self.assertIsNotNone(cmd_output["id"]) + + # Check the default values + self.assertEqual( + admin_project_id, + cmd_output["project_id"], + ) + self.assertEqual( + '', + cmd_output["description"], + ) + self.assertEqual( + 'UP', + cmd_output["admin_state_up"], + ) + self.assertEqual( + False, + cmd_output["shared"], + ) + self.assertEqual( + 'Internal', + cmd_output["router:external"], + ) + + name2 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'network create -f json ' + + '--project demo ' + + name2 + )) + self.addCleanup(self.openstack, 'network delete ' + name2) + self.assertIsNotNone(cmd_output["id"]) + self.assertEqual( + demo_project_id, + cmd_output["project_id"], + ) + self.assertEqual( + '', + cmd_output["description"], + ) + def test_network_delete(self): """Test create, delete multiple""" name1 = uuid.uuid4().hex diff --git a/releasenotes/notes/bug-1659878-f6a55b7166d99ca8.yaml b/releasenotes/notes/bug-1659878-f6a55b7166d99ca8.yaml new file mode 100644 index 0000000000..f7932a6688 --- /dev/null +++ b/releasenotes/notes/bug-1659878-f6a55b7166d99ca8.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + The ``network create`` command was ignoring the ``--project`` option and + creating networks owned by the current authenticated user's project. This + was a regression introduced in OSC 3.8.0. + [Bug `1659878 `_] diff --git a/releasenotes/notes/bug-1659993-a5fe43bef587e490.yaml b/releasenotes/notes/bug-1659993-a5fe43bef587e490.yaml new file mode 100644 index 0000000000..db2349b510 --- /dev/null +++ b/releasenotes/notes/bug-1659993-a5fe43bef587e490.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + The ``address scope list`` command failed with 'HttpException: Bad Request' + when the ``--share`` or ``--no-share`` options were used. + [Bug `1659993 `_] From 45c097c0b9e17d049d8e70ddc1ab0c571b4bb929 Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Tue, 31 Jan 2017 23:36:54 +0000 Subject: [PATCH 1531/3095] Update reno for stable/ocata Change-Id: I91d3a23f5e449efbfe350ad633135b4d725fbbe6 --- releasenotes/source/index.rst | 1 + releasenotes/source/ocata.rst | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 releasenotes/source/ocata.rst diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index 0941837950..6d55306751 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,6 +6,7 @@ OpenStackClient Release Notes :maxdepth: 1 unreleased + ocata newton 20_releases pre_20_releases diff --git a/releasenotes/source/ocata.rst b/releasenotes/source/ocata.rst new file mode 100644 index 0000000000..ebe62f42e1 --- /dev/null +++ b/releasenotes/source/ocata.rst @@ -0,0 +1,6 @@ +=================================== + Ocata Series Release Notes +=================================== + +.. release-notes:: + :branch: origin/stable/ocata From 419668d17827a923438cef9523666c655c50616f Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 1 Feb 2017 14:25:07 -0600 Subject: [PATCH 1532/3095] Add mitaka release notes to master Change-Id: I00b3abe0ac8765989a83e491f1dc53db22283bdd --- releasenotes/source/index.rst | 1 + releasenotes/source/mitaka.rst | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 releasenotes/source/mitaka.rst diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index 0941837950..a443b77c83 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -7,6 +7,7 @@ OpenStackClient Release Notes unreleased newton + mitaka 20_releases pre_20_releases diff --git a/releasenotes/source/mitaka.rst b/releasenotes/source/mitaka.rst new file mode 100644 index 0000000000..0dc585c85f --- /dev/null +++ b/releasenotes/source/mitaka.rst @@ -0,0 +1,6 @@ +============================= + Mitaka Series Release Notes +============================= + +.. release-notes:: + :branch: origin/stable/mitaka From eea09faae24a128d63742b98ac081cd38beaa834 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 1 Feb 2017 17:31:34 -0600 Subject: [PATCH 1533/3095] TODO cleanup: OSC_Config os-client-config is now at 1.22.0 in global-requirements, we can remove this pw_func setting block as the pw_func arg to __init__() was added in 1.21.0. Change-Id: I5bbc3e3aae4f3e4c4333c73bba19bda65e0d8488 --- openstackclient/common/client_config.py | 34 ------------------------- 1 file changed, 34 deletions(-) diff --git a/openstackclient/common/client_config.py b/openstackclient/common/client_config.py index ad86e9f878..ad03748109 100644 --- a/openstackclient/common/client_config.py +++ b/openstackclient/common/client_config.py @@ -26,40 +26,6 @@ # before auth plugins are loaded class OSC_Config(client_config.OSC_Config): - # TODO(dtroyer): Once os-client-config with pw_func argument is in - # global-requirements we can remove __init()__ - def __init__( - self, - config_files=None, - vendor_files=None, - override_defaults=None, - force_ipv4=None, - envvar_prefix=None, - secure_files=None, - pw_func=None, - ): - ret = super(OSC_Config, self).__init__( - config_files=config_files, - vendor_files=vendor_files, - override_defaults=override_defaults, - force_ipv4=force_ipv4, - envvar_prefix=envvar_prefix, - secure_files=secure_files, - ) - - # NOTE(dtroyer): This will be pushed down into os-client-config - # The default is there is no callback, the calling - # application must specify what to use, typically - # it will be osc_lib.shell.prompt_for_password() - if '_pw_callback' not in vars(self): - # Set the default if it doesn't already exist - self._pw_callback = None - if pw_func is not None: - # Set the passed in value - self._pw_callback = pw_func - - return ret - # TODO(dtroyer): Remove _auth_default_domain when the v3otp fix is # backported to osc-lib, should be in release 1.3.0 def _auth_default_domain(self, config): From f8e8ace88abeb8f10f12805732688ff76f5de61c Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Thu, 2 Feb 2017 11:51:47 +0800 Subject: [PATCH 1534/3095] SDK refactor: Set "is_admin_state_up" for network agent OpenStackSDK is >=0.9.13 now in requirement, so we can update "is_admin_state_up" as well for the SDK refactor Change-Id: I02de0ebc752ce602032bbe9d73256ed376993e78 --- openstackclient/network/v2/network_agent.py | 4 ++-- openstackclient/tests/unit/network/v2/test_network_agent.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openstackclient/network/v2/network_agent.py b/openstackclient/network/v2/network_agent.py index d429fa0830..66a024ccbb 100644 --- a/openstackclient/network/v2/network_agent.py +++ b/openstackclient/network/v2/network_agent.py @@ -186,11 +186,11 @@ def take_action(self, parsed_args): attrs = {} if parsed_args.description is not None: attrs['description'] = str(parsed_args.description) - # TODO(huanxuan): Also update by the new attribute name - # "is_admin_state_up" after sdk 0.9.12 if parsed_args.enable: + attrs['is_admin_state_up'] = True attrs['admin_state_up'] = True if parsed_args.disable: + attrs['is_admin_state_up'] = False attrs['admin_state_up'] = False client.update_agent(obj, **attrs) diff --git a/openstackclient/tests/unit/network/v2/test_network_agent.py b/openstackclient/tests/unit/network/v2/test_network_agent.py index 2fc0c04328..650aba3efa 100644 --- a/openstackclient/tests/unit/network/v2/test_network_agent.py +++ b/openstackclient/tests/unit/network/v2/test_network_agent.py @@ -195,8 +195,6 @@ def test_network_agents_list_host(self): self.assertEqual(self.data, list(data)) -# TODO(huanxuan): Also update by the new attribute name -# "is_admin_state_up" after sdk 0.9.12 class TestSetNetworkAgent(TestNetworkAgent): _network_agent = ( @@ -245,6 +243,7 @@ def test_set_all(self): attrs = { 'description': 'new_description', 'admin_state_up': True, + 'is_admin_state_up': True, } self.network.update_agent.assert_called_once_with( self._network_agent, **attrs) @@ -266,6 +265,7 @@ def test_set_with_disable(self): attrs = { 'admin_state_up': False, + 'is_admin_state_up': False, } self.network.update_agent.assert_called_once_with( self._network_agent, **attrs) From 43e37f9024a3ecf4c9e2f4602b85a19cee10b65b Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Thu, 2 Feb 2017 21:30:52 -0500 Subject: [PATCH 1535/3095] mention the final ocata osc version in releasenotes we neglected to add the final osc version in our release note page, it should be 3.8.1 Change-Id: I4291a09924ce8beb29c8c3aff18962a43f0241b0 --- releasenotes/source/index.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index 46320fa050..1dbd1129f1 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -23,6 +23,7 @@ OpenStack release was made is shown below: ================= ======================= OpenStack Release OpenStackClient Release ================= ======================= +Ocata 3.8.1 Newton 3.2.0 Mitaka 2.3.0 Liberty 1.7.3 From 65ac88756466c87088e8238e24892621be4c6e12 Mon Sep 17 00:00:00 2001 From: Ankur Gupta Date: Fri, 3 Feb 2017 13:51:17 -0600 Subject: [PATCH 1536/3095] NIT: replace os with openstack in command example Change-Id: Iac1b0ec3b715ffa0eb41fc2fe9020781d834a7a3 --- doc/source/command-objects/consistency-group.rst | 4 ++-- doc/source/command-objects/network-qos-rule-type.rst | 2 +- doc/source/command-objects/network-qos-rule.rst | 10 +++++----- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/source/command-objects/consistency-group.rst b/doc/source/command-objects/consistency-group.rst index e54ac65fd3..232cf8e4fb 100644 --- a/doc/source/command-objects/consistency-group.rst +++ b/doc/source/command-objects/consistency-group.rst @@ -12,7 +12,7 @@ Add volume(s) to consistency group. .. program:: consistency group add volume .. code:: bash - os consistency group add volume + openstack consistency group add volume [ ...] @@ -116,7 +116,7 @@ Remove volume(s) from consistency group. .. program:: consistency group remove volume .. code:: bash - os consistency group remove volume + openstack consistency group remove volume [ ...] diff --git a/doc/source/command-objects/network-qos-rule-type.rst b/doc/source/command-objects/network-qos-rule-type.rst index 020a05cd5f..ee53e30b41 100644 --- a/doc/source/command-objects/network-qos-rule-type.rst +++ b/doc/source/command-objects/network-qos-rule-type.rst @@ -15,4 +15,4 @@ List Network QoS rule types .. program:: network qos rule type list .. code:: bash - os network qos rule type list + openstack network qos rule type list diff --git a/doc/source/command-objects/network-qos-rule.rst b/doc/source/command-objects/network-qos-rule.rst index b98244b1c3..1baf5dbfeb 100644 --- a/doc/source/command-objects/network-qos-rule.rst +++ b/doc/source/command-objects/network-qos-rule.rst @@ -17,7 +17,7 @@ Create new Network QoS rule .. program:: network qos rule create .. code:: bash - os network qos rule create + openstack network qos rule create --type [--max-kbps ] [--max-burst-kbits ] @@ -67,7 +67,7 @@ Delete Network QoS rule .. program:: network qos rule delete .. code:: bash - os network qos rule delete + openstack network qos rule delete @@ -87,7 +87,7 @@ List Network QoS rules .. program:: network qos rule list .. code:: bash - os network qos rule list + openstack network qos rule list .. describe:: @@ -102,7 +102,7 @@ Set Network QoS rule properties .. program:: network qos rule set .. code:: bash - os network qos rule set + openstack network qos rule set [--max-kbps ] [--max-burst-kbits ] [--dscp-marks ] @@ -152,7 +152,7 @@ Display Network QoS rule details .. program:: network qos rule show .. code:: bash - os network qos rule show + openstack network qos rule show From 3afd2b7ff25af7e7998e9c8f4adac8a58a079675 Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Sat, 4 Feb 2017 17:01:21 +0800 Subject: [PATCH 1537/3095] Fix "module list --all" failed KeyError cause the command "module list --all" failed, fix it, and do refactor to filter private modules and reduce the loop times, add related unit tests and functional tests. Change-Id: Icd77739502e05b5f763a04a92547497bf82d5d63 Closes-Bug: #1661814 --- openstackclient/common/module.py | 26 ++++++----- .../tests/functional/common/test_module.py | 40 +++++++++++++++++ .../tests/unit/common/test_module.py | 43 +++++++++++++++---- .../notes/bug-1661814-1692e68a1d2d9770.yaml | 6 +++ 4 files changed, 96 insertions(+), 19 deletions(-) create mode 100644 openstackclient/tests/functional/common/test_module.py create mode 100644 releasenotes/notes/bug-1661814-1692e68a1d2d9770.yaml diff --git a/openstackclient/common/module.py b/openstackclient/common/module.py index 15719a30e8..f471b2aa02 100644 --- a/openstackclient/common/module.py +++ b/openstackclient/common/module.py @@ -74,15 +74,21 @@ def take_action(self, parsed_args): mods = sys.modules for k in mods.keys(): k = k.split('.')[0] - # TODO(dtroyer): Need a better way to decide which modules to - # show for the default (not --all) invocation. - # It should be just the things we actually care - # about like client and plugin modules... - if (parsed_args.all or 'client' in k): - try: - data[k] = mods[k].__version__ - except AttributeError: - # aw, just skip it - pass + # Skip private modules and the modules that had been added, + # like: keystoneclient, keystoneclient.exceptions and + # keystoneclient.auth + if not k.startswith('_') and k not in data: + # TODO(dtroyer): Need a better way to decide which modules to + # show for the default (not --all) invocation. + # It should be just the things we actually care + # about like client and plugin modules... + if (parsed_args.all or + # Handle xxxclient and openstacksdk + (k.endswith('client') or k == 'openstack')): + try: + data[k] = mods[k].__version__ + except Exception: + # Catch all exceptions, just skip it + pass return zip(*sorted(six.iteritems(data))) diff --git a/openstackclient/tests/functional/common/test_module.py b/openstackclient/tests/functional/common/test_module.py new file mode 100644 index 0000000000..f56c1627bb --- /dev/null +++ b/openstackclient/tests/functional/common/test_module.py @@ -0,0 +1,40 @@ +# Copyright 2017 Huawei, Inc. 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 json + +from openstackclient.tests.functional import base + + +class ModuleTest(base.TestCase): + """Functional tests for openstackclient module list output.""" + + CLIENTS = ['openstackclient', + 'keystoneclient', + 'novaclient'] + + LIBS = ['osc_lib', + 'os_client_config', + 'keystoneauth1'] + + def test_module_list_no_options(self): + json_output = json.loads(self.openstack('module list -f json')) + for one_module in self.CLIENTS: + self.assertIn(one_module, json_output.keys()) + + def test_module_list_with_all_option(self): + json_output = json.loads(self.openstack('module list --all -f json')) + for one_module in (self.CLIENTS + self.LIBS): + self.assertIn(one_module, json_output.keys()) diff --git a/openstackclient/tests/unit/common/test_module.py b/openstackclient/tests/unit/common/test_module.py index eb54dbe04b..4b586d3b2d 100644 --- a/openstackclient/tests/unit/common/test_module.py +++ b/openstackclient/tests/unit/common/test_module.py @@ -26,19 +26,28 @@ # currently == '*client*' module_name_1 = 'fakeclient' module_version_1 = '0.1.2' -MODULE_1 = { - '__version__': module_version_1, -} module_name_2 = 'zlib' module_version_2 = '1.1' -MODULE_2 = { - '__version__': module_version_2, -} + +# module_3 match openstacksdk +module_name_3 = 'openstack' +module_version_3 = '0.9.13' + +# module_4 match sub module of fakeclient +module_name_4 = 'fakeclient.submodule' +module_version_4 = '0.2.2' + +# module_5 match private module +module_name_5 = '_private_module.lib' +module_version_5 = '0.0.1' MODULES = { module_name_1: fakes.FakeModule(module_name_1, module_version_1), module_name_2: fakes.FakeModule(module_name_2, module_version_2), + module_name_3: fakes.FakeModule(module_name_3, module_version_3), + module_name_4: fakes.FakeModule(module_name_4, module_version_4), + module_name_5: fakes.FakeModule(module_name_5, module_version_5), } @@ -105,9 +114,18 @@ def test_module_list_no_options(self): # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - # Additional modules may be present, just check our additions + # Output xxxclient and openstacksdk, but not regular module, like: zlib self.assertIn(module_name_1, columns) self.assertIn(module_version_1, data) + self.assertNotIn(module_name_2, columns) + self.assertNotIn(module_version_2, data) + self.assertIn(module_name_3, columns) + self.assertIn(module_version_3, data) + # Filter sub and private modules + self.assertNotIn(module_name_4, columns) + self.assertNotIn(module_version_4, data) + self.assertNotIn(module_name_5, columns) + self.assertNotIn(module_version_5, data) def test_module_list_all(self): arglist = [ @@ -123,8 +141,15 @@ def test_module_list_all(self): # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - # Additional modules may be present, just check our additions + # Output xxxclient, openstacksdk and regular module, like: zlib self.assertIn(module_name_1, columns) - self.assertIn(module_name_2, columns) self.assertIn(module_version_1, data) + self.assertIn(module_name_2, columns) self.assertIn(module_version_2, data) + self.assertIn(module_name_3, columns) + self.assertIn(module_version_3, data) + # Filter sub and private modules + self.assertNotIn(module_name_4, columns) + self.assertNotIn(module_version_4, data) + self.assertNotIn(module_name_5, columns) + self.assertNotIn(module_version_5, data) diff --git a/releasenotes/notes/bug-1661814-1692e68a1d2d9770.yaml b/releasenotes/notes/bug-1661814-1692e68a1d2d9770.yaml new file mode 100644 index 0000000000..b321138744 --- /dev/null +++ b/releasenotes/notes/bug-1661814-1692e68a1d2d9770.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Fix ``module list --all`` command failed, and enhance the related unit + tests and funcational tests. + [Bug `1661814 `_] From e8b6a9f7be7e773396c8fe1021d8798aa0e2a4a9 Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Mon, 6 Feb 2017 18:49:40 +0800 Subject: [PATCH 1538/3095] Fix wrong behavior of parsing plugin service name When the service name end with keyword "os", like: antiddos, the parsing logic isn't suitable, that cause the service api version specified by users don't work. Change-Id: I5d6217c77d7cd2d2f360d78d8561261398b96685 Closes-Bug: #1658614 --- openstackclient/shell.py | 4 +++- releasenotes/notes/bug-1658614-f84a8cece6f2ef8c.yaml | 7 +++++++ 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/bug-1658614-f84a8cece6f2ef8c.yaml diff --git a/openstackclient/shell.py b/openstackclient/shell.py index e08eee61df..d7fe6ac193 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -76,7 +76,9 @@ def _load_plugins(self): # Loop through extensions to get API versions for mod in clientmanager.PLUGIN_MODULES: default_version = getattr(mod, 'DEFAULT_API_VERSION', None) - option = mod.API_VERSION_OPTION.replace('os_', '') + # Only replace the first instance of "os", some service names will + # have "os" in their name, like: "antiddos" + option = mod.API_VERSION_OPTION.replace('os_', '', 1) version_opt = str(self.cloud.config.get(option, default_version)) if version_opt: api = mod.API_NAME diff --git a/releasenotes/notes/bug-1658614-f84a8cece6f2ef8c.yaml b/releasenotes/notes/bug-1658614-f84a8cece6f2ef8c.yaml new file mode 100644 index 0000000000..b69e384d72 --- /dev/null +++ b/releasenotes/notes/bug-1658614-f84a8cece6f2ef8c.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + Fix wrong behavior of parsing plugin service name when the service name end + with keyword ``os``, like: antiddos. That cause the service api version + specified by users don't work. + [Bug `1658614 `_] From e0e46bca093984926de11559e312d1f1fcade108 Mon Sep 17 00:00:00 2001 From: Yan Xing'an Date: Wed, 19 Oct 2016 02:21:41 -0700 Subject: [PATCH 1539/3095] Add --fixed-ip option to the port list command Add support to allow filtering ports via --fixed-ip option to the port list command. Change-Id: I2f728368d3046b2e6feadd0848bf6f8680e31aba Partial-bug: #1634799 Partially-Implements: blueprint network-commands-options --- doc/source/command-objects/port.rst | 7 ++ openstackclient/network/v2/port.py | 35 ++++++++ .../tests/unit/network/v2/fakes.py | 3 +- .../tests/unit/network/v2/test_port.py | 87 +++++++++++++++++++ .../notes/bug-1634799-1322227c9b0188ca.yaml | 5 ++ 5 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/bug-1634799-1322227c9b0188ca.yaml diff --git a/doc/source/command-objects/port.rst b/doc/source/command-objects/port.rst index 201a889e8c..5080addb64 100644 --- a/doc/source/command-objects/port.rst +++ b/doc/source/command-objects/port.rst @@ -155,6 +155,7 @@ List ports [--router | --server ] [--network ] [--mac-address ] + [--fixed-ip subnet=,ip-address=] [--long] [--project [--project-domain ]] @@ -179,6 +180,12 @@ List ports List only ports with this MAC address +.. option:: --fixed-ip subnet=,ip-address= + + Desired IP and/or subnet (name or ID) for filtering ports: + subnet=,ip-address= + (repeat option to set multiple fixed IP addresses) + .. option:: --long List additional fields in output diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index c9de47e2ee..7283dc0706 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -194,6 +194,29 @@ def _prepare_fixed_ips(client_manager, parsed_args): parsed_args.fixed_ip = ips +def _prepare_filter_fixed_ips(client_manager, parsed_args): + """Fix and properly format fixed_ip option for filtering. + + Appropriately convert any subnet names to their respective ids. + Convert fixed_ips in parsed args to be in valid list format for filter: + ['subnet_id=foo']. + """ + client = client_manager.network + ips = [] + + for ip_spec in parsed_args.fixed_ip: + if 'subnet' in ip_spec: + subnet_name_id = ip_spec['subnet'] + if subnet_name_id: + _subnet = client.find_subnet(subnet_name_id, + ignore_missing=False) + ips.append('subnet_id=%s' % _subnet.id) + + if 'ip-address' in ip_spec: + ips.append('ip_address=%s' % ip_spec['ip-address']) + return ips + + def _add_updatable_args(parser): parser.add_argument( '--description', @@ -466,6 +489,15 @@ def get_parser(self, prog_name): help=_("List ports according to their project (name or ID)") ) identity_common.add_project_domain_option_to_parser(parser) + parser.add_argument( + '--fixed-ip', + metavar='subnet=,ip-address=', + action=parseractions.MultiKeyValueAction, + optional_keys=['subnet', 'ip-address'], + help=_("Desired IP and/or subnet (name or ID) for filtering " + "ports: subnet=,ip-address= " + "(repeat option to set multiple fixed IP addresses)") + ) return parser def take_action(self, parsed_args): @@ -516,6 +548,9 @@ def take_action(self, parsed_args): ).id filters['tenant_id'] = project_id filters['project_id'] = project_id + if parsed_args.fixed_ip: + filters['fixed_ips'] = _prepare_filter_fixed_ips( + self.app.client_manager, parsed_args) data = network_client.ports(**filters) diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index eb96533934..4b266efb42 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -470,7 +470,8 @@ def create_one_port(attrs=None): 'dns_assignment': [{}], 'dns_name': 'dns-name-' + uuid.uuid4().hex, 'extra_dhcp_opts': [{}], - 'fixed_ips': [{}], + 'fixed_ips': [{'ip_address': '10.0.0.3', + 'subnet_id': 'subnet-id-' + uuid.uuid4().hex}], 'id': 'port-id-' + uuid.uuid4().hex, 'mac_address': 'fa:16:3e:a9:4e:72', 'name': 'port-name-' + uuid.uuid4().hex, diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index fc626685ed..bfffc5c062 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -727,6 +727,92 @@ def test_port_list_mac_address_opt(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + def test_port_list_fixed_ip_opt_ip_address(self): + ip_address = self._ports[0].fixed_ips[0]['ip_address'] + arglist = [ + '--fixed-ip', "ip-address=%s" % ip_address, + ] + verifylist = [ + ('fixed_ip', [{'ip-address': ip_address}]) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.ports.assert_called_once_with(**{ + 'fixed_ips': ['ip_address=%s' % ip_address]}) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_port_list_fixed_ip_opt_subnet_id(self): + subnet_id = self._ports[0].fixed_ips[0]['subnet_id'] + arglist = [ + '--fixed-ip', "subnet=%s" % subnet_id, + ] + verifylist = [ + ('fixed_ip', [{'subnet': subnet_id}]) + ] + + self.fake_subnet = network_fakes.FakeSubnet.create_one_subnet( + {'id': subnet_id}) + self.network.find_subnet = mock.Mock(return_value=self.fake_subnet) + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.ports.assert_called_once_with(**{ + 'fixed_ips': ['subnet_id=%s' % subnet_id]}) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_port_list_fixed_ip_opts(self): + subnet_id = self._ports[0].fixed_ips[0]['subnet_id'] + ip_address = self._ports[0].fixed_ips[0]['ip_address'] + arglist = [ + '--fixed-ip', "subnet=%s,ip-address=%s" % (subnet_id, + ip_address) + ] + verifylist = [ + ('fixed_ip', [{'subnet': subnet_id, + 'ip-address': ip_address}]) + ] + + self.fake_subnet = network_fakes.FakeSubnet.create_one_subnet( + {'id': subnet_id}) + self.network.find_subnet = mock.Mock(return_value=self.fake_subnet) + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.ports.assert_called_once_with(**{ + 'fixed_ips': ['subnet_id=%s' % subnet_id, + 'ip_address=%s' % ip_address]}) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_port_list_fixed_ips(self): + subnet_id = self._ports[0].fixed_ips[0]['subnet_id'] + ip_address = self._ports[0].fixed_ips[0]['ip_address'] + arglist = [ + '--fixed-ip', "subnet=%s" % subnet_id, + '--fixed-ip', "ip-address=%s" % ip_address, + ] + verifylist = [ + ('fixed_ip', [{'subnet': subnet_id}, + {'ip-address': ip_address}]) + ] + + self.fake_subnet = network_fakes.FakeSubnet.create_one_subnet( + {'id': subnet_id}) + self.network.find_subnet = mock.Mock(return_value=self.fake_subnet) + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.ports.assert_called_once_with(**{ + 'fixed_ips': ['subnet_id=%s' % subnet_id, + 'ip_address=%s' % ip_address]}) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + def test_list_port_with_long(self): arglist = [ '--long', @@ -801,6 +887,7 @@ def test_set_fixed_ip(self): arglist = [ '--fixed-ip', 'ip-address=10.0.0.11', self._port.name, + '--no-fixed-ip', ] verifylist = [ ('fixed_ip', [{'ip-address': '10.0.0.11'}]), diff --git a/releasenotes/notes/bug-1634799-1322227c9b0188ca.yaml b/releasenotes/notes/bug-1634799-1322227c9b0188ca.yaml new file mode 100644 index 0000000000..414710d8bf --- /dev/null +++ b/releasenotes/notes/bug-1634799-1322227c9b0188ca.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``--fixed-ip`` option to the ``port list`` command. + [Bug `1634799 `_] From 4679a4c1933da0b94efb5d3d453d119801289a97 Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Thu, 9 Feb 2017 17:25:36 +0800 Subject: [PATCH 1540/3095] Fix --parents and --children options in project show Options "--parents" and "--children" don't work in "project show" command, fix the issue and add related unit and functional tests. Change-Id: Id9965267a037442f1077f8e1929d0527981f643d Closes-Bug: #1499657 --- openstackclient/identity/v3/project.py | 13 ++++-- .../functional/identity/v3/test_project.py | 13 ++++++ .../tests/unit/identity/v3/test_project.py | 46 ++++++++----------- .../notes/bug-1499657-eeb260849febacf3.yaml | 6 +++ 4 files changed, 46 insertions(+), 32 deletions(-) create mode 100644 releasenotes/notes/bug-1499657-eeb260849febacf3.yaml diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index 12197cdde1..43eca2b525 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -336,13 +336,18 @@ def take_action(self, parsed_args): project = utils.find_resource( identity_client.projects, project_str, - domain_id=domain.id, - parents_as_list=parsed_args.parents, - subtree_as_list=parsed_args.children) + domain_id=domain.id) else: project = utils.find_resource( identity_client.projects, - project_str, + project_str) + + if parsed_args.parents or parsed_args.children: + # NOTE(RuiChen): utils.find_resource() can't pass kwargs, + # if id query hit the result at first, so call + # identity manager.get() with kwargs directly. + project = identity_client.projects.get( + project.id, parents_as_list=parsed_args.parents, subtree_as_list=parsed_args.children) diff --git a/openstackclient/tests/functional/identity/v3/test_project.py b/openstackclient/tests/functional/identity/v3/test_project.py index 5639dc167c..96d41c3a14 100644 --- a/openstackclient/tests/functional/identity/v3/test_project.py +++ b/openstackclient/tests/functional/identity/v3/test_project.py @@ -10,6 +10,8 @@ # License for the specific language governing permissions and limitations # under the License. +import json + from tempest.lib.common.utils import data_utils from openstackclient.tests.functional.identity.v3 import common @@ -111,3 +113,14 @@ def test_project_show(self): 'name': self.project_name}) items = self.parse_show(raw_output) self.assert_show_fields(items, self.PROJECT_FIELDS) + + def test_project_show_with_parents_children(self): + json_output = json.loads(self.openstack( + 'project show ' + '--parents --children -f json ' + '--domain %(domain)s ' + '%(name)s' % {'domain': self.domain_name, + 'name': self.project_name})) + for attr_name in (self.PROJECT_FIELDS + ['parents', 'subtree']): + self.assertIn(attr_name, json_output) + self.assertEqual(self.project_name, json_output.get('name')) diff --git a/openstackclient/tests/unit/identity/v3/test_project.py b/openstackclient/tests/unit/identity/v3/test_project.py index 2b89809004..b99eaf8506 100644 --- a/openstackclient/tests/unit/identity/v3/test_project.py +++ b/openstackclient/tests/unit/identity/v3/test_project.py @@ -14,6 +14,7 @@ # import mock +from mock import call from osc_lib import exceptions from osc_lib import utils @@ -763,8 +764,6 @@ def setUp(self): def test_project_show(self): - self.projects_mock.get.side_effect = [Exception("Not found"), - self.project] self.projects_mock.get.return_value = self.project arglist = [ @@ -790,11 +789,7 @@ def test_project_show(self): # data to be shown. columns, data = self.cmd.take_action(parsed_args) - self.projects_mock.get.assert_called_with( - self.project.id, - parents_as_list=False, - subtree_as_list=False, - ) + self.projects_mock.get.assert_called_once_with(self.project.id) collist = ( 'description', @@ -824,8 +819,6 @@ def test_project_show_parents(self): 'parents': [{'project': {'id': self.project.parent_id}}] } ) - self.projects_mock.get.side_effect = [Exception("Not found"), - self.project] self.projects_mock.get.return_value = self.project arglist = [ @@ -849,11 +842,12 @@ def test_project_show_parents(self): } columns, data = self.cmd.take_action(parsed_args) - self.projects_mock.get.assert_called_with( - self.project.id, - parents_as_list=True, - subtree_as_list=False, - ) + + self.projects_mock.get.assert_has_calls([call(self.project.id), + call(self.project.id, + parents_as_list=True, + subtree_as_list=False, + )]) collist = ( 'description', @@ -885,8 +879,6 @@ def test_project_show_subtree(self): 'subtree': [{'project': {'id': 'children-id'}}] } ) - self.projects_mock.get.side_effect = [Exception("Not found"), - self.project] self.projects_mock.get.return_value = self.project arglist = [ @@ -910,11 +902,11 @@ def test_project_show_subtree(self): } columns, data = self.cmd.take_action(parsed_args) - self.projects_mock.get.assert_called_with( - self.project.id, - parents_as_list=False, - subtree_as_list=True, - ) + self.projects_mock.get.assert_has_calls([call(self.project.id), + call(self.project.id, + parents_as_list=False, + subtree_as_list=True, + )]) collist = ( 'description', @@ -947,8 +939,6 @@ def test_project_show_parents_and_children(self): 'subtree': [{'project': {'id': 'children-id'}}] } ) - self.projects_mock.get.side_effect = [Exception("Not found"), - self.project] self.projects_mock.get.return_value = self.project arglist = [ @@ -973,11 +963,11 @@ def test_project_show_parents_and_children(self): } columns, data = self.cmd.take_action(parsed_args) - self.projects_mock.get.assert_called_with( - self.project.id, - parents_as_list=True, - subtree_as_list=True, - ) + self.projects_mock.get.assert_has_calls([call(self.project.id), + call(self.project.id, + parents_as_list=True, + subtree_as_list=True, + )]) collist = ( 'description', diff --git a/releasenotes/notes/bug-1499657-eeb260849febacf3.yaml b/releasenotes/notes/bug-1499657-eeb260849febacf3.yaml new file mode 100644 index 0000000000..73af129b10 --- /dev/null +++ b/releasenotes/notes/bug-1499657-eeb260849febacf3.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Options ``--parents`` and ``--children`` don't work in ``project show`` + command, fix the issue. + [Bug `1499657 `_] From cfd4e2a7228c1e7f6ad677f2dd6dbd09e638dfb7 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Thu, 9 Feb 2017 18:05:32 +0800 Subject: [PATCH 1541/3095] Modify error handling for role and group commands if command failed, we usually raise exception, if command success, sometimes there is not any output (such as set, add commands) So modify the error handling for role and group commands. Change-Id: I1c0f86c04dcedd9c0d725fd73f3436be9da75ee0 --- openstackclient/identity/v3/group.py | 26 ++++------- openstackclient/identity/v3/role.py | 29 +++++-------- .../functional/identity/v3/test_group.py | 25 ++--------- .../tests/unit/identity/v3/test_group.py | 29 +++++++++++++ .../tests/unit/identity/v3/test_role.py | 43 +++++++++++++++++++ 5 files changed, 94 insertions(+), 58 deletions(-) diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py index a03a86ebd2..2afdabc171 100644 --- a/openstackclient/identity/v3/group.py +++ b/openstackclient/identity/v3/group.py @@ -62,18 +62,13 @@ def take_action(self, parsed_args): try: identity_client.users.add_to_group(user_id, group_id) - except Exception: - msg = _("%(user)s not added to group %(group)s\n") % { - 'user': parsed_args.user, - 'group': parsed_args.group, - } - sys.stderr.write(msg) - else: - msg = _("%(user)s added to group %(group)s\n") % { + except Exception as e: + msg = _("%(user)s not added to group %(group)s: %(e)s") % { 'user': parsed_args.user, 'group': parsed_args.group, + 'e': e, } - sys.stdout.write(msg) + raise exceptions.CommandError(msg) class CheckUserInGroup(command.Command): @@ -306,18 +301,13 @@ def take_action(self, parsed_args): try: identity_client.users.remove_from_group(user_id, group_id) - except Exception: - msg = _("%(user)s not removed from group %(group)s\n") % { - 'user': parsed_args.user, - 'group': parsed_args.group, - } - sys.stderr.write(msg) - else: - msg = _("%(user)s removed from group %(group)s\n") % { + except Exception as e: + msg = _("%(user)s not removed from group %(group)s: %(e)s") % { 'user': parsed_args.user, 'group': parsed_args.group, + 'e': e, } - sys.stdout.write(msg) + raise exceptions.CommandError(msg) class SetGroup(command.Command): diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index 994ecc9c8b..1bbf5f07b0 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -16,7 +16,6 @@ """Identity v3 Role action implementations""" import logging -import sys from keystoneauth1 import exceptions as ks_exc from osc_lib.command import command @@ -129,7 +128,9 @@ def take_action(self, parsed_args): if (not parsed_args.user and not parsed_args.domain and not parsed_args.group and not parsed_args.project): - return + msg = _("Role not added, incorrect set of arguments " + "provided. See openstack --help for more details") + raise exceptions.CommandError(msg) domain_id = None if parsed_args.role_domain: @@ -143,11 +144,6 @@ def take_action(self, parsed_args): kwargs = _process_identity_and_resource_options( parsed_args, self.app.client_manager.identity) - if not kwargs: - sys.stderr.write(_("Role not added, incorrect set of arguments " - "provided. See openstack --help for more " - "details\n")) - return identity_client.roles.grant(role.id, **kwargs) @@ -372,10 +368,10 @@ def take_action(self, parsed_args): ' --project --names ' 'instead.')) else: - sys.stderr.write(_("Error: If a user or group is specified, " - "either --domain or --project must also be " - "specified to list role grants.\n")) - return ([], []) + msg = _("Error: If a user or group is specified, " + "either --domain or --project must also be " + "specified to list role grants.") + raise exceptions.CommandError(msg) return (columns, (utils.get_item_properties( @@ -405,9 +401,9 @@ def take_action(self, parsed_args): if (not parsed_args.user and not parsed_args.domain and not parsed_args.group and not parsed_args.project): - sys.stderr.write(_("Incorrect set of arguments provided. " - "See openstack --help for more details\n")) - return + msg = _("Incorrect set of arguments provided. " + "See openstack --help for more details") + raise exceptions.CommandError(msg) domain_id = None if parsed_args.role_domain: @@ -421,11 +417,6 @@ def take_action(self, parsed_args): kwargs = _process_identity_and_resource_options( parsed_args, self.app.client_manager.identity) - if not kwargs: - sys.stderr.write(_("Role not removed, incorrect set of arguments " - "provided. See openstack --help for more " - "details\n")) - return identity_client.roles.revoke(role.id, **kwargs) diff --git a/openstackclient/tests/functional/identity/v3/test_group.py b/openstackclient/tests/functional/identity/v3/test_group.py index 7049118359..917d5df048 100644 --- a/openstackclient/tests/functional/identity/v3/test_group.py +++ b/openstackclient/tests/functional/identity/v3/test_group.py @@ -102,11 +102,7 @@ def test_group_add_user(self): 'user_domain': self.domain_name, 'group': group_name, 'user': username}) - self.assertEqual( - '%(user)s added to group %(group)s\n' % {'user': username, - 'group': group_name}, - raw_output - ) + self.assertOutput('', raw_output) def test_group_contains_user(self): group_name = self._create_dummy_group() @@ -128,11 +124,7 @@ def test_group_contains_user(self): 'user_domain': self.domain_name, 'group': group_name, 'user': username}) - self.assertEqual( - '%(user)s added to group %(group)s\n' % {'user': username, - 'group': group_name}, - raw_output - ) + self.assertOutput('', raw_output) raw_output = self.openstack( 'group contains user ' '--group-domain %(group_domain)s ' @@ -165,14 +157,5 @@ def test_group_remove_user(self): 'user_domain': self.domain_name, 'group': group_name, 'user': username}) - self.assertEqual( - '%(user)s added to group %(group)s\n' % {'user': username, - 'group': group_name}, - add_raw_output - ) - self.assertEqual( - '%(user)s removed from ' - 'group %(group)s\n' % {'user': username, - 'group': group_name}, - remove_raw_output - ) + self.assertOutput('', add_raw_output) + self.assertOutput('', remove_raw_output) diff --git a/openstackclient/tests/unit/identity/v3/test_group.py b/openstackclient/tests/unit/identity/v3/test_group.py index 8558de950a..00bd217dad 100644 --- a/openstackclient/tests/unit/identity/v3/test_group.py +++ b/openstackclient/tests/unit/identity/v3/test_group.py @@ -70,6 +70,20 @@ def test_group_add_user(self): self.user.id, self.group.id) self.assertIsNone(result) + def test_group_add_user_with_error(self): + self.users_mock.add_to_group.side_effect = exceptions.CommandError() + arglist = [ + self.group.name, + self.user.name, + ] + verifylist = [ + ('group', self.group.name), + ('user', self.user.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, parsed_args) + class TestGroupCheckUser(TestGroup): @@ -460,6 +474,21 @@ def test_group_remove_user(self): self.user.id, self.group.id) self.assertIsNone(result) + def test_group_remove_user_with_error(self): + self.users_mock.remove_from_group.side_effect = ( + exceptions.CommandError()) + arglist = [ + self.group.id, + self.user.id, + ] + verifylist = [ + ('group', self.group.id), + ('user', self.user.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, parsed_args) + class TestGroupSet(TestGroup): diff --git a/openstackclient/tests/unit/identity/v3/test_role.py b/openstackclient/tests/unit/identity/v3/test_role.py index c0b68bdfad..39dbd24466 100644 --- a/openstackclient/tests/unit/identity/v3/test_role.py +++ b/openstackclient/tests/unit/identity/v3/test_role.py @@ -273,6 +273,22 @@ def test_role_add_domain_role_on_user_project(self): ) self.assertIsNone(result) + def test_role_add_with_error(self): + arglist = [ + identity_fakes.role_name, + ] + verifylist = [ + ('user', None), + ('group', None), + ('domain', None), + ('project', None), + ('role', identity_fakes.role_name), + ('inherited', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, parsed_args) + class TestRoleAddInherited(TestRoleAdd, TestRoleInherited): pass @@ -771,6 +787,17 @@ def test_role_list_domain_role(self): ), ) self.assertEqual(datalist, tuple(data)) + def test_role_list_group_with_error(self): + arglist = [ + '--group', identity_fakes.group_id, + ] + verifylist = [ + ('group', identity_fakes.group_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, parsed_args) + class TestRoleRemove(TestRole): @@ -982,6 +1009,22 @@ def test_role_remove_domain_role_on_group_domain(self): ) self.assertIsNone(result) + def test_role_remove_with_error(self): + arglist = [ + identity_fakes.role_name, + ] + verifylist = [ + ('user', None), + ('group', None), + ('domain', None), + ('project', None), + ('role', identity_fakes.role_name), + ('inherited', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, parsed_args) + class TestRoleSet(TestRole): From df49c850070de0bbc67660974039d9d10bf2f379 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Fri, 10 Feb 2017 05:59:24 +0000 Subject: [PATCH 1542/3095] Updated from global requirements Change-Id: Ic3235a76147314b81290c63805118e5a8e7599a6 --- test-requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test-requirements.txt b/test-requirements.txt index 707df3ca72..61ad4e4fd5 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -11,13 +11,13 @@ oslotest>=1.10.0 # Apache-2.0 reno>=1.8.0 # Apache-2.0 requests!=2.12.2,>=2.10.0 # Apache-2.0 requests-mock>=1.1 # Apache-2.0 -sphinx!=1.3b1,<1.4,>=1.2.1 # BSD +sphinx>=1.5.1 # BSD stevedore>=1.17.1 # Apache-2.0 os-client-config>=1.22.0 # Apache-2.0 os-testr>=0.8.0 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD testtools>=1.4.0 # MIT -tempest>=12.1.0 # Apache-2.0 +tempest>=14.0.0 # Apache-2.0 osprofiler>=1.4.0 # Apache-2.0 bandit>=1.1.0 # Apache-2.0 wrapt>=1.7.0 # BSD License From e8eb0914ea9d546f8ddae462d055c55aa810e1cc Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sat, 11 Feb 2017 17:52:00 +0000 Subject: [PATCH 1543/3095] Updated from global requirements Change-Id: Ic337cb3e4a011938cab7ddc9ad29be0e9af83a4b --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 61ad4e4fd5..ef662d47a7 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -9,7 +9,7 @@ mock>=2.0 # BSD oslosphinx>=4.7.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 reno>=1.8.0 # Apache-2.0 -requests!=2.12.2,>=2.10.0 # Apache-2.0 +requests!=2.12.2,!=2.13.0,>=2.10.0 # Apache-2.0 requests-mock>=1.1 # Apache-2.0 sphinx>=1.5.1 # BSD stevedore>=1.17.1 # Apache-2.0 From 35b2724293741a5460d495932846e6a6c6e14043 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Thu, 9 Feb 2017 20:59:53 +0800 Subject: [PATCH 1544/3095] Refactor volume functional test in volume v1 There is a patch for refactor of volume funtional test in volume v2 [1], but v1 is missing, this change add the v1 refactor with json format. [1]: https://review.openstack.org/#/c/417349 Change-Id: I969df3c8dbca21a62f6245e3e95680cf3cd47dc1 --- .../tests/functional/volume/v1/test_volume.py | 246 +++++++++++++----- 1 file changed, 183 insertions(+), 63 deletions(-) diff --git a/openstackclient/tests/functional/volume/v1/test_volume.py b/openstackclient/tests/functional/volume/v1/test_volume.py index 5e4bcbea9a..992dfd66bf 100644 --- a/openstackclient/tests/functional/volume/v1/test_volume.py +++ b/openstackclient/tests/functional/volume/v1/test_volume.py @@ -10,6 +10,8 @@ # License for the specific language governing permissions and limitations # under the License. +import json +import time import uuid from openstackclient.tests.functional.volume.v1 import common @@ -18,71 +20,189 @@ class VolumeTests(common.BaseVolumeTests): """Functional tests for volume. """ - NAME = uuid.uuid4().hex - OTHER_NAME = uuid.uuid4().hex - HEADERS = ['"Display Name"'] - FIELDS = ['display_name'] - - @classmethod - def setUpClass(cls): - super(VolumeTests, cls).setUpClass() - opts = cls.get_opts(cls.FIELDS) - raw_output = cls.openstack('volume create --size 1 ' + cls.NAME + opts) - expected = cls.NAME + '\n' - cls.assertOutput(expected, raw_output) - - @classmethod - def tearDownClass(cls): - # Rename test - raw_output = cls.openstack( - 'volume set --name ' + cls.OTHER_NAME + ' ' + cls.NAME) - cls.assertOutput('', raw_output) - # Delete test - raw_output = cls.openstack('volume delete ' + cls.OTHER_NAME) - cls.assertOutput('', raw_output) + def test_volume_create_and_delete(self): + """Test create, delete multiple""" + name1 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume create -f json ' + + '--size 1 ' + + name1 + )) + self.assertEqual( + 1, + cmd_output["size"], + ) + + name2 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume create -f json ' + + '--size 2 ' + + name2 + )) + self.assertEqual( + 2, + cmd_output["size"], + ) + + self.wait_for("volume", name1, "available") + self.wait_for("volume", name2, "available") + del_output = self.openstack('volume delete ' + name1 + ' ' + name2) + self.assertOutput('', del_output) def test_volume_list(self): - opts = self.get_opts(self.HEADERS) - raw_output = self.openstack('volume list' + opts) - self.assertIn(self.NAME, raw_output) + """Test create, list filter""" + name1 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume create -f json ' + + '--size 1 ' + + name1 + )) + self.addCleanup(self.openstack, 'volume delete ' + name1) + self.assertEqual( + 1, + cmd_output["size"], + ) + self.wait_for("volume", name1, "available") + + name2 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume create -f json ' + + '--size 2 ' + + name2 + )) + self.addCleanup(self.openstack, 'volume delete ' + name2) + self.assertEqual( + 2, + cmd_output["size"], + ) + self.wait_for("volume", name2, "available") + + # Test list + cmd_output = json.loads(self.openstack( + 'volume list -f json ' + )) + names = [x["Display Name"] for x in cmd_output] + self.assertIn(name1, names) + self.assertIn(name2, names) + + # Test list --long + cmd_output = json.loads(self.openstack( + 'volume list -f json --long' + )) + bootable = [x["Bootable"] for x in cmd_output] + self.assertIn('false', bootable) + + # Test list --name + cmd_output = json.loads(self.openstack( + 'volume list -f json ' + + '--name ' + name1 + )) + names = [x["Display Name"] for x in cmd_output] + self.assertIn(name1, names) + self.assertNotIn(name2, names) - def test_volume_show(self): - opts = self.get_opts(self.FIELDS) - raw_output = self.openstack('volume show ' + self.NAME + opts) - self.assertEqual(self.NAME + "\n", raw_output) + def test_volume_set_and_unset(self): + """Tests create volume, set, unset, show, delete""" + name = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume create -f json ' + + '--size 1 ' + + '--description aaaa ' + + '--property Alpha=a ' + + name + )) + self.assertEqual( + name, + cmd_output["display_name"], + ) + self.assertEqual( + 1, + cmd_output["size"], + ) + self.assertEqual( + 'aaaa', + cmd_output["display_description"], + ) + self.assertEqual( + "Alpha='a'", + cmd_output["properties"], + ) + self.assertEqual( + 'false', + cmd_output["bootable"], + ) + self.wait_for("volume", name, "available") - def test_volume_properties(self): + # Test volume set + new_name = uuid.uuid4().hex + self.addCleanup(self.openstack, 'volume delete ' + new_name) raw_output = self.openstack( - 'volume set --property a=b --property c=d ' + self.NAME) - self.assertEqual("", raw_output) - opts = self.get_opts(["properties"]) - raw_output = self.openstack('volume show ' + self.NAME + opts) - self.assertEqual("a='b', c='d'\n", raw_output) - - raw_output = self.openstack('volume unset --property a ' + self.NAME) - self.assertEqual("", raw_output) - raw_output = self.openstack('volume show ' + self.NAME + opts) - self.assertEqual("c='d'\n", raw_output) - - def test_volume_set(self): - self.openstack('volume set --description RAMAC ' + self.NAME) - opts = self.get_opts(["display_description", "display_name"]) - raw_output = self.openstack('volume show ' + self.NAME + opts) - self.assertEqual("RAMAC\n" + self.NAME + "\n", raw_output) - - def test_volume_set_size(self): - self.openstack('volume set --size 2 ' + self.NAME) - opts = self.get_opts(["display_name", "size"]) - raw_output = self.openstack('volume show ' + self.NAME + opts) - self.assertEqual(self.NAME + "\n2\n", raw_output) - - def test_volume_set_bootable(self): - self.openstack('volume set --bootable ' + self.NAME) - opts = self.get_opts(["bootable"]) - raw_output = self.openstack('volume show ' + self.NAME + opts) - self.assertEqual("true\n", raw_output) - - self.openstack('volume set --non-bootable ' + self.NAME) - opts = self.get_opts(["bootable"]) - raw_output = self.openstack('volume show ' + self.NAME + opts) - self.assertEqual("false\n", raw_output) + 'volume set ' + + '--name ' + new_name + + ' --size 2 ' + + '--description bbbb ' + + '--property Alpha=c ' + + '--property Beta=b ' + + '--bootable ' + + name, + ) + self.assertOutput('', raw_output) + + cmd_output = json.loads(self.openstack( + 'volume show -f json ' + + new_name + )) + self.assertEqual( + new_name, + cmd_output["display_name"], + ) + self.assertEqual( + 2, + cmd_output["size"], + ) + self.assertEqual( + 'bbbb', + cmd_output["display_description"], + ) + self.assertEqual( + "Alpha='c', Beta='b'", + cmd_output["properties"], + ) + self.assertEqual( + 'true', + cmd_output["bootable"], + ) + + # Test volume unset + raw_output = self.openstack( + 'volume unset ' + + '--property Alpha ' + + new_name, + ) + self.assertOutput('', raw_output) + + cmd_output = json.loads(self.openstack( + 'volume show -f json ' + + new_name + )) + self.assertEqual( + "Beta='b'", + cmd_output["properties"], + ) + + def wait_for(self, check_type, check_name, desired_status, wait=120, + interval=5, failures=['ERROR']): + status = "notset" + total_sleep = 0 + opts = self.get_opts(['status']) + while total_sleep < wait: + status = self.openstack(check_type + ' show ' + check_name + opts) + status = status.rstrip() + print('Checking {} {} Waiting for {} current status: {}' + .format(check_type, check_name, desired_status, status)) + if status == desired_status: + break + self.assertNotIn(status, failures) + time.sleep(interval) + total_sleep += interval + self.assertEqual(desired_status, status) From 6a3c7c2a68dd2aeb8a0a05143de3b14e4beea99d Mon Sep 17 00:00:00 2001 From: Reedip Date: Wed, 28 Dec 2016 01:45:42 -0500 Subject: [PATCH 1545/3095] Overwrite/Clear Flavor property This patch adds support to overwrite/clear the flavor's property using the new ``--no-property`` option in the ``flavor set`` command. Change-Id: I873c96fcf223bbd638a19b908766d904a84e8431 Implements: blueprint allow-overwrite-set-options Co-Authored By: zhiyong.dai@easystack.cn --- doc/source/command-objects/flavor.rst | 6 ++++++ openstackclient/compute/v2/flavor.py | 17 +++++++++++++++++ .../tests/unit/compute/v2/test_flavor.py | 17 +++++++++++++++++ .../notes/add-no-property-f97e4b2f390cec06.yaml | 6 ++++++ 4 files changed, 46 insertions(+) create mode 100644 releasenotes/notes/add-no-property-f97e4b2f390cec06.yaml diff --git a/doc/source/command-objects/flavor.rst b/doc/source/command-objects/flavor.rst index 971628d720..a9f8262ee5 100644 --- a/doc/source/command-objects/flavor.rst +++ b/doc/source/command-objects/flavor.rst @@ -144,6 +144,7 @@ Set flavor properties .. code:: bash openstack flavor set + [--no-property] [--property [...] ] [--project ] [--project-domain ] @@ -162,6 +163,11 @@ Set flavor properties Domain the project belongs to (name or ID). This can be used in case collisions between project names exist. +.. option:: --no-property + + Remove all properties from this flavor (specify both --no-property and --property + to remove the current properties before setting new properties.) + .. describe:: Flavor to modify (name or ID) diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 7cd22ed7a9..7e213f7381 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -312,6 +312,14 @@ def get_parser(self, prog_name): metavar="", help=_("Flavor to modify (name or ID)") ) + parser.add_argument( + "--no-property", + action="store_true", + help=_("Remove all properties from this flavor " + "(specify both --no-property and --property" + " to remove the current properties before setting" + " new properties.)"), + ) parser.add_argument( "--property", metavar="", @@ -336,6 +344,15 @@ def take_action(self, parsed_args): flavor = _find_flavor(compute_client, parsed_args.flavor) result = 0 + key_list = [] + if parsed_args.no_property: + try: + for key in flavor.get_keys().keys(): + key_list.append(key) + flavor.unset_keys(key_list) + except Exception as e: + LOG.error(_("Failed to clear flavor property: %s"), e) + result += 1 if parsed_args.property: try: flavor.set_keys(parsed_args.property) diff --git a/openstackclient/tests/unit/compute/v2/test_flavor.py b/openstackclient/tests/unit/compute/v2/test_flavor.py index 632fcda129..4cdbb25ba0 100644 --- a/openstackclient/tests/unit/compute/v2/test_flavor.py +++ b/openstackclient/tests/unit/compute/v2/test_flavor.py @@ -528,6 +528,23 @@ def test_flavor_set_property(self): self.flavor.set_keys.assert_called_with({'FOO': '"B A R"'}) self.assertIsNone(result) + def test_flavor_set_no_property(self): + arglist = [ + '--no-property', + 'baremetal' + ] + verifylist = [ + ('no_property', True), + ('flavor', 'baremetal') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.flavors_mock.find.assert_called_with(name=parsed_args.flavor, + is_public=None) + self.flavor.unset_keys.assert_called_with(['property']) + self.assertIsNone(result) + def test_flavor_set_project(self): arglist = [ '--project', self.project.id, diff --git a/releasenotes/notes/add-no-property-f97e4b2f390cec06.yaml b/releasenotes/notes/add-no-property-f97e4b2f390cec06.yaml new file mode 100644 index 0000000000..ab50254908 --- /dev/null +++ b/releasenotes/notes/add-no-property-f97e4b2f390cec06.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add support to clear/overwrite all flavor properties using + ``--no-property`` option with ``flavor set`` command. + [ Blueprint `allow-overwrite-set-options ` _] From ee50ca30c15fc66c313188f1ddd3992610502040 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Mon, 13 Feb 2017 15:21:11 +0000 Subject: [PATCH 1546/3095] Updated from global requirements Change-Id: Ie5af4a8ea6b499db46ad6f1d3075d867faf3a3a2 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 422f782d86..3995c9a0c8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,5 +13,5 @@ oslo.i18n>=2.1.0 # Apache-2.0 oslo.utils>=3.18.0 # Apache-2.0 python-glanceclient>=2.5.0 # Apache-2.0 python-keystoneclient>=3.8.0 # Apache-2.0 -python-novaclient!=7.0.0,>=6.0.0 # Apache-2.0 +python-novaclient>=7.1.0 # Apache-2.0 python-cinderclient!=1.7.0,!=1.7.1,>=1.6.0 # Apache-2.0 From 335c8d3ef3e5d1112a90d2efa267e5f00a2c5357 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Antal?= Date: Mon, 13 Feb 2017 17:36:56 +0100 Subject: [PATCH 1547/3095] Handle log message interpolation by the logger in identity/ According to OpenStack Guideline[1], logged string message should be interpolated by the logger. [1]: http://docs.openstack.org/developer/oslo.i18n/guidelines.html#adding-variables-to-log-messages Change-Id: I1d6588093616099a9eef0947c09e038b9e53493a Related-Bug: #1596829 --- openstackclient/identity/v2_0/ec2creds.py | 4 ++-- openstackclient/identity/v2_0/endpoint.py | 4 ++-- openstackclient/identity/v2_0/service.py | 4 ++-- openstackclient/identity/v3/consumer.py | 3 +-- openstackclient/identity/v3/credential.py | 4 ++-- openstackclient/identity/v3/domain.py | 3 +-- openstackclient/identity/v3/ec2creds.py | 4 ++-- openstackclient/identity/v3/endpoint.py | 3 +-- openstackclient/identity/v3/federation_protocol.py | 4 ++-- openstackclient/identity/v3/identity_provider.py | 4 ++-- openstackclient/identity/v3/mapping.py | 3 +-- openstackclient/identity/v3/policy.py | 3 +-- openstackclient/identity/v3/region.py | 3 +-- openstackclient/identity/v3/service.py | 4 ++-- openstackclient/identity/v3/service_provider.py | 4 ++-- 15 files changed, 24 insertions(+), 30 deletions(-) diff --git a/openstackclient/identity/v2_0/ec2creds.py b/openstackclient/identity/v2_0/ec2creds.py index 2572c4f0bd..0bc48322bc 100644 --- a/openstackclient/identity/v2_0/ec2creds.py +++ b/openstackclient/identity/v2_0/ec2creds.py @@ -122,8 +122,8 @@ def take_action(self, parsed_args): except Exception as e: result += 1 LOG.error(_("Failed to delete EC2 credentials with " - "access key '%(access_key)s': %(e)s") - % {'access_key': access_key, 'e': e}) + "access key '%(access_key)s': %(e)s"), + {'access_key': access_key, 'e': e}) if result > 0: total = len(parsed_args.access_keys) diff --git a/openstackclient/identity/v2_0/endpoint.py b/openstackclient/identity/v2_0/endpoint.py index 7e0751a7ef..1628e48813 100644 --- a/openstackclient/identity/v2_0/endpoint.py +++ b/openstackclient/identity/v2_0/endpoint.py @@ -102,8 +102,8 @@ def take_action(self, parsed_args): except Exception as e: result += 1 LOG.error(_("Failed to delete endpoint with " - "ID '%(endpoint)s': %(e)s") - % {'endpoint': endpoint, 'e': e}) + "ID '%(endpoint)s': %(e)s"), + {'endpoint': endpoint, 'e': e}) if result > 0: total = len(parsed_args.endpoints) diff --git a/openstackclient/identity/v2_0/service.py b/openstackclient/identity/v2_0/service.py index f70f0fa9db..80f2d72a98 100644 --- a/openstackclient/identity/v2_0/service.py +++ b/openstackclient/identity/v2_0/service.py @@ -114,8 +114,8 @@ def take_action(self, parsed_args): except Exception as e: result += 1 LOG.error(_("Failed to delete service with " - "name or ID '%(service)s': %(e)s") - % {'service': service, 'e': e}) + "name or ID '%(service)s': %(e)s"), + {'service': service, 'e': e}) if result > 0: total = len(parsed_args.services) diff --git a/openstackclient/identity/v3/consumer.py b/openstackclient/identity/v3/consumer.py index bcb29db4dc..6dd24dccf1 100644 --- a/openstackclient/identity/v3/consumer.py +++ b/openstackclient/identity/v3/consumer.py @@ -73,8 +73,7 @@ def take_action(self, parsed_args): except Exception as e: result += 1 LOG.error(_("Failed to delete consumer with name or " - "ID '%(consumer)s': %(e)s") - % {'consumer': i, 'e': e}) + "ID '%(consumer)s': %(e)s"), {'consumer': i, 'e': e}) if result > 0: total = len(parsed_args.consumer) diff --git a/openstackclient/identity/v3/credential.py b/openstackclient/identity/v3/credential.py index 68287f2a39..2fd0062610 100644 --- a/openstackclient/identity/v3/credential.py +++ b/openstackclient/identity/v3/credential.py @@ -99,8 +99,8 @@ def take_action(self, parsed_args): except Exception as e: result += 1 LOG.error(_("Failed to delete credentials with " - "ID '%(credential)s': %(e)s") - % {'credential': i, 'e': e}) + "ID '%(credential)s': %(e)s"), + {'credential': i, 'e': e}) if result > 0: total = len(parsed_args.credential) diff --git a/openstackclient/identity/v3/domain.py b/openstackclient/identity/v3/domain.py index 59ab0f0772..064624ab45 100644 --- a/openstackclient/identity/v3/domain.py +++ b/openstackclient/identity/v3/domain.py @@ -111,8 +111,7 @@ def take_action(self, parsed_args): except Exception as e: result += 1 LOG.error(_("Failed to delete domain with name or " - "ID '%(domain)s': %(e)s") - % {'domain': i, 'e': e}) + "ID '%(domain)s': %(e)s"), {'domain': i, 'e': e}) if result > 0: total = len(parsed_args.domain) diff --git a/openstackclient/identity/v3/ec2creds.py b/openstackclient/identity/v3/ec2creds.py index 9854efda70..44e9a2c778 100644 --- a/openstackclient/identity/v3/ec2creds.py +++ b/openstackclient/identity/v3/ec2creds.py @@ -140,8 +140,8 @@ def take_action(self, parsed_args): except Exception as e: result += 1 LOG.error(_("Failed to delete EC2 credentials with " - "access key '%(access_key)s': %(e)s") - % {'access_key': i, 'e': e}) + "access key '%(access_key)s': %(e)s"), + {'access_key': i, 'e': e}) if result > 0: total = len(parsed_args.access_key) diff --git a/openstackclient/identity/v3/endpoint.py b/openstackclient/identity/v3/endpoint.py index 39fd49efd1..7bc5e6df74 100644 --- a/openstackclient/identity/v3/endpoint.py +++ b/openstackclient/identity/v3/endpoint.py @@ -122,8 +122,7 @@ def take_action(self, parsed_args): except Exception as e: result += 1 LOG.error(_("Failed to delete endpoint with " - "ID '%(endpoint)s': %(e)s") - % {'endpoint': i, 'e': e}) + "ID '%(endpoint)s': %(e)s"), {'endpoint': i, 'e': e}) if result > 0: total = len(parsed_args.endpoint) diff --git a/openstackclient/identity/v3/federation_protocol.py b/openstackclient/identity/v3/federation_protocol.py index 0752e8f6f2..6429d934c1 100644 --- a/openstackclient/identity/v3/federation_protocol.py +++ b/openstackclient/identity/v3/federation_protocol.py @@ -102,8 +102,8 @@ def take_action(self, parsed_args): except Exception as e: result += 1 LOG.error(_("Failed to delete federation protocol " - "with name or ID '%(protocol)s': %(e)s") - % {'protocol': i, 'e': e}) + "with name or ID '%(protocol)s': %(e)s"), + {'protocol': i, 'e': e}) if result > 0: total = len(parsed_args.federation_protocol) diff --git a/openstackclient/identity/v3/identity_provider.py b/openstackclient/identity/v3/identity_provider.py index 163dcb5f07..e8b3a2f459 100644 --- a/openstackclient/identity/v3/identity_provider.py +++ b/openstackclient/identity/v3/identity_provider.py @@ -115,8 +115,8 @@ def take_action(self, parsed_args): except Exception as e: result += 1 LOG.error(_("Failed to delete identity providers with " - "name or ID '%(provider)s': %(e)s") - % {'provider': i, 'e': e}) + "name or ID '%(provider)s': %(e)s"), + {'provider': i, 'e': e}) if result > 0: total = len(parsed_args.identity_provider) diff --git a/openstackclient/identity/v3/mapping.py b/openstackclient/identity/v3/mapping.py index 28080f8957..e729c410c8 100644 --- a/openstackclient/identity/v3/mapping.py +++ b/openstackclient/identity/v3/mapping.py @@ -132,8 +132,7 @@ def take_action(self, parsed_args): except Exception as e: result += 1 LOG.error(_("Failed to delete mapping with name or " - "ID '%(mapping)s': %(e)s") - % {'mapping': i, 'e': e}) + "ID '%(mapping)s': %(e)s"), {'mapping': i, 'e': e}) if result > 0: total = len(parsed_args.mapping) diff --git a/openstackclient/identity/v3/policy.py b/openstackclient/identity/v3/policy.py index c511652ede..3b6441959b 100644 --- a/openstackclient/identity/v3/policy.py +++ b/openstackclient/identity/v3/policy.py @@ -82,8 +82,7 @@ def take_action(self, parsed_args): except Exception as e: result += 1 LOG.error(_("Failed to delete policy with name or " - "ID '%(policy)s': %(e)s") - % {'policy': i, 'e': e}) + "ID '%(policy)s': %(e)s"), {'policy': i, 'e': e}) if result > 0: total = len(parsed_args.policy) diff --git a/openstackclient/identity/v3/region.py b/openstackclient/identity/v3/region.py index d3e712e375..69c8b5066a 100644 --- a/openstackclient/identity/v3/region.py +++ b/openstackclient/identity/v3/region.py @@ -87,8 +87,7 @@ def take_action(self, parsed_args): except Exception as e: result += 1 LOG.error(_("Failed to delete region with " - "ID '%(region)s': %(e)s") - % {'region': i, 'e': e}) + "ID '%(region)s': %(e)s"), {'region': i, 'e': e}) if result > 0: total = len(parsed_args.region) diff --git a/openstackclient/identity/v3/service.py b/openstackclient/identity/v3/service.py index 7daf8919d3..ac8d8d9eef 100644 --- a/openstackclient/identity/v3/service.py +++ b/openstackclient/identity/v3/service.py @@ -103,8 +103,8 @@ def take_action(self, parsed_args): except Exception as e: result += 1 LOG.error(_("Failed to delete consumer with type, " - "name or ID '%(service)s': %(e)s") - % {'service': i, 'e': e}) + "name or ID '%(service)s': %(e)s"), + {'service': i, 'e': e}) if result > 0: total = len(parsed_args.service) diff --git a/openstackclient/identity/v3/service_provider.py b/openstackclient/identity/v3/service_provider.py index 459dc00b03..bb2d9917b4 100644 --- a/openstackclient/identity/v3/service_provider.py +++ b/openstackclient/identity/v3/service_provider.py @@ -108,8 +108,8 @@ def take_action(self, parsed_args): except Exception as e: result += 1 LOG.error(_("Failed to delete service provider with " - "name or ID '%(provider)s': %(e)s") - % {'provider': i, 'e': e}) + "name or ID '%(provider)s': %(e)s"), + {'provider': i, 'e': e}) if result > 0: total = len(parsed_args.service_provider) From c9b1c2ad961a767927c7f0efc3b4df3c11804bc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Antal?= Date: Mon, 13 Feb 2017 18:13:29 +0100 Subject: [PATCH 1548/3095] Handle log message interpolation by the logger in network/ According to OpenStack Guideline[1], logged string message should be interpolated by the logger. [1]: http://docs.openstack.org/developer/oslo.i18n/guidelines.html#adding-variables-to-log-messages Related-Bug: #1596829 Change-Id: I17467d01420750c004fbbf2a07730fc2badd62b8 --- openstackclient/network/v2/network_segment.py | 4 ++-- openstackclient/network/v2/router.py | 4 ++-- openstackclient/network/v2/security_group_rule.py | 8 ++++---- openstackclient/network/v2/subnet.py | 4 ++-- openstackclient/network/v2/subnet_pool.py | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/openstackclient/network/v2/network_segment.py b/openstackclient/network/v2/network_segment.py index 709dc2965a..c1a672e2d5 100644 --- a/openstackclient/network/v2/network_segment.py +++ b/openstackclient/network/v2/network_segment.py @@ -119,8 +119,8 @@ def take_action(self, parsed_args): except Exception as e: result += 1 LOG.error(_("Failed to delete network segment with " - "ID '%(network_segment)s': %(e)s") - % {'network_segment': network_segment, 'e': e}) + "ID '%(network_segment)s': %(e)s"), + {'network_segment': network_segment, 'e': e}) if result > 0: total = len(parsed_args.network_segment) diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index aad35a9e17..f46c8696ab 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -249,8 +249,8 @@ def take_action(self, parsed_args): except Exception as e: result += 1 LOG.error(_("Failed to delete router with " - "name or ID '%(router)s': %(e)s") - % {'router': router, 'e': e}) + "name or ID '%(router)s': %(e)s"), + {'router': router, 'e': e}) if result > 0: total = len(parsed_args.router) diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index 4fb62c7bff..63b80d25e9 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -315,7 +315,7 @@ def take_action_network(self, client, parsed_args): if parsed_args.src_group: LOG.warning( _("The %(old)s option is deprecated, " - "please use %(new)s instead.") % + "please use %(new)s instead."), {'old': '--src-group', 'new': '--remote-group'}, ) elif not (parsed_args.remote_ip is None and @@ -326,7 +326,7 @@ def take_action_network(self, client, parsed_args): if parsed_args.src_ip: LOG.warning( _("The %(old)s option is deprecated, " - "please use %(new)s instead.") % + "please use %(new)s instead."), {'old': '--src-ip', 'new': '--remote-ip'}, ) elif attrs['ethertype'] == 'IPv4': @@ -370,7 +370,7 @@ def take_action_compute(self, client, parsed_args): if parsed_args.src_group: LOG.warning( _("The %(old)s option is deprecated, " - "please use %(new)s instead.") % + "please use %(new)s instead."), {'old': '--src-group', 'new': '--remote-group'}, ) if not (parsed_args.remote_ip is None and @@ -379,7 +379,7 @@ def take_action_compute(self, client, parsed_args): if parsed_args.src_ip: LOG.warning( _("The %(old)s option is deprecated, " - "please use %(new)s instead.") % + "please use %(new)s instead."), {'old': '--src-ip', 'new': '--remote-ip'}, ) else: diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 292b7c0624..2771858b97 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -365,8 +365,8 @@ def take_action(self, parsed_args): except Exception as e: result += 1 LOG.error(_("Failed to delete subnet with " - "name or ID '%(subnet)s': %(e)s") - % {'subnet': subnet, 'e': e}) + "name or ID '%(subnet)s': %(e)s"), + {'subnet': subnet, 'e': e}) if result > 0: total = len(parsed_args.subnet) diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index a5a244240e..0473111112 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -220,8 +220,8 @@ def take_action(self, parsed_args): except Exception as e: result += 1 LOG.error(_("Failed to delete subnet pool with " - "name or ID '%(pool)s': %(e)s") - % {'pool': pool, 'e': e}) + "name or ID '%(pool)s': %(e)s"), + {'pool': pool, 'e': e}) if result > 0: total = len(parsed_args.subnet_pool) From 1c91e9828d64c927cc28d9de61dd56887ca59dff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Antal?= Date: Mon, 13 Feb 2017 18:41:24 +0100 Subject: [PATCH 1549/3095] Handle log message interpolation by the logger in compute/ According to OpenStack Guideline[1], logged string message should be interpolated by the logger. [1]: http://docs.openstack.org/developer/oslo.i18n/guidelines.html#adding-variables-to-log-messages Change-Id: Ib7b5fb5f794026fc8a84260c4803afea321a9cf5 Closes-Bug: #1596829 --- openstackclient/compute/v2/aggregate.py | 4 ++-- openstackclient/compute/v2/flavor.py | 7 +++---- openstackclient/compute/v2/keypair.py | 3 +-- openstackclient/compute/v2/service.py | 3 +-- 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/openstackclient/compute/v2/aggregate.py b/openstackclient/compute/v2/aggregate.py index 76ba5cc662..7f9161a996 100644 --- a/openstackclient/compute/v2/aggregate.py +++ b/openstackclient/compute/v2/aggregate.py @@ -128,8 +128,8 @@ def take_action(self, parsed_args): except Exception as e: result += 1 LOG.error(_("Failed to delete aggregate with name or " - "ID '%(aggregate)s': %(e)s") - % {'aggregate': a, 'e': e}) + "ID '%(aggregate)s': %(e)s"), + {'aggregate': a, 'e': e}) if result > 0: total = len(parsed_args.aggregate) diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 7cd22ed7a9..aa8cd19116 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -171,7 +171,7 @@ def take_action(self, parsed_args): except Exception as e: msg = _("Failed to add project %(project)s access to " "flavor: %(e)s") - LOG.error(msg % {'project': parsed_args.project, 'e': e}) + LOG.error(msg, {'project': parsed_args.project, 'e': e}) if parsed_args.property: try: flavor.set_keys(parsed_args.property) @@ -208,8 +208,7 @@ def take_action(self, parsed_args): except Exception as e: result += 1 LOG.error(_("Failed to delete flavor with name or " - "ID '%(flavor)s': %(e)s") - % {'flavor': f, 'e': e}) + "ID '%(flavor)s': %(e)s"), {'flavor': f, 'e': e}) if result > 0: total = len(parsed_args.flavor) @@ -395,7 +394,7 @@ def take_action(self, parsed_args): except Exception as e: msg = _("Failed to get access projects list " "for flavor '%(flavor)s': %(e)s") - LOG.error(msg % {'flavor': parsed_args.flavor, 'e': e}) + LOG.error(msg, {'flavor': parsed_args.flavor, 'e': e}) flavor = resource_flavor._info.copy() flavor.update({ diff --git a/openstackclient/compute/v2/keypair.py b/openstackclient/compute/v2/keypair.py index a63cbfecc0..2a8524d62b 100644 --- a/openstackclient/compute/v2/keypair.py +++ b/openstackclient/compute/v2/keypair.py @@ -106,8 +106,7 @@ def take_action(self, parsed_args): except Exception as e: result += 1 LOG.error(_("Failed to delete key with name " - "'%(name)s': %(e)s") - % {'name': n, 'e': e}) + "'%(name)s': %(e)s"), {'name': n, 'e': e}) if result > 0: total = len(parsed_args.name) diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index 9c384f0589..7b18c2e505 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.py @@ -50,8 +50,7 @@ def take_action(self, parsed_args): except Exception as e: result += 1 LOG.error(_("Failed to delete compute service with " - "ID '%(service)s': %(e)s") - % {'service': s, 'e': e}) + "ID '%(service)s': %(e)s"), {'service': s, 'e': e}) if result > 0: total = len(parsed_args.service) From f891b6862f0b980051b7e6e0d88eb6e10b72ac29 Mon Sep 17 00:00:00 2001 From: huangtianhua Date: Mon, 9 Jan 2017 10:11:02 +0800 Subject: [PATCH 1550/3095] Use public and unified method get_console_url() Novaclient has provided a public and unified method get_console_url() to get console urls of server. This change switches to use it. Change-Id: Ie6b9d8cfc57a6943b5d64a4064e4bdd372cd8cd3 Depends-on: I36c6209b17ef453e1c2e47841daf41f81af471dc Closes-Bug: #1654913 --- openstackclient/compute/v2/console.py | 13 +-------- .../tests/unit/compute/v2/test_console.py | 28 ++++++++----------- 2 files changed, 13 insertions(+), 28 deletions(-) diff --git a/openstackclient/compute/v2/console.py b/openstackclient/compute/v2/console.py index 358df50182..25f9210886 100644 --- a/openstackclient/compute/v2/console.py +++ b/openstackclient/compute/v2/console.py @@ -126,18 +126,7 @@ def take_action(self, parsed_args): parsed_args.server, ) - data = None - if parsed_args.url_type in ['novnc', 'xvpvnc']: - data = server.get_vnc_console(parsed_args.url_type) - if parsed_args.url_type in ['spice-html5']: - data = server.get_spice_console(parsed_args.url_type) - if parsed_args.url_type in ['rdp-html5']: - data = server.get_rdp_console(parsed_args.url_type) - if parsed_args.url_type in ['serial']: - data = server.get_serial_console(parsed_args.url_type) - if parsed_args.url_type in ['webmks']: - data = server.get_mks_console() - + data = server.get_console_url(parsed_args.url_type) if not data: return ({}, {}) diff --git a/openstackclient/tests/unit/compute/v2/test_console.py b/openstackclient/tests/unit/compute/v2/test_console.py index d53d241e36..3c708aaee3 100644 --- a/openstackclient/tests/unit/compute/v2/test_console.py +++ b/openstackclient/tests/unit/compute/v2/test_console.py @@ -35,11 +35,7 @@ def setUp(self): 'protocol': 'fake_protocol', 'type': 'fake_type'}} methods = { - 'get_vnc_console': fake_console_data, - 'get_spice_console': fake_console_data, - 'get_serial_console': fake_console_data, - 'get_rdp_console': fake_console_data, - 'get_mks_console': fake_console_data, + 'get_console_url': fake_console_data } self.fake_server = compute_fakes.FakeServer.create_one_server( methods=methods) @@ -68,7 +64,7 @@ def test_console_url_show_by_default(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.fake_server.get_vnc_console.assert_called_once_with('novnc') + self.fake_server.get_console_url.assert_called_once_with('novnc') self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -83,7 +79,7 @@ def test_console_url_show_with_novnc(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.fake_server.get_vnc_console.assert_called_once_with('novnc') + self.fake_server.get_console_url.assert_called_once_with('novnc') self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -98,7 +94,7 @@ def test_console_url_show_with_xvpvnc(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.fake_server.get_vnc_console.assert_called_once_with('xvpvnc') + self.fake_server.get_console_url.assert_called_once_with('xvpvnc') self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -113,14 +109,14 @@ def test_console_url_show_with_spice(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.fake_server.get_spice_console.assert_called_once_with( + self.fake_server.get_console_url.assert_called_once_with( 'spice-html5') self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) def test_console_url_show_compatible(self): methods = { - 'get_vnc_console': {'console': {'url': 'http://localhost', + 'get_console_url': {'console': {'url': 'http://localhost', 'type': 'fake_type'}}, } old_fake_server = compute_fakes.FakeServer.create_one_server( @@ -130,8 +126,8 @@ def test_console_url_show_compatible(self): 'url', ) old_data = ( - methods['get_vnc_console']['console']['type'], - methods['get_vnc_console']['console']['url'] + methods['get_console_url']['console']['type'], + methods['get_console_url']['console']['url'] ) arglist = [ 'foo_vm', @@ -144,7 +140,7 @@ def test_console_url_show_compatible(self): with mock.patch.object(self.servers_mock, 'get', return_value=old_fake_server): columns, data = self.cmd.take_action(parsed_args) - old_fake_server.get_vnc_console.assert_called_once_with('novnc') + old_fake_server.get_console_url.assert_called_once_with('novnc') self.assertEqual(old_columns, columns) self.assertEqual(old_data, data) @@ -159,7 +155,7 @@ def test_console_url_show_with_rdp(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.fake_server.get_rdp_console.assert_called_once_with( + self.fake_server.get_console_url.assert_called_once_with( 'rdp-html5') self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -175,7 +171,7 @@ def test_console_url_show_with_serial(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.fake_server.get_serial_console.assert_called_once_with( + self.fake_server.get_console_url.assert_called_once_with( 'serial') self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -191,6 +187,6 @@ def test_console_url_show_with_mks(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.fake_server.get_mks_console.assert_called_once_with() + self.fake_server.get_console_url.assert_called_once_with('webmks') self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) From c051c5f090fa6729a005c9d462afd8a75fc1b40f Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Mon, 13 Feb 2017 15:31:23 +0800 Subject: [PATCH 1551/3095] Fix "server create" command failed when --nic auto or none "auto" and "none" options was added into --nic argument of server create command in patch https://review.openstack.org/#/c/412698/ , but that don't work and raise internal error when execute command. The patch fix that issue and add unit and functional tests. Change-Id: Ia718c3bac0a5172a0cdbe9f0d97972a9346c1172 Co-Authored-By: Kevin_Zheng Closes-Bug: #1663520 --- doc/source/command-objects/server.rst | 2 + openstackclient/compute/v2/server.py | 36 ++++- .../functional/compute/v2/test_server.py | 53 ++++++- .../tests/unit/compute/v2/test_server.py | 146 ++++++++++++++++++ .../notes/bug-1663520-d880bfa51ca7b798.yaml | 8 + 5 files changed, 232 insertions(+), 13 deletions(-) create mode 100644 releasenotes/notes/bug-1663520-d880bfa51ca7b798.yaml diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index a5d22f81c2..b2ae965a7b 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -173,6 +173,8 @@ Create a new server v6-fixed-ip: IPv6 fixed address for NIC (optional). none: (v2.37+) no network is attached. auto: (v2.37+) the compute service will automatically allocate a network. + Specifying a --nic of auto or none cannot be used with any other + --nic value. .. option:: --hint diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index f9f0df4f2f..d33c631a17 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -402,7 +402,7 @@ def get_parser(self, prog_name): parser.add_argument( '--nic', metavar="", + "port-id=port-uuid,auto,none>", action='append', default=[], help=_("Create a NIC on the server. " @@ -414,7 +414,8 @@ def get_parser(self, prog_name): "v6-fixed-ip: IPv6 fixed address for NIC (optional), " "none: (v2.37+) no network is attached, " "auto: (v2.37+) the compute service will automatically " - "allocate a network."), + "allocate a network. Specifying a --nic of auto or none " + "cannot be used with any other --nic value."), ) parser.add_argument( '--hint', @@ -547,14 +548,21 @@ def take_action(self, parsed_args): block_device_mapping_v2.append(mapping) nics = [] - if parsed_args.nic in ('auto', 'none'): - nics = [parsed_args.nic] - else: - for nic_str in parsed_args.nic: + auto_or_none = False + for nic_str in parsed_args.nic: + # Handle the special auto/none cases + if nic_str in ('auto', 'none'): + auto_or_none = True + nics.append(nic_str) + else: nic_info = {"net-id": "", "v4-fixed-ip": "", "v6-fixed-ip": "", "port-id": ""} - nic_info.update(dict(kv_str.split("=", 1) - for kv_str in nic_str.split(","))) + try: + nic_info.update(dict(kv_str.split("=", 1) + for kv_str in nic_str.split(","))) + except ValueError: + msg = _('Invalid --nic argument %s.') % nic_str + raise exceptions.CommandError(msg) if bool(nic_info["net-id"]) == bool(nic_info["port-id"]): msg = _("either net-id or port-id should be specified " "but not both") @@ -581,6 +589,18 @@ def take_action(self, parsed_args): raise exceptions.CommandError(msg) nics.append(nic_info) + if nics: + if auto_or_none: + if len(nics) > 1: + msg = _('Specifying a --nic of auto or none cannot ' + 'be used with any other --nic value.') + raise exceptions.CommandError(msg) + nics = nics[0] + else: + # Default to empty list if nothing was specified, let nova side to + # decide the default behavior. + nics = [] + hints = {} for hint in parsed_args.hint: key, _sep, value = hint.partition('=') diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py index 119ef05c5d..6ecb325564 100644 --- a/openstackclient/tests/functional/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -14,10 +14,10 @@ import time from tempest.lib.common.utils import data_utils +from tempest.lib import exceptions from openstackclient.tests.functional import base from openstackclient.tests.functional.volume.v2 import test_volume -from tempest.lib import exceptions class ServerTests(base.TestCase): @@ -319,7 +319,7 @@ def test_server_reboot(self): self.assertEqual("", raw_output) self.wait_for_status("ACTIVE") - def test_server_boot_from_volume(self): + def test_server_create_from_volume(self): """Test server create from volume, server delete Test steps: @@ -437,14 +437,57 @@ def test_server_boot_from_volume(self): cmd_output['status'], ) - def wait_for_status(self, expected_status='ACTIVE', wait=900, interval=30): + def test_server_create_with_none_network(self): + """Test server create with none network option.""" + server_name = data_utils.rand_name('TestServer') + server = json.loads(self.openstack( + # auto/none enable in nova micro version (v2.37+) + '--os-compute-api-version 2.latest ' + + 'server create -f json ' + + '--flavor ' + self.flavor_name + ' ' + + '--image ' + self.image_name + ' ' + + '--nic none ' + + server_name + )) + self.assertIsNotNone(server["id"]) + self.addCleanup(self.openstack, 'server delete --wait ' + server_name) + self.assertEqual(server_name, server['name']) + self.wait_for_status(server_name=server_name) + server = json.loads(self.openstack( + 'server show -f json ' + server_name + )) + self.assertIsNotNone(server['addresses']) + self.assertEqual('', server['addresses']) + + def test_server_create_with_empty_network_option_latest(self): + """Test server create with empty network option in nova 2.latest.""" + server_name = data_utils.rand_name('TestServer') + try: + self.openstack( + # auto/none enable in nova micro version (v2.37+) + '--os-compute-api-version 2.latest ' + + 'server create -f json ' + + '--flavor ' + self.flavor_name + ' ' + + '--image ' + self.image_name + ' ' + + server_name + ) + except exceptions.CommandFailed as e: + self.assertIn('nics are required after microversion 2.36', + e.stderr) + else: + self.fail('CommandFailed should be raised.') + + def wait_for_status(self, expected_status='ACTIVE', + wait=900, interval=30, server_name=None): """Wait until server reaches expected status.""" # TODO(thowe): Add a server wait command to osc failures = ['ERROR'] total_sleep = 0 opts = self.get_opts(['status']) + if not server_name: + server_name = self.NAME while total_sleep < wait: - status = self.openstack('server show ' + self.NAME + opts) + status = self.openstack('server show ' + server_name + opts) status = status.rstrip() print('Waiting for {} current status: {}'.format(expected_status, status)) @@ -454,7 +497,7 @@ def wait_for_status(self, expected_status='ACTIVE', wait=900, interval=30): time.sleep(interval) total_sleep += interval - status = self.openstack('server show ' + self.NAME + opts) + status = self.openstack('server show ' + server_name + opts) status = status.rstrip() self.assertEqual(status, expected_status) # give it a little bit more time diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 4cac990ec6..249902bca4 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -467,6 +467,152 @@ def test_server_create_with_network(self): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist(), data) + def test_server_create_with_auto_network(self): + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--nic', 'auto', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('nic', ['auto']), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = dict( + meta=None, + files={}, + reservation_id=None, + min_count=1, + max_count=1, + security_groups=[], + userdata=None, + key_name=None, + availability_zone=None, + block_device_mapping_v2=[], + nics='auto', + scheduler_hints={}, + config_drive=None, + ) + # ServerManager.create(name, image, flavor, **kwargs) + self.servers_mock.create.assert_called_with( + self.new_server.name, + self.image, + self.flavor, + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) + + def test_server_create_with_none_network(self): + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--nic', 'none', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('nic', ['none']), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = dict( + meta=None, + files={}, + reservation_id=None, + min_count=1, + max_count=1, + security_groups=[], + userdata=None, + key_name=None, + availability_zone=None, + block_device_mapping_v2=[], + nics='none', + scheduler_hints={}, + config_drive=None, + ) + # ServerManager.create(name, image, flavor, **kwargs) + self.servers_mock.create.assert_called_with( + self.new_server.name, + self.image, + self.flavor, + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) + + def test_server_create_with_conflict_network_options(self): + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--nic', 'none', + '--nic', 'auto', + '--nic', 'port-id=port1', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('nic', ['none', 'auto', 'port-id=port1']), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + get_endpoints = mock.Mock() + get_endpoints.return_value = {'network': []} + self.app.client_manager.auth_ref = mock.Mock() + self.app.client_manager.auth_ref.service_catalog = mock.Mock() + self.app.client_manager.auth_ref.service_catalog.get_endpoints = ( + get_endpoints) + + find_port = mock.Mock() + network_client = self.app.client_manager.network + network_client.find_port = find_port + port_resource = mock.Mock() + port_resource.id = 'port1_uuid' + find_port.return_value = port_resource + + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, parsed_args) + self.assertNotCalled(self.servers_mock.create) + + def test_server_create_with_invalid_network_options(self): + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--nic', 'abcdefgh', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('nic', ['abcdefgh']), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, parsed_args) + self.assertNotCalled(self.servers_mock.create) + @mock.patch.object(common_utils, 'wait_for_status', return_value=True) def test_server_create_with_wait_ok(self, mock_wait_for_status): arglist = [ diff --git a/releasenotes/notes/bug-1663520-d880bfa51ca7b798.yaml b/releasenotes/notes/bug-1663520-d880bfa51ca7b798.yaml new file mode 100644 index 0000000000..4c4d7e3b03 --- /dev/null +++ b/releasenotes/notes/bug-1663520-d880bfa51ca7b798.yaml @@ -0,0 +1,8 @@ +--- +fixes: + - | + Fix ``server create`` command failed when ``--nic`` auto or none. + ``auto`` and ``none`` options was added into --nic argument of server + create command, but that don't work and raise internal error when execute + command. The patch fix that issue. + [Bug `1663520 `_] From ef1a86a802149e0a62c68fb93edf66b802bc72d1 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 17 Feb 2017 12:14:33 -0600 Subject: [PATCH 1552/3095] Fix image selection in server function tests The image selection has been affected by Cirros image changes in DevStack, make the logic moe robust and convert it to JSON. The conversion for the remainder of the file will follow. Change-Id: I8f3318f55ed79d617c3594142f0c086e2bd1a7b1 --- .../tests/functional/compute/v2/test_server.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py index 119ef05c5d..140404de72 100644 --- a/openstackclient/tests/functional/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -37,13 +37,18 @@ def get_flavor(cls): @classmethod def get_image(cls): - # NOTE(rtheis): Get cirros image since functional tests may - # create other images. - images = cls.openstack('image list -c Name -f value').split('\n') + # NOTE(rtheis): Get first Cirros image since functional tests may + # create other images. Image may be named '-uec' or + # '-disk'. + cmd_output = json.loads(cls.openstack( + "image list -f json " + )) server_image = None - for image in images: - if image.startswith('cirros-') and image.endswith('-uec'): - server_image = image + for image in cmd_output: + if (image['Name'].startswith('cirros-') and + (image['Name'].endswith('-uec') or + image['Name'].endswith('-disk'))): + server_image = image['Name'] break return server_image From b399b0406ce4c55345c8f11064a5e85ef73dd8fd Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 17 Feb 2017 13:28:48 -0600 Subject: [PATCH 1553/3095] Remove quota set workaround for SDK <0.9.13 Change-Id: I89732c49e73ac5a789fdbe19536389f7e93ac0e6 --- openstackclient/common/quota.py | 33 +++------------------------------ 1 file changed, 3 insertions(+), 30 deletions(-) diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index d86aec58fb..fa6c576552 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -182,36 +182,9 @@ def take_action(self, parsed_args): project, **volume_kwargs) if network_kwargs: - if hasattr(_quota.Quota, 'allow_get'): - # TODO(huanxuan): Remove this block once the fixed - # SDK Quota class is the minimum required version. - # This is expected to be SDK release 0.9.13 - res = network_client._get_resource( - _quota.Quota, project, **network_kwargs) - if any([res._body.dirty, res._header.dirty]): - request = res._prepare_request(prepend_key=True) - # remove the id in the body - if 'id' in request.body[res.resource_key]: - del request.body[res.resource_key]['id'] - if res.patch_update: - response = network_client.session.patch( - request.uri, - endpoint_filter=res.service, - json=request.body, - headers=request.headers - ) - else: - response = network_client.session.put( - request.uri, - endpoint_filter=res.service, - json=request.body, - headers=request.headers - ) - res._translate_response(response, has_body=True) - else: - network_client.update_quota( - project, - **network_kwargs) + network_client.update_quota( + project, + **network_kwargs) class ShowQuota(command.ShowOne): From 74a35fb005aff18455d4606115a25892c4d6aa7c Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 17 Feb 2017 13:27:24 -0600 Subject: [PATCH 1554/3095] Finish converting server functional tests to JSON format Change-Id: Ic9563bd86feb1f7afd403e49499205a499f0c142 --- .../functional/compute/v2/test_server.py | 453 +++++++++--------- 1 file changed, 231 insertions(+), 222 deletions(-) diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py index 2ef63728ea..4d8019b2f3 100644 --- a/openstackclient/tests/functional/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -12,8 +12,8 @@ import json import time +import uuid -from tempest.lib.common.utils import data_utils from tempest.lib import exceptions from openstackclient.tests.functional import base @@ -27,11 +27,13 @@ class ServerTests(base.TestCase): def get_flavor(cls): # NOTE(rtheis): Get cirros256 or m1.tiny flavors since functional # tests may create other flavors. - flavors = cls.openstack('flavor list -c Name -f value').split('\n') + flavors = json.loads(cls.openstack( + "flavor list -f json " + )) server_flavor = None for flavor in flavors: - if flavor in ['m1.tiny', 'cirros256']: - server_flavor = flavor + if flavor['Name'] in ['m1.tiny', 'cirros256']: + server_flavor = flavor['Name'] break return server_flavor @@ -40,11 +42,11 @@ def get_image(cls): # NOTE(rtheis): Get first Cirros image since functional tests may # create other images. Image may be named '-uec' or # '-disk'. - cmd_output = json.loads(cls.openstack( + images = json.loads(cls.openstack( "image list -f json " )) server_image = None - for image in cmd_output: + for image in images: if (image['Name'].startswith('cirros-') and (image['Name'].endswith('-uec') or image['Name'].endswith('-disk'))): @@ -57,92 +59,84 @@ def get_network(cls): try: # NOTE(rtheis): Get private network since functional tests may # create other networks. - raw_output = cls.openstack('network show private -c id -f value') + cmd_output = json.loads(cls.openstack( + 'network show private -f json' + )) except exceptions.CommandFailed: return '' - return ' --nic net-id=' + raw_output.strip('\n') + return '--nic net-id=' + cmd_output['id'] def server_create(self, name=None): - """Create server. Add cleanup.""" - name = name or data_utils.rand_uuid() - opts = self.get_opts(self.FIELDS) - raw_output = self.openstack('--debug server create --flavor ' + - self.flavor_name + - ' --image ' + self.image_name + - self.network_arg + ' ' + - name + opts) - if not raw_output: + """Create server, with cleanup""" + name = name or uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'server create -f json ' + + '--flavor ' + self.flavor_name + ' ' + + '--image ' + self.image_name + ' ' + + self.network_arg + ' ' + + '--wait ' + + name + )) + if not cmd_output: self.fail('Server has not been created!') self.addCleanup(self.server_delete, name) - - def server_list(self, params=[]): - """List servers.""" - opts = self.get_opts(params) - return self.openstack('server list' + opts) + self.assertEqual( + name, + cmd_output["name"], + ) + return cmd_output def server_delete(self, name): - """Delete server by name.""" + """Delete server by name""" self.openstack('server delete ' + name) def setUp(self): - """Set necessary variables and create server.""" + """Select common resources""" super(ServerTests, self).setUp() self.flavor_name = self.get_flavor() self.image_name = self.get_image() self.network_arg = self.get_network() - self.NAME = data_utils.rand_name('TestServer') - self.OTHER_NAME = data_utils.rand_name('TestServer') - self.HEADERS = ['"Name"'] - self.FIELDS = ['name'] - self.IP_POOL = 'public' - self.server_create(self.NAME) - - def test_server_rename(self): - """Test server rename command. - - Test steps: - 1) Boot server in setUp - 2) Rename server - 3) Check output - 4) Rename server back to original name - """ - raw_output = self.openstack('server set --name ' + self.OTHER_NAME + - ' ' + self.NAME) - self.assertOutput("", raw_output) - self.assertNotIn(self.NAME, self.server_list(['Name'])) - self.assertIn(self.OTHER_NAME, self.server_list(['Name'])) - self.openstack('server set --name ' + self.NAME + ' ' + - self.OTHER_NAME) - def test_server_list(self): - """Test server list command. + """Test server list, set""" + cmd_output = self.server_create() + name1 = cmd_output['name'] + cmd_output = self.server_create() + name2 = cmd_output['name'] + self.wait_for_status(name1, "ACTIVE") + self.wait_for_status(name2, "ACTIVE") - Test steps: - 1) Boot server in setUp - 2) List servers - 3) Check output - """ - opts = self.get_opts(self.HEADERS) - raw_output = self.openstack('server list' + opts) - self.assertIn(self.NAME, raw_output) + cmd_output = json.loads(self.openstack( + 'server list -f json' + )) + col_name = [x["Name"] for x in cmd_output] + self.assertIn(name1, col_name) + self.assertIn(name2, col_name) - def test_server_show(self): - """Test server create, server delete commands""" - name1 = data_utils.rand_name('TestServer') + # Test list --status PAUSED + raw_output = self.openstack('server pause ' + name2) + self.assertEqual("", raw_output) + self.wait_for_status(name2, "PAUSED") cmd_output = json.loads(self.openstack( - 'server create -f json ' + - '--flavor ' + self.flavor_name + ' ' + - '--image ' + self.image_name + ' ' + - self.network_arg + ' ' + - name1 + 'server list -f json ' + + '--status ACTIVE' )) - self.assertIsNotNone(cmd_output["id"]) - self.addCleanup(self.openstack, 'server delete ' + name1) - self.assertEqual( - name1, - cmd_output["name"], - ) + col_name = [x["Name"] for x in cmd_output] + self.assertIn(name1, col_name) + self.assertNotIn(name2, col_name) + cmd_output = json.loads(self.openstack( + 'server list -f json ' + + '--status PAUSED' + )) + col_name = [x["Name"] for x in cmd_output] + self.assertNotIn(name1, col_name) + self.assertIn(name2, col_name) + + def test_server_set(self): + """Test server create, delete, set, show""" + cmd_output = self.server_create() + name = cmd_output['name'] + # self.wait_for_status(name, "ACTIVE") # Have a look at some other fields flavor = json.loads(self.openstack( @@ -170,171 +164,176 @@ def test_server_show(self): cmd_output["image"], ) - def test_server_metadata(self): - """Test command to set server metadata. + # Test properties set + raw_output = self.openstack( + 'server set ' + + '--property a=b --property c=d ' + + name + ) + self.assertOutput('', raw_output) + + cmd_output = json.loads(self.openstack( + 'server show -f json ' + + name + )) + # Really, shouldn't this be a list? + self.assertEqual( + "a='b', c='d'", + cmd_output['properties'], + ) - Test steps: - 1) Boot server in setUp - 2) Set properties for server - 3) Check server properties in server show output - 4) Unset properties for server - 5) Check server properties in server show output - """ - self.wait_for_status("ACTIVE") - # metadata raw_output = self.openstack( - 'server set --property a=b --property c=d ' + self.NAME) - opts = self.get_opts(["name", "properties"]) - raw_output = self.openstack('server show ' + self.NAME + opts) - self.assertEqual(self.NAME + "\na='b', c='d'\n", raw_output) + 'server unset ' + + '--property a ' + + name + ) + cmd_output = json.loads(self.openstack( + 'server show -f json ' + + name + )) + self.assertEqual( + "c='d'", + cmd_output['properties'], + ) + # Test set --name + new_name = uuid.uuid4().hex raw_output = self.openstack( - 'server unset --property a ' + self.NAME) - opts = self.get_opts(["name", "properties"]) - raw_output = self.openstack('server show ' + self.NAME + opts) - self.assertEqual(self.NAME + "\nc='d'\n", raw_output) - - def test_server_suspend_resume(self): - """Test server suspend and resume commands. - - Test steps: - 1) Boot server in setUp - 2) Suspend server - 3) Check for SUSPENDED server status - 4) Resume server - 5) Check for ACTIVE server status - """ - self.wait_for_status("ACTIVE") - # suspend - raw_output = self.openstack('server suspend ' + self.NAME) - self.assertEqual("", raw_output) - self.wait_for_status("SUSPENDED") - # resume - raw_output = self.openstack('server resume ' + self.NAME) - self.assertEqual("", raw_output) - self.wait_for_status("ACTIVE") + 'server set ' + + '--name ' + new_name + ' ' + + name + ) + self.assertOutput("", raw_output) + cmd_output = json.loads(self.openstack( + 'server show -f json ' + + new_name + )) + self.assertEqual( + new_name, + cmd_output["name"], + ) + # Put it back so we clean up properly + raw_output = self.openstack( + 'server set ' + + '--name ' + name + ' ' + + new_name + ) + self.assertOutput("", raw_output) - def test_server_lock_unlock(self): - """Test server lock and unlock commands. + def test_server_actions(self): + """Test server action pairs - Test steps: - 1) Boot server in setUp - 2) Lock server - 3) Check output - 4) Unlock server - 5) Check output + suspend/resume + pause/unpause + rescue/unrescue + lock/unlock """ - self.wait_for_status("ACTIVE") - # lock - raw_output = self.openstack('server lock ' + self.NAME) - self.assertEqual("", raw_output) - # unlock - raw_output = self.openstack('server unlock ' + self.NAME) + cmd_output = self.server_create() + name = cmd_output['name'] + + # suspend + raw_output = self.openstack('server suspend ' + name) self.assertEqual("", raw_output) + self.wait_for_status(name, "SUSPENDED") - def test_server_pause_unpause(self): - """Test server pause and unpause commands. + # resume + raw_output = self.openstack('server resume ' + name) + self.assertEqual("", raw_output) + self.wait_for_status(name, "ACTIVE") - Test steps: - 1) Boot server in setUp - 2) Pause server - 3) Check for PAUSED server status - 4) Unpause server - 5) Check for ACTIVE server status - """ - self.wait_for_status("ACTIVE") # pause - raw_output = self.openstack('server pause ' + self.NAME) + raw_output = self.openstack('server pause ' + name) self.assertEqual("", raw_output) - self.wait_for_status("PAUSED") + self.wait_for_status(name, "PAUSED") + # unpause - raw_output = self.openstack('server unpause ' + self.NAME) + raw_output = self.openstack('server unpause ' + name) self.assertEqual("", raw_output) - self.wait_for_status("ACTIVE") - - def test_server_rescue_unrescue(self): - """Test server rescue and unrescue commands. + self.wait_for_status(name, "ACTIVE") - Test steps: - 1) Boot server in setUp - 2) Rescue server - 3) Check for RESCUE server status - 4) Unrescue server - 5) Check for ACTIVE server status - """ - self.wait_for_status("ACTIVE") # rescue - opts = self.get_opts(["adminPass"]) - raw_output = self.openstack('server rescue ' + self.NAME + opts) + raw_output = self.openstack('server rescue ' + name) self.assertNotEqual("", raw_output) - self.wait_for_status("RESCUE") + self.wait_for_status(name, "RESCUE") + # unrescue - raw_output = self.openstack('server unrescue ' + self.NAME) + raw_output = self.openstack('server unrescue ' + name) + self.assertEqual("", raw_output) + self.wait_for_status(name, "ACTIVE") + + # lock + raw_output = self.openstack('server lock ' + name) self.assertEqual("", raw_output) - self.wait_for_status("ACTIVE") + # NOTE(dtroyer): No way to verify this status??? + + # unlock + raw_output = self.openstack('server unlock ' + name) + self.assertEqual("", raw_output) + # NOTE(dtroyer): No way to verify this status??? def test_server_attach_detach_floating_ip(self): - """Test commands to attach and detach floating IP for server. - - Test steps: - 1) Boot server in setUp - 2) Create floating IP - 3) Add floating IP to server - 4) Check for floating IP in server show output - 5) Remove floating IP from server - 6) Check that floating IP is not in server show output - 7) Delete floating IP - 8) Check output - """ - self.wait_for_status("ACTIVE") + """Test floating ip create/delete; server add/remove floating ip""" + cmd_output = self.server_create() + name = cmd_output['name'] + self.wait_for_status(name, "ACTIVE") + # attach ip - opts = self.get_opts(["id", "floating_ip_address"]) - raw_output = self.openstack('floating ip create ' + - self.IP_POOL + - opts) - ip, ipid, rol = tuple(raw_output.split('\n')) - self.assertNotEqual("", ipid) - self.assertNotEqual("", ip) - raw_output = self.openstack('server add floating ip ' + self.NAME + - ' ' + ip) + cmd_output = json.loads(self.openstack( + 'floating ip create -f json ' + + 'public' + )) + floating_ip = cmd_output['floating_ip_address'] + self.assertNotEqual('', cmd_output['id']) + self.assertNotEqual('', floating_ip) + self.addCleanup( + self.openstack, + 'floating ip delete ' + cmd_output['id'] + ) + + raw_output = self.openstack( + 'server add floating ip ' + + name + ' ' + + floating_ip + ) self.assertEqual("", raw_output) - raw_output = self.openstack('server show ' + self.NAME) - self.assertIn(ip, raw_output) + cmd_output = json.loads(self.openstack( + 'server show -f json ' + + name + )) + self.assertIn( + floating_ip, + cmd_output['addresses'], + ) # detach ip - raw_output = self.openstack('server remove floating ip ' + self.NAME + - ' ' + ip) - self.assertEqual("", raw_output) - raw_output = self.openstack('server show ' + self.NAME) - self.assertNotIn(ip, raw_output) - raw_output = self.openstack('floating ip delete ' + ipid) + raw_output = self.openstack( + 'server remove floating ip ' + + name + ' ' + + floating_ip + ) self.assertEqual("", raw_output) + cmd_output = json.loads(self.openstack( + 'server show -f json ' + + name + )) + self.assertNotIn( + floating_ip, + cmd_output['addresses'], + ) + def test_server_reboot(self): - """Test server reboot command. + """Test server reboot""" + cmd_output = self.server_create() + name = cmd_output['name'] - Test steps: - 1) Boot server in setUp - 2) Reboot server - 3) Check for ACTIVE server status - """ - self.wait_for_status("ACTIVE") # reboot - raw_output = self.openstack('server reboot ' + self.NAME) + raw_output = self.openstack('server reboot ' + name) self.assertEqual("", raw_output) - self.wait_for_status("ACTIVE") - - def test_server_create_from_volume(self): - """Test server create from volume, server delete + self.wait_for_status(name, "ACTIVE") - Test steps: - 1) Create volume from image - 2) Create empty volume - 3) Create server from new volumes - 4) Check for ACTIVE new server status - 5) Check volumes attached to server - """ - # server_image = self.get_image() + def test_server_boot_from_volume(self): + """Test server create from volume, server delete""" # get volume status wait function volume_wait_for = test_volume.VolumeTests( methodName='wait_for', @@ -353,7 +352,7 @@ def test_server_create_from_volume(self): image_size = 1 # create volume from image - volume_name = data_utils.rand_name('volume', self.image_name) + volume_name = uuid.uuid4().hex cmd_output = json.loads(self.openstack( 'volume create -f json ' + '--image ' + self.image_name + ' ' + @@ -369,7 +368,7 @@ def test_server_create_from_volume(self): volume_wait_for("volume", volume_name, "available") # create empty volume - empty_volume_name = data_utils.rand_name('TestVolume') + empty_volume_name = uuid.uuid4().hex cmd_output = json.loads(self.openstack( 'volume create -f json ' + '--size ' + str(image_size) + ' ' + @@ -384,13 +383,14 @@ def test_server_create_from_volume(self): volume_wait_for("volume", empty_volume_name, "available") # create server - server_name = data_utils.rand_name('TestServer') + server_name = uuid.uuid4().hex server = json.loads(self.openstack( 'server create -f json ' + '--flavor ' + self.flavor_name + ' ' + '--volume ' + volume_name + ' ' + '--block-device-mapping vdb=' + empty_volume_name + ' ' + self.network_arg + ' ' + + '--wait ' + server_name )) self.assertIsNotNone(server["id"]) @@ -399,7 +399,6 @@ def test_server_create_from_volume(self): server_name, server['name'], ) - volume_wait_for("server", server_name, "ACTIVE") # check volumes cmd_output = json.loads(self.openstack( @@ -444,7 +443,7 @@ def test_server_create_from_volume(self): def test_server_create_with_none_network(self): """Test server create with none network option.""" - server_name = data_utils.rand_name('TestServer') + server_name = uuid.uuid4().hex server = json.loads(self.openstack( # auto/none enable in nova micro version (v2.37+) '--os-compute-api-version 2.latest ' + @@ -457,7 +456,7 @@ def test_server_create_with_none_network(self): self.assertIsNotNone(server["id"]) self.addCleanup(self.openstack, 'server delete --wait ' + server_name) self.assertEqual(server_name, server['name']) - self.wait_for_status(server_name=server_name) + self.wait_for_status(server_name, "ACTIVE") server = json.loads(self.openstack( 'server show -f json ' + server_name )) @@ -466,7 +465,7 @@ def test_server_create_with_none_network(self): def test_server_create_with_empty_network_option_latest(self): """Test server create with empty network option in nova 2.latest.""" - server_name = data_utils.rand_name('TestServer') + server_name = uuid.uuid4().hex try: self.openstack( # auto/none enable in nova micro version (v2.37+) @@ -482,28 +481,38 @@ def test_server_create_with_empty_network_option_latest(self): else: self.fail('CommandFailed should be raised.') - def wait_for_status(self, expected_status='ACTIVE', - wait=900, interval=30, server_name=None): + def wait_for_status( + self, + name, + expected_status='ACTIVE', + wait=900, + interval=10, + ): """Wait until server reaches expected status.""" # TODO(thowe): Add a server wait command to osc failures = ['ERROR'] total_sleep = 0 - opts = self.get_opts(['status']) - if not server_name: - server_name = self.NAME while total_sleep < wait: - status = self.openstack('server show ' + server_name + opts) - status = status.rstrip() - print('Waiting for {} current status: {}'.format(expected_status, - status)) + cmd_output = json.loads(self.openstack( + 'server show -f json ' + + name + )) + status = cmd_output['status'] + print('Waiting for {}, current status: {}'.format( + expected_status, + status, + )) if status == expected_status: break self.assertNotIn(status, failures) time.sleep(interval) total_sleep += interval - status = self.openstack('server show ' + server_name + opts) - status = status.rstrip() + cmd_output = json.loads(self.openstack( + 'server show -f json ' + + name + )) + status = cmd_output['status'] self.assertEqual(status, expected_status) # give it a little bit more time time.sleep(5) From 73809a98ed63af214ced7d3f51814ca91b122bbe Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Sat, 18 Feb 2017 15:46:43 -0600 Subject: [PATCH 1555/3095] Remove remaining uses of SDK Proxy.session SDK commit Ie67c240e3caa5e100ce07db3862718195c894748 exposed lingering uses of Proxy.session in OSC. Get rid of them. Change-Id: Icab230f1897a446cf3deb0e3d0550d24e11a0ef3 --- openstackclient/common/quota.py | 36 +------------------ openstackclient/network/client.py | 5 +++ openstackclient/network/v2/floating_ip.py | 4 +-- .../tests/unit/network/v2/fakes.py | 4 +++ 4 files changed, 12 insertions(+), 37 deletions(-) diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index fa6c576552..afc6195f93 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -18,8 +18,6 @@ import itertools import sys -from openstack import exceptions as sdk_exceptions -from openstack.network.v2 import quota as _quota from osc_lib.command import command from osc_lib import utils import six @@ -253,39 +251,7 @@ def get_network_quota(self, parsed_args): project = self._get_project(parsed_args) client = self.app.client_manager.network if parsed_args.default: - # TODO(dtroyer): Remove the top of this if block once the - # fixed SDK QuotaDefault class is the minimum - # required version. This is expected to be - # SDK release 0.9.13 - if hasattr(_quota.QuotaDefault, 'project'): - # hack 0.9.11+ - quotadef_obj = client._get_resource( - _quota.QuotaDefault, - project, - ) - quotadef_obj.base_path = quotadef_obj.base_path % { - 'project': project, - } - try: - network_quota = quotadef_obj.get( - client.session, - requires_id=False, - ) - except sdk_exceptions.NotFoundException as e: - raise sdk_exceptions.ResourceNotFound( - message="No %s found for %s" % - (_quota.QuotaDefault.__name__, project), - details=e.details, - response=e.response, - request_id=e.request_id, - url=e.url, - method=e.method, - http_status=e.http_status, - cause=e.cause, - ) - # end hack-around - else: - network_quota = client.get_quota_default(project) + network_quota = client.get_quota_default(project) else: network_quota = client.get_quota(project) return network_quota diff --git a/openstackclient/network/client.py b/openstackclient/network/client.py index c562058da5..9525b947d0 100644 --- a/openstackclient/network/client.py +++ b/openstackclient/network/client.py @@ -44,6 +44,11 @@ def make_client(instance): LOG.debug('Connection: %s', conn) LOG.debug('Network client initialized using OpenStack SDK: %s', conn.network) + + # NOTE(dtroyer): Horrible ugly hack since we don't actually save + # the connection anywhere yet, so stash it in the + # instance directly from here for other uses + instance.sdk_connection = conn return conn.network diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index 980c41c716..41b208aa3b 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -241,7 +241,7 @@ def update_parser_common(self, parser): def take_action_network(self, client, parsed_args): (obj, self.ip_cache) = _find_floating_ip( - client.session, + self.app.client_manager.sdk_connection.session, self.ip_cache, self.r, ignore_missing=False, @@ -472,7 +472,7 @@ def update_parser_common(self, parser): def take_action_network(self, client, parsed_args): (obj, self.ip_cache) = _find_floating_ip( - client.session, + self.app.client_manager.sdk_connection.session, [], parsed_args.floating_ip, ignore_missing=False, diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index 4b266efb42..dcecbeee80 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -71,6 +71,10 @@ def setUp(self): token=fakes.AUTH_TOKEN, ) + self.app.client_manager.sdk_connection = mock.Mock() + self.app.client_manager.sdk_connection.network = \ + self.app.client_manager.network + self.app.client_manager.identity = ( identity_fakes_v3.FakeIdentityv3Client( endpoint=fakes.AUTH_URL, From 1e3faf9f64f36df351a15bd444d3024d9a7beeff Mon Sep 17 00:00:00 2001 From: Anh Tran Date: Mon, 20 Feb 2017 10:20:02 +0700 Subject: [PATCH 1556/3095] Remove unused logging import Change-Id: I9edf4075ffea2e8d42283bd654b74cd4ab4a3638 --- openstackclient/common/client_config.py | 5 ----- openstackclient/network/v2/network_qos_rule.py | 3 --- openstackclient/network/v2/network_qos_rule_type.py | 5 ----- 3 files changed, 13 deletions(-) diff --git a/openstackclient/common/client_config.py b/openstackclient/common/client_config.py index ad03748109..5e1395965e 100644 --- a/openstackclient/common/client_config.py +++ b/openstackclient/common/client_config.py @@ -13,15 +13,10 @@ """OpenStackConfig subclass for argument compatibility""" -import logging - from os_client_config import exceptions as occ_exceptions from osc_lib.cli import client_config -LOG = logging.getLogger(__name__) - - # Sublcass OpenStackConfig in order to munge config values # before auth plugins are loaded class OSC_Config(client_config.OSC_Config): diff --git a/openstackclient/network/v2/network_qos_rule.py b/openstackclient/network/v2/network_qos_rule.py index a662ca18a3..baed042462 100644 --- a/openstackclient/network/v2/network_qos_rule.py +++ b/openstackclient/network/v2/network_qos_rule.py @@ -14,7 +14,6 @@ # under the License. import itertools -import logging import six from osc_lib.command import command @@ -25,8 +24,6 @@ from openstackclient.network import sdk_utils -LOG = logging.getLogger(__name__) - RULE_TYPE_BANDWIDTH_LIMIT = 'bandwidth-limit' RULE_TYPE_DSCP_MARKING = 'dscp-marking' RULE_TYPE_MINIMUM_BANDWIDTH = 'minimum-bandwidth' diff --git a/openstackclient/network/v2/network_qos_rule_type.py b/openstackclient/network/v2/network_qos_rule_type.py index 657eb54b95..52f8e23576 100644 --- a/openstackclient/network/v2/network_qos_rule_type.py +++ b/openstackclient/network/v2/network_qos_rule_type.py @@ -13,17 +13,12 @@ # License for the specific language governing permissions and limitations # under the License. -import logging - from osc_lib.command import command from osc_lib import utils from openstackclient.i18n import _ -LOG = logging.getLogger(__name__) - - class ListNetworkQosRuleType(command.Lister): _description = _("List QoS rule types") From 76c7ab5c85b6c9271664d2c9227cd7f7b785889a Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Mon, 20 Feb 2017 09:37:51 -0600 Subject: [PATCH 1557/3095] Remove text about OSC as a plugin requirement python-openstackclient should not be in a plugin's requirements.txt as it will pull in the OSC dependencies that may not be desirable. Change-Id: Id72745704ec70cab438e766dbac21a838fccb523 --- doc/source/plugins.rst | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/doc/source/plugins.rst b/doc/source/plugins.rst index eafe5e8fa0..054e552fe7 100644 --- a/doc/source/plugins.rst +++ b/doc/source/plugins.rst @@ -190,12 +190,15 @@ unit tests for the plugin commands: Requirements ------------ -OSC must be included in ``requirements.txt`` or ``test-requirements.txt`` -for the plugin project. Update ``requirements.txt`` if the plugin project -considers the CLI a required feature. Update ``test-requirements.txt`` if -the plugin project can be installed as a library with the CLI being an +OSC should be included in the plugin's ``test-requirements.txt`` if +the plugin can be installed as a library with the CLI being an optional feature (available when OSC is also installed). +OSC should not appear in ``requirements.txt`` unless the plugin project +wants OSC and all of its dependencies installed with it. This is +specifically not a good idea for plugins that are also libraries +installed with OpenStack services. + .. code-block:: ini python-openstackclient>=X.Y.Z # Apache-2.0 From 0719348ba1abd0386fafff882d04720b92ea3631 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 1 Feb 2017 17:36:13 -0600 Subject: [PATCH 1558/3095] TODO cleanup: osc-lib osc-lib 1.3.0 is released, which contains these functions that still need to be migrated all the way down to os-client-config. Currently osc-lib 1.2.0 is in global-requirements so this can not be merged yet, but is included here for testing and to be ready for when g-r is unfrozen. Change-Id: I7bc8ed6cf78f38bab4a718ed3e2a88641fa23f27 --- openstackclient/common/client_config.py | 68 ------------------------- 1 file changed, 68 deletions(-) diff --git a/openstackclient/common/client_config.py b/openstackclient/common/client_config.py index 5e1395965e..a22dd0cb3c 100644 --- a/openstackclient/common/client_config.py +++ b/openstackclient/common/client_config.py @@ -13,7 +13,6 @@ """OpenStackConfig subclass for argument compatibility""" -from os_client_config import exceptions as occ_exceptions from osc_lib.cli import client_config @@ -70,70 +69,3 @@ def load_auth_plugin(self, config): config = self._validate_auth(config, loader) auth_plugin = loader.load_from_options(**config['auth']) return auth_plugin - - # TODO(dtroyer): Remove _validate_auth_ksc when it is in osc-lib 1.3.0 - def _validate_auth_ksc(self, config, cloud, fixed_argparse=None): - """Old compatibility hack for OSC, no longer needed/wanted""" - return config - - # TODO(dtroyer): Remove _validate_auth when it is in osc-lib 1.3.0 - def _validate_auth(self, config, loader, fixed_argparse=None): - """Validate auth plugin arguments""" - # May throw a keystoneauth1.exceptions.NoMatchingPlugin - - plugin_options = loader.get_options() - - msgs = [] - prompt_options = [] - for p_opt in plugin_options: - # if it's in config, win, move it and kill it from config dict - # if it's in config.auth but not in config we're good - # deprecated loses to current - # provided beats default, deprecated or not - winning_value = self._find_winning_auth_value(p_opt, config) - if not winning_value: - winning_value = self._find_winning_auth_value( - p_opt, config['auth']) - - # if the plugin tells us that this value is required - # then error if it's doesn't exist now - if not winning_value and p_opt.required: - msgs.append( - 'Missing value {auth_key}' - ' required for auth plugin {plugin}'.format( - auth_key=p_opt.name, plugin=config.get('auth_type'), - ) - ) - - # Clean up after ourselves - for opt in [p_opt.name] + [o.name for o in p_opt.deprecated]: - opt = opt.replace('-', '_') - config.pop(opt, None) - config['auth'].pop(opt, None) - - if winning_value: - # Prefer the plugin configuration dest value if the value's key - # is marked as depreciated. - if p_opt.dest is None: - config['auth'][p_opt.name.replace('-', '_')] = ( - winning_value) - else: - config['auth'][p_opt.dest] = winning_value - - # See if this needs a prompting - if ( - 'prompt' in vars(p_opt) and - p_opt.prompt is not None and - p_opt.dest not in config['auth'] and - self._pw_callback is not None - ): - # Defer these until we know all required opts are present - prompt_options.append(p_opt) - - if msgs: - raise occ_exceptions.OpenStackConfigException('\n'.join(msgs)) - else: - for p_opt in prompt_options: - config['auth'][p_opt.dest] = self._pw_callback(p_opt.prompt) - - return config From 4d5f2c3925068fe49748e05a47c5c9c7e7999b3c Mon Sep 17 00:00:00 2001 From: Justin A Wilson Date: Fri, 30 Sep 2016 13:44:26 -0700 Subject: [PATCH 1559/3095] Add Cinder v3 client support for volumes Initial Cinder v3 support Change-Id: Idd5074832e80697ed0671f06d3291dfd92dbfb08 --- .../tests/functional/volume/v3/__init__.py | 0 .../tests/functional/volume/v3/common.py | 23 +++++++ .../tests/functional/volume/v3/test_qos.py | 23 +++++++ .../functional/volume/v3/test_snapshot.py | 23 +++++++ .../volume/v3/test_transfer_request.py | 24 +++++++ .../tests/functional/volume/v3/test_volume.py | 23 +++++++ .../functional/volume/v3/test_volume_type.py | 23 +++++++ .../tests/unit/volume/v3/__init__.py | 0 openstackclient/volume/client.py | 3 +- openstackclient/volume/v3/__init__.py | 0 .../notes/change-098377fd53cce7a0.yaml | 10 +++ setup.cfg | 63 +++++++++++++++++++ 12 files changed, 214 insertions(+), 1 deletion(-) create mode 100644 openstackclient/tests/functional/volume/v3/__init__.py create mode 100644 openstackclient/tests/functional/volume/v3/common.py create mode 100644 openstackclient/tests/functional/volume/v3/test_qos.py create mode 100644 openstackclient/tests/functional/volume/v3/test_snapshot.py create mode 100644 openstackclient/tests/functional/volume/v3/test_transfer_request.py create mode 100644 openstackclient/tests/functional/volume/v3/test_volume.py create mode 100644 openstackclient/tests/functional/volume/v3/test_volume_type.py create mode 100644 openstackclient/tests/unit/volume/v3/__init__.py create mode 100644 openstackclient/volume/v3/__init__.py create mode 100644 releasenotes/notes/change-098377fd53cce7a0.yaml diff --git a/openstackclient/tests/functional/volume/v3/__init__.py b/openstackclient/tests/functional/volume/v3/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openstackclient/tests/functional/volume/v3/common.py b/openstackclient/tests/functional/volume/v3/common.py new file mode 100644 index 0000000000..57a62df643 --- /dev/null +++ b/openstackclient/tests/functional/volume/v3/common.py @@ -0,0 +1,23 @@ +# 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 os + +from openstackclient.tests.functional import base + + +class BaseVolumeTests(base.TestCase): + """Base class for Volume functional tests. """ + + @classmethod + def setUpClass(cls): + os.environ['OS_VOLUME_API_VERSION'] = '3' diff --git a/openstackclient/tests/functional/volume/v3/test_qos.py b/openstackclient/tests/functional/volume/v3/test_qos.py new file mode 100644 index 0000000000..46965ced08 --- /dev/null +++ b/openstackclient/tests/functional/volume/v3/test_qos.py @@ -0,0 +1,23 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from openstackclient.tests.functional.volume.v2 import test_qos as v2 +import os + + +class QosTests(v2.QosTests): + """Functional tests for volume qos. """ + + @classmethod + def setUpClass(cls): + super(QosTests, cls).setUpClass() + os.environ['OS_VOLUME_API_VERSION'] = '3' diff --git a/openstackclient/tests/functional/volume/v3/test_snapshot.py b/openstackclient/tests/functional/volume/v3/test_snapshot.py new file mode 100644 index 0000000000..bf05b9de9d --- /dev/null +++ b/openstackclient/tests/functional/volume/v3/test_snapshot.py @@ -0,0 +1,23 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from openstackclient.tests.functional.volume.v2 import test_snapshot as v2 +import os + + +class VolumeSnapshotTests(v2.VolumeSnapshotTests): + """Functional tests for volume snapshot. """ + + @classmethod + def setUpClass(cls): + super(VolumeSnapshotTests, cls).setUpClass() + os.environ['OS_VOLUME_API_VERSION'] = '3' diff --git a/openstackclient/tests/functional/volume/v3/test_transfer_request.py b/openstackclient/tests/functional/volume/v3/test_transfer_request.py new file mode 100644 index 0000000000..7b54dd20f5 --- /dev/null +++ b/openstackclient/tests/functional/volume/v3/test_transfer_request.py @@ -0,0 +1,24 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from openstackclient.tests.functional.volume.v2 import test_transfer_request \ + as v2 +import os + + +class TransferRequestTests(v2.TransferRequestTests): + """Functional tests for transfer request. """ + + @classmethod + def setUpClass(cls): + super(TransferRequestTests, cls).setUpClass() + os.environ['OS_VOLUME_API_VERSION'] = '3' diff --git a/openstackclient/tests/functional/volume/v3/test_volume.py b/openstackclient/tests/functional/volume/v3/test_volume.py new file mode 100644 index 0000000000..333826d833 --- /dev/null +++ b/openstackclient/tests/functional/volume/v3/test_volume.py @@ -0,0 +1,23 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from openstackclient.tests.functional.volume.v2 import test_volume as v2 +import os + + +class VolumeTests(v2.VolumeTests): + """Functional tests for volume. """ + + @classmethod + def setUpClass(cls): + super(VolumeTests, cls).setUpClass() + os.environ['OS_VOLUME_API_VERSION'] = '3' diff --git a/openstackclient/tests/functional/volume/v3/test_volume_type.py b/openstackclient/tests/functional/volume/v3/test_volume_type.py new file mode 100644 index 0000000000..f10e64b426 --- /dev/null +++ b/openstackclient/tests/functional/volume/v3/test_volume_type.py @@ -0,0 +1,23 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from openstackclient.tests.functional.volume.v2 import test_volume_type as v2 +import os + + +class VolumeTypeTests(v2.VolumeTypeTests): + """Functional tests for volume type. """ + + @classmethod + def setUpClass(cls): + super(VolumeTypeTests, cls).setUpClass() + os.environ['OS_VOLUME_API_VERSION'] = '3' diff --git a/openstackclient/tests/unit/volume/v3/__init__.py b/openstackclient/tests/unit/volume/v3/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openstackclient/volume/client.py b/openstackclient/volume/client.py index ade5a95f55..c4b0dfcabd 100644 --- a/openstackclient/volume/client.py +++ b/openstackclient/volume/client.py @@ -27,7 +27,8 @@ API_NAME = "volume" API_VERSIONS = { "1": "cinderclient.v1.client.Client", - "2": "cinderclient.v2.client.Client" + "2": "cinderclient.v2.client.Client", + "3": "cinderclient.v3.client.Client", } diff --git a/openstackclient/volume/v3/__init__.py b/openstackclient/volume/v3/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/releasenotes/notes/change-098377fd53cce7a0.yaml b/releasenotes/notes/change-098377fd53cce7a0.yaml new file mode 100644 index 0000000000..dc41a7bd79 --- /dev/null +++ b/releasenotes/notes/change-098377fd53cce7a0.yaml @@ -0,0 +1,10 @@ +--- +features: + - | + Added support for Volume API v3 for the following block storage command + resources: ``consistency group``, ``consistency group snapshot``, + ``volume``, ``volume backup``, ``volume host``, ``volume snapshot``, + ``volume type``, ``volume qos``, ``volume service``, + ``volume transfer request``. Note that microversion support for Volume API + v3 is not yet implemented, each command will assume the API version is + ``3.0``. diff --git a/setup.cfg b/setup.cfg index dbe921aa4a..ce4f017319 100644 --- a/setup.cfg +++ b/setup.cfg @@ -602,6 +602,69 @@ openstack.volume.v2 = volume_transfer_request_list = openstackclient.volume.v2.volume_transfer_request:ListTransferRequest volume_transfer_request_show = openstackclient.volume.v2.volume_transfer_request:ShowTransferRequest +openstack.volume.v3 = + consistency_group_add_volume = openstackclient.volume.v2.consistency_group:AddVolumeToConsistencyGroup + consistency_group_create = openstackclient.volume.v2.consistency_group:CreateConsistencyGroup + consistency_group_delete = openstackclient.volume.v2.consistency_group:DeleteConsistencyGroup + consistency_group_list = openstackclient.volume.v2.consistency_group:ListConsistencyGroup + consistency_group_remove_volume = openstackclient.volume.v2.consistency_group:RemoveVolumeFromConsistencyGroup + consistency_group_set = openstackclient.volume.v2.consistency_group:SetConsistencyGroup + consistency_group_show = openstackclient.volume.v2.consistency_group:ShowConsistencyGroup + + consistency_group_snapshot_create = openstackclient.volume.v2.consistency_group_snapshot:CreateConsistencyGroupSnapshot + consistency_group_snapshot_delete = openstackclient.volume.v2.consistency_group_snapshot:DeleteConsistencyGroupSnapshot + consistency_group_snapshot_list = openstackclient.volume.v2.consistency_group_snapshot:ListConsistencyGroupSnapshot + consistency_group_snapshot_show = openstackclient.volume.v2.consistency_group_snapshot:ShowConsistencyGroupSnapshot + + volume_create = openstackclient.volume.v2.volume:CreateVolume + volume_delete = openstackclient.volume.v2.volume:DeleteVolume + volume_list = openstackclient.volume.v2.volume:ListVolume + volume_migrate = openstackclient.volume.v2.volume:MigrateVolume + volume_set = openstackclient.volume.v2.volume:SetVolume + volume_show = openstackclient.volume.v2.volume:ShowVolume + volume_unset = openstackclient.volume.v2.volume:UnsetVolume + + volume_backup_create = openstackclient.volume.v2.backup:CreateVolumeBackup + volume_backup_delete = openstackclient.volume.v2.backup:DeleteVolumeBackup + volume_backup_list = openstackclient.volume.v2.backup:ListVolumeBackup + volume_backup_restore = openstackclient.volume.v2.backup:RestoreVolumeBackup + volume_backup_set = openstackclient.volume.v2.backup:SetVolumeBackup + volume_backup_show = openstackclient.volume.v2.backup:ShowVolumeBackup + + volume_host_set = openstackclient.volume.v2.volume_host:SetVolumeHost + + volume_snapshot_create = openstackclient.volume.v2.volume_snapshot:CreateVolumeSnapshot + volume_snapshot_delete = openstackclient.volume.v2.volume_snapshot:DeleteVolumeSnapshot + volume_snapshot_list = openstackclient.volume.v2.volume_snapshot:ListVolumeSnapshot + volume_snapshot_set = openstackclient.volume.v2.volume_snapshot:SetVolumeSnapshot + volume_snapshot_show = openstackclient.volume.v2.volume_snapshot:ShowVolumeSnapshot + volume_snapshot_unset = openstackclient.volume.v2.volume_snapshot:UnsetVolumeSnapshot + + volume_type_create = openstackclient.volume.v2.volume_type:CreateVolumeType + volume_type_delete = openstackclient.volume.v2.volume_type:DeleteVolumeType + volume_type_list = openstackclient.volume.v2.volume_type:ListVolumeType + volume_type_set = openstackclient.volume.v2.volume_type:SetVolumeType + volume_type_show = openstackclient.volume.v2.volume_type:ShowVolumeType + volume_type_unset = openstackclient.volume.v2.volume_type:UnsetVolumeType + + volume_qos_associate = openstackclient.volume.v2.qos_specs:AssociateQos + volume_qos_create = openstackclient.volume.v2.qos_specs:CreateQos + volume_qos_delete = openstackclient.volume.v2.qos_specs:DeleteQos + volume_qos_disassociate = openstackclient.volume.v2.qos_specs:DisassociateQos + volume_qos_list = openstackclient.volume.v2.qos_specs:ListQos + volume_qos_set = openstackclient.volume.v2.qos_specs:SetQos + volume_qos_show = openstackclient.volume.v2.qos_specs:ShowQos + volume_qos_unset = openstackclient.volume.v2.qos_specs:UnsetQos + + volume_service_list = openstackclient.volume.v2.service:ListService + volume_service_set = openstackclient.volume.v2.service:SetService + + volume_transfer_request_accept = openstackclient.volume.v2.volume_transfer_request:AcceptTransferRequest + volume_transfer_request_create = openstackclient.volume.v2.volume_transfer_request:CreateTransferRequest + volume_transfer_request_delete = openstackclient.volume.v2.volume_transfer_request:DeleteTransferRequest + volume_transfer_request_list = openstackclient.volume.v2.volume_transfer_request:ListTransferRequest + volume_transfer_request_show = openstackclient.volume.v2.volume_transfer_request:ShowTransferRequest + [build_sphinx] source-dir = doc/source build-dir = doc/build From 3a48183c01ea04e58700dceb9ae8f557e4cbaf84 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Mon, 20 Feb 2017 16:33:29 -0500 Subject: [PATCH 1560/3095] add swift and nova to osc mapping Change-Id: If3ab811868aaf8c9391a2190ff0af63b0e0ea559 --- doc/source/data/nova.csv | 140 ++++++++++++++++++++++++++++++++++++++ doc/source/data/swift.csv | 10 +++ doc/source/decoder.rst | 24 +++++++ doc/source/index.rst | 1 + 4 files changed, 175 insertions(+) create mode 100644 doc/source/data/nova.csv create mode 100644 doc/source/data/swift.csv create mode 100644 doc/source/decoder.rst diff --git a/doc/source/data/nova.csv b/doc/source/data/nova.csv new file mode 100644 index 0000000000..559803911d --- /dev/null +++ b/doc/source/data/nova.csv @@ -0,0 +1,140 @@ +add-fixed-ip,server add fixed ip,Add new IP address on a network to server. +add-secgroup,server add security group,Add a Security Group to a server. +agent-create,compute agent create,Create new agent build. +agent-delete,compute agent delete,Delete existing agent build. +agent-list,compute agent list,List all builds. +agent-modify,compute agent set,Modify existing agent build. +aggregate-add-host,aggregate add host,Add the host to the specified aggregate. +aggregate-create,aggregate create,Create a new aggregate with the specified details. +aggregate-delete,aggregate delete,Delete the aggregate. +aggregate-list,aggregate list,Print a list of all aggregates. +aggregate-remove-host,aggregate remove host,Remove the specified host from the specified aggregate. +aggregate-set-metadata,aggregate set / unset,Update the metadata associated with the aggregate. +aggregate-show,aggregate show,Show details of the specified aggregate. +aggregate-update,aggregate set / unset,Update the aggregate's name and optionally availability zone. +availability-zone-list,availability zone list,List all the availability zones. +backup,server backup create,Backup a server by creating a 'backup' type snapshot. +boot,server create,Boot a new server. +cell-capacities,,Get cell capacities for all cells or a given cell. +cell-show,,Show details of a given cell. +clear-password,server set --root-password,Clear the admin password for a server from the metadata server. +cloudpipe-configure,WONTFIX,Update the VPN IP/port of a cloudpipe instance. +cloudpipe-create,WONTFIX,Create a cloudpipe instance for the given project. +cloudpipe-list,WONTFIX,Print a list of all cloudpipe instances. +console-log,console log show,Get console log output of a server. +delete,server delete,Immediately shut down and delete specified server(s). +diagnostics,openstack server show --diagnostics,Retrieve server diagnostics. +evacuate,,Evacuate server from failed host. +flavor-access-add,,Add flavor access for the given tenant. +flavor-access-list,,Print access information about the given flavor. +flavor-access-remove,,Remove flavor access for the given tenant. +flavor-create,flavor create,Create a new flavor. +flavor-delete,flavor delete,Delete a specific flavor +flavor-key,flavor set / unset,Set or unset extra_spec for a flavor. +flavor-list,flavor list,Print a list of available 'flavors' +flavor-show,flavor show,Show details about the given flavor. +floating-ip-associate,server add floating ip,Associate a floating IP address to a server. +floating-ip-disassociate,server remove floating ip,Disassociate a floating IP address from a server. +force-delete,server delete,Force delete a server. +get-mks-console,console url show --mks,Get an MKS console to a server. +get-password,WONTFIX,Get the admin password for a server. +get-rdp-console,console url show --rdp,Get a rdp console to a server. +get-serial-console,console url show --serial,Get a serial console to a server. +get-spice-console,console url show --spice,Get a spice console to a server. +get-vnc-console,console url show --novnc | --xvpvnc,Get a vnc console to a server. +host-action,,Perform a power action on a host. +host-describe,host show,Describe a specific host. +host-evacuate,,Evacuate all instances from failed host. +host-evacuate-live,,Live migrate all instances of the specified host to other available hosts. +host-list,host list,List all hosts by service. +host-meta,host set / unset,Set or Delete metadata on all instances of a host. +host-servers-migrate,,Cold migrate all instances off the specified host to other available hosts. +host-update,host set,Update host settings. +hypervisor-list,hypervisor list,List hypervisors. +hypervisor-servers,,List servers belonging to specific hypervisors. +hypervisor-show,hypervisor show,Display the details of the specified hypervisor. +hypervisor-stats,hypervisor stats show,Get hypervisor statistics over all compute nodes. +hypervisor-uptime,,Display the uptime of the specified hypervisor. +image-create,server image create,Create a new image by taking a snapshot of a running server. +instance-action,,Show an action. +instance-action-list,,List actions on a server. +interface-attach,,Attach a network interface to a server. +interface-detach,,Detach a network interface from a server. +interface-list,,List interfaces attached to a server. +keypair-add,keypair create,Create a new key pair for use with servers. +keypair-delete,keypair delete,Delete keypair given by its name. +keypair-list,keypair list,Print a list of keypairs for a user +keypair-show,keypair show,Show details about the given keypair. +limits,limits show,Print rate and absolute limits. +list,server list,List active servers. +list-extensions,extension list,List all the os-api extensions that are available. +list-secgroup,security group list,List Security Group(s) of a server. +live-migration,,Migrate running server to a new machine. +live-migration-abort,,Abort an on-going live migration. +live-migration-force-comp,,Force on-going live migration to complete. +lock,server lock,Lock a server. +meta,server set --property / unset,Set or delete metadata on a server. +migrate,server migrate,Migrate a server. The new host will be selected by the scheduler. +migration-list,,Print a list of migrations. +pause,server pause,Pause a server. +quota-class-show,,List the quotas for a quota class. +quota-class-update,quota set --class,Update the quotas for a quota class. +quota-defaults,quota list,List the default quotas for a tenant. +quota-delete,quota set,Delete quota for a tenant/user so their quota will Revert back to default. +quota-show,quota show,List the quotas for a tenant/user. +quota-update,quota set,Update the quotas for a tenant/user. +reboot,server reboot,Reboot a server. +rebuild,server rebuild,"Shutdown, re-image, and re-boot a server." +refresh-network,WONTFIX,Refresh server network information. +remove-fixed-ip,server remove fixed ip,Remove an IP address from a server. +remove-secgroup,server remove security group,Remove a Security Group from a server. +rescue,server rescue,Reboots a server into rescue mode. +reset-network,WONTFIX,Reset network of a server. +reset-state,server set --state,Reset the state of a server. +resize,server resize,Resize a server. +resize-confirm,server resize --confirm,Confirm a previous resize. +resize-revert,server resize --revert,Revert a previous resize. +restore,server restore,Restore a soft-deleted server. +resume,server resume,Resume a server. +server-group-create,server group create,Create a new server group with the specified details. +server-group-delete,server group delete,Delete specific server group(s). +server-group-get,server group show,Get a specific server group. +server-group-list,server group list,Print a list of all server groups. +server-migration-list,,Get the migrations list of specified server. +server-migration-show,,Get the migration of specified server. +server-tag-add,,Add one or more tags to a server. +server-tag-delete,,Delete one or more tags from a server. +server-tag-delete-all,,Delete all tags from a server. +server-tag-list,,Get list of tags from a server. +server-tag-set,,Set list of tags to a server. +service-delete,compute service delete,Delete the service. +service-disable,compute service set --disable,Disable the service. +service-enable,compute service set --enable,Enable the service. +service-force-down,compute service set --force,Force service to down. +service-list,compute service list,Show a list of all running services. +set-password,server set --root-password,Change the admin password for a server. +shelve,server shelve,Shelve a server. +shelve-offload,,Remove a shelved server from the compute node. +show,server show,Show details about the given server. +ssh,server ssh,SSH into a server. +start,server start,Start the server(s). +stop,server stop,Stop the server(s). +suspend,server suspend,Suspend a server. +trigger-crash-dump,server dump create,Trigger crash dump in an instance. +unlock,server unlock,Unlock a server. +unpause,server unpause,Unpause a server. +unrescue,server unrescue,Restart the server from normal boot disk again. +unshelve,server unshelve,Unshelve a server. +update,server set / unset,Update the name or the description for a server. +usage,usage show,Show usage data for a single tenant. +usage-list,usage list,List usage data for all tenants. +version-list,,List all API versions. +virtual-interface-list,,Show virtual interface info about the given server. +volume-attach,server add volume,Attach a volume to a server. +volume-attachments,server show,List all the volumes attached to a server. +volume-detach,server remove volume,Detach a volume from a server. +volume-update,,Update volume attachment. +x509-create-cert,WONTFIX,Create x509 cert for a user in tenant. +x509-get-root-cert,WONTFIX,Fetch the x509 root cert. +bash-completion,complete,Prints all of the commands and options to +help,help,Display help about this program or one of its subcommands. \ No newline at end of file diff --git a/doc/source/data/swift.csv b/doc/source/data/swift.csv new file mode 100644 index 0000000000..681474125b --- /dev/null +++ b/doc/source/data/swift.csv @@ -0,0 +1,10 @@ +delete,object delete / container delete,Delete a container or objects within a container. +download,object save / container save,Download objects from containers. +list,object list / container list,Lists the containers for the account or the objects for a container. +post,container create / object set / container set / object store account set,"Updates meta information for the account, container, or object." +copy,,"Copies object, optionally adds meta." +stat,object show / container show / object store account show,"Displays information for the account, container, or object." +upload,object create,Uploads files or directories to the given container. +capabilities,,List cluster capabilities. +tempurl,,Create a temporary URL. +auth,WONTFIX,Display auth related environment variables. diff --git a/doc/source/decoder.rst b/doc/source/decoder.rst new file mode 100644 index 0000000000..adeb99e74c --- /dev/null +++ b/doc/source/decoder.rst @@ -0,0 +1,24 @@ +================= +OSC Mapping Table +================= + +The following is an incomplete mapping between legacy OpenStack CLIs and +OpenStackClient. Think of it as a magic decoder ring if you were using the +legacy CLIs and want to transition to OpenStack CLI. Command options are only +shown when necessary. + +``nova CLI`` +------------ + +.. csv-table:: + :header: "Nova CLI", "OSC Equivalent", "Description" + :widths: 25, 25, 50 + :file: data/nova.csv + +``swift CLI`` +------------- + +.. csv-table:: + :header: "Swift CLI", "OSC Equivalent", "Description" + :widths: 25, 25, 50 + :file: data/swift.csv diff --git a/doc/source/index.rst b/doc/source/index.rst index c8fc6f2718..2a87c9991b 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -23,6 +23,7 @@ User Documentation interactive humaninterfaceguide backwards-incompatible + decoder Getting Started --------------- From c9419f00f064f1cbaf9488dcd23fc63656d4b154 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Tue, 21 Feb 2017 01:16:01 -0500 Subject: [PATCH 1561/3095] add volume->osc mapping Change-Id: Ia773cf66c9949433eb5e5ab5c58b7c871c17fc22 --- doc/source/data/cinder.csv | 104 +++++++++++++++++++++++++++++++++++++ doc/source/decoder.rst | 8 +++ 2 files changed, 112 insertions(+) create mode 100644 doc/source/data/cinder.csv diff --git a/doc/source/data/cinder.csv b/doc/source/data/cinder.csv new file mode 100644 index 0000000000..385c738681 --- /dev/null +++ b/doc/source/data/cinder.csv @@ -0,0 +1,104 @@ +absolute-limits,limits show --absolute,Lists absolute limits for a user. +availability-zone-list,availability zone list --volume,Lists all availability zones. +backup-create,volume backup create,Creates a volume backup. +backup-delete,volume backup delete,Removes a backup. +backup-export,volume backup export,Export backup metadata record. +backup-import,volume backup import,Import backup metadata record. +backup-list,volume backup list,Lists all backups. +backup-reset-state,volume backup set --state,Explicitly updates the backup state. +backup-restore,volume backup restore,Restores a backup. +backup-show,volume backup show,Show backup details. +cgsnapshot-create,consistency group snapshot create,Creates a cgsnapshot. +cgsnapshot-delete,consistency group snapshot delete,Removes one or more cgsnapshots. +cgsnapshot-list,consistency group snapshot list,Lists all cgsnapshots. +cgsnapshot-show,consistency group snapshot show,Shows cgsnapshot details. +consisgroup-create,consistency group create,Creates a consistency group. +consisgroup-create-from-src,consistency group create --consistency-group-snapshot,Creates a consistency group from a cgsnapshot or a source CG +consisgroup-delete,consistency group delete,Removes one or more consistency groups. +consisgroup-list,consistency group list,Lists all consistencygroups. +consisgroup-show,consistency group show,Shows details of a consistency group. +consisgroup-update,consistency group set,Updates a consistencygroup. +create,volume create,Creates a volume. +credentials,WONTFIX,Shows user credentials returned from auth. +delete,volume delete,Removes one or more volumes. +encryption-type-create,volume type create --encryption-provider --enc..,Creates encryption type for a volume type. Admin only. +encryption-type-delete,volume type delete,Deletes encryption type for a volume type. Admin only. +encryption-type-list,volume type list --encryption-type,Shows encryption type details for volume types. Admin only. +encryption-type-show,volume type list --encryption-show,Shows encryption type details for volume type. Admin only. +encryption-type-update,volume type set --encryption-provider --enc..,Update encryption type information for a volume type (Admin Only). +endpoints,catalog list,Discovers endpoints registered by authentication service. +extend,volume set --size,Attempts to extend size of an existing volume. +extra-specs-list,volume types list --long,Lists current volume types and extra specs. +failover-host,volume host failover,Failover a replicating cinder-volume host. +force-delete,volume delete --force,"Attempts force-delete of volume, regardless of state." +freeze-host,volume host set --disable,Freeze and disable the specified cinder-volume host. +get-capabilities,,Show backend volume stats and properties. Admin only. +get-pools,,Show pool information for backends. Admin only. +image-metadata,volume set --image-property,Sets or deletes volume image metadata. +image-metadata-show,volume show,Shows volume image metadata. +list,volume list,Lists all volumes. +manage,volume create --remote-source k=v,Manage an existing volume. +metadata,volume set --property k=v / volume unset --property k,Sets or deletes volume metadata. +metadata-show,volume show,Shows volume metadata. +metadata-update-all,volume set --property k=v,Updates volume metadata. +migrate,volume migrate --host --force-copy --lock-volume ,Migrates volume to a new host. +qos-associate,volume qos associate,Associates qos specs with specified volume type. +qos-create,volume qos create,Creates a qos specs. +qos-delete,volume qos delete,Deletes a specified qos specs. +qos-disassociate,volume qos disassociate,Disassociates qos specs from specified volume type. +qos-disassociate-all,volume qos disassociate --all,Disassociates qos specs from all associations. +qos-get-association,volume qos show,Gets all associations for specified qos specs. +qos-key,volume qos set --property k=v / volume qos unset --property k,Sets or unsets specifications for a qos spec +qos-list,volume qos list,Lists qos specs. +qos-show,volume qos show,Shows a specified qos specs. +quota-class-show,quota show --class,Lists quotas for a quota class. +quota-class-update,quota set --class,Updates quotas for a quota class. +quota-defaults,quota show --default,Lists default quotas for a tenant. +quota-delete,,Delete the quotas for a tenant. +quota-show,quota show,Lists quotas for a tenant. +quota-update,quota set,Updates quotas for a tenant. +quota-usage,,Lists quota usage for a tenant. +rate-limits,limits show --rate,Lists rate limits for a user. +readonly-mode-update,volume set --read-only-mode | --read-write-mode,Updates volume read-only access-mode flag. +rename,volume set --name,Renames a volume. +replication-promote,WONTFIX,Promote a secondary volume to primary for a relationship +replication-reenable,WONTFIX,Sync the secondary volume with primary for a relationship +reset-state,volume set --state,Explicitly updates the volume state. +retype,volume type set --type,Changes the volume type for a volume. +service-disable,volume service set --disable,Disables the service. +service-enable,volume service set --enable,Enables the service. +service-list,volume service list,Lists all services. Filter by host and service binary. +set-bootable,volume set --bootable / --not-bootable,Update bootable status of a volume. +show,volume show,Shows volume details. +snapshot-create,snapshot create,Creates a snapshot. +snapshot-delete,snapshot delete,Remove one or more snapshots. +snapshot-list,snapshot list,Lists all snapshots. +snapshot-manage,volume snapshot create --remote-source ,Manage an existing snapshot. +snapshot-metadata,snapshot set --property k=v / snapshot unset --property k,Sets or deletes snapshot metadata. +snapshot-metadata-show,snapshot show,Shows snapshot metadata. +snapshot-metadata-update-all,snapshot set --property k=v,Updates snapshot metadata. +snapshot-rename,snapshot set --name,Renames a snapshot. +snapshot-reset-state,snapshot set --state,Explicitly updates the snapshot state. +snapshot-show,snapshot show,Shows snapshot details. +snapshot-unmanage,volume snapshot delete --remote,Stop managing a snapshot. +thaw-host,volume host set --enable,Thaw and enable the specified cinder-volume host. +transfer-accept,volume transfer accept,Accepts a volume transfer. +transfer-create,volume transfer create,Creates a volume transfer. +transfer-delete,volume transfer delete,Undoes a transfer. +transfer-list,volume transfer list,Lists all transfers. +transfer-show,volume transfer show,Show transfer details. +type-access-add,volume type set --project,Adds volume type access for the given project. +type-access-list,volume type show,Print access information about the given volume type. +type-access-remove,volume type unset --project,Removes volume type access for the given project. +type-create,volume type create,Creates a volume type. +type-default,volume type list --default,List the default volume type. +type-delete,volume type delete,Deletes a specified volume type. +type-key,volume type set --property k=v / volume type unset --property k,Sets or unsets extra_spec for a volume type. +type-list,volume type list,Lists available 'volume types'. +type-show,volume type show,Show volume type details. +type-update,volume type set,"Updates volume type name, description, and/or is_public." +unmanage,volume delete --remote,Stop managing a volume. +upload-to-image,image create --volume,Uploads volume to Image Service as an image. +bash-completion,complete,Prints arguments for bash_completion. +help,help,Shows help about this program or one of its subcommands. +list-extensions,extension list --volume,Lists all available os-api extensions. \ No newline at end of file diff --git a/doc/source/decoder.rst b/doc/source/decoder.rst index adeb99e74c..23c16ca588 100644 --- a/doc/source/decoder.rst +++ b/doc/source/decoder.rst @@ -7,6 +7,14 @@ OpenStackClient. Think of it as a magic decoder ring if you were using the legacy CLIs and want to transition to OpenStack CLI. Command options are only shown when necessary. +``cinder CLI`` +-------------- + +.. csv-table:: + :header: "Cinder CLI", "OSC Equivalent", "Description" + :widths: 25, 25, 50 + :file: data/cinder.csv + ``nova CLI`` ------------ From c7e0948a2ded4a7486331bc306092c6425f1d0c5 Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Mon, 23 Jan 2017 15:41:01 +0800 Subject: [PATCH 1562/3095] Rework port functional tests on json output format Some functional tests try to parse the CLI table output format, that cause much work on parse string by using regular expression. Using json format in functional tests is better and easier way, the patch rework for port related tests. Change-Id: I0ec997bf63da4925742848c593db09d89655ca34 --- .../tests/functional/image/v2/test_image.py | 2 +- .../tests/functional/network/v2/test_port.py | 192 +++++++----------- 2 files changed, 77 insertions(+), 117 deletions(-) diff --git a/openstackclient/tests/functional/image/v2/test_image.py b/openstackclient/tests/functional/image/v2/test_image.py index 6faff94a32..b6baf570f8 100644 --- a/openstackclient/tests/functional/image/v2/test_image.py +++ b/openstackclient/tests/functional/image/v2/test_image.py @@ -84,7 +84,7 @@ def test_image_members(self): self.openstack( 'image set --accept ' + self.NAME) shared_img_list = self.parse_listing( - self.openstack('image list --shared', self.get_opts(['name'])) + self.openstack('image list --shared') ) self.assertIn(self.NAME, [img['Name'] for img in shared_img_list]) diff --git a/openstackclient/tests/functional/network/v2/test_port.py b/openstackclient/tests/functional/network/v2/test_port.py index e100bd8296..818076d675 100644 --- a/openstackclient/tests/functional/network/v2/test_port.py +++ b/openstackclient/tests/functional/network/v2/test_port.py @@ -10,7 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. -import re +import json import uuid from openstackclient.tests.functional import base @@ -20,21 +20,11 @@ class PortTests(base.TestCase): """Functional tests for port. """ NAME = uuid.uuid4().hex NETWORK_NAME = uuid.uuid4().hex - HEADERS = ['Name'] - FIELDS = ['name'] @classmethod def setUpClass(cls): - # Set up some regex for matching below - cls.re_id = re.compile("\s+id\s+\|\s+(\S+)") - cls.re_name = re.compile("\s+name\s+\|\s+([^|]+?)\s+\|") - cls.re_description = re.compile("\s+description\s+\|\s+([^|]+?)\s+\|") - cls.re_mac_address = re.compile("\s+mac_address\s+\|\s+([^|]+?)\s+\|") - cls.re_state = re.compile("\s+admin_state_up\s+\|\s+([^|]+?)\s+\|") - # Create a network for the port - raw_output = cls.openstack('network create ' + cls.NETWORK_NAME) - cls.network_id = re.search(cls.re_id, raw_output).group(1) + cls.openstack('network create ' + cls.NETWORK_NAME) @classmethod def tearDownClass(cls): @@ -43,33 +33,23 @@ def tearDownClass(cls): def test_port_delete(self): """Test create, delete multiple""" - raw_output = self.openstack( - 'port create --network ' + self.NETWORK_NAME + ' ' + self.NAME - ) - re_id1 = re.search(self.re_id, raw_output) - self.assertIsNotNone(re_id1) - id1 = re_id1.group(1) - self.assertIsNotNone( - re.search(self.re_mac_address, raw_output).group(1), - ) - self.assertEqual( - self.NAME, - re.search(self.re_name, raw_output).group(1), - ) - - raw_output = self.openstack( - 'port create ' + - '--network ' + self.NETWORK_NAME + ' ' + + json_output = json.loads(self.openstack( + 'port create -f json --network ' + + self.NETWORK_NAME + ' ' + self.NAME + )) + id1 = json_output.get('id') + self.assertIsNotNone(id1) + self.assertIsNotNone(json_output.get('mac_address')) + self.assertEqual(self.NAME, json_output.get('name')) + + json_output = json.loads(self.openstack( + 'port create -f json --network ' + self.NETWORK_NAME + ' ' + self.NAME + 'x' - ) - id2 = re.search(self.re_id, raw_output).group(1) - self.assertIsNotNone( - re.search(self.re_mac_address, raw_output).group(1), - ) - self.assertEqual( - self.NAME + 'x', - re.search(self.re_name, raw_output).group(1), - ) + )) + id2 = json_output.get('id') + self.assertIsNotNone(id2) + self.assertIsNotNone(json_output.get('mac_address')) + self.assertEqual(self.NAME + 'x', json_output.get('name')) # Clean up after ourselves raw_output = self.openstack('port delete ' + id1 + ' ' + id2) @@ -77,100 +57,80 @@ def test_port_delete(self): def test_port_list(self): """Test create defaults, list, delete""" - raw_output = self.openstack( - 'port create --network ' + self.NETWORK_NAME + ' ' + self.NAME - ) - re_id1 = re.search(self.re_id, raw_output) - self.assertIsNotNone(re_id1) - id1 = re_id1.group(1) - mac1 = re.search(self.re_mac_address, raw_output).group(1) + json_output = json.loads(self.openstack( + 'port create -f json --network ' + self.NETWORK_NAME + ' ' + + self.NAME + )) + id1 = json_output.get('id') + self.assertIsNotNone(id1) + mac1 = json_output.get('mac_address') + self.assertIsNotNone(mac1) self.addCleanup(self.openstack, 'port delete ' + id1) - self.assertEqual( - self.NAME, - re.search(self.re_name, raw_output).group(1), - ) + self.assertEqual(self.NAME, json_output.get('name')) - raw_output = self.openstack( - 'port create ' + - '--network ' + self.NETWORK_NAME + ' ' + + json_output = json.loads(self.openstack( + 'port create -f json --network ' + self.NETWORK_NAME + ' ' + self.NAME + 'x' - ) - id2 = re.search(self.re_id, raw_output).group(1) - mac2 = re.search(self.re_mac_address, raw_output).group(1) + )) + id2 = json_output.get('id') + self.assertIsNotNone(id2) + mac2 = json_output.get('mac_address') + self.assertIsNotNone(mac2) self.addCleanup(self.openstack, 'port delete ' + id2) - self.assertEqual( - self.NAME + 'x', - re.search(self.re_name, raw_output).group(1), - ) + self.assertEqual(self.NAME + 'x', json_output.get('name')) # Test list - raw_output = self.openstack('port list') - self.assertIsNotNone(re.search("\|\s+" + id1 + "\s+\|", raw_output)) - self.assertIsNotNone(re.search("\|\s+" + id2 + "\s+\|", raw_output)) - self.assertIsNotNone(re.search("\|\s+" + mac1 + "\s+\|", raw_output)) - self.assertIsNotNone(re.search("\|\s+" + mac2 + "\s+\|", raw_output)) + json_output = json.loads(self.openstack( + 'port list -f json' + )) + item_map = {item.get('ID'): item.get('MAC Address') for item in + json_output} + self.assertIn(id1, item_map.keys()) + self.assertIn(id2, item_map.keys()) + self.assertIn(mac1, item_map.values()) + self.assertIn(mac2, item_map.values()) # Test list --long - raw_output = self.openstack('port list --long') - self.assertIsNotNone(re.search("\|\s+" + id1 + "\s+\|", raw_output)) - self.assertIsNotNone(re.search("\|\s+" + id2 + "\s+\|", raw_output)) + json_output = json.loads(self.openstack( + 'port list --long -f json' + )) + id_list = [item.get('ID') for item in json_output] + self.assertIn(id1, id_list) + self.assertIn(id2, id_list) # Test list --mac-address - raw_output = self.openstack('port list --mac-address ' + mac2) - self.assertIsNone(re.search("\|\s+" + id1 + "\s+\|", raw_output)) - self.assertIsNotNone(re.search("\|\s+" + id2 + "\s+\|", raw_output)) - self.assertIsNone(re.search("\|\s+" + mac1 + "\s+\|", raw_output)) - self.assertIsNotNone(re.search("\|\s+" + mac2 + "\s+\|", raw_output)) + json_output = json.loads(self.openstack( + 'port list -f json --mac-address ' + mac2 + )) + item_map = {item.get('ID'): item.get('MAC Address') for item in + json_output} + self.assertNotIn(id1, item_map.keys()) + self.assertIn(id2, item_map.keys()) + self.assertNotIn(mac1, item_map.values()) + self.assertIn(mac2, item_map.values()) def test_port_set(self): """Test create, set, show, delete""" - raw_output = self.openstack( - 'port create ' + + json_output = json.loads(self.openstack( + 'port create -f json ' + '--network ' + self.NETWORK_NAME + ' ' + '--description xyzpdq ' '--disable ' + self.NAME - ) - re_id = re.search(self.re_id, raw_output) - self.assertIsNotNone(re_id) - id = re_id.group(1) - self.addCleanup(self.openstack, 'port delete ' + id) - self.assertEqual( - self.NAME, - re.search(self.re_name, raw_output).group(1), - ) - self.assertEqual( - 'xyzpdq', - re.search(self.re_description, raw_output).group(1), - ) - self.assertEqual( - 'DOWN', - re.search(self.re_state, raw_output).group(1), - ) - - raw_output = self.openstack( - 'port set ' + - '--enable ' + - self.NAME - ) + )) + id1 = json_output.get('id') + self.addCleanup(self.openstack, 'port delete ' + id1) + self.assertEqual(self.NAME, json_output.get('name')) + self.assertEqual('xyzpdq', json_output.get('description')) + self.assertEqual('DOWN', json_output.get('admin_state_up')) + + raw_output = self.openstack('port set ' + '--enable ' + self.NAME) self.assertOutput('', raw_output) - raw_output = self.openstack( - 'port show ' + - self.NAME - ) - self.assertEqual( - self.NAME, - re.search(self.re_name, raw_output).group(1), - ) - self.assertEqual( - 'xyzpdq', - re.search(self.re_description, raw_output).group(1), - ) - self.assertEqual( - 'UP', - re.search(self.re_state, raw_output).group(1), - ) - self.assertIsNotNone( - re.search(self.re_mac_address, raw_output).group(1), - ) + json_output = json.loads(self.openstack( + 'port show -f json ' + self.NAME + )) + self.assertEqual(self.NAME, json_output.get('name')) + self.assertEqual('xyzpdq', json_output.get('description')) + self.assertEqual('UP', json_output.get('admin_state_up')) + self.assertIsNotNone(json_output.get('mac_address')) From 1be6c2d92fd83d9ba4a6813aa409cc888c0ce8fe Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Fri, 3 Feb 2017 15:13:52 +0800 Subject: [PATCH 1563/3095] Fix properties format for volume qos in volume v1 Notice that patch [1] fixed the error of properties format for volume qos in volume v2, but there is the same bug in volume v1, and the patch missed that, so fix the problem in v1 as well [1] https://review.openstack.org/#/c/421065/ Partial-Bug: #1656767 Change-Id: I156bf13d164dbd0d0a7ce394964176718c4ff0e5 --- .../tests/functional/volume/v1/test_qos.py | 4 ++-- .../tests/unit/volume/v1/test_qos_specs.py | 21 +++++++++---------- openstackclient/volume/v1/qos_specs.py | 11 +++++++--- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/openstackclient/tests/functional/volume/v1/test_qos.py b/openstackclient/tests/functional/volume/v1/test_qos.py index 9ca32b0a08..434840f623 100644 --- a/openstackclient/tests/functional/volume/v1/test_qos.py +++ b/openstackclient/tests/functional/volume/v1/test_qos.py @@ -94,7 +94,7 @@ def test_volume_qos_set_show_unset(self): ) self.assertEqual( "Alpha='c', Beta='b'", - cmd_output['specs'] + cmd_output['properties'] ) # Test volume qos unset @@ -115,7 +115,7 @@ def test_volume_qos_set_show_unset(self): ) self.assertEqual( "Beta='b'", - cmd_output['specs'] + cmd_output['properties'] ) # TODO(qiangjiahui): Add tests for associate and disassociate volume type diff --git a/openstackclient/tests/unit/volume/v1/test_qos_specs.py b/openstackclient/tests/unit/volume/v1/test_qos_specs.py index 464038e733..e3dc1e787a 100644 --- a/openstackclient/tests/unit/volume/v1/test_qos_specs.py +++ b/openstackclient/tests/unit/volume/v1/test_qos_specs.py @@ -70,23 +70,22 @@ def test_qos_associate(self): class TestQosCreate(TestQos): - new_qos_spec = volume_fakes.FakeQos.create_one_qos() columns = ( 'consumer', 'id', 'name', - 'specs' - ) - datalist = ( - new_qos_spec.consumer, - new_qos_spec.id, - new_qos_spec.name, - new_qos_spec.specs + 'properties' ) def setUp(self): super(TestQosCreate, self).setUp() - + self.new_qos_spec = volume_fakes.FakeQos.create_one_qos() + self.datalist = ( + self.new_qos_spec.consumer, + self.new_qos_spec.id, + self.new_qos_spec.name, + utils.format_dict(self.new_qos_spec.specs) + ) self.qos_mock.create.return_value = self.new_qos_spec # Get the command object to test self.cmd = qos_specs.CreateQos(self.app, None) @@ -336,7 +335,7 @@ def test_qos_list(self): 'Name', 'Consumer', 'Associations', - 'Specs', + 'Properties', ) self.assertEqual(collist, columns) datalist = (( @@ -413,7 +412,7 @@ def test_qos_show(self): 'consumer', 'id', 'name', - 'specs' + 'properties' ) self.assertEqual(collist, columns) datalist = ( diff --git a/openstackclient/volume/v1/qos_specs.py b/openstackclient/volume/v1/qos_specs.py index b824b35179..bae8c1ab0c 100644 --- a/openstackclient/volume/v1/qos_specs.py +++ b/openstackclient/volume/v1/qos_specs.py @@ -94,7 +94,9 @@ def take_action(self, parsed_args): specs.update(parsed_args.property) qos_spec = volume_client.qos_specs.create(parsed_args.name, specs) - + qos_spec._info.update( + {'properties': utils.format_dict(qos_spec._info.pop('specs'))} + ) return zip(*sorted(six.iteritems(qos_spec._info))) @@ -190,8 +192,10 @@ def take_action(self, parsed_args): for association in qos_associations] qos._info.update({'associations': associations}) + display_columns = ( + 'ID', 'Name', 'Consumer', 'Associations', 'Properties') columns = ('ID', 'Name', 'Consumer', 'Associations', 'Specs') - return (columns, + return (display_columns, (utils.get_dict_properties( s._info, columns, formatters={ @@ -254,7 +258,8 @@ def take_action(self, parsed_args): qos_spec._info.update({ 'associations': utils.format_list(associations) }) - qos_spec._info.update({'specs': utils.format_dict(qos_spec.specs)}) + qos_spec._info.update( + {'properties': utils.format_dict(qos_spec._info.pop('specs'))}) return zip(*sorted(six.iteritems(qos_spec._info))) From edaeece7f144545bff9a7b00fccd2ec598ee2144 Mon Sep 17 00:00:00 2001 From: Anindita Das Date: Wed, 5 Oct 2016 15:57:53 +0000 Subject: [PATCH 1564/3095] OSC Network Flavor Implements Neutron feature of Network Flavor into OpenstackClient This patch implements the following commands: network flavor create network flavor delete network flavor list network flavor show network flavor set Works with openstacksdk version 0.9.8 Change-Id: I29d7a62341010a1d067a8ca93bccb7d9b8d4c425 Partially-Implements: blueprint neutron-client-flavors Partially-Implements: blueprint network-commands-options --- doc/source/command-objects/network-flavor.rst | 133 ++++++ .../network-service-provider.rst | 1 + doc/source/commands.rst | 1 + openstackclient/network/v2/network_flavor.py | 247 +++++++++++ .../network/v2/test_network_flavor.py | 153 +++++++ .../tests/unit/network/v2/fakes.py | 63 +++ .../unit/network/v2/test_network_flavor.py | 407 ++++++++++++++++++ ...avor-command-support-afe3a9da962a09bf.yaml | 7 + setup.cfg | 6 + 9 files changed, 1018 insertions(+) create mode 100644 doc/source/command-objects/network-flavor.rst create mode 100644 openstackclient/network/v2/network_flavor.py create mode 100644 openstackclient/tests/functional/network/v2/test_network_flavor.py create mode 100644 openstackclient/tests/unit/network/v2/test_network_flavor.py create mode 100644 releasenotes/notes/network-flavor-command-support-afe3a9da962a09bf.yaml diff --git a/doc/source/command-objects/network-flavor.rst b/doc/source/command-objects/network-flavor.rst new file mode 100644 index 0000000000..1324499e7d --- /dev/null +++ b/doc/source/command-objects/network-flavor.rst @@ -0,0 +1,133 @@ +============== +network flavor +============== + +A **network flavor** extension allows the user selection of operator-curated +flavors during resource creations. It allows administrators to create network +service flavors. + +Network v2 + +.. _network_flavor_create: +network flavor create +--------------------- + +Create network flavor + +.. program:: network flavor create +.. code:: bash + + openstack network flavor create + --service-type + [--description ] + [--enable | --disable] + [--project [--project-domain ]] + + +.. option:: --service-type + + Service type to which the flavor applies to: e.g. VPN. + (See openstack :ref:'network_service_provider_list` (required) + +.. option:: --description + + Description for the flavor + +.. option:: --enable + + Enable the flavor (default) + +.. option:: --disable + + Disable the flavor + +.. option:: --project + + Owner's project (name or ID) + +.. option:: --project-domain + + Domain the project belongs to (name or ID). This can + be used in case collisions between project names + exist. + +.. describe:: + + Name for the flavor + +.. _network_flavor_delete: +network flavor delete +--------------------- + +Delete network flavor(s) + +.. program:: network flavor delete +.. code:: bash + + openstack network flavor delete + [ ...] + +.. describe:: + + Flavor(s) to delete (name or ID) + +network flavor list +------------------- + +List network flavors + +.. program:: network flavor list +.. code:: bash + + openstack network flavor list + +.. _network_flavor_set: +network flavor set +------------------ + +Set network flavor properties + +.. program:: network flavor set +.. code:: bash + + openstack network flavor set + [--name ] + [--description ] + [--enable | --disable] + + +.. option:: --name + + Set flavor name + +.. option:: --description + + Set network flavor description + +.. option:: --enable + + Enable network flavor + +.. option:: --disable + + Disable network flavor + +.. describe:: + + Flavor to update (name or ID) + +.. _network_flavor_show: +network flavor show +------------------- + +Show network flavor + +.. program:: network flavor show +.. code:: bash + + openstack network flavor show + + +.. describe:: + + Flavor to display (name or ID) diff --git a/doc/source/command-objects/network-service-provider.rst b/doc/source/command-objects/network-service-provider.rst index 8db2ab448b..8ccec45505 100644 --- a/doc/source/command-objects/network-service-provider.rst +++ b/doc/source/command-objects/network-service-provider.rst @@ -7,6 +7,7 @@ networking service Network v2 +.. _network_service_provider_list: network service provider list ----------------------------- diff --git a/doc/source/commands.rst b/doc/source/commands.rst index ece9b34e3f..68c50d24d1 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -111,6 +111,7 @@ referring to both Compute and Volume quotas. * ``module``: (**Internal**) - installed Python modules in the OSC process * ``network``: (**Compute**, **Network**) - a virtual network for connecting servers and other resources * ``network agent``: (**Network**) - A network agent is an agent that handles various tasks used to implement virtual networks +* ``network flavor``: (**Network**) - allows the user to choose the type of service by a set of advertised service capabilities (e.g., LOADBALANCER, FWAAS, L3, VPN, etc) rather than by a provider type or named vendor * ``network meter``: (**Network**) - allow traffic metering in a network * ``network meter rule``: (**Network**) - rules for network traffic metering * ``network rbac``: (**Network**) - an RBAC policy for network resources diff --git a/openstackclient/network/v2/network_flavor.py b/openstackclient/network/v2/network_flavor.py new file mode 100644 index 0000000000..3a3324c09d --- /dev/null +++ b/openstackclient/network/v2/network_flavor.py @@ -0,0 +1,247 @@ +# 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. +# + +"""Flavor action implementations""" + +import logging + +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils + +from openstackclient.i18n import _ +from openstackclient.identity import common as identity_common +from openstackclient.network import sdk_utils + + +LOG = logging.getLogger(__name__) + + +def _get_columns(item): + column_map = { + 'is_enabled': 'enabled', + 'tenant_id': 'project_id', + } + + return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + + +def _get_attrs(client_manager, parsed_args): + attrs = {} + attrs['name'] = parsed_args.name + attrs['service_type'] = parsed_args.service_type + if parsed_args.description is not None: + attrs['description'] = parsed_args.description + if parsed_args.enable: + attrs['enabled'] = True + if parsed_args.disable: + attrs['enabled'] = False + if 'project' in parsed_args and parsed_args.project is not None: + identity_client = client_manager.identity + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + attrs['tenant_id'] = project_id + + return attrs + + +# TODO(dasanind): Use the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. +class CreateNetworkFlavor(command.ShowOne): + _description = _("Create new network flavor") + + def get_parser(self, prog_name): + parser = super(CreateNetworkFlavor, self).get_parser(prog_name) + parser.add_argument( + 'name', + metavar="", + help=_("Name for the flavor") + ) + parser.add_argument( + '--service-type', + metavar="", + required=True, + help=_('Service type to which the flavor applies to: e.g. VPN ' + '(See openstack network service provider list for loaded ' + 'examples.)') + ) + parser.add_argument( + '--description', + help=_('Description for the flavor') + ) + parser.add_argument( + '--project', + metavar="", + help=_("Owner's project (name or ID)") + ) + identity_common.add_project_domain_option_to_parser(parser) + + enable_group = parser.add_mutually_exclusive_group() + enable_group.add_argument( + '--enable', + action='store_true', + help=_("Enable the flavor (default)") + ) + enable_group.add_argument( + '--disable', + action='store_true', + help=_("Disable the flavor") + ) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + attrs = _get_attrs(self.app.client_manager, parsed_args) + obj = client.create_flavor(**attrs) + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns, formatters={}) + + return (display_columns, data) + + +class DeleteNetworkFlavor(command.Command): + _description = _("Delete network flavors") + + def get_parser(self, prog_name): + parser = super(DeleteNetworkFlavor, self).get_parser(prog_name) + + parser.add_argument( + 'flavor', + metavar='', + nargs='+', + help=_('Flavor(s) to delete (name or ID)') + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + result = 0 + + for flavor in parsed_args.flavor: + try: + obj = client.find_flavor(flavor, ignore_missing=False) + client.delete_flavor(obj) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete flavor with " + "name or ID '%(flavor)s': %(e)s"), + {"flavor": flavor, "e": e}) + if result > 0: + total = len(parsed_args.flavor) + msg = (_("%(result)s of %(total)s flavors failed " + "to delete.") % {"result": result, "total": total}) + raise exceptions.CommandError(msg) + + +class ListNetworkFlavor(command.Lister): + _description = _("List network flavors") + + def take_action(self, parsed_args): + client = self.app.client_manager.network + + columns = ( + 'id', + 'name', + 'is_enabled', + 'service_type', + 'description' + ) + column_headers = ( + 'ID', + 'Name', + 'Enabled', + 'Service Type', + 'Description' + ) + + data = client.flavors() + return (column_headers, + (utils.get_item_properties( + s, columns, + ) for s in data)) + + +# TODO(dasanind): Use only the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. +class SetNetworkFlavor(command.Command): + _description = _("Set network flavor properties") + + def get_parser(self, prog_name): + parser = super(SetNetworkFlavor, self).get_parser(prog_name) + parser.add_argument( + 'flavor', + metavar="", + help=_("Flavor to update (name or ID)") + ) + parser.add_argument( + '--description', + help=_('Set network flavor description') + ) + enable_group = parser.add_mutually_exclusive_group() + enable_group.add_argument( + '--disable', + action='store_true', + help=_("Disable network flavor") + ) + enable_group.add_argument( + '--enable', + action='store_true', + help=_("Enable network flavor") + ) + parser.add_argument( + '--name', + metavar="", + help=_('Set flavor name') + ) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.find_flavor( + parsed_args.flavor, + ignore_missing=False) + attrs = {} + if parsed_args.name is not None: + attrs['name'] = parsed_args.name + if parsed_args.description is not None: + attrs['description'] = parsed_args.description + if parsed_args.enable: + attrs['enabled'] = True + if parsed_args.disable: + attrs['enabled'] = False + client.update_flavor(obj, **attrs) + + +class ShowNetworkFlavor(command.ShowOne): + _description = _("Display network flavor details") + + def get_parser(self, prog_name): + parser = super(ShowNetworkFlavor, self).get_parser(prog_name) + parser.add_argument( + 'flavor', + metavar='', + help=_('Flavor to display (name or ID)') + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.find_flavor(parsed_args.flavor, ignore_missing=False) + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns) + return display_columns, data diff --git a/openstackclient/tests/functional/network/v2/test_network_flavor.py b/openstackclient/tests/functional/network/v2/test_network_flavor.py new file mode 100644 index 0000000000..e37a7bc71f --- /dev/null +++ b/openstackclient/tests/functional/network/v2/test_network_flavor.py @@ -0,0 +1,153 @@ +# 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 re +import uuid + +from openstackclient.tests.functional import base + + +class NetworkFlavorTests(base.TestCase): + """Functional tests for network flavor.""" + + @classmethod + def setUpClass(cls): + # Set up some regex for matching below + cls.re_name = re.compile("name\s+\|\s+([^|]+?)\s+\|") + cls.re_enabled = re.compile("enabled\s+\|\s+(\S+)") + cls.re_description = re.compile("description\s+\|\s+([^|]+?)\s+\|") + cls.SERVICE_TYPE = 'L3_ROUTER_NAT' + + def test_network_flavor_delete(self): + """Test create, delete multiple""" + name1 = uuid.uuid4().hex + raw_output = self.openstack( + 'network flavor create --description testdescription --enable' + ' --service-type ' + self.SERVICE_TYPE + ' ' + name1, + ) + self.assertEqual( + name1, + re.search(self.re_name, raw_output).group(1)) + self.assertEqual( + 'True', + re.search(self.re_enabled, raw_output).group(1)) + self.assertEqual( + 'testdescription', + re.search(self.re_description, raw_output).group(1)) + + name2 = uuid.uuid4().hex + raw_output = self.openstack( + 'network flavor create --description testdescription1 --disable' + ' --service-type ' + self.SERVICE_TYPE + ' ' + name2, + ) + self.assertEqual( + name2, + re.search(self.re_name, raw_output).group(1)) + self.assertEqual( + 'False', + re.search(self.re_enabled, raw_output).group(1)) + self.assertEqual( + 'testdescription1', + re.search(self.re_description, raw_output).group(1)) + + raw_output = self.openstack( + 'network flavor delete ' + name1 + " " + name2) + self.assertOutput('', raw_output) + + def test_network_flavor_list(self): + name1 = uuid.uuid4().hex + raw_output = self.openstack( + 'network flavor create --description testdescription --enable' + ' --service-type ' + self.SERVICE_TYPE + ' ' + name1, + ) + self.addCleanup(self.openstack, "network flavor delete " + name1) + self.assertEqual( + name1, + re.search(self.re_name, raw_output).group(1)) + self.assertEqual( + 'True', + re.search(self.re_enabled, raw_output).group(1)) + self.assertEqual( + 'testdescription', + re.search(self.re_description, raw_output).group(1)) + + name2 = uuid.uuid4().hex + raw_output = self.openstack( + 'network flavor create --description testdescription --disable' + ' --service-type ' + self.SERVICE_TYPE + ' ' + name2, + ) + self.assertEqual( + name2, + re.search(self.re_name, raw_output).group(1)) + self.assertEqual( + 'False', + re.search(self.re_enabled, raw_output).group(1)) + self.assertEqual( + 'testdescription', + re.search(self.re_description, raw_output).group(1)) + self.addCleanup(self.openstack, "network flavor delete " + name2) + + # Test list + raw_output = self.openstack('network flavor list') + self.assertIsNotNone(raw_output) + self.assertIsNotNone(re.search(name1, raw_output)) + self.assertIsNotNone(re.search(name2, raw_output)) + + def test_network_flavor_set(self): + name = uuid.uuid4().hex + newname = name + "_" + raw_output = self.openstack( + 'network flavor create --description testdescription --enable' + ' --service-type ' + self.SERVICE_TYPE + ' ' + name, + ) + self.addCleanup(self.openstack, "network flavor delete " + newname) + self.assertEqual( + name, + re.search(self.re_name, raw_output).group(1)) + self.assertEqual( + 'True', + re.search(self.re_enabled, raw_output).group(1)) + self.assertEqual( + 'testdescription', + re.search(self.re_description, raw_output).group(1)) + + self.openstack( + 'network flavor set --name ' + newname + ' --disable ' + name + ) + raw_output = self.openstack('network flavor show ' + newname) + self.assertEqual( + newname, + re.search(self.re_name, raw_output).group(1)) + self.assertEqual( + 'False', + re.search(self.re_enabled, raw_output).group(1)) + self.assertEqual( + 'testdescription', + re.search(self.re_description, raw_output).group(1)) + + def test_network_flavor_show(self): + name = uuid.uuid4().hex + self.openstack( + 'network flavor create --description testdescription --enable' + ' --service-type ' + self.SERVICE_TYPE + ' ' + name, + ) + self.addCleanup(self.openstack, "network flavor delete " + name) + raw_output = self.openstack('network flavor show ' + name) + self.assertEqual( + name, + re.search(self.re_name, raw_output).group(1)) + self.assertEqual( + 'True', + re.search(self.re_enabled, raw_output).group(1)) + self.assertEqual( + 'testdescription', + re.search(self.re_description, raw_output).group(1)) diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index dcecbeee80..6a73b7e9b7 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -390,6 +390,69 @@ def get_networks(networks=None, count=2): return mock.Mock(side_effect=networks) +class FakeNetworkFlavor(object): + """Fake Network Flavor.""" + + @staticmethod + def create_one_network_flavor(attrs=None): + """Create a fake network flavor. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object faking the network flavor + """ + attrs = attrs or {} + + fake_uuid = uuid.uuid4().hex + network_flavor_attrs = { + 'description': 'network-flavor-description-' + fake_uuid, + 'enabled': True, + 'id': 'network-flavor-id-' + fake_uuid, + 'name': 'network-flavor-name-' + fake_uuid, + 'service_type': 'vpn', + 'tenant_id': 'project-id-' + uuid.uuid4().hex, + } + + # Overwrite default attributes. + network_flavor_attrs.update(attrs) + + network_flavor = fakes.FakeResource( + info=copy.deepcopy(network_flavor_attrs), + loaded=True + ) + + network_flavor.project_id = network_flavor_attrs['tenant_id'] + network_flavor.is_enabled = network_flavor_attrs['enabled'] + + return network_flavor + + @staticmethod + def create_flavor(attrs=None, count=2): + """Create multiple fake network flavors. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of network flavors to fake + :return: + A list of FakeResource objects faking the network falvors + """ + network_flavors = [] + for i in range(0, count): + network_flavors.append( + FakeNetworkFlavor.create_one_network_flavor(attrs) + ) + return network_flavors + + @staticmethod + def get_flavor(network_flavors=None, count=2): + """Get a list of flavors.""" + if network_flavors is None: + network_flavors = (FakeNetworkFlavor.create_flavor(count)) + return mock.Mock(side_effect=network_flavors) + + class FakeNetworkSegment(object): """Fake one or more network segments.""" diff --git a/openstackclient/tests/unit/network/v2/test_network_flavor.py b/openstackclient/tests/unit/network/v2/test_network_flavor.py new file mode 100644 index 0000000000..11e27841d4 --- /dev/null +++ b/openstackclient/tests/unit/network/v2/test_network_flavor.py @@ -0,0 +1,407 @@ +# 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 osc_lib import exceptions + +from openstackclient.network.v2 import network_flavor +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3 +from openstackclient.tests.unit.network.v2 import fakes as network_fakes +from openstackclient.tests.unit import utils as tests_utils + + +class TestNetworkFlavor(network_fakes.TestNetworkV2): + + def setUp(self): + super(TestNetworkFlavor, self).setUp() + + # Get a shortcut to the network client + self.network = self.app.client_manager.network + # Get a shortcut to the ProjectManager Mock + self.projects_mock = self.app.client_manager.identity.projects + # Get a shortcut to the DomainManager Mock + self.domains_mock = self.app.client_manager.identity.domains + + +class TestCreateNetworkFlavor(TestNetworkFlavor): + + project = identity_fakes_v3.FakeProject.create_one_project() + domain = identity_fakes_v3.FakeDomain.create_one_domain() + # The new network flavor created. + new_network_flavor = ( + network_fakes.FakeNetworkFlavor.create_one_network_flavor()) + columns = ( + 'description', + 'enabled', + 'id', + 'name', + 'project_id', + 'service_type' + ) + data = ( + new_network_flavor.description, + new_network_flavor.enabled, + new_network_flavor.id, + new_network_flavor.name, + new_network_flavor.project_id, + new_network_flavor.service_type, + ) + + def setUp(self): + super(TestCreateNetworkFlavor, self).setUp() + self.network.create_flavor = mock.Mock( + return_value=self.new_network_flavor) + + # Get the command object to test + self.cmd = network_flavor.CreateNetworkFlavor(self.app, self.namespace) + + self.projects_mock.get.return_value = self.project + self.domains_mock.get.return_value = self.domain + + def test_create_no_options(self): + arglist = [] + verifylist = [] + + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_create_default_options(self): + arglist = [ + '--service-type', self.new_network_flavor.service_type, + self.new_network_flavor.name, + ] + verifylist = [ + ('service_type', self.new_network_flavor.service_type), + ('name', self.new_network_flavor.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_flavor.assert_called_once_with(**{ + 'service_type': self.new_network_flavor.service_type, + 'name': self.new_network_flavor.name, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_all_options(self): + arglist = [ + '--description', self.new_network_flavor.description, + '--enable', + '--project', self.new_network_flavor.project_id, + '--project-domain', self.domain.name, + '--service-type', self.new_network_flavor.service_type, + self.new_network_flavor.name, + ] + verifylist = [ + ('description', self.new_network_flavor.description), + ('enable', True), + ('project', self.new_network_flavor.project_id), + ('project_domain', self.domain.name), + ('service_type', self.new_network_flavor.service_type), + ('name', self.new_network_flavor.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_flavor.assert_called_once_with(**{ + 'description': self.new_network_flavor.description, + 'enabled': True, + 'tenant_id': self.project.id, + 'service_type': self.new_network_flavor.service_type, + 'name': self.new_network_flavor.name, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_disable(self): + arglist = [ + '--disable', + '--service-type', self.new_network_flavor.service_type, + self.new_network_flavor.name, + ] + verifylist = [ + ('disable', True), + ('service_type', self.new_network_flavor.service_type), + ('name', self.new_network_flavor.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_flavor.assert_called_once_with(**{ + 'enabled': False, + 'service_type': self.new_network_flavor.service_type, + 'name': self.new_network_flavor.name, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestDeleteNetworkFlavor(TestNetworkFlavor): + + # The network flavor to delete. + _network_flavors = ( + network_fakes.FakeNetworkFlavor.create_flavor(count=2)) + + def setUp(self): + super(TestDeleteNetworkFlavor, self).setUp() + self.network.delete_flavor = mock.Mock(return_value=None) + self.network.find_flavor = ( + network_fakes.FakeNetworkFlavor.get_flavor( + network_flavors=self._network_flavors) + ) + + # Get the command object to test + self.cmd = network_flavor.DeleteNetworkFlavor(self.app, self.namespace) + + def test_network_flavor_delete(self): + arglist = [ + self._network_flavors[0].name, + ] + verifylist = [ + ('flavor', [self._network_flavors[0].name]), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.network.find_flavor.assert_called_once_with( + self._network_flavors[0].name, ignore_missing=False) + self.network.delete_flavor.assert_called_once_with( + self._network_flavors[0]) + self.assertIsNone(result) + + def test_multi_network_flavors_delete(self): + arglist = [] + verifylist = [] + + for a in self._network_flavors: + arglist.append(a.name) + verifylist = [ + ('flavor', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [] + for a in self._network_flavors: + calls.append(mock.call(a)) + self.network.delete_flavor.assert_has_calls(calls) + self.assertIsNone(result) + + def test_multi_network_flavors_delete_with_exception(self): + arglist = [ + self._network_flavors[0].name, + 'unexist_network_flavor', + ] + verifylist = [ + ('flavor', + [self._network_flavors[0].name, 'unexist_network_flavor']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self._network_flavors[0], exceptions.CommandError] + self.network.find_flavor = ( + mock.Mock(side_effect=find_mock_result) + ) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 flavors failed to delete.', str(e)) + + self.network.find_flavor.assert_any_call( + self._network_flavors[0].name, ignore_missing=False) + self.network.find_flavor.assert_any_call( + 'unexist_network_flavor', ignore_missing=False) + self.network.delete_flavor.assert_called_once_with( + self._network_flavors[0] + ) + + +class TestListNetworkFlavor(TestNetworkFlavor): + + # The network flavors to list up. + _network_flavors = ( + network_fakes.FakeNetworkFlavor.create_flavor(count=2)) + columns = ( + 'ID', + 'Name', + 'Enabled', + 'Service Type', + 'Description', + ) + data = [] + for flavor in _network_flavors: + data.append(( + flavor.id, + flavor.name, + flavor.enabled, + flavor.service_type, + flavor.description, + )) + + def setUp(self): + super(TestListNetworkFlavor, self).setUp() + self.network.flavors = mock.Mock( + return_value=self._network_flavors) + + # Get the command object to test + self.cmd = network_flavor.ListNetworkFlavor(self.app, self.namespace) + + def test_network_flavor_list(self): + arglist = [] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.flavors.assert_called_once_with(**{}) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + +class TestShowNetworkFlavor(TestNetworkFlavor): + + # The network flavor to show. + new_network_flavor = ( + network_fakes.FakeNetworkFlavor.create_one_network_flavor()) + columns = ( + 'description', + 'enabled', + 'id', + 'name', + 'project_id', + 'service_type' + ) + data = ( + new_network_flavor.description, + new_network_flavor.enabled, + new_network_flavor.id, + new_network_flavor.name, + new_network_flavor.project_id, + new_network_flavor.service_type, + ) + + def setUp(self): + super(TestShowNetworkFlavor, self).setUp() + self.network.find_flavor = mock.Mock( + return_value=self.new_network_flavor) + + # Get the command object to test + self.cmd = network_flavor.ShowNetworkFlavor(self.app, self.namespace) + + def test_show_no_options(self): + arglist = [] + verifylist = [] + + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_show_all_options(self): + arglist = [ + self.new_network_flavor.name, + ] + verifylist = [ + ('flavor', self.new_network_flavor.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.find_flavor.assert_called_once_with( + self.new_network_flavor.name, ignore_missing=False) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestSetNetworkFlavor(TestNetworkFlavor): + + # The network flavor to set. + new_network_flavor = ( + network_fakes.FakeNetworkFlavor.create_one_network_flavor()) + + def setUp(self): + super(TestSetNetworkFlavor, self).setUp() + self.network.update_flavor = mock.Mock(return_value=None) + self.network.find_flavor = mock.Mock( + return_value=self.new_network_flavor) + + # Get the command object to test + self.cmd = network_flavor.SetNetworkFlavor(self.app, self.namespace) + + def test_set_nothing(self): + arglist = [self.new_network_flavor.name, ] + verifylist = [ + ('flavor', self.new_network_flavor.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = {} + self.network.update_flavor.assert_called_with( + self.new_network_flavor, **attrs) + self.assertIsNone(result) + + def test_set_name_and_enable(self): + arglist = [ + '--name', 'new_network_flavor', + '--enable', + self.new_network_flavor.name, + ] + verifylist = [ + ('name', 'new_network_flavor'), + ('enable', True), + ('flavor', self.new_network_flavor.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + attrs = { + 'name': "new_network_flavor", + 'enabled': True, + } + self.network.update_flavor.assert_called_with( + self.new_network_flavor, **attrs) + self.assertIsNone(result) + + def test_set_disable(self): + arglist = [ + '--disable', + self.new_network_flavor.name, + ] + verifylist = [ + ('disable', True), + ('flavor', self.new_network_flavor.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + attrs = { + 'enabled': False, + } + self.network.update_flavor.assert_called_with( + self.new_network_flavor, **attrs) + self.assertIsNone(result) diff --git a/releasenotes/notes/network-flavor-command-support-afe3a9da962a09bf.yaml b/releasenotes/notes/network-flavor-command-support-afe3a9da962a09bf.yaml new file mode 100644 index 0000000000..e1d8996136 --- /dev/null +++ b/releasenotes/notes/network-flavor-command-support-afe3a9da962a09bf.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Add ``network flavor create``, ``network flavor delete``, + ``network flavor list``, Add ``network flavor show`` and + ``network flavor set`` command + [Implement Neutron Flavors in OSC `https://blueprints.launchpad.net/python-openstackclient/+spec/neutron-client-flavors`] diff --git a/setup.cfg b/setup.cfg index aa81559e4c..da7c0aa4f7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -360,6 +360,12 @@ openstack.network.v2 = network_agent_set = openstackclient.network.v2.network_agent:SetNetworkAgent network_agent_show = openstackclient.network.v2.network_agent:ShowNetworkAgent + network_flavor_create = openstackclient.network.v2.network_flavor:CreateNetworkFlavor + network_flavor_delete = openstackclient.network.v2.network_flavor:DeleteNetworkFlavor + network_flavor_list = openstackclient.network.v2.network_flavor:ListNetworkFlavor + network_flavor_set = openstackclient.network.v2.network_flavor:SetNetworkFlavor + network_flavor_show = openstackclient.network.v2.network_flavor:ShowNetworkFlavor + network_create = openstackclient.network.v2.network:CreateNetwork network_delete = openstackclient.network.v2.network:DeleteNetwork network_list = openstackclient.network.v2.network:ListNetwork From 10f0300f704ca8dd5928d9a8b41ac6c16732f203 Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Wed, 22 Feb 2017 16:18:56 +0800 Subject: [PATCH 1565/3095] Show openstacksdk version info in "module list" openstacksdk bug/1588823 exist, no good way to add __version__ for openstack module properly, fix the issue in osc side, make openstacksdk module information be available. Change-Id: I27ff61792443d1aa07f31598bed3aa32f924ff40 Partial-Bug: #1662058 --- openstackclient/common/module.py | 10 +++++++++- openstackclient/tests/functional/common/test_module.py | 3 ++- openstackclient/tests/unit/fakes.py | 3 +++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/openstackclient/common/module.py b/openstackclient/common/module.py index f471b2aa02..ba911ecb8f 100644 --- a/openstackclient/common/module.py +++ b/openstackclient/common/module.py @@ -86,7 +86,15 @@ def take_action(self, parsed_args): # Handle xxxclient and openstacksdk (k.endswith('client') or k == 'openstack')): try: - data[k] = mods[k].__version__ + # NOTE(RuiChen): openstacksdk bug/1588823 exist, + # no good way to add __version__ for + # openstack module properly, hard code + # looks bad, but openstacksdk module + # information is important. + if k == 'openstack': + data[k] = mods[k].version.__version__ + else: + data[k] = mods[k].__version__ except Exception: # Catch all exceptions, just skip it pass diff --git a/openstackclient/tests/functional/common/test_module.py b/openstackclient/tests/functional/common/test_module.py index b56c83ad24..f4f2e95215 100644 --- a/openstackclient/tests/functional/common/test_module.py +++ b/openstackclient/tests/functional/common/test_module.py @@ -23,7 +23,8 @@ class ModuleTest(base.TestCase): CLIENTS = ['openstackclient', 'keystoneclient', - 'novaclient'] + 'novaclient', + 'openstack'] LIBS = ['osc_lib', 'os_client_config', diff --git a/openstackclient/tests/unit/fakes.py b/openstackclient/tests/unit/fakes.py index 626b466d35..f28f9103d9 100644 --- a/openstackclient/tests/unit/fakes.py +++ b/openstackclient/tests/unit/fakes.py @@ -161,6 +161,9 @@ class FakeModule(object): def __init__(self, name, version): self.name = name self.__version__ = version + # Workaround for openstacksdk case + self.version = mock.Mock() + self.version.__version__ = version class FakeResource(object): From c828216e2b02a9fcee3bb9a10fde2ec5ccd59026 Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Wed, 22 Feb 2017 17:56:03 +0800 Subject: [PATCH 1566/3095] Fix "endpoint list" help message OSC support to filter OpenStack endpoints by service type, name and ID, but help message and document don't contain "type" for "--service" option of "endpoint list" command. Change-Id: I5c8bc28e037b4c6f96ec83525a319353f01f256a Closes-Bug: #1666841 --- doc/source/command-objects/endpoint.rst | 4 ++-- openstackclient/identity/v3/endpoint.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/source/command-objects/endpoint.rst b/doc/source/command-objects/endpoint.rst index d6eb362af5..b98055a1df 100644 --- a/doc/source/command-objects/endpoint.rst +++ b/doc/source/command-objects/endpoint.rst @@ -103,14 +103,14 @@ List endpoints .. code:: bash openstack endpoint list - [--service ] [--interface ] [--region ] [--long] .. option:: --service - Filter by service (name or ID) + Filter by service (type, name or ID) *Identity version 3 only* diff --git a/openstackclient/identity/v3/endpoint.py b/openstackclient/identity/v3/endpoint.py index 7bc5e6df74..15760a1724 100644 --- a/openstackclient/identity/v3/endpoint.py +++ b/openstackclient/identity/v3/endpoint.py @@ -139,7 +139,7 @@ def get_parser(self, prog_name): parser.add_argument( '--service', metavar='', - help=_('Filter by service (name or ID)'), + help=_('Filter by service (type, name or ID)'), ) parser.add_argument( '--interface', From 4b293c57c83fc5214d9e2785261d4667ba3aa93e Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Wed, 22 Feb 2017 14:09:25 -0600 Subject: [PATCH 1567/3095] add keystone and glance -> osc mapping Change-Id: I99cd8b11a5d5fc6dc5d2f4584b837ea0f44d665a --- doc/source/data/glance.csv | 24 +++++++++++++++++++++++ doc/source/data/keystone.csv | 37 ++++++++++++++++++++++++++++++++++++ doc/source/decoder.rst | 17 +++++++++++++++++ 3 files changed, 78 insertions(+) create mode 100644 doc/source/data/glance.csv create mode 100644 doc/source/data/keystone.csv diff --git a/doc/source/data/glance.csv b/doc/source/data/glance.csv new file mode 100644 index 0000000000..2985e307c7 --- /dev/null +++ b/doc/source/data/glance.csv @@ -0,0 +1,24 @@ +explain,WONTFIX,Describe a specific model. +image-create,image create,Create a new image. +image-deactivate,image set --deactivate,Deactivate specified image. +image-delete,image delete,Delete specified image. +image-download,image save,Download a specific image. +image-list,image list,List images you can access. +image-reactivate,image set --activate,Reactivate specified image. +image-show,image show,Describe a specific image. +image-tag-delete,image set --tag ,Delete the tag associated with the given image. +image-tag-update,image unset --tag ,Update an image with the given tag. +image-update,image set,Update an existing image. +image-upload,,Upload data for a specific image. +location-add,,Add a location (and related metadata) to an image. +location-delete,,Remove locations (and related metadata) from an image. +location-update,,Update metadata of an image's location. +member-create,image add project,Create member for a given image. +member-delete,image remove project,Delete image member. +member-list,,Describe sharing permissions by image. +member-update,image set --accept --reject --status,Update the status of a member for a given image. +task-create,,Create a new task. +task-list,,List tasks you can access. +task-show,,Describe a specific task. +bash-completion,complete,Prints arguments for bash_completion. +help,help,Display help about this program or one of its subcommands. \ No newline at end of file diff --git a/doc/source/data/keystone.csv b/doc/source/data/keystone.csv new file mode 100644 index 0000000000..b1364010fa --- /dev/null +++ b/doc/source/data/keystone.csv @@ -0,0 +1,37 @@ +catalog,catalog show,"List service catalog, possibly filtered by service." +ec2-credentials-create,ec2 credentials create,Create EC2-compatible credentials for user per tenant. +ec2-credentials-delete,ec2 credentials delete,Delete EC2-compatible credentials. +ec2-credentials-get,ec2 credentials show,Display EC2-compatible credentials. +ec2-credentials-list,ec2 credentials list,List EC2-compatible credentials for a user. +endpoint-create,endpoint create,Create a new endpoint associated with a service. +endpoint-delete,endpoint delete,Delete a service endpoint. +endpoint-get,endpoint get,Find endpoint filtered by a specific attribute or service type. +endpoint-list,endpoint list,List configured service endpoints. +password-update,user password set,Update own password. +role-create,role create,Create new role. +role-delete,role delete,Delete role. +role-get,role show,Display role details. +role-list,role list,List all roles. +service-create,service create,Add service to Service Catalog. +service-delete,service delete,Delete service from Service Catalog. +service-get,service show,Display service from Service Catalog. +service-list,service list,List all services in Service Catalog. +tenant-create,project create,Create new tenant. +tenant-delete,project delete,Delete tenant. +tenant-get,proejct show,Display tenant details. +tenant-list,project list,List all tenants. +tenant-update,project set,"Update tenant name, description, enabled status." +token-get,issue token,Display the current user token. +user-create,user create,Create new user. +user-delete,user delete,Delete user. +user-get,user show,Display user details. +user-list,user list,List users. +user-password-update,user set --password,Update user password. +user-role-add,role add --user --project,Add role to user. +user-role-list,role assignment list --user --project,List roles granted to a user. +user-role-remove,role remove --user --project,Remove role from user. +user-update,user set,"Update user's name, email, and enabled status." +discover,WONTFIX,"Discover Keystone servers, supported API versions and extensions." +bootstrap,WONTFIX,"Grants a new role to a new user on a new tenant, after creating each." +bash-completion,complete,Prints all of the commands and options to stdout. +help,help,Display help about this program or one of its subcommands. \ No newline at end of file diff --git a/doc/source/decoder.rst b/doc/source/decoder.rst index 23c16ca588..3d07bb6fc6 100644 --- a/doc/source/decoder.rst +++ b/doc/source/decoder.rst @@ -15,6 +15,23 @@ shown when necessary. :widths: 25, 25, 50 :file: data/cinder.csv +``glance CLI`` +-------------- + +.. csv-table:: + :header: "Glance CLI", "OSC Equivalent", "Description" + :widths: 25, 25, 50 + :file: data/glance.csv + +``keystone CLI`` +---------------- + +.. csv-table:: + :header: "Keystone CLI", "OSC Equivalent", "Description" + :widths: 25, 25, 50 + :file: data/keystone.csv + + ``nova CLI`` ------------ From aef9dacc7f65c4183c6401956ea6c87f16045e96 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Wed, 22 Feb 2017 14:19:38 -0600 Subject: [PATCH 1568/3095] minor tweaks to mapping table - no need to prefix it with OSC - since it's a page with a lot of content, make it show sub headings Change-Id: I1aab4f71def53f1a11a8a0e0f6b7748233ff02c7 --- doc/source/decoder.rst | 6 +++--- doc/source/index.rst | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/doc/source/decoder.rst b/doc/source/decoder.rst index 3d07bb6fc6..178ba01c60 100644 --- a/doc/source/decoder.rst +++ b/doc/source/decoder.rst @@ -1,6 +1,6 @@ -================= -OSC Mapping Table -================= +============= +Mapping Guide +============= The following is an incomplete mapping between legacy OpenStack CLIs and OpenStackClient. Think of it as a magic decoder ring if you were using the diff --git a/doc/source/index.rst b/doc/source/index.rst index 2a87c9991b..e19b466b93 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -23,6 +23,10 @@ User Documentation interactive humaninterfaceguide backwards-incompatible + +.. toctree:: + :maxdepth: 2 + decoder Getting Started From 1169434f42a751ca9ef37fed2fa2fd04fe8b6f8b Mon Sep 17 00:00:00 2001 From: Ankur Gupta Date: Thu, 27 Oct 2016 23:37:27 -0500 Subject: [PATCH 1569/3095] Auto allocated topology for OSC Implementation of Auto-allocated topology into OSC. Dependency merged and released in SDK v. 0.9.11 Partially Implements: blueprint network-auto-allocated-topology Change-Id: I16120910893b0b26b0f7f77a184b0378448458c5 --- .../network-auto-allocated-topology.rst | 69 +++++ doc/source/commands.rst | 1 + .../v2/network_auto_allocated_topology.py | 136 +++++++++ .../tests/unit/network/v2/fakes.py | 25 ++ .../test_network_auto_allocated_topology.py | 267 ++++++++++++++++++ ...o-allocated-topology-481580f48840bfc4.yaml | 6 + setup.cfg | 3 + 7 files changed, 507 insertions(+) create mode 100644 doc/source/command-objects/network-auto-allocated-topology.rst create mode 100644 openstackclient/network/v2/network_auto_allocated_topology.py create mode 100644 openstackclient/tests/unit/network/v2/test_network_auto_allocated_topology.py create mode 100644 releasenotes/notes/add-network-auto-allocated-topology-481580f48840bfc4.yaml diff --git a/doc/source/command-objects/network-auto-allocated-topology.rst b/doc/source/command-objects/network-auto-allocated-topology.rst new file mode 100644 index 0000000000..4ed68cdae9 --- /dev/null +++ b/doc/source/command-objects/network-auto-allocated-topology.rst @@ -0,0 +1,69 @@ +=============================== +network auto allocated topology +=============================== + +An **auto allocated topology** allows admins to quickly set up external +connectivity for end-users. Only one auto allocated topology is allowed per +project. For more information on how to set up the resources required +for auto allocated topology review the documentation at: +http://docs.openstack.org/newton/networking-guide/config-auto-allocation.html + +Network v2 + +network auto allocated topology create +-------------------------------------- + +Create the auto allocated topology for project + +.. program:: network auto allocated topology create +.. code:: bash + + openstack network auto allocated topology create + [--or-show] + [--check-resources] + [--project [--project-domain ]] + +.. option:: --or-show + + If topology exists returns the topologies information (Default). + +.. option:: --check-resources + + Validate the requirements for auto allocated topology. + Does not return a topology. + +.. option:: --project + + Return the auto allocated topology for a given project. + Default is current project. + +.. option:: --project-domain + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + +.. _network_auto_allocated_topology_create: + + +network auto allocated topology delete +-------------------------------------- + +Delete auto allocated topology for project + +.. program:: network auto allocated topology delete +.. code:: bash + + openstack network auto allocated topology delete + [--project [--project-domain ]] + +.. option:: --project + + Delete auto allocated topology for a given project. + Default is the current project. + +.. option:: --project-domain + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + +.. _network_auto_allocated_topology_delete: diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 68c50d24d1..a0c67cd4c8 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -111,6 +111,7 @@ referring to both Compute and Volume quotas. * ``module``: (**Internal**) - installed Python modules in the OSC process * ``network``: (**Compute**, **Network**) - a virtual network for connecting servers and other resources * ``network agent``: (**Network**) - A network agent is an agent that handles various tasks used to implement virtual networks +* ``network auto allocated topology``: (**Network**) - an auto-allocated topology for a project * ``network flavor``: (**Network**) - allows the user to choose the type of service by a set of advertised service capabilities (e.g., LOADBALANCER, FWAAS, L3, VPN, etc) rather than by a provider type or named vendor * ``network meter``: (**Network**) - allow traffic metering in a network * ``network meter rule``: (**Network**) - rules for network traffic metering diff --git a/openstackclient/network/v2/network_auto_allocated_topology.py b/openstackclient/network/v2/network_auto_allocated_topology.py new file mode 100644 index 0000000000..36f392006e --- /dev/null +++ b/openstackclient/network/v2/network_auto_allocated_topology.py @@ -0,0 +1,136 @@ +# 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. +# + +"""Auto-allocated Topology Implementations""" + +import logging + +from osc_lib.command import command +from osc_lib import utils + +from openstackclient.i18n import _ +from openstackclient.identity import common as identity_common +from openstackclient.network import sdk_utils + +LOG = logging.getLogger(__name__) + + +def _get_columns(item): + column_map = { + 'tenant_id': 'project_id', + } + return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + + +def _format_check_resource_columns(): + return ('dry_run',) + + +def _format_check_resource(item): + item_id = getattr(item, 'id', False) + if item_id == 'dry-run=pass': + item.check_resource = 'pass' + return item + + +def _get_attrs(client_manager, parsed_args): + attrs = {} + if parsed_args.project: + identity_client = client_manager.identity + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + attrs['tenant_id'] = project_id + if parsed_args.check_resources: + attrs['check_resources'] = True + + return attrs + + +# TODO(ankur-gupta-f): Use the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. +class CreateAutoAllocatedTopology(command.ShowOne): + _description = _("Create the auto allocated topology for project") + + def get_parser(self, prog_name): + parser = super(CreateAutoAllocatedTopology, self).get_parser(prog_name) + parser.add_argument( + '--project', + metavar='', + help=_("Return the auto allocated topology for a given project. " + "Default is current project") + ) + identity_common.add_project_domain_option_to_parser(parser) + parser.add_argument( + '--check-resources', + action='store_true', + help=_("Validate the requirements for auto allocated topology. " + "Does not return a topology.") + ) + parser.add_argument( + '--or-show', + action='store_true', + default=True, + help=_("If topology exists returns the topology's " + "information (Default)") + ) + + return parser + + def check_resource_topology(self, client, parsed_args): + obj = client.validate_auto_allocated_topology(parsed_args.project) + + columns = _format_check_resource_columns() + data = utils.get_item_properties(_format_check_resource(obj), + columns, + formatters={}) + + return (columns, data) + + def get_topology(self, client, parsed_args): + obj = client.get_auto_allocated_topology(parsed_args.project) + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns, formatters={}) + return (display_columns, data) + + def take_action(self, parsed_args): + client = self.app.client_manager.network + if parsed_args.check_resources: + columns, data = self.check_resource_topology(client, parsed_args) + else: + columns, data = self.get_topology(client, parsed_args) + return (columns, data) + + +# TODO(ankur-gupta-f): Use the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. +class DeleteAutoAllocatedTopology(command.Command): + _description = _("Delete auto allocated topology for project") + + def get_parser(self, prog_name): + parser = super(DeleteAutoAllocatedTopology, self).get_parser(prog_name) + parser.add_argument( + '--project', + metavar='', + help=_('Delete auto allocated topology for a given project. ' + 'Default is the current project') + ) + identity_common.add_project_domain_option_to_parser(parser) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + client.delete_auto_allocated_topology(parsed_args.project) diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index 6a73b7e9b7..044eba7ab8 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -157,6 +157,31 @@ def get_address_scopes(address_scopes=None, count=2): return mock.Mock(side_effect=address_scopes) +class FakeAutoAllocatedTopology(object): + """Fake Auto Allocated Topology""" + + @staticmethod + def create_one_topology(attrs=None): + attrs = attrs or {} + + auto_allocated_topology_attrs = { + 'id': 'network-id-' + uuid.uuid4().hex, + 'tenant_id': 'project-id-' + uuid.uuid4().hex, + } + + auto_allocated_topology_attrs.update(attrs) + + auto_allocated_topology = fakes.FakeResource( + info=copy.deepcopy(auto_allocated_topology_attrs), + loaded=True) + + auto_allocated_topology.project_id = auto_allocated_topology_attrs[ + 'tenant_id' + ] + + return auto_allocated_topology + + class FakeAvailabilityZone(object): """Fake one or more network availability zones (AZs).""" diff --git a/openstackclient/tests/unit/network/v2/test_network_auto_allocated_topology.py b/openstackclient/tests/unit/network/v2/test_network_auto_allocated_topology.py new file mode 100644 index 0000000000..1a231160dd --- /dev/null +++ b/openstackclient/tests/unit/network/v2/test_network_auto_allocated_topology.py @@ -0,0 +1,267 @@ +# 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_auto_allocated_topology +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes +from openstackclient.tests.unit.network.v2 import fakes as network_fakes + + +class TestAutoAllocatedTopology(network_fakes.TestNetworkV2): + def setUp(self): + super(TestAutoAllocatedTopology, self).setUp() + self.network = self.app.client_manager.network + self.projects_mock = self.app.client_manager.identity.projects + + +class TestCreateAutoAllocatedTopology(TestAutoAllocatedTopology): + project = identity_fakes.FakeProject.create_one_project() + network_object = network_fakes.FakeNetwork.create_one_network() + + topology = network_fakes.FakeAutoAllocatedTopology.create_one_topology( + attrs={'id': network_object.id, + 'tenant_id': project.id} + ) + + columns = ( + 'id', + 'project_id', + ) + + data = ( + network_object.id, + project.id, + ) + + def setUp(self): + super(TestCreateAutoAllocatedTopology, self).setUp() + + self.cmd = network_auto_allocated_topology.CreateAutoAllocatedTopology( + self.app, + self.namespace) + self.network.get_auto_allocated_topology = mock.Mock( + return_value=self.topology) + + def test_create_no_options(self): + arglist = [] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.network.get_auto_allocated_topology.assert_called_with(None) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_project_option(self): + arglist = [ + '--project', self.project.id, + ] + + verifylist = [ + ('project', self.project.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.network.get_auto_allocated_topology.assert_called_with( + self.project.id + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_project_domain_option(self): + arglist = [ + '--project', self.project.id, + '--project-domain', self.project.domain_id, + ] + + verifylist = [ + ('project', self.project.id), + ('project_domain', self.project.domain_id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.network.get_auto_allocated_topology.assert_called_with( + self.project.id + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_or_show_option(self): + arglist = [ + '--or-show', + ] + + verifylist = [ + ('or_show', True), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.network.get_auto_allocated_topology.assert_called_with(None) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestValidateAutoAllocatedTopology(TestAutoAllocatedTopology): + project = identity_fakes.FakeProject.create_one_project() + network_object = network_fakes.FakeNetwork.create_one_network() + + topology = network_fakes.FakeAutoAllocatedTopology.create_one_topology( + attrs={'id': network_object.id, + 'tenant_id': project.id} + ) + + columns = ( + 'id', + 'project_id', + ) + + data = ( + network_object.id, + project.id, + ) + + def setUp(self): + super(TestValidateAutoAllocatedTopology, self).setUp() + + self.cmd = network_auto_allocated_topology.CreateAutoAllocatedTopology( + self.app, + self.namespace) + self.network.validate_auto_allocated_topology = mock.Mock( + return_value=self.topology) + + def test_show_dry_run_no_project(self): + arglist = [ + '--check-resources', + ] + verifylist = [ + ('check_resources', True), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.validate_auto_allocated_topology.assert_called_with( + None) + + def test_show_dry_run_project_option(self): + arglist = [ + '--check-resources', + '--project', self.project.id, + ] + verifylist = [ + ('check_resources', True), + ('project', self.project.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.validate_auto_allocated_topology.assert_called_with( + self.project.id) + + def test_show_dry_run_project_domain_option(self): + arglist = [ + '--check-resources', + '--project', self.project.id, + '--project-domain', self.project.domain_id, + ] + verifylist = [ + ('check_resources', True), + ('project', self.project.id), + ('project_domain', self.project.domain_id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.validate_auto_allocated_topology.assert_called_with( + self.project.id) + + +class TestDeleteAutoAllocatedTopology(TestAutoAllocatedTopology): + project = identity_fakes.FakeProject.create_one_project() + network_object = network_fakes.FakeNetwork.create_one_network() + + topology = network_fakes.FakeAutoAllocatedTopology.create_one_topology( + attrs={'id': network_object.id, + 'tenant_id': project.id} + ) + + def setUp(self): + super(TestDeleteAutoAllocatedTopology, self).setUp() + + self.cmd = network_auto_allocated_topology.DeleteAutoAllocatedTopology( + self.app, + self.namespace) + self.network.delete_auto_allocated_topology = mock.Mock( + return_value=None) + + def test_delete_no_project(self): + arglist = [] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.network.delete_auto_allocated_topology.assert_called_once_with( + None) + + self.assertIsNone(result) + + def test_delete_project_arg(self): + arglist = [ + '--project', self.project.id, + ] + verifylist = [ + ('project', self.project.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.network.delete_auto_allocated_topology.assert_called_once_with( + self.project.id) + + self.assertIsNone(result) + + def test_delete_project_domain_arg(self): + arglist = [ + '--project', self.project.id, + '--project-domain', self.project.domain_id, + ] + verifylist = [ + ('project', self.project.id), + ('project_domain', self.project.domain_id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.network.delete_auto_allocated_topology.assert_called_once_with( + self.project.id) + + self.assertIsNone(result) diff --git a/releasenotes/notes/add-network-auto-allocated-topology-481580f48840bfc4.yaml b/releasenotes/notes/add-network-auto-allocated-topology-481580f48840bfc4.yaml new file mode 100644 index 0000000000..df0831a55a --- /dev/null +++ b/releasenotes/notes/add-network-auto-allocated-topology-481580f48840bfc4.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add support for the ``network auto allocated topology`` command for + creating and deleting auto allocated topologies. + [Blueprint :oscbp:`network-auto-allocated-topology`] diff --git a/setup.cfg b/setup.cfg index da7c0aa4f7..10e1d888eb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -360,6 +360,9 @@ openstack.network.v2 = network_agent_set = openstackclient.network.v2.network_agent:SetNetworkAgent network_agent_show = openstackclient.network.v2.network_agent:ShowNetworkAgent + network_auto_allocated_topology_create = openstackclient.network.v2.network_auto_allocated_topology:CreateAutoAllocatedTopology + network_auto_allocated_topology_delete = openstackclient.network.v2.network_auto_allocated_topology:DeleteAutoAllocatedTopology + network_flavor_create = openstackclient.network.v2.network_flavor:CreateNetworkFlavor network_flavor_delete = openstackclient.network.v2.network_flavor:DeleteNetworkFlavor network_flavor_list = openstackclient.network.v2.network_flavor:ListNetworkFlavor From 0898ebacb8d9c38ae36adaf593867a9b16a50504 Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Wed, 22 Feb 2017 14:39:09 +0800 Subject: [PATCH 1570/3095] Support list commands by group name keyword The output of current "command list" is so long, it's very difficult for users to find out the commands they care about. Add "--group " option to filter the commands by group name keyword, like: --group volume, list all openstack.volume.v2 (cinder) commands That support the scenario that users need to know the current support commands of some OpenStack services(nova, neutron, cinder and so on) in OSC. Change-Id: Id673042729ad36a0cac0b81fb31a3537c24f03fc Closes-Bug: #1666780 --- doc/source/command-objects/command.rst | 6 ++++ openstackclient/common/module.py | 14 ++++++++ .../tests/functional/common/test_module.py | 35 +++++++++++++++++++ .../tests/unit/common/test_module.py | 35 +++++++++++++++++++ .../notes/bug-1666780-c10010e9061689d3.yaml | 11 ++++++ 5 files changed, 101 insertions(+) create mode 100644 releasenotes/notes/bug-1666780-c10010e9061689d3.yaml diff --git a/doc/source/command-objects/command.rst b/doc/source/command-objects/command.rst index 34c07da625..918fd959ad 100644 --- a/doc/source/command-objects/command.rst +++ b/doc/source/command-objects/command.rst @@ -15,3 +15,9 @@ List recognized commands by group .. code:: bash openstack command list + [--group ] + +.. option:: --group + + Show commands filtered by a command group, for example: identity, volume, + compute, image, network and other keywords diff --git a/openstackclient/common/module.py b/openstackclient/common/module.py index ba911ecb8f..20497f2131 100644 --- a/openstackclient/common/module.py +++ b/openstackclient/common/module.py @@ -29,12 +29,26 @@ class ListCommand(command.Lister): auth_required = False + def get_parser(self, prog_name): + parser = super(ListCommand, self).get_parser(prog_name) + parser.add_argument( + '--group', + metavar='', + help=_('Show commands filtered by a command group, for example: ' + 'identity, volume, compute, image, network and ' + 'other keywords'), + ) + return parser + def take_action(self, parsed_args): cm = self.app.command_manager groups = cm.get_command_groups() groups = sorted(groups) columns = ('Command Group', 'Commands') + if parsed_args.group: + groups = (group for group in groups if parsed_args.group in group) + commands = [] for group in groups: command_names = cm.get_command_names(group) diff --git a/openstackclient/tests/functional/common/test_module.py b/openstackclient/tests/functional/common/test_module.py index f4f2e95215..e9e4ee3a6f 100644 --- a/openstackclient/tests/functional/common/test_module.py +++ b/openstackclient/tests/functional/common/test_module.py @@ -42,3 +42,38 @@ def test_module_list(self): cmd_output = json.loads(self.openstack('module list --all -f json')) for one_module in self.CLIENTS + self.LIBS: self.assertIn(one_module, cmd_output.keys()) + + +class CommandTest(base.TestCase): + """Functional tests for openstackclient command list.""" + GROUPS = [ + 'openstack.volume.v2', + 'openstack.network.v2', + 'openstack.image.v2', + 'openstack.identity.v3', + 'openstack.compute.v2', + 'openstack.common', + 'openstack.cli', + ] + + def test_command_list_no_option(self): + cmd_output = json.loads(self.openstack('command list -f json')) + group_names = [each.get('Command Group') for each in cmd_output] + for one_group in self.GROUPS: + self.assertIn(one_group, group_names) + + def test_command_list_with_group(self): + input_groups = [ + 'volume', + 'network', + 'image', + 'identity', + 'compute.v2' + ] + for each_input in input_groups: + cmd_output = json.loads(self.openstack( + 'command list --group %s -f json' % each_input + )) + group_names = [each.get('Command Group') for each in cmd_output] + for each_name in group_names: + self.assertIn(each_input, each_name) diff --git a/openstackclient/tests/unit/common/test_module.py b/openstackclient/tests/unit/common/test_module.py index 4b586d3b2d..2491d63965 100644 --- a/openstackclient/tests/unit/common/test_module.py +++ b/openstackclient/tests/unit/common/test_module.py @@ -88,6 +88,41 @@ def test_command_list_no_options(self): self.assertEqual(datalist, tuple(data)) + def test_command_list_with_group_not_found(self): + arglist = [ + '--group', 'not_exist', + ] + verifylist = [ + ('group', 'not_exist'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = ('Command Group', 'Commands') + self.assertEqual(collist, columns) + self.assertEqual([], data) + + def test_command_list_with_group(self): + arglist = [ + '--group', 'common', + ] + verifylist = [ + ('group', 'common'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = ('Command Group', 'Commands') + self.assertEqual(collist, columns) + datalist = (( + 'openstack.common', + 'limits show\nextension list' + ),) + + self.assertEqual(datalist, tuple(data)) + @mock.patch.dict( 'openstackclient.common.module.sys.modules', diff --git a/releasenotes/notes/bug-1666780-c10010e9061689d3.yaml b/releasenotes/notes/bug-1666780-c10010e9061689d3.yaml new file mode 100644 index 0000000000..952a1f7a95 --- /dev/null +++ b/releasenotes/notes/bug-1666780-c10010e9061689d3.yaml @@ -0,0 +1,11 @@ +--- +features: + - | + Support list commands by group name keyword. Add ``--group`` option to + filter the commands by group name keyword, like: --group volume, list all + openstack.volume.v2 (cinder) commands. + That support the scenario that users need to know the current support + commands of some OpenStack services(nova, neutron, cinder and so on) in + OSC, and make it easier for users to find out the related commands that + they care about. + [Bug `1666780 `_] From ef2a8f4d11c30c9e739f667ba4bad1730a84c739 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Thu, 23 Feb 2017 20:39:43 +0800 Subject: [PATCH 1571/3095] Revert unit tests for quota commands quota set and quota show command have been fixed by [1] [2], now can revert the unit test as well [1] https://review.openstack.org/435574 [2] https://review.openstack.org/435735 Change-Id: I3d592df6ea4e96770dac4dba91819b5c4bcb0561 --- .../tests/unit/common/test_quota.py | 72 ++++++++----------- 1 file changed, 30 insertions(+), 42 deletions(-) diff --git a/openstackclient/tests/unit/common/test_quota.py b/openstackclient/tests/unit/common/test_quota.py index 244d74d25d..7dd2337321 100644 --- a/openstackclient/tests/unit/common/test_quota.py +++ b/openstackclient/tests/unit/common/test_quota.py @@ -13,8 +13,6 @@ import copy import mock -from openstack.network.v2 import quota as _quota - from openstackclient.common import quota from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes from openstackclient.tests.unit import fakes @@ -284,32 +282,27 @@ def test_quota_set_network(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # TODO(huanxuan): Remove this if condition once the fixed - # SDK Quota class is the minimum required version. - # This is expected to be SDK release 0.9.13 - if not hasattr(_quota.Quota, 'allow_get'): - # Just run this when sdk <= 0.9.10 - result = self.cmd.take_action(parsed_args) - kwargs = { - 'subnet': network_fakes.QUOTA['subnet'], - 'network': network_fakes.QUOTA['network'], - 'floatingip': network_fakes.QUOTA['floatingip'], - 'subnetpool': network_fakes.QUOTA['subnetpool'], - 'security_group_rule': - network_fakes.QUOTA['security_group_rule'], - 'security_group': network_fakes.QUOTA['security_group'], - 'router': network_fakes.QUOTA['router'], - 'rbac_policy': network_fakes.QUOTA['rbac_policy'], - 'port': network_fakes.QUOTA['port'], - 'vip': network_fakes.QUOTA['vip'], - 'healthmonitor': network_fakes.QUOTA['healthmonitor'], - 'l7policy': network_fakes.QUOTA['l7policy'], - } - self.network_mock.update_quota.assert_called_once_with( - identity_fakes.project_id, - **kwargs - ) - self.assertIsNone(result) + result = self.cmd.take_action(parsed_args) + kwargs = { + 'subnet': network_fakes.QUOTA['subnet'], + 'network': network_fakes.QUOTA['network'], + 'floatingip': network_fakes.QUOTA['floatingip'], + 'subnetpool': network_fakes.QUOTA['subnetpool'], + 'security_group_rule': + network_fakes.QUOTA['security_group_rule'], + 'security_group': network_fakes.QUOTA['security_group'], + 'router': network_fakes.QUOTA['router'], + 'rbac_policy': network_fakes.QUOTA['rbac_policy'], + 'port': network_fakes.QUOTA['port'], + 'vip': network_fakes.QUOTA['vip'], + 'healthmonitor': network_fakes.QUOTA['healthmonitor'], + 'l7policy': network_fakes.QUOTA['l7policy'], + } + self.network_mock.update_quota.assert_called_once_with( + identity_fakes.project_id, + **kwargs + ) + self.assertIsNone(result) def test_quota_set_with_class(self): arglist = [ @@ -483,20 +476,15 @@ def test_quota_show_with_default(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # TODO(huanxuan): Remove this if condition once the fixed - # SDK QuotaDefault class is the minimum required version. - # This is expected to be SDK release 0.9.13 - if not hasattr(_quota.QuotaDefault, 'project'): - # Just run this when sdk <= 0.9.10 - self.cmd.take_action(parsed_args) - - self.quotas_mock.defaults.assert_called_once_with( - identity_fakes.project_id) - self.volume_quotas_mock.defaults.assert_called_once_with( - identity_fakes.project_id) - self.network.get_quota_default.assert_called_once_with( - identity_fakes.project_id) - self.assertNotCalled(self.network.get_quota) + self.cmd.take_action(parsed_args) + + self.quotas_mock.defaults.assert_called_once_with( + identity_fakes.project_id) + self.volume_quotas_mock.defaults.assert_called_once_with( + identity_fakes.project_id) + self.network.get_quota_default.assert_called_once_with( + identity_fakes.project_id) + self.assertNotCalled(self.network.get_quota) def test_quota_show_with_class(self): arglist = [ From 40ec7a9c96f4ce4071e47e5bf0c249aa77b5b2ee Mon Sep 17 00:00:00 2001 From: "zhiyong.dai" Date: Thu, 1 Dec 2016 02:20:23 +0800 Subject: [PATCH 1572/3095] Support --no-property in "volume set" command Add "--no-property" option to "volume set" command in v1 and v2 and update the test cases. Change-Id: Id5660f23b3b2d9aa72f4c16b19ce83f3f7ed2fa4 --- doc/source/command-objects/volume.rst | 9 +++++++- .../tests/functional/volume/v1/test_volume.py | 9 ++++---- .../tests/functional/volume/v2/test_volume.py | 22 ++++++++++++++----- .../tests/unit/volume/v1/test_volume.py | 6 +++++ .../tests/unit/volume/v2/test_volume.py | 18 +++++++++++++++ openstackclient/volume/v1/volume.py | 18 +++++++++++++++ openstackclient/volume/v2/volume.py | 19 +++++++++++++++- ...o-property-in-volume-811e67ff4a199eb6.yaml | 5 +++++ 8 files changed, 95 insertions(+), 11 deletions(-) create mode 100644 releasenotes/notes/support-no-property-in-volume-811e67ff4a199eb6.yaml diff --git a/doc/source/command-objects/volume.rst b/doc/source/command-objects/volume.rst index 56c5455283..67598e63c3 100644 --- a/doc/source/command-objects/volume.rst +++ b/doc/source/command-objects/volume.rst @@ -269,6 +269,7 @@ Set volume properties [--name ] [--size ] [--description ] + [--no-property] [--property [...] ] [--image-property [...] ] [--state ] @@ -290,6 +291,12 @@ Set volume properties New volume description +.. option:: --no-property + + Remove all properties from :ref:`\ ` + (specify both :option:`--no-property` and :option:`--property` to + remove the current properties before setting new properties.) + .. option:: --property Set a property on this volume (repeat option to set multiple properties) @@ -304,7 +311,7 @@ Set volume properties Migration policy while re-typing volume ("never" or "on-demand", default is "never" ) - (available only when "--type" option is specified) + (available only when :option:`--type` option is specified) *Volume version 2 only* diff --git a/openstackclient/tests/functional/volume/v1/test_volume.py b/openstackclient/tests/functional/volume/v1/test_volume.py index 992dfd66bf..3f04e07154 100644 --- a/openstackclient/tests/functional/volume/v1/test_volume.py +++ b/openstackclient/tests/functional/volume/v1/test_volume.py @@ -141,8 +141,9 @@ def test_volume_set_and_unset(self): '--name ' + new_name + ' --size 2 ' + '--description bbbb ' + - '--property Alpha=c ' + + '--no-property ' + '--property Beta=b ' + + '--property Gamma=c ' + '--bootable ' + name, ) @@ -165,7 +166,7 @@ def test_volume_set_and_unset(self): cmd_output["display_description"], ) self.assertEqual( - "Alpha='c', Beta='b'", + "Beta='b', Gamma='c'", cmd_output["properties"], ) self.assertEqual( @@ -176,7 +177,7 @@ def test_volume_set_and_unset(self): # Test volume unset raw_output = self.openstack( 'volume unset ' + - '--property Alpha ' + + '--property Beta ' + new_name, ) self.assertOutput('', raw_output) @@ -186,7 +187,7 @@ def test_volume_set_and_unset(self): new_name )) self.assertEqual( - "Beta='b'", + "Gamma='c'", cmd_output["properties"], ) diff --git a/openstackclient/tests/functional/volume/v2/test_volume.py b/openstackclient/tests/functional/volume/v2/test_volume.py index 203ca819a5..ce98236f8d 100644 --- a/openstackclient/tests/functional/volume/v2/test_volume.py +++ b/openstackclient/tests/functional/volume/v2/test_volume.py @@ -104,7 +104,7 @@ def test_volume_list(self): # TODO(qiangjiahui): Add project option to filter tests when we can # specify volume with project - def test_volume_set(self): + def test_volume_set_and_unset(self): """Tests create volume, set, unset, show, delete""" name = uuid.uuid4().hex new_name = name + "_" @@ -144,8 +144,11 @@ def test_volume_set(self): '--name ' + new_name + ' --size 2 ' + '--description bbbb ' + - '--property Alpha=c ' + + '--no-property ' + '--property Beta=b ' + + '--property Gamma=c ' + + '--image-property a=b ' + + '--image-property c=d ' + '--bootable ' + name, ) @@ -168,9 +171,13 @@ def test_volume_set(self): cmd_output["description"], ) self.assertEqual( - "Alpha='c', Beta='b'", + "Beta='b', Gamma='c'", cmd_output["properties"], ) + self.assertEqual( + {'a': 'b', 'c': 'd'}, + cmd_output["volume_image_metadata"], + ) self.assertEqual( 'true', cmd_output["bootable"], @@ -179,7 +186,8 @@ def test_volume_set(self): # Test volume unset raw_output = self.openstack( 'volume unset ' + - '--property Alpha ' + + '--property Beta ' + + '--image-property a ' + new_name, ) self.assertOutput('', raw_output) @@ -189,9 +197,13 @@ def test_volume_set(self): new_name )) self.assertEqual( - "Beta='b'", + "Gamma='c'", cmd_output["properties"], ) + self.assertEqual( + {'c': 'd'}, + cmd_output["volume_image_metadata"], + ) def test_volume_snapshot(self): """Tests volume create from snapshot""" diff --git a/openstackclient/tests/unit/volume/v1/test_volume.py b/openstackclient/tests/unit/volume/v1/test_volume.py index 6c6d9a1d6f..d46a7ba950 100644 --- a/openstackclient/tests/unit/volume/v1/test_volume.py +++ b/openstackclient/tests/unit/volume/v1/test_volume.py @@ -1071,6 +1071,7 @@ def test_volume_set_size_not_available(self): def test_volume_set_property(self): arglist = [ + '--no-property', '--property', 'myprop=myvalue', self._volume.display_name, ] @@ -1080,6 +1081,7 @@ def test_volume_set_property(self): ('name', None), ('description', None), ('size', None), + ('no_property', True), ('property', {'myprop': 'myvalue'}), ('volume', self._volume.display_name), ('bootable', False), @@ -1097,6 +1099,10 @@ def test_volume_set_property(self): self._volume.id, metadata ) + self.volumes_mock.delete_metadata.assert_called_with( + self._volume.id, + self._volume.metadata.keys() + ) self.volumes_mock.update_readonly_flag.assert_not_called() self.assertIsNone(result) diff --git a/openstackclient/tests/unit/volume/v2/test_volume.py b/openstackclient/tests/unit/volume/v2/test_volume.py index 4fef9dd9a6..fbe719f3eb 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume.py +++ b/openstackclient/tests/unit/volume/v2/test_volume.py @@ -1368,6 +1368,24 @@ def setUp(self): # Get the command object to test self.cmd = volume.SetVolume(self.app, None) + def test_volume_set_property(self): + arglist = [ + '--property', 'a=b', + '--property', 'c=d', + self.new_volume.id, + ] + verifylist = [ + ('property', {'a': 'b', 'c': 'd'}), + ('volume', self.new_volume.id), + ('bootable', False), + ('non_bootable', False) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + self.volumes_mock.set_metadata.assert_called_with( + self.new_volume.id, parsed_args.property) + def test_volume_set_image_property(self): arglist = [ '--image-property', 'Alpha=a', diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index 739484dfc0..8e1097f516 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -439,6 +439,15 @@ def get_parser(self, prog_name): type=int, help=_('Extend volume size in GB'), ) + parser.add_argument( + "--no-property", + dest="no_property", + action="store_true", + help=_("Remove all properties from " + "(specify both --no-property and --property to " + "remove the current properties before setting " + "new properties.)"), + ) parser.add_argument( '--property', metavar='', @@ -489,6 +498,15 @@ def take_action(self, parsed_args): except Exception as e: LOG.error(_("Failed to set volume size: %s"), e) result += 1 + + if parsed_args.no_property: + try: + volume_client.volumes.delete_metadata( + volume.id, volume.metadata.keys()) + except Exception as e: + LOG.error(_("Failed to clean volume properties: %s"), e) + result += 1 + if parsed_args.property: try: volume_client.volumes.set_metadata( diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index 78db261bd1..c361d7004c 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -523,6 +523,15 @@ def get_parser(self, prog_name): metavar='', help=_('New volume description'), ) + parser.add_argument( + "--no-property", + dest="no_property", + action="store_true", + help=_("Remove all properties from " + "(specify both --no-property and --property to " + "remove the current properties before setting " + "new properties.)"), + ) parser.add_argument( '--property', metavar='', @@ -561,7 +570,7 @@ def get_parser(self, prog_name): choices=['never', 'on-demand'], help=_('Migration policy while re-typing volume ' '("never" or "on-demand", default is "never" ) ' - '(available only when "--type" option is specified)'), + '(available only when --type option is specified)'), ) bootable_group = parser.add_mutually_exclusive_group() bootable_group.add_argument( @@ -607,6 +616,14 @@ def take_action(self, parsed_args): LOG.error(_("Failed to set volume size: %s"), e) result += 1 + if parsed_args.no_property: + try: + volume_client.volumes.delete_metadata( + volume.id, volume.metadata.keys()) + except Exception as e: + LOG.error(_("Failed to clean volume properties: %s"), e) + result += 1 + if parsed_args.property: try: volume_client.volumes.set_metadata( diff --git a/releasenotes/notes/support-no-property-in-volume-811e67ff4a199eb6.yaml b/releasenotes/notes/support-no-property-in-volume-811e67ff4a199eb6.yaml new file mode 100644 index 0000000000..fda425e180 --- /dev/null +++ b/releasenotes/notes/support-no-property-in-volume-811e67ff4a199eb6.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``--no-property`` option in ``volume set``, this removes all properties from a volume. + [Blueprint `allow-overwrite-set-options `_] From 7d93db21e59e8518ed2ca8018cecb69dc3f5b2e4 Mon Sep 17 00:00:00 2001 From: "jiahui.qiang" Date: Thu, 19 Jan 2017 04:35:29 +0800 Subject: [PATCH 1573/3095] Fix can not set is_default in network The value of is_default always be None, can not be set by "network set" command. Allow "--default" and "--no-default" options to be recognized when ``--external`` is not present. Closes-bug:#1665231 Change-Id: I7a05fc7734a15994f72ca4e47997b4952f1f72f8 --- openstackclient/network/v2/network.py | 8 +-- .../functional/network/v2/test_network.py | 68 ++++--------------- .../notes/bug-1665231-3df6d090d137fe4f.yaml | 6 ++ 3 files changed, 25 insertions(+), 57 deletions(-) create mode 100644 releasenotes/notes/bug-1665231-3df6d090d137fe4f.yaml diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 655147d95f..3aa8ec8442 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -111,10 +111,10 @@ def _get_attrs(client_manager, parsed_args): attrs['router:external'] = False if parsed_args.external: attrs['router:external'] = True - if parsed_args.no_default: - attrs['is_default'] = False - if parsed_args.default: - attrs['is_default'] = True + if parsed_args.no_default: + attrs['is_default'] = False + if parsed_args.default: + attrs['is_default'] = True # Update Provider network options if parsed_args.provider_network_type: attrs['provider:network_type'] = parsed_args.provider_network_type diff --git a/openstackclient/tests/functional/network/v2/test_network.py b/openstackclient/tests/functional/network/v2/test_network.py index 1f7b7c9e6e..126365581a 100644 --- a/openstackclient/tests/functional/network/v2/test_network.py +++ b/openstackclient/tests/functional/network/v2/test_network.py @@ -126,6 +126,7 @@ def test_network_list(self): cmd_output = json.loads(self.openstack( 'network create -f json ' + '--description aaaa ' + + '--no-default ' + name1 )) self.addCleanup(self.openstack, 'network delete ' + name1) @@ -147,17 +148,11 @@ def test_network_list(self): 'Internal', cmd_output["router:external"], ) - # NOTE(dtroyer): is_default is not present in the create output - # so make sure it stays that way. - # NOTE(stevemar): is_default *is* present in SDK 0.9.11 and newer, - # but the value seems to always be None, regardless - # of the --default or --no-default value. - # self.assertEqual('x', cmd_output) - if ('is_default' in cmd_output): - self.assertEqual( - None, - cmd_output["is_default"], - ) + + self.assertEqual( + False, + cmd_output["is_default"], + ) self.assertEqual( True, cmd_output["port_security_enabled"], @@ -185,11 +180,10 @@ def test_network_list(self): True, cmd_output["shared"], ) - if ('is_default' in cmd_output): - self.assertEqual( - None, - cmd_output["is_default"], - ) + self.assertEqual( + None, + cmd_output["is_default"], + ) self.assertEqual( True, cmd_output["port_security_enabled"], @@ -275,16 +269,11 @@ def test_network_set(self): 'Internal', cmd_output["router:external"], ) - # NOTE(dtroyer): is_default is not present in the create output - # so make sure it stays that way. - # NOTE(stevemar): is_default *is* present in SDK 0.9.11 and newer, - # but the value seems to always be None, regardless - # of the --default or --no-default value. - if ('is_default' in cmd_output): - self.assertEqual( - None, - cmd_output["is_default"], - ) + + self.assertEqual( + False, + cmd_output["is_default"], + ) self.assertEqual( True, cmd_output["port_security_enabled"], @@ -321,7 +310,6 @@ def test_network_set(self): 'External', cmd_output["router:external"], ) - # why not 'None' like above?? self.assertEqual( False, cmd_output["is_default"], @@ -330,29 +318,3 @@ def test_network_set(self): False, cmd_output["port_security_enabled"], ) - - # NOTE(dtroyer): There is ambiguity around is_default in that - # it is not in the API docs and apparently can - # not be set when the network is --external, - # although the option handling code only looks at - # the value of is_default when external is True. - raw_output = self.openstack( - 'network set ' + - '--default ' + - name - ) - self.assertOutput('', raw_output) - - cmd_output = json.loads(self.openstack( - 'network show -f json ' + name - )) - - self.assertEqual( - 'cccc', - cmd_output["description"], - ) - # NOTE(dtroyer): This should be 'True' - self.assertEqual( - False, - cmd_output["port_security_enabled"], - ) diff --git a/releasenotes/notes/bug-1665231-3df6d090d137fe4f.yaml b/releasenotes/notes/bug-1665231-3df6d090d137fe4f.yaml new file mode 100644 index 0000000000..104ea7182f --- /dev/null +++ b/releasenotes/notes/bug-1665231-3df6d090d137fe4f.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Allow ``--default`` and ``--no-default`` options in ``network create`` command to + be recognized when ``--external`` is not present. + [Bug `1665231 `_] From 4ea4f6fabb5bd45f15f21fc5d84f0f85032e4f23 Mon Sep 17 00:00:00 2001 From: David Rabel Date: Thu, 23 Feb 2017 12:10:51 +0100 Subject: [PATCH 1574/3095] openstack image create : --file and --volume exclude each other Added parser.add_mutually_exclusive_group() for --file and --volume in openstack image create. Change-Id: I4d9fc6314801d569354e5644e231ddd6c7f1853d Closes-Bug: 1666551 --- openstackclient/image/v1/image.py | 5 +++-- openstackclient/image/v2/image.py | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index f4944afa6f..60d6170901 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -128,12 +128,13 @@ def get_parser(self, prog_name): metavar="", help=_("Copy image from the data store (similar to --location)"), ) - parser.add_argument( + source_group = parser.add_mutually_exclusive_group() + source_group.add_argument( "--file", metavar="", help=_("Upload image from local file"), ) - parser.add_argument( + source_group.add_argument( "--volume", metavar="", help=_("Create image from a volume"), diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 29499ec2c8..c4be69f055 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -164,12 +164,13 @@ def get_parser(self, prog_name): type=int, help=_("Minimum RAM size needed to boot image, in megabytes"), ) - parser.add_argument( + source_group = parser.add_mutually_exclusive_group() + source_group.add_argument( "--file", metavar="", help=_("Upload image from local file"), ) - parser.add_argument( + source_group.add_argument( "--volume", metavar="", help=_("Create image from a volume"), From f63a9f402dc3761a1f7e358d92b7e1aa33098c7a Mon Sep 17 00:00:00 2001 From: Jens Rosenboom Date: Fri, 24 Feb 2017 16:04:43 +0100 Subject: [PATCH 1575/3095] Fix output of ListSecurityGroupRule The Ethertype column was always left empty because a wrong column name was being used. Change-Id: I7fc0f8d5eb7bac1efb234faba454dad0a45a7e6a Closes-Bug: 1667699 --- .../network/v2/security_group_rule.py | 2 +- .../tests/unit/network/v2/fakes.py | 2 +- .../network/v2/test_security_group_rule.py | 42 +++++++++---------- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index 63b80d25e9..8f07c5a466 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -521,7 +521,7 @@ def take_action_network(self, client, parsed_args): 'port_range', ) if parsed_args.long: - columns = columns + ('direction', 'ethertype',) + columns = columns + ('direction', 'ether_type',) columns = columns + ('remote_group_id',) # Get the security group rules using the requested query. diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index 6a73b7e9b7..1125289e28 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -1119,7 +1119,7 @@ def create_one_security_group_rule(attrs=None): 'description': 'security-group-rule-description-' + uuid.uuid4().hex, 'direction': 'ingress', - 'ethertype': 'IPv4', + 'ether_type': 'IPv4', 'id': 'security-group-rule-id-' + uuid.uuid4().hex, 'port_range_max': None, 'port_range_min': None, diff --git a/openstackclient/tests/unit/network/v2/test_security_group_rule.py b/openstackclient/tests/unit/network/v2/test_security_group_rule.py index 5fe9013eb0..e3538d5f4a 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_rule.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_rule.py @@ -62,7 +62,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): expected_columns = ( 'description', 'direction', - 'ethertype', + 'ether_type', 'id', 'port_range_max', 'port_range_min', @@ -84,7 +84,7 @@ def _setup_security_group_rule(self, attrs=None): self.expected_data = ( self._security_group_rule.description, self._security_group_rule.direction, - self._security_group_rule.ethertype, + self._security_group_rule.ether_type, self._security_group_rule.id, self._security_group_rule.port_range_max, self._security_group_rule.port_range_min, @@ -184,7 +184,7 @@ def test_create_default_rule(self): self.network.create_security_group_rule.assert_called_once_with(**{ 'direction': self._security_group_rule.direction, - 'ethertype': self._security_group_rule.ethertype, + 'ethertype': self._security_group_rule.ether_type, 'port_range_max': self._security_group_rule.port_range_max, 'port_range_min': self._security_group_rule.port_range_min, 'protocol': self._security_group_rule.protocol, @@ -216,7 +216,7 @@ def test_create_proto_option(self): self.network.create_security_group_rule.assert_called_once_with(**{ 'direction': self._security_group_rule.direction, - 'ethertype': self._security_group_rule.ethertype, + 'ethertype': self._security_group_rule.ether_type, 'protocol': self._security_group_rule.protocol, 'remote_ip_prefix': self._security_group_rule.remote_ip_prefix, 'security_group_id': self._security_group.id, @@ -249,7 +249,7 @@ def test_create_remote_group(self): self.network.create_security_group_rule.assert_called_once_with(**{ 'direction': self._security_group_rule.direction, - 'ethertype': self._security_group_rule.ethertype, + 'ethertype': self._security_group_rule.ether_type, 'port_range_max': self._security_group_rule.port_range_max, 'port_range_min': self._security_group_rule.port_range_min, 'protocol': self._security_group_rule.protocol, @@ -279,7 +279,7 @@ def test_create_source_group(self): self.network.create_security_group_rule.assert_called_once_with(**{ 'direction': self._security_group_rule.direction, - 'ethertype': self._security_group_rule.ethertype, + 'ethertype': self._security_group_rule.ether_type, 'protocol': self._security_group_rule.protocol, 'remote_group_id': self._security_group_rule.remote_group_id, 'security_group_id': self._security_group.id, @@ -308,7 +308,7 @@ def test_create_source_ip(self): self.network.create_security_group_rule.assert_called_once_with(**{ 'direction': self._security_group_rule.direction, - 'ethertype': self._security_group_rule.ethertype, + 'ethertype': self._security_group_rule.ether_type, 'protocol': self._security_group_rule.protocol, 'remote_ip_prefix': self._security_group_rule.remote_ip_prefix, 'security_group_id': self._security_group.id, @@ -337,7 +337,7 @@ def test_create_remote_ip(self): self.network.create_security_group_rule.assert_called_once_with(**{ 'direction': self._security_group_rule.direction, - 'ethertype': self._security_group_rule.ethertype, + 'ethertype': self._security_group_rule.ether_type, 'protocol': self._security_group_rule.protocol, 'remote_ip_prefix': self._security_group_rule.remote_ip_prefix, 'security_group_id': self._security_group.id, @@ -348,7 +348,7 @@ def test_create_remote_ip(self): def test_create_network_options(self): self._setup_security_group_rule({ 'direction': 'egress', - 'ethertype': 'IPv6', + 'ether_type': 'IPv6', 'port_range_max': 443, 'port_range_min': 443, 'protocol': '6', @@ -358,7 +358,7 @@ def test_create_network_options(self): arglist = [ '--dst-port', str(self._security_group_rule.port_range_min), '--egress', - '--ethertype', self._security_group_rule.ethertype, + '--ethertype', self._security_group_rule.ether_type, '--project', self.project.name, '--project-domain', self.domain.name, '--protocol', self._security_group_rule.protocol, @@ -368,7 +368,7 @@ def test_create_network_options(self): ('dst_port', (self._security_group_rule.port_range_min, self._security_group_rule.port_range_max)), ('egress', True), - ('ethertype', self._security_group_rule.ethertype), + ('ethertype', self._security_group_rule.ether_type), ('project', self.project.name), ('project_domain', self.domain.name), ('protocol', self._security_group_rule.protocol), @@ -380,7 +380,7 @@ def test_create_network_options(self): self.network.create_security_group_rule.assert_called_once_with(**{ 'direction': self._security_group_rule.direction, - 'ethertype': self._security_group_rule.ethertype, + 'ethertype': self._security_group_rule.ether_type, 'port_range_max': self._security_group_rule.port_range_max, 'port_range_min': self._security_group_rule.port_range_min, 'protocol': self._security_group_rule.protocol, @@ -444,7 +444,7 @@ def test_create_icmp_type(self): self.network.create_security_group_rule.assert_called_once_with(**{ 'direction': self._security_group_rule.direction, - 'ethertype': self._security_group_rule.ethertype, + 'ethertype': self._security_group_rule.ether_type, 'port_range_min': self._security_group_rule.port_range_min, 'protocol': self._security_group_rule.protocol, 'remote_ip_prefix': self._security_group_rule.remote_ip_prefix, @@ -455,7 +455,7 @@ def test_create_icmp_type(self): def test_create_ipv6_icmp_type_code(self): self._setup_security_group_rule({ - 'ethertype': 'IPv6', + 'ether_type': 'IPv6', 'port_range_min': 139, 'port_range_max': 2, 'protocol': 'ipv6-icmp', @@ -479,7 +479,7 @@ def test_create_ipv6_icmp_type_code(self): self.network.create_security_group_rule.assert_called_once_with(**{ 'direction': self._security_group_rule.direction, - 'ethertype': self._security_group_rule.ethertype, + 'ethertype': self._security_group_rule.ether_type, 'port_range_min': self._security_group_rule.port_range_min, 'port_range_max': self._security_group_rule.port_range_max, 'protocol': self._security_group_rule.protocol, @@ -490,7 +490,7 @@ def test_create_ipv6_icmp_type_code(self): def test_create_icmpv6_type(self): self._setup_security_group_rule({ - 'ethertype': 'IPv6', + 'ether_type': 'IPv6', 'port_range_min': 139, 'protocol': 'icmpv6', }) @@ -512,7 +512,7 @@ def test_create_icmpv6_type(self): self.network.create_security_group_rule.assert_called_once_with(**{ 'direction': self._security_group_rule.direction, - 'ethertype': self._security_group_rule.ethertype, + 'ethertype': self._security_group_rule.ether_type, 'port_range_min': self._security_group_rule.port_range_min, 'protocol': self._security_group_rule.protocol, 'security_group_id': self._security_group.id, @@ -539,7 +539,7 @@ def test_create_with_description(self): self.network.create_security_group_rule.assert_called_once_with(**{ 'description': self._security_group_rule.description, 'direction': self._security_group_rule.direction, - 'ethertype': self._security_group_rule.ethertype, + 'ethertype': self._security_group_rule.ether_type, 'protocol': self._security_group_rule.protocol, 'remote_ip_prefix': self._security_group_rule.remote_ip_prefix, 'security_group_id': self._security_group.id, @@ -1039,7 +1039,7 @@ class TestListSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): security_group_rule._format_network_port_range( _security_group_rule), _security_group_rule.direction, - _security_group_rule.ethertype, + _security_group_rule.ether_type, _security_group_rule.remote_group_id, )) expected_data_no_group.append(( @@ -1299,7 +1299,7 @@ class TestShowSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): columns = ( 'description', 'direction', - 'ethertype', + 'ether_type', 'id', 'port_range_max', 'port_range_min', @@ -1313,7 +1313,7 @@ class TestShowSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): data = ( _security_group_rule.description, _security_group_rule.direction, - _security_group_rule.ethertype, + _security_group_rule.ether_type, _security_group_rule.id, _security_group_rule.port_range_max, _security_group_rule.port_range_min, From 9d946f0f45c83c5677e9dd2688830c45cb6a24af Mon Sep 17 00:00:00 2001 From: Ankur Gupta Date: Mon, 6 Feb 2017 22:37:46 -0800 Subject: [PATCH 1576/3095] Port set/unset SecGroup Bug Fix Throwing error 'Port' object has no attribute 'security_groups' Fix for set and unset. Change-Id: I1a0625b5a432c7a91cf40249ce4f7c883f53d704 Closes-Bug: #1656788 --- openstackclient/network/v2/port.py | 48 +++++++++---------- .../tests/functional/network/v2/test_port.py | 15 +++++- .../tests/unit/network/v2/fakes.py | 4 +- .../tests/unit/network/v2/test_port.py | 44 ++++++++--------- .../notes/bug-1656788-2f5bda2205bc0329.yaml | 6 +++ 5 files changed, 67 insertions(+), 50 deletions(-) create mode 100644 releasenotes/notes/bug-1656788-2f5bda2205bc0329.yaml diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 7283dc0706..6117175e9e 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -51,7 +51,6 @@ def _format_dns_assignment(dns_assignment): 'extra_dhcp_opts': utils.format_list_of_dicts, 'fixed_ips': utils.format_list_of_dicts, 'security_group_ids': utils.format_list, - 'security_groups': utils.format_list, } @@ -64,7 +63,6 @@ def _get_columns(item): 'binding:vnic_type': 'binding_vnic_type', 'is_admin_state_up': 'admin_state_up', 'is_port_security_enabled': 'port_security_enabled', - 'security_group_ids': 'security_groups', 'tenant_id': 'project_id', } return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) @@ -349,7 +347,7 @@ def get_parser(self, prog_name): '--security-group', metavar='', action='append', - dest='security_groups', + dest='security_group', help=_("Security group to associate with this port (name or ID) " "(repeat option to set multiple security groups)") ) @@ -391,12 +389,13 @@ def take_action(self, parsed_args): _prepare_fixed_ips(self.app.client_manager, parsed_args) attrs = _get_attrs(self.app.client_manager, parsed_args) - if parsed_args.security_groups: - attrs['security_groups'] = [client.find_security_group( - sg, ignore_missing=False).id - for sg in parsed_args.security_groups] - if parsed_args.no_security_group: - attrs['security_groups'] = [] + if parsed_args.security_group: + attrs['security_group_ids'] = [client.find_security_group( + sg, ignore_missing=False).id + for sg in + parsed_args.security_group] + elif parsed_args.no_security_group: + attrs['security_group_ids'] = [] if parsed_args.allowed_address_pairs: attrs['allowed_address_pairs'] = ( _convert_address_pairs(parsed_args)) @@ -626,7 +625,7 @@ def get_parser(self, prog_name): '--security-group', metavar='', action='append', - dest='security_groups', + dest='security_group', help=_("Security group to associate with this port (name or ID) " "(repeat option to set multiple security groups)") ) @@ -694,17 +693,16 @@ def take_action(self, parsed_args): attrs['fixed_ips'] += [ip for ip in obj.fixed_ips if ip] elif parsed_args.no_fixed_ip: attrs['fixed_ips'] = [] - if parsed_args.security_groups and parsed_args.no_security_group: - attrs['security_groups'] = [client.find_security_group(sg, - ignore_missing=False).id - for sg in parsed_args.security_groups] - elif parsed_args.security_groups: - attrs['security_groups'] = obj.security_groups - for sg in parsed_args.security_groups: - sg_id = client.find_security_group(sg, ignore_missing=False).id - attrs['security_groups'].append(sg_id) + + if parsed_args.security_group: + attrs['security_group_ids'] = [ + client.find_security_group(sg, ignore_missing=False).id for + sg in parsed_args.security_group] + if not parsed_args.no_security_group: + attrs['security_group_ids'] += obj.security_group_ids + elif parsed_args.no_security_group: - attrs['security_groups'] = [] + attrs['security_group_ids'] = [] if (parsed_args.allowed_address_pairs and parsed_args.no_allowed_address_pair): @@ -769,7 +767,7 @@ def get_parser(self, prog_name): '--security-group', metavar='', action='append', - dest='security_groups', + dest='security_group_ids', help=_("Security group which should be removed this port (name " "or ID) (repeat option to unset multiple security groups)") ) @@ -802,7 +800,7 @@ def take_action(self, parsed_args): # Unset* classes tmp_fixed_ips = copy.deepcopy(obj.fixed_ips) tmp_binding_profile = copy.deepcopy(obj.binding_profile) - tmp_secgroups = copy.deepcopy(obj.security_groups) + tmp_secgroups = copy.deepcopy(obj.security_group_ids) tmp_addr_pairs = copy.deepcopy(obj.allowed_address_pairs) _prepare_fixed_ips(self.app.client_manager, parsed_args) attrs = {} @@ -822,16 +820,16 @@ def take_action(self, parsed_args): msg = _("Port does not contain binding-profile %s") % key raise exceptions.CommandError(msg) attrs['binding:profile'] = tmp_binding_profile - if parsed_args.security_groups: + if parsed_args.security_group_ids: try: - for sg in parsed_args.security_groups: + for sg in parsed_args.security_group_ids: sg_id = client.find_security_group( sg, ignore_missing=False).id tmp_secgroups.remove(sg_id) except ValueError: msg = _("Port does not contain security group %s") % sg raise exceptions.CommandError(msg) - attrs['security_groups'] = tmp_secgroups + attrs['security_group_ids'] = tmp_secgroups if parsed_args.allowed_address_pairs: try: for addr in _convert_address_pairs(parsed_args): diff --git a/openstackclient/tests/functional/network/v2/test_port.py b/openstackclient/tests/functional/network/v2/test_port.py index 818076d675..78c572730d 100644 --- a/openstackclient/tests/functional/network/v2/test_port.py +++ b/openstackclient/tests/functional/network/v2/test_port.py @@ -20,6 +20,7 @@ class PortTests(base.TestCase): """Functional tests for port. """ NAME = uuid.uuid4().hex NETWORK_NAME = uuid.uuid4().hex + SG_NAME = uuid.uuid4().hex @classmethod def setUpClass(cls): @@ -124,13 +125,25 @@ def test_port_set(self): self.assertEqual('xyzpdq', json_output.get('description')) self.assertEqual('DOWN', json_output.get('admin_state_up')) - raw_output = self.openstack('port set ' + '--enable ' + self.NAME) + raw_output = self.openstack( + 'port set ' + '--enable ' + self.NAME) self.assertOutput('', raw_output) json_output = json.loads(self.openstack( 'port show -f json ' + self.NAME )) + sg_id = json_output.get('security_group_ids') + self.assertEqual(self.NAME, json_output.get('name')) self.assertEqual('xyzpdq', json_output.get('description')) self.assertEqual('UP', json_output.get('admin_state_up')) self.assertIsNotNone(json_output.get('mac_address')) + + raw_output = self.openstack( + 'port unset --security-group ' + sg_id + ' ' + id1) + self.assertOutput('', raw_output) + + json_output = json.loads(self.openstack( + 'port show -f json ' + self.NAME + )) + self.assertEqual('', json_output.get('security_group_ids')) diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index 1125289e28..612dcab003 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -544,7 +544,7 @@ def create_one_port(attrs=None): 'name': 'port-name-' + uuid.uuid4().hex, 'network_id': 'network-id-' + uuid.uuid4().hex, 'port_security_enabled': True, - 'security_groups': [], + 'security_group_ids': [], 'status': 'ACTIVE', 'tenant_id': 'project-id-' + uuid.uuid4().hex, } @@ -564,7 +564,7 @@ def create_one_port(attrs=None): port.is_admin_state_up = port_attrs['admin_state_up'] port.is_port_security_enabled = port_attrs['port_security_enabled'] port.project_id = port_attrs['tenant_id'] - port.security_group_ids = port_attrs['security_groups'] + port.security_group_ids = port_attrs['security_group_ids'] return port diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index bfffc5c062..80eba3a875 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -57,7 +57,7 @@ def _get_common_cols_data(self, fake_port): 'network_id', 'port_security_enabled', 'project_id', - 'security_groups', + 'security_group_ids', 'status', ) @@ -82,7 +82,7 @@ def _get_common_cols_data(self, fake_port): fake_port.network_id, fake_port.port_security_enabled, fake_port.project_id, - utils.format_list(fake_port.security_groups), + utils.format_list(fake_port.security_group_ids), fake_port.status, ) @@ -251,7 +251,7 @@ def test_create_with_security_group(self): verifylist = [ ('network', self._port.network_id,), ('enable', True), - ('security_groups', [secgroup.id]), + ('security_group', [secgroup.id]), ('name', 'test-port'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -261,7 +261,7 @@ def test_create_with_security_group(self): self.network.create_port.assert_called_once_with(**{ 'admin_state_up': True, 'network_id': self._port.network_id, - 'security_groups': [secgroup.id], + 'security_group_ids': [secgroup.id], 'name': 'test-port', }) @@ -309,7 +309,7 @@ def test_create_with_security_groups(self): verifylist = [ ('network', self._port.network_id,), ('enable', True), - ('security_groups', [sg_1.id, sg_2.id]), + ('security_group', [sg_1.id, sg_2.id]), ('name', 'test-port'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -319,7 +319,7 @@ def test_create_with_security_groups(self): self.network.create_port.assert_called_once_with(**{ 'admin_state_up': True, 'network_id': self._port.network_id, - 'security_groups': [sg_1.id, sg_2.id], + 'security_group_ids': [sg_1.id, sg_2.id], 'name': 'test-port', }) @@ -346,7 +346,7 @@ def test_create_with_no_security_groups(self): self.network.create_port.assert_called_once_with(**{ 'admin_state_up': True, 'network_id': self._port.network_id, - 'security_groups': [], + 'security_group_ids': [], 'name': 'test-port', }) @@ -590,7 +590,7 @@ class TestListPort(TestPort): prt.mac_address, utils.format_list_of_dicts(prt.fixed_ips), prt.status, - utils.format_list(prt.security_groups), + utils.format_list(prt.security_group_ids), prt.device_owner, )) @@ -1111,7 +1111,7 @@ def test_set_security_group(self): self._port.name, ] verifylist = [ - ('security_groups', [sg.id]), + ('security_group', [sg.id]), ('port', self._port.name), ] @@ -1119,7 +1119,7 @@ def test_set_security_group(self): result = self.cmd.take_action(parsed_args) attrs = { - 'security_groups': [sg.id], + 'security_group_ids': [sg.id], } self.network.update_port.assert_called_once_with(self._port, **attrs) self.assertIsNone(result) @@ -1130,7 +1130,7 @@ def test_append_security_group(self): sg_3 = network_fakes.FakeSecurityGroup.create_one_security_group() self.network.find_security_group = mock.Mock(side_effect=[sg_2, sg_3]) _testport = network_fakes.FakePort.create_one_port( - {'security_groups': [sg_1.id]}) + {'security_group_ids': [sg_1.id]}) self.network.find_port = mock.Mock(return_value=_testport) arglist = [ '--security-group', sg_2.id, @@ -1138,13 +1138,13 @@ def test_append_security_group(self): _testport.name, ] verifylist = [ - ('security_groups', [sg_2.id, sg_3.id]), + ('security_group', [sg_2.id, sg_3.id]), ('port', _testport.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) attrs = { - 'security_groups': [sg_1.id, sg_2.id, sg_3.id], + 'security_group_ids': [sg_2.id, sg_3.id, sg_1.id], } self.network.update_port.assert_called_once_with(_testport, **attrs) self.assertIsNone(result) @@ -1163,7 +1163,7 @@ def test_set_no_security_groups(self): result = self.cmd.take_action(parsed_args) attrs = { - 'security_groups': [], + 'security_group_ids': [], } self.network.update_port.assert_called_once_with(self._port, **attrs) self.assertIsNone(result) @@ -1172,7 +1172,7 @@ def test_overwrite_security_group(self): sg1 = network_fakes.FakeSecurityGroup.create_one_security_group() sg2 = network_fakes.FakeSecurityGroup.create_one_security_group() _testport = network_fakes.FakePort.create_one_port( - {'security_groups': [sg1.id]}) + {'security_group_ids': [sg1.id]}) self.network.find_port = mock.Mock(return_value=_testport) self.network.find_security_group = mock.Mock(return_value=sg2) arglist = [ @@ -1181,13 +1181,13 @@ def test_overwrite_security_group(self): _testport.name, ] verifylist = [ - ('security_groups', [sg2.id]), + ('security_group', [sg2.id]), ('no_security_group', True) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) attrs = { - 'security_groups': [sg2.id], + 'security_group_ids': [sg2.id], } self.network.update_port.assert_called_once_with(_testport, **attrs) self.assertIsNone(result) @@ -1434,7 +1434,7 @@ def test_unset_security_group(self): _fake_sg1 = network_fakes.FakeSecurityGroup.create_one_security_group() _fake_sg2 = network_fakes.FakeSecurityGroup.create_one_security_group() _fake_port = network_fakes.FakePort.create_one_port( - {'security_groups': [_fake_sg1.id, _fake_sg2.id]}) + {'security_group_ids': [_fake_sg1.id, _fake_sg2.id]}) self.network.find_port = mock.Mock(return_value=_fake_port) self.network.find_security_group = mock.Mock(return_value=_fake_sg2) arglist = [ @@ -1442,14 +1442,14 @@ def test_unset_security_group(self): _fake_port.name, ] verifylist = [ - ('security_groups', [_fake_sg2.id]), + ('security_group_ids', [_fake_sg2.id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) attrs = { - 'security_groups': [_fake_sg1.id] + 'security_group_ids': [_fake_sg1.id] } self.network.update_port.assert_called_once_with( _fake_port, **attrs) @@ -1459,14 +1459,14 @@ def test_unset_port_security_group_not_existent(self): _fake_sg1 = network_fakes.FakeSecurityGroup.create_one_security_group() _fake_sg2 = network_fakes.FakeSecurityGroup.create_one_security_group() _fake_port = network_fakes.FakePort.create_one_port( - {'security_groups': [_fake_sg1.id]}) + {'security_group_ids': [_fake_sg1.id]}) self.network.find_security_group = mock.Mock(return_value=_fake_sg2) arglist = [ '--security-group', _fake_sg2.id, _fake_port.name, ] verifylist = [ - ('security_groups', [_fake_sg2.id]), + ('security_group_ids', [_fake_sg2.id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/releasenotes/notes/bug-1656788-2f5bda2205bc0329.yaml b/releasenotes/notes/bug-1656788-2f5bda2205bc0329.yaml new file mode 100644 index 0000000000..1a5fd2c103 --- /dev/null +++ b/releasenotes/notes/bug-1656788-2f5bda2205bc0329.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Fixed the ``port set`` and ``port unset`` command failures + (AttributeError) when ``--security-group`` option is included. + [Bug `1656788 `_] From 56c981e7fbd236600085e66af5c7fba9bf9b5251 Mon Sep 17 00:00:00 2001 From: David Rabel Date: Tue, 28 Feb 2017 21:29:34 +0100 Subject: [PATCH 1577/3095] Update doc/source/command-objects/image.rst Update doc/source/command-objects/image.rst to match output of 'openstack help image create' again. Forgot this in: https://review.openstack.org/#/c/437335/ Change-Id: Ied7ed88ea79da0b778cccf19d087b5ee06edcb71 --- doc/source/command-objects/image.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/source/command-objects/image.rst b/doc/source/command-objects/image.rst index 7a9ead5c61..c39d9a28e0 100644 --- a/doc/source/command-objects/image.rst +++ b/doc/source/command-objects/image.rst @@ -53,8 +53,7 @@ Create/upload an image [--min-ram ] [--location ] [--copy-from ] - [--file ] - [--volume ] + [--file | --volume ] [--force] [--checksum ] [--protected | --unprotected] From 1654f56f4e4f3757cd707bc6c908f493102d97ea Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 1 Mar 2017 04:16:15 +0000 Subject: [PATCH 1578/3095] Updated from global requirements Change-Id: Ief1a2faf6b9f80c5c7f3946d820f89587d44f8aa --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3995c9a0c8..1dfd2466c1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ keystoneauth1>=2.18.0 # Apache-2.0 openstacksdk>=0.9.13 # Apache-2.0 osc-lib>=1.2.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 -oslo.utils>=3.18.0 # Apache-2.0 +oslo.utils>=3.20.0 # Apache-2.0 python-glanceclient>=2.5.0 # Apache-2.0 python-keystoneclient>=3.8.0 # Apache-2.0 python-novaclient>=7.1.0 # Apache-2.0 From 7063ffb3cacadbaf2e47c4e6d7b2cf8d694c83a7 Mon Sep 17 00:00:00 2001 From: Rikimaru Honjo Date: Thu, 23 Feb 2017 16:59:26 +0900 Subject: [PATCH 1579/3095] Add a validation about options for server migrate command The behavior of server migrate command are different depending on whether user specify --live option or not. server migrate command will call live migration API if user specify --live option. Ohterwise server migrate command will call migration(cold migration) API. Now then, "--block-migraiton" option and "--disk-overcommit" option only affect live-migration. But, openstackclient doesn't warn user if user specify these options without "--live". But, user can't recognize that specifying options are ignored. This patch adds a validation that checks whether or not user specify these options without "--live". Change-Id: Ifa278abb23ecdba4b13f3742998359ac74eb7ad4 Closes-bug: #1662755 --- openstackclient/compute/v2/server.py | 4 + .../tests/unit/compute/v2/test_server.py | 203 ++++++++++++++++++ 2 files changed, 207 insertions(+) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index d33c631a17..1f91e6d940 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1104,6 +1104,10 @@ def take_action(self, parsed_args): disk_over_commit=parsed_args.disk_overcommit, ) else: + if parsed_args.block_migration or parsed_args.disk_overcommit: + raise exceptions.CommandError("--live must be specified if " + "--block-migration or " + "--disk-overcommit is specified") server.migrate() if parsed_args.wait: diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 249902bca4..6bdb51a91e 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -1185,6 +1185,209 @@ def test_server_lock_multi_servers(self): self.run_method_with_servers('lock', 3) +class TestServerMigrate(TestServer): + + def setUp(self): + super(TestServerMigrate, self).setUp() + + methods = { + 'migrate': None, + 'live_migrate': None, + } + self.server = compute_fakes.FakeServer.create_one_server( + methods=methods) + + # This is the return value for utils.find_resource() + self.servers_mock.get.return_value = self.server + + self.servers_mock.migrate.return_value = None + self.servers_mock.live_migrate.return_value = None + + # Get the command object to test + self.cmd = server.MigrateServer(self.app, None) + + def test_server_migrate_no_options(self): + arglist = [ + self.server.id, + ] + verifylist = [ + ('live', None), + ('block_migration', False), + ('disk_overcommit', False), + ('wait', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.server.migrate.assert_called_with() + self.assertNotCalled(self.servers_mock.live_migrate) + self.assertIsNone(result) + + def test_server_migrate_with_block_migration(self): + arglist = [ + '--block-migration', self.server.id, + ] + verifylist = [ + ('live', None), + ('block_migration', True), + ('disk_overcommit', False), + ('wait', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.assertNotCalled(self.servers_mock.live_migrate) + self.assertNotCalled(self.servers_mock.migrate) + + def test_server_migrate_with_disk_overcommit(self): + arglist = [ + '--disk-overcommit', self.server.id, + ] + verifylist = [ + ('live', None), + ('block_migration', False), + ('disk_overcommit', True), + ('wait', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.assertNotCalled(self.servers_mock.live_migrate) + self.assertNotCalled(self.servers_mock.migrate) + + def test_server_live_migrate(self): + arglist = [ + '--live', 'fakehost', self.server.id, + ] + verifylist = [ + ('live', 'fakehost'), + ('block_migration', False), + ('disk_overcommit', False), + ('wait', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.server.live_migrate.assert_called_with(block_migration=False, + disk_over_commit=False, + host='fakehost') + self.assertNotCalled(self.servers_mock.migrate) + self.assertIsNone(result) + + def test_server_block_live_migrate(self): + arglist = [ + '--live', 'fakehost', '--block-migration', self.server.id, + ] + verifylist = [ + ('live', 'fakehost'), + ('block_migration', True), + ('disk_overcommit', False), + ('wait', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.server.live_migrate.assert_called_with(block_migration=True, + disk_over_commit=False, + host='fakehost') + self.assertNotCalled(self.servers_mock.migrate) + self.assertIsNone(result) + + def test_server_live_migrate_with_disk_overcommit(self): + arglist = [ + '--live', 'fakehost', '--disk-overcommit', self.server.id, + ] + verifylist = [ + ('live', 'fakehost'), + ('block_migration', False), + ('disk_overcommit', True), + ('wait', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.server.live_migrate.assert_called_with(block_migration=False, + disk_over_commit=True, + host='fakehost') + self.assertNotCalled(self.servers_mock.migrate) + self.assertIsNone(result) + + def test_server_live_migrate_with_false_value_options(self): + arglist = [ + '--live', 'fakehost', '--no-disk-overcommit', + '--shared-migration', self.server.id, + ] + verifylist = [ + ('live', 'fakehost'), + ('block_migration', False), + ('disk_overcommit', False), + ('wait', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.server.live_migrate.assert_called_with(block_migration=False, + disk_over_commit=False, + host='fakehost') + self.assertNotCalled(self.servers_mock.migrate) + self.assertIsNone(result) + + @mock.patch.object(common_utils, 'wait_for_status', return_value=True) + def test_server_migrate_with_wait(self, mock_wait_for_status): + arglist = [ + '--wait', self.server.id, + ] + verifylist = [ + ('live', None), + ('block_migration', False), + ('disk_overcommit', False), + ('wait', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.server.migrate.assert_called_with() + self.assertNotCalled(self.servers_mock.live_migrate) + self.assertIsNone(result) + + @mock.patch.object(common_utils, 'wait_for_status', return_value=False) + def test_server_migrate_with_wait_fails(self, mock_wait_for_status): + arglist = [ + '--wait', self.server.id, + ] + verifylist = [ + ('live', None), + ('block_migration', False), + ('disk_overcommit', False), + ('wait', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises(SystemExit, self.cmd.take_action, parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.server.migrate.assert_called_with() + self.assertNotCalled(self.servers_mock.live_migrate) + + class TestServerPause(TestServer): def setUp(self): From a98d369a39a4818f68333065f669d827e8216382 Mon Sep 17 00:00:00 2001 From: Rodrigo Duarte Sousa Date: Mon, 20 Feb 2017 09:47:18 -0300 Subject: [PATCH 1580/3095] Use *_as_ids instead *_as_list The parents_as_list and subtree_as_list query parameters limit the result to only parents and subtree where the user making the call has role assignments in. Since OSC only displays the IDs, the call would be the same as the similar *_as_ids queries, the difference is that the later doesn't enforce the role assignments (making it more useful). Output example by using this patch: $ openstack project show --children root +-------------+------------------------------+ | Field | Value | +-------------+------------------------------+ | description | | | domain_id | default | | enabled | True | | id | 123 | | is_domain | False | | name | root | | parent_id | default | | subtree | {u'456': None, u'789': None} | +-------------+------------------------------+ Change-Id: Ib7b37ae8f55190a7efcc375d5be4a2823d02d1a4 --- openstackclient/identity/v3/project.py | 11 ++-------- .../tests/unit/identity/v3/test_project.py | 20 +++++++++---------- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index 43eca2b525..da6aa7345d 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -348,15 +348,8 @@ def take_action(self, parsed_args): # identity manager.get() with kwargs directly. project = identity_client.projects.get( project.id, - parents_as_list=parsed_args.parents, - subtree_as_list=parsed_args.children) - - if project._info.get('parents'): - project._info['parents'] = [str(p['project']['id']) - for p in project._info['parents']] - if project._info.get('subtree'): - project._info['subtree'] = [str(p['project']['id']) - for p in project._info['subtree']] + parents_as_ids=parsed_args.parents, + subtree_as_ids=parsed_args.children) project._info.pop('links') return zip(*sorted(six.iteritems(project._info))) diff --git a/openstackclient/tests/unit/identity/v3/test_project.py b/openstackclient/tests/unit/identity/v3/test_project.py index b99eaf8506..3a035922ce 100644 --- a/openstackclient/tests/unit/identity/v3/test_project.py +++ b/openstackclient/tests/unit/identity/v3/test_project.py @@ -845,8 +845,8 @@ def test_project_show_parents(self): self.projects_mock.get.assert_has_calls([call(self.project.id), call(self.project.id, - parents_as_list=True, - subtree_as_list=False, + parents_as_ids=True, + subtree_as_ids=False, )]) collist = ( @@ -868,7 +868,7 @@ def test_project_show_parents(self): self.project.is_domain, self.project.name, self.project.parent_id, - [self.project.parent_id], + [{'project': {'id': self.project.parent_id}}] ) self.assertEqual(data, datalist) @@ -904,8 +904,8 @@ def test_project_show_subtree(self): columns, data = self.cmd.take_action(parsed_args) self.projects_mock.get.assert_has_calls([call(self.project.id), call(self.project.id, - parents_as_list=False, - subtree_as_list=True, + parents_as_ids=False, + subtree_as_ids=True, )]) collist = ( @@ -927,7 +927,7 @@ def test_project_show_subtree(self): self.project.is_domain, self.project.name, self.project.parent_id, - ['children-id'], + [{'project': {'id': 'children-id'}}] ) self.assertEqual(data, datalist) @@ -965,8 +965,8 @@ def test_project_show_parents_and_children(self): columns, data = self.cmd.take_action(parsed_args) self.projects_mock.get.assert_has_calls([call(self.project.id), call(self.project.id, - parents_as_list=True, - subtree_as_list=True, + parents_as_ids=True, + subtree_as_ids=True, )]) collist = ( @@ -989,7 +989,7 @@ def test_project_show_parents_and_children(self): self.project.is_domain, self.project.name, self.project.parent_id, - [self.project.parent_id], - ['children-id'], + [{'project': {'id': self.project.parent_id}}], + [{'project': {'id': 'children-id'}}] ) self.assertEqual(data, datalist) From a8ec2c94e7ee06703fee5010d6f60e3708edde8c Mon Sep 17 00:00:00 2001 From: Ankur Gupta Date: Wed, 1 Mar 2017 21:08:53 -0600 Subject: [PATCH 1581/3095] Nit: Trivial doc formatting fix for network flavor Change-Id: I6e67c0859642593bc7fdd479cac81e56d87faf09 --- doc/source/command-objects/network-flavor.rst | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/doc/source/command-objects/network-flavor.rst b/doc/source/command-objects/network-flavor.rst index 1324499e7d..d5c011f683 100644 --- a/doc/source/command-objects/network-flavor.rst +++ b/doc/source/command-objects/network-flavor.rst @@ -8,7 +8,6 @@ service flavors. Network v2 -.. _network_flavor_create: network flavor create --------------------- @@ -55,7 +54,8 @@ Create network flavor Name for the flavor -.. _network_flavor_delete: +.. _network_flavor_create: + network flavor delete --------------------- @@ -71,6 +71,8 @@ Delete network flavor(s) Flavor(s) to delete (name or ID) +.. _network_flavor_delete: + network flavor list ------------------- @@ -81,7 +83,8 @@ List network flavors openstack network flavor list -.. _network_flavor_set: +.. _network_flavor_list: + network flavor set ------------------ @@ -116,7 +119,8 @@ Set network flavor properties Flavor to update (name or ID) -.. _network_flavor_show: +.. _network_flavor_set: + network flavor show ------------------- @@ -131,3 +135,5 @@ Show network flavor .. describe:: Flavor to display (name or ID) + +.. _network_flavor_show: From 6abd38cd46ade11a7509448fc6582559c2968283 Mon Sep 17 00:00:00 2001 From: Anindita Das Date: Thu, 2 Mar 2017 17:10:53 +0000 Subject: [PATCH 1582/3095] Jsonify network flavor functional tests Some functional tests try to parse the CLI table output format, that cause much work on parse string by using regular expression. Using json format in functional tests is better and easier way, this patch reworks for network flavor related tests. Change-Id: I2bc5675409945c9ae79ac65df2d5d10781fe595a --- .../network/v2/test_network_flavor.py | 163 ++++++++++-------- 1 file changed, 93 insertions(+), 70 deletions(-) diff --git a/openstackclient/tests/functional/network/v2/test_network_flavor.py b/openstackclient/tests/functional/network/v2/test_network_flavor.py index e37a7bc71f..b2fc2eaea3 100644 --- a/openstackclient/tests/functional/network/v2/test_network_flavor.py +++ b/openstackclient/tests/functional/network/v2/test_network_flavor.py @@ -10,7 +10,8 @@ # License for the specific language governing permissions and limitations # under the License. -import re + +import json import uuid from openstackclient.tests.functional import base @@ -19,135 +20,157 @@ class NetworkFlavorTests(base.TestCase): """Functional tests for network flavor.""" - @classmethod - def setUpClass(cls): - # Set up some regex for matching below - cls.re_name = re.compile("name\s+\|\s+([^|]+?)\s+\|") - cls.re_enabled = re.compile("enabled\s+\|\s+(\S+)") - cls.re_description = re.compile("description\s+\|\s+([^|]+?)\s+\|") - cls.SERVICE_TYPE = 'L3_ROUTER_NAT' - def test_network_flavor_delete(self): """Test create, delete multiple""" name1 = uuid.uuid4().hex - raw_output = self.openstack( - 'network flavor create --description testdescription --enable' - ' --service-type ' + self.SERVICE_TYPE + ' ' + name1, - ) + cmd_output = json.loads(self.openstack( + 'network flavor create -f json --description testdescription ' + '--enable --service-type L3_ROUTER_NAT ' + name1, + )) self.assertEqual( name1, - re.search(self.re_name, raw_output).group(1)) + cmd_output['name'], + ) self.assertEqual( - 'True', - re.search(self.re_enabled, raw_output).group(1)) + True, + cmd_output['enabled'], + ) self.assertEqual( 'testdescription', - re.search(self.re_description, raw_output).group(1)) + cmd_output['description'], + ) name2 = uuid.uuid4().hex - raw_output = self.openstack( - 'network flavor create --description testdescription1 --disable' - ' --service-type ' + self.SERVICE_TYPE + ' ' + name2, - ) + cmd_output = json.loads(self.openstack( + 'network flavor create -f json --description testdescription1 ' + '--disable --service-type L3_ROUTER_NAT ' + name2, + )) self.assertEqual( name2, - re.search(self.re_name, raw_output).group(1)) + cmd_output['name'], + ) self.assertEqual( - 'False', - re.search(self.re_enabled, raw_output).group(1)) + False, + cmd_output['enabled'], + ) self.assertEqual( 'testdescription1', - re.search(self.re_description, raw_output).group(1)) - + cmd_output['description'], + ) raw_output = self.openstack( 'network flavor delete ' + name1 + " " + name2) self.assertOutput('', raw_output) def test_network_flavor_list(self): + """Test create defaults, list filters, delete""" name1 = uuid.uuid4().hex - raw_output = self.openstack( - 'network flavor create --description testdescription --enable' - ' --service-type ' + self.SERVICE_TYPE + ' ' + name1, - ) + cmd_output = json.loads(self.openstack( + 'network flavor create -f json --description testdescription ' + '--enable --service-type L3_ROUTER_NAT ' + name1, + )) self.addCleanup(self.openstack, "network flavor delete " + name1) self.assertEqual( name1, - re.search(self.re_name, raw_output).group(1)) + cmd_output['name'], + ) self.assertEqual( - 'True', - re.search(self.re_enabled, raw_output).group(1)) + True, + cmd_output['enabled'], + ) self.assertEqual( 'testdescription', - re.search(self.re_description, raw_output).group(1)) + cmd_output['description'], + ) name2 = uuid.uuid4().hex - raw_output = self.openstack( - 'network flavor create --description testdescription --disable' - ' --service-type ' + self.SERVICE_TYPE + ' ' + name2, - ) + cmd_output = json.loads(self.openstack( + 'network flavor create -f json --description testdescription1 ' + '--disable --service-type L3_ROUTER_NAT ' + name2, + )) self.assertEqual( name2, - re.search(self.re_name, raw_output).group(1)) + cmd_output['name'], + ) self.assertEqual( - 'False', - re.search(self.re_enabled, raw_output).group(1)) + False, + cmd_output['enabled'], + ) self.assertEqual( - 'testdescription', - re.search(self.re_description, raw_output).group(1)) + 'testdescription1', + cmd_output['description'], + ) self.addCleanup(self.openstack, "network flavor delete " + name2) # Test list - raw_output = self.openstack('network flavor list') - self.assertIsNotNone(raw_output) - self.assertIsNotNone(re.search(name1, raw_output)) - self.assertIsNotNone(re.search(name2, raw_output)) + cmd_output = json.loads(self.openstack( + 'network flavor list -f json ',)) + self.assertIsNotNone(cmd_output) + + name_list = [item.get('Name') for item in cmd_output] + self.assertIn(name1, name_list) + self.assertIn(name2, name_list) def test_network_flavor_set(self): + """Tests create options, set, show, delete""" name = uuid.uuid4().hex newname = name + "_" - raw_output = self.openstack( - 'network flavor create --description testdescription --enable' - ' --service-type ' + self.SERVICE_TYPE + ' ' + name, - ) + cmd_output = json.loads(self.openstack( + 'network flavor create -f json --description testdescription ' + '--disable --service-type L3_ROUTER_NAT ' + name, + )) self.addCleanup(self.openstack, "network flavor delete " + newname) self.assertEqual( name, - re.search(self.re_name, raw_output).group(1)) + cmd_output['name'], + ) self.assertEqual( - 'True', - re.search(self.re_enabled, raw_output).group(1)) + False, + cmd_output['enabled'], + ) self.assertEqual( 'testdescription', - re.search(self.re_description, raw_output).group(1)) + cmd_output['description'], + ) - self.openstack( + raw_output = self.openstack( 'network flavor set --name ' + newname + ' --disable ' + name ) - raw_output = self.openstack('network flavor show ' + newname) + self.assertOutput('', raw_output) + + cmd_output = json.loads(self.openstack( + 'network flavor show -f json ' + newname,)) self.assertEqual( newname, - re.search(self.re_name, raw_output).group(1)) + cmd_output['name'], + ) self.assertEqual( - 'False', - re.search(self.re_enabled, raw_output).group(1)) + False, + cmd_output['enabled'], + ) self.assertEqual( 'testdescription', - re.search(self.re_description, raw_output).group(1)) + cmd_output['description'], + ) def test_network_flavor_show(self): + """Test show network flavor""" name = uuid.uuid4().hex - self.openstack( - 'network flavor create --description testdescription --enable' - ' --service-type ' + self.SERVICE_TYPE + ' ' + name, - ) + cmd_output = json.loads(self.openstack( + 'network flavor create -f json --description testdescription ' + '--disable --service-type L3_ROUTER_NAT ' + name, + )) self.addCleanup(self.openstack, "network flavor delete " + name) - raw_output = self.openstack('network flavor show ' + name) + cmd_output = json.loads(self.openstack( + 'network flavor show -f json ' + name,)) self.assertEqual( name, - re.search(self.re_name, raw_output).group(1)) + cmd_output['name'], + ) self.assertEqual( - 'True', - re.search(self.re_enabled, raw_output).group(1)) + False, + cmd_output['enabled'], + ) self.assertEqual( 'testdescription', - re.search(self.re_description, raw_output).group(1)) + cmd_output['description'], + ) From f97a33b971eb5cff06b7f84202f2de2449e5028e Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Fri, 3 Mar 2017 22:59:10 +0000 Subject: [PATCH 1583/3095] Updated from global requirements Change-Id: I28815dc190dba45af060b26eceb660f40bb67772 --- requirements.txt | 2 +- setup.py | 2 +- test-requirements.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 1dfd2466c1..8688e2ab9b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ # The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -pbr>=1.8 # Apache-2.0 +pbr>=2.0.0 # Apache-2.0 six>=1.9.0 # MIT Babel>=2.3.4 # BSD diff --git a/setup.py b/setup.py index 782bb21f06..566d84432e 100644 --- a/setup.py +++ b/setup.py @@ -25,5 +25,5 @@ pass setuptools.setup( - setup_requires=['pbr>=1.8'], + setup_requires=['pbr>=2.0.0'], pbr=True) diff --git a/test-requirements.txt b/test-requirements.txt index ef662d47a7..ca335d481c 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -12,7 +12,7 @@ reno>=1.8.0 # Apache-2.0 requests!=2.12.2,!=2.13.0,>=2.10.0 # Apache-2.0 requests-mock>=1.1 # Apache-2.0 sphinx>=1.5.1 # BSD -stevedore>=1.17.1 # Apache-2.0 +stevedore>=1.20.0 # Apache-2.0 os-client-config>=1.22.0 # Apache-2.0 os-testr>=0.8.0 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD From 33b092fb9a7432dcb2302efc0ac6177fd3d092ad Mon Sep 17 00:00:00 2001 From: Reedip Date: Fri, 3 Mar 2017 22:09:29 -0500 Subject: [PATCH 1584/3095] Trivial Fix Add a space in the quota error message Change-Id: I159708b42e86f6b02f8733103a687561d550f650 --- openstackclient/common/quota.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index afc6195f93..82fbf2bb07 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -165,7 +165,7 @@ def take_action(self, parsed_args): **volume_kwargs) if network_kwargs: sys.stderr.write("Network quotas are ignored since quota class" - "is not supported.") + " is not supported.") else: project = utils.find_resource( identity_client.projects, From dee22d8faa0c8a0da1d6ff62c0997c2cc770b759 Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Mon, 27 Feb 2017 14:35:05 +0800 Subject: [PATCH 1585/3095] Add "--private-key" option for "keypair create" Aim to specify the private key file to save when keypair is created. That is a convenient way to save private key in OSC interactive mode, avoid to copy CLI output, then paste it into file. Change-Id: I119d2f2a3323d17ecbe3de4e27f35e1ceef6e0a5 Closes-Bug: #1549410 --- doc/source/command-objects/keypair.rst | 7 ++++- openstackclient/compute/v2/keypair.py | 31 +++++++++++++++++-- .../functional/compute/v2/test_keypair.py | 21 +++++++++++++ .../tests/unit/compute/v2/test_keypair.py | 31 +++++++++++++++++++ .../notes/bug-1549410-8df3a4b12fe13ffa.yaml | 8 +++++ 5 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/bug-1549410-8df3a4b12fe13ffa.yaml diff --git a/doc/source/command-objects/keypair.rst b/doc/source/command-objects/keypair.rst index 47acf7f2c1..a539f0a208 100644 --- a/doc/source/command-objects/keypair.rst +++ b/doc/source/command-objects/keypair.rst @@ -18,13 +18,18 @@ Create new public or private key for server ssh access .. code:: bash openstack keypair create - [--public-key ] + [--public-key | --private-key ] .. option:: --public-key Filename for public key to add. If not used, creates a private key. +.. option:: --private-key + + Filename for private key to save. If not used, print private key in + console. + .. describe:: New public or private key name diff --git a/openstackclient/compute/v2/keypair.py b/openstackclient/compute/v2/keypair.py index 2a8524d62b..851cced0e9 100644 --- a/openstackclient/compute/v2/keypair.py +++ b/openstackclient/compute/v2/keypair.py @@ -41,12 +41,19 @@ def get_parser(self, prog_name): metavar='', help=_("New public or private key name") ) - parser.add_argument( + key_group = parser.add_mutually_exclusive_group() + key_group.add_argument( '--public-key', metavar='', help=_("Filename for public key to add. If not used, " "creates a private key.") ) + key_group.add_argument( + '--private-key', + metavar='', + help=_("Filename for private key to save. If not used, " + "print private key in console.") + ) return parser def take_action(self, parsed_args): @@ -69,13 +76,31 @@ def take_action(self, parsed_args): public_key=public_key, ) + private_key = parsed_args.private_key + # Save private key into specified file + if private_key: + try: + with io.open( + os.path.expanduser(parsed_args.private_key), 'w+' + ) as p: + p.write(keypair.private_key) + except IOError as e: + msg = _("Key file %(private_key)s can not be saved: " + "%(exception)s") + raise exceptions.CommandError( + msg % {"private_key": parsed_args.private_key, + "exception": e} + ) # NOTE(dtroyer): how do we want to handle the display of the private # key when it needs to be communicated back to the user # For now, duplicate nova keypair-add command output info = {} - if public_key: + if public_key or private_key: info.update(keypair._info) - del info['public_key'] + if 'public_key' in info: + del info['public_key'] + if 'private_key' in info: + del info['private_key'] return zip(*sorted(six.iteritems(info))) else: sys.stdout.write(keypair.private_key) diff --git a/openstackclient/tests/functional/compute/v2/test_keypair.py b/openstackclient/tests/functional/compute/v2/test_keypair.py index 01078c6136..1e1a03d67d 100644 --- a/openstackclient/tests/functional/compute/v2/test_keypair.py +++ b/openstackclient/tests/functional/compute/v2/test_keypair.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import json import tempfile from openstackclient.tests.functional import base @@ -100,6 +101,26 @@ def test_keypair_create_public_key(self): ) self.assertIn('tmpkey', raw_output) + def test_keypair_create_private_key(self): + """Test for create keypair with --private-key option. + + Test steps: + 1) Create keypair with private key file + 2) Delete keypair + """ + with tempfile.NamedTemporaryFile() as f: + cmd_output = json.loads(self.openstack( + 'keypair create -f json --private-key %s tmpkey' % f.name, + )) + self.addCleanup(self.openstack, 'keypair delete tmpkey') + self.assertEqual('tmpkey', cmd_output.get('name')) + self.assertIsNotNone(cmd_output.get('user_id')) + self.assertIsNotNone(cmd_output.get('fingerprint')) + pk_content = f.read() + self.assertInOutput('-----BEGIN RSA PRIVATE KEY-----', pk_content) + self.assertRegex(pk_content, "[0-9A-Za-z+/]+[=]{0,3}\n") + self.assertInOutput('-----END RSA PRIVATE KEY-----', pk_content) + def test_keypair_create(self): """Test keypair create command. diff --git a/openstackclient/tests/unit/compute/v2/test_keypair.py b/openstackclient/tests/unit/compute/v2/test_keypair.py index efc5463cc6..d6f5ecf457 100644 --- a/openstackclient/tests/unit/compute/v2/test_keypair.py +++ b/openstackclient/tests/unit/compute/v2/test_keypair.py @@ -15,6 +15,7 @@ import mock from mock import call +import uuid from osc_lib import exceptions from osc_lib import utils @@ -115,6 +116,36 @@ def test_keypair_create_public_key(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_keypair_create_private_key(self): + tmp_pk_file = '/tmp/kp-file-' + uuid.uuid4().hex + arglist = [ + '--private-key', tmp_pk_file, + self.keypair.name, + ] + verifylist = [ + ('private_key', tmp_pk_file), + ('name', self.keypair.name) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + with mock.patch('io.open') as mock_open: + mock_open.return_value = mock.MagicMock() + m_file = mock_open.return_value.__enter__.return_value + + columns, data = self.cmd.take_action(parsed_args) + + self.keypairs_mock.create.assert_called_with( + self.keypair.name, + public_key=None + ) + + mock_open.assert_called_once_with(tmp_pk_file, 'w+') + m_file.write.assert_called_once_with(self.keypair.private_key) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + class TestKeypairDelete(TestKeypair): diff --git a/releasenotes/notes/bug-1549410-8df3a4b12fe13ffa.yaml b/releasenotes/notes/bug-1549410-8df3a4b12fe13ffa.yaml new file mode 100644 index 0000000000..ac37f81636 --- /dev/null +++ b/releasenotes/notes/bug-1549410-8df3a4b12fe13ffa.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Add ``--private-key`` option for ``keypair create`` command to specify the + private key file to save when a keypair is created, removing the need to + copy the output and paste it into a new file. This is a convenient way + to save private key in OSC interactive mode. + [Bug `1549410 `_] From d07704dd2560149036b099e9087ecd5bacdf4f78 Mon Sep 17 00:00:00 2001 From: liusheng Date: Mon, 6 Mar 2017 19:20:45 +0800 Subject: [PATCH 1586/3095] Normalize the gnocchiclient docs entry For now, gnocchiclient has been added in the global-requirements, we should add gnocchiclient in test-requirements.txt and normalize the docs entry. Change-Id: Ide5d8262e7f7c5b1ca304fa72893ccfbf16d8ec1 --- doc/source/plugin-commands.rst | 8 ++++---- doc/source/plugins.rst | 2 +- test-requirements.txt | 1 + 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/doc/source/plugin-commands.rst b/doc/source/plugin-commands.rst index 844c48a72a..182f5392c6 100644 --- a/doc/source/plugin-commands.rst +++ b/doc/source/plugin-commands.rst @@ -33,10 +33,10 @@ designate .. list-plugins:: openstack.dns.v1 :detailed: -.. gnocchi -.. # gnocchiclient is not in global-requirements -.. # list-plugins:: openstack.metric.v1 -.. # :detailed: +gnocchi +------- +.. list-plugins:: openstack.metric.v1 + :detailed: heat ---- diff --git a/doc/source/plugins.rst b/doc/source/plugins.rst index 054e552fe7..7dda52a034 100644 --- a/doc/source/plugins.rst +++ b/doc/source/plugins.rst @@ -22,7 +22,7 @@ OpenStackClient. The following is a list of projects that are an OpenStackClient plugin. - aodhclient -- gnocchiclient\*\* +- gnocchiclient - python-barbicanclient - python-congressclient - python-designateclient diff --git a/test-requirements.txt b/test-requirements.txt index ca335d481c..24311847b3 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -24,6 +24,7 @@ wrapt>=1.7.0 # BSD License # Install these to generate sphinx autodocs aodhclient>=0.7.0 # Apache-2.0 +gnocchiclient>=2.7.0 # Apache-2.0 python-barbicanclient>=4.0.0 # Apache-2.0 python-congressclient<2000,>=1.3.0 # Apache-2.0 python-designateclient>=1.5.0 # Apache-2.0 From b421eed8681ca44e5087c41446ad96aea07faeb5 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Tue, 28 Feb 2017 15:51:20 -0500 Subject: [PATCH 1587/3095] add neutron CLI to decoder Change-Id: I4717ffa2aa1c7bab1199728455ac7c13834d746f --- doc/source/data/neutron.csv | 244 ++++++++++++++++++++++++++++++++++++ doc/source/decoder.rst | 7 ++ 2 files changed, 251 insertions(+) create mode 100644 doc/source/data/neutron.csv diff --git a/doc/source/data/neutron.csv b/doc/source/data/neutron.csv new file mode 100644 index 0000000000..c2f0b415d1 --- /dev/null +++ b/doc/source/data/neutron.csv @@ -0,0 +1,244 @@ +address-scope-create,address scope create,Create an address scope for a given tenant. +address-scope-delete,address scope delete,Delete an address scope. +address-scope-list,address scope list,List address scopes that belong to a given tenant. +address-scope-show,address scope show,Show information about an address scope. +address-scope-update,address scope set,Update an address scope. +agent-delete,network agent delete,Delete a given agent. +agent-list,network agent list,List agents. +agent-show,network agent show,Show information of a given agent. +agent-update,network agent set,Updates the admin status and description for a specified agent. +auto-allocated-topology-delete,network auto allocated topology delete,Delete the auto-allocated topology of a given tenant. +auto-allocated-topology-show,network auto allocated topology create,Show the auto-allocated topology of a given tenant. +availability-zone-list,availability zone list,List availability zones. +bash-completion,complete,Prints all of the commands and options for bash-completion. +bgp-dragent-list-hosting-speaker,,List Dynamic Routing agents hosting a BGP speaker. +bgp-dragent-speaker-add,,Add a BGP speaker to a Dynamic Routing agent. +bgp-dragent-speaker-remove,,Removes a BGP speaker from a Dynamic Routing agent. +bgp-peer-create,,Create a BGP Peer. +bgp-peer-delete,,Delete a BGP peer. +bgp-peer-list,,List BGP peers. +bgp-peer-show,,Show information of a given BGP peer. +bgp-peer-update,,Update BGP Peer's information. +bgp-speaker-advertiseroute-list,,List routes advertised by a given BGP speaker. +bgp-speaker-create,,Create a BGP Speaker. +bgp-speaker-delete,,Delete a BGP speaker. +bgp-speaker-list,,List BGP speakers. +bgp-speaker-list-on-dragent,,List BGP speakers hosted by a Dynamic Routing agent. +bgp-speaker-network-add,,Add a network to the BGP speaker. +bgp-speaker-network-remove,,Remove a network from the BGP speaker. +bgp-speaker-peer-add,,Add a peer to the BGP speaker. +bgp-speaker-peer-remove,,Remove a peer from the BGP speaker. +bgp-speaker-show,,Show information of a given BGP speaker. +bgp-speaker-update,,Update BGP Speaker's information. +dhcp-agent-list-hosting-net,network agent list --network,List DHCP agents hosting a network. +dhcp-agent-network-add,network agent add network,Add a network to a DHCP agent. +dhcp-agent-network-remove,network agent remove network,Remove a network from a DHCP agent. +ext-list,extension list,List all extensions. +ext-show,extension show,Show information of a given resource. +flavor-associate,network flavor associate,Associate a Neutron service flavor with a flavor profile. +flavor-create,network flavor create,Create a Neutron service flavor. +flavor-delete,network flavor delete,Delete a given Neutron service flavor. +flavor-disassociate,network flavor disassociate,Disassociate a Neutron service flavor from a flavor profile. +flavor-list,network flavor list,List Neutron service flavors. +flavor-profile-create,network flavor profile create,Create a Neutron service flavor profile. +flavor-profile-delete,network flavor profile delete,Delete a given Neutron service flavor profile. +flavor-profile-list,network flavor profile list,List Neutron service flavor profiles. +flavor-profile-show,network flavor profile show,Show information about a given Neutron service flavor profile. +flavor-profile-update,network flavor profile set,Update a given Neutron service flavor profile. +flavor-show,network flavor show,Show information about a given Neutron service flavor. +flavor-update,network flavor set,Update a Neutron service flavor. +floatingip-associate,floating ip set port --fixed-ip,Create a mapping between a floating IP and a fixed IP. +floatingip-create,floating ip create,Create a floating IP for a given tenant. +floatingip-delete,floating ip delete,Delete a given floating IP. +floatingip-disassociate,floating ip unset port,Remove a mapping from a floating IP to a fixed IP. +floatingip-list,floating ip list,List floating IPs that belong to a given tenant. +floatingip-show,floating ip show,Show information of a given floating IP. +help,help,print detailed help for another command +ipsec-site-connection-create,,Create an IPsec site connection. +ipsec-site-connection-delete,,Delete a given IPsec site connection. +ipsec-site-connection-list,,List IPsec site connections that belong to a given tenant. +ipsec-site-connection-show,,Show information of a given IPsec site connection. +ipsec-site-connection-update,,Update a given IPsec site connection. +l3-agent-list-hosting-router,network agent list --routers,List L3 agents hosting a router. +l3-agent-router-add,network agent add router --agent-type l3,Add a router to a L3 agent. +l3-agent-router-remove,network agent remove router --agent-type l3,Remove a router from a L3 agent. +lb-agent-hosting-pool,,Get loadbalancer agent hosting a pool. +lb-healthmonitor-associate,,Create a mapping between a health monitor and a pool. +lb-healthmonitor-create,,Create a health monitor. +lb-healthmonitor-delete,,Delete a given health monitor. +lb-healthmonitor-disassociate,,Remove a mapping from a health monitor to a pool. +lb-healthmonitor-list,,List health monitors that belong to a given tenant. +lb-healthmonitor-show,,Show information of a given health monitor. +lb-healthmonitor-update,,Update a given health monitor. +lb-member-create,,Create a member. +lb-member-delete,,Delete a given member. +lb-member-list,,List members that belong to a given tenant. +lb-member-show,,Show information of a given member. +lb-member-update,,Update a given member. +lb-pool-create,,Create a pool. +lb-pool-delete,,Delete a given pool. +lb-pool-list,,List pools that belong to a given tenant. +lb-pool-list-on-agent,,List the pools on a loadbalancer agent. +lb-pool-show,,Show information of a given pool. +lb-pool-stats,,Retrieve stats for a given pool. +lb-pool-update,,Update a given pool. +lb-vip-create,,Create a vip. +lb-vip-delete,,Delete a given vip. +lb-vip-list,,List vips that belong to a given tenant. +lb-vip-show,,Show information of a given vip. +lb-vip-update,,Update a given vip. +lbaas-agent-hosting-loadbalancer,,Get lbaas v2 agent hosting a loadbalancer. +lbaas-healthmonitor-create,,LBaaS v2 Create a healthmonitor. +lbaas-healthmonitor-delete,,LBaaS v2 Delete a given healthmonitor. +lbaas-healthmonitor-list,,LBaaS v2 List healthmonitors that belong to a given tenant. +lbaas-healthmonitor-show,,LBaaS v2 Show information of a given healthmonitor. +lbaas-healthmonitor-update,,LBaaS v2 Update a given healthmonitor. +lbaas-l7policy-create,,LBaaS v2 Create L7 policy. +lbaas-l7policy-delete,,LBaaS v2 Delete a given L7 policy. +lbaas-l7policy-list,,LBaaS v2 List L7 policies that belong to a given listener. +lbaas-l7policy-show,,LBaaS v2 Show information of a given L7 policy. +lbaas-l7policy-update,,LBaaS v2 Update a given L7 policy. +lbaas-l7rule-create,,LBaaS v2 Create L7 rule. +lbaas-l7rule-delete,,LBaaS v2 Delete a given L7 rule. +lbaas-l7rule-list,,LBaaS v2 List L7 rules that belong to a given L7 policy. +lbaas-l7rule-show,,LBaaS v2 Show information of a given rule. +lbaas-l7rule-update,,LBaaS v2 Update a given L7 rule. +lbaas-listener-create,,LBaaS v2 Create a listener. +lbaas-listener-delete,,LBaaS v2 Delete a given listener. +lbaas-listener-list,,LBaaS v2 List listeners that belong to a given tenant. +lbaas-listener-show,,LBaaS v2 Show information of a given listener. +lbaas-listener-update,,LBaaS v2 Update a given listener. +lbaas-loadbalancer-create,,LBaaS v2 Create a loadbalancer. +lbaas-loadbalancer-delete,,LBaaS v2 Delete a given loadbalancer. +lbaas-loadbalancer-list,,LBaaS v2 List loadbalancers that belong to a given tenant. +lbaas-loadbalancer-list-on-agent,,List the loadbalancers on a loadbalancer v2 agent. +lbaas-loadbalancer-show,,LBaaS v2 Show information of a given loadbalancer. +lbaas-loadbalancer-stats,,Retrieve stats for a given loadbalancer. +lbaas-loadbalancer-status,,Retrieve status for a given loadbalancer. +lbaas-loadbalancer-update,,LBaaS v2 Update a given loadbalancer. +lbaas-member-create,,LBaaS v2 Create a member. +lbaas-member-delete,,LBaaS v2 Delete a given member. +lbaas-member-list,,LBaaS v2 List members that belong to a given pool. +lbaas-member-show,,LBaaS v2 Show information of a given member. +lbaas-member-update,,LBaaS v2 Update a given member. +lbaas-pool-create,,LBaaS v2 Create a pool. +lbaas-pool-delete,,LBaaS v2 Delete a given pool. +lbaas-pool-list,,LBaaS v2 List pools that belong to a given tenant. +lbaas-pool-show,,LBaaS v2 Show information of a given pool. +lbaas-pool-update,,LBaaS v2 Update a given pool. +meter-label-create,network meter create,Create a metering label for a given tenant. +meter-label-delete,network meter delete,Delete a given metering label. +meter-label-list,network meter list,List metering labels that belong to a given tenant. +meter-label-rule-create,network meter rule create,Create a metering label rule for a given label. +meter-label-rule-delete,network meter rule delete,Delete a given metering label. +meter-label-rule-list,network meter rule list,List metering labels that belong to a given label. +meter-label-rule-show,network meter rule show,Show information of a given metering label rule. +meter-label-show,network meter show,Show information of a given metering label. +net-create,network create,Create a network for a given tenant. +net-delete,network delete,Delete a given network. +net-external-list,network list --external,List external networks that belong to a given tenant. +net-ip-availability-list,ip availability list,List IP usage of networks +net-ip-availability-show,ip availability show,Show IP usage of specific network +net-list,network list,List networks that belong to a given tenant. +net-list-on-dhcp-agent,network list --agent,List the networks on a DHCP agent. +net-show,network show,Show information of a given network. +net-update,network set,Update network's information. +port-create,port create,Create a port for a given tenant. +port-delete,port delete,Delete a given port. +port-list,port list,List ports that belong to a given tenant. +port-show,port show,Show information of a given port. +port-update,port set/port unset,Update port's information. +purge,,Delete all resources that belong to a given tenant. +qos-available-rule-types,network qos rule type list,List available qos rule types. +qos-bandwidth-limit-rule-create,network qos rule create --type bandwidth-limit,Create a qos bandwidth limit rule. +qos-bandwidth-limit-rule-delete,network qos rule delete --type bandwidth-limit,Delete a given qos bandwidth limit rule. +qos-bandwidth-limit-rule-list,network qos rule list --type bandwidth-limit,List all qos bandwidth limit rules belonging to the specified policy. +qos-bandwidth-limit-rule-show,network qos rule show --type bandwidth-limit,Show information about the given qos bandwidth limit rule. +qos-bandwidth-limit-rule-update,network qos rule update --type bandwidth-limit,Update the given qos bandwidth limit rule. +qos-dscp-marking-rule-create,network qos rule create --type dscp-marking,Create a QoS DSCP marking rule. +qos-dscp-marking-rule-delete,network qos rule delete --type dscp-marking,Delete a given qos dscp marking rule. +qos-dscp-marking-rule-list,network qos rule list --type dscp-marking,List all QoS DSCP marking rules belonging to the specified policy. +qos-dscp-marking-rule-show,network qos rule show --type dscp-marking,Show information about the given qos dscp marking rule. +qos-dscp-marking-rule-update,network qos rule update --type dscp-marking,Update the given QoS DSCP marking rule. +qos-minimum-bandwidth-rule-create,network qos rule create --type minimum-bandwidth,Create a qos minimum bandwidth rule. +qos-minimum-bandwidth-rule-delete,network qos rule delete --type minimum-bandwidth,Delete a given qos minimum bandwidth rule. +qos-minimum-bandwidth-rule-list,network qos rule list --type minimum-bandwidth,List all qos minimum bandwidth rules belonging to the specified policy. +qos-minimum-bandwidth-rule-show,network qos rule show --type minimum-bandwidth,Show information about the given qos minimum bandwidth rule. +qos-minimum-bandwidth-rule-update,network qos rule update --type minimum-bandwidth,Update the given qos minimum bandwidth rule. +qos-policy-create,network qos policy create,Create a qos policy. +qos-policy-delete,network qos policy delete,Delete a given qos policy. +qos-policy-list,network qos policy list,List QoS policies that belong to a given tenant connection. +qos-policy-show,network qos policy show,Show information of a given qos policy. +qos-policy-update,network qos policy set,Update a given qos policy. +quota-default-show,quota show --default,Show default quotas for a given tenant. +quota-delete,,Delete defined quotas of a given tenant. +quota-list,quota list,List quotas of all tenants who have non-default quota values. +quota-show,quota show,Show quotas for a given tenant. +quota-update,quota set,Define tenant's quotas not to use defaults. +rbac-create,network rbac create,Create a RBAC policy for a given tenant. +rbac-delete,network rbac delete,Delete a RBAC policy. +rbac-list,network rbac list,List RBAC policies that belong to a given tenant. +rbac-show,network rbac show,Show information of a given RBAC policy. +rbac-update,network rbac set,Update RBAC policy for given tenant. +router-create,router create,Create a router for a given tenant. +router-delete,router delete,Delete a given router. +router-gateway-clear,router unset,Remove an external network gateway from a router. +router-gateway-set,router set,Set the external network gateway for a router. +router-interface-add,router add subnet / router add port,Add an internal network interface to a router. +router-interface-delete,router remove subnet / router remove port,Remove an internal network interface from a router. +router-list,router list,List routers that belong to a given tenant. +router-list-on-l3-agent,router list --agents,List the routers on a L3 agent. +router-port-list,port list --router,"List ports that belong to a given tenant, with specified router." +router-show,router show,Show information of a given router. +router-update,router set,Update router's information. +security-group-create,security group create,Create a security group. +security-group-delete,security group delete,Delete a given security group. +security-group-list,security group list,List security groups that belong to a given tenant. +security-group-rule-create,security group rule create,Create a security group rule. +security-group-rule-delete,security group rule delete,Delete a given security group rule. +security-group-rule-list,security group rule list,List security group rules that belong to a given tenant. +security-group-rule-show,security group rule show,Show information of a given security group rule. +security-group-show,security group show,Show information of a given security group. +security-group-update,security group set,Update a given security group. +service-provider-list,network service provider list,List service providers. +subnet-create,subnet create,Create a subnet for a given tenant. +subnet-delete,subnet delete,Delete a given subnet. +subnet-list,subnet list,List subnets that belong to a given tenant. +subnet-show,subnet show,Show information of a given subnet. +subnet-update,subnet set / subnet unset,Update subnet's information. +subnetpool-create,subnet pool create,Create a subnetpool for a given tenant. +subnetpool-delete,subnet pool delete,Delete a given subnetpool. +subnetpool-list,subnet pool list,List subnetpools that belong to a given tenant. +subnetpool-show,subnet pool show,Show information of a given subnetpool. +subnetpool-update,subnet pool set / subnet pool unset,Update subnetpool's information. +tag-add,network set --tag,Add a tag into the resource. +tag-remove,network unset --tag,Remove a tag on the resource. +tag-replace,,Replace all tags on the resource. +tap-flow-create,tapflow create,Create a tap flow +tap-flow-delete,tapflow delete,Delete a tap flow +tap-flow-list,tapflow list,List all tap flows +tap-flow-show,tapflow show,Show details of the tap flow +tap-service-create,tapservice create,Create a tap service +tap-service-delete,tapservice delete,Delete a tap service +tap-service-list,tapservice list,List all tap services +tap-service-show,tapservice show,Show details of the tap service +vpn-endpoint-group-create,,Create a VPN endpoint group. +vpn-endpoint-group-delete,,Delete a given VPN endpoint group. +vpn-endpoint-group-list,,List VPN endpoint groups that belong to a given tenant. +vpn-endpoint-group-show,,Show a specific VPN endpoint group. +vpn-endpoint-group-update,,Update a given VPN endpoint group. +vpn-ikepolicy-create,,Create an IKE policy. +vpn-ikepolicy-delete,,Delete a given IKE policy. +vpn-ikepolicy-list,,List IKE policies that belong to a tenant. +vpn-ikepolicy-show,,Show information of a given IKE policy. +vpn-ikepolicy-update,,Update a given IKE policy. +vpn-ipsecpolicy-create,,Create an IPsec policy. +vpn-ipsecpolicy-delete,,Delete a given IPsec policy. +vpn-ipsecpolicy-list,,List IPsec policies that belong to a given tenant connection. +vpn-ipsecpolicy-show,,Show information of a given IPsec policy. +vpn-ipsecpolicy-update,,Update a given IPsec policy. +vpn-service-create,,Create a VPN service. +vpn-service-delete,,Delete a given VPN service. +vpn-service-list,,List VPN service configurations that belong to a given tenant. +vpn-service-show,,Show information of a given VPN service. +vpn-service-update,,Update a given VPN service. \ No newline at end of file diff --git a/doc/source/decoder.rst b/doc/source/decoder.rst index 178ba01c60..16810a4cbd 100644 --- a/doc/source/decoder.rst +++ b/doc/source/decoder.rst @@ -31,6 +31,13 @@ shown when necessary. :widths: 25, 25, 50 :file: data/keystone.csv +``neutron CLI`` +--------------- + +.. csv-table:: + :header: "Neutron CLI", "OSC Equivalent", "Description" + :widths: 25, 25, 50 + :file: data/neutron.csv ``nova CLI`` ------------ From 3907389785055e4599907dca39ae2c2bc0e78497 Mon Sep 17 00:00:00 2001 From: Ankur Gupta Date: Mon, 6 Mar 2017 14:08:55 -0600 Subject: [PATCH 1588/3095] Trivial Fix Improperly rendered release note. TrivialFix Change-Id: I9771b84eb96edea39a5dbd6a94f2ee464da3914c --- .../notes/network-flavor-command-support-afe3a9da962a09bf.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/releasenotes/notes/network-flavor-command-support-afe3a9da962a09bf.yaml b/releasenotes/notes/network-flavor-command-support-afe3a9da962a09bf.yaml index e1d8996136..eed752544b 100644 --- a/releasenotes/notes/network-flavor-command-support-afe3a9da962a09bf.yaml +++ b/releasenotes/notes/network-flavor-command-support-afe3a9da962a09bf.yaml @@ -4,4 +4,4 @@ features: Add ``network flavor create``, ``network flavor delete``, ``network flavor list``, Add ``network flavor show`` and ``network flavor set`` command - [Implement Neutron Flavors in OSC `https://blueprints.launchpad.net/python-openstackclient/+spec/neutron-client-flavors`] + [Blueprint :oscbp:`neutron-client-flavors`] From 20429bd5c624399578cb7217e582eb86068114c6 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Sat, 25 Feb 2017 08:36:53 -0600 Subject: [PATCH 1589/3095] Cleanup release notes for 3.9.0 release Change-Id: I6786fe67928d4f3d72cb4751fb70b67dccaa11e2 --- .../notes/bug-1499657-eeb260849febacf3.yaml | 3 +-- .../notes/bug-1666780-c10010e9061689d3.yaml | 15 ++++++++------- .../notes/bug-1667699-6dad786b128ca8b5.yaml | 5 +++++ 3 files changed, 14 insertions(+), 9 deletions(-) create mode 100644 releasenotes/notes/bug-1667699-6dad786b128ca8b5.yaml diff --git a/releasenotes/notes/bug-1499657-eeb260849febacf3.yaml b/releasenotes/notes/bug-1499657-eeb260849febacf3.yaml index 73af129b10..186b3a8ba0 100644 --- a/releasenotes/notes/bug-1499657-eeb260849febacf3.yaml +++ b/releasenotes/notes/bug-1499657-eeb260849febacf3.yaml @@ -1,6 +1,5 @@ --- fixes: - | - Options ``--parents`` and ``--children`` don't work in ``project show`` - command, fix the issue. + Fix ``--parents`` and ``--children`` options in ``project show`` command. [Bug `1499657 `_] diff --git a/releasenotes/notes/bug-1666780-c10010e9061689d3.yaml b/releasenotes/notes/bug-1666780-c10010e9061689d3.yaml index 952a1f7a95..ed2ee802ae 100644 --- a/releasenotes/notes/bug-1666780-c10010e9061689d3.yaml +++ b/releasenotes/notes/bug-1666780-c10010e9061689d3.yaml @@ -1,11 +1,12 @@ --- features: - | - Support list commands by group name keyword. Add ``--group`` option to - filter the commands by group name keyword, like: --group volume, list all - openstack.volume.v2 (cinder) commands. - That support the scenario that users need to know the current support - commands of some OpenStack services(nova, neutron, cinder and so on) in - OSC, and make it easier for users to find out the related commands that - they care about. + Add ``--group`` option to the ``command list`` command to filter the + commands by group name: ``openstack command list --group volume`` will + list all Volume commands for the selected API version. Use + ``--os-XXXX-api-version`` to select a specific API version for the desired APIs. + + This provides an alternative to searching help output to list the comamnds + available for specific APIs. Note that the ``--group`` argument is used as + a simple substring search in the Command Group column. [Bug `1666780 `_] diff --git a/releasenotes/notes/bug-1667699-6dad786b128ca8b5.yaml b/releasenotes/notes/bug-1667699-6dad786b128ca8b5.yaml new file mode 100644 index 0000000000..329f1404ca --- /dev/null +++ b/releasenotes/notes/bug-1667699-6dad786b128ca8b5.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Fix the ``Ethertype`` column output of ``security group rule list`` command. + [Bug `1667699 `_] From c0a23b89b16651e0bb07adf1800aa57cbfbb564b Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Tue, 7 Mar 2017 02:05:59 +0000 Subject: [PATCH 1590/3095] Updated from global requirements Change-Id: Ifda7ceeb278ff590cbf3d43f4eb65f895474fd4b --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 24311847b3..f6917e336a 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -24,7 +24,7 @@ wrapt>=1.7.0 # BSD License # Install these to generate sphinx autodocs aodhclient>=0.7.0 # Apache-2.0 -gnocchiclient>=2.7.0 # Apache-2.0 +gnocchiclient>=2.7.0 # Apache-2.0 python-barbicanclient>=4.0.0 # Apache-2.0 python-congressclient<2000,>=1.3.0 # Apache-2.0 python-designateclient>=1.5.0 # Apache-2.0 From c03b9a871c4fe6b99221cb4b0d1e0eb7c90283fe Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Thu, 2 Mar 2017 17:28:45 +0800 Subject: [PATCH 1591/3095] Add server event list and show commands OSC server event is similar to nova's instance action commands. Server event is the event record that had been done on a server, include: event type(create, delete, reboot and so on), event result(success, error), start time, finish time and so on. These are important information for server maintains. Change-Id: I8111091f46a0d2755728d8f9d43cc0dfe8842d13 Closes-Bug: #1642030 --- doc/source/command-objects/server-event.rst | 45 +++++ doc/source/commands.rst | 1 + openstackclient/compute/v2/server_event.py | 117 ++++++++++++ .../compute/v2/test_server_event.py | 97 ++++++++++ .../tests/unit/compute/v2/fakes.py | 44 +++++ .../unit/compute/v2/test_server_event.py | 167 ++++++++++++++++++ .../notes/bug-1642030-166b2b28c8adf22e.yaml | 11 ++ setup.cfg | 3 + 8 files changed, 485 insertions(+) create mode 100644 doc/source/command-objects/server-event.rst create mode 100644 openstackclient/compute/v2/server_event.py create mode 100644 openstackclient/tests/functional/compute/v2/test_server_event.py create mode 100644 openstackclient/tests/unit/compute/v2/test_server_event.py create mode 100644 releasenotes/notes/bug-1642030-166b2b28c8adf22e.yaml diff --git a/doc/source/command-objects/server-event.rst b/doc/source/command-objects/server-event.rst new file mode 100644 index 0000000000..ef4685f8d6 --- /dev/null +++ b/doc/source/command-objects/server-event.rst @@ -0,0 +1,45 @@ +============ +server event +============ + +Server event is the event record that had been done on a server, include: event +type(create, delete, reboot and so on), event result(success, error), start +time, finish time and so on. These are important information for server +maintains. + +Compute v2 + +server event list +----------------- + +List recent events of a server + +.. program:: server event list +.. code:: bash + + openstack server event list + + +.. describe:: + + Server to list events (name or ID) + +server event show +----------------- + +Show server event details + +.. program:: server event show +.. code:: bash + + openstack server event show + + + +.. describe:: + + Server to show event details (name or ID) + +.. describe:: + + Request ID of the event to show (ID only) diff --git a/doc/source/commands.rst b/doc/source/commands.rst index a0c67cd4c8..f423618892 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -137,6 +137,7 @@ referring to both Compute and Volume quotas. * ``server``: (**Compute**) virtual machine instance * ``server backup``: (**Compute**) backup server disk image by using snapshot method * ``server dump``: (**Compute**) a dump file of a server created by features like kdump +* ``server event``: (**Compute**) events of a server * ``server group``: (**Compute**) a grouping of servers * ``server image``: (**Compute**) saved server disk image * ``service``: (**Identity**) a cloud service diff --git a/openstackclient/compute/v2/server_event.py b/openstackclient/compute/v2/server_event.py new file mode 100644 index 0000000000..ccb19ef722 --- /dev/null +++ b/openstackclient/compute/v2/server_event.py @@ -0,0 +1,117 @@ +# Copyright 2017 Huawei, Inc. 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. +# + +"""Compute v2 Server operation event implementations""" + +import logging +import six + +from osc_lib.command import command +from osc_lib import utils + +from openstackclient.i18n import _ + + +LOG = logging.getLogger(__name__) + + +class ListServerEvent(command.Lister): + _description = _("List recent events of a server") + + def get_parser(self, prog_name): + parser = super(ListServerEvent, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + help=_('Server to list events (name or ID)'), + ) + parser.add_argument( + '--long', + action='store_true', + default=False, + help=_("List additional fields in output") + ) + return parser + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + server_id = utils.find_resource(compute_client.servers, + parsed_args.server).id + data = compute_client.instance_action.list(server_id) + + if parsed_args.long: + columns = ( + 'request_id', + 'instance_uuid', + 'action', + 'start_time', + 'message', + 'project_id', + 'user_id', + ) + column_headers = ( + 'Request ID', + 'Server ID', + 'Action', + 'Start Time', + 'Message', + 'Project ID', + 'User ID', + ) + else: + columns = ( + 'request_id', + 'instance_uuid', + 'action', + 'start_time', + ) + column_headers = ( + 'Request ID', + 'Server ID', + 'Action', + 'Start Time', + ) + + return (column_headers, + (utils.get_item_properties( + s, columns, + ) for s in data)) + + +class ShowServerEvent(command.ShowOne): + _description = _("Show server event details") + + def get_parser(self, prog_name): + parser = super(ShowServerEvent, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + help=_('Server to show event details (name or ID)'), + ) + parser.add_argument( + 'request_id', + metavar='', + help=_('Request ID of the event to show (ID only)'), + ) + return parser + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + server_id = utils.find_resource(compute_client.servers, + parsed_args.server).id + action_detail = compute_client.instance_action.get( + server_id, parsed_args.request_id) + + return zip(*sorted(six.iteritems(action_detail._info))) diff --git a/openstackclient/tests/functional/compute/v2/test_server_event.py b/openstackclient/tests/functional/compute/v2/test_server_event.py new file mode 100644 index 0000000000..6be5822f98 --- /dev/null +++ b/openstackclient/tests/functional/compute/v2/test_server_event.py @@ -0,0 +1,97 @@ +# Copyright 2017 Huawei, Inc. 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 json +import uuid + +from openstackclient.tests.functional import base +from openstackclient.tests.functional.compute.v2 import test_server + + +class ServerEventTests(base.TestCase): + """Functional tests for server event.""" + + def setUp(self): + super(ServerEventTests, self).setUp() + _flavor = test_server.ServerTests.get_flavor() + _image = test_server.ServerTests.get_image() + _network = test_server.ServerTests.get_network() + self.server_name = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'server create -f json ' + + '--flavor ' + _flavor + ' ' + + '--image ' + _image + ' ' + + _network + ' ' + + '--wait ' + + self.server_name + )) + if not cmd_output: + self.fail('Server has not been created!') + self.addCleanup(self.openstack, 'server delete ' + self.server_name) + self.assertEqual(self.server_name, cmd_output['name']) + self.server_id = cmd_output.get('id') + + def test_server_event_list_and_show(self): + """Test list, show server event""" + # Test 'server event list' for creating + cmd_output = json.loads(self.openstack( + 'server event list -f json ' + self.server_name + )) + request_id = None + for each_event in cmd_output: + self.assertNotIn('Message', each_event) + self.assertNotIn('Project ID', each_event) + self.assertNotIn('User ID', each_event) + if each_event.get('Action') == 'create': + self.assertEqual(self.server_id, each_event.get('Server ID')) + request_id = each_event.get('Request ID') + break + self.assertIsNotNone(request_id) + # Test 'server event show' for creating + cmd_output = json.loads(self.openstack( + 'server event show -f json ' + self.server_name + ' ' + request_id + )) + self.assertEqual(self.server_id, cmd_output.get('instance_uuid')) + self.assertEqual(request_id, cmd_output.get('request_id')) + self.assertEqual('create', cmd_output.get('action')) + self.assertIsNotNone(cmd_output.get('events')) + self.assertIsInstance(cmd_output.get('events'), list) + + # Reboot server, trigger reboot event + self.openstack('server reboot --wait ' + self.server_name) + # Test 'server event list --long' for rebooting + cmd_output = json.loads(self.openstack( + 'server event list --long -f json ' + self.server_name + )) + request_id = None + for each_event in cmd_output: + self.assertIn('Message', each_event) + self.assertIn('Project ID', each_event) + self.assertIn('User ID', each_event) + if each_event.get('Action') == 'reboot': + request_id = each_event.get('Request ID') + self.assertEqual(self.server_id, each_event.get('Server ID')) + break + self.assertIsNotNone(request_id) + # Test 'server event show' for rebooting + cmd_output = json.loads(self.openstack( + 'server event show -f json ' + self.server_name + ' ' + request_id + )) + + self.assertEqual(self.server_id, cmd_output.get('instance_uuid')) + self.assertEqual(request_id, cmd_output.get('request_id')) + self.assertEqual('reboot', cmd_output.get('action')) + self.assertIsNotNone(cmd_output.get('events')) + self.assertIsInstance(cmd_output.get('events'), list) diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index 4fe735b6b4..bbb770bbba 100644 --- a/openstackclient/tests/unit/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -204,6 +204,9 @@ def __init__(self, **kwargs): self.server_groups = mock.Mock() self.server_groups.resource_class = fakes.FakeResource(None, {}) + self.instance_action = mock.Mock() + self.instance_action.resource_class = fakes.FakeResource(None, {}) + self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] @@ -656,6 +659,47 @@ def get_servers(servers=None, count=2): return mock.Mock(side_effect=servers) +class FakeServerEvent(object): + """Fake one or more server event.""" + + @staticmethod + def create_one_server_event(attrs=None): + """Create a fake server event. + + :param attrs: + A dictionary with all attributes + :return: + A FakeResource object, with id and other attributes + """ + attrs = attrs or {} + + # Set default attributes + server_event_info = { + "instance_uuid": "server-event-" + uuid.uuid4().hex, + "user_id": "user-id-" + uuid.uuid4().hex, + "start_time": "2017-02-27T07:47:13.000000", + "request_id": "req-" + uuid.uuid4().hex, + "action": "create", + "message": None, + "project_id": "project-id-" + uuid.uuid4().hex, + "events": [{ + "finish_time": "2017-02-27T07:47:25.000000", + "start_time": "2017-02-27T07:47:15.000000", + "traceback": None, + "event": "compute__do_build_and_run_instance", + "result": "Success" + }] + } + # Overwrite default attributes + server_event_info.update(attrs) + + server_event = fakes.FakeResource( + info=copy.deepcopy(server_event_info), + loaded=True, + ) + return server_event + + class FakeService(object): """Fake one or more services.""" diff --git a/openstackclient/tests/unit/compute/v2/test_server_event.py b/openstackclient/tests/unit/compute/v2/test_server_event.py new file mode 100644 index 0000000000..5c94891a14 --- /dev/null +++ b/openstackclient/tests/unit/compute/v2/test_server_event.py @@ -0,0 +1,167 @@ +# Copyright 2017 Huawei, Inc. 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.compute.v2 import server_event +from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes + + +class TestServerEvent(compute_fakes.TestComputev2): + + fake_server = compute_fakes.FakeServer.create_one_server() + + def setUp(self): + super(TestServerEvent, self).setUp() + + self.servers_mock = self.app.client_manager.compute.servers + self.servers_mock.reset_mock() + self.events_mock = self.app.client_manager.compute.instance_action + self.events_mock.reset_mock() + + self.servers_mock.get.return_value = self.fake_server + + +class TestListServerEvent(TestServerEvent): + + fake_event = compute_fakes.FakeServerEvent.create_one_server_event() + + columns = ( + 'Request ID', + 'Server ID', + 'Action', + 'Start Time', + ) + data = (( + fake_event.request_id, + fake_event.instance_uuid, + fake_event.action, + fake_event.start_time, + ), ) + + long_columns = ( + 'Request ID', + 'Server ID', + 'Action', + 'Start Time', + 'Message', + 'Project ID', + 'User ID', + ) + long_data = (( + fake_event.request_id, + fake_event.instance_uuid, + fake_event.action, + fake_event.start_time, + fake_event.message, + fake_event.project_id, + fake_event.user_id, + ), ) + + def setUp(self): + super(TestListServerEvent, self).setUp() + + self.events_mock.list.return_value = [self.fake_event, ] + self.cmd = server_event.ListServerEvent(self.app, None) + + def test_server_event_list(self): + arglist = [ + self.fake_server.name, + ] + verifylist = [ + ('server', self.fake_server.name), + ('long', False), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_once_with(self.fake_server.name) + self.events_mock.list.assert_called_once_with(self.fake_server.id) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, tuple(data)) + + def test_server_event_list_long(self): + arglist = [ + '--long', + self.fake_server.name, + ] + verifylist = [ + ('server', self.fake_server.name), + ('long', True), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_once_with(self.fake_server.name) + self.events_mock.list.assert_called_once_with(self.fake_server.id) + + self.assertEqual(self.long_columns, columns) + self.assertEqual(self.long_data, tuple(data)) + + +class TestShowServerEvent(TestServerEvent): + + fake_event = compute_fakes.FakeServerEvent.create_one_server_event() + + columns = ( + 'action', + 'events', + 'instance_uuid', + 'message', + 'project_id', + 'request_id', + 'start_time', + 'user_id', + ) + data = ( + fake_event.action, + fake_event.events, + fake_event.instance_uuid, + fake_event.message, + fake_event.project_id, + fake_event.request_id, + fake_event.start_time, + fake_event.user_id, + ) + + def setUp(self): + super(TestShowServerEvent, self).setUp() + + self.events_mock.get.return_value = self.fake_event + self.cmd = server_event.ShowServerEvent(self.app, None) + + def test_server_event_show(self): + arglist = [ + self.fake_server.name, + self.fake_event.request_id, + ] + verifylist = [ + ('server', self.fake_server.name), + ('request_id', self.fake_event.request_id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_once_with(self.fake_server.name) + self.events_mock.get.assert_called_once_with( + self.fake_server.id, self.fake_event.request_id) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) diff --git a/releasenotes/notes/bug-1642030-166b2b28c8adf22e.yaml b/releasenotes/notes/bug-1642030-166b2b28c8adf22e.yaml new file mode 100644 index 0000000000..200c73fbe8 --- /dev/null +++ b/releasenotes/notes/bug-1642030-166b2b28c8adf22e.yaml @@ -0,0 +1,11 @@ +--- +features: + - | + Add ``server event`` list and show commands, that is similar to nova's + instance action commands. + + Server event is the event record that had been done on a server, + include: event type(create, delete, reboot and so on), + event result(success, error), start time, finish time and so on. + These are important information for server maintains. + [Bug `1642030 `_] diff --git a/setup.cfg b/setup.cfg index e18aa5c1e7..ea4691b32e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -135,6 +135,9 @@ openstack.compute.v2 = server_backup_create = openstackclient.compute.v2.server_backup:CreateServerBackup + server_event_list = openstackclient.compute.v2.server_event:ListServerEvent + server_event_show = openstackclient.compute.v2.server_event:ShowServerEvent + server_group_create = openstackclient.compute.v2.server_group:CreateServerGroup server_group_delete = openstackclient.compute.v2.server_group:DeleteServerGroup server_group_list = openstackclient.compute.v2.server_group:ListServerGroup From f4d3810c3e978382bf80e59d5d9a5dcd2485f56c Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Mon, 6 Mar 2017 14:20:20 +0900 Subject: [PATCH 1592/3095] command list: Move network meter appropriately 'network meter' commands are not placed alphabetically in the command list. It was due to the incorrect filename. Trivial Fix Change-Id: I7a76d3133915883cf41be8c7430def284d292d6e --- .../command-objects/{meter.rst => network-meter.rst} | 0 .../network/v2/{meter.py => network_meter.py} | 0 .../v2/{test_meter.py => test_network_meter.py} | 0 .../v2/{test_meter.py => test_network_meter.py} | 10 +++++----- setup.cfg | 8 ++++---- 5 files changed, 9 insertions(+), 9 deletions(-) rename doc/source/command-objects/{meter.rst => network-meter.rst} (100%) rename openstackclient/network/v2/{meter.py => network_meter.py} (100%) rename openstackclient/tests/functional/network/v2/{test_meter.py => test_network_meter.py} (100%) rename openstackclient/tests/unit/network/v2/{test_meter.py => test_network_meter.py} (96%) diff --git a/doc/source/command-objects/meter.rst b/doc/source/command-objects/network-meter.rst similarity index 100% rename from doc/source/command-objects/meter.rst rename to doc/source/command-objects/network-meter.rst diff --git a/openstackclient/network/v2/meter.py b/openstackclient/network/v2/network_meter.py similarity index 100% rename from openstackclient/network/v2/meter.py rename to openstackclient/network/v2/network_meter.py diff --git a/openstackclient/tests/functional/network/v2/test_meter.py b/openstackclient/tests/functional/network/v2/test_network_meter.py similarity index 100% rename from openstackclient/tests/functional/network/v2/test_meter.py rename to openstackclient/tests/functional/network/v2/test_network_meter.py diff --git a/openstackclient/tests/unit/network/v2/test_meter.py b/openstackclient/tests/unit/network/v2/test_network_meter.py similarity index 96% rename from openstackclient/tests/unit/network/v2/test_meter.py rename to openstackclient/tests/unit/network/v2/test_network_meter.py index b393f7fa65..2b96f7a680 100644 --- a/openstackclient/tests/unit/network/v2/test_meter.py +++ b/openstackclient/tests/unit/network/v2/test_network_meter.py @@ -18,7 +18,7 @@ from osc_lib import exceptions -from openstackclient.network.v2 import meter +from openstackclient.network.v2 import network_meter from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3 from openstackclient.tests.unit.network.v2 import fakes as network_fakes from openstackclient.tests.unit import utils as tests_utils @@ -62,7 +62,7 @@ def setUp(self): self.network.create_metering_label = mock.Mock( return_value=self.new_meter) self.projects_mock.get.return_value = self.project - self.cmd = meter.CreateMeter(self.app, self.namespace) + self.cmd = network_meter.CreateMeter(self.app, self.namespace) def test_create_no_options(self): arglist = [] @@ -134,7 +134,7 @@ def setUp(self): meter=self.meter_list ) - self.cmd = meter.DeleteMeter(self.app, self.namespace) + self.cmd = network_meter.DeleteMeter(self.app, self.namespace) def test_delete_one_meter(self): arglist = [ @@ -235,7 +235,7 @@ def setUp(self): return_value=self.meter_list ) - self.cmd = meter.ListMeter(self.app, self.namespace) + self.cmd = network_meter.ListMeter(self.app, self.namespace) def test_meter_list(self): arglist = [] @@ -274,7 +274,7 @@ class TestShowMeter(TestMeter): def setUp(self): super(TestShowMeter, self).setUp() - self.cmd = meter.ShowMeter(self.app, self.namespace) + self.cmd = network_meter.ShowMeter(self.app, self.namespace) self.network.find_metering_label = \ mock.Mock(return_value=self.new_meter) diff --git a/setup.cfg b/setup.cfg index ea4691b32e..5d8b06b721 100644 --- a/setup.cfg +++ b/setup.cfg @@ -378,10 +378,10 @@ openstack.network.v2 = network_set = openstackclient.network.v2.network:SetNetwork network_show = openstackclient.network.v2.network:ShowNetwork - network_meter_create = openstackclient.network.v2.meter:CreateMeter - network_meter_delete = openstackclient.network.v2.meter:DeleteMeter - network_meter_list = openstackclient.network.v2.meter:ListMeter - network_meter_show = openstackclient.network.v2.meter:ShowMeter + network_meter_create = openstackclient.network.v2.network_meter:CreateMeter + network_meter_delete = openstackclient.network.v2.network_meter:DeleteMeter + network_meter_list = openstackclient.network.v2.network_meter:ListMeter + network_meter_show = openstackclient.network.v2.network_meter:ShowMeter network_meter_rule_create = openstackclient.network.v2.network_meter_rule:CreateMeterRule network_meter_rule_delete = openstackclient.network.v2.network_meter_rule:DeleteMeterRule From 73c2a809f74eeec1005a61f5bf5eeb478246b3a8 Mon Sep 17 00:00:00 2001 From: Eric Brown Date: Tue, 7 Mar 2017 14:38:56 -0800 Subject: [PATCH 1593/3095] Remove py34 tox env and pypi classifier Currently only py27 and py35 (not py34) is tested in the gate, so py34 should no longer be part of the tox environment or part of the PyPi classifier. Change-Id: I155fc0e3ac06b495718d9fa3058edded738cb011 --- setup.cfg | 1 - tox.ini | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index ea4691b32e..9ac3fe99db 100644 --- a/setup.cfg +++ b/setup.cfg @@ -16,7 +16,6 @@ classifier = Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 - Programming Language :: Python :: 3.4 Programming Language :: Python :: 3.5 [files] diff --git a/tox.ini b/tox.ini index 9d18cb4e73..b2660bdba4 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] minversion = 1.6 -envlist = py35,py34,py27,pep8 +envlist = py35,py27,pep8 skipdist = True [testenv] From 8c6cfb518b9baa8a2d0e72bdee11fe6f4647ee4a Mon Sep 17 00:00:00 2001 From: Jens Rosenboom Date: Wed, 8 Mar 2017 12:22:35 +0100 Subject: [PATCH 1594/3095] Add the bgp agent type to network agent command The neutron-dynamic-routing project provides an agent called neutron-bgp-dragent with type "BGP dynamic routing agent". So we need to add this option and can avoid using the deprecated neutron CLI e.g. in [1]. [1] https://docs.openstack.org/ocata/networking-guide/config-bgp-dynamic-routing.html#verify-service-operation Change-Id: I9af1e09d122806b56b966295817d8d31393e0283 Closes-Bug: 1671040 --- openstackclient/network/v2/network_agent.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openstackclient/network/v2/network_agent.py b/openstackclient/network/v2/network_agent.py index d429fa0830..37ad9f0627 100644 --- a/openstackclient/network/v2/network_agent.py +++ b/openstackclient/network/v2/network_agent.py @@ -89,10 +89,11 @@ def get_parser(self, prog_name): parser.add_argument( '--agent-type', metavar='', - choices=["dhcp", "open-vswitch", "linux-bridge", "ofa", "l3", - "loadbalancer", "metering", "metadata", "macvtap", "nic"], + choices=["bgp", "dhcp", "open-vswitch", "linux-bridge", "ofa", + "l3", "loadbalancer", "metering", "metadata", "macvtap", + "nic"], help=_("List only agents with the specified agent type. " - "The supported agent types are: dhcp, open-vswitch, " + "The supported agent types are: bgp, dhcp, open-vswitch, " "linux-bridge, ofa, l3, loadbalancer, metering, " "metadata, macvtap, nic.") ) @@ -125,6 +126,7 @@ def take_action(self, parsed_args): ) key_value = { + 'bgp': 'BGP dynamic routing agent', 'dhcp': 'DHCP agent', 'open-vswitch': 'Open vSwitch agent', 'linux-bridge': 'Linux bridge agent', From 6c1b03bf7354fe39d61bb9cf93d2491bbb5ebb16 Mon Sep 17 00:00:00 2001 From: Tom Jose Kalapura Date: Fri, 9 Sep 2016 22:17:50 -0700 Subject: [PATCH 1595/3095] Add sort support to project list Add sort support to project list by sorting items in the client side. By default list will be sorted by name. Change-Id: I00011406846b4003aff075eeeb88ac18fa5e2820 Closes-Bug: #1596818 --- doc/source/command-objects/project.rst | 7 ++++ openstackclient/identity/v2_0/project.py | 9 +++++ openstackclient/identity/v3/project.py | 9 +++++ .../tests/unit/identity/v2_0/test_project.py | 36 ++++++++++++++++++ .../tests/unit/identity/v3/fakes.py | 17 +++++++++ .../tests/unit/identity/v3/test_project.py | 37 +++++++++++++++++++ .../notes/bug-1596818-d4cd93dd4d38d3d6.yaml | 6 +++ 7 files changed, 121 insertions(+) create mode 100644 releasenotes/notes/bug-1596818-d4cd93dd4d38d3d6.yaml diff --git a/doc/source/command-objects/project.rst b/doc/source/command-objects/project.rst index f76f79ff53..c4393d1790 100644 --- a/doc/source/command-objects/project.rst +++ b/doc/source/command-objects/project.rst @@ -96,6 +96,7 @@ List projects [--domain ] [--user ] [--long] + [--sort [:,:,..]] .. option:: --domain @@ -113,6 +114,12 @@ List projects List additional fields in output +.. option:: --sort [:,:,..] + + Sort output by selected keys and directions (asc or desc) (default: asc), + multiple keys and directions can be specified --sort + [:,:,..] + project set ----------- diff --git a/openstackclient/identity/v2_0/project.py b/openstackclient/identity/v2_0/project.py index fc5c920175..5c3ec73001 100644 --- a/openstackclient/identity/v2_0/project.py +++ b/openstackclient/identity/v2_0/project.py @@ -136,6 +136,13 @@ def get_parser(self, prog_name): default=False, help=_('List additional fields in output'), ) + parser.add_argument( + '--sort', + metavar='[:]', + help=_('Sort output by selected keys and directions (asc or desc) ' + '(default: asc), repeat this option to specify multiple ' + 'keys and directions.'), + ) return parser def take_action(self, parsed_args): @@ -144,6 +151,8 @@ def take_action(self, parsed_args): else: columns = ('ID', 'Name') data = self.app.client_manager.identity.tenants.list() + if parsed_args.sort: + data = utils.sort_items(data, parsed_args.sort) return (columns, (utils.get_item_properties( s, columns, diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index 56c4fbc8b2..861847d3d0 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -180,6 +180,13 @@ def get_parser(self, prog_name): default=False, help=_('List additional fields in output'), ) + parser.add_argument( + '--sort', + metavar='[:]', + help=_('Sort output by selected keys and directions (asc or desc) ' + '(default: asc), repeat this option to specify multiple ' + 'keys and directions.'), + ) return parser def take_action(self, parsed_args): @@ -208,6 +215,8 @@ def take_action(self, parsed_args): kwargs['user'] = user_id data = identity_client.projects.list(**kwargs) + if parsed_args.sort: + data = utils.sort_items(data, parsed_args.sort) return (columns, (utils.get_item_properties( s, columns, diff --git a/openstackclient/tests/unit/identity/v2_0/test_project.py b/openstackclient/tests/unit/identity/v2_0/test_project.py index c1f00762e4..69da739f53 100644 --- a/openstackclient/tests/unit/identity/v2_0/test_project.py +++ b/openstackclient/tests/unit/identity/v2_0/test_project.py @@ -23,6 +23,7 @@ class TestProject(identity_fakes.TestIdentityv2): fake_project = identity_fakes.FakeProject.create_one_project() + fake_projects = identity_fakes.FakeProject.create_projects() columns = ( 'description', @@ -36,6 +37,12 @@ class TestProject(identity_fakes.TestIdentityv2): fake_project.id, fake_project.name, ) + datalists = ( + (fake_projects[0].description, True, + fake_projects[0].id, fake_projects[0].name,), + (fake_projects[1].description, True, + fake_projects[1].id, fake_projects[1].name,), + ) def setUp(self): super(TestProject, self).setUp() @@ -357,6 +364,35 @@ def test_project_list_long(self): ), ) self.assertEqual(datalist, tuple(data)) + def test_project_list_sort(self): + self.projects_mock.list.return_value = self.fake_projects + + arglist = ['--sort', 'name:asc', ] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + (columns, data) = self.cmd.take_action(parsed_args) + self.projects_mock.list.assert_called_with() + + collist = ('ID', 'Name') + self.assertEqual(collist, columns) + + if self.fake_projects[0].name > self.fake_projects[1].name: + datalists = ( + (self.fake_projects[1].id, self.fake_projects[1].name), + (self.fake_projects[0].id, self.fake_projects[0].name), + ) + else: + datalists = ( + (self.fake_projects[0].id, self.fake_projects[0].name), + (self.fake_projects[1].id, self.fake_projects[1].name), + ) + + self.assertEqual(datalists, tuple(data)) + class TestProjectSet(TestProject): diff --git a/openstackclient/tests/unit/identity/v3/fakes.py b/openstackclient/tests/unit/identity/v3/fakes.py index 7b76fa6087..473efcfe80 100644 --- a/openstackclient/tests/unit/identity/v3/fakes.py +++ b/openstackclient/tests/unit/identity/v3/fakes.py @@ -622,6 +622,23 @@ def create_one_project(attrs=None): loaded=True) return project + @staticmethod + def create_projects(attrs=None, count=2): + """Create multiple fake projects. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of projects to fake + :return: + A list of FakeResource objects faking the projects + """ + + projects = [] + for i in range(0, count): + projects.append(FakeProject.create_one_project(attrs)) + return projects + class FakeDomain(object): """Fake one or more domain.""" diff --git a/openstackclient/tests/unit/identity/v3/test_project.py b/openstackclient/tests/unit/identity/v3/test_project.py index 702d920975..2edb49ef51 100644 --- a/openstackclient/tests/unit/identity/v3/test_project.py +++ b/openstackclient/tests/unit/identity/v3/test_project.py @@ -451,6 +451,7 @@ class TestProjectList(TestProject): domain = identity_fakes.FakeDomain.create_one_domain() project = identity_fakes.FakeProject.create_one_project( attrs={'domain_id': domain.id}) + projects = identity_fakes.FakeProject.create_projects() columns = ( 'ID', @@ -462,6 +463,12 @@ class TestProjectList(TestProject): project.name, ), ) + datalists = ( + (projects[0].description, True, + projects[0].id, projects[0].name,), + (projects[1].description, True, + projects[1].id, projects[1].name,), + ) def setUp(self): super(TestProjectList, self).setUp() @@ -552,6 +559,36 @@ def test_project_list_domain_no_perms(self): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) + def test_project_list_sort(self): + self.projects_mock.list.return_value = self.projects + + arglist = ['--sort', 'name:asc', ] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + (columns, data) = self.cmd.take_action(parsed_args) + self.projects_mock.list.assert_called_with() + + collist = ('ID', 'Name') + self.assertEqual(collist, columns) + + if self.projects[0].name > self.projects[1].name: + datalists = ( + (self.projects[1].id, self.projects[1].name), + (self.projects[0].id, self.projects[0].name), + ) + else: + datalists = ( + (self.projects[0].id, self.projects[0].name), + (self.projects[1].id, self.projects[1].name), + ) + + self.assertEqual(datalists, tuple(data)) + class TestProjectSet(TestProject): diff --git a/releasenotes/notes/bug-1596818-d4cd93dd4d38d3d6.yaml b/releasenotes/notes/bug-1596818-d4cd93dd4d38d3d6.yaml new file mode 100644 index 0000000000..6e31db8bfe --- /dev/null +++ b/releasenotes/notes/bug-1596818-d4cd93dd4d38d3d6.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--sort`` support to ``project list`` by sorting items in client side + By default project list will be sorted by name. + [Bug `1596818 `_] \ No newline at end of file From 3e6356a4d87351c84a29e3a44f60f0544e2947b6 Mon Sep 17 00:00:00 2001 From: Jens Rosenboom Date: Mon, 27 Feb 2017 15:37:50 +0100 Subject: [PATCH 1596/3095] Fix handling the use_default_subnet_pool attribute The attribute formerly wasn't working because it is lacking support in the SDK. The patch to add it[1] introduced a modified spelling, so we should be matching this here. Also make sure that the attribute is not shown when displaying subnet objects, as it is meant to be invisible[2]. [1] I72c0be77d96f3891748cdd69c382211dc20dbf5e [2] http://git.openstack.org/cgit/openstack/neutron/tree/neutron/extensions/default_subnetpools.py#n23 Change-Id: Ic125b818a6082d76a1114fe89e0b1fcf4c7356b0 Partial-Bug: 1668223 --- openstackclient/network/sdk_utils.py | 23 +++++++++++++++++++++-- openstackclient/network/v2/subnet.py | 10 ++++++++-- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/openstackclient/network/sdk_utils.py b/openstackclient/network/sdk_utils.py index 7bd54e4615..04f168bea3 100644 --- a/openstackclient/network/sdk_utils.py +++ b/openstackclient/network/sdk_utils.py @@ -13,8 +13,24 @@ import six -# Get the OSC show command display and attribute columns for an SDK resource. -def get_osc_show_columns_for_sdk_resource(sdk_resource, osc_column_map): +def get_osc_show_columns_for_sdk_resource( + sdk_resource, + osc_column_map, + invisible_columns=[] +): + """Get and filter the display and attribute columns for an SDK resource. + + Common utility function for preparing the output of an OSC show command. + Some of the columns may need to get renamed, others made invisible. + + :param sdk_resource: An SDK resource + :param osc_column_map: A hash of mappings for display column names + :param invisible_columns: A list of invisible column names + + :returns: Two tuples containing the names of the display and attribute + columns + """ + if getattr(sdk_resource, 'allow_get', None) is not None: resource_dict = sdk_resource.to_dict( body=True, headers=False, ignore_none=False) @@ -24,6 +40,9 @@ def get_osc_show_columns_for_sdk_resource(sdk_resource, osc_column_map): # Build the OSC column names to display for the SDK resource. attr_map = {} display_columns = list(resource_dict.keys()) + for col_name in invisible_columns: + if col_name in display_columns: + display_columns.remove(col_name) for sdk_attr, osc_attr in six.iteritems(osc_column_map): if sdk_attr in display_columns: attr_map[osc_attr] = sdk_attr diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 2771858b97..403b4cd2a1 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -132,7 +132,13 @@ def _get_columns(item): 'subnet_pool_id': 'subnetpool_id', 'tenant_id': 'project_id', } - return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + # Do not show this column when displaying a subnet + invisible_columns = ['use_default_subnetpool'] + return sdk_utils.get_osc_show_columns_for_sdk_resource( + item, + column_map, + invisible_columns=invisible_columns + ) def convert_entries_to_nexthop(entries): @@ -179,7 +185,7 @@ def _get_attrs(client_manager, parsed_args, is_create=True): ignore_missing=False) attrs['subnetpool_id'] = subnet_pool.id if parsed_args.use_default_subnet_pool: - attrs['use_default_subnetpool'] = True + attrs['use_default_subnet_pool'] = True if parsed_args.prefix_length is not None: attrs['prefixlen'] = parsed_args.prefix_length if parsed_args.subnet_range is not None: From 35dc85823d9e390e70bf8fcd4eea6c77e51359c5 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Thu, 9 Mar 2017 19:28:51 +0800 Subject: [PATCH 1597/3095] Revert "Fix port command for SDK >0.9.10" This reverts commit 8e277c64fb86dc1ca432e02b82accdcaf42a2779. After [1] merged in osc-lib, we can Avoid 'NoneType' error when format conversion now, So [2] in no longer needed, we can clean it up. Hold this until [1] released and included in the requirement in OSC. [1] https://review.openstack.org/#/c/434768/ [2] https://review.openstack.org/#/c/420420/ Depend-On: I649b4fc65ef7c19b8193b07f3bd59f00e6095f9f Change-Id: I2783713102d5e6164d9617f130c2f595bd6d939e --- openstackclient/network/v2/port.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 6117175e9e..ec7042914d 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -35,10 +35,6 @@ def _format_admin_state(state): return 'UP' if state else 'DOWN' -def _format_dns_assignment(dns_assignment): - return utils.format_list_of_dicts(dns_assignment) \ - if dns_assignment else None - _formatters = { 'admin_state_up': _format_admin_state, 'is_admin_state_up': _format_admin_state, @@ -47,7 +43,7 @@ def _format_dns_assignment(dns_assignment): 'binding_vif_details': utils.format_dict, 'binding:profile': utils.format_dict, 'binding:vif_details': utils.format_dict, - 'dns_assignment': _format_dns_assignment, + 'dns_assignment': utils.format_list_of_dicts, 'extra_dhcp_opts': utils.format_list_of_dicts, 'fixed_ips': utils.format_list_of_dicts, 'security_group_ids': utils.format_list, From 289f15a81468e3e134218bf1c2833102b6365f47 Mon Sep 17 00:00:00 2001 From: Nakul Dahiwade Date: Wed, 1 Mar 2017 21:15:10 +0000 Subject: [PATCH 1598/3095] Jsonify meter and meter rule functional tests Some functional tests try to parse the CLI table output format, that cause much work on parse string by using regular expression. Using json format in functional tests is better and easier way, this patch reworks for meter and meter rule related tests. Change-Id: I1f2a95c873a4ed23dd1afa4040900a1c7704d0bf --- .../network/v2/test_network_meter.py | 116 +++++++++++++----- .../network/v2/test_network_meter_rule.py | 83 ++++++++----- 2 files changed, 135 insertions(+), 64 deletions(-) diff --git a/openstackclient/tests/functional/network/v2/test_network_meter.py b/openstackclient/tests/functional/network/v2/test_network_meter.py index 7dce34e7e3..f73f481297 100644 --- a/openstackclient/tests/functional/network/v2/test_network_meter.py +++ b/openstackclient/tests/functional/network/v2/test_network_meter.py @@ -13,7 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. -import re +import json import uuid from openstackclient.tests.functional import base @@ -27,37 +27,45 @@ class TestMeter(base.TestCase): # has its own needs and there are collisions when running # tests in parallel. - @classmethod - def setUpClass(cls): - # Set up some regex for matching below - cls.re_name = re.compile("name\s+\|\s+([^|]+?)\s+\|") - cls.re_shared = re.compile("shared\s+\|\s+(\S+)") - cls.re_description = re.compile("description\s+\|\s+([^|]+?)\s+\|") - def test_meter_delete(self): """Test create, delete multiple""" name1 = uuid.uuid4().hex name2 = uuid.uuid4().hex - - raw_output = self.openstack( - 'network meter create ' + name1, + description = 'fakedescription' + json_output = json.loads(self.openstack( + 'network meter create -f json ' + name1 + ' --description ' + + description) ) self.assertEqual( name1, - re.search(self.re_name, raw_output).group(1), + json_output.get('name'), ) # Check if default shared values self.assertEqual( - 'False', - re.search(self.re_shared, raw_output).group(1) + False, + json_output.get('shared') + ) + self.assertEqual( + 'fakedescription', + json_output.get('description') ) - raw_output = self.openstack( - 'network meter create ' + name2, + json_output_2 = json.loads(self.openstack( + 'network meter create -f json ' + name2 + ' --description ' + + description) ) self.assertEqual( name2, - re.search(self.re_name, raw_output).group(1), + json_output_2.get('name'), + ) + # Check if default shared values + self.assertEqual( + False, + json_output_2.get('shared') + ) + self.assertEqual( + 'fakedescription', + json_output_2.get('description') ) raw_output = self.openstack( @@ -68,35 +76,83 @@ def test_meter_delete(self): def test_meter_list(self): """Test create, list filters, delete""" name1 = uuid.uuid4().hex - raw_output = self.openstack( - 'network meter create --description Test1 --share ' + name1, + json_output = json.loads(self.openstack( + 'network meter create -f json --description Test1 --share ' + + name1) ) self.addCleanup(self.openstack, 'network meter delete ' + name1) self.assertEqual( 'Test1', - re.search(self.re_description, raw_output).group(1), + json_output.get('description'), ) self.assertEqual( - 'True', - re.search(self.re_shared, raw_output).group(1), + True, + json_output.get('shared'), ) name2 = uuid.uuid4().hex - raw_output = self.openstack( - 'network meter create --description Test2 --no-share ' + name2, + json_output_2 = json.loads(self.openstack( + 'network meter create -f json --description Test2 --no-share ' + + name2) ) self.addCleanup(self.openstack, 'network meter delete ' + name2) self.assertEqual( 'Test2', - re.search(self.re_description, raw_output).group(1), + json_output_2.get('description') + ) + self.assertEqual( + False, + json_output_2.get('shared') + ) + + raw_output = json.loads(self.openstack('network meter list -f json')) + name_list = [item.get('Name') for item in raw_output] + self.assertIn(name1, name_list) + self.assertIn(name2, name_list) + + def test_meter_show(self): + """Test create, show, delete""" + name1 = uuid.uuid4().hex + description = 'fakedescription' + json_output = json.loads(self.openstack( + 'network meter create -f json ' + name1 + ' --description ' + + description) + ) + meter_id = json_output.get('id') + self.addCleanup(self.openstack, 'network meter delete ' + name1) + + # Test show with ID + json_output = json.loads(self.openstack( + 'network meter show -f json ' + meter_id) ) self.assertEqual( - 'False', - re.search(self.re_shared, raw_output).group(1), + False, + json_output.get('shared') + ) + self.assertEqual( + 'fakedescription', + json_output.get('description') + ) + self.assertEqual( + name1, + json_output.get('name') ) - raw_output = self.openstack('network meter list') - self.assertIsNotNone(re.search(name1 + "\s+\|\s+Test1", raw_output)) - self.assertIsNotNone(re.search(name2 + "\s+\|\s+Test2", raw_output)) + # Test show with name + json_output = json.loads(self.openstack( + 'network meter show -f json ' + name1) + ) + self.assertEqual( + meter_id, + json_output.get('id') + ) + self.assertEqual( + False, + json_output.get('shared') + ) + self.assertEqual( + 'fakedescription', + json_output.get('description') + ) diff --git a/openstackclient/tests/functional/network/v2/test_network_meter_rule.py b/openstackclient/tests/functional/network/v2/test_network_meter_rule.py index 4f079e3ce2..d15cdf77a8 100644 --- a/openstackclient/tests/functional/network/v2/test_network_meter_rule.py +++ b/openstackclient/tests/functional/network/v2/test_network_meter_rule.py @@ -13,7 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. -import re +import json import uuid from openstackclient.tests.functional import base @@ -27,19 +27,11 @@ class TestMeterRule(base.TestCase): @classmethod def setUpClass(cls): - # Set up some regex for matching below - cls.re_id = re.compile("id\s+\|\s+(\S+)") - cls.re_direction = re.compile("direction\s+\|\s+(\S+)") - cls.re_ip_prefix = re.compile( - "remote_ip_prefix\s+\|\s+([^|]+?)\s+\|" - ) - cls.re_meter_id = re.compile("metering_label_id\s+\|\s+(\S+)") - - raw_output = cls.openstack( - 'network meter create ' + cls.METER_NAME - ) + json_output = json.loads(cls.openstack( + 'network meter create -f json ' + cls.METER_NAME + )) - cls.METER_ID = re.search(cls.re_id, raw_output).group(1) + cls.METER_ID = json_output.get('id') @classmethod def tearDownClass(cls): @@ -49,58 +41,81 @@ def tearDownClass(cls): def test_meter_rule_delete(self): """test create, delete""" - raw_output = self.openstack( - 'network meter rule create ' + + json_output = json.loads(self.openstack( + 'network meter rule create -f json ' + '--remote-ip-prefix 10.0.0.0/8 ' + self.METER_ID - ) - rule_id = re.search(self.re_id, raw_output).group(1) - re_ip = re.search(self.re_ip_prefix, raw_output) + )) + rule_id = json_output.get('id') + re_ip = json_output.get('remote_ip_prefix') self.addCleanup(self.openstack, 'network meter rule delete ' + rule_id) self.assertIsNotNone(re_ip) self.assertIsNotNone(rule_id) + self.assertEqual( + '10.0.0.0/8', re_ip + ) def test_meter_rule_list(self): """Test create, list, delete""" - raw_output = self.openstack( - 'network meter rule create ' + + json_output = json.loads(self.openstack( + 'network meter rule create -f json ' + '--remote-ip-prefix 10.0.0.0/8 ' + self.METER_ID - ) - rule_id = re.search(self.re_id, raw_output).group(1) + )) + rule_id_1 = json_output.get('id') self.addCleanup(self.openstack, - 'network meter rule delete ' + rule_id) + 'network meter rule delete ' + rule_id_1) self.assertEqual( '10.0.0.0/8', - re.search(self.re_ip_prefix, raw_output).group(1) + json_output.get('remote_ip_prefix') ) - raw_output = self.openstack('network meter rule list') - self.assertIsNotNone(re.search(rule_id + "|\s+\|\s+\|\s+10.0.0.0/8", - raw_output)) + json_output_1 = json.loads(self.openstack( + 'network meter rule create -f json ' + + '--remote-ip-prefix 11.0.0.0/8 ' + + self.METER_ID + )) + rule_id_2 = json_output_1.get('id') + self.addCleanup(self.openstack, + 'network meter rule delete ' + rule_id_2) + self.assertEqual( + '11.0.0.0/8', + json_output_1.get('remote_ip_prefix') + ) + + json_output = json.loads(self.openstack('network meter rule list -f ' + 'json')) + rule_id_list = [item.get('ID') for item in json_output] + ip_prefix_list = [item.get('Remote IP Prefix') for item in json_output] + self.assertIn(rule_id_1, rule_id_list) + self.assertIn(rule_id_2, rule_id_list) + self.assertIn('10.0.0.0/8', ip_prefix_list) + self.assertIn('11.0.0.0/8', ip_prefix_list) def test_meter_rule_show(self): + """Test create, show, delete""" - raw_output = self.openstack( - 'network meter rule create ' + + json_output = json.loads(self.openstack( + 'network meter rule create -f json ' + '--remote-ip-prefix 10.0.0.0/8 ' + '--egress ' + self.METER_ID - ) - rule_id = re.search(self.re_id, raw_output).group(1) + )) + rule_id = json_output.get('id') self.assertEqual( 'egress', - re.search(self.re_direction, raw_output).group(1) + json_output.get('direction') ) - raw_output = self.openstack('network meter rule show ' + rule_id) + json_output = json.loads(self.openstack('network meter rule show' + ' -f json ' + rule_id)) self.assertEqual( '10.0.0.0/8', - re.search(self.re_ip_prefix, raw_output).group(1) + json_output.get('remote_ip_prefix') ) self.assertIsNotNone(rule_id) From 1e739d7aebe53d38038b9f6172eb08916a7dd23c Mon Sep 17 00:00:00 2001 From: Ankur Gupta Date: Thu, 9 Mar 2017 10:59:11 -0600 Subject: [PATCH 1599/3095] Trivial Fix Improperly rendered Release Note for v3.9.0 Change-Id: Ia9b920b5a3774dd9c720fe7a39fd41aaf7be209f --- releasenotes/notes/add-no-property-f97e4b2f390cec06.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/releasenotes/notes/add-no-property-f97e4b2f390cec06.yaml b/releasenotes/notes/add-no-property-f97e4b2f390cec06.yaml index ab50254908..ccf41aa341 100644 --- a/releasenotes/notes/add-no-property-f97e4b2f390cec06.yaml +++ b/releasenotes/notes/add-no-property-f97e4b2f390cec06.yaml @@ -3,4 +3,4 @@ features: - | Add support to clear/overwrite all flavor properties using ``--no-property`` option with ``flavor set`` command. - [ Blueprint `allow-overwrite-set-options ` _] + [Blueprint :oscbp:`allow-overwrite-set-options`] From 888022f8c0a2911a03fc682fdbe4c68c35a27db7 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Thu, 16 Feb 2017 18:43:31 +0800 Subject: [PATCH 1600/3095] Fix "security group list" command to display project ID properly The "Project" column of the output of "security group list" command is blank since the new attribute name is "project_id" not "tenant_id" in network resource, so change it to display project IDs properly Change-Id: Ie2a071afac3b5a8aaa2c6f1c50d44ae06905d916 Closes-bug: #1659967 --- openstackclient/network/v2/security_group.py | 50 ++++++++++++------- .../tests/unit/network/v2/fakes.py | 4 +- .../unit/network/v2/test_security_group.py | 2 +- .../notes/bug-1659967-644a8ee3621c9e81.yaml | 6 +++ 4 files changed, 41 insertions(+), 21 deletions(-) create mode 100644 releasenotes/notes/bug-1659967-644a8ee3621c9e81.yaml diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py index c6d9ede7f8..182d481779 100644 --- a/openstackclient/network/v2/security_group.py +++ b/openstackclient/network/v2/security_group.py @@ -210,21 +210,6 @@ def update_parser_compute(self, parser): ) return parser - def _get_return_data(self, data, include_project=True): - columns = ( - "ID", - "Name", - "Description", - ) - column_headers = columns - if include_project: - columns = columns + ('Tenant ID',) - column_headers = column_headers + ('Project',) - return (column_headers, - (utils.get_item_properties( - s, columns, - ) for s in data)) - def take_action_network(self, client, parsed_args): filters = {} if parsed_args.project: @@ -236,13 +221,42 @@ def take_action_network(self, client, parsed_args): ).id filters['tenant_id'] = project_id filters['project_id'] = project_id - return self._get_return_data(client.security_groups(**filters)) + data = client.security_groups(**filters) + + columns = ( + "ID", + "Name", + "Description", + "Project ID" + ) + column_headers = ( + "ID", + "Name", + "Description", + "Project" + ) + return (column_headers, + (utils.get_item_properties( + s, columns, + ) for s in data)) def take_action_compute(self, client, parsed_args): search = {'all_tenants': parsed_args.all_projects} data = client.security_groups.list(search_opts=search) - return self._get_return_data(data, - include_project=parsed_args.all_projects) + + columns = ( + "ID", + "Name", + "Description", + ) + column_headers = columns + if parsed_args.all_projects: + columns = columns + ('Tenant ID',) + column_headers = column_headers + ('Project',) + return (column_headers, + (utils.get_item_properties( + s, columns, + ) for s in data)) class SetSecurityGroup(common.NetworkAndComputeCommand): diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index 7afe332885..e0ee05b412 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -1071,7 +1071,7 @@ def create_one_security_group(attrs=None): 'id': 'security-group-id-' + uuid.uuid4().hex, 'name': 'security-group-name-' + uuid.uuid4().hex, 'description': 'security-group-description-' + uuid.uuid4().hex, - 'tenant_id': 'project-id-' + uuid.uuid4().hex, + 'project_id': 'project-id-' + uuid.uuid4().hex, 'security_group_rules': [], } @@ -1083,7 +1083,7 @@ def create_one_security_group(attrs=None): loaded=True) # Set attributes with special mapping in OpenStack SDK. - security_group.project_id = security_group_attrs['tenant_id'] + security_group.project_id = security_group_attrs['project_id'] return security_group diff --git a/openstackclient/tests/unit/network/v2/test_security_group.py b/openstackclient/tests/unit/network/v2/test_security_group.py index 9a30267ebd..66d357f98a 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group.py +++ b/openstackclient/tests/unit/network/v2/test_security_group.py @@ -404,7 +404,7 @@ class TestListSecurityGroupNetwork(TestSecurityGroupNetwork): grp.id, grp.name, grp.description, - grp.tenant_id, + grp.project_id, )) def setUp(self): diff --git a/releasenotes/notes/bug-1659967-644a8ee3621c9e81.yaml b/releasenotes/notes/bug-1659967-644a8ee3621c9e81.yaml new file mode 100644 index 0000000000..a42f9460fc --- /dev/null +++ b/releasenotes/notes/bug-1659967-644a8ee3621c9e81.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + ``security group list`` command now can display project IDs in the ``Project`` column + of the command output. + [Bug `1659967 `_] From 498d416bdd38812c045d9e911240ec02716a37c2 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Tue, 7 Mar 2017 11:53:09 -0600 Subject: [PATCH 1601/3095] Refactor ServerTests and ServerEventTests functional test classes Move common bits into a compute.v2.common.ComputeTestCase class so they are available as needed without calling into other test classes. Change-Id: I1afcc04ba705b0bbb85628117e7ca91080cf1895 --- .../tests/functional/compute/v2/common.py | 145 ++++++++++++++++++ .../functional/compute/v2/test_server.py | 117 +------------- .../compute/v2/test_server_event.py | 29 +--- 3 files changed, 156 insertions(+), 135 deletions(-) create mode 100644 openstackclient/tests/functional/compute/v2/common.py diff --git a/openstackclient/tests/functional/compute/v2/common.py b/openstackclient/tests/functional/compute/v2/common.py new file mode 100644 index 0000000000..99d87bb4b5 --- /dev/null +++ b/openstackclient/tests/functional/compute/v2/common.py @@ -0,0 +1,145 @@ +# 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 json +import time +import uuid + +from tempest.lib import exceptions + +from openstackclient.tests.functional import base + + +class ComputeTestCase(base.TestCase): + """Common functional test bits for Compute commands""" + + flavor_name = None + image_name = None + network_arg = None + + def setUp(self): + """Select common resources""" + super(ComputeTestCase, self).setUp() + self.flavor_name = self.get_flavor() + self.image_name = self.get_image() + self.network_arg = self.get_network() + + @classmethod + def get_flavor(cls): + # NOTE(rtheis): Get cirros256 or m1.tiny flavors since functional + # tests may create other flavors. + flavors = json.loads(cls.openstack( + "flavor list -f json " + )) + server_flavor = None + for flavor in flavors: + if flavor['Name'] in ['m1.tiny', 'cirros256']: + server_flavor = flavor['Name'] + break + return server_flavor + + @classmethod + def get_image(cls): + # NOTE(rtheis): Get first Cirros image since functional tests may + # create other images. Image may be named '-uec' or + # '-disk'. + images = json.loads(cls.openstack( + "image list -f json " + )) + server_image = None + for image in images: + if (image['Name'].startswith('cirros-') and + (image['Name'].endswith('-uec') or + image['Name'].endswith('-disk'))): + server_image = image['Name'] + break + return server_image + + @classmethod + def get_network(cls): + try: + # NOTE(rtheis): Get private network since functional tests may + # create other networks. + cmd_output = json.loads(cls.openstack( + 'network show private -f json' + )) + except exceptions.CommandFailed: + return '' + return '--nic net-id=' + cmd_output['id'] + + def server_create(self, name=None, cleanup=True): + """Create server, with cleanup""" + if not self.flavor_name: + self.flavor_name = self.get_flavor() + if not self.image_name: + self.image_name = self.get_image() + if not self.network_arg: + self.network_arg = self.get_network() + name = name or uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'server create -f json ' + + '--flavor ' + self.flavor_name + ' ' + + '--image ' + self.image_name + ' ' + + self.network_arg + ' ' + + '--wait ' + + name + )) + self.assertIsNotNone(cmd_output["id"]) + self.assertEqual( + name, + cmd_output["name"], + ) + if cleanup: + self.addCleanup(self.server_delete, name) + return cmd_output + + def server_delete(self, name): + """Delete server by name""" + raw_output = self.openstack('server delete ' + name) + self.assertOutput('', raw_output) + + def wait_for_status( + self, + name, + expected_status='ACTIVE', + wait=900, + interval=10, + ): + """Wait until server reaches expected status""" + # TODO(thowe): Add a server wait command to osc + failures = ['ERROR'] + total_sleep = 0 + while total_sleep < wait: + cmd_output = json.loads(self.openstack( + 'server show -f json ' + + name + )) + status = cmd_output['status'] + print('Waiting for {}, current status: {}'.format( + expected_status, + status, + )) + if status == expected_status: + break + self.assertNotIn(status, failures) + time.sleep(interval) + total_sleep += interval + + cmd_output = json.loads(self.openstack( + 'server show -f json ' + + name + )) + status = cmd_output['status'] + self.assertEqual(status, expected_status) + # give it a little bit more time + time.sleep(5) diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py index 4d8019b2f3..f152de80e9 100644 --- a/openstackclient/tests/functional/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -11,91 +11,16 @@ # under the License. import json -import time import uuid from tempest.lib import exceptions -from openstackclient.tests.functional import base +from openstackclient.tests.functional.compute.v2 import common from openstackclient.tests.functional.volume.v2 import test_volume -class ServerTests(base.TestCase): - """Functional tests for openstack server commands.""" - - @classmethod - def get_flavor(cls): - # NOTE(rtheis): Get cirros256 or m1.tiny flavors since functional - # tests may create other flavors. - flavors = json.loads(cls.openstack( - "flavor list -f json " - )) - server_flavor = None - for flavor in flavors: - if flavor['Name'] in ['m1.tiny', 'cirros256']: - server_flavor = flavor['Name'] - break - return server_flavor - - @classmethod - def get_image(cls): - # NOTE(rtheis): Get first Cirros image since functional tests may - # create other images. Image may be named '-uec' or - # '-disk'. - images = json.loads(cls.openstack( - "image list -f json " - )) - server_image = None - for image in images: - if (image['Name'].startswith('cirros-') and - (image['Name'].endswith('-uec') or - image['Name'].endswith('-disk'))): - server_image = image['Name'] - break - return server_image - - @classmethod - def get_network(cls): - try: - # NOTE(rtheis): Get private network since functional tests may - # create other networks. - cmd_output = json.loads(cls.openstack( - 'network show private -f json' - )) - except exceptions.CommandFailed: - return '' - return '--nic net-id=' + cmd_output['id'] - - def server_create(self, name=None): - """Create server, with cleanup""" - name = name or uuid.uuid4().hex - cmd_output = json.loads(self.openstack( - 'server create -f json ' + - '--flavor ' + self.flavor_name + ' ' + - '--image ' + self.image_name + ' ' + - self.network_arg + ' ' + - '--wait ' + - name - )) - if not cmd_output: - self.fail('Server has not been created!') - self.addCleanup(self.server_delete, name) - self.assertEqual( - name, - cmd_output["name"], - ) - return cmd_output - - def server_delete(self, name): - """Delete server by name""" - self.openstack('server delete ' + name) - - def setUp(self): - """Select common resources""" - super(ServerTests, self).setUp() - self.flavor_name = self.get_flavor() - self.image_name = self.get_image() - self.network_arg = self.get_network() +class ServerTests(common.ComputeTestCase): + """Functional tests for openstack server commands""" def test_server_list(self): """Test server list, set""" @@ -480,39 +405,3 @@ def test_server_create_with_empty_network_option_latest(self): e.stderr) else: self.fail('CommandFailed should be raised.') - - def wait_for_status( - self, - name, - expected_status='ACTIVE', - wait=900, - interval=10, - ): - """Wait until server reaches expected status.""" - # TODO(thowe): Add a server wait command to osc - failures = ['ERROR'] - total_sleep = 0 - while total_sleep < wait: - cmd_output = json.loads(self.openstack( - 'server show -f json ' + - name - )) - status = cmd_output['status'] - print('Waiting for {}, current status: {}'.format( - expected_status, - status, - )) - if status == expected_status: - break - self.assertNotIn(status, failures) - time.sleep(interval) - total_sleep += interval - - cmd_output = json.loads(self.openstack( - 'server show -f json ' + - name - )) - status = cmd_output['status'] - self.assertEqual(status, expected_status) - # give it a little bit more time - time.sleep(5) diff --git a/openstackclient/tests/functional/compute/v2/test_server_event.py b/openstackclient/tests/functional/compute/v2/test_server_event.py index 6be5822f98..953ade4307 100644 --- a/openstackclient/tests/functional/compute/v2/test_server_event.py +++ b/openstackclient/tests/functional/compute/v2/test_server_event.py @@ -14,34 +14,21 @@ # import json -import uuid -from openstackclient.tests.functional import base -from openstackclient.tests.functional.compute.v2 import test_server +from openstackclient.tests.functional.compute.v2 import common -class ServerEventTests(base.TestCase): - """Functional tests for server event.""" +class ServerEventTests(common.ComputeTestCase): + """Functional tests for server event""" def setUp(self): super(ServerEventTests, self).setUp() - _flavor = test_server.ServerTests.get_flavor() - _image = test_server.ServerTests.get_image() - _network = test_server.ServerTests.get_network() - self.server_name = uuid.uuid4().hex - cmd_output = json.loads(self.openstack( - 'server create -f json ' + - '--flavor ' + _flavor + ' ' + - '--image ' + _image + ' ' + - _network + ' ' + - '--wait ' + - self.server_name - )) - if not cmd_output: - self.fail('Server has not been created!') - self.addCleanup(self.openstack, 'server delete ' + self.server_name) - self.assertEqual(self.server_name, cmd_output['name']) + + # NOTE(dtroyer): As long as these tests are read-only we can get away + # with using the same server instance for all of them. + cmd_output = self.server_create() self.server_id = cmd_output.get('id') + self.server_name = cmd_output['name'] def test_server_event_list_and_show(self): """Test list, show server event""" From 85d598a9aca506ef7a9ef60bd5c85067ce1a2d7c Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Fri, 10 Mar 2017 21:51:34 +0900 Subject: [PATCH 1602/3095] Fix reference in network flavor create command reference network-flavor.rst * The opening quote of :ref: should be a backquote * Closing paranthesis was missing after the reference network-service-provider.rst * A blank line is required after a label definition Change-Id: Ie5c2bedbb6e5b4337b69a7d1ea75c47366e3d627 --- doc/source/command-objects/network-flavor.rst | 2 +- doc/source/command-objects/network-service-provider.rst | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/source/command-objects/network-flavor.rst b/doc/source/command-objects/network-flavor.rst index d5c011f683..4567682c5b 100644 --- a/doc/source/command-objects/network-flavor.rst +++ b/doc/source/command-objects/network-flavor.rst @@ -26,7 +26,7 @@ Create network flavor .. option:: --service-type Service type to which the flavor applies to: e.g. VPN. - (See openstack :ref:'network_service_provider_list` (required) + (See openstack :ref:`network_service_provider_list`) (required) .. option:: --description diff --git a/doc/source/command-objects/network-service-provider.rst b/doc/source/command-objects/network-service-provider.rst index 8ccec45505..f46073f677 100644 --- a/doc/source/command-objects/network-service-provider.rst +++ b/doc/source/command-objects/network-service-provider.rst @@ -8,6 +8,7 @@ networking service Network v2 .. _network_service_provider_list: + network service provider list ----------------------------- From 58591d3c37c0265d8775f881271ba4d987e5ffb6 Mon Sep 17 00:00:00 2001 From: Sindhu Devale Date: Thu, 29 Sep 2016 15:32:51 -0500 Subject: [PATCH 1603/3095] OSC Quota List Implement Neutron feature of Quota List into OpenStack Client. Change-Id: Idf941acf8d00b136776b7381b877c56d82622f57 Partially-Implements: blueprint neutron-client-quota --- doc/source/command-objects/quota.rst | 23 +++ openstackclient/common/quota.py | 173 ++++++++++++++++++ .../tests/functional/common/test_quota.py | 21 +++ .../tests/unit/common/test_quota.py | 159 ++++++++++++++++ .../tests/unit/compute/v2/fakes.py | 64 +++++++ .../tests/unit/network/v2/fakes.py | 50 +++++ openstackclient/tests/unit/volume/v2/fakes.py | 50 +++++ ...d-quota-list-command-0d865fac61db2430.yaml | 6 + setup.cfg | 1 + 9 files changed, 547 insertions(+) create mode 100644 releasenotes/notes/add-quota-list-command-0d865fac61db2430.yaml diff --git a/doc/source/command-objects/quota.rst b/doc/source/command-objects/quota.rst index 70e28dd8cf..f39536af8e 100644 --- a/doc/source/command-objects/quota.rst +++ b/doc/source/command-objects/quota.rst @@ -7,6 +7,29 @@ single object with multiple properties. Block Storage v1, v2, Compute v2, Network v2 +quota list +---------- + +List quotas for all projects with non-default quota values + +.. program:: quota list +.. code:: bash + + openstack quota list + --compute | --network | --volume + +.. option:: --network + + List network quotas + +.. option:: --compute + + List compute quotas + +.. option:: --volume + + List volume quotas + quota set --------- diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index afc6195f93..b62d661c58 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -16,6 +16,7 @@ """Quota action implementations""" import itertools +import logging import sys from osc_lib.command import command @@ -25,6 +26,8 @@ from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + # List the quota items, map the internal argument name to the option # name that the user sees. @@ -78,6 +81,176 @@ 'l7policy': 'l7policies', } +NETWORK_KEYS = ['floating_ips', 'networks', 'rbac_policies', 'routers', + 'ports', 'security_group_rules', 'security_groups', + 'subnet_pools', 'subnets'] + + +def _xform_get_quota(data, value, keys): + res = [] + res_info = {} + for key in keys: + res_info[key] = getattr(data, key, '') + + res_info['id'] = value + res.append(res_info) + return res + + +class ListQuota(command.Lister): + _description = _("List quotas for all projects " + "with non-default quota values") + + def get_parser(self, prog_name): + parser = super(ListQuota, self).get_parser(prog_name) + option = parser.add_mutually_exclusive_group(required=True) + option.add_argument( + '--compute', + action='store_true', + default=False, + help=_('List compute quota'), + ) + option.add_argument( + '--volume', + action='store_true', + default=False, + help=_('List volume quota'), + ) + option.add_argument( + '--network', + action='store_true', + default=False, + help=_('List network quota'), + ) + return parser + + def take_action(self, parsed_args): + projects = self.app.client_manager.identity.projects.list() + result = [] + project_ids = [getattr(p, 'id', '') for p in projects] + + if parsed_args.compute: + compute_client = self.app.client_manager.compute + for p in project_ids: + data = compute_client.quotas.get(p) + result_data = _xform_get_quota(data, p, + COMPUTE_QUOTAS.keys()) + default_data = compute_client.quotas.defaults(p) + result_default = _xform_get_quota(default_data, + p, + COMPUTE_QUOTAS.keys()) + if result_default != result_data: + result += result_data + + columns = ( + 'id', + 'cores', + 'fixed_ips', + 'injected_files', + 'injected_file_content_bytes', + 'injected_file_path_bytes', + 'instances', + 'key_pairs', + 'metadata_items', + 'ram', + 'server_groups', + 'server_group_members', + ) + column_headers = ( + 'Project ID', + 'Cores', + 'Fixed IPs', + 'Injected Files', + 'Injected File Content Bytes', + 'Injected File Path Bytes', + 'Instances', + 'Key Pairs', + 'Metadata Items', + 'Ram', + 'Server Groups', + 'Server Group Members', + ) + return (column_headers, + (utils.get_dict_properties( + s, columns, + ) for s in result)) + if parsed_args.volume: + volume_client = self.app.client_manager.volume + for p in project_ids: + data = volume_client.quotas.get(p) + result_data = _xform_get_quota(data, p, + VOLUME_QUOTAS.keys()) + default_data = volume_client.quotas.defaults(p) + result_default = _xform_get_quota(default_data, + p, + VOLUME_QUOTAS.keys()) + if result_default != result_data: + result += result_data + + columns = ( + 'id', + 'backups', + 'backup_gigabytes', + 'gigabytes', + 'per_volume_gigabytes', + 'snapshots', + 'volumes', + ) + column_headers = ( + 'Project ID', + 'Backups', + 'Backup Gigabytes', + 'Gigabytes', + 'Per Volume Gigabytes', + 'Snapshots', + 'Volumes', + ) + return (column_headers, + (utils.get_dict_properties( + s, columns, + ) for s in result)) + if parsed_args.network: + client = self.app.client_manager.network + for p in project_ids: + data = client.get_quota(p) + result_data = _xform_get_quota(data, p, NETWORK_KEYS) + default_data = client.get_quota_default(p) + result_default = _xform_get_quota(default_data, + p, NETWORK_KEYS) + if result_default != result_data: + result += result_data + + columns = ( + 'id', + 'floating_ips', + 'networks', + 'ports', + 'rbac_policies', + 'routers', + 'security_groups', + 'security_group_rules', + 'subnets', + 'subnet_pools', + ) + column_headers = ( + 'Project ID', + 'Floating IPs', + 'Networks', + 'Ports', + 'RBAC Policies', + 'Routers', + 'Security Groups', + 'Security Group Rules', + 'Subnets', + 'Subnet Pools' + ) + return (column_headers, + (utils.get_dict_properties( + s, columns, + ) for s in result)) + + return ((), ()) + class SetQuota(command.Command): _description = _("Set quotas for project or class") diff --git a/openstackclient/tests/functional/common/test_quota.py b/openstackclient/tests/functional/common/test_quota.py index c1de9aa92d..8092b3cee8 100644 --- a/openstackclient/tests/functional/common/test_quota.py +++ b/openstackclient/tests/functional/common/test_quota.py @@ -25,6 +25,27 @@ def setUpClass(cls): cls.PROJECT_NAME =\ cls.get_openstack_configuration_value('auth.project_name') + def test_quota_list_network_option(self): + self.openstack('quota set --networks 40 ' + + self.PROJECT_NAME) + raw_output = self.openstack('quota list --network') + self.assertIsNotNone(raw_output) + self.assertIn("40", raw_output) + + def test_quota_list_compute_option(self): + self.openstack('quota set --instances 40 ' + + self.PROJECT_NAME) + raw_output = self.openstack('quota list --compute') + self.assertIsNotNone(raw_output) + self.assertIn("40", raw_output) + + def test_quota_list_volume_option(self): + self.openstack('quota set --backups 40 ' + + self.PROJECT_NAME) + raw_output = self.openstack('quota list --volume') + self.assertIsNotNone(raw_output) + self.assertIn("40", raw_output) + def test_quota_set(self): self.openstack('quota set --instances 11 --volumes 11 --networks 11 ' + self.PROJECT_NAME) diff --git a/openstackclient/tests/unit/common/test_quota.py b/openstackclient/tests/unit/common/test_quota.py index 244d74d25d..306615bddf 100644 --- a/openstackclient/tests/unit/common/test_quota.py +++ b/openstackclient/tests/unit/common/test_quota.py @@ -19,6 +19,7 @@ from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes from openstackclient.tests.unit import fakes from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3 from openstackclient.tests.unit.network.v2 import fakes as network_fakes from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes @@ -530,3 +531,161 @@ def test_quota_show_no_project(self): self.network.get_quota.assert_called_once_with( identity_fakes.project_id) self.assertNotCalled(self.network.get_quota_default) + + +class TestQuotaList(TestQuota): + """Test cases for quota list command""" + + project = identity_fakes_v3.FakeProject.create_one_project() + + quota_list = network_fakes.FakeQuota.create_one_net_quota() + quota_list1 = compute_fakes.FakeQuota.create_one_comp_quota() + quota_list2 = volume_fakes.FakeQuota.create_one_vol_quota() + + default_quota = network_fakes.FakeQuota.create_one_default_net_quota() + default_quota1 = compute_fakes.FakeQuota.create_one_default_comp_quota() + default_quota2 = volume_fakes.FakeQuota.create_one_default_vol_quota() + + reference_data = (project.id, + quota_list.floating_ips, + quota_list.networks, + quota_list.ports, + quota_list.rbac_policies, + quota_list.routers, + quota_list.security_groups, + quota_list.security_group_rules, + quota_list.subnets, + quota_list.subnet_pools) + + comp_reference_data = (project.id, + quota_list1.cores, + quota_list1.fixed_ips, + quota_list1.injected_files, + quota_list1.injected_file_content_bytes, + quota_list1.injected_file_path_bytes, + quota_list1.instances, + quota_list1.key_pairs, + quota_list1.metadata_items, + quota_list1.ram, + quota_list1.server_groups, + quota_list1.server_group_members) + + vol_reference_data = (project.id, + quota_list2.backups, + quota_list2.backup_gigabytes, + quota_list2.gigabytes, + quota_list2.per_volume_gigabytes, + quota_list2.snapshots, + quota_list2.volumes) + + net_column_header = ( + 'Project ID', + 'Floating IPs', + 'Networks', + 'Ports', + 'RBAC Policies', + 'Routers', + 'Security Groups', + 'Security Group Rules', + 'Subnets', + 'Subnet Pools' + ) + + comp_column_header = ( + 'Project ID', + 'Cores', + 'Fixed IPs', + 'Injected Files', + 'Injected File Content Bytes', + 'Injected File Path Bytes', + 'Instances', + 'Key Pairs', + 'Metadata Items', + 'Ram', + 'Server Groups', + 'Server Group Members', + ) + + vol_column_header = ( + 'Project ID', + 'Backups', + 'Backup Gigabytes', + 'Gigabytes', + 'Per Volume Gigabytes', + 'Snapshots', + 'Volumes', + ) + + def setUp(self): + super(TestQuotaList, self).setUp() + + self.projects_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.PROJECT), + loaded=True, + ) + + self.identity = self.app.client_manager.identity + self.identity.tenants.list = mock.Mock(return_value=[self.project]) + + self.network = self.app.client_manager.network + self.compute = self.app.client_manager.compute + self.volume = self.app.client_manager.volume + + self.network.get_quota = mock.Mock(return_value=self.quota_list) + self.compute.quotas.get = mock.Mock(return_value=self.quota_list1) + self.volume.quotas.get = mock.Mock(return_value=self.quota_list2) + + self.network.get_quota_default = mock.Mock( + return_value=self.default_quota) + self.compute.quotas.defaults = mock.Mock( + return_value=self.default_quota1) + self.volume.quotas.defaults = mock.Mock( + return_value=self.default_quota2) + + self.cmd = quota.ListQuota(self.app, None) + + def test_quota_list_network(self): + arglist = [ + '--network' + ] + verifylist = [ + ('network', True) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual(self.net_column_header, columns) + + self.assertEqual(self.reference_data, list(data)[0]) + + def test_quota_list_compute(self): + arglist = [ + '--compute' + ] + verifylist = [ + ('compute', True) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual(self.comp_column_header, columns) + + self.assertEqual(self.comp_reference_data, list(data)[0]) + + def test_quota_list_volume(self): + arglist = [ + '--volume' + ] + verifylist = [ + ('volume', True) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual(self.vol_column_header, columns) + + self.assertEqual(self.vol_reference_data, list(data)[0]) diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index 4fe735b6b4..63b98a2a23 100644 --- a/openstackclient/tests/unit/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -1313,3 +1313,67 @@ def create_usages(attrs=None, count=2): usages.append(FakeUsage.create_one_usage(attrs)) return usages + + +class FakeQuota(object): + """Fake quota""" + + @staticmethod + def create_one_comp_quota(attrs=None): + """Create one quota""" + + attrs = attrs or {} + + quota_attrs = { + 'id': 'project-id-' + uuid.uuid4().hex, + 'cores': 20, + 'fixed_ips': 30, + 'injected_files': 100, + 'injected_file_content_bytes': 10240, + 'injected_file_path_bytes': 255, + 'instances': 50, + 'key_pairs': 20, + 'metadata_items': 10, + 'ram': 51200, + 'server_groups': 10, + 'server_group_members': 10 + } + + quota_attrs.update(attrs) + quota = fakes.FakeResource( + info=copy.deepcopy(quota_attrs), + loaded=True) + + quota.project_id = quota_attrs['id'] + + return quota + + @staticmethod + def create_one_default_comp_quota(attrs=None): + """Crate one quota""" + + attrs = attrs or {} + + quota_attrs = { + 'id': 'project-id-' + uuid.uuid4().hex, + 'cores': 10, + 'fixed_ips': 10, + 'injected_files': 100, + 'injected_file_content_bytes': 10240, + 'injected_file_path_bytes': 255, + 'instances': 20, + 'key_pairs': 20, + 'metadata_items': 10, + 'ram': 51200, + 'server_groups': 10, + 'server_group_members': 10 + } + + quota_attrs.update(attrs) + quota = fakes.FakeResource( + info=copy.deepcopy(quota_attrs), + loaded=True) + + quota.project_id = quota_attrs['id'] + + return quota diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index dcecbeee80..d875668855 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -1494,3 +1494,53 @@ def create_network_service_providers(attrs=None, count=2): create_one_network_service_provider( attrs)) return service_providers + + +class FakeQuota(object): + """Fake quota""" + + @staticmethod + def create_one_net_quota(attrs=None): + """Create one quota""" + attrs = attrs or {} + + quota_attrs = { + 'floating_ips': 20, + 'networks': 25, + 'ports': 11, + 'rbac_policies': 15, + 'routers': 40, + 'security_groups': 10, + 'security_group_rules': 100, + 'subnets': 20, + 'subnet_pools': 30} + + quota_attrs.update(attrs) + + quota = fakes.FakeResource( + info=copy.deepcopy(quota_attrs), + loaded=True) + return quota + + @staticmethod + def create_one_default_net_quota(attrs=None): + """Create one quota""" + attrs = attrs or {} + + quota_attrs = { + 'floatingip': 30, + 'network': 20, + 'port': 10, + 'rbac_policy': 25, + 'router': 30, + 'security_group': 30, + 'security_group_rule': 200, + 'subnet': 10, + 'subnetpool': 20} + + quota_attrs.update(attrs) + + quota = fakes.FakeResource( + info=copy.deepcopy(quota_attrs), + loaded=True) + return quota diff --git a/openstackclient/tests/unit/volume/v2/fakes.py b/openstackclient/tests/unit/volume/v2/fakes.py index d54faec7b3..d321c71a27 100644 --- a/openstackclient/tests/unit/volume/v2/fakes.py +++ b/openstackclient/tests/unit/volume/v2/fakes.py @@ -954,3 +954,53 @@ def create_one_encryption_type(attrs=None): info=copy.deepcopy(encryption_info), loaded=True) return encryption_type + + +class FakeQuota(object): + """Fake quota""" + + @staticmethod + def create_one_vol_quota(attrs=None): + """Create one quota""" + attrs = attrs or {} + + quota_attrs = { + 'id': 'project-id-' + uuid.uuid4().hex, + 'backups': 100, + 'backup_gigabytes': 100, + 'gigabytes': 10, + 'per_volume_gigabytes': 10, + 'snapshots': 0, + 'volumes': 10} + + quota_attrs.update(attrs) + + quota = fakes.FakeResource( + info=copy.deepcopy(quota_attrs), + loaded=True) + quota.project_id = quota_attrs['id'] + + return quota + + @staticmethod + def create_one_default_vol_quota(attrs=None): + """Create one quota""" + attrs = attrs or {} + + quota_attrs = { + 'id': 'project-id-' + uuid.uuid4().hex, + 'backups': 100, + 'backup_gigabytes': 100, + 'gigabytes': 100, + 'per_volume_gigabytes': 100, + 'snapshots': 100, + 'volumes': 100} + + quota_attrs.update(attrs) + + quota = fakes.FakeResource( + info=copy.deepcopy(quota_attrs), + loaded=True) + quota.project_id = quota_attrs['id'] + + return quota diff --git a/releasenotes/notes/add-quota-list-command-0d865fac61db2430.yaml b/releasenotes/notes/add-quota-list-command-0d865fac61db2430.yaml new file mode 100644 index 0000000000..49a917b575 --- /dev/null +++ b/releasenotes/notes/add-quota-list-command-0d865fac61db2430.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``quota list`` command with ``--compute``, ``--volume`` + and ``--network`` options. + [Blueprint `quota-list `_] diff --git a/setup.cfg b/setup.cfg index aa81559e4c..f6d1c453b3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -47,6 +47,7 @@ openstack.common = configuration_show = openstackclient.common.configuration:ShowConfiguration extension_list = openstackclient.common.extension:ListExtension limits_show = openstackclient.common.limits:ShowLimits + quota_list = openstackclient.common.quota:ListQuota quota_set = openstackclient.common.quota:SetQuota quota_show = openstackclient.common.quota:ShowQuota From 1a5704d22e3f2a51f8abe78abfa52cbe28c5ee45 Mon Sep 17 00:00:00 2001 From: "jiahui.qiang" Date: Sat, 21 Jan 2017 21:27:48 +0800 Subject: [PATCH 1604/3095] Functional test for ip_availability Refactor ip_availability functional tests. Change-Id: I2397bd20236e1e9e3c69177ea6afbaadf2c445ae --- .../network/v2/test_ip_availability.py | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/openstackclient/tests/functional/network/v2/test_ip_availability.py b/openstackclient/tests/functional/network/v2/test_ip_availability.py index b5c908f44d..7440f2507c 100644 --- a/openstackclient/tests/functional/network/v2/test_ip_availability.py +++ b/openstackclient/tests/functional/network/v2/test_ip_availability.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import json import uuid from openstackclient.tests.functional import base @@ -17,22 +18,19 @@ class IPAvailabilityTests(base.TestCase): """Functional tests for IP availability. """ - NAME = uuid.uuid4().hex - NETWORK_NAME = uuid.uuid4().hex - FIELDS = ['network_name'] @classmethod def setUpClass(cls): # Create a network for the subnet. + cls.NAME = uuid.uuid4().hex + cls.NETWORK_NAME = uuid.uuid4().hex cls.openstack('network create ' + cls.NETWORK_NAME) - opts = cls.get_opts(['name']) - raw_output = cls.openstack( - 'subnet create --network ' + cls.NETWORK_NAME + + cmd_output = json.loads(cls.openstack( + 'subnet create -f json --network ' + cls.NETWORK_NAME + ' --subnet-range 10.10.10.0/24 ' + - cls.NAME + opts - ) - expected = cls.NAME + '\n' - cls.assertOutput(expected, raw_output) + cls.NAME + )) + cls.assertOutput(cls.NAME, cmd_output['name']) @classmethod def tearDownClass(cls): @@ -42,12 +40,17 @@ def tearDownClass(cls): cls.assertOutput('', raw_network) def test_ip_availability_list(self): - opts = ' -f csv -c "Network Name"' - raw_output = self.openstack('ip availability list' + opts) - self.assertIn(self.NETWORK_NAME, raw_output) + """Test ip availability list""" + cmd_output = json.loads(self.openstack( + 'ip availability list -f json')) + names = [x['Network Name'] for x in cmd_output] + self.assertIn(self.NETWORK_NAME, names) def test_ip_availability_show(self): - opts = self.get_opts(self.FIELDS) - raw_output = self.openstack( - 'ip availability show ' + self.NETWORK_NAME + opts) - self.assertEqual(self.NETWORK_NAME + "\n", raw_output) + """Test ip availability show""" + cmd_output = json.loads(self.openstack( + 'ip availability show -f json ' + self.NETWORK_NAME)) + self.assertEqual( + self.NETWORK_NAME, + cmd_output['network_name'], + ) From 429b43a331cd0370f35bcc63cdb54889f72e2a1b Mon Sep 17 00:00:00 2001 From: Jose Castro Leon Date: Mon, 13 Mar 2017 15:18:30 +0100 Subject: [PATCH 1605/3095] Adds missing flavor information in the server list long command Closes-Bug: #1672396 Change-Id: Ie2a664fd1c3db1b8269ea079df181f87afc702a7 --- openstackclient/compute/v2/server.py | 27 +++++++++++++++++-- .../tests/unit/compute/v2/test_server.py | 10 +++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index d33c631a17..cd045a4050 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -917,6 +917,8 @@ def take_action(self, parsed_args): 'Networks', 'Image Name', 'Image ID', + 'Flavor Name', + 'Flavor ID', 'OS-EXT-AZ:availability_zone', 'OS-EXT-SRV-ATTR:host', 'Metadata', @@ -930,6 +932,8 @@ def take_action(self, parsed_args): 'Networks', 'Image Name', 'Image ID', + 'Flavor Name', + 'Flavor ID', 'Availability Zone', 'Host', 'Properties', @@ -977,8 +981,19 @@ def take_action(self, parsed_args): except Exception: pass - # Populate image_name and image_id attributes of server objects - # so that we can display "Image Name" and "Image ID" columns. + flavors = {} + # Create a dict that maps flavor_id to flavor object. + # Needed so that we can display the "Flavor Name" column. + # "Flavor Name" is not crucial, so we swallow any exceptions. + try: + flavors_list = compute_client.flavors.list() + for i in flavors_list: + flavors[i.id] = i + except Exception: + pass + + # Populate image_name, image_id, flavor_name and flavor_id attributes + # of server objects so that we can display those columns. for s in data: if 'id' in s.image: image = images.get(s.image['id']) @@ -988,6 +1003,14 @@ def take_action(self, parsed_args): else: s.image_name = '' s.image_id = '' + if 'id' in s.flavor: + flavor = flavors.get(s.flavor['id']) + if flavor: + s.flavor_name = flavor.name + s.flavor_id = s.flavor['id'] + else: + s.flavor_name = '' + s.flavor_id = '' table = (column_headers, (utils.get_item_properties( diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 249902bca4..8aba177e2b 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -957,6 +957,8 @@ class TestServerList(TestServer): 'Networks', 'Image Name', 'Image ID', + 'Flavor Name', + 'Flavor ID', 'Availability Zone', 'Host', 'Properties', @@ -1027,6 +1029,12 @@ def setUp(self): for s in self.servers ] + Flavor = collections.namedtuple('Flavor', 'id name') + self.flavors_mock.list.return_value = [ + Flavor(id=s.flavor['id'], name=self.flavor.name) + for s in self.servers + ] + for s in self.servers: self.data.append(( s.id, @@ -1046,6 +1054,8 @@ def setUp(self): server._format_servers_list_networks(s.networks), self.image.name, s.image['id'], + self.flavor.name, + s.flavor['id'], getattr(s, 'OS-EXT-AZ:availability_zone'), getattr(s, 'OS-EXT-SRV-ATTR:host'), s.Metadata, From 9fd3dba11e5fc60023a9c332cfb76b42d38adf05 Mon Sep 17 00:00:00 2001 From: lvjiawei Date: Wed, 23 Nov 2016 09:26:14 -0500 Subject: [PATCH 1606/3095] Add extra filtering options to qos policy list The patch adds filtering "--project", "--project-domain", "--share", "--no-share" options to qos policy list. Change-Id: I5c012fb27fb952f736ddc9fbc54ef6da4d0af5e0 Partially-Implements: blueprint network-commands-options --- .../command-objects/network-qos-policy.rst | 19 +++++++ .../network/v2/network_qos_policy.py | 29 ++++++++-- .../network/v2/test_network_qos_policy.py | 53 +++++++++++++++++++ ...-policy-list-options-9ba1ae731a88e7ac.yaml | 5 ++ 4 files changed, 102 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/add-qos-policy-list-options-9ba1ae731a88e7ac.yaml diff --git a/doc/source/command-objects/network-qos-policy.rst b/doc/source/command-objects/network-qos-policy.rst index 7ec6776c97..a75c32fe7b 100644 --- a/doc/source/command-objects/network-qos-policy.rst +++ b/doc/source/command-objects/network-qos-policy.rst @@ -73,6 +73,25 @@ List Network QoS policies .. code:: bash openstack network qos policy list + [--project [--project-domain ]] + [--share | --no-share] + +.. option:: --project + + List qos policies according to their project (name or ID) + +.. option:: --project-domain + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + +.. option:: --share + + List qos policies shared between projects + +.. option:: --no-share + + List qos policies not shared between projects network qos policy set ---------------------- diff --git a/openstackclient/network/v2/network_qos_policy.py b/openstackclient/network/v2/network_qos_policy.py index 5ccbe36b03..fef3ec88a2 100644 --- a/openstackclient/network/v2/network_qos_policy.py +++ b/openstackclient/network/v2/network_qos_policy.py @@ -37,9 +37,9 @@ def _get_columns(item): def _get_attrs(client_manager, parsed_args): attrs = {} - if parsed_args.name is not None: + if 'name' in parsed_args and parsed_args.name is not None: attrs['name'] = str(parsed_args.name) - if parsed_args.description is not None: + if 'description' in parsed_args and parsed_args.description is not None: attrs['description'] = parsed_args.description if parsed_args.share: attrs['shared'] = True @@ -143,6 +143,27 @@ def take_action(self, parsed_args): class ListNetworkQosPolicy(command.Lister): _description = _("List QoS policies") + def get_parser(self, prog_name): + parser = super(ListNetworkQosPolicy, self).get_parser(prog_name) + parser.add_argument( + '--project', + metavar='', + help=_("List qos policies according to their project (name or ID)") + ) + identity_common.add_project_domain_option_to_parser(parser) + shared_group = parser.add_mutually_exclusive_group() + shared_group.add_argument( + '--share', + action='store_true', + help=_("List qos policies shared between projects") + ) + shared_group.add_argument( + '--no-share', + action='store_true', + help=_("List qos policies not shared between projects") + ) + return parser + def take_action(self, parsed_args): client = self.app.client_manager.network columns = ( @@ -157,8 +178,8 @@ def take_action(self, parsed_args): 'Shared', 'Project', ) - data = client.qos_policies() - + attrs = _get_attrs(self.app.client_manager, parsed_args) + data = client.qos_policies(**attrs) return (column_headers, (utils.get_item_properties( s, columns, formatters={}, diff --git a/openstackclient/tests/unit/network/v2/test_network_qos_policy.py b/openstackclient/tests/unit/network/v2/test_network_qos_policy.py index bd30579af7..667f501514 100644 --- a/openstackclient/tests/unit/network/v2/test_network_qos_policy.py +++ b/openstackclient/tests/unit/network/v2/test_network_qos_policy.py @@ -250,6 +250,59 @@ def test_qos_policy_list(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + def test_qos_policy_list_share(self): + arglist = [ + '--share', + ] + verifylist = [ + ('share', True), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.qos_policies.assert_called_once_with( + **{'shared': True} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_qos_policy_list_no_share(self): + arglist = [ + '--no-share', + ] + verifylist = [ + ('no_share', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.qos_policies.assert_called_once_with( + **{'shared': False} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_network_qos_list_project(self): + project = identity_fakes_v3.FakeProject.create_one_project() + self.projects_mock.get.return_value = project + arglist = [ + '--project', project.id, + '--project-domain', project.domain_id, + ] + verifylist = [ + ('project', project.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.network.qos_policies.assert_called_once_with( + **{'tenant_id': project.id} + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + class TestSetNetworkQosPolicy(TestQosPolicy): diff --git a/releasenotes/notes/add-qos-policy-list-options-9ba1ae731a88e7ac.yaml b/releasenotes/notes/add-qos-policy-list-options-9ba1ae731a88e7ac.yaml new file mode 100644 index 0000000000..ab832ce566 --- /dev/null +++ b/releasenotes/notes/add-qos-policy-list-options-9ba1ae731a88e7ac.yaml @@ -0,0 +1,5 @@ +--- +features: + - Add ``--share``, ``--no-share``, ``--project``, ``--project-domain`` + options to ``qos policy list`` command. + [Blueprint `network-commands-options `_] From 853ea5ab59e5d7845d389e46527038575c3c170c Mon Sep 17 00:00:00 2001 From: Colleen Murphy Date: Tue, 14 Mar 2017 01:24:31 +0100 Subject: [PATCH 1607/3095] Narrow expected responses for CheckUserInGroup When checking whether a given user is in a given group, keystone will return a 404 Not Found if all went well but the user was not in the group. It may also return a 403 if the user and the group are in different backends, which would also mean that the user was not in the group[1]. Any other 400 response is a client error and any 500 response is a server error to which the user should be alerted. Without this patch, openstackclient treats any exception as a valid "not found" and may end up hiding server errors. This patch reduces the caught exceptions to 403 and 404 responses and treats everything else as an error. [1] https://developer.openstack.org/api-ref/identity/v3/?expanded=check-whether-user-belongs-to-group-detail#check-whether-user-belongs-to-group Closes-bug: #1672634 Change-Id: Id3f3b2409b7cee480ee3c19b6d6c3070599ffe8f --- openstackclient/identity/v3/group.py | 15 +++++++++------ .../tests/unit/identity/v3/test_group.py | 17 +++++++++++++++++ .../notes/bug-1672634-ef754cb5109dd0f2.yaml | 5 +++++ 3 files changed, 31 insertions(+), 6 deletions(-) create mode 100644 releasenotes/notes/bug-1672634-ef754cb5109dd0f2.yaml diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py index 2afdabc171..b5f5d8ad88 100644 --- a/openstackclient/identity/v3/group.py +++ b/openstackclient/identity/v3/group.py @@ -102,12 +102,15 @@ def take_action(self, parsed_args): try: identity_client.users.check_in_group(user_id, group_id) - except Exception: - msg = _("%(user)s not in group %(group)s\n") % { - 'user': parsed_args.user, - 'group': parsed_args.group, - } - sys.stderr.write(msg) + except ks_exc.http.HTTPClientError as e: + if e.http_status == 403 or e.http_status == 404: + msg = _("%(user)s not in group %(group)s\n") % { + 'user': parsed_args.user, + 'group': parsed_args.group, + } + sys.stderr.write(msg) + else: + raise e else: msg = _("%(user)s in group %(group)s\n") % { 'user': parsed_args.user, diff --git a/openstackclient/tests/unit/identity/v3/test_group.py b/openstackclient/tests/unit/identity/v3/test_group.py index 00bd217dad..5870e1dbc3 100644 --- a/openstackclient/tests/unit/identity/v3/test_group.py +++ b/openstackclient/tests/unit/identity/v3/test_group.py @@ -115,6 +115,23 @@ def test_group_check_user(self): self.user.id, self.group.id) self.assertIsNone(result) + def test_group_check_user_server_error(self): + def server_error(*args): + raise ks_exc.http.InternalServerError + self.users_mock.check_in_group.side_effect = server_error + arglist = [ + self.group.name, + self.user.name, + ] + verifylist = [ + ('group', self.group.name), + ('user', self.user.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises(ks_exc.http.InternalServerError, + self.cmd.take_action, parsed_args) + class TestGroupCreate(TestGroup): diff --git a/releasenotes/notes/bug-1672634-ef754cb5109dd0f2.yaml b/releasenotes/notes/bug-1672634-ef754cb5109dd0f2.yaml new file mode 100644 index 0000000000..be874ab616 --- /dev/null +++ b/releasenotes/notes/bug-1672634-ef754cb5109dd0f2.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Narrow acceptable negative response codes for ``group contains user`` + [Bug `1672634 `_] From ad5b57fd19d08bb16c539a042f0a48653b700b4a Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 16 Mar 2017 22:00:54 +0000 Subject: [PATCH 1608/3095] Updated from global requirements Change-Id: I5d0e7942a45e6fed6d387250734e882335fa3de4 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 8688e2ab9b..6d8ca54b5b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,4 +14,4 @@ oslo.utils>=3.20.0 # Apache-2.0 python-glanceclient>=2.5.0 # Apache-2.0 python-keystoneclient>=3.8.0 # Apache-2.0 python-novaclient>=7.1.0 # Apache-2.0 -python-cinderclient!=1.7.0,!=1.7.1,>=1.6.0 # Apache-2.0 +python-cinderclient>=2.0.1 # Apache-2.0 From 49f6032b699804b1b0ed56137ab14ba266251157 Mon Sep 17 00:00:00 2001 From: adrian-turjak Date: Mon, 26 Sep 2016 13:06:42 +1300 Subject: [PATCH 1609/3095] Non-Admin can't list own projects Due to a default Keystone policy until Newtown, and the use of resource_find, non-admins are unable to list their own projects. This patch bypasses this problem while also introducing better UX for non-admins wishing to get their project list. 'openstack project list' retains the default of 'list all projects' but on a forbidden error will default instead to 'list my projects'. This way for non-admins 'list my projects' feels like the default without breaking the expected admin default. Adding the '--my-projects' option allows admins to easily list their own projects or allows non-admins to be explicit and bypass the forbidden error fallback. Change-Id: I1021276f69fbbf28e13e17c4e567d932fce7ed8b Closes-Bug: #1627555 --- doc/source/command-objects/project.rst | 7 +++++ openstackclient/identity/v3/project.py | 24 ++++++++++++++- .../tests/unit/identity/v3/test_project.py | 30 +++++++++++++++++++ .../notes/bug-1627555-3b47eba215e35b3c.yaml | 9 ++++++ 4 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/bug-1627555-3b47eba215e35b3c.yaml diff --git a/doc/source/command-objects/project.rst b/doc/source/command-objects/project.rst index 018cea3e0e..cb0941ca12 100644 --- a/doc/source/command-objects/project.rst +++ b/doc/source/command-objects/project.rst @@ -95,6 +95,7 @@ List projects openstack project list [--domain ] [--user ] + [--my-projects] [--long] [--sort [:,:,..]] @@ -110,6 +111,12 @@ List projects .. versionadded:: 3 +.. option:: --my-projects + + List projects for the authenticated user. Supersedes other filters. + + .. versionadded:: 3 + .. option:: --long List additional fields in output diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index 473dda1a20..873ee9c73e 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -188,6 +188,12 @@ def get_parser(self, prog_name): metavar='', help=_('Filter projects by (name or ID)'), ) + parser.add_argument( + '--my-projects', + action='store_true', + help=_('List projects for the authenticated user. ' + 'Supersedes other filters.'), + ) parser.add_argument( '--long', action='store_true', @@ -228,9 +234,25 @@ def take_action(self, parsed_args): kwargs['user'] = user_id - data = identity_client.projects.list(**kwargs) + if parsed_args.my_projects: + # NOTE(adriant): my-projects supersedes all the other filters. + kwargs = {'user': self.app.client_manager.auth_ref.user_id} + + try: + data = identity_client.projects.list(**kwargs) + except ks_exc.Forbidden: + # NOTE(adriant): if no filters, assume a forbidden is non-admin + # wanting their own project list. + if not kwargs: + user = self.app.client_manager.auth_ref.user_id + data = identity_client.projects.list( + user=user) + else: + raise + if parsed_args.sort: data = utils.sort_items(data, parsed_args.sort) + return (columns, (utils.get_item_properties( s, columns, diff --git a/openstackclient/tests/unit/identity/v3/test_project.py b/openstackclient/tests/unit/identity/v3/test_project.py index a27bf2a509..7be81153c4 100644 --- a/openstackclient/tests/unit/identity/v3/test_project.py +++ b/openstackclient/tests/unit/identity/v3/test_project.py @@ -617,6 +617,36 @@ def test_project_list_sort(self): self.assertEqual(datalists, tuple(data)) + def test_project_list_my_projects(self): + auth_ref = identity_fakes.fake_auth_ref( + identity_fakes.TOKEN_WITH_PROJECT_ID, + ) + ar_mock = mock.PropertyMock(return_value=auth_ref) + type(self.app.client_manager).auth_ref = ar_mock + + arglist = [ + '--my-projects', + ] + verifylist = [ + ('my_projects', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + self.projects_mock.list.assert_called_with( + user=self.app.client_manager.auth_ref.user_id) + + collist = ('ID', 'Name') + self.assertEqual(collist, columns) + datalist = (( + self.project.id, + self.project.name, + ), ) + self.assertEqual(datalist, tuple(data)) + class TestProjectSet(TestProject): diff --git a/releasenotes/notes/bug-1627555-3b47eba215e35b3c.yaml b/releasenotes/notes/bug-1627555-3b47eba215e35b3c.yaml new file mode 100644 index 0000000000..6000905f3d --- /dev/null +++ b/releasenotes/notes/bug-1627555-3b47eba215e35b3c.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + The ``project list`` command lists all projects when called by an + admin user. For non-admin users it will now list projects for the + authenticated user instead of exiting with an authorization failure. + The ``--my-projects`` option has also been added to the ``project list`` + command to allow admin users to list their own projects. + [Bug `1627555 `_] From f1345dc06f91177ced17f102bcdaaa126fe12568 Mon Sep 17 00:00:00 2001 From: Bence Romsics Date: Tue, 7 Mar 2017 15:54:31 +0100 Subject: [PATCH 1610/3095] Make MAC address of port updatable openstackclient does not allow the update of a port's MAC address. However this is possible in neutron API (though by default policy it is an admin-only operation). Allow it in openstackclient too. Change-Id: Ibd9e0a6fbd1d0d461b8a8daee24dbb7c3f929df6 Closes-Bug: #1670707 --- doc/source/command-objects/port.rst | 5 +++++ openstackclient/network/v2/port.py | 14 +++++++------- .../tests/functional/network/v2/test_port.py | 19 +++++++++++++++++++ .../tests/unit/network/v2/test_port.py | 19 +++++++++++++++++++ .../notes/bug-1670707-c4799fbed39ef75b.yaml | 5 +++++ 5 files changed, 55 insertions(+), 7 deletions(-) create mode 100644 releasenotes/notes/bug-1670707-c4799fbed39ef75b.yaml diff --git a/doc/source/command-objects/port.rst b/doc/source/command-objects/port.rst index 5080addb64..98642b9a33 100644 --- a/doc/source/command-objects/port.rst +++ b/doc/source/command-objects/port.rst @@ -219,6 +219,7 @@ Set port properties [--host ] [--enable | --disable] [--name ] + [--mac-address ] [--security-group ] [--no-security-group] [--enable-port-security | --disable-port-security] @@ -285,6 +286,10 @@ Set port properties Set port name +.. option:: --mac-address + + Set port's MAC address (admin only) + .. option:: --security-group Security group to associate with this port (name or ID) diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 6117175e9e..eced93ceea 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -130,6 +130,8 @@ def _get_attrs(client_manager, parsed_args): attrs['binding:vnic_type'] = parsed_args.vnic_type if parsed_args.host: attrs['binding:host_id'] = parsed_args.host + if parsed_args.mac_address is not None: + attrs['mac_address'] = parsed_args.mac_address if parsed_args.dns_name is not None: attrs['dns_name'] = parsed_args.dns_name @@ -138,8 +140,6 @@ def _get_attrs(client_manager, parsed_args): attrs['name'] = str(parsed_args.name) # The remaining options do not support 'port set' command, so they require # additional check - if 'mac_address' in parsed_args and parsed_args.mac_address is not None: - attrs['mac_address'] = parsed_args.mac_address if 'network' in parsed_args and parsed_args.network is not None: attrs['network_id'] = parsed_args.network if 'project' in parsed_args and parsed_args.project is not None: @@ -234,6 +234,11 @@ def _add_updatable_args(parser): metavar='', help=argparse.SUPPRESS, ) + parser.add_argument( + '--mac-address', + metavar='', + help=_("MAC address of this port (admin only)") + ) parser.add_argument( '--device-owner', metavar='', @@ -324,11 +329,6 @@ def get_parser(self, prog_name): action='store_true', help=_("Disable port") ) - parser.add_argument( - '--mac-address', - metavar='', - help=_("MAC address of this port") - ) parser.add_argument( '--project', metavar='', diff --git a/openstackclient/tests/functional/network/v2/test_port.py b/openstackclient/tests/functional/network/v2/test_port.py index 78c572730d..bd5eefa5ba 100644 --- a/openstackclient/tests/functional/network/v2/test_port.py +++ b/openstackclient/tests/functional/network/v2/test_port.py @@ -147,3 +147,22 @@ def test_port_set(self): 'port show -f json ' + self.NAME )) self.assertEqual('', json_output.get('security_group_ids')) + + def test_port_admin_set(self): + """Test create, set (as admin), show, delete""" + json_output = json.loads(self.openstack( + 'port create -f json ' + + '--network ' + self.NETWORK_NAME + ' ' + self.NAME + )) + id_ = json_output.get('id') + self.addCleanup(self.openstack, 'port delete ' + id_) + + raw_output = self.openstack( + '--os-username admin ' + + 'port set --mac-address 11:22:33:44:55:66 ' + + self.NAME) + self.assertOutput('', raw_output) + json_output = json.loads(self.openstack( + 'port show -f json ' + self.NAME + )) + self.assertEqual(json_output.get('mac_address'), '11:22:33:44:55:66') diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index 80eba3a875..d2df5841fe 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -987,6 +987,25 @@ def test_overwrite_fixed_ip(self): self.network.update_port.assert_called_once_with(_testport, **attrs) self.assertIsNone(result) + def test_overwrite_mac_address(self): + _testport = network_fakes.FakePort.create_one_port( + {'mac_address': '11:22:33:44:55:66'}) + self.network.find_port = mock.Mock(return_value=_testport) + arglist = [ + '--mac-address', '66:55:44:33:22:11', + _testport.name, + ] + verifylist = [ + ('mac_address', '66:55:44:33:22:11'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + attrs = { + 'mac_address': '66:55:44:33:22:11', + } + self.network.update_port.assert_called_once_with(_testport, **attrs) + self.assertIsNone(result) + def test_set_this(self): arglist = [ '--disable', diff --git a/releasenotes/notes/bug-1670707-c4799fbed39ef75b.yaml b/releasenotes/notes/bug-1670707-c4799fbed39ef75b.yaml new file mode 100644 index 0000000000..3509ca2056 --- /dev/null +++ b/releasenotes/notes/bug-1670707-c4799fbed39ef75b.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Add ``--mac-address`` option to ``port set`` command. + [Bug `1670707 `_] From 7ef1e9ea96ef15b63304a6bccaf30f8c269f2b76 Mon Sep 17 00:00:00 2001 From: Nakul Dahiwade Date: Fri, 11 Nov 2016 21:42:24 +0000 Subject: [PATCH 1611/3095] OSC Network Flavor Profile Implement Neutron feature of Network Flavor Profile into OpenstackClient This patch implements the following commands: network flavor profile create network flavor profile delete network flavor profile list network flavor profile show network flavor profile set SDK Version needed: 0.9.9 Change-Id: Ie6fe5e53122cfb2eda8d326851d54562739a8386 Partially-Implements: blueprint neutron-client-flavors --- .../network-flavor-profile.rst | 145 ++++++ doc/source/commands.rst | 1 + .../network/v2/network_flavor_profile.py | 250 ++++++++++ .../network/v2/test_network_flavor_profile.py | 151 ++++++ .../tests/unit/network/v2/fakes.py | 47 ++ .../network/v2/test_network_flavor_profile.py | 448 ++++++++++++++++++ ...twork-flavor-profile-e7cc5b353c3ed9d9.yaml | 9 + setup.cfg | 6 + 8 files changed, 1057 insertions(+) create mode 100644 doc/source/command-objects/network-flavor-profile.rst create mode 100644 openstackclient/network/v2/network_flavor_profile.py create mode 100644 openstackclient/tests/functional/network/v2/test_network_flavor_profile.py create mode 100644 openstackclient/tests/unit/network/v2/test_network_flavor_profile.py create mode 100644 releasenotes/notes/add-network-flavor-profile-e7cc5b353c3ed9d9.yaml diff --git a/doc/source/command-objects/network-flavor-profile.rst b/doc/source/command-objects/network-flavor-profile.rst new file mode 100644 index 0000000000..fdb95059b8 --- /dev/null +++ b/doc/source/command-objects/network-flavor-profile.rst @@ -0,0 +1,145 @@ +====================== +network flavor profile +====================== + +A **network flavor profile** allows administrators to create, delete, list, +show and update network service profile, which details a framework to enable +operators to configure and users to select from different abstract +representations of a service implementation in the Networking service. +It decouples the logical configuration from its instantiation enabling +operators to create user options according to deployment needs. + +Network v2 + +network flavor profile create +----------------------------- + +Create a new network flavor profile + +.. program:: network flavor profile create +.. code:: bash + + openstack network flavor profile create + [--project [--project-domain ]] + [--description ] + [--enable | --disable] + (--driver | --metainfo | --driver --metainfo ) + +.. option:: --project + + Owner's project (name or ID) + + *Network version 2 only* + +.. option:: --project-domain + + Domain the project belongs to (name or ID). This can + be used in case collisions between project names + exist + +.. option:: --description + + Description for the flavor profile + + *Network version 2 only* + +.. option:: --enable + + Enable the flavor profile (default) + +.. option:: --disable + + Disable the flavor profile + +.. option:: --driver + + Python module path to driver + + *Network version 2 only* + +.. option:: --metainfo + + Metainfo for the flavor profile + + *Network version 2 only* + + +network flavor profile delete +----------------------------- + +Delete network flavor profile + +.. program:: network flavor profile delete +.. code:: bash + + openstack network flavor profile delete + [ ...] + +.. describe:: + + Flavor profile(s) to delete (ID only) + +network flavor profile list +--------------------------- + +List network flavor profiles + +.. program:: network flavor profile list +.. code:: bash + + openstack network flavor profile list + +network flavor profile set +-------------------------- + +Set network flavor profile properties + +.. program:: network flavor profile set +.. code:: bash + + openstack network flavor profile set + [--description ] + [--driver ] + [--enable | --disable] + [--metainfo ] + + + +.. option:: --description + + Description of the flavor profile + +.. option:: --driver + + Python module path to driver + +.. option:: --enable (Default) + + Enable the flavor profile + +.. option:: --disable + + Disable the flavor profile + +.. option:: --metainfo + + Metainfo for the flavor profile + +.. describe:: + + Flavor profile to update (ID only) + +network flavor profile show +--------------------------- + +Show network flavor profile + +.. program:: network flavor profile show +.. code:: bash + + openstack network flavor profile show + + +.. describe:: + + Flavor profile to display (ID only) diff --git a/doc/source/commands.rst b/doc/source/commands.rst index a0c67cd4c8..ccd081e8d6 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -113,6 +113,7 @@ referring to both Compute and Volume quotas. * ``network agent``: (**Network**) - A network agent is an agent that handles various tasks used to implement virtual networks * ``network auto allocated topology``: (**Network**) - an auto-allocated topology for a project * ``network flavor``: (**Network**) - allows the user to choose the type of service by a set of advertised service capabilities (e.g., LOADBALANCER, FWAAS, L3, VPN, etc) rather than by a provider type or named vendor +* ``network flavor profile``: (**Network**) - predefined neutron service configurations: driver * ``network meter``: (**Network**) - allow traffic metering in a network * ``network meter rule``: (**Network**) - rules for network traffic metering * ``network rbac``: (**Network**) - an RBAC policy for network resources diff --git a/openstackclient/network/v2/network_flavor_profile.py b/openstackclient/network/v2/network_flavor_profile.py new file mode 100644 index 0000000000..6cf0c4124d --- /dev/null +++ b/openstackclient/network/v2/network_flavor_profile.py @@ -0,0 +1,250 @@ +# 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 logging + +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils + +from openstackclient.i18n import _ +from openstackclient.identity import common as identity_common +from openstackclient.network import sdk_utils + + +LOG = logging.getLogger(__name__) + + +def _get_columns(item): + column_map = { + 'is_enabled': 'enabled', + 'tenant_id': 'project_id', + } + + return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + + +def _get_attrs(client_manager, parsed_args): + attrs = {} + if parsed_args.description is not None: + attrs['description'] = parsed_args.description + if parsed_args.driver is not None: + attrs['driver'] = parsed_args.driver + if parsed_args.metainfo is not None: + attrs['metainfo'] = parsed_args.metainfo + if parsed_args.enable: + attrs['enabled'] = True + if parsed_args.disable: + attrs['enabled'] = False + if 'project' in parsed_args and parsed_args.project is not None: + identity_client = client_manager.identity + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + attrs['tenant_id'] = project_id + + return attrs + + +# TODO(ndahiwade): Use the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. +class CreateNetworkFlavorProfile(command.ShowOne): + _description = _("Create new network flavor profile") + + def get_parser(self, prog_name): + parser = super(CreateNetworkFlavorProfile, self).get_parser(prog_name) + parser.add_argument( + '--project', + metavar="", + help=_("Owner's project (name or ID)") + ) + identity_common.add_project_domain_option_to_parser(parser) + parser.add_argument( + '--description', + metavar="", + help=_("Description for the flavor profile") + ) + enable_group = parser.add_mutually_exclusive_group() + enable_group.add_argument( + '--enable', + action='store_true', + help=_("Enable the flavor profile") + ) + enable_group.add_argument( + '--disable', + action='store_true', + help=_("Disable the flavor profile") + ) + parser.add_argument( + '--driver', + help=_("Python module path to driver. This becomes " + "required if --metainfo is missing and vice versa") + ) + parser.add_argument( + '--metainfo', + help=_("Metainfo for the flavor profile. This becomes " + "required if --driver is missing and vice versa") + ) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + attrs = _get_attrs(self.app.client_manager, parsed_args) + + if parsed_args.driver is None and parsed_args.metainfo is None: + msg = _("Either --driver or --metainfo or both are required") + raise exceptions.CommandError(msg) + + obj = client.create_service_profile(**attrs) + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns, formatters={}) + + return (display_columns, data) + + +class DeleteNetworkFlavorProfile(command.Command): + _description = _("Delete network flavor profile") + + def get_parser(self, prog_name): + parser = super(DeleteNetworkFlavorProfile, self).get_parser(prog_name) + + parser.add_argument( + 'flavor_profile', + metavar='', + nargs='+', + help=_("Flavor profile(s) to delete (ID only)") + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + result = 0 + + for flavor_profile in parsed_args.flavor_profile: + try: + obj = client.find_service_profile(flavor_profile, + ignore_missing=False) + client.delete_service_profile(obj) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete flavor profile with " + "ID '%(flavor_profile)s': %(e)s"), + {"flavor_profile": flavor_profile, "e": e}) + if result > 0: + total = len(parsed_args.flavor_profile) + msg = (_("%(result)s of %(total)s flavor_profiles failed " + "to delete.") % {"result": result, "total": total}) + raise exceptions.CommandError(msg) + + +class ListNetworkFlavorProfile(command.Lister): + _description = _("List network flavor profile(s)") + + def take_action(self, parsed_args): + client = self.app.client_manager.network + + columns = ( + 'id', + 'driver', + 'is_enabled', + 'metainfo', + 'description', + ) + column_headers = ( + 'ID', + 'Driver', + 'Enabled', + 'Metainfo', + 'Description', + ) + + data = client.service_profiles() + return (column_headers, + (utils.get_item_properties( + s, columns, + ) for s in data)) + + +# TODO(ndahiwade): Use the SDK resource mapped attribute names once the +# OSC minimum requirements include SDK 1.0. +class SetNetworkFlavorProfile(command.Command): + _description = _("Set network flavor profile properties") + + def get_parser(self, prog_name): + parser = super(SetNetworkFlavorProfile, self).get_parser(prog_name) + parser.add_argument( + 'flavor_profile', + metavar="", + help=_("Flavor profile to update (ID only)") + ) + identity_common.add_project_domain_option_to_parser(parser) + parser.add_argument( + '--description', + metavar="", + help=_("Description for the flavor profile") + ) + enable_group = parser.add_mutually_exclusive_group() + enable_group.add_argument( + '--enable', + action='store_true', + help=_("Enable the flavor profile") + ) + enable_group.add_argument( + '--disable', + action='store_true', + help=_("Disable the flavor profile") + ) + parser.add_argument( + '--driver', + help=_("Python module path to driver. This becomes " + "required if --metainfo is missing and vice versa") + ) + parser.add_argument( + '--metainfo', + help=_("Metainfo for the flavor profile. This becomes " + "required if --driver is missing and vice versa") + ) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.find_service_profile(parsed_args.flavor_profile, + ignore_missing=False) + attrs = _get_attrs(self.app.client_manager, parsed_args) + + client.update_service_profile(obj, **attrs) + + +class ShowNetworkFlavorProfile(command.ShowOne): + _description = _("Display network flavor profile details") + + def get_parser(self, prog_name): + parser = super(ShowNetworkFlavorProfile, self).get_parser(prog_name) + parser.add_argument( + 'flavor_profile', + metavar='', + help=_("Flavor profile to display (ID only)") + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.find_service_profile(parsed_args.flavor_profile, + ignore_missing=False) + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns) + return (display_columns, data) diff --git a/openstackclient/tests/functional/network/v2/test_network_flavor_profile.py b/openstackclient/tests/functional/network/v2/test_network_flavor_profile.py new file mode 100644 index 0000000000..1a82c82b0c --- /dev/null +++ b/openstackclient/tests/functional/network/v2/test_network_flavor_profile.py @@ -0,0 +1,151 @@ +# 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 json + +from openstackclient.tests.functional import base + + +class NetworkFlavorProfileTests(base.TestCase): + """Functional tests for network flavor-profile.""" + + DESCRIPTION = 'fakedescription' + METAINFO = 'Extrainfo' + + def test_network_flavor_profile_create(self): + json_output = json.loads(self.openstack( + ' network flavor profile create -f json --description ' + + self.DESCRIPTION + ' --enable --metainfo ' + self.METAINFO)) + ID = json_output.get('id') + self.assertIsNotNone(ID) + self.assertEqual( + True, + json_output.get('enabled')) + self.assertEqual( + 'fakedescription', + json_output.get('description')) + self.assertEqual( + 'Extrainfo', + json_output.get('meta_info') + ) + + # Clean up + raw_output = self.openstack('network flavor profile delete ' + ID) + self.assertOutput('', raw_output) + + def test_network_flavor_profile_list(self): + json_output = json.loads(self.openstack( + ' network flavor profile create -f json --description ' + + self.DESCRIPTION + ' --enable --metainfo ' + self.METAINFO)) + ID1 = json_output.get('id') + self.assertIsNotNone(ID1) + self.assertEqual( + True, + json_output.get('enabled')) + self.assertEqual( + 'fakedescription', + json_output.get('description')) + self.assertEqual( + 'Extrainfo', + json_output.get('meta_info') + ) + + json_output = json.loads(self.openstack( + ' network flavor profile create -f json --description ' + + self.DESCRIPTION + ' --disable --metainfo ' + self.METAINFO)) + ID2 = json_output.get('id') + self.assertIsNotNone(ID2) + self.assertEqual( + False, + json_output.get('enabled')) + self.assertEqual( + 'fakedescription', + json_output.get('description')) + self.assertEqual( + 'Extrainfo', + json_output.get('meta_info') + ) + + # Test list + json_output = json.loads(self.openstack( + 'network flavor profile list -f json')) + self.assertIsNotNone(json_output) + + id_list = [item.get('ID') for item in json_output] + self.assertIn(ID1, id_list) + self.assertIn(ID2, id_list) + + # Clean up + raw_output = self.openstack( + 'network flavor profile delete ' + ID1 + " " + ID2) + self.assertOutput('', raw_output) + + def test_network_flavor_profile_set(self): + json_output_1 = json.loads(self.openstack( + ' network flavor profile create -f json --description ' + + self.DESCRIPTION + ' --enable --metainfo ' + self.METAINFO)) + ID = json_output_1.get('id') + self.assertIsNotNone(ID) + self.assertEqual( + True, + json_output_1.get('enabled')) + self.assertEqual( + 'fakedescription', + json_output_1.get('description')) + self.assertEqual( + 'Extrainfo', + json_output_1.get('meta_info') + ) + + self.openstack('network flavor profile set --disable ' + ID) + + json_output = json.loads(self.openstack('network flavor profile show ' + '-f json ' + ID)) + self.assertEqual( + False, + json_output.get('enabled')) + self.assertEqual( + 'fakedescription', + json_output.get('description')) + self.assertEqual( + 'Extrainfo', + json_output.get('meta_info') + ) + + # Clean up + raw_output = self.openstack('network flavor profile delete ' + ID) + self.assertOutput('', raw_output) + + def test_network_flavor_profile_show(self): + json_output_1 = json.loads(self.openstack( + ' network flavor profile create -f json --description ' + + self.DESCRIPTION + ' --enable --metainfo ' + self.METAINFO)) + ID = json_output_1.get('id') + self.assertIsNotNone(ID) + json_output = json.loads(self.openstack('network flavor profile show ' + '-f json ' + ID)) + self.assertEqual( + ID, + json_output["id"]) + self.assertEqual( + True, + json_output["enabled"]) + self.assertEqual( + 'fakedescription', + json_output["description"]) + self.assertEqual( + 'Extrainfo', + json_output["meta_info"]) + + # Clean up + raw_output = self.openstack('network flavor profile delete ' + ID) + self.assertOutput('', raw_output) diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index 7afe332885..a2274a34e4 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -769,6 +769,53 @@ def get_network_rbacs(rbac_policies=None, count=2): return mock.Mock(side_effect=rbac_policies) +class FakeNetworkFlavorProfile(object): + """Fake network flavor profile.""" + + @staticmethod + def create_one_service_profile(attrs=None): + """Create flavor profile.""" + attrs = attrs or {} + + flavor_profile_attrs = { + 'id': 'flavor-profile-id' + uuid.uuid4().hex, + 'description': 'flavor-profile-description-' + uuid.uuid4().hex, + 'tenant_id': 'project-id-' + uuid.uuid4().hex, + 'driver': 'driver-' + uuid.uuid4().hex, + 'metainfo': 'metainfo-' + uuid.uuid4().hex, + 'enabled': True + } + + flavor_profile_attrs.update(attrs) + + flavor_profile = fakes.FakeResource( + info=copy.deepcopy(flavor_profile_attrs), + loaded=True) + + flavor_profile.project_id = flavor_profile_attrs['tenant_id'] + flavor_profile.is_enabled = flavor_profile_attrs['enabled'] + + return flavor_profile + + @staticmethod + def create_service_profile(attrs=None, count=2): + """Create multiple flavor profiles.""" + + flavor_profiles = [] + for i in range(0, count): + flavor_profiles.append(FakeNetworkFlavorProfile. + create_one_service_profile(attrs)) + return flavor_profiles + + @staticmethod + def get_service_profile(flavor_profile=None, count=2): + """Get a list of flavor profiles.""" + if flavor_profile is None: + flavor_profile = (FakeNetworkFlavorProfile. + create_service_profile(count)) + return mock.Mock(side_effect=flavor_profile) + + class FakeNetworkQosPolicy(object): """Fake one or more QoS policies.""" diff --git a/openstackclient/tests/unit/network/v2/test_network_flavor_profile.py b/openstackclient/tests/unit/network/v2/test_network_flavor_profile.py new file mode 100644 index 0000000000..91683241c2 --- /dev/null +++ b/openstackclient/tests/unit/network/v2/test_network_flavor_profile.py @@ -0,0 +1,448 @@ +# 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 osc_lib import exceptions + +from openstackclient.network.v2 import network_flavor_profile +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3 +from openstackclient.tests.unit.network.v2 import fakes as network_fakes + + +class TestFlavorProfile(network_fakes.TestNetworkV2): + + def setUp(self): + super(TestFlavorProfile, self).setUp() + # Get the network client + self.network = self.app.client_manager.network + # Get the ProjectManager Mock + self.projects_mock = self.app.client_manager.identity.projects + # Get the DomainManager Mock + self.domains_mock = self.app.client_manager.identity.domains + + +class TestCreateFlavorProfile(TestFlavorProfile): + project = identity_fakes_v3.FakeProject.create_one_project() + domain = identity_fakes_v3.FakeDomain.create_one_domain() + new_flavor_profile = ( + network_fakes.FakeNetworkFlavorProfile. + create_one_service_profile() + ) + columns = ( + 'description', + 'driver', + 'enabled', + 'id', + 'metainfo', + 'project_id', + ) + + data = ( + new_flavor_profile.description, + new_flavor_profile.driver, + new_flavor_profile.enabled, + new_flavor_profile.id, + new_flavor_profile.metainfo, + new_flavor_profile.project_id, + ) + + def setUp(self): + super(TestCreateFlavorProfile, self).setUp() + self.network.create_service_profile = mock.Mock( + return_value=self.new_flavor_profile) + self.projects_mock.get.return_value = self.project + # Get the command object to test + self.cmd = (network_flavor_profile.CreateNetworkFlavorProfile( + self.app, self.namespace)) + + def test_create_all_options(self): + arglist = [ + "--description", self.new_flavor_profile.description, + "--project", self.new_flavor_profile.project_id, + '--project-domain', self.domain.name, + "--enable", + "--driver", self.new_flavor_profile.driver, + "--metainfo", self.new_flavor_profile.metainfo, + ] + + verifylist = [ + ('description', self.new_flavor_profile.description), + ('project', self.new_flavor_profile.project_id), + ('project_domain', self.domain.name), + ('enable', True), + ('driver', self.new_flavor_profile.driver), + ('metainfo', self.new_flavor_profile.metainfo) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_service_profile.assert_called_once_with( + **{'description': self.new_flavor_profile.description, + 'tenant_id': self.project.id, + 'enabled': self.new_flavor_profile.enabled, + 'driver': self.new_flavor_profile.driver, + 'metainfo': self.new_flavor_profile.metainfo} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_with_metainfo(self): + arglist = [ + "--description", self.new_flavor_profile.description, + "--project", self.new_flavor_profile.project_id, + '--project-domain', self.domain.name, + "--enable", + "--metainfo", self.new_flavor_profile.metainfo, + ] + + verifylist = [ + ('description', self.new_flavor_profile.description), + ('project', self.new_flavor_profile.project_id), + ('project_domain', self.domain.name), + ('enable', True), + ('metainfo', self.new_flavor_profile.metainfo) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_service_profile.assert_called_once_with( + **{'description': self.new_flavor_profile.description, + 'tenant_id': self.project.id, + 'enabled': self.new_flavor_profile.enabled, + 'metainfo': self.new_flavor_profile.metainfo} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_with_driver(self): + arglist = [ + "--description", self.new_flavor_profile.description, + "--project", self.new_flavor_profile.project_id, + '--project-domain', self.domain.name, + "--enable", + "--driver", self.new_flavor_profile.driver, + ] + + verifylist = [ + ('description', self.new_flavor_profile.description), + ('project', self.new_flavor_profile.project_id), + ('project_domain', self.domain.name), + ('enable', True), + ('driver', self.new_flavor_profile.driver), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_service_profile.assert_called_once_with( + **{'description': self.new_flavor_profile.description, + 'tenant_id': self.project.id, + 'enabled': self.new_flavor_profile.enabled, + 'driver': self.new_flavor_profile.driver, + } + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_without_driver_and_metainfo(self): + arglist = [ + "--description", self.new_flavor_profile.description, + "--project", self.new_flavor_profile.project_id, + '--project-domain', self.domain.name, + "--enable", + ] + + verifylist = [ + ('description', self.new_flavor_profile.description), + ('project', self.new_flavor_profile.project_id), + ('project_domain', self.domain.name), + ('enable', True), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args, + ) + + def test_create_disable(self): + arglist = [ + '--disable', + '--driver', self.new_flavor_profile.driver, + ] + verifylist = [ + ('disable', True), + ('driver', self.new_flavor_profile.driver) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_service_profile.assert_called_once_with(**{ + 'enabled': False, + 'driver': self.new_flavor_profile.driver, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestDeleteFlavorProfile(TestFlavorProfile): + + # The network flavor_profiles to delete. + _network_flavor_profiles = ( + network_fakes.FakeNetworkFlavorProfile.create_service_profile(count=2)) + + def setUp(self): + super(TestDeleteFlavorProfile, self).setUp() + self.network.delete_service_profile = mock.Mock(return_value=None) + self.network.find_service_profile = ( + network_fakes.FakeNetworkFlavorProfile.get_service_profile( + flavor_profile=self._network_flavor_profiles) + ) + + # Get the command object to test + self.cmd = network_flavor_profile.DeleteNetworkFlavorProfile( + self.app, self.namespace) + + def test_network_flavor_profile_delete(self): + arglist = [ + self._network_flavor_profiles[0].id, + ] + verifylist = [ + ('flavor_profile', [self._network_flavor_profiles[0].id]), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.network.find_service_profile.assert_called_once_with( + self._network_flavor_profiles[0].id, ignore_missing=False) + self.network.delete_service_profile.assert_called_once_with( + self._network_flavor_profiles[0]) + self.assertIsNone(result) + + def test_multi_network_flavor_profiles_delete(self): + arglist = [] + + for a in self._network_flavor_profiles: + arglist.append(a.id) + verifylist = [ + ('flavor_profile', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [] + for a in self._network_flavor_profiles: + calls.append(mock.call(a)) + self.network.delete_service_profile.assert_has_calls(calls) + self.assertIsNone(result) + + def test_multi_network_flavor_profiles_delete_with_exception(self): + arglist = [ + self._network_flavor_profiles[0].id, + 'unexist_network_flavor_profile', + ] + verifylist = [ + ('flavor_profile', + [self._network_flavor_profiles[0].id, + 'unexist_network_flavor_profile']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self._network_flavor_profiles[0], + exceptions.CommandError] + self.network.find_service_profile = ( + mock.Mock(side_effect=find_mock_result) + ) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 flavor_profiles failed to delete.', + str(e)) + + self.network.find_service_profile.assert_any_call( + self._network_flavor_profiles[0].id, ignore_missing=False) + self.network.find_service_profile.assert_any_call( + 'unexist_network_flavor_profile', ignore_missing=False) + self.network.delete_service_profile.assert_called_once_with( + self._network_flavor_profiles[0] + ) + + +class TestListFlavorProfile(TestFlavorProfile): + + # The network flavor profiles list + _network_flavor_profiles = ( + network_fakes.FakeNetworkFlavorProfile.create_service_profile(count=2)) + + columns = ( + 'ID', + 'Driver', + 'Enabled', + 'Metainfo', + 'Description', + ) + data = [] + for flavor_profile in _network_flavor_profiles: + data.append(( + flavor_profile.id, + flavor_profile.driver, + flavor_profile.enabled, + flavor_profile.metainfo, + flavor_profile.description, + )) + + def setUp(self): + super(TestListFlavorProfile, self).setUp() + self.network.service_profiles = mock.Mock( + return_value=self._network_flavor_profiles) + + # Get the command object to test + self.cmd = network_flavor_profile.ListNetworkFlavorProfile( + self.app, self.namespace) + + def test_network_flavor_profile_list(self): + arglist = [] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.service_profiles.assert_called_once_with(**{}) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + +class TestShowFlavorProfile(TestFlavorProfile): + + # The network flavor profile to show. + network_flavor_profile = ( + network_fakes.FakeNetworkFlavorProfile.create_one_service_profile()) + columns = ( + 'description', + 'driver', + 'enabled', + 'id', + 'metainfo', + 'project_id', + ) + data = ( + network_flavor_profile.description, + network_flavor_profile.driver, + network_flavor_profile.enabled, + network_flavor_profile.id, + network_flavor_profile.metainfo, + network_flavor_profile.project_id, + ) + + def setUp(self): + super(TestShowFlavorProfile, self).setUp() + self.network.find_service_profile = mock.Mock( + return_value=self.network_flavor_profile) + + # Get the command object to test + self.cmd = network_flavor_profile.ShowNetworkFlavorProfile( + self.app, self.namespace) + + def test_show_all_options(self): + arglist = [ + self.network_flavor_profile.id, + ] + verifylist = [ + ('flavor_profile', self.network_flavor_profile.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.find_service_profile.assert_called_once_with( + self.network_flavor_profile.id, ignore_missing=False) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestSetFlavorProfile(TestFlavorProfile): + + # The network flavor profile to set. + network_flavor_profile = ( + network_fakes.FakeNetworkFlavorProfile.create_one_service_profile()) + + def setUp(self): + super(TestSetFlavorProfile, self).setUp() + self.network.update_service_profile = mock.Mock(return_value=None) + self.network.find_service_profile = mock.Mock( + return_value=self.network_flavor_profile) + + # Get the command object to test + self.cmd = network_flavor_profile.SetNetworkFlavorProfile( + self.app, self.namespace) + + def test_set_nothing(self): + arglist = [self.network_flavor_profile.id] + verifylist = [ + ('flavor_profile', self.network_flavor_profile.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = {} + self.network.update_service_profile.assert_called_with( + self.network_flavor_profile, **attrs) + self.assertIsNone(result) + + def test_set_enable(self): + arglist = [ + '--enable', + self.network_flavor_profile.id, + ] + verifylist = [ + ('enable', True), + ('flavor_profile', self.network_flavor_profile.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + attrs = { + 'enabled': True, + } + self.network.update_service_profile.assert_called_with( + self.network_flavor_profile, **attrs) + self.assertIsNone(result) + + def test_set_disable(self): + arglist = [ + '--disable', + self.network_flavor_profile.id, + ] + verifylist = [ + ('disable', True), + ('flavor_profile', self.network_flavor_profile.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + attrs = { + 'enabled': False, + } + self.network.update_service_profile.assert_called_with( + self.network_flavor_profile, **attrs) + self.assertIsNone(result) diff --git a/releasenotes/notes/add-network-flavor-profile-e7cc5b353c3ed9d9.yaml b/releasenotes/notes/add-network-flavor-profile-e7cc5b353c3ed9d9.yaml new file mode 100644 index 0000000000..b604762dc8 --- /dev/null +++ b/releasenotes/notes/add-network-flavor-profile-e7cc5b353c3ed9d9.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + Add support for Network Flavor Profile commands: + ``network flavor profile create``, ``network flavor profile delete``, + ``network flavor profile list``, ``network flavor profile show`` and + ``network flavor profile set`` + [Blueprint :oscbp:`neutron-client-flavors`] + diff --git a/setup.cfg b/setup.cfg index e18aa5c1e7..87251a9092 100644 --- a/setup.cfg +++ b/setup.cfg @@ -369,6 +369,12 @@ openstack.network.v2 = network_flavor_set = openstackclient.network.v2.network_flavor:SetNetworkFlavor network_flavor_show = openstackclient.network.v2.network_flavor:ShowNetworkFlavor + network_flavor_profile_create = openstackclient.network.v2.network_flavor_profile:CreateNetworkFlavorProfile + network_flavor_profile_delete = openstackclient.network.v2.network_flavor_profile:DeleteNetworkFlavorProfile + network_flavor_profile_list = openstackclient.network.v2.network_flavor_profile:ListNetworkFlavorProfile + network_flavor_profile_set = openstackclient.network.v2.network_flavor_profile:SetNetworkFlavorProfile + network_flavor_profile_show = openstackclient.network.v2.network_flavor_profile:ShowNetworkFlavorProfile + network_create = openstackclient.network.v2.network:CreateNetwork network_delete = openstackclient.network.v2.network:DeleteNetwork network_list = openstackclient.network.v2.network:ListNetwork From be1e6ca1d8601fd4b18bad1711d8b5cf729198f9 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Mon, 20 Mar 2017 13:28:42 +0000 Subject: [PATCH 1612/3095] docs: Don't include ChangeLog The ChangeLog is built using commit message summary lines. Unfortunately some of these contain invalid rST markup. There's no way to retroactively fix this, so simply stop including the doc. Change-Id: I6600c1baf142fe4c776f1ae170faa3f6a87b330c --- doc/source/history.rst | 1 - doc/source/index.rst | 1 - 2 files changed, 2 deletions(-) delete mode 100644 doc/source/history.rst diff --git a/doc/source/history.rst b/doc/source/history.rst deleted file mode 100644 index 69ed4fe6c2..0000000000 --- a/doc/source/history.rst +++ /dev/null @@ -1 +0,0 @@ -.. include:: ../../ChangeLog diff --git a/doc/source/index.rst b/doc/source/index.rst index e19b466b93..6af49c52f0 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -43,7 +43,6 @@ Release Notes :maxdepth: 1 Release Notes - history Developer Documentation ----------------------- From f4fd8f6e31dcc177b56d1e618cdefef728d09157 Mon Sep 17 00:00:00 2001 From: Shashank Kumar Shankar Date: Mon, 17 Oct 2016 18:26:01 +0000 Subject: [PATCH 1613/3095] Introduce Neutron DHCP agent commands to OSC This patch introduces neutron dhcp-agent related commands to OpenStack client. The equivalent neutron commands implemented in OSC: neutron: dhcp-agent-list-hosting-net OSC: network agent list --network neutron: dhcp-agent-network-add OSC: network agent add network neutron: dhcp-agent-network-remove OSC: network agent remove network neutron: net-list-on-dhcp-agent OSC: network list --agent Change-Id: I77a933f4b3ce875c63cef5b6a32aee78fd844b03 --- doc/source/command-objects/network-agent.rst | 55 ++++++++ doc/source/command-objects/network.rst | 5 + openstackclient/network/v2/network.py | 44 ++++-- openstackclient/network/v2/network_agent.py | 116 ++++++++++++++-- .../functional/network/v2/test_network.py | 43 ++++++ .../network/v2/test_network_agent.py | 52 +++++++ .../tests/unit/network/v2/test_network.py | 26 ++++ .../unit/network/v2/test_network_agent.py | 129 +++++++++++++++++- ...rk-dhcp-adv-commands-e61bf8757f46dc93.yaml | 7 + setup.cfg | 2 + 10 files changed, 456 insertions(+), 23 deletions(-) create mode 100644 releasenotes/notes/bp-network-dhcp-adv-commands-e61bf8757f46dc93.yaml diff --git a/doc/source/command-objects/network-agent.rst b/doc/source/command-objects/network-agent.rst index 947e07cf6c..d764f3de1c 100644 --- a/doc/source/command-objects/network-agent.rst +++ b/doc/source/command-objects/network-agent.rst @@ -10,6 +10,31 @@ agent is "True". Network v2 +network agent add network +------------------------- + +Add network to an agent + +.. program:: network agent add network +.. code:: bash + + openstack network agent add network + [--dhcp] + + + +.. describe:: --dhcp + + Add a network to DHCP agent. + +.. describe:: + + Agent to which a network is added. (ID only) + +.. describe:: + + Network to be added to an agent. (ID or name) + network agent delete -------------------- @@ -37,6 +62,7 @@ List network agents openstack network agent list [--agent-type ] [--host ] + [--network ] .. option:: --agent-type @@ -49,6 +75,10 @@ List network agents List only agents running on the specified host +.. option:: --network + + List agents hosting a network. (ID or name) + network agent set ----------------- @@ -94,3 +124,28 @@ Display network agent details .. describe:: Network agent to display (ID only) + +network agent remove network +---------------------------- + +Remove network from an agent + +.. program:: network agent remove network +.. code:: bash + + openstack network agent remove network + [--dhcp] + + + +.. describe:: --dhcp + + Remove network from DHCP agent. + +.. describe:: + + Agent to which a network is removed. (ID only) + +.. describe:: + + Network to be removed from an agent. (ID or name) diff --git a/doc/source/command-objects/network.rst b/doc/source/command-objects/network.rst index dc597443ce..9162dbff01 100644 --- a/doc/source/command-objects/network.rst +++ b/doc/source/command-objects/network.rst @@ -203,6 +203,7 @@ List networks [--provider-network-type ] [--provider-physical-network ] [--provider-segment ] + [--agent ] .. option:: --external @@ -290,6 +291,10 @@ List networks *Network version 2 only* +.. option:: --agent + + List networks hosted by agent (ID only) + network set ----------- diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 3aa8ec8442..3e0bb7764b 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -60,12 +60,10 @@ def _get_network_columns(item): def _get_columns(item): - columns = list(item.keys()) - if 'tenant_id' in columns: - columns.remove('tenant_id') - if 'project_id' not in columns: - columns.append('project_id') - return tuple(sorted(columns)) + column_map = { + 'tenant_id': 'project_id', + } + return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) def _get_attrs(client_manager, parsed_args): @@ -305,9 +303,9 @@ def take_action_network(self, client, parsed_args): def take_action_compute(self, client, parsed_args): attrs = _get_attrs_compute(self.app.client_manager, parsed_args) obj = client.networks.create(**attrs) - columns = _get_columns(obj._info) + display_columns, columns = _get_columns(obj._info) data = utils.get_dict_properties(obj._info, columns) - return (columns, data) + return (display_columns, data) class DeleteNetwork(common.NetworkAndComputeDelete): @@ -420,7 +418,11 @@ def update_parser_network(self, parser): help=_("List networks according to VLAN ID for VLAN networks " "or Tunnel ID for GENEVE/GRE/VXLAN networks") ) - + parser.add_argument( + '--agent', + metavar='', + dest='agent_id', + help=_('List networks hosted by agent (ID only)')) return parser def take_action_network(self, client, parsed_args): @@ -450,6 +452,26 @@ def take_action_network(self, client, parsed_args): 'Router Type', 'Availability Zones', ) + elif parsed_args.agent_id: + columns = ( + 'id', + 'name', + 'subnet_ids' + ) + column_headers = ( + 'ID', + 'Name', + 'Subnets', + ) + client = self.app.client_manager.network + dhcp_agent = client.get_agent(parsed_args.agent_id) + data = client.dhcp_agent_hosting_networks(dhcp_agent) + + return (column_headers, + (utils.get_item_properties( + s, columns, + formatters=_formatters, + ) for s in data)) else: columns = ( 'id', @@ -665,6 +687,6 @@ def take_action_compute(self, client, parsed_args): client.networks, parsed_args.network, ) - columns = _get_columns(obj._info) + display_columns, columns = _get_columns(obj._info) data = utils.get_dict_properties(obj._info, columns) - return (columns, data) + return (display_columns, data) diff --git a/openstackclient/network/v2/network_agent.py b/openstackclient/network/v2/network_agent.py index d429fa0830..c23d3e8bb6 100644 --- a/openstackclient/network/v2/network_agent.py +++ b/openstackclient/network/v2/network_agent.py @@ -29,7 +29,6 @@ def _format_admin_state(state): return 'UP' if state else 'DOWN' - _formatters = { 'admin_state_up': _format_admin_state, 'is_admin_state_up': _format_admin_state, @@ -45,6 +44,40 @@ def _get_network_columns(item): return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) +class AddNetworkToAgent(command.Command): + _description = _("Add network to an agent") + + def get_parser(self, prog_name): + parser = super(AddNetworkToAgent, self).get_parser(prog_name) + parser.add_argument( + '--dhcp', + action='store_true', + help=_('Add network to a DHCP agent')) + parser.add_argument( + 'agent_id', + metavar='', + help=_('Agent to which a network is added. (ID only)')) + parser.add_argument( + 'network', + metavar='', + help=_('Network to be added to an agent. (ID or name)')) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + agent = client.get_agent(parsed_args.agent_id) + if parsed_args.dhcp: + network = client.find_network( + parsed_args.network, ignore_missing=False) + try: + client.add_dhcp_agent_to_network(agent, network) + except Exception: + msg = 'Failed to add {} to {}'.format( + network.name, agent.agent_type) + exceptions.CommandError(msg) + + class DeleteNetworkAgent(command.Command): _description = _("Delete network agent(s)") @@ -101,6 +134,11 @@ def get_parser(self, prog_name): metavar='', help=_("List only agents running on the specified host") ) + parser.add_argument( + '--network', + metavar='', + help=_('List agents hosting a network (name or ID)') + ) return parser def take_action(self, parsed_args): @@ -138,16 +176,72 @@ def take_action(self, parsed_args): } filters = {} - if parsed_args.agent_type is not None: - filters['agent_type'] = key_value[parsed_args.agent_type] - if parsed_args.host is not None: - filters['host'] = parsed_args.host - - data = client.agents(**filters) - return (column_headers, - (utils.get_item_properties( - s, columns, formatters=_formatters, - ) for s in data)) + if parsed_args.network is not None: + columns = ( + 'id', + 'host', + 'is_admin_state_up', + 'is_alive', + ) + column_headers = ( + 'ID', + 'Host', + 'Admin State Up', + 'Alive', + ) + network = client.find_network( + parsed_args.network, ignore_missing=False) + data = client.network_hosting_dhcp_agents(network) + + return (column_headers, + (utils.get_item_properties( + s, columns, + formatters=_formatters, + ) for s in data)) + else: + if parsed_args.agent_type is not None: + filters['agent_type'] = key_value[parsed_args.agent_type] + if parsed_args.host is not None: + filters['host'] = parsed_args.host + + data = client.agents(**filters) + return (column_headers, + (utils.get_item_properties( + s, columns, formatters=_formatters, + ) for s in data)) + + +class RemoveNetworkFromAgent(command.Command): + _description = _("Remove network from an agent.") + + def get_parser(self, prog_name): + parser = super(RemoveNetworkFromAgent, self).get_parser(prog_name) + parser.add_argument( + '--dhcp', + action='store_true', + help=_('Remove network from DHCP agent')) + parser.add_argument( + 'agent_id', + metavar='', + help=_('Agent to which a network is removed. (ID only)')) + parser.add_argument( + 'network', + metavar='', + help=_('Network to be removed from an agent. (ID or name)')) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + agent = client.get_agent(parsed_args.agent_id) + if parsed_args.dhcp: + network = client.find_network( + parsed_args.network, ignore_missing=False) + try: + client.remove_dhcp_agent_from_network(agent, network) + except Exception: + msg = 'Failed to remove {} to {}'.format( + network.name, agent.agent_type) + exceptions.CommandError(msg) # TODO(huanxuan): Use the SDK resource mapped attribute names once the diff --git a/openstackclient/tests/functional/network/v2/test_network.py b/openstackclient/tests/functional/network/v2/test_network.py index 126365581a..0e10bfce1a 100644 --- a/openstackclient/tests/functional/network/v2/test_network.py +++ b/openstackclient/tests/functional/network/v2/test_network.py @@ -238,6 +238,49 @@ def test_network_list(self): self.assertIn(name1, col_name) self.assertNotIn(name2, col_name) + def test_network_dhcp_agent(self): + name1 = uuid.uuid4().hex + cmd_output1 = json.loads(self.openstack( + 'network create -f json ' + + '--description aaaa ' + + name1 + )) + + self.addCleanup(self.openstack, 'network delete ' + name1) + + # Get network ID + network_id = cmd_output1['id'] + + # Get DHCP Agent ID + cmd_output2 = json.loads(self.openstack( + 'network agent list -f json --agent-type dhcp' + )) + agent_id = cmd_output2[0]['ID'] + + # Add Agent to Network + self.openstack( + 'network agent add network --dhcp ' + + agent_id + ' ' + network_id + ) + + # Test network list --agent + cmd_output3 = json.loads(self.openstack( + 'network list -f json --agent ' + agent_id + )) + + # Cleanup + # Remove Agent from Network + self.openstack( + 'network agent remove network --dhcp ' + + agent_id + ' ' + network_id + ) + + # Assert + col_name = [x["ID"] for x in cmd_output3] + self.assertIn( + network_id, col_name + ) + def test_network_set(self): """Tests create options, set, show, delete""" name = uuid.uuid4().hex diff --git a/openstackclient/tests/functional/network/v2/test_network_agent.py b/openstackclient/tests/functional/network/v2/test_network_agent.py index dd6112e72e..6da721d1e7 100644 --- a/openstackclient/tests/functional/network/v2/test_network_agent.py +++ b/openstackclient/tests/functional/network/v2/test_network_agent.py @@ -10,6 +10,9 @@ # License for the specific language governing permissions and limitations # under the License. +import json +import uuid + from openstackclient.tests.functional import base @@ -39,3 +42,52 @@ def test_network_agent_set(self): self.openstack('network agent set --enable ' + self.IDs[0]) raw_output = self.openstack('network agent show ' + self.IDs[0] + opts) self.assertEqual("UP\n", raw_output) + + +class NetworkAgentListTests(base.TestCase): + """Functional test for network agent list --network. """ + + def test_network_dhcp_agent_list(self): + """Test network agent list""" + + name1 = uuid.uuid4().hex + cmd_output1 = json.loads(self.openstack( + 'network create -f json ' + + '--description aaaa ' + + name1 + )) + + self.addCleanup(self.openstack, 'network delete ' + name1) + + # Get network ID + network_id = cmd_output1['id'] + + # Get DHCP Agent ID + cmd_output2 = json.loads(self.openstack( + 'network agent list -f json --agent-type dhcp' + )) + agent_id = cmd_output2[0]['ID'] + + # Add Agent to Network + self.openstack( + 'network agent add network --dhcp ' + + agent_id + ' ' + network_id + ) + + # Test network agent list --network + cmd_output3 = json.loads(self.openstack( + 'network agent list -f json --network ' + network_id + )) + + # Cleanup + # Remove Agent from Network + self.openstack( + 'network agent remove network --dhcp ' + + agent_id + ' ' + network_id + ) + + # Assert + col_name = [x["ID"] for x in cmd_output3] + self.assertIn( + agent_id, col_name + ) diff --git a/openstackclient/tests/unit/network/v2/test_network.py b/openstackclient/tests/unit/network/v2/test_network.py index b405bef9a8..bc1279ecbe 100644 --- a/openstackclient/tests/unit/network/v2/test_network.py +++ b/openstackclient/tests/unit/network/v2/test_network.py @@ -491,6 +491,13 @@ def setUp(self): self.network.networks = mock.Mock(return_value=self._network) + self._agent = \ + network_fakes.FakeNetworkAgent.create_one_network_agent() + self.network.get_agent = mock.Mock(return_value=self._agent) + + self.network.dhcp_agent_hosting_networks = mock.Mock( + return_value=self._network) + def test_network_list_no_options(self): arglist = [] verifylist = [ @@ -765,6 +772,25 @@ def test_network_list_provider_segment(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + def test_network_list_dhcp_agent(self): + arglist = [ + '--agent', self._agent.id + ] + verifylist = [ + ('agent_id', self._agent.id), + ] + + attrs = {self._agent, } + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.dhcp_agent_hosting_networks.assert_called_once_with( + *attrs) + + self.assertEqual(self.columns, columns) + self.assertEqual(list(data), list(self.data)) + class TestSetNetwork(TestNetwork): diff --git a/openstackclient/tests/unit/network/v2/test_network_agent.py b/openstackclient/tests/unit/network/v2/test_network_agent.py index 2fc0c04328..0d741e06a9 100644 --- a/openstackclient/tests/unit/network/v2/test_network_agent.py +++ b/openstackclient/tests/unit/network/v2/test_network_agent.py @@ -31,6 +31,48 @@ def setUp(self): self.network = self.app.client_manager.network +class TestAddNetworkToAgent(TestNetworkAgent): + + net = network_fakes.FakeNetwork.create_one_network() + agent = network_fakes.FakeNetworkAgent.create_one_network_agent() + + def setUp(self): + super(TestAddNetworkToAgent, self).setUp() + + self.network.get_agent = mock.Mock(return_value=self.agent) + self.network.find_network = mock.Mock(return_value=self.net) + self.network.name = self.network.find_network.name + self.network.add_dhcp_agent_to_network = mock.Mock() + self.cmd = network_agent.AddNetworkToAgent( + self.app, self.namespace) + + def test_show_no_options(self): + arglist = [] + verifylist = [] + + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_add_network_to_dhcp_agent(self): + arglist = [ + '--dhcp', + self.agent.id, + self.net.id + ] + verifylist = [ + ('dhcp', True), + ('agent_id', self.agent.id), + ('network', self.net.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.network.add_dhcp_agent_to_network.assert_called_once_with( + self.agent, self.net) + + class TestDeleteNetworkAgent(TestNetworkAgent): network_agents = ( @@ -66,7 +108,6 @@ def test_network_agent_delete(self): def test_multi_network_agents_delete(self): arglist = [] - verifylist = [] for n in self.network_agents: arglist.append(n.id) @@ -141,11 +182,37 @@ class TestListNetworkAgent(TestNetworkAgent): agent.binary, )) + network_agent_columns = ( + 'ID', + 'Host', + 'Admin State Up', + 'Alive', + ) + + network_agent_data = [] + + for agent in network_agents: + network_agent_data.append(( + agent.id, + agent.host, + network_agent._format_admin_state(agent.admin_state_up), + agent.alive, + )) + def setUp(self): super(TestListNetworkAgent, self).setUp() self.network.agents = mock.Mock( return_value=self.network_agents) + _testagent = \ + network_fakes.FakeNetworkAgent.create_one_network_agent() + self.network.get_agent = mock.Mock(return_value=_testagent) + + self._testnetwork = network_fakes.FakeNetwork.create_one_network() + self.network.find_network = mock.Mock(return_value=self._testnetwork) + self.network.network_hosting_dhcp_agents = mock.Mock( + return_value=self.network_agents) + # Get the command object to test self.cmd = network_agent.ListNetworkAgent(self.app, self.namespace) @@ -194,6 +261,66 @@ def test_network_agents_list_host(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + def test_network_agents_list_networks(self): + arglist = [ + '--network', self._testnetwork.id, + ] + verifylist = [ + ('network', self._testnetwork.id), + ] + + attrs = {self._testnetwork, } + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.network_hosting_dhcp_agents.assert_called_once_with( + *attrs) + self.assertEqual(self.network_agent_columns, columns) + self.assertEqual(list(self.network_agent_data), list(data)) + + +class TestRemoveNetworkFromAgent(TestNetworkAgent): + + net = network_fakes.FakeNetwork.create_one_network() + agent = network_fakes.FakeNetworkAgent.create_one_network_agent() + + def setUp(self): + super(TestRemoveNetworkFromAgent, self).setUp() + + self.network.get_agent = mock.Mock(return_value=self.agent) + self.network.find_network = mock.Mock(return_value=self.net) + self.network.name = self.network.find_network.name + self.network.remove_dhcp_agent_from_network = mock.Mock() + self.cmd = network_agent.RemoveNetworkFromAgent( + self.app, self.namespace) + + def test_show_no_options(self): + arglist = [] + verifylist = [] + + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_network_from_dhcp_agent(self): + arglist = [ + '--dhcp', + self.agent.id, + self.net.id + ] + verifylist = [ + ('dhcp', True), + ('agent_id', self.agent.id), + ('network', self.net.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.network.remove_dhcp_agent_from_network.assert_called_once_with( + self.agent, self.net) + # TODO(huanxuan): Also update by the new attribute name # "is_admin_state_up" after sdk 0.9.12 diff --git a/releasenotes/notes/bp-network-dhcp-adv-commands-e61bf8757f46dc93.yaml b/releasenotes/notes/bp-network-dhcp-adv-commands-e61bf8757f46dc93.yaml new file mode 100644 index 0000000000..ce3ab644f4 --- /dev/null +++ b/releasenotes/notes/bp-network-dhcp-adv-commands-e61bf8757f46dc93.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Add network dhcp-agent related commands ``network agent add network``, + ``network agent remove network``, ``network agent list --network`` and + ``network list --agent`` for adding/removing network to dhcp agent. + [Blueprint :oscbp:`network-dhcp-adv-commands`] diff --git a/setup.cfg b/setup.cfg index e18aa5c1e7..7ebe624a3f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -355,8 +355,10 @@ openstack.network.v2 = ip_floating_pool_list = openstackclient.network.v2.floating_ip_pool:ListIPFloatingPool + network_agent_add_network = openstackclient.network.v2.network_agent:AddNetworkToAgent network_agent_delete = openstackclient.network.v2.network_agent:DeleteNetworkAgent network_agent_list = openstackclient.network.v2.network_agent:ListNetworkAgent + network_agent_remove_network = openstackclient.network.v2.network_agent:RemoveNetworkFromAgent network_agent_set = openstackclient.network.v2.network_agent:SetNetworkAgent network_agent_show = openstackclient.network.v2.network_agent:ShowNetworkAgent From f5527877bb6dab09757b6692460bcc376b7d5ec3 Mon Sep 17 00:00:00 2001 From: Cedric Brandily Date: Fri, 16 Sep 2016 22:00:23 +0200 Subject: [PATCH 1614/3095] Enable to specify which vm fixed-ip to publish This change enables to specify which vm fixed-ip will be associated to a floating ip using: openstack server add floating ip --fixed-ip-address Closes-Bug: #1624524 Change-Id: I2ddb68c5873bfed7293b0e661d1adbe111681136 --- doc/source/command-objects/server.rst | 5 +++++ openstackclient/compute/v2/server.py | 9 ++++++++- .../tests/unit/compute/v2/test_server.py | 15 ++++++++++++--- ...specify-vm-ip-to-publish-85f7207740c0cc8d.yaml | 5 +++++ 4 files changed, 30 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/allow-to-specify-vm-ip-to-publish-85f7207740c0cc8d.yaml diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index b2ae965a7b..047bf1810b 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -33,9 +33,14 @@ Add floating IP address to server .. code:: bash openstack server add floating ip + [--fixed-ip-address ] +.. option:: --fixed-ip-address + + Fixed IP address to associate with this floating IP address + .. describe:: Server (name or ID) to receive the floating IP address diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index d33c631a17..3cffa0a94a 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -235,6 +235,12 @@ def get_parser(self, prog_name): help=_("Floating IP address (IP address only) to assign " "to server"), ) + parser.add_argument( + "--fixed-ip-address", + metavar="", + help=_("Fixed IP address to associate with this floating IP " + "address"), + ) return parser def take_action(self, parsed_args): @@ -243,7 +249,8 @@ def take_action(self, parsed_args): server = utils.find_resource( compute_client.servers, parsed_args.server) - server.add_floating_ip(parsed_args.ip_address) + server.add_floating_ip(parsed_args.ip_address, + parsed_args.fixed_ip_address) class AddServerSecurityGroup(command.Command): diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 249902bca4..cdda6a97bc 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -146,24 +146,33 @@ def setUp(self): 'add_floating_ip': None, } - def test_server_add_floating_ip(self): + def _test_server_add_floating_ip(self, extralist, fixed_ip_address): servers = self.setup_servers_mock(count=1) arglist = [ servers[0].id, '1.2.3.4', - ] + ] + extralist verifylist = [ ('server', servers[0].id), ('ip_address', '1.2.3.4'), + ('fixed_ip_address', fixed_ip_address), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - servers[0].add_floating_ip.assert_called_once_with('1.2.3.4') + servers[0].add_floating_ip.assert_called_once_with('1.2.3.4', + fixed_ip_address) self.assertIsNone(result) + def test_server_add_floating_ip(self): + self._test_server_add_floating_ip([], None) + + def test_server_add_floating_ip_to_fixed_ip(self): + extralist = ['--fixed-ip-address', '5.6.7.8'] + self._test_server_add_floating_ip(extralist, '5.6.7.8') + class TestServerAddSecurityGroup(TestServer): diff --git a/releasenotes/notes/allow-to-specify-vm-ip-to-publish-85f7207740c0cc8d.yaml b/releasenotes/notes/allow-to-specify-vm-ip-to-publish-85f7207740c0cc8d.yaml new file mode 100644 index 0000000000..3d0a48016b --- /dev/null +++ b/releasenotes/notes/allow-to-specify-vm-ip-to-publish-85f7207740c0cc8d.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``--fixed-ip-address`` option to the ``server add floating ip`` command + [Bug `1624524 `_] From 82a86d2d5857148eef96332761e4a88321f035fa Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Tue, 28 Feb 2017 10:27:02 -0600 Subject: [PATCH 1615/3095] Simplify logic around option lists in port set Use a common pattern to handle option pairs --XYZ and --no-XYZ for managing lists of attributes. This pattern looks at the presence of the option in parsed_args first and branches as necessary. Some specific steps are included for the SDK Network resources to reliably set the 'dirty' flag for changed attributes via one or both of the following: * iterate over lists of original resource attributes to force the creation of a new list object * use [].extend() rather than += to add to the existing list (substitute {}.update() for dicts) Change-Id: I0c3f9a52ffe1ae2b5b230cb13d6376dd9131aaf9 --- openstackclient/network/v2/port.py | 89 ++++++------ .../tests/functional/network/v2/test_port.py | 83 +++++++++-- .../tests/unit/network/v2/test_port.py | 136 ++++++++---------- 3 files changed, 182 insertions(+), 126 deletions(-) diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index eced93ceea..f77f566d4e 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -114,8 +114,6 @@ def _get_attrs(client_manager, parsed_args): )) if parsed_args.description is not None: attrs['description'] = parsed_args.description - if parsed_args.fixed_ip is not None: - attrs['fixed_ips'] = parsed_args.fixed_ip if parsed_args.device: attrs['device_id'] = parsed_args.device if parsed_args.device_owner is not None: @@ -124,8 +122,6 @@ def _get_attrs(client_manager, parsed_args): attrs['admin_state_up'] = True if parsed_args.disable: attrs['admin_state_up'] = False - if parsed_args.binding_profile is not None: - attrs['binding:profile'] = parsed_args.binding_profile if parsed_args.vnic_type is not None: attrs['binding:vnic_type'] = parsed_args.vnic_type if parsed_args.host: @@ -389,6 +385,12 @@ def take_action(self, parsed_args): _prepare_fixed_ips(self.app.client_manager, parsed_args) attrs = _get_attrs(self.app.client_manager, parsed_args) + if parsed_args.binding_profile is not None: + attrs['binding:profile'] = parsed_args.binding_profile + + if parsed_args.fixed_ip: + attrs['fixed_ips'] = parsed_args.fixed_ip + if parsed_args.security_group: attrs['security_group_ids'] = [client.find_security_group( sg, ignore_missing=False).id @@ -396,6 +398,7 @@ def take_action(self, parsed_args): parsed_args.security_group] elif parsed_args.no_security_group: attrs['security_group_ids'] = [] + if parsed_args.allowed_address_pairs: attrs['allowed_address_pairs'] = ( _convert_address_pairs(parsed_args)) @@ -674,48 +677,50 @@ def take_action(self, parsed_args): _prepare_fixed_ips(self.app.client_manager, parsed_args) attrs = _get_attrs(self.app.client_manager, parsed_args) obj = client.find_port(parsed_args.port, ignore_missing=False) - if 'binding:profile' in attrs: - # Do not modify attrs if both binding_profile/no_binding given - if not parsed_args.no_binding_profile: - tmp_binding_profile = copy.deepcopy(obj.binding_profile) - tmp_binding_profile.update(attrs['binding:profile']) - attrs['binding:profile'] = tmp_binding_profile - elif parsed_args.no_binding_profile: - attrs['binding:profile'] = {} - if 'fixed_ips' in attrs: - # When user unsets the fixed_ips, obj.fixed_ips = [{}]. - # Adding the obj.fixed_ips list to attrs['fixed_ips'] - # would therefore add an empty dictionary, while we need - # to append the attrs['fixed_ips'] iff there is some info - # in the obj.fixed_ips. Therefore I have opted for this `for` loop - # Do not modify attrs if fixed_ip/no_fixed_ip given - if not parsed_args.no_fixed_ip: - attrs['fixed_ips'] += [ip for ip in obj.fixed_ips if ip] - elif parsed_args.no_fixed_ip: - attrs['fixed_ips'] = [] - if parsed_args.security_group: - attrs['security_group_ids'] = [ - client.find_security_group(sg, ignore_missing=False).id for - sg in parsed_args.security_group] - if not parsed_args.no_security_group: - attrs['security_group_ids'] += obj.security_group_ids + if parsed_args.no_binding_profile: + attrs['binding:profile'] = {} + if parsed_args.binding_profile: + if 'binding:profile' not in attrs: + attrs['binding:profile'] = copy.deepcopy(obj.binding_profile) + attrs['binding:profile'].update(parsed_args.binding_profile) - elif parsed_args.no_security_group: + if parsed_args.no_fixed_ip: + attrs['fixed_ips'] = [] + if parsed_args.fixed_ip: + if 'fixed_ips' not in attrs: + # obj.fixed_ips = [{}] if no fixed IPs are set. + # Only append this to attrs['fixed_ips'] if actual fixed + # IPs are present to avoid adding an empty dict. + attrs['fixed_ips'] = [ip for ip in obj.fixed_ips if ip] + attrs['fixed_ips'].extend(parsed_args.fixed_ip) + + if parsed_args.no_security_group: attrs['security_group_ids'] = [] - - if (parsed_args.allowed_address_pairs and - parsed_args.no_allowed_address_pair): - attrs['allowed_address_pairs'] = ( - _convert_address_pairs(parsed_args)) - - elif parsed_args.allowed_address_pairs: - attrs['allowed_address_pairs'] = ( - [addr for addr in obj.allowed_address_pairs if addr] + - _convert_address_pairs(parsed_args)) - - elif parsed_args.no_allowed_address_pair: + if parsed_args.security_group: + if 'security_group_ids' not in attrs: + # NOTE(dtroyer): Get existing security groups, iterate the + # list to force a new list object to be + # created and make sure the SDK Resource + # marks the attribute 'dirty'. + attrs['security_group_ids'] = [ + id for id in obj.security_group_ids + ] + attrs['security_group_ids'].extend( + client.find_security_group(sg, ignore_missing=False).id + for sg in parsed_args.security_group + ) + + if parsed_args.no_allowed_address_pair: attrs['allowed_address_pairs'] = [] + if parsed_args.allowed_address_pairs: + if 'allowed_address_pairs' not in attrs: + attrs['allowed_address_pairs'] = ( + [addr for addr in obj.allowed_address_pairs if addr] + ) + attrs['allowed_address_pairs'].extend( + _convert_address_pairs(parsed_args) + ) client.update_port(obj, **attrs) diff --git a/openstackclient/tests/functional/network/v2/test_port.py b/openstackclient/tests/functional/network/v2/test_port.py index bd5eefa5ba..62c0cbe51b 100644 --- a/openstackclient/tests/functional/network/v2/test_port.py +++ b/openstackclient/tests/functional/network/v2/test_port.py @@ -20,7 +20,6 @@ class PortTests(base.TestCase): """Functional tests for port. """ NAME = uuid.uuid4().hex NETWORK_NAME = uuid.uuid4().hex - SG_NAME = uuid.uuid4().hex @classmethod def setUpClass(cls): @@ -112,29 +111,32 @@ def test_port_list(self): def test_port_set(self): """Test create, set, show, delete""" + name = uuid.uuid4().hex json_output = json.loads(self.openstack( 'port create -f json ' + '--network ' + self.NETWORK_NAME + ' ' + - '--description xyzpdq ' + '--description xyzpdq ' + '--disable ' + - self.NAME + name )) id1 = json_output.get('id') self.addCleanup(self.openstack, 'port delete ' + id1) - self.assertEqual(self.NAME, json_output.get('name')) + self.assertEqual(name, json_output.get('name')) self.assertEqual('xyzpdq', json_output.get('description')) self.assertEqual('DOWN', json_output.get('admin_state_up')) raw_output = self.openstack( - 'port set ' + '--enable ' + self.NAME) + 'port set ' + '--enable ' + + name + ) self.assertOutput('', raw_output) json_output = json.loads(self.openstack( - 'port show -f json ' + self.NAME + 'port show -f json ' + name )) sg_id = json_output.get('security_group_ids') - self.assertEqual(self.NAME, json_output.get('name')) + self.assertEqual(name, json_output.get('name')) self.assertEqual('xyzpdq', json_output.get('description')) self.assertEqual('UP', json_output.get('admin_state_up')) self.assertIsNotNone(json_output.get('mac_address')) @@ -144,7 +146,7 @@ def test_port_set(self): self.assertOutput('', raw_output) json_output = json.loads(self.openstack( - 'port show -f json ' + self.NAME + 'port show -f json ' + name )) self.assertEqual('', json_output.get('security_group_ids')) @@ -166,3 +168,68 @@ def test_port_admin_set(self): 'port show -f json ' + self.NAME )) self.assertEqual(json_output.get('mac_address'), '11:22:33:44:55:66') + + def test_port_set_sg(self): + """Test create, set, show, delete""" + sg_name1 = uuid.uuid4().hex + json_output = json.loads(self.openstack( + 'security group create -f json ' + + sg_name1 + )) + sg_id1 = json_output.get('id') + self.addCleanup(self.openstack, 'security group delete ' + sg_id1) + + sg_name2 = uuid.uuid4().hex + json_output = json.loads(self.openstack( + 'security group create -f json ' + + sg_name2 + )) + sg_id2 = json_output.get('id') + self.addCleanup(self.openstack, 'security group delete ' + sg_id2) + + name = uuid.uuid4().hex + json_output = json.loads(self.openstack( + 'port create -f json ' + + '--network ' + self.NETWORK_NAME + ' ' + + '--security-group ' + sg_name1 + ' ' + + name + )) + id1 = json_output.get('id') + self.addCleanup(self.openstack, 'port delete ' + id1) + self.assertEqual(name, json_output.get('name')) + self.assertEqual(sg_id1, json_output.get('security_group_ids')) + + raw_output = self.openstack( + 'port set ' + + '--security-group ' + sg_name2 + ' ' + + name + ) + self.assertOutput('', raw_output) + + json_output = json.loads(self.openstack( + 'port show -f json ' + name + )) + self.assertEqual(name, json_output.get('name')) + self.assertIn( + # TODO(dtroyer): output formatters should not mess with JSON! + sg_id1, + json_output.get('security_group_ids'), + ) + self.assertIn( + # TODO(dtroyer): output formatters should not mess with JSON! + sg_id2, + json_output.get('security_group_ids'), + ) + + raw_output = self.openstack( + 'port unset --security-group ' + sg_id1 + ' ' + id1) + self.assertOutput('', raw_output) + + json_output = json.loads(self.openstack( + 'port show -f json ' + name + )) + self.assertEqual( + # TODO(dtroyer): output formatters should do this on JSON! + sg_id2, + json_output.get('security_group_ids'), + ) diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index d2df5841fe..701af8790b 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -883,106 +883,105 @@ def setUp(self): # Get the command object to test self.cmd = port.SetPort(self.app, self.namespace) - def test_set_fixed_ip(self): + def test_set_port_defaults(self): arglist = [ - '--fixed-ip', 'ip-address=10.0.0.11', self._port.name, - '--no-fixed-ip', ] verifylist = [ - ('fixed_ip', [{'ip-address': '10.0.0.11'}]), ('port', self._port.name), ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) - attrs = { - 'fixed_ips': [{'ip_address': '10.0.0.11'}], - } + result = self.cmd.take_action(parsed_args) + attrs = {} self.network.update_port.assert_called_once_with(self._port, **attrs) self.assertIsNone(result) - def test_set_dns_name(self): + def test_set_port_fixed_ip(self): + _testport = network_fakes.FakePort.create_one_port( + {'fixed_ips': [{'ip_address': '0.0.0.1'}]}) + self.network.find_port = mock.Mock(return_value=_testport) arglist = [ - '--dns-name', '8.8.8.8', - self._port.name, + '--fixed-ip', 'ip-address=10.0.0.12', + _testport.name, ] verifylist = [ - ('dns_name', '8.8.8.8'), - ('port', self._port.name), + ('fixed_ip', [{'ip-address': '10.0.0.12'}]), + ('port', _testport.name), ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) attrs = { - 'dns_name': '8.8.8.8', + 'fixed_ips': [ + {'ip_address': '0.0.0.1'}, + {'ip_address': '10.0.0.12'}, + ], } - self.network.update_port.assert_called_once_with(self._port, **attrs) + self.network.update_port.assert_called_once_with(_testport, **attrs) self.assertIsNone(result) - def test_append_fixed_ip(self): + def test_set_port_fixed_ip_clear(self): _testport = network_fakes.FakePort.create_one_port( {'fixed_ips': [{'ip_address': '0.0.0.1'}]}) self.network.find_port = mock.Mock(return_value=_testport) arglist = [ '--fixed-ip', 'ip-address=10.0.0.12', + '--no-fixed-ip', _testport.name, ] verifylist = [ ('fixed_ip', [{'ip-address': '10.0.0.12'}]), - ('port', _testport.name), + ('no_fixed_ip', True) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) attrs = { 'fixed_ips': [ - {'ip_address': '10.0.0.12'}, {'ip_address': '0.0.0.1'}], + {'ip_address': '10.0.0.12'}, + ], } self.network.update_port.assert_called_once_with(_testport, **attrs) self.assertIsNone(result) - def test_overwrite_binding_profile(self): - _testport = network_fakes.FakePort.create_one_port( - {'binding_profile': {'lok_i': 'visi_on'}}) - self.network.find_port = mock.Mock(return_value=_testport) + def test_set_port_dns_name(self): arglist = [ - '--binding-profile', 'lok_i=than_os', - '--no-binding-profile', - _testport.name, + '--dns-name', '8.8.8.8', + self._port.name, ] verifylist = [ - ('binding_profile', {'lok_i': 'than_os'}), - ('no_binding_profile', True) + ('dns_name', '8.8.8.8'), + ('port', self._port.name), ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) + attrs = { - 'binding:profile': - {'lok_i': 'than_os'}, + 'dns_name': '8.8.8.8', } - self.network.update_port.assert_called_once_with(_testport, **attrs) + self.network.update_port.assert_called_once_with(self._port, **attrs) self.assertIsNone(result) - def test_overwrite_fixed_ip(self): + def test_set_port_overwrite_binding_profile(self): _testport = network_fakes.FakePort.create_one_port( - {'fixed_ips': [{'ip_address': '0.0.0.1'}]}) + {'binding_profile': {'lok_i': 'visi_on'}}) self.network.find_port = mock.Mock(return_value=_testport) arglist = [ - '--fixed-ip', 'ip-address=10.0.0.12', - '--no-fixed-ip', + '--binding-profile', 'lok_i=than_os', + '--no-binding-profile', _testport.name, ] verifylist = [ - ('fixed_ip', [{'ip-address': '10.0.0.12'}]), - ('no_fixed_ip', True) + ('binding_profile', {'lok_i': 'than_os'}), + ('no_binding_profile', True) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) attrs = { - 'fixed_ips': [ - {'ip_address': '10.0.0.12'}], + 'binding:profile': + {'lok_i': 'than_os'}, } self.network.update_port.assert_called_once_with(_testport, **attrs) self.assertIsNone(result) @@ -1006,7 +1005,7 @@ def test_overwrite_mac_address(self): self.network.update_port.assert_called_once_with(_testport, **attrs) self.assertIsNone(result) - def test_set_this(self): + def test_set_port_this(self): arglist = [ '--disable', '--no-fixed-ip', @@ -1031,7 +1030,7 @@ def test_set_this(self): self.network.update_port.assert_called_once_with(self._port, **attrs) self.assertIsNone(result) - def test_set_that(self): + def test_set_port_that(self): arglist = [ '--description', 'newDescription', '--enable', @@ -1065,22 +1064,7 @@ def test_set_that(self): self.network.update_port.assert_called_once_with(self._port, **attrs) self.assertIsNone(result) - def test_set_nothing(self): - arglist = [ - self._port.name, - ] - verifylist = [ - ('port', self._port.name), - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) - - attrs = {} - self.network.update_port.assert_called_once_with(self._port, **attrs) - self.assertIsNone(result) - - def test_set_invalid_json_binding_profile(self): + def test_set_port_invalid_json_binding_profile(self): arglist = [ '--binding-profile', '{"parent_name"}', 'test-port', @@ -1091,7 +1075,7 @@ def test_set_invalid_json_binding_profile(self): arglist, None) - def test_set_invalid_key_value_binding_profile(self): + def test_set_port_invalid_key_value_binding_profile(self): arglist = [ '--binding-profile', 'key', 'test-port', @@ -1102,7 +1086,7 @@ def test_set_invalid_key_value_binding_profile(self): arglist, None) - def test_set_mixed_binding_profile(self): + def test_set_port_mixed_binding_profile(self): arglist = [ '--binding-profile', 'foo=bar', '--binding-profile', '{"foo2": "bar2"}', @@ -1122,7 +1106,7 @@ def test_set_mixed_binding_profile(self): self.network.update_port.assert_called_once_with(self._port, **attrs) self.assertIsNone(result) - def test_set_security_group(self): + def test_set_port_security_group(self): sg = network_fakes.FakeSecurityGroup.create_one_security_group() self.network.find_security_group = mock.Mock(return_value=sg) arglist = [ @@ -1133,17 +1117,16 @@ def test_set_security_group(self): ('security_group', [sg.id]), ('port', self._port.name), ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) attrs = { 'security_group_ids': [sg.id], } self.network.update_port.assert_called_once_with(self._port, **attrs) self.assertIsNone(result) - def test_append_security_group(self): + def test_set_port_security_group_append(self): sg_1 = network_fakes.FakeSecurityGroup.create_one_security_group() sg_2 = network_fakes.FakeSecurityGroup.create_one_security_group() sg_3 = network_fakes.FakeSecurityGroup.create_one_security_group() @@ -1161,14 +1144,15 @@ def test_append_security_group(self): ('port', _testport.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) attrs = { - 'security_group_ids': [sg_2.id, sg_3.id, sg_1.id], + 'security_group_ids': [sg_1.id, sg_2.id, sg_3.id], } self.network.update_port.assert_called_once_with(_testport, **attrs) self.assertIsNone(result) - def test_set_no_security_groups(self): + def test_set_port_security_group_clear(self): arglist = [ '--no-security-group', self._port.name, @@ -1177,17 +1161,16 @@ def test_set_no_security_groups(self): ('no_security_group', True), ('port', self._port.name), ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) attrs = { 'security_group_ids': [], } self.network.update_port.assert_called_once_with(self._port, **attrs) self.assertIsNone(result) - def test_overwrite_security_group(self): + def test_set_port_security_group_replace(self): sg1 = network_fakes.FakeSecurityGroup.create_one_security_group() sg2 = network_fakes.FakeSecurityGroup.create_one_security_group() _testport = network_fakes.FakePort.create_one_port( @@ -1204,6 +1187,7 @@ def test_overwrite_security_group(self): ('no_security_group', True) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) attrs = { 'security_group_ids': [sg2.id], @@ -1211,7 +1195,7 @@ def test_overwrite_security_group(self): self.network.update_port.assert_called_once_with(_testport, **attrs) self.assertIsNone(result) - def test_set_allowed_address_pair(self): + def test_set_port_allowed_address_pair(self): arglist = [ '--allowed-address', 'ip-address=192.168.1.123', self._port.name, @@ -1230,7 +1214,7 @@ def test_set_allowed_address_pair(self): self.network.update_port.assert_called_once_with(self._port, **attrs) self.assertIsNone(result) - def test_append_allowed_address_pair(self): + def test_set_port_append_allowed_address_pair(self): _testport = network_fakes.FakePort.create_one_port( {'allowed_address_pairs': [{'ip_address': '192.168.1.123'}]}) self.network.find_port = mock.Mock(return_value=_testport) @@ -1253,7 +1237,7 @@ def test_append_allowed_address_pair(self): self.network.update_port.assert_called_once_with(_testport, **attrs) self.assertIsNone(result) - def test_overwrite_allowed_address_pair(self): + def test_set_port_overwrite_allowed_address_pair(self): _testport = network_fakes.FakePort.create_one_port( {'allowed_address_pairs': [{'ip_address': '192.168.1.123'}]}) self.network.find_port = mock.Mock(return_value=_testport) @@ -1277,7 +1261,7 @@ def test_overwrite_allowed_address_pair(self): self.network.update_port.assert_called_once_with(_testport, **attrs) self.assertIsNone(result) - def test_set_no_allowed_address_pairs(self): + def test_set_port_no_allowed_address_pairs(self): arglist = [ '--no-allowed-address', self._port.name, @@ -1296,7 +1280,7 @@ def test_set_no_allowed_address_pairs(self): self.network.update_port.assert_called_once_with(self._port, **attrs) self.assertIsNone(result) - def test_port_security_enabled(self): + def test_set_port_security_enabled(self): arglist = [ '--enable-port-security', self._port.id, @@ -1314,7 +1298,7 @@ def test_port_security_enabled(self): 'port_security_enabled': True, }) - def test_port_security_disabled(self): + def test_set_port_security_disabled(self): arglist = [ '--disable-port-security', self._port.id, From ef5a7caf85bd6169701371da67029457abdaf47f Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Fri, 17 Mar 2017 13:28:49 +0800 Subject: [PATCH 1616/3095] Support to add/remove multi users for "group add/remove user" Similar delete commands in OSC, we can also support add/remove multi users for one specified group, this review implement it. Change-Id: I8ccf99d4ee83a18778fa3ff5c0a42bc7c6ff21fb Implements: bp support-multi-add-remove --- doc/source/command-objects/group.rst | 10 +- openstackclient/identity/v3/group.py | 68 ++++++--- .../tests/unit/identity/v3/fakes.py | 38 +++++ .../tests/unit/identity/v3/test_group.py | 131 +++++++++++++----- ...ort-multi-add-remove-9516f72cfacea11a.yaml | 5 + 5 files changed, 193 insertions(+), 59 deletions(-) create mode 100644 releasenotes/notes/bp-support-multi-add-remove-9516f72cfacea11a.yaml diff --git a/doc/source/command-objects/group.rst b/doc/source/command-objects/group.rst index 3c3199cfeb..ac938efdc2 100644 --- a/doc/source/command-objects/group.rst +++ b/doc/source/command-objects/group.rst @@ -16,7 +16,7 @@ Add user to group [--group-domain ] [--user-domain ] - + [ ...] .. option:: --group-domain @@ -38,7 +38,8 @@ Add user to group .. describe:: - User to add to (name or ID) + User(s) to add to (name or ID) + (repeat option to add multiple users) group contains user ------------------- @@ -172,7 +173,7 @@ Remove user from group [--group-domain ] [--user-domain ] - + [ ...] .. option:: --group-domain @@ -194,7 +195,8 @@ Remove user from group .. describe:: - User to remove from (name or ID) + User(s) to remove from (name or ID) + (repeat option to remove multiple users) group set --------- diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py index b5f5d8ad88..39c8547c4e 100644 --- a/openstackclient/identity/v3/group.py +++ b/openstackclient/identity/v3/group.py @@ -44,7 +44,9 @@ def get_parser(self, prog_name): parser.add_argument( 'user', metavar='', - help=_('User to add to (name or ID)'), + nargs='+', + help=_('User(s) to add to (name or ID) ' + '(repeat option to add multiple users)'), ) common.add_group_domain_option_to_parser(parser) common.add_user_domain_option_to_parser(parser) @@ -53,20 +55,32 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - user_id = common.find_user(identity_client, - parsed_args.user, - parsed_args.user_domain).id group_id = common.find_group(identity_client, parsed_args.group, parsed_args.group_domain).id - try: - identity_client.users.add_to_group(user_id, group_id) - except Exception as e: - msg = _("%(user)s not added to group %(group)s: %(e)s") % { - 'user': parsed_args.user, + result = 0 + for i in parsed_args.user: + try: + user_id = common.find_user(identity_client, + i, + parsed_args.user_domain).id + identity_client.users.add_to_group(user_id, group_id) + except Exception as e: + result += 1 + msg = _("%(user)s not added to group %(group)s: %(e)s") % { + 'user': i, + 'group': parsed_args.group, + 'e': e, + } + LOG.error(msg) + if result > 0: + total = len(parsed_args.user) + msg = (_("%(result)s of %(total)s users not added to group " + "%(group)s.")) % { + 'result': result, + 'total': total, 'group': parsed_args.group, - 'e': e, } raise exceptions.CommandError(msg) @@ -286,7 +300,9 @@ def get_parser(self, prog_name): parser.add_argument( 'user', metavar='', - help=_('User to remove from (name or ID)'), + nargs='+', + help=_('User(s) to remove from (name or ID) ' + '(repeat option to remove multiple users)'), ) common.add_group_domain_option_to_parser(parser) common.add_user_domain_option_to_parser(parser) @@ -295,20 +311,32 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - user_id = common.find_user(identity_client, - parsed_args.user, - parsed_args.user_domain).id group_id = common.find_group(identity_client, parsed_args.group, parsed_args.group_domain).id - try: - identity_client.users.remove_from_group(user_id, group_id) - except Exception as e: - msg = _("%(user)s not removed from group %(group)s: %(e)s") % { - 'user': parsed_args.user, + result = 0 + for i in parsed_args.user: + try: + user_id = common.find_user(identity_client, + i, + parsed_args.user_domain).id + identity_client.users.remove_from_group(user_id, group_id) + except Exception as e: + result += 1 + msg = _("%(user)s not removed from group %(group)s: %(e)s") % { + 'user': i, + 'group': parsed_args.group, + 'e': e, + } + LOG.error(msg) + if result > 0: + total = len(parsed_args.user) + msg = (_("%(result)s of %(total)s users not removed from group " + "%(group)s.")) % { + 'result': result, + 'total': total, 'group': parsed_args.group, - 'e': e, } raise exceptions.CommandError(msg) diff --git a/openstackclient/tests/unit/identity/v3/fakes.py b/openstackclient/tests/unit/identity/v3/fakes.py index 01b5dede0f..139d90d5b8 100644 --- a/openstackclient/tests/unit/identity/v3/fakes.py +++ b/openstackclient/tests/unit/identity/v3/fakes.py @@ -770,6 +770,44 @@ def create_one_user(attrs=None): loaded=True) return user + @staticmethod + def create_users(attrs=None, count=2): + """Create multiple fake users. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of users to fake + :return: + A list of FakeResource objects faking the users + """ + users = [] + for i in range(0, count): + user = FakeUser.create_one_user(attrs) + users.append(user) + + return users + + @staticmethod + def get_users(users=None, count=2): + """Get an iterable MagicMock object with a list of faked users. + + If users list is provided, then initialize the Mock object with + the list. Otherwise create one. + + :param List users: + A list of FakeResource objects faking users + :param Integer count: + The number of users to be faked + :return + An iterable Mock object with side_effect set to a list of faked + users + """ + if users is None: + users = FakeUser.create_users(count) + + return mock.Mock(side_effect=users) + class FakeGroup(object): """Fake one or more group.""" diff --git a/openstackclient/tests/unit/identity/v3/test_group.py b/openstackclient/tests/unit/identity/v3/test_group.py index 5870e1dbc3..81722631b3 100644 --- a/openstackclient/tests/unit/identity/v3/test_group.py +++ b/openstackclient/tests/unit/identity/v3/test_group.py @@ -42,47 +42,78 @@ def setUp(self): class TestGroupAddUser(TestGroup): - group = identity_fakes.FakeGroup.create_one_group() - user = identity_fakes.FakeUser.create_one_user() + _group = identity_fakes.FakeGroup.create_one_group() + users = identity_fakes.FakeUser.create_users(count=2) def setUp(self): super(TestGroupAddUser, self).setUp() - self.groups_mock.get.return_value = self.group - self.users_mock.get.return_value = self.user + self.groups_mock.get.return_value = self._group + self.users_mock.get = ( + identity_fakes.FakeUser.get_users(self.users)) self.users_mock.add_to_group.return_value = None self.cmd = group.AddUserToGroup(self.app, None) def test_group_add_user(self): arglist = [ - self.group.name, - self.user.name, + self._group.name, + self.users[0].name, ] verifylist = [ - ('group', self.group.name), - ('user', self.user.name), + ('group', self._group.name), + ('user', [self.users[0].name]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.users_mock.add_to_group.assert_called_once_with( - self.user.id, self.group.id) + self.users[0].id, self._group.id) self.assertIsNone(result) - def test_group_add_user_with_error(self): - self.users_mock.add_to_group.side_effect = exceptions.CommandError() + def test_group_add_multi_users(self): arglist = [ - self.group.name, - self.user.name, + self._group.name, + self.users[0].name, + self.users[1].name, ] verifylist = [ - ('group', self.group.name), - ('user', self.user.name), + ('group', self._group.name), + ('user', [self.users[0].name, self.users[1].name]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.assertRaises(exceptions.CommandError, - self.cmd.take_action, parsed_args) + + result = self.cmd.take_action(parsed_args) + calls = [call(self.users[0].id, self._group.id), + call(self.users[1].id, self._group.id)] + self.users_mock.add_to_group.assert_has_calls(calls) + self.assertIsNone(result) + + @mock.patch.object(group.LOG, 'error') + def test_group_add_user_with_error(self, mock_error): + self.users_mock.add_to_group.side_effect = [ + exceptions.CommandError(), None] + arglist = [ + self._group.name, + self.users[0].name, + self.users[1].name, + ] + verifylist = [ + ('group', self._group.name), + ('user', [self.users[0].name, self.users[1].name]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + msg = "1 of 2 users not added to group %s." % self._group.name + self.assertEqual(msg, str(e)) + msg = ("%(user)s not added to group %(group)s: ") % { + 'user': self.users[0].name, + 'group': self._group.name, + } + mock_error.assert_called_once_with(msg) class TestGroupCheckUser(TestGroup): @@ -463,48 +494,78 @@ def test_group_list_long(self): class TestGroupRemoveUser(TestGroup): - group = identity_fakes.FakeGroup.create_one_group() - user = identity_fakes.FakeUser.create_one_user() + _group = identity_fakes.FakeGroup.create_one_group() + users = identity_fakes.FakeUser.create_users(count=2) def setUp(self): super(TestGroupRemoveUser, self).setUp() - self.groups_mock.get.return_value = self.group - self.users_mock.get.return_value = self.user + self.groups_mock.get.return_value = self._group + self.users_mock.get = ( + identity_fakes.FakeUser.get_users(self.users)) self.users_mock.remove_from_group.return_value = None self.cmd = group.RemoveUserFromGroup(self.app, None) def test_group_remove_user(self): arglist = [ - self.group.id, - self.user.id, + self._group.id, + self.users[0].id, ] verifylist = [ - ('group', self.group.id), - ('user', self.user.id), + ('group', self._group.id), + ('user', [self.users[0].id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.users_mock.remove_from_group.assert_called_once_with( - self.user.id, self.group.id) + self.users[0].id, self._group.id) self.assertIsNone(result) - def test_group_remove_user_with_error(self): - self.users_mock.remove_from_group.side_effect = ( - exceptions.CommandError()) + def test_group_remove_multi_users(self): arglist = [ - self.group.id, - self.user.id, + self._group.name, + self.users[0].name, + self.users[1].name, ] verifylist = [ - ('group', self.group.id), - ('user', self.user.id), + ('group', self._group.name), + ('user', [self.users[0].name, self.users[1].name]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.assertRaises(exceptions.CommandError, - self.cmd.take_action, parsed_args) + + result = self.cmd.take_action(parsed_args) + calls = [call(self.users[0].id, self._group.id), + call(self.users[1].id, self._group.id)] + self.users_mock.remove_from_group.assert_has_calls(calls) + self.assertIsNone(result) + + @mock.patch.object(group.LOG, 'error') + def test_group_remove_user_with_error(self, mock_error): + self.users_mock.remove_from_group.side_effect = [ + exceptions.CommandError(), None] + arglist = [ + self._group.id, + self.users[0].id, + self.users[1].id, + ] + verifylist = [ + ('group', self._group.id), + ('user', [self.users[0].id, self.users[1].id]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + msg = "1 of 2 users not removed from group %s." % self._group.id + self.assertEqual(msg, str(e)) + msg = ("%(user)s not removed from group %(group)s: ") % { + 'user': self.users[0].id, + 'group': self._group.id, + } + mock_error.assert_called_once_with(msg) class TestGroupSet(TestGroup): diff --git a/releasenotes/notes/bp-support-multi-add-remove-9516f72cfacea11a.yaml b/releasenotes/notes/bp-support-multi-add-remove-9516f72cfacea11a.yaml new file mode 100644 index 0000000000..83a7c03baa --- /dev/null +++ b/releasenotes/notes/bp-support-multi-add-remove-9516f72cfacea11a.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add support to add/remove multi users by ``group add/remove user`` command. + [Blueprint :oscbp:`support-multi-add-remove`] From 8e2bc9cb9c70f3d287bf25fb8d1a2b098c6ed3e5 Mon Sep 17 00:00:00 2001 From: Reedip Date: Tue, 21 Mar 2017 02:39:52 +0000 Subject: [PATCH 1617/3095] Fix Trivial Changes in [1] [1] had some trivial changes in the main code, which are being put forward in this patch, to consider future scenarios. [1]: I77a933f4b3ce875c63cef5b6a32aee78fd844b03 TrivialFix Change-Id: I8e8fbb194cd319e5605f09c94ae09e952d0961b1 --- doc/source/command-objects/network-agent.rst | 12 ++++++------ openstackclient/network/v2/network_agent.py | 16 ++++++++-------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/doc/source/command-objects/network-agent.rst b/doc/source/command-objects/network-agent.rst index d764f3de1c..f69d0ece69 100644 --- a/doc/source/command-objects/network-agent.rst +++ b/doc/source/command-objects/network-agent.rst @@ -25,15 +25,15 @@ Add network to an agent .. describe:: --dhcp - Add a network to DHCP agent. + Add a network to DHCP agent .. describe:: - Agent to which a network is added. (ID only) + Agent to which a network is added (ID only) .. describe:: - Network to be added to an agent. (ID or name) + Network to be added to an agent (ID or name) network agent delete -------------------- @@ -77,7 +77,7 @@ List network agents .. option:: --network - List agents hosting a network. (ID or name) + List agents hosting a network (ID or name) network agent set ----------------- @@ -144,8 +144,8 @@ Remove network from an agent .. describe:: - Agent to which a network is removed. (ID only) + Agent to which a network is removed (ID only) .. describe:: - Network to be removed from an agent. (ID or name) + Network to be removed from an agent (ID or name) diff --git a/openstackclient/network/v2/network_agent.py b/openstackclient/network/v2/network_agent.py index 7c2edb88e2..dd3fdc2433 100644 --- a/openstackclient/network/v2/network_agent.py +++ b/openstackclient/network/v2/network_agent.py @@ -56,20 +56,20 @@ def get_parser(self, prog_name): parser.add_argument( 'agent_id', metavar='', - help=_('Agent to which a network is added. (ID only)')) + help=_('Agent to which a network is added (ID only)')) parser.add_argument( 'network', metavar='', - help=_('Network to be added to an agent. (ID or name)')) + help=_('Network to be added to an agent (ID or name)')) return parser def take_action(self, parsed_args): client = self.app.client_manager.network agent = client.get_agent(parsed_args.agent_id) + network = client.find_network( + parsed_args.network, ignore_missing=False) if parsed_args.dhcp: - network = client.find_network( - parsed_args.network, ignore_missing=False) try: client.add_dhcp_agent_to_network(agent, network) except Exception: @@ -225,19 +225,19 @@ def get_parser(self, prog_name): parser.add_argument( 'agent_id', metavar='', - help=_('Agent to which a network is removed. (ID only)')) + help=_('Agent to which a network is removed (ID only)')) parser.add_argument( 'network', metavar='', - help=_('Network to be removed from an agent. (ID or name)')) + help=_('Network to be removed from an agent (ID or name)')) return parser def take_action(self, parsed_args): client = self.app.client_manager.network agent = client.get_agent(parsed_args.agent_id) + network = client.find_network( + parsed_args.network, ignore_missing=False) if parsed_args.dhcp: - network = client.find_network( - parsed_args.network, ignore_missing=False) try: client.remove_dhcp_agent_from_network(agent, network) except Exception: From 535def34473565135c44e7870aab03a4ac1614aa Mon Sep 17 00:00:00 2001 From: yfzhao Date: Tue, 21 Mar 2017 14:40:28 +0800 Subject: [PATCH 1618/3095] Remove log translations Log messages are no longer being translated. This removes all use of the _LE, _LI, and _LW translation markers to simplify logging and to avoid confusion with new contributions. Change-Id: I504de69b2e64250740ebcab432042a16f966fdbe Closes-Bug: #1674584 --- openstackclient/compute/v2/service.py | 5 ++--- openstackclient/i18n.py | 10 ---------- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index 7b18c2e505..b5b9bd5e1e 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.py @@ -22,7 +22,6 @@ from osc_lib import utils from openstackclient.i18n import _ -from openstackclient.i18n import _LE LOG = logging.getLogger(__name__) @@ -189,7 +188,7 @@ def take_action(self, parsed_args): cs.disable(parsed_args.host, parsed_args.service) except Exception: status = "enabled" if enabled else "disabled" - LOG.error(_LE("Failed to set service status to %s"), status) + LOG.error("Failed to set service status to %s", status) result += 1 force_down = None @@ -203,7 +202,7 @@ def take_action(self, parsed_args): force_down=force_down) except Exception: state = "down" if force_down else "up" - LOG.error(_LE("Failed to set service state to %s"), state) + LOG.error("Failed to set service state to %s", state) result += 1 if result > 0: diff --git a/openstackclient/i18n.py b/openstackclient/i18n.py index 1d09772cab..7564c60fb2 100644 --- a/openstackclient/i18n.py +++ b/openstackclient/i18n.py @@ -19,13 +19,3 @@ # The primary translation function using the well-known name "_" _ = _translators.primary - -# Translators for log levels. -# -# The abbreviated names are meant to reflect the usual use of a short -# name like '_'. The "L" is for "log" and the other letter comes from -# the level. -_LI = _translators.log_info -_LW = _translators.log_warning -_LE = _translators.log_error -_LC = _translators.log_critical From 832b2591cf299bf51c1922df1cdd4ba0aabe88c0 Mon Sep 17 00:00:00 2001 From: Sindhu Devale Date: Tue, 4 Oct 2016 13:02:41 -0500 Subject: [PATCH 1619/3095] OSC Extension Show Implement Neutron feature of Extension Show into OpenStack Client. Change-Id: Ifecb794838cb3bf8c2466d178345349db3cd4003 Implements: blueprint extension-show --- doc/source/command-objects/extension.rst | 17 ++++++ openstackclient/common/extension.py | 29 ++++++++++ .../tests/functional/common/test_extension.py | 42 ++++++++++++++ .../tests/unit/common/test_extension.py | 58 +++++++++++++++++++ .../bp-extension-show-6f7e31a27dad0dc9.yaml | 8 +++ setup.cfg | 1 + 6 files changed, 155 insertions(+) create mode 100644 openstackclient/tests/functional/common/test_extension.py create mode 100644 releasenotes/notes/bp-extension-show-6f7e31a27dad0dc9.yaml diff --git a/doc/source/command-objects/extension.rst b/doc/source/command-objects/extension.rst index dff30fa137..4d71bbc78f 100644 --- a/doc/source/command-objects/extension.rst +++ b/doc/source/command-objects/extension.rst @@ -39,3 +39,20 @@ List API extensions .. option:: --long List additional fields in output + +extension show +-------------- + +Show API extension + +.. program:: extension show +.. code:: bash + + openstack extension show + + +.. _extension_show: +.. describe:: + + Extension to display. Currently, only network extensions are supported. + (Name or Alias) diff --git a/openstackclient/common/extension.py b/openstackclient/common/extension.py index de48001609..07c407f654 100644 --- a/openstackclient/common/extension.py +++ b/openstackclient/common/extension.py @@ -134,3 +134,32 @@ def take_action(self, parsed_args): LOG.warning(message) return (columns, extension_tuples) + + +class ShowExtension(command.ShowOne): + _description = _("Show API extension") + + def get_parser(self, prog_name): + parser = super(ShowExtension, self).get_parser(prog_name) + parser.add_argument( + 'extension', + metavar='', + help=_('Extension to display. ' + 'Currently, only network extensions are supported. ' + '(Name or Alias)'), + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + columns = ('Alias', 'Description', 'Links', 'Name', + 'Namespace', 'Updated') + ext = str(parsed_args.extension) + obj = client.find_extension(ext) + dict_tuples = (utils.get_item_properties( + obj, + columns, + formatters={},) + ) + + return columns, dict_tuples diff --git a/openstackclient/tests/functional/common/test_extension.py b/openstackclient/tests/functional/common/test_extension.py new file mode 100644 index 0000000000..7c527eaecb --- /dev/null +++ b/openstackclient/tests/functional/common/test_extension.py @@ -0,0 +1,42 @@ +# Copyright (c) 2017, 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 json + +from openstackclient.tests.functional import base + + +class TestExtension(base.TestCase): + """Functional tests for extension.""" + + def test_extension_list(self): + """Test extension list.""" + json_output = json.loads(self.openstack( + 'extension list -f json ' + '--network') + ) + self.assertEqual( + 'Default Subnetpools', + json_output[0]['Name'], + ) + + def test_extension_show(self): + """Test extension show.""" + name = 'agent' + json_output = json.loads(self.openstack( + 'extension show -f json ' + name) + ) + self.assertEqual( + name, + json_output.get('Alias')) diff --git a/openstackclient/tests/unit/common/test_extension.py b/openstackclient/tests/unit/common/test_extension.py index bf856ed1c3..68fdf17d4f 100644 --- a/openstackclient/tests/unit/common/test_extension.py +++ b/openstackclient/tests/unit/common/test_extension.py @@ -19,6 +19,7 @@ from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes from openstackclient.tests.unit.network.v2 import fakes as network_fakes from openstackclient.tests.unit import utils +from openstackclient.tests.unit import utils as tests_utils from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes @@ -242,3 +243,60 @@ def test_extension_list_volume(self): ), ) self._test_extension_list_helper(arglist, verifylist, datalist) self.volume_extensions_mock.show_all.assert_called_with() + + +class TestExtensionShow(TestExtension): + extension_details = ( + network_fakes.FakeExtension.create_one_extension() + ) + + columns = ( + 'Alias', + 'Description', + 'Links', + 'Name', + 'Namespace', + 'Updated' + ) + + data = ( + extension_details.alias, + extension_details.description, + extension_details.links, + extension_details.name, + extension_details.namespace, + extension_details.updated + ) + + def setUp(self): + super(TestExtensionShow, self).setUp() + + self.cmd = extension.ShowExtension(self.app, None) + + self.app.client_manager.network.find_extension = mock.Mock( + return_value=self.extension_details) + + def test_show_no_options(self): + arglist = [] + verifylist = [] + + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_show_all_options(self): + arglist = [ + self.extension_details.alias, + ] + verifylist = [ + ('extension', self.extension_details.alias), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.app.client_manager.network.find_extension.assert_called_with( + self.extension_details.alias) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) diff --git a/releasenotes/notes/bp-extension-show-6f7e31a27dad0dc9.yaml b/releasenotes/notes/bp-extension-show-6f7e31a27dad0dc9.yaml new file mode 100644 index 0000000000..21ed6470cb --- /dev/null +++ b/releasenotes/notes/bp-extension-show-6f7e31a27dad0dc9.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Added `openstack extension show` command to allow users + to view the details of the extension. Currently works only for + network extensions. + + [Blueprint `extension-show `_] diff --git a/setup.cfg b/setup.cfg index 2aa8874019..9869d5a236 100644 --- a/setup.cfg +++ b/setup.cfg @@ -46,6 +46,7 @@ openstack.common = availability_zone_list = openstackclient.common.availability_zone:ListAvailabilityZone configuration_show = openstackclient.common.configuration:ShowConfiguration extension_list = openstackclient.common.extension:ListExtension + extension_show = openstackclient.common.extension:ShowExtension limits_show = openstackclient.common.limits:ShowLimits quota_set = openstackclient.common.quota:SetQuota quota_show = openstackclient.common.quota:ShowQuota From c3fee25a0728ec56453845546dec6f5dd317e81d Mon Sep 17 00:00:00 2001 From: Ankur Gupta Date: Fri, 24 Mar 2017 12:18:55 -0500 Subject: [PATCH 1620/3095] Doc: Fix link in network flavors Rendered incorrectly. Change-Id: I206dd8affa45864e406796147665c94684e9bf4d --- doc/source/command-objects/network-flavor.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/command-objects/network-flavor.rst b/doc/source/command-objects/network-flavor.rst index 4567682c5b..0723eb12d6 100644 --- a/doc/source/command-objects/network-flavor.rst +++ b/doc/source/command-objects/network-flavor.rst @@ -26,7 +26,7 @@ Create network flavor .. option:: --service-type Service type to which the flavor applies to: e.g. VPN. - (See openstack :ref:`network_service_provider_list`) (required) + (See openstack :ref:`\ `) (required) .. option:: --description From 709eac73fbf0691d8012052773eec73006adf704 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 14 Oct 2016 14:01:42 -0500 Subject: [PATCH 1621/3095] Fix volume transfers request commands * Fix volume transfer request accept to actually not crash when trying to call Volume API. * Fix volume transfer request accept syntax to have only one positional argument, which is the ID of the resource in the command * Change the output column order in volume transfer request list to have ID followed by Name then the remaining columns. Closes-bug: 1633582 Change-Id: I5cc005f039d171cc70859f60e7fe649b09ead229 --- doc/source/backwards-incompatible.rst | 11 +++ .../volume-transfer-request.rst | 21 +++--- .../volume/v1/test_transfer_request.py | 12 +-- .../volume/v2/test_transfer_request.py | 12 +-- .../unit/volume/v1/test_transfer_request.py | 56 +++++++++++--- .../unit/volume/v2/test_transfer_request.py | 56 +++++++++++--- .../volume/v1/volume_transfer_request.py | 75 ++++++++++++++----- .../volume/v2/volume_transfer_request.py | 75 ++++++++++++++----- .../notes/bug-1633582-df2bee534c2da7fc.yaml | 17 +++++ 9 files changed, 261 insertions(+), 74 deletions(-) create mode 100644 releasenotes/notes/bug-1633582-df2bee534c2da7fc.yaml diff --git a/doc/source/backwards-incompatible.rst b/doc/source/backwards-incompatible.rst index 4f38aa540d..bce8671736 100644 --- a/doc/source/backwards-incompatible.rst +++ b/doc/source/backwards-incompatible.rst @@ -16,6 +16,17 @@ this backwards incompatible change handling. Backwards Incompatible Changes ============================== +.. Carry this section as comments until 4.0 release +.. Release 4.0 +.. ----------- + +.. 1. Change ``volume transfer request accept`` to use new option ``--auth-key`` +.. rather than a second positional argument. + +.. * As of: 4.0 +.. * Remove in: <5.0> +.. * Commit: + Release 3.0 ----------- diff --git a/doc/source/command-objects/volume-transfer-request.rst b/doc/source/command-objects/volume-transfer-request.rst index 77c3642964..23cd3d3e1b 100644 --- a/doc/source/command-objects/volume-transfer-request.rst +++ b/doc/source/command-objects/volume-transfer-request.rst @@ -13,17 +13,19 @@ Accept volume transfer request .. code:: bash openstack volume transfer request accept - - + --auth-key + -.. _volume_transfer_request_accept: -.. describe:: +.. option:: --auth-key - Volume transfer request to accept (name or ID) + Volume transfer request authentication key + +.. _volume_transfer_request_accept: +.. describe:: -.. describe:: + Volume transfer request to accept (ID only) - Authentication key of transfer request + Non-admin users are only able to specify the transfer request by ID. volume transfer request create ------------------------------ @@ -65,7 +67,7 @@ Delete volume transfer request(s) volume transfer request list ---------------------------- -Lists all volume transfer requests. +Lists all volume transfer requests .. program:: volume transfer request list .. code:: bash @@ -75,8 +77,7 @@ Lists all volume transfer requests. .. option:: --all-projects - Shows detail for all projects. Admin only. - (defaults to False) + Include all projects (admin only) volume transfer request show ---------------------------- diff --git a/openstackclient/tests/functional/volume/v1/test_transfer_request.py b/openstackclient/tests/functional/volume/v1/test_transfer_request.py index e03cd717cd..3fe11913f6 100644 --- a/openstackclient/tests/functional/volume/v1/test_transfer_request.py +++ b/openstackclient/tests/functional/volume/v1/test_transfer_request.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import json import uuid from openstackclient.tests.functional.volume.v1 import common @@ -67,11 +68,12 @@ def test_volume_transfer_request_accept(self): self.assertNotEqual('', auth_key) # accept the volume transfer request - opts = self.get_opts(self.FIELDS) - raw_output = self.openstack( - 'volume transfer request accept ' + name + - ' ' + auth_key + opts) - self.assertEqual(name + '\n', raw_output) + json_output = json.loads(self.openstack( + 'volume transfer request accept -f json ' + + name + ' ' + + '--auth-key ' + auth_key + )) + self.assertEqual(name, json_output.get('name')) # the volume transfer will be removed by default after accepted # so just need to delete the volume here diff --git a/openstackclient/tests/functional/volume/v2/test_transfer_request.py b/openstackclient/tests/functional/volume/v2/test_transfer_request.py index 1791f8ac1f..99d91ac0e2 100644 --- a/openstackclient/tests/functional/volume/v2/test_transfer_request.py +++ b/openstackclient/tests/functional/volume/v2/test_transfer_request.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import json import uuid from openstackclient.tests.functional.volume.v2 import common @@ -67,11 +68,12 @@ def test_volume_transfer_request_accept(self): self.assertNotEqual('', auth_key) # accept the volume transfer request - opts = self.get_opts(self.FIELDS) - raw_output = self.openstack( - 'volume transfer request accept ' + name + - ' ' + auth_key + opts) - self.assertEqual(name + '\n', raw_output) + json_output = json.loads(self.openstack( + 'volume transfer request accept -f json ' + + name + ' ' + + '--auth-key ' + auth_key + )) + self.assertEqual(name, json_output.get('name')) # the volume transfer will be removed by default after accepted # so just need to delete the volume here diff --git a/openstackclient/tests/unit/volume/v1/test_transfer_request.py b/openstackclient/tests/unit/volume/v1/test_transfer_request.py index b3788d6ec8..4c013dc007 100644 --- a/openstackclient/tests/unit/volume/v1/test_transfer_request.py +++ b/openstackclient/tests/unit/volume/v1/test_transfer_request.py @@ -64,24 +64,62 @@ def setUp(self): def test_transfer_accept(self): arglist = [ + '--auth-key', 'key_value', self.volume_transfer.id, - 'auth_key', ] verifylist = [ ('transfer_request', self.volume_transfer.id), - ('auth_key', 'auth_key'), + ('auth_key', 'key_value'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.transfer_mock.get.assert_called_once_with( - self.volume_transfer.id) + self.volume_transfer.id, + ) self.transfer_mock.accept.assert_called_once_with( - self.volume_transfer.id, 'auth_key') + self.volume_transfer.id, + 'key_value', + ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_transfer_accept_deprecated(self): + arglist = [ + self.volume_transfer.id, + 'key_value', + ] + verifylist = [ + ('transfer_request', self.volume_transfer.id), + ('old_auth_key', 'key_value'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.transfer_mock.accept.assert_called_once_with( + self.volume_transfer.id, + 'key_value', + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_transfer_accept_no_option(self): + arglist = [ + self.volume_transfer.id, + ] + verifylist = [ + ('transfer_request', self.volume_transfer.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args, + ) + class TestTransferCreate(TestTransfer): @@ -219,7 +257,7 @@ def test_delete_multiple_transfers_with_exception(self): self.fail('CommandError should be raised.') except exceptions.CommandError as e: self.assertEqual('1 of 2 volume transfer requests failed ' - 'to delete.', str(e)) + 'to delete', str(e)) find_mock.assert_any_call( self.transfer_mock, self.volume_transfers[0].id) @@ -256,8 +294,8 @@ def test_transfer_list_without_argument(self): expected_columns = [ 'ID', - 'Volume', 'Name', + 'Volume', ] # confirming if all expected columns are present in the result. @@ -265,8 +303,8 @@ def test_transfer_list_without_argument(self): datalist = (( self.volume_transfers.id, - self.volume_transfers.volume_id, self.volume_transfers.name, + self.volume_transfers.volume_id, ), ) # confirming if all expected values are present in the result. @@ -295,8 +333,8 @@ def test_transfer_list_with_argument(self): expected_columns = [ 'ID', - 'Volume', 'Name', + 'Volume', ] # confirming if all expected columns are present in the result. @@ -304,8 +342,8 @@ def test_transfer_list_with_argument(self): datalist = (( self.volume_transfers.id, - self.volume_transfers.volume_id, self.volume_transfers.name, + self.volume_transfers.volume_id, ), ) # confirming if all expected values are present in the result. diff --git a/openstackclient/tests/unit/volume/v2/test_transfer_request.py b/openstackclient/tests/unit/volume/v2/test_transfer_request.py index 8cd6534ba7..37eed11e50 100644 --- a/openstackclient/tests/unit/volume/v2/test_transfer_request.py +++ b/openstackclient/tests/unit/volume/v2/test_transfer_request.py @@ -64,24 +64,62 @@ def setUp(self): def test_transfer_accept(self): arglist = [ + '--auth-key', 'key_value', self.volume_transfer.id, - 'auth_key', ] verifylist = [ ('transfer_request', self.volume_transfer.id), - ('auth_key', 'auth_key'), + ('auth_key', 'key_value'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.transfer_mock.get.assert_called_once_with( - self.volume_transfer.id) + self.volume_transfer.id, + ) self.transfer_mock.accept.assert_called_once_with( - self.volume_transfer.id, 'auth_key') + self.volume_transfer.id, + 'key_value', + ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_transfer_accept_deprecated(self): + arglist = [ + self.volume_transfer.id, + 'key_value', + ] + verifylist = [ + ('transfer_request', self.volume_transfer.id), + ('old_auth_key', 'key_value'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.transfer_mock.accept.assert_called_once_with( + self.volume_transfer.id, + 'key_value', + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_transfer_accept_no_option(self): + arglist = [ + self.volume_transfer.id, + ] + verifylist = [ + ('transfer_request', self.volume_transfer.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args, + ) + class TestTransferCreate(TestTransfer): @@ -219,7 +257,7 @@ def test_delete_multiple_transfers_with_exception(self): self.fail('CommandError should be raised.') except exceptions.CommandError as e: self.assertEqual('1 of 2 volume transfer requests failed ' - 'to delete.', str(e)) + 'to delete', str(e)) find_mock.assert_any_call( self.transfer_mock, self.volume_transfers[0].id) @@ -256,8 +294,8 @@ def test_transfer_list_without_argument(self): expected_columns = [ 'ID', - 'Volume', 'Name', + 'Volume', ] # confirming if all expected columns are present in the result. @@ -265,8 +303,8 @@ def test_transfer_list_without_argument(self): datalist = (( self.volume_transfers.id, - self.volume_transfers.volume_id, self.volume_transfers.name, + self.volume_transfers.volume_id, ), ) # confirming if all expected values are present in the result. @@ -295,8 +333,8 @@ def test_transfer_list_with_argument(self): expected_columns = [ 'ID', - 'Volume', 'Name', + 'Volume', ] # confirming if all expected columns are present in the result. @@ -304,8 +342,8 @@ def test_transfer_list_with_argument(self): datalist = (( self.volume_transfers.id, - self.volume_transfers.volume_id, self.volume_transfers.name, + self.volume_transfers.volume_id, ), ) # confirming if all expected values are present in the result. diff --git a/openstackclient/volume/v1/volume_transfer_request.py b/openstackclient/volume/v1/volume_transfer_request.py index f24d5a5667..f5b567b94a 100644 --- a/openstackclient/volume/v1/volume_transfer_request.py +++ b/openstackclient/volume/v1/volume_transfer_request.py @@ -14,6 +14,7 @@ """Volume v1 transfer action implementations""" +import argparse import logging from osc_lib.command import command @@ -34,22 +35,54 @@ def get_parser(self, prog_name): parser = super(AcceptTransferRequest, self).get_parser(prog_name) parser.add_argument( 'transfer_request', - metavar="", - help=_('Volume transfer request to accept (name or ID)'), + metavar="", + help=_('Volume transfer request to accept (ID only)'), + ) + parser.add_argument( + 'old_auth_key', + metavar="", + nargs="?", + help=argparse.SUPPRESS, ) parser.add_argument( - 'auth_key', - metavar="", - help=_('Authentication key of transfer request'), + '--auth-key', + metavar="", + help=_('Volume transfer request authentication key'), ) return parser def take_action(self, parsed_args): volume_client = self.app.client_manager.volume - transfer_request_id = utils.find_resource( - volume_client.transfers, parsed_args.transfer_request).id + + try: + transfer_request_id = utils.find_resource( + volume_client.transfers, + parsed_args.transfer_request + ).id + except exceptions.CommandError: + # Non-admin users will fail to lookup name -> ID so we just + # move on and attempt with the user-supplied information + transfer_request_id = parsed_args.transfer_request + + # Remain backward-compatible for the previous command layout + # TODO(dtroyer): Remove this back-compat in 4.0 or Oct 2017 + if not parsed_args.auth_key: + if parsed_args.old_auth_key: + # Move the old one into the correct place + parsed_args.auth_key = parsed_args.old_auth_key + self.log.warning(_( + 'Specifying the auth-key as a positional argument ' + 'has been deprecated. Please use the --auth-key ' + 'option in the future.' + )) + else: + msg = _("argument --auth-key is required") + raise exceptions.CommandError(msg) + transfer_accept = volume_client.transfers.accept( - transfer_request_id, parsed_args.auth_key) + transfer_request_id, + parsed_args.auth_key, + ) transfer_accept._info.pop("links", None) return zip(*sorted(six.iteritems(transfer_accept._info))) @@ -75,9 +108,12 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): volume_client = self.app.client_manager.volume volume_id = utils.find_resource( - volume_client.volumes, parsed_args.volume).id + volume_client.volumes, + parsed_args.volume, + ).id volume_transfer_request = volume_client.transfers.create( - volume_id, parsed_args.name, + volume_id, + parsed_args.name, ) volume_transfer_request._info.pop("links", None) @@ -104,7 +140,9 @@ def take_action(self, parsed_args): for t in parsed_args.transfer_request: try: transfer_request_id = utils.find_resource( - volume_client.transfers, t).id + volume_client.transfers, + t, + ).id volume_client.transfers.delete(transfer_request_id) except Exception as e: result += 1 @@ -115,7 +153,7 @@ def take_action(self, parsed_args): if result > 0: total = len(parsed_args.transfer_request) msg = (_("%(result)s of %(total)s volume transfer requests failed" - " to delete.") % {'result': result, 'total': total}) + " to delete") % {'result': result, 'total': total}) raise exceptions.CommandError(msg) @@ -129,20 +167,19 @@ def get_parser(self, prog_name): dest='all_projects', action="store_true", default=False, - help=_('Shows detail for all projects. Admin only. ' - '(defaults to False)') + help=_('Include all projects (admin only)'), ) return parser def take_action(self, parsed_args): - columns = ['ID', 'Volume ID', 'Name'] - column_headers = ['ID', 'Volume', 'Name'] + columns = ['ID', 'Name', 'Volume ID'] + column_headers = ['ID', 'Name', 'Volume'] volume_client = self.app.client_manager.volume volume_transfer_result = volume_client.transfers.list( detailed=True, - search_opts={'all_tenants': parsed_args.all_projects} + search_opts={'all_tenants': parsed_args.all_projects}, ) return (column_headers, ( @@ -165,7 +202,9 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): volume_client = self.app.client_manager.volume volume_transfer_request = utils.find_resource( - volume_client.transfers, parsed_args.transfer_request) + volume_client.transfers, + parsed_args.transfer_request, + ) volume_transfer_request._info.pop("links", None) return zip(*sorted(six.iteritems(volume_transfer_request._info))) diff --git a/openstackclient/volume/v2/volume_transfer_request.py b/openstackclient/volume/v2/volume_transfer_request.py index aefe594a31..2f531dc871 100644 --- a/openstackclient/volume/v2/volume_transfer_request.py +++ b/openstackclient/volume/v2/volume_transfer_request.py @@ -14,6 +14,7 @@ """Volume v2 transfer action implementations""" +import argparse import logging from osc_lib.command import command @@ -34,22 +35,54 @@ def get_parser(self, prog_name): parser = super(AcceptTransferRequest, self).get_parser(prog_name) parser.add_argument( 'transfer_request', - metavar="", - help=_('Volume transfer request to accept (name or ID)'), + metavar="", + help=_('Volume transfer request to accept (ID only)'), + ) + parser.add_argument( + 'old_auth_key', + metavar="", + nargs="?", + help=argparse.SUPPRESS, ) parser.add_argument( - 'auth_key', - metavar="", - help=_('Authentication key of transfer request'), + '--auth-key', + metavar="", + help=_('Volume transfer request authentication key'), ) return parser def take_action(self, parsed_args): volume_client = self.app.client_manager.volume - transfer_request_id = utils.find_resource( - volume_client.transfers, parsed_args.transfer_request).id + + try: + transfer_request_id = utils.find_resource( + volume_client.transfers, + parsed_args.transfer_request + ).id + except exceptions.CommandError: + # Non-admin users will fail to lookup name -> ID so we just + # move on and attempt with the user-supplied information + transfer_request_id = parsed_args.transfer_request + + # Remain backward-compatible for the previous command layout + # TODO(dtroyer): Remove this back-compat in 4.0 or Oct 2017 + if not parsed_args.auth_key: + if parsed_args.old_auth_key: + # Move the old one into the correct place + parsed_args.auth_key = parsed_args.old_auth_key + self.log.warning(_( + 'Specifying the auth-key as a positional argument ' + 'has been deprecated. Please use the --auth-key ' + 'option in the future.' + )) + else: + msg = _("argument --auth-key is required") + raise exceptions.CommandError(msg) + transfer_accept = volume_client.transfers.accept( - transfer_request_id, parsed_args.auth_key) + transfer_request_id, + parsed_args.auth_key, + ) transfer_accept._info.pop("links", None) return zip(*sorted(six.iteritems(transfer_accept._info))) @@ -75,9 +108,12 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): volume_client = self.app.client_manager.volume volume_id = utils.find_resource( - volume_client.volumes, parsed_args.volume).id + volume_client.volumes, + parsed_args.volume, + ).id volume_transfer_request = volume_client.transfers.create( - volume_id, parsed_args.name, + volume_id, + parsed_args.name, ) volume_transfer_request._info.pop("links", None) @@ -104,7 +140,9 @@ def take_action(self, parsed_args): for t in parsed_args.transfer_request: try: transfer_request_id = utils.find_resource( - volume_client.transfers, t).id + volume_client.transfers, + t, + ).id volume_client.transfers.delete(transfer_request_id) except Exception as e: result += 1 @@ -115,7 +153,7 @@ def take_action(self, parsed_args): if result > 0: total = len(parsed_args.transfer_request) msg = (_("%(result)s of %(total)s volume transfer requests failed" - " to delete.") % {'result': result, 'total': total}) + " to delete") % {'result': result, 'total': total}) raise exceptions.CommandError(msg) @@ -129,20 +167,19 @@ def get_parser(self, prog_name): dest='all_projects', action="store_true", default=False, - help=_('Shows detail for all projects. Admin only. ' - '(defaults to False)') + help=_('Include all projects (admin only)'), ) return parser def take_action(self, parsed_args): - columns = ['ID', 'Volume ID', 'Name'] - column_headers = ['ID', 'Volume', 'Name'] + columns = ['ID', 'Name', 'Volume ID'] + column_headers = ['ID', 'Name', 'Volume'] volume_client = self.app.client_manager.volume volume_transfer_result = volume_client.transfers.list( detailed=True, - search_opts={'all_tenants': parsed_args.all_projects} + search_opts={'all_tenants': parsed_args.all_projects}, ) return (column_headers, ( @@ -165,7 +202,9 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): volume_client = self.app.client_manager.volume volume_transfer_request = utils.find_resource( - volume_client.transfers, parsed_args.transfer_request) + volume_client.transfers, + parsed_args.transfer_request, + ) volume_transfer_request._info.pop("links", None) return zip(*sorted(six.iteritems(volume_transfer_request._info))) diff --git a/releasenotes/notes/bug-1633582-df2bee534c2da7fc.yaml b/releasenotes/notes/bug-1633582-df2bee534c2da7fc.yaml new file mode 100644 index 0000000000..f99457e4d2 --- /dev/null +++ b/releasenotes/notes/bug-1633582-df2bee534c2da7fc.yaml @@ -0,0 +1,17 @@ +--- +deprecations: + - | + ``volume transfer request accept`` has been changed to move the ``auth-key`` + positional argument to a requried option ``--auth-key``. This leaves + the transfer request ID as the only positional arguemnt, as per the + OpenStackClient command format. The old format is still functional, but is + dperecated and will be removed in the next major release. +fixes: + - | + Fix ``volume transfer request accept`` to not fail the transfer request + name/ID lookup for non-admin users as the Volume API does not allow non-admin + users access to transfers in other projects. + [Bug `1633582 `_] + - | + Change the output column order in ``volume transfer request list`` to have + ``ID`` followed by ``Name`` then the remaining columns. From 1c3cb0a3b54cdb6cd5b27e88372b65cfac844422 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Mon, 27 Mar 2017 15:52:04 -0500 Subject: [PATCH 1622/3095] Change noauth strategy for plugin loading Don't do it. os-client-config's plugin loading has been causing this pain for a long time, removing the KSC hack-around in o-c-c unmasked this again. So when auth is not reuired, just don't let o-c-c do any plugin loading at all. Ever. Of course, this shouldn't be in OSC either, but we have to do this until the equivalent fix lands in osc-lib, is released and makes it into the global requirements minimum version. Depends-on: Ie68c82f7b073012685f0513b615ab1bf00bc0c3a Change-Id: Ifdf65f3bb90fb923947a2cbe80a881d71a3fee56 --- openstackclient/shell.py | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index d7fe6ac193..4ec357cde0 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -175,24 +175,17 @@ def initialize_app(self, argv): def prepare_to_run_command(self, cmd): """Set up auth and API versions""" - # TODO(dtroyer): Move this to osc-lib - # NOTE(dtroyer): If auth is not required for a command, force fake - # token auth so KSA plugins are happy - - kwargs = {} - if not cmd.auth_required: - # Build fake token creds to keep ksa and o-c-c hushed - kwargs['auth_type'] = 'token_endpoint' - kwargs['auth'] = {} - kwargs['auth']['token'] = 'x' - kwargs['auth']['url'] = 'x' + # TODO(dtroyer): Move this to osc-lib, remove entire method when 1.4.0 + # release is minimum version is in global-requirements + # NOTE(dtroyer): If auth is not required for a command, skip + # get_one_Cloud()'s validation to avoid loading plugins + validate = cmd.auth_required # Validate auth options self.cloud = self.cloud_config.get_one_cloud( cloud=self.options.cloud, argparse=self.options, - validate=True, - **kwargs + validate=validate, ) # Push the updated args into ClientManager self.client_manager._cli_options = self.cloud From 9f471eede95b7c555c4b71673806bd11943460e0 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Mon, 20 Mar 2017 13:31:29 +0000 Subject: [PATCH 1623/3095] doc: Remove local fork of apidoc This is unnecessary as pbr has since been fixed. It was causing a broken build as it didn't respect the '[pbr] autodoc_tree_excludes' setting in setup.cfg. The 'openstackclient/volume/v3' directory is an empty module containing only an '__init__' file). Empty modules = unhappy autodoc, thus, this module is ignored. Change-Id: Ie355b14c14b7badccb5c25f7c17edbc5e4b3804f --- doc/ext/__init__.py | 0 doc/ext/apidoc.py | 43 ------------------------------------------- doc/source/conf.py | 5 ----- setup.cfg | 8 ++++++++ 4 files changed, 8 insertions(+), 48 deletions(-) delete mode 100644 doc/ext/__init__.py delete mode 100644 doc/ext/apidoc.py diff --git a/doc/ext/__init__.py b/doc/ext/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/doc/ext/apidoc.py b/doc/ext/apidoc.py deleted file mode 100644 index 5e18385a6d..0000000000 --- a/doc/ext/apidoc.py +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright 2014 OpenStack Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import os.path as path - -from sphinx import apidoc - - -# NOTE(blk-u): pbr will run Sphinx multiple times when it generates -# documentation. Once for each builder. To run this extension we use the -# 'builder-inited' hook that fires at the beginning of a Sphinx build. -# We use ``run_already`` to make sure apidocs are only generated once -# even if Sphinx is run multiple times. -run_already = False - - -def run_apidoc(app): - global run_already - if run_already: - return - run_already = True - - package_dir = path.abspath(path.join(app.srcdir, '..', '..', - 'openstackclient')) - source_dir = path.join(app.srcdir, 'api') - apidoc.main(['apidoc', package_dir, '-f', - '-H', 'openstackclient Modules', - '-o', source_dir]) - - -def setup(app): - app.connect('builder-inited', run_apidoc) diff --git a/doc/source/conf.py b/doc/source/conf.py index a244ea09a1..420291460f 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -22,10 +22,6 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))) -# NOTE(blk-u): Path for our Sphinx extension, remove when -# https://launchpad.net/bugs/1260495 is fixed. -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) - # -- General configuration ---------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. @@ -37,7 +33,6 @@ 'sphinx.ext.doctest', 'sphinx.ext.todo', 'oslosphinx', - 'ext.apidoc', 'stevedore.sphinxext', ] diff --git a/setup.cfg b/setup.cfg index 3a8d67246a..cadbe72df9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -687,6 +687,14 @@ openstack.volume.v3 = volume_transfer_request_list = openstackclient.volume.v2.volume_transfer_request:ListTransferRequest volume_transfer_request_show = openstackclient.volume.v2.volume_transfer_request:ShowTransferRequest +[pbr] +autodoc_tree_index_modules = True +autodoc_tree_excludes = + setup.py + openstackclient/volume/v3 + openstackclient/tests/ + openstackclient/tests/* + [build_sphinx] source-dir = doc/source build-dir = doc/build From 70170656fd4d3c8020391354b13dca63e9e024d1 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Mon, 20 Mar 2017 16:19:27 +0000 Subject: [PATCH 1624/3095] doc: Correct Sphinx warnings - Fix option-describe typos - Correct option and envvar markup, for commands that are using the reference form instead of the definition form or are marking up option arguments as options - Avoid duplicate commands - Fix some invalid docstrings - Fix some invalid indentation - Disable the murano plugin, which has invalid docs - Correct issues with- and track the network-topology spec - Include API modules in docs Change-Id: I3d5ed5e872540fe13f3e4bd5e9335829dc9a5226 --- doc/source/backwards-incompatible.rst | 12 +- .../consistency-group-snapshot.rst | 2 +- .../command-objects/consistency-group.rst | 2 +- doc/source/command-objects/endpoint.rst | 4 +- doc/source/command-objects/volume.rst | 16 +- doc/source/command-options.rst | 7 +- doc/source/index.rst | 2 +- doc/source/man/openstack.rst | 188 ++++++++++++------ doc/source/plugin-commands.rst | 9 +- doc/source/specs/commands.rst | 7 +- doc/source/specs/network-topology.rst | 2 +- openstackclient/compute/client.py | 1 + .../tests/unit/identity/v3/fakes.py | 4 +- openstackclient/tests/unit/image/v2/fakes.py | 6 +- 14 files changed, 169 insertions(+), 93 deletions(-) diff --git a/doc/source/backwards-incompatible.rst b/doc/source/backwards-incompatible.rst index 4f38aa540d..54f29bc358 100644 --- a/doc/source/backwards-incompatible.rst +++ b/doc/source/backwards-incompatible.rst @@ -46,7 +46,7 @@ Releases Before 3.0 2. should not be optional for command `openstack service create` Previously, the command was `openstack service create --type `, - whereas now it is: `openstack service create --name ` + whereas now it is: `openstack service create --name `. This bug also affected python-keystoneclient, and keystone. * In favor of: making a positional argument. @@ -170,12 +170,12 @@ Releases Before 3.0 * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1546065 * Commit: https://review.openstack.org/#/c/281088/ -12. should be optional for command `openstack compute agent set` +12. ` ` should be optional for command `openstack + compute agent set` - Previously, the command was `openstack compute agent set `, - whereas now it is: `openstack compute agent set --version - --url - --md5hash `. + Previously, the command was `openstack compute agent set + `, whereas now it is: `openstack compute agent set --version + --url --md5hash `. * In favor of: making optional. * As of: NA diff --git a/doc/source/command-objects/consistency-group-snapshot.rst b/doc/source/command-objects/consistency-group-snapshot.rst index 3d455038ec..29d5065663 100644 --- a/doc/source/command-objects/consistency-group-snapshot.rst +++ b/doc/source/command-objects/consistency-group-snapshot.rst @@ -27,7 +27,7 @@ Create new consistency group snapshot. Description of this consistency group snapshot .. _consistency_group_snapshot_create-snapshot-name: -.. option:: +.. describe:: Name of new consistency group snapshot (default to None) diff --git a/doc/source/command-objects/consistency-group.rst b/doc/source/command-objects/consistency-group.rst index 232cf8e4fb..57082c6df8 100644 --- a/doc/source/command-objects/consistency-group.rst +++ b/doc/source/command-objects/consistency-group.rst @@ -62,7 +62,7 @@ Create new consistency group. (not available if creating consistency group from source) .. _consistency_group_create-name: -.. option:: +.. describe:: Name of new consistency group (default to None) diff --git a/doc/source/command-objects/endpoint.rst b/doc/source/command-objects/endpoint.rst index b98055a1df..02a75bea1f 100644 --- a/doc/source/command-objects/endpoint.rst +++ b/doc/source/command-objects/endpoint.rst @@ -11,7 +11,7 @@ Create new endpoint *Identity version 2 only* -.. program:: endpoint create +.. program:: endpoint create (v2) .. code:: bash openstack endpoint create @@ -44,7 +44,7 @@ Create new endpoint *Identity version 3 only* -.. program:: endpoint create +.. program:: endpoint create (v3) .. code:: bash openstack endpoint create diff --git a/doc/source/command-objects/volume.rst b/doc/source/command-objects/volume.rst index 67598e63c3..85a920ebab 100644 --- a/doc/source/command-objects/volume.rst +++ b/doc/source/command-objects/volume.rst @@ -37,18 +37,18 @@ Create new volume Set the type of volume - Select :option:`\` from the available types as shown + Select ```` from the available types as shown by ``volume type list``. .. option:: --image - Use :option:`\` as source of volume (name or ID) + Use ```` as source of volume (name or ID) This is commonly used to create a boot volume for a server. .. option:: --snapshot - Use :option:`\` as source of volume (name or ID) + Use ```` as source of volume (name or ID) .. option:: --source @@ -72,7 +72,7 @@ Create new volume .. option:: --availability-zone - Create volume in :option:`\` + Create volume in ```` .. option:: --consistency-group @@ -163,7 +163,7 @@ List volumes .. option:: --project - Filter results by :option:`\` (name or ID) (admin only) + Filter results by ```` (name or ID) (admin only) *Volume version 2 only* @@ -177,7 +177,7 @@ List volumes .. option:: --user - Filter results by :option:`\` (name or ID) (admin only) + Filter results by ```` (name or ID) (admin only) *Volume version 2 only* @@ -337,8 +337,8 @@ Set volume properties (repeat option to set multiple image properties) Image properties are copied along with the image when creating a volume - using :option:`--image`. Note that these properties are immutable on the - image itself, this option updates the copy attached to this volume. + using ``--image``. Note that these properties are immutable on the image + itself, this option updates the copy attached to this volume. *Volume version 2 only* diff --git a/doc/source/command-options.rst b/doc/source/command-options.rst index faa82bee21..8d1d1c4e69 100644 --- a/doc/source/command-options.rst +++ b/doc/source/command-options.rst @@ -16,9 +16,10 @@ new command without understanding why or why not that instance is correct. The :doc:`Human Interface Guide ` describes the guildelines for option names and usage. In short: - * All option names shall be GNU-style long names (two leading dashes). - * Some global options may have short names, generally limited to those defined - in support libraries such as ``cliff``. + +* All option names shall be GNU-style long names (two leading dashes). +* Some global options may have short names, generally limited to those defined + in support libraries such as ``cliff``. General Command Options ======================= diff --git a/doc/source/index.rst b/doc/source/index.rst index 6af49c52f0..ae0ee0ffb5 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -57,6 +57,7 @@ Developer Documentation command-errors command-logs specs/commands + api/modules Project Goals ------------- @@ -92,5 +93,4 @@ Indices and Tables ================== * :ref:`genindex` -* :ref:`modindex` * :ref:`search` diff --git a/doc/source/man/openstack.rst b/doc/source/man/openstack.rst index 5793c6ee85..3021ef8756 100644 --- a/doc/source/man/openstack.rst +++ b/doc/source/man/openstack.rst @@ -58,95 +58,146 @@ OPTIONS :program:`openstack` recognizes the following global options: -:option:`--os-cloud` +.. option:: --os-cloud + :program:`openstack` will look for a ``clouds.yaml`` file that contains a cloud configuration to use for authentication. See CLOUD CONFIGURATION below for more information. -:option:`--os-auth-type` +.. option:: --os-auth-type + The authentication plugin type to use when connecting to the Identity service. + If this option is not set, :program:`openstack` will attempt to guess the authentication method to use based on the other options. - If this option is set, its version must match :option:`--os-identity-api-version` -:option:`--os-auth-url` + If this option is set, its version must match + :option:`--os-identity-api-version` + +.. option:: --os-auth-url + Authentication URL -:option:`--os-url` +.. option:: --os-url + Service URL, when using a service token for authentication -:option:`--os-domain-name` | :option:`--os-domain-id` - Domain-level authorization scope (name or ID) +.. option:: --os-domain-name -:option:`--os-project-name` | :option:`--os-project-id` - Project-level authentication scope (name or ID) + Domain-level authorization scope (by name) -:option:`--os-project-domain-name` | :option:`--os-project-domain-id` - Domain name or ID containing project +.. option:: --os-domain-id + + Domain-level authorization scope (by ID) + +.. option:: --os-project-name + + Project-level authentication scope (by name) + +.. option:: --os-project-id + + Project-level authentication scope (by ID) + +.. option:: --os-project-domain-name + + Domain name containing project + +.. option:: --os-project-domain-id + + Domain ID containing project + +.. option:: --os-username -:option:`--os-username` Authentication username -:option:`--os-password` +.. option:: --os-password + Authentication password -:option:`--os-token` +.. option:: --os-token + Authenticated token or service token -:option:`--os-user-domain-name` | :option:`--os-user-domain-id` - Domain name or ID containing user +.. option:: --os-user-domain-name + + Domain name containing user + +.. option:: --os-user-domain-id + + Domain ID containing user + +.. option:: --os-trust-id -:option:`--os-trust-id` ID of the trust to use as a trustee user -:option:`--os-default-domain` +.. option:: --os-default-domain + Default domain ID (Default: 'default') -:option:`--os-region-name` +.. option:: --os-region-name + Authentication region name -:option:`--os-cacert` +.. option:: --os-cacert + CA certificate bundle file -:option:`--verify` | :option:`--insecure` +.. option:: --verify` | :option:`--insecure + Verify or ignore server certificate (default: verify) -:option:`--os-cert` +.. option:: --os-cert + Client certificate bundle file -:option:`--os-key` +.. option:: --os-key + Client certificate key file -:option:`--os-identity-api-version` +.. option:: --os-identity-api-version + Identity API version (Default: 2.0) -:option:`--os-XXXX-api-version` - Additional API version options will be available depending on the installed API libraries. +.. option:: --os-XXXX-api-version + + Additional API version options will be available depending on the installed + API libraries. + +.. option:: --os-interface -:option:`--os-interface` Interface type. Valid options are `public`, `admin` and `internal`. -:option:`--os-profile` +.. option:: --os-profile + Performance profiling HMAC key for encrypting context data This key should be the value of one of the HMAC keys defined in the configuration files of OpenStack services to be traced. -:option:`--os-beta-command` +.. option:: --os-beta-command + Enable beta commands which are subject to change -:option:`--log-file` +.. option:: --log-file + Specify a file to log output. Disabled by default. -:option:`-v, --verbose` +.. option:: -v, --verbose + Increase verbosity of output. Can be repeated. -:option:`-q, --quiet` +.. option:: -q, --quiet + Suppress output except warnings and errors -:option:`--debug` +.. option:: --debug + Show tracebacks on errors and set verbosity to debug +.. option:: --help + + Show help message and exit + COMMANDS ======== @@ -160,14 +211,16 @@ To get a description of a specific command:: Note that the set of commands shown will vary depending on the API versions that are in effect at that time. For example, to force the display of the -Identity v3 commands: +Identity v3 commands:: openstack --os-identity-api-version 3 --help -:option:`complete` +.. option:: complete + Print the bash completion functions for the current command set. -:option:`help ` +.. option:: help + Print help for an individual command Additional information on the OpenStackClient command structure and arguments @@ -328,64 +381,85 @@ ENVIRONMENT VARIABLES The following environment variables can be set to alter the behaviour of :program:`openstack`. Most of them have corresponding command-line options that take precedence if set. -:envvar:`OS_CLOUD` +.. envvar:: OS_CLOUD + The name of a cloud configuration in ``clouds.yaml``. -:envvar:`OS_AUTH_PLUGIN` +.. envvar:: OS_AUTH_PLUGIN + The authentication plugin to use when connecting to the Identity service, its version must match the Identity API version -:envvar:`OS_AUTH_URL` +.. envvar:: OS_AUTH_URL + Authentication URL -:envvar:`OS_URL` +.. envvar:: OS_URL + Service URL (when using the service token) -:envvar:`OS_DOMAIN_NAME` +.. envvar:: OS_DOMAIN_NAME + Domain-level authorization scope (name or ID) -:envvar:`OS_PROJECT_NAME` +.. envvar:: OS_PROJECT_NAME + Project-level authentication scope (name or ID) -:envvar:`OS_PROJECT_DOMAIN_NAME` +.. envvar:: OS_PROJECT_DOMAIN_NAME + Domain name or ID containing project -:envvar:`OS_USERNAME` +.. envvar:: OS_USERNAME + Authentication username -:envvar:`OS_TOKEN` +.. envvar:: OS_TOKEN + Authenticated or service token -:envvar:`OS_PASSWORD` +.. envvar:: OS_PASSWORD + Authentication password -:envvar:`OS_USER_DOMAIN_NAME` +.. envvar:: OS_USER_DOMAIN_NAME + Domain name or ID containing user -:envvar:`OS_TRUST_ID` +.. envvar:: OS_TRUST_ID + ID of the trust to use as a trustee user -:envvar:`OS_DEFAULT_DOMAIN` +.. envvar:: OS_DEFAULT_DOMAIN + Default domain ID (Default: 'default') -:envvar:`OS_REGION_NAME` +.. envvar:: OS_REGION_NAME + Authentication region name -:envvar:`OS_CACERT` +.. envvar:: OS_CACERT + CA certificate bundle file -:envvar:`OS_CERT` +.. envvar:: OS_CERT + Client certificate bundle file -:envvar:`OS_KEY` +.. envvar:: OS_KEY + Client certificate key file -:envvar:`OS_IDENTITY_API_VERSION` +.. envvar:: OS_IDENTITY_API_VERSION + Identity API version (Default: 2.0) -:envvar:`OS_XXXX_API_VERSION` - Additional API version options will be available depending on the installed API libraries. +.. envvar:: OS_XXXX_API_VERSION + + Additional API version options will be available depending on the installed + API libraries. + +.. envvar:: OS_INTERFACE -:envvar:`OS_INTERFACE` Interface type. Valid options are `public`, `admin` and `internal`. diff --git a/doc/source/plugin-commands.rst b/doc/source/plugin-commands.rst index 182f5392c6..69d31c22b3 100644 --- a/doc/source/plugin-commands.rst +++ b/doc/source/plugin-commands.rst @@ -62,11 +62,10 @@ mistral .. list-plugins:: openstack.workflow_engine.v2 :detailed: -murano ------- - -.. list-plugins:: openstack.application_catalog.v1 - :detailed: +.. murano +.. # the murano docs cause warnings and a broken docs build +.. # .. list-plugins:: openstack.application_catalog.v1 +.. # :detailed: neutron ------- diff --git a/doc/source/specs/commands.rst b/doc/source/specs/commands.rst index 55bf947651..5ae0e84069 100644 --- a/doc/source/specs/commands.rst +++ b/doc/source/specs/commands.rst @@ -19,14 +19,15 @@ Objects Specs Add specifications for new objects based on the ``example`` object. -* ``example``: (**example API name**) example object description - Actions Specs ------------- Add specifications for new actions based on the ``example`` action. -* ``example`` - example action description +.. toctree:: + :maxdepth: 1 + + network-topology Commands Specs -------------- diff --git a/doc/source/specs/network-topology.rst b/doc/source/specs/network-topology.rst index a0218a1e1b..6789ee975f 100755 --- a/doc/source/specs/network-topology.rst +++ b/doc/source/specs/network-topology.rst @@ -38,7 +38,7 @@ Show network topology details openstack network topology show -.. _network_topology_show-network +.. _network_topology_show-network: .. describe:: Show network topology for a specific network (name or ID) diff --git a/openstackclient/compute/client.py b/openstackclient/compute/client.py index c5b364b238..b4b463b412 100644 --- a/openstackclient/compute/client.py +++ b/openstackclient/compute/client.py @@ -96,6 +96,7 @@ def check_api_version(check_version): """Validate version supplied by user Returns: + * True if version is OK * False if the version has not been checked and the previous plugin check should be performed diff --git a/openstackclient/tests/unit/identity/v3/fakes.py b/openstackclient/tests/unit/identity/v3/fakes.py index 139d90d5b8..291f977d4e 100644 --- a/openstackclient/tests/unit/identity/v3/fakes.py +++ b/openstackclient/tests/unit/identity/v3/fakes.py @@ -729,7 +729,7 @@ def get_credentials(credentials=None, count=2): A list of FakeResource objects faking credentials :param Integer count: The number of credentials to be faked - :return + :return: An iterable Mock object with side_effect set to a list of faked credentials """ @@ -867,7 +867,7 @@ def get_groups(groups=None, count=2): A list of FakeResource objects faking groups :param Integer count: The number of groups to be faked - :return + :return: An iterable Mock object with side_effect set to a list of faked groups """ diff --git a/openstackclient/tests/unit/image/v2/fakes.py b/openstackclient/tests/unit/image/v2/fakes.py index 8e2f587d29..7f3f02df1f 100644 --- a/openstackclient/tests/unit/image/v2/fakes.py +++ b/openstackclient/tests/unit/image/v2/fakes.py @@ -238,7 +238,7 @@ def get_images(images=None, count=2): A list of FakeResource objects faking images :param Integer count: The number of images to be faked - :return + :return: An iterable Mock object with side_effect set to a list of faked images """ @@ -253,7 +253,7 @@ def get_image_columns(image=None): :param image: A FakeResource objects faking image - :return + :return: A tuple which may include the following keys: ('id', 'name', 'owner', 'protected', 'visibility', 'tags') """ @@ -267,7 +267,7 @@ def get_image_data(image=None): :param image: A FakeResource objects faking image - :return + :return: A tuple which may include the following values: ('image-123', 'image-foo', 'admin', False, 'public', 'bar, baz') """ From f45aa9079d0f24467af3da3f0023ceef267ee7ad Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 16 Mar 2017 16:53:33 +0000 Subject: [PATCH 1625/3095] Use Sphinx 1.5 warning-is-error This will ensure doc warnings don't make their way in. Change-Id: I9a007ad89f3a2219feb960fee858bf70c1643416 --- setup.cfg | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index cadbe72df9..3bd484ce97 100644 --- a/setup.cfg +++ b/setup.cfg @@ -696,9 +696,10 @@ autodoc_tree_excludes = openstackclient/tests/* [build_sphinx] +all-files = 1 +warning-is-error = 1 source-dir = doc/source build-dir = doc/build -all_files = 1 [upload_sphinx] upload-dir = doc/build/html From c49c1257352b367a377d0a51af932ffd856a6a4d Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 30 Mar 2017 00:31:55 +0000 Subject: [PATCH 1626/3095] Updated from global requirements Change-Id: Ic9a5bd518d1b2d4a7979716b9809ce236292205f --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 6d8ca54b5b..f9bae0543e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ six>=1.9.0 # MIT Babel>=2.3.4 # BSD cliff>=2.3.0 # Apache-2.0 keystoneauth1>=2.18.0 # Apache-2.0 -openstacksdk>=0.9.13 # Apache-2.0 +openstacksdk>=0.9.14 # Apache-2.0 osc-lib>=1.2.0 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 oslo.utils>=3.20.0 # Apache-2.0 From 6d259e5d18392e8fe100d89f2479981cb860761d Mon Sep 17 00:00:00 2001 From: Ankur Gupta Date: Thu, 30 Mar 2017 11:11:40 -0500 Subject: [PATCH 1627/3095] Trivial Fix Release note fix. Extra space looks odd when built. TrivialFix Change-Id: I5b5d0f66246e25fcb3619992a96558744cf943ca --- releasenotes/notes/bp-extension-show-6f7e31a27dad0dc9.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/releasenotes/notes/bp-extension-show-6f7e31a27dad0dc9.yaml b/releasenotes/notes/bp-extension-show-6f7e31a27dad0dc9.yaml index 21ed6470cb..535f3a982e 100644 --- a/releasenotes/notes/bp-extension-show-6f7e31a27dad0dc9.yaml +++ b/releasenotes/notes/bp-extension-show-6f7e31a27dad0dc9.yaml @@ -4,5 +4,4 @@ features: Added `openstack extension show` command to allow users to view the details of the extension. Currently works only for network extensions. - [Blueprint `extension-show `_] From 53ba05325ad5e580e62addb7f4b405b96ad01d80 Mon Sep 17 00:00:00 2001 From: Cedric Brandily Date: Thu, 23 Mar 2017 15:24:38 +0100 Subject: [PATCH 1628/3095] Enable to create legacy router Some deployments create by default HA routers, this change enables to force the creation of a legacy router using: openstack router create --no-ha ... Closes-Bug: #1675514 Change-Id: I78f7dc3640a2acfdaf085e0e387b30373e8415f1 --- doc/source/command-objects/router.rst | 6 +++++- openstackclient/network/v2/router.py | 12 ++++++++++-- .../tests/unit/network/v2/test_router.py | 15 +++++++++++---- ...-to-create-legacy-router-cb4dcb44dde74684.yaml | 5 +++++ 4 files changed, 31 insertions(+), 7 deletions(-) create mode 100644 releasenotes/notes/allow-to-create-legacy-router-cb4dcb44dde74684.yaml diff --git a/doc/source/command-objects/router.rst b/doc/source/command-objects/router.rst index 9e8007af92..29131861d5 100644 --- a/doc/source/command-objects/router.rst +++ b/doc/source/command-objects/router.rst @@ -64,7 +64,7 @@ Create new router [--project [--project-domain ]] [--enable | --disable] [--distributed] - [--ha] + [--ha | --no-ha] [--description ] [--availability-zone-hint ] @@ -94,6 +94,10 @@ Create new router Create a highly available router +.. option:: --no-ha + + Create a legacy router + .. option:: --description Set router description diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index f46c8696ab..f322d5d1f2 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -183,11 +183,17 @@ def get_parser(self, prog_name): default=False, help=_("Create a distributed router") ) - parser.add_argument( + ha_group = parser.add_mutually_exclusive_group() + ha_group.add_argument( '--ha', action='store_true', help=_("Create a highly available router") ) + ha_group.add_argument( + '--no-ha', + action='store_true', + help=_("Create a legacy router") + ) parser.add_argument( '--description', metavar='', @@ -216,7 +222,9 @@ def take_action(self, parsed_args): attrs = _get_attrs(self.app.client_manager, parsed_args) if parsed_args.ha: - attrs['ha'] = parsed_args.ha + attrs['ha'] = True + if parsed_args.no_ha: + attrs['ha'] = False obj = client.create_router(**attrs) display_columns, columns = _get_columns(obj) diff --git a/openstackclient/tests/unit/network/v2/test_router.py b/openstackclient/tests/unit/network/v2/test_router.py index b837afd1e4..a4f91997c4 100644 --- a/openstackclient/tests/unit/network/v2/test_router.py +++ b/openstackclient/tests/unit/network/v2/test_router.py @@ -181,16 +181,17 @@ def test_create_default_options(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) - def test_create_with_ha_option(self): + def _test_create_with_ha_options(self, option, ha): arglist = [ - '--ha', + option, self.new_router.name, ] verifylist = [ ('name', self.new_router.name), ('enable', True), ('distributed', False), - ('ha', True), + ('ha', ha), + ('no_ha', not ha), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -199,11 +200,17 @@ def test_create_with_ha_option(self): self.network.create_router.assert_called_once_with(**{ 'admin_state_up': True, 'name': self.new_router.name, - 'ha': True, + 'ha': ha, }) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_create_with_ha_option(self): + self._test_create_with_ha_options('--ha', True) + + def test_create_with_no_ha_option(self): + self._test_create_with_ha_options('--no-ha', False) + def test_create_with_AZ_hints(self): arglist = [ self.new_router.name, diff --git a/releasenotes/notes/allow-to-create-legacy-router-cb4dcb44dde74684.yaml b/releasenotes/notes/allow-to-create-legacy-router-cb4dcb44dde74684.yaml new file mode 100644 index 0000000000..77a8b50385 --- /dev/null +++ b/releasenotes/notes/allow-to-create-legacy-router-cb4dcb44dde74684.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``--no-ha`` option to the ``router create`` command + [Bug `1675514 `_] From 61cde9c8e85182073b53e3736f8568f2a6d1453c Mon Sep 17 00:00:00 2001 From: Jens Rosenboom Date: Wed, 29 Mar 2017 14:17:49 +0000 Subject: [PATCH 1629/3095] Fix block-device-mapping when volume_size is empty The Nova API responds with an validation error when a bdm is submitted containing an empty volume_size. So instead omit that attribute when it is empty. Change-Id: Iba905fca8c440a03e828c20922f3b813bba3fa3a Closes-Bug: 1677236 --- openstackclient/compute/v2/server.py | 2 +- openstackclient/tests/unit/compute/v2/test_server.py | 3 +-- releasenotes/notes/bug-1677236-7de9d11c3f0fb5ed.yaml | 6 ++++++ 3 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/bug-1677236-7de9d11c3f0fb5ed.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 1fe5bb0d10..3ae7516765 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -544,7 +544,7 @@ def take_action(self, parsed_args): else: mapping['source_type'] = 'volume' mapping['destination_type'] = 'volume' - if len(dev_map) > 2: + if len(dev_map) > 2 and dev_map[2]: mapping['volume_size'] = dev_map[2] if len(dev_map) > 3: mapping['delete_on_termination'] = dev_map[3] diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 7691ef5945..a0716e4c85 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -817,8 +817,7 @@ def test_server_create_with_block_device_mapping(self): 'uuid': real_volume_mapping.split(':', 1)[0], 'destination_type': 'volume', 'source_type': 'volume', - 'delete_on_termination': '0', - 'volume_size': '' + 'delete_on_termination': '0' }], nics=[], scheduler_hints={}, diff --git a/releasenotes/notes/bug-1677236-7de9d11c3f0fb5ed.yaml b/releasenotes/notes/bug-1677236-7de9d11c3f0fb5ed.yaml new file mode 100644 index 0000000000..d67cc5c9c8 --- /dev/null +++ b/releasenotes/notes/bug-1677236-7de9d11c3f0fb5ed.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Fix creating a server with a block-device-mapping when volume_size + is empty. + [Bug `1677236 `_] From 341f07582ef5aa782c817ab2feab5828ac15c003 Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Sat, 1 Apr 2017 14:52:21 +0800 Subject: [PATCH 1630/3095] Add help commands withouth auth in functional A special scenairo is that users want to check the commands help message, but don't set authentication info at all. Add a related functional test case to cover it. Change-Id: I7b09701df24d6f6dfcf369f89212f72e753be6e4 --- .../tests/functional/common/test_help.py | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/openstackclient/tests/functional/common/test_help.py b/openstackclient/tests/functional/common/test_help.py index 211c52b1de..e31d3b869c 100644 --- a/openstackclient/tests/functional/common/test_help.py +++ b/openstackclient/tests/functional/common/test_help.py @@ -10,6 +10,8 @@ # License for the specific language governing permissions and limitations # under the License. +import os + from openstackclient.tests.functional import base @@ -71,3 +73,48 @@ def test_networking_commands_help(self): self.assertIn('List networks', raw_output) raw_output = self.openstack('network create --help') self.assertIn('Create new network', raw_output) + + def test_commands_help_no_auth(self): + """Check help commands without auth info.""" + # Pop all auth info + auth_info = {key: os.environ.pop(key) + for key in os.environ.keys() + if key.startswith('OS_')} + + raw_output = self.openstack('help') + self.assertIn('usage: openstack', raw_output) + raw_output = self.openstack('--help') + self.assertIn('usage: openstack', raw_output) + + raw_output = self.openstack('help network list') + self.assertIn('List networks', raw_output) + raw_output = self.openstack('network list --help') + self.assertIn('List networks', raw_output) + + raw_output = self.openstack('help volume list') + self.assertIn('List volumes', raw_output) + raw_output = self.openstack('volume list --help') + self.assertIn('List volumes', raw_output) + + raw_output = self.openstack('help server list') + self.assertIn('List servers', raw_output) + raw_output = self.openstack('server list --help') + self.assertIn('List servers', raw_output) + + raw_output = self.openstack('help user list') + self.assertIn('List users', raw_output) + raw_output = self.openstack('user list --help') + self.assertIn('List users', raw_output) + + raw_output = self.openstack('help image list') + self.assertIn('List available images', raw_output) + raw_output = self.openstack('image list --help') + self.assertIn('List available images', raw_output) + + raw_output = self.openstack('help container list') + self.assertIn('List containers', raw_output) + raw_output = self.openstack('container list --help') + self.assertIn('List containers', raw_output) + + # Restore auth info + os.environ.update(auth_info) From b51310a4bb5997137a4b6c0cf3517f481e178474 Mon Sep 17 00:00:00 2001 From: Shashank Kumar Shankar Date: Mon, 28 Nov 2016 21:10:52 +0000 Subject: [PATCH 1631/3095] Introduce neutron flavor associate, disassociate to OSC This patch introduces network flavor associate and disassociate to OSC. The following neutron equivalent commands are implemented in OSC: - neutron flavor-associate - neutron flavor-disassociate Change-Id: Icba4dbf7300a36353142586359059cd6784049dc --- doc/source/command-objects/network-flavor.rst | 44 ++++++++++ openstackclient/network/v2/network_flavor.py | 57 +++++++++++++ .../network/v2/test_network_flavor.py | 51 +++++++++++ .../unit/network/v2/test_network_flavor.py | 84 +++++++++++++++++++ ...utron-client-flavors-81387171f67a3c82.yaml | 5 ++ setup.cfg | 2 + 6 files changed, 243 insertions(+) create mode 100644 releasenotes/notes/neutron-client-flavors-81387171f67a3c82.yaml diff --git a/doc/source/command-objects/network-flavor.rst b/doc/source/command-objects/network-flavor.rst index 0723eb12d6..2d23bf056a 100644 --- a/doc/source/command-objects/network-flavor.rst +++ b/doc/source/command-objects/network-flavor.rst @@ -8,6 +8,28 @@ service flavors. Network v2 +network flavor add profile +-------------------------- + +Add network flavor to service profile + +.. program:: network flavor add profile +.. code:: bash + + openstack network flavor add profile + + + +.. describe:: + + Flavor to which service profile is added. (Name or ID) + +.. describe:: + + Service profile to be added to flavor. (ID only) + +.. _network_flavor_add_profile: + network flavor create --------------------- @@ -85,6 +107,28 @@ List network flavors .. _network_flavor_list: +network flavor remove profile +----------------------------- + +Remove network flavor from service profile + +.. program:: network flavor remove profile +.. code:: bash + + openstack network flavor remove profile + + + +.. describe:: + + Flavor from which service profile is removed. (Name or ID) + +.. describe:: + + Service profile to be removed from flavor. (ID only) + +.. _network_flavor_remove_profile: + network flavor set ------------------ diff --git a/openstackclient/network/v2/network_flavor.py b/openstackclient/network/v2/network_flavor.py index 3a3324c09d..c9d368bfc1 100644 --- a/openstackclient/network/v2/network_flavor.py +++ b/openstackclient/network/v2/network_flavor.py @@ -58,6 +58,34 @@ def _get_attrs(client_manager, parsed_args): return attrs +class AddNetworkFlavorToProfile(command.Command): + _description = _("Add a service profile to a network flavor") + + def get_parser(self, prog_name): + parser = super( + AddNetworkFlavorToProfile, self).get_parser(prog_name) + parser.add_argument( + 'flavor', + metavar="", + help=_("Network flavor (name or ID)") + ) + parser.add_argument( + 'service_profile', + metavar="", + help=_("Service profile (ID only)") + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj_flavor = client.find_flavor( + parsed_args.flavor, ignore_missing=False) + obj_service_profile = client.find_service_profile( + parsed_args.service_profile, ignore_missing=False) + client.associate_flavor_with_service_profile( + obj_flavor, obj_service_profile) + + # TODO(dasanind): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. class CreateNetworkFlavor(command.ShowOne): @@ -175,6 +203,35 @@ def take_action(self, parsed_args): ) for s in data)) +class RemoveNetworkFlavorFromProfile(command.Command): + _description = _( + "Remove service profile from network flavor") + + def get_parser(self, prog_name): + parser = super( + RemoveNetworkFlavorFromProfile, self).get_parser(prog_name) + parser.add_argument( + 'flavor', + metavar="", + help=_("Network flavor (name or ID)") + ) + parser.add_argument( + 'service_profile', + metavar="", + help=_("Service profile (ID only)") + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj_flavor = client.find_flavor( + parsed_args.flavor, ignore_missing=False) + obj_service_profile = client.find_service_profile( + parsed_args.service_profile, ignore_missing=False) + client.disassociate_flavor_from_service_profile( + obj_flavor, obj_service_profile) + + # TODO(dasanind): Use only the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. class SetNetworkFlavor(command.Command): diff --git a/openstackclient/tests/functional/network/v2/test_network_flavor.py b/openstackclient/tests/functional/network/v2/test_network_flavor.py index b2fc2eaea3..743407905f 100644 --- a/openstackclient/tests/functional/network/v2/test_network_flavor.py +++ b/openstackclient/tests/functional/network/v2/test_network_flavor.py @@ -20,6 +20,57 @@ class NetworkFlavorTests(base.TestCase): """Functional tests for network flavor.""" + def test_add_remove_network_flavor_profile(self): + """Test add and remove network flavor to/from profile""" + + # Create Flavor + name1 = uuid.uuid4().hex + cmd_output1 = json.loads(self.openstack( + 'network flavor create -f json --description testdescription ' + '--enable --service-type L3_ROUTER_NAT ' + name1, + )) + flavor_id = cmd_output1.get('id') + + # Create Service Flavor + cmd_output2 = json.loads(self.openstack( + 'network flavor profile create -f json --description ' + + 'fakedescription' + ' --enable --metainfo ' + 'Extrainfo' + )) + service_profile_id = cmd_output2.get('id') + + self.addCleanup(self.openstack, 'network flavor delete ' + + flavor_id) + self.addCleanup(self.openstack, 'network flavor profile delete ' + + service_profile_id) + # Add flavor to service profile + self.openstack( + 'network flavor add profile ' + + flavor_id + ' ' + service_profile_id + ) + + cmd_output4 = json.loads(self.openstack( + 'network flavor show -f json ' + flavor_id + )) + service_profile_ids1 = cmd_output4.get('service_profile_ids') + + # Assert + self.assertIn(service_profile_id, service_profile_ids1) + + # Cleanup + # Remove flavor from service profile + self.openstack( + 'network flavor remove profile ' + + flavor_id + ' ' + service_profile_id + ) + + cmd_output6 = json.loads(self.openstack( + 'network flavor show -f json ' + flavor_id + )) + service_profile_ids2 = cmd_output6.get('service_profile_ids') + + # Assert + self.assertNotIn(service_profile_id, service_profile_ids2) + def test_network_flavor_delete(self): """Test create, delete multiple""" name1 = uuid.uuid4().hex diff --git a/openstackclient/tests/unit/network/v2/test_network_flavor.py b/openstackclient/tests/unit/network/v2/test_network_flavor.py index 11e27841d4..896a172530 100644 --- a/openstackclient/tests/unit/network/v2/test_network_flavor.py +++ b/openstackclient/tests/unit/network/v2/test_network_flavor.py @@ -37,6 +37,48 @@ def setUp(self): self.domains_mock = self.app.client_manager.identity.domains +class TestAddNetworkFlavorToProfile(TestNetworkFlavor): + + network_flavor = \ + network_fakes.FakeNetworkFlavor.create_one_network_flavor() + service_profile = \ + network_fakes.FakeNetworkFlavorProfile.create_one_service_profile() + + def setUp(self): + super(TestAddNetworkFlavorToProfile, self).setUp() + self.network.find_flavor = mock.Mock(return_value=self.network_flavor) + self.network.find_service_profile = mock.Mock( + return_value=self.service_profile) + self.network.associate_flavor_with_service_profile = mock.Mock() + + self.cmd = network_flavor.AddNetworkFlavorToProfile( + self.app, self.namespace) + + def test_show_no_options(self): + arglist = [] + verifylist = [] + + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_add_flavor_to_service_profile(self): + arglist = [ + self.network_flavor.id, + self.service_profile.id + ] + verifylist = [ + ('flavor', self.network_flavor.id), + ('service_profile', self.service_profile.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.network.associate_flavor_with_service_profile.\ + assert_called_once_with(self.network_flavor, self.service_profile) + + class TestCreateNetworkFlavor(TestNetworkFlavor): project = identity_fakes_v3.FakeProject.create_one_project() @@ -281,6 +323,48 @@ def test_network_flavor_list(self): self.assertEqual(self.data, list(data)) +class TestRemoveNetworkFlavorFromProfile(TestNetworkFlavor): + + network_flavor = \ + network_fakes.FakeNetworkFlavor.create_one_network_flavor() + service_profile = \ + network_fakes.FakeNetworkFlavorProfile.create_one_service_profile() + + def setUp(self): + super(TestRemoveNetworkFlavorFromProfile, self).setUp() + self.network.find_flavor = mock.Mock(return_value=self.network_flavor) + self.network.find_service_profile = mock.Mock( + return_value=self.service_profile) + self.network.disassociate_flavor_from_service_profile = mock.Mock() + + self.cmd = network_flavor.RemoveNetworkFlavorFromProfile( + self.app, self.namespace) + + def test_show_no_options(self): + arglist = [] + verifylist = [] + + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_remove_flavor_from_service_profile(self): + arglist = [ + self.network_flavor.id, + self.service_profile.id + ] + verifylist = [ + ('flavor', self.network_flavor.id), + ('service_profile', self.service_profile.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.network.disassociate_flavor_from_service_profile.\ + assert_called_once_with(self.network_flavor, self.service_profile) + + class TestShowNetworkFlavor(TestNetworkFlavor): # The network flavor to show. diff --git a/releasenotes/notes/neutron-client-flavors-81387171f67a3c82.yaml b/releasenotes/notes/neutron-client-flavors-81387171f67a3c82.yaml new file mode 100644 index 0000000000..484393c9a2 --- /dev/null +++ b/releasenotes/notes/neutron-client-flavors-81387171f67a3c82.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``network flavor add profile`` and ``network flavor remove profile`` commands. + [Blueprint :oscbp:`neutron-client-flavors`] diff --git a/setup.cfg b/setup.cfg index 3bd484ce97..d003e7d8dd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -369,9 +369,11 @@ openstack.network.v2 = network_auto_allocated_topology_create = openstackclient.network.v2.network_auto_allocated_topology:CreateAutoAllocatedTopology network_auto_allocated_topology_delete = openstackclient.network.v2.network_auto_allocated_topology:DeleteAutoAllocatedTopology + network_flavor_add_profile = openstackclient.network.v2.network_flavor:AddNetworkFlavorToProfile network_flavor_create = openstackclient.network.v2.network_flavor:CreateNetworkFlavor network_flavor_delete = openstackclient.network.v2.network_flavor:DeleteNetworkFlavor network_flavor_list = openstackclient.network.v2.network_flavor:ListNetworkFlavor + network_flavor_remove_profile = openstackclient.network.v2.network_flavor:RemoveNetworkFlavorFromProfile network_flavor_set = openstackclient.network.v2.network_flavor:SetNetworkFlavor network_flavor_show = openstackclient.network.v2.network_flavor:ShowNetworkFlavor From 21510ac1a94eeb8de218a0edfe81db5ef0437249 Mon Sep 17 00:00:00 2001 From: Cedric Brandily Date: Fri, 31 Mar 2017 23:11:47 +0200 Subject: [PATCH 1632/3095] Enable to add/remove port to/from a server This change enables to add/remove a specific port to/from a server using the new commands: openstack server add port openstack server remove port Closes-Bug: #1678137 Change-Id: I6ee57df089235ccc1fb9d38316bd484956b1134d --- doc/source/command-objects/server.rst | 40 ++++++++ openstackclient/compute/v2/server.py | 66 +++++++++++++ .../tests/unit/compute/v2/test_server.py | 96 +++++++++++++++++++ ...-add-remove-vm-ports-273593d7cc1982de.yaml | 6 ++ setup.cfg | 2 + 5 files changed, 210 insertions(+) create mode 100644 releasenotes/notes/allow-to-add-remove-vm-ports-273593d7cc1982de.yaml diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index 047bf1810b..5efc057ff3 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -49,6 +49,26 @@ Add floating IP address to server Floating IP address (IP address only) to assign to server +server add port +--------------- + +Add port to server + +.. program:: server add port +.. code:: bash + + openstack server add port + + + +.. describe:: + + Server to add the port to (name or ID) + +.. describe:: + + Port to add to the server (name or ID) + server add security group ------------------------- @@ -523,6 +543,26 @@ Remove floating IP address from server Floating IP address (IP address only) to remove from server +server remove port +------------------ + +Remove port from server + +.. program:: server remove port +.. code:: bash + + openstack server remove port + + + +.. describe:: + + Server to remove the port from (name or ID) + +.. describe:: + + Port to remove from the server (name or ID) + server remove security group ---------------------------- diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 1fe5bb0d10..c6345a9289 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -253,6 +253,39 @@ def take_action(self, parsed_args): parsed_args.fixed_ip_address) +class AddPort(command.Command): + _description = _("Add port to server") + + def get_parser(self, prog_name): + parser = super(AddPort, self).get_parser(prog_name) + parser.add_argument( + "server", + metavar="", + help=_("Server to add the port to (name or ID)"), + ) + parser.add_argument( + "port", + metavar="", + help=_("Port to add to the server (name or ID)"), + ) + return parser + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + + server = utils.find_resource( + compute_client.servers, parsed_args.server) + + if self.app.client_manager.is_network_endpoint_enabled(): + network_client = self.app.client_manager.network + port_id = network_client.find_port( + parsed_args.port, ignore_missing=False).id + else: + port_id = parsed_args.port + + server.interface_attach(port_id=port_id, net_id=None, fixed_ip=None) + + class AddServerSecurityGroup(command.Command): _description = _("Add security group to server") @@ -1342,6 +1375,39 @@ def take_action(self, parsed_args): server.remove_floating_ip(parsed_args.ip_address) +class RemovePort(command.Command): + _description = _("Remove port from server") + + def get_parser(self, prog_name): + parser = super(RemovePort, self).get_parser(prog_name) + parser.add_argument( + "server", + metavar="", + help=_("Server to remove the port from (name or ID)"), + ) + parser.add_argument( + "port", + metavar="", + help=_("Port to remove from the server (name or ID)"), + ) + return parser + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + + server = utils.find_resource( + compute_client.servers, parsed_args.server) + + if self.app.client_manager.is_network_endpoint_enabled(): + network_client = self.app.client_manager.network + port_id = network_client.find_port( + parsed_args.port, ignore_missing=False).id + else: + port_id = parsed_args.port + + server.interface_detach(port_id) + + class RemoveServerSecurityGroup(command.Command): _description = _("Remove security group from server") diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 7691ef5945..5405410c72 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -146,6 +146,9 @@ def setUp(self): 'add_floating_ip': None, } + self.find_port = mock.Mock() + self.app.client_manager.network.find_port = self.find_port + def _test_server_add_floating_ip(self, extralist, fixed_ip_address): servers = self.setup_servers_mock(count=1) @@ -174,6 +177,53 @@ def test_server_add_floating_ip_to_fixed_ip(self): self._test_server_add_floating_ip(extralist, '5.6.7.8') +class TestServerAddPort(TestServer): + + def setUp(self): + super(TestServerAddPort, self).setUp() + + # Get the command object to test + self.cmd = server.AddPort(self.app, None) + + # Set add_fixed_ip method to be tested. + self.methods = { + 'interface_attach': None, + } + + self.find_port = mock.Mock() + self.app.client_manager.network.find_port = self.find_port + + def _test_server_add_port(self, port_id): + servers = self.setup_servers_mock(count=1) + port = 'fake-port' + + arglist = [ + servers[0].id, + port, + ] + verifylist = [ + ('server', servers[0].id), + ('port', port) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + servers[0].interface_attach.assert_called_once_with( + port_id=port_id, net_id=None, fixed_ip=None) + self.assertIsNone(result) + + def test_server_add_port(self): + self._test_server_add_port(self.find_port.return_value.id) + self.find_port.assert_called_once_with( + 'fake-port', ignore_missing=False) + + def test_server_add_port_no_neutron(self): + self.app.client_manager.network_endpoint_enabled = False + self._test_server_add_port('fake-port') + self.find_port.assert_not_called() + + class TestServerAddSecurityGroup(TestServer): def setUp(self): @@ -1613,6 +1663,52 @@ def test_server_remove_floating_ip(self): self.assertIsNone(result) +class TestServerRemovePort(TestServer): + + def setUp(self): + super(TestServerRemovePort, self).setUp() + + # Get the command object to test + self.cmd = server.RemovePort(self.app, None) + + # Set method to be tested. + self.methods = { + 'interface_detach': None, + } + + self.find_port = mock.Mock() + self.app.client_manager.network.find_port = self.find_port + + def _test_server_remove_port(self, port_id): + servers = self.setup_servers_mock(count=1) + port = 'fake-port' + + arglist = [ + servers[0].id, + port, + ] + verifylist = [ + ('server', servers[0].id), + ('port', port), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + servers[0].interface_detach.assert_called_once_with(port_id) + self.assertIsNone(result) + + def test_server_remove_port(self): + self._test_server_remove_port(self.find_port.return_value.id) + self.find_port.assert_called_once_with( + 'fake-port', ignore_missing=False) + + def test_server_remove_port_no_neutron(self): + self.app.client_manager.network_endpoint_enabled = False + self._test_server_remove_port('fake-port') + self.find_port.assert_not_called() + + class TestServerRemoveSecurityGroup(TestServer): def setUp(self): diff --git a/releasenotes/notes/allow-to-add-remove-vm-ports-273593d7cc1982de.yaml b/releasenotes/notes/allow-to-add-remove-vm-ports-273593d7cc1982de.yaml new file mode 100644 index 0000000000..aa36a21d9c --- /dev/null +++ b/releasenotes/notes/allow-to-add-remove-vm-ports-273593d7cc1982de.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``server add port`` and ``server remove port`` commands which enable to + add/remove ports to/from a server + [Bug `1678137 `_] diff --git a/setup.cfg b/setup.cfg index 3a8d67246a..5e4c25d042 100644 --- a/setup.cfg +++ b/setup.cfg @@ -102,6 +102,7 @@ openstack.compute.v2 = server_add_fixed_ip = openstackclient.compute.v2.server:AddFixedIP server_add_floating_ip = openstackclient.compute.v2.server:AddFloatingIP + server_add_port = openstackclient.compute.v2.server:AddPort server_add_security_group = openstackclient.compute.v2.server:AddServerSecurityGroup server_add_volume = openstackclient.compute.v2.server:AddServerVolume server_create = openstackclient.compute.v2.server:CreateServer @@ -114,6 +115,7 @@ openstack.compute.v2 = server_rebuild = openstackclient.compute.v2.server:RebuildServer server_remove_fixed_ip = openstackclient.compute.v2.server:RemoveFixedIP server_remove_floating_ip = openstackclient.compute.v2.server:RemoveFloatingIP + server_remove_port = openstackclient.compute.v2.server:RemovePort server_remove_security_group = openstackclient.compute.v2.server:RemoveServerSecurityGroup server_remove_volume = openstackclient.compute.v2.server:RemoveServerVolume server_rescue = openstackclient.compute.v2.server:RescueServer From 9915efdd0abebd91a3f05a242e0e20bbd5d5efa9 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Mon, 3 Apr 2017 16:30:36 -0500 Subject: [PATCH 1633/3095] Release notes cleanup for 3.10.0 release Change-Id: Ibf2aec57c5b9a1b3c8e5e74f8524c79939fc6f94 --- .../notes/bp-extension-show-6f7e31a27dad0dc9.yaml | 5 ++--- releasenotes/notes/bug-1633582-df2bee534c2da7fc.yaml | 2 +- releasenotes/notes/bug-1642030-166b2b28c8adf22e.yaml | 10 ++++------ 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/releasenotes/notes/bp-extension-show-6f7e31a27dad0dc9.yaml b/releasenotes/notes/bp-extension-show-6f7e31a27dad0dc9.yaml index 535f3a982e..ee8c403ab9 100644 --- a/releasenotes/notes/bp-extension-show-6f7e31a27dad0dc9.yaml +++ b/releasenotes/notes/bp-extension-show-6f7e31a27dad0dc9.yaml @@ -1,7 +1,6 @@ --- features: - | - Added `openstack extension show` command to allow users - to view the details of the extension. Currently works only for - network extensions. + Added ``extension show`` command to display the details of an extension. + Currently works only for network extensions. [Blueprint `extension-show `_] diff --git a/releasenotes/notes/bug-1633582-df2bee534c2da7fc.yaml b/releasenotes/notes/bug-1633582-df2bee534c2da7fc.yaml index f99457e4d2..2cd3a07ced 100644 --- a/releasenotes/notes/bug-1633582-df2bee534c2da7fc.yaml +++ b/releasenotes/notes/bug-1633582-df2bee534c2da7fc.yaml @@ -5,7 +5,7 @@ deprecations: positional argument to a requried option ``--auth-key``. This leaves the transfer request ID as the only positional arguemnt, as per the OpenStackClient command format. The old format is still functional, but is - dperecated and will be removed in the next major release. + deprecated and will be removed in the next major release. fixes: - | Fix ``volume transfer request accept`` to not fail the transfer request diff --git a/releasenotes/notes/bug-1642030-166b2b28c8adf22e.yaml b/releasenotes/notes/bug-1642030-166b2b28c8adf22e.yaml index 200c73fbe8..8dc8cef8e5 100644 --- a/releasenotes/notes/bug-1642030-166b2b28c8adf22e.yaml +++ b/releasenotes/notes/bug-1642030-166b2b28c8adf22e.yaml @@ -1,11 +1,9 @@ --- features: - | - Add ``server event`` list and show commands, that is similar to nova's - instance action commands. + Add ``server event list`` and ``server event show`` commands. - Server event is the event record that had been done on a server, - include: event type(create, delete, reboot and so on), - event result(success, error), start time, finish time and so on. - These are important information for server maintains. + A server event is the event record of actions performed on a server, + including: event type(create, delete, reboot and so on), + event result(success, error), start time, finish time and others. [Bug `1642030 `_] From 1686dc54f09b6e77b1de3abc708c297710987a04 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Mon, 3 Apr 2017 16:15:14 -0500 Subject: [PATCH 1634/3095] Help/docs cleanups: marker, limit, ip-address metavars Cleanup help strings and docs for clarity and to keep things consistent: * --limit metavar should be to indicate what is being counted * --marker metavar should be or to indicate the type of value being specified * <*-ip-address> metavars should be just as there is no difference in format between fixed and floating IPs * Move all occurances of '(name or ID)' to end of help text Change-Id: I2c31746ed6ded3845244e03e57d809f8bc0e6b9d --- doc/source/command-objects/container.rst | 4 +- doc/source/command-objects/flavor.rst | 8 ++-- doc/source/command-objects/floating-ip.rst | 12 ++--- doc/source/command-objects/image.rst | 16 ++++--- doc/source/command-objects/object.rst | 4 +- doc/source/command-objects/port.rst | 10 ++--- doc/source/command-objects/server.rst | 28 ++++++------ doc/source/command-objects/snapshot.rst | 8 ++-- doc/source/command-objects/subnet-pool.rst | 2 +- doc/source/command-objects/subnet.rst | 4 +- doc/source/command-objects/volume-backup.rst | 8 ++-- .../command-objects/volume-snapshot.rst | 8 ++-- doc/source/command-objects/volume.rst | 8 ++-- doc/source/command-options.rst | 12 ++--- openstackclient/compute/v2/flavor.py | 4 +- openstackclient/compute/v2/server.py | 45 +++++++++---------- openstackclient/image/v2/image.py | 8 ++-- openstackclient/network/v2/floating_ip.py | 6 +-- openstackclient/network/v2/port.py | 14 +++--- openstackclient/network/v2/subnet.py | 4 +- openstackclient/network/v2/subnet_pool.py | 2 +- openstackclient/object/v1/container.py | 2 +- openstackclient/object/v1/object.py | 2 +- openstackclient/volume/v1/volume.py | 2 +- openstackclient/volume/v2/backup.py | 4 +- openstackclient/volume/v2/snapshot.py | 4 +- openstackclient/volume/v2/volume.py | 4 +- openstackclient/volume/v2/volume_snapshot.py | 4 +- openstackclient/volume/v2/volume_type.py | 2 +- 29 files changed, 121 insertions(+), 118 deletions(-) diff --git a/doc/source/command-objects/container.rst b/doc/source/command-objects/container.rst index c431047293..e68955ad37 100644 --- a/doc/source/command-objects/container.rst +++ b/doc/source/command-objects/container.rst @@ -51,7 +51,7 @@ List containers [--prefix ] [--marker ] [--end-marker ] - [--limit ] + [--limit ] [--long] [--all] @@ -67,7 +67,7 @@ List containers End anchor for paging -.. option:: --limit +.. option:: --limit Limit the number of containers returned diff --git a/doc/source/command-objects/flavor.rst b/doc/source/command-objects/flavor.rst index a9f8262ee5..6feb44985f 100644 --- a/doc/source/command-objects/flavor.rst +++ b/doc/source/command-objects/flavor.rst @@ -108,8 +108,8 @@ List flavors openstack flavor list [--public | --private | --all] [--long] - [--marker ] - [--limit ] + [--marker ] + [--limit ] .. option:: --public @@ -127,11 +127,11 @@ List flavors List additional fields in output -.. option:: --marker +.. option:: --marker The last flavor ID of the previous page -.. option:: --limit +.. option:: --limit Maximum number of flavors to display diff --git a/doc/source/command-objects/floating-ip.rst b/doc/source/command-objects/floating-ip.rst index 5a38bc1b04..cbb473b88f 100644 --- a/doc/source/command-objects/floating-ip.rst +++ b/doc/source/command-objects/floating-ip.rst @@ -15,8 +15,8 @@ Create floating IP openstack floating ip create [--subnet ] [--port ] - [--floating-ip-address ] - [--fixed-ip-address ] + [--floating-ip-address ] + [--fixed-ip-address ] [--description ] [--project [--project-domain ]] @@ -31,12 +31,12 @@ Create floating IP Port to be associated with the floating IP (name or ID) *Network version 2 only* -.. option:: --floating-ip-address +.. option:: --floating-ip-address Floating IP address *Network version 2 only* -.. option:: --fixed-ip-address +.. option:: --fixed-ip-address Fixed IP address mapped to the floating IP *Network version 2 only* @@ -88,7 +88,7 @@ List floating IP(s) openstack floating ip list [--network ] [--port ] - [--fixed-ip-address ] + [--fixed-ip-address ] [--long] [--status ] [--project [--project-domain ]] @@ -106,7 +106,7 @@ List floating IP(s) *Network version 2 only* -.. option:: --fixed-ip-address +.. option:: --fixed-ip-address List floating IP(s) according to given fixed IP address diff --git a/doc/source/command-objects/image.rst b/doc/source/command-objects/image.rst index c39d9a28e0..ec51fa9316 100644 --- a/doc/source/command-objects/image.rst +++ b/doc/source/command-objects/image.rst @@ -199,8 +199,8 @@ List available images [--property ] [--long] [--sort [:]] - [--limit ] - [--marker ] + [--limit ] + [--marker ] .. option:: --public @@ -229,14 +229,18 @@ List available images Sort output by selected keys and directions(asc or desc) (default: name:asc), multiple keys and directions can be specified separated by comma -.. option:: --limit +.. option:: --limit Maximum number of images to display. -.. option:: --marker + *Image version 2 only* - The last image (name or ID) of the previous page. Display list of images - after marker. Display all images if not specified. +.. option:: --marker + + The last image of the previous page. Display list of images + after marker. Display all images if not specified. (name or ID) + + *Image version 2 only* image remove project -------------------- diff --git a/doc/source/command-objects/object.rst b/doc/source/command-objects/object.rst index 0a32a827b6..6323c4ed0b 100644 --- a/doc/source/command-objects/object.rst +++ b/doc/source/command-objects/object.rst @@ -62,7 +62,7 @@ List objects [--delimiter ] [--marker ] [--end-marker ] - [--limit ] + [--limit ] [--long] [--all] @@ -83,7 +83,7 @@ List objects End anchor for paging -.. option:: --limit +.. option:: --limit Limit number of objects returned diff --git a/doc/source/command-objects/port.rst b/doc/source/command-objects/port.rst index 98642b9a33..ddfbb445b3 100644 --- a/doc/source/command-objects/port.rst +++ b/doc/source/command-objects/port.rst @@ -44,7 +44,7 @@ Create new port .. option:: --fixed-ip subnet=,ip-address= - Desired IP and/or subnet (name or ID) for this port: + Desired IP and/or subnet for this port (name or ID): subnet=,ip-address= (repeat option to set multiple fixed IP addresses) @@ -182,7 +182,7 @@ List ports .. option:: --fixed-ip subnet=,ip-address= - Desired IP and/or subnet (name or ID) for filtering ports: + Desired IP and/or subnet for filtering ports (name or ID): subnet=,ip-address= (repeat option to set multiple fixed IP addresses) @@ -234,7 +234,7 @@ Set port properties .. option:: --fixed-ip subnet=,ip-address= - Desired IP and/or subnet (name or ID) for this port: + Desired IP and/or subnet for this port (name or ID): subnet=,ip-address= (repeat option to set multiple fixed IP addresses) @@ -363,8 +363,8 @@ Unset port properties .. option:: --fixed-ip subnet=,ip-address= - Desired IP and/or subnet (name or ID) which should be removed - from this port: subnet=,ip-address= + Desired IP and/or subnet which should be removed + from this port (name or ID): subnet=,ip-address= (repeat option to unset multiple fixed IP addresses) .. option:: --binding-profile diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index 047bf1810b..ce4e168da7 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -18,11 +18,11 @@ Add fixed IP address to server .. describe:: - Server (name or ID) to receive the fixed IP address + Server to receive the fixed IP address (name or ID) .. describe:: - Network (name or ID) to allocate the fixed IP address from + Network to allocate the fixed IP address from (name or ID) server add floating ip ---------------------- @@ -33,21 +33,21 @@ Add floating IP address to server .. code:: bash openstack server add floating ip - [--fixed-ip-address ] + [--fixed-ip-address ] -.. option:: --fixed-ip-address +.. option:: --fixed-ip-address Fixed IP address to associate with this floating IP address .. describe:: - Server (name or ID) to receive the floating IP address + Server to receive the floating IP address (name or ID) .. describe:: - Floating IP address (IP address only) to assign to server + Floating IP address to assign to server (IP only) server add security group ------------------------- @@ -264,7 +264,7 @@ List servers [--project [--project-domain ]] [--long] [--marker ] - [--limit ] + [--limit ] [--deleted] [--changes-since ] @@ -332,10 +332,10 @@ List servers .. option:: --marker - The last server (name or ID) of the previous page. Display list of servers - after marker. Display all servers if not specified. + The last server of the previous page. Display list of servers + after marker. Display all servers if not specified. (name or ID) -.. option:: --limit +.. option:: --limit Maximum number of servers to display. If limit equals -1, all servers will be displayed. If limit is greater than 'osapi_max_limit' option of Nova @@ -497,11 +497,11 @@ Remove fixed IP address from server .. describe:: - Server (name or ID) to remove the fixed IP address from + Server to remove the fixed IP address from (name or ID) .. describe:: - Fixed IP address (IP address only) to remove from the server + Fixed IP address to remove from the server (IP only) server remove floating ip ------------------------- @@ -517,11 +517,11 @@ Remove floating IP address from server .. describe:: - Server (name or ID) to remove the floating IP address from + Server to remove the floating IP address from (name or ID) .. describe:: - Floating IP address (IP address only) to remove from server + Floating IP address to remove from server (IP only) server remove security group ---------------------------- diff --git a/doc/source/command-objects/snapshot.rst b/doc/source/command-objects/snapshot.rst index c5c431d0c7..fc516067cb 100644 --- a/doc/source/command-objects/snapshot.rst +++ b/doc/source/command-objects/snapshot.rst @@ -72,8 +72,8 @@ List snapshots openstack snapshot list [--all-projects] [--long] - [--limit ] - [--marker ] + [--limit ] + [--marker ] .. option:: --all-projects @@ -83,13 +83,13 @@ List snapshots List additional fields in output -.. option:: --limit +.. option:: --limit Maximum number of snapshots to display *Volume version 2 only* -.. option:: --marker +.. option:: --marker The last snapshot ID of the previous page diff --git a/doc/source/command-objects/subnet-pool.rst b/doc/source/command-objects/subnet-pool.rst index 8b59bf8a65..d47673de52 100644 --- a/doc/source/command-objects/subnet-pool.rst +++ b/doc/source/command-objects/subnet-pool.rst @@ -150,7 +150,7 @@ List subnet pools .. option:: --address-scope - List only subnet pools of given address scope (name or ID) in output + List only subnet pools of given address scope in output (name or ID) subnet pool set --------------- diff --git a/doc/source/command-objects/subnet.rst b/doc/source/command-objects/subnet.rst index 5eb55c23c8..4e60936120 100644 --- a/doc/source/command-objects/subnet.rst +++ b/doc/source/command-objects/subnet.rst @@ -194,7 +194,7 @@ List subnets .. option:: --project - List only subnets which belong to a given project (name or ID) in output + List only subnets which belong to a given project in output (name or ID) .. option:: --project-domain @@ -203,7 +203,7 @@ List subnets .. option:: --network - List only subnets which belong to a given network (name or ID) in output + List only subnets which belong to a given network in output (name or ID) .. option:: --gateway diff --git a/doc/source/command-objects/volume-backup.rst b/doc/source/command-objects/volume-backup.rst index f2f814c5d3..585f47d464 100644 --- a/doc/source/command-objects/volume-backup.rst +++ b/doc/source/command-objects/volume-backup.rst @@ -92,8 +92,8 @@ List volume backups [--name ] [--status ] [--volume ] - [--marker ] - [--limit ] + [--marker ] + [--limit ] [--all-projects] .. _volume_backup_list-backup: @@ -114,13 +114,13 @@ List volume backups Filters results by the volume which they backup (name or ID)" -.. option:: --marker +.. option:: --marker The last backup of the previous page (name or ID) *Volume version 2 only* -.. option:: --limit +.. option:: --limit Maximum number of backups to display diff --git a/doc/source/command-objects/volume-snapshot.rst b/doc/source/command-objects/volume-snapshot.rst index 449e45b938..67db62f2b8 100644 --- a/doc/source/command-objects/volume-snapshot.rst +++ b/doc/source/command-objects/volume-snapshot.rst @@ -84,8 +84,8 @@ List volume snapshots [--all-projects] [--project [--project-domain ]] [--long] - [--limit ] - [--marker ] + [--limit ] + [--marker ] [--name ] [--status ] [--volume ] @@ -125,13 +125,13 @@ List volume snapshots Filters results by a volume (name or ID). -.. option:: --limit +.. option:: --limit Maximum number of snapshots to display *Volume version 2 only* -.. option:: --marker +.. option:: --marker The last snapshot ID of the previous page diff --git a/doc/source/command-objects/volume.rst b/doc/source/command-objects/volume.rst index 85a920ebab..a06a5d4007 100644 --- a/doc/source/command-objects/volume.rst +++ b/doc/source/command-objects/volume.rst @@ -158,8 +158,8 @@ List volumes [--status ] [--all-projects] [--long] - [--limit ] - [--marker ] + [--limit ] + [--marker ] .. option:: --project @@ -205,11 +205,11 @@ List volumes List additional fields in output -.. option:: --limit +.. option:: --limit Maximum number of volumes to display -.. option:: --marker +.. option:: --marker The last volume ID of the previous page diff --git a/doc/source/command-options.rst b/doc/source/command-options.rst index 8d1d1c4e69..886c17d292 100644 --- a/doc/source/command-options.rst +++ b/doc/source/command-options.rst @@ -283,11 +283,11 @@ There are many ways to do pagination, some OpenStack APIs support it, some don't. OpenStackClient attempts to define a single common way to specify pagination on the command line. -.. option:: --marker +.. option:: --marker - Anchor for paging + Anchor for paging (name or ID) -.. option:: --limit +.. option:: --limit Limit number of returned (*integer*) @@ -300,13 +300,13 @@ The parser declaration should look like this: parser.add_argument( "--marker", - metavar="", - help="Anchor for paging", + metavar="", + help="Anchor for paging (name or ID)", ) parser.add_argument( "--limit", - metavar="", + metavar="", type=int, help="Limit the number of returned", ) diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 009c9bd12c..bf9921b788 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -251,13 +251,13 @@ def get_parser(self, prog_name): ) parser.add_argument( '--marker', - metavar="", + metavar="", help=_("The last flavor ID of the previous page") ) parser.add_argument( '--limit', type=int, - metavar="", + metavar="", help=_("Maximum number of flavors to display") ) return parser diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 1fe5bb0d10..aad1ae99a5 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -197,13 +197,14 @@ def get_parser(self, prog_name): parser.add_argument( "server", metavar="", - help=_("Server (name or ID) to receive the fixed IP address"), + help=_("Server to receive the fixed IP address (name or ID)"), ) parser.add_argument( "network", metavar="", - help=_("Network (name or ID) to allocate " - "the fixed IP address from"), + help=_( + "Network to allocate the fixed IP address from (name or ID)" + ), ) return parser @@ -227,17 +228,16 @@ def get_parser(self, prog_name): parser.add_argument( "server", metavar="", - help=_("Server (name or ID) to receive the floating IP address"), + help=_("Server to receive the floating IP address (name or ID)"), ) parser.add_argument( "ip_address", metavar="", - help=_("Floating IP address (IP address only) to assign " - "to server"), + help=_("Floating IP address to assign to server (IP only)"), ) parser.add_argument( "--fixed-ip-address", - metavar="", + metavar="", help=_("Fixed IP address to associate with this floating IP " "address"), ) @@ -820,21 +820,21 @@ def get_parser(self, prog_name): ) parser.add_argument( '--marker', - metavar='', + metavar='', default=None, - help=_('The last server (name or ID) of the previous page. Display' - ' list of servers after marker. Display all servers if not' - ' specified.') + help=_('The last server of the previous page. Display ' + 'list of servers after marker. Display all servers if not ' + 'specified. (name or ID)') ) parser.add_argument( '--limit', - metavar='', + metavar='', type=int, default=None, - help=_("Maximum number of servers to display. If limit equals -1," - " all servers will be displayed. If limit is greater than" - " 'osapi_max_limit' option of Nova API," - " 'osapi_max_limit' will be used instead."), + help=_("Maximum number of servers to display. If limit equals -1, " + "all servers will be displayed. If limit is greater than " + "'osapi_max_limit' option of Nova API, " + "'osapi_max_limit' will be used instead."), ) parser.add_argument( '--deleted', @@ -1295,13 +1295,12 @@ def get_parser(self, prog_name): parser.add_argument( "server", metavar="", - help=_("Server (name or ID) to remove the fixed IP address from"), + help=_("Server to remove the fixed IP address from (name or ID)"), ) parser.add_argument( "ip_address", metavar="", - help=_("Fixed IP address (IP address only) to remove from the " - "server"), + help=_("Fixed IP address to remove from the server (IP only)"), ) return parser @@ -1322,14 +1321,14 @@ def get_parser(self, prog_name): parser.add_argument( "server", metavar="", - help=_("Server (name or ID) to remove the " - "floating IP address from"), + help=_( + "Server to remove the floating IP address from (name or ID)" + ), ) parser.add_argument( "ip_address", metavar="", - help=_("Floating IP address (IP address only) " - "to remove from server"), + help=_("Floating IP address to remove from server (IP only)"), ) return parser diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index c4be69f055..766de4de57 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -463,17 +463,17 @@ def get_parser(self, prog_name): ) parser.add_argument( "--limit", - metavar="", + metavar="", type=int, help=_("Maximum number of images to display."), ) parser.add_argument( '--marker', - metavar='', + metavar='', default=None, - help=_("The last image (name or ID) of the previous page. Display " + help=_("The last image of the previous page. Display " "list of images after marker. Display all images if not " - "specified."), + "specified. (name or ID)"), ) return parser diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index 41b208aa3b..ee39a29980 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -162,13 +162,13 @@ def update_parser_network(self, parser): ) parser.add_argument( '--floating-ip-address', - metavar='', + metavar='', dest='floating_ip_address', help=_("Floating IP address") ) parser.add_argument( '--fixed-ip-address', - metavar='', + metavar='', dest='fixed_ip_address', help=_("Fixed IP address mapped to the floating IP") ) @@ -307,7 +307,7 @@ def update_parser_network(self, parser): ) parser.add_argument( '--fixed-ip-address', - metavar='', + metavar='', help=_("List floating IP(s) according to " "given fixed IP address") ) diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index f77f566d4e..9d598fabec 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -301,7 +301,7 @@ def get_parser(self, prog_name): metavar='subnet=,ip-address=', action=parseractions.MultiKeyValueAction, optional_keys=['subnet', 'ip-address'], - help=_("Desired IP and/or subnet (name or ID) for this port: " + help=_("Desired IP and/or subnet for this port (name or ID): " "subnet=,ip-address= " "(repeat option to set multiple fixed IP addresses)") ) @@ -496,9 +496,9 @@ def get_parser(self, prog_name): metavar='subnet=,ip-address=', action=parseractions.MultiKeyValueAction, optional_keys=['subnet', 'ip-address'], - help=_("Desired IP and/or subnet (name or ID) for filtering " - "ports: subnet=,ip-address= " - "(repeat option to set multiple fixed IP addresses)") + help=_("Desired IP and/or subnet for filtering ports " + "(name or ID): subnet=,ip-address= " + "(repeat option to set multiple fixed IP addresses)"), ) return parser @@ -593,7 +593,7 @@ def get_parser(self, prog_name): metavar='subnet=,ip-address=', action=parseractions.MultiKeyValueAction, optional_keys=['subnet', 'ip-address'], - help=_("Desired IP and/or subnet (name or ID) for this port: " + help=_("Desired IP and/or subnet for this port (name or ID): " "subnet=,ip-address= " "(repeat option to set multiple fixed IP addresses)") ) @@ -757,8 +757,8 @@ def get_parser(self, prog_name): metavar='subnet=,ip-address=', action=parseractions.MultiKeyValueAction, optional_keys=['subnet', 'ip-address'], - help=_("Desired IP and/or subnet (name or ID) which should be " - "removed from this port: subnet=," + help=_("Desired IP and/or subnet which should be " + "removed from this port (name or ID): subnet=," "ip-address= (repeat option to unset multiple " "fixed IP addresses)")) diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 403b4cd2a1..2fdd11f07f 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -428,14 +428,14 @@ def get_parser(self, prog_name): '--project', metavar='', help=_("List only subnets which belong to a given project " - "(name or ID) in output") + "in output (name or ID)") ) identity_common.add_project_domain_option_to_parser(parser) parser.add_argument( '--network', metavar='', help=_("List only subnets which belong to a given network " - "(name or ID) in output") + "in output (name or ID)") ) parser.add_argument( '--gateway', diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index 0473111112..82ad94127b 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -282,7 +282,7 @@ def get_parser(self, prog_name): '--address-scope', metavar='', help=_("List only subnet pools of given address scope " - "(name or ID) in output") + "in output (name or ID)") ) return parser diff --git a/openstackclient/object/v1/container.py b/openstackclient/object/v1/container.py index 88fb860244..9f689ab6b0 100644 --- a/openstackclient/object/v1/container.py +++ b/openstackclient/object/v1/container.py @@ -119,7 +119,7 @@ def get_parser(self, prog_name): ) parser.add_argument( "--limit", - metavar="", + metavar="", type=int, help=_("Limit the number of containers returned"), ) diff --git a/openstackclient/object/v1/object.py b/openstackclient/object/v1/object.py index 71b6f52015..e79cea48ea 100644 --- a/openstackclient/object/v1/object.py +++ b/openstackclient/object/v1/object.py @@ -139,7 +139,7 @@ def get_parser(self, prog_name): ) parser.add_argument( "--limit", - metavar="", + metavar="", type=int, help=_("Limit the number of objects returned"), ) diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index 8e1097f516..0121e0596e 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -291,7 +291,7 @@ def get_parser(self, prog_name): '--limit', type=int, action=parseractions.NonNegativeAction, - metavar='', + metavar='', help=_('Maximum number of volumes to display'), ) return parser diff --git a/openstackclient/volume/v2/backup.py b/openstackclient/volume/v2/backup.py index 00389fcb86..60633a7080 100644 --- a/openstackclient/volume/v2/backup.py +++ b/openstackclient/volume/v2/backup.py @@ -202,14 +202,14 @@ def get_parser(self, prog_name): ) parser.add_argument( '--marker', - metavar='', + metavar='', help=_('The last backup of the previous page (name or ID)'), ) parser.add_argument( '--limit', type=int, action=parseractions.NonNegativeAction, - metavar='', + metavar='', help=_('Maximum number of backups to display'), ) parser.add_argument( diff --git a/openstackclient/volume/v2/snapshot.py b/openstackclient/volume/v2/snapshot.py index a18887e303..82b310338a 100644 --- a/openstackclient/volume/v2/snapshot.py +++ b/openstackclient/volume/v2/snapshot.py @@ -146,14 +146,14 @@ def get_parser(self, prog_name): ) parser.add_argument( '--marker', - metavar='', + metavar='', help=_('The last snapshot ID of the previous page'), ) parser.add_argument( '--limit', type=int, action=parseractions.NonNegativeAction, - metavar='', + metavar='', help=_('Maximum number of snapshots to display'), ) return parser diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index c361d7004c..2b6c966d79 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -341,14 +341,14 @@ def get_parser(self, prog_name): ) parser.add_argument( '--marker', - metavar='', + metavar='', help=_('The last volume ID of the previous page'), ) parser.add_argument( '--limit', type=int, action=parseractions.NonNegativeAction, - metavar='', + metavar='', help=_('Maximum number of volumes to display'), ) return parser diff --git a/openstackclient/volume/v2/volume_snapshot.py b/openstackclient/volume/v2/volume_snapshot.py index f12cfed91f..804c829111 100644 --- a/openstackclient/volume/v2/volume_snapshot.py +++ b/openstackclient/volume/v2/volume_snapshot.py @@ -180,14 +180,14 @@ def get_parser(self, prog_name): ) parser.add_argument( '--marker', - metavar='', + metavar='', help=_('The last snapshot ID of the previous page'), ) parser.add_argument( '--limit', type=int, action=parseractions.NonNegativeAction, - metavar='', + metavar='', help=_('Maximum number of snapshots to display'), ) parser.add_argument( diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index 8d2901f29c..64c4d652ba 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -544,7 +544,7 @@ def get_parser(self, prog_name): '--project', metavar='', help=_('Removes volume type access to project (name or ID) ' - ' (admin only)'), + '(admin only)'), ) identity_common.add_project_domain_option_to_parser(parser) parser.add_argument( From abb2498e714cf1915e5ec7a8a344a0a71962bc50 Mon Sep 17 00:00:00 2001 From: Shashank Kumar Shankar Date: Tue, 4 Apr 2017 15:41:27 +0000 Subject: [PATCH 1635/3095] Correct flavor associate/disassociate commands in Mapping Guide This patch fixes the neutron CLI mapping for neutron flavor-associate and flavor-disassociate since [1] got merged. [1] - https://review.openstack.org/#/c/403907/ Change-Id: Ifd09a8b091ae89c33a06590aba935df5e22e215b --- doc/source/data/neutron.csv | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/source/data/neutron.csv b/doc/source/data/neutron.csv index c2f0b415d1..a276cf8963 100644 --- a/doc/source/data/neutron.csv +++ b/doc/source/data/neutron.csv @@ -35,10 +35,10 @@ dhcp-agent-network-add,network agent add network,Add a network to a DHCP agent. dhcp-agent-network-remove,network agent remove network,Remove a network from a DHCP agent. ext-list,extension list,List all extensions. ext-show,extension show,Show information of a given resource. -flavor-associate,network flavor associate,Associate a Neutron service flavor with a flavor profile. +flavor-associate,network flavor add profile,Add a Neutron service flavor with a flavor profile. flavor-create,network flavor create,Create a Neutron service flavor. flavor-delete,network flavor delete,Delete a given Neutron service flavor. -flavor-disassociate,network flavor disassociate,Disassociate a Neutron service flavor from a flavor profile. +flavor-disassociate,network flavor remove profile,Remove a Neutron service flavor from a flavor profile. flavor-list,network flavor list,List Neutron service flavors. flavor-profile-create,network flavor profile create,Create a Neutron service flavor profile. flavor-profile-delete,network flavor profile delete,Delete a given Neutron service flavor profile. @@ -241,4 +241,4 @@ vpn-service-create,,Create a VPN service. vpn-service-delete,,Delete a given VPN service. vpn-service-list,,List VPN service configurations that belong to a given tenant. vpn-service-show,,Show information of a given VPN service. -vpn-service-update,,Update a given VPN service. \ No newline at end of file +vpn-service-update,,Update a given VPN service. From 7f9814860ad739e25b82898176d26c7b788e8e33 Mon Sep 17 00:00:00 2001 From: Cedric Brandily Date: Fri, 31 Mar 2017 16:53:20 +0200 Subject: [PATCH 1636/3095] Enable to specify which fixed-ip to add to a vm. This change enables to specify which fixed-ip will be added to a vm using: openstack server add fixed ip --fixed-ip-address This change uses interface_attach instead of add_fixed_ip[1] which is less flexible and uses a deprecated API. [1] https://review.openstack.org/384261 Closes-Bug: #1678140 Change-Id: I7fe4621439ef0d8dca080551ffaeb614c5a91174 --- doc/source/command-objects/server.rst | 5 +++++ openstackclient/compute/v2/server.py | 8 +++++++- .../tests/unit/compute/v2/test_server.py | 20 +++++++++++++------ ...llow-to-vm-ip-to-add-7721ba64b863fa77.yaml | 6 ++++++ 4 files changed, 32 insertions(+), 7 deletions(-) create mode 100644 releasenotes/notes/allow-to-vm-ip-to-add-7721ba64b863fa77.yaml diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index 4ac05c70f7..55b39ef57d 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -13,9 +13,14 @@ Add fixed IP address to server .. code:: bash openstack server add fixed ip + [--fixed-ip-address ] +.. option:: --fixed-ip-address + + Requested fixed IP address + .. describe:: Server to receive the fixed IP address (name or ID) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index edb066031a..ae83967767 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -206,6 +206,11 @@ def get_parser(self, prog_name): "Network to allocate the fixed IP address from (name or ID)" ), ) + parser.add_argument( + "--fixed-ip-address", + metavar="", + help=_("Requested fixed IP address"), + ) return parser def take_action(self, parsed_args): @@ -217,7 +222,8 @@ def take_action(self, parsed_args): network = utils.find_resource( compute_client.networks, parsed_args.network) - server.add_fixed_ip(network.id) + server.interface_attach(port_id=None, net_id=network.id, + fixed_ip=parsed_args.fixed_ip_address) class AddFloatingIP(command.Command): diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 0e3bb28f32..fed847f1da 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -104,10 +104,10 @@ def setUp(self): # Set add_fixed_ip method to be tested. self.methods = { - 'add_fixed_ip': None, + 'interface_attach': None, } - def test_server_add_fixed_ip(self): + def _test_server_add_fixed_ip(self, extralist, fixed_ip_address): servers = self.setup_servers_mock(count=1) network = compute_fakes.FakeNetwork.create_one_network() self.networks_mock.get.return_value = network @@ -115,20 +115,28 @@ def test_server_add_fixed_ip(self): arglist = [ servers[0].id, network.id, - ] + ] + extralist verifylist = [ ('server', servers[0].id), - ('network', network.id) + ('network', network.id), + ('fixed_ip_address', fixed_ip_address) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - servers[0].add_fixed_ip.assert_called_once_with( - network.id, + servers[0].interface_attach.assert_called_once_with( + port_id=None, net_id=network.id, fixed_ip=fixed_ip_address ) self.assertIsNone(result) + def test_server_add_fixed_ip(self): + self._test_server_add_fixed_ip([], None) + + def test_server_add_specific_fixed_ip(self): + extralist = ['--fixed-ip-address', '5.6.7.8'] + self._test_server_add_fixed_ip(extralist, '5.6.7.8') + class TestServerAddFloatingIP(TestServer): diff --git a/releasenotes/notes/allow-to-vm-ip-to-add-7721ba64b863fa77.yaml b/releasenotes/notes/allow-to-vm-ip-to-add-7721ba64b863fa77.yaml new file mode 100644 index 0000000000..9b7de99dec --- /dev/null +++ b/releasenotes/notes/allow-to-vm-ip-to-add-7721ba64b863fa77.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--fixed-ip-address`` option to the ``server add fixed ip`` command + [Bug `1678140 `_] + From 0e42ea3ae325cf5b168bb966e62cd6b8e9ee0159 Mon Sep 17 00:00:00 2001 From: Reedip Date: Tue, 21 Mar 2017 08:05:54 +0000 Subject: [PATCH 1637/3095] Structure FindFloatingIP() to work without ip_cache Currently we have an ip_cache returned from _find_floating_ip() which is generally ignored as it is not always required. This patch removes the need of ip_cache in _find_floating_ip(). Co-Authored-By: Sindhu Devale Change-Id: I8b92271185f82f275fa73adad03e9dad70be70e4 --- openstackclient/network/v2/floating_ip.py | 28 +++++-------------- .../tests/unit/network/v2/test_floating_ip.py | 18 ++++-------- 2 files changed, 13 insertions(+), 33 deletions(-) diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index 41b208aa3b..3ac76aa3dd 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -83,7 +83,6 @@ def _get_attrs(client_manager, parsed_args): def _find_floating_ip( session, - ip_cache, name_or_id, ignore_missing=True, **params @@ -93,11 +92,11 @@ def _find_floating_ip( The SDK's find_ip() can only locate a floating IP by ID so we have to do this ourselves. """ - def _get_one_match(name_or_id): """Given a list of results, return the match""" the_result = None - for maybe_result in ip_cache: + ip_list = list(_floating_ip.FloatingIP.list(session, **params)) + for maybe_result in ip_list: id_value = maybe_result.id ip_value = maybe_result.floating_ip_address @@ -116,19 +115,16 @@ def _get_one_match(name_or_id): # Try to short-circuit by looking directly for a matching ID. try: match = _floating_ip.FloatingIP.existing(id=name_or_id, **params) - return (match.get(session), ip_cache) + return match.get(session) except sdk_exceptions.NotFoundException: pass - if len(ip_cache) == 0: - ip_cache = list(_floating_ip.FloatingIP.list(session, **params)) - result = _get_one_match(name_or_id) if result is not None: - return (result, ip_cache) + return result if ignore_missing: - return (None, ip_cache) + return None raise sdk_exceptions.ResourceNotFound( "No %s found for %s" % (_floating_ip.FloatingIP.__name__, name_or_id)) @@ -240,9 +236,8 @@ def update_parser_common(self, parser): return parser def take_action_network(self, client, parsed_args): - (obj, self.ip_cache) = _find_floating_ip( + obj = _find_floating_ip( self.app.client_manager.sdk_connection.session, - self.ip_cache, self.r, ignore_missing=False, ) @@ -255,11 +250,6 @@ def take_action_compute(self, client, parsed_args): def take_action(self, parsed_args): """Implements a naive cache for the list of floating IPs""" - # NOTE(dtroyer): This really only prevents multiple list() - # calls when performing multiple resource deletes - # in a single command. In an interactive session - # each delete command will call list(). - self.ip_cache = [] super(DeleteFloatingIP, self).take_action(parsed_args) @@ -459,9 +449,6 @@ def take_action_compute(self, client, parsed_args): class ShowFloatingIP(common.NetworkAndComputeShowOne): _description = _("Display floating IP details") - # ip_cache is unused here but is a side effect of _find_floating_ip() - ip_cache = [] - def update_parser_common(self, parser): parser.add_argument( 'floating_ip', @@ -471,9 +458,8 @@ def update_parser_common(self, parser): return parser def take_action_network(self, client, parsed_args): - (obj, self.ip_cache) = _find_floating_ip( + obj = _find_floating_ip( self.app.client_manager.sdk_connection.session, - [], parsed_args.floating_ip, ignore_missing=False, ) diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip.py b/openstackclient/tests/unit/network/v2/test_floating_ip.py index e395300d99..0b3fd888a6 100644 --- a/openstackclient/tests/unit/network/v2/test_floating_ip.py +++ b/openstackclient/tests/unit/network/v2/test_floating_ip.py @@ -218,8 +218,8 @@ def setUp(self): ) def test_floating_ip_delete(self, find_floating_ip_mock): find_floating_ip_mock.side_effect = [ - (self.floating_ips[0], []), - (self.floating_ips[1], []), + self.floating_ips[0], + self.floating_ips[1], ] arglist = [ self.floating_ips[0].id, @@ -233,7 +233,6 @@ def test_floating_ip_delete(self, find_floating_ip_mock): find_floating_ip_mock.assert_called_once_with( mock.ANY, - [], self.floating_ips[0].id, ignore_missing=False, ) @@ -246,8 +245,8 @@ def test_floating_ip_delete(self, find_floating_ip_mock): ) def test_floating_ip_delete_multi(self, find_floating_ip_mock): find_floating_ip_mock.side_effect = [ - (self.floating_ips[0], []), - (self.floating_ips[1], []), + self.floating_ips[0], + self.floating_ips[1], ] arglist = [] verifylist = [] @@ -264,13 +263,11 @@ def test_floating_ip_delete_multi(self, find_floating_ip_mock): calls = [ call( mock.ANY, - [], self.floating_ips[0].id, ignore_missing=False, ), call( mock.ANY, - [], self.floating_ips[1].id, ignore_missing=False, ), @@ -289,7 +286,7 @@ def test_floating_ip_delete_multi(self, find_floating_ip_mock): ) def test_floating_ip_delete_multi_exception(self, find_floating_ip_mock): find_floating_ip_mock.side_effect = [ - (self.floating_ips[0], []), + self.floating_ips[0], exceptions.CommandError, ] arglist = [ @@ -310,13 +307,11 @@ def test_floating_ip_delete_multi_exception(self, find_floating_ip_mock): find_floating_ip_mock.assert_any_call( mock.ANY, - [], self.floating_ips[0].id, ignore_missing=False, ) find_floating_ip_mock.assert_any_call( mock.ANY, - [], 'unexist_floating_ip', ignore_missing=False, ) @@ -584,7 +579,7 @@ def setUp(self): "floating_ip._find_floating_ip" ) def test_floating_ip_show(self, find_floating_ip_mock): - find_floating_ip_mock.return_value = (self.floating_ip, []) + find_floating_ip_mock.return_value = self.floating_ip arglist = [ self.floating_ip.id, ] @@ -597,7 +592,6 @@ def test_floating_ip_show(self, find_floating_ip_mock): find_floating_ip_mock.assert_called_once_with( mock.ANY, - [], self.floating_ip.id, ignore_missing=False, ) From 5ff2cfd042de7afc4323a3b306ff5be1882fba46 Mon Sep 17 00:00:00 2001 From: Ha Van Tu Date: Thu, 12 Jan 2017 09:57:53 +0700 Subject: [PATCH 1638/3095] Add "qos-policy" option to "port create" & "port set" This patch adds "qos-policy" option to "port create" command, and "qos-policy", "no-qos-policy" options to "port set" command and "qos-policy" option to "port unset". Change-Id: I78072e1ff0dd30a2e23a0fb833ce6ab5cf246016 Co-Authored-By: Nguyen Phuong An Co-Authored-By: Rodolfo Alonso Hernandez Partial-Bug: #1612136 Partially-Implements: blueprint network-commands-options --- doc/source/command-objects/port.rst | 15 +++++ openstackclient/network/v2/port.py | 32 +++++++++- .../tests/unit/network/v2/fakes.py | 2 + .../tests/unit/network/v2/test_port.py | 60 ++++++++++++++++++- .../notes/bug-1612136-ec240349a933db12.yaml | 6 ++ 5 files changed, 112 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/bug-1612136-ec240349a933db12.yaml diff --git a/doc/source/command-objects/port.rst b/doc/source/command-objects/port.rst index ddfbb445b3..b3f4c7f9fd 100644 --- a/doc/source/command-objects/port.rst +++ b/doc/source/command-objects/port.rst @@ -30,6 +30,7 @@ Create new port [--security-group | --no-security-group] [--dns-name ] [--allowed-address ip-address=[,mac-address=]] + [--qos-policy ] [--project [--project-domain ]] [--enable-port-security | --disable-port-security] @@ -104,6 +105,10 @@ Create new port ip-address=[,mac-address=] (repeat option to set multiple allowed-address pairs) +.. option:: --qos-policy + + Attach QoS policy to this port (name or ID) + .. option:: --project Owner's project (name or ID) @@ -217,6 +222,7 @@ Set port properties [--binding-profile ] [--no-binding-profile] [--host ] + [--qos-policy ] [--enable | --disable] [--name ] [--mac-address ] @@ -274,6 +280,10 @@ Set port properties Allocate port on host ```` (ID only) +.. option:: --qos-policy + + Attach QoS policy to this port (name or ID) + .. option:: --enable Enable port @@ -359,6 +369,7 @@ Unset port properties [--binding-profile [...]] [--security-group [...]] [--allowed-address ip-address=[,mac-address=] [...]] + [--qos-policy] .. option:: --fixed-ip subnet=,ip-address= @@ -383,6 +394,10 @@ Unset port properties ip-address=[,mac-address=] (repeat option to unset multiple allowed-address pairs) +.. option:: --qos-policy + + Remove the QoS policy attached to the port + .. _port_unset-port: .. describe:: diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 9d598fabec..3a32916b78 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -155,6 +155,13 @@ def _get_attrs(client_manager, parsed_args): if parsed_args.enable_port_security: attrs['port_security_enabled'] = True + if 'no_qos_policy' in parsed_args and parsed_args.no_qos_policy: + attrs['qos_policy_id'] = None + + if parsed_args.qos_policy: + attrs['qos_policy_id'] = client_manager.network.find_qos_policy( + parsed_args.qos_policy, ignore_missing=False).id + return attrs @@ -337,7 +344,7 @@ def get_parser(self, prog_name): help=_("Name of this port") ) # TODO(singhj): Add support for extended options: - # qos,dhcp + # dhcp secgroups = parser.add_mutually_exclusive_group() secgroups.add_argument( '--security-group', @@ -353,6 +360,11 @@ def get_parser(self, prog_name): action='store_true', help=_("Associate no security groups with this port") ) + parser.add_argument( + '--qos-policy', + metavar='', + help=_("Attach QoS policy to this port (name or ID)") + ) port_security = parser.add_mutually_exclusive_group() port_security.add_argument( '--enable-port-security', @@ -403,6 +415,9 @@ def take_action(self, parsed_args): attrs['allowed_address_pairs'] = ( _convert_address_pairs(parsed_args)) + if parsed_args.qos_policy: + attrs['qos_policy_id'] = client.find_qos_policy( + parsed_args.qos_policy, ignore_missing=False).id obj = client.create_port(**attrs) display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) @@ -619,6 +634,11 @@ def get_parser(self, prog_name): "Specify both --binding-profile and --no-binding-profile " "to overwrite the current binding:profile information.") ) + parser.add_argument( + '--qos-policy', + metavar='', + help=_("Attach QoS policy to this port (name or ID)") + ) parser.add_argument( 'port', metavar="", @@ -675,8 +695,8 @@ def take_action(self, parsed_args): client = self.app.client_manager.network _prepare_fixed_ips(self.app.client_manager, parsed_args) - attrs = _get_attrs(self.app.client_manager, parsed_args) obj = client.find_port(parsed_args.port, ignore_missing=False) + attrs = _get_attrs(self.app.client_manager, parsed_args) if parsed_args.no_binding_profile: attrs['binding:profile'] = {} @@ -794,6 +814,12 @@ def get_parser(self, prog_name): "[,mac-address=] (repeat option to set " "multiple allowed-address pairs)") ) + parser.add_argument( + '--qos-policy', + action='store_true', + default=False, + help=_("Remove the QoS policy attached to the port") + ) return parser @@ -843,6 +869,8 @@ def take_action(self, parsed_args): msg = _("Port does not contain allowed-address-pair %s") % addr raise exceptions.CommandError(msg) attrs['allowed_address_pairs'] = tmp_addr_pairs + if parsed_args.qos_policy: + attrs['qos_policy_id'] = None if attrs: client.update_port(obj, **attrs) diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index d3685409a1..9a2899419c 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -572,6 +572,7 @@ def create_one_port(attrs=None): 'security_group_ids': [], 'status': 'ACTIVE', 'tenant_id': 'project-id-' + uuid.uuid4().hex, + 'qos_policy_id': 'qos-policy-id-' + uuid.uuid4().hex, } # Overwrite default attributes. @@ -590,6 +591,7 @@ def create_one_port(attrs=None): port.is_port_security_enabled = port_attrs['port_security_enabled'] port.project_id = port_attrs['tenant_id'] port.security_group_ids = port_attrs['security_group_ids'] + port.qos_policy_id = port_attrs['qos_policy_id'] return port diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index 701af8790b..851bf25ad2 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -57,6 +57,7 @@ def _get_common_cols_data(self, fake_port): 'network_id', 'port_security_enabled', 'project_id', + 'qos_policy_id', 'security_group_ids', 'status', ) @@ -82,6 +83,7 @@ def _get_common_cols_data(self, fake_port): fake_port.network_id, fake_port.port_security_enabled, fake_port.project_id, + fake_port.qos_policy_id, utils.format_list(fake_port.security_group_ids), fake_port.status, ) @@ -422,6 +424,35 @@ def test_create_port_with_allowed_address_pair(self): self.assertEqual(ref_columns, columns) self.assertEqual(ref_data, data) + def test_create_port_with_qos(self): + qos_policy = network_fakes.FakeNetworkQosPolicy.create_one_qos_policy() + self.network.find_qos_policy = mock.Mock(return_value=qos_policy) + arglist = [ + '--network', self._port.network_id, + '--qos-policy', qos_policy.id, + 'test-port', + ] + verifylist = [ + ('network', self._port.network_id,), + ('enable', True), + ('qos_policy', qos_policy.id), + ('name', 'test-port'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_port.assert_called_once_with(**{ + 'admin_state_up': True, + 'network_id': self._port.network_id, + 'qos_policy_id': qos_policy.id, + 'name': 'test-port', + }) + + ref_columns, ref_data = self._get_common_cols_data(self._port) + self.assertEqual(ref_columns, columns) + self.assertEqual(ref_data, data) + def test_create_port_security_enabled(self): arglist = [ '--network', self._port.network_id, @@ -1316,6 +1347,30 @@ def test_set_port_security_disabled(self): 'port_security_enabled': False, }) + def test_set_port_with_qos(self): + qos_policy = network_fakes.FakeNetworkQosPolicy.create_one_qos_policy() + self.network.find_qos_policy = mock.Mock(return_value=qos_policy) + _testport = network_fakes.FakePort.create_one_port( + {'qos_policy_id': None}) + self.network.find_port = mock.Mock(return_value=_testport) + arglist = [ + '--qos-policy', qos_policy.id, + _testport.name, + ] + verifylist = [ + ('qos_policy', qos_policy.id), + ('port', _testport.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = { + 'qos_policy_id': qos_policy.id, + } + self.network.update_port.assert_called_once_with(_testport, **attrs) + self.assertIsNone(result) + class TestShowPort(TestPort): @@ -1379,6 +1434,7 @@ def test_unset_port_parameters(self): '--fixed-ip', 'subnet=042eb10a-3a18-4658-ab-cf47c8d03152,ip-address=1.0.0.0', '--binding-profile', 'Superman', + '--qos-policy', self._testport.name, ] verifylist = [ @@ -1386,6 +1442,7 @@ def test_unset_port_parameters(self): 'subnet': '042eb10a-3a18-4658-ab-cf47c8d03152', 'ip-address': '1.0.0.0'}]), ('binding_profile', ['Superman']), + ('qos_policy', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1395,7 +1452,8 @@ def test_unset_port_parameters(self): 'fixed_ips': [{ 'subnet_id': '042eb10a-3a18-4658-ab-cf47c8d03152', 'ip_address': '0.0.0.1'}], - 'binding:profile': {'batman': 'Joker'} + 'binding:profile': {'batman': 'Joker'}, + 'qos_policy_id': None } self.network.update_port.assert_called_once_with( self._testport, **attrs) diff --git a/releasenotes/notes/bug-1612136-ec240349a933db12.yaml b/releasenotes/notes/bug-1612136-ec240349a933db12.yaml new file mode 100644 index 0000000000..42872966b5 --- /dev/null +++ b/releasenotes/notes/bug-1612136-ec240349a933db12.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--qos-policy`` option to ``port create``, ``port set`` and + ``port unset`` commands. + [Bug `1612136 `_] From 763c8c5670f238920398165e670592e006213f32 Mon Sep 17 00:00:00 2001 From: Sindhu Devale Date: Thu, 6 Oct 2016 10:01:59 -0500 Subject: [PATCH 1639/3095] "floating ip set/unset port" for OSC Implements Neutron feature of floating ip associate/disassociate into OpenStack Client. Previously, network.find_ip() function only supported to search floating ip by UUID. Hence, _find_floating_ip() function is used in floating_ip.py, to search fip both by UUID and ip_address. [1] adds the ability to find fip object using both UUID and ip_address. This functionality however, won't be available until the SDK is released. Hence, we continue to use _find_floating_ip() method, which was cleaned up by [2] to remove the use of ip_cache. Once, the SDK is released, we will remove all the usage of _find_floating_ip() method and instead only use network.find_ip(). [1] https://review.openstack.org/#/c/449879/2 [2] https://review.openstack.org/#/c/447938/ Change-Id: I6c5222287c46ca42365917d2deae70bdb626347 Co-Authored-By: Reedip Co-Authored-By: RuiChen Closes-Bug: #1560297 --- doc/source/command-objects/floating-ip.rst | 47 +++++ openstackclient/network/v2/floating_ip.py | 76 ++++++- .../functional/network/v2/test_floating_ip.py | 76 +++++++ .../tests/unit/network/v2/test_floating_ip.py | 186 +++++++++++++++--- ...ng-ip-set-unset-port-28e33875937b69cf.yaml | 5 + setup.cfg | 2 + 6 files changed, 366 insertions(+), 26 deletions(-) create mode 100644 releasenotes/notes/floating-ip-set-unset-port-28e33875937b69cf.yaml diff --git a/doc/source/command-objects/floating-ip.rst b/doc/source/command-objects/floating-ip.rst index 5a38bc1b04..5e126fe316 100644 --- a/doc/source/command-objects/floating-ip.rst +++ b/doc/source/command-objects/floating-ip.rst @@ -143,6 +143,32 @@ List floating IP(s) *Network version 2 only* +floating ip set +--------------- + +Set floating IP properties + +.. program:: floating ip set +.. code:: bash + + openstack floating ip set + --port + [--fixed-ip-address ] + + +.. option:: --port + + Assocaite the floating IP with port (name or ID) + +.. option:: --fixed-ip-address + + Fixed IP of the port (required only if port has multiple IPs) + +.. _floating_ip_set-floating-ip: +.. describe:: + + Floating IP to associate (IP address or ID) + floating ip show ---------------- @@ -156,3 +182,24 @@ Display floating IP details .. describe:: Floating IP to display (IP address or ID) + +floating ip unset +----------------- + +Unset floating IP Properties + +.. program:: floating ip unset +.. code:: bash + + openstack floating ip unset + --port + + +.. option:: --port + + Disassociate any port associated with the floating IP + +.. _floating_ip_unset-floating-ip: +.. describe:: + + Floating IP to disassociate (IP address or ID) diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index 3ac76aa3dd..9bfc7a64a2 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -17,6 +17,7 @@ from openstack import exceptions as sdk_exceptions from openstack.network.v2 import floating_ip as _floating_ip +from osc_lib.command import command from osc_lib import utils from openstackclient.i18n import _ @@ -164,7 +165,7 @@ def update_parser_network(self, parser): ) parser.add_argument( '--fixed-ip-address', - metavar='', + metavar='', dest='fixed_ip_address', help=_("Fixed IP address mapped to the floating IP") ) @@ -446,6 +447,47 @@ def take_action_compute(self, client, parsed_args): client, parsed_args) +class SetFloatingIP(command.Command): + _description = _("Set floating IP Properties") + + def get_parser(self, prog_name): + parser = super(SetFloatingIP, self).get_parser(prog_name) + parser.add_argument( + 'floating_ip', + metavar='', + help=_("Floating IP to associate (IP address or ID)")) + parser.add_argument( + '--port', + metavar='', + required=True, + help=_("Assocaite the floating IP with port (name or ID)")), + parser.add_argument( + '--fixed-ip-address', + metavar='', + dest='fixed_ip_address', + help=_("Fixed IP of the port " + "(required only if port has multiple IPs)") + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + attrs = {} + # TODO(sindhu) Use client.find_ip() once SDK 0.9.15 is released + obj = _find_floating_ip( + self.app.client_manager.sdk_connection.session, + parsed_args.floating_ip, + ignore_missing=False, + ) + port = client.find_port(parsed_args.port, + ignore_missing=False) + attrs['port_id'] = port.id + if parsed_args.fixed_ip_address: + attrs['fixed_ip_address'] = parsed_args.fixed_ip_address + + client.update_ip(obj, **attrs) + + class ShowFloatingIP(common.NetworkAndComputeShowOne): _description = _("Display floating IP details") @@ -499,3 +541,35 @@ def take_action_compute(self, client, parsed_args): 'Please use "floating ip show" instead.')) return super(ShowIPFloating, self).take_action_compute( client, parsed_args) + + +class UnsetFloatingIP(command.Command): + _description = _("Unset floating IP Properties") + + def get_parser(self, prog_name): + parser = super(UnsetFloatingIP, self).get_parser(prog_name) + parser.add_argument( + 'floating_ip', + metavar='', + help=_("Floating IP to disassociate (IP address or ID)")) + parser.add_argument( + '--port', + action='store_true', + default=False, + help=_("Disassociate any port associated with the floating IP") + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + # TODO(sindhu) Use client.find_ip() once SDK 0.9.15 is released + obj = _find_floating_ip( + self.app.client_manager.sdk_connection.session, + parsed_args.floating_ip, + ignore_missing=False, + ) + if parsed_args.port: + attrs = { + 'port_id': None, + } + client.update_ip(obj, **attrs) diff --git a/openstackclient/tests/functional/network/v2/test_floating_ip.py b/openstackclient/tests/functional/network/v2/test_floating_ip.py index 8fbec3d5f7..5da0e474ca 100644 --- a/openstackclient/tests/functional/network/v2/test_floating_ip.py +++ b/openstackclient/tests/functional/network/v2/test_floating_ip.py @@ -21,6 +21,10 @@ class FloatingIpTests(base.TestCase): """Functional tests for floating ip""" SUBNET_NAME = uuid.uuid4().hex NETWORK_NAME = uuid.uuid4().hex + PRIVATE_NETWORK_NAME = uuid.uuid4().hex + PRIVATE_SUBNET_NAME = uuid.uuid4().hex + ROUTER = uuid.uuid4().hex + PORT_NAME = uuid.uuid4().hex @classmethod def setUpClass(cls): @@ -30,6 +34,8 @@ def setUpClass(cls): cls.re_fixed_ip = re.compile("fixed_ip_address\s+\|\s+(\S+)") cls.re_description = re.compile("description\s+\|\s+([^|]+?)\s+\|") cls.re_network_id = re.compile("floating_network_id\s+\|\s+(\S+)") + cls.re_port_id = re.compile("\s+id\s+\|\s+(\S+)") + cls.re_fp_port_id = re.compile("\s+port_id\s+\|\s+(\S+)") # Create a network for the floating ip raw_output = cls.openstack( @@ -37,6 +43,12 @@ def setUpClass(cls): ) cls.network_id = re.search(cls.re_id, raw_output).group(1) + # Create a private network for the port + raw_output = cls.openstack( + 'network create ' + cls.PRIVATE_NETWORK_NAME + ) + cls.private_network_id = re.search(cls.re_id, raw_output).group(1) + # Try random subnet range for subnet creating # Because we can not determine ahead of time what subnets are already # in use, possibly by another test running in parallel, try 4 times @@ -46,6 +58,10 @@ def setUpClass(cls): str, (random.randint(0, 223) for _ in range(3)) )) + ".0/26" + cls.private_subnet = ".".join(map( + str, + (random.randint(0, 223) for _ in range(3)) + )) + ".0/26" try: # Create a subnet for the network raw_output = cls.openstack( @@ -54,6 +70,13 @@ def setUpClass(cls): '--subnet-range ' + cls.subnet + ' ' + cls.SUBNET_NAME ) + # Create a subnet for the private network + priv_raw_output = cls.openstack( + 'subnet create ' + + '--network ' + cls.PRIVATE_NETWORK_NAME + ' ' + + '--subnet-range ' + cls.private_subnet + ' ' + + cls.PRIVATE_SUBNET_NAME + ) except Exception: if (i == 3): # raise the exception at the last time @@ -64,13 +87,19 @@ def setUpClass(cls): break cls.subnet_id = re.search(cls.re_id, raw_output).group(1) + cls.private_subnet_id = re.search(cls.re_id, priv_raw_output).group(1) @classmethod def tearDownClass(cls): raw_output = cls.openstack('subnet delete ' + cls.SUBNET_NAME) cls.assertOutput('', raw_output) + raw_output = cls.openstack('subnet delete ' + cls.PRIVATE_SUBNET_NAME) + cls.assertOutput('', raw_output) raw_output = cls.openstack('network delete ' + cls.NETWORK_NAME) cls.assertOutput('', raw_output) + raw_output = cls.openstack( + 'network delete ' + cls.PRIVATE_NETWORK_NAME) + cls.assertOutput('', raw_output) def test_floating_ip_delete(self): """Test create, delete multiple""" @@ -168,3 +197,50 @@ def test_floating_ip_show(self): # re.search(self.re_floating_ip, raw_output).group(1), # ) self.assertIsNotNone(re.search(self.re_network_id, raw_output)) + + def test_floating_ip_set_and_unset_port(self): + """Test Floating IP Set and Unset port""" + raw_output = self.openstack( + 'floating ip create ' + + '--description shosho ' + + self.NETWORK_NAME + ) + re_ip = re.search(self.re_floating_ip, raw_output) + fp_ip = re_ip.group(1) + self.addCleanup(self.openstack, 'floating ip delete ' + fp_ip) + self.assertIsNotNone(fp_ip) + + raw_output1 = self.openstack( + 'port create --network ' + self.PRIVATE_NETWORK_NAME + + ' --fixed-ip subnet=' + self.PRIVATE_SUBNET_NAME + + ' ' + self.PORT_NAME + ) + re_port_id = re.search(self.re_port_id, raw_output1) + self.assertIsNotNone(re_port_id) + port_id = re_port_id.group(1) + + router = self.openstack('router create ' + self.ROUTER) + self.assertIsNotNone(router) + self.addCleanup(self.openstack, 'router delete ' + self.ROUTER) + + self.openstack('router add port ' + self.ROUTER + + ' ' + port_id) + self.openstack('router set --external-gateway ' + self.NETWORK_NAME + + ' ' + self.ROUTER) + + self.addCleanup(self.openstack, 'router unset --external-gateway ' + + self.ROUTER) + self.addCleanup(self.openstack, 'router remove port ' + self.ROUTER + + ' ' + port_id) + + raw_output = self.openstack( + 'floating ip set ' + + fp_ip + ' --port ' + port_id) + self.addCleanup(self.openstack, 'floating ip unset --port ' + fp_ip) + + show_output = self.openstack( + 'floating ip show ' + fp_ip) + + self.assertEqual( + port_id, + re.search(self.re_fp_port_id, show_output).group(1)) diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip.py b/openstackclient/tests/unit/network/v2/test_floating_ip.py index 0b3fd888a6..69fb14196a 100644 --- a/openstackclient/tests/unit/network/v2/test_floating_ip.py +++ b/openstackclient/tests/unit/network/v2/test_floating_ip.py @@ -16,7 +16,7 @@ from osc_lib import exceptions -from openstackclient.network.v2 import floating_ip +from openstackclient.network.v2 import floating_ip as fip from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3 from openstackclient.tests.unit.network.v2 import fakes as network_fakes @@ -92,7 +92,7 @@ def setUp(self): self.network.find_port = mock.Mock(return_value=self.port) # Get the command object to test - self.cmd = floating_ip.CreateFloatingIP(self.app, self.namespace) + self.cmd = fip.CreateFloatingIP(self.app, self.namespace) def test_create_no_options(self): arglist = [] @@ -210,12 +210,9 @@ def setUp(self): self.network.delete_ip = mock.Mock(return_value=None) # Get the command object to test - self.cmd = floating_ip.DeleteFloatingIP(self.app, self.namespace) + self.cmd = fip.DeleteFloatingIP(self.app, self.namespace) - @mock.patch( - "openstackclient.tests.unit.network.v2.test_floating_ip." + - "floating_ip._find_floating_ip" - ) + @mock.patch.object(fip, '_find_floating_ip') def test_floating_ip_delete(self, find_floating_ip_mock): find_floating_ip_mock.side_effect = [ self.floating_ips[0], @@ -239,10 +236,7 @@ def test_floating_ip_delete(self, find_floating_ip_mock): self.network.delete_ip.assert_called_once_with(self.floating_ips[0]) self.assertIsNone(result) - @mock.patch( - "openstackclient.tests.unit.network.v2.test_floating_ip." + - "floating_ip._find_floating_ip" - ) + @mock.patch.object(fip, '_find_floating_ip') def test_floating_ip_delete_multi(self, find_floating_ip_mock): find_floating_ip_mock.side_effect = [ self.floating_ips[0], @@ -280,10 +274,7 @@ def test_floating_ip_delete_multi(self, find_floating_ip_mock): self.network.delete_ip.assert_has_calls(calls) self.assertIsNone(result) - @mock.patch( - "openstackclient.tests.unit.network.v2.test_floating_ip." + - "floating_ip._find_floating_ip" - ) + @mock.patch.object(fip, '_find_floating_ip') def test_floating_ip_delete_multi_exception(self, find_floating_ip_mock): find_floating_ip_mock.side_effect = [ self.floating_ips[0], @@ -380,7 +371,7 @@ def setUp(self): self.network.find_router = mock.Mock(return_value=self.fake_router) # Get the command object to test - self.cmd = floating_ip.ListFloatingIP(self.app, self.namespace) + self.cmd = fip.ListFloatingIP(self.app, self.namespace) def test_floating_ip_list(self): arglist = [] @@ -572,12 +563,9 @@ def setUp(self): self.network.find_ip = mock.Mock(return_value=self.floating_ip) # Get the command object to test - self.cmd = floating_ip.ShowFloatingIP(self.app, self.namespace) + self.cmd = fip.ShowFloatingIP(self.app, self.namespace) - @mock.patch( - "openstackclient.tests.unit.network.v2.test_floating_ip." + - "floating_ip._find_floating_ip" - ) + @mock.patch.object(fip, '_find_floating_ip') def test_floating_ip_show(self, find_floating_ip_mock): find_floating_ip_mock.return_value = self.floating_ip arglist = [ @@ -599,6 +587,154 @@ def test_floating_ip_show(self, find_floating_ip_mock): self.assertEqual(self.data, data) +class TestSetFloatingIP(TestFloatingIPNetwork): + + # Fake data for option tests. + floating_network = network_fakes.FakeNetwork.create_one_network() + subnet = network_fakes.FakeSubnet.create_one_subnet() + port = network_fakes.FakePort.create_one_port() + + # The floating ip to be deleted. + floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip( + attrs={ + 'floating_network_id': floating_network.id, + 'port_id': port.id, + } + ) + + def setUp(self): + super(TestSetFloatingIP, self).setUp() + self.network.find_ip = mock.Mock(return_value=self.floating_ip) + self.network.find_port = mock.Mock(return_value=self.port) + self.network.update_ip = mock.Mock(return_value=None) + + # Get the command object to test + self.cmd = fip.SetFloatingIP(self.app, self.namespace) + + @mock.patch( + "openstackclient.tests.unit.network.v2.test_floating_ip." + + "fip._find_floating_ip" + ) + def test_port_option(self, find_floating_ip_mock): + find_floating_ip_mock.side_effect = [ + self.floating_ip, + ] + arglist = [ + self.floating_ip.id, + '--port', self.floating_ip.port_id, + ] + verifylist = [ + ('floating_ip', self.floating_ip.id), + ('port', self.floating_ip.port_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + attrs = { + 'port_id': self.floating_ip.port_id, + } + + find_floating_ip_mock.assert_called_once_with( + mock.ANY, + self.floating_ip.id, + ignore_missing=False, + ) + + self.network.update_ip.assert_called_once_with( + self.floating_ip, **attrs) + + @mock.patch( + "openstackclient.tests.unit.network.v2.test_floating_ip." + + "fip._find_floating_ip" + ) + def test_fixed_ip_option(self, find_floating_ip_mock): + find_floating_ip_mock.side_effect = [ + self.floating_ip, + ] + arglist = [ + self.floating_ip.id, + '--port', self.floating_ip.port_id, + "--fixed-ip-address", self.floating_ip.fixed_ip_address, + ] + verifylist = [ + ('floating_ip', self.floating_ip.id), + ('port', self.floating_ip.port_id), + ('fixed_ip_address', self.floating_ip.fixed_ip_address), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + attrs = { + 'port_id': self.floating_ip.port_id, + 'fixed_ip_address': self.floating_ip.fixed_ip_address, + } + find_floating_ip_mock.assert_called_once_with( + mock.ANY, + self.floating_ip.id, + ignore_missing=False, + ) + self.network.update_ip.assert_called_once_with( + self.floating_ip, **attrs) + + +class TestUnsetFloatingIP(TestFloatingIPNetwork): + + floating_network = network_fakes.FakeNetwork.create_one_network() + subnet = network_fakes.FakeSubnet.create_one_subnet() + port = network_fakes.FakePort.create_one_port() + + # The floating ip to be deleted. + floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip( + attrs={ + 'floating_network_id': floating_network.id, + 'port_id': port.id, + } + ) + + def setUp(self): + super(TestUnsetFloatingIP, self).setUp() + self.network.find_ip = mock.Mock(return_value=self.floating_ip) + self.network.update_ip = mock.Mock(return_value=None) + + # Get the command object to test + self.cmd = fip.UnsetFloatingIP(self.app, self.namespace) + + @mock.patch( + "openstackclient.tests.unit.network.v2.test_floating_ip." + + "fip._find_floating_ip" + ) + def test_floating_ip_unset_port(self, find_floating_ip_mock): + find_floating_ip_mock.side_effect = [ + self.floating_ip, + ] + arglist = [ + self.floating_ip.id, + "--port", + ] + verifylist = [ + ('floating_ip', self.floating_ip.id), + ('port', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + attrs = { + 'port_id': None, + } + find_floating_ip_mock.assert_called_once_with( + mock.ANY, + self.floating_ip.id, + ignore_missing=False, + ) + self.network.update_ip.assert_called_once_with( + self.floating_ip, **attrs) + + self.assertIsNone(result) + + # Tests for Nova network # class TestFloatingIPCompute(compute_fakes.TestComputev2): @@ -639,7 +775,7 @@ def setUp(self): self.compute.floating_ips.create.return_value = self.floating_ip # Get the command object to test - self.cmd = floating_ip.CreateFloatingIP(self.app, None) + self.cmd = fip.CreateFloatingIP(self.app, None) def test_create_no_options(self): arglist = [] @@ -682,7 +818,7 @@ def setUp(self): compute_fakes.FakeFloatingIP.get_floating_ips(self.floating_ips)) # Get the command object to test - self.cmd = floating_ip.DeleteFloatingIP(self.app, None) + self.cmd = fip.DeleteFloatingIP(self.app, None) def test_floating_ip_delete(self): arglist = [ @@ -782,7 +918,7 @@ def setUp(self): self.compute.floating_ips.list.return_value = self.floating_ips # Get the command object to test - self.cmd = floating_ip.ListFloatingIP(self.app, None) + self.cmd = fip.ListFloatingIP(self.app, None) def test_floating_ip_list(self): arglist = [] @@ -826,7 +962,7 @@ def setUp(self): self.compute.floating_ips.get.return_value = self.floating_ip # Get the command object to test - self.cmd = floating_ip.ShowFloatingIP(self.app, None) + self.cmd = fip.ShowFloatingIP(self.app, None) def test_floating_ip_show(self): arglist = [ diff --git a/releasenotes/notes/floating-ip-set-unset-port-28e33875937b69cf.yaml b/releasenotes/notes/floating-ip-set-unset-port-28e33875937b69cf.yaml new file mode 100644 index 0000000000..b4a899a426 --- /dev/null +++ b/releasenotes/notes/floating-ip-set-unset-port-28e33875937b69cf.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``floating ip set`` and ``floating ip unset`` commands. + [:lpbug:`1560297`] diff --git a/setup.cfg b/setup.cfg index 6b4e2e1d6e..92f7bbb7b0 100644 --- a/setup.cfg +++ b/setup.cfg @@ -347,7 +347,9 @@ openstack.network.v2 = floating_ip_create = openstackclient.network.v2.floating_ip:CreateFloatingIP floating_ip_delete = openstackclient.network.v2.floating_ip:DeleteFloatingIP floating_ip_list = openstackclient.network.v2.floating_ip:ListFloatingIP + floating_ip_set = openstackclient.network.v2.floating_ip:SetFloatingIP floating_ip_show = openstackclient.network.v2.floating_ip:ShowFloatingIP + floating_ip_unset = openstackclient.network.v2.floating_ip:UnsetFloatingIP floating_ip_pool_list = openstackclient.network.v2.floating_ip_pool:ListFloatingIPPool From 46b8cad4c3a5f7a4fb3a08b4ce6fb63fa47ebac3 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 1 Feb 2017 16:40:04 -0600 Subject: [PATCH 1640/3095] Clean up password prompt work-arounds osc-lib 1.2 is minimum and now handles the password prompting. Change-Id: Ie11ad64796d3a89c7396b321c34947d622d1ed39 --- openstackclient/common/clientmanager.py | 4 ++-- openstackclient/shell.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index 3e1a50e3e6..27f3b70552 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -45,12 +45,12 @@ def __init__( self, cli_options=None, api_version=None, + pw_func=None, ): super(ClientManager, self).__init__( cli_options=cli_options, api_version=api_version, - # TODO(dtroyer): Remove this when osc-lib 1.2 is released - pw_func=shell.prompt_for_password, + pw_func=pw_func, ) # TODO(dtroyer): For compatibility; mark this for removal when plugin diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 4ec357cde0..8fdf0b61f2 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -170,6 +170,7 @@ def initialize_app(self, argv): self.client_manager = clientmanager.ClientManager( cli_options=self.cloud, api_version=self.api_version, + pw_func=shell.prompt_for_password, ) def prepare_to_run_command(self, cmd): From fe59e339ae6dd6a5e9075773fb5c2a0fea9c2e53 Mon Sep 17 00:00:00 2001 From: venkata anil Date: Tue, 14 Feb 2017 05:05:20 +0000 Subject: [PATCH 1641/3095] Allow override of distributed router flag When router_distributed=True is set in neutron.conf, user can't override this and create a centralized router through openstack client. Openstack client allows modifying routers from distributed to centralized after creation but not supporting centralized flag during creation. We allow centralized and distributed flags during router creation with this change. Closes-bug: #1664255 Change-Id: I75f72ca695338ad8c381cfa89fbb9d8e61ee7bc5 --- doc/source/command-objects/router.rst | 15 +++++++++- openstackclient/network/v2/router.py | 13 +++++---- .../tests/unit/network/v2/test_router.py | 29 +++++++++++++++++++ .../notes/bug-1664255-f82c5c13d92fed2a.yaml | 9 ++++++ 4 files changed, 60 insertions(+), 6 deletions(-) create mode 100644 releasenotes/notes/bug-1664255-f82c5c13d92fed2a.yaml diff --git a/doc/source/command-objects/router.rst b/doc/source/command-objects/router.rst index 29131861d5..50e791ea7d 100644 --- a/doc/source/command-objects/router.rst +++ b/doc/source/command-objects/router.rst @@ -63,7 +63,7 @@ Create new router openstack router create [--project [--project-domain ]] [--enable | --disable] - [--distributed] + [--distributed | --centralized] [--ha | --no-ha] [--description ] [--availability-zone-hint ] @@ -90,6 +90,19 @@ Create new router Create a distributed router + The default router type (distributed vs centralized) is determined by a + configuration setting in the OpenStack deployment. Since we are unable + to know that default wihtout attempting to actually create a router it + is suggested to use either :option:`--distributed` or :option:`--centralized` + in situations where multiple cloud deployments may be used. + +.. option:: --centralized + + Create a centralized router + + See the note in :option:`--distributed` regarding the default used when + creating a new router. + .. option:: --ha Create a highly available router diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index f322d5d1f2..0da91baa44 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -78,8 +78,7 @@ def _get_attrs(client_manager, parsed_args): attrs['admin_state_up'] = True if parsed_args.disable: attrs['admin_state_up'] = False - # centralized is available only for SetRouter and not for CreateRouter - if 'centralized' in parsed_args and parsed_args.centralized: + if parsed_args.centralized: attrs['distributed'] = False if parsed_args.distributed: attrs['distributed'] = True @@ -176,13 +175,17 @@ def get_parser(self, prog_name): action='store_true', help=_("Disable router") ) - parser.add_argument( + distribute_group = parser.add_mutually_exclusive_group() + distribute_group.add_argument( '--distributed', - dest='distributed', action='store_true', - default=False, help=_("Create a distributed router") ) + distribute_group.add_argument( + '--centralized', + action='store_true', + help=_("Create a centralized router") + ) ha_group = parser.add_mutually_exclusive_group() ha_group.add_argument( '--ha', diff --git a/openstackclient/tests/unit/network/v2/test_router.py b/openstackclient/tests/unit/network/v2/test_router.py index a4f91997c4..02e0be9459 100644 --- a/openstackclient/tests/unit/network/v2/test_router.py +++ b/openstackclient/tests/unit/network/v2/test_router.py @@ -211,6 +211,35 @@ def test_create_with_ha_option(self): def test_create_with_no_ha_option(self): self._test_create_with_ha_options('--no-ha', False) + def _test_create_with_distributed_options(self, option, distributed): + arglist = [ + option, + self.new_router.name, + ] + verifylist = [ + ('name', self.new_router.name), + ('enable', True), + ('distributed', distributed), + ('centralized', not distributed), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_router.assert_called_once_with(**{ + 'admin_state_up': True, + 'name': self.new_router.name, + 'distributed': distributed, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_with_distributed_option(self): + self._test_create_with_distributed_options('--distributed', True) + + def test_create_with_centralized_option(self): + self._test_create_with_distributed_options('--centralized', False) + def test_create_with_AZ_hints(self): arglist = [ self.new_router.name, diff --git a/releasenotes/notes/bug-1664255-f82c5c13d92fed2a.yaml b/releasenotes/notes/bug-1664255-f82c5c13d92fed2a.yaml new file mode 100644 index 0000000000..ec162306b4 --- /dev/null +++ b/releasenotes/notes/bug-1664255-f82c5c13d92fed2a.yaml @@ -0,0 +1,9 @@ +--- +fixes: + - | + Allow users to create centralized (distributed=False) + routers using the ``--centralized`` option in ``router create``. + Without this, routers are created based on the default + neutron configuration of the deployment, which, for example, + could be 'distributed'. + [Bug `1664255 `_] From 488ca596ec8fbcecb3e7591ab2bbe41385ab9d8d Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 6 Apr 2017 12:11:40 -0500 Subject: [PATCH 1642/3095] Split security group tests Split up the security group tests between compute and network APIs into separate files in preparation for reworking the compute (nova-net) implementations to deal with the upcoming removal of deprecated nova-net support in novaclient 8.0.0. No code changes are intended here, just splitting two files into four. Change-Id: I3fbdde45f593a30de545ddd11e319a4d6f900b18 --- .../network/v2/test_security_group_compute.py | 403 +++++++++++++ ...roup.py => test_security_group_network.py} | 382 ------------ .../v2/test_security_group_rule_compute.py | 556 ++++++++++++++++++ ...py => test_security_group_rule_network.py} | 535 ----------------- 4 files changed, 959 insertions(+), 917 deletions(-) create mode 100644 openstackclient/tests/unit/network/v2/test_security_group_compute.py rename openstackclient/tests/unit/network/v2/{test_security_group.py => test_security_group_network.py} (53%) create mode 100644 openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py rename openstackclient/tests/unit/network/v2/{test_security_group_rule.py => test_security_group_rule_network.py} (62%) diff --git a/openstackclient/tests/unit/network/v2/test_security_group_compute.py b/openstackclient/tests/unit/network/v2/test_security_group_compute.py new file mode 100644 index 0000000000..2fd441888f --- /dev/null +++ b/openstackclient/tests/unit/network/v2/test_security_group_compute.py @@ -0,0 +1,403 @@ +# 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 mock import call + +from osc_lib import exceptions + +from openstackclient.network.v2 import security_group +from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes +from openstackclient.tests.unit import utils as tests_utils + + +class TestSecurityGroupCompute(compute_fakes.TestComputev2): + + def setUp(self): + super(TestSecurityGroupCompute, self).setUp() + + # Get a shortcut to the compute client + self.compute = self.app.client_manager.compute + + +class TestCreateSecurityGroupCompute(TestSecurityGroupCompute): + + project = identity_fakes.FakeProject.create_one_project() + domain = identity_fakes.FakeDomain.create_one_domain() + # The security group to be shown. + _security_group = \ + compute_fakes.FakeSecurityGroup.create_one_security_group() + + columns = ( + 'description', + 'id', + 'name', + 'project_id', + 'rules', + ) + + data = ( + _security_group.description, + _security_group.id, + _security_group.name, + _security_group.tenant_id, + '', + ) + + def setUp(self): + super(TestCreateSecurityGroupCompute, self).setUp() + + self.app.client_manager.network_endpoint_enabled = False + + self.compute.security_groups.create.return_value = self._security_group + + # Get the command object to test + self.cmd = security_group.CreateSecurityGroup(self.app, None) + + def test_create_no_options(self): + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, [], []) + + def test_create_network_options(self): + arglist = [ + '--project', self.project.name, + '--project-domain', self.domain.name, + self._security_group.name, + ] + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, arglist, []) + + def test_create_min_options(self): + arglist = [ + self._security_group.name, + ] + verifylist = [ + ('name', self._security_group.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.compute.security_groups.create.assert_called_once_with( + self._security_group.name, + self._security_group.name) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_all_options(self): + arglist = [ + '--description', self._security_group.description, + self._security_group.name, + ] + verifylist = [ + ('description', self._security_group.description), + ('name', self._security_group.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.compute.security_groups.create.assert_called_once_with( + self._security_group.name, + self._security_group.description) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestDeleteSecurityGroupCompute(TestSecurityGroupCompute): + + # The security groups to be deleted. + _security_groups = \ + compute_fakes.FakeSecurityGroup.create_security_groups() + + def setUp(self): + super(TestDeleteSecurityGroupCompute, self).setUp() + + self.app.client_manager.network_endpoint_enabled = False + + self.compute.security_groups.delete = mock.Mock(return_value=None) + + self.compute.security_groups.get = ( + compute_fakes.FakeSecurityGroup.get_security_groups( + self._security_groups) + ) + + # Get the command object to test + self.cmd = security_group.DeleteSecurityGroup(self.app, None) + + def test_security_group_delete(self): + arglist = [ + self._security_groups[0].id, + ] + verifylist = [ + ('group', [self._security_groups[0].id]), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.compute.security_groups.delete.assert_called_once_with( + self._security_groups[0].id) + self.assertIsNone(result) + + def test_multi_security_groups_delete(self): + arglist = [] + verifylist = [] + + for s in self._security_groups: + arglist.append(s.id) + verifylist = [ + ('group', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [] + for s in self._security_groups: + calls.append(call(s.id)) + self.compute.security_groups.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_multi_security_groups_delete_with_exception(self): + arglist = [ + self._security_groups[0].id, + 'unexist_security_group', + ] + verifylist = [ + ('group', + [self._security_groups[0].id, 'unexist_security_group']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self._security_groups[0], exceptions.CommandError] + self.compute.security_groups.get = ( + mock.Mock(side_effect=find_mock_result) + ) + self.compute.security_groups.find.side_effect = ( + exceptions.NotFound(None)) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 groups failed to delete.', str(e)) + + self.compute.security_groups.get.assert_any_call( + self._security_groups[0].id) + self.compute.security_groups.get.assert_any_call( + 'unexist_security_group') + self.compute.security_groups.delete.assert_called_once_with( + self._security_groups[0].id + ) + + +class TestListSecurityGroupCompute(TestSecurityGroupCompute): + + # The security group to be listed. + _security_groups = \ + compute_fakes.FakeSecurityGroup.create_security_groups(count=3) + + columns = ( + 'ID', + 'Name', + 'Description', + ) + columns_all_projects = ( + 'ID', + 'Name', + 'Description', + 'Project', + ) + + data = [] + for grp in _security_groups: + data.append(( + grp.id, + grp.name, + grp.description, + )) + data_all_projects = [] + for grp in _security_groups: + data_all_projects.append(( + grp.id, + grp.name, + grp.description, + grp.tenant_id, + )) + + def setUp(self): + super(TestListSecurityGroupCompute, self).setUp() + + self.app.client_manager.network_endpoint_enabled = False + self.compute.security_groups.list.return_value = self._security_groups + + # Get the command object to test + self.cmd = security_group.ListSecurityGroup(self.app, None) + + def test_security_group_list_no_options(self): + arglist = [] + verifylist = [ + ('all_projects', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + kwargs = {'search_opts': {'all_tenants': False}} + self.compute.security_groups.list.assert_called_once_with(**kwargs) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_security_group_list_all_projects(self): + arglist = [ + '--all-projects', + ] + verifylist = [ + ('all_projects', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + kwargs = {'search_opts': {'all_tenants': True}} + self.compute.security_groups.list.assert_called_once_with(**kwargs) + self.assertEqual(self.columns_all_projects, columns) + self.assertEqual(self.data_all_projects, list(data)) + + +class TestSetSecurityGroupCompute(TestSecurityGroupCompute): + + # The security group to be set. + _security_group = \ + compute_fakes.FakeSecurityGroup.create_one_security_group() + + def setUp(self): + super(TestSetSecurityGroupCompute, self).setUp() + + self.app.client_manager.network_endpoint_enabled = False + + self.compute.security_groups.update = mock.Mock(return_value=None) + + self.compute.security_groups.get = mock.Mock( + return_value=self._security_group) + + # Get the command object to test + self.cmd = security_group.SetSecurityGroup(self.app, None) + + def test_set_no_options(self): + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, [], []) + + def test_set_no_updates(self): + arglist = [ + self._security_group.name, + ] + verifylist = [ + ('group', self._security_group.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.compute.security_groups.update.assert_called_once_with( + self._security_group, + self._security_group.name, + self._security_group.description + ) + self.assertIsNone(result) + + def test_set_all_options(self): + new_name = 'new-' + self._security_group.name + new_description = 'new-' + self._security_group.description + arglist = [ + '--name', new_name, + '--description', new_description, + self._security_group.name, + ] + verifylist = [ + ('description', new_description), + ('group', self._security_group.name), + ('name', new_name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.compute.security_groups.update.assert_called_once_with( + self._security_group, + new_name, + new_description + ) + self.assertIsNone(result) + + +class TestShowSecurityGroupCompute(TestSecurityGroupCompute): + + # The security group rule to be shown with the group. + _security_group_rule = \ + compute_fakes.FakeSecurityGroupRule.create_one_security_group_rule() + + # The security group to be shown. + _security_group = \ + compute_fakes.FakeSecurityGroup.create_one_security_group( + attrs={'rules': [_security_group_rule._info]} + ) + + columns = ( + 'description', + 'id', + 'name', + 'project_id', + 'rules', + ) + + data = ( + _security_group.description, + _security_group.id, + _security_group.name, + _security_group.tenant_id, + security_group._format_compute_security_group_rules( + [_security_group_rule._info]), + ) + + def setUp(self): + super(TestShowSecurityGroupCompute, self).setUp() + + self.app.client_manager.network_endpoint_enabled = False + + self.compute.security_groups.get.return_value = self._security_group + + # Get the command object to test + self.cmd = security_group.ShowSecurityGroup(self.app, None) + + def test_show_no_options(self): + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, [], []) + + def test_show_all_options(self): + arglist = [ + self._security_group.id, + ] + verifylist = [ + ('group', self._security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.compute.security_groups.get.assert_called_once_with( + self._security_group.id) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) diff --git a/openstackclient/tests/unit/network/v2/test_security_group.py b/openstackclient/tests/unit/network/v2/test_security_group_network.py similarity index 53% rename from openstackclient/tests/unit/network/v2/test_security_group.py rename to openstackclient/tests/unit/network/v2/test_security_group_network.py index 66d357f98a..35b7e366d6 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_network.py @@ -17,7 +17,6 @@ from osc_lib import exceptions from openstackclient.network.v2 import security_group -from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes from openstackclient.tests.unit.network.v2 import fakes as network_fakes from openstackclient.tests.unit import utils as tests_utils @@ -36,15 +35,6 @@ def setUp(self): self.domains_mock = self.app.client_manager.identity.domains -class TestSecurityGroupCompute(compute_fakes.TestComputev2): - - def setUp(self): - super(TestSecurityGroupCompute, self).setUp() - - # Get a shortcut to the compute client - self.compute = self.app.client_manager.compute - - class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork): project = identity_fakes.FakeProject.create_one_project() @@ -129,90 +119,6 @@ def test_create_all_options(self): self.assertEqual(self.data, data) -class TestCreateSecurityGroupCompute(TestSecurityGroupCompute): - - project = identity_fakes.FakeProject.create_one_project() - domain = identity_fakes.FakeDomain.create_one_domain() - # The security group to be shown. - _security_group = \ - compute_fakes.FakeSecurityGroup.create_one_security_group() - - columns = ( - 'description', - 'id', - 'name', - 'project_id', - 'rules', - ) - - data = ( - _security_group.description, - _security_group.id, - _security_group.name, - _security_group.tenant_id, - '', - ) - - def setUp(self): - super(TestCreateSecurityGroupCompute, self).setUp() - - self.app.client_manager.network_endpoint_enabled = False - - self.compute.security_groups.create.return_value = self._security_group - - # Get the command object to test - self.cmd = security_group.CreateSecurityGroup(self.app, None) - - def test_create_no_options(self): - self.assertRaises(tests_utils.ParserException, - self.check_parser, self.cmd, [], []) - - def test_create_network_options(self): - arglist = [ - '--project', self.project.name, - '--project-domain', self.domain.name, - self._security_group.name, - ] - self.assertRaises(tests_utils.ParserException, - self.check_parser, self.cmd, arglist, []) - - def test_create_min_options(self): - arglist = [ - self._security_group.name, - ] - verifylist = [ - ('name', self._security_group.name), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.compute.security_groups.create.assert_called_once_with( - self._security_group.name, - self._security_group.name) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) - - def test_create_all_options(self): - arglist = [ - '--description', self._security_group.description, - self._security_group.name, - ] - verifylist = [ - ('description', self._security_group.description), - ('name', self._security_group.name), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.compute.security_groups.create.assert_called_once_with( - self._security_group.name, - self._security_group.description) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) - - class TestDeleteSecurityGroupNetwork(TestSecurityGroupNetwork): # The security groups to be deleted. @@ -297,94 +203,6 @@ def test_multi_security_groups_delete_with_exception(self): ) -class TestDeleteSecurityGroupCompute(TestSecurityGroupCompute): - - # The security groups to be deleted. - _security_groups = \ - compute_fakes.FakeSecurityGroup.create_security_groups() - - def setUp(self): - super(TestDeleteSecurityGroupCompute, self).setUp() - - self.app.client_manager.network_endpoint_enabled = False - - self.compute.security_groups.delete = mock.Mock(return_value=None) - - self.compute.security_groups.get = ( - compute_fakes.FakeSecurityGroup.get_security_groups( - self._security_groups) - ) - - # Get the command object to test - self.cmd = security_group.DeleteSecurityGroup(self.app, None) - - def test_security_group_delete(self): - arglist = [ - self._security_groups[0].id, - ] - verifylist = [ - ('group', [self._security_groups[0].id]), - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) - - self.compute.security_groups.delete.assert_called_once_with( - self._security_groups[0].id) - self.assertIsNone(result) - - def test_multi_security_groups_delete(self): - arglist = [] - verifylist = [] - - for s in self._security_groups: - arglist.append(s.id) - verifylist = [ - ('group', arglist), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - calls = [] - for s in self._security_groups: - calls.append(call(s.id)) - self.compute.security_groups.delete.assert_has_calls(calls) - self.assertIsNone(result) - - def test_multi_security_groups_delete_with_exception(self): - arglist = [ - self._security_groups[0].id, - 'unexist_security_group', - ] - verifylist = [ - ('group', - [self._security_groups[0].id, 'unexist_security_group']), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - find_mock_result = [self._security_groups[0], exceptions.CommandError] - self.compute.security_groups.get = ( - mock.Mock(side_effect=find_mock_result) - ) - self.compute.security_groups.find.side_effect = ( - exceptions.NotFound(None)) - - try: - self.cmd.take_action(parsed_args) - self.fail('CommandError should be raised.') - except exceptions.CommandError as e: - self.assertEqual('1 of 2 groups failed to delete.', str(e)) - - self.compute.security_groups.get.assert_any_call( - self._security_groups[0].id) - self.compute.security_groups.get.assert_any_call( - 'unexist_security_group') - self.compute.security_groups.delete.assert_called_once_with( - self._security_groups[0].id - ) - - class TestListSecurityGroupNetwork(TestSecurityGroupNetwork): # The security group to be listed. @@ -483,80 +301,6 @@ def test_security_group_list_project_domain(self): self.assertEqual(self.data, list(data)) -class TestListSecurityGroupCompute(TestSecurityGroupCompute): - - # The security group to be listed. - _security_groups = \ - compute_fakes.FakeSecurityGroup.create_security_groups(count=3) - - columns = ( - 'ID', - 'Name', - 'Description', - ) - columns_all_projects = ( - 'ID', - 'Name', - 'Description', - 'Project', - ) - - data = [] - for grp in _security_groups: - data.append(( - grp.id, - grp.name, - grp.description, - )) - data_all_projects = [] - for grp in _security_groups: - data_all_projects.append(( - grp.id, - grp.name, - grp.description, - grp.tenant_id, - )) - - def setUp(self): - super(TestListSecurityGroupCompute, self).setUp() - - self.app.client_manager.network_endpoint_enabled = False - self.compute.security_groups.list.return_value = self._security_groups - - # Get the command object to test - self.cmd = security_group.ListSecurityGroup(self.app, None) - - def test_security_group_list_no_options(self): - arglist = [] - verifylist = [ - ('all_projects', False), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - kwargs = {'search_opts': {'all_tenants': False}} - self.compute.security_groups.list.assert_called_once_with(**kwargs) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) - - def test_security_group_list_all_projects(self): - arglist = [ - '--all-projects', - ] - verifylist = [ - ('all_projects', True), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - kwargs = {'search_opts': {'all_tenants': True}} - self.compute.security_groups.list.assert_called_once_with(**kwargs) - self.assertEqual(self.columns_all_projects, columns) - self.assertEqual(self.data_all_projects, list(data)) - - class TestSetSecurityGroupNetwork(TestSecurityGroupNetwork): # The security group to be set. @@ -623,72 +367,6 @@ def test_set_all_options(self): self.assertIsNone(result) -class TestSetSecurityGroupCompute(TestSecurityGroupCompute): - - # The security group to be set. - _security_group = \ - compute_fakes.FakeSecurityGroup.create_one_security_group() - - def setUp(self): - super(TestSetSecurityGroupCompute, self).setUp() - - self.app.client_manager.network_endpoint_enabled = False - - self.compute.security_groups.update = mock.Mock(return_value=None) - - self.compute.security_groups.get = mock.Mock( - return_value=self._security_group) - - # Get the command object to test - self.cmd = security_group.SetSecurityGroup(self.app, None) - - def test_set_no_options(self): - self.assertRaises(tests_utils.ParserException, - self.check_parser, self.cmd, [], []) - - def test_set_no_updates(self): - arglist = [ - self._security_group.name, - ] - verifylist = [ - ('group', self._security_group.name), - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) - - self.compute.security_groups.update.assert_called_once_with( - self._security_group, - self._security_group.name, - self._security_group.description - ) - self.assertIsNone(result) - - def test_set_all_options(self): - new_name = 'new-' + self._security_group.name - new_description = 'new-' + self._security_group.description - arglist = [ - '--name', new_name, - '--description', new_description, - self._security_group.name, - ] - verifylist = [ - ('description', new_description), - ('group', self._security_group.name), - ('name', new_name), - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) - - self.compute.security_groups.update.assert_called_once_with( - self._security_group, - new_name, - new_description - ) - self.assertIsNone(result) - - class TestShowSecurityGroupNetwork(TestSecurityGroupNetwork): # The security group rule to be shown with the group. @@ -746,63 +424,3 @@ def test_show_all_options(self): self._security_group.id, ignore_missing=False) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) - - -class TestShowSecurityGroupCompute(TestSecurityGroupCompute): - - # The security group rule to be shown with the group. - _security_group_rule = \ - compute_fakes.FakeSecurityGroupRule.create_one_security_group_rule() - - # The security group to be shown. - _security_group = \ - compute_fakes.FakeSecurityGroup.create_one_security_group( - attrs={'rules': [_security_group_rule._info]} - ) - - columns = ( - 'description', - 'id', - 'name', - 'project_id', - 'rules', - ) - - data = ( - _security_group.description, - _security_group.id, - _security_group.name, - _security_group.tenant_id, - security_group._format_compute_security_group_rules( - [_security_group_rule._info]), - ) - - def setUp(self): - super(TestShowSecurityGroupCompute, self).setUp() - - self.app.client_manager.network_endpoint_enabled = False - - self.compute.security_groups.get.return_value = self._security_group - - # Get the command object to test - self.cmd = security_group.ShowSecurityGroup(self.app, None) - - def test_show_no_options(self): - self.assertRaises(tests_utils.ParserException, - self.check_parser, self.cmd, [], []) - - def test_show_all_options(self): - arglist = [ - self._security_group.id, - ] - verifylist = [ - ('group', self._security_group.id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.compute.security_groups.get.assert_called_once_with( - self._security_group.id) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) diff --git a/openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py b/openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py new file mode 100644 index 0000000000..7833c0d932 --- /dev/null +++ b/openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py @@ -0,0 +1,556 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +import copy +import mock +from mock import call + +from osc_lib import exceptions + +from openstackclient.network import utils as network_utils +from openstackclient.network.v2 import security_group_rule +from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes +from openstackclient.tests.unit import fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes +from openstackclient.tests.unit import utils as tests_utils + + +class TestSecurityGroupRuleCompute(compute_fakes.TestComputev2): + + def setUp(self): + super(TestSecurityGroupRuleCompute, self).setUp() + + # Get a shortcut to the network client + self.compute = self.app.client_manager.compute + + +class TestCreateSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): + + project = identity_fakes.FakeProject.create_one_project() + domain = identity_fakes.FakeDomain.create_one_domain() + # The security group rule to be created. + _security_group_rule = None + + # The security group that will contain the rule created. + _security_group = \ + compute_fakes.FakeSecurityGroup.create_one_security_group() + + def _setup_security_group_rule(self, attrs=None): + self._security_group_rule = \ + compute_fakes.FakeSecurityGroupRule.create_one_security_group_rule( + attrs) + self.compute.security_group_rules.create.return_value = \ + self._security_group_rule + expected_columns, expected_data = \ + security_group_rule._format_security_group_rule_show( + self._security_group_rule._info) + return expected_columns, expected_data + + def setUp(self): + super(TestCreateSecurityGroupRuleCompute, self).setUp() + + self.app.client_manager.network_endpoint_enabled = False + + self.compute.security_groups.get.return_value = self._security_group + + # Get the command object to test + self.cmd = security_group_rule.CreateSecurityGroupRule(self.app, None) + + def test_create_no_options(self): + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, [], []) + + def test_create_all_source_options(self): + arglist = [ + '--src-ip', '10.10.0.0/24', + '--src-group', self._security_group.id, + self._security_group.id, + ] + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, arglist, []) + + def test_create_all_remote_options(self): + arglist = [ + '--remote-ip', '10.10.0.0/24', + '--remote-group', self._security_group.id, + self._security_group.id, + ] + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, arglist, []) + + def test_create_bad_protocol(self): + arglist = [ + '--protocol', 'foo', + self._security_group.id, + ] + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, arglist, []) + + def test_create_all_protocol_options(self): + arglist = [ + '--protocol', 'tcp', + '--proto', 'tcp', + self._security_group.id, + ] + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, arglist, []) + + def test_create_network_options(self): + arglist = [ + '--ingress', + '--ethertype', 'IPv4', + '--icmp-type', '3', + '--icmp-code', '11', + '--project', self.project.name, + '--project-domain', self.domain.name, + self._security_group.id, + ] + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, arglist, []) + + def test_create_default_rule(self): + expected_columns, expected_data = self._setup_security_group_rule() + dst_port = str(self._security_group_rule.from_port) + ':' + \ + str(self._security_group_rule.to_port) + arglist = [ + '--dst-port', dst_port, + self._security_group.id, + ] + verifylist = [ + ('dst_port', (self._security_group_rule.from_port, + self._security_group_rule.to_port)), + ('group', self._security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.compute.security_group_rules.create.assert_called_once_with( + self._security_group.id, + self._security_group_rule.ip_protocol, + self._security_group_rule.from_port, + self._security_group_rule.to_port, + self._security_group_rule.ip_range['cidr'], + None, + ) + self.assertEqual(expected_columns, columns) + self.assertEqual(expected_data, data) + + def test_create_source_group(self): + expected_columns, expected_data = self._setup_security_group_rule({ + 'from_port': 22, + 'to_port': 22, + 'group': {'name': self._security_group.name}, + }) + arglist = [ + '--dst-port', str(self._security_group_rule.from_port), + '--src-group', self._security_group.name, + self._security_group.id, + ] + verifylist = [ + ('dst_port', (self._security_group_rule.from_port, + self._security_group_rule.to_port)), + ('src_group', self._security_group.name), + ('group', self._security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.compute.security_group_rules.create.assert_called_once_with( + self._security_group.id, + self._security_group_rule.ip_protocol, + self._security_group_rule.from_port, + self._security_group_rule.to_port, + self._security_group_rule.ip_range['cidr'], + self._security_group.id, + ) + self.assertEqual(expected_columns, columns) + self.assertEqual(expected_data, data) + + def test_create_remote_group(self): + expected_columns, expected_data = self._setup_security_group_rule({ + 'from_port': 22, + 'to_port': 22, + 'group': {'name': self._security_group.name}, + }) + arglist = [ + '--dst-port', str(self._security_group_rule.from_port), + '--remote-group', self._security_group.name, + self._security_group.id, + ] + verifylist = [ + ('dst_port', (self._security_group_rule.from_port, + self._security_group_rule.to_port)), + ('remote_group', self._security_group.name), + ('group', self._security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.compute.security_group_rules.create.assert_called_once_with( + self._security_group.id, + self._security_group_rule.ip_protocol, + self._security_group_rule.from_port, + self._security_group_rule.to_port, + self._security_group_rule.ip_range['cidr'], + self._security_group.id, + ) + self.assertEqual(expected_columns, columns) + self.assertEqual(expected_data, data) + + def test_create_source_ip(self): + expected_columns, expected_data = self._setup_security_group_rule({ + 'ip_protocol': 'icmp', + 'from_port': -1, + 'to_port': -1, + 'ip_range': {'cidr': '10.0.2.0/24'}, + }) + arglist = [ + '--protocol', self._security_group_rule.ip_protocol, + '--src-ip', self._security_group_rule.ip_range['cidr'], + self._security_group.id, + ] + verifylist = [ + ('protocol', self._security_group_rule.ip_protocol), + ('src_ip', self._security_group_rule.ip_range['cidr']), + ('group', self._security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.compute.security_group_rules.create.assert_called_once_with( + self._security_group.id, + self._security_group_rule.ip_protocol, + self._security_group_rule.from_port, + self._security_group_rule.to_port, + self._security_group_rule.ip_range['cidr'], + None, + ) + self.assertEqual(expected_columns, columns) + self.assertEqual(expected_data, data) + + def test_create_remote_ip(self): + expected_columns, expected_data = self._setup_security_group_rule({ + 'ip_protocol': 'icmp', + 'from_port': -1, + 'to_port': -1, + 'ip_range': {'cidr': '10.0.2.0/24'}, + }) + arglist = [ + '--protocol', self._security_group_rule.ip_protocol, + '--remote-ip', self._security_group_rule.ip_range['cidr'], + self._security_group.id, + ] + verifylist = [ + ('protocol', self._security_group_rule.ip_protocol), + ('remote_ip', self._security_group_rule.ip_range['cidr']), + ('group', self._security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.compute.security_group_rules.create.assert_called_once_with( + self._security_group.id, + self._security_group_rule.ip_protocol, + self._security_group_rule.from_port, + self._security_group_rule.to_port, + self._security_group_rule.ip_range['cidr'], + None, + ) + self.assertEqual(expected_columns, columns) + self.assertEqual(expected_data, data) + + def test_create_proto_option(self): + expected_columns, expected_data = self._setup_security_group_rule({ + 'ip_protocol': 'icmp', + 'from_port': -1, + 'to_port': -1, + 'ip_range': {'cidr': '10.0.2.0/24'}, + }) + arglist = [ + '--proto', self._security_group_rule.ip_protocol, + '--src-ip', self._security_group_rule.ip_range['cidr'], + self._security_group.id, + ] + verifylist = [ + ('proto', self._security_group_rule.ip_protocol), + ('protocol', None), + ('src_ip', self._security_group_rule.ip_range['cidr']), + ('group', self._security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.compute.security_group_rules.create.assert_called_once_with( + self._security_group.id, + self._security_group_rule.ip_protocol, + self._security_group_rule.from_port, + self._security_group_rule.to_port, + self._security_group_rule.ip_range['cidr'], + None, + ) + self.assertEqual(expected_columns, columns) + self.assertEqual(expected_data, data) + + +class TestDeleteSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): + + # The security group rule to be deleted. + _security_group_rules = \ + compute_fakes.FakeSecurityGroupRule.create_security_group_rules( + count=2) + + def setUp(self): + super(TestDeleteSecurityGroupRuleCompute, self).setUp() + + self.app.client_manager.network_endpoint_enabled = False + + # Get the command object to test + self.cmd = security_group_rule.DeleteSecurityGroupRule(self.app, None) + + def test_security_group_rule_delete(self): + arglist = [ + self._security_group_rules[0].id, + ] + verifylist = [ + ('rule', [self._security_group_rules[0].id]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.compute.security_group_rules.delete.assert_called_once_with( + self._security_group_rules[0].id) + self.assertIsNone(result) + + def test_multi_security_group_rules_delete(self): + arglist = [] + verifylist = [] + + for s in self._security_group_rules: + arglist.append(s.id) + verifylist = [ + ('rule', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [] + for s in self._security_group_rules: + calls.append(call(s.id)) + self.compute.security_group_rules.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_multi_security_group_rules_delete_with_exception(self): + arglist = [ + self._security_group_rules[0].id, + 'unexist_rule', + ] + verifylist = [ + ('rule', + [self._security_group_rules[0].id, 'unexist_rule']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [None, exceptions.CommandError] + self.compute.security_group_rules.delete = ( + mock.Mock(side_effect=find_mock_result) + ) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 rules failed to delete.', str(e)) + + self.compute.security_group_rules.delete.assert_any_call( + self._security_group_rules[0].id) + self.compute.security_group_rules.delete.assert_any_call( + 'unexist_rule') + + +class TestListSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): + + # The security group to hold the rules. + _security_group = \ + compute_fakes.FakeSecurityGroup.create_one_security_group() + + # The security group rule to be listed. + _security_group_rule_tcp = \ + compute_fakes.FakeSecurityGroupRule.create_one_security_group_rule({ + 'ip_protocol': 'tcp', + 'from_port': 80, + 'to_port': 80, + 'group': {'name': _security_group.name}, + }) + _security_group_rule_icmp = \ + compute_fakes.FakeSecurityGroupRule.create_one_security_group_rule({ + 'ip_protocol': 'icmp', + 'from_port': -1, + 'to_port': -1, + 'ip_range': {'cidr': '10.0.2.0/24'}, + 'group': {'name': _security_group.name}, + }) + _security_group.rules = [_security_group_rule_tcp._info, + _security_group_rule_icmp._info] + + expected_columns_with_group = ( + 'ID', + 'IP Protocol', + 'IP Range', + 'Port Range', + 'Remote Security Group', + ) + expected_columns_no_group = \ + expected_columns_with_group + ('Security Group',) + + expected_data_with_group = [] + expected_data_no_group = [] + for _security_group_rule in _security_group.rules: + rule = network_utils.transform_compute_security_group_rule( + _security_group_rule + ) + expected_rule_with_group = ( + rule['id'], + rule['ip_protocol'], + rule['ip_range'], + rule['port_range'], + rule['remote_security_group'], + ) + expected_rule_no_group = expected_rule_with_group + \ + (_security_group_rule['parent_group_id'],) + expected_data_with_group.append(expected_rule_with_group) + expected_data_no_group.append(expected_rule_no_group) + + def setUp(self): + super(TestListSecurityGroupRuleCompute, self).setUp() + + self.app.client_manager.network_endpoint_enabled = False + + self.compute.security_groups.get.return_value = \ + self._security_group + self.compute.security_groups.list.return_value = \ + [self._security_group] + + # Get the command object to test + self.cmd = security_group_rule.ListSecurityGroupRule(self.app, None) + + def test_list_default(self): + parsed_args = self.check_parser(self.cmd, [], []) + + columns, data = self.cmd.take_action(parsed_args) + self.compute.security_groups.list.assert_called_once_with( + search_opts={'all_tenants': False} + ) + self.assertEqual(self.expected_columns_no_group, columns) + self.assertEqual(self.expected_data_no_group, list(data)) + + def test_list_with_group(self): + arglist = [ + self._security_group.id, + ] + verifylist = [ + ('group', self._security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.compute.security_groups.get.assert_called_once_with( + self._security_group.id + ) + self.assertEqual(self.expected_columns_with_group, columns) + self.assertEqual(self.expected_data_with_group, list(data)) + + def test_list_all_projects(self): + arglist = [ + '--all-projects', + ] + verifylist = [ + ('all_projects', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.compute.security_groups.list.assert_called_once_with( + search_opts={'all_tenants': True} + ) + self.assertEqual(self.expected_columns_no_group, columns) + self.assertEqual(self.expected_data_no_group, list(data)) + + def test_list_with_ignored_options(self): + arglist = [ + '--long', + ] + verifylist = [ + ('long', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.compute.security_groups.list.assert_called_once_with( + search_opts={'all_tenants': False} + ) + self.assertEqual(self.expected_columns_no_group, columns) + self.assertEqual(self.expected_data_no_group, list(data)) + + +class TestShowSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): + + # The security group rule to be shown. + _security_group_rule = \ + compute_fakes.FakeSecurityGroupRule.create_one_security_group_rule() + + columns, data = \ + security_group_rule._format_security_group_rule_show( + _security_group_rule._info) + + def setUp(self): + super(TestShowSecurityGroupRuleCompute, self).setUp() + + self.app.client_manager.network_endpoint_enabled = False + + # Build a security group fake customized for this test. + security_group_rules = [self._security_group_rule._info] + security_group = fakes.FakeResource( + info=copy.deepcopy({'rules': security_group_rules}), + loaded=True) + security_group.rules = security_group_rules + self.compute.security_groups.list.return_value = [security_group] + + # Get the command object to test + self.cmd = security_group_rule.ShowSecurityGroupRule(self.app, None) + + def test_show_no_options(self): + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, [], []) + + def test_show_all_options(self): + arglist = [ + self._security_group_rule.id, + ] + verifylist = [ + ('rule', self._security_group_rule.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.compute.security_groups.list.assert_called_once_with() + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) diff --git a/openstackclient/tests/unit/network/v2/test_security_group_rule.py b/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py similarity index 62% rename from openstackclient/tests/unit/network/v2/test_security_group_rule.py rename to openstackclient/tests/unit/network/v2/test_security_group_rule_network.py index e3538d5f4a..5d9d03e9c7 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_rule.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py @@ -11,16 +11,12 @@ # under the License. # -import copy import mock from mock import call from osc_lib import exceptions -from openstackclient.network import utils as network_utils from openstackclient.network.v2 import security_group_rule -from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes -from openstackclient.tests.unit import fakes from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes from openstackclient.tests.unit.network.v2 import fakes as network_fakes from openstackclient.tests.unit import utils as tests_utils @@ -39,15 +35,6 @@ def setUp(self): self.domains_mock = self.app.client_manager.identity.domains -class TestSecurityGroupRuleCompute(compute_fakes.TestComputev2): - - def setUp(self): - super(TestSecurityGroupRuleCompute, self).setUp() - - # Get a shortcut to the network client - self.compute = self.app.client_manager.compute - - class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): project = identity_fakes.FakeProject.create_one_project() @@ -548,280 +535,6 @@ def test_create_with_description(self): self.assertEqual(self.expected_data, data) -class TestCreateSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): - - project = identity_fakes.FakeProject.create_one_project() - domain = identity_fakes.FakeDomain.create_one_domain() - # The security group rule to be created. - _security_group_rule = None - - # The security group that will contain the rule created. - _security_group = \ - compute_fakes.FakeSecurityGroup.create_one_security_group() - - def _setup_security_group_rule(self, attrs=None): - self._security_group_rule = \ - compute_fakes.FakeSecurityGroupRule.create_one_security_group_rule( - attrs) - self.compute.security_group_rules.create.return_value = \ - self._security_group_rule - expected_columns, expected_data = \ - security_group_rule._format_security_group_rule_show( - self._security_group_rule._info) - return expected_columns, expected_data - - def setUp(self): - super(TestCreateSecurityGroupRuleCompute, self).setUp() - - self.app.client_manager.network_endpoint_enabled = False - - self.compute.security_groups.get.return_value = self._security_group - - # Get the command object to test - self.cmd = security_group_rule.CreateSecurityGroupRule(self.app, None) - - def test_create_no_options(self): - self.assertRaises(tests_utils.ParserException, - self.check_parser, self.cmd, [], []) - - def test_create_all_source_options(self): - arglist = [ - '--src-ip', '10.10.0.0/24', - '--src-group', self._security_group.id, - self._security_group.id, - ] - self.assertRaises(tests_utils.ParserException, - self.check_parser, self.cmd, arglist, []) - - def test_create_all_remote_options(self): - arglist = [ - '--remote-ip', '10.10.0.0/24', - '--remote-group', self._security_group.id, - self._security_group.id, - ] - self.assertRaises(tests_utils.ParserException, - self.check_parser, self.cmd, arglist, []) - - def test_create_bad_protocol(self): - arglist = [ - '--protocol', 'foo', - self._security_group.id, - ] - self.assertRaises(tests_utils.ParserException, - self.check_parser, self.cmd, arglist, []) - - def test_create_all_protocol_options(self): - arglist = [ - '--protocol', 'tcp', - '--proto', 'tcp', - self._security_group.id, - ] - self.assertRaises(tests_utils.ParserException, - self.check_parser, self.cmd, arglist, []) - - def test_create_network_options(self): - arglist = [ - '--ingress', - '--ethertype', 'IPv4', - '--icmp-type', '3', - '--icmp-code', '11', - '--project', self.project.name, - '--project-domain', self.domain.name, - self._security_group.id, - ] - self.assertRaises(tests_utils.ParserException, - self.check_parser, self.cmd, arglist, []) - - def test_create_default_rule(self): - expected_columns, expected_data = self._setup_security_group_rule() - dst_port = str(self._security_group_rule.from_port) + ':' + \ - str(self._security_group_rule.to_port) - arglist = [ - '--dst-port', dst_port, - self._security_group.id, - ] - verifylist = [ - ('dst_port', (self._security_group_rule.from_port, - self._security_group_rule.to_port)), - ('group', self._security_group.id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.compute.security_group_rules.create.assert_called_once_with( - self._security_group.id, - self._security_group_rule.ip_protocol, - self._security_group_rule.from_port, - self._security_group_rule.to_port, - self._security_group_rule.ip_range['cidr'], - None, - ) - self.assertEqual(expected_columns, columns) - self.assertEqual(expected_data, data) - - def test_create_source_group(self): - expected_columns, expected_data = self._setup_security_group_rule({ - 'from_port': 22, - 'to_port': 22, - 'group': {'name': self._security_group.name}, - }) - arglist = [ - '--dst-port', str(self._security_group_rule.from_port), - '--src-group', self._security_group.name, - self._security_group.id, - ] - verifylist = [ - ('dst_port', (self._security_group_rule.from_port, - self._security_group_rule.to_port)), - ('src_group', self._security_group.name), - ('group', self._security_group.id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.compute.security_group_rules.create.assert_called_once_with( - self._security_group.id, - self._security_group_rule.ip_protocol, - self._security_group_rule.from_port, - self._security_group_rule.to_port, - self._security_group_rule.ip_range['cidr'], - self._security_group.id, - ) - self.assertEqual(expected_columns, columns) - self.assertEqual(expected_data, data) - - def test_create_remote_group(self): - expected_columns, expected_data = self._setup_security_group_rule({ - 'from_port': 22, - 'to_port': 22, - 'group': {'name': self._security_group.name}, - }) - arglist = [ - '--dst-port', str(self._security_group_rule.from_port), - '--remote-group', self._security_group.name, - self._security_group.id, - ] - verifylist = [ - ('dst_port', (self._security_group_rule.from_port, - self._security_group_rule.to_port)), - ('remote_group', self._security_group.name), - ('group', self._security_group.id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.compute.security_group_rules.create.assert_called_once_with( - self._security_group.id, - self._security_group_rule.ip_protocol, - self._security_group_rule.from_port, - self._security_group_rule.to_port, - self._security_group_rule.ip_range['cidr'], - self._security_group.id, - ) - self.assertEqual(expected_columns, columns) - self.assertEqual(expected_data, data) - - def test_create_source_ip(self): - expected_columns, expected_data = self._setup_security_group_rule({ - 'ip_protocol': 'icmp', - 'from_port': -1, - 'to_port': -1, - 'ip_range': {'cidr': '10.0.2.0/24'}, - }) - arglist = [ - '--protocol', self._security_group_rule.ip_protocol, - '--src-ip', self._security_group_rule.ip_range['cidr'], - self._security_group.id, - ] - verifylist = [ - ('protocol', self._security_group_rule.ip_protocol), - ('src_ip', self._security_group_rule.ip_range['cidr']), - ('group', self._security_group.id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.compute.security_group_rules.create.assert_called_once_with( - self._security_group.id, - self._security_group_rule.ip_protocol, - self._security_group_rule.from_port, - self._security_group_rule.to_port, - self._security_group_rule.ip_range['cidr'], - None, - ) - self.assertEqual(expected_columns, columns) - self.assertEqual(expected_data, data) - - def test_create_remote_ip(self): - expected_columns, expected_data = self._setup_security_group_rule({ - 'ip_protocol': 'icmp', - 'from_port': -1, - 'to_port': -1, - 'ip_range': {'cidr': '10.0.2.0/24'}, - }) - arglist = [ - '--protocol', self._security_group_rule.ip_protocol, - '--remote-ip', self._security_group_rule.ip_range['cidr'], - self._security_group.id, - ] - verifylist = [ - ('protocol', self._security_group_rule.ip_protocol), - ('remote_ip', self._security_group_rule.ip_range['cidr']), - ('group', self._security_group.id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.compute.security_group_rules.create.assert_called_once_with( - self._security_group.id, - self._security_group_rule.ip_protocol, - self._security_group_rule.from_port, - self._security_group_rule.to_port, - self._security_group_rule.ip_range['cidr'], - None, - ) - self.assertEqual(expected_columns, columns) - self.assertEqual(expected_data, data) - - def test_create_proto_option(self): - expected_columns, expected_data = self._setup_security_group_rule({ - 'ip_protocol': 'icmp', - 'from_port': -1, - 'to_port': -1, - 'ip_range': {'cidr': '10.0.2.0/24'}, - }) - arglist = [ - '--proto', self._security_group_rule.ip_protocol, - '--src-ip', self._security_group_rule.ip_range['cidr'], - self._security_group.id, - ] - verifylist = [ - ('proto', self._security_group_rule.ip_protocol), - ('protocol', None), - ('src_ip', self._security_group_rule.ip_range['cidr']), - ('group', self._security_group.id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.compute.security_group_rules.create.assert_called_once_with( - self._security_group.id, - self._security_group_rule.ip_protocol, - self._security_group_rule.from_port, - self._security_group_rule.to_port, - self._security_group_rule.ip_range['cidr'], - None, - ) - self.assertEqual(expected_columns, columns) - self.assertEqual(expected_data, data) - - class TestDeleteSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): # The security group rules to be deleted. @@ -909,83 +622,6 @@ def test_multi_security_group_rules_delete_with_exception(self): ) -class TestDeleteSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): - - # The security group rule to be deleted. - _security_group_rules = \ - compute_fakes.FakeSecurityGroupRule.create_security_group_rules( - count=2) - - def setUp(self): - super(TestDeleteSecurityGroupRuleCompute, self).setUp() - - self.app.client_manager.network_endpoint_enabled = False - - # Get the command object to test - self.cmd = security_group_rule.DeleteSecurityGroupRule(self.app, None) - - def test_security_group_rule_delete(self): - arglist = [ - self._security_group_rules[0].id, - ] - verifylist = [ - ('rule', [self._security_group_rules[0].id]), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - self.compute.security_group_rules.delete.assert_called_once_with( - self._security_group_rules[0].id) - self.assertIsNone(result) - - def test_multi_security_group_rules_delete(self): - arglist = [] - verifylist = [] - - for s in self._security_group_rules: - arglist.append(s.id) - verifylist = [ - ('rule', arglist), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - calls = [] - for s in self._security_group_rules: - calls.append(call(s.id)) - self.compute.security_group_rules.delete.assert_has_calls(calls) - self.assertIsNone(result) - - def test_multi_security_group_rules_delete_with_exception(self): - arglist = [ - self._security_group_rules[0].id, - 'unexist_rule', - ] - verifylist = [ - ('rule', - [self._security_group_rules[0].id, 'unexist_rule']), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - find_mock_result = [None, exceptions.CommandError] - self.compute.security_group_rules.delete = ( - mock.Mock(side_effect=find_mock_result) - ) - - try: - self.cmd.take_action(parsed_args) - self.fail('CommandError should be raised.') - except exceptions.CommandError as e: - self.assertEqual('1 of 2 rules failed to delete.', str(e)) - - self.compute.security_group_rules.delete.assert_any_call( - self._security_group_rules[0].id) - self.compute.security_group_rules.delete.assert_any_call( - 'unexist_rule') - - class TestListSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): # The security group to hold the rules. @@ -1165,131 +801,6 @@ def test_list_with_wrong_egress(self): self.assertEqual(self.expected_data_no_group, list(data)) -class TestListSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): - - # The security group to hold the rules. - _security_group = \ - compute_fakes.FakeSecurityGroup.create_one_security_group() - - # The security group rule to be listed. - _security_group_rule_tcp = \ - compute_fakes.FakeSecurityGroupRule.create_one_security_group_rule({ - 'ip_protocol': 'tcp', - 'from_port': 80, - 'to_port': 80, - 'group': {'name': _security_group.name}, - }) - _security_group_rule_icmp = \ - compute_fakes.FakeSecurityGroupRule.create_one_security_group_rule({ - 'ip_protocol': 'icmp', - 'from_port': -1, - 'to_port': -1, - 'ip_range': {'cidr': '10.0.2.0/24'}, - 'group': {'name': _security_group.name}, - }) - _security_group.rules = [_security_group_rule_tcp._info, - _security_group_rule_icmp._info] - - expected_columns_with_group = ( - 'ID', - 'IP Protocol', - 'IP Range', - 'Port Range', - 'Remote Security Group', - ) - expected_columns_no_group = \ - expected_columns_with_group + ('Security Group',) - - expected_data_with_group = [] - expected_data_no_group = [] - for _security_group_rule in _security_group.rules: - rule = network_utils.transform_compute_security_group_rule( - _security_group_rule - ) - expected_rule_with_group = ( - rule['id'], - rule['ip_protocol'], - rule['ip_range'], - rule['port_range'], - rule['remote_security_group'], - ) - expected_rule_no_group = expected_rule_with_group + \ - (_security_group_rule['parent_group_id'],) - expected_data_with_group.append(expected_rule_with_group) - expected_data_no_group.append(expected_rule_no_group) - - def setUp(self): - super(TestListSecurityGroupRuleCompute, self).setUp() - - self.app.client_manager.network_endpoint_enabled = False - - self.compute.security_groups.get.return_value = \ - self._security_group - self.compute.security_groups.list.return_value = \ - [self._security_group] - - # Get the command object to test - self.cmd = security_group_rule.ListSecurityGroupRule(self.app, None) - - def test_list_default(self): - parsed_args = self.check_parser(self.cmd, [], []) - - columns, data = self.cmd.take_action(parsed_args) - self.compute.security_groups.list.assert_called_once_with( - search_opts={'all_tenants': False} - ) - self.assertEqual(self.expected_columns_no_group, columns) - self.assertEqual(self.expected_data_no_group, list(data)) - - def test_list_with_group(self): - arglist = [ - self._security_group.id, - ] - verifylist = [ - ('group', self._security_group.id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - self.compute.security_groups.get.assert_called_once_with( - self._security_group.id - ) - self.assertEqual(self.expected_columns_with_group, columns) - self.assertEqual(self.expected_data_with_group, list(data)) - - def test_list_all_projects(self): - arglist = [ - '--all-projects', - ] - verifylist = [ - ('all_projects', True), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - self.compute.security_groups.list.assert_called_once_with( - search_opts={'all_tenants': True} - ) - self.assertEqual(self.expected_columns_no_group, columns) - self.assertEqual(self.expected_data_no_group, list(data)) - - def test_list_with_ignored_options(self): - arglist = [ - '--long', - ] - verifylist = [ - ('long', False), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - self.compute.security_groups.list.assert_called_once_with( - search_opts={'all_tenants': False} - ) - self.assertEqual(self.expected_columns_no_group, columns) - self.assertEqual(self.expected_data_no_group, list(data)) - - class TestShowSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): # The security group rule to be shown. @@ -1353,49 +864,3 @@ def test_show_all_options(self): self._security_group_rule.id, ignore_missing=False) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) - - -class TestShowSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): - - # The security group rule to be shown. - _security_group_rule = \ - compute_fakes.FakeSecurityGroupRule.create_one_security_group_rule() - - columns, data = \ - security_group_rule._format_security_group_rule_show( - _security_group_rule._info) - - def setUp(self): - super(TestShowSecurityGroupRuleCompute, self).setUp() - - self.app.client_manager.network_endpoint_enabled = False - - # Build a security group fake customized for this test. - security_group_rules = [self._security_group_rule._info] - security_group = fakes.FakeResource( - info=copy.deepcopy({'rules': security_group_rules}), - loaded=True) - security_group.rules = security_group_rules - self.compute.security_groups.list.return_value = [security_group] - - # Get the command object to test - self.cmd = security_group_rule.ShowSecurityGroupRule(self.app, None) - - def test_show_no_options(self): - self.assertRaises(tests_utils.ParserException, - self.check_parser, self.cmd, [], []) - - def test_show_all_options(self): - arglist = [ - self._security_group_rule.id, - ] - verifylist = [ - ('rule', self._security_group_rule.id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.compute.security_groups.list.assert_called_once_with() - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) From 358175158da2c6bc69e3e5f050d129bc07d38c61 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Fri, 7 Apr 2017 14:32:41 +0000 Subject: [PATCH 1643/3095] Updated from global requirements Change-Id: I8e7362555d330613a87c095b89bb2783df03a6e3 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f9bae0543e..a1bb8b1fc3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ pbr>=2.0.0 # Apache-2.0 six>=1.9.0 # MIT -Babel>=2.3.4 # BSD +Babel!=2.4.0,>=2.3.4 # BSD cliff>=2.3.0 # Apache-2.0 keystoneauth1>=2.18.0 # Apache-2.0 openstacksdk>=0.9.14 # Apache-2.0 From 8549071363805a9eef815dd2429b6b860db11a2c Mon Sep 17 00:00:00 2001 From: David Rabel Date: Mon, 13 Mar 2017 15:16:48 +0100 Subject: [PATCH 1644/3095] Add --network and --port to server create --nic option is quite unhandy. It is better to have two seperate options --network and --port to add a network to a new server. Change-Id: I523abdc83ca2dd4c5dd3871f8f109c2bf57c2e02 Closes-Bug: #1612898 --- doc/source/command-objects/server.rst | 26 +++++++++++ openstackclient/compute/v2/server.py | 40 +++++++++++++++-- .../tests/unit/compute/v2/test_server.py | 44 ++++++++++++++----- .../notes/bug-1612898-bea3b68251d12d81.yaml | 6 +++ 4 files changed, 103 insertions(+), 13 deletions(-) create mode 100644 releasenotes/notes/bug-1612898-bea3b68251d12d81.yaml diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index b2ae965a7b..3d2bbdf34c 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -108,6 +108,8 @@ Create a new server [--availability-zone ] [--block-device-mapping [...] ] [--nic [...] ] + [--network ] + [--port ] [--hint [...] ] [--config-drive |True ] [--min ] @@ -176,6 +178,20 @@ Create a new server Specifying a --nic of auto or none cannot be used with any other --nic value. +.. option:: --network + + Create a NIC on the server and connect it to network. + Specify option multiple times to create multiple NICs. + For more options on NICs see --nic parameter. + network: attach NIC to this network + +.. option:: --port + + Create a NIC on the server and connect it to port. + Specify option multiple times to create multiple NICs. + For more options on NICs see --nic parameter. + port: attach NIC to this port + .. option:: --hint Hints for the scheduler (optional extension) @@ -200,6 +216,16 @@ Create a new server New server name +.. + +The parameters ``--network `` and ``--port `` are actually +wrappers to ``--nic net-id=`` and ``--nic port-id=``. ``--nic`` +also provides additional options to specify an IP address, automatic network +assignment and NICs which are not assigned to any port. This functionality +is not part of ``--network`` and ``--port``, which aim to provide a simple +syntax for the standard use cases of connecting a new server to a given +network or port. + server delete ------------- diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index d33c631a17..c2482bb161 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -114,6 +114,16 @@ def _get_ip_address(addresses, address_type, ip_address_family): ) +def _prefix_checked_value(prefix): + def func(value): + if ',' in value or '=' in value: + msg = _("Invalid argument %s, " + "characters ',' and '=' are not allowed") % value + raise argparse.ArgumentTypeError(msg) + return prefix + value + return func + + def _prep_server_detail(compute_client, image_client, server): """Prepare the detailed server dict for printing @@ -404,7 +414,6 @@ def get_parser(self, prog_name): metavar="", action='append', - default=[], help=_("Create a NIC on the server. " "Specify option multiple times to create multiple NICs. " "Either net-id or port-id must be provided, but not both. " @@ -417,6 +426,28 @@ def get_parser(self, prog_name): "allocate a network. Specifying a --nic of auto or none " "cannot be used with any other --nic value."), ) + parser.add_argument( + '--network', + metavar="", + action='append', + dest='nic', + type=_prefix_checked_value('net-id='), + help=_("Create a NIC on the server and connect it to network. " + "Specify option multiple times to create multiple NICs. " + "For more options on NICs see --nic parameter. " + "network: attach NIC to this network "), + ) + parser.add_argument( + '--port', + metavar="", + action='append', + dest='nic', + type=_prefix_checked_value('port-id='), + help=_("Create a NIC on the server and connect it to port. " + "Specify option multiple times to create multiple NICs. " + "For more options on NICs see --nic parameter. " + "port: attach NIC this port "), + ) parser.add_argument( '--hint', metavar='', @@ -549,6 +580,8 @@ def take_action(self, parsed_args): nics = [] auto_or_none = False + if parsed_args.nic is None: + parsed_args.nic = [] for nic_str in parsed_args.nic: # Handle the special auto/none cases if nic_str in ('auto', 'none'): @@ -564,7 +597,7 @@ def take_action(self, parsed_args): msg = _('Invalid --nic argument %s.') % nic_str raise exceptions.CommandError(msg) if bool(nic_info["net-id"]) == bool(nic_info["port-id"]): - msg = _("either net-id or port-id should be specified " + msg = _("either network or port should be specified " "but not both") raise exceptions.CommandError(msg) if self.app.client_manager.is_network_endpoint_enabled(): @@ -593,7 +626,8 @@ def take_action(self, parsed_args): if auto_or_none: if len(nics) > 1: msg = _('Specifying a --nic of auto or none cannot ' - 'be used with any other --nic value.') + 'be used with any other --nic, --network ' + 'or --port value.') raise exceptions.CommandError(msg) nics = nics[0] else: diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 249902bca4..600b872e54 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -384,14 +384,18 @@ def test_server_create_with_network(self): arglist = [ '--image', 'image1', '--flavor', 'flavor1', - '--nic', 'net-id=net1', - '--nic', 'port-id=port1', + '--network', 'net1', + '--nic', 'net-id=net1,v4-fixed-ip=10.0.0.2', + '--port', 'port1', + '--network', 'net1', + '--nic', 'port-id=port2', self.new_server.name, ] verifylist = [ ('image', 'image1'), ('flavor', 'flavor1'), - ('nic', ['net-id=net1', 'port-id=port1']), + ('nic', ['net-id=net1', 'net-id=net1,v4-fixed-ip=10.0.0.2', + 'port-id=port1', 'net-id=net1', 'port-id=port2']), ('config_drive', False), ('server_name', self.new_server.name), ] @@ -411,20 +415,28 @@ def test_server_create_with_network(self): network_client.find_port = find_port network_resource = mock.Mock() network_resource.id = 'net1_uuid' - port_resource = mock.Mock() - port_resource.id = 'port1_uuid' + port1_resource = mock.Mock() + port1_resource.id = 'port1_uuid' + port2_resource = mock.Mock() + port2_resource.id = 'port2_uuid' find_network.return_value = network_resource - find_port.return_value = port_resource + find_port.side_effect = (lambda port_id, ignore_missing: + {"port1": port1_resource, + "port2": port2_resource}[port_id]) # Mock sdk APIs. _network = mock.Mock() _network.id = 'net1_uuid' - _port = mock.Mock() - _port.id = 'port1_uuid' + _port1 = mock.Mock() + _port1.id = 'port1_uuid' + _port2 = mock.Mock() + _port2.id = 'port2_uuid' find_network = mock.Mock() find_port = mock.Mock() find_network.return_value = _network - find_port.return_value = _port + find_port.side_effect = (lambda port_id, ignore_missing: + {"port1": _port1, + "port2": _port2}[port_id]) self.app.client_manager.network.find_network = find_network self.app.client_manager.network.find_port = find_port @@ -449,10 +461,22 @@ def test_server_create_with_network(self): 'v4-fixed-ip': '', 'v6-fixed-ip': '', 'port-id': ''}, + {'net-id': 'net1_uuid', + 'v4-fixed-ip': '10.0.0.2', + 'v6-fixed-ip': '', + 'port-id': ''}, + {'net-id': '', + 'v4-fixed-ip': '', + 'v6-fixed-ip': '', + 'port-id': 'port1_uuid'}, + {'net-id': 'net1_uuid', + 'v4-fixed-ip': '', + 'v6-fixed-ip': '', + 'port-id': ''}, {'net-id': '', 'v4-fixed-ip': '', 'v6-fixed-ip': '', - 'port-id': 'port1_uuid'}], + 'port-id': 'port2_uuid'}], scheduler_hints={}, config_drive=None, ) diff --git a/releasenotes/notes/bug-1612898-bea3b68251d12d81.yaml b/releasenotes/notes/bug-1612898-bea3b68251d12d81.yaml new file mode 100644 index 0000000000..e31b75d20a --- /dev/null +++ b/releasenotes/notes/bug-1612898-bea3b68251d12d81.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--network`` and ``--port`` options to ``server create`` command + as alternatives to ``--nic`` option. + [Bug `1612898 `_] From 13bba78ca50d7682e98c1c856b07d39fd825528a Mon Sep 17 00:00:00 2001 From: yangweiwei Date: Fri, 7 Apr 2017 22:14:31 +0800 Subject: [PATCH 1645/3095] Update to tox.ini When do the action "tox -e pep8", result is "ValueError: No closing quotation". Which is caused by the tox.ini. The min tox version is 1.6. In my environment, tox version is 2.2.1. If the tox version is more than 2.3, the error will not happen. Depends-on: Iee7b043ac7d381dadf89d26098f69e935ed81d6b Change-Id: Id10ddf6244e7e25e6f66c97773d426b0b4789479 --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index b2660bdba4..d3cafae531 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -minversion = 1.6 +minversion = 2.3 envlist = py35,py27,pep8 skipdist = True From 09286ad8583bb7771b2ca4e9bed23a90056687d6 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Mon, 10 Apr 2017 17:14:46 -0500 Subject: [PATCH 1646/3095] Split floating IP tests Split up the floating IP unit tests between compute and network APIs into separate files in preparation for reworking the compute (nova-net) implementations to deal with the removal of deprecated nova-net support in novaclient 8.0.0. No code changes are intended here, just splitting two files into four. Change-Id: Id62148bb21e913116f9f2084c5761cfa24e8d34c --- .../network/v2/test_floating_ip_compute.py | 265 ++++++++++++++++++ ...ting_ip.py => test_floating_ip_network.py} | 253 +---------------- ...ol.py => test_floating_ip_pool_compute.py} | 34 +-- .../v2/test_floating_ip_pool_network.py | 46 +++ 4 files changed, 316 insertions(+), 282 deletions(-) create mode 100644 openstackclient/tests/unit/network/v2/test_floating_ip_compute.py rename openstackclient/tests/unit/network/v2/{test_floating_ip.py => test_floating_ip_network.py} (76%) rename openstackclient/tests/unit/network/v2/{test_floating_ip_pool.py => test_floating_ip_pool_compute.py} (67%) create mode 100644 openstackclient/tests/unit/network/v2/test_floating_ip_pool_network.py diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip_compute.py b/openstackclient/tests/unit/network/v2/test_floating_ip_compute.py new file mode 100644 index 0000000000..23cd82d213 --- /dev/null +++ b/openstackclient/tests/unit/network/v2/test_floating_ip_compute.py @@ -0,0 +1,265 @@ +# 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 mock import call + +from osc_lib import exceptions + +from openstackclient.network.v2 import floating_ip as fip +from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes +from openstackclient.tests.unit import utils as tests_utils + + +# Tests for Nova network + +class TestFloatingIPCompute(compute_fakes.TestComputev2): + + def setUp(self): + super(TestFloatingIPCompute, self).setUp() + + # Get a shortcut to the compute client + self.compute = self.app.client_manager.compute + + +class TestCreateFloatingIPCompute(TestFloatingIPCompute): + + # The floating ip to be deleted. + floating_ip = compute_fakes.FakeFloatingIP.create_one_floating_ip() + + columns = ( + 'fixed_ip', + 'id', + 'instance_id', + 'ip', + 'pool', + ) + + data = ( + floating_ip.fixed_ip, + floating_ip.id, + floating_ip.instance_id, + floating_ip.ip, + floating_ip.pool, + ) + + def setUp(self): + super(TestCreateFloatingIPCompute, self).setUp() + + self.app.client_manager.network_endpoint_enabled = False + + self.compute.floating_ips.create.return_value = self.floating_ip + + # Get the command object to test + self.cmd = fip.CreateFloatingIP(self.app, None) + + def test_create_no_options(self): + arglist = [] + verifylist = [] + + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_create_default_options(self): + arglist = [ + self.floating_ip.pool, + ] + verifylist = [ + ('network', self.floating_ip.pool), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.compute.floating_ips.create.assert_called_once_with( + self.floating_ip.pool) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestDeleteFloatingIPCompute(TestFloatingIPCompute): + + # The floating ips to be deleted. + floating_ips = compute_fakes.FakeFloatingIP.create_floating_ips(count=2) + + def setUp(self): + super(TestDeleteFloatingIPCompute, self).setUp() + + self.app.client_manager.network_endpoint_enabled = False + + self.compute.floating_ips.delete.return_value = None + + # Return value of utils.find_resource() + self.compute.floating_ips.get = ( + compute_fakes.FakeFloatingIP.get_floating_ips(self.floating_ips)) + + # Get the command object to test + self.cmd = fip.DeleteFloatingIP(self.app, None) + + def test_floating_ip_delete(self): + arglist = [ + self.floating_ips[0].id, + ] + verifylist = [ + ('floating_ip', [self.floating_ips[0].id]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.compute.floating_ips.delete.assert_called_once_with( + self.floating_ips[0].id + ) + self.assertIsNone(result) + + def test_multi_floating_ips_delete(self): + arglist = [] + verifylist = [] + + for f in self.floating_ips: + arglist.append(f.id) + verifylist = [ + ('floating_ip', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [] + for f in self.floating_ips: + calls.append(call(f.id)) + self.compute.floating_ips.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_multi_floating_ips_delete_with_exception(self): + arglist = [ + self.floating_ips[0].id, + 'unexist_floating_ip', + ] + verifylist = [ + ('floating_ip', + [self.floating_ips[0].id, 'unexist_floating_ip']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self.floating_ips[0], exceptions.CommandError] + self.compute.floating_ips.get = ( + mock.Mock(side_effect=find_mock_result) + ) + self.compute.floating_ips.find.side_effect = exceptions.NotFound(None) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 floating_ips failed to delete.', str(e)) + + self.compute.floating_ips.get.assert_any_call( + self.floating_ips[0].id) + self.compute.floating_ips.get.assert_any_call( + 'unexist_floating_ip') + self.compute.floating_ips.delete.assert_called_once_with( + self.floating_ips[0].id + ) + + +class TestListFloatingIPCompute(TestFloatingIPCompute): + + # The floating ips to be list up + floating_ips = compute_fakes.FakeFloatingIP.create_floating_ips(count=3) + + columns = ( + 'ID', + 'Floating IP Address', + 'Fixed IP Address', + 'Server', + 'Pool', + ) + + data = [] + for ip in floating_ips: + data.append(( + ip.id, + ip.ip, + ip.fixed_ip, + ip.instance_id, + ip.pool, + )) + + def setUp(self): + super(TestListFloatingIPCompute, self).setUp() + + self.app.client_manager.network_endpoint_enabled = False + + self.compute.floating_ips.list.return_value = self.floating_ips + + # Get the command object to test + self.cmd = fip.ListFloatingIP(self.app, None) + + def test_floating_ip_list(self): + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.compute.floating_ips.list.assert_called_once_with() + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + +class TestShowFloatingIPCompute(TestFloatingIPCompute): + + # The floating ip to display. + floating_ip = compute_fakes.FakeFloatingIP.create_one_floating_ip() + + columns = ( + 'fixed_ip', + 'id', + 'instance_id', + 'ip', + 'pool', + ) + + data = ( + floating_ip.fixed_ip, + floating_ip.id, + floating_ip.instance_id, + floating_ip.ip, + floating_ip.pool, + ) + + def setUp(self): + super(TestShowFloatingIPCompute, self).setUp() + + self.app.client_manager.network_endpoint_enabled = False + + # Return value of utils.find_resource() + self.compute.floating_ips.get.return_value = self.floating_ip + + # Get the command object to test + self.cmd = fip.ShowFloatingIP(self.app, None) + + def test_floating_ip_show(self): + arglist = [ + self.floating_ip.id, + ] + verifylist = [ + ('floating_ip', self.floating_ip.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip.py b/openstackclient/tests/unit/network/v2/test_floating_ip_network.py similarity index 76% rename from openstackclient/tests/unit/network/v2/test_floating_ip.py rename to openstackclient/tests/unit/network/v2/test_floating_ip_network.py index 69fb14196a..4fbbc8227e 100644 --- a/openstackclient/tests/unit/network/v2/test_floating_ip.py +++ b/openstackclient/tests/unit/network/v2/test_floating_ip_network.py @@ -17,14 +17,13 @@ from osc_lib import exceptions from openstackclient.network.v2 import floating_ip as fip -from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3 from openstackclient.tests.unit.network.v2 import fakes as network_fakes from openstackclient.tests.unit import utils as tests_utils # Tests for Neutron network -# + class TestFloatingIPNetwork(network_fakes.TestNetworkV2): def setUp(self): @@ -612,7 +611,7 @@ def setUp(self): self.cmd = fip.SetFloatingIP(self.app, self.namespace) @mock.patch( - "openstackclient.tests.unit.network.v2.test_floating_ip." + + "openstackclient.tests.unit.network.v2.test_floating_ip_network." + "fip._find_floating_ip" ) def test_port_option(self, find_floating_ip_mock): @@ -645,7 +644,7 @@ def test_port_option(self, find_floating_ip_mock): self.floating_ip, **attrs) @mock.patch( - "openstackclient.tests.unit.network.v2.test_floating_ip." + + "openstackclient.tests.unit.network.v2.test_floating_ip_network." + "fip._find_floating_ip" ) def test_fixed_ip_option(self, find_floating_ip_mock): @@ -702,7 +701,7 @@ def setUp(self): self.cmd = fip.UnsetFloatingIP(self.app, self.namespace) @mock.patch( - "openstackclient.tests.unit.network.v2.test_floating_ip." + + "openstackclient.tests.unit.network.v2.test_floating_ip_network." + "fip._find_floating_ip" ) def test_floating_ip_unset_port(self, find_floating_ip_mock): @@ -733,247 +732,3 @@ def test_floating_ip_unset_port(self, find_floating_ip_mock): self.floating_ip, **attrs) self.assertIsNone(result) - - -# Tests for Nova network -# -class TestFloatingIPCompute(compute_fakes.TestComputev2): - - def setUp(self): - super(TestFloatingIPCompute, self).setUp() - - # Get a shortcut to the compute client - self.compute = self.app.client_manager.compute - - -class TestCreateFloatingIPCompute(TestFloatingIPCompute): - - # The floating ip to be deleted. - floating_ip = compute_fakes.FakeFloatingIP.create_one_floating_ip() - - columns = ( - 'fixed_ip', - 'id', - 'instance_id', - 'ip', - 'pool', - ) - - data = ( - floating_ip.fixed_ip, - floating_ip.id, - floating_ip.instance_id, - floating_ip.ip, - floating_ip.pool, - ) - - def setUp(self): - super(TestCreateFloatingIPCompute, self).setUp() - - self.app.client_manager.network_endpoint_enabled = False - - self.compute.floating_ips.create.return_value = self.floating_ip - - # Get the command object to test - self.cmd = fip.CreateFloatingIP(self.app, None) - - def test_create_no_options(self): - arglist = [] - verifylist = [] - - self.assertRaises(tests_utils.ParserException, self.check_parser, - self.cmd, arglist, verifylist) - - def test_create_default_options(self): - arglist = [ - self.floating_ip.pool, - ] - verifylist = [ - ('network', self.floating_ip.pool), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.compute.floating_ips.create.assert_called_once_with( - self.floating_ip.pool) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) - - -class TestDeleteFloatingIPCompute(TestFloatingIPCompute): - - # The floating ips to be deleted. - floating_ips = compute_fakes.FakeFloatingIP.create_floating_ips(count=2) - - def setUp(self): - super(TestDeleteFloatingIPCompute, self).setUp() - - self.app.client_manager.network_endpoint_enabled = False - - self.compute.floating_ips.delete.return_value = None - - # Return value of utils.find_resource() - self.compute.floating_ips.get = ( - compute_fakes.FakeFloatingIP.get_floating_ips(self.floating_ips)) - - # Get the command object to test - self.cmd = fip.DeleteFloatingIP(self.app, None) - - def test_floating_ip_delete(self): - arglist = [ - self.floating_ips[0].id, - ] - verifylist = [ - ('floating_ip', [self.floating_ips[0].id]), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - self.compute.floating_ips.delete.assert_called_once_with( - self.floating_ips[0].id - ) - self.assertIsNone(result) - - def test_multi_floating_ips_delete(self): - arglist = [] - verifylist = [] - - for f in self.floating_ips: - arglist.append(f.id) - verifylist = [ - ('floating_ip', arglist), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - calls = [] - for f in self.floating_ips: - calls.append(call(f.id)) - self.compute.floating_ips.delete.assert_has_calls(calls) - self.assertIsNone(result) - - def test_multi_floating_ips_delete_with_exception(self): - arglist = [ - self.floating_ips[0].id, - 'unexist_floating_ip', - ] - verifylist = [ - ('floating_ip', - [self.floating_ips[0].id, 'unexist_floating_ip']), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - find_mock_result = [self.floating_ips[0], exceptions.CommandError] - self.compute.floating_ips.get = ( - mock.Mock(side_effect=find_mock_result) - ) - self.compute.floating_ips.find.side_effect = exceptions.NotFound(None) - - try: - self.cmd.take_action(parsed_args) - self.fail('CommandError should be raised.') - except exceptions.CommandError as e: - self.assertEqual('1 of 2 floating_ips failed to delete.', str(e)) - - self.compute.floating_ips.get.assert_any_call( - self.floating_ips[0].id) - self.compute.floating_ips.get.assert_any_call( - 'unexist_floating_ip') - self.compute.floating_ips.delete.assert_called_once_with( - self.floating_ips[0].id - ) - - -class TestListFloatingIPCompute(TestFloatingIPCompute): - - # The floating ips to be list up - floating_ips = compute_fakes.FakeFloatingIP.create_floating_ips(count=3) - - columns = ( - 'ID', - 'Floating IP Address', - 'Fixed IP Address', - 'Server', - 'Pool', - ) - - data = [] - for ip in floating_ips: - data.append(( - ip.id, - ip.ip, - ip.fixed_ip, - ip.instance_id, - ip.pool, - )) - - def setUp(self): - super(TestListFloatingIPCompute, self).setUp() - - self.app.client_manager.network_endpoint_enabled = False - - self.compute.floating_ips.list.return_value = self.floating_ips - - # Get the command object to test - self.cmd = fip.ListFloatingIP(self.app, None) - - def test_floating_ip_list(self): - arglist = [] - verifylist = [] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.compute.floating_ips.list.assert_called_once_with() - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) - - -class TestShowFloatingIPCompute(TestFloatingIPCompute): - - # The floating ip to display. - floating_ip = compute_fakes.FakeFloatingIP.create_one_floating_ip() - - columns = ( - 'fixed_ip', - 'id', - 'instance_id', - 'ip', - 'pool', - ) - - data = ( - floating_ip.fixed_ip, - floating_ip.id, - floating_ip.instance_id, - floating_ip.ip, - floating_ip.pool, - ) - - def setUp(self): - super(TestShowFloatingIPCompute, self).setUp() - - self.app.client_manager.network_endpoint_enabled = False - - # Return value of utils.find_resource() - self.compute.floating_ips.get.return_value = self.floating_ip - - # Get the command object to test - self.cmd = fip.ShowFloatingIP(self.app, None) - - def test_floating_ip_show(self): - arglist = [ - self.floating_ip.id, - ] - verifylist = [ - ('floating_ip', self.floating_ip.id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip_pool.py b/openstackclient/tests/unit/network/v2/test_floating_ip_pool_compute.py similarity index 67% rename from openstackclient/tests/unit/network/v2/test_floating_ip_pool.py rename to openstackclient/tests/unit/network/v2/test_floating_ip_pool_compute.py index 11d01d36e4..8db2143035 100644 --- a/openstackclient/tests/unit/network/v2/test_floating_ip_pool.py +++ b/openstackclient/tests/unit/network/v2/test_floating_ip_pool_compute.py @@ -11,44 +11,12 @@ # under the License. # -from osc_lib import exceptions - from openstackclient.network.v2 import floating_ip_pool from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes -from openstackclient.tests.unit.network.v2 import fakes as network_fakes - - -# Tests for Network API v2 -# -class TestFloatingIPPoolNetwork(network_fakes.TestNetworkV2): - - def setUp(self): - super(TestFloatingIPPoolNetwork, self).setUp() - - # Get a shortcut to the network client - self.network = self.app.client_manager.network - - -class TestListFloatingIPPoolNetwork(TestFloatingIPPoolNetwork): - - def setUp(self): - super(TestListFloatingIPPoolNetwork, self).setUp() - - # Get the command object to test - self.cmd = floating_ip_pool.ListFloatingIPPool(self.app, - self.namespace) - - def test_floating_ip_list(self): - arglist = [] - verifylist = [] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - self.assertRaises(exceptions.CommandError, self.cmd.take_action, - parsed_args) # Tests for Compute network -# + class TestFloatingIPPoolCompute(compute_fakes.TestComputev2): def setUp(self): diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip_pool_network.py b/openstackclient/tests/unit/network/v2/test_floating_ip_pool_network.py new file mode 100644 index 0000000000..95ff5549b0 --- /dev/null +++ b/openstackclient/tests/unit/network/v2/test_floating_ip_pool_network.py @@ -0,0 +1,46 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +from osc_lib import exceptions + +from openstackclient.network.v2 import floating_ip_pool +from openstackclient.tests.unit.network.v2 import fakes as network_fakes + + +# Tests for Network API v2 + +class TestFloatingIPPoolNetwork(network_fakes.TestNetworkV2): + + def setUp(self): + super(TestFloatingIPPoolNetwork, self).setUp() + + # Get a shortcut to the network client + self.network = self.app.client_manager.network + + +class TestListFloatingIPPoolNetwork(TestFloatingIPPoolNetwork): + + def setUp(self): + super(TestListFloatingIPPoolNetwork, self).setUp() + + # Get the command object to test + self.cmd = floating_ip_pool.ListFloatingIPPool(self.app, + self.namespace) + + def test_floating_ip_list(self): + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) From a071ff91c39b7683e15f4e14a1efdd214e16ce4d Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Mon, 10 Apr 2017 19:00:24 +0800 Subject: [PATCH 1647/3095] Add document about renaming OS_ENDPOINT_TYPE to OS_INTERFACE The patch https://review.openstack.org/#/c/198506/ rename option --os-endpoint-type to --os-interface a year ago, but lots of users switch to osc from these project specified clients, like: novaclient and neutronclient, they used OS_ENDPOINT_TYPE and --os-endpoint-type for long time, we should add the notes into openstackclient manpage, let them know the gap exists, and how to make it works. Change-Id: Ic84a60744aa4e519994a18104deae0c2b5b8b0ed Closes-Bug: #1678144 --- doc/source/man/openstack.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/doc/source/man/openstack.rst b/doc/source/man/openstack.rst index 3021ef8756..66a99f3223 100644 --- a/doc/source/man/openstack.rst +++ b/doc/source/man/openstack.rst @@ -167,6 +167,11 @@ OPTIONS Interface type. Valid options are `public`, `admin` and `internal`. +.. NOTE:: + If you switch to openstackclient from project specified clients, like: + novaclient, neutronclient and so on, please use `--os-interface` instead of + `--os-endpoint-type`. + .. option:: --os-profile Performance profiling HMAC key for encrypting context data @@ -462,6 +467,10 @@ The following environment variables can be set to alter the behaviour of :progra Interface type. Valid options are `public`, `admin` and `internal`. +.. NOTE:: + If you switch to openstackclient from project specified clients, like: + novaclient, neutronclient and so on, please use `OS_INTERFACE` instead of + `OS_ENDPOINT_TYPE`. BUGS ==== From 4289ddd47a9c92eb3033eccf39966915caae05db Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 18 Sep 2014 00:55:58 -0500 Subject: [PATCH 1648/3095] Low-level Compute v2 API: security group api.compute.APIv2 starts with security group functions. novaclient 8.0 is now released without support for the previously deprecated nova-net functions, so include a new low-level REST implementation of the removed APIs. Change-Id: Id007535f0598226a8202716232313e37fe6247f9 --- openstackclient/api/compute_v2.py | 211 ++++++++++++++++ openstackclient/compute/client.py | 22 ++ openstackclient/compute/v2/server.py | 16 +- openstackclient/network/v2/security_group.py | 36 ++- .../network/v2/security_group_rule.py | 42 ++-- .../functional/compute/v2/test_server.py | 4 +- .../tests/unit/api/test_compute_v2.py | 228 ++++++++++++++++++ .../tests/unit/compute/v2/fakes.py | 15 +- .../tests/unit/compute/v2/test_server.py | 45 ++-- openstackclient/tests/unit/fakes.py | 3 + .../network/v2/test_security_group_compute.py | 200 +++++++-------- .../v2/test_security_group_rule_compute.py | 172 +++++++------ 12 files changed, 729 insertions(+), 265 deletions(-) create mode 100644 openstackclient/api/compute_v2.py create mode 100644 openstackclient/tests/unit/api/test_compute_v2.py diff --git a/openstackclient/api/compute_v2.py b/openstackclient/api/compute_v2.py new file mode 100644 index 0000000000..3bf3a0d81f --- /dev/null +++ b/openstackclient/api/compute_v2.py @@ -0,0 +1,211 @@ +# 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. +# + +"""Compute v2 API Library""" + +from keystoneauth1 import exceptions as ksa_exceptions +from osc_lib.api import api +from osc_lib import exceptions +from osc_lib.i18n import _ + + +class APIv2(api.BaseAPI): + """Compute v2 API""" + + def __init__(self, **kwargs): + super(APIv2, self).__init__(**kwargs) + + # Overrides + + # TODO(dtroyer): Override find() until these fixes get into an osc-lib + # minimum release + def find( + self, + path, + value=None, + attr=None, + ): + """Find a single resource by name or ID + + :param string path: + The API-specific portion of the URL path + :param string value: + search expression (required, really) + :param string attr: + name of attribute for secondary search + """ + + try: + ret = self._request('GET', "/%s/%s" % (path, value)).json() + if isinstance(ret, dict): + # strip off the enclosing dict + key = list(ret.keys())[0] + ret = ret[key] + except ( + ksa_exceptions.NotFound, + ksa_exceptions.BadRequest, + ): + kwargs = {attr: value} + try: + ret = self.find_one(path, **kwargs) + except ksa_exceptions.NotFound: + msg = _("%s not found") % value + raise exceptions.NotFound(msg) + + return ret + + # Security Groups + + def security_group_create( + self, + name=None, + description=None, + ): + """Create a new security group + + https://developer.openstack.org/api-ref/compute/#create-security-group + + :param string name: + Security group name + :param integer description: + Security group description + """ + + url = "/os-security-groups" + + params = { + 'name': name, + 'description': description, + } + + return self.create( + url, + json={'security_group': params}, + )['security_group'] + + def security_group_delete( + self, + security_group=None, + ): + """Delete a security group + + https://developer.openstack.org/api-ref/compute/#delete-security-group + + :param string security_group: + Security group name or ID + """ + + url = "/os-security-groups" + + security_group = self.find( + url, + attr='name', + value=security_group, + )['id'] + if security_group is not None: + return self.delete('/%s/%s' % (url, security_group)) + + return None + + def security_group_find( + self, + security_group=None, + ): + """Return a security group given name or ID + + https://developer.openstack.org/api-ref/compute/#show-security-group-details + + :param string security_group: + Security group name or ID + :returns: A dict of the security group attributes + """ + + url = "/os-security-groups" + + return self.find( + url, + attr='name', + value=security_group, + ) + + def security_group_list( + self, + limit=None, + marker=None, + search_opts=None, + ): + """Get security groups + + https://developer.openstack.org/api-ref/compute/#list-security-groups + + :param integer limit: + query return count limit + :param string marker: + query marker + :param search_opts: + (undocumented) Search filter dict + all_tenants: True|False - return all projects + :returns: + list of security groups names + """ + + params = {} + if search_opts is not None: + params = dict((k, v) for (k, v) in search_opts.items() if v) + if limit: + params['limit'] = limit + if marker: + params['offset'] = marker + + url = "/os-security-groups" + return self.list(url, **params)["security_groups"] + + def security_group_set( + self, + security_group=None, + # name=None, + # description=None, + **params + ): + """Update a security group + + https://developer.openstack.org/api-ref/compute/#update-security-group + + :param string security_group: + Security group name or ID + + TODO(dtroyer): Create an update method in osc-lib + """ + + # Short-circuit no-op + if params is None: + return None + + url = "/os-security-groups" + + security_group = self.find( + url, + attr='name', + value=security_group, + ) + if security_group is not None: + for (k, v) in params.items(): + # Only set a value if it is already present + if k in security_group: + security_group[k] = v + return self._request( + "PUT", + "/%s/%s" % (url, security_group['id']), + json={'security_group': security_group}, + ).json()['security_group'] + return None diff --git a/openstackclient/compute/client.py b/openstackclient/compute/client.py index b4b463b412..6abfef0425 100644 --- a/openstackclient/compute/client.py +++ b/openstackclient/compute/client.py @@ -31,6 +31,11 @@ "2.1": "novaclient.client", } +COMPUTE_API_TYPE = 'compute' +COMPUTE_API_VERSIONS = { + '2': 'openstackclient.api.compute_v2.APIv2', +} + # Save the microversion if in use _compute_api_version = None @@ -58,6 +63,13 @@ def make_client(instance): LOG.debug('Instantiating compute client for %s', version) + compute_api = utils.get_client_class( + API_NAME, + version.ver_major, + COMPUTE_API_VERSIONS, + ) + LOG.debug('Instantiating compute api: %s', compute_api) + # Set client http_log_debug to True if verbosity level is high enough http_log_debug = utils.get_effective_log_level() <= logging.DEBUG @@ -77,6 +89,16 @@ def make_client(instance): **kwargs ) + client.api = compute_api( + session=instance.session, + service_type=COMPUTE_API_TYPE, + endpoint=instance.get_endpoint_for_service_type( + COMPUTE_API_TYPE, + region_name=instance.region_name, + interface=instance.interface, + ) + ) + return client diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index ae83967767..7cd4588b9a 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -22,6 +22,7 @@ import os import sys +from novaclient.v2 import servers from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -29,11 +30,6 @@ from oslo_utils import timeutils import six -try: - from novaclient.v2 import servers -except ImportError: - from novaclient.v1_1 import servers - from openstackclient.i18n import _ from openstackclient.identity import common as identity_common @@ -316,12 +312,11 @@ def take_action(self, parsed_args): compute_client.servers, parsed_args.server, ) - security_group = utils.find_resource( - compute_client.security_groups, + security_group = compute_client.api.security_group_find( parsed_args.group, ) - server.add_security_group(security_group.id) + server.add_security_group(security_group['id']) class AddServerVolume(command.Command): @@ -1437,12 +1432,11 @@ def take_action(self, parsed_args): compute_client.servers, parsed_args.server, ) - security_group = utils.find_resource( - compute_client.security_groups, + security_group = compute_client.api.security_group_find( parsed_args.group, ) - server.remove_security_group(security_group.id) + server.remove_security_group(security_group['id']) class RemoveServerVolume(command.Command): diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py index 182d481779..75af3587e0 100644 --- a/openstackclient/network/v2/security_group.py +++ b/openstackclient/network/v2/security_group.py @@ -140,13 +140,13 @@ def take_action_network(self, client, parsed_args): def take_action_compute(self, client, parsed_args): description = self._get_description(parsed_args) - obj = client.security_groups.create( + obj = client.api.security_group_create( parsed_args.name, description, ) display_columns, property_columns = _get_columns(obj) data = utils.get_dict_properties( - obj._info, + obj, property_columns, formatters=_formatters_compute ) @@ -174,8 +174,7 @@ def take_action_network(self, client, parsed_args): client.delete_security_group(obj) def take_action_compute(self, client, parsed_args): - data = utils.find_resource(client.security_groups, self.r) - client.security_groups.delete(data.id) + client.api.security_group_delete(self.r) # TODO(rauta): Use the SDK resource mapped attribute names once @@ -242,7 +241,10 @@ def take_action_network(self, client, parsed_args): def take_action_compute(self, client, parsed_args): search = {'all_tenants': parsed_args.all_projects} - data = client.security_groups.list(search_opts=search) + data = client.api.security_group_list( + # TODO(dtroyer): add limit, marker + search_opts=search, + ) columns = ( "ID", @@ -254,7 +256,7 @@ def take_action_compute(self, client, parsed_args): columns = columns + ('Tenant ID',) column_headers = column_headers + ('Project',) return (column_headers, - (utils.get_item_properties( + (utils.get_dict_properties( s, columns, ) for s in data)) @@ -294,23 +296,20 @@ def take_action_network(self, client, parsed_args): client.update_security_group(obj, **attrs) def take_action_compute(self, client, parsed_args): - data = utils.find_resource( - client.security_groups, - parsed_args.group, - ) + data = client.api.security_group_find(parsed_args.group) if parsed_args.name is not None: - data.name = parsed_args.name + data['name'] = parsed_args.name if parsed_args.description is not None: - data.description = parsed_args.description + data['description'] = parsed_args.description # NOTE(rtheis): Previous behavior did not raise a CommandError # if there were no updates. Maintain this behavior and issue # the update. - client.security_groups.update( + client.api.security_group_set( data, - data.name, - data.description, + data['name'], + data['description'], ) @@ -337,13 +336,10 @@ def take_action_network(self, client, parsed_args): return (display_columns, data) def take_action_compute(self, client, parsed_args): - obj = utils.find_resource( - client.security_groups, - parsed_args.group, - ) + obj = client.api.security_group_find(parsed_args.group) display_columns, property_columns = _get_columns(obj) data = utils.get_dict_properties( - obj._info, + obj, property_columns, formatters=_formatters_compute ) diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index 8f07c5a466..33c3ff0211 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -16,11 +16,6 @@ import argparse import logging -try: - from novaclient.v2 import security_group_rules as compute_secgroup_rules -except ImportError: - from novaclient.v1_1 import security_group_rules as compute_secgroup_rules - from osc_lib.cli import parseractions from osc_lib import exceptions from osc_lib import utils @@ -348,10 +343,7 @@ def take_action_network(self, client, parsed_args): return (display_columns, data) def take_action_compute(self, client, parsed_args): - group = utils.find_resource( - client.security_groups, - parsed_args.group, - ) + group = client.api.security_group_find(parsed_args.group) protocol = self._get_protocol(parsed_args) if protocol == 'icmp': from_port, to_port = -1, -1 @@ -363,10 +355,9 @@ def take_action_compute(self, client, parsed_args): remote_ip = None if not (parsed_args.remote_group is None and parsed_args.src_group is None): - parsed_args.remote_group = utils.find_resource( - client.security_groups, + parsed_args.remote_group = client.api.security_group_find( parsed_args.remote_group or parsed_args.src_group, - ).id + )['id'] if parsed_args.src_group: LOG.warning( _("The %(old)s option is deprecated, " @@ -384,8 +375,9 @@ def take_action_compute(self, client, parsed_args): ) else: remote_ip = '0.0.0.0/0' + obj = client.security_group_rules.create( - group.id, + group['id'], protocol, from_port, to_port, @@ -567,27 +559,29 @@ def take_action_compute(self, client, parsed_args): rules_to_list = [] if parsed_args.group is not None: - group = utils.find_resource( - client.security_groups, + group = client.api.security_group_find( parsed_args.group, ) - rules_to_list = group.rules + rules_to_list = group['rules'] else: columns = columns + ('parent_group_id',) search = {'all_tenants': parsed_args.all_projects} - for group in client.security_groups.list(search_opts=search): - rules_to_list.extend(group.rules) + for group in client.api.security_group_list(search_opts=search): + rules_to_list.extend(group['rules']) # NOTE(rtheis): Turn the raw rules into resources. rules = [] for rule in rules_to_list: - rules.append(compute_secgroup_rules.SecurityGroupRule( - client.security_group_rules, + rules.append( network_utils.transform_compute_security_group_rule(rule), - )) + ) + # rules.append(compute_secgroup_rules.SecurityGroupRule( + # client.security_group_rules, + # network_utils.transform_compute_security_group_rule(rule), + # )) return (column_headers, - (utils.get_item_properties( + (utils.get_dict_properties( s, columns, ) for s in rules)) @@ -617,8 +611,8 @@ def take_action_compute(self, client, parsed_args): # the requested rule. obj = None security_group_rules = [] - for security_group in client.security_groups.list(): - security_group_rules.extend(security_group.rules) + for security_group in client.api.security_group_list(): + security_group_rules.extend(security_group['rules']) for security_group_rule in security_group_rules: if parsed_args.rule == str(security_group_rule.get('id')): obj = security_group_rule diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py index f152de80e9..dd257e9a78 100644 --- a/openstackclient/tests/functional/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -371,7 +371,7 @@ def test_server_create_with_none_network(self): server_name = uuid.uuid4().hex server = json.loads(self.openstack( # auto/none enable in nova micro version (v2.37+) - '--os-compute-api-version 2.latest ' + + '--os-compute-api-version 2.37 ' + 'server create -f json ' + '--flavor ' + self.flavor_name + ' ' + '--image ' + self.image_name + ' ' + @@ -394,7 +394,7 @@ def test_server_create_with_empty_network_option_latest(self): try: self.openstack( # auto/none enable in nova micro version (v2.37+) - '--os-compute-api-version 2.latest ' + + '--os-compute-api-version 2.37 ' + 'server create -f json ' + '--flavor ' + self.flavor_name + ' ' + '--image ' + self.image_name + ' ' + diff --git a/openstackclient/tests/unit/api/test_compute_v2.py b/openstackclient/tests/unit/api/test_compute_v2.py new file mode 100644 index 0000000000..f443e810c1 --- /dev/null +++ b/openstackclient/tests/unit/api/test_compute_v2.py @@ -0,0 +1,228 @@ +# 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. +# + +"""Compute v2 API Library Tests""" + +from requests_mock.contrib import fixture + +from keystoneclient import session +from openstackclient.api import compute_v2 as compute +from openstackclient.tests.unit import utils +from osc_lib import exceptions as osc_lib_exceptions + + +FAKE_PROJECT = 'xyzpdq' +FAKE_URL = 'http://gopher.com/v2' + + +class TestComputeAPIv2(utils.TestCase): + + def setUp(self): + super(TestComputeAPIv2, self).setUp() + sess = session.Session() + self.api = compute.APIv2(session=sess, endpoint=FAKE_URL) + self.requests_mock = self.useFixture(fixture.Fixture()) + + +class TestSecurityGroup(TestComputeAPIv2): + + FAKE_SECURITY_GROUP_RESP = { + 'id': '1', + 'name': 'sg1', + 'description': 'test security group', + 'tenant_id': '0123456789', + 'rules': [] + } + FAKE_SECURITY_GROUP_RESP_2 = { + 'id': '2', + 'name': 'sg2', + 'description': 'another test security group', + 'tenant_id': '0123456789', + 'rules': [] + } + LIST_SECURITY_GROUP_RESP = [ + FAKE_SECURITY_GROUP_RESP_2, + FAKE_SECURITY_GROUP_RESP, + ] + + def test_security_group_create_default(self): + self.requests_mock.register_uri( + 'POST', + FAKE_URL + '/os-security-groups', + json={'security_group': self.FAKE_SECURITY_GROUP_RESP}, + status_code=200, + ) + ret = self.api.security_group_create('sg1') + self.assertEqual(self.FAKE_SECURITY_GROUP_RESP, ret) + + def test_security_group_create_options(self): + self.requests_mock.register_uri( + 'POST', + FAKE_URL + '/os-security-groups', + json={'security_group': self.FAKE_SECURITY_GROUP_RESP}, + status_code=200, + ) + ret = self.api.security_group_create( + name='sg1', + description='desc', + ) + self.assertEqual(self.FAKE_SECURITY_GROUP_RESP, ret) + + def test_security_group_delete_id(self): + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/os-security-groups/1', + json={'security_group': self.FAKE_SECURITY_GROUP_RESP}, + status_code=200, + ) + self.requests_mock.register_uri( + 'DELETE', + FAKE_URL + '/os-security-groups/1', + status_code=202, + ) + ret = self.api.security_group_delete('1') + self.assertEqual(202, ret.status_code) + self.assertEqual("", ret.text) + + def test_security_group_delete_name(self): + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/os-security-groups/sg1', + status_code=404, + ) + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/os-security-groups', + json={'security_groups': self.LIST_SECURITY_GROUP_RESP}, + status_code=200, + ) + self.requests_mock.register_uri( + 'DELETE', + FAKE_URL + '/os-security-groups/1', + status_code=202, + ) + ret = self.api.security_group_delete('sg1') + self.assertEqual(202, ret.status_code) + self.assertEqual("", ret.text) + + def test_security_group_delete_not_found(self): + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/os-security-groups/sg3', + status_code=404, + ) + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/os-security-groups', + json={'security_groups': self.LIST_SECURITY_GROUP_RESP}, + status_code=200, + ) + self.assertRaises( + osc_lib_exceptions.NotFound, + self.api.security_group_delete, + 'sg3', + ) + + def test_security_group_find_id(self): + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/os-security-groups/1', + json={'security_group': self.FAKE_SECURITY_GROUP_RESP}, + status_code=200, + ) + ret = self.api.security_group_find('1') + self.assertEqual(self.FAKE_SECURITY_GROUP_RESP, ret) + + def test_security_group_find_name(self): + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/os-security-groups/sg2', + status_code=404, + ) + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/os-security-groups', + json={'security_groups': self.LIST_SECURITY_GROUP_RESP}, + status_code=200, + ) + ret = self.api.security_group_find('sg2') + self.assertEqual(self.FAKE_SECURITY_GROUP_RESP_2, ret) + + def test_security_group_find_not_found(self): + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/os-security-groups/sg3', + status_code=404, + ) + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/os-security-groups', + json={'security_groups': self.LIST_SECURITY_GROUP_RESP}, + status_code=200, + ) + self.assertRaises( + osc_lib_exceptions.NotFound, + self.api.security_group_find, + 'sg3', + ) + + def test_security_group_list_no_options(self): + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/os-security-groups', + json={'security_groups': self.LIST_SECURITY_GROUP_RESP}, + status_code=200, + ) + ret = self.api.security_group_list() + self.assertEqual(self.LIST_SECURITY_GROUP_RESP, ret) + + def test_security_group_set_options_id(self): + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/os-security-groups/1', + json={'security_group': self.FAKE_SECURITY_GROUP_RESP}, + status_code=200, + ) + self.requests_mock.register_uri( + 'PUT', + FAKE_URL + '/os-security-groups/1', + json={'security_group': self.FAKE_SECURITY_GROUP_RESP}, + status_code=200, + ) + ret = self.api.security_group_set( + security_group='1', + description='desc2') + self.assertEqual(self.FAKE_SECURITY_GROUP_RESP, ret) + + def test_security_group_set_options_name(self): + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/os-security-groups/sg2', + status_code=404, + ) + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/os-security-groups', + json={'security_groups': self.LIST_SECURITY_GROUP_RESP}, + status_code=200, + ) + self.requests_mock.register_uri( + 'PUT', + FAKE_URL + '/os-security-groups/2', + json={'security_group': self.FAKE_SECURITY_GROUP_RESP_2}, + status_code=200, + ) + ret = self.api.security_group_set( + security_group='sg2', + description='desc2') + self.assertEqual(self.FAKE_SECURITY_GROUP_RESP_2, ret) diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index 4a19485924..05cb507625 100644 --- a/openstackclient/tests/unit/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -17,6 +17,7 @@ import mock import uuid +from openstackclient.api import compute_v2 from openstackclient.tests.unit import fakes from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes from openstackclient.tests.unit.image.v2 import fakes as image_fakes @@ -180,9 +181,6 @@ def __init__(self, **kwargs): self.hypervisors_stats = mock.Mock() self.hypervisors_stats.resource_class = fakes.FakeResource(None, {}) - self.security_groups = mock.Mock() - self.security_groups.resource_class = fakes.FakeResource(None, {}) - self.security_group_rules = mock.Mock() self.security_group_rules.resource_class = fakes.FakeResource(None, {}) @@ -222,6 +220,11 @@ def setUp(self): token=fakes.AUTH_TOKEN, ) + self.app.client_manager.compute.api = compute_v2.APIv2( + session=self.app.client_manager.session, + endpoint=fakes.AUTH_URL, + ) + self.app.client_manager.identity = identity_fakes.FakeIdentityv2Client( endpoint=fakes.AUTH_URL, token=fakes.AUTH_TOKEN, @@ -485,11 +488,7 @@ def create_one_security_group(attrs=None): # Overwrite default attributes. security_group_attrs.update(attrs) - - security_group = fakes.FakeResource( - info=copy.deepcopy(security_group_attrs), - loaded=True) - return security_group + return security_group_attrs @staticmethod def create_security_groups(attrs=None, count=2): diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index fed847f1da..71288a3172 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -41,11 +41,6 @@ def setUp(self): self.flavors_mock = self.app.client_manager.compute.flavors self.flavors_mock.reset_mock() - # Get a shortcut to the compute client SecurityGroupManager Mock - self.security_groups_mock = \ - self.app.client_manager.compute.security_groups - self.security_groups_mock.reset_mock() - # Get a shortcut to the image client ImageManager Mock self.images_mock = self.app.client_manager.image.images self.images_mock.reset_mock() @@ -232,6 +227,9 @@ def test_server_add_port_no_neutron(self): self.find_port.assert_not_called() +@mock.patch( + 'openstackclient.api.compute_v2.APIv2.security_group_find' +) class TestServerAddSecurityGroup(TestServer): def setUp(self): @@ -239,11 +237,9 @@ def setUp(self): self.security_group = \ compute_fakes.FakeSecurityGroup.create_one_security_group() - # This is the return value for utils.find_resource() for security group - self.security_groups_mock.get.return_value = self.security_group attrs = { - 'security_groups': [{'name': self.security_group.id}] + 'security_groups': [{'name': self.security_group['id']}] } methods = { 'add_security_group': None, @@ -259,23 +255,24 @@ def setUp(self): # Get the command object to test self.cmd = server.AddServerSecurityGroup(self.app, None) - def test_server_add_security_group(self): + def test_server_add_security_group(self, sg_find_mock): + sg_find_mock.return_value = self.security_group arglist = [ self.server.id, - self.security_group.id + self.security_group['id'] ] verifylist = [ ('server', self.server.id), - ('group', self.security_group.id), + ('group', self.security_group['id']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.security_groups_mock.get.assert_called_with( - self.security_group.id, + sg_find_mock.assert_called_with( + self.security_group['id'], ) self.servers_mock.get.assert_called_with(self.server.id) self.server.add_security_group.assert_called_with( - self.security_group.id, + self.security_group['id'], ) self.assertIsNone(result) @@ -1716,6 +1713,9 @@ def test_server_remove_port_no_neutron(self): self.find_port.assert_not_called() +@mock.patch( + 'openstackclient.api.compute_v2.APIv2.security_group_find' +) class TestServerRemoveSecurityGroup(TestServer): def setUp(self): @@ -1723,11 +1723,9 @@ def setUp(self): self.security_group = \ compute_fakes.FakeSecurityGroup.create_one_security_group() - # This is the return value for utils.find_resource() for security group - self.security_groups_mock.get.return_value = self.security_group attrs = { - 'security_groups': [{'name': self.security_group.id}] + 'security_groups': [{'name': self.security_group['id']}] } methods = { 'remove_security_group': None, @@ -1743,23 +1741,24 @@ def setUp(self): # Get the command object to test self.cmd = server.RemoveServerSecurityGroup(self.app, None) - def test_server_remove_security_group(self): + def test_server_remove_security_group(self, sg_find_mock): + sg_find_mock.return_value = self.security_group arglist = [ self.server.id, - self.security_group.id + self.security_group['id'] ] verifylist = [ ('server', self.server.id), - ('group', self.security_group.id), + ('group', self.security_group['id']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.security_groups_mock.get.assert_called_with( - self.security_group.id, + sg_find_mock.assert_called_with( + self.security_group['id'], ) self.servers_mock.get.assert_called_with(self.server.id) self.server.remove_security_group.assert_called_with( - self.security_group.id, + self.security_group['id'], ) self.assertIsNone(result) diff --git a/openstackclient/tests/unit/fakes.py b/openstackclient/tests/unit/fakes.py index f28f9103d9..999694b779 100644 --- a/openstackclient/tests/unit/fakes.py +++ b/openstackclient/tests/unit/fakes.py @@ -228,6 +228,9 @@ def __getitem__(self, item): def get(self, item, default=None): return self._info.get(item, default) + def pop(self, key, default_value=None): + return self.info.pop(key, default_value) + class FakeResponse(requests.Response): diff --git a/openstackclient/tests/unit/network/v2/test_security_group_compute.py b/openstackclient/tests/unit/network/v2/test_security_group_compute.py index 2fd441888f..db9831bb2b 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_compute.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_compute.py @@ -31,10 +31,14 @@ def setUp(self): self.compute = self.app.client_manager.compute +@mock.patch( + 'openstackclient.api.compute_v2.APIv2.security_group_create' +) class TestCreateSecurityGroupCompute(TestSecurityGroupCompute): project = identity_fakes.FakeProject.create_one_project() domain = identity_fakes.FakeDomain.create_one_domain() + # The security group to be shown. _security_group = \ compute_fakes.FakeSecurityGroup.create_one_security_group() @@ -48,10 +52,10 @@ class TestCreateSecurityGroupCompute(TestSecurityGroupCompute): ) data = ( - _security_group.description, - _security_group.id, - _security_group.name, - _security_group.tenant_id, + _security_group['description'], + _security_group['id'], + _security_group['name'], + _security_group['tenant_id'], '', ) @@ -60,61 +64,57 @@ def setUp(self): self.app.client_manager.network_endpoint_enabled = False - self.compute.security_groups.create.return_value = self._security_group - # Get the command object to test self.cmd = security_group.CreateSecurityGroup(self.app, None) - def test_create_no_options(self): + def test_security_group_create_no_options(self, sg_mock): self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, [], []) - def test_create_network_options(self): - arglist = [ - '--project', self.project.name, - '--project-domain', self.domain.name, - self._security_group.name, - ] - self.assertRaises(tests_utils.ParserException, - self.check_parser, self.cmd, arglist, []) - - def test_create_min_options(self): + def test_security_group_create_min_options(self, sg_mock): + sg_mock.return_value = self._security_group arglist = [ - self._security_group.name, + self._security_group['name'], ] verifylist = [ - ('name', self._security_group.name), + ('name', self._security_group['name']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.compute.security_groups.create.assert_called_once_with( - self._security_group.name, - self._security_group.name) + sg_mock.assert_called_once_with( + self._security_group['name'], + self._security_group['name'], + ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) - def test_create_all_options(self): + def test_security_group_create_all_options(self, sg_mock): + sg_mock.return_value = self._security_group arglist = [ - '--description', self._security_group.description, - self._security_group.name, + '--description', self._security_group['description'], + self._security_group['name'], ] verifylist = [ - ('description', self._security_group.description), - ('name', self._security_group.name), + ('description', self._security_group['description']), + ('name', self._security_group['name']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.compute.security_groups.create.assert_called_once_with( - self._security_group.name, - self._security_group.description) + sg_mock.assert_called_once_with( + self._security_group['name'], + self._security_group['description'], + ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) +@mock.patch( + 'openstackclient.api.compute_v2.APIv2.security_group_delete' +) class TestDeleteSecurityGroupCompute(TestSecurityGroupCompute): # The security groups to be deleted. @@ -126,9 +126,7 @@ def setUp(self): self.app.client_manager.network_endpoint_enabled = False - self.compute.security_groups.delete = mock.Mock(return_value=None) - - self.compute.security_groups.get = ( + self.compute.api.security_group_find = ( compute_fakes.FakeSecurityGroup.get_security_groups( self._security_groups) ) @@ -136,27 +134,30 @@ def setUp(self): # Get the command object to test self.cmd = security_group.DeleteSecurityGroup(self.app, None) - def test_security_group_delete(self): + def test_security_group_delete(self, sg_mock): + sg_mock.return_value = mock.Mock(return_value=None) arglist = [ - self._security_groups[0].id, + self._security_groups[0]['id'], ] verifylist = [ - ('group', [self._security_groups[0].id]), + ('group', [self._security_groups[0]['id']]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.compute.security_groups.delete.assert_called_once_with( - self._security_groups[0].id) + sg_mock.assert_called_once_with( + self._security_groups[0]['id'], + ) self.assertIsNone(result) - def test_multi_security_groups_delete(self): + def test_security_group_multi_delete(self, sg_mock): + sg_mock.return_value = mock.Mock(return_value=None) arglist = [] verifylist = [] for s in self._security_groups: - arglist.append(s.id) + arglist.append(s['id']) verifylist = [ ('group', arglist), ] @@ -166,43 +167,39 @@ def test_multi_security_groups_delete(self): calls = [] for s in self._security_groups: - calls.append(call(s.id)) - self.compute.security_groups.delete.assert_has_calls(calls) + calls.append(call(s['id'])) + sg_mock.assert_has_calls(calls) self.assertIsNone(result) - def test_multi_security_groups_delete_with_exception(self): + def test_security_group_multi_delete_with_exception(self, sg_mock): + sg_mock.return_value = mock.Mock(return_value=None) + sg_mock.side_effect = ([ + mock.Mock(return_value=None), + exceptions.CommandError, + ]) arglist = [ - self._security_groups[0].id, + self._security_groups[0]['id'], 'unexist_security_group', ] verifylist = [ ('group', - [self._security_groups[0].id, 'unexist_security_group']), + [self._security_groups[0]['id'], 'unexist_security_group']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - find_mock_result = [self._security_groups[0], exceptions.CommandError] - self.compute.security_groups.get = ( - mock.Mock(side_effect=find_mock_result) - ) - self.compute.security_groups.find.side_effect = ( - exceptions.NotFound(None)) - try: self.cmd.take_action(parsed_args) self.fail('CommandError should be raised.') except exceptions.CommandError as e: self.assertEqual('1 of 2 groups failed to delete.', str(e)) - self.compute.security_groups.get.assert_any_call( - self._security_groups[0].id) - self.compute.security_groups.get.assert_any_call( - 'unexist_security_group') - self.compute.security_groups.delete.assert_called_once_with( - self._security_groups[0].id - ) + sg_mock.assert_any_call(self._security_groups[0]['id']) + sg_mock.assert_any_call('unexist_security_group') +@mock.patch( + 'openstackclient.api.compute_v2.APIv2.security_group_list' +) class TestListSecurityGroupCompute(TestSecurityGroupCompute): # The security group to be listed. @@ -224,29 +221,29 @@ class TestListSecurityGroupCompute(TestSecurityGroupCompute): data = [] for grp in _security_groups: data.append(( - grp.id, - grp.name, - grp.description, + grp['id'], + grp['name'], + grp['description'], )) data_all_projects = [] for grp in _security_groups: data_all_projects.append(( - grp.id, - grp.name, - grp.description, - grp.tenant_id, + grp['id'], + grp['name'], + grp['description'], + grp['tenant_id'], )) def setUp(self): super(TestListSecurityGroupCompute, self).setUp() self.app.client_manager.network_endpoint_enabled = False - self.compute.security_groups.list.return_value = self._security_groups # Get the command object to test self.cmd = security_group.ListSecurityGroup(self.app, None) - def test_security_group_list_no_options(self): + def test_security_group_list_no_options(self, sg_mock): + sg_mock.return_value = self._security_groups arglist = [] verifylist = [ ('all_projects', False), @@ -256,11 +253,12 @@ def test_security_group_list_no_options(self): columns, data = self.cmd.take_action(parsed_args) kwargs = {'search_opts': {'all_tenants': False}} - self.compute.security_groups.list.assert_called_once_with(**kwargs) + sg_mock.assert_called_once_with(**kwargs) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) - def test_security_group_list_all_projects(self): + def test_security_group_list_all_projects(self, sg_mock): + sg_mock.return_value = self._security_groups arglist = [ '--all-projects', ] @@ -272,11 +270,14 @@ def test_security_group_list_all_projects(self): columns, data = self.cmd.take_action(parsed_args) kwargs = {'search_opts': {'all_tenants': True}} - self.compute.security_groups.list.assert_called_once_with(**kwargs) + sg_mock.assert_called_once_with(**kwargs) self.assertEqual(self.columns_all_projects, columns) self.assertEqual(self.data_all_projects, list(data)) +@mock.patch( + 'openstackclient.api.compute_v2.APIv2.security_group_set' +) class TestSetSecurityGroupCompute(TestSecurityGroupCompute): # The security group to be set. @@ -288,54 +289,54 @@ def setUp(self): self.app.client_manager.network_endpoint_enabled = False - self.compute.security_groups.update = mock.Mock(return_value=None) - - self.compute.security_groups.get = mock.Mock( + self.compute.api.security_group_find = mock.Mock( return_value=self._security_group) # Get the command object to test self.cmd = security_group.SetSecurityGroup(self.app, None) - def test_set_no_options(self): + def test_security_group_set_no_options(self, sg_mock): self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, [], []) - def test_set_no_updates(self): + def test_security_group_set_no_updates(self, sg_mock): + sg_mock.return_value = mock.Mock(return_value=None) arglist = [ - self._security_group.name, + self._security_group['name'], ] verifylist = [ - ('group', self._security_group.name), + ('group', self._security_group['name']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.compute.security_groups.update.assert_called_once_with( + sg_mock.assert_called_once_with( self._security_group, - self._security_group.name, - self._security_group.description + self._security_group['name'], + self._security_group['description'], ) self.assertIsNone(result) - def test_set_all_options(self): - new_name = 'new-' + self._security_group.name - new_description = 'new-' + self._security_group.description + def test_security_group_set_all_options(self, sg_mock): + sg_mock.return_value = mock.Mock(return_value=None) + new_name = 'new-' + self._security_group['name'] + new_description = 'new-' + self._security_group['description'] arglist = [ '--name', new_name, '--description', new_description, - self._security_group.name, + self._security_group['name'], ] verifylist = [ ('description', new_description), - ('group', self._security_group.name), + ('group', self._security_group['name']), ('name', new_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.compute.security_groups.update.assert_called_once_with( + sg_mock.assert_called_once_with( self._security_group, new_name, new_description @@ -343,6 +344,9 @@ def test_set_all_options(self): self.assertIsNone(result) +@mock.patch( + 'openstackclient.api.compute_v2.APIv2.security_group_find' +) class TestShowSecurityGroupCompute(TestSecurityGroupCompute): # The security group rule to be shown with the group. @@ -364,10 +368,10 @@ class TestShowSecurityGroupCompute(TestSecurityGroupCompute): ) data = ( - _security_group.description, - _security_group.id, - _security_group.name, - _security_group.tenant_id, + _security_group['description'], + _security_group['id'], + _security_group['name'], + _security_group['tenant_id'], security_group._format_compute_security_group_rules( [_security_group_rule._info]), ) @@ -377,27 +381,25 @@ def setUp(self): self.app.client_manager.network_endpoint_enabled = False - self.compute.security_groups.get.return_value = self._security_group - # Get the command object to test self.cmd = security_group.ShowSecurityGroup(self.app, None) - def test_show_no_options(self): + def test_security_group_show_no_options(self, sg_mock): self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, [], []) - def test_show_all_options(self): + def test_security_group_show_all_options(self, sg_mock): + sg_mock.return_value = self._security_group arglist = [ - self._security_group.id, + self._security_group['id'], ] verifylist = [ - ('group', self._security_group.id), + ('group', self._security_group['id']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.compute.security_groups.get.assert_called_once_with( - self._security_group.id) + sg_mock.assert_called_once_with(self._security_group['id']) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) diff --git a/openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py b/openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py index 7833c0d932..06a7a25d45 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py @@ -11,7 +11,6 @@ # under the License. # -import copy import mock from mock import call @@ -20,7 +19,6 @@ from openstackclient.network import utils as network_utils from openstackclient.network.v2 import security_group_rule from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes -from openstackclient.tests.unit import fakes from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes from openstackclient.tests.unit import utils as tests_utils @@ -38,6 +36,7 @@ class TestCreateSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): project = identity_fakes.FakeProject.create_one_project() domain = identity_fakes.FakeDomain.create_one_domain() + # The security group rule to be created. _security_group_rule = None @@ -61,51 +60,53 @@ def setUp(self): self.app.client_manager.network_endpoint_enabled = False - self.compute.security_groups.get.return_value = self._security_group + self.compute.api.security_group_find = mock.Mock( + return_value=self._security_group, + ) # Get the command object to test self.cmd = security_group_rule.CreateSecurityGroupRule(self.app, None) - def test_create_no_options(self): + def test_security_group_rule_create_no_options(self): self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, [], []) - def test_create_all_source_options(self): + def test_security_group_rule_create_all_source_options(self): arglist = [ '--src-ip', '10.10.0.0/24', - '--src-group', self._security_group.id, - self._security_group.id, + '--src-group', self._security_group['id'], + self._security_group['id'], ] self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, []) - def test_create_all_remote_options(self): + def test_security_group_rule_create_all_remote_options(self): arglist = [ '--remote-ip', '10.10.0.0/24', - '--remote-group', self._security_group.id, - self._security_group.id, + '--remote-group', self._security_group['id'], + self._security_group['id'], ] self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, []) - def test_create_bad_protocol(self): + def test_security_group_rule_create_bad_protocol(self): arglist = [ '--protocol', 'foo', - self._security_group.id, + self._security_group['id'], ] self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, []) - def test_create_all_protocol_options(self): + def test_security_group_rule_create_all_protocol_options(self): arglist = [ '--protocol', 'tcp', '--proto', 'tcp', - self._security_group.id, + self._security_group['id'], ] self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, []) - def test_create_network_options(self): + def test_security_group_rule_create_network_options(self): arglist = [ '--ingress', '--ethertype', 'IPv4', @@ -113,30 +114,32 @@ def test_create_network_options(self): '--icmp-code', '11', '--project', self.project.name, '--project-domain', self.domain.name, - self._security_group.id, + self._security_group['id'], ] self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, []) - def test_create_default_rule(self): + def test_security_group_rule_create_default_rule(self): expected_columns, expected_data = self._setup_security_group_rule() dst_port = str(self._security_group_rule.from_port) + ':' + \ str(self._security_group_rule.to_port) arglist = [ '--dst-port', dst_port, - self._security_group.id, + self._security_group['id'], ] verifylist = [ ('dst_port', (self._security_group_rule.from_port, self._security_group_rule.to_port)), - ('group', self._security_group.id), + ('group', self._security_group['id']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) + # TODO(dtroyer): save this for the security group rule changes + # self.compute.api.security_group_rule_create.assert_called_once_with( self.compute.security_group_rules.create.assert_called_once_with( - self._security_group.id, + self._security_group['id'], self._security_group_rule.ip_protocol, self._security_group_rule.from_port, self._security_group_rule.to_port, @@ -146,71 +149,75 @@ def test_create_default_rule(self): self.assertEqual(expected_columns, columns) self.assertEqual(expected_data, data) - def test_create_source_group(self): + def test_security_group_rule_create_source_group(self): expected_columns, expected_data = self._setup_security_group_rule({ 'from_port': 22, 'to_port': 22, - 'group': {'name': self._security_group.name}, + 'group': {'name': self._security_group['name']}, }) arglist = [ '--dst-port', str(self._security_group_rule.from_port), - '--src-group', self._security_group.name, - self._security_group.id, + '--src-group', self._security_group['name'], + self._security_group['id'], ] verifylist = [ ('dst_port', (self._security_group_rule.from_port, self._security_group_rule.to_port)), - ('src_group', self._security_group.name), - ('group', self._security_group.id), + ('src_group', self._security_group['name']), + ('group', self._security_group['id']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) + # TODO(dtroyer): save this for the security group rule changes + # self.compute.api.security_group_rule_create.assert_called_once_with( self.compute.security_group_rules.create.assert_called_once_with( - self._security_group.id, + self._security_group['id'], self._security_group_rule.ip_protocol, self._security_group_rule.from_port, self._security_group_rule.to_port, self._security_group_rule.ip_range['cidr'], - self._security_group.id, + self._security_group['id'], ) self.assertEqual(expected_columns, columns) self.assertEqual(expected_data, data) - def test_create_remote_group(self): + def test_security_group_rule_create_remote_group(self): expected_columns, expected_data = self._setup_security_group_rule({ 'from_port': 22, 'to_port': 22, - 'group': {'name': self._security_group.name}, + 'group': {'name': self._security_group['name']}, }) arglist = [ '--dst-port', str(self._security_group_rule.from_port), - '--remote-group', self._security_group.name, - self._security_group.id, + '--remote-group', self._security_group['name'], + self._security_group['id'], ] verifylist = [ ('dst_port', (self._security_group_rule.from_port, self._security_group_rule.to_port)), - ('remote_group', self._security_group.name), - ('group', self._security_group.id), + ('remote_group', self._security_group['name']), + ('group', self._security_group['id']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) + # TODO(dtroyer): save this for the security group rule changes + # self.compute.api.security_group_rule_create.assert_called_once_with( self.compute.security_group_rules.create.assert_called_once_with( - self._security_group.id, + self._security_group['id'], self._security_group_rule.ip_protocol, self._security_group_rule.from_port, self._security_group_rule.to_port, self._security_group_rule.ip_range['cidr'], - self._security_group.id, + self._security_group['id'], ) self.assertEqual(expected_columns, columns) self.assertEqual(expected_data, data) - def test_create_source_ip(self): + def test_security_group_rule_create_source_ip(self): expected_columns, expected_data = self._setup_security_group_rule({ 'ip_protocol': 'icmp', 'from_port': -1, @@ -220,19 +227,21 @@ def test_create_source_ip(self): arglist = [ '--protocol', self._security_group_rule.ip_protocol, '--src-ip', self._security_group_rule.ip_range['cidr'], - self._security_group.id, + self._security_group['id'], ] verifylist = [ ('protocol', self._security_group_rule.ip_protocol), ('src_ip', self._security_group_rule.ip_range['cidr']), - ('group', self._security_group.id), + ('group', self._security_group['id']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) + # TODO(dtroyer): save this for the security group rule changes + # self.compute.api.security_group_rule_create.assert_called_once_with( self.compute.security_group_rules.create.assert_called_once_with( - self._security_group.id, + self._security_group['id'], self._security_group_rule.ip_protocol, self._security_group_rule.from_port, self._security_group_rule.to_port, @@ -242,7 +251,7 @@ def test_create_source_ip(self): self.assertEqual(expected_columns, columns) self.assertEqual(expected_data, data) - def test_create_remote_ip(self): + def test_security_group_rule_create_remote_ip(self): expected_columns, expected_data = self._setup_security_group_rule({ 'ip_protocol': 'icmp', 'from_port': -1, @@ -252,19 +261,21 @@ def test_create_remote_ip(self): arglist = [ '--protocol', self._security_group_rule.ip_protocol, '--remote-ip', self._security_group_rule.ip_range['cidr'], - self._security_group.id, + self._security_group['id'], ] verifylist = [ ('protocol', self._security_group_rule.ip_protocol), ('remote_ip', self._security_group_rule.ip_range['cidr']), - ('group', self._security_group.id), + ('group', self._security_group['id']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) + # TODO(dtroyer): save this for the security group rule changes + # self.compute.api.security_group_rule_create.assert_called_once_with( self.compute.security_group_rules.create.assert_called_once_with( - self._security_group.id, + self._security_group['id'], self._security_group_rule.ip_protocol, self._security_group_rule.from_port, self._security_group_rule.to_port, @@ -274,7 +285,7 @@ def test_create_remote_ip(self): self.assertEqual(expected_columns, columns) self.assertEqual(expected_data, data) - def test_create_proto_option(self): + def test_security_group_rule_create_proto_option(self): expected_columns, expected_data = self._setup_security_group_rule({ 'ip_protocol': 'icmp', 'from_port': -1, @@ -284,20 +295,22 @@ def test_create_proto_option(self): arglist = [ '--proto', self._security_group_rule.ip_protocol, '--src-ip', self._security_group_rule.ip_range['cidr'], - self._security_group.id, + self._security_group['id'], ] verifylist = [ ('proto', self._security_group_rule.ip_protocol), ('protocol', None), ('src_ip', self._security_group_rule.ip_range['cidr']), - ('group', self._security_group.id), + ('group', self._security_group['id']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) + # TODO(dtroyer): save this for the security group rule changes + # self.compute.api.security_group_rule_create.assert_called_once_with( self.compute.security_group_rules.create.assert_called_once_with( - self._security_group.id, + self._security_group['id'], self._security_group_rule.ip_protocol, self._security_group_rule.from_port, self._security_group_rule.to_port, @@ -338,7 +351,7 @@ def test_security_group_rule_delete(self): self._security_group_rules[0].id) self.assertIsNone(result) - def test_multi_security_group_rules_delete(self): + def test_security_group_rule_multi_delete(self): arglist = [] verifylist = [] @@ -357,7 +370,7 @@ def test_multi_security_group_rules_delete(self): self.compute.security_group_rules.delete.assert_has_calls(calls) self.assertIsNone(result) - def test_multi_security_group_rules_delete_with_exception(self): + def test_security_group_rule_multi_delete_with_exception(self): arglist = [ self._security_group_rules[0].id, 'unexist_rule', @@ -397,7 +410,7 @@ class TestListSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): 'ip_protocol': 'tcp', 'from_port': 80, 'to_port': 80, - 'group': {'name': _security_group.name}, + 'group': {'name': _security_group['name']}, }) _security_group_rule_icmp = \ compute_fakes.FakeSecurityGroupRule.create_one_security_group_rule({ @@ -405,10 +418,12 @@ class TestListSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): 'from_port': -1, 'to_port': -1, 'ip_range': {'cidr': '10.0.2.0/24'}, - 'group': {'name': _security_group.name}, + 'group': {'name': _security_group['name']}, }) - _security_group.rules = [_security_group_rule_tcp._info, - _security_group_rule_icmp._info] + _security_group['rules'] = [ + _security_group_rule_tcp._info, + _security_group_rule_icmp._info, + ] expected_columns_with_group = ( 'ID', @@ -422,7 +437,7 @@ class TestListSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): expected_data_with_group = [] expected_data_no_group = [] - for _security_group_rule in _security_group.rules: + for _security_group_rule in _security_group['rules']: rule = network_utils.transform_compute_security_group_rule( _security_group_rule ) @@ -443,41 +458,43 @@ def setUp(self): self.app.client_manager.network_endpoint_enabled = False - self.compute.security_groups.get.return_value = \ - self._security_group - self.compute.security_groups.list.return_value = \ - [self._security_group] + self.compute.api.security_group_find = mock.Mock( + return_value=self._security_group, + ) + self.compute.api.security_group_list = mock.Mock( + return_value=[self._security_group], + ) # Get the command object to test self.cmd = security_group_rule.ListSecurityGroupRule(self.app, None) - def test_list_default(self): + def test_security_group_rule_list_default(self): parsed_args = self.check_parser(self.cmd, [], []) columns, data = self.cmd.take_action(parsed_args) - self.compute.security_groups.list.assert_called_once_with( + self.compute.api.security_group_list.assert_called_once_with( search_opts={'all_tenants': False} ) self.assertEqual(self.expected_columns_no_group, columns) self.assertEqual(self.expected_data_no_group, list(data)) - def test_list_with_group(self): + def test_security_group_rule_list_with_group(self): arglist = [ - self._security_group.id, + self._security_group['id'], ] verifylist = [ - ('group', self._security_group.id), + ('group', self._security_group['id']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.compute.security_groups.get.assert_called_once_with( - self._security_group.id + self.compute.api.security_group_find.assert_called_once_with( + self._security_group['id'] ) self.assertEqual(self.expected_columns_with_group, columns) self.assertEqual(self.expected_data_with_group, list(data)) - def test_list_all_projects(self): + def test_security_group_rule_list_all_projects(self): arglist = [ '--all-projects', ] @@ -487,13 +504,13 @@ def test_list_all_projects(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.compute.security_groups.list.assert_called_once_with( + self.compute.api.security_group_list.assert_called_once_with( search_opts={'all_tenants': True} ) self.assertEqual(self.expected_columns_no_group, columns) self.assertEqual(self.expected_data_no_group, list(data)) - def test_list_with_ignored_options(self): + def test_security_group_rule_list_with_ignored_options(self): arglist = [ '--long', ] @@ -503,7 +520,7 @@ def test_list_with_ignored_options(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.compute.security_groups.list.assert_called_once_with( + self.compute.api.security_group_list.assert_called_once_with( search_opts={'all_tenants': False} ) self.assertEqual(self.expected_columns_no_group, columns) @@ -527,20 +544,19 @@ def setUp(self): # Build a security group fake customized for this test. security_group_rules = [self._security_group_rule._info] - security_group = fakes.FakeResource( - info=copy.deepcopy({'rules': security_group_rules}), - loaded=True) - security_group.rules = security_group_rules - self.compute.security_groups.list.return_value = [security_group] + security_group = {'rules': security_group_rules} + self.compute.api.security_group_list = mock.Mock( + return_value=[security_group], + ) # Get the command object to test self.cmd = security_group_rule.ShowSecurityGroupRule(self.app, None) - def test_show_no_options(self): + def test_security_group_rule_show_no_options(self): self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, [], []) - def test_show_all_options(self): + def test_security_group_rule_show_all_options(self): arglist = [ self._security_group_rule.id, ] @@ -551,6 +567,6 @@ def test_show_all_options(self): columns, data = self.cmd.take_action(parsed_args) - self.compute.security_groups.list.assert_called_once_with() + self.compute.api.security_group_list.assert_called_once_with() self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) From 1bf6706ad1628dcf18a515f13a7b4ba01a38b758 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 7 Apr 2017 20:59:58 -0500 Subject: [PATCH 1649/3095] Low-level Compute v2 API: security group rules api.compute.APIv2 security group rule functions. novaclient 8.0 is now released without support for the previously deprecated nova-net functions, so include a new low-level REST implementation of the removed APIs. Change-Id: Ieabd61113bc6d3562738686f52bb06aa84fca765 --- openstackclient/api/compute_v2.py | 97 ++++++++ .../network/v2/security_group_rule.py | 18 +- .../tests/unit/api/test_compute_v2.py | 81 +++++++ .../tests/unit/compute/v2/fakes.py | 5 +- .../network/v2/test_security_group_compute.py | 4 +- .../v2/test_security_group_rule_compute.py | 214 +++++++++--------- 6 files changed, 301 insertions(+), 118 deletions(-) diff --git a/openstackclient/api/compute_v2.py b/openstackclient/api/compute_v2.py index 3bf3a0d81f..065121fc68 100644 --- a/openstackclient/api/compute_v2.py +++ b/openstackclient/api/compute_v2.py @@ -19,6 +19,12 @@ from osc_lib.i18n import _ +# TODO(dtroyer): Mingrate this to osc-lib +class InvalidValue(Exception): + """An argument value is not valid: wrong type, out of range, etc""" + message = "Supplied value is not valid" + + class APIv2(api.BaseAPI): """Compute v2 API""" @@ -27,6 +33,29 @@ def __init__(self, **kwargs): # Overrides + def _check_integer(self, value, msg=None): + """Attempt to convert value to an integer + + Raises InvalidValue on failure + + :param value: + Convert this to an integer. None is converted to 0 (zero). + :param msg: + An alternate message for the exception, must include exactly + one substitution to receive the attempted value. + """ + + if value is None: + return 0 + + try: + value = int(value) + except (TypeError, ValueError): + if not msg: + msg = "%s is not an integer" % value + raise InvalidValue(msg) + return value + # TODO(dtroyer): Override find() until these fixes get into an osc-lib # minimum release def find( @@ -209,3 +238,71 @@ def security_group_set( json={'security_group': security_group}, ).json()['security_group'] return None + + # Security Group Rules + + def security_group_rule_create( + self, + security_group_id=None, + ip_protocol=None, + from_port=None, + to_port=None, + remote_ip=None, + remote_group=None, + ): + """Create a new security group rule + + https://developer.openstack.org/api-ref/compute/#create-security-group-rule + + :param string security_group_id: + Security group ID + :param ip_protocol: + IP protocol, 'tcp', 'udp' or 'icmp' + :param from_port: + Source port + :param to_port: + Destination port + :param remote_ip: + Source IP address in CIDR notation + :param remote_group: + Remote security group + """ + + url = "/os-security-group-rules" + + if ip_protocol.lower() not in ['icmp', 'tcp', 'udp']: + raise InvalidValue( + "%(s) is not one of 'icmp', 'tcp', or 'udp'" % ip_protocol + ) + + params = { + 'parent_group_id': security_group_id, + 'ip_protocol': ip_protocol, + 'from_port': self._check_integer(from_port), + 'to_port': self._check_integer(to_port), + 'cidr': remote_ip, + 'group_id': remote_group, + } + + return self.create( + url, + json={'security_group_rule': params}, + )['security_group_rule'] + + def security_group_rule_delete( + self, + security_group_rule_id=None, + ): + """Delete a security group rule + + https://developer.openstack.org/api-ref/compute/#delete-security-group-rule + + :param string security_group_rule_id: + Security group rule ID + """ + + url = "/os-security-group-rules" + if security_group_rule_id is not None: + return self.delete('/%s/%s' % (url, security_group_rule_id)) + + return None diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index 33c3ff0211..ad685cf8ae 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -376,15 +376,15 @@ def take_action_compute(self, client, parsed_args): else: remote_ip = '0.0.0.0/0' - obj = client.security_group_rules.create( - group['id'], - protocol, - from_port, - to_port, - remote_ip, - parsed_args.remote_group, + obj = client.api.security_group_rule_create( + security_group_id=group['id'], + ip_protocol=protocol, + from_port=from_port, + to_port=to_port, + remote_ip=remote_ip, + remote_group=parsed_args.remote_group, ) - return _format_security_group_rule_show(obj._info) + return _format_security_group_rule_show(obj) class DeleteSecurityGroupRule(common.NetworkAndComputeDelete): @@ -409,7 +409,7 @@ def take_action_network(self, client, parsed_args): client.delete_security_group_rule(obj) def take_action_compute(self, client, parsed_args): - client.security_group_rules.delete(self.r) + client.api.security_group_rule_delete(self.r) class ListSecurityGroupRule(common.NetworkAndComputeLister): diff --git a/openstackclient/tests/unit/api/test_compute_v2.py b/openstackclient/tests/unit/api/test_compute_v2.py index f443e810c1..949205fd8b 100644 --- a/openstackclient/tests/unit/api/test_compute_v2.py +++ b/openstackclient/tests/unit/api/test_compute_v2.py @@ -226,3 +226,84 @@ def test_security_group_set_options_name(self): security_group='sg2', description='desc2') self.assertEqual(self.FAKE_SECURITY_GROUP_RESP_2, ret) + + +class TestSecurityGroupRule(TestComputeAPIv2): + + FAKE_SECURITY_GROUP_RULE_RESP = { + 'id': '1', + 'name': 'sgr1', + 'tenant_id': 'proj-1', + 'ip_protocol': 'TCP', + 'from_port': 1, + 'to_port': 22, + 'group': {}, + # 'ip_range': , + # 'cidr': , + # 'parent_group_id': , + } + + def test_security_group_create_no_options(self): + self.requests_mock.register_uri( + 'POST', + FAKE_URL + '/os-security-group-rules', + json={'security_group_rule': self.FAKE_SECURITY_GROUP_RULE_RESP}, + status_code=200, + ) + ret = self.api.security_group_rule_create( + security_group_id='1', + ip_protocol='tcp', + ) + self.assertEqual(self.FAKE_SECURITY_GROUP_RULE_RESP, ret) + + def test_security_group_create_options(self): + self.requests_mock.register_uri( + 'POST', + FAKE_URL + '/os-security-group-rules', + json={'security_group_rule': self.FAKE_SECURITY_GROUP_RULE_RESP}, + status_code=200, + ) + ret = self.api.security_group_rule_create( + security_group_id='1', + ip_protocol='tcp', + from_port=22, + to_port=22, + remote_ip='1.2.3.4/24', + ) + self.assertEqual(self.FAKE_SECURITY_GROUP_RULE_RESP, ret) + + def test_security_group_create_port_errors(self): + self.requests_mock.register_uri( + 'POST', + FAKE_URL + '/os-security-group-rules', + json={'security_group_rule': self.FAKE_SECURITY_GROUP_RULE_RESP}, + status_code=200, + ) + self.assertRaises( + compute.InvalidValue, + self.api.security_group_rule_create, + security_group_id='1', + ip_protocol='tcp', + from_port='', + to_port=22, + remote_ip='1.2.3.4/24', + ) + self.assertRaises( + compute.InvalidValue, + self.api.security_group_rule_create, + security_group_id='1', + ip_protocol='tcp', + from_port=0, + to_port=[], + remote_ip='1.2.3.4/24', + ) + + def test_security_group_rule_delete(self): + self.requests_mock.register_uri( + 'DELETE', + FAKE_URL + '/os-security-group-rules/1', + status_code=202, + ) + ret = self.api.security_group_rule_delete('1') + self.assertEqual(202, ret.status_code) + self.assertEqual("", ret.text) diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index 05cb507625..f7e9548264 100644 --- a/openstackclient/tests/unit/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -556,10 +556,7 @@ def create_one_security_group_rule(attrs=None): # Overwrite default attributes. security_group_rule_attrs.update(attrs) - security_group_rule = fakes.FakeResource( - info=copy.deepcopy(security_group_rule_attrs), - loaded=True) - return security_group_rule + return security_group_rule_attrs @staticmethod def create_security_group_rules(attrs=None, count=2): diff --git a/openstackclient/tests/unit/network/v2/test_security_group_compute.py b/openstackclient/tests/unit/network/v2/test_security_group_compute.py index db9831bb2b..c949e2c82d 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_compute.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_compute.py @@ -356,7 +356,7 @@ class TestShowSecurityGroupCompute(TestSecurityGroupCompute): # The security group to be shown. _security_group = \ compute_fakes.FakeSecurityGroup.create_one_security_group( - attrs={'rules': [_security_group_rule._info]} + attrs={'rules': [_security_group_rule]} ) columns = ( @@ -373,7 +373,7 @@ class TestShowSecurityGroupCompute(TestSecurityGroupCompute): _security_group['name'], _security_group['tenant_id'], security_group._format_compute_security_group_rules( - [_security_group_rule._info]), + [_security_group_rule]), ) def setUp(self): diff --git a/openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py b/openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py index 06a7a25d45..5c1937e307 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py @@ -32,6 +32,9 @@ def setUp(self): self.compute = self.app.client_manager.compute +@mock.patch( + 'openstackclient.api.compute_v2.APIv2.security_group_rule_create' +) class TestCreateSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): project = identity_fakes.FakeProject.create_one_project() @@ -48,11 +51,9 @@ def _setup_security_group_rule(self, attrs=None): self._security_group_rule = \ compute_fakes.FakeSecurityGroupRule.create_one_security_group_rule( attrs) - self.compute.security_group_rules.create.return_value = \ - self._security_group_rule expected_columns, expected_data = \ security_group_rule._format_security_group_rule_show( - self._security_group_rule._info) + self._security_group_rule) return expected_columns, expected_data def setUp(self): @@ -67,11 +68,11 @@ def setUp(self): # Get the command object to test self.cmd = security_group_rule.CreateSecurityGroupRule(self.app, None) - def test_security_group_rule_create_no_options(self): + def test_security_group_rule_create_no_options(self, sgr_mock): self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, [], []) - def test_security_group_rule_create_all_source_options(self): + def test_security_group_rule_create_all_source_options(self, sgr_mock): arglist = [ '--src-ip', '10.10.0.0/24', '--src-group', self._security_group['id'], @@ -80,7 +81,7 @@ def test_security_group_rule_create_all_source_options(self): self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, []) - def test_security_group_rule_create_all_remote_options(self): + def test_security_group_rule_create_all_remote_options(self, sgr_mock): arglist = [ '--remote-ip', '10.10.0.0/24', '--remote-group', self._security_group['id'], @@ -89,7 +90,7 @@ def test_security_group_rule_create_all_remote_options(self): self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, []) - def test_security_group_rule_create_bad_protocol(self): + def test_security_group_rule_create_bad_protocol(self, sgr_mock): arglist = [ '--protocol', 'foo', self._security_group['id'], @@ -97,7 +98,7 @@ def test_security_group_rule_create_bad_protocol(self): self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, []) - def test_security_group_rule_create_all_protocol_options(self): + def test_security_group_rule_create_all_protocol_options(self, sgr_mock): arglist = [ '--protocol', 'tcp', '--proto', 'tcp', @@ -106,7 +107,7 @@ def test_security_group_rule_create_all_protocol_options(self): self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, []) - def test_security_group_rule_create_network_options(self): + def test_security_group_rule_create_network_options(self, sgr_mock): arglist = [ '--ingress', '--ethertype', 'IPv4', @@ -119,17 +120,18 @@ def test_security_group_rule_create_network_options(self): self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, []) - def test_security_group_rule_create_default_rule(self): + def test_security_group_rule_create_default_rule(self, sgr_mock): expected_columns, expected_data = self._setup_security_group_rule() - dst_port = str(self._security_group_rule.from_port) + ':' + \ - str(self._security_group_rule.to_port) + sgr_mock.return_value = self._security_group_rule + dst_port = str(self._security_group_rule['from_port']) + ':' + \ + str(self._security_group_rule['to_port']) arglist = [ '--dst-port', dst_port, self._security_group['id'], ] verifylist = [ - ('dst_port', (self._security_group_rule.from_port, - self._security_group_rule.to_port)), + ('dst_port', (self._security_group_rule['from_port'], + self._security_group_rule['to_port'])), ('group', self._security_group['id']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -138,31 +140,32 @@ def test_security_group_rule_create_default_rule(self): # TODO(dtroyer): save this for the security group rule changes # self.compute.api.security_group_rule_create.assert_called_once_with( - self.compute.security_group_rules.create.assert_called_once_with( - self._security_group['id'], - self._security_group_rule.ip_protocol, - self._security_group_rule.from_port, - self._security_group_rule.to_port, - self._security_group_rule.ip_range['cidr'], - None, + sgr_mock.assert_called_once_with( + security_group_id=self._security_group['id'], + ip_protocol=self._security_group_rule['ip_protocol'], + from_port=self._security_group_rule['from_port'], + to_port=self._security_group_rule['to_port'], + remote_ip=self._security_group_rule['ip_range']['cidr'], + remote_group=None, ) self.assertEqual(expected_columns, columns) self.assertEqual(expected_data, data) - def test_security_group_rule_create_source_group(self): + def test_security_group_rule_create_source_group(self, sgr_mock): expected_columns, expected_data = self._setup_security_group_rule({ 'from_port': 22, 'to_port': 22, 'group': {'name': self._security_group['name']}, }) + sgr_mock.return_value = self._security_group_rule arglist = [ - '--dst-port', str(self._security_group_rule.from_port), + '--dst-port', str(self._security_group_rule['from_port']), '--src-group', self._security_group['name'], self._security_group['id'], ] verifylist = [ - ('dst_port', (self._security_group_rule.from_port, - self._security_group_rule.to_port)), + ('dst_port', (self._security_group_rule['from_port'], + self._security_group_rule['to_port'])), ('src_group', self._security_group['name']), ('group', self._security_group['id']), ] @@ -172,31 +175,32 @@ def test_security_group_rule_create_source_group(self): # TODO(dtroyer): save this for the security group rule changes # self.compute.api.security_group_rule_create.assert_called_once_with( - self.compute.security_group_rules.create.assert_called_once_with( - self._security_group['id'], - self._security_group_rule.ip_protocol, - self._security_group_rule.from_port, - self._security_group_rule.to_port, - self._security_group_rule.ip_range['cidr'], - self._security_group['id'], + sgr_mock.assert_called_once_with( + security_group_id=self._security_group['id'], + ip_protocol=self._security_group_rule['ip_protocol'], + from_port=self._security_group_rule['from_port'], + to_port=self._security_group_rule['to_port'], + remote_ip=self._security_group_rule['ip_range']['cidr'], + remote_group=self._security_group['id'], ) self.assertEqual(expected_columns, columns) self.assertEqual(expected_data, data) - def test_security_group_rule_create_remote_group(self): + def test_security_group_rule_create_remote_group(self, sgr_mock): expected_columns, expected_data = self._setup_security_group_rule({ 'from_port': 22, 'to_port': 22, 'group': {'name': self._security_group['name']}, }) + sgr_mock.return_value = self._security_group_rule arglist = [ - '--dst-port', str(self._security_group_rule.from_port), + '--dst-port', str(self._security_group_rule['from_port']), '--remote-group', self._security_group['name'], self._security_group['id'], ] verifylist = [ - ('dst_port', (self._security_group_rule.from_port, - self._security_group_rule.to_port)), + ('dst_port', (self._security_group_rule['from_port'], + self._security_group_rule['to_port'])), ('remote_group', self._security_group['name']), ('group', self._security_group['id']), ] @@ -206,32 +210,33 @@ def test_security_group_rule_create_remote_group(self): # TODO(dtroyer): save this for the security group rule changes # self.compute.api.security_group_rule_create.assert_called_once_with( - self.compute.security_group_rules.create.assert_called_once_with( - self._security_group['id'], - self._security_group_rule.ip_protocol, - self._security_group_rule.from_port, - self._security_group_rule.to_port, - self._security_group_rule.ip_range['cidr'], - self._security_group['id'], + sgr_mock.assert_called_once_with( + security_group_id=self._security_group['id'], + ip_protocol=self._security_group_rule['ip_protocol'], + from_port=self._security_group_rule['from_port'], + to_port=self._security_group_rule['to_port'], + remote_ip=self._security_group_rule['ip_range']['cidr'], + remote_group=self._security_group['id'], ) self.assertEqual(expected_columns, columns) self.assertEqual(expected_data, data) - def test_security_group_rule_create_source_ip(self): + def test_security_group_rule_create_source_ip(self, sgr_mock): expected_columns, expected_data = self._setup_security_group_rule({ 'ip_protocol': 'icmp', 'from_port': -1, 'to_port': -1, 'ip_range': {'cidr': '10.0.2.0/24'}, }) + sgr_mock.return_value = self._security_group_rule arglist = [ - '--protocol', self._security_group_rule.ip_protocol, - '--src-ip', self._security_group_rule.ip_range['cidr'], + '--protocol', self._security_group_rule['ip_protocol'], + '--src-ip', self._security_group_rule['ip_range']['cidr'], self._security_group['id'], ] verifylist = [ - ('protocol', self._security_group_rule.ip_protocol), - ('src_ip', self._security_group_rule.ip_range['cidr']), + ('protocol', self._security_group_rule['ip_protocol']), + ('src_ip', self._security_group_rule['ip_range']['cidr']), ('group', self._security_group['id']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -240,32 +245,33 @@ def test_security_group_rule_create_source_ip(self): # TODO(dtroyer): save this for the security group rule changes # self.compute.api.security_group_rule_create.assert_called_once_with( - self.compute.security_group_rules.create.assert_called_once_with( - self._security_group['id'], - self._security_group_rule.ip_protocol, - self._security_group_rule.from_port, - self._security_group_rule.to_port, - self._security_group_rule.ip_range['cidr'], - None, + sgr_mock.assert_called_once_with( + security_group_id=self._security_group['id'], + ip_protocol=self._security_group_rule['ip_protocol'], + from_port=self._security_group_rule['from_port'], + to_port=self._security_group_rule['to_port'], + remote_ip=self._security_group_rule['ip_range']['cidr'], + remote_group=None, ) self.assertEqual(expected_columns, columns) self.assertEqual(expected_data, data) - def test_security_group_rule_create_remote_ip(self): + def test_security_group_rule_create_remote_ip(self, sgr_mock): expected_columns, expected_data = self._setup_security_group_rule({ 'ip_protocol': 'icmp', 'from_port': -1, 'to_port': -1, 'ip_range': {'cidr': '10.0.2.0/24'}, }) + sgr_mock.return_value = self._security_group_rule arglist = [ - '--protocol', self._security_group_rule.ip_protocol, - '--remote-ip', self._security_group_rule.ip_range['cidr'], + '--protocol', self._security_group_rule['ip_protocol'], + '--remote-ip', self._security_group_rule['ip_range']['cidr'], self._security_group['id'], ] verifylist = [ - ('protocol', self._security_group_rule.ip_protocol), - ('remote_ip', self._security_group_rule.ip_range['cidr']), + ('protocol', self._security_group_rule['ip_protocol']), + ('remote_ip', self._security_group_rule['ip_range']['cidr']), ('group', self._security_group['id']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -274,33 +280,34 @@ def test_security_group_rule_create_remote_ip(self): # TODO(dtroyer): save this for the security group rule changes # self.compute.api.security_group_rule_create.assert_called_once_with( - self.compute.security_group_rules.create.assert_called_once_with( - self._security_group['id'], - self._security_group_rule.ip_protocol, - self._security_group_rule.from_port, - self._security_group_rule.to_port, - self._security_group_rule.ip_range['cidr'], - None, + sgr_mock.assert_called_once_with( + security_group_id=self._security_group['id'], + ip_protocol=self._security_group_rule['ip_protocol'], + from_port=self._security_group_rule['from_port'], + to_port=self._security_group_rule['to_port'], + remote_ip=self._security_group_rule['ip_range']['cidr'], + remote_group=None, ) self.assertEqual(expected_columns, columns) self.assertEqual(expected_data, data) - def test_security_group_rule_create_proto_option(self): + def test_security_group_rule_create_proto_option(self, sgr_mock): expected_columns, expected_data = self._setup_security_group_rule({ 'ip_protocol': 'icmp', 'from_port': -1, 'to_port': -1, 'ip_range': {'cidr': '10.0.2.0/24'}, }) + sgr_mock.return_value = self._security_group_rule arglist = [ - '--proto', self._security_group_rule.ip_protocol, - '--src-ip', self._security_group_rule.ip_range['cidr'], + '--proto', self._security_group_rule['ip_protocol'], + '--src-ip', self._security_group_rule['ip_range']['cidr'], self._security_group['id'], ] verifylist = [ - ('proto', self._security_group_rule.ip_protocol), + ('proto', self._security_group_rule['ip_protocol']), ('protocol', None), - ('src_ip', self._security_group_rule.ip_range['cidr']), + ('src_ip', self._security_group_rule['ip_range']['cidr']), ('group', self._security_group['id']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -309,18 +316,21 @@ def test_security_group_rule_create_proto_option(self): # TODO(dtroyer): save this for the security group rule changes # self.compute.api.security_group_rule_create.assert_called_once_with( - self.compute.security_group_rules.create.assert_called_once_with( - self._security_group['id'], - self._security_group_rule.ip_protocol, - self._security_group_rule.from_port, - self._security_group_rule.to_port, - self._security_group_rule.ip_range['cidr'], - None, + sgr_mock.assert_called_once_with( + security_group_id=self._security_group['id'], + ip_protocol=self._security_group_rule['ip_protocol'], + from_port=self._security_group_rule['from_port'], + to_port=self._security_group_rule['to_port'], + remote_ip=self._security_group_rule['ip_range']['cidr'], + remote_group=None, ) self.assertEqual(expected_columns, columns) self.assertEqual(expected_data, data) +@mock.patch( + 'openstackclient.api.compute_v2.APIv2.security_group_rule_delete' +) class TestDeleteSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): # The security group rule to be deleted. @@ -336,27 +346,27 @@ def setUp(self): # Get the command object to test self.cmd = security_group_rule.DeleteSecurityGroupRule(self.app, None) - def test_security_group_rule_delete(self): + def test_security_group_rule_delete(self, sgr_mock): arglist = [ - self._security_group_rules[0].id, + self._security_group_rules[0]['id'], ] verifylist = [ - ('rule', [self._security_group_rules[0].id]), + ('rule', [self._security_group_rules[0]['id']]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.compute.security_group_rules.delete.assert_called_once_with( - self._security_group_rules[0].id) + sgr_mock.assert_called_once_with( + self._security_group_rules[0]['id']) self.assertIsNone(result) - def test_security_group_rule_multi_delete(self): + def test_security_group_rule_delete_multi(self, sgr_mock): arglist = [] verifylist = [] for s in self._security_group_rules: - arglist.append(s.id) + arglist.append(s['id']) verifylist = [ ('rule', arglist), ] @@ -366,25 +376,23 @@ def test_security_group_rule_multi_delete(self): calls = [] for s in self._security_group_rules: - calls.append(call(s.id)) - self.compute.security_group_rules.delete.assert_has_calls(calls) + calls.append(call(s['id'])) + sgr_mock.assert_has_calls(calls) self.assertIsNone(result) - def test_security_group_rule_multi_delete_with_exception(self): + def test_security_group_rule_delete_multi_with_exception(self, sgr_mock): arglist = [ - self._security_group_rules[0].id, + self._security_group_rules[0]['id'], 'unexist_rule', ] verifylist = [ ('rule', - [self._security_group_rules[0].id, 'unexist_rule']), + [self._security_group_rules[0]['id'], 'unexist_rule']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) find_mock_result = [None, exceptions.CommandError] - self.compute.security_group_rules.delete = ( - mock.Mock(side_effect=find_mock_result) - ) + sgr_mock.side_effect = find_mock_result try: self.cmd.take_action(parsed_args) @@ -392,9 +400,9 @@ def test_security_group_rule_multi_delete_with_exception(self): except exceptions.CommandError as e: self.assertEqual('1 of 2 rules failed to delete.', str(e)) - self.compute.security_group_rules.delete.assert_any_call( - self._security_group_rules[0].id) - self.compute.security_group_rules.delete.assert_any_call( + sgr_mock.assert_any_call( + self._security_group_rules[0]['id']) + sgr_mock.assert_any_call( 'unexist_rule') @@ -421,8 +429,8 @@ class TestListSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): 'group': {'name': _security_group['name']}, }) _security_group['rules'] = [ - _security_group_rule_tcp._info, - _security_group_rule_icmp._info, + _security_group_rule_tcp, + _security_group_rule_icmp, ] expected_columns_with_group = ( @@ -535,7 +543,7 @@ class TestShowSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): columns, data = \ security_group_rule._format_security_group_rule_show( - _security_group_rule._info) + _security_group_rule) def setUp(self): super(TestShowSecurityGroupRuleCompute, self).setUp() @@ -543,7 +551,7 @@ def setUp(self): self.app.client_manager.network_endpoint_enabled = False # Build a security group fake customized for this test. - security_group_rules = [self._security_group_rule._info] + security_group_rules = [self._security_group_rule] security_group = {'rules': security_group_rules} self.compute.api.security_group_list = mock.Mock( return_value=[security_group], @@ -558,10 +566,10 @@ def test_security_group_rule_show_no_options(self): def test_security_group_rule_show_all_options(self): arglist = [ - self._security_group_rule.id, + self._security_group_rule['id'], ] verifylist = [ - ('rule', self._security_group_rule.id), + ('rule', self._security_group_rule['id']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) From e6ea45b2833fdd57a8011154aec5c1f6b00f44ca Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Sat, 8 Apr 2017 11:17:30 -0500 Subject: [PATCH 1650/3095] Low-level Compute v2 API: floating ip api.compute.APIv2 floating ip functions. novaclient 8.0 is now released without support for the previously deprecated nova-net functions, so include a new low-level REST implementation of the removed APIs. Change-Id: Ic461b8d15e072e0534dcd73fff6857581d83c89b --- openstackclient/api/compute_v2.py | 82 ++++++++++ openstackclient/network/v2/floating_ip.py | 27 ++-- .../tests/unit/api/test_compute_v2.py | 111 ++++++++++++++ .../tests/unit/compute/v2/fakes.py | 6 +- .../network/v2/test_floating_ip_compute.py | 144 +++++++++--------- 5 files changed, 277 insertions(+), 93 deletions(-) diff --git a/openstackclient/api/compute_v2.py b/openstackclient/api/compute_v2.py index 3bf3a0d81f..20062a2f4d 100644 --- a/openstackclient/api/compute_v2.py +++ b/openstackclient/api/compute_v2.py @@ -64,6 +64,88 @@ def find( return ret + # Flaoting IPs + + def floating_ip_create( + self, + pool=None, + ): + """Create a new floating ip + + https://developer.openstack.org/api-ref/compute/#create-allocate-floating-ip-address + + :param pool: Name of floating IP pool + """ + + url = "/os-floating-ips" + + try: + return self.create( + url, + json={'pool': pool}, + )['floating_ip'] + except ( + ksa_exceptions.NotFound, + ksa_exceptions.BadRequest, + ): + msg = _("%s not found") % pool + raise exceptions.NotFound(msg) + + def floating_ip_delete( + self, + floating_ip_id=None, + ): + """Delete a floating IP + + https://developer.openstack.org/api-ref/compute/#delete-deallocate-floating-ip-address + + :param string security_group: + Floating IP ID + """ + + url = "/os-floating-ips" + + if floating_ip_id is not None: + return self.delete('/%s/%s' % (url, floating_ip_id)) + + return None + + def floating_ip_find( + self, + floating_ip=None, + ): + """Return a security group given name or ID + + https://developer.openstack.org/api-ref/compute/#list-floating-ip-addresses + + :param string floating_ip: + Floating IP address + :returns: A dict of the floating IP attributes + """ + + url = "/os-floating-ips" + + return self.find( + url, + attr='ip', + value=floating_ip, + ) + + def floating_ip_list( + self, + ): + """Get floating IPs + + https://developer.openstack.org/api-ref/compute/#show-floating-ip-address-details + + :returns: + list of security groups names + """ + + url = "/os-floating-ips" + + return self.list(url)["floating_ips"] + # Security Groups def security_group_create( diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index eaf27420ff..05b688a63d 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -190,9 +190,9 @@ def take_action_network(self, client, parsed_args): return (display_columns, data) def take_action_compute(self, client, parsed_args): - obj = client.floating_ips.create(parsed_args.network) - columns = _get_columns(obj._info) - data = utils.get_dict_properties(obj._info, columns) + obj = client.api.floating_ip_create(parsed_args.network) + columns = _get_columns(obj) + data = utils.get_dict_properties(obj, columns) return (columns, data) @@ -245,13 +245,7 @@ def take_action_network(self, client, parsed_args): client.delete_ip(obj) def take_action_compute(self, client, parsed_args): - obj = utils.find_resource(client.floating_ips, self.r) - client.floating_ips.delete(obj.id) - - def take_action(self, parsed_args): - """Implements a naive cache for the list of floating IPs""" - - super(DeleteFloatingIP, self).take_action(parsed_args) + client.api.floating_ip_delete(self.r) class DeleteIPFloating(DeleteFloatingIP): @@ -414,10 +408,10 @@ def take_action_compute(self, client, parsed_args): 'Pool', ) - data = client.floating_ips.list() + data = client.api.floating_ip_list() return (headers, - (utils.get_item_properties( + (utils.get_dict_properties( s, columns, formatters={}, ) for s in data)) @@ -510,12 +504,9 @@ def take_action_network(self, client, parsed_args): return (display_columns, data) def take_action_compute(self, client, parsed_args): - obj = utils.find_resource( - client.floating_ips, - parsed_args.floating_ip, - ) - columns = _get_columns(obj._info) - data = utils.get_dict_properties(obj._info, columns) + obj = client.api.floating_ip_find(parsed_args.floating_ip) + columns = _get_columns(obj) + data = utils.get_dict_properties(obj, columns) return (columns, data) diff --git a/openstackclient/tests/unit/api/test_compute_v2.py b/openstackclient/tests/unit/api/test_compute_v2.py index f443e810c1..f48986244a 100644 --- a/openstackclient/tests/unit/api/test_compute_v2.py +++ b/openstackclient/tests/unit/api/test_compute_v2.py @@ -34,6 +34,117 @@ def setUp(self): self.requests_mock = self.useFixture(fixture.Fixture()) +class TestFloatingIP(TestComputeAPIv2): + + FAKE_FLOATING_IP_RESP = { + 'id': 1, + 'ip': '203.0.113.11', # TEST-NET-3 + 'fixed_ip': '198.51.100.11', # TEST-NET-2 + 'pool': 'nova', + 'instance_id': None, + } + FAKE_FLOATING_IP_RESP_2 = { + 'id': 2, + 'ip': '203.0.113.12', # TEST-NET-3 + 'fixed_ip': '198.51.100.12', # TEST-NET-2 + 'pool': 'nova', + 'instance_id': None, + } + LIST_FLOATING_IP_RESP = [ + FAKE_FLOATING_IP_RESP, + FAKE_FLOATING_IP_RESP_2, + ] + + def test_floating_ip_create(self): + self.requests_mock.register_uri( + 'POST', + FAKE_URL + '/os-floating-ips', + json={'floating_ip': self.FAKE_FLOATING_IP_RESP}, + status_code=200, + ) + ret = self.api.floating_ip_create('nova') + self.assertEqual(self.FAKE_FLOATING_IP_RESP, ret) + + def test_floating_ip_create_not_found(self): + self.requests_mock.register_uri( + 'POST', + FAKE_URL + '/os-floating-ips', + status_code=404, + ) + self.assertRaises( + osc_lib_exceptions.NotFound, + self.api.floating_ip_create, + 'not-nova', + ) + + def test_floating_ip_delete(self): + self.requests_mock.register_uri( + 'DELETE', + FAKE_URL + '/os-floating-ips/1', + status_code=202, + ) + ret = self.api.floating_ip_delete('1') + self.assertEqual(202, ret.status_code) + self.assertEqual("", ret.text) + + def test_floating_ip_delete_none(self): + ret = self.api.floating_ip_delete() + self.assertIsNone(ret) + + def test_floating_ip_find_id(self): + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/os-floating-ips/1', + json={'floating_ip': self.FAKE_FLOATING_IP_RESP}, + status_code=200, + ) + ret = self.api.floating_ip_find('1') + self.assertEqual(self.FAKE_FLOATING_IP_RESP, ret) + + def test_floating_ip_find_ip(self): + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/os-floating-ips/' + self.FAKE_FLOATING_IP_RESP['ip'], + status_code=404, + ) + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/os-floating-ips', + json={'floating_ips': self.LIST_FLOATING_IP_RESP}, + status_code=200, + ) + ret = self.api.floating_ip_find(self.FAKE_FLOATING_IP_RESP['ip']) + self.assertEqual(self.FAKE_FLOATING_IP_RESP, ret) + + def test_floating_ip_find_not_found(self): + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/os-floating-ips/1.2.3.4', + status_code=404, + ) + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/os-floating-ips', + json={'floating_ips': self.LIST_FLOATING_IP_RESP}, + status_code=200, + ) + self.assertRaises( + osc_lib_exceptions.NotFound, + self.api.floating_ip_find, + '1.2.3.4', + ) + + def test_floating_ip_list(self): + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/os-floating-ips', + json={'floating_ips': self.LIST_FLOATING_IP_RESP}, + status_code=200, + ) + ret = self.api.floating_ip_list() + self.assertEqual(self.LIST_FLOATING_IP_RESP, ret) + + class TestSecurityGroup(TestComputeAPIv2): FAKE_SECURITY_GROUP_RESP = { diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index 05cb507625..90c2e1f9da 100644 --- a/openstackclient/tests/unit/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -1019,11 +1019,7 @@ def create_one_floating_ip(attrs=None): # Overwrite default attributes. floating_ip_attrs.update(attrs) - floating_ip = fakes.FakeResource( - info=copy.deepcopy(floating_ip_attrs), - loaded=True) - - return floating_ip + return floating_ip_attrs @staticmethod def create_floating_ips(attrs=None, count=2): diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip_compute.py b/openstackclient/tests/unit/network/v2/test_floating_ip_compute.py index 23cd82d213..0d58c158b2 100644 --- a/openstackclient/tests/unit/network/v2/test_floating_ip_compute.py +++ b/openstackclient/tests/unit/network/v2/test_floating_ip_compute.py @@ -32,10 +32,13 @@ def setUp(self): self.compute = self.app.client_manager.compute +@mock.patch( + 'openstackclient.api.compute_v2.APIv2.floating_ip_create' +) class TestCreateFloatingIPCompute(TestFloatingIPCompute): # The floating ip to be deleted. - floating_ip = compute_fakes.FakeFloatingIP.create_one_floating_ip() + _floating_ip = compute_fakes.FakeFloatingIP.create_one_floating_ip() columns = ( 'fixed_ip', @@ -46,11 +49,11 @@ class TestCreateFloatingIPCompute(TestFloatingIPCompute): ) data = ( - floating_ip.fixed_ip, - floating_ip.id, - floating_ip.instance_id, - floating_ip.ip, - floating_ip.pool, + _floating_ip['fixed_ip'], + _floating_ip['id'], + _floating_ip['instance_id'], + _floating_ip['ip'], + _floating_ip['pool'], ) def setUp(self): @@ -58,76 +61,79 @@ def setUp(self): self.app.client_manager.network_endpoint_enabled = False - self.compute.floating_ips.create.return_value = self.floating_ip + # self.compute.floating_ips.create.return_value = self.floating_ip # Get the command object to test self.cmd = fip.CreateFloatingIP(self.app, None) - def test_create_no_options(self): + def test_floating_ip_create_no_arg(self, fip_mock): arglist = [] verifylist = [] self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) - def test_create_default_options(self): + def test_floating_ip_create_default(self, fip_mock): + fip_mock.return_value = self._floating_ip arglist = [ - self.floating_ip.pool, + self._floating_ip['pool'], ] verifylist = [ - ('network', self.floating_ip.pool), + ('network', self._floating_ip['pool']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.compute.floating_ips.create.assert_called_once_with( - self.floating_ip.pool) + fip_mock.assert_called_once_with(self._floating_ip['pool']) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) +@mock.patch( + 'openstackclient.api.compute_v2.APIv2.floating_ip_delete' +) class TestDeleteFloatingIPCompute(TestFloatingIPCompute): # The floating ips to be deleted. - floating_ips = compute_fakes.FakeFloatingIP.create_floating_ips(count=2) + _floating_ips = compute_fakes.FakeFloatingIP.create_floating_ips(count=2) def setUp(self): super(TestDeleteFloatingIPCompute, self).setUp() self.app.client_manager.network_endpoint_enabled = False - self.compute.floating_ips.delete.return_value = None - # Return value of utils.find_resource() self.compute.floating_ips.get = ( - compute_fakes.FakeFloatingIP.get_floating_ips(self.floating_ips)) + compute_fakes.FakeFloatingIP.get_floating_ips(self._floating_ips)) # Get the command object to test self.cmd = fip.DeleteFloatingIP(self.app, None) - def test_floating_ip_delete(self): + def test_floating_ip_delete(self, fip_mock): + fip_mock.return_value = mock.Mock(return_value=None) arglist = [ - self.floating_ips[0].id, + self._floating_ips[0]['id'], ] verifylist = [ - ('floating_ip', [self.floating_ips[0].id]), + ('floating_ip', [self._floating_ips[0]['id']]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.compute.floating_ips.delete.assert_called_once_with( - self.floating_ips[0].id + fip_mock.assert_called_once_with( + self._floating_ips[0]['id'] ) self.assertIsNone(result) - def test_multi_floating_ips_delete(self): + def test_floating_ip_delete_multi(self, fip_mock): + fip_mock.return_value = mock.Mock(return_value=None) arglist = [] verifylist = [] - for f in self.floating_ips: - arglist.append(f.id) + for f in self._floating_ips: + arglist.append(f['id']) verifylist = [ ('floating_ip', arglist), ] @@ -136,47 +142,44 @@ def test_multi_floating_ips_delete(self): result = self.cmd.take_action(parsed_args) calls = [] - for f in self.floating_ips: - calls.append(call(f.id)) - self.compute.floating_ips.delete.assert_has_calls(calls) + for f in self._floating_ips: + calls.append(call(f['id'])) + fip_mock.assert_has_calls(calls) self.assertIsNone(result) - def test_multi_floating_ips_delete_with_exception(self): + def test_floating_ip_delete_multi_exception(self, fip_mock): + fip_mock.return_value = mock.Mock(return_value=None) + fip_mock.side_effect = ([ + mock.Mock(return_value=None), + exceptions.CommandError, + ]) arglist = [ - self.floating_ips[0].id, + self._floating_ips[0]['id'], 'unexist_floating_ip', ] - verifylist = [ - ('floating_ip', - [self.floating_ips[0].id, 'unexist_floating_ip']), - ] + verifylist = [( + 'floating_ip', + [self._floating_ips[0]['id'], 'unexist_floating_ip'], + )] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - find_mock_result = [self.floating_ips[0], exceptions.CommandError] - self.compute.floating_ips.get = ( - mock.Mock(side_effect=find_mock_result) - ) - self.compute.floating_ips.find.side_effect = exceptions.NotFound(None) - try: self.cmd.take_action(parsed_args) self.fail('CommandError should be raised.') except exceptions.CommandError as e: self.assertEqual('1 of 2 floating_ips failed to delete.', str(e)) - self.compute.floating_ips.get.assert_any_call( - self.floating_ips[0].id) - self.compute.floating_ips.get.assert_any_call( - 'unexist_floating_ip') - self.compute.floating_ips.delete.assert_called_once_with( - self.floating_ips[0].id - ) + fip_mock.assert_any_call(self._floating_ips[0]['id']) + fip_mock.assert_any_call('unexist_floating_ip') +@mock.patch( + 'openstackclient.api.compute_v2.APIv2.floating_ip_list' +) class TestListFloatingIPCompute(TestFloatingIPCompute): # The floating ips to be list up - floating_ips = compute_fakes.FakeFloatingIP.create_floating_ips(count=3) + _floating_ips = compute_fakes.FakeFloatingIP.create_floating_ips(count=3) columns = ( 'ID', @@ -187,13 +190,13 @@ class TestListFloatingIPCompute(TestFloatingIPCompute): ) data = [] - for ip in floating_ips: + for ip in _floating_ips: data.append(( - ip.id, - ip.ip, - ip.fixed_ip, - ip.instance_id, - ip.pool, + ip['id'], + ip['ip'], + ip['fixed_ip'], + ip['instance_id'], + ip['pool'], )) def setUp(self): @@ -201,27 +204,29 @@ def setUp(self): self.app.client_manager.network_endpoint_enabled = False - self.compute.floating_ips.list.return_value = self.floating_ips - # Get the command object to test self.cmd = fip.ListFloatingIP(self.app, None) - def test_floating_ip_list(self): + def test_floating_ip_list(self, fip_mock): + fip_mock.return_value = self._floating_ips arglist = [] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.compute.floating_ips.list.assert_called_once_with() + fip_mock.assert_called_once_with() self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) +@mock.patch( + 'openstackclient.api.compute_v2.APIv2.floating_ip_find' +) class TestShowFloatingIPCompute(TestFloatingIPCompute): # The floating ip to display. - floating_ip = compute_fakes.FakeFloatingIP.create_one_floating_ip() + _floating_ip = compute_fakes.FakeFloatingIP.create_one_floating_ip() columns = ( 'fixed_ip', @@ -232,11 +237,11 @@ class TestShowFloatingIPCompute(TestFloatingIPCompute): ) data = ( - floating_ip.fixed_ip, - floating_ip.id, - floating_ip.instance_id, - floating_ip.ip, - floating_ip.pool, + _floating_ip['fixed_ip'], + _floating_ip['id'], + _floating_ip['instance_id'], + _floating_ip['ip'], + _floating_ip['pool'], ) def setUp(self): @@ -244,22 +249,21 @@ def setUp(self): self.app.client_manager.network_endpoint_enabled = False - # Return value of utils.find_resource() - self.compute.floating_ips.get.return_value = self.floating_ip - # Get the command object to test self.cmd = fip.ShowFloatingIP(self.app, None) - def test_floating_ip_show(self): + def test_floating_ip_show(self, fip_mock): + fip_mock.return_value = self._floating_ip arglist = [ - self.floating_ip.id, + self._floating_ip['id'], ] verifylist = [ - ('floating_ip', self.floating_ip.id), + ('floating_ip', self._floating_ip['id']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) + fip_mock.assert_called_once_with(self._floating_ip['id']) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) From b168f2d998b70c16685a11b1e964aa9222ed363b Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 12 Apr 2017 04:21:53 +0000 Subject: [PATCH 1651/3095] Updated from global requirements Change-Id: Icd26cb6d2525ff1736923c39e9c64eb12f6d39b6 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a1bb8b1fc3..9b44113a20 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ # The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -pbr>=2.0.0 # Apache-2.0 +pbr!=2.1.0,>=2.0.0 # Apache-2.0 six>=1.9.0 # MIT Babel!=2.4.0,>=2.3.4 # BSD From 6f473be588a7d0dcd10ca189e3e1dba45a6eb2fe Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Mon, 17 Apr 2017 17:10:39 -0500 Subject: [PATCH 1652/3095] Split network tests Split up the network unit tests between compute and network APIs into separate files in preparation for reworking the compute (nova-net) implementations to deal with the removal of deprecated nova-net support in novaclient 8.0.0. No code changes are intended here, just splitting two files into four. Change-Id: I2d001118af436f95025d2851341f8ca802e78830 --- .../tests/unit/network/v2/test_network.py | 372 ----------------- .../unit/network/v2/test_network_compute.py | 392 ++++++++++++++++++ 2 files changed, 392 insertions(+), 372 deletions(-) create mode 100644 openstackclient/tests/unit/network/v2/test_network_compute.py diff --git a/openstackclient/tests/unit/network/v2/test_network.py b/openstackclient/tests/unit/network/v2/test_network.py index bc1279ecbe..1bd7bac619 100644 --- a/openstackclient/tests/unit/network/v2/test_network.py +++ b/openstackclient/tests/unit/network/v2/test_network.py @@ -19,7 +19,6 @@ from osc_lib import utils from openstackclient.network.v2 import network -from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes from openstackclient.tests.unit import fakes from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes_v2 from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3 @@ -988,374 +987,3 @@ def test_show_all_options(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) - - -# Tests for Nova network -# -class TestNetworkCompute(compute_fakes.TestComputev2): - - def setUp(self): - super(TestNetworkCompute, self).setUp() - - # Get a shortcut to the compute client - self.compute = self.app.client_manager.compute - - -class TestCreateNetworkCompute(TestNetworkCompute): - - # The network to create. - _network = compute_fakes.FakeNetwork.create_one_network() - - columns = ( - 'bridge', - 'bridge_interface', - 'broadcast', - 'cidr', - 'cidr_v6', - 'created_at', - 'deleted', - 'deleted_at', - 'dhcp_server', - 'dhcp_start', - 'dns1', - 'dns2', - 'enable_dhcp', - 'gateway', - 'gateway_v6', - 'host', - 'id', - 'injected', - 'label', - 'mtu', - 'multi_host', - 'netmask', - 'netmask_v6', - 'priority', - 'project_id', - 'rxtx_base', - 'share_address', - 'updated_at', - 'vlan', - 'vpn_private_address', - 'vpn_public_address', - 'vpn_public_port', - ) - - data = ( - _network.bridge, - _network.bridge_interface, - _network.broadcast, - _network.cidr, - _network.cidr_v6, - _network.created_at, - _network.deleted, - _network.deleted_at, - _network.dhcp_server, - _network.dhcp_start, - _network.dns1, - _network.dns2, - _network.enable_dhcp, - _network.gateway, - _network.gateway_v6, - _network.host, - _network.id, - _network.injected, - _network.label, - _network.mtu, - _network.multi_host, - _network.netmask, - _network.netmask_v6, - _network.priority, - _network.project_id, - _network.rxtx_base, - _network.share_address, - _network.updated_at, - _network.vlan, - _network.vpn_private_address, - _network.vpn_public_address, - _network.vpn_public_port, - ) - - def setUp(self): - super(TestCreateNetworkCompute, self).setUp() - - self.app.client_manager.network_endpoint_enabled = False - - self.compute.networks.create.return_value = self._network - - # Get the command object to test - self.cmd = network.CreateNetwork(self.app, None) - - def test_create_no_options(self): - arglist = [] - verifylist = [] - - # Missing required args should raise exception here - self.assertRaises(tests_utils.ParserException, self.check_parser, - self.cmd, arglist, verifylist) - - def test_create_default_options(self): - arglist = [ - "--subnet", self._network.cidr, - self._network.label, - ] - verifylist = [ - ('subnet', self._network.cidr), - ('name', self._network.label), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.compute.networks.create.assert_called_once_with(**{ - 'cidr': self._network.cidr, - 'label': self._network.label, - }) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) - - -class TestDeleteNetworkCompute(TestNetworkCompute): - - def setUp(self): - super(TestDeleteNetworkCompute, self).setUp() - - self.app.client_manager.network_endpoint_enabled = False - - # The networks to delete - self._networks = compute_fakes.FakeNetwork.create_networks(count=3) - - self.compute.networks.delete.return_value = None - - # Return value of utils.find_resource() - self.compute.networks.get = \ - compute_fakes.FakeNetwork.get_networks(networks=self._networks) - - # Get the command object to test - self.cmd = network.DeleteNetwork(self.app, None) - - def test_delete_one_network(self): - arglist = [ - self._networks[0].label, - ] - verifylist = [ - ('network', [self._networks[0].label]), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - self.compute.networks.delete.assert_called_once_with( - self._networks[0].id) - self.assertIsNone(result) - - def test_delete_multiple_networks(self): - arglist = [] - for n in self._networks: - arglist.append(n.label) - verifylist = [ - ('network', arglist), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - calls = [] - for n in self._networks: - calls.append(call(n.id)) - self.compute.networks.delete.assert_has_calls(calls) - self.assertIsNone(result) - - def test_delete_multiple_networks_exception(self): - arglist = [ - self._networks[0].id, - 'xxxx-yyyy-zzzz', - self._networks[1].id, - ] - verifylist = [ - ('network', arglist), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # Fake exception in utils.find_resource() - # In compute v2, we use utils.find_resource() to find a network. - # It calls get() several times, but find() only one time. So we - # choose to fake get() always raise exception, then pass through. - # And fake find() to find the real network or not. - self.compute.networks.get.side_effect = Exception() - ret_find = [ - self._networks[0], - Exception(), - self._networks[1], - ] - self.compute.networks.find.side_effect = ret_find - - # Fake exception in delete() - ret_delete = [ - None, - Exception(), - ] - self.compute.networks.delete = mock.Mock(side_effect=ret_delete) - - self.assertRaises(exceptions.CommandError, self.cmd.take_action, - parsed_args) - - # The second call of utils.find_resource() should fail. So delete() - # was only called twice. - calls = [ - call(self._networks[0].id), - call(self._networks[1].id), - ] - self.compute.networks.delete.assert_has_calls(calls) - - -class TestListNetworkCompute(TestNetworkCompute): - - # The networks going to be listed up. - _networks = compute_fakes.FakeNetwork.create_networks(count=3) - - columns = ( - 'ID', - 'Name', - 'Subnet', - ) - - data = [] - for net in _networks: - data.append(( - net.id, - net.label, - net.cidr, - )) - - def setUp(self): - super(TestListNetworkCompute, self).setUp() - - self.app.client_manager.network_endpoint_enabled = False - - self.compute.networks.list.return_value = self._networks - - # Get the command object to test - self.cmd = network.ListNetwork(self.app, None) - - def test_network_list_no_options(self): - arglist = [] - verifylist = [] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # In base command class Lister in cliff, abstract method take_action() - # returns a tuple containing the column names and an iterable - # containing the data to be listed. - columns, data = self.cmd.take_action(parsed_args) - - self.compute.networks.list.assert_called_once_with() - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) - - -class TestShowNetworkCompute(TestNetworkCompute): - - # The network to show. - _network = compute_fakes.FakeNetwork.create_one_network() - - columns = ( - 'bridge', - 'bridge_interface', - 'broadcast', - 'cidr', - 'cidr_v6', - 'created_at', - 'deleted', - 'deleted_at', - 'dhcp_server', - 'dhcp_start', - 'dns1', - 'dns2', - 'enable_dhcp', - 'gateway', - 'gateway_v6', - 'host', - 'id', - 'injected', - 'label', - 'mtu', - 'multi_host', - 'netmask', - 'netmask_v6', - 'priority', - 'project_id', - 'rxtx_base', - 'share_address', - 'updated_at', - 'vlan', - 'vpn_private_address', - 'vpn_public_address', - 'vpn_public_port', - ) - - data = ( - _network.bridge, - _network.bridge_interface, - _network.broadcast, - _network.cidr, - _network.cidr_v6, - _network.created_at, - _network.deleted, - _network.deleted_at, - _network.dhcp_server, - _network.dhcp_start, - _network.dns1, - _network.dns2, - _network.enable_dhcp, - _network.gateway, - _network.gateway_v6, - _network.host, - _network.id, - _network.injected, - _network.label, - _network.mtu, - _network.multi_host, - _network.netmask, - _network.netmask_v6, - _network.priority, - _network.project_id, - _network.rxtx_base, - _network.share_address, - _network.updated_at, - _network.vlan, - _network.vpn_private_address, - _network.vpn_public_address, - _network.vpn_public_port, - ) - - def setUp(self): - super(TestShowNetworkCompute, self).setUp() - - self.app.client_manager.network_endpoint_enabled = False - - # Return value of utils.find_resource() - self.compute.networks.get.return_value = self._network - - # Get the command object to test - self.cmd = network.ShowNetwork(self.app, None) - - def test_show_no_options(self): - arglist = [] - verifylist = [] - - self.assertRaises(tests_utils.ParserException, self.check_parser, - self.cmd, arglist, verifylist) - - def test_show_all_options(self): - arglist = [ - self._network.label, - ] - verifylist = [ - ('network', self._network.label), - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) - - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) diff --git a/openstackclient/tests/unit/network/v2/test_network_compute.py b/openstackclient/tests/unit/network/v2/test_network_compute.py new file mode 100644 index 0000000000..c2ec2f378b --- /dev/null +++ b/openstackclient/tests/unit/network/v2/test_network_compute.py @@ -0,0 +1,392 @@ +# 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 mock import call + +from osc_lib import exceptions + +from openstackclient.network.v2 import network +from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes +from openstackclient.tests.unit import utils as tests_utils + + +# Tests for Nova network +# +class TestNetworkCompute(compute_fakes.TestComputev2): + + def setUp(self): + super(TestNetworkCompute, self).setUp() + + # Get a shortcut to the compute client + self.compute = self.app.client_manager.compute + + +class TestCreateNetworkCompute(TestNetworkCompute): + + # The network to create. + _network = compute_fakes.FakeNetwork.create_one_network() + + columns = ( + 'bridge', + 'bridge_interface', + 'broadcast', + 'cidr', + 'cidr_v6', + 'created_at', + 'deleted', + 'deleted_at', + 'dhcp_server', + 'dhcp_start', + 'dns1', + 'dns2', + 'enable_dhcp', + 'gateway', + 'gateway_v6', + 'host', + 'id', + 'injected', + 'label', + 'mtu', + 'multi_host', + 'netmask', + 'netmask_v6', + 'priority', + 'project_id', + 'rxtx_base', + 'share_address', + 'updated_at', + 'vlan', + 'vpn_private_address', + 'vpn_public_address', + 'vpn_public_port', + ) + + data = ( + _network.bridge, + _network.bridge_interface, + _network.broadcast, + _network.cidr, + _network.cidr_v6, + _network.created_at, + _network.deleted, + _network.deleted_at, + _network.dhcp_server, + _network.dhcp_start, + _network.dns1, + _network.dns2, + _network.enable_dhcp, + _network.gateway, + _network.gateway_v6, + _network.host, + _network.id, + _network.injected, + _network.label, + _network.mtu, + _network.multi_host, + _network.netmask, + _network.netmask_v6, + _network.priority, + _network.project_id, + _network.rxtx_base, + _network.share_address, + _network.updated_at, + _network.vlan, + _network.vpn_private_address, + _network.vpn_public_address, + _network.vpn_public_port, + ) + + def setUp(self): + super(TestCreateNetworkCompute, self).setUp() + + self.app.client_manager.network_endpoint_enabled = False + + self.compute.networks.create.return_value = self._network + + # Get the command object to test + self.cmd = network.CreateNetwork(self.app, None) + + def test_create_no_options(self): + arglist = [] + verifylist = [] + + # Missing required args should raise exception here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_create_default_options(self): + arglist = [ + "--subnet", self._network.cidr, + self._network.label, + ] + verifylist = [ + ('subnet', self._network.cidr), + ('name', self._network.label), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.compute.networks.create.assert_called_once_with(**{ + 'cidr': self._network.cidr, + 'label': self._network.label, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestDeleteNetworkCompute(TestNetworkCompute): + + def setUp(self): + super(TestDeleteNetworkCompute, self).setUp() + + self.app.client_manager.network_endpoint_enabled = False + + # The networks to delete + self._networks = compute_fakes.FakeNetwork.create_networks(count=3) + + self.compute.networks.delete.return_value = None + + # Return value of utils.find_resource() + self.compute.networks.get = \ + compute_fakes.FakeNetwork.get_networks(networks=self._networks) + + # Get the command object to test + self.cmd = network.DeleteNetwork(self.app, None) + + def test_delete_one_network(self): + arglist = [ + self._networks[0].label, + ] + verifylist = [ + ('network', [self._networks[0].label]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.compute.networks.delete.assert_called_once_with( + self._networks[0].id) + self.assertIsNone(result) + + def test_delete_multiple_networks(self): + arglist = [] + for n in self._networks: + arglist.append(n.label) + verifylist = [ + ('network', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [] + for n in self._networks: + calls.append(call(n.id)) + self.compute.networks.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_delete_multiple_networks_exception(self): + arglist = [ + self._networks[0].id, + 'xxxx-yyyy-zzzz', + self._networks[1].id, + ] + verifylist = [ + ('network', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Fake exception in utils.find_resource() + # In compute v2, we use utils.find_resource() to find a network. + # It calls get() several times, but find() only one time. So we + # choose to fake get() always raise exception, then pass through. + # And fake find() to find the real network or not. + self.compute.networks.get.side_effect = Exception() + ret_find = [ + self._networks[0], + Exception(), + self._networks[1], + ] + self.compute.networks.find.side_effect = ret_find + + # Fake exception in delete() + ret_delete = [ + None, + Exception(), + ] + self.compute.networks.delete = mock.Mock(side_effect=ret_delete) + + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + + # The second call of utils.find_resource() should fail. So delete() + # was only called twice. + calls = [ + call(self._networks[0].id), + call(self._networks[1].id), + ] + self.compute.networks.delete.assert_has_calls(calls) + + +class TestListNetworkCompute(TestNetworkCompute): + + # The networks going to be listed up. + _networks = compute_fakes.FakeNetwork.create_networks(count=3) + + columns = ( + 'ID', + 'Name', + 'Subnet', + ) + + data = [] + for net in _networks: + data.append(( + net.id, + net.label, + net.cidr, + )) + + def setUp(self): + super(TestListNetworkCompute, self).setUp() + + self.app.client_manager.network_endpoint_enabled = False + + self.compute.networks.list.return_value = self._networks + + # Get the command object to test + self.cmd = network.ListNetwork(self.app, None) + + def test_network_list_no_options(self): + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + self.compute.networks.list.assert_called_once_with() + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + +class TestShowNetworkCompute(TestNetworkCompute): + + # The network to show. + _network = compute_fakes.FakeNetwork.create_one_network() + + columns = ( + 'bridge', + 'bridge_interface', + 'broadcast', + 'cidr', + 'cidr_v6', + 'created_at', + 'deleted', + 'deleted_at', + 'dhcp_server', + 'dhcp_start', + 'dns1', + 'dns2', + 'enable_dhcp', + 'gateway', + 'gateway_v6', + 'host', + 'id', + 'injected', + 'label', + 'mtu', + 'multi_host', + 'netmask', + 'netmask_v6', + 'priority', + 'project_id', + 'rxtx_base', + 'share_address', + 'updated_at', + 'vlan', + 'vpn_private_address', + 'vpn_public_address', + 'vpn_public_port', + ) + + data = ( + _network.bridge, + _network.bridge_interface, + _network.broadcast, + _network.cidr, + _network.cidr_v6, + _network.created_at, + _network.deleted, + _network.deleted_at, + _network.dhcp_server, + _network.dhcp_start, + _network.dns1, + _network.dns2, + _network.enable_dhcp, + _network.gateway, + _network.gateway_v6, + _network.host, + _network.id, + _network.injected, + _network.label, + _network.mtu, + _network.multi_host, + _network.netmask, + _network.netmask_v6, + _network.priority, + _network.project_id, + _network.rxtx_base, + _network.share_address, + _network.updated_at, + _network.vlan, + _network.vpn_private_address, + _network.vpn_public_address, + _network.vpn_public_port, + ) + + def setUp(self): + super(TestShowNetworkCompute, self).setUp() + + self.app.client_manager.network_endpoint_enabled = False + + # Return value of utils.find_resource() + self.compute.networks.get.return_value = self._network + + # Get the command object to test + self.cmd = network.ShowNetwork(self.app, None) + + def test_show_no_options(self): + arglist = [] + verifylist = [] + + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_show_all_options(self): + arglist = [ + self._network.label, + ] + verifylist = [ + ('network', self._network.label), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) From b2783dc3c44f5843a25770ff749d7a0de18b8dfc Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Mon, 17 Apr 2017 17:04:43 -0500 Subject: [PATCH 1653/3095] Low-level Compute v2 API: network api.compute.APIv2 network functions. novaclient 8.0 is now released without support for the previously deprecated nova-net functions, so include a new low-level REST implementation of the removed APIs. Change-Id: If230f128e91cda44461fe93c976cac2aecec2252 --- openstackclient/api/compute_v2.py | 94 ++++++ openstackclient/compute/v2/server.py | 10 +- openstackclient/network/v2/network.py | 30 +- .../tests/unit/api/test_compute_v2.py | 151 ++++++++++ .../tests/unit/compute/v2/fakes.py | 8 +- .../tests/unit/compute/v2/test_server.py | 43 ++- .../unit/network/v2/test_network_compute.py | 282 +++++++++--------- 7 files changed, 430 insertions(+), 188 deletions(-) diff --git a/openstackclient/api/compute_v2.py b/openstackclient/api/compute_v2.py index 181522e4ee..3141728acf 100644 --- a/openstackclient/api/compute_v2.py +++ b/openstackclient/api/compute_v2.py @@ -175,6 +175,100 @@ def floating_ip_list( return self.list(url)["floating_ips"] + # Networks + + def network_create( + self, + name=None, + subnet=None, + share_subnet=None, + ): + """Create a new network + + https://developer.openstack.org/api-ref/compute/#create-project-network + + :param string name: + Network label + :param integer subnet: + Subnet for IPv4 fixed addresses in CIDR notation + :param integer share_subnet: + Shared subnet between projects, True or False + :returns: A dict of the network attributes + """ + + url = "/os-tenant-networks" + + params = { + 'label': name, + 'cidr': subnet, + 'share_address': share_subnet, + } + + return self.create( + url, + json={'network': params}, + )['network'] + + def network_delete( + self, + network=None, + ): + """Delete a network + + https://developer.openstack.org/api-ref/compute/#delete-project-network + + :param string network: + Network name or ID + """ + + url = "/os-tenant-networks" + + network = self.find( + url, + attr='label', + value=network, + )['id'] + if network is not None: + return self.delete('/%s/%s' % (url, network)) + + return None + + def network_find( + self, + network=None, + ): + """Return a network given name or ID + + https://developer.openstack.org/api-ref/compute/#show-project-network-details + + :param string network: + Network name or ID + :returns: A dict of the network attributes + """ + + url = "/os-tenant-networks" + + return self.find( + url, + attr='label', + value=network, + ) + + def network_list( + self, + ): + """Get networks + + https://developer.openstack.org/api-ref/compute/#list-project-networks + + :returns: + list of networks + """ + + url = "/os-tenant-networks" + + return self.list(url)["networks"] + # Security Groups def security_group_create( diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 7cd4588b9a..81efd9f309 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -215,11 +215,13 @@ def take_action(self, parsed_args): server = utils.find_resource( compute_client.servers, parsed_args.server) - network = utils.find_resource( - compute_client.networks, parsed_args.network) + network = compute_client.api.network_find(parsed_args.network) - server.interface_attach(port_id=None, net_id=network.id, - fixed_ip=parsed_args.fixed_ip_address) + server.interface_attach( + port_id=None, + net_id=network['id'], + fixed_ip=parsed_args.fixed_ip_address, + ) class AddFloatingIP(command.Command): diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 3e0bb7764b..1f5d8a0361 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -171,13 +171,13 @@ def _add_additional_network_options(parser): def _get_attrs_compute(client_manager, parsed_args): attrs = {} if parsed_args.name is not None: - attrs['label'] = str(parsed_args.name) + attrs['name'] = str(parsed_args.name) if parsed_args.share: - attrs['share_address'] = True + attrs['share_subnet'] = True if parsed_args.no_share: - attrs['share_address'] = False + attrs['share_subnet'] = False if parsed_args.subnet is not None: - attrs['cidr'] = parsed_args.subnet + attrs['subnet'] = parsed_args.subnet return attrs @@ -302,9 +302,9 @@ def take_action_network(self, client, parsed_args): def take_action_compute(self, client, parsed_args): attrs = _get_attrs_compute(self.app.client_manager, parsed_args) - obj = client.networks.create(**attrs) - display_columns, columns = _get_columns(obj._info) - data = utils.get_dict_properties(obj._info, columns) + obj = client.api.network_create(**attrs) + display_columns, columns = _get_columns(obj) + data = utils.get_dict_properties(obj, columns) return (display_columns, data) @@ -330,8 +330,7 @@ def take_action_network(self, client, parsed_args): client.delete_network(obj) def take_action_compute(self, client, parsed_args): - network = utils.find_resource(client.networks, self.r) - client.networks.delete(network.id) + client.api.network_delete(self.r) # TODO(sindhu): Use the SDK resource mapped attribute names once the @@ -552,10 +551,10 @@ def take_action_compute(self, client, parsed_args): 'Subnet', ) - data = client.networks.list() + data = client.api.network_list() return (column_headers, - (utils.get_item_properties( + (utils.get_dict_properties( s, columns, formatters=_formatters, ) for s in data)) @@ -683,10 +682,7 @@ def take_action_network(self, client, parsed_args): return (display_columns, data) def take_action_compute(self, client, parsed_args): - obj = utils.find_resource( - client.networks, - parsed_args.network, - ) - display_columns, columns = _get_columns(obj._info) - data = utils.get_dict_properties(obj._info, columns) + obj = client.api.network_find(parsed_args.network) + display_columns, columns = _get_columns(obj) + data = utils.get_dict_properties(obj, columns) return (display_columns, data) diff --git a/openstackclient/tests/unit/api/test_compute_v2.py b/openstackclient/tests/unit/api/test_compute_v2.py index bb86409435..7a30223c5a 100644 --- a/openstackclient/tests/unit/api/test_compute_v2.py +++ b/openstackclient/tests/unit/api/test_compute_v2.py @@ -145,6 +145,157 @@ def test_floating_ip_list(self): self.assertEqual(self.LIST_FLOATING_IP_RESP, ret) +class TestNetwork(TestComputeAPIv2): + + FAKE_NETWORK_RESP = { + 'id': '1', + 'label': 'label1', + 'cidr': '1.2.3.0/24', + } + + FAKE_NETWORK_RESP_2 = { + 'id': '2', + 'label': 'label2', + 'cidr': '4.5.6.0/24', + } + + LIST_NETWORK_RESP = [ + FAKE_NETWORK_RESP, + FAKE_NETWORK_RESP_2, + ] + + def test_network_create_default(self): + self.requests_mock.register_uri( + 'POST', + FAKE_URL + '/os-tenant-networks', + json={'network': self.FAKE_NETWORK_RESP}, + status_code=200, + ) + ret = self.api.network_create('label1') + self.assertEqual(self.FAKE_NETWORK_RESP, ret) + + def test_network_create_options(self): + self.requests_mock.register_uri( + 'POST', + FAKE_URL + '/os-tenant-networks', + json={'network': self.FAKE_NETWORK_RESP}, + status_code=200, + ) + ret = self.api.network_create( + name='label1', + subnet='1.2.3.0/24', + ) + self.assertEqual(self.FAKE_NETWORK_RESP, ret) + + def test_network_delete_id(self): + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/os-tenant-networks/1', + json={'network': self.FAKE_NETWORK_RESP}, + status_code=200, + ) + self.requests_mock.register_uri( + 'DELETE', + FAKE_URL + '/os-tenant-networks/1', + status_code=202, + ) + ret = self.api.network_delete('1') + self.assertEqual(202, ret.status_code) + self.assertEqual("", ret.text) + + def test_network_delete_name(self): + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/os-tenant-networks/label1', + status_code=404, + ) + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/os-tenant-networks', + json={'networks': self.LIST_NETWORK_RESP}, + status_code=200, + ) + self.requests_mock.register_uri( + 'DELETE', + FAKE_URL + '/os-tenant-networks/1', + status_code=202, + ) + ret = self.api.network_delete('label1') + self.assertEqual(202, ret.status_code) + self.assertEqual("", ret.text) + + def test_network_delete_not_found(self): + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/os-tenant-networks/label3', + status_code=404, + ) + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/os-tenant-networks', + json={'networks': self.LIST_NETWORK_RESP}, + status_code=200, + ) + self.assertRaises( + osc_lib_exceptions.NotFound, + self.api.network_delete, + 'label3', + ) + + def test_network_find_id(self): + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/os-tenant-networks/1', + json={'network': self.FAKE_NETWORK_RESP}, + status_code=200, + ) + ret = self.api.network_find('1') + self.assertEqual(self.FAKE_NETWORK_RESP, ret) + + def test_network_find_name(self): + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/os-tenant-networks/label2', + status_code=404, + ) + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/os-tenant-networks', + json={'networks': self.LIST_NETWORK_RESP}, + status_code=200, + ) + ret = self.api.network_find('label2') + self.assertEqual(self.FAKE_NETWORK_RESP_2, ret) + + def test_network_find_not_found(self): + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/os-tenant-networks/label3', + status_code=404, + ) + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/os-tenant-networks', + json={'networks': self.LIST_NETWORK_RESP}, + status_code=200, + ) + self.assertRaises( + osc_lib_exceptions.NotFound, + self.api.network_find, + 'label3', + ) + + def test_network_list_no_options(self): + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/os-tenant-networks', + json={'networks': self.LIST_NETWORK_RESP}, + status_code=200, + ) + ret = self.api.network_list() + self.assertEqual(self.LIST_NETWORK_RESP, ret) + + class TestSecurityGroup(TestComputeAPIv2): FAKE_SECURITY_GROUP_RESP = { diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index df674cd80e..ff50f4fe88 100644 --- a/openstackclient/tests/unit/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -190,9 +190,6 @@ def __init__(self, **kwargs): self.floating_ip_pools = mock.Mock() self.floating_ip_pools.resource_class = fakes.FakeResource(None, {}) - self.networks = mock.Mock() - self.networks.resource_class = fakes.FakeResource(None, {}) - self.keypairs = mock.Mock() self.keypairs.resource_class = fakes.FakeResource(None, {}) @@ -1155,10 +1152,7 @@ def create_one_network(attrs=None): # Overwrite default attributes. network_attrs.update(attrs) - network = fakes.FakeResource(info=copy.deepcopy(network_attrs), - loaded=True) - - return network + return network_attrs @staticmethod def create_networks(attrs=None, count=2): diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 71288a3172..cde43d3222 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -91,9 +91,6 @@ class TestServerAddFixedIP(TestServer): def setUp(self): super(TestServerAddFixedIP, self).setUp() - # Get a shortcut to the compute client ServerManager Mock - self.networks_mock = self.app.client_manager.compute.networks - # Get the command object to test self.cmd = server.AddFixedIP(self.app, None) @@ -105,25 +102,30 @@ def setUp(self): def _test_server_add_fixed_ip(self, extralist, fixed_ip_address): servers = self.setup_servers_mock(count=1) network = compute_fakes.FakeNetwork.create_one_network() - self.networks_mock.get.return_value = network + with mock.patch( + 'openstackclient.api.compute_v2.APIv2.network_find' + ) as net_mock: + net_mock.return_value = network - arglist = [ - servers[0].id, - network.id, - ] + extralist - verifylist = [ - ('server', servers[0].id), - ('network', network.id), - ('fixed_ip_address', fixed_ip_address) - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) + arglist = [ + servers[0].id, + network['id'], + ] + extralist + verifylist = [ + ('server', servers[0].id), + ('network', network['id']), + ('fixed_ip_address', fixed_ip_address), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) - servers[0].interface_attach.assert_called_once_with( - port_id=None, net_id=network.id, fixed_ip=fixed_ip_address - ) - self.assertIsNone(result) + servers[0].interface_attach.assert_called_once_with( + port_id=None, + net_id=network['id'], + fixed_ip=fixed_ip_address, + ) + self.assertIsNone(result) def test_server_add_fixed_ip(self): self._test_server_add_fixed_ip([], None) @@ -138,9 +140,6 @@ class TestServerAddFloatingIP(TestServer): def setUp(self): super(TestServerAddFloatingIP, self).setUp() - # Get a shortcut to the compute client ServerManager Mock - self.networks_mock = self.app.client_manager.compute.networks - # Get the command object to test self.cmd = server.AddFloatingIP(self.app, None) diff --git a/openstackclient/tests/unit/network/v2/test_network_compute.py b/openstackclient/tests/unit/network/v2/test_network_compute.py index c2ec2f378b..25beb8595e 100644 --- a/openstackclient/tests/unit/network/v2/test_network_compute.py +++ b/openstackclient/tests/unit/network/v2/test_network_compute.py @@ -32,6 +32,9 @@ def setUp(self): self.compute = self.app.client_manager.compute +@mock.patch( + 'openstackclient.api.compute_v2.APIv2.network_create' +) class TestCreateNetworkCompute(TestNetworkCompute): # The network to create. @@ -73,38 +76,38 @@ class TestCreateNetworkCompute(TestNetworkCompute): ) data = ( - _network.bridge, - _network.bridge_interface, - _network.broadcast, - _network.cidr, - _network.cidr_v6, - _network.created_at, - _network.deleted, - _network.deleted_at, - _network.dhcp_server, - _network.dhcp_start, - _network.dns1, - _network.dns2, - _network.enable_dhcp, - _network.gateway, - _network.gateway_v6, - _network.host, - _network.id, - _network.injected, - _network.label, - _network.mtu, - _network.multi_host, - _network.netmask, - _network.netmask_v6, - _network.priority, - _network.project_id, - _network.rxtx_base, - _network.share_address, - _network.updated_at, - _network.vlan, - _network.vpn_private_address, - _network.vpn_public_address, - _network.vpn_public_port, + _network['bridge'], + _network['bridge_interface'], + _network['broadcast'], + _network['cidr'], + _network['cidr_v6'], + _network['created_at'], + _network['deleted'], + _network['deleted_at'], + _network['dhcp_server'], + _network['dhcp_start'], + _network['dns1'], + _network['dns2'], + _network['enable_dhcp'], + _network['gateway'], + _network['gateway_v6'], + _network['host'], + _network['id'], + _network['injected'], + _network['label'], + _network['mtu'], + _network['multi_host'], + _network['netmask'], + _network['netmask_v6'], + _network['priority'], + _network['project_id'], + _network['rxtx_base'], + _network['share_address'], + _network['updated_at'], + _network['vlan'], + _network['vpn_private_address'], + _network['vpn_public_address'], + _network['vpn_public_port'], ) def setUp(self): @@ -112,40 +115,48 @@ def setUp(self): self.app.client_manager.network_endpoint_enabled = False - self.compute.networks.create.return_value = self._network - # Get the command object to test self.cmd = network.CreateNetwork(self.app, None) - def test_create_no_options(self): + def test_network_create_no_options(self, net_mock): + net_mock.return_value = self._network arglist = [] verifylist = [] # Missing required args should raise exception here - self.assertRaises(tests_utils.ParserException, self.check_parser, - self.cmd, arglist, verifylist) - - def test_create_default_options(self): + self.assertRaises( + tests_utils.ParserException, + self.check_parser, + self.cmd, + arglist, + verifylist, + ) + + def test_network_create_default_options(self, net_mock): + net_mock.return_value = self._network arglist = [ - "--subnet", self._network.cidr, - self._network.label, + "--subnet", self._network['cidr'], + self._network['label'], ] verifylist = [ - ('subnet', self._network.cidr), - ('name', self._network.label), + ('subnet', self._network['cidr']), + ('name', self._network['label']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.compute.networks.create.assert_called_once_with(**{ - 'cidr': self._network.cidr, - 'label': self._network.label, + net_mock.assert_called_once_with(**{ + 'subnet': self._network['cidr'], + 'name': self._network['label'], }) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) +@mock.patch( + 'openstackclient.api.compute_v2.APIv2.network_delete' +) class TestDeleteNetworkCompute(TestNetworkCompute): def setUp(self): @@ -156,34 +167,35 @@ def setUp(self): # The networks to delete self._networks = compute_fakes.FakeNetwork.create_networks(count=3) - self.compute.networks.delete.return_value = None - # Return value of utils.find_resource() - self.compute.networks.get = \ + self.compute.api.network_find = \ compute_fakes.FakeNetwork.get_networks(networks=self._networks) # Get the command object to test self.cmd = network.DeleteNetwork(self.app, None) - def test_delete_one_network(self): + def test_network_delete_one(self, net_mock): + net_mock.return_value = mock.Mock(return_value=None) arglist = [ - self._networks[0].label, + self._networks[0]['label'], ] verifylist = [ - ('network', [self._networks[0].label]), + ('network', [self._networks[0]['label']]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.compute.networks.delete.assert_called_once_with( - self._networks[0].id) + net_mock.assert_called_once_with( + self._networks[0]['label'], + ) self.assertIsNone(result) - def test_delete_multiple_networks(self): + def test_network_delete_multi(self, net_mock): + net_mock.return_value = mock.Mock(return_value=None) arglist = [] for n in self._networks: - arglist.append(n.label) + arglist.append(n['id']) verifylist = [ ('network', arglist), ] @@ -193,53 +205,40 @@ def test_delete_multiple_networks(self): calls = [] for n in self._networks: - calls.append(call(n.id)) - self.compute.networks.delete.assert_has_calls(calls) + calls.append(call(n['id'])) + net_mock.assert_has_calls(calls) self.assertIsNone(result) - def test_delete_multiple_networks_exception(self): + def test_network_delete_multi_with_exception(self, net_mock): + net_mock.return_value = mock.Mock(return_value=None) + net_mock.side_effect = ([ + mock.Mock(return_value=None), + exceptions.CommandError, + ]) arglist = [ - self._networks[0].id, + self._networks[0]['id'], 'xxxx-yyyy-zzzz', - self._networks[1].id, + self._networks[1]['id'], ] verifylist = [ ('network', arglist), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # Fake exception in utils.find_resource() - # In compute v2, we use utils.find_resource() to find a network. - # It calls get() several times, but find() only one time. So we - # choose to fake get() always raise exception, then pass through. - # And fake find() to find the real network or not. - self.compute.networks.get.side_effect = Exception() - ret_find = [ - self._networks[0], - Exception(), - self._networks[1], - ] - self.compute.networks.find.side_effect = ret_find + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('2 of 3 networks failed to delete.', str(e)) - # Fake exception in delete() - ret_delete = [ - None, - Exception(), - ] - self.compute.networks.delete = mock.Mock(side_effect=ret_delete) - - self.assertRaises(exceptions.CommandError, self.cmd.take_action, - parsed_args) - - # The second call of utils.find_resource() should fail. So delete() - # was only called twice. - calls = [ - call(self._networks[0].id), - call(self._networks[1].id), - ] - self.compute.networks.delete.assert_has_calls(calls) + net_mock.assert_any_call(self._networks[0]['id']) + net_mock.assert_any_call(self._networks[1]['id']) + net_mock.assert_any_call('xxxx-yyyy-zzzz') +@mock.patch( + 'openstackclient.api.compute_v2.APIv2.network_list' +) class TestListNetworkCompute(TestNetworkCompute): # The networks going to be listed up. @@ -254,9 +253,9 @@ class TestListNetworkCompute(TestNetworkCompute): data = [] for net in _networks: data.append(( - net.id, - net.label, - net.cidr, + net['id'], + net['label'], + net['cidr'], )) def setUp(self): @@ -264,12 +263,11 @@ def setUp(self): self.app.client_manager.network_endpoint_enabled = False - self.compute.networks.list.return_value = self._networks - # Get the command object to test self.cmd = network.ListNetwork(self.app, None) - def test_network_list_no_options(self): + def test_network_list_no_options(self, net_mock): + net_mock.return_value = self._networks arglist = [] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -279,11 +277,14 @@ def test_network_list_no_options(self): # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.compute.networks.list.assert_called_once_with() + net_mock.assert_called_once_with() self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) +@mock.patch( + 'openstackclient.api.compute_v2.APIv2.network_find' +) class TestShowNetworkCompute(TestNetworkCompute): # The network to show. @@ -325,38 +326,38 @@ class TestShowNetworkCompute(TestNetworkCompute): ) data = ( - _network.bridge, - _network.bridge_interface, - _network.broadcast, - _network.cidr, - _network.cidr_v6, - _network.created_at, - _network.deleted, - _network.deleted_at, - _network.dhcp_server, - _network.dhcp_start, - _network.dns1, - _network.dns2, - _network.enable_dhcp, - _network.gateway, - _network.gateway_v6, - _network.host, - _network.id, - _network.injected, - _network.label, - _network.mtu, - _network.multi_host, - _network.netmask, - _network.netmask_v6, - _network.priority, - _network.project_id, - _network.rxtx_base, - _network.share_address, - _network.updated_at, - _network.vlan, - _network.vpn_private_address, - _network.vpn_public_address, - _network.vpn_public_port, + _network['bridge'], + _network['bridge_interface'], + _network['broadcast'], + _network['cidr'], + _network['cidr_v6'], + _network['created_at'], + _network['deleted'], + _network['deleted_at'], + _network['dhcp_server'], + _network['dhcp_start'], + _network['dns1'], + _network['dns2'], + _network['enable_dhcp'], + _network['gateway'], + _network['gateway_v6'], + _network['host'], + _network['id'], + _network['injected'], + _network['label'], + _network['mtu'], + _network['multi_host'], + _network['netmask'], + _network['netmask_v6'], + _network['priority'], + _network['project_id'], + _network['rxtx_base'], + _network['share_address'], + _network['updated_at'], + _network['vlan'], + _network['vpn_private_address'], + _network['vpn_public_address'], + _network['vpn_public_port'], ) def setUp(self): @@ -364,29 +365,34 @@ def setUp(self): self.app.client_manager.network_endpoint_enabled = False - # Return value of utils.find_resource() - self.compute.networks.get.return_value = self._network - # Get the command object to test self.cmd = network.ShowNetwork(self.app, None) - def test_show_no_options(self): + def test_show_no_options(self, net_mock): + net_mock.return_value = self._network arglist = [] verifylist = [] - self.assertRaises(tests_utils.ParserException, self.check_parser, - self.cmd, arglist, verifylist) + self.assertRaises( + tests_utils.ParserException, + self.check_parser, + self.cmd, + arglist, + verifylist, + ) - def test_show_all_options(self): + def test_show_all_options(self, net_mock): + net_mock.return_value = self._network arglist = [ - self._network.label, + self._network['label'], ] verifylist = [ - ('network', self._network.label), + ('network', self._network['label']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) + net_mock.assert_called_once_with(self._network['label']) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) From 107cad200a15a3131525436b483dbef5e88b9508 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Mon, 17 Apr 2017 15:03:14 -0500 Subject: [PATCH 1654/3095] Low-level Compute v2 API: floating ip pool api.compute.APIv2 floating ip pool function. novaclient 8.0 is now released without support for the previously deprecated nova-net functions, so include a new low-level REST implementation of the removed APIs. Also includes a handful of cleanups that the previous security group and floating IP reviews missed. Change-Id: I20116ec4fc1113857d8d917bfb30fa3170d05b9f --- openstackclient/api/compute_v2.py | 19 ++++++++++++++++++- .../network/v2/floating_ip_pool.py | 4 ++-- .../tests/unit/api/test_compute_v2.py | 18 ++++++++++++++++++ .../tests/unit/compute/v2/fakes.py | 15 +-------------- .../network/v2/test_floating_ip_compute.py | 4 ---- .../v2/test_floating_ip_pool_compute.py | 19 +++++++++++-------- 6 files changed, 50 insertions(+), 29 deletions(-) diff --git a/openstackclient/api/compute_v2.py b/openstackclient/api/compute_v2.py index 3141728acf..650226713c 100644 --- a/openstackclient/api/compute_v2.py +++ b/openstackclient/api/compute_v2.py @@ -168,13 +168,30 @@ def floating_ip_list( https://developer.openstack.org/api-ref/compute/#show-floating-ip-address-details :returns: - list of security groups names + list of floating IPs """ url = "/os-floating-ips" return self.list(url)["floating_ips"] + # Floating IP Pools + + def floating_ip_pool_list( + self, + ): + """Get floating IP pools + + https://developer.openstack.org/api-ref/compute/?expanded=#list-floating-ip-pools + + :returns: + list of floating IP pools + """ + + url = "/os-floating-ip-pools" + + return self.list(url)["floating_ip_pools"] + # Networks def network_create( diff --git a/openstackclient/network/v2/floating_ip_pool.py b/openstackclient/network/v2/floating_ip_pool.py index 73e94ead8b..ebb15da8d7 100644 --- a/openstackclient/network/v2/floating_ip_pool.py +++ b/openstackclient/network/v2/floating_ip_pool.py @@ -34,10 +34,10 @@ def take_action_compute(self, client, parsed_args): columns = ( 'Name', ) - data = client.floating_ip_pools.list() + data = client.api.floating_ip_pool_list() return (columns, - (utils.get_item_properties( + (utils.get_dict_properties( s, columns, ) for s in data)) diff --git a/openstackclient/tests/unit/api/test_compute_v2.py b/openstackclient/tests/unit/api/test_compute_v2.py index 7a30223c5a..22ee489967 100644 --- a/openstackclient/tests/unit/api/test_compute_v2.py +++ b/openstackclient/tests/unit/api/test_compute_v2.py @@ -145,6 +145,24 @@ def test_floating_ip_list(self): self.assertEqual(self.LIST_FLOATING_IP_RESP, ret) +class TestFloatingIPPool(TestComputeAPIv2): + + LIST_FLOATING_IP_POOL_RESP = [ + {"name": "tide"}, + {"name": "press"}, + ] + + def test_floating_ip_pool_list(self): + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/os-floating-ip-pools', + json={'floating_ip_pools': self.LIST_FLOATING_IP_POOL_RESP}, + status_code=200, + ) + ret = self.api.floating_ip_pool_list() + self.assertEqual(self.LIST_FLOATING_IP_POOL_RESP, ret) + + class TestNetwork(TestComputeAPIv2): FAKE_NETWORK_RESP = { diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index ff50f4fe88..e6af947684 100644 --- a/openstackclient/tests/unit/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -181,15 +181,6 @@ def __init__(self, **kwargs): self.hypervisors_stats = mock.Mock() self.hypervisors_stats.resource_class = fakes.FakeResource(None, {}) - self.security_group_rules = mock.Mock() - self.security_group_rules.resource_class = fakes.FakeResource(None, {}) - - self.floating_ips = mock.Mock() - self.floating_ips.resource_class = fakes.FakeResource(None, {}) - - self.floating_ip_pools = mock.Mock() - self.floating_ip_pools.resource_class = fakes.FakeResource(None, {}) - self.keypairs = mock.Mock() self.keypairs.resource_class = fakes.FakeResource(None, {}) @@ -1074,11 +1065,7 @@ def create_one_floating_ip_pool(attrs=None): # Overwrite default attributes. floating_ip_pool_attrs.update(attrs) - floating_ip_pool = fakes.FakeResource( - info=copy.deepcopy(floating_ip_pool_attrs), - loaded=True) - - return floating_ip_pool + return floating_ip_pool_attrs @staticmethod def create_floating_ip_pools(attrs=None, count=2): diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip_compute.py b/openstackclient/tests/unit/network/v2/test_floating_ip_compute.py index 0d58c158b2..df47e63e91 100644 --- a/openstackclient/tests/unit/network/v2/test_floating_ip_compute.py +++ b/openstackclient/tests/unit/network/v2/test_floating_ip_compute.py @@ -103,10 +103,6 @@ def setUp(self): self.app.client_manager.network_endpoint_enabled = False - # Return value of utils.find_resource() - self.compute.floating_ips.get = ( - compute_fakes.FakeFloatingIP.get_floating_ips(self._floating_ips)) - # Get the command object to test self.cmd = fip.DeleteFloatingIP(self.app, None) diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip_pool_compute.py b/openstackclient/tests/unit/network/v2/test_floating_ip_pool_compute.py index 8db2143035..591f58ca4e 100644 --- a/openstackclient/tests/unit/network/v2/test_floating_ip_pool_compute.py +++ b/openstackclient/tests/unit/network/v2/test_floating_ip_pool_compute.py @@ -11,6 +11,8 @@ # under the License. # +import mock + from openstackclient.network.v2 import floating_ip_pool from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes @@ -26,10 +28,13 @@ def setUp(self): self.compute = self.app.client_manager.compute +@mock.patch( + 'openstackclient.api.compute_v2.APIv2.floating_ip_pool_list' +) class TestListFloatingIPPoolCompute(TestFloatingIPPoolCompute): # The floating ip pools to list up - floating_ip_pools = \ + _floating_ip_pools = \ compute_fakes.FakeFloatingIPPool.create_floating_ip_pools(count=3) columns = ( @@ -37,9 +42,9 @@ class TestListFloatingIPPoolCompute(TestFloatingIPPoolCompute): ) data = [] - for pool in floating_ip_pools: + for pool in _floating_ip_pools: data.append(( - pool.name, + pool['name'], )) def setUp(self): @@ -47,19 +52,17 @@ def setUp(self): self.app.client_manager.network_endpoint_enabled = False - self.compute.floating_ip_pools.list.return_value = \ - self.floating_ip_pools - # Get the command object to test self.cmd = floating_ip_pool.ListFloatingIPPool(self.app, None) - def test_floating_ip_list(self): + def test_floating_ip_list(self, fipp_mock): + fipp_mock.return_value = self._floating_ip_pools arglist = [] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.compute.floating_ip_pools.list.assert_called_once_with() + fipp_mock.assert_called_once_with() self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) From e611aa6314b22e3b1f8d920fd666746499afce45 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 19 Apr 2017 10:29:53 +0100 Subject: [PATCH 1655/3095] Explicitly set 'builders' option An upcoming release of pbr will require explicitly stating which builders are requested, rather than defaulting to html and man. Head off any potential impact this may cause by explicitly setting this configuration now. Change-Id: I243ca33f5459009f9a9670ec5e0ad67b04760f35 --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index 92f7bbb7b0..99201b9e34 100644 --- a/setup.cfg +++ b/setup.cfg @@ -702,6 +702,7 @@ autodoc_tree_excludes = openstackclient/tests/* [build_sphinx] +builders = html,man all-files = 1 warning-is-error = 1 source-dir = doc/source From db6081fb802559cdb6623bbc5dbff597cd287b07 Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Wed, 19 Apr 2017 21:47:57 +0800 Subject: [PATCH 1656/3095] Fix network list functional test An error in network func test broke our CI, looks like the "is_default" should be "False" by default for now. Change-Id: I021eb8abd9bdf55c7c06031152c107312f104b34 --- openstackclient/tests/functional/network/v2/test_network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/tests/functional/network/v2/test_network.py b/openstackclient/tests/functional/network/v2/test_network.py index 0e10bfce1a..02fa42f242 100644 --- a/openstackclient/tests/functional/network/v2/test_network.py +++ b/openstackclient/tests/functional/network/v2/test_network.py @@ -181,7 +181,7 @@ def test_network_list(self): cmd_output["shared"], ) self.assertEqual( - None, + False, cmd_output["is_default"], ) self.assertEqual( From 6dc17bb1fbb0b265223aff90a585be43830b1ac1 Mon Sep 17 00:00:00 2001 From: David Rabel Date: Fri, 21 Apr 2017 09:23:44 +0000 Subject: [PATCH 1657/3095] Make test_server.py more elegant and simple Use only one line for mocking network resources like ports and networks in test_server.py. Change-Id: I451a504c3afdd365e64d66079516ec6308c206db Depends-On: I624b1bc557a195bdf8a7c5a32dc0e72a6fa8b075 --- .../tests/unit/compute/v2/test_server.py | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 600b872e54..3ffe642810 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -413,24 +413,18 @@ def test_server_create_with_network(self): network_client = self.app.client_manager.network network_client.find_network = find_network network_client.find_port = find_port - network_resource = mock.Mock() - network_resource.id = 'net1_uuid' - port1_resource = mock.Mock() - port1_resource.id = 'port1_uuid' - port2_resource = mock.Mock() - port2_resource.id = 'port2_uuid' + network_resource = mock.Mock(id='net1_uuid') + port1_resource = mock.Mock(id='port1_uuid') + port2_resource = mock.Mock(id='port2_uuid') find_network.return_value = network_resource find_port.side_effect = (lambda port_id, ignore_missing: {"port1": port1_resource, "port2": port2_resource}[port_id]) # Mock sdk APIs. - _network = mock.Mock() - _network.id = 'net1_uuid' - _port1 = mock.Mock() - _port1.id = 'port1_uuid' - _port2 = mock.Mock() - _port2.id = 'port2_uuid' + _network = mock.Mock(id='net1_uuid') + _port1 = mock.Mock(id='port1_uuid') + _port2 = mock.Mock(id='port2_uuid') find_network = mock.Mock() find_port = mock.Mock() find_network.return_value = _network @@ -609,8 +603,7 @@ def test_server_create_with_conflict_network_options(self): find_port = mock.Mock() network_client = self.app.client_manager.network network_client.find_port = find_port - port_resource = mock.Mock() - port_resource.id = 'port1_uuid' + port_resource = mock.Mock(id='port1_uuid') find_port.return_value = port_resource self.assertRaises(exceptions.CommandError, From af435ee0a80d9392da004d80efea0c4e090e8208 Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Mon, 24 Apr 2017 15:06:26 +0800 Subject: [PATCH 1658/3095] Remove ipdb installation in tox debug section The lastest ipdb depends on ipython 6.0, but ipython 6.0 only can be installed in python 3.3 and above, see http://paste.openstack.org/show/607632/ . If we try to run "tox -e debug" in python2.7, the install error is raised and block the function. Remove the ipdb installation, it's not necessary, we can use pdb to replace. Change-Id: Ib47bb5925b7a5b1d3a319b58fa219c1b57dccb93 --- tox.ini | 1 - 1 file changed, 1 deletion(-) diff --git a/tox.ini b/tox.ini index d3cafae531..ac5c6593fe 100644 --- a/tox.ini +++ b/tox.ini @@ -75,7 +75,6 @@ commands = [testenv:debug] passenv = OS_* commands = - pip install -q -U ipdb oslo_debug_helper -t openstackclient/tests {posargs} [testenv:docs] From ef99f444628282d06feae04514bd2a6328d87b93 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Mon, 24 Apr 2017 18:57:05 -0500 Subject: [PATCH 1659/3095] Improve no-auth path The commands that do not require authentication sometimes still need to call ClientManager.is_network_endpoint_enabled() to see if Neutron is available. Optimize the paths a bit to skip auth when it is not necessary; the upshot is Neutron will be assumed in these cases now. This gets a LOT cleaner when it appears is a future osc-lib. Change-Id: Ifaddc57dfa192bde04d0482e2cdcce111313a22a --- openstackclient/common/clientmanager.py | 15 ++++++++++++++- openstackclient/shell.py | 6 ++++++ .../tests/unit/common/test_clientmanager.py | 4 +++- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index 27f3b70552..67912f0cd0 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -41,6 +41,9 @@ class ClientManager(clientmanager.ClientManager): # A simple incrementing version for the plugin to know what is available PLUGIN_INTERFACE_VERSION = "2" + # Let the commands set this + _auth_required = False + def __init__( self, cli_options=None, @@ -72,7 +75,10 @@ def setup_auth(self): # because openstack_config is an optional argument to # CloudConfig.__init__() and we'll die if it was not # passed. - if self._cli_options._openstack_config is not None: + if ( + self._auth_required and + self._cli_options._openstack_config is not None + ): self._cli_options._openstack_config._pw_callback = \ shell.prompt_for_password try: @@ -85,6 +91,13 @@ def setup_auth(self): return super(ClientManager, self).setup_auth() + @property + def auth_ref(self): + if not self._auth_required: + return None + else: + return super(ClientManager, self).auth_ref + def _fallback_load_auth_plugin(self, e): # NOTES(RuiChen): Hack to avoid auth plugins choking on data they don't # expect, delete fake token and endpoint, then try to diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 8fdf0b61f2..2e384f8fc3 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -182,6 +182,12 @@ def prepare_to_run_command(self, cmd): # get_one_Cloud()'s validation to avoid loading plugins validate = cmd.auth_required + # Force skipping auth for commands that do not need it + # NOTE(dtroyer): This is here because ClientManager does not have + # visibility into the Command object to get + # auth_required. It needs to move into osc-lib + self.client_manager._auth_required = cmd.auth_required + # Validate auth options self.cloud = self.cloud_config.get_one_cloud( cloud=self.options.cloud, diff --git a/openstackclient/tests/unit/common/test_clientmanager.py b/openstackclient/tests/unit/common/test_clientmanager.py index 7f82c35d94..f15f9af1ff 100644 --- a/openstackclient/tests/unit/common/test_clientmanager.py +++ b/openstackclient/tests/unit/common/test_clientmanager.py @@ -66,4 +66,6 @@ def test_client_manager_network_endpoint_disabled(self): ) self.assertFalse(client_manager.is_service_available('network')) - self.assertFalse(client_manager.is_network_endpoint_enabled()) + # This is True because ClientManager.auth_ref returns None in this + # test; "no service catalog" means use Network API by default now + self.assertTrue(client_manager.is_network_endpoint_enabled()) From 1c49a1f01da73b8eed701809de88b408e738dfed Mon Sep 17 00:00:00 2001 From: Huanxuan Ao Date: Wed, 8 Mar 2017 10:01:16 +0800 Subject: [PATCH 1660/3095] Fix NoneType error for volume snapshot create command In volume snapshot command, is the same as when --volume is not specified, but cannot be None, so when is not specified ( is None), a NoneType error appears. So make no longer optional, it should be always present. Change-Id: I3d9f10753a8ef601e70816421c160598e2cc811f Closes-bug: #1659894 --- doc/source/backwards-incompatible.rst | 15 ++++++++++ .../command-objects/volume-snapshot.rst | 2 +- .../tests/unit/volume/v1/test_snapshot.py | 22 +++++--------- .../tests/unit/volume/v2/test_snapshot.py | 29 +++++++------------ openstackclient/volume/v1/volume_snapshot.py | 3 +- openstackclient/volume/v2/volume_snapshot.py | 3 +- .../notes/bug-1659894-4518b10615498ba9.yaml | 6 ++++ 7 files changed, 42 insertions(+), 38 deletions(-) create mode 100644 releasenotes/notes/bug-1659894-4518b10615498ba9.yaml diff --git a/doc/source/backwards-incompatible.rst b/doc/source/backwards-incompatible.rst index 22c300de05..0202e8a5a1 100644 --- a/doc/source/backwards-incompatible.rst +++ b/doc/source/backwards-incompatible.rst @@ -27,6 +27,21 @@ Backwards Incompatible Changes .. * Remove in: <5.0> .. * Commit: +Release 3.10 +------------ + +1. The positional argument ```` of the ``volume snapshot create`` + command is no longer optional. + + Previously when the ``--volume`` option was + present ```` defaulted to the ``--volume`` value. When the + ``--volume`` option is not present now it defaults to the value of + ````. + + * As of: 3.10 + * Bug: 1659894 + * Commit: https://review.openstack.org/440497 + Release 3.0 ----------- diff --git a/doc/source/command-objects/volume-snapshot.rst b/doc/source/command-objects/volume-snapshot.rst index 67db62f2b8..30cc77cc57 100644 --- a/doc/source/command-objects/volume-snapshot.rst +++ b/doc/source/command-objects/volume-snapshot.rst @@ -49,7 +49,7 @@ Create new volume snapshot .. _volume_snapshot_create-snapshot-name: .. describe:: - Name of the new snapshot (default to None) + Name of the new snapshot volume snapshot delete ---------------------- diff --git a/openstackclient/tests/unit/volume/v1/test_snapshot.py b/openstackclient/tests/unit/volume/v1/test_snapshot.py index 87a62b0a9b..70b55ce235 100644 --- a/openstackclient/tests/unit/volume/v1/test_snapshot.py +++ b/openstackclient/tests/unit/volume/v1/test_snapshot.py @@ -18,6 +18,7 @@ from osc_lib import exceptions from osc_lib import utils +from openstackclient.tests.unit import utils as tests_utils from openstackclient.tests.unit.volume.v1 import fakes as volume_fakes from openstackclient.volume.v1 import volume_snapshot @@ -98,26 +99,17 @@ def test_snapshot_create(self): def test_snapshot_create_without_name(self): arglist = [ "--volume", self.new_snapshot.volume_id, - "--description", self.new_snapshot.display_description, - "--force" ] verifylist = [ ("volume", self.new_snapshot.volume_id), - ("description", self.new_snapshot.display_description), - ("force", True) ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.snapshots_mock.create.assert_called_with( - self.new_snapshot.volume_id, - True, - None, - self.new_snapshot.display_description, + self.assertRaises( + tests_utils.ParserException, + self.check_parser, + self.cmd, + arglist, + verifylist, ) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) def test_snapshot_create_without_volume(self): arglist = [ diff --git a/openstackclient/tests/unit/volume/v2/test_snapshot.py b/openstackclient/tests/unit/volume/v2/test_snapshot.py index 1ad97e858a..16d0602b7c 100644 --- a/openstackclient/tests/unit/volume/v2/test_snapshot.py +++ b/openstackclient/tests/unit/volume/v2/test_snapshot.py @@ -20,6 +20,7 @@ from osc_lib import utils from openstackclient.tests.unit.identity.v3 import fakes as project_fakes +from openstackclient.tests.unit import utils as tests_utils from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes from openstackclient.volume.v2 import volume_snapshot @@ -107,27 +108,17 @@ def test_snapshot_create(self): def test_snapshot_create_without_name(self): arglist = [ "--volume", self.new_snapshot.volume_id, - "--description", self.new_snapshot.description, - "--force" ] verifylist = [ ("volume", self.new_snapshot.volume_id), - ("description", self.new_snapshot.description), - ("force", True) ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.snapshots_mock.create.assert_called_with( - self.new_snapshot.volume_id, - force=True, - name=None, - description=self.new_snapshot.description, - metadata=None, + self.assertRaises( + tests_utils.ParserException, + self.check_parser, + self.cmd, + arglist, + verifylist, ) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) def test_snapshot_create_without_volume(self): arglist = [ @@ -156,17 +147,19 @@ def test_snapshot_create_without_volume(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) - def test_snapshot_create_without_remote_source(self): + def test_snapshot_create_with_remote_source(self): arglist = [ '--remote-source', 'source-name=test_source_name', '--remote-source', 'source-id=test_source_id', '--volume', self.new_snapshot.volume_id, + self.new_snapshot.name, ] ref_dict = {'source-name': 'test_source_name', 'source-id': 'test_source_id'} verifylist = [ ('remote_source', ref_dict), ('volume', self.new_snapshot.volume_id), + ("snapshot_name", self.new_snapshot.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -175,7 +168,7 @@ def test_snapshot_create_without_remote_source(self): self.snapshots_mock.manage.assert_called_with( volume_id=self.new_snapshot.volume_id, ref=ref_dict, - name=None, + name=self.new_snapshot.name, description=None, metadata=None, ) diff --git a/openstackclient/volume/v1/volume_snapshot.py b/openstackclient/volume/v1/volume_snapshot.py index f22c338b80..3e83da5a7b 100644 --- a/openstackclient/volume/v1/volume_snapshot.py +++ b/openstackclient/volume/v1/volume_snapshot.py @@ -38,8 +38,7 @@ def get_parser(self, prog_name): parser.add_argument( 'snapshot_name', metavar='', - nargs="?", - help=_('Name of the snapshot (default to None)'), + help=_('Name of the new snapshot'), ) parser.add_argument( '--volume', diff --git a/openstackclient/volume/v2/volume_snapshot.py b/openstackclient/volume/v2/volume_snapshot.py index 804c829111..fe9694104a 100644 --- a/openstackclient/volume/v2/volume_snapshot.py +++ b/openstackclient/volume/v2/volume_snapshot.py @@ -38,8 +38,7 @@ def get_parser(self, prog_name): parser.add_argument( "snapshot_name", metavar="", - nargs="?", - help=_("Name of the new snapshot (default to None)") + help=_("Name of the new snapshot"), ) parser.add_argument( "--volume", diff --git a/releasenotes/notes/bug-1659894-4518b10615498ba9.yaml b/releasenotes/notes/bug-1659894-4518b10615498ba9.yaml new file mode 100644 index 0000000000..e1afbb45a2 --- /dev/null +++ b/releasenotes/notes/bug-1659894-4518b10615498ba9.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Now the positional parameter ```` of ``volume snapshot create`` + command is no longer optional, it should be always present. + [Bug `1659894 `_] From d519911c4345c76dfdb8691006877fc5be5d0ec6 Mon Sep 17 00:00:00 2001 From: "jiahui.qiang" Date: Wed, 18 Jan 2017 04:32:11 +0800 Subject: [PATCH 1661/3095] Functional test for subnet_pool Refactor functional tests for testing more command options. Change-Id: I0c9c3b04dd2b79766a8fe82cbc5315c030f4784d --- .../functional/network/v2/test_subnet_pool.py | 305 +++++++++++++++--- 1 file changed, 268 insertions(+), 37 deletions(-) diff --git a/openstackclient/tests/functional/network/v2/test_subnet_pool.py b/openstackclient/tests/functional/network/v2/test_subnet_pool.py index e52f06fc5d..d68ca01cf4 100644 --- a/openstackclient/tests/functional/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/functional/network/v2/test_subnet_pool.py @@ -10,46 +10,277 @@ # License for the specific language governing permissions and limitations # under the License. +import json +import random import uuid from openstackclient.tests.functional import base class SubnetPoolTests(base.TestCase): - """Functional tests for subnet pool. """ - NAME = uuid.uuid4().hex - CREATE_POOL_PREFIX = '10.100.0.0/24' - SET_POOL_PREFIX = '10.100.0.0/16' - HEADERS = ['Name'] - FIELDS = ['name'] - - @classmethod - def setUpClass(cls): - opts = cls.get_opts(cls.FIELDS) - raw_output = cls.openstack('subnet pool create --pool-prefix ' + - cls.CREATE_POOL_PREFIX + ' ' + - cls.NAME + opts) - cls.assertOutput(cls.NAME + '\n', raw_output) - - @classmethod - def tearDownClass(cls): - raw_output = cls.openstack('subnet pool delete ' + cls.NAME) - cls.assertOutput('', raw_output) - - def test_subnet_list(self): - opts = self.get_opts(self.HEADERS) - raw_output = self.openstack('subnet pool list' + opts) - self.assertIn(self.NAME, raw_output) - - def test_subnet_set(self): - self.openstack('subnet pool set --pool-prefix ' + - self.SET_POOL_PREFIX + ' ' + self.NAME) - opts = self.get_opts(['prefixes', 'name']) - raw_output = self.openstack('subnet pool show ' + self.NAME + opts) - self.assertEqual(self.NAME + '\n' + self.SET_POOL_PREFIX + '\n', - raw_output) - - def test_subnet_show(self): - opts = self.get_opts(self.FIELDS) - raw_output = self.openstack('subnet pool show ' + self.NAME + opts) - self.assertEqual(self.NAME + '\n', raw_output) + """Functional tests for subnet pool""" + + def test_subnet_pool_create_delete(self): + """Test create, delete""" + name1 = uuid.uuid4().hex + cmd_output, pool_prefix = self._subnet_pool_create("", name1) + + self.assertEqual( + name1, + cmd_output["name"] + ) + self.assertEqual( + pool_prefix, + cmd_output["prefixes"] + ) + + name2 = uuid.uuid4().hex + cmd_output, pool_prefix = self._subnet_pool_create("", name2) + + self.assertEqual( + name2, + cmd_output["name"] + ) + self.assertEqual( + pool_prefix, + cmd_output["prefixes"] + ) + + del_output = self.openstack( + 'subnet pool delete ' + name1 + ' ' + name2, + ) + self.assertOutput('', del_output) + + def test_subnet_pool_list(self): + """Test create, list filter""" + cmd_output = json.loads(self.openstack('token issue -f json')) + auth_project_id = cmd_output['project_id'] + + cmd_output = json.loads(self.openstack('project list -f json')) + admin_project_id = None + demo_project_id = None + for p in cmd_output: + if p['Name'] == 'admin': + admin_project_id = p['ID'] + if p['Name'] == 'demo': + demo_project_id = p['ID'] + + # Verify assumptions: + # * admin and demo projects are present + # * demo and admin are distinct projects + # * tests run as admin + self.assertIsNotNone(admin_project_id) + self.assertIsNotNone(demo_project_id) + self.assertNotEqual(admin_project_id, demo_project_id) + self.assertEqual(admin_project_id, auth_project_id) + + name1 = uuid.uuid4().hex + name2 = uuid.uuid4().hex + + cmd_output, pool_prefix = self._subnet_pool_create( + '--project ' + demo_project_id + + ' --no-share ', + name1, + ) + self.addCleanup(self.openstack, 'subnet pool delete ' + name1) + self.assertEqual( + name1, + cmd_output["name"], + ) + self.assertEqual( + False, + cmd_output["shared"], + ) + self.assertEqual( + demo_project_id, + cmd_output["project_id"], + ) + self.assertEqual( + pool_prefix, + cmd_output["prefixes"], + ) + + cmd_output, pool_prefix = self._subnet_pool_create( + ' --share ', + name2, + ) + self.addCleanup(self.openstack, 'subnet pool delete ' + name2) + self.assertEqual( + name2, + cmd_output["name"], + ) + self.assertEqual( + True, + cmd_output["shared"], + ) + self.assertEqual( + admin_project_id, + cmd_output["project_id"], + ) + self.assertEqual( + pool_prefix, + cmd_output["prefixes"], + ) + + # Test list --project + cmd_output = json.loads(self.openstack( + 'subnet pool list -f json ' + + '--project ' + demo_project_id + )) + names = [x["Name"] for x in cmd_output] + self.assertIn(name1, names) + self.assertNotIn(name2, names) + + # Test list --share + cmd_output = json.loads(self.openstack( + 'subnet pool list -f json ' + + '--share' + )) + names = [x["Name"] for x in cmd_output] + self.assertNotIn(name1, names) + self.assertIn(name2, names) + + # Test list --name + cmd_output = json.loads(self.openstack( + 'subnet pool list -f json ' + + '--name ' + name1 + )) + names = [x["Name"] for x in cmd_output] + self.assertIn(name1, names) + self.assertNotIn(name2, names) + + # Test list --long + cmd_output = json.loads(self.openstack( + 'subnet pool list -f json ' + + '--long ' + )) + names = [x["Name"] for x in cmd_output] + self.assertIn(name1, names) + self.assertIn(name2, names) + + def test_subnet_pool_set_show(self): + """Test create, set, show, delete""" + + name = uuid.uuid4().hex + new_name = name + "_" + cmd_output, pool_prefix = self._subnet_pool_create( + '--default-prefix-length 16 ' + + '--min-prefix-length 16 ' + + '--max-prefix-length 32 ' + + '--description aaaa ', + name, + ) + + self.addCleanup(self.openstack, 'subnet pool delete ' + new_name) + self.assertEqual( + name, + cmd_output["name"], + ) + self.assertEqual( + 'aaaa', + cmd_output["description"], + ) + self.assertEqual( + pool_prefix, + cmd_output["prefixes"], + ) + self.assertEqual( + 16, + cmd_output["default_prefixlen"], + ) + self.assertEqual( + 16, + cmd_output["min_prefixlen"], + ) + self.assertEqual( + 32, + cmd_output["max_prefixlen"], + ) + + # Test set + cmd_output = self.openstack( + 'subnet pool set ' + + '--name ' + new_name + + ' --description bbbb ' + + ' --pool-prefix 10.110.0.0/16 ' + + '--default-prefix-length 8 ' + + '--min-prefix-length 8 ' + + '--max-prefix-length 16 ' + + name + ) + self.assertOutput('', cmd_output) + + cmd_output = json.loads(self.openstack( + 'subnet pool show -f json ' + + new_name + )) + self.assertEqual( + new_name, + cmd_output["name"], + ) + self.assertEqual( + 'bbbb', + cmd_output["description"], + ) + self.assertInOutput( + "10.110.0.0/16", + cmd_output["prefixes"], + ) + self.assertEqual( + 8, + cmd_output["default_prefixlen"], + ) + self.assertEqual( + 8, + cmd_output["min_prefixlen"], + ) + self.assertEqual( + 16, + cmd_output["max_prefixlen"], + ) + + def _subnet_pool_create(self, cmd, name, is_type_ipv4=True): + """Make a random subnet pool + + :param string cmd: + The options for a subnet pool create command, not including + --pool-prefix and + :param string name: + The name of the subnet pool + :param bool is_type_ipv4: + Creates an IPv4 pool if True, creates an IPv6 pool otherwise + + Try random subnet ranges because we can not determine ahead of time + what subnets are already in use, possibly by another test running in + parallel, try 4 times before failing. + """ + for i in range(4): + # Create a random prefix + if is_type_ipv4: + pool_prefix = ".".join(map( + str, + (random.randint(0, 223) for _ in range(2)), + )) + ".0.0/16" + else: + pool_prefix = ":".join(map( + str, + (hex(random.randint(0, 65535))[2:] for _ in range(6)), + )) + ":0:0/96" + + try: + cmd_output = json.loads(self.openstack( + 'subnet pool create -f json ' + + cmd + ' ' + + '--pool-prefix ' + pool_prefix + ' ' + + name + )) + except Exception: + if (i == 3): + # Raise the exception the last time + raise + pass + else: + # Break and no longer retry if create is sucessful + break + + return cmd_output, pool_prefix From b38261e84bb6ed54ebb99150ea9e9c6a4cc2cc00 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 26 Apr 2017 19:38:28 +0000 Subject: [PATCH 1662/3095] Updated from global requirements Change-Id: Ib3ea4225636bfa815f7cf60481d2adc07077123c --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 9b44113a20..e4054d2ec8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ pbr!=2.1.0,>=2.0.0 # Apache-2.0 six>=1.9.0 # MIT Babel!=2.4.0,>=2.3.4 # BSD -cliff>=2.3.0 # Apache-2.0 +cliff>=2.6.0 # Apache-2.0 keystoneauth1>=2.18.0 # Apache-2.0 openstacksdk>=0.9.14 # Apache-2.0 osc-lib>=1.2.0 # Apache-2.0 From bffc98e4e526ffb9878a12db9a0d8b87cf73d082 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 26 Apr 2017 18:04:18 -0500 Subject: [PATCH 1663/3095] Fix server create with nova-net A Neutron-ism slipped by in server create. Change-Id: Id590d7f93df2a41d7bd7617459a2af159a6f8071 --- openstackclient/compute/v2/server.py | 5 ++--- .../tests/functional/compute/v2/test_server.py | 12 ++++++++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 8b4a37217b..60dc605caf 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -655,10 +655,9 @@ def take_action(self, parsed_args): nic_info["port-id"] = port.id else: if nic_info["net-id"]: - nic_info["net-id"] = utils.find_resource( - compute_client.networks, + nic_info["net-id"] = compute_client.api.network_find( nic_info["net-id"] - ).id + )['id'] if nic_info["port-id"]: msg = _("can't create server with port specified " "since network endpoint not enabled") diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py index dd257e9a78..76255c69e6 100644 --- a/openstackclient/tests/functional/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -207,12 +207,20 @@ def test_server_attach_detach_floating_ip(self): 'floating ip create -f json ' + 'public' )) - floating_ip = cmd_output['floating_ip_address'] + + # Look for Neutron value first, then nova-net + floating_ip = cmd_output.get( + 'floating_ip_address', + cmd_output.get( + 'ip', + None, + ), + ) self.assertNotEqual('', cmd_output['id']) self.assertNotEqual('', floating_ip) self.addCleanup( self.openstack, - 'floating ip delete ' + cmd_output['id'] + 'floating ip delete ' + str(cmd_output['id']) ) raw_output = self.openstack( From 871450abcd89f9bb5ee9f75cdef3b812695eae93 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 26 Apr 2017 17:02:12 -0500 Subject: [PATCH 1664/3095] Fix quota functional tests for nova-net We need to skip some functional tests when testing against a nova-net cloud so add the bits to detect that. Also JSON-ify the quota functional tests and add the skips for nova-net. Change-Id: Ibfeeb3f967f34c98e80271a8214cf95dc50407f1 --- openstackclient/common/quota.py | 7 +- openstackclient/tests/functional/base.py | 12 ++ .../tests/functional/common/test_quota.py | 153 ++++++++++++------ 3 files changed, 119 insertions(+), 53 deletions(-) diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index ec4c8b51b1..73dfd9091d 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -299,7 +299,6 @@ def take_action(self, parsed_args): identity_client = self.app.client_manager.identity compute_client = self.app.client_manager.compute volume_client = self.app.client_manager.volume - network_client = self.app.client_manager.network compute_kwargs = {} for k, v in COMPUTE_QUOTAS.items(): value = getattr(parsed_args, k, None) @@ -352,7 +351,11 @@ def take_action(self, parsed_args): volume_client.quotas.update( project, **volume_kwargs) - if network_kwargs: + if ( + network_kwargs and + self.app.client_manager.is_network_endpoint_enabled() + ): + network_client = self.app.client_manager.network network_client.update_quota( project, **network_kwargs) diff --git a/openstackclient/tests/functional/base.py b/openstackclient/tests/functional/base.py index 8574329621..2d0706456b 100644 --- a/openstackclient/tests/functional/base.py +++ b/openstackclient/tests/functional/base.py @@ -42,6 +42,18 @@ def execute(cmd, fail_ok=False, merge_stderr=False): return result +def is_service_enabled(service): + """Ask client cloud if service is available""" + try: + ret = execute('openstack service show -f value -c enabled ' + service) + except exceptions.CommandFailed: + # We get here for multiple reasons, all of them mean that a working + # service is not avilable + return False + + return "True" in ret + + class TestCase(testtools.TestCase): delimiter_line = re.compile('^\+\-[\+\-]+\-\+$') diff --git a/openstackclient/tests/functional/common/test_quota.py b/openstackclient/tests/functional/common/test_quota.py index 8092b3cee8..1b13e95ed2 100644 --- a/openstackclient/tests/functional/common/test_quota.py +++ b/openstackclient/tests/functional/common/test_quota.py @@ -10,74 +10,125 @@ # License for the specific language governing permissions and limitations # under the License. +import json + from openstackclient.tests.functional import base class QuotaTests(base.TestCase): - """Functional tests for quota. """ - # Test quota information for compute, network and volume. - EXPECTED_FIELDS = ['instances', 'networks', 'volumes'] - EXPECTED_CLASS_FIELDS = ['instances', 'volumes'] + """Functional tests for quota + + Note that for 'set' tests use different quotas for each API in different + test runs as these may run in parallel and otherwise step on each other. + """ + PROJECT_NAME = None @classmethod def setUpClass(cls): + cls.haz_network = base.is_service_enabled('network') cls.PROJECT_NAME =\ cls.get_openstack_configuration_value('auth.project_name') def test_quota_list_network_option(self): - self.openstack('quota set --networks 40 ' + - self.PROJECT_NAME) - raw_output = self.openstack('quota list --network') - self.assertIsNotNone(raw_output) - self.assertIn("40", raw_output) + if not self.haz_network: + self.skipTest("No Network service present") + self.openstack('quota set --networks 40 ' + self.PROJECT_NAME) + cmd_output = json.loads(self.openstack( + 'quota list -f json --network' + )) + self.assertIsNotNone(cmd_output) + self.assertEqual( + 40, + cmd_output[0]["Networks"], + ) def test_quota_list_compute_option(self): - self.openstack('quota set --instances 40 ' + - self.PROJECT_NAME) - raw_output = self.openstack('quota list --compute') - self.assertIsNotNone(raw_output) - self.assertIn("40", raw_output) + self.openstack('quota set --instances 30 ' + self.PROJECT_NAME) + cmd_output = json.loads(self.openstack( + 'quota list -f json --compute' + )) + self.assertIsNotNone(cmd_output) + self.assertEqual( + 30, + cmd_output[0]["Instances"], + ) def test_quota_list_volume_option(self): - self.openstack('quota set --backups 40 ' + - self.PROJECT_NAME) - raw_output = self.openstack('quota list --volume') - self.assertIsNotNone(raw_output) - self.assertIn("40", raw_output) - - def test_quota_set(self): - self.openstack('quota set --instances 11 --volumes 11 --networks 11 ' + - self.PROJECT_NAME) - opts = self.get_opts(self.EXPECTED_FIELDS) - raw_output = self.openstack('quota show ' + self.PROJECT_NAME + opts) - self.assertEqual("11\n11\n11\n", raw_output) - - def test_quota_show(self): - raw_output = self.openstack('quota show ' + self.PROJECT_NAME) - for expected_field in self.EXPECTED_FIELDS: - self.assertIn(expected_field, raw_output) + self.openstack('quota set --volumes 20 ' + self.PROJECT_NAME) + cmd_output = json.loads(self.openstack( + 'quota list -f json --volume' + )) + self.assertIsNotNone(cmd_output) + self.assertEqual( + 20, + cmd_output[0]["Volumes"], + ) - def test_quota_show_default_project(self): - raw_output = self.openstack('quota show') - for expected_field in self.EXPECTED_FIELDS: - self.assertIn(expected_field, raw_output) + def test_quota_set_project(self): + """Test quota set, show""" + network_option = "" + if self.haz_network: + network_option = "--routers 21 " + self.openstack( + 'quota set --cores 31 --backups 41 ' + + network_option + + self.PROJECT_NAME + ) + cmd_output = json.loads(self.openstack( + 'quota show -f json ' + self.PROJECT_NAME + )) + self.assertIsNotNone(cmd_output) + self.assertEqual( + 31, + cmd_output["cores"], + ) + self.assertEqual( + 41, + cmd_output["backups"], + ) + if self.haz_network: + self.assertEqual( + 21, + cmd_output["routers"], + ) - def test_quota_show_with_default_option(self): - raw_output = self.openstack('quota show --default') - for expected_field in self.EXPECTED_FIELDS: - self.assertIn(expected_field, raw_output) + # Check default quotas + cmd_output = json.loads(self.openstack( + 'quota show -f json --default' + )) + self.assertIsNotNone(cmd_output) + # We don't necessarily know the default quotas, we're checking the + # returned attributes + self.assertTrue(cmd_output["cores"] >= 0) + self.assertTrue(cmd_output["backups"] >= 0) + if self.haz_network: + self.assertTrue(cmd_output["routers"] >= 0) - def test_quota_show_with_class_option(self): - raw_output = self.openstack('quota show --class') - for expected_field in self.EXPECTED_CLASS_FIELDS: - self.assertIn(expected_field, raw_output) + def test_quota_set_class(self): + self.openstack( + 'quota set --key-pairs 33 --snapshots 43 ' + + '--class default' + ) + cmd_output = json.loads(self.openstack( + 'quota show -f json --class default' + )) + self.assertIsNotNone(cmd_output) + self.assertEqual( + 33, + cmd_output["key-pairs"], + ) + self.assertEqual( + 43, + cmd_output["snapshots"], + ) - def test_quota_class_set(self): - class_name = 'default' - class_expected_fields = ['instances', 'volumes'] - self.openstack('quota set --instances 11 --volumes 11 --class ' + - class_name) - opts = self.get_opts(class_expected_fields) - raw_output = self.openstack('quota show --class ' + class_name + opts) - self.assertEqual("11\n11\n", raw_output) + # Check default quota class + cmd_output = json.loads(self.openstack( + 'quota show -f json --class' + )) + self.assertIsNotNone(cmd_output) + # We don't necessarily know the default quotas, we're checking the + # returned attributes + self.assertTrue(cmd_output["key-pairs"] >= 0) + self.assertTrue(cmd_output["snapshots"] >= 0) From 589a65c3fee2d61a13eaa53785afd3525d9ae80d Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 27 Apr 2017 10:26:07 -0500 Subject: [PATCH 1665/3095] Fix Nova-net netowrk commands In cleaning up functional tests for nova-net, I discovered some problems in network create: * --subnet option is required in network create command * Switch API to use /os-networks rather than /os-tenant-networks as this is what we were actually using via novaclient * Fix functional tests for nova-net * Normalize some private function names in network/v2/network.py Change-Id: I426b864406756d58d140575a3a45ee9aee67ce84 --- doc/source/backwards-incompatible.rst | 8 +- doc/source/command-objects/network.rst | 2 + openstackclient/api/compute_v2.py | 23 +- openstackclient/network/v2/network.py | 45 +-- .../functional/network/v2/test_network.py | 316 ++++++++++++------ .../tests/unit/api/test_compute_v2.py | 30 +- .../unit/network/v2/test_network_compute.py | 18 + 7 files changed, 299 insertions(+), 143 deletions(-) diff --git a/doc/source/backwards-incompatible.rst b/doc/source/backwards-incompatible.rst index 0202e8a5a1..6516e794a6 100644 --- a/doc/source/backwards-incompatible.rst +++ b/doc/source/backwards-incompatible.rst @@ -30,7 +30,13 @@ Backwards Incompatible Changes Release 3.10 ------------ -1. The positional argument ```` of the ``volume snapshot create`` +1. The ``network create`` command now requires the ``--subnet`` option when used + with Nova-network clouds. + + * As of: 3.10 + * Commit: https://review.openstack.org/460679 + +2. The positional argument ```` of the ``volume snapshot create`` command is no longer optional. Previously when the ``--volume`` option was diff --git a/doc/source/command-objects/network.rst b/doc/source/command-objects/network.rst index 9162dbff01..636409b90f 100644 --- a/doc/source/command-objects/network.rst +++ b/doc/source/command-objects/network.rst @@ -71,6 +71,8 @@ Create new network Set network description + *Network version 2 only* + .. option:: --availability-zone-hint Availability Zone in which to create this network diff --git a/openstackclient/api/compute_v2.py b/openstackclient/api/compute_v2.py index 650226713c..51b34482ea 100644 --- a/openstackclient/api/compute_v2.py +++ b/openstackclient/api/compute_v2.py @@ -202,24 +202,25 @@ def network_create( ): """Create a new network - https://developer.openstack.org/api-ref/compute/#create-project-network + https://developer.openstack.org/api-ref/compute/#create-network :param string name: - Network label + Network label (required) :param integer subnet: - Subnet for IPv4 fixed addresses in CIDR notation + Subnet for IPv4 fixed addresses in CIDR notation (required) :param integer share_subnet: Shared subnet between projects, True or False :returns: A dict of the network attributes """ - url = "/os-tenant-networks" + url = "/os-networks" params = { 'label': name, 'cidr': subnet, - 'share_address': share_subnet, } + if share_subnet is not None: + params['share_address'] = share_subnet return self.create( url, @@ -232,13 +233,13 @@ def network_delete( ): """Delete a network - https://developer.openstack.org/api-ref/compute/#delete-project-network + https://developer.openstack.org/api-ref/compute/#delete-network :param string network: Network name or ID """ - url = "/os-tenant-networks" + url = "/os-networks" network = self.find( url, @@ -256,14 +257,14 @@ def network_find( ): """Return a network given name or ID - https://developer.openstack.org/api-ref/compute/#show-project-network-details + https://developer.openstack.org/api-ref/compute/#show-network-details :param string network: Network name or ID :returns: A dict of the network attributes """ - url = "/os-tenant-networks" + url = "/os-networks" return self.find( url, @@ -276,13 +277,13 @@ def network_list( ): """Get networks - https://developer.openstack.org/api-ref/compute/#list-project-networks + https://developer.openstack.org/api-ref/compute/#list-networks :returns: list of networks """ - url = "/os-tenant-networks" + url = "/os-networks" return self.list(url)["networks"] diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 1f5d8a0361..e4cf54bfb2 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -42,7 +42,7 @@ def _format_router_external(item): } -def _get_network_columns(item): +def _get_columns_network(item): column_map = { 'subnet_ids': 'subnets', 'is_admin_state_up': 'admin_state_up', @@ -59,14 +59,14 @@ def _get_network_columns(item): return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) -def _get_columns(item): +def _get_columns_compute(item): column_map = { 'tenant_id': 'project_id', } return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) -def _get_attrs(client_manager, parsed_args): +def _get_attrs_network(client_manager, parsed_args): attrs = {} if parsed_args.name is not None: attrs['name'] = str(parsed_args.name) @@ -135,6 +135,19 @@ def _get_attrs(client_manager, parsed_args): return attrs +def _get_attrs_compute(client_manager, parsed_args): + attrs = {} + if parsed_args.name is not None: + attrs['name'] = str(parsed_args.name) + if parsed_args.share: + attrs['share_subnet'] = True + if parsed_args.no_share: + attrs['share_subnet'] = False + if parsed_args.subnet is not None: + attrs['subnet'] = parsed_args.subnet + return attrs + + def _add_additional_network_options(parser): # Add additional network options @@ -168,19 +181,6 @@ def _add_additional_network_options(parser): help=_("Do not make the network VLAN transparent")) -def _get_attrs_compute(client_manager, parsed_args): - attrs = {} - if parsed_args.name is not None: - attrs['name'] = str(parsed_args.name) - if parsed_args.share: - attrs['share_subnet'] = True - if parsed_args.no_share: - attrs['share_subnet'] = False - if parsed_args.subnet is not None: - attrs['subnet'] = parsed_args.subnet - return attrs - - # TODO(sindhu): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. class CreateNetwork(common.NetworkAndComputeShowOne): @@ -289,21 +289,22 @@ def update_parser_compute(self, parser): parser.add_argument( '--subnet', metavar='', + required=True, help=_("IPv4 subnet for fixed IPs (in CIDR notation)") ) return parser def take_action_network(self, client, parsed_args): - attrs = _get_attrs(self.app.client_manager, parsed_args) + attrs = _get_attrs_network(self.app.client_manager, parsed_args) obj = client.create_network(**attrs) - display_columns, columns = _get_network_columns(obj) + display_columns, columns = _get_columns_network(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) return (display_columns, data) def take_action_compute(self, client, parsed_args): attrs = _get_attrs_compute(self.app.client_manager, parsed_args) obj = client.api.network_create(**attrs) - display_columns, columns = _get_columns(obj) + display_columns, columns = _get_columns_compute(obj) data = utils.get_dict_properties(obj, columns) return (display_columns, data) @@ -660,7 +661,7 @@ def take_action(self, parsed_args): client = self.app.client_manager.network obj = client.find_network(parsed_args.network, ignore_missing=False) - attrs = _get_attrs(self.app.client_manager, parsed_args) + attrs = _get_attrs_network(self.app.client_manager, parsed_args) client.update_network(obj, **attrs) @@ -677,12 +678,12 @@ def update_parser_common(self, parser): def take_action_network(self, client, parsed_args): obj = client.find_network(parsed_args.network, ignore_missing=False) - display_columns, columns = _get_network_columns(obj) + display_columns, columns = _get_columns_network(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) return (display_columns, data) def take_action_compute(self, client, parsed_args): obj = client.api.network_find(parsed_args.network) - display_columns, columns = _get_columns(obj) + display_columns, columns = _get_columns_compute(obj) data = utils.get_dict_properties(obj, columns) return (display_columns, data) diff --git a/openstackclient/tests/functional/network/v2/test_network.py b/openstackclient/tests/functional/network/v2/test_network.py index 02fa42f242..91500e0dd2 100644 --- a/openstackclient/tests/functional/network/v2/test_network.py +++ b/openstackclient/tests/functional/network/v2/test_network.py @@ -19,8 +19,65 @@ class NetworkTests(base.TestCase): """Functional tests for network""" - def test_network_create(self): - """Test create options, delete""" + @classmethod + def setUpClass(cls): + cls.haz_network = base.is_service_enabled('network') + cls.PROJECT_NAME =\ + cls.get_openstack_configuration_value('auth.project_name') + + def test_network_create_compute(self): + """Test Nova-net create options, delete""" + if self.haz_network: + self.skipTest("Skip Nova-net test") + + # Network create with minimum options + name1 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'network create -f json ' + + '--subnet 1.2.3.4/28 ' + + name1 + )) + self.addCleanup(self.openstack, 'network delete ' + name1) + self.assertIsNotNone(cmd_output["id"]) + + self.assertEqual( + name1, + cmd_output["label"], + ) + self.assertEqual( + '1.2.3.0/28', + cmd_output["cidr"], + ) + + # Network create with more options + name2 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'network create -f json ' + + '--subnet 1.2.4.4/28 ' + + '--share ' + + name2 + )) + self.addCleanup(self.openstack, 'network delete ' + name2) + self.assertIsNotNone(cmd_output["id"]) + + self.assertEqual( + name2, + cmd_output["label"], + ) + self.assertEqual( + '1.2.4.0/28', + cmd_output["cidr"], + ) + self.assertEqual( + True, + cmd_output["share_address"], + ) + + def test_network_create_network(self): + """Test Neutron create options, delete""" + if not self.haz_network: + self.skipTest("No Network service present") + # Get project IDs cmd_output = json.loads(self.openstack('token issue -f json ')) auth_project_id = cmd_output['project_id'] @@ -43,7 +100,7 @@ def test_network_create(self): self.assertNotEqual(admin_project_id, demo_project_id) self.assertEqual(admin_project_id, auth_project_id) - # network create with no options + # Network create with no options name1 = uuid.uuid4().hex cmd_output = json.loads(self.openstack( 'network create -f json ' + @@ -74,6 +131,7 @@ def test_network_create(self): cmd_output["router:external"], ) + # Network create with options name2 = uuid.uuid4().hex cmd_output = json.loads(self.openstack( 'network create -f json ' + @@ -91,154 +149,221 @@ def test_network_create(self): cmd_output["description"], ) - def test_network_delete(self): + def test_network_delete_compute(self): """Test create, delete multiple""" + if self.haz_network: + self.skipTest("Skip Nova-net test") + name1 = uuid.uuid4().hex cmd_output = json.loads(self.openstack( 'network create -f json ' + - '--description aaaa ' + + '--subnet 9.8.7.6/28 ' + name1 )) self.assertIsNotNone(cmd_output["id"]) self.assertEqual( - 'aaaa', - cmd_output["description"], + name1, + cmd_output["label"], ) name2 = uuid.uuid4().hex cmd_output = json.loads(self.openstack( 'network create -f json ' + - '--description bbbb ' + + '--subnet 8.7.6.5/28 ' + name2 )) self.assertIsNotNone(cmd_output["id"]) self.assertEqual( - 'bbbb', - cmd_output["description"], + name2, + cmd_output["label"], ) - del_output = self.openstack('network delete ' + name1 + ' ' + name2) - self.assertOutput('', del_output) + def test_network_delete_network(self): + """Test create, delete multiple""" + if not self.haz_network: + self.skipTest("No Network service present") - def test_network_list(self): - """Test create defaults, list filters, delete""" name1 = uuid.uuid4().hex cmd_output = json.loads(self.openstack( 'network create -f json ' + '--description aaaa ' + - '--no-default ' + name1 )) - self.addCleanup(self.openstack, 'network delete ' + name1) self.assertIsNotNone(cmd_output["id"]) self.assertEqual( 'aaaa', cmd_output["description"], ) - # Check the default values - self.assertEqual( - 'UP', - cmd_output["admin_state_up"], - ) - self.assertEqual( - False, - cmd_output["shared"], - ) - self.assertEqual( - 'Internal', - cmd_output["router:external"], - ) - - self.assertEqual( - False, - cmd_output["is_default"], - ) - self.assertEqual( - True, - cmd_output["port_security_enabled"], - ) name2 = uuid.uuid4().hex cmd_output = json.loads(self.openstack( 'network create -f json ' + '--description bbbb ' + - '--disable ' + - '--share ' + name2 )) - self.addCleanup(self.openstack, 'network delete ' + name2) self.assertIsNotNone(cmd_output["id"]) self.assertEqual( 'bbbb', cmd_output["description"], ) - self.assertEqual( - 'DOWN', - cmd_output["admin_state_up"], - ) - self.assertEqual( - True, - cmd_output["shared"], - ) - self.assertEqual( - False, - cmd_output["is_default"], - ) - self.assertEqual( - True, - cmd_output["port_security_enabled"], - ) - # Test list --long - cmd_output = json.loads(self.openstack( - "network list -f json " + - "--long" - )) - col_name = [x["Name"] for x in cmd_output] - self.assertIn(name1, col_name) - self.assertIn(name2, col_name) + del_output = self.openstack('network delete ' + name1 + ' ' + name2) + self.assertOutput('', del_output) - # Test list --long --enable + def test_network_list(self): + """Test create defaults, list filters, delete""" + name1 = uuid.uuid4().hex + if self.haz_network: + network_options = '--description aaaa --no-default ' + else: + network_options = '--subnet 3.4.5.6/28 ' cmd_output = json.loads(self.openstack( - "network list -f json " + - "--enable " + - "--long" + 'network create -f json ' + + network_options + + name1 )) - col_name = [x["Name"] for x in cmd_output] - self.assertIn(name1, col_name) - self.assertNotIn(name2, col_name) + self.addCleanup(self.openstack, 'network delete ' + name1) + self.assertIsNotNone(cmd_output["id"]) + if self.haz_network: + self.assertEqual( + 'aaaa', + cmd_output["description"], + ) + # Check the default values + self.assertEqual( + 'UP', + cmd_output["admin_state_up"], + ) + self.assertEqual( + False, + cmd_output["shared"], + ) + self.assertEqual( + 'Internal', + cmd_output["router:external"], + ) + self.assertEqual( + False, + cmd_output["is_default"], + ) + self.assertEqual( + True, + cmd_output["port_security_enabled"], + ) + else: + self.assertEqual( + '3.4.5.0/28', + cmd_output["cidr"], + ) - # Test list --long --disable + name2 = uuid.uuid4().hex + if self.haz_network: + network_options = '--description bbbb --disable ' + else: + network_options = '--subnet 4.5.6.7/28 ' cmd_output = json.loads(self.openstack( - "network list -f json " + - "--disable " + - "--long" + 'network create -f json ' + + '--share ' + + network_options + + name2 )) - col_name = [x["Name"] for x in cmd_output] - self.assertNotIn(name1, col_name) - self.assertIn(name2, col_name) - - # Test list --long --share + self.addCleanup(self.openstack, 'network delete ' + name2) + self.assertIsNotNone(cmd_output["id"]) + if self.haz_network: + self.assertEqual( + 'bbbb', + cmd_output["description"], + ) + self.assertEqual( + 'DOWN', + cmd_output["admin_state_up"], + ) + self.assertEqual( + True, + cmd_output["shared"], + ) + self.assertEqual( + False, + cmd_output["is_default"], + ) + self.assertEqual( + True, + cmd_output["port_security_enabled"], + ) + else: + self.assertEqual( + '4.5.6.0/28', + cmd_output["cidr"], + ) + self.assertEqual( + True, + cmd_output["share_address"], + ) + + # Test list cmd_output = json.loads(self.openstack( - "network list -f json " + - "--share " + - "--long" + "network list -f json " )) col_name = [x["Name"] for x in cmd_output] - self.assertNotIn(name1, col_name) + self.assertIn(name1, col_name) self.assertIn(name2, col_name) - # Test list --long --no-share - cmd_output = json.loads(self.openstack( - "network list -f json " + - "--no-share " + - "--long" - )) - col_name = [x["Name"] for x in cmd_output] - self.assertIn(name1, col_name) - self.assertNotIn(name2, col_name) + # Test list --long + if self.haz_network: + cmd_output = json.loads(self.openstack( + "network list -f json " + + "--long" + )) + col_name = [x["Name"] for x in cmd_output] + self.assertIn(name1, col_name) + self.assertIn(name2, col_name) + + # Test list --long --enable + if self.haz_network: + cmd_output = json.loads(self.openstack( + "network list -f json " + + "--enable " + + "--long" + )) + col_name = [x["Name"] for x in cmd_output] + self.assertIn(name1, col_name) + self.assertNotIn(name2, col_name) + + # Test list --long --disable + if self.haz_network: + cmd_output = json.loads(self.openstack( + "network list -f json " + + "--disable " + + "--long" + )) + col_name = [x["Name"] for x in cmd_output] + self.assertNotIn(name1, col_name) + self.assertIn(name2, col_name) + + # Test list --share + if self.haz_network: + cmd_output = json.loads(self.openstack( + "network list -f json " + + "--share " + )) + col_name = [x["Name"] for x in cmd_output] + self.assertNotIn(name1, col_name) + self.assertIn(name2, col_name) + + # Test list --no-share + if self.haz_network: + cmd_output = json.loads(self.openstack( + "network list -f json " + + "--no-share " + )) + col_name = [x["Name"] for x in cmd_output] + self.assertIn(name1, col_name) + self.assertNotIn(name2, col_name) def test_network_dhcp_agent(self): + if self.haz_network: + self.skipTest("No Network service present") + name1 = uuid.uuid4().hex cmd_output1 = json.loads(self.openstack( 'network create -f json ' + @@ -283,6 +408,9 @@ def test_network_dhcp_agent(self): def test_network_set(self): """Tests create options, set, show, delete""" + if self.haz_network: + self.skipTest("No Network service present") + name = uuid.uuid4().hex cmd_output = json.loads(self.openstack( 'network create -f json ' + diff --git a/openstackclient/tests/unit/api/test_compute_v2.py b/openstackclient/tests/unit/api/test_compute_v2.py index 22ee489967..f10fb6cfcf 100644 --- a/openstackclient/tests/unit/api/test_compute_v2.py +++ b/openstackclient/tests/unit/api/test_compute_v2.py @@ -185,7 +185,7 @@ class TestNetwork(TestComputeAPIv2): def test_network_create_default(self): self.requests_mock.register_uri( 'POST', - FAKE_URL + '/os-tenant-networks', + FAKE_URL + '/os-networks', json={'network': self.FAKE_NETWORK_RESP}, status_code=200, ) @@ -195,7 +195,7 @@ def test_network_create_default(self): def test_network_create_options(self): self.requests_mock.register_uri( 'POST', - FAKE_URL + '/os-tenant-networks', + FAKE_URL + '/os-networks', json={'network': self.FAKE_NETWORK_RESP}, status_code=200, ) @@ -208,13 +208,13 @@ def test_network_create_options(self): def test_network_delete_id(self): self.requests_mock.register_uri( 'GET', - FAKE_URL + '/os-tenant-networks/1', + FAKE_URL + '/os-networks/1', json={'network': self.FAKE_NETWORK_RESP}, status_code=200, ) self.requests_mock.register_uri( 'DELETE', - FAKE_URL + '/os-tenant-networks/1', + FAKE_URL + '/os-networks/1', status_code=202, ) ret = self.api.network_delete('1') @@ -224,18 +224,18 @@ def test_network_delete_id(self): def test_network_delete_name(self): self.requests_mock.register_uri( 'GET', - FAKE_URL + '/os-tenant-networks/label1', + FAKE_URL + '/os-networks/label1', status_code=404, ) self.requests_mock.register_uri( 'GET', - FAKE_URL + '/os-tenant-networks', + FAKE_URL + '/os-networks', json={'networks': self.LIST_NETWORK_RESP}, status_code=200, ) self.requests_mock.register_uri( 'DELETE', - FAKE_URL + '/os-tenant-networks/1', + FAKE_URL + '/os-networks/1', status_code=202, ) ret = self.api.network_delete('label1') @@ -245,12 +245,12 @@ def test_network_delete_name(self): def test_network_delete_not_found(self): self.requests_mock.register_uri( 'GET', - FAKE_URL + '/os-tenant-networks/label3', + FAKE_URL + '/os-networks/label3', status_code=404, ) self.requests_mock.register_uri( 'GET', - FAKE_URL + '/os-tenant-networks', + FAKE_URL + '/os-networks', json={'networks': self.LIST_NETWORK_RESP}, status_code=200, ) @@ -263,7 +263,7 @@ def test_network_delete_not_found(self): def test_network_find_id(self): self.requests_mock.register_uri( 'GET', - FAKE_URL + '/os-tenant-networks/1', + FAKE_URL + '/os-networks/1', json={'network': self.FAKE_NETWORK_RESP}, status_code=200, ) @@ -273,12 +273,12 @@ def test_network_find_id(self): def test_network_find_name(self): self.requests_mock.register_uri( 'GET', - FAKE_URL + '/os-tenant-networks/label2', + FAKE_URL + '/os-networks/label2', status_code=404, ) self.requests_mock.register_uri( 'GET', - FAKE_URL + '/os-tenant-networks', + FAKE_URL + '/os-networks', json={'networks': self.LIST_NETWORK_RESP}, status_code=200, ) @@ -288,12 +288,12 @@ def test_network_find_name(self): def test_network_find_not_found(self): self.requests_mock.register_uri( 'GET', - FAKE_URL + '/os-tenant-networks/label3', + FAKE_URL + '/os-networks/label3', status_code=404, ) self.requests_mock.register_uri( 'GET', - FAKE_URL + '/os-tenant-networks', + FAKE_URL + '/os-networks', json={'networks': self.LIST_NETWORK_RESP}, status_code=200, ) @@ -306,7 +306,7 @@ def test_network_find_not_found(self): def test_network_list_no_options(self): self.requests_mock.register_uri( 'GET', - FAKE_URL + '/os-tenant-networks', + FAKE_URL + '/os-networks', json={'networks': self.LIST_NETWORK_RESP}, status_code=200, ) diff --git a/openstackclient/tests/unit/network/v2/test_network_compute.py b/openstackclient/tests/unit/network/v2/test_network_compute.py index 25beb8595e..c649401c41 100644 --- a/openstackclient/tests/unit/network/v2/test_network_compute.py +++ b/openstackclient/tests/unit/network/v2/test_network_compute.py @@ -132,6 +132,24 @@ def test_network_create_no_options(self, net_mock): verifylist, ) + def test_network_create_missing_options(self, net_mock): + net_mock.return_value = self._network + arglist = [ + self._network['label'], + ] + verifylist = [ + ('name', self._network['label']), + ] + + # Missing required args should raise exception here + self.assertRaises( + tests_utils.ParserException, + self.check_parser, + self.cmd, + arglist, + verifylist, + ) + def test_network_create_default_options(self, net_mock): net_mock.return_value = self._network arglist = [ From e0d1af94a1ae37c2800da6f9c9ba2d6c65f2de8d Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 26 Apr 2017 19:27:00 -0500 Subject: [PATCH 1666/3095] Nova net functional tests round 1 * address scope * network agent * network flavor * network flavor profile * network meter * network meter rule Also create a new common network functional test class NetworkTests to house the setting of haz_network in a single place. The individual test skipping stays in the final classes to re-enforce the idea that some tests work with both Nova-net and Neutron. Change-Id: Ie3910231c6fc9e2031438c599afa904f43c874a7 --- .../tests/functional/network/v2/common.py | 22 ++++ .../network/v2/test_address_scope.py | 10 +- .../network/v2/test_network_agent.py | 112 ++++++++++++----- .../network/v2/test_network_flavor.py | 15 ++- .../network/v2/test_network_flavor_profile.py | 114 ++++++++++++------ .../network/v2/test_network_meter.py | 84 +++++++------ .../network/v2/test_network_meter_rule.py | 65 ++++++---- 7 files changed, 286 insertions(+), 136 deletions(-) create mode 100644 openstackclient/tests/functional/network/v2/common.py diff --git a/openstackclient/tests/functional/network/v2/common.py b/openstackclient/tests/functional/network/v2/common.py new file mode 100644 index 0000000000..bed07878f6 --- /dev/null +++ b/openstackclient/tests/functional/network/v2/common.py @@ -0,0 +1,22 @@ +# 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 NetworkTests(base.TestCase): + """Functional tests for Network commands""" + + @classmethod + def setUpClass(cls): + # super(NetworkTests, cls).setUp() + cls.haz_network = base.is_service_enabled('network') diff --git a/openstackclient/tests/functional/network/v2/test_address_scope.py b/openstackclient/tests/functional/network/v2/test_address_scope.py index eaf88969ad..e5156d7f2e 100644 --- a/openstackclient/tests/functional/network/v2/test_address_scope.py +++ b/openstackclient/tests/functional/network/v2/test_address_scope.py @@ -13,10 +13,10 @@ import json import uuid -from openstackclient.tests.functional import base +from openstackclient.tests.functional.network.v2 import common -class AddressScopeTests(base.TestCase): +class AddressScopeTests(common.NetworkTests): """Functional tests for address scope. """ # NOTE(dtroyer): Do not normalize the setup and teardown of the resource @@ -24,6 +24,12 @@ class AddressScopeTests(base.TestCase): # has its own needs and there are collisions when running # tests in parallel. + def setUp(self): + super(AddressScopeTests, self).setUp() + # Nothing in this class works with Nova Network + if not self.haz_network: + self.skipTest("No Network service present") + def test_address_scope_delete(self): """Test create, delete multiple""" name1 = uuid.uuid4().hex diff --git a/openstackclient/tests/functional/network/v2/test_network_agent.py b/openstackclient/tests/functional/network/v2/test_network_agent.py index 6da721d1e7..1648795513 100644 --- a/openstackclient/tests/functional/network/v2/test_network_agent.py +++ b/openstackclient/tests/functional/network/v2/test_network_agent.py @@ -13,39 +13,85 @@ import json import uuid -from openstackclient.tests.functional import base - - -class NetworkAgentTests(base.TestCase): - """Functional tests for network agent. """ - IDs = None - HEADERS = ['ID'] - FIELDS = ['id'] - - @classmethod - def test_network_agent_list(cls): - opts = cls.get_opts(cls.HEADERS) - raw_output = cls.openstack('network agent list' + opts) - # get the list of network agent IDs. - cls.IDs = raw_output.split('\n') - - def test_network_agent_show(self): - opts = self.get_opts(self.FIELDS) - raw_output = self.openstack('network agent show ' + self.IDs[0] + opts) - self.assertEqual(self.IDs[0] + "\n", raw_output) - - def test_network_agent_set(self): - opts = self.get_opts(['admin_state_up']) - self.openstack('network agent set --disable ' + self.IDs[0]) - raw_output = self.openstack('network agent show ' + self.IDs[0] + opts) - self.assertEqual("DOWN\n", raw_output) - self.openstack('network agent set --enable ' + self.IDs[0]) - raw_output = self.openstack('network agent show ' + self.IDs[0] + opts) - self.assertEqual("UP\n", raw_output) - - -class NetworkAgentListTests(base.TestCase): - """Functional test for network agent list --network. """ +from openstackclient.tests.functional.network.v2 import common + + +class NetworkAgentTests(common.NetworkTests): + """Functional tests for network agent""" + + def setUp(self): + super(NetworkAgentTests, self).setUp() + # Nothing in this class works with Nova Network + if not self.haz_network: + self.skipTest("No Network service present") + + def test_network_agent_list_show_set(self): + """Test network agent list, set, show commands + + Do these serially because show and set rely on the existing agent IDs + from the list output and we had races when run in parallel. + """ + + # agent list + agent_list = json.loads(self.openstack( + 'network agent list -f json' + )) + self.assertIsNotNone(agent_list[0]) + + agent_ids = list([row["ID"] for row in agent_list]) + + # agent show + cmd_output = json.loads(self.openstack( + 'network agent show -f json ' + + agent_ids[0] + )) + self.assertEqual( + agent_ids[0], + cmd_output['id'], + ) + + # agent set + raw_output = self.openstack( + 'network agent set ' + + '--disable ' + + agent_ids[0] + ) + self.assertOutput('', raw_output) + + cmd_output = json.loads(self.openstack( + 'network agent show -f json ' + + agent_ids[0] + )) + self.assertEqual( + "DOWN", + cmd_output['admin_state_up'], + ) + + raw_output = self.openstack( + 'network agent set ' + + '--enable ' + + agent_ids[0] + ) + self.assertOutput('', raw_output) + + cmd_output = json.loads(self.openstack( + 'network agent show -f json ' + + agent_ids[0] + )) + self.assertEqual( + "UP", + cmd_output['admin_state_up'], + ) + + +class NetworkAgentListTests(common.NetworkTests): + """Functional test for network agent""" + + def setUp(self): + super(NetworkAgentListTests, self).setUp() + # Nothing in this class works with Nova Network + if not self.haz_network: + self.skipTest("No Network service present") def test_network_dhcp_agent_list(self): """Test network agent list""" diff --git a/openstackclient/tests/functional/network/v2/test_network_flavor.py b/openstackclient/tests/functional/network/v2/test_network_flavor.py index 743407905f..47e7b44057 100644 --- a/openstackclient/tests/functional/network/v2/test_network_flavor.py +++ b/openstackclient/tests/functional/network/v2/test_network_flavor.py @@ -14,15 +14,20 @@ import json import uuid -from openstackclient.tests.functional import base +from openstackclient.tests.functional.network.v2 import common -class NetworkFlavorTests(base.TestCase): - """Functional tests for network flavor.""" +class NetworkFlavorTests(common.NetworkTests): + """Functional tests for network flavor""" - def test_add_remove_network_flavor_profile(self): - """Test add and remove network flavor to/from profile""" + def setUp(self): + super(NetworkFlavorTests, self).setUp() + # Nothing in this class works with Nova Network + if not self.haz_network: + self.skipTest("No Network service present") + def test_network_flavor_add_remove_profile(self): + """Test add and remove network flavor to/from profile""" # Create Flavor name1 = uuid.uuid4().hex cmd_output1 = json.loads(self.openstack( diff --git a/openstackclient/tests/functional/network/v2/test_network_flavor_profile.py b/openstackclient/tests/functional/network/v2/test_network_flavor_profile.py index 1a82c82b0c..2207c84749 100644 --- a/openstackclient/tests/functional/network/v2/test_network_flavor_profile.py +++ b/openstackclient/tests/functional/network/v2/test_network_flavor_profile.py @@ -12,30 +12,40 @@ import json -from openstackclient.tests.functional import base +from openstackclient.tests.functional.network.v2 import common -class NetworkFlavorProfileTests(base.TestCase): - """Functional tests for network flavor-profile.""" +class NetworkFlavorProfileTests(common.NetworkTests): + """Functional tests for network flavor profile""" DESCRIPTION = 'fakedescription' METAINFO = 'Extrainfo' + def setUp(self): + super(NetworkFlavorProfileTests, self).setUp() + # Nothing in this class works with Nova Network + if not self.haz_network: + self.skipTest("No Network service present") + def test_network_flavor_profile_create(self): json_output = json.loads(self.openstack( - ' network flavor profile create -f json --description ' - + self.DESCRIPTION + ' --enable --metainfo ' + self.METAINFO)) + 'network flavor profile create -f json ' + + '--description ' + self.DESCRIPTION + ' ' + + '--enable --metainfo ' + self.METAINFO + )) ID = json_output.get('id') self.assertIsNotNone(ID) self.assertEqual( True, - json_output.get('enabled')) + json_output.get('enabled'), + ) self.assertEqual( 'fakedescription', - json_output.get('description')) + json_output.get('description'), + ) self.assertEqual( 'Extrainfo', - json_output.get('meta_info') + json_output.get('meta_info'), ) # Clean up @@ -44,40 +54,51 @@ def test_network_flavor_profile_create(self): def test_network_flavor_profile_list(self): json_output = json.loads(self.openstack( - ' network flavor profile create -f json --description ' - + self.DESCRIPTION + ' --enable --metainfo ' + self.METAINFO)) + 'network flavor profile create -f json ' + + '--description ' + self.DESCRIPTION + ' ' + + '--enable ' + + '--metainfo ' + self.METAINFO + )) ID1 = json_output.get('id') self.assertIsNotNone(ID1) self.assertEqual( True, - json_output.get('enabled')) + json_output.get('enabled'), + ) self.assertEqual( 'fakedescription', - json_output.get('description')) + json_output.get('description'), + ) self.assertEqual( 'Extrainfo', - json_output.get('meta_info') + json_output.get('meta_info'), ) json_output = json.loads(self.openstack( - ' network flavor profile create -f json --description ' - + self.DESCRIPTION + ' --disable --metainfo ' + self.METAINFO)) + 'network flavor profile create -f json ' + + '--description ' + self.DESCRIPTION + ' ' + + '--disable ' + + '--metainfo ' + self.METAINFO + )) ID2 = json_output.get('id') self.assertIsNotNone(ID2) self.assertEqual( False, - json_output.get('enabled')) + json_output.get('enabled'), + ) self.assertEqual( 'fakedescription', - json_output.get('description')) + json_output.get('description'), + ) self.assertEqual( 'Extrainfo', - json_output.get('meta_info') + json_output.get('meta_info'), ) # Test list json_output = json.loads(self.openstack( - 'network flavor profile list -f json')) + 'network flavor profile list -f json' + )) self.assertIsNotNone(json_output) id_list = [item.get('ID') for item in json_output] @@ -86,39 +107,48 @@ def test_network_flavor_profile_list(self): # Clean up raw_output = self.openstack( - 'network flavor profile delete ' + ID1 + " " + ID2) + 'network flavor profile delete ' + ID1 + ' ' + ID2 + ) self.assertOutput('', raw_output) def test_network_flavor_profile_set(self): json_output_1 = json.loads(self.openstack( - ' network flavor profile create -f json --description ' - + self.DESCRIPTION + ' --enable --metainfo ' + self.METAINFO)) + 'network flavor profile create -f json ' + + '--description ' + self.DESCRIPTION + ' ' + + '--enable ' + + '--metainfo ' + self.METAINFO + )) ID = json_output_1.get('id') self.assertIsNotNone(ID) self.assertEqual( True, - json_output_1.get('enabled')) + json_output_1.get('enabled'), + ) self.assertEqual( 'fakedescription', - json_output_1.get('description')) + json_output_1.get('description'), + ) self.assertEqual( 'Extrainfo', - json_output_1.get('meta_info') + json_output_1.get('meta_info'), ) self.openstack('network flavor profile set --disable ' + ID) - json_output = json.loads(self.openstack('network flavor profile show ' - '-f json ' + ID)) + json_output = json.loads(self.openstack( + 'network flavor profile show -f json ' + ID + )) self.assertEqual( False, - json_output.get('enabled')) + json_output.get('enabled'), + ) self.assertEqual( 'fakedescription', - json_output.get('description')) + json_output.get('description'), + ) self.assertEqual( 'Extrainfo', - json_output.get('meta_info') + json_output.get('meta_info'), ) # Clean up @@ -127,24 +157,32 @@ def test_network_flavor_profile_set(self): def test_network_flavor_profile_show(self): json_output_1 = json.loads(self.openstack( - ' network flavor profile create -f json --description ' - + self.DESCRIPTION + ' --enable --metainfo ' + self.METAINFO)) + 'network flavor profile create -f json ' + + '--description ' + self.DESCRIPTION + ' ' + + '--enable ' + + '--metainfo ' + self.METAINFO + )) ID = json_output_1.get('id') self.assertIsNotNone(ID) - json_output = json.loads(self.openstack('network flavor profile show ' - '-f json ' + ID)) + json_output = json.loads(self.openstack( + 'network flavor profile show -f json ' + ID + )) self.assertEqual( ID, - json_output["id"]) + json_output["id"], + ) self.assertEqual( True, - json_output["enabled"]) + json_output["enabled"], + ) self.assertEqual( 'fakedescription', - json_output["description"]) + json_output["description"], + ) self.assertEqual( 'Extrainfo', - json_output["meta_info"]) + json_output["meta_info"], + ) # Clean up raw_output = self.openstack('network flavor profile delete ' + ID) diff --git a/openstackclient/tests/functional/network/v2/test_network_meter.py b/openstackclient/tests/functional/network/v2/test_network_meter.py index f73f481297..7f6da28d3c 100644 --- a/openstackclient/tests/functional/network/v2/test_network_meter.py +++ b/openstackclient/tests/functional/network/v2/test_network_meter.py @@ -16,26 +16,33 @@ import json import uuid -from openstackclient.tests.functional import base +from openstackclient.tests.functional.network.v2 import common -class TestMeter(base.TestCase): - """Functional tests for network meter.""" +class TestMeter(common.NetworkTests): + """Functional tests for network meter""" # NOTE(dtroyer): Do not normalize the setup and teardown of the resource # creation and deletion. Little is gained when each test # has its own needs and there are collisions when running # tests in parallel. + def setUp(self): + super(TestMeter, self).setUp() + # Nothing in this class works with Nova Network + if not self.haz_network: + self.skipTest("No Network service present") + def test_meter_delete(self): """Test create, delete multiple""" name1 = uuid.uuid4().hex name2 = uuid.uuid4().hex description = 'fakedescription' json_output = json.loads(self.openstack( - 'network meter create -f json ' + name1 + ' --description ' - + description) - ) + 'network meter create -f json ' + + ' --description ' + description + ' ' + + name1 + )) self.assertEqual( name1, json_output.get('name'), @@ -43,17 +50,18 @@ def test_meter_delete(self): # Check if default shared values self.assertEqual( False, - json_output.get('shared') + json_output.get('shared'), ) self.assertEqual( 'fakedescription', - json_output.get('description') + json_output.get('description'), ) json_output_2 = json.loads(self.openstack( - 'network meter create -f json ' + name2 + ' --description ' - + description) - ) + 'network meter create -f json ' + + '--description ' + description + ' ' + + name2 + )) self.assertEqual( name2, json_output_2.get('name'), @@ -61,11 +69,11 @@ def test_meter_delete(self): # Check if default shared values self.assertEqual( False, - json_output_2.get('shared') + json_output_2.get('shared'), ) self.assertEqual( 'fakedescription', - json_output_2.get('description') + json_output_2.get('description'), ) raw_output = self.openstack( @@ -77,10 +85,15 @@ def test_meter_list(self): """Test create, list filters, delete""" name1 = uuid.uuid4().hex json_output = json.loads(self.openstack( - 'network meter create -f json --description Test1 --share ' - + name1) + 'network meter create -f json ' + + '--description Test1 ' + + '--share ' + + name1 + )) + self.addCleanup( + self.openstack, + 'network meter delete ' + name1 ) - self.addCleanup(self.openstack, 'network meter delete ' + name1) self.assertEqual( 'Test1', @@ -93,18 +106,20 @@ def test_meter_list(self): name2 = uuid.uuid4().hex json_output_2 = json.loads(self.openstack( - 'network meter create -f json --description Test2 --no-share ' - + name2) - ) + 'network meter create -f json ' + + '--description Test2 ' + + '--no-share ' + + name2 + )) self.addCleanup(self.openstack, 'network meter delete ' + name2) self.assertEqual( 'Test2', - json_output_2.get('description') + json_output_2.get('description'), ) self.assertEqual( False, - json_output_2.get('shared') + json_output_2.get('shared'), ) raw_output = json.loads(self.openstack('network meter list -f json')) @@ -117,42 +132,43 @@ def test_meter_show(self): name1 = uuid.uuid4().hex description = 'fakedescription' json_output = json.loads(self.openstack( - 'network meter create -f json ' + name1 + ' --description ' - + description) - ) + 'network meter create -f json ' + + ' --description ' + description + ' ' + + name1 + )) meter_id = json_output.get('id') self.addCleanup(self.openstack, 'network meter delete ' + name1) # Test show with ID json_output = json.loads(self.openstack( - 'network meter show -f json ' + meter_id) - ) + 'network meter show -f json ' + meter_id + )) self.assertEqual( False, - json_output.get('shared') + json_output.get('shared'), ) self.assertEqual( 'fakedescription', - json_output.get('description') + json_output.get('description'), ) self.assertEqual( name1, - json_output.get('name') + json_output.get('name'), ) # Test show with name json_output = json.loads(self.openstack( - 'network meter show -f json ' + name1) - ) + 'network meter show -f json ' + name1 + )) self.assertEqual( meter_id, - json_output.get('id') + json_output.get('id'), ) self.assertEqual( False, - json_output.get('shared') + json_output.get('shared'), ) self.assertEqual( 'fakedescription', - json_output.get('description') + json_output.get('description'), ) diff --git a/openstackclient/tests/functional/network/v2/test_network_meter_rule.py b/openstackclient/tests/functional/network/v2/test_network_meter_rule.py index d15cdf77a8..f0c1aa2f48 100644 --- a/openstackclient/tests/functional/network/v2/test_network_meter_rule.py +++ b/openstackclient/tests/functional/network/v2/test_network_meter_rule.py @@ -16,31 +16,40 @@ import json import uuid -from openstackclient.tests.functional import base +from openstackclient.tests.functional.network.v2 import common -class TestMeterRule(base.TestCase): +class TestMeterRule(common.NetworkTests): """Functional tests for meter rule""" + METER_NAME = uuid.uuid4().hex METER_ID = None METER_RULE_ID = None @classmethod def setUpClass(cls): - json_output = json.loads(cls.openstack( - 'network meter create -f json ' + cls.METER_NAME - )) - - cls.METER_ID = json_output.get('id') + common.NetworkTests.setUpClass() + if cls.haz_network: + json_output = json.loads(cls.openstack( + 'network meter create -f json ' + cls.METER_NAME + )) + cls.METER_ID = json_output.get('id') @classmethod def tearDownClass(cls): - raw_output = cls.openstack('network meter delete ' + cls.METER_ID) - cls.assertOutput('', raw_output) + common.NetworkTests.tearDownClass() + if cls.haz_network: + raw_output = cls.openstack('network meter delete ' + cls.METER_ID) + cls.assertOutput('', raw_output) + + def setUp(self): + super(TestMeterRule, self).setUp() + # Nothing in this class works with Nova Network + if not self.haz_network: + self.skipTest("No Network service present") def test_meter_rule_delete(self): """test create, delete""" - json_output = json.loads(self.openstack( 'network meter rule create -f json ' + '--remote-ip-prefix 10.0.0.0/8 ' + @@ -49,8 +58,10 @@ def test_meter_rule_delete(self): rule_id = json_output.get('id') re_ip = json_output.get('remote_ip_prefix') - self.addCleanup(self.openstack, - 'network meter rule delete ' + rule_id) + self.addCleanup( + self.openstack, + 'network meter rule delete ' + rule_id + ) self.assertIsNotNone(re_ip) self.assertIsNotNone(rule_id) self.assertEqual( @@ -65,8 +76,10 @@ def test_meter_rule_list(self): self.METER_ID )) rule_id_1 = json_output.get('id') - self.addCleanup(self.openstack, - 'network meter rule delete ' + rule_id_1) + self.addCleanup( + self.openstack, + 'network meter rule delete ' + rule_id_1 + ) self.assertEqual( '10.0.0.0/8', json_output.get('remote_ip_prefix') @@ -78,15 +91,18 @@ def test_meter_rule_list(self): self.METER_ID )) rule_id_2 = json_output_1.get('id') - self.addCleanup(self.openstack, - 'network meter rule delete ' + rule_id_2) + self.addCleanup( + self.openstack, + 'network meter rule delete ' + rule_id_2 + ) self.assertEqual( '11.0.0.0/8', json_output_1.get('remote_ip_prefix') ) - json_output = json.loads(self.openstack('network meter rule list -f ' - 'json')) + json_output = json.loads(self.openstack( + 'network meter rule list -f json' + )) rule_id_list = [item.get('ID') for item in json_output] ip_prefix_list = [item.get('Remote IP Prefix') for item in json_output] self.assertIn(rule_id_1, rule_id_list) @@ -95,7 +111,6 @@ def test_meter_rule_list(self): self.assertIn('11.0.0.0/8', ip_prefix_list) def test_meter_rule_show(self): - """Test create, show, delete""" json_output = json.loads(self.openstack( 'network meter rule create -f json ' + @@ -110,14 +125,16 @@ def test_meter_rule_show(self): json_output.get('direction') ) - json_output = json.loads(self.openstack('network meter rule show' - ' -f json ' + rule_id)) - + json_output = json.loads(self.openstack( + 'network meter rule show -f json ' + rule_id + )) self.assertEqual( '10.0.0.0/8', json_output.get('remote_ip_prefix') ) self.assertIsNotNone(rule_id) - self.addCleanup(self.openstack, - 'network meter rule delete ' + rule_id) + self.addCleanup( + self.openstack, + 'network meter rule delete ' + rule_id + ) From dd7da49325e3b4bc430b8a3d46ae19e43491c3b5 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 27 Apr 2017 17:36:36 -0500 Subject: [PATCH 1667/3095] Nova net functional tests round 2 * floating ip * ip availability * network qos policy * network qos rule * network qos rule type * network rbac Change-Id: Id3946bdff43bfef3a1d879c058bde4792bd299c6 --- .../functional/network/v2/test_floating_ip.py | 159 ++++++++++-------- .../network/v2/test_ip_availability.py | 8 +- .../network/v2/test_network_qos_policy.py | 11 +- .../network/v2/test_network_qos_rule.py | 47 ++++-- .../network/v2/test_network_qos_rule_type.py | 10 +- .../network/v2/test_network_rbac.py | 51 ++++-- 6 files changed, 180 insertions(+), 106 deletions(-) diff --git a/openstackclient/tests/functional/network/v2/test_floating_ip.py b/openstackclient/tests/functional/network/v2/test_floating_ip.py index 5da0e474ca..ccb954e954 100644 --- a/openstackclient/tests/functional/network/v2/test_floating_ip.py +++ b/openstackclient/tests/functional/network/v2/test_floating_ip.py @@ -14,10 +14,10 @@ import re import uuid -from openstackclient.tests.functional import base +from openstackclient.tests.functional.network.v2 import common -class FloatingIpTests(base.TestCase): +class FloatingIpTests(common.NetworkTests): """Functional tests for floating ip""" SUBNET_NAME = uuid.uuid4().hex NETWORK_NAME = uuid.uuid4().hex @@ -28,78 +28,97 @@ class FloatingIpTests(base.TestCase): @classmethod def setUpClass(cls): - # Set up some regex for matching below - cls.re_id = re.compile("id\s+\|\s+(\S+)") - cls.re_floating_ip = re.compile("floating_ip_address\s+\|\s+(\S+)") - cls.re_fixed_ip = re.compile("fixed_ip_address\s+\|\s+(\S+)") - cls.re_description = re.compile("description\s+\|\s+([^|]+?)\s+\|") - cls.re_network_id = re.compile("floating_network_id\s+\|\s+(\S+)") - cls.re_port_id = re.compile("\s+id\s+\|\s+(\S+)") - cls.re_fp_port_id = re.compile("\s+port_id\s+\|\s+(\S+)") - - # Create a network for the floating ip - raw_output = cls.openstack( - 'network create --external ' + cls.NETWORK_NAME - ) - cls.network_id = re.search(cls.re_id, raw_output).group(1) + common.NetworkTests.setUpClass() + if cls.haz_network: + # Set up some regex for matching below + cls.re_id = re.compile("id\s+\|\s+(\S+)") + cls.re_floating_ip = re.compile("floating_ip_address\s+\|\s+(\S+)") + cls.re_fixed_ip = re.compile("fixed_ip_address\s+\|\s+(\S+)") + cls.re_description = re.compile("description\s+\|\s+([^|]+?)\s+\|") + cls.re_network_id = re.compile("floating_network_id\s+\|\s+(\S+)") + cls.re_port_id = re.compile("\s+id\s+\|\s+(\S+)") + cls.re_fp_port_id = re.compile("\s+port_id\s+\|\s+(\S+)") - # Create a private network for the port - raw_output = cls.openstack( - 'network create ' + cls.PRIVATE_NETWORK_NAME - ) - cls.private_network_id = re.search(cls.re_id, raw_output).group(1) - - # Try random subnet range for subnet creating - # Because we can not determine ahead of time what subnets are already - # in use, possibly by another test running in parallel, try 4 times - for i in range(4): - # Make a random subnet - cls.subnet = ".".join(map( - str, - (random.randint(0, 223) for _ in range(3)) - )) + ".0/26" - cls.private_subnet = ".".join(map( - str, - (random.randint(0, 223) for _ in range(3)) - )) + ".0/26" - try: - # Create a subnet for the network - raw_output = cls.openstack( - 'subnet create ' + - '--network ' + cls.NETWORK_NAME + ' ' + - '--subnet-range ' + cls.subnet + ' ' + - cls.SUBNET_NAME - ) - # Create a subnet for the private network - priv_raw_output = cls.openstack( - 'subnet create ' + - '--network ' + cls.PRIVATE_NETWORK_NAME + ' ' + - '--subnet-range ' + cls.private_subnet + ' ' + - cls.PRIVATE_SUBNET_NAME - ) - except Exception: - if (i == 3): - # raise the exception at the last time - raise - pass - else: - # break and no longer retry if create sucessfully - break - - cls.subnet_id = re.search(cls.re_id, raw_output).group(1) - cls.private_subnet_id = re.search(cls.re_id, priv_raw_output).group(1) + # Create a network for the floating ip + raw_output = cls.openstack( + 'network create --external ' + cls.NETWORK_NAME + ) + cls.network_id = re.search(cls.re_id, raw_output).group(1) + + # Create a private network for the port + raw_output = cls.openstack( + 'network create ' + cls.PRIVATE_NETWORK_NAME + ) + cls.private_network_id = re.search(cls.re_id, raw_output).group(1) + + # Try random subnet range for subnet creating + # Because we can not determine ahead of time what subnets are + # already in use, possibly by another test running in parallel, + # try 4 times + for i in range(4): + # Make a random subnet + cls.subnet = ".".join(map( + str, + (random.randint(0, 223) for _ in range(3)) + )) + ".0/26" + cls.private_subnet = ".".join(map( + str, + (random.randint(0, 223) for _ in range(3)) + )) + ".0/26" + try: + # Create a subnet for the network + raw_output = cls.openstack( + 'subnet create ' + + '--network ' + cls.NETWORK_NAME + ' ' + + '--subnet-range ' + cls.subnet + ' ' + + cls.SUBNET_NAME + ) + # Create a subnet for the private network + priv_raw_output = cls.openstack( + 'subnet create ' + + '--network ' + cls.PRIVATE_NETWORK_NAME + ' ' + + '--subnet-range ' + cls.private_subnet + ' ' + + cls.PRIVATE_SUBNET_NAME + ) + except Exception: + if (i == 3): + # raise the exception at the last time + raise + pass + else: + # break and no longer retry if create sucessfully + break + + cls.subnet_id = re.search(cls.re_id, raw_output).group(1) + cls.private_subnet_id = re.search( + cls.re_id, priv_raw_output + ).group(1) @classmethod def tearDownClass(cls): - raw_output = cls.openstack('subnet delete ' + cls.SUBNET_NAME) - cls.assertOutput('', raw_output) - raw_output = cls.openstack('subnet delete ' + cls.PRIVATE_SUBNET_NAME) - cls.assertOutput('', raw_output) - raw_output = cls.openstack('network delete ' + cls.NETWORK_NAME) - cls.assertOutput('', raw_output) - raw_output = cls.openstack( - 'network delete ' + cls.PRIVATE_NETWORK_NAME) - cls.assertOutput('', raw_output) + if cls.haz_network: + raw_output = cls.openstack( + 'subnet delete ' + cls.SUBNET_NAME, + ) + cls.assertOutput('', raw_output) + raw_output = cls.openstack( + 'subnet delete ' + cls.PRIVATE_SUBNET_NAME, + ) + cls.assertOutput('', raw_output) + raw_output = cls.openstack( + 'network delete ' + cls.NETWORK_NAME, + ) + cls.assertOutput('', raw_output) + raw_output = cls.openstack( + 'network delete ' + cls.PRIVATE_NETWORK_NAME, + ) + cls.assertOutput('', raw_output) + + def setUp(self): + super(FloatingIpTests, self).setUp() + # Nothing in this class works with Nova Network + if not self.haz_network: + self.skipTest("No Network service present") def test_floating_ip_delete(self): """Test create, delete multiple""" diff --git a/openstackclient/tests/functional/network/v2/test_ip_availability.py b/openstackclient/tests/functional/network/v2/test_ip_availability.py index 7440f2507c..b9cac02443 100644 --- a/openstackclient/tests/functional/network/v2/test_ip_availability.py +++ b/openstackclient/tests/functional/network/v2/test_ip_availability.py @@ -13,14 +13,18 @@ import json import uuid -from openstackclient.tests.functional import base +from openstackclient.tests.functional.network.v2 import common -class IPAvailabilityTests(base.TestCase): +class IPAvailabilityTests(common.NetworkTests): """Functional tests for IP availability. """ @classmethod def setUpClass(cls): + common.NetworkTests.setUpClass() + if not cls.haz_network: + common.NetworkTests.skipTest(cls, "No Network service present") + # Create a network for the subnet. cls.NAME = uuid.uuid4().hex cls.NETWORK_NAME = uuid.uuid4().hex diff --git a/openstackclient/tests/functional/network/v2/test_network_qos_policy.py b/openstackclient/tests/functional/network/v2/test_network_qos_policy.py index 07dea31b76..e5b97573ec 100644 --- a/openstackclient/tests/functional/network/v2/test_network_qos_policy.py +++ b/openstackclient/tests/functional/network/v2/test_network_qos_policy.py @@ -15,10 +15,10 @@ import uuid -from openstackclient.tests.functional import base +from openstackclient.tests.functional.network.v2 import common -class QosPolicyTests(base.TestCase): +class NetworkQosPolicyTests(common.NetworkTests): """Functional tests for QoS policy. """ NAME = uuid.uuid4().hex HEADERS = ['Name'] @@ -26,11 +26,18 @@ class QosPolicyTests(base.TestCase): @classmethod def setUpClass(cls): + common.NetworkTests.setUpClass() opts = cls.get_opts(cls.FIELDS) raw_output = cls.openstack('network qos policy create ' + cls.NAME + opts) cls.assertOutput(cls.NAME + "\n", raw_output) + def setUp(self): + super(NetworkQosPolicyTests, self).setUp() + # Nothing in this class works with Nova Network + if not self.haz_network: + self.skipTest("No Network service present") + @classmethod def tearDownClass(cls): raw_output = cls.openstack('network qos policy delete ' + cls.NAME) diff --git a/openstackclient/tests/functional/network/v2/test_network_qos_rule.py b/openstackclient/tests/functional/network/v2/test_network_qos_rule.py index af0c9baca7..f050635668 100644 --- a/openstackclient/tests/functional/network/v2/test_network_qos_rule.py +++ b/openstackclient/tests/functional/network/v2/test_network_qos_rule.py @@ -15,10 +15,10 @@ import uuid -from openstackclient.tests.functional import base +from openstackclient.tests.functional.network.v2 import common -class NetworkQosRuleTestsMinimumBandwidth(base.TestCase): +class NetworkQosRuleTestsMinimumBandwidth(common.NetworkTests): """Functional tests for QoS minimum bandwidth rule.""" RULE_ID = None QOS_POLICY_NAME = 'qos_policy_' + uuid.uuid4().hex @@ -31,6 +31,7 @@ class NetworkQosRuleTestsMinimumBandwidth(base.TestCase): @classmethod def setUpClass(cls): + common.NetworkTests.setUpClass() opts = cls.get_opts(cls.FIELDS) cls.openstack('network qos policy create ' + cls.QOS_POLICY_NAME) cls.RULE_ID = cls.openstack('network qos rule create --type ' + @@ -46,20 +47,26 @@ def tearDownClass(cls): cls.openstack('network qos policy delete ' + cls.QOS_POLICY_NAME) cls.assertOutput('', raw_output) - def test_qos_policy_list(self): + def setUp(self): + super(NetworkQosRuleTestsMinimumBandwidth, self).setUp() + # Nothing in this class works with Nova Network + if not self.haz_network: + self.skipTest("No Network service present") + + def test_qos_rule_list(self): opts = self.get_opts(self.HEADERS) raw_output = self.openstack('network qos rule list ' + self.QOS_POLICY_NAME + opts) self.assertIn(self.RULE_ID, raw_output) - def test_qos_policy_show(self): + def test_qos_rule_show(self): opts = self.get_opts(self.FIELDS) raw_output = self.openstack('network qos rule show ' + self.QOS_POLICY_NAME + ' ' + self.RULE_ID + opts) self.assertEqual(self.RULE_ID, raw_output) - def test_qos_policy_set(self): + def test_qos_rule_set(self): self.openstack('network qos rule set --min-kbps ' + str(self.MIN_KBPS_MODIFIED) + ' ' + self.QOS_POLICY_NAME + ' ' + self.RULE_ID) @@ -70,7 +77,7 @@ def test_qos_policy_set(self): self.assertEqual(str(self.MIN_KBPS_MODIFIED) + "\n", raw_output) -class NetworkQosRuleTestsDSCPMarking(base.TestCase): +class NetworkQosRuleTestsDSCPMarking(common.NetworkTests): """Functional tests for QoS DSCP marking rule.""" RULE_ID = None QOS_POLICY_NAME = 'qos_policy_' + uuid.uuid4().hex @@ -82,6 +89,7 @@ class NetworkQosRuleTestsDSCPMarking(base.TestCase): @classmethod def setUpClass(cls): + common.NetworkTests.setUpClass() opts = cls.get_opts(cls.FIELDS) cls.openstack('network qos policy create ' + cls.QOS_POLICY_NAME) cls.RULE_ID = cls.openstack('network qos rule create --type ' + @@ -97,20 +105,26 @@ def tearDownClass(cls): cls.openstack('network qos policy delete ' + cls.QOS_POLICY_NAME) cls.assertOutput('', raw_output) - def test_qos_policy_list(self): + def setUp(self): + super(NetworkQosRuleTestsDSCPMarking, self).setUp() + # Nothing in this class works with Nova Network + if not self.haz_network: + self.skipTest("No Network service present") + + def test_qos_rule_list(self): opts = self.get_opts(self.HEADERS) raw_output = self.openstack('network qos rule list ' + self.QOS_POLICY_NAME + opts) self.assertIn(self.RULE_ID, raw_output) - def test_qos_policy_show(self): + def test_qos_rule_show(self): opts = self.get_opts(self.FIELDS) raw_output = self.openstack('network qos rule show ' + self.QOS_POLICY_NAME + ' ' + self.RULE_ID + opts) self.assertEqual(self.RULE_ID, raw_output) - def test_qos_policy_set(self): + def test_qos_rule_set(self): self.openstack('network qos rule set --dscp-mark ' + str(self.DSCP_MARK_MODIFIED) + ' ' + self.QOS_POLICY_NAME + ' ' + self.RULE_ID) @@ -121,7 +135,7 @@ def test_qos_policy_set(self): self.assertEqual(str(self.DSCP_MARK_MODIFIED) + "\n", raw_output) -class NetworkQosRuleTestsBandwidthLimit(base.TestCase): +class NetworkQosRuleTestsBandwidthLimit(common.NetworkTests): """Functional tests for QoS bandwidth limit rule.""" RULE_ID = None QOS_POLICY_NAME = 'qos_policy_' + uuid.uuid4().hex @@ -135,6 +149,7 @@ class NetworkQosRuleTestsBandwidthLimit(base.TestCase): @classmethod def setUpClass(cls): + common.NetworkTests.setUpClass() opts = cls.get_opts(cls.FIELDS) cls.openstack('network qos policy create ' + cls.QOS_POLICY_NAME) cls.RULE_ID = cls.openstack('network qos rule create --type ' + @@ -151,20 +166,26 @@ def tearDownClass(cls): cls.openstack('network qos policy delete ' + cls.QOS_POLICY_NAME) cls.assertOutput('', raw_output) - def test_qos_policy_list(self): + def setUp(self): + super(NetworkQosRuleTestsBandwidthLimit, self).setUp() + # Nothing in this class works with Nova Network + if not self.haz_network: + self.skipTest("No Network service present") + + def test_qos_rule_list(self): opts = self.get_opts(self.HEADERS) raw_output = self.openstack('network qos rule list ' + self.QOS_POLICY_NAME + opts) self.assertIn(self.RULE_ID, raw_output) - def test_qos_policy_show(self): + def test_qos_rule_show(self): opts = self.get_opts(self.FIELDS) raw_output = self.openstack('network qos rule show ' + self.QOS_POLICY_NAME + ' ' + self.RULE_ID + opts) self.assertEqual(self.RULE_ID, raw_output) - def test_qos_policy_set(self): + def test_qos_rule_set(self): self.openstack('network qos rule set --max-kbps ' + str(self.MAX_KBPS_MODIFIED) + ' --max-burst-kbits ' + str(self.MAX_BURST_KBITS_MODIFIED) + ' ' + diff --git a/openstackclient/tests/functional/network/v2/test_network_qos_rule_type.py b/openstackclient/tests/functional/network/v2/test_network_qos_rule_type.py index 7dff0cbdad..d76129367c 100644 --- a/openstackclient/tests/functional/network/v2/test_network_qos_rule_type.py +++ b/openstackclient/tests/functional/network/v2/test_network_qos_rule_type.py @@ -13,15 +13,21 @@ # License for the specific language governing permissions and limitations # under the License. -from openstackclient.tests.functional import base +from openstackclient.tests.functional.network.v2 import common -class NetworkQosRuleTypeTests(base.TestCase): +class NetworkQosRuleTypeTests(common.NetworkTests): """Functional tests for Network QoS rule type. """ AVAILABLE_RULE_TYPES = ['dscp_marking', 'bandwidth_limit'] + def setUp(self): + super(NetworkQosRuleTypeTests, self).setUp() + # Nothing in this class works with Nova Network + if not self.haz_network: + self.skipTest("No Network service present") + def test_qos_rule_type_list(self): raw_output = self.openstack('network qos rule type list') for rule_type in self.AVAILABLE_RULE_TYPES: diff --git a/openstackclient/tests/functional/network/v2/test_network_rbac.py b/openstackclient/tests/functional/network/v2/test_network_rbac.py index 6f9f05e792..0d5492e511 100644 --- a/openstackclient/tests/functional/network/v2/test_network_rbac.py +++ b/openstackclient/tests/functional/network/v2/test_network_rbac.py @@ -12,10 +12,10 @@ import uuid -from openstackclient.tests.functional import base +from openstackclient.tests.functional.network.v2 import common -class NetworkRBACTests(base.TestCase): +class NetworkRBACTests(common.NetworkTests): """Functional tests for network rbac. """ NET_NAME = uuid.uuid4().hex PROJECT_NAME = uuid.uuid4().hex @@ -26,24 +26,41 @@ class NetworkRBACTests(base.TestCase): @classmethod def setUpClass(cls): - opts = cls.get_opts(cls.FIELDS) - raw_output = cls.openstack('network create ' + cls.NET_NAME + opts) - cls.OBJECT_ID = raw_output.strip('\n') - opts = cls.get_opts(['id', 'object_id']) - raw_output = cls.openstack('network rbac create ' + - cls.OBJECT_ID + - ' --action access_as_shared' + - ' --target-project admin' + - ' --type network' + opts) - cls.ID, object_id, rol = tuple(raw_output.split('\n')) - cls.assertOutput(cls.OBJECT_ID, object_id) + common.NetworkTests.setUpClass() + if cls.haz_network: + opts = cls.get_opts(cls.FIELDS) + raw_output = cls.openstack( + 'network create ' + cls.NET_NAME + opts + ) + cls.OBJECT_ID = raw_output.strip('\n') + opts = cls.get_opts(['id', 'object_id']) + raw_output = cls.openstack( + 'network rbac create ' + + cls.OBJECT_ID + + ' --action access_as_shared' + + ' --target-project admin' + + ' --type network' + opts + ) + cls.ID, object_id, rol = tuple(raw_output.split('\n')) + cls.assertOutput(cls.OBJECT_ID, object_id) @classmethod def tearDownClass(cls): - raw_output_rbac = cls.openstack('network rbac delete ' + cls.ID) - raw_output_network = cls.openstack('network delete ' + cls.OBJECT_ID) - cls.assertOutput('', raw_output_rbac) - cls.assertOutput('', raw_output_network) + if cls.haz_network: + raw_output_rbac = cls.openstack( + 'network rbac delete ' + cls.ID + ) + raw_output_network = cls.openstack( + 'network delete ' + cls.OBJECT_ID + ) + cls.assertOutput('', raw_output_rbac) + cls.assertOutput('', raw_output_network) + + def setUp(self): + super(NetworkRBACTests, self).setUp() + # Nothing in this class works with Nova Network + if not self.haz_network: + self.skipTest("No Network service present") def test_network_rbac_list(self): opts = self.get_opts(self.HEADERS) From 190711ecd71af2eff4683e570ef48f041fa8d91b Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 28 Apr 2017 12:41:57 -0500 Subject: [PATCH 1668/3095] Nova net functional tests round 3 * network segment * network service * port * router * security group * security group rule * subnet * subnet pool * extension The extension tests were duplicated to have both compute and network extensions tests so the nova-net case will still exercise the extension commands. Also clean up formatting from previous reviews to make the Network functional tests look and act consistently. Change-Id: I286c40572faa31ddcef595cec740da933b2defc1 --- .../tests/functional/common/test_extension.py | 65 ++++++++-- .../functional/compute/v2/test_server.py | 14 +++ .../network/v2/test_address_scope.py | 2 +- .../network/v2/test_ip_availability.py | 51 +++++--- .../functional/network/v2/test_network.py | 14 +-- .../network/v2/test_network_meter_rule.py | 8 +- .../network/v2/test_network_qos_policy.py | 26 ++-- .../network/v2/test_network_qos_rule.py | 111 ++++++++++++------ .../network/v2/test_network_segment.py | 60 ++++++---- .../v2/test_network_service_provider.py | 10 +- .../tests/functional/network/v2/test_port.py | 27 +++-- .../functional/network/v2/test_router.py | 12 +- .../network/v2/test_security_group.py | 47 +++++--- .../network/v2/test_security_group_rule.py | 65 ++++++---- .../functional/network/v2/test_subnet.py | 38 ++++-- .../functional/network/v2/test_subnet_pool.py | 10 +- 16 files changed, 387 insertions(+), 173 deletions(-) diff --git a/openstackclient/tests/functional/common/test_extension.py b/openstackclient/tests/functional/common/test_extension.py index 7c527eaecb..cc4cb7e160 100644 --- a/openstackclient/tests/functional/common/test_extension.py +++ b/openstackclient/tests/functional/common/test_extension.py @@ -18,25 +18,66 @@ from openstackclient.tests.functional import base -class TestExtension(base.TestCase): - """Functional tests for extension.""" +class ExtensionTests(base.TestCase): + """Functional tests for extension""" - def test_extension_list(self): - """Test extension list.""" + @classmethod + def setUpClass(cls): + # super(NetworkTests, cls).setUp() + cls.haz_network = base.is_service_enabled('network') + + def test_extension_list_compute(self): + """Test compute extension list""" json_output = json.loads(self.openstack( - 'extension list -f json ' + '--network') + 'extension list -f json ' + + '--compute' + )) + name_list = [item.get('Name') for item in json_output] + self.assertIn( + 'ImageSize', + name_list, ) - self.assertEqual( + + def test_extension_list_network(self): + """Test network extension list""" + if not self.haz_network: + self.skipTest("No Network service present") + + json_output = json.loads(self.openstack( + 'extension list -f json ' + + '--network' + )) + name_list = [item.get('Name') for item in json_output] + self.assertIn( 'Default Subnetpools', - json_output[0]['Name'], + name_list, ) - def test_extension_show(self): - """Test extension show.""" + # NOTE(dtroyer): Only network extensions are currently supported but + # I am going to leave this here anyway as a reminder + # fix that. + # def test_extension_show_compute(self): + # """Test compute extension show""" + # json_output = json.loads(self.openstack( + # 'extension show -f json ' + + # 'ImageSize' + # )) + # self.assertEqual( + # 'OS-EXT-IMG-SIZE', + # json_output.get('Alias'), + # ) + + def test_extension_show_network(self): + """Test network extension show""" + if not self.haz_network: + self.skipTest("No Network service present") + name = 'agent' json_output = json.loads(self.openstack( - 'extension show -f json ' + name) - ) + 'extension show -f json ' + + name + )) self.assertEqual( name, - json_output.get('Alias')) + json_output.get('Alias'), + ) diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py index 76255c69e6..a86c0c679d 100644 --- a/openstackclient/tests/functional/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -15,6 +15,7 @@ from tempest.lib import exceptions +from openstackclient.tests.functional import base from openstackclient.tests.functional.compute.v2 import common from openstackclient.tests.functional.volume.v2 import test_volume @@ -22,6 +23,10 @@ class ServerTests(common.ComputeTestCase): """Functional tests for openstack server commands""" + @classmethod + def setUpClass(cls): + cls.haz_network = base.is_service_enabled('network') + def test_server_list(self): """Test server list, set""" cmd_output = self.server_create() @@ -202,6 +207,15 @@ def test_server_attach_detach_floating_ip(self): name = cmd_output['name'] self.wait_for_status(name, "ACTIVE") + if not self.haz_network: + # nova-net needs a public subnet + cmd_output = json.loads(self.openstack( + 'network create -f json ' + + '--subnet 8.6.7.5/28 ' + + 'public' + )) + self.addCleanup(self.openstack, 'network delete public') + # attach ip cmd_output = json.loads(self.openstack( 'floating ip create -f json ' + diff --git a/openstackclient/tests/functional/network/v2/test_address_scope.py b/openstackclient/tests/functional/network/v2/test_address_scope.py index e5156d7f2e..ebd2ba86c6 100644 --- a/openstackclient/tests/functional/network/v2/test_address_scope.py +++ b/openstackclient/tests/functional/network/v2/test_address_scope.py @@ -17,7 +17,7 @@ class AddressScopeTests(common.NetworkTests): - """Functional tests for address scope. """ + """Functional tests for address scope""" # NOTE(dtroyer): Do not normalize the setup and teardown of the resource # creation and deletion. Little is gained when each test diff --git a/openstackclient/tests/functional/network/v2/test_ip_availability.py b/openstackclient/tests/functional/network/v2/test_ip_availability.py index b9cac02443..1aa0f64a7b 100644 --- a/openstackclient/tests/functional/network/v2/test_ip_availability.py +++ b/openstackclient/tests/functional/network/v2/test_ip_availability.py @@ -17,31 +17,46 @@ class IPAvailabilityTests(common.NetworkTests): - """Functional tests for IP availability. """ + """Functional tests for IP availability""" @classmethod def setUpClass(cls): common.NetworkTests.setUpClass() - if not cls.haz_network: - common.NetworkTests.skipTest(cls, "No Network service present") - - # Create a network for the subnet. - cls.NAME = uuid.uuid4().hex - cls.NETWORK_NAME = uuid.uuid4().hex - cls.openstack('network create ' + cls.NETWORK_NAME) - cmd_output = json.loads(cls.openstack( - 'subnet create -f json --network ' + cls.NETWORK_NAME + - ' --subnet-range 10.10.10.0/24 ' + - cls.NAME - )) - cls.assertOutput(cls.NAME, cmd_output['name']) + if cls.haz_network: + # Create a network for the subnet. + cls.NAME = uuid.uuid4().hex + cls.NETWORK_NAME = uuid.uuid4().hex + cls.openstack( + 'network create ' + + cls.NETWORK_NAME + ) + cmd_output = json.loads(cls.openstack( + 'subnet create -f json ' + + '--network ' + cls.NETWORK_NAME + ' ' + + '--subnet-range 10.10.10.0/24 ' + + cls.NAME + )) + cls.assertOutput(cls.NAME, cmd_output['name']) @classmethod def tearDownClass(cls): - raw_subnet = cls.openstack('subnet delete ' + cls.NAME) - raw_network = cls.openstack('network delete ' + cls.NETWORK_NAME) - cls.assertOutput('', raw_subnet) - cls.assertOutput('', raw_network) + if cls.haz_network: + raw_subnet = cls.openstack( + 'subnet delete ' + + cls.NAME + ) + raw_network = cls.openstack( + 'network delete ' + + cls.NETWORK_NAME + ) + cls.assertOutput('', raw_subnet) + cls.assertOutput('', raw_network) + + def setUp(self): + super(IPAvailabilityTests, self).setUp() + # Nothing in this class works with Nova Network + if not self.haz_network: + self.skipTest("No Network service present") def test_ip_availability_list(self): """Test ip availability list""" diff --git a/openstackclient/tests/functional/network/v2/test_network.py b/openstackclient/tests/functional/network/v2/test_network.py index 91500e0dd2..b23323a00e 100644 --- a/openstackclient/tests/functional/network/v2/test_network.py +++ b/openstackclient/tests/functional/network/v2/test_network.py @@ -13,17 +13,17 @@ import json import uuid -from openstackclient.tests.functional import base +from openstackclient.tests.functional.network.v2 import common -class NetworkTests(base.TestCase): +class NetworkTests(common.NetworkTests): """Functional tests for network""" - @classmethod - def setUpClass(cls): - cls.haz_network = base.is_service_enabled('network') - cls.PROJECT_NAME =\ - cls.get_openstack_configuration_value('auth.project_name') + def setUp(self): + super(NetworkTests, self).setUp() + # Nothing in this class works with Nova Network + if not self.haz_network: + self.skipTest("No Network service present") def test_network_create_compute(self): """Test Nova-net create options, delete""" diff --git a/openstackclient/tests/functional/network/v2/test_network_meter_rule.py b/openstackclient/tests/functional/network/v2/test_network_meter_rule.py index f0c1aa2f48..b7090707b1 100644 --- a/openstackclient/tests/functional/network/v2/test_network_meter_rule.py +++ b/openstackclient/tests/functional/network/v2/test_network_meter_rule.py @@ -31,7 +31,8 @@ def setUpClass(cls): common.NetworkTests.setUpClass() if cls.haz_network: json_output = json.loads(cls.openstack( - 'network meter create -f json ' + cls.METER_NAME + 'network meter create -f json ' + + cls.METER_NAME )) cls.METER_ID = json_output.get('id') @@ -39,7 +40,10 @@ def setUpClass(cls): def tearDownClass(cls): common.NetworkTests.tearDownClass() if cls.haz_network: - raw_output = cls.openstack('network meter delete ' + cls.METER_ID) + raw_output = cls.openstack( + 'network meter delete ' + + cls.METER_ID + ) cls.assertOutput('', raw_output) def setUp(self): diff --git a/openstackclient/tests/functional/network/v2/test_network_qos_policy.py b/openstackclient/tests/functional/network/v2/test_network_qos_policy.py index e5b97573ec..282efbfa7f 100644 --- a/openstackclient/tests/functional/network/v2/test_network_qos_policy.py +++ b/openstackclient/tests/functional/network/v2/test_network_qos_policy.py @@ -27,10 +27,23 @@ class NetworkQosPolicyTests(common.NetworkTests): @classmethod def setUpClass(cls): common.NetworkTests.setUpClass() - opts = cls.get_opts(cls.FIELDS) - raw_output = cls.openstack('network qos policy create ' + cls.NAME + - opts) - cls.assertOutput(cls.NAME + "\n", raw_output) + if cls.haz_network: + opts = cls.get_opts(cls.FIELDS) + raw_output = cls.openstack( + 'network qos policy create ' + + cls.NAME + + opts + ) + cls.assertOutput(cls.NAME + "\n", raw_output) + + @classmethod + def tearDownClass(cls): + if cls.haz_network: + raw_output = cls.openstack( + 'network qos policy delete ' + + cls.NAME + ) + cls.assertOutput('', raw_output) def setUp(self): super(NetworkQosPolicyTests, self).setUp() @@ -38,11 +51,6 @@ def setUp(self): if not self.haz_network: self.skipTest("No Network service present") - @classmethod - def tearDownClass(cls): - raw_output = cls.openstack('network qos policy delete ' + cls.NAME) - cls.assertOutput('', raw_output) - def test_qos_policy_list(self): opts = self.get_opts(self.HEADERS) raw_output = self.openstack('network qos policy list' + opts) diff --git a/openstackclient/tests/functional/network/v2/test_network_qos_rule.py b/openstackclient/tests/functional/network/v2/test_network_qos_rule.py index f050635668..c6437d9c0e 100644 --- a/openstackclient/tests/functional/network/v2/test_network_qos_rule.py +++ b/openstackclient/tests/functional/network/v2/test_network_qos_rule.py @@ -19,7 +19,7 @@ class NetworkQosRuleTestsMinimumBandwidth(common.NetworkTests): - """Functional tests for QoS minimum bandwidth rule.""" + """Functional tests for QoS minimum bandwidth rule""" RULE_ID = None QOS_POLICY_NAME = 'qos_policy_' + uuid.uuid4().hex MIN_KBPS = 2800 @@ -32,20 +32,35 @@ class NetworkQosRuleTestsMinimumBandwidth(common.NetworkTests): @classmethod def setUpClass(cls): common.NetworkTests.setUpClass() - opts = cls.get_opts(cls.FIELDS) - cls.openstack('network qos policy create ' + cls.QOS_POLICY_NAME) - cls.RULE_ID = cls.openstack('network qos rule create --type ' + - cls.TYPE + ' --min-kbps ' + - str(cls.MIN_KBPS) + ' ' + cls.DIRECTION + - ' ' + cls.QOS_POLICY_NAME + opts) - cls.assertsOutputNotNone(cls.RULE_ID) + if cls.haz_network: + opts = cls.get_opts(cls.FIELDS) + cls.openstack( + 'network qos policy create ' + + cls.QOS_POLICY_NAME + ) + cls.RULE_ID = cls.openstack( + 'network qos rule create ' + + '--type ' + cls.TYPE + ' ' + + '--min-kbps ' + str(cls.MIN_KBPS) + ' ' + + cls.DIRECTION + ' ' + + cls.QOS_POLICY_NAME + + opts + ) + cls.assertsOutputNotNone(cls.RULE_ID) @classmethod def tearDownClass(cls): - raw_output = cls.openstack('network qos rule delete ' + - cls.QOS_POLICY_NAME + ' ' + cls.RULE_ID) - cls.openstack('network qos policy delete ' + cls.QOS_POLICY_NAME) - cls.assertOutput('', raw_output) + if cls.haz_network: + raw_output = cls.openstack( + 'network qos rule delete ' + + cls.QOS_POLICY_NAME + ' ' + + cls.RULE_ID + ) + cls.openstack( + 'network qos policy delete ' + + cls.QOS_POLICY_NAME + ) + cls.assertOutput('', raw_output) def setUp(self): super(NetworkQosRuleTestsMinimumBandwidth, self).setUp() @@ -78,7 +93,7 @@ def test_qos_rule_set(self): class NetworkQosRuleTestsDSCPMarking(common.NetworkTests): - """Functional tests for QoS DSCP marking rule.""" + """Functional tests for QoS DSCP marking rule""" RULE_ID = None QOS_POLICY_NAME = 'qos_policy_' + uuid.uuid4().hex DSCP_MARK = 8 @@ -90,20 +105,31 @@ class NetworkQosRuleTestsDSCPMarking(common.NetworkTests): @classmethod def setUpClass(cls): common.NetworkTests.setUpClass() - opts = cls.get_opts(cls.FIELDS) - cls.openstack('network qos policy create ' + cls.QOS_POLICY_NAME) - cls.RULE_ID = cls.openstack('network qos rule create --type ' + - cls.TYPE + ' --dscp-mark ' + - str(cls.DSCP_MARK) + ' ' + - cls.QOS_POLICY_NAME + opts) - cls.assertsOutputNotNone(cls.RULE_ID) + if cls.haz_network: + opts = cls.get_opts(cls.FIELDS) + cls.openstack( + 'network qos policy create ' + + cls.QOS_POLICY_NAME + ) + cls.RULE_ID = cls.openstack( + 'network qos rule create ' + + '--type ' + cls.TYPE + ' ' + + '--dscp-mark ' + str(cls.DSCP_MARK) + ' ' + + cls.QOS_POLICY_NAME + + opts + ) + cls.assertsOutputNotNone(cls.RULE_ID) @classmethod def tearDownClass(cls): - raw_output = cls.openstack('network qos rule delete ' + - cls.QOS_POLICY_NAME + ' ' + cls.RULE_ID) - cls.openstack('network qos policy delete ' + cls.QOS_POLICY_NAME) - cls.assertOutput('', raw_output) + if cls.haz_network: + raw_output = cls.openstack( + 'network qos rule delete ' + + cls.QOS_POLICY_NAME + ' ' + + cls.RULE_ID + ) + cls.openstack('network qos policy delete ' + cls.QOS_POLICY_NAME) + cls.assertOutput('', raw_output) def setUp(self): super(NetworkQosRuleTestsDSCPMarking, self).setUp() @@ -136,7 +162,7 @@ def test_qos_rule_set(self): class NetworkQosRuleTestsBandwidthLimit(common.NetworkTests): - """Functional tests for QoS bandwidth limit rule.""" + """Functional tests for QoS bandwidth limit rule""" RULE_ID = None QOS_POLICY_NAME = 'qos_policy_' + uuid.uuid4().hex MAX_KBPS = 10000 @@ -150,21 +176,32 @@ class NetworkQosRuleTestsBandwidthLimit(common.NetworkTests): @classmethod def setUpClass(cls): common.NetworkTests.setUpClass() - opts = cls.get_opts(cls.FIELDS) - cls.openstack('network qos policy create ' + cls.QOS_POLICY_NAME) - cls.RULE_ID = cls.openstack('network qos rule create --type ' + - cls.TYPE + ' --max-kbps ' + - str(cls.MAX_KBPS) + ' --max-burst-kbits ' + - str(cls.MAX_BURST_KBITS) + ' ' + - cls.QOS_POLICY_NAME + opts) - cls.assertsOutputNotNone(cls.RULE_ID) + if cls.haz_network: + opts = cls.get_opts(cls.FIELDS) + cls.openstack( + 'network qos policy create ' + + cls.QOS_POLICY_NAME + ) + cls.RULE_ID = cls.openstack( + 'network qos rule create ' + + '--type ' + cls.TYPE + ' ' + + '--max-kbps ' + str(cls.MAX_KBPS) + ' ' + + '--max-burst-kbits ' + str(cls.MAX_BURST_KBITS) + ' ' + + cls.QOS_POLICY_NAME + + opts + ) + cls.assertsOutputNotNone(cls.RULE_ID) @classmethod def tearDownClass(cls): - raw_output = cls.openstack('network qos rule delete ' + - cls.QOS_POLICY_NAME + ' ' + cls.RULE_ID) - cls.openstack('network qos policy delete ' + cls.QOS_POLICY_NAME) - cls.assertOutput('', raw_output) + if cls.haz_network: + raw_output = cls.openstack( + 'network qos rule delete ' + + cls.QOS_POLICY_NAME + ' ' + + cls.RULE_ID + ) + cls.openstack('network qos policy delete ' + cls.QOS_POLICY_NAME) + cls.assertOutput('', raw_output) def setUp(self): super(NetworkQosRuleTestsBandwidthLimit, self).setUp() diff --git a/openstackclient/tests/functional/network/v2/test_network_segment.py b/openstackclient/tests/functional/network/v2/test_network_segment.py index b6f19ac4bf..de150d3106 100644 --- a/openstackclient/tests/functional/network/v2/test_network_segment.py +++ b/openstackclient/tests/functional/network/v2/test_network_segment.py @@ -12,11 +12,11 @@ import uuid -from openstackclient.tests.functional import base +from openstackclient.tests.functional.network.v2 import common -class NetworkSegmentTests(base.TestCase): - """Functional tests for network segment. """ +class NetworkSegmentTests(common.NetworkTests): + """Functional tests for network segment""" NETWORK_NAME = uuid.uuid4().hex PHYSICAL_NETWORK_NAME = uuid.uuid4().hex NETWORK_SEGMENT_ID = None @@ -25,30 +25,46 @@ class NetworkSegmentTests(base.TestCase): @classmethod def setUpClass(cls): - # Create a network for the segment. - opts = cls.get_opts(['id']) - raw_output = cls.openstack('network create ' + cls.NETWORK_NAME + opts) - cls.NETWORK_ID = raw_output.strip('\n') + common.NetworkTests.setUpClass() + if cls.haz_network: + # Create a network for the segment. + opts = cls.get_opts(['id']) + raw_output = cls.openstack( + 'network create ' + cls.NETWORK_NAME + opts + ) + cls.NETWORK_ID = raw_output.strip('\n') - # NOTE(rtheis): The segment extension is not yet enabled by default. - # Skip the tests if not enabled. - extensions = cls.get_openstack_extention_names() - if 'Segment' in extensions: - cls.NETWORK_SEGMENT_EXTENSION = 'Segment' + # NOTE(rtheis): The segment extension is not yet enabled + # by default. + # Skip the tests if not enabled. + extensions = cls.get_openstack_extention_names() + if 'Segment' in extensions: + cls.NETWORK_SEGMENT_EXTENSION = 'Segment' - if cls.NETWORK_SEGMENT_EXTENSION: - # Get the segment for the network. - opts = cls.get_opts(['ID', 'Network']) - raw_output = cls.openstack('network segment list ' - ' --network ' + cls.NETWORK_NAME + - ' ' + opts) - raw_output_row = raw_output.split('\n')[0] - cls.NETWORK_SEGMENT_ID = raw_output_row.split(' ')[0] + if cls.NETWORK_SEGMENT_EXTENSION: + # Get the segment for the network. + opts = cls.get_opts(['ID', 'Network']) + raw_output = cls.openstack( + 'network segment list ' + '--network ' + cls.NETWORK_NAME + ' ' + + opts + ) + raw_output_row = raw_output.split('\n')[0] + cls.NETWORK_SEGMENT_ID = raw_output_row.split(' ')[0] @classmethod def tearDownClass(cls): - raw_output = cls.openstack('network delete ' + cls.NETWORK_NAME) - cls.assertOutput('', raw_output) + if cls.haz_network: + raw_output = cls.openstack( + 'network delete ' + cls.NETWORK_NAME + ) + cls.assertOutput('', raw_output) + + def setUp(self): + super(NetworkSegmentTests, self).setUp() + # Nothing in this class works with Nova Network + if not self.haz_network: + self.skipTest("No Network service present") def test_network_segment_create_delete(self): if self.NETWORK_SEGMENT_EXTENSION: diff --git a/openstackclient/tests/functional/network/v2/test_network_service_provider.py b/openstackclient/tests/functional/network/v2/test_network_service_provider.py index 6fbff6c8d4..8ed44dd909 100644 --- a/openstackclient/tests/functional/network/v2/test_network_service_provider.py +++ b/openstackclient/tests/functional/network/v2/test_network_service_provider.py @@ -13,14 +13,20 @@ # License for the specific language governing permissions and limitations # under the License. -from openstackclient.tests.functional import base +from openstackclient.tests.functional.network.v2 import common -class TestNetworkServiceProvider(base.TestCase): +class TestNetworkServiceProvider(common.NetworkTests): """Functional tests for network service provider""" SERVICE_TYPE = 'L3_ROUTER_NAT' + def setUp(self): + super(TestNetworkServiceProvider, self).setUp() + # Nothing in this class works with Nova Network + if not self.haz_network: + self.skipTest("No Network service present") + 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/functional/network/v2/test_port.py b/openstackclient/tests/functional/network/v2/test_port.py index 62c0cbe51b..6659e3e0a9 100644 --- a/openstackclient/tests/functional/network/v2/test_port.py +++ b/openstackclient/tests/functional/network/v2/test_port.py @@ -13,23 +13,36 @@ import json import uuid -from openstackclient.tests.functional import base +from openstackclient.tests.functional.network.v2 import common -class PortTests(base.TestCase): - """Functional tests for port. """ +class PortTests(common.NetworkTests): + """Functional tests for port""" NAME = uuid.uuid4().hex NETWORK_NAME = uuid.uuid4().hex @classmethod def setUpClass(cls): - # Create a network for the port - cls.openstack('network create ' + cls.NETWORK_NAME) + common.NetworkTests.setUpClass() + if cls.haz_network: + # Create a network for the port + cls.openstack( + 'network create ' + cls.NETWORK_NAME + ) @classmethod def tearDownClass(cls): - raw_output = cls.openstack('network delete ' + cls.NETWORK_NAME) - cls.assertOutput('', raw_output) + if cls.haz_network: + raw_output = cls.openstack( + 'network delete ' + cls.NETWORK_NAME + ) + cls.assertOutput('', raw_output) + + def setUp(self): + super(PortTests, self).setUp() + # Nothing in this class works with Nova Network + if not self.haz_network: + self.skipTest("No Network service present") def test_port_delete(self): """Test create, delete multiple""" diff --git a/openstackclient/tests/functional/network/v2/test_router.py b/openstackclient/tests/functional/network/v2/test_router.py index aa708e0a6f..313feefc01 100644 --- a/openstackclient/tests/functional/network/v2/test_router.py +++ b/openstackclient/tests/functional/network/v2/test_router.py @@ -13,11 +13,17 @@ import json import uuid -from openstackclient.tests.functional import base +from openstackclient.tests.functional.network.v2 import common -class RouterTests(base.TestCase): - """Functional tests for router. """ +class RouterTests(common.NetworkTests): + """Functional tests for router""" + + def setUp(self): + super(RouterTests, self).setUp() + # Nothing in this class works with Nova Network + if not self.haz_network: + self.skipTest("No Network service present") def test_router_create_and_delete(self): """Test create options, delete multiple""" diff --git a/openstackclient/tests/functional/network/v2/test_security_group.py b/openstackclient/tests/functional/network/v2/test_security_group.py index debd81df6e..2a9c0b0ad4 100644 --- a/openstackclient/tests/functional/network/v2/test_security_group.py +++ b/openstackclient/tests/functional/network/v2/test_security_group.py @@ -12,11 +12,11 @@ import uuid -from openstackclient.tests.functional import base +from openstackclient.tests.functional.network.v2 import common -class SecurityGroupTests(base.TestCase): - """Functional tests for security group. """ +class SecurityGroupTests(common.NetworkTests): + """Functional tests for security group""" NAME = uuid.uuid4().hex OTHER_NAME = uuid.uuid4().hex HEADERS = ['Name'] @@ -24,20 +24,39 @@ class SecurityGroupTests(base.TestCase): @classmethod def setUpClass(cls): - opts = cls.get_opts(cls.FIELDS) - raw_output = cls.openstack('security group create ' + cls.NAME + opts) - expected = cls.NAME + '\n' - cls.assertOutput(expected, raw_output) + common.NetworkTests.setUpClass() + if cls.haz_network: + opts = cls.get_opts(cls.FIELDS) + raw_output = cls.openstack( + 'security group create ' + + cls.NAME + + opts + ) + expected = cls.NAME + '\n' + cls.assertOutput(expected, raw_output) @classmethod def tearDownClass(cls): - # Rename test - raw_output = cls.openstack('security group set --name ' + - cls.OTHER_NAME + ' ' + cls.NAME) - cls.assertOutput('', raw_output) - # Delete test - raw_output = cls.openstack('security group delete ' + cls.OTHER_NAME) - cls.assertOutput('', raw_output) + if cls.haz_network: + # Rename test + raw_output = cls.openstack( + 'security group set --name ' + + cls.OTHER_NAME + ' ' + + cls.NAME + ) + cls.assertOutput('', raw_output) + # Delete test + raw_output = cls.openstack( + 'security group delete ' + + cls.OTHER_NAME + ) + cls.assertOutput('', raw_output) + + def setUp(self): + super(SecurityGroupTests, self).setUp() + # Nothing in this class works with Nova Network + if not self.haz_network: + self.skipTest("No Network service present") def test_security_group_list(self): opts = self.get_opts(self.HEADERS) diff --git a/openstackclient/tests/functional/network/v2/test_security_group_rule.py b/openstackclient/tests/functional/network/v2/test_security_group_rule.py index c91de1a570..93f98642f9 100644 --- a/openstackclient/tests/functional/network/v2/test_security_group_rule.py +++ b/openstackclient/tests/functional/network/v2/test_security_group_rule.py @@ -12,11 +12,11 @@ import uuid -from openstackclient.tests.functional import base +from openstackclient.tests.functional.network.v2 import common -class SecurityGroupRuleTests(base.TestCase): - """Functional tests for security group rule. """ +class SecurityGroupRuleTests(common.NetworkTests): + """Functional tests for security group rule""" SECURITY_GROUP_NAME = uuid.uuid4().hex SECURITY_GROUP_RULE_ID = None NAME_FIELD = ['name'] @@ -25,32 +25,49 @@ class SecurityGroupRuleTests(base.TestCase): @classmethod def setUpClass(cls): - # Create the security group to hold the rule. - opts = cls.get_opts(cls.NAME_FIELD) - raw_output = cls.openstack('security group create ' + - cls.SECURITY_GROUP_NAME + - opts) - expected = cls.SECURITY_GROUP_NAME + '\n' - cls.assertOutput(expected, raw_output) + common.NetworkTests.setUpClass() + if cls.haz_network: + # Create the security group to hold the rule. + opts = cls.get_opts(cls.NAME_FIELD) + raw_output = cls.openstack( + 'security group create ' + + cls.SECURITY_GROUP_NAME + + opts + ) + expected = cls.SECURITY_GROUP_NAME + '\n' + cls.assertOutput(expected, raw_output) - # Create the security group rule. - opts = cls.get_opts(cls.ID_FIELD) - raw_output = cls.openstack('security group rule create ' + - cls.SECURITY_GROUP_NAME + - ' --protocol tcp --dst-port 80:80' + - ' --ingress --ethertype IPv4' + - opts) - cls.SECURITY_GROUP_RULE_ID = raw_output.strip('\n') + # Create the security group rule. + opts = cls.get_opts(cls.ID_FIELD) + raw_output = cls.openstack( + 'security group rule create ' + + cls.SECURITY_GROUP_NAME + ' ' + + '--protocol tcp --dst-port 80:80 ' + + '--ingress --ethertype IPv4 ' + + opts + ) + cls.SECURITY_GROUP_RULE_ID = raw_output.strip('\n') @classmethod def tearDownClass(cls): - raw_output = cls.openstack('security group rule delete ' + - cls.SECURITY_GROUP_RULE_ID) - cls.assertOutput('', raw_output) + if cls.haz_network: + raw_output = cls.openstack( + 'security group rule delete ' + + cls.SECURITY_GROUP_RULE_ID + ) + cls.assertOutput('', raw_output) - raw_output = cls.openstack('security group delete ' + - cls.SECURITY_GROUP_NAME) - cls.assertOutput('', raw_output) + raw_output = cls.openstack( + 'security group delete ' + + cls.SECURITY_GROUP_NAME + ) + cls.assertOutput('', raw_output) + + def setUp(self): + super(SecurityGroupRuleTests, self).setUp() + # Nothing in this class works with Nova Network + if not self.haz_network: + self.skipTest("No Network service present") def test_security_group_rule_list(self): opts = self.get_opts(self.ID_HEADER) diff --git a/openstackclient/tests/functional/network/v2/test_subnet.py b/openstackclient/tests/functional/network/v2/test_subnet.py index 61cffcde4d..5160042cda 100644 --- a/openstackclient/tests/functional/network/v2/test_subnet.py +++ b/openstackclient/tests/functional/network/v2/test_subnet.py @@ -14,27 +14,39 @@ import random import uuid -from openstackclient.tests.functional import base +from openstackclient.tests.functional.network.v2 import common -class SubnetTests(base.TestCase): - """Functional tests for subnet. """ +class SubnetTests(common.NetworkTests): + """Functional tests for subnet""" @classmethod def setUpClass(cls): - # Create a network for the all subnet tests. - cls.NETWORK_NAME = uuid.uuid4().hex - cmd_output = json.loads(cls.openstack( - 'network create -f json ' + - cls.NETWORK_NAME - )) - # Get network_id for assertEqual - cls.NETWORK_ID = cmd_output["id"] + common.NetworkTests.setUpClass() + if cls.haz_network: + # Create a network for the all subnet tests + cls.NETWORK_NAME = uuid.uuid4().hex + cmd_output = json.loads(cls.openstack( + 'network create -f json ' + + cls.NETWORK_NAME + )) + # Get network_id for assertEqual + cls.NETWORK_ID = cmd_output["id"] @classmethod def tearDownClass(cls): - raw_output = cls.openstack('network delete ' + cls.NETWORK_NAME) - cls.assertOutput('', raw_output) + if cls.haz_network: + raw_output = cls.openstack( + 'network delete ' + + cls.NETWORK_NAME + ) + cls.assertOutput('', raw_output) + + def setUp(self): + super(SubnetTests, self).setUp() + # Nothing in this class works with Nova Network + if not self.haz_network: + self.skipTest("No Network service present") def test_subnet_create_and_delete(self): """Test create, delete multiple""" diff --git a/openstackclient/tests/functional/network/v2/test_subnet_pool.py b/openstackclient/tests/functional/network/v2/test_subnet_pool.py index d68ca01cf4..640f68b708 100644 --- a/openstackclient/tests/functional/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/functional/network/v2/test_subnet_pool.py @@ -14,12 +14,18 @@ import random import uuid -from openstackclient.tests.functional import base +from openstackclient.tests.functional.network.v2 import common -class SubnetPoolTests(base.TestCase): +class SubnetPoolTests(common.NetworkTests): """Functional tests for subnet pool""" + def setUp(self): + super(SubnetPoolTests, self).setUp() + # Nothing in this class works with Nova Network + if not self.haz_network: + self.skipTest("No Network service present") + def test_subnet_pool_create_delete(self): """Test create, delete""" name1 = uuid.uuid4().hex From 2c5405ed5e69eb5b000d47d92e1019b8bb9b54f9 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 28 Apr 2017 14:25:00 -0500 Subject: [PATCH 1669/3095] Fix volume qos spec list This has been sporadically failing in functional tests due to the way the volume qos spec list command calls get_associations() for each spec. When tests run in parallel occasionally a spec from another test is present in the list returned and is deleted before the get_associations() call is made, causing a NotFound exception. We should just keep going when this occurs. * make v1 match v2 * add tests to ensure the exception is being caught and handled Closes-Bug: #1687083 Change-Id: If2d17c1deb53d293fc2c7f0c527a4e4ef6f69976 --- .../tests/functional/volume/v3/test_qos.py | 3 +- .../tests/unit/volume/v1/test_qos_specs.py | 65 ++++++++++++++----- .../tests/unit/volume/v2/test_qos_specs.py | 28 ++++++++ openstackclient/volume/v1/qos_specs.py | 19 ++++-- openstackclient/volume/v2/qos_specs.py | 19 ++++-- 5 files changed, 106 insertions(+), 28 deletions(-) diff --git a/openstackclient/tests/functional/volume/v3/test_qos.py b/openstackclient/tests/functional/volume/v3/test_qos.py index 46965ced08..a7af3c5b97 100644 --- a/openstackclient/tests/functional/volume/v3/test_qos.py +++ b/openstackclient/tests/functional/volume/v3/test_qos.py @@ -10,9 +10,10 @@ # License for the specific language governing permissions and limitations # under the License. -from openstackclient.tests.functional.volume.v2 import test_qos as v2 import os +from openstackclient.tests.functional.volume.v2 import test_qos as v2 + class QosTests(v2.QosTests): """Functional tests for volume qos. """ diff --git a/openstackclient/tests/unit/volume/v1/test_qos_specs.py b/openstackclient/tests/unit/volume/v1/test_qos_specs.py index e3dc1e787a..a88c1cd83f 100644 --- a/openstackclient/tests/unit/volume/v1/test_qos_specs.py +++ b/openstackclient/tests/unit/volume/v1/test_qos_specs.py @@ -13,6 +13,7 @@ # under the License. # +import copy import mock from mock import call @@ -309,13 +310,30 @@ def test_qos_disassociate_with_all_volume_types(self): class TestQosList(TestQos): - qos_spec = volume_fakes.FakeQos.create_one_qos() + qos_specs = volume_fakes.FakeQos.create_qoses(count=2) qos_association = volume_fakes.FakeQos.create_one_qos_association() + columns = ( + 'ID', + 'Name', + 'Consumer', + 'Associations', + 'Properties', + ) + data = [] + for q in qos_specs: + data.append(( + q.id, + q.name, + q.consumer, + qos_association.name, + utils.format_dict(q.specs), + )) + def setUp(self): super(TestQosList, self).setUp() - self.qos_mock.list.return_value = [self.qos_spec] + self.qos_mock.list.return_value = self.qos_specs self.qos_mock.get_associations.return_value = [self.qos_association] # Get the command object to test @@ -330,22 +348,35 @@ def test_qos_list(self): columns, data = self.cmd.take_action(parsed_args) self.qos_mock.list.assert_called_with() - collist = ( - 'ID', - 'Name', - 'Consumer', - 'Associations', - 'Properties', + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_qos_list_no_association(self): + self.qos_mock.reset_mock() + self.qos_mock.get_associations.side_effect = [ + [self.qos_association], + exceptions.NotFound("NotFound"), + ] + + arglist = [] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.qos_mock.list.assert_called_with() + + self.assertEqual(self.columns, columns) + + ex_data = copy.deepcopy(self.data) + ex_data[1] = ( + self.qos_specs[1].id, + self.qos_specs[1].name, + self.qos_specs[1].consumer, + None, + utils.format_dict(self.qos_specs[1].specs), ) - self.assertEqual(collist, columns) - datalist = (( - self.qos_spec.id, - self.qos_spec.name, - self.qos_spec.consumer, - self.qos_association.name, - utils.format_dict(self.qos_spec.specs), - ), ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(ex_data, list(data)) class TestQosSet(TestQos): diff --git a/openstackclient/tests/unit/volume/v2/test_qos_specs.py b/openstackclient/tests/unit/volume/v2/test_qos_specs.py index 35d9a34575..8f145a7e3f 100644 --- a/openstackclient/tests/unit/volume/v2/test_qos_specs.py +++ b/openstackclient/tests/unit/volume/v2/test_qos_specs.py @@ -13,6 +13,7 @@ # under the License. # +import copy import mock from mock import call @@ -342,6 +343,33 @@ def test_qos_list(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + def test_qos_list_no_association(self): + self.qos_mock.reset_mock() + self.qos_mock.get_associations.side_effect = [ + [self.qos_association], + exceptions.NotFound("NotFound"), + ] + + arglist = [] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.qos_mock.list.assert_called_with() + + self.assertEqual(self.columns, columns) + + ex_data = copy.deepcopy(self.data) + ex_data[1] = ( + self.qos_specs[1].id, + self.qos_specs[1].name, + self.qos_specs[1].consumer, + None, + utils.format_dict(self.qos_specs[1].specs), + ) + self.assertEqual(ex_data, list(data)) + class TestQosSet(TestQos): diff --git a/openstackclient/volume/v1/qos_specs.py b/openstackclient/volume/v1/qos_specs.py index bae8c1ab0c..26c9182971 100644 --- a/openstackclient/volume/v1/qos_specs.py +++ b/openstackclient/volume/v1/qos_specs.py @@ -186,11 +186,20 @@ def take_action(self, parsed_args): qos_specs_list = volume_client.qos_specs.list() for qos in qos_specs_list: - qos_associations = volume_client.qos_specs.get_associations(qos) - if qos_associations: - associations = [association.name - for association in qos_associations] - qos._info.update({'associations': associations}) + try: + qos_associations = volume_client.qos_specs.get_associations( + qos, + ) + if qos_associations: + associations = [ + association.name for association in qos_associations + ] + qos._info.update({'associations': associations}) + except Exception as ex: + if type(ex).__name__ == 'NotFound': + qos._info.update({'associations': None}) + else: + raise display_columns = ( 'ID', 'Name', 'Consumer', 'Associations', 'Properties') diff --git a/openstackclient/volume/v2/qos_specs.py b/openstackclient/volume/v2/qos_specs.py index 8e1d67b5c3..c71605818a 100644 --- a/openstackclient/volume/v2/qos_specs.py +++ b/openstackclient/volume/v2/qos_specs.py @@ -187,11 +187,20 @@ def take_action(self, parsed_args): qos_specs_list = volume_client.qos_specs.list() for qos in qos_specs_list: - qos_associations = volume_client.qos_specs.get_associations(qos) - if qos_associations: - associations = [association.name - for association in qos_associations] - qos._info.update({'associations': associations}) + try: + qos_associations = volume_client.qos_specs.get_associations( + qos, + ) + if qos_associations: + associations = [ + association.name for association in qos_associations + ] + qos._info.update({'associations': associations}) + except Exception as ex: + if type(ex).__name__ == 'NotFound': + qos._info.update({'associations': None}) + else: + raise display_columns = ( 'ID', 'Name', 'Consumer', 'Associations', 'Properties') From 1b9cf82b42c4236aca5c5a7f7e26eb36c4ab7ac9 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Mon, 1 May 2017 14:07:49 +0000 Subject: [PATCH 1670/3095] Updated from global requirements Change-Id: I5215bae5234dcef448c6c5b824c506f80dd1c5a8 --- requirements.txt | 2 +- test-requirements.txt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index e4054d2ec8..9d3d80d689 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ Babel!=2.4.0,>=2.3.4 # BSD cliff>=2.6.0 # Apache-2.0 keystoneauth1>=2.18.0 # Apache-2.0 openstacksdk>=0.9.14 # Apache-2.0 -osc-lib>=1.2.0 # Apache-2.0 +osc-lib>=1.5.1 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 oslo.utils>=3.20.0 # Apache-2.0 python-glanceclient>=2.5.0 # Apache-2.0 diff --git a/test-requirements.txt b/test-requirements.txt index f6917e336a..467b9695d0 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -13,7 +13,7 @@ requests!=2.12.2,!=2.13.0,>=2.10.0 # Apache-2.0 requests-mock>=1.1 # Apache-2.0 sphinx>=1.5.1 # BSD stevedore>=1.20.0 # Apache-2.0 -os-client-config>=1.22.0 # Apache-2.0 +os-client-config>=1.27.0 # Apache-2.0 os-testr>=0.8.0 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD testtools>=1.4.0 # MIT @@ -31,7 +31,7 @@ python-designateclient>=1.5.0 # Apache-2.0 python-heatclient>=1.6.1 # Apache-2.0 python-ironicclient>=1.11.0 # Apache-2.0 python-ironic-inspector-client>=1.5.0 # Apache-2.0 -python-mistralclient>=2.0.0 # Apache-2.0 +python-mistralclient>=3.1.0 # Apache-2.0 python-muranoclient>=0.8.2 # Apache-2.0 python-neutronclient>=5.1.0 # Apache-2.0 python-saharaclient>=1.1.0 # Apache-2.0 From 983cccb6628bcd8dc310a867e35709e0b410c9d6 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Tue, 2 May 2017 10:29:59 -0500 Subject: [PATCH 1671/3095] Functional tests: Identity v2 and DevStack DevStack master (as of 01May2017) no longer sets up an Identity v2 admin endpoint, so we need to skip those tests going forward and cover them via a specific leagacy job. This does the detect-and-skip. Change-Id: Ib9ab32b6bc84ec7d13508094ad6f83995d8d7bc1 --- .../tests/functional/identity/v2/common.py | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/openstackclient/tests/functional/identity/v2/common.py b/openstackclient/tests/functional/identity/v2/common.py index 4f3e180134..69ef728b15 100644 --- a/openstackclient/tests/functional/identity/v2/common.py +++ b/openstackclient/tests/functional/identity/v2/common.py @@ -13,6 +13,7 @@ import os from tempest.lib.common.utils import data_utils +from tempest.lib import exceptions as tempest_exceptions from openstackclient.tests.functional import base @@ -49,12 +50,22 @@ def setUpClass(cls): # create dummy project cls.project_name = data_utils.rand_name('TestProject') cls.project_description = data_utils.rand_name('description') - cls.openstack( - 'project create ' - '--description %(description)s ' - '--enable ' - '%(name)s' % {'description': cls.project_description, - 'name': cls.project_name}) + try: + cls.openstack( + 'project create ' + '--description %(description)s ' + '--enable ' + '%(name)s' % { + 'description': cls.project_description, + 'name': cls.project_name, + } + ) + except tempest_exceptions.CommandFailed: + # Good chance this is due to Identity v2 admin not being enabled + # TODO(dtroyer): Actually determine if Identity v2 admin is + # enabled in the target cloud. Tuens out OSC + # doesn't make this easy as it should (yet). + raise cls.skipException('No Identity v2 admin endpoint?') @classmethod def tearDownClass(cls): From 346ac9da622396575766ac97d108edfd17a21465 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Mon, 1 May 2017 14:36:19 -0500 Subject: [PATCH 1672/3095] Nova-net functional tests: aggregates Nova-net requires a cells v1 configuration to run as of Ocata, but aggregates and cells v1 are not golfing buddies, so don't let them meet on the back nine. Skip the aggregate add/remove host commands in the cells v1 config, leave the others because they should work, just not be very useful. And format things consistently. Change-Id: I131d9f883cb7aca53ad82fb7d5fc6ee1c1e7d923 --- .../functional/compute/v2/test_aggregate.py | 65 +++++++++++++------ 1 file changed, 44 insertions(+), 21 deletions(-) diff --git a/openstackclient/tests/functional/compute/v2/test_aggregate.py b/openstackclient/tests/functional/compute/v2/test_aggregate.py index 95068fc231..1eba3ffe06 100644 --- a/openstackclient/tests/functional/compute/v2/test_aggregate.py +++ b/openstackclient/tests/functional/compute/v2/test_aggregate.py @@ -17,7 +17,7 @@ class AggregateTests(base.TestCase): - """Functional tests for aggregate.""" + """Functional tests for aggregate""" def test_aggregate_create_and_delete(self): """Test create, delete multiple""" @@ -25,7 +25,8 @@ def test_aggregate_create_and_delete(self): cmd_output = json.loads(self.openstack( 'aggregate create -f json ' + '--zone nova ' + - name1)) + name1 + )) self.assertEqual( name1, cmd_output['name'] @@ -39,7 +40,8 @@ def test_aggregate_create_and_delete(self): cmd_output = json.loads(self.openstack( 'aggregate create -f json ' + '--zone nova ' + - name2)) + name2 + )) self.assertEqual( name2, cmd_output['name'] @@ -50,7 +52,10 @@ def test_aggregate_create_and_delete(self): ) del_output = self.openstack( - 'aggregate delete ' + name1 + ' ' + name2) + 'aggregate delete ' + + name1 + ' ' + + name2 + ) self.assertOutput('', del_output) def test_aggregate_list(self): @@ -60,7 +65,8 @@ def test_aggregate_list(self): 'aggregate create ' + '--zone nova ' + '--property a=b ' + - name1) + name1 + ) self.addCleanup(self.openstack, 'aggregate delete ' + name1) name2 = uuid.uuid4().hex @@ -68,11 +74,13 @@ def test_aggregate_list(self): 'aggregate create ' + '--zone internal ' + '--property c=d ' + - name2) + name2 + ) self.addCleanup(self.openstack, 'aggregate delete ' + name2) cmd_output = json.loads(self.openstack( - 'aggregate list -f json')) + 'aggregate list -f json' + )) names = [x['Name'] for x in cmd_output] self.assertIn(name1, names) self.assertIn(name2, names) @@ -82,7 +90,8 @@ def test_aggregate_list(self): # Test aggregate list --long cmd_output = json.loads(self.openstack( - 'aggregate list --long -f json')) + 'aggregate list --long -f json' + )) names = [x['Name'] for x in cmd_output] self.assertIn(name1, names) self.assertIn(name2, names) @@ -101,13 +110,14 @@ def test_aggregate_set_and_unset(self): 'aggregate create ' + '--zone nova ' + '--property a=b ' + - name1) + name1 + ) self.addCleanup(self.openstack, 'aggregate delete ' + name2) raw_output = self.openstack( - 'aggregate set --name ' + - name2 + - ' --zone internal ' + + 'aggregate set ' + + '--name ' + name2 + ' ' + + '--zone internal ' + '--no-property ' + '--property c=d ' + name1 @@ -115,7 +125,9 @@ def test_aggregate_set_and_unset(self): self.assertOutput('', raw_output) cmd_output = json.loads(self.openstack( - 'aggregate show -f json ' + name2)) + 'aggregate show -f json ' + + name2 + )) self.assertEqual( name2, cmd_output['name'] @@ -135,13 +147,16 @@ def test_aggregate_set_and_unset(self): # Test unset raw_output = self.openstack( - 'aggregate unset --property c ' + + 'aggregate unset ' + + '--property c ' + name2 ) self.assertOutput('', raw_output) cmd_output = json.loads(self.openstack( - 'aggregate show -f json ' + name2)) + 'aggregate show -f json ' + + name2 + )) self.assertNotIn( "c='d'", cmd_output['properties'] @@ -149,16 +164,24 @@ def test_aggregate_set_and_unset(self): def test_aggregate_add_and_remove_host(self): """Test aggregate add and remove host""" - name = uuid.uuid4().hex - self.openstack( - 'aggregate create ' + name) - self.addCleanup(self.openstack, 'aggregate delete ' + name) - # Get a host cmd_output = json.loads(self.openstack( - 'host list -f json')) + 'host list -f json' + )) host_name = cmd_output[0]['Host Name'] + # NOTE(dtroyer): Cells v1 is not operable with aggregates. Hostnames + # are returned as rrr@host or ccc!rrr@host. + if '@' in host_name: + self.skipTest("Skip aggregates in a Nova cells v1 configuration") + + name = uuid.uuid4().hex + self.openstack( + 'aggregate create ' + + name + ) + self.addCleanup(self.openstack, 'aggregate delete ' + name) + # Test add host cmd_output = json.loads(self.openstack( 'aggregate add host -f json ' + From d930b043eef262f3eb34489f01b9856077dc2dc4 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Mon, 1 May 2017 17:27:15 -0500 Subject: [PATCH 1673/3095] Funcional tests: quota list The quota list tests have a race in them where occasionally a project is deleted in another test between the time that quota list gets a list of all projects and it gets the quota for the projects from the service; the get quota call fails on the non-existant project. The quota list functional tests have been substantially re-written to properly test the exception handling. Change-Id: I71e6bbb5d46fcea4718a5a870f9a66a2c20fff0f --- openstackclient/common/quota.py | 78 +- .../tests/unit/common/test_quota.py | 682 +++++++++++------- 2 files changed, 490 insertions(+), 270 deletions(-) diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index 73dfd9091d..6ed9e370db 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -132,13 +132,27 @@ def take_action(self, parsed_args): if parsed_args.compute: compute_client = self.app.client_manager.compute for p in project_ids: - data = compute_client.quotas.get(p) - result_data = _xform_get_quota(data, p, - COMPUTE_QUOTAS.keys()) + try: + data = compute_client.quotas.get(p) + except Exception as ex: + if type(ex).__name__ == 'NotFound': + # Project not found, move on to next one + LOG.warning("Project %s not found: %s" % (p, ex)) + continue + else: + raise + + result_data = _xform_get_quota( + data, + p, + COMPUTE_QUOTAS.keys(), + ) default_data = compute_client.quotas.defaults(p) - result_default = _xform_get_quota(default_data, - p, - COMPUTE_QUOTAS.keys()) + result_default = _xform_get_quota( + default_data, + p, + COMPUTE_QUOTAS.keys(), + ) if result_default != result_data: result += result_data @@ -174,16 +188,31 @@ def take_action(self, parsed_args): (utils.get_dict_properties( s, columns, ) for s in result)) + if parsed_args.volume: volume_client = self.app.client_manager.volume for p in project_ids: - data = volume_client.quotas.get(p) - result_data = _xform_get_quota(data, p, - VOLUME_QUOTAS.keys()) + try: + data = volume_client.quotas.get(p) + except Exception as ex: + if type(ex).__name__ == 'NotFound': + # Project not found, move on to next one + LOG.warning("Project %s not found: %s" % (p, ex)) + continue + else: + raise + + result_data = _xform_get_quota( + data, + p, + VOLUME_QUOTAS.keys(), + ) default_data = volume_client.quotas.defaults(p) - result_default = _xform_get_quota(default_data, - p, - VOLUME_QUOTAS.keys()) + result_default = _xform_get_quota( + default_data, + p, + VOLUME_QUOTAS.keys(), + ) if result_default != result_data: result += result_data @@ -209,14 +238,31 @@ def take_action(self, parsed_args): (utils.get_dict_properties( s, columns, ) for s in result)) + if parsed_args.network: client = self.app.client_manager.network for p in project_ids: - data = client.get_quota(p) - result_data = _xform_get_quota(data, p, NETWORK_KEYS) + try: + data = client.get_quota(p) + except Exception as ex: + if type(ex).__name__ == 'NotFound': + # Project not found, move on to next one + LOG.warning("Project %s not found: %s" % (p, ex)) + continue + else: + raise + + result_data = _xform_get_quota( + data, + p, + NETWORK_KEYS, + ) default_data = client.get_quota_default(p) - result_default = _xform_get_quota(default_data, - p, NETWORK_KEYS) + result_default = _xform_get_quota( + default_data, + p, + NETWORK_KEYS, + ) if result_default != result_data: result += result_data diff --git a/openstackclient/tests/unit/common/test_quota.py b/openstackclient/tests/unit/common/test_quota.py index 63f6435f4d..1b0d2c3193 100644 --- a/openstackclient/tests/unit/common/test_quota.py +++ b/openstackclient/tests/unit/common/test_quota.py @@ -13,6 +13,8 @@ import copy import mock +from osc_lib import exceptions + from openstackclient.common import quota from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes from openstackclient.tests.unit import fakes @@ -41,17 +43,25 @@ class TestQuota(compute_fakes.TestComputev2): def setUp(self): super(TestQuota, self).setUp() - self.quotas_mock = self.app.client_manager.compute.quotas - self.quotas_mock.reset_mock() - self.quotas_class_mock = self.app.client_manager.compute.quota_classes - self.quotas_class_mock.reset_mock() + + # Set up common projects + self.projects = identity_fakes_v3.FakeProject.create_projects(count=2) + self.projects_mock = self.app.client_manager.identity.projects + self.projects_mock.reset_mock() + self.projects_mock.get.return_value = self.projects[0] + + self.compute_quotas_mock = self.app.client_manager.compute.quotas + self.compute_quotas_mock.reset_mock() + self.compute_quotas_class_mock = \ + self.app.client_manager.compute.quota_classes + self.compute_quotas_class_mock.reset_mock() + self.volume_quotas_mock = self.app.client_manager.volume.quotas self.volume_quotas_mock.reset_mock() self.volume_quotas_class_mock = \ self.app.client_manager.volume.quota_classes self.volume_quotas_class_mock.reset_mock() - self.projects_mock = self.app.client_manager.identity.projects - self.projects_mock.reset_mock() + self.app.client_manager.auth_ref = mock.Mock() self.app.client_manager.auth_ref.service_catalog = mock.Mock() self.service_catalog_mock = \ @@ -60,35 +70,362 @@ def setUp(self): self.app.client_manager.auth_ref.project_id = identity_fakes.project_id +class TestQuotaList(TestQuota): + """Test cases for quota list command""" + + compute_column_header = ( + 'Project ID', + 'Cores', + 'Fixed IPs', + 'Injected Files', + 'Injected File Content Bytes', + 'Injected File Path Bytes', + 'Instances', + 'Key Pairs', + 'Metadata Items', + 'Ram', + 'Server Groups', + 'Server Group Members', + ) + + network_column_header = ( + 'Project ID', + 'Floating IPs', + 'Networks', + 'Ports', + 'RBAC Policies', + 'Routers', + 'Security Groups', + 'Security Group Rules', + 'Subnets', + 'Subnet Pools' + ) + + volume_column_header = ( + 'Project ID', + 'Backups', + 'Backup Gigabytes', + 'Gigabytes', + 'Per Volume Gigabytes', + 'Snapshots', + 'Volumes', + ) + + def setUp(self): + super(TestQuotaList, self).setUp() + + # Work with multiple projects in this class + self.projects_mock.get.side_effect = self.projects + self.projects_mock.list.return_value = self.projects + + self.compute_quotas = [ + compute_fakes.FakeQuota.create_one_comp_quota(), + compute_fakes.FakeQuota.create_one_comp_quota(), + ] + self.compute_default_quotas = [ + compute_fakes.FakeQuota.create_one_default_comp_quota(), + compute_fakes.FakeQuota.create_one_default_comp_quota(), + ] + self.compute = self.app.client_manager.compute + self.compute.quotas.defaults = mock.Mock( + side_effect=self.compute_default_quotas, + ) + + self.compute_reference_data = ( + self.projects[0].id, + self.compute_quotas[0].cores, + self.compute_quotas[0].fixed_ips, + self.compute_quotas[0].injected_files, + self.compute_quotas[0].injected_file_content_bytes, + self.compute_quotas[0].injected_file_path_bytes, + self.compute_quotas[0].instances, + self.compute_quotas[0].key_pairs, + self.compute_quotas[0].metadata_items, + self.compute_quotas[0].ram, + self.compute_quotas[0].server_groups, + self.compute_quotas[0].server_group_members, + ) + + self.network_quotas = [ + network_fakes.FakeQuota.create_one_net_quota(), + network_fakes.FakeQuota.create_one_net_quota(), + ] + self.network_default_quotas = [ + network_fakes.FakeQuota.create_one_default_net_quota(), + network_fakes.FakeQuota.create_one_default_net_quota(), + ] + self.network = self.app.client_manager.network + self.network.get_quota_default = mock.Mock( + side_effect=self.network_default_quotas, + ) + + self.network_reference_data = ( + self.projects[0].id, + self.network_quotas[0].floating_ips, + self.network_quotas[0].networks, + self.network_quotas[0].ports, + self.network_quotas[0].rbac_policies, + self.network_quotas[0].routers, + self.network_quotas[0].security_groups, + self.network_quotas[0].security_group_rules, + self.network_quotas[0].subnets, + self.network_quotas[0].subnet_pools, + ) + + self.volume_quotas = [ + volume_fakes.FakeQuota.create_one_vol_quota(), + volume_fakes.FakeQuota.create_one_vol_quota(), + ] + self.volume_default_quotas = [ + volume_fakes.FakeQuota.create_one_default_vol_quota(), + volume_fakes.FakeQuota.create_one_default_vol_quota(), + ] + self.volume = self.app.client_manager.volume + self.volume.quotas.defaults = mock.Mock( + side_effect=self.volume_default_quotas, + ) + + self.volume_reference_data = ( + self.projects[0].id, + self.volume_quotas[0].backups, + self.volume_quotas[0].backup_gigabytes, + self.volume_quotas[0].gigabytes, + self.volume_quotas[0].per_volume_gigabytes, + self.volume_quotas[0].snapshots, + self.volume_quotas[0].volumes, + ) + + self.cmd = quota.ListQuota(self.app, None) + + def test_quota_list_compute(self): + # Two projects with non-default quotas + self.compute.quotas.get = mock.Mock( + side_effect=self.compute_quotas, + ) + + arglist = [ + '--compute', + ] + verifylist = [ + ('compute', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + ret_quotas = list(data) + + self.assertEqual(self.compute_column_header, columns) + self.assertEqual(self.compute_reference_data, ret_quotas[0]) + self.assertEqual(2, len(ret_quotas)) + + def test_quota_list_compute_default(self): + # One of the projects is at defaults + self.compute.quotas.get = mock.Mock( + side_effect=[ + self.compute_quotas[0], + compute_fakes.FakeQuota.create_one_default_comp_quota(), + ], + ) + + arglist = [ + '--compute', + ] + verifylist = [ + ('compute', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + ret_quotas = list(data) + + self.assertEqual(self.compute_column_header, columns) + self.assertEqual(self.compute_reference_data, ret_quotas[0]) + self.assertEqual(1, len(ret_quotas)) + + def test_quota_list_compute_no_project(self): + # Make one of the projects disappear + self.compute.quotas.get = mock.Mock( + side_effect=[ + self.compute_quotas[0], + exceptions.NotFound("NotFound"), + ], + ) + + arglist = [ + '--compute', + ] + verifylist = [ + ('compute', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + ret_quotas = list(data) + + self.assertEqual(self.compute_column_header, columns) + self.assertEqual(self.compute_reference_data, ret_quotas[0]) + self.assertEqual(1, len(ret_quotas)) + + def test_quota_list_network(self): + # Two projects with non-default quotas + self.network.get_quota = mock.Mock( + side_effect=self.network_quotas, + ) + + arglist = [ + '--network', + ] + verifylist = [ + ('network', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + ret_quotas = list(data) + + self.assertEqual(self.network_column_header, columns) + self.assertEqual(self.network_reference_data, ret_quotas[0]) + self.assertEqual(2, len(ret_quotas)) + + def test_quota_list_network_default(self): + # Two projects with non-default quotas + self.network.get_quota = mock.Mock( + side_effect=[ + self.network_quotas[0], + network_fakes.FakeQuota.create_one_default_net_quota(), + ], + ) + + arglist = [ + '--network', + ] + verifylist = [ + ('network', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + ret_quotas = list(data) + + self.assertEqual(self.network_column_header, columns) + self.assertEqual(self.network_reference_data, ret_quotas[0]) + self.assertEqual(1, len(ret_quotas)) + + def test_quota_list_network_no_project(self): + # Two projects with non-default quotas + self.network.get_quota = mock.Mock( + side_effect=[ + self.network_quotas[0], + exceptions.NotFound("NotFound"), + ], + ) + + arglist = [ + '--network', + ] + verifylist = [ + ('network', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + ret_quotas = list(data) + + self.assertEqual(self.network_column_header, columns) + self.assertEqual(self.network_reference_data, ret_quotas[0]) + self.assertEqual(1, len(ret_quotas)) + + def test_quota_list_volume(self): + # Two projects with non-default quotas + self.volume.quotas.get = mock.Mock( + side_effect=self.volume_quotas, + ) + + arglist = [ + '--volume', + ] + verifylist = [ + ('volume', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + ret_quotas = list(data) + + self.assertEqual(self.volume_column_header, columns) + self.assertEqual(self.volume_reference_data, ret_quotas[0]) + self.assertEqual(2, len(ret_quotas)) + + def test_quota_list_volume_default(self): + # Two projects with non-default quotas + self.volume.quotas.get = mock.Mock( + side_effect=[ + self.volume_quotas[0], + volume_fakes.FakeQuota.create_one_default_vol_quota(), + ], + ) + + arglist = [ + '--volume', + ] + verifylist = [ + ('volume', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + ret_quotas = list(data) + + self.assertEqual(self.volume_column_header, columns) + self.assertEqual(self.volume_reference_data, ret_quotas[0]) + self.assertEqual(1, len(ret_quotas)) + + def test_quota_list_volume_no_project(self): + # Two projects with non-default quotas + self.volume.quotas.get = mock.Mock( + side_effect=[ + self.volume_quotas[0], + volume_fakes.FakeQuota.create_one_default_vol_quota(), + ], + ) + + arglist = [ + '--volume', + ] + verifylist = [ + ('volume', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + ret_quotas = list(data) + + self.assertEqual(self.volume_column_header, columns) + self.assertEqual(self.volume_reference_data, ret_quotas[0]) + self.assertEqual(1, len(ret_quotas)) + + class TestQuotaSet(TestQuota): def setUp(self): super(TestQuotaSet, self).setUp() - self.quotas_mock.update.return_value = FakeQuotaResource( + self.compute_quotas_mock.update.return_value = FakeQuotaResource( None, copy.deepcopy(compute_fakes.QUOTA), loaded=True, ) - - self.volume_quotas_mock.update.return_value = FakeQuotaResource( + self.compute_quotas_class_mock.update.return_value = FakeQuotaResource( None, copy.deepcopy(compute_fakes.QUOTA), loaded=True, ) - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) - - self.quotas_class_mock.update.return_value = FakeQuotaResource( + self.volume_quotas_mock.update.return_value = FakeQuotaResource( None, copy.deepcopy(compute_fakes.QUOTA), loaded=True, ) - self.volume_quotas_class_mock.update.return_value = FakeQuotaResource( None, copy.deepcopy(compute_fakes.QUOTA), @@ -116,7 +453,7 @@ def test_quota_set(self): '--secgroups', str(compute_fakes.secgroup_num), '--server-groups', str(compute_fakes.servgroup_num), '--server-group-members', str(compute_fakes.servgroup_members_num), - identity_fakes.project_name, + self.projects[0].name, ] verifylist = [ ('floating_ips', compute_fakes.floating_ip_num), @@ -134,9 +471,8 @@ def test_quota_set(self): ('security_groups', compute_fakes.secgroup_num), ('server_groups', compute_fakes.servgroup_num), ('server_group_members', compute_fakes.servgroup_members_num), - ('project', identity_fakes.project_name), + ('project', self.projects[0].name), ] - self.app.client_manager.network_endpoint_enabled = False parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -160,8 +496,8 @@ def test_quota_set(self): 'server_group_members': compute_fakes.servgroup_members_num, } - self.quotas_mock.update.assert_called_once_with( - identity_fakes.project_id, + self.compute_quotas_mock.update.assert_called_once_with( + self.projects[0].id, **kwargs ) self.assertIsNone(result) @@ -175,7 +511,7 @@ def test_quota_set_volume(self): '--backup-gigabytes', str(volume_fakes.QUOTA['backup_gigabytes']), '--per-volume-gigabytes', str(volume_fakes.QUOTA['per_volume_gigabytes']), - identity_fakes.project_name, + self.projects[0].name, ] verifylist = [ ('gigabytes', volume_fakes.QUOTA['gigabytes']), @@ -184,9 +520,9 @@ def test_quota_set_volume(self): ('backups', volume_fakes.QUOTA['backups']), ('backup_gigabytes', volume_fakes.QUOTA['backup_gigabytes']), ('per_volume_gigabytes', - volume_fakes.QUOTA['per_volume_gigabytes']), + volume_fakes.QUOTA['per_volume_gigabytes']), + ('project', self.projects[0].name), ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) @@ -201,7 +537,7 @@ def test_quota_set_volume(self): } self.volume_quotas_mock.update.assert_called_once_with( - identity_fakes.project_id, + self.projects[0].id, **kwargs ) @@ -217,7 +553,7 @@ def test_quota_set_volume_with_volume_type(self): '--per-volume-gigabytes', str(volume_fakes.QUOTA['per_volume_gigabytes']), '--volume-type', 'volume_type_backend', - identity_fakes.project_name, + self.projects[0].name, ] verifylist = [ ('gigabytes', volume_fakes.QUOTA['gigabytes']), @@ -226,10 +562,10 @@ def test_quota_set_volume_with_volume_type(self): ('backups', volume_fakes.QUOTA['backups']), ('backup_gigabytes', volume_fakes.QUOTA['backup_gigabytes']), ('per_volume_gigabytes', - volume_fakes.QUOTA['per_volume_gigabytes']), + volume_fakes.QUOTA['per_volume_gigabytes']), ('volume_type', 'volume_type_backend'), + ('project', self.projects[0].name), ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) @@ -244,7 +580,7 @@ def test_quota_set_volume_with_volume_type(self): } self.volume_quotas_mock.update.assert_called_once_with( - identity_fakes.project_id, + self.projects[0].id, **kwargs ) self.assertIsNone(result) @@ -264,7 +600,7 @@ def test_quota_set_network(self): '--vips', str(network_fakes.QUOTA['vip']), '--health-monitors', str(network_fakes.QUOTA['healthmonitor']), '--l7policies', str(network_fakes.QUOTA['l7policy']), - identity_fakes.project_name, + self.projects[0].name, ] verifylist = [ ('subnet', network_fakes.QUOTA['subnet']), @@ -272,7 +608,7 @@ def test_quota_set_network(self): ('floatingip', network_fakes.QUOTA['floatingip']), ('subnetpool', network_fakes.QUOTA['subnetpool']), ('security_group_rule', - network_fakes.QUOTA['security_group_rule']), + network_fakes.QUOTA['security_group_rule']), ('security_group', network_fakes.QUOTA['security_group']), ('router', network_fakes.QUOTA['router']), ('rbac_policy', network_fakes.QUOTA['rbac_policy']), @@ -280,6 +616,7 @@ def test_quota_set_network(self): ('vip', network_fakes.QUOTA['vip']), ('healthmonitor', network_fakes.QUOTA['healthmonitor']), ('l7policy', network_fakes.QUOTA['l7policy']), + ('project', self.projects[0].name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -300,7 +637,7 @@ def test_quota_set_network(self): 'l7policy': network_fakes.QUOTA['l7policy'], } self.network_mock.update_quota.assert_called_once_with( - identity_fakes.project_id, + self.projects[0].id, **kwargs ) self.assertIsNone(result) @@ -321,12 +658,13 @@ def test_quota_set_with_class(self): '--snapshots', str(compute_fakes.fix_ip_num), '--volumes', str(volume_fakes.QUOTA['volumes']), '--network', str(network_fakes.QUOTA['network']), - '--class', identity_fakes.project_name, + '--class', + self.projects[0].name, ] verifylist = [ ('injected_files', compute_fakes.injected_file_num), ('injected_file_content_bytes', - compute_fakes.injected_file_size_num), + compute_fakes.injected_file_size_num), ('injected_file_path_bytes', compute_fakes.injected_path_size_num), ('key_pairs', compute_fakes.key_pair_num), ('cores', compute_fakes.core_num), @@ -339,10 +677,9 @@ def test_quota_set_with_class(self): ('snapshots', compute_fakes.fix_ip_num), ('volumes', volume_fakes.QUOTA['volumes']), ('network', network_fakes.QUOTA['network']), - ('project', identity_fakes.project_name), ('quota_class', True), + ('project', self.projects[0].name), ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) @@ -366,12 +703,12 @@ def test_quota_set_with_class(self): 'volumes': volume_fakes.QUOTA['volumes'], } - self.quotas_class_mock.update.assert_called_with( - identity_fakes.project_name, + self.compute_quotas_class_mock.update.assert_called_with( + self.projects[0].name, **kwargs_compute ) self.volume_quotas_class_mock.update.assert_called_with( - identity_fakes.project_name, + self.projects[0].name, **kwargs_volume ) self.assertNotCalled(self.network_mock.update_quota) @@ -383,25 +720,25 @@ class TestQuotaShow(TestQuota): def setUp(self): super(TestQuotaShow, self).setUp() - self.quotas_mock.get.return_value = FakeQuotaResource( - None, - copy.deepcopy(compute_fakes.QUOTA), - loaded=True, - ) - - self.quotas_mock.defaults.return_value = FakeQuotaResource( + self.compute_quota = compute_fakes.FakeQuota.create_one_comp_quota() + self.compute_quotas_mock.get.return_value = self.compute_quota + self.compute_default_quota = \ + compute_fakes.FakeQuota.create_one_default_comp_quota() + self.compute_quotas_mock.defaults.return_value = \ + self.compute_default_quota + self.compute_quotas_class_mock.get.return_value = FakeQuotaResource( None, copy.deepcopy(compute_fakes.QUOTA), loaded=True, ) - self.volume_quotas_mock.get.return_value = FakeQuotaResource( - None, - copy.deepcopy(volume_fakes.QUOTA), - loaded=True, - ) - - self.volume_quotas_mock.defaults.return_value = FakeQuotaResource( + self.volume_quota = volume_fakes.FakeQuota.create_one_vol_quota() + self.volume_quotas_mock.get.return_value = self.volume_quota + self.volume_default_quota = \ + volume_fakes.FakeQuota.create_one_default_vol_quota() + self.volume_quotas_mock.defaults.return_value = \ + self.volume_default_quota + self.volume_quotas_class_mock.get.return_value = FakeQuotaResource( None, copy.deepcopy(volume_fakes.QUOTA), loaded=True, @@ -417,94 +754,85 @@ def setUp(self): 'network': fake_network_endpoint } - self.quotas_class_mock.get.return_value = FakeQuotaResource( - None, - copy.deepcopy(compute_fakes.QUOTA), - loaded=True, - ) - - self.volume_quotas_class_mock.get.return_value = FakeQuotaResource( - None, - copy.deepcopy(volume_fakes.QUOTA), - loaded=True, - ) - - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) - self.app.client_manager.network = network_fakes.FakeNetworkV2Client( endpoint=fakes.AUTH_URL, token=fakes.AUTH_TOKEN, ) self.network = self.app.client_manager.network - self.network.get_quota = mock.Mock(return_value=network_fakes.QUOTA) + self.network.get_quota = mock.Mock( + return_value=network_fakes.QUOTA, + ) self.network.get_quota_default = mock.Mock( - return_value=network_fakes.QUOTA) + return_value=network_fakes.QUOTA, + ) self.cmd = quota.ShowQuota(self.app, None) def test_quota_show(self): arglist = [ - identity_fakes.project_name, + self.projects[0].name, ] verifylist = [ - ('project', identity_fakes.project_name), + ('project', self.projects[0].name), ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) - self.quotas_mock.get.assert_called_once_with(identity_fakes.project_id) + self.compute_quotas_mock.get.assert_called_once_with( + self.projects[0].id, + ) self.volume_quotas_mock.get.assert_called_once_with( - identity_fakes.project_id) + self.projects[0].id, + ) self.network.get_quota.assert_called_once_with( - identity_fakes.project_id) + self.projects[0].id, + ) self.assertNotCalled(self.network.get_quota_default) def test_quota_show_with_default(self): arglist = [ '--default', - identity_fakes.project_name, + self.projects[0].name, ] verifylist = [ ('default', True), - ('project', identity_fakes.project_name), + ('project', self.projects[0].name), ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) - self.quotas_mock.defaults.assert_called_once_with( - identity_fakes.project_id) + self.compute_quotas_mock.defaults.assert_called_once_with( + self.projects[0].id, + ) self.volume_quotas_mock.defaults.assert_called_once_with( - identity_fakes.project_id) + self.projects[0].id, + ) self.network.get_quota_default.assert_called_once_with( - identity_fakes.project_id) + self.projects[0].id, + ) self.assertNotCalled(self.network.get_quota) def test_quota_show_with_class(self): arglist = [ '--class', - identity_fakes.project_name, + self.projects[0].name, ] verifylist = [ ('quota_class', True), - ('project', identity_fakes.project_name), + ('project', self.projects[0].name), ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) - self.quotas_class_mock.get.assert_called_once_with( - identity_fakes.project_name) + self.compute_quotas_class_mock.get.assert_called_once_with( + self.projects[0].name, + ) self.volume_quotas_class_mock.get.assert_called_once_with( - identity_fakes.project_name) + self.projects[0].name, + ) self.assertNotCalled(self.network.get_quota) self.assertNotCalled(self.network.get_quota_default) @@ -513,167 +841,13 @@ def test_quota_show_no_project(self): self.cmd.take_action(parsed_args) - self.quotas_mock.get.assert_called_once_with(identity_fakes.project_id) + self.compute_quotas_mock.get.assert_called_once_with( + identity_fakes.project_id, + ) self.volume_quotas_mock.get.assert_called_once_with( - identity_fakes.project_id) + identity_fakes.project_id, + ) self.network.get_quota.assert_called_once_with( - identity_fakes.project_id) - self.assertNotCalled(self.network.get_quota_default) - - -class TestQuotaList(TestQuota): - """Test cases for quota list command""" - - project = identity_fakes_v3.FakeProject.create_one_project() - - quota_list = network_fakes.FakeQuota.create_one_net_quota() - quota_list1 = compute_fakes.FakeQuota.create_one_comp_quota() - quota_list2 = volume_fakes.FakeQuota.create_one_vol_quota() - - default_quota = network_fakes.FakeQuota.create_one_default_net_quota() - default_quota1 = compute_fakes.FakeQuota.create_one_default_comp_quota() - default_quota2 = volume_fakes.FakeQuota.create_one_default_vol_quota() - - reference_data = (project.id, - quota_list.floating_ips, - quota_list.networks, - quota_list.ports, - quota_list.rbac_policies, - quota_list.routers, - quota_list.security_groups, - quota_list.security_group_rules, - quota_list.subnets, - quota_list.subnet_pools) - - comp_reference_data = (project.id, - quota_list1.cores, - quota_list1.fixed_ips, - quota_list1.injected_files, - quota_list1.injected_file_content_bytes, - quota_list1.injected_file_path_bytes, - quota_list1.instances, - quota_list1.key_pairs, - quota_list1.metadata_items, - quota_list1.ram, - quota_list1.server_groups, - quota_list1.server_group_members) - - vol_reference_data = (project.id, - quota_list2.backups, - quota_list2.backup_gigabytes, - quota_list2.gigabytes, - quota_list2.per_volume_gigabytes, - quota_list2.snapshots, - quota_list2.volumes) - - net_column_header = ( - 'Project ID', - 'Floating IPs', - 'Networks', - 'Ports', - 'RBAC Policies', - 'Routers', - 'Security Groups', - 'Security Group Rules', - 'Subnets', - 'Subnet Pools' - ) - - comp_column_header = ( - 'Project ID', - 'Cores', - 'Fixed IPs', - 'Injected Files', - 'Injected File Content Bytes', - 'Injected File Path Bytes', - 'Instances', - 'Key Pairs', - 'Metadata Items', - 'Ram', - 'Server Groups', - 'Server Group Members', - ) - - vol_column_header = ( - 'Project ID', - 'Backups', - 'Backup Gigabytes', - 'Gigabytes', - 'Per Volume Gigabytes', - 'Snapshots', - 'Volumes', - ) - - def setUp(self): - super(TestQuotaList, self).setUp() - - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, + identity_fakes.project_id, ) - - self.identity = self.app.client_manager.identity - self.identity.tenants.list = mock.Mock(return_value=[self.project]) - - self.network = self.app.client_manager.network - self.compute = self.app.client_manager.compute - self.volume = self.app.client_manager.volume - - self.network.get_quota = mock.Mock(return_value=self.quota_list) - self.compute.quotas.get = mock.Mock(return_value=self.quota_list1) - self.volume.quotas.get = mock.Mock(return_value=self.quota_list2) - - self.network.get_quota_default = mock.Mock( - return_value=self.default_quota) - self.compute.quotas.defaults = mock.Mock( - return_value=self.default_quota1) - self.volume.quotas.defaults = mock.Mock( - return_value=self.default_quota2) - - self.cmd = quota.ListQuota(self.app, None) - - def test_quota_list_network(self): - arglist = [ - '--network' - ] - verifylist = [ - ('network', True) - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.assertEqual(self.net_column_header, columns) - - self.assertEqual(self.reference_data, list(data)[0]) - - def test_quota_list_compute(self): - arglist = [ - '--compute' - ] - verifylist = [ - ('compute', True) - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.assertEqual(self.comp_column_header, columns) - - self.assertEqual(self.comp_reference_data, list(data)[0]) - - def test_quota_list_volume(self): - arglist = [ - '--volume' - ] - verifylist = [ - ('volume', True) - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.assertEqual(self.vol_column_header, columns) - - self.assertEqual(self.vol_reference_data, list(data)[0]) + self.assertNotCalled(self.network.get_quota_default) From 941e189ac14f2fdce3e70c3dd1ce85aa25fc4782 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 3 May 2017 12:23:14 +0000 Subject: [PATCH 1674/3095] Updated from global requirements Change-Id: I9a60f1b0db78ed188966287939dbd8a6f82c1682 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 9d3d80d689..faf34c0a6b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ six>=1.9.0 # MIT Babel!=2.4.0,>=2.3.4 # BSD cliff>=2.6.0 # Apache-2.0 -keystoneauth1>=2.18.0 # Apache-2.0 +keystoneauth1>=2.20.0 # Apache-2.0 openstacksdk>=0.9.14 # Apache-2.0 osc-lib>=1.5.1 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 From 07a4363e995716f9b898623d9e43746fd36b5172 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 3 May 2017 14:39:35 -0500 Subject: [PATCH 1675/3095] Skip floating ip attach functional test on nova-net As of Ocata release Nova forces nova-network to run in a cells v1 configuration. Floating IP and network functions currently do not work in the gate jobs so we have to skip this. It is known to work tested against a Mitaka nova-net DevStack without cells. Change-Id: I74f67ac8eb12c7a649ddcbd7979cf745fb35cc0c --- .../tests/functional/compute/v2/test_server.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py index a86c0c679d..7d54b6a6a5 100644 --- a/openstackclient/tests/functional/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -203,19 +203,19 @@ def test_server_actions(self): def test_server_attach_detach_floating_ip(self): """Test floating ip create/delete; server add/remove floating ip""" + if not self.haz_network: + # NOTE(dtroyer): As of Ocata release Nova forces nova-network to + # run in a cells v1 configuration. Floating IP + # and network functions currently do not work in + # the gate jobs so we have to skip this. It is + # known to work tested against a Mitaka nova-net + # DevStack without cells. + self.skipTest("No Network service present") + cmd_output = self.server_create() name = cmd_output['name'] self.wait_for_status(name, "ACTIVE") - if not self.haz_network: - # nova-net needs a public subnet - cmd_output = json.loads(self.openstack( - 'network create -f json ' + - '--subnet 8.6.7.5/28 ' + - 'public' - )) - self.addCleanup(self.openstack, 'network delete public') - # attach ip cmd_output = json.loads(self.openstack( 'floating ip create -f json ' + From c69304e3d365dc2c67fab298eba0b9097d3819da Mon Sep 17 00:00:00 2001 From: Vasyl Saienko Date: Thu, 4 May 2017 10:48:11 +0300 Subject: [PATCH 1676/3095] Do not always init compute_client when doint port list This patch ensures that compute client is initialized only when needed (--server arg is passed) to openstack port list command. Otherwise it leads to failures on installations without Nova. Change-Id: I102683461daa2f7d05dd9d7a3ec72de551c65ca9 Closes-Bug: #1688194 --- openstackclient/network/v2/port.py | 2 +- releasenotes/notes/bug-1688194-bb008b65267a1169.yaml | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/bug-1688194-bb008b65267a1169.yaml diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 3a32916b78..0ed2e44a2d 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -519,7 +519,6 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): network_client = self.app.client_manager.network - compute_client = self.app.client_manager.compute identity_client = self.app.client_manager.identity columns = ( @@ -548,6 +547,7 @@ def take_action(self, parsed_args): ignore_missing=False) filters['device_id'] = _router.id if parsed_args.server: + compute_client = self.app.client_manager.compute server = utils.find_resource(compute_client.servers, parsed_args.server) filters['device_id'] = server.id diff --git a/releasenotes/notes/bug-1688194-bb008b65267a1169.yaml b/releasenotes/notes/bug-1688194-bb008b65267a1169.yaml new file mode 100644 index 0000000000..f7e6ad4975 --- /dev/null +++ b/releasenotes/notes/bug-1688194-bb008b65267a1169.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Fix issue in ``port list`` command when no Compute endpoint is in the + Service Catalog. + [Bug `1688194 `_] From 6f31634f17a4a46d9a6bf562f9b2c77e7f797300 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Sat, 6 May 2017 14:30:13 -0500 Subject: [PATCH 1677/3095] Rework floating ip functional tests Regualr expresstions were an interesting diversion, get rid of them from the functional tests. It did lead to better structure... Change-Id: I1ff32ad7715ebd88401925ce3f6c412a66e82566 --- .../functional/network/v2/test_floating_ip.py | 318 ++++++++++-------- 1 file changed, 178 insertions(+), 140 deletions(-) diff --git a/openstackclient/tests/functional/network/v2/test_floating_ip.py b/openstackclient/tests/functional/network/v2/test_floating_ip.py index ccb954e954..96521f09da 100644 --- a/openstackclient/tests/functional/network/v2/test_floating_ip.py +++ b/openstackclient/tests/functional/network/v2/test_floating_ip.py @@ -10,8 +10,8 @@ # License for the specific language governing permissions and limitations # under the License. +import json import random -import re import uuid from openstackclient.tests.functional.network.v2 import common @@ -19,8 +19,8 @@ class FloatingIpTests(common.NetworkTests): """Functional tests for floating ip""" - SUBNET_NAME = uuid.uuid4().hex - NETWORK_NAME = uuid.uuid4().hex + EXTERNAL_NETWORK_NAME = uuid.uuid4().hex + EXTERNAL_SUBNET_NAME = uuid.uuid4().hex PRIVATE_NETWORK_NAME = uuid.uuid4().hex PRIVATE_SUBNET_NAME = uuid.uuid4().hex ROUTER = uuid.uuid4().hex @@ -30,26 +30,20 @@ class FloatingIpTests(common.NetworkTests): def setUpClass(cls): common.NetworkTests.setUpClass() if cls.haz_network: - # Set up some regex for matching below - cls.re_id = re.compile("id\s+\|\s+(\S+)") - cls.re_floating_ip = re.compile("floating_ip_address\s+\|\s+(\S+)") - cls.re_fixed_ip = re.compile("fixed_ip_address\s+\|\s+(\S+)") - cls.re_description = re.compile("description\s+\|\s+([^|]+?)\s+\|") - cls.re_network_id = re.compile("floating_network_id\s+\|\s+(\S+)") - cls.re_port_id = re.compile("\s+id\s+\|\s+(\S+)") - cls.re_fp_port_id = re.compile("\s+port_id\s+\|\s+(\S+)") - # Create a network for the floating ip - raw_output = cls.openstack( - 'network create --external ' + cls.NETWORK_NAME - ) - cls.network_id = re.search(cls.re_id, raw_output).group(1) + json_output = json.loads(cls.openstack( + 'network create -f json ' + + '--external ' + + cls.EXTERNAL_NETWORK_NAME + )) + cls.external_network_id = json_output["id"] # Create a private network for the port - raw_output = cls.openstack( - 'network create ' + cls.PRIVATE_NETWORK_NAME - ) - cls.private_network_id = re.search(cls.re_id, raw_output).group(1) + json_output = json.loads(cls.openstack( + 'network create -f json ' + + cls.PRIVATE_NETWORK_NAME + )) + cls.private_network_id = json_output["id"] # Try random subnet range for subnet creating # Because we can not determine ahead of time what subnets are @@ -57,7 +51,7 @@ def setUpClass(cls): # try 4 times for i in range(4): # Make a random subnet - cls.subnet = ".".join(map( + cls.external_subnet = ".".join(map( str, (random.randint(0, 223) for _ in range(3)) )) + ".0/26" @@ -67,19 +61,21 @@ def setUpClass(cls): )) + ".0/26" try: # Create a subnet for the network - raw_output = cls.openstack( - 'subnet create ' + - '--network ' + cls.NETWORK_NAME + ' ' + - '--subnet-range ' + cls.subnet + ' ' + - cls.SUBNET_NAME - ) + json_output = json.loads(cls.openstack( + 'subnet create -f json ' + + '--network ' + cls.EXTERNAL_NETWORK_NAME + ' ' + + '--subnet-range ' + cls.external_subnet + ' ' + + cls.EXTERNAL_SUBNET_NAME + )) + cls.external_subnet_id = json_output["id"] # Create a subnet for the private network - priv_raw_output = cls.openstack( - 'subnet create ' + + json_output = json.loads(cls.openstack( + 'subnet create -f json ' + '--network ' + cls.PRIVATE_NETWORK_NAME + ' ' + '--subnet-range ' + cls.private_subnet + ' ' + cls.PRIVATE_SUBNET_NAME - ) + )) + cls.private_subnet_id = json_output["id"] except Exception: if (i == 3): # raise the exception at the last time @@ -89,30 +85,21 @@ def setUpClass(cls): # break and no longer retry if create sucessfully break - cls.subnet_id = re.search(cls.re_id, raw_output).group(1) - cls.private_subnet_id = re.search( - cls.re_id, priv_raw_output - ).group(1) - @classmethod def tearDownClass(cls): if cls.haz_network: - raw_output = cls.openstack( - 'subnet delete ' + cls.SUBNET_NAME, + del_output = cls.openstack( + 'subnet delete ' + + cls.EXTERNAL_SUBNET_NAME + ' ' + + cls.PRIVATE_SUBNET_NAME ) - cls.assertOutput('', raw_output) - raw_output = cls.openstack( - 'subnet delete ' + cls.PRIVATE_SUBNET_NAME, + cls.assertOutput('', del_output) + del_output = cls.openstack( + 'network delete ' + + cls.EXTERNAL_NETWORK_NAME + ' ' + + cls.PRIVATE_NETWORK_NAME ) - cls.assertOutput('', raw_output) - raw_output = cls.openstack( - 'network delete ' + cls.NETWORK_NAME, - ) - cls.assertOutput('', raw_output) - raw_output = cls.openstack( - 'network delete ' + cls.PRIVATE_NETWORK_NAME, - ) - cls.assertOutput('', raw_output) + cls.assertOutput('', del_output) def setUp(self): super(FloatingIpTests, self).setUp() @@ -120,146 +107,197 @@ def setUp(self): if not self.haz_network: self.skipTest("No Network service present") + # Verify setup + self.assertIsNotNone(self.external_network_id) + self.assertIsNotNone(self.private_network_id) + self.assertIsNotNone(self.external_subnet_id) + self.assertIsNotNone(self.private_subnet_id) + def test_floating_ip_delete(self): """Test create, delete multiple""" - raw_output = self.openstack( - 'floating ip create ' + + json_output = json.loads(self.openstack( + 'floating ip create -f json ' + '--description aaaa ' + - self.NETWORK_NAME - ) - re_ip = re.search(self.re_floating_ip, raw_output) - self.assertIsNotNone(re_ip) - ip1 = re_ip.group(1) + self.EXTERNAL_NETWORK_NAME + )) + self.assertIsNotNone(json_output["id"]) + ip1 = json_output["id"] self.assertEqual( 'aaaa', - re.search(self.re_description, raw_output).group(1), + json_output["description"], ) - raw_output = self.openstack( - 'floating ip create ' + + json_output = json.loads(self.openstack( + 'floating ip create -f json ' + '--description bbbb ' + - self.NETWORK_NAME - ) - ip2 = re.search(self.re_floating_ip, raw_output).group(1) + self.EXTERNAL_NETWORK_NAME + )) + self.assertIsNotNone(json_output["id"]) + ip2 = json_output["id"] self.assertEqual( 'bbbb', - re.search(self.re_description, raw_output).group(1), + json_output["description"], ) # Clean up after ourselves - raw_output = self.openstack('floating ip delete ' + ip1 + ' ' + ip2) - self.assertOutput('', raw_output) + del_output = self.openstack('floating ip delete ' + ip1 + ' ' + ip2) + self.assertOutput('', del_output) + + self.assertIsNotNone(json_output["floating_network_id"]) def test_floating_ip_list(self): """Test create defaults, list filters, delete""" - raw_output = self.openstack( - 'floating ip create ' + + json_output = json.loads(self.openstack( + 'floating ip create -f json ' + '--description aaaa ' + - self.NETWORK_NAME - ) - re_ip = re.search(self.re_floating_ip, raw_output) - self.assertIsNotNone(re_ip) - ip1 = re_ip.group(1) + self.EXTERNAL_NETWORK_NAME + )) + self.assertIsNotNone(json_output["id"]) + ip1 = json_output["id"] self.addCleanup(self.openstack, 'floating ip delete ' + ip1) self.assertEqual( 'aaaa', - re.search(self.re_description, raw_output).group(1), + json_output["description"], ) - self.assertIsNotNone(re.search(self.re_network_id, raw_output)) + self.assertIsNotNone(json_output["floating_network_id"]) + fip1 = json_output["floating_ip_address"] - raw_output = self.openstack( - 'floating ip create ' + + json_output = json.loads(self.openstack( + 'floating ip create -f json ' + '--description bbbb ' + - self.NETWORK_NAME - ) - ip2 = re.search(self.re_floating_ip, raw_output).group(1) + self.EXTERNAL_NETWORK_NAME + )) + self.assertIsNotNone(json_output["id"]) + ip2 = json_output["id"] self.addCleanup(self.openstack, 'floating ip delete ' + ip2) self.assertEqual( 'bbbb', - re.search(self.re_description, raw_output).group(1), + json_output["description"], ) + self.assertIsNotNone(json_output["floating_network_id"]) + fip2 = json_output["floating_ip_address"] # Test list - raw_output = self.openstack('floating ip list') - self.assertIsNotNone(re.search("\|\s+" + ip1 + "\s+\|", raw_output)) - self.assertIsNotNone(re.search("\|\s+" + ip2 + "\s+\|", raw_output)) + json_output = json.loads(self.openstack( + 'floating ip list -f json' + )) + fip_map = { + item.get('ID'): + item.get('Floating IP Address') for item in json_output + } + # self.assertEqual(item_map, json_output) + self.assertIn(ip1, fip_map.keys()) + self.assertIn(ip2, fip_map.keys()) + self.assertIn(fip1, fip_map.values()) + self.assertIn(fip2, fip_map.values()) # Test list --long - raw_output = self.openstack('floating ip list --long') - self.assertIsNotNone(re.search("\|\s+" + ip1 + "\s+\|", raw_output)) - self.assertIsNotNone(re.search("\|\s+" + ip2 + "\s+\|", raw_output)) + json_output = json.loads(self.openstack( + 'floating ip list -f json ' + + '--long' + )) + fip_map = { + item.get('ID'): + item.get('Floating IP Address') for item in json_output + } + self.assertIn(ip1, fip_map.keys()) + self.assertIn(ip2, fip_map.keys()) + self.assertIn(fip1, fip_map.values()) + self.assertIn(fip2, fip_map.values()) + desc_map = { + item.get('ID'): item.get('Description') for item in json_output + } + self.assertIn('aaaa', desc_map.values()) + self.assertIn('bbbb', desc_map.values()) # TODO(dtroyer): add more filter tests - def test_floating_ip_show(self): - """Test show""" - raw_output = self.openstack( - 'floating ip create ' + - '--description shosho ' + - # '--fixed-ip-address 1.2.3.4 ' + - self.NETWORK_NAME + json_output = json.loads(self.openstack( + 'floating ip show -f json ' + + ip1 + )) + self.assertIsNotNone(json_output["id"]) + self.assertEqual( + ip1, + json_output["id"], ) - re_ip = re.search(self.re_floating_ip, raw_output) - self.assertIsNotNone(re_ip) - ip = re_ip.group(1) - - raw_output = self.openstack('floating ip show ' + ip) - self.addCleanup(self.openstack, 'floating ip delete ' + ip) - self.assertEqual( - 'shosho', - re.search(self.re_description, raw_output).group(1), + 'aaaa', + json_output["description"], + ) + self.assertIsNotNone(json_output["floating_network_id"]) + self.assertEqual( + fip1, + json_output["floating_ip_address"], ) - # TODO(dtroyer): not working??? - # self.assertEqual( - # '1.2.3.4', - # re.search(self.re_floating_ip, raw_output).group(1), - # ) - self.assertIsNotNone(re.search(self.re_network_id, raw_output)) def test_floating_ip_set_and_unset_port(self): """Test Floating IP Set and Unset port""" - raw_output = self.openstack( - 'floating ip create ' + - '--description shosho ' + - self.NETWORK_NAME + json_output = json.loads(self.openstack( + 'floating ip create -f json ' + + '--description aaaa ' + + self.EXTERNAL_NETWORK_NAME + )) + self.assertIsNotNone(json_output["id"]) + ip1 = json_output["id"] + self.addCleanup(self.openstack, 'floating ip delete ' + ip1) + self.assertEqual( + 'aaaa', + json_output["description"], ) - re_ip = re.search(self.re_floating_ip, raw_output) - fp_ip = re_ip.group(1) - self.addCleanup(self.openstack, 'floating ip delete ' + fp_ip) - self.assertIsNotNone(fp_ip) - raw_output1 = self.openstack( - 'port create --network ' + self.PRIVATE_NETWORK_NAME - + ' --fixed-ip subnet=' + self.PRIVATE_SUBNET_NAME + - ' ' + self.PORT_NAME - ) - re_port_id = re.search(self.re_port_id, raw_output1) - self.assertIsNotNone(re_port_id) - port_id = re_port_id.group(1) + json_output = json.loads(self.openstack( + 'port create -f json ' + + '--network ' + self.PRIVATE_NETWORK_NAME + ' ' + + '--fixed-ip subnet=' + self.PRIVATE_SUBNET_NAME + ' ' + + self.PORT_NAME + )) + self.assertIsNotNone(json_output["id"]) + port_id = json_output["id"] - router = self.openstack('router create ' + self.ROUTER) - self.assertIsNotNone(router) + json_output = json.loads(self.openstack( + 'router create -f json ' + + self.ROUTER + )) + self.assertIsNotNone(json_output["id"]) self.addCleanup(self.openstack, 'router delete ' + self.ROUTER) - self.openstack('router add port ' + self.ROUTER + - ' ' + port_id) - self.openstack('router set --external-gateway ' + self.NETWORK_NAME + - ' ' + self.ROUTER) + self.openstack( + 'router add port ' + + self.ROUTER + ' ' + + port_id + ) - self.addCleanup(self.openstack, 'router unset --external-gateway ' - + self.ROUTER) - self.addCleanup(self.openstack, 'router remove port ' + self.ROUTER - + ' ' + port_id) + self.openstack( + 'router set ' + + '--external-gateway ' + self.EXTERNAL_NETWORK_NAME + ' ' + + self.ROUTER + ) + self.addCleanup( + self.openstack, + 'router unset --external-gateway ' + self.ROUTER, + ) + self.addCleanup( + self.openstack, + 'router remove port ' + self.ROUTER + ' ' + port_id, + ) - raw_output = self.openstack( + self.openstack( 'floating ip set ' + - fp_ip + ' --port ' + port_id) - self.addCleanup(self.openstack, 'floating ip unset --port ' + fp_ip) + '--port ' + port_id + ' ' + + ip1 + ) + self.addCleanup( + self.openstack, + 'floating ip unset --port ' + ip1, + ) - show_output = self.openstack( - 'floating ip show ' + fp_ip) + json_output = json.loads(self.openstack( + 'floating ip show -f json ' + + ip1 + )) self.assertEqual( port_id, - re.search(self.re_fp_port_id, show_output).group(1)) + json_output["port_id"], + ) From e8f3103cc14b62226a5d71d2018b8e1c96c8a2d8 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 11 May 2017 08:46:32 -0500 Subject: [PATCH 1678/3095] Ignore more exceptions in quota list Additional exceptions can be thrown here, ignore additional project lookup exceptions, but still not all. Server failures are still interesting, for example. Change-Id: I9a750ae8e8efa29a36bbd1e34b50b6ace0658260 --- openstackclient/common/quota.py | 5 +- .../tests/unit/common/test_quota.py | 49 ++++++++++++++++++- 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index 6ed9e370db..0d5cb9be31 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -135,7 +135,10 @@ def take_action(self, parsed_args): try: data = compute_client.quotas.get(p) except Exception as ex: - if type(ex).__name__ == 'NotFound': + if ( + type(ex).__name__ == 'NotFound' or + ex.http_status >= 400 and ex.http_status <= 499 + ): # Project not found, move on to next one LOG.warning("Project %s not found: %s" % (p, ex)) continue diff --git a/openstackclient/tests/unit/common/test_quota.py b/openstackclient/tests/unit/common/test_quota.py index 1b0d2c3193..482653f460 100644 --- a/openstackclient/tests/unit/common/test_quota.py +++ b/openstackclient/tests/unit/common/test_quota.py @@ -242,7 +242,7 @@ def test_quota_list_compute_default(self): self.assertEqual(self.compute_reference_data, ret_quotas[0]) self.assertEqual(1, len(ret_quotas)) - def test_quota_list_compute_no_project(self): + def test_quota_list_compute_no_project_not_found(self): # Make one of the projects disappear self.compute.quotas.get = mock.Mock( side_effect=[ @@ -266,6 +266,53 @@ def test_quota_list_compute_no_project(self): self.assertEqual(self.compute_reference_data, ret_quotas[0]) self.assertEqual(1, len(ret_quotas)) + def test_quota_list_compute_no_project_4xx(self): + # Make one of the projects disappear + self.compute.quotas.get = mock.Mock( + side_effect=[ + self.compute_quotas[0], + exceptions.BadRequest("Bad request"), + ], + ) + + arglist = [ + '--compute', + ] + verifylist = [ + ('compute', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + ret_quotas = list(data) + + self.assertEqual(self.compute_column_header, columns) + self.assertEqual(self.compute_reference_data, ret_quotas[0]) + self.assertEqual(1, len(ret_quotas)) + + def test_quota_list_compute_no_project_5xx(self): + # Make one of the projects disappear + self.compute.quotas.get = mock.Mock( + side_effect=[ + self.compute_quotas[0], + exceptions.HTTPNotImplemented("Not implemented??"), + ], + ) + + arglist = [ + '--compute', + ] + verifylist = [ + ('compute', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.HTTPNotImplemented, + self.cmd.take_action, + parsed_args, + ) + def test_quota_list_network(self): # Two projects with non-default quotas self.network.get_quota = mock.Mock( From 0181de38afc8cc4b96f226b00e173fb0c0d2e4dc Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Mon, 15 May 2017 00:54:47 +0000 Subject: [PATCH 1679/3095] Updated from global requirements Change-Id: I6210e31952a6c4b6a07f5ea357500130d41dacd6 --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 467b9695d0..136ccf8af1 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -3,7 +3,7 @@ # process, which may cause wedges in the gate later. hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0 -coverage>=4.0 # Apache-2.0 +coverage!=4.4,>=4.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD mock>=2.0 # BSD oslosphinx>=4.7.0 # Apache-2.0 From 3896d28de857da075ba7cb7f236fedd67ef71c95 Mon Sep 17 00:00:00 2001 From: caoyuan Date: Wed, 17 May 2017 00:45:51 +0800 Subject: [PATCH 1680/3095] Correct the "extra spec" command openstack Change-Id: Ia348b66a6a4340da8e9d07256a0c141792708cf6 --- doc/source/data/cinder.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/data/cinder.csv b/doc/source/data/cinder.csv index 385c738681..5c89e0864b 100644 --- a/doc/source/data/cinder.csv +++ b/doc/source/data/cinder.csv @@ -28,7 +28,7 @@ encryption-type-show,volume type list --encryption-show,Shows encryption type de encryption-type-update,volume type set --encryption-provider --enc..,Update encryption type information for a volume type (Admin Only). endpoints,catalog list,Discovers endpoints registered by authentication service. extend,volume set --size,Attempts to extend size of an existing volume. -extra-specs-list,volume types list --long,Lists current volume types and extra specs. +extra-specs-list,volume type list --long,Lists current volume types and extra specs. failover-host,volume host failover,Failover a replicating cinder-volume host. force-delete,volume delete --force,"Attempts force-delete of volume, regardless of state." freeze-host,volume host set --disable,Freeze and disable the specified cinder-volume host. @@ -101,4 +101,4 @@ unmanage,volume delete --remote,Stop managing a volume. upload-to-image,image create --volume,Uploads volume to Image Service as an image. bash-completion,complete,Prints arguments for bash_completion. help,help,Shows help about this program or one of its subcommands. -list-extensions,extension list --volume,Lists all available os-api extensions. \ No newline at end of file +list-extensions,extension list --volume,Lists all available os-api extensions. From 7a7bb06377c96f7bfc2c371aa2ff726ee364a9db Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Fri, 24 Feb 2017 16:48:05 +0800 Subject: [PATCH 1681/3095] Make block-device-mapping more stable and clear The patch fix the following issues: 1. ValueError is raised if input don't contain "=". Sometimes the whole "server create" command is very complex, it's difficult to find out root reason directly. 2. Don't support to add block device from snapshot, like: --block-device-mapping vdb=0c8ae9d8-cadc-4a23-8337-4254614d277e:snapshot:1, it's supported by novaclient, but not in osc. 3. If input "vdb=", not add any mapping information, the server will be launched successfully, not raise error message to let use add volume/snapshot id, just ignore "--block-device-mapping" option. 4. The help message of "block-device-mapping" option is so simple, need to add some details about how to add , contains. Change-Id: Ib7f7a654c3dc2a8272545f168b4c4ced230ce39e Depends-On: Ib37913891bbf7a31b570404c4668c490d5ac859b Closes-Bug: #1667266 --- doc/source/command-objects/server.rst | 18 +- openstackclient/compute/v2/server.py | 78 ++-- .../functional/compute/v2/test_server.py | 107 ++++++ .../tests/functional/volume/v2/test_volume.py | 10 +- .../tests/unit/compute/v2/test_server.py | 339 +++++++++++++++++- .../notes/bug-1667266-6497727abc2af9a5.yaml | 9 + 6 files changed, 519 insertions(+), 42 deletions(-) create mode 100644 releasenotes/notes/bug-1667266-6497727abc2af9a5.yaml diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index dc78080b6d..26bb77ca01 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -193,7 +193,23 @@ Create a new server .. option:: --block-device-mapping - Map block devices; map is ::: (optional extension) + Create a block device on the server. + + Block device mapping in the format + + =::: + + : block device name, like: vdb, xvdc (required) + + : UUID of the volume or snapshot (required) + + : volume or snapshot; default: volume (optional) + + : volume size if create from snapshot (optional) + + : true or false; default: false (optional) + + (optional extension) .. option:: --nic diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 60dc605caf..66bdeae853 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -446,10 +446,22 @@ def get_parser(self, prog_name): parser.add_argument( '--block-device-mapping', metavar='', - action='append', - default=[], - help=_('Map block devices; map is ' - '::: ' + action=parseractions.KeyValueAction, + default={}, + # NOTE(RuiChen): Add '\n' at the end of line to put each item in + # the separated line, avoid the help message looks + # messy, see _SmartHelpFormatter in cliff. + help=_('Create a block device on the server.\n' + 'Block device mapping in the format\n' + '=:::\n' + ': block device name, like: vdb, xvdc ' + '(required)\n' + ': UUID of the volume or snapshot (required)\n' + ': volume or snapshot; default: volume (optional)\n' + ': volume size if create from snapshot ' + '(optional)\n' + ': true or false; default: false ' + '(optional)\n' '(optional extension)'), ) parser.add_argument( @@ -593,33 +605,39 @@ def take_action(self, parsed_args): 'source_type': 'volume', 'destination_type': 'volume' }] - for dev_map in parsed_args.block_device_mapping: - dev_name, dev_map = dev_map.split('=', 1) - if dev_map: - dev_map = dev_map.split(':') - if len(dev_map) > 0: - mapping = { - 'device_name': dev_name, - 'uuid': utils.find_resource( - volume_client.volumes, - dev_map[0], - ).id} - # Block device mapping v1 compatibility - if len(dev_map) > 1 and \ - dev_map[1] in ('volume', 'snapshot'): - mapping['source_type'] = dev_map[1] - else: - mapping['source_type'] = 'volume' - mapping['destination_type'] = 'volume' - if len(dev_map) > 2 and dev_map[2]: - mapping['volume_size'] = dev_map[2] - if len(dev_map) > 3: - mapping['delete_on_termination'] = dev_map[3] + # Handle block device by device name order, like: vdb -> vdc -> vdd + for dev_name in sorted(six.iterkeys(parsed_args.block_device_mapping)): + dev_map = parsed_args.block_device_mapping[dev_name] + dev_map = dev_map.split(':') + if dev_map[0]: + mapping = {'device_name': dev_name} + # 1. decide source and destination type + if (len(dev_map) > 1 and + dev_map[1] in ('volume', 'snapshot')): + mapping['source_type'] = dev_map[1] else: - msg = _("Volume name or ID must be specified if " - "--block-device-mapping is specified") - raise exceptions.CommandError(msg) - block_device_mapping_v2.append(mapping) + mapping['source_type'] = 'volume' + mapping['destination_type'] = 'volume' + # 2. check target exist, update target uuid according by + # source type + if mapping['source_type'] == 'volume': + volume_id = utils.find_resource( + volume_client.volumes, dev_map[0]).id + mapping['uuid'] = volume_id + elif mapping['source_type'] == 'snapshot': + snapshot_id = utils.find_resource( + volume_client.volume_snapshots, dev_map[0]).id + mapping['uuid'] = snapshot_id + # 3. append size and delete_on_termination if exist + if len(dev_map) > 2 and dev_map[2]: + mapping['volume_size'] = dev_map[2] + if len(dev_map) > 3 and dev_map[3]: + mapping['delete_on_termination'] = dev_map[3] + else: + msg = _("Volume or snapshot (name or ID) must be specified if " + "--block-device-mapping is specified") + raise exceptions.CommandError(msg) + block_device_mapping_v2.append(mapping) nics = [] auto_or_none = False diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py index 7d54b6a6a5..9f542e2915 100644 --- a/openstackclient/tests/functional/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -388,6 +388,113 @@ def test_server_boot_from_volume(self): cmd_output['status'], ) + def test_server_boot_with_bdm_snapshot(self): + """Test server create from image with bdm snapshot, server delete""" + # get volume status wait function + volume_wait_for = test_volume.VolumeTests( + methodName='wait_for', + ).wait_for + + # create source empty volume + empty_volume_name = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume create -f json ' + + '--size 1 ' + + empty_volume_name + )) + self.assertIsNotNone(cmd_output["id"]) + self.addCleanup(self.openstack, + 'volume delete ' + empty_volume_name) + self.assertEqual( + empty_volume_name, + cmd_output['name'], + ) + volume_wait_for("volume", empty_volume_name, "available") + + # create snapshot of source empty volume + empty_snapshot_name = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume snapshot create -f json ' + + '--volume ' + empty_volume_name + ' ' + + empty_snapshot_name + )) + self.assertIsNotNone(cmd_output["id"]) + self.assertEqual( + empty_snapshot_name, + cmd_output['name'], + ) + volume_wait_for("volume snapshot", empty_snapshot_name, "available") + + # create server with bdm snapshot + server_name = uuid.uuid4().hex + server = json.loads(self.openstack( + 'server create -f json ' + + '--flavor ' + self.flavor_name + ' ' + + '--image ' + self.image_name + ' ' + + '--block-device-mapping ' + 'vdb=' + empty_snapshot_name + ':snapshot:1:true ' + + self.network_arg + ' ' + + '--wait ' + + server_name + )) + self.assertIsNotNone(server["id"]) + self.assertEqual( + server_name, + server['name'], + ) + self.wait_for_status(server_name, 'ACTIVE') + + # check server volumes_attached, format is + # {"volumes_attached": "id='2518bc76-bf0b-476e-ad6b-571973745bb5'",} + cmd_output = json.loads(self.openstack( + 'server show -f json ' + + server_name + )) + volumes_attached = cmd_output['volumes_attached'] + self.assertTrue(volumes_attached.startswith('id=')) + attached_volume_id = volumes_attached.replace('id=', '') + + # check the volume that attached on server + cmd_output = json.loads(self.openstack( + 'volume show -f json ' + + attached_volume_id + )) + attachments = cmd_output['attachments'] + self.assertEqual( + 1, + len(attachments), + ) + self.assertEqual( + server['id'], + attachments[0]['server_id'], + ) + self.assertEqual( + "in-use", + cmd_output['status'], + ) + + # delete server, then check the attached volume had been deleted, + # =true + self.openstack('server delete --wait ' + server_name) + cmd_output = json.loads(self.openstack( + 'volume list -f json' + )) + target_volume = [each_volume + for each_volume in cmd_output + if each_volume['ID'] == attached_volume_id] + if target_volume: + # check the attached volume is 'deleting' status + self.assertEqual('deleting', target_volume[0]['Status']) + else: + # the attached volume had been deleted + pass + + # clean up volume snapshot manually, make sure the snapshot and volume + # can be deleted sequentially, self.addCleanup so fast, that cause + # volume service API 400 error and the volume is left over at the end. + self.openstack('volume snapshot delete ' + empty_snapshot_name) + volume_wait_for('volume snapshot', empty_snapshot_name, 'disappear') + def test_server_create_with_none_network(self): """Test server create with none network option.""" server_name = uuid.uuid4().hex diff --git a/openstackclient/tests/functional/volume/v2/test_volume.py b/openstackclient/tests/functional/volume/v2/test_volume.py index ce98236f8d..94ac792f91 100644 --- a/openstackclient/tests/functional/volume/v2/test_volume.py +++ b/openstackclient/tests/functional/volume/v2/test_volume.py @@ -14,6 +14,8 @@ import time import uuid +from tempest.lib import exceptions + from openstackclient.tests.functional.volume.v2 import common @@ -253,7 +255,13 @@ def wait_for(self, check_type, check_name, desired_status, wait=120, total_sleep = 0 opts = self.get_opts(['status']) while total_sleep < wait: - status = self.openstack(check_type + ' show ' + check_name + opts) + try: + status = self.openstack( + check_type + ' show ' + check_name + opts + ) + except exceptions.CommandFailed: + # Show command raise Exception when object had been deleted + status = 'disappear' status = status.rstrip() print('Checking {} {} Waiting for {} current status: {}' .format(check_type, check_name, desired_status, status)) diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 9370bf6bd1..d25cabbb49 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -12,6 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. # +import argparse import collections import getpass import mock @@ -49,6 +50,10 @@ def setUp(self): self.volumes_mock = self.app.client_manager.volume.volumes self.volumes_mock.reset_mock() + # Get a shortcut to the volume client VolumeManager Mock + self.snapshots_mock = self.app.client_manager.volume.volume_snapshots + self.snapshots_mock.reset_mock() + # Set object attributes to be tested. Could be overwritten in subclass. self.attrs = {} @@ -326,7 +331,9 @@ def setUp(self): self.volume = volume_fakes.FakeVolume.create_one_volume() self.volumes_mock.get.return_value = self.volume - self.block_device_mapping = 'vda=' + self.volume.name + ':::0' + + self.snapshot = volume_fakes.FakeSnapshot.create_one_snapshot() + self.snapshots_mock.get.return_value = self.snapshot # Get the command object to test self.cmd = server.CreateServer(self.app, None) @@ -852,13 +859,13 @@ def test_server_create_with_block_device_mapping(self): arglist = [ '--image', 'image1', '--flavor', self.flavor.id, - '--block-device-mapping', self.block_device_mapping, + '--block-device-mapping', 'vda=' + self.volume.name + ':::false', self.new_server.name, ] verifylist = [ ('image', 'image1'), ('flavor', self.flavor.id), - ('block_device_mapping', [self.block_device_mapping]), + ('block_device_mapping', {'vda': self.volume.name + ':::false'}), ('config_drive', False), ('server_name', self.new_server.name), ] @@ -867,11 +874,6 @@ def test_server_create_with_block_device_mapping(self): # CreateServer.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) - real_volume_mapping = ( - (self.block_device_mapping.split('=', 1)[1]).replace( - self.volume.name, - self.volume.id)) - # Set expected values kwargs = dict( meta=None, @@ -885,10 +887,167 @@ def test_server_create_with_block_device_mapping(self): availability_zone=None, block_device_mapping_v2=[{ 'device_name': 'vda', - 'uuid': real_volume_mapping.split(':', 1)[0], + 'uuid': self.volume.id, + 'destination_type': 'volume', + 'source_type': 'volume', + 'delete_on_termination': 'false', + }], + nics=[], + scheduler_hints={}, + config_drive=None, + ) + # ServerManager.create(name, image, flavor, **kwargs) + self.servers_mock.create.assert_called_with( + self.new_server.name, + self.image, + self.flavor, + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) + + def test_server_create_with_block_device_mapping_min_input(self): + arglist = [ + '--image', 'image1', + '--flavor', self.flavor.id, + '--block-device-mapping', 'vdf=' + self.volume.name, + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', self.flavor.id), + ('block_device_mapping', {'vdf': self.volume.name}), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # CreateServer.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = dict( + meta=None, + files={}, + reservation_id=None, + min_count=1, + max_count=1, + security_groups=[], + userdata=None, + key_name=None, + availability_zone=None, + block_device_mapping_v2=[{ + 'device_name': 'vdf', + 'uuid': self.volume.id, + 'destination_type': 'volume', + 'source_type': 'volume', + }], + nics=[], + scheduler_hints={}, + config_drive=None, + ) + # ServerManager.create(name, image, flavor, **kwargs) + self.servers_mock.create.assert_called_with( + self.new_server.name, + self.image, + self.flavor, + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) + + def test_server_create_with_block_device_mapping_default_input(self): + arglist = [ + '--image', 'image1', + '--flavor', self.flavor.id, + '--block-device-mapping', 'vdf=' + self.volume.name + ':::', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', self.flavor.id), + ('block_device_mapping', {'vdf': self.volume.name + ':::'}), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # CreateServer.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = dict( + meta=None, + files={}, + reservation_id=None, + min_count=1, + max_count=1, + security_groups=[], + userdata=None, + key_name=None, + availability_zone=None, + block_device_mapping_v2=[{ + 'device_name': 'vdf', + 'uuid': self.volume.id, + 'destination_type': 'volume', + 'source_type': 'volume', + }], + nics=[], + scheduler_hints={}, + config_drive=None, + ) + # ServerManager.create(name, image, flavor, **kwargs) + self.servers_mock.create.assert_called_with( + self.new_server.name, + self.image, + self.flavor, + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) + + def test_server_create_with_block_device_mapping_full_input(self): + arglist = [ + '--image', 'image1', + '--flavor', self.flavor.id, + '--block-device-mapping', + 'vde=' + self.volume.name + ':volume:3:true', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', self.flavor.id), + ('block_device_mapping', + {'vde': self.volume.name + ':volume:3:true'}), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # CreateServer.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = dict( + meta=None, + files={}, + reservation_id=None, + min_count=1, + max_count=1, + security_groups=[], + userdata=None, + key_name=None, + availability_zone=None, + block_device_mapping_v2=[{ + 'device_name': 'vde', + 'uuid': self.volume.id, 'destination_type': 'volume', 'source_type': 'volume', - 'delete_on_termination': '0' + 'delete_on_termination': 'true', + 'volume_size': '3' }], nics=[], scheduler_hints={}, @@ -905,6 +1064,166 @@ def test_server_create_with_block_device_mapping(self): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist(), data) + def test_server_create_with_block_device_mapping_snapshot(self): + arglist = [ + '--image', 'image1', + '--flavor', self.flavor.id, + '--block-device-mapping', + 'vds=' + self.volume.name + ':snapshot:5:true', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', self.flavor.id), + ('block_device_mapping', + {'vds': self.volume.name + ':snapshot:5:true'}), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # CreateServer.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = dict( + meta=None, + files={}, + reservation_id=None, + min_count=1, + max_count=1, + security_groups=[], + userdata=None, + key_name=None, + availability_zone=None, + block_device_mapping_v2=[{ + 'device_name': 'vds', + 'uuid': self.snapshot.id, + 'destination_type': 'volume', + 'source_type': 'snapshot', + 'delete_on_termination': 'true', + 'volume_size': '5' + }], + nics=[], + scheduler_hints={}, + config_drive=None, + ) + # ServerManager.create(name, image, flavor, **kwargs) + self.servers_mock.create.assert_called_with( + self.new_server.name, + self.image, + self.flavor, + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) + + def test_server_create_with_block_device_mapping_multiple(self): + arglist = [ + '--image', 'image1', + '--flavor', self.flavor.id, + '--block-device-mapping', 'vdb=' + self.volume.name + ':::false', + '--block-device-mapping', 'vdc=' + self.volume.name + ':::true', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', self.flavor.id), + ('block_device_mapping', {'vdb': self.volume.name + ':::false', + 'vdc': self.volume.name + ':::true'}), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # CreateServer.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = dict( + meta=None, + files={}, + reservation_id=None, + min_count=1, + max_count=1, + security_groups=[], + userdata=None, + key_name=None, + availability_zone=None, + block_device_mapping_v2=[ + { + 'device_name': 'vdb', + 'uuid': self.volume.id, + 'destination_type': 'volume', + 'source_type': 'volume', + 'delete_on_termination': 'false', + }, + { + 'device_name': 'vdc', + 'uuid': self.volume.id, + 'destination_type': 'volume', + 'source_type': 'volume', + 'delete_on_termination': 'true', + } + ], + nics=[], + scheduler_hints={}, + config_drive=None, + ) + # ServerManager.create(name, image, flavor, **kwargs) + self.servers_mock.create.assert_called_with( + self.new_server.name, + self.image, + self.flavor, + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) + + def test_server_create_with_block_device_mapping_invalid_format(self): + # 1. block device mapping don't contain equal sign "=" + arglist = [ + '--image', 'image1', + '--flavor', self.flavor.id, + '--block-device-mapping', 'not_contain_equal_sign', + self.new_server.name, + ] + self.assertRaises(argparse.ArgumentTypeError, + self.check_parser, + self.cmd, arglist, []) + # 2. block device mapping don't contain device name "=uuid:::true" + arglist = [ + '--image', 'image1', + '--flavor', self.flavor.id, + '--block-device-mapping', '=uuid:::true', + self.new_server.name, + ] + self.assertRaises(argparse.ArgumentTypeError, + self.check_parser, + self.cmd, arglist, []) + + def test_server_create_with_block_device_mapping_no_uuid(self): + arglist = [ + '--image', 'image1', + '--flavor', self.flavor.id, + '--block-device-mapping', 'vdb=', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', self.flavor.id), + ('block_device_mapping', {'vdb': ''}), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, + parsed_args) + class TestServerDelete(TestServer): diff --git a/releasenotes/notes/bug-1667266-6497727abc2af9a5.yaml b/releasenotes/notes/bug-1667266-6497727abc2af9a5.yaml new file mode 100644 index 0000000000..bedfa67f93 --- /dev/null +++ b/releasenotes/notes/bug-1667266-6497727abc2af9a5.yaml @@ -0,0 +1,9 @@ +--- +fixes: + - | + Make ``block-device-mapping`` option of ``server create`` command more + stable and clear. Fix ValueError when input block device mapping option in + wrong format. Support to create block device from snapshot. Add details in + help message about block-device-mapping option format and regular value of + each item. + [Bug `1667266 `_] From 62c793c7e4fd1da69f9daa79ed89b2488704528c Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Wed, 17 May 2017 01:30:12 +0000 Subject: [PATCH 1682/3095] Convert volume functional tests into JSON format volume_type and transfer_request func tests have not been converted into JSON func tests. This commit converts them into JSON format. Change-Id: I56820c4e15bda95e911e57657c1ff5437daf83ae --- .../volume/v1/test_transfer_request.py | 48 ++++---- .../functional/volume/v1/test_volume_type.py | 85 +++++++------- .../volume/v2/test_transfer_request.py | 51 ++++----- .../functional/volume/v2/test_volume_type.py | 104 +++++++++--------- 4 files changed, 135 insertions(+), 153 deletions(-) diff --git a/openstackclient/tests/functional/volume/v1/test_transfer_request.py b/openstackclient/tests/functional/volume/v1/test_transfer_request.py index 3fe11913f6..498c90567d 100644 --- a/openstackclient/tests/functional/volume/v1/test_transfer_request.py +++ b/openstackclient/tests/functional/volume/v1/test_transfer_request.py @@ -21,23 +21,19 @@ class TransferRequestTests(common.BaseVolumeTests): NAME = uuid.uuid4().hex VOLUME_NAME = uuid.uuid4().hex - HEADERS = ['Name'] - FIELDS = ['name'] @classmethod def setUpClass(cls): super(TransferRequestTests, cls).setUpClass() - opts = cls.get_opts(['display_name']) - raw_output = cls.openstack( - 'volume create --size 1 ' + cls.VOLUME_NAME + opts) - cls.assertOutput(cls.VOLUME_NAME + '\n', raw_output) + cmd_output = json.loads(cls.openstack( + 'volume create -f json --size 1 ' + cls.VOLUME_NAME)) + cls.assertOutput(cls.VOLUME_NAME, cmd_output['display_name']) - opts = cls.get_opts(cls.FIELDS) - raw_output = cls.openstack( - 'volume transfer request create ' + + cmd_output = json.loads(cls.openstack( + 'volume transfer request create -f json ' + cls.VOLUME_NAME + - ' --name ' + cls.NAME + opts) - cls.assertOutput(cls.NAME + '\n', raw_output) + ' --name ' + cls.NAME)) + cls.assertOutput(cls.NAME, cmd_output['name']) @classmethod def tearDownClass(cls): @@ -53,19 +49,18 @@ def test_volume_transfer_request_accept(self): name = uuid.uuid4().hex # create a volume - opts = self.get_opts(['display_name']) - raw_output = self.openstack( - 'volume create --size 1 ' + volume_name + opts) - self.assertEqual(volume_name + '\n', raw_output) + cmd_output = json.loads(self.openstack( + 'volume create -f json --size 1 ' + volume_name)) + self.assertEqual(volume_name, cmd_output['display_name']) # create volume transfer request for the volume # and get the auth_key of the new transfer request - opts = self.get_opts(['auth_key']) - auth_key = self.openstack( - 'volume transfer request create ' + + cmd_output = json.loads(self.openstack( + 'volume transfer request create -f json ' + volume_name + - ' --name ' + name + opts) - self.assertNotEqual('', auth_key) + ' --name ' + name)) + auth_key = cmd_output['auth_key'] + self.assertTrue(auth_key) # accept the volume transfer request json_output = json.loads(self.openstack( @@ -82,12 +77,11 @@ def test_volume_transfer_request_accept(self): self.assertEqual('', raw_output) def test_volume_transfer_request_list(self): - opts = self.get_opts(self.HEADERS) - raw_output = self.openstack('volume transfer request list' + opts) - self.assertIn(self.NAME, raw_output) + cmd_output = json.loads(self.openstack( + 'volume transfer request list -f json')) + self.assertIn(self.NAME, [req['Name'] for req in cmd_output]) def test_volume_transfer_request_show(self): - opts = self.get_opts(self.FIELDS) - raw_output = self.openstack( - 'volume transfer request show ' + self.NAME + opts) - self.assertEqual(self.NAME + '\n', raw_output) + cmd_output = json.loads(self.openstack( + 'volume transfer request show -f json ' + self.NAME)) + self.assertEqual(self.NAME, cmd_output['name']) diff --git a/openstackclient/tests/functional/volume/v1/test_volume_type.py b/openstackclient/tests/functional/volume/v1/test_volume_type.py index d1842795df..acad34add3 100644 --- a/openstackclient/tests/functional/volume/v1/test_volume_type.py +++ b/openstackclient/tests/functional/volume/v1/test_volume_type.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import json import time import uuid @@ -20,16 +21,13 @@ class VolumeTypeTests(common.BaseVolumeTests): """Functional tests for volume type. """ NAME = uuid.uuid4().hex - HEADERS = ['"Name"'] - FIELDS = ['name'] @classmethod def setUpClass(cls): super(VolumeTypeTests, cls).setUpClass() - opts = cls.get_opts(cls.FIELDS) - raw_output = cls.openstack('volume type create ' + cls.NAME + opts) - expected = cls.NAME + '\n' - cls.assertOutput(expected, raw_output) + cmd_output = json.loads(cls.openstack( + 'volume type create -f json ' + cls.NAME)) + cls.assertOutput(cls.NAME, cmd_output['name']) @classmethod def tearDownClass(cls): @@ -37,44 +35,45 @@ def tearDownClass(cls): cls.assertOutput('', raw_output) def test_volume_type_list(self): - opts = self.get_opts(self.HEADERS) - raw_output = self.openstack('volume type list' + opts) - self.assertIn(self.NAME, raw_output) + cmd_output = json.loads(self.openstack('volume type list -f json')) + self.assertIn(self.NAME, [t['Name'] for t in cmd_output]) def test_volume_type_show(self): - opts = self.get_opts(self.FIELDS) - raw_output = self.openstack('volume type show ' + self.NAME + opts) - self.assertEqual(self.NAME + "\n", raw_output) + cmd_output = json.loads(self.openstack( + 'volume type show -f json ' + self.NAME)) + self.assertEqual(self.NAME, cmd_output['name']) def test_volume_type_set_unset_properties(self): raw_output = self.openstack( 'volume type set --property a=b --property c=d ' + self.NAME) self.assertEqual("", raw_output) - opts = self.get_opts(["properties"]) - raw_output = self.openstack('volume type show ' + self.NAME + opts) - self.assertEqual("a='b', c='d'\n", raw_output) + cmd_output = json.loads(self.openstack( + 'volume type show -f json ' + self.NAME)) + self.assertEqual("a='b', c='d'", cmd_output['properties']) raw_output = self.openstack('volume type unset --property a ' + self.NAME) self.assertEqual("", raw_output) - raw_output = self.openstack('volume type show ' + self.NAME + opts) - self.assertEqual("c='d'\n", raw_output) + cmd_output = json.loads(self.openstack( + 'volume type show -f json ' + self.NAME)) + self.assertEqual("c='d'", cmd_output['properties']) def test_volume_type_set_unset_multiple_properties(self): raw_output = self.openstack( 'volume type set --property a=b --property c=d ' + self.NAME) self.assertEqual("", raw_output) - opts = self.get_opts(["properties"]) - raw_output = self.openstack('volume type show ' + self.NAME + opts) - self.assertEqual("a='b', c='d'\n", raw_output) + cmd_output = json.loads(self.openstack( + 'volume type show -f json ' + self.NAME)) + self.assertEqual("a='b', c='d'", cmd_output['properties']) raw_output = self.openstack( 'volume type unset --property a --property c ' + self.NAME) self.assertEqual("", raw_output) - raw_output = self.openstack('volume type show ' + self.NAME + opts) - self.assertEqual("\n", raw_output) + cmd_output = json.loads(self.openstack( + 'volume type show -f json ' + self.NAME)) + self.assertEqual("", cmd_output['properties']) def test_multi_delete(self): vol_type1 = uuid.uuid4().hex @@ -84,7 +83,6 @@ def test_multi_delete(self): self.openstack('volume type create ' + vol_type2) time.sleep(5) cmd = 'volume type delete %s %s' % (vol_type1, vol_type2) - time.sleep(5) raw_output = self.openstack(cmd) self.assertOutput('', raw_output) @@ -95,40 +93,41 @@ def test_multi_delete(self): def test_encryption_type(self): encryption_type = uuid.uuid4().hex # test create new encryption type - opts = self.get_opts(['encryption']) - raw_output = self.openstack( - 'volume type create ' + cmd_output = json.loads(self.openstack( + 'volume type create -f json ' '--encryption-provider LuksEncryptor ' '--encryption-cipher aes-xts-plain64 ' '--encryption-key-size 128 ' '--encryption-control-location front-end ' + - encryption_type + opts) + encryption_type)) + # TODO(amotoki): encryption output should be machine-readable expected = ["provider='LuksEncryptor'", "cipher='aes-xts-plain64'", "key_size='128'", "control_location='front-end'"] for attr in expected: - self.assertIn(attr, raw_output) + self.assertIn(attr, cmd_output['encryption']) # test show encryption type - opts = self.get_opts(['encryption']) - raw_output = self.openstack( - 'volume type show --encryption-type ' + encryption_type + opts) + cmd_output = json.loads(self.openstack( + 'volume type show -f json --encryption-type ' + encryption_type)) expected = ["provider='LuksEncryptor'", "cipher='aes-xts-plain64'", "key_size='128'", "control_location='front-end'"] for attr in expected: - self.assertIn(attr, raw_output) + self.assertIn(attr, cmd_output['encryption']) # test list encryption type - opts = self.get_opts(['Encryption']) - raw_output = self.openstack( - 'volume type list --encryption-type ' + opts) + cmd_output = json.loads(self.openstack( + 'volume type list -f json --encryption-type')) + encryption_output = [t['Encryption'] for t in cmd_output + if t['Name'] == encryption_type][0] + # TODO(amotoki): encryption output should be machine-readable expected = ["provider='LuksEncryptor'", "cipher='aes-xts-plain64'", "key_size='128'", "control_location='front-end'"] for attr in expected: - self.assertIn(attr, raw_output) + self.assertIn(attr, encryption_output) # test set new encryption type raw_output = self.openstack( 'volume type set ' @@ -138,23 +137,21 @@ def test_encryption_type(self): '--encryption-control-location front-end ' + self.NAME) self.assertEqual('', raw_output) - opts = self.get_opts(['encryption']) - raw_output = self.openstack( - 'volume type show --encryption-type ' + self.NAME + opts) + cmd_output = json.loads(self.openstack( + 'volume type show -f json --encryption-type ' + self.NAME)) expected = ["provider='LuksEncryptor'", "cipher='aes-xts-plain64'", "key_size='128'", "control_location='front-end'"] for attr in expected: - self.assertIn(attr, raw_output) + self.assertIn(attr, cmd_output['encryption']) # test unset encryption type raw_output = self.openstack( 'volume type unset --encryption-type ' + self.NAME) self.assertEqual('', raw_output) - opts = self.get_opts(['encryption']) - raw_output = self.openstack( - 'volume type show --encryption-type ' + self.NAME + opts) - self.assertEqual('\n', raw_output) + cmd_output = json.loads(self.openstack( + 'volume type show -f json --encryption-type ' + self.NAME)) + self.assertEqual('', cmd_output['encryption']) # test delete encryption type raw_output = self.openstack('volume type delete ' + encryption_type) self.assertEqual('', raw_output) diff --git a/openstackclient/tests/functional/volume/v2/test_transfer_request.py b/openstackclient/tests/functional/volume/v2/test_transfer_request.py index 99d91ac0e2..e9c2236b7d 100644 --- a/openstackclient/tests/functional/volume/v2/test_transfer_request.py +++ b/openstackclient/tests/functional/volume/v2/test_transfer_request.py @@ -21,23 +21,20 @@ class TransferRequestTests(common.BaseVolumeTests): NAME = uuid.uuid4().hex VOLUME_NAME = uuid.uuid4().hex - HEADERS = ['Name'] - FIELDS = ['name'] @classmethod def setUpClass(cls): super(TransferRequestTests, cls).setUpClass() - opts = cls.get_opts(cls.FIELDS) - raw_output = cls.openstack( - 'volume create --size 1 ' + cls.VOLUME_NAME + opts) - cls.assertOutput(cls.VOLUME_NAME + '\n', raw_output) + cmd_output = json.loads(cls.openstack( + 'volume create -f json --size 1 ' + cls.VOLUME_NAME)) + cls.assertOutput(cls.VOLUME_NAME, cmd_output['name']) - raw_output = cls.openstack( - 'volume transfer request create ' + + cmd_output = json.loads(cls.openstack( + 'volume transfer request create -f json ' + cls.VOLUME_NAME + - ' --name ' + cls.NAME + opts) - cls.assertOutput(cls.NAME + '\n', raw_output) + ' --name ' + cls.NAME)) + cls.assertOutput(cls.NAME, cmd_output['name']) @classmethod def tearDownClass(cls): @@ -53,27 +50,26 @@ def test_volume_transfer_request_accept(self): name = uuid.uuid4().hex # create a volume - opts = self.get_opts(self.FIELDS) - raw_output = self.openstack( - 'volume create --size 1 ' + volume_name + opts) - self.assertEqual(volume_name + '\n', raw_output) + cmd_output = json.loads(self.openstack( + 'volume create -f json --size 1 ' + volume_name)) + self.assertEqual(volume_name, cmd_output['name']) # create volume transfer request for the volume # and get the auth_key of the new transfer request - opts = self.get_opts(['auth_key']) - auth_key = self.openstack( - 'volume transfer request create ' + + cmd_output = json.loads(self.openstack( + 'volume transfer request create -f json ' + volume_name + - ' --name ' + name + opts) - self.assertNotEqual('', auth_key) + ' --name ' + name)) + auth_key = cmd_output['auth_key'] + self.assertTrue(auth_key) # accept the volume transfer request - json_output = json.loads(self.openstack( + cmd_output = json.loads(self.openstack( 'volume transfer request accept -f json ' + name + ' ' + '--auth-key ' + auth_key )) - self.assertEqual(name, json_output.get('name')) + self.assertEqual(name, cmd_output['name']) # the volume transfer will be removed by default after accepted # so just need to delete the volume here @@ -82,12 +78,11 @@ def test_volume_transfer_request_accept(self): self.assertEqual('', raw_output) def test_volume_transfer_request_list(self): - opts = self.get_opts(self.HEADERS) - raw_output = self.openstack('volume transfer request list' + opts) - self.assertIn(self.NAME, raw_output) + cmd_output = json.loads(self.openstack( + 'volume transfer request list -f json')) + self.assertIn(self.NAME, [req['Name'] for req in cmd_output]) def test_volume_transfer_request_show(self): - opts = self.get_opts(self.FIELDS) - raw_output = self.openstack( - 'volume transfer request show ' + self.NAME + opts) - self.assertEqual(self.NAME + '\n', raw_output) + cmd_output = json.loads(self.openstack( + 'volume transfer request show -f json ' + self.NAME)) + self.assertEqual(self.NAME, cmd_output['name']) diff --git a/openstackclient/tests/functional/volume/v2/test_volume_type.py b/openstackclient/tests/functional/volume/v2/test_volume_type.py index a5d0a767c9..11acf2f8a7 100644 --- a/openstackclient/tests/functional/volume/v2/test_volume_type.py +++ b/openstackclient/tests/functional/volume/v2/test_volume_type.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import json import time import uuid @@ -20,17 +21,13 @@ class VolumeTypeTests(common.BaseVolumeTests): """Functional tests for volume type. """ NAME = uuid.uuid4().hex - HEADERS = ['"Name"'] - FIELDS = ['name'] @classmethod def setUpClass(cls): super(VolumeTypeTests, cls).setUpClass() - opts = cls.get_opts(cls.FIELDS) - raw_output = cls.openstack( - 'volume type create --private ' + cls.NAME + opts) - expected = cls.NAME + '\n' - cls.assertOutput(expected, raw_output) + cmd_output = json.loads(cls.openstack( + 'volume type create -f json --private ' + cls.NAME)) + cls.assertOutput(cls.NAME, cmd_output['name']) @classmethod def tearDownClass(cls): @@ -38,49 +35,50 @@ def tearDownClass(cls): cls.assertOutput('', raw_output) def test_volume_type_list(self): - opts = self.get_opts(self.HEADERS) - raw_output = self.openstack('volume type list' + opts) - self.assertIn(self.NAME, raw_output) + cmd_output = json.loads(self.openstack('volume type list -f json')) + self.assertIn(self.NAME, [t['Name'] for t in cmd_output]) def test_volume_type_list_default(self): - opts = self.get_opts(self.HEADERS) - raw_output = self.openstack('volume type list --default' + opts) - self.assertEqual("lvmdriver-1\n", raw_output) + cmd_output = json.loads(self.openstack( + 'volume type list -f json --default')) + self.assertEqual(1, len(cmd_output)) + self.assertEqual('lvmdriver-1', cmd_output[0]['Name']) def test_volume_type_show(self): - opts = self.get_opts(self.FIELDS) - raw_output = self.openstack('volume type show ' + self.NAME + opts) - self.assertEqual(self.NAME + "\n", raw_output) + cmd_output = json.loads(self.openstack( + 'volume type show -f json ' + self.NAME)) + self.assertEqual(self.NAME, cmd_output['name']) def test_volume_type_set_unset_properties(self): raw_output = self.openstack( 'volume type set --property a=b --property c=d ' + self.NAME) self.assertEqual("", raw_output) - - opts = self.get_opts(["properties"]) - raw_output = self.openstack('volume type show ' + self.NAME + opts) - self.assertEqual("a='b', c='d'\n", raw_output) + cmd_output = json.loads(self.openstack( + 'volume type show -f json ' + self.NAME)) + # TODO(amotoki): properties output should be machine-readable + self.assertEqual("a='b', c='d'", cmd_output['properties']) raw_output = self.openstack('volume type unset --property a ' + self.NAME) self.assertEqual("", raw_output) - raw_output = self.openstack('volume type show ' + self.NAME + opts) - self.assertEqual("c='d'\n", raw_output) + cmd_output = json.loads(self.openstack( + 'volume type show -f json ' + self.NAME)) + self.assertEqual("c='d'", cmd_output['properties']) def test_volume_type_set_unset_multiple_properties(self): raw_output = self.openstack( 'volume type set --property a=b --property c=d ' + self.NAME) self.assertEqual("", raw_output) - - opts = self.get_opts(["properties"]) - raw_output = self.openstack('volume type show ' + self.NAME + opts) - self.assertEqual("a='b', c='d'\n", raw_output) + cmd_output = json.loads(self.openstack( + 'volume type show -f json ' + self.NAME)) + self.assertEqual("a='b', c='d'", cmd_output['properties']) raw_output = self.openstack( 'volume type unset --property a --property c ' + self.NAME) self.assertEqual("", raw_output) - raw_output = self.openstack('volume type show ' + self.NAME + opts) - self.assertEqual("\n", raw_output) + cmd_output = json.loads(self.openstack( + 'volume type show -f json ' + self.NAME)) + self.assertEqual("", cmd_output['properties']) def test_volume_type_set_unset_project(self): raw_output = self.openstack( @@ -99,7 +97,6 @@ def test_multi_delete(self): self.openstack('volume type create ' + vol_type2) time.sleep(5) cmd = 'volume type delete %s %s' % (vol_type1, vol_type2) - time.sleep(5) raw_output = self.openstack(cmd) self.assertOutput('', raw_output) @@ -110,40 +107,42 @@ def test_multi_delete(self): def test_encryption_type(self): encryption_type = uuid.uuid4().hex # test create new encryption type - opts = self.get_opts(['encryption']) - raw_output = self.openstack( - 'volume type create ' + cmd_output = json.loads(self.openstack( + 'volume type create -f json ' '--encryption-provider LuksEncryptor ' '--encryption-cipher aes-xts-plain64 ' '--encryption-key-size 128 ' '--encryption-control-location front-end ' + - encryption_type + opts) + encryption_type)) + # TODO(amotoki): encryption output should be machine-readable expected = ["provider='LuksEncryptor'", "cipher='aes-xts-plain64'", "key_size='128'", "control_location='front-end'"] for attr in expected: - self.assertIn(attr, raw_output) + self.assertIn(attr, cmd_output['encryption']) # test show encryption type - opts = self.get_opts(['encryption']) - raw_output = self.openstack( - 'volume type show --encryption-type ' + encryption_type + opts) + cmd_output = json.loads(self.openstack( + 'volume type show -f json --encryption-type ' + encryption_type)) + # TODO(amotoki): encryption output should be machine-readable expected = ["provider='LuksEncryptor'", "cipher='aes-xts-plain64'", "key_size='128'", "control_location='front-end'"] for attr in expected: - self.assertIn(attr, raw_output) + self.assertIn(attr, cmd_output['encryption']) # test list encryption type - opts = self.get_opts(['Encryption']) - raw_output = self.openstack( - 'volume type list --encryption-type ' + opts) + cmd_output = json.loads(self.openstack( + 'volume type list -f json --encryption-type')) + encryption_output = [t['Encryption'] for t in cmd_output + if t['Name'] == encryption_type][0] + # TODO(amotoki): encryption output should be machine-readable expected = ["provider='LuksEncryptor'", "cipher='aes-xts-plain64'", "key_size='128'", "control_location='front-end'"] for attr in expected: - self.assertIn(attr, raw_output) + self.assertIn(attr, encryption_output) # test set existing encryption type raw_output = self.openstack( 'volume type set ' @@ -151,15 +150,14 @@ def test_encryption_type(self): '--encryption-control-location back-end ' + encryption_type) self.assertEqual('', raw_output) - opts = self.get_opts(['encryption']) - raw_output = self.openstack( - 'volume type show --encryption-type ' + encryption_type + opts) + cmd_output = json.loads(self.openstack( + 'volume type show -f json --encryption-type ' + encryption_type)) expected = ["provider='LuksEncryptor'", "cipher='aes-xts-plain64'", "key_size='256'", "control_location='back-end'"] for attr in expected: - self.assertIn(attr, raw_output) + self.assertIn(attr, cmd_output['encryption']) # test set new encryption type raw_output = self.openstack( 'volume type set ' @@ -169,23 +167,21 @@ def test_encryption_type(self): '--encryption-control-location front-end ' + self.NAME) self.assertEqual('', raw_output) - opts = self.get_opts(['encryption']) - raw_output = self.openstack( - 'volume type show --encryption-type ' + self.NAME + opts) + cmd_output = json.loads(self.openstack( + 'volume type show -f json --encryption-type ' + self.NAME)) expected = ["provider='LuksEncryptor'", "cipher='aes-xts-plain64'", "key_size='128'", "control_location='front-end'"] for attr in expected: - self.assertIn(attr, raw_output) + self.assertIn(attr, cmd_output['encryption']) # test unset encryption type raw_output = self.openstack( 'volume type unset --encryption-type ' + self.NAME) self.assertEqual('', raw_output) - opts = self.get_opts(['encryption']) - raw_output = self.openstack( - 'volume type show --encryption-type ' + self.NAME + opts) - self.assertEqual('\n', raw_output) + cmd_output = json.loads(self.openstack( + 'volume type show -f json --encryption-type ' + self.NAME)) + self.assertEqual('', cmd_output['encryption']) # test delete encryption type raw_output = self.openstack('volume type delete ' + encryption_type) self.assertEqual('', raw_output) From 332671f92a3c33ba6abaacd772f0a541e70aab11 Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Wed, 17 May 2017 00:25:57 +0000 Subject: [PATCH 1683/3095] Convert image functional tests into JSON format Change-Id: Ic8eb72e8f89e5e40cf2b7594a196bb31d38e6b04 --- .../tests/functional/image/v1/test_image.py | 42 +++++------ .../tests/functional/image/v2/test_image.py | 69 ++++++++++--------- 2 files changed, 59 insertions(+), 52 deletions(-) diff --git a/openstackclient/tests/functional/image/v1/test_image.py b/openstackclient/tests/functional/image/v1/test_image.py index 2a2b5734bc..7e12f2c7e6 100644 --- a/openstackclient/tests/functional/image/v1/test_image.py +++ b/openstackclient/tests/functional/image/v1/test_image.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import json import os import uuid @@ -21,16 +22,13 @@ class ImageTests(base.TestCase): NAME = uuid.uuid4().hex OTHER_NAME = uuid.uuid4().hex - HEADERS = ['Name'] - FIELDS = ['name'] @classmethod def setUpClass(cls): os.environ['OS_IMAGE_API_VERSION'] = '1' - opts = cls.get_opts(cls.FIELDS) - raw_output = cls.openstack('image create ' + cls.NAME + opts) - expected = cls.NAME + '\n' - cls.assertOutput(expected, raw_output) + cmd_output = json.loads(cls.openstack( + 'image create -f json ' + cls.NAME)) + cls.assertOutput(cls.NAME, cmd_output['name']) @classmethod def tearDownClass(cls): @@ -43,25 +41,29 @@ def tearDownClass(cls): cls.assertOutput('', raw_output) def test_image_list(self): - opts = self.get_opts(self.HEADERS) - raw_output = self.openstack('image list' + opts) - self.assertIn(self.NAME, raw_output) + cmd_output = json.loads(self.openstack('image list -f json')) + col_names = [img['Name'] for img in cmd_output] + self.assertIn(self.NAME, col_names) def test_image_show(self): - opts = self.get_opts(self.FIELDS) - raw_output = self.openstack('image show ' + self.NAME + opts) - self.assertEqual(self.NAME + "\n", raw_output) + cmd_output = json.loads(self.openstack( + 'image show -f json ' + self.NAME)) + self.assertEqual(self.NAME, cmd_output['name']) def test_image_set(self): - opts = self.get_opts([ - "disk_format", "is_public", "min_disk", "min_ram", "name"]) self.openstack('image set --min-disk 4 --min-ram 5 ' + - '--disk-format qcow2 --public ' + self.NAME) - raw_output = self.openstack('image show ' + self.NAME + opts) - self.assertEqual("qcow2\nTrue\n4\n5\n" + self.NAME + '\n', raw_output) + '--disk-format qcow2 --public ' + self.NAME) + cmd_output = json.loads(self.openstack( + 'image show -f json ' + self.NAME)) + self.assertEqual(self.NAME, cmd_output['name']) + self.assertEqual(4, cmd_output['min_disk']) + self.assertEqual(5, cmd_output['min_ram']) + self.assertEqual('qcow2', cmd_output['disk_format']) + self.assertEqual(True, cmd_output['is_public']) def test_image_metadata(self): - opts = self.get_opts(["name", "properties"]) self.openstack('image set --property a=b --property c=d ' + self.NAME) - raw_output = self.openstack('image show ' + self.NAME + opts) - self.assertEqual(self.NAME + "\na='b', c='d'\n", raw_output) + cmd_output = json.loads(self.openstack( + 'image show -f json ' + self.NAME)) + self.assertEqual(self.NAME, cmd_output['name']) + self.assertEqual("a='b', c='d'", cmd_output['properties']) diff --git a/openstackclient/tests/functional/image/v2/test_image.py b/openstackclient/tests/functional/image/v2/test_image.py index b6baf570f8..2a132ab730 100644 --- a/openstackclient/tests/functional/image/v2/test_image.py +++ b/openstackclient/tests/functional/image/v2/test_image.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import json import os import uuid @@ -21,16 +22,13 @@ class ImageTests(base.TestCase): NAME = uuid.uuid4().hex OTHER_NAME = uuid.uuid4().hex - HEADERS = ['Name'] - FIELDS = ['name'] @classmethod def setUpClass(cls): os.environ['OS_IMAGE_API_VERSION'] = '2' - opts = cls.get_opts(cls.FIELDS) - raw_output = cls.openstack('image create ' + cls.NAME + opts) - expected = cls.NAME + '\n' - cls.assertOutput(expected, raw_output) + cmd_output = json.loads(cls.openstack( + 'image create -f json ' + cls.NAME)) + cls.assertOutput(cls.NAME, cmd_output['name']) @classmethod def tearDownClass(cls): @@ -43,56 +41,63 @@ def tearDownClass(cls): cls.assertOutput('', raw_output) def test_image_list(self): - opts = self.get_opts(self.HEADERS) - raw_output = self.openstack('image list' + opts) - self.assertIn(self.NAME, raw_output) + cmd_output = json.loads(self.openstack('image list -f json')) + col_names = [x['Name'] for x in cmd_output] + self.assertIn(self.NAME, col_names) def test_image_show(self): - opts = self.get_opts(self.FIELDS) - raw_output = self.openstack('image show ' + self.NAME + opts) - self.assertEqual(self.NAME + "\n", raw_output) + cmd_output = json.loads(self.openstack( + 'image show -f json ' + self.NAME)) + self.assertEqual(self.NAME, cmd_output['name']) def test_image_set(self): - opts = self.get_opts([ - "disk_format", "visibility", "min_disk", "min_ram", "name"]) - self.openstack('image set --min-disk 4 --min-ram 5 ' + - '--public ' + self.NAME) - raw_output = self.openstack('image show ' + self.NAME + opts) - self.assertEqual("raw\n4\n5\n" + self.NAME + '\npublic\n', raw_output) + self.openstack('image set --min-disk 4 --min-ram 5 --public ' + + self.NAME) + cmd_output = json.loads(self.openstack( + 'image show -f json ' + self.NAME)) + self.assertEqual(self.NAME, cmd_output['name']) + self.assertEqual(4, cmd_output['min_disk']) + self.assertEqual(5, cmd_output['min_ram']) + self.assertEqual('raw', cmd_output['disk_format']) + self.assertEqual('public', cmd_output['visibility']) def test_image_metadata(self): - opts = self.get_opts(["name", "properties"]) self.openstack('image set --property a=b --property c=d ' + self.NAME) - raw_output = self.openstack('image show ' + self.NAME + opts) - self.assertEqual(self.NAME + "\na='b', c='d'\n", raw_output) + cmd_output = json.loads(self.openstack( + 'image show -f json ' + self.NAME)) + self.assertEqual(self.NAME, cmd_output['name']) + self.assertEqual("a='b', c='d'", cmd_output['properties']) def test_image_unset(self): - opts = self.get_opts(["name", "tags", "properties"]) self.openstack('image set --tag 01 ' + self.NAME) + cmd_output = json.loads(self.openstack( + 'image show -f json ' + self.NAME)) + self.assertEqual('01', cmd_output['tags']) self.openstack('image unset --tag 01 ' + self.NAME) # test_image_metadata has set image properties "a" and "c" self.openstack('image unset --property a --property c ' + self.NAME) - raw_output = self.openstack('image show ' + self.NAME + opts) - self.assertEqual(self.NAME + "\n\n", raw_output) + cmd_output = json.loads(self.openstack( + 'image show -f json ' + self.NAME)) + self.assertEqual(self.NAME, cmd_output['name']) + self.assertEqual('', cmd_output['tags']) + self.assertNotIn('properties', cmd_output) def test_image_members(self): - opts = self.get_opts(['project_id']) - my_project_id = self.openstack('token issue' + opts).strip() + cmd_output = json.loads(self.openstack('token issue -f json')) + my_project_id = cmd_output['project_id'] self.openstack( 'image add project {} {}'.format(self.NAME, my_project_id)) self.openstack( 'image set --accept ' + self.NAME) - shared_img_list = self.parse_listing( - self.openstack('image list --shared') - ) + shared_img_list = json.loads(self.openstack( + 'image list --shared -f json')) self.assertIn(self.NAME, [img['Name'] for img in shared_img_list]) self.openstack( 'image set --reject ' + self.NAME) - shared_img_list = self.parse_listing( - self.openstack('image list --shared', self.get_opts(['name'])) - ) + shared_img_list = json.loads(self.openstack( + 'image list --shared -f json')) self.openstack( 'image remove project {} {}'.format(self.NAME, my_project_id)) From 411cda722b082bc9274dd23f7e6b0c47e2cc9765 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 17 May 2017 03:58:25 +0000 Subject: [PATCH 1684/3095] Updated from global requirements Change-Id: I32427e2a3b8012ed516ecaf23b6881eddc28cecb --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 136ccf8af1..c1fe0f662b 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -11,7 +11,7 @@ oslotest>=1.10.0 # Apache-2.0 reno>=1.8.0 # Apache-2.0 requests!=2.12.2,!=2.13.0,>=2.10.0 # Apache-2.0 requests-mock>=1.1 # Apache-2.0 -sphinx>=1.5.1 # BSD +sphinx!=1.6.1,>=1.5.1 # BSD stevedore>=1.20.0 # Apache-2.0 os-client-config>=1.27.0 # Apache-2.0 os-testr>=0.8.0 # Apache-2.0 From acc2d106abfb4fed0ff5d0d0c246d69f9ea1758b Mon Sep 17 00:00:00 2001 From: Ankur Gupta Date: Fri, 24 Mar 2017 12:39:22 -0500 Subject: [PATCH 1685/3095] Refactor Extension show and list command 1.keep the column display order consist in extension list with and without "--long" option. 2.rework for network extentsion list, openstacksdk return object, so the logic should be same with other service. 3.add some unit test cases, like: extension list --network --long, extension list --network --compute, to cover regular use cases. 4.raise exact exception when network extension don't exist, avoid internal TypeError in "extension show" commands. Change-Id: I2e23ced80d8da8aa1106b22472db850367b351ce Closes-Bug: #1689233 --- openstackclient/common/extension.py | 49 ++++-------- .../tests/functional/common/test_extension.py | 31 +++++++- .../tests/unit/common/test_extension.py | 78 +++++++++++++++---- .../notes/bug-1689233-c3f98e159c75374e.yaml | 7 ++ 4 files changed, 112 insertions(+), 53 deletions(-) create mode 100644 releasenotes/notes/bug-1689233-c3f98e159c75374e.yaml diff --git a/openstackclient/common/extension.py b/openstackclient/common/extension.py index d5b7223894..139f43abf2 100644 --- a/openstackclient/common/extension.py +++ b/openstackclient/common/extension.py @@ -15,7 +15,6 @@ """Extension action implementations""" -import itertools import logging from osc_lib.command import command @@ -66,8 +65,8 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): if parsed_args.long: - columns = ('Name', 'Namespace', 'Description', - 'Alias', 'Updated', 'Links') + columns = ('Name', 'Alias', 'Description', + 'Namespace', 'Updated', 'Links') else: columns = ('Name', 'Alias', 'Description') @@ -104,35 +103,22 @@ def take_action(self, parsed_args): "Block Storage API") LOG.warning(message) - # Resource classes for the above + if parsed_args.network or show_all: + network_client = self.app.client_manager.network + try: + data += network_client.extensions() + except Exception: + message = _("Failed to retrieve extensions list " + "from Network API") + LOG.warning(message) + extension_tuples = ( utils.get_item_properties( s, columns, - formatters={}, ) for s in data ) - # Dictionaries for the below - if parsed_args.network or show_all: - network_client = self.app.client_manager.network - try: - data = network_client.extensions() - dict_tuples = ( - utils.get_item_properties( - s, - columns, - formatters={}, - ) for s in data - ) - extension_tuples = itertools.chain( - extension_tuples, - dict_tuples - ) - except Exception: - message = _("Extensions list not supported by Network API") - LOG.warning(message) - return (columns, extension_tuples) @@ -152,14 +138,7 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): client = self.app.client_manager.network - columns = ('Alias', 'Description', 'Links', 'Name', - 'Namespace', 'Updated') ext = str(parsed_args.extension) - obj = client.find_extension(ext) - dict_tuples = (utils.get_item_properties( - obj, - columns, - formatters={},) - ) - - return columns, dict_tuples + obj = client.find_extension(ext, ignore_missing=False).to_dict() + + return zip(*sorted(obj.items())) diff --git a/openstackclient/tests/functional/common/test_extension.py b/openstackclient/tests/functional/common/test_extension.py index cc4cb7e160..d7dc398b5e 100644 --- a/openstackclient/tests/functional/common/test_extension.py +++ b/openstackclient/tests/functional/common/test_extension.py @@ -15,6 +15,8 @@ import json +from tempest.lib import exceptions as tempest_exc + from openstackclient.tests.functional import base @@ -23,7 +25,6 @@ class ExtensionTests(base.TestCase): @classmethod def setUpClass(cls): - # super(NetworkTests, cls).setUp() cls.haz_network = base.is_service_enabled('network') def test_extension_list_compute(self): @@ -38,6 +39,18 @@ def test_extension_list_compute(self): name_list, ) + def test_extension_list_volume(self): + """Test volume extension list""" + json_output = json.loads(self.openstack( + 'extension list -f json ' + + '--volume' + )) + name_list = [item.get('Name') for item in json_output] + self.assertIn( + 'TypesManage', + name_list, + ) + def test_extension_list_network(self): """Test network extension list""" if not self.haz_network: @@ -79,5 +92,19 @@ def test_extension_show_network(self): )) self.assertEqual( name, - json_output.get('Alias'), + json_output.get('alias'), ) + + def test_extension_show_not_exist(self): + """Test extension show with not existed name""" + if not self.haz_network: + self.skipTest("No Network service present") + + name = 'not_existed_ext' + try: + self.openstack('extension show ' + name) + except tempest_exc.CommandFailed as e: + self.assertIn('ResourceNotFound', str(e)) + self.assertIn(name, str(e)) + else: + self.fail('CommandFailed should be raised') diff --git a/openstackclient/tests/unit/common/test_extension.py b/openstackclient/tests/unit/common/test_extension.py index 68fdf17d4f..eefa814c49 100644 --- a/openstackclient/tests/unit/common/test_extension.py +++ b/openstackclient/tests/unit/common/test_extension.py @@ -67,7 +67,7 @@ def setUp(self): class TestExtensionList(TestExtension): columns = ('Name', 'Alias', 'Description') - long_columns = ('Name', 'Namespace', 'Description', 'Alias', 'Updated', + long_columns = ('Name', 'Alias', 'Description', 'Namespace', 'Updated', 'Links') volume_extension = volume_fakes.FakeExtension.create_one_extension() @@ -145,33 +145,33 @@ def test_extension_list_long(self): datalist = ( ( self.identity_extension.name, - self.identity_extension.namespace, - self.identity_extension.description, self.identity_extension.alias, + self.identity_extension.description, + self.identity_extension.namespace, self.identity_extension.updated, self.identity_extension.links, ), ( self.compute_extension.name, - self.compute_extension.namespace, - self.compute_extension.description, self.compute_extension.alias, + self.compute_extension.description, + self.compute_extension.namespace, self.compute_extension.updated, self.compute_extension.links, ), ( self.volume_extension.name, - self.volume_extension.namespace, - self.volume_extension.description, self.volume_extension.alias, + self.volume_extension.description, + self.volume_extension.namespace, self.volume_extension.updated, self.volume_extension.links, ), ( self.network_extension.name, - self.network_extension.namespace, - self.network_extension.description, self.network_extension.alias, + self.network_extension.description, + self.network_extension.namespace, self.network_extension.updated, self.network_extension.links, ), @@ -214,6 +214,27 @@ def test_extension_list_network(self): self._test_extension_list_helper(arglist, verifylist, datalist) self.network_extensions_mock.assert_called_with() + def test_extension_list_network_with_long(self): + arglist = [ + '--network', + '--long', + ] + verifylist = [ + ('network', True), + ('long', True), + ] + datalist = (( + self.network_extension.name, + self.network_extension.alias, + self.network_extension.description, + self.network_extension.namespace, + self.network_extension.updated, + self.network_extension.links, + ), ) + self._test_extension_list_helper(arglist, verifylist, datalist, + long=True) + self.network_extensions_mock.assert_called_with() + def test_extension_list_compute(self): arglist = [ '--compute', @@ -229,6 +250,31 @@ def test_extension_list_compute(self): self._test_extension_list_helper(arglist, verifylist, datalist) self.compute_extensions_mock.show_all.assert_called_with() + def test_extension_list_compute_and_network(self): + arglist = [ + '--compute', + '--network', + ] + verifylist = [ + ('compute', True), + ('network', True), + ] + datalist = ( + ( + self.compute_extension.name, + self.compute_extension.alias, + self.compute_extension.description, + ), + ( + self.network_extension.name, + self.network_extension.alias, + self.network_extension.description, + ), + ) + self._test_extension_list_helper(arglist, verifylist, datalist) + self.compute_extensions_mock.show_all.assert_called_with() + self.network_extensions_mock.assert_called_with() + def test_extension_list_volume(self): arglist = [ '--volume', @@ -251,12 +297,12 @@ class TestExtensionShow(TestExtension): ) columns = ( - 'Alias', - 'Description', - 'Links', - 'Name', - 'Namespace', - 'Updated' + 'alias', + 'description', + 'links', + 'name', + 'namespace', + 'updated', ) data = ( @@ -296,7 +342,7 @@ def test_show_all_options(self): columns, data = self.cmd.take_action(parsed_args) self.app.client_manager.network.find_extension.assert_called_with( - self.extension_details.alias) + self.extension_details.alias, ignore_missing=False) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) diff --git a/releasenotes/notes/bug-1689233-c3f98e159c75374e.yaml b/releasenotes/notes/bug-1689233-c3f98e159c75374e.yaml new file mode 100644 index 0000000000..3cecc46e36 --- /dev/null +++ b/releasenotes/notes/bug-1689233-c3f98e159c75374e.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + Raise exact exception when extension don't exist in ``extension show`` + command, and keep the column display order consist in ``extension list`` + with and without ``--long`` option. + [Bug `1689233 `_] From 45496feee6ae781dc0c1df4a0e5bd71b05653748 Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Wed, 3 May 2017 15:17:10 +0800 Subject: [PATCH 1686/3095] Create server with security group ID and name Both resource ID and name are supported to identify an object in openstackclient to make user easy to input, for security group, nova only support security group name in API when launch a new server, this patch convert ID to name, then pass name to nova API, and check the security group exist before creating server. Change-Id: I1ed4a967fb9de3f91c8945a1ef63f6c7b6b2dfb2 Closes-Bug: #1687814 --- doc/source/command-objects/server.rst | 2 +- openstackclient/compute/v2/server.py | 20 +++- .../functional/compute/v2/test_server.py | 46 +++++++++ .../tests/unit/compute/v2/test_server.py | 97 ++++++++++++++++++- .../notes/bug-1687814-743ad8418923d5e3.yaml | 7 ++ 5 files changed, 168 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/bug-1687814-743ad8418923d5e3.yaml diff --git a/doc/source/command-objects/server.rst b/doc/source/command-objects/server.rst index dc78080b6d..0287fd3104 100644 --- a/doc/source/command-objects/server.rst +++ b/doc/source/command-objects/server.rst @@ -164,7 +164,7 @@ Create a new server Create server with this flavor (name or ID) -.. option:: --security-group +.. option:: --security-group Security group to assign to this server (name or ID) (repeat option to set multiple groups) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 60dc605caf..7ed7c7f6c2 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -407,7 +407,7 @@ def get_parser(self, prog_name): ) parser.add_argument( '--security-group', - metavar='', + metavar='', action='append', default=[], help=_('Security group to assign to this server (name or ID) ' @@ -677,6 +677,22 @@ def take_action(self, parsed_args): # decide the default behavior. nics = [] + # Check security group exist and convert ID to name + security_group_names = [] + if self.app.client_manager.is_network_endpoint_enabled(): + network_client = self.app.client_manager.network + for each_sg in parsed_args.security_group: + sg = network_client.find_security_group(each_sg, + ignore_missing=False) + # Use security group ID to avoid multiple security group have + # same name in neutron networking backend + security_group_names.append(sg.id) + else: + # Handle nova-network case + for each_sg in parsed_args.security_group: + sg = compute_client.api.security_group_find(each_sg) + security_group_names.append(sg['name']) + hints = {} for hint in parsed_args.hint: key, _sep, value = hint.partition('=') @@ -706,7 +722,7 @@ def take_action(self, parsed_args): reservation_id=None, min_count=parsed_args.min, max_count=parsed_args.max, - security_groups=parsed_args.security_group, + security_groups=security_group_names, userdata=userdata, key_name=parsed_args.key_name, availability_zone=parsed_args.availability_zone, diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py index 7d54b6a6a5..f4ffe9612e 100644 --- a/openstackclient/tests/functional/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -410,6 +410,52 @@ def test_server_create_with_none_network(self): self.assertIsNotNone(server['addresses']) self.assertEqual('', server['addresses']) + def test_server_create_with_security_group(self): + """Test server create with security group ID and name""" + if not self.haz_network: + # NOTE(dtroyer): As of Ocata release Nova forces nova-network to + # run in a cells v1 configuration. Security group + # and network functions currently do not work in + # the gate jobs so we have to skip this. It is + # known to work tested against a Mitaka nova-net + # DevStack without cells. + self.skipTest("No Network service present") + # Create two security group, use name and ID to create server + sg_name1 = uuid.uuid4().hex + security_group1 = json.loads(self.openstack( + 'security group create -f json ' + sg_name1 + )) + self.addCleanup(self.openstack, 'security group delete ' + sg_name1) + sg_name2 = uuid.uuid4().hex + security_group2 = json.loads(self.openstack( + 'security group create -f json ' + sg_name2 + )) + self.addCleanup(self.openstack, 'security group delete ' + sg_name2) + + server_name = uuid.uuid4().hex + server = json.loads(self.openstack( + 'server create -f json ' + + '--flavor ' + self.flavor_name + ' ' + + '--image ' + self.image_name + ' ' + + # Security group id is integer in nova-network, convert to string + '--security-group ' + str(security_group1['id']) + ' ' + + '--security-group ' + security_group2['name'] + ' ' + + self.network_arg + ' ' + + server_name + )) + self.addCleanup(self.openstack, 'server delete --wait ' + server_name) + + self.assertIsNotNone(server['id']) + self.assertEqual(server_name, server['name']) + self.assertIn(str(security_group1['id']), server['security_groups']) + self.assertIn(str(security_group2['id']), server['security_groups']) + self.wait_for_status(server_name, 'ACTIVE') + server = json.loads(self.openstack( + 'server show -f json ' + server_name + )) + self.assertIn(sg_name1, server['security_groups']) + self.assertIn(sg_name2, server['security_groups']) + def test_server_create_with_empty_network_option_latest(self): """Test server create with empty network option in nova 2.latest.""" server_name = uuid.uuid4().hex diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 9370bf6bd1..77e9c33106 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -24,6 +24,7 @@ from openstackclient.compute.v2 import server from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes from openstackclient.tests.unit.image.v2 import fakes as image_fakes +from openstackclient.tests.unit.network.v2 import fakes as network_fakes from openstackclient.tests.unit import utils from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes @@ -414,8 +415,16 @@ def test_server_create_with_options(self): # 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. + fake_sg = network_fakes.FakeSecurityGroup.create_security_groups() + mock_find_sg = ( + network_fakes.FakeSecurityGroup.get_security_groups(fake_sg) + ) + self.app.client_manager.network.find_security_group = mock_find_sg + columns, data = self.cmd.take_action(parsed_args) + mock_find_sg.assert_called_once_with('securitygroup', + ignore_missing=False) # Set expected values kwargs = dict( meta={'Beta': 'b'}, @@ -423,7 +432,7 @@ def test_server_create_with_options(self): reservation_id=None, min_count=1, max_count=1, - security_groups=['securitygroup'], + security_groups=[fake_sg[0].id], userdata=None, key_name='keyname', availability_zone=None, @@ -443,6 +452,92 @@ def test_server_create_with_options(self): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist(), data) + def test_server_create_with_not_exist_security_group(self): + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--key-name', 'keyname', + '--security-group', 'securitygroup', + '--security-group', 'not_exist_sg', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('key_name', 'keyname'), + ('security_group', ['securitygroup', 'not_exist_sg']), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + fake_sg = network_fakes.FakeSecurityGroup.create_security_groups( + count=1) + fake_sg.append(exceptions.NotFound(code=404)) + mock_find_sg = ( + network_fakes.FakeSecurityGroup.get_security_groups(fake_sg) + ) + self.app.client_manager.network.find_security_group = mock_find_sg + + self.assertRaises(exceptions.NotFound, + self.cmd.take_action, + parsed_args) + mock_find_sg.assert_called_with('not_exist_sg', + ignore_missing=False) + + def test_server_create_with_security_group_in_nova_network(self): + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--key-name', 'keyname', + '--security-group', 'securitygroup', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('key_name', 'keyname'), + ('security_group', ['securitygroup']), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + with mock.patch.object(self.app.client_manager, + 'is_network_endpoint_enabled', + return_value=False): + with mock.patch.object(self.app.client_manager.compute.api, + 'security_group_find', + return_value={'name': 'fake_sg'} + ) as mock_find: + columns, data = self.cmd.take_action(parsed_args) + mock_find.assert_called_once_with('securitygroup') + + # Set expected values + kwargs = dict( + meta=None, + files={}, + reservation_id=None, + min_count=1, + max_count=1, + security_groups=['fake_sg'], + userdata=None, + key_name='keyname', + availability_zone=None, + block_device_mapping_v2=[], + nics=[], + scheduler_hints={}, + config_drive=None, + ) + # ServerManager.create(name, image, flavor, **kwargs) + self.servers_mock.create.assert_called_with( + self.new_server.name, + self.image, + self.flavor, + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) + def test_server_create_with_network(self): arglist = [ '--image', 'image1', diff --git a/releasenotes/notes/bug-1687814-743ad8418923d5e3.yaml b/releasenotes/notes/bug-1687814-743ad8418923d5e3.yaml new file mode 100644 index 0000000000..bdd6e3f0a0 --- /dev/null +++ b/releasenotes/notes/bug-1687814-743ad8418923d5e3.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + Allow security groups in ``server create`` command to be specified by name or ID with + the ``--security-group`` option. This also checks that the security group exist before + creating the server. + [Bug `1687814 `_] From 5df961a1f7af69cd69596e9430eedd4f086204f5 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Tue, 23 May 2017 11:59:47 +0000 Subject: [PATCH 1687/3095] Updated from global requirements Change-Id: Ieb595ddda18a579990d67ca00893280347edc629 --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index faf34c0a6b..42d18f177e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,9 +7,9 @@ six>=1.9.0 # MIT Babel!=2.4.0,>=2.3.4 # BSD cliff>=2.6.0 # Apache-2.0 keystoneauth1>=2.20.0 # Apache-2.0 -openstacksdk>=0.9.14 # Apache-2.0 +openstacksdk>=0.9.16 # Apache-2.0 osc-lib>=1.5.1 # Apache-2.0 -oslo.i18n>=2.1.0 # Apache-2.0 +oslo.i18n!=3.15.2,>=2.1.0 # Apache-2.0 oslo.utils>=3.20.0 # Apache-2.0 python-glanceclient>=2.5.0 # Apache-2.0 python-keystoneclient>=3.8.0 # Apache-2.0 From 0ad6b6b2e0b26ebc93a99730bcc414bf8e091759 Mon Sep 17 00:00:00 2001 From: David Rabel Date: Mon, 15 May 2017 18:11:37 +0200 Subject: [PATCH 1688/3095] Use _get_token_resource in role assignment list If project matches the project from access token, we do not have to send an API request to /projects?name=..., because the project ID is already known. This API request may require additional permissions, so we want to avoid it, if possible. Change-Id: Ice1af8686bceea6b67229dcab7cf82eef821163e Closes-Bug: #1658189 --- openstackclient/identity/v3/role_assignment.py | 3 ++- releasenotes/notes/bug-1658189-d2b390ad74c96c79.yaml | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/bug-1658189-d2b390ad74c96c79.yaml diff --git a/openstackclient/identity/v3/role_assignment.py b/openstackclient/identity/v3/role_assignment.py index 9da050ded2..a362adb07d 100644 --- a/openstackclient/identity/v3/role_assignment.py +++ b/openstackclient/identity/v3/role_assignment.py @@ -128,7 +128,8 @@ def take_action(self, parsed_args): if parsed_args.project: project = common.find_project( identity_client, - parsed_args.project, + common._get_token_resource(identity_client, 'project', + parsed_args.project), parsed_args.project_domain, ) elif parsed_args.authproject: diff --git a/releasenotes/notes/bug-1658189-d2b390ad74c96c79.yaml b/releasenotes/notes/bug-1658189-d2b390ad74c96c79.yaml new file mode 100644 index 0000000000..211c4c31ae --- /dev/null +++ b/releasenotes/notes/bug-1658189-d2b390ad74c96c79.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Make ``role assignment list`` callable without administrator permissions + if restricted to own project with ``--project`` parameter. + [Bug `1658189 `_] From 6425fc305911c307e73cacb3066f15a186e61db9 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 11 May 2017 14:58:56 -0500 Subject: [PATCH 1689/3095] JSON-ify image functional tests Change-Id: Ica91eddfdebe68449544feb5e29113db075bf11c --- .../tests/functional/image/v1/test_image.py | 96 ++++--- .../tests/functional/image/v2/test_image.py | 270 +++++++++++++----- 2 files changed, 269 insertions(+), 97 deletions(-) diff --git a/openstackclient/tests/functional/image/v1/test_image.py b/openstackclient/tests/functional/image/v1/test_image.py index 7e12f2c7e6..901f4337a4 100644 --- a/openstackclient/tests/functional/image/v1/test_image.py +++ b/openstackclient/tests/functional/image/v1/test_image.py @@ -26,44 +26,74 @@ class ImageTests(base.TestCase): @classmethod def setUpClass(cls): os.environ['OS_IMAGE_API_VERSION'] = '1' - cmd_output = json.loads(cls.openstack( - 'image create -f json ' + cls.NAME)) - cls.assertOutput(cls.NAME, cmd_output['name']) + json_output = json.loads(cls.openstack( + 'image create -f json ' + + cls.NAME + )) + cls.image_id = json_output["id"] + cls.assertOutput(cls.NAME, json_output['name']) @classmethod def tearDownClass(cls): - # Rename test - raw_output = cls.openstack('image set --name ' + cls.OTHER_NAME + ' ' - + cls.NAME) - cls.assertOutput('', raw_output) - # Delete test - raw_output = cls.openstack('image delete ' + cls.OTHER_NAME) - cls.assertOutput('', raw_output) + cls.openstack( + 'image delete ' + + cls.image_id + ) def test_image_list(self): - cmd_output = json.loads(self.openstack('image list -f json')) - col_names = [img['Name'] for img in cmd_output] - self.assertIn(self.NAME, col_names) + json_output = json.loads(self.openstack( + 'image list -f json ' + )) + self.assertIn( + self.NAME, + [img['Name'] for img in json_output] + ) - def test_image_show(self): - cmd_output = json.loads(self.openstack( - 'image show -f json ' + self.NAME)) - self.assertEqual(self.NAME, cmd_output['name']) + def test_image_attributes(self): + """Test set, unset, show on attributes, tags and properties""" - def test_image_set(self): - self.openstack('image set --min-disk 4 --min-ram 5 ' + - '--disk-format qcow2 --public ' + self.NAME) - cmd_output = json.loads(self.openstack( - 'image show -f json ' + self.NAME)) - self.assertEqual(self.NAME, cmd_output['name']) - self.assertEqual(4, cmd_output['min_disk']) - self.assertEqual(5, cmd_output['min_ram']) - self.assertEqual('qcow2', cmd_output['disk_format']) - self.assertEqual(True, cmd_output['is_public']) + # Test explicit attributes + self.openstack( + 'image set ' + + '--min-disk 4 ' + + '--min-ram 5 ' + + '--disk-format qcow2 ' + + '--public ' + + self.NAME + ) + json_output = json.loads(self.openstack( + 'image show -f json ' + + self.NAME + )) + self.assertEqual( + 4, + json_output["min_disk"], + ) + self.assertEqual( + 5, + json_output["min_ram"], + ) + self.assertEqual( + 'qcow2', + json_output['disk_format'], + ) + self.assertTrue( + json_output["is_public"], + ) - def test_image_metadata(self): - self.openstack('image set --property a=b --property c=d ' + self.NAME) - cmd_output = json.loads(self.openstack( - 'image show -f json ' + self.NAME)) - self.assertEqual(self.NAME, cmd_output['name']) - self.assertEqual("a='b', c='d'", cmd_output['properties']) + # Test properties + self.openstack( + 'image set ' + + '--property a=b ' + + '--property c=d ' + + '--public ' + + self.NAME + ) + json_output = json.loads(self.openstack( + 'image show -f json ' + + self.NAME + )) + self.assertEqual( + "a='b', c='d'", + json_output["properties"], + ) diff --git a/openstackclient/tests/functional/image/v2/test_image.py b/openstackclient/tests/functional/image/v2/test_image.py index 2a132ab730..8fadd2000f 100644 --- a/openstackclient/tests/functional/image/v2/test_image.py +++ b/openstackclient/tests/functional/image/v2/test_image.py @@ -14,6 +14,8 @@ import os import uuid +# from glanceclient import exc as image_exceptions + from openstackclient.tests.functional import base @@ -26,78 +28,218 @@ class ImageTests(base.TestCase): @classmethod def setUpClass(cls): os.environ['OS_IMAGE_API_VERSION'] = '2' - cmd_output = json.loads(cls.openstack( - 'image create -f json ' + cls.NAME)) - cls.assertOutput(cls.NAME, cmd_output['name']) + json_output = json.loads(cls.openstack( + 'image create -f json ' + + cls.NAME + )) + cls.image_id = json_output["id"] + cls.assertOutput(cls.NAME, json_output['name']) @classmethod def tearDownClass(cls): - # Rename test - raw_output = cls.openstack('image set --name ' + cls.OTHER_NAME + ' ' - + cls.NAME) - cls.assertOutput('', raw_output) - # Delete test - raw_output = cls.openstack('image delete ' + cls.OTHER_NAME) - cls.assertOutput('', raw_output) + cls.openstack( + 'image delete ' + + cls.image_id + ) def test_image_list(self): - cmd_output = json.loads(self.openstack('image list -f json')) - col_names = [x['Name'] for x in cmd_output] - self.assertIn(self.NAME, col_names) - - def test_image_show(self): - cmd_output = json.loads(self.openstack( - 'image show -f json ' + self.NAME)) - self.assertEqual(self.NAME, cmd_output['name']) - - def test_image_set(self): - self.openstack('image set --min-disk 4 --min-ram 5 --public ' - + self.NAME) - cmd_output = json.loads(self.openstack( - 'image show -f json ' + self.NAME)) - self.assertEqual(self.NAME, cmd_output['name']) - self.assertEqual(4, cmd_output['min_disk']) - self.assertEqual(5, cmd_output['min_ram']) - self.assertEqual('raw', cmd_output['disk_format']) - self.assertEqual('public', cmd_output['visibility']) - - def test_image_metadata(self): - self.openstack('image set --property a=b --property c=d ' + self.NAME) - cmd_output = json.loads(self.openstack( - 'image show -f json ' + self.NAME)) - self.assertEqual(self.NAME, cmd_output['name']) - self.assertEqual("a='b', c='d'", cmd_output['properties']) - - def test_image_unset(self): - self.openstack('image set --tag 01 ' + self.NAME) - cmd_output = json.loads(self.openstack( - 'image show -f json ' + self.NAME)) - self.assertEqual('01', cmd_output['tags']) - self.openstack('image unset --tag 01 ' + self.NAME) - # test_image_metadata has set image properties "a" and "c" - self.openstack('image unset --property a --property c ' + self.NAME) - cmd_output = json.loads(self.openstack( - 'image show -f json ' + self.NAME)) - self.assertEqual(self.NAME, cmd_output['name']) - self.assertEqual('', cmd_output['tags']) - self.assertNotIn('properties', cmd_output) - - def test_image_members(self): - cmd_output = json.loads(self.openstack('token issue -f json')) - my_project_id = cmd_output['project_id'] + json_output = json.loads(self.openstack( + 'image list -f json ' + )) + self.assertIn( + self.NAME, + [img['Name'] for img in json_output] + ) + + def test_image_attributes(self): + """Test set, unset, show on attributes, tags and properties""" + + # Test explicit attributes self.openstack( - 'image add project {} {}'.format(self.NAME, my_project_id)) - + 'image set ' + + '--min-disk 4 ' + + '--min-ram 5 ' + + '--public ' + + self.NAME + ) + json_output = json.loads(self.openstack( + 'image show -f json ' + + self.NAME + )) + self.assertEqual( + 4, + json_output["min_disk"], + ) + self.assertEqual( + 5, + json_output["min_ram"], + ) + self.assertEqual( + 'public', + json_output["visibility"], + ) + + # Test properties self.openstack( - 'image set --accept ' + self.NAME) - shared_img_list = json.loads(self.openstack( - 'image list --shared -f json')) - self.assertIn(self.NAME, [img['Name'] for img in shared_img_list]) + 'image set ' + + '--property a=b ' + + '--property c=d ' + + '--public ' + + self.NAME + ) + json_output = json.loads(self.openstack( + 'image show -f json ' + + self.NAME + )) + self.assertEqual( + "a='b', c='d'", + json_output["properties"], + ) self.openstack( - 'image set --reject ' + self.NAME) - shared_img_list = json.loads(self.openstack( - 'image list --shared -f json')) + 'image unset ' + + '--property a ' + + '--property c ' + + self.NAME + ) + json_output = json.loads(self.openstack( + 'image show -f json ' + + self.NAME + )) + self.assertNotIn( + 'properties', + json_output, + ) + + # Test tags + self.openstack( + 'image set ' + + '--tag 01 ' + + self.NAME + ) + json_output = json.loads(self.openstack( + 'image show -f json ' + + self.NAME + )) + self.assertEqual( + '01', + json_output["tags"], + ) self.openstack( - 'image remove project {} {}'.format(self.NAME, my_project_id)) + 'image unset ' + + '--tag 01 ' + + self.NAME + ) + json_output = json.loads(self.openstack( + 'image show -f json ' + + self.NAME + )) + self.assertEqual( + '', + json_output["tags"], + ) + + def test_image_set_rename(self): + name = uuid.uuid4().hex + json_output = json.loads(self.openstack( + 'image create -f json ' + + name + )) + image_id = json_output["id"] + self.assertEqual( + name, + json_output["name"], + ) + self.openstack( + 'image set ' + + '--name ' + name + 'xx ' + + image_id + ) + json_output = json.loads(self.openstack( + 'image show -f json ' + + name + 'xx' + )) + self.assertEqual( + name + 'xx', + json_output["name"], + ) + + # TODO(dtroyer): This test is incomplete and doesn't properly test + # sharing images. Fix after the --shared option is + # properly added. + def test_image_members(self): + """Test member add, remove, accept""" + json_output = json.loads(self.openstack( + 'token issue -f json' + )) + my_project_id = json_output['project_id'] + + json_output = json.loads(self.openstack( + 'image show -f json ' + + self.NAME + )) + # NOTE(dtroyer): Until OSC supports --shared flags in create and set + # we can not properly test membership. Sometimes the + # images are shared and sometimes they are not. + if json_output["visibility"] == 'shared': + self.openstack( + 'image add project ' + + self.NAME + ' ' + + my_project_id + ) + # self.addCleanup( + # self.openstack, + # 'image remove project ' + + # self.NAME + ' ' + + # my_project_id + # ) + + self.openstack( + 'image set ' + + '--accept ' + + self.NAME + ) + json_output = json.loads(self.openstack( + 'image list -f json ' + + '--shared' + )) + self.assertIn( + self.NAME, + [img['Name'] for img in json_output] + ) + + self.openstack( + 'image set ' + + '--reject ' + + self.NAME + ) + json_output = json.loads(self.openstack( + 'image list -f json ' + + '--shared' + )) + # self.assertNotIn( + # self.NAME, + # [img['Name'] for img in json_output] + # ) + + self.openstack( + 'image remove project ' + + self.NAME + ' ' + + my_project_id + ) + + # else: + # # Test not shared + # self.assertRaises( + # image_exceptions.HTTPForbidden, + # self.openstack, + # 'image add project ' + + # self.NAME + ' ' + + # my_project_id + # ) + # self.openstack( + # 'image set ' + + # '--share ' + + # self.NAME + # ) From 6aceca218af7d1d2c708fde48f1a5f2b798bc421 Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Fri, 20 Jan 2017 14:37:14 +0800 Subject: [PATCH 1690/3095] Replace "Display Name" by "Name" in volume list Current "volume list --name" command use "display_name" as search_opts to send to cinder API, and show the result table with "Display Name" column title in osc, cinder list API support "name" as search opts too, and there is "name" attribute in volume response body, so we can replace all "Display Name" by "Name" in order to keep "volume list" command consistent with other commands, like: server list, network list and so on, only use "Name" attribute for all objects. Support a mapping for volume list -c "Display Name" (Volume v1 and v2) and volume create/show -c "display_name" (Volume v1) for minimal backward compatibility until R release. Change-Id: I120be0118e7bb30093b4237c5eeb69a9eedef077 Closes-Bug: #1657956 Depends-On: I1fb62219b092346ea380099811cbd082cae5bafe --- doc/source/backwards-incompatible.rst | 22 +++++ .../volume/v1/test_transfer_request.py | 4 +- .../tests/functional/volume/v1/test_volume.py | 51 ++++++++++- .../tests/functional/volume/v2/test_volume.py | 35 +++++++- .../tests/unit/volume/v1/test_volume.py | 84 +++++++++++++++++-- .../tests/unit/volume/v2/test_volume.py | 60 ++++++++++--- openstackclient/volume/v1/volume.py | 24 +++++- openstackclient/volume/v2/volume.py | 6 +- .../notes/bug-1657956-977a615f01775288.yaml | 13 +++ 9 files changed, 266 insertions(+), 33 deletions(-) create mode 100644 releasenotes/notes/bug-1657956-977a615f01775288.yaml diff --git a/doc/source/backwards-incompatible.rst b/doc/source/backwards-incompatible.rst index 6516e794a6..033860d34c 100644 --- a/doc/source/backwards-incompatible.rst +++ b/doc/source/backwards-incompatible.rst @@ -27,6 +27,27 @@ Backwards Incompatible Changes .. * Remove in: <5.0> .. * Commit: +Release 3.12.0 +-------------- + +1. Replace ``Display Name`` by ``Name`` in volume list. + + Change column name ``Display Name`` to ``Name`` in ``volume list`` output. + Current ``volume list --name`` command uses ``display_name`` as search_opts + to send to cinder API, and show the result table with ``Display Name`` + as column title. Replace all ``Display Name`` by ``Name`` to be consistent + with other list commands. + + Support a mapping for volume list -c ``Display Name`` (Volume v1 and v2) + and volume create/show -c ``display_name`` (Volume v1) to maintain backward + compatibility until the next major release. + + * In favor of: ``openstack volume list -c Name`` + * As of: 3.12.0 + * Removed in: n/a + * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1657956 + * Commit: https://review.openstack.org/#/c/423081/ + Release 3.10 ------------ @@ -62,6 +83,7 @@ Release 3.0 * Bug: n/a * Commit: https://review.openstack.org/332938 + Releases Before 3.0 ------------------- diff --git a/openstackclient/tests/functional/volume/v1/test_transfer_request.py b/openstackclient/tests/functional/volume/v1/test_transfer_request.py index 498c90567d..bd6128295e 100644 --- a/openstackclient/tests/functional/volume/v1/test_transfer_request.py +++ b/openstackclient/tests/functional/volume/v1/test_transfer_request.py @@ -27,7 +27,7 @@ def setUpClass(cls): super(TransferRequestTests, cls).setUpClass() cmd_output = json.loads(cls.openstack( 'volume create -f json --size 1 ' + cls.VOLUME_NAME)) - cls.assertOutput(cls.VOLUME_NAME, cmd_output['display_name']) + cls.assertOutput(cls.VOLUME_NAME, cmd_output['name']) cmd_output = json.loads(cls.openstack( 'volume transfer request create -f json ' + @@ -51,7 +51,7 @@ def test_volume_transfer_request_accept(self): # create a volume cmd_output = json.loads(self.openstack( 'volume create -f json --size 1 ' + volume_name)) - self.assertEqual(volume_name, cmd_output['display_name']) + self.assertEqual(volume_name, cmd_output['name']) # create volume transfer request for the volume # and get the auth_key of the new transfer request diff --git a/openstackclient/tests/functional/volume/v1/test_volume.py b/openstackclient/tests/functional/volume/v1/test_volume.py index 3f04e07154..6eddf21320 100644 --- a/openstackclient/tests/functional/volume/v1/test_volume.py +++ b/openstackclient/tests/functional/volume/v1/test_volume.py @@ -81,7 +81,7 @@ def test_volume_list(self): cmd_output = json.loads(self.openstack( 'volume list -f json ' )) - names = [x["Display Name"] for x in cmd_output] + names = [x["Name"] for x in cmd_output] self.assertIn(name1, names) self.assertIn(name2, names) @@ -97,7 +97,7 @@ def test_volume_list(self): 'volume list -f json ' + '--name ' + name1 )) - names = [x["Display Name"] for x in cmd_output] + names = [x["Name"] for x in cmd_output] self.assertIn(name1, names) self.assertNotIn(name2, names) @@ -113,7 +113,7 @@ def test_volume_set_and_unset(self): )) self.assertEqual( name, - cmd_output["display_name"], + cmd_output["name"], ) self.assertEqual( 1, @@ -155,7 +155,7 @@ def test_volume_set_and_unset(self): )) self.assertEqual( new_name, - cmd_output["display_name"], + cmd_output["name"], ) self.assertEqual( 2, @@ -191,6 +191,49 @@ def test_volume_set_and_unset(self): cmd_output["properties"], ) + def test_volume_create_and_list_and_show_backward_compatibility(self): + """Test backward compatibility of create, list, show""" + name1 = uuid.uuid4().hex + json_output = json.loads(self.openstack( + 'volume create -f json ' + + '-c display_name -c id ' + + '--size 1 ' + + name1 + )) + self.assertIn('display_name', json_output) + self.assertEqual(name1, json_output['display_name']) + self.assertIn('id', json_output) + volume_id = json_output['id'] + self.assertIsNotNone(volume_id) + self.assertNotIn('name', json_output) + self.addCleanup(self.openstack, 'volume delete ' + volume_id) + + self.wait_for("volume", name1, "available") + + json_output = json.loads(self.openstack( + 'volume list -f json ' + + '-c "Display Name"' + )) + for each_volume in json_output: + self.assertIn('Display Name', each_volume) + + json_output = json.loads(self.openstack( + 'volume list -f json ' + + '-c "Name"' + )) + for each_volume in json_output: + self.assertIn('Name', each_volume) + + json_output = json.loads(self.openstack( + 'volume show -f json ' + + '-c display_name -c id ' + + name1 + )) + self.assertIn('display_name', json_output) + self.assertEqual(name1, json_output['display_name']) + self.assertIn('id', json_output) + self.assertNotIn('name', json_output) + def wait_for(self, check_type, check_name, desired_status, wait=120, interval=5, failures=['ERROR']): status = "notset" diff --git a/openstackclient/tests/functional/volume/v2/test_volume.py b/openstackclient/tests/functional/volume/v2/test_volume.py index 94ac792f91..f936907ca2 100644 --- a/openstackclient/tests/functional/volume/v2/test_volume.py +++ b/openstackclient/tests/functional/volume/v2/test_volume.py @@ -90,7 +90,7 @@ def test_volume_list(self): 'volume list -f json ' + '--long' )) - names = [x["Display Name"] for x in cmd_output] + names = [x["Name"] for x in cmd_output] self.assertIn(name1, names) self.assertIn(name2, names) @@ -99,7 +99,7 @@ def test_volume_list(self): 'volume list -f json ' + '--status error' )) - names = [x["Display Name"] for x in cmd_output] + names = [x["Name"] for x in cmd_output] self.assertNotIn(name1, names) self.assertIn(name2, names) @@ -249,6 +249,37 @@ def test_volume_snapshot(self): 'volume snapshot delete ' + snapshot_name) self.assertOutput('', raw_output) + def test_volume_list_backward_compatibility(self): + """Test backward compatibility of list command""" + name1 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume create -f json ' + + '--size 1 ' + + name1 + )) + self.addCleanup(self.openstack, 'volume delete ' + name1) + self.assertEqual( + 1, + cmd_output["size"], + ) + self.wait_for("volume", name1, "available") + + # Test list -c "Display Name" + cmd_output = json.loads(self.openstack( + 'volume list -f json ' + + '-c "Display Name"' + )) + for each_volume in cmd_output: + self.assertIn('Display Name', each_volume) + + # Test list -c "Name" + cmd_output = json.loads(self.openstack( + 'volume list -f json ' + + '-c "Name"' + )) + for each_volume in cmd_output: + self.assertIn('Name', each_volume) + def wait_for(self, check_type, check_name, desired_status, wait=120, interval=5, failures=['ERROR']): status = "notset" diff --git a/openstackclient/tests/unit/volume/v1/test_volume.py b/openstackclient/tests/unit/volume/v1/test_volume.py index d46a7ba950..6b79377352 100644 --- a/openstackclient/tests/unit/volume/v1/test_volume.py +++ b/openstackclient/tests/unit/volume/v1/test_volume.py @@ -68,8 +68,8 @@ class TestVolumeCreate(TestVolume): 'bootable', 'created_at', 'display_description', - 'display_name', 'id', + 'name', 'properties', 'size', 'snapshot_id', @@ -86,8 +86,8 @@ def setUp(self): self.new_volume.bootable, self.new_volume.created_at, self.new_volume.display_description, - self.new_volume.display_name, self.new_volume.id, + self.new_volume.display_name, utils.format_dict(self.new_volume.metadata), self.new_volume.size, self.new_volume.snapshot_id, @@ -598,6 +598,38 @@ def test_volume_create_with_multi_source(self): self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) + def test_volume_create_backward_compatibility(self): + arglist = [ + '-c', 'display_name', + '--size', str(self.new_volume.size), + self.new_volume.display_name, + ] + verifylist = [ + ('columns', ['display_name']), + ('size', self.new_volume.size), + ('name', self.new_volume.display_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volumes_mock.create.assert_called_with( + self.new_volume.size, + None, + None, + self.new_volume.display_name, + None, + None, + None, + None, + None, + None, + None, + ) + self.assertIn('display_name', columns) + self.assertNotIn('name', columns) + self.assertIn(self.new_volume.display_name, data) + class TestVolumeDelete(TestVolume): @@ -695,7 +727,7 @@ class TestVolumeList(TestVolume): _volume = volume_fakes.FakeVolume.create_one_volume() columns = ( 'ID', - 'Display Name', + 'Name', 'Status', 'Size', 'Attached to', @@ -806,7 +838,7 @@ def test_volume_list_long(self): collist = ( 'ID', - 'Display Name', + 'Name', 'Status', 'Size', 'Type', @@ -863,6 +895,27 @@ def test_volume_list_negative_limit(self): self.assertRaises(argparse.ArgumentTypeError, self.check_parser, self.cmd, arglist, verifylist) + def test_volume_list_backward_compatibility(self): + arglist = [ + '-c', 'Display Name', + ] + verifylist = [ + ('columns', ['Display Name']), + ('long', False), + ('all_projects', False), + ('name', None), + ('status', None), + ('limit', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.assertIn('Display Name', columns) + self.assertNotIn('Name', columns) + for each_volume in data: + self.assertIn(self._volume.display_name, each_volume) + class TestVolumeMigrate(TestVolume): @@ -1178,8 +1231,8 @@ class TestVolumeShow(TestVolume): 'bootable', 'created_at', 'display_description', - 'display_name', 'id', + 'name', 'properties', 'size', 'snapshot_id', @@ -1196,8 +1249,8 @@ def setUp(self): self._volume.bootable, self._volume.created_at, self._volume.display_description, - self._volume.display_name, self._volume.id, + self._volume.display_name, utils.format_dict(self._volume.metadata), self._volume.size, self._volume.snapshot_id, @@ -1223,6 +1276,25 @@ def test_volume_show(self): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, data) + def test_volume_show_backward_compatibility(self): + arglist = [ + '-c', 'display_name', + self._volume.id, + ] + verifylist = [ + ('columns', ['display_name']), + ('volume', self._volume.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volumes_mock.get.assert_called_with(self._volume.id) + + self.assertIn('display_name', columns) + self.assertNotIn('name', columns) + self.assertIn(self._volume.display_name, data) + class TestVolumeUnset(TestVolume): diff --git a/openstackclient/tests/unit/volume/v2/test_volume.py b/openstackclient/tests/unit/volume/v2/test_volume.py index fbe719f3eb..71e4eceac7 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume.py +++ b/openstackclient/tests/unit/volume/v2/test_volume.py @@ -790,7 +790,7 @@ class TestVolumeList(TestVolume): columns = [ 'ID', - 'Display Name', + 'Name', 'Status', 'Size', 'Attached to', @@ -827,7 +827,7 @@ def test_volume_list_no_options(self): 'all_tenants': False, 'project_id': None, 'user_id': None, - 'display_name': None, + 'name': None, 'status': None, } self.volumes_mock.list.assert_called_once_with( @@ -870,7 +870,7 @@ def test_volume_list_project(self): 'all_tenants': True, 'project_id': self.project.id, 'user_id': None, - 'display_name': None, + 'name': None, 'status': None, } self.volumes_mock.list.assert_called_once_with( @@ -915,7 +915,7 @@ def test_volume_list_project_domain(self): 'all_tenants': True, 'project_id': self.project.id, 'user_id': None, - 'display_name': None, + 'name': None, 'status': None, } self.volumes_mock.list.assert_called_once_with( @@ -958,7 +958,7 @@ def test_volume_list_user(self): 'all_tenants': False, 'project_id': None, 'user_id': self.user.id, - 'display_name': None, + 'name': None, 'status': None, } self.volumes_mock.list.assert_called_once_with( @@ -1002,7 +1002,7 @@ def test_volume_list_user_domain(self): 'all_tenants': False, 'project_id': None, 'user_id': self.user.id, - 'display_name': None, + 'name': None, 'status': None, } self.volumes_mock.list.assert_called_once_with( @@ -1045,7 +1045,7 @@ def test_volume_list_name(self): 'all_tenants': False, 'project_id': None, 'user_id': None, - 'display_name': self.mock_volume.name, + 'name': self.mock_volume.name, 'status': None, } self.volumes_mock.list.assert_called_once_with( @@ -1088,7 +1088,7 @@ def test_volume_list_status(self): 'all_tenants': False, 'project_id': None, 'user_id': None, - 'display_name': None, + 'name': None, 'status': self.mock_volume.status, } self.volumes_mock.list.assert_called_once_with( @@ -1131,7 +1131,7 @@ def test_volume_list_all_projects(self): 'all_tenants': True, 'project_id': None, 'user_id': None, - 'display_name': None, + 'name': None, 'status': None, } self.volumes_mock.list.assert_called_once_with( @@ -1175,7 +1175,7 @@ def test_volume_list_long(self): 'all_tenants': False, 'project_id': None, 'user_id': None, - 'display_name': None, + 'name': None, 'status': None, } self.volumes_mock.list.assert_called_once_with( @@ -1186,7 +1186,7 @@ def test_volume_list_long(self): collist = [ 'ID', - 'Display Name', + 'Name', 'Status', 'Size', 'Type', @@ -1248,7 +1248,7 @@ def test_volume_list_with_marker_and_limit(self): 'status': None, 'project_id': None, 'user_id': None, - 'display_name': None, + 'name': None, 'all_tenants': False, } ) self.assertEqual(datalist, tuple(data)) @@ -1263,6 +1263,42 @@ def test_volume_list_negative_limit(self): self.assertRaises(argparse.ArgumentTypeError, self.check_parser, self.cmd, arglist, verifylist) + def test_volume_list_backward_compatibility(self): + arglist = [ + '-c', 'Display Name', + ] + verifylist = [ + ('columns', ['Display Name']), + ('long', False), + ('all_projects', False), + ('name', None), + ('status', None), + ('marker', None), + ('limit', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + search_opts = { + 'all_tenants': False, + 'project_id': None, + 'user_id': None, + 'name': None, + 'status': None, + } + self.volumes_mock.list.assert_called_once_with( + search_opts=search_opts, + marker=None, + limit=None, + ) + + self.assertIn('Display Name', columns) + self.assertNotIn('Name', columns) + + for each_volume in data: + self.assertIn(self.mock_volume.name, each_volume) + class TestVolumeMigrate(TestVolume): diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index 0121e0596e..b29429ef82 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -211,8 +211,14 @@ def take_action(self, parsed_args): 'type': volume._info.pop('volume_type'), }, ) + # Replace "display_name" by "name", keep consistent in v1 and v2 + if 'display_name' in volume._info: + volume._info.update({'name': volume._info.pop('display_name')}) + volume_info = utils.backward_compat_col_showone( + volume._info, parsed_args.columns, {'display_name': 'name'} + ) - return zip(*sorted(six.iteritems(volume._info))) + return zip(*sorted(six.iteritems(volume_info))) class DeleteVolume(command.Command): @@ -330,7 +336,7 @@ def _format_attach(attachments): ) column_headers = ( 'ID', - 'Display Name', + 'Name', 'Status', 'Size', 'Type', @@ -348,7 +354,7 @@ def _format_attach(attachments): ) column_headers = ( 'ID', - 'Display Name', + 'Name', 'Status', 'Size', 'Attached to', @@ -373,6 +379,8 @@ def _format_attach(attachments): search_opts=search_opts, limit=parsed_args.limit, ) + column_headers = utils.backward_compat_col_lister( + column_headers, parsed_args.columns, {'Display Name': 'Name'}) return (column_headers, (utils.get_item_properties( @@ -576,7 +584,15 @@ def take_action(self, parsed_args): {'project_id': volume._info.pop( 'os-vol-tenant-attr:tenant_id')} ) - return zip(*sorted(six.iteritems(volume._info))) + # Replace "display_name" by "name", keep consistent in v1 and v2 + if 'display_name' in volume._info: + volume._info.update({'name': volume._info.pop('display_name')}) + + volume_info = utils.backward_compat_col_showone( + volume._info, parsed_args.columns, {'display_name': 'name'} + ) + + return zip(*sorted(six.iteritems(volume_info))) class UnsetVolume(command.Command): diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index 2b6c966d79..61f846b02f 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -387,7 +387,6 @@ def _format_attach(attachments): 'Metadata', ] column_headers = copy.deepcopy(columns) - column_headers[1] = 'Display Name' column_headers[4] = 'Type' column_headers[6] = 'Attached to' column_headers[7] = 'Properties' @@ -400,7 +399,6 @@ def _format_attach(attachments): 'Attachments', ] column_headers = copy.deepcopy(columns) - column_headers[1] = 'Display Name' column_headers[4] = 'Attached to' # Cache the server list @@ -432,7 +430,7 @@ def _format_attach(attachments): 'all_tenants': all_projects, 'project_id': project_id, 'user_id': user_id, - 'display_name': parsed_args.name, + 'name': parsed_args.name, 'status': parsed_args.status, } @@ -441,6 +439,8 @@ def _format_attach(attachments): marker=parsed_args.marker, limit=parsed_args.limit, ) + column_headers = utils.backward_compat_col_lister( + column_headers, parsed_args.columns, {'Display Name': 'Name'}) return (column_headers, (utils.get_item_properties( diff --git a/releasenotes/notes/bug-1657956-977a615f01775288.yaml b/releasenotes/notes/bug-1657956-977a615f01775288.yaml new file mode 100644 index 0000000000..3a392bed69 --- /dev/null +++ b/releasenotes/notes/bug-1657956-977a615f01775288.yaml @@ -0,0 +1,13 @@ +--- +fixes: + - | + Change column name ``Display Name`` to ``Name`` in ``volume list`` output. + Current ``volume list --name`` command uses ``display_name`` as search_opts + to send to cinder API, and show the result table with ``Display Name`` + as column title. Replace all ``Display Name`` by ``Name`` to be consistent + with other list commands. + + Support a mapping for volume list -c ``Display Name`` (Volume v1 and v2) + and volume create/show -c ``display_name`` (Volume v1) to maintain backward + compatibility until the next major release. + [Bug `1657956 `_] From b52bbe1eec23ab498cc9fcda87045bddb28264db Mon Sep 17 00:00:00 2001 From: Vu Cong Tuan Date: Tue, 30 May 2017 16:37:01 +0700 Subject: [PATCH 1691/3095] Trivial fix typos Change-Id: I72a1da209df38e226ec02d9dbd0142ed4020c0d2 --- openstackclient/api/compute_v2.py | 2 +- openstackclient/tests/functional/base.py | 4 ++-- .../functional/network/v2/test_network_segment.py | 2 +- .../tests/functional/network/v2/test_subnet.py | 2 +- openstackclient/tests/unit/compute/v2/fakes.py | 4 ++-- openstackclient/tests/unit/image/v2/fakes.py | 2 +- openstackclient/tests/unit/network/v2/fakes.py | 14 +++++++------- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/openstackclient/api/compute_v2.py b/openstackclient/api/compute_v2.py index 51b34482ea..0ffed65549 100644 --- a/openstackclient/api/compute_v2.py +++ b/openstackclient/api/compute_v2.py @@ -128,7 +128,7 @@ def floating_ip_delete( https://developer.openstack.org/api-ref/compute/#delete-deallocate-floating-ip-address - :param string security_group: + :param string floating_ip_id: Floating IP ID """ diff --git a/openstackclient/tests/functional/base.py b/openstackclient/tests/functional/base.py index 2d0706456b..d95f7f8465 100644 --- a/openstackclient/tests/functional/base.py +++ b/openstackclient/tests/functional/base.py @@ -48,7 +48,7 @@ def is_service_enabled(service): ret = execute('openstack service show -f value -c enabled ' + service) except exceptions.CommandFailed: # We get here for multiple reasons, all of them mean that a working - # service is not avilable + # service is not available return False return "True" in ret @@ -69,7 +69,7 @@ def get_openstack_configuration_value(cls, configuration): return cls.openstack('configuration show ' + opts) @classmethod - def get_openstack_extention_names(cls): + def get_openstack_extension_names(cls): opts = cls.get_opts(['Name']) return cls.openstack('extension list ' + opts) diff --git a/openstackclient/tests/functional/network/v2/test_network_segment.py b/openstackclient/tests/functional/network/v2/test_network_segment.py index de150d3106..7fc79746b9 100644 --- a/openstackclient/tests/functional/network/v2/test_network_segment.py +++ b/openstackclient/tests/functional/network/v2/test_network_segment.py @@ -37,7 +37,7 @@ def setUpClass(cls): # NOTE(rtheis): The segment extension is not yet enabled # by default. # Skip the tests if not enabled. - extensions = cls.get_openstack_extention_names() + extensions = cls.get_openstack_extension_names() if 'Segment' in extensions: cls.NETWORK_SEGMENT_EXTENSION = 'Segment' diff --git a/openstackclient/tests/functional/network/v2/test_subnet.py b/openstackclient/tests/functional/network/v2/test_subnet.py index 5160042cda..93e0593d0d 100644 --- a/openstackclient/tests/functional/network/v2/test_subnet.py +++ b/openstackclient/tests/functional/network/v2/test_subnet.py @@ -278,6 +278,6 @@ def _subnet_create(self, cmd, name, is_type_ipv4=True): raise pass else: - # break and no longer retry if create sucessfully + # break and no longer retry if create successfully break return cmd_output diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index e6af947684..d5fc9fa957 100644 --- a/openstackclient/tests/unit/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -503,7 +503,7 @@ def get_security_groups(security_groups=None, count=2): If security groups list is provided, then initialize the Mock object with the list. Otherwise create one. - :param List security groups: + :param List security_groups: A list of FakeResource objects faking security groups :param int count: The number of security groups to fake @@ -1029,7 +1029,7 @@ def get_floating_ips(floating_ips=None, count=2): If floating_ips list is provided, then initialize the Mock object with the list. Otherwise create one. - :param List floating ips: + :param List floating_ips: A list of FakeResource objects faking floating ips :param int count: The number of floating ips to fake diff --git a/openstackclient/tests/unit/image/v2/fakes.py b/openstackclient/tests/unit/image/v2/fakes.py index 7f3f02df1f..7b2145873b 100644 --- a/openstackclient/tests/unit/image/v2/fakes.py +++ b/openstackclient/tests/unit/image/v2/fakes.py @@ -287,7 +287,7 @@ def create_one_image_member(attrs=None): """Create a fake image member. :param Dictionary attrs: - A dictionary with all attrbutes of image member + A dictionary with all attributes of image member :return: A FakeResource object with member_id, image_id and so on """ diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index 9a2899419c..e736b0fdd1 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -144,7 +144,7 @@ def get_address_scopes(address_scopes=None, count=2): If address scopes list is provided, then initialize the Mock object with the list. Otherwise create one. - :param List address scopes: + :param List address_scopes: A list of FakeResource objects faking address scopes :param int count: The number of address scopes to fake @@ -883,7 +883,7 @@ def get_qos_policies(qos_policies=None, count=2): If qos policies list is provided, then initialize the Mock object with the list. Otherwise create one. - :param List address scopes: + :param List qos_policies: A list of FakeResource objects faking qos policies :param int count: The number of QoS policies to fake @@ -961,7 +961,7 @@ def get_qos_rules(qos_rules=None, count=2): If Network QoS rules list is provided, then initialize the Mock object with the list. Otherwise create one. - :param List address scopes: + :param List qos_rules: A list of FakeResource objects faking Network QoS rules :param int count: The number of QoS minimum bandwidth rules to fake @@ -1161,7 +1161,7 @@ def get_security_groups(security_groups=None, count=2): If security groups list is provided, then initialize the Mock object with the list. Otherwise create one. - :param List security groups: + :param List security_groups: A list of FakeResource objects faking security groups :param int count: The number of security groups to fake @@ -1241,7 +1241,7 @@ def get_security_group_rules(security_group_rules=None, count=2): If security group rules list is provided, then initialize the Mock object with the list. Otherwise create one. - :param List security group rules: + :param List security_group_rules: A list of FakeResource objects faking security group rules :param int count: The number of security group rules to fake @@ -1406,7 +1406,7 @@ def get_floating_ips(floating_ips=None, count=2): If floating_ips list is provided, then initialize the Mock object with the list. Otherwise create one. - :param List floating ips: + :param List floating_ips: A list of FakeResource objects faking floating ips :param int count: The number of floating ips to fake @@ -1585,7 +1585,7 @@ def get_subnet_pools(subnet_pools=None, count=2): If subnet_pools list is provided, then initialize the Mock object with the list. Otherwise create one. - :param List subnet pools: + :param List subnet_pools: A list of FakeResource objects faking subnet pools :param int count: The number of subnet pools to fake From d034b980ab03bbd50d31ef8bd439bc80a91a7d77 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 31 May 2017 16:45:37 +0000 Subject: [PATCH 1692/3095] Updated from global requirements Change-Id: Ica0de1e6a275fee4761dca1f128caf073bcfb92b --- requirements.txt | 2 +- test-requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 42d18f177e..d77bdbf6fd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,4 +14,4 @@ oslo.utils>=3.20.0 # Apache-2.0 python-glanceclient>=2.5.0 # Apache-2.0 python-keystoneclient>=3.8.0 # Apache-2.0 python-novaclient>=7.1.0 # Apache-2.0 -python-cinderclient>=2.0.1 # Apache-2.0 +python-cinderclient>=2.1.0 # Apache-2.0 diff --git a/test-requirements.txt b/test-requirements.txt index c1fe0f662b..91e9aa2817 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -33,7 +33,7 @@ python-ironicclient>=1.11.0 # Apache-2.0 python-ironic-inspector-client>=1.5.0 # Apache-2.0 python-mistralclient>=3.1.0 # Apache-2.0 python-muranoclient>=0.8.2 # Apache-2.0 -python-neutronclient>=5.1.0 # Apache-2.0 +python-neutronclient>=6.3.0 # Apache-2.0 python-saharaclient>=1.1.0 # Apache-2.0 python-searchlightclient>=1.0.0 #Apache-2.0 python-senlinclient>=1.1.0 # Apache-2.0 From 16eedeb965222871f71b773a91d396c3e1e24861 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 1 Jun 2017 22:21:20 +0000 Subject: [PATCH 1693/3095] Updated from global requirements Change-Id: I5b8daf5b9e177b368678a605997e52eff04a0999 --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 91e9aa2817..ada0099d80 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -33,7 +33,7 @@ python-ironicclient>=1.11.0 # Apache-2.0 python-ironic-inspector-client>=1.5.0 # Apache-2.0 python-mistralclient>=3.1.0 # Apache-2.0 python-muranoclient>=0.8.2 # Apache-2.0 -python-neutronclient>=6.3.0 # Apache-2.0 +python-neutronclient!=6.3.0,>=6.2.0 # Apache-2.0 python-saharaclient>=1.1.0 # Apache-2.0 python-searchlightclient>=1.0.0 #Apache-2.0 python-senlinclient>=1.1.0 # Apache-2.0 From adac738f17f4fbf261089f45544a72f63e3ddebe Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Fri, 2 Jun 2017 22:07:23 +0000 Subject: [PATCH 1694/3095] Updated from global requirements Change-Id: Iad391c3f10f90cd238de99e6ca94c3de0edc986b --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index ada0099d80..3ec16904c1 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -8,7 +8,7 @@ fixtures>=3.0.0 # Apache-2.0/BSD mock>=2.0 # BSD oslosphinx>=4.7.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 -reno>=1.8.0 # Apache-2.0 +reno!=2.3.1,>=1.8.0 # Apache-2.0 requests!=2.12.2,!=2.13.0,>=2.10.0 # Apache-2.0 requests-mock>=1.1 # Apache-2.0 sphinx!=1.6.1,>=1.5.1 # BSD From c912717e42ce42b36125f4085c07baa04815e095 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Mon, 5 Jun 2017 21:58:44 +0000 Subject: [PATCH 1695/3095] Updated from global requirements Change-Id: If85aea646119bf12090074edf198f64a8719c949 --- requirements.txt | 2 +- test-requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index d77bdbf6fd..1a3d1812c4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ openstacksdk>=0.9.16 # Apache-2.0 osc-lib>=1.5.1 # Apache-2.0 oslo.i18n!=3.15.2,>=2.1.0 # Apache-2.0 oslo.utils>=3.20.0 # Apache-2.0 -python-glanceclient>=2.5.0 # Apache-2.0 +python-glanceclient>=2.7.0 # Apache-2.0 python-keystoneclient>=3.8.0 # Apache-2.0 python-novaclient>=7.1.0 # Apache-2.0 python-cinderclient>=2.1.0 # Apache-2.0 diff --git a/test-requirements.txt b/test-requirements.txt index 3ec16904c1..c22ae1f98d 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -33,7 +33,7 @@ python-ironicclient>=1.11.0 # Apache-2.0 python-ironic-inspector-client>=1.5.0 # Apache-2.0 python-mistralclient>=3.1.0 # Apache-2.0 python-muranoclient>=0.8.2 # Apache-2.0 -python-neutronclient!=6.3.0,>=6.2.0 # Apache-2.0 +python-neutronclient>=6.3.0 # Apache-2.0 python-saharaclient>=1.1.0 # Apache-2.0 python-searchlightclient>=1.0.0 #Apache-2.0 python-senlinclient>=1.1.0 # Apache-2.0 From eeb614c47759fa9a01e6d886ed07acceb8d9ff61 Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Sun, 21 May 2017 03:36:23 +0000 Subject: [PATCH 1696/3095] volume functest: ensure snapshots deleted when volume delete Deleting snapshot may take time. The current volume API does not allow to delete volumes with snapshots, so if deleting snapshot may take time, a delete request for a parent volume will fail. This sometimes causes functional test failures in slow environments. wait_for_status() checks whether volume status is in error statuses but previously the expected error status was wrong. Cinder API uses lower case as volume status, so it did not work expectedly. Change-Id: I095894ba39f23bf81d71351818d24dbb5ca459fb --- .../tests/functional/compute/v2/common.py | 8 +-- .../functional/compute/v2/test_server.py | 24 ++++--- .../tests/functional/volume/base.py | 64 +++++++++++++++++++ .../tests/functional/volume/v1/common.py | 4 +- .../functional/volume/v1/test_snapshot.py | 35 ++++------ .../tests/functional/volume/v1/test_volume.py | 30 ++------- .../tests/functional/volume/v2/common.py | 4 +- .../functional/volume/v2/test_snapshot.py | 38 ++++------- .../tests/functional/volume/v2/test_volume.py | 48 ++++---------- .../tests/functional/volume/v3/common.py | 4 +- 10 files changed, 130 insertions(+), 129 deletions(-) create mode 100644 openstackclient/tests/functional/volume/base.py diff --git a/openstackclient/tests/functional/compute/v2/common.py b/openstackclient/tests/functional/compute/v2/common.py index 99d87bb4b5..851664c842 100644 --- a/openstackclient/tests/functional/compute/v2/common.py +++ b/openstackclient/tests/functional/compute/v2/common.py @@ -125,12 +125,12 @@ def wait_for_status( name )) status = cmd_output['status'] - print('Waiting for {}, current status: {}'.format( - expected_status, - status, - )) if status == expected_status: + print('Server {} now has status {}'.format( + name, status)) break + print('Server {}: Waiting for {}, current status: {}'.format( + name, expected_status, status)) self.assertNotIn(status, failures) time.sleep(interval) total_sleep += interval diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py index a4b290246c..c9f4d62c85 100644 --- a/openstackclient/tests/functional/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -17,7 +17,7 @@ from openstackclient.tests.functional import base from openstackclient.tests.functional.compute.v2 import common -from openstackclient.tests.functional.volume.v2 import test_volume +from openstackclient.tests.functional.volume.v2 import common as volume_common class ServerTests(common.ComputeTestCase): @@ -282,9 +282,7 @@ def test_server_reboot(self): def test_server_boot_from_volume(self): """Test server create from volume, server delete""" # get volume status wait function - volume_wait_for = test_volume.VolumeTests( - methodName='wait_for', - ).wait_for + volume_wait_for = volume_common.BaseVolumeTests.wait_for_status # get image size cmd_output = json.loads(self.openstack( @@ -391,9 +389,8 @@ def test_server_boot_from_volume(self): def test_server_boot_with_bdm_snapshot(self): """Test server create from image with bdm snapshot, server delete""" # get volume status wait function - volume_wait_for = test_volume.VolumeTests( - methodName='wait_for', - ).wait_for + volume_wait_for = volume_common.BaseVolumeTests.wait_for_status + volume_wait_for_delete = volume_common.BaseVolumeTests.wait_for_delete # create source empty volume empty_volume_name = uuid.uuid4().hex @@ -419,6 +416,13 @@ def test_server_boot_with_bdm_snapshot(self): empty_snapshot_name )) self.assertIsNotNone(cmd_output["id"]) + # Deleting volume snapshot take time, so we need to wait until the + # snapshot goes. Entries registered by self.addCleanup will be called + # in the reverse order, so we need to register wait_for_delete first. + self.addCleanup(volume_wait_for_delete, + 'volume snapshot', empty_snapshot_name) + self.addCleanup(self.openstack, + 'volume snapshot delete ' + empty_snapshot_name) self.assertEqual( empty_snapshot_name, cmd_output['name'], @@ -489,12 +493,6 @@ def test_server_boot_with_bdm_snapshot(self): # the attached volume had been deleted pass - # clean up volume snapshot manually, make sure the snapshot and volume - # can be deleted sequentially, self.addCleanup so fast, that cause - # volume service API 400 error and the volume is left over at the end. - self.openstack('volume snapshot delete ' + empty_snapshot_name) - volume_wait_for('volume snapshot', empty_snapshot_name, 'disappear') - def test_server_create_with_none_network(self): """Test server create with none network option.""" server_name = uuid.uuid4().hex diff --git a/openstackclient/tests/functional/volume/base.py b/openstackclient/tests/functional/volume/base.py new file mode 100644 index 0000000000..53032606c6 --- /dev/null +++ b/openstackclient/tests/functional/volume/base.py @@ -0,0 +1,64 @@ +# 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 json +import time + +from openstackclient.tests.functional import base + + +class BaseVolumeTests(base.TestCase): + """Base class for Volume functional tests. """ + + @classmethod + def wait_for_status(cls, check_type, check_name, desired_status, + wait=120, interval=5, failures=None): + current_status = "notset" + if failures is None: + failures = ['error'] + total_sleep = 0 + while total_sleep < wait: + output = json.loads(cls.openstack( + check_type + ' show -f json ' + check_name)) + current_status = output['status'] + if (current_status == desired_status): + print('{} {} now has status {}' + .format(check_type, check_name, current_status)) + return + print('Checking {} {} Waiting for {} current status: {}' + .format(check_type, check_name, + desired_status, current_status)) + if current_status in failures: + raise Exception( + 'Current status {} of {} {} is one of failures {}' + .format(current_status, check_type, check_name, failures)) + time.sleep(interval) + total_sleep += interval + cls.assertOutput(desired_status, current_status) + + @classmethod + def wait_for_delete(cls, check_type, check_name, wait=120, interval=5, + name_field=None): + total_sleep = 0 + name_field = name_field or 'Name' + while total_sleep < wait: + result = json.loads(cls.openstack(check_type + ' list -f json')) + names = [x[name_field] for x in result] + if check_name not in names: + print('{} {} is now deleted'.format(check_type, check_name)) + return + print('Checking {} {} Waiting for deleted' + .format(check_type, check_name)) + time.sleep(interval) + total_sleep += interval + raise Exception('Timeout: {} {} was not deleted in {} seconds' + .format(check_type, check_name, wait)) diff --git a/openstackclient/tests/functional/volume/v1/common.py b/openstackclient/tests/functional/volume/v1/common.py index a442850daa..f9d96bbb6c 100644 --- a/openstackclient/tests/functional/volume/v1/common.py +++ b/openstackclient/tests/functional/volume/v1/common.py @@ -12,10 +12,10 @@ import os -from openstackclient.tests.functional import base +from openstackclient.tests.functional.volume import base -class BaseVolumeTests(base.TestCase): +class BaseVolumeTests(base.BaseVolumeTests): """Base class for Volume functional tests. """ @classmethod diff --git a/openstackclient/tests/functional/volume/v1/test_snapshot.py b/openstackclient/tests/functional/volume/v1/test_snapshot.py index 89a98661f4..28726762d7 100644 --- a/openstackclient/tests/functional/volume/v1/test_snapshot.py +++ b/openstackclient/tests/functional/volume/v1/test_snapshot.py @@ -11,7 +11,6 @@ # under the License. import json -import time import uuid from openstackclient.tests.functional.volume.v1 import common @@ -22,16 +21,6 @@ class VolumeSnapshotTests(common.BaseVolumeTests): VOLLY = uuid.uuid4().hex - @classmethod - def wait_for_status(cls, command, status, tries): - opts = cls.get_opts(['status']) - for attempt in range(tries): - time.sleep(1) - raw_output = cls.openstack(command + opts) - if (raw_output.rstrip() == status): - return - cls.assertOutput(status, raw_output) - @classmethod def setUpClass(cls): super(VolumeSnapshotTests, cls).setUpClass() @@ -41,12 +30,12 @@ def setUpClass(cls): '--size 1 ' + cls.VOLLY )) - cls.wait_for_status('volume show ' + cls.VOLLY, 'available', 6) + cls.wait_for_status('volume', cls.VOLLY, 'available') cls.VOLUME_ID = cmd_output['id'] @classmethod def tearDownClass(cls): - cls.wait_for_status('volume show ' + cls.VOLLY, 'available', 6) + cls.wait_for_status('volume', cls.VOLLY, 'available') raw_output = cls.openstack('volume delete --force ' + cls.VOLLY) cls.assertOutput('', raw_output) @@ -74,14 +63,14 @@ def test_volume_snapshot__delete(self): cmd_output["display_name"], ) - self.wait_for_status( - 'volume snapshot show ' + name1, 'available', 6) - self.wait_for_status( - 'volume snapshot show ' + name2, 'available', 6) + self.wait_for_status('volume snapshot', name1, 'available') + self.wait_for_status('volume snapshot', name2, 'available') del_output = self.openstack( 'volume snapshot delete ' + name1 + ' ' + name2) self.assertOutput('', del_output) + self.wait_for_delete('volume snapshot', name1) + self.wait_for_delete('volume snapshot', name2) def test_volume_snapshot_list(self): """Test create, list filter""" @@ -91,6 +80,7 @@ def test_volume_snapshot_list(self): name1 + ' --volume ' + self.VOLLY )) + self.addCleanup(self.wait_for_delete, 'volume snapshot', name1) self.addCleanup(self.openstack, 'volume snapshot delete ' + name1) self.assertEqual( name1, @@ -104,8 +94,7 @@ def test_volume_snapshot_list(self): 1, cmd_output["size"], ) - self.wait_for_status( - 'volume snapshot show ' + name1, 'available', 6) + self.wait_for_status('volume snapshot', name1, 'available') name2 = uuid.uuid4().hex cmd_output = json.loads(self.openstack( @@ -113,6 +102,7 @@ def test_volume_snapshot_list(self): name2 + ' --volume ' + self.VOLLY )) + self.addCleanup(self.wait_for_delete, 'volume snapshot', name2) self.addCleanup(self.openstack, 'volume snapshot delete ' + name2) self.assertEqual( name2, @@ -126,8 +116,7 @@ def test_volume_snapshot_list(self): 1, cmd_output["size"], ) - self.wait_for_status( - 'volume snapshot show ' + name2, 'available', 6) + self.wait_for_status('volume snapshot', name2, 'available') # Test list --long, --status cmd_output = json.loads(self.openstack( @@ -167,6 +156,7 @@ def test_snapshot_set(self): ' --description aaaa ' + name )) + self.addCleanup(self.wait_for_delete, 'volume snapshot', new_name) self.addCleanup(self.openstack, 'volume snapshot delete ' + new_name) self.assertEqual( name, @@ -180,8 +170,7 @@ def test_snapshot_set(self): 'aaaa', cmd_output["display_description"], ) - self.wait_for_status( - 'volume snapshot show ' + name, 'available', 6) + self.wait_for_status('volume snapshot', name, 'available') # Test volume snapshot set raw_output = self.openstack( diff --git a/openstackclient/tests/functional/volume/v1/test_volume.py b/openstackclient/tests/functional/volume/v1/test_volume.py index 6eddf21320..001bbe6e90 100644 --- a/openstackclient/tests/functional/volume/v1/test_volume.py +++ b/openstackclient/tests/functional/volume/v1/test_volume.py @@ -11,7 +11,6 @@ # under the License. import json -import time import uuid from openstackclient.tests.functional.volume.v1 import common @@ -44,8 +43,8 @@ def test_volume_create_and_delete(self): cmd_output["size"], ) - self.wait_for("volume", name1, "available") - self.wait_for("volume", name2, "available") + self.wait_for_status("volume", name1, "available") + self.wait_for_status("volume", name2, "available") del_output = self.openstack('volume delete ' + name1 + ' ' + name2) self.assertOutput('', del_output) @@ -62,7 +61,7 @@ def test_volume_list(self): 1, cmd_output["size"], ) - self.wait_for("volume", name1, "available") + self.wait_for_status("volume", name1, "available") name2 = uuid.uuid4().hex cmd_output = json.loads(self.openstack( @@ -75,7 +74,7 @@ def test_volume_list(self): 2, cmd_output["size"], ) - self.wait_for("volume", name2, "available") + self.wait_for_status("volume", name2, "available") # Test list cmd_output = json.loads(self.openstack( @@ -131,7 +130,7 @@ def test_volume_set_and_unset(self): 'false', cmd_output["bootable"], ) - self.wait_for("volume", name, "available") + self.wait_for_status("volume", name, "available") # Test volume set new_name = uuid.uuid4().hex @@ -208,7 +207,7 @@ def test_volume_create_and_list_and_show_backward_compatibility(self): self.assertNotIn('name', json_output) self.addCleanup(self.openstack, 'volume delete ' + volume_id) - self.wait_for("volume", name1, "available") + self.wait_for_status("volume", name1, "available") json_output = json.loads(self.openstack( 'volume list -f json ' + @@ -233,20 +232,3 @@ def test_volume_create_and_list_and_show_backward_compatibility(self): self.assertEqual(name1, json_output['display_name']) self.assertIn('id', json_output) self.assertNotIn('name', json_output) - - def wait_for(self, check_type, check_name, desired_status, wait=120, - interval=5, failures=['ERROR']): - status = "notset" - total_sleep = 0 - opts = self.get_opts(['status']) - while total_sleep < wait: - status = self.openstack(check_type + ' show ' + check_name + opts) - status = status.rstrip() - print('Checking {} {} Waiting for {} current status: {}' - .format(check_type, check_name, desired_status, status)) - if status == desired_status: - break - self.assertNotIn(status, failures) - time.sleep(interval) - total_sleep += interval - self.assertEqual(desired_status, status) diff --git a/openstackclient/tests/functional/volume/v2/common.py b/openstackclient/tests/functional/volume/v2/common.py index e279a6f6ca..1d14719fc7 100644 --- a/openstackclient/tests/functional/volume/v2/common.py +++ b/openstackclient/tests/functional/volume/v2/common.py @@ -12,10 +12,10 @@ import os -from openstackclient.tests.functional import base +from openstackclient.tests.functional.volume import base -class BaseVolumeTests(base.TestCase): +class BaseVolumeTests(base.BaseVolumeTests): """Base class for Volume functional tests. """ @classmethod diff --git a/openstackclient/tests/functional/volume/v2/test_snapshot.py b/openstackclient/tests/functional/volume/v2/test_snapshot.py index 422e5b7ceb..2fff43c820 100644 --- a/openstackclient/tests/functional/volume/v2/test_snapshot.py +++ b/openstackclient/tests/functional/volume/v2/test_snapshot.py @@ -11,7 +11,6 @@ # under the License. import json -import time import uuid from openstackclient.tests.functional.volume.v2 import common @@ -22,16 +21,6 @@ class VolumeSnapshotTests(common.BaseVolumeTests): VOLLY = uuid.uuid4().hex - @classmethod - def wait_for_status(cls, command, status, tries): - opts = cls.get_opts(['status']) - for attempt in range(tries): - time.sleep(1) - raw_output = cls.openstack(command + opts) - if (raw_output.rstrip() == status): - return - cls.assertOutput(status, raw_output) - @classmethod def setUpClass(cls): super(VolumeSnapshotTests, cls).setUpClass() @@ -41,13 +30,14 @@ def setUpClass(cls): '--size 1 ' + cls.VOLLY )) - cls.wait_for_status('volume show ' + cls.VOLLY, 'available', 6) + cls.wait_for_status('volume', cls.VOLLY, 'available') cls.VOLUME_ID = cmd_output['id'] @classmethod def tearDownClass(cls): - cls.wait_for_status('volume show ' + cls.VOLLY, 'available', 6) - raw_output = cls.openstack('volume delete --force ' + cls.VOLLY) + cls.wait_for_status('volume', cls.VOLLY, 'available') + raw_output = cls.openstack( + 'volume delete --force ' + cls.VOLLY) cls.assertOutput('', raw_output) def test_volume_snapshot__delete(self): @@ -74,14 +64,14 @@ def test_volume_snapshot__delete(self): cmd_output["name"], ) - self.wait_for_status( - 'volume snapshot show ' + name1, 'available', 6) - self.wait_for_status( - 'volume snapshot show ' + name2, 'available', 6) + self.wait_for_status('volume snapshot', name1, 'available') + self.wait_for_status('volume snapshot', name2, 'available') del_output = self.openstack( 'volume snapshot delete ' + name1 + ' ' + name2) self.assertOutput('', del_output) + self.wait_for_delete('volume snapshot', name1) + self.wait_for_delete('volume snapshot', name2) def test_volume_snapshot_list(self): """Test create, list filter""" @@ -91,6 +81,7 @@ def test_volume_snapshot_list(self): name1 + ' --volume ' + self.VOLLY )) + self.addCleanup(self.wait_for_delete, 'volume snapshot', name1) self.addCleanup(self.openstack, 'volume snapshot delete ' + name1) self.assertEqual( name1, @@ -104,8 +95,7 @@ def test_volume_snapshot_list(self): 1, cmd_output["size"], ) - self.wait_for_status( - 'volume snapshot show ' + name1, 'available', 6) + self.wait_for_status('volume snapshot', name1, 'available') name2 = uuid.uuid4().hex cmd_output = json.loads(self.openstack( @@ -113,6 +103,7 @@ def test_volume_snapshot_list(self): name2 + ' --volume ' + self.VOLLY )) + self.addCleanup(self.wait_for_delete, 'volume snapshot', name2) self.addCleanup(self.openstack, 'volume snapshot delete ' + name2) self.assertEqual( name2, @@ -126,8 +117,7 @@ def test_volume_snapshot_list(self): 1, cmd_output["size"], ) - self.wait_for_status( - 'volume snapshot show ' + name2, 'available', 6) + self.wait_for_status('volume snapshot', name2, 'available') raw_output = self.openstack( 'volume snapshot set ' + '--state error ' + @@ -174,6 +164,7 @@ def test_volume_snapshot_set(self): '--property Alpha=a ' + name )) + self.addCleanup(self.wait_for_delete, 'volume snapshot', new_name) self.addCleanup(self.openstack, 'volume snapshot delete ' + new_name) self.assertEqual( name, @@ -191,8 +182,7 @@ def test_volume_snapshot_set(self): "Alpha='a'", cmd_output["properties"], ) - self.wait_for_status( - 'volume snapshot show ' + name, 'available', 6) + self.wait_for_status('volume snapshot', name, 'available') # Test volume snapshot set raw_output = self.openstack( diff --git a/openstackclient/tests/functional/volume/v2/test_volume.py b/openstackclient/tests/functional/volume/v2/test_volume.py index f936907ca2..2930d48370 100644 --- a/openstackclient/tests/functional/volume/v2/test_volume.py +++ b/openstackclient/tests/functional/volume/v2/test_volume.py @@ -11,11 +11,8 @@ # under the License. import json -import time import uuid -from tempest.lib import exceptions - from openstackclient.tests.functional.volume.v2 import common @@ -46,8 +43,8 @@ def test_volume_delete(self): cmd_output["size"], ) - self.wait_for("volume", name1, "available") - self.wait_for("volume", name2, "available") + self.wait_for_status("volume", name1, "available") + self.wait_for_status("volume", name2, "available") del_output = self.openstack('volume delete ' + name1 + ' ' + name2) self.assertOutput('', del_output) @@ -64,7 +61,7 @@ def test_volume_list(self): 1, cmd_output["size"], ) - self.wait_for("volume", name1, "available") + self.wait_for_status("volume", name1, "available") name2 = uuid.uuid4().hex cmd_output = json.loads(self.openstack( @@ -77,7 +74,7 @@ def test_volume_list(self): 2, cmd_output["size"], ) - self.wait_for("volume", name2, "available") + self.wait_for_status("volume", name2, "available") raw_output = self.openstack( 'volume set ' + '--state error ' + @@ -138,7 +135,7 @@ def test_volume_set_and_unset(self): 'false', cmd_output["bootable"], ) - self.wait_for("volume", name, "available") + self.wait_for_status("volume", name, "available") # Test volume set raw_output = self.openstack( @@ -218,7 +215,7 @@ def test_volume_snapshot(self): '--size 1 ' + volume_name )) - self.wait_for("volume", volume_name, "available") + self.wait_for_status("volume", volume_name, "available") self.assertEqual( volume_name, cmd_output["name"], @@ -228,9 +225,10 @@ def test_volume_snapshot(self): snapshot_name + ' --volume ' + volume_name )) - self.wait_for("volume snapshot", snapshot_name, "available") + self.wait_for_status("volume snapshot", snapshot_name, "available") name = uuid.uuid4().hex + # Create volume from snapshot cmd_output = json.loads(self.openstack( 'volume create -f json ' + '--snapshot ' + snapshot_name + @@ -242,12 +240,15 @@ def test_volume_snapshot(self): name, cmd_output["name"], ) - self.wait_for("volume", name, "available") + self.wait_for_status("volume", name, "available") # Delete snapshot raw_output = self.openstack( 'volume snapshot delete ' + snapshot_name) self.assertOutput('', raw_output) + # Deleting snapshot may take time. If volume snapshot still exists when + # a parent volume delete is requested, the volume deletion will fail. + self.wait_for_delete('volume snapshot', snapshot_name) def test_volume_list_backward_compatibility(self): """Test backward compatibility of list command""" @@ -262,7 +263,7 @@ def test_volume_list_backward_compatibility(self): 1, cmd_output["size"], ) - self.wait_for("volume", name1, "available") + self.wait_for_status("volume", name1, "available") # Test list -c "Display Name" cmd_output = json.loads(self.openstack( @@ -279,26 +280,3 @@ def test_volume_list_backward_compatibility(self): )) for each_volume in cmd_output: self.assertIn('Name', each_volume) - - def wait_for(self, check_type, check_name, desired_status, wait=120, - interval=5, failures=['ERROR']): - status = "notset" - total_sleep = 0 - opts = self.get_opts(['status']) - while total_sleep < wait: - try: - status = self.openstack( - check_type + ' show ' + check_name + opts - ) - except exceptions.CommandFailed: - # Show command raise Exception when object had been deleted - status = 'disappear' - status = status.rstrip() - print('Checking {} {} Waiting for {} current status: {}' - .format(check_type, check_name, desired_status, status)) - if status == desired_status: - break - self.assertNotIn(status, failures) - time.sleep(interval) - total_sleep += interval - self.assertEqual(desired_status, status) diff --git a/openstackclient/tests/functional/volume/v3/common.py b/openstackclient/tests/functional/volume/v3/common.py index 57a62df643..bc8befa67d 100644 --- a/openstackclient/tests/functional/volume/v3/common.py +++ b/openstackclient/tests/functional/volume/v3/common.py @@ -12,10 +12,10 @@ import os -from openstackclient.tests.functional import base +from openstackclient.tests.functional.volume import base -class BaseVolumeTests(base.TestCase): +class BaseVolumeTests(base.BaseVolumeTests): """Base class for Volume functional tests. """ @classmethod From f403ff9e9c3e66a88042150f855917adda25ab53 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 8 Jun 2017 16:28:14 +0000 Subject: [PATCH 1697/3095] Updated from global requirements Change-Id: I273241692ee85f9d9e239c2ba7df3c683a429e98 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 1a3d1812c4..a4add550c0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,5 +13,5 @@ oslo.i18n!=3.15.2,>=2.1.0 # Apache-2.0 oslo.utils>=3.20.0 # Apache-2.0 python-glanceclient>=2.7.0 # Apache-2.0 python-keystoneclient>=3.8.0 # Apache-2.0 -python-novaclient>=7.1.0 # Apache-2.0 +python-novaclient>=9.0.0 # Apache-2.0 python-cinderclient>=2.1.0 # Apache-2.0 From 40adedf6cd869b67ae7cb37687d7fcf17d63399d Mon Sep 17 00:00:00 2001 From: phil-hopkins-a Date: Fri, 9 Jun 2017 14:07:26 -0500 Subject: [PATCH 1698/3095] Fix Mapping Guide Error The mapping guide showed that the openstack command for tokek-get issue token. It should have been token issue. Change-Id: I233d45ab0f4229caa9a725c931f11b3374270822 Closes-Bug: 1696246 --- doc/source/data/keystone.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/data/keystone.csv b/doc/source/data/keystone.csv index b1364010fa..03c34704da 100644 --- a/doc/source/data/keystone.csv +++ b/doc/source/data/keystone.csv @@ -21,7 +21,7 @@ tenant-delete,project delete,Delete tenant. tenant-get,proejct show,Display tenant details. tenant-list,project list,List all tenants. tenant-update,project set,"Update tenant name, description, enabled status." -token-get,issue token,Display the current user token. +token-get,token issue,Display the current user token. user-create,user create,Create new user. user-delete,user delete,Delete user. user-get,user show,Display user details. @@ -34,4 +34,4 @@ user-update,user set,"Update user's name, email, and enabled status." discover,WONTFIX,"Discover Keystone servers, supported API versions and extensions." bootstrap,WONTFIX,"Grants a new role to a new user on a new tenant, after creating each." bash-completion,complete,Prints all of the commands and options to stdout. -help,help,Display help about this program or one of its subcommands. \ No newline at end of file +help,help,Display help about this program or one of its subcommands. From e54fcd0a5ccb80b56db3b61ae461473f2ceddea9 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sat, 10 Jun 2017 21:48:48 +0000 Subject: [PATCH 1699/3095] Updated from global requirements Change-Id: Ic49fc0fcd068d03f2dad5b49e3de4d98702866dc --- requirements.txt | 2 +- test-requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index a4add550c0..978fc7b4ee 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ six>=1.9.0 # MIT Babel!=2.4.0,>=2.3.4 # BSD cliff>=2.6.0 # Apache-2.0 -keystoneauth1>=2.20.0 # Apache-2.0 +keystoneauth1>=2.21.0 # Apache-2.0 openstacksdk>=0.9.16 # Apache-2.0 osc-lib>=1.5.1 # Apache-2.0 oslo.i18n!=3.15.2,>=2.1.0 # Apache-2.0 diff --git a/test-requirements.txt b/test-requirements.txt index c22ae1f98d..5c9a80c46b 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -9,7 +9,7 @@ mock>=2.0 # BSD oslosphinx>=4.7.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 reno!=2.3.1,>=1.8.0 # Apache-2.0 -requests!=2.12.2,!=2.13.0,>=2.10.0 # Apache-2.0 +requests>=2.14.2 # Apache-2.0 requests-mock>=1.1 # Apache-2.0 sphinx!=1.6.1,>=1.5.1 # BSD stevedore>=1.20.0 # Apache-2.0 From eb793dc8c6a8bd30e612f19f30808528b10eb344 Mon Sep 17 00:00:00 2001 From: Reedip Date: Thu, 23 Feb 2017 08:05:00 -0500 Subject: [PATCH 1700/3095] Add default-quota to subnet pool commands Add --default-quota option to subnet pool create and set commands. Setting default-quota back to None may break the current Neutron behavior, therefore support for Unset command is not provided in this patch. Neutron API: https://github.com/openstack/neutron/blob/a0e0e8b6686b847a4963a6aa6a3224b5768544e6/neutron/api/v2/attributes.py#L239 Closes-Bug: #1667294 Change-Id: Ia4e7c23a49e91a090133c729353cdb8e62bc5674 --- doc/source/command-objects/subnet-pool.rst | 14 ++++++- openstackclient/network/v2/subnet_pool.py | 16 ++++++- .../functional/network/v2/test_subnet_pool.py | 39 +++++++++++++++-- .../tests/unit/network/v2/test_subnet_pool.py | 42 ++++++++++++++++++- .../notes/bug-1667294-f92efa49627eb00a.yaml | 6 +++ 5 files changed, 110 insertions(+), 7 deletions(-) create mode 100644 releasenotes/notes/bug-1667294-f92efa49627eb00a.yaml diff --git a/doc/source/command-objects/subnet-pool.rst b/doc/source/command-objects/subnet-pool.rst index d47673de52..3a60974a97 100644 --- a/doc/source/command-objects/subnet-pool.rst +++ b/doc/source/command-objects/subnet-pool.rst @@ -24,6 +24,7 @@ Create subnet pool [--address-scope ] [--default | --no-default] [--share | --no-share] + [--default-quota ] --pool-prefix [...] @@ -73,7 +74,12 @@ Create subnet pool Set this subnet pool as not shared -.. describe:: --pool-prefix +.. option:: --default-quota + + Set default quota for subnet pool as the number of + IP addresses allowed in a subnet + +.. option:: --pool-prefix Set subnet pool prefixes (in CIDR notation) (repeat option to set multiple prefixes) @@ -169,6 +175,7 @@ Set subnet pool properties [--address-scope | --no-address-scope] [--default | --no-default] [--description ] + [--default-quota ] .. option:: --name @@ -213,6 +220,11 @@ Set subnet pool properties Set subnet pool description +.. option:: --default-quota + + Set default quota for subnet pool as the number of + IP addresses allowed in a subnet + .. _subnet_pool_set-subnet-pool: .. describe:: diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index 82ad94127b..b72a74fc19 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -89,6 +89,9 @@ def _get_attrs(client_manager, parsed_args): if parsed_args.description is not None: attrs['description'] = parsed_args.description + if parsed_args.default_quota is not None: + attrs['default_quota'] = int(parsed_args.default_quota) + return attrs @@ -182,6 +185,12 @@ def get_parser(self, prog_name): metavar='', help=_("Set subnet pool description") ) + parser.add_argument( + '--default-quota', + type=int, + metavar='', + help=_("Set default quota for subnet pool as the number of" + "IP addresses allowed in a subnet")), return parser def take_action(self, parsed_args): @@ -369,7 +378,12 @@ def get_parser(self, prog_name): metavar='', help=_("Set subnet pool description") ) - + parser.add_argument( + '--default-quota', + type=int, + metavar='', + help=_("Set default quota for subnet pool as the number of" + "IP addresses allowed in a subnet")), return parser def take_action(self, parsed_args): diff --git a/openstackclient/tests/functional/network/v2/test_subnet_pool.py b/openstackclient/tests/functional/network/v2/test_subnet_pool.py index 640f68b708..a4b823f100 100644 --- a/openstackclient/tests/functional/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/functional/network/v2/test_subnet_pool.py @@ -165,7 +165,7 @@ def test_subnet_pool_list(self): self.assertIn(name2, names) def test_subnet_pool_set_show(self): - """Test create, set, show, delete""" + """Test create, delete, set, show, unset""" name = uuid.uuid4().hex new_name = name + "_" @@ -173,11 +173,15 @@ def test_subnet_pool_set_show(self): '--default-prefix-length 16 ' + '--min-prefix-length 16 ' + '--max-prefix-length 32 ' + - '--description aaaa ', + '--description aaaa ' + + '--default-quota 10 ', name, ) - self.addCleanup(self.openstack, 'subnet pool delete ' + new_name) + self.addCleanup( + self.openstack, + 'subnet pool delete ' + cmd_output['id'], + ) self.assertEqual( name, cmd_output["name"], @@ -202,6 +206,10 @@ def test_subnet_pool_set_show(self): 32, cmd_output["max_prefixlen"], ) + self.assertEqual( + 10, + cmd_output["default_quota"], + ) # Test set cmd_output = self.openstack( @@ -212,7 +220,8 @@ def test_subnet_pool_set_show(self): '--default-prefix-length 8 ' + '--min-prefix-length 8 ' + '--max-prefix-length 16 ' + - name + '--default-quota 20 ' + + name, ) self.assertOutput('', cmd_output) @@ -244,6 +253,28 @@ def test_subnet_pool_set_show(self): 16, cmd_output["max_prefixlen"], ) + self.assertEqual( + 20, + cmd_output["default_quota"], + ) + + # Test unset + # NOTE(dtroyer): The unset command --default-quota option DOES NOT + # WORK after a default quota has been set once on a + # pool. The error appears to be in a lower layer, + # once that is fixed add a test for subnet pool unset + # --default-quota. + # The unset command of --pool-prefixes also doesnt work + # right now. It would be fixed in a separate patch once + # the lower layer is fixed. + # cmd_output = self.openstack( + # '--debug ' + + # 'subnet pool unset ' + + # ' --pool-prefix 10.110.0.0/16 ' + + # new_name, + # ) + # self.assertOutput('', cmd_output) + # self.assertNone(cmd_output["prefixes"]) def _subnet_pool_create(self, cmd, name, is_type_ipv4=True): """Make a random subnet pool diff --git a/openstackclient/tests/unit/network/v2/test_subnet_pool.py b/openstackclient/tests/unit/network/v2/test_subnet_pool.py index f12537e7aa..80a57bbb30 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/unit/network/v2/test_subnet_pool.py @@ -270,6 +270,27 @@ def test_create_with_description(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_create_with_default_quota(self): + arglist = [ + '--pool-prefix', '10.0.10.0/24', + '--default-quota', '10', + self._subnet_pool.name, + ] + verifylist = [ + ('prefixes', ['10.0.10.0/24']), + ('default_quota', 10), + ('name', self._subnet_pool.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = (self.cmd.take_action(parsed_args)) + self.network.create_subnet_pool.assert_called_once_with(**{ + 'name': self._subnet_pool.name, + 'prefixes': ['10.0.10.0/24'], + 'default_quota': 10, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + class TestDeleteSubnetPool(TestSubnetPool): @@ -567,7 +588,9 @@ def test_subnet_pool_list_address_scope(self): class TestSetSubnetPool(TestSubnetPool): # The subnet_pool to set. - _subnet_pool = network_fakes.FakeSubnetPool.create_one_subnet_pool() + _subnet_pool = network_fakes.FakeSubnetPool.create_one_subnet_pool( + {'default_quota': 10}, + ) _address_scope = network_fakes.FakeAddressScope.create_one_address_scope() @@ -794,6 +817,23 @@ def test_set_description(self): self._subnet_pool, **attrs) self.assertIsNone(result) + def test_set_with_default_quota(self): + arglist = [ + '--default-quota', '20', + self._subnet_pool.name, + ] + verifylist = [ + ('default_quota', 20), + ('subnet_pool', self._subnet_pool.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.network.update_subnet_pool.assert_called_once_with( + self._subnet_pool, + **{'default_quota': 20, } + ) + self.assertIsNone(result) + class TestShowSubnetPool(TestSubnetPool): diff --git a/releasenotes/notes/bug-1667294-f92efa49627eb00a.yaml b/releasenotes/notes/bug-1667294-f92efa49627eb00a.yaml new file mode 100644 index 0000000000..8f2cd31ffc --- /dev/null +++ b/releasenotes/notes/bug-1667294-f92efa49627eb00a.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--default-quota`` option to ``subnet pool create`` + and ``subnet pool set`` commands. + [Bug `1667294 `_] From 2b66c71a7c2798dc1f9149574d54062b9086a01a Mon Sep 17 00:00:00 2001 From: Hong Hui Xiao Date: Thu, 18 May 2017 22:53:29 +0800 Subject: [PATCH 1701/3095] Don't show hint about vlan transparent in network set Update this attribute is not allowed in neutron. Change-Id: I38010b26e116246c13dbb6cc6a777d2f22f6dc30 Closes-Bug: #1691776 --- doc/source/command-objects/network.rst | 9 ------ openstackclient/network/v2/network.py | 30 +++++++++---------- .../tests/unit/network/v2/test_network.py | 3 -- ...set-vlan-transparent-eeff412264ae7c09.yaml | 7 +++++ 4 files changed, 22 insertions(+), 27 deletions(-) create mode 100644 releasenotes/notes/remove-unsupported-set-vlan-transparent-eeff412264ae7c09.yaml diff --git a/doc/source/command-objects/network.rst b/doc/source/command-objects/network.rst index 636409b90f..ed9fd13d1d 100644 --- a/doc/source/command-objects/network.rst +++ b/doc/source/command-objects/network.rst @@ -318,7 +318,6 @@ Set network properties [--provider-physical-network ] [--provider-segment ] [--qos-policy | --no-qos-policy] - [--transparent-vlan | --no-transparent-vlan] .. option:: --name @@ -393,14 +392,6 @@ Set network properties Remove the QoS policy attached to this network -.. option:: --transparent-vlan - - Make the network VLAN transparent - -.. option:: --no-transparent-vlan - - Do not make the network VLAN transparent - .. _network_set-network: .. describe:: diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index e4cf54bfb2..58ed6841fc 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -127,11 +127,6 @@ def _get_attrs_network(client_manager, parsed_args): 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 - if parsed_args.no_transparent_vlan: - attrs['vlan_transparent'] = False return attrs @@ -170,16 +165,6 @@ def _add_additional_network_options(parser): help=_("VLAN ID for VLAN networks or Tunnel ID for " "GENEVE/GRE/VXLAN networks")) - vlan_transparent_grp = parser.add_mutually_exclusive_group() - vlan_transparent_grp.add_argument( - '--transparent-vlan', - action='store_true', - help=_("Make the network VLAN transparent")) - vlan_transparent_grp.add_argument( - '--no-transparent-vlan', - action='store_true', - help=_("Do not make the network VLAN transparent")) - # TODO(sindhu): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. @@ -282,6 +267,16 @@ def update_parser_network(self, parser): metavar='', help=_("QoS policy to attach to this network (name or ID)") ) + vlan_transparent_grp = parser.add_mutually_exclusive_group() + vlan_transparent_grp.add_argument( + '--transparent-vlan', + action='store_true', + help=_("Make the network VLAN transparent")) + vlan_transparent_grp.add_argument( + '--no-transparent-vlan', + action='store_true', + help=_("Do not make the network VLAN transparent")) + _add_additional_network_options(parser) return parser @@ -296,6 +291,11 @@ def update_parser_compute(self, parser): def take_action_network(self, client, parsed_args): attrs = _get_attrs_network(self.app.client_manager, parsed_args) + if parsed_args.transparent_vlan: + attrs['vlan_transparent'] = True + if parsed_args.no_transparent_vlan: + attrs['vlan_transparent'] = False + obj = client.create_network(**attrs) display_columns, columns = _get_columns_network(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) diff --git a/openstackclient/tests/unit/network/v2/test_network.py b/openstackclient/tests/unit/network/v2/test_network.py index 1bd7bac619..4065e5ca48 100644 --- a/openstackclient/tests/unit/network/v2/test_network.py +++ b/openstackclient/tests/unit/network/v2/test_network.py @@ -821,7 +821,6 @@ def test_set_this(self): '--provider-network-type', 'vlan', '--provider-physical-network', 'physnet1', '--provider-segment', '400', - '--no-transparent-vlan', '--enable-port-security', '--qos-policy', self.qos_policy.name, ] @@ -836,7 +835,6 @@ def test_set_this(self): ('provider_network_type', 'vlan'), ('physical_network', 'physnet1'), ('segmentation_id', '400'), - ('no_transparent_vlan', True), ('enable_port_security', True), ('qos_policy', self.qos_policy.name), ] @@ -854,7 +852,6 @@ def test_set_this(self): 'provider:network_type': 'vlan', 'provider:physical_network': 'physnet1', 'provider:segmentation_id': '400', - 'vlan_transparent': False, 'port_security_enabled': True, 'qos_policy_id': self.qos_policy.id, } diff --git a/releasenotes/notes/remove-unsupported-set-vlan-transparent-eeff412264ae7c09.yaml b/releasenotes/notes/remove-unsupported-set-vlan-transparent-eeff412264ae7c09.yaml new file mode 100644 index 0000000000..36c9cd1edd --- /dev/null +++ b/releasenotes/notes/remove-unsupported-set-vlan-transparent-eeff412264ae7c09.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + Remove ``--transparent-vlan`` and ``--no-transparent-vlan`` + from ``network set``, because updating ``vlan-transparent`` + is not supported in Neutron. + [Bug `1691776 `_] From 227d4c64ef4ac04b5fed6cdff035821bf0e6ae7e Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Thu, 8 Sep 2016 15:13:38 -0700 Subject: [PATCH 1702/3095] Add project purge command to osc See the initial implementation: https://github.com/openstack/ospurge/blob/master/ospurge/client.py Partial-Bug: 1584596 Change-Id: I3aa86af7c85e7ca3b7f04b43e8e07125f7d956d1 --- doc/source/command-objects/project-purge.rst | 42 +++ doc/source/commands.rst | 1 + openstackclient/common/project_purge.py | 168 ++++++++++ .../tests/unit/common/test_project_purge.py | 314 ++++++++++++++++++ .../notes/bug-1584596-5b3109487b451bec.yaml | 5 + setup.cfg | 1 + 6 files changed, 531 insertions(+) create mode 100644 doc/source/command-objects/project-purge.rst create mode 100644 openstackclient/common/project_purge.py create mode 100644 openstackclient/tests/unit/common/test_project_purge.py create mode 100644 releasenotes/notes/bug-1584596-5b3109487b451bec.yaml diff --git a/doc/source/command-objects/project-purge.rst b/doc/source/command-objects/project-purge.rst new file mode 100644 index 0000000000..0ad0bbf969 --- /dev/null +++ b/doc/source/command-objects/project-purge.rst @@ -0,0 +1,42 @@ +============= +project purge +============= + +Clean resources associated with a specific project. + +Block Storage v1, v2; Compute v2; Image v1, v2 + +project purge +------------- + +Clean resources associated with a project + +.. program:: project purge +.. code:: bash + + openstack project purge + [--dry-run] + [--keep-project] + [--auth-project | --project ] + [--project-domain ] + +.. option:: --dry-run + + List a project's resources + +.. option:: --keep-project + + Clean project resources, but don't delete the project. + +.. option:: --auth-project + + Delete resources of the project used to authenticate + +.. option:: --project + + Project to clean (name or ID) + +.. option:: --project-domain + + Domain the project belongs to (name or ID). This can be + used in case collisions between project names exist. diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 4e0a9258e2..ba3e335c7d 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -251,6 +251,7 @@ Those actions with an opposite action are noted in parens if applicable. live server migration if possible * ``pause`` (``unpause``) - stop one or more servers and leave them in memory * ``query`` - Query resources by Elasticsearch query string or json format DSL. +* ``purge`` - clean resources associated with a specific project * ``reboot`` - forcibly reboot a server * ``rebuild`` - rebuild a server using (most of) the same arguments as in the original create * ``remove`` (``add``) - remove an object from a group of objects diff --git a/openstackclient/common/project_purge.py b/openstackclient/common/project_purge.py new file mode 100644 index 0000000000..dff954e725 --- /dev/null +++ b/openstackclient/common/project_purge.py @@ -0,0 +1,168 @@ +# Copyright 2012 OpenStack Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +import logging + +from osc_lib.command import command +from osc_lib import utils + +from openstackclient.i18n import _ +from openstackclient.identity import common as identity_common + + +LOG = logging.getLogger(__name__) + + +class ProjectPurge(command.Command): + _description = _("Clean resources associated with a project") + + def get_parser(self, prog_name): + parser = super(ProjectPurge, self).get_parser(prog_name) + parser.add_argument( + '--dry-run', + action='store_true', + help=_("List a project's resources"), + ) + parser.add_argument( + '--keep-project', + action='store_true', + help=_("Clean project resources, but don't delete the project"), + ) + project_group = parser.add_mutually_exclusive_group(required=True) + project_group.add_argument( + '--auth-project', + action='store_true', + help=_('Delete resources of the project used to authenticate'), + ) + project_group.add_argument( + '--project', + metavar='', + help=_('Project to clean (name or ID)'), + ) + identity_common.add_project_domain_option_to_parser(parser) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + + if parsed_args.auth_project: + project_id = self.app.client_manager.auth_ref.project_id + elif parsed_args.project: + try: + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + except AttributeError: # using v2 auth and supplying a domain + project_id = utils.find_resource( + identity_client.tenants, + parsed_args.project, + ).id + + # delete all non-identity resources + self.delete_resources(parsed_args.dry_run, project_id) + + # clean up the project + if not parsed_args.keep_project: + LOG.warning(_('Deleting project: %s'), project_id) + if not parsed_args.dry_run: + identity_client.projects.delete(project_id) + + def delete_resources(self, dry_run, project_id): + # servers + try: + compute_client = self.app.client_manager.compute + search_opts = {'tenant_id': project_id} + data = compute_client.servers.list(search_opts=search_opts) + self.delete_objects( + compute_client.servers.delete, data, 'server', dry_run) + except Exception: + pass + + # images + try: + image_client = self.app.client_manager.image + data = image_client.images.list(owner=project_id) + self.delete_objects( + image_client.images.delete, data, 'image', dry_run) + except Exception: + pass + + # volumes, snapshots, backups + volume_client = self.app.client_manager.volume + search_opts = {'project_id': project_id} + try: + data = volume_client.volume_snapshots.list(search_opts=search_opts) + self.delete_objects( + self.delete_one_volume_snapshot, + data, + 'volume snapshot', + dry_run) + except Exception: + pass + try: + data = volume_client.backups.list(search_opts=search_opts) + self.delete_objects( + self.delete_one_volume_backup, + data, + 'volume backup', + dry_run) + except Exception: + pass + try: + data = volume_client.volumes.list(search_opts=search_opts) + self.delete_objects( + volume_client.volumes.force_delete, data, 'volume', dry_run) + except Exception: + pass + + def delete_objects(self, func_delete, data, resource, dry_run): + result = 0 + for i in data: + LOG.warning(_('Deleting %(resource)s : %(id)s') % + {'resource': resource, 'id': i.id}) + if not dry_run: + try: + func_delete(i.id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete %(resource)s with " + "ID '%(id)s': %(e)s") + % {'resource': resource, 'id': i.id, 'e': e}) + if result > 0: + total = len(data) + msg = (_("%(result)s of %(total)s %(resource)ss failed " + "to delete.") % + {'result': result, + 'total': total, + 'resource': resource}) + LOG.error(msg) + + def delete_one_volume_snapshot(self, snapshot_id): + volume_client = self.app.client_manager.volume + try: + volume_client.volume_snapshots.delete(snapshot_id) + except Exception: + # Only volume v2 support deleting by force + volume_client.volume_snapshots.delete(snapshot_id, force=True) + + def delete_one_volume_backup(self, backup_id): + volume_client = self.app.client_manager.volume + try: + volume_client.backups.delete(backup_id) + except Exception: + # Only volume v2 support deleting by force + volume_client.backups.delete(backup_id, force=True) diff --git a/openstackclient/tests/unit/common/test_project_purge.py b/openstackclient/tests/unit/common/test_project_purge.py new file mode 100644 index 0000000000..05a8aa3e22 --- /dev/null +++ b/openstackclient/tests/unit/common/test_project_purge.py @@ -0,0 +1,314 @@ +# 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 osc_lib import exceptions + +from openstackclient.common import project_purge +from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes +from openstackclient.tests.unit import 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 import utils as tests_utils +from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes + + +class TestProjectPurgeInit(tests_utils.TestCommand): + + def setUp(self): + super(TestProjectPurgeInit, self).setUp() + compute_client = compute_fakes.FakeComputev2Client( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN, + ) + self.app.client_manager.compute = compute_client + self.servers_mock = compute_client.servers + self.servers_mock.reset_mock() + + volume_client = volume_fakes.FakeVolumeClient( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN, + ) + self.app.client_manager.volume = volume_client + self.volumes_mock = volume_client.volumes + self.volumes_mock.reset_mock() + self.snapshots_mock = volume_client.volume_snapshots + self.snapshots_mock.reset_mock() + self.backups_mock = volume_client.backups + self.backups_mock.reset_mock() + + identity_client = identity_fakes.FakeIdentityv3Client( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN, + ) + self.app.client_manager.identity = identity_client + self.domains_mock = identity_client.domains + self.domains_mock.reset_mock() + self.projects_mock = identity_client.projects + self.projects_mock.reset_mock() + + image_client = image_fakes.FakeImagev2Client( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN, + ) + self.app.client_manager.image = image_client + self.images_mock = image_client.images + self.images_mock.reset_mock() + + +class TestProjectPurge(TestProjectPurgeInit): + + project = identity_fakes.FakeProject.create_one_project() + server = compute_fakes.FakeServer.create_one_server() + image = image_fakes.FakeImage.create_one_image() + volume = volume_fakes.FakeVolume.create_one_volume() + backup = volume_fakes.FakeBackup.create_one_backup() + snapshot = volume_fakes.FakeSnapshot.create_one_snapshot() + + def setUp(self): + super(TestProjectPurge, self).setUp() + self.projects_mock.get.return_value = self.project + self.projects_mock.delete.return_value = None + self.images_mock.list.return_value = [self.image] + self.images_mock.delete.return_value = None + self.servers_mock.list.return_value = [self.server] + self.servers_mock.delete.return_value = None + self.volumes_mock.list.return_value = [self.volume] + self.volumes_mock.delete.return_value = None + self.volumes_mock.force_delete.return_value = None + self.snapshots_mock.list.return_value = [self.snapshot] + self.snapshots_mock.delete.return_value = None + self.backups_mock.list.return_value = [self.backup] + self.backups_mock.delete.return_value = None + + self.cmd = project_purge.ProjectPurge(self.app, None) + + def test_project_no_options(self): + arglist = [] + verifylist = [] + + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_project_purge_with_project(self): + arglist = [ + '--project', self.project.id, + ] + verifylist = [ + ('dry_run', False), + ('keep_project', False), + ('auth_project', False), + ('project', self.project.id), + ('project_domain', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.projects_mock.get.assert_called_once_with(self.project.id) + self.projects_mock.delete.assert_called_once_with(self.project.id) + self.servers_mock.list.assert_called_once_with( + search_opts={'tenant_id': self.project.id}) + self.images_mock.list.assert_called_once_with( + owner=self.project.id) + volume_search_opts = {'project_id': self.project.id} + self.volumes_mock.list.assert_called_once_with( + search_opts=volume_search_opts) + self.snapshots_mock.list.assert_called_once_with( + search_opts=volume_search_opts) + self.backups_mock.list.assert_called_once_with( + search_opts=volume_search_opts) + self.servers_mock.delete.assert_called_once_with(self.server.id) + self.images_mock.delete.assert_called_once_with(self.image.id) + self.volumes_mock.force_delete.assert_called_once_with(self.volume.id) + self.snapshots_mock.delete.assert_called_once_with(self.snapshot.id) + self.backups_mock.delete.assert_called_once_with(self.backup.id) + self.assertIsNone(result) + + def test_project_purge_with_dry_run(self): + arglist = [ + '--dry-run', + '--project', self.project.id, + ] + verifylist = [ + ('dry_run', True), + ('keep_project', False), + ('auth_project', False), + ('project', self.project.id), + ('project_domain', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.projects_mock.get.assert_called_once_with(self.project.id) + self.projects_mock.delete.assert_not_called() + self.servers_mock.list.assert_called_once_with( + search_opts={'tenant_id': self.project.id}) + self.images_mock.list.assert_called_once_with( + owner=self.project.id) + volume_search_opts = {'project_id': self.project.id} + self.volumes_mock.list.assert_called_once_with( + search_opts=volume_search_opts) + self.snapshots_mock.list.assert_called_once_with( + search_opts=volume_search_opts) + self.backups_mock.list.assert_called_once_with( + search_opts=volume_search_opts) + self.servers_mock.delete.assert_not_called() + self.images_mock.delete.assert_not_called() + self.volumes_mock.force_delete.assert_not_called() + self.snapshots_mock.delete.assert_not_called() + self.backups_mock.delete.assert_not_called() + self.assertIsNone(result) + + def test_project_purge_with_keep_project(self): + arglist = [ + '--keep-project', + '--project', self.project.id, + ] + verifylist = [ + ('dry_run', False), + ('keep_project', True), + ('auth_project', False), + ('project', self.project.id), + ('project_domain', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.projects_mock.get.assert_called_once_with(self.project.id) + self.projects_mock.delete.assert_not_called() + self.servers_mock.list.assert_called_once_with( + search_opts={'tenant_id': self.project.id}) + self.images_mock.list.assert_called_once_with( + owner=self.project.id) + volume_search_opts = {'project_id': self.project.id} + self.volumes_mock.list.assert_called_once_with( + search_opts=volume_search_opts) + self.snapshots_mock.list.assert_called_once_with( + search_opts=volume_search_opts) + self.backups_mock.list.assert_called_once_with( + search_opts=volume_search_opts) + self.servers_mock.delete.assert_called_once_with(self.server.id) + self.images_mock.delete.assert_called_once_with(self.image.id) + self.volumes_mock.force_delete.assert_called_once_with(self.volume.id) + self.snapshots_mock.delete.assert_called_once_with(self.snapshot.id) + self.backups_mock.delete.assert_called_once_with(self.backup.id) + self.assertIsNone(result) + + def test_project_purge_with_auth_project(self): + self.app.client_manager.auth_ref = mock.Mock() + self.app.client_manager.auth_ref.project_id = self.project.id + arglist = [ + '--auth-project', + ] + verifylist = [ + ('dry_run', False), + ('keep_project', False), + ('auth_project', True), + ('project', None), + ('project_domain', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.projects_mock.get.assert_not_called() + self.projects_mock.delete.assert_called_once_with(self.project.id) + self.servers_mock.list.assert_called_once_with( + search_opts={'tenant_id': self.project.id}) + self.images_mock.list.assert_called_once_with( + owner=self.project.id) + volume_search_opts = {'project_id': self.project.id} + self.volumes_mock.list.assert_called_once_with( + search_opts=volume_search_opts) + self.snapshots_mock.list.assert_called_once_with( + search_opts=volume_search_opts) + self.backups_mock.list.assert_called_once_with( + search_opts=volume_search_opts) + self.servers_mock.delete.assert_called_once_with(self.server.id) + self.images_mock.delete.assert_called_once_with(self.image.id) + self.volumes_mock.force_delete.assert_called_once_with(self.volume.id) + self.snapshots_mock.delete.assert_called_once_with(self.snapshot.id) + self.backups_mock.delete.assert_called_once_with(self.backup.id) + self.assertIsNone(result) + + @mock.patch.object(project_purge.LOG, 'error') + def test_project_purge_with_exception(self, mock_error): + self.servers_mock.delete.side_effect = exceptions.CommandError() + arglist = [ + '--project', self.project.id, + ] + verifylist = [ + ('dry_run', False), + ('keep_project', False), + ('auth_project', False), + ('project', self.project.id), + ('project_domain', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.projects_mock.get.assert_called_once_with(self.project.id) + self.projects_mock.delete.assert_called_once_with(self.project.id) + self.servers_mock.list.assert_called_once_with( + search_opts={'tenant_id': self.project.id}) + self.images_mock.list.assert_called_once_with( + owner=self.project.id) + volume_search_opts = {'project_id': self.project.id} + self.volumes_mock.list.assert_called_once_with( + search_opts=volume_search_opts) + self.snapshots_mock.list.assert_called_once_with( + search_opts=volume_search_opts) + self.backups_mock.list.assert_called_once_with( + search_opts=volume_search_opts) + self.servers_mock.delete.assert_called_once_with(self.server.id) + self.images_mock.delete.assert_called_once_with(self.image.id) + self.volumes_mock.force_delete.assert_called_once_with(self.volume.id) + self.snapshots_mock.delete.assert_called_once_with(self.snapshot.id) + self.backups_mock.delete.assert_called_once_with(self.backup.id) + mock_error.assert_called_with("1 of 1 servers failed to delete.") + self.assertIsNone(result) + + def test_project_purge_with_force_delete_backup(self): + self.backups_mock.delete.side_effect = [exceptions.CommandError, None] + arglist = [ + '--project', self.project.id, + ] + verifylist = [ + ('dry_run', False), + ('keep_project', False), + ('auth_project', False), + ('project', self.project.id), + ('project_domain', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.projects_mock.get.assert_called_once_with(self.project.id) + self.projects_mock.delete.assert_called_once_with(self.project.id) + self.servers_mock.list.assert_called_once_with( + search_opts={'tenant_id': self.project.id}) + self.images_mock.list.assert_called_once_with( + owner=self.project.id) + volume_search_opts = {'project_id': self.project.id} + self.volumes_mock.list.assert_called_once_with( + search_opts=volume_search_opts) + self.snapshots_mock.list.assert_called_once_with( + search_opts=volume_search_opts) + self.backups_mock.list.assert_called_once_with( + search_opts=volume_search_opts) + self.servers_mock.delete.assert_called_once_with(self.server.id) + self.images_mock.delete.assert_called_once_with(self.image.id) + self.volumes_mock.force_delete.assert_called_once_with(self.volume.id) + self.snapshots_mock.delete.assert_called_once_with(self.snapshot.id) + self.assertEqual(2, self.backups_mock.delete.call_count) + self.backups_mock.delete.assert_called_with(self.backup.id, force=True) + self.assertIsNone(result) diff --git a/releasenotes/notes/bug-1584596-5b3109487b451bec.yaml b/releasenotes/notes/bug-1584596-5b3109487b451bec.yaml new file mode 100644 index 0000000000..9d45713d02 --- /dev/null +++ b/releasenotes/notes/bug-1584596-5b3109487b451bec.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Add command ``openstack project purge`` to clean a project's resources. + [Bug `1584596 `_] diff --git a/setup.cfg b/setup.cfg index 99201b9e34..86f315c139 100644 --- a/setup.cfg +++ b/setup.cfg @@ -47,6 +47,7 @@ openstack.common = extension_list = openstackclient.common.extension:ListExtension extension_show = openstackclient.common.extension:ShowExtension limits_show = openstackclient.common.limits:ShowLimits + project_purge = openstackclient.common.project_purge:ProjectPurge quota_list = openstackclient.common.quota:ListQuota quota_set = openstackclient.common.quota:SetQuota quota_show = openstackclient.common.quota:ShowQuota From 7653cff5e6f9592332a23e452ad6bf1549542b16 Mon Sep 17 00:00:00 2001 From: chenying Date: Tue, 13 Jun 2017 11:52:57 +0800 Subject: [PATCH 1703/3095] Add support for Karbor Plugin The patch[1] about OSC plugin has been submitted to karbor project. And the plan list commend has been implemented. With more dataprotection commands being supported, another patch adding more commands to the docs will be proposed later. [1] https://review.openstack.org/#/c/473508/ Depends-On: I4dfac08fd2b04f9ac254d3aa8fdadc3a1691de0a Change-Id: I2266525650f5c2e241373493dcd09474478c2ba6 --- doc/source/commands.rst | 1 + doc/source/plugin-commands.rst | 6 ++++++ doc/source/plugins.rst | 1 + test-requirements.txt | 1 + 4 files changed, 9 insertions(+) diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 4e0a9258e2..f4f046e360 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -191,6 +191,7 @@ list check out :doc:`plugin-commands`. * ``dataprocessing image``: (**Data Processing (Sahara)**) * ``dataprocessing image tags``: (**Data Processing (Sahara)**) * ``dataprocessing plugin``: (**Data Processing (Sahara)**) +* ``data protection plan``: (**Data Protection (Karbor)**) * ``message-broker cluster``: (**Message Broker (Cue)**) * ``messaging``: (**Messaging (Zaqar)**) * ``messaging flavor``: (**Messaging (Zaqar)**) diff --git a/doc/source/plugin-commands.rst b/doc/source/plugin-commands.rst index 69d31c22b3..ee162f76e7 100644 --- a/doc/source/plugin-commands.rst +++ b/doc/source/plugin-commands.rst @@ -56,6 +56,12 @@ ironic-inspector .. list-plugins:: openstack.baremetal_introspection.v1 :detailed: +karbor +------ + +.. list-plugins:: openstack.data_protection.v1 + :detailed: + mistral ------- diff --git a/doc/source/plugins.rst b/doc/source/plugins.rst index 7dda52a034..058819a60c 100644 --- a/doc/source/plugins.rst +++ b/doc/source/plugins.rst @@ -29,6 +29,7 @@ The following is a list of projects that are an OpenStackClient plugin. - python-heatclient - python-ironicclient - python-ironic-inspector-client +- python-karborclient - python-mistralclient - python-muranoclient - python-neutronclient\*\*\* diff --git a/test-requirements.txt b/test-requirements.txt index 5c9a80c46b..58247585ec 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -31,6 +31,7 @@ python-designateclient>=1.5.0 # Apache-2.0 python-heatclient>=1.6.1 # Apache-2.0 python-ironicclient>=1.11.0 # Apache-2.0 python-ironic-inspector-client>=1.5.0 # Apache-2.0 +python-karborclient>=0.2.0 # Apache-2.0 python-mistralclient>=3.1.0 # Apache-2.0 python-muranoclient>=0.8.2 # Apache-2.0 python-neutronclient>=6.3.0 # Apache-2.0 From 4c2eb2bd8b9ba16dba1eb3707f64c0ea4f447e91 Mon Sep 17 00:00:00 2001 From: M V P Nitesh Date: Thu, 22 Jun 2017 12:56:26 +0530 Subject: [PATCH 1704/3095] Show neutron tags in OSC network show Now tag information of the network is displayed when the user tries to see the network details using the command openstack network show Change-Id: I587d2bca37b8dbef4400db3d8ace3c81d87e2db3 Closes-Bug: #1695783 --- openstackclient/network/v2/network.py | 2 ++ openstackclient/tests/unit/network/v2/fakes.py | 2 ++ openstackclient/tests/unit/network/v2/test_network.py | 6 ++++++ 3 files changed, 10 insertions(+) diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index e4cf54bfb2..e3737895ba 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -39,6 +39,7 @@ def _format_router_external(item): 'is_router_external': _format_router_external, 'availability_zones': utils.format_list, 'availability_zone_hints': utils.format_list, + 'tags': utils.format_list, } @@ -55,6 +56,7 @@ def _get_columns_network(item): 'ipv4_address_scope_id': 'ipv4_address_scope', 'ipv6_address_scope_id': 'ipv6_address_scope', 'tenant_id': 'project_id', + 'tags': 'tags', } return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index e736b0fdd1..69f28ee36d 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -346,6 +346,7 @@ def create_one_network(attrs=None): 'availability_zone_hints': [], 'is_default': False, 'port_security_enabled': True, + 'tags': ['test'], 'qos_policy_id': 'qos-policy-id-' + uuid.uuid4().hex, 'ipv4_address_scope': 'ipv4' + uuid.uuid4().hex, 'ipv6_address_scope': 'ipv6' + uuid.uuid4().hex, @@ -365,6 +366,7 @@ def create_one_network(attrs=None): network_attrs['port_security_enabled'] network.subnet_ids = network_attrs['subnets'] network.is_shared = network_attrs['shared'] + network.is_tags = network_attrs['tags'] network.provider_network_type = \ network_attrs['provider:network_type'] network.provider_physical_network = \ diff --git a/openstackclient/tests/unit/network/v2/test_network.py b/openstackclient/tests/unit/network/v2/test_network.py index 1bd7bac619..af2d08ba18 100644 --- a/openstackclient/tests/unit/network/v2/test_network.py +++ b/openstackclient/tests/unit/network/v2/test_network.py @@ -75,6 +75,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): 'shared', 'status', 'subnets', + 'tags', ) data = ( @@ -97,6 +98,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): _network.shared, _network.status, utils.format_list(_network.subnets), + utils.format_list(_network.tags), ) def setUp(self): @@ -255,6 +257,7 @@ class TestCreateNetworkIdentityV2(TestNetwork): 'shared', 'status', 'subnets', + 'tags', ) data = ( @@ -277,6 +280,7 @@ class TestCreateNetworkIdentityV2(TestNetwork): _network.shared, _network.status, utils.format_list(_network.subnets), + utils.format_list(_network.tags), ) def setUp(self): @@ -932,6 +936,7 @@ class TestShowNetwork(TestNetwork): 'shared', 'status', 'subnets', + 'tags', ) data = ( @@ -954,6 +959,7 @@ class TestShowNetwork(TestNetwork): _network.shared, _network.status, utils.format_list(_network.subnets), + utils.format_list(_network.tags), ) def setUp(self): From ccb743cf0fd96df93f28d526a0d39b5f87c981ef Mon Sep 17 00:00:00 2001 From: Rodolfo Alonso Hernandez Date: Wed, 19 Apr 2017 10:07:40 +0000 Subject: [PATCH 1705/3095] Add direction field to QoS bandwidth limit. This patch enables the direction ('ingress'/'egress') field on the QoS bandwidth limit rule object and CRUD commands. Closes-Bug: #1614121 Depends-On: Ia13568879c2b6f80fb190ccafe7e19ca05b0c6a8 Depends-On: I90c412a5c8757b3ffe8abfc1165a70bdb8744702 Change-Id: Ic6981474f22efbf294ac11c2e0304b04494a1bbe --- .../network/v2/network_qos_rule.py | 32 +++++---- .../network/v2/test_network_qos_rule.py | 16 +++-- .../tests/unit/network/v2/fakes.py | 1 + .../unit/network/v2/test_network_qos_rule.py | 70 +++++++++++++++---- ...rk-qos-bw-limit-rule-a3c5b6892074d5ae.yaml | 8 +++ 5 files changed, 93 insertions(+), 34 deletions(-) create mode 100644 releasenotes/notes/add-direction-to-network-qos-bw-limit-rule-a3c5b6892074d5ae.yaml diff --git a/openstackclient/network/v2/network_qos_rule.py b/openstackclient/network/v2/network_qos_rule.py index baed042462..f50e58b32d 100644 --- a/openstackclient/network/v2/network_qos_rule.py +++ b/openstackclient/network/v2/network_qos_rule.py @@ -14,7 +14,6 @@ # under the License. import itertools -import six from osc_lib.command import command from osc_lib import exceptions @@ -27,10 +26,14 @@ RULE_TYPE_BANDWIDTH_LIMIT = 'bandwidth-limit' RULE_TYPE_DSCP_MARKING = 'dscp-marking' RULE_TYPE_MINIMUM_BANDWIDTH = 'minimum-bandwidth' -REQUIRED_PARAMETERS = { - RULE_TYPE_MINIMUM_BANDWIDTH: ['min_kbps', 'direction'], - RULE_TYPE_DSCP_MARKING: ['dscp_mark'], - RULE_TYPE_BANDWIDTH_LIMIT: ['max_kbps', 'max_burst_kbps']} +MANDATORY_PARAMETERS = { + RULE_TYPE_MINIMUM_BANDWIDTH: {'min_kbps', 'direction'}, + RULE_TYPE_DSCP_MARKING: {'dscp_mark'}, + RULE_TYPE_BANDWIDTH_LIMIT: {'max_kbps', 'max_burst_kbps'}} +OPTIONAL_PARAMETERS = { + RULE_TYPE_MINIMUM_BANDWIDTH: set(), + RULE_TYPE_DSCP_MARKING: set(), + RULE_TYPE_BANDWIDTH_LIMIT: {'direction'}} DIRECTION_EGRESS = 'egress' DIRECTION_INGRESS = 'ingress' DSCP_VALID_MARKS = [0, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, @@ -51,17 +54,20 @@ def _get_columns(item): def _check_type_parameters(attrs, type, is_create): - req_params = REQUIRED_PARAMETERS[type] - notreq_params = list(itertools.chain( - *[v for k, v in six.iteritems(REQUIRED_PARAMETERS) if k != type])) + req_params = MANDATORY_PARAMETERS[type] + opt_params = OPTIONAL_PARAMETERS[type] + type_params = req_params | opt_params + notreq_params = set(itertools.chain( + *[v for k, v in MANDATORY_PARAMETERS.items() if k != type])) + notreq_params -= type_params if is_create and None in map(attrs.get, req_params): msg = (_('"Create" rule command for type "%(rule_type)s" requires ' - 'arguments %(args)s') % {'rule_type': type, - 'args': ", ".join(req_params)}) + 'arguments %(args)s') % + {'rule_type': type, 'args': ", ".join(sorted(req_params))}) raise exceptions.CommandError(msg) - if set(six.iterkeys(attrs)) & set(notreq_params): + if set(attrs.keys()) & notreq_params: msg = (_('Rule type "%(rule_type)s" only requires arguments %(args)s') - % {'rule_type': type, 'args': ", ".join(req_params)}) + % {'rule_type': type, 'args': ", ".join(sorted(type_params))}) raise exceptions.CommandError(msg) @@ -183,7 +189,7 @@ def get_parser(self, prog_name): RULE_TYPE_DSCP_MARKING, RULE_TYPE_BANDWIDTH_LIMIT], help=(_('QoS rule type (%s)') % - ", ".join(six.iterkeys(REQUIRED_PARAMETERS))) + ", ".join(MANDATORY_PARAMETERS.keys())) ) _add_rule_arguments(parser) return parser diff --git a/openstackclient/tests/functional/network/v2/test_network_qos_rule.py b/openstackclient/tests/functional/network/v2/test_network_qos_rule.py index c6437d9c0e..056bc1f678 100644 --- a/openstackclient/tests/functional/network/v2/test_network_qos_rule.py +++ b/openstackclient/tests/functional/network/v2/test_network_qos_rule.py @@ -169,6 +169,8 @@ class NetworkQosRuleTestsBandwidthLimit(common.NetworkTests): MAX_KBPS_MODIFIED = 15000 MAX_BURST_KBITS = 1400 MAX_BURST_KBITS_MODIFIED = 1800 + RULE_DIRECTION = 'egress' + RULE_DIRECTION_MODIFIED = 'ingress' HEADERS = ['ID'] FIELDS = ['id'] TYPE = 'bandwidth-limit' @@ -187,6 +189,7 @@ def setUpClass(cls): '--type ' + cls.TYPE + ' ' + '--max-kbps ' + str(cls.MAX_KBPS) + ' ' + '--max-burst-kbits ' + str(cls.MAX_BURST_KBITS) + ' ' + + '--' + cls.RULE_DIRECTION + ' ' + cls.QOS_POLICY_NAME + opts ) @@ -226,14 +229,13 @@ def test_qos_rule_set(self): self.openstack('network qos rule set --max-kbps ' + str(self.MAX_KBPS_MODIFIED) + ' --max-burst-kbits ' + str(self.MAX_BURST_KBITS_MODIFIED) + ' ' + + '--' + self.RULE_DIRECTION_MODIFIED + ' ' + self.QOS_POLICY_NAME + ' ' + self.RULE_ID) - opts = self.get_opts(['max_kbps']) + opts = self.get_opts(['direction', 'max_burst_kbps', 'max_kbps']) raw_output = self.openstack('network qos rule show ' + self.QOS_POLICY_NAME + ' ' + self.RULE_ID + opts) - self.assertEqual(str(self.MAX_KBPS_MODIFIED) + "\n", raw_output) - opts = self.get_opts(['max_burst_kbps']) - raw_output = self.openstack('network qos rule show ' + - self.QOS_POLICY_NAME + ' ' + self.RULE_ID + - opts) - self.assertEqual(str(self.MAX_BURST_KBITS_MODIFIED) + "\n", raw_output) + expected = (str(self.RULE_DIRECTION_MODIFIED) + "\n" + + str(self.MAX_BURST_KBITS_MODIFIED) + "\n" + + str(self.MAX_KBPS_MODIFIED) + "\n") + self.assertEqual(expected, raw_output) diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index e736b0fdd1..32b30a7947 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -921,6 +921,7 @@ def create_one_qos_rule(attrs=None): if type == RULE_TYPE_BANDWIDTH_LIMIT: qos_rule_attrs['max_kbps'] = randint(1, 10000) qos_rule_attrs['max_burst_kbits'] = randint(1, 10000) + qos_rule_attrs['direction'] = 'egress' elif type == RULE_TYPE_DSCP_MARKING: qos_rule_attrs['dscp_mark'] = choice(VALID_DSCP_MARKS) elif type == RULE_TYPE_MINIMUM_BANDWIDTH: diff --git a/openstackclient/tests/unit/network/v2/test_network_qos_rule.py b/openstackclient/tests/unit/network/v2/test_network_qos_rule.py index 41ccae32a7..176bc86d2c 100644 --- a/openstackclient/tests/unit/network/v2/test_network_qos_rule.py +++ b/openstackclient/tests/unit/network/v2/test_network_qos_rule.py @@ -127,7 +127,7 @@ def test_create_wrong_options(self): self.cmd.take_action(parsed_args) except exceptions.CommandError as e: msg = ('"Create" rule command for type "minimum-bandwidth" ' - 'requires arguments min_kbps, direction') + 'requires arguments direction, min_kbps') self.assertEqual(msg, str(e)) @@ -229,6 +229,7 @@ def setUp(self): self.new_rule = network_fakes.FakeNetworkQosRule.create_one_qos_rule( attrs) self.columns = ( + 'direction', 'id', 'max_burst_kbits', 'max_kbps', @@ -238,6 +239,7 @@ def setUp(self): ) self.data = ( + self.new_rule.direction, self.new_rule.id, self.new_rule.max_burst_kbits, self.new_rule.max_kbps, @@ -265,6 +267,7 @@ def test_create_default_options(self): '--type', RULE_TYPE_BANDWIDTH_LIMIT, '--max-kbps', str(self.new_rule.max_kbps), '--max-burst-kbits', str(self.new_rule.max_burst_kbits), + '--egress', self.new_rule.qos_policy_id, ] @@ -272,6 +275,7 @@ def test_create_default_options(self): ('type', RULE_TYPE_BANDWIDTH_LIMIT), ('max_kbps', self.new_rule.max_kbps), ('max_burst_kbits', self.new_rule.max_burst_kbits), + ('egress', True), ('qos_policy', self.new_rule.qos_policy_id), ] @@ -281,7 +285,8 @@ def test_create_default_options(self): self.network.create_qos_bandwidth_limit_rule.assert_called_once_with( self.qos_policy.id, **{'max_kbps': self.new_rule.max_kbps, - 'max_burst_kbps': self.new_rule.max_burst_kbits} + 'max_burst_kbps': self.new_rule.max_burst_kbits, + 'direction': self.new_rule.direction} ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -304,7 +309,7 @@ def test_create_wrong_options(self): self.cmd.take_action(parsed_args) except exceptions.CommandError as e: msg = ('"Create" rule command for type "bandwidth-limit" ' - 'requires arguments max_kbps, max_burst_kbps') + 'requires arguments max_burst_kbps, max_kbps') self.assertEqual(msg, str(e)) @@ -574,8 +579,8 @@ def test_set_wrong_options(self): self.cmd.take_action(parsed_args) except exceptions.CommandError as e: msg = ('Failed to set Network QoS rule ID "%(rule)s": Rule type ' - '"minimum-bandwidth" only requires arguments min_kbps, ' - 'direction' % {'rule': self.new_rule.id}) + '"minimum-bandwidth" only requires arguments direction, ' + 'min_kbps' % {'rule': self.new_rule.id}) self.assertEqual(msg, str(e)) @@ -716,9 +721,13 @@ def test_set_max_kbps(self): def test_set_max_kbps_to_zero(self): self._set_max_kbps(max_kbps=0) + def _reset_max_kbps(self, max_kbps): + self.new_rule.max_kbps = max_kbps + def _set_max_kbps(self, max_kbps=None): if max_kbps: - previous_max_kbps = self.new_rule.max_kbps + self.addCleanup(self._reset_max_kbps, + self.new_rule.max_kbps) self.new_rule.max_kbps = max_kbps arglist = [ @@ -742,18 +751,19 @@ def _set_max_kbps(self, max_kbps=None): self.new_rule, self.qos_policy.id, **attrs) self.assertIsNone(result) - if max_kbps: - self.new_rule.max_kbps = previous_max_kbps - def test_set_max_burst_kbits(self): self._set_max_burst_kbits() def test_set_max_burst_kbits_to_zero(self): self._set_max_burst_kbits(max_burst_kbits=0) + def _reset_max_burst_kbits(self, max_burst_kbits): + self.new_rule.max_burst_kbits = max_burst_kbits + def _set_max_burst_kbits(self, max_burst_kbits=None): if max_burst_kbits: - previous_max_burst_kbits = self.new_rule.max_burst_kbits + self.addCleanup(self._reset_max_burst_kbits, + self.new_rule.max_burst_kbits) self.new_rule.max_burst_kbits = max_burst_kbits arglist = [ @@ -777,8 +787,38 @@ def _set_max_burst_kbits(self, max_burst_kbits=None): self.new_rule, self.qos_policy.id, **attrs) self.assertIsNone(result) - if max_burst_kbits: - self.new_rule.max_burst_kbits = previous_max_burst_kbits + def test_set_direction_egress(self): + self._set_direction('egress') + + def test_set_direction_ingress(self): + self._set_direction('ingress') + + def _reset_direction(self, direction): + self.new_rule.direction = direction + + def _set_direction(self, direction): + self.addCleanup(self._reset_direction, self.new_rule.direction) + + arglist = [ + '--%s' % direction, + self.new_rule.qos_policy_id, + self.new_rule.id, + ] + verifylist = [ + (direction, True), + ('qos_policy', self.new_rule.qos_policy_id), + ('id', self.new_rule.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = { + 'direction': direction, + } + self.network.update_qos_bandwidth_limit_rule.assert_called_with( + self.new_rule, self.qos_policy.id, **attrs) + self.assertIsNone(result) def test_set_wrong_options(self): arglist = [ @@ -797,8 +837,8 @@ def test_set_wrong_options(self): self.cmd.take_action(parsed_args) except exceptions.CommandError as e: msg = ('Failed to set Network QoS rule ID "%(rule)s": Rule type ' - '"bandwidth-limit" only requires arguments max_kbps, ' - 'max_burst_kbps' % {'rule': self.new_rule.id}) + '"bandwidth-limit" only requires arguments direction, ' + 'max_burst_kbps, max_kbps' % {'rule': self.new_rule.id}) self.assertEqual(msg, str(e)) @@ -999,6 +1039,7 @@ def setUp(self): attrs) self.qos_policy.rules = [self.new_rule] self.columns = ( + 'direction', 'id', 'max_burst_kbits', 'max_kbps', @@ -1007,6 +1048,7 @@ def setUp(self): 'type' ) self.data = ( + self.new_rule.direction, self.new_rule.id, self.new_rule.max_burst_kbits, self.new_rule.max_kbps, diff --git a/releasenotes/notes/add-direction-to-network-qos-bw-limit-rule-a3c5b6892074d5ae.yaml b/releasenotes/notes/add-direction-to-network-qos-bw-limit-rule-a3c5b6892074d5ae.yaml new file mode 100644 index 0000000000..43a8e56dce --- /dev/null +++ b/releasenotes/notes/add-direction-to-network-qos-bw-limit-rule-a3c5b6892074d5ae.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Added directionality to Network QoS rule type ``bandwidth-limit`` in + ``network qos rule create`` and ``network qos rule set`` commands. + This makes the options ``--egress`` and ``--ingress`` valid for the ``bandwidth-limit`` + rule type. + [Bug `1614121 `_] From da53c2b33457f4f1e93bdda6c0c16172ea36bc78 Mon Sep 17 00:00:00 2001 From: Kristi Nikolla Date: Fri, 16 Jun 2017 15:33:46 -0400 Subject: [PATCH 1706/3095] When creating a trust, send role_ids instead or role_names This changes create a trust to use ids instead of names because of the possibility of roles sharing a name. Even if the user uniquely identified a role by inputting the id, the request sent to the identity service would used the name, therefore the command would fail in the case that two roles share a name. This does not change how trusts are displayed during trust list or trust show, a name will still be shown instead of an id. Depends-On: I38e0ac35946ee6e53128babac3ea759a380572e0 Change-Id: I5bdf89f1e288954a7f5c2704231f270bc7d196f5 Closes-Bug: 1696111 --- openstackclient/identity/v3/trust.py | 12 ++++++------ openstackclient/tests/unit/identity/v3/test_trust.py | 2 +- releasenotes/notes/bug-1696111-e2cf9233fa872eb7.yaml | 7 +++++++ 3 files changed, 14 insertions(+), 7 deletions(-) create mode 100644 releasenotes/notes/bug-1696111-e2cf9233fa872eb7.yaml diff --git a/openstackclient/identity/v3/trust.py b/openstackclient/identity/v3/trust.py index 52daeb4d16..155063bb39 100644 --- a/openstackclient/identity/v3/trust.py +++ b/openstackclient/identity/v3/trust.py @@ -104,16 +104,16 @@ def take_action(self, parsed_args): parsed_args.project, parsed_args.project_domain).id - role_names = [] + role_ids = [] for role in parsed_args.role: try: - role_name = utils.find_resource( + role_id = utils.find_resource( identity_client.roles, role, - ).name + ).id except identity_exc.Forbidden: - role_name = role - role_names.append(role_name) + role_id = role + role_ids.append(role_id) expires_at = None if parsed_args.expiration: @@ -124,7 +124,7 @@ def take_action(self, parsed_args): trustee_id, trustor_id, impersonation=parsed_args.impersonate, project=project_id, - role_names=role_names, + role_ids=role_ids, expires_at=expires_at, ) diff --git a/openstackclient/tests/unit/identity/v3/test_trust.py b/openstackclient/tests/unit/identity/v3/test_trust.py index 93e8f63da1..614aab5470 100644 --- a/openstackclient/tests/unit/identity/v3/test_trust.py +++ b/openstackclient/tests/unit/identity/v3/test_trust.py @@ -94,7 +94,7 @@ def test_trust_create_basic(self): kwargs = { 'impersonation': False, 'project': identity_fakes.project_id, - 'role_names': [identity_fakes.role_name], + 'role_ids': [identity_fakes.role_id], 'expires_at': None, } # TrustManager.create(trustee_id, trustor_id, impersonation=, diff --git a/releasenotes/notes/bug-1696111-e2cf9233fa872eb7.yaml b/releasenotes/notes/bug-1696111-e2cf9233fa872eb7.yaml new file mode 100644 index 0000000000..50154a8854 --- /dev/null +++ b/releasenotes/notes/bug-1696111-e2cf9233fa872eb7.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + Fixed an issue where a trust could not be created if multiple roles had + the same name. A role's ID is now sent to the identity service instead. + + [Bug '1696111 '_] From fcafd987b0143a08eff964f1e988a3aaa40ad824 Mon Sep 17 00:00:00 2001 From: M V P Nitesh Date: Wed, 14 Jun 2017 18:29:08 +0530 Subject: [PATCH 1707/3095] Now OSC server create check keys in --nic Now OSC command to create server will check all the keys in --nic and throws an exception if the key is invalid key. Change-Id: I5482da0ae63d6d4298aa614e4d09bb0547da9ec3 Closes-Bug: #1681411 --- openstackclient/compute/v2/server.py | 16 +++-- .../tests/unit/compute/v2/test_server.py | 60 +++++++++++++++++++ 2 files changed, 70 insertions(+), 6 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index e8846d1619..cd321aa6c1 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -651,12 +651,16 @@ def take_action(self, parsed_args): else: nic_info = {"net-id": "", "v4-fixed-ip": "", "v6-fixed-ip": "", "port-id": ""} - try: - nic_info.update(dict(kv_str.split("=", 1) - for kv_str in nic_str.split(","))) - except ValueError: - msg = _('Invalid --nic argument %s.') % nic_str - raise exceptions.CommandError(msg) + for kv_str in nic_str.split(","): + k, sep, v = kv_str.partition("=") + if k in nic_info and v: + nic_info[k] = v + else: + msg = (_("Invalid nic argument '%s'. Nic arguments " + "must be of the form --nic .")) + raise exceptions.CommandError(msg % k) if bool(nic_info["net-id"]) == bool(nic_info["port-id"]): msg = _("either network or port should be specified " "but not both") diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 084171ac1c..bd1f441001 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -795,6 +795,66 @@ def test_server_create_with_invalid_network_options(self): self.cmd.take_action, parsed_args) self.assertNotCalled(self.servers_mock.create) + def test_server_create_with_invalid_network_key(self): + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--nic', 'abcdefgh=12324', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('nic', ['abcdefgh=12324']), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, parsed_args) + self.assertNotCalled(self.servers_mock.create) + + def test_server_create_with_empty_network_key_value(self): + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--nic', 'net-id=', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('nic', ['net-id=']), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, parsed_args) + self.assertNotCalled(self.servers_mock.create) + + def test_server_create_with_only_network_key(self): + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--nic', 'net-id', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('nic', ['net-id']), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, parsed_args) + self.assertNotCalled(self.servers_mock.create) + @mock.patch.object(common_utils, 'wait_for_status', return_value=True) def test_server_create_with_wait_ok(self, mock_wait_for_status): arglist = [ From 20c23d8ccbdf14d2d97b039d6a8cca62f4b04873 Mon Sep 17 00:00:00 2001 From: blue55 Date: Thu, 22 Jun 2017 14:51:37 +0800 Subject: [PATCH 1708/3095] Enable some off-by-default checks Some of the available checks are disabled by default, like: [H203] Use assertIs(Not)None to check for None Change-Id: I59dafb62cedc5217b6e5eb6efb997a9ee3c29bbb --- openstackclient/tests/functional/volume/v1/test_service.py | 5 +---- openstackclient/tests/functional/volume/v2/test_service.py | 3 +-- tox.ini | 2 ++ 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/openstackclient/tests/functional/volume/v1/test_service.py b/openstackclient/tests/functional/volume/v1/test_service.py index c921c46aaa..fee73f189c 100644 --- a/openstackclient/tests/functional/volume/v1/test_service.py +++ b/openstackclient/tests/functional/volume/v1/test_service.py @@ -64,10 +64,7 @@ def test_volume_service_set(self): 'enabled', cmd_output[0]['Status'] ) - self.assertEqual( - None, - cmd_output[0]['Disabled Reason'] - ) + self.assertIsNone(cmd_output[0]['Disabled Reason']) # Test volume service set --disable and --disable-reason disable_reason = 'disable_reason' diff --git a/openstackclient/tests/functional/volume/v2/test_service.py b/openstackclient/tests/functional/volume/v2/test_service.py index 8d1944e404..6986fde69b 100644 --- a/openstackclient/tests/functional/volume/v2/test_service.py +++ b/openstackclient/tests/functional/volume/v2/test_service.py @@ -64,8 +64,7 @@ def test_volume_service_set(self): 'enabled', cmd_output[0]['Status'] ) - self.assertEqual( - None, + self.assertIsNone( cmd_output[0]['Disabled Reason'] ) diff --git a/tox.ini b/tox.ini index ac5c6593fe..0f22650a9a 100644 --- a/tox.ini +++ b/tox.ini @@ -85,6 +85,8 @@ commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasen [flake8] show-source = True +# H203: Use assertIs(Not)None to check for None +enable-extensions = H203 exclude = .git,.tox,dist,doc,*lib/python*,*egg,build,tools # If 'ignore' is not set there are default errors and warnings that are set # Doc: http://flake8.readthedocs.org/en/latest/config.html#default From 9599ffe65d9dcd4b3aa780d346eccd1e760890bf Mon Sep 17 00:00:00 2001 From: Doug Hellmann Date: Tue, 13 Jun 2017 15:55:33 -0400 Subject: [PATCH 1709/3095] reorganize existing documentation according to the new standard layout Move existing content around based on the doc-migration specification. Replace :doc: markup with :ref: to have sphinx keep track of where the files move and generate valid hyperlinks. Add a few toctrees and index pages for the new directories. Depends-On: Ia750cb049c0f53a234ea70ce1f2bbbb7a2aa9454 Change-Id: I253ee8f89d3ec40e39310c18bb87ed1d3d5de330 Signed-off-by: Doug Hellmann --- doc/source/{ => cli}/authentication.rst | 2 + .../{ => cli}/backwards-incompatible.rst | 4 +- doc/source/{ => cli}/command-list.rst | 2 + .../command-objects/access-token.rst | 0 .../command-objects/address-scope.rst | 0 .../{ => cli}/command-objects/aggregate.rst | 0 .../command-objects/availability-zone.rst | 0 .../{ => cli}/command-objects/backup.rst | 0 .../{ => cli}/command-objects/catalog.rst | 0 .../{ => cli}/command-objects/command.rst | 0 .../{ => cli}/command-objects/complete.rst | 0 .../command-objects/compute-agent.rst | 0 .../command-objects/compute-service.rst | 0 .../command-objects/configuration.rst | 2 + .../consistency-group-snapshot.rst | 0 .../command-objects/consistency-group.rst | 0 .../{ => cli}/command-objects/console-log.rst | 0 .../{ => cli}/command-objects/console-url.rst | 0 .../{ => cli}/command-objects/consumer.rst | 0 .../{ => cli}/command-objects/container.rst | 0 .../{ => cli}/command-objects/credential.rst | 0 .../{ => cli}/command-objects/domain.rst | 0 .../command-objects/ec2-credentials.rst | 0 .../{ => cli}/command-objects/endpoint.rst | 0 .../{ => cli}/command-objects/extension.rst | 0 .../command-objects/federation-protocol.rst | 0 .../{ => cli}/command-objects/flavor.rst | 0 .../command-objects/floating-ip-pool.rst | 0 .../{ => cli}/command-objects/floating-ip.rst | 0 .../{ => cli}/command-objects/group.rst | 0 doc/source/{ => cli}/command-objects/host.rst | 0 .../command-objects/hypervisor-stats.rst | 0 .../{ => cli}/command-objects/hypervisor.rst | 0 .../command-objects/identity-provider.rst | 0 .../{ => cli}/command-objects/image.rst | 0 .../command-objects/ip-availability.rst | 0 .../{ => cli}/command-objects/ip-fixed.rst | 0 .../command-objects/ip-floating-pool.rst | 0 .../{ => cli}/command-objects/ip-floating.rst | 0 .../{ => cli}/command-objects/keypair.rst | 0 .../{ => cli}/command-objects/limits.rst | 0 .../{ => cli}/command-objects/mapping.rst | 0 .../{ => cli}/command-objects/module.rst | 0 .../command-objects/network-agent.rst | 0 .../network-auto-allocated-topology.rst | 0 .../network-flavor-profile.rst | 0 .../command-objects/network-flavor.rst | 0 .../command-objects/network-meter-rule.rst | 0 .../command-objects/network-meter.rst | 0 .../command-objects/network-qos-policy.rst | 0 .../command-objects/network-qos-rule-type.rst | 0 .../command-objects/network-qos-rule.rst | 0 .../command-objects/network-rbac.rst | 0 .../command-objects/network-segment.rst | 0 .../network-service-provider.rst | 0 .../{ => cli}/command-objects/network.rst | 0 .../command-objects/object-store-account.rst | 0 .../{ => cli}/command-objects/object.rst | 0 .../{ => cli}/command-objects/policy.rst | 0 doc/source/{ => cli}/command-objects/port.rst | 0 .../command-objects/project-purge.rst | 0 .../{ => cli}/command-objects/project.rst | 0 .../{ => cli}/command-objects/quota.rst | 0 .../{ => cli}/command-objects/region.rst | 0 .../command-objects/request-token.rst | 0 .../command-objects/role-assignment.rst | 0 doc/source/{ => cli}/command-objects/role.rst | 0 .../{ => cli}/command-objects/router.rst | 0 .../command-objects/security-group-rule.rst | 0 .../command-objects/security-group.rst | 0 .../command-objects/server-backup.rst | 0 .../command-objects/server-event.rst | 0 .../command-objects/server-group.rst | 0 .../command-objects/server-image.rst | 0 .../{ => cli}/command-objects/server.rst | 0 .../command-objects/service-provider.rst | 0 .../{ => cli}/command-objects/service.rst | 0 .../{ => cli}/command-objects/snapshot.rst | 0 .../{ => cli}/command-objects/subnet-pool.rst | 0 .../{ => cli}/command-objects/subnet.rst | 0 .../{ => cli}/command-objects/token.rst | 0 .../{ => cli}/command-objects/trust.rst | 0 .../{ => cli}/command-objects/usage.rst | 0 .../{ => cli}/command-objects/user-role.rst | 0 doc/source/{ => cli}/command-objects/user.rst | 0 .../command-objects/volume-backup.rst | 0 .../{ => cli}/command-objects/volume-host.rst | 0 .../{ => cli}/command-objects/volume-qos.rst | 0 .../command-objects/volume-service.rst | 0 .../command-objects/volume-snapshot.rst | 0 .../volume-transfer-request.rst | 0 .../{ => cli}/command-objects/volume-type.rst | 0 .../{ => cli}/command-objects/volume.rst | 0 doc/source/{ => cli}/commands.rst | 11 ++++-- doc/source/{ => cli}/data/cinder.csv | 0 doc/source/{ => cli}/data/glance.csv | 0 doc/source/{ => cli}/data/keystone.csv | 0 doc/source/{ => cli}/data/neutron.csv | 0 doc/source/{ => cli}/data/nova.csv | 0 doc/source/{ => cli}/data/swift.csv | 0 doc/source/{ => cli}/decoder.rst | 0 doc/source/cli/index.rst | 15 ++++++++ doc/source/{ => cli}/interactive.rst | 0 doc/source/{ => cli}/man/openstack.rst | 4 +- doc/source/{ => cli}/plugin-commands.rst | 2 + .../index.rst} | 8 ++-- doc/source/{ => contributor}/command-beta.rst | 2 + .../{ => contributor}/command-errors.rst | 0 doc/source/{ => contributor}/command-logs.rst | 0 .../{ => contributor}/command-options.rst | 4 +- .../{ => contributor}/command-wrappers.rst | 0 doc/source/{ => contributor}/developing.rst | 0 .../{ => contributor}/humaninterfaceguide.rst | 4 +- doc/source/contributor/index.rst | 16 ++++++++ doc/source/{ => contributor}/plugins.rst | 2 + .../specs/command-objects/example.rst | 0 .../{ => contributor}/specs/commands.rst | 8 ++-- .../specs/network-topology.rst | 0 doc/source/index.rst | 37 +++++-------------- 119 files changed, 78 insertions(+), 45 deletions(-) rename doc/source/{ => cli}/authentication.rst (99%) rename doc/source/{ => cli}/backwards-incompatible.rst (98%) rename doc/source/{ => cli}/command-list.rst (84%) rename doc/source/{ => cli}/command-objects/access-token.rst (100%) rename doc/source/{ => cli}/command-objects/address-scope.rst (100%) rename doc/source/{ => cli}/command-objects/aggregate.rst (100%) rename doc/source/{ => cli}/command-objects/availability-zone.rst (100%) rename doc/source/{ => cli}/command-objects/backup.rst (100%) rename doc/source/{ => cli}/command-objects/catalog.rst (100%) rename doc/source/{ => cli}/command-objects/command.rst (100%) rename doc/source/{ => cli}/command-objects/complete.rst (100%) rename doc/source/{ => cli}/command-objects/compute-agent.rst (100%) rename doc/source/{ => cli}/command-objects/compute-service.rst (100%) rename doc/source/{ => cli}/command-objects/configuration.rst (95%) rename doc/source/{ => cli}/command-objects/consistency-group-snapshot.rst (100%) rename doc/source/{ => cli}/command-objects/consistency-group.rst (100%) rename doc/source/{ => cli}/command-objects/console-log.rst (100%) rename doc/source/{ => cli}/command-objects/console-url.rst (100%) rename doc/source/{ => cli}/command-objects/consumer.rst (100%) rename doc/source/{ => cli}/command-objects/container.rst (100%) rename doc/source/{ => cli}/command-objects/credential.rst (100%) rename doc/source/{ => cli}/command-objects/domain.rst (100%) rename doc/source/{ => cli}/command-objects/ec2-credentials.rst (100%) rename doc/source/{ => cli}/command-objects/endpoint.rst (100%) rename doc/source/{ => cli}/command-objects/extension.rst (100%) rename doc/source/{ => cli}/command-objects/federation-protocol.rst (100%) rename doc/source/{ => cli}/command-objects/flavor.rst (100%) rename doc/source/{ => cli}/command-objects/floating-ip-pool.rst (100%) rename doc/source/{ => cli}/command-objects/floating-ip.rst (100%) rename doc/source/{ => cli}/command-objects/group.rst (100%) rename doc/source/{ => cli}/command-objects/host.rst (100%) rename doc/source/{ => cli}/command-objects/hypervisor-stats.rst (100%) rename doc/source/{ => cli}/command-objects/hypervisor.rst (100%) rename doc/source/{ => cli}/command-objects/identity-provider.rst (100%) rename doc/source/{ => cli}/command-objects/image.rst (100%) rename doc/source/{ => cli}/command-objects/ip-availability.rst (100%) rename doc/source/{ => cli}/command-objects/ip-fixed.rst (100%) rename doc/source/{ => cli}/command-objects/ip-floating-pool.rst (100%) rename doc/source/{ => cli}/command-objects/ip-floating.rst (100%) rename doc/source/{ => cli}/command-objects/keypair.rst (100%) rename doc/source/{ => cli}/command-objects/limits.rst (100%) rename doc/source/{ => cli}/command-objects/mapping.rst (100%) rename doc/source/{ => cli}/command-objects/module.rst (100%) rename doc/source/{ => cli}/command-objects/network-agent.rst (100%) rename doc/source/{ => cli}/command-objects/network-auto-allocated-topology.rst (100%) rename doc/source/{ => cli}/command-objects/network-flavor-profile.rst (100%) rename doc/source/{ => cli}/command-objects/network-flavor.rst (100%) rename doc/source/{ => cli}/command-objects/network-meter-rule.rst (100%) rename doc/source/{ => cli}/command-objects/network-meter.rst (100%) rename doc/source/{ => cli}/command-objects/network-qos-policy.rst (100%) rename doc/source/{ => cli}/command-objects/network-qos-rule-type.rst (100%) rename doc/source/{ => cli}/command-objects/network-qos-rule.rst (100%) rename doc/source/{ => cli}/command-objects/network-rbac.rst (100%) rename doc/source/{ => cli}/command-objects/network-segment.rst (100%) rename doc/source/{ => cli}/command-objects/network-service-provider.rst (100%) rename doc/source/{ => cli}/command-objects/network.rst (100%) rename doc/source/{ => cli}/command-objects/object-store-account.rst (100%) rename doc/source/{ => cli}/command-objects/object.rst (100%) rename doc/source/{ => cli}/command-objects/policy.rst (100%) rename doc/source/{ => cli}/command-objects/port.rst (100%) rename doc/source/{ => cli}/command-objects/project-purge.rst (100%) rename doc/source/{ => cli}/command-objects/project.rst (100%) rename doc/source/{ => cli}/command-objects/quota.rst (100%) rename doc/source/{ => cli}/command-objects/region.rst (100%) rename doc/source/{ => cli}/command-objects/request-token.rst (100%) rename doc/source/{ => cli}/command-objects/role-assignment.rst (100%) rename doc/source/{ => cli}/command-objects/role.rst (100%) rename doc/source/{ => cli}/command-objects/router.rst (100%) rename doc/source/{ => cli}/command-objects/security-group-rule.rst (100%) rename doc/source/{ => cli}/command-objects/security-group.rst (100%) rename doc/source/{ => cli}/command-objects/server-backup.rst (100%) rename doc/source/{ => cli}/command-objects/server-event.rst (100%) rename doc/source/{ => cli}/command-objects/server-group.rst (100%) rename doc/source/{ => cli}/command-objects/server-image.rst (100%) rename doc/source/{ => cli}/command-objects/server.rst (100%) rename doc/source/{ => cli}/command-objects/service-provider.rst (100%) rename doc/source/{ => cli}/command-objects/service.rst (100%) rename doc/source/{ => cli}/command-objects/snapshot.rst (100%) rename doc/source/{ => cli}/command-objects/subnet-pool.rst (100%) rename doc/source/{ => cli}/command-objects/subnet.rst (100%) rename doc/source/{ => cli}/command-objects/token.rst (100%) rename doc/source/{ => cli}/command-objects/trust.rst (100%) rename doc/source/{ => cli}/command-objects/usage.rst (100%) rename doc/source/{ => cli}/command-objects/user-role.rst (100%) rename doc/source/{ => cli}/command-objects/user.rst (100%) rename doc/source/{ => cli}/command-objects/volume-backup.rst (100%) rename doc/source/{ => cli}/command-objects/volume-host.rst (100%) rename doc/source/{ => cli}/command-objects/volume-qos.rst (100%) rename doc/source/{ => cli}/command-objects/volume-service.rst (100%) rename doc/source/{ => cli}/command-objects/volume-snapshot.rst (100%) rename doc/source/{ => cli}/command-objects/volume-transfer-request.rst (100%) rename doc/source/{ => cli}/command-objects/volume-type.rst (100%) rename doc/source/{ => cli}/command-objects/volume.rst (100%) rename doc/source/{ => cli}/commands.rst (98%) rename doc/source/{ => cli}/data/cinder.csv (100%) rename doc/source/{ => cli}/data/glance.csv (100%) rename doc/source/{ => cli}/data/keystone.csv (100%) rename doc/source/{ => cli}/data/neutron.csv (100%) rename doc/source/{ => cli}/data/nova.csv (100%) rename doc/source/{ => cli}/data/swift.csv (100%) rename doc/source/{ => cli}/decoder.rst (100%) create mode 100644 doc/source/cli/index.rst rename doc/source/{ => cli}/interactive.rst (100%) rename doc/source/{ => cli}/man/openstack.rst (99%) rename doc/source/{ => cli}/plugin-commands.rst (98%) rename doc/source/{configuration.rst => configuration/index.rst} (97%) rename doc/source/{ => contributor}/command-beta.rst (99%) rename doc/source/{ => contributor}/command-errors.rst (100%) rename doc/source/{ => contributor}/command-logs.rst (100%) rename doc/source/{ => contributor}/command-options.rst (98%) rename doc/source/{ => contributor}/command-wrappers.rst (100%) rename doc/source/{ => contributor}/developing.rst (100%) rename doc/source/{ => contributor}/humaninterfaceguide.rst (99%) create mode 100644 doc/source/contributor/index.rst rename doc/source/{ => contributor}/plugins.rst (99%) rename doc/source/{ => contributor}/specs/command-objects/example.rst (100%) rename doc/source/{ => contributor}/specs/commands.rst (78%) rename doc/source/{ => contributor}/specs/network-topology.rst (100%) diff --git a/doc/source/authentication.rst b/doc/source/cli/authentication.rst similarity index 99% rename from doc/source/authentication.rst rename to doc/source/cli/authentication.rst index be16bd78ba..5a1d279798 100644 --- a/doc/source/authentication.rst +++ b/doc/source/cli/authentication.rst @@ -1,3 +1,5 @@ +.. _authentication: + ============== Authentication ============== diff --git a/doc/source/backwards-incompatible.rst b/doc/source/cli/backwards-incompatible.rst similarity index 98% rename from doc/source/backwards-incompatible.rst rename to doc/source/cli/backwards-incompatible.rst index 033860d34c..571d791f17 100644 --- a/doc/source/backwards-incompatible.rst +++ b/doc/source/cli/backwards-incompatible.rst @@ -10,8 +10,8 @@ Should positional arguments for a command need to change, the OpenStackClient team attempts to make the transition as painless as possible. Look for deprecation warnings that indicate the new commands (or options) to use. -Commands labeled as a beta according to :doc:`command-beta` are exempt from -this backwards incompatible change handling. +Commands labeled as a beta according to :ref:`command-beta` are exempt +from this backwards incompatible change handling. Backwards Incompatible Changes ============================== diff --git a/doc/source/command-list.rst b/doc/source/cli/command-list.rst similarity index 84% rename from doc/source/command-list.rst rename to doc/source/cli/command-list.rst index c4045b0406..9044f69389 100644 --- a/doc/source/command-list.rst +++ b/doc/source/cli/command-list.rst @@ -1,3 +1,5 @@ +.. _command-list: + ============ Command List ============ diff --git a/doc/source/command-objects/access-token.rst b/doc/source/cli/command-objects/access-token.rst similarity index 100% rename from doc/source/command-objects/access-token.rst rename to doc/source/cli/command-objects/access-token.rst diff --git a/doc/source/command-objects/address-scope.rst b/doc/source/cli/command-objects/address-scope.rst similarity index 100% rename from doc/source/command-objects/address-scope.rst rename to doc/source/cli/command-objects/address-scope.rst diff --git a/doc/source/command-objects/aggregate.rst b/doc/source/cli/command-objects/aggregate.rst similarity index 100% rename from doc/source/command-objects/aggregate.rst rename to doc/source/cli/command-objects/aggregate.rst diff --git a/doc/source/command-objects/availability-zone.rst b/doc/source/cli/command-objects/availability-zone.rst similarity index 100% rename from doc/source/command-objects/availability-zone.rst rename to doc/source/cli/command-objects/availability-zone.rst diff --git a/doc/source/command-objects/backup.rst b/doc/source/cli/command-objects/backup.rst similarity index 100% rename from doc/source/command-objects/backup.rst rename to doc/source/cli/command-objects/backup.rst diff --git a/doc/source/command-objects/catalog.rst b/doc/source/cli/command-objects/catalog.rst similarity index 100% rename from doc/source/command-objects/catalog.rst rename to doc/source/cli/command-objects/catalog.rst diff --git a/doc/source/command-objects/command.rst b/doc/source/cli/command-objects/command.rst similarity index 100% rename from doc/source/command-objects/command.rst rename to doc/source/cli/command-objects/command.rst diff --git a/doc/source/command-objects/complete.rst b/doc/source/cli/command-objects/complete.rst similarity index 100% rename from doc/source/command-objects/complete.rst rename to doc/source/cli/command-objects/complete.rst diff --git a/doc/source/command-objects/compute-agent.rst b/doc/source/cli/command-objects/compute-agent.rst similarity index 100% rename from doc/source/command-objects/compute-agent.rst rename to doc/source/cli/command-objects/compute-agent.rst diff --git a/doc/source/command-objects/compute-service.rst b/doc/source/cli/command-objects/compute-service.rst similarity index 100% rename from doc/source/command-objects/compute-service.rst rename to doc/source/cli/command-objects/compute-service.rst diff --git a/doc/source/command-objects/configuration.rst b/doc/source/cli/command-objects/configuration.rst similarity index 95% rename from doc/source/command-objects/configuration.rst rename to doc/source/cli/command-objects/configuration.rst index 0e00bbe9a0..6e704d2d25 100644 --- a/doc/source/command-objects/configuration.rst +++ b/doc/source/cli/command-objects/configuration.rst @@ -4,6 +4,8 @@ configuration Available for all services +.. _configuration-show: + configuration show ------------------ diff --git a/doc/source/command-objects/consistency-group-snapshot.rst b/doc/source/cli/command-objects/consistency-group-snapshot.rst similarity index 100% rename from doc/source/command-objects/consistency-group-snapshot.rst rename to doc/source/cli/command-objects/consistency-group-snapshot.rst diff --git a/doc/source/command-objects/consistency-group.rst b/doc/source/cli/command-objects/consistency-group.rst similarity index 100% rename from doc/source/command-objects/consistency-group.rst rename to doc/source/cli/command-objects/consistency-group.rst diff --git a/doc/source/command-objects/console-log.rst b/doc/source/cli/command-objects/console-log.rst similarity index 100% rename from doc/source/command-objects/console-log.rst rename to doc/source/cli/command-objects/console-log.rst diff --git a/doc/source/command-objects/console-url.rst b/doc/source/cli/command-objects/console-url.rst similarity index 100% rename from doc/source/command-objects/console-url.rst rename to doc/source/cli/command-objects/console-url.rst diff --git a/doc/source/command-objects/consumer.rst b/doc/source/cli/command-objects/consumer.rst similarity index 100% rename from doc/source/command-objects/consumer.rst rename to doc/source/cli/command-objects/consumer.rst diff --git a/doc/source/command-objects/container.rst b/doc/source/cli/command-objects/container.rst similarity index 100% rename from doc/source/command-objects/container.rst rename to doc/source/cli/command-objects/container.rst diff --git a/doc/source/command-objects/credential.rst b/doc/source/cli/command-objects/credential.rst similarity index 100% rename from doc/source/command-objects/credential.rst rename to doc/source/cli/command-objects/credential.rst diff --git a/doc/source/command-objects/domain.rst b/doc/source/cli/command-objects/domain.rst similarity index 100% rename from doc/source/command-objects/domain.rst rename to doc/source/cli/command-objects/domain.rst diff --git a/doc/source/command-objects/ec2-credentials.rst b/doc/source/cli/command-objects/ec2-credentials.rst similarity index 100% rename from doc/source/command-objects/ec2-credentials.rst rename to doc/source/cli/command-objects/ec2-credentials.rst diff --git a/doc/source/command-objects/endpoint.rst b/doc/source/cli/command-objects/endpoint.rst similarity index 100% rename from doc/source/command-objects/endpoint.rst rename to doc/source/cli/command-objects/endpoint.rst diff --git a/doc/source/command-objects/extension.rst b/doc/source/cli/command-objects/extension.rst similarity index 100% rename from doc/source/command-objects/extension.rst rename to doc/source/cli/command-objects/extension.rst diff --git a/doc/source/command-objects/federation-protocol.rst b/doc/source/cli/command-objects/federation-protocol.rst similarity index 100% rename from doc/source/command-objects/federation-protocol.rst rename to doc/source/cli/command-objects/federation-protocol.rst diff --git a/doc/source/command-objects/flavor.rst b/doc/source/cli/command-objects/flavor.rst similarity index 100% rename from doc/source/command-objects/flavor.rst rename to doc/source/cli/command-objects/flavor.rst diff --git a/doc/source/command-objects/floating-ip-pool.rst b/doc/source/cli/command-objects/floating-ip-pool.rst similarity index 100% rename from doc/source/command-objects/floating-ip-pool.rst rename to doc/source/cli/command-objects/floating-ip-pool.rst diff --git a/doc/source/command-objects/floating-ip.rst b/doc/source/cli/command-objects/floating-ip.rst similarity index 100% rename from doc/source/command-objects/floating-ip.rst rename to doc/source/cli/command-objects/floating-ip.rst diff --git a/doc/source/command-objects/group.rst b/doc/source/cli/command-objects/group.rst similarity index 100% rename from doc/source/command-objects/group.rst rename to doc/source/cli/command-objects/group.rst diff --git a/doc/source/command-objects/host.rst b/doc/source/cli/command-objects/host.rst similarity index 100% rename from doc/source/command-objects/host.rst rename to doc/source/cli/command-objects/host.rst diff --git a/doc/source/command-objects/hypervisor-stats.rst b/doc/source/cli/command-objects/hypervisor-stats.rst similarity index 100% rename from doc/source/command-objects/hypervisor-stats.rst rename to doc/source/cli/command-objects/hypervisor-stats.rst diff --git a/doc/source/command-objects/hypervisor.rst b/doc/source/cli/command-objects/hypervisor.rst similarity index 100% rename from doc/source/command-objects/hypervisor.rst rename to doc/source/cli/command-objects/hypervisor.rst diff --git a/doc/source/command-objects/identity-provider.rst b/doc/source/cli/command-objects/identity-provider.rst similarity index 100% rename from doc/source/command-objects/identity-provider.rst rename to doc/source/cli/command-objects/identity-provider.rst diff --git a/doc/source/command-objects/image.rst b/doc/source/cli/command-objects/image.rst similarity index 100% rename from doc/source/command-objects/image.rst rename to doc/source/cli/command-objects/image.rst diff --git a/doc/source/command-objects/ip-availability.rst b/doc/source/cli/command-objects/ip-availability.rst similarity index 100% rename from doc/source/command-objects/ip-availability.rst rename to doc/source/cli/command-objects/ip-availability.rst diff --git a/doc/source/command-objects/ip-fixed.rst b/doc/source/cli/command-objects/ip-fixed.rst similarity index 100% rename from doc/source/command-objects/ip-fixed.rst rename to doc/source/cli/command-objects/ip-fixed.rst diff --git a/doc/source/command-objects/ip-floating-pool.rst b/doc/source/cli/command-objects/ip-floating-pool.rst similarity index 100% rename from doc/source/command-objects/ip-floating-pool.rst rename to doc/source/cli/command-objects/ip-floating-pool.rst diff --git a/doc/source/command-objects/ip-floating.rst b/doc/source/cli/command-objects/ip-floating.rst similarity index 100% rename from doc/source/command-objects/ip-floating.rst rename to doc/source/cli/command-objects/ip-floating.rst diff --git a/doc/source/command-objects/keypair.rst b/doc/source/cli/command-objects/keypair.rst similarity index 100% rename from doc/source/command-objects/keypair.rst rename to doc/source/cli/command-objects/keypair.rst diff --git a/doc/source/command-objects/limits.rst b/doc/source/cli/command-objects/limits.rst similarity index 100% rename from doc/source/command-objects/limits.rst rename to doc/source/cli/command-objects/limits.rst diff --git a/doc/source/command-objects/mapping.rst b/doc/source/cli/command-objects/mapping.rst similarity index 100% rename from doc/source/command-objects/mapping.rst rename to doc/source/cli/command-objects/mapping.rst diff --git a/doc/source/command-objects/module.rst b/doc/source/cli/command-objects/module.rst similarity index 100% rename from doc/source/command-objects/module.rst rename to doc/source/cli/command-objects/module.rst diff --git a/doc/source/command-objects/network-agent.rst b/doc/source/cli/command-objects/network-agent.rst similarity index 100% rename from doc/source/command-objects/network-agent.rst rename to doc/source/cli/command-objects/network-agent.rst diff --git a/doc/source/command-objects/network-auto-allocated-topology.rst b/doc/source/cli/command-objects/network-auto-allocated-topology.rst similarity index 100% rename from doc/source/command-objects/network-auto-allocated-topology.rst rename to doc/source/cli/command-objects/network-auto-allocated-topology.rst diff --git a/doc/source/command-objects/network-flavor-profile.rst b/doc/source/cli/command-objects/network-flavor-profile.rst similarity index 100% rename from doc/source/command-objects/network-flavor-profile.rst rename to doc/source/cli/command-objects/network-flavor-profile.rst diff --git a/doc/source/command-objects/network-flavor.rst b/doc/source/cli/command-objects/network-flavor.rst similarity index 100% rename from doc/source/command-objects/network-flavor.rst rename to doc/source/cli/command-objects/network-flavor.rst diff --git a/doc/source/command-objects/network-meter-rule.rst b/doc/source/cli/command-objects/network-meter-rule.rst similarity index 100% rename from doc/source/command-objects/network-meter-rule.rst rename to doc/source/cli/command-objects/network-meter-rule.rst diff --git a/doc/source/command-objects/network-meter.rst b/doc/source/cli/command-objects/network-meter.rst similarity index 100% rename from doc/source/command-objects/network-meter.rst rename to doc/source/cli/command-objects/network-meter.rst diff --git a/doc/source/command-objects/network-qos-policy.rst b/doc/source/cli/command-objects/network-qos-policy.rst similarity index 100% rename from doc/source/command-objects/network-qos-policy.rst rename to doc/source/cli/command-objects/network-qos-policy.rst diff --git a/doc/source/command-objects/network-qos-rule-type.rst b/doc/source/cli/command-objects/network-qos-rule-type.rst similarity index 100% rename from doc/source/command-objects/network-qos-rule-type.rst rename to doc/source/cli/command-objects/network-qos-rule-type.rst diff --git a/doc/source/command-objects/network-qos-rule.rst b/doc/source/cli/command-objects/network-qos-rule.rst similarity index 100% rename from doc/source/command-objects/network-qos-rule.rst rename to doc/source/cli/command-objects/network-qos-rule.rst diff --git a/doc/source/command-objects/network-rbac.rst b/doc/source/cli/command-objects/network-rbac.rst similarity index 100% rename from doc/source/command-objects/network-rbac.rst rename to doc/source/cli/command-objects/network-rbac.rst diff --git a/doc/source/command-objects/network-segment.rst b/doc/source/cli/command-objects/network-segment.rst similarity index 100% rename from doc/source/command-objects/network-segment.rst rename to doc/source/cli/command-objects/network-segment.rst diff --git a/doc/source/command-objects/network-service-provider.rst b/doc/source/cli/command-objects/network-service-provider.rst similarity index 100% rename from doc/source/command-objects/network-service-provider.rst rename to doc/source/cli/command-objects/network-service-provider.rst diff --git a/doc/source/command-objects/network.rst b/doc/source/cli/command-objects/network.rst similarity index 100% rename from doc/source/command-objects/network.rst rename to doc/source/cli/command-objects/network.rst diff --git a/doc/source/command-objects/object-store-account.rst b/doc/source/cli/command-objects/object-store-account.rst similarity index 100% rename from doc/source/command-objects/object-store-account.rst rename to doc/source/cli/command-objects/object-store-account.rst diff --git a/doc/source/command-objects/object.rst b/doc/source/cli/command-objects/object.rst similarity index 100% rename from doc/source/command-objects/object.rst rename to doc/source/cli/command-objects/object.rst diff --git a/doc/source/command-objects/policy.rst b/doc/source/cli/command-objects/policy.rst similarity index 100% rename from doc/source/command-objects/policy.rst rename to doc/source/cli/command-objects/policy.rst diff --git a/doc/source/command-objects/port.rst b/doc/source/cli/command-objects/port.rst similarity index 100% rename from doc/source/command-objects/port.rst rename to doc/source/cli/command-objects/port.rst diff --git a/doc/source/command-objects/project-purge.rst b/doc/source/cli/command-objects/project-purge.rst similarity index 100% rename from doc/source/command-objects/project-purge.rst rename to doc/source/cli/command-objects/project-purge.rst diff --git a/doc/source/command-objects/project.rst b/doc/source/cli/command-objects/project.rst similarity index 100% rename from doc/source/command-objects/project.rst rename to doc/source/cli/command-objects/project.rst diff --git a/doc/source/command-objects/quota.rst b/doc/source/cli/command-objects/quota.rst similarity index 100% rename from doc/source/command-objects/quota.rst rename to doc/source/cli/command-objects/quota.rst diff --git a/doc/source/command-objects/region.rst b/doc/source/cli/command-objects/region.rst similarity index 100% rename from doc/source/command-objects/region.rst rename to doc/source/cli/command-objects/region.rst diff --git a/doc/source/command-objects/request-token.rst b/doc/source/cli/command-objects/request-token.rst similarity index 100% rename from doc/source/command-objects/request-token.rst rename to doc/source/cli/command-objects/request-token.rst diff --git a/doc/source/command-objects/role-assignment.rst b/doc/source/cli/command-objects/role-assignment.rst similarity index 100% rename from doc/source/command-objects/role-assignment.rst rename to doc/source/cli/command-objects/role-assignment.rst diff --git a/doc/source/command-objects/role.rst b/doc/source/cli/command-objects/role.rst similarity index 100% rename from doc/source/command-objects/role.rst rename to doc/source/cli/command-objects/role.rst diff --git a/doc/source/command-objects/router.rst b/doc/source/cli/command-objects/router.rst similarity index 100% rename from doc/source/command-objects/router.rst rename to doc/source/cli/command-objects/router.rst diff --git a/doc/source/command-objects/security-group-rule.rst b/doc/source/cli/command-objects/security-group-rule.rst similarity index 100% rename from doc/source/command-objects/security-group-rule.rst rename to doc/source/cli/command-objects/security-group-rule.rst diff --git a/doc/source/command-objects/security-group.rst b/doc/source/cli/command-objects/security-group.rst similarity index 100% rename from doc/source/command-objects/security-group.rst rename to doc/source/cli/command-objects/security-group.rst diff --git a/doc/source/command-objects/server-backup.rst b/doc/source/cli/command-objects/server-backup.rst similarity index 100% rename from doc/source/command-objects/server-backup.rst rename to doc/source/cli/command-objects/server-backup.rst diff --git a/doc/source/command-objects/server-event.rst b/doc/source/cli/command-objects/server-event.rst similarity index 100% rename from doc/source/command-objects/server-event.rst rename to doc/source/cli/command-objects/server-event.rst diff --git a/doc/source/command-objects/server-group.rst b/doc/source/cli/command-objects/server-group.rst similarity index 100% rename from doc/source/command-objects/server-group.rst rename to doc/source/cli/command-objects/server-group.rst diff --git a/doc/source/command-objects/server-image.rst b/doc/source/cli/command-objects/server-image.rst similarity index 100% rename from doc/source/command-objects/server-image.rst rename to doc/source/cli/command-objects/server-image.rst diff --git a/doc/source/command-objects/server.rst b/doc/source/cli/command-objects/server.rst similarity index 100% rename from doc/source/command-objects/server.rst rename to doc/source/cli/command-objects/server.rst diff --git a/doc/source/command-objects/service-provider.rst b/doc/source/cli/command-objects/service-provider.rst similarity index 100% rename from doc/source/command-objects/service-provider.rst rename to doc/source/cli/command-objects/service-provider.rst diff --git a/doc/source/command-objects/service.rst b/doc/source/cli/command-objects/service.rst similarity index 100% rename from doc/source/command-objects/service.rst rename to doc/source/cli/command-objects/service.rst diff --git a/doc/source/command-objects/snapshot.rst b/doc/source/cli/command-objects/snapshot.rst similarity index 100% rename from doc/source/command-objects/snapshot.rst rename to doc/source/cli/command-objects/snapshot.rst diff --git a/doc/source/command-objects/subnet-pool.rst b/doc/source/cli/command-objects/subnet-pool.rst similarity index 100% rename from doc/source/command-objects/subnet-pool.rst rename to doc/source/cli/command-objects/subnet-pool.rst diff --git a/doc/source/command-objects/subnet.rst b/doc/source/cli/command-objects/subnet.rst similarity index 100% rename from doc/source/command-objects/subnet.rst rename to doc/source/cli/command-objects/subnet.rst diff --git a/doc/source/command-objects/token.rst b/doc/source/cli/command-objects/token.rst similarity index 100% rename from doc/source/command-objects/token.rst rename to doc/source/cli/command-objects/token.rst diff --git a/doc/source/command-objects/trust.rst b/doc/source/cli/command-objects/trust.rst similarity index 100% rename from doc/source/command-objects/trust.rst rename to doc/source/cli/command-objects/trust.rst diff --git a/doc/source/command-objects/usage.rst b/doc/source/cli/command-objects/usage.rst similarity index 100% rename from doc/source/command-objects/usage.rst rename to doc/source/cli/command-objects/usage.rst diff --git a/doc/source/command-objects/user-role.rst b/doc/source/cli/command-objects/user-role.rst similarity index 100% rename from doc/source/command-objects/user-role.rst rename to doc/source/cli/command-objects/user-role.rst diff --git a/doc/source/command-objects/user.rst b/doc/source/cli/command-objects/user.rst similarity index 100% rename from doc/source/command-objects/user.rst rename to doc/source/cli/command-objects/user.rst diff --git a/doc/source/command-objects/volume-backup.rst b/doc/source/cli/command-objects/volume-backup.rst similarity index 100% rename from doc/source/command-objects/volume-backup.rst rename to doc/source/cli/command-objects/volume-backup.rst diff --git a/doc/source/command-objects/volume-host.rst b/doc/source/cli/command-objects/volume-host.rst similarity index 100% rename from doc/source/command-objects/volume-host.rst rename to doc/source/cli/command-objects/volume-host.rst diff --git a/doc/source/command-objects/volume-qos.rst b/doc/source/cli/command-objects/volume-qos.rst similarity index 100% rename from doc/source/command-objects/volume-qos.rst rename to doc/source/cli/command-objects/volume-qos.rst diff --git a/doc/source/command-objects/volume-service.rst b/doc/source/cli/command-objects/volume-service.rst similarity index 100% rename from doc/source/command-objects/volume-service.rst rename to doc/source/cli/command-objects/volume-service.rst diff --git a/doc/source/command-objects/volume-snapshot.rst b/doc/source/cli/command-objects/volume-snapshot.rst similarity index 100% rename from doc/source/command-objects/volume-snapshot.rst rename to doc/source/cli/command-objects/volume-snapshot.rst diff --git a/doc/source/command-objects/volume-transfer-request.rst b/doc/source/cli/command-objects/volume-transfer-request.rst similarity index 100% rename from doc/source/command-objects/volume-transfer-request.rst rename to doc/source/cli/command-objects/volume-transfer-request.rst diff --git a/doc/source/command-objects/volume-type.rst b/doc/source/cli/command-objects/volume-type.rst similarity index 100% rename from doc/source/command-objects/volume-type.rst rename to doc/source/cli/command-objects/volume-type.rst diff --git a/doc/source/command-objects/volume.rst b/doc/source/cli/command-objects/volume.rst similarity index 100% rename from doc/source/command-objects/volume.rst rename to doc/source/cli/command-objects/volume.rst diff --git a/doc/source/commands.rst b/doc/source/cli/commands.rst similarity index 98% rename from doc/source/commands.rst rename to doc/source/cli/commands.rst index cd71bdc8c3..5c50a1bf64 100644 --- a/doc/source/commands.rst +++ b/doc/source/cli/commands.rst @@ -1,3 +1,5 @@ +.. _command-structure: + ================= Command Structure ================= @@ -162,9 +164,10 @@ referring to both Compute and Volume quotas. Plugin Objects -------------- -The following are known `Objects` used by OpenStack :doc:`plugins`. These are -listed here to avoid name conflicts when creating new plugins. For a complete -list check out :doc:`plugin-commands`. +The following are known `Objects` used by OpenStack +:ref:`plugins`. These are listed here to avoid name +conflicts when creating new plugins. For a complete list check out +:ref:`plugin-commands`. * ``action definition``: (**Workflow Engine (Mistral)**) * ``action execution``: (**Workflow Engine (Mistral)**) @@ -281,7 +284,7 @@ Implementation The command structure is designed to support seamless addition of plugin command modules via ``setuptools`` entry points. The plugin commands must -be subclasses of Cliff's ``command.Command`` object. See :doc:`plugins` for +be subclasses of Cliff's ``command.Command`` object. See :ref:`plugins` for more information. diff --git a/doc/source/data/cinder.csv b/doc/source/cli/data/cinder.csv similarity index 100% rename from doc/source/data/cinder.csv rename to doc/source/cli/data/cinder.csv diff --git a/doc/source/data/glance.csv b/doc/source/cli/data/glance.csv similarity index 100% rename from doc/source/data/glance.csv rename to doc/source/cli/data/glance.csv diff --git a/doc/source/data/keystone.csv b/doc/source/cli/data/keystone.csv similarity index 100% rename from doc/source/data/keystone.csv rename to doc/source/cli/data/keystone.csv diff --git a/doc/source/data/neutron.csv b/doc/source/cli/data/neutron.csv similarity index 100% rename from doc/source/data/neutron.csv rename to doc/source/cli/data/neutron.csv diff --git a/doc/source/data/nova.csv b/doc/source/cli/data/nova.csv similarity index 100% rename from doc/source/data/nova.csv rename to doc/source/cli/data/nova.csv diff --git a/doc/source/data/swift.csv b/doc/source/cli/data/swift.csv similarity index 100% rename from doc/source/data/swift.csv rename to doc/source/cli/data/swift.csv diff --git a/doc/source/decoder.rst b/doc/source/cli/decoder.rst similarity index 100% rename from doc/source/decoder.rst rename to doc/source/cli/decoder.rst diff --git a/doc/source/cli/index.rst b/doc/source/cli/index.rst new file mode 100644 index 0000000000..e9aab0afae --- /dev/null +++ b/doc/source/cli/index.rst @@ -0,0 +1,15 @@ +==================== + User Documentation +==================== + +.. toctree:: + :maxdepth: 2 + + Manual Page + command-list + commands + plugin-commands + authentication + interactive + decoder + backwards-incompatible diff --git a/doc/source/interactive.rst b/doc/source/cli/interactive.rst similarity index 100% rename from doc/source/interactive.rst rename to doc/source/cli/interactive.rst diff --git a/doc/source/man/openstack.rst b/doc/source/cli/man/openstack.rst similarity index 99% rename from doc/source/man/openstack.rst rename to doc/source/cli/man/openstack.rst index 66a99f3223..ab990979fc 100644 --- a/doc/source/man/openstack.rst +++ b/doc/source/cli/man/openstack.rst @@ -1,3 +1,5 @@ +.. _manpage: + ==================== :program:`openstack` ==================== @@ -329,7 +331,7 @@ Logging Settings in configuration file. Recording the user operation, it can identify the change of the resource and it becomes useful information for troubleshooting. -See :doc:`../configuration` about Logging Settings for more details. +See :ref:`configuration` about Logging Settings for more details. NOTES diff --git a/doc/source/plugin-commands.rst b/doc/source/cli/plugin-commands.rst similarity index 98% rename from doc/source/plugin-commands.rst rename to doc/source/cli/plugin-commands.rst index ee162f76e7..55984ffb7f 100644 --- a/doc/source/plugin-commands.rst +++ b/doc/source/cli/plugin-commands.rst @@ -1,3 +1,5 @@ +.. _plugin-commands: + =============== Plugin Commands =============== diff --git a/doc/source/configuration.rst b/doc/source/configuration/index.rst similarity index 97% rename from doc/source/configuration.rst rename to doc/source/configuration/index.rst index a49f093a45..d2b273d7ee 100644 --- a/doc/source/configuration.rst +++ b/doc/source/configuration/index.rst @@ -1,3 +1,5 @@ +.. _configuration: + ============= Configuration ============= @@ -22,13 +24,13 @@ names by dropping the ``--os-`` prefix if present. Global Options -------------- -The :doc:`openstack manpage ` lists all of the global +The :ref:`openstack manpage ` lists all of the global options recognized by OpenStackClient and the default authentication plugins. Environment Variables --------------------- -The :doc:`openstack manpage ` also lists all of the +The :ref:`openstack manpage ` also lists all of the environment variables recognized by OpenStackClient and the default authentication plugins. @@ -156,7 +158,7 @@ that appears in :file:`clouds.yaml` Debugging ~~~~~~~~~ -You may find the :doc:`configuration show ` +You may find the :ref:`configuration show ` command helpful to debug configuration issues. It will display your current configuration. diff --git a/doc/source/command-beta.rst b/doc/source/contributor/command-beta.rst similarity index 99% rename from doc/source/command-beta.rst rename to doc/source/contributor/command-beta.rst index 669ef2011a..40ede67100 100644 --- a/doc/source/command-beta.rst +++ b/doc/source/contributor/command-beta.rst @@ -1,3 +1,5 @@ +.. _command-beta: + ============ Command Beta ============ diff --git a/doc/source/command-errors.rst b/doc/source/contributor/command-errors.rst similarity index 100% rename from doc/source/command-errors.rst rename to doc/source/contributor/command-errors.rst diff --git a/doc/source/command-logs.rst b/doc/source/contributor/command-logs.rst similarity index 100% rename from doc/source/command-logs.rst rename to doc/source/contributor/command-logs.rst diff --git a/doc/source/command-options.rst b/doc/source/contributor/command-options.rst similarity index 98% rename from doc/source/command-options.rst rename to doc/source/contributor/command-options.rst index 886c17d292..0662344536 100644 --- a/doc/source/command-options.rst +++ b/doc/source/contributor/command-options.rst @@ -14,8 +14,8 @@ for defining and using options in all situations. The alternative of only using it when necessary leads to errors when copy-n-paste is used for a new command without understanding why or why not that instance is correct. -The :doc:`Human Interface Guide ` -describes the guildelines for option names and usage. In short: +The :ref:`hig` describes the guildelines for option names and usage. +In short: * All option names shall be GNU-style long names (two leading dashes). * Some global options may have short names, generally limited to those defined diff --git a/doc/source/command-wrappers.rst b/doc/source/contributor/command-wrappers.rst similarity index 100% rename from doc/source/command-wrappers.rst rename to doc/source/contributor/command-wrappers.rst diff --git a/doc/source/developing.rst b/doc/source/contributor/developing.rst similarity index 100% rename from doc/source/developing.rst rename to doc/source/contributor/developing.rst diff --git a/doc/source/humaninterfaceguide.rst b/doc/source/contributor/humaninterfaceguide.rst similarity index 99% rename from doc/source/humaninterfaceguide.rst rename to doc/source/contributor/humaninterfaceguide.rst index b43c71a9e9..a7db380053 100644 --- a/doc/source/humaninterfaceguide.rst +++ b/doc/source/contributor/humaninterfaceguide.rst @@ -1,3 +1,5 @@ +.. _hig: + ===================== Human Interface Guide ===================== @@ -202,7 +204,7 @@ dash ('-') to an underscore ('_'), and converting to upper case. ('-') internally between words (:code:`--like-this`). Underscores ('_') shall not be used in option names. * Authentication options conform to the common CLI authentication guidelines in - :doc:`authentication`. + :ref:`authentication`. For example, :code:`--os-username` can be set from the environment via :code:`OS_USERNAME`. diff --git a/doc/source/contributor/index.rst b/doc/source/contributor/index.rst new file mode 100644 index 0000000000..4438f2ad78 --- /dev/null +++ b/doc/source/contributor/index.rst @@ -0,0 +1,16 @@ +=========================== + Contributor Documentation +=========================== + +.. toctree:: + :maxdepth: 1 + + developing + command-beta + command-options + command-wrappers + command-errors + command-logs + specs/commands + plugins + humaninterfaceguide diff --git a/doc/source/plugins.rst b/doc/source/contributor/plugins.rst similarity index 99% rename from doc/source/plugins.rst rename to doc/source/contributor/plugins.rst index 058819a60c..13f5d49512 100644 --- a/doc/source/plugins.rst +++ b/doc/source/contributor/plugins.rst @@ -1,3 +1,5 @@ +.. _plugins: + ======= Plugins ======= diff --git a/doc/source/specs/command-objects/example.rst b/doc/source/contributor/specs/command-objects/example.rst similarity index 100% rename from doc/source/specs/command-objects/example.rst rename to doc/source/contributor/specs/command-objects/example.rst diff --git a/doc/source/specs/commands.rst b/doc/source/contributor/specs/commands.rst similarity index 78% rename from doc/source/specs/commands.rst rename to doc/source/contributor/specs/commands.rst index 5ae0e84069..f9d757e785 100644 --- a/doc/source/specs/commands.rst +++ b/doc/source/contributor/specs/commands.rst @@ -4,15 +4,15 @@ Command Specs Specifications for new commands, objects and actions are listed below. These specifications have not been implemented. See -:doc:`Command List <../command-list>` for implemented commands and -:doc:`Command Structure <../commands>` for implemented objects and actions. +:ref:`command-list` for implemented commands and +:ref:`command-structure` for implemented objects and actions. It is optional to propose a specifications patch for new commands, objects and actions here before submitting the implementation. Once your specifications patch merges then you may proceed with the implementation. Your implementation patches should move applicable portions of the -specifications patch to the official :doc:`Command List <../command-list>` -and :doc:`Command Structure <../commands>` documentation. +specifications patch to the official :ref:`command-list` +and :ref:`command-structure` documentation. Objects Specs ------------- diff --git a/doc/source/specs/network-topology.rst b/doc/source/contributor/specs/network-topology.rst similarity index 100% rename from doc/source/specs/network-topology.rst rename to doc/source/contributor/specs/network-topology.rst diff --git a/doc/source/index.rst b/doc/source/index.rst index ae0ee0ffb5..be421d20fa 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -7,32 +7,19 @@ brings the command set for Compute, Identity, Image, Object Storage and Block Storage APIs together in a single shell with a uniform command structure. -User Documentation ------------------- - -.. toctree:: - :maxdepth: 1 - - Manual Page - command-list - commands - configuration - plugins - plugin-commands - authentication - interactive - humaninterfaceguide - backwards-incompatible +Using OpenStackClient +--------------------- .. toctree:: :maxdepth: 2 - decoder + cli/index + configuration/index Getting Started --------------- -* Try :doc:`some commands ` +* Try :ref:`some commands ` * Read the source `on OpenStack's Git server`_ * Install OpenStackClient from `PyPi`_ or a `tarball`_ @@ -44,19 +31,13 @@ Release Notes Release Notes -Developer Documentation ------------------------ +Contributor Documentation +------------------------- .. toctree:: - :maxdepth: 1 + :maxdepth: 2 - developing - command-beta - command-options - command-wrappers - command-errors - command-logs - specs/commands + contributor/index api/modules Project Goals From e7807bc0ed13cb878dc3137f8e4d5a03b348f9e8 Mon Sep 17 00:00:00 2001 From: Doug Hellmann Date: Tue, 13 Jun 2017 17:34:28 -0400 Subject: [PATCH 1710/3095] move auto-generated api docs into contributor tree Change-Id: I2e75d3014bd2252af8c01566c0ec6787608e3996 Depends-On: I2bd5652bb59cbd9c939931ba2e7db1b37d2b30bb Signed-off-by: Doug Hellmann --- .gitignore | 2 +- doc/source/contributor/index.rst | 1 + doc/source/index.rst | 1 - setup.cfg | 1 + 4 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 29b5574ce2..2c0139cf68 100644 --- a/.gitignore +++ b/.gitignore @@ -16,7 +16,7 @@ ChangeLog dist # Doc related doc/build -doc/source/api/ +doc/source/contributor/api/ # Development environment files .project .pydevproject diff --git a/doc/source/contributor/index.rst b/doc/source/contributor/index.rst index 4438f2ad78..2aa9498f1b 100644 --- a/doc/source/contributor/index.rst +++ b/doc/source/contributor/index.rst @@ -14,3 +14,4 @@ specs/commands plugins humaninterfaceguide + api/modules diff --git a/doc/source/index.rst b/doc/source/index.rst index be421d20fa..3f63edb3b4 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -38,7 +38,6 @@ Contributor Documentation :maxdepth: 2 contributor/index - api/modules Project Goals ------------- diff --git a/setup.cfg b/setup.cfg index 86f315c139..73a9c42cfb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -701,6 +701,7 @@ autodoc_tree_excludes = openstackclient/volume/v3 openstackclient/tests/ openstackclient/tests/* +api_doc_dir = contributor/api [build_sphinx] builders = html,man From 0ddda620dc957d47f1afe24707ddb06e98c44ca6 Mon Sep 17 00:00:00 2001 From: Doug Hellmann Date: Thu, 22 Jun 2017 10:03:37 -0400 Subject: [PATCH 1711/3095] update the docs URLs in the readme Change-Id: Id1168e1003f62e08d2da338cb1b81bc2b7547bfc Signed-off-by: Doug Hellmann --- README.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 86f6be136a..deca066980 100644 --- a/README.rst +++ b/README.rst @@ -39,14 +39,14 @@ language to describe operations in OpenStack. * License: Apache 2.0 .. _PyPi: https://pypi.python.org/pypi/python-openstackclient -.. _Online Documentation: http://docs.openstack.org/developer/python-openstackclient/ +.. _Online Documentation: http://docs.openstack.org/python-openstackclient/ .. _Launchpad project: https://launchpad.net/python-openstackclient .. _Blueprints: https://blueprints.launchpad.net/python-openstackclient .. _Bugs: https://bugs.launchpad.net/python-openstackclient .. _Source: https://git.openstack.org/cgit/openstack/python-openstackclient .. _Developer: http://docs.openstack.org/project-team-guide/project-setup/python.html .. _Contributing: http://docs.openstack.org/infra/manual/developers.html -.. _Testing: http://docs.openstack.org/developer/python-openstackclient/developing.html#testing +.. _Testing: http://docs.openstack.org/python-openstackclient/developing.html#testing Getting Started =============== @@ -79,7 +79,7 @@ Configuration ============= The CLI is configured via environment variables and command-line -options as listed in http://docs.openstack.org/developer/python-openstackclient/authentication.html. +options as listed in http://docs.openstack.org/python-openstackclient/authentication.html. Authentication using username/password is most commonly used:: From c0719c36d1a3f268b6bfba4c83aac632f1b546fe Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sat, 24 Jun 2017 12:15:02 +0000 Subject: [PATCH 1712/3095] Updated from global requirements Change-Id: I115d686010bd79ea49786f46decd6ed6256f6cb9 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 978fc7b4ee..d0489c4df9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ six>=1.9.0 # MIT Babel!=2.4.0,>=2.3.4 # BSD cliff>=2.6.0 # Apache-2.0 keystoneauth1>=2.21.0 # Apache-2.0 -openstacksdk>=0.9.16 # Apache-2.0 +openstacksdk>=0.9.17 # Apache-2.0 osc-lib>=1.5.1 # Apache-2.0 oslo.i18n!=3.15.2,>=2.1.0 # Apache-2.0 oslo.utils>=3.20.0 # Apache-2.0 From 6eb1551573bc8d010b1ffb7baf79d795e7c723c3 Mon Sep 17 00:00:00 2001 From: Samuel de Medeiros Queiroz Date: Wed, 21 Jun 2017 16:09:35 -0400 Subject: [PATCH 1713/3095] Use identity auth v3 the README examples Identity auth v3 is the default in devstack. The keystone team advertises all deployments to migrate over to v3. If we get our examples to use v3, that would be a great help. Change-Id: I8bd4cbf16cd42fa1654776f19bf113e3c94e25cf --- README.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.rst b/README.rst index 86f6be136a..46c14af5b1 100644 --- a/README.rst +++ b/README.rst @@ -84,15 +84,21 @@ options as listed in http://docs.openstack.org/developer/python-openstackclient Authentication using username/password is most commonly used:: export OS_AUTH_URL= + export OS_IDENTITY_API_VERSION=3 export OS_PROJECT_NAME= + export OS_PROJECT_DOMAIN_NAME= export OS_USERNAME= + export OS_USER_DOMAIN_NAME= export OS_PASSWORD= # (optional) The corresponding command-line options look very similar:: --os-auth-url + --os-identity-api-version 3 --os-project-name + --os-project-domain-name --os-username + --os-user-domain-name [--os-password ] If a password is not provided above (in plaintext), you will be interactively From cf60df426114ae2ecea504d00c4fa31a153c7353 Mon Sep 17 00:00:00 2001 From: Doug Hellmann Date: Mon, 26 Jun 2017 17:01:19 -0400 Subject: [PATCH 1714/3095] switch to openstackdocstheme Change-Id: Iee591504ba5d7506ba41a9faaa1c293a5fe2db6a Depends-On: Ifc5512c0e2373cf3387e0e0498268eab092e52bb Signed-off-by: Doug Hellmann --- doc/source/conf.py | 9 ++++++++- releasenotes/source/conf.py | 3 ++- test-requirements.txt | 4 ++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 420291460f..a90fca64ba 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -32,10 +32,15 @@ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.todo', - 'oslosphinx', + 'openstackdocstheme', 'stevedore.sphinxext', ] +# openstackdocstheme options +repository_name = 'openstack/python-openstackclient' +bug_project = 'python-openstackclient' +bug_tag = '' + # Add any paths that contain templates here, relative to this directory. #templates_path = ['_templates'] @@ -105,6 +110,7 @@ # a list of builtin themes. #html_theme_path = ["."] #html_theme = '_theme' +html_theme = 'openstackdocs' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -138,6 +144,7 @@ # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' +html_last_updated_fmt = '%Y-%m-%d %H:%M' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py index 38f0107cc4..5d270d9303 100644 --- a/releasenotes/source/conf.py +++ b/releasenotes/source/conf.py @@ -38,7 +38,7 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'oslosphinx', + 'openstackdocstheme', 'reno.sphinxext', 'sphinx.ext.extlinks', ] @@ -171,6 +171,7 @@ # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' +html_last_updated_fmt = '%Y-%m-%d %H:%M' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. diff --git a/test-requirements.txt b/test-requirements.txt index 58247585ec..661aa73d9a 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -6,12 +6,12 @@ hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0 coverage!=4.4,>=4.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD mock>=2.0 # BSD -oslosphinx>=4.7.0 # Apache-2.0 +openstackdocstheme>=1.11.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 reno!=2.3.1,>=1.8.0 # Apache-2.0 requests>=2.14.2 # Apache-2.0 requests-mock>=1.1 # Apache-2.0 -sphinx!=1.6.1,>=1.5.1 # BSD +sphinx>=1.6.2 # BSD stevedore>=1.20.0 # Apache-2.0 os-client-config>=1.27.0 # Apache-2.0 os-testr>=0.8.0 # Apache-2.0 From 1405818b7705d413e0113454a35151b9dd4ba414 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Tue, 27 Jun 2017 12:22:21 +0000 Subject: [PATCH 1715/3095] Updated from global requirements Change-Id: I6ab372eddd2eafc789e4fc7e16f36d7b0f390f2b --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 58247585ec..e9939a1c0e 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -11,7 +11,7 @@ oslotest>=1.10.0 # Apache-2.0 reno!=2.3.1,>=1.8.0 # Apache-2.0 requests>=2.14.2 # Apache-2.0 requests-mock>=1.1 # Apache-2.0 -sphinx!=1.6.1,>=1.5.1 # BSD +sphinx>=1.6.2 # BSD stevedore>=1.20.0 # Apache-2.0 os-client-config>=1.27.0 # Apache-2.0 os-testr>=0.8.0 # Apache-2.0 From ca4b9be8a23136fb709c73b702f98fd749026a8c Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 29 Jun 2017 02:28:44 +0000 Subject: [PATCH 1716/3095] Updated from global requirements Change-Id: Ifc46dfac0578144329675c2c75da1e6ddfa862a3 --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 661aa73d9a..b6f3edb627 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -6,7 +6,7 @@ hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0 coverage!=4.4,>=4.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD mock>=2.0 # BSD -openstackdocstheme>=1.11.0 # Apache-2.0 +openstackdocstheme>=1.11.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 reno!=2.3.1,>=1.8.0 # Apache-2.0 requests>=2.14.2 # Apache-2.0 From 2b7cb1559b4897a51a7afd12a648a4e5f2925ec5 Mon Sep 17 00:00:00 2001 From: wingwj Date: Thu, 29 Jun 2017 16:50:25 +0800 Subject: [PATCH 1717/3095] Remove inaccurate mapping of 'host-meta' in csv Now we don't have a similar command to 'nova host-meta' in osc, the 'host set/unset' in osc is used to manage host properties. Need to remove the inaccurate mapping in nova.csv. Change-Id: I291174a29c8e3419b62d47264dedf176262a816c Closes-Bug: #1690902 --- doc/source/cli/data/nova.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/cli/data/nova.csv b/doc/source/cli/data/nova.csv index 559803911d..74f76c77e4 100644 --- a/doc/source/cli/data/nova.csv +++ b/doc/source/cli/data/nova.csv @@ -47,7 +47,7 @@ host-describe,host show,Describe a specific host. host-evacuate,,Evacuate all instances from failed host. host-evacuate-live,,Live migrate all instances of the specified host to other available hosts. host-list,host list,List all hosts by service. -host-meta,host set / unset,Set or Delete metadata on all instances of a host. +host-meta,,Set or Delete metadata on all instances of a host. host-servers-migrate,,Cold migrate all instances off the specified host to other available hosts. host-update,host set,Update host settings. hypervisor-list,hypervisor list,List hypervisors. From 2bd124731a943756174c04420a658a9f195a395c Mon Sep 17 00:00:00 2001 From: Michael Johnson Date: Wed, 28 Jun 2017 20:16:04 -0700 Subject: [PATCH 1718/3095] Add python-octaviaclient plugin This patch adds the "loadbalancer" commands to the docs and sets up the document generation for the python-octaviaclient plugin. Depends-On: Ib123383c5f6904b4b00831e8cc7aaa180bd4a506 Change-Id: I48939cc3653cd379a328e0a0973d075019d22b00 --- doc/source/cli/commands.rst | 7 +++++++ doc/source/cli/plugin-commands.rst | 6 ++++++ doc/source/contributor/plugins.rst | 1 + test-requirements.txt | 1 + 4 files changed, 15 insertions(+) diff --git a/doc/source/cli/commands.rst b/doc/source/cli/commands.rst index 5c50a1bf64..2a05d5c91b 100644 --- a/doc/source/cli/commands.rst +++ b/doc/source/cli/commands.rst @@ -195,6 +195,13 @@ conflicts when creating new plugins. For a complete list check out * ``dataprocessing image tags``: (**Data Processing (Sahara)**) * ``dataprocessing plugin``: (**Data Processing (Sahara)**) * ``data protection plan``: (**Data Protection (Karbor)**) +* ``loadbalancer``: (**Load Balancer (Octavia)**) +* ``loadbalancer healthmonitor``: (**Load Balancer (Octavia)**) +* ``loadbalancer l7policy``: (**Load Balancer (Octavia)**) +* ``loadbalancer l7rule``: (**Load Balancer (Octavia)**) +* ``loadbalancer listener``: (**Load Balancer (Octavia)**) +* ``loadbalancer member``: (**Load Balancer (Octavia)**) +* ``loadbalancer pool``: (**Load Balancer (Octavia)**) * ``message-broker cluster``: (**Message Broker (Cue)**) * ``messaging``: (**Messaging (Zaqar)**) * ``messaging flavor``: (**Messaging (Zaqar)**) diff --git a/doc/source/cli/plugin-commands.rst b/doc/source/cli/plugin-commands.rst index 55984ffb7f..34efdc3d84 100644 --- a/doc/source/cli/plugin-commands.rst +++ b/doc/source/cli/plugin-commands.rst @@ -81,6 +81,12 @@ neutron .. list-plugins:: openstack.neutronclient.v2 :detailed: +octavia +------- + +.. list-plugins:: openstack.load_balancer.v2 + :detailed: + sahara ------ diff --git a/doc/source/contributor/plugins.rst b/doc/source/contributor/plugins.rst index 13f5d49512..fb21a0795a 100644 --- a/doc/source/contributor/plugins.rst +++ b/doc/source/contributor/plugins.rst @@ -35,6 +35,7 @@ The following is a list of projects that are an OpenStackClient plugin. - python-mistralclient - python-muranoclient - python-neutronclient\*\*\* +- python-octaviaclient - python-saharaclient - python-searchlightclient - python-senlinclient diff --git a/test-requirements.txt b/test-requirements.txt index 661aa73d9a..40093e3972 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -35,6 +35,7 @@ python-karborclient>=0.2.0 # Apache-2.0 python-mistralclient>=3.1.0 # Apache-2.0 python-muranoclient>=0.8.2 # Apache-2.0 python-neutronclient>=6.3.0 # Apache-2.0 +python-octaviaclient>=1.0.0 # Apache-2.0 python-saharaclient>=1.1.0 # Apache-2.0 python-searchlightclient>=1.0.0 #Apache-2.0 python-senlinclient>=1.1.0 # Apache-2.0 From 3468ea1ca429e8b6403ae5f989cfed521d8f5690 Mon Sep 17 00:00:00 2001 From: M V P Nitesh Date: Fri, 30 Jun 2017 14:13:02 +0530 Subject: [PATCH 1719/3095] Added 'openstack image set --visibility' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This feature will allow image owners to share images across multiple tenants/projects without explicitly creating members individually through the glance API V2. “Community images” will not appear iu user's default image listings Change-Id: Ic02bf44cca5d6d793d68a8737d3cfe3f44407d88 Closes-Bug: #1626837 --- doc/source/cli/command-objects/image.rst | 20 +++++++++-- openstackclient/image/v2/image.py | 34 ++++++++++++++++--- ...image_set_visibility-babf4ff2f687d465.yaml | 7 ++++ 3 files changed, 55 insertions(+), 6 deletions(-) create mode 100644 releasenotes/notes/image_set_visibility-babf4ff2f687d465.yaml diff --git a/doc/source/cli/command-objects/image.rst b/doc/source/cli/command-objects/image.rst index ec51fa9316..2918452baf 100644 --- a/doc/source/cli/command-objects/image.rst +++ b/doc/source/cli/command-objects/image.rst @@ -57,7 +57,7 @@ Create/upload an image [--force] [--checksum ] [--protected | --unprotected] - [--public | --private] + [--public | --private | --community | --shared] [--property [...] ] [--tag [...] ] [--project [--project-domain ]] @@ -143,6 +143,14 @@ Create/upload an image Image is inaccessible to the public (default) +.. option:: --community + + Image is accessible to the community + +.. option:: --shared + + Image can be shared + .. option:: --property Set a property on this image (repeat for multiple values) @@ -310,7 +318,7 @@ Set image properties [--disk-format ] [--size ] [--protected | --unprotected] - [--public | --private] + [--public | --private | --community | --shared] [--store ] [--location ] [--copy-from ] @@ -376,6 +384,14 @@ Set image properties Image is inaccessible to the public (default) +.. option:: --community + + Image is accessible to the community + +.. option:: --shared + + Image can be shared + .. option:: --store Upload image to this store diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index ad21cbd7ec..2b171410f4 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -205,6 +205,16 @@ def get_parser(self, prog_name): action="store_true", help=_("Image is inaccessible to the public (default)"), ) + public_group.add_argument( + "--community", + action="store_true", + help=_("Image is accessible to the community"), + ) + public_group.add_argument( + "--shared", + action="store_true", + help=_("Image can be shared"), + ) parser.add_argument( "--property", dest="properties", @@ -260,7 +270,7 @@ def take_action(self, parsed_args): kwargs = {} copy_attrs = ('name', 'id', 'container_format', 'disk_format', - 'min_disk', 'min_ram', 'tags') + 'min_disk', 'min_ram', 'tags', 'visibility') for attr in copy_attrs: if attr in parsed_args: val = getattr(parsed_args, attr, None) @@ -288,7 +298,10 @@ def take_action(self, parsed_args): kwargs['visibility'] = 'public' if parsed_args.private: kwargs['visibility'] = 'private' - + if parsed_args.community: + kwargs['visibility'] = 'community' + if parsed_args.shared: + kwargs['visibility'] = 'shared' # Handle deprecated --owner option project_arg = parsed_args.project if parsed_args.owner: @@ -698,6 +711,16 @@ def get_parser(self, prog_name): action="store_true", help=_("Image is inaccessible to the public (default)"), ) + public_group.add_argument( + "--community", + action="store_true", + help=_("Image is accessible to the community"), + ) + public_group.add_argument( + "--shared", + action="store_true", + help=_("Image can be shared"), + ) parser.add_argument( "--property", dest="properties", @@ -817,7 +840,7 @@ def take_action(self, parsed_args): copy_attrs = ('architecture', 'container_format', 'disk_format', 'file', 'instance_id', 'kernel_id', 'locations', 'min_disk', 'min_ram', 'name', 'os_distro', 'os_version', - 'prefix', 'progress', 'ramdisk_id', 'tags') + 'prefix', 'progress', 'ramdisk_id', 'tags', 'visibility') for attr in copy_attrs: if attr in parsed_args: val = getattr(parsed_args, attr, None) @@ -845,7 +868,10 @@ def take_action(self, parsed_args): kwargs['visibility'] = 'public' if parsed_args.private: kwargs['visibility'] = 'private' - + if parsed_args.community: + kwargs['visibility'] = 'community' + if parsed_args.shared: + kwargs['visibility'] = 'shared' # Handle deprecated --owner option project_arg = parsed_args.project if parsed_args.owner: diff --git a/releasenotes/notes/image_set_visibility-babf4ff2f687d465.yaml b/releasenotes/notes/image_set_visibility-babf4ff2f687d465.yaml new file mode 100644 index 0000000000..c521ac1858 --- /dev/null +++ b/releasenotes/notes/image_set_visibility-babf4ff2f687d465.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - Add ``--community`` and ``--shared`` options to the ``image create`` and + ``image set`` commands to allow image owners to share images across + multiple projects without explicitly creating image members. + “Community images” will not appear in user’s default + image listings. From 2e83c987f10217a341f894f9e81bf3b6df8efc06 Mon Sep 17 00:00:00 2001 From: Jeremy Liu Date: Tue, 4 Jul 2017 10:29:14 +0800 Subject: [PATCH 1720/3095] Add missing barbican commands Change-Id: Id5be173799cd65e80a52f01f77d1afccf089412c --- doc/source/cli/commands.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/source/cli/commands.rst b/doc/source/cli/commands.rst index 2a05d5c91b..8dca20c86b 100644 --- a/doc/source/cli/commands.rst +++ b/doc/source/cli/commands.rst @@ -169,6 +169,8 @@ The following are known `Objects` used by OpenStack conflicts when creating new plugins. For a complete list check out :ref:`plugin-commands`. +* ``acl``: (**Key Manager (Barbican)**) +* ``acl user``: (**Key Manager (Barbican)**) * ``action definition``: (**Workflow Engine (Mistral)**) * ``action execution``: (**Workflow Engine (Mistral)**) * ``baremetal``: (**Baremetal (Ironic)**) From 79b992b53bfea9f3d8e2e073417dbf8f201f37db Mon Sep 17 00:00:00 2001 From: Javier Pena Date: Tue, 4 Jul 2017 16:53:11 +0200 Subject: [PATCH 1721/3095] Fix man page build https://review.openstack.org/473964 moved the man page rst from the doc/source/man directory into doc/source/cli/man, so we need to adjust the path in conf.py to avoid issues when running: python setup.py build_sphinx -b man Change-Id: I1ab09bf298beef756b233c7e17bf052f7af4de51 --- doc/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index a90fca64ba..fc7520fd0d 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -236,7 +236,7 @@ # (source start file, name, description, authors, manual section). man_pages = [ ( - 'man/openstack', + 'cli/man/openstack', 'openstack', u'OpenStack Command Line Client', [u'OpenStack contributors'], From c17819ab58db7ded30644d63575d47ee3c963ada Mon Sep 17 00:00:00 2001 From: Rodolfo Alonso Hernandez Date: Thu, 16 Feb 2017 18:55:02 +0000 Subject: [PATCH 1722/3095] Add new parameter "is_default" to Network QoS policy. Add a set of exclusive parameters to the Network QoS policy: --default: makes this policy the default policy for the project to which the qos policy belongs. --no-default: unset the property. Closes-Bug: #1639220 Depends-On: If5ff2b00fa828f93aa089e275ddbd1ff542b79d4 Depends-On: Ibe7b7881cb190bfd5582f35b6de51a8bc21135de Change-Id: I0269b837dc29bbd8ee2089d847cadb72d800fa30 --- .../command-objects/network-qos-policy.rst | 18 ++++++++ .../network/v2/network_qos_policy.py | 44 ++++++++++++++----- .../network/v2/test_network_qos_policy.py | 13 ++++++ .../tests/unit/network/v2/fakes.py | 1 + .../network/v2/test_network_qos_policy.py | 42 ++++++++++++++++-- ...o-network-qos-policy-89b11d4df032a789.yaml | 7 +++ 6 files changed, 112 insertions(+), 13 deletions(-) create mode 100644 releasenotes/notes/add-is-default-to-network-qos-policy-89b11d4df032a789.yaml diff --git a/doc/source/cli/command-objects/network-qos-policy.rst b/doc/source/cli/command-objects/network-qos-policy.rst index a75c32fe7b..2e86f8f3ac 100644 --- a/doc/source/cli/command-objects/network-qos-policy.rst +++ b/doc/source/cli/command-objects/network-qos-policy.rst @@ -20,6 +20,7 @@ Create new Network QoS policy [--share | --no-share] [--project ] [--project-domain ] + [--default | --no-default] .. option:: --description @@ -43,6 +44,14 @@ Create new Network QoS policy Domain the project belongs to (name or ID). This can be used in case collisions between project names exist. +.. option:: --default + + Set this as a default network QoS policy + +.. option:: --no-default + + Set this as a non-default network QoS policy + .. _network_qos_policy_create-name: .. describe:: @@ -105,6 +114,7 @@ Set Network QoS policy properties [--name ] [--description ] [--share | --no-share] + [--default | --no-default] .. option:: --name @@ -123,6 +133,14 @@ Set Network QoS policy properties Make the QoS policy not accessible by other projects +.. option:: --default + + Set this as a default network QoS policy + +.. option:: --no-default + + Set this as a non-default network QoS policy + .. _network_qos_policy_set-qos-policy: .. describe:: diff --git a/openstackclient/network/v2/network_qos_policy.py b/openstackclient/network/v2/network_qos_policy.py index fef3ec88a2..2c6b841b49 100644 --- a/openstackclient/network/v2/network_qos_policy.py +++ b/openstackclient/network/v2/network_qos_policy.py @@ -45,7 +45,15 @@ def _get_attrs(client_manager, parsed_args): attrs['shared'] = True if parsed_args.no_share: attrs['shared'] = False - if parsed_args.project is not None: + # NOTE(ralonsoh): 'default' and 'no_default' parameters are defined only in + # create and set commands context only. + if 'default' in parsed_args and parsed_args.default: + attrs['is_default'] = True + if 'no_default' in parsed_args and parsed_args.no_default: + attrs['is_default'] = False + # NOTE(ralonsoh): 'project' parameter is defined only in create and list + # commands context only. + if 'project' in parsed_args and parsed_args.project is not None: identity_client = client_manager.identity project_id = identity_common.find_project( identity_client, @@ -93,6 +101,17 @@ def get_parser(self, prog_name): help=_("Owner's project (name or ID)") ) identity_common.add_project_domain_option_to_parser(parser) + default_group = parser.add_mutually_exclusive_group() + default_group.add_argument( + '--default', + action='store_true', + help=_("Set this as a default network QoS policy"), + ) + default_group.add_argument( + '--no-default', + action='store_true', + help=_("Set this as a non-default network QoS policy"), + ) return parser def take_action(self, parsed_args): @@ -170,12 +189,14 @@ def take_action(self, parsed_args): 'id', 'name', 'is_shared', + 'is_default', 'project_id', ) column_headers = ( 'ID', 'Name', 'Shared', + 'Default', 'Project', ) attrs = _get_attrs(self.app.client_manager, parsed_args) @@ -219,6 +240,17 @@ def get_parser(self, prog_name): action='store_true', help=_('Make the QoS policy not accessible by other projects'), ) + default_group = parser.add_mutually_exclusive_group() + default_group.add_argument( + '--default', + action='store_true', + help=_("Set this as a default network QoS policy"), + ) + default_group.add_argument( + '--no-default', + action='store_true', + help=_("Set this as a non-default network QoS policy"), + ) return parser def take_action(self, parsed_args): @@ -226,15 +258,7 @@ def take_action(self, parsed_args): obj = client.find_qos_policy( parsed_args.policy, ignore_missing=False) - attrs = {} - if parsed_args.name is not None: - attrs['name'] = parsed_args.name - if parsed_args.share: - attrs['shared'] = True - if parsed_args.no_share: - attrs['shared'] = False - if parsed_args.description is not None: - attrs['description'] = parsed_args.description + attrs = _get_attrs(self.app.client_manager, parsed_args) client.update_qos_policy(obj, **attrs) diff --git a/openstackclient/tests/functional/network/v2/test_network_qos_policy.py b/openstackclient/tests/functional/network/v2/test_network_qos_policy.py index 282efbfa7f..ac3e0fc6c4 100644 --- a/openstackclient/tests/functional/network/v2/test_network_qos_policy.py +++ b/openstackclient/tests/functional/network/v2/test_network_qos_policy.py @@ -68,3 +68,16 @@ def test_qos_policy_set(self): raw_output = self.openstack('network qos policy show ' + self.NAME + opts) self.assertEqual("True\n", raw_output) + + def test_qos_policy_default(self): + self.openstack('network qos policy set --default ' + self.NAME) + opts = self.get_opts(['is_default']) + raw_output = self.openstack('network qos policy show ' + self.NAME + + opts) + self.assertEqual("True\n", raw_output) + + self.openstack('network qos policy set --no-default ' + self.NAME) + opts = self.get_opts(['is_default']) + raw_output = self.openstack('network qos policy show ' + self.NAME + + opts) + self.assertEqual("False\n", raw_output) diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index 66b35cc5ac..0b8eee90b0 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -841,6 +841,7 @@ def create_one_qos_policy(attrs=None): qos_policy_attrs = { 'name': 'qos-policy-name-' + uuid.uuid4().hex, 'id': qos_id, + 'is_default': False, 'tenant_id': 'project-id-' + uuid.uuid4().hex, 'shared': False, 'description': 'qos-policy-description-' + uuid.uuid4().hex, diff --git a/openstackclient/tests/unit/network/v2/test_network_qos_policy.py b/openstackclient/tests/unit/network/v2/test_network_qos_policy.py index 667f501514..e7239932a5 100644 --- a/openstackclient/tests/unit/network/v2/test_network_qos_policy.py +++ b/openstackclient/tests/unit/network/v2/test_network_qos_policy.py @@ -48,6 +48,7 @@ class TestCreateNetworkQosPolicy(TestQosPolicy): columns = ( 'description', 'id', + 'is_default', 'name', 'project_id', 'rules', @@ -57,6 +58,7 @@ class TestCreateNetworkQosPolicy(TestQosPolicy): data = ( new_qos_policy.description, new_qos_policy.id, + new_qos_policy.is_default, new_qos_policy.name, new_qos_policy.project_id, new_qos_policy.rules, @@ -106,12 +108,14 @@ def test_create_all_options(self): '--project', self.project.name, self.new_qos_policy.name, '--description', 'QoS policy description', + '--default', ] verifylist = [ ('share', True), ('project', self.project.name), ('name', self.new_qos_policy.name), ('description', 'QoS policy description'), + ('default', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -122,6 +126,28 @@ def test_create_all_options(self): 'tenant_id': self.project.id, 'name': self.new_qos_policy.name, 'description': 'QoS policy description', + 'is_default': True, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_no_default(self): + arglist = [ + self.new_qos_policy.name, + '--no-default' + ] + verifylist = [ + ('project', None), + ('name', self.new_qos_policy.name), + ('default', False), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_qos_policy.assert_called_once_with(**{ + 'name': self.new_qos_policy.name, + 'is_default': False, }) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -220,6 +246,7 @@ class TestListNetworkQosPolicy(TestQosPolicy): 'ID', 'Name', 'Shared', + 'Default', 'Project', ) data = [] @@ -228,6 +255,7 @@ class TestListNetworkQosPolicy(TestQosPolicy): qos_policy.id, qos_policy.name, qos_policy.shared, + qos_policy.is_default, qos_policy.project_id, )) @@ -333,17 +361,19 @@ def test_set_nothing(self): self._qos_policy, **attrs) self.assertIsNone(result) - def test_set_name_share_description(self): + def test_set_name_share_description_default(self): arglist = [ '--name', 'new_qos_policy', '--share', '--description', 'QoS policy description', + '--default', self._qos_policy.name, ] verifylist = [ ('name', 'new_qos_policy'), ('share', True), ('description', 'QoS policy description'), + ('default', True), ('policy', self._qos_policy.name), ] @@ -353,25 +383,29 @@ def test_set_name_share_description(self): 'name': 'new_qos_policy', 'description': 'QoS policy description', 'shared': True, + 'is_default': True, } self.network.update_qos_policy.assert_called_with( self._qos_policy, **attrs) self.assertIsNone(result) - def test_set_no_share(self): + def test_set_no_share_no_default(self): arglist = [ '--no-share', + '--no-default', self._qos_policy.name, ] verifylist = [ ('no_share', True), + ('no_default', True), ('policy', self._qos_policy.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) attrs = { - 'shared': False + 'shared': False, + 'is_default': False } self.network.update_qos_policy.assert_called_with( self._qos_policy, **attrs) @@ -386,6 +420,7 @@ class TestShowNetworkQosPolicy(TestQosPolicy): columns = ( 'description', 'id', + 'is_default', 'name', 'project_id', 'rules', @@ -394,6 +429,7 @@ class TestShowNetworkQosPolicy(TestQosPolicy): data = ( _qos_policy.description, _qos_policy.id, + _qos_policy.is_default, _qos_policy.name, _qos_policy.project_id, _qos_policy.rules, diff --git a/releasenotes/notes/add-is-default-to-network-qos-policy-89b11d4df032a789.yaml b/releasenotes/notes/add-is-default-to-network-qos-policy-89b11d4df032a789.yaml new file mode 100644 index 0000000000..abd08f0362 --- /dev/null +++ b/releasenotes/notes/add-is-default-to-network-qos-policy-89b11d4df032a789.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Add ``--default`` and ``--no-default`` options to + ``network qos policy create`` and ``network qos policy set`` + comamnds. + [Bug `1639220 `_] From ae35a29169ab460cdd5d6b8c26d376c000a5c10e Mon Sep 17 00:00:00 2001 From: Honza Pokorny Date: Wed, 21 Jun 2017 12:53:38 -0300 Subject: [PATCH 1723/3095] Allow objects to be streamed to stdout Change-Id: Icd8de6b2122fe77926d93da9bda08f56c3672a7a --- doc/source/cli/command-objects/object.rst | 3 +- openstackclient/api/object_store_v1.py | 15 ++++--- openstackclient/object/v1/object.py | 3 +- .../tests/functional/object/v1/test_object.py | 4 ++ openstackclient/tests/unit/object/v1/fakes.py | 4 ++ .../tests/unit/object/v1/test_object_all.py | 43 +++++++++++++++++++ .../notes/object-stdout-db76cc500948b0e8.yaml | 5 +++ 7 files changed, 70 insertions(+), 7 deletions(-) create mode 100644 releasenotes/notes/object-stdout-db76cc500948b0e8.yaml diff --git a/doc/source/cli/command-objects/object.rst b/doc/source/cli/command-objects/object.rst index 6323c4ed0b..4cba38ee59 100644 --- a/doc/source/cli/command-objects/object.rst +++ b/doc/source/cli/command-objects/object.rst @@ -114,7 +114,8 @@ Save object locally .. option:: --file - Destination filename (defaults to object name) + Destination filename (defaults to object name); + using - as the filename will print the file to stdout .. describe:: diff --git a/openstackclient/api/object_store_v1.py b/openstackclient/api/object_store_v1.py index 74c4a46f20..3103352503 100644 --- a/openstackclient/api/object_store_v1.py +++ b/openstackclient/api/object_store_v1.py @@ -16,6 +16,7 @@ import io import logging import os +import sys from osc_lib import utils import six @@ -376,12 +377,16 @@ def object_save( stream=True, ) if response.status_code == 200: - if not os.path.exists(os.path.dirname(file)): - if len(os.path.dirname(file)) > 0: - os.makedirs(os.path.dirname(file)) - with open(file, 'wb') as f: + if file == '-': for chunk in response.iter_content(64 * 1024): - f.write(chunk) + sys.stdout.write(chunk) + else: + if not os.path.exists(os.path.dirname(file)): + if len(os.path.dirname(file)) > 0: + os.makedirs(os.path.dirname(file)) + with open(file, 'wb') as f: + for chunk in response.iter_content(64 * 1024): + f.write(chunk) def object_set( self, diff --git a/openstackclient/object/v1/object.py b/openstackclient/object/v1/object.py index e79cea48ea..aeb0253653 100644 --- a/openstackclient/object/v1/object.py +++ b/openstackclient/object/v1/object.py @@ -204,7 +204,8 @@ def get_parser(self, prog_name): parser.add_argument( "--file", metavar="", - help=_("Destination filename (defaults to object name)"), + help=_("Destination filename (defaults to object name); using '-'" + " as the filename will print the file to stdout"), ) parser.add_argument( 'container', diff --git a/openstackclient/tests/functional/object/v1/test_object.py b/openstackclient/tests/functional/object/v1/test_object.py index 776cf47c2c..0927d706dc 100644 --- a/openstackclient/tests/functional/object/v1/test_object.py +++ b/openstackclient/tests/functional/object/v1/test_object.py @@ -66,6 +66,10 @@ def _test_object(self, object_file): + ' ' + object_file + ' --file ' + tmp_file) # TODO(stevemar): Assert returned fields + raw_output = self.openstack('object save ' + self.CONTAINER_NAME + + ' ' + object_file + ' --file -') + self.assertEqual(raw_output, 'test content') + self.openstack('object show ' + self.CONTAINER_NAME + ' ' + object_file) # TODO(stevemar): Assert returned fields diff --git a/openstackclient/tests/unit/object/v1/fakes.py b/openstackclient/tests/unit/object/v1/fakes.py index 72646d25bb..5d65d1066f 100644 --- a/openstackclient/tests/unit/object/v1/fakes.py +++ b/openstackclient/tests/unit/object/v1/fakes.py @@ -13,6 +13,8 @@ # under the License. # +import six + from keystoneauth1 import session from openstackclient.api import object_store_v1 as object_store @@ -67,6 +69,8 @@ 'last_modified': object_modified_1, } +object_1_content = six.b('object 1 content') + OBJECT_2 = { 'name': object_name_2, 'bytes': object_bytes_2, diff --git a/openstackclient/tests/unit/object/v1/test_object_all.py b/openstackclient/tests/unit/object/v1/test_object_all.py index f215836ed0..363f2ea21e 100644 --- a/openstackclient/tests/unit/object/v1/test_object_all.py +++ b/openstackclient/tests/unit/object/v1/test_object_all.py @@ -13,8 +13,10 @@ import copy +import mock from osc_lib import exceptions from requests_mock.contrib import fixture +import six from openstackclient.object.v1 import object as object_cmds from openstackclient.tests.unit.object.v1 import fakes as object_fakes @@ -202,3 +204,44 @@ def test_object_show(self): 'manifest', ) self.assertEqual(datalist, data) + + +class TestObjectSave(TestObjectAll): + + def setUp(self): + super(TestObjectSave, self).setUp() + + # Get the command object to test + self.cmd = object_cmds.SaveObject(self.app, None) + + def test_save_to_stdout(self): + self.requests_mock.register_uri( + 'GET', + object_fakes.ENDPOINT + + '/' + + object_fakes.container_name + + '/' + + object_fakes.object_name_1, + status_code=200, + content=object_fakes.object_1_content + ) + + arglist = [ + object_fakes.container_name, + object_fakes.object_name_1, + '--file', + '-' + ] + + verifylist = [ + ('container', object_fakes.container_name), + ('object', object_fakes.object_name_1), + ('file', '-'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + with mock.patch('sys.stdout', new=six.BytesIO()) as fake_stdout: + self.cmd.take_action(parsed_args) + + self.assertEqual(fake_stdout.getvalue(), object_fakes.object_1_content) diff --git a/releasenotes/notes/object-stdout-db76cc500948b0e8.yaml b/releasenotes/notes/object-stdout-db76cc500948b0e8.yaml new file mode 100644 index 0000000000..29b21131bc --- /dev/null +++ b/releasenotes/notes/object-stdout-db76cc500948b0e8.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add support for streaming Swift objects to stdout when using the ``object + save`` command, by specifying ``--filename -``. From eb19c167ead44a5df7a3ac072af8c4a0a895f36e Mon Sep 17 00:00:00 2001 From: yushangbin Date: Thu, 6 Jul 2017 15:26:24 +0800 Subject: [PATCH 1724/3095] Add the other commands for karbor osc plugin These commands are implemented in the latest python-karborclient project. Change-Id: I1f57656cbad8f04b26e5210043b1fa979116f053 --- doc/source/cli/commands.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/source/cli/commands.rst b/doc/source/cli/commands.rst index 8dca20c86b..5a7977e483 100644 --- a/doc/source/cli/commands.rst +++ b/doc/source/cli/commands.rst @@ -197,6 +197,13 @@ conflicts when creating new plugins. For a complete list check out * ``dataprocessing image tags``: (**Data Processing (Sahara)**) * ``dataprocessing plugin``: (**Data Processing (Sahara)**) * ``data protection plan``: (**Data Protection (Karbor)**) +* ``data protection restore``: (**Data Protection (Karbor)**) +* ``data protection provider``: (**Data Protection (Karbor)**) +* ``data protection protectable``: (**Data Protection (Karbor)**) +* ``data protection protectable instance``: (**Data Protection (Karbor)**) +* ``data protection trigger``: (**Data Protection (Karbor)**) +* ``data protection checkpoint``: (**Data Protection (Karbor)**) +* ``data protection scheduledoperation``: (**Data Protection (Karbor)**) * ``loadbalancer``: (**Load Balancer (Octavia)**) * ``loadbalancer healthmonitor``: (**Load Balancer (Octavia)**) * ``loadbalancer l7policy``: (**Load Balancer (Octavia)**) From faf6e16120206de763c1570698f71114307dab98 Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Thu, 6 Jul 2017 17:51:45 +0000 Subject: [PATCH 1725/3095] Use openstackdocstheme in release note In the release notes build, openstackdocstheme is specified in the sphinx extension list, but it is actually not used. This commit configures openstackdocstheme as suggested in the openstackdocstheme document. Also specifies display_toc False in html_theme_options. This prevents openstackdocstheme to add the local TOC automatically. OSC has several releases during a single development cycle and it leads to a long TOC at the top of individual pages. It loses the readability and we can see version numbers in the left sidebar, so we can safely disable the local TOC in the release notes. [1] https://docs.openstack.org/openstackdocstheme/latest/ Change-Id: Iae08d309f7589bb13a1766f6fded70673ba24047 --- releasenotes/source/conf.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py index 5d270d9303..14800913c6 100644 --- a/releasenotes/source/conf.py +++ b/releasenotes/source/conf.py @@ -43,6 +43,11 @@ 'sphinx.ext.extlinks', ] +# openstackdocstheme options +repository_name = 'openstack/python-openstackclient' +bug_project = 'python-openstackclient' +bug_tag = '' + # Set aliases for extlinks # * lpbug - generic Launchpad bug :lpbug:`123456` # * oscbp - OSC blueprints :oscbp:`Blue Print ` @@ -132,12 +137,14 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'default' +html_theme = 'openstackdocs' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -# html_theme_options = {} +html_theme_options = { + 'display_toc': False, +} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] From 2c57f7bfb2dc39afd361b85639df1ac4e1d417f5 Mon Sep 17 00:00:00 2001 From: Boris Pavlovic Date: Thu, 29 Jun 2017 15:17:33 -0700 Subject: [PATCH 1726/3095] Add server list -n and --no-name-lookup arguments Remove translation of Image ID and Flavor ID to Image and Flavor names In large environments amount of images can be very large (thousands) Which requires ~hundreds of requests to Glance to get all images (by default client request only 20 images) As a result listing even few servers is going to take minutes This patch allows to avoid these queries by not doing translation, which allows one to get information about servers in seconds. Change-Id: I4ae00e6324a41c4c79bf5b620179dae99aea5431 --- doc/source/cli/command-objects/server.rst | 5 ++ openstackclient/compute/v2/server.py | 45 +++++++++++------ .../tests/unit/compute/v2/test_server.py | 50 +++++++++++++++++++ 3 files changed, 84 insertions(+), 16 deletions(-) diff --git a/doc/source/cli/command-objects/server.rst b/doc/source/cli/command-objects/server.rst index cc5806031c..dee2721943 100644 --- a/doc/source/cli/command-objects/server.rst +++ b/doc/source/cli/command-objects/server.rst @@ -330,6 +330,7 @@ List servers [--all-projects] [--project [--project-domain ]] [--long] + [-n | --no-name-lookup] [--marker ] [--limit ] [--deleted] @@ -397,6 +398,10 @@ List servers List additional fields in output +.. option:: --no-name-lookup + + Skips image and flavor names lookup + .. option:: --marker The last server of the previous page. Display list of servers diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index e8846d1619..cb520d620e 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -921,6 +921,12 @@ def get_parser(self, prog_name): default=False, help=_('List additional fields in output'), ) + parser.add_argument( + '-n', '--no-name-lookup', + action='store_true', + default=False, + help=_('Skip flavor and image name lookup.'), + ) parser.add_argument( '--marker', metavar='', @@ -1054,21 +1060,26 @@ def take_action(self, parsed_args): 'OS-EXT-AZ:availability_zone', 'OS-EXT-SRV-ATTR:host', ] - else: + elif parsed_args.no_name_lookup: columns = ( 'ID', 'Name', 'Status', - 'Networks', - 'Image Name', + 'Image ID', + 'Flavor ID', ) - column_headers = ( + column_headers = tuple(columns) + mixed_case_fields = [] + + else: + columns = ( 'ID', 'Name', 'Status', 'Networks', 'Image Name', ) + column_headers = tuple(columns) mixed_case_fields = [] marker_id = None @@ -1084,23 +1095,25 @@ def take_action(self, parsed_args): # Create a dict that maps image_id to image object. # Needed so that we can display the "Image Name" column. # "Image Name" is not crucial, so we swallow any exceptions. - try: - images_list = self.app.client_manager.image.images.list() - for i in images_list: - images[i.id] = i - except Exception: - pass + if not parsed_args.no_name_lookup: + try: + images_list = self.app.client_manager.image.images.list() + for i in images_list: + images[i.id] = i + except Exception: + pass flavors = {} # Create a dict that maps flavor_id to flavor object. # Needed so that we can display the "Flavor Name" column. # "Flavor Name" is not crucial, so we swallow any exceptions. - try: - flavors_list = compute_client.flavors.list() - for i in flavors_list: - flavors[i.id] = i - except Exception: - pass + if not parsed_args.no_name_lookup: + try: + flavors_list = compute_client.flavors.list() + for i in flavors_list: + flavors[i.id] = i + except Exception: + pass # Populate image_name, image_id, flavor_name and flavor_id attributes # of server objects so that we can display those columns. diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 084171ac1c..3e71ce0771 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -395,6 +395,8 @@ def test_server_create_minimal(self): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist(), data) + self.assertFalse(self.images_mock.called) + self.assertFalse(self.flavors_mock.called) def test_server_create_with_options(self): arglist = [ @@ -1457,6 +1459,14 @@ class TestServerList(TestServer): 'Properties', ) + columns_no_name_lookup = ( + 'ID', + 'Name', + 'Status', + 'Image ID', + 'Flavor ID', + ) + def setUp(self): super(TestServerList, self).setUp() @@ -1515,6 +1525,7 @@ def setUp(self): # Prepare data returned by fake Nova API. self.data = [] self.data_long = [] + self.data_no_name_lookup = [] Image = collections.namedtuple('Image', 'id name') self.images_mock.list.return_value = [ @@ -1553,6 +1564,13 @@ def setUp(self): getattr(s, 'OS-EXT-SRV-ATTR:host'), s.Metadata, )) + self.data_no_name_lookup.append(( + s.id, + s.name, + s.status, + s.image['id'], + s.flavor['id'] + )) def test_server_list_no_option(self): arglist = [] @@ -1585,6 +1603,38 @@ def test_server_list_long_option(self): self.assertEqual(self.columns_long, columns) self.assertEqual(tuple(self.data_long), tuple(data)) + def test_server_list_no_name_lookup_option(self): + arglist = [ + '--no-name-lookup', + ] + verifylist = [ + ('all_projects', False), + ('no_name_lookup', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.servers_mock.list.assert_called_with(**self.kwargs) + self.assertEqual(self.columns_no_name_lookup, columns) + self.assertEqual(tuple(self.data_no_name_lookup), tuple(data)) + + def test_server_list_n_option(self): + arglist = [ + '-n', + ] + verifylist = [ + ('all_projects', False), + ('no_name_lookup', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.servers_mock.list.assert_called_with(**self.kwargs) + self.assertEqual(self.columns_no_name_lookup, columns) + self.assertEqual(tuple(self.data_no_name_lookup), tuple(data)) + def test_server_list_with_image(self): arglist = [ From 18c532377a2f6d87adef80465fea7ed50d5a17d1 Mon Sep 17 00:00:00 2001 From: Ankur Gupta Date: Wed, 12 Oct 2016 22:39:35 -0500 Subject: [PATCH 1727/3095] Network L3 Router Commands for OSC Implements: blueprint network-l3-commands Co-Authored-By: Akihiro Motoki Change-Id: Ia24d76227e164062e89a74c1621b8acb830b26cf --- .../cli/command-objects/network-agent.rst | 121 +++++++++--- doc/source/cli/command-objects/router.rst | 5 + openstackclient/network/v2/network_agent.py | 121 +++++++++--- openstackclient/network/v2/router.py | 32 +++- .../network/v2/test_network_agent.py | 36 ++++ .../functional/network/v2/test_router.py | 34 ++++ .../unit/network/v2/test_network_agent.py | 173 +++++++++++++++--- .../tests/unit/network/v2/test_router.py | 50 +++++ ...work-l3-adv-commands-cc1df715a184f1b2.yaml | 8 + setup.cfg | 2 + 10 files changed, 504 insertions(+), 78 deletions(-) create mode 100644 releasenotes/notes/bp-network-l3-adv-commands-cc1df715a184f1b2.yaml diff --git a/doc/source/cli/command-objects/network-agent.rst b/doc/source/cli/command-objects/network-agent.rst index f69d0ece69..2ceb263f1b 100644 --- a/doc/source/cli/command-objects/network-agent.rst +++ b/doc/source/cli/command-objects/network-agent.rst @@ -33,7 +33,34 @@ Add network to an agent .. describe:: - Network to be added to an agent (ID or name) + Network to be added to an agent (name or ID) + +network agent add router +------------------------ + +Add router to an agent + +.. program:: network agent add router +.. code:: bash + + openstack network agent add router + [--l3] + + + +.. option:: --l3 + + Add router to L3 agent + +.. _network_agent_add_router-agent-id: +.. describe:: + + Agent to which a router is added (ID only) + +.. _network_agent_add_router-router: +.. describe:: + + Router to be added to an agent (name or ID) network agent delete -------------------- @@ -62,7 +89,8 @@ List network agents openstack network agent list [--agent-type ] [--host ] - [--network ] + [--network | --router ] + [--long] .. option:: --agent-type @@ -77,7 +105,69 @@ List network agents .. option:: --network - List agents hosting a network (ID or name) + List agents hosting a network (name or ID) + +.. option:: --router + + List agents hosting this router (name or ID) + +.. option:: --long + + List additional fields in output + +network agent remove network +---------------------------- + +Remove network from an agent + +.. program:: network agent remove network +.. code:: bash + + openstack network agent remove network + [--dhcp] + + + +.. option:: --dhcp + + Remove network from DHCP agent + +.. _network_agent_remove_network-agent-id: +.. describe:: + + Agent to which a network is removed (ID only) + +.. _network_agent_remove_network-network: +.. describe:: + + Network to be removed from an agent (name or ID) + +network agent remove router +--------------------------- + +Remove router from an agent + +.. program:: network agent remove router +.. code:: bash + + openstack agent remove router + [--l3] + + + +.. option:: --l3 + + Remove router from L3 agent + +.. _network_agent_remove_router-agent-id: +.. describe:: + + Agent from which router will be removed (ID only) + +.. _network_agent_remove_router-router: +.. describe:: + + Router to be removed from an agent (name or ID) network agent set ----------------- @@ -124,28 +214,3 @@ Display network agent details .. describe:: Network agent to display (ID only) - -network agent remove network ----------------------------- - -Remove network from an agent - -.. program:: network agent remove network -.. code:: bash - - openstack network agent remove network - [--dhcp] - - - -.. describe:: --dhcp - - Remove network from DHCP agent. - -.. describe:: - - Agent to which a network is removed (ID only) - -.. describe:: - - Network to be removed from an agent (ID or name) diff --git a/doc/source/cli/command-objects/router.rst b/doc/source/cli/command-objects/router.rst index 50e791ea7d..8bdf81dbf7 100644 --- a/doc/source/cli/command-objects/router.rst +++ b/doc/source/cli/command-objects/router.rst @@ -155,6 +155,11 @@ List routers [--enable | --disable] [--long] [--project [--project-domain ]] + [--agent ] + +.. option:: --agent + + List routers hosted by an agent (ID only) .. option:: --long diff --git a/openstackclient/network/v2/network_agent.py b/openstackclient/network/v2/network_agent.py index 1334f77e17..ed4970a488 100644 --- a/openstackclient/network/v2/network_agent.py +++ b/openstackclient/network/v2/network_agent.py @@ -26,10 +26,16 @@ LOG = logging.getLogger(__name__) +def _format_alive(alive): + return ":-)" if alive else "XXX" + + def _format_admin_state(state): return 'UP' if state else 'DOWN' _formatters = { + 'is_alive': _format_alive, + 'alive': _format_alive, 'admin_state_up': _format_admin_state, 'is_admin_state_up': _format_admin_state, 'configurations': utils.format_dict, @@ -60,7 +66,7 @@ def get_parser(self, prog_name): parser.add_argument( 'network', metavar='', - help=_('Network to be added to an agent (ID or name)')) + help=_('Network to be added to an agent (name or ID)')) return parser @@ -78,6 +84,37 @@ def take_action(self, parsed_args): exceptions.CommandError(msg) +class AddRouterToAgent(command.Command): + _description = _("Add router to an agent") + + def get_parser(self, prog_name): + parser = super(AddRouterToAgent, self).get_parser(prog_name) + parser.add_argument( + '--l3', + action='store_true', + help=_('Add router to an L3 agent') + ) + parser.add_argument( + 'agent_id', + metavar='', + help=_("Agent to which a router is added (ID only)") + ) + parser.add_argument( + 'router', + metavar='', + help=_("Router to be added to an agent (name or ID)") + ) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + agent = client.get_agent(parsed_args.agent_id) + router = client.find_router(parsed_args.router, ignore_missing=False) + if parsed_args.l3: + client.add_router_to_agent(agent, router) + + class DeleteNetworkAgent(command.Command): _description = _("Delete network agent(s)") @@ -135,11 +172,24 @@ def get_parser(self, prog_name): metavar='', help=_("List only agents running on the specified host") ) - parser.add_argument( + agent_type_group = parser.add_mutually_exclusive_group() + agent_type_group.add_argument( '--network', metavar='', help=_('List agents hosting a network (name or ID)') ) + agent_type_group.add_argument( + '--router', + metavar='', + help=_('List agents hosting this router (name or ID)') + ) + parser.add_argument( + '--long', + action='store_true', + default=False, + help=_("List additional fields in output") + ) + return parser def take_action(self, parsed_args): @@ -178,28 +228,18 @@ def take_action(self, parsed_args): } filters = {} + if parsed_args.network is not None: - columns = ( - 'id', - 'host', - 'is_admin_state_up', - 'is_alive', - ) - column_headers = ( - 'ID', - 'Host', - 'Admin State Up', - 'Alive', - ) network = client.find_network( parsed_args.network, ignore_missing=False) data = client.network_hosting_dhcp_agents(network) - - return (column_headers, - (utils.get_item_properties( - s, columns, - formatters=_formatters, - ) for s in data)) + elif parsed_args.router is not None: + if parsed_args.long: + columns += ('ha_state',) + column_headers += ('HA State',) + router = client.find_router(parsed_args.router, + ignore_missing=False) + data = client.routers_hosting_l3_agents(router) else: if parsed_args.agent_type is not None: filters['agent_type'] = key_value[parsed_args.agent_type] @@ -207,10 +247,10 @@ def take_action(self, parsed_args): filters['host'] = parsed_args.host data = client.agents(**filters) - return (column_headers, - (utils.get_item_properties( - s, columns, formatters=_formatters, - ) for s in data)) + return (column_headers, + (utils.get_item_properties( + s, columns, formatters=_formatters, + ) for s in data)) class RemoveNetworkFromAgent(command.Command): @@ -229,7 +269,7 @@ def get_parser(self, prog_name): parser.add_argument( 'network', metavar='', - help=_('Network to be removed from an agent (ID or name)')) + help=_('Network to be removed from an agent (name or ID)')) return parser def take_action(self, parsed_args): @@ -246,6 +286,37 @@ def take_action(self, parsed_args): exceptions.CommandError(msg) +class RemoveRouterFromAgent(command.Command): + _description = _("Remove router from an agent") + + def get_parser(self, prog_name): + parser = super(RemoveRouterFromAgent, self).get_parser(prog_name) + parser.add_argument( + '--l3', + action='store_true', + help=_('Remove router from an L3 agent') + ) + parser.add_argument( + 'agent_id', + metavar='', + help=_("Agent from which router will be removed (ID only)") + ) + parser.add_argument( + 'router', + metavar='', + help=_("Router to be removed from an agent (name or ID)") + ) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + agent = client.get_agent(parsed_args.agent_id) + router = client.find_router(parsed_args.router, ignore_missing=False) + if parsed_args.l3: + client.remove_router_from_agent(agent, router) + + # TODO(huanxuan): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. class SetNetworkAgent(command.Command): diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 0da91baa44..8db0c4393b 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -305,11 +305,18 @@ def get_parser(self, prog_name): help=_("List routers according to their project (name or ID)") ) identity_common.add_project_domain_option_to_parser(parser) + parser.add_argument( + '--agent', + metavar='', + help=_("List routers hosted by an agent (ID only)") + ) + return parser def take_action(self, parsed_args): identity_client = self.app.client_manager.identity client = self.app.client_manager.network + columns = ( 'id', 'name', @@ -349,6 +356,16 @@ def take_action(self, parsed_args): ).id args['tenant_id'] = project_id args['project_id'] = project_id + + if parsed_args.agent is not None: + agent = client.get_agent(parsed_args.agent) + data = client.agent_hosted_routers(agent) + # NOTE: Networking API does not support filtering by parameters, + # so we need filtering in the client side. + data = [d for d in data if self._filter_match(d, args)] + else: + data = client.routers(**args) + if parsed_args.long: columns = columns + ( 'routes', @@ -368,13 +385,26 @@ def take_action(self, parsed_args): 'Availability zones', ) - data = client.routers(**args) return (column_headers, (utils.get_item_properties( s, columns, formatters=_formatters, ) for s in data)) + @staticmethod + def _filter_match(data, conditions): + for key, value in conditions.items(): + try: + if getattr(data, key) != value: + return False + except AttributeError: + # Some filter attributes like tenant_id or admin_state_up + # are backward compatibility in older OpenStack SDK support. + # They does not exist in the latest release. + # In this case we just skip checking such filter condition. + continue + return True + class RemovePortFromRouter(command.Command): _description = _("Remove a port from a router") diff --git a/openstackclient/tests/functional/network/v2/test_network_agent.py b/openstackclient/tests/functional/network/v2/test_network_agent.py index 1648795513..0c74ea1d05 100644 --- a/openstackclient/tests/functional/network/v2/test_network_agent.py +++ b/openstackclient/tests/functional/network/v2/test_network_agent.py @@ -137,3 +137,39 @@ def test_network_dhcp_agent_list(self): self.assertIn( agent_id, col_name ) + + def test_network_agent_list_routers(self): + """Add agent to router, list agents on router, delete.""" + name = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'router create -f json ' + name)) + + self.addCleanup(self.openstack, 'router delete ' + name) + # Get router ID + router_id = cmd_output['id'] + # Get l3 agent id + cmd_output = json.loads(self.openstack( + 'network agent list -f json --agent-type l3')) + + # Check at least one L3 agent is included in the response. + self.assertTrue(cmd_output) + agent_id = cmd_output[0]['ID'] + + # Add router to agent + self.openstack( + 'network agent add router --l3 ' + agent_id + ' ' + router_id) + + # Test router list --agent + cmd_output = json.loads(self.openstack( + 'network agent list -f json --router ' + router_id)) + + agent_ids = [x['ID'] for x in cmd_output] + self.assertIn(agent_id, agent_ids) + + # Remove router from agent + self.openstack( + 'network agent remove router --l3 ' + agent_id + ' ' + router_id) + cmd_output = json.loads(self.openstack( + 'network agent list -f json --router ' + router_id)) + agent_ids = [x['ID'] for x in cmd_output] + self.assertNotIn(agent_id, agent_ids) diff --git a/openstackclient/tests/functional/network/v2/test_router.py b/openstackclient/tests/functional/network/v2/test_router.py index 313feefc01..2e5cb5ef55 100644 --- a/openstackclient/tests/functional/network/v2/test_router.py +++ b/openstackclient/tests/functional/network/v2/test_router.py @@ -151,6 +151,40 @@ def test_router_list(self): self.assertIn(name1, names) self.assertIn(name2, names) + def test_router_list_l3_agent(self): + """Tests create router, add l3 agent, list, delete""" + name = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'router create -f json ' + name)) + + self.addCleanup(self.openstack, 'router delete ' + name) + # Get router ID + router_id = cmd_output['id'] + # Get l3 agent id + cmd_output = json.loads(self.openstack( + 'network agent list -f json --agent-type l3')) + + # Check at least one L3 agent is included in the response. + self.assertTrue(cmd_output) + agent_id = cmd_output[0]['ID'] + + # Add router to agent + self.openstack( + 'network agent add router --l3 ' + agent_id + ' ' + router_id) + + cmd_output = json.loads(self.openstack( + 'router list -f json --agent ' + agent_id)) + router_ids = [x['ID'] for x in cmd_output] + self.assertIn(router_id, router_ids) + + # Remove router from agent + self.openstack( + 'network agent remove router --l3 ' + agent_id + ' ' + router_id) + cmd_output = json.loads(self.openstack( + 'router list -f json --agent ' + agent_id)) + router_ids = [x['ID'] for x in cmd_output] + self.assertNotIn(router_id, router_ids) + def test_router_set_show_unset(self): """Tests create router, set, unset, show""" diff --git a/openstackclient/tests/unit/network/v2/test_network_agent.py b/openstackclient/tests/unit/network/v2/test_network_agent.py index 9bb3f090f3..12e40cdbc8 100644 --- a/openstackclient/tests/unit/network/v2/test_network_agent.py +++ b/openstackclient/tests/unit/network/v2/test_network_agent.py @@ -73,6 +73,46 @@ def test_add_network_to_dhcp_agent(self): self.agent, self.net) +class TestAddRouterAgent(TestNetworkAgent): + + _router = network_fakes.FakeRouter.create_one_router() + _agent = network_fakes.FakeNetworkAgent.create_one_network_agent() + + def setUp(self): + super(TestAddRouterAgent, self).setUp() + self.network.add_router_to_agent = mock.Mock() + self.cmd = network_agent.AddRouterToAgent(self.app, self.namespace) + self.network.get_agent = mock.Mock(return_value=self._agent) + self.network.find_router = mock.Mock(return_value=self._router) + + def test_add_no_options(self): + arglist = [] + verifylist = [] + + # Missing agent ID will cause command to bail + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_add_router_required_options(self): + arglist = [ + self._agent.id, + self._router.id, + '--l3', + ] + verifylist = [ + ('l3', True), + ('agent_id', self._agent.id), + ('router', self._router.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.network.add_router_to_agent.assert_called_with( + self._agent, self._router) + self.assertIsNone(result) + + class TestDeleteNetworkAgent(TestNetworkAgent): network_agents = ( @@ -171,34 +211,16 @@ class TestListNetworkAgent(TestNetworkAgent): ) data = [] for agent in network_agents: - agent.agent_type = 'DHCP agent' data.append(( agent.id, agent.agent_type, agent.host, agent.availability_zone, - agent.alive, + network_agent._format_alive(agent.alive), network_agent._format_admin_state(agent.admin_state_up), agent.binary, )) - network_agent_columns = ( - 'ID', - 'Host', - 'Admin State Up', - 'Alive', - ) - - network_agent_data = [] - - for agent in network_agents: - network_agent_data.append(( - agent.id, - agent.host, - network_agent._format_admin_state(agent.admin_state_up), - agent.alive, - )) - def setUp(self): super(TestListNetworkAgent, self).setUp() self.network.agents = mock.Mock( @@ -213,6 +235,14 @@ def setUp(self): self.network.network_hosting_dhcp_agents = mock.Mock( return_value=self.network_agents) + self.network.get_agent = mock.Mock(return_value=_testagent) + + self._testrouter = \ + network_fakes.FakeRouter.create_one_router() + self.network.find_router = mock.Mock(return_value=self._testrouter) + self.network.routers_hosting_l3_agents = mock.Mock( + return_value=self.network_agents) + # Get the command object to test self.cmd = network_agent.ListNetworkAgent(self.app, self.namespace) @@ -239,7 +269,7 @@ def test_network_agents_list_agent_type(self): columns, data = self.cmd.take_action(parsed_args) self.network.agents.assert_called_once_with(**{ - 'agent_type': self.network_agents[0].agent_type, + 'agent_type': 'DHCP agent', }) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) @@ -276,8 +306,53 @@ def test_network_agents_list_networks(self): self.network.network_hosting_dhcp_agents.assert_called_once_with( *attrs) - self.assertEqual(self.network_agent_columns, columns) - self.assertEqual(list(self.network_agent_data), list(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_network_agents_list_routers(self): + arglist = [ + '--router', self._testrouter.id, + ] + verifylist = [ + ('router', self._testrouter.id), + ('long', False) + ] + + attrs = {self._testrouter, } + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.routers_hosting_l3_agents.assert_called_once_with( + *attrs) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_network_agents_list_routers_with_long_option(self): + arglist = [ + '--router', self._testrouter.id, + '--long', + ] + verifylist = [ + ('router', self._testrouter.id), + ('long', True) + ] + + attrs = {self._testrouter, } + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.routers_hosting_l3_agents.assert_called_once_with( + *attrs) + + # Add a column 'HA State' and corresponding data. + router_agent_columns = self.columns + ('HA State',) + router_agent_data = [d + ('',) for d in self.data] + + self.assertEqual(router_agent_columns, columns) + self.assertEqual(router_agent_data, list(data)) class TestRemoveNetworkFromAgent(TestNetworkAgent): @@ -303,6 +378,16 @@ def test_show_no_options(self): self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) + def test_network_agents_list_routers_no_arg(self): + arglist = [ + '--routers', + ] + verifylist = [] + + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + def test_network_from_dhcp_agent(self): arglist = [ '--dhcp', @@ -322,6 +407,46 @@ def test_network_from_dhcp_agent(self): self.agent, self.net) +class TestRemoveRouterAgent(TestNetworkAgent): + _router = network_fakes.FakeRouter.create_one_router() + _agent = network_fakes.FakeNetworkAgent.create_one_network_agent() + + def setUp(self): + super(TestRemoveRouterAgent, self).setUp() + self.network.remove_router_from_agent = mock.Mock() + self.cmd = network_agent.RemoveRouterFromAgent(self.app, + self.namespace) + self.network.get_agent = mock.Mock(return_value=self._agent) + self.network.find_router = mock.Mock(return_value=self._router) + + def test_remove_no_options(self): + arglist = [] + verifylist = [] + + # Missing agent ID will cause command to bail + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_remove_router_required_options(self): + arglist = [ + '--l3', + self._agent.id, + self._router.id, + ] + verifylist = [ + ('l3', True), + ('agent_id', self._agent.id), + ('router', self._router.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.network.remove_router_from_agent.assert_called_with( + self._agent, self._router) + self.assertIsNone(result) + + class TestSetNetworkAgent(TestNetworkAgent): _network_agent = ( @@ -415,9 +540,9 @@ class TestShowNetworkAgent(TestNetworkAgent): 'id', ) data = ( - network_agent._format_admin_state(_network_agent.admin_state_up), + network_agent._format_admin_state(_network_agent.is_admin_state_up), _network_agent.agent_type, - _network_agent.alive, + network_agent._format_alive(_network_agent.is_alive), _network_agent.availability_zone, _network_agent.binary, utils.format_dict(_network_agent.configurations), diff --git a/openstackclient/tests/unit/network/v2/test_router.py b/openstackclient/tests/unit/network/v2/test_router.py index 02e0be9459..c153fe4a08 100644 --- a/openstackclient/tests/unit/network/v2/test_router.py +++ b/openstackclient/tests/unit/network/v2/test_router.py @@ -381,6 +381,21 @@ class TestListRouter(TestRouter): r.ha, r.tenant_id, )) + + router_agent_data = [] + for r in routers: + router_agent_data.append(( + r.id, + r.name, + r.external_gateway_info, + )) + + agents_columns = ( + 'ID', + 'Name', + 'External Gateway Info', + ) + data_long = [] for i in range(0, len(routers)): r = routers[i] @@ -407,8 +422,15 @@ def setUp(self): # Get the command object to test self.cmd = router.ListRouter(self.app, self.namespace) + self.network.agent_hosted_routers = mock.Mock( + return_value=self.routers) self.network.routers = mock.Mock(return_value=self.routers) self.network.find_extension = mock.Mock(return_value=self._extensions) + self.network.find_router = mock.Mock(return_value=self.routers[0]) + self._testagent = \ + network_fakes.FakeNetworkAgent.create_one_network_agent() + self.network.get_agent = mock.Mock(return_value=self._testagent) + self.network.get_router = mock.Mock(return_value=self.routers[0]) def test_router_list_no_options(self): arglist = [] @@ -556,6 +578,34 @@ def test_router_list_project_domain(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + def test_router_list_agents_no_args(self): + arglist = [ + '--agents', + ] + verifylist = [] + + # Missing required router ID should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_router_list_agents(self): + arglist = [ + '--agent', self._testagent.id, + ] + verifylist = [ + ('agent', self._testagent.id), + ] + + attrs = {self._testagent.id, } + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.agent_hosted_routers( + *attrs) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + class TestRemovePortFromRouter(TestRouter): '''Remove port from a Router ''' diff --git a/releasenotes/notes/bp-network-l3-adv-commands-cc1df715a184f1b2.yaml b/releasenotes/notes/bp-network-l3-adv-commands-cc1df715a184f1b2.yaml new file mode 100644 index 0000000000..f978f4d650 --- /dev/null +++ b/releasenotes/notes/bp-network-l3-adv-commands-cc1df715a184f1b2.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Add network l3-agent related commands ``network agent add router``, + ``network agent remove router`` for adding/removing routers to l3 agents. + Add ``network agent list --router``, and ``router list --agent`` for + listing agents on routers and routers on specific agents. + [Blueprint :oscbp:`network-l3-commands`] diff --git a/setup.cfg b/setup.cfg index 73a9c42cfb..16917e5099 100644 --- a/setup.cfg +++ b/setup.cfg @@ -365,9 +365,11 @@ openstack.network.v2 = ip_floating_pool_list = openstackclient.network.v2.floating_ip_pool:ListIPFloatingPool network_agent_add_network = openstackclient.network.v2.network_agent:AddNetworkToAgent + network_agent_add_router = openstackclient.network.v2.network_agent:AddRouterToAgent network_agent_delete = openstackclient.network.v2.network_agent:DeleteNetworkAgent network_agent_list = openstackclient.network.v2.network_agent:ListNetworkAgent network_agent_remove_network = openstackclient.network.v2.network_agent:RemoveNetworkFromAgent + network_agent_remove_router = openstackclient.network.v2.network_agent:RemoveRouterFromAgent network_agent_set = openstackclient.network.v2.network_agent:SetNetworkAgent network_agent_show = openstackclient.network.v2.network_agent:ShowNetworkAgent From 3cba09e767c6af3f715828966f0d0fa21edc00a8 Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Wed, 12 Jul 2017 09:42:28 +0000 Subject: [PATCH 1728/3095] Fix unit test failures related to new os-client-config and osc-lib [breakage related to os-client-config 1.28.0] os-client-config 1.28.0 add a check if filebased and envvars are both used. This check causes OSC unit test failure. OSC now instantiates OpenStackConfig twice as a workaround. The unit test mocks _load_config_file() and it returns a config dict, but os-client-config OpenStackConfig.__init__ updates the dict returned. As a result, when OpenStackConfig is instantiated second time, the mock of _load_config_file returns a modified version of the config dict. This hits the new check in os-client-config 1.28.0. This commit changes the mock to use side_effect rather than return_value to ensure the original dict is used. [breakage related to osc-lib 1.7.0] The change in osc-lib 1.7.0 added "if" logic to avoid calling get() twice. In tests.unit.volume.test_find_resource, kwargs is empty dict in find_resource(), so the second call to get() is NOT called now. Removing the second elements of side_effect addresses the unit failure. Co-Authored-By: Rui Chen Change-Id: Ib9d14661b2755bbd6619e15c0d9023fbc9d27d70 Closes-Bug: #1703782 Closes-Bug: #1703783 --- .../tests/unit/integ/cli/test_shell.py | 32 +++++++++---------- .../tests/unit/volume/test_find_resource.py | 2 -- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/openstackclient/tests/unit/integ/cli/test_shell.py b/openstackclient/tests/unit/integ/cli/test_shell.py index 9d819ed2e4..4e91f6370e 100644 --- a/openstackclient/tests/unit/integ/cli/test_shell.py +++ b/openstackclient/tests/unit/integ/cli/test_shell.py @@ -397,14 +397,14 @@ def test_shell_args_precedence_1(self, config_mock, vendor_mock): Run 1 has --os-password on CLI """ - config_mock.return_value = ( - 'file.yaml', - copy.deepcopy(test_shell.CLOUD_2), - ) - vendor_mock.return_value = ( - 'file.yaml', - copy.deepcopy(test_shell.PUBLIC_1), - ) + def config_mock_return(): + return ('file.yaml', copy.deepcopy(test_shell.CLOUD_2)) + config_mock.side_effect = config_mock_return + + def vendor_mock_return(): + return ('file.yaml', copy.deepcopy(test_shell.PUBLIC_1)) + vendor_mock.side_effect = vendor_mock_return + _shell = shell.OpenStackShell() _shell.run( "--os-password qaz configuration show".split(), @@ -466,14 +466,14 @@ def test_shell_args_precedence_2(self, config_mock, vendor_mock): Run 2 has --os-username, --os-password, --os-project-domain-id on CLI """ - config_mock.return_value = ( - 'file.yaml', - copy.deepcopy(test_shell.CLOUD_2), - ) - vendor_mock.return_value = ( - 'file.yaml', - copy.deepcopy(test_shell.PUBLIC_1), - ) + def config_mock_return(): + return ('file.yaml', copy.deepcopy(test_shell.CLOUD_2)) + config_mock.side_effect = config_mock_return + + def vendor_mock_return(): + return ('file.yaml', copy.deepcopy(test_shell.PUBLIC_1)) + vendor_mock.side_effect = vendor_mock_return + _shell = shell.OpenStackShell() _shell.run( "--os-username zarquon --os-password qaz " diff --git a/openstackclient/tests/unit/volume/test_find_resource.py b/openstackclient/tests/unit/volume/test_find_resource.py index d25093158c..dbf9592f33 100644 --- a/openstackclient/tests/unit/volume/test_find_resource.py +++ b/openstackclient/tests/unit/volume/test_find_resource.py @@ -45,7 +45,6 @@ def setUp(self): resp = mock.Mock() body = {"volumes": [{"id": ID, 'display_name': NAME}]} api.client.get.side_effect = [Exception("Not found"), - Exception("Not found"), (resp, body)] self.manager = volumes.VolumeManager(api) @@ -69,7 +68,6 @@ def setUp(self): resp = mock.Mock() body = {"snapshots": [{"id": ID, 'display_name': NAME}]} api.client.get.side_effect = [Exception("Not found"), - Exception("Not found"), (resp, body)] self.manager = volume_snapshots.SnapshotManager(api) From 1ae904a4912494b3d0ac87f22aaf958129744548 Mon Sep 17 00:00:00 2001 From: Carlos Goncalves Date: Wed, 18 Jan 2017 11:16:39 +0000 Subject: [PATCH 1729/3095] Add 'data_plane_status' option to Port classes Adds 'data_plane_status' option to SetPort and UnsetPort classes. Closes-Bug: #1684989 Change-Id: I26e23b551afb8c37e6babdea1655efb7c5c6873b --- doc/source/cli/command-objects/port.rst | 12 ++++ openstackclient/network/v2/port.py | 18 ++++++ .../tests/unit/network/v2/fakes.py | 1 + .../tests/unit/network/v2/test_port.py | 59 +++++++++++++++++++ .../notes/bug-1684989-3bda158a822d2f73.yaml | 5 ++ 5 files changed, 95 insertions(+) create mode 100644 releasenotes/notes/bug-1684989-3bda158a822d2f73.yaml diff --git a/doc/source/cli/command-objects/port.rst b/doc/source/cli/command-objects/port.rst index b3f4c7f9fd..2102288bd4 100644 --- a/doc/source/cli/command-objects/port.rst +++ b/doc/source/cli/command-objects/port.rst @@ -232,6 +232,7 @@ Set port properties [--dns-name ] [--allowed-address ip-address=[,mac-address=]] [--no-allowed-address] + [--data-plane-status ] .. option:: --description @@ -335,6 +336,12 @@ Set port properties (Specify both --allowed-address and --no-allowed-address to overwrite the current allowed-address pairs) +.. option:: --data-plane-status + + Set data plane status of this port (ACTIVE | DOWN). + Unset it to None with the 'port unset' command + (requires data plane status extension) + .. _port_set-port: .. describe:: @@ -370,6 +377,7 @@ Unset port properties [--security-group [...]] [--allowed-address ip-address=[,mac-address=] [...]] [--qos-policy] + [--data-plane-status] .. option:: --fixed-ip subnet=,ip-address= @@ -398,6 +406,10 @@ Unset port properties Remove the QoS policy attached to the port +.. option:: --data-plane-status + + Clear existing information of data plane status + .. _port_unset-port: .. describe:: diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 42291bf219..1409a194be 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -685,6 +685,15 @@ def get_parser(self, prog_name): "(Specify both --allowed-address and --no-allowed-address" "to overwrite the current allowed-address pairs)") ) + parser.add_argument( + '--data-plane-status', + metavar='', + choices=['ACTIVE', 'DOWN'], + help=_("Set data plane status of this port (ACTIVE | DOWN). " + "Unset it to None with the 'port unset' command " + "(requires data plane status extension)") + ) + return parser def take_action(self, parsed_args): @@ -737,6 +746,8 @@ def take_action(self, parsed_args): attrs['allowed_address_pairs'].extend( _convert_address_pairs(parsed_args) ) + if parsed_args.data_plane_status: + attrs['data_plane_status'] = parsed_args.data_plane_status client.update_port(obj, **attrs) @@ -816,6 +827,11 @@ def get_parser(self, prog_name): default=False, help=_("Remove the QoS policy attached to the port") ) + parser.add_argument( + '--data-plane-status', + action='store_true', + help=_("Clear existing information of data plane status") + ) return parser @@ -867,6 +883,8 @@ def take_action(self, parsed_args): attrs['allowed_address_pairs'] = tmp_addr_pairs if parsed_args.qos_policy: attrs['qos_policy_id'] = None + if parsed_args.data_plane_status: + attrs['data_plane_status'] = None if attrs: client.update_port(obj, **attrs) diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index 0b8eee90b0..98bda1649f 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -558,6 +558,7 @@ def create_one_port(attrs=None): 'binding:vif_details': {}, 'binding:vif_type': 'ovs', 'binding:vnic_type': 'normal', + 'data_plane_status': None, 'description': 'description-' + uuid.uuid4().hex, 'device_id': 'device-id-' + uuid.uuid4().hex, 'device_owner': 'compute:nova', diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index 851bf25ad2..a8a6dba9be 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -44,6 +44,7 @@ def _get_common_cols_data(self, fake_port): 'binding_vif_details', 'binding_vif_type', 'binding_vnic_type', + 'data_plane_status', 'description', 'device_id', 'device_owner', @@ -70,6 +71,7 @@ def _get_common_cols_data(self, fake_port): utils.format_dict(fake_port.binding_vif_details), fake_port.binding_vif_type, fake_port.binding_vnic_type, + fake_port.data_plane_status, fake_port.description, fake_port.device_id, fake_port.device_owner, @@ -1371,6 +1373,40 @@ def test_set_port_with_qos(self): self.network.update_port.assert_called_once_with(_testport, **attrs) self.assertIsNone(result) + def test_set_port_data_plane_status(self): + _testport = network_fakes.FakePort.create_one_port( + {'data_plane_status': None}) + self.network.find_port = mock.Mock(return_value=_testport) + arglist = [ + '--data-plane-status', 'ACTIVE', + _testport.name, + ] + verifylist = [ + ('data_plane_status', 'ACTIVE'), + ('port', _testport.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = { + 'data_plane_status': 'ACTIVE', + } + + self.network.update_port.assert_called_once_with(_testport, **attrs) + self.assertIsNone(result) + + def test_set_port_invalid_data_plane_status_value(self): + arglist = [ + '--data-plane-status', 'Spider-Man', + 'test-port', + ] + self.assertRaises(tests_utils.ParserException, + self.check_parser, + self.cmd, + arglist, + None) + class TestShowPort(TestPort): @@ -1573,3 +1609,26 @@ def test_unset_port_allowed_address_pair_not_existent(self): self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) + + def test_unset_port_data_plane_status(self): + _fake_port = network_fakes.FakePort.create_one_port( + {'data_plane_status': 'ACTIVE'}) + self.network.find_port = mock.Mock(return_value=_fake_port) + arglist = [ + '--data-plane-status', + _fake_port.name, + ] + verifylist = [ + ('data_plane_status', True), + ('port', _fake_port.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = { + 'data_plane_status': None, + } + + self.network.update_port.assert_called_once_with(_fake_port, **attrs) + self.assertIsNone(result) diff --git a/releasenotes/notes/bug-1684989-3bda158a822d2f73.yaml b/releasenotes/notes/bug-1684989-3bda158a822d2f73.yaml new file mode 100644 index 0000000000..3c63061f4d --- /dev/null +++ b/releasenotes/notes/bug-1684989-3bda158a822d2f73.yaml @@ -0,0 +1,5 @@ +--- +features: + - Add ``--data-plane-status`` option to ``port set`` and ``port unset`` + commands. + [Bug `1684989 `_] From bca8d57eb3963beb74baa5d75e61954c610369d0 Mon Sep 17 00:00:00 2001 From: nidhimittalhada Date: Mon, 19 Jun 2017 11:37:12 +0530 Subject: [PATCH 1730/3095] image-list should support filters 'name','status' nova api support parameters like 'name', 'server', 'status', etc in image-list(). So openstackclient should support this too. DocImpact Closes-Bug: #1698742 Change-Id: Ice66b409f989e6785aa3b2d42f2fdbf6e23fa0aa --- doc/source/cli/command-objects/image.rst | 12 ++++++++ openstackclient/image/v2/image.py | 16 +++++++++++ .../tests/functional/image/v2/test_image.py | 18 ++++++++++++ .../tests/unit/image/v2/test_image.py | 28 +++++++++++++++++++ .../notes/bug-1698742-66d9d4e6c7ad274a.yaml | 6 ++++ 5 files changed, 80 insertions(+) create mode 100644 releasenotes/notes/bug-1698742-66d9d4e6c7ad274a.yaml diff --git a/doc/source/cli/command-objects/image.rst b/doc/source/cli/command-objects/image.rst index 2918452baf..92efd0a546 100644 --- a/doc/source/cli/command-objects/image.rst +++ b/doc/source/cli/command-objects/image.rst @@ -209,6 +209,9 @@ List available images [--sort [:]] [--limit ] [--marker ] + [--name ] + [--status ] + .. option:: --public @@ -248,6 +251,15 @@ List available images The last image of the previous page. Display list of images after marker. Display all images if not specified. (name or ID) +.. option:: --name + + Filter images based on name + +.. option:: --status + + Filter images based on status + + *Image version 2 only* image remove project diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 2b171410f4..c2c5c594ce 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -452,6 +452,18 @@ def get_parser(self, prog_name): action=parseractions.KeyValueAction, help=_('Filter output based on property'), ) + parser.add_argument( + '--name', + metavar='', + default=None, + help=_("Filter images based on name.") + ) + parser.add_argument( + '--status', + metavar='', + default=None, + help=_("Filter images based on status.") + ) parser.add_argument( '--long', action='store_true', @@ -505,6 +517,10 @@ def take_action(self, parsed_args): if parsed_args.marker: kwargs['marker'] = utils.find_resource(image_client.images, parsed_args.marker).id + if parsed_args.name: + kwargs['name'] = parsed_args.name + if parsed_args.status: + kwargs['status'] = parsed_args.status if parsed_args.long: columns = ( 'ID', diff --git a/openstackclient/tests/functional/image/v2/test_image.py b/openstackclient/tests/functional/image/v2/test_image.py index 8fadd2000f..a93fa8cbee 100644 --- a/openstackclient/tests/functional/image/v2/test_image.py +++ b/openstackclient/tests/functional/image/v2/test_image.py @@ -51,6 +51,24 @@ def test_image_list(self): [img['Name'] for img in json_output] ) + def test_image_list_with_name_filter(self): + json_output = json.loads(self.openstack( + 'image list --name ' + self.NAME + ' -f json' + )) + self.assertIn( + self.NAME, + [img['Name'] for img in json_output] + ) + + def test_image_list_with_status_filter(self): + json_output = json.loads(self.openstack( + 'image list ' + ' --status active -f json' + )) + self.assertIn( + 'active', + [img['Status'] for img in json_output] + ) + def test_image_attributes(self): """Test set, unset, show on attributes, tags and properties""" diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py index 65764e98e0..484a2bc64b 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -750,6 +750,34 @@ def test_image_list_marker_option(self, fr_mock): marker=image_fakes.image_id, ) + def test_image_list_name_option(self): + arglist = [ + '--name', 'abc', + ] + verifylist = [ + ('name', 'abc'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.api_mock.image_list.assert_called_with( + name='abc', marker=self._image.id + ) + + def test_image_list_status_option(self): + arglist = [ + '--status', 'active', + ] + verifylist = [ + ('status', 'active'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.api_mock.image_list.assert_called_with( + status='active', marker=self._image.id + ) + class TestRemoveProjectImage(TestImage): diff --git a/releasenotes/notes/bug-1698742-66d9d4e6c7ad274a.yaml b/releasenotes/notes/bug-1698742-66d9d4e6c7ad274a.yaml new file mode 100644 index 0000000000..eb40a41eaf --- /dev/null +++ b/releasenotes/notes/bug-1698742-66d9d4e6c7ad274a.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--name`` and ``--status`` options to ``image list`` command + to filter images based on name and status respectively. + [Bug `1698742 `_] From db7d0723f067c0e818678781cff17f1d07751f1e Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 13 Jul 2017 14:24:35 +0000 Subject: [PATCH 1731/3095] Updated from global requirements Change-Id: I32899a8110c6c4442c454e122d5ed4eedc76a938 --- test-requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test-requirements.txt b/test-requirements.txt index 411dc87786..4ab3250e04 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -17,7 +17,7 @@ os-client-config>=1.27.0 # Apache-2.0 os-testr>=0.8.0 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD testtools>=1.4.0 # MIT -tempest>=14.0.0 # Apache-2.0 +tempest>=16.1.0 # Apache-2.0 osprofiler>=1.4.0 # Apache-2.0 bandit>=1.1.0 # Apache-2.0 wrapt>=1.7.0 # BSD License @@ -29,7 +29,7 @@ python-barbicanclient>=4.0.0 # Apache-2.0 python-congressclient<2000,>=1.3.0 # Apache-2.0 python-designateclient>=1.5.0 # Apache-2.0 python-heatclient>=1.6.1 # Apache-2.0 -python-ironicclient>=1.11.0 # Apache-2.0 +python-ironicclient>=1.14.0 # Apache-2.0 python-ironic-inspector-client>=1.5.0 # Apache-2.0 python-karborclient>=0.2.0 # Apache-2.0 python-mistralclient>=3.1.0 # Apache-2.0 From 2689984ba71fa0be25d2368277f33f2fb5c41266 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Mon, 17 Jul 2017 16:03:49 -0500 Subject: [PATCH 1732/3095] Fix column names for server list --no-name-lookup When --long is not present change the 'Image Name' column to 'Image' and add the 'Flavor' column. These columns will contain Names unless --no-name-lookup is specified when they will contain IDs. Change-Id: I92cfb22136aee32616894e60e9227b4da185da99 --- doc/source/cli/command-objects/server.rst | 4 ++- openstackclient/compute/v2/server.py | 35 +++++++++++-------- .../tests/unit/compute/v2/test_server.py | 17 ++++----- .../skip-name-lookups-9f499927173c1eee.yaml | 6 ++++ 4 files changed, 36 insertions(+), 26 deletions(-) create mode 100644 releasenotes/notes/skip-name-lookups-9f499927173c1eee.yaml diff --git a/doc/source/cli/command-objects/server.rst b/doc/source/cli/command-objects/server.rst index dee2721943..1118282204 100644 --- a/doc/source/cli/command-objects/server.rst +++ b/doc/source/cli/command-objects/server.rst @@ -330,7 +330,7 @@ List servers [--all-projects] [--project [--project-domain ]] [--long] - [-n | --no-name-lookup] + [--no-name-lookup | -n] [--marker ] [--limit ] [--deleted] @@ -402,6 +402,8 @@ List servers Skips image and flavor names lookup + ``-n`` may be used as an alias for this option. + .. option:: --marker The last server of the previous page. Display list of servers diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index cb520d620e..a991ed4531 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1060,26 +1060,33 @@ def take_action(self, parsed_args): 'OS-EXT-AZ:availability_zone', 'OS-EXT-SRV-ATTR:host', ] - elif parsed_args.no_name_lookup: - columns = ( - 'ID', - 'Name', - 'Status', - 'Image ID', - 'Flavor ID', - ) - column_headers = tuple(columns) - mixed_case_fields = [] - else: - columns = ( + if parsed_args.no_name_lookup: + columns = ( + 'ID', + 'Name', + 'Status', + 'Networks', + 'Image ID', + 'Flavor ID', + ) + else: + columns = ( + 'ID', + 'Name', + 'Status', + 'Networks', + 'Image Name', + 'Flavor Name', + ) + column_headers = ( 'ID', 'Name', 'Status', 'Networks', - 'Image Name', + 'Image', + 'Flavor', ) - column_headers = tuple(columns) mixed_case_fields = [] marker_id = None diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 3e71ce0771..b02b149119 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -1441,7 +1441,8 @@ class TestServerList(TestServer): 'Name', 'Status', 'Networks', - 'Image Name', + 'Image', + 'Flavor', ) columns_long = ( 'ID', @@ -1459,14 +1460,6 @@ class TestServerList(TestServer): 'Properties', ) - columns_no_name_lookup = ( - 'ID', - 'Name', - 'Status', - 'Image ID', - 'Flavor ID', - ) - def setUp(self): super(TestServerList, self).setUp() @@ -1546,6 +1539,7 @@ def setUp(self): s.status, server._format_servers_list_networks(s.networks), self.image.name, + self.flavor.name, )) self.data_long.append(( s.id, @@ -1568,6 +1562,7 @@ def setUp(self): s.id, s.name, s.status, + server._format_servers_list_networks(s.networks), s.image['id'], s.flavor['id'] )) @@ -1616,7 +1611,7 @@ def test_server_list_no_name_lookup_option(self): columns, data = self.cmd.take_action(parsed_args) self.servers_mock.list.assert_called_with(**self.kwargs) - self.assertEqual(self.columns_no_name_lookup, columns) + self.assertEqual(self.columns, columns) self.assertEqual(tuple(self.data_no_name_lookup), tuple(data)) def test_server_list_n_option(self): @@ -1632,7 +1627,7 @@ def test_server_list_n_option(self): columns, data = self.cmd.take_action(parsed_args) self.servers_mock.list.assert_called_with(**self.kwargs) - self.assertEqual(self.columns_no_name_lookup, columns) + self.assertEqual(self.columns, columns) self.assertEqual(tuple(self.data_no_name_lookup), tuple(data)) def test_server_list_with_image(self): diff --git a/releasenotes/notes/skip-name-lookups-9f499927173c1eee.yaml b/releasenotes/notes/skip-name-lookups-9f499927173c1eee.yaml new file mode 100644 index 0000000000..69ca0f99f5 --- /dev/null +++ b/releasenotes/notes/skip-name-lookups-9f499927173c1eee.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--no-name-lookup`` option to ``server list`` command to skip the lookup of + flavor and image names. This can save a significant amount of time on clouds with + a large number of images. ``-n`` is an alias for this option. From a01bf55d2065ec1a98f486109cc8d315def501cd Mon Sep 17 00:00:00 2001 From: jiangpch Date: Thu, 13 Jul 2017 04:58:00 -0400 Subject: [PATCH 1733/3095] Fix 'domain' filter not work well in some commands The 'domain' filter not work well in commands 'project show', 'user show' and 'user set'. Depends-On: I490900d6249f01654d4cba43bddd3e7af7928a84 Closes-Bug: #1704097 Change-Id: Ib4f47cbaba27eb56c4a41d187fee74a995e62dc7 --- openstackclient/identity/common.py | 8 +++- openstackclient/identity/v3/project.py | 3 +- openstackclient/identity/v3/user.py | 6 ++- .../tests/unit/identity/v3/test_project.py | 46 +++++++++++++++++++ .../tests/unit/identity/v3/test_user.py | 38 ++++++++++++++- .../notes/bug-1704097-8ff1ce1444b81b04.yaml | 9 ++++ 6 files changed, 105 insertions(+), 5 deletions(-) create mode 100644 releasenotes/notes/bug-1704097-8ff1ce1444b81b04.yaml diff --git a/openstackclient/identity/common.py b/openstackclient/identity/common.py index 1f645b7d9c..3dc5adbbd2 100644 --- a/openstackclient/identity/common.py +++ b/openstackclient/identity/common.py @@ -58,7 +58,7 @@ def find_service(identity_client, name_type_or_id): raise exceptions.CommandError(msg % name_type_or_id) -def _get_token_resource(client, resource, parsed_name): +def _get_token_resource(client, resource, parsed_name, parsed_domain=None): """Peek into the user's auth token to get resource IDs Look into a user's token to try and find the ID of a domain, project or @@ -71,6 +71,8 @@ def _get_token_resource(client, resource, parsed_name): `project_domain`, `user_domain`, `project`, or `user`. :param parsed_name: This is input from parsed_args that the user is hoping to find in the token. + :param parsed_domain: This is domain filter from parsed_args that used to + filter the results. :returns: The ID of the resource from the token, or the original value from parsed_args if it does not match. @@ -85,6 +87,10 @@ def _get_token_resource(client, resource, parsed_name): if resource == 'domain': token_dict = token_dict['project'] obj = token_dict[resource] + + # user/project under different domain may has a same name + if parsed_domain and parsed_domain not in obj['domain'].values(): + return parsed_name return obj['id'] if obj['name'] == parsed_name else parsed_name # diaper defense in case parsing the token fails except Exception: # noqa diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index 873ee9c73e..c7806ee178 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -360,7 +360,8 @@ def take_action(self, parsed_args): identity_client = self.app.client_manager.identity project_str = common._get_token_resource(identity_client, 'project', - parsed_args.project) + parsed_args.project, + parsed_args.domain) if parsed_args.domain: domain = common.find_domain(identity_client, parsed_args.domain) diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index 9c289a6d8a..5f4fb544fe 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -358,7 +358,8 @@ def take_action(self, parsed_args): "when a user does not have a password.")) user_str = common._get_token_resource(identity_client, 'user', - parsed_args.user) + parsed_args.user, + parsed_args.domain) if parsed_args.domain: domain = common.find_domain(identity_client, parsed_args.domain) user = utils.find_resource(identity_client.users, @@ -473,7 +474,8 @@ def take_action(self, parsed_args): identity_client = self.app.client_manager.identity user_str = common._get_token_resource(identity_client, 'user', - parsed_args.user) + parsed_args.user, + parsed_args.domain) if parsed_args.domain: domain = common.find_domain(identity_client, parsed_args.domain) user = utils.find_resource(identity_client.users, diff --git a/openstackclient/tests/unit/identity/v3/test_project.py b/openstackclient/tests/unit/identity/v3/test_project.py index 7be81153c4..2ce26c6416 100644 --- a/openstackclient/tests/unit/identity/v3/test_project.py +++ b/openstackclient/tests/unit/identity/v3/test_project.py @@ -19,6 +19,7 @@ from osc_lib import exceptions from osc_lib import utils +from openstackclient.identity import common from openstackclient.identity.v3 import project from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes @@ -1060,3 +1061,48 @@ def test_project_show_parents_and_children(self): ['children-id'], ) self.assertEqual(data, datalist) + + def test_project_show_with_domain(self): + project = identity_fakes.FakeProject.create_one_project( + {"name": self.project.name}) + + self.app.client_manager.identity.tokens.get_token_data.return_value = \ + {'token': + {'project': + {'domain': {"id": self.project.domain_id}, + 'name': self.project.name, + 'id': self.project.id + } + } + } + + identity_client = self.app.client_manager.identity + arglist = [ + "--domain", self.domain.id, + project.name, + ] + verifylist = [ + ('domain', self.domain.id), + ('project', project.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + project_str = common._get_token_resource(identity_client, 'project', + parsed_args.project, + parsed_args.domain) + self.assertEqual(self.project.id, project_str) + + arglist = [ + "--domain", project.domain_id, + project.name, + ] + verifylist = [ + ('domain', project.domain_id), + ('project', project.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + project_str = common._get_token_resource(identity_client, 'project', + parsed_args.project, + parsed_args.domain) + self.assertEqual(project.name, project_str) diff --git a/openstackclient/tests/unit/identity/v3/test_user.py b/openstackclient/tests/unit/identity/v3/test_user.py index 2ce66e94a2..96f5076655 100644 --- a/openstackclient/tests/unit/identity/v3/test_user.py +++ b/openstackclient/tests/unit/identity/v3/test_user.py @@ -19,6 +19,7 @@ from osc_lib import exceptions from osc_lib import utils +from openstackclient.identity import common from openstackclient.identity.v3 import user from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes @@ -1091,7 +1092,7 @@ def setUp(self): self.app.client_manager.identity.tokens.get_token_data.return_value = \ {'token': {'user': - {'domain': {}, + {'domain': {'id': self.user.domain_id}, 'id': self.user.id, 'name': self.user.name, } @@ -1126,3 +1127,38 @@ def test_user_show(self): self.user.name, ) self.assertEqual(datalist, data) + + def test_user_show_with_domain(self): + user = identity_fakes.FakeUser.create_one_user( + {"name": self.user.name}) + identity_client = self.app.client_manager.identity + + arglist = [ + "--domain", self.user.domain_id, + user.name, + ] + verifylist = [ + ('domain', self.user.domain_id), + ('user', user.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + user_str = common._get_token_resource(identity_client, 'user', + parsed_args.user, + parsed_args.domain) + self.assertEqual(self.user.id, user_str) + + arglist = [ + "--domain", user.domain_id, + user.name, + ] + verifylist = [ + ('domain', user.domain_id), + ('user', user.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + user_str = common._get_token_resource(identity_client, 'user', + parsed_args.user, + parsed_args.domain) + self.assertEqual(user.name, user_str) diff --git a/releasenotes/notes/bug-1704097-8ff1ce1444b81b04.yaml b/releasenotes/notes/bug-1704097-8ff1ce1444b81b04.yaml new file mode 100644 index 0000000000..c20d0c9991 --- /dev/null +++ b/releasenotes/notes/bug-1704097-8ff1ce1444b81b04.yaml @@ -0,0 +1,9 @@ +--- +fixes: + - | + Fix an issue when run commands `project show`, `user show` and `user set` + with `--domain` specific, the domain filter won't work if the login user's + project name or user name is same with the resource name user hoping to + get. + + [Bug `1704097 `_] From cf5dfa77e17d273aaebca21a0b44902d587fac04 Mon Sep 17 00:00:00 2001 From: Jan Gutter Date: Thu, 13 Jul 2017 21:52:26 +0200 Subject: [PATCH 1734/3095] Add support for virtio-forwarder VNIC type * This patch adds support for the virtio-forwarder VNIC type. * The virtio-forwarder VNIC type has been added as another option for setting the --vnic-type property on the "port set" and "port create" commands. This requests a low-latency virtio port inside the instance, likely backed by hardware acceleration. Currently the Agilio OVS external plugin provides support for this, with support from other vendors following soon. * Corresponding neutron-lib change: https://review.openstack.org/#/c/483530/ * Nova spec for Agilio OVS enablement: https://specs.openstack.org/openstack/nova-specs/specs/pike/approved/netronome-smartnic-enablement.html Change-Id: Idbc8071afe95f8594b40e2f93e5411e7185f946f Signed-off-by: Jan Gutter --- doc/source/cli/command-objects/port.rst | 8 ++++---- openstackclient/network/v2/port.py | 5 +++-- .../add-virtio-forwarder-vnic-type-bad939c6a868b9e9.yaml | 9 +++++++++ 3 files changed, 16 insertions(+), 6 deletions(-) create mode 100644 releasenotes/notes/add-virtio-forwarder-vnic-type-bad939c6a868b9e9.yaml diff --git a/doc/source/cli/command-objects/port.rst b/doc/source/cli/command-objects/port.rst index 2102288bd4..37814a9595 100644 --- a/doc/source/cli/command-objects/port.rst +++ b/doc/source/cli/command-objects/port.rst @@ -60,8 +60,8 @@ Create new port .. option:: --vnic-type - VNIC type for this port (direct | direct-physical | macvtap | normal | baremetal, - default: normal) + VNIC type for this port (direct | direct-physical | macvtap | normal | baremetal | + virtio-forwarder, default: normal) .. option:: --binding-profile @@ -262,8 +262,8 @@ Set port properties .. option:: --vnic-type - VNIC type for this port (direct | direct-physical | macvtap | normal | baremetal, - default: normal) + VNIC type for this port (direct | direct-physical | macvtap | normal | baremetal | + virtio-forwarder, default: normal) .. option:: --binding-profile diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 1409a194be..d7f197e016 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -248,9 +248,10 @@ def _add_updatable_args(parser): '--vnic-type', metavar='', choices=['direct', 'direct-physical', 'macvtap', - 'normal', 'baremetal'], + 'normal', 'baremetal', 'virtio-forwarder'], help=_("VNIC type for this port (direct | direct-physical | " - "macvtap | normal | baremetal, default: normal)") + "macvtap | normal | baremetal | virtio-forwarder, " + " default: normal)") ) # NOTE(dtroyer): --host-id is deprecated in Mar 2016. Do not # remove before 3.x release or Mar 2017. diff --git a/releasenotes/notes/add-virtio-forwarder-vnic-type-bad939c6a868b9e9.yaml b/releasenotes/notes/add-virtio-forwarder-vnic-type-bad939c6a868b9e9.yaml new file mode 100644 index 0000000000..b98b7c5da4 --- /dev/null +++ b/releasenotes/notes/add-virtio-forwarder-vnic-type-bad939c6a868b9e9.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + The ``virtio-forwarder`` VNIC type has been added as another option for + setting the ``--vnic-type`` property on the ``port set`` and + ``port create`` commands. This requests a low-latency virtio port inside + the instance, likely backed by hardware acceleration. Currently the + Agilio OVS external plugin provides support for this, with support from + other vendors following soon. From 99a502b203235a62c4ec478df81246cec39a0b7b Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Wed, 19 Jul 2017 16:44:33 +0000 Subject: [PATCH 1735/3095] Disable karborclient until a fixed version is released Even though a fix for bug 1705258 has been merged in python-karborclient, it will take some time to release it and update the upper-constraints.txt, so I think it is better to exclude karborclient from the plugin commands doc temporarily. Change-Id: I092b37b30df785159495c2d681162e144cfe4083 Related-Bug: #1705258 --- doc/source/cli/plugin-commands.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/source/cli/plugin-commands.rst b/doc/source/cli/plugin-commands.rst index 34efdc3d84..90c14b87c1 100644 --- a/doc/source/cli/plugin-commands.rst +++ b/doc/source/cli/plugin-commands.rst @@ -58,11 +58,11 @@ ironic-inspector .. list-plugins:: openstack.baremetal_introspection.v1 :detailed: -karbor ------- - -.. list-plugins:: openstack.data_protection.v1 - :detailed: +.. karbor +.. ------ +.. bug 1705258: Exclude karborclient 0.4.0 until a fixed version is released. +.. .. list-plugins:: openstack.data_protection.v1 +.. :detailed: mistral ------- From 8d7ee8da0eed30bfbb3eec92729820293b3fa2f6 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 19 Jul 2017 16:33:22 -0500 Subject: [PATCH 1736/3095] Fix dynamic names in network functional tests Move all of the dynamic resource naming in Network functional tests into setUpClass() methods (if they exist) rather than assigning those names at load-time. Change-Id: Ic550ff7d40c2b3ca5128cacccbe331790d6ae340 --- .../tests/functional/network/v2/test_floating_ip.py | 13 +++++++------ .../functional/network/v2/test_ip_availability.py | 3 ++- .../network/v2/test_network_meter_rule.py | 3 ++- .../network/v2/test_network_qos_policy.py | 5 +++-- .../functional/network/v2/test_network_qos_rule.py | 3 ++- .../functional/network/v2/test_network_rbac.py | 7 ++++--- .../functional/network/v2/test_network_segment.py | 7 ++++--- .../tests/functional/network/v2/test_port.py | 7 ++++--- .../functional/network/v2/test_security_group.py | 5 +++-- .../network/v2/test_security_group_rule.py | 5 +++-- .../tests/functional/network/v2/test_subnet.py | 3 ++- 11 files changed, 36 insertions(+), 25 deletions(-) diff --git a/openstackclient/tests/functional/network/v2/test_floating_ip.py b/openstackclient/tests/functional/network/v2/test_floating_ip.py index 96521f09da..9e34622cf4 100644 --- a/openstackclient/tests/functional/network/v2/test_floating_ip.py +++ b/openstackclient/tests/functional/network/v2/test_floating_ip.py @@ -19,17 +19,18 @@ class FloatingIpTests(common.NetworkTests): """Functional tests for floating ip""" - EXTERNAL_NETWORK_NAME = uuid.uuid4().hex - EXTERNAL_SUBNET_NAME = uuid.uuid4().hex - PRIVATE_NETWORK_NAME = uuid.uuid4().hex - PRIVATE_SUBNET_NAME = uuid.uuid4().hex - ROUTER = uuid.uuid4().hex - PORT_NAME = uuid.uuid4().hex @classmethod def setUpClass(cls): common.NetworkTests.setUpClass() if cls.haz_network: + cls.EXTERNAL_NETWORK_NAME = uuid.uuid4().hex + cls.EXTERNAL_SUBNET_NAME = uuid.uuid4().hex + cls.PRIVATE_NETWORK_NAME = uuid.uuid4().hex + cls.PRIVATE_SUBNET_NAME = uuid.uuid4().hex + cls.ROUTER = uuid.uuid4().hex + cls.PORT_NAME = uuid.uuid4().hex + # Create a network for the floating ip json_output = json.loads(cls.openstack( 'network create -f json ' + diff --git a/openstackclient/tests/functional/network/v2/test_ip_availability.py b/openstackclient/tests/functional/network/v2/test_ip_availability.py index 1aa0f64a7b..cb4b8150ab 100644 --- a/openstackclient/tests/functional/network/v2/test_ip_availability.py +++ b/openstackclient/tests/functional/network/v2/test_ip_availability.py @@ -23,9 +23,10 @@ class IPAvailabilityTests(common.NetworkTests): def setUpClass(cls): common.NetworkTests.setUpClass() if cls.haz_network: - # Create a network for the subnet. cls.NAME = uuid.uuid4().hex cls.NETWORK_NAME = uuid.uuid4().hex + + # Create a network for the subnet cls.openstack( 'network create ' + cls.NETWORK_NAME diff --git a/openstackclient/tests/functional/network/v2/test_network_meter_rule.py b/openstackclient/tests/functional/network/v2/test_network_meter_rule.py index b7090707b1..71b406b46e 100644 --- a/openstackclient/tests/functional/network/v2/test_network_meter_rule.py +++ b/openstackclient/tests/functional/network/v2/test_network_meter_rule.py @@ -22,7 +22,6 @@ class TestMeterRule(common.NetworkTests): """Functional tests for meter rule""" - METER_NAME = uuid.uuid4().hex METER_ID = None METER_RULE_ID = None @@ -30,6 +29,8 @@ class TestMeterRule(common.NetworkTests): def setUpClass(cls): common.NetworkTests.setUpClass() if cls.haz_network: + cls.METER_NAME = uuid.uuid4().hex + json_output = json.loads(cls.openstack( 'network meter create -f json ' + cls.METER_NAME diff --git a/openstackclient/tests/functional/network/v2/test_network_qos_policy.py b/openstackclient/tests/functional/network/v2/test_network_qos_policy.py index ac3e0fc6c4..ccbf437c3b 100644 --- a/openstackclient/tests/functional/network/v2/test_network_qos_policy.py +++ b/openstackclient/tests/functional/network/v2/test_network_qos_policy.py @@ -19,8 +19,7 @@ class NetworkQosPolicyTests(common.NetworkTests): - """Functional tests for QoS policy. """ - NAME = uuid.uuid4().hex + """Functional tests for QoS policy""" HEADERS = ['Name'] FIELDS = ['name'] @@ -28,6 +27,8 @@ class NetworkQosPolicyTests(common.NetworkTests): def setUpClass(cls): common.NetworkTests.setUpClass() if cls.haz_network: + cls.NAME = uuid.uuid4().hex + opts = cls.get_opts(cls.FIELDS) raw_output = cls.openstack( 'network qos policy create ' + diff --git a/openstackclient/tests/functional/network/v2/test_network_qos_rule.py b/openstackclient/tests/functional/network/v2/test_network_qos_rule.py index 056bc1f678..6e40d8e0d9 100644 --- a/openstackclient/tests/functional/network/v2/test_network_qos_rule.py +++ b/openstackclient/tests/functional/network/v2/test_network_qos_rule.py @@ -21,7 +21,6 @@ class NetworkQosRuleTestsMinimumBandwidth(common.NetworkTests): """Functional tests for QoS minimum bandwidth rule""" RULE_ID = None - QOS_POLICY_NAME = 'qos_policy_' + uuid.uuid4().hex MIN_KBPS = 2800 MIN_KBPS_MODIFIED = 7500 DIRECTION = '--egress' @@ -33,6 +32,8 @@ class NetworkQosRuleTestsMinimumBandwidth(common.NetworkTests): def setUpClass(cls): common.NetworkTests.setUpClass() if cls.haz_network: + cls.QOS_POLICY_NAME = 'qos_policy_' + uuid.uuid4().hex + opts = cls.get_opts(cls.FIELDS) cls.openstack( 'network qos policy create ' + diff --git a/openstackclient/tests/functional/network/v2/test_network_rbac.py b/openstackclient/tests/functional/network/v2/test_network_rbac.py index 0d5492e511..893f199397 100644 --- a/openstackclient/tests/functional/network/v2/test_network_rbac.py +++ b/openstackclient/tests/functional/network/v2/test_network_rbac.py @@ -16,9 +16,7 @@ class NetworkRBACTests(common.NetworkTests): - """Functional tests for network rbac. """ - NET_NAME = uuid.uuid4().hex - PROJECT_NAME = uuid.uuid4().hex + """Functional tests for network rbac""" OBJECT_ID = None ID = None HEADERS = ['ID'] @@ -28,6 +26,9 @@ class NetworkRBACTests(common.NetworkTests): def setUpClass(cls): common.NetworkTests.setUpClass() if cls.haz_network: + cls.NET_NAME = uuid.uuid4().hex + cls.PROJECT_NAME = uuid.uuid4().hex + opts = cls.get_opts(cls.FIELDS) raw_output = cls.openstack( 'network create ' + cls.NET_NAME + opts diff --git a/openstackclient/tests/functional/network/v2/test_network_segment.py b/openstackclient/tests/functional/network/v2/test_network_segment.py index 7fc79746b9..6dec82d9e3 100644 --- a/openstackclient/tests/functional/network/v2/test_network_segment.py +++ b/openstackclient/tests/functional/network/v2/test_network_segment.py @@ -17,8 +17,6 @@ class NetworkSegmentTests(common.NetworkTests): """Functional tests for network segment""" - NETWORK_NAME = uuid.uuid4().hex - PHYSICAL_NETWORK_NAME = uuid.uuid4().hex NETWORK_SEGMENT_ID = None NETWORK_ID = None NETWORK_SEGMENT_EXTENSION = None @@ -27,7 +25,10 @@ class NetworkSegmentTests(common.NetworkTests): def setUpClass(cls): common.NetworkTests.setUpClass() if cls.haz_network: - # Create a network for the segment. + cls.NETWORK_NAME = uuid.uuid4().hex + cls.PHYSICAL_NETWORK_NAME = uuid.uuid4().hex + + # Create a network for the segment opts = cls.get_opts(['id']) raw_output = cls.openstack( 'network create ' + cls.NETWORK_NAME + opts diff --git a/openstackclient/tests/functional/network/v2/test_port.py b/openstackclient/tests/functional/network/v2/test_port.py index 6659e3e0a9..14454454e4 100644 --- a/openstackclient/tests/functional/network/v2/test_port.py +++ b/openstackclient/tests/functional/network/v2/test_port.py @@ -18,14 +18,15 @@ class PortTests(common.NetworkTests): """Functional tests for port""" - NAME = uuid.uuid4().hex - NETWORK_NAME = uuid.uuid4().hex @classmethod def setUpClass(cls): common.NetworkTests.setUpClass() if cls.haz_network: - # Create a network for the port + cls.NAME = uuid.uuid4().hex + cls.NETWORK_NAME = uuid.uuid4().hex + + # Create a network for the port tests cls.openstack( 'network create ' + cls.NETWORK_NAME ) diff --git a/openstackclient/tests/functional/network/v2/test_security_group.py b/openstackclient/tests/functional/network/v2/test_security_group.py index 2a9c0b0ad4..6da7e204d8 100644 --- a/openstackclient/tests/functional/network/v2/test_security_group.py +++ b/openstackclient/tests/functional/network/v2/test_security_group.py @@ -17,8 +17,6 @@ class SecurityGroupTests(common.NetworkTests): """Functional tests for security group""" - NAME = uuid.uuid4().hex - OTHER_NAME = uuid.uuid4().hex HEADERS = ['Name'] FIELDS = ['name'] @@ -26,6 +24,9 @@ class SecurityGroupTests(common.NetworkTests): def setUpClass(cls): common.NetworkTests.setUpClass() if cls.haz_network: + cls.NAME = uuid.uuid4().hex + cls.OTHER_NAME = uuid.uuid4().hex + opts = cls.get_opts(cls.FIELDS) raw_output = cls.openstack( 'security group create ' + diff --git a/openstackclient/tests/functional/network/v2/test_security_group_rule.py b/openstackclient/tests/functional/network/v2/test_security_group_rule.py index 93f98642f9..e153116b14 100644 --- a/openstackclient/tests/functional/network/v2/test_security_group_rule.py +++ b/openstackclient/tests/functional/network/v2/test_security_group_rule.py @@ -17,7 +17,6 @@ class SecurityGroupRuleTests(common.NetworkTests): """Functional tests for security group rule""" - SECURITY_GROUP_NAME = uuid.uuid4().hex SECURITY_GROUP_RULE_ID = None NAME_FIELD = ['name'] ID_FIELD = ['id'] @@ -27,7 +26,9 @@ class SecurityGroupRuleTests(common.NetworkTests): def setUpClass(cls): common.NetworkTests.setUpClass() if cls.haz_network: - # Create the security group to hold the rule. + cls.SECURITY_GROUP_NAME = uuid.uuid4().hex + + # Create the security group to hold the rule opts = cls.get_opts(cls.NAME_FIELD) raw_output = cls.openstack( 'security group create ' + diff --git a/openstackclient/tests/functional/network/v2/test_subnet.py b/openstackclient/tests/functional/network/v2/test_subnet.py index 93e0593d0d..413754157a 100644 --- a/openstackclient/tests/functional/network/v2/test_subnet.py +++ b/openstackclient/tests/functional/network/v2/test_subnet.py @@ -24,8 +24,9 @@ class SubnetTests(common.NetworkTests): def setUpClass(cls): common.NetworkTests.setUpClass() if cls.haz_network: - # Create a network for the all subnet tests cls.NETWORK_NAME = uuid.uuid4().hex + + # Create a network for the all subnet tests cmd_output = json.loads(cls.openstack( 'network create -f json ' + cls.NETWORK_NAME From b43b1f8ff256b6bab3b283e3fff475d0ffa73f24 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 20 Jul 2017 10:29:40 -0500 Subject: [PATCH 1737/3095] Minor followup to network agent docs Change-Id: I2305a22b3bac20cb25c57af68e7625b83aefef52 --- doc/source/cli/command-objects/network-agent.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/source/cli/command-objects/network-agent.rst b/doc/source/cli/command-objects/network-agent.rst index 2ceb263f1b..9f02cb6bc8 100644 --- a/doc/source/cli/command-objects/network-agent.rst +++ b/doc/source/cli/command-objects/network-agent.rst @@ -23,7 +23,7 @@ Add network to an agent -.. describe:: --dhcp +.. option:: --dhcp Add a network to DHCP agent @@ -50,7 +50,7 @@ Add router to an agent .. option:: --l3 - Add router to L3 agent + Add router to an L3 agent .. _network_agent_add_router-agent-id: .. describe:: @@ -157,7 +157,7 @@ Remove router from an agent .. option:: --l3 - Remove router from L3 agent + Remove router from an L3 agent .. _network_agent_remove_router-agent-id: .. describe:: From f1d32dbe9b6f5f2e47853b9969483fa841e451f4 Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Tue, 6 Jun 2017 21:03:33 +0800 Subject: [PATCH 1738/3095] Clean up the changes of os.environ in functional tests Use fixtures to restore the API version changes of os.environ in each functional tests, aims to avoid the following test cases failing in unexpected context. And make sure setUpClass/tearDownClass call super class's corresponding methods first. Change-Id: Ie248fe9d3a9e25f1b076c9f2c363200f29a83817 Closes-Bug: #1696080 --- .../tests/functional/common/test_extension.py | 1 + .../tests/functional/common/test_help.py | 14 ++-- .../tests/functional/common/test_quota.py | 1 + .../functional/compute/v2/test_flavor.py | 8 ++- .../functional/compute/v2/test_server.py | 1 + .../tests/functional/identity/v2/common.py | 29 +++++++-- .../tests/functional/identity/v3/common.py | 39 +++++++---- .../tests/functional/image/v1/test_image.py | 23 +++++-- .../tests/functional/image/v2/test_image.py | 22 +++++-- .../tests/functional/network/v2/common.py | 2 +- .../functional/network/v2/test_floating_ip.py | 29 +++++---- .../network/v2/test_ip_availability.py | 25 +++---- .../functional/network/v2/test_network.py | 4 +- .../network/v2/test_network_meter_rule.py | 16 +++-- .../network/v2/test_network_qos_policy.py | 15 +++-- .../network/v2/test_network_qos_rule.py | 65 +++++++++++-------- .../network/v2/test_network_rbac.py | 21 +++--- .../network/v2/test_network_segment.py | 13 ++-- .../tests/functional/network/v2/test_port.py | 13 ++-- .../network/v2/test_security_group.py | 31 +++++---- .../network/v2/test_security_group_rule.py | 26 ++++---- .../functional/network/v2/test_subnet.py | 15 +++-- .../functional/object/v1/test_container.py | 8 ++- .../tests/functional/volume/v1/common.py | 11 ++-- .../functional/volume/v1/test_snapshot.py | 9 ++- .../volume/v1/test_transfer_request.py | 15 +++-- .../functional/volume/v1/test_volume_type.py | 7 +- .../tests/functional/volume/v2/common.py | 11 ++-- .../functional/volume/v2/test_snapshot.py | 11 ++-- .../volume/v2/test_transfer_request.py | 15 +++-- .../functional/volume/v2/test_volume_type.py | 7 +- .../tests/functional/volume/v3/common.py | 11 ++-- .../tests/functional/volume/v3/test_qos.py | 10 +-- .../functional/volume/v3/test_snapshot.py | 9 +-- .../volume/v3/test_transfer_request.py | 9 +-- .../tests/functional/volume/v3/test_volume.py | 9 +-- .../functional/volume/v3/test_volume_type.py | 9 +-- 37 files changed, 336 insertions(+), 228 deletions(-) diff --git a/openstackclient/tests/functional/common/test_extension.py b/openstackclient/tests/functional/common/test_extension.py index d7dc398b5e..e3a91fe657 100644 --- a/openstackclient/tests/functional/common/test_extension.py +++ b/openstackclient/tests/functional/common/test_extension.py @@ -25,6 +25,7 @@ class ExtensionTests(base.TestCase): @classmethod def setUpClass(cls): + super(ExtensionTests, cls).setUpClass() cls.haz_network = base.is_service_enabled('network') def test_extension_list_compute(self): diff --git a/openstackclient/tests/functional/common/test_help.py b/openstackclient/tests/functional/common/test_help.py index e31d3b869c..7f27409956 100644 --- a/openstackclient/tests/functional/common/test_help.py +++ b/openstackclient/tests/functional/common/test_help.py @@ -12,6 +12,8 @@ import os +import fixtures + from openstackclient.tests.functional import base @@ -76,10 +78,11 @@ def test_networking_commands_help(self): def test_commands_help_no_auth(self): """Check help commands without auth info.""" - # Pop all auth info - auth_info = {key: os.environ.pop(key) - for key in os.environ.keys() - if key.startswith('OS_')} + # Pop all auth info. os.environ will be changed in loop, so do not + # replace os.environ.keys() to os.environ + for key in os.environ.keys(): + if key.startswith('OS_'): + self.useFixture(fixtures.EnvironmentVariable(key, None)) raw_output = self.openstack('help') self.assertIn('usage: openstack', raw_output) @@ -115,6 +118,3 @@ def test_commands_help_no_auth(self): self.assertIn('List containers', raw_output) raw_output = self.openstack('container list --help') self.assertIn('List containers', raw_output) - - # Restore auth info - os.environ.update(auth_info) diff --git a/openstackclient/tests/functional/common/test_quota.py b/openstackclient/tests/functional/common/test_quota.py index 1b13e95ed2..76c69a4d03 100644 --- a/openstackclient/tests/functional/common/test_quota.py +++ b/openstackclient/tests/functional/common/test_quota.py @@ -26,6 +26,7 @@ class QuotaTests(base.TestCase): @classmethod def setUpClass(cls): + super(QuotaTests, cls).setUpClass() cls.haz_network = base.is_service_enabled('network') cls.PROJECT_NAME =\ cls.get_openstack_configuration_value('auth.project_name') diff --git a/openstackclient/tests/functional/compute/v2/test_flavor.py b/openstackclient/tests/functional/compute/v2/test_flavor.py index 0b01da5172..eefd3fabd0 100644 --- a/openstackclient/tests/functional/compute/v2/test_flavor.py +++ b/openstackclient/tests/functional/compute/v2/test_flavor.py @@ -23,6 +23,7 @@ class FlavorTests(base.TestCase): @classmethod def setUpClass(cls): + super(FlavorTests, cls).setUpClass() # Make a project cmd_output = json.loads(cls.openstack( "project create -f json --enable " + cls.PROJECT_NAME @@ -31,8 +32,11 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - raw_output = cls.openstack("project delete " + cls.PROJECT_NAME) - cls.assertOutput('', raw_output) + try: + raw_output = cls.openstack("project delete " + cls.PROJECT_NAME) + cls.assertOutput('', raw_output) + finally: + super(FlavorTests, cls).tearDownClass() def test_flavor_delete(self): """Test create w/project, delete multiple""" diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py index c9f4d62c85..b7a2599674 100644 --- a/openstackclient/tests/functional/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -25,6 +25,7 @@ class ServerTests(common.ComputeTestCase): @classmethod def setUpClass(cls): + super(ServerTests, cls).setUpClass() cls.haz_network = base.is_service_enabled('network') def test_server_list(self): diff --git a/openstackclient/tests/functional/identity/v2/common.py b/openstackclient/tests/functional/identity/v2/common.py index 69ef728b15..f4bc10bddd 100644 --- a/openstackclient/tests/functional/identity/v2/common.py +++ b/openstackclient/tests/functional/identity/v2/common.py @@ -12,6 +12,7 @@ import os +import fixtures from tempest.lib.common.utils import data_utils from tempest.lib import exceptions as tempest_exceptions @@ -41,17 +42,13 @@ class IdentityTests(base.TestCase): @classmethod def setUpClass(cls): - # prepare v2 env - os.environ['OS_IDENTITY_API_VERSION'] = '2.0' - auth_url = os.environ.get('OS_AUTH_URL') - if auth_url: - os.environ['OS_AUTH_URL'] = auth_url.replace('v3', 'v2.0') - + super(IdentityTests, cls).setUpClass() # create dummy project cls.project_name = data_utils.rand_name('TestProject') cls.project_description = data_utils.rand_name('description') try: cls.openstack( + '--os-identity-api-version 2 ' 'project create ' '--description %(description)s ' '--enable ' @@ -69,7 +66,25 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - cls.openstack('project delete %s' % cls.project_name) + try: + cls.openstack( + '--os-identity-api-version 2 ' + 'project delete %s' % cls.project_name) + finally: + super(IdentityTests, cls).tearDownClass() + + def setUp(self): + super(IdentityTests, self).setUp() + # prepare v2 env + ver_fixture = fixtures.EnvironmentVariable( + 'OS_IDENTITY_API_VERSION', '2.0') + self.useFixture(ver_fixture) + auth_url = os.environ.get('OS_AUTH_URL') + if auth_url: + auth_url_fixture = fixtures.EnvironmentVariable( + 'OS_AUTH_URL', auth_url.replace('v3', 'v2.0') + ) + self.useFixture(auth_url_fixture) def _create_dummy_project(self, add_clean_up=True): project_name = data_utils.rand_name('TestProject') diff --git a/openstackclient/tests/functional/identity/v3/common.py b/openstackclient/tests/functional/identity/v3/common.py index 1ec3ac9281..6d7896d8fe 100644 --- a/openstackclient/tests/functional/identity/v3/common.py +++ b/openstackclient/tests/functional/identity/v3/common.py @@ -12,6 +12,7 @@ import os +import fixtures from tempest.lib.common.utils import data_utils from openstackclient.tests.functional import base @@ -53,16 +54,12 @@ class IdentityTests(base.TestCase): @classmethod def setUpClass(cls): - # prepare v3 env - os.environ['OS_IDENTITY_API_VERSION'] = '3' - auth_url = os.environ.get('OS_AUTH_URL') - if auth_url: - os.environ['OS_AUTH_URL'] = auth_url.replace('v2.0', 'v3') - + super(IdentityTests, cls).setUpClass() # create dummy domain cls.domain_name = data_utils.rand_name('TestDomain') cls.domain_description = data_utils.rand_name('description') cls.openstack( + '--os-identity-api-version 3 ' 'domain create ' '--description %(description)s ' '--enable ' @@ -73,6 +70,7 @@ def setUpClass(cls): cls.project_name = data_utils.rand_name('TestProject') cls.project_description = data_utils.rand_name('description') cls.openstack( + '--os-identity-api-version 3 ' 'project create ' '--domain %(domain)s ' '--description %(description)s ' @@ -83,11 +81,30 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - # delete dummy project - cls.openstack('project delete %s' % cls.project_name) - # disable and delete dummy domain - cls.openstack('domain set --disable %s' % cls.domain_name) - cls.openstack('domain delete %s' % cls.domain_name) + try: + # delete dummy project + cls.openstack('--os-identity-api-version 3 ' + 'project delete %s' % cls.project_name) + # disable and delete dummy domain + cls.openstack('--os-identity-api-version 3 ' + 'domain set --disable %s' % cls.domain_name) + cls.openstack('--os-identity-api-version 3 ' + 'domain delete %s' % cls.domain_name) + finally: + super(IdentityTests, cls).tearDownClass() + + def setUp(self): + super(IdentityTests, self).setUp() + # prepare v3 env + ver_fixture = fixtures.EnvironmentVariable( + 'OS_IDENTITY_API_VERSION', '3') + self.useFixture(ver_fixture) + auth_url = os.environ.get('OS_AUTH_URL') + if auth_url: + auth_url_fixture = fixtures.EnvironmentVariable( + 'OS_AUTH_URL', auth_url.replace('v2.0', 'v3') + ) + self.useFixture(auth_url_fixture) def _create_dummy_user(self, add_clean_up=True): username = data_utils.rand_name('TestUser') diff --git a/openstackclient/tests/functional/image/v1/test_image.py b/openstackclient/tests/functional/image/v1/test_image.py index 901f4337a4..fa073f99a3 100644 --- a/openstackclient/tests/functional/image/v1/test_image.py +++ b/openstackclient/tests/functional/image/v1/test_image.py @@ -11,9 +11,10 @@ # under the License. import json -import os import uuid +import fixtures + from openstackclient.tests.functional import base @@ -25,8 +26,9 @@ class ImageTests(base.TestCase): @classmethod def setUpClass(cls): - os.environ['OS_IMAGE_API_VERSION'] = '1' + super(ImageTests, cls).setUpClass() json_output = json.loads(cls.openstack( + '--os-image-api-version 1 ' 'image create -f json ' + cls.NAME )) @@ -35,10 +37,21 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - cls.openstack( - 'image delete ' + - cls.image_id + try: + cls.openstack( + '--os-image-api-version 1 ' + 'image delete ' + + cls.image_id + ) + finally: + super(ImageTests, cls).tearDownClass() + + def setUp(self): + super(ImageTests, self).setUp() + ver_fixture = fixtures.EnvironmentVariable( + 'OS_IMAGE_API_VERSION', '1' ) + self.useFixture(ver_fixture) def test_image_list(self): json_output = json.loads(self.openstack( diff --git a/openstackclient/tests/functional/image/v2/test_image.py b/openstackclient/tests/functional/image/v2/test_image.py index a93fa8cbee..278ba5b948 100644 --- a/openstackclient/tests/functional/image/v2/test_image.py +++ b/openstackclient/tests/functional/image/v2/test_image.py @@ -11,9 +11,9 @@ # under the License. import json -import os import uuid +import fixtures # from glanceclient import exc as image_exceptions from openstackclient.tests.functional import base @@ -27,8 +27,9 @@ class ImageTests(base.TestCase): @classmethod def setUpClass(cls): - os.environ['OS_IMAGE_API_VERSION'] = '2' + super(ImageTests, cls).setUpClass() json_output = json.loads(cls.openstack( + '--os-image-api-version 2 ' 'image create -f json ' + cls.NAME )) @@ -37,10 +38,21 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - cls.openstack( - 'image delete ' + - cls.image_id + try: + cls.openstack( + '--os-image-api-version 2 ' + 'image delete ' + + cls.image_id + ) + finally: + super(ImageTests, cls).tearDownClass() + + def setUp(self): + super(ImageTests, self).setUp() + ver_fixture = fixtures.EnvironmentVariable( + 'OS_IMAGE_API_VERSION', '2' ) + self.useFixture(ver_fixture) def test_image_list(self): json_output = json.loads(self.openstack( diff --git a/openstackclient/tests/functional/network/v2/common.py b/openstackclient/tests/functional/network/v2/common.py index bed07878f6..e3835abf59 100644 --- a/openstackclient/tests/functional/network/v2/common.py +++ b/openstackclient/tests/functional/network/v2/common.py @@ -18,5 +18,5 @@ class NetworkTests(base.TestCase): @classmethod def setUpClass(cls): - # super(NetworkTests, cls).setUp() + super(NetworkTests, cls).setUpClass() cls.haz_network = base.is_service_enabled('network') diff --git a/openstackclient/tests/functional/network/v2/test_floating_ip.py b/openstackclient/tests/functional/network/v2/test_floating_ip.py index 9e34622cf4..1d11fc5d2d 100644 --- a/openstackclient/tests/functional/network/v2/test_floating_ip.py +++ b/openstackclient/tests/functional/network/v2/test_floating_ip.py @@ -88,19 +88,22 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - if cls.haz_network: - del_output = cls.openstack( - 'subnet delete ' + - cls.EXTERNAL_SUBNET_NAME + ' ' + - cls.PRIVATE_SUBNET_NAME - ) - cls.assertOutput('', del_output) - del_output = cls.openstack( - 'network delete ' + - cls.EXTERNAL_NETWORK_NAME + ' ' + - cls.PRIVATE_NETWORK_NAME - ) - cls.assertOutput('', del_output) + try: + if cls.haz_network: + del_output = cls.openstack( + 'subnet delete ' + + cls.EXTERNAL_SUBNET_NAME + ' ' + + cls.PRIVATE_SUBNET_NAME + ) + cls.assertOutput('', del_output) + del_output = cls.openstack( + 'network delete ' + + cls.EXTERNAL_NETWORK_NAME + ' ' + + cls.PRIVATE_NETWORK_NAME + ) + cls.assertOutput('', del_output) + finally: + super(FloatingIpTests, cls).tearDownClass() def setUp(self): super(FloatingIpTests, self).setUp() diff --git a/openstackclient/tests/functional/network/v2/test_ip_availability.py b/openstackclient/tests/functional/network/v2/test_ip_availability.py index cb4b8150ab..86a53c0ce0 100644 --- a/openstackclient/tests/functional/network/v2/test_ip_availability.py +++ b/openstackclient/tests/functional/network/v2/test_ip_availability.py @@ -41,17 +41,20 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - if cls.haz_network: - raw_subnet = cls.openstack( - 'subnet delete ' + - cls.NAME - ) - raw_network = cls.openstack( - 'network delete ' + - cls.NETWORK_NAME - ) - cls.assertOutput('', raw_subnet) - cls.assertOutput('', raw_network) + try: + if cls.haz_network: + raw_subnet = cls.openstack( + 'subnet delete ' + + cls.NAME + ) + raw_network = cls.openstack( + 'network delete ' + + cls.NETWORK_NAME + ) + cls.assertOutput('', raw_subnet) + cls.assertOutput('', raw_network) + finally: + super(IPAvailabilityTests, cls).tearDownClass() def setUp(self): super(IPAvailabilityTests, self).setUp() diff --git a/openstackclient/tests/functional/network/v2/test_network.py b/openstackclient/tests/functional/network/v2/test_network.py index b23323a00e..91939703d1 100644 --- a/openstackclient/tests/functional/network/v2/test_network.py +++ b/openstackclient/tests/functional/network/v2/test_network.py @@ -361,7 +361,7 @@ def test_network_list(self): self.assertNotIn(name2, col_name) def test_network_dhcp_agent(self): - if self.haz_network: + if not self.haz_network: self.skipTest("No Network service present") name1 = uuid.uuid4().hex @@ -408,7 +408,7 @@ def test_network_dhcp_agent(self): def test_network_set(self): """Tests create options, set, show, delete""" - if self.haz_network: + if not self.haz_network: self.skipTest("No Network service present") name = uuid.uuid4().hex diff --git a/openstackclient/tests/functional/network/v2/test_network_meter_rule.py b/openstackclient/tests/functional/network/v2/test_network_meter_rule.py index 71b406b46e..31bc08453b 100644 --- a/openstackclient/tests/functional/network/v2/test_network_meter_rule.py +++ b/openstackclient/tests/functional/network/v2/test_network_meter_rule.py @@ -39,13 +39,15 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - common.NetworkTests.tearDownClass() - if cls.haz_network: - raw_output = cls.openstack( - 'network meter delete ' + - cls.METER_ID - ) - cls.assertOutput('', raw_output) + try: + if cls.haz_network: + raw_output = cls.openstack( + 'network meter delete ' + + cls.METER_ID + ) + cls.assertOutput('', raw_output) + finally: + common.NetworkTests.tearDownClass() def setUp(self): super(TestMeterRule, self).setUp() diff --git a/openstackclient/tests/functional/network/v2/test_network_qos_policy.py b/openstackclient/tests/functional/network/v2/test_network_qos_policy.py index ccbf437c3b..53c15ecf69 100644 --- a/openstackclient/tests/functional/network/v2/test_network_qos_policy.py +++ b/openstackclient/tests/functional/network/v2/test_network_qos_policy.py @@ -39,12 +39,15 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - if cls.haz_network: - raw_output = cls.openstack( - 'network qos policy delete ' + - cls.NAME - ) - cls.assertOutput('', raw_output) + try: + if cls.haz_network: + raw_output = cls.openstack( + 'network qos policy delete ' + + cls.NAME + ) + cls.assertOutput('', raw_output) + finally: + super(NetworkQosPolicyTests, cls).tearDownClass() def setUp(self): super(NetworkQosPolicyTests, self).setUp() diff --git a/openstackclient/tests/functional/network/v2/test_network_qos_rule.py b/openstackclient/tests/functional/network/v2/test_network_qos_rule.py index 6e40d8e0d9..8b34422fb1 100644 --- a/openstackclient/tests/functional/network/v2/test_network_qos_rule.py +++ b/openstackclient/tests/functional/network/v2/test_network_qos_rule.py @@ -51,17 +51,20 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - if cls.haz_network: - raw_output = cls.openstack( - 'network qos rule delete ' + - cls.QOS_POLICY_NAME + ' ' + - cls.RULE_ID - ) - cls.openstack( - 'network qos policy delete ' + - cls.QOS_POLICY_NAME - ) - cls.assertOutput('', raw_output) + try: + if cls.haz_network: + raw_output = cls.openstack( + 'network qos rule delete ' + + cls.QOS_POLICY_NAME + ' ' + + cls.RULE_ID + ) + cls.openstack( + 'network qos policy delete ' + + cls.QOS_POLICY_NAME + ) + cls.assertOutput('', raw_output) + finally: + super(NetworkQosRuleTestsMinimumBandwidth, cls).tearDownClass() def setUp(self): super(NetworkQosRuleTestsMinimumBandwidth, self).setUp() @@ -123,14 +126,18 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - if cls.haz_network: - raw_output = cls.openstack( - 'network qos rule delete ' + - cls.QOS_POLICY_NAME + ' ' + - cls.RULE_ID - ) - cls.openstack('network qos policy delete ' + cls.QOS_POLICY_NAME) - cls.assertOutput('', raw_output) + try: + if cls.haz_network: + raw_output = cls.openstack( + 'network qos rule delete ' + + cls.QOS_POLICY_NAME + ' ' + + cls.RULE_ID + ) + cls.openstack( + 'network qos policy delete ' + cls.QOS_POLICY_NAME) + cls.assertOutput('', raw_output) + finally: + super(NetworkQosRuleTestsDSCPMarking, cls).tearDownClass() def setUp(self): super(NetworkQosRuleTestsDSCPMarking, self).setUp() @@ -198,14 +205,18 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - if cls.haz_network: - raw_output = cls.openstack( - 'network qos rule delete ' + - cls.QOS_POLICY_NAME + ' ' + - cls.RULE_ID - ) - cls.openstack('network qos policy delete ' + cls.QOS_POLICY_NAME) - cls.assertOutput('', raw_output) + try: + if cls.haz_network: + raw_output = cls.openstack( + 'network qos rule delete ' + + cls.QOS_POLICY_NAME + ' ' + + cls.RULE_ID + ) + cls.openstack( + 'network qos policy delete ' + cls.QOS_POLICY_NAME) + cls.assertOutput('', raw_output) + finally: + super(NetworkQosRuleTestsBandwidthLimit, cls).tearDownClass() def setUp(self): super(NetworkQosRuleTestsBandwidthLimit, self).setUp() diff --git a/openstackclient/tests/functional/network/v2/test_network_rbac.py b/openstackclient/tests/functional/network/v2/test_network_rbac.py index 893f199397..2206761f04 100644 --- a/openstackclient/tests/functional/network/v2/test_network_rbac.py +++ b/openstackclient/tests/functional/network/v2/test_network_rbac.py @@ -47,15 +47,18 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - if cls.haz_network: - raw_output_rbac = cls.openstack( - 'network rbac delete ' + cls.ID - ) - raw_output_network = cls.openstack( - 'network delete ' + cls.OBJECT_ID - ) - cls.assertOutput('', raw_output_rbac) - cls.assertOutput('', raw_output_network) + try: + if cls.haz_network: + raw_output_rbac = cls.openstack( + 'network rbac delete ' + cls.ID + ) + raw_output_network = cls.openstack( + 'network delete ' + cls.OBJECT_ID + ) + cls.assertOutput('', raw_output_rbac) + cls.assertOutput('', raw_output_network) + finally: + super(NetworkRBACTests, cls).tearDownClass() def setUp(self): super(NetworkRBACTests, self).setUp() diff --git a/openstackclient/tests/functional/network/v2/test_network_segment.py b/openstackclient/tests/functional/network/v2/test_network_segment.py index 6dec82d9e3..b34515fa41 100644 --- a/openstackclient/tests/functional/network/v2/test_network_segment.py +++ b/openstackclient/tests/functional/network/v2/test_network_segment.py @@ -55,11 +55,14 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - if cls.haz_network: - raw_output = cls.openstack( - 'network delete ' + cls.NETWORK_NAME - ) - cls.assertOutput('', raw_output) + try: + if cls.haz_network: + raw_output = cls.openstack( + 'network delete ' + cls.NETWORK_NAME + ) + cls.assertOutput('', raw_output) + finally: + super(NetworkSegmentTests, cls).tearDownClass() def setUp(self): super(NetworkSegmentTests, self).setUp() diff --git a/openstackclient/tests/functional/network/v2/test_port.py b/openstackclient/tests/functional/network/v2/test_port.py index 14454454e4..09ac3566e4 100644 --- a/openstackclient/tests/functional/network/v2/test_port.py +++ b/openstackclient/tests/functional/network/v2/test_port.py @@ -33,11 +33,14 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - if cls.haz_network: - raw_output = cls.openstack( - 'network delete ' + cls.NETWORK_NAME - ) - cls.assertOutput('', raw_output) + try: + if cls.haz_network: + raw_output = cls.openstack( + 'network delete ' + cls.NETWORK_NAME + ) + cls.assertOutput('', raw_output) + finally: + super(PortTests, cls).tearDownClass() def setUp(self): super(PortTests, self).setUp() diff --git a/openstackclient/tests/functional/network/v2/test_security_group.py b/openstackclient/tests/functional/network/v2/test_security_group.py index 6da7e204d8..b601c913fd 100644 --- a/openstackclient/tests/functional/network/v2/test_security_group.py +++ b/openstackclient/tests/functional/network/v2/test_security_group.py @@ -38,20 +38,23 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - if cls.haz_network: - # Rename test - raw_output = cls.openstack( - 'security group set --name ' + - cls.OTHER_NAME + ' ' + - cls.NAME - ) - cls.assertOutput('', raw_output) - # Delete test - raw_output = cls.openstack( - 'security group delete ' + - cls.OTHER_NAME - ) - cls.assertOutput('', raw_output) + try: + if cls.haz_network: + # Rename test + raw_output = cls.openstack( + 'security group set --name ' + + cls.OTHER_NAME + ' ' + + cls.NAME + ) + cls.assertOutput('', raw_output) + # Delete test + raw_output = cls.openstack( + 'security group delete ' + + cls.OTHER_NAME + ) + cls.assertOutput('', raw_output) + finally: + super(SecurityGroupTests, cls).tearDownClass() def setUp(self): super(SecurityGroupTests, self).setUp() diff --git a/openstackclient/tests/functional/network/v2/test_security_group_rule.py b/openstackclient/tests/functional/network/v2/test_security_group_rule.py index e153116b14..40951a011d 100644 --- a/openstackclient/tests/functional/network/v2/test_security_group_rule.py +++ b/openstackclient/tests/functional/network/v2/test_security_group_rule.py @@ -51,18 +51,20 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - if cls.haz_network: - raw_output = cls.openstack( - 'security group rule delete ' + - cls.SECURITY_GROUP_RULE_ID - ) - cls.assertOutput('', raw_output) - - raw_output = cls.openstack( - 'security group delete ' + - cls.SECURITY_GROUP_NAME - ) - cls.assertOutput('', raw_output) + try: + if cls.haz_network: + raw_output = cls.openstack( + 'security group rule delete ' + + cls.SECURITY_GROUP_RULE_ID + ) + cls.assertOutput('', raw_output) + raw_output = cls.openstack( + 'security group delete ' + + cls.SECURITY_GROUP_NAME + ) + cls.assertOutput('', raw_output) + finally: + super(SecurityGroupRuleTests, cls).tearDownClass() def setUp(self): super(SecurityGroupRuleTests, self).setUp() diff --git a/openstackclient/tests/functional/network/v2/test_subnet.py b/openstackclient/tests/functional/network/v2/test_subnet.py index 413754157a..040b645b1b 100644 --- a/openstackclient/tests/functional/network/v2/test_subnet.py +++ b/openstackclient/tests/functional/network/v2/test_subnet.py @@ -36,12 +36,15 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - if cls.haz_network: - raw_output = cls.openstack( - 'network delete ' + - cls.NETWORK_NAME - ) - cls.assertOutput('', raw_output) + try: + if cls.haz_network: + raw_output = cls.openstack( + 'network delete ' + + cls.NETWORK_NAME + ) + cls.assertOutput('', raw_output) + finally: + super(SubnetTests, cls).tearDownClass() def setUp(self): super(SubnetTests, self).setUp() diff --git a/openstackclient/tests/functional/object/v1/test_container.py b/openstackclient/tests/functional/object/v1/test_container.py index af76efd96c..acfbab11de 100644 --- a/openstackclient/tests/functional/object/v1/test_container.py +++ b/openstackclient/tests/functional/object/v1/test_container.py @@ -21,14 +21,18 @@ class ContainerTests(base.TestCase): @classmethod def setUpClass(cls): + super(ContainerTests, cls).setUpClass() opts = cls.get_opts(['container']) raw_output = cls.openstack('container create ' + cls.NAME + opts) cls.assertOutput(cls.NAME + '\n', raw_output) @classmethod def tearDownClass(cls): - raw_output = cls.openstack('container delete ' + cls.NAME) - cls.assertOutput('', raw_output) + try: + raw_output = cls.openstack('container delete ' + cls.NAME) + cls.assertOutput('', raw_output) + finally: + super(ContainerTests, cls).tearDownClass() def test_container_list(self): opts = self.get_opts(['Name']) diff --git a/openstackclient/tests/functional/volume/v1/common.py b/openstackclient/tests/functional/volume/v1/common.py index f9d96bbb6c..4978cea31c 100644 --- a/openstackclient/tests/functional/volume/v1/common.py +++ b/openstackclient/tests/functional/volume/v1/common.py @@ -10,7 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. -import os +import fixtures from openstackclient.tests.functional.volume import base @@ -18,6 +18,9 @@ class BaseVolumeTests(base.BaseVolumeTests): """Base class for Volume functional tests. """ - @classmethod - def setUpClass(cls): - os.environ['OS_VOLUME_API_VERSION'] = '1' + def setUp(self): + super(BaseVolumeTests, self).setUp() + ver_fixture = fixtures.EnvironmentVariable( + 'OS_VOLUME_API_VERSION', '1' + ) + self.useFixture(ver_fixture) diff --git a/openstackclient/tests/functional/volume/v1/test_snapshot.py b/openstackclient/tests/functional/volume/v1/test_snapshot.py index 28726762d7..c60472c55e 100644 --- a/openstackclient/tests/functional/volume/v1/test_snapshot.py +++ b/openstackclient/tests/functional/volume/v1/test_snapshot.py @@ -35,9 +35,12 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - cls.wait_for_status('volume', cls.VOLLY, 'available') - raw_output = cls.openstack('volume delete --force ' + cls.VOLLY) - cls.assertOutput('', raw_output) + try: + cls.wait_for_status('volume', cls.VOLLY, 'available') + raw_output = cls.openstack('volume delete --force ' + cls.VOLLY) + cls.assertOutput('', raw_output) + finally: + super(VolumeSnapshotTests, cls).tearDownClass() def test_volume_snapshot__delete(self): """Test create, delete multiple""" diff --git a/openstackclient/tests/functional/volume/v1/test_transfer_request.py b/openstackclient/tests/functional/volume/v1/test_transfer_request.py index bd6128295e..73191fc9d2 100644 --- a/openstackclient/tests/functional/volume/v1/test_transfer_request.py +++ b/openstackclient/tests/functional/volume/v1/test_transfer_request.py @@ -37,12 +37,15 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - raw_output_transfer = cls.openstack( - 'volume transfer request delete ' + cls.NAME) - raw_output_volume = cls.openstack( - 'volume delete ' + cls.VOLUME_NAME) - cls.assertOutput('', raw_output_transfer) - cls.assertOutput('', raw_output_volume) + try: + raw_output_transfer = cls.openstack( + 'volume transfer request delete ' + cls.NAME) + raw_output_volume = cls.openstack( + 'volume delete ' + cls.VOLUME_NAME) + cls.assertOutput('', raw_output_transfer) + cls.assertOutput('', raw_output_volume) + finally: + super(TransferRequestTests, cls).tearDownClass() def test_volume_transfer_request_accept(self): volume_name = uuid.uuid4().hex diff --git a/openstackclient/tests/functional/volume/v1/test_volume_type.py b/openstackclient/tests/functional/volume/v1/test_volume_type.py index acad34add3..74e1407003 100644 --- a/openstackclient/tests/functional/volume/v1/test_volume_type.py +++ b/openstackclient/tests/functional/volume/v1/test_volume_type.py @@ -31,8 +31,11 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - raw_output = cls.openstack('volume type delete ' + cls.NAME) - cls.assertOutput('', raw_output) + try: + raw_output = cls.openstack('volume type delete ' + cls.NAME) + cls.assertOutput('', raw_output) + finally: + super(VolumeTypeTests, cls).tearDownClass() def test_volume_type_list(self): cmd_output = json.loads(self.openstack('volume type list -f json')) diff --git a/openstackclient/tests/functional/volume/v2/common.py b/openstackclient/tests/functional/volume/v2/common.py index 1d14719fc7..3817671425 100644 --- a/openstackclient/tests/functional/volume/v2/common.py +++ b/openstackclient/tests/functional/volume/v2/common.py @@ -10,7 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. -import os +import fixtures from openstackclient.tests.functional.volume import base @@ -18,6 +18,9 @@ class BaseVolumeTests(base.BaseVolumeTests): """Base class for Volume functional tests. """ - @classmethod - def setUpClass(cls): - os.environ['OS_VOLUME_API_VERSION'] = '2' + def setUp(self): + super(BaseVolumeTests, self).setUp() + ver_fixture = fixtures.EnvironmentVariable( + 'OS_VOLUME_API_VERSION', '2' + ) + self.useFixture(ver_fixture) diff --git a/openstackclient/tests/functional/volume/v2/test_snapshot.py b/openstackclient/tests/functional/volume/v2/test_snapshot.py index 2fff43c820..ba6b2c2837 100644 --- a/openstackclient/tests/functional/volume/v2/test_snapshot.py +++ b/openstackclient/tests/functional/volume/v2/test_snapshot.py @@ -35,10 +35,13 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - cls.wait_for_status('volume', cls.VOLLY, 'available') - raw_output = cls.openstack( - 'volume delete --force ' + cls.VOLLY) - cls.assertOutput('', raw_output) + try: + cls.wait_for_status('volume', cls.VOLLY, 'available') + raw_output = cls.openstack( + 'volume delete --force ' + cls.VOLLY) + cls.assertOutput('', raw_output) + finally: + super(VolumeSnapshotTests, cls).tearDownClass() def test_volume_snapshot__delete(self): """Test create, delete multiple""" diff --git a/openstackclient/tests/functional/volume/v2/test_transfer_request.py b/openstackclient/tests/functional/volume/v2/test_transfer_request.py index e9c2236b7d..33495af637 100644 --- a/openstackclient/tests/functional/volume/v2/test_transfer_request.py +++ b/openstackclient/tests/functional/volume/v2/test_transfer_request.py @@ -38,12 +38,15 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - raw_output_transfer = cls.openstack( - 'volume transfer request delete ' + cls.NAME) - raw_output_volume = cls.openstack( - 'volume delete ' + cls.VOLUME_NAME) - cls.assertOutput('', raw_output_transfer) - cls.assertOutput('', raw_output_volume) + try: + raw_output_transfer = cls.openstack( + 'volume transfer request delete ' + cls.NAME) + raw_output_volume = cls.openstack( + 'volume delete ' + cls.VOLUME_NAME) + cls.assertOutput('', raw_output_transfer) + cls.assertOutput('', raw_output_volume) + finally: + super(TransferRequestTests, cls).tearDownClass() def test_volume_transfer_request_accept(self): volume_name = uuid.uuid4().hex diff --git a/openstackclient/tests/functional/volume/v2/test_volume_type.py b/openstackclient/tests/functional/volume/v2/test_volume_type.py index 11acf2f8a7..99630e6b04 100644 --- a/openstackclient/tests/functional/volume/v2/test_volume_type.py +++ b/openstackclient/tests/functional/volume/v2/test_volume_type.py @@ -31,8 +31,11 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - raw_output = cls.openstack('volume type delete ' + cls.NAME) - cls.assertOutput('', raw_output) + try: + raw_output = cls.openstack('volume type delete ' + cls.NAME) + cls.assertOutput('', raw_output) + finally: + super(VolumeTypeTests, cls).tearDownClass() def test_volume_type_list(self): cmd_output = json.loads(self.openstack('volume type list -f json')) diff --git a/openstackclient/tests/functional/volume/v3/common.py b/openstackclient/tests/functional/volume/v3/common.py index bc8befa67d..a710a6835c 100644 --- a/openstackclient/tests/functional/volume/v3/common.py +++ b/openstackclient/tests/functional/volume/v3/common.py @@ -10,7 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. -import os +import fixtures from openstackclient.tests.functional.volume import base @@ -18,6 +18,9 @@ class BaseVolumeTests(base.BaseVolumeTests): """Base class for Volume functional tests. """ - @classmethod - def setUpClass(cls): - os.environ['OS_VOLUME_API_VERSION'] = '3' + def setUp(self): + super(BaseVolumeTests, self).setUp() + ver_fixture = fixtures.EnvironmentVariable( + 'OS_VOLUME_API_VERSION', '3' + ) + self.useFixture(ver_fixture) diff --git a/openstackclient/tests/functional/volume/v3/test_qos.py b/openstackclient/tests/functional/volume/v3/test_qos.py index a7af3c5b97..a6290fc531 100644 --- a/openstackclient/tests/functional/volume/v3/test_qos.py +++ b/openstackclient/tests/functional/volume/v3/test_qos.py @@ -10,15 +10,9 @@ # License for the specific language governing permissions and limitations # under the License. -import os - from openstackclient.tests.functional.volume.v2 import test_qos as v2 +from openstackclient.tests.functional.volume.v3 import common -class QosTests(v2.QosTests): +class QosTests(common.BaseVolumeTests, v2.QosTests): """Functional tests for volume qos. """ - - @classmethod - def setUpClass(cls): - super(QosTests, cls).setUpClass() - os.environ['OS_VOLUME_API_VERSION'] = '3' diff --git a/openstackclient/tests/functional/volume/v3/test_snapshot.py b/openstackclient/tests/functional/volume/v3/test_snapshot.py index bf05b9de9d..38e0563a7c 100644 --- a/openstackclient/tests/functional/volume/v3/test_snapshot.py +++ b/openstackclient/tests/functional/volume/v3/test_snapshot.py @@ -11,13 +11,8 @@ # under the License. from openstackclient.tests.functional.volume.v2 import test_snapshot as v2 -import os +from openstackclient.tests.functional.volume.v3 import common -class VolumeSnapshotTests(v2.VolumeSnapshotTests): +class VolumeSnapshotTests(common.BaseVolumeTests, v2.VolumeSnapshotTests): """Functional tests for volume snapshot. """ - - @classmethod - def setUpClass(cls): - super(VolumeSnapshotTests, cls).setUpClass() - os.environ['OS_VOLUME_API_VERSION'] = '3' diff --git a/openstackclient/tests/functional/volume/v3/test_transfer_request.py b/openstackclient/tests/functional/volume/v3/test_transfer_request.py index 7b54dd20f5..b325323752 100644 --- a/openstackclient/tests/functional/volume/v3/test_transfer_request.py +++ b/openstackclient/tests/functional/volume/v3/test_transfer_request.py @@ -12,13 +12,8 @@ from openstackclient.tests.functional.volume.v2 import test_transfer_request \ as v2 -import os +from openstackclient.tests.functional.volume.v3 import common -class TransferRequestTests(v2.TransferRequestTests): +class TransferRequestTests(common.BaseVolumeTests, v2.TransferRequestTests): """Functional tests for transfer request. """ - - @classmethod - def setUpClass(cls): - super(TransferRequestTests, cls).setUpClass() - os.environ['OS_VOLUME_API_VERSION'] = '3' diff --git a/openstackclient/tests/functional/volume/v3/test_volume.py b/openstackclient/tests/functional/volume/v3/test_volume.py index 333826d833..283b830f63 100644 --- a/openstackclient/tests/functional/volume/v3/test_volume.py +++ b/openstackclient/tests/functional/volume/v3/test_volume.py @@ -11,13 +11,8 @@ # under the License. from openstackclient.tests.functional.volume.v2 import test_volume as v2 -import os +from openstackclient.tests.functional.volume.v3 import common -class VolumeTests(v2.VolumeTests): +class VolumeTests(common.BaseVolumeTests, v2.VolumeTests): """Functional tests for volume. """ - - @classmethod - def setUpClass(cls): - super(VolumeTests, cls).setUpClass() - os.environ['OS_VOLUME_API_VERSION'] = '3' diff --git a/openstackclient/tests/functional/volume/v3/test_volume_type.py b/openstackclient/tests/functional/volume/v3/test_volume_type.py index f10e64b426..eb66515ed1 100644 --- a/openstackclient/tests/functional/volume/v3/test_volume_type.py +++ b/openstackclient/tests/functional/volume/v3/test_volume_type.py @@ -11,13 +11,8 @@ # under the License. from openstackclient.tests.functional.volume.v2 import test_volume_type as v2 -import os +from openstackclient.tests.functional.volume.v3 import common -class VolumeTypeTests(v2.VolumeTypeTests): +class VolumeTypeTests(common.BaseVolumeTests, v2.VolumeTypeTests): """Functional tests for volume type. """ - - @classmethod - def setUpClass(cls): - super(VolumeTypeTests, cls).setUpClass() - os.environ['OS_VOLUME_API_VERSION'] = '3' From 26ec06e28140492689697948ce476c0f7e83f652 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Fri, 21 Jul 2017 05:07:46 +0000 Subject: [PATCH 1739/3095] Updated from global requirements Change-Id: I9acdcf3a966dc7c6f92b077848d65f408fdd07eb --- requirements.txt | 4 ++-- test-requirements.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index d0489c4df9..e94ecde37e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,10 +5,10 @@ pbr!=2.1.0,>=2.0.0 # Apache-2.0 six>=1.9.0 # MIT Babel!=2.4.0,>=2.3.4 # BSD -cliff>=2.6.0 # Apache-2.0 +cliff>=2.8.0 # Apache-2.0 keystoneauth1>=2.21.0 # Apache-2.0 openstacksdk>=0.9.17 # Apache-2.0 -osc-lib>=1.5.1 # Apache-2.0 +osc-lib>=1.7.0 # Apache-2.0 oslo.i18n!=3.15.2,>=2.1.0 # Apache-2.0 oslo.utils>=3.20.0 # Apache-2.0 python-glanceclient>=2.7.0 # Apache-2.0 diff --git a/test-requirements.txt b/test-requirements.txt index 4ab3250e04..e3848618e8 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -13,7 +13,7 @@ requests>=2.14.2 # Apache-2.0 requests-mock>=1.1 # Apache-2.0 sphinx>=1.6.2 # BSD stevedore>=1.20.0 # Apache-2.0 -os-client-config>=1.27.0 # Apache-2.0 +os-client-config>=1.28.0 # Apache-2.0 os-testr>=0.8.0 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD testtools>=1.4.0 # MIT From 9cfa12df2adf9265acb82f9c5aa5f3a26dc56bb1 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Tue, 30 May 2017 10:32:19 +0100 Subject: [PATCH 1740/3095] Start using 'cliff.sphinxext' 'cliff', the command line library used by 'osc_lib' (and, thus, 'python-openstackclient') recently gained a Sphinx extension to automatically document cliff commands. This allows us to use the documentation we already have in code instead of duplicating it in the documentation. Introduce the use of this, starting with the 'server' commands. This requires extending the descriptions for two commands to ensure no information is lost. Change-Id: If701af8d5a3f78f4b173ceb476dd0c163be4b6ca --- doc/source/cli/command-objects/server.rst | 1015 ++------------------- doc/source/conf.py | 10 + openstackclient/compute/v2/server.py | 32 +- 3 files changed, 87 insertions(+), 970 deletions(-) diff --git a/doc/source/cli/command-objects/server.rst b/doc/source/cli/command-objects/server.rst index 1118282204..a7fef2578c 100644 --- a/doc/source/cli/command-objects/server.rst +++ b/doc/source/cli/command-objects/server.rst @@ -4,990 +4,83 @@ server Compute v2 -server add fixed ip -------------------- +.. autoprogram-cliff:: openstack.compute.v2 + :command: server add * -Add fixed IP address to server +.. autoprogram-cliff:: openstack.compute.v2 + :command: server create -.. program:: server add fixed ip -.. code:: bash +.. autoprogram-cliff:: openstack.compute.v2 + :command: server delete - openstack server add fixed ip - [--fixed-ip-address ] - - +.. autoprogram-cliff:: openstack.compute.v2 + :command: server dump create -.. option:: --fixed-ip-address +.. autoprogram-cliff:: openstack.compute.v2 + :command: server list - Requested fixed IP address +.. autoprogram-cliff:: openstack.compute.v2 + :command: server lock -.. describe:: +.. autoprogram-cliff:: openstack.compute.v2 + :command: server migrate - Server to receive the fixed IP address (name or ID) +.. autoprogram-cliff:: openstack.compute.v2 + :command: server pause -.. describe:: +.. autoprogram-cliff:: openstack.compute.v2 + :command: server reboot - Network to allocate the fixed IP address from (name or ID) +.. autoprogram-cliff:: openstack.compute.v2 + :command: server rebuild -server add floating ip ----------------------- +.. autoprogram-cliff:: openstack.compute.v2 + :command: server remove * -Add floating IP address to server +.. autoprogram-cliff:: openstack.compute.v2 + :command: server rescue -.. program:: server add floating ip -.. code:: bash +.. autoprogram-cliff:: openstack.compute.v2 + :command: server resize - openstack server add floating ip - [--fixed-ip-address ] - - +.. autoprogram-cliff:: openstack.compute.v2 + :command: server restore -.. option:: --fixed-ip-address +.. autoprogram-cliff:: openstack.compute.v2 + :command: server resume - Fixed IP address to associate with this floating IP address +.. autoprogram-cliff:: openstack.compute.v2 + :command: server set -.. describe:: +.. autoprogram-cliff:: openstack.compute.v2 + :command: server shelve - Server to receive the floating IP address (name or ID) +.. autoprogram-cliff:: openstack.compute.v2 + :command: server show -.. describe:: +.. autoprogram-cliff:: openstack.compute.v2 + :command: server ssh - Floating IP address to assign to server (IP only) +.. autoprogram-cliff:: openstack.compute.v2 + :command: server start -server add port ---------------- +.. autoprogram-cliff:: openstack.compute.v2 + :command: server stop -Add port to server +.. autoprogram-cliff:: openstack.compute.v2 + :command: server suspend -.. program:: server add port -.. code:: bash +.. autoprogram-cliff:: openstack.compute.v2 + :command: server unlock - openstack server add port - - +.. autoprogram-cliff:: openstack.compute.v2 + :command: server unpause -.. describe:: +.. autoprogram-cliff:: openstack.compute.v2 + :command: server unrescue - Server to add the port to (name or ID) +.. autoprogram-cliff:: openstack.compute.v2 + :command: server unset -.. describe:: - - Port to add to the server (name or ID) - -server add security group -------------------------- - -Add security group to server - -.. program:: server add security group -.. code:: bash - - openstack server add security group - - - -.. describe:: - - Server (name or ID) - -.. describe:: - - Security group to add (name or ID) - -server add volume ------------------ - -Add volume to server - -.. program:: server add volume -.. code:: bash - - openstack server add volume - [--device ] - - - -.. option:: --device - - Server internal device name for volume - -.. describe:: - - Server (name or ID) - -.. describe:: - - Volume to add (name or ID) - -server create -------------- - -Create a new server - -.. program:: server create -.. code:: bash - - openstack server create - --image | --volume - --flavor - [--security-group [...] ] - [--key-name ] - [--property [...] ] - [--file ] [...] ] - [--user-data ] - [--availability-zone ] - [--block-device-mapping [...] ] - [--nic [...] ] - [--network ] - [--port ] - [--hint [...] ] - [--config-drive |True ] - [--min ] - [--max ] - [--wait] - - -.. option:: --image - - Create server boot disk from this image (name or ID) - -.. option:: --volume - - Create server using this volume as the boot disk (name or ID) - - This option automatically creates a block device mapping with a boot - index of 0. On many hypervisors (libvirt/kvm for example) this will - be device ``vda``. Do not create a duplicate mapping using - :option:`--block-device-mapping` for this volume. - -.. option:: --flavor - - Create server with this flavor (name or ID) - -.. option:: --security-group - - Security group to assign to this server (name or ID) - (repeat option to set multiple groups) - -.. option:: --key-name - - Keypair to inject into this server (optional extension) - -.. option:: --property - - Set a property on this server - (repeat option to set multiple values) - -.. option:: --file - - File to inject into image before boot - (repeat option to set multiple files) - -.. option:: --user-data - - User data file to serve from the metadata server - -.. option:: --availability-zone - - Select an availability zone for the server - -.. option:: --block-device-mapping - - Create a block device on the server. - - Block device mapping in the format - - =::: - - : block device name, like: vdb, xvdc (required) - - : UUID of the volume or snapshot (required) - - : volume or snapshot; default: volume (optional) - - : volume size if create from snapshot (optional) - - : true or false; default: false (optional) - - (optional extension) - -.. option:: --nic - - Create a NIC on the server. Specify option multiple times to create - multiple NICs. Either net-id or port-id must be provided, but not both. - net-id: attach NIC to network with this UUID, - port-id: attach NIC to port with this UUID, - v4-fixed-ip: IPv4 fixed address for NIC (optional), - v6-fixed-ip: IPv6 fixed address for NIC (optional). - none: (v2.37+) no network is attached. - auto: (v2.37+) the compute service will automatically allocate a network. - Specifying a --nic of auto or none cannot be used with any other - --nic value. - -.. option:: --network - - Create a NIC on the server and connect it to network. - Specify option multiple times to create multiple NICs. - For more options on NICs see --nic parameter. - network: attach NIC to this network - -.. option:: --port - - Create a NIC on the server and connect it to port. - Specify option multiple times to create multiple NICs. - For more options on NICs see --nic parameter. - port: attach NIC to this port - -.. option:: --hint - - Hints for the scheduler (optional extension) - -.. option:: --config-drive |True - - Use specified volume as the config drive, or 'True' to use an ephemeral drive - -.. option:: --min - - Minimum number of servers to launch (default=1) - -.. option:: --max - - Maximum number of servers to launch (default=1) - -.. option:: --wait - - Wait for build to complete - -.. describe:: - - New server name - -.. - -The parameters ``--network `` and ``--port `` are actually -wrappers to ``--nic net-id=`` and ``--nic port-id=``. ``--nic`` -also provides additional options to specify an IP address, automatic network -assignment and NICs which are not assigned to any port. This functionality -is not part of ``--network`` and ``--port``, which aim to provide a simple -syntax for the standard use cases of connecting a new server to a given -network or port. - -server delete -------------- - -Delete server(s) - -.. program:: server delete -.. code:: bash - - openstack server delete - [ ...] [--wait] - -.. option:: --wait - - Wait for delete to complete - -.. describe:: - - Server(s) to delete (name or ID) - -server dump create ------------------- -Create a dump file in server(s) - -Trigger crash dump in server(s) with features like kdump in Linux. It will -create a dump file in the server(s) dumping the server(s)' memory, and also -crash the server(s). OSC sees the dump file (server dump) as a kind of -resource. - -.. program:: server dump create -.. code:: bash - - openstack server dump create - [ ...] - -.. describe:: - - Server(s) to create dump file (name or ID) - -server list ------------ - -List servers - -.. code:: bash - - openstack server list - [--reservation-id ] - [--ip ] - [--ip6 ] - [--name ] - [--instance-name ] - [--status ] - [--flavor ] - [--image ] - [--host ] - [--all-projects] - [--project [--project-domain ]] - [--long] - [--no-name-lookup | -n] - [--marker ] - [--limit ] - [--deleted] - [--changes-since ] - -.. option:: --reservation-id - - Only return instances that match the reservation - -.. option:: --ip - - Regular expression to match IP addresses - -.. option:: --ip6 - - Regular expression to match IPv6 addresses - -.. option:: --name - - Regular expression to match names - -.. option:: --instance-name - - Regular expression to match instance name (admin only) - -.. option:: --status - - Search by server status - -.. option:: --flavor - - Search by flavor (name or ID) - -.. option:: --image - - Search by image (name or ID) - -.. option:: --host - - Search by hostname - -.. option:: --all-projects - - Include all projects (admin only) - -.. option:: --project - - Search by project (admin only) (name or ID) - -.. option:: --project-domain - - Domain the project belongs to (name or ID). - This can be used in case collisions between project names exist. - -.. option:: --user - - Search by user (admin only) (name or ID) - -.. option:: --user-domain - - Domain the user belongs to (name or ID). - This can be used in case collisions between user names exist. - -.. option:: --long - - List additional fields in output - -.. option:: --no-name-lookup - - Skips image and flavor names lookup - - ``-n`` may be used as an alias for this option. - -.. option:: --marker - - The last server of the previous page. Display list of servers - after marker. Display all servers if not specified. (name or ID) - -.. option:: --limit - - Maximum number of servers to display. If limit equals -1, all servers will - be displayed. If limit is greater than 'osapi_max_limit' option of Nova - API, 'osapi_max_limit' will be used instead. - -.. option:: --deleted - - Only display deleted servers (Admin only). - -.. option:: --changes-since - - List only servers changed after a certain point of time. The provided time - should be an ISO 8061 formatted time. ex 2016-03-04T06:27:59Z. - -server lock ------------ - -Lock server(s). A non-admin user will not be able to execute actions - -.. program:: server lock -.. code:: bash - - openstack server lock - [ ...] - -.. describe:: - - Server(s) to lock (name or ID) - -server migrate --------------- - -Migrate server to different host - -.. program:: server migrate -.. code:: bash - - openstack server migrate - --live - [--shared-migration | --block-migration] - [--disk-overcommit | --no-disk-overcommit] - [--wait] - - -.. option:: --live - - Target hostname - -.. option:: --shared-migration - - Perform a shared live migration (default) - -.. option:: --block-migration - - Perform a block live migration - -.. option:: --disk-overcommit - - Allow disk over-commit on the destination host - -.. option:: --no-disk-overcommit - - Do not over-commit disk on the destination host (default) - -.. option:: --wait - - Wait for migrate to complete - -.. describe:: - - Server to migrate (name or ID) - -server pause ------------- - -Pause server(s) - -.. program:: server pause -.. code:: bash - - openstack server pause - [ ...] - -.. describe:: - - Server(s) to pause (name or ID) - -server reboot -------------- - -Perform a hard or soft server reboot - -.. program:: server reboot -.. code:: bash - - openstack server reboot - [--hard | --soft] - [--wait] - - -.. option:: --hard - - Perform a hard reboot - -.. option:: --soft - - Perform a soft reboot - -.. option:: --wait - - Wait for reboot to complete - -.. describe:: - - Server (name or ID) - -server rebuild --------------- - -Rebuild server - -.. program:: server rebuild -.. code:: bash - - openstack server rebuild - [--image ] - [--password ] - [--wait] - - -.. option:: --image - - Recreate server from the specified image (name or ID). Defaults to the - currently used one. - -.. option:: --password - - Set the password on the rebuilt instance - -.. option:: --wait - - Wait for rebuild to complete - -.. describe:: - - Server (name or ID) - -server remove fixed ip ----------------------- - -Remove fixed IP address from server - -.. program:: server remove fixed ip -.. code:: bash - - openstack server remove fixed ip - - - -.. describe:: - - Server to remove the fixed IP address from (name or ID) - -.. describe:: - - Fixed IP address to remove from the server (IP only) - -server remove floating ip -------------------------- - -Remove floating IP address from server - -.. program:: server remove floating ip -.. code:: bash - - openstack server remove floating ip - - - -.. describe:: - - Server to remove the floating IP address from (name or ID) - -.. describe:: - - Floating IP address to remove from server (IP only) - -server remove port ------------------- - -Remove port from server - -.. program:: server remove port -.. code:: bash - - openstack server remove port - - - -.. describe:: - - Server to remove the port from (name or ID) - -.. describe:: - - Port to remove from the server (name or ID) - -server remove security group ----------------------------- - -Remove security group from server - -.. program:: server remove security group -.. code:: bash - - openstack server remove security group - - - -.. describe:: - - Name or ID of server to use - -.. describe:: - - Name or ID of security group to remove from server - -server remove volume --------------------- - -Remove volume from server - -.. program:: server remove volume -.. code:: bash - - openstack server remove volume - - - -.. describe:: - - Server (name or ID) - -.. describe:: - - Volume to remove (name or ID) - -server rescue -------------- - -Put server in rescue mode - -.. program:: server rescue -.. code:: bash - - openstack server rescue - - -.. describe:: - - Server (name or ID) - -server resize -------------- - -Scale server to a new flavor - -.. program:: server resize -.. code:: bash - - openstack server resize - --flavor - [--wait] - - - openstack server resize - --confirm | --revert - - -.. option:: --flavor - - Resize server to specified flavor - -.. option:: --confirm - - Confirm server resize is complete - -.. option:: --revert - - Restore server state before resize - -.. option:: --wait - - Wait for resize to complete - -.. describe:: - - Server (name or ID) - -A resize operation is implemented by creating a new server and copying -the contents of the original disk into a new one. It is also a two-step -process for the user: the first is to perform the resize, the second is -to either confirm (verify) success and release the old server, or to declare -a revert to release the new server and restart the old one. - -server restore --------------- - -Restore server(s) from soft-deleted state - -.. program:: server restore -.. code:: bash - - openstack server restore - [ ...] - -.. describe:: - - Server(s) to restore (name or ID) - -server resume -------------- - -Resume server(s) - -.. program:: server resume -.. code:: bash - - openstack server resume - [ ...] - -.. describe:: - - Server(s) to resume (name or ID) - -server set ----------- - -Set server properties - -.. program:: server set -.. code:: bash - - openstack server set - --name - --property - [--property ] ... - --root-password - --state - - -.. option:: --name - - New server name - -.. option:: --root-password - - Set new root password (interactive only) - -.. option:: --property - - Property to add/change for this server - (repeat option to set multiple properties) - -.. option:: --state - - New server state (valid value: active, error) - -.. describe:: - - Server (name or ID) - -server shelve -------------- - -Shelve server(s) - -.. program:: server shelve -.. code:: bash - - openstack server shelve - [ ...] - -.. describe:: - - Server(s) to shelve (name or ID) - -server show ------------ - -Show server details - -.. program:: server show -.. code:: bash - - openstack server show - [--diagnostics] - - -.. option:: --diagnostics - - Display server diagnostics information - -.. describe:: - - Server (name or ID) - -server ssh ----------- - -SSH to server - -.. program:: server ssh -.. code:: bash - - openstack server ssh - [--login ] - [--port ] - [--identity ] - [--option ] - [--public | --private | --address-type ] - - -.. option:: --login - - Login name (ssh -l option) - -.. option:: --port - - Destination port (ssh -p option) - -.. option:: --identity - - Private key file (ssh -i option) - -.. option:: --option - - Options in ssh_config(5) format (ssh -o option) - -.. option:: --public - - Use public IP address - -.. option:: --private - - Use private IP address - -.. option:: --address-type - - Use other IP address (public, private, etc) - -.. describe:: - - Server (name or ID) - -server start ------------- - -Start server(s) - -.. program:: server start -.. code:: bash - - openstack server start - [ ...] - -.. describe:: - - Server(s) to start (name or ID) - -server stop ------------ - -Stop server(s) - -.. program:: server stop -.. code:: bash - - openstack server stop - [ ...] - -.. describe:: - - Server(s) to stop (name or ID) - -server suspend --------------- - -Suspend server(s) - -.. program:: server suspend -.. code:: bash - - openstack server suspend - [ ...] - -.. describe:: - - Server(s) to suspend (name or ID) - -server unlock -------------- - -Unlock server(s) - -.. program:: server unlock -.. code:: bash - - openstack server unlock - [ ...] - -.. describe:: - - Server(s) to unlock (name or ID) - -server unpause --------------- - -Unpause server(s) - -.. program:: server unpause -.. code:: bash - - openstack server unpause - [ ...] - -.. describe:: - - Server(s) to unpause (name or ID) - -server unrescue ---------------- - -Restore server from rescue mode - -.. program:: server unrescue -.. code:: bash - - openstack server unrescue - - -.. describe:: - - Server (name or ID) - -server unset ------------- - -Unset server properties - -.. program:: server unset -.. code:: bash - - openstack server unset - --property - [--property ] ... - - -.. option:: --property - - Property key to remove from server - (repeat option to remove multiple values) - -.. describe:: - - Server (name or ID) - -server unshelve ---------------- - -Unshelve server(s) - -.. program:: server unshelve -.. code:: bash - - openstack server unshelve - [ ...] - -.. describe:: - - Server(s) to unshelve (name or ID) +.. autoprogram-cliff:: openstack.compute.v2 + :command: server unshelve diff --git a/doc/source/conf.py b/doc/source/conf.py index fc7520fd0d..bd4fa73031 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -34,6 +34,7 @@ 'sphinx.ext.todo', 'openstackdocstheme', 'stevedore.sphinxext', + 'cliff.sphinxext', ] # openstackdocstheme options @@ -269,3 +270,12 @@ # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' + + +# -- Options for cliff.sphinxext plugin --------------------------------------- + +autoprogram_cliff_application = 'openstack' + +autoprogram_cliff_ignored = [ + '--help', '--format', '--column', '--max-width', '--fit-width', + '--print-empty', '--prefix', '--noindent'] diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index a991ed4531..d4d8c923a1 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -394,10 +394,13 @@ def get_parser(self, prog_name): disk_group.add_argument( '--volume', metavar='', - help=_( - 'Create server using this volume as the boot disk ' - '(name or ID)' - ), + help=_('Create server using this volume as the boot disk (name ' + 'or ID).\n' + 'This option automatically creates a block device mapping ' + 'with a boot index of 0. On many hypervisors (libvirt/kvm ' + 'for example) this will be device vda. Do not create a ' + 'duplicate mapping using --block-device-mapping for this ' + 'volume.'), ) parser.add_argument( '--flavor', @@ -489,8 +492,11 @@ def get_parser(self, prog_name): type=_prefix_checked_value('net-id='), help=_("Create a NIC on the server and connect it to network. " "Specify option multiple times to create multiple NICs. " - "For more options on NICs see --nic parameter. " - "network: attach NIC to this network "), + "This is a wrapper for the '--nic net-id=' " + "parameter that provides simple syntax for the standard " + "use case of connecting a new server to a given network. " + "For more advanced use cases, refer to the '--nic' " + "parameter."), ) parser.add_argument( '--port', @@ -500,8 +506,10 @@ def get_parser(self, prog_name): type=_prefix_checked_value('port-id='), help=_("Create a NIC on the server and connect it to port. " "Specify option multiple times to create multiple NICs. " - "For more options on NICs see --nic parameter. " - "port: attach NIC this port "), + "This is a wrapper for the '--nic port-id=' " + "parameter that provides simple syntax for the standard " + "use case of connecting a new server to a given port. For " + "more advanced use cases, refer to the '--nic' parameter."), ) parser.add_argument( '--hint', @@ -1587,7 +1595,13 @@ def take_action(self, parsed_args): class ResizeServer(command.Command): - _description = _("Scale server to a new flavor") + _description = _("""Scale server to a new flavor. + +A resize operation is implemented by creating a new server and copying the +contents of the original disk into a new one. It is also a two-step process for +the user: the first is to perform the resize, the second is to either confirm +(verify) success and release the old server, or to declare a revert to release +the new server and restart the old one.""") def get_parser(self, prog_name): parser = super(ResizeServer, self).get_parser(prog_name) From 77ff011ced18260242224a7317aba92d53ff1455 Mon Sep 17 00:00:00 2001 From: Kristi Nikolla Date: Fri, 16 Jun 2017 15:04:40 -0400 Subject: [PATCH 1741/3095] Add domain parameter to Identity Provider Identity providers are now associated with domains. This change allows a user to specify a domain by ID or by name when creating an identity provider. [0] This also adds the column for Domain ID in listing. Updating a domain for an identity provider is not supported, so that isn't changed. [0]. Id18b8b2fe853b97631bc990df8188ed64a6e1275 Closes-Bug: 1698390 Change-Id: Icc408e2fe88f257d5863bd3df716a777d52befcc --- .../cli/command-objects/identity-provider.rst | 6 ++ .../identity/v3/identity_provider.py | 17 +++- .../tests/unit/identity/v3/fakes.py | 3 +- .../identity/v3/test_identity_provider.py | 81 ++++++++++++++++++- .../notes/bug-1698390-0df8f0ec4fe354de.yaml | 7 ++ 5 files changed, 110 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/bug-1698390-0df8f0ec4fe354de.yaml diff --git a/doc/source/cli/command-objects/identity-provider.rst b/doc/source/cli/command-objects/identity-provider.rst index 0c2d02bd27..d96b814aea 100644 --- a/doc/source/cli/command-objects/identity-provider.rst +++ b/doc/source/cli/command-objects/identity-provider.rst @@ -17,6 +17,7 @@ Create new identity provider openstack identity provider create [--remote-id [...] | --remote-id-file ] [--description ] + [--domain ] [--enable | --disable] @@ -34,6 +35,11 @@ Create new identity provider New identity provider description +.. option:: --domain + + Name or ID of the domain to associate with the identity provider. If not + specified, one will be created automatically + .. option:: --enable Enable the identity provider (default) diff --git a/openstackclient/identity/v3/identity_provider.py b/openstackclient/identity/v3/identity_provider.py index e8b3a2f459..d8951d31c2 100644 --- a/openstackclient/identity/v3/identity_provider.py +++ b/openstackclient/identity/v3/identity_provider.py @@ -21,6 +21,7 @@ import six from openstackclient.i18n import _ +from openstackclient.identity import common LOG = logging.getLogger(__name__) @@ -55,6 +56,13 @@ def get_parser(self, prog_name): metavar='', help=_('New identity provider description'), ) + parser.add_argument( + '--domain', + metavar='', + help=_('Domain to associate with the identity provider. If not ' + 'specified, a domain will be created automatically. ' + '(Name or ID)'), + ) enable_identity_provider = parser.add_mutually_exclusive_group() enable_identity_provider.add_argument( '--enable', @@ -81,10 +89,17 @@ def take_action(self, parsed_args): else: remote_ids = (parsed_args.remote_id if parsed_args.remote_id else None) + + domain_id = None + if parsed_args.domain: + domain_id = common.find_domain(identity_client, + parsed_args.domain).id + idp = identity_client.federation.identity_providers.create( id=parsed_args.identity_provider_id, remote_ids=remote_ids, description=parsed_args.description, + domain_id=domain_id, enabled=parsed_args.enabled) idp._info.pop('links', None) @@ -129,7 +144,7 @@ class ListIdentityProvider(command.Lister): _description = _("List identity providers") def take_action(self, parsed_args): - columns = ('ID', 'Enabled', 'Description') + columns = ('ID', 'Enabled', 'Domain ID', 'Description') identity_client = self.app.client_manager.identity data = identity_client.federation.identity_providers.list() return (columns, diff --git a/openstackclient/tests/unit/identity/v3/fakes.py b/openstackclient/tests/unit/identity/v3/fakes.py index 291f977d4e..c7d298859f 100644 --- a/openstackclient/tests/unit/identity/v3/fakes.py +++ b/openstackclient/tests/unit/identity/v3/fakes.py @@ -284,7 +284,8 @@ 'id': idp_id, 'remote_ids': idp_remote_ids, 'enabled': True, - 'description': idp_description + 'description': idp_description, + 'domain_id': domain_id, } protocol_id = 'protocol' diff --git a/openstackclient/tests/unit/identity/v3/test_identity_provider.py b/openstackclient/tests/unit/identity/v3/test_identity_provider.py index cb672a92a6..def6e0ce54 100644 --- a/openstackclient/tests/unit/identity/v3/test_identity_provider.py +++ b/openstackclient/tests/unit/identity/v3/test_identity_provider.py @@ -25,21 +25,33 @@ class TestIdentityProvider(identity_fakes.TestFederatedIdentity): def setUp(self): super(TestIdentityProvider, self).setUp() + # Identity Provider mocks federation_lib = self.app.client_manager.identity.federation self.identity_providers_mock = federation_lib.identity_providers self.identity_providers_mock.reset_mock() + # Domain mocks + self.domains_mock = self.app.client_manager.identity.domains + self.domains_mock.reset_mock() + self.domain = identity_fakes.FakeDomain.create_one_domain( + identity_fakes.DOMAIN + ) + self.domains_mock.list.return_value = [self.domain] + self.domains_mock.get.return_value = self.domain + class TestIdentityProviderCreate(TestIdentityProvider): columns = ( 'description', + 'domain_id', 'enabled', 'id', 'remote_ids', ) datalist = ( identity_fakes.idp_description, + identity_fakes.domain_id, True, identity_fakes.idp_id, identity_fakes.formatted_idp_remote_ids, @@ -68,6 +80,7 @@ def test_create_identity_provider_no_options(self): 'remote_ids': None, 'enabled': True, 'description': None, + 'domain_id': None, } self.identity_providers_mock.create.assert_called_with( @@ -94,6 +107,7 @@ def test_create_identity_provider_description(self): kwargs = { 'remote_ids': None, 'description': identity_fakes.idp_description, + 'domain_id': None, 'enabled': True, } @@ -121,6 +135,7 @@ def test_create_identity_provider_remote_id(self): kwargs = { 'remote_ids': identity_fakes.idp_remote_ids[:1], 'description': None, + 'domain_id': None, 'enabled': True, } @@ -149,6 +164,7 @@ def test_create_identity_provider_remote_ids_multiple(self): kwargs = { 'remote_ids': identity_fakes.idp_remote_ids, 'description': None, + 'domain_id': None, 'enabled': True, } @@ -181,6 +197,7 @@ def test_create_identity_provider_remote_ids_file(self): kwargs = { 'remote_ids': identity_fakes.idp_remote_ids, 'description': None, + 'domain_id': None, 'enabled': True, } @@ -217,6 +234,7 @@ def test_create_identity_provider_disabled(self): 'remote_ids': None, 'enabled': False, 'description': None, + 'domain_id': None, } self.identity_providers_mock.create.assert_called_with( @@ -227,12 +245,69 @@ def test_create_identity_provider_disabled(self): self.assertEqual(self.columns, columns) datalist = ( None, + identity_fakes.domain_id, False, identity_fakes.idp_id, identity_fakes.formatted_idp_remote_ids ) self.assertEqual(datalist, data) + def test_create_identity_provider_domain_name(self): + arglist = [ + '--domain', identity_fakes.domain_name, + identity_fakes.idp_id, + ] + verifylist = [ + ('identity_provider_id', identity_fakes.idp_id), + ('domain', identity_fakes.domain_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'remote_ids': None, + 'description': None, + 'domain_id': identity_fakes.domain_id, + 'enabled': True, + } + + self.identity_providers_mock.create.assert_called_with( + id=identity_fakes.idp_id, + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) + + def test_create_identity_provider_domain_id(self): + arglist = [ + '--domain', identity_fakes.domain_id, + identity_fakes.idp_id, + ] + verifylist = [ + ('identity_provider_id', identity_fakes.idp_id), + ('domain', identity_fakes.domain_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'remote_ids': None, + 'description': None, + 'domain_id': identity_fakes.domain_id, + 'enabled': True, + } + + self.identity_providers_mock.create.assert_called_with( + id=identity_fakes.idp_id, + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) + class TestIdentityProviderDelete(TestIdentityProvider): @@ -299,11 +374,12 @@ def test_identity_provider_list_no_options(self): self.identity_providers_mock.list.assert_called_with() - collist = ('ID', 'Enabled', 'Description') + collist = ('ID', 'Enabled', 'Domain ID', 'Description') self.assertEqual(collist, columns) datalist = (( identity_fakes.idp_id, True, + identity_fakes.domain_id, identity_fakes.idp_description, ), ) self.assertEqual(datalist, tuple(data)) @@ -582,10 +658,11 @@ def test_identity_provider_show(self): id='test_idp' ) - collist = ('description', 'enabled', 'id', 'remote_ids') + collist = ('description', 'domain_id', 'enabled', 'id', 'remote_ids') self.assertEqual(collist, columns) datalist = ( identity_fakes.idp_description, + identity_fakes.domain_id, True, identity_fakes.idp_id, identity_fakes.formatted_idp_remote_ids diff --git a/releasenotes/notes/bug-1698390-0df8f0ec4fe354de.yaml b/releasenotes/notes/bug-1698390-0df8f0ec4fe354de.yaml new file mode 100644 index 0000000000..073ba9f8df --- /dev/null +++ b/releasenotes/notes/bug-1698390-0df8f0ec4fe354de.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Added the ``--domain`` option to the ``identity provider create`` command to + associate an existing domain with an identity provider on its creation. + + [Bug `1698390 `_] From 199c13c8368bf274b3f5c7d4174bac7971b8e0b7 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sat, 22 Jul 2017 16:39:06 +0000 Subject: [PATCH 1742/3095] Updated from global requirements Change-Id: I09acea781d9df7c88a90c3915724b1245f2e219b --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e94ecde37e..54ae0a03b3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ six>=1.9.0 # MIT Babel!=2.4.0,>=2.3.4 # BSD cliff>=2.8.0 # Apache-2.0 -keystoneauth1>=2.21.0 # Apache-2.0 +keystoneauth1!=3.0.0,>=2.21.0 # Apache-2.0 openstacksdk>=0.9.17 # Apache-2.0 osc-lib>=1.7.0 # Apache-2.0 oslo.i18n!=3.15.2,>=2.1.0 # Apache-2.0 From bb59353ee11d483d3a15483f70c0e6baf4852566 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sun, 23 Jul 2017 13:52:35 +0000 Subject: [PATCH 1743/3095] Updated from global requirements Change-Id: Ib6c0fbfc96d79293726ce941ad4becf31ee2ee79 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 54ae0a03b3..dd9cfd348b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ six>=1.9.0 # MIT Babel!=2.4.0,>=2.3.4 # BSD cliff>=2.8.0 # Apache-2.0 -keystoneauth1!=3.0.0,>=2.21.0 # Apache-2.0 +keystoneauth1>=3.0.1 # Apache-2.0 openstacksdk>=0.9.17 # Apache-2.0 osc-lib>=1.7.0 # Apache-2.0 oslo.i18n!=3.15.2,>=2.1.0 # Apache-2.0 From 1f2295cf6531b589de4f6d78c5d10f1c31537bb7 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 21 Jul 2017 15:07:33 -0500 Subject: [PATCH 1744/3095] Release note cleanup for 3.12.0 Change-Id: I1fab42256e7b01d2efc23af686554e0325a48af6 --- doc/source/cli/backwards-incompatible.rst | 4 ++-- ...-to-network-qos-bw-limit-rule-a3c5b6892074d5ae.yaml | 8 -------- .../bp-network-l3-adv-commands-cc1df715a184f1b2.yaml | 10 ++++++---- releasenotes/notes/bug-1584596-5b3109487b451bec.yaml | 2 +- releasenotes/notes/bug-1614121-a3c5b6892074d5ae.yaml | 7 +++++++ releasenotes/notes/bug-1658189-d2b390ad74c96c79.yaml | 4 ++-- releasenotes/notes/bug-1667266-6497727abc2af9a5.yaml | 9 ++++----- releasenotes/notes/bug-1687814-743ad8418923d5e3.yaml | 6 +++--- releasenotes/notes/bug-1696111-e2cf9233fa872eb7.yaml | 3 +-- releasenotes/notes/bug-1704097-8ff1ce1444b81b04.yaml | 9 ++++----- releasenotes/notes/object-stdout-db76cc500948b0e8.yaml | 2 +- 11 files changed, 31 insertions(+), 33 deletions(-) delete mode 100644 releasenotes/notes/add-direction-to-network-qos-bw-limit-rule-a3c5b6892074d5ae.yaml create mode 100644 releasenotes/notes/bug-1614121-a3c5b6892074d5ae.yaml diff --git a/doc/source/cli/backwards-incompatible.rst b/doc/source/cli/backwards-incompatible.rst index 571d791f17..fcb68684bf 100644 --- a/doc/source/cli/backwards-incompatible.rst +++ b/doc/source/cli/backwards-incompatible.rst @@ -27,8 +27,8 @@ Backwards Incompatible Changes .. * Remove in: <5.0> .. * Commit: -Release 3.12.0 --------------- +Release 3.12 +------------ 1. Replace ``Display Name`` by ``Name`` in volume list. diff --git a/releasenotes/notes/add-direction-to-network-qos-bw-limit-rule-a3c5b6892074d5ae.yaml b/releasenotes/notes/add-direction-to-network-qos-bw-limit-rule-a3c5b6892074d5ae.yaml deleted file mode 100644 index 43a8e56dce..0000000000 --- a/releasenotes/notes/add-direction-to-network-qos-bw-limit-rule-a3c5b6892074d5ae.yaml +++ /dev/null @@ -1,8 +0,0 @@ ---- -features: - - | - Added directionality to Network QoS rule type ``bandwidth-limit`` in - ``network qos rule create`` and ``network qos rule set`` commands. - This makes the options ``--egress`` and ``--ingress`` valid for the ``bandwidth-limit`` - rule type. - [Bug `1614121 `_] diff --git a/releasenotes/notes/bp-network-l3-adv-commands-cc1df715a184f1b2.yaml b/releasenotes/notes/bp-network-l3-adv-commands-cc1df715a184f1b2.yaml index f978f4d650..c05a06c014 100644 --- a/releasenotes/notes/bp-network-l3-adv-commands-cc1df715a184f1b2.yaml +++ b/releasenotes/notes/bp-network-l3-adv-commands-cc1df715a184f1b2.yaml @@ -1,8 +1,10 @@ --- features: - | - Add network l3-agent related commands ``network agent add router``, - ``network agent remove router`` for adding/removing routers to l3 agents. - Add ``network agent list --router``, and ``router list --agent`` for - listing agents on routers and routers on specific agents. + Add ``network agent add router`` and ``network agent remove router`` + commands for adding/removing routers to network l3 agents. + [Blueprint :oscbp:`network-l3-commands`] + - | + Add ``--router`` option to ``network agent list`` to filter by router, + and ``--agent`` option to ``router list`` command to filter by agent. [Blueprint :oscbp:`network-l3-commands`] diff --git a/releasenotes/notes/bug-1584596-5b3109487b451bec.yaml b/releasenotes/notes/bug-1584596-5b3109487b451bec.yaml index 9d45713d02..b5b58bd2ce 100644 --- a/releasenotes/notes/bug-1584596-5b3109487b451bec.yaml +++ b/releasenotes/notes/bug-1584596-5b3109487b451bec.yaml @@ -1,5 +1,5 @@ --- -fixes: +features: - | Add command ``openstack project purge`` to clean a project's resources. [Bug `1584596 `_] diff --git a/releasenotes/notes/bug-1614121-a3c5b6892074d5ae.yaml b/releasenotes/notes/bug-1614121-a3c5b6892074d5ae.yaml new file mode 100644 index 0000000000..b81520f4e1 --- /dev/null +++ b/releasenotes/notes/bug-1614121-a3c5b6892074d5ae.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Added ``--egress`` and ``--ingress`` options to + ``network qos rule create`` and ``network qos rule set`` commands. + This adds directionality to Network QoS ``bandwidth-limit`` rule type. + [Bug `1614121 `_] diff --git a/releasenotes/notes/bug-1658189-d2b390ad74c96c79.yaml b/releasenotes/notes/bug-1658189-d2b390ad74c96c79.yaml index 211c4c31ae..91df4bb86e 100644 --- a/releasenotes/notes/bug-1658189-d2b390ad74c96c79.yaml +++ b/releasenotes/notes/bug-1658189-d2b390ad74c96c79.yaml @@ -1,6 +1,6 @@ --- fixes: - | - Make ``role assignment list`` callable without administrator permissions - if restricted to own project with ``--project`` parameter. + Make the ``role assignment list`` command callable without administrator + permissions if restricted to the user's own project with the ``--project`` option. [Bug `1658189 `_] diff --git a/releasenotes/notes/bug-1667266-6497727abc2af9a5.yaml b/releasenotes/notes/bug-1667266-6497727abc2af9a5.yaml index bedfa67f93..ee21a6c1e4 100644 --- a/releasenotes/notes/bug-1667266-6497727abc2af9a5.yaml +++ b/releasenotes/notes/bug-1667266-6497727abc2af9a5.yaml @@ -1,9 +1,8 @@ --- fixes: - | - Make ``block-device-mapping`` option of ``server create`` command more - stable and clear. Fix ValueError when input block device mapping option in - wrong format. Support to create block device from snapshot. Add details in - help message about block-device-mapping option format and regular value of - each item. + Clarify the ``--block-device-mapping`` option of the ``server create`` + command: fix ValueError when the ``--block-device-mapping`` option's + argument is in the wrong format; support creating a block device from + a snapshot; add details to the help output about the option format. [Bug `1667266 `_] diff --git a/releasenotes/notes/bug-1687814-743ad8418923d5e3.yaml b/releasenotes/notes/bug-1687814-743ad8418923d5e3.yaml index bdd6e3f0a0..81c801e6ce 100644 --- a/releasenotes/notes/bug-1687814-743ad8418923d5e3.yaml +++ b/releasenotes/notes/bug-1687814-743ad8418923d5e3.yaml @@ -1,7 +1,7 @@ --- fixes: - | - Allow security groups in ``server create`` command to be specified by name or ID with - the ``--security-group`` option. This also checks that the security group exist before - creating the server. + Allow the ``--security-group`` option from the ``server create`` command + to be specified by name or ID. This also checks that the security group exist + before creating the server. [Bug `1687814 `_] diff --git a/releasenotes/notes/bug-1696111-e2cf9233fa872eb7.yaml b/releasenotes/notes/bug-1696111-e2cf9233fa872eb7.yaml index 50154a8854..39e24c332b 100644 --- a/releasenotes/notes/bug-1696111-e2cf9233fa872eb7.yaml +++ b/releasenotes/notes/bug-1696111-e2cf9233fa872eb7.yaml @@ -3,5 +3,4 @@ fixes: - | Fixed an issue where a trust could not be created if multiple roles had the same name. A role's ID is now sent to the identity service instead. - - [Bug '1696111 '_] + [Bug `1696111 `_] diff --git a/releasenotes/notes/bug-1704097-8ff1ce1444b81b04.yaml b/releasenotes/notes/bug-1704097-8ff1ce1444b81b04.yaml index c20d0c9991..ce82d8bd1c 100644 --- a/releasenotes/notes/bug-1704097-8ff1ce1444b81b04.yaml +++ b/releasenotes/notes/bug-1704097-8ff1ce1444b81b04.yaml @@ -1,9 +1,8 @@ --- fixes: - | - Fix an issue when run commands `project show`, `user show` and `user set` - with `--domain` specific, the domain filter won't work if the login user's - project name or user name is same with the resource name user hoping to - get. - + Fix an issue with the ``--domain`` option when used with the ``project show``, + ``user show`` and ``user set`` commands. The domain filter did not work when + the login user's project name or user name is the same as the requested resource + name in the specified domain. [Bug `1704097 `_] diff --git a/releasenotes/notes/object-stdout-db76cc500948b0e8.yaml b/releasenotes/notes/object-stdout-db76cc500948b0e8.yaml index 29b21131bc..8072bc382e 100644 --- a/releasenotes/notes/object-stdout-db76cc500948b0e8.yaml +++ b/releasenotes/notes/object-stdout-db76cc500948b0e8.yaml @@ -2,4 +2,4 @@ features: - | Add support for streaming Swift objects to stdout when using the ``object - save`` command, by specifying ``--filename -``. + save`` command by specifying ``-`` as a file name: ``--filename -``. From 45d0809b39d80bc0e9f3aeaf6461b0ce89e6ada1 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sun, 23 Jul 2017 19:08:42 +0000 Subject: [PATCH 1745/3095] Updated from global requirements Change-Id: I68530243bdfbcf315b0be5775c08cc26faed4770 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index dd9cfd348b..24e467231c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,4 +14,4 @@ oslo.utils>=3.20.0 # Apache-2.0 python-glanceclient>=2.7.0 # Apache-2.0 python-keystoneclient>=3.8.0 # Apache-2.0 python-novaclient>=9.0.0 # Apache-2.0 -python-cinderclient>=2.1.0 # Apache-2.0 +python-cinderclient>=3.0.0 # Apache-2.0 From 57e5840710c3b2b74d31bfd6a0da739e0fc747ed Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Sat, 29 Apr 2017 00:32:32 +0000 Subject: [PATCH 1746/3095] Network tag support Neutron tag mechanism now supports network, subnet, port, subnetpool and router. Tag support for more resources is planned. This commit introduces a common mixin class to implement tag operation and individual resource consumes it. To support tag remove, network unset command is added. Implements blueprint neutron-client-tag Change-Id: Iad59d052f46896d27d73c22d6d4bb3df889f2352 --- doc/source/cli/command-objects/network.rst | 79 ++++++++ doc/source/cli/command-objects/port.rst | 47 +++++ doc/source/cli/command-objects/router.rst | 47 +++++ .../cli/command-objects/subnet-pool.rst | 47 +++++ doc/source/cli/command-objects/subnet.rst | 47 +++++ openstackclient/network/v2/_tag.py | 134 ++++++++++++ openstackclient/network/v2/network.py | 43 +++- openstackclient/network/v2/port.py | 24 ++- openstackclient/network/v2/router.py | 19 +- openstackclient/network/v2/subnet.py | 21 +- openstackclient/network/v2/subnet_pool.py | 21 +- .../tests/functional/network/v2/common.py | 81 ++++++++ .../functional/network/v2/test_network.py | 4 +- .../tests/functional/network/v2/test_port.py | 13 +- .../functional/network/v2/test_router.py | 4 +- .../functional/network/v2/test_subnet.py | 10 +- .../functional/network/v2/test_subnet_pool.py | 8 +- .../tests/unit/network/v2/_test_tag.py | 190 ++++++++++++++++++ .../tests/unit/network/v2/fakes.py | 5 + .../tests/unit/network/v2/test_network.py | 83 +++++++- .../tests/unit/network/v2/test_port.py | 115 +++++++---- .../tests/unit/network/v2/test_router.py | 62 +++++- .../tests/unit/network/v2/test_subnet.py | 80 ++++++-- .../tests/unit/network/v2/test_subnet_pool.py | 70 ++++++- openstackclient/tests/unit/utils.py | 6 + ...p-neutron-client-tag-ff24d13e5c70e052.yaml | 13 ++ setup.cfg | 1 + 27 files changed, 1172 insertions(+), 102 deletions(-) create mode 100644 openstackclient/network/v2/_tag.py create mode 100644 openstackclient/tests/unit/network/v2/_test_tag.py create mode 100644 releasenotes/notes/bp-neutron-client-tag-ff24d13e5c70e052.yaml diff --git a/doc/source/cli/command-objects/network.rst b/doc/source/cli/command-objects/network.rst index ed9fd13d1d..5f20dc3884 100644 --- a/doc/source/cli/command-objects/network.rst +++ b/doc/source/cli/command-objects/network.rst @@ -32,6 +32,7 @@ Create new network [--provider-segment ] [--qos-policy ] [--transparent-vlan | --no-transparent-vlan] + [--tag | --no-tag] .. option:: --project @@ -165,6 +166,18 @@ Create new network *Network version 2 only* +.. option:: --tag + + Tag to be added to the network (repeat option to set multiple tags) + + *Network version 2 only* + +.. option:: --no-tag + + No tags associated with the network + + *Network version 2 only* + .. _network_create-name: .. describe:: @@ -206,6 +219,8 @@ List networks [--provider-physical-network ] [--provider-segment ] [--agent ] + [--tags [,,...]] [--any-tags [,,...]] + [--not-tags [,,...]] [--not-any-tags [,,...]] .. option:: --external @@ -297,6 +312,32 @@ List networks List networks hosted by agent (ID only) + *Network version 2 only* + +.. option:: --tags [,,...] + + List networks which have all given tag(s) + + *Network version 2 only* + +.. option:: --any-tags [,,...] + + List networks which have any given tag(s) + + *Network version 2 only* + +.. option:: --not-tags [,,...] + + Exclude networks which have all given tag(s) + + *Network version 2 only* + +.. option:: --not-any-tags [,,...] + + Exclude networks which have any given tag(s) + + *Network version 2 only* + network set ----------- @@ -318,6 +359,7 @@ Set network properties [--provider-physical-network ] [--provider-segment ] [--qos-policy | --no-qos-policy] + [--tag ] [--no-tag] .. option:: --name @@ -392,6 +434,15 @@ Set network properties Remove the QoS policy attached to this network +.. option:: --tag + + Tag to be added to the network (repeat option to set multiple tags) + +.. option:: --no-tag + + Clear tags associated with the network. Specify both --tag + and --no-tag to overwrite current tags + .. _network_set-network: .. describe:: @@ -412,3 +463,31 @@ Display network details .. describe:: Network to display (name or ID) + +network unset +------------- + +Unset network properties + +*Network version 2 only* + +.. program:: network unset +.. code:: bash + + openstack network unset + [--tag | --all-tag] + + +.. option:: --tag + + Tag to be removed from the network + (repeat option to remove multiple tags) + +.. option:: --all-tag + + Clear all tags associated with the network + +.. _network_unset-network: +.. describe:: + + Network to modify (name or ID) diff --git a/doc/source/cli/command-objects/port.rst b/doc/source/cli/command-objects/port.rst index 37814a9595..c2da09b321 100644 --- a/doc/source/cli/command-objects/port.rst +++ b/doc/source/cli/command-objects/port.rst @@ -33,6 +33,7 @@ Create new port [--qos-policy ] [--project [--project-domain ]] [--enable-port-security | --disable-port-security] + [--tag | --no-tag] .. option:: --network @@ -126,6 +127,14 @@ Create new port Disable port security for this port +.. option:: --tag + + Tag to be added to the port (repeat option to set multiple tags) + +.. option:: --no-tag + + No tags associated with the port + .. _port_create-name: .. describe:: @@ -163,6 +172,8 @@ List ports [--fixed-ip subnet=,ip-address=] [--long] [--project [--project-domain ]] + [--tags [,,...]] [--any-tags [,,...]] + [--not-tags [,,...]] [--not-any-tags [,,...]] .. option:: --device-owner @@ -204,6 +215,22 @@ List ports Domain the project belongs to (name or ID). This can be used in case collisions between project names exist. +.. option:: --tags [,,...] + + List ports which have all given tag(s) + +.. option:: --any-tags [,,...] + + List ports which have any given tag(s) + +.. option:: --not-tags [,,...] + + Exclude ports which have all given tag(s) + +.. option:: --not-any-tags [,,...] + + Exclude ports which have any given tag(s) + port set -------- @@ -233,6 +260,7 @@ Set port properties [--allowed-address ip-address=[,mac-address=]] [--no-allowed-address] [--data-plane-status ] + [--tag ] [--no-tag] .. option:: --description @@ -342,6 +370,15 @@ Set port properties Unset it to None with the 'port unset' command (requires data plane status extension) +.. option:: --tag + + Tag to be added to the port (repeat option to set multiple tags) + +.. option:: --no-tag + + Clear tags associated with the port. Specify both --tag + and --no-tag to overwrite current tags + .. _port_set-port: .. describe:: @@ -378,6 +415,7 @@ Unset port properties [--allowed-address ip-address=[,mac-address=] [...]] [--qos-policy] [--data-plane-status] + [--tag | --all-tag] .. option:: --fixed-ip subnet=,ip-address= @@ -410,6 +448,15 @@ Unset port properties Clear existing information of data plane status +.. option:: --tag + + Tag to be removed from the port + (repeat option to remove multiple tags) + +.. option:: --all-tag + + Clear all tags associated with the port + .. _port_unset-port: .. describe:: diff --git a/doc/source/cli/command-objects/router.rst b/doc/source/cli/command-objects/router.rst index 8bdf81dbf7..9c9364bc75 100644 --- a/doc/source/cli/command-objects/router.rst +++ b/doc/source/cli/command-objects/router.rst @@ -67,6 +67,7 @@ Create new router [--ha | --no-ha] [--description ] [--availability-zone-hint ] + [--tag | --no-tag] .. option:: --project @@ -121,6 +122,14 @@ Create new router (Router Availability Zone extension required, repeat option to set multiple availability zones) +.. option:: --tag + + Tag to be added to the router (repeat option to set multiple tags) + +.. option:: --no-tag + + No tags associated with the router + .. _router_create-name: .. describe:: @@ -156,6 +165,8 @@ List routers [--long] [--project [--project-domain ]] [--agent ] + [--tags [,,...]] [--any-tags [,,...]] + [--not-tags [,,...]] [--not-any-tags [,,...]] .. option:: --agent @@ -186,6 +197,22 @@ List routers Domain the project belongs to (name or ID). This can be used in case collisions between project names exist. +.. option:: --tags [,,...] + + List routers which have all given tag(s) + +.. option:: --any-tags [,,...] + + List routers which have any given tag(s) + +.. option:: --not-tags [,,...] + + Exclude routers which have all given tag(s) + +.. option:: --not-any-tags [,,...] + + Exclude routers which have any given tag(s) + router remove port ------------------ @@ -246,6 +273,7 @@ Set router properties [--route destination=,gateway= | --no-route] [--ha | --no-ha] [--external-gateway [--enable-snat|--disable-snat] [--fixed-ip subnet=,ip-address=]] + [--tag ] [--no-tag] .. option:: --name @@ -311,6 +339,15 @@ Set router properties subnet=,ip-address= (repeat option to set multiple fixed IP addresses) +.. option:: --tag + + Tag to be added to the router (repeat option to set multiple tags) + +.. option:: --no-tag + + Clear tags associated with the router. Specify both --tag + and --no-tag to overwrite current tags + .. _router_set-router: .. describe:: @@ -343,6 +380,7 @@ Unset router properties openstack router unset [--route destination=,gateway=] [--external-gateway] + [--tag | --all-tag] .. option:: --route destination=,gateway= @@ -356,6 +394,15 @@ Unset router properties Remove external gateway information from the router +.. option:: --tag + + Tag to be removed from the router + (repeat option to remove multiple tags) + +.. option:: --all-tag + + Clear all tags associated with the router + .. _router_unset-router: .. describe:: diff --git a/doc/source/cli/command-objects/subnet-pool.rst b/doc/source/cli/command-objects/subnet-pool.rst index 3a60974a97..0cff4d7f56 100644 --- a/doc/source/cli/command-objects/subnet-pool.rst +++ b/doc/source/cli/command-objects/subnet-pool.rst @@ -25,6 +25,7 @@ Create subnet pool [--default | --no-default] [--share | --no-share] [--default-quota ] + [--tag | --no-tag] --pool-prefix [...] @@ -79,6 +80,14 @@ Create subnet pool Set default quota for subnet pool as the number of IP addresses allowed in a subnet +.. option:: --tag + + Tag to be added to the subnet pool (repeat option to set multiple tags) + +.. option:: --no-tag + + No tags associated with the subnet pool + .. option:: --pool-prefix Set subnet pool prefixes (in CIDR notation) @@ -120,6 +129,8 @@ List subnet pools [--project [--project-domain ]] [--name ] [--address-scope ] + [--tags [,,...]] [--any-tags [,,...]] + [--not-tags [,,...]] [--not-any-tags [,,...]] .. option:: --long @@ -158,6 +169,22 @@ List subnet pools List only subnet pools of given address scope in output (name or ID) +.. option:: --tags [,,...] + + List subnet pools which have all given tag(s) + +.. option:: --any-tags [,,...] + + List subnet pools which have any given tag(s) + +.. option:: --not-tags [,,...] + + Exclude subnet pools which have all given tag(s) + +.. option:: --not-any-tags [,,...] + + Exclude subnet pools which have any given tag(s) + subnet pool set --------------- @@ -176,6 +203,7 @@ Set subnet pool properties [--default | --no-default] [--description ] [--default-quota ] + [--tag ] [--no-tag] .. option:: --name @@ -225,6 +253,15 @@ Set subnet pool properties Set default quota for subnet pool as the number of IP addresses allowed in a subnet +.. option:: --tag + + Tag to be added to the subnet pool (repeat option to set multiple tags) + +.. option:: --no-tag + + Clear tags associated with the subnet pool. Specify both --tag + and --no-tag to overwrite current tags + .. _subnet_pool_set-subnet-pool: .. describe:: @@ -256,6 +293,7 @@ Unset subnet pool properties openstack subnet pool unset [--pool-prefix [...]] + [--tag | --all-tag] .. option:: --pool-prefix @@ -263,6 +301,15 @@ Unset subnet pool properties Remove subnet pool prefixes (in CIDR notation). (repeat option to unset multiple prefixes). +.. option:: --tag + + Tag to be removed from the subnet pool + (repeat option to remove multiple tags) + +.. option:: --all-tag + + Clear all tags associated with the subnet pool + .. _subnet_pool_unset-subnet-pool: .. describe:: diff --git a/doc/source/cli/command-objects/subnet.rst b/doc/source/cli/command-objects/subnet.rst index 4e60936120..c228dc207d 100644 --- a/doc/source/cli/command-objects/subnet.rst +++ b/doc/source/cli/command-objects/subnet.rst @@ -31,6 +31,7 @@ Create new subnet [--ipv6-address-mode {dhcpv6-stateful,dhcpv6-stateless,slaac}] [--network-segment ] [--service-type ] + [--tag | --no-tag] --network @@ -125,6 +126,14 @@ Create new subnet Must be a valid device owner value for a network port (repeat option to set multiple service types) +.. option:: --tag + + Tag to be added to the subnet (repeat option to set multiple tags) + +.. option:: --no-tag + + No tags associated with the subnet + .. option:: --network Network this subnet belongs to (name or ID) @@ -167,6 +176,8 @@ List subnets [--gateway ] [--name ] [--subnet-range ] + [--tags [,,...]] [--any-tags [,,...]] + [--not-tags [,,...]] [--not-any-tags [,,...]] .. option:: --long @@ -218,6 +229,22 @@ List subnets List only subnets of given subnet range (in CIDR notation) in output e.g.: ``--subnet-range 10.10.0.0/16`` +.. option:: --tags [,,...] + + List subnets which have all given tag(s) + +.. option:: --any-tags [,,...] + + List subnets which have any given tag(s) + +.. option:: --not-tags [,,...] + + Exclude subnets which have all given tag(s) + +.. option:: --not-any-tags [,,...] + + Exclude subnets which have any given tag(s) + subnet set ---------- @@ -238,6 +265,7 @@ Set subnet properties [--service-type ] [--name ] [--description ] + [--tag ] [--no-tag] .. option:: --allocation-pool start=,end= @@ -305,6 +333,15 @@ Set subnet properties Updated name of the subnet +.. option:: --tag + + Tag to be added to the subnet (repeat option to set multiple tags) + +.. option:: --no-tag + + Clear tags associated with the subnet. Specify both --tag + and --no-tag to overwrite current tags + .. _subnet_set-subnet: .. describe:: @@ -340,6 +377,7 @@ Unset subnet properties [--dns-nameserver [...]] [--host-route destination=,gateway= [...]] [--service-type ] + [--tag | --all-tag] .. option:: --dns-nameserver @@ -368,6 +406,15 @@ Unset subnet properties Must be a valid device owner value for a network port (repeat option to unset multiple service types) +.. option:: --tag + + Tag to be removed from the subnet + (repeat option to remove multiple tags) + +.. option:: --all-tag + + Clear all tags associated with the subnet + .. _subnet_unset-subnet: .. describe:: diff --git a/openstackclient/network/v2/_tag.py b/openstackclient/network/v2/_tag.py new file mode 100644 index 0000000000..d1e59937fa --- /dev/null +++ b/openstackclient/network/v2/_tag.py @@ -0,0 +1,134 @@ +# 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 argparse + +from openstackclient.i18n import _ + + +class _CommaListAction(argparse.Action): + + def __call__(self, parser, namespace, values, option_string=None): + setattr(namespace, self.dest, values.split(',')) + + +def add_tag_filtering_option_to_parser(parser, collection_name): + parser.add_argument( + '--tags', + metavar='[,,...]', + action=_CommaListAction, + help=_('List %s which have all given tag(s) ' + '(Comma-separated list of tags)') % collection_name + ) + parser.add_argument( + '--any-tags', + metavar='[,,...]', + action=_CommaListAction, + help=_('List %s which have any given tag(s) ' + '(Comma-separated list of tags)') % collection_name + ) + parser.add_argument( + '--not-tags', + metavar='[,,...]', + action=_CommaListAction, + help=_('Exclude %s which have all given tag(s) ' + '(Comma-separated list of tags)') % collection_name + ) + parser.add_argument( + '--not-any-tags', + metavar='[,,...]', + action=_CommaListAction, + help=_('Exclude %s which have any given tag(s) ' + '(Comma-separated list of tags)') % collection_name + ) + + +def get_tag_filtering_args(parsed_args, args): + if parsed_args.tags: + args['tags'] = ','.join(parsed_args.tags) + if parsed_args.any_tags: + args['any_tags'] = ','.join(parsed_args.any_tags) + if parsed_args.not_tags: + args['not_tags'] = ','.join(parsed_args.not_tags) + if parsed_args.not_any_tags: + args['not_any_tags'] = ','.join(parsed_args.not_any_tags) + + +def add_tag_option_to_parser_for_create(parser, resource_name): + tag_group = parser.add_mutually_exclusive_group() + tag_group.add_argument( + '--tag', + action='append', + dest='tags', + metavar='', + help=_("Tag to be added to the %s " + "(repeat option to set multiple tags)") % resource_name + ) + tag_group.add_argument( + '--no-tag', + action='store_true', + help=_("No tags associated with the %s") % resource_name + ) + + +def add_tag_option_to_parser_for_set(parser, resource_name): + parser.add_argument( + '--tag', + action='append', + dest='tags', + metavar='', + help=_("Tag to be added to the %s " + "(repeat option to set multiple tags)") % resource_name + ) + parser.add_argument( + '--no-tag', + action='store_true', + help=_("Clear tags associated with the %s. Specify both " + "--tag and --no-tag to overwrite current tags") % resource_name + ) + + +def update_tags_for_set(client, obj, parsed_args): + if parsed_args.no_tag: + tags = set() + else: + tags = set(obj.tags) + if parsed_args.tags: + tags |= set(parsed_args.tags) + if set(obj.tags) != tags: + client.set_tags(obj, list(tags)) + + +def add_tag_option_to_parser_for_unset(parser, resource_name): + tag_group = parser.add_mutually_exclusive_group() + tag_group.add_argument( + '--tag', + action='append', + dest='tags', + metavar='', + help=_("Tag to be removed from the %s " + "(repeat option to remove multiple tags)") % resource_name) + tag_group.add_argument( + '--all-tag', + action='store_true', + help=_("Clear all tags associated with the %s") % resource_name) + + +def update_tags_for_unset(client, obj, parsed_args): + tags = set(obj.tags) + if parsed_args.all_tag: + tags = set() + if parsed_args.tags: + tags -= set(parsed_args.tags) + if set(obj.tags) != tags: + client.set_tags(obj, list(tags)) diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 33decd8213..4c1725c5f6 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -20,6 +20,7 @@ from openstackclient.identity import common as identity_common from openstackclient.network import common from openstackclient.network import sdk_utils +from openstackclient.network.v2 import _tag def _format_admin_state(item): @@ -280,6 +281,7 @@ def update_parser_network(self, parser): help=_("Do not make the network VLAN transparent")) _add_additional_network_options(parser) + _tag.add_tag_option_to_parser_for_create(parser, _('network')) return parser def update_parser_compute(self, parser): @@ -299,6 +301,8 @@ def take_action_network(self, client, parsed_args): attrs['vlan_transparent'] = False obj = client.create_network(**attrs) + # tags cannot be set when created, so tags need to be set later. + _tag.update_tags_for_set(client, obj, parsed_args) display_columns, columns = _get_columns_network(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) return (display_columns, data) @@ -424,7 +428,9 @@ def update_parser_network(self, parser): '--agent', metavar='', dest='agent_id', - help=_('List networks hosted by agent (ID only)')) + help=_('List networks hosted by agent (ID only)') + ) + _tag.add_tag_filtering_option_to_parser(parser, _('networks')) return parser def take_action_network(self, client, parsed_args): @@ -441,6 +447,7 @@ def take_action_network(self, client, parsed_args): 'provider_network_type', 'is_router_external', 'availability_zones', + 'tags', ) column_headers = ( 'ID', @@ -453,6 +460,7 @@ def take_action_network(self, client, parsed_args): 'Network Type', 'Router Type', 'Availability Zones', + 'Tags', ) elif parsed_args.agent_id: columns = ( @@ -534,6 +542,8 @@ def take_action_network(self, client, parsed_args): args['provider:segmentation_id'] = parsed_args.segmentation_id args['provider_segmentation_id'] = parsed_args.segmentation_id + _tag.get_tag_filtering_args(parsed_args, args) + data = client.networks(**args) return (column_headers, @@ -656,6 +666,7 @@ def get_parser(self, prog_name): action='store_true', help=_("Remove the QoS policy attached to this network") ) + _tag.add_tag_option_to_parser_for_set(parser, _('network')) _add_additional_network_options(parser) return parser @@ -664,7 +675,11 @@ def take_action(self, parsed_args): obj = client.find_network(parsed_args.network, ignore_missing=False) attrs = _get_attrs_network(self.app.client_manager, parsed_args) - client.update_network(obj, **attrs) + if attrs: + client.update_network(obj, **attrs) + + # tags is a subresource and it needs to be updated separately. + _tag.update_tags_for_set(client, obj, parsed_args) class ShowNetwork(common.NetworkAndComputeShowOne): @@ -689,3 +704,27 @@ def take_action_compute(self, client, parsed_args): display_columns, columns = _get_columns_compute(obj) data = utils.get_dict_properties(obj, columns) return (display_columns, data) + + +class UnsetNetwork(command.Command): + _description = _("Unset network properties") + + def get_parser(self, prog_name): + parser = super(UnsetNetwork, self).get_parser(prog_name) + parser.add_argument( + 'network', + metavar="", + help=_("Network to modify (name or ID)") + ) + _tag.add_tag_option_to_parser_for_unset(parser, _('network')) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.find_network(parsed_args.network, ignore_missing=False) + + # NOTE: As of now, UnsetNetwork has no attributes which need + # to be updated by update_network(). + + # tags is a subresource and it needs to be updated separately. + _tag.update_tags_for_unset(client, obj, parsed_args) diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index d7f197e016..9536fe8687 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -26,6 +26,7 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import sdk_utils +from openstackclient.network.v2 import _tag LOG = logging.getLogger(__name__) @@ -47,6 +48,7 @@ def _format_admin_state(state): 'extra_dhcp_opts': utils.format_list_of_dicts, 'fixed_ips': utils.format_list_of_dicts, 'security_group_ids': utils.format_list, + 'tags': utils.format_list, } @@ -384,6 +386,7 @@ def get_parser(self, prog_name): "ip-address=[,mac-address=] " "(repeat option to set multiple allowed-address pairs)") ) + _tag.add_tag_option_to_parser_for_create(parser, _('port')) return parser def take_action(self, parsed_args): @@ -416,6 +419,8 @@ def take_action(self, parsed_args): attrs['qos_policy_id'] = client.find_qos_policy( parsed_args.qos_policy, ignore_missing=False).id obj = client.create_port(**attrs) + # tags cannot be set when created, so tags need to be set later. + _tag.update_tags_for_set(client, obj, parsed_args) display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) @@ -512,6 +517,7 @@ def get_parser(self, prog_name): "(name or ID): subnet=,ip-address= " "(repeat option to set multiple fixed IP addresses)"), ) + _tag.add_tag_filtering_option_to_parser(parser, _('ports')) return parser def take_action(self, parsed_args): @@ -535,8 +541,8 @@ def take_action(self, parsed_args): filters = {} if parsed_args.long: - columns += ('security_group_ids', 'device_owner',) - column_headers += ('Security Groups', 'Device Owner',) + columns += ('security_group_ids', 'device_owner', 'tags') + column_headers += ('Security Groups', 'Device Owner', 'Tags') if parsed_args.device_owner is not None: filters['device_owner'] = parsed_args.device_owner if parsed_args.router: @@ -566,6 +572,8 @@ def take_action(self, parsed_args): filters['fixed_ips'] = _prepare_filter_fixed_ips( self.app.client_manager, parsed_args) + _tag.get_tag_filtering_args(parsed_args, filters) + data = network_client.ports(**filters) return (column_headers, @@ -694,6 +702,7 @@ def get_parser(self, prog_name): "Unset it to None with the 'port unset' command " "(requires data plane status extension)") ) + _tag.add_tag_option_to_parser_for_set(parser, _('port')) return parser @@ -750,7 +759,11 @@ def take_action(self, parsed_args): if parsed_args.data_plane_status: attrs['data_plane_status'] = parsed_args.data_plane_status - client.update_port(obj, **attrs) + if attrs: + client.update_port(obj, **attrs) + + # tags is a subresource and it needs to be updated separately. + _tag.update_tags_for_set(client, obj, parsed_args) class ShowPort(command.ShowOne): @@ -834,6 +847,8 @@ def get_parser(self, prog_name): help=_("Clear existing information of data plane status") ) + _tag.add_tag_option_to_parser_for_unset(parser, _('port')) + return parser def take_action(self, parsed_args): @@ -889,3 +904,6 @@ def take_action(self, parsed_args): if attrs: client.update_port(obj, **attrs) + + # tags is a subresource and it needs to be updated separately. + _tag.update_tags_for_unset(client, obj, parsed_args) diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 8db0c4393b..4f9085373c 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -26,6 +26,7 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import sdk_utils +from openstackclient.network.v2 import _tag LOG = logging.getLogger(__name__) @@ -57,6 +58,7 @@ def _format_routes(routes): 'availability_zones': utils.format_list, 'availability_zone_hints': utils.format_list, 'routes': _format_routes, + 'tags': utils.format_list, } @@ -217,6 +219,7 @@ def get_parser(self, prog_name): "(Router Availability Zone extension required, " "repeat option to set multiple availability zones)") ) + _tag.add_tag_option_to_parser_for_create(parser, _('router')) return parser @@ -229,6 +232,8 @@ def take_action(self, parsed_args): if parsed_args.no_ha: attrs['ha'] = False obj = client.create_router(**attrs) + # tags cannot be set when created, so tags need to be set later. + _tag.update_tags_for_set(client, obj, parsed_args) display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) @@ -310,6 +315,7 @@ def get_parser(self, prog_name): metavar='', help=_("List routers hosted by an agent (ID only)") ) + _tag.add_tag_filtering_option_to_parser(parser, _('routers')) return parser @@ -357,6 +363,8 @@ def take_action(self, parsed_args): args['tenant_id'] = project_id args['project_id'] = project_id + _tag.get_tag_filtering_args(parsed_args, args) + if parsed_args.agent is not None: agent = client.get_agent(parsed_args.agent) data = client.agent_hosted_routers(agent) @@ -384,6 +392,8 @@ def take_action(self, parsed_args): column_headers = column_headers + ( 'Availability zones', ) + columns = columns + ('tags',) + column_headers = column_headers + ('Tags',) return (column_headers, (utils.get_item_properties( @@ -567,6 +577,7 @@ def get_parser(self, prog_name): action='store_true', help=_("Disable Source NAT on external gateway") ) + _tag.add_tag_option_to_parser_for_set(parser, _('router')) return parser def take_action(self, parsed_args): @@ -625,7 +636,10 @@ def take_action(self, parsed_args): ips.append(ip_spec) gateway_info['external_fixed_ips'] = ips attrs['external_gateway_info'] = gateway_info - client.update_router(obj, **attrs) + if attrs: + client.update_router(obj, **attrs) + # tags is a subresource and it needs to be updated separately. + _tag.update_tags_for_set(client, obj, parsed_args) class ShowRouter(command.ShowOne): @@ -675,6 +689,7 @@ def get_parser(self, prog_name): metavar="", help=_("Router to modify (name or ID)") ) + _tag.add_tag_option_to_parser_for_unset(parser, _('router')) return parser def take_action(self, parsed_args): @@ -695,3 +710,5 @@ def take_action(self, parsed_args): attrs['external_gateway_info'] = {} if attrs: client.update_router(obj, **attrs) + # tags is a subresource and it needs to be updated separately. + _tag.update_tags_for_unset(client, obj, parsed_args) diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 2fdd11f07f..b96dff7f94 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -24,6 +24,7 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import sdk_utils +from openstackclient.network.v2 import _tag LOG = logging.getLogger(__name__) @@ -55,6 +56,7 @@ def _format_host_routes(data): 'dns_nameservers': utils.format_list, 'host_routes': _format_host_routes, 'service_types': utils.format_list, + 'tags': utils.format_list, } @@ -336,12 +338,15 @@ def get_parser(self, prog_name): help=_("Set subnet description") ) _get_common_parse_arguments(parser) + _tag.add_tag_option_to_parser_for_create(parser, _('subnet')) return parser def take_action(self, parsed_args): client = self.app.client_manager.network attrs = _get_attrs(self.app.client_manager, parsed_args) obj = client.create_subnet(**attrs) + # tags cannot be set when created, so tags need to be set later. + _tag.update_tags_for_set(client, obj, parsed_args) display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) return (display_columns, data) @@ -454,6 +459,7 @@ def get_parser(self, prog_name): "(in CIDR notation) in output " "e.g.: --subnet-range 10.10.0.0/16") ) + _tag.add_tag_filtering_option_to_parser(parser, _('subnets')) return parser def take_action(self, parsed_args): @@ -488,6 +494,7 @@ def take_action(self, parsed_args): filters['name'] = parsed_args.name if parsed_args.subnet_range: filters['cidr'] = parsed_args.subnet_range + _tag.get_tag_filtering_args(parsed_args, filters) data = network_client.subnets(**filters) headers = ('ID', 'Name', 'Network', 'Subnet') @@ -495,10 +502,10 @@ def take_action(self, parsed_args): if parsed_args.long: headers += ('Project', 'DHCP', 'Name Servers', 'Allocation Pools', 'Host Routes', 'IP Version', - 'Gateway', 'Service Types') + 'Gateway', 'Service Types', 'Tags') columns += ('project_id', 'is_dhcp_enabled', 'dns_nameservers', 'allocation_pools', 'host_routes', 'ip_version', - 'gateway_ip', 'service_types') + 'gateway_ip', 'service_types', 'tags') return (headers, (utils.get_item_properties( @@ -549,6 +556,7 @@ def get_parser(self, prog_name): metavar='', help=_("Set subnet description") ) + _tag.add_tag_option_to_parser_for_set(parser, _('subnet')) _get_common_parse_arguments(parser, is_create=False) return parser @@ -574,7 +582,10 @@ def take_action(self, parsed_args): attrs['allocation_pools'] = [] if 'service_types' in attrs: attrs['service_types'] += obj.service_types - client.update_subnet(obj, **attrs) + if attrs: + client.update_subnet(obj, **attrs) + # tags is a subresource and it needs to be updated separately. + _tag.update_tags_for_set(client, obj, parsed_args) return @@ -643,6 +654,7 @@ def get_parser(self, prog_name): 'Must be a valid device owner value for a network port ' '(repeat option to unset multiple service types)') ) + _tag.add_tag_option_to_parser_for_unset(parser, _('subnet')) parser.add_argument( 'subnet', metavar="", @@ -678,3 +690,6 @@ def take_action(self, parsed_args): attrs['service_types'] = tmp_obj.service_types if attrs: client.update_subnet(obj, **attrs) + + # tags is a subresource and it needs to be updated separately. + _tag.update_tags_for_unset(client, obj, parsed_args) diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index b72a74fc19..a583986856 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -24,6 +24,7 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import sdk_utils +from openstackclient.network.v2 import _tag LOG = logging.getLogger(__name__) @@ -42,6 +43,7 @@ def _get_columns(item): _formatters = { 'prefixes': utils.format_list, + 'tags': utils.format_list, } @@ -191,6 +193,7 @@ def get_parser(self, prog_name): metavar='', help=_("Set default quota for subnet pool as the number of" "IP addresses allowed in a subnet")), + _tag.add_tag_option_to_parser_for_create(parser, _('subnet pool')) return parser def take_action(self, parsed_args): @@ -200,6 +203,8 @@ def take_action(self, parsed_args): if "prefixes" not in attrs: attrs['prefixes'] = [] obj = client.create_subnet_pool(**attrs) + # tags cannot be set when created, so tags need to be set later. + _tag.update_tags_for_set(client, obj, parsed_args) display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) return (display_columns, data) @@ -293,6 +298,7 @@ def get_parser(self, prog_name): help=_("List only subnet pools of given address scope " "in output (name or ID)") ) + _tag.add_tag_filtering_option_to_parser(parser, _('subnet pools')) return parser def take_action(self, parsed_args): @@ -324,15 +330,16 @@ def take_action(self, parsed_args): parsed_args.address_scope, ignore_missing=False) filters['address_scope_id'] = address_scope.id + _tag.get_tag_filtering_args(parsed_args, filters) data = network_client.subnet_pools(**filters) headers = ('ID', 'Name', 'Prefixes') columns = ('id', 'name', 'prefixes') if parsed_args.long: headers += ('Default Prefix Length', 'Address Scope', - 'Default Subnet Pool', 'Shared') + 'Default Subnet Pool', 'Shared', 'Tags') columns += ('default_prefix_length', 'address_scope_id', - 'is_default', 'is_shared') + 'is_default', 'is_shared', 'tags') return (headers, (utils.get_item_properties( @@ -384,6 +391,8 @@ def get_parser(self, prog_name): metavar='', help=_("Set default quota for subnet pool as the number of" "IP addresses allowed in a subnet")), + _tag.add_tag_option_to_parser_for_set(parser, _('subnet pool')) + return parser def take_action(self, parsed_args): @@ -397,7 +406,10 @@ def take_action(self, parsed_args): if 'prefixes' in attrs: attrs['prefixes'].extend(obj.prefixes) - client.update_subnet_pool(obj, **attrs) + if attrs: + client.update_subnet_pool(obj, **attrs) + # tags is a subresource and it needs to be updated separately. + _tag.update_tags_for_set(client, obj, parsed_args) class ShowSubnetPool(command.ShowOne): @@ -441,6 +453,7 @@ def get_parser(self, prog_name): metavar="", help=_("Subnet pool to modify (name or ID)") ) + _tag.add_tag_option_to_parser_for_unset(parser, _('subnet pool')) return parser def take_action(self, parsed_args): @@ -461,3 +474,5 @@ def take_action(self, parsed_args): attrs['prefixes'] = tmp_prefixes if attrs: client.update_subnet_pool(obj, **attrs) + # tags is a subresource and it needs to be updated separately. + _tag.update_tags_for_unset(client, obj, parsed_args) diff --git a/openstackclient/tests/functional/network/v2/common.py b/openstackclient/tests/functional/network/v2/common.py index e3835abf59..a18bc48faf 100644 --- a/openstackclient/tests/functional/network/v2/common.py +++ b/openstackclient/tests/functional/network/v2/common.py @@ -10,6 +10,9 @@ # License for the specific language governing permissions and limitations # under the License. +import json +import uuid + from openstackclient.tests.functional import base @@ -20,3 +23,81 @@ class NetworkTests(base.TestCase): def setUpClass(cls): super(NetworkTests, cls).setUpClass() cls.haz_network = base.is_service_enabled('network') + + +class NetworkTagTests(NetworkTests): + """Functional tests with tag operation""" + + base_command = None + + def test_tag_operation(self): + # Get project IDs + cmd_output = json.loads(self.openstack('token issue -f json ')) + auth_project_id = cmd_output['project_id'] + + # Network create with no options + name1 = self._create_resource_and_tag_check('', []) + # Network create with tags + name2 = self._create_resource_and_tag_check('--tag red --tag blue', + ['red', 'blue']) + # Network create with no tag explicitly + name3 = self._create_resource_and_tag_check('--no-tag', []) + + self._set_resource_and_tag_check('set', name1, '--tag red --tag green', + ['red', 'green']) + + list_expected = ((name1, ['red', 'green']), + (name2, ['red', 'blue']), + (name3, [])) + self._list_tag_check(auth_project_id, list_expected) + + self._set_resource_and_tag_check('set', name1, '--tag blue', + ['red', 'green', 'blue']) + self._set_resource_and_tag_check( + 'set', name1, + '--no-tag --tag yellow --tag orange --tag purple', + ['yellow', 'orange', 'purple']) + self._set_resource_and_tag_check('unset', name1, '--tag yellow', + ['orange', 'purple']) + self._set_resource_and_tag_check('unset', name1, '--all-tag', []) + self._set_resource_and_tag_check('set', name2, '--no-tag', []) + + def _assertTagsEqual(self, expected, actual): + # TODO(amotoki): Should migrate to cliff format columns. + # At now, unit test assert method needs to be replaced + # to handle format columns, so format_list() is used. + # NOTE: The order of tag is undeterminestic. + actual_tags = filter(bool, actual.split(', ')) + self.assertEqual(set(expected), set(actual_tags)) + + def _list_tag_check(self, project_id, expected): + cmd_output = json.loads(self.openstack( + '{} list --long --project {} -f json'.format(self.base_command, + project_id))) + for name, tags in expected: + net = [n for n in cmd_output if n['Name'] == name][0] + self._assertTagsEqual(tags, net['Tags']) + + def _create_resource_for_tag_test(self, name, args): + return json.loads(self.openstack( + '{} create -f json {} {}'.format(self.base_command, args, name) + )) + + def _create_resource_and_tag_check(self, args, expected): + name = uuid.uuid4().hex + cmd_output = self._create_resource_for_tag_test(name, args) + self.addCleanup( + self.openstack, '{} delete {}'.format(self.base_command, name)) + self.assertIsNotNone(cmd_output["id"]) + self._assertTagsEqual(expected, cmd_output['tags']) + return name + + def _set_resource_and_tag_check(self, command, name, args, expected): + cmd_output = self.openstack( + '{} {} {} {}'.format(self.base_command, command, args, name) + ) + self.assertFalse(cmd_output) + cmd_output = json.loads(self.openstack( + '{} show -f json {}'.format(self.base_command, name) + )) + self._assertTagsEqual(expected, cmd_output['tags']) diff --git a/openstackclient/tests/functional/network/v2/test_network.py b/openstackclient/tests/functional/network/v2/test_network.py index 91939703d1..40fb382a9e 100644 --- a/openstackclient/tests/functional/network/v2/test_network.py +++ b/openstackclient/tests/functional/network/v2/test_network.py @@ -16,9 +16,11 @@ from openstackclient.tests.functional.network.v2 import common -class NetworkTests(common.NetworkTests): +class NetworkTests(common.NetworkTagTests): """Functional tests for network""" + base_command = 'network' + def setUp(self): super(NetworkTests, self).setUp() # Nothing in this class works with Nova Network diff --git a/openstackclient/tests/functional/network/v2/test_port.py b/openstackclient/tests/functional/network/v2/test_port.py index 09ac3566e4..a705979028 100644 --- a/openstackclient/tests/functional/network/v2/test_port.py +++ b/openstackclient/tests/functional/network/v2/test_port.py @@ -16,9 +16,14 @@ from openstackclient.tests.functional.network.v2 import common -class PortTests(common.NetworkTests): +class PortTests(common.NetworkTagTests): """Functional tests for port""" + base_command = 'port' + + NAME = uuid.uuid4().hex + NETWORK_NAME = uuid.uuid4().hex + @classmethod def setUpClass(cls): common.NetworkTests.setUpClass() @@ -250,3 +255,9 @@ def test_port_set_sg(self): sg_id2, json_output.get('security_group_ids'), ) + + def _create_resource_for_tag_test(self, name, args): + return json.loads(self.openstack( + '{} create -f json --network {} {} {}' + .format(self.base_command, self.NETWORK_NAME, args, name) + )) diff --git a/openstackclient/tests/functional/network/v2/test_router.py b/openstackclient/tests/functional/network/v2/test_router.py index 2e5cb5ef55..95c5a96f8b 100644 --- a/openstackclient/tests/functional/network/v2/test_router.py +++ b/openstackclient/tests/functional/network/v2/test_router.py @@ -16,9 +16,11 @@ from openstackclient.tests.functional.network.v2 import common -class RouterTests(common.NetworkTests): +class RouterTests(common.NetworkTagTests): """Functional tests for router""" + base_command = 'router' + def setUp(self): super(RouterTests, self).setUp() # Nothing in this class works with Nova Network diff --git a/openstackclient/tests/functional/network/v2/test_subnet.py b/openstackclient/tests/functional/network/v2/test_subnet.py index 040b645b1b..d5309ee674 100644 --- a/openstackclient/tests/functional/network/v2/test_subnet.py +++ b/openstackclient/tests/functional/network/v2/test_subnet.py @@ -17,9 +17,11 @@ from openstackclient.tests.functional.network.v2 import common -class SubnetTests(common.NetworkTests): +class SubnetTests(common.NetworkTagTests): """Functional tests for subnet""" + base_command = 'subnet' + @classmethod def setUpClass(cls): common.NetworkTests.setUpClass() @@ -285,3 +287,9 @@ def _subnet_create(self, cmd, name, is_type_ipv4=True): # break and no longer retry if create successfully break return cmd_output + + def _create_resource_for_tag_test(self, name, args): + cmd = ('subnet create -f json --network ' + + self.NETWORK_NAME + ' ' + args + + ' --subnet-range') + return self._subnet_create(cmd, name) diff --git a/openstackclient/tests/functional/network/v2/test_subnet_pool.py b/openstackclient/tests/functional/network/v2/test_subnet_pool.py index a4b823f100..46aa6f1433 100644 --- a/openstackclient/tests/functional/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/functional/network/v2/test_subnet_pool.py @@ -17,9 +17,11 @@ from openstackclient.tests.functional.network.v2 import common -class SubnetPoolTests(common.NetworkTests): +class SubnetPoolTests(common.NetworkTagTests): """Functional tests for subnet pool""" + base_command = 'subnet pool' + def setUp(self): super(SubnetPoolTests, self).setUp() # Nothing in this class works with Nova Network @@ -321,3 +323,7 @@ def _subnet_pool_create(self, cmd, name, is_type_ipv4=True): break return cmd_output, pool_prefix + + def _create_resource_for_tag_test(self, name, args): + cmd_output, _pool_prefix = self._subnet_pool_create(args, name) + return cmd_output diff --git a/openstackclient/tests/unit/network/v2/_test_tag.py b/openstackclient/tests/unit/network/v2/_test_tag.py new file mode 100644 index 0000000000..bd46153782 --- /dev/null +++ b/openstackclient/tests/unit/network/v2/_test_tag.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. +# + +from openstackclient.tests.unit import utils as tests_utils + + +class TestCreateTagMixin(object): + """Test case mixin to test network tag operation for resource creation. + + * Each test class must create a mock for self.network.set_tags + * If you test tag operation combined with other options, + you need to write test(s) directly in individual test cases. + * The following instance attributes must be defined: + + * _tag_test_resource: Test resource returned by mocked create_. + * _tag_create_resource_mock: Mocked create_ method of SDK. + * _tag_create_required_arglist: List of required arguments when creating + a resource with default options. + * _tag_create_required_verifylist: List of expected parsed_args params + when creating a resource with default options. + * _tag_create_required_attrs: Expected attributes passed to a mocked + create_resource method when creating a resource with default options. + """ + + def _test_create_with_tag(self, add_tags=True): + arglist = self._tag_create_required_arglist[:] + if add_tags: + arglist += ['--tag', 'red', '--tag', 'blue'] + else: + arglist += ['--no-tag'] + verifylist = self._tag_create_required_verifylist[:] + if add_tags: + verifylist.append(('tags', ['red', 'blue'])) + else: + verifylist.append(('no_tag', True)) + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = (self.cmd.take_action(parsed_args)) + + self._tag_create_resource_mock.assert_called_once_with( + **self._tag_create_required_attrs) + if add_tags: + self.network.set_tags.assert_called_once_with( + self._tag_test_resource, + tests_utils.CompareBySet(['red', 'blue'])) + else: + self.assertFalse(self.network.set_tags.called) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_with_tags(self): + self._test_create_with_tag(add_tags=True) + + def test_create_with_no_tag(self): + self._test_create_with_tag(add_tags=False) + + +class TestListTagMixin(object): + """Test case mixin to test network tag operation for resource listing. + + * A test resource returned by find_ must contains + "red" and "green" tags. + * Each test class must create a mock for self.network.set_tags + * If you test tag operation combined with other options, + you need to write test(s) directly in individual test cases. + * The following instance attributes must be defined: + + * _tag_create_resource_mock: Mocked list_ method of SDK. + """ + + def test_list_with_tag_options(self): + arglist = [ + '--tags', 'red,blue', + '--any-tags', 'red,green', + '--not-tags', 'orange,yellow', + '--not-any-tags', 'black,white', + ] + verifylist = [ + ('tags', ['red', 'blue']), + ('any_tags', ['red', 'green']), + ('not_tags', ['orange', 'yellow']), + ('not_any_tags', ['black', 'white']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self._tag_list_resource_mock.assert_called_once_with( + **{'tags': 'red,blue', + 'any_tags': 'red,green', + 'not_tags': 'orange,yellow', + 'not_any_tags': 'black,white'} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + +class TestSetTagMixin(object): + """Test case mixin to test network tag operation for resource update. + + * A test resource returned by find_ must contains + "red" and "green" tags. + * Each test class must create a mock for self.network.set_tags + * If you test tag operation combined with other options, + you need to write test(s) directly in individual test cases. + * The following instance attributes must be defined: + + * _tag_resource_name: positional arg name of a resource to be updated. + * _tag_test_resource: Test resource returned by mocked update_. + * _tag_update_resource_mock: Mocked update_ method of SDK. + """ + + def _test_set_tags(self, with_tags=True): + if with_tags: + arglist = ['--tag', 'red', '--tag', 'blue'] + verifylist = [('tags', ['red', 'blue'])] + expected_args = ['red', 'blue', 'green'] + else: + arglist = ['--no-tag'] + verifylist = [('no_tag', True)] + expected_args = [] + arglist.append(self._tag_test_resource.name) + verifylist.append( + (self._tag_resource_name, self._tag_test_resource.name)) + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.assertFalse(self._tag_update_resource_mock.called) + self.network.set_tags.assert_called_once_with( + self._tag_test_resource, + tests_utils.CompareBySet(expected_args)) + self.assertIsNone(result) + + def test_set_with_tags(self): + self._test_set_tags(with_tags=True) + + def test_set_with_no_tag(self): + self._test_set_tags(with_tags=False) + + +class TestUnsetTagMixin(object): + """Test case mixin to test network tag operation for resource update. + + * Each test class must create a mock for self.network.set_tags + * If you test tag operation combined with other options, + you need to write test(s) directly in individual test cases. + * The following instance attributes must be defined: + + * _tag_resource_name: positional arg name of a resource to be updated. + * _tag_test_resource: Test resource returned by mocked update_. + * _tag_update_resource_mock: Mocked update_ method of SDK. + """ + + def _test_unset_tags(self, with_tags=True): + if with_tags: + arglist = ['--tag', 'red', '--tag', 'blue'] + verifylist = [('tags', ['red', 'blue'])] + expected_args = ['green'] + else: + arglist = ['--all-tag'] + verifylist = [('all_tag', True)] + expected_args = [] + arglist.append(self._tag_test_resource.name) + verifylist.append( + (self._tag_resource_name, self._tag_test_resource.name)) + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.assertFalse(self._tag_update_resource_mock.called) + self.network.set_tags.assert_called_once_with( + self._tag_test_resource, + tests_utils.CompareBySet(expected_args)) + self.assertIsNone(result) + + def test_unset_with_tags(self): + self._test_unset_tags(with_tags=True) + + def test_unset_with_all_tag(self): + self._test_unset_tags(with_tags=False) diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index 98bda1649f..eadab58461 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -350,6 +350,7 @@ def create_one_network(attrs=None): 'qos_policy_id': 'qos-policy-id-' + uuid.uuid4().hex, 'ipv4_address_scope': 'ipv4' + uuid.uuid4().hex, 'ipv6_address_scope': 'ipv6' + uuid.uuid4().hex, + 'tags': [], } # Overwrite default attributes. @@ -576,6 +577,7 @@ def create_one_port(attrs=None): 'status': 'ACTIVE', 'tenant_id': 'project-id-' + uuid.uuid4().hex, 'qos_policy_id': 'qos-policy-id-' + uuid.uuid4().hex, + 'tags': [], } # Overwrite default attributes. @@ -1053,6 +1055,7 @@ def create_one_router(attrs=None): 'external_gateway_info': {}, 'availability_zone_hints': [], 'availability_zones': [], + 'tags': [], } # Overwrite default attributes. @@ -1294,6 +1297,7 @@ def create_one_subnet(attrs=None): 'service_types': [], 'subnetpool_id': None, 'description': 'subnet-description-' + uuid.uuid4().hex, + 'tags': [], } # Overwrite default attributes. @@ -1544,6 +1548,7 @@ def create_one_subnet_pool(attrs=None): 'default_quota': None, 'ip_version': '4', 'description': 'subnet-pool-description-' + uuid.uuid4().hex, + 'tags': [], } # Overwrite default attributes. diff --git a/openstackclient/tests/unit/network/v2/test_network.py b/openstackclient/tests/unit/network/v2/test_network.py index bc8c402487..e620cd9c1d 100644 --- a/openstackclient/tests/unit/network/v2/test_network.py +++ b/openstackclient/tests/unit/network/v2/test_network.py @@ -22,6 +22,7 @@ from openstackclient.tests.unit import fakes from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes_v2 from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3 +from openstackclient.tests.unit.network.v2 import _test_tag from openstackclient.tests.unit.network.v2 import fakes as network_fakes from openstackclient.tests.unit import utils as tests_utils @@ -41,7 +42,7 @@ def setUp(self): self.domains_mock = self.app.client_manager.identity.domains -class TestCreateNetworkIdentityV3(TestNetwork): +class TestCreateNetworkIdentityV3(TestNetwork, _test_tag.TestCreateTagMixin): project = identity_fakes_v3.FakeProject.create_one_project() domain = identity_fakes_v3.FakeDomain.create_one_domain() @@ -105,6 +106,7 @@ def setUp(self): super(TestCreateNetworkIdentityV3, self).setUp() self.network.create_network = mock.Mock(return_value=self._network) + self.network.set_tags = mock.Mock(return_value=None) # Get the command object to test self.cmd = network.CreateNetwork(self.app, self.namespace) @@ -113,6 +115,22 @@ def setUp(self): self.domains_mock.get.return_value = self.domain self.network.find_qos_policy = mock.Mock(return_value=self.qos_policy) + # TestCreateTagMixin + self._tag_test_resource = self._network + self._tag_create_resource_mock = self.network.create_network + self._tag_create_required_arglist = [self._network.name] + self._tag_create_required_verifylist = [ + ('name', self._network.name), + ('enable', True), + ('share', None), + ('project', None), + ('external', False), + ] + self._tag_create_required_attrs = { + 'admin_state_up': True, + 'name': self._network.name, + } + def test_create_no_options(self): arglist = [] verifylist = [] @@ -139,6 +157,7 @@ def test_create_default_options(self): 'admin_state_up': True, 'name': self._network.name, }) + self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -287,6 +306,7 @@ def setUp(self): super(TestCreateNetworkIdentityV2, self).setUp() self.network.create_network = mock.Mock(return_value=self._network) + self.network.set_tags = mock.Mock(return_value=None) # Get the command object to test self.cmd = network.CreateNetwork(self.app, self.namespace) @@ -328,6 +348,7 @@ def test_create_with_project_identityv2(self): 'tenant_id': self.project.id, 'project_id': self.project.id, }) + self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -440,7 +461,7 @@ def test_delete_multiple_networks_exception(self): self.network.delete_network.assert_has_calls(calls) -class TestListNetwork(TestNetwork): +class TestListNetwork(TestNetwork, _test_tag.TestListTagMixin): # The networks going to be listed up. _network = network_fakes.FakeNetwork.create_networks(count=3) @@ -461,6 +482,7 @@ class TestListNetwork(TestNetwork): 'Network Type', 'Router Type', 'Availability Zones', + 'Tags', ) data = [] @@ -484,6 +506,7 @@ class TestListNetwork(TestNetwork): net.provider_network_type, network._format_router_external(net.is_router_external), utils.format_list(net.availability_zones), + utils.format_list(net.tags), )) def setUp(self): @@ -501,6 +524,9 @@ def setUp(self): self.network.dhcp_agent_hosting_networks = mock.Mock( return_value=self._network) + # TestListTagMixin + self._tag_list_resource_mock = self.network.networks + def test_network_list_no_options(self): arglist = [] verifylist = [ @@ -795,10 +821,11 @@ def test_network_list_dhcp_agent(self): self.assertEqual(list(data), list(self.data)) -class TestSetNetwork(TestNetwork): +class TestSetNetwork(TestNetwork, _test_tag.TestSetTagMixin): # The network to set. - _network = network_fakes.FakeNetwork.create_one_network() + _network = network_fakes.FakeNetwork.create_one_network( + {'tags': ['green', 'red']}) qos_policy = (network_fakes.FakeNetworkQosPolicy. create_one_qos_policy(attrs={'id': _network.qos_policy_id})) @@ -806,6 +833,7 @@ def setUp(self): super(TestSetNetwork, self).setUp() self.network.update_network = mock.Mock(return_value=None) + self.network.set_tags = 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) @@ -813,6 +841,11 @@ def setUp(self): # Get the command object to test self.cmd = network.SetNetwork(self.app, self.namespace) + # TestSetTagMixin + self._tag_resource_name = 'network' + self._tag_test_resource = self._network + self._tag_update_resource_mock = self.network.update_network + def test_set_this(self): arglist = [ self._network.name, @@ -902,9 +935,8 @@ def test_set_nothing(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - attrs = {} - self.network.update_network.assert_called_once_with( - self._network, **attrs) + self.assertFalse(self.network.update_network.called) + self.assertFalse(self.network.set_tags.called) self.assertIsNone(result) @@ -990,3 +1022,40 @@ def test_show_all_options(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + + +class TestUnsetNetwork(TestNetwork, _test_tag.TestUnsetTagMixin): + + # The network to set. + _network = network_fakes.FakeNetwork.create_one_network( + {'tags': ['green', 'red']}) + qos_policy = (network_fakes.FakeNetworkQosPolicy. + create_one_qos_policy(attrs={'id': _network.qos_policy_id})) + + def setUp(self): + super(TestUnsetNetwork, self).setUp() + + self.network.update_network = mock.Mock(return_value=None) + self.network.set_tags = 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.UnsetNetwork(self.app, self.namespace) + + # TestUnsetNetwork + self._tag_resource_name = 'network' + self._tag_test_resource = self._network + self._tag_update_resource_mock = self.network.update_network + + def test_unset_nothing(self): + arglist = [self._network.name, ] + verifylist = [('network', self._network.name), ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.assertFalse(self.network.update_network.called) + self.assertFalse(self.network.set_tags.called) + self.assertIsNone(result) diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index a8a6dba9be..deda6b4128 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -21,6 +21,7 @@ from openstackclient.network.v2 import port from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes +from openstackclient.tests.unit.network.v2 import _test_tag from openstackclient.tests.unit.network.v2 import fakes as network_fakes from openstackclient.tests.unit import utils as tests_utils @@ -35,7 +36,8 @@ def setUp(self): # Get a shortcut to the ProjectManager Mock self.projects_mock = self.app.client_manager.identity.projects - def _get_common_cols_data(self, fake_port): + @staticmethod + def _get_common_cols_data(fake_port): columns = ( 'admin_state_up', 'allowed_address_pairs', @@ -61,6 +63,7 @@ def _get_common_cols_data(self, fake_port): 'qos_policy_id', 'security_group_ids', 'status', + 'tags', ) data = ( @@ -88,19 +91,22 @@ def _get_common_cols_data(self, fake_port): fake_port.qos_policy_id, utils.format_list(fake_port.security_group_ids), fake_port.status, + utils.format_list(fake_port.tags), ) return columns, data -class TestCreatePort(TestPort): +class TestCreatePort(TestPort, _test_tag.TestCreateTagMixin): _port = network_fakes.FakePort.create_one_port() + columns, data = TestPort._get_common_cols_data(_port) def setUp(self): super(TestCreatePort, self).setUp() self.network.create_port = mock.Mock(return_value=self._port) + self.network.set_tags = mock.Mock(return_value=None) fake_net = network_fakes.FakeNetwork.create_one_network({ 'id': self._port.network_id, }) @@ -110,6 +116,24 @@ def setUp(self): # Get the command object to test self.cmd = port.CreatePort(self.app, self.namespace) + # TestUnsetTagMixin + self._tag_test_resource = self._port + self._tag_create_resource_mock = self.network.create_port + self._tag_create_required_arglist = [ + '--network', self._port.network_id, + 'test-port', + ] + self._tag_create_required_verifylist = [ + ('network', self._port.network_id,), + ('enable', True), + ('name', 'test-port'), + ] + self._tag_create_required_attrs = { + 'admin_state_up': True, + 'network_id': self._port.network_id, + 'name': 'test-port', + } + def test_create_default_options(self): arglist = [ '--network', self._port.network_id, @@ -129,10 +153,10 @@ def test_create_default_options(self): 'network_id': self._port.network_id, 'name': 'test-port', }) + self.assertFalse(self.network.set_tags.called) - ref_columns, ref_data = self._get_common_cols_data(self._port) - self.assertEqual(ref_columns, columns) - self.assertEqual(ref_data, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) def test_create_full_options(self): arglist = [ @@ -166,7 +190,6 @@ def test_create_full_options(self): ('network', self._port.network_id), ('dns_name', '8.8.8.8'), ('name', 'test-port'), - ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -187,9 +210,8 @@ def test_create_full_options(self): 'name': 'test-port', }) - ref_columns, ref_data = self._get_common_cols_data(self._port) - self.assertEqual(ref_columns, columns) - self.assertEqual(ref_data, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) def test_create_invalid_json_binding_profile(self): arglist = [ @@ -239,9 +261,8 @@ def test_create_json_binding_profile(self): 'name': 'test-port', }) - ref_columns, ref_data = self._get_common_cols_data(self._port) - self.assertEqual(ref_columns, columns) - self.assertEqual(ref_data, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) def test_create_with_security_group(self): secgroup = network_fakes.FakeSecurityGroup.create_one_security_group() @@ -269,9 +290,8 @@ def test_create_with_security_group(self): 'name': 'test-port', }) - ref_columns, ref_data = self._get_common_cols_data(self._port) - self.assertEqual(ref_columns, columns) - self.assertEqual(ref_data, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) def test_create_port_with_dns_name(self): arglist = [ @@ -296,9 +316,8 @@ def test_create_port_with_dns_name(self): 'name': 'test-port', }) - ref_columns, ref_data = self._get_common_cols_data(self._port) - self.assertEqual(ref_columns, columns) - self.assertEqual(ref_data, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) def test_create_with_security_groups(self): sg_1 = network_fakes.FakeSecurityGroup.create_one_security_group() @@ -327,9 +346,8 @@ def test_create_with_security_groups(self): 'name': 'test-port', }) - ref_columns, ref_data = self._get_common_cols_data(self._port) - self.assertEqual(ref_columns, columns) - self.assertEqual(ref_data, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) def test_create_with_no_security_groups(self): arglist = [ @@ -354,9 +372,8 @@ def test_create_with_no_security_groups(self): 'name': 'test-port', }) - ref_columns, ref_data = self._get_common_cols_data(self._port) - self.assertEqual(ref_columns, columns) - self.assertEqual(ref_data, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) def test_create_port_with_allowed_address_pair_ipaddr(self): pairs = [{'ip_address': '192.168.1.123'}, @@ -385,9 +402,8 @@ def test_create_port_with_allowed_address_pair_ipaddr(self): 'name': 'test-port', }) - ref_columns, ref_data = self._get_common_cols_data(self._port) - self.assertEqual(ref_columns, columns) - self.assertEqual(ref_data, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) def test_create_port_with_allowed_address_pair(self): pairs = [{'ip_address': '192.168.1.123', @@ -422,9 +438,8 @@ def test_create_port_with_allowed_address_pair(self): 'name': 'test-port', }) - ref_columns, ref_data = self._get_common_cols_data(self._port) - self.assertEqual(ref_columns, columns) - self.assertEqual(ref_data, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) def test_create_port_with_qos(self): qos_policy = network_fakes.FakeNetworkQosPolicy.create_one_qos_policy() @@ -451,9 +466,8 @@ def test_create_port_with_qos(self): 'name': 'test-port', }) - ref_columns, ref_data = self._get_common_cols_data(self._port) - self.assertEqual(ref_columns, columns) - self.assertEqual(ref_data, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) def test_create_port_security_enabled(self): arglist = [ @@ -583,7 +597,7 @@ def test_multi_ports_delete_with_exception(self): ) -class TestListPort(TestPort): +class TestListPort(TestPort, _test_tag.TestListTagMixin): _ports = network_fakes.FakePort.create_ports(count=3) @@ -603,6 +617,7 @@ class TestListPort(TestPort): 'Status', 'Security Groups', 'Device Owner', + 'Tags', ) data = [] @@ -625,6 +640,7 @@ class TestListPort(TestPort): prt.status, utils.format_list(prt.security_group_ids), prt.device_owner, + utils.format_list(prt.tags), )) def setUp(self): @@ -642,6 +658,8 @@ def setUp(self): self.network.find_router = mock.Mock(return_value=fake_router) self.network.find_network = mock.Mock(return_value=fake_network) self.app.client_manager.compute = mock.Mock() + # TestUnsetTagMixin + self._tag_list_resource_mock = self.network.ports def test_port_list_no_options(self): arglist = [] @@ -902,9 +920,9 @@ def test_port_list_project_domain(self): self.assertEqual(self.data, list(data)) -class TestSetPort(TestPort): +class TestSetPort(TestPort, _test_tag.TestSetTagMixin): - _port = network_fakes.FakePort.create_one_port() + _port = network_fakes.FakePort.create_one_port({'tags': ['green', 'red']}) def setUp(self): super(TestSetPort, self).setUp() @@ -912,9 +930,14 @@ def setUp(self): self.network.find_subnet = mock.Mock(return_value=self.fake_subnet) self.network.find_port = mock.Mock(return_value=self._port) self.network.update_port = mock.Mock(return_value=None) + self.network.set_tags = mock.Mock(return_value=None) # Get the command object to test self.cmd = port.SetPort(self.app, self.namespace) + # TestSetTagMixin + self._tag_resource_name = 'port' + self._tag_test_resource = self._port + self._tag_update_resource_mock = self.network.update_port def test_set_port_defaults(self): arglist = [ @@ -926,8 +949,8 @@ def test_set_port_defaults(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - attrs = {} - self.network.update_port.assert_called_once_with(self._port, **attrs) + self.assertFalse(self.network.update_port.called) + self.assertFalse(self.network.set_tags.called) self.assertIsNone(result) def test_set_port_fixed_ip(self): @@ -1412,6 +1435,7 @@ class TestShowPort(TestPort): # The port to show. _port = network_fakes.FakePort.create_one_port() + columns, data = TestPort._get_common_cols_data(_port) def setUp(self): super(TestShowPort, self).setUp() @@ -1442,12 +1466,11 @@ def test_show_all_options(self): self.network.find_port.assert_called_once_with( self._port.name, ignore_missing=False) - ref_columns, ref_data = self._get_common_cols_data(self._port) - self.assertEqual(ref_columns, columns) - self.assertEqual(ref_data, data) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) -class TestUnsetPort(TestPort): +class TestUnsetPort(TestPort, _test_tag.TestUnsetTagMixin): def setUp(self): super(TestUnsetPort, self).setUp() @@ -1456,14 +1479,20 @@ def setUp(self): 'ip_address': '0.0.0.1'}, {'subnet_id': '042eb10a-3a18-4658-ab-cf47c8d03152', 'ip_address': '1.0.0.0'}], - 'binding:profile': {'batman': 'Joker', 'Superman': 'LexLuthor'}}) + 'binding:profile': {'batman': 'Joker', 'Superman': 'LexLuthor'}, + 'tags': ['green', 'red'], }) self.fake_subnet = network_fakes.FakeSubnet.create_one_subnet( {'id': '042eb10a-3a18-4658-ab-cf47c8d03152'}) self.network.find_subnet = mock.Mock(return_value=self.fake_subnet) self.network.find_port = mock.Mock(return_value=self._testport) self.network.update_port = mock.Mock(return_value=None) + self.network.set_tags = mock.Mock(return_value=None) # Get the command object to test self.cmd = port.UnsetPort(self.app, self.namespace) + # TestUnsetTagMixin + self._tag_resource_name = 'port' + self._tag_test_resource = self._testport + self._tag_update_resource_mock = self.network.update_port def test_unset_port_parameters(self): arglist = [ diff --git a/openstackclient/tests/unit/network/v2/test_router.py b/openstackclient/tests/unit/network/v2/test_router.py index c153fe4a08..d65c9aa9f7 100644 --- a/openstackclient/tests/unit/network/v2/test_router.py +++ b/openstackclient/tests/unit/network/v2/test_router.py @@ -19,6 +19,7 @@ from openstackclient.network.v2 import router from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3 +from openstackclient.tests.unit.network.v2 import _test_tag from openstackclient.tests.unit.network.v2 import fakes as network_fakes from openstackclient.tests.unit import utils as tests_utils @@ -111,7 +112,7 @@ def test_add_subnet_required_options(self): self.assertIsNone(result) -class TestCreateRouter(TestRouter): +class TestCreateRouter(TestRouter, _test_tag.TestCreateTagMixin): # The new router created. new_router = network_fakes.FakeRouter.create_one_router() @@ -129,6 +130,7 @@ class TestCreateRouter(TestRouter): 'project_id', 'routes', 'status', + 'tags', ) data = ( router._format_admin_state(new_router.admin_state_up), @@ -143,22 +145,42 @@ class TestCreateRouter(TestRouter): new_router.tenant_id, router._format_routes(new_router.routes), new_router.status, + osc_utils.format_list(new_router.tags), ) def setUp(self): super(TestCreateRouter, self).setUp() self.network.create_router = mock.Mock(return_value=self.new_router) + self.network.set_tags = mock.Mock(return_value=None) # Get the command object to test self.cmd = router.CreateRouter(self.app, self.namespace) + # TestCreateTagMixin + self._tag_test_resource = self.new_router + self._tag_create_resource_mock = self.network.create_router + self._tag_create_required_arglist = [ + self.new_router.name, + ] + self._tag_create_required_verifylist = [ + ('name', self.new_router.name), + ('enable', True), + ('distributed', False), + ('ha', False), + ] + self._tag_create_required_attrs = { + 'admin_state_up': True, + 'name': self.new_router.name, + } + def test_create_no_options(self): arglist = [] verifylist = [] self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) + self.assertFalse(self.network.set_tags.called) def test_create_default_options(self): arglist = [ @@ -178,6 +200,7 @@ def test_create_default_options(self): 'admin_state_up': True, 'name': self.new_router.name, }) + self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -345,7 +368,7 @@ def test_multi_routers_delete_with_exception(self): ) -class TestListRouter(TestRouter): +class TestListRouter(TestRouter, _test_tag.TestListTagMixin): # The routers going to be listed up. routers = network_fakes.FakeRouter.create_routers(count=3) @@ -363,11 +386,13 @@ class TestListRouter(TestRouter): columns_long = columns + ( 'Routes', 'External gateway info', - 'Availability zones' + 'Availability zones', + 'Tags', ) columns_long_no_az = columns + ( 'Routes', 'External gateway info', + 'Tags', ) data = [] @@ -404,6 +429,7 @@ class TestListRouter(TestRouter): router._format_routes(r.routes), router._format_external_gateway_info(r.external_gateway_info), osc_utils.format_list(r.availability_zones), + osc_utils.format_list(r.tags), ) ) data_long_no_az = [] @@ -413,6 +439,7 @@ class TestListRouter(TestRouter): data[i] + ( router._format_routes(r.routes), router._format_external_gateway_info(r.external_gateway_info), + osc_utils.format_list(r.tags), ) ) @@ -432,6 +459,9 @@ def setUp(self): self.network.get_agent = mock.Mock(return_value=self._testagent) self.network.get_router = mock.Mock(return_value=self.routers[0]) + # TestListTagMixin + self._tag_list_resource_mock = self.network.routers + def test_router_list_no_options(self): arglist = [] verifylist = [ @@ -684,26 +714,33 @@ def test_remove_subnet_required_options(self): self.assertIsNone(result) -class TestSetRouter(TestRouter): +class TestSetRouter(TestRouter, _test_tag.TestSetTagMixin): # The router to set. _default_route = {'destination': '10.20.20.0/24', 'nexthop': '10.20.30.1'} _network = network_fakes.FakeNetwork.create_one_network() _subnet = network_fakes.FakeSubnet.create_one_subnet() _router = network_fakes.FakeRouter.create_one_router( - attrs={'routes': [_default_route]} + attrs={'routes': [_default_route], + 'tags': ['green', 'red']} ) def setUp(self): super(TestSetRouter, self).setUp() self.network.router_add_gateway = mock.Mock() self.network.update_router = mock.Mock(return_value=None) + self.network.set_tags = mock.Mock(return_value=None) self.network.find_router = mock.Mock(return_value=self._router) self.network.find_network = mock.Mock(return_value=self._network) self.network.find_subnet = mock.Mock(return_value=self._subnet) # Get the command object to test self.cmd = router.SetRouter(self.app, self.namespace) + # TestSetTagMixin + self._tag_resource_name = 'router' + self._tag_test_resource = self._router + self._tag_update_resource_mock = self.network.update_router + def test_set_this(self): arglist = [ self._router.name, @@ -902,9 +939,8 @@ def test_set_nothing(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - attrs = {} - self.network.update_router.assert_called_once_with( - self._router, **attrs) + self.assertFalse(self.network.update_router.called) + self.assertFalse(self.network.set_tags.called) self.assertIsNone(result) def test_wrong_gateway_params(self): @@ -1030,6 +1066,7 @@ class TestShowRouter(TestRouter): 'project_id', 'routes', 'status', + 'tags', ) data = ( router._format_admin_state(_router.admin_state_up), @@ -1044,6 +1081,7 @@ class TestShowRouter(TestRouter): _router.tenant_id, router._format_routes(_router.routes), _router.status, + osc_utils.format_list(_router.tags), ) def setUp(self): @@ -1086,12 +1124,18 @@ def setUp(self): {'routes': [{"destination": "192.168.101.1/24", "nexthop": "172.24.4.3"}, {"destination": "192.168.101.2/24", - "nexthop": "172.24.4.3"}], }) + "nexthop": "172.24.4.3"}], + 'tags': ['green', 'red'], }) self.fake_subnet = network_fakes.FakeSubnet.create_one_subnet() self.network.find_router = mock.Mock(return_value=self._testrouter) self.network.update_router = mock.Mock(return_value=None) + self.network.set_tags = mock.Mock(return_value=None) # Get the command object to test self.cmd = router.UnsetRouter(self.app, self.namespace) + # TestUnsetTagMixin + self._tag_resource_name = 'router' + self._tag_test_resource = self._testrouter + self._tag_update_resource_mock = self.network.update_router def test_unset_router_params(self): arglist = [ diff --git a/openstackclient/tests/unit/network/v2/test_subnet.py b/openstackclient/tests/unit/network/v2/test_subnet.py index 47de5616df..509fbe6b12 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet.py +++ b/openstackclient/tests/unit/network/v2/test_subnet.py @@ -19,6 +19,7 @@ from openstackclient.network.v2 import subnet as subnet_v2 from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3 +from openstackclient.tests.unit.network.v2 import _test_tag from openstackclient.tests.unit.network.v2 import fakes as network_fakes from openstackclient.tests.unit import utils as tests_utils @@ -36,7 +37,7 @@ def setUp(self): self.domains_mock = self.app.client_manager.identity.domains -class TestCreateSubnet(TestSubnet): +class TestCreateSubnet(TestSubnet, _test_tag.TestCreateTagMixin): project = identity_fakes_v3.FakeProject.create_one_project() domain = identity_fakes_v3.FakeDomain.create_one_domain() @@ -125,6 +126,7 @@ class TestCreateSubnet(TestSubnet): 'segment_id', 'service_types', 'subnetpool_id', + 'tags', ) data = ( @@ -145,6 +147,7 @@ class TestCreateSubnet(TestSubnet): _subnet.segment_id, utils.format_list(_subnet.service_types), _subnet.subnetpool_id, + utils.format_list(_subnet.tags), ) data_subnet_pool = ( @@ -165,6 +168,7 @@ class TestCreateSubnet(TestSubnet): _subnet_from_pool.segment_id, utils.format_list(_subnet_from_pool.service_types), _subnet_from_pool.subnetpool_id, + utils.format_list(_subnet.tags), ) data_ipv6 = ( @@ -185,6 +189,7 @@ class TestCreateSubnet(TestSubnet): _subnet_ipv6.segment_id, utils.format_list(_subnet_ipv6.service_types), _subnet_ipv6.subnetpool_id, + utils.format_list(_subnet.tags), ) def setUp(self): @@ -197,6 +202,8 @@ def setUp(self): self.domains_mock.get.return_value = self.domain # Mock SDK calls for all tests. + self.network.create_subnet = mock.Mock(return_value=self._subnet) + self.network.set_tags = mock.Mock(return_value=None) self.network.find_network = mock.Mock(return_value=self._network) self.network.find_segment = mock.Mock( return_value=self._network_segment @@ -205,6 +212,28 @@ def setUp(self): return_value=self._subnet_pool ) + # TestUnsetTagMixin + self._tag_test_resource = self._subnet + self._tag_create_resource_mock = self.network.create_subnet + self._tag_create_required_arglist = [ + "--subnet-range", self._subnet.cidr, + "--network", self._subnet.network_id, + self._subnet.name, + ] + self._tag_create_required_verifylist = [ + ('name', self._subnet.name), + ('subnet_range', self._subnet.cidr), + ('network', self._subnet.network_id), + ('ip_version', self._subnet.ip_version), + ('gateway', 'auto'), + ] + self._tag_create_required_attrs = { + 'cidr': self._subnet.cidr, + 'ip_version': self._subnet.ip_version, + 'name': self._subnet.name, + 'network_id': self._subnet.network_id, + } + def test_create_no_options(self): arglist = [] verifylist = [] @@ -213,10 +242,11 @@ def test_create_no_options(self): # throw a "ParserExecption" self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) + self.assertFalse(self.network.create_subnet.called) + self.assertFalse(self.network.set_tags.called) def test_create_default_options(self): # Mock SDK calls for this test. - self.network.create_subnet = mock.Mock(return_value=self._subnet) self._network.id = self._subnet.network_id arglist = [ @@ -230,7 +260,6 @@ def test_create_default_options(self): ('network', self._subnet.network_id), ('ip_version', self._subnet.ip_version), ('gateway', 'auto'), - ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -242,13 +271,14 @@ def test_create_default_options(self): 'name': self._subnet.name, 'network_id': self._subnet.network_id, }) + self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) def test_create_from_subnet_pool_options(self): # Mock SDK calls for this test. - self.network.create_subnet = \ - mock.Mock(return_value=self._subnet_from_pool) + self.network.create_subnet.return_value = self._subnet_from_pool + self.network.set_tags = mock.Mock(return_value=None) self._network.id = self._subnet_from_pool.network_id arglist = [ @@ -309,7 +339,7 @@ def test_create_from_subnet_pool_options(self): def test_create_options_subnet_range_ipv6(self): # Mock SDK calls for this test. - self.network.create_subnet = mock.Mock(return_value=self._subnet_ipv6) + self.network.create_subnet.return_value = self._subnet_ipv6 self._network.id = self._subnet_ipv6.network_id arglist = [ @@ -376,12 +406,12 @@ def test_create_options_subnet_range_ipv6(self): 'allocation_pools': self._subnet_ipv6.allocation_pools, 'service_types': self._subnet_ipv6.service_types, }) + self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) self.assertEqual(self.data_ipv6, data) def test_create_with_network_segment(self): # Mock SDK calls for this test. - self.network.create_subnet = mock.Mock(return_value=self._subnet) self._network.id = self._subnet.network_id arglist = [ @@ -410,12 +440,12 @@ def test_create_with_network_segment(self): 'network_id': self._subnet.network_id, 'segment_id': self._network_segment.id, }) + self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) def test_create_with_description(self): # Mock SDK calls for this test. - self.network.create_subnet = mock.Mock(return_value=self._subnet) self._network.id = self._subnet.network_id arglist = [ @@ -444,6 +474,7 @@ def test_create_with_description(self): 'network_id': self._subnet.network_id, 'description': self._subnet.description, }) + self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -527,7 +558,7 @@ def test_multi_subnets_delete_with_exception(self): ) -class TestListSubnet(TestSubnet): +class TestListSubnet(TestSubnet, _test_tag.TestListTagMixin): # The subnets going to be listed up. _subnet = network_fakes.FakeSubnet.create_subnets(count=3) @@ -546,6 +577,7 @@ class TestListSubnet(TestSubnet): 'IP Version', 'Gateway', 'Service Types', + 'Tags', ) data = [] @@ -572,6 +604,7 @@ class TestListSubnet(TestSubnet): subnet.ip_version, subnet.gateway_ip, utils.format_list(subnet.service_types), + utils.format_list(subnet.tags), )) def setUp(self): @@ -582,6 +615,9 @@ def setUp(self): self.network.subnets = mock.Mock(return_value=self._subnet) + # TestUnsetTagMixin + self._tag_list_resource_mock = self.network.subnets + def test_subnet_list_no_options(self): arglist = [] verifylist = [ @@ -802,15 +838,21 @@ def test_subnet_list_subnet_range(self): self.assertEqual(self.data, list(data)) -class TestSetSubnet(TestSubnet): +class TestSetSubnet(TestSubnet, _test_tag.TestSetTagMixin): - _subnet = network_fakes.FakeSubnet.create_one_subnet() + _subnet = network_fakes.FakeSubnet.create_one_subnet( + {'tags': ['green', 'red']}) def setUp(self): super(TestSetSubnet, self).setUp() self.network.update_subnet = mock.Mock(return_value=None) + self.network.set_tags = mock.Mock(return_value=None) self.network.find_subnet = mock.Mock(return_value=self._subnet) self.cmd = subnet_v2.SetSubnet(self.app, self.namespace) + # TestSetTagMixin + self._tag_resource_name = 'subnet' + self._tag_test_resource = self._subnet + self._tag_update_resource_mock = self.network.update_subnet def test_set_this(self): arglist = [ @@ -867,8 +909,8 @@ def test_set_nothing(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - attrs = {} - self.network.update_subnet.assert_called_with(self._subnet, **attrs) + self.assertFalse(self.network.update_subnet.called) + self.assertFalse(self.network.set_tags.called) self.assertIsNone(result) def test_append_options(self): @@ -982,6 +1024,7 @@ class TestShowSubnet(TestSubnet): 'segment_id', 'service_types', 'subnetpool_id', + 'tags', ) data = ( @@ -1002,6 +1045,7 @@ class TestShowSubnet(TestSubnet): _subnet.segment_id, utils.format_list(_subnet.service_types), _subnet.subnetpool_id, + utils.format_list(_subnet.tags), ) def setUp(self): @@ -1039,7 +1083,7 @@ def test_show_all_options(self): self.assertEqual(self.data, data) -class TestUnsetSubnet(TestSubnet): +class TestUnsetSubnet(TestSubnet, _test_tag.TestUnsetTagMixin): def setUp(self): super(TestUnsetSubnet, self).setUp() @@ -1055,11 +1099,17 @@ def setUp(self): {'start': '8.8.8.160', 'end': '8.8.8.170'}], 'service_types': ['network:router_gateway', - 'network:floatingip_agent_gateway'], }) + 'network:floatingip_agent_gateway'], + 'tags': ['green', 'red'], }) self.network.find_subnet = mock.Mock(return_value=self._testsubnet) self.network.update_subnet = mock.Mock(return_value=None) + self.network.set_tags = mock.Mock(return_value=None) # Get the command object to test self.cmd = subnet_v2.UnsetSubnet(self.app, self.namespace) + # TestUnsetTagMixin + self._tag_resource_name = 'subnet' + self._tag_test_resource = self._testsubnet + self._tag_update_resource_mock = self.network.update_subnet def test_unset_subnet_params(self): arglist = [ diff --git a/openstackclient/tests/unit/network/v2/test_subnet_pool.py b/openstackclient/tests/unit/network/v2/test_subnet_pool.py index 80a57bbb30..af49385608 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/unit/network/v2/test_subnet_pool.py @@ -20,6 +20,7 @@ from openstackclient.network.v2 import subnet_pool from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3 +from openstackclient.tests.unit.network.v2 import _test_tag from openstackclient.tests.unit.network.v2 import fakes as network_fakes from openstackclient.tests.unit import utils as tests_utils @@ -37,7 +38,7 @@ def setUp(self): self.domains_mock = self.app.client_manager.identity.domains -class TestCreateSubnetPool(TestSubnetPool): +class TestCreateSubnetPool(TestSubnetPool, _test_tag.TestCreateTagMixin): project = identity_fakes_v3.FakeProject.create_one_project() domain = identity_fakes_v3.FakeDomain.create_one_domain() @@ -60,6 +61,7 @@ class TestCreateSubnetPool(TestSubnetPool): 'prefixes', 'project_id', 'shared', + 'tags', ) data = ( _subnet_pool.address_scope_id, @@ -75,6 +77,7 @@ class TestCreateSubnetPool(TestSubnetPool): utils.format_list(_subnet_pool.prefixes), _subnet_pool.project_id, _subnet_pool.shared, + utils.format_list(_subnet_pool.tags), ) def setUp(self): @@ -82,6 +85,7 @@ def setUp(self): self.network.create_subnet_pool = mock.Mock( return_value=self._subnet_pool) + self.network.set_tags = mock.Mock(return_value=None) # Get the command object to test self.cmd = subnet_pool.CreateSubnetPool(self.app, self.namespace) @@ -92,12 +96,29 @@ def setUp(self): self.projects_mock.get.return_value = self.project self.domains_mock.get.return_value = self.domain + # TestUnsetTagMixin + self._tag_test_resource = self._subnet_pool + self._tag_create_resource_mock = self.network.create_subnet_pool + self._tag_create_required_arglist = [ + '--pool-prefix', '10.0.10.0/24', + self._subnet_pool.name, + ] + self._tag_create_required_verifylist = [ + ('prefixes', ['10.0.10.0/24']), + ('name', self._subnet_pool.name), + ] + self._tag_create_required_attrs = { + 'prefixes': ['10.0.10.0/24'], + 'name': self._subnet_pool.name, + } + def test_create_no_options(self): arglist = [] verifylist = [] self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) + self.assertFalse(self.network.set_tags.called) def test_create_no_pool_prefix(self): """Make sure --pool-prefix is a required argument""" @@ -127,6 +148,7 @@ def test_create_default_options(self): 'prefixes': ['10.0.10.0/24'], 'name': self._subnet_pool.name, }) + self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -374,7 +396,7 @@ def test_multi_subnet_pools_delete_with_exception(self): ) -class TestListSubnetPool(TestSubnetPool): +class TestListSubnetPool(TestSubnetPool, _test_tag.TestListTagMixin): # The subnet pools going to be listed up. _subnet_pools = network_fakes.FakeSubnetPool.create_subnet_pools(count=3) @@ -388,6 +410,7 @@ class TestListSubnetPool(TestSubnetPool): 'Address Scope', 'Default Subnet Pool', 'Shared', + 'Tags', ) data = [] @@ -408,6 +431,7 @@ class TestListSubnetPool(TestSubnetPool): pool.address_scope_id, pool.is_default, pool.shared, + utils.format_list(pool.tags), )) def setUp(self): @@ -418,6 +442,9 @@ def setUp(self): self.network.subnet_pools = mock.Mock(return_value=self._subnet_pools) + # TestUnsetTagMixin + self._tag_list_resource_mock = self.network.subnet_pools + def test_subnet_pool_list_no_option(self): arglist = [] verifylist = [ @@ -585,11 +612,12 @@ def test_subnet_pool_list_address_scope(self): self.assertEqual(self.data, list(data)) -class TestSetSubnetPool(TestSubnetPool): +class TestSetSubnetPool(TestSubnetPool, _test_tag.TestSetTagMixin): # The subnet_pool to set. _subnet_pool = network_fakes.FakeSubnetPool.create_one_subnet_pool( - {'default_quota': 10}, + {'default_quota': 10, + 'tags': ['green', 'red']} ) _address_scope = network_fakes.FakeAddressScope.create_one_address_scope() @@ -598,6 +626,7 @@ def setUp(self): super(TestSetSubnetPool, self).setUp() self.network.update_subnet_pool = mock.Mock(return_value=None) + self.network.set_tags = mock.Mock(return_value=None) self.network.find_subnet_pool = mock.Mock( return_value=self._subnet_pool) @@ -608,6 +637,11 @@ def setUp(self): # Get the command object to test self.cmd = subnet_pool.SetSubnetPool(self.app, self.namespace) + # TestUnsetTagMixin + self._tag_resource_name = 'subnet_pool' + self._tag_test_resource = self._subnet_pool + self._tag_update_resource_mock = self.network.update_subnet_pool + def test_set_this(self): arglist = [ '--name', 'noob', @@ -667,9 +701,8 @@ def test_set_nothing(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - attrs = {} - self.network.update_subnet_pool.assert_called_once_with( - self._subnet_pool, **attrs) + self.assertFalse(self.network.update_subnet_pool.called) + self.assertFalse(self.network.set_tags.called) self.assertIsNone(result) def test_set_len_negative(self): @@ -854,6 +887,7 @@ class TestShowSubnetPool(TestSubnetPool): 'prefixes', 'project_id', 'shared', + 'tags', ) data = ( @@ -870,6 +904,7 @@ class TestShowSubnetPool(TestSubnetPool): utils.format_list(_subnet_pool.prefixes), _subnet_pool.tenant_id, _subnet_pool.shared, + utils.format_list(_subnet_pool.tags), ) def setUp(self): @@ -908,26 +943,36 @@ def test_show_all_options(self): self.assertEqual(self.data, data) -class TestUnsetSubnetPool(TestSubnetPool): +class TestUnsetSubnetPool(TestSubnetPool, _test_tag.TestUnsetTagMixin): def setUp(self): super(TestUnsetSubnetPool, self).setUp() self._subnetpool = network_fakes.FakeSubnetPool.create_one_subnet_pool( {'prefixes': ['10.0.10.0/24', '10.1.10.0/24', - '10.2.10.0/24'], }) + '10.2.10.0/24'], + 'tags': ['green', 'red']}) self.network.find_subnet_pool = mock.Mock( return_value=self._subnetpool) self.network.update_subnet_pool = mock.Mock(return_value=None) + self.network.set_tags = mock.Mock(return_value=None) # Get the command object to test self.cmd = subnet_pool.UnsetSubnetPool(self.app, self.namespace) + # TestUnsetTagMixin + self._tag_resource_name = 'subnet_pool' + self._tag_test_resource = self._subnetpool + self._tag_update_resource_mock = self.network.update_subnet_pool + def test_unset_subnet_pool(self): arglist = [ '--pool-prefix', '10.0.10.0/24', '--pool-prefix', '10.1.10.0/24', self._subnetpool.name, ] - verifylist = [('prefixes', ['10.0.10.0/24', '10.1.10.0/24'])] + verifylist = [ + ('prefixes', ['10.0.10.0/24', '10.1.10.0/24']), + ('subnet_pool', self._subnetpool.name), + ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) attrs = {'prefixes': ['10.2.10.0/24']} @@ -940,7 +985,10 @@ def test_unset_subnet_pool_prefix_not_existent(self): '--pool-prefix', '10.100.1.1/25', self._subnetpool.name, ] - verifylist = [('prefixes', ['10.100.1.1/25'])] + verifylist = [ + ('prefixes', ['10.100.1.1/25']), + ('subnet_pool', self._subnetpool.name), + ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.assertRaises(exceptions.CommandError, self.cmd.take_action, diff --git a/openstackclient/tests/unit/utils.py b/openstackclient/tests/unit/utils.py index 3c5c8683f1..8f9cc7b17c 100644 --- a/openstackclient/tests/unit/utils.py +++ b/openstackclient/tests/unit/utils.py @@ -25,6 +25,12 @@ class ParserException(Exception): pass +class CompareBySet(list): + """Class to compare value using set.""" + def __eq__(self, other): + return set(self) == set(other) + + class TestCase(testtools.TestCase): def setUp(self): diff --git a/releasenotes/notes/bp-neutron-client-tag-ff24d13e5c70e052.yaml b/releasenotes/notes/bp-neutron-client-tag-ff24d13e5c70e052.yaml new file mode 100644 index 0000000000..4addd07b7c --- /dev/null +++ b/releasenotes/notes/bp-neutron-client-tag-ff24d13e5c70e052.yaml @@ -0,0 +1,13 @@ +--- +features: + - | + Added support for ``tags`` to the following resources: + ``network``, ``subnet``, ``port``, ``router`` and ``subnet pool``. + [Blueprint :oscbp:`neutron-client-tag`] + + - Add ``--tag`` and ``--no-tag`` options to corresponding "create" commands. + - Add ``--tag`` and ``--no-tag`` options to corresponding "set" commands. + - Add ``--tag`` and ``--all-tag`` options to corresponding "unset" commands. + (``network unset`` command is introduced to support the tag operation) + - Add ``--tags``, ``--any-tags``, ``--not-tags`` and ``--not-any-tags`` + options to corresponding "list" commands. diff --git a/setup.cfg b/setup.cfg index 16917e5099..ec91988f10 100644 --- a/setup.cfg +++ b/setup.cfg @@ -395,6 +395,7 @@ openstack.network.v2 = network_list = openstackclient.network.v2.network:ListNetwork network_set = openstackclient.network.v2.network:SetNetwork network_show = openstackclient.network.v2.network:ShowNetwork + network_unset = openstackclient.network.v2.network:UnsetNetwork network_meter_create = openstackclient.network.v2.network_meter:CreateMeter network_meter_delete = openstackclient.network.v2.network_meter:DeleteMeter From 37998ad1c28144345d4310503b82c5729f1356f9 Mon Sep 17 00:00:00 2001 From: Hangdong Zhang Date: Mon, 24 Jul 2017 11:32:22 +0800 Subject: [PATCH 1747/3095] Update the documentation link for doc migration Change-Id: I7833e40d66abe233af5a211bcefadc141fe8e54b --- HACKING.rst | 2 +- README.rst | 10 +++++----- doc/source/cli/man/openstack.rst | 2 +- doc/source/contributor/command-logs.rst | 2 +- doc/source/contributor/developing.rst | 2 +- doc/source/contributor/plugins.rst | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/HACKING.rst b/HACKING.rst index e6c8d07827..61803e9a4d 100644 --- a/HACKING.rst +++ b/HACKING.rst @@ -2,7 +2,7 @@ OpenStack Style Commandments ============================ - Step 1: Read the OpenStack Style Commandments - http://docs.openstack.org/developer/hacking/ + https://docs.openstack.org/hacking/latest/ - Step 2: Read on General diff --git a/README.rst b/README.rst index deca066980..a8945de235 100644 --- a/README.rst +++ b/README.rst @@ -39,14 +39,14 @@ language to describe operations in OpenStack. * License: Apache 2.0 .. _PyPi: https://pypi.python.org/pypi/python-openstackclient -.. _Online Documentation: http://docs.openstack.org/python-openstackclient/ +.. _Online Documentation: https://docs.openstack.org/python-openstackclient/latest/ .. _Launchpad project: https://launchpad.net/python-openstackclient .. _Blueprints: https://blueprints.launchpad.net/python-openstackclient .. _Bugs: https://bugs.launchpad.net/python-openstackclient .. _Source: https://git.openstack.org/cgit/openstack/python-openstackclient -.. _Developer: http://docs.openstack.org/project-team-guide/project-setup/python.html -.. _Contributing: http://docs.openstack.org/infra/manual/developers.html -.. _Testing: http://docs.openstack.org/python-openstackclient/developing.html#testing +.. _Developer: https://docs.openstack.org/project-team-guide/project-setup/python.html +.. _Contributing: https://docs.openstack.org/infra/manual/developers.html +.. _Testing: https://docs.openstack.org/python-openstackclient/latest/contributor/developing.html#testing Getting Started =============== @@ -79,7 +79,7 @@ Configuration ============= The CLI is configured via environment variables and command-line -options as listed in http://docs.openstack.org/python-openstackclient/authentication.html. +options as listed in https://docs.openstack.org/python-openstackclient/latest/cli/authentication.html. Authentication using username/password is most commonly used:: diff --git a/doc/source/cli/man/openstack.rst b/doc/source/cli/man/openstack.rst index ab990979fc..632ba4be0a 100644 --- a/doc/source/cli/man/openstack.rst +++ b/doc/source/cli/man/openstack.rst @@ -502,7 +502,7 @@ http://www.apache.org/licenses/LICENSE-2.0 SEE ALSO ======== -The `OpenStackClient page `_ +The `OpenStackClient page `_ in the `OpenStack Docs `_ contains further documentation. diff --git a/doc/source/contributor/command-logs.rst b/doc/source/contributor/command-logs.rst index 6212651067..c203d02aee 100644 --- a/doc/source/contributor/command-logs.rst +++ b/doc/source/contributor/command-logs.rst @@ -10,7 +10,7 @@ OpenStack projects. The following basic rules should be followed. 2. All logs except debug log need to be translated. The log message strings that need to be translated should follow the rule of i18n guidelines: - http://docs.openstack.org/developer/oslo.i18n/guidelines.html + https://docs.openstack.org/oslo.i18n/latest/user/guidelines.html 3. There are mainly two kinds of logs in OpenStackClient: command specific log and general log. Use different logger to record them. The examples diff --git a/doc/source/contributor/developing.rst b/doc/source/contributor/developing.rst index 2981ba4d16..721a016a03 100644 --- a/doc/source/contributor/developing.rst +++ b/doc/source/contributor/developing.rst @@ -177,7 +177,7 @@ or Standardize Import Format ========================= -.. _`Import Order Guide`: http://docs.openstack.org/developer/hacking/#imports +.. _`Import Order Guide`: https://docs.openstack.org/hacking/latest/user/hacking.html#imports The import order shows below: diff --git a/doc/source/contributor/plugins.rst b/doc/source/contributor/plugins.rst index fb21a0795a..8bdfe484f0 100644 --- a/doc/source/contributor/plugins.rst +++ b/doc/source/contributor/plugins.rst @@ -243,4 +243,4 @@ Changes to python-openstackclient #. Update ``test-requirements.txt`` to include fooclient. This is necessary to auto-document the commands in the previous step. -.. _sphinxext: http://docs.openstack.org/developer/stevedore/sphinxext.html +.. _sphinxext: https://docs.openstack.org/stevedore/latest/user/sphinxext.html From 925776565e2ab6bf39e9ad4a2ab98dd7f1ce988d Mon Sep 17 00:00:00 2001 From: nidhimittalhada Date: Fri, 16 Jun 2017 17:24:06 +0530 Subject: [PATCH 1748/3095] wrong values in openstack quota show command "openstack quota show" shows wrong value in field project and project_id. project UUID is shown in field project. and project is coming as None. Corrected it. Change-Id: I237e49858feab14ec5e5bc6d8cb79c9f6f5ea9c0 Closes-Bug: #1679906 --- openstackclient/common/quota.py | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index 0d5cb9be31..282ea4284d 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -444,20 +444,30 @@ def _get_project(self, parsed_args): project = utils.find_resource( identity_client.projects, parsed_args.project, - ).id + ) + project_id = project.id + project_name = project.name elif self.app.client_manager.auth_ref: # Get the project from the current auth - project = self.app.client_manager.auth_ref.project_id + project = self.app.client_manager.auth_ref + project_id = project.project_id + project_name = project.project_name else: project = None - return project + project_id = None + project_name = None + project_info = {} + project_info['id'] = project_id + project_info['name'] = project_name + return project_info def get_compute_volume_quota(self, client, parsed_args): try: if parsed_args.quota_class: quota = client.quota_classes.get(parsed_args.project) else: - project = self._get_project(parsed_args) + project_info = self._get_project(parsed_args) + project = project_info['id'] if parsed_args.default: quota = client.quotas.defaults(project) else: @@ -473,7 +483,8 @@ def get_network_quota(self, parsed_args): if parsed_args.quota_class: return {} if self.app.client_manager.is_network_endpoint_enabled(): - project = self._get_project(parsed_args) + project_info = self._get_project(parsed_args) + project = project_info['id'] client = self.app.client_manager.network if parsed_args.default: network_quota = client.get_quota_default(project) @@ -523,5 +534,10 @@ def take_action(self, parsed_args): # Handle project ID special as it only appears in output if 'id' in info: info['project'] = info.pop('id') + if 'project_id' in info: + del info['project_id'] + project_info = self._get_project(parsed_args) + project_name = project_info['name'] + info['project_name'] = project_name return zip(*sorted(six.iteritems(info))) From 78a832441af3928994446d2afab07f0abb0dd26a Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Tue, 25 Jul 2017 09:22:29 +0000 Subject: [PATCH 1749/3095] network tag UT: avoid using mix-in test class This is a follow-up patch on https://review.openstack.org/#/c/461195/ There is a suggestion to keep test code more straight-forward and avoid using mix-in to reduce the code complexity. This commit moves all logic implemented in _test_tag.py into individual network tests. Change-Id: I0a9f8c6cd758db9035b0fd60ce4b9bfc791b6cbd --- .../tests/unit/network/v2/_test_tag.py | 190 ------------------ .../tests/unit/network/v2/test_network.py | 154 +++++++++++--- .../tests/unit/network/v2/test_port.py | 160 ++++++++++++--- .../tests/unit/network/v2/test_router.py | 156 +++++++++++--- .../tests/unit/network/v2/test_subnet.py | 168 ++++++++++++---- .../tests/unit/network/v2/test_subnet_pool.py | 159 +++++++++++---- 6 files changed, 628 insertions(+), 359 deletions(-) delete mode 100644 openstackclient/tests/unit/network/v2/_test_tag.py diff --git a/openstackclient/tests/unit/network/v2/_test_tag.py b/openstackclient/tests/unit/network/v2/_test_tag.py deleted file mode 100644 index bd46153782..0000000000 --- a/openstackclient/tests/unit/network/v2/_test_tag.py +++ /dev/null @@ -1,190 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - -from openstackclient.tests.unit import utils as tests_utils - - -class TestCreateTagMixin(object): - """Test case mixin to test network tag operation for resource creation. - - * Each test class must create a mock for self.network.set_tags - * If you test tag operation combined with other options, - you need to write test(s) directly in individual test cases. - * The following instance attributes must be defined: - - * _tag_test_resource: Test resource returned by mocked create_. - * _tag_create_resource_mock: Mocked create_ method of SDK. - * _tag_create_required_arglist: List of required arguments when creating - a resource with default options. - * _tag_create_required_verifylist: List of expected parsed_args params - when creating a resource with default options. - * _tag_create_required_attrs: Expected attributes passed to a mocked - create_resource method when creating a resource with default options. - """ - - def _test_create_with_tag(self, add_tags=True): - arglist = self._tag_create_required_arglist[:] - if add_tags: - arglist += ['--tag', 'red', '--tag', 'blue'] - else: - arglist += ['--no-tag'] - verifylist = self._tag_create_required_verifylist[:] - if add_tags: - verifylist.append(('tags', ['red', 'blue'])) - else: - verifylist.append(('no_tag', True)) - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = (self.cmd.take_action(parsed_args)) - - self._tag_create_resource_mock.assert_called_once_with( - **self._tag_create_required_attrs) - if add_tags: - self.network.set_tags.assert_called_once_with( - self._tag_test_resource, - tests_utils.CompareBySet(['red', 'blue'])) - else: - self.assertFalse(self.network.set_tags.called) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) - - def test_create_with_tags(self): - self._test_create_with_tag(add_tags=True) - - def test_create_with_no_tag(self): - self._test_create_with_tag(add_tags=False) - - -class TestListTagMixin(object): - """Test case mixin to test network tag operation for resource listing. - - * A test resource returned by find_ must contains - "red" and "green" tags. - * Each test class must create a mock for self.network.set_tags - * If you test tag operation combined with other options, - you need to write test(s) directly in individual test cases. - * The following instance attributes must be defined: - - * _tag_create_resource_mock: Mocked list_ method of SDK. - """ - - def test_list_with_tag_options(self): - arglist = [ - '--tags', 'red,blue', - '--any-tags', 'red,green', - '--not-tags', 'orange,yellow', - '--not-any-tags', 'black,white', - ] - verifylist = [ - ('tags', ['red', 'blue']), - ('any_tags', ['red', 'green']), - ('not_tags', ['orange', 'yellow']), - ('not_any_tags', ['black', 'white']), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) - - self._tag_list_resource_mock.assert_called_once_with( - **{'tags': 'red,blue', - 'any_tags': 'red,green', - 'not_tags': 'orange,yellow', - 'not_any_tags': 'black,white'} - ) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) - - -class TestSetTagMixin(object): - """Test case mixin to test network tag operation for resource update. - - * A test resource returned by find_ must contains - "red" and "green" tags. - * Each test class must create a mock for self.network.set_tags - * If you test tag operation combined with other options, - you need to write test(s) directly in individual test cases. - * The following instance attributes must be defined: - - * _tag_resource_name: positional arg name of a resource to be updated. - * _tag_test_resource: Test resource returned by mocked update_. - * _tag_update_resource_mock: Mocked update_ method of SDK. - """ - - def _test_set_tags(self, with_tags=True): - if with_tags: - arglist = ['--tag', 'red', '--tag', 'blue'] - verifylist = [('tags', ['red', 'blue'])] - expected_args = ['red', 'blue', 'green'] - else: - arglist = ['--no-tag'] - verifylist = [('no_tag', True)] - expected_args = [] - arglist.append(self._tag_test_resource.name) - verifylist.append( - (self._tag_resource_name, self._tag_test_resource.name)) - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) - - self.assertFalse(self._tag_update_resource_mock.called) - self.network.set_tags.assert_called_once_with( - self._tag_test_resource, - tests_utils.CompareBySet(expected_args)) - self.assertIsNone(result) - - def test_set_with_tags(self): - self._test_set_tags(with_tags=True) - - def test_set_with_no_tag(self): - self._test_set_tags(with_tags=False) - - -class TestUnsetTagMixin(object): - """Test case mixin to test network tag operation for resource update. - - * Each test class must create a mock for self.network.set_tags - * If you test tag operation combined with other options, - you need to write test(s) directly in individual test cases. - * The following instance attributes must be defined: - - * _tag_resource_name: positional arg name of a resource to be updated. - * _tag_test_resource: Test resource returned by mocked update_. - * _tag_update_resource_mock: Mocked update_ method of SDK. - """ - - def _test_unset_tags(self, with_tags=True): - if with_tags: - arglist = ['--tag', 'red', '--tag', 'blue'] - verifylist = [('tags', ['red', 'blue'])] - expected_args = ['green'] - else: - arglist = ['--all-tag'] - verifylist = [('all_tag', True)] - expected_args = [] - arglist.append(self._tag_test_resource.name) - verifylist.append( - (self._tag_resource_name, self._tag_test_resource.name)) - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) - - self.assertFalse(self._tag_update_resource_mock.called) - self.network.set_tags.assert_called_once_with( - self._tag_test_resource, - tests_utils.CompareBySet(expected_args)) - self.assertIsNone(result) - - def test_unset_with_tags(self): - self._test_unset_tags(with_tags=True) - - def test_unset_with_all_tag(self): - self._test_unset_tags(with_tags=False) diff --git a/openstackclient/tests/unit/network/v2/test_network.py b/openstackclient/tests/unit/network/v2/test_network.py index e620cd9c1d..7b20c79338 100644 --- a/openstackclient/tests/unit/network/v2/test_network.py +++ b/openstackclient/tests/unit/network/v2/test_network.py @@ -22,7 +22,6 @@ from openstackclient.tests.unit import fakes from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes_v2 from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3 -from openstackclient.tests.unit.network.v2 import _test_tag from openstackclient.tests.unit.network.v2 import fakes as network_fakes from openstackclient.tests.unit import utils as tests_utils @@ -42,7 +41,7 @@ def setUp(self): self.domains_mock = self.app.client_manager.identity.domains -class TestCreateNetworkIdentityV3(TestNetwork, _test_tag.TestCreateTagMixin): +class TestCreateNetworkIdentityV3(TestNetwork): project = identity_fakes_v3.FakeProject.create_one_project() domain = identity_fakes_v3.FakeDomain.create_one_domain() @@ -115,22 +114,6 @@ def setUp(self): self.domains_mock.get.return_value = self.domain self.network.find_qos_policy = mock.Mock(return_value=self.qos_policy) - # TestCreateTagMixin - self._tag_test_resource = self._network - self._tag_create_resource_mock = self.network.create_network - self._tag_create_required_arglist = [self._network.name] - self._tag_create_required_verifylist = [ - ('name', self._network.name), - ('enable', True), - ('share', None), - ('project', None), - ('external', False), - ] - self._tag_create_required_attrs = { - 'admin_state_up': True, - 'name': self._network.name, - } - def test_create_no_options(self): arglist = [] verifylist = [] @@ -247,6 +230,44 @@ def test_create_other_options(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def _test_create_with_tag(self, add_tags=True): + arglist = [self._network.name] + if add_tags: + arglist += ['--tag', 'red', '--tag', 'blue'] + else: + arglist += ['--no-tag'] + verifylist = [ + ('name', self._network.name), + ('enable', True), + ('share', None), + ('project', None), + ('external', False), + ] + if add_tags: + verifylist.append(('tags', ['red', 'blue'])) + else: + verifylist.append(('no_tag', True)) + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_network.assert_called_once_with( + name=self._network.name, admin_state_up=True) + if add_tags: + self.network.set_tags.assert_called_once_with( + self._network, + tests_utils.CompareBySet(['red', 'blue'])) + else: + self.assertFalse(self.network.set_tags.called) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_with_tags(self): + self._test_create_with_tag(add_tags=True) + + def test_create_with_no_tag(self): + self._test_create_with_tag(add_tags=False) + class TestCreateNetworkIdentityV2(TestNetwork): @@ -461,7 +482,7 @@ def test_delete_multiple_networks_exception(self): self.network.delete_network.assert_has_calls(calls) -class TestListNetwork(TestNetwork, _test_tag.TestListTagMixin): +class TestListNetwork(TestNetwork): # The networks going to be listed up. _network = network_fakes.FakeNetwork.create_networks(count=3) @@ -820,8 +841,33 @@ def test_network_list_dhcp_agent(self): self.assertEqual(self.columns, columns) self.assertEqual(list(data), list(self.data)) + def test_list_with_tag_options(self): + arglist = [ + '--tags', 'red,blue', + '--any-tags', 'red,green', + '--not-tags', 'orange,yellow', + '--not-any-tags', 'black,white', + ] + verifylist = [ + ('tags', ['red', 'blue']), + ('any_tags', ['red', 'green']), + ('not_tags', ['orange', 'yellow']), + ('not_any_tags', ['black', 'white']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.networks.assert_called_once_with( + **{'tags': 'red,blue', + 'any_tags': 'red,green', + 'not_tags': 'orange,yellow', + 'not_any_tags': 'black,white'} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) -class TestSetNetwork(TestNetwork, _test_tag.TestSetTagMixin): + +class TestSetNetwork(TestNetwork): # The network to set. _network = network_fakes.FakeNetwork.create_one_network( @@ -841,11 +887,6 @@ def setUp(self): # Get the command object to test self.cmd = network.SetNetwork(self.app, self.namespace) - # TestSetTagMixin - self._tag_resource_name = 'network' - self._tag_test_resource = self._network - self._tag_update_resource_mock = self.network.update_network - def test_set_this(self): arglist = [ self._network.name, @@ -939,6 +980,34 @@ def test_set_nothing(self): self.assertFalse(self.network.set_tags.called) self.assertIsNone(result) + def _test_set_tags(self, with_tags=True): + if with_tags: + arglist = ['--tag', 'red', '--tag', 'blue'] + verifylist = [('tags', ['red', 'blue'])] + expected_args = ['red', 'blue', 'green'] + else: + arglist = ['--no-tag'] + verifylist = [('no_tag', True)] + expected_args = [] + arglist.append(self._network.name) + verifylist.append( + ('network', self._network.name)) + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.assertFalse(self.network.update_network.called) + self.network.set_tags.assert_called_once_with( + self._network, + tests_utils.CompareBySet(expected_args)) + self.assertIsNone(result) + + def test_set_with_tags(self): + self._test_set_tags(with_tags=True) + + def test_set_with_no_tag(self): + self._test_set_tags(with_tags=False) + class TestShowNetwork(TestNetwork): @@ -1024,7 +1093,7 @@ def test_show_all_options(self): self.assertEqual(self.data, data) -class TestUnsetNetwork(TestNetwork, _test_tag.TestUnsetTagMixin): +class TestUnsetNetwork(TestNetwork): # The network to set. _network = network_fakes.FakeNetwork.create_one_network( @@ -1044,11 +1113,6 @@ def setUp(self): # Get the command object to test self.cmd = network.UnsetNetwork(self.app, self.namespace) - # TestUnsetNetwork - self._tag_resource_name = 'network' - self._tag_test_resource = self._network - self._tag_update_resource_mock = self.network.update_network - def test_unset_nothing(self): arglist = [self._network.name, ] verifylist = [('network', self._network.name), ] @@ -1059,3 +1123,31 @@ def test_unset_nothing(self): self.assertFalse(self.network.update_network.called) self.assertFalse(self.network.set_tags.called) self.assertIsNone(result) + + def _test_unset_tags(self, with_tags=True): + if with_tags: + arglist = ['--tag', 'red', '--tag', 'blue'] + verifylist = [('tags', ['red', 'blue'])] + expected_args = ['green'] + else: + arglist = ['--all-tag'] + verifylist = [('all_tag', True)] + expected_args = [] + arglist.append(self._network.name) + verifylist.append( + ('network', self._network.name)) + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.assertFalse(self.network.update_network.called) + self.network.set_tags.assert_called_once_with( + self._network, + tests_utils.CompareBySet(expected_args)) + self.assertIsNone(result) + + def test_unset_with_tags(self): + self._test_unset_tags(with_tags=True) + + def test_unset_with_all_tag(self): + self._test_unset_tags(with_tags=False) diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index deda6b4128..97be5afd94 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -21,7 +21,6 @@ from openstackclient.network.v2 import port from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes -from openstackclient.tests.unit.network.v2 import _test_tag from openstackclient.tests.unit.network.v2 import fakes as network_fakes from openstackclient.tests.unit import utils as tests_utils @@ -97,7 +96,7 @@ def _get_common_cols_data(fake_port): return columns, data -class TestCreatePort(TestPort, _test_tag.TestCreateTagMixin): +class TestCreatePort(TestPort): _port = network_fakes.FakePort.create_one_port() columns, data = TestPort._get_common_cols_data(_port) @@ -116,24 +115,6 @@ def setUp(self): # Get the command object to test self.cmd = port.CreatePort(self.app, self.namespace) - # TestUnsetTagMixin - self._tag_test_resource = self._port - self._tag_create_resource_mock = self.network.create_port - self._tag_create_required_arglist = [ - '--network', self._port.network_id, - 'test-port', - ] - self._tag_create_required_verifylist = [ - ('network', self._port.network_id,), - ('enable', True), - ('name', 'test-port'), - ] - self._tag_create_required_attrs = { - 'admin_state_up': True, - 'network_id': self._port.network_id, - 'name': 'test-port', - } - def test_create_default_options(self): arglist = [ '--network', self._port.network_id, @@ -517,6 +498,48 @@ def test_create_port_security_disabled(self): 'name': 'test-port', }) + def _test_create_with_tag(self, add_tags=True): + arglist = [ + '--network', self._port.network_id, + 'test-port', + ] + if add_tags: + arglist += ['--tag', 'red', '--tag', 'blue'] + else: + arglist += ['--no-tag'] + verifylist = [ + ('network', self._port.network_id,), + ('enable', True), + ('name', 'test-port'), + ] + if add_tags: + verifylist.append(('tags', ['red', 'blue'])) + else: + verifylist.append(('no_tag', True)) + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_port.assert_called_once_with( + admin_state_up=True, + network_id=self._port.network_id, + name='test-port' + ) + if add_tags: + self.network.set_tags.assert_called_once_with( + self._port, + tests_utils.CompareBySet(['red', 'blue'])) + else: + self.assertFalse(self.network.set_tags.called) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_with_tags(self): + self._test_create_with_tag(add_tags=True) + + def test_create_with_no_tag(self): + self._test_create_with_tag(add_tags=False) + class TestDeletePort(TestPort): @@ -597,7 +620,7 @@ def test_multi_ports_delete_with_exception(self): ) -class TestListPort(TestPort, _test_tag.TestListTagMixin): +class TestListPort(TestPort): _ports = network_fakes.FakePort.create_ports(count=3) @@ -658,8 +681,6 @@ def setUp(self): self.network.find_router = mock.Mock(return_value=fake_router) self.network.find_network = mock.Mock(return_value=fake_network) self.app.client_manager.compute = mock.Mock() - # TestUnsetTagMixin - self._tag_list_resource_mock = self.network.ports def test_port_list_no_options(self): arglist = [] @@ -919,8 +940,33 @@ def test_port_list_project_domain(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + def test_list_with_tag_options(self): + arglist = [ + '--tags', 'red,blue', + '--any-tags', 'red,green', + '--not-tags', 'orange,yellow', + '--not-any-tags', 'black,white', + ] + verifylist = [ + ('tags', ['red', 'blue']), + ('any_tags', ['red', 'green']), + ('not_tags', ['orange', 'yellow']), + ('not_any_tags', ['black', 'white']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) -class TestSetPort(TestPort, _test_tag.TestSetTagMixin): + self.network.ports.assert_called_once_with( + **{'tags': 'red,blue', + 'any_tags': 'red,green', + 'not_tags': 'orange,yellow', + 'not_any_tags': 'black,white'} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + +class TestSetPort(TestPort): _port = network_fakes.FakePort.create_one_port({'tags': ['green', 'red']}) @@ -934,10 +980,6 @@ def setUp(self): # Get the command object to test self.cmd = port.SetPort(self.app, self.namespace) - # TestSetTagMixin - self._tag_resource_name = 'port' - self._tag_test_resource = self._port - self._tag_update_resource_mock = self.network.update_port def test_set_port_defaults(self): arglist = [ @@ -1430,6 +1472,34 @@ def test_set_port_invalid_data_plane_status_value(self): arglist, None) + def _test_set_tags(self, with_tags=True): + if with_tags: + arglist = ['--tag', 'red', '--tag', 'blue'] + verifylist = [('tags', ['red', 'blue'])] + expected_args = ['red', 'blue', 'green'] + else: + arglist = ['--no-tag'] + verifylist = [('no_tag', True)] + expected_args = [] + arglist.append(self._port.name) + verifylist.append( + ('port', self._port.name)) + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.assertFalse(self.network.update_port.called) + self.network.set_tags.assert_called_once_with( + self._port, + tests_utils.CompareBySet(expected_args)) + self.assertIsNone(result) + + def test_set_with_tags(self): + self._test_set_tags(with_tags=True) + + def test_set_with_no_tag(self): + self._test_set_tags(with_tags=False) + class TestShowPort(TestPort): @@ -1470,7 +1540,7 @@ def test_show_all_options(self): self.assertEqual(self.data, data) -class TestUnsetPort(TestPort, _test_tag.TestUnsetTagMixin): +class TestUnsetPort(TestPort): def setUp(self): super(TestUnsetPort, self).setUp() @@ -1489,10 +1559,6 @@ def setUp(self): self.network.set_tags = mock.Mock(return_value=None) # Get the command object to test self.cmd = port.UnsetPort(self.app, self.namespace) - # TestUnsetTagMixin - self._tag_resource_name = 'port' - self._tag_test_resource = self._testport - self._tag_update_resource_mock = self.network.update_port def test_unset_port_parameters(self): arglist = [ @@ -1661,3 +1727,31 @@ def test_unset_port_data_plane_status(self): self.network.update_port.assert_called_once_with(_fake_port, **attrs) self.assertIsNone(result) + + def _test_unset_tags(self, with_tags=True): + if with_tags: + arglist = ['--tag', 'red', '--tag', 'blue'] + verifylist = [('tags', ['red', 'blue'])] + expected_args = ['green'] + else: + arglist = ['--all-tag'] + verifylist = [('all_tag', True)] + expected_args = [] + arglist.append(self._testport.name) + verifylist.append( + ('port', self._testport.name)) + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.assertFalse(self.network.update_port.called) + self.network.set_tags.assert_called_once_with( + self._testport, + tests_utils.CompareBySet(expected_args)) + self.assertIsNone(result) + + def test_unset_with_tags(self): + self._test_unset_tags(with_tags=True) + + def test_unset_with_all_tag(self): + self._test_unset_tags(with_tags=False) diff --git a/openstackclient/tests/unit/network/v2/test_router.py b/openstackclient/tests/unit/network/v2/test_router.py index d65c9aa9f7..2248db9a9c 100644 --- a/openstackclient/tests/unit/network/v2/test_router.py +++ b/openstackclient/tests/unit/network/v2/test_router.py @@ -19,7 +19,6 @@ from openstackclient.network.v2 import router from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3 -from openstackclient.tests.unit.network.v2 import _test_tag from openstackclient.tests.unit.network.v2 import fakes as network_fakes from openstackclient.tests.unit import utils as tests_utils @@ -112,7 +111,7 @@ def test_add_subnet_required_options(self): self.assertIsNone(result) -class TestCreateRouter(TestRouter, _test_tag.TestCreateTagMixin): +class TestCreateRouter(TestRouter): # The new router created. new_router = network_fakes.FakeRouter.create_one_router() @@ -157,23 +156,6 @@ def setUp(self): # Get the command object to test self.cmd = router.CreateRouter(self.app, self.namespace) - # TestCreateTagMixin - self._tag_test_resource = self.new_router - self._tag_create_resource_mock = self.network.create_router - self._tag_create_required_arglist = [ - self.new_router.name, - ] - self._tag_create_required_verifylist = [ - ('name', self.new_router.name), - ('enable', True), - ('distributed', False), - ('ha', False), - ] - self._tag_create_required_attrs = { - 'admin_state_up': True, - 'name': self.new_router.name, - } - def test_create_no_options(self): arglist = [] verifylist = [] @@ -288,6 +270,45 @@ def test_create_with_AZ_hints(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def _test_create_with_tag(self, add_tags=True): + arglist = [self.new_router.name] + if add_tags: + arglist += ['--tag', 'red', '--tag', 'blue'] + else: + arglist += ['--no-tag'] + verifylist = [ + ('name', self.new_router.name), + ('enable', True), + ('distributed', False), + ('ha', False), + ] + if add_tags: + verifylist.append(('tags', ['red', 'blue'])) + else: + verifylist.append(('no_tag', True)) + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_router.assert_called_once_with( + name=self.new_router.name, + admin_state_up=True + ) + if add_tags: + self.network.set_tags.assert_called_once_with( + self.new_router, + tests_utils.CompareBySet(['red', 'blue'])) + else: + self.assertFalse(self.network.set_tags.called) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_with_tags(self): + self._test_create_with_tag(add_tags=True) + + def test_create_with_no_tag(self): + self._test_create_with_tag(add_tags=False) + class TestDeleteRouter(TestRouter): @@ -368,7 +389,7 @@ def test_multi_routers_delete_with_exception(self): ) -class TestListRouter(TestRouter, _test_tag.TestListTagMixin): +class TestListRouter(TestRouter): # The routers going to be listed up. routers = network_fakes.FakeRouter.create_routers(count=3) @@ -459,9 +480,6 @@ def setUp(self): self.network.get_agent = mock.Mock(return_value=self._testagent) self.network.get_router = mock.Mock(return_value=self.routers[0]) - # TestListTagMixin - self._tag_list_resource_mock = self.network.routers - def test_router_list_no_options(self): arglist = [] verifylist = [ @@ -636,6 +654,31 @@ def test_router_list_agents(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + def test_list_with_tag_options(self): + arglist = [ + '--tags', 'red,blue', + '--any-tags', 'red,green', + '--not-tags', 'orange,yellow', + '--not-any-tags', 'black,white', + ] + verifylist = [ + ('tags', ['red', 'blue']), + ('any_tags', ['red', 'green']), + ('not_tags', ['orange', 'yellow']), + ('not_any_tags', ['black', 'white']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.routers.assert_called_once_with( + **{'tags': 'red,blue', + 'any_tags': 'red,green', + 'not_tags': 'orange,yellow', + 'not_any_tags': 'black,white'} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + class TestRemovePortFromRouter(TestRouter): '''Remove port from a Router ''' @@ -714,7 +757,7 @@ def test_remove_subnet_required_options(self): self.assertIsNone(result) -class TestSetRouter(TestRouter, _test_tag.TestSetTagMixin): +class TestSetRouter(TestRouter): # The router to set. _default_route = {'destination': '10.20.20.0/24', 'nexthop': '10.20.30.1'} @@ -736,11 +779,6 @@ def setUp(self): # Get the command object to test self.cmd = router.SetRouter(self.app, self.namespace) - # TestSetTagMixin - self._tag_resource_name = 'router' - self._tag_test_resource = self._router - self._tag_update_resource_mock = self.network.update_router - def test_set_this(self): arglist = [ self._router.name, @@ -1047,6 +1085,34 @@ def test_set_gateway_options_subnet_ipaddress(self): 'enable_snat': True, }}) self.assertIsNone(result) + def _test_set_tags(self, with_tags=True): + if with_tags: + arglist = ['--tag', 'red', '--tag', 'blue'] + verifylist = [('tags', ['red', 'blue'])] + expected_args = ['red', 'blue', 'green'] + else: + arglist = ['--no-tag'] + verifylist = [('no_tag', True)] + expected_args = [] + arglist.append(self._router.name) + verifylist.append( + ('router', self._router.name)) + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.assertFalse(self.network.update_router.called) + self.network.set_tags.assert_called_once_with( + self._router, + tests_utils.CompareBySet(expected_args)) + self.assertIsNone(result) + + def test_set_with_tags(self): + self._test_set_tags(with_tags=True) + + def test_set_with_no_tag(self): + self._test_set_tags(with_tags=False) + class TestShowRouter(TestRouter): @@ -1132,10 +1198,6 @@ def setUp(self): self.network.set_tags = mock.Mock(return_value=None) # Get the command object to test self.cmd = router.UnsetRouter(self.app, self.namespace) - # TestUnsetTagMixin - self._tag_resource_name = 'router' - self._tag_test_resource = self._testrouter - self._tag_update_resource_mock = self.network.update_router def test_unset_router_params(self): arglist = [ @@ -1184,3 +1246,31 @@ def test_unset_router_external_gateway(self): self.network.update_router.assert_called_once_with( self._testrouter, **attrs) self.assertIsNone(result) + + def _test_unset_tags(self, with_tags=True): + if with_tags: + arglist = ['--tag', 'red', '--tag', 'blue'] + verifylist = [('tags', ['red', 'blue'])] + expected_args = ['green'] + else: + arglist = ['--all-tag'] + verifylist = [('all_tag', True)] + expected_args = [] + arglist.append(self._testrouter.name) + verifylist.append( + ('router', self._testrouter.name)) + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.assertFalse(self.network.update_router.called) + self.network.set_tags.assert_called_once_with( + self._testrouter, + tests_utils.CompareBySet(expected_args)) + self.assertIsNone(result) + + def test_unset_with_tags(self): + self._test_unset_tags(with_tags=True) + + def test_unset_with_all_tag(self): + self._test_unset_tags(with_tags=False) diff --git a/openstackclient/tests/unit/network/v2/test_subnet.py b/openstackclient/tests/unit/network/v2/test_subnet.py index 509fbe6b12..c96d680fba 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet.py +++ b/openstackclient/tests/unit/network/v2/test_subnet.py @@ -19,7 +19,6 @@ from openstackclient.network.v2 import subnet as subnet_v2 from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3 -from openstackclient.tests.unit.network.v2 import _test_tag from openstackclient.tests.unit.network.v2 import fakes as network_fakes from openstackclient.tests.unit import utils as tests_utils @@ -37,7 +36,7 @@ def setUp(self): self.domains_mock = self.app.client_manager.identity.domains -class TestCreateSubnet(TestSubnet, _test_tag.TestCreateTagMixin): +class TestCreateSubnet(TestSubnet): project = identity_fakes_v3.FakeProject.create_one_project() domain = identity_fakes_v3.FakeDomain.create_one_domain() @@ -212,28 +211,6 @@ def setUp(self): return_value=self._subnet_pool ) - # TestUnsetTagMixin - self._tag_test_resource = self._subnet - self._tag_create_resource_mock = self.network.create_subnet - self._tag_create_required_arglist = [ - "--subnet-range", self._subnet.cidr, - "--network", self._subnet.network_id, - self._subnet.name, - ] - self._tag_create_required_verifylist = [ - ('name', self._subnet.name), - ('subnet_range', self._subnet.cidr), - ('network', self._subnet.network_id), - ('ip_version', self._subnet.ip_version), - ('gateway', 'auto'), - ] - self._tag_create_required_attrs = { - 'cidr': self._subnet.cidr, - 'ip_version': self._subnet.ip_version, - 'name': self._subnet.name, - 'network_id': self._subnet.network_id, - } - def test_create_no_options(self): arglist = [] verifylist = [] @@ -478,6 +455,51 @@ def test_create_with_description(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def _test_create_with_tag(self, add_tags=True): + arglist = [ + "--subnet-range", self._subnet.cidr, + "--network", self._subnet.network_id, + self._subnet.name, + ] + if add_tags: + arglist += ['--tag', 'red', '--tag', 'blue'] + else: + arglist += ['--no-tag'] + verifylist = [ + ('name', self._subnet.name), + ('subnet_range', self._subnet.cidr), + ('network', self._subnet.network_id), + ('ip_version', self._subnet.ip_version), + ('gateway', 'auto'), + ] + if add_tags: + verifylist.append(('tags', ['red', 'blue'])) + else: + verifylist.append(('no_tag', True)) + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_subnet.assert_called_once_with( + cidr=self._subnet.cidr, + ip_version=self._subnet.ip_version, + name=self._subnet.name, + network_id=self._subnet.network_id) + if add_tags: + self.network.set_tags.assert_called_once_with( + self._subnet, + tests_utils.CompareBySet(['red', 'blue'])) + else: + self.assertFalse(self.network.set_tags.called) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_with_tags(self): + self._test_create_with_tag(add_tags=True) + + def test_create_with_no_tag(self): + self._test_create_with_tag(add_tags=False) + class TestDeleteSubnet(TestSubnet): @@ -558,7 +580,7 @@ def test_multi_subnets_delete_with_exception(self): ) -class TestListSubnet(TestSubnet, _test_tag.TestListTagMixin): +class TestListSubnet(TestSubnet): # The subnets going to be listed up. _subnet = network_fakes.FakeSubnet.create_subnets(count=3) @@ -615,9 +637,6 @@ def setUp(self): self.network.subnets = mock.Mock(return_value=self._subnet) - # TestUnsetTagMixin - self._tag_list_resource_mock = self.network.subnets - def test_subnet_list_no_options(self): arglist = [] verifylist = [ @@ -837,8 +856,33 @@ def test_subnet_list_subnet_range(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + def test_list_with_tag_options(self): + arglist = [ + '--tags', 'red,blue', + '--any-tags', 'red,green', + '--not-tags', 'orange,yellow', + '--not-any-tags', 'black,white', + ] + verifylist = [ + ('tags', ['red', 'blue']), + ('any_tags', ['red', 'green']), + ('not_tags', ['orange', 'yellow']), + ('not_any_tags', ['black', 'white']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.subnets.assert_called_once_with( + **{'tags': 'red,blue', + 'any_tags': 'red,green', + 'not_tags': 'orange,yellow', + 'not_any_tags': 'black,white'} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + -class TestSetSubnet(TestSubnet, _test_tag.TestSetTagMixin): +class TestSetSubnet(TestSubnet): _subnet = network_fakes.FakeSubnet.create_one_subnet( {'tags': ['green', 'red']}) @@ -849,10 +893,6 @@ def setUp(self): self.network.set_tags = mock.Mock(return_value=None) self.network.find_subnet = mock.Mock(return_value=self._subnet) self.cmd = subnet_v2.SetSubnet(self.app, self.namespace) - # TestSetTagMixin - self._tag_resource_name = 'subnet' - self._tag_test_resource = self._subnet - self._tag_update_resource_mock = self.network.update_subnet def test_set_this(self): arglist = [ @@ -1001,6 +1041,34 @@ def test_overwrite_options(self): _testsubnet, **attrs) self.assertIsNone(result) + def _test_set_tags(self, with_tags=True): + if with_tags: + arglist = ['--tag', 'red', '--tag', 'blue'] + verifylist = [('tags', ['red', 'blue'])] + expected_args = ['red', 'blue', 'green'] + else: + arglist = ['--no-tag'] + verifylist = [('no_tag', True)] + expected_args = [] + arglist.append(self._subnet.name) + verifylist.append( + ('subnet', self._subnet.name)) + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.assertFalse(self.network.update_subnet.called) + self.network.set_tags.assert_called_once_with( + self._subnet, + tests_utils.CompareBySet(expected_args)) + self.assertIsNone(result) + + def test_set_with_tags(self): + self._test_set_tags(with_tags=True) + + def test_set_with_no_tag(self): + self._test_set_tags(with_tags=False) + class TestShowSubnet(TestSubnet): # The subnets to be shown @@ -1083,7 +1151,7 @@ def test_show_all_options(self): self.assertEqual(self.data, data) -class TestUnsetSubnet(TestSubnet, _test_tag.TestUnsetTagMixin): +class TestUnsetSubnet(TestSubnet): def setUp(self): super(TestUnsetSubnet, self).setUp() @@ -1106,10 +1174,6 @@ def setUp(self): self.network.set_tags = mock.Mock(return_value=None) # Get the command object to test self.cmd = subnet_v2.UnsetSubnet(self.app, self.namespace) - # TestUnsetTagMixin - self._tag_resource_name = 'subnet' - self._tag_test_resource = self._testsubnet - self._tag_update_resource_mock = self.network.update_subnet def test_unset_subnet_params(self): arglist = [ @@ -1219,3 +1283,31 @@ def test_unset_subnet_wrong_service_type(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) + + def _test_unset_tags(self, with_tags=True): + if with_tags: + arglist = ['--tag', 'red', '--tag', 'blue'] + verifylist = [('tags', ['red', 'blue'])] + expected_args = ['green'] + else: + arglist = ['--all-tag'] + verifylist = [('all_tag', True)] + expected_args = [] + arglist.append(self._testsubnet.name) + verifylist.append( + ('subnet', self._testsubnet.name)) + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.assertFalse(self.network.update_subnet.called) + self.network.set_tags.assert_called_once_with( + self._testsubnet, + tests_utils.CompareBySet(expected_args)) + self.assertIsNone(result) + + def test_unset_with_tags(self): + self._test_unset_tags(with_tags=True) + + def test_unset_with_all_tag(self): + self._test_unset_tags(with_tags=False) diff --git a/openstackclient/tests/unit/network/v2/test_subnet_pool.py b/openstackclient/tests/unit/network/v2/test_subnet_pool.py index af49385608..139fddf887 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/unit/network/v2/test_subnet_pool.py @@ -20,7 +20,6 @@ from openstackclient.network.v2 import subnet_pool from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3 -from openstackclient.tests.unit.network.v2 import _test_tag from openstackclient.tests.unit.network.v2 import fakes as network_fakes from openstackclient.tests.unit import utils as tests_utils @@ -38,7 +37,7 @@ def setUp(self): self.domains_mock = self.app.client_manager.identity.domains -class TestCreateSubnetPool(TestSubnetPool, _test_tag.TestCreateTagMixin): +class TestCreateSubnetPool(TestSubnetPool): project = identity_fakes_v3.FakeProject.create_one_project() domain = identity_fakes_v3.FakeDomain.create_one_domain() @@ -96,22 +95,6 @@ def setUp(self): self.projects_mock.get.return_value = self.project self.domains_mock.get.return_value = self.domain - # TestUnsetTagMixin - self._tag_test_resource = self._subnet_pool - self._tag_create_resource_mock = self.network.create_subnet_pool - self._tag_create_required_arglist = [ - '--pool-prefix', '10.0.10.0/24', - self._subnet_pool.name, - ] - self._tag_create_required_verifylist = [ - ('prefixes', ['10.0.10.0/24']), - ('name', self._subnet_pool.name), - ] - self._tag_create_required_attrs = { - 'prefixes': ['10.0.10.0/24'], - 'name': self._subnet_pool.name, - } - def test_create_no_options(self): arglist = [] verifylist = [] @@ -313,6 +296,46 @@ def test_create_with_default_quota(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def _test_create_with_tag(self, add_tags=True): + arglist = [ + '--pool-prefix', '10.0.10.0/24', + self._subnet_pool.name, + ] + if add_tags: + arglist += ['--tag', 'red', '--tag', 'blue'] + else: + arglist += ['--no-tag'] + verifylist = [ + ('prefixes', ['10.0.10.0/24']), + ('name', self._subnet_pool.name), + ] + if add_tags: + verifylist.append(('tags', ['red', 'blue'])) + else: + verifylist.append(('no_tag', True)) + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_subnet_pool.assert_called_once_with( + prefixes=['10.0.10.0/24'], + name=self._subnet_pool.name + ) + if add_tags: + self.network.set_tags.assert_called_once_with( + self._subnet_pool, + tests_utils.CompareBySet(['red', 'blue'])) + else: + self.assertFalse(self.network.set_tags.called) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_with_tags(self): + self._test_create_with_tag(add_tags=True) + + def test_create_with_no_tag(self): + self._test_create_with_tag(add_tags=False) + class TestDeleteSubnetPool(TestSubnetPool): @@ -396,7 +419,7 @@ def test_multi_subnet_pools_delete_with_exception(self): ) -class TestListSubnetPool(TestSubnetPool, _test_tag.TestListTagMixin): +class TestListSubnetPool(TestSubnetPool): # The subnet pools going to be listed up. _subnet_pools = network_fakes.FakeSubnetPool.create_subnet_pools(count=3) @@ -442,9 +465,6 @@ def setUp(self): self.network.subnet_pools = mock.Mock(return_value=self._subnet_pools) - # TestUnsetTagMixin - self._tag_list_resource_mock = self.network.subnet_pools - def test_subnet_pool_list_no_option(self): arglist = [] verifylist = [ @@ -611,8 +631,33 @@ def test_subnet_pool_list_address_scope(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + def test_list_with_tag_options(self): + arglist = [ + '--tags', 'red,blue', + '--any-tags', 'red,green', + '--not-tags', 'orange,yellow', + '--not-any-tags', 'black,white', + ] + verifylist = [ + ('tags', ['red', 'blue']), + ('any_tags', ['red', 'green']), + ('not_tags', ['orange', 'yellow']), + ('not_any_tags', ['black', 'white']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.subnet_pools.assert_called_once_with( + **{'tags': 'red,blue', + 'any_tags': 'red,green', + 'not_tags': 'orange,yellow', + 'not_any_tags': 'black,white'} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) -class TestSetSubnetPool(TestSubnetPool, _test_tag.TestSetTagMixin): + +class TestSetSubnetPool(TestSubnetPool): # The subnet_pool to set. _subnet_pool = network_fakes.FakeSubnetPool.create_one_subnet_pool( @@ -637,11 +682,6 @@ def setUp(self): # Get the command object to test self.cmd = subnet_pool.SetSubnetPool(self.app, self.namespace) - # TestUnsetTagMixin - self._tag_resource_name = 'subnet_pool' - self._tag_test_resource = self._subnet_pool - self._tag_update_resource_mock = self.network.update_subnet_pool - def test_set_this(self): arglist = [ '--name', 'noob', @@ -867,6 +907,34 @@ def test_set_with_default_quota(self): ) self.assertIsNone(result) + def _test_set_tags(self, with_tags=True): + if with_tags: + arglist = ['--tag', 'red', '--tag', 'blue'] + verifylist = [('tags', ['red', 'blue'])] + expected_args = ['red', 'blue', 'green'] + else: + arglist = ['--no-tag'] + verifylist = [('no_tag', True)] + expected_args = [] + arglist.append(self._subnet_pool.name) + verifylist.append( + ('subnet_pool', self._subnet_pool.name)) + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.assertFalse(self.network.update_subnet_pool.called) + self.network.set_tags.assert_called_once_with( + self._subnet_pool, + tests_utils.CompareBySet(expected_args)) + self.assertIsNone(result) + + def test_set_with_tags(self): + self._test_set_tags(with_tags=True) + + def test_set_with_no_tag(self): + self._test_set_tags(with_tags=False) + class TestShowSubnetPool(TestSubnetPool): @@ -943,7 +1011,7 @@ def test_show_all_options(self): self.assertEqual(self.data, data) -class TestUnsetSubnetPool(TestSubnetPool, _test_tag.TestUnsetTagMixin): +class TestUnsetSubnetPool(TestSubnetPool): def setUp(self): super(TestUnsetSubnetPool, self).setUp() @@ -958,11 +1026,6 @@ def setUp(self): # Get the command object to test self.cmd = subnet_pool.UnsetSubnetPool(self.app, self.namespace) - # TestUnsetTagMixin - self._tag_resource_name = 'subnet_pool' - self._tag_test_resource = self._subnetpool - self._tag_update_resource_mock = self.network.update_subnet_pool - def test_unset_subnet_pool(self): arglist = [ '--pool-prefix', '10.0.10.0/24', @@ -993,3 +1056,31 @@ def test_unset_subnet_pool_prefix_not_existent(self): self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) + + def _test_unset_tags(self, with_tags=True): + if with_tags: + arglist = ['--tag', 'red', '--tag', 'blue'] + verifylist = [('tags', ['red', 'blue'])] + expected_args = ['green'] + else: + arglist = ['--all-tag'] + verifylist = [('all_tag', True)] + expected_args = [] + arglist.append(self._subnetpool.name) + verifylist.append( + ('subnet_pool', self._subnetpool.name)) + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.assertFalse(self.network.update_subnet_pool.called) + self.network.set_tags.assert_called_once_with( + self._subnetpool, + tests_utils.CompareBySet(expected_args)) + self.assertIsNone(result) + + def test_unset_with_tags(self): + self._test_unset_tags(with_tags=True) + + def test_unset_with_all_tag(self): + self._test_unset_tags(with_tags=False) From 470a1f1acfe261357fc3125b2db3bc6ec10c654e Mon Sep 17 00:00:00 2001 From: zhanghongtao Date: Tue, 25 Jul 2017 15:48:27 +0800 Subject: [PATCH 1750/3095] Add optional parameter "user_id" and "type" to list credentials In keystone version 3.5, "type" optional attribute has been added to list credentials. This patch add "user_id" and "type" optional parameter in openstack client. Change-Id: Ia09ee7c39204fdff2dfd7b9b606d888d007caac5 --- doc/source/cli/command-objects/credential.rst | 19 +++++++++- openstackclient/identity/v3/credential.py | 37 +++++++++++++++++-- .../tests/unit/identity/v3/test_credential.py | 30 ++++++++++++++- ...ntial_list_user_type-c809e5b8014d6275.yaml | 5 +++ 4 files changed, 85 insertions(+), 6 deletions(-) create mode 100644 releasenotes/notes/credential_list_user_type-c809e5b8014d6275.yaml diff --git a/doc/source/cli/command-objects/credential.rst b/doc/source/cli/command-objects/credential.rst index 0b835c73f1..47c847c895 100644 --- a/doc/source/cli/command-objects/credential.rst +++ b/doc/source/cli/command-objects/credential.rst @@ -19,7 +19,7 @@ Create new credential .. option:: --type - New credential type + New credential type: cert, ec2 .. option:: --project @@ -59,6 +59,21 @@ List credentials .. code:: bash openstack credential list + [--user [--user-domain ]] + [--type ] + +.. option:: --user + + Filter credentials by (name or ID) + +.. option:: --user-domain + + Domain the user belongs to (name or ID). This can be + used in case collisions between user names exist. + +.. option:: --type + + Filter credentials by type: cert, ec2 credential set -------------- @@ -81,7 +96,7 @@ Set credential properties .. option:: --type - New credential type + New credential type: cert, ec2 .. option:: --data diff --git a/openstackclient/identity/v3/credential.py b/openstackclient/identity/v3/credential.py index 2fd0062610..79ef632cc4 100644 --- a/openstackclient/identity/v3/credential.py +++ b/openstackclient/identity/v3/credential.py @@ -23,6 +23,7 @@ import six from openstackclient.i18n import _ +from openstackclient.identity import common LOG = logging.getLogger(__name__) @@ -43,7 +44,7 @@ def get_parser(self, prog_name): default="cert", metavar='', choices=['ec2', 'cert'], - help=_('New credential type'), + help=_('New credential type: cert, ec2'), ) parser.add_argument( 'data', @@ -112,10 +113,40 @@ def take_action(self, parsed_args): class ListCredential(command.Lister): _description = _("List credentials") + def get_parser(self, prog_name): + parser = super(ListCredential, self).get_parser(prog_name) + parser.add_argument( + '--user', + metavar='', + help=_('Filter credentials by (name or ID)'), + ) + common.add_user_domain_option_to_parser(parser) + parser.add_argument( + '--type', + metavar='', + choices=['ec2', 'cert'], + help=_('Filter credentials by type: cert, ec2'), + ) + return parser + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + + kwargs = {} + if parsed_args.user: + user_id = common.find_user( + identity_client, + parsed_args.user, + parsed_args.user_domain, + ).id + kwargs["user_id"] = user_id + + if parsed_args.type: + kwargs["type"] = parsed_args.type + columns = ('ID', 'Type', 'User ID', 'Blob', 'Project ID') column_headers = ('ID', 'Type', 'User ID', 'Data', 'Project ID') - data = self.app.client_manager.identity.credentials.list() + data = self.app.client_manager.identity.credentials.list(**kwargs) return (column_headers, (utils.get_item_properties( s, columns, @@ -144,7 +175,7 @@ def get_parser(self, prog_name): metavar='', choices=['ec2', 'cert'], required=True, - help=_('New credential type'), + help=_('New credential type: cert, ec2'), ) parser.add_argument( '--data', diff --git a/openstackclient/tests/unit/identity/v3/test_credential.py b/openstackclient/tests/unit/identity/v3/test_credential.py index ce0fb5ae24..161f048447 100644 --- a/openstackclient/tests/unit/identity/v3/test_credential.py +++ b/openstackclient/tests/unit/identity/v3/test_credential.py @@ -225,12 +225,15 @@ class TestCredentialList(TestCredential): def setUp(self): super(TestCredentialList, self).setUp() + self.user = identity_fakes.FakeUser.create_one_user() + self.users_mock.get.return_value = self.user + self.credentials_mock.list.return_value = [self.credential] # Get the command object to test self.cmd = credential.ListCredential(self.app, None) - def test_domain_list_no_options(self): + def test_credential_list_no_options(self): arglist = [] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -241,6 +244,31 @@ def test_domain_list_no_options(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, tuple(data)) + def test_credential_list_with_options(self): + arglist = [ + '--user', self.credential.user_id, + '--type', self.credential.type, + ] + verifylist = [ + ('user', self.credential.user_id), + ('type', self.credential.type), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + kwargs = { + 'user_id': self.user.id, + 'type': self.credential.type, + } + self.users_mock.get.assert_called_with(self.credential.user_id) + self.credentials_mock.list.assert_called_with( + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, tuple(data)) + class TestCredentialSet(TestCredential): diff --git a/releasenotes/notes/credential_list_user_type-c809e5b8014d6275.yaml b/releasenotes/notes/credential_list_user_type-c809e5b8014d6275.yaml new file mode 100644 index 0000000000..e6280358a0 --- /dev/null +++ b/releasenotes/notes/credential_list_user_type-c809e5b8014d6275.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``--user`` and ``--type`` option to ``credential list`` command + to filter list result by different user or type. From 022fdb10ebf5e539fb4d84550d00943d55e5f746 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 26 Jul 2017 11:31:06 -0500 Subject: [PATCH 1751/3095] Skip object-store functional tests when Swift is not available Specifically, in the py3 jobs Swift is not (yet) properly starting as a py2 service, so we disabled swift in those OSC jobs and need to skip the object-store functional tests in that case. Change-Id: I073551c41b7636f04b3ee97dc6fe69630e207b67 --- .../tests/functional/object/v1/common.py | 22 ++++++++++++++++ .../functional/object/v1/test_container.py | 25 +++++++++++++------ .../tests/functional/object/v1/test_object.py | 12 ++++++--- 3 files changed, 48 insertions(+), 11 deletions(-) create mode 100644 openstackclient/tests/functional/object/v1/common.py diff --git a/openstackclient/tests/functional/object/v1/common.py b/openstackclient/tests/functional/object/v1/common.py new file mode 100644 index 0000000000..44771aaabe --- /dev/null +++ b/openstackclient/tests/functional/object/v1/common.py @@ -0,0 +1,22 @@ +# 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 ObjectStoreTests(base.TestCase): + """Functional tests for Object Store commands""" + + @classmethod + def setUpClass(cls): + super(ObjectStoreTests, cls).setUpClass() + cls.haz_object_store = base.is_service_enabled('object-store') diff --git a/openstackclient/tests/functional/object/v1/test_container.py b/openstackclient/tests/functional/object/v1/test_container.py index acfbab11de..d66aa842b0 100644 --- a/openstackclient/tests/functional/object/v1/test_container.py +++ b/openstackclient/tests/functional/object/v1/test_container.py @@ -12,28 +12,37 @@ import uuid -from openstackclient.tests.functional import base +# from openstackclient.tests.functional import base +from openstackclient.tests.functional.object.v1 import common -class ContainerTests(base.TestCase): - """Functional tests for object containers. """ +class ContainerTests(common.ObjectStoreTests): + """Functional tests for Object Store container commands""" NAME = uuid.uuid4().hex @classmethod def setUpClass(cls): super(ContainerTests, cls).setUpClass() - opts = cls.get_opts(['container']) - raw_output = cls.openstack('container create ' + cls.NAME + opts) - cls.assertOutput(cls.NAME + '\n', raw_output) + if cls.haz_object_store: + opts = cls.get_opts(['container']) + raw_output = cls.openstack('container create ' + cls.NAME + opts) + cls.assertOutput(cls.NAME + '\n', raw_output) @classmethod def tearDownClass(cls): try: - raw_output = cls.openstack('container delete ' + cls.NAME) - cls.assertOutput('', raw_output) + if cls.haz_object_store: + raw_output = cls.openstack('container delete ' + cls.NAME) + cls.assertOutput('', raw_output) finally: super(ContainerTests, cls).tearDownClass() + def setUp(self): + super(ContainerTests, self).setUp() + # Skip tests if no object-store is present + if not self.haz_object_store: + self.skipTest("No object-store service present") + def test_container_list(self): opts = self.get_opts(['Name']) raw_output = self.openstack('container list' + opts) diff --git a/openstackclient/tests/functional/object/v1/test_object.py b/openstackclient/tests/functional/object/v1/test_object.py index 0927d706dc..d1a73c54db 100644 --- a/openstackclient/tests/functional/object/v1/test_object.py +++ b/openstackclient/tests/functional/object/v1/test_object.py @@ -14,18 +14,24 @@ import tempfile import uuid -from openstackclient.tests.functional import base +from openstackclient.tests.functional.object.v1 import common BASIC_LIST_HEADERS = ['Name'] CONTAINER_FIELDS = ['account', 'container', 'x-trans-id'] OBJECT_FIELDS = ['object', 'container', 'etag'] -class ObjectTests(base.TestCase): - """Functional tests for Object commands. """ +class ObjectTests(common.ObjectStoreTests): + """Functional tests for Object Store object commands""" CONTAINER_NAME = uuid.uuid4().hex + def setUp(self): + super(ObjectTests, self).setUp() + # Skip tests if no object-store is present + if not self.haz_object_store: + self.skipTest("No object-store service present") + def test_object(self): with tempfile.NamedTemporaryFile() as f: f.write('test content') From 1134249a5ebd7c9316b62604431bd9864752e2ff Mon Sep 17 00:00:00 2001 From: Gary Kotton Date: Wed, 26 Jul 2017 23:52:04 -0700 Subject: [PATCH 1752/3095] Use instance variables for subnet tests Commit 78a832441af3928994446d2afab07f0abb0dd26a let to random failures of test_create_with_tags. This addresses that issue. Change-Id: I470da7a1863a8c22257b1c27bc6d2b1c45c9cca3 --- .../tests/unit/network/v2/test_subnet.py | 287 +++++++++--------- 1 file changed, 146 insertions(+), 141 deletions(-) diff --git a/openstackclient/tests/unit/network/v2/test_subnet.py b/openstackclient/tests/unit/network/v2/test_subnet.py index c96d680fba..b7f741cd91 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet.py +++ b/openstackclient/tests/unit/network/v2/test_subnet.py @@ -38,160 +38,165 @@ def setUp(self): class TestCreateSubnet(TestSubnet): - project = identity_fakes_v3.FakeProject.create_one_project() - domain = identity_fakes_v3.FakeDomain.create_one_domain() - # An IPv4 subnet to be created with mostly default values - _subnet = network_fakes.FakeSubnet.create_one_subnet( - attrs={ - 'tenant_id': project.id, - } - ) + def _init_subnet_variables(self): + self.project = identity_fakes_v3.FakeProject.create_one_project() + self.domain = identity_fakes_v3.FakeDomain.create_one_domain() + # An IPv4 subnet to be created with mostly default values + self._subnet = network_fakes.FakeSubnet.create_one_subnet( + attrs={ + 'tenant_id': self.project.id, + } + ) - # Subnet pool to be used to create a subnet from a pool - _subnet_pool = network_fakes.FakeSubnetPool.create_one_subnet_pool() - - # An IPv4 subnet to be created using a specific subnet pool - _subnet_from_pool = network_fakes.FakeSubnet.create_one_subnet( - attrs={ - 'tenant_id': project.id, - 'subnetpool_id': _subnet_pool.id, - 'dns_nameservers': ['8.8.8.8', - '8.8.4.4'], - 'host_routes': [{'destination': '10.20.20.0/24', - 'nexthop': '10.20.20.1'}, - {'destination': '10.30.30.0/24', - 'nexthop': '10.30.30.1'}], - 'service_types': ['network:router_gateway', - 'network:floatingip_agent_gateway'], - } - ) + # Subnet pool to be used to create a subnet from a pool + self._subnet_pool = \ + network_fakes.FakeSubnetPool.create_one_subnet_pool() - # An IPv6 subnet to be created with most options specified - _subnet_ipv6 = network_fakes.FakeSubnet.create_one_subnet( - attrs={ - 'tenant_id': project.id, - 'cidr': 'fe80:0:0:a00a::/64', - 'enable_dhcp': True, - 'dns_nameservers': ['fe80:27ff:a00a:f00f::ffff', - 'fe80:37ff:a00a:f00f::ffff'], - 'allocation_pools': [{'start': 'fe80::a00a:0:c0de:0:100', - 'end': 'fe80::a00a:0:c0de:0:f000'}, - {'start': 'fe80::a00a:0:c0de:1:100', - 'end': 'fe80::a00a:0:c0de:1:f000'}], - 'host_routes': [{'destination': 'fe80:27ff:a00a:f00f::/64', - 'nexthop': 'fe80:27ff:a00a:f00f::1'}, - {'destination': 'fe80:37ff:a00a:f00f::/64', - 'nexthop': 'fe80:37ff:a00a:f00f::1'}], - 'ip_version': 6, - 'gateway_ip': 'fe80::a00a:0:c0de:0:1', - 'ipv6_address_mode': 'slaac', - 'ipv6_ra_mode': 'slaac', - 'subnetpool_id': 'None', - 'service_types': ['network:router_gateway', - 'network:floatingip_agent_gateway'], - } - ) + # An IPv4 subnet to be created using a specific subnet pool + self._subnet_from_pool = network_fakes.FakeSubnet.create_one_subnet( + attrs={ + 'tenant_id': self.project.id, + 'subnetpool_id': self._subnet_pool.id, + 'dns_nameservers': ['8.8.8.8', + '8.8.4.4'], + 'host_routes': [{'destination': '10.20.20.0/24', + 'nexthop': '10.20.20.1'}, + {'destination': '10.30.30.0/24', + 'nexthop': '10.30.30.1'}], + 'service_types': ['network:router_gateway', + 'network:floatingip_agent_gateway'], + } + ) - # The network to be returned from find_network - _network = network_fakes.FakeNetwork.create_one_network( - attrs={ - 'id': _subnet.network_id, - } - ) + # An IPv6 subnet to be created with most options specified + self._subnet_ipv6 = network_fakes.FakeSubnet.create_one_subnet( + attrs={ + 'tenant_id': self.project.id, + 'cidr': 'fe80:0:0:a00a::/64', + 'enable_dhcp': True, + 'dns_nameservers': ['fe80:27ff:a00a:f00f::ffff', + 'fe80:37ff:a00a:f00f::ffff'], + 'allocation_pools': [{'start': 'fe80::a00a:0:c0de:0:100', + 'end': 'fe80::a00a:0:c0de:0:f000'}, + {'start': 'fe80::a00a:0:c0de:1:100', + 'end': 'fe80::a00a:0:c0de:1:f000'}], + 'host_routes': [{'destination': 'fe80:27ff:a00a:f00f::/64', + 'nexthop': 'fe80:27ff:a00a:f00f::1'}, + {'destination': 'fe80:37ff:a00a:f00f::/64', + 'nexthop': 'fe80:37ff:a00a:f00f::1'}], + 'ip_version': 6, + 'gateway_ip': 'fe80::a00a:0:c0de:0:1', + 'ipv6_address_mode': 'slaac', + 'ipv6_ra_mode': 'slaac', + 'subnetpool_id': 'None', + 'service_types': ['network:router_gateway', + 'network:floatingip_agent_gateway'], + } + ) - # The network segment to be returned from find_segment - _network_segment = \ - network_fakes.FakeNetworkSegment.create_one_network_segment( + # The network to be returned from find_network + self._network = network_fakes.FakeNetwork.create_one_network( attrs={ - 'network_id': _subnet.network_id, + 'id': self._subnet.network_id, } ) - columns = ( - 'allocation_pools', - 'cidr', - 'description', - 'dns_nameservers', - 'enable_dhcp', - 'gateway_ip', - 'host_routes', - 'id', - 'ip_version', - 'ipv6_address_mode', - 'ipv6_ra_mode', - 'name', - 'network_id', - 'project_id', - 'segment_id', - 'service_types', - 'subnetpool_id', - 'tags', - ) + # The network segment to be returned from find_segment + self._network_segment = \ + network_fakes.FakeNetworkSegment.create_one_network_segment( + attrs={ + 'network_id': self._subnet.network_id, + } + ) + + self.columns = ( + 'allocation_pools', + 'cidr', + 'description', + 'dns_nameservers', + 'enable_dhcp', + 'gateway_ip', + 'host_routes', + 'id', + 'ip_version', + 'ipv6_address_mode', + 'ipv6_ra_mode', + 'name', + 'network_id', + 'project_id', + 'segment_id', + 'service_types', + 'subnetpool_id', + 'tags', + ) - data = ( - subnet_v2._format_allocation_pools(_subnet.allocation_pools), - _subnet.cidr, - _subnet.description, - utils.format_list(_subnet.dns_nameservers), - _subnet.enable_dhcp, - _subnet.gateway_ip, - subnet_v2._format_host_routes(_subnet.host_routes), - _subnet.id, - _subnet.ip_version, - _subnet.ipv6_address_mode, - _subnet.ipv6_ra_mode, - _subnet.name, - _subnet.network_id, - _subnet.project_id, - _subnet.segment_id, - utils.format_list(_subnet.service_types), - _subnet.subnetpool_id, - utils.format_list(_subnet.tags), - ) + self.data = ( + subnet_v2._format_allocation_pools(self._subnet.allocation_pools), + self._subnet.cidr, + self._subnet.description, + utils.format_list(self._subnet.dns_nameservers), + self._subnet.enable_dhcp, + self._subnet.gateway_ip, + subnet_v2._format_host_routes(self._subnet.host_routes), + self._subnet.id, + self._subnet.ip_version, + self._subnet.ipv6_address_mode, + self._subnet.ipv6_ra_mode, + self._subnet.name, + self._subnet.network_id, + self._subnet.project_id, + self._subnet.segment_id, + utils.format_list(self._subnet.service_types), + self._subnet.subnetpool_id, + utils.format_list(self._subnet.tags), + ) - data_subnet_pool = ( - subnet_v2._format_allocation_pools(_subnet_from_pool.allocation_pools), - _subnet_from_pool.cidr, - _subnet_from_pool.description, - utils.format_list(_subnet_from_pool.dns_nameservers), - _subnet_from_pool.enable_dhcp, - _subnet_from_pool.gateway_ip, - subnet_v2._format_host_routes(_subnet_from_pool.host_routes), - _subnet_from_pool.id, - _subnet_from_pool.ip_version, - _subnet_from_pool.ipv6_address_mode, - _subnet_from_pool.ipv6_ra_mode, - _subnet_from_pool.name, - _subnet_from_pool.network_id, - _subnet_from_pool.project_id, - _subnet_from_pool.segment_id, - utils.format_list(_subnet_from_pool.service_types), - _subnet_from_pool.subnetpool_id, - utils.format_list(_subnet.tags), - ) + self.data_subnet_pool = ( + subnet_v2._format_allocation_pools( + self._subnet_from_pool.allocation_pools), + self._subnet_from_pool.cidr, + self._subnet_from_pool.description, + utils.format_list(self._subnet_from_pool.dns_nameservers), + self._subnet_from_pool.enable_dhcp, + self._subnet_from_pool.gateway_ip, + subnet_v2._format_host_routes(self._subnet_from_pool.host_routes), + self._subnet_from_pool.id, + self._subnet_from_pool.ip_version, + self._subnet_from_pool.ipv6_address_mode, + self._subnet_from_pool.ipv6_ra_mode, + self._subnet_from_pool.name, + self._subnet_from_pool.network_id, + self._subnet_from_pool.project_id, + self._subnet_from_pool.segment_id, + utils.format_list(self._subnet_from_pool.service_types), + self._subnet_from_pool.subnetpool_id, + utils.format_list(self._subnet.tags), + ) - data_ipv6 = ( - subnet_v2._format_allocation_pools(_subnet_ipv6.allocation_pools), - _subnet_ipv6.cidr, - _subnet_ipv6.description, - utils.format_list(_subnet_ipv6.dns_nameservers), - _subnet_ipv6.enable_dhcp, - _subnet_ipv6.gateway_ip, - subnet_v2._format_host_routes(_subnet_ipv6.host_routes), - _subnet_ipv6.id, - _subnet_ipv6.ip_version, - _subnet_ipv6.ipv6_address_mode, - _subnet_ipv6.ipv6_ra_mode, - _subnet_ipv6.name, - _subnet_ipv6.network_id, - _subnet_ipv6.project_id, - _subnet_ipv6.segment_id, - utils.format_list(_subnet_ipv6.service_types), - _subnet_ipv6.subnetpool_id, - utils.format_list(_subnet.tags), - ) + self.data_ipv6 = ( + subnet_v2._format_allocation_pools( + self._subnet_ipv6.allocation_pools), + self._subnet_ipv6.cidr, + self._subnet_ipv6.description, + utils.format_list(self._subnet_ipv6.dns_nameservers), + self._subnet_ipv6.enable_dhcp, + self._subnet_ipv6.gateway_ip, + subnet_v2._format_host_routes(self._subnet_ipv6.host_routes), + self._subnet_ipv6.id, + self._subnet_ipv6.ip_version, + self._subnet_ipv6.ipv6_address_mode, + self._subnet_ipv6.ipv6_ra_mode, + self._subnet_ipv6.name, + self._subnet_ipv6.network_id, + self._subnet_ipv6.project_id, + self._subnet_ipv6.segment_id, + utils.format_list(self._subnet_ipv6.service_types), + self._subnet_ipv6.subnetpool_id, + utils.format_list(self._subnet.tags), + ) def setUp(self): + self._init_subnet_variables() super(TestCreateSubnet, self).setUp() # Get the command object to test From 198a486413738148eb0de2a25749daac289a496e Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Sun, 23 Jul 2017 23:41:33 +0000 Subject: [PATCH 1753/3095] network functest: Remove condition for segment test Previously fucntional tests for network segment feature are skipped as neutron 'segment' API extension was disabled in the gate. We now enable neutron 'segment' API extension, so we can safely drop the check for the segment extension from the test code. Also setup code in test_network_segment is moved from setUpClass to setUp. There is no good reason to do them in setUpClass and having them in setUp simplifies the test code. no user once this commit is applied. Change-Id: I183310b94d9b6d7f4311a3859b59dc22d36440db --- openstackclient/tests/functional/base.py | 5 - .../network/v2/test_network_segment.py | 132 +++++++----------- 2 files changed, 48 insertions(+), 89 deletions(-) diff --git a/openstackclient/tests/functional/base.py b/openstackclient/tests/functional/base.py index d95f7f8465..4c88b13e93 100644 --- a/openstackclient/tests/functional/base.py +++ b/openstackclient/tests/functional/base.py @@ -68,11 +68,6 @@ def get_openstack_configuration_value(cls, configuration): opts = cls.get_opts([configuration]) return cls.openstack('configuration show ' + opts) - @classmethod - def get_openstack_extension_names(cls): - opts = cls.get_opts(['Name']) - return cls.openstack('extension list ' + opts) - @classmethod def get_opts(cls, fields, output_format='value'): return ' -f {0} {1}'.format(output_format, diff --git a/openstackclient/tests/functional/network/v2/test_network_segment.py b/openstackclient/tests/functional/network/v2/test_network_segment.py index b34515fa41..e1dbc7a0c5 100644 --- a/openstackclient/tests/functional/network/v2/test_network_segment.py +++ b/openstackclient/tests/functional/network/v2/test_network_segment.py @@ -17,52 +17,6 @@ class NetworkSegmentTests(common.NetworkTests): """Functional tests for network segment""" - NETWORK_SEGMENT_ID = None - NETWORK_ID = None - NETWORK_SEGMENT_EXTENSION = None - - @classmethod - def setUpClass(cls): - common.NetworkTests.setUpClass() - if cls.haz_network: - cls.NETWORK_NAME = uuid.uuid4().hex - cls.PHYSICAL_NETWORK_NAME = uuid.uuid4().hex - - # Create a network for the segment - opts = cls.get_opts(['id']) - raw_output = cls.openstack( - 'network create ' + cls.NETWORK_NAME + opts - ) - cls.NETWORK_ID = raw_output.strip('\n') - - # NOTE(rtheis): The segment extension is not yet enabled - # by default. - # Skip the tests if not enabled. - extensions = cls.get_openstack_extension_names() - if 'Segment' in extensions: - cls.NETWORK_SEGMENT_EXTENSION = 'Segment' - - if cls.NETWORK_SEGMENT_EXTENSION: - # Get the segment for the network. - opts = cls.get_opts(['ID', 'Network']) - raw_output = cls.openstack( - 'network segment list ' - '--network ' + cls.NETWORK_NAME + ' ' + - opts - ) - raw_output_row = raw_output.split('\n')[0] - cls.NETWORK_SEGMENT_ID = raw_output_row.split(' ')[0] - - @classmethod - def tearDownClass(cls): - try: - if cls.haz_network: - raw_output = cls.openstack( - 'network delete ' + cls.NETWORK_NAME - ) - cls.assertOutput('', raw_output) - finally: - super(NetworkSegmentTests, cls).tearDownClass() def setUp(self): super(NetworkSegmentTests, self).setUp() @@ -70,48 +24,58 @@ def setUp(self): if not self.haz_network: self.skipTest("No Network service present") + self.NETWORK_NAME = uuid.uuid4().hex + self.PHYSICAL_NETWORK_NAME = uuid.uuid4().hex + + # Create a network for the segment + opts = self.get_opts(['id']) + raw_output = self.openstack( + 'network create ' + self.NETWORK_NAME + opts + ) + self.addCleanup(self.openstack, + 'network delete ' + self.NETWORK_NAME) + self.NETWORK_ID = raw_output.strip('\n') + + # Get the segment for the network. + opts = self.get_opts(['ID', 'Network']) + raw_output = self.openstack( + 'network segment list ' + '--network ' + self.NETWORK_NAME + ' ' + + opts + ) + raw_output_row = raw_output.split('\n')[0] + self.NETWORK_SEGMENT_ID = raw_output_row.split(' ')[0] + def test_network_segment_create_delete(self): - if self.NETWORK_SEGMENT_EXTENSION: - opts = self.get_opts(['id']) - raw_output = self.openstack( - ' network segment create --network ' + self.NETWORK_ID + - ' --network-type geneve ' + - ' --segment 2055 test_segment ' + opts - ) - network_segment_id = raw_output.strip('\n') - raw_output = self.openstack('network segment delete ' + - network_segment_id) - self.assertOutput('', raw_output) - else: - self.skipTest('Segment extension disabled') + opts = self.get_opts(['id']) + raw_output = self.openstack( + ' network segment create --network ' + self.NETWORK_ID + + ' --network-type geneve ' + + ' --segment 2055 test_segment ' + opts + ) + network_segment_id = raw_output.strip('\n') + raw_output = self.openstack('network segment delete ' + + network_segment_id) + self.assertOutput('', raw_output) def test_network_segment_list(self): - if self.NETWORK_SEGMENT_EXTENSION: - opts = self.get_opts(['ID']) - raw_output = self.openstack('network segment list' + opts) - self.assertIn(self.NETWORK_SEGMENT_ID, raw_output) - else: - self.skipTest('Segment extension disabled') + opts = self.get_opts(['ID']) + raw_output = self.openstack('network segment list' + opts) + self.assertIn(self.NETWORK_SEGMENT_ID, raw_output) def test_network_segment_set(self): - if self.NETWORK_SEGMENT_EXTENSION: - new_description = 'new_description' - raw_output = self.openstack('network segment set ' + - '--description ' + new_description + - ' ' + self.NETWORK_SEGMENT_ID) - self.assertOutput('', raw_output) - opts = self.get_opts(['description']) - raw_output = self.openstack('network segment show ' + - self.NETWORK_SEGMENT_ID + opts) - self.assertEqual(new_description + "\n", raw_output) - else: - self.skipTest('Segment extension disabled') + new_description = 'new_description' + raw_output = self.openstack('network segment set ' + + '--description ' + new_description + + ' ' + self.NETWORK_SEGMENT_ID) + self.assertOutput('', raw_output) + opts = self.get_opts(['description']) + raw_output = self.openstack('network segment show ' + + self.NETWORK_SEGMENT_ID + opts) + self.assertEqual(new_description + "\n", raw_output) def test_network_segment_show(self): - if self.NETWORK_SEGMENT_EXTENSION: - opts = self.get_opts(['network_id']) - raw_output = self.openstack('network segment show ' + - self.NETWORK_SEGMENT_ID + opts) - self.assertEqual(self.NETWORK_ID + "\n", raw_output) - else: - self.skipTest('Segment extension disabled') + opts = self.get_opts(['network_id']) + raw_output = self.openstack('network segment show ' + + self.NETWORK_SEGMENT_ID + opts) + self.assertEqual(self.NETWORK_ID + "\n", raw_output) From d25f68f3ba21652d5c81415c593ee8e913abf854 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 27 Jul 2017 20:33:15 +0000 Subject: [PATCH 1754/3095] Updated from global requirements Change-Id: I1cba44152cf17ab3b50e5e3c423c702dbdf5039e --- requirements.txt | 2 +- test-requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 24e467231c..018afc8220 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ six>=1.9.0 # MIT Babel!=2.4.0,>=2.3.4 # BSD cliff>=2.8.0 # Apache-2.0 -keystoneauth1>=3.0.1 # Apache-2.0 +keystoneauth1>=3.1.0 # Apache-2.0 openstacksdk>=0.9.17 # Apache-2.0 osc-lib>=1.7.0 # Apache-2.0 oslo.i18n!=3.15.2,>=2.1.0 # Apache-2.0 diff --git a/test-requirements.txt b/test-requirements.txt index e3848618e8..3249895d05 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -6,7 +6,7 @@ hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0 coverage!=4.4,>=4.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD mock>=2.0 # BSD -openstackdocstheme>=1.11.0 # Apache-2.0 +openstackdocstheme>=1.16.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 reno!=2.3.1,>=1.8.0 # Apache-2.0 requests>=2.14.2 # Apache-2.0 From 25e6c31d2a7412b7d4cf53795b69af8f2ae6d9cf Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Fri, 28 Jul 2017 05:26:04 +0000 Subject: [PATCH 1755/3095] Updated from global requirements Change-Id: I7906433adde40f76eaccdab186e68e0afcbe49af --- requirements.txt | 2 +- test-requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 018afc8220..8b8b8235d1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,4 +14,4 @@ oslo.utils>=3.20.0 # Apache-2.0 python-glanceclient>=2.7.0 # Apache-2.0 python-keystoneclient>=3.8.0 # Apache-2.0 python-novaclient>=9.0.0 # Apache-2.0 -python-cinderclient>=3.0.0 # Apache-2.0 +python-cinderclient>=3.1.0 # Apache-2.0 diff --git a/test-requirements.txt b/test-requirements.txt index 3249895d05..8278d76ac8 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -31,7 +31,7 @@ python-designateclient>=1.5.0 # Apache-2.0 python-heatclient>=1.6.1 # Apache-2.0 python-ironicclient>=1.14.0 # Apache-2.0 python-ironic-inspector-client>=1.5.0 # Apache-2.0 -python-karborclient>=0.2.0 # Apache-2.0 +python-karborclient>=0.6.0 # Apache-2.0 python-mistralclient>=3.1.0 # Apache-2.0 python-muranoclient>=0.8.2 # Apache-2.0 python-neutronclient>=6.3.0 # Apache-2.0 From 6fdce1316563f79fa6adf6ffa6439018cc5b8e98 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Fri, 28 Jul 2017 21:47:55 +0000 Subject: [PATCH 1756/3095] Updated from global requirements Change-Id: Ie45d9ee68e24806b2411359068b6ec6c44dc5845 --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 8278d76ac8..4d80ef517f 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -25,7 +25,7 @@ wrapt>=1.7.0 # BSD License # Install these to generate sphinx autodocs aodhclient>=0.7.0 # Apache-2.0 gnocchiclient>=2.7.0 # Apache-2.0 -python-barbicanclient>=4.0.0 # Apache-2.0 +python-barbicanclient!=4.5.0,>=4.0.0 # Apache-2.0 python-congressclient<2000,>=1.3.0 # Apache-2.0 python-designateclient>=1.5.0 # Apache-2.0 python-heatclient>=1.6.1 # Apache-2.0 From bd079e5066e1c0b0b0100ab28d87a3e00f3d99a0 Mon Sep 17 00:00:00 2001 From: yang wang Date: Fri, 4 Aug 2017 19:03:59 +0800 Subject: [PATCH 1757/3095] Replace guildelines with guidelines Change-Id: I9c4f15d320e2c506c16634a5a0e2fc324101189a --- doc/source/contributor/command-options.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/contributor/command-options.rst b/doc/source/contributor/command-options.rst index 0662344536..70890ec69d 100644 --- a/doc/source/contributor/command-options.rst +++ b/doc/source/contributor/command-options.rst @@ -14,7 +14,7 @@ for defining and using options in all situations. The alternative of only using it when necessary leads to errors when copy-n-paste is used for a new command without understanding why or why not that instance is correct. -The :ref:`hig` describes the guildelines for option names and usage. +The :ref:`hig` describes the guidelines for option names and usage. In short: * All option names shall be GNU-style long names (two leading dashes). From a709c151e664ceb66b4cd51bd973567cf46bcd3d Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Mon, 7 Aug 2017 00:53:48 +0000 Subject: [PATCH 1758/3095] Updated from global requirements Change-Id: I6f384b021b5ae73152192c5b4c2bab190db88251 --- requirements.txt | 2 +- test-requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 8b8b8235d1..f449e200ac 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ openstacksdk>=0.9.17 # Apache-2.0 osc-lib>=1.7.0 # Apache-2.0 oslo.i18n!=3.15.2,>=2.1.0 # Apache-2.0 oslo.utils>=3.20.0 # Apache-2.0 -python-glanceclient>=2.7.0 # Apache-2.0 +python-glanceclient>=2.8.0 # Apache-2.0 python-keystoneclient>=3.8.0 # Apache-2.0 python-novaclient>=9.0.0 # Apache-2.0 python-cinderclient>=3.1.0 # Apache-2.0 diff --git a/test-requirements.txt b/test-requirements.txt index 4d80ef517f..5af8d0cf4f 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -25,7 +25,7 @@ wrapt>=1.7.0 # BSD License # Install these to generate sphinx autodocs aodhclient>=0.7.0 # Apache-2.0 gnocchiclient>=2.7.0 # Apache-2.0 -python-barbicanclient!=4.5.0,>=4.0.0 # Apache-2.0 +python-barbicanclient!=4.5.0,!=4.5.1,>=4.0.0 # Apache-2.0 python-congressclient<2000,>=1.3.0 # Apache-2.0 python-designateclient>=1.5.0 # Apache-2.0 python-heatclient>=1.6.1 # Apache-2.0 From a1af3437dcdcf6bb5edb407b078edd8428420092 Mon Sep 17 00:00:00 2001 From: chenying Date: Mon, 7 Aug 2017 17:30:37 +0800 Subject: [PATCH 1759/3095] Add new commands for karbor osc plugin These command operationlog are implemented in the latest python-karborclient project. Change-Id: Icfafcb7cbcc5052f75b5136443935e05e1c9ec84 --- doc/source/cli/commands.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/cli/commands.rst b/doc/source/cli/commands.rst index 5a7977e483..4716378621 100644 --- a/doc/source/cli/commands.rst +++ b/doc/source/cli/commands.rst @@ -204,6 +204,7 @@ conflicts when creating new plugins. For a complete list check out * ``data protection trigger``: (**Data Protection (Karbor)**) * ``data protection checkpoint``: (**Data Protection (Karbor)**) * ``data protection scheduledoperation``: (**Data Protection (Karbor)**) +* ``data protection operationlog``: (**Data Protection (Karbor)**) * ``loadbalancer``: (**Load Balancer (Octavia)**) * ``loadbalancer healthmonitor``: (**Load Balancer (Octavia)**) * ``loadbalancer l7policy``: (**Load Balancer (Octavia)**) From fd7b4f49121d3ac65e3f3a31607ead506d268300 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Mon, 7 Aug 2017 18:07:46 -0500 Subject: [PATCH 1760/3095] Add .htaccess for docs migration Change-Id: I413a6d059f4eb751ca5c8b5a9b61740ac93680b1 --- doc/source/_extra/.htaccess | 22 ++++++++++++++++++++++ doc/source/conf.py | 3 +++ 2 files changed, 25 insertions(+) create mode 100644 doc/source/_extra/.htaccess diff --git a/doc/source/_extra/.htaccess b/doc/source/_extra/.htaccess new file mode 100644 index 0000000000..d4c092b5ab --- /dev/null +++ b/doc/source/_extra/.htaccess @@ -0,0 +1,22 @@ +# Redirect docs from the old to new location following the Great Docs Migration of 2017 + +redirectmatch 301 ^/python-openstackclient/([^/]+)/command-objects/([^/.]+).html$ /python-openstackclient/$1/cli/command-objects/$2.html + +redirectmatch 301 ^/python-openstackclient/([^/]+)/authentication.html$ /python-openstackclient/$1/cli/authentication.html +redirectmatch 301 ^/python-openstackclient/([^/]+)/backward-incompatible.html$ /python-openstackclient/$1/cli/backward-incompatible.html +redirectmatch 301 ^/python-openstackclient/([^/]+)/command-list.html$ /python-openstackclient/$1/cli/command-list.html +redirectmatch 301 ^/python-openstackclient/([^/]+)/commands.html$ /python-openstackclient/$1/cli/commands.html +redirectmatch 301 ^/python-openstackclient/([^/]+)/decoder.html$ /python-openstackclient/$1/cli/decoder.html +redirectmatch 301 ^/python-openstackclient/([^/]+)/interactive.html$ /python-openstackclient/$1/cli/interactive.html +redirectmatch 301 ^/python-openstackclient/([^/]+)/plugin-commands.html$ /python-openstackclient/$1/cli/plugin-commands.html + +redirectmatch 301 ^/python-openstackclient/([^/]+)/specs/([^/.]+).html$ /python-openstackclient/$1/contributor/specs/$2.html + +redirectmatch 301 ^/python-openstackclient/([^/]+)/command-beta.html$ /python-openstackclient/$1/contributor/command-beta.html +redirectmatch 301 ^/python-openstackclient/([^/]+)/command-errors.html$ /python-openstackclient/$1/contributor/command-errors.html +redirectmatch 301 ^/python-openstackclient/([^/]+)/command-logs.html$ /python-openstackclient/$1/contributor/command-logs.html +redirectmatch 301 ^/python-openstackclient/([^/]+)/command-options.html$ /python-openstackclient/$1/contributor/command-options.html +redirectmatch 301 ^/python-openstackclient/([^/]+)/command-wrappers.html$ /python-openstackclient/$1/contributor/command-wrappers.html +redirectmatch 301 ^/python-openstackclient/([^/]+)/developing.html$ /python-openstackclient/$1/contributor/developing.html +redirectmatch 301 ^/python-openstackclient/([^/]+)/humaninterfaceguide.html$ /python-openstackclient/$1/contributor/humaninterfaceguide.html +redirectmatch 301 ^/python-openstackclient/([^/]+)/plugins.html$ /python-openstackclient/$1/contributor/plugins.html diff --git a/doc/source/conf.py b/doc/source/conf.py index bd4fa73031..ac9d0b38be 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -187,6 +187,9 @@ # Output file base name for HTML help builder. htmlhelp_basename = 'OpenStackCommandLineClientdoc' +# Add any paths that contain "extra" files, such as .htaccess or robots.txt +html_extra_path = ['_extra'] + # -- Options for LaTeX output ------------------------------------------------- From 48c7405cca1b3f63e062ce247d3d40a05e28796c Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Thu, 10 Aug 2017 19:31:07 +0000 Subject: [PATCH 1761/3095] Update reno for stable/pike Change-Id: Ieea72b22a55b29283705e957a973a46f2e6c5848 --- releasenotes/source/index.rst | 1 + releasenotes/source/pike.rst | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 releasenotes/source/pike.rst diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index 1dbd1129f1..abdb6143ee 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,6 +6,7 @@ OpenStackClient Release Notes :maxdepth: 1 unreleased + pike ocata newton mitaka diff --git a/releasenotes/source/pike.rst b/releasenotes/source/pike.rst new file mode 100644 index 0000000000..e43bfc0ce1 --- /dev/null +++ b/releasenotes/source/pike.rst @@ -0,0 +1,6 @@ +=================================== + Pike Series Release Notes +=================================== + +.. release-notes:: + :branch: stable/pike From 88b31fc12e817e2486ad0afc7565a6e6eb5ffe3b Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 10 Aug 2017 16:37:09 -0500 Subject: [PATCH 1762/3095] Update release/stable branch table in docs Change-Id: I0d18be4f2ed14137010693f41526a261a0acca3b --- releasenotes/source/index.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index abdb6143ee..a551b26a59 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -24,6 +24,7 @@ OpenStack release was made is shown below: ================= ======================= OpenStack Release OpenStackClient Release ================= ======================= +Pike 3.12.0 Ocata 3.8.1 Newton 3.2.0 Mitaka 2.3.0 From b30f0f3f0595087d1c947b614e94d4b649ac4b0f Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Mon, 7 Aug 2017 16:50:03 -0500 Subject: [PATCH 1763/3095] Convert network segment functional tests to JSON Change-Id: I8dc1e992d54c63c93bbe2bdd7acba61a7a6773d0 --- .../network/v2/test_network_segment.py | 144 ++++++++++++------ 1 file changed, 99 insertions(+), 45 deletions(-) diff --git a/openstackclient/tests/functional/network/v2/test_network_segment.py b/openstackclient/tests/functional/network/v2/test_network_segment.py index e1dbc7a0c5..8940273f85 100644 --- a/openstackclient/tests/functional/network/v2/test_network_segment.py +++ b/openstackclient/tests/functional/network/v2/test_network_segment.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import json import uuid from openstackclient.tests.functional.network.v2 import common @@ -18,64 +19,117 @@ class NetworkSegmentTests(common.NetworkTests): """Functional tests for network segment""" + @classmethod + def setUpClass(cls): + common.NetworkTests.setUpClass() + if cls.haz_network: + cls.NETWORK_NAME = uuid.uuid4().hex + cls.PHYSICAL_NETWORK_NAME = uuid.uuid4().hex + + # Create a network for the all subnet tests + cmd_output = json.loads(cls.openstack( + 'network create -f json ' + + cls.NETWORK_NAME + )) + # Get network_id for assertEqual + cls.NETWORK_ID = cmd_output["id"] + + @classmethod + def tearDownClass(cls): + try: + if cls.haz_network: + raw_output = cls.openstack( + 'network delete ' + + cls.NETWORK_NAME + ) + cls.assertOutput('', raw_output) + finally: + super(NetworkSegmentTests, cls).tearDownClass() + def setUp(self): super(NetworkSegmentTests, self).setUp() # Nothing in this class works with Nova Network if not self.haz_network: self.skipTest("No Network service present") - self.NETWORK_NAME = uuid.uuid4().hex - self.PHYSICAL_NETWORK_NAME = uuid.uuid4().hex - - # Create a network for the segment - opts = self.get_opts(['id']) - raw_output = self.openstack( - 'network create ' + self.NETWORK_NAME + opts - ) - self.addCleanup(self.openstack, - 'network delete ' + self.NETWORK_NAME) - self.NETWORK_ID = raw_output.strip('\n') - - # Get the segment for the network. - opts = self.get_opts(['ID', 'Network']) - raw_output = self.openstack( - 'network segment list ' - '--network ' + self.NETWORK_NAME + ' ' + - opts + def test_network_segment_create_delete(self): + name = uuid.uuid4().hex + json_output = json.loads(self.openstack( + ' network segment create -f json ' + + '--network ' + self.NETWORK_ID + ' ' + + '--network-type geneve ' + + '--segment 2055 ' + + name + )) + self.assertEqual( + name, + json_output["name"], ) - raw_output_row = raw_output.split('\n')[0] - self.NETWORK_SEGMENT_ID = raw_output_row.split(' ')[0] - def test_network_segment_create_delete(self): - opts = self.get_opts(['id']) raw_output = self.openstack( - ' network segment create --network ' + self.NETWORK_ID + - ' --network-type geneve ' + - ' --segment 2055 test_segment ' + opts + 'network segment delete ' + name, ) - network_segment_id = raw_output.strip('\n') - raw_output = self.openstack('network segment delete ' + - network_segment_id) self.assertOutput('', raw_output) def test_network_segment_list(self): - opts = self.get_opts(['ID']) - raw_output = self.openstack('network segment list' + opts) - self.assertIn(self.NETWORK_SEGMENT_ID, raw_output) + name = uuid.uuid4().hex + json_output = json.loads(self.openstack( + ' network segment create -f json ' + + '--network ' + self.NETWORK_ID + ' ' + + '--network-type geneve ' + + '--segment 2055 ' + + name + )) + network_segment_id = json_output.get('id') + network_segment_name = json_output.get('name') + self.addCleanup( + self.openstack, + 'network segment delete ' + network_segment_id + ) + self.assertEqual( + name, + json_output["name"], + ) + + json_output = json.loads(self.openstack( + 'network segment list -f json' + )) + item_map = { + item.get('ID'): item.get('Name') for item in json_output + } + self.assertIn(network_segment_id, item_map.keys()) + self.assertIn(network_segment_name, item_map.values()) + + def test_network_segment_set_show(self): + name = uuid.uuid4().hex + json_output = json.loads(self.openstack( + ' network segment create -f json ' + + '--network ' + self.NETWORK_ID + ' ' + + '--network-type geneve ' + + '--segment 2055 ' + + name + )) + self.addCleanup( + self.openstack, + 'network segment delete ' + name + ) + self.assertIsNone( + json_output["description"], + ) - def test_network_segment_set(self): new_description = 'new_description' - raw_output = self.openstack('network segment set ' + - '--description ' + new_description + - ' ' + self.NETWORK_SEGMENT_ID) - self.assertOutput('', raw_output) - opts = self.get_opts(['description']) - raw_output = self.openstack('network segment show ' + - self.NETWORK_SEGMENT_ID + opts) - self.assertEqual(new_description + "\n", raw_output) + cmd_output = self.openstack( + 'network segment set ' + + '--description ' + new_description + ' ' + + name + ) + self.assertOutput('', cmd_output) - def test_network_segment_show(self): - opts = self.get_opts(['network_id']) - raw_output = self.openstack('network segment show ' + - self.NETWORK_SEGMENT_ID + opts) - self.assertEqual(self.NETWORK_ID + "\n", raw_output) + json_output = json.loads(self.openstack( + 'network segment show -f json ' + + name + )) + self.assertEqual( + new_description, + json_output["description"], + ) From 53bea90a75774cb7468884da43b55381b491dc46 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 16 Aug 2017 07:50:55 +0000 Subject: [PATCH 1764/3095] Imported Translations from Zanata For more information about this automatic import see: http://docs.openstack.org/developer/i18n/reviewing-translation-import.html Change-Id: I33c68188f316b533cc702e0b3ef97c854681bca1 --- .../locale/de/LC_MESSAGES/openstackclient.po | 541 -- .../tr_TR/LC_MESSAGES/openstackclient.po | 6805 +++++++++++++++++ .../zh_TW/LC_MESSAGES/openstackclient.po | 423 - 3 files changed, 6805 insertions(+), 964 deletions(-) delete mode 100644 openstackclient/locale/de/LC_MESSAGES/openstackclient.po create mode 100644 openstackclient/locale/tr_TR/LC_MESSAGES/openstackclient.po delete mode 100644 openstackclient/locale/zh_TW/LC_MESSAGES/openstackclient.po diff --git a/openstackclient/locale/de/LC_MESSAGES/openstackclient.po b/openstackclient/locale/de/LC_MESSAGES/openstackclient.po deleted file mode 100644 index 06f1c8d672..0000000000 --- a/openstackclient/locale/de/LC_MESSAGES/openstackclient.po +++ /dev/null @@ -1,541 +0,0 @@ -# Translations template for python-openstackclient. -# Copyright (C) 2015 ORGANIZATION -# This file is distributed under the same license as the -# python-openstackclient project. -# -# Translators: -# Ettore Atalan , 2014-2015 -# Andreas Jaeger , 2016. #zanata -msgid "" -msgstr "" -"Project-Id-Version: python-openstackclient 2.5.1.dev51\n" -"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2016-06-03 19:37+0000\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2016-06-02 01:43+0000\n" -"Last-Translator: Andreas Jaeger \n" -"Language: de\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" -"Generated-By: Babel 2.0\n" -"X-Generator: Zanata 3.7.3\n" -"Language-Team: German\n" - -msgid "Add a property to (repeat option to set multiple properties)" -msgstr "" -"Fügen Sie eine Eigenschaft zu hinzu (wiederholen Sie die Option, um " -"mehrere Eigenschaften festzulegen)" - -msgid "Allow disk over-commit on the destination host" -msgstr "Festplattenüberladung auf dem Zielhost erlauben" - -msgid "Availability zone name" -msgstr "Name der Verfügbarkeitszone" - -msgid "Complete\n" -msgstr "Fertig\n" - -msgid "Confirm server resize is complete" -msgstr "Bestätigen Sie, ob die Größenänderung des Servers abgeschlossen ist" - -msgid "" -"Create a NIC on the server. Specify option multiple times to create multiple " -"NICs. Either net-id or port-id must be provided, but not both. net-id: " -"attach NIC to network with this UUID, port-id: attach NIC to port with this " -"UUID, v4-fixed-ip: IPv4 fixed address for NIC (optional), v6-fixed-ip: IPv6 " -"fixed address for NIC (optional)." -msgstr "" -"Erstellen Sie ein NIC auf dem Server. Geben Sie die Option mehrfach an, um " -"mehrere NICs zu erstellen. Entweder net-id oder port-id müssen " -"bereitgestellt werden, aber nicht beide. net-id: NIC an Netzwerk mit dieser " -"UUID binden, port-id: NIC an Port mit dieser UUID binden, v4-fixed-ip: Feste " -"IPv4-Adresse für NIC (optional), v6-fixed-ip: Feste IPv6-Adresse für NIC " -"(optional)." - -msgid "" -"Create credentials for user (name or ID; default: current authenticated user)" -msgstr "" -"Anmeldedaten für Benutzer erstellen (Name oder Kennung; Standard: aktuell " -"authentifizierter Benutzer)" - -msgid "" -"Create credentials in project (name or ID; default: current authenticated " -"project)" -msgstr "" -"Anmeldedaten in Projekt erstellen (Name oder Kennung; Standard: aktuell " -"authentifiziertes Projekt)" - -msgid "Credentials access key" -msgstr "Anmeldedaten-Zugriffsschlüssel" - -msgid "Default project (name or ID)" -msgstr "Standardprojekt (Name oder Kennung)" - -msgid "Delete credentials for user (name or ID)" -msgstr "Anmeldedaten für Benutzer löschen (name or ID)" - -msgid "Destination port (ssh -p option)" -msgstr "Zielport (ssh -p Option)" - -msgid "Disable a service" -msgstr "Dienst deaktivieren" - -msgid "Disable project" -msgstr "Projekt deaktivieren" - -msgid "Disable user" -msgstr "Benutzer deaktivieren" - -msgid "Display server diagnostics information" -msgstr "Serverdiagnoseinformationen anzeigen" - -msgid "Do not over-commit disk on the destination host (default)" -msgstr "Festplatte auf dem Zielhost nicht überladen (Standard)" - -msgid "Enable project" -msgstr "Projekt aktivieren" - -msgid "Enable project (default)" -msgstr "Projekt aktivieren (Standardeinstellung)" - -msgid "Enable user (default)" -msgstr "Benutzer aktivieren (Standardeinstellung)" - -msgid "Endpoint ID to delete" -msgstr "Zu löschende Endpunktkennung" - -msgid "Error creating server\n" -msgstr "Fehler beim Erstellen des Servers\n" - -msgid "Error creating server snapshot\n" -msgstr "Fehler beim Erstellen der Server-Schattenkopie\n" - -#, python-format -msgid "Error creating server: %s" -msgstr "Fehler beim Erstellen des Servers: %s" - -msgid "Error deleting server\n" -msgstr "Fehler beim Löschen des Servers\n" - -#, python-format -msgid "Error deleting server: %s" -msgstr "Fehler beim Löschen des Servers: %s" - -msgid "Error retrieving diagnostics data" -msgstr "Fehler beim Abrufen der Diagnosedaten" - -msgid "Filter by parent region ID" -msgstr "Nach übergeordneter Regionskennung filtern" - -msgid "Filter list by user (name or ID)" -msgstr "Liste nach Benutzer filtern (Name oder Kennung)" - -msgid "Filter users by (name or ID)" -msgstr "Benutzer nach filtern (Name oder Kennung)" - -msgid "Filter users by project (name or ID)" -msgstr "Benutzer nach Projekt filtern (Name oder Kennung)" - -msgid "Floating IP address" -msgstr "Bewegliche IP-Adresse" - -msgid "Hints for the scheduler (optional extension)" -msgstr "Hinweise für den Planer (optionale Erweiterung)" - -msgid "Include (name or ID)" -msgstr " miteinbeziehen (Name oder Kennung)" - -msgid "Include (name or ID)" -msgstr " miteinbeziehen (Name oder Kennung)" - -msgid "Include all projects (admin only)" -msgstr "Alle Projekte miteinbeziehen (nur Administrator)" - -msgid "Keypair to inject into this server (optional extension)" -msgstr "In diesem Server einzufügendes Schlüsselpaar (optionale Erweiterung)" - -msgid "List additional fields in output" -msgstr "Zusätzliche Felder in der Ausgabe auflisten" - -msgid "Login name (ssh -l option)" -msgstr "Anmeldename (ssh -l Option)" - -msgid "MD5 hash" -msgstr "MD5-Hashwert" - -msgid "" -"Map block devices; map is ::: " -"(optional extension)" -msgstr "" -"Blockorientierte Geräte abbilden; Abbildung ist :::" -" (optionale Erweiterung)" - -msgid "Maximum number of servers to launch (default=1)" -msgstr "Maximale Anzahl der zu startenden Server (Standard=1)" - -msgid "Minimum number of servers to launch (default=1)" -msgstr "Minimale Anzahl der zu startenden Server (Standard=1)" - -msgid "Name of new image (default is server name)" -msgstr "Name des neuen Abbilds (Servername ist Standard)" - -msgid "Name or ID of security group to remove from server" -msgstr "Name oder Kennung der vom Server zu entfernenden Sicherheitsgruppe" - -msgid "Name or ID of server to use" -msgstr "Name oder Kennung des zu verwendenden Servers" - -msgid "New endpoint admin URL" -msgstr "Administrator-URL des neuen Endpunktes" - -msgid "New endpoint internal URL" -msgstr "Interne URL des neuen Endpunktes" - -msgid "New endpoint public URL (required)" -msgstr "Öffentliche URL des neuen Endpunktes (erforderlich)" - -msgid "New endpoint region ID" -msgstr "Neue Endpunkt-Regionskennung" - -msgid "New endpoint service (name or ID)" -msgstr "Neuer Endpunktdienst (Name oder Kennung)" - -msgid "New parent region ID" -msgstr "Neue übergeordnete Regionskennung" - -msgid "New password: " -msgstr "Neues Passwort:" - -msgid "New project name" -msgstr "Name des neuen Projekts" - -msgid "New region ID" -msgstr "Neue Regionskennung" - -msgid "New region description" -msgstr "Beschreibung des neuen Bereichs" - -msgid "New role name" -msgstr "Name der neuen Rolle" - -msgid "New server name" -msgstr "Neuer Servername" - -msgid "New service description" -msgstr "Beschreibung des neuen Dienstes" - -msgid "New service name" -msgstr "Name des neuen Dienstes" - -msgid "New service type (compute, image, identity, volume, etc)" -msgstr "Neuer Diensttyp (Berechnen, Abbild, Identität, Datenträger, usw.)" - -msgid "New user name" -msgstr "Neuer Benutzername" - -#, python-format -msgid "No service catalog with a type, name or ID of '%s' exists." -msgstr "Kein Dienstkatalog mit Typ, Namen oder Kennung von '%s' ist vorhanden." - -msgid "Only return instances that match the reservation" -msgstr "Nur Instanzen zurückgeben, die der Einschränkung entsprechen" - -msgid "Options in ssh_config(5) format (ssh -o option)" -msgstr "Optionen im ssh_config(5)-Format (ssh -o Option)" - -msgid "Parent region ID" -msgstr "Übergeordnete Regionskennung" - -msgid "Passwords do not match, password unchanged" -msgstr "Passwörter stimmen nicht überein, Passwort unverändert" - -msgid "Perform a block live migration" -msgstr "Blockorientierte Live-Migration ausführen" - -msgid "Perform a hard reboot" -msgstr "Harten Neustart ausführen" - -msgid "Perform a shared live migration (default)" -msgstr "Gemeinsame Live-Migration ausführen (Standard)" - -msgid "Perform a soft reboot" -msgstr "Weichen Neustart ausführen" - -msgid "Private key file (ssh -i option)" -msgstr "Private Schlüsseldatei (ssh -i Option)" - -msgid "Project description" -msgstr "Projektbeschreibung" - -msgid "Project must be specified" -msgstr "Projekt muss angegeben werden" - -msgid "Project to display (name or ID)" -msgstr "Anzuzeigendes Projekt (Name oder Kennung)" - -msgid "Project to modify (name or ID)" -msgstr "Zu änderndes Projekt (Name oder Kennung)" - -msgid "Project(s) to delete (name or ID)" -msgstr "Zu löschende(s) Projekt(e) (Name oder Kennung)" - -msgid "Prompt interactively for password" -msgstr "Interaktiv nach dem Passwort abfragen" - -msgid "" -"Property to add/change for this server (repeat option to set multiple " -"properties)" -msgstr "" -"Zu hinzufügende/ändernde Eigenschaft für diesen Server (wiederholen Sie die " -"Option, um mehrere Eigenschaften festzulegen)" - -msgid "Region ID to delete" -msgstr "Zu löschende Regionskennung" - -msgid "Region to display" -msgstr "Anzuzeigende Region" - -msgid "Region to modify" -msgstr "Zu ändernde Region" - -msgid "Regular expression to match IP addresses" -msgstr "Regulärer Ausdruck zum Abgleichen mit IP-Adressen" - -msgid "Regular expression to match IPv6 addresses" -msgstr "Regulärer Ausdruck zum Abgleichen mit IPv6-Adressen" - -msgid "Regular expression to match instance name (admin only)" -msgstr "" -"Regulärer Ausdruck zum Abgleichen des Instanznamens (nur Administrator) " - -msgid "Regular expression to match names" -msgstr "Regulärer Ausdruck zum Abgleichen mit Namen" - -msgid "Resize server to specified flavor" -msgstr "Servergröße auf angegebene Variante ändern" - -msgid "Restore server state before resize" -msgstr "Serverstatus vor der Größenänderung wiederherstellen" - -msgid "Return existing domain" -msgstr "Vorhandene Domäne zurückgeben" - -msgid "Return existing group" -msgstr "Vorhandene Gruppe zurückgeben" - -msgid "Return existing project" -msgstr "Vorhandenes Projekt zurückgeben" - -msgid "Return existing role" -msgstr "Vorhandene Rolle zurückgeben" - -msgid "Return existing user" -msgstr "Vorhandenen Benutzer zurückgeben" - -msgid "Retype new password: " -msgstr "Neues Passwort erneut eingeben:" - -msgid "Role to add to : (name or ID)" -msgstr "Zu : hinzuzufügende Rolle (Name oder Kennung)" - -msgid "Role to display (name or ID)" -msgstr "Anzuzeigende Rolle (Name oder Kennung)" - -msgid "Role to remove (name or ID)" -msgstr "Zu entfernende Rolle (Name oder Kennung)" - -msgid "Role(s) to delete (name or ID)" -msgstr "Zu löschende Rolle(n) (Name oder Kennung)" - -msgid "Search by hostname" -msgstr "Nach Hostname suchen" - -msgid "Search by server status" -msgstr "Nach Serverstatus suchen" - -msgid "Search by user (admin only) (name or ID)" -msgstr "Nach Benutzer suchen (nur Administrator) (Name oder Kennung)" - -msgid "Security group to add (name or ID)" -msgstr "Zu hinzufügende Sicherheitsgruppe (Name oder Kennung)" - -msgid "Select an availability zone for the server" -msgstr "Wählen Sie eine Verfügbarkeitszone für den Server aus" - -msgid "Server (name or ID)" -msgstr "Server (Name oder Kennung)" - -msgid "Server internal device name for volume" -msgstr "Serverinterner Gerätename für Datenträger" - -msgid "Server(s) to delete (name or ID)" -msgstr "Zu löschende(r) Server (Name oder Kennung)" - -msgid "Service to delete (name or ID)" -msgstr "Zu löschender Dienst (Name oder Kennung)" - -msgid "Service to display (type or name)" -msgstr "Anzuzeigender Dienst (Typ oder Name)" - -msgid "Service to display (type, name or ID)" -msgstr "Anzuzeigender Dienst (Typ, Name oder Kennung)" - -msgid "Set a project property (repeat option to set multiple properties)" -msgstr "" -"Legen Sie eine Projekteigenschaft fest (wiederholen Sie die Option, um " -"mehrere Eigenschaften festzulegen)" - -msgid "" -"Set a scope, such as a project or domain, set a project scope with --os-" -"project-name, OS_PROJECT_NAME or auth.project_name, set a domain scope with " -"--os-domain-name, OS_DOMAIN_NAME or auth.domain_name" -msgstr "" -"Setzen Sie eine Eigenschaft, wie Projekt oder Domäne, setzen Sie eine " -"Projekteigenschaft mit --os-project-name, OS_PROJECT_NAME oder auth." -"project_name, setzen Sie eine Dömaneneigenschaft mit --os-domain-name, " -"OS_DOMAIN_NAME oder auth.domain_name." - -msgid "" -"Set a service AUTH_URL, with --os-auth-url, OS_AUTH_URL or auth.auth_url" -msgstr "" -"Legen Sie eine Dienst-AUTH_URL mit --os-auth-url, OS_AUTH_URL oder auth." -"auth_url fest" - -msgid "Set a service URL, with --os-url, OS_URL or auth.url" -msgstr "Legen Sie eine Dienst-URL mit --os-url, OS_URL oder auth.url fest" - -msgid "Set a token with --os-token, OS_TOKEN or auth.token" -msgstr "Legen Sie einen Token mit --os-token, OS_TOKEN oder auth.token fest" - -msgid "Set a username with --os-username, OS_USERNAME, or auth.username" -msgstr "" -"Legen Sie einen Benutzernamen mit --os-username, OS_USERNAME oder auth." -"username fest" - -msgid "" -"Set an authentication URL, with --os-auth-url, OS_AUTH_URL or auth.auth_url" -msgstr "" -"Legen Sie eine Authentifizierungs-URL mit --os-auth-url, OS_AUTH_URL oder " -"auth.auth_url fest" - -msgid "Set default project (name or ID)" -msgstr "Standardprojekt festlegen (Name oder Kennung)" - -msgid "Set new root password (interactive only)" -msgstr "Neues root-Passwort festlegen (Nur interaktiv)" - -msgid "Set project description" -msgstr "Projektbeschreibung festlegen" - -msgid "Set project name" -msgstr "Projektname festlegen" - -msgid "Set user email address" -msgstr "E-Mail-Adresse des Benutzers festlegen" - -msgid "Set user name" -msgstr "Benutzername festlegen" - -msgid "Set user password" -msgstr "Benutzerpasswort festlegen" - -msgid "Show credentials for user (name or ID)" -msgstr "Anmeldedaten für Benutzer anzeigen (name or ID)" - -msgid "Show service catalog information" -msgstr "Dienstkataloginformation anzeigen" - -msgid "Target hostname" -msgstr "Zielhostname" - -msgid "" -"The argument --type is deprecated, use service create --name " -"type instead." -msgstr "" -"Das Argument --type ist veraltet, verwenden Sie stattdessen service create --" -"name type." - -msgid "Token to be deleted" -msgstr "Zu löschender Token" - -msgid "URL" -msgstr "URL" - -msgid "Use only IPv4 addresses" -msgstr "Nur IPv4-Adressen verwenden" - -msgid "Use only IPv6 addresses" -msgstr "Nur IPv6-Adressen verwenden" - -msgid "Use other IP address (public, private, etc)" -msgstr "Andere IP-Adresse verwenden (öffentlich, privat, usw.)" - -msgid "Use private IP address" -msgstr "Private IP-Adresse verwenden" - -msgid "Use public IP address" -msgstr "Öffentliche IP-Adresse verwenden" - -msgid "" -"Use specified volume as the config drive, or 'True' to use an ephemeral drive" -msgstr "" -"Benutzerdefinierter Datenträger als Konfigurationslaufwerk oder 'True', um " -"ein flüchtiges Laufwerk zu verwenden" - -msgid "User data file to serve from the metadata server" -msgstr "Vom Metadatenserver anzubietende Benutzerdatendatei" - -msgid "User must be specified" -msgstr "Benutzer muss angegeben werden" - -msgid "User to change (name or ID)" -msgstr "Zu ändernder Benutzer (Name oder Kennung)" - -msgid "User to display (name or ID)" -msgstr "Anzuzeigender Benutzer (Name oder Kennung)" - -msgid "User to list (name or ID)" -msgstr "Aufzulistender Benutzer (Name oder Kennung)" - -msgid "User(s) to delete (name or ID)" -msgstr "Zu löschende(r) Benutzer (Name oder Kennung)" - -msgid "Version" -msgstr "Version" - -msgid "Volume to add (name or ID)" -msgstr "Zu hinzufügender Datenträger (Name oder Kennung)" - -msgid "Volume to remove (name or ID)" -msgstr "Zu entfernender Datenträger (Name oder Kennung)" - -msgid "Wait for build to complete" -msgstr "Warten Sie, bis die Herstellung abgeschlossen ist" - -msgid "Wait for delete to complete" -msgstr "Warten Sie, bis das Löschen abgeschlossen ist" - -msgid "Wait for image create to complete" -msgstr "Warten Sie, bis die Abbilderstellung abgeschlossen ist" - -msgid "Wait for reboot to complete" -msgstr "Warten Sie, bis der Neustart abgeschlossen ist" - -msgid "Wait for rebuild to complete" -msgstr "Warten Sie, bis die Wiederherstellung abgeschlossen ist" - -msgid "Wait for resize to complete" -msgstr "Warten Sie, bis die Größenänderung abgeschlossen ist" - -msgid "either net-id or port-id should be specified but not both" -msgstr "entweder net-id oder port-id sollten angegeben sein, aber nicht beide" - -msgid "max instances should be > 0" -msgstr "max. Instanzen sollten > 0 sein" - -msgid "min instances should be <= max instances" -msgstr "min. Instanzen sollten <= max. Instanzen sein" - -msgid "min instances should be > 0" -msgstr "min. Instanzen sollten > 0 sein" diff --git a/openstackclient/locale/tr_TR/LC_MESSAGES/openstackclient.po b/openstackclient/locale/tr_TR/LC_MESSAGES/openstackclient.po new file mode 100644 index 0000000000..f3470f1854 --- /dev/null +++ b/openstackclient/locale/tr_TR/LC_MESSAGES/openstackclient.po @@ -0,0 +1,6805 @@ +# Andreas Jaeger , 2017. #zanata +# işbaran akçayır , 2017. #zanata +msgid "" +msgstr "" +"Project-Id-Version: python-openstackclient 3.12.1.dev21\n" +"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" +"POT-Creation-Date: 2017-08-14 16:23+0000\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"PO-Revision-Date: 2017-08-15 12:09+0000\n" +"Last-Translator: Andreas Jaeger \n" +"Language-Team: Turkish (Turkey)\n" +"Language: tr-TR\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"X-Generator: Zanata 3.9.6\n" +"X-POOTLE-MTIME: 1502656444.000000\n" + +#, python-format +msgid "" +"\"Create\" rule command for type \"%(rule_type)s\" requires arguments " +"%(args)s" +msgstr "" +"\"%(rule_type)s\" türü için \"create\" kural komutu %(args)s argümanlarını " +"gerektirir" + +msgid "\"Create\" rule command requires argument \"type\"" +msgstr "\"Create\" kural komutu için \"type\" argümanı zorunludur" + +#, python-format +msgid "%(errors)s of %(total)s groups failed to delete." +msgstr "%(total)s gruptan %(errors)s grup silinirken hata oluştu." + +#, python-format +msgid "%(errors)s of %(total)s projects failed to delete." +msgstr "%(total)s projeden %(errors)s tanesinin silme işlemi başarısız." + +#, python-format +msgid "%(errors)s of %(total)s roles failed to delete." +msgstr "%(total)s rolden %(errors)s tanesinin silme işlemi başarısız." + +#, python-format +msgid "%(errors)s of %(total)s trusts failed to delete." +msgstr "%(total)s güvenden %(errors)s tanesi silinirken hata oluştu." + +#, python-format +msgid "%(errors)s of %(total)s users failed to delete." +msgstr "%(total)s kullanıcıdan %(errors)s kullanıcıyı silme işlemi başarısız." + +#, python-format +msgid "%(num)s of %(total)s %(resource)ss failed to delete." +msgstr "%(total)s %(resource)s'tan %(num)s tanesi silinirken hata oluştu." + +#, python-format +msgid "%(result)s of %(total)s %(resource)ss failed to delete." +msgstr "%(total)s'ın %(result)s %(resource)s'ların silinmesi başarısız." + +#, python-format +msgid "%(result)s of %(total)s EC2 keys failed to delete." +msgstr "" +"%(total)s EC2 anahtarlarından %(result)s anahtarın silme işlemi başarısız." + +#, python-format +msgid "%(result)s of %(total)s QoS policies failed to delete." +msgstr "" +"%(total)s QoS politikalarından %(result)s tanesi silinirken hata oluştu." + +#, python-format +msgid "%(result)s of %(total)s QoS specifications failed to delete." +msgstr "%(total)s QoS özelliklerinden %(result)s silme işlemi başarısız." + +#, python-format +msgid "%(result)s of %(total)s RBAC policies failed to delete." +msgstr "" +"%(total)s RBAC politikasından %(result)s tanesi silinirken hata oluştu." + +#, python-format +msgid "%(result)s of %(total)s address scopes failed to delete." +msgstr "%(total)s adres kapsamından %(result)s tanesi silinirken hata oluştu." + +#, python-format +msgid "%(result)s of %(total)s agents failed to delete." +msgstr "%(total)s ajandan %(result)s ajanın silinmesi başarısız." + +#, python-format +msgid "%(result)s of %(total)s aggregates failed to delete." +msgstr "%(total)s kümeden %(result)s tanesinin silme işlemi başarısız." + +#, python-format +msgid "%(result)s of %(total)s backups failed to delete." +msgstr "%(total)s yedeklerden %(result)s silinirken hata oluştu." + +#, python-format +msgid "%(result)s of %(total)s compute services failed to delete." +msgstr "" +"%(total)s hesaplama servislerinin %(result)s tanesinin silme işlemi " +"başarısız." + +#, python-format +msgid "%(result)s of %(total)s consistency group snapshots failed to delete." +msgstr "" +"%(total)s tutarlılık grubu anlık görüntülerinden %(result)s silinme işlemi " +"başarısız." + +#, python-format +msgid "%(result)s of %(total)s consistency groups failed to delete." +msgstr "%(total)s tutarlılık gruplarından %(result)s silinirken hata oluştu." + +#, python-format +msgid "%(result)s of %(total)s consumers failed to delete." +msgstr "%(total)s alıcılardan %(result)s tanesi silinirken hata oluştu." + +#, python-format +msgid "%(result)s of %(total)s credential failed to delete." +msgstr "%(total)s kimlik bilgisinden %(result)s tanesi silinirken hata oluştu." + +#, python-format +msgid "%(result)s of %(total)s domains failed to delete." +msgstr "%(total)s alandan %(result)s tanesi silinirken hata oluştu." + +#, python-format +msgid "%(result)s of %(total)s endpoints failed to delete." +msgstr "%(total)s uç noktalarından %(result)s silinmesi başarısız." + +#, python-format +msgid "%(result)s of %(total)s federation protocols failed to delete." +msgstr "" +"%(total)s federasyon protokolünden %(result)s tanesi silinirken hata oluştu." + +#, python-format +msgid "%(result)s of %(total)s flavor_profiles failed to delete." +msgstr "%(total)s flavor_profiles'in %(result)s tanesi silinirken hata oluştu." + +#, python-format +msgid "%(result)s of %(total)s flavors failed to delete." +msgstr "%(total)s flavor'dan %(result)s flavor'ın silme işlemi başarısız." + +#, python-format +msgid "%(result)s of %(total)s identity providers failed to delete." +msgstr "" +"%(total)s kimlik sağlayıcıdan %(result)s tanesi silinirken hata oluştu." + +#, python-format +msgid "%(result)s of %(total)s keys failed to delete." +msgstr "%(total)s anahtarlardan %(result)s anahtarın silme işlemi başarısız." + +#, python-format +msgid "%(result)s of %(total)s mappings failed to delete." +msgstr "%(total)s eşleştirmeden %(result)s tanesi silinirken hata oluştu." + +#, python-format +msgid "%(result)s of %(total)s meter rules failed to delete." +msgstr "" +"%(total)s ölçek kurallarından %(result)s tanesi silinirken hata oluştu." + +#, python-format +msgid "%(result)s of %(total)s meters failed to delete." +msgstr "%(total)s ölçekten %(result)s tanesi silinirken hata oluştu." + +#, python-format +msgid "%(result)s of %(total)s network agents failed to delete." +msgstr "%(total)s ağ ajanından %(result)s tanesi silinirken hata oluştu." + +#, python-format +msgid "%(result)s of %(total)s network segments failed to delete." +msgstr "%(total)s ağ dilimlerinin %(result)s tanesi silinirken hata oluştu." + +#, python-format +msgid "%(result)s of %(total)s policys failed to delete." +msgstr "%(total)s politikadan %(result)s tanesi silinirken hata oluştu." + +#, python-format +msgid "%(result)s of %(total)s ports failed to delete." +msgstr "" +"%(total)s bağlantı noktasından %(result)s tanesi silinirken hata oluştu." + +#, python-format +msgid "%(result)s of %(total)s regions failed to delete." +msgstr "%(total)s bölgeden %(result)s tanesi silinirken hata oluştu." + +#, python-format +msgid "%(result)s of %(total)s routers failed to delete." +msgstr "%(total)s yönlendiricilerden %(result)s silme işlemi başarısız." + +#, python-format +msgid "%(result)s of %(total)s server groups failed to delete." +msgstr "%(total)s sunucu grubundan %(result)s silinmesi başarısız." + +#, python-format +msgid "%(result)s of %(total)s service providers failed to delete." +msgstr "" +"%(total)s servis sağlayıcıdan %(result)s tanesinin silme işlemi başarısız." + +#, python-format +msgid "%(result)s of %(total)s services failed to delete." +msgstr "%(total)s servisten %(result)s tanesini silme başarısız." + +#, python-format +msgid "%(result)s of %(total)s snapshots failed to delete." +msgstr "%(total)s anlık görüntülerden %(result)s silinirken hata oluştu." + +#, python-format +msgid "%(result)s of %(total)s subnet pools failed to delete." +msgstr "" +"%(total)s alt ağ havuzlarından %(result)s sonucunu silme işlemi başarısız." + +#, python-format +msgid "%(result)s of %(total)s subnets failed to delete." +msgstr "%(total)s altağlardan %(result)s silinirken hata oluştu." + +#, python-format +msgid "%(result)s of %(total)s users not added to group %(group)s." +msgstr "" +"%(group)s grubuna %(total)s kullanıcıdan %(result)s kullanıcı eklenmedi." + +#, python-format +msgid "%(result)s of %(total)s users not removed from group %(group)s." +msgstr "" +"%(total)s kullanıcıdan %(result)s tanesi %(group)s grubundan silinmedi." + +#, python-format +msgid "%(result)s of %(total)s volume transfer requests failed to delete" +msgstr "" +"%(total)s disk bölümü aktarım isteklerinden %(result)s silinirken hata oluştu" + +#, python-format +msgid "%(result)s of %(total)s volume types failed to delete." +msgstr "%(total)s disk bölümü türlerinden %(result)s silinirken hata oluştu." + +#, python-format +msgid "%(result)s of %(total)s volumes failed to add." +msgstr "%(total)s disk bölümlerinden %(result)s eklenirken hata." + +#, python-format +msgid "%(result)s of %(total)s volumes failed to delete." +msgstr "%(total)s disk bölümlerinden %(result)s tanesi silinirken hata oluştu." + +#, python-format +msgid "%(result)s of %(total)s volumes failed to remove." +msgstr "%(total)s disk bölümünden %(result)s silinirken hata oluştu." + +#, python-format +msgid "%(user)s in group %(group)s\n" +msgstr "%(group)s grubundaki %(user)s\n" + +#, python-format +msgid "%(user)s not added to group %(group)s: %(e)s" +msgstr "%(user)s %(group)s grubuna eklenmedi: %(e)s" + +#, python-format +msgid "%(user)s not in group %(group)s\n" +msgstr "%(group)s grubunda olmayan %(user)s\n" + +#, python-format +msgid "%(user)s not removed from group %(group)s: %(e)s" +msgstr "%(group)s'dan %(user)s kaldırılmadı: %(e)s" + +#, python-format +msgid "%s not found" +msgstr "%s bulunamadı" + +msgid "" +"'--availability-zone' option will not work if creating consistency group " +"from source" +msgstr "" +"'--availability-zone' seçeneği tutarlılık grubu kaynaktan oluşturulursa " +"çalışmaz." + +msgid "" +"'--encryption-provider' should be specified while creating a new encryption " +"type" +msgstr "" +"Yeni bir şifreleme türü oluştururken '--encryption-provider' belirtilmelidir" + +msgid "" +"'--force' option will not work when you create new volume snapshot from an " +"existing remote volume snapshot" +msgstr "" +"Varolan uzak disk bölümünden yeni görüntüsünden yeni disk bölümü anlık " +"görüntüsü oluştururken 'force' seçeneği çalışmaz" + +msgid "'--retype-policy' option will not work without '--type' option" +msgstr "'--retype-policy' seçeneği '--type' seçeneği olmadan çalışmaz" + +msgid "--project is only allowed with --private" +msgstr "--project sadece --private ile kullanılabilir" + +msgid "" +"--size is a required option if snapshot or source volume is not specified." +msgstr "" +"Anlık görüntü veya kaynak disk bölümü belirtilmezse --size gerekli bir " +"seçenektir." + +msgid "A service URL where SAML assertions are being sent (required)" +msgstr "SAML bildirimlerinin gönderildiği bir hizmet URL'i (gerekli)" + +msgid "Accept the image membership" +msgstr "İmaj üyeliğini kabul et" + +msgid "Accept volume transfer request." +msgstr "Disk bölümü aktarım isteğini kabul et." + +msgid "" +"Action for the RBAC policy (\"access_as_external\" or \"access_as_shared\")" +msgstr "" +"RBAC politika için eylem (\"access_as_external\" veya \"access_as_shared\")" + +msgid "Activate the image" +msgstr "İmajı aktifleştir" + +msgid "" +"Add a policy to ('affinity' or 'anti-affinity', default to 'affinity')" +msgstr "" +"'e bir politika ekle ('affinity' veya 'anti-affinity', varsayılan " +"'affinity' için)" + +msgid "Add a port to a router" +msgstr "Yönlendiriciye bir bağlantı noktası ekle" + +msgid "Add a property to (repeat option to set multiple properties)" +msgstr "" +"'e bir özellik ekle (birden fazla özellik eklemek için seçeneği " +"tekrarla)" + +msgid "Add a service profile to a network flavor" +msgstr "Ağ flavor'ına bir servis profili ekle" + +msgid "Add a subnet to a router" +msgstr "Bir yönlendiriciye bir alt ağ ekle" + +msgid "" +"Add allowed-address pair associated with this port: ip-address=[," +"mac-address=] (repeat option to set multiple allowed-address " +"pairs)" +msgstr "" +"Bu bağlantı noktası ile ilişkili izin verilen adres çiftini ekle: ip-" +"address=[,mac-address=] (birden fazla izin verilen " +"adres çifti ayarlamak için seçeneği tekrarlayın)" + +msgid "Add fixed IP address to server" +msgstr "Sunucuya sabit IP adresi ekle" + +msgid "Add floating IP address to server" +msgstr "Sunucuya kayan IP adresi ekle" + +msgid "Add host to aggregate" +msgstr "Kümeye sunucu ekle" + +msgid "Add network to a DHCP agent" +msgstr "Bir DHCP ajanına ağ ekle" + +msgid "Add network to an agent" +msgstr "Bir ajana ağ ekle" + +msgid "Add port to server" +msgstr "Sunucuya bağlantı noktası ekle" + +msgid "Add role to project:user" +msgstr "Proje:kullanıcıya rol ekle" + +msgid "Add router to an L3 agent" +msgstr "L3 aracısına yönlendirici ekleyin" + +msgid "Add router to an agent" +msgstr "Bir ajana yönlendirici ekle" + +msgid "Add security group to server" +msgstr "Sunucuya güvenlik grubu ekle" + +msgid "Add user to group" +msgstr "Gruba kullanıcı ekle" + +msgid "Add volume to server" +msgstr "Disk bölümünü sunucuya ekle" + +msgid "Add volume(s) to consistency group" +msgstr "Uyum grubuna disk bölümleri ekle" + +msgid "" +"Additional route for this subnet e.g.: destination=10.10.0.0/16," +"gateway=192.168.71.254 destination: destination subnet (in CIDR notation) " +"gateway: nexthop IP address (repeat option to add multiple routes)" +msgstr "" +"Bu alt ağ için ek yönlendirici örn: hedef=10.10.0.0/16,geçit=192.168.71.254 " +"hedef: hedef alt ağ (CIDR gösteriminde) geçit: bir sonraki durak IP adresi " +"(birden fazla yölendirici eklemek için tekrarlanacak seçenek)" + +msgid "Address scope to display (name or ID)" +msgstr "Gösterilecek adres kapsamı (isim veya ID)" + +msgid "Address scope to modify (name or ID)" +msgstr "Değiştirilecek adres kapsamı (ad veya kimlik)" + +msgid "Address scope(s) to delete (name or ID)" +msgstr "Silinecek adres kapsam(lar)ı (isim veya ID)" + +msgid "Adds a role assignment to a user or group on a domain or project" +msgstr "Bir alandaki veya projedeki bir kullanıcıya veya gruba rol atama ekler" + +msgid "Agent from which router will be removed (ID only)" +msgstr "Yönlendiricinin kaldırılacağı ajan (yalnızca ID)" + +msgid "Agent to which a network is added (ID only)" +msgstr "Bir ağ eklenecek ajan (sadece ID)" + +msgid "Agent to which a network is removed (ID only)" +msgstr "Ağın kaldırılacağı ajan (sadece ID)" + +msgid "Agent to which a router is added (ID only)" +msgstr "Yönlendiricinin eklendiği ajan (yalnızca ID)" + +msgid "Aggregate (name or ID)" +msgstr "Küme (isim veya ID)" + +msgid "Aggregate to display (name or ID)" +msgstr "Gösterilecek küme (isim veya ID)" + +msgid "Aggregate to modify (name or ID)" +msgstr "Düzenlenecek küme (isim veya ID)" + +msgid "Aggregate(s) to delete (name or ID)" +msgstr "Silinecek küme(ler) (isim veya ID)" + +msgid "Allocate port on host (ID only)" +msgstr " ana bilgisayarında bağlantı noktası ayır (sadece ID)" + +msgid "" +"Allocation pool IP addresses for this subnet e.g.: start=192.168.199.2," +"end=192.168.199.254 (repeat option to add multiple IP addresses)" +msgstr "" +"Bu alt ağ için ayırma havuzu IP adresleri örn: başlangıç=192.168.199.2," +"bitiş=192.168.199.254 (birden fazla IP adresi eklemek için seçeneği tekrarla)" + +msgid "" +"Allocation pool IP addresses to be removed from this subnet e.g.: " +"start=192.168.199.2,end=192.168.199.254 (repeat option to unset multiple " +"allocation pools)" +msgstr "" +"Bu altağdan silinecek IP adres tahsis havuzu örn: başlangıç=192.168.199.2," +"bitiş=192.168.199.254 (birden fazla tahsis havuzu ayarını kaldırmak için " +"seçeneği tekrarla)" + +msgid "" +"Allow to access private flavor (name or ID) (Must be used with --" +"private option)" +msgstr "" +"'nin özel flavor'a erişmesine izin verin (isim veya ID) (--private " +"seçeneği ile beraber kullanılmalı)" + +msgid "" +"Allow to access private type (name or ID) (Must be used with --" +"private option)" +msgstr "" +"Özel türe erişimek için 'ye izin ver (isim veya ID) (--private " +"seçeneği ile kullanılması zorunludur)" + +msgid "Allow delete in state other than error or available" +msgstr "Hata veya kullanılabilirden başka durumda silinmesine izin ver" + +msgid "Allow disk over-commit on the destination host" +msgstr "Hedef ana bilgisayarda disk aşırı-işlemeye izin ver" + +msgid "Allow image to be deleted (default)" +msgstr "İmajın silinmesine izin ver (varsayılan)" + +msgid "Allow to back up an in-use volume" +msgstr "Kullanımdaki birimi yedeklemeye izin ver" + +msgid "Allow to delete in-use QoS specification(s)" +msgstr "Kullanımdaki QoS özelliklerini silmeye izin ver" + +msgid "Allow volume to be attached more than once (default to False)" +msgstr "Diskin birden fazla eklenmesine izin ver (varsayılan olarak False)" + +#, python-format +msgid "An error occurred when reading rules from file %(path)s: %(error)s" +msgstr "%(path)s dosaysından kurallar okunurken hata oluştu: %(error)s" + +msgid "Anchor for paging" +msgstr "Sayfalama için sabitleyici" + +msgid "Apply rule to incoming network traffic (default)" +msgstr "Kuralı gelen trafiğe uygula (varsayılan)" + +msgid "Apply rule to outgoing network traffic" +msgstr "Giden ağ trafiğine kural uygula" + +msgid "" +"Arbitrary scheduler hint key-value pairs to help boot an instance (repeat " +"option to set multiple hints)" +msgstr "" +"İsteğe bağlı bir önyüklemeye yardımcı olmak için keyfi zamanlayıcı ipucu " +"anahtar-değer çiftleri (birden fazla ipucu ayarlamak için seçeneği tekrarla)" + +msgid "" +"Argument --dst-port not allowed with arguments --icmp-type and --icmp-code" +msgstr "" +"--dst-port argümanı --icmp-type ve --icmp-code argümanları ile kullanılamaz" + +msgid "Argument --icmp-type required with argument --icmp-code" +msgstr "--icmp-type argümanı --icmp-code ile kullanılması zorunlu" + +msgid "Assocaite the floating IP with port (name or ID)" +msgstr "Yüzen IP'yi bağlantı noktasıyla ilişkilendirin (ad veya kimlik)" + +msgid "Associate a QoS specification to a volume type" +msgstr "Bir disk bölümü türüyle QoS özelliklerini ilişkilendir" + +msgid "Associate no security groups with this port" +msgstr "Bu bağlantı noktası ile hiç bir güvenlik grubunu ilişkilendirme" + +msgid "Associate project with image" +msgstr "Projeyi imaj ile ilişkilendirin" + +msgid "Attach QoS policy to this port (name or ID)" +msgstr "Bu bağlantı noktasına QoS politikası ekle (isim veya ID)" + +msgid "" +"Attempt forced removal of snapshot(s), regardless of state (defaults to " +"False)" +msgstr "" +"Durumdan bağımsız olarak, anlık görüntüleri zorla kaldırma girişimi " +"(varsayılan olarak False)." + +msgid "" +"Attempt forced removal of volume(s), regardless of state (defaults to False)" +msgstr "" +"Durumdan bağımsız olarak hacim(ler)in zorla kaldırılması girişimi " +"(varsayılan olarak False)" + +msgid "Attempt to mask passwords (default)" +msgstr "Parolaları maskelemeye çalış (varsayılan)" + +msgid "Attempting to upload multiple objects and using --name is not permitted" +msgstr "" +"Birden çok nesne yüklemeye çalışılıyor ve --name kullanmaya izin verilmiyor" + +msgid "Authentication URL of remote federated service provider (required)" +msgstr "Uzak federe servis sağlayıcının kimlik doğrulama URL'si (gerekli)" + +msgid "Authentication token to use" +msgstr "Kullanılacak yetkilendirme jetonu" + +msgid "Authorize a request token" +msgstr "Bir istek jetonu yetkilendir" + +msgid "" +"Auto option is not available for Subnet Set. Valid options are " +"or none" +msgstr "" +"Otomatik seçeneği Alt ağ Kümesi için kullanılabilir değildir. Geçerli " +"değerler veya none'dır" + +msgid "" +"Availability Zone in which to create this network (Network Availability Zone " +"extension required, repeat option to set multiple availability zones)" +msgstr "" +"Bu ağın oluşturulacağı kullanılabilirlik bölgesi (Ağ Kullanılabilirlik Alanı " +"uzantısı gerekli, birden çok kullanılabilirlik bölgesi ayarlamak için " +"seçeneği tekrarla)" + +msgid "" +"Availability Zone in which to create this router (Router Availability Zone " +"extension required, repeat option to set multiple availability zones)" +msgstr "" +"Bu yönlendiricinin oluşturulacağı Kullanılabilirlik Bölgesi (Yönlendirici " +"Kullanılabilirlik Bölge uzantısı gerekiyor, birden fazla kullanılabilirlik " +"bölgesi ayarlamak için seçeneği tekrarla)" + +msgid "" +"Availability zone for this consistency group (not available if creating " +"consistency group from source)" +msgstr "" +"Bu tutarlılık grubu için kullanılabilirlik bölgesi (kaynaktan tutarlılık " +"grubu oluşturursanız kullanılamaz)" + +msgid "Availability zone name" +msgstr "Kullanılabilirlik bölgesi ismi" + +msgid "Availability zones list not supported by Block Storage API" +msgstr "" +"Blok Depolama Alanı API'sı tarafından desteklenmeyen kullanılabilirlik " +"bölgeleri listesi" + +msgid "Availability zones list not supported by Network API" +msgstr "" +"Ağ API'sı tarafından desteklenmeyen kullanılabilirlik bölgeleri listesi" + +msgid "Backup to display (name or ID)" +msgstr "Gösterilecek yedek (isim veya ID)" + +msgid "Backup to modify (name or ID)" +msgstr "Düzenlenecek yedek (isim veya ID)" + +msgid "Backup to restore (name or ID)" +msgstr "Geri yüklenecek yedek (isim veya ID)" + +msgid "Backup(s) to delete (name or ID)" +msgstr "Silinecek yedek(ler) (isim veya ID)" + +#, python-format +msgid "Can't open '%(data)s': %(exception)s" +msgstr "'%(data)s' açılamadı: %(exception)s" + +#, python-format +msgid "Can't open '%(source)s': %(exception)s" +msgstr "'%(source)s' açılamadı: %(exception)s" + +msgid "Cannot remove access for a public flavor" +msgstr "Herkese açık flavor'dan erişimin kaldırılması işlemi başarısız" + +msgid "Cannot set access for a public flavor" +msgstr "Ortak flavor için erişim ayarlanamaz" + +msgid "Cannot specify option --disable-reason without --disable specified." +msgstr "--disable-reason seçeneği --disable olmadan belirtilemez." + +msgid "Change current user password" +msgstr "Mevcut kullanıcının parolasını değiştir" + +msgid "Check user membership in group" +msgstr "Kullanıcının grup üyeliğini kontrol et" + +msgid "Clean project resources, but don't delete the project" +msgstr "Projenin kaynaklarını temizle ama projeyi silme" + +msgid "Clean resources associated with a project" +msgstr "Bir proje ile alakalı kaynakları temizle" + +#, python-format +msgid "Clear all tags associated with the %s" +msgstr "%s ile ilişkili tüm etiketleri sil" + +msgid "" +"Clear associated allocation-pools from the subnet. Specify both --allocation-" +"pool and --no-allocation-pool to overwrite the current allocation pool " +"information." +msgstr "" +"Alt ağdan ilgili paylaştırma havuzunu temizle. Mevcut paylaştırma havuzu " +"bilgisinin üzerine yazmak için --allocation-pool ve --no-allocation-pool " +"seçeneklerinin her ikisini de belirle." + +msgid "" +"Clear associated host-routes from the subnet. Specify both --host-route and " +"--no-host-route to overwrite the current host route information." +msgstr "" +"Alt ağdaki ilişkili ana makine yollarını temizleyin. Mevcut ana bilgisayar " +"yönlendirme bilgisinin üzerine yazmak için --host-route ve --no-host-route " +"seçeneklerinin her ikisini de belirtin." + +msgid "" +"Clear existing allowed-address pairs associatedwith this port.(Specify both " +"--allowed-address and --no-allowed-addressto overwrite the current allowed-" +"address pairs)" +msgstr "" +"Bu bağlantı noktasıyla ilişkili mevcut izinli adres çiftlerini temizleyin." +"(Mevcut izinli adres çiftinin üzerinde yazmak için --allowed-address ve --" +"no-allowed-addressto seçeneklerinin her ikisini de belirtiniz)" + +msgid "" +"Clear existing information of DNS Nameservers. Specify both --dns-nameserver " +"and --no-dns-nameserver to overwrite the current DNS Nameserver information." +msgstr "" +"DNS isim sunucularının mevcut bilgilerini temizle. Mevcut DNS isim sunucusu " +"bilgisinin üzerine yazmak için --dns-nameserver ve --no-dns-nameserver " +"özelliklerini belirle." + +msgid "" +"Clear existing information of binding:profile.Specify both --binding-profile " +"and --no-binding-profile to overwrite the current binding:profile " +"information." +msgstr "" +"binding:profile'in mevcut bilgilerini temizle. Mevcut binding:profile " +"bilgisinin üzerine yazmak için --binding-profile ve --no-binding-profile her " +"ikisini de belirtin." + +msgid "Clear existing information of data plane status" +msgstr "Mevcut veri düzlemi durumu bilgilerini temizle" + +msgid "" +"Clear existing information of fixed IP addresses.Specify both --fixed-ip and " +"--no-fixed-ip to overwrite the current fixed IP addresses." +msgstr "" +"Sabit IP adresleri için mevcut bilgileri silin. Geçerli sabit IP " +"adreslerinin üzerine yazmak için hem --fixed-ip hem de --no-fixed-ip " +"belirtin." + +msgid "Clear existing security groups associated with this port" +msgstr "Bu bağlantı noktasıyla ilişkili mevcut güvenlik gruplarını temizle" + +msgid "Clear high availability attribute of the router (disabled router only)" +msgstr "" +"Yönlendiricinin yüksek kullanılabilirlik özelliğini temizle (sadece devre " +"dışı bırakılmış yönlendirici)" + +msgid "" +"Clear routes associated with the router. Specify both --route and --no-route " +"to overwrite current value of route." +msgstr "" +"Yönlendirici ile ilişkili yönleri temizle. Mevcut yön değerinin üzerine " +"yazmak için hem --route hem de --no-route seçeneklerini belirtin." + +#, python-format +msgid "" +"Clear tags associated with the %s. Specify both --tag and --no-tag to " +"overwrite current tags" +msgstr "" +"%s ile ilgili etiketleri temizle. Mevcut etiketlerin üzerine yazmak için hem " +"--tag hem de --no-tag seçeneğini belirtin" + +msgid "Command Failed: One or more of the operations failed" +msgstr "Komut başarısız: Bir veya birden fazla işlem başarısız" + +msgid "Complete\n" +msgstr "Tamamla\n" + +#, python-format +msgid "Compute API version, default=%s (Env: OS_COMPUTE_API_VERSION)" +msgstr "Hesaplama API sürümü, varsayılan=%s (Env: OS_COMPUTE_API_VERSION)" + +#, python-format +msgid "Compute service %(service)s of host %(host)s failed to set." +msgstr "Ana bilgisayar %(host)s'ın hesap hizmeti %(service)s ayarlanamadı." + +msgid "Compute service(s) to delete (ID only)" +msgstr "Hesaplama servis(ler)ini sil" + +msgid "Confirm server resize is complete" +msgstr "Sunucu yeniden boyutlandırmasının tamamlandığını doğrula" + +msgid "Consistency group containing (name or ID)" +msgstr "'ü içeren tutarlılık grubu (isim veya ID)" + +msgid "Consistency group snapshot to display (name or ID)" +msgstr "Tutarlılık grupunun gösterilecek anlık görüntüsü (isim veya ID)" + +msgid "Consistency group snapshot(s) to delete (name or ID)" +msgstr "Silinecek tutarlılık grubu anlık görüntüleri (isim veya ID)" + +msgid "Consistency group to contain (name or ID)" +msgstr " disk bölümünü içerecek tutarlılık grubu (isim veya ID)" + +msgid "Consistency group to display (name or ID)" +msgstr "Gösterilecek tutarlılık grubu (isim veya ID)" + +msgid "Consistency group to modify (name or ID)" +msgstr "Düzenlenecek tutarlılık grubu (isim veya ID)" + +msgid "" +"Consistency group to snapshot (name or ID) (default to be the same as " +")" +msgstr "" +"Anlık görüntüsü alınacak tutarlılık grubu (isim veya ID) (varsayılan olarak " +" ile aynı)" + +msgid "Consistency group where the new volume belongs to" +msgstr "Yeni disk bölümünün ait olduğu tutarlılık grubu" + +msgid "Consistency group(s) to delete (name or ID)" +msgstr "Silinecek tutarlılık grupları (isim veya ID)" + +msgid "Consumer key (required)" +msgstr "Alıcı anahtarı (gerekli)" + +#, python-format +msgid "Consumer of the QoS. Valid consumers: %s (defaults to 'both')" +msgstr "QoS tüketici. Geçerli tüketiciler: %s (varsayılan olarak 'her ikisi')" + +msgid "Consumer secret (required)" +msgstr "Alıcı gizli anahtarı (gerekli)" + +msgid "Consumer to display" +msgstr "Gösterilecek alıcı" + +msgid "Consumer to modify" +msgstr "Düzenlenecek alıcı" + +msgid "Consumer(s) to delete" +msgstr "Silinecek alıcı(lar)" + +msgid "Container for new object" +msgstr "Yeni nesne için kap" + +#, python-format +msgid "Container name is %s characters long, the default limit is 256" +msgstr "Kap ismi %s karakter uzunluğunda, varsayılan sınır 256'dır" + +msgid "Container to display" +msgstr "Gösterilecek kap" + +msgid "Container to list" +msgstr "Listelenecek kap" + +msgid "Container to modify" +msgstr "Düzenlenecek kap" + +msgid "Container to save" +msgstr "Kaydedilecek kap" + +msgid "Container(s) to delete" +msgstr "Silinecek kap(lar)" + +msgid "Copy image from the data store (similar to --location)" +msgstr "Veri deposundan imajı kopyala (--location'a benzer)" + +#, python-format +msgid "Could not find security group rule with ID '%s'" +msgstr "'%s' ID'li güvenlik grubu kuralı bulunamadı" + +msgid "Create EC2 credentials" +msgstr "EC2 kimlik bilgilerini oluştur" + +msgid "" +"Create a NIC on the server and connect it to network. Specify option " +"multiple times to create multiple NICs. This is a wrapper for the '--nic net-" +"id=' parameter that provides simple syntax for the standard use " +"case of connecting a new server to a given network. For more advanced use " +"cases, refer to the '--nic' parameter." +msgstr "" +"Sunucuda bir NIC oluşturun ve ağa bağlayın. Birden çok NIC oluşturmak için " +"seçeneği birden çok kez belirtin. Bu, belirli bir ağa yeni bir sunucu " +"bağlamak için standart kullanım örneği için basit sözdizimi sağlayan '--nic " +"net-id=' parametresi için bir sarmalayıcıdır. Daha gelişmiş " +"kullanım durumları için, '--nic' parametresine bakın." + +msgid "" +"Create a NIC on the server and connect it to port. Specify option multiple " +"times to create multiple NICs. This is a wrapper for the '--nic port-" +"id=' parameter that provides simple syntax for the standard use case " +"of connecting a new server to a given port. For more advanced use cases, " +"refer to the '--nic' parameter." +msgstr "" +"Sunucuda bir NIC oluşturun ve bağlantı noktasına bağlayın. Birden çok NIC " +"oluşturmak için seçeneği birden çok kez belirtin. Bu, belirli bir bağlantı " +"noktasına yeni bir sunucu bağlamak için standart kullanım örneği için basit " +"sözdizimi sağlayan '--nic port-id=' parametresi için bir " +"sarmalayıcıdır. Daha gelişmiş kullanım durumları için, '--nic' parametresine " +"bakın." + +msgid "" +"Create a NIC on the server. Specify option multiple times to create multiple " +"NICs. Either net-id or port-id must be provided, but not both. net-id: " +"attach NIC to network with this UUID, port-id: attach NIC to port with this " +"UUID, v4-fixed-ip: IPv4 fixed address for NIC (optional), v6-fixed-ip: IPv6 " +"fixed address for NIC (optional), none: (v2.37+) no network is attached, " +"auto: (v2.37+) the compute service will automatically allocate a network. " +"Specifying a --nic of auto or none cannot be used with any other --nic value." +msgstr "" +"Sunucuda bir NIC oluştur. Birden fazla NIC oluşturmak için seçeneği birden " +"fazla kere belirtin. Ya net-id ya da port-id sağlanmalı, ikisi bir arada " +"değil. net-id: NIC'nin ağa ekleneceği UUID, port-id: NIC'nin bağlantı " +"noktasına takılacağı UUID, v4-fixed-ip: NIC için sabit IPv4 adresi " +"(seçimli), v6-fixed-ip: NIC için sabit IPv6 adresi (seçimli), none: (v2.37+) " +"hiç ağ takılmaz, auto: (v2.37+) hesaplama servisi otomatik olarak bir ağ " +"ayırır. Auto veya none'ı bir --nic ile belirtmek başka bir --nic değeri ile " +"kullanılamaz." + +msgid "Create a QoS policy" +msgstr "QoS politikası oluştur" + +msgid "" +"Create a block device on the server.\n" +"Block device mapping in the format\n" +"=:::\n" +": block device name, like: vdb, xvdc (required)\n" +": UUID of the volume or snapshot (required)\n" +": volume or snapshot; default: volume (optional)\n" +": volume size if create from snapshot (optional)\n" +": true or false; default: false (optional)\n" +"(optional extension)" +msgstr "" +"Sunucu üzerinde blok aygıtı oluştur.\n" +"Blok aygıtı eşleşme formatı\n" +"=:::\n" +": blok aygıt ismi, örn: vdb, xvdc (gerekli)\n" +": disk bölümünün veya anlık görüntünün UUID'si (gerekli)\n" +": disk bölümü veya anlık görüntü; varsayılan: disk bölümü (seçimli)\n" +": eğer anlık görüntüden oluşturulduysa disk bölümü boyutu " +"(seçimli)\n" +": true veya false; varsayılan: false (seçimli)\n" +"(seçimli uzantı)" + +msgid "Create a centralized router" +msgstr "Merkezi bir yönlendirici oluştur" + +msgid "Create a distributed router" +msgstr "Dağıtık bir yönlendirici oluştur" + +msgid "Create a highly available router" +msgstr "Yüksek kullanılabilirlikli bir yönlendirici oluştur" + +msgid "Create a legacy router" +msgstr "Miraz yönlendirici oluştur" + +msgid "Create a new Address Scope" +msgstr "Yeni bir Adres Kapsamı oluştur" + +msgid "Create a new aggregate" +msgstr "Yeni bir küme oluştur" + +msgid "Create a new meter rule" +msgstr "Yeni bir ölçek kuralı oluştur" + +msgid "Create a new port" +msgstr "Yeni bir bağlantı noktası oluştur" + +msgid "Create a new router" +msgstr "Yeni bir yönlendirici oluştur" + +msgid "Create a new security group" +msgstr "Yeni bir grup oluştur" + +msgid "Create a new security group rule" +msgstr "Yeni bir güvenlik grubu kuralı oluştur" + +msgid "Create a new server" +msgstr "Yeni bir sunucu oluştur" + +msgid "Create a new server disk image from an existing server" +msgstr "Varolan bir sunucudan yeni bir disk imajı oluştur" + +msgid "Create a new server group." +msgstr "Yeni bir sunucu grubu oluştur." + +msgid "Create a request token" +msgstr "Bir istek jetonu oluştur" + +msgid "Create a server backup image" +msgstr "Bir sunucu yedek imajı oluştur" + +msgid "Create a snapshot attached to an instance. Default is False" +msgstr "Sunucuya bağlanacak bir anlık görüntü oluştur. Varsayılan Fals" + +msgid "Create a subnet" +msgstr "Bir altağ oluşturun" + +msgid "Create an access token" +msgstr "Erişim jetonu oluştur" + +msgid "Create compute agent" +msgstr "Hesaplama ajanı oluştur" + +msgid "" +"Create credentials for user (name or ID; default: current authenticated user)" +msgstr "" +"Kullanıcı için kimlik bilgileri oluştur (isim veya ID; varsayılan: mevcut " +"yetkilendirilmiş kullanıcı)" + +msgid "" +"Create credentials in project (name or ID; default: current authenticated " +"project)" +msgstr "" +"Projede kimlik bilgileri oluştur (isim veya ID; varsayılan: mevcut kimlik " +"doğrulama yapılmış proje)" + +msgid "Create description for meter" +msgstr "Sayaç için açıklama oluştur" + +msgid "Create floating IP" +msgstr "Yüzen IP oluştur" + +msgid "Create image from a volume" +msgstr "Bir disk bölümünden imaj oluştur" + +msgid "Create network RBAC policy" +msgstr "Ağ RBAC politikası oluştur" + +msgid "Create network meter" +msgstr "Ağ sayacı oluştur" + +msgid "Create new Network QoS rule" +msgstr "Yeni Ağ QoS kuralı oluştur" + +msgid "Create new QoS specification" +msgstr "Yeni QoS özelliği oluştur" + +msgid "Create new backup" +msgstr "Yeni yedek oluştur" + +msgid "Create new consistency group snapshot." +msgstr "Yeni tutarlılık grubu anlık görüntüsü oluştur." + +msgid "Create new consistency group." +msgstr "Yeni tutarlılık grubu oluştur." + +msgid "Create new consumer" +msgstr "Yeni tüketici oluştur" + +msgid "Create new container" +msgstr "Yeni kap oluştur" + +msgid "Create new credential" +msgstr "Yeni kimlik bilgileri oluştur" + +msgid "Create new domain" +msgstr "Yeni alan oluştur" + +msgid "Create new endpoint" +msgstr "Yeni bir uç nokta oluştur" + +msgid "Create new federation protocol" +msgstr "Yeni federasyon protokolü oluştur" + +msgid "Create new flavor" +msgstr "Yeni flavor oluştur" + +msgid "Create new group" +msgstr "Yeni grup oluştur" + +msgid "Create new identity provider" +msgstr "Yeni kimlik sağlayıcı oluştur" + +msgid "Create new mapping" +msgstr "Yeni eşleştirme oluştur" + +msgid "Create new network" +msgstr "Yeni ağ oluştur" + +msgid "Create new network flavor" +msgstr "Yeni ağ falvor'ı oluştur" + +msgid "Create new network flavor profile" +msgstr "Yeni ağ flavor profili oluştur" + +msgid "Create new network segment" +msgstr "Yeni ağ dilimi oluştur" + +msgid "Create new policy" +msgstr "Yeni politika oluştur" + +msgid "Create new project" +msgstr "Yeni proje oluştur" + +msgid "Create new public or private key for server ssh access" +msgstr "Sunucunun ssh erişimi için yeni açık ve kapalı anahtarları oluştur" + +msgid "Create new region" +msgstr "Yeni bölge oluştur" + +msgid "Create new role" +msgstr "Yeni rol oluştur" + +msgid "Create new service" +msgstr "Yeni servis oluştur" + +msgid "Create new service provider" +msgstr "Yeni servis sağlayıcı oluştur" + +msgid "Create new snapshot" +msgstr "Yeni anlık görüntü oluştur" + +msgid "Create new trust" +msgstr "Yeni güven oluştur" + +msgid "Create new user" +msgstr "Yeni kullanıcı oluştur" + +msgid "Create new volume" +msgstr "Yeni disk bölümü oluştur" + +msgid "Create new volume backup" +msgstr "Yeni disk bölümü yedeği oluştur" + +msgid "Create new volume snapshot" +msgstr "Yeni bir disk bölümü anlık görüntüsü oluştur" + +msgid "Create new volume type" +msgstr "Yeni disk bölümü türü oluşturun" + +msgid "Create rule in this security group (name or ID)" +msgstr "Bu güvenlik grubunda kural oluştur (isim veya ID)" + +msgid "Create server boot disk from this image (name or ID)" +msgstr "Bu imajdan sunucu ön yükleme diski oluştur (isim veya ID)" + +msgid "" +"Create server using this volume as the boot disk (name or ID).\n" +"This option automatically creates a block device mapping with a boot index " +"of 0. On many hypervisors (libvirt/kvm for example) this will be device vda. " +"Do not create a duplicate mapping using --block-device-mapping for this " +"volume." +msgstr "" +"Bu disk bölümünü ön yüklenebilir disk olarak kullanarak sunucu oluştur (isim " +"veya ID).\n" +"Bu seçenek otomatik olarak 0 ön yükleme diziniyle bir blok aygıt eşleşmesi " +"oluşturur. Bir çok yönetici arakatman (örneğin libvirt/kvm) üzerinde bu " +"aygıt vda'dir. Bu disk bölümü için --block-device-mapping kullanarak birden " +"fazla eşleşme oluşturmayın." + +msgid "Create server with this flavor (name or ID)" +msgstr "Bu flavor ile sunucu oluştur (isim veya ID)" + +msgid "Create subnet pool" +msgstr "Altağ havuzu oluştur" + +msgid "Create the auto allocated topology for project" +msgstr "Proje için otomatik ayrılmış topoloji oluştur" + +msgid "Create volume in " +msgstr " içerisinde disk bölümü oluştur" + +msgid "Create volume transfer request." +msgstr "Disk bölümü aktarım isteği oluştur." + +msgid "Create/upload an image" +msgstr "Bir imaj oluştur/yükle" + +msgid "Credentials access key" +msgstr "Kimlik bilgilerine erişim anahtarı" + +msgid "Credentials access key(s)" +msgstr "Kimlik bilgilerine erişim anahtar(lar)ı" + +msgid "" +"Custom data to be passed as binding:profile. Data may be passed as " +"= or JSON. (repeat option to set multiple binding:profile data)" +msgstr "" +"binding:profile olarak verilecek özel veri. Veri = şeklinde veya " +"JSON olarak verilebilir. (birden fazla binding:profile verisi ayarlamak için " +"seçeneği tekrarlayın)" + +msgid "DNS server for this subnet (repeat option to set multiple DNS servers)" +msgstr "" +"Bu alt ağ için DNS sunucu (birden fazla DNS sunucusu ayarlamak için seçeneği " +"tekrarlayın)" + +msgid "" +"DNS server to be removed from this subnet (repeat option to unset multiple " +"DNS servers)" +msgstr "" +"Bu altağdan silinecek DNS sunucu (Birden fazla DNS sunucusunun ayarını " +"kaldırmak için seçeneği tekrarla)" + +msgid "" +"DSCP mark: value can be 0, even numbers from 8-56, excluding 42, 44, 50, 52, " +"and 54" +msgstr "" +"DSCP işareti: değer 0 olabilir, 8-56 arasındaki çift sayılar, 42, 44, 50, " +"52, ve 54 hariç" + +msgid "Deactivate the image" +msgstr "İmajın aktivasyonunu kaldır" + +msgid "Default domain (name or ID)" +msgstr "Varsayılan alan (isim veya ID)" + +msgid "Default project (name or ID)" +msgstr "Varsayılan proje (isim veya ID)" + +msgid "Delete EC2 credentials" +msgstr "EC2 kimlik bilgilerini sil" + +msgid "Delete Network QoS rule" +msgstr "Ağ QoS kuralını sil" + +msgid "Delete QoS specification" +msgstr "QoS özelliklerini sil" + +msgid "Delete Qos Policy(s)" +msgstr "Qos Politika(lar/s)ını sil" + +msgid "Delete address scope(s)" +msgstr "Adres kapsam(lar)ını sil" + +msgid "" +"Delete auto allocated topology for a given project. Default is the current " +"project" +msgstr "" +"Belirli bir proje için otomatik ayrılan topolojiyi silin. Varsayılan geçerli " +"projedir" + +msgid "Delete auto allocated topology for project" +msgstr "Projeye otomatik ayrılan topolojiyi sil" + +msgid "Delete backup(s)" +msgstr "Yedek(ler)i sil" + +msgid "Delete compute agent(s)" +msgstr "Hesaplama ajan(lar)ını sil" + +msgid "Delete compute service(s)" +msgstr "Hesaplama servis(ler)ini sil" + +msgid "Delete consistency group snapshot(s)." +msgstr "Tutarlılık grubu anlık görüntüsünü sil." + +msgid "Delete consistency group(s)." +msgstr "Tutarlılık grubunu sil." + +msgid "Delete consumer(s)" +msgstr "Alıcıları sil" + +msgid "Delete container" +msgstr "Kabı sil" + +msgid "Delete credential(s)" +msgstr "Kimlik bilgilerini sil" + +msgid "Delete credentials for user (name or ID)" +msgstr "Kullanıcı için kimlik bilgilerini sil (isim veya ID)" + +msgid "Delete domain(s)" +msgstr "Alan(lar)ı sil" + +msgid "Delete endpoint(s)" +msgstr "Uç nokta(yı/ları) sil" + +msgid "Delete existing aggregate(s)" +msgstr "Var olan küme(ler/y)i sil" + +msgid "Delete existing server group(s)." +msgstr "Mevcut sunucu gruplarını sil." + +msgid "Delete federation protocol(s)" +msgstr "Federasyon protokol(ünü/lerini) sil" + +msgid "Delete flavor(s)" +msgstr "Flavor(lar)ı sil" + +msgid "Delete floating IP(s)" +msgstr "Yüzen IP(ler)i sil" + +msgid "Delete group(s)" +msgstr "Grup(ları/u) sil" + +msgid "Delete identity provider(s)" +msgstr "Kimlik sağlayıcı(ları/yı) sil" + +msgid "Delete image(s)" +msgstr "İmaj(lar)ı sil" + +msgid "Delete mapping(s)" +msgstr "Eşleştirme(yi/leri) sil" + +msgid "Delete meter rule(s)" +msgstr "Ölçüm kural(lar)ını sil" + +msgid "Delete network RBAC policy(s)" +msgstr "Ağ RBAC politikalarını sil" + +msgid "Delete network agent(s)" +msgstr "Ağ ajanlarını sil" + +msgid "Delete network flavor profile" +msgstr "Ağ flavor profilini sil" + +msgid "Delete network flavors" +msgstr "Ağ flavor'larını sil" + +msgid "Delete network meter" +msgstr "Ağ ölçeğini sil" + +msgid "Delete network segment(s)" +msgstr "Ağ segmentlerini sil" + +msgid "Delete network(s)" +msgstr "Ağı sil" + +msgid "Delete object from container" +msgstr "Kaptan nesne sil" + +msgid "Delete object(s) from " +msgstr "'den nesneleri sil" + +msgid "Delete policy(s)" +msgstr "Politika(ları/yı) sil" + +msgid "Delete port(s)" +msgstr "Bağlantı noktalarını sil" + +msgid "Delete project(s)" +msgstr "Proje(yi/leri) sil" + +msgid "Delete public or private key(s)" +msgstr "Genel veya özel anahtarları sil" + +msgid "Delete region(s)" +msgstr "Bölge(yi/leri) sil" + +msgid "Delete resources of the project used to authenticate" +msgstr "Kimlik kanıtlama için kullanılan proje kaynaklarını sil" + +msgid "Delete role(s)" +msgstr "Rol(leri/ü) sil" + +msgid "Delete router(s)" +msgstr "Yönlendirici(ler/y)i sil" + +msgid "Delete security group rule(s)" +msgstr "Güvenlik grubu kurallarını sil" + +msgid "Delete security group(s)" +msgstr "Güvenlik grubunu sil" + +msgid "Delete server(s)" +msgstr "Sunucu(ları/yu) sil" + +msgid "Delete service provider(s)" +msgstr "Servis sağlayıcı(ları/yı) sil" + +msgid "Delete service(s)" +msgstr "Servis(ler)i sil" + +msgid "Delete snapshot(s)" +msgstr "Anlık görüntü(yü/leri) sil" + +msgid "Delete subnet pool(s)" +msgstr "Altağ havuzunu sil" + +msgid "Delete subnet(s)" +msgstr "Altağ(lar)ı sil" + +msgid "Delete trust(s)" +msgstr "Güveni sil" + +msgid "Delete user(s)" +msgstr "Kullanıcı(yı/ları) sil" + +msgid "Delete volume backup(s)" +msgstr "Disk bölümü yedeklerini sil" + +msgid "Delete volume snapshot(s)" +msgstr "Disk bölümü anlık görüntülerini sil" + +msgid "Delete volume transfer request(s)." +msgstr "Disk bölümü aktarım isteğini sil." + +msgid "Delete volume type(s)" +msgstr "Disk bölümü türlerini sil" + +msgid "Delete volume(s)" +msgstr "Disk bölümlerini sil" + +#, python-format +msgid "Deleting %(resource)s : %(id)s" +msgstr "%(resource)s siliniyor: %(id)s" + +#, python-format +msgid "Deleting project: %s" +msgstr "Proje siliniyor: %s" + +msgid "Description for the flavor" +msgstr "Flavor için açıklama" + +msgid "Description for the flavor profile" +msgstr "Flavor profili için açıklama" + +msgid "Description of the QoS policy" +msgstr "QoS politikasının tanımı" + +msgid "Description of the backup" +msgstr "Yedeğin açıklaması" + +msgid "Description of the snapshot" +msgstr "Anlık görüntü tanımı" + +msgid "Description of this consistency group" +msgstr "Bu tutarlılık grubunun açıklaması" + +msgid "Description of this consistency group snapshot" +msgstr "Bu tutarlılık grubu anlık görüntüsünün açıklaması" + +msgid "Description of this port" +msgstr "Bu bağlantı noktasının açıklaması" + +msgid "" +"Desired IP and/or subnet (name or ID)on external gateway: subnet=,ip-" +"address= (repeat option to set multiple fixed IP addresses)" +msgstr "" +"Harici geçit üzerinde istenen IP ve/veya altağ: subnet=,ip-" +"address= (birden fazla sabit IP adres ayarlamak için seçeneği " +"tekrarla)" + +msgid "" +"Desired IP and/or subnet for filtering ports (name or ID): subnet=," +"ip-address= (repeat option to set multiple fixed IP addresses)" +msgstr "" +"Bağlantı noktalarını filtrelemek için tasarlanan IP ve/veya alt ağ (isim " +"veya ID): subnet=,ip-address= (birden fazla sabit IP " +"adresi ayarlamak için seçeneği tekrarlayın)" + +msgid "" +"Desired IP and/or subnet for this port (name or ID): subnet=,ip-" +"address= (repeat option to set multiple fixed IP addresses)" +msgstr "" +"Bu bağlantı noktası için istenen IP ve / veya alt ağ (ad veya kimlik): " +"subnet=, ip-address= (birden fazla sabit IP adresi " +"ayarlamak için seçeneği tekrarlayın)" + +msgid "" +"Desired IP and/or subnet which should be removed from this port (name or " +"ID): subnet=,ip-address= (repeat option to unset " +"multiple fixed IP addresses)" +msgstr "" +"Bu bağlantı noktasından kaldırılması gereken istenen IP ve/veya alt ağ (isim " +"veya ID): subnet=,ip-address= (birden fazla sabit IP " +"adresini kaldırmak için seçeneği tekrarlayın)" + +msgid "" +"Desired allowed-address pair which should be removed from this port: ip-" +"address= [,mac-address=] (repeat option to set " +"multiple allowed-address pairs)" +msgstr "" +"Bu bağlantıdan kaldırılması gereken tasarlanan erişilebilir adres çifti: ip-" +"address= [,mac-address=] (birden fazla izin verilen " +"adres çifti ayarlamak için seçeneği tekrarlayın)" + +msgid "" +"Desired key which should be removed from binding:profile(repeat option to " +"unset multiple binding:profile data)" +msgstr "" +"binding:profile'den çıkarılması gereken istenen anahtar (çoklu binding:" +"profile verisinin ayarını kaldırmak için seçeneği tekrarlayın)" + +msgid "" +"Destination filename (defaults to object name); using '-' as the filename " +"will print the file to stdout" +msgstr "" +"Hedef dosya ismi (nesne ismine varsayılan); '-' karakterini dosya adı olarak " +"kullanmak dosyayı stdout'a yazar" + +msgid "Destination host (takes the form: host@backend-name#pool)" +msgstr "Hedef ana bilgisayar (biçemi: anabilgisayar@artalanismi#havuz)" + +msgid "Destination port (ssh -p option)" +msgstr "Hedef bağlantı noktası (ssh -p seçeneği)" + +msgid "" +"Destination port, may be a single port or a starting and ending port range: " +"137:139. Required for IP protocols TCP and UDP. Ignored for ICMP IP " +"protocols." +msgstr "" +"Hedef bağlantı noktası, tek bir bağlantı noktası veya başlangıç ve bitiş " +"bağlantı noktası: 137:139 olabilir. TCP ve UDP IP protokolleri için " +"zorunludur. ICMP IP protokolleri için gözardı edilir." + +msgid "" +"Device owner of this port. This is the entity that uses the port (for " +"example, network:dhcp)." +msgstr "" +"Bu portun cihaz sahibi. Bu, bağlantı noktasını kullanan varlıktır (örneğin, " +"ağ: dhcp)." + +msgid "Disable DHCP" +msgstr "DHCP'yi devre dışı bırak" + +msgid "Disable Source NAT on external gateway" +msgstr "Harici geçit üzerinde Kaynak NAT'ı devre dışı bırak" + +msgid "Disable domain" +msgstr "Alanı devre dışı bırak" + +msgid "Disable endpoint" +msgstr "Uç noktayı devre dışı bırak" + +msgid "Disable maintenance mode for the host" +msgstr "Sunucu için bakım kipini devre dışı bırak" + +msgid "Disable network" +msgstr "Ağı devre dışı bırak" + +msgid "Disable network agent" +msgstr "Ağ ajanını devre dışı bırak" + +msgid "Disable network flavor" +msgstr "Ağ flavor'ını devre dışı bırak" + +msgid "Disable port" +msgstr "Bağlantı noktasını devre dışı bırak" + +msgid "Disable port security by default for ports created on this network" +msgstr "" +"Bu ağda oluşturulan bağlantı noktaları için varsayılan olarak bağlantı " +"noktası güvenlik işlevini devre dışı bırak" + +msgid "Disable port security for this port" +msgstr "Bu bağlantı noktası için bağlantı noktası güvenliğini devre dışı bırak" + +msgid "Disable project" +msgstr "Projeyi devredışı bırak" + +msgid "Disable router" +msgstr "Yönlendiriciyi devre dışı bırak" + +msgid "Disable service" +msgstr "Servisi devre dışı bırak" + +msgid "Disable the flavor" +msgstr "Flavor'ı etkisiz hale getir (varsayılan)" + +msgid "Disable the flavor profile" +msgstr "Flavor profilini etkisiz hale getir" + +msgid "Disable the host" +msgstr "Sunucuyu devredışı bırak" + +msgid "Disable the identity provider" +msgstr "Kimlik sağlayıcıyı devre dışı bırak" + +msgid "Disable the service provider" +msgstr "Servis sağlayıcıyı devre dışı bırak" + +msgid "Disable user" +msgstr "Kullanıcıyı devre dışı bırak" + +msgid "Disable volume service" +msgstr "Disk bölümü servisini devre dışı bırak" + +msgid "Disassociate a QoS specification from a volume type" +msgstr "Bir QoS özelliğini bir disk bölümü türünden ayırın" + +msgid "Disassociate any port associated with the floating IP" +msgstr "Kayan IP ile ilişkili herhangi bir bağlantıyı ayırın" + +msgid "Disassociate project with image" +msgstr "Projeyi imajla ayır" + +msgid "Disassociate the QoS from every volume type" +msgstr "Her disk bölümü türünden QoS'i ayır" + +msgid "Disk size in GB (default 0G)" +msgstr "GB cinsinden disk boyutu (varsayılan 0G)" + +msgid "Display from " +msgstr "dan göster" + +msgid "Display EC2 credentials details" +msgstr "EC2 kimlik bilgileri detaylarını göster" + +msgid "Display Network QoS rule details" +msgstr "Ağ QoS kural detaylarını göster" + +msgid "Display QoS policy details" +msgstr "QoS politikası ayrıntılarını görüntüle" + +msgid "Display QoS specification details" +msgstr "QoS belirtimi detaylarını göster" + +msgid "Display account details" +msgstr "Hesap detaylarını göster" + +msgid "Display address scope details" +msgstr "Adres kapsam detaylarını göster" + +msgid "Display aggregate details" +msgstr "Küme detaylarını göster" + +msgid "Display backup details" +msgstr "Yedek detaylarını göster" + +msgid "Display configuration details" +msgstr "Yapılandırma detaylarını göster" + +msgid "Display consistency group details." +msgstr "Tutarlılık grubu detaylarını göster." + +msgid "Display consistency group snapshot details" +msgstr "Tutarlılık grubunun anlık görüntüsünü görüntüleme" + +msgid "Display consumer details" +msgstr "Alıcı detaylarını göster" + +msgid "Display container details" +msgstr "Kap detaylarını göster" + +msgid "Display credential details" +msgstr "Kimlik bilgileri detaylarını göster" + +msgid "Display domain details" +msgstr "Alan detaylarını göster" + +msgid "Display encryption information for each volume type (admin only)" +msgstr "Her disk bölümü türü için şifreleme bilgisini göster (sadece yönetici)" + +msgid "Display encryption information of this volume type (admin only)" +msgstr "Bu disk bölümü türünün şifreleme bilgisini göster (sadece yönetici)" + +msgid "Display endpoint details" +msgstr "Uç nokta detaylarını göster" + +msgid "Display federation protocol details" +msgstr "Federasyon protokolü detaylarını göster" + +msgid "Display flavor details" +msgstr "Flavor detaylarını göster" + +msgid "Display floating IP details" +msgstr "Yüzen IP ayrıntılarını görüntüle" + +msgid "Display group details" +msgstr "Grup detaylarını göster" + +msgid "Display host details" +msgstr "Sunucu detaylarını göster" + +msgid "Display hypervisor details" +msgstr "Yönetici ara katman detaylarını göster" + +msgid "Display hypervisor stats details" +msgstr "Yönetici arakatman istatistik detaylarını göster" + +msgid "Display identity provider details" +msgstr "Kimlik sağlayıcı detaylarını göster" + +msgid "Display image details" +msgstr "İmaj detaylarını göster" + +msgid "Display information from all projects (admin only)" +msgstr "Tüm projelerden bilgileri göster (sadece yönetici)" + +msgid "Display key details" +msgstr "Anahtar detaylarını göster" + +msgid "Display mapping details" +msgstr "Eşleşme detaylarını göster" + +msgid "Display meter rules details" +msgstr "Ölçek kuralı detaylarını göster" + +msgid "Display names instead of IDs" +msgstr "ID yerine isimleri göster" + +msgid "Display network RBAC policy details" +msgstr "Ağ RBAC politikası detaylarını göster" + +msgid "Display network agent details" +msgstr "Ağ ajanının ayrıntılarını görüntüle" + +msgid "Display network flavor details" +msgstr "Ağ flavor detaylarını göster" + +msgid "Display network flavor profile details" +msgstr "Ağ flavor profil detaylarını göster" + +msgid "Display network segment details" +msgstr "Ağ dilim detaylarını göster" + +msgid "Display object details" +msgstr "Nesne detaylarını göster" + +msgid "Display policy details" +msgstr "Politika detaylarını göster" + +msgid "Display port details" +msgstr "Bağlantı noktası detaylarını gösterin" + +msgid "Display project details" +msgstr "Proje detaylarını göster" + +msgid "Display region details" +msgstr "Bölge detaylarını göster" + +msgid "Display role details" +msgstr "Rol detaylarını göster" + +msgid "Display router details" +msgstr "Yönlendirici detaylarını göster" + +msgid "Display security group details" +msgstr "Güvenlik grubu detaylarını göster" + +msgid "Display security group rule details" +msgstr "Güvenlik grubu kural detaylarını göster" + +msgid "Display server diagnostics information" +msgstr "Sunucu tanılama bilgilerini göster" + +msgid "Display server group details." +msgstr "Sunucu grup detaylarını göster." + +msgid "Display service catalog details" +msgstr "Servis katalog detaylarını göster" + +msgid "Display service details" +msgstr "Servis detaylarını göster" + +msgid "Display service provider details" +msgstr "Servis sağlayıcı detaylarını göster" + +msgid "Display snapshot details" +msgstr "Anlık görüntü detaylarını göster" + +msgid "Display subnet details" +msgstr "Altağ detaylarını göster" + +msgid "Display subnet pool details" +msgstr "Alt ağ havuz detaylarını göster" + +msgid "Display trust details" +msgstr "Güven detaylarını göster" + +msgid "Display user details" +msgstr "Kullanıcı detaylarını göster" + +msgid "Display volume backup details" +msgstr "Yedek disk bölümü detaylarını göster" + +msgid "Display volume details" +msgstr "Disk bölümü detaylarını göster" + +msgid "Display volume snapshot details" +msgstr "Disk bölümü anlık görüntü detaylarını göster" + +msgid "Display volume type details" +msgstr "Disk bölümü türü detaylarını göster" + +msgid "Do not make the network VLAN transparent" +msgstr "Ağ VLAN'ını transparan yapma" + +msgid "Do not over-commit disk on the destination host (default)" +msgstr "Hedef ana bilgisayarda disk aşırı işleme yapma (varsayılan)" + +msgid "Do not share meter between projects" +msgstr "Ölçümleri projeler arasında paylaşma" + +msgid "Do not share the address scope between projects" +msgstr "Adres kapsamını projeler arasında paylaşmayın" + +msgid "Do not share the address scope between projects (default)" +msgstr "Projeler arasında adres kapsamını paylaşma (varsayılan)" + +msgid "Do not share the network between projects" +msgstr "Ağı projeler arasında paylaşmayın" + +msgid "Do not use the network as the default external network" +msgstr "Ağı varsayılan dış ağ olarak kullanmayın" + +msgid "Do not use the network as the default external network (default)" +msgstr "Ağı varsayılan harici ağ olarak kullanma (varsayılan)" + +msgid "Domain containing (name or ID)" +msgstr "'u içeren alan (isim veya ID)" + +msgid "Domain containing group(s) (name or ID)" +msgstr "Grub(u/ları) içeren alan (isim veya ID)" + +msgid "Domain owning (name or ID)" +msgstr "'ye sahip olan alan (isim veya ID)" + +msgid "Domain owning (name or ID)" +msgstr " kullanıcının ait olduğu alan (isim veya ID)" + +msgid "Domain owning the project (name or ID)" +msgstr "Projenin ait olduğu alan (isim veya ID)" + +msgid "Domain that contains (name or ID)" +msgstr "'yi içeren alan (isim veya ID)" + +msgid "Domain that contains (name or ID)" +msgstr " içeren alan (isim veya ID)" + +msgid "" +"Domain the group belongs to (name or ID). This can be used in case " +"collisions between group names exist." +msgstr "" +"Grubun ait olduğu alan (isim veya ID). Grup isimlerinde bir çatışma olması " +"durumunda kullanılabilir." + +msgid "Domain the project belongs to (name or ID) [only valid with --absolute]" +msgstr "" +"Projenin ait olduğu alan (isim veya ID) [sadece --absolute ile geçerli]" + +msgid "" +"Domain the project belongs to (name or ID). This can be used in case " +"collisions between project names exist." +msgstr "" +"Projenin ait olduğu domain (isim veya ID). Proje isimlerinde çakışma olması " +"durumunda kullanılabilir." + +msgid "Domain the role belongs to (name or ID)" +msgstr "Rolün ait olduğu alan (isim veya ID)" + +msgid "" +"Domain the role belongs to (name or ID). This must be specified when the " +"name of a domain specific role is used." +msgstr "" +"Rolün ait olduğu alan (isim veya ID). Alana özel rol ismi kullanıldığında " +"belirtilmek zorundadır." + +msgid "" +"Domain the target project belongs to (name or ID). This can be used in case " +"collisions between project names exist." +msgstr "" +"Hedef projenin ait olduğu alan (isim veya ID) Bu, proje isimleri arasındaki " +"çakışmaların olması durumunda kullanılabilir." + +msgid "" +"Domain the user belongs to (name or ID). This can be used in case collisions " +"between user names exist." +msgstr "" +"Kullanıcının ait olduğu alan (isim veya ID). Kullanıcı adlarında bir çatışma " +"oluşması durumunda kullanılabilir." + +msgid "" +"Domain to associate with the identity provider. If not specified, a domain " +"will be created automatically. (Name or ID)" +msgstr "" +"Kimlik sağlayıcısı ile ilişkilendirilecek alan adı. Belirtilmemişse, " +"otomatik olarak bir alan oluşturulur. (İsim veya kimlik)" + +msgid "Domain to contain new group (name or ID)" +msgstr "Yeni grup içeren alan (isim veya ID)" + +msgid "Domain to display (name or ID)" +msgstr "Gösterilecek alan (isim veya ID)" + +msgid "Domain to filter (name or ID)" +msgstr "Filtrelenecek alan (isim veya ID)" + +msgid "Domain to modify (name or ID)" +msgstr "Düzenlenecek alan (isim veya ID)" + +msgid "Domain(s) to delete (name or ID)" +msgstr "Silinecek alan(lar) (isim veya ID)" + +msgid "Download from " +msgstr " nesneyi 'dan indir" + +msgid "Download image from an existing URL" +msgstr "Mevcut bir URL'den imajı indir" + +msgid "Downloaded image save filename (default: stdout)" +msgstr "İndirilen imajın kaydedileceği dosya adı (varsayılan: stdout)" + +#, python-format +msgid "" +"ERROR: --%s was given, which is an Image v1 option that is no longer " +"supported in Image v2" +msgstr "HATA: --%s, ancak bu İmaj v1 seçeneğidir ve İmaj v2'de desteklenmiyor" + +#, python-format +msgid "ERROR: No %(type)s IP version %(family)s address found" +msgstr "HATA: Hiç %(type)s IP sürüm %(family)s adresi bulunamadı" + +msgid "Egress traffic direction from the project point of view" +msgstr "Proje açısından trafik akış yönü" + +msgid "Either --driver or --metainfo or both are required" +msgstr "Ya --driver ya da --metainfo veya her ikisi de gereklidir" + +msgid "Enable DHCP" +msgstr "DHCP'yi etkinleştir" + +msgid "Enable DHCP (default)" +msgstr "DHCP etkinleştir (varsayılan)" + +msgid "Enable Source NAT on external gateway" +msgstr "Harici geçit üzerinde Kaynak NAT'ı etkinleştir" + +msgid "Enable domain" +msgstr "Alanı etkinleştir" + +msgid "Enable domain (default)" +msgstr "Alanı etkinleştir (varsayılan)" + +msgid "Enable endpoint" +msgstr "Uç noktayı etkinleştir" + +msgid "Enable endpoint (default)" +msgstr "Uç noktayı etkinleştir (varsayılan)" + +msgid "" +"Enable generic host-based force-migration, which bypasses driver " +"optimizations" +msgstr "" +"Genel anabilgisayar tabanlı sürücü optimizasyonunu atlayan güç göçünü " +"etkinleştir" + +msgid "Enable identity provider (default)" +msgstr "Kimlik sağlayıcıyı etkinleştir (varsayılan)" + +msgid "Enable maintenance mode for the host" +msgstr "Sunucu için bakım kipini etkinleştir" + +msgid "Enable network" +msgstr "Ağı etkinleştir" + +msgid "Enable network (default)" +msgstr "Ağı etkinleştir (varsayılan)" + +msgid "Enable network agent" +msgstr "Ağ ajanını etkinleştir" + +msgid "Enable network flavor" +msgstr "Ağ flavor'ını etkin hale getir" + +msgid "Enable port" +msgstr "Bağlantı noktasını etkinleştir" + +msgid "Enable port (default)" +msgstr "Bağlantı noktasının etkinleştir (varsayılan)" + +msgid "Enable port security by default for ports created on this network" +msgstr "" +"Bu ağda oluşturulan bağlantı noktaları için varsayılan olarak bağlantı " +"noktası güvenlik özelliğini etkinleştir" + +msgid "" +"Enable port security by default for ports created on this network (default)" +msgstr "" +"Bu ağda oluşturulan bağlantı noktaları için varsayılan olarak bağlantı " +"noktası güvenlik özelliğini etkinleştir (varsayılan)" + +msgid "Enable port security for this port" +msgstr "Bu bağlantı noktası için bağlantı noktası güvenliğini etkinleştir" + +msgid "Enable port security for this port (Default)" +msgstr "" +"Bu bağlantı noktası için bağlantı noktası güvenliğini etkinleştir " +"(Varsayılan)" + +msgid "Enable project" +msgstr "Projeyi etkinleştir" + +msgid "Enable project (default)" +msgstr "Projeyi etkinleştir (varsayılan)" + +msgid "Enable router" +msgstr "Yönlendiriciyi etkinleştir" + +msgid "Enable router (default)" +msgstr "Yönlendiriciyi etkinleştir (varsayılan)" + +msgid "Enable service" +msgstr "Servisi etkinleştir" + +msgid "Enable service (default)" +msgstr "Servisi etkinleştir (varsayılan)" + +msgid "Enable the flavor (default)" +msgstr "Flavor'ı etkinleştir (varsayılan)" + +msgid "Enable the flavor profile" +msgstr "Flavor profilini etkinleştir" + +msgid "Enable the host" +msgstr "Sunucuyu etkinleştir" + +msgid "Enable the identity provider" +msgstr "Kimlik sağlayıcıyı etkinleştir" + +msgid "Enable the service provider" +msgstr "Servis sağlayıcıyı etkinleştir" + +msgid "Enable the service provider (default)" +msgstr "Servis sağlayıcıyı etkinleştir (varsayılan)" + +msgid "Enable user (default)" +msgstr "Kullanıcıyı etkinleştir (varsayılan)" + +msgid "Enable volume service" +msgstr "Disk bölümü servisini etkinleştir" + +msgid "End anchor for paging" +msgstr "Sayfalama için sabitleyiciyi sonlandır" + +msgid "" +"Endpoint to display (endpoint ID, service ID, service name, service type)" +msgstr "" +"Gösterilecek uç noktalar (uç nokta ID, servis ID, servis ismi, servis türü)" + +msgid "Endpoint to modify (ID only)" +msgstr "Düzenlenecek uç nokta (sadece ID)" + +msgid "Endpoint(s) to delete (ID only)" +msgstr "Silinecek uç nokta(lar) (sadece ID)" + +msgid "Ephemeral disk size in GB (default 0G)" +msgstr "GB cinsinden geçici disk boyutu (varsayılan 0G)" + +msgid "Error creating server\n" +msgstr "Sunucu oluşturulurken hata\n" + +#, python-format +msgid "Error creating server backup: %s" +msgstr "Sunucu yedeği oluşturulurken hata: %s" + +#, python-format +msgid "Error creating server image: %s" +msgstr "Sunucu imajı oluşturma hatası: %s" + +#, python-format +msgid "Error creating server: %s" +msgstr "Sunucu oluşturma başarısız: %s" + +msgid "Error deleting server\n" +msgstr "Sunucu silinirken hata\n" + +#, python-format +msgid "Error deleting server: %s" +msgstr "Sunucu silinirken hata: %s" + +msgid "Error migrating server\n" +msgstr "Sunucu göçü sırasında hata\n" + +#, python-format +msgid "Error migrating server: %s" +msgstr "Sunucu göçü sırasında hata: %s" + +msgid "Error rebooting server\n" +msgstr "Sunucu yeniden başlatma hatası\n" + +#, python-format +msgid "Error rebooting server: %s" +msgstr "Sunucu yeniden başlatma hatası: %s" + +msgid "Error rebuilding server\n" +msgstr "Sunucu yeniden yapılandırması sırasında hata\n" + +#, python-format +msgid "Error rebuilding server: %s" +msgstr "Sunucu yeniden yapılandırması sırasında hata: %s" + +msgid "Error resizing server\n" +msgstr "Sunucunun yeniden boyutlandırması sırasında hata\n" + +#, python-format +msgid "Error resizing server: %s" +msgstr "Sunucu yeniden boyutlandırma hatası: %s" + +msgid "Error retrieving diagnostics data\n" +msgstr "Teşhis verisi alınırken hata oluştu\n" + +#, python-format +msgid "Error while executing command: %s" +msgstr "Komut çalıştırılırken hata oluştu: %s" + +msgid "" +"Error: If a user or group is specified, either --domain or --project must " +"also be specified to list role grants." +msgstr "" +"Hata: Bir kullanıcı veya grup belirtilmişse, rol izinlerini listelemek için " +"--domain veya --project de belirtilmelidir." + +msgid "" +"Ethertype of network traffic (IPv4, IPv6; default: based on IP protocol)" +msgstr "" +"Ağ trafiğinin ethertype'ı (IPv4, IPv6; varsayılan: IP protokolüne bağlı)" + +#, python-format +msgid "Exclude %s which have all given tag(s) (Comma-separated list of tags)" +msgstr "" +"Verilen tüm etiketlere sahip %s'i hariç tut (Etiketlerin virgül ile ayrılmış " +"listesi)" + +#, python-format +msgid "Exclude %s which have any given tag(s) (Comma-separated list of tags)" +msgstr "" +"Verilen etiketlerden herhangi birine sahip %s'i hariç tut (Etiketlerin " +"virgül ile ayrılmış listesi)" + +msgid "Exclude remote IP prefix from traffic count" +msgstr "Uzak IP önekini trafik sayımından hariç tut" + +msgid "Existing consistency group (name or ID)" +msgstr "Mevcut tutarlılık grubu (isim veya ID)" + +msgid "Existing consistency group snapshot (name or ID)" +msgstr "Varolan tutarlılık grubu anlık görüntüsü (isim veya ID)" + +#, python-format +msgid "" +"Expected '=' or JSON data for option %(option)s, but encountered " +"JSON parsing error: %(error)s" +msgstr "" +"%(option)s seçeneği için '=' veya JSON verisi beklenirken JSON " +"yorumlama hatası oluştu: %(error)s" + +msgid "Extend volume size in GB" +msgstr "GB cinsinden genişletilmiş disk bölümü boyutu" + +msgid "" +"Extension to display. Currently, only network extensions are supported. " +"(Name or Alias)" +msgstr "" +"Gösterilecek uzantı. Şu an sadece ağ uzantıları destekleniyor. (İsim veya " +"Takma ad)" + +msgid "Extensions list not supported by Block Storage API" +msgstr "Blok Depolama API'ı uzantıları listelemeyi desteklemiyor" + +msgid "Extensions list not supported by Compute API" +msgstr "Hesaplama API'ı uzantıların listelemeyi desteklemiyor" + +msgid "Extensions list not supported by Identity API" +msgstr "Kimlik API için uzantıların listlenmesi desteklenmiyor" + +msgid "External Network used as router's gateway (name or ID)" +msgstr "Yönlendirici geçidi olarak kullanılan Harici Ağ (isim veya ID)" + +#, python-format +msgid "Failed to add project %(project)s access to flavor: %(e)s" +msgstr "%(project)s projesinin flavor'a erişimi başarısız: %(e)s" + +#, python-format +msgid "Failed to add project %(project)s access to type: %(e)s" +msgstr "%(project)s projesinin erişiminin şu türe eklenmesi başarısız: %(e)s" + +#, python-format +msgid "Failed to clean snapshot properties: %s" +msgstr "Anlık görüntü özellikleri silinirken hata oluştu: %s" + +#, python-format +msgid "Failed to clean volume properties: %s" +msgstr "Disk bölümü özelliklerini silme başarısız: %s" + +#, python-format +msgid "Failed to clear flavor property: %s" +msgstr "Flavor özelliğinin silinmesi başarısız: %s" + +#, python-format +msgid "Failed to create Network QoS rule: %(e)s" +msgstr "Ağ QoS kuralı oluşturulurken hata oluştu: %(e)s" + +#, python-format +msgid "Failed to delete %(dresult)s of %(total)s images." +msgstr "%(total)s imajdan %(dresult)s tanesi silinirken hata oluştu." + +#, python-format +msgid "Failed to delete %(resource)s with ID '%(id)s': %(e)s" +msgstr "'%(id)s' ID'li %(resource)s silinemedi: %(e)s" + +#, python-format +msgid "Failed to delete %(resource)s with name or ID '%(name_or_id)s': %(e)s" +msgstr "" +"'%(name_or_id)s' isimli veya ID'li %(resource)s silinirken hata oluştu: %(e)s" + +#, python-format +msgid "" +"Failed to delete EC2 credentials with access key '%(access_key)s': %(e)s" +msgstr "" +"'%(access_key)s' erişim anahtarlı EC2 kullanıcı bilgileri silinemedi: %(e)s" + +#, python-format +msgid "Failed to delete Network QoS rule ID \"%(rule)s\": %(e)s" +msgstr "\"%(rule)s\" Ağ QoS kural ID'si silinirken hata oluştu: %(e)s" + +#, python-format +msgid "Failed to delete QoS policy name or ID '%(qos_policy)s': %(e)s" +msgstr "" +"'%(qos_policy)s' isim veya ID'li QoS politikası silinirken hata oluştu: %(e)s" + +#, python-format +msgid "Failed to delete QoS specification with name or ID '%(qos)s': %(e)s" +msgstr "" +"'%(qos)s' isimli veya ID'li QoS özelliklerini silme işlemi başarısız: %(e)s" + +#, python-format +msgid "Failed to delete RBAC policy with ID '%(rbac)s': %(e)s" +msgstr "'%(rbac)s' ID'li RBAC politikası silinirken hata oluştu: %(e)s" + +#, python-format +msgid "Failed to delete address scope with name or ID '%(scope)s': %(e)s" +msgstr "" +"'%(scope)s' isimli veya ID'li adres kapsamı silinirken hata oluştu: %(e)s" + +#, python-format +msgid "Failed to delete agent with ID '%(id)s': %(e)s" +msgstr "'%(id)s' ID'li ajanın silinemedi: %(e)s" + +#, python-format +msgid "Failed to delete aggregate with name or ID '%(aggregate)s': %(e)s" +msgstr "'%(aggregate)s' isimli veya ID'li küme silinemedi: %(e)s" + +#, python-format +msgid "Failed to delete backup with name or ID '%(backup)s': %(e)s" +msgstr "'%(backup)s' isimli veya ID'li yedek silinirken hata oluştu: %(e)s" + +#, python-format +msgid "Failed to delete compute service with ID '%(service)s': %(e)s" +msgstr "'%(service)s' ID'li hesaplama servisini silme işlemi başarısız: %(e)s" + +#, python-format +msgid "" +"Failed to delete consistency group snapshot with name or ID '%(snapshot)s': " +"%(e)s" +msgstr "" +"'%(snapshot)s' isim veya ID'li tutarlılık grubu anlık görüntülerini silme " +"işlemi başarısız: %(e)s" + +#, python-format +msgid "" +"Failed to delete consistency group with name or ID '%(consistency_group)s':" +"%(e)s" +msgstr "" +"'%(consistency_group)s' isim veya ID'li tutarlılık grubunu silerken hata " +"oluştu:%(e)s" + +#, python-format +msgid "Failed to delete consumer with name or ID '%(consumer)s': %(e)s" +msgstr "'%(consumer)s' ismi ve ID'si ile alıcılar silinirken hata: %(e)s" + +#, python-format +msgid "Failed to delete consumer with type, name or ID '%(service)s': %(e)s" +msgstr "" +"'%(service)s' türlü, isimli veya ID'li alıcı silinirken hata oluştu: %(e)s" + +#, python-format +msgid "Failed to delete credentials with ID '%(credential)s': %(e)s" +msgstr "'%(credential)s' ID'li kimlik bilgileri silinirken hata oluştu: %(e)s" + +#, python-format +msgid "Failed to delete domain with name or ID '%(domain)s': %(e)s" +msgstr "'%(domain)s' isimli veya ID'li alan silinirken hata oluştu: %(e)s" + +#, python-format +msgid "Failed to delete endpoint with ID '%(endpoint)s': %(e)s" +msgstr "'%(endpoint)s' ID'li uç noktanın silinmesi başarısız: %(e)s" + +#, python-format +msgid "" +"Failed to delete federation protocol with name or ID '%(protocol)s': %(e)s" +msgstr "" +"'%(protocol)s' isimli veya ID'li federasyon protokolü silinirken hata " +"oluştu: %(e)s" + +#, python-format +msgid "Failed to delete flavor profile with ID '%(flavor_profile)s': %(e)s" +msgstr "" +"'%(flavor_profile)s' ID'li flavor profili silinirken hata oluştu: %(e)s" + +#, python-format +msgid "Failed to delete flavor with name or ID '%(flavor)s': %(e)s" +msgstr "'%(flavor)s' isimli veya ID'li flavor'ı silme işlemi başarısız: %(e)s" + +#, python-format +msgid "Failed to delete group with name or ID '%(group)s': %(e)s" +msgstr "'%(group)s' isimli veya ID'li grup silinirken hata oluştu: %(e)s" + +#, python-format +msgid "" +"Failed to delete identity providers with name or ID '%(provider)s': %(e)s" +msgstr "" +"'%(provider)s' isim veya ID'li kimlik sağlayıcılar silinirken hata oluştur: " +"%(e)s" + +#, python-format +msgid "Failed to delete image with name or ID '%(image)s': %(e)s" +msgstr "'%(image)s' isimli veya ID'li imaj silinirken hata oluştu: %(e)s" + +#, python-format +msgid "Failed to delete key with name '%(name)s': %(e)s" +msgstr "'%(name)s' isimli anahtarın silinmesi başarısız: %(e)s" + +#, python-format +msgid "Failed to delete mapping with name or ID '%(mapping)s': %(e)s" +msgstr "" +"%(mapping)s' isimli veya ID'li eşleştirme silinirken hata oluştu: %(e)s" + +#, python-format +msgid "Failed to delete meter rule with ID '%(id)s': %(e)s" +msgstr "'%(id)s' ID'li ölçüm kuralını silme işlemi başarısız: %(e)s" + +#, python-format +msgid "Failed to delete meter with ID '%(meter)s': %(e)s" +msgstr "'%(meter)s' ID'li ölçek silinirken hata oluştu: %(e)s" + +#, python-format +msgid "Failed to delete network agent with ID '%(agent)s': %(e)s" +msgstr "'%(agent)s' ID'li ağ ajanının silinmesi başarısız: %(e)s" + +#, python-format +msgid "Failed to delete network segment with ID '%(network_segment)s': %(e)s" +msgstr "'%(network_segment)s' ID'li ağ dilimleri silinirken hata oluştu: %(e)s" + +#, python-format +msgid "Failed to delete policy with name or ID '%(policy)s': %(e)s" +msgstr "'%(policy)s' isimli veya ID'li politika silinirken hata oluştu: %(e)s" + +#, python-format +msgid "Failed to delete port with name or ID '%(port)s': %(e)s" +msgstr "" +"'%(port)s' isimli veya ID'li bağlantı noktası silinirken hata oluştu: %(e)s" + +#, python-format +msgid "Failed to delete project with name or ID '%(project)s': %(e)s" +msgstr "'%(project)s' isimli veya ID'li projeyi silme işlemi başarısız: %(e)s" + +#, python-format +msgid "Failed to delete region with ID '%(region)s': %(e)s" +msgstr "'%(region)s' ID'li bölge silinirken hata oluştu: %(e)s" + +#, python-format +msgid "Failed to delete role with name or ID '%(role)s': %(e)s" +msgstr "'%(role)s' isim veya ID'li rolün silme işlemi başarısız: %(e)s" + +#, python-format +msgid "Failed to delete router with name or ID '%(router)s': %(e)s" +msgstr "" +"'%(router)s' isimli veya ID'li yönlendiriciyi silme işlemi başarısız: %(e)s" + +#, python-format +msgid "Failed to delete service provider with name or ID '%(provider)s': %(e)s" +msgstr "" +"'%(provider)s' isimli veya ID'li servis sağlayıcıyı silme işlemi başarısız: " +"%(e)s" + +#, python-format +msgid "Failed to delete service with name or ID '%(service)s': %(e)s" +msgstr "'%(service)s' isimli veya ID'li servisi silme başarısız: %(e)s" + +#, python-format +msgid "Failed to delete snapshot with name or ID '%(snapshot)s': %(e)s" +msgstr "" +"'%(snapshot)s' isimli veya ID'li anlık görüntüyü silme başarısız: %(e)s" + +#, python-format +msgid "Failed to delete subnet pool with name or ID '%(pool)s': %(e)s" +msgstr "" +"'%(pool)s' isimli veya ID'li altağ havuzu silinirken hata oluştu: %(e)s" + +#, python-format +msgid "Failed to delete subnet with name or ID '%(subnet)s': %(e)s" +msgstr "'%(subnet)s' isim veya ID'li altağı silerken hata oluştu: %(e)s" + +#, python-format +msgid "Failed to delete trust with name or ID '%(trust)s': %(e)s" +msgstr "" +"'%(trust)s' ismine veya ID'sine sahip güvenin silinmesi başarısız: %(e)s" + +#, python-format +msgid "Failed to delete user with name or ID '%(user)s': %(e)s" +msgstr "'%(user)s' isimli veya ID'li kullanıcının silinmesi başarısız: %(e)s" + +#, python-format +msgid "" +"Failed to delete volume transfer request with name or ID '%(transfer)s': " +"%(e)s" +msgstr "" +"'%(transfer)s' isimli veya ID'li disk bölümü aktarım isteği silinirken hata " +"oluştu: %(e)s" + +#, python-format +msgid "Failed to delete volume type with name or ID '%(volume_type)s': %(e)s" +msgstr "" +"'%(volume_type)s' isimli veya ID'li disk bölümü türünü silerken hata oluştu: " +"%(e)s" + +#, python-format +msgid "Failed to delete volume with name or ID '%(volume)s': %(e)s" +msgstr "" +"'%(volume)s' isimli veya ID'li disk bölümü silinirken hata oluştu: %(e)s" + +#, python-format +msgid "Failed to display the encryption information of this volume type: %s" +msgstr "Bu disk bölümü türü için şifreleme bilgisini gösterme başarısız: %s" + +#, python-format +msgid "Failed to find volume with name or ID '%(volume)s':%(e)s" +msgstr "'%(volume)s' isimli veya ID'li disk bölümü bulunamadı: %(e)s" + +#, python-format +msgid "Failed to get access project list for volume type %(type)s: %(e)s" +msgstr "" +"%(type)s disk bölümü türür için proje listesine erişim sağlama başarısız: " +"%(e)s" + +#, python-format +msgid "Failed to get access projects list for flavor '%(flavor)s': %(e)s" +msgstr "" +"'%(flavor)s' flavor için erişen projelerin listesinin alınma işlemi " +"başarısız: %(e)s" + +msgid "Failed to get an image file." +msgstr "İmaj dosyası alma başarısız." + +#, python-format +msgid "Failed to remove flavor access from project: %s" +msgstr "Projeden flavor erişiminin kaldırılması başarısız: %s" + +#, python-format +msgid "Failed to remove the encryption type for this volume type: %s" +msgstr "Bu disk bölümü türü için şifreleme türünü silme başarısız: %s" + +#, python-format +msgid "Failed to remove volume type access from project: %s" +msgstr "Projeden disk bölümü türü erişimi kaldırılırken hata oluştu: %s" + +msgid "Failed to retrieve extensions list from Network API" +msgstr "Ağ API'ından uzantı listesinin alınması başarısız" + +#, python-format +msgid "Failed to set Network QoS rule ID \"%(rule)s\": %(e)s" +msgstr "\"%(rule)s\" Ağ QoS kural ID'sini ayarlarken hata oluştu: %(e)s" + +#, python-format +msgid "Failed to set backup state: %s" +msgstr "Yedek durumunu ayarlama başarısız: %s" + +#, python-format +msgid "Failed to set encryption information for this volume type: %s" +msgstr "Bu disk bölümü türü için şifreleme bilgisini ayarlama başarısız: %s" + +#, python-format +msgid "Failed to set flavor access to project: %s" +msgstr "Projeye flavor erişiminin ayarlanması başarısız: %s" + +#, python-format +msgid "Failed to set flavor property: %s" +msgstr "Flavor özelliğinin ayarlanması başarısız: %s" + +#, python-format +msgid "Failed to set image property: %s" +msgstr "İmaj özelliği ayarlarken hata oluştu: %s" + +#, python-format +msgid "Failed to set snapshot property: %s" +msgstr "Anlık görüntü özelliğini ayarlama başarısız: %s" + +#, python-format +msgid "Failed to set snapshot state: %s" +msgstr "Anlık görüntü durumunu ayarlama başarısız: %s" + +#, python-format +msgid "Failed to set volume bootable property: %s" +msgstr "Disk bölümü ön yükleme özelliği ayarlanırken hata oluştu: %s" + +#, python-format +msgid "Failed to set volume property: %s" +msgstr "Disk özelliklerini ayarlama işlemi başarısız: %s" + +#, python-format +msgid "Failed to set volume read-only access mode flag: %s" +msgstr "Disk bölümü salt okunur erişim kipi bayrağı ayarlanamadı: %s" + +#, python-format +msgid "Failed to set volume size: %s" +msgstr "Disk bölümü boyutunu ayarlama başarısız: %s" + +#, python-format +msgid "Failed to set volume state: %s" +msgstr "Disk bölümü durumunu ayarlama başarısız: %s" + +#, python-format +msgid "Failed to set volume type access to project: %s" +msgstr "Projeye erişim için disk bölümü türü ayarlama başarısız: %s" + +#, python-format +msgid "Failed to set volume type property: %s" +msgstr "Disk bölümü türü özelliğini ayarlarken hata oluştu: %s" + +#, python-format +msgid "Failed to set volume type: %s" +msgstr "Disk bölümü türü ayarlanırken hata oluştu: %s" + +#, python-format +msgid "Failed to unset %(propret)s of %(proptotal)s properties." +msgstr "" +"%(proptotal)s özellikten %(propret)s özelliğin ayarının kaldırılması " +"başarısız." + +#, python-format +msgid "" +"Failed to unset %(tagret)s of %(tagtotal)s tags,Failed to unset %(propret)s " +"of %(proptotal)s properties." +msgstr "" +"%(tagtotal)s etiketten %(tagret)s tanesinin ayarlarını kaldırmak başarısız, " +"%(proptotal)s özellikten %(propret)s özelliğin ayarının kaldırılması " +"başarısız." + +#, python-format +msgid "Failed to unset %(tagret)s of %(tagtotal)s tags." +msgstr "" +"%(tagtotal)s etiketin %(tagret)s tanesinin ayarının kaldırılması başarısız" + +#, python-format +msgid "Failed to unset flavor property: %s" +msgstr "Flavor özelliğini kaldırma işlemi başarısız: %s" + +#, python-format +msgid "Failed to unset image property: %s" +msgstr "İmaj özelliği ayarını kaldırma başarısız: %s" + +#, python-format +msgid "Failed to unset volume property: %s" +msgstr "Disk bölümü özelliği ayarlarını kaldırma başarısız: %s" + +#, python-format +msgid "Failed to unset volume type property: %s" +msgstr "Disk bölümü türü özelliğini kaldırma işlemi başarısız: %s" + +#, python-format +msgid "Failed to update backup name or description: %s" +msgstr "Yedek ismi ve açıklaması güncellenirken hata oluştu: %s" + +#, python-format +msgid "Failed to update snapshot display name or display description: %s" +msgstr "" +"Anlık görüntü görünür ismi veya görünür açıklamasını güncelleme işlemi " +"başarısız: %s" + +#, python-format +msgid "Failed to update snapshot name or description: %s" +msgstr "Anlık görüntü adı veya açıklaması güncellenemedi: %s" + +#, python-format +msgid "Failed to update volume display name or display description: %s" +msgstr "" +"Disk bölümü gösterim ismi veya gösterim açıklamasını güncelleme başarısız: %s" + +#, python-format +msgid "Failed to update volume type name or description: %s" +msgstr "Disk bölümü türü ismi veya açıklaması güncellenirken hata oluştu: %s" + +msgid "Failover volume host to different backend" +msgstr "Disk bölümü anabilgisayarını başka arka yüze devret" + +msgid "Federation protocol to display (name or ID)" +msgstr "Gösterilecek federasyon protokolü (isim veya ID)" + +msgid "Federation protocol to modify (name or ID)" +msgstr "Düzenlenecek federasyon protokolü (isim veya ID)" + +msgid "Federation protocol(s) to delete (name or ID)" +msgstr "Silinecek federasyon protokol(ü/leri) (isim veya ID)" + +msgid "" +"File to inject into image before boot (repeat option to set multiple files)" +msgstr "" +"Önyüklemeden önce imaja enjekte etmek için dosya (birden çok dosyayı " +"ayarlamak için seçeneği tekrar edin)" + +msgid "" +"Filename for private key to save. If not used, print private key in console." +msgstr "" +"Kapalı anahtarın kaydedileceği dosyanın ismi. Kullanılmazsa, kapalı anahtar " +"konsola basılır." + +msgid "Filename for public key to add. If not used, creates a private key." +msgstr "" +"Eklenecek açık anahtarın dosya ismi. Kullanılmazsa, bir kapalı anahtar " +"oluşturur." + +msgid "Filename that contains a new set of mapping rules" +msgstr "Eşleşme kurallarının yeni bir kümesini içeren dosya adı" + +msgid "Filename that contains a set of mapping rules (required)" +msgstr "Eşleştirme kuralları kümesi içeren dosya adı (gerekli)" + +msgid "Filter by interface type (admin, public or internal)" +msgstr "Arayüz türüne göre filtrele (yönetici, genele açık veya dahili)" + +msgid "Filter by parent region ID" +msgstr "Üst bölge ID'sine göre filtrele" + +msgid "Filter by region ID" +msgstr "Bölge ID'sine göre filtrele" + +msgid "Filter by service (type, name or ID)" +msgstr "Servise göre filtrele (tür, isim veya ID)" + +msgid "Filter credentials by (name or ID)" +msgstr "Kimlik bilgilerini 'ya göre filtrele (isim veya ID)" + +msgid "Filter credentials by type: cert, ec2" +msgstr "Kimlik bilgilerinin türe göre filtrele: cert, ec2" + +msgid "Filter group list by (name or ID)" +msgstr "Grup listesini 'e göre filtrele (isim veya ID)" + +msgid "Filter group list by (name or ID)" +msgstr "'ya göre grup listesini filtrele (isim veya ID)" + +msgid "Filter hypervisors using substring" +msgstr "" +" alt karakter dizisini kullanarak yönetici arakatmanları filtrele" + +msgid "Filter images based on name." +msgstr "İmajları isme göre filtrele." + +msgid "Filter images based on status." +msgstr "İmajları duruma göre filtrele." + +msgid "Filter list by user (name or ID)" +msgstr "Listeyi kullanıcıya göre filtrele (isim veya ID)" + +msgid "Filter list using " +msgstr " kullanarak listeyi filtrele" + +msgid "Filter output based on property" +msgstr "Çıktıyı özelliğe göre filtrele" + +msgid "Filter projects by (name or ID)" +msgstr "Projeleri 'e göre filtrele (isim veya ID)" + +msgid "Filter projects by (name or ID)" +msgstr "Projeleri 'ya göre filtrele (isim veya ID)" + +msgid "Filter results by project (name or ID) (admin only)" +msgstr "Sonuçları projeye göre filtrele (isim veya ID) (sadece yönetici)" + +msgid "Filter results by status" +msgstr "Sunuçları duruma göre filtrele" + +msgid "Filter results by user (name or ID) (admin only)" +msgstr "Sonuçları kullanıcıya göre filtrele (isim veya ID) (sadece yönetici)" + +msgid "Filter results by volume name" +msgstr "Sonuçları disk bölümü ismine göre filtrele" + +msgid "Filter roles by (name or ID)" +msgstr "Rolleri 'ye göre filtrele (isim veya ID)" + +msgid "Filter roles by (name or ID)" +msgstr "Rolleri 'ya göre filtrele (isim veya ID)" + +msgid "Filter users by (name or ID)" +msgstr "Kullanıcıları 'e göre filtrele (isim veya ID)" + +msgid "Filter users by membership (name or ID)" +msgstr " üyeliğine göre kullanıcıları filtrele (isim veya ID)" + +msgid "Filter users by (name or ID)" +msgstr "Kullanıcıları 'ye göre filtrele (isim veya ID)" + +msgid "Filter users by project (name or ID)" +msgstr "Kullanıcıları projeye göre filtrele (isim veya ID)" + +msgid "Filters results by a consistency group (name or ID)" +msgstr "Bir tutarlılık grubuna göre sonuçları filtrele (isim veya ID)" + +msgid "Filters results by a name." +msgstr "Bir isme göre sonuçları filtrele." + +msgid "" +"Filters results by a status (\"available\", \"error\", \"creating\", " +"\"deleting\" or \"error_deleting\")" +msgstr "" +"Sonuçları bir duruma göre filtrele (\"available\", \"error\", \"creating\", " +"\"deleting\" veya \"error_deleting\")" + +msgid "" +"Filters results by a status. ('available', 'error', 'creating', 'deleting' " +"or 'error-deleting')" +msgstr "" +"Bir duruma göre sonuçları filtrele. ('kullanılabilir', 'hata', " +"'oluşturuluyor', 'siliniyor' veya 'silinirken hata')" + +msgid "Filters results by a volume (name or ID)." +msgstr "Bir disk bölümüne göre sonuçları filtrele (isim veya ID)." + +msgid "Filters results by the backup name" +msgstr "Yedek ismine göre sonuçları listele" + +msgid "" +"Filters results by the backup status ('creating', 'available', 'deleting', " +"'error', 'restoring' or 'error_restoring')" +msgstr "" +"Yedek durumuna göre sonuçları filtrele ('oluşturuluyor', 'kullanılabilir', " +"'siliniyor', 'hata' veya 'geri yüklenirken hata')" + +msgid "Filters results by the volume which they backup (name or ID)" +msgstr "Yedeklenen disk bölümüne göre sonuçları filtrele (isim veya ID)" + +msgid "Fixed IP address mapped to the floating IP" +msgstr "Sabit IP adres yüzen IP adresi ile eşleşti" + +msgid "Fixed IP address to associate with this floating IP address" +msgstr "Bu kayan IP adresi ile ilişkili sabit IP adresi" + +msgid "Fixed IP address to remove from the server (IP only)" +msgstr "Sunucudan kaldırılacak sabit IP adresi (sadece IP)" + +msgid "Fixed IP of the port (required only if port has multiple IPs)" +msgstr "" +"Bağlantı noktasının sabit IP'si (yalnızca bağlantı noktası birden fazla IP " +"bulunduğunda gereklidir)" + +msgid "Flavor is available to other projects (default)" +msgstr "Flavor diğer projeler için kullanılabilirdir (varsayılan)" + +msgid "Flavor is not available to other projects" +msgstr "Flavor diğer projelerde kullanılabilir değildir" + +msgid "Flavor profile to display (ID only)" +msgstr "Gösterilecek flavor profili (sadece ID)" + +msgid "Flavor profile to update (ID only)" +msgstr "Güncellenecek flavor profili (sadece ID)" + +msgid "Flavor profile(s) to delete (ID only)" +msgstr "Silinecek flavor profilleri (sadece ID)" + +msgid "Flavor to display (name or ID)" +msgstr "Gösterilecek flavor (isim veya ID)" + +msgid "Flavor to modify (name or ID)" +msgstr "Düzenlenecek flavor (isim veya ID)" + +msgid "Flavor to update (name or ID)" +msgstr "Güncellenecek flavor (isim veya ID)" + +msgid "Flavor(s) to delete (name or ID)" +msgstr "Silinecek flavor(lar) (isim veya ID)" + +msgid "Floating IP address" +msgstr "Yüzen IP adresi" + +msgid "Floating IP address to assign to server (IP only)" +msgstr "Sunucuya atanacak kayan IP adresi (sadece IP)" + +msgid "Floating IP address to remove from server (IP only)" +msgstr "Sunucudan kaldırılacak kayan IP adresi (sadece IP)" + +msgid "Floating IP to associate (IP address or ID)" +msgstr "İlişkilendirilecek yüzen IP adresi (IP adres veya ID)" + +msgid "Floating IP to disassociate (IP address or ID)" +msgstr "Bağlantıyı kesmek için kayan IP (IP adresi veya kimliği)" + +msgid "Floating IP to display (IP address or ID)" +msgstr "Görüntülenecek kayan IP (IP adresi veya kimliği)" + +msgid "Floating IP(s) to delete (IP address or ID)" +msgstr "Silinecek yüzen IP(ler) (IP adres veya ID)" + +msgid "Floating ip pool operations are only available for Compute v2 network." +msgstr "" +"Yüzen IP havuz işlemleri sadece Hesaplama v2 ağı için kullanılabilirdir." + +msgid "Force down service" +msgstr "Servisi durmaya zorla" + +msgid "Force image change if volume is in use (only meaningful with --volume)" +msgstr "" +"Disk bölümü kullanımda ise imajı değişmeye zorla (sadece --volume ile " +"anlamlıdır)" + +msgid "" +"Force image creation if volume is in use (only meaningful with --volume)" +msgstr "" +"Disk bölümü kullanımda ise imaj oluşturmaya zorla (sadece --volume ile " +"anlamlıdır)" + +msgid "Force up service" +msgstr "Servisi açılmaya zorla" + +msgid "Freeze and disable the specified volume host" +msgstr "Belirtilen disk bölümü sunucusunu dondur ve devre dışı bırak" + +msgid "Group containing (name or ID)" +msgstr "'ı içeren grup (isim veya ID)" + +msgid "Group to check (name or ID)" +msgstr "Kontrol edilecek grup (isim veya ID)" + +msgid "Group to contain (name or ID)" +msgstr "'ı içeren grup (isim veya ID)" + +msgid "Group to display (name or ID)" +msgstr "Gösterilecek grup (isim veya ID)" + +msgid "Group to filter (name or ID)" +msgstr "Filtrelenecek grup (isim veya ID)" + +msgid "Group to modify (name or ID)" +msgstr "Düzenlenecek grup (isim veya ID)" + +msgid "Group(s) to delete (name or ID)" +msgstr "Silinecek grup(lar) (isim veya ID)" + +msgid "Helper class capable of reading rules from files" +msgstr "Yardımcı sınıf kuralları dosyadan okuyabilir" + +msgid "Hints for the scheduler (optional extension)" +msgstr "Zamanlayıcı için ipuçları (isteğe bağlı eklenti)" + +msgid "Host to add to " +msgstr " için eklenecek sunucu" + +msgid "Host to modify (name only)" +msgstr "Düzenlenecek sunucu (isim sadece)" + +msgid "Host to remove from " +msgstr "'den kaldırılacak sunucu" + +msgid "Hypervisor to display (name or ID)" +msgstr "Gösterilecek yönetici arakatman (isim veya ID)" + +msgid "ICMP IP protocol required with arguments --icmp-type and --icmp-code" +msgstr "" +"ICMP IP protokolü --icmp-type ve --icmp-code argümanları ile zorunludur" + +msgid "ICMP code for ICMP IP protocols" +msgstr "ICMP IP protokolleri için ICMP kodu" + +msgid "ICMP type for ICMP IP protocols" +msgstr "ICMP IP protokolü için ICMP türü" + +msgid "ID of agent(s) to delete" +msgstr "Silinecek ajanların ID'si" + +msgid "ID of credential to change" +msgstr "Değiştirilecek kimlik bilgilerinin ID'si" + +msgid "ID of credential to display" +msgstr "Gösterilecek kimlik bilgilerinin ID'si" + +msgid "ID of credential(s) to delete" +msgstr "Silinecek kimlik bilgilerinin ID'si" + +msgid "ID of kernel image used to boot this disk image" +msgstr "Bu disk imajını ön yüklemek için kullanılan çekirdek imajının kimliği" + +msgid "ID of ramdisk image used to boot this disk image" +msgstr "Bu disk imajını önyüklemek için kullanılan ramdisk imajının kimliği" + +msgid "ID of server instance used to create this image" +msgstr "Bu imajı oluşturmak için kullanılan sunucu örneği kimliği" + +msgid "ID of the agent" +msgstr "Ajanın ID'si" + +msgid "IP address to add to server (name only)" +msgstr "Sunucuya eklenecek IP adresi (sadece isim)" + +msgid "IP address to remove from server (name only)" +msgstr "Sunucudan kaldırılacak IP adresi (sadece isim)" + +msgid "" +"IP protocol (ah, dccp, egp, esp, gre, icmp, igmp, ipv6-encap, ipv6-frag, " +"ipv6-icmp, ipv6-nonxt, ipv6-opts, ipv6-route, ospf, pgm, rsvp, sctp, tcp, " +"udp, udplite, vrrp and integer representations [0-255]; default: tcp)" +msgstr "" +"IP protokolü (ah, dccp, egp, esp, gre, icmp, igmp, ipv6-encap, ipv6-frag, " +"ipv6-icmp, ipv6-nonxt, ipv6-opts, ipv6-route, ospf, pgm, rsvp, sctp, tcp, " +"udp, udplite, vrrp ve tam sayı gösterimi [0-255]; varsayılan: tcp)" + +msgid "IP protocol (icmp, tcp, udp; default: tcp)" +msgstr "IP protokolü (icmp, tcp, udp; varsayılan: tcp)" + +msgid "IP version (default is 4)" +msgstr "IP sürümü (varsayılan 4)" + +msgid "" +"IP version (default is 4). Note that when subnet pool is specified, IP " +"version is determined from the subnet pool and this option is ignored." +msgstr "" +"IP sürümü (varsayılan 4). Alt ağ havuzu belirtildiğinde, IP sürümü alt ağ " +"havuzundan belirlenir ve bu seçenek göz ardı edilir." + +msgid "IPv4 subnet for fixed IPs (in CIDR notation)" +msgstr "Sabit IP'ler için IPv4 alt ağı (CIDR gösteriminde)" + +msgid "" +"IPv6 RA (Router Advertisement) mode, valid modes: [dhcpv6-stateful, dhcpv6-" +"stateless, slaac]" +msgstr "" +"IPv6 (Yönlendirici İlanı) kipi, geçerli kipler: [dhcpv6-stateful, dhcpv6-" +"stateless, slaac]" + +msgid "" +"IPv6 address mode, valid modes: [dhcpv6-stateful, dhcpv6-stateless, slaac]" +msgstr "" +"IPv6 adres kipi, geçerli kipler: [dhcpv6-stateful, dhcpv6-stateless, slaac]" + +#, python-format +msgid "Identity API version, default=%s (Env: OS_IDENTITY_API_VERSION)" +msgstr "Kimlik API sürümü, varsayılan=%s (Env: OS_IDENTITY_API_VERSION)" + +msgid "" +"Identity provider that supports (name or ID) (required)" +msgstr "" +"'ü destekleyen kimlik sağlayıcı (isim veya ID) (gerekli)" + +msgid "" +"Identity provider that will support the new federation protocol (name or " +"ID) (required)" +msgstr "" +"Yeni federasyon protokolünü destekleyecek kimlik sağlayıcı (isim veya ID) " +"(gerekli)" + +msgid "Identity provider to display" +msgstr "Gösterilecek kimlik sağlayıcı" + +msgid "Identity provider to list (name or ID) (required)" +msgstr "Listelenecek kimlik sağlayıcı (isim veya ID) (gerekli)" + +msgid "Identity provider to modify" +msgstr "Düzenlenecek kimlik sağlayıcıları" + +msgid "Identity provider(s) to delete" +msgstr "Silinecek kimlik sağlayıcı(lar)" + +msgid "" +"If specified, the volume state will be locked and will not allow a migration " +"to be aborted (possibly by another operation)" +msgstr "" +"Belirtilirse, disk bölümü durumu kilitlenir ve geçiş işleminin iptal " +"edilmesine izin vermeyecektir (muhtemelen başka bir işlemle)" + +msgid "" +"If specified, the volume state will not be locked and the a migration can be " +"aborted (default) (possibly by another operation)" +msgstr "" +"Belirtilirse, disk bölümü durumu kilitlenmez ve bir taşıma işlemi iptal " +"edilebilir (varsayılan) (muhtemelen başka bir işlemle)" + +msgid "If topology exists returns the topology's information (Default)" +msgstr "Topoloji var ise, topolojinin bilgisini döndürür (Varsayılan)" + +#, python-format +msgid "Image %(id)s was %(status)s." +msgstr "%(id)s imaj %(status)s idi." + +#, python-format +msgid "Image API version, default=%s (Env: OS_IMAGE_API_VERSION)" +msgstr "İmaj API sürümü, öntanımlı=%s (Env: OS_IMAGE_API_VERSION)" + +msgid "Image ID to reserve" +msgstr "Ayrılacak imaj ID'si" + +msgid "Image can be shared" +msgstr "İmaj paylaşılabilir" + +#, python-format +msgid "" +"Image container format. The supported options are: %(option_list)s. The " +"default format is: %(default_opt)s" +msgstr "" +"İmaj kap biçemi. Desteklenen seçenekler: %(option_list)s. Varsayılan biçem: " +"%(default_opt)s" + +#, python-format +msgid "Image container format. The supported options are: %s" +msgstr "İmaj kap biçimi. Desteklenen seçenekler: %s" + +#, python-format +msgid "Image disk format. The supported options are: %s" +msgstr "İmaj disk biçimi. Desteklenen seçenekler: %s" + +#, python-format +msgid "Image disk format. The supported options are: %s." +msgstr "İmaj disk biçemi. Desteklenen seçenekler: %s." + +#, python-format +msgid "" +"Image disk format. The supported options are: %s. The default format is: raw" +msgstr "" +"İmaj disk biçemi. Desteklenen seçenekler: %s. Varsayılan biçem: işlem " +"görmemiş" + +msgid "Image hash used for verification" +msgstr "Doğrulama için kullanılan imaj özeti" + +msgid "Image is accessible to the community" +msgstr "İmaj topluluk tarafından erişilebilir" + +msgid "Image is accessible to the public" +msgstr "İmaj genele açıktır" + +msgid "Image is inaccessible to the public (default)" +msgstr "İmaj genele açık değildir (varsayılan)" + +msgid "Image size, in bytes (only used with --location and --copy-from)" +msgstr "" +"İmaj boyutu, bayt cinsinden (sadece --location ve --copy-from ile kullanılır)" + +msgid "Image to display (name or ID)" +msgstr "Görüntülenecek imaj (ad veya kimlik)" + +msgid "Image to modify (name or ID)" +msgstr "Düzenlenecek imaj (isim veya ID)" + +msgid "Image to save (name or ID)" +msgstr "Kaydedilecek imaj (isim veya ID)" + +msgid "Image to share (name or ID)" +msgstr "Paylaşılacak imaj (isim veya ID)" + +msgid "Image to unshare (name or ID)" +msgstr "Paylaşımdan kaldırılacak imaj (isim veya ID)" + +msgid "Image(s) to delete (name or ID)" +msgstr "Silinecek imaj(lar) (isim veya ID)" + +msgid "Include (name or ID)" +msgstr "'i içer (isim veya ID)" + +msgid "Include (name or ID)" +msgstr "'u içer (isim veya ID)" + +msgid "Include (name or ID)" +msgstr " dahil et (isim veya ID)" + +msgid "Include (name or ID)" +msgstr " dahil et (isim veya ID)" + +msgid "Include all projects (admin only)" +msgstr "Tüm projeleri dahil et (sadece yönetici)" + +msgid "Include remote IP prefix from traffic count (default)" +msgstr "Trafik sayımından uzak IP önekini ekle (varsayılan)" + +msgid "Include reservations count [only valid with --absolute]" +msgstr "Rezervasyon sayısını ekle [sadece --absolute ile geçerlidir]" + +msgid "" +"Incorrect set of arguments provided. See openstack --help for more details" +msgstr "" +"Yanlış argüman kümesi girildi. Daha fazla detay için openstack --help " +"komutunun çıktısına bakınız" + +msgid "Ingress traffic direction from the project point of view" +msgstr "Projenin bakış açısından gelen trafik yönü" + +#, python-format +msgid "Invalid argument %s, characters ',' and '=' are not allowed" +msgstr "%s geçersiz değişken, ',' ve '=' karakterlerine izin verilmiyor" + +#, python-format +msgid "Invalid changes-since value: %s" +msgstr "Geçersiz değişiklikler-şu değerden beri:%s" + +#, python-format +msgid "" +"Invalid nic argument '%s'. Nic arguments must be of the form --nic ." +msgstr "" +"Geçersiz '%s' nic argümanı, Nic argümanları --nic biçiminde olmalıdır." + +msgid "Issue new token" +msgstr "Yeni jeton yayınla" + +#, python-format +msgid "Key file %(private_key)s can not be saved: %(exception)s" +msgstr "%(private_key)s anahtar dosyası kaydedilemedi: %(exception)s" + +#, python-format +msgid "Key file %(public_key)s not found: %(exception)s" +msgstr "%(public_key)s anahtar dosyası bulunamadı: %(exception)s" + +msgid "Keypair to inject into this server (optional extension)" +msgstr "Bu sunucuya enjekte etmek için anahtarlık (isteğe bağlı uzantı)" + +msgid "Label to associate with this metering rule (name or ID)" +msgstr "Bu ölçüm kuralı ile ilişkili etiket (isim veya ID)" + +msgid "Limit the number of containers returned" +msgstr "İade edilen kap sayısını sınırla" + +msgid "Limit the number of objects returned" +msgstr "Döndürülen nesnelerin sayısını sınırla" + +#, python-format +msgid "List %s which have all given tag(s) (Comma-separated list of tags)" +msgstr "" +"Verilen tüm etiketlere sahip olan %s'i listele (Virgül ile ayrılmış etiket " +"listesi)" + +#, python-format +msgid "List %s which have any given tag(s) (Comma-separated list of tags)" +msgstr "" +"Verilen tüm etiketlere sahip olan %s'i listele (Virgül ile ayrılmış etiket " +"listesi)" + +msgid "List API extensions" +msgstr "API uzantılarını listele" + +msgid "List EC2 credentials" +msgstr "EC2 kimlik bilgilerini listele" + +msgid "List IP availability for network" +msgstr "Ağ için IP kullanılabilirliğini listele" + +msgid "List IP availability of given IP version networks (default is 4)" +msgstr "" +"Verilen IP sürüm ağlarının IP kullanılabilirliğini listele (varsayılan sürüm " +"4)" + +msgid "List IP availability of given project (name or ID)" +msgstr "Verilen projenin IP kullanılabilirliğini listeleyin (isim veya ID)" + +msgid "List Network QoS rules" +msgstr "Ağ QoS kurallarını listele" + +msgid "List QoS policies" +msgstr "QoS politikaları listesi" + +msgid "List QoS rule types" +msgstr "QoS kural türlerini listele" + +msgid "List QoS specifications" +msgstr "QoS özelliklerini listele" + +msgid "List Service Providers" +msgstr "Servis Sağlayıcıları listele" + +msgid "List a project's resources" +msgstr "Bir projenin kaynaklarını listele" + +msgid "List accessible domains" +msgstr "Erişilebilir alanları listele" + +msgid "List accessible projects" +msgstr "Erişilebilir projeleri listele" + +msgid "List additional fields in output" +msgstr "Çıktıdaki ek alanları listele" + +msgid "List address scopes" +msgstr "Adres kapsamlarını listele" + +msgid "List address scopes according to their project (name or ID)" +msgstr "Adres kapsamlarını projelerine göre listele (ad veya kimlik)" + +msgid "List address scopes not shared between projects" +msgstr "Projeler arasında paylaşılmayan adres kapsamlarını listele" + +msgid "List address scopes of given IP version networks (4 or 6)" +msgstr "Verilen IP sürüm ağlarının adres kapsamlarını listele (4 veya 6)" + +msgid "List address scopes shared between projects" +msgstr "Projeler arasında paylaşılan adres kapsamlarını listele" + +msgid "List agents hosting a network (name or ID)" +msgstr "Bir ağa ev sahipliği yapan ajanları listele (isim veya ID)" + +msgid "List agents hosting this router (name or ID)" +msgstr "Bu yönlendiriciyi barındıran ajanları listele (isim veya ID)" + +msgid "List all aggregates" +msgstr "Tüm kümeleri listele" + +msgid "List all containers (default is 10000)" +msgstr "Tüm kapları listlele (varsayılan 10000)" + +msgid "List all flavors, whether public or private" +msgstr "Herkese açık veya gizli bütün flavorları listele" + +msgid "List all objects in container (default is 10000)" +msgstr "Kaptaki tüm nesneleri listele (varsayılan 1000)" + +msgid "List all rules in this security group (name or ID)" +msgstr "Bu güvenlik grubundaki tüm kuralları listele (isim veya ID)" + +msgid "List all server groups." +msgstr "Bütün sunucu gruplarını listele." + +msgid "List availability zones and their status" +msgstr "Kullanılabilirlik alanlarını ve durumlarını listele" + +msgid "List available images" +msgstr "Kullanılabilir imajlar listesi" + +msgid "List backups" +msgstr "Yedekleri listele" + +msgid "List compute agents" +msgstr "Hesaplama ajanlarını listele" + +msgid "List compute availability zones" +msgstr "Hesaplama kullanılabilirlik alanlarını listele" + +msgid "List compute quota" +msgstr "Hesaplama kotasını listele" + +msgid "List compute services" +msgstr "Hesaplama servislerini listele" + +msgid "List consistency group snapshots." +msgstr "Tutarlılık grubu anlık görüntülerini listele." + +msgid "List consistency groups." +msgstr "Tutarlılık gruplarını listele." + +msgid "List consumers" +msgstr "Alıcıları listele" + +msgid "List containers" +msgstr "Kapları listele" + +msgid "List credentials" +msgstr "Kimlik bilgilerini listele" + +msgid "List disabled networks" +msgstr "Devre dışı bırakılan ağları listele" + +msgid "List disabled routers" +msgstr "Devre dışı bırakılmış yönlendiricileri listele" + +msgid "List domains" +msgstr "Alanları listele" + +msgid "List enabled networks" +msgstr "Aktif ağları listele" + +msgid "List enabled routers" +msgstr "Etkinleştirilmiş yönlendiricileri listele" + +msgid "List endpoints" +msgstr "Uç noktaları listele" + +msgid "List extensions for the Block Storage API" +msgstr "Blok Depolama API için uzantıları listele" + +msgid "List extensions for the Compute API" +msgstr "Hesaplama API için uzantıları listele" + +msgid "List extensions for the Identity API" +msgstr "Kimlik API için uzantıları listele" + +msgid "List extensions for the Network API" +msgstr "Ağ API için uzantıları listele" + +msgid "List external networks" +msgstr "Harici ağları listele" + +msgid "List federation protocols" +msgstr "Federasyon protokollerinin listele" + +msgid "List flavors" +msgstr "Flavorları listele" + +msgid "List floating IP(s)" +msgstr "Yüzen IP(ler) listesi" + +msgid "List floating IP(s) according to given fixed IP address" +msgstr "Verilen sabit IP adresine göre yüzen IP(ler) listesi" + +msgid "List floating IP(s) according to given network (name or ID)" +msgstr "Verilen ağdaki yüzen IP(ler) listesi (isim veya ID)" + +msgid "List floating IP(s) according to given port (name or ID)" +msgstr "Verilen bağlantı noktasına göre yüzen IP(ler) listesi (isim veya ID)" + +msgid "List floating IP(s) according to given project (name or ID)" +msgstr "Verilen projeye göre yüzen IP(ler)i listele (isim veya ID)" + +msgid "List floating IP(s) according to given router (name or ID)" +msgstr "Verilen yönlendiriciye göre yüzen IP(ler)i listele (isim veya ID)" + +msgid "List floating IP(s) according to given status ('ACTIVE', 'DOWN')" +msgstr "Verilen duruma göre yüzen IP(ler) listesi ('AKTİF', 'KAPALI')" + +msgid "List groups" +msgstr "Grupları listele" + +msgid "List hosts" +msgstr "Sunucuları listele" + +msgid "List hypervisors" +msgstr "Yönetici arakatman listesi" + +msgid "List identity providers" +msgstr "Kimlik sağlayıcılarını listele" + +msgid "List internal networks" +msgstr "Dahili ağları listele" + +msgid "List key fingerprints" +msgstr "Anahtar parmak izlerini listele" + +msgid "List mappings" +msgstr "Eşleşme listesi" + +msgid "List meter rules" +msgstr "Ölçek kurallarını listele" + +msgid "List module versions" +msgstr "Modül sürümlerini listele" + +msgid "List network RBAC policies" +msgstr "Ağ RBAC politikalarının listesi" + +msgid "" +"List network RBAC policies according to given action (\"access_as_external\" " +"or \"access_as_shared\")" +msgstr "" +"Verilen eyleme göre ağ RBAC ilkelerini listele (\"access_as_external\" veya " +"\"access_as_shared\")" + +msgid "" +"List network RBAC policies according to given object type (\"qos_policy\" or " +"\"network\")" +msgstr "" +"Verilen nesne türüne göre Ağ RBAC politikalarının listesi (\"qos_policy\" " +"veya \"network\")" + +msgid "List network agents" +msgstr "Ağ ajanlarını listele" + +msgid "List network availability zones" +msgstr "Ağ kullanılabilirlik alanlarını listele" + +msgid "List network flavor profile(s)" +msgstr "Ağ flavor profil(ler)ini listele" + +msgid "List network flavors" +msgstr "Ağ flavor'larını listele" + +msgid "List network meters" +msgstr "Ağ ölçeklerini listele" + +msgid "List network quota" +msgstr "Ağ kotasını listele" + +msgid "List network segments" +msgstr "Ağ bölümlerini listele" + +msgid "List network segments that belong to this network (name or ID)" +msgstr "Bu ağa ait ağ kesimlerini listeleyin (ad veya kimlik)" + +msgid "List networks" +msgstr "Ağları listele" + +msgid "" +"List networks according to VLAN ID for VLAN networks or Tunnel ID for GENEVE/" +"GRE/VXLAN networks" +msgstr "" +"VLAN ağları için VLAN ID'ye veya GENEVA/GRE/VXLAN ağları için Tünel ID'ye " +"göre ağları listele" + +msgid "List networks according to name of the physical network" +msgstr "Ağları fiziksel ağın adına göre listeleme" + +msgid "List networks according to their name" +msgstr "Ağları isimlerine göre listele" + +msgid "" +"List networks according to their physical mechanisms. The supported options " +"are: flat, geneve, gre, local, vlan, vxlan." +msgstr "" +"Ağları fiziksel mekanizmalarına göre listeleyin. Desteklenen seçenekler " +"şunlardır: flat, geneve, gre, local, vlan, vxlan." + +msgid "List networks according to their project (name or ID)" +msgstr "Projelerine göre ağları listele (isim veya ID)" + +msgid "" +"List networks according to their status ('ACTIVE', 'BUILD', 'DOWN', 'ERROR')" +msgstr "" +"Ağları durumlarına göre listeleyin ('AKTİF', 'KURMA', 'KAPALI', 'HATA')" + +msgid "List networks hosted by agent (ID only)" +msgstr "Aracı tarafından barındırılan ağları listele (yalnızca ID)" + +msgid "List networks not shared between projects" +msgstr "Projeler arasında paylaşılmayan ağları listele" + +msgid "List networks shared between projects" +msgstr "Projeler arasında paylaşılan ağları listele" + +msgid "List objects" +msgstr "Nesneleri listele" + +msgid "List only address scopes of given name in output" +msgstr "Çıktıda verilen adın sadece adres kapsamlarını listeleyin" + +msgid "List only agents running on the specified host" +msgstr "Yalnızca belirtilen ana bilgisayarda çalışan ajanları listele" + +msgid "" +"List only agents with the specified agent type. The supported agent types " +"are: bgp, dhcp, open-vswitch, linux-bridge, ofa, l3, loadbalancer, metering, " +"metadata, macvtap, nic." +msgstr "" +"Yalnızca belirtilen aracı türü olan aracıları listeleyin. Desteklenen aracı " +"türleri şunlardır: bgp, dhcp, open-vswitch, linux-bridge, ofa, l3, " +"loadbalancer, metering, metadata, macvtap, nic." + +msgid "List only ports attached to this router (name or ID)" +msgstr "" +"Yalnızca bu yönlendiriciye bağlı bağlantı noktalarını listeleyin (adı veya " +"ID'si)" + +msgid "List only ports attached to this server (name or ID)" +msgstr "Sadece bu sunucuya takılan bağlantı noktalarını listele (isim veya ID)" + +msgid "List only ports connected to this network (name or ID)" +msgstr "" +"Yalnızca bu ağa bağlı bağlantı noktalarını (isim veya kimliğini) listeleyin" + +msgid "" +"List only ports with the specified device owner. This is the entity that " +"uses the port (for example, network:dhcp)." +msgstr "" +"Yalnızca belirtilen aygıt sahibi olan bağlantı noktalarını listeleyin. Bu, " +"bağlantı noktasını kullanan varlıktır (örneğin, ağ: dhcp)." + +msgid "List only ports with this MAC address" +msgstr "Sadece bu MAC adresine sahip bağlantı noktalarını listele" + +msgid "List only private flavors" +msgstr "Sadece gizli flavorları listele" + +msgid "List only private images" +msgstr "Sadece gizli imajları listele" + +msgid "List only private types (admin only)" +msgstr "Sadece özel türleri listele (sadece yönetici)" + +msgid "List only public flavors (default)" +msgstr "Sadece herkese açık flavor'ları listele (varsayılan)" + +msgid "List only public images" +msgstr "Sadece genele açık imajları listele" + +msgid "List only public types" +msgstr "Sadece genele açık türleri listele" + +msgid "" +"List only servers changed after a certain point of time. The provided time " +"should be an ISO 8061 formatted time. ex 2016-03-04T06:27:59Z ." +msgstr "" +"Yalnızca belirli bir zaman noktasından sonra sunucuları listeleyin. Verilen " +"süre bir ISO 8061 biçiminde olmalıdır. Örn 2016-03-04T06: 27: 59Z ." + +msgid "List only shared images" +msgstr "Sadece paylaşılan imajları listele" + +msgid "List only specified service (name only)" +msgstr "Sadece belirtilen servisleri listele (sadece isim)" + +msgid "List only subnet pools of given address scope in output (name or ID)" +msgstr "" +"Verilen adres kapsamındaki sadece altağ havuzlarını çıktıda listele (isim " +"veya ID)" + +msgid "List only subnet pools of given name in output" +msgstr "Verilen isimdeki sadece altağ havuzlarını çıktıda listele" + +msgid "" +"List only subnets of a given service type in output e.g.: network:" +"floatingip_agent_gateway. Must be a valid device owner value for a network " +"port (repeat option to list multiple service types)" +msgstr "" +"Çıktıda verilen servisin sadece altağlarını listele örn: network:" +"floatingip_agent_gateway. Bir ağ bağlantı noktası için geçerli bir aygıt " +"sahibi değeri olmalıdır (birden fazla servis türü listelemek için seçeneği " +"tekrarla)" + +msgid "" +"List only subnets of given IP version in output.Allowed values for IP " +"version are 4 and 6." +msgstr "" +"Verilen IP sürümünün sadece altağlarını çıktıda listele. IP sürümü için izin " +"verilen sürümler 4 ve 6." + +msgid "List only subnets of given gateway IP in output" +msgstr "Verilen geçit IP'sinin sadece alt ağlarını çıktıda listele" + +msgid "List only subnets of given name in output" +msgstr "Verilen isimdeki sadece altağları çıktıda listele" + +msgid "" +"List only subnets of given subnet range (in CIDR notation) in output e.g.: --" +"subnet-range 10.10.0.0/16" +msgstr "" +"Verilen alt ağ aralığında (CIDR notasyonu ile) sadece altağları çıktıda " +"listele örn: --subnet-range 10.10.0.0/16" + +msgid "" +"List only subnets which belong to a given network in output (name or ID)" +msgstr "Verilen bir ağa ait sadece altağları çıktıda listele (isim veya ID)" + +msgid "" +"List only subnets which belong to a given project in output (name or ID)" +msgstr "" +"Verilen bir projeye ait sadece altağları çıktıda listele (isim veya ID)" + +msgid "List policies" +msgstr "Politikaları listele" + +msgid "List pools of floating IP addresses" +msgstr "Yüzer IP adresleri havuzlarını listele" + +msgid "List ports" +msgstr "Bağlantı noktalarını listele" + +msgid "List ports according to their project (name or ID)" +msgstr "Projeye bağlı olarak bağlantı noktalarını listele (isim veya ID)" + +msgid "List projects" +msgstr "Projeleri listele" + +msgid "List projects for the authenticated user. Supersedes other filters." +msgstr "" +"Yetkilendirilmiş kullanıcı için projeleri listele. Diğer filtrelerin yerini " +"alır." + +msgid "List qos policies according to their project (name or ID)" +msgstr "Projelerine göre QoS politikalarını listele (isim veya ID)" + +msgid "List qos policies not shared between projects" +msgstr "Projeler arasında paylaştırılmayan qos ilkelerini listele" + +msgid "List qos policies shared between projects" +msgstr "Projeler arasında paylaşılan qos politikalarını listele" + +msgid "List quotas for all projects with non-default quota values" +msgstr "" +"Varsayılan olmayan kota değerlerine sahip tüm projelerin kotalarını listeleme" + +msgid "List recent events of a server" +msgstr "Bir sunucunun son olaylarını listele" + +msgid "List recognized commands by group" +msgstr "Grup tarafından tanınan komutları listele" + +msgid "List regions" +msgstr "Bölgeleri listele" + +msgid "List resource usage per project" +msgstr "Her proje için kaynak kullanımını listele" + +msgid "List role assignments" +msgstr "Rol atamalarını listele" + +msgid "List roles" +msgstr "Rolleri listele" + +msgid "List routers" +msgstr "Yönlendiricilerin listesi" + +msgid "List routers according to their name" +msgstr "Yönlendiricileri isimlerine göre listele" + +msgid "List routers according to their project (name or ID)" +msgstr "Yönlendiricileri projelerine göre listele (isim veya ID)" + +msgid "List routers hosted by an agent (ID only)" +msgstr "Bir ajan tarafından sunulan yönlendiricileri listele (sadece ID)" + +msgid "List rules applied to incoming network traffic" +msgstr "Gelen ağ trafiğine uygulanan kurallar listesi" + +msgid "List rules applied to outgoing network traffic" +msgstr "Giden ağ trafiğine uygulanan kuralları listele" + +msgid "" +"List rules by the IP protocol (ah, dhcp, egp, esp, gre, icmp, igmp, ipv6-" +"encap, ipv6-frag, ipv6-icmp, ipv6-nonxt, ipv6-opts, ipv6-route, ospf, pgm, " +"rsvp, sctp, tcp, udp, udplite, vrrp and integer representations [0-255])." +msgstr "" +"Kuralları IP protokolüne göre listele (ah, dhcp, egp, esp, gre, icmp, igmp, " +"ipv6-encap, ipv6-frag, ipv6-icmp, ipv6-nonxt, ipv6-opts, ipv6-route, ospf, " +"pgm, rsvp, sctp, tcp, udp, udplite, vrrp ve tam sayı gösterimleri [0-255])" + +msgid "List security group rules" +msgstr "Güvenlik grubu kurallarını listele" + +msgid "List security groups" +msgstr "Güvenlik gruplarını listele" + +msgid "List security groups according to the project (name or ID)" +msgstr "Projeye göre güvenlik grubunu listele (isim veya ID)" + +msgid "List servers" +msgstr "Sunucuları listele" + +msgid "List service command" +msgstr "Servis komutunu listele" + +msgid "List service providers" +msgstr "Servis sağlayıcıları listele" + +msgid "List services" +msgstr "Servisleri listele" + +msgid "List services in the service catalog" +msgstr "Servis katalogundaki servisleri listele" + +msgid "List services on specified host (name only)" +msgstr "Belirtilen sunucu üzerindeki servisleri listele (sadece isim)" + +msgid "List snapshots" +msgstr "Anlık görüntüleri listele" + +msgid "List subnet pools" +msgstr "Altağ havuzlarını listele" + +msgid "List subnet pools according to their project (name or ID)" +msgstr "Alt ağ havuzlarını projelerine göre listele (isim veya ID)" + +msgid "List subnet pools not shared between projects" +msgstr "Projeler arasında paylaşılmayan altağ havularını listele" + +msgid "List subnet pools not used as the default external subnet pool" +msgstr "" +"Varsayılan harici altağ havuzu olarak kullanılmayan altağ havuzlarını listele" + +msgid "List subnet pools shared between projects" +msgstr "Projeler arasında paylaşılan altağ havuzlarını listele" + +msgid "List subnet pools used as the default external subnet pool" +msgstr "" +"Varsayılan harici altağ havuzu olarak kullanılan altağ havuzlarını listele" + +msgid "List subnets" +msgstr "Alt ağları listele" + +msgid "List subnets which have DHCP disabled" +msgstr "DHCP'nin devre dışı olduğu altağları listele" + +msgid "List subnets which have DHCP enabled" +msgstr "DHCP'nin etkin olduğu altağları listele" + +msgid "List the default volume type" +msgstr "Varsayılan disk bölümü türünü listele" + +msgid "List trusts" +msgstr "Güvenleri listele" + +msgid "List user-role assignments" +msgstr "Kullanıcı rol atamalarını listele" + +msgid "List users" +msgstr "Kullanıcıları listele" + +msgid "List volume availability zones" +msgstr "Disk bölümü kullanılabilirlik alanlarını listele" + +msgid "List volume backups" +msgstr "Disk bölümü yedeklerini listele" + +msgid "List volume quota" +msgstr "Disk bölümü kotasını listele" + +msgid "List volume snapshots" +msgstr "Disk bölümü anlık görüntülerini listele" + +msgid "List volume types" +msgstr "Disk bölümü türlerini listele" + +msgid "List volumes" +msgstr "Disk bölümlerini listele" + +msgid "" +"Listing assignments using role list is deprecated as of the Newton release. " +"Use role assignment list --user --project --names " +"instead." +msgstr "" +"Rol listesi kullanarak liste atamaları, Newton sürümünden itibaren " +"kullanımdan kaldırılmıştır. Onun yerine rol atama listesi --user " +"--project --names komutunu kullanın." + +msgid "" +"Listing assignments using role list is deprecated. Use role assignment list " +"--group --domain --names instead." +msgstr "" +"Rol listesini kullanarak liste atamaları kullanımdan kaldırıldı. Onun yerine " +"role assignment list --group --domain --names " +"kullanın." + +msgid "" +"Listing assignments using role list is deprecated. Use role assignment list " +"--group --project --names instead." +msgstr "" +"Rol listesini kullanarak liste atamaları kullanımdan kaldırıldı. Onun yerine " +"role assignment list --group --project --names " +"kullanın." + +msgid "" +"Listing assignments using role list is deprecated. Use role assignment list " +"--user --domain --names instead." +msgstr "" +"Rol listesini kullanarak liste atamaları kullanımdan kaldırıldı. Bunun " +"yerine role assignment list --user --domain --" +"names kullanın." + +msgid "" +"Listing assignments using role list is deprecated. Use role assignment list " +"--user --domain default --names instead." +msgstr "" +"Rol listesini kullanarak liste atamaları kullanımdan kaldırıldı. Onun yerine " +"role assignment list --user --domain default --names kullanın." + +msgid "" +"Listing assignments using role list is deprecated. Use role assignment list " +"--user --project --names instead." +msgstr "" +"Rol listesini kullanarak liste atamaları kullanımdan kaldırıldı. Bunun " +"yerine, role assignment list --user --project --" +"names kullanın." + +msgid "" +"Listing assignments using user role list is deprecated as of the Newton " +"release. Use role assignment list --user --project --names instead." +msgstr "" +"Atamaları kullanıcı rol listesi kullanarak listelemek Newton sürümünden " +"itibaren kullanımdan kaldırılmıştır. Onun yerine role assignment list --user " +" --project --names komutunu kullanın." + +msgid "Lists all volume transfer requests." +msgstr "Tüm disk bölümü aktarım isteklerini listele." + +msgid "Local filename(s) to upload" +msgstr "Yüklenecek yerel dosya ad(lar)ı" + +msgid "Lock server(s). A non-admin user will not be able to execute actions" +msgstr "" +"Sunucu(ları/yu) kilitle. Yönetici olmayan kullanıcılar işlem yapamayacaktır." + +msgid "Login name (ssh -l option)" +msgstr "Giriş adı (ssh -l seçeneği)" + +msgid "MAC address of this port (admin only)" +msgstr "Bu bağlantı noktasının MAC adresi (yalnızca yönetici)" + +msgid "MD5 hash" +msgstr "MD5 özeti" + +msgid "MD5 hash of the agent" +msgstr "Ajanın MD5 özeti" + +msgid "Make the QoS policy accessible by other projects" +msgstr "QoS ilkesini diğer projeler tarafından erişilebilir hale getirin" + +msgid "Make the QoS policy not accessible by other projects" +msgstr "QoS ilkesine diğer projeler tarafından erişilemez yap" + +msgid "Make the QoS policy not accessible by other projects (default)" +msgstr "" +"QoS ilkesine diğer projeler tarafından erişilebilir olmamasını sağlayın " +"(varsayılan)" + +msgid "Make the network VLAN transparent" +msgstr "Ağ VLAN'ını transparan yap" + +msgid "Mapping that is to be used (name or ID)" +msgstr "Kullanılacak eşleşme (isim veya ID)" + +msgid "Mapping that is to be used (name or ID) (required)" +msgstr "Kullanılacak eşleştirme (isim veya ID) (gerekli)" + +msgid "Mapping to display" +msgstr "Gösterilecek eşleşme" + +msgid "Mapping to modify" +msgstr "Düzenlenecek eşleşme" + +msgid "Mapping(s) to delete" +msgstr "Silinecek eşletirme(ler)" + +msgid "Mark volume as bootable" +msgstr "Disk bölümünü ön yüklenebilir olarak işaretle" + +msgid "Mark volume as non-bootable" +msgstr "Disk bölümünü ön yüklenebilir olarak işaretle" + +msgid "Mark volume as non-bootable (default)" +msgstr "Disk bölümünü ön yüklemesiz olarak işaretle (varsayılan)" + +msgid "Maximum bandwidth in kbps" +msgstr "En büyük bant genişliği kbps cinsinden" + +msgid "Maximum burst in kilobits, 0 means automatic" +msgstr "Kilo bit cinsinden en büyük atış, 0 otomatik anlamına gelir" + +msgid "Maximum number of backups to display" +msgstr "En fazla gösterilecek yedek sayısı " + +msgid "Maximum number of flavors to display" +msgstr "Gösterilecek en fazla flavor sayısı" + +msgid "Maximum number of images to display." +msgstr "Gösterilecek en fazla imaj sayısı." + +msgid "" +"Maximum number of servers to display. If limit equals -1, all servers will " +"be displayed. If limit is greater than 'osapi_max_limit' option of Nova API, " +"'osapi_max_limit' will be used instead." +msgstr "" +"Gösterilecek en fazla sunucu sayısı. Eğer sınır -1'e eşitse, tüm sunucular " +"gösterilir. Eğer limit Nova API'nın 'osapi_max_limit' seçeneğinden daha " +"büyükse, onun yerine 'osapi_max_limit' kullanılacaktır." + +msgid "Maximum number of servers to launch (default=1)" +msgstr "Başlatılması gereken en fazla sunucu sayısı (varsayılan=1)" + +msgid "Maximum number of snapshots to display" +msgstr "Gösterilecek en büyük anlık görüntü sayısı" + +msgid "Maximum number of volumes to display" +msgstr "Gösterilecek en çok disk bölümü sayısı" + +msgid "Memory size in MB (default 256M)" +msgstr "MB cinsinden bellek boyutu (varsayılan 256M)" + +msgid "" +"Metainfo for the flavor profile. This becomes required if --driver is " +"missing and vice versa" +msgstr "" +"Flavor profili için Metainfo. --driver eksikse bu gereklidir ya da tam tersi" + +msgid "Meter rule (ID only)" +msgstr "Ölçek kuralı (sadece ID)" + +msgid "Meter rule to delete (ID only)" +msgstr "Silinecek ölçüm kuralı (sadece ID)" + +msgid "Meter to delete (name or ID)" +msgstr "Silinecek ölçek (isim veya ID)" + +msgid "Meter to display (name or ID)" +msgstr "Gösterilecek ölçek (isim veya ID)" + +msgid "Migrate server to different host" +msgstr "Farklı konakçıya sunucuyu göç ettir" + +msgid "Migrate volume to a new host" +msgstr "Disk bölümünü yeni bir sunucuya göç ettir" + +msgid "" +"Migration policy while re-typing volume (\"never\" or \"on-demand\", default " +"is \"never\" ) (available only when --type option is specified)" +msgstr "" +"Disk bölümü yeniden yazarken geçiş politikası (\"hiçbir zaman\" veya \"talep " +"üzerine\", varsayılan \"hiçbir zaman\") (yalnızca --type seçeneği " +"belirtildiğinde kullanılabilir)" + +msgid "Minimum RAM size needed to boot image, in megabytes" +msgstr "" +"İmajı ön yüklemek için kullanılacak en küçük RAM boyutu, megabayt cinsinden" + +msgid "Minimum disk size needed to boot image, in gigabytes" +msgstr "" +"İmajı ön yüklemek için gerekli en küçük disk boyutu, gigabayt cinsinden" + +msgid "Minimum guaranteed bandwidth in kbps" +msgstr "En az garanti edilen kbps cinsinden bant genişliği" + +msgid "Minimum number of servers to launch (default=1)" +msgstr "Başlatılması gereken minimum sunucu sayısı (varsayılan=1)" + +msgid "Modify from " +msgstr "dan düzenle" + +#, python-format +msgid "Multiple %(resource)s exist with %(attr)s='%(value)s'" +msgstr "Çoklu %(resource)s %(attr)s='%(value)s' ile mevcut" + +#, python-format +msgid "Multiple service matches found for '%s', use an ID to be more specific." +msgstr "" +"'%s' için birden fazla servis eşleşmesi bulundu, daha belirgin olması için " +"ID kullanın." + +msgid "Name for the flavor" +msgstr "Flavor için isim" + +msgid "Name of QoS policy to create" +msgstr "Oluşturulacak QoS politikasının ismi" + +msgid "" +"Name of a file that contains many remote IDs to associate with the identity " +"provider, one per line" +msgstr "" +"Her satır başına bir tane olmak üzere, kimlik sağlayıcısı ile " +"ilişkilendirilecek çok sayıda uzak kimliği içeren bir dosyanın adı" + +msgid "Name of host" +msgstr "Sunucu ismi" + +msgid "Name of key(s) to delete (name only)" +msgstr "Silinecek anahtar(lar)ın ismi (sadece isim)" + +msgid "Name of meter" +msgstr "Ölçeğin adı" + +msgid "Name of new consistency group (default to None)" +msgstr "Yeni tutarlılık grubunun adı (varsayılan olarak None)" + +msgid "Name of new consistency group snapshot (default to None)" +msgstr "" +"Yeni tutarlılık grubunun anlık görüntüsünün adı (varsayılan olarak None)" + +msgid "Name of new disk image (default: server name)" +msgstr "Yeni disk imajlarının ismi (varsayılan: sunucu adı)" + +msgid "Name of service (Binary name)" +msgstr "Servis ismi (İkili isim)" + +msgid "Name of the backup" +msgstr "Yedek ismi" + +msgid "Name of the backup image (default: server name)" +msgstr "Yedek imajın ismi (varsayılan: sunucu adı)" + +msgid "Name of the new snapshot" +msgstr "Yeni anlık görüntünün ismi" + +msgid "Name of the new subnet pool" +msgstr "Yeni altağ havuzunun ismi" + +msgid "" +"Name of the physical network over which the virtual network is implemented" +msgstr "Sanal ağın üzerinde uygulandığı fiziksel ağın adı" + +msgid "Name of the snapshot" +msgstr "Anlık görüntü ismi" + +msgid "Name of this port" +msgstr "Bu bağlantı noktasının ismi" + +msgid "Name of volume host" +msgstr "Disk bölümü anabilgisayarının ismi" + +msgid "Name or ID of project to show usage for" +msgstr "Kullanımı gösterilecek projenin isim veya ID'si" + +msgid "Name or ID of security group to remove from server" +msgstr "Sunucudan kaldırılacak güvenlik grubunun isim veya ID'si" + +msgid "Name or ID of server to use" +msgstr "Kullanılacak sunucunun isim veya ID'si" + +#, python-format +msgid "Network API version, default=%s (Env: OS_NETWORK_API_VERSION)" +msgstr "Ağ API sürümü, varsayılan=%s (Env: OS_NETWORK_API_VERSION)" + +msgid "Network QoS rule to delete (ID)" +msgstr "Silinecek Ağ QoS kuralı (ID)" + +msgid "Network agent to display (ID only)" +msgstr "Görüntülenecek ağ ajanı (yalnızca kimlik kartı)" + +msgid "Network agent to modify (ID only)" +msgstr "Değiştirilecek ağ ajanı (yalnızca ID)" + +msgid "Network agent(s) to delete (ID only)" +msgstr "Silinecek ağ ajanları (sadece ID)" + +msgid "Network flavor (name or ID)" +msgstr "Ağ flavor'ı (isim veya ID)" + +msgid "Network segment description" +msgstr "Ağ dilimi açıklaması" + +msgid "Network segment to associate with this subnet (name or ID)" +msgstr "Bu ağ ile ilişkili ağ dilimi (isim veya ID)" + +msgid "Network segment to display (name or ID)" +msgstr "Gösterilecek ağ dilimi (isim veya ID)" + +msgid "Network segment to modify (name or ID)" +msgstr "Değiştirilecek ağ segmenti (ad veya kimlik)" + +msgid "Network segment(s) to delete (name or ID)" +msgstr "Silinecek ağ segmentleri (ad veya kimlik)" + +msgid "Network this network segment belongs to (name or ID)" +msgstr "Bu ağ bölümünün ait olduğu ağ (isim veya ID)" + +msgid "Network this port belongs to (name or ID)" +msgstr "Bu portun ait olduğu ağ (isim veya kimlik numarası)" + +msgid "Network this subnet belongs to (name or ID)" +msgstr "Bu alt ağın ait olduğu ağ (isim veya ID)" + +msgid "Network to allocate floating IP from (name or ID)" +msgstr "Yüzen IP'nin ayrılacağı ağ (isim veya ID)" + +msgid "Network to allocate the fixed IP address from (name or ID)" +msgstr "Sabit IP adresi ayrılacak ağ (isim veya ID)" + +msgid "Network to be added to an agent (name or ID)" +msgstr "Bir ajana eklenecek ağ (isim veya ID)" + +msgid "Network to be removed from an agent (name or ID)" +msgstr "Bir ajandan kaldırılacak ağ (isim veya ID)" + +msgid "Network to display (name or ID)" +msgstr "Gösterilecek ağ (isim veya ID)" + +msgid "Network to fetch an IP address from (name or ID)" +msgstr "IP adresi çekilecek ağ (isim veya ID)" + +msgid "Network to modify (name or ID)" +msgstr "Düzenlenecek ağ (isim veya ID)" + +msgid "" +"Network type of this network segment (flat, geneve, gre, local, vlan or " +"vxlan)" +msgstr "Bu ağ diliminin ağ türü (flat, geneve, gre, local, vlan veya vxlan)" + +msgid "Network(s) to delete (name or ID)" +msgstr "Silinecek ağ(lar) (isim veya ID)" + +msgid "New Authentication URL of remote federated service provider" +msgstr "Uzak federe servis sağlayıcının yeni Kimlik Doğrulama URL'si" + +msgid "New MIME type of the policy rules file" +msgstr "Politika kuralları dosyasının yeni MIME türü" + +msgid "New MIME type of the policy rules file (defaults to application/json)" +msgstr "" +"Politika kuralları dosyasının yeni MIME türü (varsayılan application/json)" + +msgid "New QoS specification name" +msgstr "Yeni QoS özellik ismi" + +msgid "New address scope name" +msgstr "Yeni adres kapsam ismi" + +msgid "New aggregate name" +msgstr "Yeni küme ismi" + +msgid "New backup description" +msgstr "Yeni yedek açıklaması" + +msgid "New backup name" +msgstr "Yeni yedek ismi" + +msgid "" +"New backup state (\"available\" or \"error\") (admin only) (This option " +"simply changes the state of the backup in the database with no regard to " +"actual status, exercise caution when using)" +msgstr "" +"Yeni yedek durumu (\"kullanılabilir\" veya \"hata\") (sadece yönetici) (Bu " +"seçenek, veritabanındaki yedeklemenin durumunu gerçek durumu dikkate almadan " +"değiştirir, kullanırken dikkatli olun.)" + +msgid "New consistency group description" +msgstr "Yeni tutarlılık grubu açıklaması" + +msgid "New consistency group name" +msgstr "Yeni tutarlılık grubu ismi" + +msgid "New consumer description" +msgstr "Yeni tüketici tanımı" + +msgid "New container name(s)" +msgstr "Yeni kap ismi" + +msgid "New credential data" +msgstr "Yeni kimlik bilgileri verisi" + +msgid "New credential type: cert, ec2" +msgstr "Yeni kimlik bilgisi türü: cert, ec2" + +msgid "New domain description" +msgstr "Yeni alan tanımı" + +msgid "New domain name" +msgstr "Yeni alan adı" + +msgid "New endpoint URL" +msgstr "Yeni uç nokta URL'i" + +msgid "New endpoint admin URL" +msgstr "Yeni uç nokta yönetici URL'i" + +msgid "New endpoint interface type (admin, public or internal)" +msgstr "Yeni uç nokta arayüz türü (yönetici, genele açık veya dahili)" + +msgid "New endpoint internal URL" +msgstr "Yeni uç nokta dahili URL'i" + +msgid "New endpoint public URL (required)" +msgstr "Yeni uç nokta herkese açık URL (gerekli)" + +msgid "New endpoint region ID" +msgstr "Yeni uç nokta bölge ID'si" + +msgid "New endpoint service (name or ID)" +msgstr "Yeni uç nokta servisi (isim veya ID)" + +msgid "New federation protocol name (must be unique per identity provider)" +msgstr "" +"Yeni federasyon protokolü ismi (her kimlik sağlayıcı için benzersiz " +"olmalıdır)" + +msgid "New flavor name" +msgstr "Yeni flavor adı" + +msgid "New group description" +msgstr "Yeni grup açıklaması" + +msgid "New group name" +msgstr "Yeni grup ismi" + +msgid "New identity provider description" +msgstr "Yeni kimlik sağlayıcı açıklaması" + +msgid "New identity provider name (must be unique)" +msgstr "Yeni kimlik sağlayıcı ismi (benzersiz olmalıdır)" + +msgid "New image name" +msgstr "Yeni imaj ismi" + +msgid "New mapping name (must be unique)" +msgstr "Yeni eşleştirme ismi (benzersiz olmalı)" + +msgid "New network name" +msgstr "Yeni ağ adı" + +msgid "New network segment name" +msgstr "Yeni ağ dilim adı" + +msgid "New parent region ID" +msgstr "Yeni üst bölge ID'si" + +msgid "New password: " +msgstr "Yeni parola: " + +msgid "New project name" +msgstr "Yeni proje adı" + +msgid "New public or private key name" +msgstr "Yeni açık ve kapalı anahtar ismi" + +msgid "New region ID" +msgstr "Yeni bölge ID'si" + +msgid "New region description" +msgstr "Yeni bölge tanımı" + +msgid "New role name" +msgstr "Yeni rol adı" + +msgid "New router name" +msgstr "Yeni yönlendirici ismi " + +msgid "New security group description" +msgstr "Yeni güvenlik grubu açıklaması" + +msgid "New security group name" +msgstr "Yeni güvenlik grubu ismi" + +msgid "New serialized policy rules file" +msgstr "Yeni dizileştirilmiş politika kuralları dosyası" + +msgid "New server group name" +msgstr "Yeni sunucu grup ismi" + +msgid "New server name" +msgstr "Yeni sunucu ismi" + +msgid "New server state (valid value: active, error)" +msgstr "Yeni sunucu durumu (geçerli değer: aktif, hata)" + +msgid "New service description" +msgstr "Yeni servis tanımı" + +msgid "New service name" +msgstr "Yeni servis ismi" + +msgid "New service provider URL, where SAML assertions are sent" +msgstr "SAML bildirimlerinin gönderildiği yeni hizmet sağlayıcı URL'si" + +msgid "New service provider description" +msgstr "Yeni servis sağlayıcı tanımı" + +msgid "New service provider name (must be unique)" +msgstr "Yeni servis sağlayıcı ismi (benzersiz olmak zorunda)" + +msgid "New service type (compute, image, identity, volume, etc)" +msgstr "Yeni servis türü (hesaplama, imaj, kimlik, disk bölümü, vs.)" + +#, python-format +msgid "New size must be greater than %s GB" +msgstr "Yeni boyut %s GB'den büyük olmak zorunda" + +msgid "New snapshot description" +msgstr "Yeni anlık görüntü tanımı" + +msgid "New snapshot name" +msgstr "Yeni anlık görüntü ismi" + +msgid "" +"New snapshot state. (\"available\", \"error\", \"creating\", \"deleting\", " +"or \"error_deleting\") (admin only) (This option simply changes the state of " +"the snapshot in the database with no regard to actual status, exercise " +"caution when using)" +msgstr "" +"Yeni anlık görüntü durumu. (\"kullanılabilir\", \"hata\", \"oluşturuluyor\", " +"\"siliniyor\", veya \"silerken hata\") (sadece yönetici) (Bu seçenek, gerçek " +"duruma bakılmaksızın veritabanındaki anlık görüntüsünün durumunu değiştirir, " +"kullanırken dikkatli olun)" + +msgid "New subnet name" +msgstr "Yeni altağ ismi" + +msgid "New transfer request name (default to None)" +msgstr "Yeni aktarım isteği ismi (varsayılan olarak None)" + +msgid "New user name" +msgstr "Yeni kullanıcı ismi" + +msgid "New user password" +msgstr "Yeni kullanıcı parolası" + +#, python-format +msgid "New value for the %s quota" +msgstr "%s kotası için yeni değer" + +msgid "New volume description" +msgstr "Yeni disk bölümü açıklaması" + +msgid "New volume name" +msgstr "Yeni disk bölümü ismi" + +msgid "" +"New volume state (\"available\", \"error\", \"creating\", \"deleting\", \"in-" +"use\", \"attaching\", \"detaching\", \"error_deleting\" or \"maintenance\") " +"(admin only) (This option simply changes the state of the volume in the " +"database with no regard to actual status, exercise caution when using)" +msgstr "" +"Yeni disk bölümü durumu (\"kullanılabilir\", \"hata\", \"oluşturuluyor\", " +"\"siliniyor\", \"kullanımda\", \"ekleniyor\", \"çıkarılıyor\", \"silinirken " +"hata\" veya \"bakım\") (sadece yönetici) (Bu seçenek, veritabanındaki " +"birimin durumunu gerçek durumu dikkate almadan değiştirir, kullanırken " +"dikkatli olun.)" + +msgid "New volume type (name or ID)" +msgstr "Yeni disk bölümü türü (isim veya ID)" + +#, python-format +msgid "No %(resource)s with a %(attr)s or ID of '%(value)s' found" +msgstr "%(attr)s veya '%(value)s' ID'li bir %(resource)s bulunamadı" + +#, python-format +msgid "No agent with a ID of '%(id)s' exists." +msgstr "'%(id)s'nin bir ID'si ile bir ajan yok." + +msgid "" +"No existing encryption type found, creating new encryption type for this " +"volume type ..." +msgstr "" +"Mevcut şifreleme türü bulunamadı, bu disk bölümü türü için yeni şifreleme " +"türü oluşturuluyor ..." + +msgid "" +"No password was supplied, authentication will fail when a user does not have " +"a password." +msgstr "" +"Parola girilmedi, bir kullanıcının parolası olmadığında kimlik doğrulama " +"başarısız olur." + +#, python-format +msgid "No service catalog with a type, name or ID of '%s' exists." +msgstr "'%s' türünde, isminde veya ID'si ile bir servis kataloğu yok." + +#, python-format +msgid "No service with a type, name or ID of '%s' exists." +msgstr "'%s' türünde, isminde veya ID'si ile bir servis bulunamadı." + +#, python-format +msgid "No tags associated with the %s" +msgstr "%s ile ilişkilendirilmiş hiç etiket yok" + +msgid "Number of backups to keep (default: 1)" +msgstr "Tutulacak yedekleme sayısı (varsayılan: 1)" + +msgid "Number of lines to display from the end of the log (default=all)" +msgstr "Günlüğün sonundan gösterilecek satır sayısı (varsayılan=tümü)" + +msgid "Number of vcpus (default 1)" +msgstr "vcpus sayısı (varsayılan 1)" + +#, python-format +msgid "Object name is %s characters long, default limit is 1024" +msgstr "Nesne ismi %s karakter uzunluğunda, varsayılan sınır 1024" + +msgid "Object to display" +msgstr "Gösterilecek nesne" + +msgid "Object to modify" +msgstr "Düzenlenecek nesne" + +msgid "Object to save" +msgstr "Kaydedilecek nesne" + +msgid "Object(s) to delete" +msgstr "Silinecek nesne(ler)" + +msgid "One or more of the set operations failed" +msgstr "Bir veya birden fazla ayar işlemleri başarısız" + +msgid "One or more of the unset operations failed" +msgstr "Bir veya birden fazla ayar kaldırma işlemi başarısız" + +msgid "Only an authorized user may issue a new token." +msgstr "Yalnızca yetkili bir kullanıcı yeni bir jeton verebilir." + +msgid "Only display deleted servers (Admin only)." +msgstr "Sadece silinen sunucuları göster (sadece admin)." + +msgid "Only return hosts in the availability zone" +msgstr "Sadece kullanılabilirlik bölgesindeki sunucuları döndürür" + +msgid "Only return instances that match the reservation" +msgstr "Sadece rezervasyonla eşleşen sunucuları döndürür" + +msgid "Operating system architecture" +msgstr "İşletim sistemi mimarisi" + +msgid "Operating system distribution name" +msgstr "İşletim sistemi dağıtım adı" + +msgid "Operating system distribution version" +msgstr "Işletim sistemi dağıtım sürümü" + +msgid "Optional backup container name" +msgstr "Seçimli yedek kap ismi" + +msgid "Options in ssh_config(5) format (ssh -o option)" +msgstr "ssh_config(5) biçemi içerisindeki seçenekler (ssh -o seçeneği)" + +msgid "Original user password" +msgstr "Orijinal kullanıcı parolası" + +msgid "Owner's project (name or ID)" +msgstr "Sahibin projesi (isim veya ID)" + +msgid "Parent of the project (name or ID)" +msgstr "Projenin üst projesi (isim veya ID)" + +msgid "Parent region ID" +msgstr "Üst bölge ID'si" + +msgid "Passwords do not match, password unchanged" +msgstr "Parolalar eşleşmedi, parola değiştirilmedi" + +msgid "Pause server(s)" +msgstr "Sunucu(ları/yu) durdur" + +msgid "Perform a block live migration" +msgstr "Bir blok gerçek göç gerçekleştir" + +msgid "Perform a hard or soft server reboot" +msgstr "Sert veya yumuşak sunucu yeniden başlatmayı gerçekleştir" + +msgid "Perform a hard reboot" +msgstr "Sert yeniden başlatmayı gerçekleştir" + +msgid "Perform a shared live migration (default)" +msgstr "Paylaşımlı canlı göç gerçekleştir (varsayılan)" + +msgid "Perform a soft reboot" +msgstr "Yumuşak yeniden başlatmayı gerçekleştir" + +msgid "Perform an incremental backup" +msgstr "Artımlı bir yedekleme gerçekleştir" + +msgid "Physical network name of this network segment" +msgstr "Bu ağ kesiminin fiziksel ağ adı" + +msgid "Policy to display" +msgstr "Gösterilecek politika" + +msgid "Policy to modify" +msgstr "Düzenlenecek politika" + +msgid "Policy(s) to delete" +msgstr "Silinecek politika(lar)" + +msgid "Port device ID" +msgstr "Bağlantı noktası aygıtı ID'si" + +#, python-format +msgid "Port does not contain allowed-address-pair %s" +msgstr "%s izin verilen adres çiftlerini içermeyen bağlantı noktası" + +#, python-format +msgid "Port does not contain binding-profile %s" +msgstr "Bağlantı noktası %s bağlantı-profilini içermiyor" + +#, python-format +msgid "Port does not contain fixed-ip %s" +msgstr "Bağlantı noktası %s sabit ip'sini içermiyor" + +#, python-format +msgid "Port does not contain security group %s" +msgstr "Bağlantı noktası %s güvenlik grubunu içermiyor" + +msgid "Port to add to the server (name or ID)" +msgstr "Sunucuya eklenecek bağlantı noktası (isim veya ID)" + +msgid "Port to be added (name or ID)" +msgstr "Eklenecek bağlantı noktası (isim veya ID)" + +msgid "Port to be associated with the floating IP (name or ID)" +msgstr "Yüzen IP ile ilişkilendirilecek bağlantı noktası (isim veya ID)" + +msgid "Port to be removed and deleted (name or ID)" +msgstr "Kaldırılacak ve silinecek bağlantı noktası (isim veya ID)" + +msgid "Port to display (name or ID)" +msgstr "Gösterilecek bağlantı noktası (isim veya ID)" + +msgid "Port to modify (name or ID)" +msgstr "Düzenlenecek bağlantı noktası (isim veya ID)" + +msgid "Port to remove from the server (name or ID)" +msgstr "Sunucudan kaldırılacak bağlantı noktası (isim veya ID)" + +msgid "Port(s) to delete (name or ID)" +msgstr "Silinecek bağlantı noktaları (isim veya ID)" + +msgid "Prefix length for subnet allocation from subnet pool" +msgstr "Altağ havuzundan altağ tahsisi için ön ek uzunluğu" + +msgid "Prevent image from being deleted" +msgstr "İmajı silinmekten koru" + +msgid "Print image size in a human-friendly format." +msgstr "İmaj boyutunu insan dostu bir biçimde bastırın." + +msgid "Private key file (ssh -i option)" +msgstr "Gizli anahtar dosyası (ssh -i seçeneği)" + +msgid "Project and User must be specified" +msgstr "Proje ve kullanıcı belirtilmeli" + +msgid "Project being delegated (name or ID) (required)" +msgstr "Devredilen proje (isim veya ID) (gerekli)" + +msgid "Project description" +msgstr "Proje açıklaması" + +msgid "Project must be specified" +msgstr "Proje belirtilmek zorunda" + +msgid "Project that consumer wants to access (name or ID) (required)" +msgstr "Alıcının erişmek istediği proje (isim veya ID) (gerekli)" + +msgid "Project to associate with image (name or ID)" +msgstr "İmaj ile ilişkili proje (isim veya ID)" + +msgid "Project to clean (name or ID)" +msgstr "Temizlenecek proje (isim veya ID)" + +msgid "Project to disassociate with image (name or ID)" +msgstr "İmaj ile ilişkişi kesilecek proje (isim veya ID)" + +msgid "Project to display (name or ID)" +msgstr "Gösterilecek proje (isim veya ID)" + +msgid "Project to filter (name or ID)" +msgstr "Filtrelenecek proje (isim veya ID)" + +msgid "Project to modify (name or ID)" +msgstr "Düzenlenecek projeler (isim veya ID)" + +msgid "Project which limits the scope of the credential (name or ID)" +msgstr "Kimlik bilgilerinin kapsamını sınırlayan proje (isim veya ID)" + +msgid "Project(s) to delete (name or ID)" +msgstr "Silinecek proje(ler) (isim veya ID)" + +msgid "Prompt interactively for password" +msgstr "Parolayı interaktif olarak sor" + +msgid "" +"Property key to remove from server (repeat option to remove multiple values)" +msgstr "" +"Sunucudan kaldırılacak özellik anahtaru (birden fazla değer silmek için " +"seçeneği tekrarla)" + +msgid "" +"Property to add for this flavor (repeat option to set multiple properties)" +msgstr "" +"Bu flavor'a eklenecek özellik (birden fazla özellik ayarlamak için seçeneği " +"tekrarlayın)" + +msgid "" +"Property to add or modify for this QoS specification (repeat option to set " +"multiple properties)" +msgstr "" +"Bu QoS belirtimi için ekleme veya değişiklik yapma özelliği (birden fazla " +"özellik ayarlamak için seçeneği tekrarla)" + +msgid "" +"Property to add or modify for this flavor (repeat option to set multiple " +"properties)" +msgstr "" +"Bu flavor için eklenecek veye düzenlenecek özellik (birden fazla özellik " +"ayarlamak için seçeneği tekrarla)" + +msgid "" +"Property to add to this aggregate (repeat option to set multiple properties)" +msgstr "" +"Bu kümeye eklenecek özellik (çoklu özellik ayarlamak için seçeneği tekrarla)" + +msgid "" +"Property to add/change for this server (repeat option to set multiple " +"properties)" +msgstr "" +"Bu sunucu için eklenecek/değiştirilecek özellik (birden falza özellik " +"ayarlamak için seçeneği tekrarla)" + +msgid "" +"Property to add/change for this snapshot (repeat option to set multiple " +"properties)" +msgstr "" +"Bu anlık görüntü için eklenecek/değiştirilecek özellik (birden fazla özellik " +"ayarlamak için seçeneği tekrarla)" + +msgid "" +"Property to remove from account (repeat option to remove multiple properties)" +msgstr "" +"Hesaptan silinecek özellik (birden fazla özellik silmek için seçeneği " +"tekrarla)" + +msgid "" +"Property to remove from aggregate (repeat option to remove multiple " +"properties)" +msgstr "" +"Kümeden kaldırılacak özellikler (birden fazla özelliği kaldırmak için " +"seçeneği tekrarlayın)" + +msgid "" +"Property to remove from container (repeat option to remove multiple " +"properties)" +msgstr "" +"Kaptan kaldırılacak özellik (birden fazla özellik kaldırmak için seçeneği " +"tekrarla)" + +msgid "" +"Property to remove from flavor (repeat option to unset multiple properties)" +msgstr "" +"Flavor'dan kaldırılacak özellik (birden fazla özelliği kaldırmak için " +"seçeneği tekrarla)" + +msgid "" +"Property to remove from object (repeat option to remove multiple properties)" +msgstr "" +"Nesneden kaldırılacak özellik (birden fazla özellik kaldırmak için seçeneği " +"tekrarla)" + +msgid "" +"Property to remove from snapshot (repeat option to remove multiple " +"properties)" +msgstr "" +"Anlık görüntüden kaldırılacak özellik (birden fazla özellik kaldırmak için " +"seçeneği tekrarlayın)" + +msgid "" +"Property to remove from the QoS specification. (repeat option to unset " +"multiple properties)" +msgstr "" +"QoS belirtiminden kaldırılacak özellik. (birden fazla özelliğin ayarını " +"kaldırmak için seçeneği tekrarla)" + +msgid "" +"Property to set on (repeat option to set multiple properties)" +msgstr "" +" üzerinde ayarlanacak özellik (çoklu özellik ayarlamak için " +"seçeneği tekrarla)" + +msgid "Public or private key to display (name only)" +msgstr "Gösterilecek açık veya kapalı anahtar (sadece isim)" + +msgid "Put server in rescue mode" +msgstr "Sunucuyu kurtarma kipine getir" + +msgid "" +"Python module path to driver. This becomes required if --metainfo is missing " +"and vice versa" +msgstr "" +"Python modülünün sürücüye yolu. --metainfo eksikse bu gereklidir ya da tam " +"tersi" + +msgid "QoS policy that contains the rule (name or ID)" +msgstr "Kuralı içeren QoS politikası (isim veya ID)" + +msgid "QoS policy to attach to this network (name or ID)" +msgstr "Bu ağa eklenecek QoS politikası (isim veya ID)" + +msgid "QoS policy to display (name or ID)" +msgstr "Görüntülenecek QoS politikası (ad veya kimlik)" + +msgid "QoS policy to modify (name or ID)" +msgstr "Değiştirmek için QoS politikası (ad veya ID)" + +msgid "QoS policy(s) to delete (name or ID)" +msgstr "Silinecek QoS politika(lar/s)ı (isim veya ID)" + +#, python-format +msgid "QoS rule type (%s)" +msgstr "QoS kural türü (%s)" + +msgid "QoS specification to display (name or ID)" +msgstr "Gösterilecek QoS belirtimi (isim veya ID)" + +msgid "QoS specification to modify (name or ID)" +msgstr "Düzenlenecek QoS özelliği (isim veya ID)" + +msgid "QoS specification(s) to delete (name or ID)" +msgstr "Silinecek QoS özellikleri (isim veya ID)" + +msgid "RBAC policy (ID only)" +msgstr "RBAC politikası (sadece ID)" + +msgid "RBAC policy to be modified (ID only)" +msgstr "Değiştirilecek RBAC politikası (yalnızca ID)" + +msgid "RBAC policy(s) to delete (ID only)" +msgstr "Silinecek RBAC politikaları (sadece ID)" + +msgid "RX/TX factor (default 1.0)" +msgstr "RX/TX faktörü (varsayılan 1.0)" + +msgid "Read image data from standard input" +msgstr "Standart girdiden imaj verilerini oku" + +msgid "" +"Reason for disabling the service (in quotas). Should be used with --disable " +"option." +msgstr "" +"Servisi devre dışı bırakma sebebi (kota cinsinden). --disable seçeneği ile " +"birlikte kullanılmalı." + +msgid "Reason for disabling the service (should be used with --disable option)" +msgstr "" +"Servisi devredışı bırakmak için sebep (--disable seçeneği ile kullanılmalı)" + +msgid "Rebuild server" +msgstr "Sunucuyu yeniden yapılandır" + +msgid "" +"Recreate server from the specified image (name or ID). Defaults to the " +"currently used one." +msgstr "" +"Sunucuyu belirtilen imajdan yeniden oluşturun (ad veya ID). Varsayılan " +"olarak o anda kullanılana ayarlıdır." + +msgid "Recursively delete objects and container" +msgstr "Nesneleri ve kabı yinelemeli olarak sil" + +msgid "Region ID(s) to delete" +msgstr "Silinecek bölge ID(leri)" + +msgid "Region to display" +msgstr "Gösterilecek bölge" + +msgid "Region to modify" +msgstr "Düzenlenecek bölge" + +msgid "Regular expression to match IP addresses" +msgstr "IP adresleriyle eşleşen düzenli ifadeler" + +msgid "Regular expression to match IPv6 addresses" +msgstr "IPv6 adresleriyle eşleşen düzenli ifadeler" + +msgid "Regular expression to match instance name (admin only)" +msgstr "Sunucu adıyla eşleşen düzenli ifadeler (yalnızca yönetici)" + +msgid "Regular expression to match names" +msgstr "Adları eşleştirmek için düzenli ifadeler" + +msgid "Reject the image membership" +msgstr "İmaj üyeliğini red et" + +msgid "" +"Remote IDs to associate with the Identity Provider (repeat option to provide " +"multiple values)" +msgstr "" +"Kimlik Sağlayıcısı ile ilişkilendirilecek uzak kimlikler (birden fazla değer " +"sağlamak için seçeneği tekrarlayın)" + +msgid "" +"Remote IP address block (may use CIDR notation; default for IPv4 rule: " +"0.0.0.0/0)" +msgstr "" +"Uzak IP adres bloğu (CIDR notasyonu kullanılabilir; IPv4 kuralı için " +"varsayılan: 0.0.0.0/0)" + +msgid "Remote security group (name or ID)" +msgstr "Uzak güvenlik grubu (isim veya ID)" + +msgid "Remove a port from a router" +msgstr "Bir yönlendiriciden bir bağlantı noktası kaldır" + +msgid "" +"Remove a property from this volume type (repeat option to remove multiple " +"properties)" +msgstr "" +"Bu disk bölümü türünden bir özelliği kaldır (birden fazla özellik silmek " +"için seçeneği tekrarla)" + +msgid "" +"Remove a property from volume (repeat option to remove multiple properties)" +msgstr "" +"Disk bölümünden bir özelliği sil (birden fazla özelliği kaldırmak için " +"seçeneği tekrarla)" + +msgid "Remove a subnet from a router" +msgstr "Alt ağı yönlendiriciden kaldır" + +msgid "Remove address scope associated with the subnet pool" +msgstr "Alt ağ havuzuyla ilişkili adres alanını kaldır" + +msgid "" +"Remove all properties from (specify both --property and --no-" +"property to overwrite the current properties)" +msgstr "" +"'den tüm özellikleri kaldır (mevcut üzelliklerin üzerine yazmak " +"için hem --property hem de --no-property'yi belirleyin)" + +msgid "" +"Remove all properties from (specify both --no-property and --" +"property to remove the current properties before setting new properties.)" +msgstr "" +"'dan tüm özellikleri kaldır (Yeni özellikleri ayarlamadan önce " +"geçerli özellikleri kaldırmak için --no-property ve --property seçeneklerini " +"belirtin.)" + +msgid "" +"Remove all properties from (specify both --no-property and --" +"property to remove the current properties before setting new properties.)" +msgstr "" +"'den tüm özellikleri kaldır (yeni özellikleri ayarlamadan önce " +"geçerli özellikleri kaldırmak için --no-property ve --property seçeneklerini " +"belirtin.)" + +msgid "" +"Remove all properties from this flavor (specify both --no-property and --" +"property to remove the current properties before setting new properties.)" +msgstr "" +"Bu flavor'daki tüm özellikleri kaldır (yeni özellikler tanımlamadan önce " +"mevcut özellikleri kaldırmak için --no-property ve --property seçeneklerinin " +"her ikisini de belirtin.)" + +msgid "" +"Remove an image property from volume (repeat option to remove multiple image " +"properties)" +msgstr "" +"Disk bölümünden bir imaj özelliğini sil (birden fazla imaj özelliği silmek " +"için seçeneği tekrarla)" + +msgid "Remove any snapshots along with volume(s) (defaults to False)" +msgstr "" +"Herhangi bir anlık görüntüsünü hacimle birlikte çıkarın (varsayılan olarak " +"False)" + +msgid "Remove external gateway information from the router" +msgstr "Yönlendiriciden harici geçit bilgisini sil" + +msgid "Remove fixed IP address from server" +msgstr "Sunucudan sabit IP adresini kaldırın" + +msgid "Remove flavor access from project (name or ID) (admin only)" +msgstr "Flavor erişimini projeden kaldır (isim veya ID) (sadece yönetici)" + +msgid "Remove floating IP address from server" +msgstr "Sunucudan kayan IP adresi kaldırın" + +msgid "Remove host from aggregate" +msgstr "Kümeden sunucuyu kaldır" + +msgid "Remove network from DHCP agent" +msgstr "Ağı DHCP ajanından sil" + +msgid "Remove network from an agent." +msgstr "Bir ajandan ağı kaldırın." + +msgid "Remove port from server" +msgstr "Sunucudan bağlantı noktasını sil" + +msgid "Remove role from project : user" +msgstr "Rolü proje : kullanıcıdan kaldırın" + +msgid "Remove router from an L3 agent" +msgstr "Yönlendiriciyi L3 ajanından kaldır" + +msgid "Remove router from an agent" +msgstr "Yönlendiriciyi bir ajandan kaldır" + +msgid "Remove security group from server" +msgstr "Sunucudan güvenlik grubunu kaldır" + +msgid "Remove service profile from network flavor" +msgstr "Servis profilini ağ flavor'ından kaldır" + +msgid "" +"Remove subnet pool prefixes (in CIDR notation). (repeat option to unset " +"multiple prefixes)." +msgstr "" +"Alt ağ havuzu öneklerini kaldır (CIDR gösteriminde). (Birden fazla önekin " +"ayarını kaldırmak için seçeneği tekrarlayın)." + +msgid "Remove the QoS policy attached to the port" +msgstr "Bağlantı noktasına eklenmiş QoS politikasını kaldır" + +msgid "Remove the QoS policy attached to this network" +msgstr "Bu ağa bağlı QoS politikasını kaldırın" + +msgid "Remove the encryption type for this volume type (admin oly)" +msgstr "Bu disk bölümü için şifreleme türünü sil (sadece yönetici)" + +msgid "Remove the encryption type for this volume type (admin only)" +msgstr "Bu disk bölümü türü için şifreleme türü kaldırıldı (sadece yönetici)" + +msgid "Remove user from group" +msgstr "Kullanıcıyı gruptan kaldır" + +msgid "Remove volume from server" +msgstr "Disk bölümünü sunucudan kaldır" + +msgid "Remove volume(s) from consistency group" +msgstr "Tutarlılık grubundan disk bölümlerini sil" + +msgid "Removes a role assignment from domain/project : user/group" +msgstr "Bir rol atamasını alan/proje : kullanıcı/gruptan kaldırır." + +msgid "Removes volume type access to project (name or ID) (admin only)" +msgstr "Projeye disk türü erişimini kaldırır (isim veya ID) (sadece yönetici)" + +msgid "Replicated volume to clone (name or ID)" +msgstr "Klonlanacak yinelenmiş disk bölümü (isim veya ID)" + +msgid "Request ID of the event to show (ID only)" +msgstr "Gösterilecek olayın ID'sini iste (sadece ID)" + +msgid "Request token to authorize (ID only) (required)" +msgstr "Yetkilendirilecek istek jetonu (sadece ID) (gerekli)" + +msgid "Request token to exchange for access token (required)" +msgstr "Erişim anahtarı alışverişi için istek jetonu (gerekli)" + +msgid "Requested fixed IP address" +msgstr "Sabit IP adresi iste" + +msgid "Reset the image membership to 'pending'" +msgstr "İmaj üyeliğini 'beklemede' olarak sıfırlayın" + +msgid "Resize server to specified flavor" +msgstr "Sunucuyu belirtilen flavor'a yeniden boyutlandır" + +msgid "Restore backup" +msgstr "Yedeği yeniden yükle" + +msgid "Restore server from rescue mode" +msgstr "Kurtarma kipinden sunucuyu onar" + +msgid "Restore server state before resize" +msgstr "Sunucu durumunu yeniden boyutlandırmadan önceki haline geri getir" + +msgid "Restore server(s)" +msgstr "Sunucu(ları/yu) onar " + +msgid "Restore volume backup" +msgstr "Disk bölümü yedeğini geri yükle" + +msgid "Resume server(s)" +msgstr "Sunucu(ları/yu) kaldığı yerden devam ettir" + +msgid "Return existing domain" +msgstr "Mevcut alanı döndür" + +msgid "Return existing group" +msgstr "Mevcut grubu döndür" + +msgid "Return existing project" +msgstr "Mevcut projeyi döndür" + +msgid "Return existing role" +msgstr "Mevcut rolü döndür" + +msgid "Return existing user" +msgstr "Mevcut kullanıcıyı döndür" + +msgid "" +"Return the auto allocated topology for a given project. Default is current " +"project" +msgstr "" +"Belirli bir proje için otomatik olarak ayrılan topolojiyi döndürür. " +"Varsayılan geçerli projedir" + +#, python-format +msgid "Returning existing domain %s" +msgstr "Mevcut alanı döndür %s" + +#, python-format +msgid "Returning existing group %s" +msgstr "Mevcut %s grubunu döndürme" + +#, python-format +msgid "Returning existing project %s" +msgstr "Mevcut %s projesi döndürülüyor" + +#, python-format +msgid "Returning existing role %s" +msgstr "Mevcut %s rol döndürülüyor" + +#, python-format +msgid "Returning existing user %s" +msgstr "Mevcut kullanıcıyı döndür %s" + +msgid "Returns only effective role assignments" +msgstr "Sadece verimli rol atamalarını döndür" + +msgid "Retype new password: " +msgstr "Yeni parolayı yeniden yaz: " + +msgid "Revoke existing token" +msgstr "Yeni jeton yayınla" + +msgid "" +"Role not added, incorrect set of arguments provided. See openstack --help " +"for more details" +msgstr "" +"Rol eklenmedi, hatalı argümanlar sağlandı. Daha fazla bilgi için openstack --" +"help komutunun çıktısına bakın." + +msgid "Role to add to : (name or ID)" +msgstr ":'a eklenecek rol (isim veya ID)" + +msgid "Role to add to (name or ID)" +msgstr " eklenecek rol (isim veya ID)" + +msgid "Role to display (name or ID)" +msgstr "Gösterilecek roller (isim veya ID)" + +msgid "Role to filter (name or ID)" +msgstr "Filtrelenecek rol (isim veya ID)" + +msgid "Role to modify (name or ID)" +msgstr "Düzenlenecek rol (isim veya ID)" + +msgid "Role to remove (name or ID)" +msgstr "Kaldırılacak rol (isim veya ID)" + +msgid "Role(s) to delete (name or ID)" +msgstr "Silinecek rol(ler) (isim veya ID)" + +msgid "" +"Roles to authorize (name or ID) (repeat option to set multiple values) " +"(required)" +msgstr "" +"Yetkilendirilecek roller (isim veya ID) (birden fazla değer ayarlamak için " +"seçeneği tekrarla) (gerekli)" + +msgid "" +"Roles to authorize (name or ID) (repeat option to set multiple values, " +"required)" +msgstr "" +"Yetkilendirilecek roller (isim veya ID) (birden fazla değer ayarlamak için " +"seçeneği tekrarlayın, gerekli)" + +msgid "Roll up items with " +msgstr "Elemanları ile toparla" + +msgid "" +"Route to be removed from this subnet e.g.: destination=10.10.0.0/16," +"gateway=192.168.71.254 destination: destination subnet (in CIDR notation) " +"gateway: nexthop IP address (repeat option to unset multiple host routes)" +msgstr "" +"Bu altağdan silinecek yön örn: hedef=10.10.0.0/16,geçit=192.168.71.254 hedef:" +"hedef altağ (CIDR notasyonunda) geçit: bir sonraki uğranacak IP adresi " +"(birden fazla sunucu yönü ayarı kaldırmak için seçeneği tekrar et)" + +#, python-format +msgid "Router does not contain route %s" +msgstr "Yönlendirici %s yönünü içermiyor" + +msgid "Router from which port will be removed (name or ID)" +msgstr "Bağlantı noktasının kaldırılacağı yönlendirici (isim veya ID)" + +msgid "Router from which the subnet will be removed (name or ID)" +msgstr "Alt ağdan kaldırılacak yönlendirici (isim veya ID)" + +msgid "Router to be added to an agent (name or ID)" +msgstr "Bir ajana eklenecek yönlendirici (ad veya kimlik)" + +msgid "Router to be removed from an agent (name or ID)" +msgstr "Bir ajandan kaldırılacak yönlendirici (ad veya kimlik)" + +msgid "Router to display (name or ID)" +msgstr "Gösterilecek yönlendirici (isim veya ID)" + +msgid "Router to modify (name or ID)" +msgstr "Düzenlenecek yönlendirici (isim veya ID)" + +msgid "Router to which port will be added (name or ID)" +msgstr "Bağlantı noktası eklenecek yönlendirici (isim veya ID)" + +msgid "Router to which subnet will be added (name or ID)" +msgstr "Alt ağın ekleneceği yönlendirici (isim veya ID)" + +msgid "Router(s) to delete (name or ID)" +msgstr "Silinecek yönlendirici(ler) (isim veya ID)" + +msgid "" +"Routes associated with the router destination: destination subnet (in CIDR " +"notation) gateway: nexthop IP address (repeat option to set multiple routes)" +msgstr "" +"Yönlendirici hedefi ile ilişkili yönlendiriciler: hedef altağ (CIDR " +"notasyonunda) geçit: bir sonraki atlanacak IP adresi (birden fazla " +"yönlendirici ayarlamak için seçeneği tekrarla)" + +msgid "" +"Routes to be removed from the router destination: destination subnet (in " +"CIDR notation) gateway: nexthop IP address (repeat option to unset multiple " +"routes)" +msgstr "" +"Yönlendirici hedefinden kaldırılacak yönlendiriciler: hedef alt ağ (CIDR " +"notasyonuyla) geçit: atlanacak sonraki IP adresi (birden fazla " +"yönlendiricinin ayarını kaldırmak için seçeneği tekrarla)" + +#, python-format +msgid "Rule ID %(rule_id)s not found" +msgstr "%(rule_id)s kural ID'si bulunamadı" + +msgid "Rule applies to incoming network traffic (default)" +msgstr "Gelen ağ trafiğine uygulanacak kural (varsayılan)" + +msgid "Rule applies to outgoing network traffic" +msgstr "Dışarıya doğru ağ trafiğine uygulanacak kural" + +#, python-format +msgid "Rule type \"%(rule_type)s\" only requires arguments %(args)s" +msgstr "\"%(rule_type)s\" kural türü sadece %(args)s argümanlarını gerektirir" + +msgid "SSH to server" +msgstr "Sunucuya SSH ile bağlan" + +msgid "Save an image locally" +msgstr "İmajı yerel olarak kaydet" + +msgid "Save container contents locally" +msgstr "Kap içeriğini yerel olarak kaydet" + +msgid "Save object locally" +msgstr "Nesneyi yerel olarak kaydet" + +msgid "" +"Scale server to a new flavor.\n" +"\n" +"A resize operation is implemented by creating a new server and copying the\n" +"contents of the original disk into a new one. It is also a two-step process " +"for\n" +"the user: the first is to perform the resize, the second is to either " +"confirm\n" +"(verify) success and release the old server, or to declare a revert to " +"release\n" +"the new server and restart the old one." +msgstr "" +"Sunucuyu yeni bir flavor'a ölçekle.\n" +"\n" +"Bir yeniden boyutlandırma işlemi yeni bir sunucu oluşturma ve orijinal \n" +"diskteki içeriğin yeni bir tanesine kopyalanması ile gerçekleştirilir. Bu " +"ayrıca\n" +"kullanıcı için iki adımlı bir süreçtir: birincisi yeniden boyutlandırmayı " +"gerçekleştirmek,\n" +"ikincisi ya başarılı olduğunu doğrulamak ve eski sunucuyu silmek, ya da " +"yenisini silip\n" +"eskisini yeniden başlatma isteğini belirtmek." + +msgid "Search by flavor (name or ID)" +msgstr "Flavor'a göre ara (isim veya ID)" + +msgid "Search by hostname" +msgstr "Sunucu adına göre ara" + +msgid "Search by image (name or ID)" +msgstr "İmaja göre ara (isim veya ID)" + +msgid "Search by project (admin only) (name or ID)" +msgstr "Projeye göre ara (sadece yönetici) (isim veya ID)" + +msgid "Search by server status" +msgstr "Sunucu durumuna göre ara" + +msgid "Search by user (admin only) (name or ID)" +msgstr "Kullanıcıya göre ara (sadece yönetici) (isim veya ID)" + +msgid "Secret associated with (required)" +msgstr " ile ilişkili gizli anahtar (gerekli)" + +msgid "Security group description" +msgstr "Güvenlik grubu açıklaması" + +msgid "Security group rule to display (ID only)" +msgstr "Gösterilecek güvenlik grubu kuralları (sadece ID)" + +msgid "Security group rule(s) to delete (ID only)" +msgstr "Silinecek güvenlik grubu kuralları (sadece ID)" + +msgid "Security group to add (name or ID)" +msgstr "Eklenecek güvenlik grubu (isim veya ID)" + +msgid "" +"Security group to assign to this server (name or ID) (repeat option to set " +"multiple groups)" +msgstr "" +"Bu sunucuya atanan güvenlik grubu (isim veya ID) (birden fazla grup " +"ayarlamak için seçeneği tekrar edin)" + +msgid "" +"Security group to associate with this port (name or ID) (repeat option to " +"set multiple security groups)" +msgstr "" +"Bu bağlantı noktası ile ilişkilendirilecek güvenlik grubu (isim veya ID) " +"(birden fazla güvenlik grubu ayarlamak için seçeneği tekrarla)" + +msgid "Security group to display (name or ID)" +msgstr "Gösterilecek güvenlik grubu (isim veya ID)" + +msgid "Security group to modify (name or ID)" +msgstr "Düzenlenecek güvenlik grubu (isim veya ID)" + +msgid "" +"Security group which should be removed this port (name or ID) (repeat option " +"to unset multiple security groups)" +msgstr "" +"Bu bağlantı noktasının kaldırılması gereken güvenlik grubu (isim veya ID) " +"(birden fazla güvenlik grubunun ayarını kaldırmak için seçeneği tekrarla)" + +msgid "Security group(s) to delete (name or ID)" +msgstr "Silinecek güvenlik grubu (isim veya ID)" + +msgid "" +"Segment identifier for this network segment which is based on the network " +"type, VLAN ID for vlan network type and tunnel ID for geneve, gre and vxlan " +"network types" +msgstr "" +"Ağ türüne göre bölüm belirteci, vlan ağ türü için VLAN ID, geneve, gre ve " +"vxlan ağ türleri için tünel ID" + +msgid "Select an availability zone for the server" +msgstr "Sunucu için kullanılabilirlik bölgesi seçin" + +msgid "Server (name or ID)" +msgstr "Sunucu (isim veya ID)" + +msgid "Server internal device name for volume" +msgstr "Disk bölümü için sunucu dahili aygıt ismi" + +msgid "Server to add the port to (name or ID)" +msgstr "Bağlantı noktası eklenecek sunucu (isim veya ID)" + +msgid "Server to back up (name or ID)" +msgstr "Yedeklenecek sunucu (isim veya ID)" + +msgid "Server to create image (name or ID)" +msgstr "İmaj oluşturulacak sunucu (isim veya ID)" + +msgid "Server to list events (name or ID)" +msgstr "Olayları listelenecek sunucu (isim veya ID)" + +msgid "Server to receive the IP address (name or ID)" +msgstr "IP adresin alınacağı sunucu (isim veya ID)" + +msgid "Server to receive the fixed IP address (name or ID)" +msgstr "Sabit IP adresleri alınacak sunucu (isim veya ID)" + +msgid "Server to receive the floating IP address (name or ID)" +msgstr "Kayan IP adresi alınacak sunucu (isim veya ID)" + +msgid "Server to remove the IP address from (name or ID)" +msgstr "IP adresin kaldırılacağı sunucu (isim veya ID)" + +msgid "Server to remove the fixed IP address from (name or ID)" +msgstr "Sabit IP adresin silineceği sunucu (isim veya ID)" + +msgid "Server to remove the floating IP address from (name or ID)" +msgstr "Kayan IP adresinin kaldırılacağı sunucu (isim veya ID)" + +msgid "Server to remove the port from (name or ID)" +msgstr "Bağlantı noktasının kaldırılacağı sunucu (isim veya ID)" + +msgid "Server to show URL (name or ID)" +msgstr "URL'i gösterilecek sunucu (isim veya ID)" + +msgid "Server to show console log (name or ID)" +msgstr "Konsol günlüğü gösterilecek sunucu (isim veya ID)" + +msgid "Server to show event details (name or ID)" +msgstr "Olay detayları gösterilecek sunucu (isim veya ID)" + +msgid "Server(s) to create dump file (name or ID)" +msgstr "Çıktı dosyası oluşturulacak sunucu(lar) (isim veya ID)" + +msgid "Server(s) to delete (name or ID)" +msgstr "Silinecek sunucu(lar) (isim veya ID)" + +msgid "Server(s) to lock (name or ID)" +msgstr "Kilitlenecek sunucu(lar) (isim veya ID)" + +msgid "Server(s) to pause (name or ID)" +msgstr "Durdurulacak sunucu(lar) (isim veya ID)" + +msgid "Server(s) to restore (name or ID)" +msgstr "Onarılacak sunucu(lar) (isim veya ID)" + +msgid "Server(s) to resume (name or ID)" +msgstr "Devam ettirilece sunucu(lar) (isim veya ID)" + +msgid "Server(s) to shelve (name or ID)" +msgstr "Rafa kaldırılacak sunucu(lar) (isim veya ID)" + +msgid "Server(s) to start (name or ID)" +msgstr "Başlatılacak sunucu(lar) (isim veya ID)" + +msgid "Server(s) to stop (name or ID)" +msgstr "Durdurulacak sunucu(lar) (isim veya ID)" + +msgid "Server(s) to suspend (name or ID)" +msgstr "Durdurulacak sunucu(lar) (isim veya ID)" + +msgid "Server(s) to unlock (name or ID)" +msgstr "Kilidi kaldırılacak sunucu(lar) (isim veya ID)" + +msgid "Server(s) to unpause (name or ID)" +msgstr "Devam ettirilecek sunucu(lar) (isim veya ID)" + +msgid "Server(s) to unshelve (name or ID)" +msgstr "Alınacak sunucu(lar) (isim veya ID)" + +msgid "Service profile (ID only)" +msgstr "Servis profili (sadece ID)" + +msgid "Service provider to display" +msgstr "Gösterilecek servis sağlayıcı" + +msgid "Service provider to modify" +msgstr "Düzenlenecek servis sağlayıcı" + +msgid "Service provider(s) to delete" +msgstr "Silinecek servis sağlayıcı(lar)" + +msgid "Service to be associated with new endpoint (name or ID)" +msgstr "Yeni uç nokta ile ilişkilendirilecek servis (isim veya ID)" + +msgid "Service to display (type or name)" +msgstr "Gösterilecek servis (tür veya isim)" + +msgid "Service to display (type, name or ID)" +msgstr "Gösterilecek servis (tür, isim veya ID)" + +msgid "Service to modify (type, name or ID)" +msgstr "Düzenlenecek servis (tür, isim veya ID)" + +msgid "" +"Service type for this subnet e.g.: network:floatingip_agent_gateway. Must be " +"a valid device owner value for a network port (repeat option to set multiple " +"service types)" +msgstr "" +"Bu alt ağ için servis türü örn: network:floatingip_agent_gateway. Bir ağ " +"bağlantı noktası için geçerli bir cihaz sahibi değeri olmalıdır (birden " +"fazla servis türü ayarlamak için seçeneği tekrarlayın)" + +msgid "" +"Service type to be removed from this subnet e.g.: network:" +"floatingip_agent_gateway. Must be a valid device owner value for a network " +"port (repeat option to unset multiple service types)" +msgstr "" +"Bu altağdan kaldırılacak servis türü örn: network:floatingip_agent_gateway. " +"Bir ağ bağlantı noktası için geçerli bir aygıt sahibi değeri olmalıdır " +"(birden fazla servis türünün ayarını kaldırmak için seçeneği tekrarlayın)" + +msgid "" +"Service type to which the flavor applies to: e.g. VPN (See openstack network " +"service provider list for loaded examples.)" +msgstr "" +"Flavor'ın uygulanacağı hizmet türü: ör. VPN (Yüklü sunucular için açık devre " +"ağ servis sağlayıcısı listesine bakın.)" + +msgid "Service(s) to delete (type, name or ID)" +msgstr "Silinecek servis(ler) (tür, isim veya ID)" + +msgid "Set DNS name to this port (requires DNS integration extension)" +msgstr "" +"DNS adını bu bağlantı noktasına ayarlayın (DNS entegrasyon uzantısı " +"gerektirir)" + +msgid "Set Network QoS rule properties" +msgstr "Ağ QoS kural özelliklerini ayarla" + +msgid "Set QoS policy name" +msgstr "QoS ilke adı ayarla" + +msgid "Set QoS policy properties" +msgstr "QoS ilke özelliklerini ayarla" + +msgid "Set QoS specification properties" +msgstr "QoS özellik özelliğini ayarla" + +msgid "" +"Set a QoS specification property (repeat option to set multiple properties)" +msgstr "" +"Bir QoS özellik özelliği ayarla (birden fazla özellik ayarlamak için " +"seçeneği tekrarla)" + +msgid "Set a project property (repeat option to set multiple properties)" +msgstr "" +"Bir proje özelliği ayarla (birden fazla özellik ayarlamak için seçeneği " +"tekrarla)" + +msgid "Set a property on (repeat option to set multiple properties)" +msgstr "" +" üzerinde bir özellik ayarla (birden fazla özellik ayarlamak için " +"seçeneği tekrarla)" + +msgid "" +"Set a property on this account (repeat option to set multiple properties)" +msgstr "" +"Bu hesap üzerindeki özelliği ayarla (birden fazla özellik ayarlamak için " +"seçeneği tekrarla)" + +msgid "" +"Set a property on this container (repeat option to set multiple properties)" +msgstr "" +"Bu kap üzerinde bir özellik ayarla (birden fazla özellik ayarlamak için " +"seçenekleri tekrarla)" + +msgid "Set a property on this image (repeat option to set multiple properties)" +msgstr "" +"Bu imaj üzerindeki bir özelliği ayarla (birden fazla özellik ayarlamak için " +"seçeneği tekrarla)" + +msgid "" +"Set a property on this object (repeat option to set multiple properties)" +msgstr "" +"Bu nesne üzerinde bir özellik ayarla (birden fazla özellik ayarlamak için " +"seçeneği tekrarla)" + +msgid "Set a property on this server (repeat option to set multiple values)" +msgstr "" +"Bu sunucuda bir özellik ayarlayın (birden fazla değeri ayarlamak için " +"seçeneği tekrarlayın)" + +msgid "" +"Set a property on this volume (repeat option to set multiple properties)" +msgstr "" +"Bu disk bölümü üzerinde bir özellik ayarla (birden fazla özellik ayarlamak " +"için seçeneği tekrarla)" + +msgid "" +"Set a property on this volume type (repeat option to set multiple properties)" +msgstr "" +"Bu disk bölümü türü üzerinde bir özellik ayarla (birden fazla özellik " +"ayarlamak için seçeneği tekrarla)" + +msgid "" +"Set a property to this snapshot (repeat option to set multiple properties)" +msgstr "" +"Bu anlık görüntü için bir özellik ayarla (birden fazla özellikleri ayarlamak " +"için seçeneği tekrarla)" + +msgid "" +"Set a property to this volume (repeat option to set multiple properties)" +msgstr "" +"Bu disk bölümüne bir özellik ayarla (birden fazla özellik ayarlamak için " +"seçeneği tekrarlayın)" + +msgid "Set a tag on this image (repeat option to set multiple tags)" +msgstr "" +"Bu imaj üzerinde bir etiket oluştur (birden fazla etiket oluşturmak için " +"seçeneği tekrarla)" + +msgid "Set account properties" +msgstr "Hesap özelliklerini ayarla" + +msgid "" +"Set address scope associated with the subnet pool (name or ID), prefixes " +"must be unique across address scopes" +msgstr "" +"Alt ağ havuzuyla ilişkili adres kapsamını ayarla (isim veya ID), öneklerin " +"adres kapsamları arasında benzersiz olması gerekir" + +msgid "Set address scope name" +msgstr "Adres kapsamı adını ayarla" + +msgid "Set address scope properties" +msgstr "Adres kapsamı özelliklerini ayarlama" + +msgid "Set aggregate name" +msgstr "Küme ismini ayarla" + +msgid "Set aggregate properties" +msgstr "Küme özelliklerini ayarla" + +msgid "Set an alternate project on this image (name or ID)" +msgstr "Bu imaj üzerinde alternatif bir proje ayarla (isim veya ID)" + +msgid "" +"Set an image property on this volume (repeat option to set multiple image " +"properties)" +msgstr "" +"Bu disk bölümü üzerinde imaj özelliği ayarla (birden fazla imaj özelliği " +"ayarlamak için seçeneği tekrarla)" + +msgid "Set availability zone name" +msgstr "Kullanılabilirlik bölge adını ayarla" + +msgid "Set compute agent properties" +msgstr "Hesaplama ajanı özelliklerini ayarla" + +msgid "Set compute service properties" +msgstr "Hesaplama servisi özelliklerini ayarla" + +msgid "Set consistency group properties" +msgstr "Tutarlılık grubu özelliklerini ayarla" + +msgid "Set consumer properties" +msgstr "Alıcı özelliklerini ayarla" + +msgid "Set container properties" +msgstr "Kap özelliklerini ayarla" + +msgid "Set credential properties" +msgstr "Kimlik bilgileri özelliklerini ayarla" + +msgid "" +"Set data plane status of this port (ACTIVE | DOWN). Unset it to None with " +"the 'port unset' command (requires data plane status extension)" +msgstr "" +"Bu bağlantı noktasının veri düzlemi durumunu ayarlayın (ACTIVE | DOWN). " +"'port unset' komutu ile None'a getirin (veri düzlemi durum uzantısı " +"gerektirir)" + +msgid "Set default project (name or ID)" +msgstr "Varsayılan projeyi ayarla (isim veya ID)" + +msgid "" +"Set default quota for subnet pool as the number ofIP addresses allowed in a " +"subnet" +msgstr "" +"Bir alt ağda izin verilen IP adreslerinin sayısı olarak alt ağ havuzu için " +"varsayılan kota ayarla" + +msgid "Set domain properties" +msgstr "Alan özelliklerini ayarla" + +msgid "Set endpoint properties" +msgstr "Uç nokta özellikleri ayarla" + +msgid "Set federation protocol properties" +msgstr "Federasyon protokolü özelliklerini ayarla" + +msgid "Set flavor access to project (name or ID) (admin only)" +msgstr "Projeye flavor erişimi ayarla (isim veya ID) (sadece yönetici)" + +msgid "Set flavor name" +msgstr "Flavor ismi tanımla" + +msgid "Set flavor properties" +msgstr "Flavor özelliklerini ayarla" + +msgid "Set floating IP Properties" +msgstr "Yüzen IP özelliklerini ayarla" + +msgid "Set floating IP description" +msgstr "Yüzen IP açıklaması ayarla" + +msgid "Set group properties" +msgstr "Grup özelliklerini ayarla" + +msgid "Set host properties" +msgstr "Sunucu özelliklerini ayarla" + +msgid "Set identity provider description" +msgstr "Kimlik sağlayıcı tanımlarını ayarla" + +msgid "Set identity provider properties" +msgstr "Kimlik sağlayıcı özelliklerini ayarla" + +msgid "Set image properties" +msgstr "İmaj özelliklerini ayarla" + +msgid "Set mapping properties" +msgstr "Eşleşme özelliklerini ayarla" + +msgid "Set network RBAC policy properties" +msgstr "Ağ RBAC ilke özelliklerini ayarlama" + +msgid "Set network agent description" +msgstr "Ağ aracı açıklaması ayarla" + +msgid "Set network agent properties" +msgstr "Ağ ajanı özelliklerini ayarla" + +msgid "Set network description" +msgstr "Ağ açıklamasını ayarla" + +msgid "Set network flavor description" +msgstr "Ağ flavor açıklamasını ayarla" + +msgid "Set network flavor profile properties" +msgstr "Ağ flavor profil özelliklerini ayarla" + +msgid "Set network flavor properties" +msgstr "Ağ flavor'ının özelliklerini ayarla" + +msgid "Set network name" +msgstr "Ağ ismini ayarla" + +msgid "Set network properties" +msgstr "Ağ özelliklerini ayarla" + +msgid "Set network segment description" +msgstr "Ağ kesimi açıklamasını ayarla" + +msgid "Set network segment name" +msgstr "Ağ bölüm ismi ayarla" + +msgid "Set network segment properties" +msgstr "Ağ kesimi özelliklerini ayarla" + +msgid "Set new root password (interactive only)" +msgstr "Yeni root parolası ayarla (sadece interaktif)" + +msgid "Set object properties" +msgstr "Nesne özelliklerini ayarla" + +msgid "Set policy properties" +msgstr "Politika özelliklerini ayarla" + +msgid "Set port name" +msgstr "Bağlantı noktası ismini ayarla" + +msgid "Set port properties" +msgstr "Bağlantı noktaları özelliklerini ayarla" + +msgid "Set project description" +msgstr "Proje açıklamasını ayarla" + +msgid "Set project name" +msgstr "Proje ismini ayarla" + +msgid "Set project properties" +msgstr "Proje özelliklerini ayarla" + +msgid "Set quotas for " +msgstr " için kotaları ayarla" + +msgid "Set quotas for a specific " +msgstr "Belirli bir için kota ayarla" + +msgid "Set quotas for project or class" +msgstr "Proje veya sınıf için kotaları ayarla" + +msgid "Set quotas for this project or class (name/ID)" +msgstr "Bu proje veya sınıf için (isim/ID) kotaları ayarla" + +msgid "Set region properties" +msgstr "Bölgenin özelliklerini ayarla" + +msgid "Set role name" +msgstr "Rol ismini ayarla" + +msgid "Set role properties" +msgstr "Rol özelliklerini ayarla" + +msgid "Set router description" +msgstr "Yönlendirici açıklaması ayarla" + +msgid "Set router name" +msgstr "Yönlendirici isimini ayarla" + +msgid "Set router properties" +msgstr "Yönlendirici özelliklerini ayarla" + +msgid "Set router to centralized mode (disabled router only)" +msgstr "" +"Yönlendiriciyi merkezi kipe ayarla (sadece devre dışı bırakılmış " +"yönlendiriciler)" + +msgid "Set router to distributed mode (disabled router only)" +msgstr "" +"Yönlendiriciyi dağıtık kipte ayarla (sadece devre dışı bırakılmış " +"yönlendiriciler)" + +msgid "Set security group properties" +msgstr "Güvenlik grubu özelliklerini ayarla" + +msgid "Set security group rule description" +msgstr "Güvenlik grubu kural tanımını ayarla" + +msgid "Set server properties" +msgstr "Sunucu özelliklerini ayarla" + +msgid "Set service properties" +msgstr "Servis özelliklerini ayarla" + +msgid "Set service provider properties" +msgstr "Servis sağlayıcı özelliklerini ayarlayın" + +msgid "Set snapshot properties" +msgstr "Anlık görüntü özelliklerini ayarlayın" + +msgid "Set subnet description" +msgstr "Alt ağ açıklaması ayarla" + +msgid "Set subnet pool default prefix length" +msgstr "Alt ağ havuzu varsayılan ön ek boyutunu ayarla " + +msgid "Set subnet pool description" +msgstr "Alt ağ havuzu açıklaması ayarla" + +msgid "Set subnet pool maximum prefix length" +msgstr "Alt ağ havuzu en büyük ön ek boyutunu ayarla" + +msgid "Set subnet pool minimum prefix length" +msgstr "Altağ havuzunun en küçün ön ek boyutunu ayarla" + +msgid "Set subnet pool name" +msgstr "Alt ağ havuz ismini ayarla" + +msgid "" +"Set subnet pool prefixes (in CIDR notation) (repeat option to set multiple " +"prefixes)" +msgstr "" +"Alt ağ havuzu öneklerini ayarla (CIDR notasyonunda) (birden fazla ön ek " +"ayarlamak için seçeneği tekrarla)" + +msgid "Set subnet pool properties" +msgstr "Alt ağ havuzu özellikleri ayarla" + +msgid "Set subnet properties" +msgstr "Altağ özelliklerini ayarla" + +msgid "" +"Set the class that provides encryption support for this volume type (e.g " +"\"LuksEncryptor\") (admin only) (This option is required when setting " +"encryption type of a volume for the first time. Consider using other " +"encryption options such as: \"--encryption-cipher\", \"--encryption-key-size" +"\" and \"--encryption-control-location\")" +msgstr "" +"Bu birim türü için şifreleme desteği sağlayan sınıfı ayarlayın (örn " +"\"LuksEncryptor\") (sadece yönetici) (Bu seçenek, bir disk bölümü şifreleme " +"türünü ilk kez ayarlarken gereklidir. Şu diğer şifreleme seçeneklerini " +"kullanmayı düşünün: \"--encryption-cipher\", \"--encryption-key-size\" ve " +"\"--encryption-control-location\")" + +msgid "" +"Set the class that provides encryption support for this volume type (e.g " +"\"LuksEncryptor\") (admin only) (This option is required when setting " +"encryption type of a volume. Consider using other encryption options such " +"as: \"--encryption-cipher\", \"--encryption-key-size\" and \"--encryption-" +"control-location\")" +msgstr "" +"Bu disk bölümü türü için şifreleme desteği sağlayan sınıfı ayarla (örn " +"\"LuksEncryptor\") (sadece yönetici) (Bir disk bölümünün şifreleme türü " +"ayarlanırken bu seçenek zorunludur. \"--encryption-cipher\", \"--encryption-" +"key-size\" ve \"--encryption-control-location\" gibi diğer şifreleme " +"seçeneklerini kullanmayı göz önünde bulundurun)" + +msgid "" +"Set the encryption algorithm or mode for this volume type (e.g \"aes-xts-" +"plain64\") (admin only)" +msgstr "" +"Bu disk bölümü türü için şifreleme algoritmasını veya kipini ayarla (örn " +"\"aes-xts-plain64\") (sadece yönetici)" + +msgid "Set the network as the default external network" +msgstr "Ağı varsayılan dış ağ olarak ayarlayın" + +msgid "" +"Set the notional service where the encryption is performed (\"front-end\" or " +"\"back-end\") (admin only) (The default value for this option is \"front-end" +"\" when setting encryption type of a volume for the first time. Consider " +"using other encryption options such as: \"--encryption-cipher\", \"--" +"encryption-key-size\" and \"--encryption-provider\")" +msgstr "" +"Şifrelemenin yapıldığı kavramsal servisi ayarla (\"front-end\" veya \"back-" +"end\") (sadece yönetici) (Disk bölümü türü şifrelemesi ilk kez ayarlanırken " +"bu seçenek için varsayılan değer \"front-end\"dir. Şu diğer şifreleme " +"seçenelerini kullanmayı düşünün: \"--encryption-cipher\", \"--encryption-key-" +"size\" ve \"--encryption-provider\")" + +msgid "" +"Set the notional service where the encryption is performed (\"front-end\" or " +"\"back-end\") (admin only) (The default value for this option is \"front-end" +"\" when setting encryption type of a volume. Consider using other encryption " +"options such as: \"--encryption-cipher\", \"--encryption-key-size\" and \"--" +"encryption-provider\")" +msgstr "" +"Şifrelemenin gerçekleştirildiği kavramsal servisi ayarla (\"front-end\" veya " +"\"back-end\") (sadece yönetici) (Bir disk bölümünün şifreleme türü " +"ayarlanırken bu seçenek için varsayılan değer \"front-end\"'dir. \"--" +"encryption-cipher\", \"--encryption-key-size\" ve \"--encryption-provider\" " +"gibi diğer şifreleme seçeneklerini kullanmayı göz önünde bulundurun)" + +msgid "Set the password on the rebuilt instance" +msgstr "Yeniden oluşturulmuş sunucudaki parolayı ayarla" + +msgid "Set the router as highly available (disabled router only)" +msgstr "" +"Yönlendiriciyi yüksek kullanılabilirlikli olarak ayarla (sadece " +"yönlendiriciyi devre dışı bırak)" + +msgid "" +"Set the size of the encryption key of this volume type (e.g \"128\" or " +"\"256\") (admin only)" +msgstr "" +"Bu disk bölümü türünün şifreleem anahtarı boyutunu ayarla (örn \"128\" veya " +"\"256\") (sadece yönetici)" + +msgid "Set the type of volume" +msgstr "Disk bölümü türünü ayarla" + +msgid "Set this as a default network QoS policy" +msgstr "Bunu varsayılan bir ağ QoS politikası olarak ayarla" + +msgid "Set this as a default subnet pool" +msgstr "Bunu varsayılan altağ havuzunu ayarla" + +msgid "Set this as a non-default network QoS policy" +msgstr "Bunu varsayılan olmayan bir ağ QoS politikası olarak ayarla" + +msgid "Set this as a non-default subnet pool" +msgstr "Bunu varsayılan olmayan altağ havuzu olarak ayarla" + +msgid "" +"Set this network as an external network (external-net extension required)" +msgstr "" +"Bu şebekeyi harici bir ağ olarak ayarlayın (harici ağ uzantısı gerekir)" + +msgid "Set this network as an internal network" +msgstr "Bu şebekeyi dahili bir şebeke olarak ayarlayın" + +msgid "Set this network as an internal network (default)" +msgstr "Bu ağı dahili bir ağ olarak ayarlayın (varsayılan)" + +msgid "Set this subnet pool as not shared" +msgstr "Bu alt ağ havuzunu paylaşılmayan olarak ayarlayın" + +msgid "Set this subnet pool as shared" +msgstr "Bu alt ağ havuzunu paylaşılan olarak ayarla" + +msgid "Set user description" +msgstr "Kullanıcı tanımını ayarla" + +msgid "Set user email address" +msgstr "Kullanıcı e-posta adresini ayarla" + +msgid "Set user name" +msgstr "Kullanıcı adını ayarla" + +msgid "Set user password" +msgstr "Kullanıcı parolası ayarla" + +msgid "Set user properties" +msgstr "Kullanıcı özelliklerini ayarla" + +msgid "Set volume backup properties" +msgstr "Disk bölümü yedek özelliklerini ayarla" + +msgid "Set volume host properties" +msgstr "Disk bölümü sunucu özelliklerini ayarla" + +msgid "Set volume properties" +msgstr "Disk bölümü özelliklerini ayarla" + +msgid "Set volume service properties" +msgstr "Disk bölümü servisi özelliklerini ayarla" + +msgid "Set volume snapshot properties" +msgstr "Disk bölümü anlık görüntüsü özelliklerini ayarla" + +msgid "Set volume to read-only access mode" +msgstr "Disk bölümünü salt okunur kipine ayarla" + +msgid "Set volume to read-write access mode" +msgstr "Disk bölümünü okuma yazma erişimi kipine ayarla" + +msgid "Set volume to read-write access mode (default)" +msgstr "Okuma-yazma erişim kipine disk bölümüne ayarla (varsayılan)" + +msgid "Set volume type access to project (name or ID) (admin only)" +msgstr "" +"Projeye disk bölümü türü erişimini ayarla (isim veya ID) (sadece yönetici)" + +msgid "Set volume type description" +msgstr "Disk bölümü türü açıklamasını ayarla" + +msgid "Set volume type name" +msgstr "Disk bölüm türü ismini ayarla" + +msgid "Set volume type properties" +msgstr "Disk bölümü türü özelliklerini ayarla" + +msgid "Sets an expiration date for the trust (format of YYYY-mm-ddTHH:MM:SS)" +msgstr "" +"Güven için son kullanma tarihi ayarlayın (YYYY-mm-ddTHH:MM:SS biçiminde)" + +msgid "Share meter between projects" +msgstr "Sayacı projeler arasında paylaş" + +msgid "Share the address scope between projects" +msgstr "Projeler arasında adres kapsamını paylaş" + +msgid "Share the network between projects" +msgstr "Ağları projeler arasında paylaşın" + +msgid "Shelve server(s)" +msgstr "Sunucu(yu/ları) raftan çıkart" + +msgid "Show API extension" +msgstr "API uzantısını göster" + +msgid "Show IP availability for a specific network (name or ID)" +msgstr "Belirli bir ağ için IP kullanılabilirliğini göster (isim veya ID)" + +msgid "Show RDP console URL" +msgstr "RDP konsol URL'ini göster" + +msgid "Show SPICE console URL" +msgstr "SPICE konsol URL'ini göster" + +msgid "Show WebMKS console URL" +msgstr "WebMKS konsol URL'ini göster" + +msgid "Show absolute limits" +msgstr "Mutlak sınırları göster" + +msgid "Show all modules that have version information" +msgstr "Sürüm bilgisine sahip tüm modülleri göster" + +msgid "" +"Show commands filtered by a command group, for example: identity, volume, " +"compute, image, network and other keywords" +msgstr "" +"Komutları bir komut grubu tarafından filtrelenmiş halde göster, örneğin: " +"kimlik, disk bölümü, hesaplama, imaj, ağ ve diğer anahtar kelimeler" + +msgid "Show compute and block storage limits" +msgstr "Hesaplama ve blok depolama sınırlarını göster" + +msgid "Show credentials for user (name or ID)" +msgstr "Kullanıcılar için kimlik bilgilerini göster (isim veya ID)" + +msgid "Show default quotas for " +msgstr " için varsayılan kotaları göster" + +msgid "Show detail for all projects (admin only) (defaults to False)" +msgstr "" +"Tüm projeler için detayları göster (sadece yönetici) (varsayılan olarak " +"False)" + +msgid "Show details for all projects. Admin only. (defaults to False)" +msgstr "" +"Tüm projeler için detayları göster. Sadece yönetici. (varsayılan olarak " +"False)" + +msgid "" +"Show limits for a specific project (name or ID) [only valid with --absolute]" +msgstr "" +"Belirli bir proje için sınırları göster (isim veya ID) [sadece --absolute " +"ile geçerli]" + +msgid "Show network IP availability details" +msgstr "Ağ IP kullanılabilirlik detaylarını göster" + +msgid "Show network details" +msgstr "Ağ detaylarını göster" + +msgid "Show network meter" +msgstr "Ağ ölçeklerini göster" + +msgid "Show noVNC console URL (default)" +msgstr "noVNC konsol URL'ini göster (varsayılan)" + +msgid "Show only bare public key paired with the generated key" +msgstr "" +"Yalnızca oluşturulan anahtarla eşleştirilmiş çıplak genel anahtarı göster" + +msgid "Show password in clear text" +msgstr "Parolaları düz metin olarak göster" + +msgid "Show project's subtree (children) as a list" +msgstr "Projenin alt projelerini (çocuklarını) bir liste olarak göster" + +msgid "Show quotas for " +msgstr " için kotaları göster" + +msgid "Show quotas for project or class" +msgstr "Proje veya sınıf için kotaları göster" + +msgid "Show quotas for this project or class (name or ID)" +msgstr "Bu proje veya sınıf (isim veaya ID) için kotaları göster" + +msgid "Show rate limits" +msgstr "Oran sınırları göster" + +msgid "Show resource usage for a single project" +msgstr "Tek bir proje için kaynak kullanımını göster" + +msgid "Show serial console URL" +msgstr "serial konsol URL'ini göster" + +msgid "Show server details" +msgstr "Sunucu detaylarını göster" + +msgid "Show server event details" +msgstr "Sunucu olay detaylarını listele" + +msgid "Show server's console output" +msgstr "Sunucunun konsol çıktısını göster" + +msgid "Show server's remote console URL" +msgstr "Sunucunun uzak konsol URL'ini göster" + +msgid "Show service catalog information" +msgstr "Servis katalog bilgisini göster" + +msgid "Show the project's parents as a list" +msgstr "Projenin üst projelerini bir liste olarak göster" + +msgid "Show volume details" +msgstr "Disk bölümü detaylarını göster" + +msgid "Show volume transfer request details." +msgstr "Disk bölümü aktarım isteği detaylarını göster." + +msgid "Show xvpvnc console URL" +msgstr "xvpvnc konsol URL'ini göster" + +msgid "Size of image data (in bytes)" +msgstr "İmaj verisinin boyutu (bayt cinsinden)" + +msgid "Skip flavor and image name lookup." +msgstr "Flavor veya imaj ismi aramasını atla." + +msgid "Snapshot to backup (name or ID)" +msgstr "Yedeği alınacak anlık görüntü (isim veya ID)" + +msgid "Snapshot to display (name or ID)" +msgstr "Gösterilecek anlık görüntü (isim veya ID)" + +msgid "Snapshot to modify (name or ID)" +msgstr "Düzenlenecek anlık görüntü (isim veya ID)" + +msgid "Snapshot(s) to delete (name or ID)" +msgstr "Silinecek anlık görüntü(ler) (isim veya ID)" + +msgid "" +"Sort output by selected keys and directions (asc or desc) (default: asc), " +"repeat this option to specify multiple keys and directions." +msgstr "" +"Çıktıyı seçilen anahtarlara ve yönlere göre sırala (artan veya azalan) " +"(varsayılan: artan), birden çok anahtar ve yön belirlemek için bu seçeneği " +"tekrarlayın." + +msgid "" +"Sort output by selected keys and directions(asc or desc) (default: name:" +"asc), multiple keys and directions can be specified separated by comma" +msgstr "" +"Çıktıyı seçilen tuşlara ve yönlere göre sırala (artan veya azalan) " +"(varsayılan: ad:artan), virgülle ayrılmış olarak birden çok anahtar ve yön " +"belirlenebilir" + +msgid "Specific service endpoint to use" +msgstr "Kullanılacak belirli bir servis uç noktası" + +msgid "Specifies if the role grant is inheritable to the sub projects" +msgstr "" +"Rol atamasının alt projelere miras bırakılıp bırakılmayacağını belirler" + +msgid "" +"Specify a gateway for the subnet. The three options are: : " +"Specific IP address to use as the gateway, 'auto': Gateway address should " +"automatically be chosen from within the subnet itself, 'none': This subnet " +"will not use a gateway, e.g.: --gateway 192.168.9.1, --gateway auto, --" +"gateway none (default is 'auto')." +msgstr "" +"Alt ağ için bir geçit belirle. Üç seçenek: : geçit olarak " +"kullanılacak belirli bir IP adresi, 'auto': Geçit adresi ağdan otomatik " +"olarak seçilmelidir, 'none': Bu alt ağ bir geçit kullanmayacak, örn: --" +"gateway 192.168.9.1, --gateway auto, --gateway none (varsayılan 'auto')." + +msgid "" +"Specify a gateway for the subnet. The options are: : Specific IP " +"address to use as the gateway, 'none': This subnet will not use a gateway, e." +"g.: --gateway 192.168.9.1, --gateway none." +msgstr "" +"Alt ağ için bir geçit belirle. Seçenekler: : Geçit olarak " +"kullanılacak belirli bir IP adresi, 'none': Bu alt ağ geçit olarak " +"kullanılmayacak, örn: --gateway 192.168.9.1, --gateway none." + +msgid "Specify an alternate project (name or ID)" +msgstr "Alternatif bir proje belirle (isim veya ID)" + +msgid "Specify an alternate user (name or ID)" +msgstr "Alternatif bir kullanıcı belirle (isim veya ID)" + +msgid "Specify if this network should be used as the default external network" +msgstr "" +"Bu ağın varsayılan dış ağ olarak kullanılması gerekip gerekmediğini " +"belirleyin" + +msgid "" +"Specifying a --nic of auto or none cannot be used with any other --nic, --" +"network or --port value." +msgstr "" +"auto veya none --nic'i belirtmek başka bir --nic, --network veya --port " +"değeriyle kullanılamaz." + +msgid "" +"Specifying the auth-key as a positional argument has been deprecated. " +"Please use the --auth-key option in the future." +msgstr "" +"Yetki anahtarını pozisyonel argüman olarak belirleme kullanımdan " +"kaldırıldı. Lütfen gelecekte --auth-key seçeneğini kullanın." + +msgid "Start server(s)." +msgstr "Sunucu(ları/yu) başlat." + +msgid "Stop server(s)." +msgstr "Sunucu(ları/yu) durdur." + +#, python-format +msgid "Subnet does not contain %(option)s %(value)s" +msgstr "Ağ %(option)s %(value)s'yü içermiyor" + +msgid "Subnet on which you want to create the floating IP (name or ID)" +msgstr "Yüzen IP'yi oluşturmak istediğiniz alt ağ (isim veya ID)" + +#, python-format +msgid "Subnet pool does not contain prefix %s" +msgstr "%s önekini içermeyen altağ havuzu" + +msgid "Subnet pool from which this subnet will obtain a CIDR (Name or ID)" +msgstr "Bu alt ağın bir CIDR alacağı alt ağ havuzu (isim veya ID)" + +msgid "Subnet pool to display (name or ID)" +msgstr "Gösterilecek altağ havuzu (isim veya ID)" + +msgid "Subnet pool to modify (name or ID)" +msgstr "Düzenlenecek altağ havuzu (isim veya ID)" + +msgid "Subnet pool(s) to delete (name or ID)" +msgstr "Silinecek altağ havuzları (isim veya ID)" + +msgid "" +"Subnet range in CIDR notation (required if --subnet-pool is not specified, " +"optional otherwise)" +msgstr "" +"CIDR notasyonunda altağ aralığı (--subnet-pool belirtilmediyse zorunlu, " +"diğer türlü seçimlidir)" + +msgid "Subnet to be added (name or ID)" +msgstr "Eklenecek alt ağ (isim veya ID)" + +msgid "Subnet to be removed (name or ID)" +msgstr "Kaldırılacak altağ (isim veya ID)" + +msgid "Subnet to display (name or ID)" +msgstr "Gösterilecek altağ (isim veya ID)" + +msgid "Subnet to modify (name or ID)" +msgstr "Düzenlenecek altağ (isim veya ID)" + +msgid "Subnet(s) to delete (name or ID)" +msgstr "Silinecek altağ(lar) (isim veya ID)" + +msgid "Suspend server(s)" +msgstr "Sunucu(yu/ları) durdur" + +msgid "Swap space size in MB (default 0M)" +msgstr "MB cinsinden takas alanı boyutu (varsayılan 0M)" + +#, python-format +msgid "Tag to be added to the %s (repeat option to set multiple tags)" +msgstr "" +"%s'e eklenecek etiketler (birden fazla etiket ayarlamak için seçeneği tekrar " +"et)" + +#, python-format +msgid "Tag to be removed from the %s (repeat option to remove multiple tags)" +msgstr "" +"%s'den kaldırılacak etiket (birden fazla etiket silmek için seçenekleri " +"tekrarlayın)" + +msgid "Target hostname" +msgstr "Hedef sunucu adı" + +msgid "Thaw and enable the specified volume host" +msgstr "Belirtilen disk bölümü barındırıcısını çöz ve etkinleştir" + +#, python-format +msgid "The %(old)s option is deprecated, please use %(new)s instead." +msgstr "" +"%(old)s seçeneği kullanımdan kaldırıldı, lütfen onun yerine %(new)s kullanın." + +msgid "The --clear-routes option is deprecated, please use --no-route instead." +msgstr "" +"--clear-routes seçeneği kullanımdan kaldırıldı, onun yerine --no-route " +"kullanın." + +msgid "The --device-id option is deprecated, please use --device instead." +msgstr "" +"--device-id seçeneği kullanımdan kaldırıldı, lütfen onun yerine --device " +"komutunu kullanın." + +msgid "The --host-id option is deprecated, please use --host instead." +msgstr "" +"--host-id seçeneği kullanımdan kaldırıldı, lütfen onun yerine --host " +"komutunu kullanın." + +msgid "The --owner option is deprecated, please use --project instead." +msgstr "" +"--owner seçeneği kullanımdan kaldırıldı, onun yerine lütfen --project " +"seçeneğini kullanın." + +msgid "" +"The ID of the volume backend replication target where the host will failover " +"to (required)" +msgstr "" +"Ana bilgisayarın üstleneceği disk bölümü arkaplan kopyası ID'si (zorunlu)" + +msgid "" +"The argument --type is deprecated, use service create --name " +"type instead." +msgstr "" +"--type argümanı kullanımdan kaldırılmıştır, onun yerine service create --" +"name type komutunu kullanın." + +msgid "" +"The attribute(s) of the exsiting remote volume snapshot (admin required) " +"(repeat option to specify multiple attributes) e.g.: '--remote-source source-" +"name=test_name --remote-source source-id=test_id'" +msgstr "" +"Varolan uzak birimin anlık görüntüsünün nitelikleri (yönetici gerekli) " +"(birden fazla özellik belirlemek için seçeneği tekrarlayın) örn: '--remote-" +"source source-name=test_name --remote-source source-id=test_id'" + +msgid "The last backup of the previous page (name or ID)" +msgstr "Bir önceki sayfanın son yedeği (isim veya ID)" + +msgid "The last flavor ID of the previous page" +msgstr "Bir önceki sayfanın son flavor ID'si" + +msgid "" +"The last image of the previous page. Display list of images after marker. " +"Display all images if not specified. (name or ID)" +msgstr "" +"Bir önceki sayfanın son imajı. İşaretçiden sonraki imajların listesini " +"görüntüle. Belirtilmemişse tüm imajları görüntüleyin. (isim veya ID)" + +msgid "" +"The last server of the previous page. Display list of servers after marker. " +"Display all servers if not specified. (name or ID)" +msgstr "" +"Bir önceki sayfanın son sunucusu. İşaretçi sonrasındaki sunucuların " +"listesini görüntüle. Belirtilmemişse tüm sunucuları görüntüleyin. (isim veya " +"ID)" + +msgid "The last snapshot ID of the previous page" +msgstr "Bir önceki sayfanın son anlık görüntü ID'si" + +msgid "The last volume ID of the previous page" +msgstr "Bir önceki sayfanın son disk bölümü ID'si" + +msgid "The object to which this RBAC policy affects (name or ID)" +msgstr "Bu RBAC politikasının etkilediği nesne (ad veya kimlik)" + +msgid "The owner project (name or ID)" +msgstr "Projenin sahibi (isim veya ID)" + +msgid "" +"The physical mechanism by which the virtual network is implemented. For " +"example: flat, geneve, gre, local, vlan, vxlan." +msgstr "" +"Sanal ağın uygulandığı fiziksel mekanizma. Örneğin: düz, geneve, gre, yerel, " +"vlan, vxlan." + +msgid "The project to which the RBAC policy will be enforced (name or ID)" +msgstr "RBAC politikası dayatılacak proje (isim veya ID)" + +msgid "The remote IP prefix to associate with this rule" +msgstr "Bu kural ile ilişkilendirilecek uzak IP ön eki" + +msgid "" +"This command has been deprecated. Please use \"floating ip create\" instead." +msgstr "" +"Bu komut kullanımdan kaldırıldı. Lütfen onun yerine \"floating ip create\" " +"kullanın." + +msgid "" +"This command has been deprecated. Please use \"floating ip delete\" instead." +msgstr "" +"Bu komut kullanımdan kaldırıldı. Lütfen yerine \"floating ip delete\" " +"komutunu kullanın." + +msgid "" +"This command has been deprecated. Please use \"floating ip list\" instead." +msgstr "" +"Bu komut kullanımdan kalktı. Onun yerine lütfen \"floating ip list\" " +"kullanın." + +msgid "" +"This command has been deprecated. Please use \"floating ip pool list\" " +"instead." +msgstr "" +"Bu komut kullanımdan kaldırıldı. Lütfen, onun yerine \"floating ip pool list" +"\" komutunu kullanın." + +msgid "" +"This command has been deprecated. Please use \"floating ip show\" instead." +msgstr "" +"Bu komut önerilmiyor. Lütfen bunun yerine \"floating ip show\"ı kullanın." + +msgid "" +"This command has been deprecated. Please use \"server add fixed ip\" instead." +msgstr "" +"Bu komut kullanımdan kaldırıldı. Onun yerine lütfen \"server add fixed ip\" " +"komutunu kullanın." + +msgid "" +"This command has been deprecated. Please use \"server add floating ip\" " +"instead." +msgstr "" +"Bu komut kullanımdan kaldırılmıştır. Onun yerine lütfen \"server add " +"floating ip\" kullanın." + +msgid "" +"This command has been deprecated. Please use \"server remove fixed ip\" " +"instead." +msgstr "" +"Bu komut kullanımdan kaldırıldı. Lütfen onun yerine \"server remove fixed ip" +"\" komutunu kullanın." + +msgid "" +"This command has been deprecated. Please use \"server remove floating ip\" " +"instead." +msgstr "" +"Bu komut kullanımdan kaldırıldı. Onun yerine lütfen \"server remove floating " +"ip\" komutunu kullanın." + +msgid "" +"This command has been deprecated. Please use \"volume backup create\" " +"instead." +msgstr "" +"Bu komut kullanımdan kaldırıldı. Onun yerine lütfen \"volume backup create\" " +"komutunu kullanın." + +msgid "" +"This command has been deprecated. Please use \"volume backup delete\" " +"instead." +msgstr "" +"Bu komut kullanımdan kaldırıldı. Lütfen onun yerine \"volume backup delete\" " +"kullanın." + +msgid "" +"This command has been deprecated. Please use \"volume backup list\" instead." +msgstr "" +"Bu komut kullanımdan kaldırıldı. Lütfen onun yerine \"volume backup list\" " +"komutunu kullanın." + +msgid "" +"This command has been deprecated. Please use \"volume backup restore\" " +"instead." +msgstr "" +"Bu komut kullanımdan kaldırıldı. Lütfen onun yerine \"volume backup restore" +"\" komutunu kullanın." + +msgid "" +"This command has been deprecated. Please use \"volume backup show\" instead." +msgstr "" +"Bu komut kullanımdan kaldırıldı. Lütfen onun yerine \"volume backup show\" " +"komutunu kullanın." + +msgid "" +"This command has been deprecated. Please use \"volume snapshot create\" " +"instead." +msgstr "" +"Bu komut kullanımdan kaldırıldı. Lütfen onun yerine \"volume snapshot create" +"\" komutunu kullanın." + +msgid "" +"This command has been deprecated. Please use \"volume snapshot delete\" " +"instead." +msgstr "" +"Bu komut kullanımdan kaldırıldı. Lütfen onun yerine \"volume snapshot delete" +"\" komutunu kullanın." + +msgid "" +"This command has been deprecated. Please use \"volume snapshot list\" " +"instead." +msgstr "" +"Bu komut kullanımdan kaldırıldı. Onun yerine lütfen \"volume snapshot list\" " +"komutunu kullanın." + +msgid "" +"This command has been deprecated. Please use \"volume snapshot set\" instead." +msgstr "" +"Bu komut kullanımdan kaldırıldı. Lütfen bunun yerine \"volume snapshot set\" " +"komutunu kullanın." + +msgid "" +"This command has been deprecated. Please use \"volume snapshot show\" " +"instead." +msgstr "" +"Bu komut kullanımdan kaldırıldı. Lütfen onun yerine \"volume snapshot show\" " +"komutunu kullan." + +msgid "" +"This command has been deprecated. Please use \"volume snapshot unset\" " +"instead." +msgstr "" +"Bu komut kullanımdan kaldırıldı. Lütfen onun yerine \"volume snapshot unset" +"\" komutunu kullanın." + +msgid "Token to be deleted" +msgstr "Silinecek jeton" + +msgid "" +"Tokens generated from the trust will represent (defaults to False)" +msgstr "" +"Güvenden oluşturulan jeton 'ı temsil edecek (varsayılan False)" + +msgid "Trust to display" +msgstr "Gösterilecek güven" + +msgid "Trust(s) to delete" +msgstr "Silinecek güvenler" + +msgid "Type of OS" +msgstr "OS türü" + +msgid "Type of architecture" +msgstr "Mimari türü" + +msgid "Type of hypervisor" +msgstr "Arakatman türü" + +msgid "" +"Type of the object that RBAC policy affects (\"qos_policy\" or \"network\")" +msgstr "" +"RBAC politikasını etkileyen nesne türü (\"qos_policy\" veya \"network\")" + +msgid "URL" +msgstr "URL" + +msgid "URL of the agent" +msgstr "Ajanın URL'i" + +msgid "Unique flavor ID; 'auto' creates a UUID (default: auto)" +msgstr "Benzersiz flavor ID'si; 'auto' bir UUID oluşturur (varsayılan: auto)" + +msgid "Unlock server(s)" +msgstr "Sunucu(ların/nun) kilidini kaldır" + +msgid "Unpause server(s)" +msgstr "Sunucu(ları/yu) devam ettir" + +msgid "Unset QoS specification properties" +msgstr "QoS belirtimi özellikleri ayarlarını kaldır" + +msgid "Unset a project property (repeat option to unset multiple properties)" +msgstr "" +"Proje özelliğini kaldır (birden fazla özellik kaldırmak için seçeneği " +"tekrarla)" + +msgid "" +"Unset a property on this image (repeat option to set multiple properties)" +msgstr "" +"Bu imajdaki bir özelliği kaldır (birden fazla özelliği ayarlamak için " +"seçeneği tekrarla)" + +msgid "Unset a tag on this image (repeat option to set multiple tags)" +msgstr "" +"Bu imajdan etiketi kaldır (birden fazla etiket ayarlamak için seçeneği " +"tekrarla)" + +msgid "Unset account properties" +msgstr "Hesap özelliklerinin ayarını kaldır" + +msgid "Unset aggregate properties" +msgstr "Küme özelliklerini kaldır" + +msgid "Unset container properties" +msgstr "Kap özelliklerinin ayarını kaldır" + +msgid "Unset flavor properties" +msgstr "Flavor özelliklerini geri al" + +msgid "Unset floating IP Properties" +msgstr "Yüzer IP Özelliklerini Kaldır" + +msgid "Unset image tags and properties" +msgstr "İmaj etiketlerinin ve özelliklerinin ayarını kaldır" + +msgid "Unset network properties" +msgstr "Ağ özelliklerinin ayarını kaldır" + +msgid "Unset object properties" +msgstr "Nesne özelliklerinin ayarını kaldır" + +msgid "Unset port properties" +msgstr "Bağlantı noktası özelliklerinin ayarını kaldır" + +msgid "Unset project properties" +msgstr "Proje özelliklerini kaldır" + +msgid "Unset router properties" +msgstr "Yönlendirici özelliklerini kaldır" + +msgid "Unset server properties" +msgstr "Sunucu özelliklerini kaldır" + +msgid "Unset snapshot properties" +msgstr "Anlık görüntü özelliklerinin ayarını kaldır" + +msgid "Unset subnet pool properties" +msgstr "Altağ havuz özelliklerinin ayarlarını kaldır" + +msgid "Unset subnet properties" +msgstr "Altağ özelliklerinin ayarını kaldır" + +msgid "Unset volume properties" +msgstr "Disk bölümü özellikleri ayarlarını kaldır" + +msgid "Unset volume snapshot properties" +msgstr "Disk bölümü anlık görüntü özelliklerinin ayarını kaldır" + +msgid "Unset volume type properties" +msgstr "Disk bölümü türü özelliklerinin ayarlarını kaldır" + +msgid "Unshelve server(s)" +msgstr "Sunucuları al" + +msgid "Updated name of the subnet" +msgstr "Alt ağın güncellenmiş ismi" + +msgid "" +"Upload a file and rename it. Can only be used when uploading a single object" +msgstr "" +"Bir dosya yükle ve yeniden adlandır. Yalnızca tek bir nesne yüklerken " +"kullanılabilir" + +msgid "Upload image from local file" +msgstr "Yerel dosyadan imajı yükle" + +msgid "Upload image to this store" +msgstr "Bu depoya imaj yükle" + +msgid "Upload object to container" +msgstr "Kabı nesneye yükle" + +msgid "Uploading data and using container are not allowed at the same time" +msgstr "Aynı anda veri yüklemek ve kap kullanmaya izin verilmiyor." + +#, python-format +msgid "Usage from %(start)s to %(end)s on project %(project)s: \n" +msgstr "%(start)s'dan %(end)s'e %(project)s projesindeki kullanım:\n" + +#, python-format +msgid "Usage from %(start)s to %(end)s: \n" +msgstr "%(start)s'dan %(end)s'e kullanım: \n" + +msgid "Usage range end date, ex 2012-01-20 (default: tomorrow)" +msgstr "Kullanım aralığı bitiş tarihi, ex 2012-01-20 (varsayılan: yarın)" + +msgid "Usage range start date, ex 2012-01-20 (default: 4 weeks ago)" +msgstr "" +"Kullanım aralığı başlangıç tarihi, ex 2012-01-20 (varsayılan: 4 hafta önce)" + +msgid "Use --stdin to enable read image data from standard input" +msgstr "Standart girdiden imaj verilerini okuyabilmek için --stdin kullanın" + +msgid "Use as source of volume (name or ID)" +msgstr "Disk bölümünün kaynağı olarak kullan (isim veya ID)" + +msgid "Use as source of volume (name or ID)" +msgstr "" +" anlık görüntüsünü disk bölümü kaynağı olarak kullan (isim veya ID)" + +msgid "Use default subnet pool for --ip-version" +msgstr "--ip-version için varsayılan altağ havuzunu kullan" + +msgid "Use only IPv4 addresses" +msgstr "Sadece IPv4 adresleri kullan" + +msgid "Use only IPv6 addresses" +msgstr "Sadece IPv6 adresleri kullan" + +msgid "Use other IP address (public, private, etc)" +msgstr "Diğer IP adresi kullan (genel, gizli, vs.)" + +msgid "Use private IP address" +msgstr "Gizli IP adresi kullan" + +msgid "Use public IP address" +msgstr "Genel IP adresi kullan" + +msgid "" +"Use specified volume as the config drive, or 'True' to use an ephemeral drive" +msgstr "" +"Belirtilen birimi yapılandırma sürücüsü olarak kullanın veya kısa ömürlü bir " +"sürücüyü kullanmak için 'True' kullanın" + +msgid "" +"Used to populate the backup_type property of the backup image (default: " +"empty)" +msgstr "" +"Yedek imajın backup_type özelliğini doldurmak için kullanılır (varsayılan: " +"boş)" + +msgid "User data file to serve from the metadata server" +msgstr "Üst veri sunucusundan sunmak için kullanıcı veri dosyası" + +msgid "User description" +msgstr "Kullanıcı tanımı" + +msgid "User must be specified" +msgstr "Kullanıcı belirtilmeli" + +msgid "User that is assuming authorization (name or ID)" +msgstr "Yetki varsayan kullanıcı (adı veya kimliği)" + +msgid "User that is delegating authorization (name or ID)" +msgstr "Yetkilendirme yetkisini devreden kullanıcı (isim veya ID)" + +msgid "User that owns the credential (name or ID)" +msgstr "Kimlik bilgilerine sahip kullanıcı (isim veya ID)" + +msgid "User to check (name or ID)" +msgstr "Kontrol edilecek kullanıcı (isim veya ID)" + +msgid "User to display (name or ID)" +msgstr "Gösterilecek kullanıcı (isim veya ID)" + +msgid "User to filter (name or ID)" +msgstr "Filtrelenecek kullanıcı (isim veya ID)" + +msgid "User to list (name or ID)" +msgstr "Listelenecek kullanıcı (isim veya ID)" + +msgid "User to modify (name or ID)" +msgstr "Düzenlenecek kullanıcı (isim veya ID)" + +msgid "" +"User(s) to add to (name or ID) (repeat option to add multiple users)" +msgstr "" +"'a eklenecek kullanıcı(lar) (isim veya ID) (birden fazla kullanıcı " +"eklemek için seçeneği tekrarlayın)" + +msgid "User(s) to delete (name or ID)" +msgstr "Silinecek kullanıcı(lar) (isim veya ID)" + +msgid "" +"User(s) to remove from (name or ID) (repeat option to remove " +"multiple users)" +msgstr "" +"'dan kaldırılacak kullanıcı(lar) (isim veya ID) (birden fazla " +"kullanıcı kaldırmak için seçeneği tekrarlayın)" + +msgid "VLAN ID for VLAN networks or Tunnel ID for GENEVE/GRE/VXLAN networks" +msgstr "VLAN ağları için VLAN ID veya GENEVA/GRE/VXLAN ağları için Tünel ID" + +msgid "" +"VNIC type for this port (direct | direct-physical | macvtap | normal | " +"baremetal | virtio-forwarder, default: normal)" +msgstr "" +"Bu bağlantı noktası için VNIC türü (direct | direct-physical | macvtap | " +"normal | baremetal | virtio-forwarder, varsayılan: normal)" + +msgid "" +"Validate the requirements for auto allocated topology. Does not return a " +"topology." +msgstr "" +"Otomatik ayrılan topoloji gereksinimlerini doğrulayın. Bir topoloji " +"döndürmez." + +msgid "Verifier associated with (required)" +msgstr " ile ilişkili doğrulayıcı (gerekli)" + +msgid "Version" +msgstr "Sürüm" + +msgid "Version of the agent" +msgstr "Ajanın sürümü" + +#, python-format +msgid "Volume API version, default=%s (Env: OS_VOLUME_API_VERSION)" +msgstr "Disk bölümü API sürümü, varsayılan=%s (Env: OS_VOLUME_API_VERSION)" + +msgid "Volume description" +msgstr "Disk bölümü açıklaması" + +#, python-format +msgid "Volume is in %s state, it must be available before size can be extended" +msgstr "" +"Disk bölümü %s durumunda, boyut genişletilmeden önce kullanılabilir olmak " +"zorunda." + +msgid "Volume name" +msgstr "Disk bölümü ismi" + +msgid "" +"Volume or snapshot (name or ID) must be specified if --block-device-mapping " +"is specified" +msgstr "" +"Disk bölümü veya anlık görüntü (isim veya ID), eğer --block-device-mapping " +"belirtildiyse, belirtilmek zorundadır." + +msgid "Volume size in GB (Required unless --snapshot or --source is specified)" +msgstr "" +"GB cinsinden disk bölümü boyutu (--snapshot veya --source belirtilmezse " +"gereklidir)" + +msgid "" +"Volume size in GB (Required unless --snapshot or --source or --source-" +"replicated is specified)" +msgstr "" +"GB cinsinden disk bölümü boyutu (--snapshot veya --source veya --source-" +"replicated belirtilmedikçe gereklidir)" + +msgid "Volume to add (name or ID)" +msgstr "Eklenecek disk bölümü (isim veya ID)" + +msgid "Volume to backup (name or ID)" +msgstr "Yedeklenecek disk bölümü (isim veya ID)" + +msgid "Volume to clone (name or ID)" +msgstr "Kopyalanacak disk bölümü (isim veya ID)" + +msgid "Volume to display (name or ID)" +msgstr "Gösterilecek disk bölümü (isim veya ID)" + +msgid "Volume to migrate (name or ID)" +msgstr "Göç ettirilecek disk bölümü (isim veya ID)" + +msgid "Volume to modify (name or ID)" +msgstr "Düzenlenecek disk bölümü (isim veya ID)" + +msgid "Volume to remove (name or ID)" +msgstr "Kaldırılacak disk bölümü (isim veya ID)" + +msgid "Volume to restore to (name or ID)" +msgstr "Geri yüklenecek disk bölümü (isim veya ID)" + +msgid "Volume to snapshot (name or ID)" +msgstr "Anlık görüntüsü oluşturulacak disk bölümü (isim veya ID)" + +msgid "Volume to snapshot (name or ID) (default is )" +msgstr "" +"Anlık görüntüsü alınacak disk bölümü (isim veya ID) (varsayılan )" + +msgid "Volume to transfer (name or ID)" +msgstr "Aktarılacak disk bölümü (isim veya ID)" + +msgid "Volume transfer request authentication key" +msgstr "Disk bölümü aktarım isteği yetkilendirme anahtarı" + +msgid "Volume transfer request to accept (ID only)" +msgstr "Kabul edilecek disk bölümü aktarım isteği (sadece ID)" + +msgid "Volume transfer request to display (name or ID)" +msgstr "Gösterilecek disk bölümü aktarım isteği (isim veya ID)" + +msgid "Volume transfer request(s) to delete (name or ID)" +msgstr "Silinecek disk bölümü aktarım isteği (isim veya ID)" + +msgid "Volume type description" +msgstr "Disk bölümü türü açıklaması" + +msgid "Volume type is accessible to the public" +msgstr "Disk bölümü türü genel olarak erişilebilir" + +msgid "Volume type is not accessible to the public" +msgstr "Disk bölümü türü genele açık değil" + +msgid "Volume type name" +msgstr "Disk bölümü tür ismi" + +msgid "Volume type of this consistency group (name or ID)" +msgstr "Bu tutarlılık grubunun disk bölümü türü (isim veya ID)" + +msgid "Volume type to associate the QoS (name or ID)" +msgstr "QoS ile ilişkili disk bölümü türü (isim veya ID)" + +msgid "Volume type to disassociate the QoS from (name or ID)" +msgstr "QoS'in ayrılacağı disk bölümü (isim veya ID)" + +msgid "Volume type to display (name or ID)" +msgstr "Gösterilecek disk bölümü türü (isim veya ID)" + +msgid "Volume type to modify (name or ID)" +msgstr "Düzenlenecek disk bölümü türü (isim veya ID)" + +msgid "Volume type(s) to delete (name or ID)" +msgstr "Disk bölümü türlerini sil" + +msgid "" +"Volume(s) to add to (name or ID) (repeat option to add " +"multiple volumes)" +msgstr "" +"'a eklenecek disk bölümleri (isim veya ID) (birden fazla " +"disk bölümü eklemek için seçeneği tekrarlayın)" + +msgid "Volume(s) to delete (name or ID)" +msgstr "Silinecek disk bölümleri (isim veya ID)" + +msgid "" +"Volume(s) to remove from (name or ID) (repeat option to " +"remove multiple volumes)" +msgstr "" +"'dan silinecek disk bölümleri (isim veya ID) (birden " +"fazla disk bölümü silmek için seçeneği tekrarlayın)" + +msgid "Wait for backup image create to complete" +msgstr "Yedek imaj oluşturmanın tamamlanması için bekleyin" + +msgid "Wait for build to complete" +msgstr "Yapılandırmanın tamamlanmasını bekle" + +msgid "Wait for delete to complete" +msgstr "Silme işleminin bitmesini bekle" + +msgid "Wait for migrate to complete" +msgstr "Göçün tamamlanması bekleniyor" + +msgid "Wait for operation to complete" +msgstr "İşlemin tamamlanmasını bekleyin" + +msgid "Wait for reboot to complete" +msgstr "Yeniden başlatmanın bitmesini bekleyin" + +msgid "Wait for rebuild to complete" +msgstr "Yeniden yapılandırmanın tamamlanmasını bekle" + +msgid "Wait for resize to complete" +msgstr "Yeniden boyutlandırmanın tamamlanmasını bekleyin" + +msgid "" +"You must specify '--external-gateway' in orderto update the SNAT or fixed-ip " +"values" +msgstr "" +"SNAT veya sabit ip değerlerini güncellemek için '--external-gateway' " +"belirtmelisiniz" + +msgid "argument --auth-key is required" +msgstr "--auth-key argümanı zorunludur" + +msgid "" +"can't create server with port specified since network endpoint not enabled" +msgstr "" +"ağ uç noktası etkin olmadığı için belirtilen bağlantı noktası ile sunucu " +"oluşturulamadı" + +msgid "either network or port should be specified but not both" +msgstr "ya ağ ya da bağlantı noktası belirtilmelidir ama ikisi bir arada değil" + +msgid "many found" +msgstr "çok bulundu" + +msgid "max instances should be > 0" +msgstr "en büyük sunucu sayısı > 0 olmalıdır" + +msgid "min instances should be <= max instances" +msgstr "en küçük sunucu sayısı <= en büyük sunucu sayısı olmalıdır" + +msgid "min instances should be > 0" +msgstr "en küçük sunucu sayısı > 0 olmalıdır" + +msgid "network" +msgstr "ağ" + +msgid "networks" +msgstr "ağlar" + +msgid "none found" +msgstr "hiç bulunamadı" + +msgid "port" +msgstr "bağlantı noktası" + +msgid "ports" +msgstr "bağlantı noktaları" + +#, python-format +msgid "property unset failed, '%s' is a nonexistent property " +msgstr "özellik kaldırma başarısız, '%s' diye bir özellik yok " + +msgid "router" +msgstr "yönlendirici" + +msgid "routers" +msgstr "yönlendiriciler" + +msgid "server group to display (name or ID)" +msgstr "gösterilecek sunucu grupları (isim veya ID)" + +msgid "server group(s) to delete (name or ID)" +msgstr "silinecek sunucu grupları (isim veya ID)" + +#, python-format +msgid "service %s not found\n" +msgstr "%s servisi bulunamadı\n" + +msgid "subnet" +msgstr "alt ağ" + +msgid "subnet pool" +msgstr "altağ havuzu" + +msgid "subnet pools" +msgstr "altağ havuzları" + +msgid "subnets" +msgstr "altağlar" + +#, python-format +msgid "tag unset failed, '%s' is a nonexistent tag " +msgstr "etiket kaldırma başarısız, '%s' diye bir etiket yok " + +msgid "user that owns the credential (name or ID)" +msgstr "Kimlik bilgilerinin sahibi kullanıcı (isim veya ID)" + +#, python-format +msgid "versions supported by client: %(min)s - %(max)s" +msgstr "istemci tarafından desteklenen sürümler: %(min)s - %(max)s" diff --git a/openstackclient/locale/zh_TW/LC_MESSAGES/openstackclient.po b/openstackclient/locale/zh_TW/LC_MESSAGES/openstackclient.po deleted file mode 100644 index d9bbf6fe7f..0000000000 --- a/openstackclient/locale/zh_TW/LC_MESSAGES/openstackclient.po +++ /dev/null @@ -1,423 +0,0 @@ -# Translations template for python-openstackclient. -# Copyright (C) 2015 ORGANIZATION -# This file is distributed under the same license as the -# python-openstackclient project. -# -# Translators: -# Andreas Jaeger , 2016. #zanata -msgid "" -msgstr "" -"Project-Id-Version: python-openstackclient 2.5.1.dev51\n" -"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2016-06-03 19:37+0000\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2015-06-14 06:41+0000\n" -"Last-Translator: openstackjenkins \n" -"Language: zh-TW\n" -"Plural-Forms: nplurals=1; plural=0;\n" -"Generated-By: Babel 2.0\n" -"X-Generator: Zanata 3.7.3\n" -"Language-Team: Chinese (Taiwan)\n" - -msgid "Add a property to (repeat option to set multiple properties)" -msgstr "加入屬性到 (重復這選項來設定多個屬性)" - -msgid "Allow disk over-commit on the destination host" -msgstr "允許目標主機過量使用" - -msgid "Complete\n" -msgstr "完成\n" - -msgid "Confirm server resize is complete" -msgstr "確認調整雲實例容量完成" - -msgid "Credentials access key" -msgstr "憑鑰存取密鑰" - -msgid "Default project (name or ID)" -msgstr "預設專案(名稱或識別號)" - -msgid "Destination port (ssh -p option)" -msgstr "目標埠口(ssh -p 選項)" - -msgid "Disable project" -msgstr "關閉專案" - -msgid "Disable user" -msgstr "關閉用戶" - -msgid "Display server diagnostics information" -msgstr "顯示雲實例診斷資訊" - -msgid "Do not over-commit disk on the destination host (default)" -msgstr "不準目標主機過量使用(預設)" - -msgid "Enable project" -msgstr "啟用專案" - -msgid "Enable project (default)" -msgstr "啟用專案(預設)" - -msgid "Enable user (default)" -msgstr "啟用用戶(預設)" - -msgid "Endpoint ID to delete" -msgstr "要刪除的端點識別號" - -#, python-format -msgid "Error creating server: %s" -msgstr "新增雲實例時出錯:%s" - -msgid "Error retrieving diagnostics data" -msgstr "獲得診斷資料時出錯" - -msgid "Filter by parent region ID" -msgstr "以父地區識別號來篩選" - -msgid "Filter users by (name or ID)" -msgstr "以 來篩選用戶(名稱或識別號)" - -msgid "Filter users by project (name or ID)" -msgstr "以專案篩選用戶(名稱或識別號)" - -msgid "Hints for the scheduler (optional extension)" -msgstr "給排程器的提示(選用)" - -msgid "Include (name or ID)" -msgstr "包括 (名稱或識別號)" - -msgid "Include (name or ID)" -msgstr "包括 (名稱或識別號)" - -msgid "Include all projects (admin only)" -msgstr "包括所有的專案(管理員專用)" - -msgid "Keypair to inject into this server (optional extension)" -msgstr "要注入到此伺服器的密鑰對(選用)" - -msgid "List additional fields in output" -msgstr "列出額外的欄位" - -msgid "Login name (ssh -l option)" -msgstr "登入名稱(ssh -l 選項)" - -msgid "" -"Map block devices; map is ::: " -"(optional extension)" -msgstr "" -"映射區塊裝置;映射是 :::(選用)" - -msgid "Maximum number of servers to launch (default=1)" -msgstr "最多要發動的雲實例(預設為 1)" - -msgid "Minimum number of servers to launch (default=1)" -msgstr "最少要發動的雲實例(預設為 1)" - -msgid "Name of new image (default is server name)" -msgstr "新映像檔的名稱(預設為雲實例名稱)" - -msgid "Name or ID of security group to remove from server" -msgstr "要從雲實例移除的安全性群組名稱或識別號" - -msgid "Name or ID of server to use" -msgstr "要用的雲實例名稱或識別號" - -msgid "New endpoint admin URL" -msgstr "新端點管理員網址" - -msgid "New endpoint internal URL" -msgstr "新端點內部網址" - -msgid "New endpoint public URL (required)" -msgstr "新端點公開網址(需要)" - -msgid "New endpoint region ID" -msgstr "新端點地區識別號" - -msgid "New endpoint service (name or ID)" -msgstr "新端點伺服器(名稱或識別號)" - -msgid "New parent region ID" -msgstr "新父地區識別號" - -msgid "New password: " -msgstr "新密碼:" - -msgid "New project name" -msgstr "新專案名稱" - -msgid "New region ID" -msgstr "新地區識別號" - -msgid "New region description" -msgstr "新地區描述" - -msgid "New role name" -msgstr "新角色名稱" - -msgid "New server name" -msgstr "新雲實例名稱" - -msgid "New service description" -msgstr "新伺服器描述" - -msgid "New service name" -msgstr "新伺服器名稱" - -msgid "New service type (compute, image, identity, volume, etc)" -msgstr "新伺服器類型(compute、image、identity 或 volume 等等)" - -msgid "New user name" -msgstr "新用戶名稱" - -#, python-format -msgid "No service catalog with a type, name or ID of '%s' exists." -msgstr "沒有相似「%s」類型、名稱或識別號的伺服器分類。" - -msgid "Only return instances that match the reservation" -msgstr "只回傳要保留的雲實例" - -msgid "Options in ssh_config(5) format (ssh -o option)" -msgstr "ssh_config(5) 格式的選項(ssh -o 選項)" - -msgid "Parent region ID" -msgstr "父地區識別號" - -msgid "Passwords do not match, password unchanged" -msgstr "密碼不一樣,未更換密碼" - -msgid "Perform a block live migration" -msgstr "覆行區塊的即時轉移" - -msgid "Perform a hard reboot" -msgstr "履行強制重開機" - -msgid "Perform a shared live migration (default)" -msgstr "覆行已分享的即時轉移(預設)" - -msgid "Perform a soft reboot" -msgstr "履行重開機" - -msgid "Private key file (ssh -i option)" -msgstr "私鑰檔案(ssh -i 選項)" - -msgid "Project description" -msgstr "專案描述" - -msgid "Project must be specified" -msgstr "必須指定專案" - -msgid "Project to display (name or ID)" -msgstr "要顯示的專案(名稱或識別號)" - -msgid "Project to modify (name or ID)" -msgstr "要更改的專案(名稱或識別號)" - -msgid "Project(s) to delete (name or ID)" -msgstr "要刪除的專案(名稱或識別號)" - -msgid "Prompt interactively for password" -msgstr "為密碼互動提示" - -msgid "" -"Property to add/change for this server (repeat option to set multiple " -"properties)" -msgstr "要加入這個雲實例的屬性(重復這選項來設定多個屬性)" - -msgid "Region ID to delete" -msgstr "要刪除的地區識別號" - -msgid "Region to display" -msgstr "要顯示的地區" - -msgid "Region to modify" -msgstr "要更改的地區" - -msgid "Regular expression to match IP addresses" -msgstr "以正規式來匹配 IP 位址" - -msgid "Regular expression to match IPv6 addresses" -msgstr "以正規式來匹配 IPv6 位址" - -msgid "Regular expression to match instance name (admin only)" -msgstr "以正規式來匹配雲實例名稱(管理員專用)" - -msgid "Regular expression to match names" -msgstr "以正規式來匹配名稱" - -msgid "Resize server to specified flavor" -msgstr "調整雲實例容量來符合指定的虛擬硬體樣板" - -msgid "Restore server state before resize" -msgstr "恢復雲實例狀態到未調整容量前" - -msgid "Return existing domain" -msgstr "回傳存在的地域" - -msgid "Return existing group" -msgstr "回傳存在的群組" - -msgid "Return existing project" -msgstr "回傳存在的專案" - -msgid "Return existing role" -msgstr "回傳存在的角色" - -msgid "Return existing user" -msgstr "回傳存在的用戶" - -msgid "Retype new password: " -msgstr "重新輸入新密碼:" - -msgid "Role to add to : (name or ID)" -msgstr "加入角色到 :(名稱或識別號)" - -msgid "Role to display (name or ID)" -msgstr "要顯示的角色(名稱或識別號)" - -msgid "Role to remove (name or ID)" -msgstr "要移除的角色(名稱或識別號)" - -msgid "Role(s) to delete (name or ID)" -msgstr "要刪除的角色(名稱或識別號)" - -msgid "Search by hostname" -msgstr "以主機名稱來尋找" - -msgid "Search by server status" -msgstr "以雲實例狀態來尋找" - -msgid "Security group to add (name or ID)" -msgstr "要加入的安全性群組(名稱或識別號)" - -msgid "Select an availability zone for the server" -msgstr "為雲實例選擇可用的區域。" - -msgid "Server (name or ID)" -msgstr "伺服器(名稱或識別號)" - -msgid "Server internal device name for volume" -msgstr "雲硬碟在雲實例內的裝置名稱" - -msgid "Server(s) to delete (name or ID)" -msgstr "要刪除的雲實例(名稱或識別號)" - -msgid "Service to delete (name or ID)" -msgstr "要刪除的伺服器(名稱或識別號)" - -msgid "Service to display (type or name)" -msgstr "要顯示的伺服器(類型或名稱)" - -msgid "Service to display (type, name or ID)" -msgstr "要顯示的伺服器(類型、名稱或識別號)" - -msgid "Set a project property (repeat option to set multiple properties)" -msgstr "設定專案屬性(重復這選項來設定多個屬性)" - -msgid "Set default project (name or ID)" -msgstr "設定預設專案(名稱或識別號)" - -msgid "Set new root password (interactive only)" -msgstr "設定新密碼(只限互動)" - -msgid "Set project description" -msgstr "設定專案描述" - -msgid "Set project name" -msgstr "設定專案名稱" - -msgid "Set user email address" -msgstr "設定用戶電子信箱位址" - -msgid "Set user name" -msgstr "設定用戶名稱" - -msgid "Set user password" -msgstr "設定用戶密碼" - -msgid "Show service catalog information" -msgstr "顯示伺服器分類資訊" - -msgid "Target hostname" -msgstr "目標主機名稱" - -msgid "" -"The argument --type is deprecated, use service create --name " -"type instead." -msgstr "" -"已經淘汰 --type 參數,請用 service create --name 來代替。" - -msgid "Token to be deleted" -msgstr "要刪除的記號" - -msgid "Use only IPv4 addresses" -msgstr "只使用 IPv4 位址" - -msgid "Use only IPv6 addresses" -msgstr "只使用 IPv6 位址" - -msgid "Use other IP address (public, private, etc)" -msgstr "使用其他 IP 位址(公開、私人等等)" - -msgid "Use private IP address" -msgstr "使用私人 IP 位址" - -msgid "Use public IP address" -msgstr "使用公開 IP 位址" - -msgid "" -"Use specified volume as the config drive, or 'True' to use an ephemeral drive" -msgstr "使用指定的雲硬碟為設定檔硬碟,或「True」來使用暫時性硬碟" - -msgid "User data file to serve from the metadata server" -msgstr "來自詮釋資料伺服器要服務的用戶資料檔案" - -msgid "User must be specified" -msgstr "必須指定用戶" - -msgid "User to change (name or ID)" -msgstr "要更換的用戶(名稱或識別號)" - -msgid "User to display (name or ID)" -msgstr "要顯示的用戶(名稱或識別號)" - -msgid "User to list (name or ID)" -msgstr "要列出的用戶(名稱或識別號)" - -msgid "User(s) to delete (name or ID)" -msgstr "要刪除的用戶(名稱或識別號)" - -msgid "Volume to add (name or ID)" -msgstr "要加入的雲硬碟(名稱或識別號)" - -msgid "Volume to remove (name or ID)" -msgstr "要移除的雲硬碟(名稱或識別號)" - -msgid "Wait for build to complete" -msgstr "等待完成建立" - -msgid "Wait for image create to complete" -msgstr "等待映像檔新增完成" - -msgid "Wait for reboot to complete" -msgstr "等待重開機完成" - -msgid "Wait for rebuild to complete" -msgstr "等待重建完成" - -msgid "Wait for resize to complete" -msgstr "等待調整容量完成" - -msgid "either net-id or port-id should be specified but not both" -msgstr "任選網路識別號或接口識別號,但不能兩者都指定" - -msgid "max instances should be > 0" -msgstr "雲實例發動的最大值要大於 0" - -msgid "min instances should be <= max instances" -msgstr "雲實例發動的最少值不應大於最大值" - -msgid "min instances should be > 0" -msgstr "雲實例發動的最少值要大於 0" From 04ef8a41acbd45ef03253240934fa07ec170d7f4 Mon Sep 17 00:00:00 2001 From: Reedip Date: Tue, 8 Aug 2017 09:23:44 +0530 Subject: [PATCH 1765/3095] Allow PD as Subnetpool during Subnet creations This commit now allows user to specify 'prefix_delegation' as a Subnetpool during Subnet creation by using the new --use-prefix-delegation option so that the IPv6 prefixes can be delegated to routers set up by the cloud admins. Change-Id: I67e5d81c4155db2e3c5c41ee1df77f2d77a17689 Closes-Bug: #1513894 --- doc/source/cli/command-objects/subnet.rst | 7 ++++++- openstackclient/network/v2/subnet.py | 7 +++++++ releasenotes/notes/bug-1513894-6d2f05db6e1df744.yaml | 6 ++++++ 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/bug-1513894-6d2f05db6e1df744.yaml diff --git a/doc/source/cli/command-objects/subnet.rst b/doc/source/cli/command-objects/subnet.rst index c228dc207d..0a56ccf18b 100644 --- a/doc/source/cli/command-objects/subnet.rst +++ b/doc/source/cli/command-objects/subnet.rst @@ -18,7 +18,7 @@ Create new subnet openstack subnet create [--project [--project-domain ]] - [--subnet-pool | --use-default-subnet-pool [--prefix-length ]] + [--subnet-pool | --use-default-subnet-pool [--prefix-length ] | --use-prefix-delegation] [--subnet-range ] [--allocation-pool start=,end=] [--dhcp | --no-dhcp] @@ -48,6 +48,11 @@ Create new subnet Subnet pool from which this subnet will obtain a CIDR (name or ID) +.. option:: --use-prefix-delegation + + Use 'prefix-delegation' if IP is IPv6 format and IP would be delegated + externally + .. option:: --use-default-subnet-pool Use default subnet pool for :option:`--ip-version` diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index b96dff7f94..c71358a52f 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -186,6 +186,8 @@ def _get_attrs(client_manager, parsed_args, is_create=True): subnet_pool = client.find_subnet_pool(parsed_args.subnet_pool, ignore_missing=False) attrs['subnetpool_id'] = subnet_pool.id + if parsed_args.use_prefix_delegation: + attrs['subnetpool_id'] = "prefix_delegation" if parsed_args.use_default_subnet_pool: attrs['use_default_subnet_pool'] = True if parsed_args.prefix_length is not None: @@ -260,6 +262,11 @@ def get_parser(self, prog_name): help=_("Subnet pool from which this subnet will obtain a CIDR " "(Name or ID)") ) + subnet_pool_group.add_argument( + '--use-prefix-delegation', + help=_("Use 'prefix-delegation' if IP is IPv6 format " + "and IP would be delegated externally") + ) subnet_pool_group.add_argument( '--use-default-subnet-pool', action='store_true', diff --git a/releasenotes/notes/bug-1513894-6d2f05db6e1df744.yaml b/releasenotes/notes/bug-1513894-6d2f05db6e1df744.yaml new file mode 100644 index 0000000000..a7d750557c --- /dev/null +++ b/releasenotes/notes/bug-1513894-6d2f05db6e1df744.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--use-prefix-delegation`` option to the ``subnet create`` command to + specify 'Prefix Delegation' as a subnetpool when creating subnets. + [Bug `1513894 `_] From f3bbf52b3c7e731796aaf38294d08109859babe5 Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Sat, 15 Jul 2017 16:58:08 +0000 Subject: [PATCH 1766/3095] Use flake8-import-order plugin In reviews we usually check import grouping but it is boring. By using flake8-import-order plugin, we can avoid this. It enforces loose checking so it sounds good to use it. This flake8 plugin is already used in tempest. Note that flake8-import-order version is pinned to avoid unexpected breakage of pep8 job. Setup for unit tests of hacking rules is tweaked to disable flake8-import-order checks. This extension assumes an actual file exists and causes hacking rule unit tests. Change-Id: I12b596820727aeeb379bee16c2bc993dee9eb637 --- openstackclient/common/clientmanager.py | 2 +- openstackclient/compute/v2/server_event.py | 2 +- openstackclient/tests/functional/base.py | 2 +- openstackclient/tests/unit/common/test_logs.py | 1 + openstackclient/tests/unit/common/test_quota.py | 2 +- openstackclient/tests/unit/compute/v2/fakes.py | 3 ++- openstackclient/tests/unit/compute/v2/test_keypair.py | 4 ++-- openstackclient/tests/unit/compute/v2/test_server.py | 2 +- openstackclient/tests/unit/compute/v2/test_usage.py | 1 + openstackclient/tests/unit/fakes.py | 2 +- openstackclient/tests/unit/identity/v2_0/fakes.py | 2 +- .../tests/unit/identity/v2_0/test_role_assignment.py | 2 +- openstackclient/tests/unit/identity/v3/fakes.py | 2 +- .../tests/unit/identity/v3/test_identity_provider.py | 1 + openstackclient/tests/unit/identity/v3/test_mappings.py | 2 +- openstackclient/tests/unit/identity/v3/test_role.py | 2 +- .../tests/unit/identity/v3/test_role_assignment.py | 1 + openstackclient/tests/unit/identity/v3/test_trust.py | 2 +- openstackclient/tests/unit/identity/v3/test_user.py | 2 +- openstackclient/tests/unit/image/v1/fakes.py | 3 ++- openstackclient/tests/unit/image/v1/test_image.py | 2 +- openstackclient/tests/unit/image/v2/fakes.py | 2 +- openstackclient/tests/unit/image/v2/test_image.py | 2 +- openstackclient/tests/unit/integ/cli/test_shell.py | 2 +- openstackclient/tests/unit/network/test_common.py | 1 + openstackclient/tests/unit/network/v2/fakes.py | 3 ++- openstackclient/tests/unit/network/v2/test_network.py | 4 ++-- openstackclient/tests/unit/network/v2/test_port.py | 2 +- openstackclient/tests/unit/network/v2/test_subnet_pool.py | 2 +- openstackclient/tests/unit/object/v1/test_container.py | 1 + openstackclient/tests/unit/object/v1/test_object.py | 1 + openstackclient/tests/unit/test_shell.py | 2 +- openstackclient/tests/unit/utils.py | 3 ++- openstackclient/tests/unit/volume/v1/fakes.py | 3 ++- openstackclient/tests/unit/volume/v1/test_qos_specs.py | 2 +- openstackclient/tests/unit/volume/v1/test_volume.py | 2 +- openstackclient/tests/unit/volume/v2/fakes.py | 2 +- openstackclient/tests/unit/volume/v2/test_qos_specs.py | 2 +- openstackclient/tests/unit/volume/v2/test_snapshot.py | 2 +- openstackclient/tests/unit/volume/v2/test_volume.py | 2 +- test-requirements.txt | 1 + tox.ini | 1 + 42 files changed, 49 insertions(+), 35 deletions(-) diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index 67912f0cd0..ea581696c0 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -16,11 +16,11 @@ """Manage access to the clients, including authenticating when needed.""" import logging -import pkg_resources import sys from osc_lib import clientmanager from osc_lib import shell +import pkg_resources LOG = logging.getLogger(__name__) diff --git a/openstackclient/compute/v2/server_event.py b/openstackclient/compute/v2/server_event.py index ccb19ef722..d8fbda0f2d 100644 --- a/openstackclient/compute/v2/server_event.py +++ b/openstackclient/compute/v2/server_event.py @@ -16,10 +16,10 @@ """Compute v2 Server operation event implementations""" import logging -import six from osc_lib.command import command from osc_lib import utils +import six from openstackclient.i18n import _ diff --git a/openstackclient/tests/functional/base.py b/openstackclient/tests/functional/base.py index 4c88b13e93..90bbc24d29 100644 --- a/openstackclient/tests/functional/base.py +++ b/openstackclient/tests/functional/base.py @@ -14,10 +14,10 @@ import re import shlex import subprocess -import testtools from tempest.lib.cli import output_parser from tempest.lib import exceptions +import testtools COMMON_DIR = os.path.dirname(os.path.abspath(__file__)) diff --git a/openstackclient/tests/unit/common/test_logs.py b/openstackclient/tests/unit/common/test_logs.py index 4842c8d45c..b1e4d61242 100644 --- a/openstackclient/tests/unit/common/test_logs.py +++ b/openstackclient/tests/unit/common/test_logs.py @@ -15,6 +15,7 @@ # or Jun 2017. import logging + import mock from openstackclient.common import logs diff --git a/openstackclient/tests/unit/common/test_quota.py b/openstackclient/tests/unit/common/test_quota.py index 482653f460..1a3da31d78 100644 --- a/openstackclient/tests/unit/common/test_quota.py +++ b/openstackclient/tests/unit/common/test_quota.py @@ -11,8 +11,8 @@ # under the License. import copy -import mock +import mock from osc_lib import exceptions from openstackclient.common import quota diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index d5fc9fa957..0fae19af8f 100644 --- a/openstackclient/tests/unit/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -14,9 +14,10 @@ # import copy -import mock import uuid +import mock + from openstackclient.api import compute_v2 from openstackclient.tests.unit import fakes from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes diff --git a/openstackclient/tests/unit/compute/v2/test_keypair.py b/openstackclient/tests/unit/compute/v2/test_keypair.py index d6f5ecf457..0e5fb14324 100644 --- a/openstackclient/tests/unit/compute/v2/test_keypair.py +++ b/openstackclient/tests/unit/compute/v2/test_keypair.py @@ -13,10 +13,10 @@ # under the License. # -import mock -from mock import call import uuid +import mock +from mock import call from osc_lib import exceptions from osc_lib import utils diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 5b0d28a1c6..a1225c3004 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -15,9 +15,9 @@ import argparse import collections import getpass + import mock from mock import call - from osc_lib import exceptions from osc_lib import utils as common_utils from oslo_utils import timeutils diff --git a/openstackclient/tests/unit/compute/v2/test_usage.py b/openstackclient/tests/unit/compute/v2/test_usage.py index a383e9036d..a7aa1374e6 100644 --- a/openstackclient/tests/unit/compute/v2/test_usage.py +++ b/openstackclient/tests/unit/compute/v2/test_usage.py @@ -12,6 +12,7 @@ # import datetime + import mock from openstackclient.compute.v2 import usage diff --git a/openstackclient/tests/unit/fakes.py b/openstackclient/tests/unit/fakes.py index 999694b779..65c76b3e33 100644 --- a/openstackclient/tests/unit/fakes.py +++ b/openstackclient/tests/unit/fakes.py @@ -14,10 +14,10 @@ # import json -import mock import sys from keystoneauth1 import fixture +import mock import requests import six diff --git a/openstackclient/tests/unit/identity/v2_0/fakes.py b/openstackclient/tests/unit/identity/v2_0/fakes.py index 3d25cadfa6..5db9422275 100644 --- a/openstackclient/tests/unit/identity/v2_0/fakes.py +++ b/openstackclient/tests/unit/identity/v2_0/fakes.py @@ -14,11 +14,11 @@ # import copy -import mock import uuid from keystoneauth1 import access from keystoneauth1 import fixture +import mock from openstackclient.tests.unit import fakes from openstackclient.tests.unit import utils diff --git a/openstackclient/tests/unit/identity/v2_0/test_role_assignment.py b/openstackclient/tests/unit/identity/v2_0/test_role_assignment.py index 87643f128d..733fda6cb1 100644 --- a/openstackclient/tests/unit/identity/v2_0/test_role_assignment.py +++ b/openstackclient/tests/unit/identity/v2_0/test_role_assignment.py @@ -12,8 +12,8 @@ # import copy -import mock +import mock from osc_lib import exceptions from openstackclient.identity.v2_0 import role_assignment diff --git a/openstackclient/tests/unit/identity/v3/fakes.py b/openstackclient/tests/unit/identity/v3/fakes.py index c7d298859f..997bcf63e8 100644 --- a/openstackclient/tests/unit/identity/v3/fakes.py +++ b/openstackclient/tests/unit/identity/v3/fakes.py @@ -14,11 +14,11 @@ # import copy -import mock import uuid from keystoneauth1 import access from keystoneauth1 import fixture +import mock from openstackclient.tests.unit import fakes from openstackclient.tests.unit import utils diff --git a/openstackclient/tests/unit/identity/v3/test_identity_provider.py b/openstackclient/tests/unit/identity/v3/test_identity_provider.py index def6e0ce54..dc82ab7422 100644 --- a/openstackclient/tests/unit/identity/v3/test_identity_provider.py +++ b/openstackclient/tests/unit/identity/v3/test_identity_provider.py @@ -13,6 +13,7 @@ # under the License. import copy + import mock from openstackclient.identity.v3 import identity_provider diff --git a/openstackclient/tests/unit/identity/v3/test_mappings.py b/openstackclient/tests/unit/identity/v3/test_mappings.py index 93fe1196c9..1d8e77d9e8 100644 --- a/openstackclient/tests/unit/identity/v3/test_mappings.py +++ b/openstackclient/tests/unit/identity/v3/test_mappings.py @@ -13,8 +13,8 @@ # under the License. import copy -import mock +import mock from osc_lib import exceptions from openstackclient.identity.v3 import mapping diff --git a/openstackclient/tests/unit/identity/v3/test_role.py b/openstackclient/tests/unit/identity/v3/test_role.py index 39dbd24466..281d530c7e 100644 --- a/openstackclient/tests/unit/identity/v3/test_role.py +++ b/openstackclient/tests/unit/identity/v3/test_role.py @@ -14,8 +14,8 @@ # import copy -import mock +import mock from osc_lib import exceptions from osc_lib import utils diff --git a/openstackclient/tests/unit/identity/v3/test_role_assignment.py b/openstackclient/tests/unit/identity/v3/test_role_assignment.py index 32fbb7f1d4..835837e608 100644 --- a/openstackclient/tests/unit/identity/v3/test_role_assignment.py +++ b/openstackclient/tests/unit/identity/v3/test_role_assignment.py @@ -12,6 +12,7 @@ # import copy + import mock from openstackclient.identity.v3 import role_assignment diff --git a/openstackclient/tests/unit/identity/v3/test_trust.py b/openstackclient/tests/unit/identity/v3/test_trust.py index 614aab5470..1355b9089b 100644 --- a/openstackclient/tests/unit/identity/v3/test_trust.py +++ b/openstackclient/tests/unit/identity/v3/test_trust.py @@ -12,8 +12,8 @@ # import copy -import mock +import mock from osc_lib import exceptions from osc_lib import utils diff --git a/openstackclient/tests/unit/identity/v3/test_user.py b/openstackclient/tests/unit/identity/v3/test_user.py index 96f5076655..920ee95051 100644 --- a/openstackclient/tests/unit/identity/v3/test_user.py +++ b/openstackclient/tests/unit/identity/v3/test_user.py @@ -14,8 +14,8 @@ # import contextlib -import mock +import mock from osc_lib import exceptions from osc_lib import utils diff --git a/openstackclient/tests/unit/image/v1/fakes.py b/openstackclient/tests/unit/image/v1/fakes.py index 4b6d278c68..8030625743 100644 --- a/openstackclient/tests/unit/image/v1/fakes.py +++ b/openstackclient/tests/unit/image/v1/fakes.py @@ -14,9 +14,10 @@ # import copy -import mock import uuid +import mock + from openstackclient.tests.unit import fakes from openstackclient.tests.unit import utils from openstackclient.tests.unit.volume.v1 import fakes as volume_fakes diff --git a/openstackclient/tests/unit/image/v1/test_image.py b/openstackclient/tests/unit/image/v1/test_image.py index 41ddc49fa5..8a83feb0c4 100644 --- a/openstackclient/tests/unit/image/v1/test_image.py +++ b/openstackclient/tests/unit/image/v1/test_image.py @@ -14,8 +14,8 @@ # import copy -import mock +import mock from osc_lib import exceptions from osc_lib import utils diff --git a/openstackclient/tests/unit/image/v2/fakes.py b/openstackclient/tests/unit/image/v2/fakes.py index 0255ce38dc..eabc932573 100644 --- a/openstackclient/tests/unit/image/v2/fakes.py +++ b/openstackclient/tests/unit/image/v2/fakes.py @@ -14,11 +14,11 @@ # import copy -import mock import random import uuid from glanceclient.v2 import schemas +import mock from osc_lib import utils as common_utils import warlock diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py index 484a2bc64b..429ddd282b 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -14,9 +14,9 @@ # import copy -import mock from glanceclient.v2 import schemas +import mock from osc_lib import exceptions from osc_lib import utils as common_utils import warlock diff --git a/openstackclient/tests/unit/integ/cli/test_shell.py b/openstackclient/tests/unit/integ/cli/test_shell.py index 4e91f6370e..78663fbcda 100644 --- a/openstackclient/tests/unit/integ/cli/test_shell.py +++ b/openstackclient/tests/unit/integ/cli/test_shell.py @@ -11,8 +11,8 @@ # under the License. import copy -import mock +import mock from osc_lib.tests import utils as osc_lib_utils from openstackclient import shell diff --git a/openstackclient/tests/unit/network/test_common.py b/openstackclient/tests/unit/network/test_common.py index 4b9a754b34..c8dce0af83 100644 --- a/openstackclient/tests/unit/network/test_common.py +++ b/openstackclient/tests/unit/network/test_common.py @@ -12,6 +12,7 @@ # import argparse + import mock import openstack diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index eadab58461..bdc1c1fb0e 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -13,11 +13,12 @@ import argparse import copy -import mock from random import choice from random import randint import uuid +import mock + from openstackclient.tests.unit import fakes from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3 from openstackclient.tests.unit import utils diff --git a/openstackclient/tests/unit/network/v2/test_network.py b/openstackclient/tests/unit/network/v2/test_network.py index 7b20c79338..357088f345 100644 --- a/openstackclient/tests/unit/network/v2/test_network.py +++ b/openstackclient/tests/unit/network/v2/test_network.py @@ -11,10 +11,10 @@ # under the License. # -import mock -from mock import call import random +import mock +from mock import call from osc_lib import exceptions from osc_lib import utils diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index 97be5afd94..45e1045da0 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -12,8 +12,8 @@ # import argparse -import mock +import mock from mock import call from osc_lib import exceptions from osc_lib import utils diff --git a/openstackclient/tests/unit/network/v2/test_subnet_pool.py b/openstackclient/tests/unit/network/v2/test_subnet_pool.py index 139fddf887..81f0278f26 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/unit/network/v2/test_subnet_pool.py @@ -12,9 +12,9 @@ # import argparse + import mock from mock import call - from osc_lib import exceptions from osc_lib import utils diff --git a/openstackclient/tests/unit/object/v1/test_container.py b/openstackclient/tests/unit/object/v1/test_container.py index 37b8c705b7..39e2d80f4a 100644 --- a/openstackclient/tests/unit/object/v1/test_container.py +++ b/openstackclient/tests/unit/object/v1/test_container.py @@ -14,6 +14,7 @@ # import copy + import mock from openstackclient.api import object_store_v1 as object_store diff --git a/openstackclient/tests/unit/object/v1/test_object.py b/openstackclient/tests/unit/object/v1/test_object.py index c0ac204dad..b629937351 100644 --- a/openstackclient/tests/unit/object/v1/test_object.py +++ b/openstackclient/tests/unit/object/v1/test_object.py @@ -14,6 +14,7 @@ # import copy + import mock from openstackclient.api import object_store_v1 as object_store diff --git a/openstackclient/tests/unit/test_shell.py b/openstackclient/tests/unit/test_shell.py index b9fac684c4..dff37f10b7 100644 --- a/openstackclient/tests/unit/test_shell.py +++ b/openstackclient/tests/unit/test_shell.py @@ -13,10 +13,10 @@ # under the License. # -import mock import os import sys +import mock from osc_lib.tests import utils as osc_lib_test_utils from oslo_utils import importutils import wrapt diff --git a/openstackclient/tests/unit/utils.py b/openstackclient/tests/unit/utils.py index 8f9cc7b17c..926dad87ea 100644 --- a/openstackclient/tests/unit/utils.py +++ b/openstackclient/tests/unit/utils.py @@ -14,8 +14,9 @@ # under the License. # -import fixtures import os + +import fixtures import testtools from openstackclient.tests.unit import fakes diff --git a/openstackclient/tests/unit/volume/v1/fakes.py b/openstackclient/tests/unit/volume/v1/fakes.py index fff5181dc4..de9c724f1e 100644 --- a/openstackclient/tests/unit/volume/v1/fakes.py +++ b/openstackclient/tests/unit/volume/v1/fakes.py @@ -14,10 +14,11 @@ # import copy -import mock import random import uuid +import mock + from openstackclient.tests.unit import fakes from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes from openstackclient.tests.unit import utils diff --git a/openstackclient/tests/unit/volume/v1/test_qos_specs.py b/openstackclient/tests/unit/volume/v1/test_qos_specs.py index a88c1cd83f..442840f922 100644 --- a/openstackclient/tests/unit/volume/v1/test_qos_specs.py +++ b/openstackclient/tests/unit/volume/v1/test_qos_specs.py @@ -14,9 +14,9 @@ # import copy + import mock from mock import call - from osc_lib import exceptions from osc_lib import utils diff --git a/openstackclient/tests/unit/volume/v1/test_volume.py b/openstackclient/tests/unit/volume/v1/test_volume.py index 6b79377352..eee5acd7ea 100644 --- a/openstackclient/tests/unit/volume/v1/test_volume.py +++ b/openstackclient/tests/unit/volume/v1/test_volume.py @@ -14,9 +14,9 @@ # import argparse + import mock from mock import call - from osc_lib import exceptions from osc_lib import utils diff --git a/openstackclient/tests/unit/volume/v2/fakes.py b/openstackclient/tests/unit/volume/v2/fakes.py index d321c71a27..27f37bd8c9 100644 --- a/openstackclient/tests/unit/volume/v2/fakes.py +++ b/openstackclient/tests/unit/volume/v2/fakes.py @@ -13,10 +13,10 @@ # import copy -import mock import random import uuid +import mock from osc_lib import utils as common_utils from openstackclient.tests.unit import fakes diff --git a/openstackclient/tests/unit/volume/v2/test_qos_specs.py b/openstackclient/tests/unit/volume/v2/test_qos_specs.py index 8f145a7e3f..2b935e205f 100644 --- a/openstackclient/tests/unit/volume/v2/test_qos_specs.py +++ b/openstackclient/tests/unit/volume/v2/test_qos_specs.py @@ -14,9 +14,9 @@ # import copy + import mock from mock import call - from osc_lib import exceptions from osc_lib import utils diff --git a/openstackclient/tests/unit/volume/v2/test_snapshot.py b/openstackclient/tests/unit/volume/v2/test_snapshot.py index 16d0602b7c..e8f4ae5a4a 100644 --- a/openstackclient/tests/unit/volume/v2/test_snapshot.py +++ b/openstackclient/tests/unit/volume/v2/test_snapshot.py @@ -13,9 +13,9 @@ # import argparse + import mock from mock import call - from osc_lib import exceptions from osc_lib import utils diff --git a/openstackclient/tests/unit/volume/v2/test_volume.py b/openstackclient/tests/unit/volume/v2/test_volume.py index 71e4eceac7..2fa924b8a5 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume.py +++ b/openstackclient/tests/unit/volume/v2/test_volume.py @@ -13,9 +13,9 @@ # import argparse + import mock from mock import call - from osc_lib import exceptions from osc_lib import utils diff --git a/test-requirements.txt b/test-requirements.txt index 5af8d0cf4f..84ada1d140 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -5,6 +5,7 @@ hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0 coverage!=4.4,>=4.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD +flake8-import-order==0.13 # LGPLv3 mock>=2.0 # BSD openstackdocstheme>=1.16.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 diff --git a/tox.ini b/tox.ini index 0f22650a9a..68b01b4537 100644 --- a/tox.ini +++ b/tox.ini @@ -91,3 +91,4 @@ exclude = .git,.tox,dist,doc,*lib/python*,*egg,build,tools # If 'ignore' is not set there are default errors and warnings that are set # Doc: http://flake8.readthedocs.org/en/latest/config.html#default ignore = __ +import-order-style = pep8 From 0e20212fa9fed002b07c5596bf32bf773faf535e Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Thu, 17 Aug 2017 07:04:45 +0000 Subject: [PATCH 1767/3095] Revert "Disable karborclient until a fixed version is released" Fixed karborclient has been released. The problem only exists in karborclient 0.4.0 and the current version is 0.6.0. This reverts commit 99a502b203235a62c4ec478df81246cec39a0b7b. Change-Id: Ie28fa1e0502792c87e68ab1c009b3349c739892b --- doc/source/cli/plugin-commands.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/source/cli/plugin-commands.rst b/doc/source/cli/plugin-commands.rst index 90c14b87c1..34efdc3d84 100644 --- a/doc/source/cli/plugin-commands.rst +++ b/doc/source/cli/plugin-commands.rst @@ -58,11 +58,11 @@ ironic-inspector .. list-plugins:: openstack.baremetal_introspection.v1 :detailed: -.. karbor -.. ------ -.. bug 1705258: Exclude karborclient 0.4.0 until a fixed version is released. -.. .. list-plugins:: openstack.data_protection.v1 -.. :detailed: +karbor +------ + +.. list-plugins:: openstack.data_protection.v1 + :detailed: mistral ------- From 4d7b4efeb508f63afbb4b12b9a4506f9fb940d7e Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Thu, 17 Aug 2017 16:46:31 -0400 Subject: [PATCH 1768/3095] auto-generate object docs Change-Id: I832eade2ddab754664e5f57b9764e524e655e616 --- doc/source/cli/command-objects/container.rst | 154 ++------------ .../command-objects/object-store-account.rst | 46 +--- doc/source/cli/command-objects/object.rst | 201 ++---------------- doc/source/conf.py | 2 +- 4 files changed, 40 insertions(+), 363 deletions(-) diff --git a/doc/source/cli/command-objects/container.rst b/doc/source/cli/command-objects/container.rst index e68955ad37..83deeb8156 100644 --- a/doc/source/cli/command-objects/container.rst +++ b/doc/source/cli/command-objects/container.rst @@ -2,149 +2,25 @@ container ========= -Object Storage v1 +A **container** defines a namespace for **objects**. Applies to Object Storage v1 -container create ----------------- +.. autoprogram-cliff:: openstack.object_store.v1 + :command: container create -Create new container +.. autoprogram-cliff:: openstack.object_store.v1 + :command: container delete -.. program:: container create -.. code:: bash +.. autoprogram-cliff:: openstack.object_store.v1 + :command: container list - openstack container create - [ ...] +.. autoprogram-cliff:: openstack.object_store.v1 + :command: container save -.. describe:: +.. autoprogram-cliff:: openstack.object_store.v1 + :command: container set - New container name(s) +.. autoprogram-cliff:: openstack.object_store.v1 + :command: container show -container delete ----------------- - -Delete container - -.. program:: container delete -.. code:: bash - - openstack container delete - [-r] | [--recursive] - [ ...] - -.. option:: --recursive, -r - - Recursively delete objects in container before container delete - -.. describe:: - - Container(s) to delete - -container list --------------- - -List containers - -.. program:: container list -.. code:: bash - - openstack container list - [--prefix ] - [--marker ] - [--end-marker ] - [--limit ] - [--long] - [--all] - -.. option:: --prefix - - Filter list using - -.. option:: --marker - - Anchor for paging - -.. option:: --end-marker - - End anchor for paging - -.. option:: --limit - - Limit the number of containers returned - -.. option:: --long - - List additional fields in output - -.. option:: --all - - List all containers (default is 10000) - -container save --------------- - -Save container contents locally - -.. program:: container save -.. code:: bash - - openstack container save - - -.. describe:: - - Container to save - -container set -------------- - -Set container properties - -.. program:: container set -.. code:: bash - - openstack container set - [--property [...] ] - - -.. option:: --property - - Set a property on this container (repeat option to set multiple properties) - -.. describe:: - - Container to modify - -container show --------------- - -Display container details - -.. program:: container show -.. code:: bash - - openstack container show - - -.. describe:: - - Container to display - -container unset ---------------- - -Unset container properties - -.. program:: container unset -.. code:: bash - - openstack container unset - [--property ] - - -.. option:: --property - - Property to remove from container (repeat option to remove multiple properties) - -.. describe:: - - Container to modify +.. autoprogram-cliff:: openstack.object_store.v1 + :command: container unset diff --git a/doc/source/cli/command-objects/object-store-account.rst b/doc/source/cli/command-objects/object-store-account.rst index e8f09d458c..6beb63aff8 100644 --- a/doc/source/cli/command-objects/object-store-account.rst +++ b/doc/source/cli/command-objects/object-store-account.rst @@ -2,44 +2,14 @@ object store account ==================== -Object Storage v1 +An **object store account** represents the top-level of the hierarchy that +is comprised of **containers** and **objects**. Applies to Object Storage v1. -object store account set ------------------------- +.. autoprogram-cliff:: openstack.object_store.v1 + :command: object store account set -Set account properties +.. autoprogram-cliff:: openstack.object_store.v1 + :command: object store account show -.. program:: object store account set -.. code:: bash - - openstack object store account set - [--property [...] ] - -.. option:: --property - - Set a property on this account (repeat option to set multiple properties) - -object store account show -------------------------- - -Display account details - -.. program:: object store account show -.. code:: bash - - openstack object store account show - -object store account unset --------------------------- - -Unset account properties - -.. program:: object store account unset -.. code:: bash - - openstack object store account unset - [--property ] - -.. option:: --property - - Property to remove from account (repeat option to remove multiple properties) +.. autoprogram-cliff:: openstack.object_store.v1 + :command: object store account unset diff --git a/doc/source/cli/command-objects/object.rst b/doc/source/cli/command-objects/object.rst index 4cba38ee59..8b328bd52f 100644 --- a/doc/source/cli/command-objects/object.rst +++ b/doc/source/cli/command-objects/object.rst @@ -2,195 +2,26 @@ object ====== -Object Storage v1 +An **object** stores data content, such as documents, images, and so on. They +can also store custom metadata with an object. Applies to Object Storage v1. -object create -------------- +.. autoprogram-cliff:: openstack.object_store.v1 + :command: object create -Upload object to container +.. autoprogram-cliff:: openstack.object_store.v1 + :command: object delete -.. program:: object create -.. code:: bash +.. autoprogram-cliff:: openstack.object_store.v1 + :command: object list - openstack object create - [--name ] - - [ ...] +.. autoprogram-cliff:: openstack.object_store.v1 + :command: object save -.. option:: --name +.. autoprogram-cliff:: openstack.object_store.v1 + :command: object set - Upload a file and rename it. Can only be used when uploading a single object +.. autoprogram-cliff:: openstack.object_store.v1 + :command: object show -.. describe:: - - Container for new object - -.. describe:: - - Local filename(s) to upload - -object delete -------------- - -Delete object from container - -.. program:: object delete -.. code:: bash - - openstack object delete - - [ ...] - -.. describe:: - - Delete object(s) from - -.. describe:: - - Object(s) to delete - -object list ------------ - -List objects - -.. program object list -.. code:: bash - - openstack object list - [--prefix ] - [--delimiter ] - [--marker ] - [--end-marker ] - [--limit ] - [--long] - [--all] - - -.. option:: --prefix - - Filter list using - -.. option:: --delimiter - - Roll up items with - -.. option:: --marker - - Anchor for paging - -.. option:: --end-marker - - End anchor for paging - -.. option:: --limit - - Limit number of objects returned - -.. option:: --long - - List additional fields in output - -.. option:: --all - - List all objects in (default is 10000) - -.. describe:: - - Container to list - -object save ------------ - -Save object locally - -.. program:: object save -.. code:: bash - - openstack object save - [--file ] - - - -.. option:: --file - - Destination filename (defaults to object name); - using - as the filename will print the file to stdout - -.. describe:: - - Download from - -.. describe:: - - Object to save - -object set ----------- - -Set object properties - -.. program:: object set -.. code:: bash - - openstack object set - [--property [...] ] - - - -.. option:: --property - - Set a property on this object (repeat option to set multiple properties) - -.. describe:: - - Modify from - -.. describe:: - - Object to modify - -object show ------------ - -Display object details - -.. program:: object show -.. code:: bash - - openstack object show - - - -.. describe:: - - Display from - -.. describe:: - - Object to display - -object unset ------------- - -Unset object properties - -.. program:: object unset -.. code:: bash - - openstack object unset - [--property ] - - - -.. option:: --property - - Property to remove from object (repeat option to remove multiple properties) - -.. describe:: - - Modify from - -.. describe:: - - Object to modify +.. autoprogram-cliff:: openstack.object_store.v1 + :command: object unset diff --git a/doc/source/conf.py b/doc/source/conf.py index ac9d0b38be..d7efbd7b91 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -281,4 +281,4 @@ autoprogram_cliff_ignored = [ '--help', '--format', '--column', '--max-width', '--fit-width', - '--print-empty', '--prefix', '--noindent'] + '--print-empty', '--prefix', '--noindent', '--quote'] From 05c66f5bb66c1bd473fa0525909bdfcf2c4a6cc8 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Fri, 18 Aug 2017 11:42:09 +0000 Subject: [PATCH 1769/3095] Updated from global requirements Change-Id: I5f7fdc751e6a9078eb1eb55f5b5015a56ff668cc --- test-requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test-requirements.txt b/test-requirements.txt index 84ada1d140..199c5d7b46 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -6,12 +6,12 @@ hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0 coverage!=4.4,>=4.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD flake8-import-order==0.13 # LGPLv3 -mock>=2.0 # BSD +mock>=2.0.0 # BSD openstackdocstheme>=1.16.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 -reno!=2.3.1,>=1.8.0 # Apache-2.0 +reno>=2.5.0 # Apache-2.0 requests>=2.14.2 # Apache-2.0 -requests-mock>=1.1 # Apache-2.0 +requests-mock>=1.1.0 # Apache-2.0 sphinx>=1.6.2 # BSD stevedore>=1.20.0 # Apache-2.0 os-client-config>=1.28.0 # Apache-2.0 From 8cd3e258c5029a8efedab40019d6cfd3eac379f5 Mon Sep 17 00:00:00 2001 From: Harry Rybacki Date: Wed, 19 Jul 2017 13:07:34 +0000 Subject: [PATCH 1770/3095] Implied Roles Allow the user to create an inference rule between two roles. The first, called the prior role is the role explicitly assigned to an individual. The second, called the implied role, is one that the user gets implicitly. For example: Role B implies Role A. User X is assigned Role B. Therefore User X also assigned Role A. The management and maintenance of the rules is performed in the Keystone server. Change-Id: If547c2f16e812bc7fffd742ec37e6a26011f3185 --- .../cli/command-objects/implied_role.rst | 57 ++++++ openstackclient/identity/v3/implied_role.py | 129 +++++++++++++ .../tests/unit/identity/v3/fakes.py | 24 +++ .../unit/identity/v3/test_implied_role.py | 181 ++++++++++++++++++ .../add-implied-role-0cdafb131fbd7453.yaml | 16 ++ setup.cfg | 4 + 6 files changed, 411 insertions(+) create mode 100644 doc/source/cli/command-objects/implied_role.rst create mode 100644 openstackclient/identity/v3/implied_role.py create mode 100644 openstackclient/tests/unit/identity/v3/test_implied_role.py create mode 100644 releasenotes/notes/add-implied-role-0cdafb131fbd7453.yaml diff --git a/doc/source/cli/command-objects/implied_role.rst b/doc/source/cli/command-objects/implied_role.rst new file mode 100644 index 0000000000..e43c9ea39e --- /dev/null +++ b/doc/source/cli/command-objects/implied_role.rst @@ -0,0 +1,57 @@ +============ +implied role +============ + +Identity v3 + + +implied role create +------------------- + +Creates an association between prior and implied roles + +.. program:: implied role create +.. code:: bash + + openstack implied role create + + --implied-role + +.. option:: + + Prior role (name or ID) implies another role + +.. option:: --implied-role + + (name or ID) implied by another role + + +implied role delete +------------------- + +Deletes an association between prior and implied roles + +.. program:: implied role delete +.. code:: bash + + openstack implied role delete + + --implied-role + +.. option:: + + Prior role (name or ID) implies another role + +.. option:: --implied-role + + (name or ID) implied by another role + +implied role list +----------------- + +List implied roles + +.. program:: implied role list +.. code:: bash + + openstack implied role list diff --git a/openstackclient/identity/v3/implied_role.py b/openstackclient/identity/v3/implied_role.py new file mode 100644 index 0000000000..c762338997 --- /dev/null +++ b/openstackclient/identity/v3/implied_role.py @@ -0,0 +1,129 @@ +# Copyright 2012-2013 OpenStack Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +"""Identity v3 Implied Role action implementations""" + +import logging + +from osc_lib.command import command +import six + +from openstackclient.i18n import _ + + +LOG = logging.getLogger(__name__) + + +def _get_role_ids(identity_client, parsed_args): + """Return prior and implied role id(s) + + If prior and implied role id(s) are retrievable from identity + client, return tuple containing them. + """ + role_id = None + implied_role_id = None + + roles = identity_client.roles.list() + + for role in roles: + role_id_or_name = (role.name, role.id) + + if parsed_args.role in role_id_or_name: + role_id = role.id + elif parsed_args.implied_role in role_id_or_name: + implied_role_id = role.id + + return (role_id, implied_role_id) + + +class CreateImpliedRole(command.ShowOne): + + _description = _("Creates an association between prior and implied roles") + + def get_parser(self, prog_name): + parser = super(CreateImpliedRole, self).get_parser(prog_name) + parser.add_argument( + 'role', + metavar='', + help=_('Role (name or ID) that implies another role'), + ) + parser.add_argument( + '--implied-role', + metavar='', + help=' (name or ID) implied by another role', + required=True, + ) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + (prior_role_id, implied_role_id) = _get_role_ids( + identity_client, parsed_args) + response = identity_client.roles.create_implied( + prior_role_id, implied_role_id) + response._info.pop('links', None) + return zip(*sorted([(k, v['id']) + for k, v in six.iteritems(response._info)])) + + +class DeleteImpliedRole(command.Command): + + _description = _("Deletes an association between prior and implied roles") + + def get_parser(self, prog_name): + parser = super(DeleteImpliedRole, self).get_parser(prog_name) + parser.add_argument( + 'role', + metavar='', + help=_('Role (name or ID) that implies another role'), + ) + parser.add_argument( + '--implied-role', + metavar='', + help=' (name or ID) implied by another role', + required=True, + ) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + (prior_role_id, implied_role_id) = _get_role_ids( + identity_client, parsed_args) + identity_client.roles.delete_implied( + prior_role_id, implied_role_id) + + +class ListImpliedRole(command.Lister): + + _description = _("List implied roles") + _COLUMNS = ['Prior Role ID', 'Prior Role Name', + 'Implied Role ID', 'Implied Role Name'] + + def get_parser(self, prog_name): + parser = super(ListImpliedRole, self).get_parser(prog_name) + return parser + + def take_action(self, parsed_args): + def _list_implied(response): + for rule in response: + for implies in rule.implies: + yield (rule.prior_role['id'], + rule.prior_role['name'], + implies['id'], + implies['name']) + + identity_client = self.app.client_manager.identity + response = identity_client.roles.list_inference_roles() + return (self._COLUMNS, _list_implied(response)) diff --git a/openstackclient/tests/unit/identity/v3/fakes.py b/openstackclient/tests/unit/identity/v3/fakes.py index 997bcf63e8..7de251524a 100644 --- a/openstackclient/tests/unit/identity/v3/fakes.py +++ b/openstackclient/tests/unit/identity/v3/fakes.py @@ -184,6 +184,8 @@ 'links': base_url + 'roles/' + 'r2', } +ROLES = [ROLE, ROLE_2] + service_id = 's-123' service_name = 'Texaco' service_type = 'gas' @@ -968,3 +970,25 @@ def create_one_role_assignment(attrs=None): info=copy.deepcopy(role_assignment_info), loaded=True) return role_assignment + + +class FakeImpliedRoleResponse(object): + """Fake one or more role assignment.""" + def __init__(self, prior_role, implied_roles): + self.prior_role = prior_role + self.implies = [role for role in implied_roles] + + @staticmethod + def create_list(): + """Create a fake implied role list response. + + :return: + A list of FakeImpliedRoleResponse objects + """ + + # set default attributes. + implied_roles = [ + FakeImpliedRoleResponse(ROLES[0], [ROLES[1]]) + ] + + return implied_roles diff --git a/openstackclient/tests/unit/identity/v3/test_implied_role.py b/openstackclient/tests/unit/identity/v3/test_implied_role.py new file mode 100644 index 0000000000..08273f7312 --- /dev/null +++ b/openstackclient/tests/unit/identity/v3/test_implied_role.py @@ -0,0 +1,181 @@ +# Copyright 2013 Nebula Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +import copy + +from openstackclient.identity.v3 import implied_role +from openstackclient.tests.unit import fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes + + +class TestRole(identity_fakes.TestIdentityv3): + + def setUp(self): + super(TestRole, self).setUp() + + # Get a shortcut to the UserManager Mock + self.users_mock = self.app.client_manager.identity.users + self.users_mock.reset_mock() + + # Get a shortcut to the UserManager Mock + self.groups_mock = self.app.client_manager.identity.groups + self.groups_mock.reset_mock() + + # Get a shortcut to the DomainManager Mock + self.domains_mock = self.app.client_manager.identity.domains + self.domains_mock.reset_mock() + + # Get a shortcut to the ProjectManager Mock + self.projects_mock = self.app.client_manager.identity.projects + self.projects_mock.reset_mock() + + # Get a shortcut to the RoleManager Mock + self.roles_mock = self.app.client_manager.identity.roles + self.roles_mock.reset_mock() + + def _is_inheritance_testcase(self): + return False + + +class TestImpliedRoleCreate(TestRole): + + def setUp(self): + super(TestImpliedRoleCreate, self).setUp() + + self.roles_mock.list.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.ROLES[0]), + loaded=True, + ), + fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.ROLES[1]), + loaded=True, + ), + ] + + self.roles_mock.create_implied.return_value = fakes.FakeResource( + None, + {'prior_role': copy.deepcopy(identity_fakes.ROLES[0]), + 'implied': copy.deepcopy(identity_fakes.ROLES[1]), }, + loaded=True, + ) + + self.cmd = implied_role.CreateImpliedRole(self.app, None) + + def test_implied_role_create(self): + + arglist = [ + identity_fakes.ROLES[0]['id'], + '--implied-role', identity_fakes.ROLES[1]['id'], + ] + verifylist = [ + ('role', identity_fakes.ROLES[0]['id']), + ('implied_role', identity_fakes.ROLES[1]['id']), + ] + 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) + + # RoleManager.create_implied(prior, implied) + self.roles_mock.create_implied.assert_called_with( + identity_fakes.ROLES[0]['id'], + identity_fakes.ROLES[1]['id'] + ) + + collist = ('implied', 'prior_role') + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.ROLES[1]['id'], + identity_fakes.ROLES[0]['id'] + ) + self.assertEqual(datalist, data) + + +class TestImpliedRoleDelete(TestRole): + + def setUp(self): + super(TestImpliedRoleDelete, self).setUp() + + self.roles_mock.list.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.ROLES[0]), + loaded=True, + ), + fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.ROLES[1]), + loaded=True, + ), + ] + + self.roles_mock.delete_implied.return_value = fakes.FakeResource( + None, + {'prior-role': copy.deepcopy(identity_fakes.ROLES[0]), + 'implied': copy.deepcopy(identity_fakes.ROLES[1]), }, + loaded=True, + ) + + self.cmd = implied_role.DeleteImpliedRole(self.app, None) + + def test_implied_role_delete(self): + arglist = [ + identity_fakes.ROLES[0]['id'], + '--implied-role', identity_fakes.ROLES[1]['id'], + ] + verifylist = [ + ('role', identity_fakes.ROLES[0]['id']), + ('implied_role', identity_fakes.ROLES[1]['id']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.roles_mock.delete_implied.assert_called_with( + identity_fakes.ROLES[0]['id'], + identity_fakes.ROLES[1]['id'] + ) + + +class TestImpliedRoleList(TestRole): + + def setUp(self): + super(TestImpliedRoleList, self).setUp() + + self.roles_mock.list_inference_roles.return_value = ( + identity_fakes.FakeImpliedRoleResponse.create_list()) + + self.cmd = implied_role.ListImpliedRole(self.app, None) + + def test_implied_role_list(self): + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.roles_mock.list_inference_roles.assert_called_with() + + collist = ['Prior Role ID', 'Prior Role Name', + 'Implied Role ID', 'Implied Role Name'] + self.assertEqual(collist, columns) + datalist = [ + (identity_fakes.ROLES[0]['id'], identity_fakes.ROLES[0]['name'], + identity_fakes.ROLES[1]['id'], identity_fakes.ROLES[1]['name']) + ] + x = [d for d in data] + self.assertEqual(datalist, x) diff --git a/releasenotes/notes/add-implied-role-0cdafb131fbd7453.yaml b/releasenotes/notes/add-implied-role-0cdafb131fbd7453.yaml new file mode 100644 index 0000000000..8d5d76eac1 --- /dev/null +++ b/releasenotes/notes/add-implied-role-0cdafb131fbd7453.yaml @@ -0,0 +1,16 @@ +--- +features: + - | + Support for creating, deleting, and listing implied roles has been added. + This allows users to create an inference rule between two roles. The + first, called the prior role is the role explicitly assigned to an + individual. The second, called the implied role, is one that the user + is assgined implicitly. Additionally, these rules can be chained, such + that an implied role from the first inference rule can be the implied role + in the second. Thus one explicitly assigned role can lead to multiple + implied roles. + ``implied role create --implied-role `` creates an + association between prior and implied roles. + ``implied role delete --implied-role `` removes an + association between prior and implied roles. + ``implied role list`` Lists all implied roles that currently exist. diff --git a/setup.cfg b/setup.cfg index ec91988f10..b4b6827d0b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -247,6 +247,10 @@ openstack.identity.v3 = identity_provider_set = openstackclient.identity.v3.identity_provider:SetIdentityProvider identity_provider_show = openstackclient.identity.v3.identity_provider:ShowIdentityProvider + implied_role_create = openstackclient.identity.v3.implied_role:CreateImpliedRole + implied_role_delete = openstackclient.identity.v3.implied_role:DeleteImpliedRole + implied_role_list = openstackclient.identity.v3.implied_role:ListImpliedRole + mapping_create = openstackclient.identity.v3.mapping:CreateMapping mapping_delete = openstackclient.identity.v3.mapping:DeleteMapping mapping_list = openstackclient.identity.v3.mapping:ListMapping From ff85c627078ec8d4770ba875ad35f5e63facb173 Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Tue, 22 Aug 2017 21:38:07 +0000 Subject: [PATCH 1771/3095] flake8-import-order: Ensure to place project imports last To ensure project imports are placed after third party import, we need to specify application-import-names. Previously flake8-import-check checked only standard imports or not. Change-Id: Iad7afa456cec7cf5b44955f1ea03c593a4c0e426 --- openstackclient/tests/functional/compute/v2/test_keypair.py | 4 ++-- .../tests/functional/identity/v3/test_service_provider.py | 3 ++- openstackclient/tests/unit/api/test_compute_v2.py | 4 ++-- openstackclient/tests/unit/network/test_common.py | 2 +- tox.ini | 1 + 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/openstackclient/tests/functional/compute/v2/test_keypair.py b/openstackclient/tests/functional/compute/v2/test_keypair.py index 1e1a03d67d..9a88e66fca 100644 --- a/openstackclient/tests/functional/compute/v2/test_keypair.py +++ b/openstackclient/tests/functional/compute/v2/test_keypair.py @@ -13,11 +13,11 @@ import json import tempfile -from openstackclient.tests.functional import base - from tempest.lib.common.utils import data_utils from tempest.lib import exceptions +from openstackclient.tests.functional import base + class KeypairBase(base.TestCase): """Methods for functional tests.""" diff --git a/openstackclient/tests/functional/identity/v3/test_service_provider.py b/openstackclient/tests/functional/identity/v3/test_service_provider.py index e072bc93b1..32b7a463d1 100644 --- a/openstackclient/tests/functional/identity/v3/test_service_provider.py +++ b/openstackclient/tests/functional/identity/v3/test_service_provider.py @@ -10,9 +10,10 @@ # License for the specific language governing permissions and limitations # under the License. -from openstackclient.tests.functional.identity.v3 import common from tempest.lib.common.utils import data_utils +from openstackclient.tests.functional.identity.v3 import common + class ServiceProviderTests(common.IdentityTests): # Introduce functional test cases for command 'Service Provider' diff --git a/openstackclient/tests/unit/api/test_compute_v2.py b/openstackclient/tests/unit/api/test_compute_v2.py index f10fb6cfcf..56b35937c7 100644 --- a/openstackclient/tests/unit/api/test_compute_v2.py +++ b/openstackclient/tests/unit/api/test_compute_v2.py @@ -13,12 +13,12 @@ """Compute v2 API Library Tests""" +from keystoneclient import session +from osc_lib import exceptions as osc_lib_exceptions from requests_mock.contrib import fixture -from keystoneclient import session from openstackclient.api import compute_v2 as compute from openstackclient.tests.unit import utils -from osc_lib import exceptions as osc_lib_exceptions FAKE_PROJECT = 'xyzpdq' diff --git a/openstackclient/tests/unit/network/test_common.py b/openstackclient/tests/unit/network/test_common.py index c8dce0af83..d4d3a277f4 100644 --- a/openstackclient/tests/unit/network/test_common.py +++ b/openstackclient/tests/unit/network/test_common.py @@ -14,8 +14,8 @@ import argparse import mock - import openstack + from openstackclient.common import exceptions from openstackclient.network import common from openstackclient.tests.unit import utils diff --git a/tox.ini b/tox.ini index 68b01b4537..7716ffb4c4 100644 --- a/tox.ini +++ b/tox.ini @@ -92,3 +92,4 @@ exclude = .git,.tox,dist,doc,*lib/python*,*egg,build,tools # Doc: http://flake8.readthedocs.org/en/latest/config.html#default ignore = __ import-order-style = pep8 +application_import_names = openstackclient From d33ab499ed510a6dfb34a44b0a33945e7f8b0d1f Mon Sep 17 00:00:00 2001 From: lihaijing Date: Wed, 23 Aug 2017 12:59:46 +0800 Subject: [PATCH 1772/3095] Fix "openstack image unset" command's help message typo Change-Id: Ie286d9ee6054cc2126473d7e6e77bafbfd80023b Closes-Bug: #1712473 --- openstackclient/image/v2/image.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index c2c5c594ce..30dd938055 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -990,7 +990,7 @@ def get_parser(self, prog_name): default=[], action='append', help=_("Unset a tag on this image " - "(repeat option to set multiple tags)"), + "(repeat option to unset multiple tags)"), ) parser.add_argument( "--property", @@ -999,7 +999,7 @@ def get_parser(self, prog_name): default=[], action='append', help=_("Unset a property on this image " - "(repeat option to set multiple properties)"), + "(repeat option to unset multiple properties)"), ) return parser From 95e279176b05e0096988ae596f1e02adda248935 Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Wed, 23 Aug 2017 21:14:47 +0000 Subject: [PATCH 1773/3095] Convert network security group functional tests to JSON Change-Id: Icb63aa0dfbce9016fb824f97915a660cf130d120 --- .../network/v2/test_security_group.py | 70 +++++---------- .../network/v2/test_security_group_rule.py | 88 +++++++------------ 2 files changed, 52 insertions(+), 106 deletions(-) diff --git a/openstackclient/tests/functional/network/v2/test_security_group.py b/openstackclient/tests/functional/network/v2/test_security_group.py index b601c913fd..8ae24b7247 100644 --- a/openstackclient/tests/functional/network/v2/test_security_group.py +++ b/openstackclient/tests/functional/network/v2/test_security_group.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import json import uuid from openstackclient.tests.functional.network.v2 import common @@ -17,44 +18,6 @@ class SecurityGroupTests(common.NetworkTests): """Functional tests for security group""" - HEADERS = ['Name'] - FIELDS = ['name'] - - @classmethod - def setUpClass(cls): - common.NetworkTests.setUpClass() - if cls.haz_network: - cls.NAME = uuid.uuid4().hex - cls.OTHER_NAME = uuid.uuid4().hex - - opts = cls.get_opts(cls.FIELDS) - raw_output = cls.openstack( - 'security group create ' + - cls.NAME + - opts - ) - expected = cls.NAME + '\n' - cls.assertOutput(expected, raw_output) - - @classmethod - def tearDownClass(cls): - try: - if cls.haz_network: - # Rename test - raw_output = cls.openstack( - 'security group set --name ' + - cls.OTHER_NAME + ' ' + - cls.NAME - ) - cls.assertOutput('', raw_output) - # Delete test - raw_output = cls.openstack( - 'security group delete ' + - cls.OTHER_NAME - ) - cls.assertOutput('', raw_output) - finally: - super(SecurityGroupTests, cls).tearDownClass() def setUp(self): super(SecurityGroupTests, self).setUp() @@ -62,22 +25,33 @@ def setUp(self): if not self.haz_network: self.skipTest("No Network service present") + self.NAME = uuid.uuid4().hex + self.OTHER_NAME = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'security group create -f json ' + + self.NAME + )) + self.addCleanup(self.openstack, + 'security group delete ' + cmd_output['id']) + self.assertEqual(self.NAME, cmd_output['name']) + def test_security_group_list(self): - opts = self.get_opts(self.HEADERS) - raw_output = self.openstack('security group list' + opts) - self.assertIn(self.NAME, raw_output) + cmd_output = json.loads(self.openstack('security group list -f json')) + self.assertIn(self.NAME, [sg['Name'] for sg in cmd_output]) def test_security_group_set(self): + other_name = uuid.uuid4().hex raw_output = self.openstack( - 'security group set --description NSA ' + self.NAME + 'security group set --description NSA --name ' + + other_name + ' ' + self.NAME ) self.assertEqual('', raw_output) - opts = self.get_opts(['description']) - raw_output = self.openstack('security group show ' + self.NAME + opts) - self.assertEqual("NSA\n", raw_output) + cmd_output = json.loads(self.openstack( + 'security group show -f json ' + other_name)) + self.assertEqual('NSA', cmd_output['description']) def test_security_group_show(self): - opts = self.get_opts(self.FIELDS) - raw_output = self.openstack('security group show ' + self.NAME + opts) - self.assertEqual(self.NAME + "\n", raw_output) + cmd_output = json.loads(self.openstack( + 'security group show -f json ' + self.NAME)) + self.assertEqual(self.NAME, cmd_output['name']) diff --git a/openstackclient/tests/functional/network/v2/test_security_group_rule.py b/openstackclient/tests/functional/network/v2/test_security_group_rule.py index 40951a011d..fe78bf477a 100644 --- a/openstackclient/tests/functional/network/v2/test_security_group_rule.py +++ b/openstackclient/tests/functional/network/v2/test_security_group_rule.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import json import uuid from openstackclient.tests.functional.network.v2 import common @@ -17,54 +18,6 @@ class SecurityGroupRuleTests(common.NetworkTests): """Functional tests for security group rule""" - SECURITY_GROUP_RULE_ID = None - NAME_FIELD = ['name'] - ID_FIELD = ['id'] - ID_HEADER = ['ID'] - - @classmethod - def setUpClass(cls): - common.NetworkTests.setUpClass() - if cls.haz_network: - cls.SECURITY_GROUP_NAME = uuid.uuid4().hex - - # Create the security group to hold the rule - opts = cls.get_opts(cls.NAME_FIELD) - raw_output = cls.openstack( - 'security group create ' + - cls.SECURITY_GROUP_NAME + - opts - ) - expected = cls.SECURITY_GROUP_NAME + '\n' - cls.assertOutput(expected, raw_output) - - # Create the security group rule. - opts = cls.get_opts(cls.ID_FIELD) - raw_output = cls.openstack( - 'security group rule create ' + - cls.SECURITY_GROUP_NAME + ' ' + - '--protocol tcp --dst-port 80:80 ' + - '--ingress --ethertype IPv4 ' + - opts - ) - cls.SECURITY_GROUP_RULE_ID = raw_output.strip('\n') - - @classmethod - def tearDownClass(cls): - try: - if cls.haz_network: - raw_output = cls.openstack( - 'security group rule delete ' + - cls.SECURITY_GROUP_RULE_ID - ) - cls.assertOutput('', raw_output) - raw_output = cls.openstack( - 'security group delete ' + - cls.SECURITY_GROUP_NAME - ) - cls.assertOutput('', raw_output) - finally: - super(SecurityGroupRuleTests, cls).tearDownClass() def setUp(self): super(SecurityGroupRuleTests, self).setUp() @@ -72,16 +25,35 @@ def setUp(self): if not self.haz_network: self.skipTest("No Network service present") + self.SECURITY_GROUP_NAME = uuid.uuid4().hex + + # Create the security group to hold the rule + cmd_output = json.loads(self.openstack( + 'security group create -f json ' + + self.SECURITY_GROUP_NAME + )) + self.addCleanup(self.openstack, + 'security group delete ' + self.SECURITY_GROUP_NAME) + self.assertEqual(self.SECURITY_GROUP_NAME, cmd_output['name']) + + # Create the security group rule. + cmd_output = json.loads(self.openstack( + 'security group rule create -f json ' + + self.SECURITY_GROUP_NAME + ' ' + + '--protocol tcp --dst-port 80:80 ' + + '--ingress --ethertype IPv4 ' + )) + self.addCleanup(self.openstack, + 'security group rule delete ' + cmd_output['id']) + self.SECURITY_GROUP_RULE_ID = cmd_output['id'] + def test_security_group_rule_list(self): - opts = self.get_opts(self.ID_HEADER) - raw_output = self.openstack('security group rule list ' + - self.SECURITY_GROUP_NAME + - opts) - self.assertIn(self.SECURITY_GROUP_RULE_ID, raw_output) + cmd_output = json.loads(self.openstack( + 'security group rule list -f json ' + self.SECURITY_GROUP_NAME)) + self.assertIn(self.SECURITY_GROUP_RULE_ID, + [rule['ID'] for rule in cmd_output]) def test_security_group_rule_show(self): - opts = self.get_opts(self.ID_FIELD) - raw_output = self.openstack('security group rule show ' + - self.SECURITY_GROUP_RULE_ID + - opts) - self.assertEqual(self.SECURITY_GROUP_RULE_ID + "\n", raw_output) + cmd_output = json.loads(self.openstack( + 'security group rule show -f json ' + self.SECURITY_GROUP_RULE_ID)) + self.assertEqual(self.SECURITY_GROUP_RULE_ID, cmd_output['id']) From fe8a50b6b1448038073c47c55b46fc4ebf4a6080 Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Wed, 23 Aug 2017 21:15:12 +0000 Subject: [PATCH 1774/3095] Convert network qos functional tests to JSON Change-Id: Ie5cde2f927ec6abb6334ea01adfb06749384ed01 --- .../network/v2/test_network_qos_policy.py | 80 ++--- .../network/v2/test_network_qos_rule.py | 339 ++++++++---------- .../network/v2/test_network_qos_rule_type.py | 7 +- 3 files changed, 193 insertions(+), 233 deletions(-) diff --git a/openstackclient/tests/functional/network/v2/test_network_qos_policy.py b/openstackclient/tests/functional/network/v2/test_network_qos_policy.py index 53c15ecf69..27c1b6aa8c 100644 --- a/openstackclient/tests/functional/network/v2/test_network_qos_policy.py +++ b/openstackclient/tests/functional/network/v2/test_network_qos_policy.py @@ -13,6 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. +import json import uuid from openstackclient.tests.functional.network.v2 import common @@ -20,34 +21,6 @@ class NetworkQosPolicyTests(common.NetworkTests): """Functional tests for QoS policy""" - HEADERS = ['Name'] - FIELDS = ['name'] - - @classmethod - def setUpClass(cls): - common.NetworkTests.setUpClass() - if cls.haz_network: - cls.NAME = uuid.uuid4().hex - - opts = cls.get_opts(cls.FIELDS) - raw_output = cls.openstack( - 'network qos policy create ' + - cls.NAME + - opts - ) - cls.assertOutput(cls.NAME + "\n", raw_output) - - @classmethod - def tearDownClass(cls): - try: - if cls.haz_network: - raw_output = cls.openstack( - 'network qos policy delete ' + - cls.NAME - ) - cls.assertOutput('', raw_output) - finally: - super(NetworkQosPolicyTests, cls).tearDownClass() def setUp(self): super(NetworkQosPolicyTests, self).setUp() @@ -55,33 +28,46 @@ def setUp(self): if not self.haz_network: self.skipTest("No Network service present") + self.NAME = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'network qos policy create -f json ' + + self.NAME + )) + self.addCleanup(self.openstack, + 'network qos policy delete ' + self.NAME) + self.assertEqual(self.NAME, cmd_output['name']) + + def test_qos_rule_create_delete(self): + # This is to check the output of qos policy delete + policy_name = uuid.uuid4().hex + self.openstack('network qos policy create -f json ' + policy_name) + raw_output = self.openstack( + 'network qos policy delete ' + policy_name) + self.assertEqual('', raw_output) + def test_qos_policy_list(self): - opts = self.get_opts(self.HEADERS) - raw_output = self.openstack('network qos policy list' + opts) - self.assertIn(self.NAME, raw_output) + cmd_output = json.loads(self.openstack( + 'network qos policy list -f json')) + self.assertIn(self.NAME, [p['Name'] for p in cmd_output]) def test_qos_policy_show(self): - opts = self.get_opts(self.FIELDS) - raw_output = self.openstack('network qos policy show ' + self.NAME + - opts) - self.assertEqual(self.NAME + "\n", raw_output) + cmd_output = json.loads(self.openstack( + 'network qos policy show -f json ' + self.NAME)) + self.assertEqual(self.NAME, cmd_output['name']) def test_qos_policy_set(self): self.openstack('network qos policy set --share ' + self.NAME) - opts = self.get_opts(['shared']) - raw_output = self.openstack('network qos policy show ' + self.NAME + - opts) - self.assertEqual("True\n", raw_output) + cmd_output = json.loads(self.openstack( + 'network qos policy show -f json ' + self.NAME)) + self.assertTrue(cmd_output['shared']) def test_qos_policy_default(self): self.openstack('network qos policy set --default ' + self.NAME) - opts = self.get_opts(['is_default']) - raw_output = self.openstack('network qos policy show ' + self.NAME + - opts) - self.assertEqual("True\n", raw_output) + cmd_output = json.loads(self.openstack( + 'network qos policy show -f json ' + self.NAME)) + self.assertTrue(cmd_output['is_default']) self.openstack('network qos policy set --no-default ' + self.NAME) - opts = self.get_opts(['is_default']) - raw_output = self.openstack('network qos policy show ' + self.NAME + - opts) - self.assertEqual("False\n", raw_output) + cmd_output = json.loads(self.openstack( + 'network qos policy show -f json ' + self.NAME)) + self.assertFalse(cmd_output['is_default']) diff --git a/openstackclient/tests/functional/network/v2/test_network_qos_rule.py b/openstackclient/tests/functional/network/v2/test_network_qos_rule.py index 8b34422fb1..770abe94fc 100644 --- a/openstackclient/tests/functional/network/v2/test_network_qos_rule.py +++ b/openstackclient/tests/functional/network/v2/test_network_qos_rule.py @@ -13,6 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. +import json import uuid from openstackclient.tests.functional.network.v2 import common @@ -20,51 +21,6 @@ class NetworkQosRuleTestsMinimumBandwidth(common.NetworkTests): """Functional tests for QoS minimum bandwidth rule""" - RULE_ID = None - MIN_KBPS = 2800 - MIN_KBPS_MODIFIED = 7500 - DIRECTION = '--egress' - HEADERS = ['ID'] - FIELDS = ['id'] - TYPE = 'minimum-bandwidth' - - @classmethod - def setUpClass(cls): - common.NetworkTests.setUpClass() - if cls.haz_network: - cls.QOS_POLICY_NAME = 'qos_policy_' + uuid.uuid4().hex - - opts = cls.get_opts(cls.FIELDS) - cls.openstack( - 'network qos policy create ' + - cls.QOS_POLICY_NAME - ) - cls.RULE_ID = cls.openstack( - 'network qos rule create ' + - '--type ' + cls.TYPE + ' ' + - '--min-kbps ' + str(cls.MIN_KBPS) + ' ' + - cls.DIRECTION + ' ' + - cls.QOS_POLICY_NAME + - opts - ) - cls.assertsOutputNotNone(cls.RULE_ID) - - @classmethod - def tearDownClass(cls): - try: - if cls.haz_network: - raw_output = cls.openstack( - 'network qos rule delete ' + - cls.QOS_POLICY_NAME + ' ' + - cls.RULE_ID - ) - cls.openstack( - 'network qos policy delete ' + - cls.QOS_POLICY_NAME - ) - cls.assertOutput('', raw_output) - finally: - super(NetworkQosRuleTestsMinimumBandwidth, cls).tearDownClass() def setUp(self): super(NetworkQosRuleTestsMinimumBandwidth, self).setUp() @@ -72,72 +28,67 @@ def setUp(self): if not self.haz_network: self.skipTest("No Network service present") + self.QOS_POLICY_NAME = 'qos_policy_' + uuid.uuid4().hex + + self.openstack( + 'network qos policy create ' + + self.QOS_POLICY_NAME + ) + self.addCleanup(self.openstack, + 'network qos policy delete ' + self.QOS_POLICY_NAME) + cmd_output = json.loads(self.openstack( + 'network qos rule create -f json ' + + '--type minimum-bandwidth ' + + '--min-kbps 2800 ' + + '--egress ' + + self.QOS_POLICY_NAME + )) + self.RULE_ID = cmd_output['id'] + self.addCleanup(self.openstack, + 'network qos rule delete ' + + self.QOS_POLICY_NAME + ' ' + + self.RULE_ID) + self.assertTrue(self.RULE_ID) + + def test_qos_rule_create_delete(self): + # This is to check the output of qos rule delete + policy_name = uuid.uuid4().hex + self.openstack('network qos policy create -f json ' + policy_name) + self.addCleanup(self.openstack, + 'network qos policy delete ' + policy_name) + rule = json.loads(self.openstack( + 'network qos rule create -f json ' + + '--type minimum-bandwidth ' + + '--min-kbps 2800 ' + + '--egress ' + policy_name + )) + raw_output = self.openstack( + 'network qos rule delete ' + + policy_name + ' ' + rule['id']) + self.assertEqual('', raw_output) + def test_qos_rule_list(self): - opts = self.get_opts(self.HEADERS) - raw_output = self.openstack('network qos rule list ' - + self.QOS_POLICY_NAME + opts) - self.assertIn(self.RULE_ID, raw_output) + cmd_output = json.loads(self.openstack( + 'network qos rule list -f json ' + self.QOS_POLICY_NAME)) + self.assertIn(self.RULE_ID, [rule['ID'] for rule in cmd_output]) def test_qos_rule_show(self): - opts = self.get_opts(self.FIELDS) - raw_output = self.openstack('network qos rule show ' + - self.QOS_POLICY_NAME + ' ' + self.RULE_ID + - opts) - self.assertEqual(self.RULE_ID, raw_output) + cmd_output = json.loads(self.openstack( + 'network qos rule show -f json ' + + self.QOS_POLICY_NAME + ' ' + self.RULE_ID)) + self.assertEqual(self.RULE_ID, cmd_output['id']) def test_qos_rule_set(self): - self.openstack('network qos rule set --min-kbps ' + - str(self.MIN_KBPS_MODIFIED) + ' ' + + self.openstack('network qos rule set --min-kbps 7500 ' + self.QOS_POLICY_NAME + ' ' + self.RULE_ID) - opts = self.get_opts(['min_kbps']) - raw_output = self.openstack('network qos rule show ' + - self.QOS_POLICY_NAME + ' ' + self.RULE_ID + - opts) - self.assertEqual(str(self.MIN_KBPS_MODIFIED) + "\n", raw_output) + cmd_output = json.loads(self.openstack( + 'network qos rule show -f json ' + + self.QOS_POLICY_NAME + ' ' + self.RULE_ID)) + self.assertEqual(7500, cmd_output['min_kbps']) class NetworkQosRuleTestsDSCPMarking(common.NetworkTests): """Functional tests for QoS DSCP marking rule""" - RULE_ID = None - QOS_POLICY_NAME = 'qos_policy_' + uuid.uuid4().hex - DSCP_MARK = 8 - DSCP_MARK_MODIFIED = 32 - HEADERS = ['ID'] - FIELDS = ['id'] - TYPE = 'dscp-marking' - - @classmethod - def setUpClass(cls): - common.NetworkTests.setUpClass() - if cls.haz_network: - opts = cls.get_opts(cls.FIELDS) - cls.openstack( - 'network qos policy create ' + - cls.QOS_POLICY_NAME - ) - cls.RULE_ID = cls.openstack( - 'network qos rule create ' + - '--type ' + cls.TYPE + ' ' + - '--dscp-mark ' + str(cls.DSCP_MARK) + ' ' + - cls.QOS_POLICY_NAME + - opts - ) - cls.assertsOutputNotNone(cls.RULE_ID) - - @classmethod - def tearDownClass(cls): - try: - if cls.haz_network: - raw_output = cls.openstack( - 'network qos rule delete ' + - cls.QOS_POLICY_NAME + ' ' + - cls.RULE_ID - ) - cls.openstack( - 'network qos policy delete ' + cls.QOS_POLICY_NAME) - cls.assertOutput('', raw_output) - finally: - super(NetworkQosRuleTestsDSCPMarking, cls).tearDownClass() def setUp(self): super(NetworkQosRuleTestsDSCPMarking, self).setUp() @@ -145,78 +96,63 @@ def setUp(self): if not self.haz_network: self.skipTest("No Network service present") + self.QOS_POLICY_NAME = 'qos_policy_' + uuid.uuid4().hex + self.openstack( + 'network qos policy create ' + + self.QOS_POLICY_NAME + ) + self.addCleanup(self.openstack, + 'network qos policy delete ' + self.QOS_POLICY_NAME) + cmd_output = json.loads(self.openstack( + 'network qos rule create -f json ' + + '--type dscp-marking ' + + '--dscp-mark 8 ' + + self.QOS_POLICY_NAME + )) + self.RULE_ID = cmd_output['id'] + self.addCleanup(self.openstack, + 'network qos rule delete ' + + self.QOS_POLICY_NAME + ' ' + self.RULE_ID) + self.assertTrue(self.RULE_ID) + + def test_qos_rule_create_delete(self): + # This is to check the output of qos rule delete + policy_name = uuid.uuid4().hex + self.openstack('network qos policy create -f json ' + policy_name) + self.addCleanup(self.openstack, + 'network qos policy delete ' + policy_name) + rule = json.loads(self.openstack( + 'network qos rule create -f json ' + + '--type dscp-marking ' + + '--dscp-mark 8 ' + policy_name + )) + raw_output = self.openstack( + 'network qos rule delete ' + + policy_name + ' ' + rule['id']) + self.assertEqual('', raw_output) + def test_qos_rule_list(self): - opts = self.get_opts(self.HEADERS) - raw_output = self.openstack('network qos rule list ' - + self.QOS_POLICY_NAME + opts) - self.assertIn(self.RULE_ID, raw_output) + cmd_output = json.loads(self.openstack( + 'network qos rule list -f json ' + self.QOS_POLICY_NAME)) + self.assertIn(self.RULE_ID, [rule['ID'] for rule in cmd_output]) def test_qos_rule_show(self): - opts = self.get_opts(self.FIELDS) - raw_output = self.openstack('network qos rule show ' + - self.QOS_POLICY_NAME + ' ' + self.RULE_ID + - opts) - self.assertEqual(self.RULE_ID, raw_output) + cmd_output = json.loads(self.openstack( + 'network qos rule show -f json ' + + self.QOS_POLICY_NAME + ' ' + self.RULE_ID)) + self.assertEqual(self.RULE_ID, cmd_output['id']) def test_qos_rule_set(self): - self.openstack('network qos rule set --dscp-mark ' + - str(self.DSCP_MARK_MODIFIED) + ' ' + + self.openstack('network qos rule set --dscp-mark 32 ' + self.QOS_POLICY_NAME + ' ' + self.RULE_ID) - opts = self.get_opts(['dscp_mark']) - raw_output = self.openstack('network qos rule show ' + - self.QOS_POLICY_NAME + ' ' + self.RULE_ID + - opts) - self.assertEqual(str(self.DSCP_MARK_MODIFIED) + "\n", raw_output) + cmd_output = json.loads(self.openstack( + 'network qos rule show -f json ' + + self.QOS_POLICY_NAME + ' ' + self.RULE_ID)) + self.assertEqual(32, cmd_output['dscp_mark']) class NetworkQosRuleTestsBandwidthLimit(common.NetworkTests): """Functional tests for QoS bandwidth limit rule""" - RULE_ID = None - QOS_POLICY_NAME = 'qos_policy_' + uuid.uuid4().hex - MAX_KBPS = 10000 - MAX_KBPS_MODIFIED = 15000 - MAX_BURST_KBITS = 1400 - MAX_BURST_KBITS_MODIFIED = 1800 - RULE_DIRECTION = 'egress' - RULE_DIRECTION_MODIFIED = 'ingress' - HEADERS = ['ID'] - FIELDS = ['id'] - TYPE = 'bandwidth-limit' - - @classmethod - def setUpClass(cls): - common.NetworkTests.setUpClass() - if cls.haz_network: - opts = cls.get_opts(cls.FIELDS) - cls.openstack( - 'network qos policy create ' + - cls.QOS_POLICY_NAME - ) - cls.RULE_ID = cls.openstack( - 'network qos rule create ' + - '--type ' + cls.TYPE + ' ' + - '--max-kbps ' + str(cls.MAX_KBPS) + ' ' + - '--max-burst-kbits ' + str(cls.MAX_BURST_KBITS) + ' ' + - '--' + cls.RULE_DIRECTION + ' ' + - cls.QOS_POLICY_NAME + - opts - ) - cls.assertsOutputNotNone(cls.RULE_ID) - - @classmethod - def tearDownClass(cls): - try: - if cls.haz_network: - raw_output = cls.openstack( - 'network qos rule delete ' + - cls.QOS_POLICY_NAME + ' ' + - cls.RULE_ID - ) - cls.openstack( - 'network qos policy delete ' + cls.QOS_POLICY_NAME) - cls.assertOutput('', raw_output) - finally: - super(NetworkQosRuleTestsBandwidthLimit, cls).tearDownClass() def setUp(self): super(NetworkQosRuleTestsBandwidthLimit, self).setUp() @@ -224,30 +160,65 @@ def setUp(self): if not self.haz_network: self.skipTest("No Network service present") + self.QOS_POLICY_NAME = 'qos_policy_' + uuid.uuid4().hex + self.openstack( + 'network qos policy create ' + + self.QOS_POLICY_NAME + ) + self.addCleanup(self.openstack, + 'network qos policy delete ' + self.QOS_POLICY_NAME) + cmd_output = json.loads(self.openstack( + 'network qos rule create -f json ' + + '--type bandwidth-limit ' + + '--max-kbps 10000 ' + + '--max-burst-kbits 1400 ' + + '--egress ' + + self.QOS_POLICY_NAME + )) + self.RULE_ID = cmd_output['id'] + self.addCleanup(self.openstack, + 'network qos rule delete ' + + self.QOS_POLICY_NAME + ' ' + self.RULE_ID) + self.assertTrue(self.RULE_ID) + + def test_qos_rule_create_delete(self): + # This is to check the output of qos rule delete + policy_name = uuid.uuid4().hex + self.openstack('network qos policy create -f json ' + policy_name) + self.addCleanup(self.openstack, + 'network qos policy delete ' + policy_name) + rule = json.loads(self.openstack( + 'network qos rule create -f json ' + + '--type bandwidth-limit ' + + '--max-kbps 10000 ' + + '--max-burst-kbits 1400 ' + + '--egress ' + policy_name + )) + raw_output = self.openstack( + 'network qos rule delete ' + + policy_name + ' ' + rule['id']) + self.assertEqual('', raw_output) + def test_qos_rule_list(self): - opts = self.get_opts(self.HEADERS) - raw_output = self.openstack('network qos rule list ' - + self.QOS_POLICY_NAME + opts) - self.assertIn(self.RULE_ID, raw_output) + cmd_output = json.loads(self.openstack( + 'network qos rule list -f json ' + + self.QOS_POLICY_NAME)) + self.assertIn(self.RULE_ID, [rule['ID'] for rule in cmd_output]) def test_qos_rule_show(self): - opts = self.get_opts(self.FIELDS) - raw_output = self.openstack('network qos rule show ' + - self.QOS_POLICY_NAME + ' ' + self.RULE_ID + - opts) - self.assertEqual(self.RULE_ID, raw_output) + cmd_output = json.loads(self.openstack( + 'network qos rule show -f json ' + + self.QOS_POLICY_NAME + ' ' + self.RULE_ID)) + self.assertEqual(self.RULE_ID, cmd_output['id']) def test_qos_rule_set(self): - self.openstack('network qos rule set --max-kbps ' + - str(self.MAX_KBPS_MODIFIED) + ' --max-burst-kbits ' + - str(self.MAX_BURST_KBITS_MODIFIED) + ' ' + - '--' + self.RULE_DIRECTION_MODIFIED + ' ' + + self.openstack('network qos rule set --max-kbps 15000 ' + + '--max-burst-kbits 1800 ' + + '--ingress ' + self.QOS_POLICY_NAME + ' ' + self.RULE_ID) - opts = self.get_opts(['direction', 'max_burst_kbps', 'max_kbps']) - raw_output = self.openstack('network qos rule show ' + - self.QOS_POLICY_NAME + ' ' + self.RULE_ID + - opts) - expected = (str(self.RULE_DIRECTION_MODIFIED) + "\n" + - str(self.MAX_BURST_KBITS_MODIFIED) + "\n" + - str(self.MAX_KBPS_MODIFIED) + "\n") - self.assertEqual(expected, raw_output) + cmd_output = json.loads(self.openstack( + 'network qos rule show -f json ' + + self.QOS_POLICY_NAME + ' ' + self.RULE_ID)) + self.assertEqual(15000, cmd_output['max_kbps']) + self.assertEqual(1800, cmd_output['max_burst_kbps']) + self.assertEqual('ingress', cmd_output['direction']) diff --git a/openstackclient/tests/functional/network/v2/test_network_qos_rule_type.py b/openstackclient/tests/functional/network/v2/test_network_qos_rule_type.py index d76129367c..a6ee3e1006 100644 --- a/openstackclient/tests/functional/network/v2/test_network_qos_rule_type.py +++ b/openstackclient/tests/functional/network/v2/test_network_qos_rule_type.py @@ -13,6 +13,8 @@ # License for the specific language governing permissions and limitations # under the License. +import json + from openstackclient.tests.functional.network.v2 import common @@ -29,6 +31,7 @@ def setUp(self): self.skipTest("No Network service present") def test_qos_rule_type_list(self): - raw_output = self.openstack('network qos rule type list') + cmd_output = json.loads(self.openstack( + 'network qos rule type list -f json')) for rule_type in self.AVAILABLE_RULE_TYPES: - self.assertIn(rule_type, raw_output) + self.assertIn(rule_type, [x['Type'] for x in cmd_output]) From c1404f14b88c38e3a6fb0a9dc12946241de2fd80 Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Wed, 23 Aug 2017 21:15:24 +0000 Subject: [PATCH 1775/3095] Convert remaining network functional tests to JSON Change-Id: Ib7dff5506cc69549b5b1fbb8bf6e649468563dd6 --- .../network/v2/test_network_rbac.py | 85 ++++++++----------- .../v2/test_network_service_provider.py | 9 +- 2 files changed, 39 insertions(+), 55 deletions(-) diff --git a/openstackclient/tests/functional/network/v2/test_network_rbac.py b/openstackclient/tests/functional/network/v2/test_network_rbac.py index 2206761f04..3bbe4f27cf 100644 --- a/openstackclient/tests/functional/network/v2/test_network_rbac.py +++ b/openstackclient/tests/functional/network/v2/test_network_rbac.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import json import uuid from openstackclient.tests.functional.network.v2 import common @@ -22,69 +23,51 @@ class NetworkRBACTests(common.NetworkTests): HEADERS = ['ID'] FIELDS = ['id'] - @classmethod - def setUpClass(cls): - common.NetworkTests.setUpClass() - if cls.haz_network: - cls.NET_NAME = uuid.uuid4().hex - cls.PROJECT_NAME = uuid.uuid4().hex - - opts = cls.get_opts(cls.FIELDS) - raw_output = cls.openstack( - 'network create ' + cls.NET_NAME + opts - ) - cls.OBJECT_ID = raw_output.strip('\n') - opts = cls.get_opts(['id', 'object_id']) - raw_output = cls.openstack( - 'network rbac create ' + - cls.OBJECT_ID + - ' --action access_as_shared' + - ' --target-project admin' + - ' --type network' + opts - ) - cls.ID, object_id, rol = tuple(raw_output.split('\n')) - cls.assertOutput(cls.OBJECT_ID, object_id) - - @classmethod - def tearDownClass(cls): - try: - if cls.haz_network: - raw_output_rbac = cls.openstack( - 'network rbac delete ' + cls.ID - ) - raw_output_network = cls.openstack( - 'network delete ' + cls.OBJECT_ID - ) - cls.assertOutput('', raw_output_rbac) - cls.assertOutput('', raw_output_network) - finally: - super(NetworkRBACTests, cls).tearDownClass() - def setUp(self): super(NetworkRBACTests, self).setUp() # Nothing in this class works with Nova Network if not self.haz_network: self.skipTest("No Network service present") + self.NET_NAME = uuid.uuid4().hex + self.PROJECT_NAME = uuid.uuid4().hex + + cmd_output = json.loads(self.openstack( + 'network create -f json ' + self.NET_NAME + )) + self.addCleanup(self.openstack, + 'network delete ' + cmd_output['id']) + self.OBJECT_ID = cmd_output['id'] + + cmd_output = json.loads(self.openstack( + 'network rbac create -f json ' + + self.OBJECT_ID + + ' --action access_as_shared' + + ' --target-project admin' + + ' --type network' + )) + self.addCleanup(self.openstack, + 'network rbac delete ' + cmd_output['id']) + self.ID = cmd_output['id'] + self.assertEqual(self.OBJECT_ID, cmd_output['object_id']) + def test_network_rbac_list(self): - opts = self.get_opts(self.HEADERS) - raw_output = self.openstack('network rbac list' + opts) - self.assertIn(self.ID, raw_output) + cmd_output = json.loads(self.openstack('network rbac list -f json')) + self.assertIn(self.ID, [rbac['ID'] for rbac in cmd_output]) def test_network_rbac_show(self): - opts = self.get_opts(self.FIELDS) - raw_output = self.openstack('network rbac show ' + self.ID + opts) - self.assertEqual(self.ID + "\n", raw_output) + cmd_output = json.loads(self.openstack( + 'network rbac show -f json ' + self.ID)) + self.assertEqual(self.ID, cmd_output['id']) def test_network_rbac_set(self): - opts = self.get_opts(self.FIELDS) - project_id = self.openstack( - 'project create ' + self.PROJECT_NAME + opts) + project_id = json.loads(self.openstack( + 'project create -f json ' + self.PROJECT_NAME))['id'] self.openstack('network rbac set ' + self.ID + ' --target-project ' + self.PROJECT_NAME) - opts = self.get_opts(['target_project_id']) - raw_output_rbac = self.openstack('network rbac show ' + self.ID + opts) + cmd_output_rbac = json.loads(self.openstack( + 'network rbac show -f json ' + self.ID)) + self.assertEqual(project_id, cmd_output_rbac['target_project_id']) raw_output_project = self.openstack( 'project delete ' + self.PROJECT_NAME) - self.assertEqual(project_id, raw_output_rbac) - self.assertOutput('', raw_output_project) + self.assertEqual('', raw_output_project) diff --git a/openstackclient/tests/functional/network/v2/test_network_service_provider.py b/openstackclient/tests/functional/network/v2/test_network_service_provider.py index 8ed44dd909..999b7eb713 100644 --- a/openstackclient/tests/functional/network/v2/test_network_service_provider.py +++ b/openstackclient/tests/functional/network/v2/test_network_service_provider.py @@ -13,14 +13,14 @@ # License for the specific language governing permissions and limitations # under the License. +import json + from openstackclient.tests.functional.network.v2 import common class TestNetworkServiceProvider(common.NetworkTests): """Functional tests for network service provider""" - SERVICE_TYPE = 'L3_ROUTER_NAT' - def setUp(self): super(TestNetworkServiceProvider, self).setUp() # Nothing in this class works with Nova Network @@ -28,5 +28,6 @@ def setUp(self): self.skipTest("No Network service present") def test_network_service_provider_list(self): - raw_output = self.openstack('network service provider list') - self.assertIn(self.SERVICE_TYPE, raw_output) + cmd_output = json.loads(self.openstack( + 'network service provider list -f json')) + self.assertIn('L3_ROUTER_NAT', [x['Service Type'] for x in cmd_output]) From e62587625f80b4496b2b092be7efb00153d43a23 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 24 Aug 2017 11:48:43 +0000 Subject: [PATCH 1776/3095] Updated from global requirements Change-Id: I45705bad68f0615dbf09d46a2a487b9fed1abb10 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f449e200ac..1a0c75b95b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,5 +13,5 @@ oslo.i18n!=3.15.2,>=2.1.0 # Apache-2.0 oslo.utils>=3.20.0 # Apache-2.0 python-glanceclient>=2.8.0 # Apache-2.0 python-keystoneclient>=3.8.0 # Apache-2.0 -python-novaclient>=9.0.0 # Apache-2.0 +python-novaclient>=9.1.0 # Apache-2.0 python-cinderclient>=3.1.0 # Apache-2.0 From ff91e26983578bf1b77e1d68fb1ae26067eed7ea Mon Sep 17 00:00:00 2001 From: lihaijing Date: Wed, 23 Aug 2017 11:23:27 +0800 Subject: [PATCH 1777/3095] Update image cli doc and fix some typos Change-Id: I0a8d095e51a96804c97612e28fac2d00aa94c638 Closes-Bug: #1711284 --- doc/source/cli/command-objects/image.rst | 64 ++++++++++++++---------- openstackclient/image/v2/image.py | 2 +- 2 files changed, 38 insertions(+), 28 deletions(-) diff --git a/doc/source/cli/command-objects/image.rst b/doc/source/cli/command-objects/image.rst index 92efd0a546..e2257bbbba 100644 --- a/doc/source/cli/command-objects/image.rst +++ b/doc/source/cli/command-objects/image.rst @@ -16,7 +16,8 @@ Associate project with image openstack image add project [--project-domain ] - + + .. option:: --project-domain @@ -60,7 +61,8 @@ Create/upload an image [--public | --private | --community | --shared] [--property [...] ] [--tag [...] ] - [--project [--project-domain ]] + [--project ] + [--project-domain ] .. option:: --id @@ -153,11 +155,11 @@ Create/upload an image .. option:: --property - Set a property on this image (repeat for multiple values) + Set a property on this image (repeat option to set multiple properties) .. option:: --tag - Set a tag on this image (repeat for multiple values) + Set a tag on this image (repeat option to set multiple tags) .. versionadded:: 2 @@ -205,13 +207,12 @@ List available images openstack image list [--public | --private | --shared] [--property ] + [--name ] + [--status ] [--long] [--sort [:]] [--limit ] [--marker ] - [--name ] - [--status ] - .. option:: --public @@ -231,6 +232,18 @@ List available images Filter output based on property +.. option:: --name + + Filter images based on name + + *Image version 2 only.* + +.. option:: --status + + Filter images based on status + + *Image version 2 only* + .. option:: --long List additional fields in output @@ -251,15 +264,6 @@ List available images The last image of the previous page. Display list of images after marker. Display all images if not specified. (name or ID) -.. option:: --name - - Filter images based on name - -.. option:: --status - - Filter images based on status - - *Image version 2 only* image remove project @@ -272,7 +276,7 @@ Disassociate project with image .. program:: image remove project .. code:: bash - openstack image remove remove + openstack image remove project [--project-domain ] @@ -347,8 +351,9 @@ Set image properties [--os-distro ] [--os-version ] [--ramdisk-id ] - [--activate|--deactivate] - [--project [--project-domain ]] + [--deactivate | --activate] + [--project ] + [--project-domain ] [--accept | --reject | --pending] @@ -460,7 +465,7 @@ Set image properties .. option:: --tag - Set a tag on this image (repeat for multiple values) + Set a tag on this image (repeat option to set multiple tags) .. versionadded:: 2 @@ -500,15 +505,15 @@ Set image properties .. versionadded:: 2 -.. option:: --activate +.. option:: --deactivate - Activate the image. + Deactivate the image. .. versionadded:: 2 -.. option:: --deactivate +.. option:: --activate - Deactivate the image. + Activate the image. .. versionadded:: 2 @@ -568,8 +573,13 @@ Display image details .. code:: bash openstack image show + [--human-readable] +.. option:: --human-readable + + Print image size in a human-friendly format. + .. _image_show-image: .. describe:: @@ -585,16 +595,16 @@ Unset image tags or properties .. program:: image unset .. code:: bash - openstack image set + openstack image unset [--tag ] - [--property ] + [--property ] .. option:: --tag Unset a tag on this image (repeat option to unset multiple tags) -.. option:: --property +.. option:: --property Unset a property on this image (repeat option to unset multiple properties) diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 30dd938055..a2e45fc707 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -995,7 +995,7 @@ def get_parser(self, prog_name): parser.add_argument( "--property", dest="properties", - metavar="", + metavar="", default=[], action='append', help=_("Unset a property on this image " From de2af66c1622115dcb28aca88aa62ce5b177c771 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Fri, 1 Sep 2017 12:47:11 +0000 Subject: [PATCH 1778/3095] Updated from global requirements Change-Id: If52bacff8458010d2762bfac5ecea29f5fb54f01 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 1a0c75b95b..b578903665 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ six>=1.9.0 # MIT Babel!=2.4.0,>=2.3.4 # BSD cliff>=2.8.0 # Apache-2.0 -keystoneauth1>=3.1.0 # Apache-2.0 +keystoneauth1>=3.2.0 # Apache-2.0 openstacksdk>=0.9.17 # Apache-2.0 osc-lib>=1.7.0 # Apache-2.0 oslo.i18n!=3.15.2,>=2.1.0 # Apache-2.0 From 8f4440a6de4425afb77f647ae2dca9452b1166f0 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 6 Sep 2017 03:15:36 +0000 Subject: [PATCH 1779/3095] Updated from global requirements Change-Id: I1c5bc67d82b7a123530a7e8a6af5ca4f7faf53f0 --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index b578903665..c11dfd8e20 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,11 +7,11 @@ six>=1.9.0 # MIT Babel!=2.4.0,>=2.3.4 # BSD cliff>=2.8.0 # Apache-2.0 keystoneauth1>=3.2.0 # Apache-2.0 -openstacksdk>=0.9.17 # Apache-2.0 +openstacksdk>=0.9.18 # Apache-2.0 osc-lib>=1.7.0 # Apache-2.0 oslo.i18n!=3.15.2,>=2.1.0 # Apache-2.0 oslo.utils>=3.20.0 # Apache-2.0 python-glanceclient>=2.8.0 # Apache-2.0 python-keystoneclient>=3.8.0 # Apache-2.0 python-novaclient>=9.1.0 # Apache-2.0 -python-cinderclient>=3.1.0 # Apache-2.0 +python-cinderclient>=3.2.0 # Apache-2.0 From 61025bf1023328893904c3cc00bea28a238be544 Mon Sep 17 00:00:00 2001 From: Huan Xiong Date: Fri, 8 Sep 2017 06:28:15 +0000 Subject: [PATCH 1780/3095] Useless line of code in shell.py Shell.py contains lines to import osprofiler. This is useless because osprofiler options support code was moved to osc-lib. Removed it. Change-Id: Ibec17700c87df908640848c3787d190ca66a7bcf Closes-Bug: #1707103 --- openstackclient/shell.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 2e384f8fc3..58a7712026 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -21,7 +21,6 @@ from osc_lib.api import auth from osc_lib import shell -from oslo_utils import importutils import six import openstackclient @@ -29,8 +28,6 @@ from openstackclient.common import clientmanager from openstackclient.common import commandmanager -osprofiler_profiler = importutils.try_import("osprofiler.profiler") - DEFAULT_DOMAIN = 'default' From 104d6d0e316e74cb8a700dea09aa3566a6d8e069 Mon Sep 17 00:00:00 2001 From: Jens Harbott Date: Tue, 12 Sep 2017 14:21:49 +0000 Subject: [PATCH 1781/3095] Fix output for subnet show The use_default_subnet_pool column is never returned by the API, it is only being used in the request to create a subnet. So make sure that we do not show it when displaying a subnet. Change-Id: Ie021149cceb8f89b779ad0f3c13ac60420509671 Related-Bug: 1668223 --- openstackclient/network/v2/subnet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index c71358a52f..2c71e1e085 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -135,7 +135,7 @@ def _get_columns(item): 'tenant_id': 'project_id', } # Do not show this column when displaying a subnet - invisible_columns = ['use_default_subnetpool'] + invisible_columns = ['use_default_subnet_pool'] return sdk_utils.get_osc_show_columns_for_sdk_resource( item, column_map, From ce468209c4664eb3680abcaddecc103e0a636e72 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Sat, 9 Sep 2017 08:48:58 -0500 Subject: [PATCH 1782/3095] Skip Volume v1 functional tests if v1 not present Volume v1 is gone in Queens Just skip it for now until DevStack does not create a v1 endpoint Change-Id: I2aa2f78b0d5c8ac2048c922c7835e5c4574028cc --- .../tests/functional/volume/v1/common.py | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/openstackclient/tests/functional/volume/v1/common.py b/openstackclient/tests/functional/volume/v1/common.py index 4978cea31c..bb3c674ea4 100644 --- a/openstackclient/tests/functional/volume/v1/common.py +++ b/openstackclient/tests/functional/volume/v1/common.py @@ -12,14 +12,35 @@ import fixtures -from openstackclient.tests.functional.volume import base +from openstackclient.tests.functional import base +from openstackclient.tests.functional.volume import base as volume_base -class BaseVolumeTests(base.BaseVolumeTests): - """Base class for Volume functional tests. """ +class BaseVolumeTests(volume_base.BaseVolumeTests): + """Base class for Volume functional tests""" + + @classmethod + def setUpClass(cls): + super(BaseVolumeTests, cls).setUpClass() + # TODO(dtroyer): This needs to be updated to specifically check for + # Volume v1 rather than just 'volume', but for now + # that is enough until we get proper version negotiation + cls.haz_volume_v1 = base.is_service_enabled('volume') def setUp(self): super(BaseVolumeTests, self).setUp() + + # This class requires Volume v1 + # if not self.haz_volume_v1: + # self.skipTest("No Volume v1 service present") + + # TODO(dtroyer): We really want the above to work but right now + # (12Sep2017) DevStack still creates a 'volume' + # service type even though there is no service behind + # it. Until that is fixed we need to just skip the + # volume v1 functional tests in master. + self.skipTest("No Volume v1 service present") + ver_fixture = fixtures.EnvironmentVariable( 'OS_VOLUME_API_VERSION', '1' ) From 760e91abcf01180651447373e37dc99fafba8f30 Mon Sep 17 00:00:00 2001 From: Gage Hugo Date: Tue, 12 Sep 2017 16:19:49 -0500 Subject: [PATCH 1783/3095] Correct import of keystoneauth1 session keystoneclient.session has been long deprecated in favor of keystoneauth1.session. This change corrects the import in the tests to use the correct library's session. Change-Id: Ic24ebde59e4b9eb70d6f14c1e0536f8d24f0de73 --- openstackclient/tests/unit/api/test_compute_v2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/tests/unit/api/test_compute_v2.py b/openstackclient/tests/unit/api/test_compute_v2.py index 56b35937c7..4f3b80316b 100644 --- a/openstackclient/tests/unit/api/test_compute_v2.py +++ b/openstackclient/tests/unit/api/test_compute_v2.py @@ -13,7 +13,7 @@ """Compute v2 API Library Tests""" -from keystoneclient import session +from keystoneauth1 import session from osc_lib import exceptions as osc_lib_exceptions from requests_mock.contrib import fixture From 95378bb37d2ba29c7055c9510dd2b5e5513e281f Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 13 Sep 2017 00:15:31 +0000 Subject: [PATCH 1784/3095] Updated from global requirements Change-Id: I30be8748ef78604eb63c61e1dcf5fc5a740822aa --- requirements.txt | 4 ++-- test-requirements.txt | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/requirements.txt b/requirements.txt index c11dfd8e20..c8789496e9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,8 +9,8 @@ cliff>=2.8.0 # Apache-2.0 keystoneauth1>=3.2.0 # Apache-2.0 openstacksdk>=0.9.18 # Apache-2.0 osc-lib>=1.7.0 # Apache-2.0 -oslo.i18n!=3.15.2,>=2.1.0 # Apache-2.0 -oslo.utils>=3.20.0 # Apache-2.0 +oslo.i18n>=3.15.3 # Apache-2.0 +oslo.utils>=3.28.0 # Apache-2.0 python-glanceclient>=2.8.0 # Apache-2.0 python-keystoneclient>=3.8.0 # Apache-2.0 python-novaclient>=9.1.0 # Apache-2.0 diff --git a/test-requirements.txt b/test-requirements.txt index 199c5d7b46..8ff7a00f03 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -15,7 +15,7 @@ requests-mock>=1.1.0 # Apache-2.0 sphinx>=1.6.2 # BSD stevedore>=1.20.0 # Apache-2.0 os-client-config>=1.28.0 # Apache-2.0 -os-testr>=0.8.0 # Apache-2.0 +os-testr>=1.0.0 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD testtools>=1.4.0 # MIT tempest>=16.1.0 # Apache-2.0 @@ -24,12 +24,12 @@ bandit>=1.1.0 # Apache-2.0 wrapt>=1.7.0 # BSD License # Install these to generate sphinx autodocs -aodhclient>=0.7.0 # Apache-2.0 -gnocchiclient>=2.7.0 # Apache-2.0 +aodhclient>=0.9.0 # Apache-2.0 +gnocchiclient>=3.3.1 # Apache-2.0 python-barbicanclient!=4.5.0,!=4.5.1,>=4.0.0 # Apache-2.0 python-congressclient<2000,>=1.3.0 # Apache-2.0 -python-designateclient>=1.5.0 # Apache-2.0 -python-heatclient>=1.6.1 # Apache-2.0 +python-designateclient>=2.7.0 # Apache-2.0 +python-heatclient>=1.10.0 # Apache-2.0 python-ironicclient>=1.14.0 # Apache-2.0 python-ironic-inspector-client>=1.5.0 # Apache-2.0 python-karborclient>=0.6.0 # Apache-2.0 @@ -37,7 +37,7 @@ python-mistralclient>=3.1.0 # Apache-2.0 python-muranoclient>=0.8.2 # Apache-2.0 python-neutronclient>=6.3.0 # Apache-2.0 python-octaviaclient>=1.0.0 # Apache-2.0 -python-saharaclient>=1.1.0 # Apache-2.0 +python-saharaclient>=1.2.0 # Apache-2.0 python-searchlightclient>=1.0.0 #Apache-2.0 python-senlinclient>=1.1.0 # Apache-2.0 python-troveclient>=2.2.0 # Apache-2.0 From c7671dd9a41a7e3dcf5086bb180630c3b4ee0978 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 13 Sep 2017 13:02:59 +0000 Subject: [PATCH 1785/3095] Updated from global requirements Change-Id: Ibeed9c623a6ef1c641c121a2b5ee75920346056f --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 8ff7a00f03..19f22bb42b 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -7,7 +7,7 @@ coverage!=4.4,>=4.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD flake8-import-order==0.13 # LGPLv3 mock>=2.0.0 # BSD -openstackdocstheme>=1.16.0 # Apache-2.0 +openstackdocstheme>=1.17.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 reno>=2.5.0 # Apache-2.0 requests>=2.14.2 # Apache-2.0 From 7274e8efe620b076bfeb8f69e9a94a44b064cfb0 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 14 Sep 2017 16:31:53 -0500 Subject: [PATCH 1786/3095] Updates for stestr Change-Id: I860981f06e31abda3141a0cb4fd13a0d49080b50 --- .gitignore | 1 + .stestr.conf | 4 ++++ tox.ini | 3 +++ 3 files changed, 8 insertions(+) create mode 100644 .stestr.conf diff --git a/.gitignore b/.gitignore index 2c0139cf68..54cdd4fccf 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ *~ .coverage .idea +.stestr/ .testrepository .tox AUTHORS diff --git a/.stestr.conf b/.stestr.conf new file mode 100644 index 0000000000..75aec670d7 --- /dev/null +++ b/.stestr.conf @@ -0,0 +1,4 @@ +[DEFAULT] +test_path=${OS_TEST_PATH:-./openstackclient/tests/unit} +top_dir=./ +group_regex=([^\.]+\.)+ diff --git a/tox.ini b/tox.ini index 7716ffb4c4..3c1b3c387a 100644 --- a/tox.ini +++ b/tox.ini @@ -8,6 +8,9 @@ usedevelop = True install_command = {toxinidir}/tools/tox_install.sh {env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages} setenv = VIRTUAL_ENV={envdir} + OS_STDOUT_CAPTURE=1 + OS_STDERR_CAPTURE=1 + OS_TEST_TIMEOUT=60 deps = -r{toxinidir}/test-requirements.txt commands = ostestr {posargs} whitelist_externals = ostestr From 12552cee707da63a5f76adab5e16b34d738b027a Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 14 Sep 2017 17:29:19 -0400 Subject: [PATCH 1787/3095] Fix subunit collection in functional tests with ostestr>=1.0.0 With the release of os-testr 1.0.0 stestr is used internally and because of that the repository dir and commands to get subunit are different. This commit updates the post-test hook to get the subunit stream if ostestr>=1.0.0 is used. Change-Id: I2cce7f4780ce418398b17a5848def9072372841e --- openstackclient/tests/functional/post_test_hook.sh | 8 ++++++++ openstackclient/tests/functional/post_test_hook_tips.sh | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/openstackclient/tests/functional/post_test_hook.sh b/openstackclient/tests/functional/post_test_hook.sh index b7a39cfed9..2ae9178d56 100755 --- a/openstackclient/tests/functional/post_test_hook.sh +++ b/openstackclient/tests/functional/post_test_hook.sh @@ -15,6 +15,14 @@ function generate_testr_results { sudo gzip -9 $BASE/logs/testr_results.html sudo chown jenkins:jenkins $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz sudo chmod a+r $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz + elif [ -f .stestr/0 ]; then + sudo .tox/functional/bin/stestr last --subunit > $WORKSPACE/testrepository.subunit + sudo mv $WORKSPACE/testrepository.subunit $BASE/logs/testrepository.subunit + sudo .tox/functional/bin/subunit2html $BASE/logs/testrepository.subunit $BASE/logs/testr_results.html + sudo gzip -9 $BASE/logs/testrepository.subunit + sudo gzip -9 $BASE/logs/testr_results.html + sudo chown jenkins:jenkins $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz + sudo chmod a+r $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz fi } diff --git a/openstackclient/tests/functional/post_test_hook_tips.sh b/openstackclient/tests/functional/post_test_hook_tips.sh index 28ab958006..a4ad19ac62 100755 --- a/openstackclient/tests/functional/post_test_hook_tips.sh +++ b/openstackclient/tests/functional/post_test_hook_tips.sh @@ -19,6 +19,14 @@ function generate_testr_results { sudo gzip -9 $BASE/logs/testr_results.html sudo chown jenkins:jenkins $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz sudo chmod a+r $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz + elif [ -f .stestr/0 ]; then + sudo .tox/functional-tips/bin/stestr last --subunit > $WORKSPACE/testrepository.subunit + sudo mv $WORKSPACE/testrepository.subunit $BASE/logs/testrepository.subunit + sudo .tox/functional-tips/bin/subunit2html $BASE/logs/testrepository.subunit $BASE/logs/testr_results.html + sudo gzip -9 $BASE/logs/testrepository.subunit + sudo gzip -9 $BASE/logs/testr_results.html + sudo chown jenkins:jenkins $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz + sudo chmod a+r $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz fi } From 949e0cb3c6fab156d7c067158c3cfd616dbfc1c7 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 15 Sep 2017 12:33:55 -0500 Subject: [PATCH 1788/3095] Attempt to work around chronically failing server issues with aggregates and qos So yeah, this is not kosher for functional tests, but we're testing the client interaction, not the raciness of Nova or Neutron. Failure to delete is not our problem. Change-Id: I21043f1de0fbacee1aec63110fb12a7cff42e0a0 --- .../tests/functional/compute/v2/test_aggregate.py | 12 ++++++++++-- .../functional/network/v2/test_network_qos_policy.py | 7 +++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/openstackclient/tests/functional/compute/v2/test_aggregate.py b/openstackclient/tests/functional/compute/v2/test_aggregate.py index 1eba3ffe06..cf9d2bc082 100644 --- a/openstackclient/tests/functional/compute/v2/test_aggregate.py +++ b/openstackclient/tests/functional/compute/v2/test_aggregate.py @@ -67,7 +67,11 @@ def test_aggregate_list(self): '--property a=b ' + name1 ) - self.addCleanup(self.openstack, 'aggregate delete ' + name1) + self.addCleanup( + self.openstack, + 'aggregate delete ' + name1, + fail_ok=True, + ) name2 = uuid.uuid4().hex self.openstack( @@ -76,7 +80,11 @@ def test_aggregate_list(self): '--property c=d ' + name2 ) - self.addCleanup(self.openstack, 'aggregate delete ' + name2) + self.addCleanup( + self.openstack, + 'aggregate delete ' + name2, + fail_ok=True, + ) cmd_output = json.loads(self.openstack( 'aggregate list -f json' diff --git a/openstackclient/tests/functional/network/v2/test_network_qos_policy.py b/openstackclient/tests/functional/network/v2/test_network_qos_policy.py index 27c1b6aa8c..66bda1c711 100644 --- a/openstackclient/tests/functional/network/v2/test_network_qos_policy.py +++ b/openstackclient/tests/functional/network/v2/test_network_qos_policy.py @@ -33,8 +33,11 @@ def setUp(self): 'network qos policy create -f json ' + self.NAME )) - self.addCleanup(self.openstack, - 'network qos policy delete ' + self.NAME) + self.addCleanup( + self.openstack, + 'network qos policy delete ' + self.NAME, + fail_ok=True, + ) self.assertEqual(self.NAME, cmd_output['name']) def test_qos_rule_create_delete(self): From e785570406cd3f942618cfac0511f07d89b496e1 Mon Sep 17 00:00:00 2001 From: Hongbin Lu Date: Fri, 15 Sep 2017 18:23:28 -0400 Subject: [PATCH 1789/3095] Add python-zunclient plugin This patch adds the "appcontainer" commands to the docs and sets up the document generation for the python-zunclient plugin Change-Id: I58bd208e98bd059d9df03ee71dcb83779044f83a --- doc/source/cli/commands.rst | 5 +++++ doc/source/cli/plugin-commands.rst | 6 ++++++ doc/source/contributor/plugins.rst | 1 + test-requirements.txt | 1 + 4 files changed, 13 insertions(+) diff --git a/doc/source/cli/commands.rst b/doc/source/cli/commands.rst index 4716378621..f70eabc378 100644 --- a/doc/source/cli/commands.rst +++ b/doc/source/cli/commands.rst @@ -173,6 +173,11 @@ conflicts when creating new plugins. For a complete list check out * ``acl user``: (**Key Manager (Barbican)**) * ``action definition``: (**Workflow Engine (Mistral)**) * ``action execution``: (**Workflow Engine (Mistral)**) +* ``appcontainer``: (**Application Container (Zun)**) +* ``appcontainer host``: (**Application Container (Zun)**) +* ``appcontainer image``: (**Application Container (Zun)**) +* ``appcontainer network``: (**Application Container (Zun)**) +* ``appcontainer service``: (**Application Container (Zun)**) * ``baremetal``: (**Baremetal (Ironic)**) * ``claim``: (**Messaging (Zaqar)**) * ``cluster``: (**Clustering (Senlin)**) diff --git a/doc/source/cli/plugin-commands.rst b/doc/source/cli/plugin-commands.rst index 34efdc3d84..51c67db613 100644 --- a/doc/source/cli/plugin-commands.rst +++ b/doc/source/cli/plugin-commands.rst @@ -126,3 +126,9 @@ zaqar .. list-plugins:: openstack.messaging.v2 :detailed: + +zun +--- + +.. list-plugins:: openstack.container.v1 + :detailed: diff --git a/doc/source/contributor/plugins.rst b/doc/source/contributor/plugins.rst index 8bdfe484f0..e69dde8bd3 100644 --- a/doc/source/contributor/plugins.rst +++ b/doc/source/contributor/plugins.rst @@ -43,6 +43,7 @@ The following is a list of projects that are an OpenStackClient plugin. - python-troveclient - python-watcherclient - python-zaqarclient +- python-zunclient \*\* Note that some clients are not listed in global-requirements. diff --git a/test-requirements.txt b/test-requirements.txt index 19f22bb42b..4f349b8bad 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -42,3 +42,4 @@ python-searchlightclient>=1.0.0 #Apache-2.0 python-senlinclient>=1.1.0 # Apache-2.0 python-troveclient>=2.2.0 # Apache-2.0 python-zaqarclient>=1.0.0 # Apache-2.0 +python-zunclient>=0.2.0 # Apache-2.0 From 59bba7c0d238df7ac00a191b3e5e0131ec725c74 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 24 Aug 2017 16:46:40 -0500 Subject: [PATCH 1790/3095] Unroll the network qos policy functional tests These seem to have gotten a bit racy in the last revision, just do it the long way now. Change-Id: I3748b8b4f264dbfa8c991b32653682e5c86eeb4c --- .../network/v2/test_network_qos_policy.py | 75 +++++++++++-------- 1 file changed, 42 insertions(+), 33 deletions(-) diff --git a/openstackclient/tests/functional/network/v2/test_network_qos_policy.py b/openstackclient/tests/functional/network/v2/test_network_qos_policy.py index 66bda1c711..fc36f49032 100644 --- a/openstackclient/tests/functional/network/v2/test_network_qos_policy.py +++ b/openstackclient/tests/functional/network/v2/test_network_qos_policy.py @@ -28,49 +28,58 @@ def setUp(self): if not self.haz_network: self.skipTest("No Network service present") - self.NAME = uuid.uuid4().hex - cmd_output = json.loads(self.openstack( - 'network qos policy create -f json ' + - self.NAME - )) - self.addCleanup( - self.openstack, - 'network qos policy delete ' + self.NAME, - fail_ok=True, - ) - self.assertEqual(self.NAME, cmd_output['name']) - def test_qos_rule_create_delete(self): # This is to check the output of qos policy delete policy_name = uuid.uuid4().hex self.openstack('network qos policy create -f json ' + policy_name) raw_output = self.openstack( - 'network qos policy delete ' + policy_name) + 'network qos policy delete ' + + policy_name + ) self.assertEqual('', raw_output) def test_qos_policy_list(self): - cmd_output = json.loads(self.openstack( - 'network qos policy list -f json')) - self.assertIn(self.NAME, [p['Name'] for p in cmd_output]) + policy_name = uuid.uuid4().hex + json_output = json.loads(self.openstack( + 'network qos policy create -f json ' + + policy_name + )) + self.addCleanup(self.openstack, + 'network qos policy delete ' + policy_name) + self.assertEqual(policy_name, json_output['name']) - def test_qos_policy_show(self): - cmd_output = json.loads(self.openstack( - 'network qos policy show -f json ' + self.NAME)) - self.assertEqual(self.NAME, cmd_output['name']) + json_output = json.loads(self.openstack( + 'network qos policy list -f json' + )) + self.assertIn(policy_name, [p['Name'] for p in json_output]) def test_qos_policy_set(self): - self.openstack('network qos policy set --share ' + self.NAME) - cmd_output = json.loads(self.openstack( - 'network qos policy show -f json ' + self.NAME)) - self.assertTrue(cmd_output['shared']) + policy_name = uuid.uuid4().hex + json_output = json.loads(self.openstack( + 'network qos policy create -f json ' + + policy_name + )) + self.addCleanup(self.openstack, + 'network qos policy delete ' + policy_name) + self.assertEqual(policy_name, json_output['name']) - def test_qos_policy_default(self): - self.openstack('network qos policy set --default ' + self.NAME) - cmd_output = json.loads(self.openstack( - 'network qos policy show -f json ' + self.NAME)) - self.assertTrue(cmd_output['is_default']) + self.openstack( + 'network qos policy set ' + + '--share ' + + '--default ' + + policy_name + ) + + json_output = json.loads(self.openstack( + 'network qos policy show -f json ' + + policy_name + )) + self.assertTrue(json_output['shared']) + self.assertTrue(json_output['is_default']) - self.openstack('network qos policy set --no-default ' + self.NAME) - cmd_output = json.loads(self.openstack( - 'network qos policy show -f json ' + self.NAME)) - self.assertFalse(cmd_output['is_default']) + self.openstack('network qos policy set --no-default ' + policy_name) + json_output = json.loads(self.openstack( + 'network qos policy show -f json ' + + policy_name + )) + self.assertFalse(json_output['is_default']) From edebe558ee817a5378f808115e8b5d5409303f23 Mon Sep 17 00:00:00 2001 From: lihaijing Date: Mon, 18 Sep 2017 17:04:48 +0800 Subject: [PATCH 1791/3095] Add functional test cases for "volume qos associate/disassociate" Change-Id: I07b25bebb8a0ea18cdf042357be65c4ec6e1cfed Closes-Bug: #1717874 --- .../tests/functional/volume/v2/test_qos.py | 94 ++++++++++++++++++- 1 file changed, 93 insertions(+), 1 deletion(-) diff --git a/openstackclient/tests/functional/volume/v2/test_qos.py b/openstackclient/tests/functional/volume/v2/test_qos.py index aee10dcaed..888f12b1e2 100644 --- a/openstackclient/tests/functional/volume/v2/test_qos.py +++ b/openstackclient/tests/functional/volume/v2/test_qos.py @@ -122,4 +122,96 @@ def test_volume_qos_set_show_unset(self): cmd_output['properties'] ) - # TODO(qiangjiahui): Add tests for associate and disassociate volume type + def test_volume_qos_asso_disasso(self): + """Tests associate and disassociate qos with volume type""" + vol_type1 = uuid.uuid4().hex + vol_type2 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume type create -f json ' + + vol_type1 + )) + self.assertEqual( + vol_type1, + cmd_output['name'] + ) + cmd_output = json.loads(self.openstack( + 'volume type create -f json ' + + vol_type2 + )) + self.assertEqual( + vol_type2, + cmd_output['name'] + ) + self.addCleanup(self.openstack, 'volume type delete ' + vol_type1) + self.addCleanup(self.openstack, 'volume type delete ' + vol_type2) + + name = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume qos create -f json ' + + name + )) + self.assertEqual( + name, + cmd_output['name'] + ) + self.addCleanup(self.openstack, 'volume qos delete ' + name) + + # Test associate + raw_output = self.openstack( + 'volume qos associate ' + + name + ' ' + vol_type1 + ) + self.assertOutput('', raw_output) + raw_output = self.openstack( + 'volume qos associate ' + + name + ' ' + vol_type2 + ) + self.assertOutput('', raw_output) + + cmd_output = json.loads(self.openstack( + 'volume qos show -f json ' + + name + )) + types = cmd_output["associations"] + self.assertIn(vol_type1, types) + self.assertIn(vol_type2, types) + + # Test disassociate + raw_output = self.openstack( + 'volume qos disassociate ' + + '--volume-type ' + vol_type1 + + ' ' + name + ) + self.assertOutput('', raw_output) + cmd_output = json.loads(self.openstack( + 'volume qos show -f json ' + + name + )) + types = cmd_output["associations"] + self.assertNotIn(vol_type1, types) + self.assertIn(vol_type2, types) + + # Test disassociate --all + raw_output = self.openstack( + 'volume qos associate ' + + name + ' ' + vol_type1 + ) + self.assertOutput('', raw_output) + cmd_output = json.loads(self.openstack( + 'volume qos show -f json ' + + name + )) + types = cmd_output["associations"] + self.assertIn(vol_type1, types) + self.assertIn(vol_type2, types) + + raw_output = self.openstack( + 'volume qos disassociate ' + + '--all ' + name + ) + self.assertOutput('', raw_output) + cmd_output = json.loads(self.openstack( + 'volume qos show -f json ' + + name + )) + self.assertNotIn("associations", cmd_output.keys()) From 844623ccc47a8fc93f8e878376087b69396c4440 Mon Sep 17 00:00:00 2001 From: lihaijing Date: Tue, 19 Sep 2017 10:05:16 +0800 Subject: [PATCH 1792/3095] Add "volume service list --host" functional test case Change-Id: I467252d9fc6083fb891a8701d7992f16ce42556f --- .../tests/functional/volume/v2/test_service.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/openstackclient/tests/functional/volume/v2/test_service.py b/openstackclient/tests/functional/volume/v2/test_service.py index 6986fde69b..7ec43fe850 100644 --- a/openstackclient/tests/functional/volume/v2/test_service.py +++ b/openstackclient/tests/functional/volume/v2/test_service.py @@ -24,6 +24,7 @@ def test_volume_service_list(self): # Get the nonredundant services and hosts services = list(set([x['Binary'] for x in cmd_output])) + hosts = list(set([x['Host'] for x in cmd_output])) # Test volume service list --service cmd_output = json.loads(self.openstack( @@ -37,8 +38,17 @@ def test_volume_service_list(self): x['Binary'] ) - # TODO(zhiyong.dai): test volume service list --host after solving - # https://bugs.launchpad.net/python-openstackclient/+bug/1664451 + # Test volume service list --host + cmd_output = json.loads(self.openstack( + 'volume service list -f json ' + + '--host ' + + hosts[0] + )) + for x in cmd_output: + self.assertIn( + hosts[0], + x['Host'] + ) def test_volume_service_set(self): From 953d74b5d65b095428c783f80cc61f2d85909841 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 21 Sep 2017 03:50:03 +0000 Subject: [PATCH 1793/3095] Updated from global requirements Change-Id: I9509fb386bc28eaaeab8d27f4cfc7109ec261b73 --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 4f349b8bad..f8425ba22e 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -42,4 +42,4 @@ python-searchlightclient>=1.0.0 #Apache-2.0 python-senlinclient>=1.1.0 # Apache-2.0 python-troveclient>=2.2.0 # Apache-2.0 python-zaqarclient>=1.0.0 # Apache-2.0 -python-zunclient>=0.2.0 # Apache-2.0 +python-zunclient>=0.2.0 # Apache-2.0 From 41352cb375ec1c628c978402dd04a7e8cc405249 Mon Sep 17 00:00:00 2001 From: lingyongxu Date: Wed, 20 Sep 2017 13:52:06 +0800 Subject: [PATCH 1794/3095] Update the documentation link for doc migration This patch is proposed according to the Direction 10 of doc migration(https://etherpad.openstack.org/p/doc-migration-tracking). Change-Id: Ieca93d77bfc5e54486312f16122cc12c5bda0934 --- doc/source/cli/authentication.rst | 2 +- openstackclient/tests/functional/post_test_hook.sh | 2 +- openstackclient/tests/functional/post_test_hook_tips.sh | 2 +- releasenotes/source/conf.py | 2 +- setup.cfg | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/source/cli/authentication.rst b/doc/source/cli/authentication.rst index 5a1d279798..43fcd88482 100644 --- a/doc/source/cli/authentication.rst +++ b/doc/source/cli/authentication.rst @@ -7,7 +7,7 @@ Authentication OpenStackClient leverages `python-keystoneclient`_ authentication plugins to support a number of different authentication methods. -.. _`python-keystoneclient`: http://docs.openstack.org/developer/python-keystoneclient/authentication-plugins.html +.. _`python-keystoneclient`: https://docs.openstack.org/python-keystoneclient/latest/using-sessions.html#sharing-authentication-plugins Authentication Process ---------------------- diff --git a/openstackclient/tests/functional/post_test_hook.sh b/openstackclient/tests/functional/post_test_hook.sh index 2ae9178d56..e3b48ef4da 100755 --- a/openstackclient/tests/functional/post_test_hook.sh +++ b/openstackclient/tests/functional/post_test_hook.sh @@ -4,7 +4,7 @@ # OpenStack cloud. It will attempt to create an instance if one is not # available. Do not run this script unless you know what you're doing. # For more information refer to: -# http://docs.openstack.org/developer/python-openstackclient/ +# https://docs.openstack.org/python-openstackclient/latest/ function generate_testr_results { if [ -f .testrepository/0 ]; then diff --git a/openstackclient/tests/functional/post_test_hook_tips.sh b/openstackclient/tests/functional/post_test_hook_tips.sh index a4ad19ac62..232527a30b 100755 --- a/openstackclient/tests/functional/post_test_hook_tips.sh +++ b/openstackclient/tests/functional/post_test_hook_tips.sh @@ -4,7 +4,7 @@ # OpenStack cloud. It will attempt to create an instance if one is not # available. Do not run this script unless you know what you're doing. # For more information refer to: -# http://docs.openstack.org/developer/python-openstackclient/ +# https://docs.openstack.org/python-openstackclient/latest/ # This particular script differs from the normal post_test_hook because # it installs the master (tip) version of osc-lib, os-client-config diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py index 14800913c6..1dc130c1c1 100644 --- a/releasenotes/source/conf.py +++ b/releasenotes/source/conf.py @@ -62,7 +62,7 @@ '', ), 'oscdoc': ( - 'http://docs.openstack.org/developer/python-openstackclient/%s.html', + 'https://docs.openstack.org/python-openstackclient/latest/%s.html', '', ), } diff --git a/setup.cfg b/setup.cfg index b4b6827d0b..2412b6fe02 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,7 +5,7 @@ description-file = README.rst author = OpenStack author-email = openstack-dev@lists.openstack.org -home-page = http://docs.openstack.org/developer/python-openstackclient +home-page = https://docs.openstack.org/python-openstackclient/latest/ classifier = Environment :: OpenStack Intended Audience :: Information Technology From 254dbf3294c0f1edc4a2a469f556b3c4b3123a00 Mon Sep 17 00:00:00 2001 From: lihaijing Date: Thu, 21 Sep 2017 14:55:17 +0800 Subject: [PATCH 1795/3095] Fix 'project purge' deletes ALL images problem Closes-Bug: #1717130 Change-Id: I33c6fc7897dfee85d1c197a1267bde4abfa5bbd9 --- openstackclient/common/project_purge.py | 9 ++++++- .../tests/unit/common/test_project_purge.py | 24 +++++++++---------- openstackclient/tests/unit/image/v1/fakes.py | 1 + openstackclient/tests/unit/image/v2/fakes.py | 1 + .../notes/bug-1717130-029211b60f74b4c4.yaml | 6 +++++ 5 files changed, 28 insertions(+), 13 deletions(-) create mode 100644 releasenotes/notes/bug-1717130-029211b60f74b4c4.yaml diff --git a/openstackclient/common/project_purge.py b/openstackclient/common/project_purge.py index dff954e725..5b1d007254 100644 --- a/openstackclient/common/project_purge.py +++ b/openstackclient/common/project_purge.py @@ -95,7 +95,14 @@ def delete_resources(self, dry_run, project_id): # images try: image_client = self.app.client_manager.image - data = image_client.images.list(owner=project_id) + api_version = int(image_client.version) + if api_version == 1: + data = image_client.images.list(owner=project_id) + elif api_version == 2: + kwargs = {'filters': {'owner': project_id}} + data = image_client.images.list(**kwargs) + else: + raise NotImplementedError self.delete_objects( image_client.images.delete, data, 'image', dry_run) except Exception: diff --git a/openstackclient/tests/unit/common/test_project_purge.py b/openstackclient/tests/unit/common/test_project_purge.py index 05a8aa3e22..2385eae893 100644 --- a/openstackclient/tests/unit/common/test_project_purge.py +++ b/openstackclient/tests/unit/common/test_project_purge.py @@ -118,8 +118,8 @@ def test_project_purge_with_project(self): self.projects_mock.delete.assert_called_once_with(self.project.id) self.servers_mock.list.assert_called_once_with( search_opts={'tenant_id': self.project.id}) - self.images_mock.list.assert_called_once_with( - owner=self.project.id) + kwargs = {'filters': {'owner': self.project.id}} + self.images_mock.list.assert_called_once_with(**kwargs) volume_search_opts = {'project_id': self.project.id} self.volumes_mock.list.assert_called_once_with( search_opts=volume_search_opts) @@ -153,8 +153,8 @@ def test_project_purge_with_dry_run(self): self.projects_mock.delete.assert_not_called() self.servers_mock.list.assert_called_once_with( search_opts={'tenant_id': self.project.id}) - self.images_mock.list.assert_called_once_with( - owner=self.project.id) + kwargs = {'filters': {'owner': self.project.id}} + self.images_mock.list.assert_called_once_with(**kwargs) volume_search_opts = {'project_id': self.project.id} self.volumes_mock.list.assert_called_once_with( search_opts=volume_search_opts) @@ -188,8 +188,8 @@ def test_project_purge_with_keep_project(self): self.projects_mock.delete.assert_not_called() self.servers_mock.list.assert_called_once_with( search_opts={'tenant_id': self.project.id}) - self.images_mock.list.assert_called_once_with( - owner=self.project.id) + kwargs = {'filters': {'owner': self.project.id}} + self.images_mock.list.assert_called_once_with(**kwargs) volume_search_opts = {'project_id': self.project.id} self.volumes_mock.list.assert_called_once_with( search_opts=volume_search_opts) @@ -224,8 +224,8 @@ def test_project_purge_with_auth_project(self): self.projects_mock.delete.assert_called_once_with(self.project.id) self.servers_mock.list.assert_called_once_with( search_opts={'tenant_id': self.project.id}) - self.images_mock.list.assert_called_once_with( - owner=self.project.id) + kwargs = {'filters': {'owner': self.project.id}} + self.images_mock.list.assert_called_once_with(**kwargs) volume_search_opts = {'project_id': self.project.id} self.volumes_mock.list.assert_called_once_with( search_opts=volume_search_opts) @@ -260,8 +260,8 @@ def test_project_purge_with_exception(self, mock_error): self.projects_mock.delete.assert_called_once_with(self.project.id) self.servers_mock.list.assert_called_once_with( search_opts={'tenant_id': self.project.id}) - self.images_mock.list.assert_called_once_with( - owner=self.project.id) + kwargs = {'filters': {'owner': self.project.id}} + self.images_mock.list.assert_called_once_with(**kwargs) volume_search_opts = {'project_id': self.project.id} self.volumes_mock.list.assert_called_once_with( search_opts=volume_search_opts) @@ -296,8 +296,8 @@ def test_project_purge_with_force_delete_backup(self): self.projects_mock.delete.assert_called_once_with(self.project.id) self.servers_mock.list.assert_called_once_with( search_opts={'tenant_id': self.project.id}) - self.images_mock.list.assert_called_once_with( - owner=self.project.id) + kwargs = {'filters': {'owner': self.project.id}} + self.images_mock.list.assert_called_once_with(**kwargs) volume_search_opts = {'project_id': self.project.id} self.volumes_mock.list.assert_called_once_with( search_opts=volume_search_opts) diff --git a/openstackclient/tests/unit/image/v1/fakes.py b/openstackclient/tests/unit/image/v1/fakes.py index 8030625743..bbec00fc06 100644 --- a/openstackclient/tests/unit/image/v1/fakes.py +++ b/openstackclient/tests/unit/image/v1/fakes.py @@ -64,6 +64,7 @@ def __init__(self, **kwargs): self.images.resource_class = fakes.FakeResource(None, {}) self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] + self.version = 1.0 class TestImagev1(utils.TestCommand): diff --git a/openstackclient/tests/unit/image/v2/fakes.py b/openstackclient/tests/unit/image/v2/fakes.py index eabc932573..d82d81146f 100644 --- a/openstackclient/tests/unit/image/v2/fakes.py +++ b/openstackclient/tests/unit/image/v2/fakes.py @@ -156,6 +156,7 @@ def __init__(self, **kwargs): self.image_tags.resource_class = fakes.FakeResource(None, {}) self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] + self.version = 2.0 class TestImagev2(utils.TestCommand): diff --git a/releasenotes/notes/bug-1717130-029211b60f74b4c4.yaml b/releasenotes/notes/bug-1717130-029211b60f74b4c4.yaml new file mode 100644 index 0000000000..5e069e81be --- /dev/null +++ b/releasenotes/notes/bug-1717130-029211b60f74b4c4.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Fix the ``project purge`` command to correctly delete only images owned by the + specified project ID when run by an administrative user. + [Bug `1717130 `_] From 221b7052abcefa56213a6516cecc6de9c73026d1 Mon Sep 17 00:00:00 2001 From: gvrangan Date: Thu, 21 Sep 2017 06:37:10 +0530 Subject: [PATCH 1796/3095] Support icmp-type and icmp-code to be set as zero When icmp-type or icmp-code are set to 0, the current implementation ignores the value, this fix will allow the value to be copied and displayed Change-Id: I96133a57883d22e98fcbb9fe0328d9e050472469 Signed-off-by: gvrangan --- .../network/v2/security_group_rule.py | 4 +- .../v2/test_security_group_rule_network.py | 170 ++++++++++++++++++ ...-icmp-type-code-zero-cbef0a36db2b8123.yaml | 5 + 3 files changed, 177 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/support-icmp-type-code-zero-cbef0a36db2b8123.yaml diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index ad685cf8ae..06d467254b 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -294,9 +294,9 @@ def take_action_network(self, client, parsed_args): if parsed_args.dst_port and not is_icmp_protocol: attrs['port_range_min'] = parsed_args.dst_port[0] attrs['port_range_max'] = parsed_args.dst_port[1] - if parsed_args.icmp_type: + if parsed_args.icmp_type is not None and parsed_args.icmp_type >= 0: attrs['port_range_min'] = parsed_args.icmp_type - if parsed_args.icmp_code: + if parsed_args.icmp_code is not None and parsed_args.icmp_code >= 0: attrs['port_range_max'] = parsed_args.icmp_code # NOTE(dtroyer): --src-ip and --src-group were deprecated in Nov 2016. diff --git a/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py b/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py index 5d9d03e9c7..36add8ca80 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py @@ -407,6 +407,78 @@ def test_create_icmp_code(self): self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) + def test_create_icmp_code_zero(self): + self._setup_security_group_rule({ + 'port_range_min': 15, + 'port_range_max': 0, + 'protocol': 'icmp', + 'remote_ip_prefix': '0.0.0.0/0', + }) + arglist = [ + '--protocol', self._security_group_rule.protocol, + '--icmp-type', str(self._security_group_rule.port_range_min), + '--icmp-code', str(self._security_group_rule.port_range_max), + self._security_group.id, + ] + verifylist = [ + ('protocol', self._security_group_rule.protocol), + ('icmp_code', self._security_group_rule.port_range_max), + ('icmp_type', self._security_group_rule.port_range_min), + ('group', self._security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.assertEqual(self.expected_columns, columns) + self.assertEqual(self.expected_data, data) + + def test_create_icmp_code_greater_than_zero(self): + self._setup_security_group_rule({ + 'port_range_min': 15, + 'port_range_max': 18, + 'protocol': 'icmp', + 'remote_ip_prefix': '0.0.0.0/0', + }) + arglist = [ + '--protocol', self._security_group_rule.protocol, + '--icmp-type', str(self._security_group_rule.port_range_min), + '--icmp-code', str(self._security_group_rule.port_range_max), + self._security_group.id, + ] + verifylist = [ + ('protocol', self._security_group_rule.protocol), + ('icmp_type', self._security_group_rule.port_range_min), + ('icmp_code', self._security_group_rule.port_range_max), + ('group', self._security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.assertEqual(self.expected_columns, columns) + self.assertEqual(self.expected_data, data) + + def test_create_icmp_code_negative_value(self): + self._setup_security_group_rule({ + 'port_range_min': 15, + 'port_range_max': None, + 'protocol': 'icmp', + 'remote_ip_prefix': '0.0.0.0/0', + }) + arglist = [ + '--protocol', self._security_group_rule.protocol, + '--icmp-type', str(self._security_group_rule.port_range_min), + '--icmp-code', '-2', + self._security_group.id, + ] + verifylist = [ + ('protocol', self._security_group_rule.protocol), + ('icmp_type', self._security_group_rule.port_range_min), + ('icmp_code', -2), + ('group', self._security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.assertEqual(self.expected_columns, columns) + self.assertEqual(self.expected_data, data) + def test_create_icmp_type(self): self._setup_security_group_rule({ 'port_range_min': 15, @@ -440,6 +512,104 @@ def test_create_icmp_type(self): self.assertEqual(self.expected_columns, columns) self.assertEqual(self.expected_data, data) + def test_create_icmp_type_zero(self): + self._setup_security_group_rule({ + 'port_range_min': 0, + 'protocol': 'icmp', + 'remote_ip_prefix': '0.0.0.0/0', + }) + arglist = [ + '--icmp-type', str(self._security_group_rule.port_range_min), + '--protocol', self._security_group_rule.protocol, + self._security_group.id, + ] + verifylist = [ + ('dst_port', None), + ('icmp_type', self._security_group_rule.port_range_min), + ('icmp_code', None), + ('protocol', self._security_group_rule.protocol), + ('group', self._security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_security_group_rule.assert_called_once_with(**{ + 'direction': self._security_group_rule.direction, + 'ethertype': self._security_group_rule.ether_type, + 'port_range_min': self._security_group_rule.port_range_min, + 'protocol': self._security_group_rule.protocol, + 'remote_ip_prefix': self._security_group_rule.remote_ip_prefix, + 'security_group_id': self._security_group.id, + }) + self.assertEqual(self.expected_columns, columns) + self.assertEqual(self.expected_data, data) + + def test_create_icmp_type_greater_than_zero(self): + self._setup_security_group_rule({ + 'port_range_min': 13, # timestamp + 'protocol': 'icmp', + 'remote_ip_prefix': '0.0.0.0/0', + }) + arglist = [ + '--icmp-type', str(self._security_group_rule.port_range_min), + '--protocol', self._security_group_rule.protocol, + self._security_group.id, + ] + verifylist = [ + ('dst_port', None), + ('icmp_type', self._security_group_rule.port_range_min), + ('icmp_code', None), + ('protocol', self._security_group_rule.protocol), + ('group', self._security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_security_group_rule.assert_called_once_with(**{ + 'direction': self._security_group_rule.direction, + 'ethertype': self._security_group_rule.ether_type, + 'port_range_min': self._security_group_rule.port_range_min, + 'protocol': self._security_group_rule.protocol, + 'remote_ip_prefix': self._security_group_rule.remote_ip_prefix, + 'security_group_id': self._security_group.id, + }) + self.assertEqual(self.expected_columns, columns) + self.assertEqual(self.expected_data, data) + + def test_create_icmp_type_negative_value(self): + self._setup_security_group_rule({ + 'port_range_min': None, # timestamp + 'protocol': 'icmp', + 'remote_ip_prefix': '0.0.0.0/0', + }) + arglist = [ + '--icmp-type', '-13', + '--protocol', self._security_group_rule.protocol, + self._security_group.id, + ] + verifylist = [ + ('dst_port', None), + ('icmp_type', -13), + ('icmp_code', None), + ('protocol', self._security_group_rule.protocol), + ('group', self._security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_security_group_rule.assert_called_once_with(**{ + 'direction': self._security_group_rule.direction, + 'ethertype': self._security_group_rule.ether_type, + 'protocol': self._security_group_rule.protocol, + 'remote_ip_prefix': self._security_group_rule.remote_ip_prefix, + 'security_group_id': self._security_group.id, + }) + self.assertEqual(self.expected_columns, columns) + self.assertEqual(self.expected_data, data) + def test_create_ipv6_icmp_type_code(self): self._setup_security_group_rule({ 'ether_type': 'IPv6', diff --git a/releasenotes/notes/support-icmp-type-code-zero-cbef0a36db2b8123.yaml b/releasenotes/notes/support-icmp-type-code-zero-cbef0a36db2b8123.yaml new file mode 100644 index 0000000000..c5910ddb39 --- /dev/null +++ b/releasenotes/notes/support-icmp-type-code-zero-cbef0a36db2b8123.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - Add support to set ``--icmp-type`` and ``--icmp-code`` to 0 in the + ``security group rule`` command. + [Bug `1703704 `_] From f6f5ce03c5b8a03180db24a02dda5b30f40b4cee Mon Sep 17 00:00:00 2001 From: Anton Frolov Date: Mon, 25 Sep 2017 12:31:24 -0700 Subject: [PATCH 1797/3095] Optimize getting endpoint list Currently ListEndpoint.take_action method unconditionally iterates over all endpoints and issue GET /v3/services/ request for each endpoint. In case of HTTPS keystone endpoint this can take significant amout of time, and it only getting worse in case of multiple regions. This commit change this logic to making just two GET requests: first it gets endpoint list, then it gets service list, searching service in the list instead of issuing GET /v3/services/ request. Change-Id: I22b61c0b45b0205a2f5a4608c2473cb7814fe3cf Closes-Bug: 1719413 --- openstackclient/identity/common.py | 10 ++++++++++ openstackclient/identity/v3/endpoint.py | 3 ++- .../tests/unit/identity/v3/test_endpoint.py | 2 ++ releasenotes/notes/bug-1719413-0401d05c91cc9094.yaml | 8 ++++++++ 4 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/bug-1719413-0401d05c91cc9094.yaml diff --git a/openstackclient/identity/common.py b/openstackclient/identity/common.py index 3dc5adbbd2..e119f66019 100644 --- a/openstackclient/identity/common.py +++ b/openstackclient/identity/common.py @@ -26,6 +26,16 @@ from openstackclient.i18n import _ +def find_service_in_list(service_list, service_id): + """Find a service by id in service list.""" + + for service in service_list: + if service.id == service_id: + return service + raise exceptions.CommandError( + "No service with a type, name or ID of '%s' exists." % service_id) + + def find_service(identity_client, name_type_or_id): """Find a service by id, name or type.""" diff --git a/openstackclient/identity/v3/endpoint.py b/openstackclient/identity/v3/endpoint.py index 15760a1724..3b4dd0de44 100644 --- a/openstackclient/identity/v3/endpoint.py +++ b/openstackclient/identity/v3/endpoint.py @@ -167,9 +167,10 @@ def take_action(self, parsed_args): if parsed_args.region: kwargs['region'] = parsed_args.region data = identity_client.endpoints.list(**kwargs) + service_list = identity_client.services.list() for ep in data: - service = common.find_service(identity_client, ep.service_id) + service = common.find_service_in_list(service_list, ep.service_id) ep.service_name = get_service_name(service) ep.service_type = service.type return (columns, diff --git a/openstackclient/tests/unit/identity/v3/test_endpoint.py b/openstackclient/tests/unit/identity/v3/test_endpoint.py index 765fbedde1..fad53fcb81 100644 --- a/openstackclient/tests/unit/identity/v3/test_endpoint.py +++ b/openstackclient/tests/unit/identity/v3/test_endpoint.py @@ -295,6 +295,7 @@ def setUp(self): # This is the return value for common.find_resource(service) self.services_mock.get.return_value = self.service + self.services_mock.list.return_value = [self.service] # Get the command object to test self.cmd = endpoint.ListEndpoint(self.app, None) @@ -726,6 +727,7 @@ def setUp(self): # This is the return value for common.find_resource(service) self.services_mock.get.return_value = self.service + self.services_mock.list.return_value = [self.service] # Get the command object to test self.cmd = endpoint.ListEndpoint(self.app, None) diff --git a/releasenotes/notes/bug-1719413-0401d05c91cc9094.yaml b/releasenotes/notes/bug-1719413-0401d05c91cc9094.yaml new file mode 100644 index 0000000000..784d19ec8f --- /dev/null +++ b/releasenotes/notes/bug-1719413-0401d05c91cc9094.yaml @@ -0,0 +1,8 @@ +--- +fixes: + - | + Fix an issue with ``endpoint list`` working slow because it is issuing one GET + request to /v3/services/ Keystone API for each endpoint. In case of HTTPS + keystone endpoint and multiple regions it can take significant amount of time. + [Bug `1719413 `_] + From de23ab8d75fe89c164b3b084c53f01c25b9040ca Mon Sep 17 00:00:00 2001 From: Hongbin Lu Date: Mon, 18 Sep 2017 01:41:32 +0000 Subject: [PATCH 1798/3095] Support creating unaddress neutron port Introduce an option '--no-fixed-ip' on port create command. If this option is specified and '--fixed-ip' is unspecified, OSC will send a request to neutron with 'fixed_ips' as an empty list, which will create an unaddress neutron port. Note: The use cases of unaddress port was outlined in: https://specs.openstack.org/openstack/neutron-specs/specs/liberty/unaddressed-port.html (dtroyer: add Depends-On for Zuul v3 test) Depends-On: I39e8e49243ab0bda631600715c971c55a34e2fd9 Change-Id: Ibe38598acbbcd0d353c952fc2a6fa67780762151 Closes-Bug: #1717829 --- doc/source/cli/command-objects/port.rst | 6 ++++- openstackclient/network/v2/port.py | 10 ++++++- .../tests/unit/network/v2/test_port.py | 26 +++++++++++++++++++ .../notes/bug-1717829-c1de1d777d3abaf9.yaml | 5 ++++ 4 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/bug-1717829-c1de1d777d3abaf9.yaml diff --git a/doc/source/cli/command-objects/port.rst b/doc/source/cli/command-objects/port.rst index c2da09b321..cf29bb2cbd 100644 --- a/doc/source/cli/command-objects/port.rst +++ b/doc/source/cli/command-objects/port.rst @@ -19,7 +19,7 @@ Create new port openstack port create --network [--description ] - [--fixed-ip subnet=,ip-address=] + [--fixed-ip subnet=,ip-address= | --no-fixed-ip] [--device ] [--device-owner ] [--vnic-type ] @@ -50,6 +50,10 @@ Create new port subnet=,ip-address= (repeat option to set multiple fixed IP addresses) +.. option:: --no-fixed-ip + + No IP or subnet for this port + .. option:: --device Port device ID diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 9536fe8687..4b23b339c9 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -302,7 +302,8 @@ def get_parser(self, prog_name): help=_("Network this port belongs to (name or ID)") ) _add_updatable_args(parser) - parser.add_argument( + fixed_ip = parser.add_mutually_exclusive_group() + fixed_ip.add_argument( '--fixed-ip', metavar='subnet=,ip-address=', action=parseractions.MultiKeyValueAction, @@ -311,6 +312,11 @@ def get_parser(self, prog_name): "subnet=,ip-address= " "(repeat option to set multiple fixed IP addresses)") ) + fixed_ip.add_argument( + '--no-fixed-ip', + action='store_true', + help=_("No IP or subnet for this port.") + ) parser.add_argument( '--binding-profile', metavar='', @@ -402,6 +408,8 @@ def take_action(self, parsed_args): if parsed_args.fixed_ip: attrs['fixed_ips'] = parsed_args.fixed_ip + elif parsed_args.no_fixed_ip: + attrs['fixed_ips'] = [] if parsed_args.security_group: attrs['security_group_ids'] = [client.find_security_group( diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index 45e1045da0..3f751818b9 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -356,6 +356,32 @@ def test_create_with_no_security_groups(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_create_with_no_fixed_ips(self): + arglist = [ + '--network', self._port.network_id, + '--no-fixed-ip', + 'test-port', + ] + verifylist = [ + ('network', self._port.network_id), + ('enable', True), + ('no_fixed_ip', True), + ('name', 'test-port'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_port.assert_called_once_with(**{ + 'admin_state_up': True, + 'network_id': self._port.network_id, + 'fixed_ips': [], + 'name': 'test-port', + }) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + def test_create_port_with_allowed_address_pair_ipaddr(self): pairs = [{'ip_address': '192.168.1.123'}, {'ip_address': '192.168.1.45'}] diff --git a/releasenotes/notes/bug-1717829-c1de1d777d3abaf9.yaml b/releasenotes/notes/bug-1717829-c1de1d777d3abaf9.yaml new file mode 100644 index 0000000000..19ea4ea504 --- /dev/null +++ b/releasenotes/notes/bug-1717829-c1de1d777d3abaf9.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Add ``--no-fixed-ip`` option to ``port create`` command. + [Bug `1717829 `_] From a87bd58fb4de5dc5f99193b88d2a3f8bd1338c52 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 5 Oct 2017 13:51:35 +0000 Subject: [PATCH 1799/3095] Updated from global requirements Change-Id: I79e4ddb75c7cf7088d6f0abc79f9f07dcd165535 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c8789496e9..176acb2ad8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ pbr!=2.1.0,>=2.0.0 # Apache-2.0 six>=1.9.0 # MIT Babel!=2.4.0,>=2.3.4 # BSD -cliff>=2.8.0 # Apache-2.0 +cliff!=2.9.0,>=2.8.0 # Apache-2.0 keystoneauth1>=3.2.0 # Apache-2.0 openstacksdk>=0.9.18 # Apache-2.0 osc-lib>=1.7.0 # Apache-2.0 From 0d94a76d956c0322e9c4607fb5ce824f7653f038 Mon Sep 17 00:00:00 2001 From: Steve Martinelli Date: Thu, 17 Aug 2017 12:17:39 -0400 Subject: [PATCH 1800/3095] auto-generate docs for v3 identity resources skipped shared v2/v3 identity resources. Change-Id: I9e1fb5dee5506780fd79a0cbfbde059d0ccd1489 --- .../cli/command-objects/access-token.rst | 42 +----- doc/source/cli/command-objects/catalog.rst | 30 +--- doc/source/cli/command-objects/consumer.rst | 87 ++--------- doc/source/cli/command-objects/domain.rst | 119 ++------------- .../command-objects/federation-protocol.rst | 116 ++------------- .../cli/command-objects/identity-provider.rst | 137 ++---------------- doc/source/cli/command-objects/mapping.rst | 95 ++---------- doc/source/cli/command-objects/policy.rst | 98 ++----------- doc/source/cli/command-objects/region.rst | 108 ++------------ .../cli/command-objects/request-token.rst | 59 +------- .../cli/command-objects/service-provider.rst | 135 ++--------------- doc/source/cli/command-objects/trust.rst | 104 ++----------- 12 files changed, 134 insertions(+), 996 deletions(-) diff --git a/doc/source/cli/command-objects/access-token.rst b/doc/source/cli/command-objects/access-token.rst index b1400412ff..7ef9167eb3 100644 --- a/doc/source/cli/command-objects/access-token.rst +++ b/doc/source/cli/command-objects/access-token.rst @@ -2,41 +2,9 @@ access token ============ -Identity v3 +An **access token** is used by the Identity service's OS-OAUTH1 extension. It +is used by the **consumer** to request new Identity API tokens on behalf of the +authorizing **user**. Applicable to Identity v3. -`Requires: OS-OAUTH1 extension` - -access token create -------------------- - -Create an access token - -.. program:: access token create -.. code:: bash - - openstack access token create - --consumer-key - --consumer-secret - --request-key - --request-secret - --verifier - -.. option:: --consumer-key - - Consumer key (required) - -.. option:: --consumer-secret - - Consumer secret (required) - -.. option:: --request-key - - Request token to exchange for access token (required) - -.. option:: --request-secret - - Secret associated with (required) - -.. option:: --verifier - - Verifier associated with (required) +.. autoprogram-cliff:: openstack.identity.v3 + :command: access token create diff --git a/doc/source/cli/command-objects/catalog.rst b/doc/source/cli/command-objects/catalog.rst index dccf780121..6db8227ec4 100644 --- a/doc/source/cli/command-objects/catalog.rst +++ b/doc/source/cli/command-objects/catalog.rst @@ -2,29 +2,11 @@ catalog ======= -Identity v2, v3 +A **catalog** lists OpenStack services that are available on the cloud. +Applicable to Identity v2 and v3 -catalog list ------------- +.. autoprogram-cliff:: openstack.identity.v3 + :command: catalog list -List services in the service catalog - -.. program:: catalog list -.. code:: bash - - openstack catalog list - -catalog show ------------- - -Display service catalog details - -.. program:: catalog show -.. code:: bash - - openstack catalog show - - -.. describe:: - - Service to display (type or name) +.. autoprogram-cliff:: openstack.identity.v3 + :command: catalog show diff --git a/doc/source/cli/command-objects/consumer.rst b/doc/source/cli/command-objects/consumer.rst index d4ce5dc5b2..8f6dda0ad4 100644 --- a/doc/source/cli/command-objects/consumer.rst +++ b/doc/source/cli/command-objects/consumer.rst @@ -2,82 +2,21 @@ consumer ======== -Identity v3 +An **consumer** is used by the Identity service's OS-OAUTH1 extension. It +is used to create a **request token** and **access token**. Applicable to +Identity v3. -`Requires: OS-OAUTH1 extension` +.. autoprogram-cliff:: openstack.identity.v3 + :command: consumer create -consumer create ---------------- +.. autoprogram-cliff:: openstack.identity.v3 + :command: consumer delete -Create new consumer +.. autoprogram-cliff:: openstack.identity.v3 + :command: consumer list -.. program:: consumer create -.. code:: bash +.. autoprogram-cliff:: openstack.identity.v3 + :command: consumer set - openstack consumer create - [--description ] - -.. option:: --description - - New consumer description - -consumer delete ---------------- - -Delete consumer(s) - -.. program:: consumer delete -.. code:: bash - - openstack consumer delete - [ ...] - -.. describe:: - - Consumer(s) to delete - -consumer list -------------- - -List consumers - -.. program:: consumer list -.. code:: bash - - openstack consumer list - -consumer set ------------- - -Set consumer properties - -.. program:: consumer set -.. code:: bash - - openstack consumer set - [--description ] - - -.. option:: --description - - New consumer description - -.. describe:: - - Consumer to modify - -consumer show -------------- - -Display consumer details - -.. program:: consumer show -.. code:: bash - - openstack consumer show - - -.. _consumer_show-consumer: -.. describe:: - - Consumer to display +.. autoprogram-cliff:: openstack.identity.v3 + :command: consumer show diff --git a/doc/source/cli/command-objects/domain.rst b/doc/source/cli/command-objects/domain.rst index 70a3e73309..81a193497b 100644 --- a/doc/source/cli/command-objects/domain.rst +++ b/doc/source/cli/command-objects/domain.rst @@ -2,114 +2,21 @@ domain ====== -Identity v3 +A **domain** is a collection of **users**, **groups**, and **projects**. Each +**group** and **project** is owned by exactly one **domain**. Applies to +Identity v3. -domain create -------------- +.. autoprogram-cliff:: openstack.identity.v3 + :command: domain create -Create new domain +.. autoprogram-cliff:: openstack.identity.v3 + :command: domain delete -.. program:: domain create -.. code:: bash +.. autoprogram-cliff:: openstack.identity.v3 + :command: domain list - openstack domain create - [--description ] - [--enable | --disable] - [--or-show] - +.. autoprogram-cliff:: openstack.identity.v3 + :command: domain set -.. option:: --description - - New domain description - -.. option:: --enable - - Enable domain (default) - -.. option:: --disable - - Disable domain - -.. option:: --or-show - - Return existing domain - - If the domain already exists, return the existing domain data and do not fail. - -.. describe:: - - New domain name - -domain delete -------------- - -Delete domain(s) - -.. program:: domain delete -.. code:: bash - - openstack domain delete - [ ...] - -.. describe:: - - Domain(s) to delete (name or ID) - -domain list ------------ - -List domains - -.. program:: domain list -.. code:: bash - - openstack domain list - -domain set ----------- - -Set domain properties - -.. program:: domain set -.. code:: bash - - openstack domain set - [--name ] - [--description ] - [--enable | --disable] - - -.. option:: --name - - New domain name - -.. option:: --description - - New domain description - -.. option:: --enable - - Enable domain - -.. option:: --disable - - Disable domain - -.. describe:: - - Domain to modify (name or ID) - -domain show ------------ - -Display domain details - -.. program:: domain show -.. code:: bash - - openstack domain show - - -.. describe:: - - Domain to display (name or ID) +.. autoprogram-cliff:: openstack.identity.v3 + :command: domain show diff --git a/doc/source/cli/command-objects/federation-protocol.rst b/doc/source/cli/command-objects/federation-protocol.rst index e1f98174c0..3a99fd72b9 100644 --- a/doc/source/cli/command-objects/federation-protocol.rst +++ b/doc/source/cli/command-objects/federation-protocol.rst @@ -2,111 +2,21 @@ federation protocol =================== -Identity v3 +A **federation protocol** is used by the Identity service's OS-FEDERATION +extension. It is used by **identity providers** and **mappings**. Applicable to +Identity v3. -`Requires: OS-FEDERATION extension` +.. autoprogram-cliff:: openstack.identity.v3 + :command: federation protocol create -federation protocol create --------------------------- +.. autoprogram-cliff:: openstack.identity.v3 + :command: federation protocol delete -Create new federation protocol +.. autoprogram-cliff:: openstack.identity.v3 + :command: federation protocol list -.. program:: federation protocol create -.. code:: bash +.. autoprogram-cliff:: openstack.identity.v3 + :command: federation protocol set - openstack federation protocol create - --identity-provider - --mapping - - -.. option:: --identity-provider - - Identity provider that will support the new federation protocol (name or ID) (required) - -.. option:: --mapping - - Mapping that is to be used (name or ID) (required) - -.. describe:: - - New federation protocol name (must be unique per identity provider) - -federation protocol delete --------------------------- - -Delete federation protocol(s) - -.. program:: federation protocol delete -.. code:: bash - - openstack federation protocol delete - --identity-provider - [ ...] - -.. option:: --identity-provider - - Identity provider that supports (name or ID) (required) - -.. describe:: - - Federation protocol(s) to delete (name or ID) - -federation protocol list ------------------------- - -List federation protocols - -.. program:: federation protocol list -.. code:: bash - - openstack federation protocol list - --identity-provider - -.. option:: --identity-provider - - Identity provider to list (name or ID) (required) - -federation protocol set ------------------------ - -Set federation protocol properties - -.. program:: federation protocol set -.. code:: bash - - openstack federation protocol set - --identity-provider - [--mapping ] - - -.. option:: --identity-provider - - Identity provider that supports (name or ID) (required) - -.. option:: --mapping - - Mapping that is to be used (name or ID) - -.. describe:: - - Federation protocol to modify (name or ID) - -federation protocol show ------------------------- - -Display federation protocol details - -.. program:: federation protocol show -.. code:: bash - - openstack federation protocol show - --identity-provider - - -.. option:: --identity-provider - - Identity provider that supports (name or ID) (required) - -.. describe:: - - Federation protocol to display (name or ID) +.. autoprogram-cliff:: openstack.identity.v3 + :command: federation protocol show diff --git a/doc/source/cli/command-objects/identity-provider.rst b/doc/source/cli/command-objects/identity-provider.rst index d96b814aea..36c9264a64 100644 --- a/doc/source/cli/command-objects/identity-provider.rst +++ b/doc/source/cli/command-objects/identity-provider.rst @@ -2,132 +2,21 @@ identity provider ================= -Identity v3 +An **identity provider** is used by the Identity service's OS-FEDERATION +extension. It is used by **federation protocols** and **mappings**. Applicable +to Identity v3. -`Requires: OS-FEDERATION extension` +.. autoprogram-cliff:: openstack.identity.v3 + :command: identity provider create -identity provider create ------------------------- +.. autoprogram-cliff:: openstack.identity.v3 + :command: identity provider delete -Create new identity provider +.. autoprogram-cliff:: openstack.identity.v3 + :command: identity provider list -.. program:: identity provider create -.. code:: bash +.. autoprogram-cliff:: openstack.identity.v3 + :command: identity provider set - openstack identity provider create - [--remote-id [...] | --remote-id-file ] - [--description ] - [--domain ] - [--enable | --disable] - - -.. option:: --remote-id - - Remote IDs to associate with the Identity Provider - (repeat option to provide multiple values) - -.. option:: --remote-id-file - - Name of a file that contains many remote IDs to associate with the identity - provider, one per line - -.. option:: --description - - New identity provider description - -.. option:: --domain - - Name or ID of the domain to associate with the identity provider. If not - specified, one will be created automatically - -.. option:: --enable - - Enable the identity provider (default) - -.. option:: --disable - - Disable the identity provider - -.. describe:: - - New identity provider name (must be unique) - -identity provider delete ------------------------- - -Delete identity provider(s) - -.. program:: identity provider delete -.. code:: bash - - openstack identity provider delete - [ ...] - -.. describe:: - - Identity provider(s) to delete - -identity provider list ----------------------- - -List identity providers - -.. program:: identity provider list -.. code:: bash - - openstack identity provider list - -identity provider set ---------------------- - -Set identity provider properties - -.. program:: identity provider set -.. code:: bash - - openstack identity provider set - [--remote-id [...] | --remote-id-file ] - [--description ] - [--enable | --disable] - - -.. option:: --remote-id - - Remote IDs to associate with the Identity Provider - (repeat option to provide multiple values) - -.. option:: --remote-id-file - - Name of a file that contains many remote IDs to associate with the identity - provider, one per line - -.. option:: --description - - Set identity provider description - -.. option:: --enable - - Enable the identity provider - -.. option:: --disable - - Disable the identity provider - -.. describe:: - - Identity provider to modify - -identity provider show ----------------------- - -Display identity provider details - -.. program:: identity provider show -.. code:: bash - - openstack identity provider show - - -.. describe:: - - Identity provider to display +.. autoprogram-cliff:: openstack.identity.v3 + :command: identity provider show diff --git a/doc/source/cli/command-objects/mapping.rst b/doc/source/cli/command-objects/mapping.rst index 1f657ed282..73dbbc8d8b 100644 --- a/doc/source/cli/command-objects/mapping.rst +++ b/doc/source/cli/command-objects/mapping.rst @@ -2,90 +2,21 @@ mapping ======= -Identity v3 +A **mapping** is used by the Identity service's OS-FEDERATION +extension. It is used by **federation protocols** and **identity providers**. +Applicable to Identity v3. -`Requires: OS-FEDERATION extension` +.. autoprogram-cliff:: openstack.identity.v3 + :command: mapping create -mapping create --------------- +.. autoprogram-cliff:: openstack.identity.v3 + :command: mapping delete -Create new mapping +.. autoprogram-cliff:: openstack.identity.v3 + :command: mapping list -.. program:: mapping create -.. code:: bash +.. autoprogram-cliff:: openstack.identity.v3 + :command: mapping set - openstack mapping create - --rules - - -.. option:: --rules - - Filename that contains a set of mapping rules (required) - -.. _mapping_create-mapping: -.. describe:: - - New mapping name (must be unique) - -mapping delete --------------- - -Delete mapping(s) - -.. program:: mapping delete -.. code:: bash - - openstack mapping delete - [ ...] - -.. _mapping_delete-mapping: -.. describe:: - - Mapping(s) to delete - -mapping list ------------- - -List mappings - -.. program:: mapping list -.. code:: bash - - openstack mapping list - -mapping set ------------ - -Set mapping properties - -.. program:: mapping set -.. code:: bash - - openstack mapping set - [--rules ] - - -.. option:: --rules - - Filename that contains a new set of mapping rules - -.. _mapping_set-mapping: -.. describe:: - - Mapping to modify - -mapping show ------------- - -Display mapping details - -.. program:: mapping show -.. code:: bash - - openstack mapping show - - -.. _mapping_show-mapping: -.. describe:: - - Mapping to display +.. autoprogram-cliff:: openstack.identity.v3 + :command: mapping show diff --git a/doc/source/cli/command-objects/policy.rst b/doc/source/cli/command-objects/policy.rst index deddf2c438..0fe104c56d 100644 --- a/doc/source/cli/command-objects/policy.rst +++ b/doc/source/cli/command-objects/policy.rst @@ -2,94 +2,20 @@ policy ====== -Identity v3 +A **policy** is an arbitrarily serialized policy engine rule set to be consumed +by a remote service. Applies to Identity v3. -policy create -------------- +.. autoprogram-cliff:: openstack.identity.v3 + :command: policy create -Create new policy +.. autoprogram-cliff:: openstack.identity.v3 + :command: policy delete -.. program:: policy create -.. code:: bash +.. autoprogram-cliff:: openstack.identity.v3 + :command: policy list - openstack policy create - [--type ] - +.. autoprogram-cliff:: openstack.identity.v3 + :command: policy set -.. option:: --type - - New MIME type of the policy rules file (defaults to application/json) - -.. describe:: - - New serialized policy rules file - -policy delete -------------- - -Delete policy(s) - -.. program:: policy delete -.. code:: bash - - openstack policy delete - [ ...] - -.. describe:: - - Policy(s) to delete - -policy list ------------ - -List policies - -.. program:: policy list -.. code:: bash - - openstack policy list - [--long] - -.. option:: --long - - List additional fields in output - -policy set ----------- - -Set policy properties - -.. program:: policy set -.. code:: bash - - openstack policy set - [--type ] - [--rules ] - - -.. option:: --type - - New MIME type of the policy rules file - -.. describe:: --rules - - New serialized policy rules file - -.. describe:: - - Policy to modify - -policy show ------------ - -Display policy details - -.. program:: policy show -.. code:: bash - - openstack policy show - - -.. describe:: - - Policy to display +.. autoprogram-cliff:: openstack.identity.v3 + :command: policy show diff --git a/doc/source/cli/command-objects/region.rst b/doc/source/cli/command-objects/region.rst index d2c63c1bd8..58cc341f9a 100644 --- a/doc/source/cli/command-objects/region.rst +++ b/doc/source/cli/command-objects/region.rst @@ -2,103 +2,21 @@ region ====== -Identity v3 +A **region** is a general division of an OpenStack deployment. You can associate +zero or more sub-regions with a region to create a tree-like structured +hierarchy. Applies to Identity v3. -region create -------------- +.. autoprogram-cliff:: openstack.identity.v3 + :command: region create -Create new region +.. autoprogram-cliff:: openstack.identity.v3 + :command: region delete -.. program:: region create -.. code:: bash +.. autoprogram-cliff:: openstack.identity.v3 + :command: region list - openstack region create - [--parent-region ] - [--description ] - +.. autoprogram-cliff:: openstack.identity.v3 + :command: region set -.. option:: --parent-region - - Parent region ID - -.. option:: --description - - New region description - -.. _region_create-region-id: -.. describe:: - - New region ID - -region delete -------------- - -Delete region(s) - -.. program:: region delete -.. code:: bash - - openstack region delete - [ ...] - -.. _region_delete-region-id: -.. describe:: - - Region ID(s) to delete - -region list ------------ - -List regions - -.. program:: region list -.. code:: bash - - openstack region list - [--parent-region ] - -.. option:: --parent-region - - Filter by parent region ID - -region set ----------- - -Set region properties - -.. program:: region set -.. code:: bash - - openstack region set - [--parent-region ] - [--description ] - - -.. option:: --parent-region - - New parent region ID - -.. option:: --description - - New region description - -.. _region_set-region-id: -.. describe:: - - Region to modify - -region show ------------ - -Display region details - -.. program:: region show -.. code:: bash - - openstack region show - - -.. _region_show-region-id: -.. describe:: - - Region to display +.. autoprogram-cliff:: openstack.identity.v3 + :command: region show diff --git a/doc/source/cli/command-objects/request-token.rst b/doc/source/cli/command-objects/request-token.rst index 3c80780f8e..e10f6640ef 100644 --- a/doc/source/cli/command-objects/request-token.rst +++ b/doc/source/cli/command-objects/request-token.rst @@ -2,57 +2,12 @@ request token ============= -Identity v3 +A **request token** is used by the Identity service's OS-OAUTH1 extension. It +is used by the **consumer** to request **access tokens**. Applicable to +Identity v3. -`Requires: OS-OAUTH1 extension` +.. autoprogram-cliff:: openstack.identity.v3 + :command: request token authorize -request token authorize ------------------------ - -Authorize a request token - -.. program:: request token authorize -.. code:: bash - - openstack request token authorize - --request-key - --role - -.. option:: --request-key - - Request token to authorize (ID only) (required) - -.. option:: --role - - Roles to authorize (name or ID) - (repeat option to set multiple values) (required) - -request token create --------------------- - -Create a request token - -.. program:: request token create -.. code:: bash - - openstack request token create - --consumer-key - --consumer-secret - --project - [--domain ] - -.. option:: --consumer-key - - Consumer key (required) - -.. option:: --description - - Consumer secret (required) - -.. option:: --project - - Project that consumer wants to access (name or ID) (required) - -.. option:: --domain - - Domain owning (name or ID) +.. autoprogram-cliff:: openstack.identity.v3 + :command: request token create diff --git a/doc/source/cli/command-objects/service-provider.rst b/doc/source/cli/command-objects/service-provider.rst index 63ef44e1f2..a92c3b208c 100644 --- a/doc/source/cli/command-objects/service-provider.rst +++ b/doc/source/cli/command-objects/service-provider.rst @@ -1,125 +1,22 @@ -================ -service provider -================ +================= +identity provider +================= -Identity v3 +A **service provider** is used by the Identity service's OS-FEDERATION +extension. It is used by to register another OpenStack Identity service. +Applicable to Identity v3. -`Requires: OS-FEDERATION extension` +.. autoprogram-cliff:: openstack.identity.v3 + :command: service provider create -service provider create ------------------------ +.. autoprogram-cliff:: openstack.identity.v3 + :command: service provider delete -Create new service provider +.. autoprogram-cliff:: openstack.identity.v3 + :command: service provider list -.. program:: service provider create -.. code:: bash +.. autoprogram-cliff:: openstack.identity.v3 + :command: service provider set - openstack service provider create - [--description ] - [--enable | --disable] - --auth-url - --service-provider-url - - -.. option:: --auth-url - - Authentication URL of remote federated service provider (required) - -.. option:: --service-provider-url - - A service URL where SAML assertions are being sent (required) - -.. option:: --description - - New service provider description - -.. option:: --enable - - Enable the service provider (default) - -.. option:: --disable - - Disable the service provider - -.. describe:: - - New service provider name (must be unique) - -service provider delete ------------------------ - -Delete service provider(s) - -.. program:: service provider delete -.. code:: bash - - openstack service provider delete - [ ...] - -.. describe:: - - Service provider(s) to delete - -service provider list ---------------------- - -List service providers - -.. program:: service provider list -.. code:: bash - - openstack service provider list - -service provider set --------------------- - -Set service provider properties - -.. program:: service provider set -.. code:: bash - - openstack service provider set - [--enable | --disable] - [--description ] - [--auth-url ] - [--service-provider-url ] - - -.. option:: --service-provider-url - - New service provider URL, where SAML assertions are sent - -.. option:: --auth-url - - New Authentication URL of remote federated service provider - -.. option:: --description - - New service provider description - -.. option:: --enable - - Enable the service provider - -.. option:: --disable - - Disable the service provider - -.. describe:: - - Service provider to modify - -service provider show ---------------------- - -Display service provider details - -.. program:: service provider show -.. code:: bash - - openstack service provider show - - -.. describe:: - - Service provider to display +.. autoprogram-cliff:: openstack.identity.v3 + :command: service provider show diff --git a/doc/source/cli/command-objects/trust.rst b/doc/source/cli/command-objects/trust.rst index 28459bcaf6..febef1c5b6 100644 --- a/doc/source/cli/command-objects/trust.rst +++ b/doc/source/cli/command-objects/trust.rst @@ -2,101 +2,17 @@ trust ===== -Identity v3 +A **trust** provide project-specific role delegation between users, with +optional impersonation. Requires the OS-TRUST extension. Applies to Identity v3. -trust create ------------- +.. autoprogram-cliff:: openstack.identity.v3 + :command: trust create -Create new trust +.. autoprogram-cliff:: openstack.identity.v3 + :command: trust delete -.. program:: trust create -.. code:: bash +.. autoprogram-cliff:: openstack.identity.v3 + :command: trust list - openstack trust create - --project - --role - [--impersonate] - [--expiration ] - [--project-domain ] - [--trustor-domain ] - [--trustee-domain ] - - - -.. option:: --project - - Project being delegated (name or ID) (required) - -.. option:: --role - - Roles to authorize (name or ID) (repeat option to set multiple values, required) - -.. option:: --impersonate - - Tokens generated from the trust will represent (defaults to False) - -.. option:: --expiration - - Sets an expiration date for the trust (format of YYYY-mm-ddTHH:MM:SS) - -.. option:: --project-domain - - Domain the project belongs to (name or ID). This can be - used in case collisions between user names exist. - -.. option:: --trustor-domain - - Domain that contains (name or ID) - -.. option:: --trustee-domain - - Domain that contains (name or ID) - -.. describe:: - - User that is delegating authorization (name or ID) - -.. describe:: - - User that is assuming authorization (name or ID) - - -trust delete ------------- - -Delete trust(s) - -.. program:: trust delete -.. code:: bash - - openstack trust delete - [ ...] - -.. describe:: - - Trust(s) to delete - -trust list ----------- - -List trusts - -.. program:: trust list -.. code:: bash - - openstack trust list - -trust show ----------- - -Display trust details - -.. program:: trust show -.. code:: bash - - openstack trust show - - -.. describe:: - - Trust to display +.. autoprogram-cliff:: openstack.identity.v3 + :command: trust show From 599fa782623598208c012125dd988a48774cfc6e Mon Sep 17 00:00:00 2001 From: Hongbin Lu Date: Sat, 7 Oct 2017 14:03:14 -0400 Subject: [PATCH 1801/3095] Be robust on import plugin module On loading external plugin, OSC should be robust on importing the plugin module so that commands from other modules can continue to execute. Closes-Bug: #1722008 Change-Id: Ibe716681c7f78fabee31b7ef281af2588d68ab30 --- openstackclient/common/clientmanager.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index ea581696c0..7b2c8a5cc3 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -134,7 +134,13 @@ def get_plugin_modules(group): for ep in pkg_resources.iter_entry_points(group): LOG.debug('Found plugin %r', ep.name) - __import__(ep.module_name) + try: + __import__(ep.module_name) + except Exception: + sys.stderr.write( + "WARNING: Failed to import plugin %r.\n" % ep.name) + continue + module = sys.modules[ep.module_name] mod_list.append(module) init_func = getattr(module, 'Initialize', None) From 885b1149c309667c6e4c3092b67fef3c01a15178 Mon Sep 17 00:00:00 2001 From: Rodolfo Alonso Hernandez Date: Tue, 10 Oct 2017 14:04:40 +0100 Subject: [PATCH 1802/3095] Avoid default mutable values in arguments Mutable values shouldn't be used as default values in function arguments [1]. [1] http://docs.python-guide.org/en/latest/writing/gotchas/ Change-Id: I3c7f915f0409c77f4c430467365eb1bcfd7757b3 --- openstackclient/network/sdk_utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openstackclient/network/sdk_utils.py b/openstackclient/network/sdk_utils.py index 04f168bea3..9f0856175d 100644 --- a/openstackclient/network/sdk_utils.py +++ b/openstackclient/network/sdk_utils.py @@ -16,7 +16,7 @@ def get_osc_show_columns_for_sdk_resource( sdk_resource, osc_column_map, - invisible_columns=[] + invisible_columns=None ): """Get and filter the display and attribute columns for an SDK resource. @@ -40,6 +40,7 @@ def get_osc_show_columns_for_sdk_resource( # Build the OSC column names to display for the SDK resource. attr_map = {} display_columns = list(resource_dict.keys()) + invisible_columns = [] if invisible_columns is None else invisible_columns for col_name in invisible_columns: if col_name in display_columns: display_columns.remove(col_name) From 358544d40ea1ad64abefd5f34f011a3446c1b37d Mon Sep 17 00:00:00 2001 From: Ian Wienand Date: Mon, 16 Oct 2017 10:47:59 +1100 Subject: [PATCH 1803/3095] Switch to $USER in post_test_hooks As a first step to zuul migration, switch to $USER from a static "jenkins" username. Change-Id: I26ed12133c75a69182c56b6ecf483fcdc37d98f3 --- openstackclient/tests/functional/post_test_hook.sh | 8 ++++---- openstackclient/tests/functional/post_test_hook_tips.sh | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/openstackclient/tests/functional/post_test_hook.sh b/openstackclient/tests/functional/post_test_hook.sh index e3b48ef4da..d990cb999f 100755 --- a/openstackclient/tests/functional/post_test_hook.sh +++ b/openstackclient/tests/functional/post_test_hook.sh @@ -13,7 +13,7 @@ function generate_testr_results { sudo .tox/functional/bin/subunit2html $BASE/logs/testrepository.subunit $BASE/logs/testr_results.html sudo gzip -9 $BASE/logs/testrepository.subunit sudo gzip -9 $BASE/logs/testr_results.html - sudo chown jenkins:jenkins $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz + sudo chown $USER:$USER $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz sudo chmod a+r $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz elif [ -f .stestr/0 ]; then sudo .tox/functional/bin/stestr last --subunit > $WORKSPACE/testrepository.subunit @@ -21,13 +21,13 @@ function generate_testr_results { sudo .tox/functional/bin/subunit2html $BASE/logs/testrepository.subunit $BASE/logs/testr_results.html sudo gzip -9 $BASE/logs/testrepository.subunit sudo gzip -9 $BASE/logs/testr_results.html - sudo chown jenkins:jenkins $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz + sudo chown $USER:$USER $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz sudo chmod a+r $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz fi } export OPENSTACKCLIENT_DIR="$BASE/new/python-openstackclient" -sudo chown -R jenkins:stack $OPENSTACKCLIENT_DIR +sudo chown -R $USER:stack $OPENSTACKCLIENT_DIR # Go to the openstackclient dir cd $OPENSTACKCLIENT_DIR @@ -42,7 +42,7 @@ echo 'Running tests with:' env | grep OS # Preserve env for OS_ credentials -sudo -E -H -u jenkins tox -efunctional +sudo -E -H -u $USER tox -efunctional EXIT_CODE=$? set -e diff --git a/openstackclient/tests/functional/post_test_hook_tips.sh b/openstackclient/tests/functional/post_test_hook_tips.sh index 232527a30b..4cae7cdfe0 100755 --- a/openstackclient/tests/functional/post_test_hook_tips.sh +++ b/openstackclient/tests/functional/post_test_hook_tips.sh @@ -17,7 +17,7 @@ function generate_testr_results { sudo .tox/functional-tips/bin/subunit2html $BASE/logs/testrepository.subunit $BASE/logs/testr_results.html sudo gzip -9 $BASE/logs/testrepository.subunit sudo gzip -9 $BASE/logs/testr_results.html - sudo chown jenkins:jenkins $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz + sudo chown $USER:$USER $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz sudo chmod a+r $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz elif [ -f .stestr/0 ]; then sudo .tox/functional-tips/bin/stestr last --subunit > $WORKSPACE/testrepository.subunit @@ -25,13 +25,13 @@ function generate_testr_results { sudo .tox/functional-tips/bin/subunit2html $BASE/logs/testrepository.subunit $BASE/logs/testr_results.html sudo gzip -9 $BASE/logs/testrepository.subunit sudo gzip -9 $BASE/logs/testr_results.html - sudo chown jenkins:jenkins $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz + sudo chown $USER:$USER $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz sudo chmod a+r $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz fi } export OPENSTACKCLIENT_DIR="$BASE/new/python-openstackclient" -sudo chown -R jenkins:stack $OPENSTACKCLIENT_DIR +sudo chown -R $USER:stack $OPENSTACKCLIENT_DIR # Go to the openstackclient dir cd $OPENSTACKCLIENT_DIR @@ -46,7 +46,7 @@ echo 'Running tests with:' env | grep OS # Preserve env for OS_ credentials -sudo -E -H -u jenkins tox -e functional-tips +sudo -E -H -u $USER tox -e functional-tips EXIT_CODE=$? set -e From 4733621ecbfdd7e319f2822e25f87cdb75081758 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Mon, 2 Oct 2017 17:21:56 -0500 Subject: [PATCH 1804/3095] Move base functional test job in-repo Depends-On: I26ed12133c75a69182c56b6ecf483fcdc37d98f3 Change-Id: Ib46eed6e038a502926bf92297120e2d494ef5b20 --- .zuul.yaml | 19 +++++ playbooks/osc-functional-devstack/post.yaml | 80 ++++++++++++++++++++ playbooks/osc-functional-devstack/run.yaml | 83 +++++++++++++++++++++ 3 files changed, 182 insertions(+) create mode 100644 .zuul.yaml create mode 100644 playbooks/osc-functional-devstack/post.yaml create mode 100644 playbooks/osc-functional-devstack/run.yaml diff --git a/.zuul.yaml b/.zuul.yaml new file mode 100644 index 0000000000..d7de0efeef --- /dev/null +++ b/.zuul.yaml @@ -0,0 +1,19 @@ +# from zuul.d/zuul-legacy-jobs.yaml legacy-osc-dsvm-functional +- job: + name: osc-functional-devstack + parent: legacy-dsvm-base + run: playbooks/osc-functional-devstack/run + post-run: playbooks/osc-functional-devstack/post + timeout: 7800 + required-projects: + - openstack-infra/devstack-gate + - openstack/python-openstackclient + +- project: + name: openstack/python-openstackclient + check: + jobs: + - osc-functional-devstack + gate: + jobs: + - osc-functional-devstack diff --git a/playbooks/osc-functional-devstack/post.yaml b/playbooks/osc-functional-devstack/post.yaml new file mode 100644 index 0000000000..dac875340a --- /dev/null +++ b/playbooks/osc-functional-devstack/post.yaml @@ -0,0 +1,80 @@ +- hosts: primary + tasks: + + - name: Copy files from {{ ansible_user_dir }}/workspace/ on node + synchronize: + src: '{{ ansible_user_dir }}/workspace/' + dest: '{{ zuul.executor.log_root }}' + mode: pull + copy_links: true + verify_host: true + rsync_opts: + - --include=**/*nose_results.html + - --include=*/ + - --exclude=* + - --prune-empty-dirs + + - name: Copy files from {{ ansible_user_dir }}/workspace/ on node + synchronize: + src: '{{ ansible_user_dir }}/workspace/' + dest: '{{ zuul.executor.log_root }}' + mode: pull + copy_links: true + verify_host: true + rsync_opts: + - --include=**/*testr_results.html.gz + - --include=*/ + - --exclude=* + - --prune-empty-dirs + + - name: Copy files from {{ ansible_user_dir }}/workspace/ on node + synchronize: + src: '{{ ansible_user_dir }}/workspace/' + dest: '{{ zuul.executor.log_root }}' + mode: pull + copy_links: true + verify_host: true + rsync_opts: + - --include=/.testrepository/tmp* + - --include=*/ + - --exclude=* + - --prune-empty-dirs + + - name: Copy files from {{ ansible_user_dir }}/workspace/ on node + synchronize: + src: '{{ ansible_user_dir }}/workspace/' + dest: '{{ zuul.executor.log_root }}' + mode: pull + copy_links: true + verify_host: true + rsync_opts: + - --include=**/*testrepository.subunit.gz + - --include=*/ + - --exclude=* + - --prune-empty-dirs + + - name: Copy files from {{ ansible_user_dir }}/workspace/ on node + synchronize: + src: '{{ ansible_user_dir }}/workspace/' + dest: '{{ zuul.executor.log_root }}/tox' + mode: pull + copy_links: true + verify_host: true + rsync_opts: + - --include=/.tox/*/log/* + - --include=*/ + - --exclude=* + - --prune-empty-dirs + + - name: Copy files from {{ ansible_user_dir }}/workspace/ on node + synchronize: + src: '{{ ansible_user_dir }}/workspace/' + dest: '{{ zuul.executor.log_root }}' + mode: pull + copy_links: true + verify_host: true + rsync_opts: + - --include=/logs/** + - --include=*/ + - --exclude=* + - --prune-empty-dirs diff --git a/playbooks/osc-functional-devstack/run.yaml b/playbooks/osc-functional-devstack/run.yaml new file mode 100644 index 0000000000..185ba437f3 --- /dev/null +++ b/playbooks/osc-functional-devstack/run.yaml @@ -0,0 +1,83 @@ +- hosts: all + name: Autoconverted job legacy-osc-dsvm-functional from old job gate-osc-dsvm-functional-ubuntu-xenial + tasks: + + - name: Ensure legacy workspace directory + file: + path: '{{ ansible_user_dir }}/workspace' + state: directory + + - shell: + cmd: | + set -e + set -x + cat > clonemap.yaml << EOF + clonemap: + - name: openstack-infra/devstack-gate + dest: devstack-gate + EOF + /usr/zuul-env/bin/zuul-cloner -m clonemap.yaml --cache-dir /opt/git \ + git://git.openstack.org \ + openstack-infra/devstack-gate + executable: /bin/bash + chdir: '{{ ansible_user_dir }}/workspace' + environment: '{{ zuul | zuul_legacy_vars }}' + + - shell: + cmd: | + set -e + set -x + cat << 'EOF' >>"/tmp/dg-local.conf" + [[local|localrc]] + # NOTE(amotoki): Some neutron features are enabled by devstack plugin + enable_plugin neutron https://git.openstack.org/openstack/neutron + enable_service q-qos + enable_service neutron-segments + # NOTE(dtroyer): OSC needs to support Image v1 for a while yet so re-enable + GLANCE_V1_ENABLED=True + # NOTE(dtroyer): Functional tests need a bit more volume headroom + VOLUME_BACKING_FILE_SIZE=20G + # NOTE(dtroyer): OSC needs to support Volume v1 for a while yet so re-enable + [[post-config|$CINDER_CONF]] + [DEFAULT] + enable_v1_api = True + + EOF + executable: /bin/bash + chdir: '{{ ansible_user_dir }}/workspace' + environment: '{{ zuul | zuul_legacy_vars }}' + + - shell: + cmd: | + set -e + set -x + export PYTHONUNBUFFERED=true + export DEVSTACK_GATE_UNSTACK=0 + export DEVSTACK_GATE_TEMPEST=0 + export DEVSTACK_GATE_EXERCISES=0 + export DEVSTACK_GATE_INSTALL_TESTONLY=1 + export DEVSTACK_GATE_NEUTRON=1 + export BRANCH_OVERRIDE=default + export DEVSTACK_PROJECT_FROM_GIT=python-openstackclient + if [ "$BRANCH_OVERRIDE" != "default" ] ; then + export OVERRIDE_ZUUL_BRANCH=$BRANCH_OVERRIDE + fi + + function post_test_hook { + # NOTE(stevemar): After the newton release was tagged the file was moved. + # But, we run functional tests for various stable releases + # (mitaka, and newton). + # TODO(stevemar): Remove this check when Newton hits EOL. + hook_location=$BASE/new/python-openstackclient/openstackclient/tests/functional/post_test_hook.sh + if [ ! -f "$hook_location" ]; then + hook_location=$BASE/new/python-openstackclient/post_test_hook.sh + fi + bash -xe $hook_location + } + export -f post_test_hook + + cp devstack-gate/devstack-vm-gate-wrap.sh ./safe-devstack-vm-gate-wrap.sh + ./safe-devstack-vm-gate-wrap.sh + executable: /bin/bash + chdir: '{{ ansible_user_dir }}/workspace' + environment: '{{ zuul | zuul_legacy_vars }}' From 82f45d9bd203aee77914c1f9e300f7dbedf673c8 Mon Sep 17 00:00:00 2001 From: Daniel Speichert Date: Sun, 15 Oct 2017 16:35:37 -0400 Subject: [PATCH 1805/3095] Allow creating security rules without protocol In order to create a rule for any protocol, the client must not specify the protocol in the API call. This is currently impossible because protocol defaults to TCP. In order not to change the default behavior, a "new" protocol name is added: "any", which makes this CLI skip sending the protocol field altogether. Change-Id: I58853d3745f3631007e5e9780c0c5c2526b730a3 Closes-Bug: 1712242 --- .../network/v2/security_group_rule.py | 8 +++-- .../v2/test_security_group_rule_network.py | 30 +++++++++++++++++++ .../notes/bug-1712242-934bbe2f2378f5bd.yaml | 12 ++++++++ 3 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/bug-1712242-934bbe2f2378f5bd.yaml diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index 06d467254b..ca0e00b9d8 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -159,8 +159,8 @@ def update_parser_network(self, parser): help=_("IP protocol (ah, dccp, egp, esp, gre, icmp, igmp, " "ipv6-encap, ipv6-frag, ipv6-icmp, ipv6-nonxt, " "ipv6-opts, ipv6-route, ospf, pgm, rsvp, sctp, tcp, " - "udp, udplite, vrrp and integer representations [0-255]; " - "default: tcp)") + "udp, udplite, vrrp and integer representations [0-255] " + "or any; default: tcp)") ) protocol_group.add_argument( '--proto', @@ -230,6 +230,8 @@ def _get_protocol(self, parsed_args): protocol = parsed_args.protocol if parsed_args.proto is not None: protocol = parsed_args.proto + if protocol == 'any': + protocol = None return protocol def _is_ipv6_protocol(self, protocol): @@ -237,7 +239,7 @@ def _is_ipv6_protocol(self, protocol): # However, while the OSC CLI doesn't document the protocol, # the code must still handle it. In addition, handle both # protocol names and numbers. - if (protocol.startswith('ipv6-') or + if (protocol is not None and protocol.startswith('ipv6-') or protocol in ['icmpv6', '41', '43', '44', '58', '59', '60']): return True else: diff --git a/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py b/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py index 36add8ca80..fe6d364928 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py @@ -211,6 +211,36 @@ def test_create_proto_option(self): self.assertEqual(self.expected_columns, columns) self.assertEqual(self.expected_data, data) + def test_create_protocol_any(self): + self._setup_security_group_rule({ + 'protocol': None, + 'remote_ip_prefix': '10.0.2.0/24', + }) + arglist = [ + '--proto', 'any', + '--src-ip', self._security_group_rule.remote_ip_prefix, + self._security_group.id, + ] + verifylist = [ + ('proto', 'any'), + ('protocol', None), + ('src_ip', self._security_group_rule.remote_ip_prefix), + ('group', self._security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_security_group_rule.assert_called_once_with(**{ + 'direction': self._security_group_rule.direction, + 'ethertype': self._security_group_rule.ether_type, + 'protocol': self._security_group_rule.protocol, + 'remote_ip_prefix': self._security_group_rule.remote_ip_prefix, + 'security_group_id': self._security_group.id, + }) + self.assertEqual(self.expected_columns, columns) + self.assertEqual(self.expected_data, data) + def test_create_remote_group(self): self._setup_security_group_rule({ 'port_range_max': 22, diff --git a/releasenotes/notes/bug-1712242-934bbe2f2378f5bd.yaml b/releasenotes/notes/bug-1712242-934bbe2f2378f5bd.yaml new file mode 100644 index 0000000000..322d0bd372 --- /dev/null +++ b/releasenotes/notes/bug-1712242-934bbe2f2378f5bd.yaml @@ -0,0 +1,12 @@ +--- +features: + - | + Add ``any`` as a ``--protocol`` option to ``security group rule create`` + command. + [Bug `1517134 `_] +fixes: + - | + It is now possible to create a security rule without specifying protocol + (using ``--protocol any``), which skips sending the protocol to the API + server entirely. Previously TCP was forced as default protocol when none + was specified. From c901620a036c31d71b2108c51431d53a9eb0dad9 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 18 Oct 2017 14:01:12 -0500 Subject: [PATCH 1806/3095] Attempt to de-race qos policy We're getting about 1-in-6 failures on qos policy delete now, with the message that the policy is in use by a network. It shouldn't be, this is possibly due to the small window where the policy is set as the default. Let's remove that and shore up the test using --share instead. Change-Id: I8d669bd3c5c88dadd2927aee89e5ef72cf4001c4 --- .../functional/network/v2/test_network_qos_policy.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/openstackclient/tests/functional/network/v2/test_network_qos_policy.py b/openstackclient/tests/functional/network/v2/test_network_qos_policy.py index fc36f49032..02e6402802 100644 --- a/openstackclient/tests/functional/network/v2/test_network_qos_policy.py +++ b/openstackclient/tests/functional/network/v2/test_network_qos_policy.py @@ -66,7 +66,6 @@ def test_qos_policy_set(self): self.openstack( 'network qos policy set ' + '--share ' + - '--default ' + policy_name ) @@ -75,11 +74,16 @@ def test_qos_policy_set(self): policy_name )) self.assertTrue(json_output['shared']) - self.assertTrue(json_output['is_default']) - self.openstack('network qos policy set --no-default ' + policy_name) + self.openstack( + 'network qos policy set ' + + '--no-share ' + + '--no-default ' + + policy_name + ) json_output = json.loads(self.openstack( 'network qos policy show -f json ' + policy_name )) + self.assertFalse(json_output['shared']) self.assertFalse(json_output['is_default']) From 161c79f7037364d6e69e43f1d0a9c605b96ebb88 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Tue, 17 Oct 2017 11:03:10 -0500 Subject: [PATCH 1807/3095] Move more jobs in-repo Now consume the merged openstackclient-plugin-jobs template in openstack/openstackclient repo. Change-Id: I60f2c8ad443c802e13de8f185647faa68e07bfe2 --- .zuul.yaml | 38 ++++++++ .../osc-functional-devstack-n-net/post.yaml | 80 +++++++++++++++++ .../osc-functional-devstack-n-net/run.yaml | 85 ++++++++++++++++++ .../osc-functional-devstack-tips/post.yaml | 80 +++++++++++++++++ .../osc-functional-devstack-tips/run.yaml | 90 +++++++++++++++++++ 5 files changed, 373 insertions(+) create mode 100644 playbooks/osc-functional-devstack-n-net/post.yaml create mode 100644 playbooks/osc-functional-devstack-n-net/run.yaml create mode 100644 playbooks/osc-functional-devstack-tips/post.yaml create mode 100644 playbooks/osc-functional-devstack-tips/run.yaml diff --git a/.zuul.yaml b/.zuul.yaml index d7de0efeef..905af05b70 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -1,4 +1,5 @@ # from zuul.d/zuul-legacy-jobs.yaml legacy-osc-dsvm-functional + - job: name: osc-functional-devstack parent: legacy-dsvm-base @@ -9,11 +10,48 @@ - openstack-infra/devstack-gate - openstack/python-openstackclient +- job: + name: osc-functional-devstack-n-net + parent: legacy-dsvm-base + run: playbooks/osc-functional-devstack-n-net/run + post-run: playbooks/osc-functional-devstack-n-net/post + timeout: 7800 + required-projects: + - openstack-infra/devstack-gate + - openstack/python-openstackclient + +- job: + name: osc-functional-devstack-tips + parent: legacy-dsvm-base + run: playbooks/osc-functional-devstack-tips/run + post-run: playbooks/osc-functional-devstack-tips/post + timeout: 7800 + required-projects: + - openstack-infra/devstack-gate + - openstack/os-client-config + - openstack/osc-lib + - openstack/python-openstackclient + - openstack/python-openstacksdk + + - project: name: openstack/python-openstackclient + templates: + - openstackclient-plugin-jobs check: jobs: - osc-functional-devstack + - osc-functional-devstack-n-net: + voting: false + # The job testing nova-network no longer works before Pike, and + # should be disabled until the New Way of testing against old clouds + # is ready and backported + branches: ^(?!stable/(newton|ocata)).*$ + - osc-functional-devstack-tips: + voting: false + # The functional-tips job only tests the latest and shouldn't be run + # on the stable branches + branches: ^(?!stable) gate: jobs: - osc-functional-devstack diff --git a/playbooks/osc-functional-devstack-n-net/post.yaml b/playbooks/osc-functional-devstack-n-net/post.yaml new file mode 100644 index 0000000000..dac875340a --- /dev/null +++ b/playbooks/osc-functional-devstack-n-net/post.yaml @@ -0,0 +1,80 @@ +- hosts: primary + tasks: + + - name: Copy files from {{ ansible_user_dir }}/workspace/ on node + synchronize: + src: '{{ ansible_user_dir }}/workspace/' + dest: '{{ zuul.executor.log_root }}' + mode: pull + copy_links: true + verify_host: true + rsync_opts: + - --include=**/*nose_results.html + - --include=*/ + - --exclude=* + - --prune-empty-dirs + + - name: Copy files from {{ ansible_user_dir }}/workspace/ on node + synchronize: + src: '{{ ansible_user_dir }}/workspace/' + dest: '{{ zuul.executor.log_root }}' + mode: pull + copy_links: true + verify_host: true + rsync_opts: + - --include=**/*testr_results.html.gz + - --include=*/ + - --exclude=* + - --prune-empty-dirs + + - name: Copy files from {{ ansible_user_dir }}/workspace/ on node + synchronize: + src: '{{ ansible_user_dir }}/workspace/' + dest: '{{ zuul.executor.log_root }}' + mode: pull + copy_links: true + verify_host: true + rsync_opts: + - --include=/.testrepository/tmp* + - --include=*/ + - --exclude=* + - --prune-empty-dirs + + - name: Copy files from {{ ansible_user_dir }}/workspace/ on node + synchronize: + src: '{{ ansible_user_dir }}/workspace/' + dest: '{{ zuul.executor.log_root }}' + mode: pull + copy_links: true + verify_host: true + rsync_opts: + - --include=**/*testrepository.subunit.gz + - --include=*/ + - --exclude=* + - --prune-empty-dirs + + - name: Copy files from {{ ansible_user_dir }}/workspace/ on node + synchronize: + src: '{{ ansible_user_dir }}/workspace/' + dest: '{{ zuul.executor.log_root }}/tox' + mode: pull + copy_links: true + verify_host: true + rsync_opts: + - --include=/.tox/*/log/* + - --include=*/ + - --exclude=* + - --prune-empty-dirs + + - name: Copy files from {{ ansible_user_dir }}/workspace/ on node + synchronize: + src: '{{ ansible_user_dir }}/workspace/' + dest: '{{ zuul.executor.log_root }}' + mode: pull + copy_links: true + verify_host: true + rsync_opts: + - --include=/logs/** + - --include=*/ + - --exclude=* + - --prune-empty-dirs diff --git a/playbooks/osc-functional-devstack-n-net/run.yaml b/playbooks/osc-functional-devstack-n-net/run.yaml new file mode 100644 index 0000000000..cb06ff813b --- /dev/null +++ b/playbooks/osc-functional-devstack-n-net/run.yaml @@ -0,0 +1,85 @@ +- hosts: all + name: Autoconverted job legacy-osc-dsvm-functional-n-net from old job gate-osc-dsvm-functional-n-net-ubuntu-xenial-nv + tasks: + + - name: Ensure legacy workspace directory + file: + path: '{{ ansible_user_dir }}/workspace' + state: directory + + - shell: + cmd: | + set -e + set -x + cat > clonemap.yaml << EOF + clonemap: + - name: openstack-infra/devstack-gate + dest: devstack-gate + EOF + /usr/zuul-env/bin/zuul-cloner -m clonemap.yaml --cache-dir /opt/git \ + git://git.openstack.org \ + openstack-infra/devstack-gate + executable: /bin/bash + chdir: '{{ ansible_user_dir }}/workspace' + environment: '{{ zuul | zuul_legacy_vars }}' + + - shell: + cmd: | + set -e + set -x + cat << 'EOF' >>"/tmp/dg-local.conf" + [[local|localrc]] + # NOTE(RuiChen): nova-network only can be enable in nova cell v1 + enable_service n-net n-cell + disable_service neutron q-svc q-agt q-dhcp q-l3 q-meta q-metering + # NOTE(dtroyer): OSC needs to support Image v1 for a while yet so re-enable + GLANCE_V1_ENABLED=True + # NOTE(dtroyer): Functional tests need a bit more volume headroom + VOLUME_BACKING_FILE_SIZE=20G + # NOTE(dtroyer): OSC needs to support Volume v1 for a while yet so re-enable + [[post-config|$CINDER_CONF]] + [DEFAULT] + enable_v1_api = True + + EOF + executable: /bin/bash + chdir: '{{ ansible_user_dir }}/workspace' + environment: '{{ zuul | zuul_legacy_vars }}' + + - shell: + cmd: | + set -e + set -x + export PYTHONUNBUFFERED=true + export DEVSTACK_GATE_UNSTACK=0 + export DEVSTACK_GATE_TEMPEST=0 + export DEVSTACK_GATE_EXERCISES=0 + export DEVSTACK_GATE_INSTALL_TESTONLY=1 + # NOTE(RuiChen): Explicitly tell devstack-gate that we need to run + # the nova-network job with cell v1. + export DEVSTACK_GATE_NEUTRON=0 + export DEVSTACK_GATE_CELLS=1 + export BRANCH_OVERRIDE=default + export DEVSTACK_PROJECT_FROM_GIT=python-openstackclient + if [ "$BRANCH_OVERRIDE" != "default" ] ; then + export OVERRIDE_ZUUL_BRANCH=$BRANCH_OVERRIDE + fi + + function post_test_hook { + # NOTE(stevemar): After the newton release was tagged the file was moved. + # But, we run functional tests for various stable releases + # (mitaka, and newton). + # TODO(stevemar): Remove this check when Newton hits EOL. + hook_location=$BASE/new/python-openstackclient/openstackclient/tests/functional/post_test_hook.sh + if [ ! -f "$hook_location" ]; then + hook_location=$BASE/new/python-openstackclient/post_test_hook.sh + fi + bash -xe $hook_location + } + export -f post_test_hook + + cp devstack-gate/devstack-vm-gate-wrap.sh ./safe-devstack-vm-gate-wrap.sh + ./safe-devstack-vm-gate-wrap.sh + executable: /bin/bash + chdir: '{{ ansible_user_dir }}/workspace' + environment: '{{ zuul | zuul_legacy_vars }}' diff --git a/playbooks/osc-functional-devstack-tips/post.yaml b/playbooks/osc-functional-devstack-tips/post.yaml new file mode 100644 index 0000000000..dac875340a --- /dev/null +++ b/playbooks/osc-functional-devstack-tips/post.yaml @@ -0,0 +1,80 @@ +- hosts: primary + tasks: + + - name: Copy files from {{ ansible_user_dir }}/workspace/ on node + synchronize: + src: '{{ ansible_user_dir }}/workspace/' + dest: '{{ zuul.executor.log_root }}' + mode: pull + copy_links: true + verify_host: true + rsync_opts: + - --include=**/*nose_results.html + - --include=*/ + - --exclude=* + - --prune-empty-dirs + + - name: Copy files from {{ ansible_user_dir }}/workspace/ on node + synchronize: + src: '{{ ansible_user_dir }}/workspace/' + dest: '{{ zuul.executor.log_root }}' + mode: pull + copy_links: true + verify_host: true + rsync_opts: + - --include=**/*testr_results.html.gz + - --include=*/ + - --exclude=* + - --prune-empty-dirs + + - name: Copy files from {{ ansible_user_dir }}/workspace/ on node + synchronize: + src: '{{ ansible_user_dir }}/workspace/' + dest: '{{ zuul.executor.log_root }}' + mode: pull + copy_links: true + verify_host: true + rsync_opts: + - --include=/.testrepository/tmp* + - --include=*/ + - --exclude=* + - --prune-empty-dirs + + - name: Copy files from {{ ansible_user_dir }}/workspace/ on node + synchronize: + src: '{{ ansible_user_dir }}/workspace/' + dest: '{{ zuul.executor.log_root }}' + mode: pull + copy_links: true + verify_host: true + rsync_opts: + - --include=**/*testrepository.subunit.gz + - --include=*/ + - --exclude=* + - --prune-empty-dirs + + - name: Copy files from {{ ansible_user_dir }}/workspace/ on node + synchronize: + src: '{{ ansible_user_dir }}/workspace/' + dest: '{{ zuul.executor.log_root }}/tox' + mode: pull + copy_links: true + verify_host: true + rsync_opts: + - --include=/.tox/*/log/* + - --include=*/ + - --exclude=* + - --prune-empty-dirs + + - name: Copy files from {{ ansible_user_dir }}/workspace/ on node + synchronize: + src: '{{ ansible_user_dir }}/workspace/' + dest: '{{ zuul.executor.log_root }}' + mode: pull + copy_links: true + verify_host: true + rsync_opts: + - --include=/logs/** + - --include=*/ + - --exclude=* + - --prune-empty-dirs diff --git a/playbooks/osc-functional-devstack-tips/run.yaml b/playbooks/osc-functional-devstack-tips/run.yaml new file mode 100644 index 0000000000..7bd537389d --- /dev/null +++ b/playbooks/osc-functional-devstack-tips/run.yaml @@ -0,0 +1,90 @@ +- hosts: all + name: Autoconverted job legacy-osc-dsvm-functional-tips from old job gate-osc-dsvm-functional-tips-ubuntu-xenial-nv + tasks: + + - name: Ensure legacy workspace directory + file: + path: '{{ ansible_user_dir }}/workspace' + state: directory + + - shell: + cmd: | + set -e + set -x + cat > clonemap.yaml << EOF + clonemap: + - name: openstack-infra/devstack-gate + dest: devstack-gate + EOF + /usr/zuul-env/bin/zuul-cloner -m clonemap.yaml --cache-dir /opt/git \ + git://git.openstack.org \ + openstack-infra/devstack-gate + executable: /bin/bash + chdir: '{{ ansible_user_dir }}/workspace' + environment: '{{ zuul | zuul_legacy_vars }}' + + - shell: + cmd: | + set -e + set -x + cat << 'EOF' >>"/tmp/dg-local.conf" + [[local|localrc]] + # NOTE(amotoki): Some neutron features are enabled by devstack plugin + enable_plugin neutron https://git.openstack.org/openstack/neutron + enable_service q-qos + enable_service neutron-segments + # NOTE(dtroyer): OSC needs to support Image v1 for a while yet so re-enable + GLANCE_V1_ENABLED=True + # NOTE(dtroyer): Functional tests need a bit more volume headroom + VOLUME_BACKING_FILE_SIZE=20G + # Swift is not ready for python3 yet: At a minimum keystonemiddleware needs + # to be installed in the py2 env, there are probably other things too... + disable_service s-account + disable_service s-container + disable_service s-object + disable_service s-proxy + # This is insufficient, but leaving it here as a reminder of what may + # someday be all we need to make this work + disable_python3_package swift + # NOTE(dtroyer): OSC needs to support Volume v1 for a while yet so re-enable + [[post-config|$CINDER_CONF]] + [DEFAULT] + enable_v1_api = True + + EOF + executable: /bin/bash + chdir: '{{ ansible_user_dir }}/workspace' + environment: '{{ zuul | zuul_legacy_vars }}' + + - shell: + cmd: | + set -e + set -x + export PYTHONUNBUFFERED=true + export DEVSTACK_GATE_USE_PYTHON3=True + export DEVSTACK_GATE_UNSTACK=0 + export DEVSTACK_GATE_TEMPEST=0 + export DEVSTACK_GATE_EXERCISES=0 + export DEVSTACK_GATE_INSTALL_TESTONLY=1 + export DEVSTACK_GATE_NEUTRON=1 + export BRANCH_OVERRIDE=default + export DEVSTACK_PROJECT_FROM_GIT="python-openstackclient,python-openstacksdk,osc-lib,os-client-config" + if [ "$BRANCH_OVERRIDE" != "default" ] ; then + export OVERRIDE_ZUUL_BRANCH=$BRANCH_OVERRIDE + fi + + function post_test_hook { + # This test hook will install the master version of the following: + # - osc-lib + # - openstacksdk + # - os-client-config + hook_location=$BASE/new/python-openstackclient/openstackclient/tests/functional/post_test_hook_tips.sh + bash -xe $hook_location + } + export -f post_test_hook + + cp devstack-gate/devstack-vm-gate-wrap.sh ./safe-devstack-vm-gate-wrap.sh + ./safe-devstack-vm-gate-wrap.sh + executable: /bin/bash + chdir: '{{ ansible_user_dir }}/workspace' + environment: '{{ zuul | zuul_legacy_vars }}' From 4464109c7754a9287f75ec2af84398700d1450e6 Mon Sep 17 00:00:00 2001 From: ShogoAdachi Date: Thu, 7 Sep 2017 19:10:24 +0900 Subject: [PATCH 1808/3095] Accept 0 for --min-disk and --min-ram The current openstackclient implementation cannot accept 0 for --min-disk and --min-ram with the "openstack image set" command. If theses options get set to 0, the option parser in openstackclient wrongly interprets 0 as no option value. The 0 is valid for these options if administrators want to make it the default(no minimum requirements). This patch fix the parser so that it avoids only 'None'. Change-Id: Ie8ee37484c02c26f54adc56263fcd167c0ce7eb3 Closes-bug: #1719499 --- openstackclient/image/v1/image.py | 6 +++-- openstackclient/image/v2/image.py | 4 +-- .../tests/unit/image/v1/test_image.py | 26 +++++++++++++++++++ .../tests/unit/image/v2/test_image.py | 26 +++++++++++++++++++ .../notes/bug-1719499-d67d80b0da0bc30a.yaml | 5 ++++ 5 files changed, 63 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/bug-1719499-d67d80b0da0bc30a.yaml diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index b92da8ce5b..7a8e67bfca 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -625,11 +625,11 @@ def take_action(self, parsed_args): kwargs = {} copy_attrs = ('name', 'owner', 'min_disk', 'min_ram', 'properties', 'container_format', 'disk_format', 'size', 'store', - 'location', 'copy_from', 'volume', 'force', 'checksum') + 'location', 'copy_from', 'volume', 'checksum') for attr in copy_attrs: if attr in parsed_args: val = getattr(parsed_args, attr, None) - if val: + if val is not None: # Only include a value in kwargs for attributes that are # actually present on the command line kwargs[attr] = val @@ -653,6 +653,8 @@ def take_action(self, parsed_args): kwargs['is_public'] = True if parsed_args.private: kwargs['is_public'] = False + if parsed_args.force: + kwargs['force'] = True # Wrap the call to catch exceptions in order to close files try: diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index a2e45fc707..d793c4599b 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -749,7 +749,7 @@ def get_parser(self, prog_name): "--tag", dest="tags", metavar="", - default=[], + default=None, action='append', help=_("Set a tag on this image " "(repeat option to set multiple tags)"), @@ -860,7 +860,7 @@ def take_action(self, parsed_args): for attr in copy_attrs: if attr in parsed_args: val = getattr(parsed_args, attr, None) - if val: + if val is not None: # Only include a value in kwargs for attributes that are # actually present on the command line kwargs[attr] = val diff --git a/openstackclient/tests/unit/image/v1/test_image.py b/openstackclient/tests/unit/image/v1/test_image.py index 8a83feb0c4..ae578d9138 100644 --- a/openstackclient/tests/unit/image/v1/test_image.py +++ b/openstackclient/tests/unit/image/v1/test_image.py @@ -689,6 +689,32 @@ def test_image_update_volume(self): ) self.assertIsNone(result) + def test_image_set_numeric_options_to_zero(self): + arglist = [ + '--min-disk', '0', + '--min-ram', '0', + self._image.name, + ] + verifylist = [ + ('min_disk', 0), + ('min_ram', 0), + ('image', self._image.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + kwargs = { + 'min_disk': 0, + 'min_ram': 0, + } + # ImageManager.update(image, **kwargs) + self.images_mock.update.assert_called_with( + self._image.id, + **kwargs + ) + self.assertIsNone(result) + class TestImageShow(TestImage): diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py index 429ddd282b..383619ef54 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -1313,6 +1313,32 @@ def test_image_set_dead_options(self): exceptions.CommandError, self.cmd.take_action, parsed_args) + def test_image_set_numeric_options_to_zero(self): + arglist = [ + '--min-disk', '0', + '--min-ram', '0', + image_fakes.image_name, + ] + verifylist = [ + ('min_disk', 0), + ('min_ram', 0), + ('image', image_fakes.image_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + kwargs = { + 'min_disk': 0, + 'min_ram': 0, + } + # ImageManager.update(image, **kwargs) + self.images_mock.update.assert_called_with( + image_fakes.image_id, + **kwargs + ) + self.assertIsNone(result) + class TestImageShow(TestImage): diff --git a/releasenotes/notes/bug-1719499-d67d80b0da0bc30a.yaml b/releasenotes/notes/bug-1719499-d67d80b0da0bc30a.yaml new file mode 100644 index 0000000000..37fbdc5529 --- /dev/null +++ b/releasenotes/notes/bug-1719499-d67d80b0da0bc30a.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Accept ``0`` as a valid value in the ``image set`` ``--min-disk`` and ``--min-ram`` options. + .. _bug 1719499: https://bugs.launchpad.net/python-openstackclient/+bug/1719499 From ef595fcfc4b42d2a7baeeddee9b804541b81637f Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Sun, 22 Oct 2017 16:11:39 +0200 Subject: [PATCH 1809/3095] Add wrapper around ostestr The functional tests assume that an openrc file has been sourced. Make a simple wrapper that will do that. Change-Id: I42584aaebcbca99a8c922f6ff90c8bbce57bbfbb --- openstackclient/tests/functional/run_ostestr.sh | 16 ++++++++++++++++ tox.ini | 4 ++++ 2 files changed, 20 insertions(+) create mode 100755 openstackclient/tests/functional/run_ostestr.sh diff --git a/openstackclient/tests/functional/run_ostestr.sh b/openstackclient/tests/functional/run_ostestr.sh new file mode 100755 index 0000000000..a6adad965c --- /dev/null +++ b/openstackclient/tests/functional/run_ostestr.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +# This is a script that runs ostestr with the openrc OS_ variables sourced. +# Do not run this script unless you know what you're doing. +# For more information refer to: +# https://docs.openstack.org/python-openstackclient/latest/ + +# Source environment variables to kick things off +if [ -f ~stack/devstack/openrc ] ; then + source ~stack/devstack/openrc admin admin +fi + +echo 'Running tests with:' +env | grep OS + +ostestr $* diff --git a/tox.ini b/tox.ini index 3c1b3c387a..a16f0eb849 100644 --- a/tox.ini +++ b/tox.ini @@ -56,6 +56,10 @@ commands = [testenv:functional] setenv = OS_TEST_PATH=./openstackclient/tests/functional passenv = OS_* +# Enable this when running Zuul v3 jobs +#whitelist_externals = openstackclient/tests/functional/run_ostestr.sh +#commands = +# {toxinidir}/openstackclient/tests/functional/run_ostestr.sh {posargs} [testenv:functional-tips] setenv = OS_TEST_PATH=./openstackclient/tests/functional From 676159555f10964d0b7f5cf77539c49b3fd90cda Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 19 Oct 2017 15:48:12 -0500 Subject: [PATCH 1810/3095] Native DevStack jobs Convert legacy DevStack jobs to native Zuul v3 form, plus some test-job-related docs updates. Change-Id: Ia8c08be81605da885b9eee799fc58129305dfc41 --- .zuul.yaml | 110 +++++++++++++++--- doc/source/contributor/plugins.rst | 17 ++- .../tests/functional/post_test_hook.sh | 51 -------- .../tests/functional/post_test_hook_tips.sh | 55 --------- playbooks/osc-devstack/post.yaml | 4 + playbooks/osc-devstack/pre.yaml | 8 ++ playbooks/osc-devstack/run.yaml | 3 + .../osc-functional-devstack-n-net/post.yaml | 80 ------------- .../osc-functional-devstack-n-net/run.yaml | 85 -------------- .../osc-functional-devstack-tips/post.yaml | 80 ------------- .../osc-functional-devstack-tips/run.yaml | 90 -------------- playbooks/osc-functional-devstack/post.yaml | 80 ------------- playbooks/osc-functional-devstack/run.yaml | 83 ------------- tox.ini | 16 +-- 14 files changed, 122 insertions(+), 640 deletions(-) delete mode 100755 openstackclient/tests/functional/post_test_hook.sh delete mode 100755 openstackclient/tests/functional/post_test_hook_tips.sh create mode 100644 playbooks/osc-devstack/post.yaml create mode 100644 playbooks/osc-devstack/pre.yaml create mode 100644 playbooks/osc-devstack/run.yaml delete mode 100644 playbooks/osc-functional-devstack-n-net/post.yaml delete mode 100644 playbooks/osc-functional-devstack-n-net/run.yaml delete mode 100644 playbooks/osc-functional-devstack-tips/post.yaml delete mode 100644 playbooks/osc-functional-devstack-tips/run.yaml delete mode 100644 playbooks/osc-functional-devstack/post.yaml delete mode 100644 playbooks/osc-functional-devstack/run.yaml diff --git a/.zuul.yaml b/.zuul.yaml index 905af05b70..ff268c2140 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -1,38 +1,112 @@ -# from zuul.d/zuul-legacy-jobs.yaml legacy-osc-dsvm-functional +- job: + name: osc-functional-devstack-base + parent: devstack + description: | + Base job for devstack-based functional tests + pre-run: playbooks/osc-devstack/pre + run: playbooks/osc-devstack/run + post-run: playbooks/osc-devstack/post + required-projects: + - name: openstack/swift + roles: + - zuul: openstack-infra/devstack + timeout: 9000 + vars: + devstack_localrc: + SWIFT_HASH: "1234123412341234" + LIBS_FROM_GIT: 'python-openstackclient' + # NOTE(dtroyer): OSC needs to support Image v1 for a while yet so re-enable + GLANCE_V1_ENABLED: True + # NOTE(dtroyer): Functional tests need a bit more volume headroom + VOLUME_BACKING_FILE_SIZE: 20G + devstack_local_conf: + post-config: + "$CINDER_CONF": + DEFAULT: + # NOTE(dtroyer): OSC needs to support Volume v1 for a while yet so re-enable + enable_v1_api: True + devstack_services: + ceilometer-acentral: False + ceilometer-acompute: False + ceilometer-alarm-evaluator: False + ceilometer-alarm-notifier: False + ceilometer-anotification: False + ceilometer-api: False + ceilometer-collector: False + horizon: False + s-account: True + s-container: True + s-object: True + s-proxy: True + osc_environment: + PYTHONUNBUFFERED: 'true' + OS_CLOUD: 'devstack-admin' + tox_install_siblings: False + zuul_work_dir: src/git.openstack.org/openstack/python-openstackclient +# The Neutron bits are here rather than in osc-functional-devstack-base to +# simplify removing Neutron in the osc-functional-devstack-n-net job. - job: name: osc-functional-devstack - parent: legacy-dsvm-base - run: playbooks/osc-functional-devstack/run - post-run: playbooks/osc-functional-devstack/post + parent: osc-functional-devstack-base timeout: 7800 - required-projects: - - openstack-infra/devstack-gate - - openstack/python-openstackclient + vars: + devstack_plugins: + # NOTE(amotoki): Some neutron features are enabled by devstack plugin + neutron: https://git.openstack.org/openstack/neutron + devstack_services: + neutron-segments: True + q-metering: True + q-qos: True + tox_envlist: functional - job: name: osc-functional-devstack-n-net - parent: legacy-dsvm-base - run: playbooks/osc-functional-devstack-n-net/run - post-run: playbooks/osc-functional-devstack-n-net/post + parent: osc-functional-devstack-base timeout: 7800 - required-projects: - - openstack-infra/devstack-gate - - openstack/python-openstackclient + vars: + devstack_localrc: + FLAT_INTERFACE: 'br_flat' + PUBLIC_INTERFACE: 'br_pub' + devstack_services: + n-cell: True + n-net: True + neutron: False + neutron-segments: False + q-agt: False + q-dhcp: False + q-l3: False + q-meta: False + q-metering: False + q-qos: False + q-svc: False + tox_envlist: functional - job: name: osc-functional-devstack-tips - parent: legacy-dsvm-base - run: playbooks/osc-functional-devstack-tips/run - post-run: playbooks/osc-functional-devstack-tips/post + parent: osc-functional-devstack timeout: 7800 required-projects: - - openstack-infra/devstack-gate - openstack/os-client-config - openstack/osc-lib - openstack/python-openstackclient - openstack/python-openstacksdk - + vars: + devstack_localrc: + USE_PYTHON3: True + LIBS_FROM_GIT: 'python-openstackclient,python-openstacksdk,osc-lib,os-client-config' + # This is insufficient, but leaving it here as a reminder of what may + # someday be all we need to make this work + # disable_python3_package swift + DISABLED_PYTHON3_PACKAGES: 'swift' + devstack_services: + # Swift is not ready for python3 yet: At a minimum keystonemiddleware needs + # to be installed in the py2 env, there are probably other things too... + s-account: False + s-container: False + s-object: False + s-proxy: False + tox_envlist: functional-tips - project: name: openstack/python-openstackclient diff --git a/doc/source/contributor/plugins.rst b/doc/source/contributor/plugins.rst index e69dde8bd3..d847bf4a96 100644 --- a/doc/source/contributor/plugins.rst +++ b/doc/source/contributor/plugins.rst @@ -217,16 +217,13 @@ more steps needed to fully integrate the client with openstackclient. Add the command checker to your CI ---------------------------------- -#. Modify the section of ``zuul/layout.yaml`` related to your repository to - add ``osc-plugin-jobs`` to the list of job templates for your project. - This job checks that to see if any new commands are: duplicated, missing - entry points, or have overlap; across all openstackclient plugins. - -#. Update ``jenkins/scripts/check-osc-plugins.sh`` to include your new - library to be installed from source. This is essential in running the - previously mentioned check job. Simply add - ``install_from_source python-fooclient`` to the block of code where all - other clients are installed. +#. Add ``openstackclient-plugin-jobs`` to the list of job templates for your project. + These jobs ensures that all plugin libraries are co-installable with + ``python-openstackclient`` and checks for conflicts across all OpenStackClient + plugins, such as duplicated commands, missing entry points, or other overlaps. + +#. Add your project to the ``required-projects`` list in the ``.zuul.yaml`` file + in the ``openstack/openstackclient`` repo. Changes to python-openstackclient --------------------------------- diff --git a/openstackclient/tests/functional/post_test_hook.sh b/openstackclient/tests/functional/post_test_hook.sh deleted file mode 100755 index d990cb999f..0000000000 --- a/openstackclient/tests/functional/post_test_hook.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/bin/bash - -# This is a script that kicks off a series of functional tests against an -# OpenStack cloud. It will attempt to create an instance if one is not -# available. Do not run this script unless you know what you're doing. -# For more information refer to: -# https://docs.openstack.org/python-openstackclient/latest/ - -function generate_testr_results { - if [ -f .testrepository/0 ]; then - sudo .tox/functional/bin/testr last --subunit > $WORKSPACE/testrepository.subunit - sudo mv $WORKSPACE/testrepository.subunit $BASE/logs/testrepository.subunit - sudo .tox/functional/bin/subunit2html $BASE/logs/testrepository.subunit $BASE/logs/testr_results.html - sudo gzip -9 $BASE/logs/testrepository.subunit - sudo gzip -9 $BASE/logs/testr_results.html - sudo chown $USER:$USER $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz - sudo chmod a+r $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz - elif [ -f .stestr/0 ]; then - sudo .tox/functional/bin/stestr last --subunit > $WORKSPACE/testrepository.subunit - sudo mv $WORKSPACE/testrepository.subunit $BASE/logs/testrepository.subunit - sudo .tox/functional/bin/subunit2html $BASE/logs/testrepository.subunit $BASE/logs/testr_results.html - sudo gzip -9 $BASE/logs/testrepository.subunit - sudo gzip -9 $BASE/logs/testr_results.html - sudo chown $USER:$USER $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz - sudo chmod a+r $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz - fi -} - -export OPENSTACKCLIENT_DIR="$BASE/new/python-openstackclient" -sudo chown -R $USER:stack $OPENSTACKCLIENT_DIR - -# Go to the openstackclient dir -cd $OPENSTACKCLIENT_DIR - -# Run tests -echo "Running openstackclient functional test suite" -set +e - -# Source environment variables to kick things off -source ~stack/devstack/openrc admin admin -echo 'Running tests with:' -env | grep OS - -# Preserve env for OS_ credentials -sudo -E -H -u $USER tox -efunctional -EXIT_CODE=$? -set -e - -# Collect and parse result -generate_testr_results -exit $EXIT_CODE diff --git a/openstackclient/tests/functional/post_test_hook_tips.sh b/openstackclient/tests/functional/post_test_hook_tips.sh deleted file mode 100755 index 4cae7cdfe0..0000000000 --- a/openstackclient/tests/functional/post_test_hook_tips.sh +++ /dev/null @@ -1,55 +0,0 @@ -#!/bin/bash - -# This is a script that kicks off a series of functional tests against an -# OpenStack cloud. It will attempt to create an instance if one is not -# available. Do not run this script unless you know what you're doing. -# For more information refer to: -# https://docs.openstack.org/python-openstackclient/latest/ - -# This particular script differs from the normal post_test_hook because -# it installs the master (tip) version of osc-lib, os-client-config -# and openstacksdk, OSCs most important dependencies. - -function generate_testr_results { - if [ -f .testrepository/0 ]; then - sudo .tox/functional-tips/bin/testr last --subunit > $WORKSPACE/testrepository.subunit - sudo mv $WORKSPACE/testrepository.subunit $BASE/logs/testrepository.subunit - sudo .tox/functional-tips/bin/subunit2html $BASE/logs/testrepository.subunit $BASE/logs/testr_results.html - sudo gzip -9 $BASE/logs/testrepository.subunit - sudo gzip -9 $BASE/logs/testr_results.html - sudo chown $USER:$USER $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz - sudo chmod a+r $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz - elif [ -f .stestr/0 ]; then - sudo .tox/functional-tips/bin/stestr last --subunit > $WORKSPACE/testrepository.subunit - sudo mv $WORKSPACE/testrepository.subunit $BASE/logs/testrepository.subunit - sudo .tox/functional-tips/bin/subunit2html $BASE/logs/testrepository.subunit $BASE/logs/testr_results.html - sudo gzip -9 $BASE/logs/testrepository.subunit - sudo gzip -9 $BASE/logs/testr_results.html - sudo chown $USER:$USER $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz - sudo chmod a+r $BASE/logs/testrepository.subunit.gz $BASE/logs/testr_results.html.gz - fi -} - -export OPENSTACKCLIENT_DIR="$BASE/new/python-openstackclient" -sudo chown -R $USER:stack $OPENSTACKCLIENT_DIR - -# Go to the openstackclient dir -cd $OPENSTACKCLIENT_DIR - -# Run tests -echo "Running openstackclient functional-tips test suite" -set +e - -# Source environment variables to kick things off -source ~stack/devstack/openrc admin admin -echo 'Running tests with:' -env | grep OS - -# Preserve env for OS_ credentials -sudo -E -H -u $USER tox -e functional-tips -EXIT_CODE=$? -set -e - -# Collect and parse result -generate_testr_results -exit $EXIT_CODE diff --git a/playbooks/osc-devstack/post.yaml b/playbooks/osc-devstack/post.yaml new file mode 100644 index 0000000000..db7ca7d67f --- /dev/null +++ b/playbooks/osc-devstack/post.yaml @@ -0,0 +1,4 @@ +- hosts: all + roles: + - fetch-tox-output + - fetch-stestr-output diff --git a/playbooks/osc-devstack/pre.yaml b/playbooks/osc-devstack/pre.yaml new file mode 100644 index 0000000000..3ec41c9cbd --- /dev/null +++ b/playbooks/osc-devstack/pre.yaml @@ -0,0 +1,8 @@ +- hosts: all + roles: + - run-devstack + - role: bindep + bindep_profile: test + bindep_dir: "{{ zuul_work_dir }}" + - test-setup + - ensure-tox diff --git a/playbooks/osc-devstack/run.yaml b/playbooks/osc-devstack/run.yaml new file mode 100644 index 0000000000..22f82096c7 --- /dev/null +++ b/playbooks/osc-devstack/run.yaml @@ -0,0 +1,3 @@ +- hosts: all + roles: + - tox diff --git a/playbooks/osc-functional-devstack-n-net/post.yaml b/playbooks/osc-functional-devstack-n-net/post.yaml deleted file mode 100644 index dac875340a..0000000000 --- a/playbooks/osc-functional-devstack-n-net/post.yaml +++ /dev/null @@ -1,80 +0,0 @@ -- hosts: primary - tasks: - - - name: Copy files from {{ ansible_user_dir }}/workspace/ on node - synchronize: - src: '{{ ansible_user_dir }}/workspace/' - dest: '{{ zuul.executor.log_root }}' - mode: pull - copy_links: true - verify_host: true - rsync_opts: - - --include=**/*nose_results.html - - --include=*/ - - --exclude=* - - --prune-empty-dirs - - - name: Copy files from {{ ansible_user_dir }}/workspace/ on node - synchronize: - src: '{{ ansible_user_dir }}/workspace/' - dest: '{{ zuul.executor.log_root }}' - mode: pull - copy_links: true - verify_host: true - rsync_opts: - - --include=**/*testr_results.html.gz - - --include=*/ - - --exclude=* - - --prune-empty-dirs - - - name: Copy files from {{ ansible_user_dir }}/workspace/ on node - synchronize: - src: '{{ ansible_user_dir }}/workspace/' - dest: '{{ zuul.executor.log_root }}' - mode: pull - copy_links: true - verify_host: true - rsync_opts: - - --include=/.testrepository/tmp* - - --include=*/ - - --exclude=* - - --prune-empty-dirs - - - name: Copy files from {{ ansible_user_dir }}/workspace/ on node - synchronize: - src: '{{ ansible_user_dir }}/workspace/' - dest: '{{ zuul.executor.log_root }}' - mode: pull - copy_links: true - verify_host: true - rsync_opts: - - --include=**/*testrepository.subunit.gz - - --include=*/ - - --exclude=* - - --prune-empty-dirs - - - name: Copy files from {{ ansible_user_dir }}/workspace/ on node - synchronize: - src: '{{ ansible_user_dir }}/workspace/' - dest: '{{ zuul.executor.log_root }}/tox' - mode: pull - copy_links: true - verify_host: true - rsync_opts: - - --include=/.tox/*/log/* - - --include=*/ - - --exclude=* - - --prune-empty-dirs - - - name: Copy files from {{ ansible_user_dir }}/workspace/ on node - synchronize: - src: '{{ ansible_user_dir }}/workspace/' - dest: '{{ zuul.executor.log_root }}' - mode: pull - copy_links: true - verify_host: true - rsync_opts: - - --include=/logs/** - - --include=*/ - - --exclude=* - - --prune-empty-dirs diff --git a/playbooks/osc-functional-devstack-n-net/run.yaml b/playbooks/osc-functional-devstack-n-net/run.yaml deleted file mode 100644 index cb06ff813b..0000000000 --- a/playbooks/osc-functional-devstack-n-net/run.yaml +++ /dev/null @@ -1,85 +0,0 @@ -- hosts: all - name: Autoconverted job legacy-osc-dsvm-functional-n-net from old job gate-osc-dsvm-functional-n-net-ubuntu-xenial-nv - tasks: - - - name: Ensure legacy workspace directory - file: - path: '{{ ansible_user_dir }}/workspace' - state: directory - - - shell: - cmd: | - set -e - set -x - cat > clonemap.yaml << EOF - clonemap: - - name: openstack-infra/devstack-gate - dest: devstack-gate - EOF - /usr/zuul-env/bin/zuul-cloner -m clonemap.yaml --cache-dir /opt/git \ - git://git.openstack.org \ - openstack-infra/devstack-gate - executable: /bin/bash - chdir: '{{ ansible_user_dir }}/workspace' - environment: '{{ zuul | zuul_legacy_vars }}' - - - shell: - cmd: | - set -e - set -x - cat << 'EOF' >>"/tmp/dg-local.conf" - [[local|localrc]] - # NOTE(RuiChen): nova-network only can be enable in nova cell v1 - enable_service n-net n-cell - disable_service neutron q-svc q-agt q-dhcp q-l3 q-meta q-metering - # NOTE(dtroyer): OSC needs to support Image v1 for a while yet so re-enable - GLANCE_V1_ENABLED=True - # NOTE(dtroyer): Functional tests need a bit more volume headroom - VOLUME_BACKING_FILE_SIZE=20G - # NOTE(dtroyer): OSC needs to support Volume v1 for a while yet so re-enable - [[post-config|$CINDER_CONF]] - [DEFAULT] - enable_v1_api = True - - EOF - executable: /bin/bash - chdir: '{{ ansible_user_dir }}/workspace' - environment: '{{ zuul | zuul_legacy_vars }}' - - - shell: - cmd: | - set -e - set -x - export PYTHONUNBUFFERED=true - export DEVSTACK_GATE_UNSTACK=0 - export DEVSTACK_GATE_TEMPEST=0 - export DEVSTACK_GATE_EXERCISES=0 - export DEVSTACK_GATE_INSTALL_TESTONLY=1 - # NOTE(RuiChen): Explicitly tell devstack-gate that we need to run - # the nova-network job with cell v1. - export DEVSTACK_GATE_NEUTRON=0 - export DEVSTACK_GATE_CELLS=1 - export BRANCH_OVERRIDE=default - export DEVSTACK_PROJECT_FROM_GIT=python-openstackclient - if [ "$BRANCH_OVERRIDE" != "default" ] ; then - export OVERRIDE_ZUUL_BRANCH=$BRANCH_OVERRIDE - fi - - function post_test_hook { - # NOTE(stevemar): After the newton release was tagged the file was moved. - # But, we run functional tests for various stable releases - # (mitaka, and newton). - # TODO(stevemar): Remove this check when Newton hits EOL. - hook_location=$BASE/new/python-openstackclient/openstackclient/tests/functional/post_test_hook.sh - if [ ! -f "$hook_location" ]; then - hook_location=$BASE/new/python-openstackclient/post_test_hook.sh - fi - bash -xe $hook_location - } - export -f post_test_hook - - cp devstack-gate/devstack-vm-gate-wrap.sh ./safe-devstack-vm-gate-wrap.sh - ./safe-devstack-vm-gate-wrap.sh - executable: /bin/bash - chdir: '{{ ansible_user_dir }}/workspace' - environment: '{{ zuul | zuul_legacy_vars }}' diff --git a/playbooks/osc-functional-devstack-tips/post.yaml b/playbooks/osc-functional-devstack-tips/post.yaml deleted file mode 100644 index dac875340a..0000000000 --- a/playbooks/osc-functional-devstack-tips/post.yaml +++ /dev/null @@ -1,80 +0,0 @@ -- hosts: primary - tasks: - - - name: Copy files from {{ ansible_user_dir }}/workspace/ on node - synchronize: - src: '{{ ansible_user_dir }}/workspace/' - dest: '{{ zuul.executor.log_root }}' - mode: pull - copy_links: true - verify_host: true - rsync_opts: - - --include=**/*nose_results.html - - --include=*/ - - --exclude=* - - --prune-empty-dirs - - - name: Copy files from {{ ansible_user_dir }}/workspace/ on node - synchronize: - src: '{{ ansible_user_dir }}/workspace/' - dest: '{{ zuul.executor.log_root }}' - mode: pull - copy_links: true - verify_host: true - rsync_opts: - - --include=**/*testr_results.html.gz - - --include=*/ - - --exclude=* - - --prune-empty-dirs - - - name: Copy files from {{ ansible_user_dir }}/workspace/ on node - synchronize: - src: '{{ ansible_user_dir }}/workspace/' - dest: '{{ zuul.executor.log_root }}' - mode: pull - copy_links: true - verify_host: true - rsync_opts: - - --include=/.testrepository/tmp* - - --include=*/ - - --exclude=* - - --prune-empty-dirs - - - name: Copy files from {{ ansible_user_dir }}/workspace/ on node - synchronize: - src: '{{ ansible_user_dir }}/workspace/' - dest: '{{ zuul.executor.log_root }}' - mode: pull - copy_links: true - verify_host: true - rsync_opts: - - --include=**/*testrepository.subunit.gz - - --include=*/ - - --exclude=* - - --prune-empty-dirs - - - name: Copy files from {{ ansible_user_dir }}/workspace/ on node - synchronize: - src: '{{ ansible_user_dir }}/workspace/' - dest: '{{ zuul.executor.log_root }}/tox' - mode: pull - copy_links: true - verify_host: true - rsync_opts: - - --include=/.tox/*/log/* - - --include=*/ - - --exclude=* - - --prune-empty-dirs - - - name: Copy files from {{ ansible_user_dir }}/workspace/ on node - synchronize: - src: '{{ ansible_user_dir }}/workspace/' - dest: '{{ zuul.executor.log_root }}' - mode: pull - copy_links: true - verify_host: true - rsync_opts: - - --include=/logs/** - - --include=*/ - - --exclude=* - - --prune-empty-dirs diff --git a/playbooks/osc-functional-devstack-tips/run.yaml b/playbooks/osc-functional-devstack-tips/run.yaml deleted file mode 100644 index 7bd537389d..0000000000 --- a/playbooks/osc-functional-devstack-tips/run.yaml +++ /dev/null @@ -1,90 +0,0 @@ -- hosts: all - name: Autoconverted job legacy-osc-dsvm-functional-tips from old job gate-osc-dsvm-functional-tips-ubuntu-xenial-nv - tasks: - - - name: Ensure legacy workspace directory - file: - path: '{{ ansible_user_dir }}/workspace' - state: directory - - - shell: - cmd: | - set -e - set -x - cat > clonemap.yaml << EOF - clonemap: - - name: openstack-infra/devstack-gate - dest: devstack-gate - EOF - /usr/zuul-env/bin/zuul-cloner -m clonemap.yaml --cache-dir /opt/git \ - git://git.openstack.org \ - openstack-infra/devstack-gate - executable: /bin/bash - chdir: '{{ ansible_user_dir }}/workspace' - environment: '{{ zuul | zuul_legacy_vars }}' - - - shell: - cmd: | - set -e - set -x - cat << 'EOF' >>"/tmp/dg-local.conf" - [[local|localrc]] - # NOTE(amotoki): Some neutron features are enabled by devstack plugin - enable_plugin neutron https://git.openstack.org/openstack/neutron - enable_service q-qos - enable_service neutron-segments - # NOTE(dtroyer): OSC needs to support Image v1 for a while yet so re-enable - GLANCE_V1_ENABLED=True - # NOTE(dtroyer): Functional tests need a bit more volume headroom - VOLUME_BACKING_FILE_SIZE=20G - # Swift is not ready for python3 yet: At a minimum keystonemiddleware needs - # to be installed in the py2 env, there are probably other things too... - disable_service s-account - disable_service s-container - disable_service s-object - disable_service s-proxy - # This is insufficient, but leaving it here as a reminder of what may - # someday be all we need to make this work - disable_python3_package swift - # NOTE(dtroyer): OSC needs to support Volume v1 for a while yet so re-enable - [[post-config|$CINDER_CONF]] - [DEFAULT] - enable_v1_api = True - - EOF - executable: /bin/bash - chdir: '{{ ansible_user_dir }}/workspace' - environment: '{{ zuul | zuul_legacy_vars }}' - - - shell: - cmd: | - set -e - set -x - export PYTHONUNBUFFERED=true - export DEVSTACK_GATE_USE_PYTHON3=True - export DEVSTACK_GATE_UNSTACK=0 - export DEVSTACK_GATE_TEMPEST=0 - export DEVSTACK_GATE_EXERCISES=0 - export DEVSTACK_GATE_INSTALL_TESTONLY=1 - export DEVSTACK_GATE_NEUTRON=1 - export BRANCH_OVERRIDE=default - export DEVSTACK_PROJECT_FROM_GIT="python-openstackclient,python-openstacksdk,osc-lib,os-client-config" - if [ "$BRANCH_OVERRIDE" != "default" ] ; then - export OVERRIDE_ZUUL_BRANCH=$BRANCH_OVERRIDE - fi - - function post_test_hook { - # This test hook will install the master version of the following: - # - osc-lib - # - openstacksdk - # - os-client-config - hook_location=$BASE/new/python-openstackclient/openstackclient/tests/functional/post_test_hook_tips.sh - bash -xe $hook_location - } - export -f post_test_hook - - cp devstack-gate/devstack-vm-gate-wrap.sh ./safe-devstack-vm-gate-wrap.sh - ./safe-devstack-vm-gate-wrap.sh - executable: /bin/bash - chdir: '{{ ansible_user_dir }}/workspace' - environment: '{{ zuul | zuul_legacy_vars }}' diff --git a/playbooks/osc-functional-devstack/post.yaml b/playbooks/osc-functional-devstack/post.yaml deleted file mode 100644 index dac875340a..0000000000 --- a/playbooks/osc-functional-devstack/post.yaml +++ /dev/null @@ -1,80 +0,0 @@ -- hosts: primary - tasks: - - - name: Copy files from {{ ansible_user_dir }}/workspace/ on node - synchronize: - src: '{{ ansible_user_dir }}/workspace/' - dest: '{{ zuul.executor.log_root }}' - mode: pull - copy_links: true - verify_host: true - rsync_opts: - - --include=**/*nose_results.html - - --include=*/ - - --exclude=* - - --prune-empty-dirs - - - name: Copy files from {{ ansible_user_dir }}/workspace/ on node - synchronize: - src: '{{ ansible_user_dir }}/workspace/' - dest: '{{ zuul.executor.log_root }}' - mode: pull - copy_links: true - verify_host: true - rsync_opts: - - --include=**/*testr_results.html.gz - - --include=*/ - - --exclude=* - - --prune-empty-dirs - - - name: Copy files from {{ ansible_user_dir }}/workspace/ on node - synchronize: - src: '{{ ansible_user_dir }}/workspace/' - dest: '{{ zuul.executor.log_root }}' - mode: pull - copy_links: true - verify_host: true - rsync_opts: - - --include=/.testrepository/tmp* - - --include=*/ - - --exclude=* - - --prune-empty-dirs - - - name: Copy files from {{ ansible_user_dir }}/workspace/ on node - synchronize: - src: '{{ ansible_user_dir }}/workspace/' - dest: '{{ zuul.executor.log_root }}' - mode: pull - copy_links: true - verify_host: true - rsync_opts: - - --include=**/*testrepository.subunit.gz - - --include=*/ - - --exclude=* - - --prune-empty-dirs - - - name: Copy files from {{ ansible_user_dir }}/workspace/ on node - synchronize: - src: '{{ ansible_user_dir }}/workspace/' - dest: '{{ zuul.executor.log_root }}/tox' - mode: pull - copy_links: true - verify_host: true - rsync_opts: - - --include=/.tox/*/log/* - - --include=*/ - - --exclude=* - - --prune-empty-dirs - - - name: Copy files from {{ ansible_user_dir }}/workspace/ on node - synchronize: - src: '{{ ansible_user_dir }}/workspace/' - dest: '{{ zuul.executor.log_root }}' - mode: pull - copy_links: true - verify_host: true - rsync_opts: - - --include=/logs/** - - --include=*/ - - --exclude=* - - --prune-empty-dirs diff --git a/playbooks/osc-functional-devstack/run.yaml b/playbooks/osc-functional-devstack/run.yaml deleted file mode 100644 index 185ba437f3..0000000000 --- a/playbooks/osc-functional-devstack/run.yaml +++ /dev/null @@ -1,83 +0,0 @@ -- hosts: all - name: Autoconverted job legacy-osc-dsvm-functional from old job gate-osc-dsvm-functional-ubuntu-xenial - tasks: - - - name: Ensure legacy workspace directory - file: - path: '{{ ansible_user_dir }}/workspace' - state: directory - - - shell: - cmd: | - set -e - set -x - cat > clonemap.yaml << EOF - clonemap: - - name: openstack-infra/devstack-gate - dest: devstack-gate - EOF - /usr/zuul-env/bin/zuul-cloner -m clonemap.yaml --cache-dir /opt/git \ - git://git.openstack.org \ - openstack-infra/devstack-gate - executable: /bin/bash - chdir: '{{ ansible_user_dir }}/workspace' - environment: '{{ zuul | zuul_legacy_vars }}' - - - shell: - cmd: | - set -e - set -x - cat << 'EOF' >>"/tmp/dg-local.conf" - [[local|localrc]] - # NOTE(amotoki): Some neutron features are enabled by devstack plugin - enable_plugin neutron https://git.openstack.org/openstack/neutron - enable_service q-qos - enable_service neutron-segments - # NOTE(dtroyer): OSC needs to support Image v1 for a while yet so re-enable - GLANCE_V1_ENABLED=True - # NOTE(dtroyer): Functional tests need a bit more volume headroom - VOLUME_BACKING_FILE_SIZE=20G - # NOTE(dtroyer): OSC needs to support Volume v1 for a while yet so re-enable - [[post-config|$CINDER_CONF]] - [DEFAULT] - enable_v1_api = True - - EOF - executable: /bin/bash - chdir: '{{ ansible_user_dir }}/workspace' - environment: '{{ zuul | zuul_legacy_vars }}' - - - shell: - cmd: | - set -e - set -x - export PYTHONUNBUFFERED=true - export DEVSTACK_GATE_UNSTACK=0 - export DEVSTACK_GATE_TEMPEST=0 - export DEVSTACK_GATE_EXERCISES=0 - export DEVSTACK_GATE_INSTALL_TESTONLY=1 - export DEVSTACK_GATE_NEUTRON=1 - export BRANCH_OVERRIDE=default - export DEVSTACK_PROJECT_FROM_GIT=python-openstackclient - if [ "$BRANCH_OVERRIDE" != "default" ] ; then - export OVERRIDE_ZUUL_BRANCH=$BRANCH_OVERRIDE - fi - - function post_test_hook { - # NOTE(stevemar): After the newton release was tagged the file was moved. - # But, we run functional tests for various stable releases - # (mitaka, and newton). - # TODO(stevemar): Remove this check when Newton hits EOL. - hook_location=$BASE/new/python-openstackclient/openstackclient/tests/functional/post_test_hook.sh - if [ ! -f "$hook_location" ]; then - hook_location=$BASE/new/python-openstackclient/post_test_hook.sh - fi - bash -xe $hook_location - } - export -f post_test_hook - - cp devstack-gate/devstack-vm-gate-wrap.sh ./safe-devstack-vm-gate-wrap.sh - ./safe-devstack-vm-gate-wrap.sh - executable: /bin/bash - chdir: '{{ ansible_user_dir }}/workspace' - environment: '{{ zuul | zuul_legacy_vars }}' diff --git a/tox.ini b/tox.ini index a16f0eb849..ca19862ed0 100644 --- a/tox.ini +++ b/tox.ini @@ -56,20 +56,20 @@ commands = [testenv:functional] setenv = OS_TEST_PATH=./openstackclient/tests/functional passenv = OS_* -# Enable this when running Zuul v3 jobs -#whitelist_externals = openstackclient/tests/functional/run_ostestr.sh -#commands = -# {toxinidir}/openstackclient/tests/functional/run_ostestr.sh {posargs} +whitelist_externals = openstackclient/tests/functional/run_ostestr.sh +commands = + {toxinidir}/openstackclient/tests/functional/run_ostestr.sh {posargs} [testenv:functional-tips] setenv = OS_TEST_PATH=./openstackclient/tests/functional passenv = OS_* +whitelist_externals = openstackclient/tests/functional/run_ostestr.sh commands = - pip install -q -U -e "git+file:///opt/stack/new/osc-lib#egg=osc_lib" - pip install -q -U -e "git+file:///opt/stack/new/python-openstacksdk#egg=openstacksdk" - pip install -q -U -e "git+file:///opt/stack/new/os-client-config#egg=os_client_config" + pip install -q -U -e "git+file://{toxinidir}/../osc-lib#egg=osc_lib" + pip install -q -U -e "git+file://{toxinidir}/../os-client-config#egg=os_client_config" + pip install -q -U -e "git+file://{toxinidir}/../python-openstacksdk#egg=openstacksdk" pip freeze - ostestr {posargs} + {toxinidir}/openstackclient/tests/functional/run_ostestr.sh {posargs} [testenv:venv] commands = {posargs} From 21212cabd52c85fdd572fc0c230868106a7f8eda Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 20 Apr 2017 11:20:59 +0100 Subject: [PATCH 1811/3095] Convert 'server' commands to use autoprogram Convert the remaining server commands. Change-Id: Ief84abb899be9fd60ce4d546abefc226e0ae4f81 --- .../cli/command-objects/server-backup.rst | 39 +--------- .../cli/command-objects/server-event.rst | 43 ++--------- .../cli/command-objects/server-group.rst | 76 +------------------ .../cli/command-objects/server-image.rst | 26 +------ 4 files changed, 13 insertions(+), 171 deletions(-) diff --git a/doc/source/cli/command-objects/server-backup.rst b/doc/source/cli/command-objects/server-backup.rst index 9ddb2cdfab..f2a2e2c958 100644 --- a/doc/source/cli/command-objects/server-backup.rst +++ b/doc/source/cli/command-objects/server-backup.rst @@ -3,42 +3,9 @@ server backup ============= A server backup is a disk image created in the Image store from a running server -instance. The backup command manages the number of archival copies to retain. +instance. The backup command manages the number of archival copies to retain. Compute v2 -server backup create --------------------- - -Create a server backup image - -.. program:: server create -.. code:: bash - - openstack server backup create - [--name ] - [--type ] - [--rotate ] - [--wait] - - -.. option:: --name - - Name of the backup image (default: server name) - -.. option:: --type - - Used to populate the ``backup_type`` property of the backup - image (default: empty) - -.. option:: --rotate - - Number of backup images to keep (default: 1) - -.. option:: --wait - - Wait for operation to complete - -.. describe:: - - Server to back up (name or ID) +.. autoprogram-cliff:: openstack.compute.v2 + :command: server backup create diff --git a/doc/source/cli/command-objects/server-event.rst b/doc/source/cli/command-objects/server-event.rst index ef4685f8d6..372cb40073 100644 --- a/doc/source/cli/command-objects/server-event.rst +++ b/doc/source/cli/command-objects/server-event.rst @@ -2,44 +2,11 @@ server event ============ -Server event is the event record that had been done on a server, include: event -type(create, delete, reboot and so on), event result(success, error), start -time, finish time and so on. These are important information for server -maintains. +Server event are event record for server operations. They consist of: type +(create, delete, reboot and so on), result (success, error), start time, finish +time and so on. These are important for server maintenance. Compute v2 -server event list ------------------ - -List recent events of a server - -.. program:: server event list -.. code:: bash - - openstack server event list - - -.. describe:: - - Server to list events (name or ID) - -server event show ------------------ - -Show server event details - -.. program:: server event show -.. code:: bash - - openstack server event show - - - -.. describe:: - - Server to show event details (name or ID) - -.. describe:: - - Request ID of the event to show (ID only) +.. autoprogram-cliff:: openstack.compute.v2 + :command: server event * diff --git a/doc/source/cli/command-objects/server-group.rst b/doc/source/cli/command-objects/server-group.rst index 9769635073..741eb4adaf 100644 --- a/doc/source/cli/command-objects/server-group.rst +++ b/doc/source/cli/command-objects/server-group.rst @@ -2,79 +2,9 @@ server group ============ -Server group provides a mechanism to group servers according to certain policy. +Server groups provide a mechanism to group servers according to certain policy. Compute v2 -server group create -------------------- - -Create a new server group - -.. program:: server group create -.. code-block:: bash - - openstack server group create - --policy - - -.. option:: --policy - - Add a policy to :ref:`\ ` - ('affinity' or 'anti-affinity', default to 'affinity') - -.. _server_group_create-name: -.. describe:: - - New server group name - -server group delete -------------------- - -Delete existing server group(s) - -.. program:: server group delete -.. code-block:: bash - - openstack server group delete - [ ...] - -.. describe:: - - Server group(s) to delete (name or ID) - (repeat to delete multiple server groups) - -server group list ------------------ - -List all server groups - -.. program:: server group list -.. code-block:: bash - - openstack server group list - [--all-projects] - [--long] - -.. option:: --all-projects - - Display information from all projects (admin only) - -.. option:: --long - - List additional fields in output - -server group show ------------------ - -Display server group details - -.. program:: server group show -.. code-block:: bash - - openstack server group show - - -.. describe:: - - Server group to display (name or ID) +.. autoprogram-cliff:: openstack.compute.v2 + :command: server group * diff --git a/doc/source/cli/command-objects/server-image.rst b/doc/source/cli/command-objects/server-image.rst index 7e625d9e42..c013ca49bb 100644 --- a/doc/source/cli/command-objects/server-image.rst +++ b/doc/source/cli/command-objects/server-image.rst @@ -7,27 +7,5 @@ image is created in the Image store. Compute v2 -server image create -------------------- - -Create a new server disk image from an existing server - -.. program:: server image create -.. code:: bash - - openstack server image create - [--name ] - [--wait] - - -.. option:: --name - - Name of new disk image (default: server name) - -.. option:: --wait - - Wait for operation to complete - -.. describe:: - - Server to create image (name or ID) +.. autoprogram-cliff:: openstack.compute.v2 + :command: server image create From e3ad82164dc5c51b5f3cb75b826bc15a6778b8b0 Mon Sep 17 00:00:00 2001 From: Hongbin Lu Date: Tue, 3 Oct 2017 15:35:56 +0000 Subject: [PATCH 1812/3095] Added AddNetwork command to server Currently, if users want to add another NIC to a running instance, they need to (i) create a neutron port and (ii) add the port to the server via teh AddPort command. It would be more convenient to have a single command to achieve the equivalent. Novaclient already support adding network to an instance via the interface-attach command. This patch introduces a similar capability in OSC. Change-Id: Ia3e39c57ae7ecb96aae1b66adc52c289daccb6ec --- openstackclient/compute/v2/server.py | 33 +++++++++++++ .../tests/unit/compute/v2/test_server.py | 47 +++++++++++++++++++ ...d-server-add-network-98ede8ff6079eb23.yaml | 6 +++ setup.cfg | 1 + 4 files changed, 87 insertions(+) create mode 100644 releasenotes/notes/add-server-add-network-98ede8ff6079eb23.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 151c6783c7..a4f847f5cd 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -300,6 +300,39 @@ def take_action(self, parsed_args): server.interface_attach(port_id=port_id, net_id=None, fixed_ip=None) +class AddNetwork(command.Command): + _description = _("Add network to server") + + def get_parser(self, prog_name): + parser = super(AddNetwork, self).get_parser(prog_name) + parser.add_argument( + "server", + metavar="", + help=_("Server to add the network to (name or ID)"), + ) + parser.add_argument( + "network", + metavar="", + help=_("Network to add to the server (name or ID)"), + ) + return parser + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + + server = utils.find_resource( + compute_client.servers, parsed_args.server) + + if self.app.client_manager.is_network_endpoint_enabled(): + network_client = self.app.client_manager.network + net_id = network_client.find_network( + parsed_args.network, ignore_missing=False).id + else: + net_id = parsed_args.network + + server.interface_attach(port_id=None, net_id=net_id, fixed_ip=None) + + class AddServerSecurityGroup(command.Command): _description = _("Add security group to server") diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index a1225c3004..1728d7a179 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -232,6 +232,53 @@ def test_server_add_port_no_neutron(self): self.find_port.assert_not_called() +class TestServerAddNetwork(TestServer): + + def setUp(self): + super(TestServerAddNetwork, self).setUp() + + # Get the command object to test + self.cmd = server.AddNetwork(self.app, None) + + # Set add_fixed_ip method to be tested. + self.methods = { + 'interface_attach': None, + } + + self.find_network = mock.Mock() + self.app.client_manager.network.find_network = self.find_network + + def _test_server_add_network(self, net_id): + servers = self.setup_servers_mock(count=1) + network = 'fake-network' + + arglist = [ + servers[0].id, + network, + ] + verifylist = [ + ('server', servers[0].id), + ('network', network) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + servers[0].interface_attach.assert_called_once_with( + port_id=None, net_id=net_id, fixed_ip=None) + self.assertIsNone(result) + + def test_server_add_network(self): + self._test_server_add_network(self.find_network.return_value.id) + self.find_network.assert_called_once_with( + 'fake-network', ignore_missing=False) + + def test_server_add_network_no_neutron(self): + self.app.client_manager.network_endpoint_enabled = False + self._test_server_add_network('fake-network') + self.find_network.assert_not_called() + + @mock.patch( 'openstackclient.api.compute_v2.APIv2.security_group_find' ) diff --git a/releasenotes/notes/add-server-add-network-98ede8ff6079eb23.yaml b/releasenotes/notes/add-server-add-network-98ede8ff6079eb23.yaml new file mode 100644 index 0000000000..cf6dada673 --- /dev/null +++ b/releasenotes/notes/add-server-add-network-98ede8ff6079eb23.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``server add network`` command. This command will create a neutron + port from the specified neutron network and attach the port to the + specified instance. diff --git a/setup.cfg b/setup.cfg index 2412b6fe02..f2a4436bbf 100644 --- a/setup.cfg +++ b/setup.cfg @@ -104,6 +104,7 @@ openstack.compute.v2 = server_add_fixed_ip = openstackclient.compute.v2.server:AddFixedIP server_add_floating_ip = openstackclient.compute.v2.server:AddFloatingIP server_add_port = openstackclient.compute.v2.server:AddPort + server_add_network = openstackclient.compute.v2.server:AddNetwork server_add_security_group = openstackclient.compute.v2.server:AddServerSecurityGroup server_add_volume = openstackclient.compute.v2.server:AddServerVolume server_create = openstackclient.compute.v2.server:CreateServer From fd23ebfbf3080b96f7ef8ba516b64e67a111971d Mon Sep 17 00:00:00 2001 From: Jose Castro Leon Date: Thu, 19 Oct 2017 15:59:45 +0200 Subject: [PATCH 1813/3095] Add missing parameters on openstack server rescue Change-Id: I27afca9e826378dbcb7feb7528e0c65c528b04b0 Closes-Bug: #1703278 --- openstackclient/compute/v2/server.py | 29 +++++++++++++++---- .../functional/compute/v2/test_server.py | 13 ++++++++- .../notes/bug-1703278-5e45a92e43552dec.yaml | 5 ++++ 3 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 releasenotes/notes/bug-1703278-5e45a92e43552dec.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 151c6783c7..d4edf8c535 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1576,7 +1576,7 @@ def take_action(self, parsed_args): ) -class RescueServer(command.ShowOne): +class RescueServer(command.Command): _description = _("Put server in rescue mode") def get_parser(self, prog_name): @@ -1586,16 +1586,35 @@ def get_parser(self, prog_name): metavar='', help=_('Server (name or ID)'), ) + parser.add_argument( + '--image', + metavar='', + help=_('Image (name or ID) to use for the rescue mode.' + ' Defaults to the currently used one.'), + ) + parser.add_argument( + '--password', + metavar='', + help=_("Set the password on the rescued instance"), + ) return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - _, body = utils.find_resource( + image_client = self.app.client_manager.image + + image = None + if parsed_args.image: + image = utils.find_resource( + image_client.images, + parsed_args.image, + ) + + utils.find_resource( compute_client.servers, parsed_args.server, - ).rescue() - return zip(*sorted(six.iteritems(body))) + ).rescue(image=image, + password=parsed_args.password) class ResizeServer(command.Command): diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py index b7a2599674..0b29fe5fbd 100644 --- a/openstackclient/tests/functional/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -184,7 +184,18 @@ def test_server_actions(self): # rescue raw_output = self.openstack('server rescue ' + name) - self.assertNotEqual("", raw_output) + self.assertEqual("", raw_output) + self.wait_for_status(name, "RESCUE") + + # unrescue + raw_output = self.openstack('server unrescue ' + name) + self.assertEqual("", raw_output) + self.wait_for_status(name, "ACTIVE") + + # rescue with image + raw_output = self.openstack('server rescue --image ' + + self.image_name + ' ' + name) + self.assertEqual("", raw_output) self.wait_for_status(name, "RESCUE") # unrescue diff --git a/releasenotes/notes/bug-1703278-5e45a92e43552dec.yaml b/releasenotes/notes/bug-1703278-5e45a92e43552dec.yaml new file mode 100644 index 0000000000..c1a3552123 --- /dev/null +++ b/releasenotes/notes/bug-1703278-5e45a92e43552dec.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Add ``--image`` and ``--password`` options to the ``server rescue`` command. + [Bug `1703278 `_] From 90230c3766ddf2a17d1aa9dacf74a6cfce1d81b6 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 27 Oct 2017 12:40:16 -0500 Subject: [PATCH 1814/3095] Zuul job updates * Add unit-tips job to run unit tests with the same project master branches as the functional-tips job (mostly useful for the unit.integ tests) * Add irrelevant-files to the osc-functional-devstack-base job * Comment out the functional-n-net job as it is horribly broken for now until the replacement package-installed OpenStack is ready Change-Id: I5acdcb0a2f0f0dfe488740ae0add36366cc0ee21 --- .zuul.yaml | 40 ++++++++++++++++++++++++++++++++++------ tox.ini | 17 +++++++++++++++++ 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/.zuul.yaml b/.zuul.yaml index ff268c2140..b687c6b606 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -1,3 +1,23 @@ +- job: + name: osc-tox-unit-tips + parent: openstack-tox + description: | + Run unit tests for OpenStackClient with master branch of important libs. + + Uses tox with the ``unit-tips`` environment and master branch of + the required-projects below. + irrelevant-files: + - ^.*\.rst$ + - ^doc/.*$ + - ^releasenotes/.*$ + required-projects: + - openstack/os-client-config + - openstack/osc-lib + - openstack/python-openstackclient + - openstack/python-openstacksdk + vars: + tox_envlist: unit-tips + - job: name: osc-functional-devstack-base parent: devstack @@ -11,6 +31,10 @@ roles: - zuul: openstack-infra/devstack timeout: 9000 + irrelevant-files: + - ^.*\.rst$ + - ^doc/.*$ + - ^releasenotes/.*$ vars: devstack_localrc: SWIFT_HASH: "1234123412341234" @@ -114,13 +138,17 @@ - openstackclient-plugin-jobs check: jobs: + - osc-tox-unit-tips: + # The functional-tips job only tests the latest and shouldn't be run + # on the stable branches + branches: ^(?!stable) - osc-functional-devstack - - osc-functional-devstack-n-net: - voting: false - # The job testing nova-network no longer works before Pike, and - # should be disabled until the New Way of testing against old clouds - # is ready and backported - branches: ^(?!stable/(newton|ocata)).*$ + # - osc-functional-devstack-n-net: + # voting: false + # # The job testing nova-network no longer works before Pike, and + # # should be disabled until the New Way of testing against old clouds + # # is ready and backported + # branches: ^(?!stable/(newton|ocata)).*$ - osc-functional-devstack-tips: voting: false # The functional-tips job only tests the latest and shouldn't be run diff --git a/tox.ini b/tox.ini index ca19862ed0..5a7850eb44 100644 --- a/tox.ini +++ b/tox.ini @@ -53,6 +53,23 @@ commands = commands = bandit -r openstackclient -x tests -s B105,B106,B107,B401,B404,B603,B606,B607,B110,B605,B101 +[testenv:unit-tips] +usedevelop = True +install_command = + {toxinidir}/tools/tox_install.sh {env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages} +setenv = VIRTUAL_ENV={envdir} + OS_STDOUT_CAPTURE=1 + OS_STDERR_CAPTURE=1 + OS_TEST_TIMEOUT=60 +deps = -r{toxinidir}/test-requirements.txt +commands = + pip install -q -U -e "git+file://{toxinidir}/../osc-lib#egg=osc_lib" + pip install -q -U -e "git+file://{toxinidir}/../os-client-config#egg=os_client_config" + pip install -q -U -e "git+file://{toxinidir}/../python-openstacksdk#egg=openstacksdk" + pip freeze + ostestr {posargs} +whitelist_externals = ostestr + [testenv:functional] setenv = OS_TEST_PATH=./openstackclient/tests/functional passenv = OS_* From 40976f006271903a29285b5bdbb5422527212a45 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 27 Oct 2017 09:50:50 -0500 Subject: [PATCH 1815/3095] Add server rescue unit tests Added to support the new options to server rescue command. Change-Id: Ia9dca85e05488fa8d5f57cd5e8920e94b8f65c22 --- .../tests/unit/compute/v2/test_server.py | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index a1225c3004..660f4f9790 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -2170,6 +2170,91 @@ def test_server_remove_fixed_ip(self): self.assertIsNone(result) +class TestServerRescue(TestServer): + + def setUp(self): + super(TestServerRescue, self).setUp() + + # Return value for utils.find_resource for image + self.image = image_fakes.FakeImage.create_one_image() + self.images_mock.get.return_value = self.image + + new_server = compute_fakes.FakeServer.create_one_server() + attrs = { + 'id': new_server.id, + 'image': { + 'id': self.image.id, + }, + 'networks': {}, + 'adminPass': 'passw0rd', + } + methods = { + 'rescue': new_server, + } + self.server = compute_fakes.FakeServer.create_one_server( + attrs=attrs, + methods=methods, + ) + + # Return value for utils.find_resource for server + self.servers_mock.get.return_value = self.server + + self.cmd = server.RescueServer(self.app, None) + + def test_rescue_with_current_image(self): + arglist = [ + self.server.id, + ] + verifylist = [ + ('server', self.server.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Get the command object to test + self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.server.rescue.assert_called_with(image=None, password=None) + + def test_rescue_with_new_image(self): + new_image = image_fakes.FakeImage.create_one_image() + self.images_mock.get.return_value = new_image + arglist = [ + '--image', new_image.id, + self.server.id, + ] + verifylist = [ + ('image', new_image.id), + ('server', self.server.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Get the command object to test + self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.images_mock.get.assert_called_with(new_image.id) + self.server.rescue.assert_called_with(image=new_image, password=None) + + def test_rescue_with_current_image_and_password(self): + password = 'password-xxx' + arglist = [ + '--password', password, + self.server.id, + ] + verifylist = [ + ('password', password), + ('server', self.server.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Get the command object to test + self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.server.rescue.assert_called_with(image=None, password=password) + + class TestServerRemoveFloatingIP(TestServer): def setUp(self): From 5b034ef65325aaf1d783bb6dfd36158e181d8448 Mon Sep 17 00:00:00 2001 From: Lin Yang Date: Thu, 12 Oct 2017 18:11:22 -0700 Subject: [PATCH 1816/3095] Add python-rsdclient into plugin list Intel RSD is new architecture that disaggregates compute, storage, and network resources, and provide the ability to dynamically compose resources based on workload-specific demands [1]. The python-rsdclient project provide specific RSD plugin to allow user to invoke RSD API through OpenStackClient. So Added it into existing plugin list. [1] https://www.intel.com/content/www/us/en/architecture-and-technology/rack-scale-design-overview.html Change-Id: Ic49efddfb003c89ece6d782905b27fb46402b3ab --- doc/source/cli/commands.rst | 1 + doc/source/cli/plugin-commands.rst | 6 ++++++ doc/source/contributor/plugins.rst | 1 + test-requirements.txt | 1 + 4 files changed, 9 insertions(+) diff --git a/doc/source/cli/commands.rst b/doc/source/cli/commands.rst index f70eabc378..0c1992ad23 100644 --- a/doc/source/cli/commands.rst +++ b/doc/source/cli/commands.rst @@ -228,6 +228,7 @@ conflicts when creating new plugins. For a complete list check out * ``ptr record``: (**DNS (Designate)**) * ``queue``: (**Messaging (Zaqar)**) * ``recordset``: (**DNS (Designate)**) +* ``rsd``: (**Disaggregated Hardware Resource Management (RSD)**) * ``search`` (**Search (Searchlight)**) * ``search facet`` (**Search (Searchlight)**) * ``search resource type`` (**Search (Searchlight)**) diff --git a/doc/source/cli/plugin-commands.rst b/doc/source/cli/plugin-commands.rst index 51c67db613..6942cd09d7 100644 --- a/doc/source/cli/plugin-commands.rst +++ b/doc/source/cli/plugin-commands.rst @@ -87,6 +87,12 @@ octavia .. list-plugins:: openstack.load_balancer.v2 :detailed: +rsd +--- + +.. list-plugins:: openstack.rsd.v1 + :detailed: + sahara ------ diff --git a/doc/source/contributor/plugins.rst b/doc/source/contributor/plugins.rst index d847bf4a96..51aec9aea4 100644 --- a/doc/source/contributor/plugins.rst +++ b/doc/source/contributor/plugins.rst @@ -36,6 +36,7 @@ The following is a list of projects that are an OpenStackClient plugin. - python-muranoclient - python-neutronclient\*\*\* - python-octaviaclient +- python-rsdclient - python-saharaclient - python-searchlightclient - python-senlinclient diff --git a/test-requirements.txt b/test-requirements.txt index f8425ba22e..b0b4585913 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -37,6 +37,7 @@ python-mistralclient>=3.1.0 # Apache-2.0 python-muranoclient>=0.8.2 # Apache-2.0 python-neutronclient>=6.3.0 # Apache-2.0 python-octaviaclient>=1.0.0 # Apache-2.0 +python-rsdclient>=0.1.0 # Apache-2.0 python-saharaclient>=1.2.0 # Apache-2.0 python-searchlightclient>=1.0.0 #Apache-2.0 python-senlinclient>=1.1.0 # Apache-2.0 From b7388dc9e33e85303186a5b25c57c9aeae1e8b43 Mon Sep 17 00:00:00 2001 From: "James E. Blair" Date: Sat, 28 Oct 2017 10:15:41 -0700 Subject: [PATCH 1817/3095] Zuul: add file extension to playbook path Zuul now supports including the file extension on the playbook path and omitting the extension is now deprecrated. Update references to include the extension. Change-Id: Ia1747b6c97140b7e12972c7f7b14cb0620ead084 --- .zuul.yaml | 90 +++++++++++++++++++++++++++--------------------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/.zuul.yaml b/.zuul.yaml index ff268c2140..66db2fce28 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -3,9 +3,9 @@ parent: devstack description: | Base job for devstack-based functional tests - pre-run: playbooks/osc-devstack/pre - run: playbooks/osc-devstack/run - post-run: playbooks/osc-devstack/post + pre-run: playbooks/osc-devstack/pre.yaml + run: playbooks/osc-devstack/run.yaml + post-run: playbooks/osc-devstack/post.yaml required-projects: - name: openstack/swift roles: @@ -13,35 +13,35 @@ timeout: 9000 vars: devstack_localrc: - SWIFT_HASH: "1234123412341234" - LIBS_FROM_GIT: 'python-openstackclient' + SWIFT_HASH: '1234123412341234' + LIBS_FROM_GIT: python-openstackclient # NOTE(dtroyer): OSC needs to support Image v1 for a while yet so re-enable - GLANCE_V1_ENABLED: True + GLANCE_V1_ENABLED: true # NOTE(dtroyer): Functional tests need a bit more volume headroom VOLUME_BACKING_FILE_SIZE: 20G devstack_local_conf: post-config: - "$CINDER_CONF": + $CINDER_CONF: DEFAULT: # NOTE(dtroyer): OSC needs to support Volume v1 for a while yet so re-enable - enable_v1_api: True + enable_v1_api: true devstack_services: - ceilometer-acentral: False - ceilometer-acompute: False - ceilometer-alarm-evaluator: False - ceilometer-alarm-notifier: False - ceilometer-anotification: False - ceilometer-api: False - ceilometer-collector: False - horizon: False - s-account: True - s-container: True - s-object: True - s-proxy: True + ceilometer-acentral: false + ceilometer-acompute: false + ceilometer-alarm-evaluator: false + ceilometer-alarm-notifier: false + ceilometer-anotification: false + ceilometer-api: false + ceilometer-collector: false + horizon: false + s-account: true + s-container: true + s-object: true + s-proxy: true osc_environment: PYTHONUNBUFFERED: 'true' - OS_CLOUD: 'devstack-admin' - tox_install_siblings: False + OS_CLOUD: devstack-admin + tox_install_siblings: false zuul_work_dir: src/git.openstack.org/openstack/python-openstackclient # The Neutron bits are here rather than in osc-functional-devstack-base to @@ -55,9 +55,9 @@ # NOTE(amotoki): Some neutron features are enabled by devstack plugin neutron: https://git.openstack.org/openstack/neutron devstack_services: - neutron-segments: True - q-metering: True - q-qos: True + neutron-segments: true + q-metering: true + q-qos: true tox_envlist: functional - job: @@ -66,20 +66,20 @@ timeout: 7800 vars: devstack_localrc: - FLAT_INTERFACE: 'br_flat' - PUBLIC_INTERFACE: 'br_pub' + FLAT_INTERFACE: br_flat + PUBLIC_INTERFACE: br_pub devstack_services: - n-cell: True - n-net: True - neutron: False - neutron-segments: False - q-agt: False - q-dhcp: False - q-l3: False - q-meta: False - q-metering: False - q-qos: False - q-svc: False + n-cell: true + n-net: true + neutron: false + neutron-segments: false + q-agt: false + q-dhcp: false + q-l3: false + q-meta: false + q-metering: false + q-qos: false + q-svc: false tox_envlist: functional - job: @@ -93,19 +93,19 @@ - openstack/python-openstacksdk vars: devstack_localrc: - USE_PYTHON3: True - LIBS_FROM_GIT: 'python-openstackclient,python-openstacksdk,osc-lib,os-client-config' + USE_PYTHON3: true + LIBS_FROM_GIT: python-openstackclient,python-openstacksdk,osc-lib,os-client-config # This is insufficient, but leaving it here as a reminder of what may # someday be all we need to make this work # disable_python3_package swift - DISABLED_PYTHON3_PACKAGES: 'swift' + DISABLED_PYTHON3_PACKAGES: swift devstack_services: # Swift is not ready for python3 yet: At a minimum keystonemiddleware needs # to be installed in the py2 env, there are probably other things too... - s-account: False - s-container: False - s-object: False - s-proxy: False + s-account: false + s-container: false + s-object: false + s-proxy: false tox_envlist: functional-tips - project: From a4d56e615dd743cfb14e4334367973119bf7c70b Mon Sep 17 00:00:00 2001 From: Pierre Hanselmann Date: Thu, 2 Nov 2017 13:44:16 +0100 Subject: [PATCH 1818/3095] Rehome test units lib Rehoming deprecated libs for the ones provided in osc-lib. Change-Id: Idb4a27f2c8edf48909ef010e3c7a1a5c0c16efc5 --- openstackclient/tests/unit/common/test_command.py | 2 +- openstackclient/tests/unit/common/test_logs.py | 3 ++- openstackclient/tests/unit/common/test_parseractions.py | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/openstackclient/tests/unit/common/test_command.py b/openstackclient/tests/unit/common/test_command.py index f24b290b62..6ddb7c1220 100644 --- a/openstackclient/tests/unit/common/test_command.py +++ b/openstackclient/tests/unit/common/test_command.py @@ -14,9 +14,9 @@ import mock +from osc_lib.command import command from osc_lib import exceptions -from openstackclient.common import command from openstackclient.tests.unit import fakes as test_fakes from openstackclient.tests.unit import utils as test_utils diff --git a/openstackclient/tests/unit/common/test_logs.py b/openstackclient/tests/unit/common/test_logs.py index b1e4d61242..421234d62c 100644 --- a/openstackclient/tests/unit/common/test_logs.py +++ b/openstackclient/tests/unit/common/test_logs.py @@ -18,7 +18,8 @@ import mock -from openstackclient.common import logs +from osc_lib import logs + from openstackclient.tests.unit import utils diff --git a/openstackclient/tests/unit/common/test_parseractions.py b/openstackclient/tests/unit/common/test_parseractions.py index 1212ad23d1..d015da430e 100644 --- a/openstackclient/tests/unit/common/test_parseractions.py +++ b/openstackclient/tests/unit/common/test_parseractions.py @@ -18,7 +18,8 @@ import argparse -from openstackclient.common import parseractions +from osc_lib.cli import parseractions + from openstackclient.tests.unit import utils From 7744046d6ac5d11c56a1a8f9be1a86d4caa92595 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Fri, 3 Nov 2017 01:07:54 +0000 Subject: [PATCH 1819/3095] Updated from global requirements Change-Id: I9935e5f3eeab0e0d8a56f5983822ad96c79a7526 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 176acb2ad8..5732862030 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ six>=1.9.0 # MIT Babel!=2.4.0,>=2.3.4 # BSD cliff!=2.9.0,>=2.8.0 # Apache-2.0 keystoneauth1>=3.2.0 # Apache-2.0 -openstacksdk>=0.9.18 # Apache-2.0 +openstacksdk>=0.9.19 # Apache-2.0 osc-lib>=1.7.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 oslo.utils>=3.28.0 # Apache-2.0 From 9ca99b991947c5b932a0c916591cd71568f2ac17 Mon Sep 17 00:00:00 2001 From: Dongcan Ye Date: Sun, 5 Nov 2017 01:16:48 -0500 Subject: [PATCH 1820/3095] Network: Add supports rbac target-all-projects Add a boolean option "target-all-projects", which allows creating rbac policy for all projects. Change-Id: Ie3af83a1bba7dd66e83b0595bb276bf8fd105831 Closes-Bug: #1728525 Closes-Bug: #1704834 --- .../cli/command-objects/network-rbac.rst | 9 +++++-- openstackclient/network/v2/network_rbac.py | 23 ++++++++++++------ .../unit/network/v2/test_network_rbac.py | 24 +++++++++++++++++++ .../notes/bug-1728525-2c40f0c19adbd0e8.yaml | 5 ++++ 4 files changed, 52 insertions(+), 9 deletions(-) create mode 100644 releasenotes/notes/bug-1728525-2c40f0c19adbd0e8.yaml diff --git a/doc/source/cli/command-objects/network-rbac.rst b/doc/source/cli/command-objects/network-rbac.rst index c49f29bb37..45fd354deb 100644 --- a/doc/source/cli/command-objects/network-rbac.rst +++ b/doc/source/cli/command-objects/network-rbac.rst @@ -19,7 +19,8 @@ Create network RBAC policy openstack network rbac create --type --action - --target-project [--target-project-domain ] + [--target-project | --target-all-projects] + [--target-project-domain ] [--project [--project-domain ]] @@ -33,7 +34,11 @@ Create network RBAC policy .. option:: --target-project - The project to which the RBAC policy will be enforced (name or ID) (required) + The project to which the RBAC policy will be enforced (name or ID) + +.. option:: --target-all-projects + + Allow creating RBAC policy for all projects. .. option:: --target-project-domain diff --git a/openstackclient/network/v2/network_rbac.py b/openstackclient/network/v2/network_rbac.py index 9075473726..6cf82559dd 100644 --- a/openstackclient/network/v2/network_rbac.py +++ b/openstackclient/network/v2/network_rbac.py @@ -51,11 +51,14 @@ def _get_attrs(client_manager, parsed_args): attrs['object_id'] = object_id identity_client = client_manager.identity - project_id = identity_common.find_project( - identity_client, - parsed_args.target_project, - parsed_args.target_project_domain, - ).id + if parsed_args.target_project is not None: + project_id = identity_common.find_project( + identity_client, + parsed_args.target_project, + parsed_args.target_project_domain, + ).id + elif parsed_args.target_all_projects: + project_id = '*' attrs['target_tenant'] = project_id if parsed_args.project is not None: project_id = identity_common.find_project( @@ -96,13 +99,19 @@ def get_parser(self, prog_name): help=_('Action for the RBAC policy ' '("access_as_external" or "access_as_shared")') ) - parser.add_argument( + target_project_group = parser.add_mutually_exclusive_group( + required=True) + target_project_group.add_argument( '--target-project', - required=True, metavar="", help=_('The project to which the RBAC policy ' 'will be enforced (name or ID)') ) + target_project_group.add_argument( + '--target-all-projects', + action='store_true', + help=_('Allow creating RBAC policy for all projects.') + ) parser.add_argument( '--target-project-domain', metavar='', diff --git a/openstackclient/tests/unit/network/v2/test_network_rbac.py b/openstackclient/tests/unit/network/v2/test_network_rbac.py index 935ce0758c..70c3852868 100644 --- a/openstackclient/tests/unit/network/v2/test_network_rbac.py +++ b/openstackclient/tests/unit/network/v2/test_network_rbac.py @@ -163,6 +163,30 @@ def test_network_rbac_create(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + def test_network_rbac_create_with_target_all_projects(self): + arglist = [ + '--type', self.rbac_policy.object_type, + '--action', self.rbac_policy.action, + '--target-all-projects', + self.rbac_policy.object_id, + ] + verifylist = [ + ('type', self.rbac_policy.object_type), + ('action', self.rbac_policy.action), + ('target_all_projects', True), + ('rbac_object', self.rbac_policy.object_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_rbac_policy.assert_called_with(**{ + 'object_id': self.rbac_policy.object_id, + 'object_type': self.rbac_policy.object_type, + 'action': self.rbac_policy.action, + 'target_tenant': '*', + }) + def test_network_rbac_create_all_options(self): arglist = [ '--type', self.rbac_policy.object_type, diff --git a/releasenotes/notes/bug-1728525-2c40f0c19adbd0e8.yaml b/releasenotes/notes/bug-1728525-2c40f0c19adbd0e8.yaml new file mode 100644 index 0000000000..67264af152 --- /dev/null +++ b/releasenotes/notes/bug-1728525-2c40f0c19adbd0e8.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Add ``target-all-projects`` option in ``rbac create`` command. + [Bug `1728525 `_] From 07f0c7aa55920d65035124c9e8bfe8452356c811 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C5=82awek=20Kap=C5=82o=C5=84ski?= Date: Thu, 7 Sep 2017 20:34:15 +0000 Subject: [PATCH 1821/3095] Display Network QoS rule type details Neutron API now supports getting details of supported QoS rule type. This patch adds support for this feature to OpenStack client. Change-Id: I74d16563ce2236a7c899f5994f1dab43ace02138 Depends-On: I448b5d4f8e4ef42eafe50d9d6c63d0be666f98fc Related-Bug: #1686035 --- .../command-objects/network-qos-rule-type.rst | 15 +++++ .../network/v2/network_qos_rule_type.py | 31 +++++++++++ .../network/v2/test_network_qos_rule_type.py | 7 +++ .../network/v2/test_network_qos_rule_type.py | 55 +++++++++++++++++++ ...k-qos-rule-type-show-57a714a1d428726e.yaml | 4 ++ setup.cfg | 1 + 6 files changed, 113 insertions(+) create mode 100644 releasenotes/notes/add-network-qos-rule-type-show-57a714a1d428726e.yaml diff --git a/doc/source/cli/command-objects/network-qos-rule-type.rst b/doc/source/cli/command-objects/network-qos-rule-type.rst index ee53e30b41..de6f2474af 100644 --- a/doc/source/cli/command-objects/network-qos-rule-type.rst +++ b/doc/source/cli/command-objects/network-qos-rule-type.rst @@ -16,3 +16,18 @@ List Network QoS rule types .. code:: bash openstack network qos rule type list + +network qos rule type show +-------------------------- + +Display Network QoS rule type details + +.. program:: network qos rule type show +.. code:: bash + + openstack network qos rule type show + + +.. describe:: + + Name of QoS rule type (minimum-bandwidth, dscp-marking, bandwidth-limit) diff --git a/openstackclient/network/v2/network_qos_rule_type.py b/openstackclient/network/v2/network_qos_rule_type.py index 52f8e23576..7b92c8ad4b 100644 --- a/openstackclient/network/v2/network_qos_rule_type.py +++ b/openstackclient/network/v2/network_qos_rule_type.py @@ -17,6 +17,17 @@ from osc_lib import utils from openstackclient.i18n import _ +from openstackclient.network import sdk_utils + + +def _get_columns(item): + column_map = { + "type": "rule_type_name", + "drivers": "drivers", + } + invisible_columns = ["id", "name"] + return sdk_utils.get_osc_show_columns_for_sdk_resource( + item, column_map, invisible_columns) class ListNetworkQosRuleType(command.Lister): @@ -36,3 +47,23 @@ def take_action(self, parsed_args): (utils.get_item_properties( s, columns, formatters={}, ) for s in data)) + + +class ShowNetworkQosRuleType(command.ShowOne): + _description = _("Show details about supported QoS rule type") + + def get_parser(self, prog_name): + parser = super(ShowNetworkQosRuleType, self).get_parser(prog_name) + parser.add_argument( + 'rule_type', + metavar="", + help=_("Name of QoS rule type") + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.get_qos_rule_type(parsed_args.rule_type) + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns) + return display_columns, data diff --git a/openstackclient/tests/functional/network/v2/test_network_qos_rule_type.py b/openstackclient/tests/functional/network/v2/test_network_qos_rule_type.py index a6ee3e1006..56cd8920b8 100644 --- a/openstackclient/tests/functional/network/v2/test_network_qos_rule_type.py +++ b/openstackclient/tests/functional/network/v2/test_network_qos_rule_type.py @@ -35,3 +35,10 @@ def test_qos_rule_type_list(self): 'network qos rule type list -f json')) for rule_type in self.AVAILABLE_RULE_TYPES: self.assertIn(rule_type, [x['Type'] for x in cmd_output]) + + def test_qos_rule_type_details(self): + for rule_type in self.AVAILABLE_RULE_TYPES: + cmd_output = json.loads(self.openstack( + 'network qos rule type show %s -f json' % rule_type)) + self.assertEqual(rule_type, cmd_output['rule_type_name']) + self.assertIn("drivers", cmd_output.keys()) diff --git a/openstackclient/tests/unit/network/v2/test_network_qos_rule_type.py b/openstackclient/tests/unit/network/v2/test_network_qos_rule_type.py index b93abe8017..80c52bf72e 100644 --- a/openstackclient/tests/unit/network/v2/test_network_qos_rule_type.py +++ b/openstackclient/tests/unit/network/v2/test_network_qos_rule_type.py @@ -17,6 +17,7 @@ from openstackclient.network.v2 import network_qos_rule_type as _qos_rule_type from openstackclient.tests.unit.network.v2 import fakes as network_fakes +from openstackclient.tests.unit import utils as tests_utils class TestNetworkQosRuleType(network_fakes.TestNetworkV2): @@ -27,6 +28,60 @@ def setUp(self): self.network = self.app.client_manager.network +class TestShowNetworkQosRuleType(TestNetworkQosRuleType): + + attrs = { + 'drivers': [{ + 'name': 'driver 1', + 'supported_parameters': [] + }] + } + # The QoS policies to show. + qos_rule_type = ( + network_fakes.FakeNetworkQosRuleType.create_one_qos_rule_type(attrs)) + columns = ( + 'drivers', + 'rule_type_name' + ) + data = [ + qos_rule_type.drivers, + qos_rule_type.type + ] + + def setUp(self): + super(TestShowNetworkQosRuleType, self).setUp() + self.network.get_qos_rule_type = mock.Mock( + return_value=self.qos_rule_type) + + # Get the command object to test + self.cmd = _qos_rule_type.ShowNetworkQosRuleType(self.app, + self.namespace) + + def test_show_no_options(self): + arglist = [] + verifylist = [] + + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_show_all_options(self): + arglist = [ + self.qos_rule_type.type, + ] + verifylist = [ + ('rule_type', self.qos_rule_type.type), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.get_qos_rule_type.assert_called_once_with( + self.qos_rule_type.type) + self.assertEqual(self.columns, columns) + self.assertEqual(list(self.data), list(data)) + + class TestListNetworkQosRuleType(TestNetworkQosRuleType): # The QoS policies to list up. diff --git a/releasenotes/notes/add-network-qos-rule-type-show-57a714a1d428726e.yaml b/releasenotes/notes/add-network-qos-rule-type-show-57a714a1d428726e.yaml new file mode 100644 index 0000000000..d1fb88cebb --- /dev/null +++ b/releasenotes/notes/add-network-qos-rule-type-show-57a714a1d428726e.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Add ``network qos rule type show`` command. diff --git a/setup.cfg b/setup.cfg index 2412b6fe02..dcfe767199 100644 --- a/setup.cfg +++ b/setup.cfg @@ -424,6 +424,7 @@ openstack.network.v2 = network_qos_rule_show = openstackclient.network.v2.network_qos_rule:ShowNetworkQosRule network_qos_rule_type_list = openstackclient.network.v2.network_qos_rule_type:ListNetworkQosRuleType + network_qos_rule_type_show = openstackclient.network.v2.network_qos_rule_type:ShowNetworkQosRuleType network_rbac_create = openstackclient.network.v2.network_rbac:CreateNetworkRBAC network_rbac_delete = openstackclient.network.v2.network_rbac:DeleteNetworkRBAC From 6c8e5177bc54ba215aad310094b1eefd6ce709cf Mon Sep 17 00:00:00 2001 From: npraveen35 Date: Fri, 18 Aug 2017 15:48:54 +0530 Subject: [PATCH 1822/3095] Neutron agent delete: remove the wrong argument The take_action() function was calling get_agent() with the wrong attribute, causing agent deletion to fail. It turns out calling get_agent() isn't necessary; this removes the call entirely and moves the 'ignore_missing' argument to the delete_agent() function. Change-Id: Iaa3754a3be0765112f396495fa5fb2e32e6eae4e Closes-Bug: #1711301 --- openstackclient/network/v2/network_agent.py | 3 +-- .../unit/network/v2/test_network_agent.py | 23 ++++++------------- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/openstackclient/network/v2/network_agent.py b/openstackclient/network/v2/network_agent.py index ed4970a488..ba2a2633ba 100644 --- a/openstackclient/network/v2/network_agent.py +++ b/openstackclient/network/v2/network_agent.py @@ -134,8 +134,7 @@ def take_action(self, parsed_args): for agent in parsed_args.network_agent: try: - obj = client.get_agent(agent, ignore_missing=False) - client.delete_agent(obj) + client.delete_agent(agent, ignore_missing=False) except Exception as e: result += 1 LOG.error(_("Failed to delete network agent with " diff --git a/openstackclient/tests/unit/network/v2/test_network_agent.py b/openstackclient/tests/unit/network/v2/test_network_agent.py index 12e40cdbc8..709fb1c6cd 100644 --- a/openstackclient/tests/unit/network/v2/test_network_agent.py +++ b/openstackclient/tests/unit/network/v2/test_network_agent.py @@ -121,10 +121,6 @@ class TestDeleteNetworkAgent(TestNetworkAgent): def setUp(self): super(TestDeleteNetworkAgent, self).setUp() self.network.delete_agent = mock.Mock(return_value=None) - self.network.get_agent = ( - network_fakes.FakeNetworkAgent.get_network_agents( - agents=self.network_agents) - ) # Get the command object to test self.cmd = network_agent.DeleteNetworkAgent(self.app, self.namespace) @@ -140,10 +136,8 @@ def test_network_agent_delete(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.network.get_agent.assert_called_once_with( - self.network_agents[0].id, ignore_missing=False) self.network.delete_agent.assert_called_once_with( - self.network_agents[0]) + self.network_agents[0].id, ignore_missing=False) self.assertIsNone(result) def test_multi_network_agents_delete(self): @@ -160,7 +154,7 @@ def test_multi_network_agents_delete(self): calls = [] for n in self.network_agents: - calls.append(call(n)) + calls.append(call(n.id, ignore_missing=False)) self.network.delete_agent.assert_has_calls(calls) self.assertIsNone(result) @@ -175,9 +169,9 @@ def test_multi_network_agents_delete_with_exception(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - find_mock_result = [self.network_agents[0], exceptions.CommandError] - self.network.get_agent = ( - mock.Mock(side_effect=find_mock_result) + delete_mock_result = [True, exceptions.CommandError] + self.network.delete_agent = ( + mock.Mock(side_effect=delete_mock_result) ) try: @@ -186,13 +180,10 @@ def test_multi_network_agents_delete_with_exception(self): except exceptions.CommandError as e: self.assertEqual('1 of 2 network agents failed to delete.', str(e)) - self.network.get_agent.assert_any_call( + self.network.delete_agent.assert_any_call( self.network_agents[0].id, ignore_missing=False) - self.network.get_agent.assert_any_call( + self.network.delete_agent.assert_any_call( 'unexist_network_agent', ignore_missing=False) - self.network.delete_agent.assert_called_once_with( - self.network_agents[0] - ) class TestListNetworkAgent(TestNetworkAgent): From 4742d4df7089cd10d03635a1b3dbca9e7e80b1cc Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sun, 12 Nov 2017 21:18:50 +0000 Subject: [PATCH 1823/3095] Updated from global requirements Change-Id: Icf2e8d6cca0194de127ef0dfaee5db04381b5cd1 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 5732862030..ffcf89d9f7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ keystoneauth1>=3.2.0 # Apache-2.0 openstacksdk>=0.9.19 # Apache-2.0 osc-lib>=1.7.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 -oslo.utils>=3.28.0 # Apache-2.0 +oslo.utils>=3.31.0 # Apache-2.0 python-glanceclient>=2.8.0 # Apache-2.0 python-keystoneclient>=3.8.0 # Apache-2.0 python-novaclient>=9.1.0 # Apache-2.0 From 7ef2867ff61d6e4d468b80a9284fdb0bd5bdb6c9 Mon Sep 17 00:00:00 2001 From: Hongbin Lu Date: Mon, 13 Nov 2017 15:23:16 +0000 Subject: [PATCH 1824/3095] Replace %r with %s on printing string variable Change-Id: Idd8c15255f024bba7079d3a9a29545dec0c91b58 --- openstackclient/common/clientmanager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index 7b2c8a5cc3..897810521d 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -132,13 +132,13 @@ def get_plugin_modules(group): """Find plugin entry points""" mod_list = [] for ep in pkg_resources.iter_entry_points(group): - LOG.debug('Found plugin %r', ep.name) + LOG.debug('Found plugin %s', ep.name) try: __import__(ep.module_name) except Exception: sys.stderr.write( - "WARNING: Failed to import plugin %r.\n" % ep.name) + "WARNING: Failed to import plugin %s.\n" % ep.name) continue module = sys.modules[ep.module_name] From 809355894fac72908869739b72d44ead1c183072 Mon Sep 17 00:00:00 2001 From: Hongbin Lu Date: Thu, 26 Oct 2017 15:35:47 +0000 Subject: [PATCH 1825/3095] Add RemoveNetwork command to server This command will detach a server from a network. All server's neutron ports that belongs to the specified networks will be removed. Change-Id: I83a064ed62ab00c6f1016900b9cf30f1c15b8382 --- openstackclient/compute/v2/server.py | 35 +++++++++++++ .../tests/unit/compute/v2/test_server.py | 51 +++++++++++++++++++ ...erver-remove-network-fb09c53d5b0c0068.yaml | 5 ++ setup.cfg | 1 + 4 files changed, 92 insertions(+) create mode 100644 releasenotes/notes/add-server-remove-network-fb09c53d5b0c0068.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 034199822d..78c567889f 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1542,6 +1542,41 @@ def take_action(self, parsed_args): server.interface_detach(port_id) +class RemoveNetwork(command.Command): + _description = _("Remove all ports of a network from server") + + def get_parser(self, prog_name): + parser = super(RemoveNetwork, self).get_parser(prog_name) + parser.add_argument( + "server", + metavar="", + help=_("Server to remove the port from (name or ID)"), + ) + parser.add_argument( + "network", + metavar="", + help=_("Network to remove from the server (name or ID)"), + ) + return parser + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + + server = utils.find_resource( + compute_client.servers, parsed_args.server) + + if self.app.client_manager.is_network_endpoint_enabled(): + network_client = self.app.client_manager.network + net_id = network_client.find_network( + parsed_args.network, ignore_missing=False).id + else: + net_id = parsed_args.network + + for inf in server.interface_list(): + if inf.net_id == net_id: + server.interface_detach(inf.port_id) + + class RemoveServerSecurityGroup(command.Command): _description = _("Remove security group from server") diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index ad52e23225..20b9532aad 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -2380,6 +2380,57 @@ def test_server_remove_port_no_neutron(self): self.find_port.assert_not_called() +class TestServerRemoveNetwork(TestServer): + + def setUp(self): + super(TestServerRemoveNetwork, self).setUp() + + # Get the command object to test + self.cmd = server.RemoveNetwork(self.app, None) + + # Set method to be tested. + self.fake_inf = mock.Mock() + self.methods = { + 'interface_list': [self.fake_inf], + 'interface_detach': None, + } + + self.find_network = mock.Mock() + self.app.client_manager.network.find_network = self.find_network + + def _test_server_remove_network(self, network_id): + self.fake_inf.net_id = network_id + self.fake_inf.port_id = 'fake-port' + servers = self.setup_servers_mock(count=1) + network = 'fake-network' + + arglist = [ + servers[0].id, + network, + ] + verifylist = [ + ('server', servers[0].id), + ('network', network), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + servers[0].interface_list.assert_called_once_with() + servers[0].interface_detach.assert_called_once_with('fake-port') + self.assertIsNone(result) + + def test_server_remove_network(self): + self._test_server_remove_network(self.find_network.return_value.id) + self.find_network.assert_called_once_with( + 'fake-network', ignore_missing=False) + + def test_server_remove_network_no_neutron(self): + self.app.client_manager.network_endpoint_enabled = False + self._test_server_remove_network('fake-network') + self.find_network.assert_not_called() + + @mock.patch( 'openstackclient.api.compute_v2.APIv2.security_group_find' ) diff --git a/releasenotes/notes/add-server-remove-network-fb09c53d5b0c0068.yaml b/releasenotes/notes/add-server-remove-network-fb09c53d5b0c0068.yaml new file mode 100644 index 0000000000..6aaf905dea --- /dev/null +++ b/releasenotes/notes/add-server-remove-network-fb09c53d5b0c0068.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``server remove network`` command. This command will remove all + network ports from the specified network and instance. diff --git a/setup.cfg b/setup.cfg index 97333f61d1..e7b9cb6a2f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -118,6 +118,7 @@ openstack.compute.v2 = server_remove_fixed_ip = openstackclient.compute.v2.server:RemoveFixedIP server_remove_floating_ip = openstackclient.compute.v2.server:RemoveFloatingIP server_remove_port = openstackclient.compute.v2.server:RemovePort + server_remove_network = openstackclient.compute.v2.server:RemoveNetwork server_remove_security_group = openstackclient.compute.v2.server:RemoveServerSecurityGroup server_remove_volume = openstackclient.compute.v2.server:RemoveServerVolume server_rescue = openstackclient.compute.v2.server:RescueServer From 8b31e2bc4843f76b659a644406c0a522bf29b612 Mon Sep 17 00:00:00 2001 From: Hongbin Lu Date: Tue, 14 Nov 2017 23:24:55 +0000 Subject: [PATCH 1826/3095] Show detailed message of neutron exception This patch improves the error message by retrieving the more details from the exception instance. Otherwise, the real error message won't be displayed (unless using --debug in the command). Change-Id: I8ba694bda86f7cc8362e301b2044d9b610dde49c --- openstackclient/network/common.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openstackclient/network/common.py b/openstackclient/network/common.py index a2e700be2f..eca0de3c9a 100644 --- a/openstackclient/network/common.py +++ b/openstackclient/network/common.py @@ -191,6 +191,8 @@ def take_action(self, parsed_args): self.app.client_manager.compute, parsed_args) except openstack.exceptions.HttpException as exc: msg = _("Error while executing command: %s") % exc.message + if exc.details: + msg += ", " + six.text_type(exc.details) raise exceptions.CommandError(msg) def get_parser(self, prog_name): From aafbb69cfb0271f2f6ded0b4d311bc29f7dbcb17 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 15 Nov 2017 01:53:52 +0000 Subject: [PATCH 1827/3095] Updated from global requirements Change-Id: I2c8a5132f6078609ad0d46093642a1b361115c5c --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index b0b4585913..9f63c31163 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -18,7 +18,7 @@ os-client-config>=1.28.0 # Apache-2.0 os-testr>=1.0.0 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD testtools>=1.4.0 # MIT -tempest>=16.1.0 # Apache-2.0 +tempest>=17.1.0 # Apache-2.0 osprofiler>=1.4.0 # Apache-2.0 bandit>=1.1.0 # Apache-2.0 wrapt>=1.7.0 # BSD License From e7ef9e855677051383446e18e393b1f96158331b Mon Sep 17 00:00:00 2001 From: ycx Date: Tue, 27 Jun 2017 10:12:06 +0800 Subject: [PATCH 1828/3095] Network: Add interfaces info in router show Add a list of interfaces info in the output of 'openstack router show'. The information of router interface are: IP address, subnet ID and port ID. Co-Authored-By: Dongcan Ye Change-Id: I1252986122122defffe795292b83dc4e84481c7e Closes-Bug: #1675489 --- openstackclient/network/v2/router.py | 21 +++++++++++++++-- .../tests/unit/network/v2/test_router.py | 23 +++++++++++++++---- .../notes/bug-1675489-a1d226f2ee911420.yaml | 6 +++++ 3 files changed, 44 insertions(+), 6 deletions(-) create mode 100644 releasenotes/notes/bug-1675489-a1d226f2ee911420.yaml diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 4f9085373c..caf3236ab5 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -36,7 +36,7 @@ def _format_admin_state(state): return 'UP' if state else 'DOWN' -def _format_external_gateway_info(info): +def _format_router_info(info): try: return json.dumps(info) except (TypeError, KeyError): @@ -54,7 +54,7 @@ def _format_routes(routes): _formatters = { 'admin_state_up': _format_admin_state, 'is_admin_state_up': _format_admin_state, - 'external_gateway_info': _format_external_gateway_info, + 'external_gateway_info': _format_router_info, 'availability_zones': utils.format_list, 'availability_zone_hints': utils.format_list, 'routes': _format_routes, @@ -69,6 +69,8 @@ def _get_columns(item): 'is_distributed': 'distributed', 'is_admin_state_up': 'admin_state_up', } + if hasattr(item, 'interfaces_info'): + column_map['interfaces_info'] = 'interfaces_info' return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) @@ -657,7 +659,22 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): client = self.app.client_manager.network obj = client.find_router(parsed_args.router, ignore_missing=False) + interfaces_info = [] + filters = {} + filters['device_id'] = obj.id + for port in client.ports(**filters): + if port.device_owner != "network:router_gateway": + for ip_spec in port.fixed_ips: + int_info = { + 'port_id': port.id, + 'ip_address': ip_spec.get('ip_address'), + 'subnet_id': ip_spec.get('subnet_id') + } + interfaces_info.append(int_info) + + setattr(obj, 'interfaces_info', interfaces_info) display_columns, columns = _get_columns(obj) + _formatters['interfaces_info'] = _format_router_info data = utils.get_item_properties(obj, columns, formatters=_formatters) return (display_columns, data) diff --git a/openstackclient/tests/unit/network/v2/test_router.py b/openstackclient/tests/unit/network/v2/test_router.py index 2248db9a9c..f383c1ddb7 100644 --- a/openstackclient/tests/unit/network/v2/test_router.py +++ b/openstackclient/tests/unit/network/v2/test_router.py @@ -137,7 +137,7 @@ class TestCreateRouter(TestRouter): osc_utils.format_list(new_router.availability_zones), new_router.description, new_router.distributed, - router._format_external_gateway_info(new_router.external_gateway_info), + router._format_router_info(new_router.external_gateway_info), new_router.ha, new_router.id, new_router.name, @@ -448,7 +448,7 @@ class TestListRouter(TestRouter): data_long.append( data[i] + ( router._format_routes(r.routes), - router._format_external_gateway_info(r.external_gateway_info), + router._format_router_info(r.external_gateway_info), osc_utils.format_list(r.availability_zones), osc_utils.format_list(r.tags), ) @@ -459,7 +459,7 @@ class TestListRouter(TestRouter): data_long_no_az.append( data[i] + ( router._format_routes(r.routes), - router._format_external_gateway_info(r.external_gateway_info), + router._format_router_info(r.external_gateway_info), osc_utils.format_list(r.tags), ) ) @@ -1118,6 +1118,15 @@ class TestShowRouter(TestRouter): # The router to set. _router = network_fakes.FakeRouter.create_one_router() + _port = network_fakes.FakePort.create_one_port({ + 'device_owner': 'network:router_interface', + 'device_id': _router.id + }) + setattr(_router, + 'interfaces_info', + [{'port_id': _port.id, + 'ip_address': _port.fixed_ips[0]['ip_address'], + 'subnet_id': _port.fixed_ips[0]['subnet_id']}]) columns = ( 'admin_state_up', @@ -1128,6 +1137,7 @@ class TestShowRouter(TestRouter): 'external_gateway_info', 'ha', 'id', + 'interfaces_info', 'name', 'project_id', 'routes', @@ -1140,9 +1150,10 @@ class TestShowRouter(TestRouter): osc_utils.format_list(_router.availability_zones), _router.description, _router.distributed, - router._format_external_gateway_info(_router.external_gateway_info), + router._format_router_info(_router.external_gateway_info), _router.ha, _router.id, + router._format_router_info(_router.interfaces_info), _router.name, _router.tenant_id, router._format_routes(_router.routes), @@ -1154,6 +1165,7 @@ def setUp(self): super(TestShowRouter, self).setUp() self.network.find_router = mock.Mock(return_value=self._router) + self.network.ports = mock.Mock(return_value=[self._port]) # Get the command object to test self.cmd = router.ShowRouter(self.app, self.namespace) @@ -1178,6 +1190,9 @@ def test_show_all_options(self): self.network.find_router.assert_called_once_with( self._router.name, ignore_missing=False) + self.network.ports.assert_called_with(**{ + 'device_id': self._router.id + }) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) diff --git a/releasenotes/notes/bug-1675489-a1d226f2ee911420.yaml b/releasenotes/notes/bug-1675489-a1d226f2ee911420.yaml new file mode 100644 index 0000000000..5a296d4835 --- /dev/null +++ b/releasenotes/notes/bug-1675489-a1d226f2ee911420.yaml @@ -0,0 +1,6 @@ +--- +features: + - Add router interfaces info (as field ``interfaces_info``) to ``router show`` command. + The information of router interface include port's ID, IP address, + the subnet ID it belongs. + [Bug `1675489 `_] From c0f910d6133626e5afbb6106b5230d5d59a4eb89 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 16 Nov 2017 11:25:40 +0000 Subject: [PATCH 1829/3095] Updated from global requirements Change-Id: I714dbe5326ed99c4edab6c63ecea4dc6a72b57e7 --- requirements.txt | 2 +- test-requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index ffcf89d9f7..9299891701 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. pbr!=2.1.0,>=2.0.0 # Apache-2.0 -six>=1.9.0 # MIT +six>=1.10.0 # MIT Babel!=2.4.0,>=2.3.4 # BSD cliff!=2.9.0,>=2.8.0 # Apache-2.0 diff --git a/test-requirements.txt b/test-requirements.txt index 9f63c31163..ffd92041d2 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -17,7 +17,7 @@ stevedore>=1.20.0 # Apache-2.0 os-client-config>=1.28.0 # Apache-2.0 os-testr>=1.0.0 # Apache-2.0 testrepository>=0.0.18 # Apache-2.0/BSD -testtools>=1.4.0 # MIT +testtools>=2.2.0 # MIT tempest>=17.1.0 # Apache-2.0 osprofiler>=1.4.0 # Apache-2.0 bandit>=1.1.0 # Apache-2.0 From 80da4d6cf8140a150c18c55c1e2b5da49dd59e9d Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Thu, 16 Nov 2017 08:09:11 -0600 Subject: [PATCH 1830/3095] Add logic to handle old and new sdk constructor SDK is removing Profile, but currently has compat code to support this invocation in OSC. While the intent is to protect people from upgrade breakage, it's python, and packaging things have a tendency to get strange. By putting in a little belt and suspenders if block here, we can hopefully protect folks who upgrade sdk for some reason without upgrading python-openstackclient. Change-Id: Id678e97a2b99dbbfc772acc8c6ba283db551723d --- openstackclient/network/client.py | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/openstackclient/network/client.py b/openstackclient/network/client.py index 9525b947d0..3566bfe525 100644 --- a/openstackclient/network/client.py +++ b/openstackclient/network/client.py @@ -14,7 +14,10 @@ import logging from openstack import connection -from openstack import profile +try: + from openstack import profile +except ImportError: + profile = None from osc_lib import utils from openstackclient.i18n import _ @@ -33,14 +36,20 @@ def make_client(instance): """Returns a network proxy""" - prof = profile.Profile() - prof.set_region(API_NAME, instance.region_name) - prof.set_version(API_NAME, instance._api_version[API_NAME]) - prof.set_interface(API_NAME, instance.interface) - conn = connection.Connection(authenticator=instance.session.auth, - verify=instance.session.verify, - cert=instance.session.cert, - profile=prof) + if profile is None: + # New SDK + conn = connection.Connection( + cloud_config=instance._cli_options, + session=instance.session) + else: + prof = profile.Profile() + prof.set_region(API_NAME, instance.region_name) + prof.set_version(API_NAME, instance._api_version[API_NAME]) + prof.set_interface(API_NAME, instance.interface) + conn = connection.Connection(authenticator=instance.session.auth, + verify=instance.session.verify, + cert=instance.session.cert, + profile=prof) LOG.debug('Connection: %s', conn) LOG.debug('Network client initialized using OpenStack SDK: %s', conn.network) From 7d85ecaa332d1f94eb3089db9cb5cc112356a6a8 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 16 Nov 2017 12:38:11 -0600 Subject: [PATCH 1831/3095] Prepare for os-clinet-config to go away We used that module in a test functional for module list, it is being absorbed into python-openstacksdk and having it listed in this test breaks -tips jobs. Change-Id: I98fdf5a5d1b3c6e30cb4c5f5fec3dd8e43e53145 --- openstackclient/tests/functional/common/test_module.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openstackclient/tests/functional/common/test_module.py b/openstackclient/tests/functional/common/test_module.py index e9e4ee3a6f..d589f19cde 100644 --- a/openstackclient/tests/functional/common/test_module.py +++ b/openstackclient/tests/functional/common/test_module.py @@ -27,7 +27,6 @@ class ModuleTest(base.TestCase): 'openstack'] LIBS = ['osc_lib', - 'os_client_config', 'keystoneauth1'] def test_module_list(self): From d7f906b526295f4e38dc34b25c21555fedc00ea8 Mon Sep 17 00:00:00 2001 From: Andreas Jaeger Date: Thu, 16 Nov 2017 20:46:56 +0100 Subject: [PATCH 1832/3095] Remove setting of version/release from releasenotes Release notes are version independent, so remove version/release values. We've found that projects now require the service package to be installed in order to build release notes, and this is entirely due to the current convention of pulling in the version information. Release notes should not need installation in order to build, so this unnecessary version setting needs to be removed. This is needed for new release notes publishing, see I56909152975f731a9d2c21b2825b972195e48ee8 and the discussion starting at http://lists.openstack.org/pipermail/openstack-dev/2017-November/124480.html . Change-Id: Ia0c8d7dc346182fde095eebb82eeeb70fc1b9770 --- releasenotes/source/conf.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py index 1dc130c1c1..3c4e4107e1 100644 --- a/releasenotes/source/conf.py +++ b/releasenotes/source/conf.py @@ -83,16 +83,11 @@ project = u'OpenStackClient Release Notes' copyright = u'2015, OpenStackClient Developers' -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -from openstackclient import version_info as openstackclient_version +# Release notes are version independent. # The full version, including alpha/beta/rc tags. -release = openstackclient_version.version_string_with_vcs() +release = '' # The short X.Y version. -version = openstackclient_version.canonical_version_string() +version = '' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. From 0f749cacc2b0a76226a6a7ab7769ecf1475a160b Mon Sep 17 00:00:00 2001 From: yangweiwei Date: Fri, 17 Nov 2017 11:39:15 +0800 Subject: [PATCH 1833/3095] Fix credentials in create, update and list. Now, keystone has supported serverl auth method, like 'totp'. Before we use this method, we should create the credential first. And we need create it with type 'totp'. But now we cannot create credential with this method. Also, I think the type should not have constrains. We can create any type in keystone project. So, we should do these actions too. The type would be more which We cannot control. Change-Id: Ie0482da3133fb515e4bb8e45f8c54f509589cc5e Closes-bug: #1731848 --- doc/source/cli/command-objects/credential.rst | 6 +++--- openstackclient/identity/v3/credential.py | 9 +++------ .../tests/unit/identity/v3/test_credential.py | 14 -------------- .../notes/bug-1731848-71d0a5fdb1a34a8b.yaml | 7 +++++++ 4 files changed, 13 insertions(+), 23 deletions(-) create mode 100644 releasenotes/notes/bug-1731848-71d0a5fdb1a34a8b.yaml diff --git a/doc/source/cli/command-objects/credential.rst b/doc/source/cli/command-objects/credential.rst index 47c847c895..7fe57310f3 100644 --- a/doc/source/cli/command-objects/credential.rst +++ b/doc/source/cli/command-objects/credential.rst @@ -19,7 +19,7 @@ Create new credential .. option:: --type - New credential type: cert, ec2 + New credential type: cert, ec2, totp and so on .. option:: --project @@ -73,7 +73,7 @@ List credentials .. option:: --type - Filter credentials by type: cert, ec2 + Filter credentials by type: cert, ec2, totp and so on credential set -------------- @@ -96,7 +96,7 @@ Set credential properties .. option:: --type - New credential type: cert, ec2 + New credential type: cert, ec2, totp and so on. .. option:: --data diff --git a/openstackclient/identity/v3/credential.py b/openstackclient/identity/v3/credential.py index 79ef632cc4..981f940aae 100644 --- a/openstackclient/identity/v3/credential.py +++ b/openstackclient/identity/v3/credential.py @@ -43,8 +43,7 @@ def get_parser(self, prog_name): '--type', default="cert", metavar='', - choices=['ec2', 'cert'], - help=_('New credential type: cert, ec2'), + help=_('New credential type: cert, ec2, totp and so on'), ) parser.add_argument( 'data', @@ -124,8 +123,7 @@ def get_parser(self, prog_name): parser.add_argument( '--type', metavar='', - choices=['ec2', 'cert'], - help=_('Filter credentials by type: cert, ec2'), + help=_('Filter credentials by type: cert, ec2, totp and so on'), ) return parser @@ -173,9 +171,8 @@ def get_parser(self, prog_name): parser.add_argument( '--type', metavar='', - choices=['ec2', 'cert'], required=True, - help=_('New credential type: cert, ec2'), + help=_('New credential type: cert, ec2, totp and so on'), ) parser.add_argument( '--data', diff --git a/openstackclient/tests/unit/identity/v3/test_credential.py b/openstackclient/tests/unit/identity/v3/test_credential.py index 161f048447..de0306dd68 100644 --- a/openstackclient/tests/unit/identity/v3/test_credential.py +++ b/openstackclient/tests/unit/identity/v3/test_credential.py @@ -124,20 +124,6 @@ def test_credential_create_with_options(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) - def test_credential_create_with_invalid_type(self): - arglist = [ - self.credential.user_id, - self.credential.blob, - '--type', 'invalid_type', - ] - verifylist = [ - ('user', self.credential.user_id), - ('data', self.credential.blob), - ('type', 'invalid_type'), - ] - self.assertRaises(utils.ParserException, self.check_parser, - self.cmd, arglist, verifylist) - class TestCredentialDelete(TestCredential): diff --git a/releasenotes/notes/bug-1731848-71d0a5fdb1a34a8b.yaml b/releasenotes/notes/bug-1731848-71d0a5fdb1a34a8b.yaml new file mode 100644 index 0000000000..e36652734e --- /dev/null +++ b/releasenotes/notes/bug-1731848-71d0a5fdb1a34a8b.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + Remove the type value limit in credentials when do create, + reset or list. Now 'totp' method is supported in keystone + project and we could create credentials with 'totp' type. + [Bug `1731848 `_] From d0917cd14b94ce13fd92237490efb5722761eda4 Mon Sep 17 00:00:00 2001 From: Matt Riedemann Date: Sun, 19 Nov 2017 18:34:24 -0500 Subject: [PATCH 1834/3095] Remove hard-coded policy choices for creating a server group The hard-coded choices for the server group policy make it impossible to create a server group with the soft-affinity or soft-anti-affinity policy rules which were added in compute API microversion 2.15. This removes the hard-coded choices so that the policy is restricted on the server side rather than the client side. Change-Id: Ib3dc39422ac1015872d56ae2fdeddf0f29613494 Closes-Bug: #1732938 --- openstackclient/compute/v2/server_group.py | 5 +++-- openstackclient/tests/unit/compute/v2/test_server_group.py | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/openstackclient/compute/v2/server_group.py b/openstackclient/compute/v2/server_group.py index c6e2161f60..c49a552f2c 100644 --- a/openstackclient/compute/v2/server_group.py +++ b/openstackclient/compute/v2/server_group.py @@ -55,11 +55,12 @@ def get_parser(self, prog_name): parser.add_argument( '--policy', metavar='', - choices=['affinity', 'anti-affinity'], default='affinity', help=_("Add a policy to " "('affinity' or 'anti-affinity', " - "default to 'affinity')") + "defaults to 'affinity'). Specify --os-compute-api-version " + "2.15 or higher for the 'soft-affinity' or " + "'soft-anti-affinity' policy.") ) return parser diff --git a/openstackclient/tests/unit/compute/v2/test_server_group.py b/openstackclient/tests/unit/compute/v2/test_server_group.py index 088497da30..dc924e2421 100644 --- a/openstackclient/tests/unit/compute/v2/test_server_group.py +++ b/openstackclient/tests/unit/compute/v2/test_server_group.py @@ -63,11 +63,11 @@ def setUp(self): def test_server_group_create(self): arglist = [ - '--policy', 'anti-affinity', + '--policy', 'soft-anti-affinity', 'affinity_group', ] verifylist = [ - ('policy', 'anti-affinity'), + ('policy', 'soft-anti-affinity'), ('name', 'affinity_group'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) From 12ee1861085144f43e6e535f82ba5d9b5d9a5632 Mon Sep 17 00:00:00 2001 From: Jose Castro Leon Date: Wed, 25 Oct 2017 15:39:44 +0200 Subject: [PATCH 1835/3095] Add support for endpoing filter commands Implements the commands that allow to link and endpoint to a project for endpoint filter management. Implements: blueprint keystone-endpoint-filter Change-Id: Iecf61495664fb8413d35ef69f07ea929d190d002 --- doc/source/cli/command-objects/endpoint.rst | 79 ++++++++++ openstackclient/identity/v3/endpoint.py | 147 ++++++++++++++++-- .../tests/functional/identity/v3/common.py | 1 + .../functional/identity/v3/test_endpoint.py | 42 +++++ .../tests/unit/identity/v3/fakes.py | 27 ++++ .../tests/unit/identity/v3/test_endpoint.py | 139 +++++++++++++++++ ...tone-endpoint-filter-e930a7b72276fa2c.yaml | 5 + setup.cfg | 4 +- 8 files changed, 426 insertions(+), 18 deletions(-) create mode 100644 releasenotes/notes/keystone-endpoint-filter-e930a7b72276fa2c.yaml diff --git a/doc/source/cli/command-objects/endpoint.rst b/doc/source/cli/command-objects/endpoint.rst index 02a75bea1f..030947c229 100644 --- a/doc/source/cli/command-objects/endpoint.rst +++ b/doc/source/cli/command-objects/endpoint.rst @@ -4,6 +4,34 @@ endpoint Identity v2, v3 +endpoint add project +-------------------- + +Associate a project to and endpoint for endpoint filtering + +.. program:: endpoint add project +.. code:: bash + + openstack endpoint add project + [--project-domain ] + + + +.. option:: --project-domain + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + +.. _endpoint_add_project-endpoint: +.. describe:: + + Endpoint to associate with specified project (name or ID) + +.. _endpoint_add_project-project: +.. describe:: + + Project to associate with specified endpoint (name or ID) + endpoint create --------------- @@ -107,6 +135,8 @@ List endpoints [--interface ] [--region ] [--long] + [--endpoint | + --project [--project-domain ]] .. option:: --service @@ -132,6 +162,55 @@ List endpoints *Identity version 2 only* +.. option:: --endpoint + + List projects that have access to that endpoint using + endpoint filtering + + *Identity version 3 only* + +.. option:: --project + + List endpoints available for the project using + endpoint filtering + + *Identity version 3 only* + +.. option:: --project-domain + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + + *Identity version 3 only* + +endpoint remove project +----------------------- + +Dissociate a project from an endpoint. + +.. program:: endpoint remove project +.. code:: bash + + openstack endpoint remove project + [--project-domain ] + + + +.. option:: --project-domain + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + +.. _endpoint_remove_project-endpoint: +.. describe:: + + Endpoint to dissociate with specified project (name or ID) + +.. _endpoint_remove_project-project: +.. describe:: + + Project to dissociate with specified endpoint (name or ID) + endpoint set ------------ diff --git a/openstackclient/identity/v3/endpoint.py b/openstackclient/identity/v3/endpoint.py index 3b4dd0de44..3229240e4a 100644 --- a/openstackclient/identity/v3/endpoint.py +++ b/openstackclient/identity/v3/endpoint.py @@ -36,6 +36,42 @@ def get_service_name(service): return '' +class AddProjectToEndpoint(command.Command): + _description = _("Associate a project to an endpoint") + + def get_parser(self, prog_name): + parser = super( + AddProjectToEndpoint, self).get_parser(prog_name) + parser.add_argument( + 'endpoint', + metavar='', + help=_('Endpoint to associate with ' + 'specified project (name or ID)'), + ) + parser.add_argument( + 'project', + metavar='', + help=_('Project to associate with ' + 'specified endpoint name or ID)'), + ) + common.add_project_domain_option_to_parser(parser) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.identity + + endpoint = utils.find_resource(client.endpoints, + parsed_args.endpoint) + + project = common.find_project(client, + parsed_args.project, + parsed_args.project_domain) + + client.endpoint_filter.add_endpoint_to_project( + project=project.id, + endpoint=endpoint.id) + + class CreateEndpoint(command.ShowOne): _description = _("Create new endpoint") @@ -152,27 +188,68 @@ def get_parser(self, prog_name): metavar='', help=_('Filter by region ID'), ) + list_group = parser.add_mutually_exclusive_group() + list_group.add_argument( + '--endpoint', + metavar='', + help=_('Endpoint to list filters'), + ) + list_group.add_argument( + '--project', + metavar='', + help=_('Project to list filters (name or ID)'), + ) + common.add_project_domain_option_to_parser(list_group) return parser def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - columns = ('ID', 'Region', 'Service Name', 'Service Type', - 'Enabled', 'Interface', 'URL') - kwargs = {} - if parsed_args.service: - service = common.find_service(identity_client, parsed_args.service) - kwargs['service'] = service.id - if parsed_args.interface: - kwargs['interface'] = parsed_args.interface - if parsed_args.region: - kwargs['region'] = parsed_args.region - data = identity_client.endpoints.list(**kwargs) - service_list = identity_client.services.list() - - for ep in data: - service = common.find_service_in_list(service_list, ep.service_id) - ep.service_name = get_service_name(service) - ep.service_type = service.type + + endpoint = None + if parsed_args.endpoint: + endpoint = utils.find_resource(identity_client.endpoints, + parsed_args.endpoint) + project = None + if parsed_args.project: + project = common.find_project(identity_client, + parsed_args.project, + parsed_args.project_domain) + + if endpoint: + columns = ('ID', 'Name') + data = ( + identity_client.endpoint_filter + .list_projects_for_endpoint(endpoint=endpoint.id) + ) + else: + columns = ('ID', 'Region', 'Service Name', 'Service Type', + 'Enabled', 'Interface', 'URL') + kwargs = {} + if parsed_args.service: + service = common.find_service(identity_client, + parsed_args.service) + kwargs['service'] = service.id + if parsed_args.interface: + kwargs['interface'] = parsed_args.interface + if parsed_args.region: + kwargs['region'] = parsed_args.region + + if project: + data = ( + identity_client.endpoint_filter + .list_endpoints_for_project(project=project.id) + ) + else: + data = identity_client.endpoints.list(**kwargs) + + service_list = identity_client.services.list() + + for ep in data: + service = common.find_service_in_list(service_list, + ep.service_id) + ep.service_name = get_service_name(service) + ep.service_type = service.type + return (columns, (utils.get_item_properties( s, columns, @@ -180,6 +257,42 @@ def take_action(self, parsed_args): ) for s in data)) +class RemoveProjectFromEndpoint(command.Command): + _description = _("Dissociate a project from an endpoint") + + def get_parser(self, prog_name): + parser = super( + RemoveProjectFromEndpoint, self).get_parser(prog_name) + parser.add_argument( + 'endpoint', + metavar='', + help=_('Endpoint to dissociate from ' + 'specified project (name or ID)'), + ) + parser.add_argument( + 'project', + metavar='', + help=_('Project to dissociate from ' + 'specified endpoint name or ID)'), + ) + common.add_project_domain_option_to_parser(parser) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.identity + + endpoint = utils.find_resource(client.endpoints, + parsed_args.endpoint) + + project = common.find_project(client, + parsed_args.project, + parsed_args.project_domain) + + client.endpoint_filter.delete_endpoint_from_project( + project=project.id, + endpoint=endpoint.id) + + class SetEndpoint(command.Command): _description = _("Set endpoint properties") diff --git a/openstackclient/tests/functional/identity/v3/common.py b/openstackclient/tests/functional/identity/v3/common.py index 6d7896d8fe..33cb5d8602 100644 --- a/openstackclient/tests/functional/identity/v3/common.py +++ b/openstackclient/tests/functional/identity/v3/common.py @@ -42,6 +42,7 @@ class IdentityTests(base.TestCase): REGION_LIST_HEADERS = ['Region', 'Parent Region', 'Description'] ENDPOINT_LIST_HEADERS = ['ID', 'Region', 'Service Name', 'Service Type', 'Enabled', 'Interface', 'URL'] + ENDPOINT_LIST_PROJECT_HEADERS = ['ID', 'Name'] IDENTITY_PROVIDER_FIELDS = ['description', 'enabled', 'id', 'remote_ids', 'domain_id'] diff --git a/openstackclient/tests/functional/identity/v3/test_endpoint.py b/openstackclient/tests/functional/identity/v3/test_endpoint.py index 22dc1b659e..41f0b4c80e 100644 --- a/openstackclient/tests/functional/identity/v3/test_endpoint.py +++ b/openstackclient/tests/functional/identity/v3/test_endpoint.py @@ -42,6 +42,29 @@ def test_endpoint_list(self): items = self.parse_listing(raw_output) self.assert_table_structure(items, self.ENDPOINT_LIST_HEADERS) + def test_endpoint_list_filter(self): + endpoint_id = self._create_dummy_endpoint(add_clean_up=False) + project_id = self._create_dummy_project(add_clean_up=False) + raw_output = self.openstack( + 'endpoint add project ' + '%(endpoint_id)s ' + '%(project_id)s' % { + 'project_id': project_id, + 'endpoint_id': endpoint_id}) + self.assertEqual(0, len(raw_output)) + raw_output = self.openstack( + 'endpoint list --endpoint %s' % endpoint_id) + self.assertIn(project_id, raw_output) + items = self.parse_listing(raw_output) + self.assert_table_structure(items, + self.ENDPOINT_LIST_PROJECT_HEADERS) + + raw_output = self.openstack( + 'endpoint list --project %s' % project_id) + self.assertIn(endpoint_id, raw_output) + items = self.parse_listing(raw_output) + self.assert_table_structure(items, self.ENDPOINT_LIST_HEADERS) + def test_endpoint_set(self): endpoint_id = self._create_dummy_endpoint() new_endpoint_url = data_utils.rand_url() @@ -65,3 +88,22 @@ def test_endpoint_show(self): raw_output = self.openstack('endpoint show %s' % endpoint_id) items = self.parse_show(raw_output) self.assert_show_fields(items, self.ENDPOINT_FIELDS) + + def test_endpoint_add_remove_project(self): + endpoint_id = self._create_dummy_endpoint(add_clean_up=False) + project_id = self._create_dummy_project(add_clean_up=False) + raw_output = self.openstack( + 'endpoint add project ' + '%(endpoint_id)s ' + '%(project_id)s' % { + 'project_id': project_id, + 'endpoint_id': endpoint_id}) + self.assertEqual(0, len(raw_output)) + + raw_output = self.openstack( + 'endpoint remove project ' + '%(endpoint_id)s ' + '%(project_id)s' % { + 'project_id': project_id, + 'endpoint_id': endpoint_id}) + self.assertEqual(0, len(raw_output)) diff --git a/openstackclient/tests/unit/identity/v3/fakes.py b/openstackclient/tests/unit/identity/v3/fakes.py index 7de251524a..3e2caf01d5 100644 --- a/openstackclient/tests/unit/identity/v3/fakes.py +++ b/openstackclient/tests/unit/identity/v3/fakes.py @@ -493,6 +493,8 @@ def __init__(self, **kwargs): self.credentials.resource_class = fakes.FakeResource(None, {}) self.endpoints = mock.Mock() self.endpoints.resource_class = fakes.FakeResource(None, {}) + self.endpoint_filter = mock.Mock() + self.endpoint_filter.resource_class = fakes.FakeResource(None, {}) self.groups = mock.Mock() self.groups.resource_class = fakes.FakeResource(None, {}) self.oauth1 = mock.Mock() @@ -911,6 +913,31 @@ def create_one_endpoint(attrs=None): loaded=True) return endpoint + @staticmethod + def create_one_endpoint_filter(attrs=None): + """Create a fake endpoint project relationship. + + :param Dictionary attrs: + A dictionary with all attributes of endpoint filter + :return: + A FakeResource object with project, endpoint and so on + """ + attrs = attrs or {} + + # Set default attribute + endpoint_filter_info = { + 'project': 'project-id-' + uuid.uuid4().hex, + 'endpoint': 'endpoint-id-' + uuid.uuid4().hex, + } + + # Overwrite default attributes if there are some attributes set + endpoint_filter_info.update(attrs) + + endpoint_filter = fakes.FakeModel( + copy.deepcopy(endpoint_filter_info)) + + return endpoint_filter + class FakeService(object): """Fake one or more service.""" diff --git a/openstackclient/tests/unit/identity/v3/test_endpoint.py b/openstackclient/tests/unit/identity/v3/test_endpoint.py index fad53fcb81..bfe930d6c7 100644 --- a/openstackclient/tests/unit/identity/v3/test_endpoint.py +++ b/openstackclient/tests/unit/identity/v3/test_endpoint.py @@ -22,11 +22,23 @@ def setUp(self): # Get a shortcut to the EndpointManager Mock self.endpoints_mock = self.app.client_manager.identity.endpoints self.endpoints_mock.reset_mock() + self.ep_filter_mock = ( + self.app.client_manager.identity.endpoint_filter + ) + self.ep_filter_mock.reset_mock() # Get a shortcut to the ServiceManager Mock self.services_mock = self.app.client_manager.identity.services self.services_mock.reset_mock() + # Get a shortcut to the DomainManager Mock + self.domains_mock = self.app.client_manager.identity.domains + self.domains_mock.reset_mock() + + # Get a shortcut to the ProjectManager Mock + self.projects_mock = self.app.client_manager.identity.projects + self.projects_mock.reset_mock() + class TestEndpointCreate(TestEndpoint): @@ -750,3 +762,130 @@ def setUp(self): # Get the command object to test self.cmd = endpoint.ShowEndpoint(self.app, None) + + +class TestAddProjectToEndpoint(TestEndpoint): + + project = identity_fakes.FakeProject.create_one_project() + domain = identity_fakes.FakeDomain.create_one_domain() + service = identity_fakes.FakeService.create_one_service() + endpoint = identity_fakes.FakeEndpoint.create_one_endpoint( + attrs={'service_id': service.id}) + + new_ep_filter = identity_fakes.FakeEndpoint.create_one_endpoint_filter( + attrs={'endpoint': endpoint.id, + 'project': project.id} + ) + + def setUp(self): + super(TestAddProjectToEndpoint, self).setUp() + + # This is the return value for utils.find_resource() + self.endpoints_mock.get.return_value = self.endpoint + + # Update the image_id in the MEMBER dict + self.ep_filter_mock.create.return_value = self.new_ep_filter + self.projects_mock.get.return_value = self.project + self.domains_mock.get.return_value = self.domain + # Get the command object to test + self.cmd = endpoint.AddProjectToEndpoint(self.app, None) + + def test_add_project_to_endpoint_no_option(self): + arglist = [ + self.endpoint.id, + self.project.id, + ] + verifylist = [ + ('endpoint', self.endpoint.id), + ('project', self.project.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.ep_filter_mock.add_endpoint_to_project.assert_called_with( + project=self.project.id, + endpoint=self.endpoint.id + ) + self.assertIsNone(result) + + def test_add_project_to_endpoint_with_option(self): + arglist = [ + self.endpoint.id, + self.project.id, + '--project-domain', self.domain.id, + ] + verifylist = [ + ('endpoint', self.endpoint.id), + ('project', self.project.id), + ('project_domain', self.domain.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.ep_filter_mock.add_endpoint_to_project.assert_called_with( + project=self.project.id, + endpoint=self.endpoint.id + ) + self.assertIsNone(result) + + +class TestRemoveProjectEndpoint(TestEndpoint): + + project = identity_fakes.FakeProject.create_one_project() + domain = identity_fakes.FakeDomain.create_one_domain() + service = identity_fakes.FakeService.create_one_service() + endpoint = identity_fakes.FakeEndpoint.create_one_endpoint( + attrs={'service_id': service.id}) + + def setUp(self): + super(TestRemoveProjectEndpoint, self).setUp() + + # This is the return value for utils.find_resource() + self.endpoints_mock.get.return_value = self.endpoint + + self.projects_mock.get.return_value = self.project + self.domains_mock.get.return_value = self.domain + self.ep_filter_mock.delete.return_value = None + + # Get the command object to test + self.cmd = endpoint.RemoveProjectFromEndpoint(self.app, None) + + def test_remove_project_endpoint_no_options(self): + arglist = [ + self.endpoint.id, + self.project.id, + ] + verifylist = [ + ('endpoint', self.endpoint.id), + ('project', self.project.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.ep_filter_mock.delete_endpoint_from_project.assert_called_with( + project=self.project.id, + endpoint=self.endpoint.id, + ) + self.assertIsNone(result) + + def test_remove_project_endpoint_with_options(self): + arglist = [ + self.endpoint.id, + self.project.id, + '--project-domain', self.domain.id, + ] + verifylist = [ + ('endpoint', self.endpoint.id), + ('project', self.project.id), + ('project_domain', self.domain.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.ep_filter_mock.delete_endpoint_from_project.assert_called_with( + project=self.project.id, + endpoint=self.endpoint.id, + ) + self.assertIsNone(result) diff --git a/releasenotes/notes/keystone-endpoint-filter-e930a7b72276fa2c.yaml b/releasenotes/notes/keystone-endpoint-filter-e930a7b72276fa2c.yaml new file mode 100644 index 0000000000..5a633eed83 --- /dev/null +++ b/releasenotes/notes/keystone-endpoint-filter-e930a7b72276fa2c.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``endpoint add project``, ``endpoint remove project`` and ``endpoint + list`` commands to manage endpoint filters in identity v3. diff --git a/setup.cfg b/setup.cfg index 97333f61d1..fd8eb5c274 100644 --- a/setup.cfg +++ b/setup.cfg @@ -227,11 +227,13 @@ openstack.identity.v3 = ec2_credentials_list = openstackclient.identity.v3.ec2creds:ListEC2Creds ec2_credentials_show = openstackclient.identity.v3.ec2creds:ShowEC2Creds + endpoint_add_project = openstackclient.identity.v3.endpoint:AddProjectToEndpoint endpoint_create = openstackclient.identity.v3.endpoint:CreateEndpoint endpoint_delete = openstackclient.identity.v3.endpoint:DeleteEndpoint + endpoint_list = openstackclient.identity.v3.endpoint:ListEndpoint + endpoint_remove_project = openstackclient.identity.v3.endpoint:RemoveProjectFromEndpoint endpoint_set = openstackclient.identity.v3.endpoint:SetEndpoint endpoint_show = openstackclient.identity.v3.endpoint:ShowEndpoint - endpoint_list = openstackclient.identity.v3.endpoint:ListEndpoint group_add_user = openstackclient.identity.v3.group:AddUserToGroup group_contains_user = openstackclient.identity.v3.group:CheckUserInGroup From cc52022447003415575f1013cac2e28d2790e1ee Mon Sep 17 00:00:00 2001 From: Brian Haley Date: Tue, 21 Nov 2017 10:02:46 -0500 Subject: [PATCH 1836/3095] Fix file mode on network-topology.rst doc/source/contributor/specs/network-topology.rst was executable, change mode to 0644. Change-Id: I7e9c8a86ee8563f47da3f59c189b5bd5f3846344 --- doc/source/contributor/specs/network-topology.rst | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 doc/source/contributor/specs/network-topology.rst diff --git a/doc/source/contributor/specs/network-topology.rst b/doc/source/contributor/specs/network-topology.rst old mode 100755 new mode 100644 From 4cff01a2be9cf42bd09f212a709b5edc905e025e Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Tue, 21 Nov 2017 15:52:35 +0000 Subject: [PATCH 1837/3095] Updated from global requirements Change-Id: I6d09649d109445bee8ec541a1366dfcf49fd6563 --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index ffd92041d2..fcd7bc7803 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -38,7 +38,7 @@ python-muranoclient>=0.8.2 # Apache-2.0 python-neutronclient>=6.3.0 # Apache-2.0 python-octaviaclient>=1.0.0 # Apache-2.0 python-rsdclient>=0.1.0 # Apache-2.0 -python-saharaclient>=1.2.0 # Apache-2.0 +python-saharaclient>=1.4.0 # Apache-2.0 python-searchlightclient>=1.0.0 #Apache-2.0 python-senlinclient>=1.1.0 # Apache-2.0 python-troveclient>=2.2.0 # Apache-2.0 From 0626f95579357918e51d46e4e29720854540bfd9 Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Fri, 24 Nov 2017 22:01:06 +0000 Subject: [PATCH 1838/3095] Allow port list to shown undefined attributes At now, OSC command implementation extracts resource attributes based on a predefined column list, so if a user specifies an unknown attribute not defined in the column lists, such attribute will be ignored. In case of 'port list', the neutron port defines many attributes and it is not a good idea to show all attributes even in the long mode from the perspective of user experience. This commit consumes osc_lib.utils.calculate_headers_and_attrs() function to show undefined port attributes if requested in -c option. Closes-Bug: #1707848 Depends-On: I6c6bc3c6e3c769c96869fd76b9d9c1661280850e Change-Id: I130a6aed41d80603698b6cab0c9a1d1dc59df743 --- openstackclient/network/v2/port.py | 6 ++++-- .../tests/functional/network/v2/test_port.py | 10 ++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 4b23b339c9..21f30c4160 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -584,9 +584,11 @@ def take_action(self, parsed_args): data = network_client.ports(**filters) - return (column_headers, + headers, attrs = utils.calculate_header_and_attrs( + column_headers, columns, parsed_args) + return (headers, (utils.get_item_properties( - s, columns, + s, attrs, formatters=_formatters, ) for s in data)) diff --git a/openstackclient/tests/functional/network/v2/test_port.py b/openstackclient/tests/functional/network/v2/test_port.py index a705979028..7357c0ed54 100644 --- a/openstackclient/tests/functional/network/v2/test_port.py +++ b/openstackclient/tests/functional/network/v2/test_port.py @@ -131,6 +131,16 @@ def test_port_list(self): self.assertNotIn(mac1, item_map.values()) self.assertIn(mac2, item_map.values()) + # Test list with unknown fields + json_output = json.loads(self.openstack( + 'port list -f json -c ID -c Name -c device_id' + )) + id_list = [p['ID'] for p in json_output] + self.assertIn(id1, id_list) + self.assertIn(id2, id_list) + # Check an unknown field exists + self.assertIn('device_id', json_output[0]) + def test_port_set(self): """Test create, set, show, delete""" name = uuid.uuid4().hex From 5176951d592582881be829ffe1bc0ebb49837fdf Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Sun, 26 Nov 2017 14:50:55 -0600 Subject: [PATCH 1839/3095] Make osc-tox-unit-tips work on other repos We want to run osc-tox-unit-tips on changes to os-client-config and python-openstacksdk - but the tox role defaults to using zuul.project.src_dir as the working directory. We want it to always be the openstackclient source dir. Change-Id: Ic7a49b79fb9141d9d0b8da40e10c85b107564edc --- .zuul.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.zuul.yaml b/.zuul.yaml index 9b22ace0ec..6060f7d925 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -17,6 +17,9 @@ - openstack/python-openstacksdk vars: tox_envlist: unit-tips + # Set work dir to openstackclient so that if it's triggered by one of the + # other repos the tests will run in the same place + zuul_work_dir: src/git.openstack.org/openstack/python-openstackclient - job: name: osc-functional-devstack-base From c778c7d6314d497da53c0dc81574e2e659740e17 Mon Sep 17 00:00:00 2001 From: Zane Bitter Date: Tue, 28 Nov 2017 18:16:01 -0500 Subject: [PATCH 1840/3095] Set correct designate endpoint in docs Change-Id: Id2ecf65f3e5afef9b6231565cac2d797650e38a7 Closes-Bug: #1726726 --- doc/source/cli/plugin-commands.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/cli/plugin-commands.rst b/doc/source/cli/plugin-commands.rst index 6942cd09d7..ae2e655712 100644 --- a/doc/source/cli/plugin-commands.rst +++ b/doc/source/cli/plugin-commands.rst @@ -32,7 +32,7 @@ congress designate --------- -.. list-plugins:: openstack.dns.v1 +.. list-plugins:: openstack.dns.v2 :detailed: gnocchi From 3a672eae7be0dcf5e2e951ea958d5d157699c341 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 29 Nov 2017 14:28:24 -0600 Subject: [PATCH 1841/3095] Release note cleanup Change-Id: Iea05a9b696a225b41bb5bf0c52498969e32acf66 --- releasenotes/notes/bug-1711301-472b577f074edd43.yaml | 6 ++++++ releasenotes/notes/bug-1732938-e4d91732ef777f9a.yaml | 7 +++++++ 2 files changed, 13 insertions(+) create mode 100644 releasenotes/notes/bug-1711301-472b577f074edd43.yaml create mode 100644 releasenotes/notes/bug-1732938-e4d91732ef777f9a.yaml diff --git a/releasenotes/notes/bug-1711301-472b577f074edd43.yaml b/releasenotes/notes/bug-1711301-472b577f074edd43.yaml new file mode 100644 index 0000000000..ba1a8a6dad --- /dev/null +++ b/releasenotes/notes/bug-1711301-472b577f074edd43.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Fix occurrences of the ``network agent delete`` command failing with newer + releases of python-openstacksdk. + [Bug `1711301 `_] diff --git a/releasenotes/notes/bug-1732938-e4d91732ef777f9a.yaml b/releasenotes/notes/bug-1732938-e4d91732ef777f9a.yaml new file mode 100644 index 0000000000..617dca91e4 --- /dev/null +++ b/releasenotes/notes/bug-1732938-e4d91732ef777f9a.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + Remove the client-side check for valid ``--policy`` values in the + ``server group create`` command. Specify ``--os-compute-api-version 2.15`` + or higher for the ``soft-affinity`` or ``soft-anti-affinity`` policy. + [Bug `1732938 `_] From 2c2c16ba5524155cd187401aa2070af23f8bb772 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 30 Nov 2017 15:30:58 -0600 Subject: [PATCH 1842/3095] Remove a bunch of things we promised to remove in 2H 2017 Change-Id: I060559fe13e354fe87551cd9dd82774bddb54640 --- openstackclient/api/auth.py | 28 ----------- openstackclient/common/command.py | 29 ----------- openstackclient/common/exceptions.py | 28 ----------- openstackclient/common/logs.py | 29 ----------- openstackclient/common/parseractions.py | 28 ----------- openstackclient/common/session.py | 50 ------------------- openstackclient/common/timing.py | 28 ----------- openstackclient/common/utils.py | 28 ----------- .../tests/unit/network/test_common.py | 2 +- 9 files changed, 1 insertion(+), 249 deletions(-) delete mode 100644 openstackclient/api/auth.py delete mode 100644 openstackclient/common/command.py delete mode 100644 openstackclient/common/exceptions.py delete mode 100644 openstackclient/common/logs.py delete mode 100644 openstackclient/common/parseractions.py delete mode 100644 openstackclient/common/session.py delete mode 100644 openstackclient/common/timing.py delete mode 100644 openstackclient/common/utils.py diff --git a/openstackclient/api/auth.py b/openstackclient/api/auth.py deleted file mode 100644 index 7c520f4964..0000000000 --- a/openstackclient/api/auth.py +++ /dev/null @@ -1,28 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - -# NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release -# or Jun 2017. - -import inspect -import sys - -from osc_lib.api.auth import * # noqa - - -parent_import = inspect.getouterframes(inspect.currentframe())[1][1] -sys.stderr.write( - "WARNING: %s is deprecated and will be removed after Jun 2017. " - "Please use osc_lib.api.auth. This warning is caused by an " - "out-of-date import in %s\n" % (__name__, parent_import) -) diff --git a/openstackclient/common/command.py b/openstackclient/common/command.py deleted file mode 100644 index 44954da337..0000000000 --- a/openstackclient/common/command.py +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright 2016 NEC Corporation -# -# 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. - -# NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release -# or Jun 2017. - -import inspect -import sys - -from osc_lib.command.command import * # noqa - - -parent_import = inspect.getouterframes(inspect.currentframe())[1][1] -sys.stderr.write( - "WARNING: %s is deprecated and will be removed after Jun 2017. " - "Please use osc_lib.command.command. This warning is caused by an " - "out-of-date import in %s\n" % (__name__, parent_import) -) diff --git a/openstackclient/common/exceptions.py b/openstackclient/common/exceptions.py deleted file mode 100644 index ed497e7bac..0000000000 --- a/openstackclient/common/exceptions.py +++ /dev/null @@ -1,28 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - -# NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release -# or Jun 2017. - -import inspect -import sys - -from osc_lib.exceptions import * # noqa - - -parent_import = inspect.getouterframes(inspect.currentframe())[1][1] -sys.stderr.write( - "WARNING: %s is deprecated and will be removed after Jun 2017. " - "Please use osc_lib.exceptions. This warning is caused by an " - "out-of-date import in %s\n" % (__name__, parent_import) -) diff --git a/openstackclient/common/logs.py b/openstackclient/common/logs.py deleted file mode 100644 index 24bf07eb39..0000000000 --- a/openstackclient/common/logs.py +++ /dev/null @@ -1,29 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - -# NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release -# or Jun 2017. - -import inspect -import sys - -from osc_lib.logs import * # noqa -from osc_lib.logs import _FileFormatter # noqa - - -parent_import = inspect.getouterframes(inspect.currentframe())[1][1] -sys.stderr.write( - "WARNING: %s is deprecated and will be removed after Jun 2017. " - "Please use osc_lib.logs. This warning is caused by an " - "out-of-date import in %s\n" % (__name__, parent_import) -) diff --git a/openstackclient/common/parseractions.py b/openstackclient/common/parseractions.py deleted file mode 100644 index 3af3a017f7..0000000000 --- a/openstackclient/common/parseractions.py +++ /dev/null @@ -1,28 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - -# NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release -# or Jun 2017. - -import inspect -import sys - -from osc_lib.cli.parseractions import * # noqa - - -parent_import = inspect.getouterframes(inspect.currentframe())[1][1] -sys.stderr.write( - "WARNING: %s is deprecated and will be removed after Jun 2017. " - "Please use osc_lib.cli.parseractions. This warning is caused by an " - "out-of-date import in %s\n" % (__name__, parent_import) -) diff --git a/openstackclient/common/session.py b/openstackclient/common/session.py deleted file mode 100644 index 9b19fd46e9..0000000000 --- a/openstackclient/common/session.py +++ /dev/null @@ -1,50 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - -"""Subclass of keystoneauth1.session""" - -from keystoneauth1 import session - - -class TimingSession(session.Session): - """A Session that supports collection of timing data per Method URL""" - - def __init__( - self, - **kwargs - ): - """Pass through all arguments except timing""" - super(TimingSession, self).__init__(**kwargs) - - # times is a list of tuples: ("method url", elapsed_time) - self.times = [] - - def get_timings(self): - return self.times - - def reset_timings(self): - self.times = [] - - def request(self, url, method, **kwargs): - """Wrap the usual request() method with the timers""" - resp = super(TimingSession, self).request(url, method, **kwargs) - for h in resp.history: - self.times.append(( - "%s %s" % (h.request.method, h.request.url), - h.elapsed, - )) - self.times.append(( - "%s %s" % (resp.request.method, resp.request.url), - resp.elapsed, - )) - return resp diff --git a/openstackclient/common/timing.py b/openstackclient/common/timing.py deleted file mode 100644 index 444f0cb2ea..0000000000 --- a/openstackclient/common/timing.py +++ /dev/null @@ -1,28 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - -# NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release -# or Jun 2017. - -import inspect -import sys - -from osc_lib.command.timing import * # noqa - - -parent_import = inspect.getouterframes(inspect.currentframe())[1][1] -sys.stderr.write( - "WARNING: %s is deprecated and will be removed after Jun 2017. " - "Please use osc_lib.command.timing. This warning is caused by an " - "out-of-date import in %s\n" % (__name__, parent_import) -) diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py deleted file mode 100644 index aeb3aea7e3..0000000000 --- a/openstackclient/common/utils.py +++ /dev/null @@ -1,28 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - -# NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release -# or Jun 2017. - -import inspect -import sys - -from osc_lib.utils import * # noqa - - -parent_import = inspect.getouterframes(inspect.currentframe())[1][1] -sys.stderr.write( - "WARNING: %s is deprecated and will be removed after Jun 2017. " - "Please use osc_lib.utils. This warning is caused by an " - "out-of-date import in %s\n" % (__name__, parent_import) -) diff --git a/openstackclient/tests/unit/network/test_common.py b/openstackclient/tests/unit/network/test_common.py index d4d3a277f4..3a20687824 100644 --- a/openstackclient/tests/unit/network/test_common.py +++ b/openstackclient/tests/unit/network/test_common.py @@ -15,8 +15,8 @@ import mock import openstack +from osc_lib import exceptions -from openstackclient.common import exceptions from openstackclient.network import common from openstackclient.tests.unit import utils From b061b9c34e2fa6ecf0922e056a0fbdad28fa0685 Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Fri, 1 Dec 2017 09:13:15 -0600 Subject: [PATCH 1843/3095] Add cliff and keystoneauth to tips jobs Both of these can severely break openstackclient. Add them to the required-projects list of the tips jobs. We should then add at least osc-tox-unit-tips but maybe also osc-functional-devstack-tips to both cliff and keystoneauth so that it's symmetrical. Change-Id: Ie0f3e9d7e221c9cdd3c5d726148f456246186ff4 --- .zuul.yaml | 4 ++++ tox.ini | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/.zuul.yaml b/.zuul.yaml index 6060f7d925..dd00db2a01 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -11,6 +11,8 @@ - ^doc/.*$ - ^releasenotes/.*$ required-projects: + - openstack/cliff + - openstack/keystoneauth - openstack/os-client-config - openstack/osc-lib - openstack/python-openstackclient @@ -114,6 +116,8 @@ parent: osc-functional-devstack timeout: 7800 required-projects: + - openstack/cliff + - openstack/keystoneauth - openstack/os-client-config - openstack/osc-lib - openstack/python-openstackclient diff --git a/tox.ini b/tox.ini index 5a7850eb44..f94e7d0731 100644 --- a/tox.ini +++ b/tox.ini @@ -63,6 +63,8 @@ setenv = VIRTUAL_ENV={envdir} OS_TEST_TIMEOUT=60 deps = -r{toxinidir}/test-requirements.txt commands = + pip install -q -U -e "git+file://{toxinidir}/../cliff#egg=cliff" + pip install -q -U -e "git+file://{toxinidir}/../keystoneauth#egg=keystoneauth" pip install -q -U -e "git+file://{toxinidir}/../osc-lib#egg=osc_lib" pip install -q -U -e "git+file://{toxinidir}/../os-client-config#egg=os_client_config" pip install -q -U -e "git+file://{toxinidir}/../python-openstacksdk#egg=openstacksdk" @@ -82,6 +84,8 @@ setenv = OS_TEST_PATH=./openstackclient/tests/functional passenv = OS_* whitelist_externals = openstackclient/tests/functional/run_ostestr.sh commands = + pip install -q -U -e "git+file://{toxinidir}/../cliff#egg=cliff" + pip install -q -U -e "git+file://{toxinidir}/../keystoneauth#egg=keystoneauth" pip install -q -U -e "git+file://{toxinidir}/../osc-lib#egg=osc_lib" pip install -q -U -e "git+file://{toxinidir}/../os-client-config#egg=os_client_config" pip install -q -U -e "git+file://{toxinidir}/../python-openstacksdk#egg=openstacksdk" From 01d3b42a060af1027e2bb0d7bbe3c53979056d7e Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Fri, 1 Dec 2017 10:13:05 -0600 Subject: [PATCH 1844/3095] Make py27 and py35 versions and template of unit-tips jobs Make py27 and py35 versions of the tips job, and a project-template to hold them so it's easy for other projects to use both (or more, we ever add them) The base tox job knows how to install siblings based on required-projects, which makes it easy to piggyback on them. Remove the irrelevant-files sections, as these are specified in the base openstack-tox jobs. Using the unit-tips or functional-tips tox env is no longer neccessary. For the gate it's actually important to not do the sibling processing by hand in tox as the base tox job version of the logic does all the right things to deal with constraints and whatnot. Leave them for local developer convenience. Leave the osc-tox-unit-tips job for now, since there are some other repos using it. Once we switch them to the template, we can remove the job. Change-Id: I599b18218c10cb08e508cca3b3bbc9c88b8f809c --- .zuul.yaml | 69 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 57 insertions(+), 12 deletions(-) diff --git a/.zuul.yaml b/.zuul.yaml index dd00db2a01..98446e6870 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -4,12 +4,50 @@ description: | Run unit tests for OpenStackClient with master branch of important libs. - Uses tox with the ``unit-tips`` environment and master branch of - the required-projects below. - irrelevant-files: - - ^.*\.rst$ - - ^doc/.*$ - - ^releasenotes/.*$ + Takes advantage of the base tox job's install-siblings feature. + required-projects: + - openstack/cliff + - openstack/keystoneauth + - openstack/os-client-config + - openstack/osc-lib + - openstack/python-openstackclient + - openstack/python-openstacksdk + vars: + tox_envlist: py27 + # Set work dir to openstackclient so that if it's triggered by one of the + # other repos the tests will run in the same place + zuul_work_dir: src/git.openstack.org/openstack/python-openstackclient + +- job: + name: osc-tox-py27-tips + parent: openstack-tox-py27 + description: | + Run unit tests for OpenStackClient with master branch of important libs. + + Takes advantage of the base tox job's install-siblings feature. + # The job only tests the latest and shouldn't be run on the stable branches + branches: ^(?!stable) + required-projects: + - openstack/cliff + - openstack/keystoneauth + - openstack/os-client-config + - openstack/osc-lib + - openstack/python-openstackclient + - openstack/python-openstacksdk + vars: + # Set work dir to openstackclient so that if it's triggered by one of the + # other repos the tests will run in the same place + zuul_work_dir: src/git.openstack.org/openstack/python-openstackclient + +- job: + name: osc-tox-py35-tips + parent: openstack-tox-py35 + description: | + Run unit tests for OpenStackClient with master branch of important libs. + + Takes advantage of the base tox job's install-siblings feature. + # The job only tests the latest and shouldn't be run on the stable branches + branches: ^(?!stable) required-projects: - openstack/cliff - openstack/keystoneauth @@ -18,7 +56,6 @@ - openstack/python-openstackclient - openstack/python-openstacksdk vars: - tox_envlist: unit-tips # Set work dir to openstackclient so that if it's triggered by one of the # other repos the tests will run in the same place zuul_work_dir: src/git.openstack.org/openstack/python-openstackclient @@ -137,18 +174,26 @@ s-container: false s-object: false s-proxy: false - tox_envlist: functional-tips + tox_envlist: functional + +- project-template: + name: osc-tox-unit-tips + check: + jobs: + - osc-tox-py27-tips + - osc-tox-py35-tips + gate: + jobs: + - osc-tox-py27-tips + - osc-tox-py35-tips - project: name: openstack/python-openstackclient templates: - openstackclient-plugin-jobs + - osc-tox-unit-tips check: jobs: - - osc-tox-unit-tips: - # The functional-tips job only tests the latest and shouldn't be run - # on the stable branches - branches: ^(?!stable) - osc-functional-devstack # - osc-functional-devstack-n-net: # voting: false From 8b32b53a0f9ab7aff351702ba65425a314045800 Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Fri, 1 Dec 2017 10:22:39 -0600 Subject: [PATCH 1845/3095] Avoid tox_install.sh for constraints support We do not need tox_install.sh, pip can handle constraints itself and install the project correctly. Thus update tox.ini and remove the now obsolete tools/tox_install.sh file. This follows https://review.openstack.org/#/c/508061 to remove tools/tox_install.sh. Change-Id: Ie7c06ead39c8597ec9326f223625d1fa0d5208d1 --- tools/tox_install.sh | 55 -------------------------------------------- tox.ini | 16 ++++--------- 2 files changed, 5 insertions(+), 66 deletions(-) delete mode 100755 tools/tox_install.sh diff --git a/tools/tox_install.sh b/tools/tox_install.sh deleted file mode 100755 index 4af3be166d..0000000000 --- a/tools/tox_install.sh +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env bash - -# Client constraint file contains this client version pin that is in conflict -# with installing the client from source. We should replace the version pin in -# the constraints file before applying it for from-source installation. - -ZUUL_CLONER=/usr/zuul-env/bin/zuul-cloner -BRANCH_NAME=master -CLIENT_NAME=python-openstackclient -requirements_installed=$(echo "import openstack_requirements" | python 2>/dev/null ; echo $?) - -set -e - -CONSTRAINTS_FILE=$1 -shift - -install_cmd="pip install" -mydir=$(mktemp -dt "$CLIENT_NAME-tox_install-XXXXXXX") -trap "rm -rf $mydir" EXIT -localfile=$mydir/upper-constraints.txt -if [[ $CONSTRAINTS_FILE != http* ]]; then - CONSTRAINTS_FILE=file://$CONSTRAINTS_FILE -fi -curl $CONSTRAINTS_FILE -k -o $localfile -install_cmd="$install_cmd -c$localfile" - -if [ $requirements_installed -eq 0 ]; then - echo "ALREADY INSTALLED" > /tmp/tox_install.txt - echo "Requirements already installed; using existing package" -elif [ -x "$ZUUL_CLONER" ]; then - echo "ZUUL CLONER" > /tmp/tox_install.txt - pushd $mydir - $ZUUL_CLONER --cache-dir \ - /opt/git \ - --branch $BRANCH_NAME \ - git://git.openstack.org \ - openstack/requirements - cd openstack/requirements - $install_cmd -e . - popd -else - echo "PIP HARDCODE" > /tmp/tox_install.txt - if [ -z "$REQUIREMENTS_PIP_LOCATION" ]; then - REQUIREMENTS_PIP_LOCATION="git+https://git.openstack.org/openstack/requirements@$BRANCH_NAME#egg=requirements" - fi - $install_cmd -U -e ${REQUIREMENTS_PIP_LOCATION} -fi - -# This is the main purpose of the script: Allow local installation of -# the current repo. It is listed in constraints file and thus any -# install will be constrained and we need to unconstrain it. -edit-constraints $localfile -- $CLIENT_NAME "-e file://$PWD#egg=$CLIENT_NAME" - -$install_cmd -U $* -exit $? diff --git a/tox.ini b/tox.ini index f94e7d0731..2bf7c6b29a 100644 --- a/tox.ini +++ b/tox.ini @@ -5,13 +5,15 @@ skipdist = True [testenv] usedevelop = True -install_command = - {toxinidir}/tools/tox_install.sh {env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages} +install_command = pip install -U {opts} {packages} setenv = VIRTUAL_ENV={envdir} OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 OS_TEST_TIMEOUT=60 -deps = -r{toxinidir}/test-requirements.txt +deps = + -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} + -r{toxinidir}/test-requirements.txt + -r{toxinidir}/requirements.txt commands = ostestr {posargs} whitelist_externals = ostestr @@ -54,14 +56,6 @@ commands = bandit -r openstackclient -x tests -s B105,B106,B107,B401,B404,B603,B606,B607,B110,B605,B101 [testenv:unit-tips] -usedevelop = True -install_command = - {toxinidir}/tools/tox_install.sh {env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages} -setenv = VIRTUAL_ENV={envdir} - OS_STDOUT_CAPTURE=1 - OS_STDERR_CAPTURE=1 - OS_TEST_TIMEOUT=60 -deps = -r{toxinidir}/test-requirements.txt commands = pip install -q -U -e "git+file://{toxinidir}/../cliff#egg=cliff" pip install -q -U -e "git+file://{toxinidir}/../keystoneauth#egg=keystoneauth" From dab49df461d7ca68001632f11dc5bf1229271de9 Mon Sep 17 00:00:00 2001 From: Carlos Konstanski Date: Fri, 1 Dec 2017 14:58:52 -0700 Subject: [PATCH 1846/3095] openstack subnet create fails when tags is None In network/v2/_tag.py lines 105 and 110: obj.tags can be None, in which case set(obj.tags) throws a NoneType exception. Change-Id: I1e965ec947844cbf84676fab27a2261fc0c0ea49 Closes-Bug: #1735836 --- openstackclient/network/v2/_tag.py | 4 ++-- releasenotes/notes/bug-1735836-9be6d777a6e6410b.yaml | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/bug-1735836-9be6d777a6e6410b.yaml diff --git a/openstackclient/network/v2/_tag.py b/openstackclient/network/v2/_tag.py index d1e59937fa..ce39f35ec0 100644 --- a/openstackclient/network/v2/_tag.py +++ b/openstackclient/network/v2/_tag.py @@ -102,10 +102,10 @@ def update_tags_for_set(client, obj, parsed_args): if parsed_args.no_tag: tags = set() else: - tags = set(obj.tags) + tags = set(obj.tags or []) if parsed_args.tags: tags |= set(parsed_args.tags) - if set(obj.tags) != tags: + if set(obj.tags or []) != tags: client.set_tags(obj, list(tags)) diff --git a/releasenotes/notes/bug-1735836-9be6d777a6e6410b.yaml b/releasenotes/notes/bug-1735836-9be6d777a6e6410b.yaml new file mode 100644 index 0000000000..346048532d --- /dev/null +++ b/releasenotes/notes/bug-1735836-9be6d777a6e6410b.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + ``openstack subnet create`` failed with a NoneType exception when + there were no tags. + [Bug `1735836 `_] From 0bf69a94626805bc03cc057ecf1ae022b403bc8a Mon Sep 17 00:00:00 2001 From: Andreas Jaeger Date: Sat, 2 Dec 2017 19:21:22 +0100 Subject: [PATCH 1847/3095] Remove -U from pip install 'pip install -U' ugrades specified packages, this is not necessary since we use constraints, remove the parameter '-U' from the line. With tools/tox_install.sh - which a previous change of mine removed - the -U was not harmful, but with the current set up, it might cause upgrades, so remove it. Change-Id: I9f818d4b78e7540498a1501be14cd63ac3e891b3 --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 2bf7c6b29a..26c643c82a 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,7 @@ skipdist = True [testenv] usedevelop = True -install_command = pip install -U {opts} {packages} +install_command = pip install {opts} {packages} setenv = VIRTUAL_ENV={envdir} OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 From b0a75d739a1c52e04267c7c9e28da849fe32a7e4 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Mon, 4 Dec 2017 13:18:25 -0600 Subject: [PATCH 1848/3095] Make functional-tips job voting Depends-on: I57cf95763d54ad2060a4ce2af91c3ba18ca04db0 Change-Id: I6cc4421e4b55df84f494ab9bb18092dcc111baeb --- .zuul.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.zuul.yaml b/.zuul.yaml index 98446e6870..c19c7a022b 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -202,7 +202,6 @@ # # is ready and backported # branches: ^(?!stable/(newton|ocata)).*$ - osc-functional-devstack-tips: - voting: false # The functional-tips job only tests the latest and shouldn't be run # on the stable branches branches: ^(?!stable) From 1d914dd9ad9cb9e5d7bba2dab50d9e292c149842 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 29 Nov 2017 11:17:19 -0600 Subject: [PATCH 1849/3095] Fix SDK Connection creation alternative to Profile Do a dummy import to determine which SDK is installed (Pre/post merge). This solves the DevStack error "Cloud defaults was not found" in -tips jobs. Depends-On: Ia111f127fbdceac2afe20fd9d1fe032145cdd72c Change-Id: I60c2d418dd5a393eee2cc2a5c2fdebfffdabf2d3 --- openstackclient/network/client.py | 11 +++++++--- .../tests/unit/integ/cli/test_shell.py | 20 +++++++++++++++---- tox.ini | 2 +- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/openstackclient/network/client.py b/openstackclient/network/client.py index 3566bfe525..6f74303d0f 100644 --- a/openstackclient/network/client.py +++ b/openstackclient/network/client.py @@ -14,10 +14,15 @@ import logging from openstack import connection + + +# NOTE(dtroyer): Attempt an import to detect if the SDK installed is new +# enough to not use Profile. If so, use that. try: - from openstack import profile -except ImportError: + from openstack.config import loader as config # noqa profile = None +except ImportError: + from openstack import profile from osc_lib import utils from openstackclient.i18n import _ @@ -39,7 +44,7 @@ def make_client(instance): if profile is None: # New SDK conn = connection.Connection( - cloud_config=instance._cli_options, + config=instance._cli_options, session=instance.session) else: prof = profile.Profile() diff --git a/openstackclient/tests/unit/integ/cli/test_shell.py b/openstackclient/tests/unit/integ/cli/test_shell.py index 78663fbcda..70303be398 100644 --- a/openstackclient/tests/unit/integ/cli/test_shell.py +++ b/openstackclient/tests/unit/integ/cli/test_shell.py @@ -19,6 +19,16 @@ from openstackclient.tests.unit.integ import base as test_base from openstackclient.tests.unit import test_shell +# NOTE(dtroyer): Attempt the import to detect if the SDK installed is new +# enough to contain the os_client_config code. If so, use +# that path for mocks. +CONFIG_MOCK_BASE = "openstack.config.loader" +try: + from openstack.config import defaults # noqa +except ImportError: + # Fall back to os-client-config + CONFIG_MOCK_BASE = "os_client_config.config" + class TestIntegShellCliV2(test_base.TestInteg): @@ -389,8 +399,8 @@ def setUp(self): test_shell.PUBLIC_1['public-clouds']['megadodo']['auth']['auth_url'] \ = test_base.V3_AUTH_URL - @mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file") - @mock.patch("os_client_config.config.OpenStackConfig._load_config_file") + @mock.patch(CONFIG_MOCK_BASE + ".OpenStackConfig._load_vendor_file") + @mock.patch(CONFIG_MOCK_BASE + ".OpenStackConfig._load_config_file") def test_shell_args_precedence_1(self, config_mock, vendor_mock): """Precedence run 1 @@ -405,6 +415,7 @@ def vendor_mock_return(): return ('file.yaml', copy.deepcopy(test_shell.PUBLIC_1)) vendor_mock.side_effect = vendor_mock_return + print("CONFIG_MOCK_BASE=%s" % CONFIG_MOCK_BASE) _shell = shell.OpenStackShell() _shell.run( "--os-password qaz configuration show".split(), @@ -458,8 +469,8 @@ def vendor_mock_return(): # +env, +cli, +occ # see test_shell_args_precedence_2() - @mock.patch("os_client_config.config.OpenStackConfig._load_vendor_file") - @mock.patch("os_client_config.config.OpenStackConfig._load_config_file") + @mock.patch(CONFIG_MOCK_BASE + ".OpenStackConfig._load_vendor_file") + @mock.patch(CONFIG_MOCK_BASE + ".OpenStackConfig._load_config_file") def test_shell_args_precedence_2(self, config_mock, vendor_mock): """Precedence run 2 @@ -474,6 +485,7 @@ def vendor_mock_return(): return ('file.yaml', copy.deepcopy(test_shell.PUBLIC_1)) vendor_mock.side_effect = vendor_mock_return + print("CONFIG_MOCK_BASE=%s" % CONFIG_MOCK_BASE) _shell = shell.OpenStackShell() _shell.run( "--os-username zarquon --os-password qaz " diff --git a/tox.ini b/tox.ini index 26c643c82a..8ce7ce8c93 100644 --- a/tox.ini +++ b/tox.ini @@ -61,7 +61,7 @@ commands = pip install -q -U -e "git+file://{toxinidir}/../keystoneauth#egg=keystoneauth" pip install -q -U -e "git+file://{toxinidir}/../osc-lib#egg=osc_lib" pip install -q -U -e "git+file://{toxinidir}/../os-client-config#egg=os_client_config" - pip install -q -U -e "git+file://{toxinidir}/../python-openstacksdk#egg=openstacksdk" + pip install -q -e "git+file://{toxinidir}/../python-openstacksdk#egg=openstacksdk" pip freeze ostestr {posargs} whitelist_externals = ostestr From 116526275d0953fda93c7ff8eacd8631c5af68d5 Mon Sep 17 00:00:00 2001 From: Daniel Speichert Date: Tue, 14 Nov 2017 11:14:11 -0500 Subject: [PATCH 1850/3095] Send 'changes-since' instead of 'changes_since' query parameter Per API reference, only 'changes-since' is accepted and the variant with underscore is ignored, making the CLI functionality broken. [dtroyer] added release note and fixed unit tests. Change-Id: I0c596531a8af03da17d5ce39d75b12e941403aa5 Closes-Bug: 1732216 --- openstackclient/compute/v2/server.py | 13 +++++++------ .../tests/unit/compute/v2/test_server.py | 4 ++-- .../notes/bug-1732216-b41bfedebff911e1.yaml | 5 +++++ 3 files changed, 14 insertions(+), 8 deletions(-) create mode 100644 releasenotes/notes/bug-1732216-b41bfedebff911e1.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 034199822d..3b805639e3 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1056,17 +1056,18 @@ def take_action(self, parsed_args): 'all_tenants': parsed_args.all_projects, 'user_id': user_id, 'deleted': parsed_args.deleted, - 'changes_since': parsed_args.changes_since, + 'changes-since': parsed_args.changes_since, } LOG.debug('search options: %s', search_opts) - if search_opts['changes_since']: + if search_opts['changes-since']: try: - timeutils.parse_isotime(search_opts['changes_since']) + timeutils.parse_isotime(search_opts['changes-since']) except ValueError: - raise exceptions.CommandError(_('Invalid changes-since value:' - ' %s') % search_opts['changes' - '_since']) + raise exceptions.CommandError( + _('Invalid changes-since value: %s') % + search_opts['changes-since'] + ) if parsed_args.long: columns = ( diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index ad52e23225..99cdfb7775 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -1584,7 +1584,7 @@ def setUp(self): 'all_tenants': False, 'user_id': None, 'deleted': False, - 'changes_since': None, + 'changes-since': None, } # Default params of the core function of the command in the case of no @@ -1791,7 +1791,7 @@ def test_server_list_with_changes_since(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.search_opts['changes_since'] = '2016-03-04T06:27:59Z' + self.search_opts['changes-since'] = '2016-03-04T06:27:59Z' self.search_opts['deleted'] = True self.servers_mock.list.assert_called_with(**self.kwargs) diff --git a/releasenotes/notes/bug-1732216-b41bfedebff911e1.yaml b/releasenotes/notes/bug-1732216-b41bfedebff911e1.yaml new file mode 100644 index 0000000000..d792f25a28 --- /dev/null +++ b/releasenotes/notes/bug-1732216-b41bfedebff911e1.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Fix the operation of the ``--changes-since`` option to the ``server list`` command. + [Bug `1732216 `_] From 9c23fc8025c0572a2f46f24e1934808229fb110c Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Sat, 23 Dec 2017 10:11:57 +0000 Subject: [PATCH 1851/3095] Updated from global requirements Change-Id: Ieb1a29ba275784f67f0d943fab5f3b59cd9fc28e --- requirements.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index 9299891701..a1c48a53a9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,12 +6,12 @@ six>=1.10.0 # MIT Babel!=2.4.0,>=2.3.4 # BSD cliff!=2.9.0,>=2.8.0 # Apache-2.0 -keystoneauth1>=3.2.0 # Apache-2.0 +keystoneauth1>=3.3.0 # Apache-2.0 openstacksdk>=0.9.19 # Apache-2.0 -osc-lib>=1.7.0 # Apache-2.0 +osc-lib>=1.8.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 -oslo.utils>=3.31.0 # Apache-2.0 +oslo.utils>=3.33.0 # Apache-2.0 python-glanceclient>=2.8.0 # Apache-2.0 python-keystoneclient>=3.8.0 # Apache-2.0 python-novaclient>=9.1.0 # Apache-2.0 -python-cinderclient>=3.2.0 # Apache-2.0 +python-cinderclient>=3.3.0 # Apache-2.0 From c19d982399f11268901d92c2ddd5ebbb2dbf6492 Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Tue, 26 Dec 2017 23:28:14 +0900 Subject: [PATCH 1852/3095] Fix func tests: Ensure to pass OS_CLOUD envvar Our functional tests depend on OS_CLOUD (or other OS_* envvars) to retrieve authentication information. Functional test failure is caused by the lack of OS_CLOUD envvar. This commit updates the job playbook to pass OS_CLOUD. Change-Id: I903ce599082cc923f02e26a2058bbfa7eb9bb2d6 --- playbooks/osc-devstack/run.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/playbooks/osc-devstack/run.yaml b/playbooks/osc-devstack/run.yaml index 22f82096c7..d1101dd4a1 100644 --- a/playbooks/osc-devstack/run.yaml +++ b/playbooks/osc-devstack/run.yaml @@ -1,3 +1,5 @@ - hosts: all + environment: + OS_CLOUD: devstack-admin roles: - tox From bafe5357c2ad8cf9f87bf726db9cd8a3a1825123 Mon Sep 17 00:00:00 2001 From: Masayuki Igawa Date: Wed, 27 Dec 2017 16:16:23 +0900 Subject: [PATCH 1853/3095] Switch to use stestr directly This commit makes to use stestr instead of ostestr directly. ostestr>1.0.0 has started to use stestr instead of testrepository. So there is no reason to use ostestr anymore. Change-Id: I6327d50c9f6dd19f1de24b9b51532104fb3e916e --- .../{run_ostestr.sh => run_stestr.sh} | 2 +- test-requirements.txt | 3 +-- tox.ini | 25 +++++++++++-------- 3 files changed, 17 insertions(+), 13 deletions(-) rename openstackclient/tests/functional/{run_ostestr.sh => run_stestr.sh} (96%) diff --git a/openstackclient/tests/functional/run_ostestr.sh b/openstackclient/tests/functional/run_stestr.sh similarity index 96% rename from openstackclient/tests/functional/run_ostestr.sh rename to openstackclient/tests/functional/run_stestr.sh index a6adad965c..229b42b676 100755 --- a/openstackclient/tests/functional/run_ostestr.sh +++ b/openstackclient/tests/functional/run_stestr.sh @@ -13,4 +13,4 @@ fi echo 'Running tests with:' env | grep OS -ostestr $* +stestr run $* diff --git a/test-requirements.txt b/test-requirements.txt index fcd7bc7803..0c64e906a0 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -15,8 +15,7 @@ requests-mock>=1.1.0 # Apache-2.0 sphinx>=1.6.2 # BSD stevedore>=1.20.0 # Apache-2.0 os-client-config>=1.28.0 # Apache-2.0 -os-testr>=1.0.0 # Apache-2.0 -testrepository>=0.0.18 # Apache-2.0/BSD +stestr>=1.0.0 # Apache-2.0 testtools>=2.2.0 # MIT tempest>=17.1.0 # Apache-2.0 osprofiler>=1.4.0 # Apache-2.0 diff --git a/tox.ini b/tox.ini index 8ce7ce8c93..8b5454f4ac 100644 --- a/tox.ini +++ b/tox.ini @@ -14,8 +14,8 @@ deps = -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} -r{toxinidir}/test-requirements.txt -r{toxinidir}/requirements.txt -commands = ostestr {posargs} -whitelist_externals = ostestr +commands = stestr run {posargs} +whitelist_externals = stestr [testenv:fast8] # Use same environment directory as pep8 env to save space and install time @@ -63,20 +63,20 @@ commands = pip install -q -U -e "git+file://{toxinidir}/../os-client-config#egg=os_client_config" pip install -q -e "git+file://{toxinidir}/../python-openstacksdk#egg=openstacksdk" pip freeze - ostestr {posargs} -whitelist_externals = ostestr + stestr run {posargs} +whitelist_externals = stestr [testenv:functional] setenv = OS_TEST_PATH=./openstackclient/tests/functional passenv = OS_* -whitelist_externals = openstackclient/tests/functional/run_ostestr.sh +whitelist_externals = openstackclient/tests/functional/run_stestr.sh commands = - {toxinidir}/openstackclient/tests/functional/run_ostestr.sh {posargs} + {toxinidir}/openstackclient/tests/functional/run_stestr.sh {posargs} [testenv:functional-tips] setenv = OS_TEST_PATH=./openstackclient/tests/functional passenv = OS_* -whitelist_externals = openstackclient/tests/functional/run_ostestr.sh +whitelist_externals = openstackclient/tests/functional/run_stestr.sh commands = pip install -q -U -e "git+file://{toxinidir}/../cliff#egg=cliff" pip install -q -U -e "git+file://{toxinidir}/../keystoneauth#egg=keystoneauth" @@ -84,15 +84,20 @@ commands = pip install -q -U -e "git+file://{toxinidir}/../os-client-config#egg=os_client_config" pip install -q -U -e "git+file://{toxinidir}/../python-openstacksdk#egg=openstacksdk" pip freeze - {toxinidir}/openstackclient/tests/functional/run_ostestr.sh {posargs} + {toxinidir}/openstackclient/tests/functional/run_stestr.sh {posargs} [testenv:venv] commands = {posargs} [testenv:cover] +setenv = + VIRTUAL_ENV={envdir} + PYTHON=coverage run --source openstackclient --parallel-mode commands = - python setup.py test --coverage --testr-args='{posargs}' - coverage report + stestr -q run {posargs} + coverage combine + coverage html -d cover + coverage xml -o cover/coverage.xml [testenv:debug] passenv = OS_* From e438c34eaa8335e8727b7dd99a77dc73d37b9d9e Mon Sep 17 00:00:00 2001 From: Chen Hanxiao Date: Thu, 28 Dec 2017 14:55:56 +0800 Subject: [PATCH 1854/3095] flavor: clarify --swap description --swap will add a additional storage device, which not affect the original swap partition/device. This patch will clarify this misleading description. Change-Id: Ic079c069985d39cc969b97876901007a81883f57 Signed-off-by: Chen Hanxiao --- doc/source/cli/command-objects/flavor.rst | 2 +- openstackclient/compute/v2/flavor.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/cli/command-objects/flavor.rst b/doc/source/cli/command-objects/flavor.rst index 6feb44985f..1cbd2df3bb 100644 --- a/doc/source/cli/command-objects/flavor.rst +++ b/doc/source/cli/command-objects/flavor.rst @@ -44,7 +44,7 @@ Create new flavor .. option:: --swap - Swap space size in MB (default 0M) + Additional swap space size in MB (default 0M) .. option:: --vcpus diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index bf9921b788..0f5dd7421e 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -91,7 +91,7 @@ def get_parser(self, prog_name): type=int, metavar="", default=0, - help=_("Swap space size in MB (default 0M)") + help=_("Additional swap space size in MB (default 0M)") ) parser.add_argument( "--vcpus", From 42e0037cd055d81d9452f046c347f8e469d27350 Mon Sep 17 00:00:00 2001 From: Guoqiang Ding Date: Thu, 28 Dec 2017 23:07:09 +0800 Subject: [PATCH 1855/3095] Update new documentation PTI jobs For compliance with the Project Testing Interface as described in [1]. For more detailed information, please refer to [2]. [1] https://governance.openstack.org/tc/reference/project-testing-interface.html [2] http://lists.openstack.org/pipermail/openstack-dev/2017-December/125710.html Change-Id: I7e8c47dead1e019e8705db3ff7559dd39b1d90d9 --- doc/requirements.txt | 3 +++ test-requirements.txt | 3 --- tox.ini | 14 ++++++++++++-- 3 files changed, 15 insertions(+), 5 deletions(-) create mode 100644 doc/requirements.txt diff --git a/doc/requirements.txt b/doc/requirements.txt new file mode 100644 index 0000000000..ea4c643b95 --- /dev/null +++ b/doc/requirements.txt @@ -0,0 +1,3 @@ +openstackdocstheme>=1.17.0 # Apache-2.0 +reno>=2.5.0 # Apache-2.0 +sphinx>=1.6.2 # BSD diff --git a/test-requirements.txt b/test-requirements.txt index 0c64e906a0..2aac28a916 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -7,12 +7,9 @@ coverage!=4.4,>=4.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD flake8-import-order==0.13 # LGPLv3 mock>=2.0.0 # BSD -openstackdocstheme>=1.17.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 -reno>=2.5.0 # Apache-2.0 requests>=2.14.2 # Apache-2.0 requests-mock>=1.1.0 # Apache-2.0 -sphinx>=1.6.2 # BSD stevedore>=1.20.0 # Apache-2.0 os-client-config>=1.28.0 # Apache-2.0 stestr>=1.0.0 # Apache-2.0 diff --git a/tox.ini b/tox.ini index 8b5454f4ac..ca30150d25 100644 --- a/tox.ini +++ b/tox.ini @@ -105,10 +105,20 @@ commands = oslo_debug_helper -t openstackclient/tests {posargs} [testenv:docs] -commands = python setup.py build_sphinx +deps = + -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} + -r{toxinidir}/requirements.txt + -r{toxinidir}/doc/requirements.txt +commands = + sphinx-build -b html doc/source doc/build/html [testenv:releasenotes] -commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html +deps = + -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} + -r{toxinidir}/requirements.txt + -r{toxinidir}/doc/requirements.txt +commands = + sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html [flake8] show-source = True From 5fdd0730c88b7a3f65b728c0ea87bb34cbc1d9c4 Mon Sep 17 00:00:00 2001 From: Hongbin Lu Date: Fri, 8 Dec 2017 22:52:59 +0000 Subject: [PATCH 1856/3095] Allow ports filtering with device_id Right now, if a neutron port is owned by a container powered by Kuryr, there is no way to list and filter those ports because OSC assumed a neutron port is owned by either a server or router. This patch adds support for that by introducing an option '--device-id' to the 'port list' command. Change-Id: Ib1fd27e8d843a99fb02ccabd8a12a24ac27cec9c --- doc/source/cli/command-objects/port.rst | 6 +++++- openstackclient/network/v2/port.py | 7 +++++++ .../tests/unit/network/v2/test_port.py | 19 +++++++++++++++++++ ...vice_id-to-port-list-0c658db51ce43c9e.yaml | 4 ++++ 4 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/add-device_id-to-port-list-0c658db51ce43c9e.yaml diff --git a/doc/source/cli/command-objects/port.rst b/doc/source/cli/command-objects/port.rst index cf29bb2cbd..f8e88230fc 100644 --- a/doc/source/cli/command-objects/port.rst +++ b/doc/source/cli/command-objects/port.rst @@ -170,7 +170,7 @@ List ports openstack port list [--device-owner ] - [--router | --server ] + [--router | --server | --device-id ] [--network ] [--mac-address ] [--fixed-ip subnet=,ip-address=] @@ -192,6 +192,10 @@ List ports List only ports attached to this server (name or ID) +.. option:: --device-id + + List only ports with the specified device ID + .. option:: --network List only ports attached to this network (name or ID) diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 21f30c4160..032e17874b 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -499,6 +499,11 @@ def get_parser(self, prog_name): metavar='', help=_("List only ports attached to this server (name or ID)"), ) + device_group.add_argument( + '--device-id', + metavar='', + help=_("List only ports with the specified device ID") + ) parser.add_argument( '--mac-address', metavar='', @@ -553,6 +558,8 @@ def take_action(self, parsed_args): column_headers += ('Security Groups', 'Device Owner', 'Tags') if parsed_args.device_owner is not None: filters['device_owner'] = parsed_args.device_owner + if parsed_args.device_id is not None: + filters['device_id'] = parsed_args.device_id if parsed_args.router: _router = network_client.find_router(parsed_args.router, ignore_missing=False) diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index 3f751818b9..908177ce65 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -759,6 +759,25 @@ def test_port_list_with_server_option(self, mock_find): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + def test_port_list_device_id_opt(self): + arglist = [ + '--device-id', self._ports[0].device_id, + ] + + verifylist = [ + ('device_id', self._ports[0].device_id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.ports.assert_called_once_with(**{ + 'device_id': self._ports[0].device_id + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + def test_port_list_device_owner_opt(self): arglist = [ '--device-owner', self._ports[0].device_owner, diff --git a/releasenotes/notes/add-device_id-to-port-list-0c658db51ce43c9e.yaml b/releasenotes/notes/add-device_id-to-port-list-0c658db51ce43c9e.yaml new file mode 100644 index 0000000000..05dae9e924 --- /dev/null +++ b/releasenotes/notes/add-device_id-to-port-list-0c658db51ce43c9e.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Add ``--device-id`` option to the ``port list`` command. From ed1b59848fd2a6b7bed7618ab5ac0db00e8110dc Mon Sep 17 00:00:00 2001 From: Mike Fedosin Date: Thu, 4 Jan 2018 17:49:28 +0100 Subject: [PATCH 1857/3095] Check that Glance returns image data before processing it Now if Glance v2 cannot find image data it returns an empty response with 204 status code, instead of raising an error. Glance client handles this situation and wraps the response with a RequestIdProxy object, whose 'wrapped' attribute is None. But when openstack client tries to parse this object using glanceclient's save_image util function, it fails with "NoneType object is not iterable" message, for the object doesn't contain any data. This patch adds additional check to prevent such behaviour and raises SystemExit exception if no data was returned from the server. Glance v1 is not affected, because it raises an error if can't find an image data. Change-Id: I016a60462ba586f9fa7585c2cfafffd7be38de7b Closes-Bug: #1741223 --- openstackclient/image/v2/image.py | 7 +++ .../tests/unit/image/v2/test_image.py | 51 +++++++++++++++++++ .../notes/bug-1741223-7a5c5b6e7f232628.yaml | 6 +++ 3 files changed, 64 insertions(+) create mode 100644 releasenotes/notes/bug-1741223-7a5c5b6e7f232628.yaml diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index d793c4599b..3db9311e25 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -17,6 +17,7 @@ import argparse import logging +import sys from glanceclient.common import utils as gc_utils from osc_lib.cli import parseractions @@ -649,6 +650,12 @@ def take_action(self, parsed_args): ) data = image_client.images.data(image.id) + if data.wrapped is None: + msg = _('Image %s has no data.') % image.id + LOG.error(msg) + sys.stdout.write(msg + '\n') + raise SystemExit + gc_utils.save_image(data, parsed_args.file) diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py index 383619ef54..e1a79d13ce 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -15,6 +15,7 @@ import copy +from glanceclient.common import utils as glanceclient_utils from glanceclient.v2 import schemas import mock from osc_lib import exceptions @@ -1505,3 +1506,53 @@ def test_image_unset_mixed_option(self): self.image.id, 'test' ) self.assertIsNone(result) + + +class TestImageSave(TestImage): + + image = image_fakes.FakeImage.create_one_image({}) + + def setUp(self): + super(TestImageSave, self).setUp() + + # Generate a request id + self.resp = mock.MagicMock() + self.resp.headers['x-openstack-request-id'] = 'req_id' + + # Get the command object to test + self.cmd = image.SaveImage(self.app, None) + + def test_save_data(self): + req_id_proxy = glanceclient_utils.RequestIdProxy( + ['some_data', self.resp] + ) + self.images_mock.data.return_value = req_id_proxy + + arglist = ['--file', '/path/to/file', self.image.id] + + verifylist = [ + ('file', '/path/to/file'), + ('image', self.image.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + with mock.patch('glanceclient.common.utils.save_image') as mocked_save: + self.cmd.take_action(parsed_args) + mocked_save.assert_called_once_with(req_id_proxy, '/path/to/file') + + def test_save_no_data(self): + req_id_proxy = glanceclient_utils.RequestIdProxy( + [None, self.resp] + ) + self.images_mock.data.return_value = req_id_proxy + + arglist = ['--file', '/path/to/file', self.image.id] + + verifylist = [ + ('file', '/path/to/file'), + ('image', self.image.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Raise SystemExit if no data was provided. + self.assertRaises(SystemExit, self.cmd.take_action, parsed_args) diff --git a/releasenotes/notes/bug-1741223-7a5c5b6e7f232628.yaml b/releasenotes/notes/bug-1741223-7a5c5b6e7f232628.yaml new file mode 100644 index 0000000000..2c3a0ef402 --- /dev/null +++ b/releasenotes/notes/bug-1741223-7a5c5b6e7f232628.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + 'NoneType' object is not iterable when Glance cannot find image data in its + backend. + [Bug `1741223 `_] From cc47c075a067e3f4f3bb80dd933cdd4d483b8105 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Tue, 9 Jan 2018 16:21:50 +0000 Subject: [PATCH 1858/3095] Updated from global requirements Change-Id: Ic85eb65f3f143fdfbf2b6fa71d7cdd6961b24c76 --- doc/requirements.txt | 3 +++ test-requirements.txt | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/requirements.txt b/doc/requirements.txt index ea4c643b95..b666029aef 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -1,3 +1,6 @@ +# The order of packages is significant, because pip processes them in the order +# of appearance. Changing the order has an impact on the overall integration +# process, which may cause wedges in the gate later. openstackdocstheme>=1.17.0 # Apache-2.0 reno>=2.5.0 # Apache-2.0 sphinx>=1.6.2 # BSD diff --git a/test-requirements.txt b/test-requirements.txt index 2aac28a916..6d3272a504 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -32,7 +32,7 @@ python-karborclient>=0.6.0 # Apache-2.0 python-mistralclient>=3.1.0 # Apache-2.0 python-muranoclient>=0.8.2 # Apache-2.0 python-neutronclient>=6.3.0 # Apache-2.0 -python-octaviaclient>=1.0.0 # Apache-2.0 +python-octaviaclient>=1.3.0 # Apache-2.0 python-rsdclient>=0.1.0 # Apache-2.0 python-saharaclient>=1.4.0 # Apache-2.0 python-searchlightclient>=1.0.0 #Apache-2.0 From cf91d7a2f46b4a8546169a4836cc64476fce44d8 Mon Sep 17 00:00:00 2001 From: LIU Yulong Date: Sun, 5 Nov 2017 12:05:09 +0800 Subject: [PATCH 1859/3095] Add floating IP qos_policy actions Now we can associate a qos policy to the floating IP, and dissociate it. The commands are: $ openstack floating ip create --qos-policy ... $ openstack floating ip set --qos-policy ... $ openstack floating ip set --no-qos-policy ... $ openstack floating ip unset --qos-policy These commands are based on the neutron change: I4efe9e49d268dffeb3df4de4ea1780152218633b Partially-Implements blueprint: floating-ip-rate-limit Change-Id: I932b32f78cc5a2b53926feaec1a0b392cf7e8b57 --- .../cli/command-objects/floating-ip.rst | 21 +++ openstackclient/network/v2/floating_ip.py | 42 +++++- .../tests/unit/network/v2/fakes.py | 1 + .../network/v2/test_floating_ip_network.py | 129 ++++++++++++++++++ ...oating-ip-rate-limit-8387c040a6fb9acd.yaml | 11 ++ 5 files changed, 201 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/bp-neutron-floating-ip-rate-limit-8387c040a6fb9acd.yaml diff --git a/doc/source/cli/command-objects/floating-ip.rst b/doc/source/cli/command-objects/floating-ip.rst index f2f101d3db..d9ed2307c3 100644 --- a/doc/source/cli/command-objects/floating-ip.rst +++ b/doc/source/cli/command-objects/floating-ip.rst @@ -18,6 +18,7 @@ Create floating IP [--floating-ip-address ] [--fixed-ip-address ] [--description ] + [--qos-policy ] [--project [--project-domain ]] @@ -46,6 +47,12 @@ Create floating IP Set floating IP description *Network version 2 only* +.. option:: --qos-policy + + QoS policy to attach to the floating IP (name or ID) + + *Network version 2 only* + .. option:: --project Owner's project (name or ID) @@ -154,6 +161,7 @@ Set floating IP properties openstack floating ip set --port [--fixed-ip-address ] + [--qos-policy | --no-qos-policy] .. option:: --port @@ -164,6 +172,14 @@ Set floating IP properties Fixed IP of the port (required only if port has multiple IPs) +.. option:: --qos-policy + + Attach QoS policy to the floating IP (name or ID) + +.. option:: --no-qos-policy + + Remove the QoS policy attached to the floating IP + .. _floating_ip_set-floating-ip: .. describe:: @@ -193,12 +209,17 @@ Unset floating IP Properties openstack floating ip unset --port + --qos-policy .. option:: --port Disassociate any port associated with the floating IP +.. option:: --qos-policy + + Remove the QoS policy attached to the floating IP + .. _floating_ip_unset-floating-ip: .. describe:: diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index 05b688a63d..181f88c054 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -67,6 +67,10 @@ def _get_attrs(client_manager, parsed_args): if parsed_args.fixed_ip_address: attrs['fixed_ip_address'] = parsed_args.fixed_ip_address + if parsed_args.qos_policy: + attrs['qos_policy_id'] = network_client.find_qos_policy( + parsed_args.qos_policy, ignore_missing=False).id + if parsed_args.description is not None: attrs['description'] = parsed_args.description @@ -169,6 +173,11 @@ def update_parser_network(self, parser): dest='fixed_ip_address', help=_("Fixed IP address mapped to the floating IP") ) + parser.add_argument( + '--qos-policy', + metavar='', + help=_("Attach QoS policy to the floating IP (name or ID)") + ) parser.add_argument( '--description', metavar='', @@ -462,6 +471,17 @@ def get_parser(self, prog_name): help=_("Fixed IP of the port " "(required only if port has multiple IPs)") ) + qos_policy_group = parser.add_mutually_exclusive_group() + qos_policy_group.add_argument( + '--qos-policy', + metavar='', + help=_("Attach QoS policy to the floating IP (name or ID)") + ) + qos_policy_group.add_argument( + '--no-qos-policy', + action='store_true', + help=_("Remove the QoS policy attached to the floating IP") + ) return parser def take_action(self, parsed_args): @@ -479,6 +499,13 @@ def take_action(self, parsed_args): if parsed_args.fixed_ip_address: attrs['fixed_ip_address'] = parsed_args.fixed_ip_address + if parsed_args.qos_policy: + attrs['qos_policy_id'] = client.find_qos_policy( + parsed_args.qos_policy, ignore_missing=False).id + + if 'no_qos_policy' in parsed_args and parsed_args.no_qos_policy: + attrs['qos_policy_id'] = None + client.update_ip(obj, **attrs) @@ -549,6 +576,12 @@ def get_parser(self, prog_name): default=False, help=_("Disassociate any port associated with the floating IP") ) + parser.add_argument( + '--qos-policy', + action='store_true', + default=False, + help=_("Remove the QoS policy attached to the floating IP") + ) return parser def take_action(self, parsed_args): @@ -559,8 +592,11 @@ def take_action(self, parsed_args): parsed_args.floating_ip, ignore_missing=False, ) + attrs = {} if parsed_args.port: - attrs = { - 'port_id': None, - } + attrs['port_id'] = None + if parsed_args.qos_policy: + attrs['qos_policy_id'] = None + + if attrs: client.update_ip(obj, **attrs) diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index bdc1c1fb0e..a77cab8b5f 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -1378,6 +1378,7 @@ def create_one_floating_ip(attrs=None): 'port_id': 'port-id-' + uuid.uuid4().hex, 'tenant_id': 'project-id-' + uuid.uuid4().hex, 'description': 'floating-ip-description-' + uuid.uuid4().hex, + 'qos_policy_id': 'qos-policy-id-' + uuid.uuid4().hex, } # Overwrite default attributes. diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip_network.py b/openstackclient/tests/unit/network/v2/test_floating_ip_network.py index 4fbbc8227e..86f64ccd2e 100644 --- a/openstackclient/tests/unit/network/v2/test_floating_ip_network.py +++ b/openstackclient/tests/unit/network/v2/test_floating_ip_network.py @@ -62,6 +62,7 @@ class TestCreateFloatingIPNetwork(TestFloatingIPNetwork): 'id', 'port_id', 'project_id', + 'qos_policy_id', 'router_id', 'status', ) @@ -76,6 +77,7 @@ class TestCreateFloatingIPNetwork(TestFloatingIPNetwork): floating_ip.id, floating_ip.port_id, floating_ip.project_id, + floating_ip.qos_policy_id, floating_ip.router_id, floating_ip.status, ) @@ -197,6 +199,28 @@ def test_floating_ip_create_project_domain(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_create_floating_ip_with_qos(self): + qos_policy = network_fakes.FakeNetworkQosPolicy.create_one_qos_policy() + self.network.find_qos_policy = mock.Mock(return_value=qos_policy) + arglist = [ + '--qos-policy', qos_policy.id, + self.floating_ip.floating_network_id, + ] + verifylist = [ + ('network', self.floating_ip.floating_network_id), + ('qos_policy', qos_policy.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_ip.assert_called_once_with(**{ + 'floating_network_id': self.floating_ip.floating_network_id, + 'qos_policy_id': qos_policy.id, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + class TestDeleteFloatingIPNetwork(TestFloatingIPNetwork): @@ -538,6 +562,7 @@ class TestShowFloatingIPNetwork(TestFloatingIPNetwork): 'id', 'port_id', 'project_id', + 'qos_policy_id', 'router_id', 'status', ) @@ -552,6 +577,7 @@ class TestShowFloatingIPNetwork(TestFloatingIPNetwork): floating_ip.id, floating_ip.port_id, floating_ip.project_id, + floating_ip.qos_policy_id, floating_ip.router_id, floating_ip.status, ) @@ -677,6 +703,76 @@ def test_fixed_ip_option(self, find_floating_ip_mock): self.network.update_ip.assert_called_once_with( self.floating_ip, **attrs) + @mock.patch( + "openstackclient.tests.unit.network.v2.test_floating_ip_network." + + "fip._find_floating_ip" + ) + def test_port_and_qos_policy_option(self, find_floating_ip_mock): + qos_policy = network_fakes.FakeNetworkQosPolicy.create_one_qos_policy() + self.network.find_qos_policy = mock.Mock(return_value=qos_policy) + find_floating_ip_mock.side_effect = [ + self.floating_ip, + ] + arglist = [ + "--qos-policy", qos_policy.id, + '--port', self.floating_ip.port_id, + self.floating_ip.id, + ] + verifylist = [ + ('qos_policy', qos_policy.id), + ('port', self.floating_ip.port_id), + ('floating_ip', self.floating_ip.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + attrs = { + 'qos_policy_id': qos_policy.id, + 'port_id': self.floating_ip.port_id, + } + find_floating_ip_mock.assert_called_once_with( + mock.ANY, + self.floating_ip.id, + ignore_missing=False, + ) + self.network.update_ip.assert_called_once_with( + self.floating_ip, **attrs) + + @mock.patch( + "openstackclient.tests.unit.network.v2.test_floating_ip_network." + + "fip._find_floating_ip" + ) + def test_port_and_no_qos_policy_option(self, find_floating_ip_mock): + find_floating_ip_mock.side_effect = [ + self.floating_ip, + ] + arglist = [ + "--no-qos-policy", + '--port', self.floating_ip.port_id, + self.floating_ip.id, + ] + verifylist = [ + ('no_qos_policy', True), + ('port', self.floating_ip.port_id), + ('floating_ip', self.floating_ip.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + attrs = { + 'qos_policy_id': None, + 'port_id': self.floating_ip.port_id, + } + find_floating_ip_mock.assert_called_once_with( + mock.ANY, + self.floating_ip.id, + ignore_missing=False, + ) + self.network.update_ip.assert_called_once_with( + self.floating_ip, **attrs) + class TestUnsetFloatingIP(TestFloatingIPNetwork): @@ -732,3 +828,36 @@ def test_floating_ip_unset_port(self, find_floating_ip_mock): self.floating_ip, **attrs) self.assertIsNone(result) + + @mock.patch( + "openstackclient.tests.unit.network.v2.test_floating_ip_network." + + "fip._find_floating_ip" + ) + def test_floating_ip_unset_qos_policy(self, find_floating_ip_mock): + find_floating_ip_mock.side_effect = [ + self.floating_ip, + ] + arglist = [ + self.floating_ip.id, + "--qos-policy", + ] + verifylist = [ + ('floating_ip', self.floating_ip.id), + ('qos_policy', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + attrs = { + 'qos_policy_id': None, + } + find_floating_ip_mock.assert_called_once_with( + mock.ANY, + self.floating_ip.id, + ignore_missing=False, + ) + self.network.update_ip.assert_called_once_with( + self.floating_ip, **attrs) + + self.assertIsNone(result) diff --git a/releasenotes/notes/bp-neutron-floating-ip-rate-limit-8387c040a6fb9acd.yaml b/releasenotes/notes/bp-neutron-floating-ip-rate-limit-8387c040a6fb9acd.yaml new file mode 100644 index 0000000000..d0aa9cd121 --- /dev/null +++ b/releasenotes/notes/bp-neutron-floating-ip-rate-limit-8387c040a6fb9acd.yaml @@ -0,0 +1,11 @@ +--- +features: + - | + Add support for attaching and removing qos policy to floating IPs. + + Add option ``--qos-policy`` to the ``floating ip create`` and + ``floating ip set`` commands to add qos policy to a floating IP. + + Add option ``--no-qos-policy`` to the ``floating ip set`` and option + ``--qos-policy`` to the ``floating ip unset`` command to remove the + qos policy from a floating IP. From f02f95f0636fee286c23ce42b1b049afe0407cbe Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Mon, 15 Jan 2018 11:00:35 +0000 Subject: [PATCH 1860/3095] Updated from global requirements Change-Id: I90df2e58bb83239f5b041982844516eb34cb5656 --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 6d3272a504..e92466d434 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -23,7 +23,7 @@ wrapt>=1.7.0 # BSD License aodhclient>=0.9.0 # Apache-2.0 gnocchiclient>=3.3.1 # Apache-2.0 python-barbicanclient!=4.5.0,!=4.5.1,>=4.0.0 # Apache-2.0 -python-congressclient<2000,>=1.3.0 # Apache-2.0 +python-congressclient<2000,>=1.9.0 # Apache-2.0 python-designateclient>=2.7.0 # Apache-2.0 python-heatclient>=1.10.0 # Apache-2.0 python-ironicclient>=1.14.0 # Apache-2.0 From 551278eb852f8694127c4a1c98a6c2458a704c03 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Tue, 16 Jan 2018 04:32:02 +0000 Subject: [PATCH 1861/3095] Updated from global requirements Change-Id: I72311597c1b62d985282cef5e219c16cd6745b42 --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index e92466d434..7aa749cc82 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -7,7 +7,7 @@ coverage!=4.4,>=4.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD flake8-import-order==0.13 # LGPLv3 mock>=2.0.0 # BSD -oslotest>=1.10.0 # Apache-2.0 +oslotest>=3.2.0 # Apache-2.0 requests>=2.14.2 # Apache-2.0 requests-mock>=1.1.0 # Apache-2.0 stevedore>=1.20.0 # Apache-2.0 From 0a2ee90204068a1284f207397bcb253ec1e13c7e Mon Sep 17 00:00:00 2001 From: Andreas Jaeger Date: Tue, 16 Jan 2018 08:11:28 +0100 Subject: [PATCH 1862/3095] Use Zuul v3 fetch-subunit-output We have consolidated the fetch output roles into one fetch-subunit-output, replace useage of old roles with new one. Depends-On: I0cdfc66ee8b046affeb0b071fef38c21cb7a4948 Change-Id: Iae2892d9b4cd870a11579434edc9ee66bd16798c --- playbooks/osc-devstack/post.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/playbooks/osc-devstack/post.yaml b/playbooks/osc-devstack/post.yaml index db7ca7d67f..a198364866 100644 --- a/playbooks/osc-devstack/post.yaml +++ b/playbooks/osc-devstack/post.yaml @@ -1,4 +1,3 @@ - hosts: all roles: - - fetch-tox-output - - fetch-stestr-output + - fetch-subunit-output From 189aec9b1881ff378f615005474ef5e54b0ff971 Mon Sep 17 00:00:00 2001 From: Doug Hellmann Date: Wed, 17 Jan 2018 10:16:17 -0500 Subject: [PATCH 1863/3095] Partially Revert "Update new documentation PTI jobs" The zuul job figures out whether to run "setup.py build_sphinx" or "build-sphinx" based on whether the project is relying on pbr's ability to auto-generate API reference docs. Because we are relying on that, we want local builds to use "setup.py build_sphinx". This reverts commit 42e0037cd055d81d9452f046c347f8e469d27350. Change-Id: Ia01188110fa9c3ccaf3d794fa3a511a6780e683e --- tox.ini | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index ca30150d25..47b745a2a8 100644 --- a/tox.ini +++ b/tox.ini @@ -109,8 +109,7 @@ deps = -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} -r{toxinidir}/requirements.txt -r{toxinidir}/doc/requirements.txt -commands = - sphinx-build -b html doc/source doc/build/html +commands = python setup.py build_sphinx [testenv:releasenotes] deps = From ab50a2ec8ab6988aad21cfb37d17dab70c9d0f83 Mon Sep 17 00:00:00 2001 From: David Rabel Date: Wed, 17 Jan 2018 18:33:33 +0100 Subject: [PATCH 1864/3095] Fix indentation in authentication.rst Fix indentation in doc/source/cli/authentication.rst Change-Id: I7d408e9d27a384903680303219f2578be0e2937e --- doc/source/cli/authentication.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/source/cli/authentication.rst b/doc/source/cli/authentication.rst index 43fcd88482..b153f54111 100644 --- a/doc/source/cli/authentication.rst +++ b/doc/source/cli/authentication.rst @@ -114,22 +114,22 @@ to tell the client in which domain the user and project exists. If using a user name and password to authenticate, specify either it's owning domain name or ID. - * ``--os-user-domain-name`` or ``OS_USER_DOMAIN_NAME`` +* ``--os-user-domain-name`` or ``OS_USER_DOMAIN_NAME`` - * ``--os-user-domain-id`` or ``OS_USER_DOMAIN_ID`` +* ``--os-user-domain-id`` or ``OS_USER_DOMAIN_ID`` If using a project name as authorization scope, specify either it's owning domain name or ID. - * ``--os-project-domain-name`` or ``OS_PROJECT_DOMAIN_NAME`` +* ``--os-project-domain-name`` or ``OS_PROJECT_DOMAIN_NAME`` - * ``--os-project-domain-id`` or ``OS_PROJECT_DOMAIN_ID`` +* ``--os-project-domain-id`` or ``OS_PROJECT_DOMAIN_ID`` If using a domain as authorization scope, set either it's name or ID. - * ``--os-domain-name`` or ``OS_DOMAIN_NAME`` +* ``--os-domain-name`` or ``OS_DOMAIN_NAME`` - * ``--os-domain-id`` or ``OS_DOMAIN_ID`` +* ``--os-domain-id`` or ``OS_DOMAIN_ID`` Note that if the user and project share the same domain, then simply setting ``--os-default-domain`` or ``OS_DEFAULT_DOMAIN`` to the domain ID is sufficient. From 001efadbdef4bc38f01b8ce5b3d76da61d126908 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 18 Jan 2018 03:30:37 +0000 Subject: [PATCH 1865/3095] Updated from global requirements Change-Id: Ic5715c21e19d92c8f3d85091bfa41c28bb271c42 --- doc/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/requirements.txt b/doc/requirements.txt index b666029aef..555b7d200b 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -3,4 +3,4 @@ # process, which may cause wedges in the gate later. openstackdocstheme>=1.17.0 # Apache-2.0 reno>=2.5.0 # Apache-2.0 -sphinx>=1.6.2 # BSD +sphinx!=1.6.6,>=1.6.2 # BSD From e8b56a37ccfcfeb5f12e873c8b58ba289e244870 Mon Sep 17 00:00:00 2001 From: David Rabel Date: Fri, 19 Jan 2018 11:18:41 +0100 Subject: [PATCH 1866/3095] Corrected spelling mistake in quotas -> in quotes Change-Id: I3adb1ccd8f3a9c495f0b9cf688aee5c4c1e63507 --- openstackclient/compute/v2/service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index b5b9bd5e1e..7331d29d01 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.py @@ -142,7 +142,7 @@ def get_parser(self, prog_name): "--disable-reason", default=None, metavar="", - help=_("Reason for disabling the service (in quotas). " + help=_("Reason for disabling the service (in quotes). " "Should be used with --disable option.") ) up_down_group = parser.add_mutually_exclusive_group() From 951956a7998dc1ab5c973c881107a5e03fea1de2 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Fri, 19 Jan 2018 12:49:58 +0000 Subject: [PATCH 1867/3095] Updated from global requirements Change-Id: I72c81c299759b883e316b450716d1528bdb06308 --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 7aa749cc82..b1ef7d6d04 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -39,4 +39,4 @@ python-searchlightclient>=1.0.0 #Apache-2.0 python-senlinclient>=1.1.0 # Apache-2.0 python-troveclient>=2.2.0 # Apache-2.0 python-zaqarclient>=1.0.0 # Apache-2.0 -python-zunclient>=0.2.0 # Apache-2.0 +python-zunclient>=1.0.0 # Apache-2.0 From ca90985f4e5e4ebcf97afd6cce38932e73120255 Mon Sep 17 00:00:00 2001 From: Pavlo Shchelokovskyy Date: Mon, 22 Jan 2018 12:24:01 +0200 Subject: [PATCH 1868/3095] Replace assert with condition a piece of code in image client has some business logic behind assert, which can be lost when running python in optimized mode (-O). Change-Id: I2179970df495e1215d691915c51cebe5cb4541a7 --- openstackclient/image/v2/image.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index d793c4599b..0b8647a0ca 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -1024,9 +1024,7 @@ def take_action(self, parsed_args): if parsed_args.properties: for k in parsed_args.properties: - try: - assert(k in image.keys()) - except AssertionError: + if k not in image: LOG.error(_("property unset failed, '%s' is a " "nonexistent property "), k) propret += 1 From a018c6d5d8c51c8b0acef3471441b31984502d46 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 1 Dec 2017 10:37:32 -0600 Subject: [PATCH 1869/3095] Rework Network client config for new SDK Connection network.client.make_client() has always put a copy of it's SDK Connection directly into ClientManager, the new-style Connection create will move into osc-lib ClientManager, do it here too until then. Change-Id: I1edfd19c9e73320768fb9640931fafe857c980b4 --- openstackclient/network/client.py | 45 ++++++++++++++++++------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/openstackclient/network/client.py b/openstackclient/network/client.py index 6f74303d0f..878672b78e 100644 --- a/openstackclient/network/client.py +++ b/openstackclient/network/client.py @@ -41,28 +41,35 @@ def make_client(instance): """Returns a network proxy""" - if profile is None: - # New SDK - conn = connection.Connection( - config=instance._cli_options, - session=instance.session) - else: - prof = profile.Profile() - prof.set_region(API_NAME, instance.region_name) - prof.set_version(API_NAME, instance._api_version[API_NAME]) - prof.set_interface(API_NAME, instance.interface) - conn = connection.Connection(authenticator=instance.session.auth, - verify=instance.session.verify, - cert=instance.session.cert, - profile=prof) + if getattr(instance, "sdk_connection", None) is None: + if profile is None: + # If the installed OpenStackSDK is new enough to not require a + # Profile obejct and osc-lib is not new enough to have created + # it for us, make an SDK Connection. + # NOTE(dtroyer): This can be removed when this bit is in the + # released osc-lib in requirements.txt. + conn = connection.Connection( + config=instance._cli_options, + session=instance.session, + ) + else: + # Fall back to the original Connection creation + prof = profile.Profile() + prof.set_region(API_NAME, instance.region_name) + prof.set_version(API_NAME, instance._api_version[API_NAME]) + prof.set_interface(API_NAME, instance.interface) + conn = connection.Connection( + authenticator=instance.session.auth, + verify=instance.session.verify, + cert=instance.session.cert, + profile=prof, + ) + + instance.sdk_connection = conn + LOG.debug('Connection: %s', conn) LOG.debug('Network client initialized using OpenStack SDK: %s', conn.network) - - # NOTE(dtroyer): Horrible ugly hack since we don't actually save - # the connection anywhere yet, so stash it in the - # instance directly from here for other uses - instance.sdk_connection = conn return conn.network From 5e411fbce7712aa39ff4d4d535b9f2259b4f20a3 Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Tue, 23 Jan 2018 08:06:19 -0600 Subject: [PATCH 1870/3095] Fix use of new openstacksdk connection We store the created conn on the instance, but we never pull it back off if there is already one present. Change-Id: I2d890dd206d4ddf67fa42d798e6fd2c652799785 --- openstackclient/network/client.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openstackclient/network/client.py b/openstackclient/network/client.py index 878672b78e..5183cbdaf5 100644 --- a/openstackclient/network/client.py +++ b/openstackclient/network/client.py @@ -67,6 +67,7 @@ def make_client(instance): instance.sdk_connection = conn + conn = instance.sdk_connection LOG.debug('Connection: %s', conn) LOG.debug('Network client initialized using OpenStack SDK: %s', conn.network) From fbee4eb76285ac8fc623b15b49c1d4efd5a71fc1 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 24 Jan 2018 01:31:48 +0000 Subject: [PATCH 1871/3095] Updated from global requirements Change-Id: I7b712b41e633f7e3dc40749b5a55706cb32fecee --- doc/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/requirements.txt b/doc/requirements.txt index 555b7d200b..597b54eda5 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -1,6 +1,6 @@ # The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -openstackdocstheme>=1.17.0 # Apache-2.0 +openstackdocstheme>=1.18.1 # Apache-2.0 reno>=2.5.0 # Apache-2.0 sphinx!=1.6.6,>=1.6.2 # BSD From db0c9231c0511453f2770246f9c27716dd67acaf Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Thu, 25 Jan 2018 13:46:13 +0000 Subject: [PATCH 1872/3095] Update reno for stable/queens Change-Id: Iace9272b22ec8ccc8790e63c3f5e435d497c5e71 --- releasenotes/source/index.rst | 1 + releasenotes/source/queens.rst | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 releasenotes/source/queens.rst diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index a551b26a59..33fb920cad 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,6 +6,7 @@ OpenStackClient Release Notes :maxdepth: 1 unreleased + queens pike ocata newton diff --git a/releasenotes/source/queens.rst b/releasenotes/source/queens.rst new file mode 100644 index 0000000000..36ac6160ca --- /dev/null +++ b/releasenotes/source/queens.rst @@ -0,0 +1,6 @@ +=================================== + Queens Series Release Notes +=================================== + +.. release-notes:: + :branch: stable/queens From 8468b2a064bca7a31cfc558ac0729ecfe2d3201d Mon Sep 17 00:00:00 2001 From: Matt Riedemann Date: Sat, 27 Jan 2018 20:43:51 -0500 Subject: [PATCH 1873/3095] Fix tox -e venv -- reno new Change I7e8c47dead1e019e8705db3ff7559dd39b1d90d9 broke the ability to create a new release note from the venv tox target because the reno requirement was moved from test-requirements.txt. This fixes it. Change-Id: Ifac83121388728fa445e0ed4433c9d981c057737 --- tox.ini | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tox.ini b/tox.ini index 47b745a2a8..c56c3fbf0c 100644 --- a/tox.ini +++ b/tox.ini @@ -87,6 +87,10 @@ commands = {toxinidir}/openstackclient/tests/functional/run_stestr.sh {posargs} [testenv:venv] +deps = + -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} + -r{toxinidir}/requirements.txt + -r{toxinidir}/doc/requirements.txt commands = {posargs} [testenv:cover] From 2ef279ab71eb56860bc0ff10e14b8a865773f83a Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Mon, 29 Jan 2018 00:35:35 +0000 Subject: [PATCH 1874/3095] Updated from global requirements Change-Id: I7febe9ca4d4c9ac5044208ff1816eab8f1d9452e --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index b1ef7d6d04..c393a53d49 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -26,7 +26,7 @@ python-barbicanclient!=4.5.0,!=4.5.1,>=4.0.0 # Apache-2.0 python-congressclient<2000,>=1.9.0 # Apache-2.0 python-designateclient>=2.7.0 # Apache-2.0 python-heatclient>=1.10.0 # Apache-2.0 -python-ironicclient>=1.14.0 # Apache-2.0 +python-ironicclient>=2.2.0 # Apache-2.0 python-ironic-inspector-client>=1.5.0 # Apache-2.0 python-karborclient>=0.6.0 # Apache-2.0 python-mistralclient>=3.1.0 # Apache-2.0 From a742e47ecf86ef514d124ec5d10df197c01b253b Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Mon, 29 Jan 2018 09:59:08 -0600 Subject: [PATCH 1875/3095] Use find_ip from openstacksdk The find_ip from openstacksdk started being usable by OSC back in 0.9.15 but the local method never got replaced. Change-Id: I18a334280e5f384f8bb96198cdad79c612a02290 --- openstackclient/network/v2/floating_ip.py | 64 +---------- .../network/v2/test_floating_ip_network.py | 108 ++++-------------- 2 files changed, 29 insertions(+), 143 deletions(-) diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index 181f88c054..a6f0340466 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -15,8 +15,6 @@ import logging -from openstack import exceptions as sdk_exceptions -from openstack.network.v2 import floating_ip as _floating_ip from osc_lib.command import command from osc_lib import utils @@ -86,54 +84,6 @@ def _get_attrs(client_manager, parsed_args): return attrs -def _find_floating_ip( - session, - name_or_id, - ignore_missing=True, - **params -): - """Find a floating IP by IP or ID - - The SDK's find_ip() can only locate a floating IP by ID so we have - to do this ourselves. - """ - def _get_one_match(name_or_id): - """Given a list of results, return the match""" - the_result = None - ip_list = list(_floating_ip.FloatingIP.list(session, **params)) - for maybe_result in ip_list: - id_value = maybe_result.id - ip_value = maybe_result.floating_ip_address - - if (id_value == name_or_id) or (ip_value == name_or_id): - # Only allow one resource to be found. If we already - # found a match, raise an exception to show it. - if the_result is None: - the_result = maybe_result - else: - msg = "More than one %s exists with the name '%s'." - msg = (msg % (_floating_ip.FloatingIP, name_or_id)) - raise sdk_exceptions.DuplicateResource(msg) - - return the_result - - # Try to short-circuit by looking directly for a matching ID. - try: - match = _floating_ip.FloatingIP.existing(id=name_or_id, **params) - return match.get(session) - except sdk_exceptions.NotFoundException: - pass - - result = _get_one_match(name_or_id) - if result is not None: - return result - - if ignore_missing: - return None - raise sdk_exceptions.ResourceNotFound( - "No %s found for %s" % (_floating_ip.FloatingIP.__name__, name_or_id)) - - class CreateFloatingIP(common.NetworkAndComputeShowOne): _description = _("Create floating IP") @@ -246,8 +196,7 @@ def update_parser_common(self, parser): return parser def take_action_network(self, client, parsed_args): - obj = _find_floating_ip( - self.app.client_manager.sdk_connection.session, + obj = client.find_ip( self.r, ignore_missing=False, ) @@ -487,9 +436,7 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): client = self.app.client_manager.network attrs = {} - # TODO(sindhu) Use client.find_ip() once SDK 0.9.15 is released - obj = _find_floating_ip( - self.app.client_manager.sdk_connection.session, + obj = client.find_ip( parsed_args.floating_ip, ignore_missing=False, ) @@ -521,8 +468,7 @@ def update_parser_common(self, parser): return parser def take_action_network(self, client, parsed_args): - obj = _find_floating_ip( - self.app.client_manager.sdk_connection.session, + obj = client.find_ip( parsed_args.floating_ip, ignore_missing=False, ) @@ -586,9 +532,7 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): client = self.app.client_manager.network - # TODO(sindhu) Use client.find_ip() once SDK 0.9.15 is released - obj = _find_floating_ip( - self.app.client_manager.sdk_connection.session, + obj = client.find_ip( parsed_args.floating_ip, ignore_missing=False, ) diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip_network.py b/openstackclient/tests/unit/network/v2/test_floating_ip_network.py index 86f64ccd2e..f19849c40e 100644 --- a/openstackclient/tests/unit/network/v2/test_floating_ip_network.py +++ b/openstackclient/tests/unit/network/v2/test_floating_ip_network.py @@ -230,14 +230,14 @@ class TestDeleteFloatingIPNetwork(TestFloatingIPNetwork): def setUp(self): super(TestDeleteFloatingIPNetwork, self).setUp() + self.network.find_ip = mock.Mock() self.network.delete_ip = mock.Mock(return_value=None) # Get the command object to test self.cmd = fip.DeleteFloatingIP(self.app, self.namespace) - @mock.patch.object(fip, '_find_floating_ip') - def test_floating_ip_delete(self, find_floating_ip_mock): - find_floating_ip_mock.side_effect = [ + def test_floating_ip_delete(self): + self.network.find_ip.side_effect = [ self.floating_ips[0], self.floating_ips[1], ] @@ -251,17 +251,15 @@ def test_floating_ip_delete(self, find_floating_ip_mock): result = self.cmd.take_action(parsed_args) - find_floating_ip_mock.assert_called_once_with( - mock.ANY, + self.network.find_ip.assert_called_once_with( self.floating_ips[0].id, ignore_missing=False, ) self.network.delete_ip.assert_called_once_with(self.floating_ips[0]) self.assertIsNone(result) - @mock.patch.object(fip, '_find_floating_ip') - def test_floating_ip_delete_multi(self, find_floating_ip_mock): - find_floating_ip_mock.side_effect = [ + def test_floating_ip_delete_multi(self): + self.network.find_ip.side_effect = [ self.floating_ips[0], self.floating_ips[1], ] @@ -279,17 +277,15 @@ def test_floating_ip_delete_multi(self, find_floating_ip_mock): calls = [ call( - mock.ANY, self.floating_ips[0].id, ignore_missing=False, ), call( - mock.ANY, self.floating_ips[1].id, ignore_missing=False, ), ] - find_floating_ip_mock.assert_has_calls(calls) + self.network.find_ip.assert_has_calls(calls) calls = [] for f in self.floating_ips: @@ -297,9 +293,8 @@ def test_floating_ip_delete_multi(self, find_floating_ip_mock): self.network.delete_ip.assert_has_calls(calls) self.assertIsNone(result) - @mock.patch.object(fip, '_find_floating_ip') - def test_floating_ip_delete_multi_exception(self, find_floating_ip_mock): - find_floating_ip_mock.side_effect = [ + def test_floating_ip_delete_multi_exception(self): + self.network.find_ip.side_effect = [ self.floating_ips[0], exceptions.CommandError, ] @@ -319,13 +314,11 @@ def test_floating_ip_delete_multi_exception(self, find_floating_ip_mock): except exceptions.CommandError as e: self.assertEqual('1 of 2 floating_ips failed to delete.', str(e)) - find_floating_ip_mock.assert_any_call( - mock.ANY, + self.network.find_ip.assert_any_call( self.floating_ips[0].id, ignore_missing=False, ) - find_floating_ip_mock.assert_any_call( - mock.ANY, + self.network.find_ip.assert_any_call( 'unexist_floating_ip', ignore_missing=False, ) @@ -590,9 +583,7 @@ def setUp(self): # Get the command object to test self.cmd = fip.ShowFloatingIP(self.app, self.namespace) - @mock.patch.object(fip, '_find_floating_ip') - def test_floating_ip_show(self, find_floating_ip_mock): - find_floating_ip_mock.return_value = self.floating_ip + def test_floating_ip_show(self): arglist = [ self.floating_ip.id, ] @@ -603,8 +594,7 @@ def test_floating_ip_show(self, find_floating_ip_mock): columns, data = self.cmd.take_action(parsed_args) - find_floating_ip_mock.assert_called_once_with( - mock.ANY, + self.network.find_ip.assert_called_once_with( self.floating_ip.id, ignore_missing=False, ) @@ -636,14 +626,7 @@ def setUp(self): # Get the command object to test self.cmd = fip.SetFloatingIP(self.app, self.namespace) - @mock.patch( - "openstackclient.tests.unit.network.v2.test_floating_ip_network." + - "fip._find_floating_ip" - ) - def test_port_option(self, find_floating_ip_mock): - find_floating_ip_mock.side_effect = [ - self.floating_ip, - ] + def test_port_option(self): arglist = [ self.floating_ip.id, '--port', self.floating_ip.port_id, @@ -660,8 +643,7 @@ def test_port_option(self, find_floating_ip_mock): 'port_id': self.floating_ip.port_id, } - find_floating_ip_mock.assert_called_once_with( - mock.ANY, + self.network.find_ip.assert_called_once_with( self.floating_ip.id, ignore_missing=False, ) @@ -669,14 +651,7 @@ def test_port_option(self, find_floating_ip_mock): self.network.update_ip.assert_called_once_with( self.floating_ip, **attrs) - @mock.patch( - "openstackclient.tests.unit.network.v2.test_floating_ip_network." + - "fip._find_floating_ip" - ) - def test_fixed_ip_option(self, find_floating_ip_mock): - find_floating_ip_mock.side_effect = [ - self.floating_ip, - ] + def test_fixed_ip_option(self): arglist = [ self.floating_ip.id, '--port', self.floating_ip.port_id, @@ -695,24 +670,16 @@ def test_fixed_ip_option(self, find_floating_ip_mock): 'port_id': self.floating_ip.port_id, 'fixed_ip_address': self.floating_ip.fixed_ip_address, } - find_floating_ip_mock.assert_called_once_with( - mock.ANY, + self.network.find_ip.assert_called_once_with( self.floating_ip.id, ignore_missing=False, ) self.network.update_ip.assert_called_once_with( self.floating_ip, **attrs) - @mock.patch( - "openstackclient.tests.unit.network.v2.test_floating_ip_network." + - "fip._find_floating_ip" - ) - def test_port_and_qos_policy_option(self, find_floating_ip_mock): + def test_port_and_qos_policy_option(self): qos_policy = network_fakes.FakeNetworkQosPolicy.create_one_qos_policy() self.network.find_qos_policy = mock.Mock(return_value=qos_policy) - find_floating_ip_mock.side_effect = [ - self.floating_ip, - ] arglist = [ "--qos-policy", qos_policy.id, '--port', self.floating_ip.port_id, @@ -731,22 +698,14 @@ def test_port_and_qos_policy_option(self, find_floating_ip_mock): 'qos_policy_id': qos_policy.id, 'port_id': self.floating_ip.port_id, } - find_floating_ip_mock.assert_called_once_with( - mock.ANY, + self.network.find_ip.assert_called_once_with( self.floating_ip.id, ignore_missing=False, ) self.network.update_ip.assert_called_once_with( self.floating_ip, **attrs) - @mock.patch( - "openstackclient.tests.unit.network.v2.test_floating_ip_network." + - "fip._find_floating_ip" - ) - def test_port_and_no_qos_policy_option(self, find_floating_ip_mock): - find_floating_ip_mock.side_effect = [ - self.floating_ip, - ] + def test_port_and_no_qos_policy_option(self): arglist = [ "--no-qos-policy", '--port', self.floating_ip.port_id, @@ -765,8 +724,7 @@ def test_port_and_no_qos_policy_option(self, find_floating_ip_mock): 'qos_policy_id': None, 'port_id': self.floating_ip.port_id, } - find_floating_ip_mock.assert_called_once_with( - mock.ANY, + self.network.find_ip.assert_called_once_with( self.floating_ip.id, ignore_missing=False, ) @@ -796,14 +754,7 @@ def setUp(self): # Get the command object to test self.cmd = fip.UnsetFloatingIP(self.app, self.namespace) - @mock.patch( - "openstackclient.tests.unit.network.v2.test_floating_ip_network." + - "fip._find_floating_ip" - ) - def test_floating_ip_unset_port(self, find_floating_ip_mock): - find_floating_ip_mock.side_effect = [ - self.floating_ip, - ] + def test_floating_ip_unset_port(self): arglist = [ self.floating_ip.id, "--port", @@ -819,8 +770,7 @@ def test_floating_ip_unset_port(self, find_floating_ip_mock): attrs = { 'port_id': None, } - find_floating_ip_mock.assert_called_once_with( - mock.ANY, + self.network.find_ip.assert_called_once_with( self.floating_ip.id, ignore_missing=False, ) @@ -829,14 +779,7 @@ def test_floating_ip_unset_port(self, find_floating_ip_mock): self.assertIsNone(result) - @mock.patch( - "openstackclient.tests.unit.network.v2.test_floating_ip_network." + - "fip._find_floating_ip" - ) - def test_floating_ip_unset_qos_policy(self, find_floating_ip_mock): - find_floating_ip_mock.side_effect = [ - self.floating_ip, - ] + def test_floating_ip_unset_qos_policy(self): arglist = [ self.floating_ip.id, "--qos-policy", @@ -852,8 +795,7 @@ def test_floating_ip_unset_qos_policy(self, find_floating_ip_mock): attrs = { 'qos_policy_id': None, } - find_floating_ip_mock.assert_called_once_with( - mock.ANY, + self.network.find_ip.assert_called_once_with( self.floating_ip.id, ignore_missing=False, ) From 375964f270e125b8887e0ca4ee1cbe15d5eddf04 Mon Sep 17 00:00:00 2001 From: Colleen Murphy Date: Sun, 21 Jan 2018 20:02:02 +0100 Subject: [PATCH 1876/3095] Add CRUD support for application credentials Add support for creating, retrieving, and deleting application credentials. Application credentials do not support updates. In order to provide a positive user experience for the `--role` option, this patch also includes an improvement to the `identity.common._get_token_resource()` function that allows it to introspect the roles list within a token. This way there is no need to make a request to keystone to retrieve a role object, which would fail most of the time anyway due to keystone's default policy prohibiting unprivileged users from retrieving roles. bp application-credentials Change-Id: I29e03b72acd931305cbdac5a9ff666854d05c6d7 --- .../application-credentials.rst | 109 ++++++ openstackclient/identity/common.py | 7 + .../identity/v3/application_credential.py | 220 +++++++++++++ .../v3/test_application_credential.py | 143 ++++++++ .../tests/unit/identity/v3/fakes.py | 32 ++ .../v3/test_application_credential.py | 309 ++++++++++++++++++ ...plication-credential-a7031a043efc4a25.yaml | 9 + setup.cfg | 5 + 8 files changed, 834 insertions(+) create mode 100644 doc/source/cli/command-objects/application-credentials.rst create mode 100644 openstackclient/identity/v3/application_credential.py create mode 100644 openstackclient/tests/functional/identity/v3/test_application_credential.py create mode 100644 openstackclient/tests/unit/identity/v3/test_application_credential.py create mode 100644 releasenotes/notes/bp-application-credential-a7031a043efc4a25.yaml diff --git a/doc/source/cli/command-objects/application-credentials.rst b/doc/source/cli/command-objects/application-credentials.rst new file mode 100644 index 0000000000..08d85b119d --- /dev/null +++ b/doc/source/cli/command-objects/application-credentials.rst @@ -0,0 +1,109 @@ +====================== +application credential +====================== + +Identity v3 + +With application credentials, a user can grant their applications limited +access to their cloud resources. Once created, users can authenticate with an +application credential by using the ``v3applicationcredential`` auth type. + +application credential create +----------------------------- + +Create new application credential + +.. program:: application credential create +.. code:: bash + + openstack application credential create + [--secret ] + [--role ] + [--expiration ] + [--description ] + [--unrestricted] + + +.. option:: --secret + + Secret to use for authentication (if not provided, one will be generated) + +.. option:: --role + + Roles to authorize (name or ID) (repeat option to set multiple values) + +.. option:: --expiration + + Sets an expiration date for the application credential (format of + YYYY-mm-ddTHH:MM:SS) + +.. option:: --description + + Application credential description + +.. option:: --unrestricted + + Enable application credential to create and delete other application + credentials and trusts (this is potentially dangerous behavior and is + disabled by default) + +.. option:: --restricted + + Prohibit application credential from creating and deleting other + application credentials and trusts (this is the default behavior) + +.. describe:: + + Name of the application credential + + +application credential delete +----------------------------- + +Delete application credential(s) + +.. program:: application credential delete +.. code:: bash + + openstack application credential delete + [ ...] + +.. describe:: + + Application credential(s) to delete (name or ID) + +application credential list +--------------------------- + +List application credentials + +.. program:: application credential list +.. code:: bash + + openstack application credential list + [--user ] + [--user-domain ] + +.. option:: --user + + User whose application credentials to list (name or ID) + +.. option:: --user-domain + + Domain the user belongs to (name or ID). This can be + used in case collisions between user names exist. + +application credential show +--------------------------- + +Display application credential details + +.. program:: application credential show +.. code:: bash + + openstack application credential show + + +.. describe:: + + Application credential to display (name or ID) diff --git a/openstackclient/identity/common.py b/openstackclient/identity/common.py index e119f66019..f36f5f73a9 100644 --- a/openstackclient/identity/common.py +++ b/openstackclient/identity/common.py @@ -101,6 +101,13 @@ def _get_token_resource(client, resource, parsed_name, parsed_domain=None): # user/project under different domain may has a same name if parsed_domain and parsed_domain not in obj['domain'].values(): return parsed_name + if isinstance(obj, list): + for item in obj: + if item['name'] == parsed_name: + return item['id'] + if item['id'] == parsed_name: + return parsed_name + return parsed_name return obj['id'] if obj['name'] == parsed_name else parsed_name # diaper defense in case parsing the token fails except Exception: # noqa diff --git a/openstackclient/identity/v3/application_credential.py b/openstackclient/identity/v3/application_credential.py new file mode 100644 index 0000000000..747fa20ed1 --- /dev/null +++ b/openstackclient/identity/v3/application_credential.py @@ -0,0 +1,220 @@ +# Copyright 2018 SUSE Linux GmbH +# +# 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. +# + +"""Identity v3 Application Credential action implementations""" + +import datetime +import logging + +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils +import six + +from openstackclient.i18n import _ +from openstackclient.identity import common + + +LOG = logging.getLogger(__name__) + + +class CreateApplicationCredential(command.ShowOne): + _description = _("Create new application credential") + + def get_parser(self, prog_name): + parser = super(CreateApplicationCredential, self).get_parser(prog_name) + parser.add_argument( + 'name', + metavar='', + help=_('Name of the application credential'), + ) + parser.add_argument( + '--secret', + metavar='', + help=_('Secret to use for authentication (if not provided, one' + ' will be generated)'), + ) + parser.add_argument( + '--role', + metavar='', + action='append', + default=[], + help=_('Roles to authorize (name or ID) (repeat option to set' + ' multiple values)'), + ) + parser.add_argument( + '--expiration', + metavar='', + help=_('Sets an expiration date for the application credential,' + ' format of YYYY-mm-ddTHH:MM:SS (if not provided, the' + ' application credential will not expire)'), + ) + parser.add_argument( + '--description', + metavar='', + help=_('Application credential description'), + ) + parser.add_argument( + '--unrestricted', + action="store_true", + help=_('Enable application credential to create and delete other' + ' application credentials and trusts (this is potentially' + ' dangerous behavior and is disabled by default)'), + ) + parser.add_argument( + '--restricted', + action="store_true", + help=_('Prohibit application credential from creating and deleting' + ' other application credentials and trusts (this is the' + ' default behavior)'), + ) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + + role_ids = [] + for role in parsed_args.role: + # A user can only create an application credential for themself, + # not for another user even as an admin, and only on the project to + # which they are currently scoped with a subset of the role + # assignments they have on that project. Don't bother trying to + # look up roles via keystone, just introspect the token. + role_id = common._get_token_resource(identity_client, "roles", + role) + role_ids.append(role_id) + + expires_at = None + if parsed_args.expiration: + expires_at = datetime.datetime.strptime(parsed_args.expiration, + '%Y-%m-%dT%H:%M:%S') + + if parsed_args.restricted: + unrestricted = False + else: + unrestricted = parsed_args.unrestricted + + app_cred_manager = identity_client.application_credentials + application_credential = app_cred_manager.create( + parsed_args.name, + roles=role_ids, + expires_at=expires_at, + description=parsed_args.description, + secret=parsed_args.secret, + unrestricted=unrestricted, + ) + + application_credential._info.pop('links', None) + + # Format roles into something sensible + roles = application_credential._info.pop('roles') + msg = ' '.join(r['name'] for r in roles) + application_credential._info['roles'] = msg + + return zip(*sorted(six.iteritems(application_credential._info))) + + +class DeleteApplicationCredential(command.Command): + _description = _("Delete application credentials(s)") + + def get_parser(self, prog_name): + parser = super(DeleteApplicationCredential, self).get_parser(prog_name) + parser.add_argument( + 'application_credential', + metavar='', + nargs="+", + help=_('Application credentials(s) to delete (name or ID)'), + ) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + + errors = 0 + for ac in parsed_args.application_credential: + try: + app_cred = utils.find_resource( + identity_client.application_credentials, ac) + identity_client.application_credentials.delete(app_cred.id) + except Exception as e: + errors += 1 + LOG.error(_("Failed to delete application credential with " + "name or ID '%(ac)s': %(e)s"), + {'ac': ac, 'e': e}) + + if errors > 0: + total = len(parsed_args.application_credential) + msg = (_("%(errors)s of %(total)s application credentials failed " + "to delete.") % {'errors': errors, 'total': total}) + raise exceptions.CommandError(msg) + + +class ListApplicationCredential(command.Lister): + _description = _("List application credentials") + + def get_parser(self, prog_name): + parser = super(ListApplicationCredential, self).get_parser(prog_name) + parser.add_argument( + '--user', + metavar='', + help=_('User whose application credentials to list (name or ID)'), + ) + common.add_user_domain_option_to_parser(parser) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + if parsed_args.user: + user_id = common.find_user(identity_client, + parsed_args.user, + parsed_args.user_domain).id + else: + user_id = None + + columns = ('ID', 'Name', 'Project ID', 'Description', 'Expires At') + data = identity_client.application_credentials.list( + user=user_id) + return (columns, + (utils.get_item_properties( + s, columns, + formatters={}, + ) for s in data)) + + +class ShowApplicationCredential(command.ShowOne): + _description = _("Display application credential details") + + def get_parser(self, prog_name): + parser = super(ShowApplicationCredential, self).get_parser(prog_name) + parser.add_argument( + 'application_credential', + metavar='', + help=_('Application credential to display (name or ID)'), + ) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + app_cred = utils.find_resource(identity_client.application_credentials, + parsed_args.application_credential) + + app_cred._info.pop('links', None) + + # Format roles into something sensible + roles = app_cred._info.pop('roles') + msg = ' '.join(r['name'] for r in roles) + app_cred._info['roles'] = msg + + return zip(*sorted(six.iteritems(app_cred._info))) diff --git a/openstackclient/tests/functional/identity/v3/test_application_credential.py b/openstackclient/tests/functional/identity/v3/test_application_credential.py new file mode 100644 index 0000000000..daf6460785 --- /dev/null +++ b/openstackclient/tests/functional/identity/v3/test_application_credential.py @@ -0,0 +1,143 @@ +# Copyright 2018 SUSE Linux GmbH +# +# 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 datetime + +from tempest.lib.common.utils import data_utils + +from openstackclient.tests.functional.identity.v3 import common + + +class ApplicationCredentialTests(common.IdentityTests): + + APPLICATION_CREDENTIAL_FIELDS = ['id', 'name', 'project_id', + 'description', 'roles', 'expires_at', + 'unrestricted'] + APPLICATION_CREDENTIAL_LIST_HEADERS = ['ID', 'Name', 'Project ID', + 'Description', 'Expires At'] + + def test_application_credential_create(self): + name = data_utils.rand_name('name') + raw_output = self.openstack('application credential create %(name)s' + % {'name': name}) + self.addCleanup( + self.openstack, + 'application credential delete %(name)s' % {'name': name}) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.APPLICATION_CREDENTIAL_FIELDS) + + def _create_role_assignments(self): + try: + user = self.openstack('configuration show -f value' + ' -c auth.username') + except Exception: + user = self.openstack('configuration show -f value' + ' -c auth.user_id') + try: + user_domain = self.openstack('configuration show -f value' + ' -c auth.user_domain_name') + except Exception: + user_domain = self.openstack('configuration show -f value' + ' -c auth.user_domain_id') + try: + project = self.openstack('configuration show -f value' + ' -c auth.project_name') + except Exception: + project = self.openstack('configuration show -f value' + ' -c auth.project_id') + try: + project_domain = self.openstack('configuration show -f value' + ' -c auth.project_domain_name') + except Exception: + project_domain = self.openstack('configuration show -f value' + ' -c auth.project_domain_id') + role1 = self._create_dummy_role() + role2 = self._create_dummy_role() + for role in role1, role2: + self.openstack('role add' + ' --user %(user)s' + ' --user-domain %(user_domain)s' + ' --project %(project)s' + ' --project-domain %(project_domain)s' + ' %(role)s' + % {'user': user, + 'user_domain': user_domain, + 'project': project, + 'project_domain': project_domain, + 'role': role}) + self.addCleanup(self.openstack, + 'role remove' + ' --user %(user)s' + ' --user-domain %(user_domain)s' + ' --project %(project)s' + ' --project-domain %(project_domain)s' + ' %(role)s' + % {'user': user, + 'user_domain': user_domain, + 'project': project, + 'project_domain': project_domain, + 'role': role}) + return role1, role2 + + def test_application_credential_create_with_options(self): + name = data_utils.rand_name('name') + secret = data_utils.rand_name('secret') + description = data_utils.rand_name('description') + tomorrow = (datetime.datetime.utcnow() + + datetime.timedelta(days=1)).strftime('%Y-%m-%dT%H:%M:%S%z') + role1, role2 = self._create_role_assignments() + raw_output = self.openstack('application credential create %(name)s' + ' --secret %(secret)s' + ' --description %(description)s' + ' --expiration %(tomorrow)s' + ' --role %(role1)s' + ' --role %(role2)s' + ' --unrestricted' + % {'name': name, + 'secret': secret, + 'description': description, + 'tomorrow': tomorrow, + 'role1': role1, + 'role2': role2}) + self.addCleanup( + self.openstack, + 'application credential delete %(name)s' % {'name': name}) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.APPLICATION_CREDENTIAL_FIELDS) + + def test_application_credential_delete(self): + name = data_utils.rand_name('name') + self.openstack('application credential create %(name)s' + % {'name': name}) + raw_output = self.openstack('application credential delete ' + '%(name)s' % {'name': name}) + self.assertEqual(0, len(raw_output)) + + def test_application_credential_list(self): + raw_output = self.openstack('application credential list') + items = self.parse_listing(raw_output) + self.assert_table_structure( + items, self.APPLICATION_CREDENTIAL_LIST_HEADERS) + + def test_application_credential_show(self): + name = data_utils.rand_name('name') + raw_output = self.openstack('application credential create %(name)s' + % {'name': name}) + self.addCleanup( + self.openstack, + 'application credential delete %(name)s' % {'name': name}) + raw_output = self.openstack('application credential show ' + '%(name)s' % {'name': name}) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.APPLICATION_CREDENTIAL_FIELDS) diff --git a/openstackclient/tests/unit/identity/v3/fakes.py b/openstackclient/tests/unit/identity/v3/fakes.py index 3e2caf01d5..fc06f9ec25 100644 --- a/openstackclient/tests/unit/identity/v3/fakes.py +++ b/openstackclient/tests/unit/identity/v3/fakes.py @@ -14,6 +14,7 @@ # import copy +import datetime import uuid from keystoneauth1 import access @@ -438,6 +439,34 @@ 'oauth_verifier': oauth_verifier_pin } +app_cred_id = 'app-cred-id' +app_cred_name = 'testing_app_cred' +app_cred_role = {"id": role_id, "name": role_name, "domain": None}, +app_cred_description = 'app credential for testing' +app_cred_expires = datetime.datetime(2022, 1, 1, 0, 0) +app_cred_expires_str = app_cred_expires.strftime('%Y-%m-%dT%H:%M:%S%z') +app_cred_secret = 'moresecuresecret' +APP_CRED_BASIC = { + 'id': app_cred_id, + 'name': app_cred_name, + 'project_id': project_id, + 'roles': app_cred_role, + 'description': None, + 'expires_at': None, + 'unrestricted': False, + 'secret': app_cred_secret +} +APP_CRED_OPTIONS = { + 'id': app_cred_id, + 'name': app_cred_name, + 'project_id': project_id, + 'roles': app_cred_role, + 'description': app_cred_description, + 'expires_at': app_cred_expires_str, + 'unrestricted': False, + 'secret': app_cred_secret +} + def fake_auth_ref(fake_token, fake_service=None): """Create an auth_ref using keystoneauth's fixtures""" @@ -523,6 +552,9 @@ def __init__(self, **kwargs): self.auth = FakeAuth() self.auth.client = mock.Mock() self.auth.client.resource_class = fakes.FakeResource(None, {}) + self.application_credentials = mock.Mock() + self.application_credentials.resource_class = fakes.FakeResource(None, + {}) class FakeFederationManager(object): diff --git a/openstackclient/tests/unit/identity/v3/test_application_credential.py b/openstackclient/tests/unit/identity/v3/test_application_credential.py new file mode 100644 index 0000000000..e7c8ede826 --- /dev/null +++ b/openstackclient/tests/unit/identity/v3/test_application_credential.py @@ -0,0 +1,309 @@ +# Copyright 2018 SUSE Linux GmbH +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +import copy + +import mock +from osc_lib import exceptions +from osc_lib import utils + +from openstackclient.identity.v3 import application_credential +from openstackclient.tests.unit import fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes + + +class TestApplicationCredential(identity_fakes.TestIdentityv3): + + def setUp(self): + super(TestApplicationCredential, self).setUp() + + identity_manager = self.app.client_manager.identity + self.app_creds_mock = identity_manager.application_credentials + self.app_creds_mock.reset_mock() + self.roles_mock = identity_manager.roles + self.roles_mock.reset_mock() + + +class TestApplicationCredentialCreate(TestApplicationCredential): + + def setUp(self): + super(TestApplicationCredentialCreate, self).setUp() + + self.roles_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.ROLE), + loaded=True, + ) + + # Get the command object to test + self.cmd = application_credential.CreateApplicationCredential( + self.app, None) + + def test_application_credential_create_basic(self): + self.app_creds_mock.create.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.APP_CRED_BASIC), + loaded=True, + ) + + name = identity_fakes.app_cred_name + arglist = [ + name + ] + verifylist = [ + ('name', identity_fakes.app_cred_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) + + # Set expected values + kwargs = { + 'secret': None, + 'roles': [], + 'expires_at': None, + 'description': None, + 'unrestricted': False, + } + self.app_creds_mock.create.assert_called_with( + name, + **kwargs + ) + + collist = ('description', 'expires_at', 'id', 'name', 'project_id', + 'roles', 'secret', 'unrestricted') + self.assertEqual(collist, columns) + datalist = ( + None, + None, + identity_fakes.app_cred_id, + identity_fakes.app_cred_name, + identity_fakes.project_id, + identity_fakes.role_name, + identity_fakes.app_cred_secret, + False, + ) + self.assertEqual(datalist, data) + + def test_application_credential_create_with_options(self): + name = identity_fakes.app_cred_name + self.app_creds_mock.create.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.APP_CRED_OPTIONS), + loaded=True, + ) + + arglist = [ + name, + '--secret', 'moresecuresecret', + '--role', identity_fakes.role_id, + '--expiration', identity_fakes.app_cred_expires_str, + '--description', 'credential for testing' + ] + verifylist = [ + ('name', identity_fakes.app_cred_name), + ('secret', 'moresecuresecret'), + ('role', [identity_fakes.role_id]), + ('expiration', identity_fakes.app_cred_expires_str), + ('description', 'credential for testing') + ] + 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) + + # Set expected values + kwargs = { + 'secret': 'moresecuresecret', + 'roles': [identity_fakes.role_id], + 'expires_at': identity_fakes.app_cred_expires, + 'description': 'credential for testing', + 'unrestricted': False + } + self.app_creds_mock.create.assert_called_with( + name, + **kwargs + ) + + collist = ('description', 'expires_at', 'id', 'name', 'project_id', + 'roles', 'secret', 'unrestricted') + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.app_cred_description, + identity_fakes.app_cred_expires_str, + identity_fakes.app_cred_id, + identity_fakes.app_cred_name, + identity_fakes.project_id, + identity_fakes.role_name, + identity_fakes.app_cred_secret, + False, + ) + self.assertEqual(datalist, data) + + +class TestApplicationCredentialDelete(TestApplicationCredential): + + def setUp(self): + super(TestApplicationCredentialDelete, self).setUp() + + # This is the return value for utils.find_resource() + self.app_creds_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.APP_CRED_BASIC), + loaded=True, + ) + self.app_creds_mock.delete.return_value = None + + # Get the command object to test + self.cmd = application_credential.DeleteApplicationCredential( + self.app, None) + + def test_application_credential_delete(self): + arglist = [ + identity_fakes.app_cred_id, + ] + verifylist = [ + ('application_credential', [identity_fakes.app_cred_id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.app_creds_mock.delete.assert_called_with( + identity_fakes.app_cred_id, + ) + self.assertIsNone(result) + + @mock.patch.object(utils, 'find_resource') + def test_delete_multi_app_creds_with_exception(self, find_mock): + find_mock.side_effect = [self.app_creds_mock.get.return_value, + exceptions.CommandError] + arglist = [ + identity_fakes.app_cred_id, + 'nonexistent_app_cred', + ] + verifylist = [ + ('application_credential', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 application credentials failed to' + ' delete.', str(e)) + + find_mock.assert_any_call(self.app_creds_mock, + identity_fakes.app_cred_id) + find_mock.assert_any_call(self.app_creds_mock, + 'nonexistent_app_cred') + + self.assertEqual(2, find_mock.call_count) + self.app_creds_mock.delete.assert_called_once_with( + identity_fakes.app_cred_id) + + +class TestApplicationCredentialList(TestApplicationCredential): + + def setUp(self): + super(TestApplicationCredentialList, self).setUp() + + self.app_creds_mock.list.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.APP_CRED_BASIC), + loaded=True, + ), + ] + + # Get the command object to test + self.cmd = application_credential.ListApplicationCredential(self.app, + None) + + def test_application_credential_list(self): + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + self.app_creds_mock.list.assert_called_with(user=None) + + collist = ('ID', 'Name', 'Project ID', 'Description', 'Expires At') + self.assertEqual(collist, columns) + datalist = (( + identity_fakes.app_cred_id, + identity_fakes.app_cred_name, + identity_fakes.project_id, + None, + None + ), ) + self.assertEqual(datalist, tuple(data)) + + +class TestApplicationCredentialShow(TestApplicationCredential): + + def setUp(self): + super(TestApplicationCredentialShow, self).setUp() + + self.app_creds_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.APP_CRED_BASIC), + loaded=True, + ) + + # Get the command object to test + self.cmd = application_credential.ShowApplicationCredential(self.app, + None) + + def test_application_credential_show(self): + arglist = [ + identity_fakes.app_cred_id, + ] + verifylist = [ + ('application_credential', identity_fakes.app_cred_id), + ] + 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.app_creds_mock.get.assert_called_with(identity_fakes.app_cred_id) + + collist = ('description', 'expires_at', 'id', 'name', 'project_id', + 'roles', 'secret', 'unrestricted') + self.assertEqual(collist, columns) + datalist = ( + None, + None, + identity_fakes.app_cred_id, + identity_fakes.app_cred_name, + identity_fakes.project_id, + identity_fakes.role_name, + identity_fakes.app_cred_secret, + False, + ) + self.assertEqual(datalist, data) diff --git a/releasenotes/notes/bp-application-credential-a7031a043efc4a25.yaml b/releasenotes/notes/bp-application-credential-a7031a043efc4a25.yaml new file mode 100644 index 0000000000..2b4ab18980 --- /dev/null +++ b/releasenotes/notes/bp-application-credential-a7031a043efc4a25.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + Adds support for creating, reading, and deleting application credentials + via the ``appication credential`` command. With application credentials, a + user can grant their applications limited access to their cloud resources. + Once created, users can authenticate with an application credential by + using the ``v3applicationcredential`` auth type. + [`blueprint application-credentials `_] diff --git a/setup.cfg b/setup.cfg index 63bfdafb13..6d348fbb04 100644 --- a/setup.cfg +++ b/setup.cfg @@ -202,6 +202,11 @@ openstack.identity.v2 = openstack.identity.v3 = access_token_create = openstackclient.identity.v3.token:CreateAccessToken + application_credential_create = openstackclient.identity.v3.application_credential:CreateApplicationCredential + application_credential_delete = openstackclient.identity.v3.application_credential:DeleteApplicationCredential + application_credential_list = openstackclient.identity.v3.application_credential:ListApplicationCredential + application_credential_show = openstackclient.identity.v3.application_credential:ShowApplicationCredential + catalog_list = openstackclient.identity.v3.catalog:ListCatalog catalog_show = openstackclient.identity.v3.catalog:ShowCatalog From 9f2ad36b91bd3a92be7de418c88987dfb90dc589 Mon Sep 17 00:00:00 2001 From: "James E. Blair" Date: Wed, 24 Jan 2018 16:45:18 -0800 Subject: [PATCH 1877/3095] Zuul: Remove project name Zuul no longer requires the project-name for in-repo configuration. Omitting it makes forking or renaming projects easier. Change-Id: I92ea13e64dcdc24a8dd1fd6d17df9f771b2fc1e0 --- .zuul.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.zuul.yaml b/.zuul.yaml index 98446e6870..3888c75f9c 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -188,7 +188,6 @@ - osc-tox-py35-tips - project: - name: openstack/python-openstackclient templates: - openstackclient-plugin-jobs - osc-tox-unit-tips From 07014e09c9393364d12cf27f5462ceea15de0658 Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Fri, 2 Feb 2018 17:36:03 -0600 Subject: [PATCH 1878/3095] Make osc-functional-devstack-tips actually use tips The base job has tox_install_siblings: false - which we want. But that means we need tox_install_siblings: true on the tips job. While we're at it - add fetch-tox-output so that we have tox log files in the fetched build output for easier verification of what wound up installed. Don't look for ResourceNotFound string in test The string ResourceNotFound is not in the error string anymore. Look for the text that is. Depends-On: https://review.openstack.org/541033 Change-Id: Id6de1485bcafb41f238f3e74277094ce64a6acf4 --- .zuul.yaml | 1 + openstackclient/tests/functional/common/test_extension.py | 2 +- playbooks/osc-devstack/post.yaml | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.zuul.yaml b/.zuul.yaml index c19c7a022b..1080b3ffa4 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -175,6 +175,7 @@ s-object: false s-proxy: false tox_envlist: functional + tox_install_siblings: true - project-template: name: osc-tox-unit-tips diff --git a/openstackclient/tests/functional/common/test_extension.py b/openstackclient/tests/functional/common/test_extension.py index e3a91fe657..db50855f71 100644 --- a/openstackclient/tests/functional/common/test_extension.py +++ b/openstackclient/tests/functional/common/test_extension.py @@ -105,7 +105,7 @@ def test_extension_show_not_exist(self): try: self.openstack('extension show ' + name) except tempest_exc.CommandFailed as e: - self.assertIn('ResourceNotFound', str(e)) + self.assertIn('No Extension found for', str(e)) self.assertIn(name, str(e)) else: self.fail('CommandFailed should be raised') diff --git a/playbooks/osc-devstack/post.yaml b/playbooks/osc-devstack/post.yaml index a198364866..7f0cb19824 100644 --- a/playbooks/osc-devstack/post.yaml +++ b/playbooks/osc-devstack/post.yaml @@ -1,3 +1,4 @@ - hosts: all roles: + - fetch-tox-output - fetch-subunit-output From d32664150fbc00340f3ff4304c13abf9a191299a Mon Sep 17 00:00:00 2001 From: Gage Hugo Date: Thu, 6 Jul 2017 16:21:03 -0500 Subject: [PATCH 1879/3095] Add project tags functionality This change adds tags functionality for projects in keystone. A user can add a single tag with "--tag", chain "--tag" to add multiple tags, or clear tags with "--no-tag". Change-Id: I31cfef3e76dcefe299dacb00c11bb1a10a252628 Partially-Implements: bp project-tags --- doc/source/cli/command-objects/project.rst | 35 ++++++ openstackclient/identity/v3/project.py | 9 +- openstackclient/identity/v3/tag.py | 116 ++++++++++++++++++ .../tests/unit/identity/v3/fakes.py | 7 ++ .../tests/unit/identity/v3/test_domain.py | 5 +- .../tests/unit/identity/v3/test_project.py | 103 +++++++++++++++- .../bp-project-tags-b544aef9672d415b.yaml | 8 ++ 7 files changed, 278 insertions(+), 5 deletions(-) create mode 100644 openstackclient/identity/v3/tag.py create mode 100644 releasenotes/notes/bp-project-tags-b544aef9672d415b.yaml diff --git a/doc/source/cli/command-objects/project.rst b/doc/source/cli/command-objects/project.rst index cb0941ca12..6891a79a48 100644 --- a/doc/source/cli/command-objects/project.rst +++ b/doc/source/cli/command-objects/project.rst @@ -19,6 +19,7 @@ Create new project [--enable | --disable] [--property ] [--or-show] + [--tag ] .. option:: --domain @@ -56,6 +57,13 @@ Create new project If the project already exists return the existing project data and do not fail. +.. option:: --tag + + Add a tag to the project + (repeat option to set multiple tags) + + .. versionadded:: 3 + .. _project_create-name: .. describe:: @@ -98,6 +106,8 @@ List projects [--my-projects] [--long] [--sort [:,:,..]] + [--tags [,,...]] [--tags-any [,,...]] + [--not-tags [,,...]] [--not-tags-any [,,...]] .. option:: --domain @@ -127,6 +137,30 @@ List projects multiple keys and directions can be specified --sort [:,:,..] +.. option:: --tags [,,...] + + List projects which have all given tag(s) + + .. versionadded:: 3 + +.. option:: --tags-any [,,...] + + List projects which have any given tag(s) + + .. versionadded:: 3 + +.. option:: --not-tags [,,...] + + Exclude projects which have all given tag(s) + + .. versionadded:: 3 + +.. option:: --not-tags-any [,,...] + + Exclude projects which have any given tag(s) + + .. versionadded:: 3 + project set ----------- @@ -141,6 +175,7 @@ Set project properties [--description ] [--enable | --disable] [--property ] + [--tag | --clear-tags | --remove-tags ] .. option:: --name diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index 60efbac4a4..e819a0a89b 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -26,7 +26,7 @@ from openstackclient.i18n import _ from openstackclient.identity import common - +from openstackclient.identity.v3 import tag LOG = logging.getLogger(__name__) @@ -79,6 +79,7 @@ def get_parser(self, prog_name): action='store_true', help=_('Return existing project'), ) + tag.add_tag_option_to_parser_for_create(parser, _('project')) return parser def take_action(self, parsed_args): @@ -102,6 +103,7 @@ def take_action(self, parsed_args): kwargs = {} if parsed_args.property: kwargs = parsed_args.property.copy() + kwargs['tags'] = list(set(parsed_args.tags)) try: project = identity_client.projects.create( @@ -207,6 +209,7 @@ def get_parser(self, prog_name): '(default: asc), repeat this option to specify multiple ' 'keys and directions.'), ) + tag.add_tag_filtering_option_to_parser(parser, _('projects')) return parser def take_action(self, parsed_args): @@ -234,6 +237,8 @@ def take_action(self, parsed_args): kwargs['user'] = user_id + tag.get_tag_filtering_args(parsed_args, kwargs) + if parsed_args.my_projects: # NOTE(adriant): my-projects supersedes all the other filters. kwargs = {'user': self.app.client_manager.auth_ref.user_id} @@ -303,6 +308,7 @@ def get_parser(self, prog_name): help=_('Set a property on ' '(repeat option to set multiple properties)'), ) + tag.add_tag_option_to_parser_for_set(parser, _('project')) return parser def take_action(self, parsed_args): @@ -323,6 +329,7 @@ def take_action(self, parsed_args): kwargs['enabled'] = False if parsed_args.property: kwargs.update(parsed_args.property) + tag.update_tags_in_args(parsed_args, project, kwargs) identity_client.projects.update(project.id, **kwargs) diff --git a/openstackclient/identity/v3/tag.py b/openstackclient/identity/v3/tag.py new file mode 100644 index 0000000000..abf022d486 --- /dev/null +++ b/openstackclient/identity/v3/tag.py @@ -0,0 +1,116 @@ +# 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 argparse + +from openstackclient.i18n import _ + + +class _CommaListAction(argparse.Action): + + def __call__(self, parser, namespace, values, option_string=None): + setattr(namespace, self.dest, values.split(',')) + + +def add_tag_filtering_option_to_parser(parser, collection_name): + parser.add_argument( + '--tags', + metavar='[,,...]', + action=_CommaListAction, + help=_('List %s which have all given tag(s) ' + '(Comma-separated list of tags)') % collection_name + ) + parser.add_argument( + '--tags-any', + metavar='[,,...]', + action=_CommaListAction, + help=_('List %s which have any given tag(s) ' + '(Comma-separated list of tags)') % collection_name + ) + parser.add_argument( + '--not-tags', + metavar='[,,...]', + action=_CommaListAction, + help=_('Exclude %s which have all given tag(s) ' + '(Comma-separated list of tags)') % collection_name + ) + parser.add_argument( + '--not-tags-any', + metavar='[,,...]', + action=_CommaListAction, + help=_('Exclude %s which have any given tag(s) ' + '(Comma-separated list of tags)') % collection_name + ) + + +def get_tag_filtering_args(parsed_args, args): + if parsed_args.tags: + args['tags'] = ','.join(parsed_args.tags) + if parsed_args.tags_any: + args['tags-any'] = ','.join(parsed_args.tags_any) + if parsed_args.not_tags: + args['not-tags'] = ','.join(parsed_args.not_tags) + if parsed_args.not_tags_any: + args['not-tags-any'] = ','.join(parsed_args.not_tags_any) + + +def add_tag_option_to_parser_for_create(parser, resource_name): + tag_group = parser.add_mutually_exclusive_group() + tag_group.add_argument( + '--tag', + action='append', + dest='tags', + metavar='', + default=[], + help=_('Tag to be added to the %s ' + '(repeat option to set multiple tags)') % resource_name + ) + + +def add_tag_option_to_parser_for_set(parser, resource_name): + parser.add_argument( + '--tag', + action='append', + dest='tags', + metavar='', + default=[], + help=_('Tag to be added to the %s ' + '(repeat option to set multiple tags)') % resource_name + ) + parser.add_argument( + '--clear-tags', + action='store_true', + help=_('Clear tags associated with the %s. Specify ' + 'both --tag and --clear-tags to overwrite ' + 'current tags') % resource_name + ) + parser.add_argument( + '--remove-tag', + metavar='', + default=[], + help=_('Tag to be deleted from the %s ' + '(repeat option to delete multiple tags)') % resource_name + ) + + +def update_tags_in_args(parsed_args, obj, args): + if parsed_args.clear_tags: + args['tags'] = [] + obj.tags = [] + if parsed_args.remove_tag: + if parsed_args.remove_tag in obj.tags: + obj.tags.remove(parsed_args.remove_tag) + args['tags'] = list(set(obj.tags)) + return + if parsed_args.tags: + args['tags'] = list(set(obj.tags).union( + set(parsed_args.tags))) diff --git a/openstackclient/tests/unit/identity/v3/fakes.py b/openstackclient/tests/unit/identity/v3/fakes.py index 3e2caf01d5..3770e29fe9 100644 --- a/openstackclient/tests/unit/identity/v3/fakes.py +++ b/openstackclient/tests/unit/identity/v3/fakes.py @@ -34,6 +34,7 @@ 'name': domain_name, 'description': domain_description, 'enabled': True, + 'tags': [], 'links': base_url + 'domains/' + domain_id, } @@ -115,6 +116,7 @@ 'description': project_description, 'enabled': True, 'domain_id': domain_id, + 'tags': [], 'links': base_url + 'projects/' + project_id, } @@ -124,6 +126,7 @@ 'description': project_description + 'plus four more', 'enabled': True, 'domain_id': domain_id, + 'tags': [], 'links': base_url + 'projects/' + project_id, } @@ -145,6 +148,7 @@ 'enabled': True, 'domain_id': domain_id, 'parent_id': project_id, + 'tags': [], 'links': base_url + 'projects/' + (project_id + '-with-parent'), } @@ -155,6 +159,7 @@ 'enabled': True, 'domain_id': domain_id, 'parent_id': PROJECT_WITH_PARENT['id'], + 'tags': [], 'links': base_url + 'projects/' + (project_id + '-with-grandparent'), } @@ -619,6 +624,7 @@ def create_one_project(attrs=None): 'is_domain': False, 'domain_id': 'domain-id-' + uuid.uuid4().hex, 'parent_id': 'parent-id-' + uuid.uuid4().hex, + 'tags': [], 'links': 'links-' + uuid.uuid4().hex, } project_info.update(attrs) @@ -666,6 +672,7 @@ def create_one_domain(attrs=None): 'name': 'domain-name-' + uuid.uuid4().hex, 'description': 'domain-description-' + uuid.uuid4().hex, 'enabled': True, + 'tags': [], 'links': 'links-' + uuid.uuid4().hex, } domain_info.update(attrs) diff --git a/openstackclient/tests/unit/identity/v3/test_domain.py b/openstackclient/tests/unit/identity/v3/test_domain.py index 36f13d3326..014986e573 100644 --- a/openstackclient/tests/unit/identity/v3/test_domain.py +++ b/openstackclient/tests/unit/identity/v3/test_domain.py @@ -31,6 +31,7 @@ class TestDomainCreate(TestDomain): 'enabled', 'id', 'name', + 'tags' ) def setUp(self): @@ -43,6 +44,7 @@ def setUp(self): True, self.domain.id, self.domain.name, + self.domain.tags ) # Get the command object to test @@ -390,12 +392,13 @@ def test_domain_show(self): self.domain.id, ) - collist = ('description', 'enabled', 'id', 'name') + collist = ('description', 'enabled', 'id', 'name', 'tags') self.assertEqual(collist, columns) datalist = ( self.domain.description, True, self.domain.id, self.domain.name, + self.domain.tags ) self.assertEqual(datalist, data) diff --git a/openstackclient/tests/unit/identity/v3/test_project.py b/openstackclient/tests/unit/identity/v3/test_project.py index 16ac3116f9..266da22775 100644 --- a/openstackclient/tests/unit/identity/v3/test_project.py +++ b/openstackclient/tests/unit/identity/v3/test_project.py @@ -50,6 +50,7 @@ class TestProjectCreate(TestProject): 'is_domain', 'name', 'parent_id', + 'tags' ) def setUp(self): @@ -67,6 +68,7 @@ def setUp(self): False, self.project.name, self.project.parent_id, + self.project.tags ) # Get the command object to test self.cmd = project.CreateProject(self.app, None) @@ -80,6 +82,7 @@ def test_project_create_no_options(self): ('enable', False), ('disable', False), ('name', self.project.name), + ('tags', []) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -95,6 +98,7 @@ def test_project_create_no_options(self): 'description': None, 'enabled': True, 'parent': None, + 'tags': [] } # ProjectManager.create(name=, domain=, description=, # enabled=, **kwargs) @@ -110,6 +114,7 @@ def test_project_create_no_options(self): 'is_domain', 'name', 'parent_id', + 'tags' ) self.assertEqual(collist, columns) datalist = ( @@ -120,6 +125,7 @@ def test_project_create_no_options(self): False, self.project.name, self.project.parent_id, + self.project.tags ) self.assertEqual(datalist, data) @@ -134,6 +140,7 @@ def test_project_create_description(self): ('disable', False), ('name', self.project.name), ('parent', None), + ('tags', []) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -149,6 +156,7 @@ def test_project_create_description(self): 'description': 'new desc', 'enabled': True, 'parent': None, + 'tags': [] } # ProjectManager.create(name=, domain=, description=, # enabled=, **kwargs) @@ -170,6 +178,7 @@ def test_project_create_domain(self): ('disable', False), ('name', self.project.name), ('parent', None), + ('tags', []) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -185,6 +194,7 @@ def test_project_create_domain(self): 'description': None, 'enabled': True, 'parent': None, + 'tags': [] } # ProjectManager.create(name=, domain=, description=, # enabled=, **kwargs) @@ -206,6 +216,7 @@ def test_project_create_domain_no_perms(self): ('disable', False), ('name', self.project.name), ('parent', None), + ('tags', []) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) mocker = mock.Mock() @@ -221,6 +232,7 @@ def test_project_create_domain_no_perms(self): 'description': None, 'enabled': True, 'parent': None, + 'tags': [] } self.projects_mock.create.assert_called_with( **kwargs @@ -238,6 +250,7 @@ def test_project_create_enable(self): ('disable', False), ('name', self.project.name), ('parent', None), + ('tags', []) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -253,6 +266,7 @@ def test_project_create_enable(self): 'description': None, 'enabled': True, 'parent': None, + 'tags': [] } # ProjectManager.create(name=, domain=, description=, # enabled=, **kwargs) @@ -288,6 +302,7 @@ def test_project_create_disable(self): 'description': None, 'enabled': False, 'parent': None, + 'tags': [] } # ProjectManager.create(name=, domain=, # description=, enabled=, **kwargs) @@ -324,6 +339,7 @@ def test_project_create_property(self): 'parent': None, 'fee': 'fi', 'fo': 'fum', + 'tags': [] } # ProjectManager.create(name=, domain=, description=, # enabled=, **kwargs) @@ -352,6 +368,7 @@ def test_project_create_parent(self): ('enable', False), ('disable', False), ('name', self.project.name), + ('tags', []) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -363,6 +380,7 @@ def test_project_create_parent(self): 'parent': self.parent.id, 'description': None, 'enabled': True, + 'tags': [] } self.projects_mock.create.assert_called_with( @@ -377,6 +395,7 @@ def test_project_create_parent(self): 'is_domain', 'name', 'parent_id', + 'tags' ) self.assertEqual(columns, collist) datalist = ( @@ -387,6 +406,7 @@ def test_project_create_parent(self): self.project.is_domain, self.project.name, self.parent.id, + self.project.tags ) self.assertEqual(data, datalist) @@ -417,6 +437,43 @@ def test_project_create_invalid_parent(self): parsed_args, ) + def test_project_create_with_tags(self): + arglist = [ + '--domain', self.project.domain_id, + '--tag', 'foo', + self.project.name, + ] + verifylist = [ + ('domain', self.project.domain_id), + ('enable', False), + ('disable', False), + ('name', self.project.name), + ('parent', None), + ('tags', ['foo']) + ] + 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) + + # Set expected values + kwargs = { + 'name': self.project.name, + 'domain': self.project.domain_id, + 'description': None, + 'enabled': True, + 'parent': None, + 'tags': ['foo'] + } + self.projects_mock.create.assert_called_with( + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) + class TestProjectDelete(TestProject): @@ -816,6 +873,38 @@ def test_project_set_property(self): ) self.assertIsNone(result) + def test_project_set_tags(self): + arglist = [ + '--name', 'qwerty', + '--domain', self.project.domain_id, + '--tag', 'foo', + self.project.name, + ] + verifylist = [ + ('name', 'qwerty'), + ('domain', self.project.domain_id), + ('enable', False), + ('disable', False), + ('project', self.project.name), + ('tags', ['foo']) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'name': 'qwerty', + 'tags': ['foo'] + } + # ProjectManager.update(project, name=, domain=, description=, + # enabled=, **kwargs) + self.projects_mock.update.assert_called_with( + self.project.id, + **kwargs + ) + self.assertIsNone(result) + class TestProjectShow(TestProject): @@ -867,6 +956,7 @@ def test_project_show(self): 'is_domain', 'name', 'parent_id', + 'tags' ) self.assertEqual(collist, columns) datalist = ( @@ -877,6 +967,7 @@ def test_project_show(self): False, self.project.name, self.project.parent_id, + self.project.tags ) self.assertEqual(datalist, data) @@ -926,6 +1017,7 @@ def test_project_show_parents(self): 'name', 'parent_id', 'parents', + 'tags' ) self.assertEqual(columns, collist) datalist = ( @@ -936,7 +1028,8 @@ def test_project_show_parents(self): self.project.is_domain, self.project.name, self.project.parent_id, - [{'project': {'id': self.project.parent_id}}] + [{'project': {'id': self.project.parent_id}}], + self.project.tags ) self.assertEqual(data, datalist) @@ -985,6 +1078,7 @@ def test_project_show_subtree(self): 'name', 'parent_id', 'subtree', + 'tags' ) self.assertEqual(columns, collist) datalist = ( @@ -995,7 +1089,8 @@ def test_project_show_subtree(self): self.project.is_domain, self.project.name, self.project.parent_id, - [{'project': {'id': 'children-id'}}] + [{'project': {'id': 'children-id'}}], + self.project.tags ) self.assertEqual(data, datalist) @@ -1047,6 +1142,7 @@ def test_project_show_parents_and_children(self): 'parent_id', 'parents', 'subtree', + 'tags' ) self.assertEqual(columns, collist) datalist = ( @@ -1058,7 +1154,8 @@ def test_project_show_parents_and_children(self): self.project.name, self.project.parent_id, [{'project': {'id': self.project.parent_id}}], - [{'project': {'id': 'children-id'}}] + [{'project': {'id': 'children-id'}}], + self.project.tags ) self.assertEqual(data, datalist) diff --git a/releasenotes/notes/bp-project-tags-b544aef9672d415b.yaml b/releasenotes/notes/bp-project-tags-b544aef9672d415b.yaml new file mode 100644 index 0000000000..0da35ac37d --- /dev/null +++ b/releasenotes/notes/bp-project-tags-b544aef9672d415b.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Add ``--tag`` option to ``project create`` command, ``--tag``, ``--clear-tags``, and + ``--remove-tag`` options to ``project set`` command. Add ``--tags``, ``--tags-any``, + ``--not-tags``, and ``--not-tags-any`` options to ``project list`` command to filter + list results by different projects based on their tags. + [`blueprint project-tags `_] From 4a9e84be994575146b30bd40a341d5686174eaad Mon Sep 17 00:00:00 2001 From: Tytus Kurek Date: Mon, 4 Sep 2017 08:14:37 +0200 Subject: [PATCH 1880/3095] Add support for "--dns-domain" argument This patchset implements support for "--dns-domain" argument to the following commands: "openstack port create" / "openstack port set". Change-Id: I4bb001054b00a969b74db3bb310e567033bf589b Depends-On: https://review.openstack.org/#/c/500660/ Closes-Bug: #1714878 Partial-Bug: #1704769 --- doc/source/cli/command-objects/port.rst | 16 ++++++++++++++-- openstackclient/network/v2/port.py | 8 ++++++++ openstackclient/tests/unit/network/v2/fakes.py | 1 + .../tests/unit/network/v2/test_port.py | 5 +++++ .../notes/bug-1714878-46806jv2yv13q054.yaml | 8 ++++++++ 5 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/bug-1714878-46806jv2yv13q054.yaml diff --git a/doc/source/cli/command-objects/port.rst b/doc/source/cli/command-objects/port.rst index c2da09b321..c3a9798cf9 100644 --- a/doc/source/cli/command-objects/port.rst +++ b/doc/source/cli/command-objects/port.rst @@ -28,6 +28,7 @@ Create new port [--enable | --disable] [--mac-address ] [--security-group | --no-security-group] + [--dns-domain ] [--dns-name ] [--allowed-address ip-address=[,mac-address=]] [--qos-policy ] @@ -95,9 +96,14 @@ Create new port Associate no security groups with this port +.. option:: --dns-domain + + Set DNS domain for this port + (requires dns_domain for ports extension) + .. option:: --dns-name - Set DNS name to this port + Set DNS name for this port (requires DNS integration extension) .. option:: --allowed-address ip-address=[,mac-address=] @@ -256,6 +262,7 @@ Set port properties [--security-group ] [--no-security-group] [--enable-port-security | --disable-port-security] + [--dns-domain ] [--dns-name ] [--allowed-address ip-address=[,mac-address=]] [--no-allowed-address] @@ -346,9 +353,14 @@ Set port properties Disable port security for this port +.. option:: --dns-domain + + Set DNS domain for this port + (requires dns_domain for ports extension) + .. option:: --dns-name - Set DNS name to this port + Set DNS name for this port (requires DNS integration extension) .. option:: --allowed-address ip-address=[,mac-address=] diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 9536fe8687..ea5a04e633 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -127,6 +127,8 @@ def _get_attrs(client_manager, parsed_args): if parsed_args.mac_address is not None: attrs['mac_address'] = parsed_args.mac_address + if parsed_args.dns_domain is not None: + attrs['dns_domain'] = parsed_args.dns_domain if parsed_args.dns_name is not None: attrs['dns_name'] = parsed_args.dns_name # It is possible that name is not updated during 'port set' @@ -268,6 +270,12 @@ def _add_updatable_args(parser): metavar='', help=argparse.SUPPRESS, ) + parser.add_argument( + '--dns-domain', + metavar='dns-domain', + help=_("Set DNS domain to this port " + "(requires dns_domain extension for ports)") + ) parser.add_argument( '--dns-name', metavar='dns-name', diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index bdc1c1fb0e..f13b6b46d7 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -565,6 +565,7 @@ def create_one_port(attrs=None): 'device_id': 'device-id-' + uuid.uuid4().hex, 'device_owner': 'compute:nova', 'dns_assignment': [{}], + 'dns_domain': 'dns-domain-' + uuid.uuid4().hex, 'dns_name': 'dns-name-' + uuid.uuid4().hex, 'extra_dhcp_opts': [{}], 'fixed_ips': [{'ip_address': '10.0.0.3', diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index 45e1045da0..7cc8ac2897 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -50,6 +50,7 @@ def _get_common_cols_data(fake_port): 'device_id', 'device_owner', 'dns_assignment', + 'dns_domain', 'dns_name', 'extra_dhcp_opts', 'fixed_ips', @@ -78,6 +79,7 @@ def _get_common_cols_data(fake_port): fake_port.device_id, fake_port.device_owner, utils.format_list_of_dicts(fake_port.dns_assignment), + fake_port.dns_domain, fake_port.dns_name, utils.format_list_of_dicts(fake_port.extra_dhcp_opts), utils.format_list_of_dicts(fake_port.fixed_ips), @@ -152,6 +154,7 @@ def test_create_full_options(self): '--binding-profile', 'foo=bar', '--binding-profile', 'foo2=bar2', '--network', self._port.network_id, + '--dns-domain', 'example.org', '--dns-name', '8.8.8.8', 'test-port', @@ -169,6 +172,7 @@ def test_create_full_options(self): ('vnic_type', 'macvtap'), ('binding_profile', {'foo': 'bar', 'foo2': 'bar2'}), ('network', self._port.network_id), + ('dns_domain', 'example.org'), ('dns_name', '8.8.8.8'), ('name', 'test-port'), ] @@ -187,6 +191,7 @@ def test_create_full_options(self): 'binding:vnic_type': 'macvtap', 'binding:profile': {'foo': 'bar', 'foo2': 'bar2'}, 'network_id': self._port.network_id, + 'dns_domain': 'example.org', 'dns_name': '8.8.8.8', 'name': 'test-port', }) diff --git a/releasenotes/notes/bug-1714878-46806jv2yv13q054.yaml b/releasenotes/notes/bug-1714878-46806jv2yv13q054.yaml new file mode 100644 index 0000000000..a2f376b8c2 --- /dev/null +++ b/releasenotes/notes/bug-1714878-46806jv2yv13q054.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Add ``--dns-domain`` option to ``port create`` and ``port set`` commands. + Requires the ``dns_domain for ports`` extension to be enabled. See the + `Neutron DNS integration `_ + documentation for information how to use this. + [Bug `1714878 `_] From 1eae301c4fab30c551ed7542cdaf8735cbbc3822 Mon Sep 17 00:00:00 2001 From: Jose Castro Leon Date: Wed, 25 Oct 2017 15:39:44 +0200 Subject: [PATCH 1881/3095] Add support for endpoint group commands Implements the commands for endpoint group filter management. Includes the CRUD management of the endpoint groups and the association management between them and the projects that are using this method. Implements: blueprint keystone-endpoint-filter Change-Id: I4265f7f8598d028191e90d76781b7b6ece6fef64 --- .../cli/command-objects/endpoint_group.rst | 28 ++ doc/source/cli/commands.rst | 1 + openstackclient/identity/v3/endpoint_group.py | 324 ++++++++++++++++++ .../tests/unit/identity/v3/fakes.py | 16 + ...stone-endpoint-group-0c55debbb66844f2.yaml | 7 + setup.cfg | 8 + 6 files changed, 384 insertions(+) create mode 100644 doc/source/cli/command-objects/endpoint_group.rst create mode 100644 openstackclient/identity/v3/endpoint_group.py create mode 100644 releasenotes/notes/keystone-endpoint-group-0c55debbb66844f2.yaml diff --git a/doc/source/cli/command-objects/endpoint_group.rst b/doc/source/cli/command-objects/endpoint_group.rst new file mode 100644 index 0000000000..ccfe5f6615 --- /dev/null +++ b/doc/source/cli/command-objects/endpoint_group.rst @@ -0,0 +1,28 @@ +============== +endpoint group +============== + +A **endpoint group** is used to create groups of endpoints that then +can be used to filter the endpoints that are available to a project. +Applicable to Identity v3 + +.. autoprogram-cliff:: openstack.identity.v3 + :command: endpoint group add project + +.. autoprogram-cliff:: openstack.identity.v3 + :command: endpoint group create + +.. autoprogram-cliff:: openstack.identity.v3 + :command: endpoint group delete + +.. autoprogram-cliff:: openstack.identity.v3 + :command: endpoint group list + +.. autoprogram-cliff:: openstack.identity.v3 + :command: endpoint group remove project + +.. autoprogram-cliff:: openstack.identity.v3 + :command: endpoint group set + +.. autoprogram-cliff:: openstack.identity.v3 + :command: endpoint group show diff --git a/doc/source/cli/commands.rst b/doc/source/cli/commands.rst index 0c1992ad23..d840549c3f 100644 --- a/doc/source/cli/commands.rst +++ b/doc/source/cli/commands.rst @@ -91,6 +91,7 @@ referring to both Compute and Volume quotas. * ``domain``: (**Identity**) a grouping of projects * ``ec2 credentials``: (**Identity**) AWS EC2-compatible credentials * ``endpoint``: (**Identity**) the base URL used to contact a specific service +* ``endpoint group``: (**Identity**) group endpoints to be used as filters * ``extension``: (**Compute**, **Identity**, **Network**, **Volume**) OpenStack server API extensions * ``federation protocol``: (**Identity**) the underlying protocol used while federating identities * ``flavor``: (**Compute**) predefined server configurations: ram, root disk and so on diff --git a/openstackclient/identity/v3/endpoint_group.py b/openstackclient/identity/v3/endpoint_group.py new file mode 100644 index 0000000000..e254973bb5 --- /dev/null +++ b/openstackclient/identity/v3/endpoint_group.py @@ -0,0 +1,324 @@ +# 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. +# + +"""Identity v3 Endpoint Group action implementations""" + +import json +import logging + +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils +import six + +from openstackclient.i18n import _ +from openstackclient.identity import common + + +LOG = logging.getLogger(__name__) + + +class _FiltersReader(object): + _description = _("Helper class capable of reading filters from files") + + def _read_filters(self, path): + """Read and parse rules from path + + Expect the file to contain a valid JSON structure. + + :param path: path to the file + :return: loaded and valid dictionary with filters + :raises exception.CommandError: In case the file cannot be + accessed or the content is not a valid JSON. + + Example of the content of the file: + { + "interface": "admin", + "service_id": "1b501a" + } + """ + blob = utils.read_blob_file_contents(path) + try: + rules = json.loads(blob) + except ValueError as e: + msg = _("An error occurred when reading filters from file " + "%(path)s: %(error)s") % {"path": path, "error": e} + raise exceptions.CommandError(msg) + else: + return rules + + +class AddProjectToEndpointGroup(command.Command): + _description = _("Add a project to an endpoint group") + + def get_parser(self, prog_name): + parser = super( + AddProjectToEndpointGroup, self).get_parser(prog_name) + parser.add_argument( + 'endpointgroup', + metavar='', + help=_('Endpoint group (name or ID)'), + ) + parser.add_argument( + 'project', + metavar='', + help=_('Project to associate (name or ID)'), + ) + common.add_project_domain_option_to_parser(parser) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.identity + + endpointgroup = utils.find_resource(client.endpoint_groups, + parsed_args.endpointgroup) + + project = common.find_project(client, + parsed_args.project, + parsed_args.project_domain) + + client.endpoint_filter.add_endpoint_group_to_project( + endpoint_group=endpointgroup.id, + project=project.id) + + +class CreateEndpointGroup(command.ShowOne, _FiltersReader): + _description = _("Create new endpoint group") + + def get_parser(self, prog_name): + parser = super(CreateEndpointGroup, self).get_parser(prog_name) + parser.add_argument( + 'name', + metavar='', + help=_('Name of the endpoint group'), + ) + parser.add_argument( + 'filters', + metavar='', + help=_('Filename that contains a new set of filters'), + ) + parser.add_argument( + '--description', + help=_('Description of the endpoint group'), + ) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + + filters = None + if parsed_args.filters: + filters = self._read_filters(parsed_args.filters) + + endpoint_group = identity_client.endpoint_groups.create( + name=parsed_args.name, + filters=filters, + description=parsed_args.description + ) + + info = {} + endpoint_group._info.pop('links') + info.update(endpoint_group._info) + return zip(*sorted(six.iteritems(info))) + + +class DeleteEndpointGroup(command.Command): + _description = _("Delete endpoint group(s)") + + def get_parser(self, prog_name): + parser = super(DeleteEndpointGroup, self).get_parser(prog_name) + parser.add_argument( + 'endpointgroup', + metavar='', + nargs='+', + help=_('Endpoint group(s) to delete (name or ID)'), + ) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + result = 0 + for i in parsed_args.endpointgroup: + try: + endpoint_id = utils.find_resource( + identity_client.endpoint_groups, i).id + identity_client.endpoint_groups.delete(endpoint_id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete endpoint group with " + "ID '%(endpointgroup)s': %(e)s"), + {'endpointgroup': i, 'e': e}) + + if result > 0: + total = len(parsed_args.endpointgroup) + msg = (_("%(result)s of %(total)s endpointgroups failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) + + +class ListEndpointGroup(command.Lister): + _description = _("List endpoint groups") + + def get_parser(self, prog_name): + parser = super(ListEndpointGroup, self).get_parser(prog_name) + list_group = parser.add_mutually_exclusive_group() + list_group.add_argument( + '--endpointgroup', + metavar='', + help=_('Endpoint Group (name or ID)'), + ) + list_group.add_argument( + '--project', + metavar='', + help=_('Project (name or ID)'), + ) + parser.add_argument( + '--domain', + metavar='', + help=_('Domain owning (name or ID)'), + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.identity + + endpointgroup = None + if parsed_args.endpointgroup: + endpointgroup = utils.find_resource(client.endpoint_groups, + parsed_args.endpointgroup) + project = None + if parsed_args.project: + project = common.find_project(client, + parsed_args.project, + parsed_args.domain) + + if endpointgroup: + # List projects associated to the endpoint group + columns = ('ID', 'Name') + data = client.endpoint_filter.list_projects_for_endpoint_group( + endpoint_group=endpointgroup.id) + elif project: + columns = ('ID', 'Name') + data = client.endpoint_filter.list_endpoint_groups_for_project( + project=project.id) + else: + columns = ('ID', 'Name', 'Description') + data = client.endpoint_groups.list() + + return (columns, + (utils.get_item_properties( + s, columns, + formatters={}, + ) for s in data)) + + +class RemoveProjectFromEndpointGroup(command.Command): + _description = _("Remove project from endpoint group") + + def get_parser(self, prog_name): + parser = super( + RemoveProjectFromEndpointGroup, self).get_parser(prog_name) + parser.add_argument( + 'endpointgroup', + metavar='', + help=_('Endpoint group (name or ID)'), + ) + parser.add_argument( + 'project', + metavar='', + help=_('Project to remove (name or ID)'), + ) + common.add_project_domain_option_to_parser(parser) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.identity + + endpointgroup = utils.find_resource(client.endpoint_groups, + parsed_args.endpointgroup) + + project = common.find_project(client, + parsed_args.project, + parsed_args.project_domain) + + client.endpoint_filter.delete_endpoint_group_to_project( + endpoint_group=endpointgroup.id, + project=project.id) + + +class SetEndpointGroup(command.Command, _FiltersReader): + _description = _("Set endpoint group properties") + + def get_parser(self, prog_name): + parser = super(SetEndpointGroup, self).get_parser(prog_name) + parser.add_argument( + 'endpointgroup', + metavar='', + help=_('Endpoint Group to modify (name or ID)'), + ) + parser.add_argument( + '--name', + metavar='', + help=_('New enpoint group name'), + ) + parser.add_argument( + '--filters', + metavar='', + help=_('Filename that contains a new set of filters'), + ) + parser.add_argument( + '--description', + metavar='', + default='', + help=_('New endpoint group description'), + ) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + endpointgroup = utils.find_resource(identity_client.endpoint_groups, + parsed_args.endpointgroup) + + filters = None + if parsed_args.filters: + filters = self._read_filters(parsed_args.filters) + + identity_client.endpoint_groups.update( + endpointgroup.id, + name=parsed_args.name, + filters=filters, + description=parsed_args.description + ) + + +class ShowEndpointGroup(command.ShowOne): + _description = _("Display endpoint group details") + + def get_parser(self, prog_name): + parser = super(ShowEndpointGroup, self).get_parser(prog_name) + parser.add_argument( + 'endpointgroup', + metavar='', + help=_('Endpoint group (name or ID)'), + ) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + endpoint_group = utils.find_resource(identity_client.endpoint_groups, + parsed_args.endpointgroup) + + info = {} + endpoint_group._info.pop('links') + info.update(endpoint_group._info) + return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/tests/unit/identity/v3/fakes.py b/openstackclient/tests/unit/identity/v3/fakes.py index 3e2caf01d5..68e38e5231 100644 --- a/openstackclient/tests/unit/identity/v3/fakes.py +++ b/openstackclient/tests/unit/identity/v3/fakes.py @@ -223,6 +223,20 @@ 'links': base_url + 'endpoints/' + endpoint_id, } +endpoint_group_id = 'eg-123' +endpoint_group_description = 'eg 123 description' +endpoint_group_filters = { + 'service_id': service_id, + 'region_id': endpoint_region, +} + +ENDPOINT_GROUP = { + 'id': endpoint_group_id, + 'filters': endpoint_group_filters, + 'description': endpoint_group_description, + 'links': base_url + 'endpoint_groups/' + endpoint_group_id, +} + user_id = 'bbbbbbb-aaaa-aaaa-aaaa-bbbbbbbaaaa' user_name = 'paul' user_description = 'Sir Paul' @@ -495,6 +509,8 @@ def __init__(self, **kwargs): self.endpoints.resource_class = fakes.FakeResource(None, {}) self.endpoint_filter = mock.Mock() self.endpoint_filter.resource_class = fakes.FakeResource(None, {}) + self.endpoint_groups = mock.Mock() + self.endpoint_groups.resource_class = fakes.FakeResource(None, {}) self.groups = mock.Mock() self.groups.resource_class = fakes.FakeResource(None, {}) self.oauth1 = mock.Mock() diff --git a/releasenotes/notes/keystone-endpoint-group-0c55debbb66844f2.yaml b/releasenotes/notes/keystone-endpoint-group-0c55debbb66844f2.yaml new file mode 100644 index 0000000000..dc3c5be659 --- /dev/null +++ b/releasenotes/notes/keystone-endpoint-group-0c55debbb66844f2.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Add endpoint group commands: ``endpoint group add project``, ``endpoint group create``, + ``endpoint group delete``, ``endpoint group list``, ``endpoint group remove project``, + ``endpoint group set`` and ``endpoint group show``. + [Blueprint `keystone-endpoint-filter `_] diff --git a/setup.cfg b/setup.cfg index 63bfdafb13..df062e362d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -236,6 +236,14 @@ openstack.identity.v3 = endpoint_set = openstackclient.identity.v3.endpoint:SetEndpoint endpoint_show = openstackclient.identity.v3.endpoint:ShowEndpoint + endpoint_group_add_project = openstackclient.identity.v3.endpoint_group:AddProjectToEndpointGroup + endpoint_group_create = openstackclient.identity.v3.endpoint_group:CreateEndpointGroup + endpoint_group_delete = openstackclient.identity.v3.endpoint_group:DeleteEndpointGroup + endpoint_group_list = openstackclient.identity.v3.endpoint_group:ListEndpointGroup + endpoint_group_remove_project = openstackclient.identity.v3.endpoint_group:RemoveProjectFromEndpointGroup + endpoint_group_set = openstackclient.identity.v3.endpoint_group:SetEndpointGroup + endpoint_group_show = openstackclient.identity.v3.endpoint_group:ShowEndpointGroup + group_add_user = openstackclient.identity.v3.group:AddUserToGroup group_contains_user = openstackclient.identity.v3.group:CheckUserInGroup group_create = openstackclient.identity.v3.group:CreateGroup From 4b9973b779e666ec0b8efc3ee1654831dc63e05b Mon Sep 17 00:00:00 2001 From: Brianna Poulos Date: Fri, 16 Feb 2018 16:19:06 -0500 Subject: [PATCH 1882/3095] Update help text for encryption provider The volume encryption provider no longer uses class names. Instead, 'luks' and 'plain' are used. This patch updates the help text for the volume encryption provider to use the new encryption provider format constants. Change-Id: I2911098505a99658e04ac4008e5f3e857db81f95 --- doc/source/cli/command-objects/volume-type.rst | 8 ++++---- openstackclient/volume/v1/volume_type.py | 8 ++++---- openstackclient/volume/v2/volume_type.py | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/doc/source/cli/command-objects/volume-type.rst b/doc/source/cli/command-objects/volume-type.rst index afa293d7b8..2b5aff9940 100644 --- a/doc/source/cli/command-objects/volume-type.rst +++ b/doc/source/cli/command-objects/volume-type.rst @@ -62,8 +62,8 @@ Create new volume type .. option:: --encryption-provider - Set the class that provides encryption support for this volume type - (e.g "LuksEncryptor") (admin only) + Set the encryption provider format for this volume type + (e.g "luks" or "plain") (admin only) This option is required when setting encryption type of a volume. Consider using other encryption options such as: :option:`--encryption-cipher`, @@ -197,8 +197,8 @@ Set volume type properties .. option:: --encryption-provider - Set the class that provides encryption support for this volume type - (e.g "LuksEncryptor") (admin only) + Set the encryption provider format for this volume type + (e.g "luks" or "plain") (admin only) This option is required when setting encryption type of a volume for the first time. Consider using other encryption options such as: :option:`--encryption-cipher`, diff --git a/openstackclient/volume/v1/volume_type.py b/openstackclient/volume/v1/volume_type.py index f9baa5be5d..b4d8eaca20 100644 --- a/openstackclient/volume/v1/volume_type.py +++ b/openstackclient/volume/v1/volume_type.py @@ -70,8 +70,8 @@ def get_parser(self, prog_name): parser.add_argument( '--encryption-provider', metavar='', - help=_('Set the class that provides encryption support for ' - 'this volume type (e.g "LuksEncryptor") (admin only) ' + help=_('Set the encryption provider format for ' + 'this volume type (e.g "luks" or "plain") (admin only) ' '(This option is required when setting encryption type ' 'of a volume. Consider using other encryption options ' 'such as: "--encryption-cipher", "--encryption-key-size" ' @@ -254,8 +254,8 @@ def get_parser(self, prog_name): parser.add_argument( '--encryption-provider', metavar='', - help=_('Set the class that provides encryption support for ' - 'this volume type (e.g "LuksEncryptor") (admin only) ' + help=_('Set the encryption provider format for ' + 'this volume type (e.g "luks" or "plain") (admin only) ' '(This option is required when setting encryption type ' 'of a volume. Consider using other encryption options ' 'such as: "--encryption-cipher", "--encryption-key-size" ' diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index 64c4d652ba..71e94a2b4e 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -112,8 +112,8 @@ def get_parser(self, prog_name): parser.add_argument( '--encryption-provider', metavar='', - help=_('Set the class that provides encryption support for ' - 'this volume type (e.g "LuksEncryptor") (admin only) ' + help=_('Set the encryption provider format for ' + 'this volume type (e.g "luks" or "plain") (admin only) ' '(This option is required when setting encryption type ' 'of a volume. Consider using other encryption options ' 'such as: "--encryption-cipher", "--encryption-key-size" ' @@ -371,8 +371,8 @@ def get_parser(self, prog_name): parser.add_argument( '--encryption-provider', metavar='', - help=_('Set the class that provides encryption support for ' - 'this volume type (e.g "LuksEncryptor") (admin only) ' + help=_('Set the encryption provider format for ' + 'this volume type (e.g "luks" or "plain") (admin only) ' '(This option is required when setting encryption type ' 'of a volume for the first time. Consider using other ' 'encryption options such as: "--encryption-cipher", ' From e5d60b220f7f8a549b15ea1bb08048798603111a Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 28 Feb 2018 11:22:56 -0500 Subject: [PATCH 1883/3095] Cleanup error messages on failure When test_server_commands_main_help() fails it dumps a ton of unformatted text on an exception message. This commit attempts to clean it up to make it easier to read. Change-Id: I793e6337728a22302a5a87938dbec60d7f2320d8 --- openstackclient/tests/functional/common/test_help.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openstackclient/tests/functional/common/test_help.py b/openstackclient/tests/functional/common/test_help.py index 7f27409956..3a9aef9ef3 100644 --- a/openstackclient/tests/functional/common/test_help.py +++ b/openstackclient/tests/functional/common/test_help.py @@ -60,8 +60,12 @@ def test_server_commands_main_help(self): """Check server commands in main help message.""" raw_output = self.openstack('help') for command, description in self.SERVER_COMMANDS: - self.assertIn(command, raw_output) - self.assertIn(description, raw_output) + msg = 'Command: %s not found in help output:\n%s' % ( + command, raw_output) + self.assertIn(command, raw_output, msg) + msg = 'Description: %s not found in help output:\n%s' % ( + description, raw_output) + self.assertIn(description, raw_output, msg) def test_server_only_help(self): """Check list of server-related commands only.""" From b10941ddf6f7f6894b7d87f25c173f227b111e4e Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 1 Mar 2018 07:25:50 +0000 Subject: [PATCH 1884/3095] Imported Translations from Zanata For more information about this automatic import see: https://docs.openstack.org/i18n/latest/reviewing-translation-import.html Change-Id: Ie76221736bb0fda2d7f4ee114787e5b11fc734ac --- .../tr_TR/LC_MESSAGES/openstackclient.po | 50 ++----------------- 1 file changed, 4 insertions(+), 46 deletions(-) diff --git a/openstackclient/locale/tr_TR/LC_MESSAGES/openstackclient.po b/openstackclient/locale/tr_TR/LC_MESSAGES/openstackclient.po index f3470f1854..b44baf889b 100644 --- a/openstackclient/locale/tr_TR/LC_MESSAGES/openstackclient.po +++ b/openstackclient/locale/tr_TR/LC_MESSAGES/openstackclient.po @@ -2,18 +2,18 @@ # işbaran akçayır , 2017. #zanata msgid "" msgstr "" -"Project-Id-Version: python-openstackclient 3.12.1.dev21\n" +"Project-Id-Version: python-openstackclient VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2017-08-14 16:23+0000\n" +"POT-Creation-Date: 2018-02-25 01:10+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2017-08-15 12:09+0000\n" "Last-Translator: Andreas Jaeger \n" "Language-Team: Turkish (Turkey)\n" -"Language: tr-TR\n" +"Language: tr_TR\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -"X-Generator: Zanata 3.9.6\n" +"X-Generator: Zanata 4.3.3\n" "X-POOTLE-MTIME: 1502656444.000000\n" #, python-format @@ -308,12 +308,6 @@ msgstr "" msgid "Activate the image" msgstr "İmajı aktifleştir" -msgid "" -"Add a policy to ('affinity' or 'anti-affinity', default to 'affinity')" -msgstr "" -"'e bir politika ekle ('affinity' veya 'anti-affinity', varsayılan " -"'affinity' için)" - msgid "Add a port to a router" msgstr "Yönlendiriciye bir bağlantı noktası ekle" @@ -2552,9 +2546,6 @@ msgstr "Servise göre filtrele (tür, isim veya ID)" msgid "Filter credentials by (name or ID)" msgstr "Kimlik bilgilerini 'ya göre filtrele (isim veya ID)" -msgid "Filter credentials by type: cert, ec2" -msgstr "Kimlik bilgilerinin türe göre filtrele: cert, ec2" - msgid "Filter group list by (name or ID)" msgstr "Grup listesini 'e göre filtrele (isim veya ID)" @@ -2817,15 +2808,6 @@ msgstr "Sunucuya eklenecek IP adresi (sadece isim)" msgid "IP address to remove from server (name only)" msgstr "Sunucudan kaldırılacak IP adresi (sadece isim)" -msgid "" -"IP protocol (ah, dccp, egp, esp, gre, icmp, igmp, ipv6-encap, ipv6-frag, " -"ipv6-icmp, ipv6-nonxt, ipv6-opts, ipv6-route, ospf, pgm, rsvp, sctp, tcp, " -"udp, udplite, vrrp and integer representations [0-255]; default: tcp)" -msgstr "" -"IP protokolü (ah, dccp, egp, esp, gre, icmp, igmp, ipv6-encap, ipv6-frag, " -"ipv6-icmp, ipv6-nonxt, ipv6-opts, ipv6-route, ospf, pgm, rsvp, sctp, tcp, " -"udp, udplite, vrrp ve tam sayı gösterimi [0-255]; varsayılan: tcp)" - msgid "IP protocol (icmp, tcp, udp; default: tcp)" msgstr "IP protokolü (icmp, tcp, udp; varsayılan: tcp)" @@ -4029,9 +4011,6 @@ msgstr "Yeni kap ismi" msgid "New credential data" msgstr "Yeni kimlik bilgileri verisi" -msgid "New credential type: cert, ec2" -msgstr "Yeni kimlik bilgisi türü: cert, ec2" - msgid "New domain description" msgstr "Yeni alan tanımı" @@ -4599,13 +4578,6 @@ msgstr "RX/TX faktörü (varsayılan 1.0)" msgid "Read image data from standard input" msgstr "Standart girdiden imaj verilerini oku" -msgid "" -"Reason for disabling the service (in quotas). Should be used with --disable " -"option." -msgstr "" -"Servisi devre dışı bırakma sebebi (kota cinsinden). --disable seçeneği ile " -"birlikte kullanılmalı." - msgid "Reason for disabling the service (should be used with --disable option)" msgstr "" "Servisi devredışı bırakmak için sebep (--disable seçeneği ile kullanılmalı)" @@ -6046,9 +6018,6 @@ msgstr "Silinecek altağ(lar) (isim veya ID)" msgid "Suspend server(s)" msgstr "Sunucu(yu/ları) durdur" -msgid "Swap space size in MB (default 0M)" -msgstr "MB cinsinden takas alanı boyutu (varsayılan 0M)" - #, python-format msgid "Tag to be added to the %s (repeat option to set multiple tags)" msgstr "" @@ -6342,17 +6311,6 @@ msgstr "" "Proje özelliğini kaldır (birden fazla özellik kaldırmak için seçeneği " "tekrarla)" -msgid "" -"Unset a property on this image (repeat option to set multiple properties)" -msgstr "" -"Bu imajdaki bir özelliği kaldır (birden fazla özelliği ayarlamak için " -"seçeneği tekrarla)" - -msgid "Unset a tag on this image (repeat option to set multiple tags)" -msgstr "" -"Bu imajdan etiketi kaldır (birden fazla etiket ayarlamak için seçeneği " -"tekrarla)" - msgid "Unset account properties" msgstr "Hesap özelliklerinin ayarını kaldır" From 5a5281e755fa06d0d04a72d4a3af4e296d65cb87 Mon Sep 17 00:00:00 2001 From: melissaml Date: Fri, 2 Mar 2018 18:12:17 +0800 Subject: [PATCH 1885/3095] Update links in README Change the outdated links to the latest links in README Change-Id: I94c781866c8b65e84708dd9f951f023a5e2913b1 --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 6c2c2401d1..3b3642ed29 100644 --- a/README.rst +++ b/README.rst @@ -2,8 +2,8 @@ Team and repository tags ======================== -.. image:: http://governance.openstack.org/badges/python-openstackclient.svg - :target: http://governance.openstack.org/reference/tags/index.html +.. image:: https://governance.openstack.org/tc/badges/python-openstackclient.svg + :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on From 395f640bc7cc79bd6aec357ef26fc0fae5f030a2 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Tue, 13 Mar 2018 07:28:18 +0000 Subject: [PATCH 1886/3095] Updated from global requirements Change-Id: Idf311a7fa08e8738bb4c70b0d84f8a4121606f56 --- requirements.txt | 4 ++-- test-requirements.txt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index a1c48a53a9..bfee36b687 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,8 +6,8 @@ six>=1.10.0 # MIT Babel!=2.4.0,>=2.3.4 # BSD cliff!=2.9.0,>=2.8.0 # Apache-2.0 -keystoneauth1>=3.3.0 # Apache-2.0 -openstacksdk>=0.9.19 # Apache-2.0 +keystoneauth1>=3.4.0 # Apache-2.0 +openstacksdk>=0.11.2 # Apache-2.0 osc-lib>=1.8.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 diff --git a/test-requirements.txt b/test-requirements.txt index c393a53d49..2dff3ff6b9 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -29,9 +29,9 @@ python-heatclient>=1.10.0 # Apache-2.0 python-ironicclient>=2.2.0 # Apache-2.0 python-ironic-inspector-client>=1.5.0 # Apache-2.0 python-karborclient>=0.6.0 # Apache-2.0 -python-mistralclient>=3.1.0 # Apache-2.0 +python-mistralclient!=3.2.0,>=3.1.0 # Apache-2.0 python-muranoclient>=0.8.2 # Apache-2.0 -python-neutronclient>=6.3.0 # Apache-2.0 +python-neutronclient>=6.7.0 # Apache-2.0 python-octaviaclient>=1.3.0 # Apache-2.0 python-rsdclient>=0.1.0 # Apache-2.0 python-saharaclient>=1.4.0 # Apache-2.0 From 53e7aab7ed4d6c981ca067c1db8bce290a5f0055 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Mon, 5 Mar 2018 14:18:41 -0600 Subject: [PATCH 1887/3095] Re-implement novaclient bits removed in 10.0 a) /os-floating-ips was removed in Compute API 2.36 and from novaclient's Python API in 10.0 Add to api.computev2: floating_ip_add() floating_ip_remove() Convert add floating IP command to nova-net/neutron split: "server add floating ip" "server remove floating ip" b) /os-hosts was removed in Compute API 2.43 and from novaclient's Python API in 10.0. Add to api.computev2: host_list() host_set() host_show() Convert host commands to use intenal api: "host list" "host set" "host show" c) The introduction of the Network-style commands into the server group broke cliff's autoprogram directive as it executes the get_parser() methods without fully initializing the Command object. NOTE: This is really three reviews squashed to get through the gate in one pass. Depends-on: Id6de87211d6c4ea8fd14aa9203d8d5b17e9e2f04 Change-Id: I5116086f9a9e4b2b31a744bf8f4558c79f0bfe59 --- openstackclient/api/compute_v2.py | 156 +++++++++++- openstackclient/compute/v2/host.py | 18 +- openstackclient/compute/v2/server.py | 59 +++-- openstackclient/network/common.py | 5 +- .../tests/unit/api/test_compute_v2.py | 176 ++++++++++++++ .../tests/unit/compute/v2/fakes.py | 5 +- .../tests/unit/compute/v2/test_host.py | 85 ++++--- .../tests/unit/compute/v2/test_server.py | 224 ++++++++++++++---- 8 files changed, 618 insertions(+), 110 deletions(-) diff --git a/openstackclient/api/compute_v2.py b/openstackclient/api/compute_v2.py index 0ffed65549..0c89e91266 100644 --- a/openstackclient/api/compute_v2.py +++ b/openstackclient/api/compute_v2.py @@ -93,7 +93,51 @@ def find( return ret - # Flaoting IPs + # Floating IPs + + def floating_ip_add( + self, + server, + address, + fixed_address=None, + ): + """Add a floating IP to a server + + :param server: + The :class:`Server` (or its ID) to add an IP to. + :param address: + The FloatingIP or string floating address to add. + :param fixed_address: + The FixedIP the floatingIP should be associated with (optional) + """ + + url = '/servers' + + server = self.find( + url, + attr='name', + value=server, + ) + + address = address.ip if hasattr(address, 'ip') else address + if fixed_address: + if hasattr(fixed_address, 'ip'): + fixed_address = fixed_address.ip + + body = { + 'address': address, + 'fixed_address': fixed_address, + } + else: + body = { + 'address': address, + } + + return self._request( + "POST", + "/%s/%s/action" % (url, server['id']), + json={'addFloatingIp': body}, + ) def floating_ip_create( self, @@ -175,6 +219,38 @@ def floating_ip_list( return self.list(url)["floating_ips"] + def floating_ip_remove( + self, + server, + address, + ): + """Remove a floating IP from a server + + :param server: + The :class:`Server` (or its ID) to add an IP to. + :param address: + The FloatingIP or string floating address to add. + """ + + url = '/servers' + + server = self.find( + url, + attr='name', + value=server, + ) + + address = address.ip if hasattr(address, 'ip') else address + body = { + 'address': address, + } + + return self._request( + "POST", + "/%s/%s/action" % (url, server['id']), + json={'removeFloatingIp': body}, + ) + # Floating IP Pools def floating_ip_pool_list( @@ -192,6 +268,84 @@ def floating_ip_pool_list( return self.list(url)["floating_ip_pools"] + # Hosts + + def host_list( + self, + zone=None, + ): + """Lists hypervisor Hosts + + https://developer.openstack.org/api-ref/compute/#list-hosts + Valid for Compute 2.0 - 2.42 + + :param string zone: + Availability zone + :returns: A dict of the floating IP attributes + """ + + url = "/os-hosts" + if zone: + url = '/os-hosts?zone=%s' % zone + + return self.list(url)["hosts"] + + def host_set( + self, + host=None, + status=None, + maintenance_mode=None, + **params + ): + """Modify host properties + + https://developer.openstack.org/api-ref/compute/#update-host-status + Valid for Compute 2.0 - 2.42 + + status + maintenance_mode + """ + + url = "/os-hosts" + + params = {} + if status: + params['status'] = status + if maintenance_mode: + params['maintenance_mode'] = maintenance_mode + if params == {}: + # Don't bother calling if nothing given + return None + else: + return self._request( + "PUT", + "/%s/%s" % (url, host), + json=params, + ).json() + + def host_show( + self, + host=None, + ): + """Show host + + https://developer.openstack.org/api-ref/compute/#show-host-details + Valid for Compute 2.0 - 2.42 + """ + + url = "/os-hosts" + + r_host = self.find( + url, + attr='host_name', + value=host, + ) + + data = [] + for h in r_host: + data.append(h['resource']) + return data + # Networks def network_create( diff --git a/openstackclient/compute/v2/host.py b/openstackclient/compute/v2/host.py index a495b367aa..9fdfd927d0 100644 --- a/openstackclient/compute/v2/host.py +++ b/openstackclient/compute/v2/host.py @@ -40,9 +40,9 @@ def take_action(self, parsed_args): "Service", "Zone" ) - data = compute_client.hosts.list_all(parsed_args.zone) + data = compute_client.api.host_list(parsed_args.zone) return (columns, - (utils.get_item_properties( + (utils.get_dict_properties( s, columns, ) for s in data)) @@ -95,13 +95,7 @@ def take_action(self, parsed_args): compute_client = self.app.client_manager.compute - # More than one hosts will be returned by using find_resource() - # so that the return value cannot be used in host update() method. - # find_resource() is just used for checking existence of host and - # keeping the exception message consistent with other commands. - utils.find_resource(compute_client.hosts, parsed_args.host) - - compute_client.hosts.update( + compute_client.api.host_set( parsed_args.host, kwargs ) @@ -128,8 +122,10 @@ def take_action(self, parsed_args): "Memory MB", "Disk GB" ) - data = compute_client.hosts.get(parsed_args.host) + + data = compute_client.api.host_show(parsed_args.host) + return (columns, - (utils.get_item_properties( + (utils.get_dict_properties( s, columns, ) for s in data)) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index c08f5caea1..85c20aee6d 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -32,6 +32,7 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import common as network_common LOG = logging.getLogger(__name__) @@ -234,11 +235,10 @@ def take_action(self, parsed_args): ) -class AddFloatingIP(command.Command): +class AddFloatingIP(network_common.NetworkAndComputeCommand): _description = _("Add floating IP address to server") - def get_parser(self, prog_name): - parser = super(AddFloatingIP, self).get_parser(prog_name) + def update_parser_common(self, parser): parser.add_argument( "server", metavar="", @@ -252,19 +252,37 @@ def get_parser(self, prog_name): parser.add_argument( "--fixed-ip-address", metavar="", - help=_("Fixed IP address to associate with this floating IP " - "address"), + help=_( + "Fixed IP address to associate with this floating IP address" + ), ) return parser - def take_action(self, parsed_args): + def take_action_network(self, client, parsed_args): compute_client = self.app.client_manager.compute + attrs = {} + obj = client.find_ip( + parsed_args.ip_address, + ignore_missing=False, + ) server = utils.find_resource( - compute_client.servers, parsed_args.server) + compute_client.servers, + parsed_args.server, + ) + port = list(client.ports(device_id=server.id))[0] + attrs['port_id'] = port.id + if parsed_args.fixed_ip_address: + attrs['fixed_ip_address'] = parsed_args.fixed_ip_address + + client.update_ip(obj, **attrs) - server.add_floating_ip(parsed_args.ip_address, - parsed_args.fixed_ip_address) + def take_action_compute(self, client, parsed_args): + client.api.floating_ip_add( + parsed_args.server, + parsed_args.ip_address, + fixed_address=parsed_args.fixed_ip_address, + ) class AddPort(command.Command): @@ -1482,11 +1500,10 @@ def take_action(self, parsed_args): server.remove_fixed_ip(parsed_args.ip_address) -class RemoveFloatingIP(command.Command): +class RemoveFloatingIP(network_common.NetworkAndComputeCommand): _description = _("Remove floating IP address from server") - def get_parser(self, prog_name): - parser = super(RemoveFloatingIP, self).get_parser(prog_name) + def update_parser_common(self, parser): parser.add_argument( "server", metavar="", @@ -1501,13 +1518,21 @@ def get_parser(self, prog_name): ) return parser - def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute + def take_action_network(self, client, parsed_args): + attrs = {} + obj = client.find_ip( + parsed_args.ip_address, + ignore_missing=False, + ) + attrs['port_id'] = None - server = utils.find_resource( - compute_client.servers, parsed_args.server) + client.update_ip(obj, **attrs) - server.remove_floating_ip(parsed_args.ip_address) + def take_action_compute(self, client, parsed_args): + client.api.floating_ip_remove( + parsed_args.server, + parsed_args.ip_address, + ) class RemovePort(command.Command): diff --git a/openstackclient/network/common.py b/openstackclient/network/common.py index eca0de3c9a..37bf1406c5 100644 --- a/openstackclient/network/common.py +++ b/openstackclient/network/common.py @@ -48,7 +48,10 @@ def get_parser(self, prog_name): parser = super(NetworkAndComputeCommand, self).get_parser(prog_name) parser = self.update_parser_common(parser) LOG.debug('common parser: %s', parser) - if self.app.client_manager.is_network_endpoint_enabled(): + if ( + self.app is None or + self.app.client_manager.is_network_endpoint_enabled() + ): return self.update_parser_network(parser) else: return self.update_parser_compute(parser) diff --git a/openstackclient/tests/unit/api/test_compute_v2.py b/openstackclient/tests/unit/api/test_compute_v2.py index 4f3b80316b..edf5258f6c 100644 --- a/openstackclient/tests/unit/api/test_compute_v2.py +++ b/openstackclient/tests/unit/api/test_compute_v2.py @@ -55,6 +55,43 @@ class TestFloatingIP(TestComputeAPIv2): FAKE_FLOATING_IP_RESP_2, ] + FAKE_SERVER_RESP_1 = { + 'id': 1, + 'name': 'server1', + } + + def test_floating_ip_add_id(self): + self.requests_mock.register_uri( + 'POST', + FAKE_URL + '/servers/1/action', + json={'server': {}}, + status_code=200, + ) + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/servers/1', + json={'server': self.FAKE_SERVER_RESP_1}, + status_code=200, + ) + ret = self.api.floating_ip_add('1', '1.0.1.0') + self.assertEqual(200, ret.status_code) + + def test_floating_ip_add_name(self): + self.requests_mock.register_uri( + 'POST', + FAKE_URL + '/servers/1/action', + json={'server': {}}, + status_code=200, + ) + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/servers/server1', + json={'server': self.FAKE_SERVER_RESP_1}, + status_code=200, + ) + ret = self.api.floating_ip_add('server1', '1.0.1.0') + self.assertEqual(200, ret.status_code) + def test_floating_ip_create(self): self.requests_mock.register_uri( 'POST', @@ -144,6 +181,36 @@ def test_floating_ip_list(self): ret = self.api.floating_ip_list() self.assertEqual(self.LIST_FLOATING_IP_RESP, ret) + def test_floating_ip_remove_id(self): + self.requests_mock.register_uri( + 'POST', + FAKE_URL + '/servers/1/action', + status_code=200, + ) + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/servers/1', + json={'server': self.FAKE_SERVER_RESP_1}, + status_code=200, + ) + ret = self.api.floating_ip_remove('1', '1.0.1.0') + self.assertEqual(200, ret.status_code) + + def test_floating_ip_remove_name(self): + self.requests_mock.register_uri( + 'POST', + FAKE_URL + '/servers/1/action', + status_code=200, + ) + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/servers/server1', + json={'server': self.FAKE_SERVER_RESP_1}, + status_code=200, + ) + ret = self.api.floating_ip_remove('server1', '1.0.1.0') + self.assertEqual(200, ret.status_code) + class TestFloatingIPPool(TestComputeAPIv2): @@ -163,6 +230,115 @@ def test_floating_ip_pool_list(self): self.assertEqual(self.LIST_FLOATING_IP_POOL_RESP, ret) +class TestHost(TestComputeAPIv2): + + FAKE_HOST_RESP_1 = { + "zone": "internal", + "host_name": "myhost", + "service": "conductor", + } + + FAKE_HOST_RESP_2 = { + "zone": "internal", + "host_name": "myhost", + "service": "scheduler", + } + + FAKE_HOST_RESP_3 = { + "zone": "nova", + "host_name": "myhost", + "service": "compute", + } + + LIST_HOST_RESP = [ + FAKE_HOST_RESP_1, + FAKE_HOST_RESP_2, + FAKE_HOST_RESP_3, + ] + + def test_host_list_no_options(self): + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/os-hosts', + json={'hosts': self.LIST_HOST_RESP}, + status_code=200, + ) + ret = self.api.host_list() + self.assertEqual(self.LIST_HOST_RESP, ret) + + def test_host_list_zone(self): + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/os-hosts?zone=nova', + json={'hosts': [self.FAKE_HOST_RESP_3]}, + status_code=200, + ) + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/os-hosts', + json={'hosts': [self.FAKE_HOST_RESP_3]}, + status_code=200, + ) + ret = self.api.host_list(zone='nova') + self.assertEqual([self.FAKE_HOST_RESP_3], ret) + + def test_host_set_none(self): + ret = self.api.host_set(host='myhost') + self.assertIsNone(ret) + + def test_host_set(self): + self.requests_mock.register_uri( + 'PUT', + FAKE_URL + '/os-hosts/myhost', + json={}, + status_code=200, + ) + ret = self.api.host_set(host='myhost', status='enabled') + self.assertEqual({}, ret) + + def test_host_show(self): + FAKE_RESOURCE_1 = { + "cpu": 2, + "disk_gb": 1028, + "host": "c1a7de0ac9d94e4baceae031d05caae3", + "memory_mb": 8192, + "project": "(total)", + } + FAKE_RESOURCE_2 = { + "cpu": 0, + "disk_gb": 0, + "host": "c1a7de0ac9d94e4baceae031d05caae3", + "memory_mb": 512, + "project": "(used_now)", + } + FAKE_RESOURCE_3 = { + "cpu": 0, + "disk_gb": 0, + "host": "c1a7de0ac9d94e4baceae031d05caae3", + "memory_mb": 0, + "project": "(used_max)", + } + FAKE_HOST_RESP = [ + {'resource': FAKE_RESOURCE_1}, + {'resource': FAKE_RESOURCE_2}, + {'resource': FAKE_RESOURCE_3}, + ] + FAKE_HOST_LIST = [ + FAKE_RESOURCE_1, + FAKE_RESOURCE_2, + FAKE_RESOURCE_3, + ] + + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '/os-hosts/myhost', + json={'host': FAKE_HOST_RESP}, + status_code=200, + ) + ret = self.api.host_show(host='myhost') + self.assertEqual(FAKE_HOST_LIST, ret) + + class TestNetwork(TestComputeAPIv2): FAKE_NETWORK_RESP = { diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index 0fae19af8f..1ec717853d 100644 --- a/openstackclient/tests/unit/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -1230,10 +1230,7 @@ def create_one_host(attrs=None): 'project': 'project-' + uuid.uuid4().hex, } host_info.update(attrs) - host = fakes.FakeResource( - info=copy.deepcopy(host_info), - loaded=True) - return host + return host_info class FakeServerGroup(object): diff --git a/openstackclient/tests/unit/compute/v2/test_host.py b/openstackclient/tests/unit/compute/v2/test_host.py index a388172f9d..329095deef 100644 --- a/openstackclient/tests/unit/compute/v2/test_host.py +++ b/openstackclient/tests/unit/compute/v2/test_host.py @@ -13,6 +13,8 @@ # under the License. # +import mock + from openstackclient.compute.v2 import host from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes from openstackclient.tests.unit import utils as tests_utils @@ -23,11 +25,13 @@ class TestHost(compute_fakes.TestComputev2): def setUp(self): super(TestHost, self).setUp() - # Get a shortcut to the FlavorManager Mock - self.host_mock = self.app.client_manager.compute.hosts - self.host_mock.reset_mock() + # Get a shortcut to the compute client + self.compute = self.app.client_manager.compute +@mock.patch( + 'openstackclient.api.compute_v2.APIv2.host_list' +) class TestHostList(TestHost): host = compute_fakes.FakeHost.create_one_host() @@ -39,19 +43,18 @@ class TestHostList(TestHost): ) data = [( - host.host_name, - host.service, - host.zone, + host['host_name'], + host['service'], + host['zone'], )] def setUp(self): super(TestHostList, self).setUp() - self.host_mock.list_all.return_value = [self.host] - self.cmd = host.ListHost(self.app, None) - def test_host_list_no_option(self): + def test_host_list_no_option(self, h_mock): + h_mock.return_value = [self.host] arglist = [] verifylist = [] @@ -59,44 +62,48 @@ def test_host_list_no_option(self): columns, data = self.cmd.take_action(parsed_args) - self.host_mock.list_all.assert_called_with(None) + h_mock.assert_called_with(None) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) - def test_host_list_with_option(self): + def test_host_list_with_option(self, h_mock): + h_mock.return_value = [self.host] arglist = [ - '--zone', self.host.zone, + '--zone', self.host['zone'], ] verifylist = [ - ('zone', self.host.zone), + ('zone', self.host['zone']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.host_mock.list_all.assert_called_with(self.host.zone) + h_mock.assert_called_with(self.host['zone']) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) +@mock.patch( + 'openstackclient.api.compute_v2.APIv2.host_set' +) class TestHostSet(TestHost): def setUp(self): super(TestHostSet, self).setUp() self.host = compute_fakes.FakeHost.create_one_host() - self.host_mock.get.return_value = self.host - self.host_mock.update.return_value = None self.cmd = host.SetHost(self.app, None) - def test_host_set_no_option(self): + def test_host_set_no_option(self, h_mock): + h_mock.return_value = self.host + h_mock.update.return_value = None arglist = [ - self.host.host + self.host['host'], ] verifylist = [ - ('host', self.host.host) + ('host', self.host['host']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -105,18 +112,20 @@ def test_host_set_no_option(self): self.assertIsNone(result) body = {} - self.host_mock.update.assert_called_with(self.host.host, body) + h_mock.assert_called_with(self.host['host'], body) - def test_host_set(self): + def test_host_set(self, h_mock): + h_mock.return_value = self.host + h_mock.update.return_value = None arglist = [ '--enable', '--disable-maintenance', - self.host.host + self.host['host'], ] verifylist = [ ('enable', True), ('enable_maintenance', False), - ('host', self.host.host) + ('host', self.host['host']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -125,9 +134,12 @@ def test_host_set(self): self.assertIsNone(result) body = {'status': 'enable', 'maintenance_mode': 'disable'} - self.host_mock.update.assert_called_with(self.host.host, body) + h_mock.assert_called_with(self.host['host'], body) +@mock.patch( + 'openstackclient.api.compute_v2.APIv2.host_show' +) class TestHostShow(TestHost): host = compute_fakes.FakeHost.create_one_host() @@ -139,22 +151,22 @@ class TestHostShow(TestHost): 'Memory MB', 'Disk GB', ) + data = [( - host.host, - host.project, - host.cpu, - host.memory_mb, - host.disk_gb, + host['host'], + host['project'], + host['cpu'], + host['memory_mb'], + host['disk_gb'], )] def setUp(self): super(TestHostShow, self).setUp() - self.host_mock.get.return_value = [self.host] - self.cmd = host.ShowHost(self.app, None) - def test_host_show_no_option(self): + def test_host_show_no_option(self, h_mock): + h_mock.host_show.return_value = [self.host] arglist = [] verifylist = [] @@ -162,18 +174,19 @@ def test_host_show_no_option(self): self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) - def test_host_show_with_option(self): + def test_host_show_with_option(self, h_mock): + h_mock.return_value = [self.host] arglist = [ - self.host.host_name, + self.host['host_name'], ] verifylist = [ - ('host', self.host.host_name), + ('host', self.host['host_name']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.host_mock.get.assert_called_with(self.host.host_name) + h_mock.assert_called_with(self.host['host_name']) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index c4e125bab2..87c9a98514 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -141,48 +141,148 @@ def test_server_add_specific_fixed_ip(self): self._test_server_add_fixed_ip(extralist, '5.6.7.8') -class TestServerAddFloatingIP(TestServer): +@mock.patch( + 'openstackclient.api.compute_v2.APIv2.floating_ip_add' +) +class TestServerAddFloatingIPCompute(compute_fakes.TestComputev2): def setUp(self): - super(TestServerAddFloatingIP, self).setUp() + super(TestServerAddFloatingIPCompute, self).setUp() + + self.app.client_manager.network_endpoint_enabled = False # Get the command object to test self.cmd = server.AddFloatingIP(self.app, None) - # Set add_floating_ip method to be tested. - self.methods = { - 'add_floating_ip': None, - } + def test_server_add_floating_ip_default(self, fip_mock): + _floating_ip = compute_fakes.FakeFloatingIP.create_one_floating_ip() + arglist = [ + 'server1', + _floating_ip['ip'], + ] + verifylist = [ + ('server', 'server1'), + ('ip_address', _floating_ip['ip']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.find_port = mock.Mock() - self.app.client_manager.network.find_port = self.find_port + self.cmd.take_action(parsed_args) - def _test_server_add_floating_ip(self, extralist, fixed_ip_address): - servers = self.setup_servers_mock(count=1) + fip_mock.assert_called_once_with( + 'server1', + _floating_ip['ip'], + fixed_address=None, + ) + def test_server_add_floating_ip_fixed(self, fip_mock): + _floating_ip = compute_fakes.FakeFloatingIP.create_one_floating_ip() arglist = [ - servers[0].id, - '1.2.3.4', - ] + extralist + '--fixed-ip-address', _floating_ip['fixed_ip'], + 'server1', + _floating_ip['ip'], + ] verifylist = [ - ('server', servers[0].id), - ('ip_address', '1.2.3.4'), - ('fixed_ip_address', fixed_ip_address), + ('fixed_ip_address', _floating_ip['fixed_ip']), + ('server', 'server1'), + ('ip_address', _floating_ip['ip']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) + self.cmd.take_action(parsed_args) - servers[0].add_floating_ip.assert_called_once_with('1.2.3.4', - fixed_ip_address) - self.assertIsNone(result) + fip_mock.assert_called_once_with( + 'server1', + _floating_ip['ip'], + fixed_address=_floating_ip['fixed_ip'], + ) - def test_server_add_floating_ip(self): - self._test_server_add_floating_ip([], None) - def test_server_add_floating_ip_to_fixed_ip(self): - extralist = ['--fixed-ip-address', '5.6.7.8'] - self._test_server_add_floating_ip(extralist, '5.6.7.8') +class TestServerAddFloatingIPNetwork( + TestServer, + network_fakes.TestNetworkV2, +): + + def setUp(self): + super(TestServerAddFloatingIPNetwork, self).setUp() + + self.app.client_manager.network = mock.Mock() + self.network = self.app.client_manager.network + self.network.update_ip = mock.Mock(return_value=None) + + # Get the command object to test + self.cmd = server.AddFloatingIP(self.app, self.namespace) + + def test_server_add_floating_ip_default(self): + _server = compute_fakes.FakeServer.create_one_server() + self.servers_mock.get.return_value = _server + _port = network_fakes.FakePort.create_one_port() + _floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip() + self.network.find_ip = mock.Mock(return_value=_floating_ip) + self.network.ports = mock.Mock(return_value=[_port]) + arglist = [ + _server.id, + _floating_ip['ip'], + ] + verifylist = [ + ('server', _server.id), + ('ip_address', _floating_ip['ip']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + attrs = { + 'port_id': _port.id, + } + + self.network.find_ip.assert_called_once_with( + _floating_ip['ip'], + ignore_missing=False, + ) + self.network.ports.assert_called_once_with( + device_id=_server.id, + ) + self.network.update_ip.assert_called_once_with( + _floating_ip, + **attrs + ) + + def test_server_add_floating_ip_fixed(self): + _server = compute_fakes.FakeServer.create_one_server() + self.servers_mock.get.return_value = _server + _port = network_fakes.FakePort.create_one_port() + _floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip() + self.network.find_ip = mock.Mock(return_value=_floating_ip) + self.network.ports = mock.Mock(return_value=[_port]) + arglist = [ + '--fixed-ip-address', _floating_ip['fixed_ip'], + _server.id, + _floating_ip['ip'], + ] + verifylist = [ + ('fixed_ip_address', _floating_ip['fixed_ip']), + ('server', _server.id), + ('ip_address', _floating_ip['ip']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + attrs = { + 'port_id': _port.id, + } + + self.network.find_ip.assert_called_once_with( + _floating_ip['ip'], + ignore_missing=False, + ) + self.network.ports.assert_called_once_with( + device_id=_server.id, + ) + self.network.update_ip.assert_called_once_with( + _floating_ip, + **attrs + ) class TestServerAddPort(TestServer): @@ -2302,36 +2402,80 @@ def test_rescue_with_current_image_and_password(self): self.server.rescue.assert_called_with(image=None, password=password) -class TestServerRemoveFloatingIP(TestServer): +@mock.patch( + 'openstackclient.api.compute_v2.APIv2.floating_ip_remove' +) +class TestServerRemoveFloatingIPCompute(compute_fakes.TestComputev2): def setUp(self): - super(TestServerRemoveFloatingIP, self).setUp() + super(TestServerRemoveFloatingIPCompute, self).setUp() + + self.app.client_manager.network_endpoint_enabled = False # Get the command object to test self.cmd = server.RemoveFloatingIP(self.app, None) - # Set unshelve method to be tested. - self.methods = { - 'remove_floating_ip': None, - } + def test_server_remove_floating_ip(self, fip_mock): + _floating_ip = compute_fakes.FakeFloatingIP.create_one_floating_ip() - def test_server_remove_floating_ip(self): - servers = self.setup_servers_mock(count=1) + arglist = [ + 'server1', + _floating_ip['ip'], + ] + verifylist = [ + ('server', 'server1'), + ('ip_address', _floating_ip['ip']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + fip_mock.assert_called_once_with( + 'server1', + _floating_ip['ip'], + ) + + +class TestServerRemoveFloatingIPNetwork(network_fakes.TestNetworkV2): + + def setUp(self): + super(TestServerRemoveFloatingIPNetwork, self).setUp() + + self.app.client_manager.network = mock.Mock() + self.network = self.app.client_manager.network + self.network.update_ip = mock.Mock(return_value=None) + + # Get the command object to test + self.cmd = server.RemoveFloatingIP(self.app, self.namespace) + def test_server_remove_floating_ip_default(self): + _server = compute_fakes.FakeServer.create_one_server() + _floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip() + self.network.find_ip = mock.Mock(return_value=_floating_ip) arglist = [ - servers[0].id, - '1.2.3.4', + _server.id, + _floating_ip['ip'], ] verifylist = [ - ('server', servers[0].id), - ('ip_address', '1.2.3.4'), + ('server', _server.id), + ('ip_address', _floating_ip['ip']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) + self.cmd.take_action(parsed_args) - servers[0].remove_floating_ip.assert_called_once_with('1.2.3.4') - self.assertIsNone(result) + attrs = { + 'port_id': None, + } + + self.network.find_ip.assert_called_once_with( + _floating_ip['ip'], + ignore_missing=False, + ) + self.network.update_ip.assert_called_once_with( + _floating_ip, + **attrs + ) class TestServerRemovePort(TestServer): From f6688986fc8e32976a84ce4a6e86c08e58a1718a Mon Sep 17 00:00:00 2001 From: Dongcan Ye Date: Sat, 24 Feb 2018 03:54:34 +0000 Subject: [PATCH 1888/3095] Remove duplicated network attributes Change-Id: If77609d06e7d80c8da5111b8a30036b58fbfa187 --- openstackclient/tests/unit/network/v2/fakes.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index a77cab8b5f..deb377dfe5 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -347,7 +347,6 @@ def create_one_network(attrs=None): 'availability_zone_hints': [], 'is_default': False, 'port_security_enabled': True, - 'tags': ['test'], 'qos_policy_id': 'qos-policy-id-' + uuid.uuid4().hex, 'ipv4_address_scope': 'ipv4' + uuid.uuid4().hex, 'ipv6_address_scope': 'ipv6' + uuid.uuid4().hex, From 372f7caa08c266ccc0e8a0bbd3179be3e8c6ccfc Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Fri, 2 Feb 2018 16:14:37 -0600 Subject: [PATCH 1889/3095] Make Profile fallback go bye-bye It's time to move on with life. (dtroyer) This requires sdk >= 0.10.0 (0.9.19 is in global-requirements.txt) and osc-lib >= 1.8.0 (1.8.0 is already in g-r). Once we have sdk bumped make_client() becomes just a pass-through for the plugin interface and existing code compatibility. Change-Id: Ie3d7c442da4257b11140c109e9df69b629336f42 --- openstackclient/network/client.py | 52 +++++++------------------------ 1 file changed, 11 insertions(+), 41 deletions(-) diff --git a/openstackclient/network/client.py b/openstackclient/network/client.py index 5183cbdaf5..39936fde71 100644 --- a/openstackclient/network/client.py +++ b/openstackclient/network/client.py @@ -13,16 +13,6 @@ import logging -from openstack import connection - - -# NOTE(dtroyer): Attempt an import to detect if the SDK installed is new -# enough to not use Profile. If so, use that. -try: - from openstack.config import loader as config # noqa - profile = None -except ImportError: - from openstack import profile from osc_lib import utils from openstackclient.i18n import _ @@ -41,37 +31,17 @@ def make_client(instance): """Returns a network proxy""" - if getattr(instance, "sdk_connection", None) is None: - if profile is None: - # If the installed OpenStackSDK is new enough to not require a - # Profile obejct and osc-lib is not new enough to have created - # it for us, make an SDK Connection. - # NOTE(dtroyer): This can be removed when this bit is in the - # released osc-lib in requirements.txt. - conn = connection.Connection( - config=instance._cli_options, - session=instance.session, - ) - else: - # Fall back to the original Connection creation - prof = profile.Profile() - prof.set_region(API_NAME, instance.region_name) - prof.set_version(API_NAME, instance._api_version[API_NAME]) - prof.set_interface(API_NAME, instance.interface) - conn = connection.Connection( - authenticator=instance.session.auth, - verify=instance.session.verify, - cert=instance.session.cert, - profile=prof, - ) - - instance.sdk_connection = conn - - conn = instance.sdk_connection - LOG.debug('Connection: %s', conn) - LOG.debug('Network client initialized using OpenStack SDK: %s', - conn.network) - return conn.network + # NOTE(dtroyer): As of osc-lib 1.8.0 and OpenStackSDK 0.10.0 the + # old Profile interface and separate client creation + # for each API that uses the SDK is unnecessary. This + # callback remains as a remnant of the original plugin + # interface and to avoid the code churn of changing all + # of the existing references. + LOG.debug( + 'Network client initialized using OpenStack SDK: %s', + instance.sdk_connection.network, + ) + return instance.sdk_connection.network def build_option_parser(parser): From 18563b4132f794cc6612c2897795f96a31b565ae Mon Sep 17 00:00:00 2001 From: Emilien Macchi Date: Thu, 15 Mar 2018 13:21:41 +0100 Subject: [PATCH 1890/3095] neutron: add --mtu for create/set network Support Neutron network mtu configuration with a new argument, --mtu that allows CLI users to set MTU for Neutron networks. Change-Id: I93d23581c7e8c84eaf9bb3b293360036f60f456b --- doc/source/cli/command-objects/network.rst | 12 ++++++++++++ openstackclient/network/v2/network.py | 14 ++++++++++++++ openstackclient/tests/unit/network/v2/fakes.py | 1 + .../tests/unit/network/v2/test_network.py | 9 +++++++++ .../notes/neutron_mtu-d87e53e2d76f8612.yaml | 5 +++++ 5 files changed, 41 insertions(+) create mode 100644 releasenotes/notes/neutron_mtu-d87e53e2d76f8612.yaml diff --git a/doc/source/cli/command-objects/network.rst b/doc/source/cli/command-objects/network.rst index 5f20dc3884..220fbf32b5 100644 --- a/doc/source/cli/command-objects/network.rst +++ b/doc/source/cli/command-objects/network.rst @@ -24,6 +24,7 @@ Create new network [--enable | --disable] [--share | --no-share] [--description ] + [--mtu ] [--availability-zone-hint ] [--enable-port-security | --disable-port-security] [--external [--default | --no-default] | --internal] @@ -74,6 +75,12 @@ Create new network *Network version 2 only* +.. option:: --mtu + + Set network mtu + + *Network version 2 only* + .. option:: --availability-zone-hint Availability Zone in which to create this network @@ -353,6 +360,7 @@ Set network properties [--enable | --disable] [--share | --no-share] [--description ] + [--mtu ] [--enable-port-security | --disable-port-security] [--external [--default | --no-default] | --internal] [--provider-network-type ] @@ -386,6 +394,10 @@ Set network properties Set network description +.. option:: --mtu + + Set network mtu + .. option:: --enable-port-security Enable port security by default for ports created on diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 4c1725c5f6..d1c7f005d3 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -107,6 +107,10 @@ def _get_attrs_network(client_manager, parsed_args): if parsed_args.description: attrs['description'] = parsed_args.description + # set mtu + if parsed_args.mtu: + attrs['mtu'] = parsed_args.mtu + # update_external_network_options if parsed_args.internal: attrs['router:external'] = False @@ -217,6 +221,11 @@ def update_parser_network(self, parser): metavar='', help=_("Set network description") ) + parser.add_argument( + '--mtu', + metavar='', + help=_("Set network mtu") + ) identity_common.add_project_domain_option_to_parser(parser) parser.add_argument( '--availability-zone-hint', @@ -619,6 +628,11 @@ def get_parser(self, prog_name): metavar=" Date: Thu, 15 Mar 2018 16:30:43 -0400 Subject: [PATCH 1891/3095] Fix typo in 'floating ip associate' command and doc Assocaite -> Associate Trivialfix Change-Id: I432e35ddcd80ef77c865bee9a54ac2777f5b6386 --- doc/source/cli/command-objects/floating-ip.rst | 2 +- openstackclient/network/v2/floating_ip.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/cli/command-objects/floating-ip.rst b/doc/source/cli/command-objects/floating-ip.rst index d9ed2307c3..925a230c8d 100644 --- a/doc/source/cli/command-objects/floating-ip.rst +++ b/doc/source/cli/command-objects/floating-ip.rst @@ -166,7 +166,7 @@ Set floating IP properties .. option:: --port - Assocaite the floating IP with port (name or ID) + Associate the floating IP with port (name or ID) .. option:: --fixed-ip-address diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index a6f0340466..4d07d9da37 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -412,7 +412,7 @@ def get_parser(self, prog_name): '--port', metavar='', required=True, - help=_("Assocaite the floating IP with port (name or ID)")), + help=_("Associate the floating IP with port (name or ID)")), parser.add_argument( '--fixed-ip-address', metavar='', From 9a173568548203757a7fd20883edd56f923cc7e8 Mon Sep 17 00:00:00 2001 From: Thomas Goirand Date: Wed, 7 Feb 2018 09:25:33 +0100 Subject: [PATCH 1892/3095] Fix crashing "console log show" Because of encoding issue, the "openstack console show log" is prone to a stack dump, as explained in the bug report. Use the stdout handle that has already been set up by cliff's App class with a sane default encoding. Change-Id: I4d8b0df7f16ee0463e638bb11276220e5b92023b Closes-Bug: 1747862 --- openstackclient/compute/v2/console.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openstackclient/compute/v2/console.py b/openstackclient/compute/v2/console.py index 25f9210886..b2f7288f40 100644 --- a/openstackclient/compute/v2/console.py +++ b/openstackclient/compute/v2/console.py @@ -15,8 +15,6 @@ """Compute v2 Console action implementations""" -import sys - from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import utils @@ -60,7 +58,10 @@ def take_action(self, parsed_args): length += 1 data = server.get_console_output(length=length) - sys.stdout.write(data) + + if data and data[-1] != '\n': + data += '\n' + self.app.stdout.write(data) class ShowConsoleURL(command.ShowOne): From 25808affd1e9152a4f02fc1aea068bcef5d1b55d Mon Sep 17 00:00:00 2001 From: Colleen Murphy Date: Fri, 16 Mar 2018 17:45:15 +0100 Subject: [PATCH 1893/3095] Correct application credential usage doc There is a --restricted flag to counter the --unrestricted flag. It was documented, but the usage example had missed it. Add it for completeness. Change-Id: Ib4cdcacdd16bfb59e9d18714106ecda99e418812 --- doc/source/cli/command-objects/application-credentials.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/cli/command-objects/application-credentials.rst b/doc/source/cli/command-objects/application-credentials.rst index 08d85b119d..2a1fbff25e 100644 --- a/doc/source/cli/command-objects/application-credentials.rst +++ b/doc/source/cli/command-objects/application-credentials.rst @@ -21,7 +21,7 @@ Create new application credential [--role ] [--expiration ] [--description ] - [--unrestricted] + [--restricted|--unrestricted] .. option:: --secret From 6df58b63667b3f7b76c7825f5204a6c4f531521c Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Tue, 20 Mar 2018 15:40:18 -0500 Subject: [PATCH 1894/3095] Fix additional output encoding issues This is a followup to https://review.openstack.org/#/c/541609/ that changes most outstanding direct uses of sys.stdout to use the encoded stdout set up by cliff. Change-Id: I07cfc418385fc787d3b7d3c32d39676cf81bb91f --- openstackclient/compute/v2/server.py | 69 +++++++++++++------ openstackclient/compute/v2/server_backup.py | 16 ++--- openstackclient/compute/v2/server_image.py | 15 ++-- openstackclient/compute/v2/usage.py | 8 +-- openstackclient/identity/v3/group.py | 5 +- openstackclient/image/v2/image.py | 3 +- .../tests/unit/compute/v2/test_server.py | 16 ++--- 7 files changed, 78 insertions(+), 54 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index c08f5caea1..fd6abee6a6 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -20,7 +20,6 @@ import io import logging import os -import sys from novaclient.v2 import servers from osc_lib.cli import parseractions @@ -189,12 +188,6 @@ def _prep_server_detail(compute_client, image_client, server): return info -def _show_progress(progress): - if progress: - sys.stdout.write('\rProgress: %s' % progress) - sys.stdout.flush() - - class AddFixedIP(command.Command): _description = _("Add fixed IP address to server") @@ -580,6 +573,12 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): + + def _show_progress(progress): + if progress: + self.app.stdout.write('\rProgress: %s' % progress) + self.app.stdout.flush() + compute_client = self.app.client_manager.compute volume_client = self.app.client_manager.volume image_client = self.app.client_manager.image @@ -814,11 +813,11 @@ def take_action(self, parsed_args): server.id, callback=_show_progress, ): - sys.stdout.write('\n') + self.app.stdout.write('\n') else: LOG.error(_('Error creating server: %s'), parsed_args.server_name) - sys.stdout.write(_('Error creating server\n')) + self.app.stdout.write(_('Error creating server\n')) raise SystemExit details = _prep_server_detail(compute_client, image_client, server) @@ -872,6 +871,12 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): + + def _show_progress(progress): + if progress: + self.app.stdout.write('\rProgress: %s' % progress) + self.app.stdout.flush() + compute_client = self.app.client_manager.compute for server in parsed_args.server: server_obj = utils.find_resource( @@ -883,11 +888,11 @@ def take_action(self, parsed_args): server_obj.id, callback=_show_progress, ): - sys.stdout.write('\n') + self.app.stdout.write('\n') else: LOG.error(_('Error deleting server: %s'), server_obj.id) - sys.stdout.write(_('Error deleting server\n')) + self.app.stdout.write(_('Error deleting server\n')) raise SystemExit @@ -1290,6 +1295,11 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): + def _show_progress(progress): + if progress: + self.app.stdout.write('\rProgress: %s' % progress) + self.app.stdout.flush() + compute_client = self.app.client_manager.compute server = utils.find_resource( @@ -1315,11 +1325,11 @@ def take_action(self, parsed_args): server.id, callback=_show_progress, ): - sys.stdout.write(_('Complete\n')) + self.app.stdout.write(_('Complete\n')) else: LOG.error(_('Error migrating server: %s'), server.id) - sys.stdout.write(_('Error migrating server\n')) + self.app.stdout.write(_('Error migrating server\n')) raise SystemExit @@ -1380,6 +1390,12 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): + + def _show_progress(progress): + if progress: + self.app.stdout.write('\rProgress: %s' % progress) + self.app.stdout.flush() + compute_client = self.app.client_manager.compute server = utils.find_resource( compute_client.servers, parsed_args.server) @@ -1391,11 +1407,11 @@ def take_action(self, parsed_args): server.id, callback=_show_progress, ): - sys.stdout.write(_('Complete\n')) + self.app.stdout.write(_('Complete\n')) else: LOG.error(_('Error rebooting server: %s'), server.id) - sys.stdout.write(_('Error rebooting server\n')) + self.app.stdout.write(_('Error rebooting server\n')) raise SystemExit @@ -1428,6 +1444,12 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): + + def _show_progress(progress): + if progress: + self.app.stdout.write('\rProgress: %s' % progress) + self.app.stdout.flush() + compute_client = self.app.client_manager.compute image_client = self.app.client_manager.image @@ -1445,11 +1467,11 @@ def take_action(self, parsed_args): server.id, callback=_show_progress, ): - sys.stdout.write(_('Complete\n')) + self.app.stdout.write(_('Complete\n')) else: LOG.error(_('Error rebuilding server: %s'), server.id) - sys.stdout.write(_('Error rebuilding server\n')) + self.app.stdout.write(_('Error rebuilding server\n')) raise SystemExit details = _prep_server_detail(compute_client, image_client, server) @@ -1727,6 +1749,11 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): + def _show_progress(progress): + if progress: + self.app.stdout.write('\rProgress: %s' % progress) + self.app.stdout.flush() + compute_client = self.app.client_manager.compute server = utils.find_resource( compute_client.servers, @@ -1745,11 +1772,11 @@ def take_action(self, parsed_args): success_status=['active', 'verify_resize'], callback=_show_progress, ): - sys.stdout.write(_('Complete\n')) + self.app.stdout.write(_('Complete\n')) else: LOG.error(_('Error resizing server: %s'), server.id) - sys.stdout.write(_('Error resizing server\n')) + self.app.stdout.write(_('Error resizing server\n')) raise SystemExit elif parsed_args.confirm: compute_client.servers.confirm_resize(server) @@ -1915,7 +1942,9 @@ def take_action(self, parsed_args): if parsed_args.diagnostics: (resp, data) = server.diagnostics() if not resp.status_code == 200: - sys.stderr.write(_("Error retrieving diagnostics data\n")) + self.app.stderr.write(_( + "Error retrieving diagnostics data\n" + )) return ({}, {}) else: data = _prep_server_detail(compute_client, diff --git a/openstackclient/compute/v2/server_backup.py b/openstackclient/compute/v2/server_backup.py index ddcf91010e..a79f5f7039 100644 --- a/openstackclient/compute/v2/server_backup.py +++ b/openstackclient/compute/v2/server_backup.py @@ -15,8 +15,6 @@ """Compute v2 Server action implementations""" -import sys - from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -26,12 +24,6 @@ from openstackclient.i18n import _ -def _show_progress(progress): - if progress: - sys.stderr.write('\rProgress: %s' % progress) - sys.stderr.flush() - - class CreateServerBackup(command.ShowOne): _description = _("Create a server backup image") @@ -74,6 +66,12 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): + + def _show_progress(progress): + if progress: + self.app.stderr.write('\rProgress: %s' % progress) + self.app.stderr.flush() + compute_client = self.app.client_manager.compute server = utils.find_resource( @@ -114,7 +112,7 @@ def take_action(self, parsed_args): image.id, callback=_show_progress, ): - sys.stdout.write('\n') + self.app.stdout.write('\n') else: msg = _('Error creating server backup: %s') % parsed_args.name raise exceptions.CommandError(msg) diff --git a/openstackclient/compute/v2/server_image.py b/openstackclient/compute/v2/server_image.py index c66e06747f..3bc5d94aab 100644 --- a/openstackclient/compute/v2/server_image.py +++ b/openstackclient/compute/v2/server_image.py @@ -16,7 +16,6 @@ """Compute v2 Server action implementations""" import logging -import sys from osc_lib.command import command from osc_lib import exceptions @@ -30,12 +29,6 @@ LOG = logging.getLogger(__name__) -def _show_progress(progress): - if progress: - sys.stdout.write('\rProgress: %s' % progress) - sys.stdout.flush() - - class CreateServerImage(command.ShowOne): _description = _("Create a new server disk image from an existing server") @@ -64,6 +57,12 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): + + def _show_progress(progress): + if progress: + self.app.stdout.write('\rProgress: %s' % progress) + self.app.stdout.flush() + compute_client = self.app.client_manager.compute server = utils.find_resource( @@ -92,7 +91,7 @@ def take_action(self, parsed_args): image_id, callback=_show_progress, ): - sys.stdout.write('\n') + self.app.stdout.write('\n') else: LOG.error(_('Error creating server image: %s'), parsed_args.server) diff --git a/openstackclient/compute/v2/usage.py b/openstackclient/compute/v2/usage.py index 3edcffe460..4320bf90b6 100644 --- a/openstackclient/compute/v2/usage.py +++ b/openstackclient/compute/v2/usage.py @@ -16,7 +16,6 @@ """Usage action implementations""" import datetime -import sys from osc_lib.command import command from osc_lib import utils @@ -96,7 +95,7 @@ def _format_project(project): pass if parsed_args.formatter == 'table' and len(usage_list) > 0: - sys.stdout.write(_("Usage from %(start)s to %(end)s: \n") % { + self.app.stdout.write(_("Usage from %(start)s to %(end)s: \n") % { "start": start.strftime(dateformat), "end": end.strftime(dateformat), }) @@ -168,8 +167,9 @@ def take_action(self, parsed_args): usage = compute_client.usage.get(project, start, end) if parsed_args.formatter == 'table': - sys.stdout.write(_("Usage from %(start)s to %(end)s on " - "project %(project)s: \n") % { + self.app.stdout.write(_( + "Usage from %(start)s to %(end)s on project %(project)s: \n" + ) % { "start": start.strftime(dateformat), "end": end.strftime(dateformat), "project": project, diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py index 39c8547c4e..02eeadd63f 100644 --- a/openstackclient/identity/v3/group.py +++ b/openstackclient/identity/v3/group.py @@ -16,7 +16,6 @@ """Group action implementations""" import logging -import sys from keystoneauth1 import exceptions as ks_exc from osc_lib.command import command @@ -122,7 +121,7 @@ def take_action(self, parsed_args): 'user': parsed_args.user, 'group': parsed_args.group, } - sys.stderr.write(msg) + self.app.stderr.write(msg) else: raise e else: @@ -130,7 +129,7 @@ def take_action(self, parsed_args): 'user': parsed_args.user, 'group': parsed_args.group, } - sys.stdout.write(msg) + self.app.stdout.write(msg) class CreateGroup(command.ShowOne): diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 7e6a7aa178..ddcee4ade9 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -17,7 +17,6 @@ import argparse import logging -import sys from glanceclient.common import utils as gc_utils from osc_lib.cli import parseractions @@ -653,7 +652,7 @@ def take_action(self, parsed_args): if data.wrapped is None: msg = _('Image %s has no data.') % image.id LOG.error(msg) - sys.stdout.write(msg + '\n') + self.app.stdout.write(msg + '\n') raise SystemExit gc_utils.save_image(data, parsed_args.file) diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index c4e125bab2..25a71db5cd 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -925,7 +925,7 @@ def test_server_create_with_wait_ok(self, mock_wait_for_status): mock_wait_for_status.assert_called_once_with( self.servers_mock.get, self.new_server.id, - callback=server._show_progress, + callback=mock.ANY, ) kwargs = dict( @@ -975,7 +975,7 @@ def test_server_create_with_wait_fails(self, mock_wait_for_status): mock_wait_for_status.assert_called_once_with( self.servers_mock.get, self.new_server.id, - callback=server._show_progress, + callback=mock.ANY, ) kwargs = dict( @@ -1494,7 +1494,7 @@ def test_server_delete_wait_ok(self, mock_wait_for_delete): mock_wait_for_delete.assert_called_once_with( self.servers_mock, servers[0].id, - callback=server._show_progress + callback=mock.ANY, ) self.assertIsNone(result) @@ -1516,7 +1516,7 @@ def test_server_delete_wait_fails(self, mock_wait_for_delete): mock_wait_for_delete.assert_called_once_with( self.servers_mock, servers[0].id, - callback=server._show_progress + callback=mock.ANY, ) @@ -2152,7 +2152,7 @@ def test_rebuild_with_wait_ok(self, mock_wait_for_status): mock_wait_for_status.assert_called_once_with( self.servers_mock.get, self.server.id, - callback=server._show_progress, + callback=mock.ANY, # **kwargs ) @@ -2177,7 +2177,7 @@ def test_rebuild_with_wait_fails(self, mock_wait_for_status): mock_wait_for_status.assert_called_once_with( self.servers_mock.get, self.server.id, - callback=server._show_progress + callback=mock.ANY, ) self.servers_mock.get.assert_called_with(self.server.id) @@ -2619,7 +2619,7 @@ def test_server_resize_with_wait_ok(self, mock_wait_for_status): mock_wait_for_status.assert_called_once_with( self.servers_mock.get, self.server.id, - callback=server._show_progress, + callback=mock.ANY, **kwargs ) @@ -2659,7 +2659,7 @@ def test_server_resize_with_wait_fails(self, mock_wait_for_status): mock_wait_for_status.assert_called_once_with( self.servers_mock.get, self.server.id, - callback=server._show_progress, + callback=mock.ANY, **kwargs ) From 1008544882fbdae16b045abca05cf3e2e8a14787 Mon Sep 17 00:00:00 2001 From: Matt Riedemann Date: Mon, 19 Feb 2018 13:14:58 -0500 Subject: [PATCH 1895/3095] Default --nic to 'auto' if creating a server with >= 2.37 Compute API version >= 2.37 requires a 'networks' value in the server create request. The novaclient CLI defaults this to 'auto' if not specified, but the novaclient ServerManager.create python API binding code does not, as it wants clients to be explicit. For the purposes of the OSC CLI, we should follow suit and if the user is requesting OS_COMPUTE_API_VERSION>=2.37 without specific nics, we should just default to 'auto'. Change-Id: Ib760c55e31209223338a4086ff1f4fee88dc6959 Closes-Bug: #1750395 --- openstackclient/compute/v2/server.py | 12 +++-- .../functional/compute/v2/test_server.py | 10 ++-- .../tests/unit/compute/v2/fakes.py | 3 ++ .../tests/unit/compute/v2/test_server.py | 50 +++++++++++++++++++ 4 files changed, 68 insertions(+), 7 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 85c20aee6d..3341fbfea1 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -22,6 +22,7 @@ import os import sys +from novaclient import api_versions from novaclient.v2 import servers from osc_lib.cli import parseractions from osc_lib.command import command @@ -754,9 +755,14 @@ def take_action(self, parsed_args): raise exceptions.CommandError(msg) nics = nics[0] else: - # Default to empty list if nothing was specified, let nova side to - # decide the default behavior. - nics = [] + # Compute API version >= 2.37 requires a value, so default to + # 'auto' to maintain legacy behavior if a nic wasn't specified. + if compute_client.api_version >= api_versions.APIVersion('2.37'): + nics = 'auto' + else: + # Default to empty list if nothing was specified, let nova + # side to decide the default behavior. + nics = [] # Check security group exist and convert ID to name security_group_names = [] diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py index 0b29fe5fbd..13fdfa0606 100644 --- a/openstackclient/tests/functional/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -586,7 +586,9 @@ def test_server_create_with_empty_network_option_latest(self): server_name ) except exceptions.CommandFailed as e: - self.assertIn('nics are required after microversion 2.36', - e.stderr) - else: - self.fail('CommandFailed should be raised.') + # If we got here, it shouldn't be because a nics value wasn't + # provided to the server; it is likely due to something else in + # the functional tests like there being multiple available + # networks and the test didn't specify a specific network. + self.assertNotIn('nics are required after microversion 2.36', + e.stderr) diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index 1ec717853d..1d97cecbef 100644 --- a/openstackclient/tests/unit/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -17,6 +17,7 @@ import uuid import mock +from novaclient import api_versions from openstackclient.api import compute_v2 from openstackclient.tests.unit import fakes @@ -198,6 +199,8 @@ def __init__(self, **kwargs): self.management_url = kwargs['endpoint'] + self.api_version = api_versions.APIVersion('2.1') + class TestComputev2(utils.TestCommand): diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 87c9a98514..8f82e7cd0b 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -18,6 +18,7 @@ import mock from mock import call +from novaclient import api_versions from osc_lib import exceptions from osc_lib import utils as common_utils from oslo_utils import timeutils @@ -844,6 +845,55 @@ def test_server_create_with_auto_network(self): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist(), data) + def test_server_create_with_auto_network_default_v2_37(self): + """Tests creating a server without specifying --nic using 2.37.""" + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Since check_parser doesn't handle compute global options like + # --os-compute-api-version, we have to mock the construction of + # the novaclient client object with our own APIVersion. + with mock.patch.object(self.app.client_manager.compute, 'api_version', + api_versions.APIVersion('2.37')): + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = dict( + meta=None, + files={}, + reservation_id=None, + min_count=1, + max_count=1, + security_groups=[], + userdata=None, + key_name=None, + availability_zone=None, + block_device_mapping_v2=[], + nics='auto', + scheduler_hints={}, + config_drive=None, + ) + # ServerManager.create(name, image, flavor, **kwargs) + self.servers_mock.create.assert_called_with( + self.new_server.name, + self.image, + self.flavor, + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) + def test_server_create_with_none_network(self): arglist = [ '--image', 'image1', From c615bcd75e85a2a2231d9944caeffd746e881e5e Mon Sep 17 00:00:00 2001 From: npraveen35 Date: Sun, 4 Feb 2018 00:08:02 -0800 Subject: [PATCH 1896/3095] Display private flavors in server list Update the code so that "openstack server list --all" also displays the names of private flavors. Change-Id: I4804fcd905eaf67b1ad9b461084eaf0caa820d2f Closes-Bug: #1742453 --- openstackclient/compute/v2/server.py | 2 +- releasenotes/notes/bug-1742453-ae4be6de90a3ae1d.yaml | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/bug-1742453-ae4be6de90a3ae1d.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index c08f5caea1..f40fbdf6bf 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1162,7 +1162,7 @@ def take_action(self, parsed_args): # "Flavor Name" is not crucial, so we swallow any exceptions. if not parsed_args.no_name_lookup: try: - flavors_list = compute_client.flavors.list() + flavors_list = compute_client.flavors.list(is_public=None) for i in flavors_list: flavors[i.id] = i except Exception: diff --git a/releasenotes/notes/bug-1742453-ae4be6de90a3ae1d.yaml b/releasenotes/notes/bug-1742453-ae4be6de90a3ae1d.yaml new file mode 100644 index 0000000000..6103e3bf62 --- /dev/null +++ b/releasenotes/notes/bug-1742453-ae4be6de90a3ae1d.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + The ``server list --all`` command now resolves non-public flavor names, + too, so that the ``Flavor`` column will be properly populated. + [Bug `1742453 `_] From 79577681d88e4851d4d32482e80d43ed9aa0f521 Mon Sep 17 00:00:00 2001 From: Mohammed Naser Date: Tue, 20 Mar 2018 11:31:50 -0400 Subject: [PATCH 1897/3095] Add support to list image members The OpenStack client presently has support to add or remove members from an image, but no way to list image members. This patch addreses this issue. Change-Id: Ie85c5de23c6beb21fd6b4c04c83ddf2a116606ef --- doc/source/cli/command-objects/image.rst | 16 +++++++ doc/source/cli/commands.rst | 1 + openstackclient/image/v2/image.py | 33 +++++++++++++ .../tests/unit/image/v2/test_image.py | 46 +++++++++++++++++++ ...dd-image-member-list-1630ead5988348c2.yaml | 4 ++ setup.cfg | 1 + 6 files changed, 101 insertions(+) create mode 100644 releasenotes/notes/add-image-member-list-1630ead5988348c2.yaml diff --git a/doc/source/cli/command-objects/image.rst b/doc/source/cli/command-objects/image.rst index e2257bbbba..f0b5bfaade 100644 --- a/doc/source/cli/command-objects/image.rst +++ b/doc/source/cli/command-objects/image.rst @@ -266,6 +266,22 @@ List available images *Image version 2 only* +image member list +----------------- + +List projects associated with image + +.. program:: image member list +.. code:: bash + + openstack image member list + + +.. _image_member_list-image: +.. describe:: + + Image(s) to view members for (name or ID) + image remove project -------------------- diff --git a/doc/source/cli/commands.rst b/doc/source/cli/commands.rst index d840549c3f..76126a7475 100644 --- a/doc/source/cli/commands.rst +++ b/doc/source/cli/commands.rst @@ -104,6 +104,7 @@ referring to both Compute and Volume quotas. * ``hypervisor stats``: (**Compute**) hypervisor statistics over all compute nodes * ``identity provider``: (**Identity**) a source of users and authentication * ``image``: (**Image**) a disk image +* ``image member``: (**Image**) a project that is a member of an Image * ``ip availability``: (**Network**) - details of IP usage of a network * ``ip fixed``: (**Compute**, **Network**) - an internal IP address assigned to a server * ``ip floating``: (**Compute**, **Network**) - a public IP address that can be mapped to a server diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 7e6a7aa178..9407e665d2 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -592,6 +592,39 @@ def take_action(self, parsed_args): ) +class ListImageProjects(command.Lister): + _description = _("List projects associated with image") + + def get_parser(self, prog_name): + parser = super(ListImageProjects, self).get_parser(prog_name) + parser.add_argument( + "image", + metavar="", + help=_("Image (name or ID)"), + ) + common.add_project_domain_option_to_parser(parser) + return parser + + def take_action(self, parsed_args): + image_client = self.app.client_manager.image + columns = ( + "Image ID", + "Member ID", + "Status" + ) + + image_id = utils.find_resource( + image_client.images, + parsed_args.image).id + + data = image_client.image_members.list(image_id) + + return (columns, + (utils.get_item_properties( + s, columns, + ) for s in data)) + + class RemoveProjectImage(command.Command): _description = _("Disassociate project with image") diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py index e1a79d13ce..301cd0377e 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -780,6 +780,52 @@ def test_image_list_status_option(self): ) +class TestListImageProjects(TestImage): + + project = identity_fakes.FakeProject.create_one_project() + _image = image_fakes.FakeImage.create_one_image() + member = image_fakes.FakeImage.create_one_image_member( + attrs={'image_id': _image.id, + 'member_id': project.id} + ) + + columns = ( + "Image ID", + "Member ID", + "Status" + ) + + datalist = (( + _image.id, + member.member_id, + member.status, + )) + + def setUp(self): + super(TestListImageProjects, self).setUp() + + self.images_mock.get.return_value = self._image + self.image_members_mock.list.return_value = self.datalist + + self.cmd = image.ListImageProjects(self.app, None) + + def test_image_member_list(self): + arglist = [ + self._image.id + ] + verifylist = [ + ('image', self._image.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.image_members_mock.list.assert_called_with(self._image.id) + + self.assertEqual(self.columns, columns) + self.assertEqual(len(self.datalist), len(tuple(data))) + + class TestRemoveProjectImage(TestImage): project = identity_fakes.FakeProject.create_one_project() diff --git a/releasenotes/notes/add-image-member-list-1630ead5988348c2.yaml b/releasenotes/notes/add-image-member-list-1630ead5988348c2.yaml new file mode 100644 index 0000000000..f939a2fd86 --- /dev/null +++ b/releasenotes/notes/add-image-member-list-1630ead5988348c2.yaml @@ -0,0 +1,4 @@ +--- +features: + - The OpenStack client now has the ability to list all members of an image + in order to faciliate management of member projects for images. diff --git a/setup.cfg b/setup.cfg index b031cc5f6c..a78dc31b71 100644 --- a/setup.cfg +++ b/setup.cfg @@ -353,6 +353,7 @@ openstack.image.v2 = image_create = openstackclient.image.v2.image:CreateImage image_delete = openstackclient.image.v2.image:DeleteImage image_list = openstackclient.image.v2.image:ListImage + image_member_list = openstackclient.image.v2.image:ListImageProjects image_remove_project = openstackclient.image.v2.image:RemoveProjectImage image_save = openstackclient.image.v2.image:SaveImage image_show = openstackclient.image.v2.image:ShowImage From 6168ca7a8969a094cd65a71d68018e5fb148c05f Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Fri, 23 Mar 2018 08:33:21 -0500 Subject: [PATCH 1898/3095] Rename python-openstacksdk to openstacksdk Depends-On: https://review.openstack.org/554662 Change-Id: I6169f9e98418055c7c3ae5bc3b76b1216703cd55 --- .zuul.yaml | 10 +++++----- tox.ini | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.zuul.yaml b/.zuul.yaml index 29702ddb4f..49dbe2d664 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -8,10 +8,10 @@ required-projects: - openstack/cliff - openstack/keystoneauth + - openstack/openstacksdk - openstack/os-client-config - openstack/osc-lib - openstack/python-openstackclient - - openstack/python-openstacksdk vars: tox_envlist: py27 # Set work dir to openstackclient so that if it's triggered by one of the @@ -30,10 +30,10 @@ required-projects: - openstack/cliff - openstack/keystoneauth + - openstack/openstacksdk - openstack/os-client-config - openstack/osc-lib - openstack/python-openstackclient - - openstack/python-openstacksdk vars: # Set work dir to openstackclient so that if it's triggered by one of the # other repos the tests will run in the same place @@ -51,10 +51,10 @@ required-projects: - openstack/cliff - openstack/keystoneauth + - openstack/openstacksdk - openstack/os-client-config - openstack/osc-lib - openstack/python-openstackclient - - openstack/python-openstacksdk vars: # Set work dir to openstackclient so that if it's triggered by one of the # other repos the tests will run in the same place @@ -155,14 +155,14 @@ required-projects: - openstack/cliff - openstack/keystoneauth + - openstack/openstacksdk - openstack/os-client-config - openstack/osc-lib - openstack/python-openstackclient - - openstack/python-openstacksdk vars: devstack_localrc: USE_PYTHON3: true - LIBS_FROM_GIT: python-openstackclient,python-openstacksdk,osc-lib,os-client-config + LIBS_FROM_GIT: python-openstackclient,openstacksdk,osc-lib,os-client-config # This is insufficient, but leaving it here as a reminder of what may # someday be all we need to make this work # disable_python3_package swift diff --git a/tox.ini b/tox.ini index c56c3fbf0c..7ba2f2191d 100644 --- a/tox.ini +++ b/tox.ini @@ -61,7 +61,7 @@ commands = pip install -q -U -e "git+file://{toxinidir}/../keystoneauth#egg=keystoneauth" pip install -q -U -e "git+file://{toxinidir}/../osc-lib#egg=osc_lib" pip install -q -U -e "git+file://{toxinidir}/../os-client-config#egg=os_client_config" - pip install -q -e "git+file://{toxinidir}/../python-openstacksdk#egg=openstacksdk" + pip install -q -e "git+file://{toxinidir}/../openstacksdk#egg=openstacksdk" pip freeze stestr run {posargs} whitelist_externals = stestr @@ -82,7 +82,7 @@ commands = pip install -q -U -e "git+file://{toxinidir}/../keystoneauth#egg=keystoneauth" pip install -q -U -e "git+file://{toxinidir}/../osc-lib#egg=osc_lib" pip install -q -U -e "git+file://{toxinidir}/../os-client-config#egg=os_client_config" - pip install -q -U -e "git+file://{toxinidir}/../python-openstacksdk#egg=openstacksdk" + pip install -q -U -e "git+file://{toxinidir}/../openstacksdk#egg=openstacksdk" pip freeze {toxinidir}/openstackclient/tests/functional/run_stestr.sh {posargs} From bf32cdf3a92e88cd90b42f1b5bfa6ed212542a39 Mon Sep 17 00:00:00 2001 From: Doug Hellmann Date: Thu, 22 Mar 2018 17:59:45 -0400 Subject: [PATCH 1899/3095] add lower-constraints job Create a tox environment for running the unit tests against the lower bounds of the dependencies. Create a lower-constraints.txt to be used to enforce the lower bounds in those tests. Add openstack-tox-lower-constraints job to the zuul configuration. See http://lists.openstack.org/pipermail/openstack-dev/2018-March/128352.html for more details. Change-Id: I4a4ca9726fab1d0cf9a33311201b7f65951a0942 Depends-On: https://review.openstack.org/555034 Signed-off-by: Doug Hellmann --- .zuul.yaml | 2 + lower-constraints.txt | 144 ++++++++++++++++++++++++++++++++++++++++++ tox.ini | 7 ++ 3 files changed, 153 insertions(+) create mode 100644 lower-constraints.txt diff --git a/.zuul.yaml b/.zuul.yaml index 49dbe2d664..4541fbde0a 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -205,6 +205,8 @@ # The functional-tips job only tests the latest and shouldn't be run # on the stable branches branches: ^(?!stable) + - openstack-tox-lower-constraints gate: jobs: - osc-functional-devstack + - openstack-tox-lower-constraints diff --git a/lower-constraints.txt b/lower-constraints.txt new file mode 100644 index 0000000000..f7c7d09faf --- /dev/null +++ b/lower-constraints.txt @@ -0,0 +1,144 @@ +amqp==2.1.1 +aodhclient==0.9.0 +appdirs==1.3.0 +asn1crypto==0.23.0 +Babel==2.3.4 +bandit==1.4.0 +cachetools==2.0.0 +cffi==1.7.0 +cliff==2.8.0 +cmd2==0.8.0 +contextlib2==0.4.0 +coverage==4.0 +cryptography==2.1 +debtcollector==1.2.0 +decorator==3.4.0 +deprecation==1.0 +docker==2.4.2 +docker-pycreds==0.2.1 +dogpile.cache==0.6.2 +eventlet==0.18.2 +extras==1.0.0 +fasteners==0.7.0 +fixtures==3.0.0 +flake8==2.5.5 +flake8-import-order==0.13 +future==0.16.0 +futurist==1.2.0 +gitdb==0.6.4 +GitPython==1.0.1 +gnocchiclient==3.3.1 +greenlet==0.4.10 +hacking==0.12.0 +httplib2==0.9.1 +idna==2.6 +iso8601==0.1.11 +Jinja2==2.10 +jmespath==0.9.0 +jsonpatch==1.16 +jsonpointer==1.13 +jsonschema==2.6.0 +keystoneauth1==3.4.0 +kombu==4.0.0 +linecache2==1.0.0 +MarkupSafe==1.0 +mccabe==0.2.1 +mock==2.0.0 +monotonic==0.6 +mox3==0.20.0 +msgpack-python==0.4.0 +munch==2.1.0 +netaddr==0.7.18 +netifaces==0.10.4 +openstacksdk==0.11.2 +os-client-config==1.28.0 +os-service-types==1.2.0 +os-testr==1.0.0 +osc-lib==1.8.0 +oslo.concurrency==3.25.0 +oslo.config==5.2.0 +oslo.context==2.19.2 +oslo.i18n==3.15.3 +oslo.log==3.36.0 +oslo.messaging==5.29.0 +oslo.middleware==3.31.0 +oslo.serialization==2.18.0 +oslo.service==1.24.0 +oslo.utils==3.33.0 +oslotest==3.2.0 +osprofiler==1.4.0 +paramiko==2.0.0 +Paste==2.0.2 +PasteDeploy==1.5.0 +pbr==2.0.0 +pep8==1.5.7 +pika==0.10.0 +pika-pool==0.1.3 +ply==3.10 +positional==1.2.1 +prettytable==0.7.2 +pyasn1==0.1.8 +pycodestyle==2.3.1 +pycparser==2.18 +pyflakes==0.8.1 +pyinotify==0.9.6 +pyOpenSSL==17.1.0 +pyparsing==2.1.0 +pyperclip==1.5.27 +python-barbicanclient==4.5.2 +python-cinderclient==3.3.0 +python-congressclient==1.9.0 +python-dateutil==2.5.3 +python-designateclient==2.7.0 +python-glanceclient==2.8.0 +python-heatclient==1.10.0 +python-ironic-inspector-client==1.5.0 +python-ironicclient==2.2.0 +python-karborclient==0.6.0 +python-keystoneclient==3.8.0 +python-mimeparse==1.6.0 +python-mistralclient==3.1.0 +python-muranoclient==0.8.2 +python-neutronclient==6.7.0 +python-novaclient==9.1.0 +python-octaviaclient==1.3.0 +python-rsdclient==0.1.0 +python-saharaclient==1.4.0 +python-searchlightclient==1.0.0 +python-senlinclient==1.1.0 +python-subunit==1.0.0 +python-swiftclient==3.2.0 +python-troveclient==2.2.0 +python-zaqarclient==1.0.0 +python-zunclient==1.2.1 +pytz==2013.6 +PyYAML==3.12 +repoze.lru==0.7 +requests==2.14.2 +requests-mock==1.1.0 +requestsexceptions==1.2.0 +rfc3986==0.3.1 +Routes==2.3.1 +rsd-lib==0.1.0 +simplejson==3.5.1 +six==1.10.0 +smmap==0.9.0 +statsd==3.2.1 +stestr==1.0.0 +stevedore==1.20.0 +sushy==0.1.0 +tempest==17.1.0 +tenacity==3.2.1 +testrepository==0.0.18 +testtools==2.2.0 +traceback2==1.4.0 +ujson==1.35 +unittest2==1.1.0 +urllib3==1.21.1 +validictory==1.1.1 +vine==1.1.4 +warlock==1.2.0 +WebOb==1.7.1 +websocket-client==0.44.0 +wrapt==1.7.0 +yaql==1.1.3 diff --git a/tox.ini b/tox.ini index 7ba2f2191d..9c10eb2fb7 100644 --- a/tox.ini +++ b/tox.ini @@ -133,3 +133,10 @@ exclude = .git,.tox,dist,doc,*lib/python*,*egg,build,tools ignore = __ import-order-style = pep8 application_import_names = openstackclient + +[testenv:lower-constraints] +basepython = python3 +deps = + -c{toxinidir}/lower-constraints.txt + -r{toxinidir}/test-requirements.txt + -r{toxinidir}/requirements.txt From 447d5d9e344060be4f284ad56b430b20eb190c2b Mon Sep 17 00:00:00 2001 From: Chen Hanxiao Date: Fri, 19 Jan 2018 14:30:18 +0800 Subject: [PATCH 1900/3095] Add --image-property parameter in 'server create' add --image-property option, just like --image-with of novaclient did. Change-Id: Ic1a8976559255529a8785b1b301a0307812433cb Signed-off-by: Chen Hanxiao --- openstackclient/compute/v2/server.py | 45 +++++ .../tests/unit/compute/v2/test_server.py | 158 ++++++++++++++++++ ...reate-image-property-ef76af26233b472b.yaml | 5 + 3 files changed, 208 insertions(+) create mode 100644 releasenotes/notes/add-server-create-image-property-ef76af26233b472b.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 85c20aee6d..f60cbe56a5 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -442,6 +442,12 @@ def get_parser(self, prog_name): metavar='', help=_('Create server boot disk from this image (name or ID)'), ) + disk_group.add_argument( + '--image-property', + metavar='', + action=parseractions.KeyValueAction, + help=_("Image property to be matched"), + ) disk_group.add_argument( '--volume', metavar='', @@ -610,6 +616,45 @@ def take_action(self, parsed_args): parsed_args.image, ) + if not image and parsed_args.image_property: + def emit_duplicated_warning(img, image_property): + img_uuid_list = [str(image.id) for image in img] + LOG.warning(_('Multiple matching images: %(img_uuid_list)s\n' + 'Using image: %(chosen_one)s') % + {'img_uuid_list': img_uuid_list, + 'chosen_one': img_uuid_list[0]}) + + def _match_image(image_api, wanted_properties): + image_list = image_api.image_list() + images_matched = [] + for img in image_list: + img_dict = {} + # exclude any unhashable entries + for key, value in img.items(): + try: + set([key, value]) + except TypeError: + pass + else: + img_dict[key] = value + if all(k in img_dict and img_dict[k] == v + for k, v in wanted_properties.items()): + images_matched.append(img) + else: + return [] + return images_matched + + images = _match_image(image_client.api, parsed_args.image_property) + if len(images) > 1: + emit_duplicated_warning(images, + parsed_args.image_property) + if images: + image = images[0] + else: + raise exceptions.CommandError(_("No images match the " + "property expected by " + "--image-property")) + # Lookup parsed_args.volume volume = None if parsed_args.volume: diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 87c9a98514..feb9d1f8a2 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -1528,6 +1528,164 @@ def test_server_create_with_block_device_mapping_no_uuid(self): self.cmd.take_action, parsed_args) + def test_server_create_image_property(self): + arglist = [ + '--image-property', 'hypervisor_type=qemu', + '--flavor', 'flavor1', + '--nic', 'none', + self.new_server.name, + ] + verifylist = [ + ('image_property', {'hypervisor_type': 'qemu'}), + ('flavor', 'flavor1'), + ('nic', ['none']), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + _image = image_fakes.FakeImage.create_one_image() + # create a image_info as the side_effect of the fake image_list() + image_info = { + 'id': _image.id, + 'name': _image.name, + 'owner': _image.owner, + 'hypervisor_type': 'qemu', + } + self.api_mock = mock.Mock() + self.api_mock.image_list.side_effect = [ + [image_info], [], + ] + self.app.client_manager.image.api = self.api_mock + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = dict( + files={}, + reservation_id=None, + min_count=1, + max_count=1, + security_groups=[], + userdata=None, + key_name=None, + availability_zone=None, + block_device_mapping_v2=[], + nics='none', + meta=None, + scheduler_hints={}, + config_drive=None, + ) + # ServerManager.create(name, image, flavor, **kwargs) + self.servers_mock.create.assert_called_with( + self.new_server.name, + image_info, + self.flavor, + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) + + def test_server_create_image_property_multi(self): + arglist = [ + '--image-property', 'hypervisor_type=qemu', + '--image-property', 'hw_disk_bus=ide', + '--flavor', 'flavor1', + '--nic', 'none', + self.new_server.name, + ] + verifylist = [ + ('image_property', {'hypervisor_type': 'qemu', + 'hw_disk_bus': 'ide'}), + ('flavor', 'flavor1'), + ('nic', ['none']), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + _image = image_fakes.FakeImage.create_one_image() + # create a image_info as the side_effect of the fake image_list() + image_info = { + 'id': _image.id, + 'name': _image.name, + 'owner': _image.owner, + 'hypervisor_type': 'qemu', + 'hw_disk_bus': 'ide', + } + self.api_mock = mock.Mock() + self.api_mock.image_list.side_effect = [ + [image_info], [], + ] + self.app.client_manager.image.api = self.api_mock + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = dict( + files={}, + reservation_id=None, + min_count=1, + max_count=1, + security_groups=[], + userdata=None, + key_name=None, + availability_zone=None, + block_device_mapping_v2=[], + nics='none', + meta=None, + scheduler_hints={}, + config_drive=None, + ) + # ServerManager.create(name, image, flavor, **kwargs) + self.servers_mock.create.assert_called_with( + self.new_server.name, + image_info, + self.flavor, + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) + + def test_server_create_image_property_missed(self): + arglist = [ + '--image-property', 'hypervisor_type=qemu', + '--image-property', 'hw_disk_bus=virtio', + '--flavor', 'flavor1', + '--nic', 'none', + self.new_server.name, + ] + verifylist = [ + ('image_property', {'hypervisor_type': 'qemu', + 'hw_disk_bus': 'virtio'}), + ('flavor', 'flavor1'), + ('nic', ['none']), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + _image = image_fakes.FakeImage.create_one_image() + # create a image_info as the side_effect of the fake image_list() + image_info = { + 'id': _image.id, + 'name': _image.name, + 'owner': _image.owner, + 'hypervisor_type': 'qemu', + 'hw_disk_bus': 'ide', + } + self.api_mock = mock.Mock() + self.api_mock.image_list.side_effect = [ + [image_info], [], + ] + self.app.client_manager.image.api = self.api_mock + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, + parsed_args) + class TestServerDelete(TestServer): diff --git a/releasenotes/notes/add-server-create-image-property-ef76af26233b472b.yaml b/releasenotes/notes/add-server-create-image-property-ef76af26233b472b.yaml new file mode 100644 index 0000000000..9d71446fbc --- /dev/null +++ b/releasenotes/notes/add-server-create-image-property-ef76af26233b472b.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add a parameter ``--image-property`` to ``server create`` command. + This parameter will filter a image which properties that are matching. From 26b1732a7c74fcf3b7b70da68fba03a504706b6a Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 28 Mar 2018 10:36:59 +0000 Subject: [PATCH 1901/3095] Updated from global requirements Change-Id: I8c400b12269f00fcf9fcaac27098ca55cdc27ac4 --- doc/requirements.txt | 2 +- test-requirements.txt | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/requirements.txt b/doc/requirements.txt index 597b54eda5..d959d4431b 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -3,4 +3,4 @@ # process, which may cause wedges in the gate later. openstackdocstheme>=1.18.1 # Apache-2.0 reno>=2.5.0 # Apache-2.0 -sphinx!=1.6.6,>=1.6.2 # BSD +sphinx!=1.6.6,!=1.6.7,>=1.6.2 # BSD diff --git a/test-requirements.txt b/test-requirements.txt index 2dff3ff6b9..8ec69331b5 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -9,7 +9,7 @@ flake8-import-order==0.13 # LGPLv3 mock>=2.0.0 # BSD oslotest>=3.2.0 # Apache-2.0 requests>=2.14.2 # Apache-2.0 -requests-mock>=1.1.0 # Apache-2.0 +requests-mock>=1.2.0 # Apache-2.0 stevedore>=1.20.0 # Apache-2.0 os-client-config>=1.28.0 # Apache-2.0 stestr>=1.0.0 # Apache-2.0 @@ -22,11 +22,11 @@ wrapt>=1.7.0 # BSD License # Install these to generate sphinx autodocs aodhclient>=0.9.0 # Apache-2.0 gnocchiclient>=3.3.1 # Apache-2.0 -python-barbicanclient!=4.5.0,!=4.5.1,>=4.0.0 # Apache-2.0 +python-barbicanclient>=4.5.2 # Apache-2.0 python-congressclient<2000,>=1.9.0 # Apache-2.0 python-designateclient>=2.7.0 # Apache-2.0 python-heatclient>=1.10.0 # Apache-2.0 -python-ironicclient>=2.2.0 # Apache-2.0 +python-ironicclient>=2.3.0 # Apache-2.0 python-ironic-inspector-client>=1.5.0 # Apache-2.0 python-karborclient>=0.6.0 # Apache-2.0 python-mistralclient!=3.2.0,>=3.1.0 # Apache-2.0 @@ -39,4 +39,4 @@ python-searchlightclient>=1.0.0 #Apache-2.0 python-senlinclient>=1.1.0 # Apache-2.0 python-troveclient>=2.2.0 # Apache-2.0 python-zaqarclient>=1.0.0 # Apache-2.0 -python-zunclient>=1.0.0 # Apache-2.0 +python-zunclient>=1.3.0 # Apache-2.0 From 24b06ef273e193819624fd2e021282d2735977b0 Mon Sep 17 00:00:00 2001 From: Jude Cross Date: Mon, 2 Apr 2018 15:07:25 -0700 Subject: [PATCH 1902/3095] Fix limits show command without Nova and Cinder This patch implements an endpoint lookup when showing limits. This addresses the issue when showing limits without both Nova and Cinder and will display limits if one is missing. Change-Id: I2214b281e0206f8fe117aae52de2bf4c4e2c6525 Closes-bug: #1707960 --- openstackclient/common/clientmanager.py | 19 +++ openstackclient/common/limits.py | 32 +++-- .../tests/unit/common/test_limits.py | 125 ++++++++++++++++++ .../tests/unit/compute/v2/fakes.py | 110 +++++++++++++++ openstackclient/tests/unit/fakes.py | 8 ++ openstackclient/tests/unit/volume/v2/fakes.py | 100 ++++++++++++++ 6 files changed, 383 insertions(+), 11 deletions(-) create mode 100644 openstackclient/tests/unit/common/test_limits.py diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index 897810521d..aa1045e469 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -125,6 +125,25 @@ def is_network_endpoint_enabled(self): # use Network API by default return self.is_service_available('network') is not False + def is_compute_endpoint_enabled(self): + """Check if Compute endpoint is enabled""" + + return self.is_service_available('compute') is not False + + def is_volume_endpoint_enabled(self, volume_client): + """Check if volume endpoint is enabled""" + # NOTE(jcross): Cinder did some interesting things with their service + # name so we need to figure out which version to look + # for when calling is_service_available() + volume_version = volume_client.api_version.ver_major + if self.is_service_available( + "volumev%s" % volume_version) is not False: + return True + elif self.is_service_available('volume') is not False: + return True + else: + return False + # Plugin Support diff --git a/openstackclient/common/limits.py b/openstackclient/common/limits.py index 957f1d0212..19db35d7df 100644 --- a/openstackclient/common/limits.py +++ b/openstackclient/common/limits.py @@ -83,24 +83,34 @@ def take_action(self, parsed_args): project_id = utils.find_resource(identity_client.projects, parsed_args.project).id - compute_limits = compute_client.limits.get(parsed_args.is_reserved, - tenant_id=project_id) - volume_limits = volume_client.limits.get() + compute_limits = None + volume_limits = None + if self.app.client_manager.is_compute_endpoint_enabled(): + compute_limits = compute_client.limits.get(parsed_args.is_reserved, + tenant_id=project_id) + + if self.app.client_manager.is_volume_endpoint_enabled(volume_client): + volume_limits = volume_client.limits.get() + + data = [] if parsed_args.is_absolute: - compute_limits = compute_limits.absolute - volume_limits = volume_limits.absolute + if compute_limits: + data.append(compute_limits.absolute) + if volume_limits: + data.append(volume_limits.absolute) columns = ["Name", "Value"] return (columns, (utils.get_item_properties(s, columns) - for s in itertools.chain(compute_limits, volume_limits))) + for s in itertools.chain(*data))) elif parsed_args.is_rate: - compute_limits = compute_limits.rate - volume_limits = volume_limits.rate + if compute_limits: + data.append(compute_limits.rate) + if volume_limits: + data.append(volume_limits.rate) columns = ["Verb", "URI", "Value", "Remain", "Unit", "Next Available"] return (columns, (utils.get_item_properties(s, columns) - for s in itertools.chain(compute_limits, volume_limits))) - + for s in itertools.chain(*data))) else: - return ({}, {}) + return {}, {} diff --git a/openstackclient/tests/unit/common/test_limits.py b/openstackclient/tests/unit/common/test_limits.py new file mode 100644 index 0000000000..d73db2cb17 --- /dev/null +++ b/openstackclient/tests/unit/common/test_limits.py @@ -0,0 +1,125 @@ +# 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.common import limits +from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes +from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes + + +class TestComputeLimits(compute_fakes.TestComputev2): + + absolute_columns = [ + 'Name', + 'Value', + ] + + rate_columns = [ + "Verb", + "URI", + "Value", + "Remain", + "Unit", + "Next Available" + ] + + def setUp(self): + super(TestComputeLimits, self).setUp() + self.app.client_manager.volume_endpoint_enabled = False + self.compute = self.app.client_manager.compute + + self.fake_limits = compute_fakes.FakeLimits() + self.compute.limits.get.return_value = self.fake_limits + + def test_compute_show_absolute(self): + arglist = ['--absolute'] + verifylist = [('is_absolute', True)] + cmd = limits.ShowLimits(self.app, None) + parsed_args = self.check_parser(cmd, arglist, verifylist) + + columns, data = cmd.take_action(parsed_args) + + ret_limits = list(data) + compute_reference_limits = self.fake_limits.absolute_limits() + + self.assertEqual(self.absolute_columns, columns) + self.assertEqual(compute_reference_limits, ret_limits) + self.assertEqual(19, len(ret_limits)) + + def test_compute_show_rate(self): + arglist = ['--rate'] + verifylist = [('is_rate', True)] + cmd = limits.ShowLimits(self.app, None) + parsed_args = self.check_parser(cmd, arglist, verifylist) + + columns, data = cmd.take_action(parsed_args) + + ret_limits = list(data) + compute_reference_limits = self.fake_limits.rate_limits() + + self.assertEqual(self.rate_columns, columns) + self.assertEqual(compute_reference_limits, ret_limits) + self.assertEqual(3, len(ret_limits)) + + +class TestVolumeLimits(volume_fakes.TestVolume): + absolute_columns = [ + 'Name', + 'Value', + ] + + rate_columns = [ + "Verb", + "URI", + "Value", + "Remain", + "Unit", + "Next Available" + ] + + def setUp(self): + super(TestVolumeLimits, self).setUp() + self.app.client_manager.compute_endpoint_enabled = False + self.volume = self.app.client_manager.volume + + self.fake_limits = volume_fakes.FakeLimits() + self.volume.limits.get.return_value = self.fake_limits + + def test_volume_show_absolute(self): + arglist = ['--absolute'] + verifylist = [('is_absolute', True)] + cmd = limits.ShowLimits(self.app, None) + parsed_args = self.check_parser(cmd, arglist, verifylist) + + columns, data = cmd.take_action(parsed_args) + + ret_limits = list(data) + compute_reference_limits = self.fake_limits.absolute_limits() + + self.assertEqual(self.absolute_columns, columns) + self.assertEqual(compute_reference_limits, ret_limits) + self.assertEqual(10, len(ret_limits)) + + def test_volume_show_rate(self): + arglist = ['--rate'] + verifylist = [('is_rate', True)] + cmd = limits.ShowLimits(self.app, None) + parsed_args = self.check_parser(cmd, arglist, verifylist) + + columns, data = cmd.take_action(parsed_args) + + ret_limits = list(data) + compute_reference_limits = self.fake_limits.rate_limits() + + self.assertEqual(self.rate_columns, columns) + self.assertEqual(compute_reference_limits, ret_limits) + self.assertEqual(3, len(ret_limits)) diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index 1ec717853d..46fa599284 100644 --- a/openstackclient/tests/unit/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -149,6 +149,9 @@ def __init__(self, **kwargs): self.images = mock.Mock() self.images.resource_class = fakes.FakeResource(None, {}) + self.limits = mock.Mock() + self.limits.resource_class = fakes.FakeResource(None, {}) + self.servers = mock.Mock() self.servers.resource_class = fakes.FakeResource(None, {}) @@ -1392,3 +1395,110 @@ def create_one_default_comp_quota(attrs=None): quota.project_id = quota_attrs['id'] return quota + + +class FakeLimits(object): + """Fake limits""" + + def __init__(self, absolute_attrs=None, rate_attrs=None): + self.absolute_limits_attrs = { + 'maxServerMeta': 128, + 'maxTotalInstances': 10, + 'maxPersonality': 5, + 'totalServerGroupsUsed': 0, + 'maxImageMeta': 128, + 'maxPersonalitySize': 10240, + 'maxTotalRAMSize': 51200, + 'maxServerGroups': 10, + 'maxSecurityGroupRules': 20, + 'maxTotalKeypairs': 100, + 'totalCoresUsed': 0, + 'totalRAMUsed': 0, + 'maxSecurityGroups': 10, + 'totalFloatingIpsUsed': 0, + 'totalInstancesUsed': 0, + 'maxServerGroupMembers': 10, + 'maxTotalFloatingIps': 10, + 'totalSecurityGroupsUsed': 0, + 'maxTotalCores': 20, + } + absolute_attrs = absolute_attrs or {} + self.absolute_limits_attrs.update(absolute_attrs) + + self.rate_limits_attrs = [{ + "uri": "*", + "limit": [ + { + "value": 10, + "verb": "POST", + "remaining": 2, + "unit": "MINUTE", + "next-available": "2011-12-15T22:42:45Z" + }, + { + "value": 10, + "verb": "PUT", + "remaining": 2, + "unit": "MINUTE", + "next-available": "2011-12-15T22:42:45Z" + }, + { + "value": 100, + "verb": "DELETE", + "remaining": 100, + "unit": "MINUTE", + "next-available": "2011-12-15T22:42:45Z" + } + ] + }] + + @property + def absolute(self): + for (name, value) in self.absolute_limits_attrs.items(): + yield FakeAbsoluteLimit(name, value) + + def absolute_limits(self): + reference_data = [] + for (name, value) in self.absolute_limits_attrs.items(): + reference_data.append((name, value)) + return reference_data + + @property + def rate(self): + for group in self.rate_limits_attrs: + uri = group['uri'] + for rate in group['limit']: + yield FakeRateLimit(rate['verb'], uri, rate['value'], + rate['remaining'], rate['unit'], + rate['next-available']) + + def rate_limits(self): + reference_data = [] + for group in self.rate_limits_attrs: + uri = group['uri'] + for rate in group['limit']: + reference_data.append((rate['verb'], uri, rate['value'], + rate['remaining'], rate['unit'], + rate['next-available'])) + return reference_data + + +class FakeAbsoluteLimit(object): + """Data model that represents an absolute limit""" + + def __init__(self, name, value): + self.name = name + self.value = value + + +class FakeRateLimit(object): + """Data model that represents a flattened view of a single rate limit""" + + def __init__(self, verb, uri, value, remain, + unit, next_available): + self.verb = verb + self.uri = uri + self.value = value + self.remain = remain + self.unit = unit + self.next_available = next_available diff --git a/openstackclient/tests/unit/fakes.py b/openstackclient/tests/unit/fakes.py index 65c76b3e33..954973ef98 100644 --- a/openstackclient/tests/unit/fakes.py +++ b/openstackclient/tests/unit/fakes.py @@ -140,6 +140,8 @@ def __init__(self): self.auth_ref = None self.auth_plugin_name = None self.network_endpoint_enabled = True + self.compute_endpoint_enabled = True + self.volume_endpoint_enabled = True def get_configuration(self): return { @@ -155,6 +157,12 @@ def get_configuration(self): def is_network_endpoint_enabled(self): return self.network_endpoint_enabled + def is_compute_endpoint_enabled(self): + return self.compute_endpoint_enabled + + def is_volume_endpoint_enabled(self, client): + return self.volume_endpoint_enabled + class FakeModule(object): diff --git a/openstackclient/tests/unit/volume/v2/fakes.py b/openstackclient/tests/unit/volume/v2/fakes.py index 27f37bd8c9..481509f3b8 100644 --- a/openstackclient/tests/unit/volume/v2/fakes.py +++ b/openstackclient/tests/unit/volume/v2/fakes.py @@ -200,6 +200,8 @@ def __init__(self, **kwargs): self.volumes.resource_class = fakes.FakeResource(None, {}) self.extensions = mock.Mock() self.extensions.resource_class = fakes.FakeResource(None, {}) + self.limits = mock.Mock() + self.limits.resource_class = fakes.FakeResource(None, {}) self.volume_snapshots = mock.Mock() self.volume_snapshots.resource_class = fakes.FakeResource(None, {}) self.backups = mock.Mock() @@ -1004,3 +1006,101 @@ def create_one_default_vol_quota(attrs=None): quota.project_id = quota_attrs['id'] return quota + + +class FakeLimits(object): + """Fake limits""" + + def __init__(self, absolute_attrs=None): + self.absolute_limits_attrs = { + 'totalSnapshotsUsed': 1, + 'maxTotalBackups': 10, + 'maxTotalVolumeGigabytes': 1000, + 'maxTotalSnapshots': 10, + 'maxTotalBackupGigabytes': 1000, + 'totalBackupGigabytesUsed': 0, + 'maxTotalVolumes': 10, + 'totalVolumesUsed': 4, + 'totalBackupsUsed': 0, + 'totalGigabytesUsed': 35 + } + absolute_attrs = absolute_attrs or {} + self.absolute_limits_attrs.update(absolute_attrs) + + self.rate_limits_attrs = [{ + "uri": "*", + "limit": [ + { + "value": 10, + "verb": "POST", + "remaining": 2, + "unit": "MINUTE", + "next-available": "2011-12-15T22:42:45Z" + }, + { + "value": 10, + "verb": "PUT", + "remaining": 2, + "unit": "MINUTE", + "next-available": "2011-12-15T22:42:45Z" + }, + { + "value": 100, + "verb": "DELETE", + "remaining": 100, + "unit": "MINUTE", + "next-available": "2011-12-15T22:42:45Z" + } + ] + }] + + @property + def absolute(self): + for (name, value) in self.absolute_limits_attrs.items(): + yield FakeAbsoluteLimit(name, value) + + def absolute_limits(self): + reference_data = [] + for (name, value) in self.absolute_limits_attrs.items(): + reference_data.append((name, value)) + return reference_data + + @property + def rate(self): + for group in self.rate_limits_attrs: + uri = group['uri'] + for rate in group['limit']: + yield FakeRateLimit(rate['verb'], uri, rate['value'], + rate['remaining'], rate['unit'], + rate['next-available']) + + def rate_limits(self): + reference_data = [] + for group in self.rate_limits_attrs: + uri = group['uri'] + for rate in group['limit']: + reference_data.append((rate['verb'], uri, rate['value'], + rate['remaining'], rate['unit'], + rate['next-available'])) + return reference_data + + +class FakeAbsoluteLimit(object): + """Data model that represents an absolute limit.""" + + def __init__(self, name, value): + self.name = name + self.value = value + + +class FakeRateLimit(object): + """Data model that represents a flattened view of a single rate limit.""" + + def __init__(self, verb, uri, value, remain, + unit, next_available): + self.verb = verb + self.uri = uri + self.value = value + self.remain = remain + self.unit = unit + self.next_available = next_available From ea89065dabf92c2684e55c4b37c7be9b667cfa1e Mon Sep 17 00:00:00 2001 From: Dongcan Ye Date: Thu, 22 Feb 2018 09:25:32 +0000 Subject: [PATCH 1903/3095] Remove deprecated ip floating commands We had already implemented floating ip(pool) commands more than two cycles, we can remove those deprecated commands. Change-Id: Ib98a7403a63bb0c48c03de4c79795737de2aa84c --- .../cli/command-objects/ip-floating-pool.rst | 16 --- .../cli/command-objects/ip-floating.rst | 129 ------------------ openstackclient/network/v2/floating_ip.py | 98 ------------- .../network/v2/floating_ip_pool.py | 25 ---- ...ip-floating-commands-d5363f313e09249a.yaml | 4 + setup.cfg | 7 - 6 files changed, 4 insertions(+), 275 deletions(-) delete mode 100644 doc/source/cli/command-objects/ip-floating-pool.rst delete mode 100644 doc/source/cli/command-objects/ip-floating.rst create mode 100644 releasenotes/notes/remove-ip-floating-commands-d5363f313e09249a.yaml diff --git a/doc/source/cli/command-objects/ip-floating-pool.rst b/doc/source/cli/command-objects/ip-floating-pool.rst deleted file mode 100644 index 6d00355ab4..0000000000 --- a/doc/source/cli/command-objects/ip-floating-pool.rst +++ /dev/null @@ -1,16 +0,0 @@ -================ -ip floating pool -================ - -Compute v2 - -ip floating pool list ---------------------- - -List pools of floating IP addresses -(Deprecated, please use ``floating ip pool list`` instead) - -.. program:: ip floating pool list -.. code:: bash - - openstack ip floating pool list diff --git a/doc/source/cli/command-objects/ip-floating.rst b/doc/source/cli/command-objects/ip-floating.rst deleted file mode 100644 index 4e5f7b008a..0000000000 --- a/doc/source/cli/command-objects/ip-floating.rst +++ /dev/null @@ -1,129 +0,0 @@ -=========== -ip floating -=========== - -Compute v2, Network v2 - -ip floating add ---------------- - -Add floating IP address to server -(Deprecated, please use ``server add floating ip`` instead) - -.. program:: ip floating add -.. code:: bash - - openstack ip floating add - - - -.. describe:: - - IP address to add to server (name only) - -.. describe:: - - Server to receive the IP address (name or ID) - -ip floating create ------------------- - -Create new floating IP address -(Deprecated, please use ``floating ip create`` instead) - -.. program:: ip floating create -.. code:: bash - - openstack ip floating create - [--subnet ] - [--port ] - [--floating-ip-address ] - [--fixed-ip-address ] - - -.. option:: --subnet - - Subnet on which you want to create the floating IP (name or ID) - (Network v2 only) - -.. option:: --port - - Port to be associated with the floating IP (name or ID) - (Network v2 only) - -.. option:: --floating-ip-address - - Floating IP address - (Network v2 only) - -.. option:: --fixed-ip-address - - Fixed IP address mapped to the floating IP - (Network v2 only) - -.. describe:: - - Network to allocate floating IP from (name or ID) - -ip floating delete ------------------- - -Delete floating IP(s) -(Deprecated, please use ``floating ip delete`` instead) - -.. program:: ip floating delete -.. code:: bash - - openstack ip floating delete - [ ...] - -.. describe:: - - Floating IP(s) to delete (IP address or ID) - -ip floating list ----------------- - -List floating IP addresses -(Deprecated, please use ``floating ip list`` instead) - -.. program:: ip floating list -.. code:: bash - - openstack ip floating list - -ip floating remove ------------------- - -Remove floating IP address from server -(Deprecated, please use ``server remove floating ip`` instead) - -.. program:: ip floating remove -.. code:: bash - - openstack ip floating remove - - - -.. describe:: - - IP address to remove from server (name only) - -.. describe:: - - Server to remove the IP address from (name or ID) - -ip floating show ----------------- - -Display floating IP details -(Deprecated, please use ``floating ip show`` instead) - -.. program:: ip floating show -.. code:: bash - - openstack ip floating show - -.. describe:: - - Floating IP to display (IP address or ID) diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index a6f0340466..6de138c150 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -13,8 +13,6 @@ """IP Floating action implementations""" -import logging - from osc_lib.command import command from osc_lib import utils @@ -155,30 +153,6 @@ def take_action_compute(self, client, parsed_args): return (columns, data) -class CreateIPFloating(CreateFloatingIP): - _description = _("Create floating IP") - - # TODO(tangchen): Remove this class and ``ip floating create`` command - # two cycles after Mitaka. - - # This notifies cliff to not display the help for this command - deprecated = True - - log = logging.getLogger('deprecated') - - def take_action_network(self, client, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "floating ip create" instead.')) - return super(CreateIPFloating, self).take_action_network( - client, parsed_args) - - def take_action_compute(self, client, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "floating ip create" instead.')) - return super(CreateIPFloating, self).take_action_compute( - client, parsed_args) - - class DeleteFloatingIP(common.NetworkAndComputeDelete): _description = _("Delete floating IP(s)") @@ -206,30 +180,6 @@ def take_action_compute(self, client, parsed_args): client.api.floating_ip_delete(self.r) -class DeleteIPFloating(DeleteFloatingIP): - _description = _("Delete floating IP(s)") - - # TODO(tangchen): Remove this class and ``ip floating delete`` command - # two cycles after Mitaka. - - # This notifies cliff to not display the help for this command - deprecated = True - - log = logging.getLogger('deprecated') - - def take_action_network(self, client, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "floating ip delete" instead.')) - return super(DeleteIPFloating, self).take_action_network( - client, parsed_args) - - def take_action_compute(self, client, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "floating ip delete" instead.')) - return super(DeleteIPFloating, self).take_action_compute( - client, parsed_args) - - class ListFloatingIP(common.NetworkAndComputeLister): # TODO(songminglong): Use SDK resource mapped attribute names once # the OSC minimum requirements include SDK 1.0 @@ -375,30 +325,6 @@ def take_action_compute(self, client, parsed_args): ) for s in data)) -class ListIPFloating(ListFloatingIP): - _description = _("List floating IP(s)") - - # TODO(tangchen): Remove this class and ``ip floating list`` command - # two cycles after Mitaka. - - # This notifies cliff to not display the help for this command - deprecated = True - - log = logging.getLogger('deprecated') - - def take_action_network(self, client, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "floating ip list" instead.')) - return super(ListIPFloating, self).take_action_network( - client, parsed_args) - - def take_action_compute(self, client, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "floating ip list" instead.')) - return super(ListIPFloating, self).take_action_compute( - client, parsed_args) - - class SetFloatingIP(command.Command): _description = _("Set floating IP Properties") @@ -483,30 +409,6 @@ def take_action_compute(self, client, parsed_args): return (columns, data) -class ShowIPFloating(ShowFloatingIP): - _description = _("Display floating IP details") - - # TODO(tangchen): Remove this class and ``ip floating show`` command - # two cycles after Mitaka. - - # This notifies cliff to not display the help for this command - deprecated = True - - log = logging.getLogger('deprecated') - - def take_action_network(self, client, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "floating ip show" instead.')) - return super(ShowIPFloating, self).take_action_network( - client, parsed_args) - - def take_action_compute(self, client, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "floating ip show" instead.')) - return super(ShowIPFloating, self).take_action_compute( - client, parsed_args) - - class UnsetFloatingIP(command.Command): _description = _("Unset floating IP Properties") diff --git a/openstackclient/network/v2/floating_ip_pool.py b/openstackclient/network/v2/floating_ip_pool.py index ebb15da8d7..32852004c5 100644 --- a/openstackclient/network/v2/floating_ip_pool.py +++ b/openstackclient/network/v2/floating_ip_pool.py @@ -13,7 +13,6 @@ """Floating IP Pool action implementations""" -import logging from osc_lib import exceptions from osc_lib import utils @@ -40,27 +39,3 @@ def take_action_compute(self, client, parsed_args): (utils.get_dict_properties( s, columns, ) for s in data)) - - -class ListIPFloatingPool(ListFloatingIPPool): - _description = _("List pools of floating IP addresses") - - # TODO(tangchen): Remove this class and ``ip floating pool list`` command - # two cycles after Mitaka. - - # This notifies cliff to not display the help for this command - deprecated = True - - log = logging.getLogger('deprecated') - - def take_action_network(self, client, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "floating ip pool list" instead.')) - return super(ListIPFloatingPool, self).take_action_network( - client, parsed_args) - - def take_action_compute(self, client, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "floating ip pool list" instead.')) - return super(ListIPFloatingPool, self).take_action_compute( - client, parsed_args) diff --git a/releasenotes/notes/remove-ip-floating-commands-d5363f313e09249a.yaml b/releasenotes/notes/remove-ip-floating-commands-d5363f313e09249a.yaml new file mode 100644 index 0000000000..9e223a412a --- /dev/null +++ b/releasenotes/notes/remove-ip-floating-commands-d5363f313e09249a.yaml @@ -0,0 +1,4 @@ +--- +other: + - | + Remove deprecated ``ip floating`` and ``ip floating pool`` commands. diff --git a/setup.cfg b/setup.cfg index 63bfdafb13..71a3dee524 100644 --- a/setup.cfg +++ b/setup.cfg @@ -365,13 +365,6 @@ openstack.network.v2 = ip_availability_list = openstackclient.network.v2.ip_availability:ListIPAvailability ip_availability_show = openstackclient.network.v2.ip_availability:ShowIPAvailability - ip_floating_create = openstackclient.network.v2.floating_ip:CreateIPFloating - ip_floating_delete = openstackclient.network.v2.floating_ip:DeleteIPFloating - ip_floating_list = openstackclient.network.v2.floating_ip:ListIPFloating - ip_floating_show = openstackclient.network.v2.floating_ip:ShowIPFloating - - ip_floating_pool_list = openstackclient.network.v2.floating_ip_pool:ListIPFloatingPool - network_agent_add_network = openstackclient.network.v2.network_agent:AddNetworkToAgent network_agent_add_router = openstackclient.network.v2.network_agent:AddRouterToAgent network_agent_delete = openstackclient.network.v2.network_agent:DeleteNetworkAgent From 30b2203dc75a4fc776875b02258f8d54660249d7 Mon Sep 17 00:00:00 2001 From: Jens Harbott Date: Mon, 9 Apr 2018 13:12:30 +0000 Subject: [PATCH 1904/3095] Add help for nova interface-list to decoder Running "openstack port list --server " is a good replacement for "nova interface-list ", add it to the decoder. Change-Id: I656e190f4da59d9c57e9ab97d068a8a0dbd77774 --- doc/source/cli/data/nova.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/cli/data/nova.csv b/doc/source/cli/data/nova.csv index 74f76c77e4..338244e05c 100644 --- a/doc/source/cli/data/nova.csv +++ b/doc/source/cli/data/nova.csv @@ -60,7 +60,7 @@ instance-action,,Show an action. instance-action-list,,List actions on a server. interface-attach,,Attach a network interface to a server. interface-detach,,Detach a network interface from a server. -interface-list,,List interfaces attached to a server. +interface-list,port list --server,List interfaces attached to a server. keypair-add,keypair create,Create a new key pair for use with servers. keypair-delete,keypair delete,Delete keypair given by its name. keypair-list,keypair list,Print a list of keypairs for a user @@ -137,4 +137,4 @@ volume-update,,Update volume attachment. x509-create-cert,WONTFIX,Create x509 cert for a user in tenant. x509-get-root-cert,WONTFIX,Fetch the x509 root cert. bash-completion,complete,Prints all of the commands and options to -help,help,Display help about this program or one of its subcommands. \ No newline at end of file +help,help,Display help about this program or one of its subcommands. From d4d97f2696a8f49599b3948fb10b9641a6ee2f10 Mon Sep 17 00:00:00 2001 From: Jens Harbott Date: Mon, 9 Apr 2018 13:50:18 +0000 Subject: [PATCH 1905/3095] Add bgp commands to neutron decoder Help folks that need to migrate their use of the neutron CLI for neutron-dynamic-routing commands. Change-Id: I324608d7bbce8c55aaab122bfd8bff0ee1ccf9e3 --- doc/source/cli/data/neutron.csv | 38 ++++++++++++++++----------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/doc/source/cli/data/neutron.csv b/doc/source/cli/data/neutron.csv index a276cf8963..be3f32a867 100644 --- a/doc/source/cli/data/neutron.csv +++ b/doc/source/cli/data/neutron.csv @@ -11,25 +11,25 @@ auto-allocated-topology-delete,network auto allocated topology delete,Delete the auto-allocated-topology-show,network auto allocated topology create,Show the auto-allocated topology of a given tenant. availability-zone-list,availability zone list,List availability zones. bash-completion,complete,Prints all of the commands and options for bash-completion. -bgp-dragent-list-hosting-speaker,,List Dynamic Routing agents hosting a BGP speaker. -bgp-dragent-speaker-add,,Add a BGP speaker to a Dynamic Routing agent. -bgp-dragent-speaker-remove,,Removes a BGP speaker from a Dynamic Routing agent. -bgp-peer-create,,Create a BGP Peer. -bgp-peer-delete,,Delete a BGP peer. -bgp-peer-list,,List BGP peers. -bgp-peer-show,,Show information of a given BGP peer. -bgp-peer-update,,Update BGP Peer's information. -bgp-speaker-advertiseroute-list,,List routes advertised by a given BGP speaker. -bgp-speaker-create,,Create a BGP Speaker. -bgp-speaker-delete,,Delete a BGP speaker. -bgp-speaker-list,,List BGP speakers. -bgp-speaker-list-on-dragent,,List BGP speakers hosted by a Dynamic Routing agent. -bgp-speaker-network-add,,Add a network to the BGP speaker. -bgp-speaker-network-remove,,Remove a network from the BGP speaker. -bgp-speaker-peer-add,,Add a peer to the BGP speaker. -bgp-speaker-peer-remove,,Remove a peer from the BGP speaker. -bgp-speaker-show,,Show information of a given BGP speaker. -bgp-speaker-update,,Update BGP Speaker's information. +bgp-dragent-list-hosting-speaker,bgp speaker show dragents,List Dynamic Routing agents hosting a BGP speaker. +bgp-dragent-speaker-add,bgp dragent add speaker,Add a BGP speaker to a Dynamic Routing agent. +bgp-dragent-speaker-remove,bgp dragent remove speaker,Removes a BGP speaker from a Dynamic Routing agent. +bgp-peer-create,bgp peer create,Create a BGP Peer. +bgp-peer-delete,bgp peer delete,Delete a BGP peer. +bgp-peer-list,bgp peer list,List BGP peers. +bgp-peer-show,bgp peer show,Show information of a given BGP peer. +bgp-peer-update,bgp peer set,Update BGP Peer's information. +bgp-speaker-advertiseroute-list,bgp speaker list advertised routes,List routes advertised by a given BGP speaker. +bgp-speaker-create,bgp speaker create,Create a BGP Speaker. +bgp-speaker-delete,bgp speaker delete,Delete a BGP speaker. +bgp-speaker-list,bgp speaker list,List BGP speakers. +bgp-speaker-list-on-dragent,bgp speaker list --agent,List BGP speakers hosted by a Dynamic Routing agent. +bgp-speaker-network-add,bgp speaker add network,Add a network to the BGP speaker. +bgp-speaker-network-remove,bgp speaker remove network,Remove a network from the BGP speaker. +bgp-speaker-peer-add,bgp speaker add peer,Add a peer to the BGP speaker. +bgp-speaker-peer-remove,bgp speaker remove peer,Remove a peer from the BGP speaker. +bgp-speaker-show,bgp speaker show,Show information of a given BGP speaker. +bgp-speaker-update,bgp speaker set,Update BGP Speaker's information. dhcp-agent-list-hosting-net,network agent list --network,List DHCP agents hosting a network. dhcp-agent-network-add,network agent add network,Add a network to a DHCP agent. dhcp-agent-network-remove,network agent remove network,Remove a network from a DHCP agent. From d60141525987bc973802b4ec9a3b027e071d1966 Mon Sep 17 00:00:00 2001 From: Sean McGinnis Date: Tue, 10 Apr 2018 14:32:33 -0500 Subject: [PATCH 1906/3095] Clean up W503 and E402 pep8 errors pycodestyle 2.40 and later enforce these rules that were not previously enforced. Rather than just skipping them, this cleans up the trivial instances of these violations. This does also include some other updates that were not triggering errors in an attempt to keep some of the style consistent. Change-Id: Id7c0a6b8f1f835e69d844b000e3ed751852ada63 Closes-bug: #1762803 --- openstackclient/__init__.py | 4 +- openstackclient/common/extension.py | 6 +- openstackclient/identity/v3/role.py | 8 +- openstackclient/image/v1/image.py | 10 +- openstackclient/network/v2/router.py | 4 +- .../functional/network/v2/test_network.py | 70 ++++---- .../network/v2/test_network_agent.py | 45 ++--- .../network/v2/test_network_flavor.py | 6 +- .../network/v2/test_network_qos_rule.py | 154 +++++++++--------- .../tests/functional/network/v2/test_port.py | 90 +++++----- .../tests/functional/object/v1/test_object.py | 32 ++-- .../functional/volume/v1/test_volume_type.py | 26 +-- .../functional/volume/v2/test_volume_type.py | 32 ++-- 13 files changed, 232 insertions(+), 255 deletions(-) diff --git a/openstackclient/__init__.py b/openstackclient/__init__.py index 89deee32ea..e6e7f7c031 100644 --- a/openstackclient/__init__.py +++ b/openstackclient/__init__.py @@ -11,10 +11,10 @@ # under the License. # -__all__ = ['__version__'] - import pbr.version +__all__ = ['__version__'] + version_info = pbr.version.VersionInfo('python-openstackclient') try: __version__ = version_info.version_string() diff --git a/openstackclient/common/extension.py b/openstackclient/common/extension.py index 139f43abf2..7120661880 100644 --- a/openstackclient/common/extension.py +++ b/openstackclient/common/extension.py @@ -75,8 +75,10 @@ def take_action(self, parsed_args): # by default we want to show everything, unless the # user specifies one or more of the APIs to show # for now, only identity and compute are supported. - show_all = (not parsed_args.identity and not parsed_args.compute - and not parsed_args.volume and not parsed_args.network) + show_all = (not parsed_args.identity and + not parsed_args.compute and + not parsed_args.volume and + not parsed_args.network) if parsed_args.identity or show_all: identity_client = self.app.client_manager.identity diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index 1bbf5f07b0..2828a34928 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -126,8 +126,8 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - if (not parsed_args.user and not parsed_args.domain - and not parsed_args.group and not parsed_args.project): + if (not parsed_args.user and not parsed_args.domain and + not parsed_args.group and not parsed_args.project): msg = _("Role not added, incorrect set of arguments " "provided. See openstack --help for more details") raise exceptions.CommandError(msg) @@ -399,8 +399,8 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - if (not parsed_args.user and not parsed_args.domain - and not parsed_args.group and not parsed_args.project): + if (not parsed_args.user and not parsed_args.domain and + not parsed_args.group and not parsed_args.project): msg = _("Incorrect set of arguments provided. " "See openstack --help for more details") raise exceptions.CommandError(msg) diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index 7a8e67bfca..7ecaa3efed 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -21,11 +21,6 @@ import os import sys -if os.name == "nt": - import msvcrt -else: - msvcrt = None - from glanceclient.common import utils as gc_utils from osc_lib.cli import parseractions from osc_lib.command import command @@ -35,6 +30,11 @@ from openstackclient.api import utils as api_utils from openstackclient.i18n import _ +if os.name == "nt": + import msvcrt +else: + msvcrt = None + CONTAINER_CHOICES = ["ami", "ari", "aki", "bare", "docker", "ova", "ovf"] DEFAULT_CONTAINER_FORMAT = 'bare' diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index caf3236ab5..f0a5196744 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -86,8 +86,8 @@ def _get_attrs(client_manager, parsed_args): attrs['distributed'] = False if parsed_args.distributed: attrs['distributed'] = True - if ('availability_zone_hints' in parsed_args - and parsed_args.availability_zone_hints is not None): + if ('availability_zone_hints' in parsed_args and + parsed_args.availability_zone_hints is not None): attrs['availability_zone_hints'] = parsed_args.availability_zone_hints if parsed_args.description is not None: attrs['description'] = parsed_args.description diff --git a/openstackclient/tests/functional/network/v2/test_network.py b/openstackclient/tests/functional/network/v2/test_network.py index 40fb382a9e..9cef135fe7 100644 --- a/openstackclient/tests/functional/network/v2/test_network.py +++ b/openstackclient/tests/functional/network/v2/test_network.py @@ -209,7 +209,7 @@ def test_network_delete_network(self): cmd_output["description"], ) - del_output = self.openstack('network delete ' + name1 + ' ' + name2) + del_output = self.openstack('network delete %s %s' % (name1, name2)) self.assertOutput('', del_output) def test_network_list(self): @@ -224,7 +224,7 @@ def test_network_list(self): network_options + name1 )) - self.addCleanup(self.openstack, 'network delete ' + name1) + self.addCleanup(self.openstack, 'network delete %s' % name1) self.assertIsNotNone(cmd_output["id"]) if self.haz_network: self.assertEqual( @@ -264,10 +264,8 @@ def test_network_list(self): else: network_options = '--subnet 4.5.6.7/28 ' cmd_output = json.loads(self.openstack( - 'network create -f json ' + - '--share ' + - network_options + - name2 + 'network create -f json --share %s%s' % + (network_options, name2) )) self.addCleanup(self.openstack, 'network delete ' + name2) self.assertIsNotNone(cmd_output["id"]) @@ -313,8 +311,7 @@ def test_network_list(self): # Test list --long if self.haz_network: cmd_output = json.loads(self.openstack( - "network list -f json " + - "--long" + "network list -f json --long" )) col_name = [x["Name"] for x in cmd_output] self.assertIn(name1, col_name) @@ -323,9 +320,7 @@ def test_network_list(self): # Test list --long --enable if self.haz_network: cmd_output = json.loads(self.openstack( - "network list -f json " + - "--enable " + - "--long" + "network list -f json --enable --long" )) col_name = [x["Name"] for x in cmd_output] self.assertIn(name1, col_name) @@ -334,9 +329,7 @@ def test_network_list(self): # Test list --long --disable if self.haz_network: cmd_output = json.loads(self.openstack( - "network list -f json " + - "--disable " + - "--long" + "network list -f json --disable --long" )) col_name = [x["Name"] for x in cmd_output] self.assertNotIn(name1, col_name) @@ -345,8 +338,7 @@ def test_network_list(self): # Test list --share if self.haz_network: cmd_output = json.loads(self.openstack( - "network list -f json " + - "--share " + "network list -f json --share " )) col_name = [x["Name"] for x in cmd_output] self.assertNotIn(name1, col_name) @@ -355,8 +347,7 @@ def test_network_list(self): # Test list --no-share if self.haz_network: cmd_output = json.loads(self.openstack( - "network list -f json " + - "--no-share " + "network list -f json --no-share " )) col_name = [x["Name"] for x in cmd_output] self.assertIn(name1, col_name) @@ -368,12 +359,10 @@ def test_network_dhcp_agent(self): name1 = uuid.uuid4().hex cmd_output1 = json.loads(self.openstack( - 'network create -f json ' + - '--description aaaa ' + - name1 + 'network create -f json --description aaaa %s' % name1 )) - self.addCleanup(self.openstack, 'network delete ' + name1) + self.addCleanup(self.openstack, 'network delete %s' % name1) # Get network ID network_id = cmd_output1['id'] @@ -386,20 +375,19 @@ def test_network_dhcp_agent(self): # Add Agent to Network self.openstack( - 'network agent add network --dhcp ' - + agent_id + ' ' + network_id + 'network agent add network --dhcp %s %s' % (agent_id, network_id) ) # Test network list --agent cmd_output3 = json.loads(self.openstack( - 'network list -f json --agent ' + agent_id + 'network list -f json --agent %s' % agent_id )) # Cleanup # Remove Agent from Network self.openstack( - 'network agent remove network --dhcp ' - + agent_id + ' ' + network_id + 'network agent remove network --dhcp %s %s' % + (agent_id, network_id) ) # Assert @@ -415,16 +403,16 @@ def test_network_set(self): name = uuid.uuid4().hex cmd_output = json.loads(self.openstack( - 'network create -f json ' + - '--description aaaa ' + - '--enable ' + - '--no-share ' + - '--internal ' + - '--no-default ' + - '--enable-port-security ' + + 'network create -f json ' + '--description aaaa ' + '--enable ' + '--no-share ' + '--internal ' + '--no-default ' + '--enable-port-security %s' % name )) - self.addCleanup(self.openstack, 'network delete ' + name) + self.addCleanup(self.openstack, 'network delete %s' % name) self.assertIsNotNone(cmd_output["id"]) self.assertEqual( 'aaaa', @@ -453,12 +441,12 @@ def test_network_set(self): ) raw_output = self.openstack( - 'network set ' + - '--description cccc ' + - '--disable ' + - '--share ' + - '--external ' + - '--disable-port-security ' + + 'network set ' + '--description cccc ' + '--disable ' + '--share ' + '--external ' + '--disable-port-security %s' % name ) self.assertOutput('', raw_output) diff --git a/openstackclient/tests/functional/network/v2/test_network_agent.py b/openstackclient/tests/functional/network/v2/test_network_agent.py index 0c74ea1d05..86769e0c8d 100644 --- a/openstackclient/tests/functional/network/v2/test_network_agent.py +++ b/openstackclient/tests/functional/network/v2/test_network_agent.py @@ -42,8 +42,7 @@ def test_network_agent_list_show_set(self): # agent show cmd_output = json.loads(self.openstack( - 'network agent show -f json ' + - agent_ids[0] + 'network agent show -f json %s' % agent_ids[0] )) self.assertEqual( agent_ids[0], @@ -52,15 +51,12 @@ def test_network_agent_list_show_set(self): # agent set raw_output = self.openstack( - 'network agent set ' + - '--disable ' + - agent_ids[0] + 'network agent set --disable %s' % agent_ids[0] ) self.assertOutput('', raw_output) cmd_output = json.loads(self.openstack( - 'network agent show -f json ' + - agent_ids[0] + 'network agent show -f json %s' % agent_ids[0] )) self.assertEqual( "DOWN", @@ -68,15 +64,12 @@ def test_network_agent_list_show_set(self): ) raw_output = self.openstack( - 'network agent set ' + - '--enable ' + - agent_ids[0] + 'network agent set --enable %s' % agent_ids[0] ) self.assertOutput('', raw_output) cmd_output = json.loads(self.openstack( - 'network agent show -f json ' + - agent_ids[0] + 'network agent show -f json %s' % agent_ids[0] )) self.assertEqual( "UP", @@ -98,12 +91,10 @@ def test_network_dhcp_agent_list(self): name1 = uuid.uuid4().hex cmd_output1 = json.loads(self.openstack( - 'network create -f json ' + - '--description aaaa ' + - name1 + 'network create -f json --description aaaa %s' % name1 )) - self.addCleanup(self.openstack, 'network delete ' + name1) + self.addCleanup(self.openstack, 'network delete %s' % name1) # Get network ID network_id = cmd_output1['id'] @@ -116,20 +107,20 @@ def test_network_dhcp_agent_list(self): # Add Agent to Network self.openstack( - 'network agent add network --dhcp ' - + agent_id + ' ' + network_id + 'network agent add network --dhcp %s %s' % + (agent_id, network_id) ) # Test network agent list --network cmd_output3 = json.loads(self.openstack( - 'network agent list -f json --network ' + network_id + 'network agent list -f json --network %s' % network_id )) # Cleanup # Remove Agent from Network self.openstack( - 'network agent remove network --dhcp ' - + agent_id + ' ' + network_id + 'network agent remove network --dhcp %s %s' % + (agent_id, network_id) ) # Assert @@ -142,9 +133,9 @@ def test_network_agent_list_routers(self): """Add agent to router, list agents on router, delete.""" name = uuid.uuid4().hex cmd_output = json.loads(self.openstack( - 'router create -f json ' + name)) + 'router create -f json %s' % name)) - self.addCleanup(self.openstack, 'router delete ' + name) + self.addCleanup(self.openstack, 'router delete %s' % name) # Get router ID router_id = cmd_output['id'] # Get l3 agent id @@ -157,19 +148,19 @@ def test_network_agent_list_routers(self): # Add router to agent self.openstack( - 'network agent add router --l3 ' + agent_id + ' ' + router_id) + 'network agent add router --l3 %s %s' % (agent_id, router_id)) # Test router list --agent cmd_output = json.loads(self.openstack( - 'network agent list -f json --router ' + router_id)) + 'network agent list -f json --router %s' % router_id)) agent_ids = [x['ID'] for x in cmd_output] self.assertIn(agent_id, agent_ids) # Remove router from agent self.openstack( - 'network agent remove router --l3 ' + agent_id + ' ' + router_id) + 'network agent remove router --l3 %s %s' % (agent_id, router_id)) cmd_output = json.loads(self.openstack( - 'network agent list -f json --router ' + router_id)) + 'network agent list -f json --router %s' % router_id)) agent_ids = [x['ID'] for x in cmd_output] self.assertNotIn(agent_id, agent_ids) diff --git a/openstackclient/tests/functional/network/v2/test_network_flavor.py b/openstackclient/tests/functional/network/v2/test_network_flavor.py index 47e7b44057..ba3de2cdb5 100644 --- a/openstackclient/tests/functional/network/v2/test_network_flavor.py +++ b/openstackclient/tests/functional/network/v2/test_network_flavor.py @@ -39,13 +39,13 @@ def test_network_flavor_add_remove_profile(self): # Create Service Flavor cmd_output2 = json.loads(self.openstack( 'network flavor profile create -f json --description ' - + 'fakedescription' + ' --enable --metainfo ' + 'Extrainfo' + 'fakedescription --enable --metainfo Extrainfo' )) service_profile_id = cmd_output2.get('id') - self.addCleanup(self.openstack, 'network flavor delete ' + + self.addCleanup(self.openstack, 'network flavor delete %s' % flavor_id) - self.addCleanup(self.openstack, 'network flavor profile delete ' + + self.addCleanup(self.openstack, 'network flavor profile delete %s' % service_profile_id) # Add flavor to service profile self.openstack( diff --git a/openstackclient/tests/functional/network/v2/test_network_qos_rule.py b/openstackclient/tests/functional/network/v2/test_network_qos_rule.py index 770abe94fc..5d235405c5 100644 --- a/openstackclient/tests/functional/network/v2/test_network_qos_rule.py +++ b/openstackclient/tests/functional/network/v2/test_network_qos_rule.py @@ -28,62 +28,60 @@ def setUp(self): if not self.haz_network: self.skipTest("No Network service present") - self.QOS_POLICY_NAME = 'qos_policy_' + uuid.uuid4().hex + self.QOS_POLICY_NAME = 'qos_policy_%s' % uuid.uuid4().hex self.openstack( - 'network qos policy create ' + - self.QOS_POLICY_NAME + 'network qos policy create %s' % self.QOS_POLICY_NAME ) self.addCleanup(self.openstack, - 'network qos policy delete ' + self.QOS_POLICY_NAME) + 'network qos policy delete %s' % self.QOS_POLICY_NAME) cmd_output = json.loads(self.openstack( - 'network qos rule create -f json ' + - '--type minimum-bandwidth ' + - '--min-kbps 2800 ' + - '--egress ' + + 'network qos rule create -f json ' + '--type minimum-bandwidth ' + '--min-kbps 2800 ' + '--egress %s' % self.QOS_POLICY_NAME )) self.RULE_ID = cmd_output['id'] self.addCleanup(self.openstack, - 'network qos rule delete ' + - self.QOS_POLICY_NAME + ' ' + - self.RULE_ID) + 'network qos rule delete %s %s' % + (self.QOS_POLICY_NAME, self.RULE_ID)) self.assertTrue(self.RULE_ID) def test_qos_rule_create_delete(self): # This is to check the output of qos rule delete policy_name = uuid.uuid4().hex - self.openstack('network qos policy create -f json ' + policy_name) + self.openstack('network qos policy create -f json %s' % policy_name) self.addCleanup(self.openstack, - 'network qos policy delete ' + policy_name) + 'network qos policy delete %s' % policy_name) rule = json.loads(self.openstack( - 'network qos rule create -f json ' + - '--type minimum-bandwidth ' + - '--min-kbps 2800 ' + - '--egress ' + policy_name + 'network qos rule create -f json ' + '--type minimum-bandwidth ' + '--min-kbps 2800 ' + '--egress %s' % policy_name )) raw_output = self.openstack( - 'network qos rule delete ' + - policy_name + ' ' + rule['id']) + 'network qos rule delete %s %s' % + (policy_name, rule['id'])) self.assertEqual('', raw_output) def test_qos_rule_list(self): cmd_output = json.loads(self.openstack( - 'network qos rule list -f json ' + self.QOS_POLICY_NAME)) + 'network qos rule list -f json %s' % self.QOS_POLICY_NAME)) self.assertIn(self.RULE_ID, [rule['ID'] for rule in cmd_output]) def test_qos_rule_show(self): cmd_output = json.loads(self.openstack( - 'network qos rule show -f json ' + - self.QOS_POLICY_NAME + ' ' + self.RULE_ID)) + 'network qos rule show -f json %s %s' % + (self.QOS_POLICY_NAME, self.RULE_ID))) self.assertEqual(self.RULE_ID, cmd_output['id']) def test_qos_rule_set(self): - self.openstack('network qos rule set --min-kbps 7500 ' + - self.QOS_POLICY_NAME + ' ' + self.RULE_ID) + self.openstack('network qos rule set --min-kbps 7500 %s %s' % + (self.QOS_POLICY_NAME, self.RULE_ID)) cmd_output = json.loads(self.openstack( - 'network qos rule show -f json ' + - self.QOS_POLICY_NAME + ' ' + self.RULE_ID)) + 'network qos rule show -f json %s %s' % + (self.QOS_POLICY_NAME, self.RULE_ID))) self.assertEqual(7500, cmd_output['min_kbps']) @@ -96,58 +94,57 @@ def setUp(self): if not self.haz_network: self.skipTest("No Network service present") - self.QOS_POLICY_NAME = 'qos_policy_' + uuid.uuid4().hex + self.QOS_POLICY_NAME = 'qos_policy_%s' % uuid.uuid4().hex self.openstack( - 'network qos policy create ' + - self.QOS_POLICY_NAME + 'network qos policy create %s' % self.QOS_POLICY_NAME ) self.addCleanup(self.openstack, - 'network qos policy delete ' + self.QOS_POLICY_NAME) + 'network qos policy delete %s' % self.QOS_POLICY_NAME) cmd_output = json.loads(self.openstack( - 'network qos rule create -f json ' + - '--type dscp-marking ' + - '--dscp-mark 8 ' + + 'network qos rule create -f json ' + '--type dscp-marking ' + '--dscp-mark 8 %s' % self.QOS_POLICY_NAME )) self.RULE_ID = cmd_output['id'] self.addCleanup(self.openstack, - 'network qos rule delete ' + - self.QOS_POLICY_NAME + ' ' + self.RULE_ID) + 'network qos rule delete %s %s' % + (self.QOS_POLICY_NAME, self.RULE_ID)) self.assertTrue(self.RULE_ID) def test_qos_rule_create_delete(self): # This is to check the output of qos rule delete policy_name = uuid.uuid4().hex - self.openstack('network qos policy create -f json ' + policy_name) + self.openstack('network qos policy create -f json %s' % policy_name) self.addCleanup(self.openstack, - 'network qos policy delete ' + policy_name) + 'network qos policy delete %s' % policy_name) rule = json.loads(self.openstack( - 'network qos rule create -f json ' + - '--type dscp-marking ' + - '--dscp-mark 8 ' + policy_name + 'network qos rule create -f json ' + '--type dscp-marking ' + '--dscp-mark 8 %s' % policy_name )) raw_output = self.openstack( - 'network qos rule delete ' + - policy_name + ' ' + rule['id']) + 'network qos rule delete %s %s' % + (policy_name, rule['id'])) self.assertEqual('', raw_output) def test_qos_rule_list(self): cmd_output = json.loads(self.openstack( - 'network qos rule list -f json ' + self.QOS_POLICY_NAME)) + 'network qos rule list -f json %s' % self.QOS_POLICY_NAME)) self.assertIn(self.RULE_ID, [rule['ID'] for rule in cmd_output]) def test_qos_rule_show(self): cmd_output = json.loads(self.openstack( - 'network qos rule show -f json ' + - self.QOS_POLICY_NAME + ' ' + self.RULE_ID)) + 'network qos rule show -f json %s %s' % + (self.QOS_POLICY_NAME, self.RULE_ID))) self.assertEqual(self.RULE_ID, cmd_output['id']) def test_qos_rule_set(self): - self.openstack('network qos rule set --dscp-mark 32 ' + - self.QOS_POLICY_NAME + ' ' + self.RULE_ID) + self.openstack('network qos rule set --dscp-mark 32 %s %s' % + (self.QOS_POLICY_NAME, self.RULE_ID)) cmd_output = json.loads(self.openstack( - 'network qos rule show -f json ' + - self.QOS_POLICY_NAME + ' ' + self.RULE_ID)) + 'network qos rule show -f json %s %s' % + (self.QOS_POLICY_NAME, self.RULE_ID))) self.assertEqual(32, cmd_output['dscp_mark']) @@ -160,65 +157,64 @@ def setUp(self): if not self.haz_network: self.skipTest("No Network service present") - self.QOS_POLICY_NAME = 'qos_policy_' + uuid.uuid4().hex + self.QOS_POLICY_NAME = 'qos_policy_%s' % uuid.uuid4().hex self.openstack( - 'network qos policy create ' + - self.QOS_POLICY_NAME + 'network qos policy create %s' % self.QOS_POLICY_NAME ) self.addCleanup(self.openstack, - 'network qos policy delete ' + self.QOS_POLICY_NAME) + 'network qos policy delete %s' % self.QOS_POLICY_NAME) cmd_output = json.loads(self.openstack( - 'network qos rule create -f json ' + - '--type bandwidth-limit ' + - '--max-kbps 10000 ' + - '--max-burst-kbits 1400 ' + - '--egress ' + + 'network qos rule create -f json ' + '--type bandwidth-limit ' + '--max-kbps 10000 ' + '--max-burst-kbits 1400 ' + '--egress %s' % self.QOS_POLICY_NAME )) self.RULE_ID = cmd_output['id'] self.addCleanup(self.openstack, - 'network qos rule delete ' + - self.QOS_POLICY_NAME + ' ' + self.RULE_ID) + 'network qos rule delete %s %s' % + (self.QOS_POLICY_NAME, self.RULE_ID)) self.assertTrue(self.RULE_ID) def test_qos_rule_create_delete(self): # This is to check the output of qos rule delete policy_name = uuid.uuid4().hex - self.openstack('network qos policy create -f json ' + policy_name) + self.openstack('network qos policy create -f json %s' % policy_name) self.addCleanup(self.openstack, - 'network qos policy delete ' + policy_name) + 'network qos policy delete %s' % policy_name) rule = json.loads(self.openstack( - 'network qos rule create -f json ' + - '--type bandwidth-limit ' + - '--max-kbps 10000 ' + - '--max-burst-kbits 1400 ' + - '--egress ' + policy_name + 'network qos rule create -f json ' + '--type bandwidth-limit ' + '--max-kbps 10000 ' + '--max-burst-kbits 1400 ' + '--egress %s' % policy_name )) raw_output = self.openstack( - 'network qos rule delete ' + - policy_name + ' ' + rule['id']) + 'network qos rule delete %s %s' % + (policy_name, rule['id'])) self.assertEqual('', raw_output) def test_qos_rule_list(self): cmd_output = json.loads(self.openstack( - 'network qos rule list -f json ' - + self.QOS_POLICY_NAME)) + 'network qos rule list -f json %s' % + self.QOS_POLICY_NAME)) self.assertIn(self.RULE_ID, [rule['ID'] for rule in cmd_output]) def test_qos_rule_show(self): cmd_output = json.loads(self.openstack( - 'network qos rule show -f json ' + - self.QOS_POLICY_NAME + ' ' + self.RULE_ID)) + 'network qos rule show -f json %s %s' % + (self.QOS_POLICY_NAME, self.RULE_ID))) self.assertEqual(self.RULE_ID, cmd_output['id']) def test_qos_rule_set(self): - self.openstack('network qos rule set --max-kbps 15000 ' + - '--max-burst-kbits 1800 ' + - '--ingress ' + - self.QOS_POLICY_NAME + ' ' + self.RULE_ID) + self.openstack('network qos rule set --max-kbps 15000 ' + '--max-burst-kbits 1800 ' + '--ingress %s %s' % + (self.QOS_POLICY_NAME, self.RULE_ID)) cmd_output = json.loads(self.openstack( - 'network qos rule show -f json ' + - self.QOS_POLICY_NAME + ' ' + self.RULE_ID)) + 'network qos rule show -f json %s %s' % + (self.QOS_POLICY_NAME, self.RULE_ID))) self.assertEqual(15000, cmd_output['max_kbps']) self.assertEqual(1800, cmd_output['max_burst_kbps']) self.assertEqual('ingress', cmd_output['direction']) diff --git a/openstackclient/tests/functional/network/v2/test_port.py b/openstackclient/tests/functional/network/v2/test_port.py index 7357c0ed54..e3067d90a3 100644 --- a/openstackclient/tests/functional/network/v2/test_port.py +++ b/openstackclient/tests/functional/network/v2/test_port.py @@ -33,7 +33,7 @@ def setUpClass(cls): # Create a network for the port tests cls.openstack( - 'network create ' + cls.NETWORK_NAME + 'network create %s' % cls.NETWORK_NAME ) @classmethod @@ -41,7 +41,7 @@ def tearDownClass(cls): try: if cls.haz_network: raw_output = cls.openstack( - 'network delete ' + cls.NETWORK_NAME + 'network delete %s' % cls.NETWORK_NAME ) cls.assertOutput('', raw_output) finally: @@ -56,8 +56,8 @@ def setUp(self): def test_port_delete(self): """Test create, delete multiple""" json_output = json.loads(self.openstack( - 'port create -f json --network ' + - self.NETWORK_NAME + ' ' + self.NAME + 'port create -f json --network %s %s' % + (self.NETWORK_NAME, self.NAME) )) id1 = json_output.get('id') self.assertIsNotNone(id1) @@ -65,8 +65,8 @@ def test_port_delete(self): self.assertEqual(self.NAME, json_output.get('name')) json_output = json.loads(self.openstack( - 'port create -f json --network ' + self.NETWORK_NAME + ' ' + - self.NAME + 'x' + 'port create -f json --network %s %sx' % + (self.NETWORK_NAME, self.NAME) )) id2 = json_output.get('id') self.assertIsNotNone(id2) @@ -74,31 +74,31 @@ def test_port_delete(self): self.assertEqual(self.NAME + 'x', json_output.get('name')) # Clean up after ourselves - raw_output = self.openstack('port delete ' + id1 + ' ' + id2) + raw_output = self.openstack('port delete %s %s' % (id1, id2)) self.assertOutput('', raw_output) def test_port_list(self): """Test create defaults, list, delete""" json_output = json.loads(self.openstack( - 'port create -f json --network ' + self.NETWORK_NAME + ' ' + - self.NAME + 'port create -f json --network %s %s' % + (self.NETWORK_NAME, self.NAME) )) id1 = json_output.get('id') self.assertIsNotNone(id1) mac1 = json_output.get('mac_address') self.assertIsNotNone(mac1) - self.addCleanup(self.openstack, 'port delete ' + id1) + self.addCleanup(self.openstack, 'port delete %s' % id1) self.assertEqual(self.NAME, json_output.get('name')) json_output = json.loads(self.openstack( - 'port create -f json --network ' + self.NETWORK_NAME + ' ' + - self.NAME + 'x' + 'port create -f json --network %s %sx' % + (self.NETWORK_NAME, self.NAME) )) id2 = json_output.get('id') self.assertIsNotNone(id2) mac2 = json_output.get('mac_address') self.assertIsNotNone(mac2) - self.addCleanup(self.openstack, 'port delete ' + id2) + self.addCleanup(self.openstack, 'port delete %s' % id2) self.assertEqual(self.NAME + 'x', json_output.get('name')) # Test list @@ -122,7 +122,7 @@ def test_port_list(self): # Test list --mac-address json_output = json.loads(self.openstack( - 'port list -f json --mac-address ' + mac2 + 'port list -f json --mac-address %s' % mac2 )) item_map = {item.get('ID'): item.get('MAC Address') for item in json_output} @@ -145,26 +145,26 @@ def test_port_set(self): """Test create, set, show, delete""" name = uuid.uuid4().hex json_output = json.loads(self.openstack( - 'port create -f json ' + - '--network ' + self.NETWORK_NAME + ' ' + - '--description xyzpdq ' + - '--disable ' + - name + 'port create -f json ' + '--network %s ' + '--description xyzpdq ' + '--disable %s' % + (self.NETWORK_NAME, name) )) id1 = json_output.get('id') - self.addCleanup(self.openstack, 'port delete ' + id1) + self.addCleanup(self.openstack, 'port delete %s' % id1) self.assertEqual(name, json_output.get('name')) self.assertEqual('xyzpdq', json_output.get('description')) self.assertEqual('DOWN', json_output.get('admin_state_up')) raw_output = self.openstack( - 'port set ' + '--enable ' + + 'port set --enable %s' % name ) self.assertOutput('', raw_output) json_output = json.loads(self.openstack( - 'port show -f json ' + name + 'port show -f json %s' % name )) sg_id = json_output.get('security_group_ids') @@ -174,30 +174,30 @@ def test_port_set(self): self.assertIsNotNone(json_output.get('mac_address')) raw_output = self.openstack( - 'port unset --security-group ' + sg_id + ' ' + id1) + 'port unset --security-group %s %s' % (sg_id, id1)) self.assertOutput('', raw_output) json_output = json.loads(self.openstack( - 'port show -f json ' + name + 'port show -f json %s' % name )) self.assertEqual('', json_output.get('security_group_ids')) def test_port_admin_set(self): """Test create, set (as admin), show, delete""" json_output = json.loads(self.openstack( - 'port create -f json ' + - '--network ' + self.NETWORK_NAME + ' ' + self.NAME + 'port create -f json ' + '--network %s %s' % (self.NETWORK_NAME, self.NAME) )) id_ = json_output.get('id') - self.addCleanup(self.openstack, 'port delete ' + id_) + self.addCleanup(self.openstack, 'port delete %s' % id_) raw_output = self.openstack( '--os-username admin ' - + 'port set --mac-address 11:22:33:44:55:66 ' - + self.NAME) + 'port set --mac-address 11:22:33:44:55:66 %s' % + self.NAME) self.assertOutput('', raw_output) json_output = json.loads(self.openstack( - 'port show -f json ' + self.NAME + 'port show -f json %s' % self.NAME )) self.assertEqual(json_output.get('mac_address'), '11:22:33:44:55:66') @@ -205,41 +205,41 @@ def test_port_set_sg(self): """Test create, set, show, delete""" sg_name1 = uuid.uuid4().hex json_output = json.loads(self.openstack( - 'security group create -f json ' + + 'security group create -f json %s' % sg_name1 )) sg_id1 = json_output.get('id') - self.addCleanup(self.openstack, 'security group delete ' + sg_id1) + self.addCleanup(self.openstack, 'security group delete %s' % sg_id1) sg_name2 = uuid.uuid4().hex json_output = json.loads(self.openstack( - 'security group create -f json ' + + 'security group create -f json %s' % sg_name2 )) sg_id2 = json_output.get('id') - self.addCleanup(self.openstack, 'security group delete ' + sg_id2) + self.addCleanup(self.openstack, 'security group delete %s' % sg_id2) name = uuid.uuid4().hex json_output = json.loads(self.openstack( - 'port create -f json ' + - '--network ' + self.NETWORK_NAME + ' ' + - '--security-group ' + sg_name1 + ' ' + - name + 'port create -f json ' + '--network %s ' + '--security-group %s %s' % + (self.NETWORK_NAME, sg_name1, name) )) id1 = json_output.get('id') - self.addCleanup(self.openstack, 'port delete ' + id1) + self.addCleanup(self.openstack, 'port delete %s' % id1) self.assertEqual(name, json_output.get('name')) self.assertEqual(sg_id1, json_output.get('security_group_ids')) raw_output = self.openstack( - 'port set ' + - '--security-group ' + sg_name2 + ' ' + - name + 'port set ' + '--security-group %s %s' % + (sg_name2, name) ) self.assertOutput('', raw_output) json_output = json.loads(self.openstack( - 'port show -f json ' + name + 'port show -f json %s' % name )) self.assertEqual(name, json_output.get('name')) self.assertIn( @@ -254,11 +254,11 @@ def test_port_set_sg(self): ) raw_output = self.openstack( - 'port unset --security-group ' + sg_id1 + ' ' + id1) + 'port unset --security-group %s %s' % (sg_id1, id1)) self.assertOutput('', raw_output) json_output = json.loads(self.openstack( - 'port show -f json ' + name + 'port show -f json %s' % name )) self.assertEqual( # TODO(dtroyer): output formatters should do this on JSON! diff --git a/openstackclient/tests/functional/object/v1/test_object.py b/openstackclient/tests/functional/object/v1/test_object.py index d1a73c54db..226ef8adbd 100644 --- a/openstackclient/tests/functional/object/v1/test_object.py +++ b/openstackclient/tests/functional/object/v1/test_object.py @@ -53,39 +53,39 @@ def _test_object(self, object_file): self.openstack('container save ' + self.CONTAINER_NAME) # TODO(stevemar): Assert returned fields - raw_output = self.openstack('object create ' + self.CONTAINER_NAME - + ' ' + object_file) + raw_output = self.openstack('object create %s %s' % + (self.CONTAINER_NAME, object_file)) items = self.parse_listing(raw_output) self.assert_show_fields(items, OBJECT_FIELDS) - raw_output = self.openstack('object list ' + self.CONTAINER_NAME) + raw_output = self.openstack('object list %s' % self.CONTAINER_NAME) items = self.parse_listing(raw_output) self.assert_table_structure(items, BASIC_LIST_HEADERS) - self.openstack('object save ' + self.CONTAINER_NAME - + ' ' + object_file) + self.openstack('object save %s %s' % + (self.CONTAINER_NAME, object_file)) # TODO(stevemar): Assert returned fields tmp_file = 'tmp.txt' self.addCleanup(os.remove, tmp_file) - self.openstack('object save ' + self.CONTAINER_NAME - + ' ' + object_file + ' --file ' + tmp_file) + self.openstack('object save %s %s --file %s' % + (self.CONTAINER_NAME, object_file, tmp_file)) # TODO(stevemar): Assert returned fields - raw_output = self.openstack('object save ' + self.CONTAINER_NAME - + ' ' + object_file + ' --file -') + raw_output = self.openstack('object save %s %s --file -' % + (self.CONTAINER_NAME, object_file)) self.assertEqual(raw_output, 'test content') - self.openstack('object show ' + self.CONTAINER_NAME - + ' ' + object_file) + self.openstack('object show %s %s' % + (self.CONTAINER_NAME, object_file)) # TODO(stevemar): Assert returned fields - raw_output = self.openstack('object delete ' + self.CONTAINER_NAME - + ' ' + object_file) + raw_output = self.openstack('object delete %s %s' % + (self.CONTAINER_NAME, object_file)) self.assertEqual(0, len(raw_output)) - self.openstack('object create ' + self.CONTAINER_NAME - + ' ' + object_file) - raw_output = self.openstack('container delete -r ' + + self.openstack('object create %s %s' % + (self.CONTAINER_NAME, object_file)) + raw_output = self.openstack('container delete -r %s' % self.CONTAINER_NAME) self.assertEqual(0, len(raw_output)) diff --git a/openstackclient/tests/functional/volume/v1/test_volume_type.py b/openstackclient/tests/functional/volume/v1/test_volume_type.py index 74e1407003..c5886a696a 100644 --- a/openstackclient/tests/functional/volume/v1/test_volume_type.py +++ b/openstackclient/tests/functional/volume/v1/test_volume_type.py @@ -26,13 +26,13 @@ class VolumeTypeTests(common.BaseVolumeTests): def setUpClass(cls): super(VolumeTypeTests, cls).setUpClass() cmd_output = json.loads(cls.openstack( - 'volume type create -f json ' + cls.NAME)) + 'volume type create -f json %s' % cls.NAME)) cls.assertOutput(cls.NAME, cmd_output['name']) @classmethod def tearDownClass(cls): try: - raw_output = cls.openstack('volume type delete ' + cls.NAME) + raw_output = cls.openstack('volume type delete %s' % cls.NAME) cls.assertOutput('', raw_output) finally: super(VolumeTypeTests, cls).tearDownClass() @@ -43,47 +43,47 @@ def test_volume_type_list(self): def test_volume_type_show(self): cmd_output = json.loads(self.openstack( - 'volume type show -f json ' + self.NAME)) + 'volume type show -f json %s' % self.NAME)) self.assertEqual(self.NAME, cmd_output['name']) def test_volume_type_set_unset_properties(self): raw_output = self.openstack( - 'volume type set --property a=b --property c=d ' + self.NAME) + 'volume type set --property a=b --property c=d %s' % self.NAME) self.assertEqual("", raw_output) cmd_output = json.loads(self.openstack( 'volume type show -f json ' + self.NAME)) self.assertEqual("a='b', c='d'", cmd_output['properties']) - raw_output = self.openstack('volume type unset --property a ' - + self.NAME) + raw_output = self.openstack('volume type unset --property a %s' % + self.NAME) self.assertEqual("", raw_output) cmd_output = json.loads(self.openstack( - 'volume type show -f json ' + self.NAME)) + 'volume type show -f json %s' % self.NAME)) self.assertEqual("c='d'", cmd_output['properties']) def test_volume_type_set_unset_multiple_properties(self): raw_output = self.openstack( - 'volume type set --property a=b --property c=d ' + self.NAME) + 'volume type set --property a=b --property c=d %s' % self.NAME) self.assertEqual("", raw_output) cmd_output = json.loads(self.openstack( - 'volume type show -f json ' + self.NAME)) + 'volume type show -f json %s' % self.NAME)) self.assertEqual("a='b', c='d'", cmd_output['properties']) raw_output = self.openstack( - 'volume type unset --property a --property c ' + self.NAME) + 'volume type unset --property a --property c %s' % self.NAME) self.assertEqual("", raw_output) cmd_output = json.loads(self.openstack( - 'volume type show -f json ' + self.NAME)) + 'volume type show -f json %s' % self.NAME)) self.assertEqual("", cmd_output['properties']) def test_multi_delete(self): vol_type1 = uuid.uuid4().hex vol_type2 = uuid.uuid4().hex - self.openstack('volume type create ' + vol_type1) + self.openstack('volume type create %s' % vol_type1) time.sleep(5) - self.openstack('volume type create ' + vol_type2) + self.openstack('volume type create %s' % vol_type2) time.sleep(5) cmd = 'volume type delete %s %s' % (vol_type1, vol_type2) raw_output = self.openstack(cmd) diff --git a/openstackclient/tests/functional/volume/v2/test_volume_type.py b/openstackclient/tests/functional/volume/v2/test_volume_type.py index 99630e6b04..5c551ca945 100644 --- a/openstackclient/tests/functional/volume/v2/test_volume_type.py +++ b/openstackclient/tests/functional/volume/v2/test_volume_type.py @@ -26,13 +26,13 @@ class VolumeTypeTests(common.BaseVolumeTests): def setUpClass(cls): super(VolumeTypeTests, cls).setUpClass() cmd_output = json.loads(cls.openstack( - 'volume type create -f json --private ' + cls.NAME)) + 'volume type create -f json --private %s' % cls.NAME)) cls.assertOutput(cls.NAME, cmd_output['name']) @classmethod def tearDownClass(cls): try: - raw_output = cls.openstack('volume type delete ' + cls.NAME) + raw_output = cls.openstack('volume type delete %s' % cls.NAME) cls.assertOutput('', raw_output) finally: super(VolumeTypeTests, cls).tearDownClass() @@ -49,55 +49,55 @@ def test_volume_type_list_default(self): def test_volume_type_show(self): cmd_output = json.loads(self.openstack( - 'volume type show -f json ' + self.NAME)) + 'volume type show -f json %s' % self.NAME)) self.assertEqual(self.NAME, cmd_output['name']) def test_volume_type_set_unset_properties(self): raw_output = self.openstack( - 'volume type set --property a=b --property c=d ' + self.NAME) + 'volume type set --property a=b --property c=d %s' % self.NAME) self.assertEqual("", raw_output) cmd_output = json.loads(self.openstack( - 'volume type show -f json ' + self.NAME)) + 'volume type show -f json %s' % self.NAME)) # TODO(amotoki): properties output should be machine-readable self.assertEqual("a='b', c='d'", cmd_output['properties']) - raw_output = self.openstack('volume type unset --property a ' - + self.NAME) + raw_output = self.openstack('volume type unset --property a %s' % + self.NAME) self.assertEqual("", raw_output) cmd_output = json.loads(self.openstack( - 'volume type show -f json ' + self.NAME)) + 'volume type show -f json %s' % self.NAME)) self.assertEqual("c='d'", cmd_output['properties']) def test_volume_type_set_unset_multiple_properties(self): raw_output = self.openstack( - 'volume type set --property a=b --property c=d ' + self.NAME) + 'volume type set --property a=b --property c=d %s' % self.NAME) self.assertEqual("", raw_output) cmd_output = json.loads(self.openstack( - 'volume type show -f json ' + self.NAME)) + 'volume type show -f json %s' % self.NAME)) self.assertEqual("a='b', c='d'", cmd_output['properties']) raw_output = self.openstack( - 'volume type unset --property a --property c ' + self.NAME) + 'volume type unset --property a --property c %s' % self.NAME) self.assertEqual("", raw_output) cmd_output = json.loads(self.openstack( - 'volume type show -f json ' + self.NAME)) + 'volume type show -f json %s' % self.NAME)) self.assertEqual("", cmd_output['properties']) def test_volume_type_set_unset_project(self): raw_output = self.openstack( - 'volume type set --project admin ' + self.NAME) + 'volume type set --project admin %s' % self.NAME) self.assertEqual("", raw_output) raw_output = self.openstack( - 'volume type unset --project admin ' + self.NAME) + 'volume type unset --project admin %s' % self.NAME) self.assertEqual("", raw_output) def test_multi_delete(self): vol_type1 = uuid.uuid4().hex vol_type2 = uuid.uuid4().hex - self.openstack('volume type create ' + vol_type1) + self.openstack('volume type create %s' % vol_type1) time.sleep(5) - self.openstack('volume type create ' + vol_type2) + self.openstack('volume type create %s' % vol_type2) time.sleep(5) cmd = 'volume type delete %s %s' % (vol_type1, vol_type2) raw_output = self.openstack(cmd) From 09a0916daeeb9c257d84175a43062d5b4a1d0b1a Mon Sep 17 00:00:00 2001 From: Dongcan Ye Date: Fri, 23 Feb 2018 08:03:12 +0000 Subject: [PATCH 1907/3095] Network: Add tag support for floating ip Change-Id: I7a500a4ff6cec2442b4050df26c0b017d9f71903 Closes-Bug: #1750985 --- .../cli/command-objects/floating-ip.rst | 59 ++++++++ openstackclient/network/v2/floating_ip.py | 23 ++- .../tests/unit/network/v2/fakes.py | 1 + .../network/v2/test_floating_ip_network.py | 136 +++++++++++++++++- .../notes/bug-1750985-a5345f715a14825c.yaml | 5 + 5 files changed, 220 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/bug-1750985-a5345f715a14825c.yaml diff --git a/doc/source/cli/command-objects/floating-ip.rst b/doc/source/cli/command-objects/floating-ip.rst index d9ed2307c3..7efdbd8581 100644 --- a/doc/source/cli/command-objects/floating-ip.rst +++ b/doc/source/cli/command-objects/floating-ip.rst @@ -20,6 +20,7 @@ Create floating IP [--description ] [--qos-policy ] [--project [--project-domain ]] + [--tag | --no-tag] .. option:: --subnet @@ -66,6 +67,18 @@ Create floating IP *Network version 2 only* +.. option:: --tag + + Tag to be added to the floating IP (repeat option to set multiple tags) + + *Network version 2 only* + +.. option:: --no-tag + + No tags associated with the floating IP + + *Network version 2 only* + .. describe:: Network to allocate floating IP from (name or ID) @@ -100,6 +113,8 @@ List floating IP(s) [--status ] [--project [--project-domain ]] [--router ] + [--tags [,,...]] [--any-tags [,,...]] + [--not-tags [,,...]] [--not-any-tags [,,...]] .. option:: --network @@ -150,6 +165,30 @@ List floating IP(s) *Network version 2 only* +.. option:: --tags [,,...] + + List floating IP(s) which have all given tag(s) + + *Network version 2 only* + +.. option:: --any-tags [,,...] + + List floating IP(s) which have any given tag(s) + + *Network version 2 only* + +.. option:: --not-tags [,,...] + + Exclude floating IP(s) which have all given tag(s) + + *Network version 2 only* + +.. option:: --not-any-tags [,,...] + + Exclude floating IP(s) which have any given tag(s) + + *Network version 2 only* + floating ip set --------------- @@ -162,6 +201,7 @@ Set floating IP properties --port [--fixed-ip-address ] [--qos-policy | --no-qos-policy] + [--tag ] [--no-tag] .. option:: --port @@ -180,6 +220,15 @@ Set floating IP properties Remove the QoS policy attached to the floating IP +.. option:: --tag + + Tag to be added to the floating IP (repeat option to set multiple tags) + +.. option:: --no-tag + + Clear tags associated with the floating IP. Specify both --tag + and --no-tag to overwrite current tags + .. _floating_ip_set-floating-ip: .. describe:: @@ -210,6 +259,7 @@ Unset floating IP Properties openstack floating ip unset --port --qos-policy + [--tag | --all-tag] .. option:: --port @@ -220,6 +270,15 @@ Unset floating IP Properties Remove the QoS policy attached to the floating IP +.. option:: --tag + + Tag to be removed from the floating IP + (repeat option to remove multiple tags) + +.. option:: --all-tag + + Clear all tags associated with the floating IP + .. _floating_ip_unset-floating-ip: .. describe:: diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index a6f0340466..2c7317b2f1 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -22,6 +22,7 @@ from openstackclient.identity import common as identity_common from openstackclient.network import common from openstackclient.network import sdk_utils +from openstackclient.network.v2 import _tag def _get_network_columns(item): @@ -139,11 +140,14 @@ def update_parser_network(self, parser): help=_("Owner's project (name or ID)") ) identity_common.add_project_domain_option_to_parser(parser) + _tag.add_tag_option_to_parser_for_create(parser, _('floating IP')) return parser def take_action_network(self, client, parsed_args): attrs = _get_attrs(self.app.client_manager, parsed_args) obj = client.create_ip(**attrs) + # tags cannot be set when created, so tags need to be set later. + _tag.update_tags_for_set(client, obj, parsed_args) display_columns, columns = _get_network_columns(obj) data = utils.get_item_properties(obj, columns) return (display_columns, data) @@ -280,6 +284,7 @@ def update_parser_network(self, parser): help=_("List floating IP(s) according to " "given router (name or ID)") ) + _tag.add_tag_filtering_option_to_parser(parser, _('floating IP')) return parser @@ -308,11 +313,13 @@ def take_action_network(self, client, parsed_args): 'router_id', 'status', 'description', + 'tags', ) headers = headers + ( 'Router', 'Status', 'Description', + 'Tags', ) query = {} @@ -342,6 +349,8 @@ def take_action_network(self, client, parsed_args): ignore_missing=False) query['router_id'] = router.id + _tag.get_tag_filtering_args(parsed_args, query) + data = client.ips(**query) return (headers, @@ -431,6 +440,9 @@ def get_parser(self, prog_name): action='store_true', help=_("Remove the QoS policy attached to the floating IP") ) + + _tag.add_tag_option_to_parser_for_set(parser, _('floating IP')) + return parser def take_action(self, parsed_args): @@ -453,7 +465,11 @@ def take_action(self, parsed_args): if 'no_qos_policy' in parsed_args and parsed_args.no_qos_policy: attrs['qos_policy_id'] = None - client.update_ip(obj, **attrs) + if attrs: + client.update_ip(obj, **attrs) + + # tags is a subresource and it needs to be updated separately. + _tag.update_tags_for_set(client, obj, parsed_args) class ShowFloatingIP(common.NetworkAndComputeShowOne): @@ -528,6 +544,8 @@ def get_parser(self, prog_name): default=False, help=_("Remove the QoS policy attached to the floating IP") ) + _tag.add_tag_option_to_parser_for_unset(parser, _('floating IP')) + return parser def take_action(self, parsed_args): @@ -544,3 +562,6 @@ def take_action(self, parsed_args): if attrs: client.update_ip(obj, **attrs) + + # tags is a subresource and it needs to be updated separately. + _tag.update_tags_for_unset(client, obj, parsed_args) diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index a77cab8b5f..82bd7cd99c 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -1379,6 +1379,7 @@ def create_one_floating_ip(attrs=None): 'tenant_id': 'project-id-' + uuid.uuid4().hex, 'description': 'floating-ip-description-' + uuid.uuid4().hex, 'qos_policy_id': 'qos-policy-id-' + uuid.uuid4().hex, + 'tags': [], } # Overwrite default attributes. diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip_network.py b/openstackclient/tests/unit/network/v2/test_floating_ip_network.py index f19849c40e..65d873770a 100644 --- a/openstackclient/tests/unit/network/v2/test_floating_ip_network.py +++ b/openstackclient/tests/unit/network/v2/test_floating_ip_network.py @@ -44,7 +44,7 @@ class TestCreateFloatingIPNetwork(TestFloatingIPNetwork): subnet = network_fakes.FakeSubnet.create_one_subnet() port = network_fakes.FakePort.create_one_port() - # The floating ip to be deleted. + # The floating ip created. floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip( attrs={ 'floating_network_id': floating_network.id, @@ -65,6 +65,7 @@ class TestCreateFloatingIPNetwork(TestFloatingIPNetwork): 'qos_policy_id', 'router_id', 'status', + 'tags', ) data = ( @@ -80,12 +81,14 @@ class TestCreateFloatingIPNetwork(TestFloatingIPNetwork): floating_ip.qos_policy_id, floating_ip.router_id, floating_ip.status, + floating_ip.tags, ) def setUp(self): super(TestCreateFloatingIPNetwork, self).setUp() self.network.create_ip = mock.Mock(return_value=self.floating_ip) + self.network.set_tags = mock.Mock(return_value=None) self.network.find_network = mock.Mock( return_value=self.floating_network) @@ -221,6 +224,42 @@ def test_create_floating_ip_with_qos(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def _test_create_with_tag(self, add_tags=True): + arglist = [self.floating_ip.floating_network_id] + if add_tags: + arglist += ['--tag', 'red', '--tag', 'blue'] + else: + arglist += ['--no-tag'] + + verifylist = [ + ('network', self.floating_ip.floating_network_id), + ] + if add_tags: + verifylist.append(('tags', ['red', 'blue'])) + else: + verifylist.append(('no_tag', True)) + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_ip.assert_called_once_with(**{ + 'floating_network_id': self.floating_ip.floating_network_id, + }) + if add_tags: + self.network.set_tags.assert_called_once_with( + self.floating_ip, + tests_utils.CompareBySet(['red', 'blue'])) + else: + self.assertFalse(self.network.set_tags.called) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_with_tags(self): + self._test_create_with_tag(add_tags=True) + + def test_create_with_no_tag(self): + self._test_create_with_tag(add_tags=False) + class TestDeleteFloatingIPNetwork(TestFloatingIPNetwork): @@ -353,6 +392,7 @@ class TestListFloatingIPNetwork(TestFloatingIPNetwork): 'Router', 'Status', 'Description', + 'Tags', ) data = [] @@ -376,6 +416,7 @@ class TestListFloatingIPNetwork(TestFloatingIPNetwork): ip.router_id, ip.status, ip.description, + ip.tags, )) def setUp(self): @@ -539,6 +580,31 @@ def test_floating_ip_list_router(self): self.assertEqual(self.columns_long, columns) self.assertEqual(self.data_long, list(data)) + def test_list_with_tag_options(self): + arglist = [ + '--tags', 'red,blue', + '--any-tags', 'red,green', + '--not-tags', 'orange,yellow', + '--not-any-tags', 'black,white', + ] + verifylist = [ + ('tags', ['red', 'blue']), + ('any_tags', ['red', 'green']), + ('not_tags', ['orange', 'yellow']), + ('not_any_tags', ['black', 'white']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.ips.assert_called_once_with( + **{'tags': 'red,blue', + 'any_tags': 'red,green', + 'not_tags': 'orange,yellow', + 'not_any_tags': 'black,white'} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + class TestShowFloatingIPNetwork(TestFloatingIPNetwork): @@ -558,6 +624,7 @@ class TestShowFloatingIPNetwork(TestFloatingIPNetwork): 'qos_policy_id', 'router_id', 'status', + 'tags', ) data = ( @@ -573,6 +640,7 @@ class TestShowFloatingIPNetwork(TestFloatingIPNetwork): floating_ip.qos_policy_id, floating_ip.router_id, floating_ip.status, + floating_ip.tags, ) def setUp(self): @@ -609,11 +677,12 @@ class TestSetFloatingIP(TestFloatingIPNetwork): subnet = network_fakes.FakeSubnet.create_one_subnet() port = network_fakes.FakePort.create_one_port() - # The floating ip to be deleted. + # The floating ip to be set. floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip( attrs={ 'floating_network_id': floating_network.id, 'port_id': port.id, + 'tags': ['green', 'red'], } ) @@ -622,6 +691,7 @@ def setUp(self): self.network.find_ip = mock.Mock(return_value=self.floating_ip) self.network.find_port = mock.Mock(return_value=self.port) self.network.update_ip = mock.Mock(return_value=None) + self.network.set_tags = mock.Mock(return_value=None) # Get the command object to test self.cmd = fip.SetFloatingIP(self.app, self.namespace) @@ -731,6 +801,36 @@ def test_port_and_no_qos_policy_option(self): self.network.update_ip.assert_called_once_with( self.floating_ip, **attrs) + def _test_set_tags(self, with_tags=True): + if with_tags: + arglist = ['--tag', 'red', '--tag', 'blue'] + verifylist = [('tags', ['red', 'blue'])] + expected_args = ['red', 'blue', 'green'] + else: + arglist = ['--no-tag'] + verifylist = [('no_tag', True)] + expected_args = [] + arglist.extend(['--port', self.floating_ip.port_id, + self.floating_ip.id]) + verifylist.extend([ + ('port', self.floating_ip.port_id), + ('floating_ip', self.floating_ip.id)]) + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.assertTrue(self.network.update_ip.called) + self.network.set_tags.assert_called_once_with( + self.floating_ip, + tests_utils.CompareBySet(expected_args)) + self.assertIsNone(result) + + def test_set_with_tags(self): + self._test_set_tags(with_tags=True) + + def test_set_with_no_tag(self): + self._test_set_tags(with_tags=False) + class TestUnsetFloatingIP(TestFloatingIPNetwork): @@ -738,11 +838,12 @@ class TestUnsetFloatingIP(TestFloatingIPNetwork): subnet = network_fakes.FakeSubnet.create_one_subnet() port = network_fakes.FakePort.create_one_port() - # The floating ip to be deleted. + # The floating ip to be unset. floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip( attrs={ 'floating_network_id': floating_network.id, 'port_id': port.id, + 'tags': ['green', 'red'], } ) @@ -750,6 +851,7 @@ def setUp(self): super(TestUnsetFloatingIP, self).setUp() self.network.find_ip = mock.Mock(return_value=self.floating_ip) self.network.update_ip = mock.Mock(return_value=None) + self.network.set_tags = mock.Mock(return_value=None) # Get the command object to test self.cmd = fip.UnsetFloatingIP(self.app, self.namespace) @@ -803,3 +905,31 @@ def test_floating_ip_unset_qos_policy(self): self.floating_ip, **attrs) self.assertIsNone(result) + + def _test_unset_tags(self, with_tags=True): + if with_tags: + arglist = ['--tag', 'red', '--tag', 'blue'] + verifylist = [('tags', ['red', 'blue'])] + expected_args = ['green'] + else: + arglist = ['--all-tag'] + verifylist = [('all_tag', True)] + expected_args = [] + arglist.append(self.floating_ip.id) + verifylist.append( + ('floating_ip', self.floating_ip.id)) + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.assertFalse(self.network.update_ip.called) + self.network.set_tags.assert_called_once_with( + self.floating_ip, + tests_utils.CompareBySet(expected_args)) + self.assertIsNone(result) + + def test_unset_with_tags(self): + self._test_unset_tags(with_tags=True) + + def test_unset_with_all_tag(self): + self._test_unset_tags(with_tags=False) diff --git a/releasenotes/notes/bug-1750985-a5345f715a14825c.yaml b/releasenotes/notes/bug-1750985-a5345f715a14825c.yaml new file mode 100644 index 0000000000..87b0f29abe --- /dev/null +++ b/releasenotes/notes/bug-1750985-a5345f715a14825c.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Add ``--tag`` support to ``floating ip create|list|set|unset`` commands. + [:lpbug:`1750985`] From b776f7099a93e4b83dc4dbd25686a166a987cfa2 Mon Sep 17 00:00:00 2001 From: Dongcan Ye Date: Wed, 18 Apr 2018 16:51:26 +0000 Subject: [PATCH 1908/3095] Fix functional job failed After Neutron patch I3c93818002c2d7753454547231ba08544b6fa1c0 merged, the default value of segment description is an empty string. This patch will determine whether the Neuron shim extension standard-attr-segment supported. Change-Id: I68a3c018f03e5bb53bd637844ac9d7742a765db0 --- .../network/v2/test_network_segment.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/openstackclient/tests/functional/network/v2/test_network_segment.py b/openstackclient/tests/functional/network/v2/test_network_segment.py index 8940273f85..6ffb11cfc0 100644 --- a/openstackclient/tests/functional/network/v2/test_network_segment.py +++ b/openstackclient/tests/functional/network/v2/test_network_segment.py @@ -113,9 +113,20 @@ def test_network_segment_set_show(self): self.openstack, 'network segment delete ' + name ) - self.assertIsNone( - json_output["description"], - ) + + extension_output = json.loads(self.openstack( + "extension list -f json " + )) + ext_alias = [x["Alias"] for x in extension_output] + if "standard-attr-segment" in ext_alias: + self.assertEqual( + '', + json_output["description"], + ) + else: + self.assertIsNone( + json_output["description"], + ) new_description = 'new_description' cmd_output = self.openstack( From 5a9fc918308e19d4e563711be8eda5e75ea7f482 Mon Sep 17 00:00:00 2001 From: Tovin Seven Date: Fri, 20 Apr 2018 17:23:30 +0700 Subject: [PATCH 1909/3095] Trivial: Update pypi url to new url Pypi url changed from [1] to [2] [1] https://pypi.python.org/pypi/ [2] https://pypi.org/project/ Change-Id: I61f3d53737616dbbd6df725823b5335a57045dba --- README.rst | 6 +++--- doc/source/index.rst | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index 3b3642ed29..de216f1eb3 100644 --- a/README.rst +++ b/README.rst @@ -12,11 +12,11 @@ OpenStackClient =============== .. image:: https://img.shields.io/pypi/v/python-openstackclient.svg - :target: https://pypi.python.org/pypi/python-openstackclient/ + :target: https://pypi.org/project/python-openstackclient/ :alt: Latest Version .. image:: https://img.shields.io/pypi/dm/python-openstackclient.svg - :target: https://pypi.python.org/pypi/python-openstackclient/ + :target: https://pypi.org/project/python-openstackclient/ :alt: Downloads OpenStackClient (aka OSC) is a command-line client for OpenStack that brings @@ -38,7 +38,7 @@ language to describe operations in OpenStack. * IRC: #openstack-sdks on Freenode (irc.freenode.net) * License: Apache 2.0 -.. _PyPi: https://pypi.python.org/pypi/python-openstackclient +.. _PyPi: https://pypi.org/project/python-openstackclient .. _Online Documentation: https://docs.openstack.org/python-openstackclient/latest/ .. _Launchpad project: https://launchpad.net/python-openstackclient .. _Blueprints: https://blueprints.launchpad.net/python-openstackclient diff --git a/doc/source/index.rst b/doc/source/index.rst index 3f63edb3b4..37cc6c5727 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -65,7 +65,7 @@ Developers may also be found in the `IRC channel`_ ``#openstack-sdks``. .. _Gerrit: http://docs.openstack.org/infra/manual/developers.html#development-workflow .. _Bug reports: https://bugs.launchpad.net/python-openstackclient/+bugs .. _blueprints: https://blueprints.launchpad.net/python-openstackclient -.. _PyPi: https://pypi.python.org/pypi/python-openstackclient +.. _PyPi: https://pypi.org/project/python-openstackclient .. _tarball: http://tarballs.openstack.org/python-openstackclient .. _IRC channel: https://wiki.openstack.org/wiki/IRC From b8754e15e7adc9a04587f67c83febaf49b64f18c Mon Sep 17 00:00:00 2001 From: Pierre Hanselmann Date: Tue, 31 Oct 2017 15:35:10 +0100 Subject: [PATCH 1910/3095] Add dns-domain support to Network object Add "dns-domain" parameter to Network class. Also check backend extensions and send an error message in case of an argument (like dns-domain) is sent and the extension is missing (dns-integration in this case). Change-Id: I7303658c27d9b9f2d8381ccea0b29e96909cab54 Closes-Bug: 1633214 Partial-Bug: 1547736 --- doc/source/cli/command-objects/network.rst | 10 ++++++++ openstackclient/network/common.py | 25 +++++++++++++++++++ openstackclient/network/v2/network.py | 18 +++++++++++-- openstackclient/network/v2/port.py | 14 ++++++++--- .../tests/unit/network/v2/fakes.py | 1 + .../tests/unit/network/v2/test_network.py | 12 +++++++++ ...work_dns_integration-5914b2c2be474a41.yaml | 9 +++++++ 7 files changed, 83 insertions(+), 6 deletions(-) create mode 100644 releasenotes/notes/network_dns_integration-5914b2c2be474a41.yaml diff --git a/doc/source/cli/command-objects/network.rst b/doc/source/cli/command-objects/network.rst index 220fbf32b5..75113f89da 100644 --- a/doc/source/cli/command-objects/network.rst +++ b/doc/source/cli/command-objects/network.rst @@ -33,6 +33,7 @@ Create new network [--provider-segment ] [--qos-policy ] [--transparent-vlan | --no-transparent-vlan] + [--dns-domain ] [--tag | --no-tag] @@ -173,6 +174,10 @@ Create new network *Network version 2 only* +.. option:: --dns-domain + + Set DNS domain for this network (requires DNS integration extension). + .. option:: --tag Tag to be added to the network (repeat option to set multiple tags) @@ -367,6 +372,7 @@ Set network properties [--provider-physical-network ] [--provider-segment ] [--qos-policy | --no-qos-policy] + [--dns-domain ] [--tag ] [--no-tag] @@ -446,6 +452,10 @@ Set network properties Remove the QoS policy attached to this network +.. option:: --dns-domain + + Set DNS domain for this network (requires DNS integration extension). + .. option:: --tag Tag to be added to the network (repeat option to set multiple tags) diff --git a/openstackclient/network/common.py b/openstackclient/network/common.py index 37bf1406c5..d22b2caa3c 100644 --- a/openstackclient/network/common.py +++ b/openstackclient/network/common.py @@ -12,6 +12,7 @@ # import abc +import contextlib import logging import openstack.exceptions @@ -24,6 +25,30 @@ LOG = logging.getLogger(__name__) +_required_opt_extensions_map = { + 'allowed_address_pairs': 'allowed-address-pairs', + 'dns_domain': 'dns-integration', + 'dns_name': 'dns-integration', + 'extra_dhcp_opts': 'extra_dhcp_opt', + 'qos_policy_id': 'qos', + 'security_groups': 'security-groups', +} + + +@contextlib.contextmanager +def check_missing_extension_if_error(client_manager, attrs): + # If specified option requires extension, then try to + # find out if it exists. If it does not exist, + # then an exception with the appropriate message + # will be thrown from within client.find_extension + try: + yield + except openstack.exceptions.HttpException: + for opt, ext in _required_opt_extensions_map.items(): + if opt in attrs: + client_manager.find_extension(ext, ignore_missing=False) + raise + @six.add_metaclass(abc.ABCMeta) class NetworkAndComputeCommand(command.Command): diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index d1c7f005d3..0fdf62c95b 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -134,6 +134,9 @@ def _get_attrs_network(client_manager, parsed_args): 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 DNS network options + if parsed_args.dns_domain: + attrs['dns_domain'] = parsed_args.dns_domain return attrs @@ -171,6 +174,13 @@ def _add_additional_network_options(parser): dest='segmentation_id', help=_("VLAN ID for VLAN networks or Tunnel ID for " "GENEVE/GRE/VXLAN networks")) + parser.add_argument( + '--dns-domain', + metavar='', + dest='dns_domain', + help=_("Set DNS domain for this network " + "(requires DNS integration extension)") + ) # TODO(sindhu): Use the SDK resource mapped attribute names once the @@ -308,8 +318,10 @@ def take_action_network(self, client, parsed_args): attrs['vlan_transparent'] = True if parsed_args.no_transparent_vlan: attrs['vlan_transparent'] = False + with common.check_missing_extension_if_error( + self.app.client_manager.network, attrs): + obj = client.create_network(**attrs) - obj = client.create_network(**attrs) # tags cannot be set when created, so tags need to be set later. _tag.update_tags_for_set(client, obj, parsed_args) display_columns, columns = _get_columns_network(obj) @@ -690,7 +702,9 @@ def take_action(self, parsed_args): attrs = _get_attrs_network(self.app.client_manager, parsed_args) if attrs: - client.update_network(obj, **attrs) + with common.check_missing_extension_if_error( + self.app.client_manager.network, attrs): + client.update_network(obj, **attrs) # tags is a subresource and it needs to be updated separately. _tag.update_tags_for_set(client, obj, parsed_args) diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index f13ee7b90b..e00f63794d 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -25,6 +25,7 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import common from openstackclient.network import sdk_utils from openstackclient.network.v2 import _tag @@ -278,8 +279,8 @@ def _add_updatable_args(parser): ) parser.add_argument( '--dns-name', - metavar='dns-name', - help=_("Set DNS name to this port " + metavar='', + help=_("Set DNS name for this port " "(requires DNS integration extension)") ) @@ -434,7 +435,10 @@ def take_action(self, parsed_args): if parsed_args.qos_policy: attrs['qos_policy_id'] = client.find_qos_policy( parsed_args.qos_policy, ignore_missing=False).id - obj = client.create_port(**attrs) + with common.check_missing_extension_if_error( + self.app.client_manager.network, attrs): + obj = client.create_port(**attrs) + # tags cannot be set when created, so tags need to be set later. _tag.update_tags_for_set(client, obj, parsed_args) display_columns, columns = _get_columns(obj) @@ -785,7 +789,9 @@ def take_action(self, parsed_args): attrs['data_plane_status'] = parsed_args.data_plane_status if attrs: - client.update_port(obj, **attrs) + with common.check_missing_extension_if_error( + self.app.client_manager.network, attrs): + client.update_port(obj, **attrs) # tags is a subresource and it needs to be updated separately. _tag.update_tags_for_set(client, obj, parsed_args) diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index 0e21e2f8ac..85688b1f9f 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -335,6 +335,7 @@ def create_one_network(attrs=None): 'name': 'network-name-' + uuid.uuid4().hex, 'status': 'ACTIVE', 'description': 'network-description-' + uuid.uuid4().hex, + 'dns_domain': 'example.org.', 'mtu': '1350', 'tenant_id': 'project-id-' + uuid.uuid4().hex, 'admin_state_up': True, diff --git a/openstackclient/tests/unit/network/v2/test_network.py b/openstackclient/tests/unit/network/v2/test_network.py index 9f4a6accf8..0f57f0eec9 100644 --- a/openstackclient/tests/unit/network/v2/test_network.py +++ b/openstackclient/tests/unit/network/v2/test_network.py @@ -60,6 +60,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): 'availability_zone_hints', 'availability_zones', 'description', + 'dns_domain', 'id', 'ipv4_address_scope', 'ipv6_address_scope', @@ -84,6 +85,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): utils.format_list(_network.availability_zone_hints), utils.format_list(_network.availability_zones), _network.description, + _network.dns_domain, _network.id, _network.ipv4_address_scope_id, _network.ipv6_address_scope_id, @@ -162,6 +164,7 @@ def test_create_all_options(self): "--qos-policy", self.qos_policy.id, "--transparent-vlan", "--enable-port-security", + "--dns-domain", "example.org.", self._network.name, ] verifylist = [ @@ -181,6 +184,7 @@ def test_create_all_options(self): ('transparent_vlan', True), ('enable_port_security', True), ('name', self._network.name), + ('dns_domain', 'example.org.'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -204,6 +208,7 @@ def test_create_all_options(self): 'qos_policy_id': self.qos_policy.id, 'vlan_transparent': True, 'port_security_enabled': True, + 'dns_domain': 'example.org.', }) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -287,6 +292,7 @@ class TestCreateNetworkIdentityV2(TestNetwork): 'availability_zone_hints', 'availability_zones', 'description', + 'dns_domain', 'id', 'ipv4_address_scope', 'ipv6_address_scope', @@ -311,6 +317,7 @@ class TestCreateNetworkIdentityV2(TestNetwork): utils.format_list(_network.availability_zone_hints), utils.format_list(_network.availability_zones), _network.description, + _network.dns_domain, _network.id, _network.ipv4_address_scope_id, _network.ipv6_address_scope_id, @@ -901,6 +908,7 @@ def test_set_this(self): '--name', 'noob', '--share', '--description', self._network.description, + '--dns-domain', 'example.org.', '--external', '--default', '--provider-network-type', 'vlan', @@ -922,6 +930,7 @@ def test_set_this(self): ('segmentation_id', '400'), ('enable_port_security', True), ('qos_policy', self.qos_policy.name), + ('dns_domain', 'example.org.'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -939,6 +948,7 @@ def test_set_this(self): 'provider:segmentation_id': '400', 'port_security_enabled': True, 'qos_policy_id': self.qos_policy.id, + 'dns_domain': 'example.org.', } self.network.update_network.assert_called_once_with( self._network, **attrs) @@ -1026,6 +1036,7 @@ class TestShowNetwork(TestNetwork): 'availability_zone_hints', 'availability_zones', 'description', + 'dns_domain', 'id', 'ipv4_address_scope', 'ipv6_address_scope', @@ -1050,6 +1061,7 @@ class TestShowNetwork(TestNetwork): utils.format_list(_network.availability_zone_hints), utils.format_list(_network.availability_zones), _network.description, + _network.dns_domain, _network.id, _network.ipv4_address_scope_id, _network.ipv6_address_scope_id, diff --git a/releasenotes/notes/network_dns_integration-5914b2c2be474a41.yaml b/releasenotes/notes/network_dns_integration-5914b2c2be474a41.yaml new file mode 100644 index 0000000000..ede567472c --- /dev/null +++ b/releasenotes/notes/network_dns_integration-5914b2c2be474a41.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + Add dns-domain support for network commands. + The new parameter ``--dns-domain`` is added to the ``network create`` + and ``network set`` commands. This parameter sets + the domain name for the network. + Check backend available extension and return an error + message if it is missing (instead of a Bad Request HTTP 400). From 46f8614da836538f7b0a3d250e13bea05288932f Mon Sep 17 00:00:00 2001 From: Hongbin Lu Date: Mon, 15 Jan 2018 22:21:19 +0000 Subject: [PATCH 1911/3095] Format port_details field of Floating IP Depends-On: I31e940d2986278d2fbee6fdfea4ff15f7c07ebaa Change-Id: I115739ea253ce26e075cd3f10e719b13f18afa5b Partial-Bug: #1723026 --- openstackclient/network/v2/floating_ip.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index f51baed578..572cbef660 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -25,6 +25,11 @@ from openstackclient.network.v2 import _tag +_formatters = { + 'port_details': utils.format_dict, +} + + def _get_network_columns(item): column_map = { 'tenant_id': 'project_id', @@ -489,7 +494,7 @@ def take_action_network(self, client, parsed_args): ignore_missing=False, ) display_columns, columns = _get_network_columns(obj) - data = utils.get_item_properties(obj, columns) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return (display_columns, data) def take_action_compute(self, client, parsed_args): From de9a6fc0700d821f9fca6bf347407eccd87f0064 Mon Sep 17 00:00:00 2001 From: Julie Pichon Date: Wed, 9 May 2018 17:43:09 +0100 Subject: [PATCH 1912/3095] Prevent "server migrate --wait" from hanging Migrate uses the same mechanism in the backend than Resize and so the steps and step names are similar. Currently when using the --wait option with 'migrate', we wait forever because the status won't get to active until the user performs an action. This makes it return on verify_resize status just like 'resize' does, so that the user can perform the next manual step. Change-Id: Ie1aeac52506bc8801f88fd6a6eb4f6094cf20050 Story: 2001994 Task: 19621 --- openstackclient/compute/v2/server.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 5daefc0cd6..c80b5a3c25 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1386,6 +1386,7 @@ def _show_progress(progress): if utils.wait_for_status( compute_client.servers.get, server.id, + success_status=['active', 'verify_resize'], callback=_show_progress, ): self.app.stdout.write(_('Complete\n')) From f7e4d31820e797e0d374e7dfde1142373245ea87 Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Fri, 18 May 2018 07:11:30 -0500 Subject: [PATCH 1913/3095] Update command test for volume.v3 The default cinder version in devstack changed to v3 in https://review.openstack.org/#/c/566747/which breaks this test. Change the test to test what's going to happen. Change-Id: Iff4d8b47812a86d21bf5dbdddbd642b9d63ff8fe --- openstackclient/tests/functional/common/test_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/tests/functional/common/test_module.py b/openstackclient/tests/functional/common/test_module.py index d589f19cde..41aabb7f85 100644 --- a/openstackclient/tests/functional/common/test_module.py +++ b/openstackclient/tests/functional/common/test_module.py @@ -46,7 +46,7 @@ def test_module_list(self): class CommandTest(base.TestCase): """Functional tests for openstackclient command list.""" GROUPS = [ - 'openstack.volume.v2', + 'openstack.volume.v3', 'openstack.network.v2', 'openstack.image.v2', 'openstack.identity.v3', From 819663d1343a5025e2f677803b0faa91acc83318 Mon Sep 17 00:00:00 2001 From: chenxing Date: Tue, 22 May 2018 15:36:19 +0800 Subject: [PATCH 1914/3095] Update the content about Import Format Following by https://git.openstack.org/cgit/openstack-dev/hacking/tree/HACKING.rst#n71 Change-Id: I2f32d773c12d484e8c0e435a78a3fe16d0eeae03 --- doc/source/contributor/developing.rst | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/doc/source/contributor/developing.rst b/doc/source/contributor/developing.rst index 721a016a03..1488816846 100644 --- a/doc/source/contributor/developing.rst +++ b/doc/source/contributor/developing.rst @@ -177,18 +177,21 @@ or Standardize Import Format ========================= -.. _`Import Order Guide`: https://docs.openstack.org/hacking/latest/user/hacking.html#imports +More information about Import Format, see `Import Order Guide +`__. The import order shows below: -* {{stdlib imports in human alphabetical order}} -* \n -* {{third-party lib imports in human alphabetical order}} -* \n -* {{project imports in human alphabetical order}} -* \n -* \n -* {{begin your code}} +.. code-block:: none + + {{stdlib imports in human alphabetical order}} + \n + {{third-party lib imports in human alphabetical order}} + \n + {{project imports in human alphabetical order}} + \n + \n + {{begin your code}} Example ~~~~~~~ From dbff17d72005910ddd74355062bf6fe4c6e2ca63 Mon Sep 17 00:00:00 2001 From: "zhang.lei" Date: Wed, 23 May 2018 09:20:23 +0000 Subject: [PATCH 1915/3095] Add cliff project link Change-Id: I6a964781667aa92f4b8d364f63a25f1c76309dca --- doc/source/contributor/command-options.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/contributor/command-options.rst b/doc/source/contributor/command-options.rst index 70890ec69d..845d51f9f2 100644 --- a/doc/source/contributor/command-options.rst +++ b/doc/source/contributor/command-options.rst @@ -19,7 +19,7 @@ In short: * All option names shall be GNU-style long names (two leading dashes). * Some global options may have short names, generally limited to those defined - in support libraries such as ``cliff``. + in support libraries such as `cliff `__. General Command Options ======================= From 8db3933feb35f91f3ff3d121c155286973c66122 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C5=82awek=20Kap=C5=82o=C5=84ski?= Date: Thu, 10 May 2018 17:16:48 +0200 Subject: [PATCH 1916/3095] Don't display router's is_ha and is_distributed attributes always In case when is_ha or is_distributed attribute of Neutron's router is set to None, it means that it wasn't returned from server and should not be displayed. Otherwise it might be confusing for user is making openstack router show call as an admin will return e.g. is_ha=True but same call done as regular user will return False or None. It might happen like that because returning of those attributes is forbidden for regular users in Neutron's policy.json Depends-On: https://review.openstack.org/567606/ Change-Id: I626b5193d9ecb308baad7b27939f9673c32b4182 Closes-Bug: #1689510 Task: 19789 Story: 2002110 --- openstackclient/network/v2/router.py | 24 ++++++++--- .../tests/unit/network/v2/test_router.py | 43 ++++++++++++++++++- 2 files changed, 60 insertions(+), 7 deletions(-) diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index f0a5196744..4819f279f2 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -71,7 +71,15 @@ def _get_columns(item): } if hasattr(item, 'interfaces_info'): column_map['interfaces_info'] = 'interfaces_info' - return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + invisible_columns = [] + if item.is_ha is None: + invisible_columns.append('is_ha') + column_map.pop('is_ha') + if item.is_distributed is None: + invisible_columns.append('is_distributed') + column_map.pop('is_distributed') + return sdk_utils.get_osc_show_columns_for_sdk_resource( + item, column_map, invisible_columns) def _get_attrs(client_manager, parsed_args): @@ -330,8 +338,6 @@ def take_action(self, parsed_args): 'name', 'status', 'is_admin_state_up', - 'is_distributed', - 'is_ha', 'project_id', ) column_headers = ( @@ -339,8 +345,6 @@ def take_action(self, parsed_args): 'Name', 'Status', 'State', - 'Distributed', - 'HA', 'Project', ) @@ -376,6 +380,16 @@ def take_action(self, parsed_args): else: data = client.routers(**args) + # check if "HA" and "Distributed" columns should be displayed also + data = list(data) + for d in data: + if (d.is_distributed is not None and + 'is_distributed' not in columns): + columns = columns + ('is_distributed',) + column_headers = column_headers + ('Distributed',) + if d.is_ha is not None and 'is_ha' not in columns: + columns = columns + ('is_ha',) + column_headers = column_headers + ('HA',) if parsed_args.long: columns = columns + ( 'routes', diff --git a/openstackclient/tests/unit/network/v2/test_router.py b/openstackclient/tests/unit/network/v2/test_router.py index f383c1ddb7..7a7bcf90a4 100644 --- a/openstackclient/tests/unit/network/v2/test_router.py +++ b/openstackclient/tests/unit/network/v2/test_router.py @@ -400,9 +400,9 @@ class TestListRouter(TestRouter): 'Name', 'Status', 'State', + 'Project', 'Distributed', 'HA', - 'Project', ) columns_long = columns + ( 'Routes', @@ -423,9 +423,9 @@ class TestListRouter(TestRouter): r.name, r.status, router._format_admin_state(r.admin_state_up), + r.tenant_id, r.distributed, r.ha, - r.tenant_id, )) router_agent_data = [] @@ -496,6 +496,25 @@ def test_router_list_no_options(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + def test_router_list_no_ha_no_distributed(self): + _routers = network_fakes.FakeRouter.create_routers({ + 'ha': None, + 'distributed': None}, + count=3) + + arglist = [] + verifylist = [ + ('long', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + with mock.patch.object( + self.network, "routers", return_value=_routers): + columns, data = self.cmd.take_action(parsed_args) + + self.assertNotIn("is_distributed", columns) + self.assertNotIn("is_ha", columns) + def test_router_list_long(self): arglist = [ '--long', @@ -1196,6 +1215,26 @@ def test_show_all_options(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_show_no_ha_no_distributed(self): + _router = network_fakes.FakeRouter.create_one_router({ + 'ha': None, + 'distributed': None}) + + arglist = [ + _router.name, + ] + verifylist = [ + ('router', _router.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + with mock.patch.object( + self.network, "find_router", return_value=_router): + columns, data = self.cmd.take_action(parsed_args) + + self.assertNotIn("is_distributed", columns) + self.assertNotIn("is_ha", columns) + class TestUnsetRouter(TestRouter): From 9b6d02d5f958e5b2ea6155879fd0514a15ce2231 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C5=82awek=20Kap=C5=82o=C5=84ski?= Date: Fri, 11 May 2018 13:04:23 +0200 Subject: [PATCH 1917/3095] Make max_burst_kbps option as optional for bw limit QoS rule Attribute max_burst_kbps of QoS bandwidth limit rule in Neutron's is optional in API so it should be also optional on client's side. Change-Id: Ie085b73fa885ff12f9ac080666cf3ca6a09b632a Related-Bug:#1770622 Task: 19658 Story: 2002017 --- .../network/v2/network_qos_rule.py | 8 +-- .../network/v2/test_network_qos_rule.py | 1 - .../unit/network/v2/test_network_qos_rule.py | 55 +++++++++++++++++-- 3 files changed, 53 insertions(+), 11 deletions(-) diff --git a/openstackclient/network/v2/network_qos_rule.py b/openstackclient/network/v2/network_qos_rule.py index f50e58b32d..9c4275a82b 100644 --- a/openstackclient/network/v2/network_qos_rule.py +++ b/openstackclient/network/v2/network_qos_rule.py @@ -29,11 +29,11 @@ MANDATORY_PARAMETERS = { RULE_TYPE_MINIMUM_BANDWIDTH: {'min_kbps', 'direction'}, RULE_TYPE_DSCP_MARKING: {'dscp_mark'}, - RULE_TYPE_BANDWIDTH_LIMIT: {'max_kbps', 'max_burst_kbps'}} + RULE_TYPE_BANDWIDTH_LIMIT: {'max_kbps'}} OPTIONAL_PARAMETERS = { RULE_TYPE_MINIMUM_BANDWIDTH: set(), RULE_TYPE_DSCP_MARKING: set(), - RULE_TYPE_BANDWIDTH_LIMIT: {'direction'}} + RULE_TYPE_BANDWIDTH_LIMIT: {'direction', 'max_burst_kbps'}} DIRECTION_EGRESS = 'egress' DIRECTION_INGRESS = 'ingress' DSCP_VALID_MARKS = [0, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, @@ -62,11 +62,11 @@ def _check_type_parameters(attrs, type, is_create): notreq_params -= type_params if is_create and None in map(attrs.get, req_params): msg = (_('"Create" rule command for type "%(rule_type)s" requires ' - 'arguments %(args)s') % + 'arguments: %(args)s') % {'rule_type': type, 'args': ", ".join(sorted(req_params))}) raise exceptions.CommandError(msg) if set(attrs.keys()) & notreq_params: - msg = (_('Rule type "%(rule_type)s" only requires arguments %(args)s') + msg = (_('Rule type "%(rule_type)s" only requires arguments: %(args)s') % {'rule_type': type, 'args': ", ".join(sorted(type_params))}) raise exceptions.CommandError(msg) diff --git a/openstackclient/tests/functional/network/v2/test_network_qos_rule.py b/openstackclient/tests/functional/network/v2/test_network_qos_rule.py index 5d235405c5..98e588e82d 100644 --- a/openstackclient/tests/functional/network/v2/test_network_qos_rule.py +++ b/openstackclient/tests/functional/network/v2/test_network_qos_rule.py @@ -167,7 +167,6 @@ def setUp(self): 'network qos rule create -f json ' '--type bandwidth-limit ' '--max-kbps 10000 ' - '--max-burst-kbits 1400 ' '--egress %s' % self.QOS_POLICY_NAME )) diff --git a/openstackclient/tests/unit/network/v2/test_network_qos_rule.py b/openstackclient/tests/unit/network/v2/test_network_qos_rule.py index 176bc86d2c..5b54d318ac 100644 --- a/openstackclient/tests/unit/network/v2/test_network_qos_rule.py +++ b/openstackclient/tests/unit/network/v2/test_network_qos_rule.py @@ -127,7 +127,7 @@ def test_create_wrong_options(self): self.cmd.take_action(parsed_args) except exceptions.CommandError as e: msg = ('"Create" rule command for type "minimum-bandwidth" ' - 'requires arguments direction, min_kbps') + 'requires arguments: direction, min_kbps') self.assertEqual(msg, str(e)) @@ -213,7 +213,7 @@ def test_create_wrong_options(self): self.cmd.take_action(parsed_args) except exceptions.CommandError as e: msg = ('"Create" rule command for type "dscp-marking" ' - 'requires arguments dscp_mark') + 'requires arguments: dscp_mark') self.assertEqual(msg, str(e)) @@ -263,6 +263,49 @@ def test_create_no_options(self): self.cmd, arglist, verifylist) def test_create_default_options(self): + arglist = [ + '--type', RULE_TYPE_BANDWIDTH_LIMIT, + '--max-kbps', str(self.new_rule.max_kbps), + '--egress', + self.new_rule.qos_policy_id, + ] + + verifylist = [ + ('type', RULE_TYPE_BANDWIDTH_LIMIT), + ('max_kbps', self.new_rule.max_kbps), + ('egress', True), + ('qos_policy', self.new_rule.qos_policy_id), + ] + + rule = network_fakes.FakeNetworkQosRule.create_one_qos_rule( + {'qos_policy_id': self.qos_policy.id, + 'type': RULE_TYPE_BANDWIDTH_LIMIT}) + rule.max_burst_kbits = 0 + expected_data = ( + rule.direction, + rule.id, + rule.max_burst_kbits, + rule.max_kbps, + rule.project_id, + rule.qos_policy_id, + rule.type, + ) + + with mock.patch.object( + self.network, "create_qos_bandwidth_limit_rule", + return_value=rule) as create_qos_bandwidth_limit_rule: + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = (self.cmd.take_action(parsed_args)) + + create_qos_bandwidth_limit_rule.assert_called_once_with( + self.qos_policy.id, + **{'max_kbps': self.new_rule.max_kbps, + 'direction': self.new_rule.direction} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(expected_data, data) + + def test_create_all_options(self): arglist = [ '--type', RULE_TYPE_BANDWIDTH_LIMIT, '--max-kbps', str(self.new_rule.max_kbps), @@ -309,7 +352,7 @@ def test_create_wrong_options(self): self.cmd.take_action(parsed_args) except exceptions.CommandError as e: msg = ('"Create" rule command for type "bandwidth-limit" ' - 'requires arguments max_burst_kbps, max_kbps') + 'requires arguments: max_kbps') self.assertEqual(msg, str(e)) @@ -579,7 +622,7 @@ def test_set_wrong_options(self): self.cmd.take_action(parsed_args) except exceptions.CommandError as e: msg = ('Failed to set Network QoS rule ID "%(rule)s": Rule type ' - '"minimum-bandwidth" only requires arguments direction, ' + '"minimum-bandwidth" only requires arguments: direction, ' 'min_kbps' % {'rule': self.new_rule.id}) self.assertEqual(msg, str(e)) @@ -673,7 +716,7 @@ def test_set_wrong_options(self): self.cmd.take_action(parsed_args) except exceptions.CommandError as e: msg = ('Failed to set Network QoS rule ID "%(rule)s": Rule type ' - '"dscp-marking" only requires arguments dscp_mark' % + '"dscp-marking" only requires arguments: dscp_mark' % {'rule': self.new_rule.id}) self.assertEqual(msg, str(e)) @@ -837,7 +880,7 @@ def test_set_wrong_options(self): self.cmd.take_action(parsed_args) except exceptions.CommandError as e: msg = ('Failed to set Network QoS rule ID "%(rule)s": Rule type ' - '"bandwidth-limit" only requires arguments direction, ' + '"bandwidth-limit" only requires arguments: direction, ' 'max_burst_kbps, max_kbps' % {'rule': self.new_rule.id}) self.assertEqual(msg, str(e)) From 47d0d0e0c02529cf6516532758e1dc565ef7cc1a Mon Sep 17 00:00:00 2001 From: Jens Harbott Date: Thu, 31 May 2018 07:41:06 +0000 Subject: [PATCH 1918/3095] Fix lower-constraints.txt This was originally generated before the compliance test was installed, so it is now failing for every unrelated change to requirements. Replace lower-constraints.txt with the output of running openstack/requirements/tools/fix-lower-constraints.py . Change-Id: I492e663622db75994bdababfa6dc81589e3fb53b --- lower-constraints.txt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lower-constraints.txt b/lower-constraints.txt index f7c7d09faf..f8a6cde90d 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -3,7 +3,7 @@ aodhclient==0.9.0 appdirs==1.3.0 asn1crypto==0.23.0 Babel==2.3.4 -bandit==1.4.0 +bandit==1.1.0 cachetools==2.0.0 cffi==1.7.0 cliff==2.8.0 @@ -14,15 +14,15 @@ cryptography==2.1 debtcollector==1.2.0 decorator==3.4.0 deprecation==1.0 -docker==2.4.2 docker-pycreds==0.2.1 +docker==2.4.2 dogpile.cache==0.6.2 eventlet==0.18.2 extras==1.0.0 fasteners==0.7.0 fixtures==3.0.0 -flake8==2.5.5 flake8-import-order==0.13 +flake8==2.5.5 future==0.16.0 futurist==1.2.0 gitdb==0.6.4 @@ -72,8 +72,8 @@ Paste==2.0.2 PasteDeploy==1.5.0 pbr==2.0.0 pep8==1.5.7 -pika==0.10.0 pika-pool==0.1.3 +pika==0.10.0 ply==3.10 positional==1.2.1 prettytable==0.7.2 @@ -93,7 +93,7 @@ python-designateclient==2.7.0 python-glanceclient==2.8.0 python-heatclient==1.10.0 python-ironic-inspector-client==1.5.0 -python-ironicclient==2.2.0 +python-ironicclient==2.3.0 python-karborclient==0.6.0 python-keystoneclient==3.8.0 python-mimeparse==1.6.0 @@ -110,12 +110,12 @@ python-subunit==1.0.0 python-swiftclient==3.2.0 python-troveclient==2.2.0 python-zaqarclient==1.0.0 -python-zunclient==1.2.1 +python-zunclient==1.3.0 pytz==2013.6 PyYAML==3.12 repoze.lru==0.7 +requests-mock==1.2.0 requests==2.14.2 -requests-mock==1.1.0 requestsexceptions==1.2.0 rfc3986==0.3.1 Routes==2.3.1 From 8bfa180430354d1db11b11a3443486fe04415443 Mon Sep 17 00:00:00 2001 From: Lance Bragstad Date: Thu, 30 Nov 2017 22:42:55 +0000 Subject: [PATCH 1919/3095] Add system role functionality This commit adds the necessary bits to expose system role assignments to openstackclient via python-keystoneclient. bp system-scope Depends-On: Iecbcbf020a15f2bec777334c648d4477f89f3b2c Change-Id: I261e84700b51e8715eaebdc3f8f8bc46b68542c2 --- lower-constraints.txt | 2 +- openstackclient/identity/v3/role.py | 33 +++++++++++---- .../identity/v3/role_assignment.py | 35 +++++++++++++--- openstackclient/identity/v3/token.py | 6 +++ .../unit/identity/v3/test_role_assignment.py | 42 ++++++++++++++++++- ...plement-system-scope-4c3c47996f98deac.yaml | 8 ++++ requirements.txt | 2 +- 7 files changed, 112 insertions(+), 16 deletions(-) create mode 100644 releasenotes/notes/implement-system-scope-4c3c47996f98deac.yaml diff --git a/lower-constraints.txt b/lower-constraints.txt index f8a6cde90d..88c75cf47b 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -95,7 +95,7 @@ python-heatclient==1.10.0 python-ironic-inspector-client==1.5.0 python-ironicclient==2.3.0 python-karborclient==0.6.0 -python-keystoneclient==3.8.0 +python-keystoneclient==3.15.0 python-mimeparse==1.6.0 python-mistralclient==3.1.0 python-muranoclient==0.8.2 diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index 2828a34928..58a76f8a65 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -31,13 +31,18 @@ def _add_identity_and_resource_options_to_parser(parser): - domain_or_project = parser.add_mutually_exclusive_group() - domain_or_project.add_argument( + system_or_domain_or_project = parser.add_mutually_exclusive_group() + system_or_domain_or_project.add_argument( + '--system', + metavar='', + help=_('Include (all)'), + ) + system_or_domain_or_project.add_argument( '--domain', metavar='', help=_('Include (name or ID)'), ) - domain_or_project.add_argument( + system_or_domain_or_project.add_argument( '--project', metavar='', help=_('Include (name or ID)'), @@ -62,7 +67,14 @@ def _add_identity_and_resource_options_to_parser(parser): def _process_identity_and_resource_options(parsed_args, identity_client_manager): kwargs = {} - if parsed_args.user and parsed_args.domain: + if parsed_args.user and parsed_args.system: + kwargs['user'] = common.find_user( + identity_client_manager, + parsed_args.user, + parsed_args.user_domain, + ).id + kwargs['system'] = parsed_args.system + elif parsed_args.user and parsed_args.domain: kwargs['user'] = common.find_user( identity_client_manager, parsed_args.user, @@ -83,6 +95,13 @@ def _process_identity_and_resource_options(parsed_args, parsed_args.project, parsed_args.project_domain, ).id + elif parsed_args.group and parsed_args.system: + kwargs['group'] = common.find_group( + identity_client_manager, + parsed_args.group, + parsed_args.group_domain, + ).id + kwargs['system'] = parsed_args.system elif parsed_args.group and parsed_args.domain: kwargs['group'] = common.find_group( identity_client_manager, @@ -109,8 +128,8 @@ def _process_identity_and_resource_options(parsed_args, class AddRole(command.Command): - _description = _("Adds a role assignment to a user or group on a domain " - "or project") + _description = _("Adds a role assignment to a user or group on the " + "system, a domain, or a project") def get_parser(self, prog_name): parser = super(AddRole, self).get_parser(prog_name) @@ -381,7 +400,7 @@ def take_action(self, parsed_args): class RemoveRole(command.Command): - _description = _("Removes a role assignment from domain/project : " + _description = _("Removes a role assignment from system/domain/project : " "user/group") def get_parser(self, prog_name): diff --git a/openstackclient/identity/v3/role_assignment.py b/openstackclient/identity/v3/role_assignment.py index a362adb07d..9c2f3d249e 100644 --- a/openstackclient/identity/v3/role_assignment.py +++ b/openstackclient/identity/v3/role_assignment.py @@ -55,17 +55,22 @@ def get_parser(self, prog_name): help=_('Group to filter (name or ID)'), ) common.add_group_domain_option_to_parser(parser) - domain_or_project = parser.add_mutually_exclusive_group() - domain_or_project.add_argument( + system_or_domain_or_project = parser.add_mutually_exclusive_group() + system_or_domain_or_project.add_argument( '--domain', metavar='', help=_('Domain to filter (name or ID)'), ) - domain_or_project.add_argument( + system_or_domain_or_project.add_argument( '--project', metavar='', help=_('Project to filter (name or ID)'), ) + system_or_domain_or_project.add_argument( + '--system', + metavar='', + help=_('Filter based on system role assignments'), + ) common.add_project_domain_option_to_parser(parser) common.add_inherited_option_to_parser(parser) parser.add_argument( @@ -85,7 +90,8 @@ def get_parser(self, prog_name): def _as_tuple(self, assignment): return (assignment.role, assignment.user, assignment.group, - assignment.project, assignment.domain, assignment.inherited) + assignment.project, assignment.domain, assignment.system, + assignment.inherited) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity @@ -117,6 +123,10 @@ def take_action(self, parsed_args): auth_ref.user_id ) + system = None + if parsed_args.system: + system = parsed_args.system + domain = None if parsed_args.domain: domain = common.find_domain( @@ -149,7 +159,9 @@ def take_action(self, parsed_args): include_names = True if parsed_args.names else False effective = True if parsed_args.effective else False - columns = ('Role', 'User', 'Group', 'Project', 'Domain', 'Inherited') + columns = ( + 'Role', 'User', 'Group', 'Project', 'Domain', 'System', 'Inherited' + ) inherited_to = 'projects' if parsed_args.inherited else None data = identity_client.role_assignments.list( @@ -157,6 +169,7 @@ def take_action(self, parsed_args): user=user, group=group, project=project, + system=system, role=role, effective=effective, os_inherit_extension_inherited_to=inherited_to, @@ -174,14 +187,24 @@ def take_action(self, parsed_args): else: setattr(assignment, 'project', scope['project']['id']) assignment.domain = '' + assignment.system = '' elif 'domain' in scope: if include_names: setattr(assignment, 'domain', scope['domain']['name']) else: setattr(assignment, 'domain', scope['domain']['id']) assignment.project = '' - + assignment.system = '' + elif 'system' in scope: + # NOTE(lbragstad): If, or when, keystone supports role + # assignments on subsets of a system, this will have to evolve + # to handle that case instead of hardcoding to the entire + # system. + setattr(assignment, 'system', 'all') + assignment.domain = '' + assignment.project = '' else: + assignment.system = '' assignment.domain = '' assignment.project = '' diff --git a/openstackclient/identity/v3/token.py b/openstackclient/identity/v3/token.py index effb9e3525..1933ecad65 100644 --- a/openstackclient/identity/v3/token.py +++ b/openstackclient/identity/v3/token.py @@ -192,6 +192,12 @@ def take_action(self, parsed_args): data['user_id'] = auth_ref.user_id if auth_ref.domain_id: data['domain_id'] = auth_ref.domain_id + if auth_ref.system_scoped: + # NOTE(lbragstad): This could change in the future when, or if, + # keystone supports the ability to scope to a subset of the entire + # deployment system. When that happens, this will have to relay + # scope information and IDs like we do for projects and domains. + data['system'] = 'all' return zip(*sorted(six.iteritems(data))) diff --git a/openstackclient/tests/unit/identity/v3/test_role_assignment.py b/openstackclient/tests/unit/identity/v3/test_role_assignment.py index 835837e608..bff6c56df1 100644 --- a/openstackclient/tests/unit/identity/v3/test_role_assignment.py +++ b/openstackclient/tests/unit/identity/v3/test_role_assignment.py @@ -34,6 +34,7 @@ class TestRoleAssignmentList(TestRoleAssignment): 'Group', 'Project', 'Domain', + 'System', 'Inherited', ) @@ -95,6 +96,7 @@ def test_role_assignment_list_no_filters(self): self.role_assignments_mock.list.assert_called_with( domain=None, + system=None, group=None, effective=False, role=None, @@ -110,12 +112,14 @@ def test_role_assignment_list_no_filters(self): '', identity_fakes.project_id, '', + '', False ), (identity_fakes.role_id, '', identity_fakes.group_id, identity_fakes.project_id, '', + '', False ),) self.assertEqual(datalist, tuple(data)) @@ -143,6 +147,7 @@ def test_role_assignment_list_user(self): verifylist = [ ('user', identity_fakes.user_name), ('group', None), + ('system', None), ('domain', None), ('project', None), ('role', None), @@ -159,6 +164,7 @@ def test_role_assignment_list_user(self): self.role_assignments_mock.list.assert_called_with( domain=None, + system=None, user=self.users_mock.get(), group=None, project=None, @@ -174,12 +180,14 @@ def test_role_assignment_list_user(self): '', '', identity_fakes.domain_id, + '', False ), (identity_fakes.role_id, identity_fakes.user_id, '', identity_fakes.project_id, '', + '', False ),) self.assertEqual(datalist, tuple(data)) @@ -207,6 +215,7 @@ def test_role_assignment_list_group(self): verifylist = [ ('user', None), ('group', identity_fakes.group_name), + ('system', None), ('domain', None), ('project', None), ('role', None), @@ -223,6 +232,7 @@ def test_role_assignment_list_group(self): self.role_assignments_mock.list.assert_called_with( domain=None, + system=None, group=self.groups_mock.get(), effective=False, project=None, @@ -238,12 +248,14 @@ def test_role_assignment_list_group(self): identity_fakes.group_id, '', identity_fakes.domain_id, + '', False ), (identity_fakes.role_id, '', identity_fakes.group_id, identity_fakes.project_id, '', + '', False ),) self.assertEqual(datalist, tuple(data)) @@ -271,6 +283,7 @@ def test_role_assignment_list_domain(self): verifylist = [ ('user', None), ('group', None), + ('system', None), ('domain', identity_fakes.domain_name), ('project', None), ('role', None), @@ -287,6 +300,7 @@ def test_role_assignment_list_domain(self): self.role_assignments_mock.list.assert_called_with( domain=self.domains_mock.get(), + system=None, group=None, effective=False, project=None, @@ -302,12 +316,14 @@ def test_role_assignment_list_domain(self): '', '', identity_fakes.domain_id, + '', False ), (identity_fakes.role_id, '', identity_fakes.group_id, '', identity_fakes.domain_id, + '', False ),) self.assertEqual(datalist, tuple(data)) @@ -335,6 +351,7 @@ def test_role_assignment_list_project(self): verifylist = [ ('user', None), ('group', None), + ('system', None), ('domain', None), ('project', identity_fakes.project_name), ('role', None), @@ -351,6 +368,7 @@ def test_role_assignment_list_project(self): self.role_assignments_mock.list.assert_called_with( domain=None, + system=None, group=None, effective=False, project=self.projects_mock.get(), @@ -366,12 +384,14 @@ def test_role_assignment_list_project(self): '', identity_fakes.project_id, '', + '', False ), (identity_fakes.role_id, '', identity_fakes.group_id, identity_fakes.project_id, '', + '', False ),) self.assertEqual(datalist, tuple(data)) @@ -398,6 +418,7 @@ def test_role_assignment_list_def_creds(self): verifylist = [ ('user', None), ('group', None), + ('system', None), ('domain', None), ('project', None), ('role', None), @@ -416,6 +437,7 @@ def test_role_assignment_list_def_creds(self): self.role_assignments_mock.list.assert_called_with( domain=None, + system=None, user=self.users_mock.get(), group=None, project=self.projects_mock.get(), @@ -431,6 +453,7 @@ def test_role_assignment_list_def_creds(self): '', identity_fakes.project_id, '', + '', False ),) self.assertEqual(datalist, tuple(data)) @@ -456,6 +479,7 @@ def test_role_assignment_list_effective(self): verifylist = [ ('user', None), ('group', None), + ('system', None), ('domain', None), ('project', None), ('role', None), @@ -472,6 +496,7 @@ def test_role_assignment_list_effective(self): self.role_assignments_mock.list.assert_called_with( domain=None, + system=None, group=None, effective=True, project=None, @@ -487,12 +512,14 @@ def test_role_assignment_list_effective(self): '', identity_fakes.project_id, '', + '', False ), (identity_fakes.role_id, identity_fakes.user_id, '', '', identity_fakes.domain_id, + '', False ),) self.assertEqual(tuple(data), datalist) @@ -520,6 +547,7 @@ def test_role_assignment_list_inherited(self): verifylist = [ ('user', None), ('group', None), + ('system', None), ('domain', None), ('project', None), ('role', None), @@ -536,6 +564,7 @@ def test_role_assignment_list_inherited(self): self.role_assignments_mock.list.assert_called_with( domain=None, + system=None, group=None, effective=False, project=None, @@ -551,12 +580,14 @@ def test_role_assignment_list_inherited(self): '', identity_fakes.project_id, '', + '', True ), (identity_fakes.role_id, identity_fakes.user_id, '', '', identity_fakes.domain_id, + '', True ),) self.assertEqual(datalist, tuple(data)) @@ -584,6 +615,7 @@ def test_role_assignment_list_include_names(self): verifylist = [ ('user', None), ('group', None), + ('system', None), ('domain', None), ('project', None), ('role', None), @@ -602,6 +634,7 @@ def test_role_assignment_list_include_names(self): self.role_assignments_mock.list.assert_called_with( domain=None, + system=None, group=None, effective=False, project=None, @@ -610,7 +643,9 @@ def test_role_assignment_list_include_names(self): os_inherit_extension_inherited_to=None, include_names=True) - collist = ('Role', 'User', 'Group', 'Project', 'Domain', 'Inherited') + collist = ( + 'Role', 'User', 'Group', 'Project', 'Domain', 'System', 'Inherited' + ) self.assertEqual(columns, collist) datalist1 = (( @@ -620,12 +655,14 @@ def test_role_assignment_list_include_names(self): '@'.join([identity_fakes.project_name, identity_fakes.domain_name]), '', + '', False ), (identity_fakes.role_name, '@'.join([identity_fakes.user_name, identity_fakes.domain_name]), '', '', identity_fakes.domain_name, + '', False ),) self.assertEqual(tuple(data), datalist1) @@ -648,6 +685,7 @@ def test_role_assignment_list_domain_role(self): verifylist = [ ('user', None), ('group', None), + ('system', None), ('domain', None), ('project', None), ('role', identity_fakes.ROLE_2['name']), @@ -664,6 +702,7 @@ def test_role_assignment_list_domain_role(self): self.role_assignments_mock.list.assert_called_with( domain=None, + system=None, user=None, group=None, project=None, @@ -679,6 +718,7 @@ def test_role_assignment_list_domain_role(self): '', '', identity_fakes.domain_id, + '', False ),) self.assertEqual(datalist, tuple(data)) diff --git a/releasenotes/notes/implement-system-scope-4c3c47996f98deac.yaml b/releasenotes/notes/implement-system-scope-4c3c47996f98deac.yaml new file mode 100644 index 0000000000..6ad3d8244d --- /dev/null +++ b/releasenotes/notes/implement-system-scope-4c3c47996f98deac.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Added support for system-scope. This includes support for the ability to + generate system-scoped tokens using ``system_scope: all`` in ``cloud.yaml`` + or ``OS_SYSTEM_SCOPE=all`` in an environment variable. Support is also + included for managing role assignments on the system using ``--system`` + when adding and removing roles. diff --git a/requirements.txt b/requirements.txt index bfee36b687..c5795fd5ed 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,6 +12,6 @@ osc-lib>=1.8.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 python-glanceclient>=2.8.0 # Apache-2.0 -python-keystoneclient>=3.8.0 # Apache-2.0 +python-keystoneclient>=3.15.0 # Apache-2.0 python-novaclient>=9.1.0 # Apache-2.0 python-cinderclient>=3.3.0 # Apache-2.0 From e8c731547d85b1241c7898d2fb77b8d635901dfd Mon Sep 17 00:00:00 2001 From: Harald Jensas Date: Mon, 27 Nov 2017 21:08:26 +0100 Subject: [PATCH 1920/3095] Allow setting network-segment on subnet update To enable the possibility to migrate a non-routed network to a routed network allow updating the segment_id of a subnet. Change-Id: I3ebae2ff28d5d4e5373ebd1f52194f8c52071b88 Partial-Bug: bug/1692490 Depends-On: I1aee29dfb59e9769ec0f1cb1f5d2933bc5dc0dc5 --- doc/source/cli/command-objects/subnet.rst | 8 +++++++ openstackclient/network/v2/subnet.py | 16 +++++++++---- .../tests/unit/network/v2/test_subnet.py | 23 +++++++++++++++++++ ...ubnet-set-segment-id-4440e433b170f9f3.yaml | 5 ++++ 4 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/subnet-set-segment-id-4440e433b170f9f3.yaml diff --git a/doc/source/cli/command-objects/subnet.rst b/doc/source/cli/command-objects/subnet.rst index 0a56ccf18b..73e656d895 100644 --- a/doc/source/cli/command-objects/subnet.rst +++ b/doc/source/cli/command-objects/subnet.rst @@ -265,6 +265,7 @@ Set subnet properties [--dns-nameserver ] [--no-dns-nameserver] [--gateway ] + [--network-segment ] [--host-route destination=,gateway=] [--no-host-route] [--service-type ] @@ -310,6 +311,13 @@ Set subnet properties 'none': This subnet will not use a gateway, e.g.: ``--gateway 192.168.9.1``, ``--gateway none``. +.. option:: --network-segment + + Network segment to associate with this subnet (name or ID). It is only + allowed to set the segment if the current value is `None`, the network + must also have only one segment and only one subnet can exist on the + network. + .. option:: --host-route destination=,gateway= Additional route for this subnet e.g.: diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 2c71e1e085..b5a8b35af8 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -167,6 +167,7 @@ def convert_entries_to_gateway(entries): def _get_attrs(client_manager, parsed_args, is_create=True): attrs = {} + client = client_manager.network if 'name' in parsed_args and parsed_args.name is not None: attrs['name'] = str(parsed_args.name) @@ -179,7 +180,6 @@ def _get_attrs(client_manager, parsed_args, is_create=True): parsed_args.project_domain, ).id attrs['tenant_id'] = project_id - client = client_manager.network attrs['network_id'] = client.find_network(parsed_args.network, ignore_missing=False).id if parsed_args.subnet_pool is not None: @@ -200,10 +200,10 @@ def _get_attrs(client_manager, parsed_args, is_create=True): attrs['ipv6_ra_mode'] = parsed_args.ipv6_ra_mode if parsed_args.ipv6_address_mode is not None: attrs['ipv6_address_mode'] = parsed_args.ipv6_address_mode - if parsed_args.network_segment is not None: - attrs['segment_id'] = client.find_segment( - parsed_args.network_segment, ignore_missing=False).id + if parsed_args.network_segment is not None: + attrs['segment_id'] = client.find_segment( + parsed_args.network_segment, ignore_missing=False).id if 'gateway' in parsed_args and parsed_args.gateway is not None: gateway = parsed_args.gateway.lower() @@ -558,6 +558,14 @@ def get_parser(self, prog_name): "'none': This subnet will not use a gateway, " "e.g.: --gateway 192.168.9.1, --gateway none.") ) + parser.add_argument( + '--network-segment', + metavar='', + help=_("Network segment to associate with this subnet (name or " + "ID). It is only allowed to set the segment if the current " + "value is `None`, the network must also have only one " + "segment and only one subnet can exist on the network.") + ) parser.add_argument( '--description', metavar='', diff --git a/openstackclient/tests/unit/network/v2/test_subnet.py b/openstackclient/tests/unit/network/v2/test_subnet.py index b7f741cd91..f5212c6172 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet.py +++ b/openstackclient/tests/unit/network/v2/test_subnet.py @@ -1074,6 +1074,29 @@ def test_set_with_tags(self): def test_set_with_no_tag(self): self._test_set_tags(with_tags=False) + def test_set_segment(self): + _net = network_fakes.FakeNetwork.create_one_network() + _segment = network_fakes.FakeNetworkSegment.create_one_network_segment( + attrs={'network_id': _net.id}) + _subnet = network_fakes.FakeSubnet.create_one_subnet( + {'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'}], + 'dns_nameservers': ["10.0.0.1"], + 'network_id': _net.id, + 'segment_id': None}) + self.network.find_subnet = mock.Mock(return_value=_subnet) + self.network.find_segment = mock.Mock(return_value=_segment) + arglist = ['--network-segment', _segment.id, _subnet.name] + verifylist = [('network_segment', _segment.id)] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + attrs = {'segment_id': _segment.id} + self.network.update_subnet.assert_called_once_with(_subnet, **attrs) + self.network.update_subnet.assert_called_with(_subnet, **attrs) + self.assertIsNone(result) + class TestShowSubnet(TestSubnet): # The subnets to be shown diff --git a/releasenotes/notes/subnet-set-segment-id-4440e433b170f9f3.yaml b/releasenotes/notes/subnet-set-segment-id-4440e433b170f9f3.yaml new file mode 100644 index 0000000000..55a823188a --- /dev/null +++ b/releasenotes/notes/subnet-set-segment-id-4440e433b170f9f3.yaml @@ -0,0 +1,5 @@ +--- +features: + - Add ``--network-segment`` option to ``subnet set`` command. This + enables the possiblity to set the ``segment_id`` of a subnet on + update. From f904efb037142fee7364eee0e841097942537799 Mon Sep 17 00:00:00 2001 From: Chen Date: Thu, 7 Jun 2018 17:03:32 +0800 Subject: [PATCH 1921/3095] Fix urls in README.rst 1. bug tracking has been moved to storyboard 2. syntax issues 3. remove PyPI downloads since it is no longer maintained https://packaging.python.org/guides/analyzing-pypi-package-downloads/ Change-Id: Ic57c7032af0296d666841021c19e6e56f0b3e89a --- README.rst | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/README.rst b/README.rst index de216f1eb3..4940ffc1a4 100644 --- a/README.rst +++ b/README.rst @@ -15,10 +15,6 @@ OpenStackClient :target: https://pypi.org/project/python-openstackclient/ :alt: Latest Version -.. image:: https://img.shields.io/pypi/dm/python-openstackclient.svg - :target: https://pypi.org/project/python-openstackclient/ - :alt: Downloads - OpenStackClient (aka OSC) is a command-line client for OpenStack that brings the command set for Compute, Identity, Image, Object Store and Block Storage APIs together in a single shell with a uniform command structure. @@ -32,9 +28,9 @@ language to describe operations in OpenStack. * `Blueprints`_ - feature specifications * `Bugs`_ - issue tracking * `Source`_ -* `Developer` - getting started as a developer -* `Contributing` - contributing code -* `Testing` - testing code +* `Developer`_ - getting started as a developer +* `Contributing`_ - contributing code +* `Testing`_ - testing code * IRC: #openstack-sdks on Freenode (irc.freenode.net) * License: Apache 2.0 @@ -42,7 +38,7 @@ language to describe operations in OpenStack. .. _Online Documentation: https://docs.openstack.org/python-openstackclient/latest/ .. _Launchpad project: https://launchpad.net/python-openstackclient .. _Blueprints: https://blueprints.launchpad.net/python-openstackclient -.. _Bugs: https://bugs.launchpad.net/python-openstackclient +.. _Bugs: https://storyboard.openstack.org/#!/project/975 .. _Source: https://git.openstack.org/cgit/openstack/python-openstackclient .. _Developer: https://docs.openstack.org/project-team-guide/project-setup/python.html .. _Contributing: https://docs.openstack.org/infra/manual/developers.html From aefddf3a90feb8b70ba2b70e3119e83a490b2d59 Mon Sep 17 00:00:00 2001 From: Lance Bragstad Date: Thu, 7 Jun 2018 15:57:39 +0000 Subject: [PATCH 1922/3095] Update role document to include system parameter With the recent addition of system scope support in osc, we should add system docs to the role documentation. bp system-scope Change-Id: Id77511c52ff0c36ea845e0f0fbbe3ec14818ee58 --- doc/source/cli/command-objects/role.rst | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/doc/source/cli/command-objects/role.rst b/doc/source/cli/command-objects/role.rst index fe3126c0f7..9819fd1231 100644 --- a/doc/source/cli/command-objects/role.rst +++ b/doc/source/cli/command-objects/role.rst @@ -13,12 +13,21 @@ Add role assignment to a user or group in a project or domain .. code:: bash openstack role add - --domain | --project [--project-domain ] + --system | --domain | --project [--project-domain ] --user [--user-domain ] | --group [--group-domain ] --role-domain --inherited +.. option:: --system + + Include + + System or service to grant authorization to. Currently only ``all`` is + supported which encompasses the entire deployment system. + + .. versionadded:: 3 + .. option:: --domain Include (name or ID) @@ -210,12 +219,21 @@ Remove role assignment from domain/project : user/group .. code:: bash openstack role remove - --domain | --project [--project-domain ] + --system | --domain | --project [--project-domain ] --user [--user-domain ] | --group [--group-domain ] --role-domain --inherited +.. option:: --system + + Include + + System or service to remove authorization from. Currently only ``all`` is + supported which encompasses the entire deployment system. + + .. versionadded:: 3 + .. option:: --domain Include (name or ID) From 181f14319bac0917fcbe5f232cae0603c6939d10 Mon Sep 17 00:00:00 2001 From: Matt Riedemann Date: Fri, 8 Jun 2018 10:13:10 -0400 Subject: [PATCH 1923/3095] Add note about version 2.5 when listing servers using --ip6 The --ip6 filter when listing servers as a non-admin user only applies when also using --os-compute-api-microversion 2.5 or greater. This change simply adds a note about that in the --ip6 option help text. We could probably get more sophisticated by trying to determine if the user has the admin role or not and if not, and using --ip6 without microversion >= 2.5, we could error out, but that seems excessive at this point. Change-Id: I665c64e0bdac04c695fa119a479df43f70b0fa62 Story: #2002184 Task: #20057 --- openstackclient/compute/v2/server.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index c80b5a3c25..c414f7e18e 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -977,7 +977,9 @@ def get_parser(self, prog_name): parser.add_argument( '--ip6', metavar='', - help=_('Regular expression to match IPv6 addresses'), + help=_('Regular expression to match IPv6 addresses. Note ' + 'that this option only applies for non-admin users ' + 'when using ``--os-compute-api-version`` 2.5 or greater.'), ) parser.add_argument( '--name', From 56b346754992adeef18257598c6f840027394377 Mon Sep 17 00:00:00 2001 From: Matt Riedemann Date: Fri, 8 Jun 2018 11:13:40 -0400 Subject: [PATCH 1924/3095] Use Server.to_dict() rather than Server._info There is a to_dict() method on the Server object from python-novaclient which makes a deepcopy of the internal Server._info - use this instead of accessing the _info attribute directly. Also, while in here, fixed a typo in _prep_server_detail. Change-Id: I679b4489c815f8a54368ef6b23b9f77e75b4d0bc --- openstackclient/compute/v2/server.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index c80b5a3c25..88016c0bcf 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -124,13 +124,14 @@ def _prep_server_detail(compute_client, image_client, server): """Prepare the detailed server dict for printing :param compute_client: a compute client instance + :param image_client: an image client instance :param server: a Server resource :rtype: a dict of server details """ - info = server._info.copy() + info = server.to_dict() server = utils.find_resource(compute_client.servers, info['id']) - info.update(server._info) + info.update(server.to_dict()) # Convert the image blob to a name image_info = info.get('image', {}) @@ -178,7 +179,7 @@ def _prep_server_detail(compute_client, image_client, server): if 'tenant_id' in info: info['project_id'] = info.pop('tenant_id') - # Map power state num to meanful string + # Map power state num to meaningful string if 'OS-EXT-STS:power_state' in info: info['OS-EXT-STS:power_state'] = _format_servers_list_power_state( info['OS-EXT-STS:power_state']) @@ -1521,7 +1522,8 @@ def _show_progress(progress): compute_client.servers, parsed_args.server) # If parsed_args.image is not set, default to the currently used one. - image_id = parsed_args.image or server._info.get('image', {}).get('id') + image_id = parsed_args.image or server.to_dict().get( + 'image', {}).get('id') image = utils.find_resource(image_client.images, image_id) server = server.rebuild(image, parsed_args.password) From 752a2db332780ac98c4a4c3a3778b178131a8d59 Mon Sep 17 00:00:00 2001 From: Matt Riedemann Date: Fri, 8 Jun 2018 11:46:29 -0400 Subject: [PATCH 1925/3095] Optimize _prep_server_detail to avoid redundant find_resource When showing a server or doing a rebuild, we already have the latest version of the server so _prep_server_detail getting the server again is an unnecessary performance hit. ShowServer is pretty obvious here. For RebuildServer, the compute API actually refreshes the server before returning it in the response, so the client already gets the latest when the rebuild call returns. The only other usage of _prep_server_detail that does require a refresh is CreateServer since the POST /servers response is a minimal version of the server object. This adds a new refresh kwarg, backward compatible by default, to _prep_server_detail but changes ShowServer and RebuildServer to no longer refresh. Change-Id: Ib1c9c424ed1cafc2dfd8be90af8de8a774bdfbf0 --- openstackclient/compute/v2/server.py | 17 +++++++++++------ .../tests/unit/compute/v2/test_server.py | 8 ++++---- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 88016c0bcf..c2d9cf3af2 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -120,18 +120,21 @@ def func(value): return func -def _prep_server_detail(compute_client, image_client, server): +def _prep_server_detail(compute_client, image_client, server, refresh=True): """Prepare the detailed server dict for printing :param compute_client: a compute client instance :param image_client: an image client instance :param server: a Server resource + :param refresh: Flag indicating if ``server`` is already the latest version + or if it needs to be refreshed, for example when showing + the latest details of a server after creating it. :rtype: a dict of server details """ info = server.to_dict() - - server = utils.find_resource(compute_client.servers, info['id']) - info.update(server.to_dict()) + if refresh: + server = utils.find_resource(compute_client.servers, info['id']) + info.update(server.to_dict()) # Convert the image blob to a name image_info = info.get('image', {}) @@ -1540,7 +1543,8 @@ def _show_progress(progress): self.app.stdout.write(_('Error rebuilding server\n')) raise SystemExit - details = _prep_server_detail(compute_client, image_client, server) + details = _prep_server_detail(compute_client, image_client, server, + refresh=False) return zip(*sorted(six.iteritems(details))) @@ -2021,7 +2025,8 @@ def take_action(self, parsed_args): return ({}, {}) else: data = _prep_server_detail(compute_client, - self.app.client_manager.image, server) + self.app.client_manager.image, server, + refresh=False) return zip(*sorted(six.iteritems(data))) diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index d242dc2616..61c81132a0 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -2331,17 +2331,17 @@ def setUp(self): self.images_mock.get.return_value = self.image # Fake the rebuilt new server. - new_server = compute_fakes.FakeServer.create_one_server() - - # Fake the server to be rebuilt. The IDs of them should be the same. attrs = { - 'id': new_server.id, 'image': { 'id': self.image.id }, 'networks': {}, 'adminPass': 'passw0rd', } + new_server = compute_fakes.FakeServer.create_one_server(attrs=attrs) + + # Fake the server to be rebuilt. The IDs of them should be the same. + attrs['id'] = new_server.id methods = { 'rebuild': new_server, } From da7572a5ff9f54dbab4f8328632c562a2816a4fb Mon Sep 17 00:00:00 2001 From: Matt Riedemann Date: Fri, 8 Jun 2018 14:43:41 -0400 Subject: [PATCH 1926/3095] Fix server show for microversion 2.47 Compute API version 2.47 embeds the server's internal flavor in the response. The original flavor id is not preserved since it could have changed if the flavor was deleted and re-created after the server was created, which was the dreaded Horizon "Edit Flavor" issue. So the flavor dict in the server response is a dict of information about the flavor representing the server "right now" excluding the id. The original flavor name is shown though along with the ram/disk/vcpu etc information. The server list command has a similar issue which will be fixed in a follow up change. Change-Id: I1a92999758006d02567c542b6be8902a049899cc Task: 13864 Story: 1751104 --- openstackclient/compute/v2/server.py | 26 +++++++++++++----- .../tests/unit/compute/v2/test_server.py | 27 +++++++++++++++++++ ...104-compute-api-2.47-4bfa21cfaa13f408.yaml | 6 +++++ 3 files changed, 52 insertions(+), 7 deletions(-) create mode 100644 releasenotes/notes/bug-1751104-compute-api-2.47-4bfa21cfaa13f408.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index c2d9cf3af2..a7b99306d2 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -148,12 +148,18 @@ def _prep_server_detail(compute_client, image_client, server, refresh=True): # Convert the flavor blob to a name flavor_info = info.get('flavor', {}) - flavor_id = flavor_info.get('id', '') - try: - flavor = utils.find_resource(compute_client.flavors, flavor_id) - info['flavor'] = "%s (%s)" % (flavor.name, flavor_id) - except Exception: - info['flavor'] = flavor_id + # Microversion 2.47 puts the embedded flavor into the server response + # body but omits the id, so if not present we just expose the flavor + # dict in the server output. + if 'id' in flavor_info: + flavor_id = flavor_info.get('id', '') + try: + flavor = utils.find_resource(compute_client.flavors, flavor_id) + info['flavor'] = "%s (%s)" % (flavor.name, flavor_id) + except Exception: + info['flavor'] = flavor_id + else: + info['flavor'] = utils.format_dict(flavor_info) if 'os-extended-volumes:volumes_attached' in info: info.update( @@ -1257,6 +1263,10 @@ def take_action(self, parsed_args): s.flavor_name = flavor.name s.flavor_id = s.flavor['id'] else: + # TODO(mriedem): Fix this for microversion >= 2.47 where the + # flavor is embedded in the server response without the id. + # We likely need to drop the Flavor ID column in that case if + # --long is specified. s.flavor_name = '' s.flavor_id = '' @@ -1994,7 +2004,9 @@ def take_action(self, parsed_args): class ShowServer(command.ShowOne): - _description = _("Show server details") + _description = _( + "Show server details. Specify ``--os-compute-api-version 2.47`` " + "or higher to see the embedded flavor information for the server.") def get_parser(self, prog_name): parser = super(ShowServer, self).get_parser(prog_name) diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 61c81132a0..a53c6c8193 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -3217,6 +3217,33 @@ def test_show(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_show_embedded_flavor(self): + # Tests using --os-compute-api-version >= 2.47 where the flavor + # details are embedded in the server response body excluding the id. + arglist = [ + self.server.name, + ] + verifylist = [ + ('diagnostics', False), + ('server', self.server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.server.info['flavor'] = { + 'ephemeral': 0, + 'ram': 512, + 'original_name': 'm1.tiny', + 'vcpus': 1, + 'extra_specs': {}, + 'swap': 0, + 'disk': 1 + } + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual(self.columns, columns) + # Since the flavor details are in a dict we can't be sure of the + # ordering so just assert that one of the keys is in the output. + self.assertIn('original_name', data[2]) + def test_show_diagnostics(self): arglist = [ '--diagnostics', diff --git a/releasenotes/notes/bug-1751104-compute-api-2.47-4bfa21cfaa13f408.yaml b/releasenotes/notes/bug-1751104-compute-api-2.47-4bfa21cfaa13f408.yaml new file mode 100644 index 0000000000..ef89c785ef --- /dev/null +++ b/releasenotes/notes/bug-1751104-compute-api-2.47-4bfa21cfaa13f408.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + The ``openstack server show`` command will now properly show the server's + flavor information when using ``--os-compute-api-version 2.47`` or higher. + See: https://storyboard.openstack.org/#!/story/1751104 From 577e2e850c58cfd9e8d0731508db5be9fec407f1 Mon Sep 17 00:00:00 2001 From: Matt Riedemann Date: Fri, 8 Jun 2018 16:37:26 -0400 Subject: [PATCH 1927/3095] Mention 2.51 in help for openstack server event show With the 2.51 compute API microversion, non-admin users can also see event details for a given request. This change mentions that in the help text for "openstack server event show". While in here, change the _info private attribute access to the to_dict() usage. Change-Id: I5fd487b17c4b85bd7e619112ad262ffdd3a940c8 Task: 21199 Story: 2002193 --- openstackclient/compute/v2/server_event.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openstackclient/compute/v2/server_event.py b/openstackclient/compute/v2/server_event.py index d8fbda0f2d..c7d2e2e385 100644 --- a/openstackclient/compute/v2/server_event.py +++ b/openstackclient/compute/v2/server_event.py @@ -91,7 +91,9 @@ def take_action(self, parsed_args): class ShowServerEvent(command.ShowOne): - _description = _("Show server event details") + _description = _( + "Show server event details. Specify ``--os-compute-api-version 2.51`` " + "or higher to show events for non-admin users.") def get_parser(self, prog_name): parser = super(ShowServerEvent, self).get_parser(prog_name) @@ -114,4 +116,4 @@ def take_action(self, parsed_args): action_detail = compute_client.instance_action.get( server_id, parsed_args.request_id) - return zip(*sorted(six.iteritems(action_detail._info))) + return zip(*sorted(six.iteritems(action_detail.to_dict()))) From 71f138b172e95515b9d54eee9b3e318df3381791 Mon Sep 17 00:00:00 2001 From: Yang Youseok Date: Wed, 27 Dec 2017 18:34:11 +0900 Subject: [PATCH 1928/3095] Fix RuntimeError when showing project which has extra properties If you use python3, items() returns iterator which is not allowed to remove item during iteration. Fix to iterate by copied list. Change-Id: I64c037d04e2b127d8f19f56cab65122af89a7200 Closes-Bug: 1740232 --- openstackclient/identity/v2_0/project.py | 2 +- releasenotes/notes/bug-1740232-91ad72c2ac165f35.yaml | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/bug-1740232-91ad72c2ac165f35.yaml diff --git a/openstackclient/identity/v2_0/project.py b/openstackclient/identity/v2_0/project.py index 04d422ecdb..9bb5fc4d08 100644 --- a/openstackclient/identity/v2_0/project.py +++ b/openstackclient/identity/v2_0/project.py @@ -289,7 +289,7 @@ def take_action(self, parsed_args): # the API has and handle the extra top level properties. reserved = ('name', 'id', 'enabled', 'description') properties = {} - for k, v in info.items(): + for k, v in list(info.items()): if k not in reserved: # If a key is not in `reserved` it's a property, pop it info.pop(k) diff --git a/releasenotes/notes/bug-1740232-91ad72c2ac165f35.yaml b/releasenotes/notes/bug-1740232-91ad72c2ac165f35.yaml new file mode 100644 index 0000000000..fb9ad7be04 --- /dev/null +++ b/releasenotes/notes/bug-1740232-91ad72c2ac165f35.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Fix RuntimeError in ``project show`` command running under Python 3. + [Bug `1740232 `_] From aaed4b315fa1e86b1ddf73201b5350dbdb4a660e Mon Sep 17 00:00:00 2001 From: Dongcan Ye Date: Sat, 24 Feb 2018 09:13:48 +0000 Subject: [PATCH 1929/3095] Network: Add tag support for security group Change-Id: Icccb23429913724c6a8bd15d4737672b47a5f13a Closes-Bug: #1750983 --- .../cli/command-objects/security-group.rst | 74 ++++++++ openstackclient/network/v2/security_group.py | 43 ++++- .../tests/unit/network/v2/fakes.py | 1 + .../network/v2/test_security_group_network.py | 174 +++++++++++++++++- .../notes/bug-1750983-420945d6c0afb509.yaml | 8 + setup.cfg | 1 + 6 files changed, 295 insertions(+), 6 deletions(-) create mode 100644 releasenotes/notes/bug-1750983-420945d6c0afb509.yaml diff --git a/doc/source/cli/command-objects/security-group.rst b/doc/source/cli/command-objects/security-group.rst index a95a96f49f..403e5fc08f 100644 --- a/doc/source/cli/command-objects/security-group.rst +++ b/doc/source/cli/command-objects/security-group.rst @@ -19,6 +19,7 @@ Create a new security group openstack security group create [--description ] [--project [--project-domain ]] + [--tag | --no-tag] .. option:: --description @@ -38,6 +39,18 @@ Create a new security group *Network version 2 only* +.. option:: --tag + + Tag to be added to the security group (repeat option to set multiple tags) + + *Network version 2 only* + +.. option:: --no-tag + + No tags associated with the security group + + *Network version 2 only* + .. describe:: New security group name @@ -68,6 +81,8 @@ List security groups openstack security group list [--all-projects] [--project [--project-domain ]] + [--tags [,,...]] [--any-tags [,,...]] + [--not-tags [,,...]] [--not-any-tags [,,...]] .. option:: --all-projects @@ -89,6 +104,30 @@ List security groups *Network version 2 only* +.. option:: --tags [,,...] + + List security groups which have all given tag(s) + + *Network version 2 only* + +.. option:: --any-tags [,,...] + + List security groups which have any given tag(s) + + *Network version 2 only* + +.. option:: --not-tags [,,...] + + Exclude security groups which have all given tag(s) + + *Network version 2 only* + +.. option:: --not-any-tags [,,...] + + Exclude security groups which have any given tag(s) + + *Network version 2 only* + security group set ------------------ @@ -100,6 +139,7 @@ Set security group properties openstack security group set [--name ] [--description ] + [--tag ] [--no-tag] .. option:: --name @@ -110,6 +150,15 @@ Set security group properties New security group description +.. option:: --tag + + Tag to be added to the security group (repeat option to set multiple tags) + +.. option:: --no-tag + + Clear tags associated with the security group. Specify both --tag + and --no-tag to overwrite current tags + .. describe:: Security group to modify (name or ID) @@ -128,3 +177,28 @@ Display security group details .. describe:: Security group to display (name or ID) + +security group unset +-------------------- + +Unset security group properties + +.. program:: security group unset +.. code:: bash + + openstack security group unset + [--tag | --all-tag] + + +.. option:: --tag + + Tag to be removed from the security group + (repeat option to remove multiple tags) + +.. option:: --all-tag + + Clear all tags associated with the security group + +.. describe:: + + Security group to modify (name or ID) diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py index 75af3587e0..ed6c8d7c24 100644 --- a/openstackclient/network/v2/security_group.py +++ b/openstackclient/network/v2/security_group.py @@ -15,6 +15,7 @@ import argparse +from osc_lib.command import command from osc_lib import utils import six @@ -23,6 +24,7 @@ from openstackclient.network import common from openstackclient.network import sdk_utils from openstackclient.network import utils as network_utils +from openstackclient.network.v2 import _tag def _format_network_security_group_rules(sg_rules): @@ -106,6 +108,7 @@ def update_parser_network(self, parser): help=_("Owner's project (name or ID)") ) identity_common.add_project_domain_option_to_parser(parser) + _tag.add_tag_option_to_parser_for_create(parser, _('security group')) return parser def _get_description(self, parsed_args): @@ -130,6 +133,8 @@ def take_action_network(self, client, parsed_args): # Create the security group and display the results. obj = client.create_security_group(**attrs) + # tags cannot be set when created, so tags need to be set later. + _tag.update_tags_for_set(client, obj, parsed_args) display_columns, property_columns = _get_columns(obj) data = utils.get_item_properties( obj, @@ -198,6 +203,7 @@ def update_parser_network(self, parser): "(name or ID)") ) identity_common.add_project_domain_option_to_parser(parser) + _tag.add_tag_filtering_option_to_parser(parser, _('security group')) return parser def update_parser_compute(self, parser): @@ -220,19 +226,23 @@ def take_action_network(self, client, parsed_args): ).id filters['tenant_id'] = project_id filters['project_id'] = project_id + + _tag.get_tag_filtering_args(parsed_args, filters) data = client.security_groups(**filters) columns = ( "ID", "Name", "Description", - "Project ID" + "Project ID", + "tags" ) column_headers = ( "ID", "Name", "Description", - "Project" + "Project", + "Tags" ) return (column_headers, (utils.get_item_properties( @@ -282,6 +292,10 @@ def update_parser_common(self, parser): ) return parser + def update_parser_network(self, parser): + _tag.add_tag_option_to_parser_for_set(parser, _('security group')) + return parser + def take_action_network(self, client, parsed_args): obj = client.find_security_group(parsed_args.group, ignore_missing=False) @@ -295,6 +309,9 @@ def take_action_network(self, client, parsed_args): # the update. client.update_security_group(obj, **attrs) + # tags is a subresource and it needs to be updated separately. + _tag.update_tags_for_set(client, obj, parsed_args) + def take_action_compute(self, client, parsed_args): data = client.api.security_group_find(parsed_args.group) @@ -344,3 +361,25 @@ def take_action_compute(self, client, parsed_args): formatters=_formatters_compute ) return (display_columns, data) + + +class UnsetSecurityGroup(command.Command): + _description = _("Unset security group properties") + + def get_parser(self, prog_name): + parser = super(UnsetSecurityGroup, self).get_parser(prog_name) + parser.add_argument( + 'group', + metavar="", + help=_("Security group to modify (name or ID)") + ) + _tag.add_tag_option_to_parser_for_unset(parser, _('security group')) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.find_security_group(parsed_args.group, + ignore_missing=False) + + # tags is a subresource and it needs to be updated separately. + _tag.update_tags_for_unset(client, obj, parsed_args) diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index a77cab8b5f..e69df88adf 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -1131,6 +1131,7 @@ def create_one_security_group(attrs=None): 'description': 'security-group-description-' + uuid.uuid4().hex, 'project_id': 'project-id-' + uuid.uuid4().hex, 'security_group_rules': [], + 'tags': [] } # Overwrite default attributes. diff --git a/openstackclient/tests/unit/network/v2/test_security_group_network.py b/openstackclient/tests/unit/network/v2/test_security_group_network.py index 35b7e366d6..83208287cc 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_network.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_network.py @@ -40,8 +40,8 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork): project = identity_fakes.FakeProject.create_one_project() domain = identity_fakes.FakeDomain.create_one_domain() # The security group to be created. - _security_group = \ - network_fakes.FakeSecurityGroup.create_one_security_group() + _security_group = ( + network_fakes.FakeSecurityGroup.create_one_security_group()) columns = ( 'description', @@ -49,6 +49,7 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork): 'name', 'project_id', 'rules', + 'tags', ) data = ( @@ -57,6 +58,7 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork): _security_group.name, _security_group.project_id, '', + _security_group.tags, ) def setUp(self): @@ -67,6 +69,7 @@ def setUp(self): self.projects_mock.get.return_value = self.project self.domains_mock.get.return_value = self.domain + self.network.set_tags = mock.Mock(return_value=None) # Get the command object to test self.cmd = security_group.CreateSecurityGroup(self.app, self.namespace) @@ -118,6 +121,43 @@ def test_create_all_options(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def _test_create_with_tag(self, add_tags=True): + arglist = [self._security_group.name] + if add_tags: + arglist += ['--tag', 'red', '--tag', 'blue'] + else: + arglist += ['--no-tag'] + + verifylist = [ + ('name', self._security_group.name), + ] + if add_tags: + verifylist.append(('tags', ['red', 'blue'])) + else: + verifylist.append(('no_tag', True)) + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_security_group.assert_called_once_with(**{ + 'description': self._security_group.name, + 'name': self._security_group.name, + }) + if add_tags: + self.network.set_tags.assert_called_once_with( + self._security_group, + tests_utils.CompareBySet(['red', 'blue'])) + else: + self.assertFalse(self.network.set_tags.called) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_with_tags(self): + self._test_create_with_tag(add_tags=True) + + def test_create_with_no_tag(self): + self._test_create_with_tag(add_tags=False) + class TestDeleteSecurityGroupNetwork(TestSecurityGroupNetwork): @@ -214,6 +254,7 @@ class TestListSecurityGroupNetwork(TestSecurityGroupNetwork): 'Name', 'Description', 'Project', + 'Tags', ) data = [] @@ -223,6 +264,7 @@ class TestListSecurityGroupNetwork(TestSecurityGroupNetwork): grp.name, grp.description, grp.project_id, + grp.tags, )) def setUp(self): @@ -300,12 +342,38 @@ def test_security_group_list_project_domain(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + def test_list_with_tag_options(self): + arglist = [ + '--tags', 'red,blue', + '--any-tags', 'red,green', + '--not-tags', 'orange,yellow', + '--not-any-tags', 'black,white', + ] + verifylist = [ + ('tags', ['red', 'blue']), + ('any_tags', ['red', 'green']), + ('not_tags', ['orange', 'yellow']), + ('not_any_tags', ['black', 'white']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.security_groups.assert_called_once_with( + **{'tags': 'red,blue', + 'any_tags': 'red,green', + 'not_tags': 'orange,yellow', + 'not_any_tags': 'black,white'} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + class TestSetSecurityGroupNetwork(TestSecurityGroupNetwork): # The security group to be set. - _security_group = \ - network_fakes.FakeSecurityGroup.create_one_security_group() + _security_group = ( + network_fakes.FakeSecurityGroup.create_one_security_group( + attrs={'tags': ['green', 'red']})) def setUp(self): super(TestSetSecurityGroupNetwork, self).setUp() @@ -314,6 +382,7 @@ def setUp(self): self.network.find_security_group = mock.Mock( return_value=self._security_group) + self.network.set_tags = mock.Mock(return_value=None) # Get the command object to test self.cmd = security_group.SetSecurityGroup(self.app, self.namespace) @@ -366,6 +435,34 @@ def test_set_all_options(self): ) self.assertIsNone(result) + def _test_set_tags(self, with_tags=True): + if with_tags: + arglist = ['--tag', 'red', '--tag', 'blue'] + verifylist = [('tags', ['red', 'blue'])] + expected_args = ['red', 'blue', 'green'] + else: + arglist = ['--no-tag'] + verifylist = [('no_tag', True)] + expected_args = [] + arglist.append(self._security_group.name) + verifylist.append( + ('group', self._security_group.name)) + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.assertTrue(self.network.update_security_group.called) + self.network.set_tags.assert_called_once_with( + self._security_group, + tests_utils.CompareBySet(expected_args)) + self.assertIsNone(result) + + def test_set_with_tags(self): + self._test_set_tags(with_tags=True) + + def test_set_with_no_tag(self): + self._test_set_tags(with_tags=False) + class TestShowSecurityGroupNetwork(TestSecurityGroupNetwork): @@ -385,6 +482,7 @@ class TestShowSecurityGroupNetwork(TestSecurityGroupNetwork): 'name', 'project_id', 'rules', + 'tags', ) data = ( @@ -394,6 +492,7 @@ class TestShowSecurityGroupNetwork(TestSecurityGroupNetwork): _security_group.project_id, security_group._format_network_security_group_rules( [_security_group_rule._info]), + _security_group.tags, ) def setUp(self): @@ -424,3 +523,70 @@ def test_show_all_options(self): self._security_group.id, ignore_missing=False) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + + +class TestUnsetSecurityGroupNetwork(TestSecurityGroupNetwork): + + # The security group to be unset. + _security_group = ( + network_fakes.FakeSecurityGroup.create_one_security_group( + attrs={'tags': ['green', 'red']})) + + def setUp(self): + super(TestUnsetSecurityGroupNetwork, self).setUp() + + self.network.update_security_group = mock.Mock(return_value=None) + + self.network.find_security_group = mock.Mock( + return_value=self._security_group) + self.network.set_tags = mock.Mock(return_value=None) + + # Get the command object to test + self.cmd = security_group.UnsetSecurityGroup(self.app, self.namespace) + + def test_set_no_options(self): + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, [], []) + + def test_set_no_updates(self): + arglist = [ + self._security_group.name, + ] + verifylist = [ + ('group', self._security_group.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.assertFalse(self.network.update_security_group.called) + self.assertFalse(self.network.set_tags.called) + self.assertIsNone(result) + + def _test_unset_tags(self, with_tags=True): + if with_tags: + arglist = ['--tag', 'red', '--tag', 'blue'] + verifylist = [('tags', ['red', 'blue'])] + expected_args = ['green'] + else: + arglist = ['--all-tag'] + verifylist = [('all_tag', True)] + expected_args = [] + arglist.append(self._security_group.name) + verifylist.append( + ('group', self._security_group.name)) + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.assertFalse(self.network.update_security_group.called) + self.network.set_tags.assert_called_once_with( + self._security_group, + tests_utils.CompareBySet(expected_args)) + self.assertIsNone(result) + + def test_unset_with_tags(self): + self._test_unset_tags(with_tags=True) + + def test_unset_with_all_tag(self): + self._test_unset_tags(with_tags=False) diff --git a/releasenotes/notes/bug-1750983-420945d6c0afb509.yaml b/releasenotes/notes/bug-1750983-420945d6c0afb509.yaml new file mode 100644 index 0000000000..2c37d88fad --- /dev/null +++ b/releasenotes/notes/bug-1750983-420945d6c0afb509.yaml @@ -0,0 +1,8 @@ +--- +fixes: + - | + Add supports tagging for Network security group. + Add ``tag``, ``no-tag`` to ``security group create`` and ``security group set`` commands. + Add ``tags``, ``any-tags``, ``not-tags``, ``not-any-tags`` to ``security group list`` command. + Add ``tag`` and ``all-tag`` to ``security group unset`` command. + [Bug `1750983 `_] diff --git a/setup.cfg b/setup.cfg index 63bfdafb13..37592f495f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -467,6 +467,7 @@ openstack.network.v2 = security_group_list = openstackclient.network.v2.security_group:ListSecurityGroup security_group_set = openstackclient.network.v2.security_group:SetSecurityGroup security_group_show = openstackclient.network.v2.security_group:ShowSecurityGroup + security_group_unset = openstackclient.network.v2.security_group:UnsetSecurityGroup security_group_rule_create = openstackclient.network.v2.security_group_rule:CreateSecurityGroupRule security_group_rule_delete = openstackclient.network.v2.security_group_rule:DeleteSecurityGroupRule From 402c9a21b347509520be206e28ee7d0ef4004b92 Mon Sep 17 00:00:00 2001 From: yanpuqing Date: Wed, 13 Jun 2018 05:21:53 -0400 Subject: [PATCH 1930/3095] Do not require port argument when updating floating IP When setting floating ip other properties, port argument is force to use. The patch modifies the command, when setting floating ip other properties, like tags, no need port argument. Change-Id: I908712c8913f32d3dd5fdfefe7347277d72f66de Story: 1751431 Task: 13865 --- .../cli/command-objects/floating-ip.rst | 6 +- openstackclient/network/v2/floating_ip.py | 11 ++-- .../network/v2/test_floating_ip_network.py | 57 +++++++++++++++++-- 3 files changed, 60 insertions(+), 14 deletions(-) diff --git a/doc/source/cli/command-objects/floating-ip.rst b/doc/source/cli/command-objects/floating-ip.rst index c0ba7ebf35..e3e50adcb7 100644 --- a/doc/source/cli/command-objects/floating-ip.rst +++ b/doc/source/cli/command-objects/floating-ip.rst @@ -198,7 +198,7 @@ Set floating IP properties .. code:: bash openstack floating ip set - --port + [--port ] [--fixed-ip-address ] [--qos-policy | --no-qos-policy] [--tag ] [--no-tag] @@ -257,8 +257,8 @@ Unset floating IP Properties .. code:: bash openstack floating ip unset - --port - --qos-policy + [--port] + [--qos-policy] [--tag | --all-tag] diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index f51baed578..2f0e7403ff 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -416,11 +416,10 @@ def get_parser(self, prog_name): parser.add_argument( 'floating_ip', metavar='', - help=_("Floating IP to associate (IP address or ID)")) + help=_("Floating IP to modify (IP address or ID)")) parser.add_argument( '--port', metavar='', - required=True, help=_("Associate the floating IP with port (name or ID)")), parser.add_argument( '--fixed-ip-address', @@ -452,9 +451,11 @@ def take_action(self, parsed_args): parsed_args.floating_ip, ignore_missing=False, ) - port = client.find_port(parsed_args.port, - ignore_missing=False) - attrs['port_id'] = port.id + if parsed_args.port: + port = client.find_port(parsed_args.port, + ignore_missing=False) + attrs['port_id'] = port.id + if parsed_args.fixed_ip_address: attrs['fixed_ip_address'] = parsed_args.fixed_ip_address diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip_network.py b/openstackclient/tests/unit/network/v2/test_floating_ip_network.py index 65d873770a..822d3ae879 100644 --- a/openstackclient/tests/unit/network/v2/test_floating_ip_network.py +++ b/openstackclient/tests/unit/network/v2/test_floating_ip_network.py @@ -747,6 +747,31 @@ def test_fixed_ip_option(self): self.network.update_ip.assert_called_once_with( self.floating_ip, **attrs) + def test_qos_policy_option(self): + qos_policy = network_fakes.FakeNetworkQosPolicy.create_one_qos_policy() + self.network.find_qos_policy = mock.Mock(return_value=qos_policy) + arglist = [ + "--qos-policy", qos_policy.id, + self.floating_ip.id, + ] + verifylist = [ + ('qos_policy', qos_policy.id), + ('floating_ip', self.floating_ip.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + attrs = { + 'qos_policy_id': qos_policy.id, + } + self.network.find_ip.assert_called_once_with( + self.floating_ip.id, + ignore_missing=False, + ) + self.network.update_ip.assert_called_once_with( + self.floating_ip, **attrs) + def test_port_and_qos_policy_option(self): qos_policy = network_fakes.FakeNetworkQosPolicy.create_one_qos_policy() self.network.find_qos_policy = mock.Mock(return_value=qos_policy) @@ -775,6 +800,29 @@ def test_port_and_qos_policy_option(self): self.network.update_ip.assert_called_once_with( self.floating_ip, **attrs) + def test_no_qos_policy_option(self): + arglist = [ + "--no-qos-policy", + self.floating_ip.id, + ] + verifylist = [ + ('no_qos_policy', True), + ('floating_ip', self.floating_ip.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + attrs = { + 'qos_policy_id': None, + } + self.network.find_ip.assert_called_once_with( + self.floating_ip.id, + ignore_missing=False, + ) + self.network.update_ip.assert_called_once_with( + self.floating_ip, **attrs) + def test_port_and_no_qos_policy_option(self): arglist = [ "--no-qos-policy", @@ -810,16 +858,13 @@ def _test_set_tags(self, with_tags=True): arglist = ['--no-tag'] verifylist = [('no_tag', True)] expected_args = [] - arglist.extend(['--port', self.floating_ip.port_id, - self.floating_ip.id]) - verifylist.extend([ - ('port', self.floating_ip.port_id), - ('floating_ip', self.floating_ip.id)]) + arglist.extend([self.floating_ip.id]) + verifylist.extend([('floating_ip', self.floating_ip.id)]) parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.assertTrue(self.network.update_ip.called) + self.assertFalse(self.network.update_ip.called) self.network.set_tags.assert_called_once_with( self.floating_ip, tests_utils.CompareBySet(expected_args)) From 5bb5585aa98e17e22963996c02bd7bd688d7871e Mon Sep 17 00:00:00 2001 From: Huang Cheng Date: Sat, 3 Feb 2018 10:48:04 +0800 Subject: [PATCH 1931/3095] Fix subnet host_routes error When updating subnet with "no-host-route" option, set host_routes to an empty list as neutron_lib.api.validators expected. Change-Id: I6fe039793d813758429c7a104fd40172b4f8122b Closes-Bug: #1747101 --- openstackclient/network/v2/subnet.py | 2 +- .../tests/unit/network/v2/test_subnet.py | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index b5a8b35af8..9c56186f98 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -589,7 +589,7 @@ def take_action(self, parsed_args): if not parsed_args.no_host_route: attrs['host_routes'] += obj.host_routes elif parsed_args.no_host_route: - attrs['host_routes'] = '' + attrs['host_routes'] = [] if 'allocation_pools' in attrs: if not parsed_args.no_allocation_pool: attrs['allocation_pools'] += obj.allocation_pools diff --git a/openstackclient/tests/unit/network/v2/test_subnet.py b/openstackclient/tests/unit/network/v2/test_subnet.py index f5212c6172..39cb4f537d 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet.py +++ b/openstackclient/tests/unit/network/v2/test_subnet.py @@ -1046,6 +1046,36 @@ def test_overwrite_options(self): _testsubnet, **attrs) self.assertIsNone(result) + def test_clear_options(self): + _testsubnet = network_fakes.FakeSubnet.create_one_subnet( + {'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'}], + 'dns_nameservers': ['10.0.0.1'], }) + self.network.find_subnet = mock.Mock(return_value=_testsubnet) + arglist = [ + '--no-host-route', + '--no-allocation-pool', + '--no-dns-nameservers', + _testsubnet.name, + ] + verifylist = [ + ('no_dns_nameservers', True), + ('no_host_route', True), + ('no_allocation_pool', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + attrs = { + 'host_routes': [], + 'allocation_pools': [], + 'dns_nameservers': [], + } + self.network.update_subnet.assert_called_once_with( + _testsubnet, **attrs) + self.assertIsNone(result) + def _test_set_tags(self, with_tags=True): if with_tags: arglist = ['--tag', 'red', '--tag', 'blue'] From 956eabe96783727d6e86e3896cacf89fb893e0f5 Mon Sep 17 00:00:00 2001 From: He Jie Xu Date: Tue, 19 Jun 2018 12:59:03 +0000 Subject: [PATCH 1932/3095] compute: limit the service's force down command above 2.10 The force down action is added in Microversion 2.11, we should limit the command only can be executed when the microversion is above 2.10. Change-Id: I0a87e02e71ff025d30181fc17ebcd003a590f110 --- openstackclient/compute/v2/service.py | 26 ++++++++++++------- .../tests/unit/compute/v2/test_service.py | 10 ++++++- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index 7331d29d01..18e6d9d95e 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.py @@ -17,6 +17,7 @@ import logging +from novaclient import api_versions from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -192,18 +193,23 @@ def take_action(self, parsed_args): result += 1 force_down = None - try: - if parsed_args.down: - force_down = True - if parsed_args.up: - force_down = False - if force_down is not None: + if parsed_args.down: + force_down = True + if parsed_args.up: + force_down = False + if force_down is not None: + if compute_client.api_version < api_versions.APIVersion( + '2.11'): + msg = _('--os-compute-api-version 2.11 or later is ' + 'required') + raise exceptions.CommandError(msg) + try: cs.force_down(parsed_args.host, parsed_args.service, force_down=force_down) - except Exception: - state = "down" if force_down else "up" - LOG.error("Failed to set service state to %s", state) - result += 1 + except Exception: + state = "down" if force_down else "up" + LOG.error("Failed to set service state to %s", state) + result += 1 if result > 0: msg = _("Compute service %(service)s of host %(host)s failed to " diff --git a/openstackclient/tests/unit/compute/v2/test_service.py b/openstackclient/tests/unit/compute/v2/test_service.py index 8403efc9c8..bd29912341 100644 --- a/openstackclient/tests/unit/compute/v2/test_service.py +++ b/openstackclient/tests/unit/compute/v2/test_service.py @@ -15,7 +15,7 @@ import mock from mock import call - +from novaclient import api_versions from osc_lib import exceptions from openstackclient.compute.v2 import service @@ -340,6 +340,8 @@ def test_service_set_state_up(self): ('service', self.service.binary), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.11') result = self.cmd.take_action(parsed_args) self.service_mock.force_down.assert_called_once_with( self.service.host, self.service.binary, force_down=False) @@ -359,6 +361,8 @@ def test_service_set_state_down(self): ('service', self.service.binary), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.11') result = self.cmd.take_action(parsed_args) self.service_mock.force_down.assert_called_once_with( self.service.host, self.service.binary, force_down=True) @@ -380,6 +384,8 @@ def test_service_set_enable_and_state_down(self): ('service', self.service.binary), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.11') result = self.cmd.take_action(parsed_args) self.service_mock.enable.assert_called_once_with( self.service.host, self.service.binary) @@ -402,6 +408,8 @@ def test_service_set_enable_and_state_down_with_exception(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.11') with mock.patch.object(self.service_mock, 'enable', side_effect=Exception()): self.assertRaises(exceptions.CommandError, From 4a68ba625ce39de6e6d260ff06e0b0d88512f794 Mon Sep 17 00:00:00 2001 From: tianhui Date: Thu, 14 Jun 2018 18:20:04 +0800 Subject: [PATCH 1933/3095] Compute: Add description support for flavor Co-Authored-By: Fan Zhang Change-Id: I0dc80bee3ba6ff4ec8cc3fc113b6de7807e0bf2a Story: 2002196 Task: 21681 --- doc/source/cli/command-objects/flavor.rst | 10 ++ openstackclient/compute/v2/flavor.py | 28 +++- .../tests/unit/compute/v2/fakes.py | 1 + .../tests/unit/compute/v2/test_flavor.py | 136 +++++++++++++++++- ...avor-add-description-b618abd4a7fb6545.yaml | 6 + 5 files changed, 177 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/flavor-add-description-b618abd4a7fb6545.yaml diff --git a/doc/source/cli/command-objects/flavor.rst b/doc/source/cli/command-objects/flavor.rst index 1cbd2df3bb..2d946de308 100644 --- a/doc/source/cli/command-objects/flavor.rst +++ b/doc/source/cli/command-objects/flavor.rst @@ -24,6 +24,7 @@ Create new flavor [--property [...] ] [--project ] [--project-domain ] + [--description ] .. option:: --id @@ -76,6 +77,10 @@ Create new flavor Domain the project belongs to (name or ID). This can be used in case collisions between project names exist. +.. option:: --description + + Description to add for this flavor + .. _flavor_create-flavor-name: .. describe:: @@ -148,6 +153,7 @@ Set flavor properties [--property [...] ] [--project ] [--project-domain ] + [--description ] .. option:: --property @@ -168,6 +174,10 @@ Set flavor properties Remove all properties from this flavor (specify both --no-property and --property to remove the current properties before setting new properties.) +.. option:: --description + + Set description to this flavor + .. describe:: Flavor to modify (name or ID) diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 0f5dd7421e..2cc5f1e891 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -17,6 +17,7 @@ import logging +from novaclient import api_versions from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -134,6 +135,12 @@ def get_parser(self, prog_name): help=_("Allow to access private flavor (name or ID) " "(Must be used with --private option)"), ) + parser.add_argument( + '--description', + metavar='', + help=_("Description for the flavor.(Supported by API versions " + "'2.55' - '2.latest'") + ) identity_common.add_project_domain_option_to_parser(parser) return parser @@ -145,6 +152,11 @@ def take_action(self, parsed_args): msg = _("--project is only allowed with --private") raise exceptions.CommandError(msg) + if parsed_args.description: + if compute_client.api_version < api_versions.APIVersion("2.55"): + msg = _("--os-compute-api-version 2.55 or later is required") + raise exceptions.CommandError(msg) + args = ( parsed_args.name, parsed_args.ram, @@ -154,7 +166,8 @@ def take_action(self, parsed_args): parsed_args.ephemeral, parsed_args.swap, parsed_args.rxtx_factor, - parsed_args.public + parsed_args.public, + parsed_args.description ) flavor = compute_client.flavors.create(*args) @@ -332,6 +345,12 @@ def get_parser(self, prog_name): help=_('Set flavor access to project (name or ID) ' '(admin only)'), ) + parser.add_argument( + '--description', + metavar='', + help=_("Set description for the flavor.(Supported by API " + "versions '2.55' - '2.latest'") + ) identity_common.add_project_domain_option_to_parser(parser) return parser @@ -380,6 +399,13 @@ def take_action(self, parsed_args): raise exceptions.CommandError(_("Command Failed: One or more of" " the operations failed")) + if parsed_args.description: + if compute_client.api_version < api_versions.APIVersion("2.55"): + msg = _("--os-compute-api-version 2.55 or later is required") + raise exceptions.CommandError(msg) + compute_client.flavors.update(flavor=parsed_args.flavor, + description=parsed_args.description) + class ShowFlavor(command.ShowOne): _description = _("Display flavor details") diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index 46fa599284..9a065be13d 100644 --- a/openstackclient/tests/unit/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -765,6 +765,7 @@ def create_one_flavor(attrs=None): 'rxtx_factor': 1.0, 'OS-FLV-DISABLED:disabled': False, 'os-flavor-access:is_public': True, + 'description': 'description', 'OS-FLV-EXT-DATA:ephemeral': 0, 'properties': {'property': 'value'}, } diff --git a/openstackclient/tests/unit/compute/v2/test_flavor.py b/openstackclient/tests/unit/compute/v2/test_flavor.py index 4cdbb25ba0..a112fc1fae 100644 --- a/openstackclient/tests/unit/compute/v2/test_flavor.py +++ b/openstackclient/tests/unit/compute/v2/test_flavor.py @@ -16,6 +16,7 @@ import mock from mock import call +import novaclient from osc_lib import exceptions from osc_lib import utils @@ -50,6 +51,7 @@ class TestFlavorCreate(TestFlavor): columns = ( 'OS-FLV-DISABLED:disabled', 'OS-FLV-EXT-DATA:ephemeral', + 'description', 'disk', 'id', 'name', @@ -63,6 +65,7 @@ class TestFlavorCreate(TestFlavor): data = ( flavor.disabled, flavor.ephemeral, + flavor.description, flavor.disk, flavor.id, flavor.name, @@ -101,7 +104,8 @@ def test_flavor_create_default_options(self): 0, 0, 1.0, - True + True, + None, ) columns, data = self.cmd.take_action(parsed_args) self.flavors_mock.create.assert_called_once_with(*default_args) @@ -120,6 +124,7 @@ def test_flavor_create_all_options(self): '--vcpus', str(self.flavor.vcpus), '--rxtx-factor', str(self.flavor.rxtx_factor), '--public', + '--description', str(self.flavor.description), '--property', 'property=value', self.flavor.name, ] @@ -132,6 +137,7 @@ def test_flavor_create_all_options(self): ('vcpus', self.flavor.vcpus), ('rxtx_factor', self.flavor.rxtx_factor), ('public', True), + ('description', self.flavor.description), ('property', {'property': 'value'}), ('name', self.flavor.name), ] @@ -147,8 +153,13 @@ def test_flavor_create_all_options(self): self.flavor.swap, self.flavor.rxtx_factor, self.flavor.is_public, + self.flavor.description, ) - columns, data = self.cmd.take_action(parsed_args) + self.app.client_manager.compute.api_version = 2.55 + with mock.patch.object(novaclient.api_versions, + 'APIVersion', + return_value=2.55): + columns, data = self.cmd.take_action(parsed_args) self.flavors_mock.create.assert_called_once_with(*args) self.flavor.set_keys.assert_called_once_with({'property': 'value'}) self.flavor.get_keys.assert_called_once_with() @@ -168,6 +179,7 @@ def test_flavor_create_other_options(self): '--vcpus', str(self.flavor.vcpus), '--rxtx-factor', str(self.flavor.rxtx_factor), '--private', + '--description', str(self.flavor.description), '--project', self.project.id, '--property', 'key1=value1', '--property', 'key2=value2', @@ -181,6 +193,7 @@ def test_flavor_create_other_options(self): ('vcpus', self.flavor.vcpus), ('rxtx_factor', self.flavor.rxtx_factor), ('public', False), + ('description', 'description'), ('project', self.project.id), ('property', {'key1': 'value1', 'key2': 'value2'}), ('name', self.flavor.name), @@ -197,8 +210,13 @@ def test_flavor_create_other_options(self): self.flavor.swap, self.flavor.rxtx_factor, self.flavor.is_public, + self.flavor.description, ) - columns, data = self.cmd.take_action(parsed_args) + self.app.client_manager.compute.api_version = 2.55 + with mock.patch.object(novaclient.api_versions, + 'APIVersion', + return_value=2.55): + columns, data = self.cmd.take_action(parsed_args) self.flavors_mock.create.assert_called_once_with(*args) self.flavor_access_mock.add_tenant_access.assert_called_with( self.flavor.id, @@ -234,6 +252,79 @@ def test_flavor_create_no_options(self): arglist, verifylist) + def test_flavor_create_with_description_api_newer(self): + arglist = [ + '--id', self.flavor.id, + '--ram', str(self.flavor.ram), + '--disk', str(self.flavor.disk), + '--ephemeral', str(self.flavor.ephemeral), + '--swap', str(self.flavor.swap), + '--vcpus', str(self.flavor.vcpus), + '--rxtx-factor', str(self.flavor.rxtx_factor), + '--private', + '--description', 'fake description', + self.flavor.name, + ] + verifylist = [ + ('id', self.flavor.id), + ('ram', self.flavor.ram), + ('disk', self.flavor.disk), + ('ephemeral', self.flavor.ephemeral), + ('swap', self.flavor.swap), + ('vcpus', self.flavor.vcpus), + ('rxtx_factor', self.flavor.rxtx_factor), + ('public', False), + ('description', 'fake description'), + ('name', self.flavor.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.app.client_manager.compute.api_version = 2.55 + with mock.patch.object(novaclient.api_versions, + 'APIVersion', + return_value=2.55): + columns, data = self.cmd.take_action(parsed_args) + + args = ( + self.flavor.name, + self.flavor.ram, + self.flavor.vcpus, + self.flavor.disk, + self.flavor.id, + self.flavor.ephemeral, + self.flavor.swap, + self.flavor.rxtx_factor, + False, + 'fake description', + ) + + self.flavors_mock.create.assert_called_once_with(*args) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_flavor_create_with_description_api_older(self): + arglist = [ + '--id', self.flavor.id, + '--ram', str(self.flavor.ram), + '--vcpus', str(self.flavor.vcpus), + '--description', 'description', + self.flavor.name, + ] + verifylist = [ + ('ram', self.flavor.ram), + ('vcpus', self.flavor.vcpus), + ('description', 'description'), + ('name', self.flavor.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.app.client_manager.compute.api_version = 2.54 + with mock.patch.object(novaclient.api_versions, + 'APIVersion', + return_value=2.55): + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + class TestFlavorDelete(TestFlavor): @@ -622,6 +713,42 @@ def test_flavor_set_nothing(self): self.flavor_access_mock.add_tenant_access.assert_not_called() self.assertIsNone(result) + def test_flavor_set_description_api_newer(self): + arglist = [ + '--description', 'description', + self.flavor.id, + ] + verifylist = [ + ('description', 'description'), + ('flavor', self.flavor.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.app.client_manager.compute.api_version = 2.55 + with mock.patch.object(novaclient.api_versions, + 'APIVersion', + return_value=2.55): + result = self.cmd.take_action(parsed_args) + self.flavors_mock.update.assert_called_with( + flavor=self.flavor.id, description='description') + self.assertIsNone(result) + + def test_flavor_set_description_api_older(self): + arglist = [ + '--description', 'description', + self.flavor.id, + ] + verifylist = [ + ('description', 'description'), + ('flavor', self.flavor.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.app.client_manager.compute.api_version = 2.54 + with mock.patch.object(novaclient.api_versions, + 'APIVersion', + return_value=2.55): + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + class TestFlavorShow(TestFlavor): @@ -633,6 +760,7 @@ class TestFlavorShow(TestFlavor): 'OS-FLV-DISABLED:disabled', 'OS-FLV-EXT-DATA:ephemeral', 'access_project_ids', + 'description', 'disk', 'id', 'name', @@ -648,6 +776,7 @@ class TestFlavorShow(TestFlavor): flavor.disabled, flavor.ephemeral, None, + flavor.description, flavor.disk, flavor.id, flavor.name, @@ -710,6 +839,7 @@ def test_private_flavor_show(self): private_flavor.disabled, private_flavor.ephemeral, self.flavor_access.tenant_id, + private_flavor.description, private_flavor.disk, private_flavor.id, private_flavor.name, diff --git a/releasenotes/notes/flavor-add-description-b618abd4a7fb6545.yaml b/releasenotes/notes/flavor-add-description-b618abd4a7fb6545.yaml new file mode 100644 index 0000000000..f148175f79 --- /dev/null +++ b/releasenotes/notes/flavor-add-description-b618abd4a7fb6545.yaml @@ -0,0 +1,6 @@ +--- +features: + - Add ``--description`` option to ``flavor set`` command to update the + description of the server. + - Add ``--description`` option to ``flavor create`` command to set the + description of the server. From b18e79c09bcaea86b3f8470c909664e5fe940682 Mon Sep 17 00:00:00 2001 From: yanpuqing Date: Tue, 19 Jun 2018 05:13:48 -0400 Subject: [PATCH 1934/3095] Delete the LB object quotas set command in openstackclient Setting octavia quotas should use "openstack loadbalancer quota set", not "openstack quota set". The vip parameter had be removed from octavia. The patch removes '--vips', '--health-monitors', '--l7policies' parameter in "openstack quota set" command. Change-Id: Id0046195aa93bae62264d9de7d123cf63bd0fb7e Task: 19657 Story: 2002016 --- doc/source/cli/command-objects/quota.rst | 3 --- openstackclient/common/quota.py | 3 --- openstackclient/tests/unit/common/test_quota.py | 9 --------- 3 files changed, 15 deletions(-) diff --git a/doc/source/cli/command-objects/quota.rst b/doc/source/cli/command-objects/quota.rst index f39536af8e..126b210c4c 100644 --- a/doc/source/cli/command-objects/quota.rst +++ b/doc/source/cli/command-objects/quota.rst @@ -70,10 +70,7 @@ Set quotas for project [--ports ] [--routers ] [--rbac-policies ] - [--vips ] [--subnetpools ] - [--members ] - [--health-monitors ] diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index 282ea4284d..1027859acd 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -75,10 +75,7 @@ 'port': 'ports', 'router': 'routers', 'rbac_policy': 'rbac-policies', - 'vip': 'vips', 'subnetpool': 'subnetpools', - 'healthmonitor': 'health-monitors', - 'l7policy': 'l7policies', } NETWORK_KEYS = ['floating_ips', 'networks', 'rbac_policies', 'routers', diff --git a/openstackclient/tests/unit/common/test_quota.py b/openstackclient/tests/unit/common/test_quota.py index 1a3da31d78..fc975b980d 100644 --- a/openstackclient/tests/unit/common/test_quota.py +++ b/openstackclient/tests/unit/common/test_quota.py @@ -644,9 +644,6 @@ def test_quota_set_network(self): '--routers', str(network_fakes.QUOTA['router']), '--rbac-policies', str(network_fakes.QUOTA['rbac_policy']), '--ports', str(network_fakes.QUOTA['port']), - '--vips', str(network_fakes.QUOTA['vip']), - '--health-monitors', str(network_fakes.QUOTA['healthmonitor']), - '--l7policies', str(network_fakes.QUOTA['l7policy']), self.projects[0].name, ] verifylist = [ @@ -660,9 +657,6 @@ def test_quota_set_network(self): ('router', network_fakes.QUOTA['router']), ('rbac_policy', network_fakes.QUOTA['rbac_policy']), ('port', network_fakes.QUOTA['port']), - ('vip', network_fakes.QUOTA['vip']), - ('healthmonitor', network_fakes.QUOTA['healthmonitor']), - ('l7policy', network_fakes.QUOTA['l7policy']), ('project', self.projects[0].name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -679,9 +673,6 @@ def test_quota_set_network(self): 'router': network_fakes.QUOTA['router'], 'rbac_policy': network_fakes.QUOTA['rbac_policy'], 'port': network_fakes.QUOTA['port'], - 'vip': network_fakes.QUOTA['vip'], - 'healthmonitor': network_fakes.QUOTA['healthmonitor'], - 'l7policy': network_fakes.QUOTA['l7policy'], } self.network_mock.update_quota.assert_called_once_with( self.projects[0].id, From 15a079faa6a6cbae465bc96a6069c892af9c0e04 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 14 Jun 2018 09:31:01 -0500 Subject: [PATCH 1935/3095] Fix volume type functional tests Convert functional VolumeTypeTests to not use class methods for setup. Depends-On: https://review.openstack.org/577147 Change-Id: I855583ad1a50bf5f5046acdb85e977ab9e3c45d2 --- .../functional/volume/v1/test_volume_type.py | 115 ++++++++++----- .../tests/functional/volume/v2/test_qos.py | 5 +- .../functional/volume/v2/test_volume_type.py | 132 ++++++++++++------ 3 files changed, 175 insertions(+), 77 deletions(-) diff --git a/openstackclient/tests/functional/volume/v1/test_volume_type.py b/openstackclient/tests/functional/volume/v1/test_volume_type.py index c5886a696a..eb9d7f64ae 100644 --- a/openstackclient/tests/functional/volume/v1/test_volume_type.py +++ b/openstackclient/tests/functional/volume/v1/test_volume_type.py @@ -20,62 +20,92 @@ class VolumeTypeTests(common.BaseVolumeTests): """Functional tests for volume type. """ - NAME = uuid.uuid4().hex - - @classmethod - def setUpClass(cls): - super(VolumeTypeTests, cls).setUpClass() - cmd_output = json.loads(cls.openstack( - 'volume type create -f json %s' % cls.NAME)) - cls.assertOutput(cls.NAME, cmd_output['name']) - - @classmethod - def tearDownClass(cls): - try: - raw_output = cls.openstack('volume type delete %s' % cls.NAME) - cls.assertOutput('', raw_output) - finally: - super(VolumeTypeTests, cls).tearDownClass() - - def test_volume_type_list(self): + def test_volume_type_create_list(self): + name = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume type create -f json --private ' + + name, + )) + self.addCleanup( + self.openstack, + 'volume type delete ' + + name, + ) + self.assertEqual(name, cmd_output['name']) + + cmd_output = json.loads(self.openstack( + 'volume type show -f json %s' % name + )) + self.assertEqual(self.NAME, cmd_output['name']) + cmd_output = json.loads(self.openstack('volume type list -f json')) self.assertIn(self.NAME, [t['Name'] for t in cmd_output]) - def test_volume_type_show(self): cmd_output = json.loads(self.openstack( - 'volume type show -f json %s' % self.NAME)) - self.assertEqual(self.NAME, cmd_output['name']) + 'volume type list -f json --default' + )) + self.assertEqual(1, len(cmd_output)) + self.assertEqual('lvmdriver-1', cmd_output[0]['Name']) def test_volume_type_set_unset_properties(self): + name = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume type create -f json --private ' + + name, + )) + self.addCleanup( + self.openstack, + 'volume type delete ' + name + ) + self.assertEqual(name, cmd_output['name']) + raw_output = self.openstack( - 'volume type set --property a=b --property c=d %s' % self.NAME) + 'volume type set --property a=b --property c=d %s' % name + ) self.assertEqual("", raw_output) - cmd_output = json.loads(self.openstack( - 'volume type show -f json ' + self.NAME)) + 'volume type show -f json %s' % name + )) + # TODO(amotoki): properties output should be machine-readable self.assertEqual("a='b', c='d'", cmd_output['properties']) - raw_output = self.openstack('volume type unset --property a %s' % - self.NAME) + raw_output = self.openstack( + 'volume type unset --property a %s' % name + ) self.assertEqual("", raw_output) cmd_output = json.loads(self.openstack( - 'volume type show -f json %s' % self.NAME)) + 'volume type show -f json %s' % name + )) self.assertEqual("c='d'", cmd_output['properties']) def test_volume_type_set_unset_multiple_properties(self): + name = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume type create -f json --private ' + + name, + )) + self.addCleanup( + self.openstack, + 'volume type delete ' + name + ) + self.assertEqual(name, cmd_output['name']) + raw_output = self.openstack( - 'volume type set --property a=b --property c=d %s' % self.NAME) + 'volume type set --property a=b --property c=d %s' % name + ) self.assertEqual("", raw_output) - cmd_output = json.loads(self.openstack( - 'volume type show -f json %s' % self.NAME)) + 'volume type show -f json %s' % name + )) self.assertEqual("a='b', c='d'", cmd_output['properties']) raw_output = self.openstack( - 'volume type unset --property a --property c %s' % self.NAME) + 'volume type unset --property a --property c %s' % name + ) self.assertEqual("", raw_output) cmd_output = json.loads(self.openstack( - 'volume type show -f json %s' % self.NAME)) + 'volume type show -f json %s' % name + )) self.assertEqual("", cmd_output['properties']) def test_multi_delete(self): @@ -140,8 +170,21 @@ def test_encryption_type(self): '--encryption-control-location front-end ' + self.NAME) self.assertEqual('', raw_output) + + name = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume type create -f json --private ' + + name, + )) + self.addCleanup( + self.openstack, + 'volume type delete ' + name, + ) + self.assertEqual(name, cmd_output['name']) + cmd_output = json.loads(self.openstack( - 'volume type show -f json --encryption-type ' + self.NAME)) + 'volume type show -f json --encryption-type ' + name + )) expected = ["provider='LuksEncryptor'", "cipher='aes-xts-plain64'", "key_size='128'", @@ -150,10 +193,12 @@ def test_encryption_type(self): self.assertIn(attr, cmd_output['encryption']) # test unset encryption type raw_output = self.openstack( - 'volume type unset --encryption-type ' + self.NAME) + 'volume type unset --encryption-type ' + name + ) self.assertEqual('', raw_output) cmd_output = json.loads(self.openstack( - 'volume type show -f json --encryption-type ' + self.NAME)) + 'volume type show -f json --encryption-type ' + name + )) self.assertEqual('', cmd_output['encryption']) # test delete encryption type raw_output = self.openstack('volume type delete ' + encryption_type) diff --git a/openstackclient/tests/functional/volume/v2/test_qos.py b/openstackclient/tests/functional/volume/v2/test_qos.py index 888f12b1e2..646becc1a7 100644 --- a/openstackclient/tests/functional/volume/v2/test_qos.py +++ b/openstackclient/tests/functional/volume/v2/test_qos.py @@ -125,7 +125,6 @@ def test_volume_qos_set_show_unset(self): def test_volume_qos_asso_disasso(self): """Tests associate and disassociate qos with volume type""" vol_type1 = uuid.uuid4().hex - vol_type2 = uuid.uuid4().hex cmd_output = json.loads(self.openstack( 'volume type create -f json ' + vol_type1 @@ -134,6 +133,9 @@ def test_volume_qos_asso_disasso(self): vol_type1, cmd_output['name'] ) + self.addCleanup(self.openstack, 'volume type delete ' + vol_type1) + + vol_type2 = uuid.uuid4().hex cmd_output = json.loads(self.openstack( 'volume type create -f json ' + vol_type2 @@ -142,7 +144,6 @@ def test_volume_qos_asso_disasso(self): vol_type2, cmd_output['name'] ) - self.addCleanup(self.openstack, 'volume type delete ' + vol_type1) self.addCleanup(self.openstack, 'volume type delete ' + vol_type2) name = uuid.uuid4().hex diff --git a/openstackclient/tests/functional/volume/v2/test_volume_type.py b/openstackclient/tests/functional/volume/v2/test_volume_type.py index 5c551ca945..d8dd5bd626 100644 --- a/openstackclient/tests/functional/volume/v2/test_volume_type.py +++ b/openstackclient/tests/functional/volume/v2/test_volume_type.py @@ -20,76 +20,113 @@ class VolumeTypeTests(common.BaseVolumeTests): """Functional tests for volume type. """ - NAME = uuid.uuid4().hex - - @classmethod - def setUpClass(cls): - super(VolumeTypeTests, cls).setUpClass() - cmd_output = json.loads(cls.openstack( - 'volume type create -f json --private %s' % cls.NAME)) - cls.assertOutput(cls.NAME, cmd_output['name']) - - @classmethod - def tearDownClass(cls): - try: - raw_output = cls.openstack('volume type delete %s' % cls.NAME) - cls.assertOutput('', raw_output) - finally: - super(VolumeTypeTests, cls).tearDownClass() - - def test_volume_type_list(self): + def test_volume_type_create_list(self): + name = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume type create -f json --private ' + + name, + )) + self.addCleanup( + self.openstack, + 'volume type delete ' + name, + ) + self.assertEqual(name, cmd_output['name']) + + cmd_output = json.loads(self.openstack( + 'volume type show -f json %s' % name + )) + self.assertEqual(name, cmd_output['name']) + cmd_output = json.loads(self.openstack('volume type list -f json')) - self.assertIn(self.NAME, [t['Name'] for t in cmd_output]) + self.assertIn(name, [t['Name'] for t in cmd_output]) - def test_volume_type_list_default(self): cmd_output = json.loads(self.openstack( - 'volume type list -f json --default')) + 'volume type list -f json --default' + )) self.assertEqual(1, len(cmd_output)) self.assertEqual('lvmdriver-1', cmd_output[0]['Name']) - def test_volume_type_show(self): + def test_volume_type_set_unset_properties(self): + name = uuid.uuid4().hex cmd_output = json.loads(self.openstack( - 'volume type show -f json %s' % self.NAME)) - self.assertEqual(self.NAME, cmd_output['name']) + 'volume type create -f json --private ' + + name, + )) + self.addCleanup( + self.openstack, + 'volume type delete ' + name + ) + self.assertEqual(name, cmd_output['name']) - def test_volume_type_set_unset_properties(self): raw_output = self.openstack( - 'volume type set --property a=b --property c=d %s' % self.NAME) + 'volume type set --property a=b --property c=d %s' % name + ) self.assertEqual("", raw_output) cmd_output = json.loads(self.openstack( - 'volume type show -f json %s' % self.NAME)) + 'volume type show -f json %s' % name + )) # TODO(amotoki): properties output should be machine-readable self.assertEqual("a='b', c='d'", cmd_output['properties']) - raw_output = self.openstack('volume type unset --property a %s' % - self.NAME) + raw_output = self.openstack( + 'volume type unset --property a %s' % name + ) self.assertEqual("", raw_output) cmd_output = json.loads(self.openstack( - 'volume type show -f json %s' % self.NAME)) + 'volume type show -f json %s' % name + )) self.assertEqual("c='d'", cmd_output['properties']) def test_volume_type_set_unset_multiple_properties(self): + name = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume type create -f json --private ' + + name, + )) + self.addCleanup( + self.openstack, + 'volume type delete ' + name + ) + self.assertEqual(name, cmd_output['name']) + raw_output = self.openstack( - 'volume type set --property a=b --property c=d %s' % self.NAME) + 'volume type set --property a=b --property c=d %s' % name + ) self.assertEqual("", raw_output) cmd_output = json.loads(self.openstack( - 'volume type show -f json %s' % self.NAME)) + 'volume type show -f json %s' % name + )) self.assertEqual("a='b', c='d'", cmd_output['properties']) raw_output = self.openstack( - 'volume type unset --property a --property c %s' % self.NAME) + 'volume type unset --property a --property c %s' % name + ) self.assertEqual("", raw_output) cmd_output = json.loads(self.openstack( - 'volume type show -f json %s' % self.NAME)) + 'volume type show -f json %s' % name + )) self.assertEqual("", cmd_output['properties']) def test_volume_type_set_unset_project(self): + name = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume type create -f json --private ' + + name, + )) + self.addCleanup( + self.openstack, + 'volume type delete ' + name + ) + self.assertEqual(name, cmd_output['name']) + raw_output = self.openstack( - 'volume type set --project admin %s' % self.NAME) + 'volume type set --project admin %s' % name + ) self.assertEqual("", raw_output) raw_output = self.openstack( - 'volume type unset --project admin %s' % self.NAME) + 'volume type unset --project admin %s' % name + ) self.assertEqual("", raw_output) def test_multi_delete(self): @@ -108,6 +145,7 @@ def test_multi_delete(self): # these to new test format when beef up all tests for # volume tye commands. def test_encryption_type(self): + name = uuid.uuid4().hex encryption_type = uuid.uuid4().hex # test create new encryption type cmd_output = json.loads(self.openstack( @@ -162,16 +200,28 @@ def test_encryption_type(self): for attr in expected: self.assertIn(attr, cmd_output['encryption']) # test set new encryption type + cmd_output = json.loads(self.openstack( + 'volume type create -f json --private ' + + name, + )) + self.addCleanup( + self.openstack, + 'volume type delete ' + name, + ) + self.assertEqual(name, cmd_output['name']) + raw_output = self.openstack( 'volume type set ' '--encryption-provider LuksEncryptor ' '--encryption-cipher aes-xts-plain64 ' '--encryption-key-size 128 ' '--encryption-control-location front-end ' + - self.NAME) + name) self.assertEqual('', raw_output) + cmd_output = json.loads(self.openstack( - 'volume type show -f json --encryption-type ' + self.NAME)) + 'volume type show -f json --encryption-type ' + name + )) expected = ["provider='LuksEncryptor'", "cipher='aes-xts-plain64'", "key_size='128'", @@ -180,10 +230,12 @@ def test_encryption_type(self): self.assertIn(attr, cmd_output['encryption']) # test unset encryption type raw_output = self.openstack( - 'volume type unset --encryption-type ' + self.NAME) + 'volume type unset --encryption-type ' + name + ) self.assertEqual('', raw_output) cmd_output = json.loads(self.openstack( - 'volume type show -f json --encryption-type ' + self.NAME)) + 'volume type show -f json --encryption-type ' + name + )) self.assertEqual('', cmd_output['encryption']) # test delete encryption type raw_output = self.openstack('volume type delete ' + encryption_type) From 08dbd154e5da266e44f44386f711a3177e9061bd Mon Sep 17 00:00:00 2001 From: Sami MAKKI Date: Wed, 28 Mar 2018 16:50:48 +0200 Subject: [PATCH 1936/3095] Fix the `role implies list` command. The code was calling an unexisting function which never existed. The module refers now to the correct `InferenceRuleManager`. It also allows the compatibility with the future python-keystoneclient in which the compatibility method will be removed from the RoleManager. Change-Id: I08f785dc9e840da2e16915683eecfe49189c44b3 --- openstackclient/identity/v3/implied_role.py | 6 ++-- .../tests/functional/identity/v3/common.py | 13 ++++++++ .../tests/functional/identity/v3/test_role.py | 25 +++++++++++++++ .../tests/unit/identity/v3/fakes.py | 2 ++ .../unit/identity/v3/test_implied_role.py | 32 ++++++++++++------- 5 files changed, 63 insertions(+), 15 deletions(-) diff --git a/openstackclient/identity/v3/implied_role.py b/openstackclient/identity/v3/implied_role.py index c762338997..4e3df88ac1 100644 --- a/openstackclient/identity/v3/implied_role.py +++ b/openstackclient/identity/v3/implied_role.py @@ -71,7 +71,7 @@ def take_action(self, parsed_args): identity_client = self.app.client_manager.identity (prior_role_id, implied_role_id) = _get_role_ids( identity_client, parsed_args) - response = identity_client.roles.create_implied( + response = identity_client.inference_rules.create( prior_role_id, implied_role_id) response._info.pop('links', None) return zip(*sorted([(k, v['id']) @@ -101,7 +101,7 @@ def take_action(self, parsed_args): identity_client = self.app.client_manager.identity (prior_role_id, implied_role_id) = _get_role_ids( identity_client, parsed_args) - identity_client.roles.delete_implied( + identity_client.inference_rules.delete( prior_role_id, implied_role_id) @@ -125,5 +125,5 @@ def _list_implied(response): implies['name']) identity_client = self.app.client_manager.identity - response = identity_client.roles.list_inference_roles() + response = identity_client.inference_rules.list_inference_roles() return (self._COLUMNS, _list_implied(response)) diff --git a/openstackclient/tests/functional/identity/v3/common.py b/openstackclient/tests/functional/identity/v3/common.py index 33cb5d8602..54132be58c 100644 --- a/openstackclient/tests/functional/identity/v3/common.py +++ b/openstackclient/tests/functional/identity/v3/common.py @@ -52,6 +52,8 @@ class IdentityTests(base.TestCase): 'id', 'relay_state_prefix', 'sp_url'] SERVICE_PROVIDER_LIST_HEADERS = ['ID', 'Enabled', 'Description', 'Auth URL'] + IMPLIED_ROLE_LIST_HEADERS = ['Prior Role ID', 'Prior Role Name', + 'Implied Role ID', 'Implied Role Name'] @classmethod def setUpClass(cls): @@ -149,6 +151,17 @@ def _create_dummy_role(self, add_clean_up=True): self.assertEqual(role_name, role['name']) return role_name + def _create_dummy_implied_role(self, add_clean_up=True): + role_name = self._create_dummy_role(add_clean_up) + implied_role_name = self._create_dummy_role(add_clean_up) + self.openstack( + 'implied role create ' + '--implied-role %(implied_role)s ' + '%(role)s' % {'implied_role': implied_role_name, + 'role': role_name}) + + return implied_role_name, role_name + def _create_dummy_group(self, add_clean_up=True): group_name = data_utils.rand_name('TestGroup') description = data_utils.rand_name('description') diff --git a/openstackclient/tests/functional/identity/v3/test_role.py b/openstackclient/tests/functional/identity/v3/test_role.py index ab8af9c04e..fb9e061424 100644 --- a/openstackclient/tests/functional/identity/v3/test_role.py +++ b/openstackclient/tests/functional/identity/v3/test_role.py @@ -143,3 +143,28 @@ def test_role_remove(self): 'role': role_name}) self.assertEqual(0, len(add_raw_output)) self.assertEqual(0, len(remove_raw_output)) + + def test_implied_role_list(self): + self._create_dummy_implied_role() + raw_output = self.openstack('implied role list') + items = self.parse_listing(raw_output) + self.assert_table_structure(items, self.IMPLIED_ROLE_LIST_HEADERS) + self.assertEqual(3, len(items)) + + def test_implied_role_create(self): + role_name = self._create_dummy_role() + implied_role_name = self._create_dummy_role() + self.openstack( + 'implied role create ' + '--implied-role %(implied_role)s ' + '%(role)s' % {'implied_role': implied_role_name, + 'role': role_name}) + + def test_implied_role_delete(self): + implied_role_name, role_name = self._create_dummy_implied_role() + raw_output = self.openstack( + 'implied role delete ' + '--implied-role %(implied_role)s ' + '%(role)s' % {'implied_role': implied_role_name, + 'role': role_name}) + self.assertEqual(0, len(raw_output)) diff --git a/openstackclient/tests/unit/identity/v3/fakes.py b/openstackclient/tests/unit/identity/v3/fakes.py index 779287924d..7aa9cd7ce2 100644 --- a/openstackclient/tests/unit/identity/v3/fakes.py +++ b/openstackclient/tests/unit/identity/v3/fakes.py @@ -576,6 +576,8 @@ def __init__(self, **kwargs): self.application_credentials = mock.Mock() self.application_credentials.resource_class = fakes.FakeResource(None, {}) + self.inference_rules = mock.Mock() + self.inference_rules.resource_class = fakes.FakeResource(None, {}) class FakeFederationManager(object): diff --git a/openstackclient/tests/unit/identity/v3/test_implied_role.py b/openstackclient/tests/unit/identity/v3/test_implied_role.py index 08273f7312..749681293b 100644 --- a/openstackclient/tests/unit/identity/v3/test_implied_role.py +++ b/openstackclient/tests/unit/identity/v3/test_implied_role.py @@ -25,26 +25,32 @@ class TestRole(identity_fakes.TestIdentityv3): def setUp(self): super(TestRole, self).setUp() + identity_client = self.app.client_manager.identity + # Get a shortcut to the UserManager Mock - self.users_mock = self.app.client_manager.identity.users + self.users_mock = identity_client.users self.users_mock.reset_mock() # Get a shortcut to the UserManager Mock - self.groups_mock = self.app.client_manager.identity.groups + self.groups_mock = identity_client.groups self.groups_mock.reset_mock() # Get a shortcut to the DomainManager Mock - self.domains_mock = self.app.client_manager.identity.domains + self.domains_mock = identity_client.domains self.domains_mock.reset_mock() # Get a shortcut to the ProjectManager Mock - self.projects_mock = self.app.client_manager.identity.projects + self.projects_mock = identity_client.projects self.projects_mock.reset_mock() # Get a shortcut to the RoleManager Mock - self.roles_mock = self.app.client_manager.identity.roles + self.roles_mock = identity_client.roles self.roles_mock.reset_mock() + # Get a shortcut to the InferenceRuleManager Mock + self.inference_rules_mock = identity_client.inference_rules + self.inference_rules_mock.reset_mock() + def _is_inheritance_testcase(self): return False @@ -67,12 +73,13 @@ def setUp(self): ), ] - self.roles_mock.create_implied.return_value = fakes.FakeResource( + fake_resource = fakes.FakeResource( None, {'prior_role': copy.deepcopy(identity_fakes.ROLES[0]), 'implied': copy.deepcopy(identity_fakes.ROLES[1]), }, loaded=True, ) + self.inference_rules_mock.create.return_value = fake_resource self.cmd = implied_role.CreateImpliedRole(self.app, None) @@ -93,8 +100,8 @@ def test_implied_role_create(self): # data to be shown. columns, data = self.cmd.take_action(parsed_args) - # RoleManager.create_implied(prior, implied) - self.roles_mock.create_implied.assert_called_with( + # InferenceRuleManager.create(prior, implied) + self.inference_rules_mock.create.assert_called_with( identity_fakes.ROLES[0]['id'], identity_fakes.ROLES[1]['id'] ) @@ -126,12 +133,13 @@ def setUp(self): ), ] - self.roles_mock.delete_implied.return_value = fakes.FakeResource( + fake_resource = fakes.FakeResource( None, {'prior-role': copy.deepcopy(identity_fakes.ROLES[0]), 'implied': copy.deepcopy(identity_fakes.ROLES[1]), }, loaded=True, ) + self.inference_rules_mock.delete.return_value = fake_resource self.cmd = implied_role.DeleteImpliedRole(self.app, None) @@ -147,7 +155,7 @@ def test_implied_role_delete(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) - self.roles_mock.delete_implied.assert_called_with( + self.inference_rules_mock.delete.assert_called_with( identity_fakes.ROLES[0]['id'], identity_fakes.ROLES[1]['id'] ) @@ -158,7 +166,7 @@ class TestImpliedRoleList(TestRole): def setUp(self): super(TestImpliedRoleList, self).setUp() - self.roles_mock.list_inference_roles.return_value = ( + self.inference_rules_mock.list_inference_roles.return_value = ( identity_fakes.FakeImpliedRoleResponse.create_list()) self.cmd = implied_role.ListImpliedRole(self.app, None) @@ -168,7 +176,7 @@ def test_implied_role_list(self): verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.roles_mock.list_inference_roles.assert_called_with() + self.inference_rules_mock.list_inference_roles.assert_called_with() collist = ['Prior Role ID', 'Prior Role Name', 'Implied Role ID', 'Implied Role Name'] From 412ee7f1d89f899dca8a1a3558ab4f6c57b3c43f Mon Sep 17 00:00:00 2001 From: Telles Nobrega Date: Wed, 20 Jun 2018 09:07:00 -0300 Subject: [PATCH 1937/3095] Adding api_version to FakeApp In order to use app.api_version[''] we need to have the api_version on FakeApp otherwise the test will fail. Patch that will benefit from this: https://review.openstack.org/#/c/572536/ Change-Id: Ibb3c548daf2a62b6c4aefb5e257372dd5c56521e --- openstackclient/tests/unit/fakes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openstackclient/tests/unit/fakes.py b/openstackclient/tests/unit/fakes.py index 954973ef98..bca457e457 100644 --- a/openstackclient/tests/unit/fakes.py +++ b/openstackclient/tests/unit/fakes.py @@ -106,6 +106,7 @@ class FakeApp(object): def __init__(self, _stdout, _log): self.stdout = _stdout self.client_manager = None + self.api_version = {} self.stdin = sys.stdin self.stdout = _stdout or sys.stdout self.stderr = sys.stderr From 603fe255672f529f797461b0cc6638e2a66ef9a3 Mon Sep 17 00:00:00 2001 From: tianhui Date: Wed, 27 Jun 2018 07:44:53 +0000 Subject: [PATCH 1938/3095] Change bug url to a correct one The url for the python-openstackclient bugs is deprecated and needs to change it to a correct one. Change-Id: I3762faf27fdb647dbff2e47b19d238108ef1975f --- doc/source/cli/man/openstack.rst | 4 ++-- doc/source/index.rst | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/source/cli/man/openstack.rst b/doc/source/cli/man/openstack.rst index 632ba4be0a..0745fb7a06 100644 --- a/doc/source/cli/man/openstack.rst +++ b/doc/source/cli/man/openstack.rst @@ -477,8 +477,8 @@ The following environment variables can be set to alter the behaviour of :progra BUGS ==== -Bug reports are accepted at the python-openstackclient LaunchPad project -"https://bugs.launchpad.net/python-openstackclient/+bugs". +Bug reports are accepted at the python-openstackclient StoryBoard project +"https://storyboard.openstack.org/#!/project/975". AUTHORS diff --git a/doc/source/index.rst b/doc/source/index.rst index 37cc6c5727..a6d1e89606 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -63,7 +63,7 @@ Developers may also be found in the `IRC channel`_ ``#openstack-sdks``. .. _`on OpenStack's Git server`: https://git.openstack.org/cgit/openstack/python-openstackclient/tree .. _Launchpad: https://launchpad.net/python-openstackclient .. _Gerrit: http://docs.openstack.org/infra/manual/developers.html#development-workflow -.. _Bug reports: https://bugs.launchpad.net/python-openstackclient/+bugs +.. _Bug reports: https://storyboard.openstack.org/#!/project/975 .. _blueprints: https://blueprints.launchpad.net/python-openstackclient .. _PyPi: https://pypi.org/project/python-openstackclient .. _tarball: http://tarballs.openstack.org/python-openstackclient From ddcc25e7f3e16ec4a1eef6302d7f12a07869615c Mon Sep 17 00:00:00 2001 From: "wu.chunyang" Date: Thu, 28 Jun 2018 13:27:25 +0800 Subject: [PATCH 1939/3095] Add release note link in README Change-Id: I53896535dc369a499a6850b71d23feff9350e557 --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index 4940ffc1a4..8e37b325f7 100644 --- a/README.rst +++ b/README.rst @@ -43,6 +43,7 @@ language to describe operations in OpenStack. .. _Developer: https://docs.openstack.org/project-team-guide/project-setup/python.html .. _Contributing: https://docs.openstack.org/infra/manual/developers.html .. _Testing: https://docs.openstack.org/python-openstackclient/latest/contributor/developing.html#testing +.. _Release Notes: https://docs.openstack.org/releasenotes/python-openstackclient Getting Started =============== From 9edbab8c90bb74ba12892d0c77c8e8a99d4868fe Mon Sep 17 00:00:00 2001 From: Adam Harwell Date: Wed, 13 Jun 2018 15:22:17 -0700 Subject: [PATCH 1940/3095] Add ability to filter image list by tag Change-Id: I2e222d3e69df9d8d7cd472663caaee31bedd848c --- doc/source/cli/command-objects/image.rst | 7 +++++ openstackclient/image/v2/image.py | 8 +++++ .../tests/functional/image/v2/test_image.py | 29 ++++++++++++++----- .../tests/unit/image/v2/test_image.py | 14 +++++++++ ...e-tag-filter-support-5cb039416b07caab.yaml | 4 +++ 5 files changed, 55 insertions(+), 7 deletions(-) create mode 100644 releasenotes/notes/add-image-tag-filter-support-5cb039416b07caab.yaml diff --git a/doc/source/cli/command-objects/image.rst b/doc/source/cli/command-objects/image.rst index f0b5bfaade..a09a8d9ffa 100644 --- a/doc/source/cli/command-objects/image.rst +++ b/doc/source/cli/command-objects/image.rst @@ -209,6 +209,7 @@ List available images [--property ] [--name ] [--status ] + [--tag ] [--long] [--sort [:]] [--limit ] @@ -244,6 +245,12 @@ List available images *Image version 2 only* +.. option:: --tag + + Filter images based on tag + + *Image version 2 only* + .. option:: --long List additional fields in output diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 4a51062fed..4c7c815f22 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -464,6 +464,12 @@ def get_parser(self, prog_name): default=None, help=_("Filter images based on status.") ) + parser.add_argument( + '--tag', + metavar='', + default=None, + help=_('Filter images based on tag.'), + ) parser.add_argument( '--long', action='store_true', @@ -521,6 +527,8 @@ def take_action(self, parsed_args): kwargs['name'] = parsed_args.name if parsed_args.status: kwargs['status'] = parsed_args.status + if parsed_args.tag: + kwargs['tag'] = parsed_args.tag if parsed_args.long: columns = ( 'ID', diff --git a/openstackclient/tests/functional/image/v2/test_image.py b/openstackclient/tests/functional/image/v2/test_image.py index 278ba5b948..3037b903cc 100644 --- a/openstackclient/tests/functional/image/v2/test_image.py +++ b/openstackclient/tests/functional/image/v2/test_image.py @@ -28,10 +28,11 @@ class ImageTests(base.TestCase): @classmethod def setUpClass(cls): super(ImageTests, cls).setUpClass() + cls.image_tag = 'my_tag' json_output = json.loads(cls.openstack( '--os-image-api-version 2 ' - 'image create -f json ' + - cls.NAME + 'image create -f json --tag {tag} {name}'.format( + tag=cls.image_tag, name=cls.NAME) )) cls.image_id = json_output["id"] cls.assertOutput(cls.NAME, json_output['name']) @@ -81,6 +82,16 @@ def test_image_list_with_status_filter(self): [img['Status'] for img in json_output] ) + def test_image_list_with_tag_filter(self): + json_output = json.loads(self.openstack( + 'image list --tag ' + self.image_tag + ' --long -f json' + )) + for taglist in [img['Tags'].split(', ') for img in json_output]: + self.assertIn( + self.image_tag, + taglist + ) + def test_image_attributes(self): """Test set, unset, show on attributes, tags and properties""" @@ -142,6 +153,10 @@ def test_image_attributes(self): ) # Test tags + self.assertNotIn( + '01', + json_output["tags"].split(', ') + ) self.openstack( 'image set ' + '--tag 01 ' + @@ -151,9 +166,9 @@ def test_image_attributes(self): 'image show -f json ' + self.NAME )) - self.assertEqual( + self.assertIn( '01', - json_output["tags"], + json_output["tags"].split(', ') ) self.openstack( @@ -165,9 +180,9 @@ def test_image_attributes(self): 'image show -f json ' + self.NAME )) - self.assertEqual( - '', - json_output["tags"], + self.assertNotIn( + '01', + json_output["tags"].split(', ') ) def test_image_set_rename(self): diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py index 301cd0377e..b769d1f654 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -779,6 +779,20 @@ def test_image_list_status_option(self): status='active', marker=self._image.id ) + def test_image_list_tag_option(self): + arglist = [ + '--tag', 'abc', + ] + verifylist = [ + ('tag', 'abc'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.api_mock.image_list.assert_called_with( + tag='abc', marker=self._image.id + ) + class TestListImageProjects(TestImage): diff --git a/releasenotes/notes/add-image-tag-filter-support-5cb039416b07caab.yaml b/releasenotes/notes/add-image-tag-filter-support-5cb039416b07caab.yaml new file mode 100644 index 0000000000..1c9ece84cc --- /dev/null +++ b/releasenotes/notes/add-image-tag-filter-support-5cb039416b07caab.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Add ``--tag`` option to ``image list`` command to filter by tag. From 7e8c55fa1bbc5f44b9233602786c22d6019eef22 Mon Sep 17 00:00:00 2001 From: Matt Riedemann Date: Thu, 28 Jun 2018 14:00:49 -0400 Subject: [PATCH 1941/3095] Fix docs from I0dc80bee3ba6ff4ec8cc3fc113b6de7807e0bf2a The CLI usage docs should mention the microversion restriction for creating a flavor with a description and setting a description on a flavor. Also, the release note talks about setting the server description on the flavor commands (wrong resource). Change-Id: I93af3da9a0be62395a1c719fd1dcbfd2b669580d Story: 2002196 Task: 22607 --- doc/source/cli/command-objects/flavor.rst | 6 ++++-- .../notes/flavor-add-description-b618abd4a7fb6545.yaml | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/doc/source/cli/command-objects/flavor.rst b/doc/source/cli/command-objects/flavor.rst index 2d946de308..f22463b78e 100644 --- a/doc/source/cli/command-objects/flavor.rst +++ b/doc/source/cli/command-objects/flavor.rst @@ -79,7 +79,8 @@ Create new flavor .. option:: --description - Description to add for this flavor + Description to add for this flavor. Only available starting with + ``--os-compute-api-version 2.55``. .. _flavor_create-flavor-name: .. describe:: @@ -176,7 +177,8 @@ Set flavor properties .. option:: --description - Set description to this flavor + Description to set for this flavor. Only available starting with + ``--os-compute-api-version 2.55``. .. describe:: diff --git a/releasenotes/notes/flavor-add-description-b618abd4a7fb6545.yaml b/releasenotes/notes/flavor-add-description-b618abd4a7fb6545.yaml index f148175f79..0d22354f78 100644 --- a/releasenotes/notes/flavor-add-description-b618abd4a7fb6545.yaml +++ b/releasenotes/notes/flavor-add-description-b618abd4a7fb6545.yaml @@ -1,6 +1,8 @@ --- features: - Add ``--description`` option to ``flavor set`` command to update the - description of the server. + description of the flavor. Only available starting with + ``--os-compute-api-version 2.55``. - Add ``--description`` option to ``flavor create`` command to set the - description of the server. + description of the flavor. Only available starting with + ``--os-compute-api-version 2.55``. From 4a9cb8eea8e47950cb30ecaa7572a23d80d5bfcd Mon Sep 17 00:00:00 2001 From: Hongbin Lu Date: Tue, 9 Jan 2018 21:24:08 +0000 Subject: [PATCH 1942/3095] Support filtering port with IP address substring Change-Id: I9559f1c0a6db943705bd32aefb60d7ea7054dd1b Related-Bug: #1718605 --- doc/source/cli/command-objects/port.rst | 9 +++++---- openstackclient/network/v2/port.py | 11 ++++++++--- .../tests/unit/network/v2/test_port.py | 18 ++++++++++++++++++ ...ith-ip-address-substr-14c5805b241e402f.yaml | 7 +++++++ 4 files changed, 38 insertions(+), 7 deletions(-) create mode 100644 releasenotes/notes/allow-port-list-with-ip-address-substr-14c5805b241e402f.yaml diff --git a/doc/source/cli/command-objects/port.rst b/doc/source/cli/command-objects/port.rst index 335c22707a..bb037fa3e5 100644 --- a/doc/source/cli/command-objects/port.rst +++ b/doc/source/cli/command-objects/port.rst @@ -179,7 +179,7 @@ List ports [--router | --server | --device-id ] [--network ] [--mac-address ] - [--fixed-ip subnet=,ip-address=] + [--fixed-ip subnet=,ip-address=,ip-substring=] [--long] [--project [--project-domain ]] [--tags [,,...]] [--any-tags [,,...]] @@ -210,10 +210,11 @@ List ports List only ports with this MAC address -.. option:: --fixed-ip subnet=,ip-address= +.. option:: --fixed-ip subnet=,ip-address=,ip-substring= - Desired IP and/or subnet for filtering ports (name or ID): - subnet=,ip-address= + Desired IP address, IP address substring and/or subnet (name or ID) for + filtering ports: + subnet=,ip-address=,ip-substring= (repeat option to set multiple fixed IP addresses) .. option:: --long diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index f13ee7b90b..af6efa9d0a 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -215,6 +215,9 @@ def _prepare_filter_fixed_ips(client_manager, parsed_args): if 'ip-address' in ip_spec: ips.append('ip_address=%s' % ip_spec['ip-address']) + + if 'ip-substring' in ip_spec: + ips.append('ip_address_substr=%s' % ip_spec['ip-substring']) return ips @@ -531,11 +534,13 @@ def get_parser(self, prog_name): identity_common.add_project_domain_option_to_parser(parser) parser.add_argument( '--fixed-ip', - metavar='subnet=,ip-address=', + metavar=('subnet=,ip-address=,' + 'ip-substring='), action=parseractions.MultiKeyValueAction, - optional_keys=['subnet', 'ip-address'], + optional_keys=['subnet', 'ip-address', 'ip-substring'], help=_("Desired IP and/or subnet for filtering ports " - "(name or ID): subnet=,ip-address= " + "(name or ID): subnet=,ip-address=," + "ip-substring= " "(repeat option to set multiple fixed IP addresses)"), ) _tag.add_tag_filtering_option_to_parser(parser, _('ports')) diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index 03e1d841bd..78d7fd6ca1 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -867,6 +867,24 @@ def test_port_list_fixed_ip_opt_ip_address(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + def test_port_list_fixed_ip_opt_ip_address_substr(self): + ip_address_ss = self._ports[0].fixed_ips[0]['ip_address'][:-1] + arglist = [ + '--fixed-ip', "ip-substring=%s" % ip_address_ss, + ] + verifylist = [ + ('fixed_ip', [{'ip-substring': ip_address_ss}]) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.ports.assert_called_once_with(**{ + 'fixed_ips': ['ip_address_substr=%s' % ip_address_ss]}) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + def test_port_list_fixed_ip_opt_subnet_id(self): subnet_id = self._ports[0].fixed_ips[0]['subnet_id'] arglist = [ diff --git a/releasenotes/notes/allow-port-list-with-ip-address-substr-14c5805b241e402f.yaml b/releasenotes/notes/allow-port-list-with-ip-address-substr-14c5805b241e402f.yaml new file mode 100644 index 0000000000..bbb0af6ba0 --- /dev/null +++ b/releasenotes/notes/allow-port-list-with-ip-address-substr-14c5805b241e402f.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Add an ``ip-substring`` key to the ``--fixed-ip`` option of the + ``port list`` command. This allows filtering ports by a substring + match of an IP address. + [Bug `1718605 `_] \ No newline at end of file From 83a9db280d2c9dd749c65ff635c430348bd356c0 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 28 Jun 2018 13:35:38 -0500 Subject: [PATCH 1943/3095] Retry floating IP tests test_server_attach_detach_floating_ip() has a test for server add/remove floating IP that seems to be racy, add a retry loop to let neutron and nova do their thing before calling it bad. Change-Id: I999a0d7dae1706d746053bafb7ab4e3b791d0042 --- .../functional/compute/v2/test_server.py | 40 +++++++++++++++++-- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py index 0b29fe5fbd..bba16f6282 100644 --- a/openstackclient/tests/functional/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -11,6 +11,7 @@ # under the License. import json +import time import uuid from tempest.lib import exceptions @@ -255,10 +256,24 @@ def test_server_attach_detach_floating_ip(self): floating_ip ) self.assertEqual("", raw_output) - cmd_output = json.loads(self.openstack( - 'server show -f json ' + - name - )) + + # Loop a few times since this is timing-sensitive + # Just hard-code it for now, since there is no pause and it is + # racy we shouldn't have to wait too long, a minute seems reasonable + wait_time = 0 + while wait_time < 60: + cmd_output = json.loads(self.openstack( + 'server show -f json ' + + name + )) + if floating_ip not in cmd_output['addresses']: + # Hang out for a bit and try again + print('retrying floating IP check') + wait_time += 10 + time.sleep(10) + else: + break + self.assertIn( floating_ip, cmd_output['addresses'], @@ -272,6 +287,23 @@ def test_server_attach_detach_floating_ip(self): ) self.assertEqual("", raw_output) + # Loop a few times since this is timing-sensitive + # Just hard-code it for now, since there is no pause and it is + # racy we shouldn't have to wait too long, a minute seems reasonable + wait_time = 0 + while wait_time < 60: + cmd_output = json.loads(self.openstack( + 'server show -f json ' + + name + )) + if floating_ip in cmd_output['addresses']: + # Hang out for a bit and try again + print('retrying floating IP check') + wait_time += 10 + time.sleep(10) + else: + break + cmd_output = json.loads(self.openstack( 'server show -f json ' + name From 26c268a910eaa9a36112aff0c38892759057c351 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 6 Jul 2018 13:15:59 -0500 Subject: [PATCH 1944/3095] Slow down and retry aggregate create/delete to lessen race Change-Id: I676894c1bfd3156313d88e6457250b9ff226118b Signed-off-by: Dean Troyer --- .../functional/compute/v2/test_aggregate.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/openstackclient/tests/functional/compute/v2/test_aggregate.py b/openstackclient/tests/functional/compute/v2/test_aggregate.py index cf9d2bc082..7102675716 100644 --- a/openstackclient/tests/functional/compute/v2/test_aggregate.py +++ b/openstackclient/tests/functional/compute/v2/test_aggregate.py @@ -11,6 +11,7 @@ # under the License. import json +import time import uuid from openstackclient.tests.functional import base @@ -51,6 +52,23 @@ def test_aggregate_create_and_delete(self): cmd_output['availability_zone'] ) + # Loop a few times since this is timing-sensitive + # Just hard-code it for now, since there is no pause and it is + # racy we shouldn't have to wait too long, a minute seems reasonable + wait_time = 0 + while wait_time < 60: + cmd_output = json.loads(self.openstack( + 'aggregate show -f json ' + + name2 + )) + if cmd_output['name'] != name2: + # Hang out for a bit and try again + print('retrying aggregate check') + wait_time += 10 + time.sleep(10) + else: + break + del_output = self.openstack( 'aggregate delete ' + name1 + ' ' + From b9fab849f7be93fa62e793ce68303a9473c54fa7 Mon Sep 17 00:00:00 2001 From: Pavlo Shchelokovskyy Date: Mon, 14 May 2018 17:57:28 +0000 Subject: [PATCH 1945/3095] Skip calls to glance and nova when got no servers save (potentially many) HTTP calls to Glance API for image list and a call to Nova API for flavor list when the server list actually returned no servers. Change-Id: I93a56138c50b82fb4dce67a2f788107f71c5f423 Story: #2002039 Task: #19681 --- openstackclient/compute/v2/server.py | 4 ++-- .../tests/unit/compute/v2/test_server.py | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index b82f895c0e..777f7744e7 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1226,7 +1226,7 @@ def take_action(self, parsed_args): # Create a dict that maps image_id to image object. # Needed so that we can display the "Image Name" column. # "Image Name" is not crucial, so we swallow any exceptions. - if not parsed_args.no_name_lookup: + if data and not parsed_args.no_name_lookup: try: images_list = self.app.client_manager.image.images.list() for i in images_list: @@ -1238,7 +1238,7 @@ def take_action(self, parsed_args): # Create a dict that maps flavor_id to flavor object. # Needed so that we can display the "Flavor Name" column. # "Flavor Name" is not crucial, so we swallow any exceptions. - if not parsed_args.no_name_lookup: + if data and not parsed_args.no_name_lookup: try: flavors_list = compute_client.flavors.list(is_public=None) for i in flavors_list: diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index a53c6c8193..46d4c24114 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -1947,6 +1947,25 @@ def test_server_list_no_option(self): self.assertEqual(self.columns, columns) self.assertEqual(tuple(self.data), tuple(data)) + def test_server_list_no_servers(self): + arglist = [] + verifylist = [ + ('all_projects', False), + ('long', False), + ('deleted', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.servers_mock.list.return_value = [] + self.data = () + + columns, data = self.cmd.take_action(parsed_args) + + self.servers_mock.list.assert_called_with(**self.kwargs) + self.assertEqual(0, self.images_mock.list.call_count) + self.assertEqual(0, self.flavors_mock.list.call_count) + self.assertEqual(self.columns, columns) + self.assertEqual(tuple(self.data), tuple(data)) + def test_server_list_long_option(self): arglist = [ '--long', From 63d741fd6646b360818c07a9679344e889031949 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Tue, 3 Apr 2018 17:29:59 +0100 Subject: [PATCH 1946/3095] Replace pbr autodoc with sphinxcontrib-apidoc This fixes local building of the documentation using tox, and allows the gate to stop relying on pbr and move completely to the new docs PTI. http://lists.openstack.org/pipermail/openstack-dev/2018-March/128594.html Change-Id: I485acda07098a435753e91c1ca45e586de199c35 --- doc/requirements.txt | 3 ++- doc/source/conf.py | 19 +++++++++++-------- setup.cfg | 16 ---------------- tox.ini | 5 +++-- 4 files changed, 16 insertions(+), 27 deletions(-) diff --git a/doc/requirements.txt b/doc/requirements.txt index d959d4431b..1afc73d6a6 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -3,4 +3,5 @@ # process, which may cause wedges in the gate later. openstackdocstheme>=1.18.1 # Apache-2.0 reno>=2.5.0 # Apache-2.0 -sphinx!=1.6.6,!=1.6.7,>=1.6.2 # BSD +sphinx!=1.6.6,!=1.6.7,>=1.6.5 # BSD +sphinxcontrib-apidoc>=0.2.0 # BSD diff --git a/doc/source/conf.py b/doc/source/conf.py index d7efbd7b91..003bfcaa34 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -12,16 +12,8 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import os -import sys - import pbr.version -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))) - # -- General configuration ---------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. @@ -35,6 +27,7 @@ 'openstackdocstheme', 'stevedore.sphinxext', 'cliff.sphinxext', + 'sphinxcontrib.apidoc', ] # openstackdocstheme options @@ -282,3 +275,13 @@ autoprogram_cliff_ignored = [ '--help', '--format', '--column', '--max-width', '--fit-width', '--print-empty', '--prefix', '--noindent', '--quote'] + + +# -- Options for sphinxcontrib.apidoc ---------------------------------------- + +apidoc_module_dir = '../../openstackclient' +apidoc_excluded_paths = [ + 'volume/v3', + 'tests', +] +apidoc_output_dir = 'contributor/api' diff --git a/setup.cfg b/setup.cfg index ac45a77dcd..f9c0b3efcf 100644 --- a/setup.cfg +++ b/setup.cfg @@ -714,22 +714,6 @@ openstack.volume.v3 = volume_transfer_request_list = openstackclient.volume.v2.volume_transfer_request:ListTransferRequest volume_transfer_request_show = openstackclient.volume.v2.volume_transfer_request:ShowTransferRequest -[pbr] -autodoc_tree_index_modules = True -autodoc_tree_excludes = - setup.py - openstackclient/volume/v3 - openstackclient/tests/ - openstackclient/tests/* -api_doc_dir = contributor/api - -[build_sphinx] -builders = html,man -all-files = 1 -warning-is-error = 1 -source-dir = doc/source -build-dir = doc/build - [upload_sphinx] upload-dir = doc/build/html diff --git a/tox.ini b/tox.ini index 9c10eb2fb7..4aad012cd6 100644 --- a/tox.ini +++ b/tox.ini @@ -113,12 +113,13 @@ deps = -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} -r{toxinidir}/requirements.txt -r{toxinidir}/doc/requirements.txt -commands = python setup.py build_sphinx +commands = + sphinx-build -a -E -W -d doc/build/doctrees -b html doc/source doc/build/html + sphinx-build -a -E -W -d doc/build/doctrees -b man doc/source doc/build/man [testenv:releasenotes] deps = -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} - -r{toxinidir}/requirements.txt -r{toxinidir}/doc/requirements.txt commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html From 70031687f9252bd077c3cfa2c1541a9d8536bd90 Mon Sep 17 00:00:00 2001 From: Tuan Do Anh Date: Wed, 11 Jul 2018 14:06:54 +0700 Subject: [PATCH 1947/3095] Fix lower-constraints.txt During the change https://review.openstack.org/#/c/573216/ neutron-vpnaas lower-constraints.txt looks out-of-date. This commit fixes lower-constraints.txt. Change-Id: Id9cfb463a98bdcc3c45505d8701c515549ecaa55 --- lower-constraints.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lower-constraints.txt b/lower-constraints.txt index 88c75cf47b..e3c160ecfd 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -55,7 +55,7 @@ os-client-config==1.28.0 os-service-types==1.2.0 os-testr==1.0.0 osc-lib==1.8.0 -oslo.concurrency==3.25.0 +oslo.concurrency==3.26.0 oslo.config==5.2.0 oslo.context==2.19.2 oslo.i18n==3.15.3 From fc76db0def66df5bbbc1dc67231b11a531d279fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Knecht?= Date: Tue, 17 Jul 2018 14:13:39 +0200 Subject: [PATCH 1948/3095] compute: host: expand kwargs in host_set() call `host_set()` expects `status` and `maintenance_mode` as keyword arguments, but in `SetHost.take_action()`, it is called without expanding the keyword arguments. So it's called as ``` host_set(host, {'status': 'enable'}) ``` instead of ``` host_set(host, status='enable') ``` Change-Id: If0b37ac60091161a892bfc694fce31a988f66005 Task: 23023 --- openstackclient/compute/v2/host.py | 2 +- openstackclient/tests/unit/compute/v2/test_host.py | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/openstackclient/compute/v2/host.py b/openstackclient/compute/v2/host.py index 9fdfd927d0..07c92a8c16 100644 --- a/openstackclient/compute/v2/host.py +++ b/openstackclient/compute/v2/host.py @@ -97,7 +97,7 @@ def take_action(self, parsed_args): compute_client.api.host_set( parsed_args.host, - kwargs + **kwargs ) diff --git a/openstackclient/tests/unit/compute/v2/test_host.py b/openstackclient/tests/unit/compute/v2/test_host.py index 329095deef..244da41317 100644 --- a/openstackclient/tests/unit/compute/v2/test_host.py +++ b/openstackclient/tests/unit/compute/v2/test_host.py @@ -111,8 +111,7 @@ def test_host_set_no_option(self, h_mock): result = self.cmd.take_action(parsed_args) self.assertIsNone(result) - body = {} - h_mock.assert_called_with(self.host['host'], body) + h_mock.assert_called_with(self.host['host']) def test_host_set(self, h_mock): h_mock.return_value = self.host @@ -133,8 +132,8 @@ def test_host_set(self, h_mock): result = self.cmd.take_action(parsed_args) self.assertIsNone(result) - body = {'status': 'enable', 'maintenance_mode': 'disable'} - h_mock.assert_called_with(self.host['host'], body) + h_mock.assert_called_with(self.host['host'], status='enable', + maintenance_mode='disable') @mock.patch( From 3dd9613b21d78005e1c8f5b3b20b48b686459717 Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Mon, 23 Jul 2018 10:19:50 -0500 Subject: [PATCH 1949/3095] Pass volume snapshot size to volume create When creating a volume from a snapshot, the size parameter is required and type is checked. Since we have to pass something and it needs to be a valid data type (None is not acceptable) grab the size from the snapshot object and pass it. Change-Id: Ie23e3d23828919234e40336b5c65b22e140d337c --- .../tests/unit/volume/v2/test_volume.py | 2 +- openstackclient/volume/v2/volume.py | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/openstackclient/tests/unit/volume/v2/test_volume.py b/openstackclient/tests/unit/volume/v2/test_volume.py index 2fa924b8a5..304aa91cbc 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume.py +++ b/openstackclient/tests/unit/volume/v2/test_volume.py @@ -430,7 +430,7 @@ def test_volume_create_with_snapshot(self): columns, data = self.cmd.take_action(parsed_args) self.volumes_mock.create.assert_called_once_with( - size=None, + size=snapshot.size, snapshot_id=snapshot.id, name=self.new_volume.name, description=None, diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index 61f846b02f..ee3d2f20d3 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -186,11 +186,21 @@ def take_action(self, parsed_args): image_client.images, parsed_args.image).id + size = parsed_args.size + snapshot = None if parsed_args.snapshot: - snapshot = utils.find_resource( + snapshot_obj = utils.find_resource( volume_client.volume_snapshots, - parsed_args.snapshot).id + parsed_args.snapshot) + snapshot = snapshot_obj.id + # Cinder requires a value for size when creating a volume + # even if creating from a snapshot. Cinder will create the + # volume with at least the same size as the snapshot anyway, + # so since we have the object here, just override the size + # value if it's either not given or is smaller than the + # snapshot size. + size = max(size or 0, snapshot_obj.size) project = None if parsed_args.project: @@ -205,7 +215,7 @@ def take_action(self, parsed_args): parsed_args.user).id volume = volume_client.volumes.create( - size=parsed_args.size, + size=size, snapshot_id=snapshot, name=parsed_args.name, description=parsed_args.description, From 641a4faac1d021518350925b4297677eeff98fde Mon Sep 17 00:00:00 2001 From: Lance Bragstad Date: Mon, 11 Jun 2018 22:03:52 +0000 Subject: [PATCH 1950/3095] Implement support for registered limits This commit adds support for users to manage registered limits via the command line. bp unified-limits Depends-On: https://review.openstack.org/#/c/574391/ Change-Id: Id8377363f7a3248b45aeeba21d2acc02684a0305 --- .../cli/command-objects/registered-limit.rst | 140 +++++ lower-constraints.txt | 2 +- .../identity/v3/registered_limit.py | 260 +++++++++ .../tests/functional/identity/v3/common.py | 37 ++ .../identity/v3/test_registered_limit.py | 184 +++++++ .../tests/unit/identity/v3/fakes.py | 23 + .../unit/identity/v3/test_registered_limit.py | 510 ++++++++++++++++++ .../bp-unified-limits-58f166401534a4ff.yaml | 7 + requirements.txt | 2 +- setup.cfg | 6 + 10 files changed, 1169 insertions(+), 2 deletions(-) create mode 100644 doc/source/cli/command-objects/registered-limit.rst create mode 100644 openstackclient/identity/v3/registered_limit.py create mode 100644 openstackclient/tests/functional/identity/v3/test_registered_limit.py create mode 100644 openstackclient/tests/unit/identity/v3/test_registered_limit.py create mode 100644 releasenotes/notes/bp-unified-limits-58f166401534a4ff.yaml diff --git a/doc/source/cli/command-objects/registered-limit.rst b/doc/source/cli/command-objects/registered-limit.rst new file mode 100644 index 0000000000..586fd1ffcc --- /dev/null +++ b/doc/source/cli/command-objects/registered-limit.rst @@ -0,0 +1,140 @@ +================ +registered limit +================ + +Identity v3 + +Registered limits are used to define default limits for resources within a +deployment. + +registered limit create +----------------------- + +Create a new registered limit + +.. program:: registered limit create +.. code:: bash + + openstack registered limit create + [--description ] + [--region ] + --service + --default-limit + + +.. option:: --description + + Useful description of the registered limit or its purpose + +.. option:: --region + + Region that the limit should be applied to + +.. describe:: --service + + The service that is responsible for the resource being limited (required) + +.. describe:: --default-limit + + The default limit for projects to assume unless explicitly overridden + (required) + +.. describe:: + + The name of the resource to limit (e.g. cores or volumes) + +registered limit delete +----------------------- + +Delete registered limit(s) + +.. program:: registered limit delete +.. code:: bash + + openstack registered limit delete + [ ...] + + +.. describe:: + + Registered limit(s) to delete (ID) + +registered limit list +--------------------- + +List registered limits + +.. program:: registered limit list +.. code:: bash + + openstack registered limit list + [--service ] + [--resource-name ] + [--region ] + +.. option:: --service + + The service to filter the response by (name or ID) + +.. option:: --resource-name + + The name of the resource to filter the response by + +.. option:: --region + + The region name to filter the response by + +registered limit show +--------------------- + +Display details about a registered limit + +.. program:: registered limit show +.. code:: bash + + openstack registered limit show + + +.. describe:: + + Registered limit to display (ID) + +registered limit set +-------------------- + +Update a registered limit + +.. program:: registered limit set +.. code:: bash + + openstack registered limit set + [--service ] + [--resource-name ] + [--default-limit ] + [--description ] + [--region ] + + +.. option:: --service + + The service of the registered limit to update (ID or name) + +.. option:: --resource-name + + The name of the resource for the limit + +.. option:: --default-limit + + The default limit for projects to assume for a given resource + +.. option:: --description + + A useful description of the limit or its purpose + +.. option:: --region + + The region the limit should apply to + +.. describe:: + + Registered limit to display (ID) diff --git a/lower-constraints.txt b/lower-constraints.txt index 88c75cf47b..bcfc57b37f 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -95,7 +95,7 @@ python-heatclient==1.10.0 python-ironic-inspector-client==1.5.0 python-ironicclient==2.3.0 python-karborclient==0.6.0 -python-keystoneclient==3.15.0 +python-keystoneclient==3.17.0 python-mimeparse==1.6.0 python-mistralclient==3.1.0 python-muranoclient==0.8.2 diff --git a/openstackclient/identity/v3/registered_limit.py b/openstackclient/identity/v3/registered_limit.py new file mode 100644 index 0000000000..72e07297e6 --- /dev/null +++ b/openstackclient/identity/v3/registered_limit.py @@ -0,0 +1,260 @@ +# 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. +# + +"""Registered limits 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 _ +from openstackclient.identity import common as common_utils + +LOG = logging.getLogger(__name__) + + +class CreateRegisteredLimit(command.ShowOne): + _description = _("Create a registered limit") + + def get_parser(self, prog_name): + parser = super(CreateRegisteredLimit, self).get_parser(prog_name) + parser.add_argument( + '--description', + metavar='', + help=_('Description of the registered limit'), + ) + parser.add_argument( + '--region', + metavar='', + help=_('Region for the registered limit to affect'), + ) + parser.add_argument( + '--service', + metavar='', + required=True, + help=_('Service responsible for the resource to limit (required)'), + ) + parser.add_argument( + '--default-limit', + type=int, + metavar='', + required=True, + help=_('The default limit for the resources to assume (required)'), + ) + parser.add_argument( + 'resource_name', + metavar='', + help=_('The name of the resource to limit'), + ) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + + service = utils.find_resource( + identity_client.services, parsed_args.service + ) + region = None + if parsed_args.region: + region = utils.find_resource( + identity_client.regions, parsed_args.region + ) + + registered_limit = identity_client.registered_limits.create( + service, + parsed_args.resource_name, + parsed_args.default_limit, + description=parsed_args.description, + region=region + ) + + registered_limit._info.pop('links', None) + return zip(*sorted(six.iteritems(registered_limit._info))) + + +class DeleteRegisteredLimit(command.Command): + _description = _("Delete a registered limit") + + def get_parser(self, prog_name): + parser = super(DeleteRegisteredLimit, self).get_parser(prog_name) + parser.add_argument( + 'registered_limit_id', + metavar='', + nargs="+", + help=_('Registered limit to delete (ID)'), + ) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + + errors = 0 + for registered_limit_id in parsed_args.registered_limit_id: + try: + identity_client.registered_limits.delete(registered_limit_id) + except Exception as e: + errors += 1 + from pprint import pprint + pprint(type(e)) + LOG.error(_("Failed to delete registered limit with ID " + "'%(id)s': %(e)s"), + {'id': registered_limit_id, 'e': e}) + + if errors > 0: + total = len(parsed_args.registered_limit_id) + msg = (_("%(errors)s of %(total)s registered limits failed to " + "delete.") % {'errors': errors, 'total': total}) + raise exceptions.CommandError(msg) + + +class ListRegisteredLimit(command.Lister): + _description = _("List registered limits") + + def get_parser(self, prog_name): + parser = super(ListRegisteredLimit, self).get_parser(prog_name) + parser.add_argument( + '--service', + metavar='', + help=_('Service responsible for the resource to limit'), + ) + parser.add_argument( + '--resource-name', + metavar='', + dest='resource_name', + help=_('The name of the resource to limit'), + ) + parser.add_argument( + '--region', + metavar='', + help=_('Region for the limit to affect.'), + ) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + + service = None + if parsed_args.service: + service = common_utils.find_service( + identity_client, parsed_args.service + ) + region = None + if parsed_args.region: + region = utils.find_resource( + identity_client.regions, parsed_args.region + ) + + registered_limits = identity_client.registered_limits.list( + service=service, + resource_name=parsed_args.resource_name, + region=region + ) + + columns = ( + 'ID', 'Service ID', 'Resource Name', 'Default Limit', + 'Description', 'Region ID' + ) + return ( + columns, + (utils.get_item_properties(s, columns) for s in registered_limits), + ) + + +class SetRegisteredLimit(command.ShowOne): + _description = _("Update information about a registered limit") + + def get_parser(self, prog_name): + parser = super(SetRegisteredLimit, self).get_parser(prog_name) + parser.add_argument( + 'registered_limit_id', + metavar='', + help=_('Registered limit to update (ID)'), + ) + parser.add_argument( + '--service', + metavar='', + help=_('Service responsible for the resource to limit'), + ) + parser.add_argument( + '--resource-name', + metavar='', + help=_('The name of the resource to limit'), + ) + parser.add_argument( + '--default-limit', + metavar='', + type=int, + help=_('The default limit for the resources to assume'), + ) + parser.add_argument( + '--description', + metavar='', + help=_('Description of the registered limit'), + ) + parser.add_argument( + '--region', + metavar='', + help=_('Region for the registered limit to affect.'), + ) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + + service = None + if parsed_args.service: + service = common_utils.find_service( + identity_client, parsed_args.service + ) + + region = None + if parsed_args.region: + region = utils.find_resource( + identity_client.regions, parsed_args.region + ) + + registered_limit = identity_client.registered_limits.update( + parsed_args.registered_limit_id, + service=service, + resource_name=parsed_args.resource_name, + default_limit=parsed_args.default_limit, + description=parsed_args.description, + region=region + ) + + registered_limit._info.pop('links', None) + return zip(*sorted(six.iteritems(registered_limit._info))) + + +class ShowRegisteredLimit(command.ShowOne): + _description = _("Display registered limit details") + + def get_parser(self, prog_name): + parser = super(ShowRegisteredLimit, self).get_parser(prog_name) + parser.add_argument( + 'registered_limit_id', + metavar='', + help=_('Registered limit to display (ID)'), + ) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + registered_limit = identity_client.registered_limits.get( + parsed_args.registered_limit_id + ) + registered_limit._info.pop('links', None) + return zip(*sorted(six.iteritems(registered_limit._info))) diff --git a/openstackclient/tests/functional/identity/v3/common.py b/openstackclient/tests/functional/identity/v3/common.py index 54132be58c..525a31a218 100644 --- a/openstackclient/tests/functional/identity/v3/common.py +++ b/openstackclient/tests/functional/identity/v3/common.py @@ -54,6 +54,11 @@ class IdentityTests(base.TestCase): 'Auth URL'] IMPLIED_ROLE_LIST_HEADERS = ['Prior Role ID', 'Prior Role Name', 'Implied Role ID', 'Implied Role Name'] + REGISTERED_LIMIT_FIELDS = ['id', 'service_id', 'resource_name', + 'default_limit', 'description', 'region_id'] + REGISTERED_LIMIT_LIST_HEADERS = ['ID', 'Service ID', 'Resource Name', + 'Default Limit', 'Description', + 'Region ID'] @classmethod def setUpClass(cls): @@ -319,3 +324,35 @@ def _create_dummy_sp(self, add_clean_up=True): items = self.parse_show(raw_output) self.assert_show_fields(items, self.SERVICE_PROVIDER_FIELDS) return service_provider + + def _create_dummy_registered_limit(self, add_clean_up=True): + service_name = self._create_dummy_service() + resource_name = data_utils.rand_name('resource_name') + params = { + 'service_name': service_name, + 'default_limit': 10, + 'resource_name': resource_name + } + raw_output = self.openstack( + 'registered limit create' + ' --service %(service_name)s' + ' --default-limit %(default_limit)s' + ' %(resource_name)s' % params + ) + items = self.parse_show(raw_output) + registered_limit_id = self._extract_value_from_items('id', items) + + if add_clean_up: + self.addCleanup( + self.openstack, + 'registered limit delete %s' % registered_limit_id + ) + + self.assert_show_fields(items, self.REGISTERED_LIMIT_FIELDS) + return registered_limit_id + + def _extract_value_from_items(self, key, items): + for d in items: + for k, v in d.iteritems(): + if k == key: + return v diff --git a/openstackclient/tests/functional/identity/v3/test_registered_limit.py b/openstackclient/tests/functional/identity/v3/test_registered_limit.py new file mode 100644 index 0000000000..09e90ce206 --- /dev/null +++ b/openstackclient/tests/functional/identity/v3/test_registered_limit.py @@ -0,0 +1,184 @@ +# 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 tempest.lib.common.utils import data_utils + +from openstackclient.tests.functional.identity.v3 import common + + +class RegisteredLimitTestCase(common.IdentityTests): + + def test_registered_limit_create_with_service_name(self): + self._create_dummy_registered_limit() + + def test_registered_limit_create_with_service_id(self): + service_name = self._create_dummy_service() + raw_output = self.openstack( + 'service show' + ' %(service_name)s' % {'service_name': service_name} + ) + service_items = self.parse_show(raw_output) + service_id = self._extract_value_from_items('id', service_items) + + raw_output = self.openstack( + 'registered limit create' + ' --service %(service_id)s' + ' --default-limit %(default_limit)s' + ' %(resource_name)s' % { + 'service_id': service_id, + 'default_limit': 10, + 'resource_name': 'cores' + } + ) + items = self.parse_show(raw_output) + registered_limit_id = self._extract_value_from_items('id', items) + self.addCleanup( + self.openstack, + 'registered limit delete' + ' %(registered_limit_id)s' % { + 'registered_limit_id': registered_limit_id + } + ) + + self.assert_show_fields(items, self.REGISTERED_LIMIT_FIELDS) + + def test_registered_limit_create_with_options(self): + service_name = self._create_dummy_service() + region_id = self._create_dummy_region() + params = { + 'service_name': service_name, + 'resource_name': 'cores', + 'default_limit': 10, + 'description': 'default limit for cores', + 'region_id': region_id + } + + raw_output = self.openstack( + 'registered limit create' + ' --description \'%(description)s\'' + ' --region %(region_id)s' + ' --service %(service_name)s' + ' --default-limit %(default_limit)s' + ' %(resource_name)s' % params + ) + items = self.parse_show(raw_output) + registered_limit_id = self._extract_value_from_items('id', items) + self.addCleanup( + self.openstack, + 'registered limit delete %(registered_limit_id)s' % { + 'registered_limit_id': registered_limit_id + } + ) + + self.assert_show_fields(items, self.REGISTERED_LIMIT_FIELDS) + + def test_registered_limit_show(self): + registered_limit_id = self._create_dummy_registered_limit() + raw_output = self.openstack( + 'registered limit show %(registered_limit_id)s' % { + 'registered_limit_id': registered_limit_id + } + ) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.REGISTERED_LIMIT_FIELDS) + + def test_registered_limit_set_region_id(self): + region_id = self._create_dummy_region() + registered_limit_id = self._create_dummy_registered_limit() + + params = { + 'registered_limit_id': registered_limit_id, + 'region_id': region_id + } + raw_output = self.openstack( + 'registered limit set' + ' %(registered_limit_id)s' + ' --region %(region_id)s' % params + ) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.REGISTERED_LIMIT_FIELDS) + + def test_registered_limit_set_description(self): + registered_limit_id = self._create_dummy_registered_limit() + params = { + 'registered_limit_id': registered_limit_id, + 'description': 'updated description' + } + raw_output = self.openstack( + 'registered limit set' + ' %(registered_limit_id)s' + ' --description \'%(description)s\'' % params + ) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.REGISTERED_LIMIT_FIELDS) + + def test_registered_limit_set_service(self): + registered_limit_id = self._create_dummy_registered_limit() + service_name = self._create_dummy_service() + params = { + 'registered_limit_id': registered_limit_id, + 'service': service_name + } + raw_output = self.openstack( + 'registered limit set' + ' %(registered_limit_id)s' + ' --service %(service)s' % params + ) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.REGISTERED_LIMIT_FIELDS) + + def test_registered_limit_set_default_limit(self): + registered_limit_id = self._create_dummy_registered_limit() + params = { + 'registered_limit_id': registered_limit_id, + 'default_limit': 20 + } + raw_output = self.openstack( + 'registered limit set' + ' %(registered_limit_id)s' + ' --default-limit %(default_limit)s' % params + ) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.REGISTERED_LIMIT_FIELDS) + + def test_registered_limit_set_resource_name(self): + registered_limit_id = self._create_dummy_registered_limit() + resource_name = data_utils.rand_name('resource_name') + params = { + 'registered_limit_id': registered_limit_id, + 'resource_name': resource_name + } + raw_output = self.openstack( + 'registered limit set' + ' %(registered_limit_id)s' + ' --resource-name %(resource_name)s' % params + ) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.REGISTERED_LIMIT_FIELDS) + + def test_registered_limit_list(self): + self._create_dummy_registered_limit() + raw_output = self.openstack('registered limit list') + items = self.parse_listing(raw_output) + self.assert_table_structure(items, self.REGISTERED_LIMIT_LIST_HEADERS) + + def test_registered_limit_delete(self): + registered_limit_id = self._create_dummy_registered_limit( + add_clean_up=False + ) + raw_output = self.openstack( + 'registered limit delete' + ' %(registered_limit_id)s' % { + 'registered_limit_id': registered_limit_id + } + ) + self.assertEqual(0, len(raw_output)) diff --git a/openstackclient/tests/unit/identity/v3/fakes.py b/openstackclient/tests/unit/identity/v3/fakes.py index 7aa9cd7ce2..3cae45157d 100644 --- a/openstackclient/tests/unit/identity/v3/fakes.py +++ b/openstackclient/tests/unit/identity/v3/fakes.py @@ -486,6 +486,27 @@ 'secret': app_cred_secret } +registered_limit_id = 'registered-limit-id' +registered_limit_default_limit = 10 +registered_limit_description = 'default limit of foobars' +registered_limit_resource_name = 'foobars' +REGISTERED_LIMIT = { + 'id': registered_limit_id, + 'default_limit': registered_limit_default_limit, + 'resource_name': registered_limit_resource_name, + 'service_id': service_id, + 'description': None, + 'region_id': None +} +REGISTERED_LIMIT_OPTIONS = { + 'id': registered_limit_id, + 'default_limit': registered_limit_default_limit, + 'resource_name': registered_limit_resource_name, + 'service_id': service_id, + 'description': registered_limit_description, + 'region_id': region_id +} + def fake_auth_ref(fake_token, fake_service=None): """Create an auth_ref using keystoneauth's fixtures""" @@ -578,6 +599,8 @@ def __init__(self, **kwargs): {}) self.inference_rules = mock.Mock() self.inference_rules.resource_class = fakes.FakeResource(None, {}) + self.registered_limits = mock.Mock() + self.registered_limits.resource_class = fakes.FakeResource(None, {}) class FakeFederationManager(object): diff --git a/openstackclient/tests/unit/identity/v3/test_registered_limit.py b/openstackclient/tests/unit/identity/v3/test_registered_limit.py new file mode 100644 index 0000000000..262ca4f98f --- /dev/null +++ b/openstackclient/tests/unit/identity/v3/test_registered_limit.py @@ -0,0 +1,510 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import copy + +from keystoneauth1.exceptions import http as ksa_exceptions +from osc_lib import exceptions + +from openstackclient.identity.v3 import registered_limit +from openstackclient.tests.unit import fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes + + +class TestRegisteredLimit(identity_fakes.TestIdentityv3): + + def setUp(self): + super(TestRegisteredLimit, self).setUp() + + identity_manager = self.app.client_manager.identity + self.registered_limit_mock = identity_manager.registered_limits + + self.services_mock = identity_manager.services + self.services_mock.reset_mock() + + self.regions_mock = identity_manager.regions + self.regions_mock.reset_mock() + + +class TestRegisteredLimitCreate(TestRegisteredLimit): + + def setUp(self): + super(TestRegisteredLimitCreate, self).setUp() + + self.service = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.SERVICE), + loaded=True + ) + self.services_mock.get.return_value = self.service + + self.region = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.REGION), + loaded=True + ) + self.regions_mock.get.return_value = self.region + + self.cmd = registered_limit.CreateRegisteredLimit(self.app, None) + + def test_registered_limit_create_without_options(self): + self.registered_limit_mock.create.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.REGISTERED_LIMIT), + loaded=True + ) + + resource_name = identity_fakes.registered_limit_resource_name + default_limit = identity_fakes.registered_limit_default_limit + arglist = [ + '--service', identity_fakes.service_id, + '--default-limit', '10', + resource_name, + ] + + verifylist = [ + ('service', identity_fakes.service_id), + ('default_limit', default_limit), + ('resource_name', resource_name) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + kwargs = {'description': None, 'region': None} + self.registered_limit_mock.create.assert_called_with( + self.service, resource_name, default_limit, **kwargs + ) + + collist = ('default_limit', 'description', 'id', 'region_id', + 'resource_name', 'service_id') + + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.registered_limit_default_limit, + None, + identity_fakes.registered_limit_id, + None, + identity_fakes.registered_limit_resource_name, + identity_fakes.service_id + ) + self.assertEqual(datalist, data) + + def test_registered_limit_create_with_options(self): + self.registered_limit_mock.create.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.REGISTERED_LIMIT_OPTIONS), + loaded=True + ) + + resource_name = identity_fakes.registered_limit_resource_name + default_limit = identity_fakes.registered_limit_default_limit + description = identity_fakes.registered_limit_description + arglist = [ + '--region', identity_fakes.region_id, + '--description', description, + '--service', identity_fakes.service_id, + '--default-limit', '10', + resource_name + ] + + verifylist = [ + ('region', identity_fakes.region_id), + ('description', description), + ('service', identity_fakes.service_id), + ('default_limit', default_limit), + ('resource_name', resource_name) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + kwargs = {'description': description, 'region': self.region} + self.registered_limit_mock.create.assert_called_with( + self.service, resource_name, default_limit, **kwargs + ) + + collist = ('default_limit', 'description', 'id', 'region_id', + 'resource_name', 'service_id') + + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.registered_limit_default_limit, + description, + identity_fakes.registered_limit_id, + identity_fakes.region_id, + identity_fakes.registered_limit_resource_name, + identity_fakes.service_id + ) + self.assertEqual(datalist, data) + + +class TestRegisteredLimitDelete(TestRegisteredLimit): + + def setUp(self): + super(TestRegisteredLimitDelete, self).setUp() + + self.cmd = registered_limit.DeleteRegisteredLimit(self.app, None) + + def test_registered_limit_delete(self): + self.registered_limit_mock.delete.return_value = None + + arglist = [identity_fakes.registered_limit_id] + verifylist = [ + ('registered_limit_id', [identity_fakes.registered_limit_id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.registered_limit_mock.delete.assert_called_with( + identity_fakes.registered_limit_id + ) + self.assertIsNone(result) + + def test_registered_limit_delete_with_exception(self): + return_value = ksa_exceptions.NotFound() + self.registered_limit_mock.delete.side_effect = return_value + + arglist = ['fake-registered-limit-id'] + verifylist = [ + ('registered_limit_id', ['fake-registered-limit-id']) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual( + '1 of 1 registered limits failed to delete.', str(e) + ) + + +class TestRegisteredLimitShow(TestRegisteredLimit): + + def setUp(self): + super(TestRegisteredLimitShow, self).setUp() + + self.registered_limit_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.REGISTERED_LIMIT), + loaded=True + ) + + self.cmd = registered_limit.ShowRegisteredLimit(self.app, None) + + def test_registered_limit_show(self): + arglist = [identity_fakes.registered_limit_id] + verifylist = [ + ('registered_limit_id', identity_fakes.registered_limit_id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.registered_limit_mock.get.assert_called_with( + identity_fakes.registered_limit_id + ) + + collist = ( + 'default_limit', 'description', 'id', 'region_id', 'resource_name', + 'service_id' + ) + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.registered_limit_default_limit, + None, + identity_fakes.registered_limit_id, + None, + identity_fakes.registered_limit_resource_name, + identity_fakes.service_id + ) + self.assertEqual(datalist, data) + + +class TestRegisteredLimitSet(TestRegisteredLimit): + + def setUp(self): + super(TestRegisteredLimitSet, self).setUp() + self.cmd = registered_limit.SetRegisteredLimit(self.app, None) + + def test_registered_limit_set_description(self): + registered_limit = copy.deepcopy(identity_fakes.REGISTERED_LIMIT) + registered_limit['description'] = ( + identity_fakes.registered_limit_description + ) + self.registered_limit_mock.update.return_value = fakes.FakeResource( + None, registered_limit, loaded=True + ) + + arglist = [ + '--description', identity_fakes.registered_limit_description, + identity_fakes.registered_limit_id + ] + verifylist = [ + ('description', identity_fakes.registered_limit_description), + ('registered_limit_id', identity_fakes.registered_limit_id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.registered_limit_mock.update.assert_called_with( + identity_fakes.registered_limit_id, + service=None, + resource_name=None, + default_limit=None, + description=identity_fakes.registered_limit_description, + region=None + ) + + collist = ( + 'default_limit', 'description', 'id', 'region_id', 'resource_name', + 'service_id' + ) + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.registered_limit_default_limit, + identity_fakes.registered_limit_description, + identity_fakes.registered_limit_id, + None, + identity_fakes.registered_limit_resource_name, + identity_fakes.service_id + ) + self.assertEqual(datalist, data) + + def test_registered_limit_set_default_limit(self): + registered_limit = copy.deepcopy(identity_fakes.REGISTERED_LIMIT) + default_limit = 20 + registered_limit['default_limit'] = default_limit + self.registered_limit_mock.update.return_value = fakes.FakeResource( + None, registered_limit, loaded=True + ) + + arglist = [ + '--default-limit', str(default_limit), + identity_fakes.registered_limit_id + ] + verifylist = [ + ('default_limit', default_limit), + ('registered_limit_id', identity_fakes.registered_limit_id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.registered_limit_mock.update.assert_called_with( + identity_fakes.registered_limit_id, + service=None, + resource_name=None, + default_limit=default_limit, + description=None, + region=None + ) + + collist = ( + 'default_limit', 'description', 'id', 'region_id', 'resource_name', + 'service_id' + ) + self.assertEqual(collist, columns) + datalist = ( + default_limit, + None, + identity_fakes.registered_limit_id, + None, + identity_fakes.registered_limit_resource_name, + identity_fakes.service_id + ) + self.assertEqual(datalist, data) + + def test_registered_limit_set_resource_name(self): + registered_limit = copy.deepcopy(identity_fakes.REGISTERED_LIMIT) + resource_name = 'volumes' + registered_limit['resource_name'] = resource_name + self.registered_limit_mock.update.return_value = fakes.FakeResource( + None, registered_limit, loaded=True + ) + + arglist = [ + '--resource-name', resource_name, + identity_fakes.registered_limit_id + ] + verifylist = [ + ('resource_name', resource_name), + ('registered_limit_id', identity_fakes.registered_limit_id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.registered_limit_mock.update.assert_called_with( + identity_fakes.registered_limit_id, + service=None, + resource_name=resource_name, + default_limit=None, + description=None, + region=None + ) + + collist = ( + 'default_limit', 'description', 'id', 'region_id', 'resource_name', + 'service_id' + ) + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.registered_limit_default_limit, + None, + identity_fakes.registered_limit_id, + None, + resource_name, + identity_fakes.service_id + ) + self.assertEqual(datalist, data) + + def test_registered_limit_set_service(self): + registered_limit = copy.deepcopy(identity_fakes.REGISTERED_LIMIT) + service = identity_fakes.FakeService.create_one_service() + registered_limit['service_id'] = service.id + self.registered_limit_mock.update.return_value = fakes.FakeResource( + None, registered_limit, loaded=True + ) + self.services_mock.get.return_value = service + + arglist = [ + '--service', service.id, + identity_fakes.registered_limit_id + ] + verifylist = [ + ('service', service.id), + ('registered_limit_id', identity_fakes.registered_limit_id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.registered_limit_mock.update.assert_called_with( + identity_fakes.registered_limit_id, + service=service, + resource_name=None, + default_limit=None, + description=None, + region=None + ) + + collist = ( + 'default_limit', 'description', 'id', 'region_id', 'resource_name', + 'service_id' + ) + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.registered_limit_default_limit, + None, + identity_fakes.registered_limit_id, + None, + identity_fakes.registered_limit_resource_name, + service.id + ) + self.assertEqual(datalist, data) + + def test_registered_limit_set_region(self): + registered_limit = copy.deepcopy(identity_fakes.REGISTERED_LIMIT) + region = identity_fakes.REGION + region['id'] = 'RegionTwo' + region = fakes.FakeResource( + None, + copy.deepcopy(region), + loaded=True + ) + registered_limit['region_id'] = region.id + self.registered_limit_mock.update.return_value = fakes.FakeResource( + None, registered_limit, loaded=True + ) + self.regions_mock.get.return_value = region + + arglist = [ + '--region', region.id, + identity_fakes.registered_limit_id + ] + verifylist = [ + ('region', region.id), + ('registered_limit_id', identity_fakes.registered_limit_id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.registered_limit_mock.update.assert_called_with( + identity_fakes.registered_limit_id, + service=None, + resource_name=None, + default_limit=None, + description=None, + region=region + ) + + collist = ( + 'default_limit', 'description', 'id', 'region_id', 'resource_name', + 'service_id' + ) + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.registered_limit_default_limit, + None, + identity_fakes.registered_limit_id, + region.id, + identity_fakes.registered_limit_resource_name, + identity_fakes.service_id + ) + self.assertEqual(datalist, data) + + +class TestRegisteredLimitList(TestRegisteredLimit): + + def setUp(self): + super(TestRegisteredLimitList, self).setUp() + + self.registered_limit_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.REGISTERED_LIMIT), + loaded=True + ) + + self.cmd = registered_limit.ShowRegisteredLimit(self.app, None) + + def test_limit_show(self): + arglist = [identity_fakes.registered_limit_id] + verifylist = [ + ('registered_limit_id', identity_fakes.registered_limit_id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.registered_limit_mock.get.assert_called_with( + identity_fakes.registered_limit_id + ) + + collist = ( + 'default_limit', 'description', 'id', 'region_id', 'resource_name', + 'service_id' + ) + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.registered_limit_default_limit, + None, + identity_fakes.registered_limit_id, + None, + identity_fakes.registered_limit_resource_name, + identity_fakes.service_id + ) + self.assertEqual(datalist, data) diff --git a/releasenotes/notes/bp-unified-limits-58f166401534a4ff.yaml b/releasenotes/notes/bp-unified-limits-58f166401534a4ff.yaml new file mode 100644 index 0000000000..20050bb264 --- /dev/null +++ b/releasenotes/notes/bp-unified-limits-58f166401534a4ff.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + [`bp unified-limits `_] + Support has been added for managing registered limits in keystone via the + ``registered limit`` command. Registered limits define limits of resources + for projects to assume by default. diff --git a/requirements.txt b/requirements.txt index c5795fd5ed..175ce74c5c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,6 +12,6 @@ osc-lib>=1.8.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 python-glanceclient>=2.8.0 # Apache-2.0 -python-keystoneclient>=3.15.0 # Apache-2.0 +python-keystoneclient>=3.17.0 # Apache-2.0 python-novaclient>=9.1.0 # Apache-2.0 python-cinderclient>=3.3.0 # Apache-2.0 diff --git a/setup.cfg b/setup.cfg index ac45a77dcd..af7bbbf20e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -301,6 +301,12 @@ openstack.identity.v3 = region_set = openstackclient.identity.v3.region:SetRegion region_show = openstackclient.identity.v3.region:ShowRegion + registered_limit_create = openstackclient.identity.v3.registered_limit:CreateRegisteredLimit + registered_limit_delete = openstackclient.identity.v3.registered_limit:DeleteRegisteredLimit + registered_limit_list = openstackclient.identity.v3.registered_limit:ListRegisteredLimit + registered_limit_set = openstackclient.identity.v3.registered_limit:SetRegisteredLimit + registered_limit_show = openstackclient.identity.v3.registered_limit:ShowRegisteredLimit + request_token_authorize = openstackclient.identity.v3.token:AuthorizeRequestToken request_token_create = openstackclient.identity.v3.token:CreateRequestToken From 735896eb1ad4543823008693c9d337691512f8bf Mon Sep 17 00:00:00 2001 From: Lance Bragstad Date: Wed, 13 Jun 2018 19:33:08 +0000 Subject: [PATCH 1951/3095] Implement support for project limits This commit let's users manage limits via the command line. bp unified-limits Change-Id: I7c44bbb60557378b66c5c43a7ba917f40dc2b633 --- doc/source/cli/command-objects/limit.rst | 128 ++++++ openstackclient/identity/v3/limit.py | 238 +++++++++++ .../tests/functional/identity/v3/common.py | 43 ++ .../functional/identity/v3/test_limit.py | 192 +++++++++ .../tests/unit/identity/v3/fakes.py | 25 ++ .../tests/unit/identity/v3/test_limit.py | 382 ++++++++++++++++++ .../bp-unified-limits-6c5fdb1c26805d86.yaml | 7 + setup.cfg | 6 + 8 files changed, 1021 insertions(+) create mode 100644 doc/source/cli/command-objects/limit.rst create mode 100644 openstackclient/identity/v3/limit.py create mode 100644 openstackclient/tests/functional/identity/v3/test_limit.py create mode 100644 openstackclient/tests/unit/identity/v3/test_limit.py create mode 100644 releasenotes/notes/bp-unified-limits-6c5fdb1c26805d86.yaml diff --git a/doc/source/cli/command-objects/limit.rst b/doc/source/cli/command-objects/limit.rst new file mode 100644 index 0000000000..71cf2a420d --- /dev/null +++ b/doc/source/cli/command-objects/limit.rst @@ -0,0 +1,128 @@ +===== +limit +===== + +Identity v3 + +Limits are used to specify project-specific limits thresholds of resources. + +limit create +------------ + +Create a new limit + +.. program:: limit create +.. code:: bash + + openstack limit create + [--description ] + [--region ] + --project + --service + --resource-limit + + +.. option:: --description + + Useful description of the limit or its purpose + +.. option:: --region + + Region that the limit should be applied to + +.. describe:: --project + + The project that the limit applies to (required) + +.. describe:: --service + + The service that is responsible for the resource being limited (required) + +.. describe:: --resource-limit + + The limit to apply to the project (required) + +.. describe:: + + The name of the resource to limit (e.g. cores or volumes) + +limit delete +------------ + +Delete project-specific limit(s) + +.. program:: limit delete +.. code:: bash + + openstack limit delete + [ ...] + +.. describe:: + + Limit(s) to delete (ID) + +limit list +---------- + +List project-specific limits + +.. program:: limit list +.. code:: bash + + openstack limit list + [--service ] + [--resource-name ] + [--region ] + +.. option:: --service + + The service to filter the response by (name or ID) + +.. option:: --resource-name + + The name of the resource to filter the response by + +.. option:: --region + + The region name to filter the response by + +limit show +---------- + +Display details about a limit + +.. program:: limit show +.. code:: bash + + openstack limit show + + +.. describe:: + + Limit to display (ID) + +limit set +--------- + +Update a limit + +.. program:: limit show +.. code:: bash + + openstack limit set + [--description ] + [--resource-limit ] + + + +.. option:: --description + + Useful description of the limit or its purpose + +.. option:: --resource-limit + + The limit to apply to the project + +.. describe:: + + Limit to update (ID) diff --git a/openstackclient/identity/v3/limit.py b/openstackclient/identity/v3/limit.py new file mode 100644 index 0000000000..c6f1cb1fb5 --- /dev/null +++ b/openstackclient/identity/v3/limit.py @@ -0,0 +1,238 @@ +# 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. +# + +"""Limits 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 _ +from openstackclient.identity import common as common_utils + +LOG = logging.getLogger(__name__) + + +class CreateLimit(command.ShowOne): + _description = _("Create a limit") + + def get_parser(self, prog_name): + parser = super(CreateLimit, self).get_parser(prog_name) + parser.add_argument( + '--description', + metavar='', + help=_('Description of the limit'), + ) + parser.add_argument( + '--region', + metavar='', + help=_('Region for the limit to affect.'), + ) + parser.add_argument( + '--project', + metavar='', + required=True, + help=_('Project to associate the resource limit to'), + ) + parser.add_argument( + '--service', + metavar='', + required=True, + help=_('Service responsible for the resource to limit'), + ) + parser.add_argument( + '--resource-limit', + metavar='', + required=True, + type=int, + help=_('The resource limit for the project to assume'), + ) + parser.add_argument( + 'resource_name', + metavar='', + help=_('The name of the resource to limit'), + ) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + + project = common_utils.find_project( + identity_client, parsed_args.project + ) + service = common_utils.find_service( + identity_client, parsed_args.service + ) + region = None + if parsed_args.region: + region = utils.find_resource( + identity_client.regions, parsed_args.region + ) + + limit = identity_client.limits.create( + project, + service, + parsed_args.resource_name, + parsed_args.resource_limit, + description=parsed_args.description, + region=region + ) + + limit._info.pop('links', None) + return zip(*sorted(six.iteritems(limit._info))) + + +class ListLimit(command.Lister): + _description = _("List limits") + + def get_parser(self, prog_name): + parser = super(ListLimit, self).get_parser(prog_name) + parser.add_argument( + '--service', + metavar='', + help=_('Service responsible for the resource to limit'), + ) + parser.add_argument( + '--resource-name', + metavar='', + dest='resource_name', + help=_('The name of the resource to limit'), + ) + parser.add_argument( + '--region', + metavar='', + help=_('Region for the registered limit to affect.'), + ) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + + service = None + if parsed_args.service: + service = common_utils.find_service( + identity_client, parsed_args.service + ) + region = None + if parsed_args.region: + region = utils.find_resource( + identity_client.regions, parsed_args.region + ) + + limits = identity_client.limits.list( + service=service, + resource_name=parsed_args.resource_name, + region=region + ) + + columns = ( + 'ID', 'Project ID', 'Service ID', 'Resource Name', + 'Resource Limit', 'Description', 'Region ID' + ) + return ( + columns, + (utils.get_item_properties(s, columns) for s in limits), + ) + + +class ShowLimit(command.ShowOne): + _description = _("Display limit details") + + def get_parser(self, prog_name): + parser = super(ShowLimit, self).get_parser(prog_name) + parser.add_argument( + 'limit_id', + metavar='', + help=_('Limit to display (ID)'), + ) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + limit = identity_client.limits.get(parsed_args.limit_id) + limit._info.pop('links', None) + return zip(*sorted(six.iteritems(limit._info))) + + +class SetLimit(command.ShowOne): + _description = _("Update information about a limit") + + def get_parser(self, prog_name): + parser = super(SetLimit, self).get_parser(prog_name) + parser.add_argument( + 'limit_id', + metavar='', + help=_('Limit to update (ID)'), + ) + parser.add_argument( + '--description', + metavar='', + help=_('Description of the limit'), + ) + parser.add_argument( + '--resource-limit', + metavar='', + dest='resource_limit', + type=int, + help=_('The resource limit for the project to assume'), + ) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + + limit = identity_client.limits.update( + parsed_args.limit_id, + description=parsed_args.description, + resource_limit=parsed_args.resource_limit + ) + + limit._info.pop('links', None) + + return zip(*sorted(six.iteritems(limit._info))) + + +class DeleteLimit(command.Command): + _description = _("Delete a limit") + + def get_parser(self, prog_name): + parser = super(DeleteLimit, self).get_parser(prog_name) + parser.add_argument( + 'limit_id', + metavar='', + nargs="+", + help=_('Limit to delete (ID)'), + ) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + + errors = 0 + for limit_id in parsed_args.limit_id: + try: + identity_client.limits.delete(limit_id) + except Exception as e: + errors += 1 + LOG.error(_("Failed to delete limit with ID " + "'%(id)s': %(e)s"), + {'id': limit_id, 'e': e}) + + if errors > 0: + total = len(parsed_args.limit_id) + msg = (_("%(errors)s of %(total)s limits failed to " + "delete.") % {'errors': errors, 'total': total}) + raise exceptions.CommandError(msg) diff --git a/openstackclient/tests/functional/identity/v3/common.py b/openstackclient/tests/functional/identity/v3/common.py index 525a31a218..58468bc7e7 100644 --- a/openstackclient/tests/functional/identity/v3/common.py +++ b/openstackclient/tests/functional/identity/v3/common.py @@ -59,6 +59,10 @@ class IdentityTests(base.TestCase): REGISTERED_LIMIT_LIST_HEADERS = ['ID', 'Service ID', 'Resource Name', 'Default Limit', 'Description', 'Region ID'] + LIMIT_FIELDS = ['id', 'project_id', 'service_id', 'resource_name', + 'resource_limit', 'description', 'region_id'] + LIMIT_LIST_HEADERS = ['ID', 'Project ID', 'Service ID', 'Resource Name', + 'Resource Limit', 'Description', 'Region ID'] @classmethod def setUpClass(cls): @@ -356,3 +360,42 @@ def _extract_value_from_items(self, key, items): for k, v in d.iteritems(): if k == key: return v + + def _create_dummy_limit(self, add_clean_up=True): + registered_limit_id = self._create_dummy_registered_limit() + + raw_output = self.openstack( + 'registered limit show %s' % registered_limit_id + ) + items = self.parse_show(raw_output) + resource_name = self._extract_value_from_items('resource_name', items) + service_id = self._extract_value_from_items('service_id', items) + resource_limit = 15 + + project_name = self._create_dummy_project() + raw_output = self.openstack('project show %s' % project_name) + items = self.parse_show(raw_output) + project_id = self._extract_value_from_items('id', items) + + params = { + 'project_id': project_id, + 'service_id': service_id, + 'resource_name': resource_name, + 'resource_limit': resource_limit + } + + raw_output = self.openstack( + 'limit create' + ' --project %(project_id)s' + ' --service %(service_id)s' + ' --resource-limit %(resource_limit)s' + ' %(resource_name)s' % params + ) + items = self.parse_show(raw_output) + limit_id = self._extract_value_from_items('id', items) + + if add_clean_up: + self.addCleanup(self.openstack, 'limit delete %s' % limit_id) + + self.assert_show_fields(items, self.LIMIT_FIELDS) + return limit_id diff --git a/openstackclient/tests/functional/identity/v3/test_limit.py b/openstackclient/tests/functional/identity/v3/test_limit.py new file mode 100644 index 0000000000..03bcb06e4b --- /dev/null +++ b/openstackclient/tests/functional/identity/v3/test_limit.py @@ -0,0 +1,192 @@ +# 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 tempest.lib.common.utils import data_utils + +from openstackclient.tests.functional.identity.v3 import common + + +class LimitTestCase(common.IdentityTests): + + def test_limit_create_with_service_name(self): + registered_limit_id = self._create_dummy_registered_limit() + raw_output = self.openstack( + 'registered limit show %s' % registered_limit_id + ) + items = self.parse_show(raw_output) + service_id = self._extract_value_from_items('service_id', items) + resource_name = self._extract_value_from_items('resource_name', items) + + raw_output = self.openstack('service show %s' % service_id) + items = self.parse_show(raw_output) + service_name = self._extract_value_from_items('name', items) + + project_name = self._create_dummy_project() + raw_output = self.openstack('project show %s' % project_name) + items = self.parse_show(raw_output) + project_id = self._extract_value_from_items('id', items) + + params = { + 'project_id': project_id, + 'service_name': service_name, + 'resource_name': resource_name, + 'resource_limit': 15 + } + raw_output = self.openstack( + 'limit create' + ' --project %(project_id)s' + ' --service %(service_name)s' + ' --resource-limit %(resource_limit)s' + ' %(resource_name)s' % params + ) + items = self.parse_show(raw_output) + limit_id = self._extract_value_from_items('id', items) + self.addCleanup(self.openstack, 'limit delete %s' % limit_id) + + self.assert_show_fields(items, self.LIMIT_FIELDS) + + def test_limit_create_with_project_name(self): + registered_limit_id = self._create_dummy_registered_limit() + raw_output = self.openstack( + 'registered limit show %s' % registered_limit_id + ) + items = self.parse_show(raw_output) + service_id = self._extract_value_from_items('service_id', items) + resource_name = self._extract_value_from_items('resource_name', items) + + raw_output = self.openstack('service show %s' % service_id) + items = self.parse_show(raw_output) + service_name = self._extract_value_from_items('name', items) + + project_name = self._create_dummy_project() + + params = { + 'project_name': project_name, + 'service_name': service_name, + 'resource_name': resource_name, + 'resource_limit': 15 + } + raw_output = self.openstack( + 'limit create' + ' --project %(project_name)s' + ' --service %(service_name)s' + ' --resource-limit %(resource_limit)s' + ' %(resource_name)s' % params + ) + items = self.parse_show(raw_output) + limit_id = self._extract_value_from_items('id', items) + self.addCleanup(self.openstack, 'limit delete %s' % limit_id) + + self.assert_show_fields(items, self.LIMIT_FIELDS) + registered_limit_id = self._create_dummy_registered_limit() + + def test_limit_create_with_service_id(self): + self._create_dummy_limit() + + def test_limit_create_with_project_id(self): + self._create_dummy_limit() + + def test_limit_create_with_options(self): + registered_limit_id = self._create_dummy_registered_limit() + region_id = self._create_dummy_region() + + params = { + 'region_id': region_id, + 'registered_limit_id': registered_limit_id + } + + raw_output = self.openstack( + 'registered limit set' + ' %(registered_limit_id)s' + ' --region %(region_id)s' % params + ) + items = self.parse_show(raw_output) + service_id = self._extract_value_from_items('service_id', items) + resource_name = self._extract_value_from_items('resource_name', items) + + project_name = self._create_dummy_project() + raw_output = self.openstack('project show %s' % project_name) + items = self.parse_show(raw_output) + project_id = self._extract_value_from_items('id', items) + description = data_utils.arbitrary_string() + + params = { + 'project_id': project_id, + 'service_id': service_id, + 'resource_name': resource_name, + 'resource_limit': 15, + 'region_id': region_id, + 'description': description + } + raw_output = self.openstack( + 'limit create' + ' --project %(project_id)s' + ' --service %(service_id)s' + ' --resource-limit %(resource_limit)s' + ' --region %(region_id)s' + ' --description %(description)s' + ' %(resource_name)s' % params + ) + items = self.parse_show(raw_output) + limit_id = self._extract_value_from_items('id', items) + self.addCleanup(self.openstack, 'limit delete %s' % limit_id) + + self.assert_show_fields(items, self.LIMIT_FIELDS) + + def test_limit_show(self): + limit_id = self._create_dummy_limit() + raw_output = self.openstack('limit show %s' % limit_id) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.LIMIT_FIELDS) + + def test_limit_set_description(self): + limit_id = self._create_dummy_limit() + + params = { + 'description': data_utils.arbitrary_string(), + 'limit_id': limit_id + } + + raw_output = self.openstack( + 'limit set' + ' --description %(description)s' + ' %(limit_id)s' % params + ) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.LIMIT_FIELDS) + + def test_limit_set_resource_limit(self): + limit_id = self._create_dummy_limit() + + params = { + 'resource_limit': 5, + 'limit_id': limit_id + } + + raw_output = self.openstack( + 'limit set' + ' --resource-limit %(resource_limit)s' + ' %(limit_id)s' % params + ) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.LIMIT_FIELDS) + + def test_limit_list(self): + self._create_dummy_limit() + raw_output = self.openstack('limit list') + items = self.parse_listing(raw_output) + self.assert_table_structure(items, self.LIMIT_LIST_HEADERS) + + def test_limit_delete(self): + limit_id = self._create_dummy_limit(add_clean_up=False) + raw_output = self.openstack('limit delete %s' % limit_id) + self.assertEqual(0, len(raw_output)) diff --git a/openstackclient/tests/unit/identity/v3/fakes.py b/openstackclient/tests/unit/identity/v3/fakes.py index 3cae45157d..27ee9fd026 100644 --- a/openstackclient/tests/unit/identity/v3/fakes.py +++ b/openstackclient/tests/unit/identity/v3/fakes.py @@ -507,6 +507,29 @@ 'region_id': region_id } +limit_id = 'limit-id' +limit_resource_limit = 15 +limit_description = 'limit of foobars' +limit_resource_name = 'foobars' +LIMIT = { + 'id': limit_id, + 'project_id': project_id, + 'resource_limit': limit_resource_limit, + 'resource_name': limit_resource_name, + 'service_id': service_id, + 'description': None, + 'region_id': None +} +LIMIT_OPTIONS = { + 'id': limit_id, + 'project_id': project_id, + 'resource_limit': limit_resource_limit, + 'resource_name': limit_resource_name, + 'service_id': service_id, + 'description': limit_description, + 'region_id': region_id +} + def fake_auth_ref(fake_token, fake_service=None): """Create an auth_ref using keystoneauth's fixtures""" @@ -601,6 +624,8 @@ def __init__(self, **kwargs): self.inference_rules.resource_class = fakes.FakeResource(None, {}) self.registered_limits = mock.Mock() self.registered_limits.resource_class = fakes.FakeResource(None, {}) + self.limits = mock.Mock() + self.limits.resource_class = fakes.FakeResource(None, {}) class FakeFederationManager(object): diff --git a/openstackclient/tests/unit/identity/v3/test_limit.py b/openstackclient/tests/unit/identity/v3/test_limit.py new file mode 100644 index 0000000000..44c0358d89 --- /dev/null +++ b/openstackclient/tests/unit/identity/v3/test_limit.py @@ -0,0 +1,382 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import copy + +from keystoneauth1.exceptions import http as ksa_exceptions +from osc_lib import exceptions + +from openstackclient.identity.v3 import limit +from openstackclient.tests.unit import fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes + + +class TestLimit(identity_fakes.TestIdentityv3): + + def setUp(self): + super(TestLimit, self).setUp() + + identity_manager = self.app.client_manager.identity + + self.limit_mock = identity_manager.limits + + self.services_mock = identity_manager.services + self.services_mock.reset_mock() + + self.projects_mock = identity_manager.projects + self.projects_mock.reset_mock() + + self.regions_mock = identity_manager.regions + self.regions_mock.reset_mock() + + +class TestLimitCreate(TestLimit): + + def setUp(self): + super(TestLimitCreate, self).setUp() + + self.service = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.SERVICE), + loaded=True + ) + self.services_mock.get.return_value = self.service + + self.project = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.PROJECT), + loaded=True + ) + self.projects_mock.get.return_value = self.project + + self.region = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.REGION), + loaded=True + ) + self.regions_mock.get.return_value = self.region + + self.cmd = limit.CreateLimit(self.app, None) + + def test_limit_create_without_options(self): + self.limit_mock.create.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.LIMIT), + loaded=True + ) + + resource_limit = 15 + arglist = [ + '--project', identity_fakes.project_id, + '--service', identity_fakes.service_id, + '--resource-limit', str(resource_limit), + identity_fakes.limit_resource_name + ] + verifylist = [ + ('project', identity_fakes.project_id), + ('service', identity_fakes.service_id), + ('resource_name', identity_fakes.limit_resource_name), + ('resource_limit', resource_limit) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + kwargs = {'description': None, 'region': None} + self.limit_mock.create.assert_called_with( + self.project, + self.service, + identity_fakes.limit_resource_name, + resource_limit, + **kwargs + ) + + collist = ('description', 'id', 'project_id', 'region_id', + 'resource_limit', 'resource_name', 'service_id') + self.assertEqual(collist, columns) + datalist = ( + None, + identity_fakes.limit_id, + identity_fakes.project_id, + None, + resource_limit, + identity_fakes.limit_resource_name, + identity_fakes.service_id + ) + self.assertEqual(datalist, data) + + def test_limit_create_with_options(self): + self.limit_mock.create.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.LIMIT_OPTIONS), + loaded=True + ) + + resource_limit = 15 + arglist = [ + '--project', identity_fakes.project_id, + '--service', identity_fakes.service_id, + '--resource-limit', str(resource_limit), + '--region', identity_fakes.region_id, + '--description', identity_fakes.limit_description, + identity_fakes.limit_resource_name + ] + verifylist = [ + ('project', identity_fakes.project_id), + ('service', identity_fakes.service_id), + ('resource_name', identity_fakes.limit_resource_name), + ('resource_limit', resource_limit), + ('region', identity_fakes.region_id), + ('description', identity_fakes.limit_description) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + kwargs = { + 'description': identity_fakes.limit_description, + 'region': self.region + } + self.limit_mock.create.assert_called_with( + self.project, + self.service, + identity_fakes.limit_resource_name, + resource_limit, + **kwargs + ) + + collist = ('description', 'id', 'project_id', 'region_id', + 'resource_limit', 'resource_name', 'service_id') + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.limit_description, + identity_fakes.limit_id, + identity_fakes.project_id, + identity_fakes.region_id, + resource_limit, + identity_fakes.limit_resource_name, + identity_fakes.service_id + ) + self.assertEqual(datalist, data) + + +class TestLimitDelete(TestLimit): + + def setUp(self): + super(TestLimitDelete, self).setUp() + self.cmd = limit.DeleteLimit(self.app, None) + + def test_limit_delete(self): + self.limit_mock.delete.return_value = None + + arglist = [identity_fakes.limit_id] + verifylist = [ + ('limit_id', [identity_fakes.limit_id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.limit_mock.delete.assert_called_with( + identity_fakes.limit_id + ) + self.assertIsNone(result) + + def test_limit_delete_with_exception(self): + return_value = ksa_exceptions.NotFound() + self.limit_mock.delete.side_effect = return_value + + arglist = ['fake-limit-id'] + verifylist = [ + ('limit_id', ['fake-limit-id']) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual( + '1 of 1 limits failed to delete.', str(e) + ) + + +class TestLimitShow(TestLimit): + + def setUp(self): + super(TestLimitShow, self).setUp() + + self.limit_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.LIMIT), + loaded=True + ) + + self.cmd = limit.ShowLimit(self.app, None) + + def test_limit_show(self): + arglist = [identity_fakes.limit_id] + verifylist = [('limit_id', identity_fakes.limit_id)] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.limit_mock.get.assert_called_with(identity_fakes.limit_id) + + collist = ( + 'description', 'id', 'project_id', 'region_id', 'resource_limit', + 'resource_name', 'service_id' + ) + self.assertEqual(collist, columns) + datalist = ( + None, + identity_fakes.limit_id, + identity_fakes.project_id, + None, + identity_fakes.limit_resource_limit, + identity_fakes.limit_resource_name, + identity_fakes.service_id + ) + self.assertEqual(datalist, data) + + +class TestLimitSet(TestLimit): + + def setUp(self): + super(TestLimitSet, self).setUp() + self.cmd = limit.SetLimit(self.app, None) + + def test_limit_set_description(self): + limit = copy.deepcopy(identity_fakes.LIMIT) + limit['description'] = identity_fakes.limit_description + self.limit_mock.update.return_value = fakes.FakeResource( + None, limit, loaded=True + ) + + arglist = [ + '--description', identity_fakes.limit_description, + identity_fakes.limit_id + ] + verifylist = [ + ('description', identity_fakes.limit_description), + ('limit_id', identity_fakes.limit_id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.limit_mock.update.assert_called_with( + identity_fakes.limit_id, + description=identity_fakes.limit_description, + resource_limit=None + ) + + collist = ( + 'description', 'id', 'project_id', 'region_id', 'resource_limit', + 'resource_name', 'service_id' + ) + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.limit_description, + identity_fakes.limit_id, + identity_fakes.project_id, + None, + identity_fakes.limit_resource_limit, + identity_fakes.limit_resource_name, + identity_fakes.service_id + ) + self.assertEqual(datalist, data) + + def test_limit_set_resource_limit(self): + resource_limit = 20 + limit = copy.deepcopy(identity_fakes.LIMIT) + limit['resource_limit'] = resource_limit + self.limit_mock.update.return_value = fakes.FakeResource( + None, limit, loaded=True + ) + + arglist = [ + '--resource-limit', str(resource_limit), + identity_fakes.limit_id + ] + verifylist = [ + ('resource_limit', resource_limit), + ('limit_id', identity_fakes.limit_id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.limit_mock.update.assert_called_with( + identity_fakes.limit_id, + description=None, + resource_limit=resource_limit + ) + + collist = ( + 'description', 'id', 'project_id', 'region_id', 'resource_limit', + 'resource_name', 'service_id' + ) + self.assertEqual(collist, columns) + datalist = ( + None, + identity_fakes.limit_id, + identity_fakes.project_id, + None, + resource_limit, + identity_fakes.limit_resource_name, + identity_fakes.service_id + ) + self.assertEqual(datalist, data) + + +class TestLimitList(TestLimit): + + def setUp(self): + super(TestLimitList, self).setUp() + + self.limit_mock.list.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.LIMIT), + loaded=True + ) + ] + + self.cmd = limit.ListLimit(self.app, None) + + def test_limit_list(self): + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.limit_mock.list.assert_called_with( + service=None, resource_name=None, region=None + ) + + collist = ( + 'ID', 'Project ID', 'Service ID', 'Resource Name', + 'Resource Limit', 'Description', 'Region ID' + ) + self.assertEqual(collist, columns) + datalist = (( + identity_fakes.limit_id, + identity_fakes.project_id, + identity_fakes.service_id, + identity_fakes.limit_resource_name, + identity_fakes.limit_resource_limit, + None, + None + ), ) + self.assertEqual(datalist, tuple(data)) diff --git a/releasenotes/notes/bp-unified-limits-6c5fdb1c26805d86.yaml b/releasenotes/notes/bp-unified-limits-6c5fdb1c26805d86.yaml new file mode 100644 index 0000000000..b00de40c69 --- /dev/null +++ b/releasenotes/notes/bp-unified-limits-6c5fdb1c26805d86.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + [`bp unified-limits `_] + Support has been added for managing project-specific limits in keystone via + the ``limit`` command. Limits define limits of resources for projects to + consume once a limit has been registered. diff --git a/setup.cfg b/setup.cfg index af7bbbf20e..64a5533eda 100644 --- a/setup.cfg +++ b/setup.cfg @@ -268,6 +268,12 @@ openstack.identity.v3 = implied_role_delete = openstackclient.identity.v3.implied_role:DeleteImpliedRole implied_role_list = openstackclient.identity.v3.implied_role:ListImpliedRole + limit_create = openstackclient.identity.v3.limit:CreateLimit + limit_delete = openstackclient.identity.v3.limit:DeleteLimit + limit_list = openstackclient.identity.v3.limit:ListLimit + limit_set = openstackclient.identity.v3.limit:SetLimit + limit_show = openstackclient.identity.v3.limit:ShowLimit + mapping_create = openstackclient.identity.v3.mapping:CreateMapping mapping_delete = openstackclient.identity.v3.mapping:DeleteMapping mapping_list = openstackclient.identity.v3.mapping:ListMapping From 9ece632f96844fd78c2f717f2f6d35e61c3b9ef2 Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Mon, 23 Jul 2018 09:31:17 -0500 Subject: [PATCH 1952/3095] Add command to show all service versions Knowing what services and what versions of those services exist on a cloud isn't always a spectacular experience. Add a command that will use get_all_version_data from keystoneauth to produce a report of the available services and the version info for each service. Depends-On: https://review.openstack.org/584944 Change-Id: I84751c175d0c5f6d857a5473d2db6d5f1b41f946 --- doc/source/cli/command-objects/versions.rst | 41 +++++++ openstackclient/common/versions.py | 114 ++++++++++++++++++ .../tests/functional/common/test_versions.py | 31 +++++ .../notes/versions-show-12a2443624c83048.yaml | 7 ++ setup.cfg | 1 + 5 files changed, 194 insertions(+) create mode 100644 doc/source/cli/command-objects/versions.rst create mode 100644 openstackclient/common/versions.py create mode 100644 openstackclient/tests/functional/common/test_versions.py create mode 100644 releasenotes/notes/versions-show-12a2443624c83048.yaml diff --git a/doc/source/cli/command-objects/versions.rst b/doc/source/cli/command-objects/versions.rst new file mode 100644 index 0000000000..6742565265 --- /dev/null +++ b/doc/source/cli/command-objects/versions.rst @@ -0,0 +1,41 @@ +======== +versions +======== + +Get a list of every version of every service in a given cloud. + +versions show +------------- + +Show service versions: + +.. program:: versions show +.. code:: bash + + openstack versions show + [--all-interfaces] + [--interface ] + [--region-name ] + [--service ] + +.. option:: --all-interfaces + + Return results for every interface of every service. + [Mutually exclusive with --interface] + +.. option:: --interface + + Limit results to only those on given interface. + [Default 'public'. Mutually exclusive with --all-interfaces] + +.. option:: --region-name + + Limit results to only those from region-name + +.. option:: --service + + Limit results to only those for service. The argument should be either + an exact match to what is in the catalog or a known official value or + alias from `service-types-authority`_. + +.. _service-types-authority: https://service-types.openstack.org/ diff --git a/openstackclient/common/versions.py b/openstackclient/common/versions.py new file mode 100644 index 0000000000..6a93d3002b --- /dev/null +++ b/openstackclient/common/versions.py @@ -0,0 +1,114 @@ +# Copyright 2018 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Versions Action Implementation""" + +import os_service_types +from osc_lib.command import command + +from openstackclient.i18n import _ + + +class ShowVersions(command.Lister): + _description = _("Show available versions of services") + + def get_parser(self, prog_name): + parser = super(ShowVersions, self).get_parser(prog_name) + interface_group = parser.add_mutually_exclusive_group() + interface_group.add_argument( + "--all-interfaces", + dest="is_all_interfaces", + action="store_true", + default=False, + help=_("Show values for all interfaces"), + ) + interface_group.add_argument( + '--interface', + default='public', + metavar='', + help=_('Show versions for a specific interface.'), + ) + parser.add_argument( + '--region-name', + metavar='', + help=_('Show versions for a specific region.'), + ) + parser.add_argument( + '--service', + metavar='', + help=_('Show versions for a specific service.'), + ) + parser.add_argument( + '--status', + metavar='', + help=_('Show versions for a specific status.' + ' [Valid values are SUPPORTED, CURRENT,' + ' DEPRECATED, EXPERIMENTAL]'), + ) + return parser + + def take_action(self, parsed_args): + + interface = parsed_args.interface + if parsed_args.is_all_interfaces: + interface = None + + session = self.app.client_manager.session + version_data = session.get_all_version_data( + interface=interface, + region_name=parsed_args.region_name) + + columns = [ + "Region Name", + "Service Type", + "Version", + "Status", + "Endpoint", + "Min Microversion", + "Max Microversion", + ] + + status = parsed_args.status + if status: + status = status.upper() + + service = parsed_args.service + if service: + # Normalize service type argument to official type + service_type_manager = os_service_types.ServiceTypes() + service = service_type_manager.get_service_type(service) + + versions = [] + for region_name, interfaces in version_data.items(): + for interface, services in interfaces.items(): + for service_type, service_versions in services.items(): + if service and service != service_type: + # TODO(mordred) Once there is a version of + # keystoneauth that can do this filtering + # before making all the discovery calls, switch + # to that. + continue + for data in service_versions: + if status and status != data['status']: + continue + versions.append(( + region_name, + service_type, + data['version'], + data['status'], + data['url'], + data['min_microversion'], + data['max_microversion'], + )) + return (columns, versions) diff --git a/openstackclient/tests/functional/common/test_versions.py b/openstackclient/tests/functional/common/test_versions.py new file mode 100644 index 0000000000..adc74ebc6c --- /dev/null +++ b/openstackclient/tests/functional/common/test_versions.py @@ -0,0 +1,31 @@ +# 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 json + +from openstackclient.tests.functional import base + + +class VersionsTests(base.TestCase): + """Functional tests for versions.""" + + def test_versions_show(self): + # TODO(mordred) Make this better. The trick is knowing what in the + # payload to test for. + cmd_output = json.loads(self.openstack( + 'versions show -f json' + )) + self.assertIsNotNone(cmd_output) + self.assertIn( + "Region Name", + cmd_output[0], + ) diff --git a/releasenotes/notes/versions-show-12a2443624c83048.yaml b/releasenotes/notes/versions-show-12a2443624c83048.yaml new file mode 100644 index 0000000000..4f5652f76a --- /dev/null +++ b/releasenotes/notes/versions-show-12a2443624c83048.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + A new command, ``openstack versions show`` was added, which will + provide a list of all versions of all services in the cloud. It + includes relevant metadata, such as min/max microversion, endpoint, + status and region. diff --git a/setup.cfg b/setup.cfg index f9c0b3efcf..e2fd73bf9d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -51,6 +51,7 @@ openstack.common = quota_list = openstackclient.common.quota:ListQuota quota_set = openstackclient.common.quota:SetQuota quota_show = openstackclient.common.quota:ShowQuota + versions_show = openstackclient.common.versions:ShowVersions openstack.compute.v2 = compute_agent_create = openstackclient.compute.v2.agent:CreateAgent From b90b93e143e88a5f425e44a45f3c976c4f891407 Mon Sep 17 00:00:00 2001 From: Jake Yip Date: Thu, 21 Dec 2017 14:50:29 +1100 Subject: [PATCH 1953/3095] Fix error with image show when image name is None Need to bump osc-lib to 1.10.0 Closes-Bug: #1736696 Depends-On: I2aab5cc1f550848bda2b90ef7ef9a60f07b88996 Change-Id: I7420204f28d36529354e5671bd88587d9b15bb06 --- lower-constraints.txt | 2 +- openstackclient/image/v2/image.py | 2 +- openstackclient/tests/unit/image/v2/test_image.py | 3 ++- requirements.txt | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lower-constraints.txt b/lower-constraints.txt index e3c160ecfd..5951937a19 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -54,7 +54,7 @@ openstacksdk==0.11.2 os-client-config==1.28.0 os-service-types==1.2.0 os-testr==1.0.0 -osc-lib==1.8.0 +osc-lib==1.10.0 oslo.concurrency==3.26.0 oslo.config==5.2.0 oslo.context==2.19.2 diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 4c7c815f22..5c7d32d4a6 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -585,7 +585,7 @@ def take_action(self, parsed_args): property_field='properties', ) - data = utils.sort_items(data, parsed_args.sort) + data = utils.sort_items(data, parsed_args.sort, str) return ( column_headers, diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py index b769d1f654..e7cd34c370 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -708,7 +708,8 @@ def test_image_list_sort_option(self, si_mock): ) si_mock.assert_called_with( [self._image], - 'name:asc' + 'name:asc', + str, ) self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) diff --git a/requirements.txt b/requirements.txt index c5795fd5ed..c3c1650f80 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ Babel!=2.4.0,>=2.3.4 # BSD cliff!=2.9.0,>=2.8.0 # Apache-2.0 keystoneauth1>=3.4.0 # Apache-2.0 openstacksdk>=0.11.2 # Apache-2.0 -osc-lib>=1.8.0 # Apache-2.0 +osc-lib>=1.10.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 python-glanceclient>=2.8.0 # Apache-2.0 From d6121782d3aa6a95b883220b27a154e641b61f7f Mon Sep 17 00:00:00 2001 From: Artom Lifshitz Date: Thu, 12 Jul 2018 14:34:51 -0400 Subject: [PATCH 1954/3095] Don't sent disk_over_commit if nova api > 2.24 In API microversion 2.25 Nova removed the disk_over_commit parameter to the live migration server action. This patch makes sure that we don't include it in our request if we're running with 2.25 or higher. Story: #2002963 Task: #22966 Change-Id: I1bbdd33be96d82422a05982508e370237c3560f3 --- openstackclient/compute/v2/server.py | 13 ++++--- .../tests/unit/compute/v2/test_server.py | 35 +++++++++++++++++++ 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 777f7744e7..a6a5908440 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -21,6 +21,7 @@ import logging import os +from novaclient import api_versions from novaclient.v2 import servers from osc_lib.cli import parseractions from osc_lib.command import command @@ -1384,11 +1385,13 @@ def _show_progress(progress): parsed_args.server, ) if parsed_args.live: - server.live_migrate( - host=parsed_args.live, - block_migration=parsed_args.block_migration, - disk_over_commit=parsed_args.disk_overcommit, - ) + kwargs = { + 'host': parsed_args.live, + 'block_migration': parsed_args.block_migration + } + if compute_client.api_version < api_versions.APIVersion('2.25'): + kwargs['disk_over_commit'] = parsed_args.disk_overcommit + server.live_migrate(**kwargs) else: if parsed_args.block_migration or parsed_args.disk_overcommit: raise exceptions.CommandError("--live must be specified if " diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 46d4c24114..b3d326819b 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -18,6 +18,7 @@ import mock from mock import call +from novaclient import api_versions from osc_lib import exceptions from osc_lib import utils as common_utils from oslo_utils import timeutils @@ -2207,6 +2208,9 @@ def test_server_live_migrate(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.24') + result = self.cmd.take_action(parsed_args) self.servers_mock.get.assert_called_with(self.server.id) @@ -2228,6 +2232,9 @@ def test_server_block_live_migrate(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.24') + result = self.cmd.take_action(parsed_args) self.servers_mock.get.assert_called_with(self.server.id) @@ -2249,6 +2256,9 @@ def test_server_live_migrate_with_disk_overcommit(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.24') + result = self.cmd.take_action(parsed_args) self.servers_mock.get.assert_called_with(self.server.id) @@ -2271,6 +2281,9 @@ def test_server_live_migrate_with_false_value_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.24') + result = self.cmd.take_action(parsed_args) self.servers_mock.get.assert_called_with(self.server.id) @@ -2280,6 +2293,28 @@ def test_server_live_migrate_with_false_value_options(self): self.assertNotCalled(self.servers_mock.migrate) self.assertIsNone(result) + def test_server_live_migrate_225(self): + arglist = [ + '--live', 'fakehost', self.server.id, + ] + verifylist = [ + ('live', 'fakehost'), + ('block_migration', False), + ('wait', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.25') + + result = self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.server.live_migrate.assert_called_with(block_migration=False, + host='fakehost') + self.assertNotCalled(self.servers_mock.migrate) + self.assertIsNone(result) + @mock.patch.object(common_utils, 'wait_for_status', return_value=True) def test_server_migrate_with_wait(self, mock_wait_for_status): arglist = [ From 860639a548a2c07193662cd361432cb5061c2a7f Mon Sep 17 00:00:00 2001 From: Nobuto Murata Date: Mon, 30 Apr 2018 14:32:08 +0900 Subject: [PATCH 1955/3095] Support --community in openstack image list "--community" was added to "image create" and "image set" previously, but was missed in "image list". Change-Id: I959fdd7f67ae62c8326659ce52389228152ec019 Story: 2001925 Task: 14453 --- doc/source/cli/command-objects/image.rst | 8 ++++- openstackclient/api/image_v2.py | 17 +++++++---- openstackclient/image/v2/image.py | 9 ++++++ .../tests/unit/image/v2/test_image.py | 29 +++++++++++++++++++ ...option-to-image-list-ac0651eb2e5d632f.yaml | 4 +++ 5 files changed, 60 insertions(+), 7 deletions(-) create mode 100644 releasenotes/notes/add-community-option-to-image-list-ac0651eb2e5d632f.yaml diff --git a/doc/source/cli/command-objects/image.rst b/doc/source/cli/command-objects/image.rst index a09a8d9ffa..95486e334b 100644 --- a/doc/source/cli/command-objects/image.rst +++ b/doc/source/cli/command-objects/image.rst @@ -205,7 +205,7 @@ List available images .. code:: bash openstack image list - [--public | --private | --shared] + [--public | --private | --community | --shared] [--property ] [--name ] [--status ] @@ -223,6 +223,12 @@ List available images List only private images +.. option:: --community + + List only community images + + *Image version 2 only.* + .. option:: --shared List only shared images diff --git a/openstackclient/api/image_v2.py b/openstackclient/api/image_v2.py index c36281212c..d016318957 100644 --- a/openstackclient/api/image_v2.py +++ b/openstackclient/api/image_v2.py @@ -31,6 +31,7 @@ def image_list( detailed=False, public=False, private=False, + community=False, shared=False, **filter ): @@ -44,25 +45,29 @@ def image_list( Return public images if True :param private: Return private images if True + :param community: + Return commuity images if True :param shared: Return shared images if True - If public, private and shared are all True or all False then all - images are returned. All arguments False is equivalent to no filter - and all images are returned. All arguments True is a filter that - includes all public, private and shared images which is the same set - as all images. + If public, private, community and shared are all True or all False + then all images are returned. All arguments False is equivalent to no + filter and all images are returned. All arguments True is a filter + that includes all public, private, community and shared images which + is the same set as all images. http://docs.openstack.org/api/openstack-image-service/2.0/content/list-images.html """ - if not public and not private and not shared: + if not public and not private and not community and not shared: # No filtering for all False filter.pop('visibility', None) elif public: filter['visibility'] = 'public' elif private: filter['visibility'] = 'private' + elif community: + filter['visibility'] = 'community' elif shared: filter['visibility'] = 'shared' diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 4c7c815f22..51963f7da3 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -439,6 +439,13 @@ def get_parser(self, prog_name): default=False, help=_("List only private images"), ) + public_group.add_argument( + "--community", + dest="community", + action="store_true", + default=False, + help=_("List only community images"), + ) public_group.add_argument( "--shared", dest="shared", @@ -516,6 +523,8 @@ def take_action(self, parsed_args): kwargs['public'] = True if parsed_args.private: kwargs['private'] = True + if parsed_args.community: + kwargs['community'] = True if parsed_args.shared: kwargs['shared'] = True if parsed_args.limit: diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py index b769d1f654..3ad4514521 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -527,6 +527,7 @@ def test_image_list_no_options(self): verifylist = [ ('public', False), ('private', False), + ('community', False), ('shared', False), ('long', False), ] @@ -550,6 +551,7 @@ def test_image_list_public_option(self): verifylist = [ ('public', True), ('private', False), + ('community', False), ('shared', False), ('long', False), ] @@ -574,6 +576,7 @@ def test_image_list_private_option(self): verifylist = [ ('public', False), ('private', True), + ('community', False), ('shared', False), ('long', False), ] @@ -591,6 +594,31 @@ def test_image_list_private_option(self): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) + def test_image_list_community_option(self): + arglist = [ + '--community', + ] + verifylist = [ + ('public', False), + ('private', False), + ('community', True), + ('shared', False), + ('long', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + self.api_mock.image_list.assert_called_with( + community=True, + marker=self._image.id, + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) + def test_image_list_shared_option(self): arglist = [ '--shared', @@ -598,6 +626,7 @@ def test_image_list_shared_option(self): verifylist = [ ('public', False), ('private', False), + ('community', False), ('shared', True), ('long', False), ] diff --git a/releasenotes/notes/add-community-option-to-image-list-ac0651eb2e5d632f.yaml b/releasenotes/notes/add-community-option-to-image-list-ac0651eb2e5d632f.yaml new file mode 100644 index 0000000000..b42dae0c7b --- /dev/null +++ b/releasenotes/notes/add-community-option-to-image-list-ac0651eb2e5d632f.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - Add ``--community`` option to ``image list`` command. + [Bug `2001925 `_] From 4236d777ffb6f03bb2682142aaa18b48e9a00d96 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 25 Jul 2018 14:08:05 -0500 Subject: [PATCH 1956/3095] Release note cleanup for 3.16.0 release Change-Id: I64efea562117706529c4b474085f5c12939ba4d6 Signed-off-by: Dean Troyer --- .../add-image-member-list-1630ead5988348c2.yaml | 3 +-- ...er-create-image-property-ef76af26233b472b.yaml | 2 +- .../notes/bp-unified-limits-58f166401534a4ff.yaml | 5 ++--- .../notes/bp-unified-limits-6c5fdb1c26805d86.yaml | 6 +++--- .../notes/bug-1750983-420945d6c0afb509.yaml | 15 ++++++++++----- ...1751104-compute-api-2.47-4bfa21cfaa13f408.yaml | 4 ++-- .../implement-system-scope-4c3c47996f98deac.yaml | 3 ++- 7 files changed, 21 insertions(+), 17 deletions(-) diff --git a/releasenotes/notes/add-image-member-list-1630ead5988348c2.yaml b/releasenotes/notes/add-image-member-list-1630ead5988348c2.yaml index f939a2fd86..c220b3842b 100644 --- a/releasenotes/notes/add-image-member-list-1630ead5988348c2.yaml +++ b/releasenotes/notes/add-image-member-list-1630ead5988348c2.yaml @@ -1,4 +1,3 @@ --- features: - - The OpenStack client now has the ability to list all members of an image - in order to faciliate management of member projects for images. + - Add ``image member list`` command to list all members of an image. diff --git a/releasenotes/notes/add-server-create-image-property-ef76af26233b472b.yaml b/releasenotes/notes/add-server-create-image-property-ef76af26233b472b.yaml index 9d71446fbc..412d7bb719 100644 --- a/releasenotes/notes/add-server-create-image-property-ef76af26233b472b.yaml +++ b/releasenotes/notes/add-server-create-image-property-ef76af26233b472b.yaml @@ -1,5 +1,5 @@ --- features: - | - Add a parameter ``--image-property`` to ``server create`` command. + Add ``--image-property`` option to ``server create`` command. This parameter will filter a image which properties that are matching. diff --git a/releasenotes/notes/bp-unified-limits-58f166401534a4ff.yaml b/releasenotes/notes/bp-unified-limits-58f166401534a4ff.yaml index 20050bb264..9335330a09 100644 --- a/releasenotes/notes/bp-unified-limits-58f166401534a4ff.yaml +++ b/releasenotes/notes/bp-unified-limits-58f166401534a4ff.yaml @@ -1,7 +1,6 @@ --- features: - | + Add ``registered limit`` commands for managing registered limits in Keystone. + Registered limits define limits of resources for projects to assume by default. [`bp unified-limits `_] - Support has been added for managing registered limits in keystone via the - ``registered limit`` command. Registered limits define limits of resources - for projects to assume by default. diff --git a/releasenotes/notes/bp-unified-limits-6c5fdb1c26805d86.yaml b/releasenotes/notes/bp-unified-limits-6c5fdb1c26805d86.yaml index b00de40c69..815a83e653 100644 --- a/releasenotes/notes/bp-unified-limits-6c5fdb1c26805d86.yaml +++ b/releasenotes/notes/bp-unified-limits-6c5fdb1c26805d86.yaml @@ -1,7 +1,7 @@ --- features: - | + Add ``limit`` commands for managing project-specific limits in keystone. + Limits define limits of resources for projects to consume once a limit + has been registered. [`bp unified-limits `_] - Support has been added for managing project-specific limits in keystone via - the ``limit`` command. Limits define limits of resources for projects to - consume once a limit has been registered. diff --git a/releasenotes/notes/bug-1750983-420945d6c0afb509.yaml b/releasenotes/notes/bug-1750983-420945d6c0afb509.yaml index 2c37d88fad..a0042380a7 100644 --- a/releasenotes/notes/bug-1750983-420945d6c0afb509.yaml +++ b/releasenotes/notes/bug-1750983-420945d6c0afb509.yaml @@ -1,8 +1,13 @@ --- -fixes: +features: - | - Add supports tagging for Network security group. - Add ``tag``, ``no-tag`` to ``security group create`` and ``security group set`` commands. - Add ``tags``, ``any-tags``, ``not-tags``, ``not-any-tags`` to ``security group list`` command. - Add ``tag`` and ``all-tag`` to ``security group unset`` command. + Add ``--tag`` and ``--no-tag`` options to ``security group create`` and + ``security group set`` commands. + [Bug `1750983 `_] + - | + Add ``--tags``, ``--any-tags``, ``--not-tags`` and ``--not-any-tags`` options + to ``security group list`` command. + [Bug `1750983 `_] + - | + Add ``--tag`` and ``--all-tag`` options to ``security group unset`` command. [Bug `1750983 `_] diff --git a/releasenotes/notes/bug-1751104-compute-api-2.47-4bfa21cfaa13f408.yaml b/releasenotes/notes/bug-1751104-compute-api-2.47-4bfa21cfaa13f408.yaml index ef89c785ef..72f763f1e0 100644 --- a/releasenotes/notes/bug-1751104-compute-api-2.47-4bfa21cfaa13f408.yaml +++ b/releasenotes/notes/bug-1751104-compute-api-2.47-4bfa21cfaa13f408.yaml @@ -1,6 +1,6 @@ --- fixes: - | - The ``openstack server show`` command will now properly show the server's + The ``server show`` command will now properly show the server's flavor information when using ``--os-compute-api-version 2.47`` or higher. - See: https://storyboard.openstack.org/#!/story/1751104 + [Bug `1751104 `_] diff --git a/releasenotes/notes/implement-system-scope-4c3c47996f98deac.yaml b/releasenotes/notes/implement-system-scope-4c3c47996f98deac.yaml index 6ad3d8244d..dc9e07ce77 100644 --- a/releasenotes/notes/implement-system-scope-4c3c47996f98deac.yaml +++ b/releasenotes/notes/implement-system-scope-4c3c47996f98deac.yaml @@ -1,8 +1,9 @@ --- features: - | - Added support for system-scope. This includes support for the ability to + Add support for system-scope to ``role`` commands. This includes the ability to generate system-scoped tokens using ``system_scope: all`` in ``cloud.yaml`` or ``OS_SYSTEM_SCOPE=all`` in an environment variable. Support is also included for managing role assignments on the system using ``--system`` when adding and removing roles. + [`bp system-scope `_] From 2b62b6e6ecb5fbb6d3078a3f7737364e13ec4e6c Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Thu, 26 Jul 2018 08:58:10 +0000 Subject: [PATCH 1957/3095] Update reno for stable/rocky Change-Id: Ieb4130d9f9d420fb3b858b6972a1f2a896fa7fc4 --- releasenotes/source/index.rst | 1 + releasenotes/source/rocky.rst | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 releasenotes/source/rocky.rst diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index 33fb920cad..2413a57425 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,6 +6,7 @@ OpenStackClient Release Notes :maxdepth: 1 unreleased + rocky queens pike ocata diff --git a/releasenotes/source/rocky.rst b/releasenotes/source/rocky.rst new file mode 100644 index 0000000000..40dd517b75 --- /dev/null +++ b/releasenotes/source/rocky.rst @@ -0,0 +1,6 @@ +=================================== + Rocky Series Release Notes +=================================== + +.. release-notes:: + :branch: stable/rocky From 6cb0f0f79c0625b72609683d81848a9cd0b290c7 Mon Sep 17 00:00:00 2001 From: Brian Haley Date: Fri, 3 Aug 2018 12:29:34 -0400 Subject: [PATCH 1958/3095] Fix missing trailing spaces in network help messages Trivialfix Change-Id: I1eeab576e7f50d858860a19c045f24a33449dc92 --- openstackclient/network/v2/router.py | 2 +- openstackclient/network/v2/subnet.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index f0a5196744..187c241fc9 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -563,7 +563,7 @@ def get_parser(self, prog_name): metavar='subnet=,ip-address=', action=parseractions.MultiKeyValueAction, optional_keys=['subnet', 'ip-address'], - help=_("Desired IP and/or subnet (name or ID)" + help=_("Desired IP and/or subnet (name or ID) " "on external gateway: " "subnet=,ip-address= " "(repeat option to set multiple fixed IP addresses)") diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 9c56186f98..2c4b9c1c83 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -412,7 +412,7 @@ def get_parser(self, prog_name): choices=[4, 6], metavar='', dest='ip_version', - help=_("List only subnets of given IP version in output." + help=_("List only subnets of given IP version in output. " "Allowed values for IP version are 4 and 6."), ) dhcp_enable_group = parser.add_mutually_exclusive_group() From ed09f28a9dd1cbc0f8c141a8e38587b7022d4166 Mon Sep 17 00:00:00 2001 From: Jens Harbott Date: Wed, 4 Apr 2018 13:44:24 +0000 Subject: [PATCH 1959/3095] Add DNS support to floating IP commands Add the DNS domain and name options to the ``floating ip create`` command. Also add these two columns to the output of the ``floating ip list --long`` command. Change-Id: Id4cb18b51b252f19b87b24ec5d77183771189d17 Story: 1547736 Task: 13114 --- .../cli/command-objects/floating-ip.rst | 10 +++++++ openstackclient/network/v2/floating_ip.py | 29 ++++++++++++++++++- .../network/v2/test_floating_ip_network.py | 12 ++++++++ ...ngip_dns_integration-f26c7575694d098d.yaml | 14 +++++++++ 4 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/floatingip_dns_integration-f26c7575694d098d.yaml diff --git a/doc/source/cli/command-objects/floating-ip.rst b/doc/source/cli/command-objects/floating-ip.rst index c0ba7ebf35..aaa6c7a19d 100644 --- a/doc/source/cli/command-objects/floating-ip.rst +++ b/doc/source/cli/command-objects/floating-ip.rst @@ -21,6 +21,8 @@ Create floating IP [--qos-policy ] [--project [--project-domain ]] [--tag | --no-tag] + [--dns-domain ] + [--dns-name ] .. option:: --subnet @@ -79,6 +81,14 @@ Create floating IP *Network version 2 only* +.. option:: --dns-domain + + Set DNS domain for this floating IP (requires DNS integration extension). + +.. option:: --dns-name + + Set DNS name for this floating IP (requires DNS integration extension). + .. describe:: Network to allocate floating IP from (name or ID) diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index f51baed578..5e6c331ab6 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -82,6 +82,12 @@ def _get_attrs(client_manager, parsed_args): ).id attrs['tenant_id'] = project_id + if parsed_args.dns_domain: + attrs['dns_domain'] = parsed_args.dns_domain + + if parsed_args.dns_name: + attrs['dns_name'] = parsed_args.dns_name + return attrs @@ -139,15 +145,32 @@ def update_parser_network(self, parser): metavar='', help=_("Owner's project (name or ID)") ) + parser.add_argument( + '--dns-domain', + metavar='', + dest='dns_domain', + help=_("Set DNS domain for this floating IP") + ) + parser.add_argument( + '--dns-name', + metavar='', + dest='dns_name', + help=_("Set DNS name for this floating IP") + ) + identity_common.add_project_domain_option_to_parser(parser) _tag.add_tag_option_to_parser_for_create(parser, _('floating IP')) return parser def take_action_network(self, client, parsed_args): attrs = _get_attrs(self.app.client_manager, parsed_args) - obj = client.create_ip(**attrs) + with common.check_missing_extension_if_error( + self.app.client_manager.network, attrs): + obj = client.create_ip(**attrs) + # tags cannot be set when created, so tags need to be set later. _tag.update_tags_for_set(client, obj, parsed_args) + display_columns, columns = _get_network_columns(obj) data = utils.get_item_properties(obj, columns) return (display_columns, data) @@ -314,12 +337,16 @@ def take_action_network(self, client, parsed_args): 'status', 'description', 'tags', + 'dns_name', + 'dns_domain', ) headers = headers + ( 'Router', 'Status', 'Description', 'Tags', + 'DNS Name', + 'DNS Domain', ) query = {} diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip_network.py b/openstackclient/tests/unit/network/v2/test_floating_ip_network.py index 65d873770a..d71121a760 100644 --- a/openstackclient/tests/unit/network/v2/test_floating_ip_network.py +++ b/openstackclient/tests/unit/network/v2/test_floating_ip_network.py @@ -49,6 +49,8 @@ class TestCreateFloatingIPNetwork(TestFloatingIPNetwork): attrs={ 'floating_network_id': floating_network.id, 'port_id': port.id, + 'dns_domain': 'example.org.', + 'dns_name': 'fip1', } ) @@ -129,6 +131,8 @@ def test_create_all_options(self): '--floating-ip-address', self.floating_ip.floating_ip_address, '--fixed-ip-address', self.floating_ip.fixed_ip_address, '--description', self.floating_ip.description, + '--dns-domain', self.floating_ip.dns_domain, + '--dns-name', self.floating_ip.dns_name, self.floating_ip.floating_network_id, ] verifylist = [ @@ -137,6 +141,8 @@ def test_create_all_options(self): ('fixed_ip_address', self.floating_ip.fixed_ip_address), ('network', self.floating_ip.floating_network_id), ('description', self.floating_ip.description), + ('dns_domain', self.floating_ip.dns_domain), + ('dns_name', self.floating_ip.dns_name), ('floating_ip_address', self.floating_ip.floating_ip_address), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -150,6 +156,8 @@ def test_create_all_options(self): 'fixed_ip_address': self.floating_ip.fixed_ip_address, 'floating_network_id': self.floating_ip.floating_network_id, 'description': self.floating_ip.description, + 'dns_domain': self.floating_ip.dns_domain, + 'dns_name': self.floating_ip.dns_name, }) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -393,6 +401,8 @@ class TestListFloatingIPNetwork(TestFloatingIPNetwork): 'Status', 'Description', 'Tags', + 'DNS Name', + 'DNS Domain', ) data = [] @@ -417,6 +427,8 @@ class TestListFloatingIPNetwork(TestFloatingIPNetwork): ip.status, ip.description, ip.tags, + ip.dns_domain, + ip.dns_name, )) def setUp(self): diff --git a/releasenotes/notes/floatingip_dns_integration-f26c7575694d098d.yaml b/releasenotes/notes/floatingip_dns_integration-f26c7575694d098d.yaml new file mode 100644 index 0000000000..9c1d4cf5d2 --- /dev/null +++ b/releasenotes/notes/floatingip_dns_integration-f26c7575694d098d.yaml @@ -0,0 +1,14 @@ +--- +features: + - | + Add ``--dns-domain`` and ``--dns-name`` options to the + ``floating ip create`` commands. These options + set the DNS domain and name for the floating IP. + + Check backend available extension and return an error + message if it is missing (instead of a Bad Request HTTP 400). + [Bug `1547736 `_] + - | + Add ``--long`` option to the ``floating ip list`` command. This adds + ``DNS Name`` and ``DNS Domain`` columns to the floating IP list. + [Bug `1547736 `_] From def83a0e94de2f98e3bd68ee412c0f0a2c316f32 Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Mon, 30 Jul 2018 19:31:27 +0800 Subject: [PATCH 1960/3095] Fix broken gate jobs This patch aims at fixing the broken gate jobs because of cinder and glance patches [1], [2], [3] and [4]. * Remove parameter `--source-replicated` to drop volume replication v1 support * Address some timing issues with volume transfer requests * Only run Image v1 tests when the test cloud has v1 available * Get tolerant of unexpected additional attributes being returned in Image data [1].https://review.openstack.org/#/c/586293/ [2].https://review.openstack.org/#/c/532503/ [3].https://review.openstack.org/#/c/533564/ [4].https://review.openstack.org/#/c/578755/ Co-Authored-By: Dean Troyer Co-Authored-By: Monty Taylor Depends-on: https://review.openstack.org/588664 Change-Id: I2a785750e92155185d3344e6116c7f5c6fdd3cbe Signed-off-by: Fan Zhang --- doc/source/cli/command-objects/volume.rst | 8 +- .../tests/functional/image/base.py | 24 ++++ .../tests/functional/image/v1/test_image.py | 55 +++++---- .../tests/functional/image/v2/test_image.py | 107 +++++++++--------- .../volume/v1/test_transfer_request.py | 37 +++--- .../volume/v2/test_transfer_request.py | 42 ++++--- .../volume/v3/test_transfer_request.py | 2 + .../tests/unit/volume/v2/test_volume.py | 47 -------- openstackclient/volume/v2/volume.py | 14 +-- 9 files changed, 160 insertions(+), 176 deletions(-) create mode 100644 openstackclient/tests/functional/image/base.py diff --git a/doc/source/cli/command-objects/volume.rst b/doc/source/cli/command-objects/volume.rst index a06a5d4007..8df48d462c 100644 --- a/doc/source/cli/command-objects/volume.rst +++ b/doc/source/cli/command-objects/volume.rst @@ -15,7 +15,7 @@ Create new volume openstack volume create [--size ] [--type ] - [--image | --snapshot | --source | --source-replicated ] + [--image | --snapshot | --source ] [--description ] [--user ] [--project ] @@ -31,7 +31,7 @@ Create new volume .. option:: --size Volume size in GB - (Required unless --snapshot or --source or --source-replicated is specified) + (Required unless --snapshot or --source is specified) .. option:: --type @@ -54,10 +54,6 @@ Create new volume Volume to clone (name or ID) -.. option:: --source-replicated - - Replicated volume to clone (name or ID) - .. option:: --description Volume description diff --git a/openstackclient/tests/functional/image/base.py b/openstackclient/tests/functional/image/base.py new file mode 100644 index 0000000000..4b2ab64b73 --- /dev/null +++ b/openstackclient/tests/functional/image/base.py @@ -0,0 +1,24 @@ +# 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 BaseImageTests(base.TestCase): + """Functional tests for Image commands""" + + @classmethod + def setUpClass(cls): + super(BaseImageTests, cls).setUpClass() + # TODO(dtroyer): maybe do image API discovery here to determine + # what is available, it isn't in the service catalog + cls.haz_v1_api = False diff --git a/openstackclient/tests/functional/image/v1/test_image.py b/openstackclient/tests/functional/image/v1/test_image.py index fa073f99a3..30490bf516 100644 --- a/openstackclient/tests/functional/image/v1/test_image.py +++ b/openstackclient/tests/functional/image/v1/test_image.py @@ -15,50 +15,47 @@ import fixtures -from openstackclient.tests.functional import base +from openstackclient.tests.functional.image import base -class ImageTests(base.TestCase): - """Functional tests for image. """ +class ImageTests(base.BaseImageTests): + """Functional tests for Image commands""" - NAME = uuid.uuid4().hex - OTHER_NAME = uuid.uuid4().hex + def setUp(self): + super(ImageTests, self).setUp() + if not self.haz_v1_api: + self.skipTest('No Image v1 API present') - @classmethod - def setUpClass(cls): - super(ImageTests, cls).setUpClass() - json_output = json.loads(cls.openstack( + self.name = uuid.uuid4().hex + json_output = json.loads(self.openstack( '--os-image-api-version 1 ' 'image create -f json ' + - cls.NAME + self.name )) - cls.image_id = json_output["id"] - cls.assertOutput(cls.NAME, json_output['name']) + self.image_id = json_output["id"] + self.assertOutput(self.name, json_output['name']) + + ver_fixture = fixtures.EnvironmentVariable( + 'OS_IMAGE_API_VERSION', '1' + ) + self.useFixture(ver_fixture) - @classmethod - def tearDownClass(cls): + def tearDown(self): try: - cls.openstack( + self.openstack( '--os-image-api-version 1 ' 'image delete ' + - cls.image_id + self.image_id ) finally: - super(ImageTests, cls).tearDownClass() - - def setUp(self): - super(ImageTests, self).setUp() - ver_fixture = fixtures.EnvironmentVariable( - 'OS_IMAGE_API_VERSION', '1' - ) - self.useFixture(ver_fixture) + super(ImageTests, self).tearDown() def test_image_list(self): json_output = json.loads(self.openstack( 'image list -f json ' )) self.assertIn( - self.NAME, + self.name, [img['Name'] for img in json_output] ) @@ -72,11 +69,11 @@ def test_image_attributes(self): '--min-ram 5 ' + '--disk-format qcow2 ' + '--public ' + - self.NAME + self.name ) json_output = json.loads(self.openstack( 'image show -f json ' + - self.NAME + self.name )) self.assertEqual( 4, @@ -100,11 +97,11 @@ def test_image_attributes(self): '--property a=b ' + '--property c=d ' + '--public ' + - self.NAME + self.name ) json_output = json.loads(self.openstack( 'image show -f json ' + - self.NAME + self.name )) self.assertEqual( "a='b', c='d'", diff --git a/openstackclient/tests/functional/image/v2/test_image.py b/openstackclient/tests/functional/image/v2/test_image.py index 3037b903cc..3185c3bddf 100644 --- a/openstackclient/tests/functional/image/v2/test_image.py +++ b/openstackclient/tests/functional/image/v2/test_image.py @@ -16,60 +16,55 @@ import fixtures # from glanceclient import exc as image_exceptions -from openstackclient.tests.functional import base +from openstackclient.tests.functional.image import base -class ImageTests(base.TestCase): - """Functional tests for image. """ +class ImageTests(base.BaseImageTests): + """Functional tests for Image commands""" - NAME = uuid.uuid4().hex - OTHER_NAME = uuid.uuid4().hex + def setUp(self): + super(ImageTests, self).setUp() - @classmethod - def setUpClass(cls): - super(ImageTests, cls).setUpClass() - cls.image_tag = 'my_tag' - json_output = json.loads(cls.openstack( + self.name = uuid.uuid4().hex + self.image_tag = 'my_tag' + json_output = json.loads(self.openstack( '--os-image-api-version 2 ' 'image create -f json --tag {tag} {name}'.format( - tag=cls.image_tag, name=cls.NAME) + tag=self.image_tag, name=self.name) )) - cls.image_id = json_output["id"] - cls.assertOutput(cls.NAME, json_output['name']) + self.image_id = json_output["id"] + self.assertOutput(self.name, json_output['name']) + + ver_fixture = fixtures.EnvironmentVariable( + 'OS_IMAGE_API_VERSION', '2' + ) + self.useFixture(ver_fixture) - @classmethod - def tearDownClass(cls): + def tearDown(self): try: - cls.openstack( + self.openstack( '--os-image-api-version 2 ' 'image delete ' + - cls.image_id + self.image_id ) finally: - super(ImageTests, cls).tearDownClass() - - def setUp(self): - super(ImageTests, self).setUp() - ver_fixture = fixtures.EnvironmentVariable( - 'OS_IMAGE_API_VERSION', '2' - ) - self.useFixture(ver_fixture) + super(ImageTests, self).tearDown() def test_image_list(self): json_output = json.loads(self.openstack( 'image list -f json ' )) self.assertIn( - self.NAME, + self.name, [img['Name'] for img in json_output] ) def test_image_list_with_name_filter(self): json_output = json.loads(self.openstack( - 'image list --name ' + self.NAME + ' -f json' + 'image list --name ' + self.name + ' -f json' )) self.assertIn( - self.NAME, + self.name, [img['Name'] for img in json_output] ) @@ -101,11 +96,11 @@ def test_image_attributes(self): '--min-disk 4 ' + '--min-ram 5 ' + '--public ' + - self.NAME + self.name ) json_output = json.loads(self.openstack( 'image show -f json ' + - self.NAME + self.name )) self.assertEqual( 4, @@ -126,31 +121,31 @@ def test_image_attributes(self): '--property a=b ' + '--property c=d ' + '--public ' + - self.NAME + self.name ) json_output = json.loads(self.openstack( 'image show -f json ' + - self.NAME + self.name )) - self.assertEqual( - "a='b', c='d'", - json_output["properties"], - ) + # NOTE(dtroyer): Don't do a full-string compare so we are tolerant of + # new artributes in the returned data + self.assertIn("a='b'", json_output["properties"]) + self.assertIn("c='d'", json_output["properties"]) self.openstack( 'image unset ' + '--property a ' + '--property c ' + - self.NAME + self.name ) json_output = json.loads(self.openstack( 'image show -f json ' + - self.NAME + self.name )) - self.assertNotIn( - 'properties', - json_output, - ) + # NOTE(dtroyer): Don't do a full-string compare so we are tolerant of + # new artributes in the returned data + self.assertNotIn("a='b'", json_output["properties"]) + self.assertNotIn("c='d'", json_output["properties"]) # Test tags self.assertNotIn( @@ -160,11 +155,11 @@ def test_image_attributes(self): self.openstack( 'image set ' + '--tag 01 ' + - self.NAME + self.name ) json_output = json.loads(self.openstack( 'image show -f json ' + - self.NAME + self.name )) self.assertIn( '01', @@ -174,11 +169,11 @@ def test_image_attributes(self): self.openstack( 'image unset ' + '--tag 01 ' + - self.NAME + self.name ) json_output = json.loads(self.openstack( 'image show -f json ' + - self.NAME + self.name )) self.assertNotIn( '01', @@ -222,7 +217,7 @@ def test_image_members(self): json_output = json.loads(self.openstack( 'image show -f json ' + - self.NAME + self.name )) # NOTE(dtroyer): Until OSC supports --shared flags in create and set # we can not properly test membership. Sometimes the @@ -230,47 +225,47 @@ def test_image_members(self): if json_output["visibility"] == 'shared': self.openstack( 'image add project ' + - self.NAME + ' ' + + self.name + ' ' + my_project_id ) # self.addCleanup( # self.openstack, # 'image remove project ' + - # self.NAME + ' ' + + # self.name + ' ' + # my_project_id # ) self.openstack( 'image set ' + '--accept ' + - self.NAME + self.name ) json_output = json.loads(self.openstack( 'image list -f json ' + '--shared' )) self.assertIn( - self.NAME, + self.name, [img['Name'] for img in json_output] ) self.openstack( 'image set ' + '--reject ' + - self.NAME + self.name ) json_output = json.loads(self.openstack( 'image list -f json ' + '--shared' )) # self.assertNotIn( - # self.NAME, + # self.name, # [img['Name'] for img in json_output] # ) self.openstack( 'image remove project ' + - self.NAME + ' ' + + self.name + ' ' + my_project_id ) @@ -280,11 +275,11 @@ def test_image_members(self): # image_exceptions.HTTPForbidden, # self.openstack, # 'image add project ' + - # self.NAME + ' ' + + # self.name + ' ' + # my_project_id # ) # self.openstack( # 'image set ' + # '--share ' + - # self.NAME + # self.name # ) diff --git a/openstackclient/tests/functional/volume/v1/test_transfer_request.py b/openstackclient/tests/functional/volume/v1/test_transfer_request.py index 73191fc9d2..0399e6cc99 100644 --- a/openstackclient/tests/functional/volume/v1/test_transfer_request.py +++ b/openstackclient/tests/functional/volume/v1/test_transfer_request.py @@ -29,20 +29,13 @@ def setUpClass(cls): 'volume create -f json --size 1 ' + cls.VOLUME_NAME)) cls.assertOutput(cls.VOLUME_NAME, cmd_output['name']) - cmd_output = json.loads(cls.openstack( - 'volume transfer request create -f json ' + - cls.VOLUME_NAME + - ' --name ' + cls.NAME)) - cls.assertOutput(cls.NAME, cmd_output['name']) + cls.wait_for_status("volume", cls.VOLUME_NAME, "available") @classmethod def tearDownClass(cls): try: - raw_output_transfer = cls.openstack( - 'volume transfer request delete ' + cls.NAME) raw_output_volume = cls.openstack( 'volume delete ' + cls.VOLUME_NAME) - cls.assertOutput('', raw_output_transfer) cls.assertOutput('', raw_output_volume) finally: super(TransferRequestTests, cls).tearDownClass() @@ -79,12 +72,28 @@ def test_volume_transfer_request_accept(self): 'volume delete ' + volume_name) self.assertEqual('', raw_output) - def test_volume_transfer_request_list(self): + def test_volume_transfer_request_list_show(self): + name = uuid.uuid4().hex cmd_output = json.loads(self.openstack( - 'volume transfer request list -f json')) - self.assertIn(self.NAME, [req['Name'] for req in cmd_output]) + 'volume transfer request create -f json ' + + ' --name ' + name + ' ' + + self.VOLUME_NAME + )) + self.addCleanup( + self.openstack, + 'volume transfer request delete ' + name + ) + self.assertOutput(name, cmd_output['name']) + auth_key = cmd_output['auth_key'] + self.assertTrue(auth_key) - def test_volume_transfer_request_show(self): cmd_output = json.loads(self.openstack( - 'volume transfer request show -f json ' + self.NAME)) - self.assertEqual(self.NAME, cmd_output['name']) + 'volume transfer request list -f json' + )) + self.assertIn(name, [req['Name'] for req in cmd_output]) + + cmd_output = json.loads(self.openstack( + 'volume transfer request show -f json ' + + name + )) + self.assertEqual(name, cmd_output['name']) diff --git a/openstackclient/tests/functional/volume/v2/test_transfer_request.py b/openstackclient/tests/functional/volume/v2/test_transfer_request.py index 33495af637..33d8ce77f7 100644 --- a/openstackclient/tests/functional/volume/v2/test_transfer_request.py +++ b/openstackclient/tests/functional/volume/v2/test_transfer_request.py @@ -21,29 +21,24 @@ class TransferRequestTests(common.BaseVolumeTests): NAME = uuid.uuid4().hex VOLUME_NAME = uuid.uuid4().hex + API_VERSION = '2' @classmethod def setUpClass(cls): super(TransferRequestTests, cls).setUpClass() cmd_output = json.loads(cls.openstack( + '--os-volume-api-version ' + cls.API_VERSION + ' ' + 'volume create -f json --size 1 ' + cls.VOLUME_NAME)) cls.assertOutput(cls.VOLUME_NAME, cmd_output['name']) - cmd_output = json.loads(cls.openstack( - 'volume transfer request create -f json ' + - cls.VOLUME_NAME + - ' --name ' + cls.NAME)) - cls.assertOutput(cls.NAME, cmd_output['name']) + cls.wait_for_status("volume", cls.VOLUME_NAME, "available") @classmethod def tearDownClass(cls): try: - raw_output_transfer = cls.openstack( - 'volume transfer request delete ' + cls.NAME) raw_output_volume = cls.openstack( 'volume delete ' + cls.VOLUME_NAME) - cls.assertOutput('', raw_output_transfer) cls.assertOutput('', raw_output_volume) finally: super(TransferRequestTests, cls).tearDownClass() @@ -80,12 +75,31 @@ def test_volume_transfer_request_accept(self): 'volume delete ' + volume_name) self.assertEqual('', raw_output) - def test_volume_transfer_request_list(self): + def test_volume_transfer_request_list_show(self): + name = uuid.uuid4().hex cmd_output = json.loads(self.openstack( - 'volume transfer request list -f json')) - self.assertIn(self.NAME, [req['Name'] for req in cmd_output]) + '--os-volume-api-version ' + self.API_VERSION + ' ' + + 'volume transfer request create -f json ' + + ' --name ' + name + ' ' + + self.VOLUME_NAME + )) + self.addCleanup( + self.openstack, + 'volume transfer request delete ' + name + ) + self.assertEqual(name, cmd_output['name']) + auth_key = cmd_output['auth_key'] + self.assertTrue(auth_key) + + cmd_output = json.loads(self.openstack( + '--os-volume-api-version ' + self.API_VERSION + ' ' + + 'volume transfer request list -f json' + )) + self.assertIn(name, [req['Name'] for req in cmd_output]) - def test_volume_transfer_request_show(self): cmd_output = json.loads(self.openstack( - 'volume transfer request show -f json ' + self.NAME)) - self.assertEqual(self.NAME, cmd_output['name']) + '--os-volume-api-version ' + self.API_VERSION + ' ' + + 'volume transfer request show -f json ' + + name + )) + self.assertEqual(name, cmd_output['name']) diff --git a/openstackclient/tests/functional/volume/v3/test_transfer_request.py b/openstackclient/tests/functional/volume/v3/test_transfer_request.py index b325323752..f16dfafa7a 100644 --- a/openstackclient/tests/functional/volume/v3/test_transfer_request.py +++ b/openstackclient/tests/functional/volume/v3/test_transfer_request.py @@ -17,3 +17,5 @@ class TransferRequestTests(common.BaseVolumeTests, v2.TransferRequestTests): """Functional tests for transfer request. """ + + API_VERSION = '3' diff --git a/openstackclient/tests/unit/volume/v2/test_volume.py b/openstackclient/tests/unit/volume/v2/test_volume.py index 304aa91cbc..971567cb85 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume.py +++ b/openstackclient/tests/unit/volume/v2/test_volume.py @@ -133,7 +133,6 @@ def test_volume_create_min_options(self): imageRef=None, source_volid=None, consistencygroup_id=None, - source_replica=None, multiattach=False, scheduler_hints=None, ) @@ -185,7 +184,6 @@ def test_volume_create_options(self): imageRef=None, source_volid=None, consistencygroup_id=consistency_group.id, - source_replica=None, multiattach=True, scheduler_hints={'k': 'v'}, ) @@ -231,7 +229,6 @@ def test_volume_create_user_project_id(self): imageRef=None, source_volid=None, consistencygroup_id=None, - source_replica=None, multiattach=False, scheduler_hints=None, ) @@ -277,7 +274,6 @@ def test_volume_create_user_project_name(self): imageRef=None, source_volid=None, consistencygroup_id=None, - source_replica=None, multiattach=False, scheduler_hints=None, ) @@ -317,7 +313,6 @@ def test_volume_create_properties(self): imageRef=None, source_volid=None, consistencygroup_id=None, - source_replica=None, multiattach=False, scheduler_hints=None, ) @@ -359,7 +354,6 @@ def test_volume_create_image_id(self): imageRef=image.id, source_volid=None, consistencygroup_id=None, - source_replica=None, multiattach=False, scheduler_hints=None, ) @@ -401,7 +395,6 @@ def test_volume_create_image_name(self): imageRef=image.id, source_volid=None, consistencygroup_id=None, - source_replica=None, multiattach=False, scheduler_hints=None, ) @@ -442,7 +435,6 @@ def test_volume_create_with_snapshot(self): imageRef=None, source_volid=None, consistencygroup_id=None, - source_replica=None, multiattach=False, scheduler_hints=None, ) @@ -484,7 +476,6 @@ def test_volume_create_with_bootable_and_readonly(self): imageRef=None, source_volid=None, consistencygroup_id=None, - source_replica=None, multiattach=False, scheduler_hints=None, ) @@ -530,7 +521,6 @@ def test_volume_create_with_nonbootable_and_readwrite(self): imageRef=None, source_volid=None, consistencygroup_id=None, - source_replica=None, multiattach=False, scheduler_hints=None, ) @@ -585,7 +575,6 @@ def test_volume_create_with_bootable_and_readonly_fail( imageRef=None, source_volid=None, consistencygroup_id=None, - source_replica=None, multiattach=False, scheduler_hints=None, ) @@ -598,40 +587,6 @@ def test_volume_create_with_bootable_and_readonly_fail( self.volumes_mock.update_readonly_flag.assert_called_with( self.new_volume.id, True) - def test_volume_create_with_source_replicated(self): - self.volumes_mock.get.return_value = self.new_volume - arglist = [ - '--source-replicated', self.new_volume.id, - self.new_volume.name, - ] - verifylist = [ - ('source_replicated', self.new_volume.id), - ('name', self.new_volume.name), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - self.volumes_mock.create.assert_called_once_with( - size=None, - snapshot_id=None, - name=self.new_volume.name, - description=None, - volume_type=None, - user_id=None, - project_id=None, - availability_zone=None, - metadata=None, - imageRef=None, - source_volid=None, - consistencygroup_id=None, - source_replica=self.new_volume.id, - multiattach=False, - scheduler_hints=None, - ) - - self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) - def test_volume_create_without_size(self): arglist = [ self.new_volume.name, @@ -649,7 +604,6 @@ def test_volume_create_with_multi_source(self): '--image', 'source_image', '--source', 'source_volume', '--snapshot', 'source_snapshot', - '--source-replicated', 'source_replicated_volume', '--size', str(self.new_volume.size), self.new_volume.name, ] @@ -657,7 +611,6 @@ def test_volume_create_with_multi_source(self): ('image', 'source_image'), ('source', 'source_volume'), ('snapshot', 'source_snapshot'), - ('source-replicated', 'source_replicated_volume'), ('size', self.new_volume.size), ('name', self.new_volume.name), ] diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index ee3d2f20d3..fc648cef39 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -14,6 +14,7 @@ """Volume V2 Volume action implementations""" +import argparse import copy import logging @@ -37,7 +38,7 @@ def _check_size_arg(args): volume is not specified. """ - if ((args.snapshot or args.source or args.source_replicated) + if ((args.snapshot or args.source) is None and args.size is None): msg = _("--size is a required option if snapshot " "or source volume is not specified.") @@ -59,7 +60,7 @@ def get_parser(self, prog_name): metavar="", type=int, help=_("Volume size in GB (Required unless --snapshot or " - "--source or --source-replicated is specified)"), + "--source is specified)"), ) parser.add_argument( "--type", @@ -85,7 +86,7 @@ def get_parser(self, prog_name): source_group.add_argument( "--source-replicated", metavar="", - help=_("Replicated volume to clone (name or ID)"), + help=argparse.SUPPRESS, ) parser.add_argument( "--description", @@ -168,12 +169,6 @@ def take_action(self, parsed_args): volume_client.volumes, parsed_args.source).id - replicated_source_volume = None - if parsed_args.source_replicated: - replicated_source_volume = utils.find_resource( - volume_client.volumes, - parsed_args.source_replicated).id - consistency_group = None if parsed_args.consistency_group: consistency_group = utils.find_resource( @@ -227,7 +222,6 @@ def take_action(self, parsed_args): imageRef=image, source_volid=source_volume, consistencygroup_id=consistency_group, - source_replica=replicated_source_volume, multiattach=parsed_args.multi_attach, scheduler_hints=parsed_args.hint, ) From 5c0de6edc2aa55fbf1aa65bbcee0c0bfeb9db17f Mon Sep 17 00:00:00 2001 From: Lajos Katona Date: Thu, 2 Aug 2018 09:38:31 +0200 Subject: [PATCH 1961/3095] Detailed help message for QoS max-burst-kbps value For QoS rule type bandwidth-limit the max-burst-kbps value for ovs and linuxbridge is suggested to be 80% of the maxkbps value, let's give a detailed help message for the CLI user. For details see https://docs.openstack.org/neutron/queens/admin/ config-qos.html#user-workflow Change-Id: Ia6e38ec7052b7af64880c2f4c5e242d7cb980df5 Closes-Bug: #1777866 --- openstackclient/network/v2/network_qos_rule.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openstackclient/network/v2/network_qos_rule.py b/openstackclient/network/v2/network_qos_rule.py index 9c4275a82b..28c5600aa6 100644 --- a/openstackclient/network/v2/network_qos_rule.py +++ b/openstackclient/network/v2/network_qos_rule.py @@ -141,7 +141,10 @@ def _add_rule_arguments(parser): dest='max_burst_kbits', metavar='', type=int, - help=_('Maximum burst in kilobits, 0 means automatic') + help=_('Maximum burst in kilobits, 0 or not specified means ' + 'automatic, which is 80%% of the bandwidth limit, which works ' + 'for typical TCP traffic. For details check the QoS user ' + 'workflow.') ) parser.add_argument( '--dscp-mark', From 29ca9904fcbf179b6cc2c41221a3b4825069b1ad Mon Sep 17 00:00:00 2001 From: Chen Date: Wed, 6 Jun 2018 21:39:27 +0800 Subject: [PATCH 1962/3095] Fix inconsistency (nit) Word choice of description for "host-evacuate-live" is slightly different from that in novaclient ("migrate of" instead of "migrate off"). https://github.com/openstack/python-novaclient/blob/master/doc/source/cli/nova.rst#nova-host-evacuate-live They are supposed to be exactly the same, so use "migrate off" to keep consistent. Change-Id: Ie54c6ed83b6e9a3116e1832b3fb36dd80781366d --- doc/source/cli/data/nova.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/cli/data/nova.csv b/doc/source/cli/data/nova.csv index 338244e05c..fe2aa362fe 100644 --- a/doc/source/cli/data/nova.csv +++ b/doc/source/cli/data/nova.csv @@ -45,7 +45,7 @@ get-vnc-console,console url show --novnc | --xvpvnc,Get a vnc console to a serve host-action,,Perform a power action on a host. host-describe,host show,Describe a specific host. host-evacuate,,Evacuate all instances from failed host. -host-evacuate-live,,Live migrate all instances of the specified host to other available hosts. +host-evacuate-live,,Live migrate all instances off the specified host to other available hosts. host-list,host list,List all hosts by service. host-meta,,Set or Delete metadata on all instances of a host. host-servers-migrate,,Cold migrate all instances off the specified host to other available hosts. From 030fd71390e8e4b8413e916e64076337035f9aa7 Mon Sep 17 00:00:00 2001 From: Alan Bishop Date: Fri, 10 Aug 2018 10:20:34 -0400 Subject: [PATCH 1963/3095] Deprecate volume create --project and --user options Cinder's volume create API does not support overriding the project_id and user_id, and it silently igores those API inputs. Cinder always uses the project and user info in the keystone identity associated with the API request. If a user specifies the --project or --user option, the volume create is aborted and a CommandError exception is raised. This prevents a volume from being created, but without the desired project/user values. A user wishing to specify alternate values can still do so using identity overrides (e.g. --os-username, --os-project-id). Story: 2002583 Task: 22192 Change-Id: Ia9f910ea1b0e61797e8c8c463fa28e7390f15bf9 --- .../tests/unit/volume/v2/test_volume.py | 94 ++----------------- openstackclient/volume/v2/volume.py | 30 +++--- .../notes/bug-1777153-750e6044aa28d5d8.yaml | 15 +++ 3 files changed, 41 insertions(+), 98 deletions(-) create mode 100644 releasenotes/notes/bug-1777153-750e6044aa28d5d8.yaml diff --git a/openstackclient/tests/unit/volume/v2/test_volume.py b/openstackclient/tests/unit/volume/v2/test_volume.py index 971567cb85..bb6263bbe2 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume.py +++ b/openstackclient/tests/unit/volume/v2/test_volume.py @@ -126,8 +126,6 @@ def test_volume_create_min_options(self): name=self.new_volume.name, description=None, volume_type=None, - user_id=None, - project_id=None, availability_zone=None, metadata=None, imageRef=None, @@ -177,8 +175,6 @@ def test_volume_create_options(self): name=self.new_volume.name, description=self.new_volume.description, volume_type=self.new_volume.volume_type, - user_id=None, - project_id=None, availability_zone=self.new_volume.availability_zone, metadata=None, imageRef=None, @@ -191,95 +187,39 @@ def test_volume_create_options(self): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, data) - def test_volume_create_user_project_id(self): - # Return a project - self.projects_mock.get.return_value = self.project - # Return a user - self.users_mock.get.return_value = self.user - + def test_volume_create_user(self): arglist = [ '--size', str(self.new_volume.size), - '--project', self.project.id, '--user', self.user.id, self.new_volume.name, ] verifylist = [ ('size', self.new_volume.size), - ('project', self.project.id), ('user', self.user.id), ('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, - user_id=self.user.id, - project_id=self.project.id, - availability_zone=None, - metadata=None, - imageRef=None, - source_volid=None, - consistencygroup_id=None, - multiattach=False, - scheduler_hints=None, - ) - - self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) - - def test_volume_create_user_project_name(self): - # Return a project - self.projects_mock.get.return_value = self.project - # Return a user - self.users_mock.get.return_value = self.user + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + self.volumes_mock.create.assert_not_called() + def test_volume_create_project(self): arglist = [ '--size', str(self.new_volume.size), - '--project', self.project.name, - '--user', self.user.name, + '--project', self.project.id, self.new_volume.name, ] verifylist = [ ('size', self.new_volume.size), - ('project', self.project.name), - ('user', self.user.name), + ('project', self.project.id), ('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, - user_id=self.user.id, - project_id=self.project.id, - availability_zone=None, - metadata=None, - imageRef=None, - source_volid=None, - consistencygroup_id=None, - multiattach=False, - scheduler_hints=None, - ) - - self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + self.volumes_mock.create.assert_not_called() def test_volume_create_properties(self): arglist = [ @@ -306,8 +246,6 @@ def test_volume_create_properties(self): name=self.new_volume.name, description=None, volume_type=None, - user_id=None, - project_id=None, availability_zone=None, metadata={'Alpha': 'a', 'Beta': 'b'}, imageRef=None, @@ -347,8 +285,6 @@ def test_volume_create_image_id(self): name=self.new_volume.name, description=None, volume_type=None, - user_id=None, - project_id=None, availability_zone=None, metadata=None, imageRef=image.id, @@ -388,8 +324,6 @@ def test_volume_create_image_name(self): name=self.new_volume.name, description=None, volume_type=None, - user_id=None, - project_id=None, availability_zone=None, metadata=None, imageRef=image.id, @@ -428,8 +362,6 @@ def test_volume_create_with_snapshot(self): name=self.new_volume.name, description=None, volume_type=None, - user_id=None, - project_id=None, availability_zone=None, metadata=None, imageRef=None, @@ -469,8 +401,6 @@ def test_volume_create_with_bootable_and_readonly(self): name=self.new_volume.name, description=None, volume_type=None, - user_id=None, - project_id=None, availability_zone=None, metadata=None, imageRef=None, @@ -514,8 +444,6 @@ def test_volume_create_with_nonbootable_and_readwrite(self): name=self.new_volume.name, description=None, volume_type=None, - user_id=None, - project_id=None, availability_zone=None, metadata=None, imageRef=None, @@ -568,8 +496,6 @@ def test_volume_create_with_bootable_and_readonly_fail( name=self.new_volume.name, description=None, volume_type=None, - user_id=None, - project_id=None, availability_zone=None, metadata=None, imageRef=None, diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index fc648cef39..8ab61d2aa1 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -96,12 +96,12 @@ def get_parser(self, prog_name): parser.add_argument( '--user', metavar='', - help=_('Specify an alternate user (name or ID)'), + help=argparse.SUPPRESS, ) parser.add_argument( '--project', metavar='', - help=_('Specify an alternate project (name or ID)'), + help=argparse.SUPPRESS, ) parser.add_argument( "--availability-zone", @@ -159,7 +159,6 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): _check_size_arg(parsed_args) - identity_client = self.app.client_manager.identity volume_client = self.app.client_manager.volume image_client = self.app.client_manager.image @@ -197,17 +196,22 @@ def take_action(self, parsed_args): # snapshot size. size = max(size or 0, snapshot_obj.size) - project = None + # NOTE(abishop): Cinder's volumes.create() has 'project_id' and + # 'user_id' args, but they're not wired up to anything. The only way + # to specify an alternate project or user for the volume is to use + # the identity overrides (e.g. "--os-project-id"). + # + # Now, if the project or user arg is specified then the command is + # rejected. Otherwise, Cinder would actually create a volume, but + # without the specified property. if parsed_args.project: - project = utils.find_resource( - identity_client.projects, - parsed_args.project).id - - user = None + raise exceptions.CommandError( + _("ERROR: --project is deprecated, please use" + " --os-project-name or --os-project-id instead.")) if parsed_args.user: - user = utils.find_resource( - identity_client.users, - parsed_args.user).id + raise exceptions.CommandError( + _("ERROR: --user is deprecated, please use" + " --os-username instead.")) volume = volume_client.volumes.create( size=size, @@ -215,8 +219,6 @@ def take_action(self, parsed_args): name=parsed_args.name, description=parsed_args.description, volume_type=parsed_args.type, - user_id=user, - project_id=project, availability_zone=parsed_args.availability_zone, metadata=parsed_args.property, imageRef=image, diff --git a/releasenotes/notes/bug-1777153-750e6044aa28d5d8.yaml b/releasenotes/notes/bug-1777153-750e6044aa28d5d8.yaml new file mode 100644 index 0000000000..a2a9b51aea --- /dev/null +++ b/releasenotes/notes/bug-1777153-750e6044aa28d5d8.yaml @@ -0,0 +1,15 @@ +--- +deprecations: + - | + The ``--project`` and ``--user`` options for the ``volume create`` + command have been deprecated. They are deprecated because Cinder's + volume create API ignores the corresponding API inputs. +fixes: + - | + Fix ``volume create`` by removing two broken options. The ``--project`` + and ``--user`` options were intended to specify an alternate project + and/or user for the volume, but the Volume service's API does not + support this behavior. This caused the volume to be created, but + without the expected project/user values. However, an alternate + project and/or user may be specified using identity overrides (e.g. + --os-username, --os-project-id). From 77a533f277e435c936da591fa5b51e9c822704b6 Mon Sep 17 00:00:00 2001 From: qingszhao Date: Tue, 21 Aug 2018 14:56:17 +0000 Subject: [PATCH 1964/3095] import zuul job settings from project-config This is a mechanically generated patch to complete step 1 of moving the zuul job settings out of project-config and into each project repository. Because there will be a separate patch on each branch, the branch specifiers for branch-specific jobs have been removed. Because this patch is generated by a script, there may be some cosmetic changes to the layout of the YAML file(s) as the contents are normalized. See the python3-first goal document for details: https://governance.openstack.org/tc/goals/stein/python3-first.html Change-Id: I43acfa46c1df8912ae641529f3cc975c8e752480 Story: #2002586 Task: #24320 --- .zuul.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.zuul.yaml b/.zuul.yaml index 4541fbde0a..ee3abfcbe8 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -192,6 +192,12 @@ templates: - openstackclient-plugin-jobs - osc-tox-unit-tips + - openstack-python-jobs + - openstack-python35-jobs + - publish-openstack-sphinx-docs + - check-requirements + - release-notes-jobs + - lib-forward-testing check: jobs: - osc-functional-devstack @@ -210,3 +216,6 @@ jobs: - osc-functional-devstack - openstack-tox-lower-constraints + post: + jobs: + - openstack-tox-cover From d8020abbe79bd50b2bfa0f31516d85dcf95bf6d2 Mon Sep 17 00:00:00 2001 From: qingszhao Date: Tue, 21 Aug 2018 14:56:19 +0000 Subject: [PATCH 1965/3095] switch documentation job to new PTI This is a mechanically generated patch to switch the documentation jobs to use the new PTI versions of the jobs as part of the python3-first goal. See the python3-first goal document for details: https://governance.openstack.org/tc/goals/stein/python3-first.html Change-Id: If0416171105673b6ed031398e41d46257fe113eb Story: #2002586 Task: #24320 --- .zuul.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.zuul.yaml b/.zuul.yaml index ee3abfcbe8..dc0d685c77 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -194,9 +194,9 @@ - osc-tox-unit-tips - openstack-python-jobs - openstack-python35-jobs - - publish-openstack-sphinx-docs + - publish-openstack-docs-pti - check-requirements - - release-notes-jobs + - release-notes-jobs-python3 - lib-forward-testing check: jobs: From 67eeacdd717831c4ead43ffc4e1d60dd7807522c Mon Sep 17 00:00:00 2001 From: qingszhao Date: Tue, 21 Aug 2018 14:56:23 +0000 Subject: [PATCH 1966/3095] add python 3.6 unit test job This is a mechanically generated patch to add a unit test job running under Python 3.6 as part of the python3-first goal. See the python3-first goal document for details: https://governance.openstack.org/tc/goals/stein/python3-first.html Change-Id: I1681490fcaa52593abcbf8f935cbe1df7769f2eb Story: #2002586 Task: #24320 --- .zuul.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.zuul.yaml b/.zuul.yaml index dc0d685c77..d5beefc3ed 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -194,6 +194,7 @@ - osc-tox-unit-tips - openstack-python-jobs - openstack-python35-jobs + - openstack-python36-jobs - publish-openstack-docs-pti - check-requirements - release-notes-jobs-python3 From 0461b7ce0c23f7aebb60ee362d1d89cfa38abe4b Mon Sep 17 00:00:00 2001 From: qingszhao Date: Tue, 21 Aug 2018 14:56:25 +0000 Subject: [PATCH 1967/3095] add lib-forward-testing-python3 test job This is a mechanically generated patch to add a functional test job running under Python 3 as part of the python3-first goal. See the python3-first goal document for details: https://governance.openstack.org/tc/goals/stein/python3-first.html Change-Id: I1d186a6759bd3e633661c75e62545d0d56700244 Story: #2002586 Task: #24320 --- .zuul.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.zuul.yaml b/.zuul.yaml index d5beefc3ed..317234a5cd 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -199,6 +199,7 @@ - check-requirements - release-notes-jobs-python3 - lib-forward-testing + - lib-forward-testing-python3 check: jobs: - osc-functional-devstack From a5865b176304b92736a71cd6ad2dd1199c588ba5 Mon Sep 17 00:00:00 2001 From: Bernard Cafarelli Date: Tue, 4 Sep 2018 17:52:46 +0200 Subject: [PATCH 1968/3095] Partially Revert "Add command to unset information from Subnet-pools" We do not support removing a prefix from a subnet pool, only updating with a larger prefix (which is handled by the set command) This reverts commit 063c722a110031883e9615064092644de6df8da2. Change-Id: I11224fbdb94dc1caef42a8a64cbcebaf1dc542fe Story: #1670230 Task: #13697 --- .../cli/command-objects/subnet-pool.rst | 6 ---- openstackclient/network/v2/subnet_pool.py | 23 ------------ .../tests/unit/network/v2/test_subnet_pool.py | 35 +------------------ 3 files changed, 1 insertion(+), 63 deletions(-) diff --git a/doc/source/cli/command-objects/subnet-pool.rst b/doc/source/cli/command-objects/subnet-pool.rst index 0cff4d7f56..4606881b54 100644 --- a/doc/source/cli/command-objects/subnet-pool.rst +++ b/doc/source/cli/command-objects/subnet-pool.rst @@ -292,15 +292,9 @@ Unset subnet pool properties .. code:: bash openstack subnet pool unset - [--pool-prefix [...]] [--tag | --all-tag] -.. option:: --pool-prefix - - Remove subnet pool prefixes (in CIDR notation). - (repeat option to unset multiple prefixes). - .. option:: --tag Tag to be removed from the subnet pool diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index a583986856..d85410691a 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -13,7 +13,6 @@ """Subnet pool action implementations""" -import copy import logging from osc_lib.cli import parseractions @@ -440,14 +439,6 @@ class UnsetSubnetPool(command.Command): def get_parser(self, prog_name): parser = super(UnsetSubnetPool, self).get_parser(prog_name) - parser.add_argument( - '--pool-prefix', - metavar='', - action='append', - dest='prefixes', - help=_('Remove subnet pool prefixes (in CIDR notation). ' - '(repeat option to unset multiple prefixes).'), - ) parser.add_argument( 'subnet_pool', metavar="", @@ -460,19 +451,5 @@ def take_action(self, parsed_args): client = self.app.client_manager.network obj = client.find_subnet_pool( parsed_args.subnet_pool, ignore_missing=False) - tmp_prefixes = copy.deepcopy(obj.prefixes) - attrs = {} - if parsed_args.prefixes: - for prefix in parsed_args.prefixes: - try: - tmp_prefixes.remove(prefix) - except ValueError: - msg = _( - "Subnet pool does not " - "contain prefix %s") % prefix - raise exceptions.CommandError(msg) - attrs['prefixes'] = tmp_prefixes - if attrs: - client.update_subnet_pool(obj, **attrs) # tags is a subresource and it needs to be updated separately. _tag.update_tags_for_unset(client, obj, parsed_args) diff --git a/openstackclient/tests/unit/network/v2/test_subnet_pool.py b/openstackclient/tests/unit/network/v2/test_subnet_pool.py index 81f0278f26..3d8f1028e6 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/unit/network/v2/test_subnet_pool.py @@ -1016,9 +1016,7 @@ class TestUnsetSubnetPool(TestSubnetPool): def setUp(self): super(TestUnsetSubnetPool, self).setUp() self._subnetpool = network_fakes.FakeSubnetPool.create_one_subnet_pool( - {'prefixes': ['10.0.10.0/24', '10.1.10.0/24', - '10.2.10.0/24'], - 'tags': ['green', 'red']}) + {'tags': ['green', 'red']}) self.network.find_subnet_pool = mock.Mock( return_value=self._subnetpool) self.network.update_subnet_pool = mock.Mock(return_value=None) @@ -1026,37 +1024,6 @@ def setUp(self): # Get the command object to test self.cmd = subnet_pool.UnsetSubnetPool(self.app, self.namespace) - def test_unset_subnet_pool(self): - arglist = [ - '--pool-prefix', '10.0.10.0/24', - '--pool-prefix', '10.1.10.0/24', - self._subnetpool.name, - ] - verifylist = [ - ('prefixes', ['10.0.10.0/24', '10.1.10.0/24']), - ('subnet_pool', self._subnetpool.name), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) - attrs = {'prefixes': ['10.2.10.0/24']} - self.network.update_subnet_pool.assert_called_once_with( - self._subnetpool, **attrs) - self.assertIsNone(result) - - def test_unset_subnet_pool_prefix_not_existent(self): - arglist = [ - '--pool-prefix', '10.100.1.1/25', - self._subnetpool.name, - ] - verifylist = [ - ('prefixes', ['10.100.1.1/25']), - ('subnet_pool', self._subnetpool.name), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.assertRaises(exceptions.CommandError, - self.cmd.take_action, - parsed_args) - def _test_unset_tags(self, with_tags=True): if with_tags: arglist = ['--tag', 'red', '--tag', 'blue'] From 62302ad98a9893470d6f3999dd30473b798e6aab Mon Sep 17 00:00:00 2001 From: Andreas Jaeger Date: Fri, 7 Sep 2018 15:12:09 +0200 Subject: [PATCH 1969/3095] Use templates for cover and lower-constraints Use openstack-tox-cover template, this runs the cover job in the check queue only. Use openstack-lower-constraints-jobs template Remove jobs that are part of the templates. Change-Id: Id210b2f15dcba9dcf9ad75e0436995e7d5c1b62b --- .zuul.yaml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.zuul.yaml b/.zuul.yaml index 317234a5cd..7cf30cf138 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -192,6 +192,8 @@ templates: - openstackclient-plugin-jobs - osc-tox-unit-tips + - openstack-cover-jobs + - openstack-lower-constraints-jobs - openstack-python-jobs - openstack-python35-jobs - openstack-python36-jobs @@ -213,11 +215,6 @@ # The functional-tips job only tests the latest and shouldn't be run # on the stable branches branches: ^(?!stable) - - openstack-tox-lower-constraints gate: jobs: - osc-functional-devstack - - openstack-tox-lower-constraints - post: - jobs: - - openstack-tox-cover From 1981eb22500b7c389befc8e91d7ba5cebbe19a88 Mon Sep 17 00:00:00 2001 From: Josephine Seifert Date: Wed, 4 Jul 2018 10:17:18 +0200 Subject: [PATCH 1970/3095] osc-included image signing (using openstacksdk) This extension adds image signing functionality to the "image create" command. Therefore, new CLI options --sign-key-path and --sign-cert-id have been added. This patch uses openstacksdk as the signing backend library instead of cursive. Therefore, requirements and lower_constraints have been updated to use the openstacksdk version 0.17 or higher. Depends-On: Idc15b9a12d408bd4b2e096da8402c374be56f9fa Change-Id: Ia20bc02a49c3fbeb9222e485e3396395f4ab817a Story: 2002128 Co-Authored-By: Markus Hentsch --- lower-constraints.txt | 2 +- openstackclient/image/v2/image.py | 58 +++++++++++++++++++ ...cluded-image-signing-a7021a4dbdcf6336.yaml | 11 ++++ requirements.txt | 2 +- 4 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/osc-included-image-signing-a7021a4dbdcf6336.yaml diff --git a/lower-constraints.txt b/lower-constraints.txt index 6cb1b44fc0..db92fef36b 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -50,7 +50,7 @@ msgpack-python==0.4.0 munch==2.1.0 netaddr==0.7.18 netifaces==0.10.4 -openstacksdk==0.11.2 +openstacksdk==0.17.0 os-client-config==1.28.0 os-service-types==1.2.0 os-testr==1.0.0 diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 3d9c199d18..1e67692a54 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -16,9 +16,11 @@ """Image V2 Action Implementations""" import argparse +from base64 import b64encode import logging from glanceclient.common import utils as gc_utils +from openstack.image import image_signer from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -183,6 +185,22 @@ def get_parser(self, prog_name): help=_("Force image creation if volume is in use " "(only meaningful with --volume)"), ) + parser.add_argument( + '--sign-key-path', + metavar="", + default=[], + help=_("Sign the image using the specified private key. " + "Only use in combination with --sign-cert-id") + ) + parser.add_argument( + '--sign-cert-id', + metavar="", + default=[], + help=_("The specified certificate UUID is a reference to " + "the certificate in the key manager that corresponds " + "to the public key and is used for signature validation. " + "Only use in combination with --sign-key-path") + ) protected_group = parser.add_mutually_exclusive_group() protected_group.add_argument( "--protected", @@ -335,6 +353,46 @@ def take_action(self, parsed_args): parsed_args.project_domain, ).id + # sign an image using a given local private key file + if parsed_args.sign_key_path or parsed_args.sign_cert_id: + if not parsed_args.file: + msg = (_("signing an image requires the --file option, " + "passing files via stdin when signing is not " + "supported.")) + raise exceptions.CommandError(msg) + if (len(parsed_args.sign_key_path) < 1 or + len(parsed_args.sign_cert_id) < 1): + msg = (_("'sign-key-path' and 'sign-cert-id' must both be " + "specified when attempting to sign an image.")) + raise exceptions.CommandError(msg) + else: + sign_key_path = parsed_args.sign_key_path + sign_cert_id = parsed_args.sign_cert_id + signer = image_signer.ImageSigner() + try: + pw = utils.get_password( + self.app.stdin, + prompt=("Please enter private key password, leave " + "empty if none: "), + confirm=False) + if not pw or len(pw) < 1: + pw = None + signer.load_private_key( + sign_key_path, + password=pw) + except Exception: + msg = (_("Error during sign operation: private key could " + "not be loaded.")) + raise exceptions.CommandError(msg) + + signature = signer.generate_signature(fp) + signature_b64 = b64encode(signature) + kwargs['img_signature'] = signature_b64 + kwargs['img_signature_certificate_uuid'] = sign_cert_id + kwargs['img_signature_hash_method'] = signer.hash_method + if signer.padding_method: + kwargs['img_signature_key_type'] = signer.padding_method + # If a volume is specified. if parsed_args.volume: volume_client = self.app.client_manager.volume diff --git a/releasenotes/notes/osc-included-image-signing-a7021a4dbdcf6336.yaml b/releasenotes/notes/osc-included-image-signing-a7021a4dbdcf6336.yaml new file mode 100644 index 0000000000..7c7a2d6163 --- /dev/null +++ b/releasenotes/notes/osc-included-image-signing-a7021a4dbdcf6336.yaml @@ -0,0 +1,11 @@ +--- +features: + - | + Add options ``--sign-key-path`` and ``--sign-cert-id`` to the ``image create`` + command. Tthe image must be present on disk, therefore the ``file`` option + is required: + + ``image create --file --sign-key-path --sign-cert-id ``. + + A prompt for a password ensures, that the private key can be protected too. + [Bug `2002128 `_] \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 2ee03a5cd3..205d4e64ec 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ six>=1.10.0 # MIT Babel!=2.4.0,>=2.3.4 # BSD cliff!=2.9.0,>=2.8.0 # Apache-2.0 keystoneauth1>=3.4.0 # Apache-2.0 -openstacksdk>=0.11.2 # Apache-2.0 +openstacksdk>=0.17.0 # Apache-2.0 osc-lib>=1.10.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 From 14ceb6307f620d3b4e5779046d4f54c9bc0deb4b Mon Sep 17 00:00:00 2001 From: David Rabel Date: Wed, 12 Sep 2018 17:25:10 +0200 Subject: [PATCH 1971/3095] Add metavar for name parameter in subnet create Change-Id: I2511677006687fff3166441c51e91191492962f1 Closes-Bug: #1747731 --- openstackclient/network/v2/subnet.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 2c4b9c1c83..d252a7a51d 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -247,6 +247,7 @@ def get_parser(self, prog_name): parser = super(CreateSubnet, self).get_parser(prog_name) parser.add_argument( 'name', + metavar='', help=_("New subnet name") ) parser.add_argument( From 1b66ad9067cc404ebfdc8569822d226d5bffddd6 Mon Sep 17 00:00:00 2001 From: Julie Pichon Date: Fri, 14 Sep 2018 09:59:32 +0100 Subject: [PATCH 1972/3095] Fix 'project purge' deleting wrong project's servers and volumes Project purge would delete the servers and volumes for the project the user is currently authenticated for, regardless of the --project flag. Note: This change means that no server at all will be deleted if the logged in user doesn't have the get_all_tenants permission set in the Nova policy (default: admin_api). This doesn't appear to be an issue with Cinder as the default rule appears to be admin_or_owner. Change-Id: If1c54e24e1482438b81c3c32fd5fc9fdd7a7be04 Story: 1747988 Task: 13854 --- openstackclient/common/project_purge.py | 4 +-- .../tests/unit/common/test_project_purge.py | 30 +++++++++++-------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/openstackclient/common/project_purge.py b/openstackclient/common/project_purge.py index 5b1d007254..76ed4563b0 100644 --- a/openstackclient/common/project_purge.py +++ b/openstackclient/common/project_purge.py @@ -85,7 +85,7 @@ def delete_resources(self, dry_run, project_id): # servers try: compute_client = self.app.client_manager.compute - search_opts = {'tenant_id': project_id} + search_opts = {'tenant_id': project_id, 'all_tenants': True} data = compute_client.servers.list(search_opts=search_opts) self.delete_objects( compute_client.servers.delete, data, 'server', dry_run) @@ -110,7 +110,7 @@ def delete_resources(self, dry_run, project_id): # volumes, snapshots, backups volume_client = self.app.client_manager.volume - search_opts = {'project_id': project_id} + search_opts = {'project_id': project_id, 'all_tenants': True} try: data = volume_client.volume_snapshots.list(search_opts=search_opts) self.delete_objects( diff --git a/openstackclient/tests/unit/common/test_project_purge.py b/openstackclient/tests/unit/common/test_project_purge.py index 2385eae893..6e8ce188f7 100644 --- a/openstackclient/tests/unit/common/test_project_purge.py +++ b/openstackclient/tests/unit/common/test_project_purge.py @@ -117,10 +117,11 @@ def test_project_purge_with_project(self): self.projects_mock.get.assert_called_once_with(self.project.id) self.projects_mock.delete.assert_called_once_with(self.project.id) self.servers_mock.list.assert_called_once_with( - search_opts={'tenant_id': self.project.id}) + search_opts={'tenant_id': self.project.id, 'all_tenants': True}) kwargs = {'filters': {'owner': self.project.id}} self.images_mock.list.assert_called_once_with(**kwargs) - volume_search_opts = {'project_id': self.project.id} + volume_search_opts = {'project_id': self.project.id, + 'all_tenants': True} self.volumes_mock.list.assert_called_once_with( search_opts=volume_search_opts) self.snapshots_mock.list.assert_called_once_with( @@ -152,10 +153,11 @@ def test_project_purge_with_dry_run(self): self.projects_mock.get.assert_called_once_with(self.project.id) self.projects_mock.delete.assert_not_called() self.servers_mock.list.assert_called_once_with( - search_opts={'tenant_id': self.project.id}) + search_opts={'tenant_id': self.project.id, 'all_tenants': True}) kwargs = {'filters': {'owner': self.project.id}} self.images_mock.list.assert_called_once_with(**kwargs) - volume_search_opts = {'project_id': self.project.id} + volume_search_opts = {'project_id': self.project.id, + 'all_tenants': True} self.volumes_mock.list.assert_called_once_with( search_opts=volume_search_opts) self.snapshots_mock.list.assert_called_once_with( @@ -187,10 +189,11 @@ def test_project_purge_with_keep_project(self): self.projects_mock.get.assert_called_once_with(self.project.id) self.projects_mock.delete.assert_not_called() self.servers_mock.list.assert_called_once_with( - search_opts={'tenant_id': self.project.id}) + search_opts={'tenant_id': self.project.id, 'all_tenants': True}) kwargs = {'filters': {'owner': self.project.id}} self.images_mock.list.assert_called_once_with(**kwargs) - volume_search_opts = {'project_id': self.project.id} + volume_search_opts = {'project_id': self.project.id, + 'all_tenants': True} self.volumes_mock.list.assert_called_once_with( search_opts=volume_search_opts) self.snapshots_mock.list.assert_called_once_with( @@ -223,10 +226,11 @@ def test_project_purge_with_auth_project(self): self.projects_mock.get.assert_not_called() self.projects_mock.delete.assert_called_once_with(self.project.id) self.servers_mock.list.assert_called_once_with( - search_opts={'tenant_id': self.project.id}) + search_opts={'tenant_id': self.project.id, 'all_tenants': True}) kwargs = {'filters': {'owner': self.project.id}} self.images_mock.list.assert_called_once_with(**kwargs) - volume_search_opts = {'project_id': self.project.id} + volume_search_opts = {'project_id': self.project.id, + 'all_tenants': True} self.volumes_mock.list.assert_called_once_with( search_opts=volume_search_opts) self.snapshots_mock.list.assert_called_once_with( @@ -259,10 +263,11 @@ def test_project_purge_with_exception(self, mock_error): self.projects_mock.get.assert_called_once_with(self.project.id) self.projects_mock.delete.assert_called_once_with(self.project.id) self.servers_mock.list.assert_called_once_with( - search_opts={'tenant_id': self.project.id}) + search_opts={'tenant_id': self.project.id, 'all_tenants': True}) kwargs = {'filters': {'owner': self.project.id}} self.images_mock.list.assert_called_once_with(**kwargs) - volume_search_opts = {'project_id': self.project.id} + volume_search_opts = {'project_id': self.project.id, + 'all_tenants': True} self.volumes_mock.list.assert_called_once_with( search_opts=volume_search_opts) self.snapshots_mock.list.assert_called_once_with( @@ -295,10 +300,11 @@ def test_project_purge_with_force_delete_backup(self): self.projects_mock.get.assert_called_once_with(self.project.id) self.projects_mock.delete.assert_called_once_with(self.project.id) self.servers_mock.list.assert_called_once_with( - search_opts={'tenant_id': self.project.id}) + search_opts={'tenant_id': self.project.id, 'all_tenants': True}) kwargs = {'filters': {'owner': self.project.id}} self.images_mock.list.assert_called_once_with(**kwargs) - volume_search_opts = {'project_id': self.project.id} + volume_search_opts = {'project_id': self.project.id, + 'all_tenants': True} self.volumes_mock.list.assert_called_once_with( search_opts=volume_search_opts) self.snapshots_mock.list.assert_called_once_with( From eb4f839ec7c1a5e791d6f798c5efb705d55b1e01 Mon Sep 17 00:00:00 2001 From: Nguyen Van Duc Date: Wed, 18 Jul 2018 15:50:18 +0700 Subject: [PATCH 1973/3095] Replace port 35357 with 5000 for "auth_url" Based on the change in Keystone Install Guide [1], this patch replace port 35357 with 5000 for "auth_url". For more details, please check similar changes which have been done on other projects: Nova [2], Neutron [3], Cinder [4], Glance [5]. [1] https://review.openstack.org/#/c/541857 [2] https://review.openstack.org/#/c/562812 [3] https://review.openstack.org/#/c/566491 [4] https://review.openstack.org/#/c/565464 [5] https://review.openstack.org/#/c/558932 Change-Id: I4faabbb107f912c7ed1cc5d3467ea5a94197d4a0 --- doc/source/cli/man/openstack.rst | 4 ++-- doc/source/configuration/index.rst | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/source/cli/man/openstack.rst b/doc/source/cli/man/openstack.rst index 0745fb7a06..649657523b 100644 --- a/doc/source/cli/man/openstack.rst +++ b/doc/source/cli/man/openstack.rst @@ -278,14 +278,14 @@ The keys match the :program:`openstack` global options but without the clouds: devstack: auth: - auth_url: http://192.168.122.10:35357/ + auth_url: http://192.168.122.10:5000/ project_name: demo username: demo password: 0penstack region_name: RegionOne ds-admin: auth: - auth_url: http://192.168.122.10:35357/ + auth_url: http://192.168.122.10:5000/ project_name: admin username: admin password: 0penstack diff --git a/doc/source/configuration/index.rst b/doc/source/configuration/index.rst index d2b273d7ee..b74bb3659b 100644 --- a/doc/source/configuration/index.rst +++ b/doc/source/configuration/index.rst @@ -61,14 +61,14 @@ The keys match the :program:`openstack` global options but without the clouds: devstack: auth: - auth_url: http://192.168.122.10:35357/ + auth_url: http://192.168.122.10:5000/ project_name: demo username: demo password: 0penstack region_name: RegionOne ds-admin: auth: - auth_url: http://192.168.122.10:35357/ + auth_url: http://192.168.122.10:5000/ project_name: admin username: admin password: 0penstack @@ -121,7 +121,7 @@ domain name as shown in the example below: clouds: devstack: auth: - auth_url: http://192.168.122.10:35357/ + auth_url: http://192.168.122.10:5000/ project_name: demo username: demo password: 0penstack @@ -171,7 +171,7 @@ By setting `log_level` or `log_file` in the configuration clouds: devstack: auth: - auth_url: http://192.168.122.10:35357/ + auth_url: http://192.168.122.10:5000/ project_name: demo username: demo password: 0penstack @@ -182,7 +182,7 @@ By setting `log_level` or `log_file` in the configuration level: info ds-admin: auth: - auth_url: http://192.168.122.10:35357/ + auth_url: http://192.168.122.10:5000/ project_name: admin username: admin password: 0penstack From eb001733fd3c1a98027f7439b84e952f1eb2a406 Mon Sep 17 00:00:00 2001 From: M V P Nitesh Date: Mon, 17 Jul 2017 18:08:58 +0530 Subject: [PATCH 1974/3095] Now we can add description for role creation in OSC Now user can add the description when user create's the role using OSC ``openstack role create`` command. User can add the description by adding `--description ` to OSC ``openstack role create`` command. Co-Authored-By: Deepak Mourya Change-Id: I858e004c3b29c687b6a39c8a1ed5fb029eb19c67 Depends-on: I230af9cc833af13064636b5d9a7ce6334c3f6e9a Closes-Bug: #1669080 --- doc/source/cli/command-objects/role.rst | 4 + openstackclient/identity/v3/role.py | 16 +++- .../tests/functional/identity/v3/common.py | 2 +- .../tests/functional/identity/v3/test_role.py | 25 ++++++ .../tests/unit/identity/v3/fakes.py | 1 + .../tests/unit/identity/v3/test_role.py | 79 +++++++++++++++++++ ...-description-to-role-afe7b6ff668df261.yaml | 8 ++ 7 files changed, 132 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/add-description-to-role-afe7b6ff668df261.yaml diff --git a/doc/source/cli/command-objects/role.rst b/doc/source/cli/command-objects/role.rst index 9819fd1231..d84eec9d4b 100644 --- a/doc/source/cli/command-objects/role.rst +++ b/doc/source/cli/command-objects/role.rst @@ -115,6 +115,10 @@ Create new role New role name +.. option:: --description + + Add description about the role + role delete ----------- diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index 58a76f8a65..09e914b5c8 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -177,6 +177,11 @@ def get_parser(self, prog_name): metavar='', help=_('New role name'), ) + parser.add_argument( + '--description', + metavar='', + help=_('Add description about the role'), + ) parser.add_argument( '--domain', metavar='', @@ -199,7 +204,8 @@ def take_action(self, parsed_args): try: role = identity_client.roles.create( - name=parsed_args.name, domain=domain_id) + name=parsed_args.name, domain=domain_id, + description=parsed_args.description) except ks_exc.Conflict: if parsed_args.or_show: @@ -449,6 +455,11 @@ def get_parser(self, prog_name): metavar='', help=_('Role to modify (name or ID)'), ) + parser.add_argument( + '--description', + metavar='', + help=_('Add description about the role'), + ) parser.add_argument( '--domain', metavar='', @@ -473,7 +484,8 @@ def take_action(self, parsed_args): parsed_args.role, domain_id=domain_id) - identity_client.roles.update(role.id, name=parsed_args.name) + identity_client.roles.update(role.id, name=parsed_args.name, + description=parsed_args.description) class ShowRole(command.ShowOne): diff --git a/openstackclient/tests/functional/identity/v3/common.py b/openstackclient/tests/functional/identity/v3/common.py index 58468bc7e7..434bd949d7 100644 --- a/openstackclient/tests/functional/identity/v3/common.py +++ b/openstackclient/tests/functional/identity/v3/common.py @@ -32,7 +32,7 @@ class IdentityTests(base.TestCase): 'password_expires_at'] PROJECT_FIELDS = ['description', 'id', 'domain_id', 'is_domain', 'enabled', 'name', 'parent_id'] - ROLE_FIELDS = ['id', 'name', 'domain_id'] + ROLE_FIELDS = ['id', 'name', 'domain_id', 'description'] SERVICE_FIELDS = ['id', 'enabled', 'name', 'type', 'description'] REGION_FIELDS = ['description', 'enabled', 'parent_region', 'region'] ENDPOINT_FIELDS = ['id', 'region', 'region_id', 'service_id', diff --git a/openstackclient/tests/functional/identity/v3/test_role.py b/openstackclient/tests/functional/identity/v3/test_role.py index fb9e061424..8e9f0f90b8 100644 --- a/openstackclient/tests/functional/identity/v3/test_role.py +++ b/openstackclient/tests/functional/identity/v3/test_role.py @@ -20,6 +20,21 @@ class RoleTests(common.IdentityTests): def test_role_create(self): self._create_dummy_role() + def test_role_create_with_description(self): + role_name = data_utils.rand_name('TestRole') + description = data_utils.rand_name('description') + raw_output = self.openstack( + 'role create ' + '--description %(description)s ' + '%(name)s' % {'description': description, + 'name': role_name}) + role = self.parse_show_as_object(raw_output) + self.addCleanup(self.openstack, 'role delete %s' % role['id']) + items = self.parse_show(raw_output) + self.assert_show_fields(items, self.ROLE_FIELDS) + self.assertEqual(description, role['description']) + return role_name + def test_role_delete(self): role_name = self._create_dummy_role(add_clean_up=False) raw_output = self.openstack('role delete %s' % role_name) @@ -88,6 +103,16 @@ def test_role_set(self): role = self.parse_show_as_object(raw_output) self.assertEqual(new_role_name, role['name']) + def test_role_set_description(self): + role_name = self._create_dummy_role() + description = data_utils.rand_name("NewDescription") + raw_output = self.openstack('role set --description %s %s' + % (description, role_name)) + self.assertEqual(0, len(raw_output)) + raw_output = self.openstack('role show %s' % role_name) + role = self.parse_show_as_object(raw_output) + self.assertEqual(description, role['description']) + def test_role_add(self): role_name = self._create_dummy_role() username = self._create_dummy_user() diff --git a/openstackclient/tests/unit/identity/v3/fakes.py b/openstackclient/tests/unit/identity/v3/fakes.py index 27ee9fd026..2bca48a7c8 100644 --- a/openstackclient/tests/unit/identity/v3/fakes.py +++ b/openstackclient/tests/unit/identity/v3/fakes.py @@ -175,6 +175,7 @@ role_id = 'r1' role_name = 'roller' +role_description = 'role description' ROLE = { 'id': role_id, diff --git a/openstackclient/tests/unit/identity/v3/test_role.py b/openstackclient/tests/unit/identity/v3/test_role.py index 281d530c7e..14569ffac4 100644 --- a/openstackclient/tests/unit/identity/v3/test_role.py +++ b/openstackclient/tests/unit/identity/v3/test_role.py @@ -332,6 +332,7 @@ def test_role_create_no_options(self): kwargs = { 'domain': None, 'name': identity_fakes.role_name, + 'description': None, } # RoleManager.create(name=, domain=) @@ -375,6 +376,7 @@ def test_role_create_with_domain(self): kwargs = { 'domain': identity_fakes.domain_id, 'name': identity_fakes.ROLE_2['name'], + 'description': None, } # RoleManager.create(name=, domain=) @@ -391,6 +393,49 @@ def test_role_create_with_domain(self): ) self.assertEqual(datalist, data) + def test_role_create_with_description(self): + + self.roles_mock.create.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.ROLE_2), + loaded=True, + ) + arglist = [ + '--description', identity_fakes.role_description, + identity_fakes.ROLE_2['name'], + ] + verifylist = [ + ('description', identity_fakes.role_description), + ('name', identity_fakes.ROLE_2['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) + + # Set expected values + kwargs = { + 'description': identity_fakes.role_description, + 'name': identity_fakes.ROLE_2['name'], + 'domain': None, + } + + # RoleManager.create(name=, domain=) + self.roles_mock.create.assert_called_with( + **kwargs + ) + + collist = ('domain', 'id', 'name') + self.assertEqual(collist, columns) + datalist = ( + 'd1', + identity_fakes.ROLE_2['id'], + identity_fakes.ROLE_2['name'], + ) + self.assertEqual(datalist, data) + class TestRoleDelete(TestRole): @@ -1057,6 +1102,7 @@ def test_role_set_no_options(self): # Set expected values kwargs = { 'name': 'over', + 'description': None, } # RoleManager.update(role, name=) self.roles_mock.update.assert_called_with( @@ -1088,6 +1134,39 @@ def test_role_set_domain_role(self): # Set expected values kwargs = { 'name': 'over', + 'description': None, + } + # RoleManager.update(role, name=) + self.roles_mock.update.assert_called_with( + identity_fakes.ROLE_2['id'], + **kwargs + ) + self.assertIsNone(result) + + def test_role_set_description(self): + self.roles_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.ROLE_2), + loaded=True, + ) + arglist = [ + '--name', 'over', + '--description', identity_fakes.role_description, + identity_fakes.ROLE_2['name'], + ] + verifylist = [ + ('name', 'over'), + ('description', identity_fakes.role_description), + ('role', identity_fakes.ROLE_2['name']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'name': 'over', + 'description': identity_fakes.role_description, } # RoleManager.update(role, name=) self.roles_mock.update.assert_called_with( diff --git a/releasenotes/notes/add-description-to-role-afe7b6ff668df261.yaml b/releasenotes/notes/add-description-to-role-afe7b6ff668df261.yaml new file mode 100644 index 0000000000..31efb3778f --- /dev/null +++ b/releasenotes/notes/add-description-to-role-afe7b6ff668df261.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Now user can add the description when user create's + the role using OSC ``openstack role create`` command. + User can add the description by adding + `--description ` to OSC + ``openstack role create`` command. From 01c3548dc8c35da198453af4e20331b5aa15f436 Mon Sep 17 00:00:00 2001 From: Wenran Xiao Date: Tue, 18 Sep 2018 11:32:03 +0800 Subject: [PATCH 1975/3095] Fix help message for subnetpool default-quota value Default pre-project quota is the number of IP addresses that can be allocated from the subnet pool. For example, with a quota of 128, I might get a 64 addresses subnet1, and 16 addresses subnet2, and still have room to allocate 48 more addresses in the future. Change-Id: Ia0ba827790b190647aed990e47347560fc9e9f0c --- doc/source/cli/command-objects/subnet-pool.rst | 8 ++++---- openstackclient/network/v2/subnet_pool.py | 10 ++++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/doc/source/cli/command-objects/subnet-pool.rst b/doc/source/cli/command-objects/subnet-pool.rst index 0cff4d7f56..c6fda03caa 100644 --- a/doc/source/cli/command-objects/subnet-pool.rst +++ b/doc/source/cli/command-objects/subnet-pool.rst @@ -77,8 +77,8 @@ Create subnet pool .. option:: --default-quota - Set default quota for subnet pool as the number of - IP addresses allowed in a subnet + Set default per-project quota for this subnet pool as the number of + IP addresses that can be allocated from the subnet pool .. option:: --tag @@ -250,8 +250,8 @@ Set subnet pool properties .. option:: --default-quota - Set default quota for subnet pool as the number of - IP addresses allowed in a subnet + Set default per-project quota for this subnet pool as the number of + IP addresses that can be allocated from the subnet pool .. option:: --tag diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index a583986856..81765ca17e 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -191,8 +191,9 @@ def get_parser(self, prog_name): '--default-quota', type=int, metavar='', - help=_("Set default quota for subnet pool as the number of" - "IP addresses allowed in a subnet")), + help=_("Set default per-project quota for this subnet pool " + "as the number of IP addresses that can be allocated " + "from the subnet pool")), _tag.add_tag_option_to_parser_for_create(parser, _('subnet pool')) return parser @@ -389,8 +390,9 @@ def get_parser(self, prog_name): '--default-quota', type=int, metavar='', - help=_("Set default quota for subnet pool as the number of" - "IP addresses allowed in a subnet")), + help=_("Set default per-project quota for this subnet pool " + "as the number of IP addresses that can be allocated " + "from the subnet pool")), _tag.add_tag_option_to_parser_for_set(parser, _('subnet pool')) return parser From 341333b7c3e65e3aaf9d88a9d4bd5fddbe5263a4 Mon Sep 17 00:00:00 2001 From: melissaml Date: Sun, 23 Sep 2018 23:06:16 +0800 Subject: [PATCH 1976/3095] Update the URL in doc Change-Id: I1c0c58dada83a930fafc7141d5446c754ca5c80c --- releasenotes/notes/bug-1519502-f72236598d14d350.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/releasenotes/notes/bug-1519502-f72236598d14d350.yaml b/releasenotes/notes/bug-1519502-f72236598d14d350.yaml index 1ace59bd66..b793b99b0d 100644 --- a/releasenotes/notes/bug-1519502-f72236598d14d350.yaml +++ b/releasenotes/notes/bug-1519502-f72236598d14d350.yaml @@ -9,4 +9,4 @@ features: upgrade: - Output of command ``ip floating list`` for nova network has been changed. And it is different from the output of neutron network. - [Ref ``_] + [Ref ``_] From 544f0e2b3f38aa02d56b3eb64428e29685934e36 Mon Sep 17 00:00:00 2001 From: Doug Hellmann Date: Wed, 26 Sep 2018 18:45:25 -0400 Subject: [PATCH 1977/3095] fix tox python3 overrides We want to default to running all tox environments under python 3, so set the basepython value in each environment. We do not want to specify a minor version number, because we do not want to have to update the file every time we upgrade python. We do not want to set the override once in testenv, because that breaks the more specific versions used in default environments like py35 and py36. Change-Id: I051f1c18b719a27372b626d483e47327085dd3b7 Signed-off-by: Doug Hellmann --- tox.ini | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tox.ini b/tox.ini index 4aad012cd6..0b2aa78f50 100644 --- a/tox.ini +++ b/tox.ini @@ -24,6 +24,7 @@ commands = {toxinidir}/tools/fast8.sh [testenv:pep8] +basepython = python3 commands = flake8 bandit -r openstackclient -x tests -s B105,B106,B107,B401,B404,B603,B606,B607,B110,B605,B101 @@ -87,6 +88,7 @@ commands = {toxinidir}/openstackclient/tests/functional/run_stestr.sh {posargs} [testenv:venv] +basepython = python3 deps = -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} -r{toxinidir}/requirements.txt @@ -94,6 +96,7 @@ deps = commands = {posargs} [testenv:cover] +basepython = python3 setenv = VIRTUAL_ENV={envdir} PYTHON=coverage run --source openstackclient --parallel-mode @@ -109,6 +112,7 @@ commands = oslo_debug_helper -t openstackclient/tests {posargs} [testenv:docs] +basepython = python3 deps = -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} -r{toxinidir}/requirements.txt @@ -118,6 +122,7 @@ commands = sphinx-build -a -E -W -d doc/build/doctrees -b man doc/source doc/build/man [testenv:releasenotes] +basepython = python3 deps = -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} -r{toxinidir}/doc/requirements.txt From 3c5824415f93b86d62b7d778d5bb52a23b961a40 Mon Sep 17 00:00:00 2001 From: Brian Haley Date: Mon, 17 Sep 2018 14:43:05 -0400 Subject: [PATCH 1978/3095] Fix some spaces in help messages Fix some missing and not necessary trailing spaces in the network v2 API files. Also fixed one block indent that was different from all its friends. Trivialfix Change-Id: Ic6491203c2fb9085543d69f0bb5f38e5a96039da --- openstackclient/network/v2/port.py | 16 ++++++++-------- openstackclient/network/v2/router.py | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 80fab14ffd..0d276d9da4 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -259,7 +259,7 @@ def _add_updatable_args(parser): 'normal', 'baremetal', 'virtio-forwarder'], help=_("VNIC type for this port (direct | direct-physical | " "macvtap | normal | baremetal | virtio-forwarder, " - " default: normal)") + "default: normal)") ) # NOTE(dtroyer): --host-id is deprecated in Mar 2016. Do not # remove before 3.x release or Mar 2017. @@ -669,7 +669,7 @@ def get_parser(self, prog_name): parser.add_argument( '--no-binding-profile', action='store_true', - help=_("Clear existing information of binding:profile." + help=_("Clear existing information of binding:profile. " "Specify both --binding-profile and --no-binding-profile " "to overwrite the current binding:profile information.") ) @@ -723,9 +723,9 @@ def get_parser(self, prog_name): '--no-allowed-address', dest='no_allowed_address_pair', action='store_true', - help=_("Clear existing allowed-address pairs associated" - "with this port." - "(Specify both --allowed-address and --no-allowed-address" + help=_("Clear existing allowed-address pairs associated " + "with this port. " + "(Specify both --allowed-address and --no-allowed-address " "to overwrite the current allowed-address pairs)") ) parser.add_argument( @@ -843,7 +843,7 @@ def get_parser(self, prog_name): '--binding-profile', metavar='', action='append', - help=_("Desired key which should be removed from binding:profile" + help=_("Desired key which should be removed from binding:profile " "(repeat option to unset multiple binding:profile data)")) parser.add_argument( '--security-group', @@ -867,8 +867,8 @@ def get_parser(self, prog_name): required_keys=['ip-address'], optional_keys=['mac-address'], help=_("Desired allowed-address pair which should be removed " - "from this port: ip-address= " - "[,mac-address=] (repeat option to set " + "from this port: ip-address=" + "[,mac-address=] (repeat option to unset " "multiple allowed-address pairs)") ) parser.add_argument( diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 187c241fc9..746452e40c 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -611,10 +611,10 @@ def take_action(self, parsed_args): elif parsed_args.no_route or parsed_args.clear_routes: attrs['routes'] = [] if (parsed_args.disable_snat or parsed_args.enable_snat or - parsed_args.fixed_ip) and not parsed_args.external_gateway: - msg = (_("You must specify '--external-gateway' in order" - "to update the SNAT or fixed-ip values")) - raise exceptions.CommandError(msg) + parsed_args.fixed_ip) and not parsed_args.external_gateway: + msg = (_("You must specify '--external-gateway' in order " + "to update the SNAT or fixed-ip values")) + raise exceptions.CommandError(msg) if parsed_args.external_gateway: gateway_info = {} network = client.find_network( From 424ab43a0e2b7c0dbffdbf9e410e887e1abb0295 Mon Sep 17 00:00:00 2001 From: Witold Bedyk Date: Fri, 28 Sep 2018 10:44:07 +0200 Subject: [PATCH 1979/3095] Add monascaclient to `not plugins` list Change-Id: I377a018a4e3c7b6fa44ce9323d1ee2edbd0e4728 --- doc/source/contributor/plugins.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/contributor/plugins.rst b/doc/source/contributor/plugins.rst index 51aec9aea4..6b6da4691c 100644 --- a/doc/source/contributor/plugins.rst +++ b/doc/source/contributor/plugins.rst @@ -54,6 +54,7 @@ The following is a list of projects that are not an OpenStackClient plugin. - python-magnumclient - python-ceilometerclient +- python-monascaclient - python-solumclient Implementation From a4fcae2ac2375191daea14fe524f0175d4c3300d Mon Sep 17 00:00:00 2001 From: Chen Date: Fri, 28 Sep 2018 19:20:07 +0800 Subject: [PATCH 1980/3095] trivial: remove commented-out code These comments have existed from the beginning. But they seem to be meaningless. Change-Id: Ic38272ecfb321d77219d477634e9e29b968e7f00 --- openstackclient/api/api.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openstackclient/api/api.py b/openstackclient/api/api.py index 04d88f31e9..7e2fe38f0e 100644 --- a/openstackclient/api/api.py +++ b/openstackclient/api/api.py @@ -186,7 +186,6 @@ def list( ret = self._request( 'POST', path, - # service=self.service_type, json=body, params=params, ) @@ -194,7 +193,6 @@ def list( ret = self._request( 'GET', path, - # service=self.service_type, params=params, ) try: From 01eeed28f7659a225415ae839e31e86f925d8f5a Mon Sep 17 00:00:00 2001 From: Witold Bedyk Date: Fri, 28 Sep 2018 10:46:53 +0200 Subject: [PATCH 1981/3095] Remove python-ceilometerclient Project is retired [1]. [1] https://github.com/openstack/python-ceilometerclient Change-Id: Idc3a340c99c9fd79d80c90e45fd9a1c44c205218 --- doc/source/contributor/plugins.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/source/contributor/plugins.rst b/doc/source/contributor/plugins.rst index 6b6da4691c..effcd85ac6 100644 --- a/doc/source/contributor/plugins.rst +++ b/doc/source/contributor/plugins.rst @@ -53,7 +53,6 @@ The following is a list of projects that are an OpenStackClient plugin. The following is a list of projects that are not an OpenStackClient plugin. - python-magnumclient -- python-ceilometerclient - python-monascaclient - python-solumclient From e3dc30fe8c1ae6a13926bf1eae52097e8bb37aab Mon Sep 17 00:00:00 2001 From: Ruby Loo Date: Fri, 5 Oct 2018 16:26:32 -0400 Subject: [PATCH 1982/3095] Add --property option to 'server rebuild' command Add '--property' option to the 'server rebuild' command, to provide the ability to specify properties of the rebuilt instance. This is equivalent to the '--meta' option of the compute's 'nova rebuild' command. Change-Id: I25ea6622e970416090109316e1e28fab8b0b3f07 Story: #2003979 Task: #26922 --- openstackclient/compute/v2/server.py | 13 +++++++++++- .../tests/unit/compute/v2/test_server.py | 21 +++++++++++++++++++ ...ver-rebuild-property-e8c6439b04e27c80.yaml | 6 ++++++ 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/server-rebuild-property-e8c6439b04e27c80.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index a6a5908440..67b4140a4e 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1517,6 +1517,13 @@ def get_parser(self, prog_name): metavar='', help=_("Set the password on the rebuilt instance"), ) + parser.add_argument( + '--property', + metavar='', + action=parseractions.KeyValueAction, + help=_('Set a property on the rebuilt instance ' + '(repeat option to set multiple values)'), + ) parser.add_argument( '--wait', action='store_true', @@ -1542,7 +1549,11 @@ def _show_progress(progress): 'image', {}).get('id') image = utils.find_resource(image_client.images, image_id) - server = server.rebuild(image, parsed_args.password) + kwargs = {} + if parsed_args.property: + kwargs['meta'] = parsed_args.property + + server = server.rebuild(image, parsed_args.password, **kwargs) if parsed_args.wait: if utils.wait_for_status( compute_client.servers.get, diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index b3d326819b..e938564b50 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -2496,6 +2496,27 @@ def test_rebuild_with_wait_fails(self, mock_wait_for_status): self.images_mock.get.assert_called_with(self.image.id) self.server.rebuild.assert_called_with(self.image, None) + def test_rebuild_with_property(self): + arglist = [ + self.server.id, + '--property', 'key1=value1', + '--property', 'key2=value2' + ] + expected_property = {'key1': 'value1', 'key2': 'value2'} + verifylist = [ + ('server', self.server.id), + ('property', expected_property) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Get the command object to test + self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.images_mock.get.assert_called_with(self.image.id) + self.server.rebuild.assert_called_with( + self.image, None, meta=expected_property) + class TestServerRemoveFixedIP(TestServer): diff --git a/releasenotes/notes/server-rebuild-property-e8c6439b04e27c80.yaml b/releasenotes/notes/server-rebuild-property-e8c6439b04e27c80.yaml new file mode 100644 index 0000000000..3bdf5060e9 --- /dev/null +++ b/releasenotes/notes/server-rebuild-property-e8c6439b04e27c80.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--property`` option to the ``server rebuild`` command, to provide + the ability to specify properties of the rebuilt instance. + [Story `2003979 `_] From f82c5b85ce9d6fee62550044fbabe54155e4b367 Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Tue, 19 Jun 2018 16:25:40 +0800 Subject: [PATCH 1983/3095] Add --key-name and --key-unset option for server rebuild API. Change-Id: I6d4793a8e961080ea1d6d414cef8d6bbed0c53e7 Story: 2002609 Task: 22228 Signed-off-by: Fan Zhang --- lower-constraints.txt | 2 +- openstackclient/compute/v2/server.py | 26 ++++++ .../tests/unit/compute/v2/test_server.py | 90 +++++++++++++++++++ ...ld-with-keypair-name-83c1aa20db136d91.yaml | 6 ++ requirements.txt | 2 +- 5 files changed, 124 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/server-rebuild-with-keypair-name-83c1aa20db136d91.yaml diff --git a/lower-constraints.txt b/lower-constraints.txt index db92fef36b..64911cf947 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -100,7 +100,7 @@ python-mimeparse==1.6.0 python-mistralclient==3.1.0 python-muranoclient==0.8.2 python-neutronclient==6.7.0 -python-novaclient==9.1.0 +python-novaclient==10.0.0 python-octaviaclient==1.3.0 python-rsdclient==0.1.0 python-saharaclient==1.4.0 diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 67b4140a4e..5723dae306 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1529,6 +1529,22 @@ def get_parser(self, prog_name): action='store_true', help=_('Wait for rebuild to complete'), ) + key_group = parser.add_mutually_exclusive_group() + key_group.add_argument( + '--key-name', + metavar='', + help=_("Set the key name of key pair on the rebuilt instance." + " Cannot be specified with the '--key-unset' option." + " (Supported by API versions '2.54' - '2.latest')"), + ) + key_group.add_argument( + '--key-unset', + action='store_true', + default=False, + help=_("Unset the key name of key pair on the rebuilt instance." + " Cannot be specified with the '--key-name' option." + " (Supported by API versions '2.54' - '2.latest')"), + ) return parser def take_action(self, parsed_args): @@ -1553,6 +1569,16 @@ def _show_progress(progress): if parsed_args.property: kwargs['meta'] = parsed_args.property + if parsed_args.key_name or parsed_args.key_unset: + if compute_client.api_version < api_versions.APIVersion('2.54'): + msg = _('--os-compute-api-version 2.54 or later is required') + raise exceptions.CommandError(msg) + + if parsed_args.key_unset: + kwargs['key_name'] = None + if parsed_args.key_name: + kwargs['key_name'] = parsed_args.key_name + server = server.rebuild(image, parsed_args.password, **kwargs) if parsed_args.wait: if utils.wait_for_status( diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index e938564b50..9adc3cc6df 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -2517,6 +2517,96 @@ def test_rebuild_with_property(self): self.server.rebuild.assert_called_with( self.image, None, meta=expected_property) + def test_rebuild_with_keypair_name(self): + self.server.key_name = 'mykey' + arglist = [ + self.server.id, + '--key-name', self.server.key_name, + ] + verifylist = [ + ('server', self.server.id), + ('key_name', self.server.key_name) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.app.client_manager.compute.api_version = 2.54 + with mock.patch.object(api_versions, + 'APIVersion', + return_value=2.54): + self.cmd.take_action(parsed_args) + args = ( + self.image, + None, + ) + kwargs = dict( + key_name=self.server.key_name, + ) + self.servers_mock.get.assert_called_with(self.server.id) + self.images_mock.get.assert_called_with(self.image.id) + self.server.rebuild.assert_called_with(*args, **kwargs) + + def test_rebuild_with_keypair_name_older_version(self): + self.server.key_name = 'mykey' + arglist = [ + self.server.id, + '--key-name', self.server.key_name, + ] + verifylist = [ + ('server', self.server.id), + ('key_name', self.server.key_name) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.app.client_manager.compute.api_version = 2.53 + with mock.patch.object(api_versions, + 'APIVersion', + return_value=2.54): + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + def test_rebuild_with_keypair_unset(self): + self.server.key_name = 'mykey' + arglist = [ + self.server.id, + '--key-unset', + ] + verifylist = [ + ('server', self.server.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.app.client_manager.compute.api_version = 2.54 + with mock.patch.object(api_versions, + 'APIVersion', + return_value=2.54): + self.cmd.take_action(parsed_args) + args = ( + self.image, + None, + ) + kwargs = dict( + key_name=None, + ) + self.servers_mock.get.assert_called_with(self.server.id) + self.images_mock.get.assert_called_with(self.image.id) + self.server.rebuild.assert_called_with(*args, **kwargs) + + def test_rebuild_with_key_name_and_unset(self): + self.server.key_name = 'mykey' + arglist = [ + self.server.id, + '--key-name', self.server.key_name, + '--key-unset', + ] + verifylist = [ + ('server', self.server.id), + ('key_name', self.server.key_name) + ] + self.assertRaises(utils.ParserException, + self.check_parser, + self.cmd, arglist, verifylist) + class TestServerRemoveFixedIP(TestServer): diff --git a/releasenotes/notes/server-rebuild-with-keypair-name-83c1aa20db136d91.yaml b/releasenotes/notes/server-rebuild-with-keypair-name-83c1aa20db136d91.yaml new file mode 100644 index 0000000000..c670740828 --- /dev/null +++ b/releasenotes/notes/server-rebuild-with-keypair-name-83c1aa20db136d91.yaml @@ -0,0 +1,6 @@ +--- +features: + - Add ``--key-name`` option to ``server rebuild`` command to set keypair of + the server. Note that it requires --os-compute-api-version 2.54 or later. + - Add ``--key-unset`` option to ``server rebuild`` command to unset + keypair. Note that it requires --os-compute-api-version 2.54 or later. diff --git a/requirements.txt b/requirements.txt index 205d4e64ec..c986667206 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,5 +13,5 @@ oslo.i18n>=3.15.3 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 python-glanceclient>=2.8.0 # Apache-2.0 python-keystoneclient>=3.17.0 # Apache-2.0 -python-novaclient>=9.1.0 # Apache-2.0 +python-novaclient>=10.0.0 # Apache-2.0 python-cinderclient>=3.3.0 # Apache-2.0 From 91a2d888625488da3f65ad372b4248e9747b9a3e Mon Sep 17 00:00:00 2001 From: Julie Pichon Date: Tue, 9 Oct 2018 11:37:22 +0100 Subject: [PATCH 1984/3095] Allow endpoint filtering on both project and project-domain The --project and --project-domain flags are currently mutually exclusive for listing endpoints, however the --project-domain argument is supposed to help with filtering projects with colliding names. They should be allowed together. Story: 2004018 Task: 27007 Change-Id: I7340e01f509e3515f07cb46f175fb603f1ce8b67 --- openstackclient/identity/v3/endpoint.py | 2 +- .../tests/unit/identity/v3/test_endpoint.py | 41 +++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/openstackclient/identity/v3/endpoint.py b/openstackclient/identity/v3/endpoint.py index 3229240e4a..858b50363b 100644 --- a/openstackclient/identity/v3/endpoint.py +++ b/openstackclient/identity/v3/endpoint.py @@ -199,7 +199,7 @@ def get_parser(self, prog_name): metavar='', help=_('Project to list filters (name or ID)'), ) - common.add_project_domain_option_to_parser(list_group) + common.add_project_domain_option_to_parser(parser) return parser def take_action(self, parsed_args): diff --git a/openstackclient/tests/unit/identity/v3/test_endpoint.py b/openstackclient/tests/unit/identity/v3/test_endpoint.py index bfe930d6c7..62dcf58d5e 100644 --- a/openstackclient/tests/unit/identity/v3/test_endpoint.py +++ b/openstackclient/tests/unit/identity/v3/test_endpoint.py @@ -439,6 +439,47 @@ def test_endpoint_list_region(self): ) self.assertEqual(datalist, tuple(data)) + def test_endpoint_list_project_with_project_domain(self): + project = identity_fakes.FakeProject.create_one_project() + domain = identity_fakes.FakeDomain.create_one_domain() + + self.ep_filter_mock.list_endpoints_for_project.return_value = [ + self.endpoint + ] + self.projects_mock.get.return_value = project + + arglist = [ + '--project', project.name, + '--project-domain', domain.name + ] + verifylist = [ + ('project', project.name), + ('project_domain', domain.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + self.ep_filter_mock.list_endpoints_for_project.assert_called_with( + project=project.id + ) + + self.assertEqual(self.columns, columns) + datalist = ( + ( + self.endpoint.id, + self.endpoint.region, + self.service.name, + self.service.type, + True, + self.endpoint.interface, + self.endpoint.url, + ), + ) + self.assertEqual(datalist, tuple(data)) + class TestEndpointSet(TestEndpoint): From 4039d0d94f4eaf277f2b42c33ef873555f84f159 Mon Sep 17 00:00:00 2001 From: Sean McGinnis Date: Tue, 9 Oct 2018 15:24:51 -0500 Subject: [PATCH 1985/3095] Add volume backend capability show command Adds and equivalend for "cinder get-capabilities" command to show the capabilities supported by a Cinder backend. Story: 1655624 Task: 26947 Change-Id: I38686a26cd503e45ce0102705a6632994ef10274 Signed-off-by: Sean McGinnis --- .../cli/command-objects/volume-backend.rst | 8 ++ doc/source/cli/commands.rst | 1 + doc/source/cli/data/cinder.csv | 2 +- openstackclient/tests/unit/volume/v2/fakes.py | 61 ++++++++++++++++ .../unit/volume/v2/test_volume_backend.py | 73 +++++++++++++++++++ openstackclient/volume/v2/volume_backend.py | 61 ++++++++++++++++ .../volume-backend-c5faae0b31556a24.yaml | 7 ++ setup.cfg | 2 + 8 files changed, 214 insertions(+), 1 deletion(-) create mode 100644 doc/source/cli/command-objects/volume-backend.rst create mode 100644 openstackclient/tests/unit/volume/v2/test_volume_backend.py create mode 100644 openstackclient/volume/v2/volume_backend.py create mode 100644 releasenotes/notes/volume-backend-c5faae0b31556a24.yaml diff --git a/doc/source/cli/command-objects/volume-backend.rst b/doc/source/cli/command-objects/volume-backend.rst new file mode 100644 index 0000000000..0285d61b84 --- /dev/null +++ b/doc/source/cli/command-objects/volume-backend.rst @@ -0,0 +1,8 @@ +============== +volume backend +============== + +Volume v2 + +.. autoprogram-cliff:: openstack.volume.v2 + :command: volume backend * diff --git a/doc/source/cli/commands.rst b/doc/source/cli/commands.rst index 76126a7475..1ca674d543 100644 --- a/doc/source/cli/commands.rst +++ b/doc/source/cli/commands.rst @@ -156,6 +156,7 @@ referring to both Compute and Volume quotas. * ``user role``: (**Identity**) roles assigned to a user * ``volume``: (**Volume**) block volumes * ``volume backup``: (**Volume**) backup for volumes +* ``volume backend``: (**volume**) volume backend storage * ``volume host``: (**Volume**) the physical computer for volumes * ``volume qos``: (**Volume**) quality-of-service (QoS) specification for volumes * ``volume snapshot``: (**Volume**) a point-in-time copy of a volume diff --git a/doc/source/cli/data/cinder.csv b/doc/source/cli/data/cinder.csv index 5c89e0864b..068ffb7fac 100644 --- a/doc/source/cli/data/cinder.csv +++ b/doc/source/cli/data/cinder.csv @@ -32,7 +32,7 @@ extra-specs-list,volume type list --long,Lists current volume types and extra sp failover-host,volume host failover,Failover a replicating cinder-volume host. force-delete,volume delete --force,"Attempts force-delete of volume, regardless of state." freeze-host,volume host set --disable,Freeze and disable the specified cinder-volume host. -get-capabilities,,Show backend volume stats and properties. Admin only. +get-capabilities,volume backend capability show,Show capabilities of a volume backend. Admin only. get-pools,,Show pool information for backends. Admin only. image-metadata,volume set --image-property,Sets or deletes volume image metadata. image-metadata-show,volume show,Shows volume image metadata. diff --git a/openstackclient/tests/unit/volume/v2/fakes.py b/openstackclient/tests/unit/volume/v2/fakes.py index 481509f3b8..ad13f1b9cf 100644 --- a/openstackclient/tests/unit/volume/v2/fakes.py +++ b/openstackclient/tests/unit/volume/v2/fakes.py @@ -193,6 +193,65 @@ def create_services(attrs=None, count=2): return services +class FakeCapability(object): + """Fake capability.""" + + @staticmethod + def create_one_capability(attrs=None): + """Create a fake volume backend capability. + + :param Dictionary attrs: + A dictionary with all attributes of the Capabilities. + :return: + A FakeResource object with capability name and attrs. + """ + # Set default attribute + capability_info = { + "namespace": "OS::Storage::Capabilities::fake", + "vendor_name": "OpenStack", + "volume_backend_name": "lvmdriver-1", + "pool_name": "pool", + "driver_version": "2.0.0", + "storage_protocol": "iSCSI", + "display_name": "Capabilities of Cinder LVM driver", + "description": "Blah, blah.", + "visibility": "public", + "replication_targets": [], + "properties": { + "compression": { + "title": "Compression", + "description": "Enables compression.", + "type": "boolean" + }, + "qos": { + "title": "QoS", + "description": "Enables QoS.", + "type": "boolean" + }, + "replication": { + "title": "Replication", + "description": "Enables replication.", + "type": "boolean" + }, + "thin_provisioning": { + "title": "Thin Provisioning", + "description": "Sets thin provisioning.", + "type": "boolean" + } + } + } + + # Overwrite default attributes if there are some attributes set + capability_info.update(attrs or {}) + + capability = fakes.FakeResource( + None, + capability_info, + loaded=True) + + return capability + + class FakeVolumeClient(object): def __init__(self, **kwargs): @@ -233,6 +292,8 @@ def __init__(self, **kwargs): self.cgsnapshots.resource_class = fakes.FakeResource(None, {}) self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] + self.capabilities = mock.Mock() + self.capabilities.resource_class = fakes.FakeResource(None, {}) class TestVolume(utils.TestCommand): diff --git a/openstackclient/tests/unit/volume/v2/test_volume_backend.py b/openstackclient/tests/unit/volume/v2/test_volume_backend.py new file mode 100644 index 0000000000..73df6032de --- /dev/null +++ b/openstackclient/tests/unit/volume/v2/test_volume_backend.py @@ -0,0 +1,73 @@ +# +# 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.unit.volume.v2 import fakes as volume_fakes +from openstackclient.volume.v2 import volume_backend + + +class TestShowVolumeCapability(volume_fakes.TestVolume): + """Test backend capability functionality.""" + + # The capability to be listed + capability = volume_fakes.FakeCapability.create_one_capability() + + def setUp(self): + super(TestShowVolumeCapability, self).setUp() + + # Get a shortcut to the capability Mock + self.capability_mock = self.app.client_manager.volume.capabilities + self.capability_mock.get.return_value = self.capability + + # Get the command object to test + self.cmd = volume_backend.ShowCapability(self.app, None) + + def test_capability_show(self): + arglist = [ + 'fake', + ] + verifylist = [ + ('host', 'fake'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + expected_columns = [ + 'Title', + 'Key', + 'Type', + 'Description', + ] + + # confirming if all expected columns are present in the result. + self.assertEqual(expected_columns, columns) + + capabilities = [ + 'Compression', + 'Replication', + 'QoS', + 'Thin Provisioning', + ] + + # confirming if all expected values are present in the result. + for cap in data: + self.assertTrue(cap[0] in capabilities) + + # checking if proper call was made to get capabilities + self.capability_mock.get.assert_called_with( + 'fake', + ) diff --git a/openstackclient/volume/v2/volume_backend.py b/openstackclient/volume/v2/volume_backend.py new file mode 100644 index 0000000000..0dff18c9ad --- /dev/null +++ b/openstackclient/volume/v2/volume_backend.py @@ -0,0 +1,61 @@ +# +# 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. +# + +"""Capability action implementations""" + +from osc_lib.command import command +from osc_lib import utils + +from openstackclient.i18n import _ + + +class ShowCapability(command.Lister): + _description = _("Show capability command") + + def get_parser(self, prog_name): + parser = super(ShowCapability, self).get_parser(prog_name) + parser.add_argument( + "host", + metavar="", + help=_("List capabilities of specified host (host@backend-name)") + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + columns = [ + 'Title', + 'Key', + 'Type', + 'Description', + ] + + data = volume_client.capabilities.get(parsed_args.host) + + # The get capabilities API is... interesting. We only want the names of + # the capabilities that can set for a backend through extra specs, so + # we need to extract out that part of the mess that is returned. + print_data = [] + keys = data.properties + for key in keys: + # Stuff the key into the details to make it easier to output + capability_data = data.properties[key] + capability_data['key'] = key + print_data.append(capability_data) + + return (columns, + (utils.get_dict_properties( + s, columns, + ) for s in print_data)) diff --git a/releasenotes/notes/volume-backend-c5faae0b31556a24.yaml b/releasenotes/notes/volume-backend-c5faae0b31556a24.yaml new file mode 100644 index 0000000000..851ab2decd --- /dev/null +++ b/releasenotes/notes/volume-backend-c5faae0b31556a24.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + A new command, ``openstack volume backend capability show `` was + added which will provide a list of all capabilities that can be configured + for the requested backend. The required `` parameter takes the form + `host@backend-name`. diff --git a/setup.cfg b/setup.cfg index d9ca47ca56..888e259b22 100644 --- a/setup.cfg +++ b/setup.cfg @@ -629,6 +629,8 @@ openstack.volume.v2 = volume_backup_set = openstackclient.volume.v2.backup:SetVolumeBackup volume_backup_show = openstackclient.volume.v2.backup:ShowVolumeBackup + volume_backend_capability_show = openstackclient.volume.v2.volume_backend:ShowCapability + volume_host_failover = openstackclient.volume.v2.volume_host:FailoverVolumeHost volume_host_set = openstackclient.volume.v2.volume_host:SetVolumeHost From 9647d43bd5e77c815ac2f08b7de2226a657a66bc Mon Sep 17 00:00:00 2001 From: Sean McGinnis Date: Mon, 8 Oct 2018 13:17:54 -0500 Subject: [PATCH 1986/3095] Add volume backend pool list command Adds an equivalent for "cinder get-pools" with "volume backend pool list" and "cinder get-pools --detail" with "volume backend pool list --long". Story: 1655624 Task: 136949 Signed-off-by: Sean McGinnis Change-Id: I826c9946ffe11340d44ad57914f72fc2a72b6938 --- doc/source/cli/data/cinder.csv | 2 +- openstackclient/tests/unit/volume/v2/fakes.py | 37 ++++++++ .../unit/volume/v2/test_volume_backend.py | 95 +++++++++++++++++++ openstackclient/volume/v2/volume_backend.py | 54 ++++++++++- .../volume-backend-c5faae0b31556a24.yaml | 4 + setup.cfg | 1 + 6 files changed, 191 insertions(+), 2 deletions(-) diff --git a/doc/source/cli/data/cinder.csv b/doc/source/cli/data/cinder.csv index 068ffb7fac..cc8ef2d91e 100644 --- a/doc/source/cli/data/cinder.csv +++ b/doc/source/cli/data/cinder.csv @@ -33,7 +33,7 @@ failover-host,volume host failover,Failover a replicating cinder-volume host. force-delete,volume delete --force,"Attempts force-delete of volume, regardless of state." freeze-host,volume host set --disable,Freeze and disable the specified cinder-volume host. get-capabilities,volume backend capability show,Show capabilities of a volume backend. Admin only. -get-pools,,Show pool information for backends. Admin only. +get-pools,volume backend pool list,Show pool information for backends. Admin only. image-metadata,volume set --image-property,Sets or deletes volume image metadata. image-metadata-show,volume show,Shows volume image metadata. list,volume list,Lists all volumes. diff --git a/openstackclient/tests/unit/volume/v2/fakes.py b/openstackclient/tests/unit/volume/v2/fakes.py index ad13f1b9cf..8d1db63e9b 100644 --- a/openstackclient/tests/unit/volume/v2/fakes.py +++ b/openstackclient/tests/unit/volume/v2/fakes.py @@ -252,6 +252,41 @@ def create_one_capability(attrs=None): return capability +class FakePool(object): + """Fake Pools.""" + + @staticmethod + def create_one_pool(attrs=None): + """Create a fake pool. + + :param Dictionary attrs: + A dictionary with all attributes of the pool + :return: + A FakeResource object with pool name and attrs. + """ + # Set default attribute + pool_info = { + 'name': 'host@lvmdriver-1#lvmdriver-1', + 'storage_protocol': 'iSCSI', + 'thick_provisioning_support': False, + 'thin_provisioning_support': True, + 'total_volumes': 99, + 'total_capacity_gb': 1000.00, + 'allocated_capacity_gb': 100, + 'max_over_subscription_ratio': 200.0, + } + + # Overwrite default attributes if there are some attributes set + pool_info.update(attrs or {}) + + pool = fakes.FakeResource( + None, + pool_info, + loaded=True) + + return pool + + class FakeVolumeClient(object): def __init__(self, **kwargs): @@ -294,6 +329,8 @@ def __init__(self, **kwargs): self.management_url = kwargs['endpoint'] self.capabilities = mock.Mock() self.capabilities.resource_class = fakes.FakeResource(None, {}) + self.pools = mock.Mock() + self.pools.resource_class = fakes.FakeResource(None, {}) class TestVolume(utils.TestCommand): diff --git a/openstackclient/tests/unit/volume/v2/test_volume_backend.py b/openstackclient/tests/unit/volume/v2/test_volume_backend.py index 73df6032de..db18866087 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume_backend.py +++ b/openstackclient/tests/unit/volume/v2/test_volume_backend.py @@ -71,3 +71,98 @@ def test_capability_show(self): self.capability_mock.get.assert_called_with( 'fake', ) + + +class TestListVolumePool(volume_fakes.TestVolume): + """Tests for volume backend pool listing.""" + + # The pool to be listed + pools = volume_fakes.FakePool.create_one_pool() + + def setUp(self): + super(TestListVolumePool, self).setUp() + + self.pool_mock = self.app.client_manager.volume.pools + self.pool_mock.list.return_value = [self.pools] + + # Get the command object to test + self.cmd = volume_backend.ListPool(self.app, None) + + def test_pool_list(self): + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + expected_columns = [ + 'Name', + ] + + # confirming if all expected columns are present in the result. + self.assertEqual(expected_columns, columns) + + datalist = (( + self.pools.name, + ), ) + + # confirming if all expected values are present in the result. + self.assertEqual(datalist, tuple(data)) + + # checking if proper call was made to list pools + self.pool_mock.list.assert_called_with( + detailed=False, + ) + + # checking if long columns are present in output + self.assertNotIn("total_volumes", columns) + self.assertNotIn("storage_protocol", columns) + + def test_service_list_with_long_option(self): + arglist = [ + '--long' + ] + verifylist = [ + ('long', True) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + expected_columns = [ + 'Name', + 'Protocol', + 'Thick', + 'Thin', + 'Volumes', + 'Capacity', + 'Allocated', + 'Max Over Ratio', + ] + + # confirming if all expected columns are present in the result. + self.assertEqual(expected_columns, columns) + + datalist = (( + self.pools.name, + self.pools.storage_protocol, + self.pools.thick_provisioning_support, + self.pools.thin_provisioning_support, + self.pools.total_volumes, + self.pools.total_capacity_gb, + self.pools.allocated_capacity_gb, + self.pools.max_over_subscription_ratio, + ), ) + + # confirming if all expected values are present in the result. + self.assertEqual(datalist, tuple(data)) + + self.pool_mock.list.assert_called_with( + detailed=True, + ) diff --git a/openstackclient/volume/v2/volume_backend.py b/openstackclient/volume/v2/volume_backend.py index 0dff18c9ad..c5194d3509 100644 --- a/openstackclient/volume/v2/volume_backend.py +++ b/openstackclient/volume/v2/volume_backend.py @@ -12,7 +12,7 @@ # under the License. # -"""Capability action implementations""" +"""Storage backend action implementations""" from osc_lib.command import command from osc_lib import utils @@ -59,3 +59,55 @@ def take_action(self, parsed_args): (utils.get_dict_properties( s, columns, ) for s in print_data)) + + +class ListPool(command.Lister): + _description = _("List pool command") + + def get_parser(self, prog_name): + parser = super(ListPool, self).get_parser(prog_name) + parser.add_argument( + "--long", + action="store_true", + default=False, + help=_("Show detailed information about pools.") + ) + # TODO(smcginnis): Starting with Cinder microversion 3.33, user is also + # able to pass in --filters with a = pair to filter on. + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if parsed_args.long: + columns = [ + 'name', + 'storage_protocol', + 'thick_provisioning_support', + 'thin_provisioning_support', + 'total_volumes', + 'total_capacity_gb', + 'allocated_capacity_gb', + 'max_over_subscription_ratio', + ] + headers = [ + 'Name', + 'Protocol', + 'Thick', + 'Thin', + 'Volumes', + 'Capacity', + 'Allocated', + 'Max Over Ratio' + ] + else: + columns = [ + 'Name', + ] + headers = columns + + data = volume_client.pools.list(detailed=parsed_args.long) + return (headers, + (utils.get_item_properties( + s, columns, + ) for s in data)) diff --git a/releasenotes/notes/volume-backend-c5faae0b31556a24.yaml b/releasenotes/notes/volume-backend-c5faae0b31556a24.yaml index 851ab2decd..6698309860 100644 --- a/releasenotes/notes/volume-backend-c5faae0b31556a24.yaml +++ b/releasenotes/notes/volume-backend-c5faae0b31556a24.yaml @@ -5,3 +5,7 @@ features: added which will provide a list of all capabilities that can be configured for the requested backend. The required `` parameter takes the form `host@backend-name`. + - | + A new command, ``openstack volume backend pool list`` was added which will + provide a list of all backend storage pools. The optional ``-long`` + parameter includes some basic configuration and stats for each pool. diff --git a/setup.cfg b/setup.cfg index 888e259b22..73c5fde933 100644 --- a/setup.cfg +++ b/setup.cfg @@ -630,6 +630,7 @@ openstack.volume.v2 = volume_backup_show = openstackclient.volume.v2.backup:ShowVolumeBackup volume_backend_capability_show = openstackclient.volume.v2.volume_backend:ShowCapability + volume_backend_pool_list = openstackclient.volume.v2.volume_backend:ListPool volume_host_failover = openstackclient.volume.v2.volume_host:FailoverVolumeHost volume_host_set = openstackclient.volume.v2.volume_host:SetVolumeHost From 651f0c38a1f364953dc3cb3f7458385410395fd8 Mon Sep 17 00:00:00 2001 From: Sean McGinnis Date: Wed, 10 Oct 2018 11:12:40 -0500 Subject: [PATCH 1987/3095] Handle not having cinderclient.v1 available The Cinder v1 API was deprecated several years ago and may be removed from python-cinderclient in the near future. To handle the case where v1 is no longer present, this updates cinderclient initialization to work without it and give an appropriate error if v1 is requested with a version where it is no longer available. Change-Id: I277d7b48b8ad4cce383ec3722f8117938378f615 Signed-off-by: Sean McGinnis --- .../tests/unit/volume/test_find_resource.py | 4 ++-- .../tests/unit/volume/v2/test_volume.py | 11 ---------- openstackclient/volume/client.py | 21 ++++++++++++------- openstackclient/volume/v2/volume.py | 4 +++- 4 files changed, 19 insertions(+), 21 deletions(-) diff --git a/openstackclient/tests/unit/volume/test_find_resource.py b/openstackclient/tests/unit/volume/test_find_resource.py index dbf9592f33..60591eff7f 100644 --- a/openstackclient/tests/unit/volume/test_find_resource.py +++ b/openstackclient/tests/unit/volume/test_find_resource.py @@ -15,8 +15,8 @@ import mock -from cinderclient.v1 import volume_snapshots -from cinderclient.v1 import volumes +from cinderclient.v3 import volume_snapshots +from cinderclient.v3 import volumes from osc_lib import exceptions from osc_lib import utils diff --git a/openstackclient/tests/unit/volume/v2/test_volume.py b/openstackclient/tests/unit/volume/v2/test_volume.py index bb6263bbe2..183fb2280f 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume.py +++ b/openstackclient/tests/unit/volume/v2/test_volume.py @@ -131,7 +131,6 @@ def test_volume_create_min_options(self): imageRef=None, source_volid=None, consistencygroup_id=None, - multiattach=False, scheduler_hints=None, ) @@ -149,7 +148,6 @@ def test_volume_create_options(self): '--availability-zone', self.new_volume.availability_zone, '--consistency-group', consistency_group.id, '--hint', 'k=v', - '--multi-attach', self.new_volume.name, ] verifylist = [ @@ -159,7 +157,6 @@ def test_volume_create_options(self): ('availability_zone', self.new_volume.availability_zone), ('consistency_group', consistency_group.id), ('hint', {'k': 'v'}), - ('multi_attach', True), ('name', self.new_volume.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -180,7 +177,6 @@ def test_volume_create_options(self): imageRef=None, source_volid=None, consistencygroup_id=consistency_group.id, - multiattach=True, scheduler_hints={'k': 'v'}, ) @@ -251,7 +247,6 @@ def test_volume_create_properties(self): imageRef=None, source_volid=None, consistencygroup_id=None, - multiattach=False, scheduler_hints=None, ) @@ -290,7 +285,6 @@ def test_volume_create_image_id(self): imageRef=image.id, source_volid=None, consistencygroup_id=None, - multiattach=False, scheduler_hints=None, ) @@ -329,7 +323,6 @@ def test_volume_create_image_name(self): imageRef=image.id, source_volid=None, consistencygroup_id=None, - multiattach=False, scheduler_hints=None, ) @@ -367,7 +360,6 @@ def test_volume_create_with_snapshot(self): imageRef=None, source_volid=None, consistencygroup_id=None, - multiattach=False, scheduler_hints=None, ) @@ -406,7 +398,6 @@ def test_volume_create_with_bootable_and_readonly(self): imageRef=None, source_volid=None, consistencygroup_id=None, - multiattach=False, scheduler_hints=None, ) @@ -449,7 +440,6 @@ def test_volume_create_with_nonbootable_and_readwrite(self): imageRef=None, source_volid=None, consistencygroup_id=None, - multiattach=False, scheduler_hints=None, ) @@ -501,7 +491,6 @@ def test_volume_create_with_bootable_and_readonly_fail( imageRef=None, source_volid=None, consistencygroup_id=None, - multiattach=False, scheduler_hints=None, ) diff --git a/openstackclient/volume/client.py b/openstackclient/volume/client.py index c4b0dfcabd..e0e670a9ce 100644 --- a/openstackclient/volume/client.py +++ b/openstackclient/volume/client.py @@ -37,13 +37,20 @@ def make_client(instance): # Defer client imports until we actually need them from cinderclient import extension - from cinderclient.v1.contrib import list_extensions - from cinderclient.v1 import volume_snapshots - from cinderclient.v1 import volumes - - # Monkey patch for v1 cinderclient - volumes.Volume.NAME_ATTR = 'display_name' - volume_snapshots.Snapshot.NAME_ATTR = 'display_name' + from cinderclient.v3.contrib import list_extensions + from cinderclient.v3 import volume_snapshots + from cinderclient.v3 import volumes + + # Try a small import to check if cinderclient v1 is supported + try: + from cinderclient.v1 import services # noqa + except Exception: + del API_VERSIONS['1'] + + if instance._api_version[API_NAME] == '1': + # Monkey patch for v1 cinderclient + volumes.Volume.NAME_ATTR = 'display_name' + volume_snapshots.Snapshot.NAME_ATTR = 'display_name' volume_client = utils.get_client_class( API_NAME, diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index 8ab61d2aa1..7a5c207af9 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -212,6 +212,9 @@ def take_action(self, parsed_args): raise exceptions.CommandError( _("ERROR: --user is deprecated, please use" " --os-username instead.")) + if parsed_args.multi_attach: + LOG.warning(_("'--multi-attach' option is no longer supported by " + "the block storage service.")) volume = volume_client.volumes.create( size=size, @@ -224,7 +227,6 @@ def take_action(self, parsed_args): imageRef=image, source_volid=source_volume, consistencygroup_id=consistency_group, - multiattach=parsed_args.multi_attach, scheduler_hints=parsed_args.hint, ) From e0615e8d69152ce0417f873b30050802a2da0da8 Mon Sep 17 00:00:00 2001 From: Sean McGinnis Date: Fri, 12 Oct 2018 15:54:02 -0500 Subject: [PATCH 1988/3095] Address issues from volume backend commands This fixes some minor issues in release notes and the command list for the new volume backend commands. Also sorts the fakes used for volume unit tests to allow for multiple command update patches to hopefully reduce the odds of merge conflicts. Change-Id: Ic6e40f4c639368338cf085c68c17038f81da5361 Signed-off-by: Sean McGinnis --- doc/source/cli/commands.rst | 3 +- openstackclient/tests/unit/volume/v2/fakes.py | 66 +++++++++---------- .../volume-backend-c5faae0b31556a24.yaml | 8 +-- 3 files changed, 39 insertions(+), 38 deletions(-) diff --git a/doc/source/cli/commands.rst b/doc/source/cli/commands.rst index 1ca674d543..d7c9124042 100644 --- a/doc/source/cli/commands.rst +++ b/doc/source/cli/commands.rst @@ -156,7 +156,8 @@ referring to both Compute and Volume quotas. * ``user role``: (**Identity**) roles assigned to a user * ``volume``: (**Volume**) block volumes * ``volume backup``: (**Volume**) backup for volumes -* ``volume backend``: (**volume**) volume backend storage +* ``volume backend capability``: (**volume**) volume backend storage capabilities +* ``volume backend pool``: (**volume**) volume backend storage pools * ``volume host``: (**Volume**) the physical computer for volumes * ``volume qos``: (**Volume**) quality-of-service (QoS) specification for volumes * ``volume snapshot``: (**Volume**) a point-in-time copy of a volume diff --git a/openstackclient/tests/unit/volume/v2/fakes.py b/openstackclient/tests/unit/volume/v2/fakes.py index 8d1db63e9b..59b08d0b90 100644 --- a/openstackclient/tests/unit/volume/v2/fakes.py +++ b/openstackclient/tests/unit/volume/v2/fakes.py @@ -290,47 +290,47 @@ def create_one_pool(attrs=None): class FakeVolumeClient(object): def __init__(self, **kwargs): - self.volumes = mock.Mock() - self.volumes.resource_class = fakes.FakeResource(None, {}) + self.auth_token = kwargs['token'] + self.management_url = kwargs['endpoint'] + self.availability_zones = mock.Mock() + self.availability_zones.resource_class = fakes.FakeResource(None, {}) + self.backups = mock.Mock() + self.backups.resource_class = fakes.FakeResource(None, {}) + self.capabilities = mock.Mock() + self.capabilities.resource_class = fakes.FakeResource(None, {}) + self.cgsnapshots = mock.Mock() + self.cgsnapshots.resource_class = fakes.FakeResource(None, {}) + self.consistencygroups = mock.Mock() + self.consistencygroups.resource_class = fakes.FakeResource(None, {}) self.extensions = mock.Mock() self.extensions.resource_class = fakes.FakeResource(None, {}) self.limits = mock.Mock() self.limits.resource_class = fakes.FakeResource(None, {}) - self.volume_snapshots = mock.Mock() - self.volume_snapshots.resource_class = fakes.FakeResource(None, {}) - self.backups = mock.Mock() - self.backups.resource_class = fakes.FakeResource(None, {}) - self.volume_types = mock.Mock() - self.volume_types.resource_class = fakes.FakeResource(None, {}) - self.volume_type_access = mock.Mock() - self.volume_type_access.resource_class = fakes.FakeResource(None, {}) - self.volume_encryption_types = mock.Mock() - self.volume_encryption_types.resource_class = ( - fakes.FakeResource(None, {})) - self.restores = mock.Mock() - self.restores.resource_class = fakes.FakeResource(None, {}) + self.pools = mock.Mock() + self.pools.resource_class = fakes.FakeResource(None, {}) self.qos_specs = mock.Mock() self.qos_specs.resource_class = fakes.FakeResource(None, {}) - self.availability_zones = mock.Mock() - self.availability_zones.resource_class = fakes.FakeResource(None, {}) - self.transfers = mock.Mock() - self.transfers.resource_class = fakes.FakeResource(None, {}) - self.services = mock.Mock() - self.services.resource_class = fakes.FakeResource(None, {}) - self.quotas = mock.Mock() - self.quotas.resource_class = fakes.FakeResource(None, {}) self.quota_classes = mock.Mock() 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'] - self.capabilities = mock.Mock() - self.capabilities.resource_class = fakes.FakeResource(None, {}) - self.pools = mock.Mock() - self.pools.resource_class = fakes.FakeResource(None, {}) + self.quotas = mock.Mock() + self.quotas.resource_class = fakes.FakeResource(None, {}) + self.restores = mock.Mock() + self.restores.resource_class = fakes.FakeResource(None, {}) + self.services = mock.Mock() + self.services.resource_class = fakes.FakeResource(None, {}) + self.transfers = mock.Mock() + self.transfers.resource_class = fakes.FakeResource(None, {}) + self.volume_encryption_types = mock.Mock() + self.volume_encryption_types.resource_class = ( + fakes.FakeResource(None, {})) + self.volume_snapshots = mock.Mock() + self.volume_snapshots.resource_class = fakes.FakeResource(None, {}) + self.volume_type_access = mock.Mock() + self.volume_type_access.resource_class = fakes.FakeResource(None, {}) + self.volume_types = mock.Mock() + self.volume_types.resource_class = fakes.FakeResource(None, {}) + self.volumes = mock.Mock() + self.volumes.resource_class = fakes.FakeResource(None, {}) class TestVolume(utils.TestCommand): diff --git a/releasenotes/notes/volume-backend-c5faae0b31556a24.yaml b/releasenotes/notes/volume-backend-c5faae0b31556a24.yaml index 6698309860..f4f131e648 100644 --- a/releasenotes/notes/volume-backend-c5faae0b31556a24.yaml +++ b/releasenotes/notes/volume-backend-c5faae0b31556a24.yaml @@ -1,11 +1,11 @@ --- features: - | - A new command, ``openstack volume backend capability show `` was - added which will provide a list of all capabilities that can be configured + Add ``openstack volume backend capability show `` command that + provides a list of all capabilities that can be configured for the requested backend. The required `` parameter takes the form `host@backend-name`. - | - A new command, ``openstack volume backend pool list`` was added which will - provide a list of all backend storage pools. The optional ``-long`` + Add ``openstack volume backend pool list`` command that provides + a list of all backend storage pools. The optional ``--long`` parameter includes some basic configuration and stats for each pool. From 4173690b2492680ab504ef0d60498eeb5a3d8ae2 Mon Sep 17 00:00:00 2001 From: Robin Cernin Date: Thu, 18 Oct 2018 10:39:49 +1000 Subject: [PATCH 1989/3095] Improve document 'openstack complete' The openstack complete command requires bash-completion package Without the relevant package the bash completion doesn't work. Change-Id: I47c77f3e7efe112417d2b96936fcd0cafeb9442e Closes-Bug: #1798493 --- doc/source/cli/command-objects/complete.rst | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/doc/source/cli/command-objects/complete.rst b/doc/source/cli/command-objects/complete.rst index 20e5c41da4..165d5d07d5 100644 --- a/doc/source/cli/command-objects/complete.rst +++ b/doc/source/cli/command-objects/complete.rst @@ -11,8 +11,11 @@ Typical usage for this command is:: openstack complete | sudo tee /etc/bash_completion.d/osc.bash_completion > /dev/null -If installing ``python-openstackclient`` from a package (``apt-get`` or ``yum``), -then this command will likely be run for you. +It is highly recommended to install ``python-openstackclient`` from a package +(``apt-get`` or ``yum``). In some distributions the package ``bash-completion`` is shipped +as dependency, and the `openstack complete` command will be run as a post-install action, +however not every distribution include this dependency and you might need to install +``bash-completion`` package to enable autocomplete feature. complete -------- From dfd37a2e6e49fc547e7a2e8f7840561ece8ff46f Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Mon, 23 Jul 2018 15:53:53 -0500 Subject: [PATCH 1990/3095] Make use of keystoneauth service-type filtering for versions The first version of the versions show command does client-side service-type filtering, which while functional, causes many more API calls than needed. Now that keystoneauth supports the filtering at the source, use it. Change-Id: I57c49e67f9cb285a5f5bc19ec53a42d10de9f0da --- lower-constraints.txt | 2 +- openstackclient/common/versions.py | 16 ++-------------- requirements.txt | 2 +- 3 files changed, 4 insertions(+), 16 deletions(-) diff --git a/lower-constraints.txt b/lower-constraints.txt index db92fef36b..acd11d3238 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -38,7 +38,7 @@ jmespath==0.9.0 jsonpatch==1.16 jsonpointer==1.13 jsonschema==2.6.0 -keystoneauth1==3.4.0 +keystoneauth1==3.6.2 kombu==4.0.0 linecache2==1.0.0 MarkupSafe==1.0 diff --git a/openstackclient/common/versions.py b/openstackclient/common/versions.py index 6a93d3002b..3c267bfe0a 100644 --- a/openstackclient/common/versions.py +++ b/openstackclient/common/versions.py @@ -14,7 +14,6 @@ """Versions Action Implementation""" -import os_service_types from osc_lib.command import command from openstackclient.i18n import _ @@ -67,7 +66,8 @@ def take_action(self, parsed_args): session = self.app.client_manager.session version_data = session.get_all_version_data( interface=interface, - region_name=parsed_args.region_name) + region_name=parsed_args.region_name, + service_type=parsed_args.service) columns = [ "Region Name", @@ -83,22 +83,10 @@ def take_action(self, parsed_args): if status: status = status.upper() - service = parsed_args.service - if service: - # Normalize service type argument to official type - service_type_manager = os_service_types.ServiceTypes() - service = service_type_manager.get_service_type(service) - versions = [] for region_name, interfaces in version_data.items(): for interface, services in interfaces.items(): for service_type, service_versions in services.items(): - if service and service != service_type: - # TODO(mordred) Once there is a version of - # keystoneauth that can do this filtering - # before making all the discovery calls, switch - # to that. - continue for data in service_versions: if status and status != data['status']: continue diff --git a/requirements.txt b/requirements.txt index 205d4e64ec..78c10b4497 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ six>=1.10.0 # MIT Babel!=2.4.0,>=2.3.4 # BSD cliff!=2.9.0,>=2.8.0 # Apache-2.0 -keystoneauth1>=3.4.0 # Apache-2.0 +keystoneauth1>=3.6.2 # Apache-2.0 openstacksdk>=0.17.0 # Apache-2.0 osc-lib>=1.10.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 From 746d42430b26b2d4fc3217044a10a2f8ae08eee1 Mon Sep 17 00:00:00 2001 From: Sean McGinnis Date: Tue, 23 Oct 2018 11:52:39 -0500 Subject: [PATCH 1991/3095] Update release note version reference table The table indicating the openstackclient release at the time of the overall OpenStack release had not been updated since Pike. This adds release versions for Queens and Rocky. Change-Id: Icd9a72c2460fae3d1b714b6da473564e6c709ef6 Signed-off-by: Sean McGinnis --- releasenotes/source/index.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index 2413a57425..19df6740de 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -26,6 +26,8 @@ OpenStack release was made is shown below: ================= ======================= OpenStack Release OpenStackClient Release ================= ======================= +Rocky 3.16.0 +Queens 3.14.0 Pike 3.12.0 Ocata 3.8.1 Newton 3.2.0 From dd958bc1ee13d1604b85b37eb783dc345b8a72de Mon Sep 17 00:00:00 2001 From: Brian Haley Date: Fri, 26 Oct 2018 10:39:47 -0400 Subject: [PATCH 1992/3095] Update the Neutron CLI decoder document The floatingip-disassociate mapping had 'port' instead of '--port', also fixed a few places where 'unset' was missing from some of the mappings. Trivialfix Change-Id: I3b01db28dda674e9988176d496154fbd26e4449f --- doc/source/cli/data/neutron.csv | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/source/cli/data/neutron.csv b/doc/source/cli/data/neutron.csv index be3f32a867..080114e241 100644 --- a/doc/source/cli/data/neutron.csv +++ b/doc/source/cli/data/neutron.csv @@ -50,7 +50,7 @@ flavor-update,network flavor set,Update a Neutron service flavor. floatingip-associate,floating ip set port --fixed-ip,Create a mapping between a floating IP and a fixed IP. floatingip-create,floating ip create,Create a floating IP for a given tenant. floatingip-delete,floating ip delete,Delete a given floating IP. -floatingip-disassociate,floating ip unset port,Remove a mapping from a floating IP to a fixed IP. +floatingip-disassociate,floating ip unset --port,Remove a mapping from a floating IP to a fixed IP. floatingip-list,floating ip list,List floating IPs that belong to a given tenant. floatingip-show,floating ip show,Show information of a given floating IP. help,help,print detailed help for another command @@ -142,12 +142,12 @@ net-ip-availability-show,ip availability show,Show IP usage of specific network net-list,network list,List networks that belong to a given tenant. net-list-on-dhcp-agent,network list --agent,List the networks on a DHCP agent. net-show,network show,Show information of a given network. -net-update,network set,Update network's information. +net-update,network set / network unset,Update network's information. port-create,port create,Create a port for a given tenant. port-delete,port delete,Delete a given port. port-list,port list,List ports that belong to a given tenant. port-show,port show,Show information of a given port. -port-update,port set/port unset,Update port's information. +port-update,port set / port unset,Update port's information. purge,,Delete all resources that belong to a given tenant. qos-available-rule-types,network qos rule type list,List available qos rule types. qos-bandwidth-limit-rule-create,network qos rule create --type bandwidth-limit,Create a qos bandwidth limit rule. @@ -183,7 +183,7 @@ rbac-update,network rbac set,Update RBAC policy for given tenant. router-create,router create,Create a router for a given tenant. router-delete,router delete,Delete a given router. router-gateway-clear,router unset,Remove an external network gateway from a router. -router-gateway-set,router set,Set the external network gateway for a router. +router-gateway-set,router set / router unset,Set the external network gateway for a router. router-interface-add,router add subnet / router add port,Add an internal network interface to a router. router-interface-delete,router remove subnet / router remove port,Remove an internal network interface from a router. router-list,router list,List routers that belong to a given tenant. @@ -199,7 +199,7 @@ security-group-rule-delete,security group rule delete,Delete a given security gr security-group-rule-list,security group rule list,List security group rules that belong to a given tenant. security-group-rule-show,security group rule show,Show information of a given security group rule. security-group-show,security group show,Show information of a given security group. -security-group-update,security group set,Update a given security group. +security-group-update,security group set / security group unset,Update a given security group. service-provider-list,network service provider list,List service providers. subnet-create,subnet create,Create a subnet for a given tenant. subnet-delete,subnet delete,Delete a given subnet. From f00ffebea61f94f2334d1c1578bc23986bbcf58c Mon Sep 17 00:00:00 2001 From: Sean McGinnis Date: Wed, 4 Apr 2018 14:56:27 -0500 Subject: [PATCH 1993/3095] Remove invalid 'unlock-volume' migration arg There is an optional flag that can be passed in to a volume migration to tell Cinder to 'lock' a volume so no other process can abort the migration. This is reflected correctly with the --lock-volume argument flag to `openstack volume migrate`, but there is another --unlock-volume flag that is shown in the help text for this command that does not do anything and is not used anywhere. Since there is no action to "unlock" a volume, this just causes confusion - including for Cinder developers that know this API. To avoid confusion, this invalid flag should just be removed from the command. Change-Id: I5f111ed58803a1bf5d34e828341d735099247108 --- doc/source/cli/command-objects/volume.rst | 9 +------ .../tests/unit/volume/v2/test_volume.py | 24 ------------------- openstackclient/volume/v2/volume.py | 10 +------- .../notes/unlock-volume-a6990fc3bf1f5f67.yaml | 5 ++++ 4 files changed, 7 insertions(+), 41 deletions(-) create mode 100644 releasenotes/notes/unlock-volume-a6990fc3bf1f5f67.yaml diff --git a/doc/source/cli/command-objects/volume.rst b/doc/source/cli/command-objects/volume.rst index a06a5d4007..fd704b04c2 100644 --- a/doc/source/cli/command-objects/volume.rst +++ b/doc/source/cli/command-objects/volume.rst @@ -226,7 +226,7 @@ Migrate volume to a new host openstack volume migrate --host [--force-host-copy] - [--lock-volume | --unlock-volume] + [--lock-volume] .. option:: --host @@ -245,13 +245,6 @@ Migrate volume to a new host *Volume version 2 only* -.. option:: --unlock-volume - - If specified, the volume state will not be locked and the a - migration can be aborted (default) (possibly by another operation) - - *Volume version 2 only* - .. _volume_migrate-volume: .. describe:: diff --git a/openstackclient/tests/unit/volume/v2/test_volume.py b/openstackclient/tests/unit/volume/v2/test_volume.py index 2fa924b8a5..f9a8b7c680 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume.py +++ b/openstackclient/tests/unit/volume/v2/test_volume.py @@ -1320,7 +1320,6 @@ def test_volume_migrate(self): verifylist = [ ("force_host_copy", False), ("lock_volume", False), - ("unlock_volume", False), ("host", "host@backend-name#pool"), ("volume", self._volume.id), ] @@ -1342,7 +1341,6 @@ def test_volume_migrate_with_option(self): verifylist = [ ("force_host_copy", True), ("lock_volume", True), - ("unlock_volume", False), ("host", "host@backend-name#pool"), ("volume", self._volume.id), ] @@ -1354,27 +1352,6 @@ def test_volume_migrate_with_option(self): self._volume.id, "host@backend-name#pool", True, True) self.assertIsNone(result) - def test_volume_migrate_with_unlock_volume(self): - arglist = [ - "--unlock-volume", - "--host", "host@backend-name#pool", - self._volume.id, - ] - verifylist = [ - ("force_host_copy", False), - ("lock_volume", False), - ("unlock_volume", True), - ("host", "host@backend-name#pool"), - ("volume", self._volume.id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - self.volumes_mock.get.assert_called_once_with(self._volume.id) - self.volumes_mock.migrate_volume.assert_called_once_with( - self._volume.id, "host@backend-name#pool", False, False) - self.assertIsNone(result) - def test_volume_migrate_without_host(self): arglist = [ self._volume.id, @@ -1382,7 +1359,6 @@ def test_volume_migrate_without_host(self): verifylist = [ ("force_host_copy", False), ("lock_volume", False), - ("unlock_volume", False), ("volume", self._volume.id), ] diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index 61f846b02f..55b7fac300 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -472,21 +472,13 @@ def get_parser(self, prog_name): help=_("Enable generic host-based force-migration, " "which bypasses driver optimizations") ) - lock_group = parser.add_mutually_exclusive_group() - lock_group.add_argument( + parser.add_argument( '--lock-volume', action="store_true", help=_("If specified, the volume state will be locked " "and will not allow a migration to be aborted " "(possibly by another operation)") ) - lock_group.add_argument( - '--unlock-volume', - action="store_true", - help=_("If specified, the volume state will not be " - "locked and the a migration can be aborted " - "(default) (possibly by another operation)") - ) return parser def take_action(self, parsed_args): diff --git a/releasenotes/notes/unlock-volume-a6990fc3bf1f5f67.yaml b/releasenotes/notes/unlock-volume-a6990fc3bf1f5f67.yaml new file mode 100644 index 0000000000..60124bf890 --- /dev/null +++ b/releasenotes/notes/unlock-volume-a6990fc3bf1f5f67.yaml @@ -0,0 +1,5 @@ +--- +upgrade: + - | + The ``volume migrate --unlock`` argument did not actually do anything and + has now been removed. From 4c387fe94cd33ce9357df52d848d04b69c90b3fd Mon Sep 17 00:00:00 2001 From: lvxianguo Date: Thu, 1 Nov 2018 16:45:27 +0800 Subject: [PATCH 1994/3095] trivial: modify spelling error of project Change-Id: I6cf68325bc58cfd073c5fca4eb2773d108735399 --- doc/source/cli/data/keystone.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/cli/data/keystone.csv b/doc/source/cli/data/keystone.csv index 03c34704da..bcc305515f 100644 --- a/doc/source/cli/data/keystone.csv +++ b/doc/source/cli/data/keystone.csv @@ -18,7 +18,7 @@ service-get,service show,Display service from Service Catalog. service-list,service list,List all services in Service Catalog. tenant-create,project create,Create new tenant. tenant-delete,project delete,Delete tenant. -tenant-get,proejct show,Display tenant details. +tenant-get,project show,Display tenant details. tenant-list,project list,List all tenants. tenant-update,project set,"Update tenant name, description, enabled status." token-get,token issue,Display the current user token. From e782f49927a6b142a35f85a1a4a759fd2b1ceedf Mon Sep 17 00:00:00 2001 From: Pavlo Shchelokovskyy Date: Mon, 14 May 2018 18:13:28 +0000 Subject: [PATCH 1995/3095] Add --name-lookup-one-by-one option to server list usually in a big cloud there are many images and flavors, while each given project might use only some of those. This patch introduces '--name-lookup-one-by-one' argument to server list command (mutually exclusive with '--no-name-lookup') When provided (or either '--image' or '--flavor' is specified) to the `server list` command, name resolving for corresponding entity is now using targeted GET commands instead of full entities list. In some situations this can significantly speedup the execution of the `server list` command by reducing the number of API requests performed. Change-Id: I59cbf3f75c55e5d3747654edcc9be86ad954cf40 Story: #2002039 Task: #19682 --- openstackclient/compute/v2/server.py | 70 +++++++++++++------ .../tests/unit/compute/v2/test_server.py | 30 +++++++- ...me-lookup-one-by-one-e0f15f4eab329b19.yaml | 14 ++++ 3 files changed, 91 insertions(+), 23 deletions(-) create mode 100644 releasenotes/notes/name-lookup-one-by-one-e0f15f4eab329b19.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 777f7744e7..9247c40e53 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1044,11 +1044,22 @@ def get_parser(self, prog_name): default=False, help=_('List additional fields in output'), ) - parser.add_argument( + name_lookup_group = parser.add_mutually_exclusive_group() + name_lookup_group.add_argument( '-n', '--no-name-lookup', action='store_true', default=False, - help=_('Skip flavor and image name lookup.'), + help=_('Skip flavor and image name lookup.' + 'Mutually exclusive with "--name-lookup-one-by-one"' + ' option.'), + ) + name_lookup_group.add_argument( + '--name-lookup-one-by-one', + action='store_true', + default=False, + help=_('When looking up flavor and image names, look them up' + 'one by one as needed instead of all together (default). ' + 'Mutually exclusive with "--no-name-lookup|-n" option.'), ) parser.add_argument( '--marker', @@ -1223,28 +1234,43 @@ def take_action(self, parsed_args): limit=parsed_args.limit) images = {} - # Create a dict that maps image_id to image object. - # Needed so that we can display the "Image Name" column. - # "Image Name" is not crucial, so we swallow any exceptions. - if data and not parsed_args.no_name_lookup: - try: - images_list = self.app.client_manager.image.images.list() - for i in images_list: - images[i.id] = i - except Exception: - pass - flavors = {} - # Create a dict that maps flavor_id to flavor object. - # Needed so that we can display the "Flavor Name" column. - # "Flavor Name" is not crucial, so we swallow any exceptions. if data and not parsed_args.no_name_lookup: - try: - flavors_list = compute_client.flavors.list(is_public=None) - for i in flavors_list: - flavors[i.id] = i - except Exception: - pass + # Create a dict that maps image_id to image object. + # Needed so that we can display the "Image Name" column. + # "Image Name" is not crucial, so we swallow any exceptions. + if parsed_args.name_lookup_one_by_one or image_id: + for i_id in set(filter(lambda x: x is not None, + (s.image.get('id') for s in data))): + try: + images[i_id] = image_client.images.get(i_id) + except Exception: + pass + else: + try: + images_list = image_client.images.list() + for i in images_list: + images[i.id] = i + except Exception: + pass + + # Create a dict that maps flavor_id to flavor object. + # Needed so that we can display the "Flavor Name" column. + # "Flavor Name" is not crucial, so we swallow any exceptions. + if parsed_args.name_lookup_one_by_one or flavor_id: + for f_id in set(filter(lambda x: x is not None, + (s.flavor.get('id') for s in data))): + try: + flavors[f_id] = compute_client.flavors.get(f_id) + except Exception: + pass + else: + try: + flavors_list = compute_client.flavors.list(is_public=None) + for i in flavors_list: + flavors[i.id] = i + except Exception: + pass # Populate image_name, image_id, flavor_name and flavor_id attributes # of server objects so that we can display those columns. diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 46d4c24114..6243b49ddf 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -1938,12 +1938,18 @@ def test_server_list_no_option(self): ('all_projects', False), ('long', False), ('deleted', False), + ('name_lookup_one_by_one', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.servers_mock.list.assert_called_with(**self.kwargs) + self.images_mock.list.assert_called() + self.flavors_mock.list.assert_called() + # we did not pass image or flavor, so gets on those must be absent + self.assertFalse(self.flavors_mock.get.call_count) + self.assertFalse(self.images_mock.get.call_count) self.assertEqual(self.columns, columns) self.assertEqual(tuple(self.data), tuple(data)) @@ -2014,6 +2020,28 @@ def test_server_list_n_option(self): self.assertEqual(self.columns, columns) self.assertEqual(tuple(self.data_no_name_lookup), tuple(data)) + def test_server_list_name_lookup_one_by_one(self): + arglist = [ + '--name-lookup-one-by-one' + ] + verifylist = [ + ('all_projects', False), + ('no_name_lookup', False), + ('name_lookup_one_by_one', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.servers_mock.list.assert_called_with(**self.kwargs) + self.assertFalse(self.images_mock.list.call_count) + self.assertFalse(self.flavors_mock.list.call_count) + self.images_mock.get.assert_called() + self.flavors_mock.get.assert_called() + + self.assertEqual(self.columns, columns) + self.assertEqual(tuple(self.data), tuple(data)) + def test_server_list_with_image(self): arglist = [ @@ -2046,7 +2074,7 @@ def test_server_list_with_flavor(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.flavors_mock.get.assert_called_with(self.flavor.id) + self.flavors_mock.get.has_calls(self.flavor.id) self.search_opts['flavor'] = self.flavor.id self.servers_mock.list.assert_called_with(**self.kwargs) diff --git a/releasenotes/notes/name-lookup-one-by-one-e0f15f4eab329b19.yaml b/releasenotes/notes/name-lookup-one-by-one-e0f15f4eab329b19.yaml new file mode 100644 index 0000000000..b572b75d5e --- /dev/null +++ b/releasenotes/notes/name-lookup-one-by-one-e0f15f4eab329b19.yaml @@ -0,0 +1,14 @@ +--- +features: + - | + Add ``--name-lookup-one-by-one`` option to the ``server list`` command + that is (mutually exclusive with ``-n | --no-name-lookup``). + When provided, the names of images and flavors will be resolved one by one + only for those images and flavors that are needed to display the obtained + list of servers instead of fetching all the images and flavors. + Depending on amount of images in your deployment this can speed up the + execution of this command. + - | + When given ``--image`` or ``--flavor`` argument, the ``server list`` + command now resolves only single image or flavor instead of fetching + all the images or flavors for name lookup purposes. From 013c9a4f3a44cb0b81fc7affe9b933e701cb5dba Mon Sep 17 00:00:00 2001 From: melanie witt Date: Fri, 2 Nov 2018 22:27:55 +0000 Subject: [PATCH 1996/3095] Handle multiple ports in AddFloatingIP AddFloatingIP refers to an old nova proxy API to neutron that was deprecated in nova. The neutron API for floating IP associate requires a port to be specified. Currently, the code is selecting the first port if the server has multiple ports. But, an attempt to associate the first port with a floating IP can fail if the first port is not on a network that is attached to an external gateway. In order to make the command work better for users who have a server with multiple ports, we can: 1. Select the port corresponding to the fixed_ip_address, if one was specified 2. Try to associate the floating IP with each port until one of the attempts succeeds, else re-raise the last exception. (404 ExternalGatewayForFloatingIPNotFound from neutron) This also fixes incorrect FakeFloatingIP attributes that were being set in the TestServerAddFloatingIPNetwork unit tests, which were causing the tests to use None as parsed args for ip-address and --fixed-ip-address and thus bypassing code in the 'if parsed_args.fixed_ip_address:' block. Task: 27800 Story: 2004263 Change-Id: I11fbcebf6b00f12a030b000c84dcf1d6b5e86250 --- openstackclient/compute/v2/server.py | 51 ++++++-- .../tests/unit/compute/v2/test_server.py | 114 ++++++++++++++++-- ...oating-ip-multi-port-9779e88f590cae23.yaml | 14 +++ 3 files changed, 164 insertions(+), 15 deletions(-) create mode 100644 releasenotes/notes/floating-ip-multi-port-9779e88f590cae23.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 4b0aedd7ac..9051c8d25b 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -23,6 +23,7 @@ from novaclient import api_versions from novaclient.v2 import servers +from openstack import exceptions as sdk_exceptions from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -251,13 +252,16 @@ def update_parser_common(self, parser): parser.add_argument( "ip_address", metavar="", - help=_("Floating IP address to assign to server (IP only)"), + help=_("Floating IP address to assign to the first available " + "server port (IP only)"), ) parser.add_argument( "--fixed-ip-address", metavar="", help=_( - "Fixed IP address to associate with this floating IP address" + "Fixed IP address to associate with this floating IP address. " + "The first server port containing the fixed IP address will " + "be used" ), ) return parser @@ -274,12 +278,45 @@ def take_action_network(self, client, parsed_args): compute_client.servers, parsed_args.server, ) - port = list(client.ports(device_id=server.id))[0] - attrs['port_id'] = port.id + ports = list(client.ports(device_id=server.id)) + # If the fixed IP address was specified, we need to find the + # corresponding port. if parsed_args.fixed_ip_address: - attrs['fixed_ip_address'] = parsed_args.fixed_ip_address - - client.update_ip(obj, **attrs) + fip_address = parsed_args.fixed_ip_address + attrs['fixed_ip_address'] = fip_address + for port in ports: + for ip in port.fixed_ips: + if ip['ip_address'] == fip_address: + attrs['port_id'] = port.id + break + else: + continue + break + if 'port_id' not in attrs: + msg = _('No port found for fixed IP address %s') + raise exceptions.CommandError(msg % fip_address) + client.update_ip(obj, **attrs) + else: + # It's possible that one or more ports are not connected to a + # router and thus could fail association with a floating IP. + # Try each port until one succeeds. If none succeed, re-raise the + # last exception. + error = None + for port in ports: + attrs['port_id'] = port.id + try: + client.update_ip(obj, **attrs) + except sdk_exceptions.NotFoundException as exp: + # 404 ExternalGatewayForFloatingIPNotFound from neutron + LOG.info('Skipped port %s because it is not attached to ' + 'an external gateway', port.id) + error = exp + continue + else: + error = None + break + if error: + raise error def take_action_compute(self, client, parsed_args): client.api.floating_ip_add( diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 928794fba7..afa2e80a46 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -19,6 +19,7 @@ import mock from mock import call from novaclient import api_versions +from openstack import exceptions as sdk_exceptions from osc_lib import exceptions from osc_lib import utils as common_utils from oslo_utils import timeutils @@ -222,11 +223,11 @@ def test_server_add_floating_ip_default(self): self.network.ports = mock.Mock(return_value=[_port]) arglist = [ _server.id, - _floating_ip['ip'], + _floating_ip['floating_ip_address'], ] verifylist = [ ('server', _server.id), - ('ip_address', _floating_ip['ip']), + ('ip_address', _floating_ip['floating_ip_address']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -237,7 +238,7 @@ def test_server_add_floating_ip_default(self): } self.network.find_ip.assert_called_once_with( - _floating_ip['ip'], + _floating_ip['floating_ip_address'], ignore_missing=False, ) self.network.ports.assert_called_once_with( @@ -248,6 +249,64 @@ def test_server_add_floating_ip_default(self): **attrs ) + def test_server_add_floating_ip_default_no_external_gateway(self, + success=False): + _server = compute_fakes.FakeServer.create_one_server() + self.servers_mock.get.return_value = _server + _port = network_fakes.FakePort.create_one_port() + _floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip() + self.network.find_ip = mock.Mock(return_value=_floating_ip) + return_value = [_port] + # In the success case, we'll have two ports, where the first port is + # not attached to an external gateway but the second port is. + if success: + return_value.append(_port) + self.network.ports = mock.Mock(return_value=return_value) + side_effect = [sdk_exceptions.NotFoundException()] + if success: + side_effect.append(None) + self.network.update_ip = mock.Mock(side_effect=side_effect) + arglist = [ + _server.id, + _floating_ip['floating_ip_address'], + ] + verifylist = [ + ('server', _server.id), + ('ip_address', _floating_ip['floating_ip_address']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + if success: + self.cmd.take_action(parsed_args) + else: + self.assertRaises(sdk_exceptions.NotFoundException, + self.cmd.take_action, parsed_args) + + attrs = { + 'port_id': _port.id, + } + + self.network.find_ip.assert_called_once_with( + _floating_ip['floating_ip_address'], + ignore_missing=False, + ) + self.network.ports.assert_called_once_with( + device_id=_server.id, + ) + if success: + self.assertEqual(2, self.network.update_ip.call_count) + calls = [mock.call(_floating_ip, **attrs)] * 2 + self.network.update_ip.assert_has_calls(calls) + else: + self.network.update_ip.assert_called_once_with( + _floating_ip, + **attrs + ) + + def test_server_add_floating_ip_default_one_external_gateway(self): + self.test_server_add_floating_ip_default_no_external_gateway( + success=True) + def test_server_add_floating_ip_fixed(self): _server = compute_fakes.FakeServer.create_one_server() self.servers_mock.get.return_value = _server @@ -255,26 +314,31 @@ def test_server_add_floating_ip_fixed(self): _floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip() self.network.find_ip = mock.Mock(return_value=_floating_ip) self.network.ports = mock.Mock(return_value=[_port]) + # The user has specified a fixed ip that matches one of the ports + # already attached to the instance. arglist = [ - '--fixed-ip-address', _floating_ip['fixed_ip'], + '--fixed-ip-address', _port.fixed_ips[0]['ip_address'], _server.id, - _floating_ip['ip'], + _floating_ip['floating_ip_address'], ] verifylist = [ - ('fixed_ip_address', _floating_ip['fixed_ip']), + ('fixed_ip_address', _port.fixed_ips[0]['ip_address']), ('server', _server.id), - ('ip_address', _floating_ip['ip']), + ('ip_address', _floating_ip['floating_ip_address']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) + # We expect the update_ip call to specify a new fixed_ip_address which + # will overwrite the floating ip's existing fixed_ip_address. attrs = { 'port_id': _port.id, + 'fixed_ip_address': _port.fixed_ips[0]['ip_address'], } self.network.find_ip.assert_called_once_with( - _floating_ip['ip'], + _floating_ip['floating_ip_address'], ignore_missing=False, ) self.network.ports.assert_called_once_with( @@ -285,6 +349,40 @@ def test_server_add_floating_ip_fixed(self): **attrs ) + def test_server_add_floating_ip_fixed_no_port_found(self): + _server = compute_fakes.FakeServer.create_one_server() + self.servers_mock.get.return_value = _server + _port = network_fakes.FakePort.create_one_port() + _floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip() + self.network.find_ip = mock.Mock(return_value=_floating_ip) + self.network.ports = mock.Mock(return_value=[_port]) + # The user has specified a fixed ip that does not match any of the + # ports already attached to the instance. + nonexistent_ip = '10.0.0.9' + arglist = [ + '--fixed-ip-address', nonexistent_ip, + _server.id, + _floating_ip['floating_ip_address'], + ] + verifylist = [ + ('fixed_ip_address', nonexistent_ip), + ('server', _server.id), + ('ip_address', _floating_ip['floating_ip_address']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + + self.network.find_ip.assert_called_once_with( + _floating_ip['floating_ip_address'], + ignore_missing=False, + ) + self.network.ports.assert_called_once_with( + device_id=_server.id, + ) + self.network.update_ip.assert_not_called() + class TestServerAddPort(TestServer): diff --git a/releasenotes/notes/floating-ip-multi-port-9779e88f590cae23.yaml b/releasenotes/notes/floating-ip-multi-port-9779e88f590cae23.yaml new file mode 100644 index 0000000000..738190f90b --- /dev/null +++ b/releasenotes/notes/floating-ip-multi-port-9779e88f590cae23.yaml @@ -0,0 +1,14 @@ +--- +fixes: + - | + The ``openstack server add floating ip`` command has been fixed to handle + servers with multiple ports attached. Previously, the command was using + the first port in the port list when attempting to associate the floating + ip. This could fail if the server had multiple ports and the first port + in the list was not attached to an external gateway. Another way it could + fail is if the ``--fixed-ip-address`` option was passed and the first port + did not have the specified fixed IP address attached to it. + Now, the ``openstack server add floating ip`` command will find the port + attached to the specified ``--fixed-ip-address``, if provided, else it will + try multiple ports until one is found attached to an external gateway. If + a suitable port is not found in the port list, an error will be returned. From 21e4c87bdeb78ef5c5a251ae9e671efd1b5e3102 Mon Sep 17 00:00:00 2001 From: Sven Wegener Date: Fri, 12 Jan 2018 14:19:11 +0100 Subject: [PATCH 1997/3095] image/v2: support multiple property filters Change-Id: I8ba40cb8ca647ec24b80c2824bb64e84430535d4 Signed-off-by: Sven Wegener --- openstackclient/image/v2/image.py | 18 +++++++++--------- ...r-multiple-properties-03c51d43131ee3b6.yaml | 6 ++++++ 2 files changed, 15 insertions(+), 9 deletions(-) create mode 100644 releasenotes/notes/image-list-filter-multiple-properties-03c51d43131ee3b6.yaml diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 1e67692a54..06eebe984c 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -515,7 +515,8 @@ def get_parser(self, prog_name): '--property', metavar='', action=parseractions.KeyValueAction, - help=_('Filter output based on property'), + help=_('Filter output based on property ' + '(repeat option to filter on multiple properties)'), ) parser.add_argument( '--name', @@ -643,14 +644,13 @@ def take_action(self, parsed_args): marker = page[-1]['id'] if parsed_args.property: - # NOTE(dtroyer): coerce to a list to subscript it in py3 - attr, value = list(parsed_args.property.items())[0] - api_utils.simple_filter( - data, - attr=attr, - value=value, - property_field='properties', - ) + for attr, value in parsed_args.property.items(): + api_utils.simple_filter( + data, + attr=attr, + value=value, + property_field='properties', + ) data = utils.sort_items(data, parsed_args.sort, str) diff --git a/releasenotes/notes/image-list-filter-multiple-properties-03c51d43131ee3b6.yaml b/releasenotes/notes/image-list-filter-multiple-properties-03c51d43131ee3b6.yaml new file mode 100644 index 0000000000..294f43810e --- /dev/null +++ b/releasenotes/notes/image-list-filter-multiple-properties-03c51d43131ee3b6.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + The ``image list`` command now properly filters images on multiple + ``--property`` options. + [Bug `2004290 `_] From 0d764cdb5a1f9a8b113721634fcc8948ddd82c8d Mon Sep 17 00:00:00 2001 From: Vishakha Agarwal Date: Fri, 19 Oct 2018 12:29:00 +0530 Subject: [PATCH 1998/3095] Add project param in LimitList parser when doing openstack limit list --project xyz_id, CLI raising error unrecognized arguments, whereas in api-ref document [1], user can pass project_id as query param.This addresses the above issue, by adding param --project in parser of LimitList. [1] https://developer.openstack.org/api-ref/identity/v3/index.html Change-Id: If4644cc99a3803f61f4a688b828aeb73977fc0dd Closes-Bug: #1798744 --- doc/source/cli/command-objects/limit.rst | 5 +++++ openstackclient/identity/v3/limit.py | 13 ++++++++++++- .../tests/unit/identity/v3/test_limit.py | 3 ++- .../notes/bug-1798744-5512256baf4dc633.yaml | 4 ++++ 4 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/bug-1798744-5512256baf4dc633.yaml diff --git a/doc/source/cli/command-objects/limit.rst b/doc/source/cli/command-objects/limit.rst index 71cf2a420d..46da6c1b70 100644 --- a/doc/source/cli/command-objects/limit.rst +++ b/doc/source/cli/command-objects/limit.rst @@ -73,6 +73,7 @@ List project-specific limits [--service ] [--resource-name ] [--region ] + [--project ] .. option:: --service @@ -86,6 +87,10 @@ List project-specific limits The region name to filter the response by +.. option:: --project + + List resource limits associated with project + limit show ---------- diff --git a/openstackclient/identity/v3/limit.py b/openstackclient/identity/v3/limit.py index c6f1cb1fb5..57d1dfd641 100644 --- a/openstackclient/identity/v3/limit.py +++ b/openstackclient/identity/v3/limit.py @@ -116,6 +116,11 @@ def get_parser(self, prog_name): metavar='', help=_('Region for the registered limit to affect.'), ) + parser.add_argument( + '--project', + metavar='', + help=_('List resource limits associated with project'), + ) return parser def take_action(self, parsed_args): @@ -131,11 +136,17 @@ def take_action(self, parsed_args): region = utils.find_resource( identity_client.regions, parsed_args.region ) + project = None + if parsed_args.project: + project = utils.find_resource( + identity_client.projects, parsed_args.project + ) limits = identity_client.limits.list( service=service, resource_name=parsed_args.resource_name, - region=region + region=region, + project=project ) columns = ( diff --git a/openstackclient/tests/unit/identity/v3/test_limit.py b/openstackclient/tests/unit/identity/v3/test_limit.py index 44c0358d89..e5cd87b8bd 100644 --- a/openstackclient/tests/unit/identity/v3/test_limit.py +++ b/openstackclient/tests/unit/identity/v3/test_limit.py @@ -362,7 +362,8 @@ def test_limit_list(self): columns, data = self.cmd.take_action(parsed_args) self.limit_mock.list.assert_called_with( - service=None, resource_name=None, region=None + service=None, resource_name=None, region=None, + project=None ) collist = ( diff --git a/releasenotes/notes/bug-1798744-5512256baf4dc633.yaml b/releasenotes/notes/bug-1798744-5512256baf4dc633.yaml new file mode 100644 index 0000000000..1dd01ba001 --- /dev/null +++ b/releasenotes/notes/bug-1798744-5512256baf4dc633.yaml @@ -0,0 +1,4 @@ +--- +features: + - Add ``--project`` option to ``limit list`` command. + [Bug `1798744 `_] From 81fd5c995d7cce45f45e37af8a6b0309308a4cba Mon Sep 17 00:00:00 2001 From: Vishakha Agarwal Date: Mon, 22 Oct 2018 13:51:30 +0530 Subject: [PATCH 1999/3095] Updated the take_actions for unified limits When user passes --region None, the find_resource of osc_lib calls get() of region. The get API of region ignores the name param returning all the regions in result. As the find_resource checks many cases against the result returned by get API. The output comes greater than 1, thus returning "More than one region ID exist" which is incorrect. However in case of region which cannot be filtered by name we do not require to check these many cases. The solution is to directly call the get method of APIs and returning No resource name exist with the xyz" on passing invaid parameter. And returning all in case of None. Thus created a new function get_resource which can be used in future too by these types of API's. Change-Id: Ib3f881d34a82af97199ce51bfbefc6f3f08599f1 Closes-bug: #1799153 --- openstackclient/identity/common.py | 19 ++++++++ openstackclient/identity/v3/limit.py | 29 +++++++++-- .../identity/v3/registered_limit.py | 48 +++++++++++++++---- 3 files changed, 84 insertions(+), 12 deletions(-) diff --git a/openstackclient/identity/common.py b/openstackclient/identity/common.py index f36f5f73a9..e825116649 100644 --- a/openstackclient/identity/common.py +++ b/openstackclient/identity/common.py @@ -68,6 +68,25 @@ def find_service(identity_client, name_type_or_id): raise exceptions.CommandError(msg % name_type_or_id) +def get_resource(manager, name_type_or_id): + # NOTE (vishakha): Due to bug #1799153 and for any another related case + # where GET resource API does not support the filter by name, + # osc_lib.utils.find_resource() method cannot be used because that method + # try to fall back to list all the resource if requested resource cannot + # be get via name. Which ends up with NoUniqueMatch error. + # This new function is the replacement for osc_lib.utils.find_resource() + # for resources does not support GET by name. + # For example: identity GET /regions. + """Find a resource by id or name.""" + + try: + return manager.get(name_type_or_id) + except identity_exc.NotFound: + # raise NotFound exception + msg = _("No resource with name or id of '%s' exists") + raise exceptions.CommandError(msg % name_type_or_id) + + def _get_token_resource(client, resource, parsed_name, parsed_domain=None): """Peek into the user's auth token to get resource IDs diff --git a/openstackclient/identity/v3/limit.py b/openstackclient/identity/v3/limit.py index 57d1dfd641..f2af81e9f0 100644 --- a/openstackclient/identity/v3/limit.py +++ b/openstackclient/identity/v3/limit.py @@ -78,9 +78,19 @@ def take_action(self, parsed_args): ) region = None if parsed_args.region: - region = utils.find_resource( - identity_client.regions, parsed_args.region - ) + val = getattr(parsed_args, 'region', None) + if 'None' not in val: + # NOTE (vishakha): Due to bug #1799153 and for any another + # related case where GET resource API does not support the + # filter by name, osc_lib.utils.find_resource() method cannot + # be used because that method try to fall back to list all the + # resource if requested resource cannot be get via name. Which + # ends up with NoUniqueMatch error. + # So osc_lib.utils.find_resource() function cannot be used for + # 'regions', using common_utils.get_resource() instead. + region = common_utils.get_resource( + identity_client.regions, parsed_args.region + ) limit = identity_client.limits.create( project, @@ -136,6 +146,19 @@ def take_action(self, parsed_args): region = utils.find_resource( identity_client.regions, parsed_args.region ) + val = getattr(parsed_args, 'region', None) + if 'None' not in val: + # NOTE (vishakha): Due to bug #1799153 and for any another + # related case where GET resource API does not support the + # filter by name, osc_lib.utils.find_resource() method cannot + # be used because that method try to fall back to list all the + # resource if requested resource cannot be get via name. Which + # ends up with NoUniqueMatch error. + # So osc_lib.utils.find_resource() function cannot be used for + # 'regions', using common_utils.get_resource() instead. + region = common_utils.get_resource( + identity_client.regions, parsed_args.region + ) project = None if parsed_args.project: project = utils.find_resource( diff --git a/openstackclient/identity/v3/registered_limit.py b/openstackclient/identity/v3/registered_limit.py index 72e07297e6..b308080ea2 100644 --- a/openstackclient/identity/v3/registered_limit.py +++ b/openstackclient/identity/v3/registered_limit.py @@ -69,9 +69,19 @@ def take_action(self, parsed_args): ) region = None if parsed_args.region: - region = utils.find_resource( - identity_client.regions, parsed_args.region - ) + val = getattr(parsed_args, 'region', None) + if 'None' not in val: + # NOTE (vishakha): Due to bug #1799153 and for any another + # related case where GET resource API does not support the + # filter by name, osc_lib.utils.find_resource() method cannot + # be used because that method try to fall back to list all the + # resource if requested resource cannot be get via name. Which + # ends up with NoUniqueMatch error. + # So osc_lib.utils.find_resource() function cannot be used for + # 'regions', using common_utils.get_resource() instead. + region = common_utils.get_resource( + identity_client.regions, parsed_args.region + ) registered_limit = identity_client.registered_limits.create( service, @@ -153,9 +163,19 @@ def take_action(self, parsed_args): ) region = None if parsed_args.region: - region = utils.find_resource( - identity_client.regions, parsed_args.region - ) + val = getattr(parsed_args, 'region', None) + if 'None' not in val: + # NOTE (vishakha): Due to bug #1799153 and for any another + # related case where GET resource API does not support the + # filter by name, osc_lib.utils.find_resource() method cannot + # be used because that method try to fall back to list all the + # resource if requested resource cannot be get via name. Which + # ends up with NoUniqueMatch error. + # So osc_lib.utils.find_resource() function cannot be used for + # 'regions', using common_utils.get_resource() instead. + region = common_utils.get_resource( + identity_client.regions, parsed_args.region + ) registered_limits = identity_client.registered_limits.list( service=service, @@ -222,9 +242,19 @@ def take_action(self, parsed_args): region = None if parsed_args.region: - region = utils.find_resource( - identity_client.regions, parsed_args.region - ) + val = getattr(parsed_args, 'region', None) + if 'None' not in val: + # NOTE (vishakha): Due to bug #1799153 and for any another + # related case where GET resource API does not support the + # filter by name, osc_lib.utils.find_resource() method cannot + # be used because that method try to fall back to list all the + # resource if requested resource cannot be get via name. Which + # ends up with NoUniqueMatch error. + # So osc_lib.utils.find_resource() function cannot be used for + # 'regions', using common_utils.get_resource() instead. + region = common_utils.get_resource( + identity_client.regions, parsed_args.region + ) registered_limit = identity_client.registered_limits.update( parsed_args.registered_limit_id, From eb06a24e4ff1e92821dafc37015315191a593445 Mon Sep 17 00:00:00 2001 From: Vishakha Agarwal Date: Fri, 12 Oct 2018 15:19:19 +0530 Subject: [PATCH 2000/3095] Modify the help message for 'registered limit set' Regsitered limit set CLI takes --service, --region and --resource-name as param which can be updated along with --default limit for existing registered limit. Default limit can be updated with same value and CLI return the success. But --service, --region and --resource- name cannot be same as existing one. CLI return 409 for this case. Which is valid behaviour because more than one limit with same service and same resource cannot exist. But help message of --service, --region and --resource-name are not much clear to tell that they cannot be passed with same value. This patch clarifies the help message for resigtered limit set CLI. Reference Scenario: * openstack registered limit set --default-limit 91 64c2e97fbe904b888544ffdcab21989b limit updated sucessfully Updating limit with exsiting service and resource-name: * openstack registered limit set --default-limit 92 --resource-name snapshot 64c2e97fbe904b888544ffdcab21989b Conflict occurred attempting to store registered_limit - Duplicate entry. (HTTP 409) *openstack registered limit set --default-limit 93 --service compute 64c2e97fbe904b888544ffdcab21989b Conflict occurred attempting to store registered_limit - Duplicate entry. (HTTP 409) *openstack registered limit set --default-limit 91 --resource-name snapshot --service glance 64c2e97fbe904b888544ffdcab21989b Conflict occurred attempting to store registered_limit - Duplicate entry. (HTTP 409) Change-Id: I9e78a6250567cd981adde96946818bb016760a49 --- openstackclient/identity/v3/registered_limit.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/openstackclient/identity/v3/registered_limit.py b/openstackclient/identity/v3/registered_limit.py index 72e07297e6..2bf069048d 100644 --- a/openstackclient/identity/v3/registered_limit.py +++ b/openstackclient/identity/v3/registered_limit.py @@ -186,12 +186,18 @@ def get_parser(self, prog_name): parser.add_argument( '--service', metavar='', - help=_('Service responsible for the resource to limit'), + help=_('Service to be updated responsible for the resource to ' + 'limit. Either --service, --resource-name or --region must ' + 'be different than existing value otherwise it will be ' + 'duplicate entry') ) parser.add_argument( '--resource-name', metavar='', - help=_('The name of the resource to limit'), + help=_('Resource to be updated responsible for the resource to ' + 'limit. Either --service, --resource-name or --region must ' + 'be different than existing value otherwise it will be ' + 'duplicate entry'), ) parser.add_argument( '--default-limit', @@ -202,12 +208,15 @@ def get_parser(self, prog_name): parser.add_argument( '--description', metavar='', - help=_('Description of the registered limit'), + help=_('Description to update of the registered limit'), ) parser.add_argument( '--region', metavar='', - help=_('Region for the registered limit to affect.'), + help=_('Region for the registered limit to affect. Either ' + '--service, --resource-name or --region must be ' + 'different than existing value otherwise it will be ' + 'duplicate entry'), ) return parser From b90c780d2b99a91dd479bcc5f20caddcfb652f76 Mon Sep 17 00:00:00 2001 From: Sean McGinnis Date: Tue, 23 Oct 2018 10:36:34 -0500 Subject: [PATCH 2001/3095] Add volume backup import/export commands This adds commands to import and export volume backup records so they can be imported and restored on other Cinder instances or to the original instance if the service or database has been lost and had to be rebuilt. I know this is a commonly used process by some users, so it would be good to have this functionality in osc so they do not have to switch clients. More details about the export and import process can be found here: https://docs.openstack.org/cinder/latest/admin/blockstorage-volume-backups-export-import.html Change-Id: Ic95f87b36a416a2b50cb2193fd5759ab59336975 Signed-off-by: Sean McGinnis --- .../cli/command-objects/volume-backup.rst | 198 +----------------- doc/source/cli/commands.rst | 2 + doc/source/cli/data/cinder.csv | 4 +- openstackclient/tests/unit/volume/v2/fakes.py | 29 +++ .../unit/volume/v2/test_backup_record.py | 114 ++++++++++ openstackclient/volume/v2/backup_record.py | 82 ++++++++ ...volume-backup-record-9f5987c45e294dc6.yaml | 15 ++ setup.cfg | 3 + 8 files changed, 250 insertions(+), 197 deletions(-) create mode 100644 openstackclient/tests/unit/volume/v2/test_backup_record.py create mode 100644 openstackclient/volume/v2/backup_record.py create mode 100644 releasenotes/notes/volume-backup-record-9f5987c45e294dc6.yaml diff --git a/doc/source/cli/command-objects/volume-backup.rst b/doc/source/cli/command-objects/volume-backup.rst index 585f47d464..632e215e2b 100644 --- a/doc/source/cli/command-objects/volume-backup.rst +++ b/doc/source/cli/command-objects/volume-backup.rst @@ -2,200 +2,8 @@ volume backup ============= -Block Storage v1, v2 +Volume v1, v2 -volume backup create --------------------- +.. autoprogram-cliff:: openstack.volume.v2 + :command: volume backup * -Create new volume backup - -.. program:: volume backup create -.. code:: bash - - openstack volume backup create - [--container ] - [--name ] - [--description ] - [--snapshot ] - [--force] - [--incremental] - - -.. option:: --container - - Optional backup container name - -.. option:: --name - - Name of the backup - -.. option:: --description - - Description of the backup - -.. option:: --snapshot - - Snapshot to backup (name or ID) - - *Volume version 2 only* - -.. option:: --force - - Allow to back up an in-use volume - - *Volume version 2 only* - -.. option:: --incremental - - Perform an incremental backup - - *Volume version 2 only* - -.. _volume_backup_create-backup: -.. describe:: - - Volume to backup (name or ID) - -volume backup delete --------------------- - -Delete volume backup(s) - -.. program:: volume backup delete -.. code:: bash - - openstack volume backup delete - [--force] - [ ...] - -.. option:: --force - - Allow delete in state other than error or available - - *Volume version 2 only* - -.. _volume_backup_delete-backup: -.. describe:: - - Backup(s) to delete (name or ID) - -volume backup list ------------------- - -List volume backups - -.. program:: volume backup list -.. code:: bash - - openstack volume backup list - [--long] - [--name ] - [--status ] - [--volume ] - [--marker ] - [--limit ] - [--all-projects] - -.. _volume_backup_list-backup: -.. option:: --long - - List additional fields in output - -.. option:: --name - - Filters results by the backup name - -.. option:: --status - - Filters results by the backup status - ('creating', 'available', 'deleting', 'error', 'restoring' or 'error_restoring') - -.. option:: --volume - - Filters results by the volume which they backup (name or ID)" - -.. option:: --marker - - The last backup of the previous page (name or ID) - - *Volume version 2 only* - -.. option:: --limit - - Maximum number of backups to display - - *Volume version 2 only* - -.. option:: --all-projects - - Include all projects (admin only) - -volume backup restore ---------------------- - -Restore volume backup - -.. program:: volume backup restore -.. code:: bash - - openstack volume backup restore - - - -.. _volume_backup_restore-backup: -.. describe:: - - Backup to restore (name or ID) - -.. describe:: - - Volume to restore to (name or ID) - -volume backup set ------------------ - -Set volume backup properties - -.. program:: volume backup set -.. code:: bash - - openstack volume backup set - [--name ] - [--description ] - [--state ] - - -.. option:: --name - - New backup name - -.. option:: --description - - New backup description - -.. option:: --state - - New backup state ("available" or "error") (admin only) - (This option simply changes the state of the backup in the database with - no regard to actual status, exercise caution when using) - -.. _backup_set-volume-backup: -.. describe:: - - Backup to modify (name or ID) - -volume backup show ------------------- - -Display volume backup details - -.. program:: volume backup show -.. code:: bash - - openstack volume backup show - - -.. _volume_backup_show-backup: -.. describe:: - - Backup to display (name or ID) diff --git a/doc/source/cli/commands.rst b/doc/source/cli/commands.rst index d7c9124042..cdd5e63c95 100644 --- a/doc/source/cli/commands.rst +++ b/doc/source/cli/commands.rst @@ -158,6 +158,8 @@ referring to both Compute and Volume quotas. * ``volume backup``: (**Volume**) backup for volumes * ``volume backend capability``: (**volume**) volume backend storage capabilities * ``volume backend pool``: (**volume**) volume backend storage pools +* ``volume backup record``: (**Volume**) volume record that can be imported or exported +* ``volume backend``: (**volume**) volume backend storage * ``volume host``: (**Volume**) the physical computer for volumes * ``volume qos``: (**Volume**) quality-of-service (QoS) specification for volumes * ``volume snapshot``: (**Volume**) a point-in-time copy of a volume diff --git a/doc/source/cli/data/cinder.csv b/doc/source/cli/data/cinder.csv index cc8ef2d91e..22fb84cffa 100644 --- a/doc/source/cli/data/cinder.csv +++ b/doc/source/cli/data/cinder.csv @@ -2,8 +2,8 @@ absolute-limits,limits show --absolute,Lists absolute limits for a user. availability-zone-list,availability zone list --volume,Lists all availability zones. backup-create,volume backup create,Creates a volume backup. backup-delete,volume backup delete,Removes a backup. -backup-export,volume backup export,Export backup metadata record. -backup-import,volume backup import,Import backup metadata record. +backup-export,volume backup record export,Export backup metadata record. +backup-import,volume backup record import,Import backup metadata record. backup-list,volume backup list,Lists all backups. backup-reset-state,volume backup set --state,Explicitly updates the backup state. backup-restore,volume backup restore,Restores a backup. diff --git a/openstackclient/tests/unit/volume/v2/fakes.py b/openstackclient/tests/unit/volume/v2/fakes.py index 59b08d0b90..c245cbf66b 100644 --- a/openstackclient/tests/unit/volume/v2/fakes.py +++ b/openstackclient/tests/unit/volume/v2/fakes.py @@ -596,6 +596,35 @@ def get_backups(backups=None, count=2): return mock.Mock(side_effect=backups) + @staticmethod + def create_backup_record(): + """Gets a fake backup record for a given backup. + + :return: An "exported" backup record. + """ + + return { + 'backup_service': 'cinder.backup.drivers.swift.SwiftBackupDriver', + 'backup_url': 'eyJzdGF0dXMiOiAiYXZh', + } + + @staticmethod + def import_backup_record(): + """Creates a fake backup record import response from a backup. + + :return: The fake backup object that was encoded. + """ + return { + 'backup': { + 'id': 'backup.id', + 'name': 'backup.name', + 'links': [ + {'href': 'link1', 'rel': 'self'}, + {'href': 'link2', 'rel': 'bookmark'}, + ], + }, + } + class FakeConsistencyGroup(object): """Fake one or more consistency group.""" diff --git a/openstackclient/tests/unit/volume/v2/test_backup_record.py b/openstackclient/tests/unit/volume/v2/test_backup_record.py new file mode 100644 index 0000000000..0e24174c5e --- /dev/null +++ b/openstackclient/tests/unit/volume/v2/test_backup_record.py @@ -0,0 +1,114 @@ +# +# 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.unit.volume.v2 import fakes as volume_fakes +from openstackclient.volume.v2 import backup_record + + +class TestBackupRecord(volume_fakes.TestVolume): + + def setUp(self): + super(TestBackupRecord, self).setUp() + + self.backups_mock = self.app.client_manager.volume.backups + self.backups_mock.reset_mock() + + +class TestBackupRecordExport(TestBackupRecord): + + new_backup = volume_fakes.FakeBackup.create_one_backup( + attrs={'volume_id': 'a54708a2-0388-4476-a909-09579f885c25'}) + new_record = volume_fakes.FakeBackup.create_backup_record() + + def setUp(self): + super(TestBackupRecordExport, self).setUp() + + self.backups_mock.export_record.return_value = self.new_record + self.backups_mock.get.return_value = self.new_backup + + # Get the command object to mock + self.cmd = backup_record.ExportBackupRecord(self.app, None) + + def test_backup_export_table(self): + arglist = [ + self.new_backup.name, + ] + verifylist = [ + ("backup", self.new_backup.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + parsed_args.formatter = 'table' + columns, __ = self.cmd.take_action(parsed_args) + + self.backups_mock.export_record.assert_called_with( + self.new_backup.id, + ) + + expected_columns = ('Backup Service', 'Metadata') + self.assertEqual(columns, expected_columns) + + def test_backup_export_json(self): + arglist = [ + self.new_backup.name, + ] + verifylist = [ + ("backup", self.new_backup.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + parsed_args.formatter = 'json' + columns, __ = self.cmd.take_action(parsed_args) + + self.backups_mock.export_record.assert_called_with( + self.new_backup.id, + ) + + expected_columns = ('backup_service', 'backup_url') + self.assertEqual(columns, expected_columns) + + +class TestBackupRecordImport(TestBackupRecord): + + new_backup = volume_fakes.FakeBackup.create_one_backup( + attrs={'volume_id': 'a54708a2-0388-4476-a909-09579f885c25'}) + new_import = volume_fakes.FakeBackup.import_backup_record() + + def setUp(self): + super(TestBackupRecordImport, self).setUp() + + self.backups_mock.import_record.return_value = self.new_import + + # Get the command object to mock + self.cmd = backup_record.ImportBackupRecord(self.app, None) + + def test_backup_import(self): + arglist = [ + "cinder.backup.drivers.swift.SwiftBackupDriver", + "fake_backup_record_data", + ] + verifylist = [ + ("backup_service", + "cinder.backup.drivers.swift.SwiftBackupDriver"), + ("backup_metadata", "fake_backup_record_data"), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, __ = self.cmd.take_action(parsed_args) + + self.backups_mock.import_record.assert_called_with( + "cinder.backup.drivers.swift.SwiftBackupDriver", + "fake_backup_record_data", + ) + self.assertEqual(columns, ('backup',)) diff --git a/openstackclient/volume/v2/backup_record.py b/openstackclient/volume/v2/backup_record.py new file mode 100644 index 0000000000..f491803272 --- /dev/null +++ b/openstackclient/volume/v2/backup_record.py @@ -0,0 +1,82 @@ +# +# 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 Backup 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 ExportBackupRecord(command.ShowOne): + _description = _('Export volume backup details. Backup information can be ' + 'imported into a new service instance to be able to ' + 'restore.') + + def get_parser(self, prog_name): + parser = super(ExportBackupRecord, self).get_parser(prog_name) + parser.add_argument( + "backup", + metavar="", + help=_("Backup to export (name or ID)") + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + backup = utils.find_resource(volume_client.backups, parsed_args.backup) + backup_data = volume_client.backups.export_record(backup.id) + + # We only want to show "friendly" display names, but also want to keep + # json structure compatibility with cinderclient + if parsed_args.formatter == 'table': + backup_data['Backup Service'] = backup_data.pop('backup_service') + backup_data['Metadata'] = backup_data.pop('backup_url') + + return zip(*sorted(six.iteritems(backup_data))) + + +class ImportBackupRecord(command.ShowOne): + _description = _('Import volume backup details. Exported backup details ' + 'contain the metadata necessary to restore to a new or ' + 'rebuilt service instance') + + def get_parser(self, prog_name): + parser = super(ImportBackupRecord, self).get_parser(prog_name) + parser.add_argument( + "backup_service", + metavar="", + help=_("Backup service containing the backup.") + ) + parser.add_argument( + "backup_metadata", + metavar="", + help=_("Encoded backup metadata from export.") + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + backup_data = volume_client.backups.import_record( + parsed_args.backup_service, + parsed_args.backup_metadata) + backup_data.pop('links', None) + return zip(*sorted(six.iteritems(backup_data))) diff --git a/releasenotes/notes/volume-backup-record-9f5987c45e294dc6.yaml b/releasenotes/notes/volume-backup-record-9f5987c45e294dc6.yaml new file mode 100644 index 0000000000..955e99714b --- /dev/null +++ b/releasenotes/notes/volume-backup-record-9f5987c45e294dc6.yaml @@ -0,0 +1,15 @@ +--- +features: + - | + Add ``openstack volume backup record export `` command that + provides encrypted backup record metadata that can then be used to import + and restore on a different or rebuilt block storage service. Information + about volume backup export and import can be found in the `Cinder block + storage service documentation `_. + - | + Add ``openstack volume backup record import + `` command that can be used to import and restore on a + different or rebuilt block storage service. Information about volume backup + export and import can be found in the `Cinder block storage service + documentation `_. + diff --git a/setup.cfg b/setup.cfg index 73c5fde933..818b6ef25e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -629,6 +629,9 @@ openstack.volume.v2 = volume_backup_set = openstackclient.volume.v2.backup:SetVolumeBackup volume_backup_show = openstackclient.volume.v2.backup:ShowVolumeBackup + volume_backup_record_export = openstackclient.volume.v2.backup_record:ExportBackupRecord + volume_backup_record_import = openstackclient.volume.v2.backup_record:ImportBackupRecord + volume_backend_capability_show = openstackclient.volume.v2.volume_backend:ShowCapability volume_backend_pool_list = openstackclient.volume.v2.volume_backend:ListPool From 4f66f66b6c80206e3d432f74c3763ffe2e5c2949 Mon Sep 17 00:00:00 2001 From: liuyamin Date: Tue, 13 Nov 2018 17:04:59 +0800 Subject: [PATCH 2002/3095] Fix i18n issue This patch fix some i18n issues in the files vapi/compute_v2.py. Change-Id: Ic4da472ca585a35ce64512cf0e72e2fe9d4c9d6e --- openstackclient/api/compute_v2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/api/compute_v2.py b/openstackclient/api/compute_v2.py index 0c89e91266..7dc4e4468a 100644 --- a/openstackclient/api/compute_v2.py +++ b/openstackclient/api/compute_v2.py @@ -52,7 +52,7 @@ def _check_integer(self, value, msg=None): value = int(value) except (TypeError, ValueError): if not msg: - msg = "%s is not an integer" % value + msg = _("%s is not an integer") % value raise InvalidValue(msg) return value From 5bec3b7e3b7d4fed1bedd5a848294f550d30ed47 Mon Sep 17 00:00:00 2001 From: liuyamin Date: Wed, 14 Nov 2018 11:19:25 +0800 Subject: [PATCH 2003/3095] Replace assertEqual(True/False, expr) with assertTrue/assertFalse In some cases, If the result of expr is a boolen value, we shoud use assertTrue/assertFalse to instead. Because it is clear and simple. Change-Id: I53b345fc3915a7b0e737e9dd4d58fe09c746d61c --- .../functional/compute/v2/test_flavor.py | 6 +- .../functional/identity/v2/test_project.py | 2 +- .../network/v2/test_address_scope.py | 25 ++----- .../functional/network/v2/test_network.py | 71 +++++-------------- .../network/v2/test_network_flavor.py | 10 +-- .../network/v2/test_network_flavor_profile.py | 30 ++------ .../network/v2/test_network_meter.py | 28 ++------ .../functional/network/v2/test_router.py | 5 +- 8 files changed, 41 insertions(+), 136 deletions(-) diff --git a/openstackclient/tests/functional/compute/v2/test_flavor.py b/openstackclient/tests/functional/compute/v2/test_flavor.py index eefd3fabd0..c274adf2e7 100644 --- a/openstackclient/tests/functional/compute/v2/test_flavor.py +++ b/openstackclient/tests/functional/compute/v2/test_flavor.py @@ -112,8 +112,7 @@ def test_flavor_list(self): 0, cmd_output["disk"], ) - self.assertEqual( - False, + self.assertFalse( cmd_output["os-flavor-access:is_public"], ) self.assertEqual( @@ -199,8 +198,7 @@ def test_flavor_properties(self): 20, cmd_output["disk"], ) - self.assertEqual( - False, + self.assertFalse( cmd_output["os-flavor-access:is_public"], ) self.assertEqual( diff --git a/openstackclient/tests/functional/identity/v2/test_project.py b/openstackclient/tests/functional/identity/v2/test_project.py index b6222a1bbf..38777c36c7 100644 --- a/openstackclient/tests/functional/identity/v2/test_project.py +++ b/openstackclient/tests/functional/identity/v2/test_project.py @@ -72,7 +72,7 @@ def test_project_set(self): self.assert_show_fields(items, fields) project = self.parse_show_as_object(raw_output) self.assertEqual(new_project_name, project['name']) - self.assertEqual('False', project['enabled']) + self.assertFalse(project['enabled']) self.assertEqual("k0='v0'", project['properties']) def test_project_show(self): diff --git a/openstackclient/tests/functional/network/v2/test_address_scope.py b/openstackclient/tests/functional/network/v2/test_address_scope.py index ebd2ba86c6..8a99ec5e61 100644 --- a/openstackclient/tests/functional/network/v2/test_address_scope.py +++ b/openstackclient/tests/functional/network/v2/test_address_scope.py @@ -42,10 +42,7 @@ def test_address_scope_delete(self): cmd_output['name'], ) # Check the default values - self.assertEqual( - False, - cmd_output['shared'], - ) + self.assertFalse(cmd_output['shared']) name2 = uuid.uuid4().hex cmd_output = json.loads(self.openstack( @@ -80,10 +77,7 @@ def test_address_scope_list(self): 4, cmd_output['ip_version'], ) - self.assertEqual( - True, - cmd_output['shared'], - ) + self.assertTrue(cmd_output['shared']) name2 = uuid.uuid4().hex cmd_output = json.loads(self.openstack( @@ -101,10 +95,7 @@ def test_address_scope_list(self): 6, cmd_output['ip_version'], ) - self.assertEqual( - False, - cmd_output['shared'], - ) + self.assertFalse(cmd_output['shared']) # Test list cmd_output = json.loads(self.openstack( @@ -149,10 +140,7 @@ def test_address_scope_set(self): 4, cmd_output['ip_version'], ) - self.assertEqual( - False, - cmd_output['shared'], - ) + self.assertFalse(cmd_output['shared']) raw_output = self.openstack( 'address scope set ' + @@ -174,7 +162,4 @@ def test_address_scope_set(self): 4, cmd_output['ip_version'], ) - self.assertEqual( - True, - cmd_output['shared'], - ) + self.assertTrue(cmd_output['shared']) diff --git a/openstackclient/tests/functional/network/v2/test_network.py b/openstackclient/tests/functional/network/v2/test_network.py index 9cef135fe7..1a74496968 100644 --- a/openstackclient/tests/functional/network/v2/test_network.py +++ b/openstackclient/tests/functional/network/v2/test_network.py @@ -70,8 +70,7 @@ def test_network_create_compute(self): '1.2.4.0/28', cmd_output["cidr"], ) - self.assertEqual( - True, + self.assertTrue( cmd_output["share_address"], ) @@ -124,8 +123,7 @@ def test_network_create_network(self): 'UP', cmd_output["admin_state_up"], ) - self.assertEqual( - False, + self.assertFalse( cmd_output["shared"], ) self.assertEqual( @@ -236,21 +234,14 @@ def test_network_list(self): 'UP', cmd_output["admin_state_up"], ) - self.assertEqual( - False, - cmd_output["shared"], - ) + self.assertFalse(cmd_output["shared"]) self.assertEqual( 'Internal', cmd_output["router:external"], ) - self.assertEqual( - False, - cmd_output["is_default"], - ) - self.assertEqual( - True, - cmd_output["port_security_enabled"], + self.assertFalse(cmd_output["is_default"]) + self.assertTrue( + cmd_output["port_security_enabled"] ) else: self.assertEqual( @@ -278,27 +269,15 @@ def test_network_list(self): 'DOWN', cmd_output["admin_state_up"], ) - self.assertEqual( - True, - cmd_output["shared"], - ) - self.assertEqual( - False, - cmd_output["is_default"], - ) - self.assertEqual( - True, - cmd_output["port_security_enabled"], - ) + self.assertTrue(cmd_output["shared"]) + self.assertFalse(cmd_output["is_default"]) + self.assertTrue(cmd_output["port_security_enabled"]) else: self.assertEqual( '4.5.6.0/28', cmd_output["cidr"], ) - self.assertEqual( - True, - cmd_output["share_address"], - ) + self.assertTrue(cmd_output["share_address"]) # Test list cmd_output = json.loads(self.openstack( @@ -422,22 +401,15 @@ def test_network_set(self): 'UP', cmd_output["admin_state_up"], ) - self.assertEqual( - False, - cmd_output["shared"], - ) + self.assertFalse(cmd_output["shared"]) self.assertEqual( 'Internal', cmd_output["router:external"], ) - self.assertEqual( - False, - cmd_output["is_default"], - ) - self.assertEqual( - True, - cmd_output["port_security_enabled"], + self.assertFalse(cmd_output["is_default"]) + self.assertTrue( + cmd_output["port_security_enabled"] ) raw_output = self.openstack( @@ -463,19 +435,12 @@ def test_network_set(self): 'DOWN', cmd_output["admin_state_up"], ) - self.assertEqual( - True, - cmd_output["shared"], - ) + self.assertTrue(cmd_output["shared"]) self.assertEqual( 'External', cmd_output["router:external"], ) - self.assertEqual( - False, - cmd_output["is_default"], - ) - self.assertEqual( - False, - cmd_output["port_security_enabled"], + self.assertFalse(cmd_output["is_default"]) + self.assertFalse( + cmd_output["port_security_enabled"] ) diff --git a/openstackclient/tests/functional/network/v2/test_network_flavor.py b/openstackclient/tests/functional/network/v2/test_network_flavor.py index ba3de2cdb5..cf68a096a2 100644 --- a/openstackclient/tests/functional/network/v2/test_network_flavor.py +++ b/openstackclient/tests/functional/network/v2/test_network_flavor.py @@ -87,10 +87,7 @@ def test_network_flavor_delete(self): name1, cmd_output['name'], ) - self.assertEqual( - True, - cmd_output['enabled'], - ) + self.assertTrue(cmd_output['enabled']) self.assertEqual( 'testdescription', cmd_output['description'], @@ -105,10 +102,7 @@ def test_network_flavor_delete(self): name2, cmd_output['name'], ) - self.assertEqual( - False, - cmd_output['enabled'], - ) + self.assertFalse(cmd_output['enabled']) self.assertEqual( 'testdescription1', cmd_output['description'], diff --git a/openstackclient/tests/functional/network/v2/test_network_flavor_profile.py b/openstackclient/tests/functional/network/v2/test_network_flavor_profile.py index 2207c84749..5b5ec926de 100644 --- a/openstackclient/tests/functional/network/v2/test_network_flavor_profile.py +++ b/openstackclient/tests/functional/network/v2/test_network_flavor_profile.py @@ -35,10 +35,7 @@ def test_network_flavor_profile_create(self): )) ID = json_output.get('id') self.assertIsNotNone(ID) - self.assertEqual( - True, - json_output.get('enabled'), - ) + self.assertTrue(json_output.get('enabled')) self.assertEqual( 'fakedescription', json_output.get('description'), @@ -61,10 +58,7 @@ def test_network_flavor_profile_list(self): )) ID1 = json_output.get('id') self.assertIsNotNone(ID1) - self.assertEqual( - True, - json_output.get('enabled'), - ) + self.assertTrue(json_output.get('enabled')) self.assertEqual( 'fakedescription', json_output.get('description'), @@ -82,10 +76,7 @@ def test_network_flavor_profile_list(self): )) ID2 = json_output.get('id') self.assertIsNotNone(ID2) - self.assertEqual( - False, - json_output.get('enabled'), - ) + self.assertFalse(json_output.get('enabled')) self.assertEqual( 'fakedescription', json_output.get('description'), @@ -120,10 +111,7 @@ def test_network_flavor_profile_set(self): )) ID = json_output_1.get('id') self.assertIsNotNone(ID) - self.assertEqual( - True, - json_output_1.get('enabled'), - ) + self.assertTrue(json_output_1.get('enabled')) self.assertEqual( 'fakedescription', json_output_1.get('description'), @@ -138,10 +126,7 @@ def test_network_flavor_profile_set(self): json_output = json.loads(self.openstack( 'network flavor profile show -f json ' + ID )) - self.assertEqual( - False, - json_output.get('enabled'), - ) + self.assertFalse(json_output.get('enabled')) self.assertEqual( 'fakedescription', json_output.get('description'), @@ -171,10 +156,7 @@ def test_network_flavor_profile_show(self): ID, json_output["id"], ) - self.assertEqual( - True, - json_output["enabled"], - ) + self.assertTrue(json_output["enabled"]) self.assertEqual( 'fakedescription', json_output["description"], diff --git a/openstackclient/tests/functional/network/v2/test_network_meter.py b/openstackclient/tests/functional/network/v2/test_network_meter.py index 7f6da28d3c..0a8b89cae0 100644 --- a/openstackclient/tests/functional/network/v2/test_network_meter.py +++ b/openstackclient/tests/functional/network/v2/test_network_meter.py @@ -48,10 +48,7 @@ def test_meter_delete(self): json_output.get('name'), ) # Check if default shared values - self.assertEqual( - False, - json_output.get('shared'), - ) + self.assertFalse(json_output.get('shared')) self.assertEqual( 'fakedescription', json_output.get('description'), @@ -67,10 +64,7 @@ def test_meter_delete(self): json_output_2.get('name'), ) # Check if default shared values - self.assertEqual( - False, - json_output_2.get('shared'), - ) + self.assertFalse(json_output_2.get('shared')) self.assertEqual( 'fakedescription', json_output_2.get('description'), @@ -99,10 +93,7 @@ def test_meter_list(self): 'Test1', json_output.get('description'), ) - self.assertEqual( - True, - json_output.get('shared'), - ) + self.assertTrue(json_output.get('shared')) name2 = uuid.uuid4().hex json_output_2 = json.loads(self.openstack( @@ -117,8 +108,7 @@ def test_meter_list(self): 'Test2', json_output_2.get('description'), ) - self.assertEqual( - False, + self.assertFalse( json_output_2.get('shared'), ) @@ -143,10 +133,7 @@ def test_meter_show(self): json_output = json.loads(self.openstack( 'network meter show -f json ' + meter_id )) - self.assertEqual( - False, - json_output.get('shared'), - ) + self.assertFalse(json_output.get('shared')) self.assertEqual( 'fakedescription', json_output.get('description'), @@ -164,10 +151,7 @@ def test_meter_show(self): meter_id, json_output.get('id'), ) - self.assertEqual( - False, - json_output.get('shared'), - ) + self.assertFalse(json_output.get('shared')) self.assertEqual( 'fakedescription', json_output.get('description'), diff --git a/openstackclient/tests/functional/network/v2/test_router.py b/openstackclient/tests/functional/network/v2/test_router.py index 95c5a96f8b..9d5beff059 100644 --- a/openstackclient/tests/functional/network/v2/test_router.py +++ b/openstackclient/tests/functional/network/v2/test_router.py @@ -247,10 +247,7 @@ def test_router_set_show_unset(self): 'router show -f json ' + new_name )) - self.assertEqual( - True, - cmd_output["distributed"], - ) + self.assertTrue(cmd_output["distributed"]) self.assertIsNotNone(cmd_output["external_gateway_info"]) # Test unset From 8120cb8b55091d9027c7b8ff519b82a87e7d6b17 Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Wed, 6 Dec 2017 09:33:23 -0600 Subject: [PATCH 2004/3095] Use devstack functional base job We extracted some of our functional base job to the devstack repo. Consume it. Change-Id: I11e6f9dab935e4b2cd16228f031f7e0adb3a6c89 Depends-On: I84de60181cb88574e341ff83cd4857cce241f2dd --- .zuul.yaml | 12 +----------- playbooks/osc-devstack/post.yaml | 4 ---- playbooks/osc-devstack/pre.yaml | 8 -------- playbooks/osc-devstack/run.yaml | 5 ----- 4 files changed, 1 insertion(+), 28 deletions(-) delete mode 100644 playbooks/osc-devstack/post.yaml delete mode 100644 playbooks/osc-devstack/pre.yaml delete mode 100644 playbooks/osc-devstack/run.yaml diff --git a/.zuul.yaml b/.zuul.yaml index 7cf30cf138..d646a0f766 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -62,16 +62,9 @@ - job: name: osc-functional-devstack-base - parent: devstack + parent: devstack-tox-functional description: | Base job for devstack-based functional tests - pre-run: playbooks/osc-devstack/pre.yaml - run: playbooks/osc-devstack/run.yaml - post-run: playbooks/osc-devstack/post.yaml - required-projects: - - name: openstack/swift - roles: - - zuul: openstack-infra/devstack timeout: 9000 irrelevant-files: - ^.*\.rst$ @@ -79,7 +72,6 @@ - ^releasenotes/.*$ vars: devstack_localrc: - SWIFT_HASH: '1234123412341234' LIBS_FROM_GIT: python-openstackclient # NOTE(dtroyer): OSC needs to support Image v1 for a while yet so re-enable GLANCE_V1_ENABLED: true @@ -99,7 +91,6 @@ ceilometer-anotification: false ceilometer-api: false ceilometer-collector: false - horizon: false s-account: true s-container: true s-object: true @@ -107,7 +98,6 @@ osc_environment: PYTHONUNBUFFERED: 'true' OS_CLOUD: devstack-admin - tox_install_siblings: false zuul_work_dir: src/git.openstack.org/openstack/python-openstackclient # The Neutron bits are here rather than in osc-functional-devstack-base to diff --git a/playbooks/osc-devstack/post.yaml b/playbooks/osc-devstack/post.yaml deleted file mode 100644 index 7f0cb19824..0000000000 --- a/playbooks/osc-devstack/post.yaml +++ /dev/null @@ -1,4 +0,0 @@ -- hosts: all - roles: - - fetch-tox-output - - fetch-subunit-output diff --git a/playbooks/osc-devstack/pre.yaml b/playbooks/osc-devstack/pre.yaml deleted file mode 100644 index 3ec41c9cbd..0000000000 --- a/playbooks/osc-devstack/pre.yaml +++ /dev/null @@ -1,8 +0,0 @@ -- hosts: all - roles: - - run-devstack - - role: bindep - bindep_profile: test - bindep_dir: "{{ zuul_work_dir }}" - - test-setup - - ensure-tox diff --git a/playbooks/osc-devstack/run.yaml b/playbooks/osc-devstack/run.yaml deleted file mode 100644 index d1101dd4a1..0000000000 --- a/playbooks/osc-devstack/run.yaml +++ /dev/null @@ -1,5 +0,0 @@ -- hosts: all - environment: - OS_CLOUD: devstack-admin - roles: - - tox From 029c148b2979eecc39bee36d79f523f4777192d8 Mon Sep 17 00:00:00 2001 From: qingszhao Date: Fri, 30 Nov 2018 07:13:39 +0000 Subject: [PATCH 2005/3095] Add Python 3.6 classifier to setup.cfg Change-Id: Id723040f895f06527d46755dd408dec16200c001 --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index 818b6ef25e..05ff1268e4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -17,6 +17,7 @@ classifier = Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.5 + Programming Language :: Python :: 3.6 [files] packages = From 8be53a50e5278281640b5023c4d8c4b28da22cb9 Mon Sep 17 00:00:00 2001 From: sunjia Date: Mon, 3 Dec 2018 22:11:36 -0500 Subject: [PATCH 2006/3095] Change openstack-dev to openstack-discuss Mailinglists have been updated. Openstack-discuss replaces openstack-dev. Change-Id: I7c7fdfb71348f77aa6063ec99b3c4a90bde21d06 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 05ff1268e4..48c2247ff7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -4,7 +4,7 @@ summary = OpenStack Command-line Client description-file = README.rst author = OpenStack -author-email = openstack-dev@lists.openstack.org +author-email = openstack-discuss@lists.openstack.org home-page = https://docs.openstack.org/python-openstackclient/latest/ classifier = Environment :: OpenStack From c82f4237e59dd61f180b2bfbd3383c6540cd6cef Mon Sep 17 00:00:00 2001 From: Hongbin Lu Date: Fri, 27 Jul 2018 16:41:45 -0400 Subject: [PATCH 2007/3095] Support enable/disable uplink status propagation Add options to enable/disable uplink status propagation on creating a neutron port. Related patches: * neutron: https://review.openstack.org/#/c/571899/ * openstacksdk: https://review.openstack.org/#/c/586687/ Depends-On: https://review.openstack.org/#/c/586687/ Change-Id: I095a98fc5f5aee62d979a16b3cd79d91ec3b9ddb Related-Bug: #1722720 --- doc/source/cli/command-objects/port.rst | 9 +++++ openstackclient/network/v2/port.py | 18 +++++++++ .../tests/unit/network/v2/fakes.py | 3 ++ .../tests/unit/network/v2/test_port.py | 39 +++++++++++++++++++ ...k_status_propagation-4d37452bcf03e0f8.yaml | 5 +++ 5 files changed, 74 insertions(+) create mode 100644 releasenotes/notes/support-uplink_status_propagation-4d37452bcf03e0f8.yaml diff --git a/doc/source/cli/command-objects/port.rst b/doc/source/cli/command-objects/port.rst index bb037fa3e5..bc9f5dde02 100644 --- a/doc/source/cli/command-objects/port.rst +++ b/doc/source/cli/command-objects/port.rst @@ -26,6 +26,7 @@ Create new port [--binding-profile ] [--host ] [--enable | --disable] + [--enable-uplink-status-propagation | --disable-uplink-status-propagation] [--mac-address ] [--security-group | --no-security-group] [--dns-domain ] @@ -87,6 +88,14 @@ Create new port Disable port +.. option:: --enable-uplink-status-propagation + + Enable uplink status propagate + +.. option:: --disable-uplink-status-propagation + + Disable uplink status propagate (default) + .. option:: --mac-address MAC address of this port diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 0d276d9da4..1001e6cf41 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -163,6 +163,13 @@ def _get_attrs(client_manager, parsed_args): attrs['qos_policy_id'] = client_manager.network.find_qos_policy( parsed_args.qos_policy, ignore_missing=False).id + if ('enable_uplink_status_propagation' in parsed_args and + parsed_args.enable_uplink_status_propagation): + attrs['propagate_uplink_status'] = True + if ('disable_uplink_status_propagation' in parsed_args and + parsed_args.disable_uplink_status_propagation): + attrs['propagate_uplink_status'] = False + return attrs @@ -349,6 +356,17 @@ def get_parser(self, prog_name): action='store_true', help=_("Disable port") ) + uplink_status_group = parser.add_mutually_exclusive_group() + uplink_status_group.add_argument( + '--enable-uplink-status-propagation', + action='store_true', + help=_("Enable uplink status propagate") + ) + uplink_status_group.add_argument( + '--disable-uplink-status-propagation', + action='store_true', + help=_("Disable uplink status propagate (default)") + ) parser.add_argument( '--project', metavar='', diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index aec7402f94..28e92d1196 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -581,6 +581,7 @@ def create_one_port(attrs=None): 'tenant_id': 'project-id-' + uuid.uuid4().hex, 'qos_policy_id': 'qos-policy-id-' + uuid.uuid4().hex, 'tags': [], + 'uplink_status_propagation': False, } # Overwrite default attributes. @@ -600,6 +601,8 @@ def create_one_port(attrs=None): port.project_id = port_attrs['tenant_id'] port.security_group_ids = port_attrs['security_group_ids'] port.qos_policy_id = port_attrs['qos_policy_id'] + port.uplink_status_propagation = port_attrs[ + 'uplink_status_propagation'] return port diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index 78d7fd6ca1..8ac3e54f4a 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -64,6 +64,7 @@ def _get_common_cols_data(fake_port): 'security_group_ids', 'status', 'tags', + 'uplink_status_propagation', ) data = ( @@ -93,6 +94,7 @@ def _get_common_cols_data(fake_port): utils.format_list(fake_port.security_group_ids), fake_port.status, utils.format_list(fake_port.tags), + fake_port.uplink_status_propagation, ) return columns, data @@ -571,6 +573,43 @@ def test_create_with_tags(self): def test_create_with_no_tag(self): self._test_create_with_tag(add_tags=False) + def _test_create_with_uplink_status_propagation(self, enable=True): + arglist = [ + '--network', self._port.network_id, + 'test-port', + ] + if enable: + arglist += ['--enable-uplink-status-propagation'] + else: + arglist += ['--disable-uplink-status-propagation'] + verifylist = [ + ('network', self._port.network_id,), + ('name', 'test-port'), + ] + if enable: + verifylist.append(('enable_uplink_status_propagation', True)) + else: + verifylist.append(('disable_uplink_status_propagation', True)) + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_port.assert_called_once_with(**{ + 'admin_state_up': True, + 'network_id': self._port.network_id, + 'propagate_uplink_status': enable, + 'name': 'test-port', + }) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_with_uplink_status_propagation_enabled(self): + self._test_create_with_uplink_status_propagation(enable=True) + + def test_create_with_uplink_status_propagation_disabled(self): + self._test_create_with_uplink_status_propagation(enable=False) + class TestDeletePort(TestPort): diff --git a/releasenotes/notes/support-uplink_status_propagation-4d37452bcf03e0f8.yaml b/releasenotes/notes/support-uplink_status_propagation-4d37452bcf03e0f8.yaml new file mode 100644 index 0000000000..766e8abbf8 --- /dev/null +++ b/releasenotes/notes/support-uplink_status_propagation-4d37452bcf03e0f8.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``--enable-uplink-status-propagation`` option and + ``--disable-uplink-status-propagation`` option to ``port create`` command. From fd23025227a07e879e0b29c67063401b1d392518 Mon Sep 17 00:00:00 2001 From: LIU Yulong Date: Wed, 18 Jul 2018 15:28:51 +0800 Subject: [PATCH 2008/3095] Supports router gateway IP QoS Adds --qos-policy and --no-qos-policy to `openstack router set`: --qos-policy Attach QoS policy to router gateway IPs --no-qos-policy Remove QoS policy from router gateway IPs Adds --qos-policy to `openstack router unset`: --qos-policy Remove QoS policy from router gateway IPs Partially-Implements blueprint: router-gateway-ip-qos Closes-Bug: #1757044 Change-Id: Ifec3b2cf9bdb59513c8bcd7bd60305506a071192 --- openstackclient/network/v2/router.py | 53 ++++++ .../tests/unit/network/v2/test_router.py | 156 +++++++++++++++++- ...outer-gateway-IP-QoS-c8ba95e180bca05f.yaml | 9 + 3 files changed, 217 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/router-gateway-IP-QoS-c8ba95e180bca05f.yaml diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 746452e40c..a3a4eb756a 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -579,6 +579,17 @@ def get_parser(self, prog_name): action='store_true', help=_("Disable Source NAT on external gateway") ) + qos_policy_group = parser.add_mutually_exclusive_group() + qos_policy_group.add_argument( + '--qos-policy', + metavar='', + help=_("Attach QoS policy to router gateway IPs") + ) + qos_policy_group.add_argument( + '--no-qos-policy', + action='store_true', + help=_("Remove QoS policy from router gateway IPs") + ) _tag.add_tag_option_to_parser_for_set(parser, _('router')) return parser @@ -638,6 +649,27 @@ def take_action(self, parsed_args): ips.append(ip_spec) gateway_info['external_fixed_ips'] = ips attrs['external_gateway_info'] = gateway_info + + if ((parsed_args.qos_policy or parsed_args.no_qos_policy) and + not parsed_args.external_gateway): + try: + original_net_id = obj.external_gateway_info['network_id'] + except (KeyError, TypeError): + msg = (_("You must specify '--external-gateway' or the router " + "must already have an external network in order to " + "set router gateway IP QoS")) + raise exceptions.CommandError(msg) + else: + if not attrs.get('external_gateway_info'): + attrs['external_gateway_info'] = {} + attrs['external_gateway_info']['network_id'] = original_net_id + if parsed_args.qos_policy: + check_qos_id = client.find_qos_policy( + parsed_args.qos_policy, ignore_missing=False).id + attrs['external_gateway_info']['qos_policy_id'] = check_qos_id + + if 'no_qos_policy' in parsed_args and parsed_args.no_qos_policy: + attrs['external_gateway_info']['qos_policy_id'] = None if attrs: client.update_router(obj, **attrs) # tags is a subresource and it needs to be updated separately. @@ -701,6 +733,12 @@ def get_parser(self, prog_name): action='store_true', default=False, help=_("Remove external gateway information from the router")) + parser.add_argument( + '--qos-policy', + action='store_true', + default=False, + help=_("Remove QoS policy from router gateway IPs") + ) parser.add_argument( 'router', metavar="", @@ -713,6 +751,7 @@ def take_action(self, parsed_args): client = self.app.client_manager.network obj = client.find_router(parsed_args.router, ignore_missing=False) tmp_routes = copy.deepcopy(obj.routes) + tmp_external_gateway_info = copy.deepcopy(obj.external_gateway_info) attrs = {} if parsed_args.routes: try: @@ -723,6 +762,20 @@ def take_action(self, parsed_args): msg = (_("Router does not contain route %s") % route) raise exceptions.CommandError(msg) attrs['routes'] = tmp_routes + if parsed_args.qos_policy: + try: + if (tmp_external_gateway_info['network_id'] and + tmp_external_gateway_info['qos_policy_id']): + pass + except (KeyError, TypeError): + msg = _("Router does not have external network or qos policy") + raise exceptions.CommandError(msg) + else: + attrs['external_gateway_info'] = { + 'network_id': tmp_external_gateway_info['network_id'], + 'qos_policy_id': None + } + if parsed_args.external_gateway: attrs['external_gateway_info'] = {} if attrs: diff --git a/openstackclient/tests/unit/network/v2/test_router.py b/openstackclient/tests/unit/network/v2/test_router.py index f383c1ddb7..5102d09102 100644 --- a/openstackclient/tests/unit/network/v2/test_router.py +++ b/openstackclient/tests/unit/network/v2/test_router.py @@ -1113,6 +1113,102 @@ def test_set_with_tags(self): def test_set_with_no_tag(self): self._test_set_tags(with_tags=False) + def test_set_gateway_ip_qos(self): + qos_policy = network_fakes.FakeNetworkQosPolicy.create_one_qos_policy() + self.network.find_qos_policy = mock.Mock(return_value=qos_policy) + arglist = [ + "--external-gateway", self._network.id, + "--qos-policy", qos_policy.id, + self._router.id, + ] + verifylist = [ + ('router', self._router.id), + ('external_gateway', self._network.id), + ('qos_policy', qos_policy.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.network.update_router.assert_called_with( + self._router, **{'external_gateway_info': { + 'network_id': self._network.id, + 'qos_policy_id': qos_policy.id, }}) + self.assertIsNone(result) + + def test_unset_gateway_ip_qos(self): + arglist = [ + "--external-gateway", self._network.id, + "--no-qos-policy", + self._router.id, + ] + verifylist = [ + ('router', self._router.id), + ('external_gateway', self._network.id), + ('no_qos_policy', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.network.update_router.assert_called_with( + self._router, **{'external_gateway_info': { + 'network_id': self._network.id, + 'qos_policy_id': None, }}) + self.assertIsNone(result) + + def test_set_unset_gateway_ip_qos(self): + qos_policy = network_fakes.FakeNetworkQosPolicy.create_one_qos_policy() + self.network.find_qos_policy = mock.Mock(return_value=qos_policy) + arglist = [ + "--external-gateway", self._network.id, + "--qos-policy", qos_policy.id, + "--no-qos-policy", + self._router.id, + ] + verifylist = [ + ('router', self._router.id), + ('external_gateway', self._network.id), + ('qos_policy', qos_policy.id), + ('no_qos_policy', True), + ] + + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_set_gateway_ip_qos_no_gateway(self): + qos_policy = network_fakes.FakeNetworkQosPolicy.create_one_qos_policy() + self.network.find_qos_policy = mock.Mock(return_value=qos_policy) + router = network_fakes.FakeRouter.create_one_router() + self.network.find_router = mock.Mock(return_value=router) + arglist = [ + "--qos-policy", qos_policy.id, + router.id, + ] + verifylist = [ + ('router', router.id), + ('qos_policy', qos_policy.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, parsed_args) + + def test_unset_gateway_ip_qos_no_gateway(self): + qos_policy = network_fakes.FakeNetworkQosPolicy.create_one_qos_policy() + self.network.find_qos_policy = mock.Mock(return_value=qos_policy) + router = network_fakes.FakeRouter.create_one_router() + self.network.find_router = mock.Mock(return_value=router) + arglist = [ + "--no-qos-policy", + router.id, + ] + verifylist = [ + ('router', router.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, parsed_args) + class TestShowRouter(TestRouter): @@ -1201,12 +1297,19 @@ class TestUnsetRouter(TestRouter): def setUp(self): super(TestUnsetRouter, self).setUp() + self.fake_network = network_fakes.FakeNetwork.create_one_network() + self.fake_qos_policy = ( + network_fakes.FakeNetworkQosPolicy.create_one_qos_policy()) self._testrouter = network_fakes.FakeRouter.create_one_router( {'routes': [{"destination": "192.168.101.1/24", "nexthop": "172.24.4.3"}, {"destination": "192.168.101.2/24", "nexthop": "172.24.4.3"}], - 'tags': ['green', 'red'], }) + 'tags': ['green', 'red'], + 'external_gateway_info': { + 'network_id': self.fake_network.id, + 'qos_policy_id': self.fake_qos_policy.id + }}) self.fake_subnet = network_fakes.FakeSubnet.create_one_subnet() self.network.find_router = mock.Mock(return_value=self._testrouter) self.network.update_router = mock.Mock(return_value=None) @@ -1289,3 +1392,54 @@ def test_unset_with_tags(self): def test_unset_with_all_tag(self): self._test_unset_tags(with_tags=False) + + def test_unset_router_qos_policy(self): + arglist = [ + '--qos-policy', + self._testrouter.name, + ] + verifylist = [ + ('qos_policy', True) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + attrs = {'external_gateway_info': {"network_id": self.fake_network.id, + "qos_policy_id": None}} + self.network.update_router.assert_called_once_with( + self._testrouter, **attrs) + self.assertIsNone(result) + + def test_unset_gateway_ip_qos_no_network(self): + qos_policy = network_fakes.FakeNetworkQosPolicy.create_one_qos_policy() + self.network.find_qos_policy = mock.Mock(return_value=qos_policy) + router = network_fakes.FakeRouter.create_one_router() + self.network.find_router = mock.Mock(return_value=router) + arglist = [ + "--qos-policy", + router.id, + ] + verifylist = [ + ('router', router.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, parsed_args) + + def test_unset_gateway_ip_qos_no_qos(self): + qos_policy = network_fakes.FakeNetworkQosPolicy.create_one_qos_policy() + self.network.find_qos_policy = mock.Mock(return_value=qos_policy) + router = network_fakes.FakeRouter.create_one_router( + {"external_gateway_info": {"network_id": "fake-id"}}) + self.network.find_router = mock.Mock(return_value=router) + arglist = [ + "--qos-policy", + router.id, + ] + verifylist = [ + ('router', router.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, parsed_args) diff --git a/releasenotes/notes/router-gateway-IP-QoS-c8ba95e180bca05f.yaml b/releasenotes/notes/router-gateway-IP-QoS-c8ba95e180bca05f.yaml new file mode 100644 index 0000000000..efe9a37624 --- /dev/null +++ b/releasenotes/notes/router-gateway-IP-QoS-c8ba95e180bca05f.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + Add support for attaching and removing qos policy to router gateway IPs. + + Add ``--qos-policy`` and ``--no-qos-policy`` options to the + ``router set`` command. + + Add ``--qos-policy`` option to the ``router unset`` command. From f9df3ce3cd0c98f3cd1e875b9b0c97161a15160d Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Mon, 17 Dec 2018 14:58:02 -0600 Subject: [PATCH 2009/3095] More volume functional test fixes Remove the use of class setup/teardown from volume transfer functional tests as that just doesn't work too well here. Also wait for volume status before attempting transfer request operations, some test nodes take a while to create the volumes. Change-Id: Ib9378ab5c973deb2aa86c9b9ed31408f3a05115a Signed-off-by: Dean Troyer --- .../volume/v2/test_transfer_request.py | 91 ++++++++++--------- 1 file changed, 48 insertions(+), 43 deletions(-) diff --git a/openstackclient/tests/functional/volume/v2/test_transfer_request.py b/openstackclient/tests/functional/volume/v2/test_transfer_request.py index 33d8ce77f7..d6aff73c48 100644 --- a/openstackclient/tests/functional/volume/v2/test_transfer_request.py +++ b/openstackclient/tests/functional/volume/v2/test_transfer_request.py @@ -19,75 +19,80 @@ class TransferRequestTests(common.BaseVolumeTests): """Functional tests for transfer request. """ - NAME = uuid.uuid4().hex - VOLUME_NAME = uuid.uuid4().hex API_VERSION = '2' - @classmethod - def setUpClass(cls): - super(TransferRequestTests, cls).setUpClass() - - cmd_output = json.loads(cls.openstack( - '--os-volume-api-version ' + cls.API_VERSION + ' ' + - 'volume create -f json --size 1 ' + cls.VOLUME_NAME)) - cls.assertOutput(cls.VOLUME_NAME, cmd_output['name']) - - cls.wait_for_status("volume", cls.VOLUME_NAME, "available") - - @classmethod - def tearDownClass(cls): - try: - raw_output_volume = cls.openstack( - 'volume delete ' + cls.VOLUME_NAME) - cls.assertOutput('', raw_output_volume) - finally: - super(TransferRequestTests, cls).tearDownClass() - def test_volume_transfer_request_accept(self): volume_name = uuid.uuid4().hex - name = uuid.uuid4().hex + xfer_name = uuid.uuid4().hex # create a volume cmd_output = json.loads(self.openstack( - 'volume create -f json --size 1 ' + volume_name)) + 'volume create -f json ' + + '--size 1 ' + + volume_name + )) self.assertEqual(volume_name, cmd_output['name']) + self.addCleanup( + self.openstack, + '--os-volume-api-version ' + self.API_VERSION + ' ' + + 'volume delete ' + + volume_name + ) + self.wait_for_status("volume", volume_name, "available") # create volume transfer request for the volume # and get the auth_key of the new transfer request cmd_output = json.loads(self.openstack( + '--os-volume-api-version ' + self.API_VERSION + ' ' + 'volume transfer request create -f json ' + - volume_name + - ' --name ' + name)) + ' --name ' + xfer_name + ' ' + + volume_name + )) + self.assertEqual(xfer_name, cmd_output['name']) auth_key = cmd_output['auth_key'] self.assertTrue(auth_key) # accept the volume transfer request cmd_output = json.loads(self.openstack( + '--os-volume-api-version ' + self.API_VERSION + ' ' + 'volume transfer request accept -f json ' + - name + ' ' + - '--auth-key ' + auth_key + '--auth-key ' + auth_key + ' ' + + xfer_name )) - self.assertEqual(name, cmd_output['name']) - - # the volume transfer will be removed by default after accepted - # so just need to delete the volume here - raw_output = self.openstack( - 'volume delete ' + volume_name) - self.assertEqual('', raw_output) + self.assertEqual(xfer_name, cmd_output['name']) def test_volume_transfer_request_list_show(self): - name = uuid.uuid4().hex + volume_name = uuid.uuid4().hex + xfer_name = uuid.uuid4().hex + + # create a volume + cmd_output = json.loads(self.openstack( + 'volume create -f json ' + + '--size 1 ' + + volume_name + )) + self.assertEqual(volume_name, cmd_output['name']) + self.addCleanup( + self.openstack, + '--os-volume-api-version ' + self.API_VERSION + ' ' + + 'volume delete ' + + volume_name + ) + self.wait_for_status("volume", volume_name, "available") + cmd_output = json.loads(self.openstack( '--os-volume-api-version ' + self.API_VERSION + ' ' + 'volume transfer request create -f json ' + - ' --name ' + name + ' ' + - self.VOLUME_NAME + ' --name ' + xfer_name + ' ' + + volume_name )) self.addCleanup( self.openstack, - 'volume transfer request delete ' + name + '--os-volume-api-version ' + self.API_VERSION + ' ' + + 'volume transfer request delete ' + + xfer_name ) - self.assertEqual(name, cmd_output['name']) + self.assertEqual(xfer_name, cmd_output['name']) auth_key = cmd_output['auth_key'] self.assertTrue(auth_key) @@ -95,11 +100,11 @@ def test_volume_transfer_request_list_show(self): '--os-volume-api-version ' + self.API_VERSION + ' ' + 'volume transfer request list -f json' )) - self.assertIn(name, [req['Name'] for req in cmd_output]) + self.assertIn(xfer_name, [req['Name'] for req in cmd_output]) cmd_output = json.loads(self.openstack( '--os-volume-api-version ' + self.API_VERSION + ' ' + 'volume transfer request show -f json ' + - name + xfer_name )) - self.assertEqual(name, cmd_output['name']) + self.assertEqual(xfer_name, cmd_output['name']) From 7fb866af4ee0ad050a081f88b9b3a899746644bb Mon Sep 17 00:00:00 2001 From: Rui Yuan Dou Date: Thu, 20 Dec 2018 13:53:39 +0800 Subject: [PATCH 2010/3095] Remove testr.conf as it's been replaced by stestr Change-Id: Ia596789a916b9271933c5b10f00399d83f18d44a --- .testr.conf | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 .testr.conf diff --git a/.testr.conf b/.testr.conf deleted file mode 100644 index 633aae1faf..0000000000 --- a/.testr.conf +++ /dev/null @@ -1,9 +0,0 @@ -[DEFAULT] -test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \ - OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \ - OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \ - ${PYTHON:-python} -m subunit.run discover -t ./ ${OS_TEST_PATH:-./openstackclient/tests/unit} $LISTOPT $IDOPTION - -test_id_option=--load-list $IDFILE -test_list_option=--list -group_regex=([^\.]+\.)+ From aaa106059768735e5dae5218ebd3a75ae2ec23d3 Mon Sep 17 00:00:00 2001 From: Jens Harbott Date: Thu, 20 Dec 2018 14:46:03 +0000 Subject: [PATCH 2011/3095] Add osc repo to the base job definition The osc-functional-devstack job is to be run against devstack changes in order to verify that the devstack-tox-functional job, which our job is based upon, doesn't introduce a regression[0]. In order to prevent this from failing, include our own repo as required-project. [0] https://review.openstack.org/526115 Change-Id: I782cd51dda1477c2e3a067cd902a3d3f29490083 --- .zuul.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.zuul.yaml b/.zuul.yaml index d646a0f766..3ce2f6ff21 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -70,6 +70,8 @@ - ^.*\.rst$ - ^doc/.*$ - ^releasenotes/.*$ + required-projects: + - openstack/python-openstackclient vars: devstack_localrc: LIBS_FROM_GIT: python-openstackclient From 2dd5393167119c043ae125fead9f8ed9ba84241d Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Tue, 8 Jan 2019 15:39:58 +0000 Subject: [PATCH 2012/3095] Use os-cloud instead of OS env vars for functional tests In order to support switching auth contexts, such as for registered_limits which take a system scoped token, switch the functional tests to using the --os-cloud command line parameter. However, honor the OS_CLOUD env var as a way that someone can select a different cloud, including 'envvars', to use. Use devstack-system-admin cloud for limit tests Keystone requires these to have system scope now. Change-Id: Ia81eebd3e00ae986cf3ba7e3d98f3e8a1647b622 --- openstackclient/tests/functional/base.py | 7 ++- .../tests/functional/identity/v3/common.py | 18 ++++-- .../functional/identity/v3/test_limit.py | 57 ++++++++++++++----- .../identity/v3/test_registered_limit.py | 34 +++++++---- .../tests/functional/run_stestr.sh | 16 ------ tox.ini | 6 +- 6 files changed, 87 insertions(+), 51 deletions(-) delete mode 100755 openstackclient/tests/functional/run_stestr.sh diff --git a/openstackclient/tests/functional/base.py b/openstackclient/tests/functional/base.py index 90bbc24d29..7705c65539 100644 --- a/openstackclient/tests/functional/base.py +++ b/openstackclient/tests/functional/base.py @@ -24,6 +24,7 @@ FUNCTIONAL_DIR = os.path.normpath(os.path.join(COMMON_DIR, '..')) ROOT_DIR = os.path.normpath(os.path.join(FUNCTIONAL_DIR, '..')) EXAMPLE_DIR = os.path.join(ROOT_DIR, 'examples') +ADMIN_CLOUD = os.environ.get('OS_ADMIN_CLOUD', 'devstack-admin') def execute(cmd, fail_ok=False, merge_stderr=False): @@ -59,9 +60,11 @@ class TestCase(testtools.TestCase): delimiter_line = re.compile('^\+\-[\+\-]+\-\+$') @classmethod - def openstack(cls, cmd, fail_ok=False): + def openstack(cls, cmd, cloud=ADMIN_CLOUD, fail_ok=False): """Executes openstackclient command for the given action.""" - return execute('openstack ' + cmd, fail_ok=fail_ok) + return execute( + 'openstack --os-cloud={cloud} '.format(cloud=cloud) + + cmd, fail_ok=fail_ok) @classmethod def get_openstack_configuration_value(cls, configuration): diff --git a/openstackclient/tests/functional/identity/v3/common.py b/openstackclient/tests/functional/identity/v3/common.py index 58468bc7e7..43b416aabe 100644 --- a/openstackclient/tests/functional/identity/v3/common.py +++ b/openstackclient/tests/functional/identity/v3/common.py @@ -19,6 +19,7 @@ BASIC_LIST_HEADERS = ['ID', 'Name'] +SYSTEM_CLOUD = os.environ.get('OS_SYSTEM_CLOUD', 'devstack-system-admin') class IdentityTests(base.TestCase): @@ -341,7 +342,8 @@ def _create_dummy_registered_limit(self, add_clean_up=True): 'registered limit create' ' --service %(service_name)s' ' --default-limit %(default_limit)s' - ' %(resource_name)s' % params + ' %(resource_name)s' % params, + cloud=SYSTEM_CLOUD ) items = self.parse_show(raw_output) registered_limit_id = self._extract_value_from_items('id', items) @@ -349,7 +351,8 @@ def _create_dummy_registered_limit(self, add_clean_up=True): if add_clean_up: self.addCleanup( self.openstack, - 'registered limit delete %s' % registered_limit_id + 'registered limit delete %s' % registered_limit_id, + cloud=SYSTEM_CLOUD ) self.assert_show_fields(items, self.REGISTERED_LIMIT_FIELDS) @@ -365,7 +368,8 @@ def _create_dummy_limit(self, add_clean_up=True): registered_limit_id = self._create_dummy_registered_limit() raw_output = self.openstack( - 'registered limit show %s' % registered_limit_id + 'registered limit show %s' % registered_limit_id, + cloud=SYSTEM_CLOUD ) items = self.parse_show(raw_output) resource_name = self._extract_value_from_items('resource_name', items) @@ -389,13 +393,17 @@ def _create_dummy_limit(self, add_clean_up=True): ' --project %(project_id)s' ' --service %(service_id)s' ' --resource-limit %(resource_limit)s' - ' %(resource_name)s' % params + ' %(resource_name)s' % params, + cloud=SYSTEM_CLOUD ) items = self.parse_show(raw_output) limit_id = self._extract_value_from_items('id', items) if add_clean_up: - self.addCleanup(self.openstack, 'limit delete %s' % limit_id) + self.addCleanup( + self.openstack, 'limit delete %s' % limit_id, + cloud=SYSTEM_CLOUD + ) self.assert_show_fields(items, self.LIMIT_FIELDS) return limit_id diff --git a/openstackclient/tests/functional/identity/v3/test_limit.py b/openstackclient/tests/functional/identity/v3/test_limit.py index 03bcb06e4b..b03f0f282a 100644 --- a/openstackclient/tests/functional/identity/v3/test_limit.py +++ b/openstackclient/tests/functional/identity/v3/test_limit.py @@ -10,17 +10,22 @@ # License for the specific language governing permissions and limitations # under the License. +import os + from tempest.lib.common.utils import data_utils from openstackclient.tests.functional.identity.v3 import common +SYSTEM_CLOUD = os.environ.get('OS_SYSTEM_CLOUD', 'devstack-system-admin') + class LimitTestCase(common.IdentityTests): def test_limit_create_with_service_name(self): registered_limit_id = self._create_dummy_registered_limit() raw_output = self.openstack( - 'registered limit show %s' % registered_limit_id + 'registered limit show %s' % registered_limit_id, + cloud=SYSTEM_CLOUD ) items = self.parse_show(raw_output) service_id = self._extract_value_from_items('service_id', items) @@ -46,18 +51,24 @@ def test_limit_create_with_service_name(self): ' --project %(project_id)s' ' --service %(service_name)s' ' --resource-limit %(resource_limit)s' - ' %(resource_name)s' % params + ' %(resource_name)s' % params, + cloud=SYSTEM_CLOUD ) items = self.parse_show(raw_output) limit_id = self._extract_value_from_items('id', items) - self.addCleanup(self.openstack, 'limit delete %s' % limit_id) + self.addCleanup( + self.openstack, + 'limit delete %s' % limit_id, + cloud=SYSTEM_CLOUD + ) self.assert_show_fields(items, self.LIMIT_FIELDS) def test_limit_create_with_project_name(self): registered_limit_id = self._create_dummy_registered_limit() raw_output = self.openstack( - 'registered limit show %s' % registered_limit_id + 'registered limit show %s' % registered_limit_id, + cloud=SYSTEM_CLOUD ) items = self.parse_show(raw_output) service_id = self._extract_value_from_items('service_id', items) @@ -80,11 +91,16 @@ def test_limit_create_with_project_name(self): ' --project %(project_name)s' ' --service %(service_name)s' ' --resource-limit %(resource_limit)s' - ' %(resource_name)s' % params + ' %(resource_name)s' % params, + cloud=SYSTEM_CLOUD ) items = self.parse_show(raw_output) limit_id = self._extract_value_from_items('id', items) - self.addCleanup(self.openstack, 'limit delete %s' % limit_id) + self.addCleanup( + self.openstack, + 'limit delete %s' % limit_id, + cloud=SYSTEM_CLOUD + ) self.assert_show_fields(items, self.LIMIT_FIELDS) registered_limit_id = self._create_dummy_registered_limit() @@ -107,7 +123,8 @@ def test_limit_create_with_options(self): raw_output = self.openstack( 'registered limit set' ' %(registered_limit_id)s' - ' --region %(region_id)s' % params + ' --region %(region_id)s' % params, + cloud=SYSTEM_CLOUD ) items = self.parse_show(raw_output) service_id = self._extract_value_from_items('service_id', items) @@ -134,17 +151,25 @@ def test_limit_create_with_options(self): ' --resource-limit %(resource_limit)s' ' --region %(region_id)s' ' --description %(description)s' - ' %(resource_name)s' % params + ' %(resource_name)s' % params, + cloud=SYSTEM_CLOUD ) items = self.parse_show(raw_output) limit_id = self._extract_value_from_items('id', items) - self.addCleanup(self.openstack, 'limit delete %s' % limit_id) + self.addCleanup( + self.openstack, + 'limit delete %s' % limit_id, + cloud=SYSTEM_CLOUD + ) self.assert_show_fields(items, self.LIMIT_FIELDS) def test_limit_show(self): limit_id = self._create_dummy_limit() - raw_output = self.openstack('limit show %s' % limit_id) + raw_output = self.openstack( + 'limit show %s' % limit_id, + cloud=SYSTEM_CLOUD + ) items = self.parse_show(raw_output) self.assert_show_fields(items, self.LIMIT_FIELDS) @@ -159,7 +184,8 @@ def test_limit_set_description(self): raw_output = self.openstack( 'limit set' ' --description %(description)s' - ' %(limit_id)s' % params + ' %(limit_id)s' % params, + cloud=SYSTEM_CLOUD ) items = self.parse_show(raw_output) self.assert_show_fields(items, self.LIMIT_FIELDS) @@ -175,18 +201,21 @@ def test_limit_set_resource_limit(self): raw_output = self.openstack( 'limit set' ' --resource-limit %(resource_limit)s' - ' %(limit_id)s' % params + ' %(limit_id)s' % params, + cloud=SYSTEM_CLOUD ) items = self.parse_show(raw_output) self.assert_show_fields(items, self.LIMIT_FIELDS) def test_limit_list(self): self._create_dummy_limit() - raw_output = self.openstack('limit list') + raw_output = self.openstack('limit list', cloud=SYSTEM_CLOUD) items = self.parse_listing(raw_output) self.assert_table_structure(items, self.LIMIT_LIST_HEADERS) def test_limit_delete(self): limit_id = self._create_dummy_limit(add_clean_up=False) - raw_output = self.openstack('limit delete %s' % limit_id) + raw_output = self.openstack( + 'limit delete %s' % limit_id, + cloud=SYSTEM_CLOUD) self.assertEqual(0, len(raw_output)) diff --git a/openstackclient/tests/functional/identity/v3/test_registered_limit.py b/openstackclient/tests/functional/identity/v3/test_registered_limit.py index 09e90ce206..80f51ad99f 100644 --- a/openstackclient/tests/functional/identity/v3/test_registered_limit.py +++ b/openstackclient/tests/functional/identity/v3/test_registered_limit.py @@ -10,10 +10,14 @@ # License for the specific language governing permissions and limitations # under the License. +import os + from tempest.lib.common.utils import data_utils from openstackclient.tests.functional.identity.v3 import common +SYSTEM_CLOUD = os.environ.get('OS_SYSTEM_CLOUD', 'devstack-system-admin') + class RegisteredLimitTestCase(common.IdentityTests): @@ -37,7 +41,8 @@ def test_registered_limit_create_with_service_id(self): 'service_id': service_id, 'default_limit': 10, 'resource_name': 'cores' - } + }, + cloud=SYSTEM_CLOUD ) items = self.parse_show(raw_output) registered_limit_id = self._extract_value_from_items('id', items) @@ -46,7 +51,8 @@ def test_registered_limit_create_with_service_id(self): 'registered limit delete' ' %(registered_limit_id)s' % { 'registered_limit_id': registered_limit_id - } + }, + cloud=SYSTEM_CLOUD ) self.assert_show_fields(items, self.REGISTERED_LIMIT_FIELDS) @@ -68,7 +74,8 @@ def test_registered_limit_create_with_options(self): ' --region %(region_id)s' ' --service %(service_name)s' ' --default-limit %(default_limit)s' - ' %(resource_name)s' % params + ' %(resource_name)s' % params, + cloud=SYSTEM_CLOUD ) items = self.parse_show(raw_output) registered_limit_id = self._extract_value_from_items('id', items) @@ -76,7 +83,8 @@ def test_registered_limit_create_with_options(self): self.openstack, 'registered limit delete %(registered_limit_id)s' % { 'registered_limit_id': registered_limit_id - } + }, + cloud=SYSTEM_CLOUD ) self.assert_show_fields(items, self.REGISTERED_LIMIT_FIELDS) @@ -102,7 +110,8 @@ def test_registered_limit_set_region_id(self): raw_output = self.openstack( 'registered limit set' ' %(registered_limit_id)s' - ' --region %(region_id)s' % params + ' --region %(region_id)s' % params, + cloud=SYSTEM_CLOUD ) items = self.parse_show(raw_output) self.assert_show_fields(items, self.REGISTERED_LIMIT_FIELDS) @@ -116,7 +125,8 @@ def test_registered_limit_set_description(self): raw_output = self.openstack( 'registered limit set' ' %(registered_limit_id)s' - ' --description \'%(description)s\'' % params + ' --description \'%(description)s\'' % params, + cloud=SYSTEM_CLOUD ) items = self.parse_show(raw_output) self.assert_show_fields(items, self.REGISTERED_LIMIT_FIELDS) @@ -131,7 +141,8 @@ def test_registered_limit_set_service(self): raw_output = self.openstack( 'registered limit set' ' %(registered_limit_id)s' - ' --service %(service)s' % params + ' --service %(service)s' % params, + cloud=SYSTEM_CLOUD ) items = self.parse_show(raw_output) self.assert_show_fields(items, self.REGISTERED_LIMIT_FIELDS) @@ -145,7 +156,8 @@ def test_registered_limit_set_default_limit(self): raw_output = self.openstack( 'registered limit set' ' %(registered_limit_id)s' - ' --default-limit %(default_limit)s' % params + ' --default-limit %(default_limit)s' % params, + cloud=SYSTEM_CLOUD ) items = self.parse_show(raw_output) self.assert_show_fields(items, self.REGISTERED_LIMIT_FIELDS) @@ -160,7 +172,8 @@ def test_registered_limit_set_resource_name(self): raw_output = self.openstack( 'registered limit set' ' %(registered_limit_id)s' - ' --resource-name %(resource_name)s' % params + ' --resource-name %(resource_name)s' % params, + cloud=SYSTEM_CLOUD ) items = self.parse_show(raw_output) self.assert_show_fields(items, self.REGISTERED_LIMIT_FIELDS) @@ -179,6 +192,7 @@ def test_registered_limit_delete(self): 'registered limit delete' ' %(registered_limit_id)s' % { 'registered_limit_id': registered_limit_id - } + }, + cloud=SYSTEM_CLOUD ) self.assertEqual(0, len(raw_output)) diff --git a/openstackclient/tests/functional/run_stestr.sh b/openstackclient/tests/functional/run_stestr.sh deleted file mode 100755 index 229b42b676..0000000000 --- a/openstackclient/tests/functional/run_stestr.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -# This is a script that runs ostestr with the openrc OS_ variables sourced. -# Do not run this script unless you know what you're doing. -# For more information refer to: -# https://docs.openstack.org/python-openstackclient/latest/ - -# Source environment variables to kick things off -if [ -f ~stack/devstack/openrc ] ; then - source ~stack/devstack/openrc admin admin -fi - -echo 'Running tests with:' -env | grep OS - -stestr run $* diff --git a/tox.ini b/tox.ini index 0b2aa78f50..67eccd4e76 100644 --- a/tox.ini +++ b/tox.ini @@ -70,14 +70,12 @@ whitelist_externals = stestr [testenv:functional] setenv = OS_TEST_PATH=./openstackclient/tests/functional passenv = OS_* -whitelist_externals = openstackclient/tests/functional/run_stestr.sh commands = - {toxinidir}/openstackclient/tests/functional/run_stestr.sh {posargs} + stestr run {posargs} [testenv:functional-tips] setenv = OS_TEST_PATH=./openstackclient/tests/functional passenv = OS_* -whitelist_externals = openstackclient/tests/functional/run_stestr.sh commands = pip install -q -U -e "git+file://{toxinidir}/../cliff#egg=cliff" pip install -q -U -e "git+file://{toxinidir}/../keystoneauth#egg=keystoneauth" @@ -85,7 +83,7 @@ commands = pip install -q -U -e "git+file://{toxinidir}/../os-client-config#egg=os_client_config" pip install -q -U -e "git+file://{toxinidir}/../openstacksdk#egg=openstacksdk" pip freeze - {toxinidir}/openstackclient/tests/functional/run_stestr.sh {posargs} + stestr run {posargs} [testenv:venv] basepython = python3 From b8438adbbf6f2024c9440ffec064088a02c807df Mon Sep 17 00:00:00 2001 From: Johannes Kulik Date: Mon, 7 Jan 2019 13:02:57 +0100 Subject: [PATCH 2013/3095] Add floating IP filter to floating IP list command Add a parameter ``--floating-ip-address`` to ``floating ip list`` because it's supported by the API and also more efficient than the current ``floating ip show``. This also works as a work-around for pagination issues ``floating ip show`` might run into with an IP parameter. Change-Id: I113e3fa2495e1e86bb553c55c44f71a3f9f49d23 --- doc/source/cli/command-objects/floating-ip.rst | 6 ++++++ openstackclient/network/v2/floating_ip.py | 8 ++++++++ .../unit/network/v2/test_floating_ip_network.py | 17 +++++++++++++++++ 3 files changed, 31 insertions(+) diff --git a/doc/source/cli/command-objects/floating-ip.rst b/doc/source/cli/command-objects/floating-ip.rst index 14615749ca..749c32d6b8 100644 --- a/doc/source/cli/command-objects/floating-ip.rst +++ b/doc/source/cli/command-objects/floating-ip.rst @@ -144,6 +144,12 @@ List floating IP(s) *Network version 2 only* +.. option:: --floating-ip-address + + List floating IP(s) according to given floating IP address + + *Network version 2 only* + .. option:: --long List additional fields in output diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index e1ec82748f..8ac8e10703 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -236,6 +236,12 @@ def update_parser_network(self, parser): help=_("List floating IP(s) according to " "given fixed IP address") ) + parser.add_argument( + '--floating-ip-address', + metavar='', + help=_("List floating IP(s) according to " + "given floating IP address") + ) parser.add_argument( '--long', action='store_true', @@ -316,6 +322,8 @@ def take_action_network(self, client, parsed_args): query['port_id'] = port.id if parsed_args.fixed_ip_address is not None: query['fixed_ip_address'] = parsed_args.fixed_ip_address + if parsed_args.floating_ip_address is not None: + query['floating_ip_address'] = parsed_args.floating_ip_address if parsed_args.status: query['status'] = parsed_args.status if parsed_args.project is not None: diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip_network.py b/openstackclient/tests/unit/network/v2/test_floating_ip_network.py index 209c01cf1c..cbd4da38ca 100644 --- a/openstackclient/tests/unit/network/v2/test_floating_ip_network.py +++ b/openstackclient/tests/unit/network/v2/test_floating_ip_network.py @@ -504,6 +504,23 @@ def test_floating_ip_list_fixed_ip_address(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + def test_floating_ip_list_floating_ip_address(self): + arglist = [ + '--floating-ip-address', self.floating_ips[0].floating_ip_address, + ] + verifylist = [ + ('floating_ip_address', self.floating_ips[0].floating_ip_address), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.ips.assert_called_once_with(**{ + 'floating_ip_address': self.floating_ips[0].floating_ip_address, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + def test_floating_ip_list_long(self): arglist = ['--long', ] verifylist = [('long', True), ] From 7276610595499ab45872d1b0587effc3dba195e2 Mon Sep 17 00:00:00 2001 From: Noam Angel Date: Thu, 27 Dec 2018 08:35:45 +0000 Subject: [PATCH 2014/3095] fix multiple server delete produce multiple new lines Closes-Bug: #1809874 Change-Id: Ib988b189b41af03d3d871b660bb5b5cc090c3f30 --- openstackclient/compute/v2/server.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 3534fc7d9a..b608ecbd52 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -999,13 +999,9 @@ def _show_progress(progress): compute_client.servers, server) compute_client.servers.delete(server_obj.id) if parsed_args.wait: - if utils.wait_for_delete( - compute_client.servers, - server_obj.id, - callback=_show_progress, - ): - self.app.stdout.write('\n') - else: + if not utils.wait_for_delete(compute_client.servers, + server_obj.id, + callback=_show_progress): LOG.error(_('Error deleting server: %s'), server_obj.id) self.app.stdout.write(_('Error deleting server\n')) From aaf73cbf6d51a4a3b25cc0a5f1b7c5fb0d462c57 Mon Sep 17 00:00:00 2001 From: Radoslaw Smigielski Date: Mon, 10 Dec 2018 19:18:05 +0100 Subject: [PATCH 2015/3095] Fix --limit option in image list sub-command Client site fix of --limit option. This bugfix makes client "image list" command working again with "--limit" option. This option was ignored and even if user specified it, still list of all available images was returned. Story: 2004314 Change-Id: I30a78d65a644c9b7d23706a6637ce77bca2c2386 Depends-On: https://review.openstack.org/#/c/634776/ --- openstackclient/image/v2/image.py | 5 +++++ openstackclient/tests/unit/image/v2/test_image.py | 9 +++++---- releasenotes/notes/bug-27882-402ced7ffe930058.yaml | 5 +++++ 3 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/bug-27882-402ced7ffe930058.yaml diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 06eebe984c..1464c7b85d 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -630,6 +630,9 @@ def take_action(self, parsed_args): # List of image data received data = [] + limit = None + if 'limit' in kwargs: + limit = kwargs['limit'] if 'marker' in kwargs: data = image_client.api.image_list(**kwargs) else: @@ -642,6 +645,8 @@ def take_action(self, parsed_args): data.extend(page) # Set the marker to the id of the last item we received marker = page[-1]['id'] + if limit: + break if parsed_args.property: for attr, value in parsed_args.property.items(): diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py index 170a7f036b..a1a3035a65 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -744,21 +744,22 @@ def test_image_list_sort_option(self, si_mock): self.assertEqual(self.datalist, tuple(data)) def test_image_list_limit_option(self): + ret_limit = 1 arglist = [ - '--limit', str(1), + '--limit', str(ret_limit), ] verifylist = [ - ('limit', 1), + ('limit', ret_limit), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( - limit=1, marker=self._image.id + limit=ret_limit, marker=None ) self.assertEqual(self.columns, columns) - self.assertEqual(len(self.datalist), len(tuple(data))) + self.assertEqual(ret_limit, len(tuple(data))) @mock.patch('osc_lib.utils.find_resource') def test_image_list_marker_option(self, fr_mock): diff --git a/releasenotes/notes/bug-27882-402ced7ffe930058.yaml b/releasenotes/notes/bug-27882-402ced7ffe930058.yaml new file mode 100644 index 0000000000..6bd090aa5c --- /dev/null +++ b/releasenotes/notes/bug-27882-402ced7ffe930058.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + The ``--limit`` option of the ``image list`` command was previously ignored. + [Bug `https://storyboard.openstack.org/#!/story/2004314`] From 1a0bef2b46c76e1c6308def709199c30c68aae47 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Mon, 4 Feb 2019 12:48:20 -0600 Subject: [PATCH 2016/3095] More state handling in volume transfer requests functional tests Using addCleanup() for removing the pending volume transfer request has no way to wait for the volume status to become available before cleaning up the volume and gets racy when the tests are run with slow performance in the volume backend. So we pause at the end of the test after either accepting the transfer request or explicitly deleting it so the cleanup can delete the volume. Change-Id: I04862069cab28bc76eeafd60ba32be646f478d86 Signed-off-by: Dean Troyer --- .../volume/v2/test_transfer_request.py | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/openstackclient/tests/functional/volume/v2/test_transfer_request.py b/openstackclient/tests/functional/volume/v2/test_transfer_request.py index d6aff73c48..00d0865c5c 100644 --- a/openstackclient/tests/functional/volume/v2/test_transfer_request.py +++ b/openstackclient/tests/functional/volume/v2/test_transfer_request.py @@ -49,17 +49,20 @@ def test_volume_transfer_request_accept(self): volume_name )) self.assertEqual(xfer_name, cmd_output['name']) + xfer_id = cmd_output['id'] auth_key = cmd_output['auth_key'] self.assertTrue(auth_key) + self.wait_for_status("volume", volume_name, "awaiting-transfer") # accept the volume transfer request cmd_output = json.loads(self.openstack( '--os-volume-api-version ' + self.API_VERSION + ' ' + 'volume transfer request accept -f json ' + '--auth-key ' + auth_key + ' ' + - xfer_name + xfer_id )) self.assertEqual(xfer_name, cmd_output['name']) + self.wait_for_status("volume", volume_name, "available") def test_volume_transfer_request_list_show(self): volume_name = uuid.uuid4().hex @@ -86,15 +89,11 @@ def test_volume_transfer_request_list_show(self): ' --name ' + xfer_name + ' ' + volume_name )) - self.addCleanup( - self.openstack, - '--os-volume-api-version ' + self.API_VERSION + ' ' + - 'volume transfer request delete ' + - xfer_name - ) self.assertEqual(xfer_name, cmd_output['name']) + xfer_id = cmd_output['id'] auth_key = cmd_output['auth_key'] self.assertTrue(auth_key) + self.wait_for_status("volume", volume_name, "awaiting-transfer") cmd_output = json.loads(self.openstack( '--os-volume-api-version ' + self.API_VERSION + ' ' + @@ -105,6 +104,18 @@ def test_volume_transfer_request_list_show(self): cmd_output = json.loads(self.openstack( '--os-volume-api-version ' + self.API_VERSION + ' ' + 'volume transfer request show -f json ' + - xfer_name + xfer_id )) self.assertEqual(xfer_name, cmd_output['name']) + + # NOTE(dtroyer): We need to delete the transfer request to allow the + # volume to be deleted. The addCleanup() route does + # not have a mechanism to wait for the volume status + # to become 'available' before attempting to delete + # the volume. + cmd_output = self.openstack( + '--os-volume-api-version ' + self.API_VERSION + ' ' + + 'volume transfer request delete ' + + xfer_id + ) + self.wait_for_status("volume", volume_name, "available") From 811b001234b9652f8fbfb7419bc9be51ee43de64 Mon Sep 17 00:00:00 2001 From: Jeremy Houser Date: Mon, 11 Feb 2019 11:21:36 -0600 Subject: [PATCH 2017/3095] This fix removes an erroneous underscore found within the function named test_snapshot_delete within test_snapshot.py found in both volume v1 and v2 of python-openstackclient. Story: 2004977 Change-Id: Iae29ba7992dcf8596f4fb4333d8bcf1889ecd7e6 --- openstackclient/tests/functional/volume/v1/test_snapshot.py | 2 +- openstackclient/tests/functional/volume/v2/test_snapshot.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openstackclient/tests/functional/volume/v1/test_snapshot.py b/openstackclient/tests/functional/volume/v1/test_snapshot.py index c60472c55e..083cd1b087 100644 --- a/openstackclient/tests/functional/volume/v1/test_snapshot.py +++ b/openstackclient/tests/functional/volume/v1/test_snapshot.py @@ -42,7 +42,7 @@ def tearDownClass(cls): finally: super(VolumeSnapshotTests, cls).tearDownClass() - def test_volume_snapshot__delete(self): + def test_volume_snapshot_delete(self): """Test create, delete multiple""" name1 = uuid.uuid4().hex cmd_output = json.loads(self.openstack( diff --git a/openstackclient/tests/functional/volume/v2/test_snapshot.py b/openstackclient/tests/functional/volume/v2/test_snapshot.py index ba6b2c2837..264f4adb6b 100644 --- a/openstackclient/tests/functional/volume/v2/test_snapshot.py +++ b/openstackclient/tests/functional/volume/v2/test_snapshot.py @@ -43,7 +43,7 @@ def tearDownClass(cls): finally: super(VolumeSnapshotTests, cls).tearDownClass() - def test_volume_snapshot__delete(self): + def test_volume_snapshot_delete(self): """Test create, delete multiple""" name1 = uuid.uuid4().hex cmd_output = json.loads(self.openstack( From 0a187905c01f6bc2b9855081ac0042f00715dedf Mon Sep 17 00:00:00 2001 From: whoami-rajat Date: Wed, 13 Feb 2019 12:39:11 +0530 Subject: [PATCH 2018/3095] Add py36 env While running `tox` command in binoic env, we see the following message, ERROR: InterpreterNotFound: python3.5 It is because the default py3 version for binoic is py36. This patch adds the env in OSC also maintaining consistency with setup.cfg Change-Id: I8e5cf72901cba34ad44f2b356609f85b3b0c431f --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 67eccd4e76..e008f9562c 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] minversion = 2.3 -envlist = py35,py27,pep8 +envlist = py36,py35,py27,pep8 skipdist = True [testenv] From 05521bf84cb108c73bb36b270569b1986ad13f53 Mon Sep 17 00:00:00 2001 From: Bernard Cafarelli Date: Mon, 28 Jan 2019 14:17:46 +0100 Subject: [PATCH 2019/3095] Remove str() when setting network objects names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Most network commands use str() on name argument, which fails on python 2 with Unicode characters. This comes from parsed arguments so does not actually need this call. Sample command failing with current code: openstack network create test_unicode™ Change-Id: Ie10b67864c912ee5c33e90b10c3d9705ee8307e7 Story: 2004356 Task: 27955 --- openstackclient/network/v2/network.py | 4 ++-- openstackclient/network/v2/network_agent.py | 2 +- openstackclient/network/v2/network_qos_policy.py | 2 +- openstackclient/network/v2/port.py | 2 +- openstackclient/network/v2/router.py | 2 +- openstackclient/network/v2/subnet.py | 2 +- openstackclient/network/v2/subnet_pool.py | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 0fdf62c95b..f5123932a3 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -72,7 +72,7 @@ def _get_columns_compute(item): def _get_attrs_network(client_manager, parsed_args): attrs = {} if parsed_args.name is not None: - attrs['name'] = str(parsed_args.name) + attrs['name'] = parsed_args.name if parsed_args.enable: attrs['admin_state_up'] = True if parsed_args.disable: @@ -143,7 +143,7 @@ def _get_attrs_network(client_manager, parsed_args): def _get_attrs_compute(client_manager, parsed_args): attrs = {} if parsed_args.name is not None: - attrs['name'] = str(parsed_args.name) + attrs['name'] = parsed_args.name if parsed_args.share: attrs['share_subnet'] = True if parsed_args.no_share: diff --git a/openstackclient/network/v2/network_agent.py b/openstackclient/network/v2/network_agent.py index ba2a2633ba..46e8d4b21c 100644 --- a/openstackclient/network/v2/network_agent.py +++ b/openstackclient/network/v2/network_agent.py @@ -351,7 +351,7 @@ def take_action(self, parsed_args): obj = client.get_agent(parsed_args.network_agent) attrs = {} if parsed_args.description is not None: - attrs['description'] = str(parsed_args.description) + attrs['description'] = parsed_args.description if parsed_args.enable: attrs['is_admin_state_up'] = True attrs['admin_state_up'] = True diff --git a/openstackclient/network/v2/network_qos_policy.py b/openstackclient/network/v2/network_qos_policy.py index 2c6b841b49..fd5ff93771 100644 --- a/openstackclient/network/v2/network_qos_policy.py +++ b/openstackclient/network/v2/network_qos_policy.py @@ -38,7 +38,7 @@ def _get_columns(item): def _get_attrs(client_manager, parsed_args): attrs = {} if 'name' in parsed_args and parsed_args.name is not None: - attrs['name'] = str(parsed_args.name) + attrs['name'] = parsed_args.name if 'description' in parsed_args and parsed_args.description is not None: attrs['description'] = parsed_args.description if parsed_args.share: diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 1001e6cf41..f6d6fc7280 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -134,7 +134,7 @@ def _get_attrs(client_manager, parsed_args): attrs['dns_name'] = parsed_args.dns_name # It is possible that name is not updated during 'port set' if parsed_args.name is not None: - attrs['name'] = str(parsed_args.name) + attrs['name'] = parsed_args.name # The remaining options do not support 'port set' command, so they require # additional check if 'network' in parsed_args and parsed_args.network is not None: diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 1b7d6374a9..2ec3e2f094 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -85,7 +85,7 @@ def _get_columns(item): def _get_attrs(client_manager, parsed_args): attrs = {} if parsed_args.name is not None: - attrs['name'] = str(parsed_args.name) + attrs['name'] = parsed_args.name if parsed_args.enable: attrs['admin_state_up'] = True if parsed_args.disable: diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index d252a7a51d..5f8113bb8d 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -169,7 +169,7 @@ def _get_attrs(client_manager, parsed_args, is_create=True): attrs = {} client = client_manager.network if 'name' in parsed_args and parsed_args.name is not None: - attrs['name'] = str(parsed_args.name) + attrs['name'] = parsed_args.name if is_create: if 'project' in parsed_args and parsed_args.project is not None: diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index 81765ca17e..d8c06eb882 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -52,7 +52,7 @@ def _get_attrs(client_manager, parsed_args): network_client = client_manager.network if parsed_args.name is not None: - attrs['name'] = str(parsed_args.name) + attrs['name'] = parsed_args.name if parsed_args.prefixes is not None: attrs['prefixes'] = parsed_args.prefixes if parsed_args.default_prefix_length is not None: From fa3c5e636e02b521d22c186603f15666c2363210 Mon Sep 17 00:00:00 2001 From: ZhongShengping Date: Tue, 19 Feb 2019 16:45:05 +0800 Subject: [PATCH 2020/3095] add python 3.7 unit test job This is a mechanically generated patch to add a unit test job running under Python 3.7. See ML discussion here [1] for context. [1] http://lists.openstack.org/pipermail/openstack-dev/2018-October/135626.html Change-Id: Ic1d05a80d286ff95777eb30d66086ef2b57bdb7f Story: #2004073 Task: #27438 --- .zuul.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.zuul.yaml b/.zuul.yaml index 3ce2f6ff21..1ae311e844 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -189,6 +189,7 @@ - openstack-python-jobs - openstack-python35-jobs - openstack-python36-jobs + - openstack-python37-jobs - publish-openstack-docs-pti - check-requirements - release-notes-jobs-python3 From 55cbbbe4692002e58120b0c129eb2add4f0bc18a Mon Sep 17 00:00:00 2001 From: David Rabel Date: Tue, 19 Feb 2019 12:49:18 +0100 Subject: [PATCH 2021/3095] Fix help message of image add project Only with the admin role you can use the project name with 'image add project'. With the normal member role you have to use the project id instead. If you try to use the name, you don't receive an error, but it won't work. Change-Id: I2d11c07a256917d12c46a7c302c5a5e8752a1df0 Task: 29543 Story: 2002535 --- openstackclient/image/v2/image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 06eebe984c..a898e20fef 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -86,7 +86,7 @@ def get_parser(self, prog_name): parser.add_argument( "project", metavar="", - help=_("Project to associate with image (name or ID)"), + help=_("Project to associate with image (ID)"), ) common.add_project_domain_option_to_parser(parser) return parser From 4d76d7539a1c2bb430b040d2fbc8728fbf7a4eee Mon Sep 17 00:00:00 2001 From: David Rabel Date: Wed, 20 Feb 2019 10:32:16 +0100 Subject: [PATCH 2022/3095] Fix help message of image add project Only with the admin role you can use the project name with 'image add project'. With the normal member role you have to use the project id instead. If you try to use the name, you don't receive an error, but it won't work. Change-Id: I61d402b39558320502dc08905b8c3a146e5e740a Task: 29543 Story: 2002535 --- doc/source/cli/command-objects/image.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/cli/command-objects/image.rst b/doc/source/cli/command-objects/image.rst index 95486e334b..b6e3594b16 100644 --- a/doc/source/cli/command-objects/image.rst +++ b/doc/source/cli/command-objects/image.rst @@ -32,7 +32,7 @@ Associate project with image .. _image_add_project-project: .. describe:: - Project to associate with image (name or ID) + Project to associate with image (ID) image create ------------ From 75cba9d1cbdd7b14b0d507af27f896c6c45e713e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C5=82awek=20Kap=C5=82o=C5=84ski?= Date: Fri, 19 Oct 2018 12:46:21 +0300 Subject: [PATCH 2023/3095] Add support for get details of Quota With passing "--detail" argument to "openstack quota list", details about current usage should be returned. It is currently supported by Nova and Neutron so details of resources from those projects can be returned. Change-Id: I48fda15b34283bb7c66ea18ed28262f48b9229fe Related-Bug: #1716043 --- doc/source/cli/command-objects/quota.rst | 10 + openstackclient/common/quota.py | 231 +++++++++++++----- .../tests/functional/common/test_quota.py | 32 +++ .../tests/unit/common/test_quota.py | 87 ++++++- .../tests/unit/compute/v2/fakes.py | 32 +++ .../tests/unit/network/v2/fakes.py | 23 ++ ...d-quota-informations-1755129e1c68a252.yaml | 6 + 7 files changed, 353 insertions(+), 68 deletions(-) create mode 100644 releasenotes/notes/list-detailed-quota-informations-1755129e1c68a252.yaml diff --git a/doc/source/cli/command-objects/quota.rst b/doc/source/cli/command-objects/quota.rst index f39536af8e..c538cfb3dd 100644 --- a/doc/source/cli/command-objects/quota.rst +++ b/doc/source/cli/command-objects/quota.rst @@ -17,6 +17,8 @@ List quotas for all projects with non-default quota values openstack quota list --compute | --network | --volume + [--project ] + [--detail] .. option:: --network @@ -30,6 +32,14 @@ List quotas for all projects with non-default quota values List volume quotas +.. option:: --project + + List quotas for this project (name or ID) + +.. option:: --detail + + Show details about quotas usage + quota set --------- diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index 282ea4284d..dba6873ff9 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -97,12 +97,164 @@ def _xform_get_quota(data, value, keys): return res -class ListQuota(command.Lister): - _description = _("List quotas for all projects " - "with non-default quota values") +class BaseQuota(object): + def _get_project(self, parsed_args): + if parsed_args.project is not None: + identity_client = self.app.client_manager.identity + project = utils.find_resource( + identity_client.projects, + parsed_args.project, + ) + project_id = project.id + project_name = project.name + elif self.app.client_manager.auth_ref: + # Get the project from the current auth + project = self.app.client_manager.auth_ref + project_id = project.project_id + project_name = project.project_name + else: + project = None + project_id = None + project_name = None + project_info = {} + project_info['id'] = project_id + project_info['name'] = project_name + return project_info + + def get_compute_quota(self, client, parsed_args): + quota_class = ( + parsed_args.quota_class if 'quota_class' in parsed_args else False) + detail = parsed_args.detail if 'detail' in parsed_args else False + default = parsed_args.default if 'default' in parsed_args else False + try: + if quota_class: + quota = client.quota_classes.get(parsed_args.project) + else: + project_info = self._get_project(parsed_args) + project = project_info['id'] + if default: + quota = client.quotas.defaults(project) + else: + quota = client.quotas.get(project, detail=detail) + except Exception as e: + if type(e).__name__ == 'EndpointNotFound': + return {} + else: + raise + return quota._info + + def get_volume_quota(self, client, parsed_args): + quota_class = ( + parsed_args.quota_class if 'quota_class' in parsed_args else False) + default = parsed_args.default if 'default' in parsed_args else False + try: + if quota_class: + quota = client.quota_classes.get(parsed_args.project) + else: + project_info = self._get_project(parsed_args) + project = project_info['id'] + if default: + quota = client.quotas.defaults(project) + else: + quota = client.quotas.get(project) + except Exception as e: + if type(e).__name__ == 'EndpointNotFound': + return {} + else: + raise + return quota._info + + def get_network_quota(self, parsed_args): + quota_class = ( + parsed_args.quota_class if 'quota_class' in parsed_args else False) + detail = parsed_args.detail if 'detail' in parsed_args else False + default = parsed_args.default if 'default' in parsed_args else False + if quota_class: + return {} + if self.app.client_manager.is_network_endpoint_enabled(): + project_info = self._get_project(parsed_args) + project = project_info['id'] + client = self.app.client_manager.network + if default: + network_quota = client.get_quota_default(project) + if type(network_quota) is not dict: + network_quota = network_quota.to_dict() + else: + network_quota = client.get_quota(project, + details=detail) + if type(network_quota) is not dict: + network_quota = network_quota.to_dict() + if detail: + # NOTE(slaweq): Neutron returns values with key "used" but + # Nova for example returns same data with key "in_use" + # instead. + # Because of that we need to convert Neutron key to + # the same as is returned from Nova to make result + # more consistent + for key, values in network_quota.items(): + if type(values) is dict and "used" in values: + values[u'in_use'] = values.pop("used") + network_quota[key] = values + return network_quota + else: + return {} + + +class ListQuota(command.Lister, BaseQuota): + _description = _( + "List quotas for all projects with non-default quota values or " + "list detailed quota informations for requested project") + + def _get_detailed_quotas(self, parsed_args): + columns = ( + 'resource', + 'in_use', + 'reserved', + 'limit' + ) + column_headers = ( + 'Resource', + 'In Use', + 'Reserved', + 'Limit' + ) + quotas = {} + if parsed_args.compute: + quotas.update(self.get_compute_quota( + self.app.client_manager.compute, parsed_args)) + if parsed_args.network: + quotas.update(self.get_network_quota(parsed_args)) + + result = [] + for resource, values in quotas.items(): + # NOTE(slaweq): there is no detailed quotas info for some resources + # and it should't be displayed here + if type(values) is dict: + result.append({ + 'resource': resource, + 'in_use': values.get('in_use'), + 'reserved': values.get('reserved'), + 'limit': values.get('limit') + }) + return (column_headers, + (utils.get_dict_properties( + s, columns, + ) for s in result)) def get_parser(self, prog_name): parser = super(ListQuota, self).get_parser(prog_name) + parser.add_argument( + '--project', + metavar='', + help=_('List quotas for this project (name or ID)'), + ) + parser.add_argument( + '--detail', + dest='detail', + action='store_true', + default=False, + help=_('Show details about quotas usage') + ) option = parser.add_mutually_exclusive_group(required=True) option.add_argument( '--compute', @@ -130,6 +282,8 @@ def take_action(self, parsed_args): project_ids = [getattr(p, 'id', '') for p in projects] if parsed_args.compute: + if parsed_args.detail: + return self._get_detailed_quotas(parsed_args) compute_client = self.app.client_manager.compute for p in project_ids: try: @@ -193,6 +347,9 @@ def take_action(self, parsed_args): ) for s in result)) if parsed_args.volume: + if parsed_args.detail: + LOG.warning("Volume service doesn't provide detailed quota" + " information") volume_client = self.app.client_manager.volume for p in project_ids: try: @@ -243,6 +400,8 @@ def take_action(self, parsed_args): ) for s in result)) if parsed_args.network: + if parsed_args.detail: + return self._get_detailed_quotas(parsed_args) client = self.app.client_manager.network for p in project_ids: try: @@ -410,7 +569,7 @@ def take_action(self, parsed_args): **network_kwargs) -class ShowQuota(command.ShowOne): +class ShowQuota(command.ShowOne, BaseQuota): _description = _("Show quotas for project or class") def get_parser(self, prog_name): @@ -438,62 +597,6 @@ def get_parser(self, prog_name): ) return parser - def _get_project(self, parsed_args): - if parsed_args.project is not None: - identity_client = self.app.client_manager.identity - project = utils.find_resource( - identity_client.projects, - parsed_args.project, - ) - project_id = project.id - project_name = project.name - elif self.app.client_manager.auth_ref: - # Get the project from the current auth - project = self.app.client_manager.auth_ref - project_id = project.project_id - project_name = project.project_name - else: - project = None - project_id = None - project_name = None - project_info = {} - project_info['id'] = project_id - project_info['name'] = project_name - return project_info - - def get_compute_volume_quota(self, client, parsed_args): - try: - if parsed_args.quota_class: - quota = client.quota_classes.get(parsed_args.project) - else: - project_info = self._get_project(parsed_args) - project = project_info['id'] - if parsed_args.default: - quota = client.quotas.defaults(project) - else: - quota = client.quotas.get(project) - except Exception as e: - if type(e).__name__ == 'EndpointNotFound': - return {} - else: - raise - return quota._info - - def get_network_quota(self, parsed_args): - if parsed_args.quota_class: - return {} - if self.app.client_manager.is_network_endpoint_enabled(): - project_info = self._get_project(parsed_args) - project = project_info['id'] - client = self.app.client_manager.network - if parsed_args.default: - network_quota = client.get_quota_default(project) - else: - network_quota = client.get_quota(project) - return network_quota - else: - return {} - def take_action(self, parsed_args): compute_client = self.app.client_manager.compute @@ -504,10 +607,10 @@ def take_action(self, parsed_args): # does not exist. If this is determined to be the # intended behaviour of the API we will validate # the argument with Identity ourselves later. - compute_quota_info = self.get_compute_volume_quota(compute_client, - parsed_args) - volume_quota_info = self.get_compute_volume_quota(volume_client, - parsed_args) + compute_quota_info = self.get_compute_quota(compute_client, + parsed_args) + volume_quota_info = self.get_volume_quota(volume_client, + parsed_args) network_quota_info = self.get_network_quota(parsed_args) # NOTE(reedip): Remove the below check once requirement for # Openstack SDK is fixed to version 0.9.12 and above diff --git a/openstackclient/tests/functional/common/test_quota.py b/openstackclient/tests/functional/common/test_quota.py index 76c69a4d03..859422812b 100644 --- a/openstackclient/tests/functional/common/test_quota.py +++ b/openstackclient/tests/functional/common/test_quota.py @@ -31,6 +31,38 @@ def setUpClass(cls): cls.PROJECT_NAME =\ cls.get_openstack_configuration_value('auth.project_name') + def test_quota_list_details_compute(self): + expected_headers = ["Resource", "In Use", "Reserved", "Limit"] + cmd_output = json.loads(self.openstack( + 'quota list -f json --detail --compute' + )) + self.assertIsNotNone(cmd_output) + resources = [] + for row in cmd_output: + row_headers = [str(r) for r in row.keys()] + self.assertEqual(sorted(expected_headers), sorted(row_headers)) + resources.append(row['Resource']) + # Ensure that returned quota is compute quota + self.assertIn("instances", resources) + # and that there is no network quota here + self.assertNotIn("networks", resources) + + def test_quota_list_details_network(self): + expected_headers = ["Resource", "In Use", "Reserved", "Limit"] + cmd_output = json.loads(self.openstack( + 'quota list -f json --detail --network' + )) + self.assertIsNotNone(cmd_output) + resources = [] + for row in cmd_output: + row_headers = [str(r) for r in row.keys()] + self.assertEqual(sorted(expected_headers), sorted(row_headers)) + resources.append(row['Resource']) + # Ensure that returned quota is network quota + self.assertIn("networks", resources) + # and that there is no compute quota here + self.assertNotIn("instances", resources) + def test_quota_list_network_option(self): if not self.haz_network: self.skipTest("No Network service present") diff --git a/openstackclient/tests/unit/common/test_quota.py b/openstackclient/tests/unit/common/test_quota.py index 1a3da31d78..4f9e321b92 100644 --- a/openstackclient/tests/unit/common/test_quota.py +++ b/openstackclient/tests/unit/common/test_quota.py @@ -197,6 +197,85 @@ def setUp(self): self.cmd = quota.ListQuota(self.app, None) + @staticmethod + def _get_detailed_reference_data(quota): + reference_data = [] + for name, values in quota.to_dict().items(): + if type(values) is dict: + if 'used' in values: + # For network quota it's "used" key instead of "in_use" + in_use = values['used'] + else: + in_use = values['in_use'] + resource_values = [ + in_use, + values['reserved'], + values['limit']] + reference_data.append(tuple([name] + resource_values)) + return reference_data + + def test_quota_list_details_compute(self): + detailed_quota = ( + compute_fakes.FakeQuota.create_one_comp_detailed_quota()) + + detailed_column_header = ( + 'Resource', + 'In Use', + 'Reserved', + 'Limit', + ) + detailed_reference_data = ( + self._get_detailed_reference_data(detailed_quota)) + + self.compute.quotas.get = mock.Mock(return_value=detailed_quota) + + arglist = [ + '--detail', '--compute', + ] + verifylist = [ + ('detail', True), + ('compute', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + ret_quotas = list(data) + + self.assertEqual(detailed_column_header, columns) + self.assertEqual( + sorted(detailed_reference_data), sorted(ret_quotas)) + + def test_quota_list_details_network(self): + detailed_quota = ( + network_fakes.FakeQuota.create_one_net_detailed_quota()) + + detailed_column_header = ( + 'Resource', + 'In Use', + 'Reserved', + 'Limit', + ) + detailed_reference_data = ( + self._get_detailed_reference_data(detailed_quota)) + + self.network.get_quota = mock.Mock(return_value=detailed_quota) + + arglist = [ + '--detail', '--network', + ] + verifylist = [ + ('detail', True), + ('network', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + ret_quotas = list(data) + + self.assertEqual(detailed_column_header, columns) + self.assertEqual( + sorted(detailed_reference_data), sorted(ret_quotas)) + def test_quota_list_compute(self): # Two projects with non-default quotas self.compute.quotas.get = mock.Mock( @@ -827,13 +906,13 @@ def test_quota_show(self): self.cmd.take_action(parsed_args) self.compute_quotas_mock.get.assert_called_once_with( - self.projects[0].id, + self.projects[0].id, detail=False ) self.volume_quotas_mock.get.assert_called_once_with( self.projects[0].id, ) self.network.get_quota.assert_called_once_with( - self.projects[0].id, + self.projects[0].id, details=False ) self.assertNotCalled(self.network.get_quota_default) @@ -889,12 +968,12 @@ def test_quota_show_no_project(self): self.cmd.take_action(parsed_args) self.compute_quotas_mock.get.assert_called_once_with( - identity_fakes.project_id, + identity_fakes.project_id, detail=False ) self.volume_quotas_mock.get.assert_called_once_with( identity_fakes.project_id, ) self.network.get_quota.assert_called_once_with( - identity_fakes.project_id, + identity_fakes.project_id, details=False ) self.assertNotCalled(self.network.get_quota_default) diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index 234bbd9b80..37535aa017 100644 --- a/openstackclient/tests/unit/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -1400,6 +1400,38 @@ def create_one_default_comp_quota(attrs=None): return quota + @staticmethod + def create_one_comp_detailed_quota(attrs=None): + """Create one quota""" + + attrs = attrs or {} + + quota_attrs = { + 'id': 'project-id-' + uuid.uuid4().hex, + 'cores': {'reserved': 0, 'in_use': 0, 'limit': 20}, + 'fixed_ips': {'reserved': 0, 'in_use': 0, 'limit': 30}, + 'injected_files': {'reserved': 0, 'in_use': 0, 'limit': 100}, + 'injected_file_content_bytes': { + 'reserved': 0, 'in_use': 0, 'limit': 10240}, + 'injected_file_path_bytes': { + 'reserved': 0, 'in_use': 0, 'limit': 255}, + 'instances': {'reserved': 0, 'in_use': 0, 'limit': 50}, + 'key_pairs': {'reserved': 0, 'in_use': 0, 'limit': 20}, + 'metadata_items': {'reserved': 0, 'in_use': 0, 'limit': 10}, + 'ram': {'reserved': 0, 'in_use': 0, 'limit': 51200}, + 'server_groups': {'reserved': 0, 'in_use': 0, 'limit': 10}, + 'server_group_members': {'reserved': 0, 'in_use': 0, 'limit': 10} + } + + quota_attrs.update(attrs) + quota = fakes.FakeResource( + info=copy.deepcopy(quota_attrs), + loaded=True) + + quota.project_id = quota_attrs['id'] + + return quota + class FakeLimits(object): """Fake limits""" diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index 28e92d1196..ee0919fdd5 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -1700,3 +1700,26 @@ def create_one_default_net_quota(attrs=None): info=copy.deepcopy(quota_attrs), loaded=True) return quota + + @staticmethod + def create_one_net_detailed_quota(attrs=None): + """Create one quota""" + attrs = attrs or {} + + quota_attrs = { + 'floating_ips': {'used': 0, 'reserved': 0, 'limit': 20}, + 'networks': {'used': 0, 'reserved': 0, 'limit': 25}, + 'ports': {'used': 0, 'reserved': 0, 'limit': 11}, + 'rbac_policies': {'used': 0, 'reserved': 0, 'limit': 15}, + 'routers': {'used': 0, 'reserved': 0, 'limit': 40}, + 'security_groups': {'used': 0, 'reserved': 0, 'limit': 10}, + 'security_group_rules': {'used': 0, 'reserved': 0, 'limit': 100}, + 'subnets': {'used': 0, 'reserved': 0, 'limit': 20}, + 'subnet_pools': {'used': 0, 'reserved': 0, 'limit': 30}} + + quota_attrs.update(attrs) + + quota = fakes.FakeResource( + info=copy.deepcopy(quota_attrs), + loaded=True) + return quota diff --git a/releasenotes/notes/list-detailed-quota-informations-1755129e1c68a252.yaml b/releasenotes/notes/list-detailed-quota-informations-1755129e1c68a252.yaml new file mode 100644 index 0000000000..7dbe202cdd --- /dev/null +++ b/releasenotes/notes/list-detailed-quota-informations-1755129e1c68a252.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add support for list detailed ``quota`` usage for project. + This can be done by passing ``--detail`` parameter to `quota list` command. + [Bug `1716043 `_] From 444a40c656b9f6007364ecd3bcf38964bbcd4556 Mon Sep 17 00:00:00 2001 From: Artem Goncharov Date: Tue, 26 Feb 2019 11:13:25 +0100 Subject: [PATCH 2024/3095] Add possibility to filter images using member_status In order to see image sharing membership it is required to additionally pass member_status filter to API. Otherwise only those with status 'all' will be returned. Thus adding possibility to see images shared with project to be approved or rejected. Change-Id: Ifd6e13e5a4ef09fbc29e76d464c93fbdbb178ae4 --- doc/source/cli/command-objects/image.rst | 7 +++ openstackclient/image/v2/image.py | 13 ++++++ .../tests/unit/image/v2/test_image.py | 43 +++++++++++++++++++ ...member-status-filter-2e118b2c93151223.yaml | 3 ++ 4 files changed, 66 insertions(+) create mode 100644 releasenotes/notes/add-member-status-filter-2e118b2c93151223.yaml diff --git a/doc/source/cli/command-objects/image.rst b/doc/source/cli/command-objects/image.rst index 95486e334b..0c5b0333cc 100644 --- a/doc/source/cli/command-objects/image.rst +++ b/doc/source/cli/command-objects/image.rst @@ -209,6 +209,7 @@ List available images [--property ] [--name ] [--status ] + [--member-status ] [--tag ] [--long] [--sort [:]] @@ -251,6 +252,12 @@ List available images *Image version 2 only* +.. option:: --member-status + + Filter images based on member status + + *Image version 2 only* + .. option:: --tag Filter images based on tag diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 06eebe984c..f4ab5bdc62 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -37,6 +37,7 @@ DEFAULT_DISK_FORMAT = 'raw' DISK_CHOICES = ["ami", "ari", "aki", "vhd", "vmdk", "raw", "qcow2", "vhdx", "vdi", "iso", "ploop"] +MEMBER_STATUS_CHOICES = ["accepted", "pending", "rejected", "all"] LOG = logging.getLogger(__name__) @@ -530,6 +531,16 @@ def get_parser(self, prog_name): default=None, help=_("Filter images based on status.") ) + parser.add_argument( + '--member-status', + metavar='', + default=None, + type=lambda s: s.lower(), + choices=MEMBER_STATUS_CHOICES, + help=(_("Filter images based on member status. " + "The supported options are: %s. ") % + ', '.join(MEMBER_STATUS_CHOICES)) + ) parser.add_argument( '--tag', metavar='', @@ -595,6 +606,8 @@ def take_action(self, parsed_args): kwargs['name'] = parsed_args.name if parsed_args.status: kwargs['status'] = parsed_args.status + if parsed_args.member_status: + kwargs['member_status'] = parsed_args.member_status if parsed_args.tag: kwargs['tag'] = parsed_args.tag if parsed_args.long: diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py index 170a7f036b..087d8751e7 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -644,6 +644,49 @@ def test_image_list_shared_option(self): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) + def test_image_list_shared_member_status_option(self): + arglist = [ + '--shared', + '--member-status', 'all' + ] + verifylist = [ + ('public', False), + ('private', False), + ('community', False), + ('shared', True), + ('long', False), + ('member_status', 'all') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + self.api_mock.image_list.assert_called_with( + shared=True, + member_status='all', + marker=self._image.id, + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) + + def test_image_list_shared_member_status_lower(self): + arglist = [ + '--shared', + '--member-status', 'ALl' + ] + verifylist = [ + ('public', False), + ('private', False), + ('community', False), + ('shared', True), + ('long', False), + ('member_status', 'all') + ] + self.check_parser(self.cmd, arglist, verifylist) + def test_image_list_long_option(self): arglist = [ '--long', diff --git a/releasenotes/notes/add-member-status-filter-2e118b2c93151223.yaml b/releasenotes/notes/add-member-status-filter-2e118b2c93151223.yaml new file mode 100644 index 0000000000..f7440df1c4 --- /dev/null +++ b/releasenotes/notes/add-member-status-filter-2e118b2c93151223.yaml @@ -0,0 +1,3 @@ +--- +features: + - Add ``--member-status`` option to ``image list`` command. From d43178c3a429f8f0f8d6c2e7f15cee75d7c4ba0e Mon Sep 17 00:00:00 2001 From: whoami-rajat Date: Tue, 26 Feb 2019 23:48:40 +0530 Subject: [PATCH 2025/3095] Disabling c-backup service for osc-functional-devstack-tips job Since swift isn't compatible with py3 currently and disabled for this gate job, c-backup service will always fail configuring. The backup related tests can be handled by other jobs having swift enabled. The c-backup service can be enabled along with swift services once swift is compatible with py3. This patch disables the the c-backup service for osc-functional-devstack-tips gate job. Change-Id: Ifd3a4e1a15f1365107a2a1367513e2ef79bd13cc --- .zuul.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.zuul.yaml b/.zuul.yaml index 3ce2f6ff21..356a97e6d5 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -166,6 +166,11 @@ s-container: false s-object: false s-proxy: false + # As swift is not available for this job, c-backup service won't be functional. + # The backup related tests can be handled by other jobs having swift enabled. + # The backup service along with swift services can be enabled once swift is + # compatible with py3 + c-backup: false tox_envlist: functional tox_install_siblings: true From 6475882fd8eaae98141d85c40c760cd220aedad1 Mon Sep 17 00:00:00 2001 From: Christian Schneemann Date: Thu, 7 Feb 2019 11:10:20 +0100 Subject: [PATCH 2026/3095] Typo fix Just a typo fix. Change-Id: I1d1fe6eb95c0b167265b3664314d764e3c316fe2 --- openstackclient/compute/v2/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index b608ecbd52..eb012e02ec 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -604,7 +604,7 @@ def get_parser(self, prog_name): type=_prefix_checked_value('port-id='), help=_("Create a NIC on the server and connect it to port. " "Specify option multiple times to create multiple NICs. " - "This is a wrapper for the '--nic port-id=' " + "This is a wrapper for the '--nic port-id=' " "parameter that provides simple syntax for the standard " "use case of connecting a new server to a given port. For " "more advanced use cases, refer to the '--nic' parameter."), From 24255ad0dde608a19d371a27c13714098097f185 Mon Sep 17 00:00:00 2001 From: whoami-rajat Date: Thu, 13 Dec 2018 11:15:09 +0530 Subject: [PATCH 2027/3095] Fix: Restore output 'VolumeBackupsRestore' object is not iterable VolumeBackupsRetore object has '_info' attribute which contains the output data of the restore command which should be returned instead of the 'VolumeBackupsRestore' object. Change-Id: I64b75649c1ac9c24e05a197f7280975564b4d386 Story: 2004740 Task: 28811 --- .zuul.yaml | 4 +- .../tests/functional/volume/v2/test_backup.py | 58 +++++++++++++++++++ .../tests/unit/volume/v2/test_backup.py | 6 +- openstackclient/volume/v2/backup.py | 4 +- 4 files changed, 67 insertions(+), 5 deletions(-) create mode 100644 openstackclient/tests/functional/volume/v2/test_backup.py diff --git a/.zuul.yaml b/.zuul.yaml index 356a97e6d5..9a94dd934b 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -166,11 +166,11 @@ s-container: false s-object: false s-proxy: false - # As swift is not available for this job, c-backup service won't be functional. + # As swift is not available for this job, c-bak service won't be functional. # The backup related tests can be handled by other jobs having swift enabled. # The backup service along with swift services can be enabled once swift is # compatible with py3 - c-backup: false + c-bak: false tox_envlist: functional tox_install_siblings: true diff --git a/openstackclient/tests/functional/volume/v2/test_backup.py b/openstackclient/tests/functional/volume/v2/test_backup.py new file mode 100644 index 0000000000..e4890b00ea --- /dev/null +++ b/openstackclient/tests/functional/volume/v2/test_backup.py @@ -0,0 +1,58 @@ +# 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 json +import uuid + +from openstackclient.tests.functional.volume.v2 import common + + +class VolumeBackupTests(common.BaseVolumeTests): + """Functional tests for volume backups. """ + + def setUp(self): + super(VolumeBackupTests, self).setUp() + self.backup_enabled = False + serv_list = json.loads(self.openstack('volume service list -f json')) + for service in serv_list: + if service['Binary'] == 'cinder-backup': + if service['Status'] == 'enabled': + self.backup_enabled = True + + def test_volume_backup_restore(self): + """Test restore backup""" + if not self.backup_enabled: + self.skipTest('Backup service is not enabled') + vol_id = uuid.uuid4().hex + # create a volume + json.loads(self.openstack( + 'volume create -f json ' + + '--size 1 ' + + vol_id + )) + # create a backup + backup = json.loads(self.openstack( + 'volume backup create -f json ' + + vol_id + )) + + self.wait_for_status("volume", vol_id, "available") + self.wait_for_status("backup", backup['id'], "available") + # restore the backup + backup_restored = json.loads(self.openstack( + 'volume backup restore -f json %s %s' + % (backup['id'], vol_id))) + self.assertEqual(backup_restored['backup_id'], backup['id']) + self.wait_for_status("backup", backup['id'], "available") + self.wait_for_status("volume", backup_restored['volume_id'], + "available") + self.addCleanup(self.openstack, 'volume delete %s' % vol_id) diff --git a/openstackclient/tests/unit/volume/v2/test_backup.py b/openstackclient/tests/unit/volume/v2/test_backup.py index a8e81c7eb8..9a2ce71809 100644 --- a/openstackclient/tests/unit/volume/v2/test_backup.py +++ b/openstackclient/tests/unit/volume/v2/test_backup.py @@ -367,7 +367,9 @@ def setUp(self): self.backups_mock.get.return_value = self.backup self.volumes_mock.get.return_value = self.volume - self.restores_mock.restore.return_value = None + self.restores_mock.restore.return_value = ( + volume_fakes.FakeVolume.create_one_volume( + {'id': self.volume['id']})) # Get the command object to mock self.cmd = backup.RestoreVolumeBackup(self.app, None) @@ -385,7 +387,7 @@ def test_backup_restore(self): result = self.cmd.take_action(parsed_args) self.restores_mock.restore.assert_called_with(self.backup.id, self.backup.volume_id) - self.assertIsNone(result) + self.assertIsNotNone(result) class TestBackupSet(TestBackup): diff --git a/openstackclient/volume/v2/backup.py b/openstackclient/volume/v2/backup.py index 60633a7080..d4aec8d747 100644 --- a/openstackclient/volume/v2/backup.py +++ b/openstackclient/volume/v2/backup.py @@ -319,7 +319,9 @@ def take_action(self, parsed_args): backup = utils.find_resource(volume_client.backups, parsed_args.backup) destination_volume = utils.find_resource(volume_client.volumes, parsed_args.volume) - return volume_client.restores.restore(backup.id, destination_volume.id) + backup = volume_client.restores.restore(backup.id, + destination_volume.id) + return zip(*sorted(six.iteritems(backup._info))) class RestoreBackup(RestoreVolumeBackup): From e776a4f0260af1d2ae66439e647794395d470578 Mon Sep 17 00:00:00 2001 From: David Rabel Date: Fri, 22 Feb 2019 15:21:17 +0100 Subject: [PATCH 2028/3095] Add --attached / --detached parameter to volume set As to reflect cinder reset-state --attach-status functionality, this patch adds --attached / --detached parameter to OSC's volume set command. Change-Id: Ic8ee928c9ab0e579512cfb7608f63bfcc2993c7b Closes-Bug: #1745699 --- doc/source/cli/command-objects/volume.rst | 17 +++++++++ .../tests/unit/volume/v2/test_volume.py | 36 +++++++++++++++++++ openstackclient/volume/v2/volume.py | 35 ++++++++++++++++++ .../notes/bug-1745699-afa7318b9dc96696.yaml | 7 ++++ 4 files changed, 95 insertions(+) create mode 100644 releasenotes/notes/bug-1745699-afa7318b9dc96696.yaml diff --git a/doc/source/cli/command-objects/volume.rst b/doc/source/cli/command-objects/volume.rst index 5c86d10deb..fc6188c0a8 100644 --- a/doc/source/cli/command-objects/volume.rst +++ b/doc/source/cli/command-objects/volume.rst @@ -262,6 +262,7 @@ Set volume properties [--property [...] ] [--image-property [...] ] [--state ] + [--attached | --detached ] [--type ] [--retype-policy ] [--bootable | --non-bootable] @@ -341,6 +342,22 @@ Set volume properties *Volume version 2 only* +.. option:: --attached + + Set volume attachment status to "attached" (admin only) + (This option simply changes the state of the volume in the database with + no regard to actual status, exercise caution when using) + + *Volume version 2 only* + +.. option:: --deattach + + Set volume attachment status to "detached" (admin only) + (This option simply changes the state of the volume in the database with + no regard to actual status, exercise caution when using) + + *Volume version 2 only* + .. _volume_set-volume: .. describe:: diff --git a/openstackclient/tests/unit/volume/v2/test_volume.py b/openstackclient/tests/unit/volume/v2/test_volume.py index 1f53ce9471..dbe69ea0ff 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume.py +++ b/openstackclient/tests/unit/volume/v2/test_volume.py @@ -1327,6 +1327,42 @@ def test_volume_set_state_failed(self): self.volumes_mock.reset_state.assert_called_with( self.new_volume.id, 'error') + def test_volume_set_attached(self): + arglist = [ + '--attached', + self.new_volume.id + ] + verifylist = [ + ('attached', True), + ('detached', False), + ('volume', self.new_volume.id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.volumes_mock.reset_state.assert_called_with( + self.new_volume.id, attach_status='attached', state=None) + self.assertIsNone(result) + + def test_volume_set_detached(self): + arglist = [ + '--detached', + self.new_volume.id + ] + verifylist = [ + ('attached', False), + ('detached', True), + ('volume', self.new_volume.id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.volumes_mock.reset_state.assert_called_with( + self.new_volume.id, attach_status='detached', state=None) + self.assertIsNone(result) + def test_volume_set_bootable(self): arglist = [ ['--bootable', self.new_volume.id], diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index 14a454c228..fa587b5f67 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -559,6 +559,25 @@ def get_parser(self, prog_name): 'in the database with no regard to actual status, ' 'exercise caution when using)'), ) + attached_group = parser.add_mutually_exclusive_group() + attached_group.add_argument( + "--attached", + action="store_true", + help=_('Set volume attachment status to "attached" ' + '(admin only) ' + '(This option simply changes the state of the volume ' + 'in the database with no regard to actual status, ' + 'exercise caution when using)'), + ) + attached_group.add_argument( + "--detached", + action="store_true", + help=_('Set volume attachment status to "detached" ' + '(admin only) ' + '(This option simply changes the state of the volume ' + 'in the database with no regard to actual status, ' + 'exercise caution when using)'), + ) parser.add_argument( '--type', metavar='', @@ -645,6 +664,22 @@ def take_action(self, parsed_args): except Exception as e: LOG.error(_("Failed to set volume state: %s"), e) result += 1 + if parsed_args.attached: + try: + volume_client.volumes.reset_state( + volume.id, state=None, + attach_status="attached") + except Exception as e: + LOG.error(_("Failed to set volume attach-status: %s"), e) + result += 1 + if parsed_args.detached: + try: + volume_client.volumes.reset_state( + volume.id, state=None, + attach_status="detached") + except Exception as e: + LOG.error(_("Failed to set volume attach-status: %s"), e) + result += 1 if parsed_args.bootable or parsed_args.non_bootable: try: volume_client.volumes.set_bootable( diff --git a/releasenotes/notes/bug-1745699-afa7318b9dc96696.yaml b/releasenotes/notes/bug-1745699-afa7318b9dc96696.yaml new file mode 100644 index 0000000000..68b48cb03d --- /dev/null +++ b/releasenotes/notes/bug-1745699-afa7318b9dc96696.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Add ``--attached`` and ``--detached`` options to ``volume set`` command to set the + volume status in the database. This is the functional equivalent to + ``cinder reset-state --attach-status``. + [`bug 1745699 `_ \ No newline at end of file From c79de8a90bc1f1149b8302052580e5a76876724c Mon Sep 17 00:00:00 2001 From: Pavlo Shchelokovskyy Date: Wed, 27 Feb 2019 14:21:08 +0200 Subject: [PATCH 2029/3095] Paginate over usage list to return all usages since nova api 2.40 the os-simple-tenant-usage API supports pagination and will by default return a number of entities configured internally in Nova. This means that when there are many enough projects, the single call to usage.list() will not return usages for all projects. This patch effectively copy-pastes the logic to paginate over usage list results from novaclient/v2/shell.py code. Change-Id: I1b639fe386b7b7db3223f6965495094b9d51533a Story: #2005099 Task: #29713 --- openstackclient/compute/v2/usage.py | 50 ++++++++++++++++++- .../tests/unit/compute/v2/fakes.py | 1 + .../tests/unit/compute/v2/test_usage.py | 26 ++++++++++ 3 files changed, 76 insertions(+), 1 deletion(-) diff --git a/openstackclient/compute/v2/usage.py b/openstackclient/compute/v2/usage.py index 4320bf90b6..f84cd61dfb 100644 --- a/openstackclient/compute/v2/usage.py +++ b/openstackclient/compute/v2/usage.py @@ -15,8 +15,10 @@ """Usage action implementations""" +import collections import datetime +from novaclient import api_versions from osc_lib.command import command from osc_lib import utils import six @@ -24,6 +26,36 @@ from openstackclient.i18n import _ +def _get_usage_marker(usage): + marker = None + if hasattr(usage, 'server_usages') and usage.server_usages: + marker = usage.server_usages[-1]['instance_id'] + return marker + + +def _get_usage_list_marker(usage_list): + marker = None + if usage_list: + marker = _get_usage_marker(usage_list[-1]) + return marker + + +def _merge_usage(usage, next_usage): + usage.server_usages.extend(next_usage.server_usages) + usage.total_hours += next_usage.total_hours + usage.total_memory_mb_usage += next_usage.total_memory_mb_usage + usage.total_vcpus_usage += next_usage.total_vcpus_usage + usage.total_local_gb_usage += next_usage.total_local_gb_usage + + +def _merge_usage_list(usages, next_usage_list): + for next_usage in next_usage_list: + if next_usage.tenant_id in usages: + _merge_usage(usages[next_usage.tenant_id], next_usage) + else: + usages[next_usage.tenant_id] = next_usage + + class ListUsage(command.Lister): _description = _("List resource usage per project") @@ -83,7 +115,23 @@ def _format_project(project): else: end = now + datetime.timedelta(days=1) - usage_list = compute_client.usage.list(start, end, detailed=True) + if compute_client.api_version < api_versions.APIVersion("2.40"): + usage_list = compute_client.usage.list(start, end, detailed=True) + else: + # If the number of instances used to calculate the usage is greater + # than CONF.api.max_limit, the usage will be split across multiple + # requests and the responses will need to be merged back together. + usages = collections.OrderedDict() + usage_list = compute_client.usage.list(start, end, detailed=True) + _merge_usage_list(usages, usage_list) + marker = _get_usage_list_marker(usage_list) + while marker: + next_usage_list = compute_client.usage.list( + start, end, detailed=True, marker=marker) + marker = _get_usage_list_marker(next_usage_list) + if marker: + _merge_usage_list(usages, next_usage_list) + usage_list = list(usages.values()) # Cache the project list project_cache = {} diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index 234bbd9b80..38f4ff67f1 100644 --- a/openstackclient/tests/unit/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -1304,6 +1304,7 @@ def create_one_usage(attrs=None): 'local_gb': 1, 'memory_mb': 512, 'name': 'usage-name-' + uuid.uuid4().hex, + 'instance_id': uuid.uuid4().hex, 'state': 'active', 'uptime': 3600, 'vcpus': 1 diff --git a/openstackclient/tests/unit/compute/v2/test_usage.py b/openstackclient/tests/unit/compute/v2/test_usage.py index a7aa1374e6..76dcc963fb 100644 --- a/openstackclient/tests/unit/compute/v2/test_usage.py +++ b/openstackclient/tests/unit/compute/v2/test_usage.py @@ -14,6 +14,7 @@ import datetime import mock +from novaclient import api_versions from openstackclient.compute.v2 import usage from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes @@ -104,6 +105,31 @@ def test_usage_list_with_options(self): self.assertEqual(self.columns, columns) self.assertEqual(tuple(self.data), tuple(data)) + def test_usage_list_with_pagination(self): + arglist = [] + verifylist = [ + ('start', None), + ('end', None), + ] + + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.40') + self.usage_mock.list.reset_mock() + self.usage_mock.list.side_effect = [self.usages, []] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.projects_mock.list.assert_called_with() + self.usage_mock.list.assert_has_calls([ + mock.call(mock.ANY, mock.ANY, detailed=True), + mock.call(mock.ANY, mock.ANY, detailed=True, + marker=self.usages[0]['server_usages'][0]['instance_id']) + ]) + self.assertEqual(self.columns, columns) + self.assertEqual(tuple(self.data), tuple(data)) + class TestUsageShow(TestUsage): From be7a75814ca4a503dd384f36166f93f12f6cb8da Mon Sep 17 00:00:00 2001 From: Doug Wiegley Date: Wed, 13 Feb 2019 14:17:33 -0700 Subject: [PATCH 2030/3095] Add 'security_group' type support to network rbac commands Partial-Bug: #1817119 Depends-On: https://review.openstack.org/635311 Change-Id: I5f132fa54714514d8dae62df8bc494f3f6476768 --- .../cli/command-objects/network-rbac.rst | 4 +- openstackclient/network/v2/network_rbac.py | 13 ++++-- .../tests/unit/network/v2/fakes.py | 33 +++++++++++++++ .../unit/network/v2/test_network_rbac.py | 40 +++++++++++++++++++ ...c-add-security-group-35370701a06ac906.yaml | 5 +++ 5 files changed, 89 insertions(+), 6 deletions(-) create mode 100644 releasenotes/notes/rbac-add-security-group-35370701a06ac906.yaml diff --git a/doc/source/cli/command-objects/network-rbac.rst b/doc/source/cli/command-objects/network-rbac.rst index 45fd354deb..22733a2cd2 100644 --- a/doc/source/cli/command-objects/network-rbac.rst +++ b/doc/source/cli/command-objects/network-rbac.rst @@ -26,7 +26,7 @@ Create network RBAC policy .. option:: --type - Type of the object that RBAC policy affects ("qos_policy" or "network") (required) + Type of the object that RBAC policy affects ("security_group", "qos_policy" or "network") (required) .. option:: --action @@ -90,7 +90,7 @@ List network RBAC policies .. option:: --type - List network RBAC policies according to given object type ("qos_policy" or "network") + List network RBAC policies according to given object type ("security_group", "qos_policy" or "network") .. option:: --action diff --git a/openstackclient/network/v2/network_rbac.py b/openstackclient/network/v2/network_rbac.py index 6cf82559dd..140c837e38 100644 --- a/openstackclient/network/v2/network_rbac.py +++ b/openstackclient/network/v2/network_rbac.py @@ -48,6 +48,10 @@ def _get_attrs(client_manager, parsed_args): object_id = network_client.find_qos_policy( parsed_args.rbac_object, ignore_missing=False).id + if parsed_args.type == 'security_group': + object_id = network_client.find_security_group( + parsed_args.rbac_object, + ignore_missing=False).id attrs['object_id'] = object_id identity_client = client_manager.identity @@ -87,9 +91,9 @@ def get_parser(self, prog_name): '--type', metavar="", required=True, - choices=['qos_policy', 'network'], + choices=['security_group', 'qos_policy', 'network'], help=_('Type of the object that RBAC policy ' - 'affects ("qos_policy" or "network")') + 'affects ("security_group", "qos_policy" or "network")') ) parser.add_argument( '--action', @@ -178,9 +182,10 @@ def get_parser(self, prog_name): parser.add_argument( '--type', metavar='', - choices=['qos_policy', 'network'], + choices=['security_group', 'qos_policy', 'network'], help=_('List network RBAC policies according to ' - 'given object type ("qos_policy" or "network")') + 'given object type ("security_group", "qos_policy" ' + 'or "network")') ) parser.add_argument( '--action', diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index 28e92d1196..5860ff452f 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -908,6 +908,39 @@ def get_qos_policies(qos_policies=None, count=2): return mock.Mock(side_effect=qos_policies) +class FakeNetworkSecGroup(object): + """Fake one security group.""" + + @staticmethod + def create_one_security_group(attrs=None): + """Create a fake security group. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object with name, id, etc. + """ + attrs = attrs or {} + sg_id = attrs.get('id') or 'security-group-id-' + uuid.uuid4().hex + + # Set default attributes. + security_group_attrs = { + 'name': 'security-group-name-' + uuid.uuid4().hex, + 'id': sg_id, + 'tenant_id': 'project-id-' + uuid.uuid4().hex, + 'description': 'security-group-description-' + uuid.uuid4().hex + } + + security_group = fakes.FakeResource( + info=copy.deepcopy(security_group_attrs), + loaded=True) + + # Set attributes with special mapping in OpenStack SDK. + security_group.project_id = security_group_attrs['tenant_id'] + + return security_group + + class FakeNetworkQosRule(object): """Fake one or more Network QoS rules.""" diff --git a/openstackclient/tests/unit/network/v2/test_network_rbac.py b/openstackclient/tests/unit/network/v2/test_network_rbac.py index 70c3852868..96440091c1 100644 --- a/openstackclient/tests/unit/network/v2/test_network_rbac.py +++ b/openstackclient/tests/unit/network/v2/test_network_rbac.py @@ -37,6 +37,7 @@ class TestCreateNetworkRBAC(TestNetworkRBAC): network_object = network_fakes.FakeNetwork.create_one_network() qos_object = network_fakes.FakeNetworkQosPolicy.create_one_qos_policy() + sg_object = network_fakes.FakeNetworkSecGroup.create_one_security_group() project = identity_fakes_v3.FakeProject.create_one_project() rbac_policy = network_fakes.FakeNetworkRBAC.create_one_network_rbac( attrs={'tenant_id': project.id, @@ -74,6 +75,8 @@ def setUp(self): return_value=self.network_object) self.network.find_qos_policy = mock.Mock( return_value=self.qos_object) + self.network.find_security_group = mock.Mock( + return_value=self.sg_object) self.projects_mock.get.return_value = self.project def test_network_rbac_create_no_type(self): @@ -258,6 +261,43 @@ def test_network_rbac_create_qos_object(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + def test_network_rbac_create_security_group_object(self): + self.rbac_policy.object_type = 'security_group' + self.rbac_policy.object_id = self.sg_object.id + arglist = [ + '--type', 'security_group', + '--action', self.rbac_policy.action, + '--target-project', self.rbac_policy.target_tenant, + self.sg_object.name, + ] + verifylist = [ + ('type', 'security_group'), + ('action', self.rbac_policy.action), + ('target_project', self.rbac_policy.target_tenant), + ('rbac_object', self.sg_object.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_rbac_policy.assert_called_with(**{ + 'object_id': self.sg_object.id, + 'object_type': 'security_group', + 'action': self.rbac_policy.action, + 'target_tenant': self.rbac_policy.target_tenant, + }) + self.data = [ + self.rbac_policy.action, + self.rbac_policy.id, + self.sg_object.id, + 'security_group', + self.rbac_policy.tenant_id, + self.rbac_policy.target_tenant, + ] + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + class TestDeleteNetworkRBAC(TestNetworkRBAC): diff --git a/releasenotes/notes/rbac-add-security-group-35370701a06ac906.yaml b/releasenotes/notes/rbac-add-security-group-35370701a06ac906.yaml new file mode 100644 index 0000000000..f21eebb795 --- /dev/null +++ b/releasenotes/notes/rbac-add-security-group-35370701a06ac906.yaml @@ -0,0 +1,5 @@ +features: + - | + Add ``security_group`` as a valid ``--type`` value for the + ``network rbac create`` and ``network rbac list`` commands. + From 239b103849b96213dc9cb317006346ce311228e4 Mon Sep 17 00:00:00 2001 From: Surya Seetharaman Date: Mon, 28 Jan 2019 19:30:00 +0100 Subject: [PATCH 2031/3095] API microversion 2.69: Handles Down Cells This patch explicitly points out the change needed while forming the detailed lists for servers. In those cases where the server response for ``openstack server list`` has the flavor and image keys missing for the instances in the down cell, the servers will be skipped from being processed. Depends-On: https://review.openstack.org/591657/ Related to blueprint handling-down-cell Change-Id: Ibcfe9febdc45db1cb86c6e88f65976feceb01c02 --- openstackclient/compute/v2/server.py | 8 ++++ .../tests/unit/compute/v2/test_server.py | 44 +++++++++++++++++++ ...p-handling-down-cell-ab8af2897f1a8c85.yaml | 9 ++++ 3 files changed, 61 insertions(+) create mode 100644 releasenotes/notes/bp-handling-down-cell-ab8af2897f1a8c85.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index b608ecbd52..36a674339f 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1314,6 +1314,14 @@ def take_action(self, parsed_args): # Populate image_name, image_id, flavor_name and flavor_id attributes # of server objects so that we can display those columns. for s in data: + if compute_client.api_version >= api_versions.APIVersion('2.69'): + # NOTE(tssurya): From 2.69, we will have the keys 'flavor' + # and 'image' missing in the server response during + # infrastructure failure situations. + # For those servers with partial constructs we just skip the + # processing of the image and flavor informations. + if not hasattr(s, 'image') or not hasattr(s, 'flavor'): + continue if 'id' in s.image: image = images.get(s.image['id']) if image: diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index c529d840de..c30af8fb8e 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -2272,6 +2272,50 @@ def test_server_list_with_invalid_changes_since(self, mock_parse_isotime): 'Invalid time value' ) + def test_server_list_v269_with_partial_constructs(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.69') + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + # include "partial results" from non-responsive part of + # infrastructure. + server_dict = { + "id": "server-id-95a56bfc4xxxxxx28d7e418bfd97813a", + "status": "UNKNOWN", + "tenant_id": "6f70656e737461636b20342065766572", + "created": "2018-12-03T21:06:18Z", + "links": [ + { + "href": "http://fake/v2.1/", + "rel": "self" + }, + { + "href": "http://fake", + "rel": "bookmark" + } + ], + # We need to pass networks as {} because its defined as a property + # of the novaclient Server class which gives {} by default. If not + # it will fail at formatting the networks info later on. + "networks": {} + } + server = compute_fakes.fakes.FakeResource( + info=server_dict, + ) + self.servers.append(server) + columns, data = self.cmd.take_action(parsed_args) + # get the first three servers out since our interest is in the partial + # server. + next(data) + next(data) + next(data) + partial_server = next(data) + expected_row = ( + 'server-id-95a56bfc4xxxxxx28d7e418bfd97813a', '', + 'UNKNOWN', '', '', '') + self.assertEqual(expected_row, partial_server) + class TestServerLock(TestServer): diff --git a/releasenotes/notes/bp-handling-down-cell-ab8af2897f1a8c85.yaml b/releasenotes/notes/bp-handling-down-cell-ab8af2897f1a8c85.yaml new file mode 100644 index 0000000000..f9e1ad278d --- /dev/null +++ b/releasenotes/notes/bp-handling-down-cell-ab8af2897f1a8c85.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + From microversion 2.69 the results of ``openstack server list`` and + ``openstack server show`` may contain missing information in their outputs + when there are partial infrastructure failure periods in the deployment. + See `Handling Down Cells`_ for more information on the missing keys/info. + + .. _Handling Down Cells: https://developer.openstack.org/api-guide/compute/down_cells.html \ No newline at end of file From d52920b3878479f7dd549cba1679870b4f00ee33 Mon Sep 17 00:00:00 2001 From: Kailun Qin Date: Wed, 27 Feb 2019 23:02:52 +0800 Subject: [PATCH 2032/3095] Add network segment range command object Add network segment range command object in support of network segment range management. This patch set includes documentation, unit tests and functional tests (currently skipped unit network segment range enabled in Neutron by default) for the following new commands: - "os network segment range create" - "os network segment range delete" - "os network segment range list" - "os network segment range set" - "os network segment range show" Co-authored-by: Allain Legacy [depends on removed by dtroyer as those are all +W and trying to pass the gate, OSC has it's freeze dealine looming] Depends: https://review.openstack.org/624708 Depends: https://review.openstack.org/624709 Depends: https://review.openstack.org/638386 Partially-implements: blueprint network-segment-range-management Change-Id: I335692f2db5be07c1c164f09b13f1abb80b7ba33 --- .zuul.yaml | 1 + .../command-objects/network_segment_range.rst | 168 ++++++ doc/source/cli/commands.rst | 1 + .../network/v2/network_segment_range.py | 458 +++++++++++++++ .../network/v2/test_network_segment_range.py | 145 +++++ .../tests/unit/network/v2/fakes.py | 60 ++ .../network/v2/test_network_segment_range.py | 552 ++++++++++++++++++ ...ent-range-management-0abf03fe03eea149.yaml | 6 + setup.cfg | 6 + 9 files changed, 1397 insertions(+) create mode 100644 doc/source/cli/command-objects/network_segment_range.rst create mode 100644 openstackclient/network/v2/network_segment_range.py create mode 100644 openstackclient/tests/functional/network/v2/test_network_segment_range.py create mode 100644 openstackclient/tests/unit/network/v2/test_network_segment_range.py create mode 100644 releasenotes/notes/bp-network-segment-range-management-0abf03fe03eea149.yaml diff --git a/.zuul.yaml b/.zuul.yaml index 3ce2f6ff21..9d3845c639 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -113,6 +113,7 @@ # NOTE(amotoki): Some neutron features are enabled by devstack plugin neutron: https://git.openstack.org/openstack/neutron devstack_services: + neutron-network-segment-range: true neutron-segments: true q-metering: true q-qos: true diff --git a/doc/source/cli/command-objects/network_segment_range.rst b/doc/source/cli/command-objects/network_segment_range.rst new file mode 100644 index 0000000000..71155d2224 --- /dev/null +++ b/doc/source/cli/command-objects/network_segment_range.rst @@ -0,0 +1,168 @@ +===================== +network segment range +===================== + +A **network segment range** is a resource for tenant network segment +allocation. +A network segment range exposes the segment range management to be administered +via the Neutron API. In addition, it introduces the ability for the +administrator to control the segment ranges globally or on a per-tenant basis. + +Network v2 + +network segment range create +---------------------------- + +Create new network segment range + +.. program:: network segment range create +.. code:: bash + + openstack network segment range create + (--private | --shared) + [--project [--project-domain ]] + --network-type + [--physical-network ] + --minimum + --maximum + + +.. option:: --private + + Network segment range is assigned specifically to the project + +.. option:: --shared + + Network segment range is shared with other projects + +.. option:: --project + + Network segment range owner (name or ID). Optional when the segment + range is shared + +.. option:: --project-domain + + Domain the project belongs to (name or ID). + This can be used in case collisions between project names exist. + +.. option:: --physical-network + + Physical network name of this network segment range + +.. option:: --network-type + + Network type of this network segment range + (geneve, gre, vlan or vxlan) + +.. option:: --minimum + + Minimum segment identifier for this network segment range which is based + on the network type, VLAN ID for vlan network type and tunnel ID for + geneve, gre and vxlan network types + +.. option:: --maximum + + Maximum segment identifier for this network segment range which is based + on the network type, VLAN ID for vlan network type and tunnel ID for + geneve, gre and vxlan network types + +.. _network_segment_range_create-name: +.. describe:: + + Name of new network segment range + +network segment range delete +---------------------------- + +Delete network segment range(s) + +.. program:: network segment range delete +.. code:: bash + + openstack network segment range delete + [ ...] + +.. _network_segment_range_delete-network-segment-range: +.. describe:: + + Network segment range (s) to delete (name or ID) + +network segment range list +-------------------------- + +List network segment ranges + +.. program:: network segment range list +.. code:: bash + + openstack network segment range list + [--long] + [--used | --unused] + [--available | --unavailable] + +.. option:: --long + + List additional fields in output + +.. option:: --used + + List network segment ranges that have segments in use + +.. option:: --unused + + List network segment ranges that do not have segments not in use + +.. option:: --available + + List network segment ranges that have available segments + +.. option:: --unavailable + + List network segment ranges without available segments + +network segment range set +------------------------- + +Set network segment range properties + +.. program:: network segment range set +.. code:: bash + + openstack network segment range set + [--name ] + [--minimum ] + [--maximum ] + + +.. option:: --name + + Set network segment range name + +.. option:: --minimum + + Set network segment range minimum segment identifier + +.. option:: --maximum + + Set network segment range maximum segment identifier + +.. _network_segment_range_set-network-segment-range: +.. describe:: + + Network segment range to modify (name or ID) + +network segment range show +-------------------------- + +Display network segment range details + +.. program:: network segment range show +.. code:: bash + + openstack network segment range show + + +.. _network_segment_range_show-network-segment-range: +.. describe:: + + Network segment range to display (name or ID) diff --git a/doc/source/cli/commands.rst b/doc/source/cli/commands.rst index cdd5e63c95..e302fdad05 100644 --- a/doc/source/cli/commands.rst +++ b/doc/source/cli/commands.rst @@ -125,6 +125,7 @@ referring to both Compute and Volume quotas. * ``network qos policy``: (**Network**) - a QoS policy for network resources * ``network qos rule type``: (**Network**) - list of QoS available rule types * ``network segment``: (**Network**) - a segment of a virtual network +* ``network segment range``: (**Network**) - a segment range for tenant network segment allocation * ``network service provider``: (**Network**) - a driver providing a network service * ``object``: (**Object Storage**) a single file in the Object Storage * ``object store account``: (**Object Storage**) owns a group of Object Storage resources diff --git a/openstackclient/network/v2/network_segment_range.py b/openstackclient/network/v2/network_segment_range.py new file mode 100644 index 0000000000..f5c8ccbc60 --- /dev/null +++ b/openstackclient/network/v2/network_segment_range.py @@ -0,0 +1,458 @@ +# Copyright (c) 2019, 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. +# + +"""Network segment action implementations""" + +import itertools +import logging + +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils +import six + +from openstackclient.i18n import _ +from openstackclient.identity import common as identity_common +from openstackclient.network import sdk_utils + + +LOG = logging.getLogger(__name__) + + +def _get_columns(item): + return sdk_utils.get_osc_show_columns_for_sdk_resource(item, {}) + + +def _get_ranges(item): + item = [int(i) if isinstance(i, six.string_types) else i for i in item] + for a, b in itertools.groupby(enumerate(item), lambda xy: xy[1] - xy[0]): + b = list(b) + yield "%s-%s" % (b[0][1], b[-1][1]) if b[0][1] != b[-1][1] else \ + str(b[0][1]) + + +def _hack_tuple_value_update_by_index(tup, index, value): + lot = list(tup) + lot[index] = value + return tuple(lot) + + +def _is_prop_empty(columns, props, prop_name): + return True if not props[columns.index(prop_name)] else False + + +def _exchange_dict_keys_with_values(orig_dict): + updated_dict = dict() + for k, v in six.iteritems(orig_dict): + k = [k] + if not updated_dict.get(v): + updated_dict[v] = k + else: + updated_dict[v].extend(k) + return updated_dict + + +def _update_available_from_props(columns, props): + index_available = columns.index('available') + props = _hack_tuple_value_update_by_index( + props, index_available, list(_get_ranges(props[index_available]))) + return props + + +def _update_used_from_props(columns, props): + index_used = columns.index('used') + updated_used = _exchange_dict_keys_with_values(props[index_used]) + for k, v in six.iteritems(updated_used): + updated_used[k] = list(_get_ranges(v)) + props = _hack_tuple_value_update_by_index( + props, index_used, updated_used) + return props + + +def _update_additional_fields_from_props(columns, props): + props = _update_available_from_props(columns, props) + props = _update_used_from_props(columns, props) + return props + + +class CreateNetworkSegmentRange(command.ShowOne): + _description = _("Create new network segment range") + + def get_parser(self, prog_name): + parser = super(CreateNetworkSegmentRange, self).get_parser(prog_name) + shared_group = parser.add_mutually_exclusive_group() + shared_group.add_argument( + "--private", + dest="private", + action="store_true", + help=_('Network segment range is assigned specifically to the ' + 'project'), + ) + shared_group.add_argument( + "--shared", + dest="shared", + action="store_true", + help=_('Network segment range is shared with other projects'), + ) + parser.add_argument( + 'name', + metavar='', + help=_('Name of new network segment range') + ) + parser.add_argument( + '--project', + metavar='', + help=_('Network segment range owner (name or ID). Optional when ' + 'the segment range is shared'), + ) + identity_common.add_project_domain_option_to_parser(parser) + parser.add_argument( + '--network-type', + metavar='', + choices=['geneve', 'gre', 'vlan', 'vxlan'], + required=True, + help=_('Network type of this network segment range ' + '(geneve, gre, vlan or vxlan)'), + ) + parser.add_argument( + '--physical-network', + metavar='', + help=_('Physical network name of this network segment range'), + ) + parser.add_argument( + '--minimum', + metavar='', + type=int, + required=True, + help=_('Minimum segment identifier for this network segment ' + 'range which is based on the network type, VLAN ID for ' + 'vlan network type and tunnel ID for geneve, gre and vxlan ' + 'network types'), + ) + parser.add_argument( + '--maximum', + metavar='', + type=int, + required=True, + help=_('Maximum segment identifier for this network segment ' + 'range which is based on the network type, VLAN ID for ' + 'vlan network type and tunnel ID for geneve, gre and vxlan ' + 'network types'), + ) + + return parser + + def take_action(self, parsed_args): + network_client = self.app.client_manager.network + try: + # Verify that the extension exists. + network_client.find_extension('network-segment-range', + ignore_missing=False) + except Exception as e: + msg = (_('Network segment range create not supported by ' + 'Network API: %(e)s') % {'e': e}) + raise exceptions.CommandError(msg) + + identity_client = self.app.client_manager.identity + + if parsed_args.shared and parsed_args.project: + msg = _("--project is only allowed with --private") + raise exceptions.CommandError(msg) + + if (parsed_args.network_type.lower() != 'vlan' and + parsed_args.physical_network): + msg = _("--physical-network is only allowed with --network-type " + "vlan") + raise exceptions.CommandError(msg) + + attrs = {} + if parsed_args.shared or parsed_args.private: + attrs['shared'] = parsed_args.shared + else: + # default to be ``shared`` if not specified + attrs['shared'] = True + attrs['network_type'] = parsed_args.network_type + attrs['minimum'] = parsed_args.minimum + attrs['maximum'] = parsed_args.maximum + if parsed_args.name: + attrs['name'] = parsed_args.name + + if parsed_args.project: + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + if project_id: + attrs['project_id'] = project_id + else: + msg = (_("Failed to create the network segment range for " + "project %(project_id)s") % parsed_args.project_id) + raise exceptions.CommandError(msg) + elif not parsed_args.shared: + # default to the current project if no project specified and shared + # is not specified. + # Get the project id from the current auth. + attrs['project_id'] = self.app.client_manager.auth_ref.project_id + else: + attrs['project_id'] = None + + if parsed_args.physical_network: + attrs['physical_network'] = parsed_args.physical_network + obj = network_client.create_network_segment_range(**attrs) + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns) + data = _update_additional_fields_from_props(columns, props=data) + return (display_columns, data) + + +class DeleteNetworkSegmentRange(command.Command): + _description = _("Delete network segment range(s)") + + def get_parser(self, prog_name): + parser = super(DeleteNetworkSegmentRange, self).get_parser(prog_name) + parser.add_argument( + 'network_segment_range', + metavar='', + nargs='+', + help=_('Network segment range(s) to delete (name or ID)'), + ) + return parser + + def take_action(self, parsed_args): + network_client = self.app.client_manager.network + try: + # Verify that the extension exists. + network_client.find_extension('network-segment-range', + ignore_missing=False) + except Exception as e: + msg = (_('Network segment range delete not supported by ' + 'Network API: %(e)s') % {'e': e}) + raise exceptions.CommandError(msg) + + result = 0 + for network_segment_range in parsed_args.network_segment_range: + try: + obj = network_client.find_network_segment_range( + network_segment_range, ignore_missing=False) + network_client.delete_network_segment_range(obj) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete network segment range with " + "ID '%(network_segment_range)s': %(e)s"), + {'network_segment_range': network_segment_range, + 'e': e}) + + if result > 0: + total = len(parsed_args.network_segment_range) + msg = (_("%(result)s of %(total)s network segment ranges failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) + + +class ListNetworkSegmentRange(command.Lister): + _description = _("List network segment ranges") + + def get_parser(self, prog_name): + parser = super(ListNetworkSegmentRange, self).get_parser(prog_name) + parser.add_argument( + '--long', + action='store_true', + help=_('List additional fields in output'), + ) + used_group = parser.add_mutually_exclusive_group() + used_group.add_argument( + '--used', + action='store_true', + help=_('List network segment ranges that have segments in use'), + ) + used_group.add_argument( + '--unused', + action='store_true', + help=_('List network segment ranges that have segments ' + 'not in use'), + ) + available_group = parser.add_mutually_exclusive_group() + available_group.add_argument( + '--available', + action='store_true', + help=_('List network segment ranges that have available segments'), + ) + available_group.add_argument( + '--unavailable', + action='store_true', + help=_('List network segment ranges without available segments'), + ) + return parser + + def take_action(self, parsed_args): + network_client = self.app.client_manager.network + try: + # Verify that the extension exists. + network_client.find_extension('network-segment-range', + ignore_missing=False) + except Exception as e: + msg = (_('Network segment ranges list not supported by ' + 'Network API: %(e)s') % {'e': e}) + raise exceptions.CommandError(msg) + + filters = {} + data = network_client.network_segment_ranges(**filters) + + headers = ( + 'ID', + 'Name', + 'Default', + 'Shared', + 'Project ID', + 'Network Type', + 'Physical Network', + 'Minimum ID', + 'Maximum ID' + ) + columns = ( + 'id', + 'name', + 'default', + 'shared', + 'project_id', + 'network_type', + 'physical_network', + 'minimum', + 'maximum', + ) + if parsed_args.available or parsed_args.unavailable or \ + parsed_args.used or parsed_args.unused: + # If one of `--available`, `--unavailable`, `--used`, + # `--unused` is specified, we assume that additional fields + # should be listed in output. + parsed_args.long = True + if parsed_args.long: + headers = headers + ( + 'Used', + 'Available', + ) + columns = columns + ( + 'used', + 'available', + ) + + display_props = tuple() + for s in data: + props = utils.get_item_properties(s, columns) + if parsed_args.available and \ + _is_prop_empty(columns, props, 'available') or \ + parsed_args.unavailable and \ + not _is_prop_empty(columns, props, 'available') or \ + parsed_args.used and _is_prop_empty(columns, props, 'used') or \ + parsed_args.unused and \ + not _is_prop_empty(columns, props, 'used'): + continue + if parsed_args.long: + props = _update_additional_fields_from_props(columns, props) + display_props += (props,) + + return headers, display_props + + +class SetNetworkSegmentRange(command.Command): + _description = _("Set network segment range properties") + + def get_parser(self, prog_name): + parser = super(SetNetworkSegmentRange, self).get_parser(prog_name) + parser.add_argument( + 'network_segment_range', + metavar='', + help=_('Network segment range to modify (name or ID)'), + ) + parser.add_argument( + '--name', + metavar='', + help=_('Set network segment name'), + ) + parser.add_argument( + '--minimum', + metavar='', + type=int, + help=_('Set network segment range minimum segment identifier'), + ) + parser.add_argument( + '--maximum', + metavar='', + type=int, + help=_('Set network segment range maximum segment identifier'), + ) + return parser + + def take_action(self, parsed_args): + network_client = self.app.client_manager.network + try: + # Verify that the extension exists. + network_client.find_extension('network-segment-range', + ignore_missing=False) + except Exception as e: + msg = (_('Network segment range set not supported by ' + 'Network API: %(e)s') % {'e': e}) + raise exceptions.CommandError(msg) + + if (parsed_args.minimum and not parsed_args.maximum) or \ + (parsed_args.maximum and not parsed_args.minimum): + msg = _("--minimum and --maximum are both required") + raise exceptions.CommandError(msg) + + obj = network_client.find_network_segment_range( + parsed_args.network_segment_range, ignore_missing=False) + attrs = {} + if parsed_args.name: + attrs['name'] = parsed_args.name + if parsed_args.minimum: + attrs['minimum'] = parsed_args.minimum + if parsed_args.maximum: + attrs['maximum'] = parsed_args.maximum + network_client.update_network_segment_range(obj, **attrs) + + +class ShowNetworkSegmentRange(command.ShowOne): + _description = _("Display network segment range details") + + def get_parser(self, prog_name): + parser = super(ShowNetworkSegmentRange, self).get_parser(prog_name) + parser.add_argument( + 'network_segment_range', + metavar='', + help=_('Network segment range to display (name or ID)'), + ) + return parser + + def take_action(self, parsed_args): + network_client = self.app.client_manager.network + try: + # Verify that the extension exists. + network_client.find_extension('network-segment-range', + ignore_missing=False) + except Exception as e: + msg = (_('Network segment range show not supported by ' + 'Network API: %(e)s') % {'e': e}) + raise exceptions.CommandError(msg) + + obj = network_client.find_network_segment_range( + parsed_args.network_segment_range, + ignore_missing=False + ) + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns) + data = _update_additional_fields_from_props(columns, props=data) + return (display_columns, data) diff --git a/openstackclient/tests/functional/network/v2/test_network_segment_range.py b/openstackclient/tests/functional/network/v2/test_network_segment_range.py new file mode 100644 index 0000000000..95402dcb3f --- /dev/null +++ b/openstackclient/tests/functional/network/v2/test_network_segment_range.py @@ -0,0 +1,145 @@ +# Copyright (c) 2019, 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 json +import uuid + +from openstackclient.tests.functional.network.v2 import common + + +class NetworkSegmentRangeTests(common.NetworkTests): + """Functional tests for network segment range""" + + def setUp(self): + super(NetworkSegmentRangeTests, self).setUp() + # Nothing in this class works with Nova Network + if not self.haz_network: + self.skipTest("No Network service present") + self.PROJECT_NAME = uuid.uuid4().hex + + def test_network_segment_range_create_delete(self): + # Make a project + project_id = json.loads(self.openstack( + 'project create -f json ' + self.PROJECT_NAME))['id'] + name = uuid.uuid4().hex + json_output = json.loads(self.openstack( + ' network segment range create -f json ' + + '--private ' + + "--project " + self.PROJECT_NAME + " " + + '--network-type vxlan ' + + '--minimum 2018 ' + + '--maximum 2055 ' + + name + )) + self.assertEqual( + name, + json_output["name"], + ) + self.assertEqual( + project_id, + json_output["project_id"], + ) + + raw_output = self.openstack( + 'network segment range delete ' + name, + ) + self.assertOutput('', raw_output) + raw_output_project = self.openstack( + 'project delete ' + self.PROJECT_NAME) + self.assertEqual('', raw_output_project) + + def test_network_segment_range_list(self): + name = uuid.uuid4().hex + json_output = json.loads(self.openstack( + ' network segment range create -f json ' + + '--shared ' + + '--network-type geneve ' + + '--minimum 2018 ' + + '--maximum 2055 ' + + name + )) + network_segment_range_id = json_output.get('id') + network_segment_range_name = json_output.get('name') + self.addCleanup( + self.openstack, + 'network segment range delete ' + network_segment_range_id + ) + self.assertEqual( + name, + json_output["name"], + ) + + json_output = json.loads(self.openstack( + 'network segment list -f json' + )) + item_map = { + item.get('ID'): item.get('Name') for item in json_output + } + self.assertIn(network_segment_range_id, item_map.keys()) + self.assertIn(network_segment_range_name, item_map.values()) + + def test_network_segment_range_set_show(self): + project_id = json.loads(self.openstack( + 'project create -f json ' + self.PROJECT_NAME))['id'] + name = uuid.uuid4().hex + json_output = json.loads(self.openstack( + ' network segment range create -f json ' + + '--private ' + + "--project " + self.PROJECT_NAME + " " + + '--network-type geneve ' + + '--minimum 2018 ' + + '--maximum 2055 ' + + name + )) + self.addCleanup( + self.openstack, + 'network segment range delete ' + name + ) + self.assertEqual( + name, + json_output["name"], + ) + self.assertEqual( + project_id, + json_output["project_id"], + ) + + new_minimum = '2010' + new_maximum = '2060' + cmd_output = self.openstack( + 'network segment range set ' + + '--minimum ' + new_minimum + ' ' + + '--maximum ' + new_maximum + ' ' + + name + ) + self.assertOutput('', cmd_output) + + json_output = json.loads(self.openstack( + 'network segment range show -f json ' + + name + )) + self.assertEqual( + new_minimum, + json_output["minimum"], + ) + self.assertEqual( + new_maximum, + json_output["maximum"], + ) + + raw_output_project = self.openstack( + 'project delete ' + self.PROJECT_NAME) + self.assertEqual('', raw_output_project) diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index 28e92d1196..38c865dae2 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -538,6 +538,66 @@ def create_network_segments(attrs=None, count=2): return network_segments +class FakeNetworkSegmentRange(object): + """Fake one or more network segment ranges.""" + + @staticmethod + def create_one_network_segment_range(attrs=None): + """Create a fake network segment range. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object faking the network segment range + """ + attrs = attrs or {} + + # Set default attributes. + fake_uuid = uuid.uuid4().hex + network_segment_range_attrs = { + 'id': 'network-segment-range-id-' + fake_uuid, + 'name': 'network-segment-name-' + fake_uuid, + 'default': False, + 'shared': False, + 'project_id': 'project-id-' + fake_uuid, + 'network_type': 'vlan', + 'physical_network': 'physical-network-name-' + fake_uuid, + 'minimum': 100, + 'maximum': 106, + 'used': {104: '3312e4ba67864b2eb53f3f41432f8efc', + 106: '3312e4ba67864b2eb53f3f41432f8efc'}, + 'available': [100, 101, 102, 103, 105], + } + + # Overwrite default attributes. + network_segment_range_attrs.update(attrs) + + network_segment_range = fakes.FakeResource( + info=copy.deepcopy(network_segment_range_attrs), + loaded=True + ) + + return network_segment_range + + @staticmethod + def create_network_segment_ranges(attrs=None, count=2): + """Create multiple fake network segment ranges. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of network segment ranges to fake + :return: + A list of FakeResource objects faking the network segment ranges + """ + network_segment_ranges = [] + for i in range(0, count): + network_segment_ranges.append( + FakeNetworkSegmentRange.create_one_network_segment_range(attrs) + ) + return network_segment_ranges + + class FakePort(object): """Fake one or more ports.""" diff --git a/openstackclient/tests/unit/network/v2/test_network_segment_range.py b/openstackclient/tests/unit/network/v2/test_network_segment_range.py new file mode 100644 index 0000000000..6387a28194 --- /dev/null +++ b/openstackclient/tests/unit/network/v2/test_network_segment_range.py @@ -0,0 +1,552 @@ +# Copyright (c) 2019, 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 mock import call + +from osc_lib import exceptions + +from openstackclient.network.v2 import network_segment_range +from openstackclient.tests.unit.network.v2 import fakes as network_fakes +from openstackclient.tests.unit import utils as tests_utils + + +class TestNetworkSegmentRange(network_fakes.TestNetworkV2): + + def setUp(self): + super(TestNetworkSegmentRange, self).setUp() + + # Get a shortcut to the network client + self.network = self.app.client_manager.network + + +class TestCreateNetworkSegmentRange(TestNetworkSegmentRange): + + # The network segment range to create. + _network_segment_range = network_fakes.FakeNetworkSegmentRange.\ + create_one_network_segment_range() + + columns = ( + 'available', + 'default', + 'id', + 'maximum', + 'minimum', + 'name', + 'network_type', + 'physical_network', + 'project_id', + 'shared', + 'used', + ) + + data = ( + ['100-103', '105'], + _network_segment_range.default, + _network_segment_range.id, + _network_segment_range.maximum, + _network_segment_range.minimum, + _network_segment_range.name, + _network_segment_range.network_type, + _network_segment_range.physical_network, + _network_segment_range.project_id, + _network_segment_range.shared, + {'3312e4ba67864b2eb53f3f41432f8efc': ['104', '106']}, + ) + + def setUp(self): + super(TestCreateNetworkSegmentRange, self).setUp() + + self.network.find_extension = mock.Mock() + self.network.create_network_segment_range = mock.Mock( + return_value=self._network_segment_range + ) + + # Get the command object to test + self.cmd = network_segment_range.CreateNetworkSegmentRange( + self.app, + self.namespace + ) + + def test_create_no_options(self): + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, [], []) + + def test_create_invalid_network_type(self): + arglist = [ + '--private', + '--project', self._network_segment_range.project_id, + '--network-type', 'foo', + '--minimum', str(self._network_segment_range.minimum), + '--maximum', str(self._network_segment_range.maximum), + self._network_segment_range.name, + ] + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, []) + + def test_create_shared_with_project_id(self): + arglist = [ + '--shared', + '--project', self._network_segment_range.project_id, + '--network-type', 'vxlan', + '--minimum', str(self._network_segment_range.minimum), + '--maximum', str(self._network_segment_range.maximum), + self._network_segment_range.name, + ] + verifylist = [ + ('shared', True), + ('project', self._network_segment_range.project_id), + ('network_type', 'vxlan'), + ('minimum', self._network_segment_range.minimum), + ('maximum', self._network_segment_range.maximum), + ('name', self._network_segment_range.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + def test_create_tunnel_with_physical_network(self): + arglist = [ + '--shared', + '--network-type', 'vxlan', + '--physical-network', self._network_segment_range.physical_network, + '--minimum', str(self._network_segment_range.minimum), + '--maximum', str(self._network_segment_range.maximum), + self._network_segment_range.name, + ] + verifylist = [ + ('shared', True), + ('network_type', 'vxlan'), + ('physical_network', self._network_segment_range.physical_network), + ('minimum', self._network_segment_range.minimum), + ('maximum', self._network_segment_range.maximum), + ('name', self._network_segment_range.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + def test_create_minimum_options(self): + arglist = [ + '--private', + '--project', self._network_segment_range.project_id, + '--network-type', self._network_segment_range.network_type, + '--minimum', str(self._network_segment_range.minimum), + '--maximum', str(self._network_segment_range.maximum), + self._network_segment_range.name, + ] + verifylist = [ + ('shared', self._network_segment_range.shared), + ('project', self._network_segment_range.project_id), + ('network_type', self._network_segment_range.network_type), + ('minimum', self._network_segment_range.minimum), + ('maximum', self._network_segment_range.maximum), + ('name', self._network_segment_range.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_network_segment_range.assert_called_once_with(**{ + 'shared': self._network_segment_range.shared, + 'project_id': mock.ANY, + 'network_type': self._network_segment_range.network_type, + 'minimum': self._network_segment_range.minimum, + 'maximum': self._network_segment_range.maximum, + 'name': self._network_segment_range.name, + }) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_all_options(self): + arglist = [ + '--private', + '--project', self._network_segment_range.project_id, + '--network-type', self._network_segment_range.network_type, + '--physical-network', self._network_segment_range.physical_network, + '--minimum', str(self._network_segment_range.minimum), + '--maximum', str(self._network_segment_range.maximum), + self._network_segment_range.name, + ] + verifylist = [ + ('shared', self._network_segment_range.shared), + ('project', self._network_segment_range.project_id), + ('network_type', self._network_segment_range.network_type), + ('physical_network', self._network_segment_range.physical_network), + ('minimum', self._network_segment_range.minimum), + ('maximum', self._network_segment_range.maximum), + ('name', self._network_segment_range.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_network_segment_range.assert_called_once_with(**{ + 'shared': self._network_segment_range.shared, + 'project_id': mock.ANY, + 'network_type': self._network_segment_range.network_type, + 'physical_network': self._network_segment_range.physical_network, + 'minimum': self._network_segment_range.minimum, + 'maximum': self._network_segment_range.maximum, + 'name': self._network_segment_range.name, + }) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestDeleteNetworkSegmentRange(TestNetworkSegmentRange): + + # The network segment ranges to delete. + _network_segment_ranges = \ + network_fakes.FakeNetworkSegmentRange.create_network_segment_ranges() + + def setUp(self): + super(TestDeleteNetworkSegmentRange, self).setUp() + + self.network.find_extension = mock.Mock() + self.network.delete_network_segment_range = mock.Mock( + return_value=None) + self.network.find_network_segment_range = mock.Mock( + side_effect=self._network_segment_ranges + ) + + # Get the command object to test + self.cmd = network_segment_range.DeleteNetworkSegmentRange( + self.app, + self.namespace + ) + + def test_delete(self): + arglist = [ + self._network_segment_ranges[0].id, + ] + verifylist = [ + ('network_segment_range', [self._network_segment_ranges[0].id]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.network.delete_network_segment_range.assert_called_once_with( + self._network_segment_ranges[0] + ) + self.assertIsNone(result) + + def test_delete_multiple(self): + arglist = [] + for _network_segment_range in self._network_segment_ranges: + arglist.append(_network_segment_range.id) + verifylist = [ + ('network_segment_range', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [] + for _network_segment_range in self._network_segment_ranges: + calls.append(call(_network_segment_range)) + self.network.delete_network_segment_range.assert_has_calls(calls) + self.assertIsNone(result) + + def test_delete_multiple_with_exception(self): + arglist = [ + self._network_segment_ranges[0].id, + 'doesnotexist' + ] + verifylist = [ + ('network_segment_range', + [self._network_segment_ranges[0].id, 'doesnotexist']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self._network_segment_ranges[0], + exceptions.CommandError] + self.network.find_network_segment_range = ( + mock.Mock(side_effect=find_mock_result) + ) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 network segment ranges failed to delete.', + str(e)) + + self.network.find_network_segment_range.assert_any_call( + self._network_segment_ranges[0].id, ignore_missing=False) + self.network.find_network_segment_range.assert_any_call( + 'doesnotexist', ignore_missing=False) + self.network.delete_network_segment_range.assert_called_once_with( + self._network_segment_ranges[0] + ) + + +class TestListNetworkSegmentRange(TestNetworkSegmentRange): + _network_segment_ranges = network_fakes.FakeNetworkSegmentRange.\ + create_network_segment_ranges(count=3) + + columns = ( + 'ID', + 'Name', + 'Default', + 'Shared', + 'Project ID', + 'Network Type', + 'Physical Network', + 'Minimum ID', + 'Maximum ID' + ) + columns_long = columns + ( + 'Used', + 'Available', + ) + + data = [] + for _network_segment_range in _network_segment_ranges: + data.append(( + _network_segment_range.id, + _network_segment_range.name, + _network_segment_range.default, + _network_segment_range.shared, + _network_segment_range.project_id, + _network_segment_range.network_type, + _network_segment_range.physical_network, + _network_segment_range.minimum, + _network_segment_range.maximum, + )) + + data_long = [] + for _network_segment_range in _network_segment_ranges: + data_long.append(( + _network_segment_range.id, + _network_segment_range.name, + _network_segment_range.default, + _network_segment_range.shared, + _network_segment_range.project_id, + _network_segment_range.network_type, + _network_segment_range.physical_network, + _network_segment_range.minimum, + _network_segment_range.maximum, + {'3312e4ba67864b2eb53f3f41432f8efc': ['104', '106']}, + ['100-103', '105'], + )) + + def setUp(self): + super(TestListNetworkSegmentRange, self).setUp() + + # Get the command object to test + self.cmd = network_segment_range.ListNetworkSegmentRange( + self.app, self.namespace) + + self.network.find_extension = mock.Mock() + self.network.network_segment_ranges = mock.Mock( + return_value=self._network_segment_ranges) + + def test_list_no_option(self): + arglist = [] + verifylist = [ + ('long', False), + ('available', False), + ('unavailable', False), + ('used', False), + ('unused', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.network_segment_ranges.assert_called_once_with() + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_list_long(self): + arglist = [ + '--long', + ] + verifylist = [ + ('long', True), + ('available', False), + ('unavailable', False), + ('used', False), + ('unused', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.network_segment_ranges.assert_called_once_with() + self.assertEqual(self.columns_long, columns) + self.assertEqual(self.data_long, list(data)) + + +class TestSetNetworkSegmentRange(TestNetworkSegmentRange): + + # The network segment range to set. + _network_segment_range = network_fakes.FakeNetworkSegmentRange.\ + create_one_network_segment_range() + # The network segment range updated. + minimum_updated = _network_segment_range.minimum - 5 + maximum_updated = _network_segment_range.maximum + 5 + available_updated = (list(range(minimum_updated, 104)) + [105] + + list(range(107, maximum_updated + 1))) + _network_segment_range_updated = network_fakes.FakeNetworkSegmentRange.\ + create_one_network_segment_range( + attrs={'minimum': minimum_updated, + 'maximum': maximum_updated, + 'used': {104: '3312e4ba67864b2eb53f3f41432f8efc', + 106: '3312e4ba67864b2eb53f3f41432f8efc'}, + 'available': available_updated} + ) + + def setUp(self): + super(TestSetNetworkSegmentRange, self).setUp() + + self.network.find_extension = mock.Mock() + self.network.find_network_segment_range = mock.Mock( + return_value=self._network_segment_range + ) + + # Get the command object to test + self.cmd = network_segment_range.SetNetworkSegmentRange(self.app, + self.namespace) + + def test_set_no_options(self): + arglist = [ + self._network_segment_range.id, + ] + verifylist = [ + ('network_segment_range', self._network_segment_range.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.network.update_network_segment_range = mock.Mock( + return_value=self._network_segment_range + ) + result = self.cmd.take_action(parsed_args) + + self.network.update_network_segment_range.assert_called_once_with( + self._network_segment_range, **{} + ) + self.assertIsNone(result) + + def test_set_all_options(self): + arglist = [ + '--name', 'new name', + '--minimum', str(self.minimum_updated), + '--maximum', str(self.maximum_updated), + self._network_segment_range.id, + ] + verifylist = [ + ('name', 'new name'), + ('minimum', self.minimum_updated), + ('maximum', self.maximum_updated), + ('network_segment_range', self._network_segment_range.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.network.update_network_segment_range = mock.Mock( + return_value=self._network_segment_range_updated + ) + result = self.cmd.take_action(parsed_args) + + attrs = { + 'name': 'new name', + 'minimum': self.minimum_updated, + 'maximum': self.maximum_updated, + } + self.network.update_network_segment_range.assert_called_once_with( + self._network_segment_range, **attrs + ) + self.assertIsNone(result) + + +class TestShowNetworkSegmentRange(TestNetworkSegmentRange): + + # The network segment range to show. + _network_segment_range = network_fakes.FakeNetworkSegmentRange.\ + create_one_network_segment_range() + + columns = ( + 'available', + 'default', + 'id', + 'maximum', + 'minimum', + 'name', + 'network_type', + 'physical_network', + 'project_id', + 'shared', + 'used', + ) + + data = ( + ['100-103', '105'], + _network_segment_range.default, + _network_segment_range.id, + _network_segment_range.maximum, + _network_segment_range.minimum, + _network_segment_range.name, + _network_segment_range.network_type, + _network_segment_range.physical_network, + _network_segment_range.project_id, + _network_segment_range.shared, + {'3312e4ba67864b2eb53f3f41432f8efc': ['104', '106']}, + ) + + def setUp(self): + super(TestShowNetworkSegmentRange, self).setUp() + + self.network.find_extension = mock.Mock() + self.network.find_network_segment_range = mock.Mock( + return_value=self._network_segment_range + ) + + # Get the command object to test + self.cmd = network_segment_range.ShowNetworkSegmentRange( + self.app, self.namespace) + + def test_show_no_options(self): + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, [], []) + + def test_show_all_options(self): + arglist = [ + self._network_segment_range.id, + ] + verifylist = [ + ('network_segment_range', self._network_segment_range.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.find_network_segment_range.assert_called_once_with( + self._network_segment_range.id, + ignore_missing=False + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) diff --git a/releasenotes/notes/bp-network-segment-range-management-0abf03fe03eea149.yaml b/releasenotes/notes/bp-network-segment-range-management-0abf03fe03eea149.yaml new file mode 100644 index 0000000000..4ff4f57536 --- /dev/null +++ b/releasenotes/notes/bp-network-segment-range-management-0abf03fe03eea149.yaml @@ -0,0 +1,6 @@ +--- +features: + - Add ``network segment range create``, ``network segment range delete``, + ``network segment range list``, ``network segment range show`` and + ``network segment range set`` commands. + [Blueprint `network-segment-range-management `_] diff --git a/setup.cfg b/setup.cfg index 48c2247ff7..c73f2ce9f6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -463,6 +463,12 @@ openstack.network.v2 = network_segment_set = openstackclient.network.v2.network_segment:SetNetworkSegment network_segment_show = openstackclient.network.v2.network_segment:ShowNetworkSegment + network_segment_range_create = openstackclient.network.v2.network_segment_range:CreateNetworkSegmentRange + network_segment_range_delete = openstackclient.network.v2.network_segment_range:DeleteNetworkSegmentRange + network_segment_range_list = openstackclient.network.v2.network_segment_range:ListNetworkSegmentRange + network_segment_range_set = openstackclient.network.v2.network_segment_range:SetNetworkSegmentRange + network_segment_range_show = openstackclient.network.v2.network_segment_range:ShowNetworkSegmentRange + network_service_provider_list = openstackclient.network.v2.network_service_provider:ListNetworkServiceProvider port_create = openstackclient.network.v2.port:CreatePort From 626a3a021c50759841804eeb9aad832ea9e57551 Mon Sep 17 00:00:00 2001 From: Matt Riedemann Date: Fri, 8 Jun 2018 16:59:08 -0400 Subject: [PATCH 2033/3095] Mention compute API 2.50 in openstack quota show --class There is a bug in the compute API until microversion 2.50 where the server-groups and server-group-members class quota fields aren't returned. This just mentions that microversion in the command help text. Change-Id: I029a614a922d642c578618c478c4d0a29a394fc2 Task: 21490 Story: 2002194 --- doc/source/cli/command-objects/quota.rst | 4 +++- openstackclient/common/quota.py | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/doc/source/cli/command-objects/quota.rst b/doc/source/cli/command-objects/quota.rst index c538cfb3dd..8c8cea6ddd 100644 --- a/doc/source/cli/command-objects/quota.rst +++ b/doc/source/cli/command-objects/quota.rst @@ -244,7 +244,9 @@ Set quotas for class quota show ---------- -Show quotas for project or class +Show quotas for project or class. Specify ``--os-compute-api-version 2.50`` or +higher to see ``server-groups`` and ``server-group-members`` output for a given +quota class. .. program:: quota show .. code:: bash diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index dba6873ff9..dae1b18e39 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -570,7 +570,10 @@ def take_action(self, parsed_args): class ShowQuota(command.ShowOne, BaseQuota): - _description = _("Show quotas for project or class") + _description = _( + "Show quotas for project or class. Specify " + "``--os-compute-api-version 2.50`` or higher to see ``server-groups`` " + "and ``server-group-members`` output for a given quota class.") def get_parser(self, prog_name): parser = super(ShowQuota, self).get_parser(prog_name) From 28c06d06885b3ae93da07eb14411d92c3df7e792 Mon Sep 17 00:00:00 2001 From: Kailun Qin Date: Wed, 13 Mar 2019 01:37:31 +0800 Subject: [PATCH 2034/3095] Fix: set invalid None project_id on range creation "project_id" attribute should not be set to None on shared network segment range creation since it is not a valid string type which is required for the API. Change-Id: Ia2bab12e39b4bb7e05ff2acfffb851252c100651 Story: 2005205 Task: 29975 --- .../network/v2/network_segment_range.py | 2 - .../network/v2/test_network_segment_range.py | 42 ++++++++++++++++--- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/openstackclient/network/v2/network_segment_range.py b/openstackclient/network/v2/network_segment_range.py index f5c8ccbc60..75820bc5ef 100644 --- a/openstackclient/network/v2/network_segment_range.py +++ b/openstackclient/network/v2/network_segment_range.py @@ -207,8 +207,6 @@ def take_action(self, parsed_args): # is not specified. # Get the project id from the current auth. attrs['project_id'] = self.app.client_manager.auth_ref.project_id - else: - attrs['project_id'] = None if parsed_args.physical_network: attrs['physical_network'] = parsed_args.physical_network diff --git a/openstackclient/tests/unit/network/v2/test_network_segment_range.py b/openstackclient/tests/unit/network/v2/test_network_segment_range.py index 6387a28194..63257086cd 100644 --- a/openstackclient/tests/unit/network/v2/test_network_segment_range.py +++ b/openstackclient/tests/unit/network/v2/test_network_segment_range.py @@ -144,19 +144,19 @@ def test_create_tunnel_with_physical_network(self): self.cmd.take_action, parsed_args) - def test_create_minimum_options(self): + def test_create_private_minimum_options(self): arglist = [ '--private', '--project', self._network_segment_range.project_id, - '--network-type', self._network_segment_range.network_type, + '--network-type', 'vxlan', '--minimum', str(self._network_segment_range.minimum), '--maximum', str(self._network_segment_range.maximum), self._network_segment_range.name, ] verifylist = [ - ('shared', self._network_segment_range.shared), + ('shared', False), ('project', self._network_segment_range.project_id), - ('network_type', self._network_segment_range.network_type), + ('network_type', 'vxlan'), ('minimum', self._network_segment_range.minimum), ('maximum', self._network_segment_range.maximum), ('name', self._network_segment_range.name), @@ -166,9 +166,39 @@ def test_create_minimum_options(self): columns, data = self.cmd.take_action(parsed_args) self.network.create_network_segment_range.assert_called_once_with(**{ - 'shared': self._network_segment_range.shared, + 'shared': False, 'project_id': mock.ANY, - 'network_type': self._network_segment_range.network_type, + 'network_type': 'vxlan', + 'minimum': self._network_segment_range.minimum, + 'maximum': self._network_segment_range.maximum, + 'name': self._network_segment_range.name, + }) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_shared_minimum_options(self): + arglist = [ + '--shared', + '--network-type', 'vxlan', + '--minimum', str(self._network_segment_range.minimum), + '--maximum', str(self._network_segment_range.maximum), + self._network_segment_range.name, + ] + verifylist = [ + ('shared', True), + ('network_type', 'vxlan'), + ('minimum', self._network_segment_range.minimum), + ('maximum', self._network_segment_range.maximum), + ('name', self._network_segment_range.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_network_segment_range.assert_called_once_with(**{ + 'shared': True, + 'network_type': 'vxlan', 'minimum': self._network_segment_range.minimum, 'maximum': self._network_segment_range.maximum, 'name': self._network_segment_range.name, From 7741347041b078ca4d687597897194d7797d202d Mon Sep 17 00:00:00 2001 From: Glenn Van de Water Date: Fri, 8 Mar 2019 16:31:23 +0100 Subject: [PATCH 2035/3095] Fix service discovery in functional tests If a required service is not enabled then we skip the test. The discovery is done by tests/functional/base.py:is_service_enabled but this method is broken, credentials are not passed to the 'openstack service show' command so every call will fail and every test that relies on it will be skipped. This commit fixed that method and the issues that popped up when re-enabling tests. Network segment range: - issue where we assumed network-segment-range extension is always present - issue where we compare integers and string representations of numbers Subnet: - issue where we try to deepcopy an uncopyable object in UnsetSubnet Change-Id: Id3cc907c1ed2a25b49cf6f4a7233e0401a02383a Story: 2005169 Task: 29908 --- openstackclient/network/v2/subnet.py | 19 +++++----- openstackclient/tests/functional/base.py | 35 ++++++++++--------- .../tests/functional/common/test_extension.py | 2 +- .../tests/functional/common/test_quota.py | 2 +- .../functional/compute/v2/test_server.py | 3 +- .../tests/functional/network/v2/common.py | 2 +- .../network/v2/test_network_segment_range.py | 14 ++++---- .../tests/functional/object/v1/common.py | 2 +- .../tests/functional/volume/v1/common.py | 3 +- 9 files changed, 41 insertions(+), 41 deletions(-) diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 5f8113bb8d..0733f37c8a 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -681,29 +681,30 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): client = self.app.client_manager.network obj = client.find_subnet(parsed_args.subnet, ignore_missing=False) - tmp_obj = copy.deepcopy(obj) + attrs = {} if parsed_args.dns_nameservers: - _update_arguments(tmp_obj.dns_nameservers, + attrs['dns_nameservers'] = copy.deepcopy(obj.dns_nameservers) + _update_arguments(attrs['dns_nameservers'], parsed_args.dns_nameservers, 'dns-nameserver') - attrs['dns_nameservers'] = tmp_obj.dns_nameservers if parsed_args.host_routes: + attrs['host_routes'] = copy.deepcopy(obj.host_routes) _update_arguments( - tmp_obj.host_routes, + attrs['host_routes'], convert_entries_to_nexthop(parsed_args.host_routes), 'host-route') - attrs['host_routes'] = tmp_obj.host_routes if parsed_args.allocation_pools: - _update_arguments(tmp_obj.allocation_pools, + attrs['allocation_pools'] = copy.deepcopy(obj.allocation_pools) + _update_arguments(attrs['allocation_pools'], parsed_args.allocation_pools, 'allocation-pool') - attrs['allocation_pools'] = tmp_obj.allocation_pools + if parsed_args.service_types: - _update_arguments(tmp_obj.service_types, + attrs['service_types'] = copy.deepcopy(obj.service_types) + _update_arguments(attrs['service_types'], parsed_args.service_types, 'service-type') - attrs['service_types'] = tmp_obj.service_types if attrs: client.update_subnet(obj, **attrs) diff --git a/openstackclient/tests/functional/base.py b/openstackclient/tests/functional/base.py index 7705c65539..1414e6bbb7 100644 --- a/openstackclient/tests/functional/base.py +++ b/openstackclient/tests/functional/base.py @@ -11,7 +11,6 @@ # under the License. import os -import re import shlex import subprocess @@ -30,8 +29,6 @@ def execute(cmd, fail_ok=False, merge_stderr=False): """Executes specified command for the given action.""" cmdlist = shlex.split(cmd) - result = '' - result_err = '' stdout = subprocess.PIPE stderr = subprocess.STDOUT if merge_stderr else subprocess.PIPE proc = subprocess.Popen(cmdlist, stdout=stdout, stderr=stderr) @@ -43,22 +40,8 @@ def execute(cmd, fail_ok=False, merge_stderr=False): return result -def is_service_enabled(service): - """Ask client cloud if service is available""" - try: - ret = execute('openstack service show -f value -c enabled ' + service) - except exceptions.CommandFailed: - # We get here for multiple reasons, all of them mean that a working - # service is not available - return False - - return "True" in ret - - class TestCase(testtools.TestCase): - delimiter_line = re.compile('^\+\-[\+\-]+\-\+$') - @classmethod def openstack(cls, cmd, cloud=ADMIN_CLOUD, fail_ok=False): """Executes openstackclient command for the given action.""" @@ -66,6 +49,24 @@ def openstack(cls, cmd, cloud=ADMIN_CLOUD, fail_ok=False): 'openstack --os-cloud={cloud} '.format(cloud=cloud) + cmd, fail_ok=fail_ok) + @classmethod + def is_service_enabled(cls, service): + """Ask client cloud if service is available""" + cmd = ('service show -f value -c enabled {service}' + .format(service=service)) + try: + return "True" in cls.openstack(cmd) + except exceptions.CommandFailed as e: + if "No service with a type, name or ID of" in str(e): + return False + else: + raise # Unable to determine if service is enabled + + @classmethod + def is_extension_enabled(cls, alias): + """Ask client cloud if extension is enabled""" + return alias in cls.openstack('extension list -f value -c Alias') + @classmethod def get_openstack_configuration_value(cls, configuration): opts = cls.get_opts([configuration]) diff --git a/openstackclient/tests/functional/common/test_extension.py b/openstackclient/tests/functional/common/test_extension.py index db50855f71..92efabefe9 100644 --- a/openstackclient/tests/functional/common/test_extension.py +++ b/openstackclient/tests/functional/common/test_extension.py @@ -26,7 +26,7 @@ class ExtensionTests(base.TestCase): @classmethod def setUpClass(cls): super(ExtensionTests, cls).setUpClass() - cls.haz_network = base.is_service_enabled('network') + cls.haz_network = cls.is_service_enabled('network') def test_extension_list_compute(self): """Test compute extension list""" diff --git a/openstackclient/tests/functional/common/test_quota.py b/openstackclient/tests/functional/common/test_quota.py index 859422812b..9c05746077 100644 --- a/openstackclient/tests/functional/common/test_quota.py +++ b/openstackclient/tests/functional/common/test_quota.py @@ -27,7 +27,7 @@ class QuotaTests(base.TestCase): @classmethod def setUpClass(cls): super(QuotaTests, cls).setUpClass() - cls.haz_network = base.is_service_enabled('network') + cls.haz_network = cls.is_service_enabled('network') cls.PROJECT_NAME =\ cls.get_openstack_configuration_value('auth.project_name') diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py index 3cb72d9f9f..c8fb44d380 100644 --- a/openstackclient/tests/functional/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -16,7 +16,6 @@ from tempest.lib import exceptions -from openstackclient.tests.functional import base from openstackclient.tests.functional.compute.v2 import common from openstackclient.tests.functional.volume.v2 import common as volume_common @@ -27,7 +26,7 @@ class ServerTests(common.ComputeTestCase): @classmethod def setUpClass(cls): super(ServerTests, cls).setUpClass() - cls.haz_network = base.is_service_enabled('network') + cls.haz_network = cls.is_service_enabled('network') def test_server_list(self): """Test server list, set""" diff --git a/openstackclient/tests/functional/network/v2/common.py b/openstackclient/tests/functional/network/v2/common.py index a18bc48faf..5243ecd0b9 100644 --- a/openstackclient/tests/functional/network/v2/common.py +++ b/openstackclient/tests/functional/network/v2/common.py @@ -22,7 +22,7 @@ class NetworkTests(base.TestCase): @classmethod def setUpClass(cls): super(NetworkTests, cls).setUpClass() - cls.haz_network = base.is_service_enabled('network') + cls.haz_network = cls.is_service_enabled('network') class NetworkTagTests(NetworkTests): diff --git a/openstackclient/tests/functional/network/v2/test_network_segment_range.py b/openstackclient/tests/functional/network/v2/test_network_segment_range.py index 95402dcb3f..c42f8744c1 100644 --- a/openstackclient/tests/functional/network/v2/test_network_segment_range.py +++ b/openstackclient/tests/functional/network/v2/test_network_segment_range.py @@ -28,6 +28,8 @@ def setUp(self): # Nothing in this class works with Nova Network if not self.haz_network: self.skipTest("No Network service present") + if not self.is_extension_enabled('network-segment-range'): + self.skipTest("No network-segment-range extension present") self.PROJECT_NAME = uuid.uuid4().hex def test_network_segment_range_create_delete(self): @@ -83,7 +85,7 @@ def test_network_segment_range_list(self): ) json_output = json.loads(self.openstack( - 'network segment list -f json' + 'network segment range list -f json' )) item_map = { item.get('ID'): item.get('Name') for item in json_output @@ -117,13 +119,11 @@ def test_network_segment_range_set_show(self): json_output["project_id"], ) - new_minimum = '2010' - new_maximum = '2060' + new_minimum = 2010 + new_maximum = 2060 cmd_output = self.openstack( - 'network segment range set ' + - '--minimum ' + new_minimum + ' ' + - '--maximum ' + new_maximum + ' ' + - name + 'network segment range set --minimum {min} --maximum {max} {name}' + .format(min=new_minimum, max=new_maximum, name=name) ) self.assertOutput('', cmd_output) diff --git a/openstackclient/tests/functional/object/v1/common.py b/openstackclient/tests/functional/object/v1/common.py index 44771aaabe..b013343027 100644 --- a/openstackclient/tests/functional/object/v1/common.py +++ b/openstackclient/tests/functional/object/v1/common.py @@ -19,4 +19,4 @@ class ObjectStoreTests(base.TestCase): @classmethod def setUpClass(cls): super(ObjectStoreTests, cls).setUpClass() - cls.haz_object_store = base.is_service_enabled('object-store') + cls.haz_object_store = cls.is_service_enabled('object-store') diff --git a/openstackclient/tests/functional/volume/v1/common.py b/openstackclient/tests/functional/volume/v1/common.py index bb3c674ea4..04eb1f48ae 100644 --- a/openstackclient/tests/functional/volume/v1/common.py +++ b/openstackclient/tests/functional/volume/v1/common.py @@ -12,7 +12,6 @@ import fixtures -from openstackclient.tests.functional import base from openstackclient.tests.functional.volume import base as volume_base @@ -25,7 +24,7 @@ def setUpClass(cls): # TODO(dtroyer): This needs to be updated to specifically check for # Volume v1 rather than just 'volume', but for now # that is enough until we get proper version negotiation - cls.haz_volume_v1 = base.is_service_enabled('volume') + cls.haz_volume_v1 = cls.is_service_enabled('volume') def setUp(self): super(BaseVolumeTests, self).setUp() From 510e9a7b8e567ce644473cad9e10651ff4bf2ca1 Mon Sep 17 00:00:00 2001 From: Kailun Qin Date: Wed, 13 Mar 2019 02:43:59 +0800 Subject: [PATCH 2036/3095] Fix: incorrect check when no shared/private input When neither of "--shared" and "--private" is input, we should not allow to specify "--project". Defaulting the created network segment range to shared is expected. Therefore, "project_id" attr should only be populated on a private range creation. Change-Id: Iab345e1651dd8b7904ff64a20633f194d719bb84 Story: 2005206 Task: 29980 --- .../network/v2/network_segment_range.py | 4 +- .../network/v2/test_network_segment_range.py | 49 +++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/openstackclient/network/v2/network_segment_range.py b/openstackclient/network/v2/network_segment_range.py index 75820bc5ef..567b5b6eb9 100644 --- a/openstackclient/network/v2/network_segment_range.py +++ b/openstackclient/network/v2/network_segment_range.py @@ -168,7 +168,7 @@ def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - if parsed_args.shared and parsed_args.project: + if not parsed_args.private and parsed_args.project: msg = _("--project is only allowed with --private") raise exceptions.CommandError(msg) @@ -202,7 +202,7 @@ def take_action(self, parsed_args): msg = (_("Failed to create the network segment range for " "project %(project_id)s") % parsed_args.project_id) raise exceptions.CommandError(msg) - elif not parsed_args.shared: + elif not attrs['shared']: # default to the current project if no project specified and shared # is not specified. # Get the project id from the current auth. diff --git a/openstackclient/tests/unit/network/v2/test_network_segment_range.py b/openstackclient/tests/unit/network/v2/test_network_segment_range.py index 63257086cd..22e25df133 100644 --- a/openstackclient/tests/unit/network/v2/test_network_segment_range.py +++ b/openstackclient/tests/unit/network/v2/test_network_segment_range.py @@ -98,6 +98,27 @@ def test_create_invalid_network_type(self): self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, []) + def test_create_default_with_project_id(self): + arglist = [ + '--project', self._network_segment_range.project_id, + '--network-type', 'vxlan', + '--minimum', str(self._network_segment_range.minimum), + '--maximum', str(self._network_segment_range.maximum), + self._network_segment_range.name, + ] + verifylist = [ + ('project', self._network_segment_range.project_id), + ('network_type', 'vxlan'), + ('minimum', self._network_segment_range.minimum), + ('maximum', self._network_segment_range.maximum), + ('name', self._network_segment_range.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, + parsed_args) + def test_create_shared_with_project_id(self): arglist = [ '--shared', @@ -144,6 +165,34 @@ def test_create_tunnel_with_physical_network(self): self.cmd.take_action, parsed_args) + def test_create_minimum_options(self): + arglist = [ + '--network-type', 'vxlan', + '--minimum', str(self._network_segment_range.minimum), + '--maximum', str(self._network_segment_range.maximum), + self._network_segment_range.name, + ] + verifylist = [ + ('network_type', 'vxlan'), + ('minimum', self._network_segment_range.minimum), + ('maximum', self._network_segment_range.maximum), + ('name', self._network_segment_range.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_network_segment_range.assert_called_once_with(**{ + 'shared': True, + 'network_type': 'vxlan', + 'minimum': self._network_segment_range.minimum, + 'maximum': self._network_segment_range.maximum, + 'name': self._network_segment_range.name, + }) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + def test_create_private_minimum_options(self): arglist = [ '--private', From c684fd926a9b4d54bda147fe306d1324b3805d19 Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Fri, 22 Mar 2019 00:46:00 +0000 Subject: [PATCH 2037/3095] Update master for stable/stein Add file to the reno documentation build to show release notes for stable/stein. Use pbr instruction to increment the minor version number automatically so that master versions are higher than the versions on stable/stein. Change-Id: I3edfae7c1c5f8268186455efc7add28dc38810fb Sem-Ver: feature --- releasenotes/source/index.rst | 1 + releasenotes/source/stein.rst | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 releasenotes/source/stein.rst diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index 19df6740de..9e2d8ce10b 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,6 +6,7 @@ OpenStackClient Release Notes :maxdepth: 1 unreleased + stein rocky queens pike diff --git a/releasenotes/source/stein.rst b/releasenotes/source/stein.rst new file mode 100644 index 0000000000..efaceb667b --- /dev/null +++ b/releasenotes/source/stein.rst @@ -0,0 +1,6 @@ +=================================== + Stein Series Release Notes +=================================== + +.. release-notes:: + :branch: stable/stein From c53de3214ed74ffd5b53e6d1cf8a0c0fa73dac99 Mon Sep 17 00:00:00 2001 From: Jim Rollenhagen Date: Mon, 1 Apr 2019 15:41:17 -0400 Subject: [PATCH 2038/3095] Ignore case in security group rule --ethertype Currently, this only allows 'IPv4' or 'IPv6', but one can imagine a user frequently typing e.g. 'ipv6' and getting frustrated. Allow any case, while still keeping correct case for the choices and the value sent to Neutron. Change-Id: I70ce1f43d32aad01b174437d03c984a5b608b161 --- .../network/v2/security_group_rule.py | 9 +++++++ .../v2/test_security_group_rule_network.py | 24 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index ca0e00b9d8..961125a9a7 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -73,6 +73,14 @@ def _convert_to_lowercase(string): return string.lower() +def _convert_ipvx_case(string): + if string.lower() == 'ipv4': + return 'IPv4' + if string.lower() == 'ipv6': + return 'IPv6' + return string + + def _is_icmp_protocol(protocol): # NOTE(rtheis): Neutron has deprecated protocol icmpv6. # However, while the OSC CLI doesn't document the protocol, @@ -183,6 +191,7 @@ def update_parser_network(self, parser): '--ethertype', metavar='', choices=['IPv4', 'IPv6'], + type=_convert_ipvx_case, help=_("Ethertype of network traffic " "(IPv4, IPv6; default: based on IP protocol)") ) diff --git a/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py b/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py index fe6d364928..b070ab6aab 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py @@ -125,6 +125,30 @@ def test_create_bad_ethertype(self): self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, []) + def test_lowercase_ethertype(self): + arglist = [ + '--ethertype', 'ipv4', + self._security_group.id, + ] + parsed_args = self.check_parser(self.cmd, arglist, []) + self.assertEqual('IPv4', parsed_args.ethertype) + + def test_lowercase_v6_ethertype(self): + arglist = [ + '--ethertype', 'ipv6', + self._security_group.id, + ] + parsed_args = self.check_parser(self.cmd, arglist, []) + self.assertEqual('IPv6', parsed_args.ethertype) + + def test_proper_case_ethertype(self): + arglist = [ + '--ethertype', 'IPv6', + self._security_group.id, + ] + parsed_args = self.check_parser(self.cmd, arglist, []) + self.assertEqual('IPv6', parsed_args.ethertype) + def test_create_all_protocol_options(self): arglist = [ '--protocol', 'tcp', From 589026cdd4707556058b5bebf341e2af5062bed4 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Tue, 2 Apr 2019 08:36:33 -0500 Subject: [PATCH 2039/3095] Volume backup functional test tweak Waiting for status in all the wrong places... Change-Id: I531ee6e0c00b623c6fd30d40df1f1f36bf86233f Signed-off-by: Dean Troyer --- openstackclient/tests/functional/volume/v2/test_backup.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openstackclient/tests/functional/volume/v2/test_backup.py b/openstackclient/tests/functional/volume/v2/test_backup.py index e4890b00ea..8f5c032c0a 100644 --- a/openstackclient/tests/functional/volume/v2/test_backup.py +++ b/openstackclient/tests/functional/volume/v2/test_backup.py @@ -39,14 +39,15 @@ def test_volume_backup_restore(self): '--size 1 ' + vol_id )) + self.wait_for_status("volume", vol_id, "available") + # create a backup backup = json.loads(self.openstack( 'volume backup create -f json ' + vol_id )) - - self.wait_for_status("volume", vol_id, "available") self.wait_for_status("backup", backup['id'], "available") + # restore the backup backup_restored = json.loads(self.openstack( 'volume backup restore -f json %s %s' From 415b48056d9d021e04ec972029040a89a6b13928 Mon Sep 17 00:00:00 2001 From: Tim Burke Date: Thu, 7 Jun 2018 16:40:58 -0700 Subject: [PATCH 2040/3095] Before writing object data to stdout, re-open it in binary mode Otherwise, you can hit TypeErrors on Python3. Change-Id: I9a891508886feddac3982ce593bd95130392e035 Closes-Bug: 1775482 --- openstackclient/api/object_store_v1.py | 5 +++-- .../tests/unit/object/v1/test_object_all.py | 22 +++++++++++++++++-- .../notes/bug-1775482-7ed2a9a8765b313e.yaml | 6 +++++ 3 files changed, 29 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/bug-1775482-7ed2a9a8765b313e.yaml diff --git a/openstackclient/api/object_store_v1.py b/openstackclient/api/object_store_v1.py index 3103352503..d1e5dfaf45 100644 --- a/openstackclient/api/object_store_v1.py +++ b/openstackclient/api/object_store_v1.py @@ -378,8 +378,9 @@ def object_save( ) if response.status_code == 200: if file == '-': - for chunk in response.iter_content(64 * 1024): - sys.stdout.write(chunk) + with os.fdopen(sys.stdout.fileno(), 'wb') as f: + for chunk in response.iter_content(64 * 1024): + f.write(chunk) else: if not os.path.exists(os.path.dirname(file)): if len(os.path.dirname(file)) > 0: diff --git a/openstackclient/tests/unit/object/v1/test_object_all.py b/openstackclient/tests/unit/object/v1/test_object_all.py index 363f2ea21e..08a7534d06 100644 --- a/openstackclient/tests/unit/object/v1/test_object_all.py +++ b/openstackclient/tests/unit/object/v1/test_object_all.py @@ -241,7 +241,25 @@ def test_save_to_stdout(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) - with mock.patch('sys.stdout', new=six.BytesIO()) as fake_stdout: + class FakeStdout(six.BytesIO): + def __init__(self): + six.BytesIO.__init__(self) + self.context_manager_calls = [] + + def __enter__(self): + self.context_manager_calls.append('__enter__') + return self + + def __exit__(self, *a): + self.context_manager_calls.append('__exit__') + + with mock.patch('sys.stdout') as fake_stdout, mock.patch( + 'os.fdopen', return_value=FakeStdout()) as fake_fdopen: + fake_stdout.fileno.return_value = 123 self.cmd.take_action(parsed_args) - self.assertEqual(fake_stdout.getvalue(), object_fakes.object_1_content) + self.assertEqual(fake_fdopen.return_value.getvalue(), + object_fakes.object_1_content) + self.assertEqual(fake_fdopen.mock_calls, [mock.call(123, 'wb')]) + self.assertEqual(fake_fdopen.return_value.context_manager_calls, + ['__enter__', '__exit__']) diff --git a/releasenotes/notes/bug-1775482-7ed2a9a8765b313e.yaml b/releasenotes/notes/bug-1775482-7ed2a9a8765b313e.yaml new file mode 100644 index 0000000000..88d9890a76 --- /dev/null +++ b/releasenotes/notes/bug-1775482-7ed2a9a8765b313e.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Re-open stdout in binary mode before writing object data in + ``object save --file -`` command. + [Bug `1775482 `_] From 4f3cda730f9e9cb070e45846b3583ef53971c391 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 12 Apr 2019 13:52:44 -0500 Subject: [PATCH 2041/3095] Tweak network segment range fiunction tests We seem to be having occasional overlaps in the ranges, as they were identical in all tests, change each test to not overlap the others so running in parallel is not racy. Change-Id: I7ea467a3aa2e4a4b4a334c10ea6ba21409c46af0 Signed-off-by: Dean Troyer --- .../network/v2/test_network_segment_range.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/openstackclient/tests/functional/network/v2/test_network_segment_range.py b/openstackclient/tests/functional/network/v2/test_network_segment_range.py index c42f8744c1..37c87dd59d 100644 --- a/openstackclient/tests/functional/network/v2/test_network_segment_range.py +++ b/openstackclient/tests/functional/network/v2/test_network_segment_range.py @@ -42,8 +42,8 @@ def test_network_segment_range_create_delete(self): '--private ' + "--project " + self.PROJECT_NAME + " " + '--network-type vxlan ' + - '--minimum 2018 ' + - '--maximum 2055 ' + + '--minimum 2005 ' + + '--maximum 2009 ' + name )) self.assertEqual( @@ -69,8 +69,8 @@ def test_network_segment_range_list(self): ' network segment range create -f json ' + '--shared ' + '--network-type geneve ' + - '--minimum 2018 ' + - '--maximum 2055 ' + + '--minimum 2013 ' + + '--maximum 2017 ' + name )) network_segment_range_id = json_output.get('id') @@ -102,8 +102,8 @@ def test_network_segment_range_set_show(self): '--private ' + "--project " + self.PROJECT_NAME + " " + '--network-type geneve ' + - '--minimum 2018 ' + - '--maximum 2055 ' + + '--minimum 2021 ' + + '--maximum 2025 ' + name )) self.addCleanup( @@ -119,8 +119,8 @@ def test_network_segment_range_set_show(self): json_output["project_id"], ) - new_minimum = 2010 - new_maximum = 2060 + new_minimum = 2020 + new_maximum = 2029 cmd_output = self.openstack( 'network segment range set --minimum {min} --maximum {max} {name}' .format(min=new_minimum, max=new_maximum, name=name) From 8ce203f879ab3be6135d5ce01315bfc15aa70d57 Mon Sep 17 00:00:00 2001 From: Matt Riedemann Date: Tue, 16 Apr 2019 18:40:14 -0400 Subject: [PATCH 2042/3095] Fix docs bug link to go to storyboard rather than launchpad This fixes the docs bug link generation for the normal docs and release notes docs. The requirement on openstackdocstheme is bumped to 1.23.2 to pick up fix I2ed164b9b0badade702c50543ac1a5eea4d1867b. Change-Id: I89711a391ee0fb7e40c1fbf83f950e2b582358d9 Story: #2005467 Task: #30546 --- doc/requirements.txt | 2 +- doc/source/conf.py | 3 +-- releasenotes/source/conf.py | 3 +-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/doc/requirements.txt b/doc/requirements.txt index 1afc73d6a6..a2649afb1f 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -1,7 +1,7 @@ # The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -openstackdocstheme>=1.18.1 # Apache-2.0 +openstackdocstheme>=1.23.2 # Apache-2.0 reno>=2.5.0 # Apache-2.0 sphinx!=1.6.6,!=1.6.7,>=1.6.5 # BSD sphinxcontrib-apidoc>=0.2.0 # BSD diff --git a/doc/source/conf.py b/doc/source/conf.py index 003bfcaa34..45045002aa 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -32,8 +32,7 @@ # openstackdocstheme options repository_name = 'openstack/python-openstackclient' -bug_project = 'python-openstackclient' -bug_tag = '' +use_storyboard = True # Add any paths that contain templates here, relative to this directory. #templates_path = ['_templates'] diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py index 3c4e4107e1..4060f49c79 100644 --- a/releasenotes/source/conf.py +++ b/releasenotes/source/conf.py @@ -45,8 +45,7 @@ # openstackdocstheme options repository_name = 'openstack/python-openstackclient' -bug_project = 'python-openstackclient' -bug_tag = '' +use_storyboard = True # Set aliases for extlinks # * lpbug - generic Launchpad bug :lpbug:`123456` From b3da2a67262852b18a522034604d056e7c7c730c Mon Sep 17 00:00:00 2001 From: OpenDev Sysadmins Date: Fri, 19 Apr 2019 19:45:05 +0000 Subject: [PATCH 2043/3095] OpenDev Migration Patch This commit was bulk generated and pushed by the OpenDev sysadmins as a part of the Git hosting and code review systems migration detailed in these mailing list posts: http://lists.openstack.org/pipermail/openstack-discuss/2019-March/003603.html http://lists.openstack.org/pipermail/openstack-discuss/2019-April/004920.html Attempts have been made to correct repository namespaces and hostnames based on simple pattern matching, but it's possible some were updated incorrectly or missed entirely. Please reach out to us via the contact information listed at https://opendev.org/ with any questions you may have. --- .gitreview | 2 +- .zuul.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.gitreview b/.gitreview index 784c1900ea..4eee726db6 100644 --- a/.gitreview +++ b/.gitreview @@ -1,4 +1,4 @@ [gerrit] -host=review.openstack.org +host=review.opendev.org port=29418 project=openstack/python-openstackclient.git diff --git a/.zuul.yaml b/.zuul.yaml index e1ffb0ebe0..69c39a64c4 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -16,7 +16,7 @@ tox_envlist: py27 # Set work dir to openstackclient so that if it's triggered by one of the # other repos the tests will run in the same place - zuul_work_dir: src/git.openstack.org/openstack/python-openstackclient + zuul_work_dir: src/opendev.org/openstack/python-openstackclient - job: name: osc-tox-py27-tips @@ -37,7 +37,7 @@ vars: # Set work dir to openstackclient so that if it's triggered by one of the # other repos the tests will run in the same place - zuul_work_dir: src/git.openstack.org/openstack/python-openstackclient + zuul_work_dir: src/opendev.org/openstack/python-openstackclient - job: name: osc-tox-py35-tips @@ -58,7 +58,7 @@ vars: # Set work dir to openstackclient so that if it's triggered by one of the # other repos the tests will run in the same place - zuul_work_dir: src/git.openstack.org/openstack/python-openstackclient + zuul_work_dir: src/opendev.org/openstack/python-openstackclient - job: name: osc-functional-devstack-base @@ -100,7 +100,7 @@ osc_environment: PYTHONUNBUFFERED: 'true' OS_CLOUD: devstack-admin - zuul_work_dir: src/git.openstack.org/openstack/python-openstackclient + zuul_work_dir: src/opendev.org/openstack/python-openstackclient # The Neutron bits are here rather than in osc-functional-devstack-base to # simplify removing Neutron in the osc-functional-devstack-n-net job. @@ -111,7 +111,7 @@ vars: devstack_plugins: # NOTE(amotoki): Some neutron features are enabled by devstack plugin - neutron: https://git.openstack.org/openstack/neutron + neutron: https://opendev.org/openstack/neutron devstack_services: neutron-network-segment-range: true neutron-segments: true From 0f56b7d07421bb7267a2be4c8e78efbfcc77a81e Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 19 Apr 2019 23:01:53 -0500 Subject: [PATCH 2044/3095] Followup opendev cleanup and test jobs * upper-constraints references need s/plain/raw/ Change-Id: I04368dc42f1a62a048ac9d11497747ef6f600515 Signed-off-by: Dean Troyer --- README.rst | 2 +- doc/source/index.rst | 2 +- tox.ini | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.rst b/README.rst index 8e37b325f7..e1824aed6d 100644 --- a/README.rst +++ b/README.rst @@ -39,7 +39,7 @@ language to describe operations in OpenStack. .. _Launchpad project: https://launchpad.net/python-openstackclient .. _Blueprints: https://blueprints.launchpad.net/python-openstackclient .. _Bugs: https://storyboard.openstack.org/#!/project/975 -.. _Source: https://git.openstack.org/cgit/openstack/python-openstackclient +.. _Source: https://opendev.org/openstack/python-openstackclient .. _Developer: https://docs.openstack.org/project-team-guide/project-setup/python.html .. _Contributing: https://docs.openstack.org/infra/manual/developers.html .. _Testing: https://docs.openstack.org/python-openstackclient/latest/contributor/developing.html#testing diff --git a/doc/source/index.rst b/doc/source/index.rst index a6d1e89606..fe9e66a115 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -60,7 +60,7 @@ on `Launchpad`_. Code may be submitted to the :code:`openstack/python-openstackclient` project using `Gerrit`_. Developers may also be found in the `IRC channel`_ ``#openstack-sdks``. -.. _`on OpenStack's Git server`: https://git.openstack.org/cgit/openstack/python-openstackclient/tree +.. _`on OpenStack's Git server`: https://opendev.org/openstack/python-openstackclient/tree .. _Launchpad: https://launchpad.net/python-openstackclient .. _Gerrit: http://docs.openstack.org/infra/manual/developers.html#development-workflow .. _Bug reports: https://storyboard.openstack.org/#!/project/975 diff --git a/tox.ini b/tox.ini index e008f9562c..c8854e5c5d 100644 --- a/tox.ini +++ b/tox.ini @@ -11,7 +11,7 @@ setenv = VIRTUAL_ENV={envdir} OS_STDERR_CAPTURE=1 OS_TEST_TIMEOUT=60 deps = - -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} + -c{env:UPPER_CONSTRAINTS_FILE:https://opendev.org/openstack/requirements/raw/upper-constraints.txt} -r{toxinidir}/test-requirements.txt -r{toxinidir}/requirements.txt commands = stestr run {posargs} @@ -88,7 +88,7 @@ commands = [testenv:venv] basepython = python3 deps = - -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} + -c{env:UPPER_CONSTRAINTS_FILE:https://opendev.org/openstack/requirements/raw/upper-constraints.txt} -r{toxinidir}/requirements.txt -r{toxinidir}/doc/requirements.txt commands = {posargs} @@ -112,7 +112,7 @@ commands = [testenv:docs] basepython = python3 deps = - -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} + -c{env:UPPER_CONSTRAINTS_FILE:https://opendev.org/openstack/requirements/raw/upper-constraints.txt} -r{toxinidir}/requirements.txt -r{toxinidir}/doc/requirements.txt commands = @@ -122,7 +122,7 @@ commands = [testenv:releasenotes] basepython = python3 deps = - -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} + -c{env:UPPER_CONSTRAINTS_FILE:https://opendev.org/openstack/requirements/raw/upper-constraints.txt} -r{toxinidir}/doc/requirements.txt commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html From a8309a2a85e96907d277b9763f13fd1f414f81e7 Mon Sep 17 00:00:00 2001 From: Ghanshyam Mann Date: Mon, 22 Apr 2019 23:45:06 +0000 Subject: [PATCH 2045/3095] Dropping the py35 testing All the integration testing has been moved to Bionic now[1] and py3.5 is not tested runtime for Train or stable/stein[2]. As per below ML thread, we are good to drop the py35 testing now: http://lists.openstack.org/pipermail/openstack-discuss/2019-April/005097.html [1] http://lists.openstack.org/pipermail/openstack-discuss/2019-April/004647.html [2] https://governance.openstack.org/tc/reference/runtimes/stein.html https://governance.openstack.org/tc/reference/runtimes/train.html Change-Id: Ie7bcc327fd588a1ff6b2556d49017df56bc55bf8 --- .zuul.yaml | 9 ++++----- setup.cfg | 1 - tox.ini | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.zuul.yaml b/.zuul.yaml index 69c39a64c4..95a6890c71 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -40,8 +40,8 @@ zuul_work_dir: src/opendev.org/openstack/python-openstackclient - job: - name: osc-tox-py35-tips - parent: openstack-tox-py35 + name: osc-tox-py36-tips + parent: openstack-tox-py36 description: | Run unit tests for OpenStackClient with master branch of important libs. @@ -180,11 +180,11 @@ check: jobs: - osc-tox-py27-tips - - osc-tox-py35-tips + - osc-tox-py36-tips gate: jobs: - osc-tox-py27-tips - - osc-tox-py35-tips + - osc-tox-py36-tips - project: templates: @@ -193,7 +193,6 @@ - openstack-cover-jobs - openstack-lower-constraints-jobs - openstack-python-jobs - - openstack-python35-jobs - openstack-python36-jobs - openstack-python37-jobs - publish-openstack-docs-pti diff --git a/setup.cfg b/setup.cfg index c73f2ce9f6..db03c48423 100644 --- a/setup.cfg +++ b/setup.cfg @@ -16,7 +16,6 @@ classifier = Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 - Programming Language :: Python :: 3.5 Programming Language :: Python :: 3.6 [files] diff --git a/tox.ini b/tox.ini index c8854e5c5d..8145179ece 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] minversion = 2.3 -envlist = py36,py35,py27,pep8 +envlist = py36,py27,pep8 skipdist = True [testenv] From 42cd4b2e40455591de42439352ad1516bc1fa788 Mon Sep 17 00:00:00 2001 From: Matt Riedemann Date: Wed, 1 May 2019 17:59:16 -0400 Subject: [PATCH 2046/3095] Document that server dump create requires 2.17 There is no indication to the user in the command help that they have to use 2.17 or greater [1] to run the "openstack server dump create" command. This mentions that requirement in the help of the command. [1] https://developer.openstack.org/api-ref/compute/#trigger-crash-dump-in-server Change-Id: I02c06e10a26eb38ddecb70f970cfcbfad962201c --- openstackclient/compute/v2/server.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index a040e4f088..cb9f8d43eb 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -947,6 +947,8 @@ class CreateServerDump(command.Command): It will create a dump file in the server(s) dumping the server(s)' memory, and also crash the server(s). OSC sees the dump file (server dump) as a kind of resource. + + This command requires ``--os-compute-api-version`` 2.17 or greater. """ def get_parser(self, prog_name): From 8c1ec6f97f2100e0a689204b7718c3eeed8df83b Mon Sep 17 00:00:00 2001 From: Adam Spiers Date: Wed, 1 May 2019 16:48:54 -0600 Subject: [PATCH 2047/3095] Fix link to new opendev repo The switch to an opendev URL wasn't quite right, resulting in a 404. Change-Id: I652f093384a584a56290a9b080913392873efd9f --- doc/source/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/index.rst b/doc/source/index.rst index fe9e66a115..abe0803374 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -60,7 +60,7 @@ on `Launchpad`_. Code may be submitted to the :code:`openstack/python-openstackclient` project using `Gerrit`_. Developers may also be found in the `IRC channel`_ ``#openstack-sdks``. -.. _`on OpenStack's Git server`: https://opendev.org/openstack/python-openstackclient/tree +.. _`on OpenStack's Git server`: https://opendev.org/openstack/python-openstackclient/ .. _Launchpad: https://launchpad.net/python-openstackclient .. _Gerrit: http://docs.openstack.org/infra/manual/developers.html#development-workflow .. _Bug reports: https://storyboard.openstack.org/#!/project/975 From 33a255612c661f174d2cb5d4ca93f8d7096e9290 Mon Sep 17 00:00:00 2001 From: Brian Haley Date: Fri, 10 Nov 2017 10:58:58 -0500 Subject: [PATCH 2048/3095] Change default security group protocol to 'any' The default protocol used to create a security rule was changed to ``tcp``, which was a regression from the neutron client. Change it back to ``any``, which skips sending the protocol to the API server entirely when using the Neutron v2 API. Users that had been creating rules without specifying a protocol and expecting ``tcp`` need to change to use ``--protocol tcp`` explicitly. Change-Id: Iedaa027240e00dced551513d8fa828564386b79f Closes-bug: #1716789 --- .../cli/command-objects/security-group-rule.rst | 6 +++--- .../network/v2/security_group_rule.py | 12 ++++++------ openstackclient/tests/unit/network/v2/fakes.py | 2 +- .../v2/test_security_group_rule_network.py | 4 ++++ .../notes/bug-1716789-abfae897b7e61246.yaml | 17 +++++++++++++++++ 5 files changed, 31 insertions(+), 10 deletions(-) create mode 100644 releasenotes/notes/bug-1716789-abfae897b7e61246.yaml diff --git a/doc/source/cli/command-objects/security-group-rule.rst b/doc/source/cli/command-objects/security-group-rule.rst index 1dbf16d22b..5809e00278 100644 --- a/doc/source/cli/command-objects/security-group-rule.rst +++ b/doc/source/cli/command-objects/security-group-rule.rst @@ -61,8 +61,8 @@ Create a new security group rule IP protocol (ah, dccp, egp, esp, gre, icmp, igmp, ipv6-encap, ipv6-frag, ipv6-icmp, ipv6-nonxt, ipv6-opts, ipv6-route, ospf, pgm, rsvp, sctp, tcp, - udp, udplite, vrrp and integer representations [0-255]; - default: tcp) + udp, udplite, vrrp and integer representations [0-255] + or any; default: any (all protocols)) *Network version 2* @@ -157,7 +157,7 @@ List security group rules List rules by the IP protocol (ah, dhcp, egp, esp, gre, icmp, igmp, ipv6-encap, ipv6-frag, ipv6-icmp, ipv6-nonxt,ipv6-opts, ipv6-route, ospf, pgm, rsvp, sctp, tcp, udp, udplite, vrrp and integer - representations [0-255]) + representations [0-255] or any; default: any (all protocols)) *Network version 2* diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index 961125a9a7..c93b3af461 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -168,7 +168,7 @@ def update_parser_network(self, parser): "ipv6-encap, ipv6-frag, ipv6-icmp, ipv6-nonxt, " "ipv6-opts, ipv6-route, ospf, pgm, rsvp, sctp, tcp, " "udp, udplite, vrrp and integer representations [0-255] " - "or any; default: tcp)") + "or any; default: any (all protocols))") ) protocol_group.add_argument( '--proto', @@ -233,8 +233,8 @@ def update_parser_compute(self, parser): ) return parser - def _get_protocol(self, parsed_args): - protocol = 'tcp' + def _get_protocol(self, parsed_args, default_protocol='any'): + protocol = default_protocol if parsed_args.protocol is not None: protocol = parsed_args.protocol if parsed_args.proto is not None: @@ -355,7 +355,7 @@ def take_action_network(self, client, parsed_args): def take_action_compute(self, client, parsed_args): group = client.api.security_group_find(parsed_args.group) - protocol = self._get_protocol(parsed_args) + protocol = self._get_protocol(parsed_args, default_protocol='tcp') if protocol == 'icmp': from_port, to_port = -1, -1 else: @@ -462,8 +462,8 @@ def update_parser_network(self, parser): "ah, dhcp, egp, esp, gre, icmp, igmp, " "ipv6-encap, ipv6-frag, ipv6-icmp, ipv6-nonxt, " "ipv6-opts, ipv6-route, ospf, pgm, rsvp, sctp, tcp, " - "udp, udplite, vrrp and integer representations [0-255])." - ) + "udp, udplite, vrrp and integer representations [0-255] " + "or any; default: any (all protocols))") ) direction_group = parser.add_mutually_exclusive_group() direction_group.add_argument( diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index 100ea2b1ad..e41621a48e 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -1305,7 +1305,7 @@ def create_one_security_group_rule(attrs=None): 'id': 'security-group-rule-id-' + uuid.uuid4().hex, 'port_range_max': None, 'port_range_min': None, - 'protocol': 'tcp', + 'protocol': None, 'remote_group_id': None, 'remote_ip_prefix': '0.0.0.0/0', 'security_group_id': 'security-group-id-' + uuid.uuid4().hex, diff --git a/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py b/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py index b070ab6aab..06849112e2 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py @@ -177,10 +177,12 @@ def test_create_all_port_range_options(self): def test_create_default_rule(self): self._setup_security_group_rule({ + 'protocol': 'tcp', 'port_range_max': 443, 'port_range_min': 443, }) arglist = [ + '--protocol', 'tcp', '--dst-port', str(self._security_group_rule.port_range_min), self._security_group.id, ] @@ -267,11 +269,13 @@ def test_create_protocol_any(self): def test_create_remote_group(self): self._setup_security_group_rule({ + 'protocol': 'tcp', 'port_range_max': 22, 'port_range_min': 22, 'remote_group_id': self._security_group.id, }) arglist = [ + '--protocol', 'tcp', '--dst-port', str(self._security_group_rule.port_range_min), '--ingress', '--src-group', self._security_group.name, diff --git a/releasenotes/notes/bug-1716789-abfae897b7e61246.yaml b/releasenotes/notes/bug-1716789-abfae897b7e61246.yaml new file mode 100644 index 0000000000..1fd0a13de6 --- /dev/null +++ b/releasenotes/notes/bug-1716789-abfae897b7e61246.yaml @@ -0,0 +1,17 @@ +--- +features: + - | + Change to use ``any`` as the default ``--protocol`` option to + ``security group rule create`` command when using the Neutron v2 API. + [Bug `1716789 `_] +fixes: + - | + The default protocol used to create a security rule was changed to + ``tcp``, which was a regression from the neutron client when using + the Neutron v2 API. Change it back to ``any``, which skips sending + the protocol to the API server entirely. +upgrade: + - | + Users that had been creating rules without specifying a protocol + and expecting ``tcp`` need to change to use ``--protocol tcp`` + explicitly when using the Neutron v2 API. From e6bbc995c5e53d41f3712f1cd032261f4dc12ea4 Mon Sep 17 00:00:00 2001 From: Guang Yee Date: Tue, 7 May 2019 09:34:00 -0700 Subject: [PATCH 2049/3095] document the --timing option Change-Id: I2d13088ea026ac7288213fe808874c4a3a81313a Story: #2005315 Task: #30863 --- doc/source/cli/man/openstack.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/source/cli/man/openstack.rst b/doc/source/cli/man/openstack.rst index 0745fb7a06..1ba9db0ddd 100644 --- a/doc/source/cli/man/openstack.rst +++ b/doc/source/cli/man/openstack.rst @@ -205,6 +205,10 @@ OPTIONS Show help message and exit +.. option:: --timing + + Print API call timing information + COMMANDS ======== From 04e03b2a1fe34046e2148d3c1d17b9053010c8af Mon Sep 17 00:00:00 2001 From: Jose Castro Leon Date: Thu, 14 Mar 2019 12:33:51 +0000 Subject: [PATCH 2050/3095] Fix bug in endpoint group deletion There is a typo in the endpoint group deletion, due to this you can't remove endpoint groups once assigned. I am adding also the unit tests to avoid this kind of issues in the future Task: 30640 Story: 2005521 Change-Id: Ie938f2c9894bb39b4c0ed1f7aa3a6a751a303058 --- openstackclient/identity/v3/endpoint_group.py | 6 +- .../tests/unit/identity/v3/fakes.py | 62 +++ .../unit/identity/v3/test_endpoint_group.py | 495 ++++++++++++++++++ .../notes/bug-2005521-0274fc26bd9b3842.yaml | 5 + 4 files changed, 565 insertions(+), 3 deletions(-) create mode 100644 openstackclient/tests/unit/identity/v3/test_endpoint_group.py create mode 100644 releasenotes/notes/bug-2005521-0274fc26bd9b3842.yaml diff --git a/openstackclient/identity/v3/endpoint_group.py b/openstackclient/identity/v3/endpoint_group.py index e254973bb5..66bd164d0d 100644 --- a/openstackclient/identity/v3/endpoint_group.py +++ b/openstackclient/identity/v3/endpoint_group.py @@ -204,11 +204,11 @@ def take_action(self, parsed_args): if endpointgroup: # List projects associated to the endpoint group - columns = ('ID', 'Name') + columns = ('ID', 'Name', 'Description') data = client.endpoint_filter.list_projects_for_endpoint_group( endpoint_group=endpointgroup.id) elif project: - columns = ('ID', 'Name') + columns = ('ID', 'Name', 'Description') data = client.endpoint_filter.list_endpoint_groups_for_project( project=project.id) else: @@ -251,7 +251,7 @@ def take_action(self, parsed_args): parsed_args.project, parsed_args.project_domain) - client.endpoint_filter.delete_endpoint_group_to_project( + client.endpoint_filter.delete_endpoint_group_from_project( endpoint_group=endpointgroup.id, project=project.id) diff --git a/openstackclient/tests/unit/identity/v3/fakes.py b/openstackclient/tests/unit/identity/v3/fakes.py index 27ee9fd026..e5727a6a20 100644 --- a/openstackclient/tests/unit/identity/v3/fakes.py +++ b/openstackclient/tests/unit/identity/v3/fakes.py @@ -235,6 +235,10 @@ 'service_id': service_id, 'region_id': endpoint_region, } +endpoint_group_filters_2 = { + 'region_id': endpoint_region, +} +endpoint_group_file_path = '/tmp/path/to/file' ENDPOINT_GROUP = { 'id': endpoint_group_id, @@ -1044,6 +1048,64 @@ def create_one_endpoint_filter(attrs=None): return endpoint_filter +class FakeEndpointGroup(object): + """Fake one or more endpoint group.""" + + @staticmethod + def create_one_endpointgroup(attrs=None): + """Create a fake endpoint group. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object, with id, url, and so on + """ + + attrs = attrs or {} + + # set default attributes. + endpointgroup_info = { + 'id': 'endpoint-group-id-' + uuid.uuid4().hex, + 'name': 'endpoint-group-name-' + uuid.uuid4().hex, + 'filters': { + 'region': 'region-' + uuid.uuid4().hex, + 'service_id': 'service-id-' + uuid.uuid4().hex, + }, + 'description': 'endpoint-group-description-' + uuid.uuid4().hex, + 'links': 'links-' + uuid.uuid4().hex, + } + endpointgroup_info.update(attrs) + + endpoint = fakes.FakeResource(info=copy.deepcopy(endpointgroup_info), + loaded=True) + return endpoint + + @staticmethod + def create_one_endpointgroup_filter(attrs=None): + """Create a fake endpoint project relationship. + + :param Dictionary attrs: + A dictionary with all attributes of endpointgroup filter + :return: + A FakeResource object with project, endpointgroup and so on + """ + attrs = attrs or {} + + # Set default attribute + endpointgroup_filter_info = { + 'project': 'project-id-' + uuid.uuid4().hex, + 'endpointgroup': 'endpointgroup-id-' + uuid.uuid4().hex, + } + + # Overwrite default attributes if there are some attributes set + endpointgroup_filter_info.update(attrs) + + endpointgroup_filter = fakes.FakeModel( + copy.deepcopy(endpointgroup_filter_info)) + + return endpointgroup_filter + + class FakeService(object): """Fake one or more service.""" diff --git a/openstackclient/tests/unit/identity/v3/test_endpoint_group.py b/openstackclient/tests/unit/identity/v3/test_endpoint_group.py new file mode 100644 index 0000000000..6e9da9c78e --- /dev/null +++ b/openstackclient/tests/unit/identity/v3/test_endpoint_group.py @@ -0,0 +1,495 @@ +# 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.identity.v3 import endpoint_group +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes + + +class TestEndpointGroup(identity_fakes.TestIdentityv3): + + def setUp(self): + super(TestEndpointGroup, self).setUp() + + # Get a shortcut to the EndpointManager Mock + self.endpoint_groups_mock = ( + self.app.client_manager.identity.endpoint_groups + ) + self.endpoint_groups_mock.reset_mock() + self.epf_mock = ( + self.app.client_manager.identity.endpoint_filter + ) + self.epf_mock.reset_mock() + + # Get a shortcut to the ServiceManager Mock + self.services_mock = self.app.client_manager.identity.services + self.services_mock.reset_mock() + + # Get a shortcut to the DomainManager Mock + self.domains_mock = self.app.client_manager.identity.domains + self.domains_mock.reset_mock() + + # Get a shortcut to the ProjectManager Mock + self.projects_mock = self.app.client_manager.identity.projects + self.projects_mock.reset_mock() + + +class TestEndpointGroupCreate(TestEndpointGroup): + + columns = ( + 'description', + 'filters', + 'id', + 'name', + ) + + def setUp(self): + super(TestEndpointGroupCreate, self).setUp() + + self.endpoint_group = ( + identity_fakes.FakeEndpointGroup.create_one_endpointgroup( + attrs={'filters': identity_fakes.endpoint_group_filters})) + + self.endpoint_groups_mock.create.return_value = self.endpoint_group + + # Get the command object to test + self.cmd = endpoint_group.CreateEndpointGroup(self.app, None) + + def test_endpointgroup_create_no_options(self): + arglist = [ + '--description', self.endpoint_group.description, + self.endpoint_group.name, + identity_fakes.endpoint_group_file_path, + ] + verifylist = [ + ('name', self.endpoint_group.name), + ('filters', identity_fakes.endpoint_group_file_path), + ('description', self.endpoint_group.description), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + mocker = mock.Mock() + mocker.return_value = identity_fakes.endpoint_group_filters + with mock.patch("openstackclient.identity.v3.endpoint_group." + "CreateEndpointGroup._read_filters", mocker): + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'name': self.endpoint_group.name, + 'filters': identity_fakes.endpoint_group_filters, + 'description': self.endpoint_group.description, + } + + self.endpoint_groups_mock.create.assert_called_with( + **kwargs + ) + + self.assertEqual(self.columns, columns) + datalist = ( + self.endpoint_group.description, + identity_fakes.endpoint_group_filters, + self.endpoint_group.id, + self.endpoint_group.name, + ) + self.assertEqual(datalist, data) + + +class TestEndpointGroupDelete(TestEndpointGroup): + + endpoint_group = ( + identity_fakes.FakeEndpointGroup.create_one_endpointgroup()) + + def setUp(self): + super(TestEndpointGroupDelete, self).setUp() + + # This is the return value for utils.find_resource(endpoint) + self.endpoint_groups_mock.get.return_value = self.endpoint_group + self.endpoint_groups_mock.delete.return_value = None + + # Get the command object to test + self.cmd = endpoint_group.DeleteEndpointGroup(self.app, None) + + def test_endpointgroup_delete(self): + arglist = [ + self.endpoint_group.id, + ] + verifylist = [ + ('endpointgroup', [self.endpoint_group.id]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.endpoint_groups_mock.delete.assert_called_with( + self.endpoint_group.id, + ) + self.assertIsNone(result) + + +class TestEndpointGroupList(TestEndpointGroup): + + endpoint_group = ( + identity_fakes.FakeEndpointGroup.create_one_endpointgroup()) + project = identity_fakes.FakeProject.create_one_project() + domain = identity_fakes.FakeDomain.create_one_domain() + + columns = ( + 'ID', + 'Name', + 'Description', + ) + + def setUp(self): + super(TestEndpointGroupList, self).setUp() + + self.endpoint_groups_mock.list.return_value = [self.endpoint_group] + self.endpoint_groups_mock.get.return_value = self.endpoint_group + self.epf_mock.list_projects_for_endpoint_group.return_value = [ + self.project] + self.epf_mock.list_endpoint_groups_for_project.return_value = [ + self.endpoint_group] + + # Get the command object to test + self.cmd = endpoint_group.ListEndpointGroup(self.app, None) + + def test_endpoint_group_list_no_options(self): + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + self.endpoint_groups_mock.list.assert_called_with() + + self.assertEqual(self.columns, columns) + datalist = ( + ( + self.endpoint_group.id, + self.endpoint_group.name, + self.endpoint_group.description, + ), + ) + self.assertEqual(datalist, tuple(data)) + + def test_endpoint_group_list_projects_by_endpoint_group(self): + arglist = [ + '--endpointgroup', self.endpoint_group.id, + ] + verifylist = [ + ('endpointgroup', self.endpoint_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + self.epf_mock.list_projects_for_endpoint_group.assert_called_with( + endpoint_group=self.endpoint_group.id + ) + + self.assertEqual(self.columns, columns) + datalist = ( + ( + self.project.id, + self.project.name, + self.project.description, + ), + ) + self.assertEqual(datalist, tuple(data)) + + def test_endpoint_group_list_by_project(self): + self.epf_mock.list_endpoints_for_project.return_value = [ + self.endpoint_group + ] + self.projects_mock.get.return_value = self.project + + arglist = [ + '--project', self.project.name, + '--domain', self.domain.name + ] + verifylist = [ + ('project', self.project.name), + ('domain', self.domain.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + self.epf_mock.list_endpoint_groups_for_project.assert_called_with( + project=self.project.id + ) + + self.assertEqual(self.columns, columns) + datalist = ( + ( + self.endpoint_group.id, + self.endpoint_group.name, + self.endpoint_group.description, + ), + ) + self.assertEqual(datalist, tuple(data)) + + +class TestEndpointGroupSet(TestEndpointGroup): + + endpoint_group = ( + identity_fakes.FakeEndpointGroup.create_one_endpointgroup()) + + def setUp(self): + super(TestEndpointGroupSet, self).setUp() + + # This is the return value for utils.find_resource(endpoint) + self.endpoint_groups_mock.get.return_value = self.endpoint_group + + self.endpoint_groups_mock.update.return_value = self.endpoint_group + + # Get the command object to test + self.cmd = endpoint_group.SetEndpointGroup(self.app, None) + + def test_endpoint_group_set_no_options(self): + arglist = [ + self.endpoint_group.id, + ] + verifylist = [ + ('endpointgroup', self.endpoint_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + kwargs = { + 'name': None, + 'filters': None, + 'description': '' + } + self.endpoint_groups_mock.update.assert_called_with( + self.endpoint_group.id, + **kwargs + ) + self.assertIsNone(result) + + def test_endpoint_group_set_name(self): + arglist = [ + '--name', 'qwerty', + self.endpoint_group.id + ] + verifylist = [ + ('name', 'qwerty'), + ('endpointgroup', self.endpoint_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'name': 'qwerty', + 'filters': None, + 'description': '' + } + self.endpoint_groups_mock.update.assert_called_with( + self.endpoint_group.id, + **kwargs + ) + self.assertIsNone(result) + + def test_endpoint_group_set_filters(self): + arglist = [ + '--filters', identity_fakes.endpoint_group_file_path, + self.endpoint_group.id, + ] + verifylist = [ + ('filters', identity_fakes.endpoint_group_file_path), + ('endpointgroup', self.endpoint_group.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + mocker = mock.Mock() + mocker.return_value = identity_fakes.endpoint_group_filters_2 + with mock.patch("openstackclient.identity.v3.endpoint_group." + "SetEndpointGroup._read_filters", mocker): + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'name': None, + 'filters': identity_fakes.endpoint_group_filters_2, + 'description': '', + } + + self.endpoint_groups_mock.update.assert_called_with( + self.endpoint_group.id, + **kwargs + ) + + self.assertIsNone(result) + + def test_endpoint_group_set_description(self): + arglist = [ + '--description', 'qwerty', + self.endpoint_group.id + ] + verifylist = [ + ('description', 'qwerty'), + ('endpointgroup', self.endpoint_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'name': None, + 'filters': None, + 'description': 'qwerty', + } + self.endpoint_groups_mock.update.assert_called_with( + self.endpoint_group.id, + **kwargs + ) + self.assertIsNone(result) + + +class TestAddProjectToEndpointGroup(TestEndpointGroup): + + project = identity_fakes.FakeProject.create_one_project() + domain = identity_fakes.FakeDomain.create_one_domain() + endpoint_group = ( + identity_fakes.FakeEndpointGroup.create_one_endpointgroup()) + + new_ep_filter = ( + identity_fakes.FakeEndpointGroup.create_one_endpointgroup_filter( + attrs={'endpointgroup': endpoint_group.id, + 'project': project.id})) + + def setUp(self): + super(TestAddProjectToEndpointGroup, self).setUp() + + # This is the return value for utils.find_resource() + self.endpoint_groups_mock.get.return_value = self.endpoint_group + + # Update the image_id in the MEMBER dict + self.epf_mock.create.return_value = self.new_ep_filter + self.projects_mock.get.return_value = self.project + self.domains_mock.get.return_value = self.domain + + # Get the command object to test + self.cmd = endpoint_group.AddProjectToEndpointGroup(self.app, None) + + def test_add_project_to_endpoint_no_option(self): + arglist = [ + self.endpoint_group.id, + self.project.id, + ] + verifylist = [ + ('endpointgroup', self.endpoint_group.id), + ('project', self.project.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.epf_mock.add_endpoint_group_to_project.assert_called_with( + project=self.project.id, + endpoint_group=self.endpoint_group.id, + ) + self.assertIsNone(result) + + def test_add_project_to_endpoint_with_option(self): + arglist = [ + self.endpoint_group.id, + self.project.id, + '--project-domain', self.domain.id, + ] + verifylist = [ + ('endpointgroup', self.endpoint_group.id), + ('project', self.project.id), + ('project_domain', self.domain.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.epf_mock.add_endpoint_group_to_project.assert_called_with( + project=self.project.id, + endpoint_group=self.endpoint_group.id, + ) + self.assertIsNone(result) + + +class TestRemoveProjectEndpointGroup(TestEndpointGroup): + + project = identity_fakes.FakeProject.create_one_project() + domain = identity_fakes.FakeDomain.create_one_domain() + endpoint_group = ( + identity_fakes.FakeEndpointGroup.create_one_endpointgroup()) + + def setUp(self): + super(TestRemoveProjectEndpointGroup, self).setUp() + + # This is the return value for utils.find_resource() + self.endpoint_groups_mock.get.return_value = self.endpoint_group + + self.projects_mock.get.return_value = self.project + self.domains_mock.get.return_value = self.domain + self.epf_mock.delete.return_value = None + + # Get the command object to test + self.cmd = endpoint_group.RemoveProjectFromEndpointGroup( + self.app, None) + + def test_remove_project_endpoint_no_options(self): + arglist = [ + self.endpoint_group.id, + self.project.id, + ] + verifylist = [ + ('endpointgroup', self.endpoint_group.id), + ('project', self.project.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.epf_mock.delete_endpoint_group_from_project.assert_called_with( + project=self.project.id, + endpoint_group=self.endpoint_group.id, + ) + self.assertIsNone(result) + + def test_remove_project_endpoint_with_options(self): + arglist = [ + self.endpoint_group.id, + self.project.id, + '--project-domain', self.domain.id, + ] + verifylist = [ + ('endpointgroup', self.endpoint_group.id), + ('project', self.project.id), + ('project_domain', self.domain.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.epf_mock.delete_endpoint_group_from_project.assert_called_with( + project=self.project.id, + endpoint_group=self.endpoint_group.id, + ) + self.assertIsNone(result) diff --git a/releasenotes/notes/bug-2005521-0274fc26bd9b3842.yaml b/releasenotes/notes/bug-2005521-0274fc26bd9b3842.yaml new file mode 100644 index 0000000000..2d5cacfbe6 --- /dev/null +++ b/releasenotes/notes/bug-2005521-0274fc26bd9b3842.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Fix ``endpoint group delete`` command to properly delete endpoint groups. + [Story `2005521 `_] \ No newline at end of file From 6385d64237c9973dd4c7dd53efb6664ea2c719da Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 9 May 2019 16:47:41 -0500 Subject: [PATCH 2051/3095] Blacklist Bandit 1.6.0 due to directory exclusion bug Bandit 1.6.0 introduces a regression[0] with the -x option, a fix is expected to be included in 1.6.1 soon. [0] https://github.com/PyCQA/bandit/issues/488 [1] https://github.com/PyCQA/bandit/pull/489 Change-Id: I110829ef960e3ee146f47871ef076491244bf4fa Signed-off-by: Dean Troyer --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 8ec69331b5..4cb66cfdb6 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -16,7 +16,7 @@ stestr>=1.0.0 # Apache-2.0 testtools>=2.2.0 # MIT tempest>=17.1.0 # Apache-2.0 osprofiler>=1.4.0 # Apache-2.0 -bandit>=1.1.0 # Apache-2.0 +bandit!=1.6.0,>=1.1.0 # Apache-2.0 wrapt>=1.7.0 # BSD License # Install these to generate sphinx autodocs From c44f26eb7e41c28bb13ef9bd31c8ddda9e638862 Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Wed, 3 May 2017 14:19:27 +0000 Subject: [PATCH 2052/3095] Use cliff formattable columns in network commands Use cliff formattable columns not to convert complex fields into a string when a machine readable format like JSON or YAML is requested. Partial-Bug: #1687955 Partially implement blueprint osc-formattable-columns Change-Id: I9878f327e39f56852cc0fb6e4eee9105b7141da9 --- openstackclient/network/v2/ip_availability.py | 3 +- openstackclient/network/v2/network.py | 30 +++--- openstackclient/network/v2/network_agent.py | 23 +++-- openstackclient/network/v2/port.py | 31 +++--- openstackclient/network/v2/router.py | 47 +++++---- openstackclient/network/v2/security_group.py | 15 ++- openstackclient/network/v2/subnet.py | 30 +++--- openstackclient/network/v2/subnet_pool.py | 5 +- .../tests/functional/network/v2/common.py | 14 +-- .../functional/network/v2/test_network.py | 18 ++-- .../network/v2/test_network_agent.py | 4 +- .../tests/functional/network/v2/test_port.py | 29 +++--- .../functional/network/v2/test_router.py | 6 +- .../functional/network/v2/test_subnet.py | 4 +- .../functional/network/v2/test_subnet_pool.py | 16 +-- .../unit/network/v2/test_ip_availability.py | 12 +-- .../tests/unit/network/v2/test_network.py | 97 ++++++++++--------- .../unit/network/v2/test_network_agent.py | 26 ++--- .../tests/unit/network/v2/test_port.py | 87 +++++++++-------- .../tests/unit/network/v2/test_router.py | 76 +++++++-------- .../network/v2/test_security_group_compute.py | 15 ++- .../network/v2/test_security_group_network.py | 20 ++-- .../tests/unit/network/v2/test_subnet.py | 92 +++++++++--------- .../tests/unit/network/v2/test_subnet_pool.py | 57 +++++------ openstackclient/tests/unit/utils.py | 17 ++++ 25 files changed, 407 insertions(+), 367 deletions(-) diff --git a/openstackclient/network/v2/ip_availability.py b/openstackclient/network/v2/ip_availability.py index 1d96358054..ddc88e557e 100644 --- a/openstackclient/network/v2/ip_availability.py +++ b/openstackclient/network/v2/ip_availability.py @@ -13,6 +13,7 @@ """IP Availability Info implementations""" +from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import utils @@ -21,7 +22,7 @@ from openstackclient.network import sdk_utils _formatters = { - 'subnet_ip_availability': utils.format_list_of_dicts, + 'subnet_ip_availability': format_columns.ListDictColumn, } diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index f5123932a3..63aec71497 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -13,6 +13,8 @@ """Network action implementations""" +from cliff import columns as cliff_columns +from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import utils @@ -23,24 +25,26 @@ from openstackclient.network.v2 import _tag -def _format_admin_state(item): - return 'UP' if item else 'DOWN' +class AdminStateColumn(cliff_columns.FormattableColumn): + def human_readable(self): + return 'UP' if self._value else 'DOWN' -def _format_router_external(item): - return 'External' if item else 'Internal' +class RouterExternalColumn(cliff_columns.FormattableColumn): + def human_readable(self): + return 'External' if self._value else 'Internal' _formatters = { - 'subnets': utils.format_list, - 'subnet_ids': utils.format_list, - 'admin_state_up': _format_admin_state, - 'is_admin_state_up': _format_admin_state, - 'router:external': _format_router_external, - 'is_router_external': _format_router_external, - 'availability_zones': utils.format_list, - 'availability_zone_hints': utils.format_list, - 'tags': utils.format_list, + 'subnets': format_columns.ListColumn, + 'subnet_ids': format_columns.ListColumn, + 'admin_state_up': AdminStateColumn, + 'is_admin_state_up': AdminStateColumn, + 'router:external': RouterExternalColumn, + 'is_router_external': RouterExternalColumn, + 'availability_zones': format_columns.ListColumn, + 'availability_zone_hints': format_columns.ListColumn, + 'tags': format_columns.ListColumn, } diff --git a/openstackclient/network/v2/network_agent.py b/openstackclient/network/v2/network_agent.py index 46e8d4b21c..1678485434 100644 --- a/openstackclient/network/v2/network_agent.py +++ b/openstackclient/network/v2/network_agent.py @@ -15,6 +15,8 @@ import logging +from cliff import columns as cliff_columns +from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -26,19 +28,22 @@ LOG = logging.getLogger(__name__) -def _format_alive(alive): - return ":-)" if alive else "XXX" +class AliveColumn(cliff_columns.FormattableColumn): + def human_readable(self): + return ":-)" if self._value else "XXX" -def _format_admin_state(state): - return 'UP' if state else 'DOWN' +class AdminStateColumn(cliff_columns.FormattableColumn): + def human_readable(self): + return 'UP' if self._value else 'DOWN' + _formatters = { - 'is_alive': _format_alive, - 'alive': _format_alive, - 'admin_state_up': _format_admin_state, - 'is_admin_state_up': _format_admin_state, - 'configurations': utils.format_dict, + 'is_alive': AliveColumn, + 'alive': AliveColumn, + 'admin_state_up': AdminStateColumn, + 'is_admin_state_up': AdminStateColumn, + 'configurations': format_columns.DictColumn, } diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index f6d6fc7280..6094dfc4d3 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -18,6 +18,8 @@ import json import logging +from cliff import columns as cliff_columns +from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -33,23 +35,24 @@ LOG = logging.getLogger(__name__) -def _format_admin_state(state): - return 'UP' if state else 'DOWN' +class AdminStateColumn(cliff_columns.FormattableColumn): + def human_readable(self): + return 'UP' if self._value else 'DOWN' _formatters = { - 'admin_state_up': _format_admin_state, - 'is_admin_state_up': _format_admin_state, - 'allowed_address_pairs': utils.format_list_of_dicts, - 'binding_profile': utils.format_dict, - 'binding_vif_details': utils.format_dict, - 'binding:profile': utils.format_dict, - 'binding:vif_details': utils.format_dict, - 'dns_assignment': utils.format_list_of_dicts, - 'extra_dhcp_opts': utils.format_list_of_dicts, - 'fixed_ips': utils.format_list_of_dicts, - 'security_group_ids': utils.format_list, - 'tags': utils.format_list, + 'admin_state_up': AdminStateColumn, + 'is_admin_state_up': AdminStateColumn, + 'allowed_address_pairs': format_columns.ListDictColumn, + 'binding_profile': format_columns.DictColumn, + 'binding_vif_details': format_columns.DictColumn, + 'binding:profile': format_columns.DictColumn, + 'binding:vif_details': format_columns.DictColumn, + 'dns_assignment': format_columns.ListDictColumn, + 'extra_dhcp_opts': format_columns.ListDictColumn, + 'fixed_ips': format_columns.ListDictColumn, + 'security_group_ids': format_columns.ListColumn, + 'tags': format_columns.ListColumn, } diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 2ec3e2f094..fd6a24fdb9 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -18,6 +18,8 @@ import json import logging +from cliff import columns as cliff_columns +from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -32,33 +34,36 @@ LOG = logging.getLogger(__name__) -def _format_admin_state(state): - return 'UP' if state else 'DOWN' +class AdminStateColumn(cliff_columns.FormattableColumn): + def human_readable(self): + return 'UP' if self._value else 'DOWN' -def _format_router_info(info): - try: - return json.dumps(info) - except (TypeError, KeyError): - return '' +class RouterInfoColumn(cliff_columns.FormattableColumn): + def human_readable(self): + try: + return json.dumps(self._value) + except (TypeError, KeyError): + return '' -def _format_routes(routes): - # Map the route keys to match --route option. - for route in routes: - if 'nexthop' in route: - route['gateway'] = route.pop('nexthop') - return utils.format_list_of_dicts(routes) +class RoutesColumn(cliff_columns.FormattableColumn): + def human_readable(self): + # Map the route keys to match --route option. + for route in self._value: + if 'nexthop' in route: + route['gateway'] = route.pop('nexthop') + return utils.format_list_of_dicts(self._value) _formatters = { - 'admin_state_up': _format_admin_state, - 'is_admin_state_up': _format_admin_state, - 'external_gateway_info': _format_router_info, - 'availability_zones': utils.format_list, - 'availability_zone_hints': utils.format_list, - 'routes': _format_routes, - 'tags': utils.format_list, + 'admin_state_up': AdminStateColumn, + 'is_admin_state_up': AdminStateColumn, + 'external_gateway_info': RouterInfoColumn, + 'availability_zones': format_columns.ListColumn, + 'availability_zone_hints': format_columns.ListColumn, + 'routes': RoutesColumn, + 'tags': format_columns.ListColumn, } @@ -720,7 +725,7 @@ def take_action(self, parsed_args): setattr(obj, 'interfaces_info', interfaces_info) display_columns, columns = _get_columns(obj) - _formatters['interfaces_info'] = _format_router_info + _formatters['interfaces_info'] = RouterInfoColumn data = utils.get_item_properties(obj, columns, formatters=_formatters) return (display_columns, data) diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py index ed6c8d7c24..e389473828 100644 --- a/openstackclient/network/v2/security_group.py +++ b/openstackclient/network/v2/security_group.py @@ -15,6 +15,7 @@ import argparse +from cliff import columns as cliff_columns from osc_lib.command import command from osc_lib import utils import six @@ -65,13 +66,23 @@ def _format_compute_security_group_rules(sg_rules): return utils.format_list(rules, separator='\n') +class NetworkSecurityGroupRulesColumn(cliff_columns.FormattableColumn): + def human_readable(self): + return _format_network_security_group_rules(self._value) + + +class ComputeSecurityGroupRulesColumn(cliff_columns.FormattableColumn): + def human_readable(self): + return _format_compute_security_group_rules(self._value) + + _formatters_network = { - 'security_group_rules': _format_network_security_group_rules, + 'security_group_rules': NetworkSecurityGroupRulesColumn, } _formatters_compute = { - 'rules': _format_compute_security_group_rules, + 'rules': ComputeSecurityGroupRulesColumn, } diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 0733f37c8a..1f0c2d9478 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -16,6 +16,8 @@ import copy import logging +from cliff import columns as cliff_columns +from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -40,23 +42,27 @@ def _update_arguments(obj_list, parsed_args_list, option): raise exceptions.CommandError(msg) -def _format_allocation_pools(data): - pool_formatted = ['%s-%s' % (pool.get('start', ''), pool.get('end', '')) - for pool in data] - return ','.join(pool_formatted) +class AllocationPoolsColumn(cliff_columns.FormattableColumn): + def human_readable(self): + pool_formatted = ['%s-%s' % (pool.get('start', ''), + pool.get('end', '')) + for pool in self._value] + return ','.join(pool_formatted) -def _format_host_routes(data): - # Map the host route keys to match --host-route option. - return utils.format_list_of_dicts(convert_entries_to_gateway(data)) +class HostRoutesColumn(cliff_columns.FormattableColumn): + def human_readable(self): + # Map the host route keys to match --host-route option. + return utils.format_list_of_dicts( + convert_entries_to_gateway(self._value)) _formatters = { - 'allocation_pools': _format_allocation_pools, - 'dns_nameservers': utils.format_list, - 'host_routes': _format_host_routes, - 'service_types': utils.format_list, - 'tags': utils.format_list, + 'allocation_pools': AllocationPoolsColumn, + 'dns_nameservers': format_columns.ListColumn, + 'host_routes': HostRoutesColumn, + 'service_types': format_columns.ListColumn, + 'tags': format_columns.ListColumn, } diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index ba0b6c452c..7ece263a36 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -15,6 +15,7 @@ import logging +from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -41,8 +42,8 @@ def _get_columns(item): _formatters = { - 'prefixes': utils.format_list, - 'tags': utils.format_list, + 'prefixes': format_columns.ListColumn, + 'tags': format_columns.ListColumn, } diff --git a/openstackclient/tests/functional/network/v2/common.py b/openstackclient/tests/functional/network/v2/common.py index 5243ecd0b9..2287f32930 100644 --- a/openstackclient/tests/functional/network/v2/common.py +++ b/openstackclient/tests/functional/network/v2/common.py @@ -62,21 +62,13 @@ def test_tag_operation(self): self._set_resource_and_tag_check('unset', name1, '--all-tag', []) self._set_resource_and_tag_check('set', name2, '--no-tag', []) - def _assertTagsEqual(self, expected, actual): - # TODO(amotoki): Should migrate to cliff format columns. - # At now, unit test assert method needs to be replaced - # to handle format columns, so format_list() is used. - # NOTE: The order of tag is undeterminestic. - actual_tags = filter(bool, actual.split(', ')) - self.assertEqual(set(expected), set(actual_tags)) - def _list_tag_check(self, project_id, expected): cmd_output = json.loads(self.openstack( '{} list --long --project {} -f json'.format(self.base_command, project_id))) for name, tags in expected: net = [n for n in cmd_output if n['Name'] == name][0] - self._assertTagsEqual(tags, net['Tags']) + self.assertEqual(set(tags), set(net['Tags'])) def _create_resource_for_tag_test(self, name, args): return json.loads(self.openstack( @@ -89,7 +81,7 @@ def _create_resource_and_tag_check(self, args, expected): self.addCleanup( self.openstack, '{} delete {}'.format(self.base_command, name)) self.assertIsNotNone(cmd_output["id"]) - self._assertTagsEqual(expected, cmd_output['tags']) + self.assertEqual(set(expected), set(cmd_output['tags'])) return name def _set_resource_and_tag_check(self, command, name, args, expected): @@ -100,4 +92,4 @@ def _set_resource_and_tag_check(self, command, name, args, expected): cmd_output = json.loads(self.openstack( '{} show -f json {}'.format(self.base_command, name) )) - self._assertTagsEqual(expected, cmd_output['tags']) + self.assertEqual(set(expected), set(cmd_output['tags'])) diff --git a/openstackclient/tests/functional/network/v2/test_network.py b/openstackclient/tests/functional/network/v2/test_network.py index 1a74496968..71b533ed22 100644 --- a/openstackclient/tests/functional/network/v2/test_network.py +++ b/openstackclient/tests/functional/network/v2/test_network.py @@ -120,14 +120,14 @@ def test_network_create_network(self): cmd_output["description"], ) self.assertEqual( - 'UP', + True, cmd_output["admin_state_up"], ) self.assertFalse( cmd_output["shared"], ) self.assertEqual( - 'Internal', + False, cmd_output["router:external"], ) @@ -231,12 +231,12 @@ def test_network_list(self): ) # Check the default values self.assertEqual( - 'UP', + True, cmd_output["admin_state_up"], ) self.assertFalse(cmd_output["shared"]) self.assertEqual( - 'Internal', + False, cmd_output["router:external"], ) self.assertFalse(cmd_output["is_default"]) @@ -266,7 +266,7 @@ def test_network_list(self): cmd_output["description"], ) self.assertEqual( - 'DOWN', + False, cmd_output["admin_state_up"], ) self.assertTrue(cmd_output["shared"]) @@ -398,12 +398,12 @@ def test_network_set(self): cmd_output["description"], ) self.assertEqual( - 'UP', + True, cmd_output["admin_state_up"], ) self.assertFalse(cmd_output["shared"]) self.assertEqual( - 'Internal', + False, cmd_output["router:external"], ) @@ -432,12 +432,12 @@ def test_network_set(self): cmd_output["description"], ) self.assertEqual( - 'DOWN', + False, cmd_output["admin_state_up"], ) self.assertTrue(cmd_output["shared"]) self.assertEqual( - 'External', + True, cmd_output["router:external"], ) self.assertFalse(cmd_output["is_default"]) diff --git a/openstackclient/tests/functional/network/v2/test_network_agent.py b/openstackclient/tests/functional/network/v2/test_network_agent.py index 86769e0c8d..963227de48 100644 --- a/openstackclient/tests/functional/network/v2/test_network_agent.py +++ b/openstackclient/tests/functional/network/v2/test_network_agent.py @@ -59,7 +59,7 @@ def test_network_agent_list_show_set(self): 'network agent show -f json %s' % agent_ids[0] )) self.assertEqual( - "DOWN", + False, cmd_output['admin_state_up'], ) @@ -72,7 +72,7 @@ def test_network_agent_list_show_set(self): 'network agent show -f json %s' % agent_ids[0] )) self.assertEqual( - "UP", + True, cmd_output['admin_state_up'], ) diff --git a/openstackclient/tests/functional/network/v2/test_port.py b/openstackclient/tests/functional/network/v2/test_port.py index e3067d90a3..a20d2043fd 100644 --- a/openstackclient/tests/functional/network/v2/test_port.py +++ b/openstackclient/tests/functional/network/v2/test_port.py @@ -155,7 +155,7 @@ def test_port_set(self): self.addCleanup(self.openstack, 'port delete %s' % id1) self.assertEqual(name, json_output.get('name')) self.assertEqual('xyzpdq', json_output.get('description')) - self.assertEqual('DOWN', json_output.get('admin_state_up')) + self.assertEqual(False, json_output.get('admin_state_up')) raw_output = self.openstack( 'port set --enable %s' % @@ -166,11 +166,11 @@ def test_port_set(self): json_output = json.loads(self.openstack( 'port show -f json %s' % name )) - sg_id = json_output.get('security_group_ids') + sg_id = json_output.get('security_group_ids')[0] self.assertEqual(name, json_output.get('name')) self.assertEqual('xyzpdq', json_output.get('description')) - self.assertEqual('UP', json_output.get('admin_state_up')) + self.assertEqual(True, json_output.get('admin_state_up')) self.assertIsNotNone(json_output.get('mac_address')) raw_output = self.openstack( @@ -180,7 +180,7 @@ def test_port_set(self): json_output = json.loads(self.openstack( 'port show -f json %s' % name )) - self.assertEqual('', json_output.get('security_group_ids')) + self.assertEqual([], json_output.get('security_group_ids')) def test_port_admin_set(self): """Test create, set (as admin), show, delete""" @@ -229,7 +229,7 @@ def test_port_set_sg(self): id1 = json_output.get('id') self.addCleanup(self.openstack, 'port delete %s' % id1) self.assertEqual(name, json_output.get('name')) - self.assertEqual(sg_id1, json_output.get('security_group_ids')) + self.assertEqual([sg_id1], json_output.get('security_group_ids')) raw_output = self.openstack( 'port set ' @@ -242,16 +242,10 @@ def test_port_set_sg(self): 'port show -f json %s' % name )) self.assertEqual(name, json_output.get('name')) - self.assertIn( - # TODO(dtroyer): output formatters should not mess with JSON! - sg_id1, - json_output.get('security_group_ids'), - ) - self.assertIn( - # TODO(dtroyer): output formatters should not mess with JSON! - sg_id2, - json_output.get('security_group_ids'), - ) + # NOTE(amotoki): The order of the field is not predictable, + self.assertIsInstance(json_output.get('security_group_ids'), list) + self.assertEqual(sorted([sg_id1, sg_id2]), + sorted(json_output.get('security_group_ids'))) raw_output = self.openstack( 'port unset --security-group %s %s' % (sg_id1, id1)) @@ -261,9 +255,8 @@ def test_port_set_sg(self): 'port show -f json %s' % name )) self.assertEqual( - # TODO(dtroyer): output formatters should do this on JSON! - sg_id2, - json_output.get('security_group_ids'), + [sg_id2], + json_output.get('security_group_ids') ) def _create_resource_for_tag_test(self, name, args): diff --git a/openstackclient/tests/functional/network/v2/test_router.py b/openstackclient/tests/functional/network/v2/test_router.py index 9d5beff059..05aad7a013 100644 --- a/openstackclient/tests/functional/network/v2/test_router.py +++ b/openstackclient/tests/functional/network/v2/test_router.py @@ -90,7 +90,7 @@ def test_router_list(self): cmd_output["name"], ) self.assertEqual( - "DOWN", + False, cmd_output["admin_state_up"], ) self.assertEqual( @@ -109,7 +109,7 @@ def test_router_list(self): cmd_output["name"], ) self.assertEqual( - "UP", + True, cmd_output["admin_state_up"], ) self.assertEqual( @@ -230,7 +230,7 @@ def test_router_set_show_unset(self): cmd_output["description"], ) self.assertEqual( - 'DOWN', + False, cmd_output["admin_state_up"], ) diff --git a/openstackclient/tests/functional/network/v2/test_subnet.py b/openstackclient/tests/functional/network/v2/test_subnet.py index d5309ee674..38030e0198 100644 --- a/openstackclient/tests/functional/network/v2/test_subnet.py +++ b/openstackclient/tests/functional/network/v2/test_subnet.py @@ -236,7 +236,7 @@ def test_subnet_set_show_unset(self): cmd_output["gateway_ip"], ) self.assertEqual( - 'network:floatingip_agent_gateway', + ['network:floatingip_agent_gateway'], cmd_output["service_types"], ) @@ -253,7 +253,7 @@ def test_subnet_set_show_unset(self): new_name )) self.assertEqual( - '', + [], cmd_output["service_types"], ) diff --git a/openstackclient/tests/functional/network/v2/test_subnet_pool.py b/openstackclient/tests/functional/network/v2/test_subnet_pool.py index 46aa6f1433..dad97f8457 100644 --- a/openstackclient/tests/functional/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/functional/network/v2/test_subnet_pool.py @@ -38,7 +38,7 @@ def test_subnet_pool_create_delete(self): cmd_output["name"] ) self.assertEqual( - pool_prefix, + [pool_prefix], cmd_output["prefixes"] ) @@ -50,7 +50,7 @@ def test_subnet_pool_create_delete(self): cmd_output["name"] ) self.assertEqual( - pool_prefix, + [pool_prefix], cmd_output["prefixes"] ) @@ -104,7 +104,7 @@ def test_subnet_pool_list(self): cmd_output["project_id"], ) self.assertEqual( - pool_prefix, + [pool_prefix], cmd_output["prefixes"], ) @@ -126,7 +126,7 @@ def test_subnet_pool_list(self): cmd_output["project_id"], ) self.assertEqual( - pool_prefix, + [pool_prefix], cmd_output["prefixes"], ) @@ -193,7 +193,7 @@ def test_subnet_pool_set_show(self): cmd_output["description"], ) self.assertEqual( - pool_prefix, + [pool_prefix], cmd_output["prefixes"], ) self.assertEqual( @@ -239,9 +239,9 @@ def test_subnet_pool_set_show(self): 'bbbb', cmd_output["description"], ) - self.assertInOutput( - "10.110.0.0/16", - cmd_output["prefixes"], + self.assertEqual( + sorted(["10.110.0.0/16", pool_prefix]), + sorted(cmd_output["prefixes"]), ) self.assertEqual( 8, diff --git a/openstackclient/tests/unit/network/v2/test_ip_availability.py b/openstackclient/tests/unit/network/v2/test_ip_availability.py index c7c5a9b49e..21508a8df1 100644 --- a/openstackclient/tests/unit/network/v2/test_ip_availability.py +++ b/openstackclient/tests/unit/network/v2/test_ip_availability.py @@ -13,7 +13,7 @@ import mock -from osc_lib import utils as common_utils +from osc_lib.cli import format_columns from openstackclient.network.v2 import ip_availability from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes @@ -75,7 +75,7 @@ def test_list_no_options(self): self.network.network_ip_availabilities.assert_called_once_with( **filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_list_ip_version(self): arglist = [ @@ -93,7 +93,7 @@ def test_list_ip_version(self): self.network.network_ip_availabilities.assert_called_once_with( **filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_list_project(self): arglist = [ @@ -113,7 +113,7 @@ def test_list_project(self): self.network.network_ip_availabilities.assert_called_once_with( **filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) class TestShowIPAvailability(TestIPAvailability): @@ -135,7 +135,7 @@ class TestShowIPAvailability(TestIPAvailability): _ip_availability.network_id, _ip_availability.network_name, _ip_availability.tenant_id, - common_utils.format_list( + format_columns.ListDictColumn( _ip_availability.subnet_ip_availability), _ip_availability.total_ips, _ip_availability.used_ips, @@ -176,4 +176,4 @@ def test_show_all_options(self): self._ip_availability.network_name, ignore_missing=False) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) diff --git a/openstackclient/tests/unit/network/v2/test_network.py b/openstackclient/tests/unit/network/v2/test_network.py index 0f57f0eec9..5c97c363f5 100644 --- a/openstackclient/tests/unit/network/v2/test_network.py +++ b/openstackclient/tests/unit/network/v2/test_network.py @@ -15,8 +15,9 @@ import mock from mock import call + +from osc_lib.cli import format_columns from osc_lib import exceptions -from osc_lib import utils from openstackclient.network.v2 import network from openstackclient.tests.unit import fakes @@ -81,9 +82,9 @@ class TestCreateNetworkIdentityV3(TestNetwork): ) data = ( - network._format_admin_state(_network.admin_state_up), - utils.format_list(_network.availability_zone_hints), - utils.format_list(_network.availability_zones), + network.AdminStateColumn(_network.admin_state_up), + format_columns.ListColumn(_network.availability_zone_hints), + format_columns.ListColumn(_network.availability_zones), _network.description, _network.dns_domain, _network.id, @@ -98,11 +99,11 @@ class TestCreateNetworkIdentityV3(TestNetwork): _network.provider_physical_network, _network.provider_segmentation_id, _network.qos_policy_id, - network._format_router_external(_network.is_router_external), + network.RouterExternalColumn(_network.is_router_external), _network.shared, _network.status, - utils.format_list(_network.subnets), - utils.format_list(_network.tags), + format_columns.ListColumn(_network.subnets), + format_columns.ListColumn(_network.tags), ) def setUp(self): @@ -146,7 +147,7 @@ def test_create_default_options(self): }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_all_options(self): arglist = [ @@ -211,7 +212,7 @@ def test_create_all_options(self): 'dns_domain': 'example.org.', }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_other_options(self): arglist = [ @@ -238,7 +239,7 @@ def test_create_other_options(self): 'port_security_enabled': False, }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def _test_create_with_tag(self, add_tags=True): arglist = [self._network.name] @@ -270,7 +271,7 @@ def _test_create_with_tag(self, add_tags=True): else: self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_with_tags(self): self._test_create_with_tag(add_tags=True) @@ -313,9 +314,9 @@ class TestCreateNetworkIdentityV2(TestNetwork): ) data = ( - network._format_admin_state(_network.admin_state_up), - utils.format_list(_network.availability_zone_hints), - utils.format_list(_network.availability_zones), + network.AdminStateColumn(_network.admin_state_up), + format_columns.ListColumn(_network.availability_zone_hints), + format_columns.ListColumn(_network.availability_zones), _network.description, _network.dns_domain, _network.id, @@ -330,11 +331,11 @@ class TestCreateNetworkIdentityV2(TestNetwork): _network.provider_physical_network, _network.provider_segmentation_id, _network.qos_policy_id, - network._format_router_external(_network.is_router_external), + network.RouterExternalColumn(_network.is_router_external), _network.shared, _network.status, - utils.format_list(_network.subnets), - utils.format_list(_network.tags), + format_columns.ListColumn(_network.subnets), + format_columns.ListColumn(_network.tags), ) def setUp(self): @@ -385,7 +386,7 @@ def test_create_with_project_identityv2(self): }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_with_domain_identityv2(self): arglist = [ @@ -525,7 +526,7 @@ class TestListNetwork(TestNetwork): data.append(( net.id, net.name, - utils.format_list(net.subnets), + format_columns.ListColumn(net.subnets), )) data_long = [] @@ -535,13 +536,13 @@ class TestListNetwork(TestNetwork): net.name, net.status, net.project_id, - network._format_admin_state(net.admin_state_up), + network.AdminStateColumn(net.admin_state_up), net.shared, - utils.format_list(net.subnets), + format_columns.ListColumn(net.subnets), net.provider_network_type, - network._format_router_external(net.is_router_external), - utils.format_list(net.availability_zones), - utils.format_list(net.tags), + network.RouterExternalColumn(net.is_router_external), + format_columns.ListColumn(net.availability_zones), + format_columns.ListColumn(net.tags), )) def setUp(self): @@ -577,7 +578,7 @@ def test_network_list_no_options(self): self.network.networks.assert_called_once_with() self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_list_external(self): arglist = [ @@ -598,7 +599,7 @@ def test_list_external(self): **{'router:external': True, 'is_router_external': True} ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_list_internal(self): arglist = [ @@ -615,7 +616,7 @@ def test_list_internal(self): **{'router:external': False, 'is_router_external': False} ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_network_list_long(self): arglist = [ @@ -634,7 +635,7 @@ def test_network_list_long(self): self.network.networks.assert_called_once_with() self.assertEqual(self.columns_long, columns) - self.assertEqual(self.data_long, list(data)) + self.assertListItemEqual(self.data_long, list(data)) def test_list_name(self): test_name = "fakename" @@ -653,7 +654,7 @@ def test_list_name(self): **{'name': test_name} ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_network_list_enable(self): arglist = [ @@ -671,7 +672,7 @@ def test_network_list_enable(self): **{'admin_state_up': True, 'is_admin_state_up': True} ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_network_list_disable(self): arglist = [ @@ -689,7 +690,7 @@ def test_network_list_disable(self): **{'admin_state_up': False, 'is_admin_state_up': False} ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_network_list_project(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -708,7 +709,7 @@ def test_network_list_project(self): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_network_list_project_domain(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -727,7 +728,7 @@ def test_network_list_project_domain(self): self.network.networks.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_network_list_share(self): arglist = [ @@ -744,7 +745,7 @@ def test_network_list_share(self): **{'shared': True, 'is_shared': True} ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_network_list_no_share(self): arglist = [ @@ -761,7 +762,7 @@ def test_network_list_no_share(self): **{'shared': False, 'is_shared': False} ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_network_list_status(self): choices = ['ACTIVE', 'BUILD', 'DOWN', 'ERROR'] @@ -780,7 +781,7 @@ def test_network_list_status(self): **{'status': test_status} ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_network_list_provider_network_type(self): network_type = self._network[0].provider_network_type @@ -798,7 +799,7 @@ def test_network_list_provider_network_type(self): 'provider_network_type': network_type} ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_network_list_provider_physical_network(self): physical_network = self._network[0].provider_physical_network @@ -816,7 +817,7 @@ def test_network_list_provider_physical_network(self): 'provider_physical_network': physical_network} ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_network_list_provider_segment(self): segmentation_id = self._network[0].provider_segmentation_id @@ -834,7 +835,7 @@ def test_network_list_provider_segment(self): 'provider_segmentation_id': segmentation_id} ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_network_list_dhcp_agent(self): arglist = [ @@ -853,7 +854,7 @@ def test_network_list_dhcp_agent(self): *attrs) self.assertEqual(self.columns, columns) - self.assertEqual(list(data), list(self.data)) + self.assertListItemEqual(list(data), list(self.data)) def test_list_with_tag_options(self): arglist = [ @@ -878,7 +879,7 @@ def test_list_with_tag_options(self): 'not_any_tags': 'black,white'} ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) class TestSetNetwork(TestNetwork): @@ -1057,9 +1058,9 @@ class TestShowNetwork(TestNetwork): ) data = ( - network._format_admin_state(_network.admin_state_up), - utils.format_list(_network.availability_zone_hints), - utils.format_list(_network.availability_zones), + network.AdminStateColumn(_network.admin_state_up), + format_columns.ListColumn(_network.availability_zone_hints), + format_columns.ListColumn(_network.availability_zones), _network.description, _network.dns_domain, _network.id, @@ -1074,11 +1075,11 @@ class TestShowNetwork(TestNetwork): _network.provider_physical_network, _network.provider_segmentation_id, _network.qos_policy_id, - network._format_router_external(_network.is_router_external), + network.RouterExternalColumn(_network.is_router_external), _network.shared, _network.status, - utils.format_list(_network.subnets), - utils.format_list(_network.tags), + format_columns.ListColumn(_network.subnets), + format_columns.ListColumn(_network.tags), ) def setUp(self): @@ -1111,7 +1112,7 @@ def test_show_all_options(self): self._network.name, ignore_missing=False) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) class TestUnsetNetwork(TestNetwork): diff --git a/openstackclient/tests/unit/network/v2/test_network_agent.py b/openstackclient/tests/unit/network/v2/test_network_agent.py index 709fb1c6cd..8500d08ea6 100644 --- a/openstackclient/tests/unit/network/v2/test_network_agent.py +++ b/openstackclient/tests/unit/network/v2/test_network_agent.py @@ -14,8 +14,8 @@ import mock from mock import call +from osc_lib.cli import format_columns from osc_lib import exceptions -from osc_lib import utils from openstackclient.network.v2 import network_agent from openstackclient.tests.unit.network.v2 import fakes as network_fakes @@ -207,8 +207,8 @@ class TestListNetworkAgent(TestNetworkAgent): agent.agent_type, agent.host, agent.availability_zone, - network_agent._format_alive(agent.alive), - network_agent._format_admin_state(agent.admin_state_up), + network_agent.AliveColumn(agent.alive), + network_agent.AdminStateColumn(agent.admin_state_up), agent.binary, )) @@ -246,7 +246,7 @@ def test_network_agents_list(self): self.network.agents.assert_called_once_with(**{}) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_network_agents_list_agent_type(self): arglist = [ @@ -263,7 +263,7 @@ def test_network_agents_list_agent_type(self): 'agent_type': 'DHCP agent', }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_network_agents_list_host(self): arglist = [ @@ -280,7 +280,7 @@ def test_network_agents_list_host(self): 'host': self.network_agents[0].host, }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_network_agents_list_networks(self): arglist = [ @@ -298,7 +298,7 @@ def test_network_agents_list_networks(self): self.network.network_hosting_dhcp_agents.assert_called_once_with( *attrs) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_network_agents_list_routers(self): arglist = [ @@ -318,7 +318,7 @@ def test_network_agents_list_routers(self): *attrs) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_network_agents_list_routers_with_long_option(self): arglist = [ @@ -343,7 +343,7 @@ def test_network_agents_list_routers_with_long_option(self): router_agent_data = [d + ('',) for d in self.data] self.assertEqual(router_agent_columns, columns) - self.assertEqual(router_agent_data, list(data)) + self.assertListItemEqual(router_agent_data, list(data)) class TestRemoveNetworkFromAgent(TestNetworkAgent): @@ -531,12 +531,12 @@ class TestShowNetworkAgent(TestNetworkAgent): 'id', ) data = ( - network_agent._format_admin_state(_network_agent.is_admin_state_up), + network_agent.AdminStateColumn(_network_agent.admin_state_up), _network_agent.agent_type, - network_agent._format_alive(_network_agent.is_alive), + network_agent.AliveColumn(_network_agent.is_alive), _network_agent.availability_zone, _network_agent.binary, - utils.format_dict(_network_agent.configurations), + format_columns.DictColumn(_network_agent.configurations), _network_agent.host, _network_agent.id, ) @@ -571,4 +571,4 @@ def test_show_all_options(self): self.network.get_agent.assert_called_once_with( self._network_agent.id) self.assertEqual(self.columns, columns) - self.assertEqual(list(self.data), list(data)) + self.assertItemEqual(list(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 8ac3e54f4a..acf85f4652 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -15,6 +15,7 @@ import mock from mock import call +from osc_lib.cli import format_columns from osc_lib import exceptions from osc_lib import utils @@ -68,22 +69,22 @@ def _get_common_cols_data(fake_port): ) data = ( - port._format_admin_state(fake_port.admin_state_up), - utils.format_list_of_dicts(fake_port.allowed_address_pairs), + port.AdminStateColumn(fake_port.admin_state_up), + format_columns.ListDictColumn(fake_port.allowed_address_pairs), fake_port.binding_host_id, - utils.format_dict(fake_port.binding_profile), - utils.format_dict(fake_port.binding_vif_details), + format_columns.DictColumn(fake_port.binding_profile), + format_columns.DictColumn(fake_port.binding_vif_details), fake_port.binding_vif_type, fake_port.binding_vnic_type, fake_port.data_plane_status, fake_port.description, fake_port.device_id, fake_port.device_owner, - utils.format_list_of_dicts(fake_port.dns_assignment), + format_columns.ListDictColumn(fake_port.dns_assignment), fake_port.dns_domain, fake_port.dns_name, - utils.format_list_of_dicts(fake_port.extra_dhcp_opts), - utils.format_list_of_dicts(fake_port.fixed_ips), + format_columns.ListDictColumn(fake_port.extra_dhcp_opts), + format_columns.ListDictColumn(fake_port.fixed_ips), fake_port.id, fake_port.mac_address, fake_port.name, @@ -91,9 +92,9 @@ def _get_common_cols_data(fake_port): fake_port.port_security_enabled, fake_port.project_id, fake_port.qos_policy_id, - utils.format_list(fake_port.security_group_ids), + format_columns.ListColumn(fake_port.security_group_ids), fake_port.status, - utils.format_list(fake_port.tags), + format_columns.ListColumn(fake_port.tags), fake_port.uplink_status_propagation, ) @@ -141,7 +142,7 @@ def test_create_default_options(self): self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_full_options(self): arglist = [ @@ -199,7 +200,7 @@ def test_create_full_options(self): }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_invalid_json_binding_profile(self): arglist = [ @@ -250,7 +251,7 @@ def test_create_json_binding_profile(self): }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_with_security_group(self): secgroup = network_fakes.FakeSecurityGroup.create_one_security_group() @@ -279,7 +280,7 @@ def test_create_with_security_group(self): }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_port_with_dns_name(self): arglist = [ @@ -305,7 +306,7 @@ def test_create_port_with_dns_name(self): }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_with_security_groups(self): sg_1 = network_fakes.FakeSecurityGroup.create_one_security_group() @@ -335,7 +336,7 @@ def test_create_with_security_groups(self): }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_with_no_security_groups(self): arglist = [ @@ -361,7 +362,7 @@ def test_create_with_no_security_groups(self): }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_with_no_fixed_ips(self): arglist = [ @@ -387,7 +388,7 @@ def test_create_with_no_fixed_ips(self): }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_port_with_allowed_address_pair_ipaddr(self): pairs = [{'ip_address': '192.168.1.123'}, @@ -417,7 +418,7 @@ def test_create_port_with_allowed_address_pair_ipaddr(self): }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_port_with_allowed_address_pair(self): pairs = [{'ip_address': '192.168.1.123', @@ -453,7 +454,7 @@ def test_create_port_with_allowed_address_pair(self): }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_port_with_qos(self): qos_policy = network_fakes.FakeNetworkQosPolicy.create_one_qos_policy() @@ -481,7 +482,7 @@ def test_create_port_with_qos(self): }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_port_security_enabled(self): arglist = [ @@ -565,7 +566,7 @@ def _test_create_with_tag(self, add_tags=True): else: self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_with_tags(self): self._test_create_with_tag(add_tags=True) @@ -602,7 +603,7 @@ def _test_create_with_uplink_status_propagation(self, enable=True): }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_with_uplink_status_propagation_enabled(self): self._test_create_with_uplink_status_propagation(enable=True) @@ -719,7 +720,7 @@ class TestListPort(TestPort): prt.id, prt.name, prt.mac_address, - utils.format_list_of_dicts(prt.fixed_ips), + format_columns.ListDictColumn(prt.fixed_ips), prt.status, )) @@ -729,11 +730,11 @@ class TestListPort(TestPort): prt.id, prt.name, prt.mac_address, - utils.format_list_of_dicts(prt.fixed_ips), + format_columns.ListDictColumn(prt.fixed_ips), prt.status, - utils.format_list(prt.security_group_ids), + format_columns.ListColumn(prt.security_group_ids), prt.device_owner, - utils.format_list(prt.tags), + format_columns.ListColumn(prt.tags), )) def setUp(self): @@ -762,7 +763,7 @@ def test_port_list_no_options(self): self.network.ports.assert_called_once_with() self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_port_list_router_opt(self): arglist = [ @@ -781,7 +782,7 @@ def test_port_list_router_opt(self): 'device_id': 'fake-router-id' }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) @mock.patch.object(utils, 'find_resource') def test_port_list_with_server_option(self, mock_find): @@ -801,7 +802,7 @@ def test_port_list_with_server_option(self, mock_find): device_id=fake_server.id) mock_find.assert_called_once_with(mock.ANY, 'fake-server-name') self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_port_list_device_id_opt(self): arglist = [ @@ -820,7 +821,7 @@ def test_port_list_device_id_opt(self): 'device_id': self._ports[0].device_id }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_port_list_device_owner_opt(self): arglist = [ @@ -839,7 +840,7 @@ def test_port_list_device_owner_opt(self): 'device_owner': self._ports[0].device_owner }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_port_list_all_opt(self): arglist = [ @@ -867,7 +868,7 @@ def test_port_list_all_opt(self): 'mac_address': self._ports[0].mac_address }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_port_list_mac_address_opt(self): arglist = [ @@ -886,7 +887,7 @@ def test_port_list_mac_address_opt(self): 'mac_address': self._ports[0].mac_address }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_port_list_fixed_ip_opt_ip_address(self): ip_address = self._ports[0].fixed_ips[0]['ip_address'] @@ -904,7 +905,7 @@ def test_port_list_fixed_ip_opt_ip_address(self): self.network.ports.assert_called_once_with(**{ 'fixed_ips': ['ip_address=%s' % ip_address]}) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_port_list_fixed_ip_opt_ip_address_substr(self): ip_address_ss = self._ports[0].fixed_ips[0]['ip_address'][:-1] @@ -922,7 +923,7 @@ def test_port_list_fixed_ip_opt_ip_address_substr(self): self.network.ports.assert_called_once_with(**{ 'fixed_ips': ['ip_address_substr=%s' % ip_address_ss]}) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_port_list_fixed_ip_opt_subnet_id(self): subnet_id = self._ports[0].fixed_ips[0]['subnet_id'] @@ -942,7 +943,7 @@ def test_port_list_fixed_ip_opt_subnet_id(self): self.network.ports.assert_called_once_with(**{ 'fixed_ips': ['subnet_id=%s' % subnet_id]}) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_port_list_fixed_ip_opts(self): subnet_id = self._ports[0].fixed_ips[0]['subnet_id'] @@ -966,7 +967,7 @@ def test_port_list_fixed_ip_opts(self): 'fixed_ips': ['subnet_id=%s' % subnet_id, 'ip_address=%s' % ip_address]}) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_port_list_fixed_ips(self): subnet_id = self._ports[0].fixed_ips[0]['subnet_id'] @@ -990,7 +991,7 @@ def test_port_list_fixed_ips(self): 'fixed_ips': ['subnet_id=%s' % subnet_id, 'ip_address=%s' % ip_address]}) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_list_port_with_long(self): arglist = [ @@ -1007,7 +1008,7 @@ def test_list_port_with_long(self): self.network.ports.assert_called_once_with() self.assertEqual(self.columns_long, columns) - self.assertEqual(self.data_long, list(data)) + self.assertListItemEqual(self.data_long, list(data)) def test_port_list_project(self): project = identity_fakes.FakeProject.create_one_project() @@ -1025,7 +1026,7 @@ def test_port_list_project(self): self.network.ports.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_port_list_project_domain(self): project = identity_fakes.FakeProject.create_one_project() @@ -1045,7 +1046,7 @@ def test_port_list_project_domain(self): self.network.ports.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_list_with_tag_options(self): arglist = [ @@ -1070,7 +1071,7 @@ def test_list_with_tag_options(self): 'not_any_tags': 'black,white'} ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) class TestSetPort(TestPort): @@ -1644,7 +1645,7 @@ def test_show_all_options(self): self._port.name, ignore_missing=False) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) class TestUnsetPort(TestPort): diff --git a/openstackclient/tests/unit/network/v2/test_router.py b/openstackclient/tests/unit/network/v2/test_router.py index 618adf3516..3e5ceac436 100644 --- a/openstackclient/tests/unit/network/v2/test_router.py +++ b/openstackclient/tests/unit/network/v2/test_router.py @@ -14,8 +14,8 @@ import mock from mock import call +from osc_lib.cli import format_columns from osc_lib import exceptions -from osc_lib import utils as osc_utils from openstackclient.network.v2 import router from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3 @@ -132,19 +132,19 @@ class TestCreateRouter(TestRouter): 'tags', ) data = ( - router._format_admin_state(new_router.admin_state_up), - osc_utils.format_list(new_router.availability_zone_hints), - osc_utils.format_list(new_router.availability_zones), + router.AdminStateColumn(new_router.admin_state_up), + format_columns.ListColumn(new_router.availability_zone_hints), + format_columns.ListColumn(new_router.availability_zones), new_router.description, new_router.distributed, - router._format_router_info(new_router.external_gateway_info), + router.RouterInfoColumn(new_router.external_gateway_info), new_router.ha, new_router.id, new_router.name, new_router.tenant_id, - router._format_routes(new_router.routes), + router.RoutesColumn(new_router.routes), new_router.status, - osc_utils.format_list(new_router.tags), + format_columns.ListColumn(new_router.tags), ) def setUp(self): @@ -184,7 +184,7 @@ def test_create_default_options(self): }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def _test_create_with_ha_options(self, option, ha): arglist = [ @@ -208,7 +208,7 @@ def _test_create_with_ha_options(self, option, ha): 'ha': ha, }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_with_ha_option(self): self._test_create_with_ha_options('--ha', True) @@ -237,7 +237,7 @@ def _test_create_with_distributed_options(self, option, distributed): 'distributed': distributed, }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_with_distributed_option(self): self._test_create_with_distributed_options('--distributed', True) @@ -268,7 +268,7 @@ def test_create_with_AZ_hints(self): }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def _test_create_with_tag(self, add_tags=True): arglist = [self.new_router.name] @@ -301,7 +301,7 @@ def _test_create_with_tag(self, add_tags=True): else: self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_with_tags(self): self._test_create_with_tag(add_tags=True) @@ -422,7 +422,7 @@ class TestListRouter(TestRouter): r.id, r.name, r.status, - router._format_admin_state(r.admin_state_up), + router.AdminStateColumn(r.admin_state_up), r.tenant_id, r.distributed, r.ha, @@ -447,10 +447,10 @@ class TestListRouter(TestRouter): r = routers[i] data_long.append( data[i] + ( - router._format_routes(r.routes), - router._format_router_info(r.external_gateway_info), - osc_utils.format_list(r.availability_zones), - osc_utils.format_list(r.tags), + router.RoutesColumn(r.routes), + router.RouterInfoColumn(r.external_gateway_info), + format_columns.ListColumn(r.availability_zones), + format_columns.ListColumn(r.tags), ) ) data_long_no_az = [] @@ -458,9 +458,9 @@ class TestListRouter(TestRouter): r = routers[i] data_long_no_az.append( data[i] + ( - router._format_routes(r.routes), - router._format_router_info(r.external_gateway_info), - osc_utils.format_list(r.tags), + router.RoutesColumn(r.routes), + router.RouterInfoColumn(r.external_gateway_info), + format_columns.ListColumn(r.tags), ) ) @@ -494,7 +494,7 @@ def test_router_list_no_options(self): self.network.routers.assert_called_once_with() self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_router_list_no_ha_no_distributed(self): _routers = network_fakes.FakeRouter.create_routers({ @@ -531,7 +531,7 @@ def test_router_list_long(self): self.network.routers.assert_called_once_with() self.assertEqual(self.columns_long, columns) - self.assertEqual(self.data_long, list(data)) + self.assertListItemEqual(self.data_long, list(data)) def test_router_list_long_no_az(self): arglist = [ @@ -552,7 +552,7 @@ def test_router_list_long_no_az(self): self.network.routers.assert_called_once_with() self.assertEqual(self.columns_long_no_az, columns) - self.assertEqual(self.data_long_no_az, list(data)) + self.assertListItemEqual(self.data_long_no_az, list(data)) def test_list_name(self): test_name = "fakename" @@ -570,7 +570,7 @@ def test_list_name(self): **{'name': test_name} ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_router_list_enable(self): arglist = [ @@ -587,7 +587,7 @@ def test_router_list_enable(self): **{'admin_state_up': True, 'is_admin_state_up': True} ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_router_list_disable(self): arglist = [ @@ -605,7 +605,7 @@ def test_router_list_disable(self): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_router_list_project(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -623,7 +623,7 @@ def test_router_list_project(self): self.network.routers.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_router_list_project_domain(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -643,7 +643,7 @@ def test_router_list_project_domain(self): self.network.routers.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_router_list_agents_no_args(self): arglist = [ @@ -671,7 +671,7 @@ def test_router_list_agents(self): self.network.agent_hosted_routers( *attrs) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_list_with_tag_options(self): arglist = [ @@ -696,7 +696,7 @@ def test_list_with_tag_options(self): 'not_any_tags': 'black,white'} ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) class TestRemovePortFromRouter(TestRouter): @@ -1260,20 +1260,20 @@ class TestShowRouter(TestRouter): 'tags', ) data = ( - router._format_admin_state(_router.admin_state_up), - osc_utils.format_list(_router.availability_zone_hints), - osc_utils.format_list(_router.availability_zones), + router.AdminStateColumn(_router.admin_state_up), + format_columns.ListColumn(_router.availability_zone_hints), + format_columns.ListColumn(_router.availability_zones), _router.description, _router.distributed, - router._format_router_info(_router.external_gateway_info), + router.RouterInfoColumn(_router.external_gateway_info), _router.ha, _router.id, - router._format_router_info(_router.interfaces_info), + router.RouterInfoColumn(_router.interfaces_info), _router.name, _router.tenant_id, - router._format_routes(_router.routes), + router.RoutesColumn(_router.routes), _router.status, - osc_utils.format_list(_router.tags), + format_columns.ListColumn(_router.tags), ) def setUp(self): @@ -1309,7 +1309,7 @@ def test_show_all_options(self): 'device_id': self._router.id }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_show_no_ha_no_distributed(self): _router = network_fakes.FakeRouter.create_one_router({ diff --git a/openstackclient/tests/unit/network/v2/test_security_group_compute.py b/openstackclient/tests/unit/network/v2/test_security_group_compute.py index c949e2c82d..df36006899 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_compute.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_compute.py @@ -56,7 +56,7 @@ class TestCreateSecurityGroupCompute(TestSecurityGroupCompute): _security_group['id'], _security_group['name'], _security_group['tenant_id'], - '', + security_group.ComputeSecurityGroupRulesColumn([]), ) def setUp(self): @@ -88,7 +88,7 @@ def test_security_group_create_min_options(self, sg_mock): self._security_group['name'], ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_security_group_create_all_options(self, sg_mock): sg_mock.return_value = self._security_group @@ -109,7 +109,7 @@ def test_security_group_create_all_options(self, sg_mock): self._security_group['description'], ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) @mock.patch( @@ -255,7 +255,7 @@ def test_security_group_list_no_options(self, sg_mock): kwargs = {'search_opts': {'all_tenants': False}} sg_mock.assert_called_once_with(**kwargs) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_security_group_list_all_projects(self, sg_mock): sg_mock.return_value = self._security_groups @@ -272,7 +272,7 @@ def test_security_group_list_all_projects(self, sg_mock): kwargs = {'search_opts': {'all_tenants': True}} sg_mock.assert_called_once_with(**kwargs) self.assertEqual(self.columns_all_projects, columns) - self.assertEqual(self.data_all_projects, list(data)) + self.assertListItemEqual(self.data_all_projects, list(data)) @mock.patch( @@ -372,8 +372,7 @@ class TestShowSecurityGroupCompute(TestSecurityGroupCompute): _security_group['id'], _security_group['name'], _security_group['tenant_id'], - security_group._format_compute_security_group_rules( - [_security_group_rule]), + security_group.ComputeSecurityGroupRulesColumn([_security_group_rule]), ) def setUp(self): @@ -402,4 +401,4 @@ def test_security_group_show_all_options(self, sg_mock): sg_mock.assert_called_once_with(self._security_group['id']) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) diff --git a/openstackclient/tests/unit/network/v2/test_security_group_network.py b/openstackclient/tests/unit/network/v2/test_security_group_network.py index 83208287cc..57698ec586 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_network.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_network.py @@ -57,7 +57,7 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork): _security_group.id, _security_group.name, _security_group.project_id, - '', + security_group.NetworkSecurityGroupRulesColumn([]), _security_group.tags, ) @@ -94,7 +94,7 @@ def test_create_min_options(self): 'name': self._security_group.name, }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_all_options(self): arglist = [ @@ -119,7 +119,7 @@ def test_create_all_options(self): 'tenant_id': self.project.id, }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def _test_create_with_tag(self, add_tags=True): arglist = [self._security_group.name] @@ -150,7 +150,7 @@ def _test_create_with_tag(self, add_tags=True): else: self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_with_tags(self): self._test_create_with_tag(add_tags=True) @@ -287,7 +287,7 @@ def test_security_group_list_no_options(self): self.network.security_groups.assert_called_once_with() self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_security_group_list_all_projects(self): arglist = [ @@ -302,7 +302,7 @@ def test_security_group_list_all_projects(self): self.network.security_groups.assert_called_once_with() self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_security_group_list_project(self): project = identity_fakes.FakeProject.create_one_project() @@ -320,7 +320,7 @@ def test_security_group_list_project(self): self.network.security_groups.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_security_group_list_project_domain(self): project = identity_fakes.FakeProject.create_one_project() @@ -340,7 +340,7 @@ def test_security_group_list_project_domain(self): self.network.security_groups.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_list_with_tag_options(self): arglist = [ @@ -490,7 +490,7 @@ class TestShowSecurityGroupNetwork(TestSecurityGroupNetwork): _security_group.id, _security_group.name, _security_group.project_id, - security_group._format_network_security_group_rules( + security_group.NetworkSecurityGroupRulesColumn( [_security_group_rule._info]), _security_group.tags, ) @@ -522,7 +522,7 @@ def test_show_all_options(self): self.network.find_security_group.assert_called_once_with( self._security_group.id, ignore_missing=False) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) class TestUnsetSecurityGroupNetwork(TestSecurityGroupNetwork): diff --git a/openstackclient/tests/unit/network/v2/test_subnet.py b/openstackclient/tests/unit/network/v2/test_subnet.py index 39cb4f537d..9903b0423a 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet.py +++ b/openstackclient/tests/unit/network/v2/test_subnet.py @@ -14,8 +14,8 @@ import mock from mock import call +from osc_lib.cli import format_columns from osc_lib import exceptions -from osc_lib import utils from openstackclient.network.v2 import subnet as subnet_v2 from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3 @@ -131,13 +131,13 @@ def _init_subnet_variables(self): ) self.data = ( - subnet_v2._format_allocation_pools(self._subnet.allocation_pools), + subnet_v2.AllocationPoolsColumn(self._subnet.allocation_pools), self._subnet.cidr, self._subnet.description, - utils.format_list(self._subnet.dns_nameservers), + format_columns.ListColumn(self._subnet.dns_nameservers), self._subnet.enable_dhcp, self._subnet.gateway_ip, - subnet_v2._format_host_routes(self._subnet.host_routes), + subnet_v2.HostRoutesColumn(self._subnet.host_routes), self._subnet.id, self._subnet.ip_version, self._subnet.ipv6_address_mode, @@ -146,20 +146,20 @@ def _init_subnet_variables(self): self._subnet.network_id, self._subnet.project_id, self._subnet.segment_id, - utils.format_list(self._subnet.service_types), + format_columns.ListColumn(self._subnet.service_types), self._subnet.subnetpool_id, - utils.format_list(self._subnet.tags), + format_columns.ListColumn(self._subnet.tags), ) self.data_subnet_pool = ( - subnet_v2._format_allocation_pools( + subnet_v2.AllocationPoolsColumn( self._subnet_from_pool.allocation_pools), self._subnet_from_pool.cidr, self._subnet_from_pool.description, - utils.format_list(self._subnet_from_pool.dns_nameservers), + format_columns.ListColumn(self._subnet_from_pool.dns_nameservers), self._subnet_from_pool.enable_dhcp, self._subnet_from_pool.gateway_ip, - subnet_v2._format_host_routes(self._subnet_from_pool.host_routes), + subnet_v2.HostRoutesColumn(self._subnet_from_pool.host_routes), self._subnet_from_pool.id, self._subnet_from_pool.ip_version, self._subnet_from_pool.ipv6_address_mode, @@ -168,20 +168,20 @@ def _init_subnet_variables(self): self._subnet_from_pool.network_id, self._subnet_from_pool.project_id, self._subnet_from_pool.segment_id, - utils.format_list(self._subnet_from_pool.service_types), + format_columns.ListColumn(self._subnet_from_pool.service_types), self._subnet_from_pool.subnetpool_id, - utils.format_list(self._subnet.tags), + format_columns.ListColumn(self._subnet_from_pool.tags), ) self.data_ipv6 = ( - subnet_v2._format_allocation_pools( + subnet_v2.AllocationPoolsColumn( self._subnet_ipv6.allocation_pools), self._subnet_ipv6.cidr, self._subnet_ipv6.description, - utils.format_list(self._subnet_ipv6.dns_nameservers), + format_columns.ListColumn(self._subnet_ipv6.dns_nameservers), self._subnet_ipv6.enable_dhcp, self._subnet_ipv6.gateway_ip, - subnet_v2._format_host_routes(self._subnet_ipv6.host_routes), + subnet_v2.HostRoutesColumn(self._subnet_ipv6.host_routes), self._subnet_ipv6.id, self._subnet_ipv6.ip_version, self._subnet_ipv6.ipv6_address_mode, @@ -190,9 +190,9 @@ def _init_subnet_variables(self): self._subnet_ipv6.network_id, self._subnet_ipv6.project_id, self._subnet_ipv6.segment_id, - utils.format_list(self._subnet_ipv6.service_types), + format_columns.ListColumn(self._subnet_ipv6.service_types), self._subnet_ipv6.subnetpool_id, - utils.format_list(self._subnet.tags), + format_columns.ListColumn(self._subnet_ipv6.tags), ) def setUp(self): @@ -255,7 +255,7 @@ def test_create_default_options(self): }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_from_subnet_pool_options(self): # Mock SDK calls for this test. @@ -317,7 +317,7 @@ def test_create_from_subnet_pool_options(self): 'service_types': self._subnet_from_pool.service_types, }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data_subnet_pool, data) + self.assertItemEqual(self.data_subnet_pool, data) def test_create_options_subnet_range_ipv6(self): # Mock SDK calls for this test. @@ -390,7 +390,7 @@ def test_create_options_subnet_range_ipv6(self): }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertEqual(self.data_ipv6, data) + self.assertItemEqual(self.data_ipv6, data) def test_create_with_network_segment(self): # Mock SDK calls for this test. @@ -424,7 +424,7 @@ def test_create_with_network_segment(self): }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_with_description(self): # Mock SDK calls for this test. @@ -458,7 +458,7 @@ def test_create_with_description(self): }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def _test_create_with_tag(self, add_tags=True): arglist = [ @@ -497,7 +497,7 @@ def _test_create_with_tag(self, add_tags=True): else: self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_with_tags(self): self._test_create_with_tag(add_tags=True) @@ -625,13 +625,13 @@ class TestListSubnet(TestSubnet): subnet.cidr, subnet.tenant_id, subnet.enable_dhcp, - utils.format_list(subnet.dns_nameservers), - subnet_v2._format_allocation_pools(subnet.allocation_pools), - utils.format_list(subnet.host_routes), + format_columns.ListColumn(subnet.dns_nameservers), + subnet_v2.AllocationPoolsColumn(subnet.allocation_pools), + subnet_v2.HostRoutesColumn(subnet.host_routes), subnet.ip_version, subnet.gateway_ip, - utils.format_list(subnet.service_types), - utils.format_list(subnet.tags), + format_columns.ListColumn(subnet.service_types), + format_columns.ListColumn(subnet.tags), )) def setUp(self): @@ -653,7 +653,7 @@ def test_subnet_list_no_options(self): self.network.subnets.assert_called_once_with() self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_subnet_list_long(self): arglist = [ @@ -668,7 +668,7 @@ def test_subnet_list_long(self): self.network.subnets.assert_called_once_with() self.assertEqual(self.columns_long, columns) - self.assertEqual(self.data_long, list(data)) + self.assertListItemEqual(self.data_long, list(data)) def test_subnet_list_ip_version(self): arglist = [ @@ -684,7 +684,7 @@ def test_subnet_list_ip_version(self): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_subnet_list_dhcp(self): arglist = [ @@ -700,7 +700,7 @@ def test_subnet_list_dhcp(self): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_subnet_list_no_dhcp(self): arglist = [ @@ -716,7 +716,7 @@ def test_subnet_list_no_dhcp(self): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_subnet_list_service_type(self): arglist = [ @@ -731,7 +731,7 @@ def test_subnet_list_service_type(self): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_subnet_list_project(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -749,7 +749,7 @@ def test_subnet_list_project(self): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_subnet_list_service_type_multiple(self): arglist = [ @@ -767,7 +767,7 @@ def test_subnet_list_service_type_multiple(self): 'network:floatingip_agent_gateway']} self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_subnet_list_project_domain(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -787,7 +787,7 @@ def test_subnet_list_project_domain(self): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_subnet_list_network(self): network = network_fakes.FakeNetwork.create_one_network() @@ -805,7 +805,7 @@ def test_subnet_list_network(self): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_subnet_list_gateway(self): subnet = network_fakes.FakeSubnet.create_one_subnet() @@ -823,7 +823,7 @@ def test_subnet_list_gateway(self): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_subnet_list_name(self): subnet = network_fakes.FakeSubnet.create_one_subnet() @@ -841,7 +841,7 @@ def test_subnet_list_name(self): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_subnet_list_subnet_range(self): subnet = network_fakes.FakeSubnet.create_one_subnet() @@ -859,7 +859,7 @@ def test_subnet_list_subnet_range(self): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_list_with_tag_options(self): arglist = [ @@ -1154,13 +1154,13 @@ class TestShowSubnet(TestSubnet): ) data = ( - subnet_v2._format_allocation_pools(_subnet.allocation_pools), + subnet_v2.AllocationPoolsColumn(_subnet.allocation_pools), _subnet.cidr, _subnet.description, - utils.format_list(_subnet.dns_nameservers), + format_columns.ListColumn(_subnet.dns_nameservers), _subnet.enable_dhcp, _subnet.gateway_ip, - utils.format_list(_subnet.host_routes), + subnet_v2.HostRoutesColumn(_subnet.host_routes), _subnet.id, _subnet.ip_version, _subnet.ipv6_address_mode, @@ -1169,9 +1169,9 @@ class TestShowSubnet(TestSubnet): _subnet.network_id, _subnet.tenant_id, _subnet.segment_id, - utils.format_list(_subnet.service_types), + format_columns.ListColumn(_subnet.service_types), _subnet.subnetpool_id, - utils.format_list(_subnet.tags), + format_columns.ListColumn(_subnet.tags), ) def setUp(self): @@ -1206,7 +1206,7 @@ def test_show_all_options(self): self._subnet.name, ignore_missing=False) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) class TestUnsetSubnet(TestSubnet): diff --git a/openstackclient/tests/unit/network/v2/test_subnet_pool.py b/openstackclient/tests/unit/network/v2/test_subnet_pool.py index 3d8f1028e6..2271c08944 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/unit/network/v2/test_subnet_pool.py @@ -15,8 +15,9 @@ import mock from mock import call + +from osc_lib.cli import format_columns from osc_lib import exceptions -from osc_lib import utils from openstackclient.network.v2 import subnet_pool from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3 @@ -73,10 +74,10 @@ class TestCreateSubnetPool(TestSubnetPool): _subnet_pool.max_prefixlen, _subnet_pool.min_prefixlen, _subnet_pool.name, - utils.format_list(_subnet_pool.prefixes), + format_columns.ListColumn(_subnet_pool.prefixes), _subnet_pool.project_id, _subnet_pool.shared, - utils.format_list(_subnet_pool.tags), + format_columns.ListColumn(_subnet_pool.tags), ) def setUp(self): @@ -133,7 +134,7 @@ def test_create_default_options(self): }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_prefixlen_options(self): arglist = [ @@ -163,7 +164,7 @@ def test_create_prefixlen_options(self): 'name': self._subnet_pool.name, }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_len_negative(self): arglist = [ @@ -201,7 +202,7 @@ def test_create_project_domain(self): 'name': self._subnet_pool.name, }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_address_scope_option(self): arglist = [ @@ -224,7 +225,7 @@ def test_create_address_scope_option(self): 'name': self._subnet_pool.name, }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_default_and_shared_options(self): arglist = [ @@ -250,7 +251,7 @@ def test_create_default_and_shared_options(self): 'shared': True, }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_with_description(self): arglist = [ @@ -273,7 +274,7 @@ def test_create_with_description(self): 'description': self._subnet_pool.description, }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_with_default_quota(self): arglist = [ @@ -294,7 +295,7 @@ def test_create_with_default_quota(self): 'default_quota': 10, }) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def _test_create_with_tag(self, add_tags=True): arglist = [ @@ -328,7 +329,7 @@ def _test_create_with_tag(self, add_tags=True): else: self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_create_with_tags(self): self._test_create_with_tag(add_tags=True) @@ -441,7 +442,7 @@ class TestListSubnetPool(TestSubnetPool): data.append(( pool.id, pool.name, - utils.format_list(pool.prefixes), + format_columns.ListColumn(pool.prefixes), )) data_long = [] @@ -449,12 +450,12 @@ class TestListSubnetPool(TestSubnetPool): data_long.append(( pool.id, pool.name, - utils.format_list(pool.prefixes), + format_columns.ListColumn(pool.prefixes), pool.default_prefixlen, pool.address_scope_id, pool.is_default, pool.shared, - utils.format_list(pool.tags), + format_columns.ListColumn(pool.tags), )) def setUp(self): @@ -476,7 +477,7 @@ def test_subnet_pool_list_no_option(self): self.network.subnet_pools.assert_called_once_with() self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_subnet_pool_list_long(self): arglist = [ @@ -491,7 +492,7 @@ def test_subnet_pool_list_long(self): self.network.subnet_pools.assert_called_once_with() self.assertEqual(self.columns_long, columns) - self.assertEqual(self.data_long, list(data)) + self.assertListItemEqual(self.data_long, list(data)) def test_subnet_pool_list_no_share(self): arglist = [ @@ -507,7 +508,7 @@ def test_subnet_pool_list_no_share(self): self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_subnet_pool_list_share(self): arglist = [ @@ -523,7 +524,7 @@ def test_subnet_pool_list_share(self): self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_subnet_pool_list_no_default(self): arglist = [ @@ -539,7 +540,7 @@ def test_subnet_pool_list_no_default(self): self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_subnet_pool_list_default(self): arglist = [ @@ -555,7 +556,7 @@ def test_subnet_pool_list_default(self): self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_subnet_pool_list_project(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -573,7 +574,7 @@ def test_subnet_pool_list_project(self): self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_subnet_pool_list_project_domain(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -593,7 +594,7 @@ def test_subnet_pool_list_project_domain(self): self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_subnet_pool_list_name(self): subnet_pool = network_fakes.FakeSubnetPool.create_one_subnet_pool() @@ -611,7 +612,7 @@ def test_subnet_pool_list_name(self): self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_subnet_pool_list_address_scope(self): addr_scope = network_fakes.FakeAddressScope.create_one_address_scope() @@ -629,7 +630,7 @@ def test_subnet_pool_list_address_scope(self): self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_list_with_tag_options(self): arglist = [ @@ -654,7 +655,7 @@ def test_list_with_tag_options(self): 'not_any_tags': 'black,white'} ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) class TestSetSubnetPool(TestSubnetPool): @@ -969,10 +970,10 @@ class TestShowSubnetPool(TestSubnetPool): _subnet_pool.max_prefixlen, _subnet_pool.min_prefixlen, _subnet_pool.name, - utils.format_list(_subnet_pool.prefixes), + format_columns.ListColumn(_subnet_pool.prefixes), _subnet_pool.tenant_id, _subnet_pool.shared, - utils.format_list(_subnet_pool.tags), + format_columns.ListColumn(_subnet_pool.tags), ) def setUp(self): @@ -1008,7 +1009,7 @@ def test_show_all_options(self): ignore_missing=False ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) class TestUnsetSubnetPool(TestSubnetPool): diff --git a/openstackclient/tests/unit/utils.py b/openstackclient/tests/unit/utils.py index 926dad87ea..c15d8bbfe2 100644 --- a/openstackclient/tests/unit/utils.py +++ b/openstackclient/tests/unit/utils.py @@ -19,6 +19,8 @@ import fixtures import testtools +from cliff import columns as cliff_columns + from openstackclient.tests.unit import fakes @@ -80,3 +82,18 @@ def check_parser(self, cmd, args, verify_args): self.assertIn(attr, parsed_args) self.assertEqual(value, getattr(parsed_args, attr)) return parsed_args + + def assertListItemEqual(self, expected, actual): + self.assertEqual(len(expected), len(actual)) + for item_expected, item_actual in zip(expected, actual): + self.assertItemEqual(item_expected, item_actual) + + def assertItemEqual(self, expected, actual): + self.assertEqual(len(expected), len(actual)) + for col_expected, col_actual in zip(expected, actual): + if isinstance(col_expected, cliff_columns.FormattableColumn): + self.assertIsInstance(col_actual, col_expected.__class__) + self.assertEqual(col_expected.human_readable(), + col_actual.human_readable()) + else: + self.assertEqual(col_expected, col_actual) From 4b91cd49658bd5a9224976ebd3a6f352d1eef5b0 Mon Sep 17 00:00:00 2001 From: Brian Haley Date: Thu, 9 May 2019 15:08:54 -0400 Subject: [PATCH 2053/3095] Stop leaving temp files after unit test runs test_shell.CLOUD_2 is using an absolute path for a temp file, so leaves /tmp/test_log_file around after the unit tests are run. Use a fixture instead so it's cleaned automatically, which also removes the possibility of two tests using the same file and interfering with each other. Change-Id: If722b860be4010b91635c6d46f634da980e17152 --- .../tests/unit/integ/cli/test_shell.py | 19 +++++++++- openstackclient/tests/unit/test_shell.py | 37 ++++++++++--------- 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/openstackclient/tests/unit/integ/cli/test_shell.py b/openstackclient/tests/unit/integ/cli/test_shell.py index 70303be398..200f9b1869 100644 --- a/openstackclient/tests/unit/integ/cli/test_shell.py +++ b/openstackclient/tests/unit/integ/cli/test_shell.py @@ -12,6 +12,7 @@ import copy +import fixtures import mock from osc_lib.tests import utils as osc_lib_utils @@ -399,6 +400,16 @@ def setUp(self): test_shell.PUBLIC_1['public-clouds']['megadodo']['auth']['auth_url'] \ = test_base.V3_AUTH_URL + def get_temp_file_path(self, filename): + """Returns an absolute path for a temporary file. + + :param filename: filename + :type filename: string + :returns: absolute file path string + """ + temp_dir = self.useFixture(fixtures.TempDir()) + return temp_dir.join(filename) + @mock.patch(CONFIG_MOCK_BASE + ".OpenStackConfig._load_vendor_file") @mock.patch(CONFIG_MOCK_BASE + ".OpenStackConfig._load_config_file") def test_shell_args_precedence_1(self, config_mock, vendor_mock): @@ -408,7 +419,9 @@ def test_shell_args_precedence_1(self, config_mock, vendor_mock): """ def config_mock_return(): - return ('file.yaml', copy.deepcopy(test_shell.CLOUD_2)) + log_file = self.get_temp_file_path('test_log_file') + cloud2 = test_shell.get_cloud(log_file) + return ('file.yaml', cloud2) config_mock.side_effect = config_mock_return def vendor_mock_return(): @@ -478,7 +491,9 @@ def test_shell_args_precedence_2(self, config_mock, vendor_mock): """ def config_mock_return(): - return ('file.yaml', copy.deepcopy(test_shell.CLOUD_2)) + log_file = self.get_temp_file_path('test_log_file') + cloud2 = test_shell.get_cloud(log_file) + return ('file.yaml', cloud2) config_mock.side_effect = config_mock_return def vendor_mock_return(): diff --git a/openstackclient/tests/unit/test_shell.py b/openstackclient/tests/unit/test_shell.py index dff37f10b7..5d413e7e3c 100644 --- a/openstackclient/tests/unit/test_shell.py +++ b/openstackclient/tests/unit/test_shell.py @@ -70,23 +70,6 @@ } } -CLOUD_2 = { - 'clouds': { - 'megacloud': { - 'cloud': 'megadodo', - 'auth': { - 'project_name': 'heart-o-gold', - 'username': 'zaphod', - }, - 'region_name': 'occ-cloud,krikkit,occ-env', - 'log_file': '/tmp/test_log_file', - 'log_level': 'debug', - 'cert': 'mycert', - 'key': 'mickey', - } - } -} - PUBLIC_1 = { 'public-clouds': { 'megadodo': { @@ -118,6 +101,26 @@ } +def get_cloud(log_file): + CLOUD = { + 'clouds': { + 'megacloud': { + 'cloud': 'megadodo', + 'auth': { + 'project_name': 'heart-o-gold', + 'username': 'zaphod', + }, + 'region_name': 'occ-cloud,krikkit,occ-env', + 'log_file': log_file, + 'log_level': 'debug', + 'cert': 'mycert', + 'key': 'mickey', + } + } + } + return CLOUD + + # Wrap the osc_lib make_shell() function to set the shell class since # osc-lib's TestShell class doesn't allow us to specify it yet. # TODO(dtroyer): remove this once the shell_class_patch patch is released From aabc67f3a2946c783056b5f282c9facad164e630 Mon Sep 17 00:00:00 2001 From: melissaml Date: Sun, 12 May 2019 04:40:55 +0800 Subject: [PATCH 2054/3095] Rename review.openstack.org to review.opendev.org There are many references to review.openstack.org, and while the redirect should work, we can also go ahead and fix them. Change-Id: I82e3797dd4c05e4944f40c950b4fafe9a5334cbf --- doc/source/cli/backwards-incompatible.rst | 36 +++++++++---------- openstackclient/compute/v2/flavor.py | 2 +- .../functional/compute/v2/test_server.py | 2 +- .../tests/unit/api/test_object_store_v1.py | 2 +- openstackclient/volume/v2/volume_type.py | 2 +- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/doc/source/cli/backwards-incompatible.rst b/doc/source/cli/backwards-incompatible.rst index fcb68684bf..51c1d134b2 100644 --- a/doc/source/cli/backwards-incompatible.rst +++ b/doc/source/cli/backwards-incompatible.rst @@ -46,7 +46,7 @@ Release 3.12 * As of: 3.12.0 * Removed in: n/a * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1657956 - * Commit: https://review.openstack.org/#/c/423081/ + * Commit: https://review.opendev.org/#/c/423081/ Release 3.10 ------------ @@ -55,7 +55,7 @@ Release 3.10 with Nova-network clouds. * As of: 3.10 - * Commit: https://review.openstack.org/460679 + * Commit: https://review.opendev.org/460679 2. The positional argument ```` of the ``volume snapshot create`` command is no longer optional. @@ -67,7 +67,7 @@ Release 3.10 * As of: 3.10 * Bug: 1659894 - * Commit: https://review.openstack.org/440497 + * Commit: https://review.opendev.org/440497 Release 3.0 ----------- @@ -81,7 +81,7 @@ Release 3.0 * As of: 3.0 * Removed in: n/a * Bug: n/a - * Commit: https://review.openstack.org/332938 + * Commit: https://review.opendev.org/332938 Releases Before 3.0 @@ -95,7 +95,7 @@ Releases Before 3.0 * As of: 1.0.2 * Removed in: TBD * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1406654 - * Commit: https://review.openstack.org/#/c/147379/ + * Commit: https://review.opendev.org/#/c/147379/ 2. should not be optional for command `openstack service create` @@ -107,7 +107,7 @@ Releases Before 3.0 * As of: 1.0.2 * Removed in: TBD * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1404073 - * Commit: https://review.openstack.org/#/c/143242/ + * Commit: https://review.opendev.org/#/c/143242/ 3. Command `openstack security group rule delete` now requires rule id @@ -119,7 +119,7 @@ Releases Before 3.0 * As of: 1.2.1 * Removed in: NA * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1450872 - * Commit: https://review.openstack.org/#/c/179446/ + * Commit: https://review.opendev.org/#/c/179446/ 4. Command `openstack image create` does not update already existing image @@ -133,7 +133,7 @@ Releases Before 3.0 * As of: 1.5.0 * Removed in: NA * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1461817 - * Commit: https://review.openstack.org/#/c/194654/ + * Commit: https://review.opendev.org/#/c/194654/ 5. Command `openstack network list --dhcp` has been removed @@ -147,7 +147,7 @@ Releases Before 3.0 * As of: 1.6.0 * Removed in: NA * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/472613 - * Commit: https://review.openstack.org/#/c/194654/ + * Commit: https://review.opendev.org/#/c/194654/ 6. Plugin interface change for default API versions @@ -162,7 +162,7 @@ Releases Before 3.0 * As of: 1.2.1 * Removed in: NA * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1453229 - * Commit: https://review.openstack.org/#/c/181514/ + * Commit: https://review.opendev.org/#/c/181514/ 7. `image set` commands will no longer return the modified resource @@ -186,7 +186,7 @@ Releases Before 3.0 * As of 1.9.0 * Removed in: NA * Bug: https://launchpad.net/bugs/1506841 - * Commit: https://review.openstack.org/#/c/236736/ + * Commit: https://review.opendev.org/#/c/236736/ 9. `flavor set/unset` commands will no longer return the modified resource @@ -198,7 +198,7 @@ Releases Before 3.0 * As of: NA * Removed in: NA * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1546065 - * Commit: https://review.openstack.org/#/c/280663/ + * Commit: https://review.opendev.org/#/c/280663/ 10. `security group set` commands will no longer return the modified resource @@ -210,7 +210,7 @@ Releases Before 3.0 * As of: NA * Removed in: NA * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1546065 - * Commit: https://review.openstack.org/#/c/281087/ + * Commit: https://review.opendev.org/#/c/281087/ 11. `compute agent set` commands will no longer return the modified resource @@ -222,7 +222,7 @@ Releases Before 3.0 * As of: NA * Removed in: NA * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1546065 - * Commit: https://review.openstack.org/#/c/281088/ + * Commit: https://review.opendev.org/#/c/281088/ 12. ` ` should be optional for command `openstack compute agent set` @@ -235,7 +235,7 @@ Releases Before 3.0 * As of: NA * Removed in: NA * Bug: NA - * Commit: https://review.openstack.org/#/c/328819/ + * Commit: https://review.opendev.org/#/c/328819/ 13. `aggregate set` commands will no longer return the modified resource @@ -247,7 +247,7 @@ Releases Before 3.0 * As of: NA * Removed in: NA * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1546065 - * Commit: https://review.openstack.org/#/c/281089/ + * Commit: https://review.opendev.org/#/c/281089/ 14. Output of `ip floating list` command has changed. @@ -291,7 +291,7 @@ Releases Before 3.0 * As of: NA * Removed in: NA * Bug: https://bugs.launchpad.net/python-openstackclient/+bug/1519502 - * Commit: https://review.openstack.org/#/c/277720/ + * Commit: https://review.opendev.org/#/c/277720/ For Developers ============== @@ -302,4 +302,4 @@ update this file. To review all changes that are affected, use the following query: -https://review.openstack.org/#/q/project:openstack/python-openstackclient+AND+message:BackwardsIncompatibleImpact,n,z +https://review.opendev.org/#/q/project:openstack/python-openstackclient+AND+message:BackwardsIncompatibleImpact,n,z diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 2cc5f1e891..4f1e48af06 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -432,7 +432,7 @@ def take_action(self, parsed_args): projects = [utils.get_field(access, 'tenant_id') for access in flavor_access] # TODO(Huanxuan Ao): This format case can be removed after - # patch https://review.openstack.org/#/c/330223/ merged. + # patch https://review.opendev.org/#/c/330223/ merged. access_projects = utils.format_list(projects) except Exception as e: msg = _("Failed to get access projects list " diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py index c8fb44d380..aa7d7cb7be 100644 --- a/openstackclient/tests/functional/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -407,7 +407,7 @@ def test_server_boot_from_volume(self): cmd_output['status'], ) - # NOTE(dtroyer): Prior to https://review.openstack.org/#/c/407111 + # NOTE(dtroyer): Prior to https://review.opendev.org/#/c/407111 # --block-device-mapping was ignored if --volume # present on the command line. Now we should see the # attachment. diff --git a/openstackclient/tests/unit/api/test_object_store_v1.py b/openstackclient/tests/unit/api/test_object_store_v1.py index acf955501c..74b62493db 100644 --- a/openstackclient/tests/unit/api/test_object_store_v1.py +++ b/openstackclient/tests/unit/api/test_object_store_v1.py @@ -184,7 +184,7 @@ def base_object_create(self, file_contents, mock_open): } # TODO(dtroyer): When requests_mock gains the ability to # match against request.body add this check - # https://review.openstack.org/127316 + # https://review.opendev.org/127316 self.requests_mock.register_uri( 'PUT', FAKE_URL + '/qaz/counter.txt', diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index 71e94a2b4e..749d1dd6fd 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -501,7 +501,7 @@ def take_action(self, parsed_args): project_ids = [utils.get_field(item, 'project_id') for item in volume_type_access] # TODO(Rui Chen): This format list case can be removed after - # patch https://review.openstack.org/#/c/330223/ merged. + # patch https://review.opendev.org/#/c/330223/ merged. access_project_ids = utils.format_list(project_ids) except Exception as e: msg = _('Failed to get access project list for volume type ' From f1791179768115b6d074f70f9a8695f9c1e0b9f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natal=20Ng=C3=A9tal?= Date: Wed, 15 May 2019 14:13:49 +0200 Subject: [PATCH 2055/3095] Update sphinx requirement. Sphinx 2.0 no longer works on python 2.7, start cappingit there as well. Change-Id: I8a7d227b2f925066fc8213aa62b5756927ee263b --- doc/requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/requirements.txt b/doc/requirements.txt index a2649afb1f..91854914a5 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -3,5 +3,6 @@ # process, which may cause wedges in the gate later. openstackdocstheme>=1.23.2 # Apache-2.0 reno>=2.5.0 # Apache-2.0 -sphinx!=1.6.6,!=1.6.7,>=1.6.5 # BSD +sphinx!=1.6.6,!=1.6.7,>=1.6.5,<2.0.0;python_version=='2.7' # BSD +sphinx!=1.6.6,!=1.6.7,>=1.6.5;python_version>='3.4' # BSD sphinxcontrib-apidoc>=0.2.0 # BSD From bb659cf438dd22fa06a31d23c4f30c30f2a3376c Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 16 May 2019 07:43:49 -0500 Subject: [PATCH 2056/3095] Aggregate functional test tweak This seems to still be racy, lengthen the timeout to wait for agregate creation. Change-Id: I3601c5baee03745ae21714b9dff0e278ad016877 Signed-off-by: Dean Troyer --- .../tests/functional/compute/v2/test_aggregate.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openstackclient/tests/functional/compute/v2/test_aggregate.py b/openstackclient/tests/functional/compute/v2/test_aggregate.py index 7102675716..590d28cb03 100644 --- a/openstackclient/tests/functional/compute/v2/test_aggregate.py +++ b/openstackclient/tests/functional/compute/v2/test_aggregate.py @@ -54,9 +54,10 @@ def test_aggregate_create_and_delete(self): # Loop a few times since this is timing-sensitive # Just hard-code it for now, since there is no pause and it is - # racy we shouldn't have to wait too long, a minute seems reasonable + # racy we shouldn't have to wait too long, a minute or two + # seems reasonable wait_time = 0 - while wait_time < 60: + while wait_time <= 120: cmd_output = json.loads(self.openstack( 'aggregate show -f json ' + name2 From 1b2595a9594e1a6965e6086aad7782cf3e39bafa Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 16 May 2019 08:24:58 -0500 Subject: [PATCH 2057/3095] Remove code migrated to osc-lib long ago * Remove openstackclient.api.utils and use osc_lib.api.utils * Remove openstackclient.common.clientmanager.ClientManager.auth_ref * Remove openstackclient.common.commandmanager Change-Id: I67e1dbc53cc0b37967c0011bcb2fc09bdef62d94 Signed-off-by: Dean Troyer --- openstackclient/api/utils.py | 84 ------------- openstackclient/common/clientmanager.py | 7 -- openstackclient/common/commandmanager.py | 59 --------- openstackclient/image/v1/image.py | 2 +- openstackclient/image/v2/image.py | 2 +- openstackclient/shell.py | 2 +- openstackclient/tests/unit/api/test_utils.py | 115 ------------------ .../tests/unit/common/test_commandmanager.py | 107 ---------------- .../tests/unit/image/v1/test_image.py | 2 +- .../tests/unit/image/v2/test_image.py | 2 +- 10 files changed, 5 insertions(+), 377 deletions(-) delete mode 100644 openstackclient/api/utils.py delete mode 100644 openstackclient/common/commandmanager.py delete mode 100644 openstackclient/tests/unit/api/test_utils.py delete mode 100644 openstackclient/tests/unit/common/test_commandmanager.py diff --git a/openstackclient/api/utils.py b/openstackclient/api/utils.py deleted file mode 100644 index 6407cd4457..0000000000 --- a/openstackclient/api/utils.py +++ /dev/null @@ -1,84 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - -"""API Utilities Library""" - - -def simple_filter( - data=None, - attr=None, - value=None, - property_field=None, -): - """Filter a list of dicts - - :param list data: - The list to be filtered. The list is modified in-place and will - be changed if any filtering occurs. - :param string attr: - The name of the attribute to filter. If attr does not exist no - match will succeed and no rows will be returned. If attr is - None no filtering will be performed and all rows will be returned. - :param string value: - The value to filter. None is considered to be a 'no filter' value. - '' matches against a Python empty string. - :param string property_field: - The name of the data field containing a property dict to filter. - If property_field is None, attr is a field name. If property_field - is not None, attr is a property key name inside the named property - field. - - :returns: - Returns the filtered list - :rtype list: - - This simple filter (one attribute, one exact-match value) searches a - list of dicts to select items. It first searches the item dict for a - matching ``attr`` then does an exact-match on the ``value``. If - ``property_field`` is given, it will look inside that field (if it - exists and is a dict) for a matching ``value``. - """ - - # Take the do-nothing case shortcut - if not data or not attr or value is None: - return data - - # NOTE:(dtroyer): This filter modifies the provided list in-place using - # list.remove() so we need to start at the end so the loop pointer does - # not skip any items after a deletion. - for d in reversed(data): - if attr in d: - # Searching data fields - search_value = d[attr] - elif (property_field and property_field in d and - isinstance(d[property_field], dict)): - # Searching a properties field - do this separately because - # we don't want to fail over to checking the fields if a - # property name is given. - if attr in d[property_field]: - search_value = d[property_field][attr] - else: - search_value = None - else: - search_value = None - - # could do regex here someday... - if not search_value or search_value != value: - # remove from list - try: - data.remove(d) - except ValueError: - # it's already gone! - pass - - return data diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index aa1045e469..c1118ad3ef 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -91,13 +91,6 @@ def setup_auth(self): return super(ClientManager, self).setup_auth() - @property - def auth_ref(self): - if not self._auth_required: - return None - else: - return super(ClientManager, self).auth_ref - def _fallback_load_auth_plugin(self, e): # NOTES(RuiChen): Hack to avoid auth plugins choking on data they don't # expect, delete fake token and endpoint, then try to diff --git a/openstackclient/common/commandmanager.py b/openstackclient/common/commandmanager.py deleted file mode 100644 index c190e33e0d..0000000000 --- a/openstackclient/common/commandmanager.py +++ /dev/null @@ -1,59 +0,0 @@ -# Copyright 2012-2013 OpenStack Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - -"""Modify cliff.CommandManager""" - -import pkg_resources - -import cliff.commandmanager - - -class CommandManager(cliff.commandmanager.CommandManager): - """Add additional functionality to cliff.CommandManager - - Load additional command groups after initialization - Add _command_group() methods - """ - - def __init__(self, namespace, convert_underscores=True): - self.group_list = [] - super(CommandManager, self).__init__(namespace, convert_underscores) - - def load_commands(self, namespace): - self.group_list.append(namespace) - return super(CommandManager, self).load_commands(namespace) - - def add_command_group(self, group=None): - """Adds another group of command entrypoints""" - if group: - self.load_commands(group) - - def get_command_groups(self): - """Returns a list of the loaded command groups""" - return self.group_list - - def get_command_names(self, group=None): - """Returns a list of commands loaded for the specified group""" - group_list = [] - if group is not None: - for ep in pkg_resources.iter_entry_points(group): - cmd_name = ( - ep.name.replace('_', ' ') - if self.convert_underscores - else ep.name - ) - group_list.append(cmd_name) - return group_list - return list(self.commands.keys()) diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index 7ecaa3efed..64c4049cfd 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -22,12 +22,12 @@ import sys from glanceclient.common import utils as gc_utils +from osc_lib.api import utils as api_utils from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import utils import six -from openstackclient.api import utils as api_utils from openstackclient.i18n import _ if os.name == "nt": diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 3efde80879..bdec99d7be 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -21,13 +21,13 @@ from glanceclient.common import utils as gc_utils from openstack.image import image_signer +from osc_lib.api import utils as api_utils from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils import six -from openstackclient.api import utils as api_utils from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 58a7712026..22d8412cc7 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -20,13 +20,13 @@ import sys from osc_lib.api import auth +from osc_lib.command import commandmanager from osc_lib import shell import six import openstackclient from openstackclient.common import client_config as cloud_config from openstackclient.common import clientmanager -from openstackclient.common import commandmanager DEFAULT_DOMAIN = 'default' diff --git a/openstackclient/tests/unit/api/test_utils.py b/openstackclient/tests/unit/api/test_utils.py deleted file mode 100644 index 1f52855864..0000000000 --- a/openstackclient/tests/unit/api/test_utils.py +++ /dev/null @@ -1,115 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - -"""API Utilities Library Tests""" - -import copy - -from openstackclient.api import api -from openstackclient.api import utils as api_utils -from openstackclient.tests.unit.api import fakes as api_fakes - - -class TestBaseAPIFilter(api_fakes.TestSession): - """The filters can be tested independently""" - - def setUp(self): - super(TestBaseAPIFilter, self).setUp() - self.api = api.BaseAPI( - session=self.sess, - endpoint=self.BASE_URL, - ) - - self.input_list = [ - api_fakes.RESP_ITEM_1, - api_fakes.RESP_ITEM_2, - api_fakes.RESP_ITEM_3, - ] - - def test_simple_filter_none(self): - output = api_utils.simple_filter( - ) - self.assertIsNone(output) - - def test_simple_filter_no_attr(self): - output = api_utils.simple_filter( - copy.deepcopy(self.input_list), - ) - self.assertEqual(self.input_list, output) - - def test_simple_filter_attr_only(self): - output = api_utils.simple_filter( - copy.deepcopy(self.input_list), - attr='status', - ) - self.assertEqual(self.input_list, output) - - def test_simple_filter_attr_value(self): - output = api_utils.simple_filter( - copy.deepcopy(self.input_list), - attr='status', - value='', - ) - self.assertEqual([], output) - - output = api_utils.simple_filter( - copy.deepcopy(self.input_list), - attr='status', - value='UP', - ) - self.assertEqual( - [api_fakes.RESP_ITEM_1, api_fakes.RESP_ITEM_3], - output, - ) - - output = api_utils.simple_filter( - copy.deepcopy(self.input_list), - attr='fred', - value='UP', - ) - self.assertEqual([], output) - - def test_simple_filter_prop_attr_only(self): - output = api_utils.simple_filter( - copy.deepcopy(self.input_list), - attr='b', - property_field='props', - ) - self.assertEqual(self.input_list, output) - - output = api_utils.simple_filter( - copy.deepcopy(self.input_list), - attr='status', - property_field='props', - ) - self.assertEqual(self.input_list, output) - - def test_simple_filter_prop_attr_value(self): - output = api_utils.simple_filter( - copy.deepcopy(self.input_list), - attr='b', - value=2, - property_field='props', - ) - self.assertEqual( - [api_fakes.RESP_ITEM_1, api_fakes.RESP_ITEM_2], - output, - ) - - output = api_utils.simple_filter( - copy.deepcopy(self.input_list), - attr='b', - value=9, - property_field='props', - ) - self.assertEqual([], output) diff --git a/openstackclient/tests/unit/common/test_commandmanager.py b/openstackclient/tests/unit/common/test_commandmanager.py deleted file mode 100644 index 0c6c99c05d..0000000000 --- a/openstackclient/tests/unit/common/test_commandmanager.py +++ /dev/null @@ -1,107 +0,0 @@ -# Copyright 2012-2013 OpenStack Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - -import mock - -from openstackclient.common import commandmanager -from openstackclient.tests.unit import utils - - -class FakeCommand(object): - - @classmethod - def load(cls): - return cls - - def __init__(self): - return - -FAKE_CMD_ONE = FakeCommand -FAKE_CMD_TWO = FakeCommand -FAKE_CMD_ALPHA = FakeCommand -FAKE_CMD_BETA = FakeCommand - - -class FakeCommandManager(commandmanager.CommandManager): - commands = {} - - def load_commands(self, namespace): - if namespace == 'test': - self.commands['one'] = FAKE_CMD_ONE - self.commands['two'] = FAKE_CMD_TWO - self.group_list.append(namespace) - elif namespace == 'greek': - self.commands['alpha'] = FAKE_CMD_ALPHA - self.commands['beta'] = FAKE_CMD_BETA - self.group_list.append(namespace) - - -class TestCommandManager(utils.TestCase): - - def test_add_command_group(self): - mgr = FakeCommandManager('test') - - # Make sure add_command() still functions - mock_cmd_one = mock.Mock() - mgr.add_command('mock', mock_cmd_one) - cmd_mock, name, args = mgr.find_command(['mock']) - self.assertEqual(mock_cmd_one, cmd_mock) - - # Find a command added in initialization - cmd_one, name, args = mgr.find_command(['one']) - self.assertEqual(FAKE_CMD_ONE, cmd_one) - - # Load another command group - mgr.add_command_group('greek') - - # Find a new command - cmd_alpha, name, args = mgr.find_command(['alpha']) - self.assertEqual(FAKE_CMD_ALPHA, cmd_alpha) - - # Ensure that the original commands were not overwritten - cmd_two, name, args = mgr.find_command(['two']) - self.assertEqual(FAKE_CMD_TWO, cmd_two) - - def test_get_command_groups(self): - mgr = FakeCommandManager('test') - - # Make sure add_command() still functions - mock_cmd_one = mock.Mock() - mgr.add_command('mock', mock_cmd_one) - cmd_mock, name, args = mgr.find_command(['mock']) - self.assertEqual(mock_cmd_one, cmd_mock) - - # Load another command group - mgr.add_command_group('greek') - - gl = mgr.get_command_groups() - self.assertEqual(['test', 'greek'], gl) - - def test_get_command_names(self): - mock_cmd_one = mock.Mock() - mock_cmd_one.name = 'one' - mock_cmd_two = mock.Mock() - mock_cmd_two.name = 'cmd two' - mock_pkg_resources = mock.Mock( - return_value=[mock_cmd_one, mock_cmd_two], - ) - with mock.patch( - 'pkg_resources.iter_entry_points', - mock_pkg_resources, - ) as iter_entry_points: - mgr = commandmanager.CommandManager('test') - iter_entry_points.assert_called_once_with('test') - cmds = mgr.get_command_names('test') - self.assertEqual(['one', 'cmd two'], cmds) diff --git a/openstackclient/tests/unit/image/v1/test_image.py b/openstackclient/tests/unit/image/v1/test_image.py index ae578d9138..6babd4ffca 100644 --- a/openstackclient/tests/unit/image/v1/test_image.py +++ b/openstackclient/tests/unit/image/v1/test_image.py @@ -417,7 +417,7 @@ def test_image_list_long_option(self): ), ) self.assertEqual(datalist, tuple(data)) - @mock.patch('openstackclient.api.utils.simple_filter') + @mock.patch('osc_lib.api.utils.simple_filter') def test_image_list_property_option(self, sf_mock): sf_mock.side_effect = [ [self.image_info], [], diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py index 16a393df7d..4cc8f44827 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -734,7 +734,7 @@ def test_image_list_long_option(self): ), ) self.assertEqual(datalist, tuple(data)) - @mock.patch('openstackclient.api.utils.simple_filter') + @mock.patch('osc_lib.api.utils.simple_filter') def test_image_list_property_option(self, sf_mock): sf_mock.return_value = [copy.deepcopy(self._image)] From c77a9621beaf7748e5ccfecb3ac37fa9602c5bfe Mon Sep 17 00:00:00 2001 From: Chen Date: Tue, 15 May 2018 15:25:40 +0800 Subject: [PATCH 2058/3095] Compute: Add description support for server This patch adds functionality to configure server's description with: 1 server create 2 server set 3 server unset 4 server rebuild Change-Id: Ic06d97b29e51828b29d7ac5172645c288e4ada9e Story: 2002005 Task: 19640 --- doc/source/cli/data/nova.csv | 3 +- openstackclient/compute/v2/server.py | 57 +++++ .../tests/unit/compute/v2/test_server.py | 223 ++++++++++++++++++ .../server-description-ae9618fc09544cac.yaml | 6 + 4 files changed, 288 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/server-description-ae9618fc09544cac.yaml diff --git a/doc/source/cli/data/nova.csv b/doc/source/cli/data/nova.csv index fe2aa362fe..872d6e09b2 100644 --- a/doc/source/cli/data/nova.csv +++ b/doc/source/cli/data/nova.csv @@ -125,7 +125,8 @@ unlock,server unlock,Unlock a server. unpause,server unpause,Unpause a server. unrescue,server unrescue,Restart the server from normal boot disk again. unshelve,server unshelve,Unshelve a server. -update,server set / unset,Update the name or the description for a server. +update,server set / unset --description,Update or unset the description for a server. +update,server set --name,Update the name for a server. usage,usage show,Show usage data for a single tenant. usage-list,usage list,List usage data for all tenants. version-list,,List all API versions. diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 4f67242862..4bf3b90a5d 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -502,6 +502,12 @@ def get_parser(self, prog_name): metavar='', help=_('User data file to serve from the metadata server'), ) + parser.add_argument( + '--description', + metavar='', + help=_('Set description for the server (supported by ' + '--os-compute-api-version 2.19 or above)'), + ) parser.add_argument( '--availability-zone', metavar='', @@ -712,6 +718,12 @@ def _match_image(image_api, wanted_properties): "exception": e} ) + if parsed_args.description: + if compute_client.api_version < api_versions.APIVersion("2.19"): + msg = _("Description is not supported for " + "--os-compute-api-version less than 2.19") + raise exceptions.CommandError(msg) + block_device_mapping_v2 = [] if volume: block_device_mapping_v2 = [{'uuid': volume, @@ -872,6 +884,9 @@ def _match_image(image_api, wanted_properties): scheduler_hints=hints, config_drive=config_drive) + if parsed_args.description: + boot_kwargs['description'] = parsed_args.description + LOG.debug('boot_args: %s', boot_args) LOG.debug('boot_kwargs: %s', boot_kwargs) @@ -1529,6 +1544,12 @@ def get_parser(self, prog_name): help=_('Set a property on the rebuilt instance ' '(repeat option to set multiple values)'), ) + parser.add_argument( + '--description', + metavar='', + help=_('New description for the server (supported by ' + '--os-compute-api-version 2.19 or above'), + ) parser.add_argument( '--wait', action='store_true', @@ -1557,6 +1578,12 @@ def _show_progress(progress): kwargs = {} if parsed_args.property: kwargs['meta'] = parsed_args.property + if parsed_args.description: + if server.api_version < api_versions.APIVersion("2.19"): + msg = _("Description is not supported for " + "--os-compute-api-version less than 2.19") + raise exceptions.CommandError(msg) + kwargs['description'] = parsed_args.description server = server.rebuild(image, parsed_args.password, **kwargs) if parsed_args.wait: @@ -1968,6 +1995,12 @@ def get_parser(self, prog_name): choices=['active', 'error'], help=_('New server state (valid value: active, error)'), ) + parser.add_argument( + '--description', + metavar='', + help=_('New server description (supported by ' + '--os-compute-api-version 2.19 or above)'), + ) return parser def take_action(self, parsed_args): @@ -1999,6 +2032,13 @@ def take_action(self, parsed_args): msg = _("Passwords do not match, password unchanged") raise exceptions.CommandError(msg) + if parsed_args.description: + if server.api_version < api_versions.APIVersion("2.19"): + msg = _("Description is not supported for " + "--os-compute-api-version less than 2.19") + raise exceptions.CommandError(msg) + server.update(description=parsed_args.description) + class ShelveServer(command.Command): _description = _("Shelve server(s)") @@ -2358,6 +2398,13 @@ def get_parser(self, prog_name): help=_('Property key to remove from server ' '(repeat option to remove multiple values)'), ) + parser.add_argument( + '--description', + dest='description', + action='store_true', + help=_('Unset server description (supported by ' + '--os-compute-api-version 2.19 or above)'), + ) return parser def take_action(self, parsed_args): @@ -2373,6 +2420,16 @@ def take_action(self, parsed_args): parsed_args.property, ) + if parsed_args.description: + if compute_client.api_version < api_versions.APIVersion("2.19"): + msg = _("Description is not supported for " + "--os-compute-api-version less than 2.19") + raise exceptions.CommandError(msg) + compute_client.servers.update( + server, + description="", + ) + class UnshelveServer(command.Command): _description = _("Unshelve server(s)") diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 44e433d89d..b8fa5e7c78 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -1736,6 +1736,90 @@ def test_server_create_image_property_missed(self): self.cmd.take_action, parsed_args) + def test_server_create_with_description_api_newer(self): + + # Description is supported for nova api version 2.19 or above + self.app.client_manager.compute.api_version = 2.19 + + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--description', 'description1', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('description', 'description1'), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + with mock.patch.object(api_versions, + 'APIVersion', + return_value=2.19): + # 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) + + # Set expected values + kwargs = dict( + meta=None, + files={}, + reservation_id=None, + min_count=1, + max_count=1, + security_groups=[], + userdata=None, + key_name=None, + availability_zone=None, + block_device_mapping_v2=[], + nics='auto', + scheduler_hints={}, + config_drive=None, + description='description1', + ) + # ServerManager.create(name, image, flavor, **kwargs) + self.servers_mock.create.assert_called_with( + self.new_server.name, + self.image, + self.flavor, + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) + self.assertFalse(self.images_mock.called) + self.assertFalse(self.flavors_mock.called) + + def test_server_create_with_description_api_older(self): + + # Description is not supported for nova api version below 2.19 + self.app.client_manager.compute.api_version = 2.18 + + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--description', 'description1', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('description', 'description1'), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + with mock.patch.object(api_versions, + 'APIVersion', + return_value=2.19): + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + class TestServerDelete(TestServer): @@ -2493,6 +2577,55 @@ def test_rebuild_with_current_image_and_password(self): self.images_mock.get.assert_called_with(self.image.id) self.server.rebuild.assert_called_with(self.image, password) + def test_rebuild_with_description_api_older(self): + + # Description is not supported for nova api version below 2.19 + self.server.api_version = 2.18 + + description = 'description1' + arglist = [ + self.server.id, + '--description', description + ] + verifylist = [ + ('server', self.server.id), + ('description', description) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + with mock.patch.object(api_versions, + 'APIVersion', + return_value=2.19): + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + + def test_rebuild_with_description_api_newer(self): + + # Description is supported for nova api version 2.19 or above + self.server.api_version = 2.19 + + description = 'description1' + arglist = [ + self.server.id, + '--description', description + ] + verifylist = [ + ('server', self.server.id), + ('description', description) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + with mock.patch.object(api_versions, + 'APIVersion', + return_value=2.19): + # Get the command object to test + self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.images_mock.get.assert_called_with(self.image.id) + self.server.rebuild.assert_called_with(self.image, None, + description=description) + @mock.patch.object(common_utils, 'wait_for_status', return_value=True) def test_rebuild_with_wait_ok(self, mock_wait_for_status): arglist = [ @@ -3140,6 +3273,10 @@ class TestServerSet(TestServer): def setUp(self): super(TestServerSet, self).setUp() + self.attrs = { + 'api_version': None, + } + self.methods = { 'update': None, 'reset_state': None, @@ -3242,6 +3379,48 @@ def test_server_set_with_root_password(self, mock_getpass): mock.sentinel.fake_pass) self.assertIsNone(result) + def test_server_set_with_description_api_newer(self): + + # Description is supported for nova api version 2.19 or above + self.fake_servers[0].api_version = 2.19 + + arglist = [ + '--description', 'foo_description', + 'foo_vm', + ] + verifylist = [ + ('description', 'foo_description'), + ('server', 'foo_vm'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + with mock.patch.object(api_versions, + 'APIVersion', + return_value=2.19): + result = self.cmd.take_action(parsed_args) + self.fake_servers[0].update.assert_called_once_with( + description='foo_description') + self.assertIsNone(result) + + def test_server_set_with_description_api_older(self): + + # Description is not supported for nova api version below 2.19 + self.fake_servers[0].api_version = 2.18 + + arglist = [ + '--description', 'foo_description', + 'foo_vm', + ] + verifylist = [ + ('description', 'foo_description'), + ('server', 'foo_vm'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + with mock.patch.object(api_versions, + 'APIVersion', + return_value=2.19): + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + class TestServerShelve(TestServer): @@ -3523,6 +3702,50 @@ def test_server_unset_with_property(self): self.fake_server, ['key1', 'key2']) self.assertIsNone(result) + def test_server_unset_with_description_api_newer(self): + + # Description is supported for nova api version 2.19 or above + self.app.client_manager.compute.api_version = 2.19 + + arglist = [ + '--description', + 'foo_vm', + ] + verifylist = [ + ('description', True), + ('server', 'foo_vm'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + with mock.patch.object(api_versions, + 'APIVersion', + return_value=2.19): + result = self.cmd.take_action(parsed_args) + self.servers_mock.update.assert_called_once_with( + self.fake_server, description="") + self.assertIsNone(result) + + def test_server_unset_with_description_api_older(self): + + # Description is not supported for nova api version below 2.19 + self.app.client_manager.compute.api_version = 2.18 + + arglist = [ + '--description', + 'foo_vm', + ] + verifylist = [ + ('description', True), + ('server', 'foo_vm'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + with mock.patch.object(api_versions, + 'APIVersion', + return_value=2.19): + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + class TestServerUnshelve(TestServer): diff --git a/releasenotes/notes/server-description-ae9618fc09544cac.yaml b/releasenotes/notes/server-description-ae9618fc09544cac.yaml new file mode 100644 index 0000000000..469f18454b --- /dev/null +++ b/releasenotes/notes/server-description-ae9618fc09544cac.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--description`` option to ``server create``, ``server rebuild``, + ``server set`` and ``server unset`` commands. + [Bug `2002005 `_] From 99c3be93c852af0c8f701d8d65c54310f53b30d2 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 17 May 2019 16:40:12 -0500 Subject: [PATCH 2059/3095] Serialize more aggregate functional tests These tests are showing signs of problems running in parallel so serialse the create/delete/list/set/unset tests. They all used two aggregates each anyway... Change-Id: Iba4b52c179e6914eaeefea1da0f7eaefcdcf1f87 Signed-off-by: Dean Troyer --- openstackclient/compute/v2/aggregate.py | 2 + .../functional/compute/v2/test_aggregate.py | 148 ++++++------------ 2 files changed, 52 insertions(+), 98 deletions(-) diff --git a/openstackclient/compute/v2/aggregate.py b/openstackclient/compute/v2/aggregate.py index 7f9161a996..fa64647899 100644 --- a/openstackclient/compute/v2/aggregate.py +++ b/openstackclient/compute/v2/aggregate.py @@ -101,6 +101,8 @@ def take_action(self, parsed_args): parsed_args.property, )._info) + # TODO(dtroyer): re-format metadata field to properites as + # in the set command return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/tests/functional/compute/v2/test_aggregate.py b/openstackclient/tests/functional/compute/v2/test_aggregate.py index 590d28cb03..c591dd61fd 100644 --- a/openstackclient/tests/functional/compute/v2/test_aggregate.py +++ b/openstackclient/tests/functional/compute/v2/test_aggregate.py @@ -11,7 +11,6 @@ # under the License. import json -import time import uuid from openstackclient.tests.functional import base @@ -20,12 +19,13 @@ class AggregateTests(base.TestCase): """Functional tests for aggregate""" - def test_aggregate_create_and_delete(self): + def test_aggregate_crud(self): """Test create, delete multiple""" name1 = uuid.uuid4().hex cmd_output = json.loads(self.openstack( 'aggregate create -f json ' + '--zone nova ' + + '--property a=b ' + name1 )) self.assertEqual( @@ -36,11 +36,17 @@ def test_aggregate_create_and_delete(self): 'nova', cmd_output['availability_zone'] ) + # TODO(dtroyer): enable the following once the properties field + # is correctly formatted in the create output + # self.assertIn( + # "a='b'", + # cmd_output['properties'] + # ) name2 = uuid.uuid4().hex cmd_output = json.loads(self.openstack( 'aggregate create -f json ' + - '--zone nova ' + + '--zone external ' + name2 )) self.assertEqual( @@ -48,102 +54,15 @@ def test_aggregate_create_and_delete(self): cmd_output['name'] ) self.assertEqual( - 'nova', + 'external', cmd_output['availability_zone'] ) - # Loop a few times since this is timing-sensitive - # Just hard-code it for now, since there is no pause and it is - # racy we shouldn't have to wait too long, a minute or two - # seems reasonable - wait_time = 0 - while wait_time <= 120: - cmd_output = json.loads(self.openstack( - 'aggregate show -f json ' + - name2 - )) - if cmd_output['name'] != name2: - # Hang out for a bit and try again - print('retrying aggregate check') - wait_time += 10 - time.sleep(10) - else: - break - - del_output = self.openstack( - 'aggregate delete ' + - name1 + ' ' + - name2 - ) - self.assertOutput('', del_output) - - def test_aggregate_list(self): - """Test aggregate list""" - name1 = uuid.uuid4().hex - self.openstack( - 'aggregate create ' + - '--zone nova ' + - '--property a=b ' + - name1 - ) - self.addCleanup( - self.openstack, - 'aggregate delete ' + name1, - fail_ok=True, - ) - - name2 = uuid.uuid4().hex - self.openstack( - 'aggregate create ' + - '--zone internal ' + - '--property c=d ' + - name2 - ) - self.addCleanup( - self.openstack, - 'aggregate delete ' + name2, - fail_ok=True, - ) - - cmd_output = json.loads(self.openstack( - 'aggregate list -f json' - )) - names = [x['Name'] for x in cmd_output] - self.assertIn(name1, names) - self.assertIn(name2, names) - zones = [x['Availability Zone'] for x in cmd_output] - self.assertIn('nova', zones) - self.assertIn('internal', zones) - - # Test aggregate list --long - cmd_output = json.loads(self.openstack( - 'aggregate list --long -f json' - )) - names = [x['Name'] for x in cmd_output] - self.assertIn(name1, names) - self.assertIn(name2, names) - zones = [x['Availability Zone'] for x in cmd_output] - self.assertIn('nova', zones) - self.assertIn('internal', zones) - properties = [x['Properties'] for x in cmd_output] - self.assertIn({'a': 'b'}, properties) - self.assertIn({'c': 'd'}, properties) - - def test_aggregate_set_and_unset(self): - """Test aggregate set, show and unset""" - name1 = uuid.uuid4().hex - name2 = uuid.uuid4().hex - self.openstack( - 'aggregate create ' + - '--zone nova ' + - '--property a=b ' + - name1 - ) - self.addCleanup(self.openstack, 'aggregate delete ' + name2) - + # Test aggregate set + name3 = uuid.uuid4().hex raw_output = self.openstack( 'aggregate set ' + - '--name ' + name2 + ' ' + + '--name ' + name3 + ' ' + '--zone internal ' + '--no-property ' + '--property c=d ' + @@ -153,10 +72,10 @@ def test_aggregate_set_and_unset(self): cmd_output = json.loads(self.openstack( 'aggregate show -f json ' + - name2 + name3 )) self.assertEqual( - name2, + name3, cmd_output['name'] ) self.assertEqual( @@ -172,23 +91,56 @@ def test_aggregate_set_and_unset(self): cmd_output['properties'] ) + # Test aggregate list + cmd_output = json.loads(self.openstack( + 'aggregate list -f json' + )) + names = [x['Name'] for x in cmd_output] + self.assertIn(name3, names) + self.assertIn(name2, names) + zones = [x['Availability Zone'] for x in cmd_output] + self.assertIn('external', zones) + self.assertIn('internal', zones) + + # Test aggregate list --long + cmd_output = json.loads(self.openstack( + 'aggregate list --long -f json' + )) + names = [x['Name'] for x in cmd_output] + self.assertIn(name3, names) + self.assertIn(name2, names) + zones = [x['Availability Zone'] for x in cmd_output] + self.assertIn('external', zones) + self.assertIn('internal', zones) + properties = [x['Properties'] for x in cmd_output] + self.assertNotIn({'a': 'b'}, properties) + self.assertIn({'c': 'd'}, properties) + # Test unset raw_output = self.openstack( 'aggregate unset ' + '--property c ' + - name2 + name3 ) self.assertOutput('', raw_output) cmd_output = json.loads(self.openstack( 'aggregate show -f json ' + - name2 + name3 )) self.assertNotIn( "c='d'", cmd_output['properties'] ) + # test aggregate delete + del_output = self.openstack( + 'aggregate delete ' + + name3 + ' ' + + name2 + ) + self.assertOutput('', del_output) + def test_aggregate_add_and_remove_host(self): """Test aggregate add and remove host""" # Get a host From b7742b59371b9e0b2e417547eb2cdaa80954b50d Mon Sep 17 00:00:00 2001 From: Sean McGinnis Date: Tue, 23 Oct 2018 14:02:49 -0500 Subject: [PATCH 2060/3095] Remove deprecated compute commands The following were deprecated over two years ago and can now be removed: * ``ip fixed add|remove`` in favor of ``server add|remove fixed ip`` * ``ip floating add|remove`` in favor of ``server add|remove floating ip`` These are backwards incompatible changes and will require a major version bump after they are merged. Change-Id: I10c4d32a3c0b55ad41a02afd3b14249bafcb55a9 Signed-off-by: Sean McGinnis Signed-off-by: Dean Troyer --- doc/source/cli/backwards-incompatible.rst | 17 ++- .../cli/command-objects/floating-ip-pool.rst | 2 +- .../cli/command-objects/floating-ip.rst | 2 +- doc/source/cli/command-objects/ip-fixed.rst | 47 -------- doc/source/cli/commands.rst | 9 +- openstackclient/compute/v2/fixedip.py | 100 ------------------ openstackclient/compute/v2/floatingip.py | 97 ----------------- .../notes/osc4-compute-09246008eff260cb.yaml | 8 ++ setup.cfg | 6 -- 9 files changed, 27 insertions(+), 261 deletions(-) delete mode 100644 doc/source/cli/command-objects/ip-fixed.rst delete mode 100644 openstackclient/compute/v2/fixedip.py delete mode 100644 openstackclient/compute/v2/floatingip.py create mode 100644 releasenotes/notes/osc4-compute-09246008eff260cb.yaml diff --git a/doc/source/cli/backwards-incompatible.rst b/doc/source/cli/backwards-incompatible.rst index fcb68684bf..a86ce35977 100644 --- a/doc/source/cli/backwards-incompatible.rst +++ b/doc/source/cli/backwards-incompatible.rst @@ -16,9 +16,20 @@ from this backwards incompatible change handling. Backwards Incompatible Changes ============================== -.. Carry this section as comments until 4.0 release -.. Release 4.0 -.. ----------- +Release 4.0 +----------- + +1. Remove ``ip fixed add|remove`` commands. + Use ``server add|remove fixed ip`` commands instead. + + * Removed in: 4.0 + * Commit: https://review.opendev.org/612781 + +2. Remove ``ip floating add|remove`` commands. + Use ``server add|remove floating ip`` commands instead. + + * Removed in: 4.0 + * Commit: https://review.opendev.org/612781 .. 1. Change ``volume transfer request accept`` to use new option ``--auth-key`` .. rather than a second positional argument. diff --git a/doc/source/cli/command-objects/floating-ip-pool.rst b/doc/source/cli/command-objects/floating-ip-pool.rst index 9213b86dd0..5c8f3aa13d 100644 --- a/doc/source/cli/command-objects/floating-ip-pool.rst +++ b/doc/source/cli/command-objects/floating-ip-pool.rst @@ -2,7 +2,7 @@ floating ip pool ================ -Compute v2, Network v2 +Network v2 floating ip pool list --------------------- diff --git a/doc/source/cli/command-objects/floating-ip.rst b/doc/source/cli/command-objects/floating-ip.rst index 749c32d6b8..d122ccbea3 100644 --- a/doc/source/cli/command-objects/floating-ip.rst +++ b/doc/source/cli/command-objects/floating-ip.rst @@ -2,7 +2,7 @@ floating ip =========== -Compute v2, Network v2 +Network v2 floating ip create ------------------ diff --git a/doc/source/cli/command-objects/ip-fixed.rst b/doc/source/cli/command-objects/ip-fixed.rst deleted file mode 100644 index f5b11dc61b..0000000000 --- a/doc/source/cli/command-objects/ip-fixed.rst +++ /dev/null @@ -1,47 +0,0 @@ -======== -ip fixed -======== - -Compute v2 - -ip fixed add ------------- - -Add fixed IP address to server -(Deprecated, please use ``server add fixed ip`` instead) - -.. program:: ip fixed add -.. code:: bash - - openstack ip fixed add - - - -.. describe:: - - Network to fetch an IP address from (name or ID) - -.. describe:: - - Server to receive the IP address (name or ID) - -ip fixed remove ---------------- - -Remove fixed IP address from server -(Deprecated, please use ``server remove fixed ip`` instead) - -.. program:: ip fixed remove -.. code:: bash - - openstack ip fixed remove - - - -.. describe:: - - IP address to remove from server (name only) - -.. describe:: - - Server to remove the IP address from (name or ID) diff --git a/doc/source/cli/commands.rst b/doc/source/cli/commands.rst index e302fdad05..7c38aa5b5c 100644 --- a/doc/source/cli/commands.rst +++ b/doc/source/cli/commands.rst @@ -95,9 +95,9 @@ referring to both Compute and Volume quotas. * ``extension``: (**Compute**, **Identity**, **Network**, **Volume**) OpenStack server API extensions * ``federation protocol``: (**Identity**) the underlying protocol used while federating identities * ``flavor``: (**Compute**) predefined server configurations: ram, root disk and so on -* ``fixed ip``: (**Compute**, **Network**) - an internal IP address assigned to a server -* ``floating ip``: (**Compute**, **Network**) - a public IP address that can be mapped to a server -* ``floating ip pool``: (**Compute**, **Network**) - a pool of public IP addresses +* ``fixed ip``: (**Compute**) - an internal IP address assigned to a server +* ``floating ip``: (**Network**) - a public IP address that can be mapped to a server +* ``floating ip pool``: (**Network**) - a pool of public IP addresses * ``group``: (**Identity**) a grouping of users * ``host``: (**Compute**) - the physical computer running compute services * ``hypervisor``: (**Compute**) the virtual machine manager @@ -106,9 +106,6 @@ referring to both Compute and Volume quotas. * ``image``: (**Image**) a disk image * ``image member``: (**Image**) a project that is a member of an Image * ``ip availability``: (**Network**) - details of IP usage of a network -* ``ip fixed``: (**Compute**, **Network**) - an internal IP address assigned to a server -* ``ip floating``: (**Compute**, **Network**) - a public IP address that can be mapped to a server -* ``ip floating pool``: (**Compute**, **Network**) - a pool of public IP addresses * ``keypair``: (**Compute**) an SSH public key * ``limits``: (**Compute**, **Volume**) resource usage limits * ``mapping``: (**Identity**) a definition to translate identity provider attributes to Identity concepts diff --git a/openstackclient/compute/v2/fixedip.py b/openstackclient/compute/v2/fixedip.py deleted file mode 100644 index 0c0b619e8e..0000000000 --- a/openstackclient/compute/v2/fixedip.py +++ /dev/null @@ -1,100 +0,0 @@ -# Copyright 2013 OpenStack Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - -"""Fixed IP action implementations""" - -import logging - -from osc_lib.command import command -from osc_lib import utils - -from openstackclient.i18n import _ - - -class AddFixedIP(command.Command): - _description = _("Add fixed IP address to server") - - # TODO(tangchen): Remove this class and ``ip fixed add`` command - # two cycles after Mitaka. - - # This notifies cliff to not display the help for this command - deprecated = True - - log = logging.getLogger('deprecated') - - def get_parser(self, prog_name): - parser = super(AddFixedIP, self).get_parser(prog_name) - parser.add_argument( - "network", - metavar="", - help=_("Network to fetch an IP address from (name or ID)"), - ) - parser.add_argument( - "server", - metavar="", - help=_("Server to receive the IP address (name or ID)"), - ) - return parser - - def take_action(self, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "server add fixed ip" instead.')) - - compute_client = self.app.client_manager.compute - - network = utils.find_resource( - compute_client.networks, parsed_args.network) - - server = utils.find_resource( - compute_client.servers, parsed_args.server) - - server.add_fixed_ip(network.id) - - -class RemoveFixedIP(command.Command): - _description = _("Remove fixed IP address from server") - - # TODO(tangchen): Remove this class and ``ip fixed remove`` command - # two cycles after Mitaka. - - # This notifies cliff to not display the help for this command - deprecated = True - - log = logging.getLogger('deprecated') - - def get_parser(self, prog_name): - parser = super(RemoveFixedIP, self).get_parser(prog_name) - parser.add_argument( - "ip_address", - metavar="", - help=_("IP address to remove from server (name only)"), - ) - parser.add_argument( - "server", - metavar="", - help=_("Server to remove the IP address from (name or ID)"), - ) - return parser - - def take_action(self, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "server remove fixed ip" instead.')) - - compute_client = self.app.client_manager.compute - - server = utils.find_resource( - compute_client.servers, parsed_args.server) - - server.remove_fixed_ip(parsed_args.ip_address) diff --git a/openstackclient/compute/v2/floatingip.py b/openstackclient/compute/v2/floatingip.py deleted file mode 100644 index 69595bed2f..0000000000 --- a/openstackclient/compute/v2/floatingip.py +++ /dev/null @@ -1,97 +0,0 @@ -# Copyright 2013 OpenStack Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - -"""Floating IP action implementations""" - -import logging - -from osc_lib.command import command -from osc_lib import utils - -from openstackclient.i18n import _ - - -class AddFloatingIP(command.Command): - _description = _("Add floating IP address to server") - - # TODO(tangchen): Remove this class and ``ip floating add`` command - # two cycles after Mitaka. - - # This notifies cliff to not display the help for this command - deprecated = True - - log = logging.getLogger('deprecated') - - def get_parser(self, prog_name): - parser = super(AddFloatingIP, self).get_parser(prog_name) - parser.add_argument( - "ip_address", - metavar="", - help=_("IP address to add to server (name only)"), - ) - parser.add_argument( - "server", - metavar="", - help=_("Server to receive the IP address (name or ID)"), - ) - return parser - - def take_action(self, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "server add floating ip" instead.')) - - compute_client = self.app.client_manager.compute - - server = utils.find_resource( - compute_client.servers, parsed_args.server) - - server.add_floating_ip(parsed_args.ip_address) - - -class RemoveFloatingIP(command.Command): - _description = _("Remove floating IP address from server") - - # TODO(tangchen): Remove this class and ``ip floating remove`` command - # two cycles after Mitaka. - - # This notifies cliff to not display the help for this command - deprecated = True - - log = logging.getLogger('deprecated') - - def get_parser(self, prog_name): - parser = super(RemoveFloatingIP, self).get_parser(prog_name) - parser.add_argument( - "ip_address", - metavar="", - help=_("IP address to remove from server (name only)"), - ) - parser.add_argument( - "server", - metavar="", - help=_("Server to remove the IP address from (name or ID)"), - ) - return parser - - def take_action(self, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "server remove floating ip" instead.')) - - compute_client = self.app.client_manager.compute - - server = utils.find_resource( - compute_client.servers, parsed_args.server) - - server.remove_floating_ip(parsed_args.ip_address) diff --git a/releasenotes/notes/osc4-compute-09246008eff260cb.yaml b/releasenotes/notes/osc4-compute-09246008eff260cb.yaml new file mode 100644 index 0000000000..ab7baa67a0 --- /dev/null +++ b/releasenotes/notes/osc4-compute-09246008eff260cb.yaml @@ -0,0 +1,8 @@ +--- +upgrade: + - | + Remove deprecated ``ip fixed add|remove`` commands. + Use ``server add|remove fixed ip`` commands instead. + - | + Remove deprecated ``ip floating add|remove`` commands. + Use ``server add|remove floating ip`` commands instead. diff --git a/setup.cfg b/setup.cfg index db03c48423..031776a99d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -91,12 +91,6 @@ openstack.compute.v2 = hypervisor_stats_show = openstackclient.compute.v2.hypervisor_stats:ShowHypervisorStats - ip_fixed_add = openstackclient.compute.v2.fixedip:AddFixedIP - ip_fixed_remove = openstackclient.compute.v2.fixedip:RemoveFixedIP - - ip_floating_add = openstackclient.compute.v2.floatingip:AddFloatingIP - ip_floating_remove = openstackclient.compute.v2.floatingip:RemoveFloatingIP - keypair_create = openstackclient.compute.v2.keypair:CreateKeypair keypair_delete = openstackclient.compute.v2.keypair:DeleteKeypair keypair_list = openstackclient.compute.v2.keypair:ListKeypair From f9fdc296bc6f2c1f7d6196352b754a7617c4f2d2 Mon Sep 17 00:00:00 2001 From: Sean McGinnis Date: Tue, 23 Oct 2018 15:37:38 -0500 Subject: [PATCH 2061/3095] Remove deprecated identity commands and args The following were deprecated over two years ago and can now be removed/changed: * Remove ``service create`` option ``--type`` * Remove ``role list`` options ``--project`` and ``--user`` * Remove ``user role list`` command These are backwards incompatible changes and will require a major version bump after they are merged. Change-Id: I29e2fc9516dffbfd83eef0bc91e834dde99b4105 Signed-off-by: Sean McGinnis Signed-off-by: Dean Troyer --- doc/source/cli/backwards-incompatible.rst | 18 ++ doc/source/cli/command-objects/role.rst | 58 +---- doc/source/cli/command-objects/user-role.rst | 27 -- openstackclient/identity/v2_0/role.py | 148 +---------- openstackclient/identity/v2_0/service.py | 30 +-- openstackclient/identity/v3/role.py | 125 +--------- .../tests/functional/identity/v2/test_role.py | 32 --- .../tests/functional/identity/v3/test_role.py | 41 ---- .../tests/unit/identity/v2_0/test_role.py | 133 +--------- .../tests/unit/identity/v2_0/test_service.py | 45 +--- .../tests/unit/identity/v3/test_role.py | 232 ------------------ .../notes/osc4-identity-6564257c67d43106.yaml | 12 + setup.cfg | 2 - 13 files changed, 56 insertions(+), 847 deletions(-) delete mode 100644 doc/source/cli/command-objects/user-role.rst create mode 100644 releasenotes/notes/osc4-identity-6564257c67d43106.yaml diff --git a/doc/source/cli/backwards-incompatible.rst b/doc/source/cli/backwards-incompatible.rst index a86ce35977..51743cb2b2 100644 --- a/doc/source/cli/backwards-incompatible.rst +++ b/doc/source/cli/backwards-incompatible.rst @@ -31,6 +31,24 @@ Release 4.0 * Removed in: 4.0 * Commit: https://review.opendev.org/612781 +3. Remove ``service create`` option ``--type``. Service type is + a positional argument. + + * Removed in: 4.0 + * Commit: https://review.opendev.org/612798 + +4. Remove ``role list`` options ``--project`` and ``--user``. + Use ``role assignment list`` options ``--project`` and ``--user`` instead. + + * Removed in: 4.0 + * Commit: https://review.opendev.org/612798 + +5. Remove ``user role list`` command. + Use ``role assignment list`` options ``--project`` and ``--user`` instead. + + * Removed in: 4.0 + * Commit: https://review.opendev.org/612798 + .. 1. Change ``volume transfer request accept`` to use new option ``--auth-key`` .. rather than a second positional argument. diff --git a/doc/source/cli/command-objects/role.rst b/doc/source/cli/command-objects/role.rst index 9819fd1231..e2a2b457a0 100644 --- a/doc/source/cli/command-objects/role.rst +++ b/doc/source/cli/command-objects/role.rst @@ -146,68 +146,12 @@ List roles .. code:: bash openstack role list - --domain | --project [--project-domain ] - --user [--user-domain ] | --group [--group-domain ] - --inherited + [--domain ] .. option:: --domain Filter roles by (name or ID) - (Deprecated if being used to list assignments in conjunction with the - ``--user ``, option, please use ``role assignment list`` instead) - -.. option:: --project - - Filter roles by (name or ID) - - (Deprecated, please use ``role assignment list`` instead) - -.. option:: --user - - Filter roles by (name or ID) - - (Deprecated, please use ``role assignment list`` instead) - -.. option:: --group - - Filter roles by (name or ID) - - (Deprecated, please use ``role assignment list`` instead) - -.. option:: --user-domain - - Domain the user belongs to (name or ID). - This can be used in case collisions between user names exist. - - (Deprecated, please use ``role assignment list`` instead) - - .. versionadded:: 3 - -.. option:: --group-domain - - Domain the group belongs to (name or ID). - This can be used in case collisions between group names exist. - - (Deprecated, please use ``role assignment list`` instead) - - .. versionadded:: 3 - -.. option:: --project-domain - - Domain the project belongs to (name or ID). - This can be used in case collisions between project names exist. - - (Deprecated, please use ``role assignment list`` instead) - - .. versionadded:: 3 - -.. option:: --inherited - - Specifies if the role grant is inheritable to the sub projects. - - (Deprecated, please use ``role assignment list`` instead) - .. versionadded:: 3 role remove diff --git a/doc/source/cli/command-objects/user-role.rst b/doc/source/cli/command-objects/user-role.rst deleted file mode 100644 index 4f443f3126..0000000000 --- a/doc/source/cli/command-objects/user-role.rst +++ /dev/null @@ -1,27 +0,0 @@ -========= -user role -========= - -Identity v2 - -user role list --------------- - -List user-role assignments - -*Removed in version 3.* - -.. program:: user role list -.. code:: bash - - openstack user role list - [--project ] - [] - -.. option:: --project - - Filter users by `` (name or ID) - -.. describe:: - - User to list (name or ID) diff --git a/openstackclient/identity/v2_0/role.py b/openstackclient/identity/v2_0/role.py index e254e05fd8..e9fe50fa4a 100644 --- a/openstackclient/identity/v2_0/role.py +++ b/openstackclient/identity/v2_0/role.py @@ -148,155 +148,11 @@ def take_action(self, parsed_args): class ListRole(command.Lister): _description = _("List roles") - def get_parser(self, prog_name): - parser = super(ListRole, self).get_parser(prog_name) - parser.add_argument( - '--project', - metavar='', - help=_('Filter roles by (name or ID)'), - ) - parser.add_argument( - '--user', - metavar='', - help=_('Filter roles by (name or ID)'), - ) - return parser - def take_action(self, parsed_args): - - def _deprecated(): - # NOTE(henry-nash): Deprecated as of Newton, so we should remove - # this in the 'P' release. - self.log.warning(_('Listing assignments using role list is ' - 'deprecated as of the Newton release. Use role ' - 'assignment list --user --project ' - ' --names instead.')) - identity_client = self.app.client_manager.identity - auth_ref = self.app.client_manager.auth_ref - - # No user or project specified, list all roles in the system - if not parsed_args.user and not parsed_args.project: - columns = ('ID', 'Name') - data = identity_client.roles.list() - elif parsed_args.user and parsed_args.project: - user = utils.find_resource( - identity_client.users, - parsed_args.user, - ) - project = utils.find_resource( - identity_client.projects, - parsed_args.project, - ) - _deprecated() - data = identity_client.roles.roles_for_user(user.id, project.id) - - elif parsed_args.user: - user = utils.find_resource( - identity_client.users, - parsed_args.user, - ) - if self.app.client_manager.auth_ref: - project = utils.find_resource( - identity_client.projects, - auth_ref.project_id - ) - else: - msg = _("Project must be specified") - raise exceptions.CommandError(msg) - _deprecated() - data = identity_client.roles.roles_for_user(user.id, project.id) - elif parsed_args.project: - project = utils.find_resource( - identity_client.projects, - parsed_args.project, - ) - if self.app.client_manager.auth_ref: - user = utils.find_resource( - identity_client.users, - auth_ref.user_id - ) - else: - msg = _("User must be specified") - raise exceptions.CommandError(msg) - _deprecated() - data = identity_client.roles.roles_for_user(user.id, project.id) - - if parsed_args.user or parsed_args.project: - columns = ('ID', 'Name', 'Project', 'User') - for user_role in data: - user_role.user = user.name - user_role.project = project.name - - return (columns, - (utils.get_item_properties( - s, columns, - formatters={}, - ) for s in data)) - - -class ListUserRole(command.Lister): - _description = _("List user-role assignments") - - def get_parser(self, prog_name): - parser = super(ListUserRole, self).get_parser(prog_name) - parser.add_argument( - 'user', - metavar='', - nargs='?', - help=_('User to list (name or ID)'), - ) - parser.add_argument( - '--project', - metavar='', - help=_('Filter users by (name or ID)'), - ) - return parser - - def take_action(self, parsed_args): - identity_client = self.app.client_manager.identity - auth_ref = self.app.client_manager.auth_ref - - # Project and user are required, if not included in command args - # default to the values used for authentication. For token-flow - # authentication they must be included on the command line. - if (not parsed_args.project and - self.app.client_manager.auth_ref.project_id): - parsed_args.project = auth_ref.project_id - if not parsed_args.project: - msg = _("Project must be specified") - raise exceptions.CommandError(msg) - - if (not parsed_args.user and - self.app.client_manager.auth_ref.user_id): - parsed_args.user = auth_ref.user_id - if not parsed_args.user: - msg = _("User must be specified") - raise exceptions.CommandError(msg) - - self.log.warning(_('Listing assignments using user role list is ' - 'deprecated as of the Newton release. Use role ' - 'assignment list --user --project ' - ' --names instead.')) - project = utils.find_resource( - identity_client.tenants, - parsed_args.project, - ) - user = utils.find_resource(identity_client.users, parsed_args.user) - - data = identity_client.roles.roles_for_user(user.id, project.id) - - columns = ( - 'ID', - 'Name', - 'Project', - 'User', - ) - # Add the names to the output even though they will be constant - for role in data: - role.user = user.name - role.project = project.name + columns = ('ID', 'Name') + data = identity_client.roles.list() return (columns, (utils.get_item_properties( diff --git a/openstackclient/identity/v2_0/service.py b/openstackclient/identity/v2_0/service.py index 80f2d72a98..653de8eb47 100644 --- a/openstackclient/identity/v2_0/service.py +++ b/openstackclient/identity/v2_0/service.py @@ -15,7 +15,6 @@ """Service action implementations""" -import argparse import logging from osc_lib.command import command @@ -36,17 +35,11 @@ class CreateService(command.ShowOne): def get_parser(self, prog_name): parser = super(CreateService, self).get_parser(prog_name) parser.add_argument( - 'type_or_name', + 'type', metavar='', help=_('New service type (compute, image, identity, volume, etc)'), ) - type_or_name_group = parser.add_mutually_exclusive_group() - type_or_name_group.add_argument( - '--type', - metavar='', - help=argparse.SUPPRESS, - ) - type_or_name_group.add_argument( + parser.add_argument( '--name', metavar='', help=_('New service name'), @@ -61,29 +54,14 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - type_or_name = parsed_args.type_or_name name = parsed_args.name type = parsed_args.type - # If only a single positional is present, it's a . - # This is not currently legal so it is considered a new case. - if not type and not name: - type = type_or_name - # If --type option is present then positional is handled as ; - # display deprecation message. - elif type: - name = type_or_name - LOG.warning(_('The argument --type is deprecated, use service' - ' create --name type instead.')) - # If --name option is present the positional is handled as . - # Making --type optional is new, but back-compatible - elif name: - type = type_or_name - service = identity_client.services.create( name, type, - parsed_args.description) + parsed_args.description, + ) info = {} info.update(service._info) diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index 58a76f8a65..0eeddd37fb 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -266,131 +266,28 @@ class ListRole(command.Lister): def get_parser(self, prog_name): parser = super(ListRole, self).get_parser(prog_name) - - # TODO(henry-nash): The use of the List Role command to list - # assignments (as well as roles) has been deprecated. In order - # to support domain specific roles, we are overriding the domain - # option to allow specification of the domain for the role. This does - # not conflict with any existing commands, since for the deprecated - # assignments listing you were never allowed to only specify a domain - # (you also needed to specify a user). - # - # Once we have removed the deprecated options entirely, we must - # replace the call to _add_identity_and_resource_options_to_parser() - # below with just adding the domain option into the parser. - _add_identity_and_resource_options_to_parser(parser) + parser.add_argument( + '--domain', + metavar='', + help=_('Include (name or ID)'), + ) return parser def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - if parsed_args.user: - user = common.find_user( - identity_client, - parsed_args.user, - parsed_args.user_domain, - ) - elif parsed_args.group: - group = common.find_group( - identity_client, - parsed_args.group, - parsed_args.group_domain, - ) - if parsed_args.domain: domain = common.find_domain( identity_client, parsed_args.domain, ) - elif parsed_args.project: - project = common.find_project( - identity_client, - parsed_args.project, - parsed_args.project_domain, - ) - - # no user or group specified, list all roles in the system - if not parsed_args.user and not parsed_args.group: - if not parsed_args.domain: - columns = ('ID', 'Name') - data = identity_client.roles.list() - else: - columns = ('ID', 'Name', 'Domain') - data = identity_client.roles.list(domain_id=domain.id) - for role in data: - role.domain = domain.name - elif parsed_args.user and parsed_args.domain: - columns = ('ID', 'Name', 'Domain', 'User') - data = identity_client.roles.list( - user=user, - domain=domain, - os_inherit_extension_inherited=parsed_args.inherited - ) - for user_role in data: - user_role.user = user.name - user_role.domain = domain.name - self.log.warning(_('Listing assignments using role list is ' - 'deprecated. Use role assignment list --user ' - ' --domain --names ' - 'instead.')) - elif parsed_args.user and parsed_args.project: - columns = ('ID', 'Name', 'Project', 'User') - data = identity_client.roles.list( - user=user, - project=project, - os_inherit_extension_inherited=parsed_args.inherited - ) - for user_role in data: - user_role.user = user.name - user_role.project = project.name - self.log.warning(_('Listing assignments using role list is ' - 'deprecated. Use role assignment list --user ' - ' --project --names ' - 'instead.')) - elif parsed_args.user: - columns = ('ID', 'Name') - data = identity_client.roles.list( - user=user, - domain='default', - os_inherit_extension_inherited=parsed_args.inherited - ) - self.log.warning(_('Listing assignments using role list is ' - 'deprecated. Use role assignment list --user ' - ' --domain default --names ' - 'instead.')) - elif parsed_args.group and parsed_args.domain: - columns = ('ID', 'Name', 'Domain', 'Group') - data = identity_client.roles.list( - group=group, - domain=domain, - os_inherit_extension_inherited=parsed_args.inherited - ) - for group_role in data: - group_role.group = group.name - group_role.domain = domain.name - self.log.warning(_('Listing assignments using role list is ' - 'deprecated. Use role assignment list --group ' - ' --domain --names ' - 'instead.')) - elif parsed_args.group and parsed_args.project: - columns = ('ID', 'Name', 'Project', 'Group') - data = identity_client.roles.list( - group=group, - project=project, - os_inherit_extension_inherited=parsed_args.inherited - ) - for group_role in data: - group_role.group = group.name - group_role.project = project.name - self.log.warning(_('Listing assignments using role list is ' - 'deprecated. Use role assignment list --group ' - ' --project --names ' - 'instead.')) + columns = ('ID', 'Name', 'Domain') + data = identity_client.roles.list(domain_id=domain.id) + for role in data: + role.domain = domain.name else: - msg = _("Error: If a user or group is specified, " - "either --domain or --project must also be " - "specified to list role grants.") - raise exceptions.CommandError(msg) + columns = ('ID', 'Name') + data = identity_client.roles.list() return (columns, (utils.get_item_properties( diff --git a/openstackclient/tests/functional/identity/v2/test_role.py b/openstackclient/tests/functional/identity/v2/test_role.py index 82e19aaba9..124603d8b6 100644 --- a/openstackclient/tests/functional/identity/v2/test_role.py +++ b/openstackclient/tests/functional/identity/v2/test_role.py @@ -29,38 +29,6 @@ def test_role_list(self): items = self.parse_listing(raw_output) self.assert_table_structure(items, common.BASIC_LIST_HEADERS) - def test_role_list_with_user_project(self): - project_name = self._create_dummy_project() - role_name = self._create_dummy_role() - username = self._create_dummy_user() - raw_output = self.openstack( - 'role add ' - '--project %(project)s ' - '--user %(user)s ' - '%(role)s' % {'project': project_name, - 'user': username, - 'role': role_name}) - self.addCleanup( - self.openstack, - 'role remove ' - '--project %(project)s ' - '--user %(user)s ' - '%(role)s' % {'project': project_name, - 'user': username, - 'role': role_name}) - items = self.parse_show(raw_output) - self.assert_show_fields(items, self.ROLE_FIELDS) - - raw_output = self.openstack( - 'role list ' - '--project %(project)s ' - '--user %(user)s ' - '' % {'project': project_name, - 'user': username}) - items = self.parse_listing(raw_output) - self.assert_table_structure(items, common.BASIC_LIST_HEADERS) - self.assertEqual(1, len(items)) - def test_role_show(self): role_name = self._create_dummy_role() raw_output = self.openstack('role show %s' % role_name) diff --git a/openstackclient/tests/functional/identity/v3/test_role.py b/openstackclient/tests/functional/identity/v3/test_role.py index fb9e061424..38bfff7187 100644 --- a/openstackclient/tests/functional/identity/v3/test_role.py +++ b/openstackclient/tests/functional/identity/v3/test_role.py @@ -31,47 +31,6 @@ def test_role_list(self): items = self.parse_listing(raw_output) self.assert_table_structure(items, common.BASIC_LIST_HEADERS) - def test_role_list_with_user_project(self): - role_name = self._create_dummy_role() - username = self._create_dummy_user() - raw_output = self.openstack( - 'role add ' - '--project %(project)s ' - '--project-domain %(project_domain)s ' - '--user %(user)s ' - '--user-domain %(user_domain)s ' - '%(role)s' % {'project': self.project_name, - 'project_domain': self.domain_name, - 'user': username, - 'user_domain': self.domain_name, - 'role': role_name}) - self.addCleanup( - self.openstack, - 'role remove ' - '--project %(project)s ' - '--project-domain %(project_domain)s ' - '--user %(user)s ' - '--user-domain %(user_domain)s ' - '%(role)s' % {'project': self.project_name, - 'project_domain': self.domain_name, - 'user': username, - 'user_domain': self.domain_name, - 'role': role_name}) - self.assertEqual(0, len(raw_output)) - raw_output = self.openstack( - 'role list ' - '--project %(project)s ' - '--project-domain %(project_domain)s ' - '--user %(user)s ' - '--user-domain %(user_domain)s ' - '' % {'project': self.project_name, - 'project_domain': self.domain_name, - 'user': username, - 'user_domain': self.domain_name}) - items = self.parse_listing(raw_output) - self.assert_table_structure(items, common.BASIC_LIST_HEADERS) - self.assertEqual(1, len(items)) - def test_role_show(self): role_name = self._create_dummy_role() raw_output = self.openstack('role show %s' % role_name) diff --git a/openstackclient/tests/unit/identity/v2_0/test_role.py b/openstackclient/tests/unit/identity/v2_0/test_role.py index 684ce803a2..643d77f600 100644 --- a/openstackclient/tests/unit/identity/v2_0/test_role.py +++ b/openstackclient/tests/unit/identity/v2_0/test_role.py @@ -278,7 +278,7 @@ def setUp(self): # Get the command object to test self.cmd = role.ListRole(self.app, None) - def test_role_list_no_options(self): + def test_role_list(self): arglist = [] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -299,137 +299,6 @@ def test_role_list_no_options(self): self.assertEqual(datalist, tuple(data)) -class TestUserRoleList(TestRole): - - columns = ( - 'ID', - 'Name', - 'Project', - 'User' - ) - - def setUp(self): - super(TestUserRoleList, self).setUp() - - self.projects_mock.get.return_value = self.fake_project - - self.users_mock.get.return_value = self.fake_user - - self.roles_mock.roles_for_user.return_value = [self.fake_role] - - # Get the command object to test - self.cmd = role.ListUserRole(self.app, None) - - def test_user_role_list_no_options_unscoped_token(self): - auth_ref = identity_fakes.fake_auth_ref( - identity_fakes.UNSCOPED_TOKEN, - fake_service=self.fake_service, - ) - self.ar_mock = mock.PropertyMock(return_value=auth_ref) - type(self.app.client_manager).auth_ref = self.ar_mock - - arglist = [] - verifylist = [] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # This argument combination should raise a CommandError - self.assertRaises( - exceptions.CommandError, - self.cmd.take_action, - parsed_args, - ) - - def test_user_role_list_no_options_scoped_token(self): - arglist = [] - verifylist = [] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # In base command class Lister in cliff, abstract method take_action() - # returns a tuple containing the column names and an iterable - # containing the data to be listed. - columns, data = self.cmd.take_action(parsed_args) - - self.roles_mock.roles_for_user.assert_called_with( - self.fake_user.id, - self.fake_project.id, - ) - - collist = ('ID', 'Name', 'Project', 'User') - self.assertEqual(collist, columns) - datalist = (( - self.fake_role.id, - self.fake_role.name, - self.fake_project.name, - self.fake_user.name, - ), ) - self.assertEqual(datalist, tuple(data)) - - def test_user_role_list_project_unscoped_token(self): - auth_ref = identity_fakes.fake_auth_ref( - identity_fakes.UNSCOPED_TOKEN, - fake_service=self.fake_service, - ) - self.ar_mock = mock.PropertyMock(return_value=auth_ref) - type(self.app.client_manager).auth_ref = self.ar_mock - - self.projects_mock.get.return_value = self.fake_project - arglist = [ - '--project', self.fake_project.name, - ] - verifylist = [ - ('project', self.fake_project.name), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # In base command class Lister in cliff, abstract method take_action() - # returns a tuple containing the column names and an iterable - # containing the data to be listed. - columns, data = self.cmd.take_action(parsed_args) - - self.roles_mock.roles_for_user.assert_called_with( - self.fake_user.id, - self.fake_project.id, - ) - - self.assertEqual(columns, columns) - datalist = (( - self.fake_role.id, - self.fake_role.name, - self.fake_project.name, - self.fake_user.name, - ), ) - self.assertEqual(datalist, tuple(data)) - - def test_user_role_list_project_scoped_token(self): - self.projects_mock.get.return_value = self.fake_project - arglist = [ - '--project', self.fake_project.name, - ] - verifylist = [ - ('project', self.fake_project.name), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # In base command class Lister in cliff, abstract method take_action() - # returns a tuple containing the column names and an iterable - # containing the data to be listed. - columns, data = self.cmd.take_action(parsed_args) - - self.roles_mock.roles_for_user.assert_called_with( - self.fake_user.id, - self.fake_project.id, - ) - - self.assertEqual(columns, columns) - datalist = (( - self.fake_role.id, - self.fake_role.name, - self.fake_project.name, - self.fake_user.name, - ), ) - self.assertEqual(datalist, tuple(data)) - - class TestRoleRemove(TestRole): def setUp(self): diff --git a/openstackclient/tests/unit/identity/v2_0/test_service.py b/openstackclient/tests/unit/identity/v2_0/test_service.py index 1948bf4ab0..6c4374eff3 100644 --- a/openstackclient/tests/unit/identity/v2_0/test_service.py +++ b/openstackclient/tests/unit/identity/v2_0/test_service.py @@ -55,43 +55,14 @@ def setUp(self): # Get the command object to test self.cmd = service.CreateService(self.app, None) - def test_service_create_with_type_positional(self): + def test_service_create(self): arglist = [ self.fake_service_c.type, ] verifylist = [ - ('type_or_name', self.fake_service_c.type), - ('type', None), - ('description', None), - ('name', None), - ] - 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) - - # ServiceManager.create(name, service_type, description) - self.services_mock.create.assert_called_with( - None, - self.fake_service_c.type, - None, - ) - - self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) - - def test_service_create_with_type_option(self): - arglist = [ - '--type', self.fake_service_c.type, - self.fake_service_c.name, - ] - verifylist = [ - ('type_or_name', self.fake_service_c.name), ('type', self.fake_service_c.type), - ('description', None), ('name', None), + ('description', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -102,7 +73,7 @@ def test_service_create_with_type_option(self): # ServiceManager.create(name, service_type, description) self.services_mock.create.assert_called_with( - self.fake_service_c.name, + None, self.fake_service_c.type, None, ) @@ -116,10 +87,9 @@ def test_service_create_with_name_option(self): self.fake_service_c.type, ] verifylist = [ - ('type_or_name', self.fake_service_c.type), - ('type', None), - ('description', None), + ('type', self.fake_service_c.type), ('name', self.fake_service_c.name), + ('description', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -145,10 +115,9 @@ def test_service_create_description(self): self.fake_service_c.type, ] verifylist = [ - ('type_or_name', self.fake_service_c.type), - ('type', None), - ('description', self.fake_service_c.description), + ('type', self.fake_service_c.type), ('name', self.fake_service_c.name), + ('description', self.fake_service_c.description), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/unit/identity/v3/test_role.py b/openstackclient/tests/unit/identity/v3/test_role.py index 281d530c7e..99f3a2de74 100644 --- a/openstackclient/tests/unit/identity/v3/test_role.py +++ b/openstackclient/tests/unit/identity/v3/test_role.py @@ -508,21 +508,6 @@ def setUp(self): copy.deepcopy(identity_fakes.DOMAIN), loaded=True, ) - self.projects_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROJECT), - loaded=True, - ) - self.users_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.USER), - loaded=True, - ) - self.groups_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.GROUP), - loaded=True, - ) # Get the command object to test self.cmd = role.ListRole(self.app, None) @@ -542,212 +527,6 @@ def test_role_list_no_options(self): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) - def test_user_list_inherited(self): - arglist = [ - '--user', identity_fakes.user_id, - '--inherited', - ] - verifylist = [ - ('user', identity_fakes.user_id), - ('inherited', True), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # In base command class Lister in cliff, abstract method take_action() - # returns a tuple containing the column names and an iterable - # containing the data to be listed. - columns, data = self.cmd.take_action(parsed_args) - - # Set expected values - kwargs = { - 'domain': 'default', - 'user': self.users_mock.get(), - 'os_inherit_extension_inherited': True, - } - # RoleManager.list(user=, group=, domain=, project=, **kwargs) - self.roles_mock.list.assert_called_with( - **kwargs - ) - - self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, tuple(data)) - - def test_user_list_user(self): - arglist = [ - '--user', identity_fakes.user_id, - ] - verifylist = [ - ('user', identity_fakes.user_id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # In base command class Lister in cliff, abstract method take_action() - # returns a tuple containing the column names and an iterable - # containing the data to be listed. - columns, data = self.cmd.take_action(parsed_args) - - # Set expected values - kwargs = { - 'domain': 'default', - 'user': self.users_mock.get(), - 'os_inherit_extension_inherited': False - } - # RoleManager.list(user=, group=, domain=, project=, **kwargs) - self.roles_mock.list.assert_called_with( - **kwargs - ) - - self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, tuple(data)) - - def test_role_list_domain_user(self): - arglist = [ - '--domain', identity_fakes.domain_name, - '--user', identity_fakes.user_id, - ] - verifylist = [ - ('domain', identity_fakes.domain_name), - ('user', identity_fakes.user_id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # In base command class Lister in cliff, abstract method take_action() - # returns a tuple containing the column names and an iterable - # containing the data to be listed. - columns, data = self.cmd.take_action(parsed_args) - - # Set expected values - kwargs = { - 'domain': self.domains_mock.get(), - 'user': self.users_mock.get(), - 'os_inherit_extension_inherited': False - } - # RoleManager.list(user=, group=, domain=, project=, **kwargs) - self.roles_mock.list.assert_called_with( - **kwargs - ) - - collist = ('ID', 'Name', 'Domain', 'User') - self.assertEqual(collist, columns) - datalist = (( - identity_fakes.role_id, - identity_fakes.role_name, - identity_fakes.domain_name, - identity_fakes.user_name, - ), ) - self.assertEqual(datalist, tuple(data)) - - def test_role_list_domain_group(self): - arglist = [ - '--domain', identity_fakes.domain_name, - '--group', identity_fakes.group_id, - ] - verifylist = [ - ('domain', identity_fakes.domain_name), - ('group', identity_fakes.group_id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # In base command class Lister in cliff, abstract method take_action() - # returns a tuple containing the column names and an iterable - # containing the data to be listed. - columns, data = self.cmd.take_action(parsed_args) - - # Set expected values - kwargs = { - 'domain': self.domains_mock.get(), - 'group': self.groups_mock.get(), - 'os_inherit_extension_inherited': False - } - # RoleManager.list(user=, group=, domain=, project=, **kwargs) - self.roles_mock.list.assert_called_with( - **kwargs - ) - - collist = ('ID', 'Name', 'Domain', 'Group') - self.assertEqual(collist, columns) - datalist = (( - identity_fakes.role_id, - identity_fakes.role_name, - identity_fakes.domain_name, - identity_fakes.group_name, - ), ) - self.assertEqual(datalist, tuple(data)) - - def test_role_list_project_user(self): - arglist = [ - '--project', identity_fakes.project_name, - '--user', identity_fakes.user_id, - ] - verifylist = [ - ('project', identity_fakes.project_name), - ('user', identity_fakes.user_id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # In base command class Lister in cliff, abstract method take_action() - # returns a tuple containing the column names and an iterable - # containing the data to be listed. - columns, data = self.cmd.take_action(parsed_args) - - # Set expected values - kwargs = { - 'project': self.projects_mock.get(), - 'user': self.users_mock.get(), - 'os_inherit_extension_inherited': False - } - # RoleManager.list(user=, group=, domain=, project=, **kwargs) - self.roles_mock.list.assert_called_with( - **kwargs - ) - - collist = ('ID', 'Name', 'Project', 'User') - self.assertEqual(collist, columns) - datalist = (( - identity_fakes.role_id, - identity_fakes.role_name, - identity_fakes.project_name, - identity_fakes.user_name, - ), ) - self.assertEqual(datalist, tuple(data)) - - def test_role_list_project_group(self): - arglist = [ - '--project', identity_fakes.project_name, - '--group', identity_fakes.group_id, - ] - verifylist = [ - ('project', identity_fakes.project_name), - ('group', identity_fakes.group_id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # In base command class Lister in cliff, abstract method take_action() - # returns a tuple containing the column names and an iterable - # containing the data to be listed. - columns, data = self.cmd.take_action(parsed_args) - - # Set expected values - kwargs = { - 'project': self.projects_mock.get(), - 'group': self.groups_mock.get(), - 'os_inherit_extension_inherited': False - } - # RoleManager.list(user=, group=, domain=, project=, **kwargs) - self.roles_mock.list.assert_called_with( - **kwargs - ) - - collist = ('ID', 'Name', 'Project', 'Group') - self.assertEqual(collist, columns) - datalist = (( - identity_fakes.role_id, - identity_fakes.role_name, - identity_fakes.project_name, - identity_fakes.group_name, - ), ) - self.assertEqual(datalist, tuple(data)) - def test_role_list_domain_role(self): self.roles_mock.list.return_value = [ fakes.FakeResource( @@ -787,17 +566,6 @@ def test_role_list_domain_role(self): ), ) self.assertEqual(datalist, tuple(data)) - def test_role_list_group_with_error(self): - arglist = [ - '--group', identity_fakes.group_id, - ] - verifylist = [ - ('group', identity_fakes.group_id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.assertRaises(exceptions.CommandError, - self.cmd.take_action, parsed_args) - class TestRoleRemove(TestRole): diff --git a/releasenotes/notes/osc4-identity-6564257c67d43106.yaml b/releasenotes/notes/osc4-identity-6564257c67d43106.yaml new file mode 100644 index 0000000000..a5105c801c --- /dev/null +++ b/releasenotes/notes/osc4-identity-6564257c67d43106.yaml @@ -0,0 +1,12 @@ +--- +upgrade: + - | + Remove deprecated ``role list`` options ``--project`` and ``--user``. + Use ``role assignment list`` options ``--project`` and ``--user`` instead. + - | + Remove deprecated ``user role list`` command. + Use ``role assignment list`` options ``--project`` and ``--user`` instead. + - | + Remove deprecated ``service create`` option ``--type``. + The type is supplied as a positional argument in The + ``service create --name type`` command. diff --git a/setup.cfg b/setup.cfg index 031776a99d..cc57a1037a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -192,8 +192,6 @@ openstack.identity.v2 = user_set = openstackclient.identity.v2_0.user:SetUser user_show = openstackclient.identity.v2_0.user:ShowUser - user_role_list = openstackclient.identity.v2_0.role:ListUserRole - openstack.identity.v3 = access_token_create = openstackclient.identity.v3.token:CreateAccessToken From 67dadda746759d8cf47822e6f426c473e46acc27 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 15 May 2019 22:20:25 -0500 Subject: [PATCH 2062/3095] Remove deprecated image commands * Remove ``image create|set`` option ``--owner`` Change-Id: I1fabab98c8660eba6d0dd75e74544c6c9d432b9e Signed-off-by: Dean Troyer --- doc/source/cli/backwards-incompatible.rst | 6 +++ doc/source/cli/data/glance.csv | 48 ++++++++--------- openstackclient/image/v1/image.py | 30 +---------- openstackclient/image/v2/image.py | 49 +++-------------- .../tests/unit/image/v2/test_image.py | 52 ------------------- .../notes/osc4-image-e922ee6f8e028648.yaml | 5 ++ 6 files changed, 43 insertions(+), 147 deletions(-) create mode 100644 releasenotes/notes/osc4-image-e922ee6f8e028648.yaml diff --git a/doc/source/cli/backwards-incompatible.rst b/doc/source/cli/backwards-incompatible.rst index 51743cb2b2..84fa4897d5 100644 --- a/doc/source/cli/backwards-incompatible.rst +++ b/doc/source/cli/backwards-incompatible.rst @@ -49,6 +49,12 @@ Release 4.0 * Removed in: 4.0 * Commit: https://review.opendev.org/612798 +6. Remove ``image create|set`` option ``--owner``. + Use ``--project`` option instead. + + * Removed in: 4.0 + * Commit: https://review.opendev.org/659431 + .. 1. Change ``volume transfer request accept`` to use new option ``--auth-key`` .. rather than a second positional argument. diff --git a/doc/source/cli/data/glance.csv b/doc/source/cli/data/glance.csv index 2985e307c7..994a8fdaae 100644 --- a/doc/source/cli/data/glance.csv +++ b/doc/source/cli/data/glance.csv @@ -1,24 +1,24 @@ -explain,WONTFIX,Describe a specific model. -image-create,image create,Create a new image. -image-deactivate,image set --deactivate,Deactivate specified image. -image-delete,image delete,Delete specified image. -image-download,image save,Download a specific image. -image-list,image list,List images you can access. -image-reactivate,image set --activate,Reactivate specified image. -image-show,image show,Describe a specific image. -image-tag-delete,image set --tag ,Delete the tag associated with the given image. -image-tag-update,image unset --tag ,Update an image with the given tag. -image-update,image set,Update an existing image. -image-upload,,Upload data for a specific image. -location-add,,Add a location (and related metadata) to an image. -location-delete,,Remove locations (and related metadata) from an image. -location-update,,Update metadata of an image's location. -member-create,image add project,Create member for a given image. -member-delete,image remove project,Delete image member. -member-list,,Describe sharing permissions by image. -member-update,image set --accept --reject --status,Update the status of a member for a given image. -task-create,,Create a new task. -task-list,,List tasks you can access. -task-show,,Describe a specific task. -bash-completion,complete,Prints arguments for bash_completion. -help,help,Display help about this program or one of its subcommands. \ No newline at end of file +explain,WONTFIX,Describe a specific model. +image-create,image create,Create a new image. +image-deactivate,image set --deactivate,Deactivate specified image. +image-delete,image delete,Delete specified image. +image-download,image save,Download a specific image. +image-list,image list,List images you can access. +image-reactivate,image set --activate,Reactivate specified image. +image-show,image show,Describe a specific image. +image-tag-delete,image unset --tag ,Delete the tag associated with the given image. +image-tag-update,image set --tag ,Update an image with the given tag. +image-update,image set,Update an existing image. +image-upload,,Upload data for a specific image. +location-add,,Add a location (and related metadata) to an image. +location-delete,,Remove locations (and related metadata) from an image. +location-update,,Update metadata of an image's location. +member-create,image add project,Create member for a given image. +member-delete,image remove project,Delete image member. +member-list,,Describe sharing permissions by image. +member-update,image set --accept --reject --status,Update the status of a member for a given image. +task-create,,Create a new task. +task-list,,List tasks you can access. +task-show,,Describe a specific task. +bash-completion,complete,Prints arguments for bash_completion. +help,help,Display help about this program or one of its subcommands. diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index 64c4049cfd..caf3d54f47 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -182,29 +182,16 @@ def get_parser(self, prog_name): help=_("Set a property on this image " "(repeat option to set multiple properties)"), ) - # NOTE(dtroyer): --owner is deprecated in Jan 2016 in an early - # 2.x release. Do not remove before Jan 2017 - # and a 3.x release. - project_group = parser.add_mutually_exclusive_group() - project_group.add_argument( + parser.add_argument( "--project", metavar="", help=_("Set an alternate project on this image (name or ID)"), ) - project_group.add_argument( - "--owner", - metavar="", - help=argparse.SUPPRESS, - ) return parser def take_action(self, parsed_args): image_client = self.app.client_manager.image - if getattr(parsed_args, 'owner', None) is not None: - LOG.warning(_('The --owner option is deprecated, ' - 'please use --project instead.')) - # Build an attribute dict from the parsed args, only include # attributes that were actually set on the command line kwargs = {} @@ -599,29 +586,16 @@ def get_parser(self, prog_name): metavar="", help=_("Image hash used for verification"), ) - # NOTE(dtroyer): --owner is deprecated in Jan 2016 in an early - # 2.x release. Do not remove before Jan 2017 - # and a 3.x release. - project_group = parser.add_mutually_exclusive_group() - project_group.add_argument( + parser.add_argument( "--project", metavar="", help=_("Set an alternate project on this image (name or ID)"), ) - project_group.add_argument( - "--owner", - metavar="", - help=argparse.SUPPRESS, - ) return parser def take_action(self, parsed_args): image_client = self.app.client_manager.image - if getattr(parsed_args, 'owner', None) is not None: - LOG.warning(_('The --owner option is deprecated, ' - 'please use --project instead.')) - kwargs = {} copy_attrs = ('name', 'owner', 'min_disk', 'min_ram', 'properties', 'container_format', 'disk_format', 'size', 'store', diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index bdec99d7be..97169a9237 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -250,20 +250,11 @@ def get_parser(self, prog_name): help=_("Set a tag on this image " "(repeat option to set multiple tags)"), ) - # NOTE(dtroyer): --owner is deprecated in Jan 2016 in an early - # 2.x release. Do not remove before Jan 2017 - # and a 3.x release. - project_group = parser.add_mutually_exclusive_group() - project_group.add_argument( + parser.add_argument( "--project", metavar="", help=_("Set an alternate project on this image (name or ID)"), ) - project_group.add_argument( - "--owner", - metavar="", - help=argparse.SUPPRESS, - ) common.add_project_domain_option_to_parser(parser) for deadopt in self.deadopts: parser.add_argument( @@ -321,16 +312,10 @@ def take_action(self, parsed_args): kwargs['visibility'] = 'community' if parsed_args.shared: kwargs['visibility'] = 'shared' - # Handle deprecated --owner option - project_arg = parsed_args.project - if parsed_args.owner: - project_arg = parsed_args.owner - LOG.warning(_('The --owner option is deprecated, ' - 'please use --project instead.')) - if project_arg: + if parsed_args.project: kwargs['owner'] = common.find_project( identity_client, - project_arg, + parsed_args.project, parsed_args.project_domain, ).id @@ -347,13 +332,6 @@ def take_action(self, parsed_args): LOG.warning(_("Failed to get an image file.")) return {}, {} - if parsed_args.owner: - kwargs['owner'] = common.find_project( - identity_client, - parsed_args.owner, - parsed_args.project_domain, - ).id - # sign an image using a given local private key file if parsed_args.sign_key_path or parsed_args.sign_cert_id: if not parsed_args.file: @@ -933,20 +911,11 @@ def get_parser(self, prog_name): action="store_true", help=_("Activate the image"), ) - # NOTE(dtroyer): --owner is deprecated in Jan 2016 in an early - # 2.x release. Do not remove before Jan 2017 - # and a 3.x release. - project_group = parser.add_mutually_exclusive_group() - project_group.add_argument( + parser.add_argument( "--project", metavar="", help=_("Set an alternate project on this image (name or ID)"), ) - project_group.add_argument( - "--owner", - metavar="", - help=argparse.SUPPRESS, - ) common.add_project_domain_option_to_parser(parser) for deadopt in self.deadopts: parser.add_argument( @@ -1020,17 +989,11 @@ def take_action(self, parsed_args): kwargs['visibility'] = 'community' if parsed_args.shared: kwargs['visibility'] = 'shared' - # Handle deprecated --owner option - project_arg = parsed_args.project - if parsed_args.owner: - project_arg = parsed_args.owner - LOG.warning(_('The --owner option is deprecated, ' - 'please use --project instead.')) project_id = None - if project_arg: + if parsed_args.project: project_id = common.find_project( identity_client, - project_arg, + parsed_args.project, parsed_args.project_domain, ).id kwargs['owner'] = project_id diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py index 4cc8f44827..45b5a0a8cb 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -188,40 +188,6 @@ def test_image_reserve_options(self, mock_open): image_fakes.FakeImage.get_image_data(self.new_image), data) - def test_image_create_with_unexist_owner(self): - self.project_mock.get.side_effect = exceptions.NotFound(None) - self.project_mock.find.side_effect = exceptions.NotFound(None) - - arglist = [ - '--container-format', 'ovf', - '--disk-format', 'ami', - '--min-disk', '10', - '--min-ram', '4', - '--owner', 'unexist_owner', - '--protected', - '--private', - image_fakes.image_name, - ] - verifylist = [ - ('container_format', 'ovf'), - ('disk_format', 'ami'), - ('min_disk', 10), - ('min_ram', 4), - ('owner', 'unexist_owner'), - ('protected', True), - ('unprotected', False), - ('public', False), - ('private', True), - ('name', image_fakes.image_name), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - self.assertRaises( - exceptions.CommandError, - self.cmd.take_action, - parsed_args, - ) - def test_image_create_with_unexist_project(self): self.project_mock.get.side_effect = exceptions.NotFound(None) self.project_mock.find.side_effect = exceptions.NotFound(None) @@ -1146,24 +1112,6 @@ def test_image_set_options(self): image_fakes.image_id, **kwargs) self.assertIsNone(result) - def test_image_set_with_unexist_owner(self): - self.project_mock.get.side_effect = exceptions.NotFound(None) - self.project_mock.find.side_effect = exceptions.NotFound(None) - - arglist = [ - '--owner', 'unexist_owner', - image_fakes.image_id, - ] - verifylist = [ - ('owner', 'unexist_owner'), - ('image', image_fakes.image_id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - self.assertRaises( - exceptions.CommandError, - self.cmd.take_action, parsed_args) - def test_image_set_with_unexist_project(self): self.project_mock.get.side_effect = exceptions.NotFound(None) self.project_mock.find.side_effect = exceptions.NotFound(None) diff --git a/releasenotes/notes/osc4-image-e922ee6f8e028648.yaml b/releasenotes/notes/osc4-image-e922ee6f8e028648.yaml new file mode 100644 index 0000000000..eab3f70c21 --- /dev/null +++ b/releasenotes/notes/osc4-image-e922ee6f8e028648.yaml @@ -0,0 +1,5 @@ +--- +upgrade: + - | + Remove ``image create|set`` option ``--owner``. + Use ``--project`` option instead. From 5a0fc68a87d1c9733c1dd5bb6f68b2e518fe2105 Mon Sep 17 00:00:00 2001 From: Sean McGinnis Date: Fri, 26 Oct 2018 11:50:32 -0500 Subject: [PATCH 2063/3095] Remove deprecated network options The following were deprecated for several releases and can now be removed: * Remove ``port create|set`` options ``--device-id`` and ``--port-id`` * Remove ``router set`` option ``--clear-routes`` * Remove ``security group rule create`` options ``--src-group`` and ``--src-ip`` These are backwards incompatible changes and will require a major version bump after they are merged. Change-Id: Ieae74c14f6b3e263721a3146cf76f94a9ab792f6 Signed-off-by: Sean McGinnis Signed-off-by: Dean Troyer --- doc/source/cli/backwards-incompatible.rst | 18 ++++ openstackclient/network/v2/port.py | 35 +------- openstackclient/network/v2/router.py | 19 +---- .../network/v2/security_group_rule.py | 63 ++------------ .../tests/unit/network/v2/test_router.py | 46 ---------- .../v2/test_security_group_rule_compute.py | 83 +------------------ .../v2/test_security_group_rule_network.py | 32 +++---- .../notes/osc4-network-db2aed696d964ca6.yaml | 12 +++ 8 files changed, 56 insertions(+), 252 deletions(-) create mode 100644 releasenotes/notes/osc4-network-db2aed696d964ca6.yaml diff --git a/doc/source/cli/backwards-incompatible.rst b/doc/source/cli/backwards-incompatible.rst index 84fa4897d5..cbf1823c13 100644 --- a/doc/source/cli/backwards-incompatible.rst +++ b/doc/source/cli/backwards-incompatible.rst @@ -55,6 +55,24 @@ Release 4.0 * Removed in: 4.0 * Commit: https://review.opendev.org/659431 +7. Remove ``port create|set`` options ``--device-id`` and ``--host-id``. + Use ``--device`` and ``--host`` instead. + + * Removed in: 4.0 + * Commit: https://review.opendev.org/613644 + +8. Remove ``router set`` option ``--clear-routes``. + Use ``no-route`` option instead. + + * Removed in: 4.0 + * Commit: https://review.opendev.org/613644 + +9. Remove ``security group rule create`` options ``--src-ip`` and ``--src-group``. + Use ``--remote-ip`` and ``--remote-group`` options instead. + + * Removed in: 4.0 + * Commit: https://review.opendev.org/613644 + .. 1. Change ``volume transfer request accept`` to use new option ``--auth-key`` .. rather than a second positional argument. diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index f6d6fc7280..50bc742f28 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -96,21 +96,6 @@ def __call__(self, parser, namespace, values, option_string=None): def _get_attrs(client_manager, parsed_args): attrs = {} - # Handle deprecated options - # NOTE(dtroyer): --device-id and --host-id were deprecated in Mar 2016. - # Do not remove before 3.x release or Mar 2017. - if parsed_args.device_id: - attrs['device_id'] = parsed_args.device_id - LOG.warning(_( - 'The --device-id option is deprecated, ' - 'please use --device instead.' - )) - if parsed_args.host_id: - attrs['binding:host_id'] = parsed_args.host_id - LOG.warning(_( - 'The --host-id option is deprecated, ' - 'please use --host instead.' - )) if parsed_args.description is not None: attrs['description'] = parsed_args.description if parsed_args.device: @@ -235,19 +220,11 @@ def _add_updatable_args(parser): metavar='', help=_("Description of this port") ) - # NOTE(dtroyer): --device-id is deprecated in Mar 2016. Do not - # remove before 3.x release or Mar 2017. - device_group = parser.add_mutually_exclusive_group() - device_group.add_argument( + parser.add_argument( '--device', metavar='', help=_("Port device ID") ) - device_group.add_argument( - '--device-id', - metavar='', - help=argparse.SUPPRESS, - ) parser.add_argument( '--mac-address', metavar='', @@ -268,19 +245,11 @@ def _add_updatable_args(parser): "macvtap | normal | baremetal | virtio-forwarder, " "default: normal)") ) - # NOTE(dtroyer): --host-id is deprecated in Mar 2016. Do not - # remove before 3.x release or Mar 2017. - host_group = parser.add_mutually_exclusive_group() - host_group.add_argument( + parser.add_argument( '--host', metavar='', help=_("Allocate port on host (ID only)") ) - host_group.add_argument( - '--host-id', - metavar='', - help=argparse.SUPPRESS, - ) parser.add_argument( '--dns-domain', metavar='dns-domain', diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 2ec3e2f094..13de66aaa7 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -13,7 +13,6 @@ """Router action implementations""" -import argparse import copy import json import logging @@ -528,8 +527,6 @@ def get_parser(self, prog_name): action='store_true', help=_("Set router to centralized mode (disabled router only)") ) - routes_group = parser.add_mutually_exclusive_group() - # ToDo(Reedip):Remove mutual exclusiveness once clear-routes is removed parser.add_argument( '--route', metavar='destination=,gateway=', @@ -542,18 +539,13 @@ def get_parser(self, prog_name): "gateway: nexthop IP address " "(repeat option to set multiple routes)") ) - routes_group.add_argument( + parser.add_argument( '--no-route', action='store_true', help=_("Clear routes associated with the router. " "Specify both --route and --no-route to overwrite " "current value of route.") ) - routes_group.add_argument( - '--clear-routes', - action='store_true', - help=argparse.SUPPRESS, - ) routes_ha = parser.add_mutually_exclusive_group() routes_ha.add_argument( '--ha', @@ -619,21 +611,16 @@ def take_action(self, parsed_args): attrs['ha'] = True elif parsed_args.no_ha: attrs['ha'] = False - if parsed_args.clear_routes: - LOG.warning(_( - 'The --clear-routes option is deprecated, ' - 'please use --no-route instead.' - )) if parsed_args.routes is not None: for route in parsed_args.routes: route['nexthop'] = route.pop('gateway') attrs['routes'] = parsed_args.routes - if not (parsed_args.no_route or parsed_args.clear_routes): + if not parsed_args.no_route: # Map the route keys and append to the current routes. # The REST API will handle route validation and duplicates. attrs['routes'] += obj.routes - elif parsed_args.no_route or parsed_args.clear_routes: + elif parsed_args.no_route: attrs['routes'] = [] if (parsed_args.disable_snat or parsed_args.enable_snat or parsed_args.fixed_ip) and not parsed_args.external_gateway: diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index 961125a9a7..df19af201f 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -115,19 +115,6 @@ def update_parser_common(self, parser): metavar="", help=_("Remote security group (name or ID)"), ) - # Handle deprecated options - # NOTE(dtroyer): --src-ip and --src-group were deprecated in Nov 2016. - # Do not remove before 4.x release or Nov 2017. - remote_group.add_argument( - "--src-ip", - metavar="", - help=argparse.SUPPRESS, - ) - remote_group.add_argument( - "--src-group", - metavar="", - help=argparse.SUPPRESS, - ) return parser def update_parser_network(self, parser): @@ -310,31 +297,13 @@ def take_action_network(self, client, parsed_args): if parsed_args.icmp_code is not None and parsed_args.icmp_code >= 0: attrs['port_range_max'] = parsed_args.icmp_code - # NOTE(dtroyer): --src-ip and --src-group were deprecated in Nov 2016. - # Do not remove before 4.x release or Nov 2017. - if not (parsed_args.remote_group is None and - parsed_args.src_group is None): + if parsed_args.remote_group is not None: attrs['remote_group_id'] = client.find_security_group( - parsed_args.remote_group or parsed_args.src_group, + parsed_args.remote_group, ignore_missing=False ).id - if parsed_args.src_group: - LOG.warning( - _("The %(old)s option is deprecated, " - "please use %(new)s instead."), - {'old': '--src-group', 'new': '--remote-group'}, - ) - elif not (parsed_args.remote_ip is None and - parsed_args.src_ip is None): - attrs['remote_ip_prefix'] = ( - parsed_args.remote_ip or parsed_args.src_ip - ) - if parsed_args.src_ip: - LOG.warning( - _("The %(old)s option is deprecated, " - "please use %(new)s instead."), - {'old': '--src-ip', 'new': '--remote-ip'}, - ) + elif parsed_args.remote_ip is not None: + attrs['remote_ip_prefix'] = parsed_args.remote_ip elif attrs['ethertype'] == 'IPv4': attrs['remote_ip_prefix'] = '0.0.0.0/0' attrs['security_group_id'] = security_group_id @@ -361,29 +330,13 @@ def take_action_compute(self, client, parsed_args): else: from_port, to_port = parsed_args.dst_port - # NOTE(dtroyer): --src-ip and --src-group were deprecated in Nov 2016. - # Do not remove before 4.x release or Nov 2017. remote_ip = None - if not (parsed_args.remote_group is None and - parsed_args.src_group is None): + if parsed_args.remote_group is not None: parsed_args.remote_group = client.api.security_group_find( - parsed_args.remote_group or parsed_args.src_group, + parsed_args.remote_group, )['id'] - if parsed_args.src_group: - LOG.warning( - _("The %(old)s option is deprecated, " - "please use %(new)s instead."), - {'old': '--src-group', 'new': '--remote-group'}, - ) - if not (parsed_args.remote_ip is None and - parsed_args.src_ip is None): - remote_ip = parsed_args.remote_ip or parsed_args.src_ip - if parsed_args.src_ip: - LOG.warning( - _("The %(old)s option is deprecated, " - "please use %(new)s instead."), - {'old': '--src-ip', 'new': '--remote-ip'}, - ) + if parsed_args.remote_ip is not None: + remote_ip = parsed_args.remote_ip else: remote_ip = '0.0.0.0/0' diff --git a/openstackclient/tests/unit/network/v2/test_router.py b/openstackclient/tests/unit/network/v2/test_router.py index 618adf3516..a07b2e4b12 100644 --- a/openstackclient/tests/unit/network/v2/test_router.py +++ b/openstackclient/tests/unit/network/v2/test_router.py @@ -939,52 +939,6 @@ def test_set_route_overwrite_route(self): _testrouter, **attrs) self.assertIsNone(result) - def test_set_clear_routes(self): - arglist = [ - self._router.name, - '--clear-routes', - ] - verifylist = [ - ('router', self._router.name), - ('clear_routes', True), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - attrs = { - 'routes': [], - } - self.network.update_router.assert_called_once_with( - self._router, **attrs) - self.assertIsNone(result) - - def test_overwrite_route_clear_routes(self): - _testrouter = network_fakes.FakeRouter.create_one_router( - {'routes': [{"destination": "10.0.0.2", - "nexthop": "1.1.1.1"}]}) - self.network.find_router = mock.Mock(return_value=_testrouter) - arglist = [ - _testrouter.name, - '--route', 'destination=10.20.30.0/24,gateway=10.20.30.1', - '--clear-routes', - ] - verifylist = [ - ('router', _testrouter.name), - ('routes', [{'destination': '10.20.30.0/24', - 'gateway': '10.20.30.1'}]), - ('clear_routes', True), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) - attrs = { - 'routes': [{'destination': '10.20.30.0/24', - 'nexthop': '10.20.30.1'}] - } - self.network.update_router.assert_called_once_with( - _testrouter, **attrs) - self.assertIsNone(result) - def test_set_nothing(self): arglist = [ self._router.name, diff --git a/openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py b/openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py index 5c1937e307..6814c197b3 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py @@ -72,15 +72,6 @@ def test_security_group_rule_create_no_options(self, sgr_mock): self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, [], []) - def test_security_group_rule_create_all_source_options(self, sgr_mock): - arglist = [ - '--src-ip', '10.10.0.0/24', - '--src-group', self._security_group['id'], - self._security_group['id'], - ] - self.assertRaises(tests_utils.ParserException, - self.check_parser, self.cmd, arglist, []) - def test_security_group_rule_create_all_remote_options(self, sgr_mock): arglist = [ '--remote-ip', '10.10.0.0/24', @@ -151,41 +142,6 @@ def test_security_group_rule_create_default_rule(self, sgr_mock): self.assertEqual(expected_columns, columns) self.assertEqual(expected_data, data) - def test_security_group_rule_create_source_group(self, sgr_mock): - expected_columns, expected_data = self._setup_security_group_rule({ - 'from_port': 22, - 'to_port': 22, - 'group': {'name': self._security_group['name']}, - }) - sgr_mock.return_value = self._security_group_rule - arglist = [ - '--dst-port', str(self._security_group_rule['from_port']), - '--src-group', self._security_group['name'], - self._security_group['id'], - ] - verifylist = [ - ('dst_port', (self._security_group_rule['from_port'], - self._security_group_rule['to_port'])), - ('src_group', self._security_group['name']), - ('group', self._security_group['id']), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - # TODO(dtroyer): save this for the security group rule changes - # self.compute.api.security_group_rule_create.assert_called_once_with( - sgr_mock.assert_called_once_with( - security_group_id=self._security_group['id'], - ip_protocol=self._security_group_rule['ip_protocol'], - from_port=self._security_group_rule['from_port'], - to_port=self._security_group_rule['to_port'], - remote_ip=self._security_group_rule['ip_range']['cidr'], - remote_group=self._security_group['id'], - ) - self.assertEqual(expected_columns, columns) - self.assertEqual(expected_data, data) - def test_security_group_rule_create_remote_group(self, sgr_mock): expected_columns, expected_data = self._setup_security_group_rule({ 'from_port': 22, @@ -221,41 +177,6 @@ def test_security_group_rule_create_remote_group(self, sgr_mock): self.assertEqual(expected_columns, columns) self.assertEqual(expected_data, data) - def test_security_group_rule_create_source_ip(self, sgr_mock): - expected_columns, expected_data = self._setup_security_group_rule({ - 'ip_protocol': 'icmp', - 'from_port': -1, - 'to_port': -1, - 'ip_range': {'cidr': '10.0.2.0/24'}, - }) - sgr_mock.return_value = self._security_group_rule - arglist = [ - '--protocol', self._security_group_rule['ip_protocol'], - '--src-ip', self._security_group_rule['ip_range']['cidr'], - self._security_group['id'], - ] - verifylist = [ - ('protocol', self._security_group_rule['ip_protocol']), - ('src_ip', self._security_group_rule['ip_range']['cidr']), - ('group', self._security_group['id']), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - # TODO(dtroyer): save this for the security group rule changes - # self.compute.api.security_group_rule_create.assert_called_once_with( - sgr_mock.assert_called_once_with( - security_group_id=self._security_group['id'], - ip_protocol=self._security_group_rule['ip_protocol'], - from_port=self._security_group_rule['from_port'], - to_port=self._security_group_rule['to_port'], - remote_ip=self._security_group_rule['ip_range']['cidr'], - remote_group=None, - ) - self.assertEqual(expected_columns, columns) - self.assertEqual(expected_data, data) - def test_security_group_rule_create_remote_ip(self, sgr_mock): expected_columns, expected_data = self._setup_security_group_rule({ 'ip_protocol': 'icmp', @@ -301,13 +222,13 @@ def test_security_group_rule_create_proto_option(self, sgr_mock): sgr_mock.return_value = self._security_group_rule arglist = [ '--proto', self._security_group_rule['ip_protocol'], - '--src-ip', self._security_group_rule['ip_range']['cidr'], + '--remote-ip', self._security_group_rule['ip_range']['cidr'], self._security_group['id'], ] verifylist = [ ('proto', self._security_group_rule['ip_protocol']), ('protocol', None), - ('src_ip', self._security_group_rule['ip_range']['cidr']), + ('remote_ip', self._security_group_rule['ip_range']['cidr']), ('group', self._security_group['id']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py b/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py index b070ab6aab..2b0de0d2a1 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py @@ -99,15 +99,6 @@ def test_create_no_options(self): self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, [], []) - def test_create_all_source_options(self): - arglist = [ - '--src-ip', '10.10.0.0/24', - '--src-group', self._security_group.id, - self._security_group.id, - ] - self.assertRaises(tests_utils.ParserException, - self.check_parser, self.cmd, arglist, []) - def test_create_all_remote_options(self): arglist = [ '--remote-ip', '10.10.0.0/24', @@ -212,13 +203,13 @@ def test_create_proto_option(self): }) arglist = [ '--proto', self._security_group_rule.protocol, - '--src-ip', self._security_group_rule.remote_ip_prefix, + '--remote-ip', self._security_group_rule.remote_ip_prefix, self._security_group.id, ] verifylist = [ ('proto', self._security_group_rule.protocol), ('protocol', None), - ('src_ip', self._security_group_rule.remote_ip_prefix), + ('remote_ip', self._security_group_rule.remote_ip_prefix), ('group', self._security_group.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -242,13 +233,13 @@ def test_create_protocol_any(self): }) arglist = [ '--proto', 'any', - '--src-ip', self._security_group_rule.remote_ip_prefix, + '--remote-ip', self._security_group_rule.remote_ip_prefix, self._security_group.id, ] verifylist = [ ('proto', 'any'), ('protocol', None), - ('src_ip', self._security_group_rule.remote_ip_prefix), + ('remote_ip', self._security_group_rule.remote_ip_prefix), ('group', self._security_group.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -269,19 +260,18 @@ def test_create_remote_group(self): self._setup_security_group_rule({ 'port_range_max': 22, 'port_range_min': 22, - 'remote_group_id': self._security_group.id, }) arglist = [ '--dst-port', str(self._security_group_rule.port_range_min), '--ingress', - '--src-group', self._security_group.name, + '--remote-group', self._security_group.name, self._security_group.id, ] verifylist = [ ('dst_port', (self._security_group_rule.port_range_min, self._security_group_rule.port_range_max)), ('ingress', True), - ('src_group', self._security_group.name), + ('remote_group', self._security_group.name), ('group', self._security_group.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -294,7 +284,7 @@ def test_create_remote_group(self): 'port_range_max': self._security_group_rule.port_range_max, 'port_range_min': self._security_group_rule.port_range_min, 'protocol': self._security_group_rule.protocol, - 'remote_group_id': self._security_group_rule.remote_group_id, + 'remote_group_id': self._security_group.id, 'security_group_id': self._security_group.id, }) self.assertEqual(self.expected_columns, columns) @@ -306,12 +296,12 @@ def test_create_source_group(self): }) arglist = [ '--ingress', - '--src-group', self._security_group.name, + '--remote-group', self._security_group.name, self._security_group.id, ] verifylist = [ ('ingress', True), - ('src_group', self._security_group.name), + ('remote_group', self._security_group.name), ('group', self._security_group.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -335,12 +325,12 @@ def test_create_source_ip(self): }) arglist = [ '--protocol', self._security_group_rule.protocol, - '--src-ip', self._security_group_rule.remote_ip_prefix, + '--remote-ip', self._security_group_rule.remote_ip_prefix, self._security_group.id, ] verifylist = [ ('protocol', self._security_group_rule.protocol), - ('src_ip', self._security_group_rule.remote_ip_prefix), + ('remote_ip', self._security_group_rule.remote_ip_prefix), ('group', self._security_group.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/releasenotes/notes/osc4-network-db2aed696d964ca6.yaml b/releasenotes/notes/osc4-network-db2aed696d964ca6.yaml new file mode 100644 index 0000000000..7ae300158f --- /dev/null +++ b/releasenotes/notes/osc4-network-db2aed696d964ca6.yaml @@ -0,0 +1,12 @@ +--- +upgrade: + - | + Remove deprecated ``port create|set`` options ``--device-id`` and ``--host-id``. + Use ``--device`` and ``--host`` options instead. + - | + Remove deprecated ``router set`` option ``--clear-routes``. + Use ``--no-route`` option instead. + - | + Remove deprecated ``security group rule create`` options ``--src-ip`` and + ``--src-group``. + Use ``--remote-ip`` and ``--remote-group`` options instead. From 6f1f44d422ed3f384ea6d4d729227277f0988b99 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 17 May 2019 16:02:13 -0500 Subject: [PATCH 2064/3095] Batch up minor cleanups for release Change-Id: Id45788e17c5388cee54e79cab1c120cfcc8f9f62 Signed-off-by: Dean Troyer --- .../tests/functional/network/v2/test_floating_ip.py | 2 +- .../tests/functional/network/v2/test_subnet_pool.py | 4 ++-- releasenotes/notes/bug-1633582-df2bee534c2da7fc.yaml | 4 ++-- releasenotes/notes/bug-27882-402ced7ffe930058.yaml | 2 +- releasenotes/source/pre_20_releases.rst | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/openstackclient/tests/functional/network/v2/test_floating_ip.py b/openstackclient/tests/functional/network/v2/test_floating_ip.py index 1d11fc5d2d..f189c2da6c 100644 --- a/openstackclient/tests/functional/network/v2/test_floating_ip.py +++ b/openstackclient/tests/functional/network/v2/test_floating_ip.py @@ -83,7 +83,7 @@ def setUpClass(cls): raise pass else: - # break and no longer retry if create sucessfully + # break and no longer retry if create successfully break @classmethod diff --git a/openstackclient/tests/functional/network/v2/test_subnet_pool.py b/openstackclient/tests/functional/network/v2/test_subnet_pool.py index 46aa6f1433..6be505292c 100644 --- a/openstackclient/tests/functional/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/functional/network/v2/test_subnet_pool.py @@ -266,7 +266,7 @@ def test_subnet_pool_set_show(self): # pool. The error appears to be in a lower layer, # once that is fixed add a test for subnet pool unset # --default-quota. - # The unset command of --pool-prefixes also doesnt work + # The unset command of --pool-prefixes also doesn't work # right now. It would be fixed in a separate patch once # the lower layer is fixed. # cmd_output = self.openstack( @@ -319,7 +319,7 @@ def _subnet_pool_create(self, cmd, name, is_type_ipv4=True): raise pass else: - # Break and no longer retry if create is sucessful + # Break and no longer retry if create is successful break return cmd_output, pool_prefix diff --git a/releasenotes/notes/bug-1633582-df2bee534c2da7fc.yaml b/releasenotes/notes/bug-1633582-df2bee534c2da7fc.yaml index 2cd3a07ced..5d195a393b 100644 --- a/releasenotes/notes/bug-1633582-df2bee534c2da7fc.yaml +++ b/releasenotes/notes/bug-1633582-df2bee534c2da7fc.yaml @@ -2,8 +2,8 @@ deprecations: - | ``volume transfer request accept`` has been changed to move the ``auth-key`` - positional argument to a requried option ``--auth-key``. This leaves - the transfer request ID as the only positional arguemnt, as per the + positional argument to a required option ``--auth-key``. This leaves + the transfer request ID as the only positional argument, as per the OpenStackClient command format. The old format is still functional, but is deprecated and will be removed in the next major release. fixes: diff --git a/releasenotes/notes/bug-27882-402ced7ffe930058.yaml b/releasenotes/notes/bug-27882-402ced7ffe930058.yaml index 6bd090aa5c..bf300de429 100644 --- a/releasenotes/notes/bug-27882-402ced7ffe930058.yaml +++ b/releasenotes/notes/bug-27882-402ced7ffe930058.yaml @@ -2,4 +2,4 @@ fixes: - | The ``--limit`` option of the ``image list`` command was previously ignored. - [Bug `https://storyboard.openstack.org/#!/story/2004314`] + [Bug `2004314 `_] diff --git a/releasenotes/source/pre_20_releases.rst b/releasenotes/source/pre_20_releases.rst index 06e7c667c2..049be75b5f 100644 --- a/releasenotes/source/pre_20_releases.rst +++ b/releasenotes/source/pre_20_releases.rst @@ -347,7 +347,7 @@ Pre-2.0 Releases ``--proto ICMP`` is selected. Bug `1443963 `_ -* Correctly pass ``--location`` arguemnt in ``image create`` command. +* Correctly pass ``--location`` argument in ``image create`` command. Bug `1445460 `_ * Correctly handle use of ``role`` commands for project admins. Using IDs will From e76e10c0bac9cf87c564a7f0201df189f7cd8b52 Mon Sep 17 00:00:00 2001 From: Sean McGinnis Date: Tue, 23 Oct 2018 11:34:07 -0500 Subject: [PATCH 2065/3095] Remove deprecated volume commands and args The following were deprecated over two years ago and can now be removed: * Remove ``backup`` commands in favor of ``volume backup`` * Remove ``snapshot`` commands in favor of ``volume snapshot`` * Remove ``volume create`` options ``--project``, ``--user`` and ``--multi-attach`` * Use of an auth-key positional argument in volume transfers * ``volume transfer request`` no longer accepts 'auth_key' as a positional arg, ``--auth-key`` is now required Internal (non-user-visible) * Rename backup.py to volume_backup.py for Volume v1 and v2, update tests These are backwards incompatible changes and will require a major version bump after they are merged. Change-Id: I94aa7a9824e44f9585ffb45e5e7637b9588539b4 Signed-off-by: Sean McGinnis Signed-off-by: Dean Troyer --- doc/source/cli/backwards-incompatible.rst | 26 +- doc/source/cli/command-objects/backup.rst | 137 ---- doc/source/cli/command-objects/snapshot.rst | 176 ----- .../{test_backup.py => test_volume_backup.py} | 4 +- ...st_snapshot.py => test_volume_snapshot.py} | 0 ...st_snapshot.py => test_volume_snapshot.py} | 2 +- .../tests/unit/volume/v1/test_snapshot.py | 580 -------------- .../unit/volume/v1/test_transfer_request.py | 20 - .../{test_backup.py => test_volume_backup.py} | 12 +- .../tests/unit/volume/v2/test_snapshot.py | 741 ------------------ .../unit/volume/v2/test_transfer_request.py | 30 +- .../tests/unit/volume/v2/test_volume.py | 34 - .../{test_backup.py => test_volume_backup.py} | 14 +- openstackclient/volume/v1/snapshot.py | 318 -------- .../volume/v1/{backup.py => volume_backup.py} | 85 -- .../volume/v1/volume_transfer_request.py | 22 +- openstackclient/volume/v2/snapshot.py | 351 --------- openstackclient/volume/v2/volume.py | 36 - .../volume/v2/{backup.py => volume_backup.py} | 85 -- .../volume/v2/volume_transfer_request.py | 23 +- .../notes/osc4-volume-470422e5a453310e.yaml | 14 + setup.cfg | 48 +- 22 files changed, 71 insertions(+), 2687 deletions(-) delete mode 100644 doc/source/cli/command-objects/backup.rst delete mode 100644 doc/source/cli/command-objects/snapshot.rst rename openstackclient/tests/functional/volume/v2/{test_backup.py => test_volume_backup.py} (93%) rename openstackclient/tests/functional/volume/v2/{test_snapshot.py => test_volume_snapshot.py} (100%) rename openstackclient/tests/functional/volume/v3/{test_snapshot.py => test_volume_snapshot.py} (89%) delete mode 100644 openstackclient/tests/unit/volume/v1/test_snapshot.py rename openstackclient/tests/unit/volume/v1/{test_backup.py => test_volume_backup.py} (96%) delete mode 100644 openstackclient/tests/unit/volume/v2/test_snapshot.py rename openstackclient/tests/unit/volume/v2/{test_backup.py => test_volume_backup.py} (97%) delete mode 100644 openstackclient/volume/v1/snapshot.py rename openstackclient/volume/v1/{backup.py => volume_backup.py} (73%) delete mode 100644 openstackclient/volume/v2/snapshot.py rename openstackclient/volume/v2/{backup.py => volume_backup.py} (80%) create mode 100644 releasenotes/notes/osc4-volume-470422e5a453310e.yaml diff --git a/doc/source/cli/backwards-incompatible.rst b/doc/source/cli/backwards-incompatible.rst index cbf1823c13..adb638d126 100644 --- a/doc/source/cli/backwards-incompatible.rst +++ b/doc/source/cli/backwards-incompatible.rst @@ -73,12 +73,28 @@ Release 4.0 * Removed in: 4.0 * Commit: https://review.opendev.org/613644 -.. 1. Change ``volume transfer request accept`` to use new option ``--auth-key`` -.. rather than a second positional argument. +10. Remove ``backup`` commands. + Use ``volume backup`` commands instead. -.. * As of: 4.0 -.. * Remove in: <5.0> -.. * Commit: + * Removed in: 4.0 + * Commit: https://review.opendev.org/612751 + +11. Remove ``snapshot`` commands. + Use ``volume snapshot`` commands instead. + + * Removed in: 4.0 + * Commit: https://review.opendev.org/612751 + +12. Remove ``volume create`` options ``--project``, ``--user``, ``--multi-attach``. + + * Removed in: 4.0 + * Commit: https://review.opendev.org/612751 + +13. Change ``volume transfer request accept`` to use new option ``--auth-key`` + rather than a second positional argument. + + * Removed in: 4.0 + * Commit: https://review.opendev.org/612751 Release 3.12 ------------ diff --git a/doc/source/cli/command-objects/backup.rst b/doc/source/cli/command-objects/backup.rst deleted file mode 100644 index f892327695..0000000000 --- a/doc/source/cli/command-objects/backup.rst +++ /dev/null @@ -1,137 +0,0 @@ -====== -backup -====== - -Block Storage v1, v2 - -backup create -------------- - -Create new backup -(Deprecated, please use ``volume backup create`` instead) - -.. program:: backup create -.. code:: bash - - openstack backup create - [--container ] - [--name ] - [--description ] - [--snapshot ] - [--force] - [--incremental] - - -.. option:: --container - - Optional backup container name - -.. option:: --name - - Name of the backup - -.. option:: --description - - Description of the backup - -.. option:: --snapshot - - Snapshot to backup (name or ID) - - *Volume version 2 only* - -.. option:: --force - - Allow to back up an in-use volume - - *Volume version 2 only* - -.. option:: --incremental - - Perform an incremental backup - - *Volume version 2 only* - -.. _backup_create-backup: -.. describe:: - - Volume to backup (name or ID) - -backup delete -------------- - -Delete backup(s) -(Deprecated, please use ``volume backup delete`` instead) - -.. program:: backup delete -.. code:: bash - - openstack backup delete - [--force] - [ ...] - -.. option:: --force - - Allow delete in state other than error or available - - *Volume version 2 only* - -.. _backup_delete-backup: -.. describe:: - - Backup(s) to delete (name or ID) - -backup list ------------ - -List backups -(Deprecated, please use ``volume backup list`` instead) - -.. program:: backup list -.. code:: bash - - openstack backup list - -.. _backup_list-backup: -.. option:: --long - - List additional fields in output - -backup restore --------------- - -Restore backup -(Deprecated, please use ``volume backup restore`` instead) - -.. program:: backup restore -.. code:: bash - - openstack backup restore - - - -.. _backup_restore-backup: -.. describe:: - - Backup to restore (name or ID) - -.. describe:: - - Volume to restore to (name or ID) - -backup show ------------ - -Display backup details -(Deprecated, please use ``volume backup show`` instead) - -.. program:: backup show -.. code:: bash - - openstack backup show - - -.. _backup_show-backup: -.. describe:: - - Backup to display (name or ID) diff --git a/doc/source/cli/command-objects/snapshot.rst b/doc/source/cli/command-objects/snapshot.rst deleted file mode 100644 index fc516067cb..0000000000 --- a/doc/source/cli/command-objects/snapshot.rst +++ /dev/null @@ -1,176 +0,0 @@ -======== -snapshot -======== - -Block Storage v1, v2 - -snapshot create ---------------- - -Create new snapshot -(Deprecated, please use ``volume snapshot create`` instead) - -.. program:: snapshot create -.. code:: bash - - openstack snapshot create - [--name ] - [--description ] - [--force] - [--property [...] ] - - -.. option:: --name - - Name of the snapshot - -.. option:: --description - - Description of the snapshot - -.. option:: --force - - Create a snapshot attached to an instance. Default is False - -.. option:: --property - - Set a property to this snapshot (repeat option to set multiple properties) - - *Volume version 2 only* - -.. _snapshot_create-snapshot: -.. describe:: - - Volume to snapshot (name or ID) - -snapshot delete ---------------- - -Delete snapshot(s) -(Deprecated, please use ``volume snapshot delete`` instead) - -.. program:: snapshot delete -.. code:: bash - - openstack snapshot delete - [ ...] - -.. _snapshot_delete-snapshot: -.. describe:: - - Snapshot(s) to delete (name or ID) - -snapshot list -------------- - -List snapshots -(Deprecated, please use ``volume snapshot list`` instead) - -.. program:: snapshot list -.. code:: bash - - openstack snapshot list - [--all-projects] - [--long] - [--limit ] - [--marker ] - -.. option:: --all-projects - - Include all projects (admin only) - -.. option:: --long - - List additional fields in output - -.. option:: --limit - - Maximum number of snapshots to display - - *Volume version 2 only* - -.. option:: --marker - - The last snapshot ID of the previous page - - *Volume version 2 only* - -snapshot set ------------- - -Set snapshot properties -(Deprecated, please use ``volume snapshot set`` instead) - -.. program:: snapshot set -.. code:: bash - - openstack snapshot set - [--name ] - [--description ] - [--property [...] ] - [--state ] - - -.. _snapshot_restore-snapshot: -.. option:: --name - - New snapshot name - -.. option:: --description - - New snapshot description - -.. option:: --property - - Property to add or modify for this snapshot (repeat option to set multiple properties) - -.. option:: --state - - New snapshot state. - ("available", "error", "creating", "deleting", or "error_deleting") (admin only) - (This option simply changes the state of the snapshot in the database with - no regard to actual status, exercise caution when using) - - *Volume version 2 only* - -.. describe:: - - Snapshot to modify (name or ID) - -snapshot show -------------- - -Display snapshot details -(Deprecated, please use ``volume snapshot show`` instead) - -.. program:: snapshot show -.. code:: bash - - openstack snapshot show - - -.. _snapshot_show-snapshot: -.. describe:: - - Snapshot to display (name or ID) - -snapshot unset --------------- - -Unset snapshot properties -(Deprecated, please use ``volume snapshot unset`` instead) - -.. program:: snapshot unset -.. code:: bash - - openstack snapshot unset - [--property ] - - -.. option:: --property - - Property to remove from snapshot (repeat option to remove multiple properties) - -.. describe:: - - Snapshot to modify (name or ID) diff --git a/openstackclient/tests/functional/volume/v2/test_backup.py b/openstackclient/tests/functional/volume/v2/test_volume_backup.py similarity index 93% rename from openstackclient/tests/functional/volume/v2/test_backup.py rename to openstackclient/tests/functional/volume/v2/test_volume_backup.py index 8f5c032c0a..6868bd40d7 100644 --- a/openstackclient/tests/functional/volume/v2/test_backup.py +++ b/openstackclient/tests/functional/volume/v2/test_volume_backup.py @@ -46,14 +46,14 @@ def test_volume_backup_restore(self): 'volume backup create -f json ' + vol_id )) - self.wait_for_status("backup", backup['id'], "available") + self.wait_for_status("volume backup", backup['id'], "available") # restore the backup backup_restored = json.loads(self.openstack( 'volume backup restore -f json %s %s' % (backup['id'], vol_id))) self.assertEqual(backup_restored['backup_id'], backup['id']) - self.wait_for_status("backup", backup['id'], "available") + self.wait_for_status("volume backup", backup['id'], "available") self.wait_for_status("volume", backup_restored['volume_id'], "available") self.addCleanup(self.openstack, 'volume delete %s' % vol_id) diff --git a/openstackclient/tests/functional/volume/v2/test_snapshot.py b/openstackclient/tests/functional/volume/v2/test_volume_snapshot.py similarity index 100% rename from openstackclient/tests/functional/volume/v2/test_snapshot.py rename to openstackclient/tests/functional/volume/v2/test_volume_snapshot.py diff --git a/openstackclient/tests/functional/volume/v3/test_snapshot.py b/openstackclient/tests/functional/volume/v3/test_volume_snapshot.py similarity index 89% rename from openstackclient/tests/functional/volume/v3/test_snapshot.py rename to openstackclient/tests/functional/volume/v3/test_volume_snapshot.py index 38e0563a7c..28eee6d24a 100644 --- a/openstackclient/tests/functional/volume/v3/test_snapshot.py +++ b/openstackclient/tests/functional/volume/v3/test_volume_snapshot.py @@ -10,7 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. -from openstackclient.tests.functional.volume.v2 import test_snapshot as v2 +from openstackclient.tests.functional.volume.v2 import test_volume_snapshot as v2 # noqa from openstackclient.tests.functional.volume.v3 import common diff --git a/openstackclient/tests/unit/volume/v1/test_snapshot.py b/openstackclient/tests/unit/volume/v1/test_snapshot.py deleted file mode 100644 index 70b55ce235..0000000000 --- a/openstackclient/tests/unit/volume/v1/test_snapshot.py +++ /dev/null @@ -1,580 +0,0 @@ -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - -import mock -from mock import call - -from osc_lib import exceptions -from osc_lib import utils - -from openstackclient.tests.unit import utils as tests_utils -from openstackclient.tests.unit.volume.v1 import fakes as volume_fakes -from openstackclient.volume.v1 import volume_snapshot - - -class TestSnapshot(volume_fakes.TestVolumev1): - - def setUp(self): - super(TestSnapshot, self).setUp() - - self.snapshots_mock = self.app.client_manager.volume.volume_snapshots - self.snapshots_mock.reset_mock() - self.volumes_mock = self.app.client_manager.volume.volumes - self.volumes_mock.reset_mock() - - -class TestSnapshotCreate(TestSnapshot): - - columns = ( - 'created_at', - 'display_description', - 'display_name', - 'id', - 'properties', - 'size', - 'status', - 'volume_id', - ) - - def setUp(self): - super(TestSnapshotCreate, self).setUp() - - self.volume = volume_fakes.FakeVolume.create_one_volume() - self.new_snapshot = volume_fakes.FakeSnapshot.create_one_snapshot( - attrs={'volume_id': self.volume.id}) - - self.data = ( - self.new_snapshot.created_at, - self.new_snapshot.display_description, - self.new_snapshot.display_name, - self.new_snapshot.id, - utils.format_dict(self.new_snapshot.metadata), - self.new_snapshot.size, - self.new_snapshot.status, - self.new_snapshot.volume_id, - ) - - self.volumes_mock.get.return_value = self.volume - self.snapshots_mock.create.return_value = self.new_snapshot - # Get the command object to test - self.cmd = volume_snapshot.CreateVolumeSnapshot(self.app, None) - - def test_snapshot_create(self): - arglist = [ - "--volume", self.new_snapshot.volume_id, - "--description", self.new_snapshot.display_description, - "--force", - self.new_snapshot.display_name, - ] - verifylist = [ - ("volume", self.new_snapshot.volume_id), - ("description", self.new_snapshot.display_description), - ("force", True), - ("snapshot_name", self.new_snapshot.display_name), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.snapshots_mock.create.assert_called_with( - self.new_snapshot.volume_id, - True, - self.new_snapshot.display_name, - self.new_snapshot.display_description, - ) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) - - def test_snapshot_create_without_name(self): - arglist = [ - "--volume", self.new_snapshot.volume_id, - ] - verifylist = [ - ("volume", self.new_snapshot.volume_id), - ] - self.assertRaises( - tests_utils.ParserException, - self.check_parser, - self.cmd, - arglist, - verifylist, - ) - - def test_snapshot_create_without_volume(self): - arglist = [ - "--description", self.new_snapshot.display_description, - "--force", - self.new_snapshot.display_name - ] - verifylist = [ - ("description", self.new_snapshot.display_description), - ("force", True), - ("snapshot_name", self.new_snapshot.display_name) - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.volumes_mock.get.assert_called_once_with( - self.new_snapshot.display_name) - self.snapshots_mock.create.assert_called_once_with( - self.new_snapshot.volume_id, - True, - self.new_snapshot.display_name, - self.new_snapshot.display_description, - ) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) - - -class TestSnapshotDelete(TestSnapshot): - - snapshots = volume_fakes.FakeSnapshot.create_snapshots(count=2) - - def setUp(self): - super(TestSnapshotDelete, self).setUp() - - self.snapshots_mock.get = ( - volume_fakes.FakeSnapshot.get_snapshots(self.snapshots)) - self.snapshots_mock.delete.return_value = None - - # Get the command object to mock - self.cmd = volume_snapshot.DeleteVolumeSnapshot(self.app, None) - - def test_snapshot_delete(self): - arglist = [ - self.snapshots[0].id - ] - verifylist = [ - ("snapshots", [self.snapshots[0].id]) - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - self.snapshots_mock.delete.assert_called_with( - self.snapshots[0].id) - self.assertIsNone(result) - - def test_delete_multiple_snapshots(self): - arglist = [] - for s in self.snapshots: - arglist.append(s.id) - verifylist = [ - ('snapshots', arglist), - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) - - calls = [] - for s in self.snapshots: - calls.append(call(s.id)) - self.snapshots_mock.delete.assert_has_calls(calls) - self.assertIsNone(result) - - def test_delete_multiple_snapshots_with_exception(self): - arglist = [ - self.snapshots[0].id, - 'unexist_snapshot', - ] - verifylist = [ - ('snapshots', arglist), - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - find_mock_result = [self.snapshots[0], exceptions.CommandError] - with mock.patch.object(utils, 'find_resource', - side_effect=find_mock_result) as find_mock: - try: - self.cmd.take_action(parsed_args) - self.fail('CommandError should be raised.') - except exceptions.CommandError as e: - self.assertEqual('1 of 2 snapshots failed to delete.', - str(e)) - - find_mock.assert_any_call( - self.snapshots_mock, self.snapshots[0].id) - find_mock.assert_any_call(self.snapshots_mock, 'unexist_snapshot') - - self.assertEqual(2, find_mock.call_count) - self.snapshots_mock.delete.assert_called_once_with( - self.snapshots[0].id - ) - - -class TestSnapshotList(TestSnapshot): - - volume = volume_fakes.FakeVolume.create_one_volume() - snapshots = volume_fakes.FakeSnapshot.create_snapshots( - attrs={'volume_id': volume.display_name}, count=3) - - columns = [ - "ID", - "Name", - "Description", - "Status", - "Size" - ] - columns_long = columns + [ - "Created At", - "Volume", - "Properties" - ] - - data = [] - for s in snapshots: - data.append(( - s.id, - s.display_name, - s.display_description, - s.status, - s.size, - )) - data_long = [] - for s in snapshots: - data_long.append(( - s.id, - s.display_name, - s.display_description, - s.status, - s.size, - s.created_at, - s.volume_id, - utils.format_dict(s.metadata), - )) - - def setUp(self): - super(TestSnapshotList, self).setUp() - - self.volumes_mock.list.return_value = [self.volume] - self.volumes_mock.get.return_value = self.volume - self.snapshots_mock.list.return_value = self.snapshots - # Get the command to test - self.cmd = volume_snapshot.ListVolumeSnapshot(self.app, None) - - def test_snapshot_list_without_options(self): - arglist = [] - verifylist = [ - ('all_projects', False), - ("long", False) - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.snapshots_mock.list.assert_called_once_with( - search_opts={ - 'all_tenants': False, - 'display_name': None, - 'status': None, - 'volume_id': None - } - ) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) - - def test_snapshot_list_with_long(self): - arglist = [ - "--long", - ] - verifylist = [ - ("long", True), - ('all_projects', False), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.snapshots_mock.list.assert_called_once_with( - search_opts={ - 'all_tenants': False, - 'display_name': None, - 'status': None, - 'volume_id': None - } - ) - self.assertEqual(self.columns_long, columns) - self.assertEqual(self.data_long, list(data)) - - def test_snapshot_list_name_option(self): - arglist = [ - '--name', self.snapshots[0].display_name, - ] - verifylist = [ - ('all_projects', False), - ('long', False), - ('name', self.snapshots[0].display_name), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.snapshots_mock.list.assert_called_once_with( - search_opts={ - 'all_tenants': False, - 'display_name': self.snapshots[0].display_name, - 'status': None, - 'volume_id': None - } - ) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) - - def test_snapshot_list_status_option(self): - arglist = [ - '--status', self.snapshots[0].status, - ] - verifylist = [ - ('all_projects', False), - ('long', False), - ('status', self.snapshots[0].status), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.snapshots_mock.list.assert_called_once_with( - search_opts={ - 'all_tenants': False, - 'display_name': None, - 'status': self.snapshots[0].status, - 'volume_id': None - } - ) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) - - def test_snapshot_list_volumeid_option(self): - arglist = [ - '--volume', self.volume.id, - ] - verifylist = [ - ('all_projects', False), - ('long', False), - ('volume', self.volume.id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.snapshots_mock.list.assert_called_once_with( - search_opts={ - 'all_tenants': False, - 'display_name': None, - 'status': None, - 'volume_id': self.volume.id - } - ) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) - - def test_snapshot_list_all_projects(self): - arglist = [ - '--all-projects', - ] - verifylist = [ - ('long', False), - ('all_projects', True) - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.snapshots_mock.list.assert_called_once_with( - search_opts={ - 'all_tenants': True, - 'display_name': None, - 'status': None, - 'volume_id': None - } - ) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) - - -class TestSnapshotSet(TestSnapshot): - - snapshot = volume_fakes.FakeSnapshot.create_one_snapshot() - - def setUp(self): - super(TestSnapshotSet, self).setUp() - - self.snapshots_mock.get.return_value = self.snapshot - self.snapshots_mock.set_metadata.return_value = None - # Get the command object to mock - self.cmd = volume_snapshot.SetVolumeSnapshot(self.app, None) - - def test_snapshot_set_all(self): - arglist = [ - "--name", "new_snapshot", - "--description", "new_description", - "--property", "foo_1=foo_1", - "--property", "foo_2=foo_2", - "--no-property", - self.snapshot.id, - ] - new_property = {"foo_1": "foo_1", "foo_2": "foo_2"} - verifylist = [ - ("name", "new_snapshot"), - ("description", "new_description"), - ("property", new_property), - ("no_property", True), - ("snapshot", self.snapshot.id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - kwargs = { - "display_name": "new_snapshot", - "display_description": "new_description", - } - self.snapshot.update.assert_called_with(**kwargs) - self.snapshots_mock.delete_metadata.assert_called_with( - self.snapshot.id, ["foo"] - ) - self.snapshots_mock.set_metadata.assert_called_with( - self.snapshot.id, {"foo_2": "foo_2", "foo_1": "foo_1"} - ) - self.assertIsNone(result) - - def test_snapshot_set_nothing(self): - arglist = [ - self.snapshot.id, - ] - verifylist = [ - ("snapshot", self.snapshot.id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - self.assertIsNone(result) - - def test_snapshot_set_fail(self): - self.snapshots_mock.set_metadata.side_effect = ( - exceptions.CommandError()) - arglist = [ - "--name", "new_snapshot", - "--description", "new_description", - "--property", "x=y", - "--property", "foo=foo", - self.snapshot.id, - ] - new_property = {"x": "y", "foo": "foo"} - verifylist = [ - ("name", "new_snapshot"), - ("description", "new_description"), - ("property", new_property), - ("snapshot", self.snapshot.id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - self.assertRaises(exceptions.CommandError, - self.cmd.take_action, parsed_args) - - -class TestSnapshotShow(TestSnapshot): - - columns = ( - 'created_at', - 'display_description', - 'display_name', - 'id', - 'properties', - 'size', - 'status', - 'volume_id', - ) - - def setUp(self): - super(TestSnapshotShow, self).setUp() - - self.snapshot = volume_fakes.FakeSnapshot.create_one_snapshot() - - self.data = ( - self.snapshot.created_at, - self.snapshot.display_description, - self.snapshot.display_name, - self.snapshot.id, - utils.format_dict(self.snapshot.metadata), - self.snapshot.size, - self.snapshot.status, - self.snapshot.volume_id, - ) - - self.snapshots_mock.get.return_value = self.snapshot - # Get the command object to test - self.cmd = volume_snapshot.ShowVolumeSnapshot(self.app, None) - - def test_snapshot_show(self): - arglist = [ - self.snapshot.id - ] - verifylist = [ - ("snapshot", self.snapshot.id) - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - self.snapshots_mock.get.assert_called_with(self.snapshot.id) - - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) - - -class TestSnapshotUnset(TestSnapshot): - - snapshot = volume_fakes.FakeSnapshot.create_one_snapshot() - - def setUp(self): - super(TestSnapshotUnset, self).setUp() - - self.snapshots_mock.get.return_value = self.snapshot - self.snapshots_mock.delete_metadata.return_value = None - # Get the command object to mock - self.cmd = volume_snapshot.UnsetVolumeSnapshot(self.app, None) - - def test_snapshot_unset(self): - arglist = [ - "--property", "foo", - self.snapshot.id, - ] - verifylist = [ - ("property", ["foo"]), - ("snapshot", self.snapshot.id), - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - self.snapshots_mock.delete_metadata.assert_called_with( - self.snapshot.id, ["foo"] - ) - self.assertIsNone(result) - - def test_snapshot_unset_nothing(self): - arglist = [ - self.snapshot.id, - ] - verifylist = [ - ("snapshot", self.snapshot.id), - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - self.assertIsNone(result) diff --git a/openstackclient/tests/unit/volume/v1/test_transfer_request.py b/openstackclient/tests/unit/volume/v1/test_transfer_request.py index 4c013dc007..680561d522 100644 --- a/openstackclient/tests/unit/volume/v1/test_transfer_request.py +++ b/openstackclient/tests/unit/volume/v1/test_transfer_request.py @@ -85,26 +85,6 @@ def test_transfer_accept(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) - def test_transfer_accept_deprecated(self): - arglist = [ - self.volume_transfer.id, - 'key_value', - ] - verifylist = [ - ('transfer_request', self.volume_transfer.id), - ('old_auth_key', 'key_value'), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.transfer_mock.accept.assert_called_once_with( - self.volume_transfer.id, - 'key_value', - ) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) - def test_transfer_accept_no_option(self): arglist = [ self.volume_transfer.id, diff --git a/openstackclient/tests/unit/volume/v1/test_backup.py b/openstackclient/tests/unit/volume/v1/test_volume_backup.py similarity index 96% rename from openstackclient/tests/unit/volume/v1/test_backup.py rename to openstackclient/tests/unit/volume/v1/test_volume_backup.py index 1097d3f121..22c1f8cf58 100644 --- a/openstackclient/tests/unit/volume/v1/test_backup.py +++ b/openstackclient/tests/unit/volume/v1/test_volume_backup.py @@ -19,7 +19,7 @@ from osc_lib import utils from openstackclient.tests.unit.volume.v1 import fakes as volume_fakes -from openstackclient.volume.v1 import backup +from openstackclient.volume.v1 import volume_backup class TestBackup(volume_fakes.TestVolumev1): @@ -74,7 +74,7 @@ def setUp(self): self.backups_mock.create.return_value = self.new_backup # Get the command object to test - self.cmd = backup.CreateVolumeBackup(self.app, None) + self.cmd = volume_backup.CreateVolumeBackup(self.app, None) def test_backup_create(self): arglist = [ @@ -139,7 +139,7 @@ def setUp(self): self.backups_mock.delete.return_value = None # Get the command object to mock - self.cmd = backup.DeleteVolumeBackup(self.app, None) + self.cmd = volume_backup.DeleteVolumeBackup(self.app, None) def test_backup_delete(self): arglist = [ @@ -251,7 +251,7 @@ def setUp(self): self.backups_mock.list.return_value = self.backups self.volumes_mock.get.return_value = self.volume # Get the command to test - self.cmd = backup.ListVolumeBackup(self.app, None) + self.cmd = volume_backup.ListVolumeBackup(self.app, None) def test_backup_list_without_options(self): arglist = [] @@ -325,7 +325,7 @@ def setUp(self): self.volumes_mock.get.return_value = self.volume self.restores_mock.restore.return_value = None # Get the command object to mock - self.cmd = backup.RestoreVolumeBackup(self.app, None) + self.cmd = volume_backup.RestoreVolumeBackup(self.app, None) def test_backup_restore(self): arglist = [ @@ -376,7 +376,7 @@ def setUp(self): ) self.backups_mock.get.return_value = self.backup # Get the command object to test - self.cmd = backup.ShowVolumeBackup(self.app, None) + self.cmd = volume_backup.ShowVolumeBackup(self.app, None) def test_backup_show(self): arglist = [ diff --git a/openstackclient/tests/unit/volume/v2/test_snapshot.py b/openstackclient/tests/unit/volume/v2/test_snapshot.py deleted file mode 100644 index e8f4ae5a4a..0000000000 --- a/openstackclient/tests/unit/volume/v2/test_snapshot.py +++ /dev/null @@ -1,741 +0,0 @@ -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - -import argparse - -import mock -from mock import call -from osc_lib import exceptions -from osc_lib import utils - -from openstackclient.tests.unit.identity.v3 import fakes as project_fakes -from openstackclient.tests.unit import utils as tests_utils -from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes -from openstackclient.volume.v2 import volume_snapshot - - -class TestSnapshot(volume_fakes.TestVolume): - - def setUp(self): - super(TestSnapshot, self).setUp() - - self.snapshots_mock = self.app.client_manager.volume.volume_snapshots - self.snapshots_mock.reset_mock() - self.volumes_mock = self.app.client_manager.volume.volumes - self.volumes_mock.reset_mock() - self.project_mock = self.app.client_manager.identity.projects - self.project_mock.reset_mock() - - -class TestSnapshotCreate(TestSnapshot): - - columns = ( - 'created_at', - 'description', - 'id', - 'name', - 'properties', - 'size', - 'status', - 'volume_id', - ) - - def setUp(self): - super(TestSnapshotCreate, self).setUp() - - self.volume = volume_fakes.FakeVolume.create_one_volume() - self.new_snapshot = volume_fakes.FakeSnapshot.create_one_snapshot( - attrs={'volume_id': self.volume.id}) - - self.data = ( - self.new_snapshot.created_at, - self.new_snapshot.description, - self.new_snapshot.id, - self.new_snapshot.name, - utils.format_dict(self.new_snapshot.metadata), - self.new_snapshot.size, - self.new_snapshot.status, - self.new_snapshot.volume_id, - ) - - self.volumes_mock.get.return_value = self.volume - self.snapshots_mock.create.return_value = self.new_snapshot - self.snapshots_mock.manage.return_value = self.new_snapshot - # Get the command object to test - self.cmd = volume_snapshot.CreateVolumeSnapshot(self.app, None) - - def test_snapshot_create(self): - arglist = [ - "--volume", self.new_snapshot.volume_id, - "--description", self.new_snapshot.description, - "--force", - '--property', 'Alpha=a', - '--property', 'Beta=b', - self.new_snapshot.name, - ] - verifylist = [ - ("volume", self.new_snapshot.volume_id), - ("description", self.new_snapshot.description), - ("force", True), - ('property', {'Alpha': 'a', 'Beta': 'b'}), - ("snapshot_name", self.new_snapshot.name), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.snapshots_mock.create.assert_called_with( - self.new_snapshot.volume_id, - force=True, - name=self.new_snapshot.name, - description=self.new_snapshot.description, - metadata={'Alpha': 'a', 'Beta': 'b'}, - ) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) - - def test_snapshot_create_without_name(self): - arglist = [ - "--volume", self.new_snapshot.volume_id, - ] - verifylist = [ - ("volume", self.new_snapshot.volume_id), - ] - self.assertRaises( - tests_utils.ParserException, - self.check_parser, - self.cmd, - arglist, - verifylist, - ) - - def test_snapshot_create_without_volume(self): - arglist = [ - "--description", self.new_snapshot.description, - "--force", - self.new_snapshot.name - ] - verifylist = [ - ("description", self.new_snapshot.description), - ("force", True), - ("snapshot_name", self.new_snapshot.name) - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.volumes_mock.get.assert_called_once_with( - self.new_snapshot.name) - self.snapshots_mock.create.assert_called_once_with( - self.new_snapshot.volume_id, - force=True, - name=self.new_snapshot.name, - description=self.new_snapshot.description, - metadata=None, - ) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) - - def test_snapshot_create_with_remote_source(self): - arglist = [ - '--remote-source', 'source-name=test_source_name', - '--remote-source', 'source-id=test_source_id', - '--volume', self.new_snapshot.volume_id, - self.new_snapshot.name, - ] - ref_dict = {'source-name': 'test_source_name', - 'source-id': 'test_source_id'} - verifylist = [ - ('remote_source', ref_dict), - ('volume', self.new_snapshot.volume_id), - ("snapshot_name", self.new_snapshot.name), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.snapshots_mock.manage.assert_called_with( - volume_id=self.new_snapshot.volume_id, - ref=ref_dict, - name=self.new_snapshot.name, - description=None, - metadata=None, - ) - self.snapshots_mock.create.assert_not_called() - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) - - -class TestSnapshotDelete(TestSnapshot): - - snapshots = volume_fakes.FakeSnapshot.create_snapshots(count=2) - - def setUp(self): - super(TestSnapshotDelete, self).setUp() - - self.snapshots_mock.get = ( - volume_fakes.FakeSnapshot.get_snapshots(self.snapshots)) - self.snapshots_mock.delete.return_value = None - - # Get the command object to mock - self.cmd = volume_snapshot.DeleteVolumeSnapshot(self.app, None) - - def test_snapshot_delete(self): - arglist = [ - self.snapshots[0].id - ] - verifylist = [ - ("snapshots", [self.snapshots[0].id]) - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - self.snapshots_mock.delete.assert_called_with( - self.snapshots[0].id, False) - self.assertIsNone(result) - - def test_snapshot_delete_with_force(self): - arglist = [ - '--force', - self.snapshots[0].id - ] - verifylist = [ - ('force', True), - ("snapshots", [self.snapshots[0].id]) - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - self.snapshots_mock.delete.assert_called_with( - self.snapshots[0].id, True) - self.assertIsNone(result) - - def test_delete_multiple_snapshots(self): - arglist = [] - for s in self.snapshots: - arglist.append(s.id) - verifylist = [ - ('snapshots', arglist), - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) - - calls = [] - for s in self.snapshots: - calls.append(call(s.id, False)) - self.snapshots_mock.delete.assert_has_calls(calls) - self.assertIsNone(result) - - def test_delete_multiple_snapshots_with_exception(self): - arglist = [ - self.snapshots[0].id, - 'unexist_snapshot', - ] - verifylist = [ - ('snapshots', arglist), - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - find_mock_result = [self.snapshots[0], exceptions.CommandError] - with mock.patch.object(utils, 'find_resource', - side_effect=find_mock_result) as find_mock: - try: - self.cmd.take_action(parsed_args) - self.fail('CommandError should be raised.') - except exceptions.CommandError as e: - self.assertEqual('1 of 2 snapshots failed to delete.', - str(e)) - - find_mock.assert_any_call( - self.snapshots_mock, self.snapshots[0].id) - find_mock.assert_any_call(self.snapshots_mock, 'unexist_snapshot') - - self.assertEqual(2, find_mock.call_count) - self.snapshots_mock.delete.assert_called_once_with( - self.snapshots[0].id, False - ) - - -class TestSnapshotList(TestSnapshot): - - volume = volume_fakes.FakeVolume.create_one_volume() - project = project_fakes.FakeProject.create_one_project() - snapshots = volume_fakes.FakeSnapshot.create_snapshots( - attrs={'volume_id': volume.name}, count=3) - - columns = [ - "ID", - "Name", - "Description", - "Status", - "Size" - ] - columns_long = columns + [ - "Created At", - "Volume", - "Properties" - ] - - data = [] - for s in snapshots: - data.append(( - s.id, - s.name, - s.description, - s.status, - s.size, - )) - data_long = [] - for s in snapshots: - data_long.append(( - s.id, - s.name, - s.description, - s.status, - s.size, - s.created_at, - s.volume_id, - utils.format_dict(s.metadata), - )) - - def setUp(self): - super(TestSnapshotList, self).setUp() - - self.volumes_mock.list.return_value = [self.volume] - self.volumes_mock.get.return_value = self.volume - self.project_mock.get.return_value = self.project - self.snapshots_mock.list.return_value = self.snapshots - # Get the command to test - self.cmd = volume_snapshot.ListVolumeSnapshot(self.app, None) - - def test_snapshot_list_without_options(self): - arglist = [] - verifylist = [ - ('all_projects', False), - ('long', False) - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.snapshots_mock.list.assert_called_once_with( - limit=None, marker=None, - search_opts={ - 'all_tenants': False, - 'name': None, - 'status': None, - 'project_id': None, - 'volume_id': None - } - ) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) - - def test_snapshot_list_with_options(self): - arglist = [ - "--long", - "--limit", "2", - "--project", self.project.id, - "--marker", self.snapshots[0].id, - ] - verifylist = [ - ("long", True), - ("limit", 2), - ("project", self.project.id), - ("marker", self.snapshots[0].id), - ('all_projects', False), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.snapshots_mock.list.assert_called_once_with( - limit=2, - marker=self.snapshots[0].id, - search_opts={ - 'all_tenants': True, - 'project_id': self.project.id, - 'name': None, - 'status': None, - 'volume_id': None - } - ) - self.assertEqual(self.columns_long, columns) - self.assertEqual(self.data_long, list(data)) - - def test_snapshot_list_all_projects(self): - arglist = [ - '--all-projects', - ] - verifylist = [ - ('long', False), - ('all_projects', True) - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.snapshots_mock.list.assert_called_once_with( - limit=None, marker=None, - search_opts={ - 'all_tenants': True, - 'name': None, - 'status': None, - 'project_id': None, - 'volume_id': None - } - ) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) - - def test_snapshot_list_name_option(self): - arglist = [ - '--name', self.snapshots[0].name, - ] - verifylist = [ - ('all_projects', False), - ('long', False), - ('name', self.snapshots[0].name), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.snapshots_mock.list.assert_called_once_with( - limit=None, marker=None, - search_opts={ - 'all_tenants': False, - 'name': self.snapshots[0].name, - 'status': None, - 'project_id': None, - 'volume_id': None - } - ) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) - - def test_snapshot_list_status_option(self): - arglist = [ - '--status', self.snapshots[0].status, - ] - verifylist = [ - ('all_projects', False), - ('long', False), - ('status', self.snapshots[0].status), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.snapshots_mock.list.assert_called_once_with( - limit=None, marker=None, - search_opts={ - 'all_tenants': False, - 'name': None, - 'status': self.snapshots[0].status, - 'project_id': None, - 'volume_id': None - } - ) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) - - def test_snapshot_list_volumeid_option(self): - arglist = [ - '--volume', self.volume.id, - ] - verifylist = [ - ('all_projects', False), - ('long', False), - ('volume', self.volume.id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.snapshots_mock.list.assert_called_once_with( - limit=None, marker=None, - search_opts={ - 'all_tenants': False, - 'name': None, - 'status': None, - 'project_id': None, - 'volume_id': self.volume.id - } - ) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) - - def test_snapshot_list_negative_limit(self): - arglist = [ - "--limit", "-2", - ] - verifylist = [ - ("limit", -2), - ] - self.assertRaises(argparse.ArgumentTypeError, self.check_parser, - self.cmd, arglist, verifylist) - - -class TestSnapshotSet(TestSnapshot): - - snapshot = volume_fakes.FakeSnapshot.create_one_snapshot() - - def setUp(self): - super(TestSnapshotSet, self).setUp() - - self.snapshots_mock.get.return_value = self.snapshot - self.snapshots_mock.set_metadata.return_value = None - self.snapshots_mock.update.return_value = None - # Get the command object to mock - self.cmd = volume_snapshot.SetVolumeSnapshot(self.app, None) - - def test_snapshot_set_no_option(self): - arglist = [ - self.snapshot.id, - ] - verifylist = [ - ("snapshot", self.snapshot.id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - self.snapshots_mock.get.assert_called_once_with(parsed_args.snapshot) - self.assertNotCalled(self.snapshots_mock.reset_state) - self.assertNotCalled(self.snapshots_mock.update) - self.assertNotCalled(self.snapshots_mock.set_metadata) - self.assertIsNone(result) - - def test_snapshot_set_name_and_property(self): - arglist = [ - "--name", "new_snapshot", - "--property", "x=y", - "--property", "foo=foo", - self.snapshot.id, - ] - new_property = {"x": "y", "foo": "foo"} - verifylist = [ - ("name", "new_snapshot"), - ("property", new_property), - ("snapshot", self.snapshot.id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - kwargs = { - "name": "new_snapshot", - } - self.snapshots_mock.update.assert_called_with( - self.snapshot.id, **kwargs) - self.snapshots_mock.set_metadata.assert_called_with( - self.snapshot.id, new_property - ) - self.assertIsNone(result) - - def test_snapshot_set_with_no_property(self): - arglist = [ - "--no-property", - self.snapshot.id, - ] - verifylist = [ - ("no_property", True), - ("snapshot", self.snapshot.id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - self.snapshots_mock.get.assert_called_once_with(parsed_args.snapshot) - self.assertNotCalled(self.snapshots_mock.reset_state) - self.assertNotCalled(self.snapshots_mock.update) - self.assertNotCalled(self.snapshots_mock.set_metadata) - self.snapshots_mock.delete_metadata.assert_called_with( - self.snapshot.id, ["foo"] - ) - self.assertIsNone(result) - - def test_snapshot_set_with_no_property_and_property(self): - arglist = [ - "--no-property", - "--property", "foo_1=bar_1", - self.snapshot.id, - ] - verifylist = [ - ("no_property", True), - ("property", {"foo_1": "bar_1"}), - ("snapshot", self.snapshot.id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - self.snapshots_mock.get.assert_called_once_with(parsed_args.snapshot) - self.assertNotCalled(self.snapshots_mock.reset_state) - self.assertNotCalled(self.snapshots_mock.update) - self.snapshots_mock.delete_metadata.assert_called_with( - self.snapshot.id, ["foo"] - ) - self.snapshots_mock.set_metadata.assert_called_once_with( - self.snapshot.id, {"foo_1": "bar_1"}) - self.assertIsNone(result) - - def test_snapshot_set_state_to_error(self): - arglist = [ - "--state", "error", - self.snapshot.id - ] - verifylist = [ - ("state", "error"), - ("snapshot", self.snapshot.id) - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - self.snapshots_mock.reset_state.assert_called_with( - self.snapshot.id, "error") - self.assertIsNone(result) - - def test_volume_set_state_failed(self): - self.snapshots_mock.reset_state.side_effect = exceptions.CommandError() - arglist = [ - '--state', 'error', - self.snapshot.id - ] - verifylist = [ - ('state', 'error'), - ('snapshot', self.snapshot.id) - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - try: - self.cmd.take_action(parsed_args) - self.fail('CommandError should be raised.') - except exceptions.CommandError as e: - self.assertEqual('One or more of the set operations failed', - str(e)) - self.snapshots_mock.reset_state.assert_called_once_with( - self.snapshot.id, 'error') - - def test_volume_set_name_and_state_failed(self): - self.snapshots_mock.reset_state.side_effect = exceptions.CommandError() - arglist = [ - '--state', 'error', - "--name", "new_snapshot", - self.snapshot.id - ] - verifylist = [ - ('state', 'error'), - ("name", "new_snapshot"), - ('snapshot', self.snapshot.id) - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - try: - self.cmd.take_action(parsed_args) - self.fail('CommandError should be raised.') - except exceptions.CommandError as e: - self.assertEqual('One or more of the set operations failed', - str(e)) - kwargs = { - "name": "new_snapshot", - } - self.snapshots_mock.update.assert_called_once_with( - self.snapshot.id, **kwargs) - self.snapshots_mock.reset_state.assert_called_once_with( - self.snapshot.id, 'error') - - -class TestSnapshotShow(TestSnapshot): - - columns = ( - 'created_at', - 'description', - 'id', - 'name', - 'properties', - 'size', - 'status', - 'volume_id', - ) - - def setUp(self): - super(TestSnapshotShow, self).setUp() - - self.snapshot = volume_fakes.FakeSnapshot.create_one_snapshot() - - self.data = ( - self.snapshot.created_at, - self.snapshot.description, - self.snapshot.id, - self.snapshot.name, - utils.format_dict(self.snapshot.metadata), - self.snapshot.size, - self.snapshot.status, - self.snapshot.volume_id, - ) - - self.snapshots_mock.get.return_value = self.snapshot - # Get the command object to test - self.cmd = volume_snapshot.ShowVolumeSnapshot(self.app, None) - - def test_snapshot_show(self): - arglist = [ - self.snapshot.id - ] - verifylist = [ - ("snapshot", self.snapshot.id) - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - self.snapshots_mock.get.assert_called_with(self.snapshot.id) - - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) - - -class TestSnapshotUnset(TestSnapshot): - - snapshot = volume_fakes.FakeSnapshot.create_one_snapshot() - - def setUp(self): - super(TestSnapshotUnset, self).setUp() - - self.snapshots_mock.get.return_value = self.snapshot - self.snapshots_mock.delete_metadata.return_value = None - # Get the command object to mock - self.cmd = volume_snapshot.UnsetVolumeSnapshot(self.app, None) - - def test_snapshot_unset(self): - arglist = [ - "--property", "foo", - self.snapshot.id, - ] - verifylist = [ - ("property", ["foo"]), - ("snapshot", self.snapshot.id), - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - self.snapshots_mock.delete_metadata.assert_called_with( - self.snapshot.id, ["foo"] - ) - self.assertIsNone(result) diff --git a/openstackclient/tests/unit/volume/v2/test_transfer_request.py b/openstackclient/tests/unit/volume/v2/test_transfer_request.py index 37eed11e50..1ea6648f6a 100644 --- a/openstackclient/tests/unit/volume/v2/test_transfer_request.py +++ b/openstackclient/tests/unit/volume/v2/test_transfer_request.py @@ -18,6 +18,7 @@ from osc_lib import exceptions from osc_lib import utils +from openstackclient.tests.unit import utils as test_utils from openstackclient.tests.unit.volume.v2 import fakes as transfer_fakes from openstackclient.volume.v2 import volume_transfer_request @@ -85,26 +86,6 @@ def test_transfer_accept(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) - def test_transfer_accept_deprecated(self): - arglist = [ - self.volume_transfer.id, - 'key_value', - ] - verifylist = [ - ('transfer_request', self.volume_transfer.id), - ('old_auth_key', 'key_value'), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - columns, data = self.cmd.take_action(parsed_args) - - self.transfer_mock.accept.assert_called_once_with( - self.volume_transfer.id, - 'key_value', - ) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) - def test_transfer_accept_no_option(self): arglist = [ self.volume_transfer.id, @@ -112,12 +93,13 @@ def test_transfer_accept_no_option(self): verifylist = [ ('transfer_request', self.volume_transfer.id), ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.assertRaises( - exceptions.CommandError, - self.cmd.take_action, - parsed_args, + test_utils.ParserException, + self.check_parser, + self.cmd, + arglist, + verifylist, ) diff --git a/openstackclient/tests/unit/volume/v2/test_volume.py b/openstackclient/tests/unit/volume/v2/test_volume.py index dbe69ea0ff..97da160148 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume.py +++ b/openstackclient/tests/unit/volume/v2/test_volume.py @@ -183,40 +183,6 @@ def test_volume_create_options(self): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, data) - def test_volume_create_user(self): - arglist = [ - '--size', str(self.new_volume.size), - '--user', self.user.id, - self.new_volume.name, - ] - verifylist = [ - ('size', self.new_volume.size), - ('user', self.user.id), - ('name', self.new_volume.name), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - self.assertRaises(exceptions.CommandError, self.cmd.take_action, - parsed_args) - self.volumes_mock.create.assert_not_called() - - def test_volume_create_project(self): - arglist = [ - '--size', str(self.new_volume.size), - '--project', self.project.id, - self.new_volume.name, - ] - verifylist = [ - ('size', self.new_volume.size), - ('project', self.project.id), - ('name', self.new_volume.name), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - self.assertRaises(exceptions.CommandError, self.cmd.take_action, - parsed_args) - self.volumes_mock.create.assert_not_called() - def test_volume_create_properties(self): arglist = [ '--property', 'Alpha=a', diff --git a/openstackclient/tests/unit/volume/v2/test_backup.py b/openstackclient/tests/unit/volume/v2/test_volume_backup.py similarity index 97% rename from openstackclient/tests/unit/volume/v2/test_backup.py rename to openstackclient/tests/unit/volume/v2/test_volume_backup.py index 9a2ce71809..d0d1da3b29 100644 --- a/openstackclient/tests/unit/volume/v2/test_backup.py +++ b/openstackclient/tests/unit/volume/v2/test_volume_backup.py @@ -19,7 +19,7 @@ from osc_lib import utils from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes -from openstackclient.volume.v2 import backup +from openstackclient.volume.v2 import volume_backup class TestBackup(volume_fakes.TestVolume): @@ -77,7 +77,7 @@ def setUp(self): self.backups_mock.create.return_value = self.new_backup # Get the command object to test - self.cmd = backup.CreateVolumeBackup(self.app, None) + self.cmd = volume_backup.CreateVolumeBackup(self.app, None) def test_backup_create(self): arglist = [ @@ -154,7 +154,7 @@ def setUp(self): self.backups_mock.delete.return_value = None # Get the command object to mock - self.cmd = backup.DeleteVolumeBackup(self.app, None) + self.cmd = volume_backup.DeleteVolumeBackup(self.app, None) def test_backup_delete(self): arglist = [ @@ -283,7 +283,7 @@ def setUp(self): self.volumes_mock.get.return_value = self.volume self.backups_mock.get.return_value = self.backups[0] # Get the command to test - self.cmd = backup.ListVolumeBackup(self.app, None) + self.cmd = volume_backup.ListVolumeBackup(self.app, None) def test_backup_list_without_options(self): arglist = [] @@ -371,7 +371,7 @@ def setUp(self): volume_fakes.FakeVolume.create_one_volume( {'id': self.volume['id']})) # Get the command object to mock - self.cmd = backup.RestoreVolumeBackup(self.app, None) + self.cmd = volume_backup.RestoreVolumeBackup(self.app, None) def test_backup_restore(self): arglist = [ @@ -400,7 +400,7 @@ def setUp(self): self.backups_mock.get.return_value = self.backup # Get the command object to test - self.cmd = backup.SetVolumeBackup(self.app, None) + self.cmd = volume_backup.SetVolumeBackup(self.app, None) def test_backup_set_name(self): arglist = [ @@ -517,7 +517,7 @@ def setUp(self): self.backups_mock.get.return_value = self.backup # Get the command object to test - self.cmd = backup.ShowVolumeBackup(self.app, None) + self.cmd = volume_backup.ShowVolumeBackup(self.app, None) def test_backup_show(self): arglist = [ diff --git a/openstackclient/volume/v1/snapshot.py b/openstackclient/volume/v1/snapshot.py deleted file mode 100644 index e9e3894bab..0000000000 --- a/openstackclient/volume/v1/snapshot.py +++ /dev/null @@ -1,318 +0,0 @@ -# Copyright 2012-2013 OpenStack Foundation -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - -# TODO(Huanxuan Ao): Remove this file and "snapshot create", "snapshot delete", -# "snapshot set", "snapshot show" and "snapshot unset" -# commands two cycles after Ocata. - -"""Volume v1 Snapshot action implementations""" - -import copy -import logging - -from osc_lib.cli import parseractions -from osc_lib.command import command -from osc_lib import exceptions -from osc_lib import utils -import six - -from openstackclient.i18n import _ - - -deprecated = True -LOG_DEP = logging.getLogger('deprecated') -LOG = logging.getLogger(__name__) - - -class CreateSnapshot(command.ShowOne): - _description = _("Create new snapshot") - - def get_parser(self, prog_name): - parser = super(CreateSnapshot, self).get_parser(prog_name) - parser.add_argument( - 'volume', - metavar='', - help=_('Volume to snapshot (name or ID)'), - ) - parser.add_argument( - '--name', - metavar='', - help=_('Name of the snapshot'), - ) - parser.add_argument( - '--description', - metavar='', - help=_('Description of the snapshot'), - ) - parser.add_argument( - '--force', - dest='force', - action='store_true', - default=False, - help=_('Create a snapshot attached to an instance. ' - 'Default is False'), - ) - return parser - - def take_action(self, parsed_args): - LOG_DEP.warning(_('This command has been deprecated. ' - 'Please use "volume snapshot create" instead.')) - volume_client = self.app.client_manager.volume - volume_id = utils.find_resource(volume_client.volumes, - parsed_args.volume).id - snapshot = volume_client.volume_snapshots.create( - volume_id, - parsed_args.force, - parsed_args.name, - parsed_args.description - ) - - snapshot._info.update( - {'properties': utils.format_dict(snapshot._info.pop('metadata'))} - ) - - return zip(*sorted(six.iteritems(snapshot._info))) - - -class DeleteSnapshot(command.Command): - _description = _("Delete snapshot(s)") - - def get_parser(self, prog_name): - parser = super(DeleteSnapshot, self).get_parser(prog_name) - parser.add_argument( - 'snapshots', - metavar='', - nargs="+", - help=_('Snapshot(s) to delete (name or ID)'), - ) - return parser - - def take_action(self, parsed_args): - LOG_DEP.warning(_('This command has been deprecated. ' - 'Please use "volume snapshot delete" instead.')) - volume_client = self.app.client_manager.volume - result = 0 - - for i in parsed_args.snapshots: - try: - snapshot_id = utils.find_resource( - volume_client.volume_snapshots, i).id - volume_client.volume_snapshots.delete(snapshot_id) - except Exception as e: - result += 1 - LOG.error(_("Failed to delete snapshot with " - "name or ID '%(snapshot)s': %(e)s"), - {'snapshot': i, 'e': e}) - - if result > 0: - total = len(parsed_args.snapshots) - msg = (_("%(result)s of %(total)s snapshots failed " - "to delete.") % {'result': result, 'total': total}) - raise exceptions.CommandError(msg) - - -class ListSnapshot(command.Lister): - _description = _("List snapshots") - - def get_parser(self, prog_name): - parser = super(ListSnapshot, self).get_parser(prog_name) - parser.add_argument( - '--all-projects', - action='store_true', - default=False, - help=_('Include all projects (admin only)'), - ) - parser.add_argument( - '--long', - action='store_true', - default=False, - help=_('List additional fields in output'), - ) - return parser - - def take_action(self, parsed_args): - LOG_DEP.warning(_('This command has been deprecated. ' - 'Please use "volume snapshot list" instead.')) - - def _format_volume_id(volume_id): - """Return a volume name if available - - :param volume_id: a volume ID - :rtype: either the volume ID or name - """ - - volume = volume_id - if volume_id in volume_cache.keys(): - volume = volume_cache[volume_id].display_name - return volume - - if parsed_args.long: - columns = ['ID', 'Display Name', 'Display Description', 'Status', - 'Size', 'Created At', 'Volume ID', 'Metadata'] - column_headers = copy.deepcopy(columns) - column_headers[6] = 'Volume' - column_headers[7] = 'Properties' - else: - columns = ['ID', 'Display Name', 'Display Description', 'Status', - 'Size'] - column_headers = copy.deepcopy(columns) - - # Always update Name and Description - column_headers[1] = 'Name' - column_headers[2] = 'Description' - - # Cache the volume list - volume_cache = {} - try: - for s in self.app.client_manager.volume.volumes.list(): - volume_cache[s.id] = s - except Exception: - # Just forget it if there's any trouble - pass - - search_opts = { - 'all_tenants': parsed_args.all_projects, - } - - data = self.app.client_manager.volume.volume_snapshots.list( - search_opts=search_opts) - return (column_headers, - (utils.get_item_properties( - s, columns, - formatters={'Metadata': utils.format_dict, - 'Volume ID': _format_volume_id}, - ) for s in data)) - - -class SetSnapshot(command.Command): - _description = _("Set snapshot properties") - - def get_parser(self, prog_name): - parser = super(SetSnapshot, self).get_parser(prog_name) - parser.add_argument( - 'snapshot', - metavar='', - help=_('Snapshot to modify (name or ID)') - ) - parser.add_argument( - '--name', - metavar='', - help=_('New snapshot name') - ) - parser.add_argument( - '--description', - metavar='', - help=_('New snapshot description') - ) - parser.add_argument( - '--property', - metavar='', - action=parseractions.KeyValueAction, - help=_('Property to add/change for this snapshot ' - '(repeat option to set multiple properties)'), - ) - return parser - - def take_action(self, parsed_args): - LOG_DEP.warning(_('This command has been deprecated. ' - 'Please use "volume snapshot set" instead.')) - volume_client = self.app.client_manager.volume - snapshot = utils.find_resource(volume_client.volume_snapshots, - parsed_args.snapshot) - - result = 0 - if parsed_args.property: - try: - volume_client.volume_snapshots.set_metadata( - snapshot.id, parsed_args.property) - except Exception as e: - LOG.error(_("Failed to set snapshot property: %s"), e) - result += 1 - - kwargs = {} - if parsed_args.name: - kwargs['display_name'] = parsed_args.name - if parsed_args.description: - kwargs['display_description'] = parsed_args.description - if kwargs: - try: - snapshot.update(**kwargs) - except Exception as e: - LOG.error(_("Failed to update snapshot display name " - "or display description: %s"), e) - result += 1 - - if result > 0: - raise exceptions.CommandError(_("One or more of the " - "set operations failed")) - - -class ShowSnapshot(command.ShowOne): - _description = _("Display snapshot details") - - def get_parser(self, prog_name): - parser = super(ShowSnapshot, self).get_parser(prog_name) - parser.add_argument( - 'snapshot', - metavar='', - help=_('Snapshot to display (name or ID)') - ) - return parser - - def take_action(self, parsed_args): - LOG_DEP.warning(_('This command has been deprecated. ' - 'Please use "volume snapshot show" instead.')) - volume_client = self.app.client_manager.volume - snapshot = utils.find_resource(volume_client.volume_snapshots, - parsed_args.snapshot) - - snapshot._info.update( - {'properties': utils.format_dict(snapshot._info.pop('metadata'))} - ) - - return zip(*sorted(six.iteritems(snapshot._info))) - - -class UnsetSnapshot(command.Command): - _description = _("Unset snapshot properties") - - def get_parser(self, prog_name): - parser = super(UnsetSnapshot, self).get_parser(prog_name) - parser.add_argument( - 'snapshot', - metavar='', - help=_('Snapshot to modify (name or ID)'), - ) - parser.add_argument( - '--property', - metavar='', - action='append', - help=_('Property to remove from snapshot ' - '(repeat option to remove multiple properties)'), - ) - return parser - - def take_action(self, parsed_args): - LOG_DEP.warning(_('This command has been deprecated. ' - 'Please use "volume snapshot unset" instead.')) - volume_client = self.app.client_manager.volume - snapshot = utils.find_resource( - volume_client.volume_snapshots, parsed_args.snapshot) - - if parsed_args.property: - volume_client.volume_snapshots.delete_metadata( - snapshot.id, - parsed_args.property, - ) diff --git a/openstackclient/volume/v1/backup.py b/openstackclient/volume/v1/volume_backup.py similarity index 73% rename from openstackclient/volume/v1/backup.py rename to openstackclient/volume/v1/volume_backup.py index 9ac1302a55..9af327055b 100644 --- a/openstackclient/volume/v1/backup.py +++ b/openstackclient/volume/v1/volume_backup.py @@ -72,23 +72,6 @@ def take_action(self, parsed_args): return zip(*sorted(six.iteritems(backup._info))) -class CreateBackup(CreateVolumeBackup): - _description = _("Create new backup") - - # TODO(Huanxuan Ao): Remove this class and ``backup create`` command - # two cycles after Newton. - - # This notifies cliff to not display the help for this command - deprecated = True - - log = logging.getLogger('deprecated') - - def take_action(self, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "volume backup create" instead.')) - return super(CreateBackup, self).take_action(parsed_args) - - class DeleteVolumeBackup(command.Command): _description = _("Delete volume backup(s)") @@ -124,23 +107,6 @@ def take_action(self, parsed_args): raise exceptions.CommandError(msg) -class DeleteBackup(DeleteVolumeBackup): - _description = _("Delete backup(s)") - - # TODO(Huanxuan Ao): Remove this class and ``backup delete`` command - # two cycles after Newton. - - # This notifies cliff to not display the help for this command - deprecated = True - - log = logging.getLogger('deprecated') - - def take_action(self, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "volume backup delete" instead.')) - return super(DeleteBackup, self).take_action(parsed_args) - - class ListVolumeBackup(command.Lister): _description = _("List volume backups") @@ -234,23 +200,6 @@ def _format_volume_id(volume_id): ) for s in data)) -class ListBackup(ListVolumeBackup): - _description = _("List backups") - - # TODO(Huanxuan Ao): Remove this class and ``backup list`` command - # two cycles after Newton. - - # This notifies cliff to not display the help for this command - deprecated = True - - log = logging.getLogger('deprecated') - - def take_action(self, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "volume backup list" instead.')) - return super(ListBackup, self).take_action(parsed_args) - - class RestoreVolumeBackup(command.Command): _description = _("Restore volume backup") @@ -278,23 +227,6 @@ def take_action(self, parsed_args): destination_volume.id) -class RestoreBackup(RestoreVolumeBackup): - _description = _("Restore backup") - - # TODO(Huanxuan Ao): Remove this class and ``backup restore`` command - # two cycles after Newton. - - # This notifies cliff to not display the help for this command - deprecated = True - - log = logging.getLogger('deprecated') - - def take_action(self, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "volume backup restore" instead.')) - return super(RestoreBackup, self).take_action(parsed_args) - - class ShowVolumeBackup(command.ShowOne): _description = _("Display volume backup details") @@ -313,20 +245,3 @@ def take_action(self, parsed_args): parsed_args.backup) backup._info.pop('links') return zip(*sorted(six.iteritems(backup._info))) - - -class ShowBackup(ShowVolumeBackup): - _description = _("Display backup details") - - # TODO(Huanxuan Ao): Remove this class and ``backup show`` command - # two cycles after Newton. - - # This notifies cliff to not display the help for this command - deprecated = True - - log = logging.getLogger('deprecated') - - def take_action(self, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "volume backup show" instead.')) - return super(ShowBackup, self).take_action(parsed_args) diff --git a/openstackclient/volume/v1/volume_transfer_request.py b/openstackclient/volume/v1/volume_transfer_request.py index f5b567b94a..6f79658e5a 100644 --- a/openstackclient/volume/v1/volume_transfer_request.py +++ b/openstackclient/volume/v1/volume_transfer_request.py @@ -14,7 +14,6 @@ """Volume v1 transfer action implementations""" -import argparse import logging from osc_lib.command import command @@ -38,12 +37,6 @@ def get_parser(self, prog_name): metavar="", help=_('Volume transfer request to accept (ID only)'), ) - parser.add_argument( - 'old_auth_key', - metavar="", - nargs="?", - help=argparse.SUPPRESS, - ) parser.add_argument( '--auth-key', metavar="", @@ -64,20 +57,9 @@ def take_action(self, parsed_args): # move on and attempt with the user-supplied information transfer_request_id = parsed_args.transfer_request - # Remain backward-compatible for the previous command layout - # TODO(dtroyer): Remove this back-compat in 4.0 or Oct 2017 if not parsed_args.auth_key: - if parsed_args.old_auth_key: - # Move the old one into the correct place - parsed_args.auth_key = parsed_args.old_auth_key - self.log.warning(_( - 'Specifying the auth-key as a positional argument ' - 'has been deprecated. Please use the --auth-key ' - 'option in the future.' - )) - else: - msg = _("argument --auth-key is required") - raise exceptions.CommandError(msg) + msg = _("argument --auth-key is required") + raise exceptions.CommandError(msg) transfer_accept = volume_client.transfers.accept( transfer_request_id, diff --git a/openstackclient/volume/v2/snapshot.py b/openstackclient/volume/v2/snapshot.py deleted file mode 100644 index 82b310338a..0000000000 --- a/openstackclient/volume/v2/snapshot.py +++ /dev/null @@ -1,351 +0,0 @@ -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - -# TODO(Huanxuan Ao): Remove this file and "snapshot create", "snapshot delete", -# "snapshot set", "snapshot show" and "snapshot unset" -# commands two cycles after Ocata. - -"""Volume v2 snapshot action implementations""" - -import copy -import logging - -from osc_lib.cli import parseractions -from osc_lib.command import command -from osc_lib import exceptions -from osc_lib import utils -import six - -from openstackclient.i18n import _ - - -deprecated = True -LOG_DEP = logging.getLogger('deprecated') -LOG = logging.getLogger(__name__) - - -class CreateSnapshot(command.ShowOne): - _description = _("Create new snapshot") - - def get_parser(self, prog_name): - parser = super(CreateSnapshot, self).get_parser(prog_name) - parser.add_argument( - "volume", - metavar="", - help=_("Volume to snapshot (name or ID)") - ) - parser.add_argument( - "--name", - metavar="", - help=_("Name of the snapshot") - ) - parser.add_argument( - "--description", - metavar="", - help=_("Description of the snapshot") - ) - parser.add_argument( - "--force", - action="store_true", - default=False, - help=_("Create a snapshot attached to an instance. " - "Default is False") - ) - parser.add_argument( - "--property", - metavar="", - action=parseractions.KeyValueAction, - help=_("Set a property to this snapshot " - "(repeat option to set multiple properties)"), - ) - return parser - - def take_action(self, parsed_args): - LOG_DEP.warning(_('This command has been deprecated. ' - 'Please use "volume snapshot create" instead.')) - volume_client = self.app.client_manager.volume - volume_id = utils.find_resource( - volume_client.volumes, parsed_args.volume).id - snapshot = volume_client.volume_snapshots.create( - volume_id, - force=parsed_args.force, - name=parsed_args.name, - description=parsed_args.description, - metadata=parsed_args.property, - ) - snapshot._info.update( - {'properties': utils.format_dict(snapshot._info.pop('metadata'))} - ) - return zip(*sorted(six.iteritems(snapshot._info))) - - -class DeleteSnapshot(command.Command): - _description = _("Delete volume snapshot(s)") - - def get_parser(self, prog_name): - parser = super(DeleteSnapshot, self).get_parser(prog_name) - parser.add_argument( - "snapshots", - metavar="", - nargs="+", - help=_("Snapshot(s) to delete (name or ID)") - ) - return parser - - def take_action(self, parsed_args): - LOG_DEP.warning(_('This command has been deprecated. ' - 'Please use "volume snapshot delete" instead.')) - volume_client = self.app.client_manager.volume - result = 0 - - for i in parsed_args.snapshots: - try: - snapshot_id = utils.find_resource( - volume_client.volume_snapshots, i).id - volume_client.volume_snapshots.delete(snapshot_id) - except Exception as e: - result += 1 - LOG.error(_("Failed to delete snapshot with " - "name or ID '%(snapshot)s': %(e)s") - % {'snapshot': i, 'e': e}) - - if result > 0: - total = len(parsed_args.snapshots) - msg = (_("%(result)s of %(total)s snapshots failed " - "to delete.") % {'result': result, 'total': total}) - raise exceptions.CommandError(msg) - - -class ListSnapshot(command.Lister): - _description = _("List snapshots") - - def get_parser(self, prog_name): - parser = super(ListSnapshot, self).get_parser(prog_name) - parser.add_argument( - '--all-projects', - action='store_true', - default=False, - help=_('Include all projects (admin only)'), - ) - parser.add_argument( - '--long', - action='store_true', - default=False, - help=_('List additional fields in output'), - ) - parser.add_argument( - '--marker', - metavar='', - help=_('The last snapshot ID of the previous page'), - ) - parser.add_argument( - '--limit', - type=int, - action=parseractions.NonNegativeAction, - metavar='', - help=_('Maximum number of snapshots to display'), - ) - return parser - - def take_action(self, parsed_args): - LOG_DEP.warning(_('This command has been deprecated. ' - 'Please use "volume snapshot list" instead.')) - - def _format_volume_id(volume_id): - """Return a volume name if available - - :param volume_id: a volume ID - :rtype: either the volume ID or name - """ - - volume = volume_id - if volume_id in volume_cache.keys(): - volume = volume_cache[volume_id].name - return volume - - if parsed_args.long: - columns = ['ID', 'Name', 'Description', 'Status', - 'Size', 'Created At', 'Volume ID', 'Metadata'] - column_headers = copy.deepcopy(columns) - column_headers[6] = 'Volume' - column_headers[7] = 'Properties' - else: - columns = ['ID', 'Name', 'Description', 'Status', 'Size'] - column_headers = copy.deepcopy(columns) - - # Cache the volume list - volume_cache = {} - try: - for s in self.app.client_manager.volume.volumes.list(): - volume_cache[s.id] = s - except Exception: - # Just forget it if there's any trouble - pass - - search_opts = { - 'all_tenants': parsed_args.all_projects, - } - - data = self.app.client_manager.volume.volume_snapshots.list( - search_opts=search_opts, - marker=parsed_args.marker, - limit=parsed_args.limit, - ) - return (column_headers, - (utils.get_item_properties( - s, columns, - formatters={'Metadata': utils.format_dict, - 'Volume ID': _format_volume_id}, - ) for s in data)) - - -class SetSnapshot(command.Command): - _description = _("Set snapshot properties") - - def get_parser(self, prog_name): - parser = super(SetSnapshot, self).get_parser(prog_name) - parser.add_argument( - 'snapshot', - metavar='', - help=_('Snapshot to modify (name or ID)') - ) - parser.add_argument( - '--name', - metavar='', - help=_('New snapshot name') - ) - parser.add_argument( - '--description', - metavar='', - help=_('New snapshot description') - ) - parser.add_argument( - '--property', - metavar='', - action=parseractions.KeyValueAction, - help=_('Property to add/change for this snapshot ' - '(repeat option to set multiple properties)'), - ) - parser.add_argument( - '--state', - metavar='', - choices=['available', 'error', 'creating', 'deleting', - 'error-deleting'], - help=_('New snapshot state. ("available", "error", "creating", ' - '"deleting", or "error_deleting") (admin only) ' - '(This option simply changes the state of the snapshot ' - 'in the database with no regard to actual status, ' - 'exercise caution when using)'), - ) - return parser - - def take_action(self, parsed_args): - LOG_DEP.warning(_('This command has been deprecated. ' - 'Please use "volume snapshot set" instead.')) - volume_client = self.app.client_manager.volume - snapshot = utils.find_resource(volume_client.volume_snapshots, - parsed_args.snapshot) - - result = 0 - if parsed_args.property: - try: - volume_client.volume_snapshots.set_metadata( - snapshot.id, parsed_args.property) - except Exception as e: - LOG.error(_("Failed to set snapshot property: %s"), e) - result += 1 - - if parsed_args.state: - try: - volume_client.volume_snapshots.reset_state( - snapshot.id, parsed_args.state) - except Exception as e: - LOG.error(_("Failed to set snapshot state: %s"), e) - result += 1 - - kwargs = {} - if parsed_args.name: - kwargs['name'] = parsed_args.name - if parsed_args.description: - kwargs['description'] = parsed_args.description - if kwargs: - try: - volume_client.volume_snapshots.update( - snapshot.id, **kwargs) - except Exception as e: - LOG.error(_("Failed to update snapshot name " - "or description: %s"), e) - result += 1 - - if result > 0: - raise exceptions.CommandError(_("One or more of the " - "set operations failed")) - - -class ShowSnapshot(command.ShowOne): - _description = _("Display snapshot details") - - def get_parser(self, prog_name): - parser = super(ShowSnapshot, self).get_parser(prog_name) - parser.add_argument( - "snapshot", - metavar="", - help=_("Snapshot to display (name or ID)") - ) - return parser - - def take_action(self, parsed_args): - LOG_DEP.warning(_('This command has been deprecated. ' - 'Please use "volume snapshot show" instead.')) - volume_client = self.app.client_manager.volume - snapshot = utils.find_resource( - volume_client.volume_snapshots, parsed_args.snapshot) - snapshot._info.update( - {'properties': utils.format_dict(snapshot._info.pop('metadata'))} - ) - return zip(*sorted(six.iteritems(snapshot._info))) - - -class UnsetSnapshot(command.Command): - _description = _("Unset snapshot properties") - - def get_parser(self, prog_name): - parser = super(UnsetSnapshot, self).get_parser(prog_name) - parser.add_argument( - 'snapshot', - metavar='', - help=_('Snapshot to modify (name or ID)'), - ) - parser.add_argument( - '--property', - metavar='', - action='append', - default=[], - help=_('Property to remove from snapshot ' - '(repeat option to remove multiple properties)'), - ) - return parser - - def take_action(self, parsed_args): - LOG_DEP.warning(_('This command has been deprecated. ' - 'Please use "volume snapshot unset" instead.')) - volume_client = self.app.client_manager.volume - snapshot = utils.find_resource( - volume_client.volume_snapshots, parsed_args.snapshot) - - if parsed_args.property: - volume_client.volume_snapshots.delete_metadata( - snapshot.id, - parsed_args.property, - ) diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index fa587b5f67..ef65d0970a 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -93,16 +93,6 @@ def get_parser(self, prog_name): metavar="", help=_("Volume description"), ) - parser.add_argument( - '--user', - metavar='', - help=argparse.SUPPRESS, - ) - parser.add_argument( - '--project', - metavar='', - help=argparse.SUPPRESS, - ) parser.add_argument( "--availability-zone", metavar="", @@ -127,12 +117,6 @@ def get_parser(self, prog_name): help=_("Arbitrary scheduler hint key-value pairs to help boot " "an instance (repeat option to set multiple hints)"), ) - parser.add_argument( - "--multi-attach", - action="store_true", - help=_("Allow volume to be attached more than once " - "(default to False)") - ) bootable_group = parser.add_mutually_exclusive_group() bootable_group.add_argument( "--bootable", @@ -196,26 +180,6 @@ def take_action(self, parsed_args): # snapshot size. size = max(size or 0, snapshot_obj.size) - # NOTE(abishop): Cinder's volumes.create() has 'project_id' and - # 'user_id' args, but they're not wired up to anything. The only way - # to specify an alternate project or user for the volume is to use - # the identity overrides (e.g. "--os-project-id"). - # - # Now, if the project or user arg is specified then the command is - # rejected. Otherwise, Cinder would actually create a volume, but - # without the specified property. - if parsed_args.project: - raise exceptions.CommandError( - _("ERROR: --project is deprecated, please use" - " --os-project-name or --os-project-id instead.")) - if parsed_args.user: - raise exceptions.CommandError( - _("ERROR: --user is deprecated, please use" - " --os-username instead.")) - if parsed_args.multi_attach: - LOG.warning(_("'--multi-attach' option is no longer supported by " - "the block storage service.")) - volume = volume_client.volumes.create( size=size, snapshot_id=snapshot, diff --git a/openstackclient/volume/v2/backup.py b/openstackclient/volume/v2/volume_backup.py similarity index 80% rename from openstackclient/volume/v2/backup.py rename to openstackclient/volume/v2/volume_backup.py index d4aec8d747..1d2b0cde04 100644 --- a/openstackclient/volume/v2/backup.py +++ b/openstackclient/volume/v2/volume_backup.py @@ -94,23 +94,6 @@ def take_action(self, parsed_args): return zip(*sorted(six.iteritems(backup._info))) -class CreateBackup(CreateVolumeBackup): - _description = _("Create new backup") - - # TODO(Huanxuan Ao): Remove this class and ``backup create`` command - # two cycles after Newton. - - # This notifies cliff to not display the help for this command - deprecated = True - - log = logging.getLogger('deprecated') - - def take_action(self, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "volume backup create" instead.')) - return super(CreateBackup, self).take_action(parsed_args) - - class DeleteVolumeBackup(command.Command): _description = _("Delete volume backup(s)") @@ -152,23 +135,6 @@ def take_action(self, parsed_args): raise exceptions.CommandError(msg) -class DeleteBackup(DeleteVolumeBackup): - _description = _("Delete backup(s)") - - # TODO(Huanxuan Ao): Remove this class and ``backup delete`` command - # two cycles after Newton. - - # This notifies cliff to not display the help for this command - deprecated = True - - log = logging.getLogger('deprecated') - - def take_action(self, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "volume backup delete" instead.')) - return super(DeleteBackup, self).take_action(parsed_args) - - class ListVolumeBackup(command.Lister): _description = _("List volume backups") @@ -280,23 +246,6 @@ def _format_volume_id(volume_id): ) for s in data)) -class ListBackup(ListVolumeBackup): - _description = _("List backups") - - # TODO(Huanxuan Ao): Remove this class and ``backup list`` command - # two cycles after Newton. - - # This notifies cliff to not display the help for this command - deprecated = True - - log = logging.getLogger('deprecated') - - def take_action(self, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "volume backup list" instead.')) - return super(ListBackup, self).take_action(parsed_args) - - class RestoreVolumeBackup(command.ShowOne): _description = _("Restore volume backup") @@ -324,23 +273,6 @@ def take_action(self, parsed_args): return zip(*sorted(six.iteritems(backup._info))) -class RestoreBackup(RestoreVolumeBackup): - _description = _("Restore backup") - - # TODO(Huanxuan Ao): Remove this class and ``backup restore`` command - # two cycles after Newton. - - # This notifies cliff to not display the help for this command - deprecated = True - - log = logging.getLogger('deprecated') - - def take_action(self, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "volume backup restore" instead.')) - return super(RestoreBackup, self).take_action(parsed_args) - - class SetVolumeBackup(command.Command): _description = _("Set volume backup properties") @@ -421,20 +353,3 @@ def take_action(self, parsed_args): parsed_args.backup) backup._info.pop("links", None) return zip(*sorted(six.iteritems(backup._info))) - - -class ShowBackup(ShowVolumeBackup): - _description = _("Display backup details") - - # TODO(Huanxuan Ao): Remove this class and ``backup show`` command - # two cycles after Newton. - - # This notifies cliff to not display the help for this command - deprecated = True - - log = logging.getLogger('deprecated') - - def take_action(self, parsed_args): - self.log.warning(_('This command has been deprecated. ' - 'Please use "volume backup show" instead.')) - return super(ShowBackup, self).take_action(parsed_args) diff --git a/openstackclient/volume/v2/volume_transfer_request.py b/openstackclient/volume/v2/volume_transfer_request.py index 2f531dc871..4c4741bc30 100644 --- a/openstackclient/volume/v2/volume_transfer_request.py +++ b/openstackclient/volume/v2/volume_transfer_request.py @@ -14,7 +14,6 @@ """Volume v2 transfer action implementations""" -import argparse import logging from osc_lib.command import command @@ -38,15 +37,10 @@ def get_parser(self, prog_name): metavar="", help=_('Volume transfer request to accept (ID only)'), ) - parser.add_argument( - 'old_auth_key', - metavar="", - nargs="?", - help=argparse.SUPPRESS, - ) parser.add_argument( '--auth-key', metavar="", + required=True, help=_('Volume transfer request authentication key'), ) return parser @@ -64,21 +58,6 @@ def take_action(self, parsed_args): # move on and attempt with the user-supplied information transfer_request_id = parsed_args.transfer_request - # Remain backward-compatible for the previous command layout - # TODO(dtroyer): Remove this back-compat in 4.0 or Oct 2017 - if not parsed_args.auth_key: - if parsed_args.old_auth_key: - # Move the old one into the correct place - parsed_args.auth_key = parsed_args.old_auth_key - self.log.warning(_( - 'Specifying the auth-key as a positional argument ' - 'has been deprecated. Please use the --auth-key ' - 'option in the future.' - )) - else: - msg = _("argument --auth-key is required") - raise exceptions.CommandError(msg) - transfer_accept = volume_client.transfers.accept( transfer_request_id, parsed_args.auth_key, diff --git a/releasenotes/notes/osc4-volume-470422e5a453310e.yaml b/releasenotes/notes/osc4-volume-470422e5a453310e.yaml new file mode 100644 index 0000000000..e03f0fdcd6 --- /dev/null +++ b/releasenotes/notes/osc4-volume-470422e5a453310e.yaml @@ -0,0 +1,14 @@ +--- +upgrade: + - | + Remove deprecated ``backup`` commands. + Use ``volume backup`` commands instead. + - | + Remove deprecated ``snapshot`` commands. + Use ``volume snapshot`` commands instead. + - | + Remove deprecated ``volume create`` options ``--project``, ``--user`` + and ``--multi-attach``. + - | + Change ``volume transfer request accept`` to use new required option + ``--auth-key`` rather than a second positional argument. diff --git a/setup.cfg b/setup.cfg index cc57a1037a..d3d79983b3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -526,19 +526,6 @@ openstack.object_store.v1 = object_unset = openstackclient.object.v1.object:UnsetObject openstack.volume.v1 = - backup_create = openstackclient.volume.v1.backup:CreateBackup - backup_delete = openstackclient.volume.v1.backup:DeleteBackup - backup_list = openstackclient.volume.v1.backup:ListBackup - backup_restore = openstackclient.volume.v1.backup:RestoreBackup - backup_show = openstackclient.volume.v1.backup:ShowBackup - - snapshot_create = openstackclient.volume.v1.snapshot:CreateSnapshot - snapshot_delete = openstackclient.volume.v1.snapshot:DeleteSnapshot - snapshot_list = openstackclient.volume.v1.snapshot:ListSnapshot - snapshot_set = openstackclient.volume.v1.snapshot:SetSnapshot - snapshot_show = openstackclient.volume.v1.snapshot:ShowSnapshot - snapshot_unset = openstackclient.volume.v1.snapshot:UnsetSnapshot - volume_create = openstackclient.volume.v1.volume:CreateVolume volume_delete = openstackclient.volume.v1.volume:DeleteVolume volume_list = openstackclient.volume.v1.volume:ListVolume @@ -547,11 +534,11 @@ openstack.volume.v1 = volume_show = openstackclient.volume.v1.volume:ShowVolume volume_unset = openstackclient.volume.v1.volume:UnsetVolume - volume_backup_create = openstackclient.volume.v1.backup:CreateVolumeBackup - volume_backup_delete = openstackclient.volume.v1.backup:DeleteVolumeBackup - volume_backup_list = openstackclient.volume.v1.backup:ListVolumeBackup - volume_backup_restore = openstackclient.volume.v1.backup:RestoreVolumeBackup - volume_backup_show = openstackclient.volume.v1.backup:ShowVolumeBackup + volume_backup_create = openstackclient.volume.v1.volume_backup:CreateVolumeBackup + volume_backup_delete = openstackclient.volume.v1.volume_backup:DeleteVolumeBackup + volume_backup_list = openstackclient.volume.v1.volume_backup:ListVolumeBackup + volume_backup_restore = openstackclient.volume.v1.volume_backup:RestoreVolumeBackup + volume_backup_show = openstackclient.volume.v1.volume_backup:ShowVolumeBackup volume_snapshot_create = openstackclient.volume.v1.volume_snapshot:CreateVolumeSnapshot volume_snapshot_delete = openstackclient.volume.v1.volume_snapshot:DeleteVolumeSnapshot @@ -586,12 +573,6 @@ openstack.volume.v1 = volume_transfer_request_show = openstackclient.volume.v1.volume_transfer_request:ShowTransferRequest openstack.volume.v2 = - backup_create = openstackclient.volume.v2.backup:CreateBackup - backup_delete = openstackclient.volume.v2.backup:DeleteBackup - backup_list = openstackclient.volume.v2.backup:ListBackup - backup_restore = openstackclient.volume.v2.backup:RestoreBackup - backup_show = openstackclient.volume.v2.backup:ShowBackup - consistency_group_add_volume = openstackclient.volume.v2.consistency_group:AddVolumeToConsistencyGroup consistency_group_create = openstackclient.volume.v2.consistency_group:CreateConsistencyGroup consistency_group_delete = openstackclient.volume.v2.consistency_group:DeleteConsistencyGroup @@ -605,13 +586,6 @@ openstack.volume.v2 = consistency_group_snapshot_list = openstackclient.volume.v2.consistency_group_snapshot:ListConsistencyGroupSnapshot consistency_group_snapshot_show = openstackclient.volume.v2.consistency_group_snapshot:ShowConsistencyGroupSnapshot - snapshot_create = openstackclient.volume.v2.snapshot:CreateSnapshot - snapshot_delete = openstackclient.volume.v2.snapshot:DeleteSnapshot - snapshot_list = openstackclient.volume.v2.snapshot:ListSnapshot - snapshot_set = openstackclient.volume.v2.snapshot:SetSnapshot - snapshot_show = openstackclient.volume.v2.snapshot:ShowSnapshot - snapshot_unset = openstackclient.volume.v2.snapshot:UnsetSnapshot - volume_create = openstackclient.volume.v2.volume:CreateVolume volume_delete = openstackclient.volume.v2.volume:DeleteVolume volume_list = openstackclient.volume.v2.volume:ListVolume @@ -620,12 +594,12 @@ openstack.volume.v2 = volume_show = openstackclient.volume.v2.volume:ShowVolume volume_unset = openstackclient.volume.v2.volume:UnsetVolume - volume_backup_create = openstackclient.volume.v2.backup:CreateVolumeBackup - volume_backup_delete = openstackclient.volume.v2.backup:DeleteVolumeBackup - volume_backup_list = openstackclient.volume.v2.backup:ListVolumeBackup - volume_backup_restore = openstackclient.volume.v2.backup:RestoreVolumeBackup - volume_backup_set = openstackclient.volume.v2.backup:SetVolumeBackup - volume_backup_show = openstackclient.volume.v2.backup:ShowVolumeBackup + volume_backup_create = openstackclient.volume.v2.volume_backup:CreateVolumeBackup + volume_backup_delete = openstackclient.volume.v2.volume_backup:DeleteVolumeBackup + volume_backup_list = openstackclient.volume.v2.volume_backup:ListVolumeBackup + volume_backup_restore = openstackclient.volume.v2.volume_backup:RestoreVolumeBackup + volume_backup_set = openstackclient.volume.v2.volume_backup:SetVolumeBackup + volume_backup_show = openstackclient.volume.v2.volume_backup:ShowVolumeBackup volume_backup_record_export = openstackclient.volume.v2.backup_record:ExportBackupRecord volume_backup_record_import = openstackclient.volume.v2.backup_record:ImportBackupRecord From 3057989714e5e510e275c454258e0f167ed551d2 Mon Sep 17 00:00:00 2001 From: Matt Riedemann Date: Wed, 15 May 2019 17:57:29 -0400 Subject: [PATCH 2066/3095] Deprecate openstack server migrate --host option Per the discussion at the Train Forum [1] this deprecates the problematic --live option on the server migrate command which, depending on the compute API version used, forcefully bypasses the scheduler and also does not allow you to live migrate a server and let the scheduler pick a host. The --live option is replaced here with two new options: * --live-migration: this simply tells the command you want to perform a live rather than cold migration; if specified with --live the --live-migration option takes priority. * --host: when specified, this will request a target host for the live migration and will be validated by the scheduler; if not specified, the scheduler will pick a host. This option is mutually exclusive with --live. We can build on the --host option by supporting cold migrations with a specified host when using compute API version 2.56 or greater but that will come in a separate change. If the --live option is ever used we log a warning. Note there are several related changes for this issue: - https://review.openstack.org/#/c/628334/ - https://review.openstack.org/#/c/626949/ - https://review.openstack.org/#/c/627801/ - https://review.openstack.org/#/c/589012/ - https://review.openstack.org/#/c/460059/ This change allows us to deprecate the --live option and provide a replacement which is backward compatible without having to use something potentially error-prone like nargs='?'. Closes-Bug: #1411190 [1] https://etherpad.openstack.org/p/DEN-osc-compute-api-gaps Change-Id: I95d3d588e4abeb6848bdccf6915f7b5da40b5d4f --- openstackclient/compute/v2/server.py | 78 ++++++++- .../tests/unit/compute/v2/test_server.py | 158 +++++++++++++++++- ...-live-migration-host-655ae6befa6a3de2.yaml | 27 +++ 3 files changed, 255 insertions(+), 8 deletions(-) create mode 100644 releasenotes/notes/bug-1411190-live-migration-host-655ae6befa6a3de2.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index cb9f8d43eb..fefd4b3773 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1407,9 +1407,38 @@ def get_parser(self, prog_name): help=_('Server (name or ID)'), ) parser.add_argument( + '--live-migration', + dest='live_migration', + action='store_true', + help=_('Live migrate the server. Use the ``--host`` option to ' + 'specify a target host for the migration which will be ' + 'validated by the scheduler.'), + ) + # The --live and --host options are mutually exclusive ways of asking + # for a target host during a live migration. + host_group = parser.add_mutually_exclusive_group() + # TODO(mriedem): Remove --live in the next major version bump after + # the Train release. + host_group.add_argument( '--live', metavar='', - help=_('Target hostname'), + help=_('**Deprecated** This option is problematic in that it ' + 'requires a host and prior to compute API version 2.30, ' + 'specifying a host during live migration will bypass ' + 'validation by the scheduler which could result in ' + 'failures to actually migrate the server to the specified ' + 'host or over-subscribe the host. Use the ' + '``--live-migration`` option instead. If both this option ' + 'and ``--live-migration`` are used, ``--live-migration`` ' + 'takes priority.'), + ) + # TODO(mriedem): Add support for --os-compute-api-version >= 2.56 where + # you can cold migrate to a specified target host. + host_group.add_argument( + '--host', + metavar='', + help=_('Live migrate the server to the specified host. Requires ' + '``--os-compute-api-version`` 2.30 or greater.'), ) migration_group = parser.add_mutually_exclusive_group() migration_group.add_argument( @@ -1447,6 +1476,15 @@ def get_parser(self, prog_name): ) return parser + def _log_warning_for_live(self, parsed_args): + if parsed_args.live: + # NOTE(mriedem): The --live option requires a host and if + # --os-compute-api-version is less than 2.30 it will forcefully + # bypass the scheduler which is dangerous. + self.log.warning(_( + 'The --live option has been deprecated. Please use the ' + '--live-migration option instead.')) + def take_action(self, parsed_args): def _show_progress(progress): @@ -1460,19 +1498,45 @@ def _show_progress(progress): compute_client.servers, parsed_args.server, ) - if parsed_args.live: + # Check for live migration. + if parsed_args.live or parsed_args.live_migration: + # Always log a warning if --live is used. + self._log_warning_for_live(parsed_args) kwargs = { - 'host': parsed_args.live, 'block_migration': parsed_args.block_migration } + # Prefer --live-migration over --live if both are specified. + if parsed_args.live_migration: + # Technically we could pass a non-None host with + # --os-compute-api-version < 2.30 but that is the same thing + # as the --live option bypassing the scheduler which we don't + # want to support, so if the user is using --live-migration + # and --host, we want to enforce that they are using version + # 2.30 or greater. + if (parsed_args.host and + compute_client.api_version < + api_versions.APIVersion('2.30')): + raise exceptions.CommandError( + '--os-compute-api-version 2.30 or greater is required ' + 'when using --host') + # The host parameter is required in the API even if None. + kwargs['host'] = parsed_args.host + else: + kwargs['host'] = parsed_args.live + if compute_client.api_version < api_versions.APIVersion('2.25'): kwargs['disk_over_commit'] = parsed_args.disk_overcommit server.live_migrate(**kwargs) else: - if parsed_args.block_migration or parsed_args.disk_overcommit: - raise exceptions.CommandError("--live must be specified if " - "--block-migration or " - "--disk-overcommit is specified") + if (parsed_args.block_migration or parsed_args.disk_overcommit or + parsed_args.host): + # TODO(mriedem): Allow --host for cold migration if + # --os-compute-api-version >= 2.56. + raise exceptions.CommandError( + "--live-migration must be specified if " + "--block-migration, --disk-overcommit or --host is " + "specified") + server.migrate() if parsed_args.wait: diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index c30af8fb8e..2a301b4541 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -23,6 +23,7 @@ from osc_lib import exceptions from osc_lib import utils as common_utils from oslo_utils import timeutils +import six from openstackclient.compute.v2 import server from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes @@ -2415,12 +2416,40 @@ def test_server_migrate_with_disk_overcommit(self): self.assertNotCalled(self.servers_mock.live_migrate) self.assertNotCalled(self.servers_mock.migrate) + def test_server_migrate_with_host(self): + # Tests that --host is not allowed for a cold migration. + arglist = [ + '--host', 'fakehost', self.server.id, + ] + verifylist = [ + ('live', None), + ('live_migration', False), + ('host', 'fakehost'), + ('block_migration', False), + ('disk_overcommit', False), + ('wait', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + + # Make sure it's the error we expect. + self.assertIn("--live-migration must be specified if " + "--block-migration, --disk-overcommit or --host is " + "specified", six.text_type(ex)) + self.servers_mock.get.assert_called_with(self.server.id) + self.assertNotCalled(self.servers_mock.live_migrate) + self.assertNotCalled(self.servers_mock.migrate) + def test_server_live_migrate(self): arglist = [ '--live', 'fakehost', self.server.id, ] verifylist = [ ('live', 'fakehost'), + ('live_migration', False), + ('host', None), ('block_migration', False), ('disk_overcommit', False), ('wait', False), @@ -2430,7 +2459,8 @@ def test_server_live_migrate(self): self.app.client_manager.compute.api_version = \ api_versions.APIVersion('2.24') - result = self.cmd.take_action(parsed_args) + with mock.patch.object(self.cmd.log, 'warning') as mock_warning: + result = self.cmd.take_action(parsed_args) self.servers_mock.get.assert_called_with(self.server.id) self.server.live_migrate.assert_called_with(block_migration=False, @@ -2438,6 +2468,132 @@ def test_server_live_migrate(self): host='fakehost') self.assertNotCalled(self.servers_mock.migrate) self.assertIsNone(result) + # A warning should have been logged for using --live. + mock_warning.assert_called_once() + self.assertIn('The --live option has been deprecated.', + six.text_type(mock_warning.call_args[0][0])) + + def test_server_live_migrate_host_pre_2_30(self): + # Tests that the --host option is not supported for --live-migration + # before microversion 2.30 (the test defaults to 2.1). + arglist = [ + '--live-migration', '--host', 'fakehost', self.server.id, + ] + verifylist = [ + ('live', None), + ('live_migration', True), + ('host', 'fakehost'), + ('block_migration', False), + ('disk_overcommit', False), + ('wait', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + + # Make sure it's the error we expect. + self.assertIn('--os-compute-api-version 2.30 or greater is required ' + 'when using --host', six.text_type(ex)) + + self.servers_mock.get.assert_called_with(self.server.id) + self.assertNotCalled(self.servers_mock.live_migrate) + self.assertNotCalled(self.servers_mock.migrate) + + def test_server_live_migrate_no_host(self): + # Tests the --live-migration option without --host or --live. + arglist = [ + '--live-migration', self.server.id, + ] + verifylist = [ + ('live', None), + ('live_migration', True), + ('host', None), + ('block_migration', False), + ('disk_overcommit', False), + ('wait', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + with mock.patch.object(self.cmd.log, 'warning') as mock_warning: + result = self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.server.live_migrate.assert_called_with(block_migration=False, + disk_over_commit=False, + host=None) + self.assertNotCalled(self.servers_mock.migrate) + self.assertIsNone(result) + # Since --live wasn't used a warning shouldn't have been logged. + mock_warning.assert_not_called() + + def test_server_live_migrate_with_host(self): + # Tests the --live-migration option with --host but no --live. + # This requires --os-compute-api-version >= 2.30 so the test uses 2.30. + arglist = [ + '--live-migration', '--host', 'fakehost', self.server.id, + ] + verifylist = [ + ('live', None), + ('live_migration', True), + ('host', 'fakehost'), + ('block_migration', False), + ('disk_overcommit', False), + ('wait', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.30') + + result = self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + # No disk_overcommit with microversion >= 2.25. + self.server.live_migrate.assert_called_with(block_migration=False, + host='fakehost') + self.assertNotCalled(self.servers_mock.migrate) + self.assertIsNone(result) + + def test_server_live_migrate_without_host_override_live(self): + # Tests the --live-migration option without --host and with --live. + # The --live-migration option will take precedence and a warning is + # logged for using --live. + arglist = [ + '--live', 'fakehost', '--live-migration', self.server.id, + ] + verifylist = [ + ('live', 'fakehost'), + ('live_migration', True), + ('host', None), + ('block_migration', False), + ('disk_overcommit', False), + ('wait', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + with mock.patch.object(self.cmd.log, 'warning') as mock_warning: + result = self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.server.live_migrate.assert_called_with(block_migration=False, + disk_over_commit=False, + host=None) + self.assertNotCalled(self.servers_mock.migrate) + self.assertIsNone(result) + # A warning should have been logged for using --live. + mock_warning.assert_called_once() + self.assertIn('The --live option has been deprecated.', + six.text_type(mock_warning.call_args[0][0])) + + def test_server_live_migrate_live_and_host_mutex(self): + # Tests specifying both the --live and --host options which are in a + # mutex group so argparse should fail. + arglist = [ + '--live', 'fakehost', '--host', 'fakehost', self.server.id, + ] + self.assertRaises(utils.ParserException, + self.check_parser, self.cmd, arglist, verify_args=[]) def test_server_block_live_migrate(self): arglist = [ diff --git a/releasenotes/notes/bug-1411190-live-migration-host-655ae6befa6a3de2.yaml b/releasenotes/notes/bug-1411190-live-migration-host-655ae6befa6a3de2.yaml new file mode 100644 index 0000000000..927d6a6451 --- /dev/null +++ b/releasenotes/notes/bug-1411190-live-migration-host-655ae6befa6a3de2.yaml @@ -0,0 +1,27 @@ +--- +deprecations: + - | + The ``--live`` option on the ``openstack server migrate`` command has + been deprecated and is being replaced with two new options: + + * ``--live-migration``: This will signal that the migration is a live + migration. + * ``--host``: This can be used to request a target host for the live + migration but requires ``--os-compute-api-version`` 2.30 or greater + so the requested host can be validated by the scheduler. + + The ``--live`` option is problematic in that it requires a host and + prior to compute API version 2.30, specifying a host during live migration + will bypass validation by the scheduler which could result in failures to + actually migrate the server to the specified host or over-subscribe the + host. + + The ``--live`` and ``--host`` options are mutually exclusive. Furthermore, + if both the ``--live`` and ``--live-migration`` options are used the + ``--live-migration`` option takes priority. +fixes: + - | + `Bug 1411190`_ has been fixed by providing a ``--live-migration`` and + ``--host`` option to the ``openstack server migrate`` command. + + .. _Bug 1411190: https://bugs.launchpad.net/python-openstackclient/+bug/1411190 From ef1fd388154eee11b9e83f80e5004670fdffb6cc Mon Sep 17 00:00:00 2001 From: zhangbailin Date: Mon, 6 May 2019 19:06:55 +0800 Subject: [PATCH 2067/3095] Add changes-before attribute to server list Closes-Bug: #1827844 Part of bp support-to-query-nova-resources-filter-by-changes-before Change-Id: I4f28168188973730247bcbcb70ba0e70eb81e3be --- openstackclient/compute/v2/server.py | 27 ++++++- .../functional/compute/v2/test_server.py | 78 +++++++++++++++++++ .../tests/unit/compute/v2/test_server.py | 66 ++++++++++++++++ .../notes/bug-1827844-8f1de120087c6a22.yaml | 6 ++ 4 files changed, 175 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/bug-1827844-8f1de120087c6a22.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index cb9f8d43eb..bc6b137bf1 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1128,13 +1128,22 @@ def get_parser(self, prog_name): default=False, help=_('Only display deleted servers (Admin only).') ) + parser.add_argument( + '--changes-before', + metavar='', + default=None, + help=_("List only servers changed before a certain point of time. " + "The provided time should be an ISO 8061 formatted time " + "(e.g., 2016-03-05T06:27:59Z). " + "(Supported by API versions '2.66' - '2.latest')") + ) parser.add_argument( '--changes-since', metavar='', default=None, help=_("List only servers changed after a certain point of time." - " The provided time should be an ISO 8061 formatted time." - " ex 2016-03-04T06:27:59Z .") + " The provided time should be an ISO 8061 formatted time" + " (e.g., 2016-03-04T06:27:59Z).") ) return parser @@ -1188,10 +1197,24 @@ def take_action(self, parsed_args): 'all_tenants': parsed_args.all_projects, 'user_id': user_id, 'deleted': parsed_args.deleted, + 'changes-before': parsed_args.changes_before, 'changes-since': parsed_args.changes_since, } LOG.debug('search options: %s', search_opts) + if search_opts['changes-before']: + if compute_client.api_version < api_versions.APIVersion('2.66'): + msg = _('--os-compute-api-version 2.66 or later is required') + raise exceptions.CommandError(msg) + + try: + timeutils.parse_isotime(search_opts['changes-before']) + except ValueError: + raise exceptions.CommandError( + _('Invalid changes-before value: %s') % + search_opts['changes-before'] + ) + if search_opts['changes-since']: try: timeutils.parse_isotime(search_opts['changes-since']) diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py index c8fb44d380..6330ac98d5 100644 --- a/openstackclient/tests/functional/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -63,6 +63,84 @@ def test_server_list(self): self.assertNotIn(name1, col_name) self.assertIn(name2, col_name) + def test_server_list_with_changes_before(self): + """Test server list. + + Getting the servers list with updated_at time equal or + before than changes-before. + """ + cmd_output = self.server_create() + server_name1 = cmd_output['name'] + + cmd_output = self.server_create() + server_name2 = cmd_output['name'] + updated_at2 = cmd_output['updated'] + + cmd_output = self.server_create() + server_name3 = cmd_output['name'] + + cmd_output = json.loads(self.openstack( + '--os-compute-api-version 2.66 ' + + 'server list -f json ' + '--changes-before ' + updated_at2 + )) + + col_updated = [server["Name"] for server in cmd_output] + self.assertIn(server_name1, col_updated) + self.assertIn(server_name2, col_updated) + self.assertNotIn(server_name3, col_updated) + + def test_server_list_with_changes_since(self): + """Test server list. + + Getting the servers list with updated_at time equal or + later than changes-since. + """ + cmd_output = self.server_create() + server_name1 = cmd_output['name'] + cmd_output = self.server_create() + server_name2 = cmd_output['name'] + updated_at2 = cmd_output['updated'] + cmd_output = self.server_create() + server_name3 = cmd_output['name'] + + cmd_output = json.loads(self.openstack( + 'server list -f json ' + '--changes-since ' + updated_at2 + )) + + col_updated = [server["Name"] for server in cmd_output] + self.assertNotIn(server_name1, col_updated) + self.assertIn(server_name2, col_updated) + self.assertIn(server_name3, col_updated) + + def test_server_list_with_changes_before_and_changes_since(self): + """Test server list. + + Getting the servers list with updated_at time equal or before than + changes-before and equal or later than changes-since. + """ + cmd_output = self.server_create() + server_name1 = cmd_output['name'] + cmd_output = self.server_create() + server_name2 = cmd_output['name'] + updated_at2 = cmd_output['updated'] + cmd_output = self.server_create() + server_name3 = cmd_output['name'] + updated_at3 = cmd_output['updated'] + + cmd_output = json.loads(self.openstack( + '--os-compute-api-version 2.66 ' + + 'server list -f json ' + + '--changes-since ' + updated_at2 + + ' --changes-before ' + updated_at3 + )) + + col_updated = [server["Name"] for server in cmd_output] + self.assertNotIn(server_name1, col_updated) + self.assertIn(server_name2, col_updated) + self.assertIn(server_name3, col_updated) + def test_server_set(self): """Test server create, delete, set, show""" cmd_output = self.server_create() diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index c30af8fb8e..f0c8843b5d 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -1991,6 +1991,7 @@ def setUp(self): 'user_id': None, 'deleted': False, 'changes-since': None, + 'changes-before': None, } # Default params of the core function of the command in the case of no @@ -2272,6 +2273,71 @@ def test_server_list_with_invalid_changes_since(self, mock_parse_isotime): 'Invalid time value' ) + def test_server_list_v266_with_changes_before(self): + self.app.client_manager.compute.api_version = ( + api_versions.APIVersion('2.66')) + arglist = [ + '--changes-before', '2016-03-05T06:27:59Z', + '--deleted' + ] + verifylist = [ + ('changes_before', '2016-03-05T06:27:59Z'), + ('deleted', True), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.search_opts['changes-before'] = '2016-03-05T06:27:59Z' + self.search_opts['deleted'] = True + self.servers_mock.list.assert_called_with(**self.kwargs) + + self.assertEqual(self.columns, columns) + self.assertEqual(tuple(self.data), tuple(data)) + + @mock.patch.object(timeutils, 'parse_isotime', side_effect=ValueError) + def test_server_list_v266_with_invalid_changes_before( + self, mock_parse_isotime): + self.app.client_manager.compute.api_version = ( + api_versions.APIVersion('2.66')) + + arglist = [ + '--changes-before', 'Invalid time value', + ] + verifylist = [ + ('changes_before', 'Invalid time value'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('Invalid changes-before value: Invalid time ' + 'value', str(e)) + mock_parse_isotime.assert_called_once_with( + 'Invalid time value' + ) + + def test_server_with_changes_before_older_version(self): + self.app.client_manager.compute.api_version = ( + api_versions.APIVersion('2.65')) + + arglist = [ + '--changes-before', '2016-03-05T06:27:59Z', + '--deleted' + ] + verifylist = [ + ('changes_before', '2016-03-05T06:27:59Z'), + ('deleted', True), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, + parsed_args) + def test_server_list_v269_with_partial_constructs(self): self.app.client_manager.compute.api_version = \ api_versions.APIVersion('2.69') diff --git a/releasenotes/notes/bug-1827844-8f1de120087c6a22.yaml b/releasenotes/notes/bug-1827844-8f1de120087c6a22.yaml new file mode 100644 index 0000000000..56b69c1cde --- /dev/null +++ b/releasenotes/notes/bug-1827844-8f1de120087c6a22.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--changes-before`` option to the ``server list`` command. + This requires Compute API version '2.66' or later. + [:lpbug: `1827844`] From eb399c52ad45285ac50ea81bd3a8e4bc459544a5 Mon Sep 17 00:00:00 2001 From: Matt Riedemann Date: Wed, 12 Jun 2019 11:23:06 -0400 Subject: [PATCH 2068/3095] Add server event command documentation for compute API 2.21 The 2.21 compute API microversion allows listing instance action events and getting action event details for a deleted server (which can be useful for auditing until the deleted server is purged). As far as OSC is concerned it's just a matter of specifying --os-compute-api-version 2.21 or higher when listing events or showing event details, so this change mentions 2.21 in the description of those commands. Related to nova blueprint os-instance-actions-read-deleted-instances Change-Id: If276c794f448b6fa5b0845499f3507a159acab85 --- openstackclient/compute/v2/server_event.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/openstackclient/compute/v2/server_event.py b/openstackclient/compute/v2/server_event.py index c7d2e2e385..6d33d02d50 100644 --- a/openstackclient/compute/v2/server_event.py +++ b/openstackclient/compute/v2/server_event.py @@ -28,7 +28,10 @@ class ListServerEvent(command.Lister): - _description = _("List recent events of a server") + _description = _( + "List recent events of a server. " + "Specify ``--os-compute-api-version 2.21`` " + "or higher to show events for a deleted server.") def get_parser(self, prog_name): parser = super(ListServerEvent, self).get_parser(prog_name) @@ -92,8 +95,11 @@ def take_action(self, parsed_args): class ShowServerEvent(command.ShowOne): _description = _( - "Show server event details. Specify ``--os-compute-api-version 2.51`` " - "or higher to show events for non-admin users.") + "Show server event details. " + "Specify ``--os-compute-api-version 2.21`` " + "or higher to show event details for a deleted server. " + "Specify ``--os-compute-api-version 2.51`` " + "or higher to show event details for non-admin users.") def get_parser(self, prog_name): parser = super(ShowServerEvent, self).get_parser(prog_name) From 3df5f92b447e647cdfff76010d729d74c89b39b9 Mon Sep 17 00:00:00 2001 From: Matt Riedemann Date: Wed, 12 Jun 2019 11:39:54 -0400 Subject: [PATCH 2069/3095] Add server add/remove volume description for microversion 2.20 The compute API 2.20 microversion allows attaching and detaching a volume to/from a server with status SHELVED or SHELVED_OFFLOADED. For OSC this just means the user has to specify the appropriate minimum microversion to make that work, so this change mentions that in the "server add volume" and "server remove volume" command description. Related to nova blueprint volume-ops-when-shelved Change-Id: I4824175e5d9e124e3bd9e9a8fd5a89277efc6cff --- openstackclient/compute/v2/server.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 3e1deed59a..654440fc55 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -424,7 +424,10 @@ def take_action(self, parsed_args): class AddServerVolume(command.Command): - _description = _("Add volume to server") + _description = _( + "Add volume to server. " + "Specify ``--os-compute-api-version 2.20`` or higher to add a volume " + "to a server with status ``SHELVED`` or ``SHELVED_OFFLOADED``.") def get_parser(self, prog_name): parser = super(AddServerVolume, self).get_parser(prog_name) @@ -1949,7 +1952,11 @@ def take_action(self, parsed_args): class RemoveServerVolume(command.Command): - _description = _("Remove volume from server") + _description = _( + "Remove volume from server. " + "Specify ``--os-compute-api-version 2.20`` or higher to remove a " + "volume from a server with status ``SHELVED`` or " + "``SHELVED_OFFLOADED``.") def get_parser(self, prog_name): parser = super(RemoveServerVolume, self).get_parser(prog_name) From 1aad94349bcd8f4b772bd6f216e4af897d46053b Mon Sep 17 00:00:00 2001 From: Martin Chlumsky Date: Thu, 13 Jun 2019 23:16:53 -0400 Subject: [PATCH 2070/3095] Allow "server migrate" (not live) to take "--host" option Currently, doing a cold migration while specifying a target host is not possible however nova api supports it since version 2.56. This patch allows passing "--host" when doing a cold migration. It runs normally if --os-compute-api-version is 2.56 or greater and returns an error otherwise. Change-Id: I960109008096ce8bb4e4c8ca6ffb22c33aacd995 Story: 2003325 Task: 24359 --- openstackclient/compute/v2/server.py | 26 ++++++++----- .../tests/unit/compute/v2/test_server.py | 38 ++++++++++++++++--- ...er-migrate-with-host-4884a71903c5c8a9.yaml | 8 ++++ 3 files changed, 57 insertions(+), 15 deletions(-) create mode 100644 releasenotes/notes/add-server-migrate-with-host-4884a71903c5c8a9.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 3e1deed59a..1d022f0336 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1470,13 +1470,13 @@ def get_parser(self, prog_name): 'and ``--live-migration`` are used, ``--live-migration`` ' 'takes priority.'), ) - # TODO(mriedem): Add support for --os-compute-api-version >= 2.56 where - # you can cold migrate to a specified target host. host_group.add_argument( '--host', metavar='', - help=_('Live migrate the server to the specified host. Requires ' - '``--os-compute-api-version`` 2.30 or greater.'), + help=_('Migrate the server to the specified host. Requires ' + '``--os-compute-api-version`` 2.30 or greater when used ' + 'with the ``--live-migration`` option, otherwise requires ' + '``--os-compute-api-version`` 2.56 or greater.'), ) migration_group = parser.add_mutually_exclusive_group() migration_group.add_argument( @@ -1566,16 +1566,22 @@ def _show_progress(progress): kwargs['disk_over_commit'] = parsed_args.disk_overcommit server.live_migrate(**kwargs) else: - if (parsed_args.block_migration or parsed_args.disk_overcommit or - parsed_args.host): - # TODO(mriedem): Allow --host for cold migration if - # --os-compute-api-version >= 2.56. + if parsed_args.block_migration or parsed_args.disk_overcommit: raise exceptions.CommandError( "--live-migration must be specified if " - "--block-migration, --disk-overcommit or --host is " + "--block-migration or --disk-overcommit is " "specified") + if parsed_args.host: + if (compute_client.api_version < + api_versions.APIVersion('2.56')): + msg = _( + '--os-compute-api-version 2.56 or greater is ' + 'required to use --host without --live-migration.' + ) + raise exceptions.CommandError(msg) - server.migrate() + kwargs = {'host': parsed_args.host} if parsed_args.host else {} + server.migrate(**kwargs) if parsed_args.wait: if utils.wait_for_status( diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 4a37a81216..473de802a2 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -2528,6 +2528,32 @@ def test_server_migrate_no_options(self): self.assertNotCalled(self.servers_mock.live_migrate) self.assertIsNone(result) + def test_server_migrate_with_host_2_56(self): + # Tests that --host is allowed for a cold migration + # for microversion 2.56 and greater. + arglist = [ + '--host', 'fakehost', self.server.id, + ] + verifylist = [ + ('live', None), + ('live_migration', False), + ('host', 'fakehost'), + ('block_migration', False), + ('disk_overcommit', False), + ('wait', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.56') + + result = self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.server.migrate.assert_called_with(host='fakehost') + self.assertNotCalled(self.servers_mock.live_migrate) + self.assertIsNone(result) + def test_server_migrate_with_block_migration(self): arglist = [ '--block-migration', self.server.id, @@ -2566,8 +2592,9 @@ def test_server_migrate_with_disk_overcommit(self): self.assertNotCalled(self.servers_mock.live_migrate) self.assertNotCalled(self.servers_mock.migrate) - def test_server_migrate_with_host(self): - # Tests that --host is not allowed for a cold migration. + def test_server_migrate_with_host_pre_2_56(self): + # Tests that --host is not allowed for a cold migration + # before microversion 2.56 (the test defaults to 2.1). arglist = [ '--host', 'fakehost', self.server.id, ] @@ -2585,9 +2612,10 @@ def test_server_migrate_with_host(self): parsed_args) # Make sure it's the error we expect. - self.assertIn("--live-migration must be specified if " - "--block-migration, --disk-overcommit or --host is " - "specified", six.text_type(ex)) + self.assertIn('--os-compute-api-version 2.56 or greater is required ' + 'to use --host without --live-migration.', + six.text_type(ex)) + self.servers_mock.get.assert_called_with(self.server.id) self.assertNotCalled(self.servers_mock.live_migrate) self.assertNotCalled(self.servers_mock.migrate) diff --git a/releasenotes/notes/add-server-migrate-with-host-4884a71903c5c8a9.yaml b/releasenotes/notes/add-server-migrate-with-host-4884a71903c5c8a9.yaml new file mode 100644 index 0000000000..dd974ff0b3 --- /dev/null +++ b/releasenotes/notes/add-server-migrate-with-host-4884a71903c5c8a9.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Added the ability to specify ``--host`` with ``server migrate`` + (cold migration) to specify the target host of the migration. + Requires ``--os-compute-api-version`` 2.56 or greater to target a + specific host for the (cold) migration. + [Story `2003325 `_] From 187be0ac22be5d2d1c42d45e9299e62cfc3796ae Mon Sep 17 00:00:00 2001 From: Surya Seetharaman Date: Tue, 14 May 2019 18:35:54 +0200 Subject: [PATCH 2071/3095] Microversion 2.73: Support adding the reason behind a server lock This patch adds a new parameter ``--reason`` to ``openstack server lock`` command and ``--locked``, ``unlocked`` filtering parameters to ``openstack server list`` command. This can help users to provide a reason when locking the server and to filter instances based on their locked value from 2.73 microversion. Implements blueprint add-locked-reason Depends-On: https://review.opendev.org/#/c/661785/ Change-Id: Ib2714f98b24d47e570da8a6c231e765acd2ff595 --- lower-constraints.txt | 2 +- openstackclient/compute/v2/server.py | 47 +++++- .../tests/unit/compute/v2/test_server.py | 149 +++++++++++++++++- ...bp-add-locked-reason-425efd2def1144f1.yaml | 13 ++ requirements.txt | 2 +- 5 files changed, 206 insertions(+), 7 deletions(-) create mode 100644 releasenotes/notes/bp-add-locked-reason-425efd2def1144f1.yaml diff --git a/lower-constraints.txt b/lower-constraints.txt index 43d724a1e0..f387827948 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -100,7 +100,7 @@ python-mimeparse==1.6.0 python-mistralclient==3.1.0 python-muranoclient==0.8.2 python-neutronclient==6.7.0 -python-novaclient==10.0.0 +python-novaclient==14.1.0 python-octaviaclient==1.3.0 python-rsdclient==0.1.0 python-saharaclient==1.4.0 diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index cb9f8d43eb..b37b8c5294 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1136,6 +1136,21 @@ def get_parser(self, prog_name): " The provided time should be an ISO 8061 formatted time." " ex 2016-03-04T06:27:59Z .") ) + lock_group = parser.add_mutually_exclusive_group() + lock_group.add_argument( + '--locked', + action='store_true', + default=False, + help=_('Only display locked servers. ' + 'Requires ``--os-compute-api-version`` 2.73 or greater.'), + ) + lock_group.add_argument( + '--unlocked', + action='store_true', + default=False, + help=_('Only display unlocked servers. ' + 'Requires ``--os-compute-api-version`` 2.73 or greater.'), + ) return parser def take_action(self, parsed_args): @@ -1190,6 +1205,18 @@ def take_action(self, parsed_args): 'deleted': parsed_args.deleted, 'changes-since': parsed_args.changes_since, } + support_locked = (compute_client.api_version >= + api_versions.APIVersion('2.73')) + if not support_locked and (parsed_args.locked or parsed_args.unlocked): + msg = _('--os-compute-api-version 2.73 or greater is required to ' + 'use the (un)locked filter option.') + raise exceptions.CommandError(msg) + elif support_locked: + # Only from 2.73. + if parsed_args.locked: + search_opts['locked'] = True + if parsed_args.unlocked: + search_opts['locked'] = False LOG.debug('search options: %s', search_opts) if search_opts['changes-since']: @@ -1374,16 +1401,28 @@ def get_parser(self, prog_name): nargs='+', help=_('Server(s) to lock (name or ID)'), ) + parser.add_argument( + '--reason', + metavar='', + default=None, + help=_("Reason for locking the server(s). Requires " + "``--os-compute-api-version`` 2.73 or greater.") + ) return parser def take_action(self, parsed_args): compute_client = self.app.client_manager.compute + support_reason = compute_client.api_version >= api_versions.APIVersion( + '2.73') + if not support_reason and parsed_args.reason: + msg = _('--os-compute-api-version 2.73 or greater is required to ' + 'use the --reason option.') + raise exceptions.CommandError(msg) for server in parsed_args.server: - utils.find_resource( - compute_client.servers, - server, - ).lock() + serv = utils.find_resource(compute_client.servers, server) + (serv.lock(reason=parsed_args.reason) if support_reason + else serv.lock()) # FIXME(dtroyer): Here is what I want, how with argparse/cliff? diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index c30af8fb8e..c71a31cec8 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -90,7 +90,14 @@ def run_method_with_servers(self, method_name, server_count): for s in servers: method = getattr(s, method_name) - method.assert_called_with() + if method_name == 'lock': + version = self.app.client_manager.compute.api_version + if version >= api_versions.APIVersion('2.73'): + method.assert_called_with(reason=None) + else: + method.assert_called_with() + else: + method.assert_called_with() self.assertIsNone(result) @@ -2210,6 +2217,80 @@ def test_server_list_with_image(self): self.assertEqual(self.columns, columns) self.assertEqual(tuple(self.data), tuple(data)) + def test_server_list_with_locked_pre_v273(self): + + arglist = [ + '--locked' + ] + verifylist = [ + ('locked', True) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises(exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.73 or greater is required', str(ex)) + + def test_server_list_with_locked_v273(self): + + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.73') + arglist = [ + '--locked' + ] + verifylist = [ + ('locked', True) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.search_opts['locked'] = True + self.servers_mock.list.assert_called_with(**self.kwargs) + + self.assertEqual(self.columns, columns) + self.assertEqual(tuple(self.data), tuple(data)) + + def test_server_list_with_unlocked_v273(self): + + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.73') + arglist = [ + '--unlocked' + ] + verifylist = [ + ('unlocked', True) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.search_opts['locked'] = False + self.servers_mock.list.assert_called_with(**self.kwargs) + + self.assertEqual(self.columns, columns) + self.assertEqual(tuple(self.data), tuple(data)) + + def test_server_list_with_locked_and_unlocked_v273(self): + + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.73') + arglist = [ + '--locked', + '--unlocked' + ] + verifylist = [ + ('locked', True), + ('unlocked', True) + ] + + ex = self.assertRaises( + utils.ParserException, + self.check_parser, self.cmd, arglist, verifylist) + self.assertIn('Argument parse failed', str(ex)) + def test_server_list_with_flavor(self): arglist = [ @@ -2336,6 +2417,72 @@ def test_server_lock_one_server(self): def test_server_lock_multi_servers(self): self.run_method_with_servers('lock', 3) + def test_server_lock_with_reason(self): + server = compute_fakes.FakeServer.create_one_server() + arglist = [ + server.id, + '--reason', "blah", + ] + verifylist = [ + ('reason', "blah"), + ('server', [server.id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises(exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.73 or greater is required', str(ex)) + + +class TestServerLockV273(TestServerLock): + + def setUp(self): + super(TestServerLockV273, self).setUp() + + self.server = compute_fakes.FakeServer.create_one_server( + methods=self.methods) + + # This is the return value for utils.find_resource() + self.servers_mock.get.return_value = self.server + + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.73') + + # Get the command object to test + self.cmd = server.LockServer(self.app, None) + + def test_server_lock_with_reason(self): + arglist = [ + self.server.id, + '--reason', "blah", + ] + verifylist = [ + ('reason', "blah"), + ('server', [self.server.id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.servers_mock.get.assert_called_with(self.server.id) + self.server.lock.assert_called_with(reason="blah") + + def test_server_lock_multi_servers_with_reason(self): + server2 = compute_fakes.FakeServer.create_one_server( + methods=self.methods) + arglist = [ + self.server.id, server2.id, + '--reason', "choo..choo", + ] + verifylist = [ + ('reason', "choo..choo"), + ('server', [self.server.id, server2.id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.assertEqual(2, self.servers_mock.get.call_count) + self.server.lock.assert_called_with(reason="choo..choo") + self.assertEqual(2, self.server.lock.call_count) + class TestServerMigrate(TestServer): diff --git a/releasenotes/notes/bp-add-locked-reason-425efd2def1144f1.yaml b/releasenotes/notes/bp-add-locked-reason-425efd2def1144f1.yaml new file mode 100644 index 0000000000..e9f6cc6d9e --- /dev/null +++ b/releasenotes/notes/bp-add-locked-reason-425efd2def1144f1.yaml @@ -0,0 +1,13 @@ +--- +features: + - Add ``--reason`` option to the ``server lock`` command to specify a reason + when locking a server. + Requires ``–os-compute-api-version`` 2.73 or greater. + - Add ``--locked`` option to the ``server list`` command to list only locked + servers. + Requires ``–os-compute-api-version`` 2.73 or greater. + - Add ``--unlocked`` option to the ``server list`` command list only unlocked + servers. + Requires ``–os-compute-api-version`` 2.73 or greater. + [Blueprint `add-locked-reason `_] + \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index aa5debf4ed..3db6caef0b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,5 +13,5 @@ oslo.i18n>=3.15.3 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 python-glanceclient>=2.8.0 # Apache-2.0 python-keystoneclient>=3.17.0 # Apache-2.0 -python-novaclient>=10.0.0 # Apache-2.0 +python-novaclient>=14.1.0 # Apache-2.0 python-cinderclient>=3.3.0 # Apache-2.0 From fa5046a3dbf8cf398b9a05c68e6ee4badbb14ee7 Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Mon, 15 May 2017 04:00:32 +0000 Subject: [PATCH 2072/3095] Use cliff formattable columns in identity commands Partial-Bug: #1687955 Partially implement blueprint osc-formattable-columns Change-Id: Ia13314a012b3a7363ffb24a13c79c6ecdff1ed7b --- openstackclient/identity/v2_0/catalog.py | 34 ++++++------ openstackclient/identity/v2_0/project.py | 3 +- openstackclient/identity/v2_0/user.py | 41 ++++++++++---- openstackclient/identity/v3/catalog.py | 24 ++++---- .../identity/v3/identity_provider.py | 5 +- .../tests/unit/identity/v2_0/test_catalog.py | 55 ++++++++++++------- .../tests/unit/identity/v2_0/test_project.py | 5 +- .../tests/unit/identity/v2_0/test_user.py | 12 ++-- .../tests/unit/identity/v3/fakes.py | 3 +- .../tests/unit/identity/v3/test_catalog.py | 25 +++++---- .../identity/v3/test_identity_provider.py | 20 +++---- 11 files changed, 140 insertions(+), 87 deletions(-) diff --git a/openstackclient/identity/v2_0/catalog.py b/openstackclient/identity/v2_0/catalog.py index 993bdd5343..5d1e30626f 100644 --- a/openstackclient/identity/v2_0/catalog.py +++ b/openstackclient/identity/v2_0/catalog.py @@ -15,6 +15,7 @@ import logging +from cliff import columns as cliff_columns from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -26,20 +27,21 @@ LOG = logging.getLogger(__name__) -def _format_endpoints(eps=None): - if not eps: - return "" - ret = '' - for index, ep in enumerate(eps): - region = eps[index].get('region') - if region is None: - region = '' - ret += region + '\n' - for endpoint_type in ['publicURL', 'internalURL', 'adminURL']: - url = eps[index].get(endpoint_type) - if url: - ret += " %s: %s\n" % (endpoint_type, url) - return ret +class EndpointsColumn(cliff_columns.FormattableColumn): + def human_readable(self): + if not self._value: + return "" + ret = '' + for ep in self._value: + region = ep.get('region') + if region is None: + region = '' + ret += region + '\n' + for endpoint_type in ['publicURL', 'internalURL', 'adminURL']: + url = ep.get(endpoint_type) + if url: + ret += " %s: %s\n" % (endpoint_type, url) + return ret class ListCatalog(command.Lister): @@ -60,7 +62,7 @@ def take_action(self, parsed_args): (utils.get_dict_properties( s, columns, formatters={ - 'Endpoints': _format_endpoints, + 'Endpoints': EndpointsColumn, }, ) for s in data)) @@ -91,7 +93,7 @@ def take_action(self, parsed_args): if (service.get('name') == parsed_args.service or service.get('type') == parsed_args.service): data = service - data['endpoints'] = _format_endpoints(data['endpoints']) + data['endpoints'] = EndpointsColumn(data['endpoints']) if 'endpoints_links' in data: data.pop('endpoints_links') break diff --git a/openstackclient/identity/v2_0/project.py b/openstackclient/identity/v2_0/project.py index 04d422ecdb..df57574b0b 100644 --- a/openstackclient/identity/v2_0/project.py +++ b/openstackclient/identity/v2_0/project.py @@ -18,6 +18,7 @@ import logging from keystoneauth1 import exceptions as ks_exc +from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -297,7 +298,7 @@ def take_action(self, parsed_args): if v is not None: properties[k] = v - info['properties'] = utils.format_dict(properties) + info['properties'] = format_columns.DictColumn(properties) return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/identity/v2_0/user.py b/openstackclient/identity/v2_0/user.py index 2a3dde6b52..0675877b9b 100644 --- a/openstackclient/identity/v2_0/user.py +++ b/openstackclient/identity/v2_0/user.py @@ -15,8 +15,10 @@ """Identity v2.0 User action implementations""" +import functools import logging +from cliff import columns as cliff_columns from keystoneauth1 import exceptions as ks_exc from osc_lib.command import command from osc_lib import exceptions @@ -29,6 +31,31 @@ LOG = logging.getLogger(__name__) +class ProjectColumn(cliff_columns.FormattableColumn): + """Formattable column for project column. + + Unlike the parent FormattableColumn class, the initializer of the + class takes project_cache as the second argument. + osc_lib.utils.get_item_properties instantiate cliff FormattableColumn + object with a single parameter "column value", so you need to pass + a partially initialized class like + ``functools.partial(ProjectColumn, project_cache)``. + """ + + def __init__(self, value, project_cache=None): + super(ProjectColumn, self).__init__(value) + self.project_cache = project_cache or {} + + def human_readable(self): + project = self._value + if not project: + return "" + if project in self.project_cache.keys(): + return self.project_cache[project].name + else: + return project + + class CreateUser(command.ShowOne): _description = _("Create new user") @@ -187,15 +214,7 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - - def _format_project(project): - if not project: - return "" - if project in project_cache.keys(): - return project_cache[project].name - else: - return project - + formatters = {} project = None if parsed_args.project: project = utils.find_resource( @@ -227,6 +246,8 @@ def _format_project(project): except Exception: # Just forget it if there's any trouble pass + formatters['tenantId'] = functools.partial( + ProjectColumn, project_cache=project_cache) else: columns = column_headers = ('ID', 'Name') data = identity_client.users.list(tenant_id=project) @@ -251,7 +272,7 @@ def _format_project(project): (utils.get_item_properties( s, columns, mixed_case_fields=('tenantId',), - formatters={'tenantId': _format_project}, + formatters=formatters, ) for s in data)) diff --git a/openstackclient/identity/v3/catalog.py b/openstackclient/identity/v3/catalog.py index 28f4fadad5..59430c4ce0 100644 --- a/openstackclient/identity/v3/catalog.py +++ b/openstackclient/identity/v3/catalog.py @@ -15,6 +15,7 @@ import logging +from cliff import columns as cliff_columns from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -26,15 +27,16 @@ LOG = logging.getLogger(__name__) -def _format_endpoints(eps=None): - if not eps: - return "" - ret = '' - for ep in eps: - region = ep.get('region_id') or ep.get('region') or '' - ret += region + '\n' - ret += " %s: %s\n" % (ep['interface'], ep['url']) - return ret +class EndpointsColumn(cliff_columns.FormattableColumn): + def human_readable(self): + if not self._value: + return "" + ret = '' + for ep in self._value: + region = ep.get('region_id') or ep.get('region') or '' + ret += region + '\n' + ret += " %s: %s\n" % (ep['interface'], ep['url']) + return ret class ListCatalog(command.Lister): @@ -55,7 +57,7 @@ def take_action(self, parsed_args): (utils.get_dict_properties( s, columns, formatters={ - 'Endpoints': _format_endpoints, + 'Endpoints': EndpointsColumn, }, ) for s in data)) @@ -86,7 +88,7 @@ def take_action(self, parsed_args): if (service.get('name') == parsed_args.service or service.get('type') == parsed_args.service): data = dict(service) - data['endpoints'] = _format_endpoints(data['endpoints']) + data['endpoints'] = EndpointsColumn(data['endpoints']) if 'links' in data: data.pop('links') break diff --git a/openstackclient/identity/v3/identity_provider.py b/openstackclient/identity/v3/identity_provider.py index d8951d31c2..b331518213 100644 --- a/openstackclient/identity/v3/identity_provider.py +++ b/openstackclient/identity/v3/identity_provider.py @@ -15,6 +15,7 @@ import logging +from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -103,7 +104,7 @@ def take_action(self, parsed_args): enabled=parsed_args.enabled) idp._info.pop('links', None) - remote_ids = utils.format_list(idp._info.pop('remote_ids', [])) + remote_ids = format_columns.ListColumn(idp._info.pop('remote_ids', [])) idp._info['remote_ids'] = remote_ids return zip(*sorted(six.iteritems(idp._info))) @@ -245,6 +246,6 @@ def take_action(self, parsed_args): id=parsed_args.identity_provider) idp._info.pop('links', None) - remote_ids = utils.format_list(idp._info.pop('remote_ids', [])) + remote_ids = format_columns.ListColumn(idp._info.pop('remote_ids', [])) idp._info['remote_ids'] = remote_ids return zip(*sorted(six.iteritems(idp._info))) diff --git a/openstackclient/tests/unit/identity/v2_0/test_catalog.py b/openstackclient/tests/unit/identity/v2_0/test_catalog.py index 65af58a24e..362dec0885 100644 --- a/openstackclient/tests/unit/identity/v2_0/test_catalog.py +++ b/openstackclient/tests/unit/identity/v2_0/test_catalog.py @@ -71,17 +71,9 @@ def test_catalog_list(self): datalist = (( 'supernova', 'compute', - 'one\n publicURL: https://public.one.example.com\n ' - 'internalURL: https://internal.one.example.com\n ' - 'adminURL: https://admin.one.example.com\n' - 'two\n publicURL: https://public.two.example.com\n ' - 'internalURL: https://internal.two.example.com\n ' - 'adminURL: https://admin.two.example.com\n' - '\n publicURL: https://public.none.example.com\n ' - 'internalURL: https://internal.none.example.com\n ' - 'adminURL: https://admin.none.example.com\n', + catalog.EndpointsColumn(self.service_catalog['endpoints']), ), ) - self.assertEqual(datalist, tuple(data)) + self.assertListItemEqual(datalist, tuple(data)) def test_catalog_list_with_endpoint_url(self): attr = { @@ -121,11 +113,9 @@ def test_catalog_list_with_endpoint_url(self): datalist = (( 'supernova', 'compute', - 'one\n publicURL: https://public.one.example.com\n' - 'two\n publicURL: https://public.two.example.com\n ' - 'internalURL: https://internal.two.example.com\n' + catalog.EndpointsColumn(service_catalog['endpoints']), ), ) - self.assertEqual(datalist, tuple(data)) + self.assertListItemEqual(datalist, tuple(data)) class TestCatalogShow(TestCatalog): @@ -160,6 +150,18 @@ def test_catalog_show(self): collist = ('endpoints', 'id', 'name', 'type') self.assertEqual(collist, columns) datalist = ( + catalog.EndpointsColumn(self.service_catalog['endpoints']), + self.service_catalog.id, + 'supernova', + 'compute', + ) + self.assertItemEqual(datalist, data) + + +class TestFormatColumns(TestCatalog): + def test_endpoints_column_human_readabale(self): + col = catalog.EndpointsColumn(self.service_catalog['endpoints']) + self.assertEqual( 'one\n publicURL: https://public.one.example.com\n ' 'internalURL: https://internal.one.example.com\n ' 'adminURL: https://admin.one.example.com\n' @@ -169,8 +171,23 @@ def test_catalog_show(self): '\n publicURL: https://public.none.example.com\n ' 'internalURL: https://internal.none.example.com\n ' 'adminURL: https://admin.none.example.com\n', - self.service_catalog.id, - 'supernova', - 'compute', - ) - self.assertEqual(datalist, data) + col.human_readable()) + + def test_endpoints_column_human_readable_with_partial_endpoint_urls(self): + endpoints = [ + { + 'region': 'one', + 'publicURL': 'https://public.one.example.com', + }, + { + 'region': 'two', + 'publicURL': 'https://public.two.example.com', + 'internalURL': 'https://internal.two.example.com', + }, + ] + col = catalog.EndpointsColumn(endpoints) + self.assertEqual( + 'one\n publicURL: https://public.one.example.com\n' + 'two\n publicURL: https://public.two.example.com\n ' + 'internalURL: https://internal.two.example.com\n', + col.human_readable()) diff --git a/openstackclient/tests/unit/identity/v2_0/test_project.py b/openstackclient/tests/unit/identity/v2_0/test_project.py index c726f2a671..7af7b3946e 100644 --- a/openstackclient/tests/unit/identity/v2_0/test_project.py +++ b/openstackclient/tests/unit/identity/v2_0/test_project.py @@ -16,6 +16,7 @@ import mock from keystoneauth1 import exceptions as ks_exc +from osc_lib.cli import format_columns from osc_lib import exceptions from osc_lib import utils @@ -640,9 +641,9 @@ def test_project_show(self): True, self.fake_proj_show.id, self.fake_proj_show.name, - '', + format_columns.DictColumn({}), ) - self.assertEqual(datalist, data) + self.assertItemEqual(datalist, data) class TestProjectUnset(TestProject): diff --git a/openstackclient/tests/unit/identity/v2_0/test_user.py b/openstackclient/tests/unit/identity/v2_0/test_user.py index a8b9497ecf..0a0d4b3651 100644 --- a/openstackclient/tests/unit/identity/v2_0/test_user.py +++ b/openstackclient/tests/unit/identity/v2_0/test_user.py @@ -482,7 +482,7 @@ def test_user_list_no_options(self): self.users_mock.list.assert_called_with(tenant_id=None) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, tuple(data)) + self.assertListItemEqual(self.datalist, tuple(data)) def test_user_list_project(self): arglist = [ @@ -502,7 +502,7 @@ def test_user_list_project(self): self.users_mock.list.assert_called_with(tenant_id=project_id) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, tuple(data)) + self.assertListItemEqual(self.datalist, tuple(data)) def test_user_list_long(self): arglist = [ @@ -525,11 +525,13 @@ def test_user_list_long(self): datalist = (( self.fake_user_l.id, self.fake_user_l.name, - self.fake_project_l.name, + user.ProjectColumn( + self.fake_project_l.id, + {self.fake_project_l.id: self.fake_project_l}), self.fake_user_l.email, True, ), ) - self.assertEqual(datalist, tuple(data)) + self.assertListItemEqual(datalist, tuple(data)) class TestUserSet(TestUser): @@ -817,4 +819,4 @@ def test_user_show(self): self.fake_user.name, self.fake_project.id, ) - self.assertEqual(datalist, data) + self.assertItemEqual(datalist, data) diff --git a/openstackclient/tests/unit/identity/v3/fakes.py b/openstackclient/tests/unit/identity/v3/fakes.py index 27ee9fd026..5caf156b2c 100644 --- a/openstackclient/tests/unit/identity/v3/fakes.py +++ b/openstackclient/tests/unit/identity/v3/fakes.py @@ -20,6 +20,7 @@ from keystoneauth1 import access from keystoneauth1 import fixture import mock +from osc_lib.cli import format_columns from openstackclient.tests.unit import fakes from openstackclient.tests.unit import utils @@ -300,7 +301,7 @@ idp_id = 'test_idp' idp_description = 'super exciting IdP description' idp_remote_ids = ['entity1', 'entity2'] -formatted_idp_remote_ids = 'entity1, entity2' +formatted_idp_remote_ids = format_columns.ListColumn(idp_remote_ids) IDENTITY_PROVIDER = { 'id': idp_id, diff --git a/openstackclient/tests/unit/identity/v3/test_catalog.py b/openstackclient/tests/unit/identity/v3/test_catalog.py index 53008e8cbc..ba076dbddf 100644 --- a/openstackclient/tests/unit/identity/v3/test_catalog.py +++ b/openstackclient/tests/unit/identity/v3/test_catalog.py @@ -91,12 +91,9 @@ def test_catalog_list(self): datalist = (( 'supernova', 'compute', - 'onlyone\n public: https://public.example.com\n' - 'onlyone\n admin: https://admin.example.com\n' - '\n internal: https://internal.example.com\n' - '\n none: https://none.example.com\n', + catalog.EndpointsColumn(self.fake_service['endpoints']), ), ) - self.assertEqual(datalist, tuple(data)) + self.assertListItemEqual(datalist, tuple(data)) class TestCatalogShow(TestCatalog): @@ -131,12 +128,20 @@ def test_catalog_show(self): collist = ('endpoints', 'id', 'name', 'type') self.assertEqual(collist, columns) datalist = ( - 'onlyone\n public: https://public.example.com\nonlyone\n' - ' admin: https://admin.example.com\n' - '\n internal: https://internal.example.com\n' - '\n none: https://none.example.com\n', + catalog.EndpointsColumn(self.fake_service['endpoints']), 'qwertyuiop', 'supernova', 'compute', ) - self.assertEqual(datalist, data) + self.assertItemEqual(datalist, data) + + +class TestFormatColumns(TestCatalog): + def test_endpoints_column_human_readabale(self): + col = catalog.EndpointsColumn(self.fake_service['endpoints']) + self.assertEqual( + 'onlyone\n public: https://public.example.com\n' + 'onlyone\n admin: https://admin.example.com\n' + '\n internal: https://internal.example.com\n' + '\n none: https://none.example.com\n', + col.human_readable()) diff --git a/openstackclient/tests/unit/identity/v3/test_identity_provider.py b/openstackclient/tests/unit/identity/v3/test_identity_provider.py index dc82ab7422..0163c6c8d4 100644 --- a/openstackclient/tests/unit/identity/v3/test_identity_provider.py +++ b/openstackclient/tests/unit/identity/v3/test_identity_provider.py @@ -90,7 +90,7 @@ def test_create_identity_provider_no_options(self): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_create_identity_provider_description(self): arglist = [ @@ -118,7 +118,7 @@ def test_create_identity_provider_description(self): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_create_identity_provider_remote_id(self): arglist = [ @@ -146,7 +146,7 @@ def test_create_identity_provider_remote_id(self): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_create_identity_provider_remote_ids_multiple(self): arglist = [ @@ -175,7 +175,7 @@ def test_create_identity_provider_remote_ids_multiple(self): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_create_identity_provider_remote_ids_file(self): arglist = [ @@ -208,7 +208,7 @@ def test_create_identity_provider_remote_ids_file(self): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_create_identity_provider_disabled(self): @@ -251,7 +251,7 @@ def test_create_identity_provider_disabled(self): identity_fakes.idp_id, identity_fakes.formatted_idp_remote_ids ) - self.assertEqual(datalist, data) + self.assertItemEqual(datalist, data) def test_create_identity_provider_domain_name(self): arglist = [ @@ -279,7 +279,7 @@ def test_create_identity_provider_domain_name(self): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_create_identity_provider_domain_id(self): arglist = [ @@ -307,7 +307,7 @@ def test_create_identity_provider_domain_id(self): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) class TestIdentityProviderDelete(TestIdentityProvider): @@ -383,7 +383,7 @@ def test_identity_provider_list_no_options(self): identity_fakes.domain_id, identity_fakes.idp_description, ), ) - self.assertEqual(datalist, tuple(data)) + self.assertListItemEqual(datalist, tuple(data)) class TestIdentityProviderSet(TestIdentityProvider): @@ -668,4 +668,4 @@ def test_identity_provider_show(self): identity_fakes.idp_id, identity_fakes.formatted_idp_remote_ids ) - self.assertEqual(datalist, data) + self.assertItemEqual(datalist, data) From 8d63e3f0c38bbcc44db2e82a7e3d0ff5488d45af Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Mon, 15 May 2017 04:00:53 +0000 Subject: [PATCH 2073/3095] Use cliff formattable columns in image commands Related functional tests are converted into JSON format. Otherwise, it is not easy to check results. Partial-Bug: #1687955 Partially implement blueprint osc-formattable-columns Change-Id: Ib82e15738544975fede0c54cc5eaf239f4c67277 --- openstackclient/image/v1/image.py | 33 ++++++++++--------- openstackclient/image/v2/image.py | 7 ++-- .../tests/functional/image/v1/test_image.py | 2 +- .../tests/functional/image/v2/test_image.py | 21 +++++------- .../unit/compute/v2/test_server_backup.py | 9 ++--- .../unit/compute/v2/test_server_image.py | 9 ++--- .../tests/unit/image/v1/test_image.py | 22 +++++++------ openstackclient/tests/unit/image/v2/fakes.py | 6 ++-- .../tests/unit/image/v2/test_image.py | 26 +++++++-------- 9 files changed, 69 insertions(+), 66 deletions(-) diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index caf3d54f47..c2dab3eec3 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -21,8 +21,10 @@ import os import sys +from cliff import columns as cliff_columns from glanceclient.common import utils as gc_utils from osc_lib.api import utils as api_utils +from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import utils @@ -46,19 +48,18 @@ LOG = logging.getLogger(__name__) -def _format_visibility(data): - """Return a formatted visibility string +class VisibilityColumn(cliff_columns.FormattableColumn): + def human_readable(self): + """Return a formatted visibility string - :param data: - The server's visibility (is_public) status value: True, False - :rtype: - A string formatted to public/private - """ + :rtype: + A string formatted to public/private + """ - if data: - return 'public' - else: - return 'private' + if self._value: + return 'public' + else: + return 'private' class CreateImage(command.ShowOne): @@ -268,7 +269,8 @@ def take_action(self, parsed_args): kwargs['data'].close() info.update(image._info) - info['properties'] = utils.format_dict(info.get('properties', {})) + info['properties'] = format_columns.DictColumn( + info.get('properties', {})) return zip(*sorted(six.iteritems(info))) @@ -429,8 +431,8 @@ def take_action(self, parsed_args): s, columns, formatters={ - 'is_public': _format_visibility, - 'properties': utils.format_dict, + 'is_public': VisibilityColumn, + 'properties': format_columns.DictColumn, }, ) for s in data) ) @@ -714,5 +716,6 @@ def take_action(self, parsed_args): if parsed_args.human_readable: if 'size' in info: info['size'] = utils.format_size(info['size']) - info['properties'] = utils.format_dict(info.get('properties', {})) + info['properties'] = format_columns.DictColumn( + info.get('properties', {})) return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 97169a9237..2b10c3adda 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -22,6 +22,7 @@ from glanceclient.common import utils as gc_utils from openstack.image import image_signer from osc_lib.api import utils as api_utils +from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -65,11 +66,11 @@ def _format_image(image): properties[key] = image.get(key) # format the tags if they are there - info['tags'] = utils.format_list(image.get('tags')) + info['tags'] = format_columns.ListColumn(image.get('tags')) # add properties back into the dictionary as a top-level key if properties: - info['properties'] = utils.format_dict(properties) + info['properties'] = format_columns.DictColumn(properties) return info @@ -656,7 +657,7 @@ def take_action(self, parsed_args): s, columns, formatters={ - 'tags': utils.format_list, + 'tags': format_columns.ListColumn, }, ) for s in data) ) diff --git a/openstackclient/tests/functional/image/v1/test_image.py b/openstackclient/tests/functional/image/v1/test_image.py index 30490bf516..b9774ab53d 100644 --- a/openstackclient/tests/functional/image/v1/test_image.py +++ b/openstackclient/tests/functional/image/v1/test_image.py @@ -104,6 +104,6 @@ def test_image_attributes(self): self.name )) self.assertEqual( - "a='b', c='d'", + {'a': 'b', 'c': 'd'}, json_output["properties"], ) diff --git a/openstackclient/tests/functional/image/v2/test_image.py b/openstackclient/tests/functional/image/v2/test_image.py index 3185c3bddf..264ba5199c 100644 --- a/openstackclient/tests/functional/image/v2/test_image.py +++ b/openstackclient/tests/functional/image/v2/test_image.py @@ -14,7 +14,6 @@ import uuid import fixtures -# from glanceclient import exc as image_exceptions from openstackclient.tests.functional.image import base @@ -81,7 +80,7 @@ def test_image_list_with_tag_filter(self): json_output = json.loads(self.openstack( 'image list --tag ' + self.image_tag + ' --long -f json' )) - for taglist in [img['Tags'].split(', ') for img in json_output]: + for taglist in [img['Tags'] for img in json_output]: self.assertIn( self.image_tag, taglist @@ -127,10 +126,8 @@ def test_image_attributes(self): 'image show -f json ' + self.name )) - # NOTE(dtroyer): Don't do a full-string compare so we are tolerant of - # new artributes in the returned data - self.assertIn("a='b'", json_output["properties"]) - self.assertIn("c='d'", json_output["properties"]) + self.assertIn("a", json_output["properties"]) + self.assertIn("c", json_output["properties"]) self.openstack( 'image unset ' + @@ -142,15 +139,13 @@ def test_image_attributes(self): 'image show -f json ' + self.name )) - # NOTE(dtroyer): Don't do a full-string compare so we are tolerant of - # new artributes in the returned data - self.assertNotIn("a='b'", json_output["properties"]) - self.assertNotIn("c='d'", json_output["properties"]) + self.assertNotIn("a", json_output["properties"]) + self.assertNotIn("c", json_output["properties"]) # Test tags self.assertNotIn( '01', - json_output["tags"].split(', ') + json_output["tags"] ) self.openstack( 'image set ' + @@ -163,7 +158,7 @@ def test_image_attributes(self): )) self.assertIn( '01', - json_output["tags"].split(', ') + json_output["tags"] ) self.openstack( @@ -177,7 +172,7 @@ def test_image_attributes(self): )) self.assertNotIn( '01', - json_output["tags"].split(', ') + json_output["tags"] ) def test_image_set_rename(self): diff --git a/openstackclient/tests/unit/compute/v2/test_server_backup.py b/openstackclient/tests/unit/compute/v2/test_server_backup.py index 9a481e0a67..24a94531a4 100644 --- a/openstackclient/tests/unit/compute/v2/test_server_backup.py +++ b/openstackclient/tests/unit/compute/v2/test_server_backup.py @@ -13,6 +13,7 @@ import mock +from osc_lib.cli import format_columns from osc_lib import exceptions from osc_lib import utils as common_utils @@ -69,7 +70,7 @@ def image_data(self, image): image['owner'], image['protected'], 'active', - common_utils.format_list(image.get('tags')), + format_columns.ListColumn(image.get('tags')), image['visibility'], ) return datalist @@ -134,7 +135,7 @@ def test_server_backup_defaults(self): ) self.assertEqual(self.image_columns(images[0]), columns) - self.assertEqual(self.image_data(images[0]), data) + self.assertItemEqual(self.image_data(images[0]), data) def test_server_backup_create_options(self): servers = self.setup_servers_mock(count=1) @@ -168,7 +169,7 @@ def test_server_backup_create_options(self): ) self.assertEqual(self.image_columns(images[0]), columns) - self.assertEqual(self.image_data(images[0]), data) + self.assertItemEqual(self.image_data(images[0]), data) @mock.patch.object(common_utils, 'wait_for_status', return_value=False) def test_server_backup_wait_fail(self, mock_wait_for_status): @@ -268,4 +269,4 @@ def test_server_backup_wait_ok(self, mock_wait_for_status): ) self.assertEqual(self.image_columns(images[0]), columns) - self.assertEqual(self.image_data(images[0]), data) + self.assertItemEqual(self.image_data(images[0]), data) diff --git a/openstackclient/tests/unit/compute/v2/test_server_image.py b/openstackclient/tests/unit/compute/v2/test_server_image.py index 47bd682f17..02e43129ed 100644 --- a/openstackclient/tests/unit/compute/v2/test_server_image.py +++ b/openstackclient/tests/unit/compute/v2/test_server_image.py @@ -12,6 +12,7 @@ # import mock +from osc_lib.cli import format_columns from osc_lib import exceptions from osc_lib import utils as common_utils @@ -67,7 +68,7 @@ def image_data(self, image): image['owner'], image['protected'], 'active', - common_utils.format_list(image.get('tags')), + format_columns.ListColumn(image.get('tags')), image['visibility'], ) return datalist @@ -129,7 +130,7 @@ def test_server_image_create_defaults(self): ) self.assertEqual(self.image_columns(images[0]), columns) - self.assertEqual(self.image_data(images[0]), data) + self.assertItemEqual(self.image_data(images[0]), data) def test_server_image_create_options(self): servers = self.setup_servers_mock(count=1) @@ -157,7 +158,7 @@ def test_server_image_create_options(self): ) self.assertEqual(self.image_columns(images[0]), columns) - self.assertEqual(self.image_data(images[0]), data) + self.assertItemEqual(self.image_data(images[0]), data) @mock.patch.object(common_utils, 'wait_for_status', return_value=False) def test_server_create_image_wait_fail(self, mock_wait_for_status): @@ -225,4 +226,4 @@ def test_server_create_image_wait_ok(self, mock_wait_for_status): ) self.assertEqual(self.image_columns(images[0]), columns) - self.assertEqual(self.image_data(images[0]), data) + self.assertItemEqual(self.image_data(images[0]), data) diff --git a/openstackclient/tests/unit/image/v1/test_image.py b/openstackclient/tests/unit/image/v1/test_image.py index 6babd4ffca..0997d76546 100644 --- a/openstackclient/tests/unit/image/v1/test_image.py +++ b/openstackclient/tests/unit/image/v1/test_image.py @@ -16,8 +16,9 @@ import copy import mock + +from osc_lib.cli import format_columns from osc_lib import exceptions -from osc_lib import utils from openstackclient.image.v1 import image from openstackclient.tests.unit import fakes @@ -58,7 +59,7 @@ class TestImageCreate(TestImage): new_image.min_ram, new_image.name, new_image.owner, - utils.format_dict(new_image.properties), + format_columns.DictColumn(new_image.properties), new_image.protected, ) @@ -106,7 +107,7 @@ def test_image_reserve_no_options(self): self.assertEqual(self.images_mock.update.call_args_list, []) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_image_reserve_options(self): mock_exception = { @@ -160,7 +161,7 @@ def test_image_reserve_options(self): self.assertEqual(self.images_mock.update.call_args_list, []) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) @mock.patch('openstackclient.image.v1.image.io.open', name='Open') def test_image_create_file(self, mock_open): @@ -224,7 +225,7 @@ def test_image_create_file(self, mock_open): self.assertEqual(self.images_mock.update.call_args_list, []) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) class TestImageDelete(TestImage): @@ -410,12 +411,13 @@ def test_image_list_long_option(self): '', '', '', - 'public', + image.VisibilityColumn(True), False, self._image.owner, - "Alpha='a', Beta='b', Gamma='g'", + format_columns.DictColumn( + {'Alpha': 'a', 'Beta': 'b', 'Gamma': 'g'}), ), ) - self.assertEqual(datalist, tuple(data)) + self.assertListItemEqual(datalist, tuple(data)) @mock.patch('osc_lib.api.utils.simple_filter') def test_image_list_property_option(self, sf_mock): @@ -742,7 +744,7 @@ class TestImageShow(TestImage): _image.min_ram, _image.name, _image.owner, - utils.format_dict(_image.properties), + format_columns.DictColumn(_image.properties), _image.protected, _image.size, ) @@ -773,7 +775,7 @@ def test_image_show(self): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_image_show_human_readable(self): arglist = [ diff --git a/openstackclient/tests/unit/image/v2/fakes.py b/openstackclient/tests/unit/image/v2/fakes.py index d82d81146f..f69a2bc311 100644 --- a/openstackclient/tests/unit/image/v2/fakes.py +++ b/openstackclient/tests/unit/image/v2/fakes.py @@ -19,7 +19,7 @@ from glanceclient.v2 import schemas import mock -from osc_lib import utils as common_utils +from osc_lib.cli import format_columns import warlock from openstackclient.tests.unit import fakes @@ -48,7 +48,7 @@ IMAGE_data = tuple((IMAGE[x] for x in sorted(IMAGE))) IMAGE_SHOW = copy.copy(IMAGE) -IMAGE_SHOW['tags'] = '' +IMAGE_SHOW['tags'] = format_columns.ListColumn(IMAGE_SHOW['tags']) IMAGE_SHOW_data = tuple((IMAGE_SHOW[x] for x in sorted(IMAGE_SHOW))) # Just enough v2 schema to do some testing @@ -281,7 +281,7 @@ def get_image_data(image=None): if x == 'tags': # The 'tags' should be format_list data_list.append( - common_utils.format_list(getattr(image, x))) + format_columns.ListColumn(getattr(image, x))) else: data_list.append(getattr(image, x)) return tuple(data_list) diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py index 45b5a0a8cb..748a61aaf4 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -18,8 +18,8 @@ from glanceclient.common import utils as glanceclient_utils from glanceclient.v2 import schemas import mock +from osc_lib.cli import format_columns from osc_lib import exceptions -from osc_lib import utils as common_utils import warlock from openstackclient.image.v2 import image @@ -116,7 +116,7 @@ def test_image_reserve_no_options(self): self.assertEqual( image_fakes.FakeImage.get_image_columns(self.new_image), columns) - self.assertEqual( + self.assertItemEqual( image_fakes.FakeImage.get_image_data(self.new_image), data) @@ -184,7 +184,7 @@ def test_image_reserve_options(self, mock_open): self.assertEqual( image_fakes.FakeImage.get_image_columns(self.new_image), columns) - self.assertEqual( + self.assertItemEqual( image_fakes.FakeImage.get_image_data(self.new_image), data) @@ -284,7 +284,7 @@ def test_image_create_file(self, mock_open): self.assertEqual( image_fakes.FakeImage.get_image_columns(self.new_image), columns) - self.assertEqual( + self.assertItemEqual( image_fakes.FakeImage.get_image_data(self.new_image), data) @@ -508,7 +508,7 @@ def test_image_list_no_options(self): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, tuple(data)) + self.assertListItemEqual(self.datalist, tuple(data)) def test_image_list_public_option(self): arglist = [ @@ -533,7 +533,7 @@ def test_image_list_public_option(self): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, tuple(data)) + self.assertListItemEqual(self.datalist, tuple(data)) def test_image_list_private_option(self): arglist = [ @@ -558,7 +558,7 @@ def test_image_list_private_option(self): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, tuple(data)) + self.assertListItemEqual(self.datalist, tuple(data)) def test_image_list_community_option(self): arglist = [ @@ -608,7 +608,7 @@ def test_image_list_shared_option(self): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, tuple(data)) + self.assertListItemEqual(self.datalist, tuple(data)) def test_image_list_shared_member_status_option(self): arglist = [ @@ -696,9 +696,9 @@ def test_image_list_long_option(self): self._image.visibility, self._image.protected, self._image.owner, - common_utils.format_list(self._image.tags), + format_columns.ListColumn(self._image.tags), ), ) - self.assertEqual(datalist, tuple(data)) + self.assertListItemEqual(datalist, tuple(data)) @mock.patch('osc_lib.api.utils.simple_filter') def test_image_list_property_option(self, sf_mock): @@ -727,7 +727,7 @@ def test_image_list_property_option(self, sf_mock): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, tuple(data)) + self.assertListItemEqual(self.datalist, tuple(data)) @mock.patch('osc_lib.utils.sort_items') def test_image_list_sort_option(self, si_mock): @@ -750,7 +750,7 @@ def test_image_list_sort_option(self, si_mock): str, ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, tuple(data)) + self.assertListItemEqual(self.datalist, tuple(data)) def test_image_list_limit_option(self): ret_limit = 1 @@ -1460,7 +1460,7 @@ def test_image_show(self): ) self.assertEqual(image_fakes.IMAGE_columns, columns) - self.assertEqual(image_fakes.IMAGE_SHOW_data, data) + self.assertItemEqual(image_fakes.IMAGE_SHOW_data, data) def test_image_show_human_readable(self): self.images_mock.get.return_value = self.new_image From c2630ae91a0e4bae662926a0e45d71c79683b584 Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Mon, 15 May 2017 04:01:07 +0000 Subject: [PATCH 2074/3095] Use cliff formattable columns in object storage commands Partial-Bug: #1687955 Partially implement blueprint osc-formattable-columns Change-Id: I65737561c9b5ef29f5878316d2ff89f3d538158f --- openstackclient/object/v1/account.py | 5 +++-- openstackclient/object/v1/container.py | 3 ++- openstackclient/object/v1/object.py | 3 ++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/openstackclient/object/v1/account.py b/openstackclient/object/v1/account.py index 4847f8bbc2..95be8132c9 100644 --- a/openstackclient/object/v1/account.py +++ b/openstackclient/object/v1/account.py @@ -13,9 +13,9 @@ """Account v1 action implementations""" +from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command -from osc_lib import utils import six from openstackclient.i18n import _ @@ -48,7 +48,8 @@ class ShowAccount(command.ShowOne): def take_action(self, parsed_args): data = self.app.client_manager.object_store.account_show() if 'properties' in data: - data['properties'] = utils.format_dict(data.pop('properties')) + data['properties'] = format_columns.DictColumn( + data.pop('properties')) return zip(*sorted(six.iteritems(data))) diff --git a/openstackclient/object/v1/container.py b/openstackclient/object/v1/container.py index 9f689ab6b0..02e8d27780 100644 --- a/openstackclient/object/v1/container.py +++ b/openstackclient/object/v1/container.py @@ -17,6 +17,7 @@ import logging +from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import utils @@ -230,7 +231,7 @@ def take_action(self, parsed_args): container=parsed_args.container, ) if 'properties' in data: - data['properties'] = utils.format_dict(data.pop('properties')) + data['properties'] = format_columns.DictColumn(data['properties']) return zip(*sorted(six.iteritems(data))) diff --git a/openstackclient/object/v1/object.py b/openstackclient/object/v1/object.py index aeb0253653..3747e19e47 100644 --- a/openstackclient/object/v1/object.py +++ b/openstackclient/object/v1/object.py @@ -17,6 +17,7 @@ import logging +from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -284,7 +285,7 @@ def take_action(self, parsed_args): object=parsed_args.object, ) if 'properties' in data: - data['properties'] = utils.format_dict(data.pop('properties')) + data['properties'] = format_columns.DictColumn(data['properties']) return zip(*sorted(six.iteritems(data))) From 1af3056e301807e4474cde49eef3e00931ab08c2 Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Mon, 15 May 2017 07:55:41 +0000 Subject: [PATCH 2075/3095] Use cliff formattable columns in volume v1 commands Partial-Bug: #1687955 Partially implement blueprint osc-formattable-columns Change-Id: Ib4c5798171e32a8ddc08a37ee1d416e366a71d76 --- .../tests/functional/volume/v1/test_qos.py | 4 +- .../functional/volume/v1/test_snapshot.py | 9 +-- .../tests/functional/volume/v1/test_volume.py | 6 +- .../functional/volume/v1/test_volume_type.py | 61 +++++++------- .../tests/unit/volume/v1/test_qos_specs.py | 28 ++++--- .../tests/unit/volume/v1/test_type.py | 59 ++++++++++---- .../tests/unit/volume/v1/test_volume.py | 79 +++++++++++++------ .../unit/volume/v1/test_volume_backup.py | 12 +-- openstackclient/volume/v1/qos_specs.py | 13 +-- openstackclient/volume/v1/volume.py | 62 ++++++++++----- openstackclient/volume/v1/volume_backup.py | 45 ++++++++--- openstackclient/volume/v1/volume_snapshot.py | 54 +++++++++---- openstackclient/volume/v1/volume_type.py | 60 +++++++++++--- 13 files changed, 323 insertions(+), 169 deletions(-) diff --git a/openstackclient/tests/functional/volume/v1/test_qos.py b/openstackclient/tests/functional/volume/v1/test_qos.py index 434840f623..d8277dfc81 100644 --- a/openstackclient/tests/functional/volume/v1/test_qos.py +++ b/openstackclient/tests/functional/volume/v1/test_qos.py @@ -93,7 +93,7 @@ def test_volume_qos_set_show_unset(self): cmd_output['name'] ) self.assertEqual( - "Alpha='c', Beta='b'", + {'Alpha': 'c', 'Beta': 'b'}, cmd_output['properties'] ) @@ -114,7 +114,7 @@ def test_volume_qos_set_show_unset(self): cmd_output['name'] ) self.assertEqual( - "Beta='b'", + {'Beta': 'b'}, cmd_output['properties'] ) diff --git a/openstackclient/tests/functional/volume/v1/test_snapshot.py b/openstackclient/tests/functional/volume/v1/test_snapshot.py index 083cd1b087..5a76a2e950 100644 --- a/openstackclient/tests/functional/volume/v1/test_snapshot.py +++ b/openstackclient/tests/functional/volume/v1/test_snapshot.py @@ -204,7 +204,7 @@ def test_snapshot_set(self): cmd_output["display_description"], ) self.assertEqual( - "Alpha='a', Beta='b'", + {'Alpha': 'a', 'Beta': 'b'}, cmd_output["properties"], ) @@ -221,7 +221,7 @@ def test_snapshot_set(self): new_name )) self.assertEqual( - "Beta='b'", + {'Beta': 'b'}, cmd_output["properties"], ) @@ -236,7 +236,4 @@ def test_snapshot_set(self): 'volume snapshot show -f json ' + new_name )) - self.assertNotIn( - "Beta='b'", - cmd_output["properties"], - ) + self.assertEqual({}, cmd_output["properties"]) diff --git a/openstackclient/tests/functional/volume/v1/test_volume.py b/openstackclient/tests/functional/volume/v1/test_volume.py index 001bbe6e90..013bc6a4a8 100644 --- a/openstackclient/tests/functional/volume/v1/test_volume.py +++ b/openstackclient/tests/functional/volume/v1/test_volume.py @@ -123,7 +123,7 @@ def test_volume_set_and_unset(self): cmd_output["display_description"], ) self.assertEqual( - "Alpha='a'", + {'Alpha': 'a'}, cmd_output["properties"], ) self.assertEqual( @@ -165,7 +165,7 @@ def test_volume_set_and_unset(self): cmd_output["display_description"], ) self.assertEqual( - "Beta='b', Gamma='c'", + {'Beta': 'b', 'Gamma': 'c'}, cmd_output["properties"], ) self.assertEqual( @@ -186,7 +186,7 @@ def test_volume_set_and_unset(self): new_name )) self.assertEqual( - "Gamma='c'", + {'Gamma': 'c'}, cmd_output["properties"], ) diff --git a/openstackclient/tests/functional/volume/v1/test_volume_type.py b/openstackclient/tests/functional/volume/v1/test_volume_type.py index eb9d7f64ae..fb8dabdb04 100644 --- a/openstackclient/tests/functional/volume/v1/test_volume_type.py +++ b/openstackclient/tests/functional/volume/v1/test_volume_type.py @@ -66,8 +66,7 @@ def test_volume_type_set_unset_properties(self): cmd_output = json.loads(self.openstack( 'volume type show -f json %s' % name )) - # TODO(amotoki): properties output should be machine-readable - self.assertEqual("a='b', c='d'", cmd_output['properties']) + self.assertEqual({'a': 'b', 'c': 'd'}, cmd_output['properties']) raw_output = self.openstack( 'volume type unset --property a %s' % name @@ -76,7 +75,7 @@ def test_volume_type_set_unset_properties(self): cmd_output = json.loads(self.openstack( 'volume type show -f json %s' % name )) - self.assertEqual("c='d'", cmd_output['properties']) + self.assertEqual({'c': 'd'}, cmd_output['properties']) def test_volume_type_set_unset_multiple_properties(self): name = uuid.uuid4().hex @@ -97,7 +96,7 @@ def test_volume_type_set_unset_multiple_properties(self): cmd_output = json.loads(self.openstack( 'volume type show -f json %s' % name )) - self.assertEqual("a='b', c='d'", cmd_output['properties']) + self.assertEqual({'a': 'b', 'c': 'd'}, cmd_output['properties']) raw_output = self.openstack( 'volume type unset --property a --property c %s' % name @@ -106,7 +105,7 @@ def test_volume_type_set_unset_multiple_properties(self): cmd_output = json.loads(self.openstack( 'volume type show -f json %s' % name )) - self.assertEqual("", cmd_output['properties']) + self.assertEqual({}, cmd_output['properties']) def test_multi_delete(self): vol_type1 = uuid.uuid4().hex @@ -133,34 +132,32 @@ def test_encryption_type(self): '--encryption-key-size 128 ' '--encryption-control-location front-end ' + encryption_type)) - # TODO(amotoki): encryption output should be machine-readable - expected = ["provider='LuksEncryptor'", - "cipher='aes-xts-plain64'", - "key_size='128'", - "control_location='front-end'"] - for attr in expected: - self.assertIn(attr, cmd_output['encryption']) + expected = {'provider': 'LuksEncryptor', + 'cipher': 'aes-xts-plain64', + 'key_size': 128, + 'control_location': 'front-end'} + for attr, value in expected.items(): + self.assertEqual(value, cmd_output['encryption'][attr]) # test show encryption type cmd_output = json.loads(self.openstack( 'volume type show -f json --encryption-type ' + encryption_type)) - expected = ["provider='LuksEncryptor'", - "cipher='aes-xts-plain64'", - "key_size='128'", - "control_location='front-end'"] - for attr in expected: - self.assertIn(attr, cmd_output['encryption']) + expected = {'provider': 'LuksEncryptor', + 'cipher': 'aes-xts-plain64', + 'key_size': 128, + 'control_location': 'front-end'} + for attr, value in expected.items(): + self.assertEqual(value, cmd_output['encryption'][attr]) # test list encryption type cmd_output = json.loads(self.openstack( 'volume type list -f json --encryption-type')) encryption_output = [t['Encryption'] for t in cmd_output if t['Name'] == encryption_type][0] - # TODO(amotoki): encryption output should be machine-readable - expected = ["provider='LuksEncryptor'", - "cipher='aes-xts-plain64'", - "key_size='128'", - "control_location='front-end'"] - for attr in expected: - self.assertIn(attr, encryption_output) + expected = {'provider': 'LuksEncryptor', + 'cipher': 'aes-xts-plain64', + 'key_size': 128, + 'control_location': 'front-end'} + for attr, value in expected.items(): + self.assertEqual(value, encryption_output[attr]) # test set new encryption type raw_output = self.openstack( 'volume type set ' @@ -185,12 +182,12 @@ def test_encryption_type(self): cmd_output = json.loads(self.openstack( 'volume type show -f json --encryption-type ' + name )) - expected = ["provider='LuksEncryptor'", - "cipher='aes-xts-plain64'", - "key_size='128'", - "control_location='front-end'"] - for attr in expected: - self.assertIn(attr, cmd_output['encryption']) + expected = {'provider': 'LuksEncryptor', + 'cipher': 'aes-xts-plain64', + 'key_size': 128, + 'control_location': 'front-end'} + for attr, value in expected.items(): + self.assertEqual(value, cmd_output['encryption'][attr]) # test unset encryption type raw_output = self.openstack( 'volume type unset --encryption-type ' + name @@ -199,7 +196,7 @@ def test_encryption_type(self): cmd_output = json.loads(self.openstack( 'volume type show -f json --encryption-type ' + name )) - self.assertEqual('', cmd_output['encryption']) + self.assertEqual({}, cmd_output['encryption']) # test delete encryption type raw_output = self.openstack('volume type delete ' + encryption_type) self.assertEqual('', raw_output) diff --git a/openstackclient/tests/unit/volume/v1/test_qos_specs.py b/openstackclient/tests/unit/volume/v1/test_qos_specs.py index 442840f922..11dc80844a 100644 --- a/openstackclient/tests/unit/volume/v1/test_qos_specs.py +++ b/openstackclient/tests/unit/volume/v1/test_qos_specs.py @@ -17,6 +17,8 @@ import mock from mock import call + +from osc_lib.cli import format_columns from osc_lib import exceptions from osc_lib import utils @@ -85,7 +87,7 @@ def setUp(self): self.new_qos_spec.consumer, self.new_qos_spec.id, self.new_qos_spec.name, - utils.format_dict(self.new_qos_spec.specs) + format_columns.DictColumn(self.new_qos_spec.specs) ) self.qos_mock.create.return_value = self.new_qos_spec # Get the command object to test @@ -108,7 +110,7 @@ def test_qos_create_without_properties(self): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_qos_create_with_consumer(self): arglist = [ @@ -128,7 +130,7 @@ def test_qos_create_with_consumer(self): {'consumer': self.new_qos_spec.consumer} ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_qos_create_with_properties(self): arglist = [ @@ -154,7 +156,7 @@ def test_qos_create_with_properties(self): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) class TestQosDelete(TestQos): @@ -326,8 +328,8 @@ class TestQosList(TestQos): q.id, q.name, q.consumer, - qos_association.name, - utils.format_dict(q.specs), + format_columns.ListColumn([qos_association.name]), + format_columns.DictColumn(q.specs), )) def setUp(self): @@ -349,7 +351,7 @@ def test_qos_list(self): self.qos_mock.list.assert_called_with() self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_qos_list_no_association(self): self.qos_mock.reset_mock() @@ -373,10 +375,10 @@ def test_qos_list_no_association(self): self.qos_specs[1].id, self.qos_specs[1].name, self.qos_specs[1].consumer, - None, - utils.format_dict(self.qos_specs[1].specs), + format_columns.ListColumn(None), + format_columns.DictColumn(self.qos_specs[1].specs), ) - self.assertEqual(ex_data, list(data)) + self.assertListItemEqual(ex_data, list(data)) class TestQosSet(TestQos): @@ -447,13 +449,13 @@ def test_qos_show(self): ) self.assertEqual(collist, columns) datalist = ( - self.qos_association.name, + format_columns.ListColumn([self.qos_association.name]), self.qos_spec.consumer, self.qos_spec.id, self.qos_spec.name, - utils.format_dict(self.qos_spec.specs), + format_columns.DictColumn(self.qos_spec.specs), ) - self.assertEqual(datalist, tuple(data)) + self.assertItemEqual(datalist, tuple(data)) class TestQosUnset(TestQos): diff --git a/openstackclient/tests/unit/volume/v1/test_type.py b/openstackclient/tests/unit/volume/v1/test_type.py index dcdd3d56dd..beff83364c 100644 --- a/openstackclient/tests/unit/volume/v1/test_type.py +++ b/openstackclient/tests/unit/volume/v1/test_type.py @@ -15,6 +15,7 @@ import mock from mock import call +from osc_lib.cli import format_columns from osc_lib import exceptions from osc_lib import utils @@ -77,7 +78,7 @@ def test_type_create(self): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_type_create_with_encryption(self): encryption_info = { @@ -102,7 +103,7 @@ def test_type_create_with_encryption(self): ) encryption_data = ( self.new_volume_type.description, - utils.format_dict(encryption_info), + format_columns.DictColumn(encryption_info), self.new_volume_type.id, True, self.new_volume_type.name, @@ -138,7 +139,7 @@ def test_type_create_with_encryption(self): body, ) self.assertEqual(encryption_columns, columns) - self.assertEqual(encryption_data, data) + self.assertItemEqual(encryption_data, data) class TestTypeDelete(TestType): @@ -246,7 +247,7 @@ class TestTypeList(TestType): t.id, t.name, t.is_public, - utils.format_dict(t.extra_specs), + format_columns.DictColumn(t.extra_specs), )) def setUp(self): @@ -269,7 +270,7 @@ def test_type_list_without_options(self): columns, data = self.cmd.take_action(parsed_args) self.types_mock.list.assert_called_once_with() self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_type_list_with_options(self): arglist = [ @@ -283,7 +284,7 @@ def test_type_list_with_options(self): columns, data = self.cmd.take_action(parsed_args) self.types_mock.list.assert_called_once_with() self.assertEqual(self.columns_long, columns) - self.assertEqual(self.data_long, list(data)) + self.assertListItemEqual(self.data_long, list(data)) def test_type_list_with_encryption(self): encryption_type = volume_fakes.FakeType.create_one_encryption_type( @@ -302,13 +303,16 @@ def test_type_list_with_encryption(self): self.volume_types[0].id, self.volume_types[0].name, self.volume_types[0].is_public, - utils.format_dict(encryption_info), + volume_type.EncryptionInfoColumn( + self.volume_types[0].id, + {self.volume_types[0].id: encryption_info}), )) encryption_data.append(( self.volume_types[1].id, self.volume_types[1].name, self.volume_types[1].is_public, - '-', + volume_type.EncryptionInfoColumn( + self.volume_types[1].id, {}), )) self.encryption_types_mock.list.return_value = [encryption_type] @@ -324,7 +328,7 @@ def test_type_list_with_encryption(self): self.encryption_types_mock.list.assert_called_once_with() self.types_mock.list.assert_called_once_with() self.assertEqual(encryption_columns, columns) - self.assertEqual(encryption_data, list(data)) + self.assertListItemEqual(encryption_data, list(data)) class TestTypeSet(TestType): @@ -443,7 +447,7 @@ def setUp(self): self.volume_type.id, True, self.volume_type.name, - utils.format_dict(self.volume_type.extra_specs) + format_columns.DictColumn(self.volume_type.extra_specs) ) self.types_mock.get.return_value = self.volume_type @@ -465,7 +469,7 @@ def test_type_show(self): self.types_mock.get.assert_called_with(self.volume_type.id) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_type_show_with_encryption(self): encryption_type = volume_fakes.FakeType.create_one_encryption_type() @@ -489,11 +493,11 @@ def test_type_show_with_encryption(self): ) encryption_data = ( self.volume_type.description, - utils.format_dict(encryption_info), + format_columns.DictColumn(encryption_info), self.volume_type.id, True, self.volume_type.name, - utils.format_dict(self.volume_type.extra_specs) + format_columns.DictColumn(self.volume_type.extra_specs) ) arglist = [ '--encryption-type', @@ -509,7 +513,7 @@ def test_type_show_with_encryption(self): self.types_mock.get.assert_called_with(self.volume_type.id) self.encryption_types_mock.get.assert_called_with(self.volume_type.id) self.assertEqual(encryption_columns, columns) - self.assertEqual(encryption_data, data) + self.assertItemEqual(encryption_data, data) class TestTypeUnset(TestType): @@ -587,3 +591,30 @@ def test_type_unset_encryption_type(self): result = self.cmd.take_action(parsed_args) self.encryption_types_mock.delete.assert_called_with(self.volume_type) self.assertIsNone(result) + + +class TestColumns(TestType): + + def test_encryption_info_column_with_info(self): + fake_volume_type = volume_fakes.FakeType.create_one_type() + type_id = fake_volume_type.id + + encryption_info = { + 'provider': 'LuksEncryptor', + 'cipher': None, + 'key_size': None, + 'control_location': 'front-end', + } + col = volume_type.EncryptionInfoColumn(type_id, + {type_id: encryption_info}) + self.assertEqual(utils.format_dict(encryption_info), + col.human_readable()) + self.assertEqual(encryption_info, col.machine_readable()) + + def test_encryption_info_column_without_info(self): + fake_volume_type = volume_fakes.FakeType.create_one_type() + type_id = fake_volume_type.id + + col = volume_type.EncryptionInfoColumn(type_id, {}) + self.assertEqual('-', col.human_readable()) + self.assertIsNone(col.machine_readable()) diff --git a/openstackclient/tests/unit/volume/v1/test_volume.py b/openstackclient/tests/unit/volume/v1/test_volume.py index eee5acd7ea..c415455534 100644 --- a/openstackclient/tests/unit/volume/v1/test_volume.py +++ b/openstackclient/tests/unit/volume/v1/test_volume.py @@ -17,6 +17,8 @@ import mock from mock import call + +from osc_lib.cli import format_columns from osc_lib import exceptions from osc_lib import utils @@ -88,7 +90,7 @@ def setUp(self): self.new_volume.display_description, self.new_volume.id, self.new_volume.display_name, - utils.format_dict(self.new_volume.metadata), + format_columns.DictColumn(self.new_volume.metadata), self.new_volume.size, self.new_volume.snapshot_id, self.new_volume.status, @@ -134,7 +136,7 @@ def test_volume_create_min_options(self): None, ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_volume_create_options(self): arglist = [ @@ -178,7 +180,7 @@ def test_volume_create_options(self): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_volume_create_user_project_id(self): # Return a project @@ -225,7 +227,7 @@ def test_volume_create_user_project_id(self): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_volume_create_user_project_name(self): # Return a project @@ -272,7 +274,7 @@ def test_volume_create_user_project_name(self): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_volume_create_properties(self): arglist = [ @@ -313,7 +315,7 @@ def test_volume_create_properties(self): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_volume_create_image_id(self): image = image_fakes.FakeImage.create_one_image() @@ -356,7 +358,7 @@ def test_volume_create_image_id(self): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_volume_create_image_name(self): image = image_fakes.FakeImage.create_one_image() @@ -399,7 +401,7 @@ def test_volume_create_image_name(self): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_volume_create_with_source(self): self.volumes_mock.get.return_value = self.new_volume @@ -429,7 +431,7 @@ def test_volume_create_with_source(self): None, ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_volume_create_with_bootable_and_readonly(self): arglist = [ @@ -467,7 +469,7 @@ def test_volume_create_with_bootable_and_readonly(self): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(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( @@ -509,7 +511,7 @@ def test_volume_create_with_nonbootable_and_readwrite(self): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(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( @@ -561,7 +563,7 @@ def test_volume_create_with_bootable_and_readonly_fail( self.assertEqual(2, mock_error.call_count) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(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( @@ -732,16 +734,13 @@ class TestVolumeList(TestVolume): 'Size', 'Attached to', ) - server = _volume.attachments[0]['server_id'] - device = _volume.attachments[0]['device'] - msg = 'Attached to %s on %s ' % (server, device) datalist = ( ( _volume.id, _volume.display_name, _volume.status, _volume.size, - msg, + volume.AttachmentsColumn(_volume.attachments), ), ) @@ -767,7 +766,7 @@ def test_volume_list_no_options(self): columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, tuple(data)) + self.assertListItemEqual(self.datalist, tuple(data)) def test_volume_list_name(self): arglist = [ @@ -784,7 +783,7 @@ def test_volume_list_name(self): columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, tuple(columns)) - self.assertEqual(self.datalist, tuple(data)) + self.assertListItemEqual(self.datalist, tuple(data)) def test_volume_list_status(self): arglist = [ @@ -801,7 +800,7 @@ def test_volume_list_status(self): columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, tuple(columns)) - self.assertEqual(self.datalist, tuple(data)) + self.assertListItemEqual(self.datalist, tuple(data)) def test_volume_list_all_projects(self): arglist = [ @@ -818,7 +817,7 @@ def test_volume_list_all_projects(self): columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, tuple(columns)) - self.assertEqual(self.datalist, tuple(data)) + self.assertListItemEqual(self.datalist, tuple(data)) def test_volume_list_long(self): arglist = [ @@ -855,10 +854,10 @@ def test_volume_list_long(self): self._volume.size, self._volume.volume_type, self._volume.bootable, - self.msg, - utils.format_dict(self._volume.metadata), + volume.AttachmentsColumn(self._volume.attachments), + format_columns.DictColumn(self._volume.metadata), ), ) - self.assertEqual(datalist, tuple(data)) + self.assertListItemEqual(datalist, tuple(data)) def test_volume_list_with_limit(self): arglist = [ @@ -883,7 +882,7 @@ def test_volume_list_with_limit(self): 'all_tenants': False, } ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, tuple(data)) + self.assertListItemEqual(self.datalist, tuple(data)) def test_volume_list_negative_limit(self): arglist = [ @@ -1251,7 +1250,7 @@ def setUp(self): self._volume.display_description, self._volume.id, self._volume.display_name, - utils.format_dict(self._volume.metadata), + format_columns.DictColumn(self._volume.metadata), self._volume.size, self._volume.snapshot_id, self._volume.status, @@ -1274,7 +1273,7 @@ def test_volume_show(self): self.volumes_mock.get.assert_called_with(self._volume.id) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_volume_show_backward_compatibility(self): arglist = [ @@ -1339,3 +1338,31 @@ def test_volume_unset_property(self): self._volume.id, ['myprop'] ) self.assertIsNone(result) + + +class TestColumns(TestVolume): + + def test_attachments_column_without_server_cache(self): + _volume = volume_fakes.FakeVolume.create_one_volume() + server_id = _volume.attachments[0]['server_id'] + device = _volume.attachments[0]['device'] + + col = volume.AttachmentsColumn(_volume.attachments, {}) + self.assertEqual('Attached to %s on %s ' % (server_id, device), + col.human_readable()) + self.assertEqual(_volume.attachments, col.machine_readable()) + + def test_attachments_column_with_server_cache(self): + _volume = volume_fakes.FakeVolume.create_one_volume() + + server_id = _volume.attachments[0]['server_id'] + device = _volume.attachments[0]['device'] + fake_server = mock.Mock() + fake_server.name = 'fake-server-name' + server_cache = {server_id: fake_server} + + col = volume.AttachmentsColumn(_volume.attachments, server_cache) + self.assertEqual( + 'Attached to %s on %s ' % ('fake-server-name', device), + col.human_readable()) + self.assertEqual(_volume.attachments, col.machine_readable()) diff --git a/openstackclient/tests/unit/volume/v1/test_volume_backup.py b/openstackclient/tests/unit/volume/v1/test_volume_backup.py index 22c1f8cf58..20be6e46d7 100644 --- a/openstackclient/tests/unit/volume/v1/test_volume_backup.py +++ b/openstackclient/tests/unit/volume/v1/test_volume_backup.py @@ -100,7 +100,7 @@ def test_backup_create(self): self.new_backup.description, ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_backup_create_without_name(self): arglist = [ @@ -124,7 +124,7 @@ def test_backup_create_without_name(self): self.new_backup.description, ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) class TestBackupDelete(TestBackup): @@ -240,7 +240,7 @@ class TestBackupList(TestBackup): b.status, b.size, b.availability_zone, - b.volume_id, + volume_backup.VolumeIdColumn(b.volume_id), b.container, )) @@ -277,7 +277,7 @@ def test_backup_list_without_options(self): search_opts=search_opts, ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_backup_list_with_options(self): arglist = [ @@ -309,7 +309,7 @@ def test_backup_list_with_options(self): search_opts=search_opts, ) self.assertEqual(self.columns_long, columns) - self.assertEqual(self.data_long, list(data)) + self.assertListItemEqual(self.data_long, list(data)) class TestBackupRestore(TestBackup): @@ -391,4 +391,4 @@ def test_backup_show(self): self.backups_mock.get.assert_called_with(self.backup.id) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) diff --git a/openstackclient/volume/v1/qos_specs.py b/openstackclient/volume/v1/qos_specs.py index 26c9182971..0b6a7fa0ca 100644 --- a/openstackclient/volume/v1/qos_specs.py +++ b/openstackclient/volume/v1/qos_specs.py @@ -17,6 +17,7 @@ import logging +from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -95,7 +96,8 @@ def take_action(self, parsed_args): qos_spec = volume_client.qos_specs.create(parsed_args.name, specs) qos_spec._info.update( - {'properties': utils.format_dict(qos_spec._info.pop('specs'))} + {'properties': + format_columns.DictColumn(qos_spec._info.pop('specs'))} ) return zip(*sorted(six.iteritems(qos_spec._info))) @@ -208,8 +210,8 @@ def take_action(self, parsed_args): (utils.get_dict_properties( s._info, columns, formatters={ - 'Specs': utils.format_dict, - 'Associations': utils.format_list + 'Specs': format_columns.DictColumn, + 'Associations': format_columns.ListColumn }, ) for s in qos_specs_list)) @@ -265,10 +267,11 @@ def take_action(self, parsed_args): associations = [association.name for association in qos_associations] qos_spec._info.update({ - 'associations': utils.format_list(associations) + 'associations': format_columns.ListColumn(associations) }) qos_spec._info.update( - {'properties': utils.format_dict(qos_spec._info.pop('specs'))}) + {'properties': + format_columns.DictColumn(qos_spec._info.pop('specs'))}) return zip(*sorted(six.iteritems(qos_spec._info))) diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index b29429ef82..36c7ef8985 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -16,8 +16,11 @@ """Volume v1 Volume action implementations""" import argparse +import functools import logging +from cliff import columns as cliff_columns +from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -30,6 +33,37 @@ LOG = logging.getLogger(__name__) +class AttachmentsColumn(cliff_columns.FormattableColumn): + """Formattable column for attachments column. + + Unlike the parent FormattableColumn class, the initializer of the + class takes server_cache as the second argument. + osc_lib.utils.get_item_properties instantiate cliff FormattableColumn + object with a single parameter "column value", so you need to pass + a partially initialized class like + ``functools.partial(AttachmentsColumn, server_cache)``. + """ + + def __init__(self, value, server_cache=None): + super(AttachmentsColumn, self).__init__(value) + self._server_cache = server_cache or {} + + def human_readable(self): + """Return a formatted string of a volume's attached instances + + :rtype: a string of formatted instances + """ + + msg = '' + for attachment in self._value: + server = attachment['server_id'] + if server in self._server_cache.keys(): + server = self._server_cache[server].name + device = attachment['device'] + msg += 'Attached to %s on %s ' % (server, device) + return msg + + def _check_size_arg(args): """Check whether --size option is required or not. @@ -207,7 +241,8 @@ def take_action(self, parsed_args): # Map 'metadata' column to 'properties' volume._info.update( { - 'properties': utils.format_dict(volume._info.pop('metadata')), + 'properties': + format_columns.DictColumn(volume._info.pop('metadata')), 'type': volume._info.pop('volume_type'), }, ) @@ -307,22 +342,6 @@ def take_action(self, parsed_args): volume_client = self.app.client_manager.volume compute_client = self.app.client_manager.compute - def _format_attach(attachments): - """Return a formatted string of a volume's attached instances - - :param attachments: a volume.attachments field - :rtype: a string of formatted instances - """ - - msg = '' - for attachment in attachments: - server = attachment['server_id'] - if server in server_cache.keys(): - server = server_cache[server].name - device = attachment['device'] - msg += 'Attached to %s on %s ' % (server, device) - return msg - if parsed_args.long: columns = ( 'ID', @@ -368,6 +387,8 @@ def _format_attach(attachments): except Exception: # Just forget it if there's any trouble pass + AttachmentsColumnWithCache = functools.partial( + AttachmentsColumn, server_cache=server_cache) search_opts = { 'all_tenants': parsed_args.all_projects, @@ -385,8 +406,8 @@ def _format_attach(attachments): return (column_headers, (utils.get_item_properties( s, columns, - formatters={'Metadata': utils.format_dict, - 'Attachments': _format_attach}, + formatters={'Metadata': format_columns.DictColumn, + 'Attachments': AttachmentsColumnWithCache}, ) for s in data)) @@ -575,7 +596,8 @@ def take_action(self, parsed_args): # Map 'metadata' column to 'properties' volume._info.update( { - 'properties': utils.format_dict(volume._info.pop('metadata')), + 'properties': + format_columns.DictColumn(volume._info.pop('metadata')), 'type': volume._info.pop('volume_type'), }, ) diff --git a/openstackclient/volume/v1/volume_backup.py b/openstackclient/volume/v1/volume_backup.py index 9af327055b..2daa23cb92 100644 --- a/openstackclient/volume/v1/volume_backup.py +++ b/openstackclient/volume/v1/volume_backup.py @@ -16,8 +16,10 @@ """Volume v1 Backup action implementations""" import copy +import functools import logging +from cliff import columns as cliff_columns from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -29,6 +31,33 @@ LOG = logging.getLogger(__name__) +class VolumeIdColumn(cliff_columns.FormattableColumn): + """Formattable column for volume ID column. + + Unlike the parent FormattableColumn class, the initializer of the + class takes volume_cache as the second argument. + osc_lib.utils.get_item_properties instantiate cliff FormattableColumn + object with a single parameter "column value", so you need to pass + a partially initialized class like + ``functools.partial(VolumeIdColumn, volume_cache)``. + """ + + def __init__(self, value, volume_cache=None): + super(VolumeIdColumn, self).__init__(value) + self._volume_cache = volume_cache or {} + + def human_readable(self): + """Return a volume name if available + + :rtype: either the volume ID or name + """ + volume_id = self._value + volume = volume_id + if volume_id in self._volume_cache.keys(): + volume = self._volume_cache[volume_id].display_name + return volume + + class CreateVolumeBackup(command.ShowOne): _description = _("Create new volume backup") @@ -149,18 +178,6 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): volume_client = self.app.client_manager.volume - def _format_volume_id(volume_id): - """Return a volume name if available - - :param volume_id: a volume ID - :rtype: either the volume ID or name - """ - - volume = volume_id - if volume_id in volume_cache.keys(): - volume = volume_cache[volume_id].display_name - return volume - if parsed_args.long: columns = ['ID', 'Name', 'Description', 'Status', 'Size', 'Availability Zone', 'Volume ID', 'Container'] @@ -178,6 +195,8 @@ def _format_volume_id(volume_id): except Exception: # Just forget it if there's any trouble pass + VolumeIdColumnWithCache = functools.partial(VolumeIdColumn, + volume_cache=volume_cache) filter_volume_id = None if parsed_args.volume: @@ -196,7 +215,7 @@ def _format_volume_id(volume_id): return (column_headers, (utils.get_item_properties( s, columns, - formatters={'Volume ID': _format_volume_id}, + formatters={'Volume ID': VolumeIdColumnWithCache}, ) for s in data)) diff --git a/openstackclient/volume/v1/volume_snapshot.py b/openstackclient/volume/v1/volume_snapshot.py index 3e83da5a7b..50f81771a0 100644 --- a/openstackclient/volume/v1/volume_snapshot.py +++ b/openstackclient/volume/v1/volume_snapshot.py @@ -16,8 +16,11 @@ """Volume v1 Snapshot action implementations""" import copy +import functools import logging +from cliff import columns as cliff_columns +from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -30,6 +33,33 @@ LOG = logging.getLogger(__name__) +class VolumeIdColumn(cliff_columns.FormattableColumn): + """Formattable column for volume ID column. + + Unlike the parent FormattableColumn class, the initializer of the + class takes volume_cache as the second argument. + osc_lib.utils.get_item_properties instantiate cliff FormattableColumn + object with a single parameter "column value", so you need to pass + a partially initialized class like + ``functools.partial(VolumeIdColumn, volume_cache)``. + """ + + def __init__(self, value, volume_cache=None): + super(VolumeIdColumn, self).__init__(value) + self._volume_cache = volume_cache or {} + + def human_readable(self): + """Return a volume name if available + + :rtype: either the volume ID or name + """ + volume_id = self._value + volume = volume_id + if volume_id in self._volume_cache.keys(): + volume = self._volume_cache[volume_id].display_name + return volume + + class CreateVolumeSnapshot(command.ShowOne): _description = _("Create new volume snapshot") @@ -76,7 +106,8 @@ def take_action(self, parsed_args): ) snapshot._info.update( - {'properties': utils.format_dict(snapshot._info.pop('metadata'))} + {'properties': + format_columns.DictColumn(snapshot._info.pop('metadata'))} ) return zip(*sorted(six.iteritems(snapshot._info))) @@ -160,18 +191,6 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): volume_client = self.app.client_manager.volume - def _format_volume_id(volume_id): - """Return a volume name if available - - :param volume_id: a volume ID - :rtype: either the volume ID or name - """ - - volume = volume_id - if volume_id in volume_cache.keys(): - volume = volume_cache[volume_id].display_name - return volume - if parsed_args.long: columns = ['ID', 'Display Name', 'Display Description', 'Status', 'Size', 'Created At', 'Volume ID', 'Metadata'] @@ -195,6 +214,8 @@ def _format_volume_id(volume_id): except Exception: # Just forget it if there's any trouble pass + VolumeIdColumnWithCache = functools.partial(VolumeIdColumn, + volume_cache=volume_cache) volume_id = None if parsed_args.volume: @@ -213,8 +234,8 @@ def _format_volume_id(volume_id): return (column_headers, (utils.get_item_properties( s, columns, - formatters={'Metadata': utils.format_dict, - 'Volume ID': _format_volume_id}, + formatters={'Metadata': format_columns.DictColumn, + 'Volume ID': VolumeIdColumnWithCache}, ) for s in data)) @@ -317,7 +338,8 @@ def take_action(self, parsed_args): parsed_args.snapshot) snapshot._info.update( - {'properties': utils.format_dict(snapshot._info.pop('metadata'))} + {'properties': + format_columns.DictColumn(snapshot._info.pop('metadata'))} ) return zip(*sorted(six.iteritems(snapshot._info))) diff --git a/openstackclient/volume/v1/volume_type.py b/openstackclient/volume/v1/volume_type.py index b4d8eaca20..e744e92fdb 100644 --- a/openstackclient/volume/v1/volume_type.py +++ b/openstackclient/volume/v1/volume_type.py @@ -15,8 +15,11 @@ """Volume v1 Type action implementations""" +import functools import logging +from cliff import columns as cliff_columns +from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -29,6 +32,36 @@ LOG = logging.getLogger(__name__) +class EncryptionInfoColumn(cliff_columns.FormattableColumn): + """Formattable column for encryption info column. + + Unlike the parent FormattableColumn class, the initializer of the + class takes encryption_data as the second argument. + osc_lib.utils.get_item_properties instantiate cliff FormattableColumn + object with a single parameter "column value", so you need to pass + a partially initialized class like + ``functools.partial(EncryptionInfoColumn encryption_data)``. + """ + + def __init__(self, value, encryption_data=None): + super(EncryptionInfoColumn, self).__init__(value) + self._encryption_data = encryption_data or {} + + def _get_encryption_info(self): + type_id = self._value + return self._encryption_data.get(type_id) + + def human_readable(self): + encryption_info = self._get_encryption_info() + if encryption_info: + return utils.format_dict(encryption_info) + else: + return '-' + + def machine_readable(self): + return self._get_encryption_info() + + def _create_encryption_type(volume_client, volume_type, parsed_args): if not parsed_args.encryption_provider: msg = _("'--encryption-provider' should be specified while " @@ -110,7 +143,8 @@ def take_action(self, parsed_args): volume_type._info.pop('extra_specs') if parsed_args.property: result = volume_type.set_keys(parsed_args.property) - volume_type._info.update({'properties': utils.format_dict(result)}) + volume_type._info.update( + {'properties': format_columns.DictColumn(result)}) if (parsed_args.encryption_provider or parsed_args.encryption_cipher or parsed_args.encryption_key_size or @@ -125,7 +159,7 @@ def take_action(self, parsed_args): # add encryption info in result encryption._info.pop("volume_type_id", None) volume_type._info.update( - {'encryption': utils.format_dict(encryption._info)}) + {'encryption': format_columns.DictColumn(encryption._info)}) volume_type._info.pop("os-volume-type-access:is_public", None) return zip(*sorted(six.iteritems(volume_type._info))) @@ -196,12 +230,7 @@ def take_action(self, parsed_args): column_headers = ['ID', 'Name', 'Is Public'] data = volume_client.volume_types.list() - def _format_encryption_info(type_id, encryption_data=None): - encryption_data = encryption - encryption_info = '-' - if type_id in encryption_data.keys(): - encryption_info = encryption_data[type_id] - return encryption_info + formatters = {'Extra Specs': format_columns.DictColumn} if parsed_args.encryption_type: encryption = {} @@ -218,18 +247,21 @@ def _format_encryption_info(type_id, encryption_data=None): for key in del_key: d._info.pop(key, None) # save the encryption information with their volume type ID - encryption[volume_type_id] = utils.format_dict(d._info) + encryption[volume_type_id] = d._info # We need to get volume type ID, then show encryption # information according to the ID, so use "id" to keep # difference to the real "ID" column. columns += ['id'] column_headers += ['Encryption'] + _EncryptionInfoColumn = functools.partial( + EncryptionInfoColumn, encryption_data=encryption) + formatters['id'] = _EncryptionInfoColumn + return (column_headers, (utils.get_item_properties( s, columns, - formatters={'Extra Specs': utils.format_dict, - 'id': _format_encryption_info}, + formatters=formatters, ) for s in data)) @@ -340,7 +372,8 @@ def take_action(self, parsed_args): volume_client = self.app.client_manager.volume volume_type = utils.find_resource( volume_client.volume_types, parsed_args.volume_type) - properties = utils.format_dict(volume_type._info.pop('extra_specs')) + properties = format_columns.DictColumn( + volume_type._info.pop('extra_specs')) volume_type._info.update({'properties': properties}) if parsed_args.encryption_type: # show encryption type information for this volume type @@ -349,7 +382,8 @@ def take_action(self, parsed_args): volume_type.id) encryption._info.pop("volume_type_id", None) volume_type._info.update( - {'encryption': utils.format_dict(encryption._info)}) + {'encryption': + format_columns.DictColumn(encryption._info)}) except Exception as e: LOG.error(_("Failed to display the encryption information " "of this volume type: %s"), e) From 4cd614305fbe8b90d45ad6803bfa8b7978f19318 Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Mon, 15 May 2017 10:19:07 +0000 Subject: [PATCH 2076/3095] Use cliff formattable columns in volume v2 commands Partial-Bug: #1687955 Partially implement blueprint osc-formattable-columns Change-Id: I761ccac126208927594ad0d98a3cf5ad8b44bd48 --- .../tests/functional/volume/v2/test_qos.py | 6 +- .../tests/functional/volume/v2/test_volume.py | 6 +- .../volume/v2/test_volume_snapshot.py | 8 +- .../functional/volume/v2/test_volume_type.py | 74 +++++----- openstackclient/tests/unit/volume/v2/fakes.py | 5 +- .../unit/volume/v2/test_consistency_group.py | 17 +-- .../tests/unit/volume/v2/test_qos_specs.py | 28 ++-- .../tests/unit/volume/v2/test_type.py | 75 ++++++++--- .../tests/unit/volume/v2/test_volume.py | 126 +++++++++--------- .../unit/volume/v2/test_volume_backup.py | 6 +- .../volume/v2/consistency_group.py | 3 +- openstackclient/volume/v2/qos_specs.py | 13 +- openstackclient/volume/v2/volume.py | 62 ++++++--- openstackclient/volume/v2/volume_backup.py | 45 +++++-- openstackclient/volume/v2/volume_snapshot.py | 54 +++++--- openstackclient/volume/v2/volume_type.py | 61 +++++++-- 16 files changed, 360 insertions(+), 229 deletions(-) diff --git a/openstackclient/tests/functional/volume/v2/test_qos.py b/openstackclient/tests/functional/volume/v2/test_qos.py index 646becc1a7..f9f6e099fa 100644 --- a/openstackclient/tests/functional/volume/v2/test_qos.py +++ b/openstackclient/tests/functional/volume/v2/test_qos.py @@ -74,7 +74,7 @@ def test_volume_qos_set_show_unset(self): cmd_output['consumer'] ) self.assertEqual( - "Alpha='a'", + {'Alpha': 'a'}, cmd_output['properties'] ) @@ -97,7 +97,7 @@ def test_volume_qos_set_show_unset(self): cmd_output['name'] ) self.assertEqual( - "Alpha='c', Beta='b'", + {'Alpha': 'c', 'Beta': 'b'}, cmd_output['properties'] ) @@ -118,7 +118,7 @@ def test_volume_qos_set_show_unset(self): cmd_output['name'] ) self.assertEqual( - "Beta='b'", + {'Beta': 'b'}, cmd_output['properties'] ) diff --git a/openstackclient/tests/functional/volume/v2/test_volume.py b/openstackclient/tests/functional/volume/v2/test_volume.py index 2930d48370..19fd58950e 100644 --- a/openstackclient/tests/functional/volume/v2/test_volume.py +++ b/openstackclient/tests/functional/volume/v2/test_volume.py @@ -128,7 +128,7 @@ def test_volume_set_and_unset(self): cmd_output["description"], ) self.assertEqual( - "Alpha='a'", + {'Alpha': 'a'}, cmd_output["properties"], ) self.assertEqual( @@ -170,7 +170,7 @@ def test_volume_set_and_unset(self): cmd_output["description"], ) self.assertEqual( - "Beta='b', Gamma='c'", + {'Beta': 'b', 'Gamma': 'c'}, cmd_output["properties"], ) self.assertEqual( @@ -196,7 +196,7 @@ def test_volume_set_and_unset(self): new_name )) self.assertEqual( - "Gamma='c'", + {'Gamma': 'c'}, cmd_output["properties"], ) self.assertEqual( diff --git a/openstackclient/tests/functional/volume/v2/test_volume_snapshot.py b/openstackclient/tests/functional/volume/v2/test_volume_snapshot.py index 264f4adb6b..8d32d99731 100644 --- a/openstackclient/tests/functional/volume/v2/test_volume_snapshot.py +++ b/openstackclient/tests/functional/volume/v2/test_volume_snapshot.py @@ -182,7 +182,7 @@ def test_volume_snapshot_set(self): cmd_output["description"], ) self.assertEqual( - "Alpha='a'", + {'Alpha': 'a'}, cmd_output["properties"], ) self.wait_for_status('volume snapshot', name, 'available') @@ -216,7 +216,7 @@ def test_volume_snapshot_set(self): cmd_output["description"], ) self.assertEqual( - "Alpha='c', Beta='b'", + {'Alpha': 'c', 'Beta': 'b'}, cmd_output["properties"], ) @@ -233,7 +233,7 @@ def test_volume_snapshot_set(self): new_name )) self.assertEqual( - "Beta='b'", + {'Beta': 'b'}, cmd_output["properties"], ) @@ -249,6 +249,6 @@ def test_volume_snapshot_set(self): new_name )) self.assertNotIn( - "Beta='b'", + {'Beta': 'b'}, cmd_output["properties"], ) diff --git a/openstackclient/tests/functional/volume/v2/test_volume_type.py b/openstackclient/tests/functional/volume/v2/test_volume_type.py index d8dd5bd626..3f1a6ea8a5 100644 --- a/openstackclient/tests/functional/volume/v2/test_volume_type.py +++ b/openstackclient/tests/functional/volume/v2/test_volume_type.py @@ -65,8 +65,7 @@ def test_volume_type_set_unset_properties(self): cmd_output = json.loads(self.openstack( 'volume type show -f json %s' % name )) - # TODO(amotoki): properties output should be machine-readable - self.assertEqual("a='b', c='d'", cmd_output['properties']) + self.assertEqual({'a': 'b', 'c': 'd'}, cmd_output['properties']) raw_output = self.openstack( 'volume type unset --property a %s' % name @@ -75,7 +74,7 @@ def test_volume_type_set_unset_properties(self): cmd_output = json.loads(self.openstack( 'volume type show -f json %s' % name )) - self.assertEqual("c='d'", cmd_output['properties']) + self.assertEqual({'c': 'd'}, cmd_output['properties']) def test_volume_type_set_unset_multiple_properties(self): name = uuid.uuid4().hex @@ -96,7 +95,7 @@ def test_volume_type_set_unset_multiple_properties(self): cmd_output = json.loads(self.openstack( 'volume type show -f json %s' % name )) - self.assertEqual("a='b', c='d'", cmd_output['properties']) + self.assertEqual({'a': 'b', 'c': 'd'}, cmd_output['properties']) raw_output = self.openstack( 'volume type unset --property a --property c %s' % name @@ -105,7 +104,7 @@ def test_volume_type_set_unset_multiple_properties(self): cmd_output = json.loads(self.openstack( 'volume type show -f json %s' % name )) - self.assertEqual("", cmd_output['properties']) + self.assertEqual({}, cmd_output['properties']) def test_volume_type_set_unset_project(self): name = uuid.uuid4().hex @@ -155,35 +154,32 @@ def test_encryption_type(self): '--encryption-key-size 128 ' '--encryption-control-location front-end ' + encryption_type)) - # TODO(amotoki): encryption output should be machine-readable - expected = ["provider='LuksEncryptor'", - "cipher='aes-xts-plain64'", - "key_size='128'", - "control_location='front-end'"] - for attr in expected: - self.assertIn(attr, cmd_output['encryption']) + expected = {'provider': 'LuksEncryptor', + 'cipher': 'aes-xts-plain64', + 'key_size': 128, + 'control_location': 'front-end'} + for attr, value in expected.items(): + self.assertEqual(value, cmd_output['encryption'][attr]) # test show encryption type cmd_output = json.loads(self.openstack( 'volume type show -f json --encryption-type ' + encryption_type)) - # TODO(amotoki): encryption output should be machine-readable - expected = ["provider='LuksEncryptor'", - "cipher='aes-xts-plain64'", - "key_size='128'", - "control_location='front-end'"] - for attr in expected: - self.assertIn(attr, cmd_output['encryption']) + expected = {'provider': 'LuksEncryptor', + 'cipher': 'aes-xts-plain64', + 'key_size': 128, + 'control_location': 'front-end'} + for attr, value in expected.items(): + self.assertEqual(value, cmd_output['encryption'][attr]) # test list encryption type cmd_output = json.loads(self.openstack( 'volume type list -f json --encryption-type')) encryption_output = [t['Encryption'] for t in cmd_output if t['Name'] == encryption_type][0] - # TODO(amotoki): encryption output should be machine-readable - expected = ["provider='LuksEncryptor'", - "cipher='aes-xts-plain64'", - "key_size='128'", - "control_location='front-end'"] - for attr in expected: - self.assertIn(attr, encryption_output) + expected = {'provider': 'LuksEncryptor', + 'cipher': 'aes-xts-plain64', + 'key_size': 128, + 'control_location': 'front-end'} + for attr, value in expected.items(): + self.assertEqual(value, encryption_output[attr]) # test set existing encryption type raw_output = self.openstack( 'volume type set ' @@ -193,12 +189,12 @@ def test_encryption_type(self): self.assertEqual('', raw_output) cmd_output = json.loads(self.openstack( 'volume type show -f json --encryption-type ' + encryption_type)) - expected = ["provider='LuksEncryptor'", - "cipher='aes-xts-plain64'", - "key_size='256'", - "control_location='back-end'"] - for attr in expected: - self.assertIn(attr, cmd_output['encryption']) + expected = {'provider': 'LuksEncryptor', + 'cipher': 'aes-xts-plain64', + 'key_size': 256, + 'control_location': 'back-end'} + for attr, value in expected.items(): + self.assertEqual(value, cmd_output['encryption'][attr]) # test set new encryption type cmd_output = json.loads(self.openstack( 'volume type create -f json --private ' + @@ -222,12 +218,12 @@ def test_encryption_type(self): cmd_output = json.loads(self.openstack( 'volume type show -f json --encryption-type ' + name )) - expected = ["provider='LuksEncryptor'", - "cipher='aes-xts-plain64'", - "key_size='128'", - "control_location='front-end'"] - for attr in expected: - self.assertIn(attr, cmd_output['encryption']) + expected = {'provider': 'LuksEncryptor', + 'cipher': 'aes-xts-plain64', + 'key_size': 128, + 'control_location': 'front-end'} + for attr, value in expected.items(): + self.assertEqual(value, cmd_output['encryption'][attr]) # test unset encryption type raw_output = self.openstack( 'volume type unset --encryption-type ' + name @@ -236,7 +232,7 @@ def test_encryption_type(self): cmd_output = json.loads(self.openstack( 'volume type show -f json --encryption-type ' + name )) - self.assertEqual('', cmd_output['encryption']) + self.assertEqual({}, cmd_output['encryption']) # test delete encryption type raw_output = self.openstack('volume type delete ' + encryption_type) self.assertEqual('', raw_output) diff --git a/openstackclient/tests/unit/volume/v2/fakes.py b/openstackclient/tests/unit/volume/v2/fakes.py index c245cbf66b..5f976b0625 100644 --- a/openstackclient/tests/unit/volume/v2/fakes.py +++ b/openstackclient/tests/unit/volume/v2/fakes.py @@ -17,7 +17,8 @@ import uuid import mock -from osc_lib import utils as common_utils + +from osc_lib.cli import format_columns from openstackclient.tests.unit import fakes from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes @@ -468,7 +469,7 @@ def get_volume_data(volume=None): if x == 'tags': # The 'tags' should be format_list data_list.append( - common_utils.format_list(volume.info.get(x))) + format_columns.ListColumn(volume.info.get(x))) else: data_list.append(volume.info.get(x)) return tuple(data_list) diff --git a/openstackclient/tests/unit/volume/v2/test_consistency_group.py b/openstackclient/tests/unit/volume/v2/test_consistency_group.py index 6eeeae393e..d238818245 100644 --- a/openstackclient/tests/unit/volume/v2/test_consistency_group.py +++ b/openstackclient/tests/unit/volume/v2/test_consistency_group.py @@ -15,6 +15,7 @@ import mock from mock import call +from osc_lib.cli import format_columns from osc_lib import exceptions from osc_lib import utils @@ -250,7 +251,7 @@ def test_consistency_group_create_without_name(self): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_consistency_group_create_from_source(self): arglist = [ @@ -278,7 +279,7 @@ def test_consistency_group_create_from_source(self): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_consistency_group_create_from_snapshot(self): arglist = [ @@ -306,7 +307,7 @@ def test_consistency_group_create_from_snapshot(self): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) class TestConsistencyGroupDelete(TestConsistencyGroup): @@ -439,7 +440,7 @@ class TestConsistencyGroupList(TestConsistencyGroup): c.availability_zone, c.name, c.description, - utils.format_list(c.volume_types) + format_columns.ListColumn(c.volume_types) )) def setUp(self): @@ -462,7 +463,7 @@ def test_consistency_group_list_without_options(self): self.consistencygroups_mock.list.assert_called_once_with( detailed=True, search_opts={'all_tenants': False}) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_consistency_group_list_with_all_project(self): arglist = [ @@ -479,7 +480,7 @@ def test_consistency_group_list_with_all_project(self): self.consistencygroups_mock.list.assert_called_once_with( detailed=True, search_opts={'all_tenants': True}) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_consistency_group_list_with_long(self): arglist = [ @@ -496,7 +497,7 @@ def test_consistency_group_list_with_long(self): self.consistencygroups_mock.list.assert_called_once_with( detailed=True, search_opts={'all_tenants': False}) self.assertEqual(self.columns_long, columns) - self.assertEqual(self.data_long, list(data)) + self.assertListItemEqual(self.data_long, list(data)) class TestConsistencyGroupRemoveVolume(TestConsistencyGroup): @@ -704,4 +705,4 @@ def test_consistency_group_show(self): self.consistencygroups_mock.get.assert_called_once_with( self.consistency_group.id) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) diff --git a/openstackclient/tests/unit/volume/v2/test_qos_specs.py b/openstackclient/tests/unit/volume/v2/test_qos_specs.py index 2b935e205f..454747f5aa 100644 --- a/openstackclient/tests/unit/volume/v2/test_qos_specs.py +++ b/openstackclient/tests/unit/volume/v2/test_qos_specs.py @@ -17,6 +17,8 @@ import mock from mock import call + +from osc_lib.cli import format_columns from osc_lib import exceptions from osc_lib import utils @@ -88,7 +90,7 @@ def setUp(self): self.new_qos_spec.consumer, self.new_qos_spec.id, self.new_qos_spec.name, - utils.format_dict(self.new_qos_spec.specs) + format_columns.DictColumn(self.new_qos_spec.specs) ) # Get the command object to test @@ -111,7 +113,7 @@ def test_qos_create_without_properties(self): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_qos_create_with_consumer(self): arglist = [ @@ -132,7 +134,7 @@ def test_qos_create_with_consumer(self): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_qos_create_with_properties(self): arglist = [ @@ -158,7 +160,7 @@ def test_qos_create_with_properties(self): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) class TestQosDelete(TestQos): @@ -318,8 +320,8 @@ class TestQosList(TestQos): q.id, q.name, q.consumer, - qos_association.name, - utils.format_dict(q.specs), + format_columns.ListColumn([qos_association.name]), + format_columns.DictColumn(q.specs), )) def setUp(self): @@ -341,7 +343,7 @@ def test_qos_list(self): self.qos_mock.list.assert_called_with() self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_qos_list_no_association(self): self.qos_mock.reset_mock() @@ -365,10 +367,10 @@ def test_qos_list_no_association(self): self.qos_specs[1].id, self.qos_specs[1].name, self.qos_specs[1].consumer, - None, - utils.format_dict(self.qos_specs[1].specs), + format_columns.ListColumn(None), + format_columns.DictColumn(self.qos_specs[1].specs), ) - self.assertEqual(ex_data, list(data)) + self.assertListItemEqual(ex_data, list(data)) class TestQosSet(TestQos): @@ -416,11 +418,11 @@ class TestQosShow(TestQos): 'properties' ) data = ( - qos_association.name, + format_columns.ListColumn([qos_association.name]), qos_spec.consumer, qos_spec.id, qos_spec.name, - utils.format_dict(qos_spec.specs), + format_columns.DictColumn(qos_spec.specs), ) def setUp(self): @@ -448,7 +450,7 @@ def test_qos_show(self): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, tuple(data)) + self.assertItemEqual(self.data, tuple(data)) class TestQosUnset(TestQos): diff --git a/openstackclient/tests/unit/volume/v2/test_type.py b/openstackclient/tests/unit/volume/v2/test_type.py index 4023d55b3d..17915928dc 100644 --- a/openstackclient/tests/unit/volume/v2/test_type.py +++ b/openstackclient/tests/unit/volume/v2/test_type.py @@ -15,6 +15,7 @@ import mock from mock import call +from osc_lib.cli import format_columns from osc_lib import exceptions from osc_lib import utils @@ -92,7 +93,7 @@ def test_type_create_public(self): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_type_create_private(self): arglist = [ @@ -118,7 +119,7 @@ def test_type_create_private(self): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_public_type_create_with_project(self): arglist = [ @@ -158,7 +159,7 @@ def test_type_create_with_encryption(self): ) encryption_data = ( self.new_volume_type.description, - utils.format_dict(encryption_info), + format_columns.DictColumn(encryption_info), self.new_volume_type.id, True, self.new_volume_type.name, @@ -195,7 +196,7 @@ def test_type_create_with_encryption(self): body, ) self.assertEqual(encryption_columns, columns) - self.assertEqual(encryption_data, data) + self.assertItemEqual(encryption_data, data) class TestTypeDelete(TestType): @@ -305,7 +306,7 @@ class TestTypeList(TestType): t.name, t.is_public, t.description, - utils.format_dict(t.extra_specs), + format_columns.DictColumn(t.extra_specs), )) def setUp(self): @@ -329,7 +330,7 @@ def test_type_list_without_options(self): columns, data = self.cmd.take_action(parsed_args) self.types_mock.list.assert_called_once_with(is_public=None) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_type_list_with_options(self): arglist = [ @@ -347,7 +348,7 @@ def test_type_list_with_options(self): columns, data = self.cmd.take_action(parsed_args) self.types_mock.list.assert_called_once_with(is_public=True) self.assertEqual(self.columns_long, columns) - self.assertEqual(self.data_long, list(data)) + self.assertListItemEqual(self.data_long, list(data)) def test_type_list_with_private_option(self): arglist = [ @@ -364,7 +365,7 @@ def test_type_list_with_private_option(self): columns, data = self.cmd.take_action(parsed_args) self.types_mock.list.assert_called_once_with(is_public=False) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_type_list_with_default_option(self): arglist = [ @@ -382,7 +383,7 @@ def test_type_list_with_default_option(self): columns, data = self.cmd.take_action(parsed_args) self.types_mock.default.assert_called_once_with() self.assertEqual(self.columns, columns) - self.assertEqual(self.data_with_default_type, list(data)) + self.assertListItemEqual(self.data_with_default_type, list(data)) def test_type_list_with_encryption(self): encryption_type = volume_fakes.FakeType.create_one_encryption_type( @@ -401,13 +402,16 @@ def test_type_list_with_encryption(self): self.volume_types[0].id, self.volume_types[0].name, self.volume_types[0].is_public, - utils.format_dict(encryption_info), + volume_type.EncryptionInfoColumn( + self.volume_types[0].id, + {self.volume_types[0].id: encryption_info}), )) encryption_data.append(( self.volume_types[1].id, self.volume_types[1].name, self.volume_types[1].is_public, - '-', + volume_type.EncryptionInfoColumn( + self.volume_types[1].id, {}), )) self.encryption_types_mock.list.return_value = [encryption_type] @@ -423,7 +427,7 @@ def test_type_list_with_encryption(self): self.encryption_types_mock.list.assert_called_once_with() self.types_mock.list.assert_called_once_with(is_public=None) self.assertEqual(encryption_columns, columns) - self.assertEqual(encryption_data, list(data)) + self.assertListItemEqual(encryption_data, list(data)) class TestTypeSet(TestType): @@ -687,7 +691,7 @@ def setUp(self): self.volume_type.id, True, self.volume_type.name, - utils.format_dict(self.volume_type.extra_specs) + format_columns.DictColumn(self.volume_type.extra_specs) ) self.types_mock.get.return_value = self.volume_type @@ -709,7 +713,7 @@ def test_type_show(self): self.types_mock.get.assert_called_with(self.volume_type.id) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_type_show_with_access(self): arglist = [ @@ -735,14 +739,14 @@ def test_type_show_with_access(self): self.assertEqual(self.columns, columns) private_type_data = ( - utils.format_list([type_access_list.project_id]), + format_columns.ListColumn([type_access_list.project_id]), private_type.description, private_type.id, private_type.is_public, private_type.name, - utils.format_dict(private_type.extra_specs) + format_columns.DictColumn(private_type.extra_specs) ) - self.assertEqual(private_type_data, data) + self.assertItemEqual(private_type_data, data) def test_type_show_with_list_access_exec(self): arglist = [ @@ -772,9 +776,9 @@ def test_type_show_with_list_access_exec(self): private_type.id, private_type.is_public, private_type.name, - utils.format_dict(private_type.extra_specs) + format_columns.DictColumn(private_type.extra_specs) ) - self.assertEqual(private_type_data, data) + self.assertItemEqual(private_type_data, data) def test_type_show_with_encryption(self): encryption_type = volume_fakes.FakeType.create_one_encryption_type() @@ -800,11 +804,11 @@ def test_type_show_with_encryption(self): encryption_data = ( None, self.volume_type.description, - utils.format_dict(encryption_info), + format_columns.DictColumn(encryption_info), self.volume_type.id, True, self.volume_type.name, - utils.format_dict(self.volume_type.extra_specs) + format_columns.DictColumn(self.volume_type.extra_specs) ) arglist = [ '--encryption-type', @@ -820,7 +824,7 @@ def test_type_show_with_encryption(self): self.types_mock.get.assert_called_with(self.volume_type.id) self.encryption_types_mock.get.assert_called_with(self.volume_type.id) self.assertEqual(encryption_columns, columns) - self.assertEqual(encryption_data, data) + self.assertItemEqual(encryption_data, data) class TestTypeUnset(TestType): @@ -923,3 +927,30 @@ def test_type_unset_encryption_type(self): result = self.cmd.take_action(parsed_args) self.encryption_types_mock.delete.assert_called_with(self.volume_type) self.assertIsNone(result) + + +class TestColumns(TestType): + + def test_encryption_info_column_with_info(self): + fake_volume_type = volume_fakes.FakeType.create_one_type() + type_id = fake_volume_type.id + + encryption_info = { + 'provider': 'LuksEncryptor', + 'cipher': None, + 'key_size': None, + 'control_location': 'front-end', + } + col = volume_type.EncryptionInfoColumn(type_id, + {type_id: encryption_info}) + self.assertEqual(utils.format_dict(encryption_info), + col.human_readable()) + self.assertEqual(encryption_info, col.machine_readable()) + + def test_encryption_info_column_without_info(self): + fake_volume_type = volume_fakes.FakeType.create_one_type() + type_id = fake_volume_type.id + + col = volume_type.EncryptionInfoColumn(type_id, {}) + self.assertEqual('-', col.human_readable()) + self.assertIsNone(col.machine_readable()) diff --git a/openstackclient/tests/unit/volume/v2/test_volume.py b/openstackclient/tests/unit/volume/v2/test_volume.py index 97da160148..332b50a78f 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume.py +++ b/openstackclient/tests/unit/volume/v2/test_volume.py @@ -16,6 +16,8 @@ import mock from mock import call + +from osc_lib.cli import format_columns from osc_lib import exceptions from osc_lib import utils @@ -94,7 +96,7 @@ def setUp(self): self.new_volume.description, self.new_volume.id, self.new_volume.name, - utils.format_dict(self.new_volume.metadata), + format_columns.DictColumn(self.new_volume.metadata), self.new_volume.size, self.new_volume.snapshot_id, self.new_volume.status, @@ -135,7 +137,7 @@ def test_volume_create_min_options(self): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_volume_create_options(self): consistency_group = ( @@ -181,7 +183,7 @@ def test_volume_create_options(self): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_volume_create_properties(self): arglist = [ @@ -217,7 +219,7 @@ def test_volume_create_properties(self): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_volume_create_image_id(self): image = image_fakes.FakeImage.create_one_image() @@ -255,7 +257,7 @@ def test_volume_create_image_id(self): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_volume_create_image_name(self): image = image_fakes.FakeImage.create_one_image() @@ -293,7 +295,7 @@ def test_volume_create_image_name(self): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_volume_create_with_snapshot(self): snapshot = volume_fakes.FakeSnapshot.create_one_snapshot() @@ -330,7 +332,7 @@ def test_volume_create_with_snapshot(self): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_volume_create_with_bootable_and_readonly(self): arglist = [ @@ -368,7 +370,7 @@ def test_volume_create_with_bootable_and_readonly(self): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(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( @@ -410,7 +412,7 @@ def test_volume_create_with_nonbootable_and_readwrite(self): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(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( @@ -462,7 +464,7 @@ def test_volume_create_with_bootable_and_readonly_fail( self.assertEqual(2, mock_error.call_count) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(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( @@ -672,17 +674,14 @@ def test_volume_list_no_options(self): self.assertEqual(self.columns, columns) - server = self.mock_volume.attachments[0]['server_id'] - device = self.mock_volume.attachments[0]['device'] - msg = 'Attached to %s on %s ' % (server, device) datalist = (( self.mock_volume.id, self.mock_volume.name, self.mock_volume.status, self.mock_volume.size, - msg, + volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertEqual(datalist, tuple(data)) + self.assertListItemEqual(datalist, tuple(data)) def test_volume_list_project(self): arglist = [ @@ -715,17 +714,14 @@ def test_volume_list_project(self): self.assertEqual(self.columns, columns) - server = self.mock_volume.attachments[0]['server_id'] - device = self.mock_volume.attachments[0]['device'] - msg = 'Attached to %s on %s ' % (server, device) datalist = (( self.mock_volume.id, self.mock_volume.name, self.mock_volume.status, self.mock_volume.size, - msg, + volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertEqual(datalist, tuple(data)) + self.assertListItemEqual(datalist, tuple(data)) def test_volume_list_project_domain(self): arglist = [ @@ -760,17 +756,14 @@ def test_volume_list_project_domain(self): self.assertEqual(self.columns, columns) - server = self.mock_volume.attachments[0]['server_id'] - device = self.mock_volume.attachments[0]['device'] - msg = 'Attached to %s on %s ' % (server, device) datalist = (( self.mock_volume.id, self.mock_volume.name, self.mock_volume.status, self.mock_volume.size, - msg, + volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertEqual(datalist, tuple(data)) + self.assertListItemEqual(datalist, tuple(data)) def test_volume_list_user(self): arglist = [ @@ -800,19 +793,16 @@ def test_volume_list_user(self): marker=None, limit=None, ) - self.assertEqual(self.columns, columns) - server = self.mock_volume.attachments[0]['server_id'] - device = self.mock_volume.attachments[0]['device'] - msg = 'Attached to %s on %s ' % (server, device) + datalist = (( self.mock_volume.id, self.mock_volume.name, self.mock_volume.status, self.mock_volume.size, - msg, + volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertEqual(datalist, tuple(data)) + self.assertListItemEqual(datalist, tuple(data)) def test_volume_list_user_domain(self): arglist = [ @@ -847,17 +837,14 @@ def test_volume_list_user_domain(self): self.assertEqual(self.columns, columns) - server = self.mock_volume.attachments[0]['server_id'] - device = self.mock_volume.attachments[0]['device'] - msg = 'Attached to %s on %s ' % (server, device) datalist = (( self.mock_volume.id, self.mock_volume.name, self.mock_volume.status, self.mock_volume.size, - msg, + volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertEqual(datalist, tuple(data)) + self.assertListItemEqual(datalist, tuple(data)) def test_volume_list_name(self): arglist = [ @@ -890,17 +877,14 @@ def test_volume_list_name(self): self.assertEqual(self.columns, columns) - server = self.mock_volume.attachments[0]['server_id'] - device = self.mock_volume.attachments[0]['device'] - msg = 'Attached to %s on %s ' % (server, device) datalist = (( self.mock_volume.id, self.mock_volume.name, self.mock_volume.status, self.mock_volume.size, - msg, + volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertEqual(datalist, tuple(data)) + self.assertListItemEqual(datalist, tuple(data)) def test_volume_list_status(self): arglist = [ @@ -933,17 +917,14 @@ def test_volume_list_status(self): self.assertEqual(self.columns, columns) - server = self.mock_volume.attachments[0]['server_id'] - device = self.mock_volume.attachments[0]['device'] - msg = 'Attached to %s on %s ' % (server, device) datalist = (( self.mock_volume.id, self.mock_volume.name, self.mock_volume.status, self.mock_volume.size, - msg, + volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertEqual(datalist, tuple(data)) + self.assertListItemEqual(datalist, tuple(data)) def test_volume_list_all_projects(self): arglist = [ @@ -976,17 +957,14 @@ def test_volume_list_all_projects(self): self.assertEqual(self.columns, columns) - server = self.mock_volume.attachments[0]['server_id'] - device = self.mock_volume.attachments[0]['device'] - msg = 'Attached to %s on %s ' % (server, device) datalist = (( self.mock_volume.id, self.mock_volume.name, self.mock_volume.status, self.mock_volume.size, - msg, + volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertEqual(datalist, tuple(data)) + self.assertListItemEqual(datalist, tuple(data)) def test_volume_list_long(self): arglist = [ @@ -1030,9 +1008,6 @@ def test_volume_list_long(self): ] self.assertEqual(collist, columns) - server = self.mock_volume.attachments[0]['server_id'] - device = self.mock_volume.attachments[0]['device'] - msg = 'Attached to %s on %s ' % (server, device) datalist = (( self.mock_volume.id, self.mock_volume.name, @@ -1040,10 +1015,10 @@ def test_volume_list_long(self): self.mock_volume.size, self.mock_volume.volume_type, self.mock_volume.bootable, - msg, - utils.format_dict(self.mock_volume.metadata), + volume.AttachmentsColumn(self.mock_volume.attachments), + format_columns.DictColumn(self.mock_volume.metadata), ), ) - self.assertEqual(datalist, tuple(data)) + self.assertListItemEqual(datalist, tuple(data)) def test_volume_list_with_marker_and_limit(self): arglist = [ @@ -1064,15 +1039,12 @@ def test_volume_list_with_marker_and_limit(self): self.assertEqual(self.columns, columns) - server = self.mock_volume.attachments[0]['server_id'] - device = self.mock_volume.attachments[0]['device'] - msg = 'Attached to %s on %s ' % (server, device) datalist = (( self.mock_volume.id, self.mock_volume.name, self.mock_volume.status, self.mock_volume.size, - msg, + volume.AttachmentsColumn(self.mock_volume.attachments), ), ) self.volumes_mock.list.assert_called_once_with( @@ -1085,7 +1057,7 @@ def test_volume_list_with_marker_and_limit(self): 'name': None, 'all_tenants': False, } ) - self.assertEqual(datalist, tuple(data)) + self.assertListItemEqual(datalist, tuple(data)) def test_volume_list_negative_limit(self): arglist = [ @@ -1479,7 +1451,7 @@ def test_volume_show(self): volume_fakes.FakeVolume.get_volume_columns(self._volume), columns) - self.assertEqual( + self.assertItemEqual( volume_fakes.FakeVolume.get_volume_data(self._volume), data) @@ -1562,3 +1534,31 @@ def test_volume_unset_image_property_fail(self): self.new_volume.id, parsed_args.image_property) self.volumes_mock.delete_metadata.assert_called_with( self.new_volume.id, parsed_args.property) + + +class TestColumns(TestVolume): + + def test_attachments_column_without_server_cache(self): + _volume = volume_fakes.FakeVolume.create_one_volume() + server_id = _volume.attachments[0]['server_id'] + device = _volume.attachments[0]['device'] + + col = volume.AttachmentsColumn(_volume.attachments, {}) + self.assertEqual('Attached to %s on %s ' % (server_id, device), + col.human_readable()) + self.assertEqual(_volume.attachments, col.machine_readable()) + + def test_attachments_column_with_server_cache(self): + _volume = volume_fakes.FakeVolume.create_one_volume() + + server_id = _volume.attachments[0]['server_id'] + device = _volume.attachments[0]['device'] + fake_server = mock.Mock() + fake_server.name = 'fake-server-name' + server_cache = {server_id: fake_server} + + col = volume.AttachmentsColumn(_volume.attachments, server_cache) + self.assertEqual( + 'Attached to %s on %s ' % ('fake-server-name', device), + col.human_readable()) + self.assertEqual(_volume.attachments, col.machine_readable()) diff --git a/openstackclient/tests/unit/volume/v2/test_volume_backup.py b/openstackclient/tests/unit/volume/v2/test_volume_backup.py index d0d1da3b29..30c7915dd9 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume_backup.py +++ b/openstackclient/tests/unit/volume/v2/test_volume_backup.py @@ -271,7 +271,7 @@ class TestBackupList(TestBackup): b.status, b.size, b.availability_zone, - b.volume_id, + volume_backup.VolumeIdColumn(b.volume_id), b.container, )) @@ -314,7 +314,7 @@ def test_backup_list_without_options(self): limit=None, ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_backup_list_with_options(self): arglist = [ @@ -353,7 +353,7 @@ def test_backup_list_with_options(self): limit=3, ) self.assertEqual(self.columns_long, columns) - self.assertEqual(self.data_long, list(data)) + self.assertListItemEqual(self.data_long, list(data)) class TestBackupRestore(TestBackup): diff --git a/openstackclient/volume/v2/consistency_group.py b/openstackclient/volume/v2/consistency_group.py index 0a932f8454..26dd8ffca1 100644 --- a/openstackclient/volume/v2/consistency_group.py +++ b/openstackclient/volume/v2/consistency_group.py @@ -16,6 +16,7 @@ import logging +from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -238,7 +239,7 @@ def take_action(self, parsed_args): return (columns, ( utils.get_item_properties( s, columns, - formatters={'Volume Types': utils.format_list}) + formatters={'Volume Types': format_columns.ListColumn}) for s in consistency_groups)) diff --git a/openstackclient/volume/v2/qos_specs.py b/openstackclient/volume/v2/qos_specs.py index c71605818a..3037d34ae8 100644 --- a/openstackclient/volume/v2/qos_specs.py +++ b/openstackclient/volume/v2/qos_specs.py @@ -17,6 +17,7 @@ import logging +from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -96,7 +97,8 @@ def take_action(self, parsed_args): qos_spec = volume_client.qos_specs.create(parsed_args.name, specs) qos_spec._info.update( - {'properties': utils.format_dict(qos_spec._info.pop('specs'))} + {'properties': + format_columns.DictColumn(qos_spec._info.pop('specs'))} ) return zip(*sorted(six.iteritems(qos_spec._info))) @@ -210,8 +212,8 @@ def take_action(self, parsed_args): (utils.get_dict_properties( s._info, columns, formatters={ - 'Specs': utils.format_dict, - 'Associations': utils.format_list + 'Specs': format_columns.DictColumn, + 'Associations': format_columns.ListColumn }, ) for s in qos_specs_list)) @@ -267,10 +269,11 @@ def take_action(self, parsed_args): associations = [association.name for association in qos_associations] qos_spec._info.update({ - 'associations': utils.format_list(associations) + 'associations': format_columns.ListColumn(associations) }) qos_spec._info.update( - {'properties': utils.format_dict(qos_spec._info.pop('specs'))}) + {'properties': + format_columns.DictColumn(qos_spec._info.pop('specs'))}) return zip(*sorted(six.iteritems(qos_spec._info))) diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index ef65d0970a..17ccd3d31e 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -16,8 +16,11 @@ import argparse import copy +import functools import logging +from cliff import columns as cliff_columns +from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -31,6 +34,37 @@ LOG = logging.getLogger(__name__) +class AttachmentsColumn(cliff_columns.FormattableColumn): + """Formattable column for attachments column. + + Unlike the parent FormattableColumn class, the initializer of the + class takes server_cache as the second argument. + osc_lib.utils.get_item_properties instantiate cliff FormattableColumn + object with a single parameter "column value", so you need to pass + a partially initialized class like + ``functools.partial(AttachmentsColumn, server_cache)``. + """ + + def __init__(self, value, server_cache=None): + super(AttachmentsColumn, self).__init__(value) + self._server_cache = server_cache or {} + + def human_readable(self): + """Return a formatted string of a volume's attached instances + + :rtype: a string of formatted instances + """ + + msg = '' + for attachment in self._value: + server = attachment['server_id'] + if server in self._server_cache.keys(): + server = self._server_cache[server].name + device = attachment['device'] + msg += 'Attached to %s on %s ' % (server, device) + return msg + + def _check_size_arg(args): """Check whether --size option is required or not. @@ -212,7 +246,8 @@ def take_action(self, parsed_args): # Remove key links from being displayed volume._info.update( { - 'properties': utils.format_dict(volume._info.pop('metadata')), + 'properties': + format_columns.DictColumn(volume._info.pop('metadata')), 'type': volume._info.pop('volume_type') } ) @@ -331,22 +366,6 @@ def take_action(self, parsed_args): compute_client = self.app.client_manager.compute identity_client = self.app.client_manager.identity - def _format_attach(attachments): - """Return a formatted string of a volume's attached instances - - :param attachments: a volume.attachments field - :rtype: a string of formatted instances - """ - - msg = '' - for attachment in attachments: - server = attachment['server_id'] - if server in server_cache: - server = server_cache[server].name - device = attachment['device'] - msg += 'Attached to %s on %s ' % (server, device) - return msg - if parsed_args.long: columns = [ 'ID', @@ -381,6 +400,8 @@ def _format_attach(attachments): except Exception: # Just forget it if there's any trouble pass + AttachmentsColumnWithCache = functools.partial( + AttachmentsColumn, server_cache=server_cache) project_id = None if parsed_args.project: @@ -417,8 +438,8 @@ def _format_attach(attachments): return (column_headers, (utils.get_item_properties( s, columns, - formatters={'Metadata': utils.format_dict, - 'Attachments': _format_attach}, + formatters={'Metadata': format_columns.DictColumn, + 'Attachments': AttachmentsColumnWithCache}, ) for s in data)) @@ -722,7 +743,8 @@ def take_action(self, parsed_args): # 'volume_type' --> 'type' volume._info.update( { - 'properties': utils.format_dict(volume._info.pop('metadata')), + 'properties': + format_columns.DictColumn(volume._info.pop('metadata')), 'type': volume._info.pop('volume_type'), }, ) diff --git a/openstackclient/volume/v2/volume_backup.py b/openstackclient/volume/v2/volume_backup.py index 1d2b0cde04..4d0d54c1b5 100644 --- a/openstackclient/volume/v2/volume_backup.py +++ b/openstackclient/volume/v2/volume_backup.py @@ -15,8 +15,10 @@ """Volume v2 Backup action implementations""" import copy +import functools import logging +from cliff import columns as cliff_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -29,6 +31,33 @@ LOG = logging.getLogger(__name__) +class VolumeIdColumn(cliff_columns.FormattableColumn): + """Formattable column for volume ID column. + + Unlike the parent FormattableColumn class, the initializer of the + class takes volume_cache as the second argument. + osc_lib.utils.get_item_properties instantiate cliff FormattableColumn + object with a single parameter "column value", so you need to pass + a partially initialized class like + ``functools.partial(VolumeIdColumn, volume_cache)``. + """ + + def __init__(self, value, volume_cache=None): + super(VolumeIdColumn, self).__init__(value) + self._volume_cache = volume_cache or {} + + def human_readable(self): + """Return a volume name if available + + :rtype: either the volume ID or name + """ + volume_id = self._value + volume = volume_id + if volume_id in self._volume_cache.keys(): + volume = self._volume_cache[volume_id].name + return volume + + class CreateVolumeBackup(command.ShowOne): _description = _("Create new volume backup") @@ -189,18 +218,6 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): volume_client = self.app.client_manager.volume - def _format_volume_id(volume_id): - """Return a volume name if available - - :param volume_id: a volume ID - :rtype: either the volume ID or name - """ - - volume = volume_id - if volume_id in volume_cache.keys(): - volume = volume_cache[volume_id].name - return volume - if parsed_args.long: columns = ['ID', 'Name', 'Description', 'Status', 'Size', 'Availability Zone', 'Volume ID', 'Container'] @@ -218,6 +235,8 @@ def _format_volume_id(volume_id): except Exception: # Just forget it if there's any trouble pass + _VolumeIdColumn = functools.partial(VolumeIdColumn, + volume_cache=volume_cache) filter_volume_id = None if parsed_args.volume: @@ -242,7 +261,7 @@ def _format_volume_id(volume_id): return (column_headers, (utils.get_item_properties( s, columns, - formatters={'Volume ID': _format_volume_id}, + formatters={'Volume ID': _VolumeIdColumn}, ) for s in data)) diff --git a/openstackclient/volume/v2/volume_snapshot.py b/openstackclient/volume/v2/volume_snapshot.py index fe9694104a..2b26ae323b 100644 --- a/openstackclient/volume/v2/volume_snapshot.py +++ b/openstackclient/volume/v2/volume_snapshot.py @@ -15,8 +15,11 @@ """Volume v2 snapshot action implementations""" import copy +import functools import logging +from cliff import columns as cliff_columns +from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -30,6 +33,33 @@ LOG = logging.getLogger(__name__) +class VolumeIdColumn(cliff_columns.FormattableColumn): + """Formattable column for volume ID column. + + Unlike the parent FormattableColumn class, the initializer of the + class takes volume_cache as the second argument. + osc_lib.utils.get_item_properties instantiate cliff FormattableColumn + object with a single parameter "column value", so you need to pass + a partially initialized class like + ``functools.partial(VolumeIdColumn, volume_cache)``. + """ + + def __init__(self, value, volume_cache=None): + super(VolumeIdColumn, self).__init__(value) + self._volume_cache = volume_cache or {} + + def human_readable(self): + """Return a volume name if available + + :rtype: either the volume ID or name + """ + volume_id = self._value + volume = volume_id + if volume_id in self._volume_cache.keys(): + volume = self._volume_cache[volume_id].name + return volume + + class CreateVolumeSnapshot(command.ShowOne): _description = _("Create new volume snapshot") @@ -107,7 +137,8 @@ def take_action(self, parsed_args): metadata=parsed_args.property, ) snapshot._info.update( - {'properties': utils.format_dict(snapshot._info.pop('metadata'))} + {'properties': + format_columns.DictColumn(snapshot._info.pop('metadata'))} ) return zip(*sorted(six.iteritems(snapshot._info))) @@ -216,18 +247,6 @@ def take_action(self, parsed_args): volume_client = self.app.client_manager.volume identity_client = self.app.client_manager.identity - def _format_volume_id(volume_id): - """Return a volume name if available - - :param volume_id: a volume ID - :rtype: either the volume ID or name - """ - - volume = volume_id - if volume_id in volume_cache.keys(): - volume = volume_cache[volume_id].name - return volume - if parsed_args.long: columns = ['ID', 'Name', 'Description', 'Status', 'Size', 'Created At', 'Volume ID', 'Metadata'] @@ -246,6 +265,8 @@ def _format_volume_id(volume_id): except Exception: # Just forget it if there's any trouble pass + _VolumeIdColumn = functools.partial(VolumeIdColumn, + volume_cache=volume_cache) volume_id = None if parsed_args.volume: @@ -279,8 +300,8 @@ def _format_volume_id(volume_id): return (column_headers, (utils.get_item_properties( s, columns, - formatters={'Metadata': utils.format_dict, - 'Volume ID': _format_volume_id}, + formatters={'Metadata': format_columns.DictColumn, + 'Volume ID': _VolumeIdColumn}, ) for s in data)) @@ -402,7 +423,8 @@ def take_action(self, parsed_args): snapshot = utils.find_resource( volume_client.volume_snapshots, parsed_args.snapshot) snapshot._info.update( - {'properties': utils.format_dict(snapshot._info.pop('metadata'))} + {'properties': + format_columns.DictColumn(snapshot._info.pop('metadata'))} ) return zip(*sorted(six.iteritems(snapshot._info))) diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index 749d1dd6fd..54b1f49719 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -14,8 +14,11 @@ """Volume v2 Type action implementations""" +import functools import logging +from cliff import columns as cliff_columns +from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -29,6 +32,36 @@ LOG = logging.getLogger(__name__) +class EncryptionInfoColumn(cliff_columns.FormattableColumn): + """Formattable column for encryption info column. + + Unlike the parent FormattableColumn class, the initializer of the + class takes encryption_data as the second argument. + osc_lib.utils.get_item_properties instantiate cliff FormattableColumn + object with a single parameter "column value", so you need to pass + a partially initialized class like + ``functools.partial(EncryptionInfoColumn encryption_data)``. + """ + + def __init__(self, value, encryption_data=None): + super(EncryptionInfoColumn, self).__init__(value) + self._encryption_data = encryption_data or {} + + def _get_encryption_info(self): + type_id = self._value + return self._encryption_data.get(type_id) + + def human_readable(self): + encryption_info = self._get_encryption_info() + if encryption_info: + return utils.format_dict(encryption_info) + else: + return '-' + + def machine_readable(self): + return self._get_encryption_info() + + def _create_encryption_type(volume_client, volume_type, parsed_args): if not parsed_args.encryption_provider: msg = _("'--encryption-provider' should be specified while " @@ -183,7 +216,8 @@ def take_action(self, parsed_args): LOG.error(msg % {'project': parsed_args.project, 'e': e}) if parsed_args.property: result = volume_type.set_keys(parsed_args.property) - volume_type._info.update({'properties': utils.format_dict(result)}) + volume_type._info.update( + {'properties': format_columns.DictColumn(result)}) if (parsed_args.encryption_provider or parsed_args.encryption_cipher or parsed_args.encryption_key_size or @@ -198,7 +232,7 @@ def take_action(self, parsed_args): # add encryption info in result encryption._info.pop("volume_type_id", None) volume_type._info.update( - {'encryption': utils.format_dict(encryption._info)}) + {'encryption': format_columns.DictColumn(encryption._info)}) volume_type._info.pop("os-volume-type-access:is_public", None) return zip(*sorted(six.iteritems(volume_type._info))) @@ -296,12 +330,7 @@ def take_action(self, parsed_args): data = volume_client.volume_types.list( is_public=is_public) - def _format_encryption_info(type_id, encryption_data=None): - encryption_data = encryption - encryption_info = '-' - if type_id in encryption_data.keys(): - encryption_info = encryption_data[type_id] - return encryption_info + formatters = {'Extra Specs': format_columns.DictColumn} if parsed_args.encryption_type: encryption = {} @@ -318,18 +347,21 @@ def _format_encryption_info(type_id, encryption_data=None): for key in del_key: d._info.pop(key, None) # save the encryption information with their volume type ID - encryption[volume_type_id] = utils.format_dict(d._info) + encryption[volume_type_id] = d._info # We need to get volume type ID, then show encryption # information according to the ID, so use "id" to keep # difference to the real "ID" column. columns += ['id'] column_headers += ['Encryption'] + _EncryptionInfoColumn = functools.partial( + EncryptionInfoColumn, encryption_data=encryption) + formatters['id'] = _EncryptionInfoColumn + return (column_headers, (utils.get_item_properties( s, columns, - formatters={'Extra Specs': utils.format_dict, - 'id': _format_encryption_info}, + formatters=formatters, ) for s in data)) @@ -490,7 +522,7 @@ def take_action(self, parsed_args): volume_client = self.app.client_manager.volume volume_type = utils.find_resource( volume_client.volume_types, parsed_args.volume_type) - properties = utils.format_dict( + properties = format_columns.DictColumn( volume_type._info.pop('extra_specs', {})) volume_type._info.update({'properties': properties}) access_project_ids = None @@ -502,7 +534,7 @@ def take_action(self, parsed_args): for item in volume_type_access] # TODO(Rui Chen): This format list case can be removed after # patch https://review.opendev.org/#/c/330223/ merged. - access_project_ids = utils.format_list(project_ids) + access_project_ids = format_columns.ListColumn(project_ids) except Exception as e: msg = _('Failed to get access project list for volume type ' '%(type)s: %(e)s') @@ -515,7 +547,8 @@ def take_action(self, parsed_args): volume_type.id) encryption._info.pop("volume_type_id", None) volume_type._info.update( - {'encryption': utils.format_dict(encryption._info)}) + {'encryption': + format_columns.DictColumn(encryption._info)}) except Exception as e: LOG.error(_("Failed to display the encryption information " "of this volume type: %s"), e) From bfc34e11b3437506508b3e120accc0e212268ac6 Mon Sep 17 00:00:00 2001 From: melanie witt Date: Fri, 28 Jun 2019 18:17:10 +0000 Subject: [PATCH 2077/3095] Fix BFV server list handling with --name-lookup-one-by-one When the --name-lookup-one-by-one option passed to the 'server list' command, the image and flavor names will be looked up for each server being listed instead of fetching all image/flavor names. The current code assumes all servers have an image attribute, but servers booted from volumes have no image, so the following error is raised when listing BFV servers with --name-lookup-one-by-one: AttributeError: ('unicode'|'str') object has no attribute 'get' The error occurs when the code attempts server.image.get('id'). This fixes the --name-lookup-one-by-one code not to assume an image for a server. The unit tests for 'server list' have also been robustified to feature one BFV server to enhance our test coverage. Story: #2006063 Task: #34777 Change-Id: I312c971346c7ded93f6fcaa515098554b8580295 --- openstackclient/compute/v2/server.py | 5 ++- .../tests/unit/compute/v2/test_server.py | 34 ++++++++++++++----- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 2792e315f6..a4216e65dc 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1350,9 +1350,12 @@ def take_action(self, parsed_args): # Create a dict that maps image_id to image object. # Needed so that we can display the "Image Name" column. # "Image Name" is not crucial, so we swallow any exceptions. + # The 'image' attribute can be an empty string if the server was + # booted from a volume. if parsed_args.name_lookup_one_by_one or image_id: for i_id in set(filter(lambda x: x is not None, - (s.image.get('id') for s in data))): + (s.image.get('id') for s in data + if s.image))): try: images[i_id] = image_client.images.get(i_id) except Exception: diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 8ea59a3850..713bf07aa7 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -14,6 +14,7 @@ # import argparse import collections +import copy import getpass import mock @@ -65,9 +66,22 @@ def setUp(self): self.methods = {} def setup_servers_mock(self, count): - servers = compute_fakes.FakeServer.create_servers(attrs=self.attrs, - methods=self.methods, - count=count) + # If we are creating more than one server, make one of them + # boot-from-volume + include_bfv = count > 1 + servers = compute_fakes.FakeServer.create_servers( + attrs=self.attrs, + methods=self.methods, + count=count - 1 if include_bfv else count + ) + if include_bfv: + attrs = copy.deepcopy(self.attrs) + attrs['image'] = '' + bfv_server = compute_fakes.FakeServer.create_one_server( + attrs=attrs, + methods=self.methods + ) + servers.append(bfv_server) # This is the return value for utils.find_resource() self.servers_mock.get = compute_fakes.FakeServer.get_servers(servers, @@ -2129,7 +2143,8 @@ def setUp(self): Image = collections.namedtuple('Image', 'id name') self.images_mock.list.return_value = [ Image(id=s.image['id'], name=self.image.name) - for s in self.servers + # Image will be an empty string if boot-from-volume + for s in self.servers if s.image ] Flavor = collections.namedtuple('Flavor', 'id name') @@ -2144,7 +2159,8 @@ def setUp(self): s.name, s.status, server._format_servers_list_networks(s.networks), - self.image.name, + # Image will be an empty string if boot-from-volume + self.image.name if s.image else s.image, self.flavor.name, )) self.data_long.append(( @@ -2156,8 +2172,9 @@ def setUp(self): getattr(s, 'OS-EXT-STS:power_state') ), server._format_servers_list_networks(s.networks), - self.image.name, - s.image['id'], + # Image will be an empty string if boot-from-volume + self.image.name if s.image else s.image, + s.image['id'] if s.image else s.image, self.flavor.name, s.flavor['id'], getattr(s, 'OS-EXT-AZ:availability_zone'), @@ -2169,7 +2186,8 @@ def setUp(self): s.name, s.status, server._format_servers_list_networks(s.networks), - s.image['id'], + # Image will be an empty string if boot-from-volume + s.image['id'] if s.image else s.image, s.flavor['id'] )) From 5986f473064cd43bf09909b5c3f616bf44329724 Mon Sep 17 00:00:00 2001 From: pengyuesheng Date: Mon, 1 Jul 2019 15:06:43 +0800 Subject: [PATCH 2078/3095] Add Python 3 Train unit tests See the Train python3-updates goal document for details: https://governance.openstack.org/tc/goals/train/python3-updates.html Change-Id: I897645a4cb9f03ca464daa14f0895572212b81d9 --- .zuul.yaml | 3 +-- setup.cfg | 1 + tox.ini | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.zuul.yaml b/.zuul.yaml index 95a6890c71..574734659e 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -193,8 +193,7 @@ - openstack-cover-jobs - openstack-lower-constraints-jobs - openstack-python-jobs - - openstack-python36-jobs - - openstack-python37-jobs + - openstack-python3-train-jobs - publish-openstack-docs-pti - check-requirements - release-notes-jobs-python3 diff --git a/setup.cfg b/setup.cfg index d3d79983b3..c48088087f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -17,6 +17,7 @@ classifier = Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 [files] packages = diff --git a/tox.ini b/tox.ini index 8145179ece..00c179ddb9 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] minversion = 2.3 -envlist = py36,py27,pep8 +envlist = py37,py36,py27,pep8 skipdist = True [testenv] From d16d98b27b87ea5d6186c241a19b1c60e00db3bb Mon Sep 17 00:00:00 2001 From: pengyuesheng Date: Mon, 1 Jul 2019 15:13:08 +0800 Subject: [PATCH 2079/3095] Update the constraints url For more detail, see http://lists.openstack.org/pipermail/openstack-discuss/2019-May/006478.html Change-Id: Ie0a41fa97696bcd8b5fd2e670efdf9379ff1080e --- tox.ini | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tox.ini b/tox.ini index 8145179ece..e50744367f 100644 --- a/tox.ini +++ b/tox.ini @@ -11,7 +11,7 @@ setenv = VIRTUAL_ENV={envdir} OS_STDERR_CAPTURE=1 OS_TEST_TIMEOUT=60 deps = - -c{env:UPPER_CONSTRAINTS_FILE:https://opendev.org/openstack/requirements/raw/upper-constraints.txt} + -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/test-requirements.txt -r{toxinidir}/requirements.txt commands = stestr run {posargs} @@ -88,7 +88,7 @@ commands = [testenv:venv] basepython = python3 deps = - -c{env:UPPER_CONSTRAINTS_FILE:https://opendev.org/openstack/requirements/raw/upper-constraints.txt} + -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt -r{toxinidir}/doc/requirements.txt commands = {posargs} @@ -112,7 +112,7 @@ commands = [testenv:docs] basepython = python3 deps = - -c{env:UPPER_CONSTRAINTS_FILE:https://opendev.org/openstack/requirements/raw/upper-constraints.txt} + -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt -r{toxinidir}/doc/requirements.txt commands = @@ -122,7 +122,7 @@ commands = [testenv:releasenotes] basepython = python3 deps = - -c{env:UPPER_CONSTRAINTS_FILE:https://opendev.org/openstack/requirements/raw/upper-constraints.txt} + -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/doc/requirements.txt commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html From 879f8207786caf1cd9e1465e32e322abd111508c Mon Sep 17 00:00:00 2001 From: Matt Riedemann Date: Mon, 1 Jul 2019 15:06:56 -0400 Subject: [PATCH 2080/3095] docs: clarify compute service --service option The compute service commands emit a "Binary" in the output but the --service filter option isn't as clear that it's the binary (for set it is but not list) nor do the docs give an example of a binary (typically nova-compute but could be others like nova-conductor, nova-scheduler, etc). This simply mentions that the --service option is the binary for "compute service list" and gives an example value for the option in both list and set help. Change-Id: If87fc37352c3a251cc89041723adbe04dedf4f8a --- doc/source/cli/command-objects/compute-service.rst | 5 +++-- openstackclient/compute/v2/service.py | 6 ++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/doc/source/cli/command-objects/compute-service.rst b/doc/source/cli/command-objects/compute-service.rst index ba624ea0a4..51d5c86433 100644 --- a/doc/source/cli/command-objects/compute-service.rst +++ b/doc/source/cli/command-objects/compute-service.rst @@ -39,7 +39,8 @@ List compute services .. option:: --service - List only specified service (name only) + List only specified service binaries (name only). For example, + ``nova-compute``, ``nova-conductor``, etc. .. option:: --long @@ -86,4 +87,4 @@ Set compute service properties .. describe:: - Name of service (Binary name) + Name of service (Binary name), for example ``nova-compute`` diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index 18e6d9d95e..98347c9f86 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.py @@ -72,7 +72,8 @@ def get_parser(self, prog_name): parser.add_argument( "--service", metavar="", - help=_("List only specified service (name only)") + help=_("List only specified service binaries (name only). For " + "example, ``nova-compute``, ``nova-conductor``, etc.") ) parser.add_argument( "--long", @@ -126,7 +127,8 @@ def get_parser(self, prog_name): parser.add_argument( "service", metavar="", - help=_("Name of service (Binary name)") + help=_("Name of service (Binary name), for example " + "``nova-compute``") ) enabled_group = parser.add_mutually_exclusive_group() enabled_group.add_argument( From b41d7518c381a453a66f3c09d38752f85b57b7e0 Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Fri, 5 Jul 2019 11:54:01 -0400 Subject: [PATCH 2081/3095] Add Python 3 Train unit tests This is a mechanically generated patch to ensure unit testing is in place for all of the Tested Runtimes for Train. See the Train python3-updates goal document for details: https://governance.openstack.org/tc/goals/train/python3-updates.html Change-Id: I7d0a996b33d4d1eec436f92fbd390968cd37630c Story: #2005924 Task: #34232 --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 629b74d00d..eaf66bc493 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] minversion = 2.3 -envlist = py37,py36,py27,pep8 +envlist = py27,py37,pep8 skipdist = True [testenv] From 969e6abd20570ae64b3d1fd049da1521fa148b5c Mon Sep 17 00:00:00 2001 From: Brian Haley Date: Thu, 30 Nov 2017 17:53:29 -0500 Subject: [PATCH 2082/3095] Support IPv6 addresses better When adding a security group rule, if no IP address is given we will use '0.0.0.0/0', but if the ethertype is IPv6 we will leave it as None. Change this to be '::/0' to match what we do for IPv4 - use the "any" address. The neutron server treats them both the same when checking for duplicates. Because there are most likely entries in the DB using None for the IP, print them as '0.0.0.0/0' or '::/0' so it is more obvious what address they are actually referring to. Also change to display the Ethertype column by default instead of with --long, since easily knowing IPv4 or IPv6 is useful. Change-Id: Ic396fc23caa66b6b0034c5d30b27c6ed499de5a6 Closes-bug: #1735575 --- .../command-objects/security-group-rule.rst | 13 +++-- .../network/v2/security_group_rule.py | 49 +++++++++++++++---- .../v2/test_security_group_rule_compute.py | 4 ++ .../v2/test_security_group_rule_network.py | 13 +++-- ...security-group-rules-95272847349982e5.yaml | 15 ++++++ 5 files changed, 79 insertions(+), 15 deletions(-) create mode 100644 releasenotes/notes/better-ipv6-address-suppport-security-group-rules-95272847349982e5.yaml diff --git a/doc/source/cli/command-objects/security-group-rule.rst b/doc/source/cli/command-objects/security-group-rule.rst index 5809e00278..5a2d8342b3 100644 --- a/doc/source/cli/command-objects/security-group-rule.rst +++ b/doc/source/cli/command-objects/security-group-rule.rst @@ -27,8 +27,9 @@ Create a new security group rule .. option:: --remote-ip - Remote IP address block - (may use CIDR notation; default for IPv4 rule: 0.0.0.0/0) + Remote IP address block (may use CIDR notation; + default for IPv4 rule: 0.0.0.0/0, + default for IPv6 rule: ::/0) .. option:: --remote-group @@ -134,6 +135,7 @@ List security group rules openstack security group rule list [--all-projects] [--protocol ] + [--ethertype ] [--ingress | --egress] [--long] [] @@ -151,7 +153,6 @@ List security group rules *Compute version 2 does not have additional fields to display.* - .. option:: --protocol List rules by the IP protocol (ah, dhcp, egp, esp, gre, icmp, igmp, @@ -161,6 +162,12 @@ List security group rules *Network version 2* +.. option:: --ethertype + + List rules by the Ethertype (IPv4 or IPv6) + + *Network version 2* + .. option:: --ingress List rules applied to incoming network traffic diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index 637fba1d95..dbea747300 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -62,6 +62,17 @@ def _format_network_port_range(rule): return port_range +def _format_remote_ip_prefix(rule): + remote_ip_prefix = rule['remote_ip_prefix'] + if remote_ip_prefix is None: + ethertype = rule['ether_type'] + if ethertype == 'IPv4': + remote_ip_prefix = '0.0.0.0/0' + elif ethertype == 'IPv6': + remote_ip_prefix = '::/0' + return remote_ip_prefix + + def _get_columns(item): column_map = { 'tenant_id': 'project_id', @@ -108,7 +119,8 @@ def update_parser_common(self, parser): "--remote-ip", metavar="", help=_("Remote IP address block (may use CIDR notation; " - "default for IPv4 rule: 0.0.0.0/0)"), + "default for IPv4 rule: 0.0.0.0/0, " + "default for IPv6 rule: ::/0)"), ) remote_group.add_argument( "--remote-group", @@ -230,6 +242,14 @@ def _get_protocol(self, parsed_args, default_protocol='any'): protocol = None return protocol + def _get_ethertype(self, parsed_args, protocol): + ethertype = 'IPv4' + if parsed_args.ethertype is not None: + ethertype = parsed_args.ethertype + elif self._is_ipv6_protocol(protocol): + ethertype = 'IPv6' + return ethertype + def _is_ipv6_protocol(self, protocol): # NOTE(rtheis): Neutron has deprecated protocol icmpv6. # However, while the OSC CLI doesn't document the protocol, @@ -264,12 +284,8 @@ def take_action_network(self, client, parsed_args): # NOTE(rtheis): Use ethertype specified else default based # on IP protocol. - if parsed_args.ethertype: - attrs['ethertype'] = parsed_args.ethertype - elif self._is_ipv6_protocol(attrs['protocol']): - attrs['ethertype'] = 'IPv6' - else: - attrs['ethertype'] = 'IPv4' + attrs['ethertype'] = self._get_ethertype(parsed_args, + attrs['protocol']) # NOTE(rtheis): Validate the port range and ICMP type and code. # It would be ideal if argparse could do this. @@ -306,6 +322,8 @@ def take_action_network(self, client, parsed_args): attrs['remote_ip_prefix'] = parsed_args.remote_ip elif attrs['ethertype'] == 'IPv4': attrs['remote_ip_prefix'] = '0.0.0.0/0' + elif attrs['ethertype'] == 'IPv6': + attrs['remote_ip_prefix'] = '::/0' attrs['security_group_id'] = security_group_id if parsed_args.project is not None: identity_client = self.app.client_manager.identity @@ -387,6 +405,7 @@ def _format_network_security_group_rule(self, rule): """ rule = rule.to_dict() rule['port_range'] = _format_network_port_range(rule) + rule['remote_ip_prefix'] = _format_remote_ip_prefix(rule) return rule def update_parser_common(self, parser): @@ -418,6 +437,12 @@ def update_parser_network(self, parser): "udp, udplite, vrrp and integer representations [0-255] " "or any; default: any (all protocols))") ) + parser.add_argument( + '--ethertype', + metavar='', + type=_convert_to_lowercase, + help=_("List rules by the Ethertype (IPv4 or IPv6)") + ) direction_group = parser.add_mutually_exclusive_group() direction_group.add_argument( '--ingress', @@ -458,11 +483,12 @@ def _get_column_headers(self, parsed_args): column_headers = ( 'ID', 'IP Protocol', + 'Ethertype', 'IP Range', 'Port Range', ) if parsed_args.long: - column_headers = column_headers + ('Direction', 'Ethertype',) + column_headers = column_headers + ('Direction',) column_headers = column_headers + ('Remote Security Group',) if parsed_args.group is None: column_headers = column_headers + ('Security Group',) @@ -473,11 +499,12 @@ def take_action_network(self, client, parsed_args): columns = ( 'id', 'protocol', + 'ether_type', 'remote_ip_prefix', 'port_range', ) if parsed_args.long: - columns = columns + ('direction', 'ether_type',) + columns = columns + ('direction',) columns = columns + ('remote_group_id',) # Get the security group rules using the requested query. @@ -516,6 +543,7 @@ def take_action_compute(self, client, parsed_args): columns = ( "ID", "IP Protocol", + "Ethertype", "IP Range", "Port Range", "Remote Security Group", @@ -564,6 +592,9 @@ def update_parser_common(self, parser): def take_action_network(self, client, parsed_args): obj = client.find_security_group_rule(parsed_args.rule, ignore_missing=False) + # necessary for old rules that have None in this field + if not obj['remote_ip_prefix']: + obj['remote_ip_prefix'] = _format_remote_ip_prefix(obj) display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns) return (display_columns, data) diff --git a/openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py b/openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py index 6814c197b3..cf5261b284 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py @@ -337,6 +337,7 @@ class TestListSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): _security_group_rule_tcp = \ compute_fakes.FakeSecurityGroupRule.create_one_security_group_rule({ 'ip_protocol': 'tcp', + 'ethertype': 'IPv4', 'from_port': 80, 'to_port': 80, 'group': {'name': _security_group['name']}, @@ -344,6 +345,7 @@ class TestListSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): _security_group_rule_icmp = \ compute_fakes.FakeSecurityGroupRule.create_one_security_group_rule({ 'ip_protocol': 'icmp', + 'ethertype': 'IPv4', 'from_port': -1, 'to_port': -1, 'ip_range': {'cidr': '10.0.2.0/24'}, @@ -357,6 +359,7 @@ class TestListSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): expected_columns_with_group = ( 'ID', 'IP Protocol', + 'Ethertype', 'IP Range', 'Port Range', 'Remote Security Group', @@ -373,6 +376,7 @@ class TestListSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): expected_rule_with_group = ( rule['id'], rule['ip_protocol'], + rule['ethertype'], rule['ip_range'], rule['port_range'], rule['remote_security_group'], diff --git a/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py b/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py index eb0cf310c0..49c3d5dbec 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py @@ -388,7 +388,7 @@ def test_create_network_options(self): 'port_range_min': 443, 'protocol': '6', 'remote_group_id': None, - 'remote_ip_prefix': None, + 'remote_ip_prefix': '::/0', }) arglist = [ '--dst-port', str(self._security_group_rule.port_range_min), @@ -419,6 +419,7 @@ def test_create_network_options(self): 'port_range_max': self._security_group_rule.port_range_max, 'port_range_min': self._security_group_rule.port_range_min, 'protocol': self._security_group_rule.protocol, + 'remote_ip_prefix': self._security_group_rule.remote_ip_prefix, 'security_group_id': self._security_group.id, 'tenant_id': self.project.id, }) @@ -664,6 +665,7 @@ def test_create_ipv6_icmp_type_code(self): 'port_range_min': 139, 'port_range_max': 2, 'protocol': 'ipv6-icmp', + 'remote_ip_prefix': '::/0', }) arglist = [ '--icmp-type', str(self._security_group_rule.port_range_min), @@ -688,6 +690,7 @@ def test_create_ipv6_icmp_type_code(self): 'port_range_min': self._security_group_rule.port_range_min, 'port_range_max': self._security_group_rule.port_range_max, 'protocol': self._security_group_rule.protocol, + 'remote_ip_prefix': self._security_group_rule.remote_ip_prefix, 'security_group_id': self._security_group.id, }) self.assertEqual(self.expected_columns, columns) @@ -698,6 +701,7 @@ def test_create_icmpv6_type(self): 'ether_type': 'IPv6', 'port_range_min': 139, 'protocol': 'icmpv6', + 'remote_ip_prefix': '::/0', }) arglist = [ '--icmp-type', str(self._security_group_rule.port_range_min), @@ -720,6 +724,7 @@ def test_create_icmpv6_type(self): 'ethertype': self._security_group_rule.ether_type, 'port_range_min': self._security_group_rule.port_range_min, 'protocol': self._security_group_rule.protocol, + 'remote_ip_prefix': self._security_group_rule.remote_ip_prefix, 'security_group_id': self._security_group.id, }) self.assertEqual(self.expected_columns, columns) @@ -868,15 +873,16 @@ class TestListSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): expected_columns_with_group_and_long = ( 'ID', 'IP Protocol', + 'Ethertype', 'IP Range', 'Port Range', 'Direction', - 'Ethertype', 'Remote Security Group', ) expected_columns_no_group = ( 'ID', 'IP Protocol', + 'Ethertype', 'IP Range', 'Port Range', 'Remote Security Group', @@ -889,16 +895,17 @@ class TestListSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): expected_data_with_group_and_long.append(( _security_group_rule.id, _security_group_rule.protocol, + _security_group_rule.ether_type, _security_group_rule.remote_ip_prefix, security_group_rule._format_network_port_range( _security_group_rule), _security_group_rule.direction, - _security_group_rule.ether_type, _security_group_rule.remote_group_id, )) expected_data_no_group.append(( _security_group_rule.id, _security_group_rule.protocol, + _security_group_rule.ether_type, _security_group_rule.remote_ip_prefix, security_group_rule._format_network_port_range( _security_group_rule), diff --git a/releasenotes/notes/better-ipv6-address-suppport-security-group-rules-95272847349982e5.yaml b/releasenotes/notes/better-ipv6-address-suppport-security-group-rules-95272847349982e5.yaml new file mode 100644 index 0000000000..6cac67cf88 --- /dev/null +++ b/releasenotes/notes/better-ipv6-address-suppport-security-group-rules-95272847349982e5.yaml @@ -0,0 +1,15 @@ +--- +features: + - | + Security group rules can now be filtered by Ethertype in + ``security group rule list`` using ``--ethertype`` with either + ``ipv4`` or ``ipv6`` as an argument. +upgrade: + - | + Security group rule listings now have the ``Ethertype`` field displayed + by default to more easily differentiate between IPv4 and IPv6 rules. + In addition, the ``IP Range`` field of a security group will be + changed to ``0.0.0.0/0`` for IPv4 and ``::/0`` for IPv6 if no + value is returned for the address, based on the Ethertype field of + the rule. For further information see + [Bug `1735575 `_] From 340f25fa14d42205a4134ce4cba47792764b8542 Mon Sep 17 00:00:00 2001 From: "zhu.boxiang" Date: Mon, 8 Jul 2019 14:48:23 +0800 Subject: [PATCH 2083/3095] Add host and hypervisor_hostname to create servers Adds the --host and --hypervisor-hostname options to ``openstack server create`` CLI. Depends-On: https://review.opendev.org/670558 Change-Id: If188c3d96fa506dbe62ef256418f2f9bca1520c2 Blueprint: add-host-and-hypervisor-hostname-flag-to-create-server --- lower-constraints.txt | 2 +- openstackclient/compute/v2/server.py | 29 +++ .../tests/unit/compute/v2/test_server.py | 231 ++++++++++++++++++ ...lag-to-create-server-cb8b39a9f9311d42.yaml | 6 + requirements.txt | 2 +- 5 files changed, 268 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/add-host-and-hypervisor-hostname-flag-to-create-server-cb8b39a9f9311d42.yaml diff --git a/lower-constraints.txt b/lower-constraints.txt index f387827948..c3dc477eaf 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -100,7 +100,7 @@ python-mimeparse==1.6.0 python-mistralclient==3.1.0 python-muranoclient==0.8.2 python-neutronclient==6.7.0 -python-novaclient==14.1.0 +python-novaclient==14.2.0 python-octaviaclient==1.3.0 python-rsdclient==0.1.0 python-saharaclient==1.4.0 diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 2792e315f6..240c7cb2a0 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -553,6 +553,20 @@ def get_parser(self, prog_name): metavar='', help=_('Select an availability zone for the server'), ) + parser.add_argument( + '--host', + metavar='', + help=_('Requested host to create servers. Admin only ' + 'by default. (supported by --os-compute-api-version 2.74 ' + 'or above)'), + ) + parser.add_argument( + '--hypervisor-hostname', + metavar='', + help=_('Requested hypervisor hostname to create servers. Admin ' + 'only by default. (supported by --os-compute-api-version ' + '2.74 or above)'), + ) parser.add_argument( '--block-device-mapping', metavar='', @@ -927,6 +941,21 @@ def _match_image(image_api, wanted_properties): if parsed_args.description: boot_kwargs['description'] = parsed_args.description + if parsed_args.host: + if compute_client.api_version < api_versions.APIVersion("2.74"): + msg = _("Specifying --host is not supported for " + "--os-compute-api-version less than 2.74") + raise exceptions.CommandError(msg) + boot_kwargs['host'] = parsed_args.host + + if parsed_args.hypervisor_hostname: + if compute_client.api_version < api_versions.APIVersion("2.74"): + msg = _("Specifying --hypervisor-hostname is not supported " + "for --os-compute-api-version less than 2.74") + raise exceptions.CommandError(msg) + boot_kwargs['hypervisor_hostname'] = ( + parsed_args.hypervisor_hostname) + LOG.debug('boot_args: %s', boot_args) LOG.debug('boot_kwargs: %s', boot_kwargs) diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 8ea59a3850..dc7ff1aa3f 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -1926,6 +1926,237 @@ def test_server_create_with_description_api_older(self): self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) + def test_server_create_with_host_v274(self): + + # Explicit host is supported for nova api version 2.74 or above + self.app.client_manager.compute.api_version = 2.74 + + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--host', 'host1', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('host', 'host1'), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + with mock.patch.object(api_versions, + 'APIVersion', + return_value=2.74): + # 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) + + # Set expected values + kwargs = dict( + meta=None, + files={}, + reservation_id=None, + min_count=1, + max_count=1, + security_groups=[], + userdata=None, + key_name=None, + availability_zone=None, + block_device_mapping_v2=[], + nics='auto', + scheduler_hints={}, + config_drive=None, + host='host1', + ) + # ServerManager.create(name, image, flavor, **kwargs) + self.servers_mock.create.assert_called_with( + self.new_server.name, + self.image, + self.flavor, + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) + self.assertFalse(self.images_mock.called) + self.assertFalse(self.flavors_mock.called) + + def test_server_create_with_host_pre_v274(self): + + # Host is not supported for nova api version below 2.74 + self.app.client_manager.compute.api_version = 2.73 + + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--host', 'host1', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('host', 'host1'), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + with mock.patch.object(api_versions, + 'APIVersion', + return_value=2.74): + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + + def test_server_create_with_hypervisor_hostname_v274(self): + + # Explicit hypervisor_hostname is supported for nova api version + # 2.74 or above + self.app.client_manager.compute.api_version = 2.74 + + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--hypervisor-hostname', 'node1', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('hypervisor_hostname', 'node1'), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + with mock.patch.object(api_versions, + 'APIVersion', + return_value=2.74): + # 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) + + # Set expected values + kwargs = dict( + meta=None, + files={}, + reservation_id=None, + min_count=1, + max_count=1, + security_groups=[], + userdata=None, + key_name=None, + availability_zone=None, + block_device_mapping_v2=[], + nics='auto', + scheduler_hints={}, + config_drive=None, + hypervisor_hostname='node1', + ) + # ServerManager.create(name, image, flavor, **kwargs) + self.servers_mock.create.assert_called_with( + self.new_server.name, + self.image, + self.flavor, + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) + self.assertFalse(self.images_mock.called) + self.assertFalse(self.flavors_mock.called) + + def test_server_create_with_hypervisor_hostname_pre_v274(self): + + # Hypervisor_hostname is not supported for nova api version below 2.74 + self.app.client_manager.compute.api_version = 2.73 + + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--hypervisor-hostname', 'node1', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('hypervisor_hostname', 'node1'), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + with mock.patch.object(api_versions, + 'APIVersion', + return_value=2.74): + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + + def test_server_create_with_host_and_hypervisor_hostname_v274(self): + + # Explicit host and hypervisor_hostname is supported for nova api + # version 2.74 or above + self.app.client_manager.compute.api_version = 2.74 + + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--host', 'host1', + '--hypervisor-hostname', 'node1', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('host', 'host1'), + ('hypervisor_hostname', 'node1'), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + with mock.patch.object(api_versions, + 'APIVersion', + return_value=2.74): + # 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) + + # Set expected values + kwargs = dict( + meta=None, + files={}, + reservation_id=None, + min_count=1, + max_count=1, + security_groups=[], + userdata=None, + key_name=None, + availability_zone=None, + block_device_mapping_v2=[], + nics='auto', + scheduler_hints={}, + config_drive=None, + host='host1', + hypervisor_hostname='node1', + ) + # ServerManager.create(name, image, flavor, **kwargs) + self.servers_mock.create.assert_called_with( + self.new_server.name, + self.image, + self.flavor, + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) + self.assertFalse(self.images_mock.called) + self.assertFalse(self.flavors_mock.called) + class TestServerDelete(TestServer): diff --git a/releasenotes/notes/add-host-and-hypervisor-hostname-flag-to-create-server-cb8b39a9f9311d42.yaml b/releasenotes/notes/add-host-and-hypervisor-hostname-flag-to-create-server-cb8b39a9f9311d42.yaml new file mode 100644 index 0000000000..fbd4c70402 --- /dev/null +++ b/releasenotes/notes/add-host-and-hypervisor-hostname-flag-to-create-server-cb8b39a9f9311d42.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--host`` and ``--hypervisor-hostname`` options to + ``server create`` command. + [Blueprint `add-host-and-hypervisor-hostname-flag-to-create-server `_] \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 3db6caef0b..cf67906646 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,5 +13,5 @@ oslo.i18n>=3.15.3 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 python-glanceclient>=2.8.0 # Apache-2.0 python-keystoneclient>=3.17.0 # Apache-2.0 -python-novaclient>=14.1.0 # Apache-2.0 +python-novaclient>=14.2.0 # Apache-2.0 python-cinderclient>=3.3.0 # Apache-2.0 From c609b98c408d521cdd34884c912aeb8f64257753 Mon Sep 17 00:00:00 2001 From: Andreas Jaeger Date: Mon, 22 Jul 2019 20:55:43 +0200 Subject: [PATCH 2084/3095] Update api-ref location The api documentation is now published on docs.openstack.org instead of developer.openstack.org. Update all links that are changed to the new location. Note that redirects will be set up as well but let's point now to the new location. For details, see: http://lists.openstack.org/pipermail/openstack-discuss/2019-July/007828.html Change-Id: I1572a21632740b4d9a233a6a31c49e3bac5394ef --- openstackclient/api/compute_v2.py | 38 +++++++++++++++---------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/openstackclient/api/compute_v2.py b/openstackclient/api/compute_v2.py index 7dc4e4468a..e30177a240 100644 --- a/openstackclient/api/compute_v2.py +++ b/openstackclient/api/compute_v2.py @@ -145,7 +145,7 @@ def floating_ip_create( ): """Create a new floating ip - https://developer.openstack.org/api-ref/compute/#create-allocate-floating-ip-address + https://docs.openstack.org/api-ref/compute/#create-allocate-floating-ip-address :param pool: Name of floating IP pool """ @@ -170,7 +170,7 @@ def floating_ip_delete( ): """Delete a floating IP - https://developer.openstack.org/api-ref/compute/#delete-deallocate-floating-ip-address + https://docs.openstack.org/api-ref/compute/#delete-deallocate-floating-ip-address :param string floating_ip_id: Floating IP ID @@ -189,7 +189,7 @@ def floating_ip_find( ): """Return a security group given name or ID - https://developer.openstack.org/api-ref/compute/#list-floating-ip-addresses + https://docs.openstack.org/api-ref/compute/#list-floating-ip-addresses :param string floating_ip: Floating IP address @@ -209,7 +209,7 @@ def floating_ip_list( ): """Get floating IPs - https://developer.openstack.org/api-ref/compute/#show-floating-ip-address-details + https://docs.openstack.org/api-ref/compute/#show-floating-ip-address-details :returns: list of floating IPs @@ -258,7 +258,7 @@ def floating_ip_pool_list( ): """Get floating IP pools - https://developer.openstack.org/api-ref/compute/?expanded=#list-floating-ip-pools + https://docs.openstack.org/api-ref/compute/?expanded=#list-floating-ip-pools :returns: list of floating IP pools @@ -276,7 +276,7 @@ def host_list( ): """Lists hypervisor Hosts - https://developer.openstack.org/api-ref/compute/#list-hosts + https://docs.openstack.org/api-ref/compute/#list-hosts Valid for Compute 2.0 - 2.42 :param string zone: @@ -299,7 +299,7 @@ def host_set( ): """Modify host properties - https://developer.openstack.org/api-ref/compute/#update-host-status + https://docs.openstack.org/api-ref/compute/#update-host-status Valid for Compute 2.0 - 2.42 status @@ -329,7 +329,7 @@ def host_show( ): """Show host - https://developer.openstack.org/api-ref/compute/#show-host-details + https://docs.openstack.org/api-ref/compute/#show-host-details Valid for Compute 2.0 - 2.42 """ @@ -356,7 +356,7 @@ def network_create( ): """Create a new network - https://developer.openstack.org/api-ref/compute/#create-network + https://docs.openstack.org/api-ref/compute/#create-network :param string name: Network label (required) @@ -387,7 +387,7 @@ def network_delete( ): """Delete a network - https://developer.openstack.org/api-ref/compute/#delete-network + https://docs.openstack.org/api-ref/compute/#delete-network :param string network: Network name or ID @@ -411,7 +411,7 @@ def network_find( ): """Return a network given name or ID - https://developer.openstack.org/api-ref/compute/#show-network-details + https://docs.openstack.org/api-ref/compute/#show-network-details :param string network: Network name or ID @@ -431,7 +431,7 @@ def network_list( ): """Get networks - https://developer.openstack.org/api-ref/compute/#list-networks + https://docs.openstack.org/api-ref/compute/#list-networks :returns: list of networks @@ -450,7 +450,7 @@ def security_group_create( ): """Create a new security group - https://developer.openstack.org/api-ref/compute/#create-security-group + https://docs.openstack.org/api-ref/compute/#create-security-group :param string name: Security group name @@ -476,7 +476,7 @@ def security_group_delete( ): """Delete a security group - https://developer.openstack.org/api-ref/compute/#delete-security-group + https://docs.openstack.org/api-ref/compute/#delete-security-group :param string security_group: Security group name or ID @@ -500,7 +500,7 @@ def security_group_find( ): """Return a security group given name or ID - https://developer.openstack.org/api-ref/compute/#show-security-group-details + https://docs.openstack.org/api-ref/compute/#show-security-group-details :param string security_group: Security group name or ID @@ -523,7 +523,7 @@ def security_group_list( ): """Get security groups - https://developer.openstack.org/api-ref/compute/#list-security-groups + https://docs.openstack.org/api-ref/compute/#list-security-groups :param integer limit: query return count limit @@ -556,7 +556,7 @@ def security_group_set( ): """Update a security group - https://developer.openstack.org/api-ref/compute/#update-security-group + https://docs.openstack.org/api-ref/compute/#update-security-group :param string security_group: Security group name or ID @@ -600,7 +600,7 @@ def security_group_rule_create( ): """Create a new security group rule - https://developer.openstack.org/api-ref/compute/#create-security-group-rule + https://docs.openstack.org/api-ref/compute/#create-security-group-rule :param string security_group_id: Security group ID @@ -643,7 +643,7 @@ def security_group_rule_delete( ): """Delete a security group rule - https://developer.openstack.org/api-ref/compute/#delete-security-group-rule + https://docs.openstack.org/api-ref/compute/#delete-security-group-rule :param string security_group_rule_id: Security group rule ID From b52a831f6b92daaba100e09299ec967679adf308 Mon Sep 17 00:00:00 2001 From: Matt Riedemann Date: Wed, 24 Jul 2019 12:03:01 -0400 Subject: [PATCH 2085/3095] Mention compute service set --up|--down requires 2.11 or greater This simply updates the docs for the compute service set --up and --down options to mention that --os-compute-api-version 2.11 or greater is required to use those options. Change-Id: I52891fe36c84d0df3e868ab4f3c8e2357e9ba529 --- doc/source/cli/command-objects/compute-service.rst | 5 +++-- openstackclient/compute/v2/service.py | 6 ++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/doc/source/cli/command-objects/compute-service.rst b/doc/source/cli/command-objects/compute-service.rst index 51d5c86433..45e781e298 100644 --- a/doc/source/cli/command-objects/compute-service.rst +++ b/doc/source/cli/command-objects/compute-service.rst @@ -74,11 +74,12 @@ Set compute service properties .. option:: --up - Force up service + Force up service. Requires ``--os-compute-api-version`` 2.11 or greater. .. option:: --down - Force down service + Force down service. . Requires ``--os-compute-api-version`` 2.11 or + greater. .. _compute_service_set-host: .. describe:: diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index 98347c9f86..f59f85de26 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.py @@ -152,12 +152,14 @@ def get_parser(self, prog_name): up_down_group.add_argument( '--up', action='store_true', - help=_('Force up service'), + help=_('Force up service. Requires ``--os-compute-api-version`` ' + '2.11 or greater.'), ) up_down_group.add_argument( '--down', action='store_true', - help=_('Force down service'), + help=_('Force down service. Requires ``--os-compute-api-version`` ' + '2.11 or greater.'), ) return parser From 68809fce5a1073659001a87aee4f9407affd5d0e Mon Sep 17 00:00:00 2001 From: zhouhenglc Date: Tue, 7 May 2019 10:52:49 +0800 Subject: [PATCH 2086/3095] openstack port create support --extra-dhcp-option neutron create-port API has extra_dhcp_opts parameter, this parameter can set port with special extra dhcp options. Change-Id: I199f17e95c509a33f809ac85c65f685a37acd198 --- openstackclient/network/v2/port.py | 30 ++++++++++++- .../tests/unit/network/v2/test_port.py | 42 +++++++++++++++++++ ...h-extra-dhcp-options-c2c40e4002b52e2a.yaml | 6 +++ 3 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/allow-port-create-with-extra-dhcp-options-c2c40e4002b52e2a.yaml diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index d42fbb683f..e6bfe8537f 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -280,6 +280,19 @@ def _convert_address_pairs(parsed_args): return ops +def _convert_extra_dhcp_options(parsed_args): + dhcp_options = [] + for opt in parsed_args.extra_dhcp_options: + option = {} + option['opt_name'] = opt['name'] + if 'value' in opt: + option['opt_value'] = opt['value'] + if 'ip-version' in opt: + option['ip_version'] = opt['ip-version'] + dhcp_options.append(option) + return dhcp_options + + class CreatePort(command.ShowOne): _description = _("Create a new port") @@ -350,8 +363,18 @@ def get_parser(self, prog_name): metavar='', help=_("Name of this port") ) - # TODO(singhj): Add support for extended options: - # dhcp + parser.add_argument( + '--extra-dhcp-option', + metavar='name=[,value=,ip-version={4,6}]', + default=[], + action=parseractions.MultiKeyValueCommaAction, + dest='extra_dhcp_options', + required_keys=['name'], + optional_keys=['value', "ip-version"], + help=_('Extra DHCP options to be assigned to this port: ' + 'name=[,value=,ip-version={4,6}] ' + '(repeat option to set multiple extra DHCP options)')) + secgroups = parser.add_mutually_exclusive_group() secgroups.add_argument( '--security-group', @@ -425,6 +448,9 @@ def take_action(self, parsed_args): attrs['allowed_address_pairs'] = ( _convert_address_pairs(parsed_args)) + if parsed_args.extra_dhcp_options: + attrs["extra_dhcp_opts"] = _convert_extra_dhcp_options(parsed_args) + if parsed_args.qos_policy: attrs['qos_policy_id'] = client.find_qos_policy( parsed_args.qos_policy, ignore_missing=False).id diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index acf85f4652..c30d682f59 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -611,6 +611,48 @@ def test_create_with_uplink_status_propagation_enabled(self): def test_create_with_uplink_status_propagation_disabled(self): self._test_create_with_uplink_status_propagation(enable=False) + def test_create_port_with_extra_dhcp_option(self): + extra_dhcp_options = [{'opt_name': 'classless-static-route', + 'opt_value': '169.254.169.254/32,22.2.0.2,' + '0.0.0.0/0,22.2.0.1', + 'ip_version': '4'}, + {'opt_name': 'dns-server', + 'opt_value': '240C::6666', + 'ip_version': '6'}] + arglist = [ + '--network', self._port.network_id, + '--extra-dhcp-option', 'name=classless-static-route,' + 'value=169.254.169.254/32,22.2.0.2,' + '0.0.0.0/0,22.2.0.1,' + 'ip-version=4', + '--extra-dhcp-option', 'name=dns-server,value=240C::6666,' + 'ip-version=6', + 'test-port', + ] + + verifylist = [ + ('network', self._port.network_id,), + ('extra_dhcp_options', [{'name': 'classless-static-route', + 'value': '169.254.169.254/32,22.2.0.2,' + '0.0.0.0/0,22.2.0.1', + 'ip-version': '4'}, + {'name': 'dns-server', + 'value': '240C::6666', + 'ip-version': '6'}]), + ('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, + 'extra_dhcp_opts': extra_dhcp_options, + 'name': 'test-port', + }) + class TestDeletePort(TestPort): diff --git a/releasenotes/notes/allow-port-create-with-extra-dhcp-options-c2c40e4002b52e2a.yaml b/releasenotes/notes/allow-port-create-with-extra-dhcp-options-c2c40e4002b52e2a.yaml new file mode 100644 index 0000000000..ed4fa1bca0 --- /dev/null +++ b/releasenotes/notes/allow-port-create-with-extra-dhcp-options-c2c40e4002b52e2a.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--extra-dhcp-options`` parameter to the ``port create`` command. The + neutronclient ``port-create`` command can accept extra DHCP options, add + it to the openstackclient in order to be consistent. \ No newline at end of file From 1bad2cb3c34a2f39e5006f732978c72e9c2caa5a Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Fri, 26 Jul 2019 17:35:21 +0900 Subject: [PATCH 2087/3095] Fix module paths for volumev3 volume backup commands https://review.opendev.org/#/c/612751/ renamed volume.v2.backup to volume.v2.volume_backup. volume.v3 backup commands refer volume.v2.backup but they were not updated. This causes "volume backup xxx" commands when OS_VOLUME_API_VERSION=3 is specified. Change-Id: Ib897dd483a7963763016ee3f1e8e1c3cc81d0bb0 Story:o 2006284 Task: 35990 --- setup.cfg | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/setup.cfg b/setup.cfg index c48088087f..c57bc41dda 100644 --- a/setup.cfg +++ b/setup.cfg @@ -665,12 +665,12 @@ openstack.volume.v3 = volume_show = openstackclient.volume.v2.volume:ShowVolume volume_unset = openstackclient.volume.v2.volume:UnsetVolume - volume_backup_create = openstackclient.volume.v2.backup:CreateVolumeBackup - volume_backup_delete = openstackclient.volume.v2.backup:DeleteVolumeBackup - volume_backup_list = openstackclient.volume.v2.backup:ListVolumeBackup - volume_backup_restore = openstackclient.volume.v2.backup:RestoreVolumeBackup - volume_backup_set = openstackclient.volume.v2.backup:SetVolumeBackup - volume_backup_show = openstackclient.volume.v2.backup:ShowVolumeBackup + volume_backup_create = openstackclient.volume.v2.volume_backup:CreateVolumeBackup + volume_backup_delete = openstackclient.volume.v2.volume_backup:DeleteVolumeBackup + volume_backup_list = openstackclient.volume.v2.volume_backup:ListVolumeBackup + volume_backup_restore = openstackclient.volume.v2.volume_backup:RestoreVolumeBackup + volume_backup_set = openstackclient.volume.v2.volume_backup:SetVolumeBackup + volume_backup_show = openstackclient.volume.v2.volume_backup:ShowVolumeBackup volume_host_set = openstackclient.volume.v2.volume_host:SetVolumeHost From ba0a8e9318d62d21596824d563a3420c8a0f3a7a Mon Sep 17 00:00:00 2001 From: Tim Burke Date: Tue, 30 Jul 2019 11:49:51 -0700 Subject: [PATCH 2088/3095] Fix typo: "to and endpoint" Change-Id: I33b7bbf7f452991d7f066aa6c17e905f5a4ddb05 --- doc/source/cli/command-objects/endpoint.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/cli/command-objects/endpoint.rst b/doc/source/cli/command-objects/endpoint.rst index 030947c229..6d0253278b 100644 --- a/doc/source/cli/command-objects/endpoint.rst +++ b/doc/source/cli/command-objects/endpoint.rst @@ -7,7 +7,7 @@ Identity v2, v3 endpoint add project -------------------- -Associate a project to and endpoint for endpoint filtering +Associate a project to an endpoint for endpoint filtering .. program:: endpoint add project .. code:: bash From 865e182970c9ce42d5be07cd3b81fb5dd1a3e656 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 26 Jul 2019 16:41:04 -0500 Subject: [PATCH 2089/3095] Make configuration show not require auth The configuration show should not require auth to just display the OSC config object. Changes to make it not require auth have knock-on effects of needing to change a bunch of tests that use it assuming it _does_ require auth so change those to use 'extension list' instead. This sets up further testing of the command line options for changes in behaviour when we switch to straight SDK usage for configuration. Change-Id: I6c52485341214ba401064c0f2d1e2b95fdc225c0 Signed-off-by: Dean Troyer --- openstackclient/common/configuration.py | 20 +++- openstackclient/tests/functional/base.py | 14 ++- .../functional/common/test_configuration.py | 27 +++++ .../tests/unit/integ/cli/test_project.py | 32 +++--- .../tests/unit/integ/cli/test_shell.py | 105 +++++++++++++----- .../notes/config-show-00512dc60882e5c0.yaml | 6 + 6 files changed, 150 insertions(+), 54 deletions(-) create mode 100644 releasenotes/notes/config-show-00512dc60882e5c0.yaml diff --git a/openstackclient/common/configuration.py b/openstackclient/common/configuration.py index 57825bb056..53b30d5fdd 100644 --- a/openstackclient/common/configuration.py +++ b/openstackclient/common/configuration.py @@ -25,6 +25,8 @@ class ShowConfiguration(command.ShowOne): _description = _("Display configuration details") + auth_required = False + def get_parser(self, prog_name): parser = super(ShowConfiguration, self).get_parser(prog_name) mask_group = parser.add_mutually_exclusive_group() @@ -45,13 +47,21 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): - auth_plg_name = self.app.client_manager.auth_plugin_name - secret_opts = [o.dest for o in base.get_plugin_options(auth_plg_name) - if o.secret] - info = self.app.client_manager.get_configuration() + + # Assume a default secret list in case we do not have an auth_plugin + secret_opts = ["password", "token"] + + if getattr(self.app.client_manager, "auth_plugin_name", None): + auth_plg_name = self.app.client_manager.auth_plugin_name + secret_opts = [ + o.dest for o in base.get_plugin_options(auth_plg_name) + if o.secret + ] + for key, value in six.iteritems(info.pop('auth', {})): if parsed_args.mask and key.lower() in secret_opts: - value = REDACTED + value = REDACTED info['auth.' + key] = value + return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/tests/functional/base.py b/openstackclient/tests/functional/base.py index 1414e6bbb7..c34ca39332 100644 --- a/openstackclient/tests/functional/base.py +++ b/openstackclient/tests/functional/base.py @@ -45,9 +45,17 @@ class TestCase(testtools.TestCase): @classmethod def openstack(cls, cmd, cloud=ADMIN_CLOUD, fail_ok=False): """Executes openstackclient command for the given action.""" - return execute( - 'openstack --os-cloud={cloud} '.format(cloud=cloud) + - cmd, fail_ok=fail_ok) + if cloud is not None: + return execute( + 'openstack --os-cloud={cloud} '.format(cloud=cloud) + cmd, + fail_ok=fail_ok + ) + else: + # Execute command with no auth + return execute( + 'openstack --os-auth-type none ' + cmd, + fail_ok=fail_ok + ) @classmethod def is_service_enabled(cls, service): diff --git a/openstackclient/tests/functional/common/test_configuration.py b/openstackclient/tests/functional/common/test_configuration.py index 63a17d0e77..17e0f45d1f 100644 --- a/openstackclient/tests/functional/common/test_configuration.py +++ b/openstackclient/tests/functional/common/test_configuration.py @@ -37,6 +37,10 @@ def test_configuration_show(self): configuration.REDACTED, cmd_output['auth.password'] ) + self.assertIn( + 'auth.password', + cmd_output.keys(), + ) # Test show --mask cmd_output = json.loads(self.openstack( @@ -65,3 +69,26 @@ def test_configuration_show(self): configuration.REDACTED, cmd_output['auth.password'] ) + + +class ConfigurationTestsNoAuth(base.TestCase): + """Functional test for configuration with no auth""" + + def test_configuration_show(self): + + # Test show without option + raw_output = self.openstack( + 'configuration show', + cloud=None, + ) + items = self.parse_listing(raw_output) + self.assert_table_structure(items, BASIC_CONFIG_HEADERS) + + cmd_output = json.loads(self.openstack( + 'configuration show -f json', + cloud=None, + )) + self.assertNotIn( + 'auth.password', + cmd_output, + ) diff --git a/openstackclient/tests/unit/integ/cli/test_project.py b/openstackclient/tests/unit/integ/cli/test_project.py index 6a7c6d1bd6..4e707a3762 100644 --- a/openstackclient/tests/unit/integ/cli/test_project.py +++ b/openstackclient/tests/unit/integ/cli/test_project.py @@ -36,10 +36,10 @@ def setUp(self): def test_project_id_env(self): _shell = shell.OpenStackShell() - _shell.run("configuration show".split()) + _shell.run("extension list".split()) # Check general calls - self.assertEqual(len(self.requests_mock.request_history), 2) + self.assertNotEqual(len(self.requests_mock.request_history), 0) # Check discovery request self.assertEqual( @@ -57,10 +57,10 @@ def test_project_id_env(self): def test_project_id_arg(self): _shell = shell.OpenStackShell() - _shell.run("--os-project-id wsx configuration show".split()) + _shell.run("--os-project-id wsx extension list".split()) # Check general calls - self.assertEqual(len(self.requests_mock.request_history), 2) + self.assertNotEqual(len(self.requests_mock.request_history), 0) # Check discovery request self.assertEqual( @@ -94,10 +94,10 @@ def setUp(self): def test_project_name_env(self): _shell = shell.OpenStackShell() - _shell.run("configuration show".split()) + _shell.run("extension list".split()) # Check general calls - self.assertEqual(len(self.requests_mock.request_history), 2) + self.assertNotEqual(len(self.requests_mock.request_history), 0) # Check discovery request self.assertEqual( @@ -115,10 +115,10 @@ def test_project_name_env(self): def test_project_name_arg(self): _shell = shell.OpenStackShell() - _shell.run("--os-project-name qaz configuration show".split()) + _shell.run("--os-project-name qaz extension list".split()) # Check general calls - self.assertEqual(len(self.requests_mock.request_history), 2) + self.assertNotEqual(len(self.requests_mock.request_history), 0) # Check discovery request self.assertEqual( @@ -154,10 +154,10 @@ def setUp(self): def test_project_id_env(self): _shell = shell.OpenStackShell() - _shell.run("configuration show".split()) + _shell.run("extension list".split()) # Check general calls - self.assertEqual(len(self.requests_mock.request_history), 2) + self.assertNotEqual(len(self.requests_mock.request_history), 0) # Check discovery request self.assertEqual( @@ -173,10 +173,10 @@ def test_project_id_env(self): def test_project_id_arg(self): _shell = shell.OpenStackShell() - _shell.run("--os-project-id wsx configuration show".split()) + _shell.run("--os-project-id wsx extension list".split()) # Check general calls - self.assertEqual(len(self.requests_mock.request_history), 2) + self.assertNotEqual(len(self.requests_mock.request_history), 0) # Check discovery request self.assertEqual( @@ -210,10 +210,10 @@ def setUp(self): def test_project_name_env(self): _shell = shell.OpenStackShell() - _shell.run("configuration show".split()) + _shell.run("extension list".split()) # Check general calls - self.assertEqual(len(self.requests_mock.request_history), 2) + self.assertNotEqual(len(self.requests_mock.request_history), 0) # Check discovery request self.assertEqual( @@ -234,10 +234,10 @@ def test_project_name_env(self): def test_project_name_arg(self): _shell = shell.OpenStackShell() - _shell.run("--os-project-name wsx configuration show".split()) + _shell.run("--os-project-name wsx extension list".split()) # Check general calls - self.assertEqual(len(self.requests_mock.request_history), 2) + self.assertNotEqual(len(self.requests_mock.request_history), 0) # Check discovery request self.assertEqual( diff --git a/openstackclient/tests/unit/integ/cli/test_shell.py b/openstackclient/tests/unit/integ/cli/test_shell.py index 200f9b1869..2598517113 100644 --- a/openstackclient/tests/unit/integ/cli/test_shell.py +++ b/openstackclient/tests/unit/integ/cli/test_shell.py @@ -31,6 +31,51 @@ CONFIG_MOCK_BASE = "os_client_config.config" +class TestIntegShellCliNoAuth(test_base.TestInteg): + + def setUp(self): + super(TestIntegShellCliNoAuth, self).setUp() + env = {} + self.useFixture(osc_lib_utils.EnvFixture(copy.deepcopy(env))) + + # self.token = test_base.make_v2_token(self.requests_mock) + + def test_shell_args_no_options(self): + _shell = shell.OpenStackShell() + _shell.run("configuration show".split()) + + # Check general calls + self.assertEqual(len(self.requests_mock.request_history), 0) + + def test_shell_args_verify(self): + _shell = shell.OpenStackShell() + _shell.run("--verify configuration show".split()) + + # Check general calls + self.assertEqual(len(self.requests_mock.request_history), 0) + + def test_shell_args_insecure(self): + _shell = shell.OpenStackShell() + _shell.run("--insecure configuration show".split()) + + # Check general calls + self.assertEqual(len(self.requests_mock.request_history), 0) + + def test_shell_args_cacert(self): + _shell = shell.OpenStackShell() + _shell.run("--os-cacert xyzpdq configuration show".split()) + + # Check general calls + self.assertEqual(len(self.requests_mock.request_history), 0) + + def test_shell_args_cacert_insecure(self): + _shell = shell.OpenStackShell() + _shell.run("--os-cacert xyzpdq --insecure configuration show".split()) + + # Check general calls + self.assertEqual(len(self.requests_mock.request_history), 0) + + class TestIntegShellCliV2(test_base.TestInteg): def setUp(self): @@ -48,10 +93,10 @@ def setUp(self): def test_shell_args_no_options(self): _shell = shell.OpenStackShell() - _shell.run("configuration show".split()) + _shell.run("extension list".split()) # Check general calls - self.assertEqual(len(self.requests_mock.request_history), 2) + self.assertNotEqual(len(self.requests_mock.request_history), 0) # Check discovery request self.assertEqual( @@ -77,30 +122,30 @@ def test_shell_args_no_options(self): def test_shell_args_verify(self): _shell = shell.OpenStackShell() - _shell.run("--verify configuration show".split()) + _shell.run("--verify extension list".split()) # Check general calls - self.assertEqual(len(self.requests_mock.request_history), 2) + self.assertNotEqual(len(self.requests_mock.request_history), 0) # Check verify self.assertTrue(self.requests_mock.request_history[0].verify) def test_shell_args_insecure(self): _shell = shell.OpenStackShell() - _shell.run("--insecure configuration show".split()) + _shell.run("--insecure extension list".split()) # Check general calls - self.assertEqual(len(self.requests_mock.request_history), 2) + self.assertNotEqual(len(self.requests_mock.request_history), 0) # Check verify self.assertFalse(self.requests_mock.request_history[0].verify) def test_shell_args_cacert(self): _shell = shell.OpenStackShell() - _shell.run("--os-cacert xyzpdq configuration show".split()) + _shell.run("--os-cacert xyzpdq extension list".split()) # Check general calls - self.assertEqual(len(self.requests_mock.request_history), 2) + self.assertNotEqual(len(self.requests_mock.request_history), 0) # Check verify self.assertEqual( @@ -110,10 +155,10 @@ def test_shell_args_cacert(self): def test_shell_args_cacert_insecure(self): _shell = shell.OpenStackShell() - _shell.run("--os-cacert xyzpdq --insecure configuration show".split()) + _shell.run("--os-cacert xyzpdq --insecure extension list".split()) # Check general calls - self.assertEqual(len(self.requests_mock.request_history), 2) + self.assertNotEqual(len(self.requests_mock.request_history), 0) # Check verify self.assertFalse(self.requests_mock.request_history[0].verify) @@ -138,10 +183,10 @@ def setUp(self): def test_shell_args_ignore_v3(self): _shell = shell.OpenStackShell() - _shell.run("configuration show".split()) + _shell.run("extension list".split()) # Check general calls - self.assertEqual(len(self.requests_mock.request_history), 2) + self.assertNotEqual(len(self.requests_mock.request_history), 0) # Check discovery request self.assertEqual( @@ -184,10 +229,10 @@ def setUp(self): def test_shell_args_no_options(self): _shell = shell.OpenStackShell() - _shell.run("configuration show".split()) + _shell.run("extension list".split()) # Check general calls - self.assertEqual(len(self.requests_mock.request_history), 2) + self.assertNotEqual(len(self.requests_mock.request_history), 0) # Check discovery request self.assertEqual( @@ -213,30 +258,30 @@ def test_shell_args_no_options(self): def test_shell_args_verify(self): _shell = shell.OpenStackShell() - _shell.run("--verify configuration show".split()) + _shell.run("--verify extension list".split()) # Check general calls - self.assertEqual(len(self.requests_mock.request_history), 2) + self.assertNotEqual(len(self.requests_mock.request_history), 0) # Check verify self.assertTrue(self.requests_mock.request_history[0].verify) def test_shell_args_insecure(self): _shell = shell.OpenStackShell() - _shell.run("--insecure configuration show".split()) + _shell.run("--insecure extension list".split()) # Check general calls - self.assertEqual(len(self.requests_mock.request_history), 2) + self.assertNotEqual(len(self.requests_mock.request_history), 0) # Check verify self.assertFalse(self.requests_mock.request_history[0].verify) def test_shell_args_cacert(self): _shell = shell.OpenStackShell() - _shell.run("--os-cacert xyzpdq configuration show".split()) + _shell.run("--os-cacert xyzpdq extension list".split()) # Check general calls - self.assertEqual(len(self.requests_mock.request_history), 2) + self.assertNotEqual(len(self.requests_mock.request_history), 0) # Check verify self.assertEqual( @@ -248,10 +293,10 @@ def test_shell_args_cacert_insecure(self): # This test verifies the outcome of bug 1447784 # https://bugs.launchpad.net/python-openstackclient/+bug/1447784 _shell = shell.OpenStackShell() - _shell.run("--os-cacert xyzpdq --insecure configuration show".split()) + _shell.run("--os-cacert xyzpdq --insecure extension list".split()) # Check general calls - self.assertEqual(len(self.requests_mock.request_history), 2) + self.assertNotEqual(len(self.requests_mock.request_history), 0) # Check verify self.assertFalse(self.requests_mock.request_history[0].verify) @@ -276,10 +321,10 @@ def setUp(self): def test_shell_callback(self, mock_prompt): mock_prompt.return_value = "qaz" _shell = shell.OpenStackShell() - _shell.run("configuration show".split()) + _shell.run("extension list".split()) # Check general calls - self.assertEqual(len(self.requests_mock.request_history), 2) + self.assertNotEqual(len(self.requests_mock.request_history), 0) # Check password callback set correctly self.assertEqual( @@ -332,11 +377,11 @@ def test_shell_args_options(self): _shell = shell.OpenStackShell() _shell.run( "--os-username zarquon --os-password qaz " - "configuration show".split(), + "extension list".split(), ) # Check general calls - self.assertEqual(len(self.requests_mock.request_history), 2) + self.assertNotEqual(len(self.requests_mock.request_history), 0) # Check discovery request self.assertEqual( @@ -431,11 +476,11 @@ def vendor_mock_return(): print("CONFIG_MOCK_BASE=%s" % CONFIG_MOCK_BASE) _shell = shell.OpenStackShell() _shell.run( - "--os-password qaz configuration show".split(), + "--os-password qaz extension list".split(), ) # Check general calls - self.assertEqual(len(self.requests_mock.request_history), 2) + self.assertNotEqual(len(self.requests_mock.request_history), 0) # Check discovery request self.assertEqual( @@ -504,11 +549,11 @@ def vendor_mock_return(): _shell = shell.OpenStackShell() _shell.run( "--os-username zarquon --os-password qaz " - "--os-project-domain-id 5678 configuration show".split(), + "--os-project-domain-id 5678 extension list".split(), ) # Check general calls - self.assertEqual(len(self.requests_mock.request_history), 2) + self.assertNotEqual(len(self.requests_mock.request_history), 0) # Check discovery request self.assertEqual( diff --git a/releasenotes/notes/config-show-00512dc60882e5c0.yaml b/releasenotes/notes/config-show-00512dc60882e5c0.yaml new file mode 100644 index 0000000000..da57dff7f9 --- /dev/null +++ b/releasenotes/notes/config-show-00512dc60882e5c0.yaml @@ -0,0 +1,6 @@ +--- +other: + - | + The ``configuration show`` command no longer requires authentication. + This is useful in debugging cloud configurations, particularly + auth configurations. From 7561e062ebd80a28655523320cbe1a49e94ee509 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 13 Jun 2019 17:44:28 +0100 Subject: [PATCH 2090/3095] Add 'openstack server resize (confirm|revert)' commands These are currently exposed as flags on the 'openstack server resize' command but they are in fact operation and should be exposed as commands in their own right. The old flag-based variants are deprecated for removal in 4.0. Change-Id: I733796d3bda6c3755a3d3548bbe695abb474a6a0 Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server.py | 55 +++++++++++++ .../tests/unit/compute/v2/test_server.py | 82 ++++++++++++++++++- ...firm-revert-commands-98854ca98965432a.yaml | 12 +++ setup.cfg | 2 + 4 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/add-server-resize-confirm-revert-commands-98854ca98965432a.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 3e1deed59a..9882922643 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -2096,11 +2096,66 @@ def _show_progress(progress): self.app.stdout.write(_('Error resizing server\n')) raise SystemExit elif parsed_args.confirm: + self.log.warning(_( + "The --confirm option has been deprecated. Please use the " + "'openstack server resize confirm' command instead.")) compute_client.servers.confirm_resize(server) elif parsed_args.revert: + self.log.warning(_( + "The --revert option has been deprecated. Please use the " + "'openstack server resize revert' command instead.")) compute_client.servers.revert_resize(server) +class ResizeConfirm(command.Command): + _description = _("""Confirm server resize. + +Confirm (verify) success of resize operation and release the old server.""") + + def get_parser(self, prog_name): + parser = super(ResizeConfirm, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + help=_('Server (name or ID)'), + ) + return parser + + def take_action(self, parsed_args): + + compute_client = self.app.client_manager.compute + server = utils.find_resource( + compute_client.servers, + parsed_args.server, + ) + server.confirm_resize() + + +class ResizeRevert(command.Command): + _description = _("""Revert server resize. + +Revert the resize operation. Release the new server and restart the old +one.""") + + def get_parser(self, prog_name): + parser = super(ResizeRevert, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + help=_('Server (name or ID)'), + ) + return parser + + def take_action(self, parsed_args): + + compute_client = self.app.client_manager.compute + server = utils.find_resource( + compute_client.servers, + parsed_args.server, + ) + server.revert_resize() + + class RestoreServer(command.Command): _description = _("Restore server(s)") diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 4a37a81216..974be24c45 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -3603,13 +3603,18 @@ def test_server_resize_confirm(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) + with mock.patch.object(self.cmd.log, 'warning') as mock_warning: + result = self.cmd.take_action(parsed_args) self.servers_mock.get.assert_called_with(self.server.id) self.assertNotCalled(self.servers_mock.resize) self.servers_mock.confirm_resize.assert_called_with(self.server) self.assertNotCalled(self.servers_mock.revert_resize) self.assertIsNone(result) + # A warning should have been logged for using --confirm. + mock_warning.assert_called_once() + self.assertIn('The --confirm option has been deprecated.', + six.text_type(mock_warning.call_args[0][0])) def test_server_resize_revert(self): arglist = [ @@ -3623,13 +3628,18 @@ def test_server_resize_revert(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) + with mock.patch.object(self.cmd.log, 'warning') as mock_warning: + result = self.cmd.take_action(parsed_args) self.servers_mock.get.assert_called_with(self.server.id) self.assertNotCalled(self.servers_mock.resize) self.assertNotCalled(self.servers_mock.confirm_resize) self.servers_mock.revert_resize.assert_called_with(self.server) self.assertIsNone(result) + # A warning should have been logged for using --revert. + mock_warning.assert_called_once() + self.assertIn('The --revert option has been deprecated.', + six.text_type(mock_warning.call_args[0][0])) @mock.patch.object(common_utils, 'wait_for_status', return_value=True) def test_server_resize_with_wait_ok(self, mock_wait_for_status): @@ -3710,6 +3720,74 @@ def test_server_resize_with_wait_fails(self, mock_wait_for_status): ) +class TestServerResizeConfirm(TestServer): + + def setUp(self): + super(TestServerResizeConfirm, self).setUp() + + methods = { + 'confirm_resize': None, + } + self.server = compute_fakes.FakeServer.create_one_server( + methods=methods) + + # This is the return value for utils.find_resource() + self.servers_mock.get.return_value = self.server + + self.servers_mock.confirm_resize.return_value = None + + # Get the command object to test + self.cmd = server.ResizeConfirm(self.app, None) + + def test_resize_confirm(self): + arglist = [ + self.server.id, + ] + verifylist = [ + ('server', self.server.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.server.confirm_resize.assert_called_with() + + +class TestServerResizeRevert(TestServer): + + def setUp(self): + super(TestServerResizeRevert, self).setUp() + + methods = { + 'revert_resize': None, + } + self.server = compute_fakes.FakeServer.create_one_server( + methods=methods) + + # This is the return value for utils.find_resource() + self.servers_mock.get.return_value = self.server + + self.servers_mock.revert_resize.return_value = None + + # Get the command object to test + self.cmd = server.ResizeRevert(self.app, None) + + def test_resize_revert(self): + arglist = [ + self.server.id, + ] + verifylist = [ + ('server', self.server.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.server.revert_resize.assert_called_with() + + class TestServerRestore(TestServer): def setUp(self): diff --git a/releasenotes/notes/add-server-resize-confirm-revert-commands-98854ca98965432a.yaml b/releasenotes/notes/add-server-resize-confirm-revert-commands-98854ca98965432a.yaml new file mode 100644 index 0000000000..2399a1a659 --- /dev/null +++ b/releasenotes/notes/add-server-resize-confirm-revert-commands-98854ca98965432a.yaml @@ -0,0 +1,12 @@ +--- +features: + - | + Add ``server resize confirm`` and ``server resize revert`` commands. + These replace the now deprecated ``--confirm`` and ``--revert`` + options to the ``server resize`` commands, respectively. +deprecations: + - | + Deprecate the ``--confirm`` and ``--revert`` options for the + ``server resize`` command. They have been replaced with the + ``server resize confirm`` and `server resize revert`` commands, + respectively. diff --git a/setup.cfg b/setup.cfg index db03c48423..aa72637184 100644 --- a/setup.cfg +++ b/setup.cfg @@ -124,6 +124,8 @@ openstack.compute.v2 = server_remove_volume = openstackclient.compute.v2.server:RemoveServerVolume server_rescue = openstackclient.compute.v2.server:RescueServer server_resize = openstackclient.compute.v2.server:ResizeServer + server_resize_confirm = openstackclient.compute.v2.server:ResizeConfirm + server_resize_revert = openstackclient.compute.v2.server:ResizeRevert server_restore = openstackclient.compute.v2.server:RestoreServer server_resume = openstackclient.compute.v2.server:ResumeServer server_set = openstackclient.compute.v2.server:SetServer From 4bd53dc1090fda86f6ce25b76a079e250c9206d8 Mon Sep 17 00:00:00 2001 From: Matt Riedemann Date: Wed, 24 Jul 2019 14:39:07 -0400 Subject: [PATCH 2091/3095] Fix compute service set handling for 2.53+ With compute API microversion 2.53 there is a single PUT /os-services/{service_id} API which takes the service id as a UUID. Since the openstack compute service set command only takes --host and --service (binary) to identify the service, this change checks if 2.53 or greater is being used and if so, looks up the service by host and binary and calls the appropriate methods in novaclient. If the command cannot uniquely identify a compute service with the given host and binary, an error is raised. A future change could add an --id option to be used with 2.53+ to pass the service id (as UUID) directly to avoid the host/binary filtering. Change-Id: I868e0868e8eb17e7e34eef3d2d58dceedd29c2b0 Story: 2005349 Task: 30302 --- openstackclient/compute/v2/service.py | 63 +++++++++-- .../tests/unit/compute/v2/test_service.py | 104 +++++++++++++++++- ...ute-service-set-2.53-3d2db875154e633a.yaml | 6 + 3 files changed, 162 insertions(+), 11 deletions(-) create mode 100644 releasenotes/notes/story-2005349-compute-service-set-2.53-3d2db875154e633a.yaml diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index f59f85de26..80c0be7ecb 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.py @@ -163,6 +163,33 @@ def get_parser(self, prog_name): ) return parser + @staticmethod + def _find_service_by_host_and_binary(cs, host, binary): + """Utility method to find a compute service by host and binary + + :param host: the name of the compute service host + :param binary: the compute service binary, e.g. nova-compute + :returns: novaclient.v2.services.Service dict-like object + :raises: CommandError if no or multiple results were found + """ + services = cs.list(host=host, binary=binary) + # Did we find anything? + if not len(services): + msg = _('Compute service for host "%(host)s" and binary ' + '"%(binary)s" not found.') % { + 'host': host, 'binary': binary} + raise exceptions.CommandError(msg) + # Did we find more than one result? This should not happen but let's + # be safe. + if len(services) > 1: + # TODO(mriedem): If we have an --id option for 2.53+ then we can + # say to use that option to uniquely identify the service. + msg = _('Multiple compute services found for host "%(host)s" and ' + 'binary "%(binary)s". Unable to proceed.') % { + 'host': host, 'binary': binary} + raise exceptions.CommandError(msg) + return services[0] + def take_action(self, parsed_args): compute_client = self.app.client_manager.compute cs = compute_client.services @@ -173,6 +200,20 @@ def take_action(self, parsed_args): "--disable specified.") raise exceptions.CommandError(msg) + # Starting with microversion 2.53, there is a single + # PUT /os-services/{service_id} API for updating nova-compute + # services. If 2.53+ is used we need to find the nova-compute + # service using the --host and --service (binary) values. + requires_service_id = ( + compute_client.api_version >= api_versions.APIVersion('2.53')) + service_id = None + if requires_service_id: + # TODO(mriedem): Add an --id option so users can pass the service + # id (as a uuid) directly rather than make us look it up using + # host/binary. + service_id = SetService._find_service_by_host_and_binary( + cs, parsed_args.host, parsed_args.service).id + result = 0 enabled = None try: @@ -183,14 +224,21 @@ def take_action(self, parsed_args): if enabled is not None: if enabled: - cs.enable(parsed_args.host, parsed_args.service) + args = (service_id,) if requires_service_id else ( + parsed_args.host, parsed_args.service) + cs.enable(*args) else: if parsed_args.disable_reason: - cs.disable_log_reason(parsed_args.host, - parsed_args.service, - parsed_args.disable_reason) + args = (service_id, parsed_args.disable_reason) if \ + requires_service_id else ( + parsed_args.host, + parsed_args.service, + parsed_args.disable_reason) + cs.disable_log_reason(*args) else: - cs.disable(parsed_args.host, parsed_args.service) + args = (service_id,) if requires_service_id else ( + parsed_args.host, parsed_args.service) + cs.disable(*args) except Exception: status = "enabled" if enabled else "disabled" LOG.error("Failed to set service status to %s", status) @@ -208,8 +256,9 @@ def take_action(self, parsed_args): 'required') raise exceptions.CommandError(msg) try: - cs.force_down(parsed_args.host, parsed_args.service, - force_down=force_down) + args = (service_id, force_down) if requires_service_id else ( + parsed_args.host, parsed_args.service, force_down) + cs.force_down(*args) except Exception: state = "down" if force_down else "up" LOG.error("Failed to set service state to %s", state) diff --git a/openstackclient/tests/unit/compute/v2/test_service.py b/openstackclient/tests/unit/compute/v2/test_service.py index bd29912341..0d663b2e67 100644 --- a/openstackclient/tests/unit/compute/v2/test_service.py +++ b/openstackclient/tests/unit/compute/v2/test_service.py @@ -17,6 +17,7 @@ from mock import call from novaclient import api_versions from osc_lib import exceptions +import six from openstackclient.compute.v2 import service from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes @@ -344,7 +345,7 @@ def test_service_set_state_up(self): '2.11') result = self.cmd.take_action(parsed_args) self.service_mock.force_down.assert_called_once_with( - self.service.host, self.service.binary, force_down=False) + self.service.host, self.service.binary, False) self.assertNotCalled(self.service_mock.enable) self.assertNotCalled(self.service_mock.disable) self.assertIsNone(result) @@ -365,7 +366,7 @@ def test_service_set_state_down(self): '2.11') result = self.cmd.take_action(parsed_args) self.service_mock.force_down.assert_called_once_with( - self.service.host, self.service.binary, force_down=True) + self.service.host, self.service.binary, True) self.assertNotCalled(self.service_mock.enable) self.assertNotCalled(self.service_mock.disable) self.assertIsNone(result) @@ -390,7 +391,7 @@ def test_service_set_enable_and_state_down(self): self.service_mock.enable.assert_called_once_with( self.service.host, self.service.binary) self.service_mock.force_down.assert_called_once_with( - self.service.host, self.service.binary, force_down=True) + self.service.host, self.service.binary, True) self.assertIsNone(result) def test_service_set_enable_and_state_down_with_exception(self): @@ -415,4 +416,99 @@ def test_service_set_enable_and_state_down_with_exception(self): self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) self.service_mock.force_down.assert_called_once_with( - self.service.host, self.service.binary, force_down=True) + self.service.host, self.service.binary, True) + + def test_service_set_2_53_disable_down(self): + # Tests disabling and forcing down a compute service with microversion + # 2.53 which requires looking up the service by host and binary. + arglist = [ + '--disable', + '--down', + self.service.host, + self.service.binary, + ] + verifylist = [ + ('disable', True), + ('down', True), + ('host', self.service.host), + ('service', self.service.binary), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.53') + service_id = '339478d0-0b95-4a94-be63-d5be05dfeb1c' + self.service_mock.list.return_value = [mock.Mock(id=service_id)] + result = self.cmd.take_action(parsed_args) + self.service_mock.disable.assert_called_once_with(service_id) + self.service_mock.force_down.assert_called_once_with(service_id, True) + self.assertIsNone(result) + + def test_service_set_2_53_disable_reason(self): + # Tests disabling with reason a compute service with microversion + # 2.53 which requires looking up the service by host and binary. + reason = 'earthquake' + arglist = [ + '--disable', + '--disable-reason', reason, + self.service.host, + self.service.binary, + ] + verifylist = [ + ('disable', True), + ('disable_reason', reason), + ('host', self.service.host), + ('service', self.service.binary), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.53') + service_id = '339478d0-0b95-4a94-be63-d5be05dfeb1c' + self.service_mock.list.return_value = [mock.Mock(id=service_id)] + result = self.cmd.take_action(parsed_args) + self.service_mock.disable_log_reason.assert_called_once_with( + service_id, reason) + self.assertIsNone(result) + + def test_service_set_2_53_enable_up(self): + # Tests enabling and bringing up a compute service with microversion + # 2.53 which requires looking up the service by host and binary. + arglist = [ + '--enable', + '--up', + self.service.host, + self.service.binary, + ] + verifylist = [ + ('enable', True), + ('up', True), + ('host', self.service.host), + ('service', self.service.binary), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.53') + service_id = '339478d0-0b95-4a94-be63-d5be05dfeb1c' + self.service_mock.list.return_value = [mock.Mock(id=service_id)] + result = self.cmd.take_action(parsed_args) + self.service_mock.enable.assert_called_once_with(service_id) + self.service_mock.force_down.assert_called_once_with(service_id, False) + self.assertIsNone(result) + + def test_service_set_find_service_by_host_and_binary_no_results(self): + # Tests that no compute services are found by host and binary. + self.service_mock.list.return_value = [] + ex = self.assertRaises(exceptions.CommandError, + self.cmd._find_service_by_host_and_binary, + self.service_mock, 'fake-host', 'nova-compute') + self.assertIn('Compute service for host "fake-host" and binary ' + '"nova-compute" not found.', six.text_type(ex)) + + def test_service_set_find_service_by_host_and_binary_many_results(self): + # Tests that more than one compute service is found by host and binary. + self.service_mock.list.return_value = [mock.Mock(), mock.Mock()] + ex = self.assertRaises(exceptions.CommandError, + self.cmd._find_service_by_host_and_binary, + self.service_mock, 'fake-host', 'nova-compute') + self.assertIn('Multiple compute services found for host "fake-host" ' + 'and binary "nova-compute". Unable to proceed.', + six.text_type(ex)) diff --git a/releasenotes/notes/story-2005349-compute-service-set-2.53-3d2db875154e633a.yaml b/releasenotes/notes/story-2005349-compute-service-set-2.53-3d2db875154e633a.yaml new file mode 100644 index 0000000000..cff3196014 --- /dev/null +++ b/releasenotes/notes/story-2005349-compute-service-set-2.53-3d2db875154e633a.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + The ``compute service set`` command now properly handles + ``--os-compute-api-version`` 2.53 and greater. + [Story `2005349 `_] From 6a199bd14152889f18ad95919a4bee9c0c083d5d Mon Sep 17 00:00:00 2001 From: Matt Riedemann Date: Mon, 29 Jul 2019 12:59:52 -0400 Subject: [PATCH 2092/3095] Support type=image with --block-device-mapping option The --block-device-mapping option on the server create command currently only supports booting from volume and volume snapshot. A common boot-from-volume scenario is providing an image and letting nova orchestrate the creation of the image-backed volume and attaching it to the server. This adds support for type=image in the --block-device-mapping option. The volume size is required in this case. Note that the CLI currently says if type=snapshot that size is also required but that's technically not true. When booting from a volume snapshot, the compute API will use the size of the volume snapshot to create the volume if an explicit size is not provided. For the purposes of this patch, we need the size anyway for the image being the block device mapping source type. Change-Id: I57b3c261d8309f7b9f62a3e91612bce592a887a3 Story: 2006302 Task: 36016 --- openstackclient/compute/v2/server.py | 30 +++++-- .../functional/compute/v2/test_server.py | 87 +++++++++++++++++++ ...pport-bdm-type-image-0becfb63bd4fb969.yaml | 8 ++ 3 files changed, 119 insertions(+), 6 deletions(-) create mode 100644 releasenotes/notes/story-2006302-support-bdm-type-image-0becfb63bd4fb969.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index ea87d9cea1..a0afa389f6 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -575,14 +575,17 @@ def get_parser(self, prog_name): # NOTE(RuiChen): Add '\n' at the end of line to put each item in # the separated line, avoid the help message looks # messy, see _SmartHelpFormatter in cliff. + # FIXME(mriedem): Technically can be the name or ID. help=_('Create a block device on the server.\n' 'Block device mapping in the format\n' '=:::\n' ': block device name, like: vdb, xvdc ' '(required)\n' - ': UUID of the volume or snapshot (required)\n' - ': volume or snapshot; default: volume (optional)\n' - ': volume size if create from snapshot ' + ': UUID of the volume, volume snapshot or image ' + '(required)\n' + ': volume, snapshot or image; default: volume ' + '(optional)\n' + ': volume size if create from image or snapshot ' '(optional)\n' ': true or false; default: false ' '(optional)\n' @@ -793,7 +796,7 @@ def _match_image(image_api, wanted_properties): mapping = {'device_name': dev_name} # 1. decide source and destination type if (len(dev_map) > 1 and - dev_map[1] in ('volume', 'snapshot')): + dev_map[1] in ('volume', 'snapshot', 'image')): mapping['source_type'] = dev_map[1] else: mapping['source_type'] = 'volume' @@ -808,14 +811,29 @@ def _match_image(image_api, wanted_properties): snapshot_id = utils.find_resource( volume_client.volume_snapshots, dev_map[0]).id mapping['uuid'] = snapshot_id + elif mapping['source_type'] == 'image': + # NOTE(mriedem): In case --image is specified with the same + # image, that becomes the root disk for the server. If the + # block device is specified with a root device name, e.g. + # vda, then the compute API will likely fail complaining + # that there is a conflict. So if using the same image ID, + # which doesn't really make sense but it's allowed, the + # device name would need to be a non-root device, e.g. vdb. + # Otherwise if the block device image is different from the + # one specified by --image, then the compute service will + # create a volume from the image and attach it to the + # server as a non-root volume. + image_id = utils.find_resource( + image_client.images, dev_map[0]).id + mapping['uuid'] = image_id # 3. append size and delete_on_termination if exist if len(dev_map) > 2 and dev_map[2]: mapping['volume_size'] = dev_map[2] if len(dev_map) > 3 and dev_map[3]: mapping['delete_on_termination'] = dev_map[3] else: - msg = _("Volume or snapshot (name or ID) must be specified if " - "--block-device-mapping is specified") + msg = _("Volume, volume snapshot or image (name or ID) must " + "be specified if --block-device-mapping is specified") raise exceptions.CommandError(msg) block_device_mapping_v2.append(mapping) diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py index e52a42d3a1..67e2a66ef8 100644 --- a/openstackclient/tests/functional/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -614,6 +614,93 @@ def test_server_boot_with_bdm_snapshot(self): # the attached volume had been deleted pass + def test_server_boot_with_bdm_image(self): + # Tests creating a server where the root disk is backed by the given + # --image but a --block-device-mapping with type=image is provided so + # that the compute service creates a volume from that image and + # attaches it as a non-root volume on the server. The block device is + # marked as delete_on_termination=True so it will be automatically + # deleted when the server is deleted. + + # create server with bdm type=image + # NOTE(mriedem): This test is a bit unrealistic in that specifying the + # same image in the block device as the --image option does not really + # make sense, but we just want to make sure everything is processed + # as expected where nova creates a volume from the image and attaches + # that volume to the server. + server_name = uuid.uuid4().hex + server = json.loads(self.openstack( + 'server create -f json ' + + '--flavor ' + self.flavor_name + ' ' + + '--image ' + self.image_name + ' ' + + '--block-device-mapping ' + # This means create a 1GB volume from the specified image, attach + # it to the server at /dev/vdb and delete the volume when the + # server is deleted. + 'vdb=' + self.image_name + ':image:1:true ' + + self.network_arg + ' ' + + '--wait ' + + server_name + )) + self.assertIsNotNone(server["id"]) + self.assertEqual( + server_name, + server['name'], + ) + self.wait_for_status(server_name, 'ACTIVE') + + # check server volumes_attached, format is + # {"volumes_attached": "id='2518bc76-bf0b-476e-ad6b-571973745bb5'",} + cmd_output = json.loads(self.openstack( + 'server show -f json ' + + server_name + )) + volumes_attached = cmd_output['volumes_attached'] + self.assertTrue(volumes_attached.startswith('id=')) + attached_volume_id = volumes_attached.replace('id=', '') + + # check the volume that attached on server + cmd_output = json.loads(self.openstack( + 'volume show -f json ' + + attached_volume_id + )) + attachments = cmd_output['attachments'] + self.assertEqual( + 1, + len(attachments), + ) + self.assertEqual( + server['id'], + attachments[0]['server_id'], + ) + self.assertEqual( + "in-use", + cmd_output['status'], + ) + # TODO(mriedem): If we can parse the volume_image_metadata field from + # the volume show output we could assert the image_name is what we + # specified. volume_image_metadata is something like this: + # {u'container_format': u'bare', u'min_ram': u'0', + # u'disk_format': u'qcow2', u'image_name': u'cirros-0.4.0-x86_64-disk', + # u'image_id': u'05496c83-e2df-4c2f-9e48-453b6e49160d', + # u'checksum': u'443b7623e27ecf03dc9e01ee93f67afe', u'min_disk': u'0', + # u'size': u'12716032'} + + # delete server, then check the attached volume has been deleted + self.openstack('server delete --wait ' + server_name) + cmd_output = json.loads(self.openstack( + 'volume list -f json' + )) + target_volume = [each_volume + for each_volume in cmd_output + if each_volume['ID'] == attached_volume_id] + if target_volume: + # check the attached volume is 'deleting' status + self.assertEqual('deleting', target_volume[0]['Status']) + else: + # the attached volume had been deleted + pass + def test_server_create_with_none_network(self): """Test server create with none network option.""" server_name = uuid.uuid4().hex diff --git a/releasenotes/notes/story-2006302-support-bdm-type-image-0becfb63bd4fb969.yaml b/releasenotes/notes/story-2006302-support-bdm-type-image-0becfb63bd4fb969.yaml new file mode 100644 index 0000000000..6e8e8ba0b8 --- /dev/null +++ b/releasenotes/notes/story-2006302-support-bdm-type-image-0becfb63bd4fb969.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + The ``server create --block-device-mapping`` option now supports + an ``image`` type in addition to ``volume`` and ``snapshot``. When + specifying an image block device the compute service will create a volume + from the image of the specified size and attach it to the server. + [Story `2006302 `_] From c28ed25e3a6f2d9cf5fc349c544354a9d07aa957 Mon Sep 17 00:00:00 2001 From: Matt Riedemann Date: Thu, 1 Aug 2019 14:15:32 -0400 Subject: [PATCH 2093/3095] Fix description for --block-device-mapping The portion of a --block-device-mapping value can be the resource name or id since the code uses the appropriate type-specific find_resource utility to lookup the resource based on the value given. This change simply fixes the description of to mention it's name or id rather than just "UUID". My guess is the description was originally copied from novaclient where id must be an id since name resolution does not happen in novaclient. Change-Id: I567f6f6efb3a3b6d387133d21aa81354b2d753bc --- openstackclient/compute/v2/server.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index a0afa389f6..fee2b27d4a 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -575,13 +575,12 @@ def get_parser(self, prog_name): # NOTE(RuiChen): Add '\n' at the end of line to put each item in # the separated line, avoid the help message looks # messy, see _SmartHelpFormatter in cliff. - # FIXME(mriedem): Technically can be the name or ID. help=_('Create a block device on the server.\n' 'Block device mapping in the format\n' '=:::\n' ': block device name, like: vdb, xvdc ' '(required)\n' - ': UUID of the volume, volume snapshot or image ' + ': Name or ID of the volume, volume snapshot or image ' '(required)\n' ': volume, snapshot or image; default: volume ' '(optional)\n' From b9d63105566c84db11a976846844ad7b3a0b331e Mon Sep 17 00:00:00 2001 From: Matt Riedemann Date: Thu, 1 Aug 2019 15:11:29 -0400 Subject: [PATCH 2094/3095] Add openstack server create --boot-from-volume option This adds a --boot-from-volume option to the server create command which is used with the --image or --image-property option and will create a volume-backed server from the specified image with the specified size. Similar to the --volume option, the created root volume will not be deleted when the server is deleted. The --boot-from-volume option is not allowed with the --volume option since they both create a block device mapping with boot_index=0. Change-Id: I88c590361cb232c1df7b5bb010dcea307080d34c Story: 2006302 Task: 36017 --- openstackclient/compute/v2/server.py | 33 +++++++- .../functional/compute/v2/test_server.py | 76 +++++++++++++++++++ .../tests/unit/compute/v2/test_server.py | 27 +++++++ ...add-boot-from-volume-cd411b1ca776bb3c.yaml | 8 ++ 4 files changed, 142 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/story-2006302-add-boot-from-volume-cd411b1ca776bb3c.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index fee2b27d4a..95c2f28a81 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -567,6 +567,19 @@ def get_parser(self, prog_name): 'only by default. (supported by --os-compute-api-version ' '2.74 or above)'), ) + parser.add_argument( + '--boot-from-volume', + metavar='', + type=int, + help=_('When used in conjunction with the ``--image`` or ' + '``--image-property`` option, this option automatically ' + 'creates a block device mapping with a boot index of 0 ' + 'and tells the compute service to create a volume of the ' + 'given size (in GB) from the specified image and use it ' + 'as the root disk of the server. The root volume will not ' + 'be deleted when the server is deleted. This option is ' + 'mutually exclusive with the ``--volume`` option.') + ) parser.add_argument( '--block-device-mapping', metavar='', @@ -730,6 +743,10 @@ def _match_image(image_api, wanted_properties): # Lookup parsed_args.volume volume = None if parsed_args.volume: + # --volume and --boot-from-volume are mutually exclusive. + if parsed_args.boot_from_volume: + raise exceptions.CommandError( + _('--volume is not allowed with --boot-from-volume')) volume = utils.find_resource( volume_client.volumes, parsed_args.volume, @@ -739,8 +756,6 @@ def _match_image(image_api, wanted_properties): flavor = utils.find_resource(compute_client.flavors, parsed_args.flavor) - boot_args = [parsed_args.server_name, image, flavor] - files = {} for f in parsed_args.file: dst, src = f.split('=', 1) @@ -787,6 +802,20 @@ def _match_image(image_api, wanted_properties): 'source_type': 'volume', 'destination_type': 'volume' }] + elif parsed_args.boot_from_volume: + # Tell nova to create a root volume from the image provided. + block_device_mapping_v2 = [{ + 'uuid': image.id, + 'boot_index': '0', + 'source_type': 'image', + 'destination_type': 'volume', + 'volume_size': parsed_args.boot_from_volume + }] + # If booting from volume we do not pass an image to compute. + image = None + + boot_args = [parsed_args.server_name, image, flavor] + # Handle block device by device name order, like: vdb -> vdc -> vdd for dev_name in sorted(six.iterkeys(parsed_args.block_device_mapping)): dev_map = parsed_args.block_device_mapping[dev_name] diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py index 67e2a66ef8..2bca70d0ec 100644 --- a/openstackclient/tests/functional/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -701,6 +701,82 @@ def test_server_boot_with_bdm_image(self): # the attached volume had been deleted pass + def test_boot_from_volume(self): + # Tests creating a server using --image and --boot-from-volume where + # the compute service will create a root volume of the specified size + # using the provided image, attach it as the root disk for the server + # and not delete the volume when the server is deleted. + server_name = uuid.uuid4().hex + server = json.loads(self.openstack( + 'server create -f json ' + + '--flavor ' + self.flavor_name + ' ' + + '--image ' + self.image_name + ' ' + + '--boot-from-volume 1 ' + # create a 1GB volume from the image + self.network_arg + ' ' + + '--wait ' + + server_name + )) + self.assertIsNotNone(server["id"]) + self.assertEqual( + server_name, + server['name'], + ) + self.wait_for_status(server_name, 'ACTIVE') + + # check server volumes_attached, format is + # {"volumes_attached": "id='2518bc76-bf0b-476e-ad6b-571973745bb5'",} + cmd_output = json.loads(self.openstack( + 'server show -f json ' + + server_name + )) + volumes_attached = cmd_output['volumes_attached'] + self.assertTrue(volumes_attached.startswith('id=')) + attached_volume_id = volumes_attached.replace('id=', '') + # Don't leak the volume when the test exits. + self.addCleanup(self.openstack, 'volume delete ' + attached_volume_id) + + # Since the server is volume-backed the GET /servers/{server_id} + # response will have image=''. + self.assertEqual('', cmd_output['image']) + + # check the volume that attached on server + cmd_output = json.loads(self.openstack( + 'volume show -f json ' + + attached_volume_id + )) + # The volume size should be what we specified on the command line. + self.assertEqual(1, int(cmd_output['size'])) + attachments = cmd_output['attachments'] + self.assertEqual( + 1, + len(attachments), + ) + self.assertEqual( + server['id'], + attachments[0]['server_id'], + ) + self.assertEqual( + "in-use", + cmd_output['status'], + ) + # TODO(mriedem): If we can parse the volume_image_metadata field from + # the volume show output we could assert the image_name is what we + # specified. volume_image_metadata is something like this: + # {u'container_format': u'bare', u'min_ram': u'0', + # u'disk_format': u'qcow2', u'image_name': u'cirros-0.4.0-x86_64-disk', + # u'image_id': u'05496c83-e2df-4c2f-9e48-453b6e49160d', + # u'checksum': u'443b7623e27ecf03dc9e01ee93f67afe', u'min_disk': u'0', + # u'size': u'12716032'} + + # delete server, then check the attached volume was not deleted + self.openstack('server delete --wait ' + server_name) + cmd_output = json.loads(self.openstack( + 'volume show -f json ' + + attached_volume_id + )) + # check the volume is in 'available' status + self.assertEqual('available', cmd_output['status']) + def test_server_create_with_none_network(self): """Test server create with none network option.""" server_name = uuid.uuid4().hex diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index cd12db066f..ae6b6e40f4 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -1698,6 +1698,33 @@ def test_server_create_with_block_device_mapping_no_uuid(self): self.cmd.take_action, parsed_args) + def test_server_create_volume_boot_from_volume_conflict(self): + # Tests that specifying --volume and --boot-from-volume results in + # an error. Since --boot-from-volume requires --image or + # --image-property but those are in a mutex group with --volume, we + # only specify --volume and --boot-from-volume for this test since + # the validation is not handled with argparse. + arglist = [ + '--flavor', self.flavor.id, + '--volume', 'volume1', + '--boot-from-volume', '1', + self.new_server.name, + ] + verifylist = [ + ('flavor', self.flavor.id), + ('volume', 'volume1'), + ('boot_from_volume', 1), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises(exceptions.CommandError, + self.cmd.take_action, parsed_args) + # Assert it is the error we expect. + self.assertIn('--volume is not allowed with --boot-from-volume', + six.text_type(ex)) + def test_server_create_image_property(self): arglist = [ '--image-property', 'hypervisor_type=qemu', diff --git a/releasenotes/notes/story-2006302-add-boot-from-volume-cd411b1ca776bb3c.yaml b/releasenotes/notes/story-2006302-add-boot-from-volume-cd411b1ca776bb3c.yaml new file mode 100644 index 0000000000..1176c883f1 --- /dev/null +++ b/releasenotes/notes/story-2006302-add-boot-from-volume-cd411b1ca776bb3c.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Add ``--boot-from-volume`` option to the ``server create`` command + to create a volume-backed server from the specified image with the + specified size when used in conjunction with the ``--image`` or + ``--image-property`` options. + [Story `2006302 `_] From 1557afb554e908e097abd39081891ea78083e20e Mon Sep 17 00:00:00 2001 From: Matt Riedemann Date: Thu, 25 Jul 2019 14:28:43 -0400 Subject: [PATCH 2095/3095] Document 2.53 behavior for compute service list/delete With compute API microversion 2.53, nova-compute services can only be deleted with the ID as a UUID to uniquely identify the service in a multi-cell deployment. This documents that for the "compute service delete " argument. The description of the "compute service list" command is also updated to mention that the ID can be retrieved as a UUID using 2.53 or greater. Change-Id: If7d4a27c0aaef588bcd77dd9edddec1e535fbf31 Story: 2005349 Task: 30302 --- doc/source/cli/command-objects/compute-service.rst | 8 +++++++- openstackclient/compute/v2/service.py | 11 +++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/doc/source/cli/command-objects/compute-service.rst b/doc/source/cli/command-objects/compute-service.rst index 45e781e298..a76a9c26b9 100644 --- a/doc/source/cli/command-objects/compute-service.rst +++ b/doc/source/cli/command-objects/compute-service.rst @@ -18,13 +18,19 @@ Delete compute service(s) .. _compute_service_delete-service: .. describe:: - Compute service(s) to delete (ID only) + Compute service(s) to delete (ID only). If using + ``--os-compute-api-version`` 2.53 or greater, the ID is a UUID which can + be retrieved by listing compute services using the same 2.53+ microversion. compute service list -------------------- List compute services +Using ``--os-compute-api-version`` 2.53 or greater will return the ID as a +UUID value which can be used to uniquely identify the service in a multi-cell +deployment. + .. program:: compute service list .. code:: bash diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index 80c0be7ecb..625c0fad3a 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.py @@ -37,7 +37,10 @@ def get_parser(self, prog_name): "service", metavar="", nargs='+', - help=_("Compute service(s) to delete (ID only)") + help=_("Compute service(s) to delete (ID only). If using " + "``--os-compute-api-version`` 2.53 or greater, the ID is " + "a UUID which can be retrieved by listing compute services " + "using the same 2.53+ microversion.") ) return parser @@ -60,7 +63,11 @@ def take_action(self, parsed_args): class ListService(command.Lister): - _description = _("List compute services") + _description = _("List compute services. Using " + "``--os-compute-api-version`` 2.53 or greater will " + "return the ID as a UUID value which can be used to " + "uniquely identify the service in a multi-cell " + "deployment.") def get_parser(self, prog_name): parser = super(ListService, self).get_parser(prog_name) From 3b2863e369ef472042a054e11d189b6fbc34bb42 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 22 Aug 2019 11:05:05 -0500 Subject: [PATCH 2096/3095] Fix functional.base.TestCase.openstack() to optionally omit --os-auth-type Change the functional test TestCase.openstack() method to add a way to not include the --os-auth-type option in order to test the default auth-type logic. Change-Id: I0f1ca2f7517a41278afaad5aaf4e98accb16bea2 Signed-off-by: Dean Troyer --- openstackclient/tests/functional/base.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/openstackclient/tests/functional/base.py b/openstackclient/tests/functional/base.py index c34ca39332..08e9390e3e 100644 --- a/openstackclient/tests/functional/base.py +++ b/openstackclient/tests/functional/base.py @@ -44,16 +44,30 @@ class TestCase(testtools.TestCase): @classmethod def openstack(cls, cmd, cloud=ADMIN_CLOUD, fail_ok=False): - """Executes openstackclient command for the given action.""" - if cloud is not None: + """Executes openstackclient command for the given action + + NOTE(dtroyer): There is a subtle distinction between pasing + cloud=None and cloud='': for compatibility reasons passing + cloud=None continues to include the option '--os-auth-type none' + in the command while passing cloud='' omits the '--os-auth-type' + option completely to let the default handlers be invoked. + """ + if cloud is None: + # Execute command with no auth return execute( - 'openstack --os-cloud={cloud} '.format(cloud=cloud) + cmd, + 'openstack --os-auth-type none ' + cmd, + fail_ok=fail_ok + ) + elif cloud == '': + # Execute command with no auth options at all + return execute( + 'openstack ' + cmd, fail_ok=fail_ok ) else: - # Execute command with no auth + # Execure command with an explicit cloud specified return execute( - 'openstack --os-auth-type none ' + cmd, + 'openstack --os-cloud=' + cloud + ' ' + cmd, fail_ok=fail_ok ) From 75f0f82c411e0bc94648c845669dc9312f0152ba Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 21 Aug 2019 14:15:58 -0500 Subject: [PATCH 2097/3095] Add CLI argument tests before making changes Add these tests before hacking on the global args and removing the compatibility stuff so we can clearly see what actually changes. Change-Id: Ic86c89da1475b4914ff7cb2396199cd219a12097 Signed-off-by: Dean Troyer --- .../tests/functional/common/test_args.py | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 openstackclient/tests/functional/common/test_args.py diff --git a/openstackclient/tests/functional/common/test_args.py b/openstackclient/tests/functional/common/test_args.py new file mode 100644 index 0000000000..dd1fd0fb2c --- /dev/null +++ b/openstackclient/tests/functional/common/test_args.py @@ -0,0 +1,79 @@ +# 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 json + +from openstackclient.tests.functional import base + + +class ArgumentTests(base.TestCase): + """Functional tests for command line arguments""" + + def test_default_auth_type(self): + cmd_output = json.loads(self.openstack( + 'configuration show -f json', + cloud='', + )) + self.assertIsNotNone(cmd_output) + self.assertIn( + 'auth_type', + cmd_output.keys(), + ) + self.assertEqual( + 'password', + cmd_output['auth_type'], + ) + + def test_auth_type_none(self): + cmd_output = json.loads(self.openstack( + 'configuration show -f json', + cloud=None, + )) + self.assertIsNotNone(cmd_output) + self.assertIn( + 'auth_type', + cmd_output.keys(), + ) + self.assertEqual( + 'none', + cmd_output['auth_type'], + ) + + def test_auth_type_token_endpoint_opt(self): + cmd_output = json.loads(self.openstack( + 'configuration show -f json --os-auth-type token_endpoint', + cloud=None, + )) + self.assertIsNotNone(cmd_output) + self.assertIn( + 'auth_type', + cmd_output.keys(), + ) + self.assertEqual( + 'token_endpoint', + cmd_output['auth_type'], + ) + + def test_auth_type_password_opt(self): + cmd_output = json.loads(self.openstack( + 'configuration show -f json --os-auth-type password', + cloud=None, + )) + self.assertIsNotNone(cmd_output) + self.assertIn( + 'auth_type', + cmd_output.keys(), + ) + self.assertEqual( + 'password', + cmd_output['auth_type'], + ) From 03a2accb2f13f82b1090e1ae6f154bf263e7e252 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 17 May 2019 20:06:44 -0500 Subject: [PATCH 2098/3095] Format aggregate command fields and de-race functional tests Rename metadata to property in all aggregate commands Beef up functional tests to reduce street racing Change-Id: I4598da73b85a954f3e6a3981db21891b45d9548c Signed-off-by: Dean Troyer --- openstackclient/compute/v2/aggregate.py | 46 ++++++++++++++- .../functional/compute/v2/test_aggregate.py | 59 ++++++++++++++++--- .../tests/unit/compute/v2/test_aggregate.py | 41 +++++++------ 3 files changed, 117 insertions(+), 29 deletions(-) diff --git a/openstackclient/compute/v2/aggregate.py b/openstackclient/compute/v2/aggregate.py index fa64647899..3834de1f0b 100644 --- a/openstackclient/compute/v2/aggregate.py +++ b/openstackclient/compute/v2/aggregate.py @@ -18,6 +18,7 @@ import logging +from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -58,6 +59,15 @@ def take_action(self, parsed_args): info = {} info.update(data._info) + + # Special mapping for columns to make the output easier to read: + # 'metadata' --> 'properties' + info.update( + { + 'hosts': format_columns.ListColumn(info.pop('hosts')), + 'properties': format_columns.DictColumn(info.pop('metadata')), + }, + ) return zip(*sorted(six.iteritems(info))) @@ -101,8 +111,20 @@ def take_action(self, parsed_args): parsed_args.property, )._info) - # TODO(dtroyer): re-format metadata field to properites as - # in the set command + # Special mapping for columns to make the output easier to read: + # 'metadata' --> 'properties' + hosts = None + properties = None + if 'hosts' in info.keys(): + hosts = format_columns.ListColumn(info.pop('hosts')) + if 'metadata' in info.keys(): + properties = format_columns.DictColumn(info.pop('metadata')) + info.update( + { + 'hosts': hosts, + 'properties': properties, + }, + ) return zip(*sorted(six.iteritems(info))) @@ -186,6 +208,10 @@ def take_action(self, parsed_args): return (column_headers, (utils.get_item_properties( s, columns, + formatters={ + 'Hosts': format_columns.ListColumn, + 'Metadata': format_columns.DictColumn, + }, ) for s in data)) @@ -220,6 +246,15 @@ def take_action(self, parsed_args): info = {} info.update(data._info) + + # Special mapping for columns to make the output easier to read: + # 'metadata' --> 'properties' + info.update( + { + 'hosts': format_columns.ListColumn(info.pop('hosts')), + 'properties': format_columns.DictColumn(info.pop('metadata')), + }, + ) return zip(*sorted(six.iteritems(info))) @@ -326,7 +361,12 @@ def take_action(self, parsed_args): # 'metadata' --> 'properties' data._info.update( { - 'properties': utils.format_dict(data._info.pop('metadata')), + 'hosts': format_columns.ListColumn( + data._info.pop('hosts') + ), + 'properties': format_columns.DictColumn( + data._info.pop('metadata') + ), }, ) diff --git a/openstackclient/tests/functional/compute/v2/test_aggregate.py b/openstackclient/tests/functional/compute/v2/test_aggregate.py index c591dd61fd..8a082e82ad 100644 --- a/openstackclient/tests/functional/compute/v2/test_aggregate.py +++ b/openstackclient/tests/functional/compute/v2/test_aggregate.py @@ -11,6 +11,7 @@ # under the License. import json +import time import uuid from openstackclient.tests.functional import base @@ -19,6 +20,32 @@ class AggregateTests(base.TestCase): """Functional tests for aggregate""" + @classmethod + def wait_for_status(cls, check_type, check_name, desired_status, + wait=120, interval=5, failures=None): + current_status = "notset" + if failures is None: + failures = ['error'] + total_sleep = 0 + while total_sleep < wait: + output = json.loads(cls.openstack( + check_type + ' show -f json ' + check_name)) + current_status = output['name'] + if (current_status == desired_status): + print('{} {} now has status {}' + .format(check_type, check_name, current_status)) + return + print('Checking {} {} Waiting for {} current status: {}' + .format(check_type, check_name, + desired_status, current_status)) + if current_status in failures: + raise Exception( + 'Current status {} of {} {} is one of failures {}' + .format(current_status, check_type, check_name, failures)) + time.sleep(interval) + total_sleep += interval + cls.assertOutput(desired_status, current_status) + def test_aggregate_crud(self): """Test create, delete multiple""" name1 = uuid.uuid4().hex @@ -36,12 +63,16 @@ def test_aggregate_crud(self): 'nova', cmd_output['availability_zone'] ) - # TODO(dtroyer): enable the following once the properties field - # is correctly formatted in the create output - # self.assertIn( - # "a='b'", - # cmd_output['properties'] - # ) + self.assertIn( + 'a', + cmd_output['properties'] + ) + self.wait_for_status('aggregate', name1, name1) + self.addCleanup( + self.openstack, + 'aggregate delete ' + name1, + fail_ok=True, + ) name2 = uuid.uuid4().hex cmd_output = json.loads(self.openstack( @@ -57,6 +88,12 @@ def test_aggregate_crud(self): 'external', cmd_output['availability_zone'] ) + self.wait_for_status('aggregate', name2, name2) + self.addCleanup( + self.openstack, + 'aggregate delete ' + name2, + fail_ok=True, + ) # Test aggregate set name3 = uuid.uuid4().hex @@ -69,6 +106,12 @@ def test_aggregate_crud(self): name1 ) self.assertOutput('', raw_output) + self.wait_for_status('aggregate', name3, name3) + self.addCleanup( + self.openstack, + 'aggregate delete ' + name3, + fail_ok=True, + ) cmd_output = json.loads(self.openstack( 'aggregate show -f json ' + @@ -83,11 +126,11 @@ def test_aggregate_crud(self): cmd_output['availability_zone'] ) self.assertIn( - "c='d'", + 'c', cmd_output['properties'] ) self.assertNotIn( - "a='b'", + 'a', cmd_output['properties'] ) diff --git a/openstackclient/tests/unit/compute/v2/test_aggregate.py b/openstackclient/tests/unit/compute/v2/test_aggregate.py index 3efe0dbd36..0937047cf0 100644 --- a/openstackclient/tests/unit/compute/v2/test_aggregate.py +++ b/openstackclient/tests/unit/compute/v2/test_aggregate.py @@ -16,6 +16,7 @@ import mock from mock import call +from osc_lib.cli import format_columns from osc_lib import exceptions from osc_lib import utils @@ -31,16 +32,16 @@ class TestAggregate(compute_fakes.TestComputev2): 'availability_zone', 'hosts', 'id', - 'metadata', 'name', + 'properties', ) data = ( fake_ag.availability_zone, - fake_ag.hosts, + format_columns.ListColumn(fake_ag.hosts), fake_ag.id, - fake_ag.metadata, fake_ag.name, + format_columns.DictColumn(fake_ag.metadata), ) def setUp(self): @@ -75,7 +76,7 @@ def test_aggregate_add_host(self): self.aggregate_mock.add_host.assert_called_once_with(self.fake_ag, parsed_args.host) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) class TestAggregateCreate(TestAggregate): @@ -99,7 +100,7 @@ def test_aggregate_create(self): self.aggregate_mock.create.assert_called_once_with(parsed_args.name, None) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_aggregate_create_with_zone(self): arglist = [ @@ -116,7 +117,7 @@ def test_aggregate_create_with_zone(self): self.aggregate_mock.create.assert_called_once_with(parsed_args.name, parsed_args.zone) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_aggregate_create_with_property(self): arglist = [ @@ -135,7 +136,7 @@ def test_aggregate_create_with_property(self): self.aggregate_mock.set_metadata.assert_called_once_with( self.fake_ag, parsed_args.property) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) class TestAggregateDelete(TestAggregate): @@ -234,8 +235,11 @@ class TestAggregateList(TestAggregate): TestAggregate.fake_ag.id, TestAggregate.fake_ag.name, TestAggregate.fake_ag.availability_zone, - {key: value for key, value in TestAggregate.fake_ag.metadata.items() - if key != 'availability_zone'}, + format_columns.DictColumn({ + key: value + for key, value in TestAggregate.fake_ag.metadata.items() + if key != 'availability_zone' + }), ), ) def setUp(self): @@ -250,7 +254,7 @@ def test_aggregate_list(self): columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.list_columns, columns) - self.assertEqual(self.list_data, tuple(data)) + self.assertItemEqual(self.list_data, tuple(data)) def test_aggregate_list_with_long(self): arglist = [ @@ -263,7 +267,7 @@ def test_aggregate_list_with_long(self): columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.list_columns_long, columns) - self.assertEqual(self.list_data_long, tuple(data)) + self.assertListItemEqual(self.list_data_long, tuple(data)) class TestAggregateRemoveHost(TestAggregate): @@ -290,7 +294,7 @@ def test_aggregate_add_host(self): self.aggregate_mock.remove_host.assert_called_once_with( self.fake_ag, parsed_args.host) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) class TestAggregateSet(TestAggregate): @@ -440,13 +444,14 @@ class TestAggregateShow(TestAggregate): data = ( TestAggregate.fake_ag.availability_zone, - TestAggregate.fake_ag.hosts, + format_columns.ListColumn(TestAggregate.fake_ag.hosts), TestAggregate.fake_ag.id, TestAggregate.fake_ag.name, - utils.format_dict( - {key: value - for key, value in TestAggregate.fake_ag.metadata.items() - if key != 'availability_zone'}), + format_columns.DictColumn({ + key: value + for key, value in TestAggregate.fake_ag.metadata.items() + if key != 'availability_zone' + }), ) def setUp(self): @@ -467,7 +472,7 @@ def test_aggregate_show(self): self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, tuple(data)) class TestAggregateUnset(TestAggregate): From 6fcc2608b17d84cf3699bb4a5bae692404393ca1 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Wed, 21 Aug 2019 12:20:41 -0500 Subject: [PATCH 2099/3095] Remove token_endpoint auth type The token_endpoint was a compatibility auth type to maintain support for the --url global option that dated back to the beginning of OpenStack CLI auth. The common keystoneauth library implements 'admin_token' which provides the same functionality using --endpoint rather than --url. Change-Id: I1b9fbb96e447889a41b705324725a2ffc8ecfd9f Signed-off-by: Dean Troyer --- doc/source/cli/authentication.rst | 9 --- doc/source/cli/backwards-incompatible.rst | 8 +++ openstackclient/api/auth_plugin.py | 61 ------------------- openstackclient/shell.py | 8 +-- .../tests/functional/common/test_args.py | 26 ++++---- .../tests/unit/common/test_clientmanager.py | 8 +-- openstackclient/tests/unit/test_shell.py | 22 +++---- setup.cfg | 3 - 8 files changed, 40 insertions(+), 105 deletions(-) delete mode 100644 openstackclient/api/auth_plugin.py diff --git a/doc/source/cli/authentication.rst b/doc/source/cli/authentication.rst index b153f54111..f8aaadf492 100644 --- a/doc/source/cli/authentication.rst +++ b/doc/source/cli/authentication.rst @@ -39,15 +39,6 @@ There are at least three authentication types that are always available: (described below as token/endpoint) in that a token and an authentication URL are supplied and the plugin retrieves a new token. [Required: ``--os-auth-url``, ``--os-token``] -* **Token/Endpoint**: This is the original token authentication (known as 'token - flow' in the early CLI documentation in the OpenStack wiki). It requires - a token and a direct endpoint that is used in the API call. The difference - from the new Token type is this token is used as-is, no call is made - to the Identity service from the client. This type is most often used to - bootstrap a Keystone server where the token is the ``admin_token`` configured - in ``keystone.conf``. It will also work with other services and a regular - scoped token such as one obtained from a ``token issue`` command. - [Required: ``--os-url``, ``--os-token``] * **Others**: Other authentication plugins such as SAML, Kerberos, and OAuth1.0 are under development and also supported. To use them, they must be selected by supplying the ``--os-auth-type`` option. diff --git a/doc/source/cli/backwards-incompatible.rst b/doc/source/cli/backwards-incompatible.rst index da956b232b..9d43754e01 100644 --- a/doc/source/cli/backwards-incompatible.rst +++ b/doc/source/cli/backwards-incompatible.rst @@ -96,6 +96,14 @@ Release 4.0 * Removed in: 4.0 * Commit: https://review.opendev.org/612751 +14. Remove 'Token/Endpoint' auth plugin support (type ``token_endpoint``). + This remained as a compatibility for the ``admin_token`` auth type to + support the ``--url`` global option. That option is also now removed, + use ``--endpoint`` instead. + + * Removed in: 4.0 + * Commit: https://review.opendev.org/ + Release 3.12 ------------ diff --git a/openstackclient/api/auth_plugin.py b/openstackclient/api/auth_plugin.py deleted file mode 100644 index a106b1ebc1..0000000000 --- a/openstackclient/api/auth_plugin.py +++ /dev/null @@ -1,61 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - -"""Authentication Plugin Library""" - -from keystoneauth1 import loading -from keystoneauth1 import token_endpoint - -from openstackclient.i18n import _ - - -class TokenEndpoint(loading.BaseLoader): - """Auth plugin to handle traditional token/endpoint usage - - Keystoneauth contains a Token plugin class that now correctly - handles the token/endpoint auth compatible with OSC. However, - the AdminToken loader deprecates the 'url' argument, which breaks - OSC compatibility, so make one that works. - """ - - @property - def plugin_class(self): - return token_endpoint.Token - - def load_from_options(self, url, token, **kwargs): - """A plugin for static authentication with an existing token - - :param string url: Service endpoint - :param string token: Existing token - """ - - return super(TokenEndpoint, self).load_from_options( - endpoint=url, - token=token, - ) - - def get_options(self): - """Return the legacy options""" - - options = [ - loading.Opt( - 'url', - help=_('Specific service endpoint to use'), - ), - loading.Opt( - 'token', - secret=True, - help=_('Authentication token to use'), - ), - ] - return options diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 22d8412cc7..91a15fd4dc 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -58,10 +58,10 @@ def build_option_parser(self, description, version): def _final_defaults(self): super(OpenStackShell, self)._final_defaults() - # Set the default plugin to token_endpoint if url and token are given - if (self.options.url and self.options.token): - # Use service token authentication - self._auth_type = 'token_endpoint' + # Set the default plugin to admin_token if endpoint and token are given + if (self.options.endpoint and self.options.token): + # Use token authentication + self._auth_type = 'admin_token' else: self._auth_type = 'password' diff --git a/openstackclient/tests/functional/common/test_args.py b/openstackclient/tests/functional/common/test_args.py index dd1fd0fb2c..02cad6c16a 100644 --- a/openstackclient/tests/functional/common/test_args.py +++ b/openstackclient/tests/functional/common/test_args.py @@ -12,6 +12,8 @@ import json +from tempest.lib import exceptions as tempest_exc + from openstackclient.tests.functional import base @@ -49,19 +51,17 @@ def test_auth_type_none(self): ) def test_auth_type_token_endpoint_opt(self): - cmd_output = json.loads(self.openstack( - 'configuration show -f json --os-auth-type token_endpoint', - cloud=None, - )) - self.assertIsNotNone(cmd_output) - self.assertIn( - 'auth_type', - cmd_output.keys(), - ) - self.assertEqual( - 'token_endpoint', - cmd_output['auth_type'], - ) + # Make sure token_endpoint is really gone + try: + self.openstack( + 'configuration show -f json --os-auth-type token_endpoint', + cloud=None, + ) + except tempest_exc.CommandFailed as e: + self.assertIn('--os-auth-type: invalid choice:', str(e)) + self.assertIn('token_endpoint', str(e)) + else: + self.fail('CommandFailed should be raised') def test_auth_type_password_opt(self): cmd_output = json.loads(self.openstack( diff --git a/openstackclient/tests/unit/common/test_clientmanager.py b/openstackclient/tests/unit/common/test_clientmanager.py index f15f9af1ff..a83b131800 100644 --- a/openstackclient/tests/unit/common/test_clientmanager.py +++ b/openstackclient/tests/unit/common/test_clientmanager.py @@ -28,19 +28,19 @@ def _clientmanager_class(self): """Allow subclasses to override the ClientManager class""" return clientmanager.ClientManager - def test_client_manager_token_endpoint(self): + def test_client_manager_admin_token(self): token_auth = { - 'url': fakes.AUTH_URL, + 'endpoint': fakes.AUTH_URL, 'token': fakes.AUTH_TOKEN, } client_manager = self._make_clientmanager( auth_args=token_auth, - auth_plugin_name='token_endpoint', + auth_plugin_name='admin_token', ) self.assertEqual( fakes.AUTH_URL, - client_manager._cli_options.config['auth']['url'], + client_manager._cli_options.config['auth']['endpoint'], ) self.assertEqual( fakes.AUTH_TOKEN, diff --git a/openstackclient/tests/unit/test_shell.py b/openstackclient/tests/unit/test_shell.py index 5d413e7e3c..31675c47e6 100644 --- a/openstackclient/tests/unit/test_shell.py +++ b/openstackclient/tests/unit/test_shell.py @@ -153,7 +153,7 @@ def setUp(self): # released in osc-lib self.shell_class = importutils.import_class(self.shell_class_name) - def _assert_token_endpoint_auth(self, cmd_options, default_args): + def _assert_admin_token_auth(self, cmd_options, default_args): with mock.patch( self.shell_class_name + ".initialize_app", self.app, @@ -172,9 +172,9 @@ def _assert_token_endpoint_auth(self, cmd_options, default_args): "token", ) self.assertEqual( - default_args.get("url", ''), - _shell.options.url, - "url", + default_args.get("endpoint", ''), + _shell.options.endpoint, + "endpoint", ) def _assert_token_auth(self, cmd_options, default_args): @@ -338,7 +338,7 @@ def setUp(self): super(TestShellTokenEndpointAuthEnv, self).setUp() env = { "OS_TOKEN": DEFAULT_TOKEN, - "OS_URL": DEFAULT_SERVICE_URL, + "OS_ENDPOINT": DEFAULT_SERVICE_URL, } self.useFixture(osc_lib_test_utils.EnvFixture(env.copy())) @@ -346,23 +346,23 @@ def test_env(self): flag = "" kwargs = { "token": DEFAULT_TOKEN, - "url": DEFAULT_SERVICE_URL, + "endpoint": DEFAULT_SERVICE_URL, } - self._assert_token_endpoint_auth(flag, kwargs) + self._assert_admin_token_auth(flag, kwargs) def test_only_token(self): flag = "--os-token xyzpdq" kwargs = { "token": "xyzpdq", - "url": DEFAULT_SERVICE_URL, + "endpoint": DEFAULT_SERVICE_URL, } self._assert_token_auth(flag, kwargs) def test_only_url(self): - flag = "--os-url http://cloud.local:555" + flag = "--os-endpoint http://cloud.local:555" kwargs = { "token": DEFAULT_TOKEN, - "url": "http://cloud.local:555", + "endpoint": "http://cloud.local:555", } self._assert_token_auth(flag, kwargs) @@ -371,7 +371,7 @@ def test_empty_auth(self): flag = "" kwargs = { "token": '', - "url": '', + "endpoint": '', } self._assert_token_auth(flag, kwargs) diff --git a/setup.cfg b/setup.cfg index a9f7de2ce1..a10a3d7647 100644 --- a/setup.cfg +++ b/setup.cfg @@ -27,9 +27,6 @@ packages = console_scripts = openstack = openstackclient.shell:main -keystoneauth1.plugin = - token_endpoint = openstackclient.api.auth_plugin:TokenEndpoint - openstack.cli = command_list = openstackclient.common.module:ListCommand module_list = openstackclient.common.module:ListModule From 6419533f436c6c369e05662c6ced26ad0bc68240 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Fri, 14 Jun 2019 11:51:52 +0100 Subject: [PATCH 2100/3095] Bump hacking version Pick up newer versions of this library. Thankfully no serious changes are needed. Change-Id: I69e523844529fc1c8aa0c1ce764182dbe29cfeb6 Signed-off-by: Stephen Finucane --- lower-constraints.txt | 7 +-- openstackclient/common/configuration.py | 2 +- openstackclient/compute/v2/hypervisor.py | 2 +- openstackclient/compute/v2/server.py | 58 +++++++++---------- .../network/v2/network_segment_range.py | 2 +- openstackclient/shell.py | 1 + .../tests/unit/compute/v2/fakes.py | 2 +- .../tests/unit/network/v2/fakes.py | 2 +- test-requirements.txt | 3 +- tox.ini | 5 +- 10 files changed, 41 insertions(+), 43 deletions(-) diff --git a/lower-constraints.txt b/lower-constraints.txt index c3dc477eaf..710ff0e1c4 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -22,14 +22,14 @@ extras==1.0.0 fasteners==0.7.0 fixtures==3.0.0 flake8-import-order==0.13 -flake8==2.5.5 +flake8==2.6.2 future==0.16.0 futurist==1.2.0 gitdb==0.6.4 GitPython==1.0.1 gnocchiclient==3.3.1 greenlet==0.4.10 -hacking==0.12.0 +hacking==1.1.0 httplib2==0.9.1 idna==2.6 iso8601==0.1.11 @@ -71,14 +71,13 @@ paramiko==2.0.0 Paste==2.0.2 PasteDeploy==1.5.0 pbr==2.0.0 -pep8==1.5.7 pika-pool==0.1.3 pika==0.10.0 ply==3.10 positional==1.2.1 prettytable==0.7.2 pyasn1==0.1.8 -pycodestyle==2.3.1 +pycodestyle==2.0.0 pycparser==2.18 pyflakes==0.8.1 pyinotify==0.9.6 diff --git a/openstackclient/common/configuration.py b/openstackclient/common/configuration.py index 57825bb056..ce646b1e45 100644 --- a/openstackclient/common/configuration.py +++ b/openstackclient/common/configuration.py @@ -52,6 +52,6 @@ def take_action(self, parsed_args): info = self.app.client_manager.get_configuration() for key, value in six.iteritems(info.pop('auth', {})): if parsed_args.mask and key.lower() in secret_opts: - value = REDACTED + value = REDACTED info['auth.' + key] = value return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/compute/v2/hypervisor.py b/openstackclient/compute/v2/hypervisor.py index 406aa9175b..0d367fee86 100644 --- a/openstackclient/compute/v2/hypervisor.py +++ b/openstackclient/compute/v2/hypervisor.py @@ -112,7 +112,7 @@ def take_action(self, parsed_args): # example: 17:37:14 up 2:33, 3 users, # load average: 0.33, 0.36, 0.34 m = re.match( - "\s*(.+)\sup\s+(.+),\s+(.+)\susers?,\s+load average:\s(.+)", + r"\s*(.+)\sup\s+(.+),\s+(.+)\susers?,\s+load average:\s(.+)", uptime['uptime']) if m: hypervisor["host_time"] = m.group(1) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 95c2f28a81..5922dc500b 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -79,37 +79,37 @@ def _format_servers_list_power_state(state): def _get_ip_address(addresses, address_type, ip_address_family): - # Old style addresses - if address_type in addresses: - for addy in addresses[address_type]: + # Old style addresses + if address_type in addresses: + for addy in addresses[address_type]: + if int(addy['version']) in ip_address_family: + return addy['addr'] + + # New style addresses + new_address_type = address_type + if address_type == 'public': + new_address_type = 'floating' + if address_type == 'private': + new_address_type = 'fixed' + for network in addresses: + for addy in addresses[network]: + # Case where it is list of strings + if isinstance(addy, six.string_types): + if new_address_type == 'fixed': + return addresses[network][0] + else: + return addresses[network][-1] + # Case where it is a dict + if 'OS-EXT-IPS:type' not in addy: + continue + if addy['OS-EXT-IPS:type'] == new_address_type: if int(addy['version']) in ip_address_family: return addy['addr'] - - # New style addresses - new_address_type = address_type - if address_type == 'public': - new_address_type = 'floating' - if address_type == 'private': - new_address_type = 'fixed' - for network in addresses: - for addy in addresses[network]: - # Case where it is list of strings - if isinstance(addy, six.string_types): - if new_address_type == 'fixed': - return addresses[network][0] - else: - return addresses[network][-1] - # Case where it is a dict - if 'OS-EXT-IPS:type' not in addy: - continue - if addy['OS-EXT-IPS:type'] == new_address_type: - if int(addy['version']) in ip_address_family: - return addy['addr'] - msg = _("ERROR: No %(type)s IP version %(family)s address found") - raise exceptions.CommandError( - msg % {"type": address_type, - "family": ip_address_family} - ) + msg = _("ERROR: No %(type)s IP version %(family)s address found") + raise exceptions.CommandError( + msg % {"type": address_type, + "family": ip_address_family} + ) def _prefix_checked_value(prefix): diff --git a/openstackclient/network/v2/network_segment_range.py b/openstackclient/network/v2/network_segment_range.py index 567b5b6eb9..b938bdd920 100644 --- a/openstackclient/network/v2/network_segment_range.py +++ b/openstackclient/network/v2/network_segment_range.py @@ -358,7 +358,7 @@ def take_action(self, parsed_args): parsed_args.used and _is_prop_empty(columns, props, 'used') or \ parsed_args.unused and \ not _is_prop_empty(columns, props, 'used'): - continue + continue if parsed_args.long: props = _update_additional_fields_from_props(columns, props) display_props += (props,) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 22d8412cc7..86aa032b61 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -209,5 +209,6 @@ def main(argv=None): return OpenStackShell().run(argv) + if __name__ == "__main__": sys.exit(main()) diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index ee7d49831f..7357e1430a 100644 --- a/openstackclient/tests/unit/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -505,7 +505,7 @@ def create_security_groups(attrs=None, count=2): @staticmethod def get_security_groups(security_groups=None, count=2): - """Get an iterable MagicMock object with a list of faked security groups. + """Get an iterable MagicMock with a list of faked security groups. If security groups list is provided, then initialize the Mock object with the list. Otherwise create one. diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index e41621a48e..1519bd4b8c 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -1344,7 +1344,7 @@ def create_security_group_rules(attrs=None, count=2): @staticmethod def get_security_group_rules(security_group_rules=None, count=2): - """Get an iterable Mock object with a list of faked security group rules. + """Get an iterable Mock with a list of faked security group rules. If security group rules list is provided, then initialize the Mock object with the list. Otherwise create one. diff --git a/test-requirements.txt b/test-requirements.txt index 4cb66cfdb6..64e7a0773b 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,8 +1,7 @@ # The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0 - +hacking>=1.1.0,<1.2.0 # Apache-2.0 coverage!=4.4,>=4.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD flake8-import-order==0.13 # LGPLv3 diff --git a/tox.ini b/tox.ini index eaf66bc493..a973b107f7 100644 --- a/tox.ini +++ b/tox.ini @@ -132,9 +132,8 @@ show-source = True # H203: Use assertIs(Not)None to check for None enable-extensions = H203 exclude = .git,.tox,dist,doc,*lib/python*,*egg,build,tools -# If 'ignore' is not set there are default errors and warnings that are set -# Doc: http://flake8.readthedocs.org/en/latest/config.html#default -ignore = __ +# W504 is disabled since you must choose between this or W503 +ignore = W504 import-order-style = pep8 application_import_names = openstackclient From f044016e296fd261f61eda69fb2f5ef87de6e0a9 Mon Sep 17 00:00:00 2001 From: LIU Yulong Date: Wed, 12 Sep 2018 06:18:04 +0800 Subject: [PATCH 2101/3095] Add floating IP Port Forwarding commands Add following commands: floating ip port forwarding create floating ip port forwarding delete floating ip port forwarding list floating ip port forwarding set floating ip port forwarding show Closes-Bug: #1811352 Change-Id: I6a5642e8acce28fc830410d4fa3180597b862761 --- .../floating-ip-port-forwarding.rst | 173 ++++++ .../network/v2/floating_ip_port_forwarding.py | 371 +++++++++++++ .../tests/unit/network/v2/fakes.py | 77 +++ .../v2/test_floating_ip_port_forwarding.py | 502 ++++++++++++++++++ ...tforwarding-commands-6e4d8ace698ee308.yaml | 9 + setup.cfg | 6 + 6 files changed, 1138 insertions(+) create mode 100644 doc/source/cli/command-objects/floating-ip-port-forwarding.rst create mode 100644 openstackclient/network/v2/floating_ip_port_forwarding.py create mode 100644 openstackclient/tests/unit/network/v2/test_floating_ip_port_forwarding.py create mode 100644 releasenotes/notes/add-fip-portforwarding-commands-6e4d8ace698ee308.yaml diff --git a/doc/source/cli/command-objects/floating-ip-port-forwarding.rst b/doc/source/cli/command-objects/floating-ip-port-forwarding.rst new file mode 100644 index 0000000000..5012787aaf --- /dev/null +++ b/doc/source/cli/command-objects/floating-ip-port-forwarding.rst @@ -0,0 +1,173 @@ +=========================== +floating ip port forwarding +=========================== + +Network v2 + +floating ip port forwarding create +---------------------------------- + +Create floating IP Port Forwarding + +.. program:: floating ip port forwarding create +.. code:: bash + + openstack floating ip port forwarding create + --internal-ip-address + --port + --internal-protocol-port + --external-protocol-port + --protocol + + + +.. describe:: --internal-ip-address + + The fixed IPv4 address of the network port associated + to the floating IP port forwarding + +.. describe:: --port + + The name or ID of the network port associated to the + floating IP port forwarding + +.. describe:: --internal-protocol-port + + The protocol port number of the network port fixed + IPv4 address associated to the floating IP port + forwarding + +.. describe:: --external-protocol-port + + The protocol port number of the port forwarding's + floating IP address + +.. describe:: --protocol + + The protocol used in the floating IP port forwarding, + for instance: TCP, UDP + +.. describe:: + + Floating IP that the port forwarding belongs to (IP + address or ID) + +floating ip port forwarding delete +---------------------------------- + +Delete floating IP Port Forwarding(s) + +.. program:: floating ip port forwarding delete +.. code:: bash + + openstack floating ip port forwarding delete + [ ...] + +.. describe:: + + Floating IP that the port forwarding belongs to (IP + address or ID) + +.. describe:: + + The ID of the floating IP port forwarding + +floating ip port forwarding list +-------------------------------- + +List floating IP Port Forwarding(s) + +.. program:: floating ip port forwarding list +.. code:: bash + + openstack floating ip port forwarding list + [--port ] + [--external-protocol-port ] + [--protocol protocol] + + +.. option:: --port + + The ID of the network port associated to the floating + IP port forwarding + +.. option:: --protocol + + The IP protocol used in the floating IP port + forwarding + +.. describe:: + + Floating IP that the port forwarding belongs to (IP + address or ID) + +floating ip port forwarding set +------------------------------- + +Set floating IP Port Forwarding properties + +.. program:: floating ip port forwarding set +.. code:: bash + + openstack floating ip port forwarding set + [--port ] + [--internal-ip-address ] + [--internal-protocol-port ] + [--external-protocol-port ] + [--protocol ] + + + +.. option:: --port + + The ID of the network port associated to the floating + IP port forwarding + +.. option:: --internal-ip-address + + The fixed IPv4 address of the network port associated + to the floating IP port forwarding + +.. option:: --internal-protocol-port + + The TCP/UDP/other protocol port number of the network + port fixed IPv4 address associated to the floating IP + port forwarding + +.. option:: --external-protocol-port + + The TCP/UDP/other protocol port number of the port + forwarding's floating IP address + +.. option:: --protocol + + The IP protocol used in the floating IP port + forwarding + +.. describe:: + + Floating IP that the port forwarding belongs to (IP + address or ID) + +.. describe:: + + The ID of the floating IP port forwarding + +floating ip port forwarding show +-------------------------------- + +Display floating IP Port Forwarding details + +.. program:: floating ip port forwarding show +.. code:: bash + + openstack floating ip show + +.. describe:: + + Floating IP that the port forwarding belongs to (IP + address or ID) + +.. describe:: + + The ID of the floating IP port forwarding diff --git a/openstackclient/network/v2/floating_ip_port_forwarding.py b/openstackclient/network/v2/floating_ip_port_forwarding.py new file mode 100644 index 0000000000..f94bcc065a --- /dev/null +++ b/openstackclient/network/v2/floating_ip_port_forwarding.py @@ -0,0 +1,371 @@ +# 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. +# + +"""Floating IP Port Forwarding action implementations""" +import logging + +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils + +from openstackclient.i18n import _ +from openstackclient.network import sdk_utils + + +LOG = logging.getLogger(__name__) + + +def _get_columns(item): + column_map = { + 'tenant_id': 'project_id', + } + return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + + +class CreateFloatingIPPortForwarding(command.ShowOne): + _description = _("Create floating IP port forwarding") + + def get_parser(self, prog_name): + parser = super(CreateFloatingIPPortForwarding, + self).get_parser(prog_name) + parser.add_argument( + '--internal-ip-address', + required=True, + metavar='', + help=_("The fixed IPv4 address of the network " + "port associated to the floating IP port forwarding") + ) + parser.add_argument( + '--port', + metavar='', + required=True, + help=_("The name or ID of the network port associated " + "to the floating IP port forwarding") + ) + parser.add_argument( + '--internal-protocol-port', + type=int, + metavar='', + required=True, + help=_("The protocol port number " + "of the network port fixed IPv4 address " + "associated to the floating IP port forwarding") + ) + parser.add_argument( + '--external-protocol-port', + type=int, + metavar='', + required=True, + help=_("The protocol port number of " + "the port forwarding's floating IP address") + ) + parser.add_argument( + '--protocol', + metavar='', + required=True, + help=_("The protocol used in the floating IP " + "port forwarding, for instance: TCP, UDP") + ) + parser.add_argument( + 'floating_ip', + metavar='', + help=_("Floating IP that the port forwarding belongs to " + "(IP address or ID)") + ) + return parser + + def take_action(self, parsed_args): + attrs = {} + client = self.app.client_manager.network + floating_ip = client.find_ip( + parsed_args.floating_ip, + ignore_missing=False, + ) + + if parsed_args.internal_protocol_port is not None: + if (parsed_args.internal_protocol_port <= 0 or + parsed_args.internal_protocol_port > 65535): + msg = _("The port number range is <1-65535>") + raise exceptions.CommandError(msg) + attrs['internal_port'] = parsed_args.internal_protocol_port + + if parsed_args.external_protocol_port is not None: + if (parsed_args.external_protocol_port <= 0 or + parsed_args.external_protocol_port > 65535): + msg = _("The port number range is <1-65535>") + raise exceptions.CommandError(msg) + attrs['external_port'] = parsed_args.external_protocol_port + + if parsed_args.port: + port = client.find_port(parsed_args.port, + ignore_missing=False) + attrs['internal_port_id'] = port.id + attrs['internal_ip_address'] = parsed_args.internal_ip_address + attrs['protocol'] = parsed_args.protocol + + obj = client.create_floating_ip_port_forwarding( + floating_ip.id, + **attrs + ) + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns) + return (display_columns, data) + + +class DeleteFloatingIPPortForwarding(command.Command): + _description = _("Delete floating IP port forwarding") + + def get_parser(self, prog_name): + parser = super(DeleteFloatingIPPortForwarding, + self).get_parser(prog_name) + parser.add_argument( + 'floating_ip', + metavar='', + help=_("Floating IP that the port forwarding belongs to " + "(IP address or ID)") + ) + parser.add_argument( + 'port_forwarding_id', + nargs="+", + metavar="", + help=_("The ID of the floating IP port forwarding(s) to delete") + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + floating_ip = client.find_ip( + parsed_args.floating_ip, + ignore_missing=False, + ) + result = 0 + + for port_forwarding_id in parsed_args.port_forwarding_id: + try: + client.delete_floating_ip_port_forwarding( + floating_ip.id, + port_forwarding_id, + ignore_missing=False, + ) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete floating IP port forwarding " + "'%(port_forwarding_id)s': %(e)s"), + {'port_forwarding_id': port_forwarding_id, 'e': e}) + if result > 0: + total = len(parsed_args.port_forwarding_id) + msg = (_("%(result)s of %(total)s Port forwarding failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) + + +class ListFloatingIPPortForwarding(command.Lister): + _description = _("List floating IP port forwarding") + + def get_parser(self, prog_name): + parser = super(ListFloatingIPPortForwarding, + self).get_parser(prog_name) + parser.add_argument( + 'floating_ip', + metavar='', + help=_("Floating IP that the port forwarding belongs to " + "(IP address or ID)") + ) + parser.add_argument( + '--port', + metavar='', + help=_("Filter the list result by the ID or name of " + "the internal network port") + ) + parser.add_argument( + '--external-protocol-port', + metavar='', + dest='external_protocol_port', + help=_("Filter the list result by the " + "protocol port number of the floating IP") + ) + parser.add_argument( + '--protocol', + metavar='protocol', + help=_("Filter the list result by the port protocol") + ) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + + columns = ( + 'id', + 'internal_port_id', + 'internal_ip_address', + 'internal_port', + 'external_port', + 'protocol', + ) + headers = ( + 'ID', + 'Internal Port ID', + 'Internal IP Address', + 'Internal Port', + 'External Port', + 'Protocol', + ) + + query = {} + + if parsed_args.port: + port = client.find_port(parsed_args.port, + ignore_missing=False) + query['internal_port_id'] = port.id + if parsed_args.external_protocol_port is not None: + query['external_port'] = parsed_args.external_protocol_port + if parsed_args.protocol is not None: + query['protocol'] = parsed_args.protocol + + obj = client.find_ip( + parsed_args.floating_ip, + ignore_missing=False, + ) + + data = client.floating_ip_port_forwardings(obj, **query) + + return (headers, + (utils.get_item_properties( + s, columns, + formatters={}, + ) for s in data)) + + +class SetFloatingIPPortForwarding(command.Command): + _description = _("Set floating IP Port Forwarding Properties") + + def get_parser(self, prog_name): + parser = super(SetFloatingIPPortForwarding, + self).get_parser(prog_name) + parser.add_argument( + 'floating_ip', + metavar='', + help=_("Floating IP that the port forwarding belongs to " + "(IP address or ID)") + ) + parser.add_argument( + 'port_forwarding_id', + metavar='', + help=_("The ID of the floating IP port forwarding") + ) + parser.add_argument( + '--port', + metavar='', + help=_("The ID of the network port associated to " + "the floating IP port forwarding") + ) + parser.add_argument( + '--internal-ip-address', + metavar='', + help=_("The fixed IPv4 address of the network port " + "associated to the floating IP port forwarding") + ) + parser.add_argument( + '--internal-protocol-port', + metavar='', + type=int, + help=_("The TCP/UDP/other protocol port number of the " + "network port fixed IPv4 address associated to " + "the floating IP port forwarding") + ) + parser.add_argument( + '--external-protocol-port', + type=int, + metavar='', + help=_("The TCP/UDP/other protocol port number of the " + "port forwarding's floating IP address") + ) + parser.add_argument( + '--protocol', + metavar='', + choices=['tcp', 'udp'], + help=_("The IP protocol used in the floating IP port forwarding") + ) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + floating_ip = client.find_ip( + parsed_args.floating_ip, + ignore_missing=False, + ) + + attrs = {} + if parsed_args.port: + port = client.find_port(parsed_args.port, + ignore_missing=False) + attrs['internal_port_id'] = port.id + + if parsed_args.internal_ip_address: + attrs['internal_ip_address'] = parsed_args.internal_ip_address + if parsed_args.internal_protocol_port is not None: + if (parsed_args.internal_protocol_port <= 0 or + parsed_args.internal_protocol_port > 65535): + msg = _("The port number range is <1-65535>") + raise exceptions.CommandError(msg) + attrs['internal_port'] = parsed_args.internal_protocol_port + + if parsed_args.external_protocol_port is not None: + if (parsed_args.external_protocol_port <= 0 or + parsed_args.external_protocol_port > 65535): + msg = _("The port number range is <1-65535>") + raise exceptions.CommandError(msg) + attrs['external_port'] = parsed_args.external_protocol_port + + if parsed_args.protocol: + attrs['protocol'] = parsed_args.protocol + + client.update_floating_ip_port_forwarding( + floating_ip.id, parsed_args.port_forwarding_id, **attrs) + + +class ShowFloatingIPPortForwarding(command.ShowOne): + _description = _("Display floating IP Port Forwarding details") + + def get_parser(self, prog_name): + parser = super(ShowFloatingIPPortForwarding, + self).get_parser(prog_name) + parser.add_argument( + 'floating_ip', + metavar='', + help=_("Floating IP that the port forwarding belongs to " + "(IP address or ID)") + ) + parser.add_argument( + 'port_forwarding_id', + metavar="", + help=_("The ID of the floating IP port forwarding") + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + floating_ip = client.find_ip( + parsed_args.floating_ip, + ignore_missing=False, + ) + obj = client.find_floating_ip_port_forwarding( + floating_ip, + parsed_args.port_forwarding_id, + ignore_missing=False, + ) + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns) + return (display_columns, data) diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index e41621a48e..27a7485a40 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -1816,3 +1816,80 @@ def create_one_net_detailed_quota(attrs=None): info=copy.deepcopy(quota_attrs), loaded=True) return quota + + +class FakeFloatingIPPortForwarding(object): + """"Fake one or more Port forwarding""" + + @staticmethod + def create_one_port_forwarding(attrs=None): + """Create a fake Port Forwarding. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object with name, id, etc. + """ + attrs = attrs or {} + floatingip_id = ( + attrs.get('floatingip_id') or'floating-ip-id-' + uuid.uuid4().hex + ) + # Set default attributes. + port_forwarding_attrs = { + 'id': uuid.uuid4().hex, + 'floatingip_id': floatingip_id, + 'internal_port_id': 'internal-port-id-' + uuid.uuid4().hex, + 'internal_ip_address': '192.168.1.2', + 'internal_port': randint(1, 65535), + 'external_port': randint(1, 65535), + 'protocol': 'tcp', + } + + # Overwrite default attributes. + port_forwarding_attrs.update(attrs) + + port_forwarding = fakes.FakeResource( + info=copy.deepcopy(port_forwarding_attrs), + loaded=True + ) + return port_forwarding + + @staticmethod + def create_port_forwardings(attrs=None, count=2): + """Create multiple fake Port Forwarding. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of Port Forwarding rule to fake + :return: + A list of FakeResource objects faking the Port Forwardings + """ + port_forwardings = [] + for i in range(0, count): + port_forwardings.append( + FakeFloatingIPPortForwarding.create_one_port_forwarding(attrs) + ) + return port_forwardings + + @staticmethod + def get_port_forwardings(port_forwardings=None, count=2): + """Get a list of faked Port Forwardings. + + If port forwardings list is provided, then initialize the Mock object + with the list. Otherwise create one. + + :param List port forwardings: + A list of FakeResource objects faking port forwardings + :param int count: + The number of Port Forwardings to fake + :return: + An iterable Mock object with side_effect set to a list of faked + Port Forwardings + """ + if port_forwardings is None: + port_forwardings = ( + FakeFloatingIPPortForwarding.create_port_forwardings(count) + ) + + return mock.Mock(side_effect=port_forwardings) diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip_port_forwarding.py b/openstackclient/tests/unit/network/v2/test_floating_ip_port_forwarding.py new file mode 100644 index 0000000000..b51158be37 --- /dev/null +++ b/openstackclient/tests/unit/network/v2/test_floating_ip_port_forwarding.py @@ -0,0 +1,502 @@ +# Copyright (c) 2018 China Telecom 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 mock import call + +from osc_lib import exceptions + +from openstackclient.network.v2 import floating_ip_port_forwarding +from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes_v2 +from openstackclient.tests.unit.network.v2 import fakes as network_fakes +from openstackclient.tests.unit import utils as tests_utils + + +class TestFloatingIPPortForwarding(network_fakes.TestNetworkV2): + + def setUp(self): + super(TestFloatingIPPortForwarding, self).setUp() + self.network = self.app.client_manager.network + self.floating_ip = (network_fakes.FakeFloatingIP. + create_one_floating_ip()) + self.port = network_fakes.FakePort.create_one_port() + self.project = identity_fakes_v2.FakeProject.create_one_project() + self.network.find_port = mock.Mock(return_value=self.port) + + +class TestCreateFloatingIPPortForwarding(TestFloatingIPPortForwarding): + + def setUp(self): + project_id = '' + super(TestCreateFloatingIPPortForwarding, self).setUp() + self.new_port_forwarding = ( + network_fakes.FakeFloatingIPPortForwarding. + create_one_port_forwarding( + attrs={ + 'internal_port_id': self.port.id, + 'floatingip_id': self.floating_ip.id, + } + ) + ) + self.network.create_floating_ip_port_forwarding = mock.Mock( + return_value=self.new_port_forwarding) + + self.network.find_ip = mock.Mock( + return_value=self.floating_ip + ) + + # Get the command object to test + self.cmd = floating_ip_port_forwarding.CreateFloatingIPPortForwarding( + self.app, self.namespace) + + self.columns = ( + 'external_port', + 'floatingip_id', + 'id', + 'internal_ip_address', + 'internal_port', + 'internal_port_id', + 'project_id', + 'protocol' + ) + + self.data = ( + self.new_port_forwarding.external_port, + self.new_port_forwarding.floatingip_id, + self.new_port_forwarding.id, + self.new_port_forwarding.internal_ip_address, + self.new_port_forwarding.internal_port, + self.new_port_forwarding.internal_port_id, + project_id, + self.new_port_forwarding.protocol, + ) + + def test_create_no_options(self): + arglist = [] + verifylist = [] + + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_create_all_options(self): + arglist = [ + '--port', self.new_port_forwarding.internal_port_id, + '--internal-protocol-port', + str(self.new_port_forwarding.internal_port), + '--external-protocol-port', + str(self.new_port_forwarding.external_port), + '--protocol', self.new_port_forwarding.protocol, + self.new_port_forwarding.floatingip_id, + '--internal-ip-address', + self.new_port_forwarding.internal_ip_address, + ] + verifylist = [ + ('port', self.new_port_forwarding.internal_port_id), + ('internal_protocol_port', self.new_port_forwarding.internal_port), + ('external_protocol_port', self.new_port_forwarding.external_port), + ('protocol', self.new_port_forwarding.protocol), + ('floating_ip', self.new_port_forwarding.floatingip_id), + ('internal_ip_address', self.new_port_forwarding. + internal_ip_address), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_floating_ip_port_forwarding.\ + assert_called_once_with( + self.new_port_forwarding.floatingip_id, + **{ + 'external_port': self.new_port_forwarding.external_port, + 'internal_ip_address': self.new_port_forwarding. + internal_ip_address, + 'internal_port': self.new_port_forwarding.internal_port, + 'internal_port_id': self.new_port_forwarding. + internal_port_id, + 'protocol': self.new_port_forwarding.protocol, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestDeleteFloatingIPPortForwarding(TestFloatingIPPortForwarding): + + def setUp(self): + super(TestDeleteFloatingIPPortForwarding, self).setUp() + self._port_forwarding = ( + network_fakes.FakeFloatingIPPortForwarding.create_port_forwardings( + count=2, attrs={ + 'floatingip_id': self.floating_ip.id, + } + ) + ) + self.network.delete_floating_ip_port_forwarding = mock.Mock( + return_value=None + ) + + self.network.find_ip = mock.Mock( + return_value=self.floating_ip + ) + # Get the command object to test + self.cmd = floating_ip_port_forwarding.DeleteFloatingIPPortForwarding( + self.app, self.namespace) + + def test_port_forwarding_delete(self): + arglist = [ + self.floating_ip.id, + self._port_forwarding[0].id, + ] + verifylist = [ + ('floating_ip', self.floating_ip.id), + ('port_forwarding_id', [self._port_forwarding[0].id]), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.network.delete_floating_ip_port_forwarding.\ + assert_called_once_with( + self.floating_ip.id, + self._port_forwarding[0].id, + ignore_missing=False + ) + + self.assertIsNone(result) + + def test_multi_port_forwardings_delete(self): + arglist = [] + pf_id = [] + + arglist.append(str(self.floating_ip)) + + for a in self._port_forwarding: + arglist.append(a.id) + pf_id.append(a.id) + + verifylist = [ + ('floating_ip', str(self.floating_ip)), + ('port_forwarding_id', pf_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [] + for a in self._port_forwarding: + calls.append(call(a.floatingip_id, a.id, ignore_missing=False)) + + self.network.delete_floating_ip_port_forwarding.assert_has_calls(calls) + self.assertIsNone(result) + + def test_multi_port_forwarding_delete_with_exception(self): + arglist = [ + self.floating_ip.id, + self._port_forwarding[0].id, + 'unexist_port_forwarding_id', + ] + verifylist = [ + ('floating_ip', self.floating_ip.id), + ('port_forwarding_id', + [self._port_forwarding[0].id, 'unexist_port_forwarding_id']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + delete_mock_result = [None, exceptions.CommandError] + + self.network.delete_floating_ip_port_forwarding = ( + mock.MagicMock(side_effect=delete_mock_result) + ) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual( + '1 of 2 Port forwarding failed to delete.', + str(e) + ) + + self.network.delete_floating_ip_port_forwarding.\ + assert_any_call( + self.floating_ip.id, + 'unexist_port_forwarding_id', + ignore_missing=False + ) + self.network.delete_floating_ip_port_forwarding.\ + assert_any_call( + self.floating_ip.id, + self._port_forwarding[0].id, + ignore_missing=False + ) + + +class TestListFloatingIPPortForwarding(TestFloatingIPPortForwarding): + + columns = ( + 'ID', + 'Internal Port ID', + 'Internal IP Address', + 'Internal Port', + 'External Port', + 'Protocol' + ) + + def setUp(self): + super(TestListFloatingIPPortForwarding, self).setUp() + self.port_forwardings = ( + network_fakes.FakeFloatingIPPortForwarding.create_port_forwardings( + count=3, attrs={ + 'internal_port_id': self.port.id, + 'floatingip_id': self.floating_ip.id, + } + ) + ) + self.data = [] + for port_forwarding in self.port_forwardings: + self.data.append(( + port_forwarding.id, + port_forwarding.internal_port_id, + port_forwarding.internal_ip_address, + port_forwarding.internal_port, + port_forwarding.external_port, + port_forwarding.protocol, + )) + self.network.floating_ip_port_forwardings = mock.Mock( + return_value=self.port_forwardings + ) + self.network.find_ip = mock.Mock( + return_value=self.floating_ip + ) + # Get the command object to test + self.cmd = floating_ip_port_forwarding.ListFloatingIPPortForwarding( + self.app, + self.namespace + ) + + def test_port_forwarding_list(self): + arglist = [ + self.floating_ip.id + ] + verifylist = [ + ('floating_ip', self.floating_ip.id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.floating_ip_port_forwardings.assert_called_once_with( + self.floating_ip, + **{} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_port_forwarding_list_all_options(self): + arglist = [ + '--port', self.port_forwardings[0].internal_port_id, + '--external-protocol-port', + str(self.port_forwardings[0].external_port), + '--protocol', self.port_forwardings[0].protocol, + self.port_forwardings[0].floatingip_id, + ] + + verifylist = [ + ('port', self.port_forwardings[0].internal_port_id), + ('external_protocol_port', + str(self.port_forwardings[0].external_port)), + ('protocol', self.port_forwardings[0].protocol), + ('floating_ip', self.port_forwardings[0].floatingip_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + query = { + 'internal_port_id': self.port_forwardings[0].internal_port_id, + 'external_port': str(self.port_forwardings[0].external_port), + 'protocol': self.port_forwardings[0].protocol, + } + + self.network.floating_ip_port_forwardings.assert_called_once_with( + self.floating_ip, + **query + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + +class TestSetFloatingIPPortForwarding(TestFloatingIPPortForwarding): + + # The Port Forwarding to set. + def setUp(self): + super(TestSetFloatingIPPortForwarding, self).setUp() + self._port_forwarding = ( + network_fakes.FakeFloatingIPPortForwarding. + create_one_port_forwarding( + attrs={ + 'floatingip_id': self.floating_ip.id, + } + ) + ) + self.network.update_floating_ip_port_forwarding = mock.Mock( + return_value=None + ) + + self.network.find_floating_ip_port_forwarding = mock.Mock( + return_value=self._port_forwarding) + self.network.find_ip = mock.Mock( + return_value=self.floating_ip + ) + # Get the command object to test + self.cmd = floating_ip_port_forwarding.SetFloatingIPPortForwarding( + self.app, + self.namespace + ) + + def test_set_nothing(self): + arglist = [ + self._port_forwarding.floatingip_id, + self._port_forwarding.id, + ] + verifylist = [ + ('floating_ip', self._port_forwarding.floatingip_id), + ('port_forwarding_id', self._port_forwarding.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = {} + self.network.update_floating_ip_port_forwarding.assert_called_with( + self._port_forwarding.floatingip_id, + self._port_forwarding.id, + **attrs + ) + self.assertIsNone(result) + + def test_set_all_thing(self): + arglist = [ + '--port', self.port.id, + '--internal-ip-address', 'new_internal_ip_address', + '--internal-protocol-port', '100', + '--external-protocol-port', '200', + '--protocol', 'tcp', + self._port_forwarding.floatingip_id, + self._port_forwarding.id, + ] + verifylist = [ + ('port', self.port.id), + ('internal_ip_address', 'new_internal_ip_address'), + ('internal_protocol_port', 100), + ('external_protocol_port', 200), + ('protocol', 'tcp'), + ('floating_ip', self._port_forwarding.floatingip_id), + ('port_forwarding_id', self._port_forwarding.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + attrs = { + 'internal_port_id': self.port.id, + 'internal_ip_address': 'new_internal_ip_address', + 'internal_port': 100, + 'external_port': 200, + 'protocol': 'tcp', + } + self.network.update_floating_ip_port_forwarding.assert_called_with( + self._port_forwarding.floatingip_id, + self._port_forwarding.id, + **attrs + ) + self.assertIsNone(result) + + +class TestShowFloatingIPPortForwarding(TestFloatingIPPortForwarding): + + # The port forwarding to show. + columns = ( + 'external_port', + 'floatingip_id', + 'id', + 'internal_ip_address', + 'internal_port', + 'internal_port_id', + 'project_id', + 'protocol', + ) + + def setUp(self): + project_id = '' + super(TestShowFloatingIPPortForwarding, self).setUp() + self._port_forwarding = ( + network_fakes.FakeFloatingIPPortForwarding. + create_one_port_forwarding( + attrs={ + 'floatingip_id': self.floating_ip.id, + } + ) + ) + self.data = ( + self._port_forwarding.external_port, + self._port_forwarding.floatingip_id, + self._port_forwarding.id, + self._port_forwarding.internal_ip_address, + self._port_forwarding.internal_port, + self._port_forwarding.internal_port_id, + project_id, + self._port_forwarding.protocol, + ) + self.network.find_floating_ip_port_forwarding = mock.Mock( + return_value=self._port_forwarding + ) + self.network.find_ip = mock.Mock( + return_value=self.floating_ip + ) + # Get the command object to test + self.cmd = floating_ip_port_forwarding.ShowFloatingIPPortForwarding( + self.app, + self.namespace + ) + + def test_show_no_options(self): + arglist = [] + verifylist = [] + + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_show_default_options(self): + arglist = [ + self._port_forwarding.floatingip_id, + self._port_forwarding.id, + ] + verifylist = [ + ('floating_ip', self._port_forwarding.floatingip_id), + ('port_forwarding_id', self._port_forwarding.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.find_floating_ip_port_forwarding.assert_called_once_with( + self.floating_ip, + self._port_forwarding.id, + ignore_missing=False + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(list(self.data), list(data)) diff --git a/releasenotes/notes/add-fip-portforwarding-commands-6e4d8ace698ee308.yaml b/releasenotes/notes/add-fip-portforwarding-commands-6e4d8ace698ee308.yaml new file mode 100644 index 0000000000..87175e4f6e --- /dev/null +++ b/releasenotes/notes/add-fip-portforwarding-commands-6e4d8ace698ee308.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + Add floating IP Port Forwarding commands: + ``floating ip port forwarding create``, + ``floating ip port forwarding delete``, + ``floating ip port forwarding list``, + ``floating ip port forwarding set`` and + ``floating ip port forwarding show``. diff --git a/setup.cfg b/setup.cfg index a9f7de2ce1..80d7772653 100644 --- a/setup.cfg +++ b/setup.cfg @@ -384,6 +384,12 @@ openstack.network.v2 = floating_ip_pool_list = openstackclient.network.v2.floating_ip_pool:ListFloatingIPPool + floating_ip_port_forwarding_create = openstackclient.network.v2.floating_ip_port_forwarding:CreateFloatingIPPortForwarding + floating_ip_port_forwarding_delete = openstackclient.network.v2.floating_ip_port_forwarding:DeleteFloatingIPPortForwarding + floating_ip_port_forwarding_list = openstackclient.network.v2.floating_ip_port_forwarding:ListFloatingIPPortForwarding + floating_ip_port_forwarding_set = openstackclient.network.v2.floating_ip_port_forwarding:SetFloatingIPPortForwarding + floating_ip_port_forwarding_show = openstackclient.network.v2.floating_ip_port_forwarding:ShowFloatingIPPortForwarding + ip_availability_list = openstackclient.network.v2.ip_availability:ListIPAvailability ip_availability_show = openstackclient.network.v2.ip_availability:ShowIPAvailability From 6ee7b8d138e07bfc37c5cd887f7afa49cdabb02f Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Fri, 30 Aug 2019 12:53:15 -0500 Subject: [PATCH 2102/3095] Format location columns in network commands These return a Munch from the SDK, which can be handled exactly like a dict so do that. Note that the location column has a nested project dict in the return value, this is addressed separately in osc_lib.format_columns in https://review.opendev.org/#/c/679474/. Change-Id: I99a6d192749a4ac76777f72be8118261c0521cb0 Signed-off-by: Dean Troyer --- openstackclient/network/v2/address_scope.py | 10 ++++++++-- openstackclient/network/v2/floating_ip.py | 2 ++ openstackclient/network/v2/ip_availability.py | 2 ++ openstackclient/network/v2/network.py | 1 + openstackclient/network/v2/network_agent.py | 1 + .../v2/network_auto_allocated_topology.py | 17 ++++++++++++----- openstackclient/network/v2/network_flavor.py | 10 ++++++++-- .../network/v2/network_flavor_profile.py | 10 ++++++++-- openstackclient/network/v2/network_meter.py | 10 ++++++++-- .../network/v2/network_meter_rule.py | 10 ++++++++-- .../network/v2/network_qos_policy.py | 10 ++++++++-- openstackclient/network/v2/network_qos_rule.py | 10 ++++++++-- .../network/v2/network_qos_rule_type.py | 8 +++++++- openstackclient/network/v2/network_rbac.py | 10 ++++++++-- openstackclient/network/v2/network_segment.py | 10 ++++++++-- .../network/v2/network_segment_range.py | 9 +++++++-- openstackclient/network/v2/port.py | 1 + openstackclient/network/v2/router.py | 1 + openstackclient/network/v2/security_group.py | 3 +++ .../network/v2/security_group_rule.py | 10 ++++++++-- openstackclient/network/v2/subnet.py | 1 + openstackclient/network/v2/subnet_pool.py | 1 + 22 files changed, 119 insertions(+), 28 deletions(-) diff --git a/openstackclient/network/v2/address_scope.py b/openstackclient/network/v2/address_scope.py index 71c1a9afb7..7efbb6311e 100644 --- a/openstackclient/network/v2/address_scope.py +++ b/openstackclient/network/v2/address_scope.py @@ -15,6 +15,7 @@ import logging +from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -27,6 +28,11 @@ LOG = logging.getLogger(__name__) +_formatters = { + 'location': format_columns.DictColumn, +} + + def _get_columns(item): column_map = { 'is_shared': 'shared', @@ -100,7 +106,7 @@ def take_action(self, parsed_args): attrs = _get_attrs(self.app.client_manager, parsed_args) obj = client.create_address_scope(**attrs) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns, formatters={}) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return (display_columns, data) @@ -289,6 +295,6 @@ def take_action(self, parsed_args): parsed_args.address_scope, ignore_missing=False) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns, formatters={}) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return (display_columns, data) diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index 8ac8e10703..e0aa0435ed 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -13,6 +13,7 @@ """IP Floating action implementations""" +from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import utils @@ -24,6 +25,7 @@ _formatters = { + 'location': format_columns.DictColumn, 'port_details': utils.format_dict, } diff --git a/openstackclient/network/v2/ip_availability.py b/openstackclient/network/v2/ip_availability.py index ddc88e557e..c026baa0cc 100644 --- a/openstackclient/network/v2/ip_availability.py +++ b/openstackclient/network/v2/ip_availability.py @@ -21,7 +21,9 @@ from openstackclient.identity import common as identity_common from openstackclient.network import sdk_utils + _formatters = { + 'location': format_columns.DictColumn, 'subnet_ip_availability': format_columns.ListDictColumn, } diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 63aec71497..09f3556c43 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -40,6 +40,7 @@ def human_readable(self): 'subnet_ids': format_columns.ListColumn, 'admin_state_up': AdminStateColumn, 'is_admin_state_up': AdminStateColumn, + 'location': format_columns.DictColumn, 'router:external': RouterExternalColumn, 'is_router_external': RouterExternalColumn, 'availability_zones': format_columns.ListColumn, diff --git a/openstackclient/network/v2/network_agent.py b/openstackclient/network/v2/network_agent.py index 1678485434..a6ed36299c 100644 --- a/openstackclient/network/v2/network_agent.py +++ b/openstackclient/network/v2/network_agent.py @@ -43,6 +43,7 @@ def human_readable(self): 'alive': AliveColumn, 'admin_state_up': AdminStateColumn, 'is_admin_state_up': AdminStateColumn, + 'location': format_columns.DictColumn, 'configurations': format_columns.DictColumn, } diff --git a/openstackclient/network/v2/network_auto_allocated_topology.py b/openstackclient/network/v2/network_auto_allocated_topology.py index 36f392006e..f6070a0277 100644 --- a/openstackclient/network/v2/network_auto_allocated_topology.py +++ b/openstackclient/network/v2/network_auto_allocated_topology.py @@ -15,6 +15,7 @@ import logging +from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import utils @@ -25,6 +26,11 @@ LOG = logging.getLogger(__name__) +_formatters = { + 'location': format_columns.DictColumn, +} + + def _get_columns(item): column_map = { 'tenant_id': 'project_id', @@ -93,16 +99,17 @@ def check_resource_topology(self, client, parsed_args): obj = client.validate_auto_allocated_topology(parsed_args.project) columns = _format_check_resource_columns() - data = utils.get_item_properties(_format_check_resource(obj), - columns, - formatters={}) - + data = utils.get_item_properties( + _format_check_resource(obj), + columns, + formatters=_formatters, + ) return (columns, data) def get_topology(self, client, parsed_args): obj = client.get_auto_allocated_topology(parsed_args.project) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns, formatters={}) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return (display_columns, data) def take_action(self, parsed_args): diff --git a/openstackclient/network/v2/network_flavor.py b/openstackclient/network/v2/network_flavor.py index c9d368bfc1..355d04c1af 100644 --- a/openstackclient/network/v2/network_flavor.py +++ b/openstackclient/network/v2/network_flavor.py @@ -15,6 +15,7 @@ import logging +from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -27,6 +28,11 @@ LOG = logging.getLogger(__name__) +_formatters = { + 'location': format_columns.DictColumn, +} + + def _get_columns(item): column_map = { 'is_enabled': 'enabled', @@ -136,7 +142,7 @@ def take_action(self, parsed_args): attrs = _get_attrs(self.app.client_manager, parsed_args) obj = client.create_flavor(**attrs) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns, formatters={}) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return (display_columns, data) @@ -300,5 +306,5 @@ def take_action(self, parsed_args): client = self.app.client_manager.network obj = client.find_flavor(parsed_args.flavor, ignore_missing=False) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return display_columns, data diff --git a/openstackclient/network/v2/network_flavor_profile.py b/openstackclient/network/v2/network_flavor_profile.py index 6cf0c4124d..492fd432a9 100644 --- a/openstackclient/network/v2/network_flavor_profile.py +++ b/openstackclient/network/v2/network_flavor_profile.py @@ -13,6 +13,7 @@ import logging +from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -25,6 +26,11 @@ LOG = logging.getLogger(__name__) +_formatters = { + 'location': format_columns.DictColumn, +} + + def _get_columns(item): column_map = { 'is_enabled': 'enabled', @@ -110,7 +116,7 @@ def take_action(self, parsed_args): obj = client.create_service_profile(**attrs) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns, formatters={}) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return (display_columns, data) @@ -246,5 +252,5 @@ def take_action(self, parsed_args): obj = client.find_service_profile(parsed_args.flavor_profile, ignore_missing=False) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return (display_columns, data) diff --git a/openstackclient/network/v2/network_meter.py b/openstackclient/network/v2/network_meter.py index df0e1da119..cde7a30403 100644 --- a/openstackclient/network/v2/network_meter.py +++ b/openstackclient/network/v2/network_meter.py @@ -15,6 +15,7 @@ import logging +from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -26,6 +27,11 @@ LOG = logging.getLogger(__name__) +_formatters = { + 'location': format_columns.DictColumn, +} + + def _get_columns(item): column_map = { 'is_shared': 'shared', @@ -102,7 +108,7 @@ def take_action(self, parsed_args): attrs = _get_attrs(self.app.client_manager, parsed_args) obj = client.create_metering_label(**attrs) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns, formatters={}) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return (display_columns, data) @@ -186,5 +192,5 @@ def take_action(self, parsed_args): obj = client.find_metering_label(parsed_args.meter, ignore_missing=False) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return display_columns, data diff --git a/openstackclient/network/v2/network_meter_rule.py b/openstackclient/network/v2/network_meter_rule.py index 49ff9e1b0e..5f31255a69 100644 --- a/openstackclient/network/v2/network_meter_rule.py +++ b/openstackclient/network/v2/network_meter_rule.py @@ -15,6 +15,7 @@ import logging +from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -26,6 +27,11 @@ LOG = logging.getLogger(__name__) +_formatters = { + 'location': format_columns.DictColumn, +} + + def _get_columns(item): column_map = { 'tenant_id': 'project_id', @@ -116,7 +122,7 @@ def take_action(self, parsed_args): attrs = _get_attrs(self.app.client_manager, parsed_args) obj = client.create_metering_label_rule(**attrs) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns, formatters={}) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return (display_columns, data) @@ -199,5 +205,5 @@ def take_action(self, parsed_args): obj = client.find_metering_label_rule(parsed_args.meter_rule_id, ignore_missing=False) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return display_columns, data diff --git a/openstackclient/network/v2/network_qos_policy.py b/openstackclient/network/v2/network_qos_policy.py index fd5ff93771..1622de4a7a 100644 --- a/openstackclient/network/v2/network_qos_policy.py +++ b/openstackclient/network/v2/network_qos_policy.py @@ -15,6 +15,7 @@ import logging +from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -27,6 +28,11 @@ LOG = logging.getLogger(__name__) +_formatters = { + 'location': format_columns.DictColumn, +} + + def _get_columns(item): column_map = { 'is_shared': 'shared', @@ -119,7 +125,7 @@ def take_action(self, parsed_args): attrs = _get_attrs(self.app.client_manager, parsed_args) obj = client.create_qos_policy(**attrs) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns, formatters={}) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return (display_columns, data) @@ -279,5 +285,5 @@ def take_action(self, parsed_args): obj = client.find_qos_policy(parsed_args.policy, ignore_missing=False) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return (display_columns, data) diff --git a/openstackclient/network/v2/network_qos_rule.py b/openstackclient/network/v2/network_qos_rule.py index 28c5600aa6..d74beda70f 100644 --- a/openstackclient/network/v2/network_qos_rule.py +++ b/openstackclient/network/v2/network_qos_rule.py @@ -15,6 +15,7 @@ import itertools +from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -46,6 +47,11 @@ ACTION_SHOW = 'get' +_formatters = { + 'location': format_columns.DictColumn, +} + + def _get_columns(item): column_map = { 'tenant_id': 'project_id', @@ -208,7 +214,7 @@ def take_action(self, parsed_args): msg = (_('Failed to create Network QoS rule: %(e)s') % {'e': e}) raise exceptions.CommandError(msg) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return display_columns, data @@ -358,5 +364,5 @@ def take_action(self, parsed_args): {'rule': rule_id, 'e': e}) raise exceptions.CommandError(msg) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return display_columns, data diff --git a/openstackclient/network/v2/network_qos_rule_type.py b/openstackclient/network/v2/network_qos_rule_type.py index 7b92c8ad4b..e842944cce 100644 --- a/openstackclient/network/v2/network_qos_rule_type.py +++ b/openstackclient/network/v2/network_qos_rule_type.py @@ -13,6 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. +from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import utils @@ -20,6 +21,11 @@ from openstackclient.network import sdk_utils +_formatters = { + 'location': format_columns.DictColumn, +} + + def _get_columns(item): column_map = { "type": "rule_type_name", @@ -65,5 +71,5 @@ def take_action(self, parsed_args): client = self.app.client_manager.network obj = client.get_qos_rule_type(parsed_args.rule_type) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return display_columns, data diff --git a/openstackclient/network/v2/network_rbac.py b/openstackclient/network/v2/network_rbac.py index 140c837e38..1781193f61 100644 --- a/openstackclient/network/v2/network_rbac.py +++ b/openstackclient/network/v2/network_rbac.py @@ -15,6 +15,7 @@ import logging +from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -27,6 +28,11 @@ LOG = logging.getLogger(__name__) +_formatters = { + 'location': format_columns.DictColumn, +} + + def _get_columns(item): column_map = { 'target_tenant': 'target_project_id', @@ -136,7 +142,7 @@ def take_action(self, parsed_args): attrs = _get_attrs(self.app.client_manager, parsed_args) obj = client.create_rbac_policy(**attrs) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return display_columns, data @@ -293,5 +299,5 @@ def take_action(self, parsed_args): obj = client.find_rbac_policy(parsed_args.rbac_policy, ignore_missing=False) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return display_columns, data diff --git a/openstackclient/network/v2/network_segment.py b/openstackclient/network/v2/network_segment.py index c1a672e2d5..5899dc697b 100644 --- a/openstackclient/network/v2/network_segment.py +++ b/openstackclient/network/v2/network_segment.py @@ -15,6 +15,7 @@ import logging +from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -26,6 +27,11 @@ LOG = logging.getLogger(__name__) +_formatters = { + 'location': format_columns.DictColumn, +} + + def _get_columns(item): return sdk_utils.get_osc_show_columns_for_sdk_resource(item, {}) @@ -90,7 +96,7 @@ def take_action(self, parsed_args): attrs['segmentation_id'] = parsed_args.segment obj = client.create_segment(**attrs) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return (display_columns, data) @@ -242,5 +248,5 @@ def take_action(self, parsed_args): ignore_missing=False ) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return (display_columns, data) diff --git a/openstackclient/network/v2/network_segment_range.py b/openstackclient/network/v2/network_segment_range.py index b938bdd920..f03bcc1c0b 100644 --- a/openstackclient/network/v2/network_segment_range.py +++ b/openstackclient/network/v2/network_segment_range.py @@ -19,6 +19,7 @@ import itertools import logging +from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -31,6 +32,10 @@ LOG = logging.getLogger(__name__) +_formatters = { + 'location': format_columns.DictColumn, +} + def _get_columns(item): return sdk_utils.get_osc_show_columns_for_sdk_resource(item, {}) @@ -212,7 +217,7 @@ def take_action(self, parsed_args): attrs['physical_network'] = parsed_args.physical_network obj = network_client.create_network_segment_range(**attrs) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns) + data = utils.get_item_properties(obj, columns, formatters=_formatters) data = _update_additional_fields_from_props(columns, props=data) return (display_columns, data) @@ -451,6 +456,6 @@ def take_action(self, parsed_args): ignore_missing=False ) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns) + data = utils.get_item_properties(obj, columns, formatters=_formatters) data = _update_additional_fields_from_props(columns, props=data) return (display_columns, data) diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index e6bfe8537f..4d7e518975 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -51,6 +51,7 @@ def human_readable(self): 'dns_assignment': format_columns.ListDictColumn, 'extra_dhcp_opts': format_columns.ListDictColumn, 'fixed_ips': format_columns.ListDictColumn, + 'location': format_columns.DictColumn, 'security_group_ids': format_columns.ListColumn, 'tags': format_columns.ListColumn, } diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 11b012e67e..02da50c38e 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -61,6 +61,7 @@ def human_readable(self): 'external_gateway_info': RouterInfoColumn, 'availability_zones': format_columns.ListColumn, 'availability_zone_hints': format_columns.ListColumn, + 'location': format_columns.DictColumn, 'routes': RoutesColumn, 'tags': format_columns.ListColumn, } diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py index e389473828..38b4e97a80 100644 --- a/openstackclient/network/v2/security_group.py +++ b/openstackclient/network/v2/security_group.py @@ -16,6 +16,7 @@ import argparse from cliff import columns as cliff_columns +from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import utils import six @@ -77,11 +78,13 @@ def human_readable(self): _formatters_network = { + 'location': format_columns.DictColumn, 'security_group_rules': NetworkSecurityGroupRulesColumn, } _formatters_compute = { + 'location': format_columns.DictColumn, 'rules': ComputeSecurityGroupRulesColumn, } diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index dbea747300..15f099b1eb 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -16,6 +16,7 @@ import argparse import logging +from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib import exceptions from osc_lib import utils @@ -31,6 +32,11 @@ LOG = logging.getLogger(__name__) +_formatters = { + 'location': format_columns.DictColumn, +} + + def _format_security_group_rule_show(obj): data = network_utils.transform_compute_security_group_rule(obj) return zip(*sorted(six.iteritems(data))) @@ -337,7 +343,7 @@ def take_action_network(self, client, parsed_args): # Create and show the security group rule. obj = client.create_security_group_rule(**attrs) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return (display_columns, data) def take_action_compute(self, client, parsed_args): @@ -596,7 +602,7 @@ def take_action_network(self, client, parsed_args): if not obj['remote_ip_prefix']: obj['remote_ip_prefix'] = _format_remote_ip_prefix(obj) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return (display_columns, data) def take_action_compute(self, client, parsed_args): diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 1f0c2d9478..c53688615f 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -61,6 +61,7 @@ def human_readable(self): 'allocation_pools': AllocationPoolsColumn, 'dns_nameservers': format_columns.ListColumn, 'host_routes': HostRoutesColumn, + 'location': format_columns.DictColumn, 'service_types': format_columns.ListColumn, 'tags': format_columns.ListColumn, } diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index 7ece263a36..d5a15475ef 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -42,6 +42,7 @@ def _get_columns(item): _formatters = { + 'location': format_columns.DictColumn, 'prefixes': format_columns.ListColumn, 'tags': format_columns.ListColumn, } From a88e95873e3a5b2f5f7e8cb7ee82f426d9c320ae Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 29 Aug 2019 00:01:37 -0500 Subject: [PATCH 2103/3095] Bump min osc-lib to 1.14.0 This is required for osc4 and the movement of functions out of OSC. Change-Id: I690954b6dccb11dd1a4f512b6777d645de5191f9 Signed-off-by: Dean Troyer --- lower-constraints.txt | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lower-constraints.txt b/lower-constraints.txt index 710ff0e1c4..2593dd309a 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -54,7 +54,7 @@ openstacksdk==0.17.0 os-client-config==1.28.0 os-service-types==1.2.0 os-testr==1.0.0 -osc-lib==1.10.0 +osc-lib==1.14.0 oslo.concurrency==3.26.0 oslo.config==5.2.0 oslo.context==2.19.2 diff --git a/requirements.txt b/requirements.txt index cf67906646..1044c39ee2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ Babel!=2.4.0,>=2.3.4 # BSD cliff!=2.9.0,>=2.8.0 # Apache-2.0 keystoneauth1>=3.6.2 # Apache-2.0 openstacksdk>=0.17.0 # Apache-2.0 -osc-lib>=1.10.0 # Apache-2.0 +osc-lib>=1.14.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 python-glanceclient>=2.8.0 # Apache-2.0 From 31c47adebb68b7ef508310bc9651fec0a26cd818 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Sat, 31 Aug 2019 09:03:41 -0500 Subject: [PATCH 2104/3095] Remove races in floating ip functional tests Multiple subnets with the same name are occasionally created when running tests in parallel. Change-Id: Ifb85e39ee53b529e2b97abf782c7fba93d48e9e2 Signed-off-by: Dean Troyer --- .../functional/network/v2/test_floating_ip.py | 120 ++++++++++-------- 1 file changed, 67 insertions(+), 53 deletions(-) diff --git a/openstackclient/tests/functional/network/v2/test_floating_ip.py b/openstackclient/tests/functional/network/v2/test_floating_ip.py index f189c2da6c..9d109f878b 100644 --- a/openstackclient/tests/functional/network/v2/test_floating_ip.py +++ b/openstackclient/tests/functional/network/v2/test_floating_ip.py @@ -24,12 +24,9 @@ class FloatingIpTests(common.NetworkTests): def setUpClass(cls): common.NetworkTests.setUpClass() if cls.haz_network: + # Create common networks that all tests share cls.EXTERNAL_NETWORK_NAME = uuid.uuid4().hex - cls.EXTERNAL_SUBNET_NAME = uuid.uuid4().hex cls.PRIVATE_NETWORK_NAME = uuid.uuid4().hex - cls.PRIVATE_SUBNET_NAME = uuid.uuid4().hex - cls.ROUTER = uuid.uuid4().hex - cls.PORT_NAME = uuid.uuid4().hex # Create a network for the floating ip json_output = json.loads(cls.openstack( @@ -46,56 +43,10 @@ def setUpClass(cls): )) cls.private_network_id = json_output["id"] - # Try random subnet range for subnet creating - # Because we can not determine ahead of time what subnets are - # already in use, possibly by another test running in parallel, - # try 4 times - for i in range(4): - # Make a random subnet - cls.external_subnet = ".".join(map( - str, - (random.randint(0, 223) for _ in range(3)) - )) + ".0/26" - cls.private_subnet = ".".join(map( - str, - (random.randint(0, 223) for _ in range(3)) - )) + ".0/26" - try: - # Create a subnet for the network - json_output = json.loads(cls.openstack( - 'subnet create -f json ' + - '--network ' + cls.EXTERNAL_NETWORK_NAME + ' ' + - '--subnet-range ' + cls.external_subnet + ' ' + - cls.EXTERNAL_SUBNET_NAME - )) - cls.external_subnet_id = json_output["id"] - # Create a subnet for the private network - json_output = json.loads(cls.openstack( - 'subnet create -f json ' + - '--network ' + cls.PRIVATE_NETWORK_NAME + ' ' + - '--subnet-range ' + cls.private_subnet + ' ' + - cls.PRIVATE_SUBNET_NAME - )) - cls.private_subnet_id = json_output["id"] - except Exception: - if (i == 3): - # raise the exception at the last time - raise - pass - else: - # break and no longer retry if create successfully - break - @classmethod def tearDownClass(cls): try: if cls.haz_network: - del_output = cls.openstack( - 'subnet delete ' + - cls.EXTERNAL_SUBNET_NAME + ' ' + - cls.PRIVATE_SUBNET_NAME - ) - cls.assertOutput('', del_output) del_output = cls.openstack( 'network delete ' + cls.EXTERNAL_NETWORK_NAME + ' ' + @@ -114,11 +65,50 @@ def setUp(self): # Verify setup self.assertIsNotNone(self.external_network_id) self.assertIsNotNone(self.private_network_id) - self.assertIsNotNone(self.external_subnet_id) - self.assertIsNotNone(self.private_subnet_id) + + def _create_subnet(self, network_name, subnet_name): + subnet_id = None + + # Try random subnet range for subnet creating + # Because we can not determine ahead of time what subnets are + # already in use, possibly by another test running in parallel, + # try 4 times + for i in range(4): + # Make a random subnet + subnet = ".".join(map( + str, + (random.randint(0, 223) for _ in range(3)) + )) + ".0/26" + try: + # Create a subnet for the network + json_output = json.loads(self.openstack( + 'subnet create -f json ' + + '--network ' + network_name + ' ' + + '--subnet-range ' + subnet + ' ' + + subnet_name + )) + self.assertIsNotNone(json_output["id"]) + subnet_id = json_output["id"] + except Exception: + if (i == 3): + # raise the exception at the last time + raise + pass + else: + # break and no longer retry if create successfully + break + return subnet_id def test_floating_ip_delete(self): """Test create, delete multiple""" + + # Subnets must exist even if not directly referenced here + ext_subnet_id = self._create_subnet( + self.EXTERNAL_NETWORK_NAME, + "ext-test-delete" + ) + self.addCleanup(self.openstack, 'subnet delete ' + ext_subnet_id) + json_output = json.loads(self.openstack( 'floating ip create -f json ' + '--description aaaa ' + @@ -151,6 +141,14 @@ def test_floating_ip_delete(self): def test_floating_ip_list(self): """Test create defaults, list filters, delete""" + + # Subnets must exist even if not directly referenced here + ext_subnet_id = self._create_subnet( + self.EXTERNAL_NETWORK_NAME, + "ext-test-delete" + ) + self.addCleanup(self.openstack, 'subnet delete ' + ext_subnet_id) + json_output = json.loads(self.openstack( 'floating ip create -f json ' + '--description aaaa ' + @@ -237,6 +235,22 @@ def test_floating_ip_list(self): def test_floating_ip_set_and_unset_port(self): """Test Floating IP Set and Unset port""" + + # Subnets must exist even if not directly referenced here + ext_subnet_id = self._create_subnet( + self.EXTERNAL_NETWORK_NAME, + "ext-test-delete" + ) + self.addCleanup(self.openstack, 'subnet delete ' + ext_subnet_id) + priv_subnet_id = self._create_subnet( + self.PRIVATE_NETWORK_NAME, + "priv-test-delete" + ) + self.addCleanup(self.openstack, 'subnet delete ' + priv_subnet_id) + + self.ROUTER = uuid.uuid4().hex + self.PORT_NAME = uuid.uuid4().hex + json_output = json.loads(self.openstack( 'floating ip create -f json ' + '--description aaaa ' + @@ -253,7 +267,7 @@ def test_floating_ip_set_and_unset_port(self): json_output = json.loads(self.openstack( 'port create -f json ' + '--network ' + self.PRIVATE_NETWORK_NAME + ' ' + - '--fixed-ip subnet=' + self.PRIVATE_SUBNET_NAME + ' ' + + '--fixed-ip subnet=' + priv_subnet_id + ' ' + self.PORT_NAME )) self.assertIsNotNone(json_output["id"]) From 7549d260aa8724f2104a84cfb2cbd88e24b4778c Mon Sep 17 00:00:00 2001 From: Hongbin Lu Date: Sun, 1 Sep 2019 22:35:58 +0000 Subject: [PATCH 2105/3095] Bump lower constraint of python-zunclient Projects that depends on python-zunclient should use the latest version as lower constraint. Change-Id: Idc865788f35427cc0f2926b31089ec4097831334 --- lower-constraints.txt | 2 +- test-requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lower-constraints.txt b/lower-constraints.txt index 2593dd309a..7993a18325 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -109,7 +109,7 @@ python-subunit==1.0.0 python-swiftclient==3.2.0 python-troveclient==2.2.0 python-zaqarclient==1.0.0 -python-zunclient==1.3.0 +python-zunclient==3.4.0 pytz==2013.6 PyYAML==3.12 repoze.lru==0.7 diff --git a/test-requirements.txt b/test-requirements.txt index 64e7a0773b..4db30c7fca 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -38,4 +38,4 @@ python-searchlightclient>=1.0.0 #Apache-2.0 python-senlinclient>=1.1.0 # Apache-2.0 python-troveclient>=2.2.0 # Apache-2.0 python-zaqarclient>=1.0.0 # Apache-2.0 -python-zunclient>=1.3.0 # Apache-2.0 +python-zunclient>=3.4.0 # Apache-2.0 From 7c1b6a799e0ac6fea511a2cf1e97aebb2f94e0d6 Mon Sep 17 00:00:00 2001 From: Takashi Kajinami Date: Mon, 19 Aug 2019 11:13:14 +0900 Subject: [PATCH 2106/3095] Add parent project filter for listing projects This patch introduces a new option --parent into project list, to specify a parent project to filter projects which has the given project as their parent. Depends-on: https://review.opendev.org/#/c/677101 Change-Id: I6725262cf040e0ec6ceca9cf0462ce59224049c6 --- doc/source/cli/command-objects/project.rst | 7 ++++++ openstackclient/identity/v3/project.py | 10 +++++++++ .../tests/unit/identity/v3/test_project.py | 22 +++++++++++++++++++ ...t-option-to-projects-10382a7176993366.yaml | 5 +++++ 4 files changed, 44 insertions(+) create mode 100644 releasenotes/notes/add-parent-list-option-to-projects-10382a7176993366.yaml diff --git a/doc/source/cli/command-objects/project.rst b/doc/source/cli/command-objects/project.rst index 6891a79a48..ac7e8cd1ce 100644 --- a/doc/source/cli/command-objects/project.rst +++ b/doc/source/cli/command-objects/project.rst @@ -102,6 +102,7 @@ List projects openstack project list [--domain ] + [--parent ] [--user ] [--my-projects] [--long] @@ -115,6 +116,12 @@ List projects .. versionadded:: 3 +.. option:: --parent + + Filter projects whose parent is :option:`\ <--parent>` (name or ID) + + .. versionadded:: 3 + .. option:: --user Filter projects by :option:`\ <--user>` (name or ID) diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index e819a0a89b..073fb6df11 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -185,6 +185,11 @@ def get_parser(self, prog_name): metavar='', help=_('Filter projects by (name or ID)'), ) + parser.add_argument( + '--parent', + metavar='', + help=_('Filter projects whose parent is (name or ID)'), + ) parser.add_argument( '--user', metavar='', @@ -226,6 +231,11 @@ def take_action(self, parsed_args): parsed_args.domain).id kwargs['domain'] = domain_id + if parsed_args.parent: + parent_id = common.find_project(identity_client, + parsed_args.parent).id + kwargs['parent'] = parent_id + if parsed_args.user: if parsed_args.domain: user_id = utils.find_resource(identity_client.users, diff --git a/openstackclient/tests/unit/identity/v3/test_project.py b/openstackclient/tests/unit/identity/v3/test_project.py index 266da22775..db27fedcdf 100644 --- a/openstackclient/tests/unit/identity/v3/test_project.py +++ b/openstackclient/tests/unit/identity/v3/test_project.py @@ -645,6 +645,28 @@ def test_project_list_domain_no_perms(self): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) + def test_project_list_parent(self): + self.parent = identity_fakes.FakeProject.create_one_project() + self.project = identity_fakes.FakeProject.create_one_project( + attrs={'domain_id': self.domain.id, 'parent_id': self.parent.id}) + + arglist = [ + '--parent', self.parent.id, + ] + verifylist = [ + ('parent', self.parent.id), + ] + + self.projects_mock.get.return_value = self.parent + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.projects_mock.list.assert_called_with(parent=self.parent.id) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) + def test_project_list_sort(self): self.projects_mock.list.return_value = self.projects diff --git a/releasenotes/notes/add-parent-list-option-to-projects-10382a7176993366.yaml b/releasenotes/notes/add-parent-list-option-to-projects-10382a7176993366.yaml new file mode 100644 index 0000000000..fe1801179b --- /dev/null +++ b/releasenotes/notes/add-parent-list-option-to-projects-10382a7176993366.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``--parent`` option to ``project list`` command to filter projects + by the specified parent project. From fcd46acb69250205e18274cc9a59ab5f46d075c3 Mon Sep 17 00:00:00 2001 From: zhangbailin Date: Fri, 14 Jun 2019 16:40:40 +0800 Subject: [PATCH 2107/3095] Microversion 2.77: Support Specifying AZ to unshelve This patch adds a new parameter ``--availability-zone`` to ``openstack server unshelve`` command. This can help users to specify an ``availability_zone`` to unshelve a shelve offloaded server from 2.77 microversion. Depends-On: https://review.opendev.org/679295 Implements: blueprint support-specifying-az-when-restore-shelved-server Change-Id: Ia431e27c2a17fe16466707cc362532860ecf22df --- lower-constraints.txt | 2 +- openstackclient/compute/v2/server.py | 28 ++++++++-- .../tests/unit/compute/v2/test_server.py | 54 +++++++++++++++++++ ...store-shelved-server-16e864223d51b50a.yaml | 7 +++ requirements.txt | 2 +- 5 files changed, 87 insertions(+), 6 deletions(-) create mode 100644 releasenotes/notes/bp-support-specifying-az-when-restore-shelved-server-16e864223d51b50a.yaml diff --git a/lower-constraints.txt b/lower-constraints.txt index 7993a18325..a84643caed 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -99,7 +99,7 @@ python-mimeparse==1.6.0 python-mistralclient==3.1.0 python-muranoclient==0.8.2 python-neutronclient==6.7.0 -python-novaclient==14.2.0 +python-novaclient==15.0.0 python-octaviaclient==1.3.0 python-rsdclient==0.1.0 python-saharaclient==1.4.0 diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 9ba918123e..538c9c4fa0 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -2811,12 +2811,32 @@ def get_parser(self, prog_name): nargs='+', help=_('Server(s) to unshelve (name or ID)'), ) + parser.add_argument( + '--availability-zone', + default=None, + help=_('Name of the availability zone in which to unshelve a ' + 'SHELVED_OFFLOADED server (supported by ' + '--os-compute-api-version 2.77 or above)'), + ) return parser def take_action(self, parsed_args): compute_client = self.app.client_manager.compute + support_az = compute_client.api_version >= api_versions.APIVersion( + '2.77') + if not support_az and parsed_args.availability_zone: + msg = _("--os-compute-api-version 2.77 or greater is required " + "to support the '--availability-zone' option.") + raise exceptions.CommandError(msg) + for server in parsed_args.server: - utils.find_resource( - compute_client.servers, - server, - ).unshelve() + if support_az: + utils.find_resource( + compute_client.servers, + server + ).unshelve(availability_zone=parsed_args.availability_zone) + else: + utils.find_resource( + compute_client.servers, + server, + ).unshelve() diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 0793116af6..5c98188a76 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -109,6 +109,10 @@ def run_method_with_servers(self, method_name, server_count): version = self.app.client_manager.compute.api_version if version >= api_versions.APIVersion('2.73'): method.assert_called_with(reason=None) + elif method_name == 'unshelve': + version = self.app.client_manager.compute.api_version + if version >= api_versions.APIVersion('2.77'): + method.assert_called_with(availability_zone=None) else: method.assert_called_with() else: @@ -4777,6 +4781,56 @@ def test_unshelve_one_server(self): def test_unshelve_multi_servers(self): self.run_method_with_servers('unshelve', 3) + def test_unshelve_server_with_specified_az(self): + server = compute_fakes.FakeServer.create_one_server() + arglist = [ + server.id, + '--availability-zone', "foo-az", + ] + verifylist = [ + ('availability_zone', "foo-az"), + ('server', [server.id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises(exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.77 or greater is required', str(ex)) + + +class TestServerUnshelveV277(TestServerUnshelve): + + def setUp(self): + super(TestServerUnshelveV277, self).setUp() + + self.server = compute_fakes.FakeServer.create_one_server( + methods=self.methods) + + # This is the return value for utils.find_resource() + self.servers_mock.get.return_value = self.server + + # Get the command object to test + self.cmd = server.UnshelveServer(self.app, None) + + def test_specified_az_to_unshelve_with_v277(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.77') + + arglist = [ + '--availability-zone', "foo-az", + self.server.id, + ] + verifylist = [ + ('availability_zone', "foo-az"), + ('server', [self.server.id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + self.servers_mock.get.assert_called_with(self.server.id) + self.server.unshelve.assert_called_with(availability_zone="foo-az") + class TestServerGeneral(TestServer): OLD = { diff --git a/releasenotes/notes/bp-support-specifying-az-when-restore-shelved-server-16e864223d51b50a.yaml b/releasenotes/notes/bp-support-specifying-az-when-restore-shelved-server-16e864223d51b50a.yaml new file mode 100644 index 0000000000..0e7dacbe17 --- /dev/null +++ b/releasenotes/notes/bp-support-specifying-az-when-restore-shelved-server-16e864223d51b50a.yaml @@ -0,0 +1,7 @@ +--- +features: + - Add ``--availability-zone`` option to ``server unshelve`` + command to enable users to specify an availability zone during + unshelve of a shelved offloaded server. Note that it requires + ``--os-compute-api-version 2.77`` or greater. + [Blueprint ` =3.15.3 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 python-glanceclient>=2.8.0 # Apache-2.0 python-keystoneclient>=3.17.0 # Apache-2.0 -python-novaclient>=14.2.0 # Apache-2.0 +python-novaclient>=15.0.0 # Apache-2.0 python-cinderclient>=3.3.0 # Apache-2.0 From dd1ce370424264166be686578bcab0fa41ac973e Mon Sep 17 00:00:00 2001 From: zhangbailin Date: Thu, 5 Sep 2019 11:11:24 +0800 Subject: [PATCH 2108/3095] Follow-up: fix the invalid releasenote link By reviewing OSC's releasenote document [1], I found that the blueprint index is invalid. This patch fixes this problem. [1]https://docs.openstack.org/releasenotes/python-openstackclient/unreleased.html#new-features Related-On: https://review.opendev.org/#/c/665336 Part of blueprint support-specifying-az-when-restore-shelved-server Change-Id: I14066dcfff5a585d51b4f365883a86a5cc086d45 --- ...cifying-az-when-restore-shelved-server-16e864223d51b50a.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/releasenotes/notes/bp-support-specifying-az-when-restore-shelved-server-16e864223d51b50a.yaml b/releasenotes/notes/bp-support-specifying-az-when-restore-shelved-server-16e864223d51b50a.yaml index 0e7dacbe17..adb55b1bbb 100644 --- a/releasenotes/notes/bp-support-specifying-az-when-restore-shelved-server-16e864223d51b50a.yaml +++ b/releasenotes/notes/bp-support-specifying-az-when-restore-shelved-server-16e864223d51b50a.yaml @@ -4,4 +4,4 @@ features: command to enable users to specify an availability zone during unshelve of a shelved offloaded server. Note that it requires ``--os-compute-api-version 2.77`` or greater. - [Blueprint ` `_] From ee48777207e0682862cf43fdf2f2f5dffc37d22a Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 22 Aug 2019 23:55:01 -0500 Subject: [PATCH 2109/3095] Clean up app initialization and config * Remove unnecessary code in OpenStackShell.initialize_app() - only the bits it instantiate our subclass of ClientManager remain * Remove OSC_Config - with https://review.opendev.org/#/c/678095/ the last remaining required bit moves to osc-lib Thos requires osc-lib 1.14.0 Change-Id: Ia4b3c737de9dc34949e74632441621014ef9eea9 Signed-off-by: Dean Troyer --- openstackclient/common/client_config.py | 71 ------------------------- openstackclient/shell.py | 33 +----------- 2 files changed, 1 insertion(+), 103 deletions(-) delete mode 100644 openstackclient/common/client_config.py diff --git a/openstackclient/common/client_config.py b/openstackclient/common/client_config.py deleted file mode 100644 index a22dd0cb3c..0000000000 --- a/openstackclient/common/client_config.py +++ /dev/null @@ -1,71 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - -"""OpenStackConfig subclass for argument compatibility""" - -from osc_lib.cli import client_config - - -# Sublcass OpenStackConfig in order to munge config values -# before auth plugins are loaded -class OSC_Config(client_config.OSC_Config): - - # TODO(dtroyer): Remove _auth_default_domain when the v3otp fix is - # backported to osc-lib, should be in release 1.3.0 - def _auth_default_domain(self, config): - """Set a default domain from available arguments - - Migrated from clientmanager.setup_auth() - """ - - identity_version = config.get('identity_api_version', '') - auth_type = config.get('auth_type', None) - - # TODO(mordred): This is a usability improvement that's broadly useful - # We should port it back up into os-client-config. - default_domain = config.get('default_domain', None) - if (identity_version == '3' and - not auth_type.startswith('v2') and - default_domain): - - # NOTE(stevemar): If PROJECT_DOMAIN_ID or PROJECT_DOMAIN_NAME is - # present, then do not change the behaviour. Otherwise, set the - # PROJECT_DOMAIN_ID to 'OS_DEFAULT_DOMAIN' for better usability. - if ( - auth_type in ("password", "v3password", "v3totp") and - not config['auth'].get('project_domain_id') and - not config['auth'].get('project_domain_name') - ): - config['auth']['project_domain_id'] = default_domain - - # NOTE(stevemar): If USER_DOMAIN_ID or USER_DOMAIN_NAME is present, - # then do not change the behaviour. Otherwise, set the - # USER_DOMAIN_ID to 'OS_DEFAULT_DOMAIN' for better usability. - # NOTE(aloga): this should only be set if there is a username. - # TODO(dtroyer): Move this to os-client-config after the plugin has - # been loaded so we can check directly if the options are accepted. - if ( - auth_type in ("password", "v3password", "v3totp") and - not config['auth'].get('user_domain_id') and - not config['auth'].get('user_domain_name') - ): - config['auth']['user_domain_id'] = default_domain - return config - - def load_auth_plugin(self, config): - """Get auth plugin and validate args""" - - loader = self._get_auth_loader(config) - config = self._validate_auth(config, loader) - auth_plugin = loader.load_from_options(**config['auth']) - return auth_plugin diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 4489219f9c..7fbda1aee5 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -25,7 +25,6 @@ import six import openstackclient -from openstackclient.common import client_config as cloud_config from openstackclient.common import clientmanager @@ -133,37 +132,7 @@ def _load_commands(self): def initialize_app(self, argv): super(OpenStackShell, self).initialize_app(argv) - # Argument precedence is really broken in multiple places - # so we're just going to fix it here until o-c-c and osc-lib - # get sorted out. - # TODO(dtroyer): remove when os-client-config and osc-lib are fixed - - # First, throw away what has already been done with o-c-c and - # use our own. - try: - self.cloud_config = cloud_config.OSC_Config( - override_defaults={ - 'interface': None, - 'auth_type': self._auth_type, - }, - ) - except (IOError, OSError): - self.log.critical("Could not read clouds.yaml configuration file") - self.print_help_if_requested() - raise - - if not self.options.debug: - self.options.debug = None - - # NOTE(dtroyer): Need to do this with validate=False to defer the - # auth plugin handling to ClientManager.setup_auth() - self.cloud = self.cloud_config.get_one_cloud( - cloud=self.options.cloud, - argparse=self.options, - validate=False, - ) - - # Then, re-create the client_manager with the correct arguments + # Re-create the client_manager with our subclass self.client_manager = clientmanager.ClientManager( cli_options=self.cloud, api_version=self.api_version, From a96089ff6d54237a9e058dc1c7e6fc7e25695ecb Mon Sep 17 00:00:00 2001 From: Sean McGinnis Date: Wed, 14 Aug 2019 14:35:21 -0500 Subject: [PATCH 2110/3095] Default to Cinder v3 API This switches the default Cinder API version to v3 to prepare for v2 going away. Change-Id: Icca1512b715409e3001c0fd2d1ea663d5e71ec02 Signed-off-by: Sean McGinnis --- openstackclient/volume/client.py | 2 +- releasenotes/notes/volume-v3-default-0ffa9bebb43b5057.yaml | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/volume-v3-default-0ffa9bebb43b5057.yaml diff --git a/openstackclient/volume/client.py b/openstackclient/volume/client.py index e0e670a9ce..fdd1794be5 100644 --- a/openstackclient/volume/client.py +++ b/openstackclient/volume/client.py @@ -22,7 +22,7 @@ LOG = logging.getLogger(__name__) -DEFAULT_API_VERSION = '2' +DEFAULT_API_VERSION = '3' API_VERSION_OPTION = 'os_volume_api_version' API_NAME = "volume" API_VERSIONS = { diff --git a/releasenotes/notes/volume-v3-default-0ffa9bebb43b5057.yaml b/releasenotes/notes/volume-v3-default-0ffa9bebb43b5057.yaml new file mode 100644 index 0000000000..a38a5434d8 --- /dev/null +++ b/releasenotes/notes/volume-v3-default-0ffa9bebb43b5057.yaml @@ -0,0 +1,7 @@ +--- +upgrade: + - | + Volume commands now default to Volume API 3. On older clouds + that do not support Volume 3.x ``--os-volume-api-version 2`` + of the adition of ``volume_api_version: '2' in ``clouds.yaml`` + will be required. From 037a800538feb690af0541d4f276dcdf48af3f72 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Mon, 9 Sep 2019 10:31:43 -0500 Subject: [PATCH 2111/3095] Add doc and relnote for review 639652 https://review.opendev.org/639652/ Change-Id: I10c0f8a0e09150e7d516ed9cb7ffb2a8e8fe4911 Signed-off-by: Dean Troyer --- doc/source/cli/command-objects/usage.rst | 3 +++ releasenotes/notes/usage-list-all-49ca7a62c50e71d3.yaml | 6 ++++++ 2 files changed, 9 insertions(+) create mode 100644 releasenotes/notes/usage-list-all-49ca7a62c50e71d3.yaml diff --git a/doc/source/cli/command-objects/usage.rst b/doc/source/cli/command-objects/usage.rst index 9cd0f70eb3..bb13176cc8 100644 --- a/doc/source/cli/command-objects/usage.rst +++ b/doc/source/cli/command-objects/usage.rst @@ -9,6 +9,9 @@ usage list List resource usage per project +Compute API v2.40+ returns all matching entities rather than being +limited to the API server configured maximum (``CONF.api.max_limit``). + .. program:: usage list .. code:: bash diff --git a/releasenotes/notes/usage-list-all-49ca7a62c50e71d3.yaml b/releasenotes/notes/usage-list-all-49ca7a62c50e71d3.yaml new file mode 100644 index 0000000000..76a09cc546 --- /dev/null +++ b/releasenotes/notes/usage-list-all-49ca7a62c50e71d3.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Compute API v2.40+ returns all matching entities rather than being + limited to the API server configured maximum (``CONF.api.max_limit``). + [Story `2005099 `_] From 609988ebac2c6bbb596aa77b46589274ffbc1e07 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Mon, 10 Jun 2019 17:09:56 +0100 Subject: [PATCH 2112/3095] Add 'openstack server migrate (confirm|revert)' commands While cold migration and resize are essentially the same operation under the hood, meaning one could use the 'openstack server resize confirm' and 'openstack server resize revert' commands instead, there is no reason the operator needs to know this. Add these flags as syntactic sugar to help simplify operators lives. The help texts for both the 'openstack server resize' and 'openstack server migrate' commands are updated to clarify the relationship between the two operations. Change-Id: I0cb6304c794bffaec785add9f7b8cf53ab28cacd Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server.py | 31 ++++++++++++++++--- ...firm-revert-commands-9d8079c9fddea36d.yaml | 6 ++++ setup.cfg | 2 ++ 3 files changed, 34 insertions(+), 5 deletions(-) create mode 100644 releasenotes/notes/add-server-migrate-confirm-revert-commands-9d8079c9fddea36d.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 493fd5ad9c..096291382e 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1555,7 +1555,15 @@ def take_action(self, parsed_args): # then adding the groups doesn't seem to work class MigrateServer(command.Command): - _description = _("Migrate server to different host") + _description = _("""Migrate server to different host. + +A migrate operation is implemented as a resize operation using the same flavor +as the old server. This means that, like resize, migrate works by creating a +new server using the same flavor and copying the contents of the original disk +into a new one. As with resize, the migrate operation is a two-step process for +the user: the first step is to perform the migrate, and the second step is to +either confirm (verify) success and release the old server, or to declare a +revert to release the new server and restart the old one.""") def get_parser(self, prog_name): parser = super(MigrateServer, self).get_parser(prog_name) @@ -2159,10 +2167,10 @@ class ResizeServer(command.Command): _description = _("""Scale server to a new flavor. A resize operation is implemented by creating a new server and copying the -contents of the original disk into a new one. It is also a two-step process for -the user: the first is to perform the resize, the second is to either confirm -(verify) success and release the old server, or to declare a revert to release -the new server and restart the old one.""") +contents of the original disk into a new one. It is a two-step process for the +user: the first step is to perform the resize, and the second step is to either +confirm (verify) success and release the old server or to declare a revert to +release the new server and restart the old one.""") def get_parser(self, prog_name): parser = super(ResizeServer, self).get_parser(prog_name) @@ -2261,6 +2269,12 @@ def take_action(self, parsed_args): server.confirm_resize() +class MigrateConfirm(ResizeConfirm): + _description = _("""Confirm server migrate. + +Confirm (verify) success of migrate operation and release the old server.""") + + class ResizeRevert(command.Command): _description = _("""Revert server resize. @@ -2286,6 +2300,13 @@ def take_action(self, parsed_args): server.revert_resize() +class MigrateRevert(ResizeRevert): + _description = _("""Revert server migrate. + +Revert the migrate operation. Release the new server and restart the old +one.""") + + class RestoreServer(command.Command): _description = _("Restore server(s)") diff --git a/releasenotes/notes/add-server-migrate-confirm-revert-commands-9d8079c9fddea36d.yaml b/releasenotes/notes/add-server-migrate-confirm-revert-commands-9d8079c9fddea36d.yaml new file mode 100644 index 0000000000..d1fe2411dd --- /dev/null +++ b/releasenotes/notes/add-server-migrate-confirm-revert-commands-9d8079c9fddea36d.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``server migrate confirm`` and ``server migrate revert`` commands. + These are aliases of the ``server resize confirm`` and ``server resize revert`` + commands, respectively. diff --git a/setup.cfg b/setup.cfg index a9f7de2ce1..694b682c86 100644 --- a/setup.cfg +++ b/setup.cfg @@ -108,6 +108,8 @@ openstack.compute.v2 = server_list = openstackclient.compute.v2.server:ListServer server_lock = openstackclient.compute.v2.server:LockServer server_migrate = openstackclient.compute.v2.server:MigrateServer + server_migrate_confirm = openstackclient.compute.v2.server:MigrateConfirm + server_migrate_revert = openstackclient.compute.v2.server:MigrateRevert server_pause = openstackclient.compute.v2.server:PauseServer server_reboot = openstackclient.compute.v2.server:RebootServer server_rebuild = openstackclient.compute.v2.server:RebuildServer From de8ab5e8fd05c3942a8ec332c01f4c92213f52d4 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Mon, 9 Sep 2019 10:54:34 -0500 Subject: [PATCH 2113/3095] More aggregate functional race chasing AggregateTests.wait_for_status() was a classmethod, those often are sources of conflict in parallel testing... Change-Id: I6211fd9c36926ca97de51a11923933d4d9d2dfda Signed-off-by: Dean Troyer --- .../tests/functional/compute/v2/test_aggregate.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/openstackclient/tests/functional/compute/v2/test_aggregate.py b/openstackclient/tests/functional/compute/v2/test_aggregate.py index 8a082e82ad..be318ae5e3 100644 --- a/openstackclient/tests/functional/compute/v2/test_aggregate.py +++ b/openstackclient/tests/functional/compute/v2/test_aggregate.py @@ -20,15 +20,14 @@ class AggregateTests(base.TestCase): """Functional tests for aggregate""" - @classmethod - def wait_for_status(cls, check_type, check_name, desired_status, + def wait_for_status(self, check_type, check_name, desired_status, wait=120, interval=5, failures=None): current_status = "notset" if failures is None: failures = ['error'] total_sleep = 0 while total_sleep < wait: - output = json.loads(cls.openstack( + output = json.loads(self.openstack( check_type + ' show -f json ' + check_name)) current_status = output['name'] if (current_status == desired_status): @@ -44,7 +43,7 @@ def wait_for_status(cls, check_type, check_name, desired_status, .format(current_status, check_type, check_name, failures)) time.sleep(interval) total_sleep += interval - cls.assertOutput(desired_status, current_status) + self.assertOutput(desired_status, current_status) def test_aggregate_crud(self): """Test create, delete multiple""" @@ -106,7 +105,6 @@ def test_aggregate_crud(self): name1 ) self.assertOutput('', raw_output) - self.wait_for_status('aggregate', name3, name3) self.addCleanup( self.openstack, 'aggregate delete ' + name3, From cb0c20b23c564dc6b260492923aef58ee66620d9 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Mon, 9 Sep 2019 11:48:28 -0500 Subject: [PATCH 2114/3095] Update release table for Train and 4.0.0 Also clean up some docs and release notes. Change-Id: I73feec747ca1bd12be5e5700c9ca608ed3a8b2c2 Signed-off-by: Dean Troyer --- README.rst | 16 ++-------------- doc/source/cli/authentication.rst | 2 -- doc/source/index.rst | 3 +-- ...erver-migrate-with-host-4884a71903c5c8a9.yaml | 2 +- ...rt-security-group-rules-95272847349982e5.yaml | 5 ++--- .../notes/bug-1716789-abfae897b7e61246.yaml | 15 ++++----------- .../volume-v3-default-0ffa9bebb43b5057.yaml | 2 +- releasenotes/source/index.rst | 2 ++ 8 files changed, 13 insertions(+), 34 deletions(-) diff --git a/README.rst b/README.rst index e1824aed6d..cd80079fab 100644 --- a/README.rst +++ b/README.rst @@ -16,8 +16,8 @@ OpenStackClient :alt: Latest Version OpenStackClient (aka OSC) is a command-line client for OpenStack that brings -the command set for Compute, Identity, Image, Object Store and Block Storage -APIs together in a single shell with a uniform command structure. +the command set for Compute, Identity, Image, Network, Object Store and Block +Storage APIs together in a single shell with a uniform command structure. The primary goal is to provide a unified shell command structure and a common language to describe operations in OpenStack. @@ -100,15 +100,3 @@ The corresponding command-line options look very similar:: If a password is not provided above (in plaintext), you will be interactively prompted to provide one securely. - -Authentication may also be performed using an already-acquired token -and a URL pointing directly to the service API that presumably was acquired -from the Service Catalog:: - - export OS_TOKEN= - export OS_URL= - -The corresponding command-line options look very similar:: - - --os-token - --os-url diff --git a/doc/source/cli/authentication.rst b/doc/source/cli/authentication.rst index f8aaadf492..3b404bce5b 100644 --- a/doc/source/cli/authentication.rst +++ b/doc/source/cli/authentication.rst @@ -67,8 +67,6 @@ by the ``ClientManager`` object. is selected based on the existing options. This is a short-circuit evaluation, the first match wins. - * If ``--os-url`` and ``--os-token`` are both present ``token_endpoint`` - is selected * If ``--os-username`` is supplied ``password`` is selected * If ``--os-token`` is supplied ``token`` is selected * If no selection has been made by now exit with error diff --git a/doc/source/index.rst b/doc/source/index.rst index abe0803374..ea4cd3e9f9 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -55,7 +55,7 @@ Contributing OpenStackClient utilizes all of the usual OpenStack processes and requirements for contributions. The code is hosted `on OpenStack's Git server`_. `Bug reports`_ -and `blueprints`_ may be submitted to the :code:`python-openstackclient` project +may be submitted to the :code:`python-openstackclient` Storyboard project on `Launchpad`_. Code may be submitted to the :code:`openstack/python-openstackclient` project using `Gerrit`_. Developers may also be found in the `IRC channel`_ ``#openstack-sdks``. @@ -64,7 +64,6 @@ Developers may also be found in the `IRC channel`_ ``#openstack-sdks``. .. _Launchpad: https://launchpad.net/python-openstackclient .. _Gerrit: http://docs.openstack.org/infra/manual/developers.html#development-workflow .. _Bug reports: https://storyboard.openstack.org/#!/project/975 -.. _blueprints: https://blueprints.launchpad.net/python-openstackclient .. _PyPi: https://pypi.org/project/python-openstackclient .. _tarball: http://tarballs.openstack.org/python-openstackclient .. _IRC channel: https://wiki.openstack.org/wiki/IRC diff --git a/releasenotes/notes/add-server-migrate-with-host-4884a71903c5c8a9.yaml b/releasenotes/notes/add-server-migrate-with-host-4884a71903c5c8a9.yaml index dd974ff0b3..8da704ac2d 100644 --- a/releasenotes/notes/add-server-migrate-with-host-4884a71903c5c8a9.yaml +++ b/releasenotes/notes/add-server-migrate-with-host-4884a71903c5c8a9.yaml @@ -1,7 +1,7 @@ --- features: - | - Added the ability to specify ``--host`` with ``server migrate`` + Add ``--host`` option to ``server migrate`` command (cold migration) to specify the target host of the migration. Requires ``--os-compute-api-version`` 2.56 or greater to target a specific host for the (cold) migration. diff --git a/releasenotes/notes/better-ipv6-address-suppport-security-group-rules-95272847349982e5.yaml b/releasenotes/notes/better-ipv6-address-suppport-security-group-rules-95272847349982e5.yaml index 6cac67cf88..7177873a02 100644 --- a/releasenotes/notes/better-ipv6-address-suppport-security-group-rules-95272847349982e5.yaml +++ b/releasenotes/notes/better-ipv6-address-suppport-security-group-rules-95272847349982e5.yaml @@ -1,9 +1,8 @@ --- features: - | - Security group rules can now be filtered by Ethertype in - ``security group rule list`` using ``--ethertype`` with either - ``ipv4`` or ``ipv6`` as an argument. + Add ``--ethertype`` option to ``security group rule list`` command. + Valid values are ``ipv4`` and ``ipv6``. upgrade: - | Security group rule listings now have the ``Ethertype`` field displayed diff --git a/releasenotes/notes/bug-1716789-abfae897b7e61246.yaml b/releasenotes/notes/bug-1716789-abfae897b7e61246.yaml index 1fd0a13de6..4177f80bd1 100644 --- a/releasenotes/notes/bug-1716789-abfae897b7e61246.yaml +++ b/releasenotes/notes/bug-1716789-abfae897b7e61246.yaml @@ -1,17 +1,10 @@ --- -features: +fixes: - | - Change to use ``any`` as the default ``--protocol`` option to + Change the default value of ``--protocol`` option to ``any`` in ``security group rule create`` command when using the Neutron v2 API. [Bug `1716789 `_] -fixes: - - | - The default protocol used to create a security rule was changed to - ``tcp``, which was a regression from the neutron client when using - the Neutron v2 API. Change it back to ``any``, which skips sending - the protocol to the API server entirely. upgrade: - | - Users that had been creating rules without specifying a protocol - and expecting ``tcp`` need to change to use ``--protocol tcp`` - explicitly when using the Neutron v2 API. + Commands that assumed the default value of ``--protocol`` to be ``tcp`` + now must include ``--protocol tcp`` explicitly in Network commands. diff --git a/releasenotes/notes/volume-v3-default-0ffa9bebb43b5057.yaml b/releasenotes/notes/volume-v3-default-0ffa9bebb43b5057.yaml index a38a5434d8..6b0840777f 100644 --- a/releasenotes/notes/volume-v3-default-0ffa9bebb43b5057.yaml +++ b/releasenotes/notes/volume-v3-default-0ffa9bebb43b5057.yaml @@ -3,5 +3,5 @@ upgrade: - | Volume commands now default to Volume API 3. On older clouds that do not support Volume 3.x ``--os-volume-api-version 2`` - of the adition of ``volume_api_version: '2' in ``clouds.yaml`` + or the adition of ``volume_api_version: '2'`` in ``clouds.yaml`` will be required. diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index 9e2d8ce10b..c9cc7a253c 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -27,6 +27,8 @@ OpenStack release was made is shown below: ================= ======================= OpenStack Release OpenStackClient Release ================= ======================= +Train 4.0.0 +Stein 3.18.0 Rocky 3.16.0 Queens 3.14.0 Pike 3.12.0 From b4e9b225b4435a7a3598b70617c1e6eb73613fa0 Mon Sep 17 00:00:00 2001 From: Jens Harbott Date: Tue, 3 Sep 2019 17:36:56 +0000 Subject: [PATCH 2115/3095] Add dns_publish_fixed_ip attribute to subnets With the subnet_dns_publish_fixed_ip extension Neutron has added a new attribute to subnets, allowing to select whether DNS records should be published for fixed IPs from that subnet. Add support for this when creating and updating subnets. [0] https://bugs.launchpad.net/neutron/+bug/1784879 [1] https://review.opendev.org/662405 [2] https://review.opendev.org/662409 Depends-On: https://review.opendev.org/679833 Change-Id: Ia804e878acfd1f05e1f00c2ac9202c1d260827f4 --- openstackclient/network/v2/subnet.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 1f0c2d9478..60994acacd 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -229,6 +229,10 @@ def _get_attrs(client_manager, parsed_args, is_create=True): attrs['enable_dhcp'] = True if parsed_args.no_dhcp: attrs['enable_dhcp'] = False + if parsed_args.dns_publish_fixed_ip: + attrs['dns_publish_fixed_ip'] = True + if parsed_args.no_dns_publish_fixed_ip: + attrs['dns_publish_fixed_ip'] = False if ('dns_nameservers' in parsed_args and parsed_args.dns_nameservers is not None): attrs['dns_nameservers'] = parsed_args.dns_nameservers @@ -302,6 +306,17 @@ def get_parser(self, prog_name): action='store_true', help=_("Disable DHCP") ) + dns_publish_fixed_ip_group = parser.add_mutually_exclusive_group() + dns_publish_fixed_ip_group.add_argument( + '--dns-publish-fixed-ip', + action='store_true', + help=_("Enable publishing fixed IPs in DNS") + ) + dns_publish_fixed_ip_group.add_argument( + '--no-dns-publish-fixed-ip', + action='store_true', + help=_("Disable publishing fixed IPs in DNS (default)") + ) parser.add_argument( '--gateway', metavar='', @@ -557,6 +572,17 @@ def get_parser(self, prog_name): action='store_true', help=_("Disable DHCP") ) + dns_publish_fixed_ip_group = parser.add_mutually_exclusive_group() + dns_publish_fixed_ip_group.add_argument( + '--dns-publish-fixed-ip', + action='store_true', + help=_("Enable publishing fixed IPs in DNS") + ) + dns_publish_fixed_ip_group.add_argument( + '--no-dns-publish-fixed-ip', + action='store_true', + help=_("Disable publishing fixed IPs in DNS") + ) parser.add_argument( '--gateway', metavar='', From 9ad343968947fc025b530644c3369c60a2c14ea5 Mon Sep 17 00:00:00 2001 From: Andreas Florath Date: Thu, 19 Sep 2019 11:44:14 +0000 Subject: [PATCH 2116/3095] Fix osc-lib interface change: catch osc-lib Forbidden The patch https://review.opendev.org/#/c/673389/ introduced a regression by changing the osc-lib interface. The patch https://review.opendev.org/683119 changes the exception from the generic CommandError back to a specific Forbidden exception. This patch catches this exception and passes on, i.e. re-implements the same behavior as before. Story: 2006547 Change-Id: I17b1ec7abaa5b0828ccbcad40bd928565c5c59fb Signed-off-by: Andreas Florath --- openstackclient/identity/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/identity/common.py b/openstackclient/identity/common.py index e825116649..d125d2851d 100644 --- a/openstackclient/identity/common.py +++ b/openstackclient/identity/common.py @@ -207,7 +207,7 @@ def _find_identity_resource(identity_client_manager, name_or_id, name_or_id, **kwargs) if identity_resource is not None: return identity_resource - except identity_exc.Forbidden: + except exceptions.Forbidden: pass return resource_type(None, {'id': name_or_id, 'name': name_or_id}) From 154df0d069cdf8a10678b9f6d1b715350847fe96 Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Fri, 4 Oct 2019 13:06:18 +0000 Subject: [PATCH 2117/3095] Update master for stable/train Add file to the reno documentation build to show release notes for stable/train. Use pbr instruction to increment the minor version number automatically so that master versions are higher than the versions on stable/train. Change-Id: I2cab236b14bc5eac173d815a634c8d8eb2afee04 Sem-Ver: feature --- releasenotes/source/index.rst | 1 + releasenotes/source/train.rst | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 releasenotes/source/train.rst diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index c9cc7a253c..fed41f9ec4 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,6 +6,7 @@ OpenStackClient Release Notes :maxdepth: 1 unreleased + train stein rocky queens diff --git a/releasenotes/source/train.rst b/releasenotes/source/train.rst new file mode 100644 index 0000000000..583900393c --- /dev/null +++ b/releasenotes/source/train.rst @@ -0,0 +1,6 @@ +========================== +Train Series Release Notes +========================== + +.. release-notes:: + :branch: stable/train From cf1006bf0ef67a693357ba3913962037aaf6c3d5 Mon Sep 17 00:00:00 2001 From: Eric Fried Date: Mon, 21 Oct 2019 16:09:10 -0500 Subject: [PATCH 2118/3095] Fix plugin autodoc generation Documentation for plugins wasn't being generated because the plugin projects weren't being installed in the docs environment. Add them to doc/requirements.txt to make this work. Change-Id: Id9be39971110fd2eb4519a0582c9bf2514cdcacd Story: #1735016 Task: #13825 --- doc/requirements.txt | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/doc/requirements.txt b/doc/requirements.txt index 91854914a5..3bf3a2708b 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -6,3 +6,25 @@ reno>=2.5.0 # Apache-2.0 sphinx!=1.6.6,!=1.6.7,>=1.6.5,<2.0.0;python_version=='2.7' # BSD sphinx!=1.6.6,!=1.6.7,>=1.6.5;python_version>='3.4' # BSD sphinxcontrib-apidoc>=0.2.0 # BSD + +# Install these to generate sphinx autodocs +aodhclient>=0.9.0 # Apache-2.0 +gnocchiclient>=3.3.1 # Apache-2.0 +python-barbicanclient>=4.5.2 # Apache-2.0 +python-congressclient<2000,>=1.9.0 # Apache-2.0 +python-designateclient>=2.7.0 # Apache-2.0 +python-heatclient>=1.10.0 # Apache-2.0 +python-ironicclient>=2.3.0 # Apache-2.0 +python-ironic-inspector-client>=1.5.0 # Apache-2.0 +python-karborclient>=0.6.0 # Apache-2.0 +python-mistralclient!=3.2.0,>=3.1.0 # Apache-2.0 +python-muranoclient>=0.8.2 # Apache-2.0 +python-neutronclient>=6.7.0 # Apache-2.0 +python-octaviaclient>=1.3.0 # Apache-2.0 +python-rsdclient>=0.1.0 # Apache-2.0 +python-saharaclient>=1.4.0 # Apache-2.0 +python-searchlightclient>=1.0.0 #Apache-2.0 +python-senlinclient>=1.1.0 # Apache-2.0 +python-troveclient>=2.2.0 # Apache-2.0 +python-zaqarclient>=1.0.0 # Apache-2.0 +python-zunclient>=3.4.0 # Apache-2.0 From 8cdc7348d184555cac3e32facaf949784f0564d2 Mon Sep 17 00:00:00 2001 From: Eric Fried Date: Mon, 21 Oct 2019 15:45:36 -0500 Subject: [PATCH 2119/3095] Add placement to known plugins Add the osc-placement project (osc plugin for the placement service) to the list of known plugins. Change-Id: I77b614b38ecf872d0d93473b834994913930b76f --- doc/requirements.txt | 1 + doc/source/cli/plugin-commands.rst | 6 ++++++ doc/source/contributor/plugins.rst | 1 + lower-constraints.txt | 1 + test-requirements.txt | 1 + 5 files changed, 10 insertions(+) diff --git a/doc/requirements.txt b/doc/requirements.txt index 3bf3a2708b..12ed5f01a2 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -10,6 +10,7 @@ sphinxcontrib-apidoc>=0.2.0 # BSD # Install these to generate sphinx autodocs aodhclient>=0.9.0 # Apache-2.0 gnocchiclient>=3.3.1 # Apache-2.0 +osc-placement>=1.7.0 # Apache-2.0 python-barbicanclient>=4.5.2 # Apache-2.0 python-congressclient<2000,>=1.9.0 # Apache-2.0 python-designateclient>=2.7.0 # Apache-2.0 diff --git a/doc/source/cli/plugin-commands.rst b/doc/source/cli/plugin-commands.rst index ae2e655712..5796420555 100644 --- a/doc/source/cli/plugin-commands.rst +++ b/doc/source/cli/plugin-commands.rst @@ -87,6 +87,12 @@ octavia .. list-plugins:: openstack.load_balancer.v2 :detailed: +placement +--------- + +.. list-plugins:: openstack.placement.v1 + :detailed: + rsd --- diff --git a/doc/source/contributor/plugins.rst b/doc/source/contributor/plugins.rst index effcd85ac6..d6bcbee197 100644 --- a/doc/source/contributor/plugins.rst +++ b/doc/source/contributor/plugins.rst @@ -25,6 +25,7 @@ The following is a list of projects that are an OpenStackClient plugin. - aodhclient - gnocchiclient +- osc-placement - python-barbicanclient - python-congressclient - python-designateclient diff --git a/lower-constraints.txt b/lower-constraints.txt index a84643caed..db0562e124 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -55,6 +55,7 @@ os-client-config==1.28.0 os-service-types==1.2.0 os-testr==1.0.0 osc-lib==1.14.0 +osc-placement==1.7.0 oslo.concurrency==3.26.0 oslo.config==5.2.0 oslo.context==2.19.2 diff --git a/test-requirements.txt b/test-requirements.txt index 4db30c7fca..71adb0d1d1 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -21,6 +21,7 @@ wrapt>=1.7.0 # BSD License # Install these to generate sphinx autodocs aodhclient>=0.9.0 # Apache-2.0 gnocchiclient>=3.3.1 # Apache-2.0 +osc-placement>=1.7.0 # Apache-2.0 python-barbicanclient>=4.5.2 # Apache-2.0 python-congressclient<2000,>=1.9.0 # Apache-2.0 python-designateclient>=2.7.0 # Apache-2.0 From abdec78fa331ce724008ba864841f36fde294fd9 Mon Sep 17 00:00:00 2001 From: Eric Fried Date: Mon, 21 Oct 2019 16:37:41 -0500 Subject: [PATCH 2120/3095] Remove plugin projects from test-requirements.txt Plugin projects were listed in test-requirements.txt ostensibly to pull them in for autodoc building. However, test-requirements.txt wasn't being used for that purpose -- doc/requirements.txt was, so [1] added them there. So this commit removes them from test-requirements.txt where they're unused. [1] Id9be39971110fd2eb4519a0582c9bf2514cdcacd Change-Id: Ia89888ee05e17da636ee46894232624e0178d6bc --- test-requirements.txt | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/test-requirements.txt b/test-requirements.txt index 71adb0d1d1..0f59e46faa 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -17,26 +17,3 @@ tempest>=17.1.0 # Apache-2.0 osprofiler>=1.4.0 # Apache-2.0 bandit!=1.6.0,>=1.1.0 # Apache-2.0 wrapt>=1.7.0 # BSD License - -# Install these to generate sphinx autodocs -aodhclient>=0.9.0 # Apache-2.0 -gnocchiclient>=3.3.1 # Apache-2.0 -osc-placement>=1.7.0 # Apache-2.0 -python-barbicanclient>=4.5.2 # Apache-2.0 -python-congressclient<2000,>=1.9.0 # Apache-2.0 -python-designateclient>=2.7.0 # Apache-2.0 -python-heatclient>=1.10.0 # Apache-2.0 -python-ironicclient>=2.3.0 # Apache-2.0 -python-ironic-inspector-client>=1.5.0 # Apache-2.0 -python-karborclient>=0.6.0 # Apache-2.0 -python-mistralclient!=3.2.0,>=3.1.0 # Apache-2.0 -python-muranoclient>=0.8.2 # Apache-2.0 -python-neutronclient>=6.7.0 # Apache-2.0 -python-octaviaclient>=1.3.0 # Apache-2.0 -python-rsdclient>=0.1.0 # Apache-2.0 -python-saharaclient>=1.4.0 # Apache-2.0 -python-searchlightclient>=1.0.0 #Apache-2.0 -python-senlinclient>=1.1.0 # Apache-2.0 -python-troveclient>=2.2.0 # Apache-2.0 -python-zaqarclient>=1.0.0 # Apache-2.0 -python-zunclient>=3.4.0 # Apache-2.0 From 85c83530ee63e1c66bc132d4b0c97d7f2b077ee8 Mon Sep 17 00:00:00 2001 From: Eric Fried Date: Mon, 21 Oct 2019 17:05:57 -0500 Subject: [PATCH 2121/3095] Split plugin docs per project Once [1] fixed plugin doc generation, the (single) page it produced was unusably huge. This commit splits it into one page per project. Note that there are four plugin projects that didn't have sections included: - cue - murano - tripleo - watcher These were noted in hidden rst which is preserved in the (new) index page. [1] Id9be39971110fd2eb4519a0582c9bf2514cdcacd Change-Id: I0214ddb00a5a292a46d7cfb539d6dcc540fdae79 Story: #1735016 Task: #37239 --- doc/source/_extra/.htaccess | 1 + doc/source/cli/index.rst | 2 +- doc/source/cli/plugin-commands.rst | 146 ------------------ doc/source/cli/plugin-commands/aodh.rst | 5 + doc/source/cli/plugin-commands/barbican.rst | 5 + doc/source/cli/plugin-commands/congress.rst | 5 + doc/source/cli/plugin-commands/designate.rst | 5 + doc/source/cli/plugin-commands/gnocchi.rst | 5 + doc/source/cli/plugin-commands/heat.rst | 5 + doc/source/cli/plugin-commands/index.rst | 51 ++++++ .../cli/plugin-commands/ironic-inspector.rst | 5 + doc/source/cli/plugin-commands/ironic.rst | 5 + doc/source/cli/plugin-commands/karbor.rst | 5 + doc/source/cli/plugin-commands/mistral.rst | 5 + doc/source/cli/plugin-commands/neutron.rst | 5 + doc/source/cli/plugin-commands/octavia.rst | 5 + doc/source/cli/plugin-commands/placement.rst | 5 + doc/source/cli/plugin-commands/rsd.rst | 5 + doc/source/cli/plugin-commands/sahara.rst | 5 + .../cli/plugin-commands/searchlight.rst | 5 + doc/source/cli/plugin-commands/senlin.rst | 5 + doc/source/cli/plugin-commands/trove.rst | 5 + doc/source/cli/plugin-commands/zaqar.rst | 5 + doc/source/cli/plugin-commands/zun.rst | 5 + 24 files changed, 153 insertions(+), 147 deletions(-) delete mode 100644 doc/source/cli/plugin-commands.rst create mode 100644 doc/source/cli/plugin-commands/aodh.rst create mode 100644 doc/source/cli/plugin-commands/barbican.rst create mode 100644 doc/source/cli/plugin-commands/congress.rst create mode 100644 doc/source/cli/plugin-commands/designate.rst create mode 100644 doc/source/cli/plugin-commands/gnocchi.rst create mode 100644 doc/source/cli/plugin-commands/heat.rst create mode 100644 doc/source/cli/plugin-commands/index.rst create mode 100644 doc/source/cli/plugin-commands/ironic-inspector.rst create mode 100644 doc/source/cli/plugin-commands/ironic.rst create mode 100644 doc/source/cli/plugin-commands/karbor.rst create mode 100644 doc/source/cli/plugin-commands/mistral.rst create mode 100644 doc/source/cli/plugin-commands/neutron.rst create mode 100644 doc/source/cli/plugin-commands/octavia.rst create mode 100644 doc/source/cli/plugin-commands/placement.rst create mode 100644 doc/source/cli/plugin-commands/rsd.rst create mode 100644 doc/source/cli/plugin-commands/sahara.rst create mode 100644 doc/source/cli/plugin-commands/searchlight.rst create mode 100644 doc/source/cli/plugin-commands/senlin.rst create mode 100644 doc/source/cli/plugin-commands/trove.rst create mode 100644 doc/source/cli/plugin-commands/zaqar.rst create mode 100644 doc/source/cli/plugin-commands/zun.rst diff --git a/doc/source/_extra/.htaccess b/doc/source/_extra/.htaccess index d4c092b5ab..fd91901e87 100644 --- a/doc/source/_extra/.htaccess +++ b/doc/source/_extra/.htaccess @@ -20,3 +20,4 @@ redirectmatch 301 ^/python-openstackclient/([^/]+)/command-wrappers.html$ /pytho redirectmatch 301 ^/python-openstackclient/([^/]+)/developing.html$ /python-openstackclient/$1/contributor/developing.html redirectmatch 301 ^/python-openstackclient/([^/]+)/humaninterfaceguide.html$ /python-openstackclient/$1/contributor/humaninterfaceguide.html redirectmatch 301 ^/python-openstackclient/([^/]+)/plugins.html$ /python-openstackclient/$1/contributor/plugins.html +redirectmatch 301 ^/python-openstackclient/([^/]+)/cli/plugin-commands.html$ /python-openstackclient/$1/cli/plugin-commands/index.html diff --git a/doc/source/cli/index.rst b/doc/source/cli/index.rst index e9aab0afae..17e50a07d2 100644 --- a/doc/source/cli/index.rst +++ b/doc/source/cli/index.rst @@ -8,7 +8,7 @@ Manual Page command-list commands - plugin-commands + plugin-commands/index authentication interactive decoder diff --git a/doc/source/cli/plugin-commands.rst b/doc/source/cli/plugin-commands.rst deleted file mode 100644 index 5796420555..0000000000 --- a/doc/source/cli/plugin-commands.rst +++ /dev/null @@ -1,146 +0,0 @@ -.. _plugin-commands: - -=============== -Plugin Commands -=============== - -.. list-plugins:: openstack.cli.extension - -aodh ----- - -.. list-plugins:: openstack.alarming.v2 - :detailed: - -barbican --------- - -.. list-plugins:: openstack.key_manager.v1 - :detailed: - -congress --------- - -.. list-plugins:: openstack.congressclient.v1 - :detailed: - -.. cue -.. # cueclient is not in global-requirements -.. # list-plugins:: openstack.mb.v1 -.. # :detailed: - -designate ---------- - -.. list-plugins:: openstack.dns.v2 - :detailed: - -gnocchi -------- -.. list-plugins:: openstack.metric.v1 - :detailed: - -heat ----- - -.. list-plugins:: openstack.orchestration.v1 - :detailed: - -ironic ------- - -.. list-plugins:: openstack.baremetal.v1 - :detailed: - -ironic-inspector ----------------- - -.. list-plugins:: openstack.baremetal_introspection.v1 - :detailed: - -karbor ------- - -.. list-plugins:: openstack.data_protection.v1 - :detailed: - -mistral -------- - -.. list-plugins:: openstack.workflow_engine.v2 - :detailed: - -.. murano -.. # the murano docs cause warnings and a broken docs build -.. # .. list-plugins:: openstack.application_catalog.v1 -.. # :detailed: - -neutron -------- - -.. list-plugins:: openstack.neutronclient.v2 - :detailed: - -octavia -------- - -.. list-plugins:: openstack.load_balancer.v2 - :detailed: - -placement ---------- - -.. list-plugins:: openstack.placement.v1 - :detailed: - -rsd ---- - -.. list-plugins:: openstack.rsd.v1 - :detailed: - -sahara ------- - -.. list-plugins:: openstack.data_processing.v1 - :detailed: - -searchlight ------------ - -.. list-plugins:: openstack.search.v1 - :detailed: - -senlin ------- - -.. list-plugins:: openstack.clustering.v1 - :detailed: - -.. tripleo -.. # tripleoclient is not in global-requirements -.. # list-plugins:: openstack.tripleoclient.v1 -.. # :detailed: - -trove ------- - -.. list-plugins:: openstack.database.v1 - :detailed: - -.. watcher -.. # watcherclient is not in global-requirements -.. # list-plugins:: openstack.infra_optim.v1 -.. # :detailed: - -zaqar ------ - -.. list-plugins:: openstack.messaging.v2 - :detailed: - -zun ---- - -.. list-plugins:: openstack.container.v1 - :detailed: diff --git a/doc/source/cli/plugin-commands/aodh.rst b/doc/source/cli/plugin-commands/aodh.rst new file mode 100644 index 0000000000..26e7bf70fa --- /dev/null +++ b/doc/source/cli/plugin-commands/aodh.rst @@ -0,0 +1,5 @@ +aodh +---- + +.. list-plugins:: openstack.alarming.v2 + :detailed: diff --git a/doc/source/cli/plugin-commands/barbican.rst b/doc/source/cli/plugin-commands/barbican.rst new file mode 100644 index 0000000000..21b9b5545b --- /dev/null +++ b/doc/source/cli/plugin-commands/barbican.rst @@ -0,0 +1,5 @@ +barbican +-------- + +.. list-plugins:: openstack.key_manager.v1 + :detailed: diff --git a/doc/source/cli/plugin-commands/congress.rst b/doc/source/cli/plugin-commands/congress.rst new file mode 100644 index 0000000000..c21e2fa399 --- /dev/null +++ b/doc/source/cli/plugin-commands/congress.rst @@ -0,0 +1,5 @@ +congress +-------- + +.. list-plugins:: openstack.congressclient.v1 + :detailed: diff --git a/doc/source/cli/plugin-commands/designate.rst b/doc/source/cli/plugin-commands/designate.rst new file mode 100644 index 0000000000..94071bf547 --- /dev/null +++ b/doc/source/cli/plugin-commands/designate.rst @@ -0,0 +1,5 @@ +designate +--------- + +.. list-plugins:: openstack.dns.v2 + :detailed: diff --git a/doc/source/cli/plugin-commands/gnocchi.rst b/doc/source/cli/plugin-commands/gnocchi.rst new file mode 100644 index 0000000000..a545a5eab2 --- /dev/null +++ b/doc/source/cli/plugin-commands/gnocchi.rst @@ -0,0 +1,5 @@ +gnocchi +------- + +.. list-plugins:: openstack.metric.v1 + :detailed: diff --git a/doc/source/cli/plugin-commands/heat.rst b/doc/source/cli/plugin-commands/heat.rst new file mode 100644 index 0000000000..3699aeaf4a --- /dev/null +++ b/doc/source/cli/plugin-commands/heat.rst @@ -0,0 +1,5 @@ +heat +---- + +.. list-plugins:: openstack.orchestration.v1 + :detailed: diff --git a/doc/source/cli/plugin-commands/index.rst b/doc/source/cli/plugin-commands/index.rst new file mode 100644 index 0000000000..f6ff51bd63 --- /dev/null +++ b/doc/source/cli/plugin-commands/index.rst @@ -0,0 +1,51 @@ +.. _plugin-commands: + +=============== +Plugin Commands +=============== + +.. toctree:: + :maxdepth: 1 + + aodh + barbican + congress + designate + gnocchi + heat + ironic + ironic-inspector + karbor + mistral + neutron + octavia + placement + rsd + sahara + searchlight + senlin + trove + zaqar + zun + +.. TODO(efried): Make pages for the following once they're fixed. + +.. cue +.. # cueclient is not in global-requirements +.. # list-plugins:: openstack.mb.v1 +.. # :detailed: + +.. murano +.. # the murano docs cause warnings and a broken docs build +.. # .. list-plugins:: openstack.application_catalog.v1 +.. # :detailed: + +.. tripleo +.. # tripleoclient is not in global-requirements +.. # list-plugins:: openstack.tripleoclient.v1 +.. # :detailed: + +.. watcher +.. # watcherclient is not in global-requirements +.. # list-plugins:: openstack.infra_optim.v1 +.. # :detailed: diff --git a/doc/source/cli/plugin-commands/ironic-inspector.rst b/doc/source/cli/plugin-commands/ironic-inspector.rst new file mode 100644 index 0000000000..c149066212 --- /dev/null +++ b/doc/source/cli/plugin-commands/ironic-inspector.rst @@ -0,0 +1,5 @@ +ironic-inspector +---------------- + +.. list-plugins:: openstack.baremetal_introspection.v1 + :detailed: diff --git a/doc/source/cli/plugin-commands/ironic.rst b/doc/source/cli/plugin-commands/ironic.rst new file mode 100644 index 0000000000..032c57d286 --- /dev/null +++ b/doc/source/cli/plugin-commands/ironic.rst @@ -0,0 +1,5 @@ +ironic +------ + +.. list-plugins:: openstack.baremetal.v1 + :detailed: diff --git a/doc/source/cli/plugin-commands/karbor.rst b/doc/source/cli/plugin-commands/karbor.rst new file mode 100644 index 0000000000..aed14a6691 --- /dev/null +++ b/doc/source/cli/plugin-commands/karbor.rst @@ -0,0 +1,5 @@ +karbor +------ + +.. list-plugins:: openstack.data_protection.v1 + :detailed: diff --git a/doc/source/cli/plugin-commands/mistral.rst b/doc/source/cli/plugin-commands/mistral.rst new file mode 100644 index 0000000000..3facc506b5 --- /dev/null +++ b/doc/source/cli/plugin-commands/mistral.rst @@ -0,0 +1,5 @@ +mistral +------- + +.. list-plugins:: openstack.workflow_engine.v2 + :detailed: diff --git a/doc/source/cli/plugin-commands/neutron.rst b/doc/source/cli/plugin-commands/neutron.rst new file mode 100644 index 0000000000..6e67ae9480 --- /dev/null +++ b/doc/source/cli/plugin-commands/neutron.rst @@ -0,0 +1,5 @@ +neutron +------- + +.. list-plugins:: openstack.neutronclient.v2 + :detailed: diff --git a/doc/source/cli/plugin-commands/octavia.rst b/doc/source/cli/plugin-commands/octavia.rst new file mode 100644 index 0000000000..5384530fc8 --- /dev/null +++ b/doc/source/cli/plugin-commands/octavia.rst @@ -0,0 +1,5 @@ +octavia +------- + +.. list-plugins:: openstack.load_balancer.v2 + :detailed: diff --git a/doc/source/cli/plugin-commands/placement.rst b/doc/source/cli/plugin-commands/placement.rst new file mode 100644 index 0000000000..972818c768 --- /dev/null +++ b/doc/source/cli/plugin-commands/placement.rst @@ -0,0 +1,5 @@ +placement +--------- + +.. list-plugins:: openstack.placement.v1 + :detailed: diff --git a/doc/source/cli/plugin-commands/rsd.rst b/doc/source/cli/plugin-commands/rsd.rst new file mode 100644 index 0000000000..d7962014e5 --- /dev/null +++ b/doc/source/cli/plugin-commands/rsd.rst @@ -0,0 +1,5 @@ +rsd +--- + +.. list-plugins:: openstack.rsd.v1 + :detailed: diff --git a/doc/source/cli/plugin-commands/sahara.rst b/doc/source/cli/plugin-commands/sahara.rst new file mode 100644 index 0000000000..28ac867828 --- /dev/null +++ b/doc/source/cli/plugin-commands/sahara.rst @@ -0,0 +1,5 @@ +sahara +------ + +.. list-plugins:: openstack.data_processing.v1 + :detailed: diff --git a/doc/source/cli/plugin-commands/searchlight.rst b/doc/source/cli/plugin-commands/searchlight.rst new file mode 100644 index 0000000000..fed56f0912 --- /dev/null +++ b/doc/source/cli/plugin-commands/searchlight.rst @@ -0,0 +1,5 @@ +searchlight +----------- + +.. list-plugins:: openstack.search.v1 + :detailed: diff --git a/doc/source/cli/plugin-commands/senlin.rst b/doc/source/cli/plugin-commands/senlin.rst new file mode 100644 index 0000000000..f5f81d9e13 --- /dev/null +++ b/doc/source/cli/plugin-commands/senlin.rst @@ -0,0 +1,5 @@ +senlin +------ + +.. list-plugins:: openstack.clustering.v1 + :detailed: diff --git a/doc/source/cli/plugin-commands/trove.rst b/doc/source/cli/plugin-commands/trove.rst new file mode 100644 index 0000000000..b4575edd8a --- /dev/null +++ b/doc/source/cli/plugin-commands/trove.rst @@ -0,0 +1,5 @@ +trove +----- + +.. list-plugins:: openstack.database.v1 + :detailed: diff --git a/doc/source/cli/plugin-commands/zaqar.rst b/doc/source/cli/plugin-commands/zaqar.rst new file mode 100644 index 0000000000..3649a274d3 --- /dev/null +++ b/doc/source/cli/plugin-commands/zaqar.rst @@ -0,0 +1,5 @@ +zaqar +----- + +.. list-plugins:: openstack.messaging.v2 + :detailed: diff --git a/doc/source/cli/plugin-commands/zun.rst b/doc/source/cli/plugin-commands/zun.rst new file mode 100644 index 0000000000..56c5ff59ce --- /dev/null +++ b/doc/source/cli/plugin-commands/zun.rst @@ -0,0 +1,5 @@ +zun +--- + +.. list-plugins:: openstack.container.v1 + :detailed: From 3fd63c0021d5ddddfb0ecce5d5f38799bcb13661 Mon Sep 17 00:00:00 2001 From: Eric Fried Date: Mon, 21 Oct 2019 17:31:04 -0500 Subject: [PATCH 2122/3095] Produce complete content for plugin docs Plugin documentation previously used ``.. list-plugins::`` for each plugin command, which just produced the summary line. This might be useful if there were also a link to the complete (per-project) docs for the commands; but since we have the content available, we might as well produce the complete plugin docs inline. That's going to be most useful to a reader anyway. So this commit switches from ``list-plugins`` to ``autoprogram-cliff`` -- except for the following, whose docs break the build when this is done: - octavia - rsd - trove - zun These fixups are tracked under task #37241 under this same story. Change-Id: I2f17e203fe3da92a709884c9052c8e39ff87f4c8 Story: #1735016 Task: #37240 --- doc/source/cli/plugin-commands/aodh.rst | 3 +-- doc/source/cli/plugin-commands/barbican.rst | 3 +-- doc/source/cli/plugin-commands/congress.rst | 3 +-- doc/source/cli/plugin-commands/designate.rst | 3 +-- doc/source/cli/plugin-commands/gnocchi.rst | 3 +-- doc/source/cli/plugin-commands/heat.rst | 3 +-- doc/source/cli/plugin-commands/ironic-inspector.rst | 3 +-- doc/source/cli/plugin-commands/ironic.rst | 3 +-- doc/source/cli/plugin-commands/karbor.rst | 3 +-- doc/source/cli/plugin-commands/mistral.rst | 3 +-- doc/source/cli/plugin-commands/neutron.rst | 3 +-- doc/source/cli/plugin-commands/octavia.rst | 2 ++ doc/source/cli/plugin-commands/placement.rst | 3 +-- doc/source/cli/plugin-commands/rsd.rst | 2 ++ doc/source/cli/plugin-commands/sahara.rst | 3 +-- doc/source/cli/plugin-commands/searchlight.rst | 3 +-- doc/source/cli/plugin-commands/senlin.rst | 3 +-- doc/source/cli/plugin-commands/trove.rst | 2 ++ doc/source/cli/plugin-commands/zaqar.rst | 3 +-- doc/source/cli/plugin-commands/zun.rst | 2 ++ 20 files changed, 24 insertions(+), 32 deletions(-) diff --git a/doc/source/cli/plugin-commands/aodh.rst b/doc/source/cli/plugin-commands/aodh.rst index 26e7bf70fa..5d8b4332cf 100644 --- a/doc/source/cli/plugin-commands/aodh.rst +++ b/doc/source/cli/plugin-commands/aodh.rst @@ -1,5 +1,4 @@ aodh ---- -.. list-plugins:: openstack.alarming.v2 - :detailed: +.. autoprogram-cliff:: openstack.alarming.v2 diff --git a/doc/source/cli/plugin-commands/barbican.rst b/doc/source/cli/plugin-commands/barbican.rst index 21b9b5545b..224b274f42 100644 --- a/doc/source/cli/plugin-commands/barbican.rst +++ b/doc/source/cli/plugin-commands/barbican.rst @@ -1,5 +1,4 @@ barbican -------- -.. list-plugins:: openstack.key_manager.v1 - :detailed: +.. autoprogram-cliff:: openstack.key_manager.v1 diff --git a/doc/source/cli/plugin-commands/congress.rst b/doc/source/cli/plugin-commands/congress.rst index c21e2fa399..7a1e63b76a 100644 --- a/doc/source/cli/plugin-commands/congress.rst +++ b/doc/source/cli/plugin-commands/congress.rst @@ -1,5 +1,4 @@ congress -------- -.. list-plugins:: openstack.congressclient.v1 - :detailed: +.. autoprogram-cliff:: openstack.congressclient.v1 diff --git a/doc/source/cli/plugin-commands/designate.rst b/doc/source/cli/plugin-commands/designate.rst index 94071bf547..045072b243 100644 --- a/doc/source/cli/plugin-commands/designate.rst +++ b/doc/source/cli/plugin-commands/designate.rst @@ -1,5 +1,4 @@ designate --------- -.. list-plugins:: openstack.dns.v2 - :detailed: +.. autoprogram-cliff:: openstack.dns.v2 diff --git a/doc/source/cli/plugin-commands/gnocchi.rst b/doc/source/cli/plugin-commands/gnocchi.rst index a545a5eab2..bbc7f6c8eb 100644 --- a/doc/source/cli/plugin-commands/gnocchi.rst +++ b/doc/source/cli/plugin-commands/gnocchi.rst @@ -1,5 +1,4 @@ gnocchi ------- -.. list-plugins:: openstack.metric.v1 - :detailed: +.. autoprogram-cliff:: openstack.metric.v1 diff --git a/doc/source/cli/plugin-commands/heat.rst b/doc/source/cli/plugin-commands/heat.rst index 3699aeaf4a..025561425a 100644 --- a/doc/source/cli/plugin-commands/heat.rst +++ b/doc/source/cli/plugin-commands/heat.rst @@ -1,5 +1,4 @@ heat ---- -.. list-plugins:: openstack.orchestration.v1 - :detailed: +.. autoprogram-cliff:: openstack.orchestration.v1 diff --git a/doc/source/cli/plugin-commands/ironic-inspector.rst b/doc/source/cli/plugin-commands/ironic-inspector.rst index c149066212..7ac12618e2 100644 --- a/doc/source/cli/plugin-commands/ironic-inspector.rst +++ b/doc/source/cli/plugin-commands/ironic-inspector.rst @@ -1,5 +1,4 @@ ironic-inspector ---------------- -.. list-plugins:: openstack.baremetal_introspection.v1 - :detailed: +.. autoprogram-cliff:: openstack.baremetal_introspection.v1 diff --git a/doc/source/cli/plugin-commands/ironic.rst b/doc/source/cli/plugin-commands/ironic.rst index 032c57d286..d9254e9bd3 100644 --- a/doc/source/cli/plugin-commands/ironic.rst +++ b/doc/source/cli/plugin-commands/ironic.rst @@ -1,5 +1,4 @@ ironic ------ -.. list-plugins:: openstack.baremetal.v1 - :detailed: +.. autoprogram-cliff:: openstack.baremetal.v1 diff --git a/doc/source/cli/plugin-commands/karbor.rst b/doc/source/cli/plugin-commands/karbor.rst index aed14a6691..0e28ba5733 100644 --- a/doc/source/cli/plugin-commands/karbor.rst +++ b/doc/source/cli/plugin-commands/karbor.rst @@ -1,5 +1,4 @@ karbor ------ -.. list-plugins:: openstack.data_protection.v1 - :detailed: +.. autoprogram-cliff:: openstack.data_protection.v1 diff --git a/doc/source/cli/plugin-commands/mistral.rst b/doc/source/cli/plugin-commands/mistral.rst index 3facc506b5..5f538d29fa 100644 --- a/doc/source/cli/plugin-commands/mistral.rst +++ b/doc/source/cli/plugin-commands/mistral.rst @@ -1,5 +1,4 @@ mistral ------- -.. list-plugins:: openstack.workflow_engine.v2 - :detailed: +.. autoprogram-cliff:: openstack.workflow_engine.v2 diff --git a/doc/source/cli/plugin-commands/neutron.rst b/doc/source/cli/plugin-commands/neutron.rst index 6e67ae9480..0167931f05 100644 --- a/doc/source/cli/plugin-commands/neutron.rst +++ b/doc/source/cli/plugin-commands/neutron.rst @@ -1,5 +1,4 @@ neutron ------- -.. list-plugins:: openstack.neutronclient.v2 - :detailed: +.. autoprogram-cliff:: openstack.neutronclient.v2 diff --git a/doc/source/cli/plugin-commands/octavia.rst b/doc/source/cli/plugin-commands/octavia.rst index 5384530fc8..fb48f0ac7e 100644 --- a/doc/source/cli/plugin-commands/octavia.rst +++ b/doc/source/cli/plugin-commands/octavia.rst @@ -1,5 +1,7 @@ octavia ------- +.. TODO(efried): cut over to autoprogram-cliff once doc build is fixed + .. list-plugins:: openstack.load_balancer.v2 :detailed: diff --git a/doc/source/cli/plugin-commands/placement.rst b/doc/source/cli/plugin-commands/placement.rst index 972818c768..203ecc2992 100644 --- a/doc/source/cli/plugin-commands/placement.rst +++ b/doc/source/cli/plugin-commands/placement.rst @@ -1,5 +1,4 @@ placement --------- -.. list-plugins:: openstack.placement.v1 - :detailed: +.. autoprogram-cliff:: openstack.placement.v1 diff --git a/doc/source/cli/plugin-commands/rsd.rst b/doc/source/cli/plugin-commands/rsd.rst index d7962014e5..9436c5ae75 100644 --- a/doc/source/cli/plugin-commands/rsd.rst +++ b/doc/source/cli/plugin-commands/rsd.rst @@ -1,5 +1,7 @@ rsd --- +.. TODO(efried): Cut over to autoprogram-cliff once rsd plugin docs build + .. list-plugins:: openstack.rsd.v1 :detailed: diff --git a/doc/source/cli/plugin-commands/sahara.rst b/doc/source/cli/plugin-commands/sahara.rst index 28ac867828..7c51756a3a 100644 --- a/doc/source/cli/plugin-commands/sahara.rst +++ b/doc/source/cli/plugin-commands/sahara.rst @@ -1,5 +1,4 @@ sahara ------ -.. list-plugins:: openstack.data_processing.v1 - :detailed: +.. autoprogram-cliff:: openstack.data_processing.v1 diff --git a/doc/source/cli/plugin-commands/searchlight.rst b/doc/source/cli/plugin-commands/searchlight.rst index fed56f0912..be934aeb36 100644 --- a/doc/source/cli/plugin-commands/searchlight.rst +++ b/doc/source/cli/plugin-commands/searchlight.rst @@ -1,5 +1,4 @@ searchlight ----------- -.. list-plugins:: openstack.search.v1 - :detailed: +.. autoprogram-cliff:: openstack.search.v1 diff --git a/doc/source/cli/plugin-commands/senlin.rst b/doc/source/cli/plugin-commands/senlin.rst index f5f81d9e13..90929058f8 100644 --- a/doc/source/cli/plugin-commands/senlin.rst +++ b/doc/source/cli/plugin-commands/senlin.rst @@ -1,5 +1,4 @@ senlin ------ -.. list-plugins:: openstack.clustering.v1 - :detailed: +.. autoprogram-cliff:: openstack.clustering.v1 diff --git a/doc/source/cli/plugin-commands/trove.rst b/doc/source/cli/plugin-commands/trove.rst index b4575edd8a..75da22726c 100644 --- a/doc/source/cli/plugin-commands/trove.rst +++ b/doc/source/cli/plugin-commands/trove.rst @@ -1,5 +1,7 @@ trove ----- +.. TODO(efried): cut over to autoprogram-cliff once doc build is fixed + .. list-plugins:: openstack.database.v1 :detailed: diff --git a/doc/source/cli/plugin-commands/zaqar.rst b/doc/source/cli/plugin-commands/zaqar.rst index 3649a274d3..6415149029 100644 --- a/doc/source/cli/plugin-commands/zaqar.rst +++ b/doc/source/cli/plugin-commands/zaqar.rst @@ -1,5 +1,4 @@ zaqar ----- -.. list-plugins:: openstack.messaging.v2 - :detailed: +.. autoprogram-cliff:: openstack.messaging.v2 diff --git a/doc/source/cli/plugin-commands/zun.rst b/doc/source/cli/plugin-commands/zun.rst index 56c5ff59ce..40480e70ca 100644 --- a/doc/source/cli/plugin-commands/zun.rst +++ b/doc/source/cli/plugin-commands/zun.rst @@ -1,5 +1,7 @@ zun --- +.. TODO(efried): cut over to autoprogram-cliff once doc build is fixed + .. list-plugins:: openstack.container.v1 :detailed: From 67a5654d4977998c8c767e2d5c595fba5b12164f Mon Sep 17 00:00:00 2001 From: Eric Fried Date: Mon, 21 Oct 2019 18:08:41 -0500 Subject: [PATCH 2123/3095] Add plugin doc page for watcher This was being omitted because whenever the plugin page was produced, python-watcherclient wasn't in global-requirements. It is now, so include a page for it in the plugin docs. NOTE: We would like to use autoprogram-cliff to make the documentation complete, but that breaks the build. For now, this is better than nothing. Change-Id: I49822242b9a0c031a053d6c2fd9f644a585f4ba5 --- doc/requirements.txt | 1 + doc/source/cli/plugin-commands/index.rst | 6 +----- doc/source/cli/plugin-commands/watcher.rst | 7 +++++++ lower-constraints.txt | 1 + 4 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 doc/source/cli/plugin-commands/watcher.rst diff --git a/doc/requirements.txt b/doc/requirements.txt index 12ed5f01a2..d603c1d4b7 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -27,5 +27,6 @@ python-saharaclient>=1.4.0 # Apache-2.0 python-searchlightclient>=1.0.0 #Apache-2.0 python-senlinclient>=1.1.0 # Apache-2.0 python-troveclient>=2.2.0 # Apache-2.0 +python-watcherclient>=2.4.0 # Apache-2.0 python-zaqarclient>=1.0.0 # Apache-2.0 python-zunclient>=3.4.0 # Apache-2.0 diff --git a/doc/source/cli/plugin-commands/index.rst b/doc/source/cli/plugin-commands/index.rst index f6ff51bd63..2c3bda3e0f 100644 --- a/doc/source/cli/plugin-commands/index.rst +++ b/doc/source/cli/plugin-commands/index.rst @@ -25,6 +25,7 @@ Plugin Commands searchlight senlin trove + watcher zaqar zun @@ -44,8 +45,3 @@ Plugin Commands .. # tripleoclient is not in global-requirements .. # list-plugins:: openstack.tripleoclient.v1 .. # :detailed: - -.. watcher -.. # watcherclient is not in global-requirements -.. # list-plugins:: openstack.infra_optim.v1 -.. # :detailed: diff --git a/doc/source/cli/plugin-commands/watcher.rst b/doc/source/cli/plugin-commands/watcher.rst new file mode 100644 index 0000000000..7f9afbdc26 --- /dev/null +++ b/doc/source/cli/plugin-commands/watcher.rst @@ -0,0 +1,7 @@ +watcher +------- + +.. TODO(efried): cut over to autoprogram-cliff once doc build is fixed + +.. list-plugins:: openstack.infra_optim.v1 + :detailed: diff --git a/lower-constraints.txt b/lower-constraints.txt index db0562e124..6533edafc3 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -109,6 +109,7 @@ python-senlinclient==1.1.0 python-subunit==1.0.0 python-swiftclient==3.2.0 python-troveclient==2.2.0 +python-watcherclient==2.4.0 python-zaqarclient==1.0.0 python-zunclient==3.4.0 pytz==2013.6 From fcae62841f9a0ddc70a7e5be5202eea379378d70 Mon Sep 17 00:00:00 2001 From: Eric Fried Date: Tue, 22 Oct 2019 08:08:28 -0500 Subject: [PATCH 2124/3095] Link to (some) plugin doc pages We would like to use autoprogram-cliff to generate full docs inline for each plugin. But for the following projects, that breaks the build: - octavia - rsd - trove - watcher - zun For those projects, we're using list-plugins instead, because that builds, and it's better than nothing; but it only provides summaries of the commands. So with this commit, we add a link to the plugin documentation from the actual plugin project where such documentation exists, which currently is just: - octavia - watcher - zun (For rsd, I couldn't find openstack-published docs at all; for trove, published docs exist, but the osc plugin isn't documented.) Change-Id: I7c826ecef4319bead239e11b5f975302b2f24d1b Story: #1735016 Task: #37244 --- doc/source/cli/plugin-commands/octavia.rst | 3 +++ doc/source/cli/plugin-commands/watcher.rst | 3 +++ doc/source/cli/plugin-commands/zun.rst | 3 +++ doc/source/conf.py | 6 ++++++ 4 files changed, 15 insertions(+) diff --git a/doc/source/cli/plugin-commands/octavia.rst b/doc/source/cli/plugin-commands/octavia.rst index fb48f0ac7e..ce6259dd25 100644 --- a/doc/source/cli/plugin-commands/octavia.rst +++ b/doc/source/cli/plugin-commands/octavia.rst @@ -3,5 +3,8 @@ octavia .. TODO(efried): cut over to autoprogram-cliff once doc build is fixed +For more details, see the :python-octaviaclient-doc:`python-octaviaclient +plugin documentation `. + .. list-plugins:: openstack.load_balancer.v2 :detailed: diff --git a/doc/source/cli/plugin-commands/watcher.rst b/doc/source/cli/plugin-commands/watcher.rst index 7f9afbdc26..c888d3a153 100644 --- a/doc/source/cli/plugin-commands/watcher.rst +++ b/doc/source/cli/plugin-commands/watcher.rst @@ -3,5 +3,8 @@ watcher .. TODO(efried): cut over to autoprogram-cliff once doc build is fixed +For more details, see the :python-watcherclient-doc:`python-watcherclient +plugin documentation `. + .. list-plugins:: openstack.infra_optim.v1 :detailed: diff --git a/doc/source/cli/plugin-commands/zun.rst b/doc/source/cli/plugin-commands/zun.rst index 40480e70ca..69f4f62c84 100644 --- a/doc/source/cli/plugin-commands/zun.rst +++ b/doc/source/cli/plugin-commands/zun.rst @@ -3,5 +3,8 @@ zun .. TODO(efried): cut over to autoprogram-cliff once doc build is fixed +For more details, see the :python-zunclient-doc:`python-zunclient plugin +documentation `. + .. list-plugins:: openstack.container.v1 :detailed: diff --git a/doc/source/conf.py b/doc/source/conf.py index 45045002aa..92febda46d 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -34,6 +34,12 @@ repository_name = 'openstack/python-openstackclient' use_storyboard = True +openstack_projects = [ + 'python-octaviaclient', + 'python-watcherclient', + 'python-zunclient', +] + # Add any paths that contain templates here, relative to this directory. #templates_path = ['_templates'] From d0bfef807d5597f32f8eda5ab3fae2d1f5924ecc Mon Sep 17 00:00:00 2001 From: Eric Fried Date: Tue, 22 Oct 2019 07:35:37 -0500 Subject: [PATCH 2125/3095] Doc: launchpad => storyboard A few docs still referred to launchpad, which osc hasn't used in a couple of years. Cut over to storyboard. Change-Id: Ic9abf0fe1e52c255976bd7a019e999a8e610455e --- CONTRIBUTING.rst | 4 ++-- README.rst | 8 +++----- doc/source/index.rst | 10 +++++----- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index de9324ec7f..dfdfe471a7 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -11,6 +11,6 @@ the workflow documented at: Pull requests submitted through GitHub will be ignored. -Bugs should be filed on Launchpad, not GitHub: +Bugs should be filed on Storyboard, not GitHub or Launchpad: - https://bugs.launchpad.net/python-openstackclient + https://storyboard.openstack.org/#!/project/openstack/python-openstackclient diff --git a/README.rst b/README.rst index cd80079fab..41d0112428 100644 --- a/README.rst +++ b/README.rst @@ -24,9 +24,8 @@ language to describe operations in OpenStack. * `PyPi`_ - package installation * `Online Documentation`_ -* `Launchpad project`_ - release management -* `Blueprints`_ - feature specifications -* `Bugs`_ - issue tracking +* `Storyboard project`_ - bugs and feature requests +* `Blueprints`_ - feature specifications (historical only) * `Source`_ * `Developer`_ - getting started as a developer * `Contributing`_ - contributing code @@ -36,9 +35,8 @@ language to describe operations in OpenStack. .. _PyPi: https://pypi.org/project/python-openstackclient .. _Online Documentation: https://docs.openstack.org/python-openstackclient/latest/ -.. _Launchpad project: https://launchpad.net/python-openstackclient .. _Blueprints: https://blueprints.launchpad.net/python-openstackclient -.. _Bugs: https://storyboard.openstack.org/#!/project/975 +.. _`Storyboard project`: https://storyboard.openstack.org/#!/project/openstack/python-openstackclient .. _Source: https://opendev.org/openstack/python-openstackclient .. _Developer: https://docs.openstack.org/project-team-guide/project-setup/python.html .. _Contributing: https://docs.openstack.org/infra/manual/developers.html diff --git a/doc/source/index.rst b/doc/source/index.rst index ea4cd3e9f9..732e13872a 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -55,13 +55,13 @@ Contributing OpenStackClient utilizes all of the usual OpenStack processes and requirements for contributions. The code is hosted `on OpenStack's Git server`_. `Bug reports`_ -may be submitted to the :code:`python-openstackclient` Storyboard project -on `Launchpad`_. Code may be submitted to the -:code:`openstack/python-openstackclient` project using `Gerrit`_. -Developers may also be found in the `IRC channel`_ ``#openstack-sdks``. +may be submitted to the :code:`python-openstackclient` `Storyboard project`_. +Code may be submitted to the :code:`openstack/python-openstackclient` project +using `Gerrit`_. Developers may also be found in the `IRC channel`_ +``#openstack-sdks``. .. _`on OpenStack's Git server`: https://opendev.org/openstack/python-openstackclient/ -.. _Launchpad: https://launchpad.net/python-openstackclient +.. _`Storyboard project`: https://storyboard.openstack.org/#!/project/openstack/python-openstackclient .. _Gerrit: http://docs.openstack.org/infra/manual/developers.html#development-workflow .. _Bug reports: https://storyboard.openstack.org/#!/project/975 .. _PyPi: https://pypi.org/project/python-openstackclient From 977b0c8591e40d3f59bbc1af9adc75f2c2edb694 Mon Sep 17 00:00:00 2001 From: Eric Fried Date: Tue, 22 Oct 2019 14:18:51 -0500 Subject: [PATCH 2126/3095] Use autoprogram-cliff for remaining plugin docs Sphinx errors have been fixed in the plugin projects for octavia, rsd, trove, watcher, and zun, so we can now use autoprogram-cliff to generate the docs for those. Change-Id: Ia7790c5e86957afd0aec8f9a04ffc7aa968b4eeb Story: #1735016 Task: #37241 --- doc/requirements.txt | 10 +++++----- doc/source/cli/plugin-commands/octavia.rst | 8 +------- doc/source/cli/plugin-commands/rsd.rst | 5 +---- doc/source/cli/plugin-commands/trove.rst | 5 +---- doc/source/cli/plugin-commands/watcher.rst | 8 +------- doc/source/cli/plugin-commands/zun.rst | 8 +------- doc/source/conf.py | 7 ++----- lower-constraints.txt | 10 +++++----- 8 files changed, 17 insertions(+), 44 deletions(-) diff --git a/doc/requirements.txt b/doc/requirements.txt index d603c1d4b7..ec692e75a0 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -21,12 +21,12 @@ python-karborclient>=0.6.0 # Apache-2.0 python-mistralclient!=3.2.0,>=3.1.0 # Apache-2.0 python-muranoclient>=0.8.2 # Apache-2.0 python-neutronclient>=6.7.0 # Apache-2.0 -python-octaviaclient>=1.3.0 # Apache-2.0 -python-rsdclient>=0.1.0 # Apache-2.0 +python-octaviaclient>=1.11.0 # Apache-2.0 +python-rsdclient>=1.0.1 # Apache-2.0 python-saharaclient>=1.4.0 # Apache-2.0 python-searchlightclient>=1.0.0 #Apache-2.0 python-senlinclient>=1.1.0 # Apache-2.0 -python-troveclient>=2.2.0 # Apache-2.0 -python-watcherclient>=2.4.0 # Apache-2.0 +python-troveclient>=3.1.0 # Apache-2.0 +python-watcherclient>=2.5.0 # Apache-2.0 python-zaqarclient>=1.0.0 # Apache-2.0 -python-zunclient>=3.4.0 # Apache-2.0 +python-zunclient>=3.6.0 # Apache-2.0 diff --git a/doc/source/cli/plugin-commands/octavia.rst b/doc/source/cli/plugin-commands/octavia.rst index ce6259dd25..640af1ace8 100644 --- a/doc/source/cli/plugin-commands/octavia.rst +++ b/doc/source/cli/plugin-commands/octavia.rst @@ -1,10 +1,4 @@ octavia ------- -.. TODO(efried): cut over to autoprogram-cliff once doc build is fixed - -For more details, see the :python-octaviaclient-doc:`python-octaviaclient -plugin documentation `. - -.. list-plugins:: openstack.load_balancer.v2 - :detailed: +.. autoprogram-cliff:: openstack.load_balancer.v2 diff --git a/doc/source/cli/plugin-commands/rsd.rst b/doc/source/cli/plugin-commands/rsd.rst index 9436c5ae75..d28cea316f 100644 --- a/doc/source/cli/plugin-commands/rsd.rst +++ b/doc/source/cli/plugin-commands/rsd.rst @@ -1,7 +1,4 @@ rsd --- -.. TODO(efried): Cut over to autoprogram-cliff once rsd plugin docs build - -.. list-plugins:: openstack.rsd.v1 - :detailed: +.. autoprogram-cliff:: openstack.rsd.v2 diff --git a/doc/source/cli/plugin-commands/trove.rst b/doc/source/cli/plugin-commands/trove.rst index 75da22726c..43560d4e2b 100644 --- a/doc/source/cli/plugin-commands/trove.rst +++ b/doc/source/cli/plugin-commands/trove.rst @@ -1,7 +1,4 @@ trove ----- -.. TODO(efried): cut over to autoprogram-cliff once doc build is fixed - -.. list-plugins:: openstack.database.v1 - :detailed: +.. autoprogram-cliff:: openstack.database.v1 diff --git a/doc/source/cli/plugin-commands/watcher.rst b/doc/source/cli/plugin-commands/watcher.rst index c888d3a153..9cf6dfeea0 100644 --- a/doc/source/cli/plugin-commands/watcher.rst +++ b/doc/source/cli/plugin-commands/watcher.rst @@ -1,10 +1,4 @@ watcher ------- -.. TODO(efried): cut over to autoprogram-cliff once doc build is fixed - -For more details, see the :python-watcherclient-doc:`python-watcherclient -plugin documentation `. - -.. list-plugins:: openstack.infra_optim.v1 - :detailed: +.. autoprogram-cliff:: openstack.infra_optim.v1 diff --git a/doc/source/cli/plugin-commands/zun.rst b/doc/source/cli/plugin-commands/zun.rst index 69f4f62c84..17baac557a 100644 --- a/doc/source/cli/plugin-commands/zun.rst +++ b/doc/source/cli/plugin-commands/zun.rst @@ -1,10 +1,4 @@ zun --- -.. TODO(efried): cut over to autoprogram-cliff once doc build is fixed - -For more details, see the :python-zunclient-doc:`python-zunclient plugin -documentation `. - -.. list-plugins:: openstack.container.v1 - :detailed: +.. autoprogram-cliff:: openstack.container.v1 diff --git a/doc/source/conf.py b/doc/source/conf.py index 92febda46d..d3dfccd235 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -34,11 +34,8 @@ repository_name = 'openstack/python-openstackclient' use_storyboard = True -openstack_projects = [ - 'python-octaviaclient', - 'python-watcherclient', - 'python-zunclient', -] +# Add project 'foo' to this list to enable the :foo-doc: role +# openstack_projects = [] # Add any paths that contain templates here, relative to this directory. #templates_path = ['_templates'] diff --git a/lower-constraints.txt b/lower-constraints.txt index 6533edafc3..201726ac65 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -101,17 +101,17 @@ python-mistralclient==3.1.0 python-muranoclient==0.8.2 python-neutronclient==6.7.0 python-novaclient==15.0.0 -python-octaviaclient==1.3.0 -python-rsdclient==0.1.0 +python-octaviaclient==1.11.0 +python-rsdclient==1.0.1 python-saharaclient==1.4.0 python-searchlightclient==1.0.0 python-senlinclient==1.1.0 python-subunit==1.0.0 python-swiftclient==3.2.0 -python-troveclient==2.2.0 -python-watcherclient==2.4.0 +python-troveclient==3.1.0 +python-watcherclient==2.5.0 python-zaqarclient==1.0.0 -python-zunclient==3.4.0 +python-zunclient==3.6.0 pytz==2013.6 PyYAML==3.12 repoze.lru==0.7 From f1d742f32adeb662a3fdf8fa3ef3bc391e71ed81 Mon Sep 17 00:00:00 2001 From: Eric Fried Date: Tue, 29 Oct 2019 15:55:11 -0500 Subject: [PATCH 2127/3095] Fix functional tests for py3 Fix various things so the functional tests will work under python3: - A hashlib.md5() can only be update()d with an encoded string in py3. - There's no dict.iteritems(), change to dict.items() (which is already an iterator). - Open temp files with 'w+' mode rather than the default 'w+b' (as an alternative to encoding all the write and expected-read payloads as bytes). - (This is a weird one) Explicitly raise SkipTest from unittest (rather than unittest2, which is where cls.skipException landed). Not sure why this is busted, but this moves the ball. Change-Id: Ic9b2b47848a600e87a3674289ae7ae8c3e091fee --- openstackclient/tests/functional/compute/v2/test_agent.py | 4 ++-- openstackclient/tests/functional/compute/v2/test_keypair.py | 4 ++-- openstackclient/tests/functional/identity/v2/common.py | 3 ++- openstackclient/tests/functional/identity/v3/common.py | 2 +- openstackclient/tests/functional/object/v1/test_object.py | 2 +- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/openstackclient/tests/functional/compute/v2/test_agent.py b/openstackclient/tests/functional/compute/v2/test_agent.py index 1a112e8212..25d8c868dc 100644 --- a/openstackclient/tests/functional/compute/v2/test_agent.py +++ b/openstackclient/tests/functional/compute/v2/test_agent.py @@ -21,10 +21,10 @@ class ComputeAgentTests(base.TestCase): # Generate two different md5hash MD5HASH1 = hashlib.md5() - MD5HASH1.update('agent_1') + MD5HASH1.update('agent_1'.encode('utf-8')) MD5HASH1 = MD5HASH1.hexdigest() MD5HASH2 = hashlib.md5() - MD5HASH2.update('agent_2') + MD5HASH2.update('agent_2'.encode('utf-8')) MD5HASH2 = MD5HASH2.hexdigest() def test_compute_agent_delete(self): diff --git a/openstackclient/tests/functional/compute/v2/test_keypair.py b/openstackclient/tests/functional/compute/v2/test_keypair.py index 9a88e66fca..42f334a46a 100644 --- a/openstackclient/tests/functional/compute/v2/test_keypair.py +++ b/openstackclient/tests/functional/compute/v2/test_keypair.py @@ -88,7 +88,7 @@ def test_keypair_create_public_key(self): 1) Create keypair with given public key 2) Delete keypair """ - with tempfile.NamedTemporaryFile() as f: + with tempfile.NamedTemporaryFile(mode='w+') as f: f.write(self.PUBLIC_KEY) f.flush() @@ -108,7 +108,7 @@ def test_keypair_create_private_key(self): 1) Create keypair with private key file 2) Delete keypair """ - with tempfile.NamedTemporaryFile() as f: + with tempfile.NamedTemporaryFile(mode='w+') as f: cmd_output = json.loads(self.openstack( 'keypair create -f json --private-key %s tmpkey' % f.name, )) diff --git a/openstackclient/tests/functional/identity/v2/common.py b/openstackclient/tests/functional/identity/v2/common.py index f4bc10bddd..43c0cbf273 100644 --- a/openstackclient/tests/functional/identity/v2/common.py +++ b/openstackclient/tests/functional/identity/v2/common.py @@ -11,6 +11,7 @@ # under the License. import os +import unittest import fixtures from tempest.lib.common.utils import data_utils @@ -62,7 +63,7 @@ def setUpClass(cls): # TODO(dtroyer): Actually determine if Identity v2 admin is # enabled in the target cloud. Tuens out OSC # doesn't make this easy as it should (yet). - raise cls.skipException('No Identity v2 admin endpoint?') + raise unittest.case.SkipTest('No Identity v2 admin endpoint?') @classmethod def tearDownClass(cls): diff --git a/openstackclient/tests/functional/identity/v3/common.py b/openstackclient/tests/functional/identity/v3/common.py index 43b416aabe..86f090bcc0 100644 --- a/openstackclient/tests/functional/identity/v3/common.py +++ b/openstackclient/tests/functional/identity/v3/common.py @@ -360,7 +360,7 @@ def _create_dummy_registered_limit(self, add_clean_up=True): def _extract_value_from_items(self, key, items): for d in items: - for k, v in d.iteritems(): + for k, v in d.items(): if k == key: return v diff --git a/openstackclient/tests/functional/object/v1/test_object.py b/openstackclient/tests/functional/object/v1/test_object.py index 226ef8adbd..b3f23e5287 100644 --- a/openstackclient/tests/functional/object/v1/test_object.py +++ b/openstackclient/tests/functional/object/v1/test_object.py @@ -33,7 +33,7 @@ def setUp(self): self.skipTest("No object-store service present") def test_object(self): - with tempfile.NamedTemporaryFile() as f: + with tempfile.NamedTemporaryFile(mode='w+') as f: f.write('test content') f.flush() self._test_object(f.name) From 4b393681d98e6a78248f6fa36e576a9f09c4ee34 Mon Sep 17 00:00:00 2001 From: Matt Riedemann Date: Thu, 24 Oct 2019 13:58:12 -0400 Subject: [PATCH 2128/3095] Use SDK to get compute API extensions python-novaclient 16.0.0 removed the deprecated list_extensions module [1] so this changes the extensions command to use openstacksdk to get the compute API extensions. The functional test ExtensionTests.test_extension_list_compute ensures this works. [1] https://review.opendev.org/686516/ Change-Id: I9894bc395c0474aaa6494ac4534862efe4ea7984 Story: #2006769 Task: #37284 --- openstackclient/common/extension.py | 4 ++-- .../tests/unit/common/test_extension.py | 21 +++++++------------ 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/openstackclient/common/extension.py b/openstackclient/common/extension.py index 7120661880..1ed2012c10 100644 --- a/openstackclient/common/extension.py +++ b/openstackclient/common/extension.py @@ -89,9 +89,9 @@ def take_action(self, parsed_args): LOG.warning(message) if parsed_args.compute or show_all: - compute_client = self.app.client_manager.compute + compute_client = self.app.client_manager.sdk_connection.compute try: - data += compute_client.list_extensions.show_all() + data += compute_client.extensions() except Exception: message = _("Extensions list not supported by Compute API") LOG.warning(message) diff --git a/openstackclient/tests/unit/common/test_extension.py b/openstackclient/tests/unit/common/test_extension.py index eefa814c49..87c62da499 100644 --- a/openstackclient/tests/unit/common/test_extension.py +++ b/openstackclient/tests/unit/common/test_extension.py @@ -36,13 +36,9 @@ def setUp(self): self.identity_extensions_mock = identity_client.extensions self.identity_extensions_mock.reset_mock() - compute_client = compute_fakes.FakeComputev2Client( - endpoint=fakes.AUTH_URL, - token=fakes.AUTH_TOKEN, - ) - self.app.client_manager.compute = compute_client - compute_client.list_extensions = mock.Mock() - self.compute_extensions_mock = compute_client.list_extensions + sdk_connection = mock.Mock() + self.app.client_manager.sdk_connection = sdk_connection + self.compute_extensions_mock = sdk_connection.compute.extensions self.compute_extensions_mock.reset_mock() volume_client = volume_fakes.FakeVolumeClient( @@ -80,8 +76,7 @@ def setUp(self): self.identity_extensions_mock.list.return_value = [ self.identity_extension] - self.compute_extensions_mock.show_all.return_value = [ - self.compute_extension] + self.compute_extensions_mock.return_value = [self.compute_extension] self.volume_extensions_mock.show_all.return_value = [ self.volume_extension] self.network_extensions_mock.return_value = [self.network_extension] @@ -131,7 +126,7 @@ def test_extension_list_no_options(self): ) self._test_extension_list_helper(arglist, verifylist, datalist) self.identity_extensions_mock.list.assert_called_with() - self.compute_extensions_mock.show_all.assert_called_with() + self.compute_extensions_mock.assert_called_with() self.volume_extensions_mock.show_all.assert_called_with() self.network_extensions_mock.assert_called_with() @@ -178,7 +173,7 @@ def test_extension_list_long(self): ) self._test_extension_list_helper(arglist, verifylist, datalist, True) self.identity_extensions_mock.list.assert_called_with() - self.compute_extensions_mock.show_all.assert_called_with() + self.compute_extensions_mock.assert_called_with() self.volume_extensions_mock.show_all.assert_called_with() self.network_extensions_mock.assert_called_with() @@ -248,7 +243,7 @@ def test_extension_list_compute(self): self.compute_extension.description, ), ) self._test_extension_list_helper(arglist, verifylist, datalist) - self.compute_extensions_mock.show_all.assert_called_with() + self.compute_extensions_mock.assert_called_with() def test_extension_list_compute_and_network(self): arglist = [ @@ -272,7 +267,7 @@ def test_extension_list_compute_and_network(self): ), ) self._test_extension_list_helper(arglist, verifylist, datalist) - self.compute_extensions_mock.show_all.assert_called_with() + self.compute_extensions_mock.assert_called_with() self.network_extensions_mock.assert_called_with() def test_extension_list_volume(self): From 40f74816c11c2d46eb0595795ef3611c18920482 Mon Sep 17 00:00:00 2001 From: Eric Fried Date: Tue, 29 Oct 2019 08:55:51 -0500 Subject: [PATCH 2129/3095] Remove redundant OpenStackShell.prepare_to_run_command osc-lib's OpenStackShell.prepare_to_run_command has been a superset of python-openstackclient's since at least osc-lib 1.4.0. We require 1.14.0 now, so the redundant override can be removed. Change-Id: I5658e3df5af1100e139623505d0375588edae63c --- openstackclient/shell.py | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 7fbda1aee5..755af24d2e 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -139,32 +139,6 @@ def initialize_app(self, argv): pw_func=shell.prompt_for_password, ) - def prepare_to_run_command(self, cmd): - """Set up auth and API versions""" - - # TODO(dtroyer): Move this to osc-lib, remove entire method when 1.4.0 - # release is minimum version is in global-requirements - # NOTE(dtroyer): If auth is not required for a command, skip - # get_one_Cloud()'s validation to avoid loading plugins - validate = cmd.auth_required - - # Force skipping auth for commands that do not need it - # NOTE(dtroyer): This is here because ClientManager does not have - # visibility into the Command object to get - # auth_required. It needs to move into osc-lib - self.client_manager._auth_required = cmd.auth_required - - # Validate auth options - self.cloud = self.cloud_config.get_one_cloud( - cloud=self.options.cloud, - argparse=self.options, - validate=validate, - ) - # Push the updated args into ClientManager - self.client_manager._cli_options = self.cloud - - return super(OpenStackShell, self).prepare_to_run_command(cmd) - def main(argv=None): if argv is None: From 61ad83b57580c76a1c448e03064c4df6bcc01e87 Mon Sep 17 00:00:00 2001 From: Pierre Prinetti Date: Thu, 3 Oct 2019 15:53:39 +0200 Subject: [PATCH 2130/3095] versions: Fix 'versions show' help message The command `openstack versions show --help` shows a copy-paste nit. Change-Id: I9e4e86429ffd630c566bbdf2929e7995c9b0dbe1 Signed-off-by: Pierre Prinetti --- doc/source/cli/command-objects/versions.rst | 12 +++++++++++- openstackclient/common/versions.py | 4 ++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/doc/source/cli/command-objects/versions.rst b/doc/source/cli/command-objects/versions.rst index 6742565265..0aa9a1b785 100644 --- a/doc/source/cli/command-objects/versions.rst +++ b/doc/source/cli/command-objects/versions.rst @@ -17,6 +17,7 @@ Show service versions: [--interface ] [--region-name ] [--service ] + [--status ] .. option:: --all-interfaces @@ -30,7 +31,7 @@ Show service versions: .. option:: --region-name - Limit results to only those from region-name + Limit results to only those from a given region. .. option:: --service @@ -38,4 +39,13 @@ Show service versions: an exact match to what is in the catalog or a known official value or alias from `service-types-authority`_. +.. option:: --status + + Limit results to only those in the given state. Valid values are: + + - SUPPORTED + - CURRENT + - DEPRECATED + - EXPERIMENTAL + .. _service-types-authority: https://service-types.openstack.org/ diff --git a/openstackclient/common/versions.py b/openstackclient/common/versions.py index 3c267bfe0a..e6781bc66f 100644 --- a/openstackclient/common/versions.py +++ b/openstackclient/common/versions.py @@ -45,12 +45,12 @@ def get_parser(self, prog_name): ) parser.add_argument( '--service', - metavar='', + metavar='', help=_('Show versions for a specific service.'), ) parser.add_argument( '--status', - metavar='', + metavar='', help=_('Show versions for a specific status.' ' [Valid values are SUPPORTED, CURRENT,' ' DEPRECATED, EXPERIMENTAL]'), From cd6c285cc6c2274e6b42cc452ba4a61a3487ca23 Mon Sep 17 00:00:00 2001 From: Eric Fried Date: Mon, 28 Oct 2019 17:27:38 -0500 Subject: [PATCH 2131/3095] neutron: autogenerate docs $namespace = openstack.network.v2 The subcommand documents for $namespace were hardcoded and thus prone to drift over time. This commit removes the hardcoded content and uses the autoprogram-cliff directive to generate them automatically from the subcommand configuration classes. This one turned out to be quite involved, because we support both neutron and nova-network. When running in a real cloud, the command classes detect whether the neutron service is present, assume nova-network if that service is not found, and only add parser options relevant to the detected service. But the docs need to present both sets of options. This was easy enough when they were hardcoded, but required a bit of additional infrastructure for generated docs. Change-Id: I426261eb1d86bcc68656aabd61f10b7f082da402 --- .../cli/command-objects/address-scope.rst | 142 +---- .../cli/command-objects/floating-ip-pool.rst | 11 +- .../floating-ip-port-forwarding.rst | 168 +----- .../cli/command-objects/floating-ip.rst | 303 +---------- .../cli/command-objects/ip-availability.rst | 56 +- .../cli/command-objects/network-agent.rst | 206 +------ .../network-auto-allocated-topology.rst | 59 +- .../network-flavor-profile.rst | 134 +---- .../cli/command-objects/network-flavor.rst | 182 +------ .../command-objects/network-meter-rule.rst | 92 +--- .../cli/command-objects/network-meter.rst | 87 +-- .../command-objects/network-qos-policy.rst | 156 +----- .../command-objects/network-qos-rule-type.rst | 26 +- .../cli/command-objects/network-qos-rule.rst | 161 +----- .../cli/command-objects/network-rbac.rst | 135 +---- .../cli/command-objects/network-segment.rst | 129 +---- .../network-service-provider.rst | 11 +- doc/source/cli/command-objects/network.rst | 510 +----------------- .../command-objects/network_segment_range.rst | 158 +----- doc/source/cli/command-objects/port.rst | 485 +---------------- doc/source/cli/command-objects/router.rst | 401 +------------- .../command-objects/security-group-rule.rst | 193 +------ .../cli/command-objects/security-group.rst | 202 +------ .../cli/command-objects/subnet-pool.rst | 303 +---------- doc/source/cli/command-objects/subnet.rst | 432 +-------------- openstackclient/identity/common.py | 8 +- openstackclient/network/common.py | 199 +++---- openstackclient/network/v2/_tag.py | 46 +- openstackclient/network/v2/floating_ip.py | 74 ++- openstackclient/network/v2/network.py | 109 ++-- openstackclient/network/v2/security_group.py | 46 +- .../network/v2/security_group_rule.py | 205 +++---- 32 files changed, 525 insertions(+), 4904 deletions(-) diff --git a/doc/source/cli/command-objects/address-scope.rst b/doc/source/cli/command-objects/address-scope.rst index 9155d09ed1..38141d5c97 100644 --- a/doc/source/cli/command-objects/address-scope.rst +++ b/doc/source/cli/command-objects/address-scope.rst @@ -7,143 +7,5 @@ to a given project and may be shared between projects. Network v2 -address scope create --------------------- - -Create new address scope - -.. program:: address scope create -.. code:: bash - - openstack address scope create - [--project [--project-domain ]] - [--ip-version ] - [--share | --no-share] - - -.. option:: --project - - Owner's project (name or ID) - -.. option:: --project-domain - - Domain the project belongs to (name or ID). - This can be used in case collisions between project names exist. - -.. option:: --ip-version - - IP version (4 or 6, default is 4) - -.. option:: --share - - Share the address scope between projects - -.. option:: --no-share - - Do not share the address scope between projects (default) - -.. _address_scope_create-name: -.. describe:: - - New address scope name - -address scope delete --------------------- - -Delete address scope(s) - -.. program:: address scope delete -.. code:: bash - - openstack address scope delete - [ ...] - -.. _address_scope_delete-address-scope: -.. describe:: - - Address scope(s) to delete (name or ID) - -address scope list ------------------- - -List address scopes - -.. program:: address scope list -.. code:: bash - - openstack address scope list - [--name ] - [--ip-version ] - [--project [--project-domain ]] - [--share | --no-share] - -.. option:: --name - - List only address scopes of given name in output - -.. option:: --ip-version - - List address scopes of given IP version networks (4 or 6) - -.. option:: --project - - List address scopes according to their project (name or ID) - -.. option:: --project-domain - - Domain the project belongs to (name or ID). - This can be used in case collisions between project names exist. - -.. option:: --share - - List address scopes shared between projects - -.. option:: --no-share - - List address scopes not shared between projects - -address scope set ------------------ - -Set address scope properties - -.. program:: address scope set -.. code:: bash - - openstack address scope set - [--name ] - [--share | --no-share] - - -.. option:: --name - - Set address scope name - -.. option:: --share - - Share the address scope between projects - -.. option:: --no-share - - Do not share the address scope between projects - -.. _address_scope_set-address-scope: -.. describe:: - - Address scope to modify (name or ID) - -address scope show ------------------- - -Display address scope details - -.. program:: address scope show -.. code:: bash - - openstack address scope show - - -.. _address_scope_show-address-scope: -.. describe:: - - Address scope to display (name or ID) +.. autoprogram-cliff:: openstack.network.v2 + :command: address scope * diff --git a/doc/source/cli/command-objects/floating-ip-pool.rst b/doc/source/cli/command-objects/floating-ip-pool.rst index 5c8f3aa13d..ab6f83a993 100644 --- a/doc/source/cli/command-objects/floating-ip-pool.rst +++ b/doc/source/cli/command-objects/floating-ip-pool.rst @@ -4,12 +4,5 @@ floating ip pool Network v2 -floating ip pool list ---------------------- - -List pools of floating IP addresses - -.. program:: floating ip pool list -.. code:: bash - - openstack floating ip pool list +.. autoprogram-cliff:: openstack.network.v2 + :command: floating ip pool * diff --git a/doc/source/cli/command-objects/floating-ip-port-forwarding.rst b/doc/source/cli/command-objects/floating-ip-port-forwarding.rst index 5012787aaf..052ea0cc7f 100644 --- a/doc/source/cli/command-objects/floating-ip-port-forwarding.rst +++ b/doc/source/cli/command-objects/floating-ip-port-forwarding.rst @@ -4,170 +4,6 @@ floating ip port forwarding Network v2 -floating ip port forwarding create ----------------------------------- +.. autoprogram-cliff:: openstack.network.v2 + :command: floating ip port forwarding * -Create floating IP Port Forwarding - -.. program:: floating ip port forwarding create -.. code:: bash - - openstack floating ip port forwarding create - --internal-ip-address - --port - --internal-protocol-port - --external-protocol-port - --protocol - - - -.. describe:: --internal-ip-address - - The fixed IPv4 address of the network port associated - to the floating IP port forwarding - -.. describe:: --port - - The name or ID of the network port associated to the - floating IP port forwarding - -.. describe:: --internal-protocol-port - - The protocol port number of the network port fixed - IPv4 address associated to the floating IP port - forwarding - -.. describe:: --external-protocol-port - - The protocol port number of the port forwarding's - floating IP address - -.. describe:: --protocol - - The protocol used in the floating IP port forwarding, - for instance: TCP, UDP - -.. describe:: - - Floating IP that the port forwarding belongs to (IP - address or ID) - -floating ip port forwarding delete ----------------------------------- - -Delete floating IP Port Forwarding(s) - -.. program:: floating ip port forwarding delete -.. code:: bash - - openstack floating ip port forwarding delete - [ ...] - -.. describe:: - - Floating IP that the port forwarding belongs to (IP - address or ID) - -.. describe:: - - The ID of the floating IP port forwarding - -floating ip port forwarding list --------------------------------- - -List floating IP Port Forwarding(s) - -.. program:: floating ip port forwarding list -.. code:: bash - - openstack floating ip port forwarding list - [--port ] - [--external-protocol-port ] - [--protocol protocol] - - -.. option:: --port - - The ID of the network port associated to the floating - IP port forwarding - -.. option:: --protocol - - The IP protocol used in the floating IP port - forwarding - -.. describe:: - - Floating IP that the port forwarding belongs to (IP - address or ID) - -floating ip port forwarding set -------------------------------- - -Set floating IP Port Forwarding properties - -.. program:: floating ip port forwarding set -.. code:: bash - - openstack floating ip port forwarding set - [--port ] - [--internal-ip-address ] - [--internal-protocol-port ] - [--external-protocol-port ] - [--protocol ] - - - -.. option:: --port - - The ID of the network port associated to the floating - IP port forwarding - -.. option:: --internal-ip-address - - The fixed IPv4 address of the network port associated - to the floating IP port forwarding - -.. option:: --internal-protocol-port - - The TCP/UDP/other protocol port number of the network - port fixed IPv4 address associated to the floating IP - port forwarding - -.. option:: --external-protocol-port - - The TCP/UDP/other protocol port number of the port - forwarding's floating IP address - -.. option:: --protocol - - The IP protocol used in the floating IP port - forwarding - -.. describe:: - - Floating IP that the port forwarding belongs to (IP - address or ID) - -.. describe:: - - The ID of the floating IP port forwarding - -floating ip port forwarding show --------------------------------- - -Display floating IP Port Forwarding details - -.. program:: floating ip port forwarding show -.. code:: bash - - openstack floating ip show - -.. describe:: - - Floating IP that the port forwarding belongs to (IP - address or ID) - -.. describe:: - - The ID of the floating IP port forwarding diff --git a/doc/source/cli/command-objects/floating-ip.rst b/doc/source/cli/command-objects/floating-ip.rst index d122ccbea3..c781a1edb4 100644 --- a/doc/source/cli/command-objects/floating-ip.rst +++ b/doc/source/cli/command-objects/floating-ip.rst @@ -4,298 +4,23 @@ floating ip Network v2 -floating ip create ------------------- +.. NOTE(efried): have to list these out one by one; 'floating ip' pulls in + ... pool and ... port forwarding. -Create floating IP +.. autoprogram-cliff:: openstack.network.v2 + :command: floating ip create -.. program:: floating ip create -.. code:: bash +.. autoprogram-cliff:: openstack.network.v2 + :command: floating ip delete - openstack floating ip create - [--subnet ] - [--port ] - [--floating-ip-address ] - [--fixed-ip-address ] - [--description ] - [--qos-policy ] - [--project [--project-domain ]] - [--tag | --no-tag] - [--dns-domain ] - [--dns-name ] - +.. autoprogram-cliff:: openstack.network.v2 + :command: floating ip list -.. option:: --subnet +.. autoprogram-cliff:: openstack.network.v2 + :command: floating ip set - Subnet on which you want to create the floating IP (name or ID) - *Network version 2 only* +.. autoprogram-cliff:: openstack.network.v2 + :command: floating ip show -.. option:: --port - - Port to be associated with the floating IP (name or ID) - *Network version 2 only* - -.. option:: --floating-ip-address - - Floating IP address - *Network version 2 only* - -.. option:: --fixed-ip-address - - Fixed IP address mapped to the floating IP - *Network version 2 only* - -.. option:: --description - - Set floating IP description - *Network version 2 only* - -.. option:: --qos-policy - - QoS policy to attach to the floating IP (name or ID) - - *Network version 2 only* - -.. option:: --project - - Owner's project (name or ID) - - *Network version 2 only* - -.. option:: --project-domain - - Domain the project belongs to (name or ID). - This can be used in case collisions between project names exist. - - *Network version 2 only* - -.. option:: --tag - - Tag to be added to the floating IP (repeat option to set multiple tags) - - *Network version 2 only* - -.. option:: --no-tag - - No tags associated with the floating IP - - *Network version 2 only* - -.. option:: --dns-domain - - Set DNS domain for this floating IP (requires DNS integration extension). - -.. option:: --dns-name - - Set DNS name for this floating IP (requires DNS integration extension). - -.. describe:: - - Network to allocate floating IP from (name or ID) - -floating ip delete ------------------- - -Delete floating IP(s) - -.. program:: floating ip delete -.. code:: bash - - openstack floating ip delete [ ...] - -.. describe:: - - Floating IP(s) to delete (IP address or ID) - -floating ip list ----------------- - -List floating IP(s) - -.. program:: floating ip list -.. code:: bash - - openstack floating ip list - [--network ] - [--port ] - [--fixed-ip-address ] - [--long] - [--status ] - [--project [--project-domain ]] - [--router ] - [--tags [,,...]] [--any-tags [,,...]] - [--not-tags [,,...]] [--not-any-tags [,,...]] - -.. option:: --network - - List floating IP(s) according to given network (name or ID) - - *Network version 2 only* - -.. option:: --port - - List floating IP(s) according to given port (name or ID) - - *Network version 2 only* - -.. option:: --fixed-ip-address - - List floating IP(s) according to given fixed IP address - - *Network version 2 only* - -.. option:: --floating-ip-address - - List floating IP(s) according to given floating IP address - - *Network version 2 only* - -.. option:: --long - - List additional fields in output - - *Network version 2 only* - -.. option:: --status - - List floating IP(s) according to given status ('ACTIVE', 'DOWN') - - *Network version 2 only* - -.. option:: --project - - List floating IP(s) according to given project (name or ID) - - *Network version 2 only* - -.. option:: --project-domain - - Domain the project belongs to (name or ID). This can - be used in case collisions between project names exist. - - *Network version 2 only* - -.. option:: --router - - List floating IP(s) according to given router (name or ID) - - *Network version 2 only* - -.. option:: --tags [,,...] - - List floating IP(s) which have all given tag(s) - - *Network version 2 only* - -.. option:: --any-tags [,,...] - - List floating IP(s) which have any given tag(s) - - *Network version 2 only* - -.. option:: --not-tags [,,...] - - Exclude floating IP(s) which have all given tag(s) - - *Network version 2 only* - -.. option:: --not-any-tags [,,...] - - Exclude floating IP(s) which have any given tag(s) - - *Network version 2 only* - -floating ip set ---------------- - -Set floating IP properties - -.. program:: floating ip set -.. code:: bash - - openstack floating ip set - [--port ] - [--fixed-ip-address ] - [--qos-policy | --no-qos-policy] - [--tag ] [--no-tag] - - -.. option:: --port - - Associate the floating IP with port (name or ID) - -.. option:: --fixed-ip-address - - Fixed IP of the port (required only if port has multiple IPs) - -.. option:: --qos-policy - - Attach QoS policy to the floating IP (name or ID) - -.. option:: --no-qos-policy - - Remove the QoS policy attached to the floating IP - -.. option:: --tag - - Tag to be added to the floating IP (repeat option to set multiple tags) - -.. option:: --no-tag - - Clear tags associated with the floating IP. Specify both --tag - and --no-tag to overwrite current tags - -.. _floating_ip_set-floating-ip: -.. describe:: - - Floating IP to associate (IP address or ID) - -floating ip show ----------------- - -Display floating IP details - -.. program:: floating ip show -.. code:: bash - - openstack floating ip show - -.. describe:: - - Floating IP to display (IP address or ID) - -floating ip unset ------------------ - -Unset floating IP Properties - -.. program:: floating ip unset -.. code:: bash - - openstack floating ip unset - [--port] - [--qos-policy] - [--tag | --all-tag] - - -.. option:: --port - - Disassociate any port associated with the floating IP - -.. option:: --qos-policy - - Remove the QoS policy attached to the floating IP - -.. option:: --tag - - Tag to be removed from the floating IP - (repeat option to remove multiple tags) - -.. option:: --all-tag - - Clear all tags associated with the floating IP - -.. _floating_ip_unset-floating-ip: -.. describe:: - - Floating IP to disassociate (IP address or ID) +.. autoprogram-cliff:: openstack.network.v2 + :command: floating ip unset diff --git a/doc/source/cli/command-objects/ip-availability.rst b/doc/source/cli/command-objects/ip-availability.rst index dd39e649fe..94e60459b1 100644 --- a/doc/source/cli/command-objects/ip-availability.rst +++ b/doc/source/cli/command-objects/ip-availability.rst @@ -4,57 +4,5 @@ ip availability Network v2 -ip availability list --------------------- - -List IP availability for network - -This command retrieves information about IP availability. -Useful for admins who need a quick way to check the -IP availability for all associated networks. -List specifically returns total IP capacity and the -number of allocated IP addresses from that pool. - -.. program:: ip availability list -.. code:: bash - - openstack ip availability list - [--ip-version {4,6}] - [--project ] - -.. option:: --ip-version {4,6} - - List IP availability of given IP version networks - (default is 4) - -.. option:: --project - - List IP availability of given project - (name or ID) - -ip availability show --------------------- - -Show network IP availability details - -This command retrieves information about IP availability. -Useful for admins who need a quick way to -check the IP availability and details for a -specific network. - -This command will return information about -IP availability for the network as a whole, and -return availability information for each individual -subnet within the network as well. - - -.. program:: ip availability show -.. code:: bash - - openstack ip availability show - - -.. _ip_availability_show-network: -.. describe:: - - Show IP availability for a specific network (name or ID) +.. autoprogram-cliff:: openstack.network.v2 + :command: ip availability * diff --git a/doc/source/cli/command-objects/network-agent.rst b/doc/source/cli/command-objects/network-agent.rst index 9f02cb6bc8..7e721db1ea 100644 --- a/doc/source/cli/command-objects/network-agent.rst +++ b/doc/source/cli/command-objects/network-agent.rst @@ -10,207 +10,5 @@ agent is "True". Network v2 -network agent add network -------------------------- - -Add network to an agent - -.. program:: network agent add network -.. code:: bash - - openstack network agent add network - [--dhcp] - - - -.. option:: --dhcp - - Add a network to DHCP agent - -.. describe:: - - Agent to which a network is added (ID only) - -.. describe:: - - Network to be added to an agent (name or ID) - -network agent add router ------------------------- - -Add router to an agent - -.. program:: network agent add router -.. code:: bash - - openstack network agent add router - [--l3] - - - -.. option:: --l3 - - Add router to an L3 agent - -.. _network_agent_add_router-agent-id: -.. describe:: - - Agent to which a router is added (ID only) - -.. _network_agent_add_router-router: -.. describe:: - - Router to be added to an agent (name or ID) - -network agent delete --------------------- - -Delete network agent(s) - -.. program:: network agent delete -.. code:: bash - - openstack network agent delete - [ ...] - -.. _network_agent_delete-network-agent: -.. describe:: - - Network agent(s) to delete (ID only) - -network agent list ------------------- - -List network agents - -.. program:: network agent list -.. code:: bash - - openstack network agent list - [--agent-type ] - [--host ] - [--network | --router ] - [--long] - -.. option:: --agent-type - - List only agents with the specified agent type. - The supported agent types are: dhcp, open-vswitch, - linux-bridge, ofa, l3, loadbalancer, metering, - metadata, macvtap, nic. - -.. option:: --host - - List only agents running on the specified host - -.. option:: --network - - List agents hosting a network (name or ID) - -.. option:: --router - - List agents hosting this router (name or ID) - -.. option:: --long - - List additional fields in output - -network agent remove network ----------------------------- - -Remove network from an agent - -.. program:: network agent remove network -.. code:: bash - - openstack network agent remove network - [--dhcp] - - - -.. option:: --dhcp - - Remove network from DHCP agent - -.. _network_agent_remove_network-agent-id: -.. describe:: - - Agent to which a network is removed (ID only) - -.. _network_agent_remove_network-network: -.. describe:: - - Network to be removed from an agent (name or ID) - -network agent remove router ---------------------------- - -Remove router from an agent - -.. program:: network agent remove router -.. code:: bash - - openstack agent remove router - [--l3] - - - -.. option:: --l3 - - Remove router from an L3 agent - -.. _network_agent_remove_router-agent-id: -.. describe:: - - Agent from which router will be removed (ID only) - -.. _network_agent_remove_router-router: -.. describe:: - - Router to be removed from an agent (name or ID) - -network agent set ------------------ - -Set network agent properties - -.. program:: network agent set -.. code:: bash - - openstack network agent set - [--description ] - [--enable | --disable] - - -.. option:: --description - - Set network agent description - -.. option:: --enable - - Enable network agent - -.. option:: --disable - - Disable network agent - -.. _network_agent_set-network-agent: -.. describe:: - - Network agent to modify (ID only) - -network agent show ------------------- - -Display network agent details - -.. program:: network agent show -.. code:: bash - - openstack network agent show - - -.. _network_agent_show-network-agent: -.. describe:: - - Network agent to display (ID only) +.. autoprogram-cliff:: openstack.network.v2 + :command: network agent * diff --git a/doc/source/cli/command-objects/network-auto-allocated-topology.rst b/doc/source/cli/command-objects/network-auto-allocated-topology.rst index 4ed68cdae9..00e4c6b9bc 100644 --- a/doc/source/cli/command-objects/network-auto-allocated-topology.rst +++ b/doc/source/cli/command-objects/network-auto-allocated-topology.rst @@ -10,60 +10,5 @@ http://docs.openstack.org/newton/networking-guide/config-auto-allocation.html Network v2 -network auto allocated topology create --------------------------------------- - -Create the auto allocated topology for project - -.. program:: network auto allocated topology create -.. code:: bash - - openstack network auto allocated topology create - [--or-show] - [--check-resources] - [--project [--project-domain ]] - -.. option:: --or-show - - If topology exists returns the topologies information (Default). - -.. option:: --check-resources - - Validate the requirements for auto allocated topology. - Does not return a topology. - -.. option:: --project - - Return the auto allocated topology for a given project. - Default is current project. - -.. option:: --project-domain - - Domain the project belongs to (name or ID). - This can be used in case collisions between project names exist. - -.. _network_auto_allocated_topology_create: - - -network auto allocated topology delete --------------------------------------- - -Delete auto allocated topology for project - -.. program:: network auto allocated topology delete -.. code:: bash - - openstack network auto allocated topology delete - [--project [--project-domain ]] - -.. option:: --project - - Delete auto allocated topology for a given project. - Default is the current project. - -.. option:: --project-domain - - Domain the project belongs to (name or ID). - This can be used in case collisions between project names exist. - -.. _network_auto_allocated_topology_delete: +.. autoprogram-cliff:: openstack.network.v2 + :command: network auto allocated topology * diff --git a/doc/source/cli/command-objects/network-flavor-profile.rst b/doc/source/cli/command-objects/network-flavor-profile.rst index fdb95059b8..c5e9aa0c13 100644 --- a/doc/source/cli/command-objects/network-flavor-profile.rst +++ b/doc/source/cli/command-objects/network-flavor-profile.rst @@ -11,135 +11,5 @@ operators to create user options according to deployment needs. Network v2 -network flavor profile create ------------------------------ - -Create a new network flavor profile - -.. program:: network flavor profile create -.. code:: bash - - openstack network flavor profile create - [--project [--project-domain ]] - [--description ] - [--enable | --disable] - (--driver | --metainfo | --driver --metainfo ) - -.. option:: --project - - Owner's project (name or ID) - - *Network version 2 only* - -.. option:: --project-domain - - Domain the project belongs to (name or ID). This can - be used in case collisions between project names - exist - -.. option:: --description - - Description for the flavor profile - - *Network version 2 only* - -.. option:: --enable - - Enable the flavor profile (default) - -.. option:: --disable - - Disable the flavor profile - -.. option:: --driver - - Python module path to driver - - *Network version 2 only* - -.. option:: --metainfo - - Metainfo for the flavor profile - - *Network version 2 only* - - -network flavor profile delete ------------------------------ - -Delete network flavor profile - -.. program:: network flavor profile delete -.. code:: bash - - openstack network flavor profile delete - [ ...] - -.. describe:: - - Flavor profile(s) to delete (ID only) - -network flavor profile list ---------------------------- - -List network flavor profiles - -.. program:: network flavor profile list -.. code:: bash - - openstack network flavor profile list - -network flavor profile set --------------------------- - -Set network flavor profile properties - -.. program:: network flavor profile set -.. code:: bash - - openstack network flavor profile set - [--description ] - [--driver ] - [--enable | --disable] - [--metainfo ] - - - -.. option:: --description - - Description of the flavor profile - -.. option:: --driver - - Python module path to driver - -.. option:: --enable (Default) - - Enable the flavor profile - -.. option:: --disable - - Disable the flavor profile - -.. option:: --metainfo - - Metainfo for the flavor profile - -.. describe:: - - Flavor profile to update (ID only) - -network flavor profile show ---------------------------- - -Show network flavor profile - -.. program:: network flavor profile show -.. code:: bash - - openstack network flavor profile show - - -.. describe:: - - Flavor profile to display (ID only) +.. autoprogram-cliff:: openstack.network.v2 + :command: network flavor profile * diff --git a/doc/source/cli/command-objects/network-flavor.rst b/doc/source/cli/command-objects/network-flavor.rst index 2d23bf056a..6fe7504a0d 100644 --- a/doc/source/cli/command-objects/network-flavor.rst +++ b/doc/source/cli/command-objects/network-flavor.rst @@ -8,176 +8,26 @@ service flavors. Network v2 -network flavor add profile --------------------------- +.. NOTE(efried): have to list these out one by one; 'network flavor' pulls in + ... profile *. -Add network flavor to service profile +.. autoprogram-cliff:: openstack.network.v2 + :command: network flavor add profile -.. program:: network flavor add profile -.. code:: bash +.. autoprogram-cliff:: openstack.network.v2 + :command: network flavor create - openstack network flavor add profile - - +.. autoprogram-cliff:: openstack.network.v2 + :command: network flavor delete -.. describe:: +.. autoprogram-cliff:: openstack.network.v2 + :command: network flavor list - Flavor to which service profile is added. (Name or ID) +.. autoprogram-cliff:: openstack.network.v2 + :command: network flavor remove profile -.. describe:: +.. autoprogram-cliff:: openstack.network.v2 + :command: network flavor set - Service profile to be added to flavor. (ID only) - -.. _network_flavor_add_profile: - -network flavor create ---------------------- - -Create network flavor - -.. program:: network flavor create -.. code:: bash - - openstack network flavor create - --service-type - [--description ] - [--enable | --disable] - [--project [--project-domain ]] - - -.. option:: --service-type - - Service type to which the flavor applies to: e.g. VPN. - (See openstack :ref:`\ `) (required) - -.. option:: --description - - Description for the flavor - -.. option:: --enable - - Enable the flavor (default) - -.. option:: --disable - - Disable the flavor - -.. option:: --project - - Owner's project (name or ID) - -.. option:: --project-domain - - Domain the project belongs to (name or ID). This can - be used in case collisions between project names - exist. - -.. describe:: - - Name for the flavor - -.. _network_flavor_create: - -network flavor delete ---------------------- - -Delete network flavor(s) - -.. program:: network flavor delete -.. code:: bash - - openstack network flavor delete - [ ...] - -.. describe:: - - Flavor(s) to delete (name or ID) - -.. _network_flavor_delete: - -network flavor list -------------------- - -List network flavors - -.. program:: network flavor list -.. code:: bash - - openstack network flavor list - -.. _network_flavor_list: - -network flavor remove profile ------------------------------ - -Remove network flavor from service profile - -.. program:: network flavor remove profile -.. code:: bash - - openstack network flavor remove profile - - - -.. describe:: - - Flavor from which service profile is removed. (Name or ID) - -.. describe:: - - Service profile to be removed from flavor. (ID only) - -.. _network_flavor_remove_profile: - -network flavor set ------------------- - -Set network flavor properties - -.. program:: network flavor set -.. code:: bash - - openstack network flavor set - [--name ] - [--description ] - [--enable | --disable] - - -.. option:: --name - - Set flavor name - -.. option:: --description - - Set network flavor description - -.. option:: --enable - - Enable network flavor - -.. option:: --disable - - Disable network flavor - -.. describe:: - - Flavor to update (name or ID) - -.. _network_flavor_set: - -network flavor show -------------------- - -Show network flavor - -.. program:: network flavor show -.. code:: bash - - openstack network flavor show - - -.. describe:: - - Flavor to display (name or ID) - -.. _network_flavor_show: +.. autoprogram-cliff:: openstack.network.v2 + :command: network flavor show diff --git a/doc/source/cli/command-objects/network-meter-rule.rst b/doc/source/cli/command-objects/network-meter-rule.rst index 22d50aa907..616c4bc516 100644 --- a/doc/source/cli/command-objects/network-meter-rule.rst +++ b/doc/source/cli/command-objects/network-meter-rule.rst @@ -9,93 +9,5 @@ metering extension. Network v2 -network meter rule create -------------------------- - -Create meter rule - -.. program:: network meter rule create -.. code:: bash - - openstack network meter rule create - --remote-ip-prefix - [--ingress | --egress] - [--exclude | --include] - [--project [--project-domain ]] - - -.. option:: --project - - Owner's project (name or ID) - -.. option:: --project-domain - - Domain the project belongs to (name of ID). - This can be used in case collisions between project names exist. - -.. option:: --ingress - - Rule is applied to incoming traffic (default) - -.. option:: --egress - - Rule is applied to outgoing traffic - -.. option:: --exclude - - Exclude remote_ip_prefix from count of the traffic of IP addresses - -.. option:: --include - - Include remote_ip_prefix into count of the traffic of IP addresses - (default) - -.. option:: --remote-ip-prefix - - The remote IP prefix to associate with this metering rule packet - -.. _network_meter_rule_create: -.. describe:: - - Meter to associate with this meter rule (name or ID) - - -network meter rule delete -------------------------- - -Delete meter rule(s) - -.. program:: network meter rule delete -.. code:: bash - - openstack network meter rule delete [ ...] - -.. _network_meter_rule_delete: -.. describe:: - - ID of meter rule(s) to delete - -network meter rule list ------------------------ - -List meter rules - -.. program:: network meter rule list -.. code:: bash - - openstack network meter rule list - -network meter rule show ------------------------ - -Show meter rule - -.. program:: network meter rule show -.. code:: bash - - openstack network meter rule show - -.. _network_meter_show: -.. describe:: - - Meter rule to display (ID only) +.. autoprogram-cliff:: openstack.network.v2 + :command: network meter rule * diff --git a/doc/source/cli/command-objects/network-meter.rst b/doc/source/cli/command-objects/network-meter.rst index 6077ce9226..5752e6f419 100644 --- a/doc/source/cli/command-objects/network-meter.rst +++ b/doc/source/cli/command-objects/network-meter.rst @@ -8,84 +8,17 @@ are specific to the L3 metering extension. Network v2 -network meter create --------------------- +.. NOTE(efried): have to list these out one by one; 'network meter *' pulls in + ... rule *. -Create network meter +.. autoprogram-cliff:: openstack.network.v2 + :command: network meter create -.. program:: network meter create -.. code:: bash +.. autoprogram-cliff:: openstack.network.v2 + :command: network meter delete - openstack network meter create - [--project [--project-domain ]] - [--description ] - [--share | --no-share] - +.. autoprogram-cliff:: openstack.network.v2 + :command: network meter list -.. option:: --project - - Owner's project (name of ID) - - *Network version 2 only* - -.. option:: --description - - Description of meter - - *Network version 2 only* - -.. option:: --share - - Share the meter between projects - -.. option:: --no-share - - Do not share the meter between projects (Default) - -.. _network_meter_create: -.. describe:: - - New meter name - -network meter delete --------------------- - -Delete network meter(s) - -.. program:: network meter delete -.. code:: bash - - openstack network meter delete - [ ...] - -.. _network_meter_delete: -.. describe:: - - Meter(s) to delete (name or ID) - -network meter list ------------------- - -List network meters - -.. program:: network meter list -.. code:: bash - - openstack network meter list - - -network meter show ------------------- - -Show network meter - -.. program:: network meter show -.. code:: bash - - openstack network meter show - - -.. _network_meter_show: -.. describe:: - - Meter to display (name or ID) +.. autoprogram-cliff:: openstack.network.v2 + :command: network meter show diff --git a/doc/source/cli/command-objects/network-qos-policy.rst b/doc/source/cli/command-objects/network-qos-policy.rst index 2e86f8f3ac..af7a104070 100644 --- a/doc/source/cli/command-objects/network-qos-policy.rst +++ b/doc/source/cli/command-objects/network-qos-policy.rst @@ -7,157 +7,5 @@ network or a port. Network v2 -network qos policy create -------------------------- - -Create new Network QoS policy - -.. program:: network qos policy create -.. code:: bash - - openstack network qos policy create - [--description ] - [--share | --no-share] - [--project ] - [--project-domain ] - [--default | --no-default] - - -.. option:: --description - - Description of the QoS policy - -.. option:: --share - - Make the QoS policy accessible by other projects - -.. option:: --no-share - - Make the QoS policy not accessible by other projects (default) - -.. option:: --project - - Owner's project (name or ID) - -.. option:: --project-domain - - Domain the project belongs to (name or ID). - This can be used in case collisions between project names exist. - -.. option:: --default - - Set this as a default network QoS policy - -.. option:: --no-default - - Set this as a non-default network QoS policy - -.. _network_qos_policy_create-name: -.. describe:: - - New QoS policy specification name - -network qos policy delete -------------------------- - -Delete Network QoS policy - -.. program:: network qos policy delete -.. code:: bash - - openstack network qos policy delete - [ ...] - -.. _network_qos_policy_delete-qos-policy: -.. describe:: - - Network QoS policy(s) to delete (name or ID) - -network qos policy list ------------------------ - -List Network QoS policies - -.. program:: network qos policy list -.. code:: bash - - openstack network qos policy list - [--project [--project-domain ]] - [--share | --no-share] - -.. option:: --project - - List qos policies according to their project (name or ID) - -.. option:: --project-domain - - Domain the project belongs to (name or ID). - This can be used in case collisions between project names exist. - -.. option:: --share - - List qos policies shared between projects - -.. option:: --no-share - - List qos policies not shared between projects - -network qos policy set ----------------------- - -Set Network QoS policy properties - -.. program:: network qos policy set -.. code:: bash - - openstack network qos policy set - [--name ] - [--description ] - [--share | --no-share] - [--default | --no-default] - - -.. option:: --name - - Name of the QoS policy - -.. option:: --description - - Description of the QoS policy - -.. option:: --share - - Make the QoS policy accessible by other projects - -.. option:: --no-share - - Make the QoS policy not accessible by other projects - -.. option:: --default - - Set this as a default network QoS policy - -.. option:: --no-default - - Set this as a non-default network QoS policy - -.. _network_qos_policy_set-qos-policy: -.. describe:: - - Network QoS policy to modify (name or ID) - -network qos policy show ------------------------ - -Display Network QoS policy details - -.. program:: network qos policy show -.. code:: bash - - openstack network qos policy show - - -.. _network_qos_policy_show-qos-policy: -.. describe:: - - Network QoS policy to display (name or ID) +.. autoprogram-cliff:: openstack.network.v2 + :command: network qos policy * diff --git a/doc/source/cli/command-objects/network-qos-rule-type.rst b/doc/source/cli/command-objects/network-qos-rule-type.rst index de6f2474af..197a86002f 100644 --- a/doc/source/cli/command-objects/network-qos-rule-type.rst +++ b/doc/source/cli/command-objects/network-qos-rule-type.rst @@ -7,27 +7,5 @@ used. Network v2 -network qos rule type list --------------------------- - -List Network QoS rule types - -.. program:: network qos rule type list -.. code:: bash - - openstack network qos rule type list - -network qos rule type show --------------------------- - -Display Network QoS rule type details - -.. program:: network qos rule type show -.. code:: bash - - openstack network qos rule type show - - -.. describe:: - - Name of QoS rule type (minimum-bandwidth, dscp-marking, bandwidth-limit) +.. autoprogram-cliff:: openstack.network.v2 + :command: network qos rule type * diff --git a/doc/source/cli/command-objects/network-qos-rule.rst b/doc/source/cli/command-objects/network-qos-rule.rst index 1baf5dbfeb..8b715c03ab 100644 --- a/doc/source/cli/command-objects/network-qos-rule.rst +++ b/doc/source/cli/command-objects/network-qos-rule.rst @@ -9,157 +9,20 @@ rules, each of them Network v2 -network qos rule create ------------------------ +.. NOTE(efried): have to list these out one by one; 'network qos rule *' pulls + network qos rule type *. -Create new Network QoS rule +.. autoprogram-cliff:: openstack.network.v2 + :command: network qos rule create -.. program:: network qos rule create -.. code:: bash +.. autoprogram-cliff:: openstack.network.v2 + :command: network qos rule delete - openstack network qos rule create - --type - [--max-kbps ] - [--max-burst-kbits ] - [--dscp-marks ] - [--min-kbps ] - [--ingress | --egress] - +.. autoprogram-cliff:: openstack.network.v2 + :command: network qos rule list -.. option:: --type +.. autoprogram-cliff:: openstack.network.v2 + :command: network qos rule set - QoS rule type (minimum-bandwidth, dscp-marking, bandwidth-limit) - -.. option:: --max-kbps - - Maximum bandwidth in kbps - -.. option:: --max-burst-kbits - - Maximum burst in kilobits, 0 means automatic - -.. option:: --dscp-mark - - DSCP mark: value can be 0, even numbers from 8-56, excluding 42, 44, 50, - 52, and 54 - -.. option:: --min-kbps - - Minimum guaranteed bandwidth in kbps - -.. option:: --ingress - - Ingress traffic direction from the project point of view - -.. option:: --egress - - Egress traffic direction from the project point of view - -.. describe:: - - QoS policy that contains the rule (name or ID) - -network qos rule delete ------------------------ - -Delete Network QoS rule - -.. program:: network qos rule delete -.. code:: bash - - openstack network qos rule delete - - - -.. describe:: - - QoS policy that contains the rule (name or ID) - -.. describe:: - - Network QoS rule to delete (ID) - -network qos rule list ---------------------- - -List Network QoS rules - -.. program:: network qos rule list -.. code:: bash - - openstack network qos rule list - - -.. describe:: - - QoS policy that contains the rule (name or ID) - -network qos rule set --------------------- - -Set Network QoS rule properties - -.. program:: network qos rule set -.. code:: bash - - openstack network qos rule set - [--max-kbps ] - [--max-burst-kbits ] - [--dscp-marks ] - [--min-kbps ] - [--ingress | --egress] - - - -.. option:: --max-kbps - - Maximum bandwidth in kbps - -.. option:: --max-burst-kbits - - Maximum burst in kilobits, 0 means automatic - -.. option:: --dscp-mark - - DSCP mark: value can be 0, even numbers from 8-56, excluding 42, 44, 50, - 52, and 54 - -.. option:: --min-kbps - - Minimum guaranteed bandwidth in kbps - -.. option:: --ingress - - Ingress traffic direction from the project point of view - -.. option:: --egress - - Egress traffic direction from the project point of view - -.. describe:: - - QoS policy that contains the rule (name or ID) - -.. describe:: - - Network QoS rule to delete (ID) - -network qos rule show ---------------------- - -Display Network QoS rule details - -.. program:: network qos rule show -.. code:: bash - - openstack network qos rule show - - - -.. describe:: - - QoS policy that contains the rule (name or ID) - -.. describe:: - - Network QoS rule to delete (ID) +.. autoprogram-cliff:: openstack.network.v2 + :command: network qos rule show diff --git a/doc/source/cli/command-objects/network-rbac.rst b/doc/source/cli/command-objects/network-rbac.rst index 22733a2cd2..d9e48554d3 100644 --- a/doc/source/cli/command-objects/network-rbac.rst +++ b/doc/source/cli/command-objects/network-rbac.rst @@ -8,136 +8,5 @@ to network resources for specific projects. Network v2 -network rbac create -------------------- - -Create network RBAC policy - -.. program:: network rbac create -.. code:: bash - - openstack network rbac create - --type - --action - [--target-project | --target-all-projects] - [--target-project-domain ] - [--project [--project-domain ]] - - -.. option:: --type - - Type of the object that RBAC policy affects ("security_group", "qos_policy" or "network") (required) - -.. option:: --action - - Action for the RBAC policy ("access_as_external" or "access_as_shared") (required) - -.. option:: --target-project - - The project to which the RBAC policy will be enforced (name or ID) - -.. option:: --target-all-projects - - Allow creating RBAC policy for all projects. - -.. option:: --target-project-domain - - Domain the target project belongs to (name or ID). - This can be used in case collisions between project names exist. - -.. option:: --project - - The owner project (name or ID) - -.. option:: --project-domain - - Domain the project belongs to (name or ID). - This can be used in case collisions between project names exist. - -.. _network_rbac_create-rbac-policy: -.. describe:: - - The object to which this RBAC policy affects (name or ID) - -network rbac delete -------------------- - -Delete network RBAC policy(s) - -.. program:: network rbac delete -.. code:: bash - - openstack network rbac delete - [ ...] - -.. _network_rbac_delete-rbac-policy: -.. describe:: - - RBAC policy(s) to delete (ID only) - -network rbac list ------------------ - -List network RBAC policies - -.. program:: network rbac list -.. code:: bash - - openstack network rbac list - [--type ] - [--action ] - [--long] - -.. option:: --type - - List network RBAC policies according to given object type ("security_group", "qos_policy" or "network") - -.. option:: --action - - List network RBAC policies according to given action ("access_as_external" or "access_as_shared") - -.. option:: --long - - List additional fields in output - -network rbac set ----------------- - -Set network RBAC policy properties - -.. program:: network rbac set -.. code:: bash - - openstack network rbac set - [--target-project [--target-project-domain ]] - - -.. option:: --target-project - - The project to which the RBAC policy will be enforced (name or ID) - -.. option:: --target-project-domain - - Domain the target project belongs to (name or ID). - This can be used in case collisions between project names exist. - -.. _network_rbac_set-rbac-policy: -.. describe:: - - RBAC policy to be modified (ID only) - -network rbac show ------------------ - -Display network RBAC policy details - -.. program:: network rbac show -.. code:: bash - - openstack network rbac show - - -.. _network_rbac_show-rbac-policy: -.. describe:: - - RBAC policy (ID only) +.. autoprogram-cliff:: openstack.network.v2 + :command: network rbac * diff --git a/doc/source/cli/command-objects/network-segment.rst b/doc/source/cli/command-objects/network-segment.rst index d6a66aa0b9..2e9143801d 100644 --- a/doc/source/cli/command-objects/network-segment.rst +++ b/doc/source/cli/command-objects/network-segment.rst @@ -9,125 +9,20 @@ within a network may not be guaranteed. Network v2 -network segment create ----------------------- +.. NOTE(efried): have to list these out one by one; 'network segment *' pulls + ... range *. -Create new network segment +.. autoprogram-cliff:: openstack.network.v2 + :command: network segment create -.. program:: network segment create -.. code:: bash +.. autoprogram-cliff:: openstack.network.v2 + :command: network segment delete - openstack network segment create - [--description ] - [--physical-network ] - [--segment ] - --network - --network-type - +.. autoprogram-cliff:: openstack.network.v2 + :command: network segment list -.. option:: --description +.. autoprogram-cliff:: openstack.network.v2 + :command: network segment set - Network segment description - -.. option:: --physical-network - - Physical network name of this network segment - -.. option:: --segment - - Segment identifier for this network segment which is - based on the network type, VLAN ID for vlan network - type and tunnel ID for geneve, gre and vxlan network - types - -.. option:: --network - - Network this network segment belongs to (name or ID) - -.. option:: --network-type - - Network type of this network segment - (flat, geneve, gre, local, vlan or vxlan) - -.. _network_segment_create-name: -.. describe:: - - New network segment name - -network segment delete ----------------------- - -Delete network segment(s) - -.. program:: network segment delete -.. code:: bash - - openstack network segment delete - [ ...] - -.. _network_segment_delete-segment: -.. describe:: - - Network segment(s) to delete (name or ID) - -network segment list --------------------- - -List network segments - -.. program:: network segment list -.. code:: bash - - openstack network segment list - [--long] - [--network ] - -.. option:: --long - - List additional fields in output - -.. option:: --network - - List network segments that belong to this network (name or ID) - -network segment set -------------------- - -Set network segment properties - -.. program:: network segment set -.. code:: bash - - openstack network segment set - [--description ] - [--name ] - - -.. option:: --description - - Set network segment description - -.. option:: --name - - Set network segment name - -.. _network_segment_set-segment: -.. describe:: - - Network segment to modify (name or ID) - -network segment show --------------------- - -Display network segment details - -.. program:: network segment show -.. code:: bash - - openstack network segment show - - -.. _network_segment_show-segment: -.. describe:: - - Network segment to display (name or ID) +.. autoprogram-cliff:: openstack.network.v2 + :command: network segment show diff --git a/doc/source/cli/command-objects/network-service-provider.rst b/doc/source/cli/command-objects/network-service-provider.rst index f46073f677..52d0288dd0 100644 --- a/doc/source/cli/command-objects/network-service-provider.rst +++ b/doc/source/cli/command-objects/network-service-provider.rst @@ -9,12 +9,5 @@ Network v2 .. _network_service_provider_list: -network service provider list ------------------------------ - -List service providers - -.. program:: network service provider list -.. code:: bash - - openstack network service provider list +.. autoprogram-cliff:: openstack.network.v2 + :command: network service provider list diff --git a/doc/source/cli/command-objects/network.rst b/doc/source/cli/command-objects/network.rst index 75113f89da..626b11a928 100644 --- a/doc/source/cli/command-objects/network.rst +++ b/doc/source/cli/command-objects/network.rst @@ -11,505 +11,23 @@ provider networks. Networks can be connected via routers. Compute v2, Network v2 -network create --------------- +.. NOTE(efried): have to list these out one by one; 'network *' pulls in + ... flavor *, ... qos policy *, etc. -Create new network +.. autoprogram-cliff:: openstack.network.v2 + :command: network create -.. program:: network create -.. code:: bash +.. autoprogram-cliff:: openstack.network.v2 + :command: network delete - openstack network create - [--project [--project-domain ]] - [--enable | --disable] - [--share | --no-share] - [--description ] - [--mtu ] - [--availability-zone-hint ] - [--enable-port-security | --disable-port-security] - [--external [--default | --no-default] | --internal] - [--provider-network-type ] - [--provider-physical-network ] - [--provider-segment ] - [--qos-policy ] - [--transparent-vlan | --no-transparent-vlan] - [--dns-domain ] - [--tag | --no-tag] - +.. autoprogram-cliff:: openstack.network.v2 + :command: network list -.. option:: --project +.. autoprogram-cliff:: openstack.network.v2 + :command: network set - Owner's project (name or ID) +.. autoprogram-cliff:: openstack.network.v2 + :command: network show - *Network version 2 only* - -.. option:: --project-domain - - Domain the project belongs to (name or ID). - This can be used in case collisions between project names exist. - - *Network version 2 only* - -.. option:: --enable - - Enable network (default) - - *Network version 2 only* - -.. option:: --disable - - Disable network - - *Network version 2 only* - -.. option:: --share - - Share the network between projects - -.. option:: --no-share - - Do not share the network between projects - -.. option:: --description - - Set network description - - *Network version 2 only* - -.. option:: --mtu - - Set network mtu - - *Network version 2 only* - -.. option:: --availability-zone-hint - - Availability Zone in which to create this network - (Network Availability Zone extension required, - repeat option to set multiple availability zones) - - *Network version 2 only* - -.. option:: --enable-port-security - - Enable port security by default for ports created on - this network (default) - - *Network version 2 only* - -.. option:: --disable-port-security - - Disable port security by default for ports created on - this network - - *Network version 2 only* - -.. option:: --subnet - - IPv4 subnet for fixed IPs (in CIDR notation) - - *Compute version 2 only* - -.. option:: --external - - Set this network as an external network - (external-net extension required) - - *Network version 2 only* - -.. option:: --internal - - Set this network as an internal network (default) - - *Network version 2 only* - -.. option:: --default - - Specify if this network should be used as - the default external network - - *Network version 2 only* - -.. option:: --no-default - - Do not use the network as the default external network - (default) - - *Network version 2 only* - -.. option:: --provider-network-type - - The physical mechanism by which the virtual network is implemented. - The supported options are: flat, geneve, gre, local, vlan, vxlan. - - *Network version 2 only* - -.. option:: --provider-physical-network - - Name of the physical network over which the virtual network is implemented - - *Network version 2 only* - -.. option:: --provider-segment - - VLAN ID for VLAN networks or Tunnel ID for GENEVE/GRE/VXLAN networks - - *Network version 2 only* - -.. option:: --qos-policy - - QoS policy to attach to this network (name or ID) - - *Network version 2 only* - -.. option:: --transparent-vlan - - Make the network VLAN transparent - - *Network version 2 only* - -.. option:: --no-transparent-vlan - - Do not make the network VLAN transparent - - *Network version 2 only* - -.. option:: --dns-domain - - Set DNS domain for this network (requires DNS integration extension). - -.. option:: --tag - - Tag to be added to the network (repeat option to set multiple tags) - - *Network version 2 only* - -.. option:: --no-tag - - No tags associated with the network - - *Network version 2 only* - -.. _network_create-name: -.. describe:: - - New network name - -network delete --------------- - -Delete network(s) - -.. program:: network delete -.. code:: bash - - openstack network delete - [ ...] - -.. _network_delete-network: -.. describe:: - - Network(s) to delete (name or ID) - -network list ------------- - -List networks - -.. program:: network list -.. code:: bash - - openstack network list - [--external | --internal] - [--long] - [--name ] - [--enable | --disable] - [--project [--project-domain ]] - [--share | --no-share] - [--status ] - [--provider-network-type ] - [--provider-physical-network ] - [--provider-segment ] - [--agent ] - [--tags [,,...]] [--any-tags [,,...]] - [--not-tags [,,...]] [--not-any-tags [,,...]] - -.. option:: --external - - List external networks - - *Network version 2 only* - -.. option:: --internal - - List internal networks - - *Network version 2 only* - -.. option:: --long - - List additional fields in output - - *Network version 2 only* - -.. option:: --name - - List networks according to their name - - *Network version 2 only* - -.. option:: --enable - - List enabled networks - - *Network version 2 only* - -.. option:: --disable - - List disabled networks - - *Network version 2 only* - -.. option:: --project - - List networks according to their project (name or ID) - - *Network version 2 only* - -.. option:: --project-domain - - Domain the project belongs to (name or ID). - This can be used in case collisions between project names exist. - - *Network version 2 only* - -.. option:: --share - - List networks shared between projects - - *Network version 2 only* - -.. option:: --no-share - - List networks not shared between projects - - *Network version 2 only* - -.. option:: --status - - List networks according to their status - ('ACTIVE', 'BUILD', 'DOWN', 'ERROR') - -.. option:: --provider-network-type - - List networks according to their physical mechanisms. - The supported options are: flat, geneve, gre, local, vlan, vxlan. - - *Network version 2 only* - -.. option:: --provider-physical-network - - List networks according to name of the physical network - - *Network version 2 only* - -.. option:: --provider-segment - - List networks according to VLAN ID for VLAN networks - or Tunnel ID for GENEVE/GRE/VXLAN networks - - *Network version 2 only* - -.. option:: --agent - - List networks hosted by agent (ID only) - - *Network version 2 only* - -.. option:: --tags [,,...] - - List networks which have all given tag(s) - - *Network version 2 only* - -.. option:: --any-tags [,,...] - - List networks which have any given tag(s) - - *Network version 2 only* - -.. option:: --not-tags [,,...] - - Exclude networks which have all given tag(s) - - *Network version 2 only* - -.. option:: --not-any-tags [,,...] - - Exclude networks which have any given tag(s) - - *Network version 2 only* - -network set ------------ - -Set network properties - -*Network version 2 only* - -.. program:: network set -.. code:: bash - - openstack network set - [--name ] - [--enable | --disable] - [--share | --no-share] - [--description ] - [--mtu ] - [--enable-port-security | --disable-port-security] - [--external [--default | --no-default] | --internal] - [--provider-network-type ] - [--provider-physical-network ] - [--provider-segment ] - [--qos-policy | --no-qos-policy] - [--dns-domain ] - [--tag ] [--no-tag] - - -.. option:: --name - - Set network name - -.. option:: --enable - - Enable network - -.. option:: --disable - - Disable network - -.. option:: --share - - Share the network between projects - -.. option:: --no-share - - Do not share the network between projects - -.. option:: --description - - Set network description - -.. option:: --mtu - - Set network mtu - -.. option:: --enable-port-security - - Enable port security by default for ports created on - this network - -.. option:: --disable-port-security - - Disable port security by default for ports created on - this network - -.. option:: --external - - Set this network as an external network. - (external-net extension required) - -.. option:: --internal - - Set this network as an internal network - -.. option:: --default - - Set the network as the default external network - -.. option:: --no-default - - Do not use the network as the default external network. - -.. option:: --provider-network-type - - The physical mechanism by which the virtual network is implemented. - The supported options are: flat, gre, local, vlan, vxlan. - -.. option:: --provider-physical-network - - Name of the physical network over which the virtual network is implemented - -.. option:: --provider-segment - - VLAN ID for VLAN networks or Tunnel ID for GRE/VXLAN networks - -.. option:: --qos-policy - - QoS policy to attach to this network (name or ID) - -.. option:: --no-qos-policy - - Remove the QoS policy attached to this network - -.. option:: --dns-domain - - Set DNS domain for this network (requires DNS integration extension). - -.. option:: --tag - - Tag to be added to the network (repeat option to set multiple tags) - -.. option:: --no-tag - - Clear tags associated with the network. Specify both --tag - and --no-tag to overwrite current tags - -.. _network_set-network: -.. describe:: - - Network to modify (name or ID) - -network show ------------- - -Display network details - -.. program:: network show -.. code:: bash - - openstack network show - - -.. _network_show-network: -.. describe:: - - Network to display (name or ID) - -network unset -------------- - -Unset network properties - -*Network version 2 only* - -.. program:: network unset -.. code:: bash - - openstack network unset - [--tag | --all-tag] - - -.. option:: --tag - - Tag to be removed from the network - (repeat option to remove multiple tags) - -.. option:: --all-tag - - Clear all tags associated with the network - -.. _network_unset-network: -.. describe:: - - Network to modify (name or ID) +.. autoprogram-cliff:: openstack.network.v2 + :command: network unset diff --git a/doc/source/cli/command-objects/network_segment_range.rst b/doc/source/cli/command-objects/network_segment_range.rst index 71155d2224..f40007f385 100644 --- a/doc/source/cli/command-objects/network_segment_range.rst +++ b/doc/source/cli/command-objects/network_segment_range.rst @@ -10,159 +10,5 @@ administrator to control the segment ranges globally or on a per-tenant basis. Network v2 -network segment range create ----------------------------- - -Create new network segment range - -.. program:: network segment range create -.. code:: bash - - openstack network segment range create - (--private | --shared) - [--project [--project-domain ]] - --network-type - [--physical-network ] - --minimum - --maximum - - -.. option:: --private - - Network segment range is assigned specifically to the project - -.. option:: --shared - - Network segment range is shared with other projects - -.. option:: --project - - Network segment range owner (name or ID). Optional when the segment - range is shared - -.. option:: --project-domain - - Domain the project belongs to (name or ID). - This can be used in case collisions between project names exist. - -.. option:: --physical-network - - Physical network name of this network segment range - -.. option:: --network-type - - Network type of this network segment range - (geneve, gre, vlan or vxlan) - -.. option:: --minimum - - Minimum segment identifier for this network segment range which is based - on the network type, VLAN ID for vlan network type and tunnel ID for - geneve, gre and vxlan network types - -.. option:: --maximum - - Maximum segment identifier for this network segment range which is based - on the network type, VLAN ID for vlan network type and tunnel ID for - geneve, gre and vxlan network types - -.. _network_segment_range_create-name: -.. describe:: - - Name of new network segment range - -network segment range delete ----------------------------- - -Delete network segment range(s) - -.. program:: network segment range delete -.. code:: bash - - openstack network segment range delete - [ ...] - -.. _network_segment_range_delete-network-segment-range: -.. describe:: - - Network segment range (s) to delete (name or ID) - -network segment range list --------------------------- - -List network segment ranges - -.. program:: network segment range list -.. code:: bash - - openstack network segment range list - [--long] - [--used | --unused] - [--available | --unavailable] - -.. option:: --long - - List additional fields in output - -.. option:: --used - - List network segment ranges that have segments in use - -.. option:: --unused - - List network segment ranges that do not have segments not in use - -.. option:: --available - - List network segment ranges that have available segments - -.. option:: --unavailable - - List network segment ranges without available segments - -network segment range set -------------------------- - -Set network segment range properties - -.. program:: network segment range set -.. code:: bash - - openstack network segment range set - [--name ] - [--minimum ] - [--maximum ] - - -.. option:: --name - - Set network segment range name - -.. option:: --minimum - - Set network segment range minimum segment identifier - -.. option:: --maximum - - Set network segment range maximum segment identifier - -.. _network_segment_range_set-network-segment-range: -.. describe:: - - Network segment range to modify (name or ID) - -network segment range show --------------------------- - -Display network segment range details - -.. program:: network segment range show -.. code:: bash - - openstack network segment range show - - -.. _network_segment_range_show-network-segment-range: -.. describe:: - - Network segment range to display (name or ID) +.. autoprogram-cliff:: openstack.network.v2 + :command: network segment range * diff --git a/doc/source/cli/command-objects/port.rst b/doc/source/cli/command-objects/port.rst index bc9f5dde02..3af5272eb2 100644 --- a/doc/source/cli/command-objects/port.rst +++ b/doc/source/cli/command-objects/port.rst @@ -8,486 +8,5 @@ configuration, such as the MAC and IP addresses to be used on that port. Network v2 -port create ------------ - -Create new port - -.. program:: port create -.. code:: bash - - openstack port create - --network - [--description ] - [--fixed-ip subnet=,ip-address= | --no-fixed-ip] - [--device ] - [--device-owner ] - [--vnic-type ] - [--binding-profile ] - [--host ] - [--enable | --disable] - [--enable-uplink-status-propagation | --disable-uplink-status-propagation] - [--mac-address ] - [--security-group | --no-security-group] - [--dns-domain ] - [--dns-name ] - [--allowed-address ip-address=[,mac-address=]] - [--qos-policy ] - [--project [--project-domain ]] - [--enable-port-security | --disable-port-security] - [--tag | --no-tag] - - -.. option:: --network - - Network this port belongs to (name or ID) - -.. option:: --description - - Description of this port - -.. option:: --fixed-ip subnet=,ip-address= - - Desired IP and/or subnet for this port (name or ID): - subnet=,ip-address= - (repeat option to set multiple fixed IP addresses) - -.. option:: --no-fixed-ip - - No IP or subnet for this port - -.. option:: --device - - Port device ID - -.. option:: --device-owner - - Device owner of this port. This is the entity that uses - the port (for example, network:dhcp). - -.. option:: --vnic-type - - VNIC type for this port (direct | direct-physical | macvtap | normal | baremetal | - virtio-forwarder, default: normal) - -.. option:: --binding-profile - - Custom data to be passed as binding:profile. Data may - be passed as = or JSON. - (repeat option to set multiple binding:profile data) - -.. option:: --host - - Allocate port on host ```` (ID only) - -.. option:: --enable - - Enable port (default) - -.. option:: --disable - - Disable port - -.. option:: --enable-uplink-status-propagation - - Enable uplink status propagate - -.. option:: --disable-uplink-status-propagation - - Disable uplink status propagate (default) - -.. option:: --mac-address - - MAC address of this port - -.. option:: --security-group - - Security group to associate with this port (name or ID) - (repeat option to set multiple security groups) - -.. option:: --no-security-group - - Associate no security groups with this port - -.. option:: --dns-domain - - Set DNS domain for this port - (requires dns_domain for ports extension) - -.. option:: --dns-name - - Set DNS name for this port - (requires DNS integration extension) - -.. option:: --allowed-address ip-address=[,mac-address=] - - Add allowed-address pair associated with this port: - ip-address=[,mac-address=] - (repeat option to set multiple allowed-address pairs) - -.. option:: --qos-policy - - Attach QoS policy to this port (name or ID) - -.. option:: --project - - Owner's project (name or ID) - -.. option:: --project-domain - - Domain the project belongs to (name or ID). - This can be used in case collisions between project names exist. - -.. option:: --enable-port-security - - Enable port security for this port (Default) - -.. option:: --disable-port-security - - Disable port security for this port - -.. option:: --tag - - Tag to be added to the port (repeat option to set multiple tags) - -.. option:: --no-tag - - No tags associated with the port - -.. _port_create-name: -.. describe:: - - Name of this port - -port delete ------------ - -Delete port(s) - -.. program:: port delete -.. code:: bash - - openstack port delete - [ ...] - -.. _port_delete-port: -.. describe:: - - Port(s) to delete (name or ID) - -port list ---------- - -List ports - -.. program:: port list -.. code:: bash - - openstack port list - [--device-owner ] - [--router | --server | --device-id ] - [--network ] - [--mac-address ] - [--fixed-ip subnet=,ip-address=,ip-substring=] - [--long] - [--project [--project-domain ]] - [--tags [,,...]] [--any-tags [,,...]] - [--not-tags [,,...]] [--not-any-tags [,,...]] - -.. option:: --device-owner - - List only ports with the specified device owner. This is - the entity that uses the port (for example, network:dhcp). - -.. option:: --router - - List only ports attached to this router (name or ID) - -.. option:: --server - - List only ports attached to this server (name or ID) - -.. option:: --device-id - - List only ports with the specified device ID - -.. option:: --network - - List only ports attached to this network (name or ID) - -.. option:: --mac-address - - List only ports with this MAC address - -.. option:: --fixed-ip subnet=,ip-address=,ip-substring= - - Desired IP address, IP address substring and/or subnet (name or ID) for - filtering ports: - subnet=,ip-address=,ip-substring= - (repeat option to set multiple fixed IP addresses) - -.. option:: --long - - List additional fields in output - -.. option:: --project - - List ports according to their project (name or ID) - -.. option:: --project-domain - - Domain the project belongs to (name or ID). - This can be used in case collisions between project names exist. - -.. option:: --tags [,,...] - - List ports which have all given tag(s) - -.. option:: --any-tags [,,...] - - List ports which have any given tag(s) - -.. option:: --not-tags [,,...] - - Exclude ports which have all given tag(s) - -.. option:: --not-any-tags [,,...] - - Exclude ports which have any given tag(s) - -port set --------- - -Set port properties - -.. program:: port set -.. code:: bash - - openstack port set - [--description ] - [--fixed-ip subnet=,ip-address=] - [--no-fixed-ip] - [--device ] - [--device-owner ] - [--vnic-type ] - [--binding-profile ] - [--no-binding-profile] - [--host ] - [--qos-policy ] - [--enable | --disable] - [--name ] - [--mac-address ] - [--security-group ] - [--no-security-group] - [--enable-port-security | --disable-port-security] - [--dns-domain ] - [--dns-name ] - [--allowed-address ip-address=[,mac-address=]] - [--no-allowed-address] - [--data-plane-status ] - [--tag ] [--no-tag] - - -.. option:: --description - - Description of this port - -.. option:: --fixed-ip subnet=,ip-address= - - Desired IP and/or subnet for this port (name or ID): - subnet=,ip-address= - (repeat option to set multiple fixed IP addresses) - -.. option:: --no-fixed-ip - - Clear existing information of fixed IP addresses. - Specify both :option:`--fixed-ip` and :option:`--no-fixed-ip` - to overwrite the current fixed IP addresses. - -.. option:: --device - - Port device ID - -.. option:: --device-owner - - Device owner of this port. This is the entity that uses - the port (for example, network:dhcp). - -.. option:: --vnic-type - - VNIC type for this port (direct | direct-physical | macvtap | normal | baremetal | - virtio-forwarder, default: normal) - -.. option:: --binding-profile - - Custom data to be passed as binding:profile. Data may - be passed as = or JSON. - (repeat option to set multiple binding:profile data) - -.. option:: --no-binding-profile - - Clear existing information of binding:profile. - Specify both :option:`--binding-profile` and :option:`--no-binding-profile` - to overwrite the current binding:profile information. - -.. option:: --host - - Allocate port on host ```` (ID only) - -.. option:: --qos-policy - - Attach QoS policy to this port (name or ID) - -.. option:: --enable - - Enable port - -.. option:: --disable - - Disable port - -.. option:: --name - - Set port name - -.. option:: --mac-address - - Set port's MAC address (admin only) - -.. option:: --security-group - - Security group to associate with this port (name or ID) - (repeat option to set multiple security groups) - -.. option:: --no-security-group - - Clear existing security groups associated with this port - -.. option:: --enable-port-security - - Enable port security for this port - -.. option:: --disable-port-security - - Disable port security for this port - -.. option:: --dns-domain - - Set DNS domain for this port - (requires dns_domain for ports extension) - -.. option:: --dns-name - - Set DNS name for this port - (requires DNS integration extension) - -.. option:: --allowed-address ip-address=[,mac-address=] - - Add allowed-address pair associated with this port: - ip-address=[,mac-address=] - (repeat option to set multiple allowed-address pairs) - -.. option:: --no-allowed-address - - Clear existing allowed-address pairs associated - with this port. - (Specify both --allowed-address and --no-allowed-address - to overwrite the current allowed-address pairs) - -.. option:: --data-plane-status - - Set data plane status of this port (ACTIVE | DOWN). - Unset it to None with the 'port unset' command - (requires data plane status extension) - -.. option:: --tag - - Tag to be added to the port (repeat option to set multiple tags) - -.. option:: --no-tag - - Clear tags associated with the port. Specify both --tag - and --no-tag to overwrite current tags - -.. _port_set-port: -.. describe:: - - Port to modify (name or ID) - -port show ---------- - -Display port details - -.. program:: port show -.. code:: bash - - openstack port show - - -.. _port_show-port: -.. describe:: - - Port to display (name or ID) - -port unset ----------- - -Unset port properties - -.. program:: port unset -.. code:: bash - - openstack port unset - [--fixed-ip subnet=,ip-address= [...]] - [--binding-profile [...]] - [--security-group [...]] - [--allowed-address ip-address=[,mac-address=] [...]] - [--qos-policy] - [--data-plane-status] - [--tag | --all-tag] - - -.. option:: --fixed-ip subnet=,ip-address= - - Desired IP and/or subnet which should be removed - from this port (name or ID): subnet=,ip-address= - (repeat option to unset multiple fixed IP addresses) - -.. option:: --binding-profile - - Desired key which should be removed from binding-profile - (repeat option to unset multiple binding:profile data) - -.. option:: --security-group - - Security group which should be removed from this port (name or ID) - (repeat option to unset multiple security groups) - -.. option:: --allowed-address ip-address=[,mac-address=] - - Desired allowed-address pair which should be removed from this port: - ip-address=[,mac-address=] - (repeat option to unset multiple allowed-address pairs) - -.. option:: --qos-policy - - Remove the QoS policy attached to the port - -.. option:: --data-plane-status - - Clear existing information of data plane status - -.. option:: --tag - - Tag to be removed from the port - (repeat option to remove multiple tags) - -.. option:: --all-tag - - Clear all tags associated with the port - -.. _port_unset-port: -.. describe:: - - Port to modify (name or ID) +.. autoprogram-cliff:: openstack.network.v2 + :command: port * diff --git a/doc/source/cli/command-objects/router.rst b/doc/source/cli/command-objects/router.rst index 9c9364bc75..6e8e05d7c0 100644 --- a/doc/source/cli/command-objects/router.rst +++ b/doc/source/cli/command-objects/router.rst @@ -8,402 +8,5 @@ network access for servers on project networks. Network v2 -router add port ---------------- - -Add a port to a router - -.. program:: router add port -.. code:: bash - - openstack router add port - - - -.. _router_add_port: - -.. describe:: - - Router to which port will be added (name or ID) - -.. describe:: - - Port to be added (name or ID) - -router add subnet ------------------ - -Add a subnet to a router - -.. program:: router add subnet -.. code:: bash - - openstack router add subnet - - - -.. _router_add_subnet: - -.. describe:: - - Router to which subnet will be added (name or ID) - -.. describe:: - - Subnet to be added (name or ID) - -router create -------------- - -Create new router - -.. program:: router create -.. code:: bash - - openstack router create - [--project [--project-domain ]] - [--enable | --disable] - [--distributed | --centralized] - [--ha | --no-ha] - [--description ] - [--availability-zone-hint ] - [--tag | --no-tag] - - -.. option:: --project - - Owner's project (name or ID) - -.. option:: --project-domain - - Domain the project belongs to (name or ID). - This can be used in case collisions between project names exist. - -.. option:: --enable - - Enable router (default) - -.. option:: --disable - - Disable router - -.. option:: --distributed - - Create a distributed router - - The default router type (distributed vs centralized) is determined by a - configuration setting in the OpenStack deployment. Since we are unable - to know that default wihtout attempting to actually create a router it - is suggested to use either :option:`--distributed` or :option:`--centralized` - in situations where multiple cloud deployments may be used. - -.. option:: --centralized - - Create a centralized router - - See the note in :option:`--distributed` regarding the default used when - creating a new router. - -.. option:: --ha - - Create a highly available router - -.. option:: --no-ha - - Create a legacy router - -.. option:: --description - - Set router description - -.. option:: --availability-zone-hint - - Availability Zone in which to create this router - (Router Availability Zone extension required, - repeat option to set multiple availability zones) - -.. option:: --tag - - Tag to be added to the router (repeat option to set multiple tags) - -.. option:: --no-tag - - No tags associated with the router - -.. _router_create-name: -.. describe:: - - New router name - -router delete -------------- - -Delete router(s) - -.. program:: router delete -.. code:: bash - - openstack router delete - [ ...] - -.. _router_delete-router: -.. describe:: - - Router(s) to delete (name or ID) - -router list ------------ - -List routers - -.. program:: router list -.. code:: bash - - openstack router list - [--name ] - [--enable | --disable] - [--long] - [--project [--project-domain ]] - [--agent ] - [--tags [,,...]] [--any-tags [,,...]] - [--not-tags [,,...]] [--not-any-tags [,,...]] - -.. option:: --agent - - List routers hosted by an agent (ID only) - -.. option:: --long - - List additional fields in output - -.. option:: --name - - List routers according to their name - -.. option:: --enable - - List enabled routers - -.. option:: --disable - - List disabled routers - -.. option:: --project - - List routers according to their project (name or ID) - -.. option:: --project-domain - - Domain the project belongs to (name or ID). - This can be used in case collisions between project names exist. - -.. option:: --tags [,,...] - - List routers which have all given tag(s) - -.. option:: --any-tags [,,...] - - List routers which have any given tag(s) - -.. option:: --not-tags [,,...] - - Exclude routers which have all given tag(s) - -.. option:: --not-any-tags [,,...] - - Exclude routers which have any given tag(s) - -router remove port ------------------- - -Remove a port from a router - -.. program:: router remove port -.. code:: bash - - openstack router remove port - - - -.. _router_remove_port: - -.. describe:: - - Router from which port will be removed (name or ID) - -.. describe:: - - Port to be removed and deleted (name or ID) - -router remove subnet --------------------- - -Remove a subnet from a router - -.. program:: router remove subnet -.. code:: bash - - openstack router remove subnet - - - -.. _router_remove_subnet: - -.. describe:: - - Router from which subnet will be removed (name or ID) - -.. describe:: - - Subnet to be removed (name or ID) - -router set ----------- - -Set router properties - -.. program:: router set -.. code:: bash - - openstack router set - [--name ] - [--enable | --disable] - [--distributed | --centralized] - [--description ] - [--route destination=,gateway= | --no-route] - [--ha | --no-ha] - [--external-gateway [--enable-snat|--disable-snat] [--fixed-ip subnet=,ip-address=]] - [--tag ] [--no-tag] - - -.. option:: --name - - Set router name - -.. option:: --enable - - Enable router - -.. option:: --disable - - Disable router - -.. option:: --distributed - - Set router to distributed mode (disabled router only) - -.. option:: --centralized - - Set router to centralized mode (disabled router only) - -.. option:: --description - - Set router description - -.. option:: --route destination=,gateway= - - Routes associated with the router - destination: destination subnet (in CIDR notation) - gateway: nexthop IP address - (repeat option to set multiple routes) - -.. option:: --no-route - - Clear routes associated with the router. - Specify both --route and --no-route to overwrite - current value of route. - -.. option:: --ha - - Set the router as highly available (disabled router only) - -.. option:: --no-ha - - Clear high availablability attribute of the router (disabled router only) - -.. option:: --external-gateway - - External Network used as router's gateway (name or ID) - -.. option:: --enable-snat - - Enable Source NAT on external gateway - -.. option:: --disable-snat - - Disable Source NAT on external gateway - -.. option:: --fixed-ip subnet=,ip-address= - - Desired IP and/or subnet (name or ID) on external gateway: - subnet=,ip-address= - (repeat option to set multiple fixed IP addresses) - -.. option:: --tag - - Tag to be added to the router (repeat option to set multiple tags) - -.. option:: --no-tag - - Clear tags associated with the router. Specify both --tag - and --no-tag to overwrite current tags - -.. _router_set-router: -.. describe:: - - Router to modify (name or ID) - -router show ------------ - -Display router details - -.. program:: router show -.. code:: bash - - openstack router show - - -.. _router_show-router: -.. describe:: - - Router to display (name or ID) - -router unset ------------- - -Unset router properties - -.. program:: router unset -.. code:: bash - - openstack router unset - [--route destination=,gateway=] - [--external-gateway] - [--tag | --all-tag] - - -.. option:: --route destination=,gateway= - - Routes to be removed from the router - destination: destination subnet (in CIDR notation) - gateway: nexthop IP address - (repeat option to unset multiple routes) - -.. option:: --external-gateway - - Remove external gateway information from the router - -.. option:: --tag - - Tag to be removed from the router - (repeat option to remove multiple tags) - -.. option:: --all-tag - - Clear all tags associated with the router - -.. _router_unset-router: -.. describe:: - - Router to modify (name or ID) +.. autoprogram-cliff:: openstack.network.v2 + :command: router * diff --git a/doc/source/cli/command-objects/security-group-rule.rst b/doc/source/cli/command-objects/security-group-rule.rst index 5a2d8342b3..429bcf270b 100644 --- a/doc/source/cli/command-objects/security-group-rule.rst +++ b/doc/source/cli/command-objects/security-group-rule.rst @@ -7,194 +7,5 @@ and other resources on the network. Compute v2, Network v2 -security group rule create --------------------------- - -Create a new security group rule - -.. program:: security group rule create -.. code:: bash - - openstack security group rule create - [--remote-ip | --remote-group ] - [--dst-port | [--icmp-type [--icmp-code ]]] - [--protocol ] - [--ingress | --egress] - [--ethertype ] - [--project [--project-domain ]] - [--description ] - - -.. option:: --remote-ip - - Remote IP address block (may use CIDR notation; - default for IPv4 rule: 0.0.0.0/0, - default for IPv6 rule: ::/0) - -.. option:: --remote-group - - Remote security group (name or ID) - -.. option:: --dst-port - - Destination port, may be a single port or a starting and - ending port range: 137:139. Required for IP protocols TCP - and UDP. Ignored for ICMP IP protocols. - -.. option:: --icmp-type - - ICMP type for ICMP IP protocols - - *Network version 2 only* - -.. option:: --icmp-code - - ICMP code for ICMP IP protocols - - *Network version 2 only* - -.. option:: --protocol - - IP protocol (icmp, tcp, udp; default: tcp) - - *Compute version 2* - - IP protocol (ah, dccp, egp, esp, gre, icmp, igmp, - ipv6-encap, ipv6-frag, ipv6-icmp, ipv6-nonxt, - ipv6-opts, ipv6-route, ospf, pgm, rsvp, sctp, tcp, - udp, udplite, vrrp and integer representations [0-255] - or any; default: any (all protocols)) - - *Network version 2* - -.. option:: --ingress - - Rule applies to incoming network traffic (default) - - *Network version 2 only* - -.. option:: --egress - - Rule applies to outgoing network traffic - - *Network version 2 only* - -.. option:: --ethertype - - Ethertype of network traffic - (IPv4, IPv6; default: based on IP protocol) - - *Network version 2 only* - -.. option:: --project - - Owner's project (name or ID) - - *Network version 2 only* - -.. option:: --project-domain - - Domain the project belongs to (name or ID). - This can be used in case collisions between project names exist. - - *Network version 2 only* - -.. option:: --description - - Set security group rule description - - *Network version 2 only* - -.. describe:: - - Create rule in this security group (name or ID) - -security group rule delete --------------------------- - -Delete security group rule(s) - -.. program:: security group rule delete -.. code:: bash - - openstack security group rule delete - [ ...] - -.. describe:: - - Security group rule(s) to delete (ID only) - -security group rule list ------------------------- - -List security group rules - -.. program:: security group rule list -.. code:: bash - - openstack security group rule list - [--all-projects] - [--protocol ] - [--ethertype ] - [--ingress | --egress] - [--long] - [] - -.. option:: --all-projects - - Display information from all projects (admin only) - - *Network version 2 ignores this option and will always display information* - *for all projects (admin only).* - -.. option:: --long - - List additional fields in output - - *Compute version 2 does not have additional fields to display.* - -.. option:: --protocol - - List rules by the IP protocol (ah, dhcp, egp, esp, gre, icmp, igmp, - ipv6-encap, ipv6-frag, ipv6-icmp, ipv6-nonxt,ipv6-opts, ipv6-route, - ospf, pgm, rsvp, sctp, tcp, udp, udplite, vrrp and integer - representations [0-255] or any; default: any (all protocols)) - - *Network version 2* - -.. option:: --ethertype - - List rules by the Ethertype (IPv4 or IPv6) - - *Network version 2* - -.. option:: --ingress - - List rules applied to incoming network traffic - - *Network version 2 only* - -.. option:: --egress - - List rules applied to outgoing network traffic - - *Network version 2 only* - -.. describe:: - - List all rules in this security group (name or ID) - -security group rule show ------------------------- - -Display security group rule details - -.. program:: security group rule show -.. code:: bash - - openstack security group rule show - - -.. describe:: - - Security group rule to display (ID only) +.. autoprogram-cliff:: openstack.network.v2 + :command: security group rule * diff --git a/doc/source/cli/command-objects/security-group.rst b/doc/source/cli/command-objects/security-group.rst index 403e5fc08f..4edc199547 100644 --- a/doc/source/cli/command-objects/security-group.rst +++ b/doc/source/cli/command-objects/security-group.rst @@ -8,197 +8,23 @@ which specify the network access rules. Compute v2, Network v2 -security group create ---------------------- +.. NOTE(efried): have to list these out one by one; 'security group *' pulls in + ... rule *. -Create a new security group +.. autoprogram-cliff:: openstack.network.v2 + :command: security group create -.. program:: security group create -.. code:: bash +.. autoprogram-cliff:: openstack.network.v2 + :command: security group delete - openstack security group create - [--description ] - [--project [--project-domain ]] - [--tag | --no-tag] - +.. autoprogram-cliff:: openstack.network.v2 + :command: security group list -.. option:: --description +.. autoprogram-cliff:: openstack.network.v2 + :command: security group set - Security group description +.. autoprogram-cliff:: openstack.network.v2 + :command: security group show -.. option:: --project - - Owner's project (name or ID) - - *Network version 2 only* - -.. option:: --project-domain - - Domain the project belongs to (name or ID). - This can be used in case collisions between project names exist. - - *Network version 2 only* - -.. option:: --tag - - Tag to be added to the security group (repeat option to set multiple tags) - - *Network version 2 only* - -.. option:: --no-tag - - No tags associated with the security group - - *Network version 2 only* - -.. describe:: - - New security group name - -security group delete ---------------------- - -Delete security group(s) - -.. program:: security group delete -.. code:: bash - - openstack security group delete - [ ...] - -.. describe:: - - Security group(s) to delete (name or ID) - -security group list -------------------- - -List security groups - -.. program:: security group list -.. code:: bash - - openstack security group list - [--all-projects] - [--project [--project-domain ]] - [--tags [,,...]] [--any-tags [,,...]] - [--not-tags [,,...]] [--not-any-tags [,,...]] - -.. option:: --all-projects - - Display information from all projects (admin only) - - *Network version 2 ignores this option and will always display information* - *for all projects (admin only).* - -.. option:: --project - - List security groups according to the project (name or ID) - - *Network version 2 only* - -.. option:: --project-domain - - Domain the project belongs to (name or ID). - This can be used in case collisions between project names exist. - - *Network version 2 only* - -.. option:: --tags [,,...] - - List security groups which have all given tag(s) - - *Network version 2 only* - -.. option:: --any-tags [,,...] - - List security groups which have any given tag(s) - - *Network version 2 only* - -.. option:: --not-tags [,,...] - - Exclude security groups which have all given tag(s) - - *Network version 2 only* - -.. option:: --not-any-tags [,,...] - - Exclude security groups which have any given tag(s) - - *Network version 2 only* - -security group set ------------------- - -Set security group properties - -.. program:: security group set -.. code:: bash - - openstack security group set - [--name ] - [--description ] - [--tag ] [--no-tag] - - -.. option:: --name - - New security group name - -.. option:: --description - - New security group description - -.. option:: --tag - - Tag to be added to the security group (repeat option to set multiple tags) - -.. option:: --no-tag - - Clear tags associated with the security group. Specify both --tag - and --no-tag to overwrite current tags - -.. describe:: - - Security group to modify (name or ID) - -security group show -------------------- - -Display security group details - -.. program:: security group show -.. code:: bash - - openstack security group show - - -.. describe:: - - Security group to display (name or ID) - -security group unset --------------------- - -Unset security group properties - -.. program:: security group unset -.. code:: bash - - openstack security group unset - [--tag | --all-tag] - - -.. option:: --tag - - Tag to be removed from the security group - (repeat option to remove multiple tags) - -.. option:: --all-tag - - Clear all tags associated with the security group - -.. describe:: - - Security group to modify (name or ID) +.. autoprogram-cliff:: openstack.network.v2 + :command: security group unset diff --git a/doc/source/cli/command-objects/subnet-pool.rst b/doc/source/cli/command-objects/subnet-pool.rst index 77259a83fd..ce9649dc94 100644 --- a/doc/source/cli/command-objects/subnet-pool.rst +++ b/doc/source/cli/command-objects/subnet-pool.rst @@ -7,304 +7,5 @@ that are available for IP address allocation. Network v2 -subnet pool create ------------------- - -Create subnet pool - -.. program:: subnet pool create -.. code:: bash - - openstack subnet pool create - [--default-prefix-length ] - [--min-prefix-length ] - [--max-prefix-length ] - [--description ] - [--project [--project-domain ]] - [--address-scope ] - [--default | --no-default] - [--share | --no-share] - [--default-quota ] - [--tag | --no-tag] - --pool-prefix [...] - - -.. option:: --default-prefix-length - - Set subnet pool default prefix length - -.. option:: --min-prefix-length - - Set subnet pool minimum prefix length - -.. option:: --max-prefix-length - - Set subnet pool maximum prefix length - -.. option:: --description - - Set subnet pool description - -.. option:: --project - - Owner's project (name or ID) - -.. option:: --project-domain - - Domain the project belongs to (name or ID). This can be used in case - collisions between project names exist. - -.. option:: --address-scope - - Set address scope associated with the subnet pool (name or ID), - prefixes must be unique across address scopes - -.. option:: --default - - Set this as a default subnet pool - -.. option:: --no-default - - Set this as a non-default subnet pool - -.. option:: --share - - Set this subnet pool as shared - -.. option:: --no-share - - Set this subnet pool as not shared - -.. option:: --default-quota - - Set default per-project quota for this subnet pool as the number of - IP addresses that can be allocated from the subnet pool - -.. option:: --tag - - Tag to be added to the subnet pool (repeat option to set multiple tags) - -.. option:: --no-tag - - No tags associated with the subnet pool - -.. option:: --pool-prefix - - Set subnet pool prefixes (in CIDR notation) - (repeat option to set multiple prefixes) - -.. _subnet_pool_create-name: -.. describe:: - - Name of the new subnet pool - -subnet pool delete ------------------- - -Delete subnet pool(s) - -.. program:: subnet pool delete -.. code:: bash - - openstack subnet pool delete - [ ...] - -.. _subnet_pool_delete-subnet-pool: -.. describe:: - - Subnet pool(s) to delete (name or ID) - -subnet pool list ----------------- - -List subnet pools - -.. program:: subnet pool list -.. code:: bash - - openstack subnet pool list - [--long] - [--share | --no-share] - [--default | --no-default] - [--project [--project-domain ]] - [--name ] - [--address-scope ] - [--tags [,,...]] [--any-tags [,,...]] - [--not-tags [,,...]] [--not-any-tags [,,...]] - -.. option:: --long - - List additional fields in output - -.. option:: --share - - List subnet pools shared between projects - -.. option:: --no-share - - List subnet pools not shared between projects - -.. option:: --default - - List subnet pools used as the default external subnet pool - -.. option:: --no-default - - List subnet pools not used as the default external subnet pool - -.. option:: --project - - List subnet pools according to their project (name or ID) - -.. option:: --project-domain - - Domain the project belongs to (name or ID). - This can be used in case collisions between project names exist. - -.. option:: --name - - List only subnet pools of given name in output - -.. option:: --address-scope - - List only subnet pools of given address scope in output (name or ID) - -.. option:: --tags [,,...] - - List subnet pools which have all given tag(s) - -.. option:: --any-tags [,,...] - - List subnet pools which have any given tag(s) - -.. option:: --not-tags [,,...] - - Exclude subnet pools which have all given tag(s) - -.. option:: --not-any-tags [,,...] - - Exclude subnet pools which have any given tag(s) - -subnet pool set ---------------- - -Set subnet pool properties - -.. program:: subnet pool set -.. code:: bash - - openstack subnet pool set - [--name ] - [--pool-prefix [...]] - [--default-prefix-length ] - [--min-prefix-length ] - [--max-prefix-length ] - [--address-scope | --no-address-scope] - [--default | --no-default] - [--description ] - [--default-quota ] - [--tag ] [--no-tag] - - -.. option:: --name - - Set subnet pool name - -.. option:: --pool-prefix - - Set subnet pool prefixes (in CIDR notation) - (repeat option to set multiple prefixes) - -.. option:: --default-prefix-length - - Set subnet pool default prefix length - -.. option:: --min-prefix-length - - Set subnet pool minimum prefix length - -.. option:: --max-prefix-length - - Set subnet pool maximum prefix length - -.. option:: --address-scope - - Set address scope associated with the subnet pool (name or ID), - prefixes must be unique across address scopes - -.. option:: --no-address-scope - - Remove address scope associated with the subnet pool - -.. option:: --default - - Set this as a default subnet pool - -.. option:: --no-default - - Set this as a non-default subnet pool - -.. option:: --description - - Set subnet pool description - -.. option:: --default-quota - - Set default per-project quota for this subnet pool as the number of - IP addresses that can be allocated from the subnet pool - -.. option:: --tag - - Tag to be added to the subnet pool (repeat option to set multiple tags) - -.. option:: --no-tag - - Clear tags associated with the subnet pool. Specify both --tag - and --no-tag to overwrite current tags - -.. _subnet_pool_set-subnet-pool: -.. describe:: - - Subnet pool to modify (name or ID) - -subnet pool show ----------------- - -Display subnet pool details - -.. program:: subnet pool show -.. code:: bash - - openstack subnet pool show - - -.. _subnet_pool_show-subnet-pool: -.. describe:: - - Subnet pool to display (name or ID) - -subnet pool unset ------------------ - -Unset subnet pool properties - -.. program:: subnet pool unset -.. code:: bash - - openstack subnet pool unset - [--tag | --all-tag] - - -.. option:: --tag - - Tag to be removed from the subnet pool - (repeat option to remove multiple tags) - -.. option:: --all-tag - - Clear all tags associated with the subnet pool - -.. _subnet_pool_unset-subnet-pool: -.. describe:: - - Subnet pool to modify (name or ID) +.. autoprogram-cliff:: openstack.network.v2 + :command: subnet pool * diff --git a/doc/source/cli/command-objects/subnet.rst b/doc/source/cli/command-objects/subnet.rst index 73e656d895..488fc5a2f5 100644 --- a/doc/source/cli/command-objects/subnet.rst +++ b/doc/source/cli/command-objects/subnet.rst @@ -8,427 +8,23 @@ network. Network v2 -subnet create -------------- +.. NOTE(efried): have to list these out one by one; 'subnet *' pulls in + subnet pool *. -Create new subnet +.. autoprogram-cliff:: openstack.network.v2 + :command: subnet create -.. program:: subnet create -.. code:: bash +.. autoprogram-cliff:: openstack.network.v2 + :command: subnet delete - openstack subnet create - [--project [--project-domain ]] - [--subnet-pool | --use-default-subnet-pool [--prefix-length ] | --use-prefix-delegation] - [--subnet-range ] - [--allocation-pool start=,end=] - [--dhcp | --no-dhcp] - [--dns-nameserver ] - [--gateway ] - [--host-route destination=,gateway=] - [--ip-version {4,6}] - [--description ] - [--ipv6-ra-mode {dhcpv6-stateful,dhcpv6-stateless,slaac}] - [--ipv6-address-mode {dhcpv6-stateful,dhcpv6-stateless,slaac}] - [--network-segment ] - [--service-type ] - [--tag | --no-tag] - --network - +.. autoprogram-cliff:: openstack.network.v2 + :command: subnet list -.. option:: --project +.. autoprogram-cliff:: openstack.network.v2 + :command: subnet set - Owner's project (name or ID) +.. autoprogram-cliff:: openstack.network.v2 + :command: subnet show -.. option:: --project-domain - - Domain the project belongs to (name or ID). - This can be used in case collisions between project names exist. - -.. option:: --subnet-pool - - Subnet pool from which this subnet will obtain a CIDR (name or ID) - -.. option:: --use-prefix-delegation - - Use 'prefix-delegation' if IP is IPv6 format and IP would be delegated - externally - -.. option:: --use-default-subnet-pool - - Use default subnet pool for :option:`--ip-version` - -.. option:: --prefix-length - - Prefix length for subnet allocation from subnet pool - -.. option:: --subnet-range - - Subnet range in CIDR notation - (required if :option:`--subnet-pool` is not specified, optional otherwise) - -.. option:: --allocation-pool start=,end= - - Allocation pool IP addresses for this subnet e.g.: - ``start=192.168.199.2,end=192.168.199.254`` - (repeat option to add multiple IP addresses) - -.. option:: --dhcp - - Enable DHCP (default) - -.. option:: --no-dhcp - - Disable DHCP - -.. option:: --dns-nameserver - - DNS server for this subnet (repeat option to set multiple DNS servers) - -.. option:: --gateway - - Specify a gateway for the subnet. The three options are: - : Specific IP address to use as the gateway, - 'auto': Gateway address should automatically be chosen from - within the subnet itself, 'none': This subnet will not use - a gateway, e.g.: ``--gateway 192.168.9.1``, ``--gateway auto``, - ``--gateway none`` (default is 'auto'). - -.. option:: --host-route destination=,gateway= - - Additional route for this subnet e.g.: - ``destination=10.10.0.0/16,gateway=192.168.71.254`` - destination: destination subnet (in CIDR notation) - gateway: nexthop IP address - (repeat option to add multiple routes) - -.. option:: --ip-version {4,6} - - IP version (default is 4). Note that when subnet pool is specified, - IP version is determined from the subnet pool and this option - is ignored. - -.. option:: --description - - Set subnet description - -.. option:: --ipv6-ra-mode {dhcpv6-stateful,dhcpv6-stateless,slaac} - - IPv6 RA (Router Advertisement) mode, - valid modes: [dhcpv6-stateful, dhcpv6-stateless, slaac] - -.. option:: --ipv6-address-mode {dhcpv6-stateful,dhcpv6-stateless,slaac} - - IPv6 address mode, valid modes: [dhcpv6-stateful, dhcpv6-stateless, slaac] - -.. option:: --network-segment - - Network segment to associate with this subnet (name or ID) - -.. option:: --service-type - - Service type for this subnet e.g.: - ``network:floatingip_agent_gateway``. - Must be a valid device owner value for a network port - (repeat option to set multiple service types) - -.. option:: --tag - - Tag to be added to the subnet (repeat option to set multiple tags) - -.. option:: --no-tag - - No tags associated with the subnet - -.. option:: --network - - Network this subnet belongs to (name or ID) - -.. _subnet_create-name: -.. describe:: - - Name of subnet to create - -subnet delete -------------- - -Delete subnet(s) - -.. program:: subnet delete -.. code:: bash - - openstack subnet delete - [ ...] - -.. _subnet_delete-subnet: -.. describe:: - - Subnet(s) to delete (name or ID) - -subnet list ------------ - -List subnets - -.. program:: subnet list -.. code:: bash - - openstack subnet list - [--long] - [--ip-version {4,6}] - [--dhcp | --no-dhcp] - [--project [--project-domain ]] - [--network ] - [--gateway ] - [--name ] - [--subnet-range ] - [--tags [,,...]] [--any-tags [,,...]] - [--not-tags [,,...]] [--not-any-tags [,,...]] - -.. option:: --long - - List additional fields in output - -.. option:: --ip-version {4, 6} - - List only subnets of given IP version in output. - Allowed values for IP version are 4 and 6. - -.. option:: --dhcp - - List subnets which have DHCP enabled - -.. option:: --no-dhcp - - List subnets which have DHCP disabled - -.. option:: --service-type - - List only subnets of a given service type in output - e.g.: ``network:floatingip_agent_gateway``. - Must be a valid device owner value for a network port - (repeat option to list multiple service types) - -.. option:: --project - - List only subnets which belong to a given project in output (name or ID) - -.. option:: --project-domain - - Domain the project belongs to (name or ID). - This can be used in case collisions between project names exist. - -.. option:: --network - - List only subnets which belong to a given network in output (name or ID) - -.. option:: --gateway - - List only subnets of given gateway IP in output - -.. option:: --name - - List only subnets of given name in output - -.. option:: --subnet-range - - List only subnets of given subnet range (in CIDR notation) in output - e.g.: ``--subnet-range 10.10.0.0/16`` - -.. option:: --tags [,,...] - - List subnets which have all given tag(s) - -.. option:: --any-tags [,,...] - - List subnets which have any given tag(s) - -.. option:: --not-tags [,,...] - - Exclude subnets which have all given tag(s) - -.. option:: --not-any-tags [,,...] - - Exclude subnets which have any given tag(s) - -subnet set ----------- - -Set subnet properties - -.. program:: subnet set -.. code:: bash - - openstack subnet set - [--allocation-pool start=,end=] - [--no-allocation-pool] - [--dhcp | --no-dhcp] - [--dns-nameserver ] - [--no-dns-nameserver] - [--gateway ] - [--network-segment ] - [--host-route destination=,gateway=] - [--no-host-route] - [--service-type ] - [--name ] - [--description ] - [--tag ] [--no-tag] - - -.. option:: --allocation-pool start=,end= - - Allocation pool IP addresses for this subnet e.g.: - ``start=192.168.199.2,end=192.168.199.254`` - (repeat option to add multiple IP addresses) - -.. option:: --no-allocation-pool - - Clear associated allocation pools from this subnet. - Specify both :option:`--allocation-pool` and :option:`--no-allocation-pool` - to overwrite the current allocation pool information. - -.. option:: --dhcp - - Enable DHCP - -.. option:: --no-dhcp - - Disable DHCP - -.. option:: --dns-nameserver - - DNS server for this subnet (repeat option to set multiple DNS servers) - -.. option:: --no-dns-nameservers - - Clear existing information of DNS servers. - Specify both :option:`--dns-nameserver` and :option:`--no-dns-nameservers` - to overwrite the current DNS server information. - -.. option:: --gateway - - Specify a gateway for the subnet. The options are: - : Specific IP address to use as the gateway, - 'none': This subnet will not use a gateway, - e.g.: ``--gateway 192.168.9.1``, ``--gateway none``. - -.. option:: --network-segment - - Network segment to associate with this subnet (name or ID). It is only - allowed to set the segment if the current value is `None`, the network - must also have only one segment and only one subnet can exist on the - network. - -.. option:: --host-route destination=,gateway= - - Additional route for this subnet e.g.: - ``destination=10.10.0.0/16,gateway=192.168.71.254`` - destination: destination subnet (in CIDR notation) - gateway: nexthop IP address - -.. option:: --no-host-route - - Clear associated host routes from this subnet. - Specify both :option:`--host-route` and :option:`--no-host-route` - to overwrite the current host route information. - -.. option:: --service-type - - Service type for this subnet e.g.: - ``network:floatingip_agent_gateway``. - Must be a valid device owner value for a network port - (repeat option to set multiple service types) - -.. option:: --description - - Set subnet description - -.. option:: --name - - Updated name of the subnet - -.. option:: --tag - - Tag to be added to the subnet (repeat option to set multiple tags) - -.. option:: --no-tag - - Clear tags associated with the subnet. Specify both --tag - and --no-tag to overwrite current tags - -.. _subnet_set-subnet: -.. describe:: - - Subnet to modify (name or ID) - - -subnet show ------------ - -Display subnet details - -.. program:: subnet show -.. code:: bash - - openstack subnet show - - -.. _subnet_show-subnet: -.. describe:: - - Subnet to display (name or ID) - -subnet unset ------------- - -Unset subnet properties - -.. program:: subnet unset -.. code:: bash - - openstack subnet unset - [--allocation-pool start=,end= [...]] - [--dns-nameserver [...]] - [--host-route destination=,gateway= [...]] - [--service-type ] - [--tag | --all-tag] - - -.. option:: --dns-nameserver - - DNS server to be removed from this subnet - (repeat option to unset multiple DNS servers) - -.. option:: --allocation-pool start=,end= - - Allocation pool IP addresses to be removed from this - subnet e.g.: ``start=192.168.199.2,end=192.168.199.254`` - (repeat option to unset multiple allocation pools) - -.. option:: --host-route destination=,gateway= - - Route to be removed from this subnet e.g.: - ``destination=10.10.0.0/16,gateway=192.168.71.254`` - destination: destination subnet (in CIDR notation) - gateway: nexthop IP address - (repeat option to unset multiple host routes) - -.. option:: --service-type - - Service type to be removed from this subnet e.g.: - ``network:floatingip_agent_gateway``. - Must be a valid device owner value for a network port - (repeat option to unset multiple service types) - -.. option:: --tag - - Tag to be removed from the subnet - (repeat option to remove multiple tags) - -.. option:: --all-tag - - Clear all tags associated with the subnet - -.. _subnet_unset-subnet: -.. describe:: - - Subnet to modify (name or ID) +.. autoprogram-cliff:: openstack.network.v2 + :command: subnet unset diff --git a/openstackclient/identity/common.py b/openstackclient/identity/common.py index d125d2851d..7be2a17b72 100644 --- a/openstackclient/identity/common.py +++ b/openstackclient/identity/common.py @@ -233,13 +233,13 @@ def add_group_domain_option_to_parser(parser): ) -def add_project_domain_option_to_parser(parser): +def add_project_domain_option_to_parser(parser, enhance_help=lambda _h: _h): parser.add_argument( '--project-domain', metavar='', - help=_('Domain the project belongs to (name or ID). ' - 'This can be used in case collisions between project names ' - 'exist.'), + help=enhance_help(_('Domain the project belongs to (name or ID). This ' + 'can be used in case collisions between project ' + 'names exist.')), ) diff --git a/openstackclient/network/common.py b/openstackclient/network/common.py index d22b2caa3c..e68628b3f1 100644 --- a/openstackclient/network/common.py +++ b/openstackclient/network/common.py @@ -34,6 +34,10 @@ 'security_groups': 'security-groups', } +_NET_TYPE_NEUTRON = 'neutron' +_NET_TYPE_COMPUTE = 'nova-network' +_QUALIFIER_FMT = "%s\n\n*%s*" + @contextlib.contextmanager def check_missing_extension_if_error(client_manager, attrs): @@ -51,35 +55,87 @@ def check_missing_extension_if_error(client_manager, attrs): @six.add_metaclass(abc.ABCMeta) -class NetworkAndComputeCommand(command.Command): - """Network and Compute Command +class NetDetectionMixin(object): + """Convenience methods for nova-network vs. neutron decisions. - Command class for commands that support implementation via - the network or compute endpoint. Such commands have different - implementations for take_action() and may even have different - arguments. - """ + A live environment detects which network type it is running and creates its + parser with only the options relevant to that network type. - def take_action(self, parsed_args): - if self.app.client_manager.is_network_endpoint_enabled(): - return self.take_action_network(self.app.client_manager.network, - parsed_args) - else: - return self.take_action_compute(self.app.client_manager.compute, - parsed_args) + But the command classes are used for docs builds as well, and docs must + present the options for both network types, often qualified accordingly. + """ + @property + def _network_type(self): + """Discover whether the running cloud is using neutron or nova-network. + + :return: + * ``NET_TYPE_NEUTRON`` if neutron is detected + * ``NET_TYPE_COMPUTE`` if running in a cloud but neutron is not + detected. + * ``None`` if not running in a cloud, which hopefully means we're + building docs. + """ + # Have we set it up yet for this command? + if not hasattr(self, '_net_type'): + # import pdb; pdb.set_trace() + try: + if self.app.client_manager.is_network_endpoint_enabled(): + net_type = _NET_TYPE_NEUTRON + else: + net_type = _NET_TYPE_COMPUTE + except AttributeError: + LOG.warning( + "%s: Could not detect a network type. Assuming we are " + "building docs.", self.__class__.__name__) + net_type = None + self._net_type = net_type + return self._net_type + + @property + def is_neutron(self): + return self._network_type is _NET_TYPE_NEUTRON + + @property + def is_nova_network(self): + return self._network_type is _NET_TYPE_COMPUTE + + @property + def is_docs_build(self): + return self._network_type is None + + def enhance_help_neutron(self, _help): + if self.is_docs_build: + # Why can't we say 'neutron'? + return _QUALIFIER_FMT % (_help, _("Network version 2 only")) + return _help + + def enhance_help_nova_network(self, _help): + if self.is_docs_build: + # Why can't we say 'nova-network'? + return _QUALIFIER_FMT % (_help, _("Compute version 2 only")) + return _help + + @staticmethod + def split_help(network_help, compute_help): + return ( + "*%(network_qualifier)s:*\n %(network_help)s\n\n" + "*%(compute_qualifier)s:*\n %(compute_help)s" % dict( + network_qualifier=_("Network version 2"), + network_help=network_help, + compute_qualifier=_("Compute version 2"), + compute_help=compute_help)) def get_parser(self, prog_name): LOG.debug('get_parser(%s)', prog_name) - parser = super(NetworkAndComputeCommand, self).get_parser(prog_name) + parser = super(NetDetectionMixin, self).get_parser(prog_name) parser = self.update_parser_common(parser) LOG.debug('common parser: %s', parser) - if ( - self.app is None or - self.app.client_manager.is_network_endpoint_enabled() - ): - return self.update_parser_network(parser) - else: - return self.update_parser_compute(parser) + if self.is_neutron or self.is_docs_build: + parser = self.update_parser_network(parser) + if self.is_nova_network or self.is_docs_build: + # Add nova-net options if running nova-network or building docs + parser = self.update_parser_compute(parser) + return parser def update_parser_common(self, parser): """Default is no updates to parser.""" @@ -93,17 +149,35 @@ def update_parser_compute(self, parser): """Default is no updates to parser.""" return parser - @abc.abstractmethod + def take_action(self, parsed_args): + if self.is_neutron: + return self.take_action_network(self.app.client_manager.network, + parsed_args) + elif self.is_nova_network: + return self.take_action_compute(self.app.client_manager.compute, + parsed_args) + def take_action_network(self, client, parsed_args): """Override to do something useful.""" pass - @abc.abstractmethod def take_action_compute(self, client, parsed_args): """Override to do something useful.""" pass +@six.add_metaclass(abc.ABCMeta) +class NetworkAndComputeCommand(NetDetectionMixin, command.Command): + """Network and Compute Command + + Command class for commands that support implementation via + the network or compute endpoint. Such commands have different + implementations for take_action() and may even have different + arguments. + """ + pass + + @six.add_metaclass(abc.ABCMeta) class NetworkAndComputeDelete(NetworkAndComputeCommand): """Network and Compute Delete @@ -149,7 +223,7 @@ def take_action(self, parsed_args): @six.add_metaclass(abc.ABCMeta) -class NetworkAndComputeLister(command.Lister): +class NetworkAndComputeLister(NetDetectionMixin, command.Lister): """Network and Compute Lister Lister class for commands that support implementation via @@ -157,50 +231,11 @@ class NetworkAndComputeLister(command.Lister): implementations for take_action() and may even have different arguments. """ - - def take_action(self, parsed_args): - if self.app.client_manager.is_network_endpoint_enabled(): - return self.take_action_network(self.app.client_manager.network, - parsed_args) - else: - return self.take_action_compute(self.app.client_manager.compute, - parsed_args) - - def get_parser(self, prog_name): - LOG.debug('get_parser(%s)', prog_name) - parser = super(NetworkAndComputeLister, self).get_parser(prog_name) - parser = self.update_parser_common(parser) - LOG.debug('common parser: %s', parser) - if self.app.client_manager.is_network_endpoint_enabled(): - return self.update_parser_network(parser) - else: - return self.update_parser_compute(parser) - - def update_parser_common(self, parser): - """Default is no updates to parser.""" - return parser - - def update_parser_network(self, parser): - """Default is no updates to parser.""" - return parser - - def update_parser_compute(self, parser): - """Default is no updates to parser.""" - return parser - - @abc.abstractmethod - def take_action_network(self, client, parsed_args): - """Override to do something useful.""" - pass - - @abc.abstractmethod - def take_action_compute(self, client, parsed_args): - """Override to do something useful.""" - pass + pass @six.add_metaclass(abc.ABCMeta) -class NetworkAndComputeShowOne(command.ShowOne): +class NetworkAndComputeShowOne(NetDetectionMixin, command.ShowOne): """Network and Compute ShowOne ShowOne class for commands that support implementation via @@ -222,35 +257,3 @@ def take_action(self, parsed_args): if exc.details: msg += ", " + six.text_type(exc.details) raise exceptions.CommandError(msg) - - def get_parser(self, prog_name): - LOG.debug('get_parser(%s)', prog_name) - parser = super(NetworkAndComputeShowOne, self).get_parser(prog_name) - parser = self.update_parser_common(parser) - LOG.debug('common parser: %s', parser) - if self.app.client_manager.is_network_endpoint_enabled(): - return self.update_parser_network(parser) - else: - return self.update_parser_compute(parser) - - def update_parser_common(self, parser): - """Default is no updates to parser.""" - return parser - - def update_parser_network(self, parser): - """Default is no updates to parser.""" - return parser - - def update_parser_compute(self, parser): - """Default is no updates to parser.""" - return parser - - @abc.abstractmethod - def take_action_network(self, client, parsed_args): - """Override to do something useful.""" - pass - - @abc.abstractmethod - def take_action_compute(self, client, parsed_args): - """Override to do something useful.""" - pass diff --git a/openstackclient/network/v2/_tag.py b/openstackclient/network/v2/_tag.py index ce39f35ec0..e1cba22ea9 100644 --- a/openstackclient/network/v2/_tag.py +++ b/openstackclient/network/v2/_tag.py @@ -22,34 +22,39 @@ def __call__(self, parser, namespace, values, option_string=None): setattr(namespace, self.dest, values.split(',')) -def add_tag_filtering_option_to_parser(parser, collection_name): +def add_tag_filtering_option_to_parser(parser, collection_name, + enhance_help=lambda _h: _h): parser.add_argument( '--tags', metavar='[,,...]', action=_CommaListAction, - help=_('List %s which have all given tag(s) ' - '(Comma-separated list of tags)') % collection_name + help=enhance_help( + _('List %s which have all given tag(s) (Comma-separated list of ' + 'tags)') % collection_name) ) parser.add_argument( '--any-tags', metavar='[,,...]', action=_CommaListAction, - help=_('List %s which have any given tag(s) ' - '(Comma-separated list of tags)') % collection_name + help=enhance_help( + _('List %s which have any given tag(s) (Comma-separated list of ' + 'tags)') % collection_name) ) parser.add_argument( '--not-tags', metavar='[,,...]', action=_CommaListAction, - help=_('Exclude %s which have all given tag(s) ' - '(Comma-separated list of tags)') % collection_name + help=enhance_help( + _('Exclude %s which have all given tag(s) (Comma-separated list ' + 'of tags)') % collection_name) ) parser.add_argument( '--not-any-tags', metavar='[,,...]', action=_CommaListAction, - help=_('Exclude %s which have any given tag(s) ' - '(Comma-separated list of tags)') % collection_name + help=enhance_help( + _('Exclude %s which have any given tag(s) (Comma-separated list ' + 'of tags)') % collection_name) ) @@ -64,37 +69,42 @@ def get_tag_filtering_args(parsed_args, args): args['not_any_tags'] = ','.join(parsed_args.not_any_tags) -def add_tag_option_to_parser_for_create(parser, resource_name): +def add_tag_option_to_parser_for_create(parser, resource_name, + enhance_help=lambda _h: _h): tag_group = parser.add_mutually_exclusive_group() tag_group.add_argument( '--tag', action='append', dest='tags', metavar='', - help=_("Tag to be added to the %s " - "(repeat option to set multiple tags)") % resource_name + help=enhance_help( + _("Tag to be added to the %s " + "(repeat option to set multiple tags)") % resource_name) ) tag_group.add_argument( '--no-tag', action='store_true', - help=_("No tags associated with the %s") % resource_name + help=enhance_help(_("No tags associated with the %s") % resource_name) ) -def add_tag_option_to_parser_for_set(parser, resource_name): +def add_tag_option_to_parser_for_set(parser, resource_name, + enhance_help=lambda _h: _h): parser.add_argument( '--tag', action='append', dest='tags', metavar='', - help=_("Tag to be added to the %s " - "(repeat option to set multiple tags)") % resource_name + help=enhance_help( + _("Tag to be added to the %s (repeat option to set multiple " + "tags)") % resource_name) ) parser.add_argument( '--no-tag', action='store_true', - help=_("Clear tags associated with the %s. Specify both " - "--tag and --no-tag to overwrite current tags") % resource_name + help=enhance_help( + _("Clear tags associated with the %s. Specify both --tag and " + "--no-tag to overwrite current tags") % resource_name) ) diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index e0aa0435ed..bd43379aff 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -114,57 +114,65 @@ def update_parser_network(self, parser): parser.add_argument( '--subnet', metavar='', - help=_("Subnet on which you want to create the floating IP " - "(name or ID)") + help=self.enhance_help_neutron( + _("Subnet on which you want to create the floating IP " + "(name or ID)")) ) parser.add_argument( '--port', metavar='', - help=_("Port to be associated with the floating IP " - "(name or ID)") + help=self.enhance_help_neutron( + _("Port to be associated with the floating IP " + "(name or ID)")) ) parser.add_argument( '--floating-ip-address', metavar='', dest='floating_ip_address', - help=_("Floating IP address") + help=self.enhance_help_neutron(_("Floating IP address")) ) parser.add_argument( '--fixed-ip-address', metavar='', dest='fixed_ip_address', - help=_("Fixed IP address mapped to the floating IP") + help=self.enhance_help_neutron( + _("Fixed IP address mapped to the floating IP")) ) parser.add_argument( '--qos-policy', metavar='', - help=_("Attach QoS policy to the floating IP (name or ID)") + help=self.enhance_help_neutron( + _("Attach QoS policy to the floating IP (name or ID)")) ) parser.add_argument( '--description', metavar='', - help=_('Set floating IP description') + help=self.enhance_help_neutron(_('Set floating IP description')) ) parser.add_argument( '--project', metavar='', - help=_("Owner's project (name or ID)") + help=self.enhance_help_neutron(_("Owner's project (name or ID)")) ) parser.add_argument( '--dns-domain', metavar='', dest='dns_domain', - help=_("Set DNS domain for this floating IP") + help=self.enhance_help_neutron( + _("Set DNS domain for this floating IP")) ) parser.add_argument( '--dns-name', metavar='', dest='dns_name', - help=_("Set DNS name for this floating IP") + help=self.enhance_help_neutron( + _("Set DNS name for this floating IP")) ) - identity_common.add_project_domain_option_to_parser(parser) - _tag.add_tag_option_to_parser_for_create(parser, _('floating IP')) + identity_common.add_project_domain_option_to_parser( + parser, enhance_help=self.enhance_help_neutron) + _tag.add_tag_option_to_parser_for_create( + parser, _('floating IP'), enhance_help=self.enhance_help_neutron) return parser def take_action_network(self, client, parsed_args): @@ -217,60 +225,68 @@ def take_action_compute(self, client, parsed_args): class ListFloatingIP(common.NetworkAndComputeLister): # TODO(songminglong): Use SDK resource mapped attribute names once # the OSC minimum requirements include SDK 1.0 + _description = _("List floating IP(s)") def update_parser_network(self, parser): parser.add_argument( '--network', metavar='', - help=_("List floating IP(s) according to " - "given network (name or ID)") + help=self.enhance_help_neutron( + _("List floating IP(s) according to " + "given network (name or ID)")) ) parser.add_argument( '--port', metavar='', - help=_("List floating IP(s) according to " - "given port (name or ID)") + help=self.enhance_help_neutron( + _("List floating IP(s) according to given port (name or ID)")) ) parser.add_argument( '--fixed-ip-address', metavar='', - help=_("List floating IP(s) according to " - "given fixed IP address") + help=self.enhance_help_neutron( + _("List floating IP(s) according to given fixed IP address")) ) parser.add_argument( '--floating-ip-address', metavar='', - help=_("List floating IP(s) according to " - "given floating IP address") + help=self.enhance_help_neutron( + _("List floating IP(s) according to given floating IP " + "address")) ) parser.add_argument( '--long', action='store_true', default=False, - help=_("List additional fields in output") + help=self.enhance_help_neutron( + _("List additional fields in output")) ) parser.add_argument( '--status', metavar='', choices=['ACTIVE', 'DOWN'], - help=_("List floating IP(s) according to " - "given status ('ACTIVE', 'DOWN')") + help=self.enhance_help_neutron( + _("List floating IP(s) according to given status ('ACTIVE', " + "'DOWN')")) ) parser.add_argument( '--project', metavar='', - help=_("List floating IP(s) according to " - "given project (name or ID)") + help=self.enhance_help_neutron( + _("List floating IP(s) according to given project (name or " + "ID)")) ) identity_common.add_project_domain_option_to_parser(parser) parser.add_argument( '--router', metavar='', - help=_("List floating IP(s) according to " - "given router (name or ID)") + help=self.enhance_help_neutron( + _("List floating IP(s) according to given router (name or " + "ID)")) ) - _tag.add_tag_filtering_option_to_parser(parser, _('floating IP')) + _tag.add_tag_filtering_option_to_parser( + parser, _('floating IP'), enhance_help=self.enhance_help_neutron) return parser diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 09f3556c43..e7031266ec 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -219,27 +219,27 @@ def update_parser_network(self, parser): '--enable', action='store_true', default=True, - help=_("Enable network (default)") + help=self.enhance_help_neutron(_("Enable network (default)")) ) admin_group.add_argument( '--disable', action='store_true', - help=_("Disable network") + help=self.enhance_help_neutron(_("Disable network")) ) parser.add_argument( '--project', metavar='', - help=_("Owner's project (name or ID)") + help=self.enhance_help_neutron(_("Owner's project (name or ID)")) ) parser.add_argument( '--description', metavar='', - help=_("Set network description") + help=self.enhance_help_neutron(_("Set network description")) ) parser.add_argument( '--mtu', metavar='', - help=_("Set network mtu") + help=self.enhance_help_neutron(_("Set network mtu")) ) identity_common.add_project_domain_option_to_parser(parser) parser.add_argument( @@ -247,65 +247,76 @@ def update_parser_network(self, parser): action='append', dest='availability_zone_hints', metavar='', - help=_("Availability Zone in which to create this network " - "(Network Availability Zone extension required, " - "repeat option to set multiple availability zones)") + help=self.enhance_help_neutron( + _("Availability Zone in which to create this network " + "(Network Availability Zone extension required, " + "repeat option to set multiple availability zones)")) ) port_security_group = parser.add_mutually_exclusive_group() port_security_group.add_argument( '--enable-port-security', action='store_true', - help=_("Enable port security by default for ports created on " - "this network (default)") + help=self.enhance_help_neutron( + _("Enable port security by default for ports created on " + "this network (default)")) ) port_security_group.add_argument( '--disable-port-security', action='store_true', - help=_("Disable port security by default for ports created on " - "this network") + help=self.enhance_help_neutron( + _("Disable port security by default for ports created on " + "this network")) ) external_router_grp = parser.add_mutually_exclusive_group() external_router_grp.add_argument( '--external', action='store_true', - help=_("Set this network as an external network " - "(external-net extension required)") + help=self.enhance_help_neutron( + _("Set this network as an external network " + "(external-net extension required)")) ) external_router_grp.add_argument( '--internal', action='store_true', - help=_("Set this network as an internal network (default)") + help=self.enhance_help_neutron( + _("Set this network as an internal network (default)")) ) default_router_grp = parser.add_mutually_exclusive_group() default_router_grp.add_argument( '--default', action='store_true', - help=_("Specify if this network should be used as " - "the default external network") + help=self.enhance_help_neutron( + _("Specify if this network should be used as the default " + "external network")) ) default_router_grp.add_argument( '--no-default', action='store_true', - help=_("Do not use the network as the default external network " - "(default)") + help=self.enhance_help_neutron( + _("Do not use the network as the default external network " + "(default)")) ) parser.add_argument( '--qos-policy', metavar='', - help=_("QoS policy to attach to this network (name or ID)") + help=self.enhance_help_neutron( + _("QoS policy to attach to this network (name or ID)")) ) vlan_transparent_grp = parser.add_mutually_exclusive_group() vlan_transparent_grp.add_argument( '--transparent-vlan', action='store_true', - help=_("Make the network VLAN transparent")) + help=self.enhance_help_neutron( + _("Make the network VLAN transparent"))) vlan_transparent_grp.add_argument( '--no-transparent-vlan', action='store_true', - help=_("Do not make the network VLAN transparent")) + help=self.enhance_help_neutron( + _("Do not make the network VLAN transparent"))) _add_additional_network_options(parser) - _tag.add_tag_option_to_parser_for_create(parser, _('network')) + _tag.add_tag_option_to_parser_for_create( + parser, _('network'), enhance_help=self.enhance_help_neutron) return parser def update_parser_compute(self, parser): @@ -313,7 +324,8 @@ def update_parser_compute(self, parser): '--subnet', metavar='', required=True, - help=_("IPv4 subnet for fixed IPs (in CIDR notation)") + help=self.enhance_help_nova_network( + _("IPv4 subnet for fixed IPs (in CIDR notation)")) ) return parser @@ -376,87 +388,98 @@ def update_parser_network(self, parser): router_ext_group.add_argument( '--external', action='store_true', - help=_("List external networks") + help=self.enhance_help_neutron(_("List external networks")) ) router_ext_group.add_argument( '--internal', action='store_true', - help=_("List internal networks") + help=self.enhance_help_neutron(_("List internal networks")) ) parser.add_argument( '--long', action='store_true', - help=_("List additional fields in output") + help=self.enhance_help_neutron( + _("List additional fields in output")) ) parser.add_argument( '--name', metavar='', - help=_("List networks according to their name") + help=self.enhance_help_neutron( + _("List networks according to their name")) ) admin_state_group = parser.add_mutually_exclusive_group() admin_state_group.add_argument( '--enable', action='store_true', - help=_("List enabled networks") + help=self.enhance_help_neutron(_("List enabled networks")) ) admin_state_group.add_argument( '--disable', action='store_true', - help=_("List disabled networks") + help=self.enhance_help_neutron(_("List disabled networks")) ) parser.add_argument( '--project', metavar='', help=_("List networks according to their project (name or ID)") ) - identity_common.add_project_domain_option_to_parser(parser) + identity_common.add_project_domain_option_to_parser( + parser, enhance_help=self.enhance_help_neutron) shared_group = parser.add_mutually_exclusive_group() shared_group.add_argument( '--share', action='store_true', - help=_("List networks shared between projects") + help=self.enhance_help_neutron( + _("List networks shared between projects")) ) shared_group.add_argument( '--no-share', action='store_true', - help=_("List networks not shared between projects") + help=self.enhance_help_neutron( + _("List networks not shared between projects")) ) parser.add_argument( '--status', metavar='', choices=['ACTIVE', 'BUILD', 'DOWN', 'ERROR'], - help=_("List networks according to their status " - "('ACTIVE', 'BUILD', 'DOWN', 'ERROR')") + help=self.enhance_help_neutron( + _("List networks according to their status " + "('ACTIVE', 'BUILD', 'DOWN', 'ERROR')")) ) parser.add_argument( '--provider-network-type', metavar='', choices=['flat', 'geneve', 'gre', 'local', 'vlan', 'vxlan'], - help=_("List networks according to their physical mechanisms. " - "The supported options are: flat, geneve, gre, local, " - "vlan, vxlan.") + help=self.enhance_help_neutron( + _("List networks according to their physical mechanisms. The " + "supported options are: flat, geneve, gre, local, vlan, " + "vxlan.")) ) parser.add_argument( '--provider-physical-network', metavar='', dest='physical_network', - help=_("List networks according to name of the physical network") + help=self.enhance_help_neutron( + _("List networks according to name of the physical network")) ) parser.add_argument( '--provider-segment', metavar='', dest='segmentation_id', - help=_("List networks according to VLAN ID for VLAN networks " - "or Tunnel ID for GENEVE/GRE/VXLAN networks") + help=self.enhance_help_neutron( + _("List networks according to VLAN ID for VLAN networks or " + "Tunnel ID for GENEVE/GRE/VXLAN networks")) ) parser.add_argument( '--agent', metavar='', dest='agent_id', - help=_('List networks hosted by agent (ID only)') + help=self.enhance_help_neutron( + _('List networks hosted by agent (ID only)')) ) - _tag.add_tag_filtering_option_to_parser(parser, _('networks')) + _tag.add_tag_filtering_option_to_parser( + parser, _('networks'), enhance_help=self.enhance_help_neutron) return parser def take_action_network(self, client, parsed_args): diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py index 38b4e97a80..9f0ca0a1f4 100644 --- a/openstackclient/network/v2/security_group.py +++ b/openstackclient/network/v2/security_group.py @@ -119,10 +119,13 @@ def update_parser_network(self, parser): parser.add_argument( '--project', metavar='', - help=_("Owner's project (name or ID)") + help=self.enhance_help_neutron(_("Owner's project (name or ID)")) ) - identity_common.add_project_domain_option_to_parser(parser) - _tag.add_tag_option_to_parser_for_create(parser, _('security group')) + identity_common.add_project_domain_option_to_parser( + parser, enhance_help=self.enhance_help_neutron) + _tag.add_tag_option_to_parser_for_create( + parser, _('security group'), + enhance_help=self.enhance_help_neutron) return parser def _get_description(self, parsed_args): @@ -202,22 +205,28 @@ class ListSecurityGroup(common.NetworkAndComputeLister): _description = _("List security groups") def update_parser_network(self, parser): - # Maintain and hide the argument for backwards compatibility. - # Network will always return all projects for an admin. - parser.add_argument( - '--all-projects', - action='store_true', - default=False, - help=argparse.SUPPRESS, - ) + if not self.is_docs_build: + # Maintain and hide the argument for backwards compatibility. + # Network will always return all projects for an admin. + parser.add_argument( + '--all-projects', + action='store_true', + default=False, + help=argparse.SUPPRESS, + ) + parser.add_argument( '--project', metavar='', - help=_("List security groups according to the project " - "(name or ID)") + help=self.enhance_help_neutron( + _("List security groups according to the project (name or " + "ID)")) ) - identity_common.add_project_domain_option_to_parser(parser) - _tag.add_tag_filtering_option_to_parser(parser, _('security group')) + identity_common.add_project_domain_option_to_parser( + parser, enhance_help=self.enhance_help_neutron) + _tag.add_tag_filtering_option_to_parser( + parser, _('security group'), + enhance_help=self.enhance_help_neutron) return parser def update_parser_compute(self, parser): @@ -225,7 +234,8 @@ def update_parser_compute(self, parser): '--all-projects', action='store_true', default=False, - help=_("Display information from all projects (admin only)") + help=self.enhance_help_nova_network( + _("Display information from all projects (admin only)")) ) return parser @@ -307,7 +317,9 @@ def update_parser_common(self, parser): return parser def update_parser_network(self, parser): - _tag.add_tag_option_to_parser_for_set(parser, _('security group')) + _tag.add_tag_option_to_parser_for_set( + parser, _('security group'), + enhance_help=self.enhance_help_neutron) return parser def take_action_network(self, client, parsed_args): diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index 15f099b1eb..a38587fa52 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -133,109 +133,120 @@ def update_parser_common(self, parser): metavar="", help=_("Remote security group (name or ID)"), ) - return parser - def update_parser_network(self, parser): - parser.add_argument( - '--description', - metavar='', - help=_("Set security group rule description") - ) + # NOTE(efried): The --dst-port, --protocol, and --proto options exist + # for both nova-network and neutron, but differ slightly. For the sake + # of the docs build, which has to account for both variants, but only + # add each to the parser once, they are handled here rather than in the + # _network- or _compute-specific methods below. + + # --dst-port has a default for nova-net only + if self.is_nova_network: + dst_port_default = dict(default=(0, 0)) + else: + dst_port_default = {} parser.add_argument( '--dst-port', metavar='', action=parseractions.RangeAction, help=_("Destination port, may be a single port or a starting and " "ending port range: 137:139. Required for IP protocols TCP " - "and UDP. Ignored for ICMP IP protocols.") - ) - parser.add_argument( - '--icmp-type', - metavar='', - type=int, - help=_("ICMP type for ICMP IP protocols") - ) - parser.add_argument( - '--icmp-code', - metavar='', - type=int, - help=_("ICMP code for ICMP IP protocols") + "and UDP. Ignored for ICMP IP protocols."), + **dst_port_default ) + # NOTE(rtheis): Support either protocol option name for now. # However, consider deprecating and then removing --proto in # a future release. protocol_group = parser.add_mutually_exclusive_group() + # --proto[col] has choices for nova-network only + if self.is_nova_network: + proto_choices = dict(choices=['icmp', 'tcp', 'udp']) + else: + proto_choices = {} + protocol_help_compute = _("IP protocol (icmp, tcp, udp; default: tcp)") + protocol_help_network = _( + "IP protocol (ah, dccp, egp, esp, gre, icmp, igmp, ipv6-encap, " + "ipv6-frag, ipv6-icmp, ipv6-nonxt, ipv6-opts, ipv6-route, ospf, " + "pgm, rsvp, sctp, tcp, udp, udplite, vrrp and integer " + "representations [0-255] or any; default: any (all protocols))") + if self.is_nova_network: + protocol_help = protocol_help_compute + elif self.is_neutron: + protocol_help = protocol_help_network + else: + # Docs build: compose help for both nova-network and neutron + protocol_help = self.split_help( + protocol_help_network, protocol_help_compute) + protocol_group.add_argument( '--protocol', metavar='', type=_convert_to_lowercase, - help=_("IP protocol (ah, dccp, egp, esp, gre, icmp, igmp, " - "ipv6-encap, ipv6-frag, ipv6-icmp, ipv6-nonxt, " - "ipv6-opts, ipv6-route, ospf, pgm, rsvp, sctp, tcp, " - "udp, udplite, vrrp and integer representations [0-255] " - "or any; default: any (all protocols))") + help=protocol_help, + **proto_choices ) - protocol_group.add_argument( - '--proto', - metavar='', - type=_convert_to_lowercase, - help=argparse.SUPPRESS + if not self.is_docs_build: + protocol_group.add_argument( + '--proto', + metavar='', + type=_convert_to_lowercase, + help=argparse.SUPPRESS, + **proto_choices + ) + + return parser + + def update_parser_network(self, parser): + parser.add_argument( + '--description', + metavar='', + help=self.enhance_help_neutron( + _("Set security group rule description")) + ) + parser.add_argument( + '--icmp-type', + metavar='', + type=int, + help=self.enhance_help_neutron( + _("ICMP type for ICMP IP protocols")) + ) + parser.add_argument( + '--icmp-code', + metavar='', + type=int, + help=self.enhance_help_neutron( + _("ICMP code for ICMP IP protocols")) ) direction_group = parser.add_mutually_exclusive_group() direction_group.add_argument( '--ingress', action='store_true', - help=_("Rule applies to incoming network traffic (default)") + help=self.enhance_help_neutron( + _("Rule applies to incoming network traffic (default)")) ) direction_group.add_argument( '--egress', action='store_true', - help=_("Rule applies to outgoing network traffic") + help=self.enhance_help_neutron( + _("Rule applies to outgoing network traffic")) ) parser.add_argument( '--ethertype', metavar='', choices=['IPv4', 'IPv6'], type=_convert_ipvx_case, - help=_("Ethertype of network traffic " - "(IPv4, IPv6; default: based on IP protocol)") + help=self.enhance_help_neutron( + _("Ethertype of network traffic " + "(IPv4, IPv6; default: based on IP protocol)")) ) parser.add_argument( '--project', metavar='', - help=_("Owner's project (name or ID)") - ) - identity_common.add_project_domain_option_to_parser(parser) - return parser - - def update_parser_compute(self, parser): - parser.add_argument( - '--dst-port', - metavar='', - default=(0, 0), - action=parseractions.RangeAction, - help=_("Destination port, may be a single port or a starting and " - "ending port range: 137:139. Required for IP protocols TCP " - "and UDP. Ignored for ICMP IP protocols.") - ) - # NOTE(rtheis): Support either protocol option name for now. - # However, consider deprecating and then removing --proto in - # a future release. - protocol_group = parser.add_mutually_exclusive_group() - protocol_group.add_argument( - '--protocol', - metavar='', - choices=['icmp', 'tcp', 'udp'], - type=_convert_to_lowercase, - help=_("IP protocol (icmp, tcp, udp; default: tcp)") - ) - protocol_group.add_argument( - '--proto', - metavar='', - choices=['icmp', 'tcp', 'udp'], - type=_convert_to_lowercase, - help=argparse.SUPPRESS + help=self.enhance_help_neutron(_("Owner's project (name or ID)")) ) + identity_common.add_project_domain_option_to_parser( + parser, enhance_help=self.enhance_help_neutron) return parser def _get_protocol(self, parsed_args, default_protocol='any'): @@ -424,47 +435,53 @@ def update_parser_common(self, parser): return parser def update_parser_network(self, parser): - # Accept but hide the argument for consistency with compute. - # Network will always return all projects for an admin. - parser.add_argument( - '--all-projects', - action='store_true', - default=False, - help=argparse.SUPPRESS - ) + if not self.is_docs_build: + # Accept but hide the argument for consistency with compute. + # Network will always return all projects for an admin. + parser.add_argument( + '--all-projects', + action='store_true', + default=False, + help=argparse.SUPPRESS + ) + parser.add_argument( '--protocol', metavar='', type=_convert_to_lowercase, - help=_("List rules by the IP protocol (" - "ah, dhcp, egp, esp, gre, icmp, igmp, " - "ipv6-encap, ipv6-frag, ipv6-icmp, ipv6-nonxt, " - "ipv6-opts, ipv6-route, ospf, pgm, rsvp, sctp, tcp, " - "udp, udplite, vrrp and integer representations [0-255] " - "or any; default: any (all protocols))") + help=self.enhance_help_neutron( + _("List rules by the IP protocol (ah, dhcp, egp, esp, gre, " + "icmp, igmp, ipv6-encap, ipv6-frag, ipv6-icmp, ipv6-nonxt, " + "ipv6-opts, ipv6-route, ospf, pgm, rsvp, sctp, tcp, udp, " + "udplite, vrrp and integer representations [0-255] or any; " + "default: any (all protocols))")) ) parser.add_argument( '--ethertype', metavar='', type=_convert_to_lowercase, - help=_("List rules by the Ethertype (IPv4 or IPv6)") + help=self.enhance_help_neutron( + _("List rules by the Ethertype (IPv4 or IPv6)")) ) direction_group = parser.add_mutually_exclusive_group() direction_group.add_argument( '--ingress', action='store_true', - help=_("List rules applied to incoming network traffic") + help=self.enhance_help_neutron( + _("List rules applied to incoming network traffic")) ) direction_group.add_argument( '--egress', action='store_true', - help=_("List rules applied to outgoing network traffic") + help=self.enhance_help_neutron( + _("List rules applied to outgoing network traffic")) ) parser.add_argument( '--long', action='store_true', default=False, - help=_("List additional fields in output") + help=self.enhance_help_neutron( + _("List additional fields in output")) ) return parser @@ -473,16 +490,18 @@ def update_parser_compute(self, parser): '--all-projects', action='store_true', default=False, - help=_("Display information from all projects (admin only)") - ) - # Accept but hide the argument for consistency with network. - # There are no additional fields to display at this time. - parser.add_argument( - '--long', - action='store_false', - default=False, - help=argparse.SUPPRESS + help=self.enhance_help_nova_network( + _("Display information from all projects (admin only)")) ) + if not self.is_docs_build: + # Accept but hide the argument for consistency with network. + # There are no additional fields to display at this time. + parser.add_argument( + '--long', + action='store_false', + default=False, + help=argparse.SUPPRESS + ) return parser def _get_column_headers(self, parsed_args): From c7dbe857055a04402f12dfaa3b261b0ecbf86d7c Mon Sep 17 00:00:00 2001 From: Eric Fried Date: Fri, 1 Nov 2019 14:08:56 -0500 Subject: [PATCH 2132/3095] Update a stale doc reference to use :neutron-doc: The help page for network auto allocated topology had a link to a newton-era networking guide document that has been superseded and is now maintained in the neutron repository. This commit adds 'neutron' to the openstackdocstheme configuration so that the :neutron-doc: role works, and updates the link to point to the modern version therein. Change-Id: I5bcb40e265b22f15ff2f5ca4936160e231bb4075 --- .../cli/command-objects/network-auto-allocated-topology.rst | 4 ++-- doc/source/conf.py | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/doc/source/cli/command-objects/network-auto-allocated-topology.rst b/doc/source/cli/command-objects/network-auto-allocated-topology.rst index 00e4c6b9bc..436836484a 100644 --- a/doc/source/cli/command-objects/network-auto-allocated-topology.rst +++ b/doc/source/cli/command-objects/network-auto-allocated-topology.rst @@ -5,8 +5,8 @@ network auto allocated topology An **auto allocated topology** allows admins to quickly set up external connectivity for end-users. Only one auto allocated topology is allowed per project. For more information on how to set up the resources required -for auto allocated topology review the documentation at: -http://docs.openstack.org/newton/networking-guide/config-auto-allocation.html +for auto allocated topology review :neutron-doc:`the documentation +`. Network v2 diff --git a/doc/source/conf.py b/doc/source/conf.py index d3dfccd235..ff37783fb5 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -35,7 +35,9 @@ use_storyboard = True # Add project 'foo' to this list to enable the :foo-doc: role -# openstack_projects = [] +openstack_projects = [ + 'neutron', +] # Add any paths that contain templates here, relative to this directory. #templates_path = ['_templates'] From 4c0f3bfa89dfc9f207c4f6d6dc6ded85b86fee87 Mon Sep 17 00:00:00 2001 From: Eric Fried Date: Tue, 29 Oct 2019 16:22:18 -0500 Subject: [PATCH 2133/3095] common: autogenerate docs $namespace = openstack.common The subcommand documents for $namespace were hardcoded and thus prone to drift over time. This commit removes the hardcoded content and uses the autoprogram-cliff directive to generate them automatically from the subcommand configuration classes. This incorporates a correction to `openstack versions show`: The command `openstack versions show --help` showed a copy/paste error, using for the metavar for both --service and --status. Fix. Change-Id: I7658fed40d71f4c20ee27908ade433534657cfe5 Co-Authored-By: Pierre Prinetti Co-Authored-By: Matt Riedemann --- .../cli/command-objects/availability-zone.rst | 31 +- .../cli/command-objects/configuration.rst | 23 +- doc/source/cli/command-objects/extension.rst | 52 +--- doc/source/cli/command-objects/limits.rst | 34 +-- .../cli/command-objects/project-purge.rst | 35 +-- doc/source/cli/command-objects/quota.rst | 271 +----------------- doc/source/cli/command-objects/versions.rst | 47 +-- openstackclient/common/quota.py | 42 ++- openstackclient/common/versions.py | 15 +- 9 files changed, 56 insertions(+), 494 deletions(-) diff --git a/doc/source/cli/command-objects/availability-zone.rst b/doc/source/cli/command-objects/availability-zone.rst index d4c117a0f8..bdc64f1537 100644 --- a/doc/source/cli/command-objects/availability-zone.rst +++ b/doc/source/cli/command-objects/availability-zone.rst @@ -7,32 +7,5 @@ compute and network services. Block Storage v2, Compute v2, Network v2 -availability zone list ----------------------- - -List availability zones and their status - -.. program availability zone list -.. code:: bash - - openstack availability zone list - [--compute] - [--network] - [--volume] - [--long] - -.. option:: --compute - - List compute availability zones - -.. option:: --network - - List network availability zones - -.. option:: --volume - - List volume availability zones - -.. option:: --long - - List additional fields in output +.. autoprogram-cliff:: openstack.common + :command: availability zone list diff --git a/doc/source/cli/command-objects/configuration.rst b/doc/source/cli/command-objects/configuration.rst index 6e704d2d25..22fd13e43d 100644 --- a/doc/source/cli/command-objects/configuration.rst +++ b/doc/source/cli/command-objects/configuration.rst @@ -6,24 +6,5 @@ Available for all services .. _configuration-show: -configuration show ------------------- - -Show the current openstack client configuration. This command is a little -different from other show commands because it does not take a resource name -or id to show. The command line options, such as --os-cloud, can be used to -show different configurations. - -.. program:: configuration show -.. code:: bash - - openstack configuration show - [--mask | --unmask] - -.. option:: --mask - - Attempt to mask passwords (default) - -.. option:: --unmask - - Show password in clear text +.. autoprogram-cliff:: openstack.common + :command: configuration show diff --git a/doc/source/cli/command-objects/extension.rst b/doc/source/cli/command-objects/extension.rst index 36cf418bb8..1002a5fff0 100644 --- a/doc/source/cli/command-objects/extension.rst +++ b/doc/source/cli/command-objects/extension.rst @@ -5,54 +5,6 @@ extension Many OpenStack server APIs include API extensions that enable additional functionality. -extension list --------------- -List API extensions - -.. program:: extension list -.. code:: bash - - openstack extension list - [--compute] - [--identity] - [--network] - [--volume] - [--long] - -.. option:: --compute - - List extensions for the Compute API - -.. option:: --identity - - List extensions for the Identity API - -.. option:: --network - - List extensions for the Network API - -.. option:: --volume - - List extensions for the Block Storage API - -.. option:: --long - - List additional fields in output - -extension show --------------- - -Show API extension - -.. program:: extension show -.. code:: bash - - openstack extension show - - -.. _extension_show: -.. describe:: - - Extension to display. Currently, only network extensions are supported. - (Name or Alias) +.. autoprogram-cliff:: openstack.common + :command: extension * diff --git a/doc/source/cli/command-objects/limits.rst b/doc/source/cli/command-objects/limits.rst index 9261420951..3a0f99b376 100644 --- a/doc/source/cli/command-objects/limits.rst +++ b/doc/source/cli/command-objects/limits.rst @@ -6,36 +6,6 @@ The Compute and Block Storage APIs have resource usage limits. Compute v2, Block Storage v1 -limits show ------------ -Show compute and block storage limits - -.. program:: limits show -.. code:: bash - - openstack limits show - --absolute | --rate - [--reserved] - [--project ] - [--domain ] - -.. option:: --absolute - - Show absolute limits - -.. option:: --rate - - Show rate limits - -.. option:: --reserved - - Include reservations count [only valid with :option:`--absolute`] - -.. option:: --project - - Show limits for a specific project (name or ID) [only valid with :option:`--absolute`] - -.. option:: --domain - - Domain the project belongs to (name or ID) [only valid with :option:`--absolute`] +.. autoprogram-cliff:: openstack.common + :command: limits * diff --git a/doc/source/cli/command-objects/project-purge.rst b/doc/source/cli/command-objects/project-purge.rst index 0ad0bbf969..8f10a77452 100644 --- a/doc/source/cli/command-objects/project-purge.rst +++ b/doc/source/cli/command-objects/project-purge.rst @@ -6,37 +6,6 @@ Clean resources associated with a specific project. Block Storage v1, v2; Compute v2; Image v1, v2 -project purge -------------- - -Clean resources associated with a project - -.. program:: project purge -.. code:: bash - - openstack project purge - [--dry-run] - [--keep-project] - [--auth-project | --project ] - [--project-domain ] - -.. option:: --dry-run - - List a project's resources - -.. option:: --keep-project - - Clean project resources, but don't delete the project. - -.. option:: --auth-project - - Delete resources of the project used to authenticate - -.. option:: --project - - Project to clean (name or ID) - -.. option:: --project-domain - Domain the project belongs to (name or ID). This can be - used in case collisions between project names exist. +.. autoprogram-cliff:: openstack.common + :command: project purge diff --git a/doc/source/cli/command-objects/quota.rst b/doc/source/cli/command-objects/quota.rst index 25d0188a3d..cab1265240 100644 --- a/doc/source/cli/command-objects/quota.rst +++ b/doc/source/cli/command-objects/quota.rst @@ -7,272 +7,5 @@ single object with multiple properties. Block Storage v1, v2, Compute v2, Network v2 -quota list ----------- - -List quotas for all projects with non-default quota values - -.. program:: quota list -.. code:: bash - - openstack quota list - --compute | --network | --volume - [--project ] - [--detail] - -.. option:: --network - - List network quotas - -.. option:: --compute - - List compute quotas - -.. option:: --volume - - List volume quotas - -.. option:: --project - - List quotas for this project (name or ID) - -.. option:: --detail - - Show details about quotas usage - -quota set ---------- - -Set quotas for project - -.. program:: quota set -.. code:: bash - - openstack quota set - # Compute settings - [--cores ] - [--fixed-ips ] - [--floating-ips ] - [--injected-file-size ] - [--injected-files ] - [--instances ] - [--key-pairs ] - [--properties ] - [--ram ] - [--server-groups ] - [--server-group-members ] - - # Block Storage settings - [--backups ] - [--backup-gigabytes ] - [--gigabytes ] - [--per-volume-gigabytes ] - [--snapshots ] - [--volumes ] - [--volume-type ] - - # Network settings - [--floating-ips ] - [--secgroup-rules ] - [--secgroups ] - [--networks ] - [--subnets ] - [--ports ] - [--routers ] - [--rbac-policies ] - [--subnetpools ] - - - -Set quotas for class - -.. code:: bash - - openstack quota set - --class - # Compute settings - [--cores ] - [--fixed-ips ] - [--floating-ips ] - [--injected-file-size ] - [--injected-files ] - [--instances ] - [--key-pairs ] - [--properties ] - [--ram ] - [--server-groups ] - [--server-group-members ] - - # Block Storage settings - [--backups ] - [--backup-gigabytes ] - [--gigabytes ] - [--per-volume-gigabytes ] - [--snapshots ] - [--volumes ] - - - -.. option:: --class - - Set quotas for ```` - -.. option:: --properties - - New value for the properties quota - -.. option:: --ram - - New value for the ram quota - -.. option:: --secgroup-rules - - New value for the secgroup-rules quota - -.. option:: --instances - - New value for the instances quota - -.. option:: --key-pairs - - New value for the key-pairs quota - -.. option:: --fixed-ips - - New value for the fixed-ips quota - -.. option:: --secgroups - - New value for the secgroups quota - -.. option:: --injected-file-size - - New value for the injected-file-size quota - -.. option:: --server-groups - - New value for the server-groups quota - -.. option:: --server-group-members - - New value for the server-group-members quota - -.. option:: --floating-ips - - New value for the floating-ips quota - -.. option:: --injected-files - - New value for the injected-files quota - -.. option:: --cores - - New value for the cores quota - -.. option:: --injected-path-size - - New value for the injected-path-size quota - -.. option:: --backups - - New value for the backups quota - -.. option:: --backup-gigabytes - - New value for the backup gigabytes quota - -.. option:: --gigabytes - - New value for the gigabytes quota - -.. option:: --per-volume-gigabytes - - New value for the gigabytes quota of per volume - -.. option:: --volumes - - New value for the volumes quota - -.. option:: --snapshots - - New value for the snapshots quota - -.. option:: --volume-type - - Set quotas for a specific . The supported quotas are: - gigabytes, snapshots, volumes. - -.. option:: --networks - - New value for the networks quota - -.. option:: --subnets - - New value for the subnets quota - -.. option:: --ports - - New value for the ports quota - -.. option:: --routers - - New value for the routers quota - -.. option:: --rbac-policies - - New value for the rbac-policies quota - -.. option:: --vips - - New value for the vips quota - -.. option:: --subnetpools - - New value for the subnetpools quota - -.. option:: --members - - New value for the members quota - -.. option:: --health-monitors - - New value for the health-monitors quota - -quota show ----------- - -Show quotas for project or class. Specify ``--os-compute-api-version 2.50`` or -higher to see ``server-groups`` and ``server-group-members`` output for a given -quota class. - -.. program:: quota show -.. code:: bash - - openstack quota show - [--default] - [] - - -.. option:: --default - - Show default quotas for :ref:`\ ` - -.. _quota_show-project: -.. describe:: - - Show quotas for this project (name or ID) - -.. code:: bash - - openstack quota show - --class - [] - -.. option:: --class - - Show quotas for :ref:`\ ` - -.. _quota_show-class: -.. describe:: - - Show quotas for this class (name or ID) +.. autoprogram-cliff:: openstack.common + :command: quota * diff --git a/doc/source/cli/command-objects/versions.rst b/doc/source/cli/command-objects/versions.rst index 0aa9a1b785..ebebec192a 100644 --- a/doc/source/cli/command-objects/versions.rst +++ b/doc/source/cli/command-objects/versions.rst @@ -4,48 +4,5 @@ versions Get a list of every version of every service in a given cloud. -versions show -------------- - -Show service versions: - -.. program:: versions show -.. code:: bash - - openstack versions show - [--all-interfaces] - [--interface ] - [--region-name ] - [--service ] - [--status ] - -.. option:: --all-interfaces - - Return results for every interface of every service. - [Mutually exclusive with --interface] - -.. option:: --interface - - Limit results to only those on given interface. - [Default 'public'. Mutually exclusive with --all-interfaces] - -.. option:: --region-name - - Limit results to only those from a given region. - -.. option:: --service - - Limit results to only those for service. The argument should be either - an exact match to what is in the catalog or a known official value or - alias from `service-types-authority`_. - -.. option:: --status - - Limit results to only those in the given state. Valid values are: - - - SUPPORTED - - CURRENT - - DEPRECATED - - EXPERIMENTAL - -.. _service-types-authority: https://service-types.openstack.org/ +.. autoprogram-cliff:: openstack.common + :command: versions show diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index fb4e86031b..80c8749ac1 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -24,6 +24,7 @@ import six from openstackclient.i18n import _ +from openstackclient.network import common LOG = logging.getLogger(__name__) @@ -457,18 +458,37 @@ def take_action(self, parsed_args): return ((), ()) -class SetQuota(command.Command): +class SetQuota(common.NetDetectionMixin, command.Command): _description = _("Set quotas for project or class") def _build_options_list(self): - if self.app.client_manager.is_network_endpoint_enabled(): - return itertools.chain(COMPUTE_QUOTAS.items(), - VOLUME_QUOTAS.items(), - NETWORK_QUOTAS.items()) - else: - return itertools.chain(COMPUTE_QUOTAS.items(), - VOLUME_QUOTAS.items(), - NOVA_NETWORK_QUOTAS.items()) + help_fmt = _('New value for the %s quota') + # Compute and volume quota options are always the same + rets = [(k, v, help_fmt % v) for k, v in itertools.chain( + COMPUTE_QUOTAS.items(), + VOLUME_QUOTAS.items(), + )] + # For docs build, we want to produce helps for both neutron and + # nova-network options. They overlap, so we have to figure out which + # need to be tagged as specific to one network type or the other. + if self.is_docs_build: + # NOTE(efried): This takes advantage of the fact that we know the + # nova-net options are a subset of the neutron options. If that + # ever changes, this algorithm will need to be adjusted accordingly + inv_compute = set(NOVA_NETWORK_QUOTAS.values()) + for k, v in NETWORK_QUOTAS.items(): + _help = help_fmt % v + if v not in inv_compute: + # This one is unique to neutron + _help = self.enhance_help_neutron(_help) + rets.append((k, v, _help)) + elif self.is_neutron: + rets.extend( + [(k, v, help_fmt % v) for k, v in NETWORK_QUOTAS.items()]) + elif self.is_nova_network: + rets.extend( + [(k, v, help_fmt % v) for k, v in NOVA_NETWORK_QUOTAS.items()]) + return rets def get_parser(self, prog_name): parser = super(SetQuota, self).get_parser(prog_name) @@ -484,13 +504,13 @@ def get_parser(self, prog_name): default=False, help=_('Set quotas for '), ) - for k, v in self._build_options_list(): + for k, v, h in self._build_options_list(): parser.add_argument( '--%s' % v, metavar='<%s>' % v, dest=k, type=int, - help=_('New value for the %s quota') % v, + help=h, ) parser.add_argument( '--volume-type', diff --git a/openstackclient/common/versions.py b/openstackclient/common/versions.py index e6781bc66f..3acd9f73d7 100644 --- a/openstackclient/common/versions.py +++ b/openstackclient/common/versions.py @@ -46,14 +46,21 @@ def get_parser(self, prog_name): parser.add_argument( '--service', metavar='', - help=_('Show versions for a specific service.'), + help=_('Show versions for a specific service. The argument should ' + 'be either an exact match to what is in the catalog or a ' + 'known official value or alias from ' + 'service-types-authority ' + '(https://service-types.openstack.org/)'), ) parser.add_argument( '--status', metavar='', - help=_('Show versions for a specific status.' - ' [Valid values are SUPPORTED, CURRENT,' - ' DEPRECATED, EXPERIMENTAL]'), + help=_("""Show versions for a specific status. Valid values are: + +- SUPPORTED +- CURRENT +- DEPRECATED +- EXPERIMENTAL""") ) return parser From da56b8f4cf15cc5a77249b680dd0c4d05137be9a Mon Sep 17 00:00:00 2001 From: Eric Fried Date: Mon, 4 Nov 2019 15:48:27 -0600 Subject: [PATCH 2134/3095] openstack.cli: autogenerate docs $namespace = openstack.cli The subcommand documents for $namespace were hardcoded and thus prone to drift over time. This commit removes the hardcoded content and uses the autoprogram-cliff directive to generate them automatically from the subcommand configuration classes. Change-Id: I1f7e9d0e5748f887dbc35200c3c8b4407da43e0b --- doc/source/cli/command-objects/command.rst | 17 ++--------------- doc/source/cli/command-objects/module.rst | 16 ++-------------- 2 files changed, 4 insertions(+), 29 deletions(-) diff --git a/doc/source/cli/command-objects/command.rst b/doc/source/cli/command-objects/command.rst index 918fd959ad..5afc4940ab 100644 --- a/doc/source/cli/command-objects/command.rst +++ b/doc/source/cli/command-objects/command.rst @@ -6,18 +6,5 @@ Internal Installed commands in the OSC process. -command list ------------- - -List recognized commands by group - -.. program:: command list -.. code:: bash - - openstack command list - [--group ] - -.. option:: --group - - Show commands filtered by a command group, for example: identity, volume, - compute, image, network and other keywords +.. autoprogram-cliff:: openstack.cli + :command: command * diff --git a/doc/source/cli/command-objects/module.rst b/doc/source/cli/command-objects/module.rst index f4b32e7589..82269f472d 100644 --- a/doc/source/cli/command-objects/module.rst +++ b/doc/source/cli/command-objects/module.rst @@ -6,17 +6,5 @@ Internal Installed Python modules in the OSC process. -module list ------------ - -List module versions - -.. program:: module list -.. code:: bash - - openstack module list - [--all] - -.. option:: --all - - Show all modules that have version information +.. autoprogram-cliff:: openstack.cli + :command: module * From c6266b5ab22b911ae7e0ca5077d0c04a23dabca9 Mon Sep 17 00:00:00 2001 From: Eric Fried Date: Mon, 4 Nov 2019 16:02:31 -0600 Subject: [PATCH 2135/3095] compute: autogenerate docs $namespace = openstack.compute.v2 The subcommand documents for $namespace were hardcoded and thus prone to drift over time. This commit removes the hardcoded content and uses the autoprogram-cliff directive to generate them automatically from the subcommand configuration classes. Many of these were already being generated. Some were missing a few sub-subcommands, so those are added. Change-Id: I1aa4b2655bafd2f6a5d83b658742f65d180eb128 --- doc/source/cli/command-objects/aggregate.rst | 179 +------------- .../cli/command-objects/compute-agent.rst | 98 +------- .../cli/command-objects/compute-service.rst | 93 +------ .../cli/command-objects/console-log.rst | 21 +- .../cli/command-objects/console-url.rst | 42 +--- doc/source/cli/command-objects/flavor.rst | 228 +----------------- doc/source/cli/command-objects/host.rst | 66 +---- .../cli/command-objects/hypervisor-stats.rst | 12 +- doc/source/cli/command-objects/hypervisor.rst | 39 +-- doc/source/cli/command-objects/keypair.rst | 71 +----- doc/source/cli/command-objects/server.rst | 4 +- doc/source/cli/command-objects/usage.rst | 49 +--- setup.cfg | 2 +- 13 files changed, 29 insertions(+), 875 deletions(-) diff --git a/doc/source/cli/command-objects/aggregate.rst b/doc/source/cli/command-objects/aggregate.rst index 2029a6c8cf..0f62ce8f43 100644 --- a/doc/source/cli/command-objects/aggregate.rst +++ b/doc/source/cli/command-objects/aggregate.rst @@ -7,180 +7,5 @@ criteria. Compute v2 -aggregate add host ------------------- - -Add host to aggregate - -.. program:: aggregate add host -.. code:: bash - - openstack aggregate add host - - - -.. _aggregate_add_host-aggregate: -.. describe:: - - Aggregate (name or ID) - -.. _aggregate_add_host-host: -.. describe:: - - Host to add to :ref:`\ ` - -aggregate create ----------------- - -Create a new aggregate - -.. program:: aggregate create -.. code:: bash - - openstack aggregate create - [--zone ] - [--property [...] ] - - -.. option:: --zone - - Availability zone name - -.. option:: --property - - Property to add to this aggregate (repeat option to set multiple properties) - -.. _aggregate_create-name: -.. describe:: - - New aggregate name - -aggregate delete ----------------- - -Delete existing aggregate(s) - -.. program:: aggregate delete -.. code:: bash - - openstack aggregate delete - [ ...] - -.. _aggregate_delete-aggregate: -.. describe:: - - Aggregate(s) to delete (name or ID) - -aggregate list --------------- - -List all aggregates - -.. program:: aggregate list -.. code:: bash - - openstack aggregate list - [--long] - -.. option:: --long - - List additional fields in output - -aggregate remove host ---------------------- - -Remove host from aggregate - -.. program:: aggregate remove host -.. code:: bash - - openstack aggregate remove host - - - -.. _aggregate_remove_host-aggregate: -.. describe:: - - Aggregate (name or ID) - -.. _aggregate_remove_host-host: -.. describe:: - - Host to remove from :ref:`\ ` - -aggregate set -------------- - -Set aggregate properties - -.. program:: aggregate set -.. code:: bash - - openstack aggregate set - [--name ] - [--zone ] - [--property [...] ] - [--no-property] - - -.. option:: --name - - Set aggregate name - -.. option:: --zone - - Set availability zone name - -.. option:: --property - - Property to set on :ref:`\ ` - (repeat option to set multiple properties) - -.. option:: --no-property - - Remove all properties from :ref:`\ ` - (specify both :option:`--property` and :option:`--no-property` to - overwrite the current properties) - -.. _aggregate_set-aggregate: -.. describe:: - - Aggregate to modify (name or ID) - -aggregate show --------------- - -Display aggregate details - -.. program:: aggregate show -.. code:: bash - - openstack aggregate show - - -.. _aggregate_show-aggregate: -.. describe:: - - Aggregate to display (name or ID) - -aggregate unset ---------------- - -Unset aggregate properties - -.. program:: aggregate unset -.. code-block:: bash - - openstack aggregate unset - [--property [...] ] - - -.. option:: --property - - Property to remove from :ref:`\ ` - (repeat option to remove multiple properties) - -.. _aggregate_unset-aggregate: -.. describe:: - - Aggregate to modify (name or ID) +.. autoprogram-cliff:: openstack.compute.v2 + :command: aggregate * diff --git a/doc/source/cli/command-objects/compute-agent.rst b/doc/source/cli/command-objects/compute-agent.rst index e8317b4825..89d7492879 100644 --- a/doc/source/cli/command-objects/compute-agent.rst +++ b/doc/source/cli/command-objects/compute-agent.rst @@ -4,99 +4,5 @@ compute agent Compute v2 -compute agent create --------------------- - -Create compute agent - -.. program:: compute agent create -.. code:: bash - - openstack compute agent create - - - -.. _compute_agent-create: -.. describe:: - - Type of OS - -.. describe:: - - Type of architecture - -.. describe:: - - Version - -.. describe:: - - URL - -.. describe:: - - MD5 hash - -.. describe:: - - Type of hypervisor - -compute agent delete --------------------- - -Delete compute agent(s) - -.. program:: compute agent delete -.. code:: bash - - openstack compute agent delete [ ...] - -.. _compute_agent-delete: -.. describe:: - - ID of agent(s) to delete - -compute agent list ------------------- - -List compute agents - -.. program:: compute agent list -.. code:: bash - - openstack compute agent list [--hypervisor ] - -.. option:: --hypervisor - - Type of hypervisor - -compute agent set ------------------ - -Set compute agent properties - -.. program:: agent set -.. code:: bash - - openstack compute agent set - [--agent-version ] - [--url ] - - -.. _compute_agent-set: -.. option:: --agent-version - - Version of the agent - -.. option:: --url - - URL of the agent - -.. option:: --md5hash - - MD5 hash of the agent - -.. describe:: - - Agent to modify (ID only) +.. autoprogram-cliff:: openstack.compute.v2 + :command: compute agent * diff --git a/doc/source/cli/command-objects/compute-service.rst b/doc/source/cli/command-objects/compute-service.rst index a76a9c26b9..ac54786e5b 100644 --- a/doc/source/cli/command-objects/compute-service.rst +++ b/doc/source/cli/command-objects/compute-service.rst @@ -4,94 +4,5 @@ compute service Compute v2 -compute service delete ----------------------- - -Delete compute service(s) - -.. program:: compute service delete -.. code:: bash - - openstack compute service delete - [ ...] - -.. _compute_service_delete-service: -.. describe:: - - Compute service(s) to delete (ID only). If using - ``--os-compute-api-version`` 2.53 or greater, the ID is a UUID which can - be retrieved by listing compute services using the same 2.53+ microversion. - -compute service list --------------------- - -List compute services - -Using ``--os-compute-api-version`` 2.53 or greater will return the ID as a -UUID value which can be used to uniquely identify the service in a multi-cell -deployment. - -.. program:: compute service list -.. code:: bash - - openstack compute service list - [--host ] - [--service ] - [--long] - -.. option:: --host - - List services on specified host (name only) - -.. option:: --service - - List only specified service binaries (name only). For example, - ``nova-compute``, ``nova-conductor``, etc. - -.. option:: --long - - List additional fields in output - -compute service set -------------------- - -Set compute service properties - -.. program:: compute service set -.. code:: bash - - openstack compute service set - [--enable | --disable] - [--disable-reason ] - [--up | --down] - - -.. option:: --enable - - Enable service - -.. option:: --disable - - Disable service - -.. option:: --disable-reason - - Reason for disabling the service (in quotes). Should be used with :option:`--disable` option. - -.. option:: --up - - Force up service. Requires ``--os-compute-api-version`` 2.11 or greater. - -.. option:: --down - - Force down service. . Requires ``--os-compute-api-version`` 2.11 or - greater. - -.. _compute_service_set-host: -.. describe:: - - Name of host - -.. describe:: - - Name of service (Binary name), for example ``nova-compute`` +.. autoprogram-cliff:: openstack.compute.v2 + :command: compute service * diff --git a/doc/source/cli/command-objects/console-log.rst b/doc/source/cli/command-objects/console-log.rst index bcb23e7009..46ef370d84 100644 --- a/doc/source/cli/command-objects/console-log.rst +++ b/doc/source/cli/command-objects/console-log.rst @@ -6,22 +6,5 @@ Server console text dump Compute v2 -console log show ----------------- - -Show server's console output - -.. program:: console log show -.. code:: bash - - openstack console log show - [--lines ] - - -.. option:: --lines - - Number of lines to display from the end of the log (default=all) - -.. describe:: - - Server to show log console log (name or ID) +.. autoprogram-cliff:: openstack.compute.v2 + :command: console log * diff --git a/doc/source/cli/command-objects/console-url.rst b/doc/source/cli/command-objects/console-url.rst index 8a5807b6a3..001ccc56c7 100644 --- a/doc/source/cli/command-objects/console-url.rst +++ b/doc/source/cli/command-objects/console-url.rst @@ -6,43 +6,5 @@ Server remote console URL Compute v2 -console url show ----------------- - -Show server's remote console URL - -.. program:: console url show -.. code:: bash - - openstack console url show - [--novnc | --xvpvnc | --spice] - [--rdp | --serial | --mks] - - -.. option:: --novnc - - Show noVNC console URL (default) - -.. option:: --xvpvnc - - Show xvpvnc console URL - -.. option:: --spice - - Show SPICE console URL - -.. option:: --rdp - - Show RDP console URL - -.. option:: --serial - - Show serial console URL - -.. option:: --mks - - Show WebMKS console URL - -.. describe:: - - Server to show URL (name or ID) +.. autoprogram-cliff:: openstack.compute.v2 + :command: console url * diff --git a/doc/source/cli/command-objects/flavor.rst b/doc/source/cli/command-objects/flavor.rst index f22463b78e..ee09b3ace7 100644 --- a/doc/source/cli/command-objects/flavor.rst +++ b/doc/source/cli/command-objects/flavor.rst @@ -4,229 +4,5 @@ flavor Compute v2 -flavor create -------------- - -Create new flavor - -.. program:: flavor create -.. code:: bash - - openstack flavor create - [--id ] - [--ram ] - [--disk ] - [--ephemeral-disk ] - [--swap ] - [--vcpus ] - [--rxtx-factor ] - [--public | --private] - [--property [...] ] - [--project ] - [--project-domain ] - [--description ] - - -.. option:: --id - - Unique flavor ID; 'auto' creates a UUID (default: auto) - -.. option:: --ram - - Memory size in MB (default 256M) - -.. option:: --disk - - Disk size in GB (default 0G) - -.. option:: --ephemeral-disk - - Ephemeral disk size in GB (default 0G) - -.. option:: --swap - - Additional swap space size in MB (default 0M) - -.. option:: --vcpus - - Number of vcpus (default 1) - -.. option:: --rxtx-factor - - RX/TX factor (default 1.0) - -.. option:: --public - - Flavor is available to other projects (default) - -.. option:: --private - - Flavor is not available to other projects - -.. option:: --property - - Property to add for this flavor (repeat option to set multiple properties) - -.. option:: --project - - Allow to access private flavor (name or ID) - (Must be used with :option:`--private` option) - -.. option:: --project-domain - - Domain the project belongs to (name or ID). - This can be used in case collisions between project names exist. - -.. option:: --description - - Description to add for this flavor. Only available starting with - ``--os-compute-api-version 2.55``. - -.. _flavor_create-flavor-name: -.. describe:: - - New flavor name - -flavor delete -------------- - -Delete flavor(s) - -.. program:: flavor delete -.. code:: bash - - openstack flavor delete - [ ...] - -.. _flavor_delete-flavor: -.. describe:: - - Flavor(s) to delete (name or ID) - -flavor list ------------ - -List flavors - -.. program:: flavor list -.. code:: bash - - openstack flavor list - [--public | --private | --all] - [--long] - [--marker ] - [--limit ] - -.. option:: --public - - List only public flavors (default) - -.. option:: --private - - List only private flavors - -.. option:: --all - - List all flavors, whether public or private - -.. option:: --long - - List additional fields in output - -.. option:: --marker - - The last flavor ID of the previous page - -.. option:: --limit - - Maximum number of flavors to display - -flavor set ----------- - -Set flavor properties - -.. program:: flavor set -.. code:: bash - - openstack flavor set - [--no-property] - [--property [...] ] - [--project ] - [--project-domain ] - [--description ] - - -.. option:: --property - - Property to add or modify for this flavor (repeat option to set multiple properties) - -.. option:: --project - - Set flavor access to project (name or ID) (admin only) - -.. option:: --project-domain - - Domain the project belongs to (name or ID). - This can be used in case collisions between project names exist. - -.. option:: --no-property - - Remove all properties from this flavor (specify both --no-property and --property - to remove the current properties before setting new properties.) - -.. option:: --description - - Description to set for this flavor. Only available starting with - ``--os-compute-api-version 2.55``. - -.. describe:: - - Flavor to modify (name or ID) - -flavor show ------------ - -Display flavor details - -.. program:: flavor show -.. code:: bash - - openstack flavor show - - -.. _flavor_show-flavor: -.. describe:: - - Flavor to display (name or ID) - -flavor unset ------------- - -Unset flavor properties - -.. program:: flavor unset -.. code:: bash - - openstack flavor unset - [--property [...] ] - [--project ] - [--project-domain ] - - -.. option:: --property - - Property to remove from flavor (repeat option to remove multiple properties) - -.. option:: --project - - Remove flavor access from project (name or ID) (admin only) - -.. option:: --project-domain - - Domain the project belongs to (name or ID). - This can be used in case collisions between project names exist. - -.. describe:: - - Flavor to modify (name or ID) +.. autoprogram-cliff:: openstack.compute.v2 + :command: flavor * diff --git a/doc/source/cli/command-objects/host.rst b/doc/source/cli/command-objects/host.rst index cbf3439827..acd5a287fe 100644 --- a/doc/source/cli/command-objects/host.rst +++ b/doc/source/cli/command-objects/host.rst @@ -6,67 +6,5 @@ Compute v2 The physical computer running a hypervisor. -host list ---------- - -List hosts - -.. program:: host list -.. code:: bash - - openstack host list - [--zone ] - -.. option:: --zone - - Only return hosts in the availability zone - -host set --------- - -Set host properties - -.. program:: host set -.. code:: bash - - openstack host set - [--enable | --disable] - [--enable-maintenance | --disable-maintenance] - - -.. _host-set: -.. option:: --enable - - Enable the host - -.. option:: --disable - - Disable the host - -.. _maintenance-set: -.. option:: --enable-maintenance - - Enable maintenance mode for the host - -.. option:: --disable-maintenance - - Disable maintenance mode for the host - -.. describe:: - - Host to modify (name only) - -host show ---------- - -Display host details - -.. program:: host show -.. code:: bash - - openstack host show - - -.. describe:: - - Name of host +.. autoprogram-cliff:: openstack.compute.v2 + :command: host * diff --git a/doc/source/cli/command-objects/hypervisor-stats.rst b/doc/source/cli/command-objects/hypervisor-stats.rst index 89faf135f4..1f5768f298 100644 --- a/doc/source/cli/command-objects/hypervisor-stats.rst +++ b/doc/source/cli/command-objects/hypervisor-stats.rst @@ -4,13 +4,5 @@ hypervisor stats Compute v2 -hypervisor stats show ---------------------- - -Display hypervisor stats details - -.. program:: hypervisor stats show -.. code:: bash - - openstack hypervisor stats show - +.. autoprogram-cliff:: openstack.compute.v2 + :command: hypervisor stats * diff --git a/doc/source/cli/command-objects/hypervisor.rst b/doc/source/cli/command-objects/hypervisor.rst index 9db384a247..9ae82bf122 100644 --- a/doc/source/cli/command-objects/hypervisor.rst +++ b/doc/source/cli/command-objects/hypervisor.rst @@ -4,38 +4,11 @@ hypervisor Compute v2 -hypervisor list ---------------- +.. NOTE(efried): have to list these out one by one; 'hypervisor *' pulls in + ... stats. -List hypervisors +.. autoprogram-cliff:: openstack.compute.v2 + :command: hypervisor list -.. program:: hypervisor list -.. code:: bash - - openstack hypervisor list - [--matching ] - [--long] - -.. option:: --matching - - Filter hypervisors using substring - -.. option:: --long - - List additional fields in output - -hypervisor show ---------------- - -Display hypervisor details - -.. program:: hypervisor show -.. code:: bash - - openstack hypervisor show - - -.. _hypervisor_show-flavor: -.. describe:: - - Hypervisor to display (name or ID) +.. autoprogram-cliff:: openstack.compute.v2 + :command: hypervisor show diff --git a/doc/source/cli/command-objects/keypair.rst b/doc/source/cli/command-objects/keypair.rst index a539f0a208..f8bce3756b 100644 --- a/doc/source/cli/command-objects/keypair.rst +++ b/doc/source/cli/command-objects/keypair.rst @@ -9,72 +9,5 @@ command. Compute v2 -keypair create --------------- - -Create new public or private key for server ssh access - -.. program:: keypair create -.. code:: bash - - openstack keypair create - [--public-key | --private-key ] - - -.. option:: --public-key - - Filename for public key to add. If not used, creates a private key. - -.. option:: --private-key - - Filename for private key to save. If not used, print private key in - console. - -.. describe:: - - New public or private key name - -keypair delete --------------- - -Delete public or private key(s) - -.. program:: keypair delete -.. code:: bash - - openstack keypair delete - [ ...] - -.. describe:: - - Name of key(s) to delete (name only) - -keypair list ------------- - -List key fingerprints - -.. program:: keypair list -.. code:: bash - - openstack keypair list - -keypair show ------------- - -Display key details - -.. program:: keypair show -.. code:: bash - - openstack keypair show - [--public-key] - - -.. option:: --public-key - - Show only bare public key paired with the generated key - -.. describe:: - - Public or private key to display (name only) +.. autoprogram-cliff:: openstack.compute.v2 + :command: keypair * diff --git a/doc/source/cli/command-objects/server.rst b/doc/source/cli/command-objects/server.rst index a7fef2578c..89eb4e3710 100644 --- a/doc/source/cli/command-objects/server.rst +++ b/doc/source/cli/command-objects/server.rst @@ -23,7 +23,7 @@ Compute v2 :command: server lock .. autoprogram-cliff:: openstack.compute.v2 - :command: server migrate + :command: server migrate* .. autoprogram-cliff:: openstack.compute.v2 :command: server pause @@ -41,7 +41,7 @@ Compute v2 :command: server rescue .. autoprogram-cliff:: openstack.compute.v2 - :command: server resize + :command: server resize* .. autoprogram-cliff:: openstack.compute.v2 :command: server restore diff --git a/doc/source/cli/command-objects/usage.rst b/doc/source/cli/command-objects/usage.rst index bb13176cc8..c2bcde0610 100644 --- a/doc/source/cli/command-objects/usage.rst +++ b/doc/source/cli/command-objects/usage.rst @@ -4,50 +4,5 @@ usage Compute v2 -usage list ----------- - -List resource usage per project - -Compute API v2.40+ returns all matching entities rather than being -limited to the API server configured maximum (``CONF.api.max_limit``). - -.. program:: usage list -.. code:: bash - - openstack usage list - [--start ] - [--end ] - -.. option:: --start - - Usage range start date, ex 2012-01-20 (default: 4 weeks ago) - -.. option:: --end - - Usage range end date, ex 2012-01-20 (default: tomorrow) - -usage show ----------- - -Show resource usage for a single project - -.. program:: usage show -.. code:: bash - - openstack usage show - [--project ] - [--start ] - [--end ] - -.. option:: --project - - Name or ID of project to show usage for - -.. option:: --start - - Usage range start date, ex 2012-01-20 (default: 4 weeks ago) - -.. option:: --end - - Usage range end date, ex 2012-01-20 (default: tomorrow) +.. autoprogram-cliff:: openstack.compute.v2 + :command: usage * diff --git a/setup.cfg b/setup.cfg index d2bee32e0b..2d6422787f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -102,6 +102,7 @@ openstack.compute.v2 = server_add_volume = openstackclient.compute.v2.server:AddServerVolume server_create = openstackclient.compute.v2.server:CreateServer server_delete = openstackclient.compute.v2.server:DeleteServer + server_dump_create = openstackclient.compute.v2.server:CreateServerDump server_list = openstackclient.compute.v2.server:ListServer server_lock = openstackclient.compute.v2.server:LockServer server_migrate = openstackclient.compute.v2.server:MigrateServer @@ -129,7 +130,6 @@ openstack.compute.v2 = server_start = openstackclient.compute.v2.server:StartServer server_stop = openstackclient.compute.v2.server:StopServer server_suspend = openstackclient.compute.v2.server:SuspendServer - server_dump_create = openstackclient.compute.v2.server:CreateServerDump server_unlock = openstackclient.compute.v2.server:UnlockServer server_unpause = openstackclient.compute.v2.server:UnpauseServer server_unrescue = openstackclient.compute.v2.server:UnrescueServer From 6f07828bf044e5eda53037e986d9ae642ee499af Mon Sep 17 00:00:00 2001 From: Eric Fried Date: Mon, 4 Nov 2019 17:42:57 -0600 Subject: [PATCH 2136/3095] Add redirect testing Adds a dependency on and invocation of the `whereto` command to validate redirects in the .htaccess file during doc builds. Change-Id: Ib6cc2953f0fd774de3c3a0c8a2bd6cff49667c14 --- doc/requirements.txt | 3 +++ doc/test/redirect-tests.txt | 18 ++++++++++++++++++ tox.ini | 2 ++ 3 files changed, 23 insertions(+) create mode 100644 doc/test/redirect-tests.txt diff --git a/doc/requirements.txt b/doc/requirements.txt index ec692e75a0..8639f9360a 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -7,6 +7,9 @@ sphinx!=1.6.6,!=1.6.7,>=1.6.5,<2.0.0;python_version=='2.7' # BSD sphinx!=1.6.6,!=1.6.7,>=1.6.5;python_version>='3.4' # BSD sphinxcontrib-apidoc>=0.2.0 # BSD +# redirect tests in docs +whereto>=0.4.0 # Apache-2.0 + # Install these to generate sphinx autodocs aodhclient>=0.9.0 # Apache-2.0 gnocchiclient>=3.3.1 # Apache-2.0 diff --git a/doc/test/redirect-tests.txt b/doc/test/redirect-tests.txt new file mode 100644 index 0000000000..9473d69225 --- /dev/null +++ b/doc/test/redirect-tests.txt @@ -0,0 +1,18 @@ +/python-openstackclient/latest/command-objects/this-is-a-test.html 301 /python-openstackclient/latest/cli/command-objects/this-is-a-test.html +/python-openstackclient/latest/authentication.html 301 /python-openstackclient/latest/cli/authentication.html +/python-openstackclient/latest/backward-incompatible.html 301 /python-openstackclient/latest/cli/backward-incompatible.html +/python-openstackclient/latest/command-list.html 301 /python-openstackclient/latest/cli/command-list.html +/python-openstackclient/latest/commands.html 301 /python-openstackclient/latest/cli/commands.html +/python-openstackclient/latest/decoder.html 301 /python-openstackclient/latest/cli/decoder.html +/python-openstackclient/latest/interactive.html 301 /python-openstackclient/latest/cli/interactive.html +/python-openstackclient/latest/plugin-commands.html 301 /python-openstackclient/latest/cli/plugin-commands.html +/python-openstackclient/latest/specs/this-is-a-test.html 301 /python-openstackclient/latest/contributor/specs/this-is-a-test.html +/python-openstackclient/latest/command-beta.html 301 /python-openstackclient/latest/contributor/command-beta.html +/python-openstackclient/latest/command-errors.html 301 /python-openstackclient/latest/contributor/command-errors.html +/python-openstackclient/latest/command-logs.html 301 /python-openstackclient/latest/contributor/command-logs.html +/python-openstackclient/latest/command-options.html 301 /python-openstackclient/latest/contributor/command-options.html +/python-openstackclient/latest/command-wrappers.html 301 /python-openstackclient/latest/contributor/command-wrappers.html +/python-openstackclient/latest/developing.html 301 /python-openstackclient/latest/contributor/developing.html +/python-openstackclient/latest/humaninterfaceguide.html 301 /python-openstackclient/latest/contributor/humaninterfaceguide.html +/python-openstackclient/latest/plugins.html 301 /python-openstackclient/latest/contributor/plugins.html +/python-openstackclient/latest/cli/plugin-commands.html 301 /python-openstackclient/latest/cli/plugin-commands/index.html diff --git a/tox.ini b/tox.ini index a973b107f7..addafa2bfe 100644 --- a/tox.ini +++ b/tox.ini @@ -118,6 +118,8 @@ deps = commands = sphinx-build -a -E -W -d doc/build/doctrees -b html doc/source doc/build/html sphinx-build -a -E -W -d doc/build/doctrees -b man doc/source doc/build/man + # Validate redirects (must be done after the docs build + whereto doc/build/html/.htaccess doc/test/redirect-tests.txt [testenv:releasenotes] basepython = python3 From e2d8dc0f1c33613c56da37758d73d63b550642c0 Mon Sep 17 00:00:00 2001 From: Eric Fried Date: Mon, 4 Nov 2019 17:55:40 -0600 Subject: [PATCH 2137/3095] Deflate .htaccess Use some regexes to reduce the number of lines in .htaccess and hopefully make it slightly clearer which groupings of files redirect to which new locations. Also collapse one redundant double-redirect. Change-Id: I65c4960856985d71076291f175df17f27a5ab8cc --- doc/source/_extra/.htaccess | 18 +++--------------- doc/test/redirect-tests.txt | 2 +- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/doc/source/_extra/.htaccess b/doc/source/_extra/.htaccess index fd91901e87..513deddbdc 100644 --- a/doc/source/_extra/.htaccess +++ b/doc/source/_extra/.htaccess @@ -2,22 +2,10 @@ redirectmatch 301 ^/python-openstackclient/([^/]+)/command-objects/([^/.]+).html$ /python-openstackclient/$1/cli/command-objects/$2.html -redirectmatch 301 ^/python-openstackclient/([^/]+)/authentication.html$ /python-openstackclient/$1/cli/authentication.html -redirectmatch 301 ^/python-openstackclient/([^/]+)/backward-incompatible.html$ /python-openstackclient/$1/cli/backward-incompatible.html -redirectmatch 301 ^/python-openstackclient/([^/]+)/command-list.html$ /python-openstackclient/$1/cli/command-list.html -redirectmatch 301 ^/python-openstackclient/([^/]+)/commands.html$ /python-openstackclient/$1/cli/commands.html -redirectmatch 301 ^/python-openstackclient/([^/]+)/decoder.html$ /python-openstackclient/$1/cli/decoder.html -redirectmatch 301 ^/python-openstackclient/([^/]+)/interactive.html$ /python-openstackclient/$1/cli/interactive.html -redirectmatch 301 ^/python-openstackclient/([^/]+)/plugin-commands.html$ /python-openstackclient/$1/cli/plugin-commands.html +redirectmatch 301 ^/python-openstackclient/([^/]+)/(authentication|backward-incompatible|command-list|commands|decoder|interactive).html$ /python-openstackclient/$1/cli/$2.html +redirectmatch 301 ^/python-openstackclient/([^/]+)/plugin-commands.html$ /python-openstackclient/$1/cli/plugin-commands/index.html redirectmatch 301 ^/python-openstackclient/([^/]+)/specs/([^/.]+).html$ /python-openstackclient/$1/contributor/specs/$2.html -redirectmatch 301 ^/python-openstackclient/([^/]+)/command-beta.html$ /python-openstackclient/$1/contributor/command-beta.html -redirectmatch 301 ^/python-openstackclient/([^/]+)/command-errors.html$ /python-openstackclient/$1/contributor/command-errors.html -redirectmatch 301 ^/python-openstackclient/([^/]+)/command-logs.html$ /python-openstackclient/$1/contributor/command-logs.html -redirectmatch 301 ^/python-openstackclient/([^/]+)/command-options.html$ /python-openstackclient/$1/contributor/command-options.html -redirectmatch 301 ^/python-openstackclient/([^/]+)/command-wrappers.html$ /python-openstackclient/$1/contributor/command-wrappers.html -redirectmatch 301 ^/python-openstackclient/([^/]+)/developing.html$ /python-openstackclient/$1/contributor/developing.html -redirectmatch 301 ^/python-openstackclient/([^/]+)/humaninterfaceguide.html$ /python-openstackclient/$1/contributor/humaninterfaceguide.html -redirectmatch 301 ^/python-openstackclient/([^/]+)/plugins.html$ /python-openstackclient/$1/contributor/plugins.html +redirectmatch 301 ^/python-openstackclient/([^/]+)/(command-(beta|errors|logs|options|wrappers)|developing|humaninterfaceguide|plugins).html$ /python-openstackclient/$1/contributor/$2.html redirectmatch 301 ^/python-openstackclient/([^/]+)/cli/plugin-commands.html$ /python-openstackclient/$1/cli/plugin-commands/index.html diff --git a/doc/test/redirect-tests.txt b/doc/test/redirect-tests.txt index 9473d69225..ef3a48decb 100644 --- a/doc/test/redirect-tests.txt +++ b/doc/test/redirect-tests.txt @@ -5,7 +5,7 @@ /python-openstackclient/latest/commands.html 301 /python-openstackclient/latest/cli/commands.html /python-openstackclient/latest/decoder.html 301 /python-openstackclient/latest/cli/decoder.html /python-openstackclient/latest/interactive.html 301 /python-openstackclient/latest/cli/interactive.html -/python-openstackclient/latest/plugin-commands.html 301 /python-openstackclient/latest/cli/plugin-commands.html +/python-openstackclient/latest/plugin-commands.html 301 /python-openstackclient/latest/cli/plugin-commands/index.html /python-openstackclient/latest/specs/this-is-a-test.html 301 /python-openstackclient/latest/contributor/specs/this-is-a-test.html /python-openstackclient/latest/command-beta.html 301 /python-openstackclient/latest/contributor/command-beta.html /python-openstackclient/latest/command-errors.html 301 /python-openstackclient/latest/contributor/command-errors.html From 3b409e4d0e136380042a59a421ec4c4dc5b95c18 Mon Sep 17 00:00:00 2001 From: Eric Fried Date: Tue, 5 Nov 2019 13:26:19 -0600 Subject: [PATCH 2138/3095] Refactor AggregateTests While investigating the referenced story/bug I noticed that wait_for_status in openstackclient.tests.functional.compute.v2.test_aggregate.AggregateTests was doing a lot more than it should ever need to (it probably got copied in from somewhere). The two places calling it only need to a) check the output of `openstack aggregate show`, and b) try once -- since they just got done creating the aggregate synchronously, there should never be a need to delay/retry. So this commit removes the helper method and just inlines the check. At the same time, the addCleanup(aggregate delete) directives are moved above their respective creates. This is a defensive best practice which makes sure cleanup happens even if something fails very soon after the actual back-end create (as was in fact the case with the referenced bug/story). It is unknown whether this will impact the referenced bug. Change-Id: I0d7432f13642fbccd5ca79da9c76adfcbabb5fa9 Story: 2006811 Related-Bug: #1851391 --- .../functional/compute/v2/test_aggregate.py | 60 ++++++------------- 1 file changed, 19 insertions(+), 41 deletions(-) diff --git a/openstackclient/tests/functional/compute/v2/test_aggregate.py b/openstackclient/tests/functional/compute/v2/test_aggregate.py index be318ae5e3..1de5309921 100644 --- a/openstackclient/tests/functional/compute/v2/test_aggregate.py +++ b/openstackclient/tests/functional/compute/v2/test_aggregate.py @@ -11,7 +11,6 @@ # under the License. import json -import time import uuid from openstackclient.tests.functional import base @@ -20,34 +19,14 @@ class AggregateTests(base.TestCase): """Functional tests for aggregate""" - def wait_for_status(self, check_type, check_name, desired_status, - wait=120, interval=5, failures=None): - current_status = "notset" - if failures is None: - failures = ['error'] - total_sleep = 0 - while total_sleep < wait: - output = json.loads(self.openstack( - check_type + ' show -f json ' + check_name)) - current_status = output['name'] - if (current_status == desired_status): - print('{} {} now has status {}' - .format(check_type, check_name, current_status)) - return - print('Checking {} {} Waiting for {} current status: {}' - .format(check_type, check_name, - desired_status, current_status)) - if current_status in failures: - raise Exception( - 'Current status {} of {} {} is one of failures {}' - .format(current_status, check_type, check_name, failures)) - time.sleep(interval) - total_sleep += interval - self.assertOutput(desired_status, current_status) - def test_aggregate_crud(self): """Test create, delete multiple""" name1 = uuid.uuid4().hex + self.addCleanup( + self.openstack, + 'aggregate delete ' + name1, + fail_ok=True, + ) cmd_output = json.loads(self.openstack( 'aggregate create -f json ' + '--zone nova ' + @@ -66,14 +45,16 @@ def test_aggregate_crud(self): 'a', cmd_output['properties'] ) - self.wait_for_status('aggregate', name1, name1) + cmd_output = json.loads(self.openstack( + 'aggregate show -f json ' + name1)) + self.assertEqual(name1, cmd_output['name']) + + name2 = uuid.uuid4().hex self.addCleanup( self.openstack, - 'aggregate delete ' + name1, + 'aggregate delete ' + name2, fail_ok=True, ) - - name2 = uuid.uuid4().hex cmd_output = json.loads(self.openstack( 'aggregate create -f json ' + '--zone external ' + @@ -87,15 +68,17 @@ def test_aggregate_crud(self): 'external', cmd_output['availability_zone'] ) - self.wait_for_status('aggregate', name2, name2) + cmd_output = json.loads(self.openstack( + 'aggregate show -f json ' + name2)) + self.assertEqual(name2, cmd_output['name']) + + # Test aggregate set + name3 = uuid.uuid4().hex self.addCleanup( self.openstack, - 'aggregate delete ' + name2, + 'aggregate delete ' + name3, fail_ok=True, ) - - # Test aggregate set - name3 = uuid.uuid4().hex raw_output = self.openstack( 'aggregate set ' + '--name ' + name3 + ' ' + @@ -105,11 +88,6 @@ def test_aggregate_crud(self): name1 ) self.assertOutput('', raw_output) - self.addCleanup( - self.openstack, - 'aggregate delete ' + name3, - fail_ok=True, - ) cmd_output = json.loads(self.openstack( 'aggregate show -f json ' + @@ -196,11 +174,11 @@ def test_aggregate_add_and_remove_host(self): self.skipTest("Skip aggregates in a Nova cells v1 configuration") name = uuid.uuid4().hex + self.addCleanup(self.openstack, 'aggregate delete ' + name) self.openstack( 'aggregate create ' + name ) - self.addCleanup(self.openstack, 'aggregate delete ' + name) # Test add host cmd_output = json.loads(self.openstack( From 1c0160c8aa8482958df54e0814023c5eeed0611e Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Mon, 18 Nov 2019 14:44:49 -0600 Subject: [PATCH 2139/3095] Create Volume v3 functional tests Until now-ish Volume v3 has been a pass-through to v2. In order to prepare to make the Volume v3 commands stand-alone copy the v2 functional tests to v3. This is the first of a series of reviews to completely separate Volume v2 and v3 commands. Once these are split we can begin to implement v3 microversion support and/or start using the OpenStack SDK as the REST library. Change-Id: Iefd78d8ef6bb851d7360596337a88ee8f8476767 Signed-off-by: Dean Troyer --- .../tests/functional/volume/v3/test_qos.py | 204 ++++++++++++- .../volume/v3/test_transfer_request.py | 106 ++++++- .../tests/functional/volume/v3/test_volume.py | 268 +++++++++++++++++- .../volume/v3/test_volume_snapshot.py | 240 +++++++++++++++- .../functional/volume/v3/test_volume_type.py | 224 ++++++++++++++- 5 files changed, 1031 insertions(+), 11 deletions(-) diff --git a/openstackclient/tests/functional/volume/v3/test_qos.py b/openstackclient/tests/functional/volume/v3/test_qos.py index a6290fc531..fdfa682791 100644 --- a/openstackclient/tests/functional/volume/v3/test_qos.py +++ b/openstackclient/tests/functional/volume/v3/test_qos.py @@ -10,9 +10,209 @@ # License for the specific language governing permissions and limitations # under the License. -from openstackclient.tests.functional.volume.v2 import test_qos as v2 +import json +import uuid + from openstackclient.tests.functional.volume.v3 import common -class QosTests(common.BaseVolumeTests, v2.QosTests): +class QosTests(common.BaseVolumeTests): """Functional tests for volume qos. """ + + def test_volume_qos_create_delete_list(self): + """Test create, list, delete multiple""" + name1 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume qos create -f json ' + + name1 + )) + self.assertEqual( + name1, + cmd_output['name'] + ) + + name2 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume qos create -f json ' + + name2 + )) + self.assertEqual( + name2, + cmd_output['name'] + ) + + # Test list + cmd_output = json.loads(self.openstack( + 'volume qos list -f json' + )) + names = [x["Name"] for x in cmd_output] + self.assertIn(name1, names) + self.assertIn(name2, names) + + # Test delete multiple + del_output = self.openstack('volume qos delete ' + name1 + ' ' + name2) + self.assertOutput('', del_output) + + def test_volume_qos_set_show_unset(self): + """Tests create volume qos, set, unset, show, delete""" + + name = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume qos create -f json ' + + '--consumer front-end ' + '--property Alpha=a ' + + name + )) + self.addCleanup(self.openstack, 'volume qos delete ' + name) + self.assertEqual( + name, + cmd_output['name'] + ) + + self.assertEqual( + "front-end", + cmd_output['consumer'] + ) + self.assertEqual( + {'Alpha': 'a'}, + cmd_output['properties'] + ) + + # Test volume qos set + raw_output = self.openstack( + 'volume qos set ' + + '--property Alpha=c ' + + '--property Beta=b ' + + name, + ) + self.assertOutput('', raw_output) + + # Test volume qos show + cmd_output = json.loads(self.openstack( + 'volume qos show -f json ' + + name + )) + self.assertEqual( + name, + cmd_output['name'] + ) + self.assertEqual( + {'Alpha': 'c', 'Beta': 'b'}, + cmd_output['properties'] + ) + + # Test volume qos unset + raw_output = self.openstack( + 'volume qos unset ' + + '--property Alpha ' + + name, + ) + self.assertOutput('', raw_output) + + cmd_output = json.loads(self.openstack( + 'volume qos show -f json ' + + name + )) + self.assertEqual( + name, + cmd_output['name'] + ) + self.assertEqual( + {'Beta': 'b'}, + cmd_output['properties'] + ) + + def test_volume_qos_asso_disasso(self): + """Tests associate and disassociate qos with volume type""" + vol_type1 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume type create -f json ' + + vol_type1 + )) + self.assertEqual( + vol_type1, + cmd_output['name'] + ) + self.addCleanup(self.openstack, 'volume type delete ' + vol_type1) + + vol_type2 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume type create -f json ' + + vol_type2 + )) + self.assertEqual( + vol_type2, + cmd_output['name'] + ) + self.addCleanup(self.openstack, 'volume type delete ' + vol_type2) + + name = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume qos create -f json ' + + name + )) + self.assertEqual( + name, + cmd_output['name'] + ) + self.addCleanup(self.openstack, 'volume qos delete ' + name) + + # Test associate + raw_output = self.openstack( + 'volume qos associate ' + + name + ' ' + vol_type1 + ) + self.assertOutput('', raw_output) + raw_output = self.openstack( + 'volume qos associate ' + + name + ' ' + vol_type2 + ) + self.assertOutput('', raw_output) + + cmd_output = json.loads(self.openstack( + 'volume qos show -f json ' + + name + )) + types = cmd_output["associations"] + self.assertIn(vol_type1, types) + self.assertIn(vol_type2, types) + + # Test disassociate + raw_output = self.openstack( + 'volume qos disassociate ' + + '--volume-type ' + vol_type1 + + ' ' + name + ) + self.assertOutput('', raw_output) + cmd_output = json.loads(self.openstack( + 'volume qos show -f json ' + + name + )) + types = cmd_output["associations"] + self.assertNotIn(vol_type1, types) + self.assertIn(vol_type2, types) + + # Test disassociate --all + raw_output = self.openstack( + 'volume qos associate ' + + name + ' ' + vol_type1 + ) + self.assertOutput('', raw_output) + cmd_output = json.loads(self.openstack( + 'volume qos show -f json ' + + name + )) + types = cmd_output["associations"] + self.assertIn(vol_type1, types) + self.assertIn(vol_type2, types) + + raw_output = self.openstack( + 'volume qos disassociate ' + + '--all ' + name + ) + self.assertOutput('', raw_output) + cmd_output = json.loads(self.openstack( + 'volume qos show -f json ' + + name + )) + self.assertNotIn("associations", cmd_output.keys()) diff --git a/openstackclient/tests/functional/volume/v3/test_transfer_request.py b/openstackclient/tests/functional/volume/v3/test_transfer_request.py index f16dfafa7a..1bbfedc902 100644 --- a/openstackclient/tests/functional/volume/v3/test_transfer_request.py +++ b/openstackclient/tests/functional/volume/v3/test_transfer_request.py @@ -10,12 +10,112 @@ # License for the specific language governing permissions and limitations # under the License. -from openstackclient.tests.functional.volume.v2 import test_transfer_request \ - as v2 +import json +import uuid + from openstackclient.tests.functional.volume.v3 import common -class TransferRequestTests(common.BaseVolumeTests, v2.TransferRequestTests): +class TransferRequestTests(common.BaseVolumeTests): """Functional tests for transfer request. """ API_VERSION = '3' + + def test_volume_transfer_request_accept(self): + volume_name = uuid.uuid4().hex + xfer_name = uuid.uuid4().hex + + # create a volume + cmd_output = json.loads(self.openstack( + 'volume create -f json ' + + '--size 1 ' + + volume_name + )) + self.assertEqual(volume_name, cmd_output['name']) + self.addCleanup( + self.openstack, + '--os-volume-api-version ' + self.API_VERSION + ' ' + + 'volume delete ' + + volume_name + ) + self.wait_for_status("volume", volume_name, "available") + + # create volume transfer request for the volume + # and get the auth_key of the new transfer request + cmd_output = json.loads(self.openstack( + '--os-volume-api-version ' + self.API_VERSION + ' ' + + 'volume transfer request create -f json ' + + ' --name ' + xfer_name + ' ' + + volume_name + )) + self.assertEqual(xfer_name, cmd_output['name']) + xfer_id = cmd_output['id'] + auth_key = cmd_output['auth_key'] + self.assertTrue(auth_key) + self.wait_for_status("volume", volume_name, "awaiting-transfer") + + # accept the volume transfer request + cmd_output = json.loads(self.openstack( + '--os-volume-api-version ' + self.API_VERSION + ' ' + + 'volume transfer request accept -f json ' + + '--auth-key ' + auth_key + ' ' + + xfer_id + )) + self.assertEqual(xfer_name, cmd_output['name']) + self.wait_for_status("volume", volume_name, "available") + + def test_volume_transfer_request_list_show(self): + volume_name = uuid.uuid4().hex + xfer_name = uuid.uuid4().hex + + # create a volume + cmd_output = json.loads(self.openstack( + 'volume create -f json ' + + '--size 1 ' + + volume_name + )) + self.assertEqual(volume_name, cmd_output['name']) + self.addCleanup( + self.openstack, + '--os-volume-api-version ' + self.API_VERSION + ' ' + + 'volume delete ' + + volume_name + ) + self.wait_for_status("volume", volume_name, "available") + + cmd_output = json.loads(self.openstack( + '--os-volume-api-version ' + self.API_VERSION + ' ' + + 'volume transfer request create -f json ' + + ' --name ' + xfer_name + ' ' + + volume_name + )) + self.assertEqual(xfer_name, cmd_output['name']) + xfer_id = cmd_output['id'] + auth_key = cmd_output['auth_key'] + self.assertTrue(auth_key) + self.wait_for_status("volume", volume_name, "awaiting-transfer") + + cmd_output = json.loads(self.openstack( + '--os-volume-api-version ' + self.API_VERSION + ' ' + + 'volume transfer request list -f json' + )) + self.assertIn(xfer_name, [req['Name'] for req in cmd_output]) + + cmd_output = json.loads(self.openstack( + '--os-volume-api-version ' + self.API_VERSION + ' ' + + 'volume transfer request show -f json ' + + xfer_id + )) + self.assertEqual(xfer_name, cmd_output['name']) + + # NOTE(dtroyer): We need to delete the transfer request to allow the + # volume to be deleted. The addCleanup() route does + # not have a mechanism to wait for the volume status + # to become 'available' before attempting to delete + # the volume. + cmd_output = self.openstack( + '--os-volume-api-version ' + self.API_VERSION + ' ' + + 'volume transfer request delete ' + + xfer_id + ) + self.wait_for_status("volume", volume_name, "available") diff --git a/openstackclient/tests/functional/volume/v3/test_volume.py b/openstackclient/tests/functional/volume/v3/test_volume.py index 283b830f63..6635167dbd 100644 --- a/openstackclient/tests/functional/volume/v3/test_volume.py +++ b/openstackclient/tests/functional/volume/v3/test_volume.py @@ -10,9 +10,273 @@ # License for the specific language governing permissions and limitations # under the License. -from openstackclient.tests.functional.volume.v2 import test_volume as v2 +import json +import uuid + from openstackclient.tests.functional.volume.v3 import common -class VolumeTests(common.BaseVolumeTests, v2.VolumeTests): +class VolumeTests(common.BaseVolumeTests): """Functional tests for volume. """ + + def test_volume_delete(self): + """Test create, delete multiple""" + name1 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume create -f json ' + + '--size 1 ' + + name1 + )) + self.assertEqual( + 1, + cmd_output["size"], + ) + + name2 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume create -f json ' + + '--size 2 ' + + name2 + )) + self.assertEqual( + 2, + cmd_output["size"], + ) + + self.wait_for_status("volume", name1, "available") + self.wait_for_status("volume", name2, "available") + del_output = self.openstack('volume delete ' + name1 + ' ' + name2) + self.assertOutput('', del_output) + + def test_volume_list(self): + """Test create, list filter""" + name1 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume create -f json ' + + '--size 1 ' + + name1 + )) + self.addCleanup(self.openstack, 'volume delete ' + name1) + self.assertEqual( + 1, + cmd_output["size"], + ) + self.wait_for_status("volume", name1, "available") + + name2 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume create -f json ' + + '--size 2 ' + + name2 + )) + self.addCleanup(self.openstack, 'volume delete ' + name2) + self.assertEqual( + 2, + cmd_output["size"], + ) + self.wait_for_status("volume", name2, "available") + raw_output = self.openstack( + 'volume set ' + + '--state error ' + + name2 + ) + self.assertOutput('', raw_output) + + # Test list --long + cmd_output = json.loads(self.openstack( + 'volume list -f json ' + + '--long' + )) + names = [x["Name"] for x in cmd_output] + self.assertIn(name1, names) + self.assertIn(name2, names) + + # Test list --status + cmd_output = json.loads(self.openstack( + 'volume list -f json ' + + '--status error' + )) + names = [x["Name"] for x in cmd_output] + self.assertNotIn(name1, names) + self.assertIn(name2, names) + + # TODO(qiangjiahui): Add project option to filter tests when we can + # specify volume with project + + def test_volume_set_and_unset(self): + """Tests create volume, set, unset, show, delete""" + name = uuid.uuid4().hex + new_name = name + "_" + cmd_output = json.loads(self.openstack( + 'volume create -f json ' + + '--size 1 ' + + '--description aaaa ' + + '--property Alpha=a ' + + name + )) + self.addCleanup(self.openstack, 'volume delete ' + new_name) + self.assertEqual( + name, + cmd_output["name"], + ) + self.assertEqual( + 1, + cmd_output["size"], + ) + self.assertEqual( + 'aaaa', + cmd_output["description"], + ) + self.assertEqual( + {'Alpha': 'a'}, + cmd_output["properties"], + ) + self.assertEqual( + 'false', + cmd_output["bootable"], + ) + self.wait_for_status("volume", name, "available") + + # Test volume set + raw_output = self.openstack( + 'volume set ' + + '--name ' + new_name + + ' --size 2 ' + + '--description bbbb ' + + '--no-property ' + + '--property Beta=b ' + + '--property Gamma=c ' + + '--image-property a=b ' + + '--image-property c=d ' + + '--bootable ' + + name, + ) + self.assertOutput('', raw_output) + + cmd_output = json.loads(self.openstack( + 'volume show -f json ' + + new_name + )) + self.assertEqual( + new_name, + cmd_output["name"], + ) + self.assertEqual( + 2, + cmd_output["size"], + ) + self.assertEqual( + 'bbbb', + cmd_output["description"], + ) + self.assertEqual( + {'Beta': 'b', 'Gamma': 'c'}, + cmd_output["properties"], + ) + self.assertEqual( + {'a': 'b', 'c': 'd'}, + cmd_output["volume_image_metadata"], + ) + self.assertEqual( + 'true', + cmd_output["bootable"], + ) + + # Test volume unset + raw_output = self.openstack( + 'volume unset ' + + '--property Beta ' + + '--image-property a ' + + new_name, + ) + self.assertOutput('', raw_output) + + cmd_output = json.loads(self.openstack( + 'volume show -f json ' + + new_name + )) + self.assertEqual( + {'Gamma': 'c'}, + cmd_output["properties"], + ) + self.assertEqual( + {'c': 'd'}, + cmd_output["volume_image_metadata"], + ) + + def test_volume_snapshot(self): + """Tests volume create from snapshot""" + + volume_name = uuid.uuid4().hex + snapshot_name = uuid.uuid4().hex + # Make a snapshot + cmd_output = json.loads(self.openstack( + 'volume create -f json ' + + '--size 1 ' + + volume_name + )) + self.wait_for_status("volume", volume_name, "available") + self.assertEqual( + volume_name, + cmd_output["name"], + ) + cmd_output = json.loads(self.openstack( + 'volume snapshot create -f json ' + + snapshot_name + + ' --volume ' + volume_name + )) + self.wait_for_status("volume snapshot", snapshot_name, "available") + + name = uuid.uuid4().hex + # Create volume from snapshot + cmd_output = json.loads(self.openstack( + 'volume create -f json ' + + '--snapshot ' + snapshot_name + + ' ' + name + )) + self.addCleanup(self.openstack, 'volume delete ' + name) + self.addCleanup(self.openstack, 'volume delete ' + volume_name) + self.assertEqual( + name, + cmd_output["name"], + ) + self.wait_for_status("volume", name, "available") + + # Delete snapshot + raw_output = self.openstack( + 'volume snapshot delete ' + snapshot_name) + self.assertOutput('', raw_output) + # Deleting snapshot may take time. If volume snapshot still exists when + # a parent volume delete is requested, the volume deletion will fail. + self.wait_for_delete('volume snapshot', snapshot_name) + + def test_volume_list_backward_compatibility(self): + """Test backward compatibility of list command""" + name1 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume create -f json ' + + '--size 1 ' + + name1 + )) + self.addCleanup(self.openstack, 'volume delete ' + name1) + self.assertEqual( + 1, + cmd_output["size"], + ) + self.wait_for_status("volume", name1, "available") + + # Test list -c "Display Name" + cmd_output = json.loads(self.openstack( + 'volume list -f json ' + + '-c "Display Name"' + )) + for each_volume in cmd_output: + self.assertIn('Display Name', each_volume) + + # Test list -c "Name" + cmd_output = json.loads(self.openstack( + 'volume list -f json ' + + '-c "Name"' + )) + for each_volume in cmd_output: + self.assertIn('Name', each_volume) diff --git a/openstackclient/tests/functional/volume/v3/test_volume_snapshot.py b/openstackclient/tests/functional/volume/v3/test_volume_snapshot.py index 28eee6d24a..edfdafb6bd 100644 --- a/openstackclient/tests/functional/volume/v3/test_volume_snapshot.py +++ b/openstackclient/tests/functional/volume/v3/test_volume_snapshot.py @@ -10,9 +10,245 @@ # License for the specific language governing permissions and limitations # under the License. -from openstackclient.tests.functional.volume.v2 import test_volume_snapshot as v2 # noqa +import json +import uuid + from openstackclient.tests.functional.volume.v3 import common -class VolumeSnapshotTests(common.BaseVolumeTests, v2.VolumeSnapshotTests): +class VolumeSnapshotTests(common.BaseVolumeTests): """Functional tests for volume snapshot. """ + + VOLLY = uuid.uuid4().hex + + @classmethod + def setUpClass(cls): + super(VolumeSnapshotTests, cls).setUpClass() + # create a volume for all tests to create snapshot + cmd_output = json.loads(cls.openstack( + 'volume create -f json ' + + '--size 1 ' + + cls.VOLLY + )) + cls.wait_for_status('volume', cls.VOLLY, 'available') + cls.VOLUME_ID = cmd_output['id'] + + @classmethod + def tearDownClass(cls): + try: + cls.wait_for_status('volume', cls.VOLLY, 'available') + raw_output = cls.openstack( + 'volume delete --force ' + cls.VOLLY) + cls.assertOutput('', raw_output) + finally: + super(VolumeSnapshotTests, cls).tearDownClass() + + def test_volume_snapshot_delete(self): + """Test create, delete multiple""" + name1 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume snapshot create -f json ' + + name1 + + ' --volume ' + self.VOLLY + )) + self.assertEqual( + name1, + cmd_output["name"], + ) + + name2 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume snapshot create -f json ' + + name2 + + ' --volume ' + self.VOLLY + )) + self.assertEqual( + name2, + cmd_output["name"], + ) + + self.wait_for_status('volume snapshot', name1, 'available') + self.wait_for_status('volume snapshot', name2, 'available') + + del_output = self.openstack( + 'volume snapshot delete ' + name1 + ' ' + name2) + self.assertOutput('', del_output) + self.wait_for_delete('volume snapshot', name1) + self.wait_for_delete('volume snapshot', name2) + + def test_volume_snapshot_list(self): + """Test create, list filter""" + name1 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume snapshot create -f json ' + + name1 + + ' --volume ' + self.VOLLY + )) + self.addCleanup(self.wait_for_delete, 'volume snapshot', name1) + self.addCleanup(self.openstack, 'volume snapshot delete ' + name1) + self.assertEqual( + name1, + cmd_output["name"], + ) + self.assertEqual( + self.VOLUME_ID, + cmd_output["volume_id"], + ) + self.assertEqual( + 1, + cmd_output["size"], + ) + self.wait_for_status('volume snapshot', name1, 'available') + + name2 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume snapshot create -f json ' + + name2 + + ' --volume ' + self.VOLLY + )) + self.addCleanup(self.wait_for_delete, 'volume snapshot', name2) + self.addCleanup(self.openstack, 'volume snapshot delete ' + name2) + self.assertEqual( + name2, + cmd_output["name"], + ) + self.assertEqual( + self.VOLUME_ID, + cmd_output["volume_id"], + ) + self.assertEqual( + 1, + cmd_output["size"], + ) + self.wait_for_status('volume snapshot', name2, 'available') + raw_output = self.openstack( + 'volume snapshot set ' + + '--state error ' + + name2 + ) + self.assertOutput('', raw_output) + + # Test list --long, --status + cmd_output = json.loads(self.openstack( + 'volume snapshot list -f json ' + + '--long ' + + '--status error' + )) + names = [x["Name"] for x in cmd_output] + self.assertNotIn(name1, names) + self.assertIn(name2, names) + + # Test list --volume + cmd_output = json.loads(self.openstack( + 'volume snapshot list -f json ' + + '--volume ' + self.VOLLY + )) + names = [x["Name"] for x in cmd_output] + self.assertIn(name1, names) + self.assertIn(name2, names) + + # Test list --name + cmd_output = json.loads(self.openstack( + 'volume snapshot list -f json ' + + '--name ' + name1 + )) + names = [x["Name"] for x in cmd_output] + self.assertIn(name1, names) + self.assertNotIn(name2, names) + + def test_volume_snapshot_set(self): + """Test create, set, unset, show, delete volume snapshot""" + name = uuid.uuid4().hex + new_name = name + "_" + cmd_output = json.loads(self.openstack( + 'volume snapshot create -f json ' + + '--volume ' + self.VOLLY + + ' --description aaaa ' + + '--property Alpha=a ' + + name + )) + self.addCleanup(self.wait_for_delete, 'volume snapshot', new_name) + self.addCleanup(self.openstack, 'volume snapshot delete ' + new_name) + self.assertEqual( + name, + cmd_output["name"], + ) + self.assertEqual( + 1, + cmd_output["size"], + ) + self.assertEqual( + 'aaaa', + cmd_output["description"], + ) + self.assertEqual( + {'Alpha': 'a'}, + cmd_output["properties"], + ) + self.wait_for_status('volume snapshot', name, 'available') + + # Test volume snapshot set + raw_output = self.openstack( + 'volume snapshot set ' + + '--name ' + new_name + + ' --description bbbb ' + + '--property Alpha=c ' + + '--property Beta=b ' + + name, + ) + self.assertOutput('', raw_output) + + # Show snapshot set result + cmd_output = json.loads(self.openstack( + 'volume snapshot show -f json ' + + new_name + )) + self.assertEqual( + new_name, + cmd_output["name"], + ) + self.assertEqual( + 1, + cmd_output["size"], + ) + self.assertEqual( + 'bbbb', + cmd_output["description"], + ) + self.assertEqual( + {'Alpha': 'c', 'Beta': 'b'}, + cmd_output["properties"], + ) + + # Test volume snapshot unset + raw_output = self.openstack( + 'volume snapshot unset ' + + '--property Alpha ' + + new_name, + ) + self.assertOutput('', raw_output) + + cmd_output = json.loads(self.openstack( + 'volume snapshot show -f json ' + + new_name + )) + self.assertEqual( + {'Beta': 'b'}, + cmd_output["properties"], + ) + + # Test volume snapshot set --no-property + raw_output = self.openstack( + 'volume snapshot set ' + + '--no-property ' + + new_name, + ) + self.assertOutput('', raw_output) + cmd_output = json.loads(self.openstack( + 'volume snapshot show -f json ' + + new_name + )) + self.assertNotIn( + {'Beta': 'b'}, + cmd_output["properties"], + ) diff --git a/openstackclient/tests/functional/volume/v3/test_volume_type.py b/openstackclient/tests/functional/volume/v3/test_volume_type.py index eb66515ed1..79d4096998 100644 --- a/openstackclient/tests/functional/volume/v3/test_volume_type.py +++ b/openstackclient/tests/functional/volume/v3/test_volume_type.py @@ -10,9 +10,229 @@ # License for the specific language governing permissions and limitations # under the License. -from openstackclient.tests.functional.volume.v2 import test_volume_type as v2 +import json +import time +import uuid + from openstackclient.tests.functional.volume.v3 import common -class VolumeTypeTests(common.BaseVolumeTests, v2.VolumeTypeTests): +class VolumeTypeTests(common.BaseVolumeTests): """Functional tests for volume type. """ + + def test_volume_type_create_list(self): + name = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume type create -f json --private ' + + name, + )) + self.addCleanup( + self.openstack, + 'volume type delete ' + name, + ) + self.assertEqual(name, cmd_output['name']) + + cmd_output = json.loads(self.openstack( + 'volume type show -f json %s' % name + )) + self.assertEqual(name, cmd_output['name']) + + cmd_output = json.loads(self.openstack('volume type list -f json')) + self.assertIn(name, [t['Name'] for t in cmd_output]) + + cmd_output = json.loads(self.openstack( + 'volume type list -f json --default' + )) + self.assertEqual(1, len(cmd_output)) + self.assertEqual('lvmdriver-1', cmd_output[0]['Name']) + + def test_volume_type_set_unset_properties(self): + name = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume type create -f json --private ' + + name, + )) + self.addCleanup( + self.openstack, + 'volume type delete ' + name + ) + self.assertEqual(name, cmd_output['name']) + + raw_output = self.openstack( + 'volume type set --property a=b --property c=d %s' % name + ) + self.assertEqual("", raw_output) + cmd_output = json.loads(self.openstack( + 'volume type show -f json %s' % name + )) + self.assertEqual({'a': 'b', 'c': 'd'}, cmd_output['properties']) + + raw_output = self.openstack( + 'volume type unset --property a %s' % name + ) + self.assertEqual("", raw_output) + cmd_output = json.loads(self.openstack( + 'volume type show -f json %s' % name + )) + self.assertEqual({'c': 'd'}, cmd_output['properties']) + + def test_volume_type_set_unset_multiple_properties(self): + name = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume type create -f json --private ' + + name, + )) + self.addCleanup( + self.openstack, + 'volume type delete ' + name + ) + self.assertEqual(name, cmd_output['name']) + + raw_output = self.openstack( + 'volume type set --property a=b --property c=d %s' % name + ) + self.assertEqual("", raw_output) + cmd_output = json.loads(self.openstack( + 'volume type show -f json %s' % name + )) + self.assertEqual({'a': 'b', 'c': 'd'}, cmd_output['properties']) + + raw_output = self.openstack( + 'volume type unset --property a --property c %s' % name + ) + self.assertEqual("", raw_output) + cmd_output = json.loads(self.openstack( + 'volume type show -f json %s' % name + )) + self.assertEqual({}, cmd_output['properties']) + + def test_volume_type_set_unset_project(self): + name = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume type create -f json --private ' + + name, + )) + self.addCleanup( + self.openstack, + 'volume type delete ' + name + ) + self.assertEqual(name, cmd_output['name']) + + raw_output = self.openstack( + 'volume type set --project admin %s' % name + ) + self.assertEqual("", raw_output) + + raw_output = self.openstack( + 'volume type unset --project admin %s' % name + ) + self.assertEqual("", raw_output) + + def test_multi_delete(self): + vol_type1 = uuid.uuid4().hex + vol_type2 = uuid.uuid4().hex + self.openstack('volume type create %s' % vol_type1) + time.sleep(5) + self.openstack('volume type create %s' % vol_type2) + time.sleep(5) + cmd = 'volume type delete %s %s' % (vol_type1, vol_type2) + raw_output = self.openstack(cmd) + self.assertOutput('', raw_output) + + # NOTE: Add some basic funtional tests with the old format to + # make sure the command works properly, need to change + # these to new test format when beef up all tests for + # volume tye commands. + def test_encryption_type(self): + name = uuid.uuid4().hex + encryption_type = uuid.uuid4().hex + # test create new encryption type + cmd_output = json.loads(self.openstack( + 'volume type create -f json ' + '--encryption-provider LuksEncryptor ' + '--encryption-cipher aes-xts-plain64 ' + '--encryption-key-size 128 ' + '--encryption-control-location front-end ' + + encryption_type)) + expected = {'provider': 'LuksEncryptor', + 'cipher': 'aes-xts-plain64', + 'key_size': 128, + 'control_location': 'front-end'} + for attr, value in expected.items(): + self.assertEqual(value, cmd_output['encryption'][attr]) + # test show encryption type + cmd_output = json.loads(self.openstack( + 'volume type show -f json --encryption-type ' + encryption_type)) + expected = {'provider': 'LuksEncryptor', + 'cipher': 'aes-xts-plain64', + 'key_size': 128, + 'control_location': 'front-end'} + for attr, value in expected.items(): + self.assertEqual(value, cmd_output['encryption'][attr]) + # test list encryption type + cmd_output = json.loads(self.openstack( + 'volume type list -f json --encryption-type')) + encryption_output = [t['Encryption'] for t in cmd_output + if t['Name'] == encryption_type][0] + expected = {'provider': 'LuksEncryptor', + 'cipher': 'aes-xts-plain64', + 'key_size': 128, + 'control_location': 'front-end'} + for attr, value in expected.items(): + self.assertEqual(value, encryption_output[attr]) + # test set existing encryption type + raw_output = self.openstack( + 'volume type set ' + '--encryption-key-size 256 ' + '--encryption-control-location back-end ' + + encryption_type) + self.assertEqual('', raw_output) + cmd_output = json.loads(self.openstack( + 'volume type show -f json --encryption-type ' + encryption_type)) + expected = {'provider': 'LuksEncryptor', + 'cipher': 'aes-xts-plain64', + 'key_size': 256, + 'control_location': 'back-end'} + for attr, value in expected.items(): + self.assertEqual(value, cmd_output['encryption'][attr]) + # test set new encryption type + cmd_output = json.loads(self.openstack( + 'volume type create -f json --private ' + + name, + )) + self.addCleanup( + self.openstack, + 'volume type delete ' + name, + ) + self.assertEqual(name, cmd_output['name']) + + raw_output = self.openstack( + 'volume type set ' + '--encryption-provider LuksEncryptor ' + '--encryption-cipher aes-xts-plain64 ' + '--encryption-key-size 128 ' + '--encryption-control-location front-end ' + + name) + self.assertEqual('', raw_output) + + cmd_output = json.loads(self.openstack( + 'volume type show -f json --encryption-type ' + name + )) + expected = {'provider': 'LuksEncryptor', + 'cipher': 'aes-xts-plain64', + 'key_size': 128, + 'control_location': 'front-end'} + for attr, value in expected.items(): + self.assertEqual(value, cmd_output['encryption'][attr]) + # test unset encryption type + raw_output = self.openstack( + 'volume type unset --encryption-type ' + name + ) + self.assertEqual('', raw_output) + cmd_output = json.loads(self.openstack( + 'volume type show -f json --encryption-type ' + name + )) + self.assertEqual({}, cmd_output['encryption']) + # test delete encryption type + raw_output = self.openstack('volume type delete ' + encryption_type) + self.assertEqual('', raw_output) From 874a726f522dae3a08832f32230e2590a787d45c Mon Sep 17 00:00:00 2001 From: zhangbailin Date: Wed, 31 Jul 2019 12:24:04 +0800 Subject: [PATCH 2140/3095] Microversion 2.79: Add delete_on_termination to volume-attach API Added ``--disable-delete-on-termination`` and ``--enable-delete-on-termination`` options to the ``openstack server add volume`` command that enables users to mark whether to delete the attached volume when the server is destroyed. Depends-On: https://review.opendev.org/#/c/681267/ Part of blueprint support-delete-on-termination-in-server-attach-volume Change-Id: I6b5cd54b82a1135335a71b9768a1a2c2012f755b --- lower-constraints.txt | 2 +- openstackclient/compute/v2/server.py | 41 ++++- .../tests/unit/compute/v2/test_server.py | 172 ++++++++++++++++++ ...store-shelved-server-9179045f04815bbb.yaml | 9 + requirements.txt | 2 +- 5 files changed, 223 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/bp-support-specifying-az-when-restore-shelved-server-9179045f04815bbb.yaml diff --git a/lower-constraints.txt b/lower-constraints.txt index 201726ac65..ad911e771d 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -100,7 +100,7 @@ python-mimeparse==1.6.0 python-mistralclient==3.1.0 python-muranoclient==0.8.2 python-neutronclient==6.7.0 -python-novaclient==15.0.0 +python-novaclient==15.1.0 python-octaviaclient==1.11.0 python-rsdclient==1.0.1 python-saharaclient==1.4.0 diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index fb7596a9fd..414abb626a 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -446,6 +446,21 @@ def get_parser(self, prog_name): metavar='', help=_('Server internal device name for volume'), ) + termination_group = parser.add_mutually_exclusive_group() + termination_group.add_argument( + '--enable-delete-on-termination', + action='store_true', + help=_("Specify if the attached volume should be deleted when " + "the server is destroyed. (Supported with " + "``--os-compute-api-version`` 2.79 or greater.)"), + ) + termination_group.add_argument( + '--disable-delete-on-termination', + action='store_true', + help=_("Specify if the attached volume should not be deleted " + "when the server is destroyed. (Supported with " + "``--os-compute-api-version`` 2.79 or greater.)"), + ) return parser def take_action(self, parsed_args): @@ -461,10 +476,34 @@ def take_action(self, parsed_args): parsed_args.volume, ) + support_set_delete_on_termination = (compute_client.api_version >= + api_versions.APIVersion('2.79')) + + if not support_set_delete_on_termination: + if parsed_args.enable_delete_on_termination: + msg = _('--os-compute-api-version 2.79 or greater ' + 'is required to support the ' + '--enable-delete-on-termination option.') + raise exceptions.CommandError(msg) + if parsed_args.disable_delete_on_termination: + msg = _('--os-compute-api-version 2.79 or greater ' + 'is required to support the ' + '--disable-delete-on-termination option.') + raise exceptions.CommandError(msg) + + kwargs = { + "device": parsed_args.device + } + + if parsed_args.enable_delete_on_termination: + kwargs['delete_on_termination'] = True + if parsed_args.disable_delete_on_termination: + kwargs['delete_on_termination'] = False + compute_client.volumes.create_server_volume( server.id, volume.id, - parsed_args.device, + **kwargs ) diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 5c98188a76..c70d6d72d0 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -43,6 +43,10 @@ def setUp(self): self.servers_mock = self.app.client_manager.compute.servers self.servers_mock.reset_mock() + # Get a shortcut to the compute client volumeManager Mock + self.servers_volumes_mock = self.app.client_manager.compute.volumes + self.servers_volumes_mock.reset_mock() + # Get a shortcut to the compute client FlavorManager Mock self.flavors_mock = self.app.client_manager.compute.flavors self.flavors_mock.reset_mock() @@ -457,6 +461,174 @@ def test_server_add_port_no_neutron(self): self.find_port.assert_not_called() +class TestServerVolume(TestServer): + + def setUp(self): + super(TestServerVolume, self).setUp() + + self.volume = volume_fakes.FakeVolume.create_one_volume() + self.volumes_mock.get.return_value = self.volume + + self.methods = { + 'create_server_volume': None, + } + + # Get the command object to test + self.cmd = server.AddServerVolume(self.app, None) + + def test_server_add_volume(self): + servers = self.setup_servers_mock(count=1) + arglist = [ + '--device', '/dev/sdb', + servers[0].id, + self.volume.id, + ] + verifylist = [ + ('server', servers[0].id), + ('volume', self.volume.id), + ('device', '/dev/sdb'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.servers_volumes_mock.create_server_volume.assert_called_once_with( + servers[0].id, self.volume.id, device='/dev/sdb') + self.assertIsNone(result) + + +class TestServerVolumeV279(TestServerVolume): + + def test_server_add_volume_with_enable_delete_on_termination(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.79') + + servers = self.setup_servers_mock(count=1) + arglist = [ + '--enable-delete-on-termination', + '--device', '/dev/sdb', + servers[0].id, + self.volume.id, + ] + + verifylist = [ + ('server', servers[0].id), + ('volume', self.volume.id), + ('device', '/dev/sdb'), + ('enable_delete_on_termination', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.servers_volumes_mock.create_server_volume.assert_called_once_with( + servers[0].id, self.volume.id, + device='/dev/sdb', delete_on_termination=True) + self.assertIsNone(result) + + def test_server_add_volume_with_disable_delete_on_termination(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.79') + + servers = self.setup_servers_mock(count=1) + arglist = [ + '--disable-delete-on-termination', + '--device', '/dev/sdb', + servers[0].id, + self.volume.id, + ] + + verifylist = [ + ('server', servers[0].id), + ('volume', self.volume.id), + ('device', '/dev/sdb'), + ('disable_delete_on_termination', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.servers_volumes_mock.create_server_volume.assert_called_once_with( + servers[0].id, self.volume.id, + device='/dev/sdb', delete_on_termination=False) + self.assertIsNone(result) + + def test_server_add_volume_with_enable_delete_on_termination_pre_v279( + self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.78') + + servers = self.setup_servers_mock(count=1) + arglist = [ + servers[0].id, + self.volume.id, + '--enable-delete-on-termination', + ] + verifylist = [ + ('server', servers[0].id), + ('volume', self.volume.id), + ('enable_delete_on_termination', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises(exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn('--os-compute-api-version 2.79 or greater is required', + str(ex)) + + def test_server_add_volume_with_disable_delete_on_termination_pre_v279( + self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.78') + + servers = self.setup_servers_mock(count=1) + arglist = [ + servers[0].id, + self.volume.id, + '--disable-delete-on-termination', + ] + verifylist = [ + ('server', servers[0].id), + ('volume', self.volume.id), + ('disable_delete_on_termination', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises(exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn('--os-compute-api-version 2.79 or greater is required', + str(ex)) + + def test_server_add_volume_with_disable_and_enable_delete_on_termination( + self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.79') + + servers = self.setup_servers_mock(count=1) + arglist = [ + '--enable-delete-on-termination', + '--disable-delete-on-termination', + '--device', '/dev/sdb', + servers[0].id, + self.volume.id, + ] + + verifylist = [ + ('server', servers[0].id), + ('volume', self.volume.id), + ('device', '/dev/sdb'), + ('enable_delete_on_termination', True), + ('disable_delete_on_termination', True), + ] + ex = self.assertRaises(utils.ParserException, + self.check_parser, + self.cmd, arglist, verifylist) + self.assertIn('Argument parse failed', str(ex)) + + class TestServerAddNetwork(TestServer): def setUp(self): diff --git a/releasenotes/notes/bp-support-specifying-az-when-restore-shelved-server-9179045f04815bbb.yaml b/releasenotes/notes/bp-support-specifying-az-when-restore-shelved-server-9179045f04815bbb.yaml new file mode 100644 index 0000000000..ec207eac0c --- /dev/null +++ b/releasenotes/notes/bp-support-specifying-az-when-restore-shelved-server-9179045f04815bbb.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + Add ``--disable-delete-on-termination`` and + ``--enable-delete-on-termination`` options to the ``server add volume`` + command to indicate when to delete the attached volume when the server + is deleted. + Note that it requires ``--os-compute-api-version 2.79`` or greater. + [Blueprint support-specifying-az-when-restore-shelved-server ``_] diff --git a/requirements.txt b/requirements.txt index 67139bbdbc..eea51e5163 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,5 +13,5 @@ oslo.i18n>=3.15.3 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 python-glanceclient>=2.8.0 # Apache-2.0 python-keystoneclient>=3.17.0 # Apache-2.0 -python-novaclient>=15.0.0 # Apache-2.0 +python-novaclient>=15.1.0 # Apache-2.0 python-cinderclient>=3.3.0 # Apache-2.0 From 5b3a827a1ff80e4b51c7ede44b84bf640d5b6380 Mon Sep 17 00:00:00 2001 From: Matt Riedemann Date: Wed, 18 Sep 2019 11:58:12 -0400 Subject: [PATCH 2141/3095] Provide stderr in exception when check_parser fails For negative tests that are asserting an argparse failure it would be useful to assert the specific reason for the failure in the test rather than just getting an exception, especially to avoid false positives in the tests when what is being tested and failing isn't the actual expected reason for the failure. This wraps the check_parser code that parses the args and mocks sys.stderr so we can trap that output and put it in the exception message that gets raised to the test. As a result, we can tighten up a test that was passing before for the wrong reason [1]. [1] https://review.opendev.org/#/c/673725/12/openstackclient/tests/unit/compute/v2/test_server.py@605 Change-Id: I0f1dc1215bdfb3eba98ccaf66a0041d220b93812 --- openstackclient/tests/unit/compute/v2/test_server.py | 3 ++- openstackclient/tests/unit/utils.py | 12 ++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index c70d6d72d0..c2bac2771f 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -626,7 +626,8 @@ def test_server_add_volume_with_disable_and_enable_delete_on_termination( ex = self.assertRaises(utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) - self.assertIn('Argument parse failed', str(ex)) + self.assertIn('argument --disable-delete-on-termination: not allowed ' + 'with argument --enable-delete-on-termination', str(ex)) class TestServerAddNetwork(TestServer): diff --git a/openstackclient/tests/unit/utils.py b/openstackclient/tests/unit/utils.py index c15d8bbfe2..8df81a50eb 100644 --- a/openstackclient/tests/unit/utils.py +++ b/openstackclient/tests/unit/utils.py @@ -17,6 +17,7 @@ import os import fixtures +from six.moves import StringIO import testtools from cliff import columns as cliff_columns @@ -72,10 +73,13 @@ def setUp(self): def check_parser(self, cmd, args, verify_args): cmd_parser = cmd.get_parser('check_parser') - try: - parsed_args = cmd_parser.parse_args(args) - except SystemExit: - raise ParserException("Argument parse failed") + stderr = StringIO() + with fixtures.MonkeyPatch('sys.stderr', stderr): + try: + parsed_args = cmd_parser.parse_args(args) + except SystemExit: + raise ParserException("Argument parse failed: %s" % + stderr.getvalue()) for av in verify_args: attr, value = av if attr: From 509ca3ed36b4ef512a47ff8d39c9df751084015a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89douard=20Thuleau?= Date: Wed, 4 Dec 2019 08:21:50 +0100 Subject: [PATCH 2142/3095] Fix router create/show if extraroute not supported If neutron does not support extraroute l3 extension, the route column formatter fails. Change-Id: I7b89c4f818865073947e0850e86c18d0d2415a51 --- openstackclient/network/v2/router.py | 2 +- .../tests/unit/network/v2/test_router.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 02da50c38e..5d85e485a0 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -49,7 +49,7 @@ def human_readable(self): class RoutesColumn(cliff_columns.FormattableColumn): def human_readable(self): # Map the route keys to match --route option. - for route in self._value: + for route in self._value or []: if 'nexthop' in route: route['gateway'] = route.pop('nexthop') return utils.format_list_of_dicts(self._value) diff --git a/openstackclient/tests/unit/network/v2/test_router.py b/openstackclient/tests/unit/network/v2/test_router.py index 079b9746ae..500cfbe5a3 100644 --- a/openstackclient/tests/unit/network/v2/test_router.py +++ b/openstackclient/tests/unit/network/v2/test_router.py @@ -1285,6 +1285,24 @@ def test_show_no_ha_no_distributed(self): self.assertNotIn("is_distributed", columns) self.assertNotIn("is_ha", columns) + def test_show_no_extra_route_extension(self): + _router = network_fakes.FakeRouter.create_one_router({'routes': None}) + + arglist = [ + _router.name, + ] + verifylist = [ + ('router', _router.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + with mock.patch.object( + self.network, "find_router", return_value=_router): + columns, data = self.cmd.take_action(parsed_args) + + self.assertIn("routes", columns) + self.assertIsNone(list(data)[columns.index('routes')].human_readable()) + class TestUnsetRouter(TestRouter): From 924627678d9bb1e9cdf84b524fb69dd37f4345ec Mon Sep 17 00:00:00 2001 From: Daniel Bengtsson Date: Thu, 21 Nov 2019 14:20:42 +0100 Subject: [PATCH 2143/3095] Stop testing python 2 in tox and zuul. Remove python 2 from envlist parameter. Check the link: https://etherpad.openstack.org/p/drop-python2-support The plan is drop the python 2 support from OpenStack in Ussuri release. Remove the zuul jobs. Note that the (non-voting) openstackclient-check-plugins job is still running under py2 at this time. That will need to be fixed in the python/openstackclient repository where the job is defined. Change-Id: I3148db053b9ef0fcf7dc88e5cc075d974c93d819 --- .zuul.yaml | 29 +---------------------------- tox.ini | 13 ++++++------- 2 files changed, 7 insertions(+), 35 deletions(-) diff --git a/.zuul.yaml b/.zuul.yaml index 574734659e..15f719c356 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -5,28 +5,6 @@ Run unit tests for OpenStackClient with master branch of important libs. Takes advantage of the base tox job's install-siblings feature. - required-projects: - - openstack/cliff - - openstack/keystoneauth - - openstack/openstacksdk - - openstack/os-client-config - - openstack/osc-lib - - openstack/python-openstackclient - vars: - tox_envlist: py27 - # Set work dir to openstackclient so that if it's triggered by one of the - # other repos the tests will run in the same place - zuul_work_dir: src/opendev.org/openstack/python-openstackclient - -- job: - name: osc-tox-py27-tips - parent: openstack-tox-py27 - description: | - Run unit tests for OpenStackClient with master branch of important libs. - - Takes advantage of the base tox job's install-siblings feature. - # The job only tests the latest and shouldn't be run on the stable branches - branches: ^(?!stable) required-projects: - openstack/cliff - openstack/keystoneauth @@ -154,7 +132,6 @@ - openstack/python-openstackclient vars: devstack_localrc: - USE_PYTHON3: true LIBS_FROM_GIT: python-openstackclient,openstacksdk,osc-lib,os-client-config # This is insufficient, but leaving it here as a reminder of what may # someday be all we need to make this work @@ -179,11 +156,9 @@ name: osc-tox-unit-tips check: jobs: - - osc-tox-py27-tips - osc-tox-py36-tips gate: jobs: - - osc-tox-py27-tips - osc-tox-py36-tips - project: @@ -192,12 +167,10 @@ - osc-tox-unit-tips - openstack-cover-jobs - openstack-lower-constraints-jobs - - openstack-python-jobs - - openstack-python3-train-jobs + - openstack-python3-ussuri-jobs - publish-openstack-docs-pti - check-requirements - release-notes-jobs-python3 - - lib-forward-testing - lib-forward-testing-python3 check: jobs: diff --git a/tox.ini b/tox.ini index addafa2bfe..f15529cdc6 100644 --- a/tox.ini +++ b/tox.ini @@ -1,10 +1,15 @@ [tox] minversion = 2.3 -envlist = py27,py37,pep8 +envlist = py37,pep8 skipdist = True +# Automatic envs (pyXX) will only use the python version appropriate to that +# env and ignore basepython inherited from [testenv] if we set +# ignore_basepython_conflict. +ignore_basepython_conflict = True [testenv] usedevelop = True +basepython = python3 install_command = pip install {opts} {packages} setenv = VIRTUAL_ENV={envdir} OS_STDOUT_CAPTURE=1 @@ -24,7 +29,6 @@ commands = {toxinidir}/tools/fast8.sh [testenv:pep8] -basepython = python3 commands = flake8 bandit -r openstackclient -x tests -s B105,B106,B107,B401,B404,B603,B606,B607,B110,B605,B101 @@ -86,7 +90,6 @@ commands = stestr run {posargs} [testenv:venv] -basepython = python3 deps = -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt @@ -94,7 +97,6 @@ deps = commands = {posargs} [testenv:cover] -basepython = python3 setenv = VIRTUAL_ENV={envdir} PYTHON=coverage run --source openstackclient --parallel-mode @@ -110,7 +112,6 @@ commands = oslo_debug_helper -t openstackclient/tests {posargs} [testenv:docs] -basepython = python3 deps = -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt @@ -122,7 +123,6 @@ commands = whereto doc/build/html/.htaccess doc/test/redirect-tests.txt [testenv:releasenotes] -basepython = python3 deps = -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/doc/requirements.txt @@ -140,7 +140,6 @@ import-order-style = pep8 application_import_names = openstackclient [testenv:lower-constraints] -basepython = python3 deps = -c{toxinidir}/lower-constraints.txt -r{toxinidir}/test-requirements.txt From 69870ae439f18863979949f5728543a0fb1cbe83 Mon Sep 17 00:00:00 2001 From: Bram Verschueren Date: Fri, 13 Dec 2019 11:13:54 +0100 Subject: [PATCH 2144/3095] Fix faulthy state argument choice The correct state name for a failing volume snapshot deletion is 'error_deleting' instead of 'error-deleting'. [1] [1] https://opendev.org/openstack/cinder/src/commit/89d6a5042fcb2ede5a0b1112d72fae805ea52fcd/cinder/objects/fields.py#L126 Task: #37844 Story: #2007037 Change-Id: Ia99900ece4f1cd29769b22ddaa3965789d719556 --- .../cli/command-objects/volume-snapshot.rst | 2 +- .../volume/v2/test_volume_snapshot.py | 18 ++++++++++++++++++ openstackclient/volume/v1/volume_snapshot.py | 4 ++-- .../volume/v2/consistency_group_snapshot.py | 2 +- openstackclient/volume/v2/volume_snapshot.py | 6 +++--- 5 files changed, 25 insertions(+), 7 deletions(-) diff --git a/doc/source/cli/command-objects/volume-snapshot.rst b/doc/source/cli/command-objects/volume-snapshot.rst index 30cc77cc57..21a8937018 100644 --- a/doc/source/cli/command-objects/volume-snapshot.rst +++ b/doc/source/cli/command-objects/volume-snapshot.rst @@ -115,7 +115,7 @@ List volume snapshots .. option:: --status Filters results by a status. - ('available', 'error', 'creating', 'deleting' or 'error-deleting') + ('available', 'error', 'creating', 'deleting' or 'error_deleting') .. option:: --name diff --git a/openstackclient/tests/functional/volume/v2/test_volume_snapshot.py b/openstackclient/tests/functional/volume/v2/test_volume_snapshot.py index 8d32d99731..4977a73ebc 100644 --- a/openstackclient/tests/functional/volume/v2/test_volume_snapshot.py +++ b/openstackclient/tests/functional/volume/v2/test_volume_snapshot.py @@ -121,6 +121,24 @@ def test_volume_snapshot_list(self): cmd_output["size"], ) self.wait_for_status('volume snapshot', name2, 'available') + + raw_output = self.openstack( + 'volume snapshot set ' + + '--state error_deleting ' + + name2 + ) + self.assertOutput('', raw_output) + + # Test list --long, --status + cmd_output = json.loads(self.openstack( + 'volume snapshot list -f json ' + + '--long ' + + '--status error_deleting' + )) + names = [x["Name"] for x in cmd_output] + self.assertNotIn(name1, names) + self.assertIn(name2, names) + raw_output = self.openstack( 'volume snapshot set ' + '--state error ' + diff --git a/openstackclient/volume/v1/volume_snapshot.py b/openstackclient/volume/v1/volume_snapshot.py index 50f81771a0..bfe249f526 100644 --- a/openstackclient/volume/v1/volume_snapshot.py +++ b/openstackclient/volume/v1/volume_snapshot.py @@ -175,10 +175,10 @@ def get_parser(self, prog_name): '--status', metavar='', choices=['available', 'error', 'creating', 'deleting', - 'error-deleting'], + 'error_deleting'], help=_("Filters results by a status. " "('available', 'error', 'creating', 'deleting'" - " or 'error-deleting')") + " or 'error_deleting')") ) parser.add_argument( '--volume', diff --git a/openstackclient/volume/v2/consistency_group_snapshot.py b/openstackclient/volume/v2/consistency_group_snapshot.py index 540deb0187..3cba4ecaa9 100644 --- a/openstackclient/volume/v2/consistency_group_snapshot.py +++ b/openstackclient/volume/v2/consistency_group_snapshot.py @@ -129,7 +129,7 @@ def get_parser(self, prog_name): '--status', metavar="", choices=['available', 'error', 'creating', 'deleting', - 'error-deleting'], + 'error_deleting'], help=_('Filters results by a status ("available", "error", ' '"creating", "deleting" or "error_deleting")') ) diff --git a/openstackclient/volume/v2/volume_snapshot.py b/openstackclient/volume/v2/volume_snapshot.py index 2b26ae323b..6cbd1156ad 100644 --- a/openstackclient/volume/v2/volume_snapshot.py +++ b/openstackclient/volume/v2/volume_snapshot.py @@ -230,10 +230,10 @@ def get_parser(self, prog_name): '--status', metavar='', choices=['available', 'error', 'creating', 'deleting', - 'error-deleting'], + 'error_deleting'], help=_("Filters results by a status. " "('available', 'error', 'creating', 'deleting'" - " or 'error-deleting')") + " or 'error_deleting')") ) parser.add_argument( '--volume', @@ -345,7 +345,7 @@ def get_parser(self, prog_name): '--state', metavar='', choices=['available', 'error', 'creating', 'deleting', - 'error-deleting'], + 'error_deleting'], help=_('New snapshot state. ("available", "error", "creating", ' '"deleting", or "error_deleting") (admin only) ' '(This option simply changes the state of the snapshot ' From f5384ae16a24cdd54149fa21827d14b8b8983d4f Mon Sep 17 00:00:00 2001 From: KeithMnemonic Date: Thu, 24 Oct 2019 14:39:50 -0400 Subject: [PATCH 2145/3095] Fix openstack server list --deleted --marker option This patch removes using the "name" option for a marker when --deleted is also used. The find_resource() function that is being called does not correctly handle using the marker as the "name" in the search when also using deleted=True. One simple way to fix this is force the marker to only be an ID when --deleted is used. This is how the nova client works. Using the --deleted option is available to users with the admin role by default. If you're an admin listing --deleted servers with a marker by name, find_resource() is going to fail to find it since it doesn't apply the --deleted filter to find_resource(). The find_resource() function is trying to find the marker server by name if it's not found by id, and to find it by name it's listing servers with the given marker as the name, but not applying the --deleted filter so it doesn't get back any results. In the story it was suggested modifying find_resource to include the deleted query param when it's specified on the command line but that didn't work because it still results in something like this: http://192.168.1.123/compute/v2.1/servers?deleted=True&name=4cecd49f-bc25-4a7e-826e-4aea6f9267d9 It seems like there are bugs in find_resource(). Restricting the marker to be the server ID when listing deleted servers is probably OK since if you're using --deleted you're an admin and you could be listing across all projects and if you're filtering by a server across all projects anyway (not that you have to, I'm just saying if you are), or even showing a server in another project, you have to do it by id rather than name because find_resource() won't find the server in another project by name, only ID. story: 2006761 Task: 37258 Change-Id: Ib878982b1d469212ca3483dcfaf407a8e1d2b417 --- openstackclient/compute/v2/server.py | 15 +++++-- .../functional/compute/v2/test_server.py | 43 +++++++++++++++++++ .../notes/bug-2006761-9041d1b25e845cfb.yaml | 8 ++++ 3 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/bug-2006761-9041d1b25e845cfb.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 414abb626a..84061a53c9 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1242,7 +1242,8 @@ def get_parser(self, prog_name): default=None, help=_('The last server of the previous page. Display ' 'list of servers after marker. Display all servers if not ' - 'specified. (name or ID)') + 'specified. When used with ``--deleted``, the marker must ' + 'be an ID, otherwise a name or ID can be used.'), ) parser.add_argument( '--limit', @@ -1450,9 +1451,17 @@ def take_action(self, parsed_args): mixed_case_fields = [] marker_id = None + if parsed_args.marker: - marker_id = utils.find_resource(compute_client.servers, - parsed_args.marker).id + # Check if both "--marker" and "--deleted" are used. + # In that scenario a lookup is not needed as the marker + # needs to be an ID, because find_resource does not + # handle deleted resources + if parsed_args.deleted: + marker_id = parsed_args.marker + else: + marker_id = utils.find_resource(compute_client.servers, + parsed_args.marker).id data = compute_client.servers.list(search_opts=search_opts, marker=marker_id, diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py index 2bca70d0ec..6e080e9ba2 100644 --- a/openstackclient/tests/functional/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -63,6 +63,49 @@ def test_server_list(self): self.assertNotIn(name1, col_name) self.assertIn(name2, col_name) + def test_server_list_with_marker_and_deleted(self): + """Test server list with deleted and marker""" + cmd_output = self.server_create(cleanup=False) + name1 = cmd_output['name'] + cmd_output = self.server_create(cleanup=False) + name2 = cmd_output['name'] + id2 = cmd_output['id'] + self.wait_for_status(name1, "ACTIVE") + self.wait_for_status(name2, "ACTIVE") + + # Test list --marker with ID + cmd_output = json.loads(self.openstack( + 'server list -f json --marker ' + id2 + )) + col_name = [x["Name"] for x in cmd_output] + self.assertIn(name1, col_name) + + # Test list --marker with Name + cmd_output = json.loads(self.openstack( + 'server list -f json --marker ' + name2 + )) + col_name = [x["Name"] for x in cmd_output] + self.assertIn(name1, col_name) + + self.openstack('server delete --wait ' + name1) + self.openstack('server delete --wait ' + name2) + + # Test list --deleted --marker with ID + cmd_output = json.loads(self.openstack( + 'server list -f json --deleted --marker ' + id2 + )) + col_name = [x["Name"] for x in cmd_output] + self.assertIn(name1, col_name) + + # Test list --deleted --marker with Name + try: + cmd_output = json.loads(self.openstack( + 'server list -f json --deleted --marker ' + name2 + )) + except exceptions.CommandFailed as e: + self.assertIn('marker [%s] not found (HTTP 400)' % (name2), + e.stderr.decode('utf-8')) + def test_server_list_with_changes_before(self): """Test server list. diff --git a/releasenotes/notes/bug-2006761-9041d1b25e845cfb.yaml b/releasenotes/notes/bug-2006761-9041d1b25e845cfb.yaml new file mode 100644 index 0000000000..e647cf2114 --- /dev/null +++ b/releasenotes/notes/bug-2006761-9041d1b25e845cfb.yaml @@ -0,0 +1,8 @@ +--- +fixes: + - | + Fixes the "No server with a name or ID of 'id' exists" error when running + ``server list --deleted --marker``. The fix removes using a name for + the marker when both ``--deleted`` and ``--marker`` are used. In + this scenario an ID must be supplied for the marker. + [Story `2006761 `_] From 32080f7a428f7a56363e43563d0d316b04219f43 Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Tue, 7 Jan 2020 09:22:30 -0500 Subject: [PATCH 2146/3095] Bump tox minversion ignore_basepython_conflict was introduced in tox 3.1.0. Change-Id: I7a6049b6a4fd3ee376a3478e94837c0afe89d4df --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index f15529cdc6..3a7ed09794 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -minversion = 2.3 +minversion = 3.1 envlist = py37,pep8 skipdist = True # Automatic envs (pyXX) will only use the python version appropriate to that From 780d9b49a0f9a1bef5007e3e75275a1d48a96b13 Mon Sep 17 00:00:00 2001 From: Alex Katz Date: Sun, 5 Jan 2020 14:08:04 +0200 Subject: [PATCH 2147/3095] Show correct name for resource with quota set to zero In case quota for the resource is set to zero "openstack quota show" command will not map the resource name according to one of the following dicts: - COMPUTE_QUOTAS - NOVA_NETWORK_QUOTAS - VOLUME_QUOTAS - NETWORK_QUOTAS For example: $ openstack quota set --secgroups 10 admin $ openstack quota show admin -f json|egrep "(secgroups|security_groups)" "secgroups": 10, $ openstack quota set --secgroups 0 admin $ openstack quota show admin -f json|egrep "(secgroups|security_groups)" "security_groups": 0, Change-Id: I94ed9e6b41b1cc692297c01e6c7582998dcacfda --- openstackclient/common/quota.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index 80c8749ac1..ef21e69624 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -650,7 +650,7 @@ def take_action(self, parsed_args): for k, v in itertools.chain( COMPUTE_QUOTAS.items(), NOVA_NETWORK_QUOTAS.items(), VOLUME_QUOTAS.items(), NETWORK_QUOTAS.items()): - if not k == v and info.get(k): + if not k == v and info.get(k) is not None: info[v] = info[k] info.pop(k) From d15bbada73f81136c966007d9c564dd6cfb2fd9c Mon Sep 17 00:00:00 2001 From: lihaijing Date: Fri, 7 Jul 2017 11:48:48 +0800 Subject: [PATCH 2148/3095] Replace six.iteritems() with .items() 1. As mentioned in [1], we should avoid using six.iteritems to achieve iterators. We can use dict.items instead, as it will return iterators in PY3 as well. And dict.items/keys will more readable. 2. In py2, the performance about list should be negligible, see the link [2]. [1] https://wiki.openstack.org/wiki/Python3 [2] http://lists.openstack.org/pipermail/openstack-dev/2015-June/066391.html Co-Authored-By: Akihiro Motoki Change-Id: I4b9edb326444264c0f6c4ad281acaac356a07e85 Implements: blueprint replace-iteritems-with-items --- HACKING.rst | 3 +-- openstackclient/api/object_store_v1.py | 5 ++--- openstackclient/common/availability_zone.py | 5 ++--- openstackclient/common/configuration.py | 5 ++--- openstackclient/common/module.py | 3 +-- openstackclient/common/quota.py | 3 +-- openstackclient/compute/v2/agent.py | 3 +-- openstackclient/compute/v2/aggregate.py | 9 ++++----- openstackclient/compute/v2/console.py | 3 +-- openstackclient/compute/v2/flavor.py | 5 ++--- openstackclient/compute/v2/hypervisor.py | 3 +-- openstackclient/compute/v2/hypervisor_stats.py | 3 +-- openstackclient/compute/v2/keypair.py | 5 ++--- openstackclient/compute/v2/server.py | 6 +++--- openstackclient/compute/v2/server_backup.py | 3 +-- openstackclient/compute/v2/server_event.py | 3 +-- openstackclient/compute/v2/server_image.py | 3 +-- openstackclient/compute/v2/usage.py | 3 +-- openstackclient/identity/v2_0/catalog.py | 3 +-- openstackclient/identity/v2_0/ec2creds.py | 5 ++--- openstackclient/identity/v2_0/endpoint.py | 5 ++--- openstackclient/identity/v2_0/project.py | 5 ++--- openstackclient/identity/v2_0/role.py | 7 +++---- openstackclient/identity/v2_0/service.py | 9 ++++----- openstackclient/identity/v2_0/token.py | 3 +-- openstackclient/identity/v2_0/user.py | 5 ++--- openstackclient/identity/v3/application_credential.py | 5 ++--- openstackclient/identity/v3/catalog.py | 3 +-- openstackclient/identity/v3/consumer.py | 5 ++--- openstackclient/identity/v3/credential.py | 5 ++--- openstackclient/identity/v3/domain.py | 5 ++--- openstackclient/identity/v3/ec2creds.py | 5 ++--- openstackclient/identity/v3/endpoint.py | 5 ++--- openstackclient/identity/v3/endpoint_group.py | 5 ++--- openstackclient/identity/v3/federation_protocol.py | 7 +++---- openstackclient/identity/v3/group.py | 5 ++--- openstackclient/identity/v3/identity_provider.py | 5 ++--- openstackclient/identity/v3/implied_role.py | 3 +-- openstackclient/identity/v3/limit.py | 7 +++---- openstackclient/identity/v3/mapping.py | 5 ++--- openstackclient/identity/v3/policy.py | 5 ++--- openstackclient/identity/v3/project.py | 5 ++--- openstackclient/identity/v3/region.py | 5 ++--- openstackclient/identity/v3/registered_limit.py | 7 +++---- openstackclient/identity/v3/role.py | 5 ++--- openstackclient/identity/v3/service.py | 5 ++--- openstackclient/identity/v3/service_provider.py | 5 ++--- openstackclient/identity/v3/token.py | 9 ++++----- openstackclient/identity/v3/trust.py | 5 ++--- openstackclient/identity/v3/user.py | 5 ++--- openstackclient/image/v1/image.py | 5 ++--- openstackclient/image/v2/image.py | 10 +++++----- openstackclient/network/sdk_utils.py | 4 +--- openstackclient/network/v2/network_segment_range.py | 4 ++-- openstackclient/network/v2/security_group.py | 3 +-- openstackclient/network/v2/security_group_rule.py | 3 +-- openstackclient/object/v1/account.py | 3 +-- openstackclient/object/v1/container.py | 3 +-- openstackclient/object/v1/object.py | 3 +-- .../tests/unit/common/test_availability_zone.py | 6 ++---- openstackclient/tests/unit/fakes.py | 4 ++-- openstackclient/volume/v1/qos_specs.py | 5 ++--- openstackclient/volume/v1/volume.py | 5 ++--- openstackclient/volume/v1/volume_backup.py | 5 ++--- openstackclient/volume/v1/volume_snapshot.py | 5 ++--- openstackclient/volume/v1/volume_transfer_request.py | 7 +++---- openstackclient/volume/v1/volume_type.py | 5 ++--- openstackclient/volume/v2/backup_record.py | 5 ++--- openstackclient/volume/v2/consistency_group.py | 5 ++--- .../volume/v2/consistency_group_snapshot.py | 5 ++--- openstackclient/volume/v2/qos_specs.py | 5 ++--- openstackclient/volume/v2/volume.py | 5 ++--- openstackclient/volume/v2/volume_backup.py | 7 +++---- openstackclient/volume/v2/volume_snapshot.py | 5 ++--- openstackclient/volume/v2/volume_transfer_request.py | 7 +++---- openstackclient/volume/v2/volume_type.py | 5 ++--- 76 files changed, 148 insertions(+), 222 deletions(-) diff --git a/HACKING.rst b/HACKING.rst index 61803e9a4d..52e96caa93 100644 --- a/HACKING.rst +++ b/HACKING.rst @@ -93,8 +93,7 @@ OpenStackClient strives to be Python 3.3 compatible. Common guidelines: * Convert print statements to functions: print statements should be converted to an appropriate log or other output mechanism. -* Use six where applicable: x.iteritems is converted to six.iteritems(x) - for example. +* Prefer to x.items() over six.iteritems(x). Running Tests ------------- diff --git a/openstackclient/api/object_store_v1.py b/openstackclient/api/object_store_v1.py index d1e5dfaf45..44ff7a01fb 100644 --- a/openstackclient/api/object_store_v1.py +++ b/openstackclient/api/object_store_v1.py @@ -19,7 +19,6 @@ import sys from osc_lib import utils -import six from six.moves import urllib from openstackclient.api import api @@ -559,7 +558,7 @@ def _set_properties(self, properties, header_tag): log = logging.getLogger(__name__ + '._set_properties') headers = {} - for k, v in six.iteritems(properties): + for k, v in properties.items(): if not utils.is_ascii(k) or not utils.is_ascii(v): log.error('Cannot set property %s to non-ascii value', k) continue @@ -572,7 +571,7 @@ def _get_properties(self, headers, header_tag): # Add in properties as a top level key, this is consistent with other # OSC commands properties = {} - for k, v in six.iteritems(headers): + for k, v in headers.items(): if k.lower().startswith(header_tag): properties[k[len(header_tag):]] = v return properties diff --git a/openstackclient/common/availability_zone.py b/openstackclient/common/availability_zone.py index b2385ef743..3b2fa848a0 100644 --- a/openstackclient/common/availability_zone.py +++ b/openstackclient/common/availability_zone.py @@ -19,7 +19,6 @@ from novaclient import exceptions as nova_exceptions from osc_lib.command import command from osc_lib import utils -import six from openstackclient.i18n import _ @@ -47,11 +46,11 @@ def _xform_compute_availability_zone(az, include_extra): return result if hasattr(az, 'hosts') and az.hosts: - for host, services in six.iteritems(az.hosts): + for host, services in az.hosts.items(): host_info = copy.deepcopy(zone_info) host_info['host_name'] = host - for svc, state in six.iteritems(services): + for svc, state in services.items(): info = copy.deepcopy(host_info) info['service_name'] = svc info['service_status'] = '%s %s %s' % ( diff --git a/openstackclient/common/configuration.py b/openstackclient/common/configuration.py index 53b30d5fdd..49ef0e05ca 100644 --- a/openstackclient/common/configuration.py +++ b/openstackclient/common/configuration.py @@ -15,7 +15,6 @@ from keystoneauth1.loading import base from osc_lib.command import command -import six from openstackclient.i18n import _ @@ -59,9 +58,9 @@ def take_action(self, parsed_args): if o.secret ] - for key, value in six.iteritems(info.pop('auth', {})): + for key, value in info.pop('auth', {}).items(): if parsed_args.mask and key.lower() in secret_opts: value = REDACTED info['auth.' + key] = value - return zip(*sorted(six.iteritems(info))) + return zip(*sorted(info.items())) diff --git a/openstackclient/common/module.py b/openstackclient/common/module.py index 20497f2131..f55fdce048 100644 --- a/openstackclient/common/module.py +++ b/openstackclient/common/module.py @@ -19,7 +19,6 @@ from osc_lib.command import command from osc_lib import utils -import six from openstackclient.i18n import _ @@ -113,4 +112,4 @@ def take_action(self, parsed_args): # Catch all exceptions, just skip it pass - return zip(*sorted(six.iteritems(data))) + return zip(*sorted(data.items())) diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index 80c8749ac1..c7b3ad0707 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -21,7 +21,6 @@ from osc_lib.command import command from osc_lib import utils -import six from openstackclient.i18n import _ from openstackclient.network import common @@ -663,4 +662,4 @@ def take_action(self, parsed_args): project_name = project_info['name'] info['project_name'] = project_name - return zip(*sorted(six.iteritems(info))) + return zip(*sorted(info.items())) diff --git a/openstackclient/compute/v2/agent.py b/openstackclient/compute/v2/agent.py index 151dcc1e82..3feb99ec83 100644 --- a/openstackclient/compute/v2/agent.py +++ b/openstackclient/compute/v2/agent.py @@ -20,7 +20,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ @@ -77,7 +76,7 @@ def take_action(self, parsed_args): parsed_args.hypervisor ) agent = compute_client.agents.create(*args)._info.copy() - return zip(*sorted(six.iteritems(agent))) + return zip(*sorted(agent.items())) class DeleteAgent(command.Command): diff --git a/openstackclient/compute/v2/aggregate.py b/openstackclient/compute/v2/aggregate.py index 3834de1f0b..599659a3e2 100644 --- a/openstackclient/compute/v2/aggregate.py +++ b/openstackclient/compute/v2/aggregate.py @@ -23,7 +23,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ @@ -68,7 +67,7 @@ def take_action(self, parsed_args): 'properties': format_columns.DictColumn(info.pop('metadata')), }, ) - return zip(*sorted(six.iteritems(info))) + return zip(*sorted(info.items())) class CreateAggregate(command.ShowOne): @@ -125,7 +124,7 @@ def take_action(self, parsed_args): 'properties': properties, }, ) - return zip(*sorted(six.iteritems(info))) + return zip(*sorted(info.items())) class DeleteAggregate(command.Command): @@ -255,7 +254,7 @@ def take_action(self, parsed_args): 'properties': format_columns.DictColumn(info.pop('metadata')), }, ) - return zip(*sorted(six.iteritems(info))) + return zip(*sorted(info.items())) class SetAggregate(command.Command): @@ -372,7 +371,7 @@ def take_action(self, parsed_args): info = {} info.update(data._info) - return zip(*sorted(six.iteritems(info))) + return zip(*sorted(info.items())) class UnsetAggregate(command.Command): diff --git a/openstackclient/compute/v2/console.py b/openstackclient/compute/v2/console.py index b2f7288f40..110b21b81e 100644 --- a/openstackclient/compute/v2/console.py +++ b/openstackclient/compute/v2/console.py @@ -18,7 +18,6 @@ from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import utils -import six from openstackclient.i18n import _ @@ -138,4 +137,4 @@ def take_action(self, parsed_args): # handle for different microversion API. console_data = data.get('remote_console', data.get('console')) info.update(console_data) - return zip(*sorted(six.iteritems(info))) + return zip(*sorted(info.items())) diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 4f1e48af06..42649db504 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -22,7 +22,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ from openstackclient.identity import common as identity_common @@ -195,7 +194,7 @@ def take_action(self, parsed_args): flavor_info.pop("links") flavor_info['properties'] = utils.format_dict(flavor.get_keys()) - return zip(*sorted(six.iteritems(flavor_info))) + return zip(*sorted(flavor_info.items())) class DeleteFlavor(command.Command): @@ -447,7 +446,7 @@ def take_action(self, parsed_args): flavor['properties'] = utils.format_dict(resource_flavor.get_keys()) - return zip(*sorted(six.iteritems(flavor))) + return zip(*sorted(flavor.items())) class UnsetFlavor(command.Command): diff --git a/openstackclient/compute/v2/hypervisor.py b/openstackclient/compute/v2/hypervisor.py index 0d367fee86..7f110028b6 100644 --- a/openstackclient/compute/v2/hypervisor.py +++ b/openstackclient/compute/v2/hypervisor.py @@ -20,7 +20,6 @@ from novaclient import exceptions as nova_exceptions from osc_lib.command import command from osc_lib import utils -import six from openstackclient.i18n import _ @@ -126,4 +125,4 @@ def take_action(self, parsed_args): hypervisor["service_host"] = hypervisor["service"]["host"] del hypervisor["service"] - return zip(*sorted(six.iteritems(hypervisor))) + return zip(*sorted(hypervisor.items())) diff --git a/openstackclient/compute/v2/hypervisor_stats.py b/openstackclient/compute/v2/hypervisor_stats.py index b0413005b6..4493e08018 100644 --- a/openstackclient/compute/v2/hypervisor_stats.py +++ b/openstackclient/compute/v2/hypervisor_stats.py @@ -15,7 +15,6 @@ """Hypervisor Stats action implementations""" from osc_lib.command import command -import six from openstackclient.i18n import _ @@ -27,4 +26,4 @@ def take_action(self, parsed_args): compute_client = self.app.client_manager.compute hypervisor_stats = compute_client.hypervisors.statistics().to_dict() - return zip(*sorted(six.iteritems(hypervisor_stats))) + return zip(*sorted(hypervisor_stats.items())) diff --git a/openstackclient/compute/v2/keypair.py b/openstackclient/compute/v2/keypair.py index 851cced0e9..2b365cebff 100644 --- a/openstackclient/compute/v2/keypair.py +++ b/openstackclient/compute/v2/keypair.py @@ -23,7 +23,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ @@ -101,7 +100,7 @@ def take_action(self, parsed_args): del info['public_key'] if 'private_key' in info: del info['private_key'] - return zip(*sorted(six.iteritems(info))) + return zip(*sorted(info.items())) else: sys.stdout.write(keypair.private_key) return ({}, {}) @@ -184,7 +183,7 @@ def take_action(self, parsed_args): info.update(keypair._info) if not parsed_args.public_key: del info['public_key'] - return zip(*sorted(six.iteritems(info))) + return zip(*sorted(info.items())) else: # NOTE(dtroyer): a way to get the public key in a similar form # as the private key in the create command diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 84061a53c9..b5c420fe87 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1069,7 +1069,7 @@ def _match_image(image_api, wanted_properties): raise SystemExit details = _prep_server_detail(compute_client, image_client, server) - return zip(*sorted(six.iteritems(details))) + return zip(*sorted(details.items())) class CreateServerDump(command.Command): @@ -1967,7 +1967,7 @@ def _show_progress(progress): details = _prep_server_detail(compute_client, image_client, server, refresh=False) - return zip(*sorted(six.iteritems(details))) + return zip(*sorted(details.items())) class RemoveFixedIP(command.Command): @@ -2537,7 +2537,7 @@ def take_action(self, parsed_args): self.app.client_manager.image, server, refresh=False) - return zip(*sorted(six.iteritems(data))) + return zip(*sorted(data.items())) class SshServer(command.Command): diff --git a/openstackclient/compute/v2/server_backup.py b/openstackclient/compute/v2/server_backup.py index a79f5f7039..1d560dc0c7 100644 --- a/openstackclient/compute/v2/server_backup.py +++ b/openstackclient/compute/v2/server_backup.py @@ -19,7 +19,6 @@ from osc_lib import exceptions from osc_lib import utils from oslo_utils import importutils -import six from openstackclient.i18n import _ @@ -129,4 +128,4 @@ def _show_progress(progress): ] ) info = image_module._format_image(image) - return zip(*sorted(six.iteritems(info))) + return zip(*sorted(info.items())) diff --git a/openstackclient/compute/v2/server_event.py b/openstackclient/compute/v2/server_event.py index 6d33d02d50..4fcc913614 100644 --- a/openstackclient/compute/v2/server_event.py +++ b/openstackclient/compute/v2/server_event.py @@ -19,7 +19,6 @@ from osc_lib.command import command from osc_lib import utils -import six from openstackclient.i18n import _ @@ -122,4 +121,4 @@ def take_action(self, parsed_args): action_detail = compute_client.instance_action.get( server_id, parsed_args.request_id) - return zip(*sorted(six.iteritems(action_detail.to_dict()))) + return zip(*sorted(action_detail.to_dict().items())) diff --git a/openstackclient/compute/v2/server_image.py b/openstackclient/compute/v2/server_image.py index 3bc5d94aab..b93cd4d887 100644 --- a/openstackclient/compute/v2/server_image.py +++ b/openstackclient/compute/v2/server_image.py @@ -21,7 +21,6 @@ from osc_lib import exceptions from osc_lib import utils from oslo_utils import importutils -import six from openstackclient.i18n import _ @@ -109,4 +108,4 @@ def _show_progress(progress): ] ) info = image_module._format_image(image) - return zip(*sorted(six.iteritems(info))) + return zip(*sorted(info.items())) diff --git a/openstackclient/compute/v2/usage.py b/openstackclient/compute/v2/usage.py index f84cd61dfb..307c238afe 100644 --- a/openstackclient/compute/v2/usage.py +++ b/openstackclient/compute/v2/usage.py @@ -21,7 +21,6 @@ from novaclient import api_versions from osc_lib.command import command from osc_lib import utils -import six from openstackclient.i18n import _ @@ -236,4 +235,4 @@ def take_action(self, parsed_args): info['Disk GB-Hours'] = ( float("%.2f" % usage.total_local_gb_usage) if hasattr(usage, "total_local_gb_usage") else None) - return zip(*sorted(six.iteritems(info))) + return zip(*sorted(info.items())) diff --git a/openstackclient/identity/v2_0/catalog.py b/openstackclient/identity/v2_0/catalog.py index 5d1e30626f..ccedbf3312 100644 --- a/openstackclient/identity/v2_0/catalog.py +++ b/openstackclient/identity/v2_0/catalog.py @@ -19,7 +19,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ @@ -102,4 +101,4 @@ def take_action(self, parsed_args): LOG.error(_('service %s not found\n'), parsed_args.service) return ((), ()) - return zip(*sorted(six.iteritems(data))) + return zip(*sorted(data.items())) diff --git a/openstackclient/identity/v2_0/ec2creds.py b/openstackclient/identity/v2_0/ec2creds.py index 0bc48322bc..f712bf4584 100644 --- a/openstackclient/identity/v2_0/ec2creds.py +++ b/openstackclient/identity/v2_0/ec2creds.py @@ -21,7 +21,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ @@ -82,7 +81,7 @@ def take_action(self, parsed_args): {'project_id': info.pop('tenant_id')} ) - return zip(*sorted(six.iteritems(info))) + return zip(*sorted(info.items())) class DeleteEC2Creds(command.Command): @@ -206,4 +205,4 @@ def take_action(self, parsed_args): {'project_id': info.pop('tenant_id')} ) - return zip(*sorted(six.iteritems(info))) + return zip(*sorted(info.items())) diff --git a/openstackclient/identity/v2_0/endpoint.py b/openstackclient/identity/v2_0/endpoint.py index 1628e48813..57906ddff6 100644 --- a/openstackclient/identity/v2_0/endpoint.py +++ b/openstackclient/identity/v2_0/endpoint.py @@ -20,7 +20,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ from openstackclient.identity import common @@ -76,7 +75,7 @@ def take_action(self, parsed_args): info.update(endpoint._info) info['service_name'] = service.name info['service_type'] = service.type - return zip(*sorted(six.iteritems(info))) + return zip(*sorted(info.items())) class DeleteEndpoint(command.Command): @@ -178,4 +177,4 @@ def take_action(self, parsed_args): info.update(match._info) info['service_name'] = service.name info['service_type'] = service.type - return zip(*sorted(six.iteritems(info))) + return zip(*sorted(info.items())) diff --git a/openstackclient/identity/v2_0/project.py b/openstackclient/identity/v2_0/project.py index e0018860e2..f431c02144 100644 --- a/openstackclient/identity/v2_0/project.py +++ b/openstackclient/identity/v2_0/project.py @@ -23,7 +23,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ @@ -100,7 +99,7 @@ def take_action(self, parsed_args): # TODO(stevemar): Remove the line below when we support multitenancy project._info.pop('parent_id', None) - return zip(*sorted(six.iteritems(project._info))) + return zip(*sorted(project._info.items())) class DeleteProject(command.Command): @@ -299,7 +298,7 @@ def take_action(self, parsed_args): properties[k] = v info['properties'] = format_columns.DictColumn(properties) - return zip(*sorted(six.iteritems(info))) + return zip(*sorted(info.items())) class UnsetProject(command.Command): diff --git a/openstackclient/identity/v2_0/role.py b/openstackclient/identity/v2_0/role.py index e9fe50fa4a..5c53fbcd5c 100644 --- a/openstackclient/identity/v2_0/role.py +++ b/openstackclient/identity/v2_0/role.py @@ -21,7 +21,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ @@ -69,7 +68,7 @@ def take_action(self, parsed_args): info = {} info.update(role._info) - return zip(*sorted(six.iteritems(info))) + return zip(*sorted(info.items())) class CreateRole(command.ShowOne): @@ -105,7 +104,7 @@ def take_action(self, parsed_args): info = {} info.update(role._info) - return zip(*sorted(six.iteritems(info))) + return zip(*sorted(info.items())) class DeleteRole(command.Command): @@ -217,4 +216,4 @@ def take_action(self, parsed_args): info = {} info.update(role._info) - return zip(*sorted(six.iteritems(info))) + return zip(*sorted(info.items())) diff --git a/openstackclient/identity/v2_0/service.py b/openstackclient/identity/v2_0/service.py index 653de8eb47..afc0b3d7ad 100644 --- a/openstackclient/identity/v2_0/service.py +++ b/openstackclient/identity/v2_0/service.py @@ -20,7 +20,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ from openstackclient.identity import common @@ -65,7 +64,7 @@ def take_action(self, parsed_args): info = {} info.update(service._info) - return zip(*sorted(six.iteritems(info))) + return zip(*sorted(info.items())) class DeleteService(command.Command): @@ -153,11 +152,11 @@ def take_action(self, parsed_args): if parsed_args.catalog: endpoints = auth_ref.service_catalog.get_endpoints( service_type=parsed_args.service) - for (service, service_endpoints) in six.iteritems(endpoints): + for (service, service_endpoints) in endpoints.items(): if service_endpoints: info = {"type": service} info.update(service_endpoints[0]) - return zip(*sorted(six.iteritems(info))) + return zip(*sorted(info.items())) msg = _("No service catalog with a type, name or ID of '%s' " "exists.") % (parsed_args.service) @@ -166,4 +165,4 @@ def take_action(self, parsed_args): service = common.find_service(identity_client, parsed_args.service) info = {} info.update(service._info) - return zip(*sorted(six.iteritems(info))) + return zip(*sorted(info.items())) diff --git a/openstackclient/identity/v2_0/token.py b/openstackclient/identity/v2_0/token.py index 3b08b4750d..205e15d30b 100644 --- a/openstackclient/identity/v2_0/token.py +++ b/openstackclient/identity/v2_0/token.py @@ -17,7 +17,6 @@ from osc_lib.command import command from osc_lib import exceptions -import six from openstackclient.i18n import _ @@ -49,7 +48,7 @@ def take_action(self, parsed_args): data['project_id'] = auth_ref.project_id if auth_ref.user_id: data['user_id'] = auth_ref.user_id - return zip(*sorted(six.iteritems(data))) + return zip(*sorted(data.items())) class RevokeToken(command.Command): diff --git a/openstackclient/identity/v2_0/user.py b/openstackclient/identity/v2_0/user.py index 0675877b9b..8dac093ef4 100644 --- a/openstackclient/identity/v2_0/user.py +++ b/openstackclient/identity/v2_0/user.py @@ -23,7 +23,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ @@ -154,7 +153,7 @@ def take_action(self, parsed_args): info = {} info.update(user._info) - return zip(*sorted(six.iteritems(info))) + return zip(*sorted(info.items())) class DeleteUser(command.Command): @@ -418,4 +417,4 @@ def take_action(self, parsed_args): {'project_id': info.pop('tenant_id')} ) - return zip(*sorted(six.iteritems(info))) + return zip(*sorted(info.items())) diff --git a/openstackclient/identity/v3/application_credential.py b/openstackclient/identity/v3/application_credential.py index 747fa20ed1..ea0b30cdbb 100644 --- a/openstackclient/identity/v3/application_credential.py +++ b/openstackclient/identity/v3/application_credential.py @@ -21,7 +21,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ from openstackclient.identity import common @@ -123,7 +122,7 @@ def take_action(self, parsed_args): msg = ' '.join(r['name'] for r in roles) application_credential._info['roles'] = msg - return zip(*sorted(six.iteritems(application_credential._info))) + return zip(*sorted(application_credential._info.items())) class DeleteApplicationCredential(command.Command): @@ -217,4 +216,4 @@ def take_action(self, parsed_args): msg = ' '.join(r['name'] for r in roles) app_cred._info['roles'] = msg - return zip(*sorted(six.iteritems(app_cred._info))) + return zip(*sorted(app_cred._info.items())) diff --git a/openstackclient/identity/v3/catalog.py b/openstackclient/identity/v3/catalog.py index 59430c4ce0..d1f7d31909 100644 --- a/openstackclient/identity/v3/catalog.py +++ b/openstackclient/identity/v3/catalog.py @@ -19,7 +19,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ @@ -97,4 +96,4 @@ def take_action(self, parsed_args): LOG.error(_('service %s not found\n'), parsed_args.service) return ((), ()) - return zip(*sorted(six.iteritems(data))) + return zip(*sorted(data.items())) diff --git a/openstackclient/identity/v3/consumer.py b/openstackclient/identity/v3/consumer.py index 6dd24dccf1..2f925aba87 100644 --- a/openstackclient/identity/v3/consumer.py +++ b/openstackclient/identity/v3/consumer.py @@ -20,7 +20,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ @@ -46,7 +45,7 @@ def take_action(self, parsed_args): parsed_args.description ) consumer._info.pop('links', None) - return zip(*sorted(six.iteritems(consumer._info))) + return zip(*sorted(consumer._info.items())) class DeleteConsumer(command.Command): @@ -142,4 +141,4 @@ def take_action(self, parsed_args): identity_client.oauth1.consumers, parsed_args.consumer) consumer._info.pop('links', None) - return zip(*sorted(six.iteritems(consumer._info))) + return zip(*sorted(consumer._info.items())) diff --git a/openstackclient/identity/v3/credential.py b/openstackclient/identity/v3/credential.py index 981f940aae..bf48df83d5 100644 --- a/openstackclient/identity/v3/credential.py +++ b/openstackclient/identity/v3/credential.py @@ -20,7 +20,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ from openstackclient.identity import common @@ -74,7 +73,7 @@ def take_action(self, parsed_args): project=project) credential._info.pop('links') - return zip(*sorted(six.iteritems(credential._info))) + return zip(*sorted(credential._info.items())) class DeleteCredential(command.Command): @@ -225,4 +224,4 @@ def take_action(self, parsed_args): parsed_args.credential) credential._info.pop('links') - return zip(*sorted(six.iteritems(credential._info))) + return zip(*sorted(credential._info.items())) diff --git a/openstackclient/identity/v3/domain.py b/openstackclient/identity/v3/domain.py index 064624ab45..dbcc97f6d2 100644 --- a/openstackclient/identity/v3/domain.py +++ b/openstackclient/identity/v3/domain.py @@ -21,7 +21,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ from openstackclient.identity import common @@ -85,7 +84,7 @@ def take_action(self, parsed_args): raise domain._info.pop('links') - return zip(*sorted(six.iteritems(domain._info))) + return zip(*sorted(domain._info.items())) class DeleteDomain(command.Command): @@ -206,4 +205,4 @@ def take_action(self, parsed_args): domain_str) domain._info.pop('links') - return zip(*sorted(six.iteritems(domain._info))) + return zip(*sorted(domain._info.items())) diff --git a/openstackclient/identity/v3/ec2creds.py b/openstackclient/identity/v3/ec2creds.py index 44e9a2c778..921b9168b0 100644 --- a/openstackclient/identity/v3/ec2creds.py +++ b/openstackclient/identity/v3/ec2creds.py @@ -17,7 +17,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ from openstackclient.identity import common @@ -108,7 +107,7 @@ def take_action(self, parsed_args): {'project_id': info.pop('tenant_id')} ) - return zip(*sorted(six.iteritems(info))) + return zip(*sorted(info.items())) class DeleteEC2Creds(command.Command): @@ -209,4 +208,4 @@ def take_action(self, parsed_args): {'project_id': info.pop('tenant_id')} ) - return zip(*sorted(six.iteritems(info))) + return zip(*sorted(info.items())) diff --git a/openstackclient/identity/v3/endpoint.py b/openstackclient/identity/v3/endpoint.py index 858b50363b..a3bd2683ee 100644 --- a/openstackclient/identity/v3/endpoint.py +++ b/openstackclient/identity/v3/endpoint.py @@ -20,7 +20,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ from openstackclient.identity import common @@ -131,7 +130,7 @@ def take_action(self, parsed_args): info.update(endpoint._info) info['service_name'] = get_service_name(service) info['service_type'] = service.type - return zip(*sorted(six.iteritems(info))) + return zip(*sorted(info.items())) class DeleteEndpoint(command.Command): @@ -389,4 +388,4 @@ def take_action(self, parsed_args): info.update(endpoint._info) info['service_name'] = get_service_name(service) info['service_type'] = service.type - return zip(*sorted(six.iteritems(info))) + return zip(*sorted(info.items())) diff --git a/openstackclient/identity/v3/endpoint_group.py b/openstackclient/identity/v3/endpoint_group.py index 66bd164d0d..cbe27edb4b 100644 --- a/openstackclient/identity/v3/endpoint_group.py +++ b/openstackclient/identity/v3/endpoint_group.py @@ -19,7 +19,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ from openstackclient.identity import common @@ -129,7 +128,7 @@ def take_action(self, parsed_args): info = {} endpoint_group._info.pop('links') info.update(endpoint_group._info) - return zip(*sorted(six.iteritems(info))) + return zip(*sorted(info.items())) class DeleteEndpointGroup(command.Command): @@ -321,4 +320,4 @@ def take_action(self, parsed_args): info = {} endpoint_group._info.pop('links') info.update(endpoint_group._info) - return zip(*sorted(six.iteritems(info))) + return zip(*sorted(info.items())) diff --git a/openstackclient/identity/v3/federation_protocol.py b/openstackclient/identity/v3/federation_protocol.py index 6429d934c1..0929469e7e 100644 --- a/openstackclient/identity/v3/federation_protocol.py +++ b/openstackclient/identity/v3/federation_protocol.py @@ -19,7 +19,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ @@ -68,7 +67,7 @@ def take_action(self, parsed_args): info['identity_provider'] = parsed_args.identity_provider info['mapping'] = info.pop('mapping_id') info.pop('links', None) - return zip(*sorted(six.iteritems(info))) + return zip(*sorted(info.items())) class DeleteProtocol(command.Command): @@ -175,7 +174,7 @@ def take_action(self, parsed_args): # user. info['identity_provider'] = parsed_args.identity_provider info['mapping'] = info.pop('mapping_id') - return zip(*sorted(six.iteritems(info))) + return zip(*sorted(info.items())) class ShowProtocol(command.ShowOne): @@ -205,4 +204,4 @@ def take_action(self, parsed_args): info = dict(protocol._info) info['mapping'] = info.pop('mapping_id') info.pop('links', None) - return zip(*sorted(six.iteritems(info))) + return zip(*sorted(info.items())) diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py index 02eeadd63f..46c3142cdd 100644 --- a/openstackclient/identity/v3/group.py +++ b/openstackclient/identity/v3/group.py @@ -21,7 +21,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ from openstackclient.identity import common @@ -182,7 +181,7 @@ def take_action(self, parsed_args): raise group._info.pop('links') - return zip(*sorted(six.iteritems(group._info))) + return zip(*sorted(group._info.items())) class DeleteGroup(command.Command): @@ -405,4 +404,4 @@ def take_action(self, parsed_args): domain_name_or_id=parsed_args.domain) group._info.pop('links') - return zip(*sorted(six.iteritems(group._info))) + return zip(*sorted(group._info.items())) diff --git a/openstackclient/identity/v3/identity_provider.py b/openstackclient/identity/v3/identity_provider.py index b331518213..2b2d9d11bd 100644 --- a/openstackclient/identity/v3/identity_provider.py +++ b/openstackclient/identity/v3/identity_provider.py @@ -19,7 +19,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ from openstackclient.identity import common @@ -106,7 +105,7 @@ def take_action(self, parsed_args): idp._info.pop('links', None) remote_ids = format_columns.ListColumn(idp._info.pop('remote_ids', [])) idp._info['remote_ids'] = remote_ids - return zip(*sorted(six.iteritems(idp._info))) + return zip(*sorted(idp._info.items())) class DeleteIdentityProvider(command.Command): @@ -248,4 +247,4 @@ def take_action(self, parsed_args): idp._info.pop('links', None) remote_ids = format_columns.ListColumn(idp._info.pop('remote_ids', [])) idp._info['remote_ids'] = remote_ids - return zip(*sorted(six.iteritems(idp._info))) + return zip(*sorted(idp._info.items())) diff --git a/openstackclient/identity/v3/implied_role.py b/openstackclient/identity/v3/implied_role.py index 4e3df88ac1..054f30285c 100644 --- a/openstackclient/identity/v3/implied_role.py +++ b/openstackclient/identity/v3/implied_role.py @@ -18,7 +18,6 @@ import logging from osc_lib.command import command -import six from openstackclient.i18n import _ @@ -75,7 +74,7 @@ def take_action(self, parsed_args): prior_role_id, implied_role_id) response._info.pop('links', None) return zip(*sorted([(k, v['id']) - for k, v in six.iteritems(response._info)])) + for k, v in response._info.items()])) class DeleteImpliedRole(command.Command): diff --git a/openstackclient/identity/v3/limit.py b/openstackclient/identity/v3/limit.py index f2af81e9f0..b155cbd863 100644 --- a/openstackclient/identity/v3/limit.py +++ b/openstackclient/identity/v3/limit.py @@ -18,7 +18,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ from openstackclient.identity import common as common_utils @@ -102,7 +101,7 @@ def take_action(self, parsed_args): ) limit._info.pop('links', None) - return zip(*sorted(six.iteritems(limit._info))) + return zip(*sorted(limit._info.items())) class ListLimit(command.Lister): @@ -198,7 +197,7 @@ def take_action(self, parsed_args): identity_client = self.app.client_manager.identity limit = identity_client.limits.get(parsed_args.limit_id) limit._info.pop('links', None) - return zip(*sorted(six.iteritems(limit._info))) + return zip(*sorted(limit._info.items())) class SetLimit(command.ShowOne): @@ -236,7 +235,7 @@ def take_action(self, parsed_args): limit._info.pop('links', None) - return zip(*sorted(six.iteritems(limit._info))) + return zip(*sorted(limit._info.items())) class DeleteLimit(command.Command): diff --git a/openstackclient/identity/v3/mapping.py b/openstackclient/identity/v3/mapping.py index e729c410c8..7d40a2b7f9 100644 --- a/openstackclient/identity/v3/mapping.py +++ b/openstackclient/identity/v3/mapping.py @@ -21,7 +21,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ @@ -107,7 +106,7 @@ def take_action(self, parsed_args): rules=rules) mapping._info.pop('links', None) - return zip(*sorted(six.iteritems(mapping._info))) + return zip(*sorted(mapping._info.items())) class DeleteMapping(command.Command): @@ -202,4 +201,4 @@ def take_action(self, parsed_args): mapping = identity_client.federation.mappings.get(parsed_args.mapping) mapping._info.pop('links', None) - return zip(*sorted(six.iteritems(mapping._info))) + return zip(*sorted(mapping._info.items())) diff --git a/openstackclient/identity/v3/policy.py b/openstackclient/identity/v3/policy.py index 3b6441959b..45674210f0 100644 --- a/openstackclient/identity/v3/policy.py +++ b/openstackclient/identity/v3/policy.py @@ -20,7 +20,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ @@ -57,7 +56,7 @@ def take_action(self, parsed_args): policy._info.pop('links') policy._info.update({'rules': policy._info.pop('blob')}) - return zip(*sorted(six.iteritems(policy._info))) + return zip(*sorted(policy._info.items())) class DeletePolicy(command.Command): @@ -176,4 +175,4 @@ def take_action(self, parsed_args): policy._info.pop('links') policy._info.update({'rules': policy._info.pop('blob')}) - return zip(*sorted(six.iteritems(policy._info))) + return zip(*sorted(policy._info.items())) diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index 073fb6df11..9ecc70ef4e 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -22,7 +22,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ from openstackclient.identity import common @@ -124,7 +123,7 @@ def take_action(self, parsed_args): raise project._info.pop('links') - return zip(*sorted(six.iteritems(project._info))) + return zip(*sorted(project._info.items())) class DeleteProject(command.Command): @@ -401,4 +400,4 @@ def take_action(self, parsed_args): subtree_as_ids=parsed_args.children) project._info.pop('links') - return zip(*sorted(six.iteritems(project._info))) + return zip(*sorted(project._info.items())) diff --git a/openstackclient/identity/v3/region.py b/openstackclient/identity/v3/region.py index 69c8b5066a..20ee073c3b 100644 --- a/openstackclient/identity/v3/region.py +++ b/openstackclient/identity/v3/region.py @@ -18,7 +18,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ @@ -62,7 +61,7 @@ def take_action(self, parsed_args): region._info['region'] = region._info.pop('id') region._info['parent_region'] = region._info.pop('parent_region_id') region._info.pop('links', None) - return zip(*sorted(six.iteritems(region._info))) + return zip(*sorted(region._info.items())) class DeleteRegion(command.Command): @@ -181,4 +180,4 @@ def take_action(self, parsed_args): region._info['region'] = region._info.pop('id') region._info['parent_region'] = region._info.pop('parent_region_id') region._info.pop('links', None) - return zip(*sorted(six.iteritems(region._info))) + return zip(*sorted(region._info.items())) diff --git a/openstackclient/identity/v3/registered_limit.py b/openstackclient/identity/v3/registered_limit.py index 9366ec1efc..53117c71ae 100644 --- a/openstackclient/identity/v3/registered_limit.py +++ b/openstackclient/identity/v3/registered_limit.py @@ -18,7 +18,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ from openstackclient.identity import common as common_utils @@ -92,7 +91,7 @@ def take_action(self, parsed_args): ) registered_limit._info.pop('links', None) - return zip(*sorted(six.iteritems(registered_limit._info))) + return zip(*sorted(registered_limit._info.items())) class DeleteRegisteredLimit(command.Command): @@ -275,7 +274,7 @@ def take_action(self, parsed_args): ) registered_limit._info.pop('links', None) - return zip(*sorted(six.iteritems(registered_limit._info))) + return zip(*sorted(registered_limit._info.items())) class ShowRegisteredLimit(command.ShowOne): @@ -296,4 +295,4 @@ def take_action(self, parsed_args): parsed_args.registered_limit_id ) registered_limit._info.pop('links', None) - return zip(*sorted(six.iteritems(registered_limit._info))) + return zip(*sorted(registered_limit._info.items())) diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index 0eeddd37fb..986f823fcf 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -21,7 +21,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ from openstackclient.identity import common @@ -211,7 +210,7 @@ def take_action(self, parsed_args): raise role._info.pop('links') - return zip(*sorted(six.iteritems(role._info))) + return zip(*sorted(role._info.items())) class DeleteRole(command.Command): @@ -403,4 +402,4 @@ def take_action(self, parsed_args): domain_id=domain_id) role._info.pop('links') - return zip(*sorted(six.iteritems(role._info))) + return zip(*sorted(role._info.items())) diff --git a/openstackclient/identity/v3/service.py b/openstackclient/identity/v3/service.py index ac8d8d9eef..9dc6696251 100644 --- a/openstackclient/identity/v3/service.py +++ b/openstackclient/identity/v3/service.py @@ -20,7 +20,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ from openstackclient.identity import common @@ -77,7 +76,7 @@ def take_action(self, parsed_args): ) service._info.pop('links') - return zip(*sorted(six.iteritems(service._info))) + return zip(*sorted(service._info.items())) class DeleteService(command.Command): @@ -218,4 +217,4 @@ def take_action(self, parsed_args): service = common.find_service(identity_client, parsed_args.service) service._info.pop('links') - return zip(*sorted(six.iteritems(service._info))) + return zip(*sorted(service._info.items())) diff --git a/openstackclient/identity/v3/service_provider.py b/openstackclient/identity/v3/service_provider.py index bb2d9917b4..e106c787b4 100644 --- a/openstackclient/identity/v3/service_provider.py +++ b/openstackclient/identity/v3/service_provider.py @@ -18,7 +18,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ @@ -83,7 +82,7 @@ def take_action(self, parsed_args): sp_url=parsed_args.service_provider_url) sp._info.pop('links', None) - return zip(*sorted(six.iteritems(sp._info))) + return zip(*sorted(sp._info.items())) class DeleteServiceProvider(command.Command): @@ -211,4 +210,4 @@ def take_action(self, parsed_args): id=parsed_args.service_provider) service_provider._info.pop('links', None) - return zip(*sorted(six.iteritems(service_provider._info))) + return zip(*sorted(service_provider._info.items())) diff --git a/openstackclient/identity/v3/token.py b/openstackclient/identity/v3/token.py index 1933ecad65..f14dd8bc3e 100644 --- a/openstackclient/identity/v3/token.py +++ b/openstackclient/identity/v3/token.py @@ -18,7 +18,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ from openstackclient.identity import common @@ -62,7 +61,7 @@ def take_action(self, parsed_args): parsed_args.request_key, roles) - return zip(*sorted(six.iteritems(verifier_pin._info))) + return zip(*sorted(verifier_pin._info.items())) class CreateAccessToken(command.ShowOne): @@ -108,7 +107,7 @@ def take_action(self, parsed_args): parsed_args.consumer_key, parsed_args.consumer_secret, parsed_args.request_key, parsed_args.request_secret, parsed_args.verifier) - return zip(*sorted(six.iteritems(access_token._info))) + return zip(*sorted(access_token._info.items())) class CreateRequestToken(command.ShowOne): @@ -160,7 +159,7 @@ def take_action(self, parsed_args): parsed_args.consumer_key, parsed_args.consumer_secret, project.id) - return zip(*sorted(six.iteritems(request_token._info))) + return zip(*sorted(request_token._info.items())) class IssueToken(command.ShowOne): @@ -198,7 +197,7 @@ def take_action(self, parsed_args): # deployment system. When that happens, this will have to relay # scope information and IDs like we do for projects and domains. data['system'] = 'all' - return zip(*sorted(six.iteritems(data))) + return zip(*sorted(data.items())) class RevokeToken(command.Command): diff --git a/openstackclient/identity/v3/trust.py b/openstackclient/identity/v3/trust.py index 155063bb39..cd3a65d0da 100644 --- a/openstackclient/identity/v3/trust.py +++ b/openstackclient/identity/v3/trust.py @@ -20,7 +20,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ from openstackclient.identity import common @@ -136,7 +135,7 @@ def take_action(self, parsed_args): msg = ' '.join(r['name'] for r in roles) trust._info['roles'] = msg - return zip(*sorted(six.iteritems(trust._info))) + return zip(*sorted(trust._info.items())) class DeleteTrust(command.Command): @@ -213,4 +212,4 @@ def take_action(self, parsed_args): msg = ' '.join(r['name'] for r in roles) trust._info['roles'] = msg - return zip(*sorted(six.iteritems(trust._info))) + return zip(*sorted(trust._info.items())) diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index 5f4fb544fe..ca85c5d8a8 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -22,7 +22,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ from openstackclient.identity import common @@ -135,7 +134,7 @@ def take_action(self, parsed_args): raise user._info.pop('links') - return zip(*sorted(six.iteritems(user._info))) + return zip(*sorted(user._info.items())) class DeleteUser(command.Command): @@ -486,4 +485,4 @@ def take_action(self, parsed_args): user_str) user._info.pop('links') - return zip(*sorted(six.iteritems(user._info))) + return zip(*sorted(user._info.items())) diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index c2dab3eec3..a711a1288b 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -28,7 +28,6 @@ from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import utils -import six from openstackclient.i18n import _ @@ -271,7 +270,7 @@ def take_action(self, parsed_args): info.update(image._info) info['properties'] = format_columns.DictColumn( info.get('properties', {})) - return zip(*sorted(six.iteritems(info))) + return zip(*sorted(info.items())) class DeleteImage(command.Command): @@ -718,4 +717,4 @@ def take_action(self, parsed_args): info['size'] = utils.format_size(info['size']) info['properties'] = format_columns.DictColumn( info.get('properties', {})) - return zip(*sorted(six.iteritems(info))) + return zip(*sorted(info.items())) diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 2b10c3adda..feeb256744 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -110,7 +110,7 @@ def take_action(self, parsed_args): project_id, ) - return zip(*sorted(six.iteritems(image_member))) + return zip(*sorted(image_member.items())) class CreateImage(command.ShowOne): @@ -292,7 +292,7 @@ def take_action(self, parsed_args): # properties should get flattened into the general kwargs if getattr(parsed_args, 'properties', None): - for k, v in six.iteritems(parsed_args.properties): + for k, v in parsed_args.properties.items(): kwargs[k] = str(v) # Handle exclusive booleans with care @@ -417,7 +417,7 @@ def take_action(self, parsed_args): if not info: info = _format_image(image) - return zip(*sorted(six.iteritems(info))) + return zip(*sorted(info.items())) class DeleteImage(command.Command): @@ -969,7 +969,7 @@ def take_action(self, parsed_args): # Properties should get flattened into the general kwargs if getattr(parsed_args, 'properties', None): - for k, v in six.iteritems(parsed_args.properties): + for k, v in parsed_args.properties.items(): kwargs[k] = str(v) # Handle exclusive booleans with care @@ -1066,7 +1066,7 @@ def take_action(self, parsed_args): image['size'] = utils.format_size(image['size']) info = _format_image(image) - return zip(*sorted(six.iteritems(info))) + return zip(*sorted(info.items())) class UnsetImage(command.Command): diff --git a/openstackclient/network/sdk_utils.py b/openstackclient/network/sdk_utils.py index 9f0856175d..af9c74f944 100644 --- a/openstackclient/network/sdk_utils.py +++ b/openstackclient/network/sdk_utils.py @@ -10,8 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -import six - def get_osc_show_columns_for_sdk_resource( sdk_resource, @@ -44,7 +42,7 @@ def get_osc_show_columns_for_sdk_resource( for col_name in invisible_columns: if col_name in display_columns: display_columns.remove(col_name) - for sdk_attr, osc_attr in six.iteritems(osc_column_map): + for sdk_attr, osc_attr in osc_column_map.items(): if sdk_attr in display_columns: attr_map[osc_attr] = sdk_attr display_columns.remove(sdk_attr) diff --git a/openstackclient/network/v2/network_segment_range.py b/openstackclient/network/v2/network_segment_range.py index f03bcc1c0b..2cdae642e5 100644 --- a/openstackclient/network/v2/network_segment_range.py +++ b/openstackclient/network/v2/network_segment_range.py @@ -61,7 +61,7 @@ def _is_prop_empty(columns, props, prop_name): def _exchange_dict_keys_with_values(orig_dict): updated_dict = dict() - for k, v in six.iteritems(orig_dict): + for k, v in orig_dict.items(): k = [k] if not updated_dict.get(v): updated_dict[v] = k @@ -80,7 +80,7 @@ def _update_available_from_props(columns, props): def _update_used_from_props(columns, props): index_used = columns.index('used') updated_used = _exchange_dict_keys_with_values(props[index_used]) - for k, v in six.iteritems(updated_used): + for k, v in updated_used.items(): updated_used[k] = list(_get_ranges(v)) props = _hack_tuple_value_update_by_index( props, index_used, updated_used) diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py index 9f0ca0a1f4..24f71ab678 100644 --- a/openstackclient/network/v2/security_group.py +++ b/openstackclient/network/v2/security_group.py @@ -19,7 +19,6 @@ from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import utils -import six from openstackclient.i18n import _ from openstackclient.identity import common as identity_common @@ -34,7 +33,7 @@ def _format_network_security_group_rules(sg_rules): # rules, trim keys with caller known (e.g. security group and tenant ID) # or empty values. for sg_rule in sg_rules: - empty_keys = [k for k, v in six.iteritems(sg_rule) if not v] + empty_keys = [k for k, v in sg_rule.items() if not v] for key in empty_keys: sg_rule.pop(key) sg_rule.pop('security_group_id', None) diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index a38587fa52..f48478ea74 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -20,7 +20,6 @@ from osc_lib.cli import parseractions from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ from openstackclient.identity import common as identity_common @@ -39,7 +38,7 @@ def _format_security_group_rule_show(obj): data = network_utils.transform_compute_security_group_rule(obj) - return zip(*sorted(six.iteritems(data))) + return zip(*sorted(data.items())) def _format_network_port_range(rule): diff --git a/openstackclient/object/v1/account.py b/openstackclient/object/v1/account.py index 95be8132c9..d6bc9fd780 100644 --- a/openstackclient/object/v1/account.py +++ b/openstackclient/object/v1/account.py @@ -16,7 +16,6 @@ from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command -import six from openstackclient.i18n import _ @@ -50,7 +49,7 @@ def take_action(self, parsed_args): if 'properties' in data: data['properties'] = format_columns.DictColumn( data.pop('properties')) - return zip(*sorted(six.iteritems(data))) + return zip(*sorted(data.items())) class UnsetAccount(command.Command): diff --git a/openstackclient/object/v1/container.py b/openstackclient/object/v1/container.py index 02e8d27780..47ca5bc1da 100644 --- a/openstackclient/object/v1/container.py +++ b/openstackclient/object/v1/container.py @@ -21,7 +21,6 @@ from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import utils -import six from openstackclient.i18n import _ @@ -233,7 +232,7 @@ def take_action(self, parsed_args): if 'properties' in data: data['properties'] = format_columns.DictColumn(data['properties']) - return zip(*sorted(six.iteritems(data))) + return zip(*sorted(data.items())) class UnsetContainer(command.Command): diff --git a/openstackclient/object/v1/object.py b/openstackclient/object/v1/object.py index 3747e19e47..01e537eef7 100644 --- a/openstackclient/object/v1/object.py +++ b/openstackclient/object/v1/object.py @@ -22,7 +22,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ @@ -287,7 +286,7 @@ def take_action(self, parsed_args): if 'properties' in data: data['properties'] = format_columns.DictColumn(data['properties']) - return zip(*sorted(six.iteritems(data))) + return zip(*sorted(data.items())) class UnsetObject(command.Command): diff --git a/openstackclient/tests/unit/common/test_availability_zone.py b/openstackclient/tests/unit/common/test_availability_zone.py index 6c7adc43b6..49e5904dbd 100644 --- a/openstackclient/tests/unit/common/test_availability_zone.py +++ b/openstackclient/tests/unit/common/test_availability_zone.py @@ -13,8 +13,6 @@ import mock -import six - from openstackclient.common import availability_zone from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes from openstackclient.tests.unit import fakes @@ -31,8 +29,8 @@ def _build_compute_az_datalist(compute_az, long_datalist=False): 'available', ) else: - for host, services in six.iteritems(compute_az.hosts): - for service, state in six.iteritems(services): + for host, services in compute_az.hosts.items(): + for service, state in services.items(): datalist += ( compute_az.zoneName, 'available', diff --git a/openstackclient/tests/unit/fakes.py b/openstackclient/tests/unit/fakes.py index bca457e457..59cbbe1055 100644 --- a/openstackclient/tests/unit/fakes.py +++ b/openstackclient/tests/unit/fakes.py @@ -200,7 +200,7 @@ def __init__(self, manager=None, info=None, loaded=False, methods=None): self._loaded = loaded def _add_details(self, info): - for (k, v) in six.iteritems(info): + for (k, v) in info.items(): setattr(self, k, v) def _add_methods(self, methods): @@ -211,7 +211,7 @@ def _add_methods(self, methods): @value. When users access the attribute with (), @value will be returned, which looks like a function call. """ - for (name, ret) in six.iteritems(methods): + for (name, ret) in methods.items(): method = mock.Mock(return_value=ret) setattr(self, name, method) diff --git a/openstackclient/volume/v1/qos_specs.py b/openstackclient/volume/v1/qos_specs.py index 0b6a7fa0ca..79dff1c677 100644 --- a/openstackclient/volume/v1/qos_specs.py +++ b/openstackclient/volume/v1/qos_specs.py @@ -22,7 +22,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ @@ -99,7 +98,7 @@ def take_action(self, parsed_args): {'properties': format_columns.DictColumn(qos_spec._info.pop('specs'))} ) - return zip(*sorted(six.iteritems(qos_spec._info))) + return zip(*sorted(qos_spec._info.items())) class DeleteQos(command.Command): @@ -273,7 +272,7 @@ def take_action(self, parsed_args): {'properties': format_columns.DictColumn(qos_spec._info.pop('specs'))}) - return zip(*sorted(six.iteritems(qos_spec._info))) + return zip(*sorted(qos_spec._info.items())) class UnsetQos(command.Command): diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index 36c7ef8985..460bd85a8b 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -25,7 +25,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ @@ -253,7 +252,7 @@ def take_action(self, parsed_args): volume._info, parsed_args.columns, {'display_name': 'name'} ) - return zip(*sorted(six.iteritems(volume_info))) + return zip(*sorted(volume_info.items())) class DeleteVolume(command.Command): @@ -614,7 +613,7 @@ def take_action(self, parsed_args): volume._info, parsed_args.columns, {'display_name': 'name'} ) - return zip(*sorted(six.iteritems(volume_info))) + return zip(*sorted(volume_info.items())) class UnsetVolume(command.Command): diff --git a/openstackclient/volume/v1/volume_backup.py b/openstackclient/volume/v1/volume_backup.py index 2daa23cb92..1a83a3c040 100644 --- a/openstackclient/volume/v1/volume_backup.py +++ b/openstackclient/volume/v1/volume_backup.py @@ -23,7 +23,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ @@ -98,7 +97,7 @@ def take_action(self, parsed_args): ) backup._info.pop('links') - return zip(*sorted(six.iteritems(backup._info))) + return zip(*sorted(backup._info.items())) class DeleteVolumeBackup(command.Command): @@ -263,4 +262,4 @@ def take_action(self, parsed_args): backup = utils.find_resource(volume_client.backups, parsed_args.backup) backup._info.pop('links') - return zip(*sorted(six.iteritems(backup._info))) + return zip(*sorted(backup._info.items())) diff --git a/openstackclient/volume/v1/volume_snapshot.py b/openstackclient/volume/v1/volume_snapshot.py index 50f81771a0..966db48ff1 100644 --- a/openstackclient/volume/v1/volume_snapshot.py +++ b/openstackclient/volume/v1/volume_snapshot.py @@ -25,7 +25,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ @@ -110,7 +109,7 @@ def take_action(self, parsed_args): format_columns.DictColumn(snapshot._info.pop('metadata'))} ) - return zip(*sorted(six.iteritems(snapshot._info))) + return zip(*sorted(snapshot._info.items())) class DeleteVolumeSnapshot(command.Command): @@ -342,7 +341,7 @@ def take_action(self, parsed_args): format_columns.DictColumn(snapshot._info.pop('metadata'))} ) - return zip(*sorted(six.iteritems(snapshot._info))) + return zip(*sorted(snapshot._info.items())) class UnsetVolumeSnapshot(command.Command): diff --git a/openstackclient/volume/v1/volume_transfer_request.py b/openstackclient/volume/v1/volume_transfer_request.py index 6f79658e5a..971b9ab592 100644 --- a/openstackclient/volume/v1/volume_transfer_request.py +++ b/openstackclient/volume/v1/volume_transfer_request.py @@ -19,7 +19,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ @@ -67,7 +66,7 @@ def take_action(self, parsed_args): ) transfer_accept._info.pop("links", None) - return zip(*sorted(six.iteritems(transfer_accept._info))) + return zip(*sorted(transfer_accept._info.items())) class CreateTransferRequest(command.ShowOne): @@ -99,7 +98,7 @@ def take_action(self, parsed_args): ) volume_transfer_request._info.pop("links", None) - return zip(*sorted(six.iteritems(volume_transfer_request._info))) + return zip(*sorted(volume_transfer_request._info.items())) class DeleteTransferRequest(command.Command): @@ -189,4 +188,4 @@ def take_action(self, parsed_args): ) volume_transfer_request._info.pop("links", None) - return zip(*sorted(six.iteritems(volume_transfer_request._info))) + return zip(*sorted(volume_transfer_request._info.items())) diff --git a/openstackclient/volume/v1/volume_type.py b/openstackclient/volume/v1/volume_type.py index e744e92fdb..4f015d13f6 100644 --- a/openstackclient/volume/v1/volume_type.py +++ b/openstackclient/volume/v1/volume_type.py @@ -24,7 +24,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ @@ -162,7 +161,7 @@ def take_action(self, parsed_args): {'encryption': format_columns.DictColumn(encryption._info)}) volume_type._info.pop("os-volume-type-access:is_public", None) - return zip(*sorted(six.iteritems(volume_type._info))) + return zip(*sorted(volume_type._info.items())) class DeleteVolumeType(command.Command): @@ -388,7 +387,7 @@ def take_action(self, parsed_args): LOG.error(_("Failed to display the encryption information " "of this volume type: %s"), e) volume_type._info.pop("os-volume-type-access:is_public", None) - return zip(*sorted(six.iteritems(volume_type._info))) + return zip(*sorted(volume_type._info.items())) class UnsetVolumeType(command.Command): diff --git a/openstackclient/volume/v2/backup_record.py b/openstackclient/volume/v2/backup_record.py index f491803272..64ff4f67ad 100644 --- a/openstackclient/volume/v2/backup_record.py +++ b/openstackclient/volume/v2/backup_record.py @@ -18,7 +18,6 @@ from osc_lib.command import command from osc_lib import utils -import six from openstackclient.i18n import _ @@ -51,7 +50,7 @@ def take_action(self, parsed_args): backup_data['Backup Service'] = backup_data.pop('backup_service') backup_data['Metadata'] = backup_data.pop('backup_url') - return zip(*sorted(six.iteritems(backup_data))) + return zip(*sorted(backup_data.items())) class ImportBackupRecord(command.ShowOne): @@ -79,4 +78,4 @@ def take_action(self, parsed_args): parsed_args.backup_service, parsed_args.backup_metadata) backup_data.pop('links', None) - return zip(*sorted(six.iteritems(backup_data))) + return zip(*sorted(backup_data.items())) diff --git a/openstackclient/volume/v2/consistency_group.py b/openstackclient/volume/v2/consistency_group.py index 26dd8ffca1..c50a1b5bb2 100644 --- a/openstackclient/volume/v2/consistency_group.py +++ b/openstackclient/volume/v2/consistency_group.py @@ -20,7 +20,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ @@ -161,7 +160,7 @@ def take_action(self, parsed_args): ) ) - return zip(*sorted(six.iteritems(consistency_group._info))) + return zip(*sorted(consistency_group._info.items())) class DeleteConsistencyGroup(command.Command): @@ -335,4 +334,4 @@ def take_action(self, parsed_args): consistency_group = utils.find_resource( volume_client.consistencygroups, parsed_args.consistency_group) - return zip(*sorted(six.iteritems(consistency_group._info))) + return zip(*sorted(consistency_group._info.items())) diff --git a/openstackclient/volume/v2/consistency_group_snapshot.py b/openstackclient/volume/v2/consistency_group_snapshot.py index 540deb0187..3df66e6962 100644 --- a/openstackclient/volume/v2/consistency_group_snapshot.py +++ b/openstackclient/volume/v2/consistency_group_snapshot.py @@ -19,7 +19,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ @@ -68,7 +67,7 @@ def take_action(self, parsed_args): description=parsed_args.description, ) - return zip(*sorted(six.iteritems(consistency_group_snapshot._info))) + return zip(*sorted(consistency_group_snapshot._info.items())) class DeleteConsistencyGroupSnapshot(command.Command): @@ -187,4 +186,4 @@ def take_action(self, parsed_args): consistency_group_snapshot = utils.find_resource( volume_client.cgsnapshots, parsed_args.consistency_group_snapshot) - return zip(*sorted(six.iteritems(consistency_group_snapshot._info))) + return zip(*sorted(consistency_group_snapshot._info.items())) diff --git a/openstackclient/volume/v2/qos_specs.py b/openstackclient/volume/v2/qos_specs.py index 3037d34ae8..e6e6b9f8b7 100644 --- a/openstackclient/volume/v2/qos_specs.py +++ b/openstackclient/volume/v2/qos_specs.py @@ -22,7 +22,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ @@ -100,7 +99,7 @@ def take_action(self, parsed_args): {'properties': format_columns.DictColumn(qos_spec._info.pop('specs'))} ) - return zip(*sorted(six.iteritems(qos_spec._info))) + return zip(*sorted(qos_spec._info.items())) class DeleteQos(command.Command): @@ -275,7 +274,7 @@ def take_action(self, parsed_args): {'properties': format_columns.DictColumn(qos_spec._info.pop('specs'))}) - return zip(*sorted(six.iteritems(qos_spec._info))) + return zip(*sorted(qos_spec._info.items())) class UnsetQos(command.Command): diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index 17ccd3d31e..4dde1340a7 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -25,7 +25,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ from openstackclient.identity import common as identity_common @@ -252,7 +251,7 @@ def take_action(self, parsed_args): } ) volume._info.pop("links", None) - return zip(*sorted(six.iteritems(volume._info))) + return zip(*sorted(volume._info.items())) class DeleteVolume(command.Command): @@ -751,7 +750,7 @@ def take_action(self, parsed_args): # Remove key links from being displayed volume._info.pop("links", None) - return zip(*sorted(six.iteritems(volume._info))) + return zip(*sorted(volume._info.items())) class UnsetVolume(command.Command): diff --git a/openstackclient/volume/v2/volume_backup.py b/openstackclient/volume/v2/volume_backup.py index 4d0d54c1b5..c336f6c9ac 100644 --- a/openstackclient/volume/v2/volume_backup.py +++ b/openstackclient/volume/v2/volume_backup.py @@ -23,7 +23,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ @@ -120,7 +119,7 @@ def take_action(self, parsed_args): snapshot_id=snapshot_id, ) backup._info.pop("links", None) - return zip(*sorted(six.iteritems(backup._info))) + return zip(*sorted(backup._info.items())) class DeleteVolumeBackup(command.Command): @@ -289,7 +288,7 @@ def take_action(self, parsed_args): parsed_args.volume) backup = volume_client.restores.restore(backup.id, destination_volume.id) - return zip(*sorted(six.iteritems(backup._info))) + return zip(*sorted(backup._info.items())) class SetVolumeBackup(command.Command): @@ -371,4 +370,4 @@ def take_action(self, parsed_args): backup = utils.find_resource(volume_client.backups, parsed_args.backup) backup._info.pop("links", None) - return zip(*sorted(six.iteritems(backup._info))) + return zip(*sorted(backup._info.items())) diff --git a/openstackclient/volume/v2/volume_snapshot.py b/openstackclient/volume/v2/volume_snapshot.py index 2b26ae323b..edacf68364 100644 --- a/openstackclient/volume/v2/volume_snapshot.py +++ b/openstackclient/volume/v2/volume_snapshot.py @@ -24,7 +24,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ from openstackclient.identity import common as identity_common @@ -140,7 +139,7 @@ def take_action(self, parsed_args): {'properties': format_columns.DictColumn(snapshot._info.pop('metadata'))} ) - return zip(*sorted(six.iteritems(snapshot._info))) + return zip(*sorted(snapshot._info.items())) class DeleteVolumeSnapshot(command.Command): @@ -426,7 +425,7 @@ def take_action(self, parsed_args): {'properties': format_columns.DictColumn(snapshot._info.pop('metadata'))} ) - return zip(*sorted(six.iteritems(snapshot._info))) + return zip(*sorted(snapshot._info.items())) class UnsetVolumeSnapshot(command.Command): diff --git a/openstackclient/volume/v2/volume_transfer_request.py b/openstackclient/volume/v2/volume_transfer_request.py index 4c4741bc30..2a1ace1f42 100644 --- a/openstackclient/volume/v2/volume_transfer_request.py +++ b/openstackclient/volume/v2/volume_transfer_request.py @@ -19,7 +19,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ @@ -64,7 +63,7 @@ def take_action(self, parsed_args): ) transfer_accept._info.pop("links", None) - return zip(*sorted(six.iteritems(transfer_accept._info))) + return zip(*sorted(transfer_accept._info.items())) class CreateTransferRequest(command.ShowOne): @@ -96,7 +95,7 @@ def take_action(self, parsed_args): ) volume_transfer_request._info.pop("links", None) - return zip(*sorted(six.iteritems(volume_transfer_request._info))) + return zip(*sorted(volume_transfer_request._info.items())) class DeleteTransferRequest(command.Command): @@ -186,4 +185,4 @@ def take_action(self, parsed_args): ) volume_transfer_request._info.pop("links", None) - return zip(*sorted(six.iteritems(volume_transfer_request._info))) + return zip(*sorted(volume_transfer_request._info.items())) diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index 54b1f49719..483e6dd3b5 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -23,7 +23,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ from openstackclient.identity import common as identity_common @@ -235,7 +234,7 @@ def take_action(self, parsed_args): {'encryption': format_columns.DictColumn(encryption._info)}) volume_type._info.pop("os-volume-type-access:is_public", None) - return zip(*sorted(six.iteritems(volume_type._info))) + return zip(*sorted(volume_type._info.items())) class DeleteVolumeType(command.Command): @@ -553,7 +552,7 @@ def take_action(self, parsed_args): LOG.error(_("Failed to display the encryption information " "of this volume type: %s"), e) volume_type._info.pop("os-volume-type-access:is_public", None) - return zip(*sorted(six.iteritems(volume_type._info))) + return zip(*sorted(volume_type._info.items())) class UnsetVolumeType(command.Command): From 90ca67bdae937244bcb0f3ee9bd89e89dcf128d9 Mon Sep 17 00:00:00 2001 From: Sean McGinnis Date: Fri, 10 Jan 2020 11:43:07 -0600 Subject: [PATCH 2149/3095] Raise hacking to more recent 2.0.0 Change-Id: I3cf36ed4d8fb5d003acae762820a8d80f75a11e9 Signed-off-by: Sean McGinnis --- lower-constraints.txt | 2 +- test-requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lower-constraints.txt b/lower-constraints.txt index ad911e771d..6bc10e57bc 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -29,7 +29,7 @@ gitdb==0.6.4 GitPython==1.0.1 gnocchiclient==3.3.1 greenlet==0.4.10 -hacking==1.1.0 +hacking==2.0.0 httplib2==0.9.1 idna==2.6 iso8601==0.1.11 diff --git a/test-requirements.txt b/test-requirements.txt index 0f59e46faa..15b3e9c495 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,7 +1,7 @@ # The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -hacking>=1.1.0,<1.2.0 # Apache-2.0 +hacking>=2.0.0 # Apache-2.0 coverage!=4.4,>=4.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD flake8-import-order==0.13 # LGPLv3 From 69db9fe73c81bc6341c37948365163902b6437ee Mon Sep 17 00:00:00 2001 From: Sean McGinnis Date: Fri, 10 Jan 2020 14:21:47 -0600 Subject: [PATCH 2150/3095] Raise flake8-import-order version to latest We had this library capped at a release that is a few years old. Now that we have dropped py2 testing, we can pick up the latest version. This uncovered a few things to clean up. Mostly the fact that mock is now a part of the StdLib unittest since Python 3.3. Change-Id: I27484dd4c25378413ff16e97a35a1a46062357bc Signed-off-by: Sean McGinnis --- openstackclient/api/api.py | 3 +-- openstackclient/tests/unit/api/test_object_store_v1.py | 2 +- openstackclient/tests/unit/common/test_availability_zone.py | 2 +- openstackclient/tests/unit/common/test_command.py | 2 +- openstackclient/tests/unit/common/test_configuration.py | 2 +- openstackclient/tests/unit/common/test_extension.py | 2 +- openstackclient/tests/unit/common/test_logs.py | 3 +-- openstackclient/tests/unit/common/test_module.py | 2 +- openstackclient/tests/unit/common/test_project_purge.py | 2 +- openstackclient/tests/unit/common/test_quota.py | 2 +- openstackclient/tests/unit/compute/v2/fakes.py | 2 +- openstackclient/tests/unit/compute/v2/test_agent.py | 4 ++-- openstackclient/tests/unit/compute/v2/test_aggregate.py | 4 ++-- openstackclient/tests/unit/compute/v2/test_console.py | 2 +- openstackclient/tests/unit/compute/v2/test_flavor.py | 4 ++-- openstackclient/tests/unit/compute/v2/test_host.py | 2 +- openstackclient/tests/unit/compute/v2/test_keypair.py | 4 ++-- openstackclient/tests/unit/compute/v2/test_server.py | 4 ++-- openstackclient/tests/unit/compute/v2/test_server_backup.py | 2 +- openstackclient/tests/unit/compute/v2/test_server_group.py | 2 +- openstackclient/tests/unit/compute/v2/test_server_image.py | 2 +- openstackclient/tests/unit/compute/v2/test_service.py | 5 +++-- openstackclient/tests/unit/compute/v2/test_usage.py | 2 +- openstackclient/tests/unit/fakes.py | 2 +- openstackclient/tests/unit/identity/v2_0/fakes.py | 2 +- openstackclient/tests/unit/identity/v2_0/test_catalog.py | 2 +- openstackclient/tests/unit/identity/v2_0/test_project.py | 2 +- openstackclient/tests/unit/identity/v2_0/test_role.py | 2 +- .../tests/unit/identity/v2_0/test_role_assignment.py | 2 +- openstackclient/tests/unit/identity/v2_0/test_token.py | 2 +- openstackclient/tests/unit/identity/v2_0/test_user.py | 2 +- openstackclient/tests/unit/identity/v3/fakes.py | 2 +- .../tests/unit/identity/v3/test_application_credential.py | 2 +- openstackclient/tests/unit/identity/v3/test_catalog.py | 2 +- openstackclient/tests/unit/identity/v3/test_credential.py | 4 ++-- .../tests/unit/identity/v3/test_endpoint_group.py | 2 +- openstackclient/tests/unit/identity/v3/test_group.py | 4 ++-- .../tests/unit/identity/v3/test_identity_provider.py | 3 +-- openstackclient/tests/unit/identity/v3/test_mappings.py | 2 +- openstackclient/tests/unit/identity/v3/test_project.py | 4 ++-- openstackclient/tests/unit/identity/v3/test_role.py | 2 +- .../tests/unit/identity/v3/test_role_assignment.py | 3 +-- openstackclient/tests/unit/identity/v3/test_token.py | 2 +- openstackclient/tests/unit/identity/v3/test_trust.py | 2 +- openstackclient/tests/unit/identity/v3/test_user.py | 2 +- openstackclient/tests/unit/image/v1/fakes.py | 3 +-- openstackclient/tests/unit/image/v1/test_image.py | 3 +-- openstackclient/tests/unit/image/v2/fakes.py | 2 +- openstackclient/tests/unit/image/v2/test_image.py | 2 +- openstackclient/tests/unit/integ/cli/test_shell.py | 2 +- openstackclient/tests/unit/network/test_common.py | 2 +- openstackclient/tests/unit/network/v2/fakes.py | 3 +-- openstackclient/tests/unit/network/v2/test_address_scope.py | 4 ++-- .../tests/unit/network/v2/test_floating_ip_compute.py | 4 ++-- .../tests/unit/network/v2/test_floating_ip_network.py | 4 ++-- .../tests/unit/network/v2/test_floating_ip_pool_compute.py | 2 +- .../unit/network/v2/test_floating_ip_port_forwarding.py | 4 ++-- .../tests/unit/network/v2/test_ip_availability.py | 2 +- openstackclient/tests/unit/network/v2/test_network.py | 5 ++--- openstackclient/tests/unit/network/v2/test_network_agent.py | 4 ++-- .../unit/network/v2/test_network_auto_allocated_topology.py | 2 +- .../tests/unit/network/v2/test_network_compute.py | 4 ++-- openstackclient/tests/unit/network/v2/test_network_flavor.py | 2 +- .../tests/unit/network/v2/test_network_flavor_profile.py | 2 +- openstackclient/tests/unit/network/v2/test_network_meter.py | 4 ++-- .../tests/unit/network/v2/test_network_meter_rule.py | 4 ++-- .../tests/unit/network/v2/test_network_qos_policy.py | 4 ++-- .../tests/unit/network/v2/test_network_qos_rule.py | 2 +- .../tests/unit/network/v2/test_network_qos_rule_type.py | 2 +- openstackclient/tests/unit/network/v2/test_network_rbac.py | 4 ++-- .../tests/unit/network/v2/test_network_segment.py | 4 ++-- .../tests/unit/network/v2/test_network_segment_range.py | 4 ++-- .../tests/unit/network/v2/test_network_service_provider.py | 2 +- openstackclient/tests/unit/network/v2/test_port.py | 4 ++-- openstackclient/tests/unit/network/v2/test_router.py | 4 ++-- .../tests/unit/network/v2/test_security_group_compute.py | 4 ++-- .../tests/unit/network/v2/test_security_group_network.py | 4 ++-- .../unit/network/v2/test_security_group_rule_compute.py | 4 ++-- .../unit/network/v2/test_security_group_rule_network.py | 4 ++-- openstackclient/tests/unit/network/v2/test_subnet.py | 4 ++-- openstackclient/tests/unit/network/v2/test_subnet_pool.py | 5 ++--- openstackclient/tests/unit/object/v1/fakes.py | 3 +-- openstackclient/tests/unit/object/v1/test_container.py | 3 +-- openstackclient/tests/unit/object/v1/test_object.py | 3 +-- openstackclient/tests/unit/object/v1/test_object_all.py | 2 +- openstackclient/tests/unit/test_shell.py | 2 +- openstackclient/tests/unit/utils.py | 3 +-- openstackclient/tests/unit/volume/test_find_resource.py | 2 +- openstackclient/tests/unit/volume/v1/fakes.py | 3 +-- openstackclient/tests/unit/volume/v1/test_qos_specs.py | 5 ++--- .../tests/unit/volume/v1/test_transfer_request.py | 4 ++-- openstackclient/tests/unit/volume/v1/test_type.py | 4 ++-- openstackclient/tests/unit/volume/v1/test_volume.py | 5 ++--- openstackclient/tests/unit/volume/v1/test_volume_backup.py | 4 ++-- openstackclient/tests/unit/volume/v2/fakes.py | 3 +-- .../tests/unit/volume/v2/test_consistency_group.py | 4 ++-- .../tests/unit/volume/v2/test_consistency_group_snapshot.py | 2 +- openstackclient/tests/unit/volume/v2/test_qos_specs.py | 5 ++--- .../tests/unit/volume/v2/test_transfer_request.py | 4 ++-- openstackclient/tests/unit/volume/v2/test_type.py | 4 ++-- openstackclient/tests/unit/volume/v2/test_volume.py | 5 ++--- openstackclient/tests/unit/volume/v2/test_volume_backup.py | 4 ++-- test-requirements.txt | 2 +- 103 files changed, 145 insertions(+), 163 deletions(-) diff --git a/openstackclient/api/api.py b/openstackclient/api/api.py index 7e2fe38f0e..d4772f94c3 100644 --- a/openstackclient/api/api.py +++ b/openstackclient/api/api.py @@ -13,11 +13,10 @@ """Base API Library""" -import simplejson as json - from keystoneauth1 import exceptions as ks_exceptions from keystoneauth1 import session as ks_session from osc_lib import exceptions +import simplejson as json from openstackclient.i18n import _ diff --git a/openstackclient/tests/unit/api/test_object_store_v1.py b/openstackclient/tests/unit/api/test_object_store_v1.py index 74b62493db..1c55be7192 100644 --- a/openstackclient/tests/unit/api/test_object_store_v1.py +++ b/openstackclient/tests/unit/api/test_object_store_v1.py @@ -13,7 +13,7 @@ """Object Store v1 API Library Tests""" -import mock +from unittest import mock from keystoneauth1 import session from requests_mock.contrib import fixture diff --git a/openstackclient/tests/unit/common/test_availability_zone.py b/openstackclient/tests/unit/common/test_availability_zone.py index 49e5904dbd..8733b51014 100644 --- a/openstackclient/tests/unit/common/test_availability_zone.py +++ b/openstackclient/tests/unit/common/test_availability_zone.py @@ -11,7 +11,7 @@ # under the License. # -import mock +from unittest import mock from openstackclient.common import availability_zone from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes diff --git a/openstackclient/tests/unit/common/test_command.py b/openstackclient/tests/unit/common/test_command.py index 6ddb7c1220..4fde5301d5 100644 --- a/openstackclient/tests/unit/common/test_command.py +++ b/openstackclient/tests/unit/common/test_command.py @@ -12,7 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. -import mock +from unittest import mock from osc_lib.command import command from osc_lib import exceptions diff --git a/openstackclient/tests/unit/common/test_configuration.py b/openstackclient/tests/unit/common/test_configuration.py index e10522b99c..bdd3debff5 100644 --- a/openstackclient/tests/unit/common/test_configuration.py +++ b/openstackclient/tests/unit/common/test_configuration.py @@ -11,7 +11,7 @@ # under the License. # -import mock +from unittest import mock from openstackclient.common import configuration from openstackclient.tests.unit import fakes diff --git a/openstackclient/tests/unit/common/test_extension.py b/openstackclient/tests/unit/common/test_extension.py index 87c62da499..5093cbbb09 100644 --- a/openstackclient/tests/unit/common/test_extension.py +++ b/openstackclient/tests/unit/common/test_extension.py @@ -11,7 +11,7 @@ # under the License. # -import mock +from unittest import mock from openstackclient.common import extension from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes diff --git a/openstackclient/tests/unit/common/test_logs.py b/openstackclient/tests/unit/common/test_logs.py index 421234d62c..0e7105619d 100644 --- a/openstackclient/tests/unit/common/test_logs.py +++ b/openstackclient/tests/unit/common/test_logs.py @@ -15,8 +15,7 @@ # or Jun 2017. import logging - -import mock +from unittest import mock from osc_lib import logs diff --git a/openstackclient/tests/unit/common/test_module.py b/openstackclient/tests/unit/common/test_module.py index 2491d63965..d2e8293fa3 100644 --- a/openstackclient/tests/unit/common/test_module.py +++ b/openstackclient/tests/unit/common/test_module.py @@ -15,7 +15,7 @@ """Test module module""" -import mock +from unittest import mock from openstackclient.common import module as osc_module from openstackclient.tests.unit import fakes diff --git a/openstackclient/tests/unit/common/test_project_purge.py b/openstackclient/tests/unit/common/test_project_purge.py index 6e8ce188f7..adc48ce26f 100644 --- a/openstackclient/tests/unit/common/test_project_purge.py +++ b/openstackclient/tests/unit/common/test_project_purge.py @@ -10,7 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. -import mock +from unittest import mock from osc_lib import exceptions diff --git a/openstackclient/tests/unit/common/test_quota.py b/openstackclient/tests/unit/common/test_quota.py index 297452a2dc..bd59ca77fe 100644 --- a/openstackclient/tests/unit/common/test_quota.py +++ b/openstackclient/tests/unit/common/test_quota.py @@ -11,8 +11,8 @@ # under the License. import copy +from unittest import mock -import mock from osc_lib import exceptions from openstackclient.common import quota diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index 7357e1430a..6e12f735d4 100644 --- a/openstackclient/tests/unit/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -14,9 +14,9 @@ # import copy +from unittest import mock import uuid -import mock from novaclient import api_versions from openstackclient.api import compute_v2 diff --git a/openstackclient/tests/unit/compute/v2/test_agent.py b/openstackclient/tests/unit/compute/v2/test_agent.py index 169940e29c..c6d4f2b655 100644 --- a/openstackclient/tests/unit/compute/v2/test_agent.py +++ b/openstackclient/tests/unit/compute/v2/test_agent.py @@ -13,8 +13,8 @@ # under the License. # -import mock -from mock import call +from unittest import mock +from unittest.mock import call from osc_lib import exceptions diff --git a/openstackclient/tests/unit/compute/v2/test_aggregate.py b/openstackclient/tests/unit/compute/v2/test_aggregate.py index 0937047cf0..cd0c1525ee 100644 --- a/openstackclient/tests/unit/compute/v2/test_aggregate.py +++ b/openstackclient/tests/unit/compute/v2/test_aggregate.py @@ -13,8 +13,8 @@ # under the License. # -import mock -from mock import call +from unittest import mock +from unittest.mock import call from osc_lib.cli import format_columns from osc_lib import exceptions diff --git a/openstackclient/tests/unit/compute/v2/test_console.py b/openstackclient/tests/unit/compute/v2/test_console.py index 3c708aaee3..99a14f048d 100644 --- a/openstackclient/tests/unit/compute/v2/test_console.py +++ b/openstackclient/tests/unit/compute/v2/test_console.py @@ -13,7 +13,7 @@ # under the License. # -import mock +from unittest import mock from openstackclient.compute.v2 import console from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes diff --git a/openstackclient/tests/unit/compute/v2/test_flavor.py b/openstackclient/tests/unit/compute/v2/test_flavor.py index a112fc1fae..fe7ce17481 100644 --- a/openstackclient/tests/unit/compute/v2/test_flavor.py +++ b/openstackclient/tests/unit/compute/v2/test_flavor.py @@ -13,8 +13,8 @@ # under the License. # -import mock -from mock import call +from unittest import mock +from unittest.mock import call import novaclient from osc_lib import exceptions diff --git a/openstackclient/tests/unit/compute/v2/test_host.py b/openstackclient/tests/unit/compute/v2/test_host.py index 244da41317..4e1b5ad15b 100644 --- a/openstackclient/tests/unit/compute/v2/test_host.py +++ b/openstackclient/tests/unit/compute/v2/test_host.py @@ -13,7 +13,7 @@ # under the License. # -import mock +from unittest import mock from openstackclient.compute.v2 import host from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes diff --git a/openstackclient/tests/unit/compute/v2/test_keypair.py b/openstackclient/tests/unit/compute/v2/test_keypair.py index 0e5fb14324..1f3f56f989 100644 --- a/openstackclient/tests/unit/compute/v2/test_keypair.py +++ b/openstackclient/tests/unit/compute/v2/test_keypair.py @@ -13,10 +13,10 @@ # under the License. # +from unittest import mock +from unittest.mock import call import uuid -import mock -from mock import call from osc_lib import exceptions from osc_lib import utils diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index c2bac2771f..b3be514724 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -16,9 +16,9 @@ import collections import copy import getpass +from unittest import mock +from unittest.mock import call -import mock -from mock import call from novaclient import api_versions from openstack import exceptions as sdk_exceptions from osc_lib import exceptions diff --git a/openstackclient/tests/unit/compute/v2/test_server_backup.py b/openstackclient/tests/unit/compute/v2/test_server_backup.py index 24a94531a4..7dd459d823 100644 --- a/openstackclient/tests/unit/compute/v2/test_server_backup.py +++ b/openstackclient/tests/unit/compute/v2/test_server_backup.py @@ -11,7 +11,7 @@ # under the License. # -import mock +from unittest import mock from osc_lib.cli import format_columns from osc_lib import exceptions diff --git a/openstackclient/tests/unit/compute/v2/test_server_group.py b/openstackclient/tests/unit/compute/v2/test_server_group.py index dc924e2421..9cd876ead1 100644 --- a/openstackclient/tests/unit/compute/v2/test_server_group.py +++ b/openstackclient/tests/unit/compute/v2/test_server_group.py @@ -13,7 +13,7 @@ # under the License. # -import mock +from unittest import mock from osc_lib import exceptions from osc_lib import utils diff --git a/openstackclient/tests/unit/compute/v2/test_server_image.py b/openstackclient/tests/unit/compute/v2/test_server_image.py index 02e43129ed..f9d7b10e75 100644 --- a/openstackclient/tests/unit/compute/v2/test_server_image.py +++ b/openstackclient/tests/unit/compute/v2/test_server_image.py @@ -10,7 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. # -import mock +from unittest import mock from osc_lib.cli import format_columns from osc_lib import exceptions diff --git a/openstackclient/tests/unit/compute/v2/test_service.py b/openstackclient/tests/unit/compute/v2/test_service.py index 0d663b2e67..7a03683364 100644 --- a/openstackclient/tests/unit/compute/v2/test_service.py +++ b/openstackclient/tests/unit/compute/v2/test_service.py @@ -13,8 +13,9 @@ # under the License. # -import mock -from mock import call +from unittest import mock +from unittest.mock import call + from novaclient import api_versions from osc_lib import exceptions import six diff --git a/openstackclient/tests/unit/compute/v2/test_usage.py b/openstackclient/tests/unit/compute/v2/test_usage.py index 76dcc963fb..c08710256b 100644 --- a/openstackclient/tests/unit/compute/v2/test_usage.py +++ b/openstackclient/tests/unit/compute/v2/test_usage.py @@ -12,8 +12,8 @@ # import datetime +from unittest import mock -import mock from novaclient import api_versions from openstackclient.compute.v2 import usage diff --git a/openstackclient/tests/unit/fakes.py b/openstackclient/tests/unit/fakes.py index 59cbbe1055..e5476f06bb 100644 --- a/openstackclient/tests/unit/fakes.py +++ b/openstackclient/tests/unit/fakes.py @@ -15,9 +15,9 @@ import json import sys +from unittest import mock from keystoneauth1 import fixture -import mock import requests import six diff --git a/openstackclient/tests/unit/identity/v2_0/fakes.py b/openstackclient/tests/unit/identity/v2_0/fakes.py index 5db9422275..bd76a784a0 100644 --- a/openstackclient/tests/unit/identity/v2_0/fakes.py +++ b/openstackclient/tests/unit/identity/v2_0/fakes.py @@ -14,11 +14,11 @@ # import copy +from unittest import mock import uuid from keystoneauth1 import access from keystoneauth1 import fixture -import mock from openstackclient.tests.unit import fakes from openstackclient.tests.unit import utils diff --git a/openstackclient/tests/unit/identity/v2_0/test_catalog.py b/openstackclient/tests/unit/identity/v2_0/test_catalog.py index 362dec0885..1735507469 100644 --- a/openstackclient/tests/unit/identity/v2_0/test_catalog.py +++ b/openstackclient/tests/unit/identity/v2_0/test_catalog.py @@ -11,7 +11,7 @@ # under the License. # -import mock +from unittest import mock from openstackclient.identity.v2_0 import catalog from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes diff --git a/openstackclient/tests/unit/identity/v2_0/test_project.py b/openstackclient/tests/unit/identity/v2_0/test_project.py index 7af7b3946e..cd8c825d15 100644 --- a/openstackclient/tests/unit/identity/v2_0/test_project.py +++ b/openstackclient/tests/unit/identity/v2_0/test_project.py @@ -13,7 +13,7 @@ # under the License. # -import mock +from unittest import mock from keystoneauth1 import exceptions as ks_exc from osc_lib.cli import format_columns diff --git a/openstackclient/tests/unit/identity/v2_0/test_role.py b/openstackclient/tests/unit/identity/v2_0/test_role.py index 643d77f600..423884d9f5 100644 --- a/openstackclient/tests/unit/identity/v2_0/test_role.py +++ b/openstackclient/tests/unit/identity/v2_0/test_role.py @@ -13,7 +13,7 @@ # under the License. # -import mock +from unittest import mock from keystoneauth1 import exceptions as ks_exc from osc_lib import exceptions diff --git a/openstackclient/tests/unit/identity/v2_0/test_role_assignment.py b/openstackclient/tests/unit/identity/v2_0/test_role_assignment.py index 733fda6cb1..3e1231aa30 100644 --- a/openstackclient/tests/unit/identity/v2_0/test_role_assignment.py +++ b/openstackclient/tests/unit/identity/v2_0/test_role_assignment.py @@ -12,8 +12,8 @@ # import copy +from unittest import mock -import mock from osc_lib import exceptions from openstackclient.identity.v2_0 import role_assignment diff --git a/openstackclient/tests/unit/identity/v2_0/test_token.py b/openstackclient/tests/unit/identity/v2_0/test_token.py index dd7f4f4ab4..c079ce6796 100644 --- a/openstackclient/tests/unit/identity/v2_0/test_token.py +++ b/openstackclient/tests/unit/identity/v2_0/test_token.py @@ -13,7 +13,7 @@ # under the License. # -import mock +from unittest import mock from openstackclient.identity.v2_0 import token from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes diff --git a/openstackclient/tests/unit/identity/v2_0/test_user.py b/openstackclient/tests/unit/identity/v2_0/test_user.py index 0a0d4b3651..4308b05d05 100644 --- a/openstackclient/tests/unit/identity/v2_0/test_user.py +++ b/openstackclient/tests/unit/identity/v2_0/test_user.py @@ -13,7 +13,7 @@ # under the License. # -import mock +from unittest import mock from keystoneauth1 import exceptions as ks_exc from osc_lib import exceptions diff --git a/openstackclient/tests/unit/identity/v3/fakes.py b/openstackclient/tests/unit/identity/v3/fakes.py index e9ff068944..c394ab8237 100644 --- a/openstackclient/tests/unit/identity/v3/fakes.py +++ b/openstackclient/tests/unit/identity/v3/fakes.py @@ -15,11 +15,11 @@ import copy import datetime +from unittest import mock import uuid from keystoneauth1 import access from keystoneauth1 import fixture -import mock from osc_lib.cli import format_columns from openstackclient.tests.unit import fakes diff --git a/openstackclient/tests/unit/identity/v3/test_application_credential.py b/openstackclient/tests/unit/identity/v3/test_application_credential.py index e7c8ede826..163aae9db9 100644 --- a/openstackclient/tests/unit/identity/v3/test_application_credential.py +++ b/openstackclient/tests/unit/identity/v3/test_application_credential.py @@ -14,8 +14,8 @@ # import copy +from unittest import mock -import mock from osc_lib import exceptions from osc_lib import utils diff --git a/openstackclient/tests/unit/identity/v3/test_catalog.py b/openstackclient/tests/unit/identity/v3/test_catalog.py index ba076dbddf..3630ccb6a3 100644 --- a/openstackclient/tests/unit/identity/v3/test_catalog.py +++ b/openstackclient/tests/unit/identity/v3/test_catalog.py @@ -11,7 +11,7 @@ # under the License. # -import mock +from unittest import mock from openstackclient.identity.v3 import catalog from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes diff --git a/openstackclient/tests/unit/identity/v3/test_credential.py b/openstackclient/tests/unit/identity/v3/test_credential.py index de0306dd68..40596d5874 100644 --- a/openstackclient/tests/unit/identity/v3/test_credential.py +++ b/openstackclient/tests/unit/identity/v3/test_credential.py @@ -10,8 +10,8 @@ # License for the specific language governing permissions and limitations # under the License. -import mock -from mock import call +from unittest import mock +from unittest.mock import call from osc_lib import exceptions diff --git a/openstackclient/tests/unit/identity/v3/test_endpoint_group.py b/openstackclient/tests/unit/identity/v3/test_endpoint_group.py index 6e9da9c78e..c081fa1f11 100644 --- a/openstackclient/tests/unit/identity/v3/test_endpoint_group.py +++ b/openstackclient/tests/unit/identity/v3/test_endpoint_group.py @@ -11,7 +11,7 @@ # under the License. # -import mock +from unittest import mock from openstackclient.identity.v3 import endpoint_group from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes diff --git a/openstackclient/tests/unit/identity/v3/test_group.py b/openstackclient/tests/unit/identity/v3/test_group.py index 81722631b3..04ba0dbe03 100644 --- a/openstackclient/tests/unit/identity/v3/test_group.py +++ b/openstackclient/tests/unit/identity/v3/test_group.py @@ -11,8 +11,8 @@ # under the License. # -import mock -from mock import call +from unittest import mock +from unittest.mock import call from keystoneauth1 import exceptions as ks_exc from osc_lib import exceptions diff --git a/openstackclient/tests/unit/identity/v3/test_identity_provider.py b/openstackclient/tests/unit/identity/v3/test_identity_provider.py index 0163c6c8d4..a419a9bcb1 100644 --- a/openstackclient/tests/unit/identity/v3/test_identity_provider.py +++ b/openstackclient/tests/unit/identity/v3/test_identity_provider.py @@ -13,8 +13,7 @@ # under the License. import copy - -import mock +from unittest import mock from openstackclient.identity.v3 import identity_provider from openstackclient.tests.unit import fakes diff --git a/openstackclient/tests/unit/identity/v3/test_mappings.py b/openstackclient/tests/unit/identity/v3/test_mappings.py index 1d8e77d9e8..184bd2a265 100644 --- a/openstackclient/tests/unit/identity/v3/test_mappings.py +++ b/openstackclient/tests/unit/identity/v3/test_mappings.py @@ -13,8 +13,8 @@ # under the License. import copy +from unittest import mock -import mock from osc_lib import exceptions from openstackclient.identity.v3 import mapping diff --git a/openstackclient/tests/unit/identity/v3/test_project.py b/openstackclient/tests/unit/identity/v3/test_project.py index db27fedcdf..466bea1867 100644 --- a/openstackclient/tests/unit/identity/v3/test_project.py +++ b/openstackclient/tests/unit/identity/v3/test_project.py @@ -13,8 +13,8 @@ # under the License. # -import mock -from mock import call +from unittest import mock +from unittest.mock import call from osc_lib import exceptions from osc_lib import utils diff --git a/openstackclient/tests/unit/identity/v3/test_role.py b/openstackclient/tests/unit/identity/v3/test_role.py index 99f3a2de74..ead2cb58bb 100644 --- a/openstackclient/tests/unit/identity/v3/test_role.py +++ b/openstackclient/tests/unit/identity/v3/test_role.py @@ -14,8 +14,8 @@ # import copy +from unittest import mock -import mock from osc_lib import exceptions from osc_lib import utils diff --git a/openstackclient/tests/unit/identity/v3/test_role_assignment.py b/openstackclient/tests/unit/identity/v3/test_role_assignment.py index bff6c56df1..7d38d360b0 100644 --- a/openstackclient/tests/unit/identity/v3/test_role_assignment.py +++ b/openstackclient/tests/unit/identity/v3/test_role_assignment.py @@ -12,8 +12,7 @@ # import copy - -import mock +from unittest import mock from openstackclient.identity.v3 import role_assignment from openstackclient.tests.unit import fakes diff --git a/openstackclient/tests/unit/identity/v3/test_token.py b/openstackclient/tests/unit/identity/v3/test_token.py index 7321909f47..adb491b30f 100644 --- a/openstackclient/tests/unit/identity/v3/test_token.py +++ b/openstackclient/tests/unit/identity/v3/test_token.py @@ -13,7 +13,7 @@ # under the License. # -import mock +from unittest import mock from openstackclient.identity.v3 import token from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes diff --git a/openstackclient/tests/unit/identity/v3/test_trust.py b/openstackclient/tests/unit/identity/v3/test_trust.py index 1355b9089b..d8cfc59fe8 100644 --- a/openstackclient/tests/unit/identity/v3/test_trust.py +++ b/openstackclient/tests/unit/identity/v3/test_trust.py @@ -12,8 +12,8 @@ # import copy +from unittest import mock -import mock from osc_lib import exceptions from osc_lib import utils diff --git a/openstackclient/tests/unit/identity/v3/test_user.py b/openstackclient/tests/unit/identity/v3/test_user.py index 920ee95051..4b14bca05a 100644 --- a/openstackclient/tests/unit/identity/v3/test_user.py +++ b/openstackclient/tests/unit/identity/v3/test_user.py @@ -14,8 +14,8 @@ # import contextlib +from unittest import mock -import mock from osc_lib import exceptions from osc_lib import utils diff --git a/openstackclient/tests/unit/image/v1/fakes.py b/openstackclient/tests/unit/image/v1/fakes.py index bbec00fc06..de23223572 100644 --- a/openstackclient/tests/unit/image/v1/fakes.py +++ b/openstackclient/tests/unit/image/v1/fakes.py @@ -14,10 +14,9 @@ # import copy +from unittest import mock import uuid -import mock - from openstackclient.tests.unit import fakes from openstackclient.tests.unit import utils from openstackclient.tests.unit.volume.v1 import fakes as volume_fakes diff --git a/openstackclient/tests/unit/image/v1/test_image.py b/openstackclient/tests/unit/image/v1/test_image.py index 0997d76546..970b36c68a 100644 --- a/openstackclient/tests/unit/image/v1/test_image.py +++ b/openstackclient/tests/unit/image/v1/test_image.py @@ -14,8 +14,7 @@ # import copy - -import mock +from unittest import mock from osc_lib.cli import format_columns from osc_lib import exceptions diff --git a/openstackclient/tests/unit/image/v2/fakes.py b/openstackclient/tests/unit/image/v2/fakes.py index f69a2bc311..655ae341c6 100644 --- a/openstackclient/tests/unit/image/v2/fakes.py +++ b/openstackclient/tests/unit/image/v2/fakes.py @@ -15,10 +15,10 @@ import copy import random +from unittest import mock import uuid from glanceclient.v2 import schemas -import mock from osc_lib.cli import format_columns import warlock diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py index 748a61aaf4..78d857e269 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -14,10 +14,10 @@ # import copy +from unittest import mock from glanceclient.common import utils as glanceclient_utils from glanceclient.v2 import schemas -import mock from osc_lib.cli import format_columns from osc_lib import exceptions import warlock diff --git a/openstackclient/tests/unit/integ/cli/test_shell.py b/openstackclient/tests/unit/integ/cli/test_shell.py index 2598517113..0c98a12942 100644 --- a/openstackclient/tests/unit/integ/cli/test_shell.py +++ b/openstackclient/tests/unit/integ/cli/test_shell.py @@ -11,9 +11,9 @@ # under the License. import copy +from unittest import mock import fixtures -import mock from osc_lib.tests import utils as osc_lib_utils from openstackclient import shell diff --git a/openstackclient/tests/unit/network/test_common.py b/openstackclient/tests/unit/network/test_common.py index 3a20687824..cde321aa23 100644 --- a/openstackclient/tests/unit/network/test_common.py +++ b/openstackclient/tests/unit/network/test_common.py @@ -12,8 +12,8 @@ # import argparse +from unittest import mock -import mock import openstack from osc_lib import exceptions diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index 35f0b1a59c..5809b225e1 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -15,10 +15,9 @@ import copy from random import choice from random import randint +from unittest import mock import uuid -import mock - from openstackclient.tests.unit import fakes from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3 from openstackclient.tests.unit import utils diff --git a/openstackclient/tests/unit/network/v2/test_address_scope.py b/openstackclient/tests/unit/network/v2/test_address_scope.py index 400671881f..17f13e8351 100644 --- a/openstackclient/tests/unit/network/v2/test_address_scope.py +++ b/openstackclient/tests/unit/network/v2/test_address_scope.py @@ -11,8 +11,8 @@ # under the License. # -import mock -from mock import call +from unittest import mock +from unittest.mock import call from osc_lib import exceptions diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip_compute.py b/openstackclient/tests/unit/network/v2/test_floating_ip_compute.py index df47e63e91..18212cf707 100644 --- a/openstackclient/tests/unit/network/v2/test_floating_ip_compute.py +++ b/openstackclient/tests/unit/network/v2/test_floating_ip_compute.py @@ -11,8 +11,8 @@ # under the License. # -import mock -from mock import call +from unittest import mock +from unittest.mock import call from osc_lib import exceptions diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip_network.py b/openstackclient/tests/unit/network/v2/test_floating_ip_network.py index cbd4da38ca..a98051e769 100644 --- a/openstackclient/tests/unit/network/v2/test_floating_ip_network.py +++ b/openstackclient/tests/unit/network/v2/test_floating_ip_network.py @@ -11,8 +11,8 @@ # under the License. # -import mock -from mock import call +from unittest import mock +from unittest.mock import call from osc_lib import exceptions diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip_pool_compute.py b/openstackclient/tests/unit/network/v2/test_floating_ip_pool_compute.py index 591f58ca4e..3dd99362c1 100644 --- a/openstackclient/tests/unit/network/v2/test_floating_ip_pool_compute.py +++ b/openstackclient/tests/unit/network/v2/test_floating_ip_pool_compute.py @@ -11,7 +11,7 @@ # under the License. # -import mock +from unittest import mock from openstackclient.network.v2 import floating_ip_pool from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip_port_forwarding.py b/openstackclient/tests/unit/network/v2/test_floating_ip_port_forwarding.py index b51158be37..ea6cdd266f 100644 --- a/openstackclient/tests/unit/network/v2/test_floating_ip_port_forwarding.py +++ b/openstackclient/tests/unit/network/v2/test_floating_ip_port_forwarding.py @@ -13,8 +13,8 @@ # License for the specific language governing permissions and limitations # under the License. -import mock -from mock import call +from unittest import mock +from unittest.mock import call from osc_lib import exceptions diff --git a/openstackclient/tests/unit/network/v2/test_ip_availability.py b/openstackclient/tests/unit/network/v2/test_ip_availability.py index 21508a8df1..9a712704c6 100644 --- a/openstackclient/tests/unit/network/v2/test_ip_availability.py +++ b/openstackclient/tests/unit/network/v2/test_ip_availability.py @@ -11,7 +11,7 @@ # under the License. # -import mock +from unittest import mock from osc_lib.cli import format_columns diff --git a/openstackclient/tests/unit/network/v2/test_network.py b/openstackclient/tests/unit/network/v2/test_network.py index 5c97c363f5..5f8eed6702 100644 --- a/openstackclient/tests/unit/network/v2/test_network.py +++ b/openstackclient/tests/unit/network/v2/test_network.py @@ -12,9 +12,8 @@ # import random - -import mock -from mock import call +from unittest import mock +from unittest.mock import call from osc_lib.cli import format_columns from osc_lib import exceptions diff --git a/openstackclient/tests/unit/network/v2/test_network_agent.py b/openstackclient/tests/unit/network/v2/test_network_agent.py index 8500d08ea6..3181ee78a1 100644 --- a/openstackclient/tests/unit/network/v2/test_network_agent.py +++ b/openstackclient/tests/unit/network/v2/test_network_agent.py @@ -11,8 +11,8 @@ # under the License. # -import mock -from mock import call +from unittest import mock +from unittest.mock import call from osc_lib.cli import format_columns from osc_lib import exceptions diff --git a/openstackclient/tests/unit/network/v2/test_network_auto_allocated_topology.py b/openstackclient/tests/unit/network/v2/test_network_auto_allocated_topology.py index 1a231160dd..e9687a70d3 100644 --- a/openstackclient/tests/unit/network/v2/test_network_auto_allocated_topology.py +++ b/openstackclient/tests/unit/network/v2/test_network_auto_allocated_topology.py @@ -13,7 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. -import mock +from unittest import mock from openstackclient.network.v2 import network_auto_allocated_topology from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes diff --git a/openstackclient/tests/unit/network/v2/test_network_compute.py b/openstackclient/tests/unit/network/v2/test_network_compute.py index c649401c41..89330fffe9 100644 --- a/openstackclient/tests/unit/network/v2/test_network_compute.py +++ b/openstackclient/tests/unit/network/v2/test_network_compute.py @@ -11,8 +11,8 @@ # under the License. # -import mock -from mock import call +from unittest import mock +from unittest.mock import call from osc_lib import exceptions diff --git a/openstackclient/tests/unit/network/v2/test_network_flavor.py b/openstackclient/tests/unit/network/v2/test_network_flavor.py index 896a172530..010f53d33c 100644 --- a/openstackclient/tests/unit/network/v2/test_network_flavor.py +++ b/openstackclient/tests/unit/network/v2/test_network_flavor.py @@ -14,7 +14,7 @@ # under the License. # -import mock +from unittest import mock from osc_lib import exceptions diff --git a/openstackclient/tests/unit/network/v2/test_network_flavor_profile.py b/openstackclient/tests/unit/network/v2/test_network_flavor_profile.py index 91683241c2..fcf24da9f2 100644 --- a/openstackclient/tests/unit/network/v2/test_network_flavor_profile.py +++ b/openstackclient/tests/unit/network/v2/test_network_flavor_profile.py @@ -10,7 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. -import mock +from unittest import mock from osc_lib import exceptions diff --git a/openstackclient/tests/unit/network/v2/test_network_meter.py b/openstackclient/tests/unit/network/v2/test_network_meter.py index 2b96f7a680..4fadcfe1f8 100644 --- a/openstackclient/tests/unit/network/v2/test_network_meter.py +++ b/openstackclient/tests/unit/network/v2/test_network_meter.py @@ -13,8 +13,8 @@ # License for the specific language governing permissions and limitations # under the License. -import mock -from mock import call +from unittest import mock +from unittest.mock import call from osc_lib import exceptions diff --git a/openstackclient/tests/unit/network/v2/test_network_meter_rule.py b/openstackclient/tests/unit/network/v2/test_network_meter_rule.py index af481793d7..8f8922c020 100644 --- a/openstackclient/tests/unit/network/v2/test_network_meter_rule.py +++ b/openstackclient/tests/unit/network/v2/test_network_meter_rule.py @@ -13,8 +13,8 @@ # License for the specific language governing permissions and limitations # under the License. -import mock -from mock import call +from unittest import mock +from unittest.mock import call from osc_lib import exceptions diff --git a/openstackclient/tests/unit/network/v2/test_network_qos_policy.py b/openstackclient/tests/unit/network/v2/test_network_qos_policy.py index e7239932a5..d6a78410d4 100644 --- a/openstackclient/tests/unit/network/v2/test_network_qos_policy.py +++ b/openstackclient/tests/unit/network/v2/test_network_qos_policy.py @@ -13,8 +13,8 @@ # License for the specific language governing permissions and limitations # under the License. -import mock -from mock import call +from unittest import mock +from unittest.mock import call from osc_lib import exceptions diff --git a/openstackclient/tests/unit/network/v2/test_network_qos_rule.py b/openstackclient/tests/unit/network/v2/test_network_qos_rule.py index 5b54d318ac..217e481ea7 100644 --- a/openstackclient/tests/unit/network/v2/test_network_qos_rule.py +++ b/openstackclient/tests/unit/network/v2/test_network_qos_rule.py @@ -13,7 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. -import mock +from unittest import mock from osc_lib import exceptions diff --git a/openstackclient/tests/unit/network/v2/test_network_qos_rule_type.py b/openstackclient/tests/unit/network/v2/test_network_qos_rule_type.py index 80c52bf72e..08a83fabda 100644 --- a/openstackclient/tests/unit/network/v2/test_network_qos_rule_type.py +++ b/openstackclient/tests/unit/network/v2/test_network_qos_rule_type.py @@ -13,7 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. -import mock +from unittest import mock from openstackclient.network.v2 import network_qos_rule_type as _qos_rule_type from openstackclient.tests.unit.network.v2 import fakes as network_fakes diff --git a/openstackclient/tests/unit/network/v2/test_network_rbac.py b/openstackclient/tests/unit/network/v2/test_network_rbac.py index 96440091c1..078188ceb5 100644 --- a/openstackclient/tests/unit/network/v2/test_network_rbac.py +++ b/openstackclient/tests/unit/network/v2/test_network_rbac.py @@ -11,8 +11,8 @@ # under the License. # -import mock -from mock import call +from unittest import mock +from unittest.mock import call from osc_lib import exceptions diff --git a/openstackclient/tests/unit/network/v2/test_network_segment.py b/openstackclient/tests/unit/network/v2/test_network_segment.py index 0639766d26..6cd948e3bb 100644 --- a/openstackclient/tests/unit/network/v2/test_network_segment.py +++ b/openstackclient/tests/unit/network/v2/test_network_segment.py @@ -11,8 +11,8 @@ # under the License. # -import mock -from mock import call +from unittest import mock +from unittest.mock import call from osc_lib import exceptions diff --git a/openstackclient/tests/unit/network/v2/test_network_segment_range.py b/openstackclient/tests/unit/network/v2/test_network_segment_range.py index 22e25df133..89a0c223ce 100644 --- a/openstackclient/tests/unit/network/v2/test_network_segment_range.py +++ b/openstackclient/tests/unit/network/v2/test_network_segment_range.py @@ -14,8 +14,8 @@ # under the License. # -import mock -from mock import call +from unittest import mock +from unittest.mock import call from osc_lib import exceptions diff --git a/openstackclient/tests/unit/network/v2/test_network_service_provider.py b/openstackclient/tests/unit/network/v2/test_network_service_provider.py index 5ba85ddb9a..5e4ddea6cf 100644 --- a/openstackclient/tests/unit/network/v2/test_network_service_provider.py +++ b/openstackclient/tests/unit/network/v2/test_network_service_provider.py @@ -13,7 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. -import mock +from unittest import mock from openstackclient.network.v2 import network_service_provider \ as service_provider diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index c30d682f59..d64cc79d56 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -12,9 +12,9 @@ # import argparse +from unittest import mock +from unittest.mock import call -import mock -from mock import call from osc_lib.cli import format_columns from osc_lib import exceptions from osc_lib import utils diff --git a/openstackclient/tests/unit/network/v2/test_router.py b/openstackclient/tests/unit/network/v2/test_router.py index 500cfbe5a3..38861b0ad5 100644 --- a/openstackclient/tests/unit/network/v2/test_router.py +++ b/openstackclient/tests/unit/network/v2/test_router.py @@ -11,8 +11,8 @@ # under the License. # -import mock -from mock import call +from unittest import mock +from unittest.mock import call from osc_lib.cli import format_columns from osc_lib import exceptions diff --git a/openstackclient/tests/unit/network/v2/test_security_group_compute.py b/openstackclient/tests/unit/network/v2/test_security_group_compute.py index df36006899..b4ddcf803d 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_compute.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_compute.py @@ -11,8 +11,8 @@ # under the License. # -import mock -from mock import call +from unittest import mock +from unittest.mock import call from osc_lib import exceptions diff --git a/openstackclient/tests/unit/network/v2/test_security_group_network.py b/openstackclient/tests/unit/network/v2/test_security_group_network.py index 57698ec586..14d5751436 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_network.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_network.py @@ -11,8 +11,8 @@ # under the License. # -import mock -from mock import call +from unittest import mock +from unittest.mock import call from osc_lib import exceptions diff --git a/openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py b/openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py index cf5261b284..5720e30584 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py @@ -11,8 +11,8 @@ # under the License. # -import mock -from mock import call +from unittest import mock +from unittest.mock import call from osc_lib import exceptions diff --git a/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py b/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py index 49c3d5dbec..0a9522b0bc 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py @@ -11,8 +11,8 @@ # under the License. # -import mock -from mock import call +from unittest import mock +from unittest.mock import call from osc_lib import exceptions diff --git a/openstackclient/tests/unit/network/v2/test_subnet.py b/openstackclient/tests/unit/network/v2/test_subnet.py index 9903b0423a..e71e1dd6ed 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet.py +++ b/openstackclient/tests/unit/network/v2/test_subnet.py @@ -11,8 +11,8 @@ # under the License. # -import mock -from mock import call +from unittest import mock +from unittest.mock import call from osc_lib.cli import format_columns from osc_lib import exceptions diff --git a/openstackclient/tests/unit/network/v2/test_subnet_pool.py b/openstackclient/tests/unit/network/v2/test_subnet_pool.py index 2271c08944..eb454646de 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/unit/network/v2/test_subnet_pool.py @@ -12,9 +12,8 @@ # import argparse - -import mock -from mock import call +from unittest import mock +from unittest.mock import call from osc_lib.cli import format_columns from osc_lib import exceptions diff --git a/openstackclient/tests/unit/object/v1/fakes.py b/openstackclient/tests/unit/object/v1/fakes.py index 5d65d1066f..0ed791a5bc 100644 --- a/openstackclient/tests/unit/object/v1/fakes.py +++ b/openstackclient/tests/unit/object/v1/fakes.py @@ -13,9 +13,8 @@ # under the License. # -import six - from keystoneauth1 import session +import six from openstackclient.api import object_store_v1 as object_store from openstackclient.tests.unit import utils diff --git a/openstackclient/tests/unit/object/v1/test_container.py b/openstackclient/tests/unit/object/v1/test_container.py index 39e2d80f4a..7d3cc8d840 100644 --- a/openstackclient/tests/unit/object/v1/test_container.py +++ b/openstackclient/tests/unit/object/v1/test_container.py @@ -14,8 +14,7 @@ # import copy - -import mock +from unittest import mock from openstackclient.api import object_store_v1 as object_store from openstackclient.object.v1 import container diff --git a/openstackclient/tests/unit/object/v1/test_object.py b/openstackclient/tests/unit/object/v1/test_object.py index b629937351..fc3073c8bc 100644 --- a/openstackclient/tests/unit/object/v1/test_object.py +++ b/openstackclient/tests/unit/object/v1/test_object.py @@ -14,8 +14,7 @@ # import copy - -import mock +from unittest import mock from openstackclient.api import object_store_v1 as object_store from openstackclient.object.v1 import object as obj diff --git a/openstackclient/tests/unit/object/v1/test_object_all.py b/openstackclient/tests/unit/object/v1/test_object_all.py index 08a7534d06..dd587142fa 100644 --- a/openstackclient/tests/unit/object/v1/test_object_all.py +++ b/openstackclient/tests/unit/object/v1/test_object_all.py @@ -12,8 +12,8 @@ # import copy +from unittest import mock -import mock from osc_lib import exceptions from requests_mock.contrib import fixture import six diff --git a/openstackclient/tests/unit/test_shell.py b/openstackclient/tests/unit/test_shell.py index 31675c47e6..94f4f44d24 100644 --- a/openstackclient/tests/unit/test_shell.py +++ b/openstackclient/tests/unit/test_shell.py @@ -15,8 +15,8 @@ import os import sys +from unittest import mock -import mock from osc_lib.tests import utils as osc_lib_test_utils from oslo_utils import importutils import wrapt diff --git a/openstackclient/tests/unit/utils.py b/openstackclient/tests/unit/utils.py index 8df81a50eb..4f1bc46a98 100644 --- a/openstackclient/tests/unit/utils.py +++ b/openstackclient/tests/unit/utils.py @@ -16,12 +16,11 @@ import os +from cliff import columns as cliff_columns import fixtures from six.moves import StringIO import testtools -from cliff import columns as cliff_columns - from openstackclient.tests.unit import fakes diff --git a/openstackclient/tests/unit/volume/test_find_resource.py b/openstackclient/tests/unit/volume/test_find_resource.py index 60591eff7f..208f55b94e 100644 --- a/openstackclient/tests/unit/volume/test_find_resource.py +++ b/openstackclient/tests/unit/volume/test_find_resource.py @@ -13,7 +13,7 @@ # under the License. # -import mock +from unittest import mock from cinderclient.v3 import volume_snapshots from cinderclient.v3 import volumes diff --git a/openstackclient/tests/unit/volume/v1/fakes.py b/openstackclient/tests/unit/volume/v1/fakes.py index de9c724f1e..adb775edf2 100644 --- a/openstackclient/tests/unit/volume/v1/fakes.py +++ b/openstackclient/tests/unit/volume/v1/fakes.py @@ -15,10 +15,9 @@ import copy import random +from unittest import mock import uuid -import mock - from openstackclient.tests.unit import fakes from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes from openstackclient.tests.unit import utils diff --git a/openstackclient/tests/unit/volume/v1/test_qos_specs.py b/openstackclient/tests/unit/volume/v1/test_qos_specs.py index 11dc80844a..83c533b6f0 100644 --- a/openstackclient/tests/unit/volume/v1/test_qos_specs.py +++ b/openstackclient/tests/unit/volume/v1/test_qos_specs.py @@ -14,9 +14,8 @@ # import copy - -import mock -from mock import call +from unittest import mock +from unittest.mock import call from osc_lib.cli import format_columns from osc_lib import exceptions diff --git a/openstackclient/tests/unit/volume/v1/test_transfer_request.py b/openstackclient/tests/unit/volume/v1/test_transfer_request.py index 680561d522..333bf52688 100644 --- a/openstackclient/tests/unit/volume/v1/test_transfer_request.py +++ b/openstackclient/tests/unit/volume/v1/test_transfer_request.py @@ -12,8 +12,8 @@ # under the License. # -import mock -from mock import call +from unittest import mock +from unittest.mock import call from osc_lib import exceptions from osc_lib import utils diff --git a/openstackclient/tests/unit/volume/v1/test_type.py b/openstackclient/tests/unit/volume/v1/test_type.py index beff83364c..8bee574795 100644 --- a/openstackclient/tests/unit/volume/v1/test_type.py +++ b/openstackclient/tests/unit/volume/v1/test_type.py @@ -12,8 +12,8 @@ # under the License. # -import mock -from mock import call +from unittest import mock +from unittest.mock import call from osc_lib.cli import format_columns from osc_lib import exceptions diff --git a/openstackclient/tests/unit/volume/v1/test_volume.py b/openstackclient/tests/unit/volume/v1/test_volume.py index c415455534..25cdf92a43 100644 --- a/openstackclient/tests/unit/volume/v1/test_volume.py +++ b/openstackclient/tests/unit/volume/v1/test_volume.py @@ -14,9 +14,8 @@ # import argparse - -import mock -from mock import call +from unittest import mock +from unittest.mock import call from osc_lib.cli import format_columns from osc_lib import exceptions diff --git a/openstackclient/tests/unit/volume/v1/test_volume_backup.py b/openstackclient/tests/unit/volume/v1/test_volume_backup.py index 20be6e46d7..20aadcd344 100644 --- a/openstackclient/tests/unit/volume/v1/test_volume_backup.py +++ b/openstackclient/tests/unit/volume/v1/test_volume_backup.py @@ -12,8 +12,8 @@ # under the License. # -import mock -from mock import call +from unittest import mock +from unittest.mock import call from osc_lib import exceptions from osc_lib import utils diff --git a/openstackclient/tests/unit/volume/v2/fakes.py b/openstackclient/tests/unit/volume/v2/fakes.py index 5f976b0625..5f18990e96 100644 --- a/openstackclient/tests/unit/volume/v2/fakes.py +++ b/openstackclient/tests/unit/volume/v2/fakes.py @@ -14,10 +14,9 @@ import copy import random +from unittest import mock import uuid -import mock - from osc_lib.cli import format_columns from openstackclient.tests.unit import fakes diff --git a/openstackclient/tests/unit/volume/v2/test_consistency_group.py b/openstackclient/tests/unit/volume/v2/test_consistency_group.py index d238818245..c3bd71e325 100644 --- a/openstackclient/tests/unit/volume/v2/test_consistency_group.py +++ b/openstackclient/tests/unit/volume/v2/test_consistency_group.py @@ -12,8 +12,8 @@ # under the License. # -import mock -from mock import call +from unittest import mock +from unittest.mock import call from osc_lib.cli import format_columns from osc_lib import exceptions diff --git a/openstackclient/tests/unit/volume/v2/test_consistency_group_snapshot.py b/openstackclient/tests/unit/volume/v2/test_consistency_group_snapshot.py index 3bfe93df04..2202b85b98 100644 --- a/openstackclient/tests/unit/volume/v2/test_consistency_group_snapshot.py +++ b/openstackclient/tests/unit/volume/v2/test_consistency_group_snapshot.py @@ -12,7 +12,7 @@ # under the License. # -from mock import call +from unittest.mock import call from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes from openstackclient.volume.v2 import consistency_group_snapshot diff --git a/openstackclient/tests/unit/volume/v2/test_qos_specs.py b/openstackclient/tests/unit/volume/v2/test_qos_specs.py index 454747f5aa..073ec5709b 100644 --- a/openstackclient/tests/unit/volume/v2/test_qos_specs.py +++ b/openstackclient/tests/unit/volume/v2/test_qos_specs.py @@ -14,9 +14,8 @@ # import copy - -import mock -from mock import call +from unittest import mock +from unittest.mock import call from osc_lib.cli import format_columns from osc_lib import exceptions diff --git a/openstackclient/tests/unit/volume/v2/test_transfer_request.py b/openstackclient/tests/unit/volume/v2/test_transfer_request.py index 1ea6648f6a..c9dce3cab0 100644 --- a/openstackclient/tests/unit/volume/v2/test_transfer_request.py +++ b/openstackclient/tests/unit/volume/v2/test_transfer_request.py @@ -12,8 +12,8 @@ # under the License. # -import mock -from mock import call +from unittest import mock +from unittest.mock import call from osc_lib import exceptions from osc_lib import utils diff --git a/openstackclient/tests/unit/volume/v2/test_type.py b/openstackclient/tests/unit/volume/v2/test_type.py index 17915928dc..f13d08517a 100644 --- a/openstackclient/tests/unit/volume/v2/test_type.py +++ b/openstackclient/tests/unit/volume/v2/test_type.py @@ -12,8 +12,8 @@ # under the License. # -import mock -from mock import call +from unittest import mock +from unittest.mock import call from osc_lib.cli import format_columns from osc_lib import exceptions diff --git a/openstackclient/tests/unit/volume/v2/test_volume.py b/openstackclient/tests/unit/volume/v2/test_volume.py index 332b50a78f..5d41b3a180 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume.py +++ b/openstackclient/tests/unit/volume/v2/test_volume.py @@ -13,9 +13,8 @@ # import argparse - -import mock -from mock import call +from unittest import mock +from unittest.mock import call from osc_lib.cli import format_columns from osc_lib import exceptions diff --git a/openstackclient/tests/unit/volume/v2/test_volume_backup.py b/openstackclient/tests/unit/volume/v2/test_volume_backup.py index 30c7915dd9..4e1f7ee135 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume_backup.py +++ b/openstackclient/tests/unit/volume/v2/test_volume_backup.py @@ -12,8 +12,8 @@ # under the License. # -import mock -from mock import call +from unittest import mock +from unittest.mock import call from osc_lib import exceptions from osc_lib import utils diff --git a/test-requirements.txt b/test-requirements.txt index 0f59e46faa..d0bce4405c 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -4,7 +4,7 @@ hacking>=1.1.0,<1.2.0 # Apache-2.0 coverage!=4.4,>=4.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD -flake8-import-order==0.13 # LGPLv3 +flake8-import-order>=0.13 # LGPLv3 mock>=2.0.0 # BSD oslotest>=3.2.0 # Apache-2.0 requests>=2.14.2 # Apache-2.0 From 68aa35f35f21476085e25ad2e3da51a1961948e4 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Mon, 13 Jan 2020 11:13:42 -0600 Subject: [PATCH 2151/3095] Add unit tests and release note for dns_publish_fixed_ip Follow-up to https://review.opendev.org/#/c/679834/ which added the options and lacked both a release note and minimal option-handling unit tests. Change-Id: Ibb2820add9b2fedaf5a8b1a77babf043f6641724 Signed-off-by: Dean Troyer --- .../tests/unit/network/v2/test_subnet.py | 38 +++++++++++++++++++ .../notes/bug-1784879-9b632174d4af853f.yaml | 8 ++++ 2 files changed, 46 insertions(+) create mode 100644 releasenotes/notes/bug-1784879-9b632174d4af853f.yaml diff --git a/openstackclient/tests/unit/network/v2/test_subnet.py b/openstackclient/tests/unit/network/v2/test_subnet.py index 9903b0423a..d34be18cb2 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet.py +++ b/openstackclient/tests/unit/network/v2/test_subnet.py @@ -460,6 +460,44 @@ def test_create_with_description(self): self.assertEqual(self.columns, columns) self.assertItemEqual(self.data, data) + def _test_create_with_dns(self, publish_dns=True): + arglist = [ + "--subnet-range", self._subnet.cidr, + "--network", self._subnet.network_id, + self._subnet.name, + ] + if publish_dns: + arglist += ['--dns-publish-fixed-ip'] + else: + arglist += ['--no-dns-publish-fixed-ip'] + verifylist = [ + ('name', self._subnet.name), + ('subnet_range', self._subnet.cidr), + ('network', self._subnet.network_id), + ('ip_version', self._subnet.ip_version), + ('gateway', 'auto'), + ] + verifylist.append(('dns_publish_fixed_ip', publish_dns)) + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_subnet.assert_called_once_with( + cidr=self._subnet.cidr, + ip_version=self._subnet.ip_version, + name=self._subnet.name, + network_id=self._subnet.network_id, + dns_publish_fixed_ip=publish_dns, + ) + self.assertEqual(self.columns, columns) + self.assertItemEqual(self.data, data) + + def test_create_with_dns(self): + self._test_create_with_dns(publish_dns=True) + + def test_create_with_no_dns(self): + self._test_create_with_dns(publish_dns=False) + def _test_create_with_tag(self, add_tags=True): arglist = [ "--subnet-range", self._subnet.cidr, diff --git a/releasenotes/notes/bug-1784879-9b632174d4af853f.yaml b/releasenotes/notes/bug-1784879-9b632174d4af853f.yaml new file mode 100644 index 0000000000..67085731d0 --- /dev/null +++ b/releasenotes/notes/bug-1784879-9b632174d4af853f.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Add ``--dns-publish-fixed-ip`` and ``--no-dns-publish-fixed-ip`` + options to ``create subnet`` and ``set subnet`` commands to + control the publishing of fixed IPs in DNS when the + ``subnet_dns_publish_fixed_ip`` Neutron extension is enabled. + [Bug `1784879 `_] From db29e28b7c1a6ef737f0c4cd459906379f59b252 Mon Sep 17 00:00:00 2001 From: Michael Johnson Date: Mon, 3 Jun 2019 14:37:41 -0700 Subject: [PATCH 2152/3095] Switch to using osc_lib.utils.tags This patch updates the network modules to use the new osc_lib.utils.tags module and removes the in tree _tag.py version. A previous patch[1] moves the _tag.py code to osc-lib to allow other projects to leverage the code. [1] https://review.opendev.org/662859 Change-Id: Id0c34029e327de50c5fd2732bae5fbf45bbd16ee --- lower-constraints.txt | 2 +- openstackclient/network/v2/_tag.py | 144 ------------------- openstackclient/network/v2/floating_ip.py | 2 +- openstackclient/network/v2/network.py | 2 +- openstackclient/network/v2/port.py | 2 +- openstackclient/network/v2/router.py | 2 +- openstackclient/network/v2/security_group.py | 2 +- openstackclient/network/v2/subnet.py | 2 +- openstackclient/network/v2/subnet_pool.py | 2 +- requirements.txt | 2 +- 10 files changed, 9 insertions(+), 153 deletions(-) delete mode 100644 openstackclient/network/v2/_tag.py diff --git a/lower-constraints.txt b/lower-constraints.txt index 6bc10e57bc..91a543a617 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -54,7 +54,7 @@ openstacksdk==0.17.0 os-client-config==1.28.0 os-service-types==1.2.0 os-testr==1.0.0 -osc-lib==1.14.0 +osc-lib==2.0.0 osc-placement==1.7.0 oslo.concurrency==3.26.0 oslo.config==5.2.0 diff --git a/openstackclient/network/v2/_tag.py b/openstackclient/network/v2/_tag.py deleted file mode 100644 index e1cba22ea9..0000000000 --- a/openstackclient/network/v2/_tag.py +++ /dev/null @@ -1,144 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - -import argparse - -from openstackclient.i18n import _ - - -class _CommaListAction(argparse.Action): - - def __call__(self, parser, namespace, values, option_string=None): - setattr(namespace, self.dest, values.split(',')) - - -def add_tag_filtering_option_to_parser(parser, collection_name, - enhance_help=lambda _h: _h): - parser.add_argument( - '--tags', - metavar='[,,...]', - action=_CommaListAction, - help=enhance_help( - _('List %s which have all given tag(s) (Comma-separated list of ' - 'tags)') % collection_name) - ) - parser.add_argument( - '--any-tags', - metavar='[,,...]', - action=_CommaListAction, - help=enhance_help( - _('List %s which have any given tag(s) (Comma-separated list of ' - 'tags)') % collection_name) - ) - parser.add_argument( - '--not-tags', - metavar='[,,...]', - action=_CommaListAction, - help=enhance_help( - _('Exclude %s which have all given tag(s) (Comma-separated list ' - 'of tags)') % collection_name) - ) - parser.add_argument( - '--not-any-tags', - metavar='[,,...]', - action=_CommaListAction, - help=enhance_help( - _('Exclude %s which have any given tag(s) (Comma-separated list ' - 'of tags)') % collection_name) - ) - - -def get_tag_filtering_args(parsed_args, args): - if parsed_args.tags: - args['tags'] = ','.join(parsed_args.tags) - if parsed_args.any_tags: - args['any_tags'] = ','.join(parsed_args.any_tags) - if parsed_args.not_tags: - args['not_tags'] = ','.join(parsed_args.not_tags) - if parsed_args.not_any_tags: - args['not_any_tags'] = ','.join(parsed_args.not_any_tags) - - -def add_tag_option_to_parser_for_create(parser, resource_name, - enhance_help=lambda _h: _h): - tag_group = parser.add_mutually_exclusive_group() - tag_group.add_argument( - '--tag', - action='append', - dest='tags', - metavar='', - help=enhance_help( - _("Tag to be added to the %s " - "(repeat option to set multiple tags)") % resource_name) - ) - tag_group.add_argument( - '--no-tag', - action='store_true', - help=enhance_help(_("No tags associated with the %s") % resource_name) - ) - - -def add_tag_option_to_parser_for_set(parser, resource_name, - enhance_help=lambda _h: _h): - parser.add_argument( - '--tag', - action='append', - dest='tags', - metavar='', - help=enhance_help( - _("Tag to be added to the %s (repeat option to set multiple " - "tags)") % resource_name) - ) - parser.add_argument( - '--no-tag', - action='store_true', - help=enhance_help( - _("Clear tags associated with the %s. Specify both --tag and " - "--no-tag to overwrite current tags") % resource_name) - ) - - -def update_tags_for_set(client, obj, parsed_args): - if parsed_args.no_tag: - tags = set() - else: - tags = set(obj.tags or []) - if parsed_args.tags: - tags |= set(parsed_args.tags) - if set(obj.tags or []) != tags: - client.set_tags(obj, list(tags)) - - -def add_tag_option_to_parser_for_unset(parser, resource_name): - tag_group = parser.add_mutually_exclusive_group() - tag_group.add_argument( - '--tag', - action='append', - dest='tags', - metavar='', - help=_("Tag to be removed from the %s " - "(repeat option to remove multiple tags)") % resource_name) - tag_group.add_argument( - '--all-tag', - action='store_true', - help=_("Clear all tags associated with the %s") % resource_name) - - -def update_tags_for_unset(client, obj, parsed_args): - tags = set(obj.tags) - if parsed_args.all_tag: - tags = set() - if parsed_args.tags: - tags -= set(parsed_args.tags) - if set(obj.tags) != tags: - client.set_tags(obj, list(tags)) diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index bd43379aff..4525913f52 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -16,12 +16,12 @@ from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import utils +from osc_lib.utils import tags as _tag from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common from openstackclient.network import sdk_utils -from openstackclient.network.v2 import _tag _formatters = { diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index e7031266ec..00cb782b73 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -17,12 +17,12 @@ from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import utils +from osc_lib.utils import tags as _tag from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common from openstackclient.network import sdk_utils -from openstackclient.network.v2 import _tag class AdminStateColumn(cliff_columns.FormattableColumn): diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 4d7e518975..a22bcafb56 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -24,12 +24,12 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from osc_lib.utils import tags as _tag from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common from openstackclient.network import sdk_utils -from openstackclient.network.v2 import _tag LOG = logging.getLogger(__name__) diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 5d85e485a0..464dbbec78 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -23,11 +23,11 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from osc_lib.utils import tags as _tag from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import sdk_utils -from openstackclient.network.v2 import _tag LOG = logging.getLogger(__name__) diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py index 24f71ab678..f8153fa8b2 100644 --- a/openstackclient/network/v2/security_group.py +++ b/openstackclient/network/v2/security_group.py @@ -19,13 +19,13 @@ from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import utils +from osc_lib.utils import tags as _tag from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common from openstackclient.network import sdk_utils from openstackclient.network import utils as network_utils -from openstackclient.network.v2 import _tag def _format_network_security_group_rules(sg_rules): diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index f282f42b06..f684406558 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -22,11 +22,11 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from osc_lib.utils import tags as _tag from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import sdk_utils -from openstackclient.network.v2 import _tag LOG = logging.getLogger(__name__) diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index d5a15475ef..2750574a97 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -20,11 +20,11 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from osc_lib.utils import tags as _tag from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import sdk_utils -from openstackclient.network.v2 import _tag LOG = logging.getLogger(__name__) diff --git a/requirements.txt b/requirements.txt index eea51e5163..08777b5fef 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ Babel!=2.4.0,>=2.3.4 # BSD cliff!=2.9.0,>=2.8.0 # Apache-2.0 keystoneauth1>=3.6.2 # Apache-2.0 openstacksdk>=0.17.0 # Apache-2.0 -osc-lib>=1.14.0 # Apache-2.0 +osc-lib>=2.0.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 python-glanceclient>=2.8.0 # Apache-2.0 From 70ab3f9dd56a638cdff516ca85baa5ebd64c888b Mon Sep 17 00:00:00 2001 From: Colleen Murphy Date: Wed, 21 Aug 2019 17:38:29 -0700 Subject: [PATCH 2153/3095] Add support for app cred access rules This commit introduces the --access-rules option for 'application credential create' as well as new 'access rule' commands for listing, showing, and deleting access rules. bp whitelist-extension-for-app-creds Change-Id: I04834b2874ec2a70da456a380b5bef03a392effa --- .../cli/command-objects/access-rules.rst | 61 ++++++ .../application-credentials.rst | 7 + lower-constraints.txt | 2 +- openstackclient/identity/v3/access_rule.py | 118 ++++++++++++ .../identity/v3/application_credential.py | 27 +++ .../tests/unit/identity/v3/fakes.py | 33 +++- .../unit/identity/v3/test_access_rule.py | 174 ++++++++++++++++++ .../v3/test_application_credential.py | 125 ++++++++++++- ...ension-for-app-creds-9afd5009b374190b.yaml | 6 + requirements.txt | 2 +- setup.cfg | 4 + 11 files changed, 548 insertions(+), 11 deletions(-) create mode 100644 doc/source/cli/command-objects/access-rules.rst create mode 100644 openstackclient/identity/v3/access_rule.py create mode 100644 openstackclient/tests/unit/identity/v3/test_access_rule.py create mode 100644 releasenotes/notes/bp-whitelist-extension-for-app-creds-9afd5009b374190b.yaml diff --git a/doc/source/cli/command-objects/access-rules.rst b/doc/source/cli/command-objects/access-rules.rst new file mode 100644 index 0000000000..bc8458283f --- /dev/null +++ b/doc/source/cli/command-objects/access-rules.rst @@ -0,0 +1,61 @@ +=========== +access rule +=========== + +Identity v3 + +Access rules are fine-grained permissions for application credentials. An access +rule comprises of a service type, a request path, and a request method. Access +rules may only be created as attributes of application credentials, but they may +be viewed and deleted independently. + + +access rule delete +------------------ + +Delete access rule(s) + +.. program:: access rule delete +.. code:: bash + + openstack access rule delete [ ...] + +.. describe:: + + Access rule(s) to delete (ID) + +access rule list +---------------- + +List access rules + +.. program:: access rule list +.. code:: bash + + openstack access rule list + [--user ] + [--user-domain ] + +.. option:: --user + + User whose access rules to list (name or ID). If not provided, looks up the + current user's access rules. + +.. option:: --user-domain + + Domain the user belongs to (name or ID). This can be + used in case collisions between user names exist. + +access rule show +--------------------------- + +Display access rule details + +.. program:: access rule show +.. code:: bash + + openstack access rule show + +.. describe:: + + Access rule to display (ID) diff --git a/doc/source/cli/command-objects/application-credentials.rst b/doc/source/cli/command-objects/application-credentials.rst index 2a1fbff25e..047f5ab661 100644 --- a/doc/source/cli/command-objects/application-credentials.rst +++ b/doc/source/cli/command-objects/application-credentials.rst @@ -22,6 +22,7 @@ Create new application credential [--expiration ] [--description ] [--restricted|--unrestricted] + [--access-rules ] .. option:: --secret @@ -52,6 +53,12 @@ Create new application credential Prohibit application credential from creating and deleting other application credentials and trusts (this is the default behavior) +.. option:: --access-rules + + Either a string or file path containing a JSON-formatted list of access + rules, each containing a request method, path, and service, for example + '[{"method": "GET", "path": "/v2.1/servers", "service": "compute"}]' + .. describe:: Name of the application credential diff --git a/lower-constraints.txt b/lower-constraints.txt index 91a543a617..0e00a11488 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -95,7 +95,7 @@ python-heatclient==1.10.0 python-ironic-inspector-client==1.5.0 python-ironicclient==2.3.0 python-karborclient==0.6.0 -python-keystoneclient==3.17.0 +python-keystoneclient==3.22.0 python-mimeparse==1.6.0 python-mistralclient==3.1.0 python-muranoclient==0.8.2 diff --git a/openstackclient/identity/v3/access_rule.py b/openstackclient/identity/v3/access_rule.py new file mode 100644 index 0000000000..d96b44daf9 --- /dev/null +++ b/openstackclient/identity/v3/access_rule.py @@ -0,0 +1,118 @@ +# Copyright 2019 SUSE LLC +# +# 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. +# + +"""Identity v3 Access Rule 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 _ +from openstackclient.identity import common + + +LOG = logging.getLogger(__name__) + + +class DeleteAccessRule(command.Command): + _description = _("Delete access rule(s)") + + def get_parser(self, prog_name): + parser = super(DeleteAccessRule, self).get_parser(prog_name) + parser.add_argument( + 'access_rule', + metavar='', + nargs="+", + help=_('Application credentials(s) to delete (name or ID)'), + ) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + + errors = 0 + for ac in parsed_args.access_rule: + try: + access_rule = utils.find_resource( + identity_client.access_rules, ac) + identity_client.access_rules.delete(access_rule.id) + except Exception as e: + errors += 1 + LOG.error(_("Failed to delete access rule with " + "ID '%(ac)s': %(e)s"), + {'ac': ac, 'e': e}) + + if errors > 0: + total = len(parsed_args.access_rule) + msg = (_("%(errors)s of %(total)s access rules failed " + "to delete.") % {'errors': errors, 'total': total}) + raise exceptions.CommandError(msg) + + +class ListAccessRule(command.Lister): + _description = _("List access rules") + + def get_parser(self, prog_name): + parser = super(ListAccessRule, self).get_parser(prog_name) + parser.add_argument( + '--user', + metavar='', + help=_('User whose access rules to list (name or ID)'), + ) + common.add_user_domain_option_to_parser(parser) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + if parsed_args.user: + user_id = common.find_user(identity_client, + parsed_args.user, + parsed_args.user_domain).id + else: + user_id = None + + columns = ('ID', 'Service', 'Method', 'Path') + data = identity_client.access_rules.list( + user=user_id) + return (columns, + (utils.get_item_properties( + s, columns, + formatters={}, + ) for s in data)) + + +class ShowAccessRule(command.ShowOne): + _description = _("Display access rule details") + + def get_parser(self, prog_name): + parser = super(ShowAccessRule, self).get_parser(prog_name) + parser.add_argument( + 'access_rule', + metavar='', + help=_('Application credential to display (name or ID)'), + ) + return parser + + def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + access_rule = utils.find_resource(identity_client.access_rules, + parsed_args.access_rule) + + access_rule._info.pop('links', None) + + return zip(*sorted(six.iteritems(access_rule._info))) diff --git a/openstackclient/identity/v3/application_credential.py b/openstackclient/identity/v3/application_credential.py index ea0b30cdbb..a208985624 100644 --- a/openstackclient/identity/v3/application_credential.py +++ b/openstackclient/identity/v3/application_credential.py @@ -16,6 +16,7 @@ """Identity v3 Application Credential action implementations""" import datetime +import json import logging from osc_lib.command import command @@ -79,6 +80,17 @@ def get_parser(self, prog_name): ' other application credentials and trusts (this is the' ' default behavior)'), ) + parser.add_argument( + '--access-rules', + metavar='', + help=_('Either a string or file path containing a JSON-formatted ' + 'list of access rules, each containing a request method, ' + 'path, and service, for example ' + '\'[{"method": "GET", ' + '"path": "/v2.1/servers", ' + '"service": "compute"}]\''), + + ) return parser def take_action(self, parsed_args): @@ -105,6 +117,20 @@ def take_action(self, parsed_args): else: unrestricted = parsed_args.unrestricted + if parsed_args.access_rules: + try: + access_rules = json.loads(parsed_args.access_rules) + except ValueError: + try: + with open(parsed_args.access_rules) as f: + access_rules = json.load(f) + except IOError: + raise exceptions.CommandError( + _("Access rules is not valid JSON string or file does" + " not exist.")) + else: + access_rules = None + app_cred_manager = identity_client.application_credentials application_credential = app_cred_manager.create( parsed_args.name, @@ -113,6 +139,7 @@ def take_action(self, parsed_args): description=parsed_args.description, secret=parsed_args.secret, unrestricted=unrestricted, + access_rules=access_rules, ) application_credential._info.pop('links', None) diff --git a/openstackclient/tests/unit/identity/v3/fakes.py b/openstackclient/tests/unit/identity/v3/fakes.py index c394ab8237..fc4a48e37f 100644 --- a/openstackclient/tests/unit/identity/v3/fakes.py +++ b/openstackclient/tests/unit/identity/v3/fakes.py @@ -470,6 +470,14 @@ app_cred_expires = datetime.datetime(2022, 1, 1, 0, 0) app_cred_expires_str = app_cred_expires.strftime('%Y-%m-%dT%H:%M:%S%z') app_cred_secret = 'moresecuresecret' +app_cred_access_rules = ( + '[{"path": "/v2.1/servers", "method": "GET", "service": "compute"}]' +) +app_cred_access_rules_path = '/tmp/access_rules.json' +access_rule_id = 'access-rule-id' +access_rule_service = 'compute' +access_rule_path = '/v2.1/servers' +access_rule_method = 'GET' APP_CRED_BASIC = { 'id': app_cred_id, 'name': app_cred_name, @@ -478,7 +486,8 @@ 'description': None, 'expires_at': None, 'unrestricted': False, - 'secret': app_cred_secret + 'secret': app_cred_secret, + 'access_rules': None } APP_CRED_OPTIONS = { 'id': app_cred_id, @@ -488,7 +497,25 @@ 'description': app_cred_description, 'expires_at': app_cred_expires_str, 'unrestricted': False, - 'secret': app_cred_secret + 'secret': app_cred_secret, + 'access_rules': None, +} +ACCESS_RULE = { + 'id': access_rule_id, + 'service': access_rule_service, + 'path': access_rule_path, + 'method': access_rule_method, +} +APP_CRED_ACCESS_RULES = { + 'id': app_cred_id, + 'name': app_cred_name, + 'project_id': project_id, + 'roles': app_cred_role, + 'description': None, + 'expires_at': None, + 'unrestricted': False, + 'secret': app_cred_secret, + 'access_rules': app_cred_access_rules } registered_limit_id = 'registered-limit-id' @@ -625,6 +652,8 @@ def __init__(self, **kwargs): self.application_credentials = mock.Mock() self.application_credentials.resource_class = fakes.FakeResource(None, {}) + self.access_rules = mock.Mock() + self.access_rules.resource_class = fakes.FakeResource(None, {}) self.inference_rules = mock.Mock() self.inference_rules.resource_class = fakes.FakeResource(None, {}) self.registered_limits = mock.Mock() diff --git a/openstackclient/tests/unit/identity/v3/test_access_rule.py b/openstackclient/tests/unit/identity/v3/test_access_rule.py new file mode 100644 index 0000000000..f8b6093a6c --- /dev/null +++ b/openstackclient/tests/unit/identity/v3/test_access_rule.py @@ -0,0 +1,174 @@ +# Copyright 2019 SUSE LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +import copy + +import mock +from osc_lib import exceptions +from osc_lib import utils + +from openstackclient.identity.v3 import access_rule +from openstackclient.tests.unit import fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes + + +class TestAccessRule(identity_fakes.TestIdentityv3): + + def setUp(self): + super(TestAccessRule, self).setUp() + + identity_manager = self.app.client_manager.identity + self.access_rules_mock = identity_manager.access_rules + self.access_rules_mock.reset_mock() + self.roles_mock = identity_manager.roles + self.roles_mock.reset_mock() + + +class TestAccessRuleDelete(TestAccessRule): + + def setUp(self): + super(TestAccessRuleDelete, self).setUp() + + # This is the return value for utils.find_resource() + self.access_rules_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.ACCESS_RULE), + loaded=True, + ) + self.access_rules_mock.delete.return_value = None + + # Get the command object to test + self.cmd = access_rule.DeleteAccessRule( + self.app, None) + + def test_access_rule_delete(self): + arglist = [ + identity_fakes.access_rule_id, + ] + verifylist = [ + ('access_rule', [identity_fakes.access_rule_id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.access_rules_mock.delete.assert_called_with( + identity_fakes.access_rule_id, + ) + self.assertIsNone(result) + + @mock.patch.object(utils, 'find_resource') + def test_delete_multi_access_rules_with_exception(self, find_mock): + find_mock.side_effect = [self.access_rules_mock.get.return_value, + exceptions.CommandError] + arglist = [ + identity_fakes.access_rule_id, + 'nonexistent_access_rule', + ] + verifylist = [ + ('access_rule', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 access rules failed to' + ' delete.', str(e)) + + find_mock.assert_any_call(self.access_rules_mock, + identity_fakes.access_rule_id) + find_mock.assert_any_call(self.access_rules_mock, + 'nonexistent_access_rule') + + self.assertEqual(2, find_mock.call_count) + self.access_rules_mock.delete.assert_called_once_with( + identity_fakes.access_rule_id) + + +class TestAccessRuleList(TestAccessRule): + + def setUp(self): + super(TestAccessRuleList, self).setUp() + + self.access_rules_mock.list.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.ACCESS_RULE), + loaded=True, + ), + ] + + # Get the command object to test + self.cmd = access_rule.ListAccessRule(self.app, None) + + def test_access_rule_list(self): + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.access_rules_mock.list.assert_called_with(user=None) + + collist = ('ID', 'Service', 'Method', 'Path') + self.assertEqual(collist, columns) + datalist = (( + identity_fakes.access_rule_id, + identity_fakes.access_rule_service, + identity_fakes.access_rule_method, + identity_fakes.access_rule_path, + ), ) + self.assertEqual(datalist, tuple(data)) + + +class TestAccessRuleShow(TestAccessRule): + + def setUp(self): + super(TestAccessRuleShow, self).setUp() + + self.access_rules_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.ACCESS_RULE), + loaded=True, + ) + + # Get the command object to test + self.cmd = access_rule.ShowAccessRule(self.app, None) + + def test_access_rule_show(self): + arglist = [ + identity_fakes.access_rule_id, + ] + verifylist = [ + ('access_rule', identity_fakes.access_rule_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.access_rules_mock.get.assert_called_with( + identity_fakes.access_rule_id) + + collist = ('id', 'method', 'path', 'service') + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.access_rule_id, + identity_fakes.access_rule_method, + identity_fakes.access_rule_path, + identity_fakes.access_rule_service, + ) + self.assertEqual(datalist, data) diff --git a/openstackclient/tests/unit/identity/v3/test_application_credential.py b/openstackclient/tests/unit/identity/v3/test_application_credential.py index 163aae9db9..24bafc9f5f 100644 --- a/openstackclient/tests/unit/identity/v3/test_application_credential.py +++ b/openstackclient/tests/unit/identity/v3/test_application_credential.py @@ -14,6 +14,7 @@ # import copy +import json from unittest import mock from osc_lib import exceptions @@ -79,16 +80,18 @@ def test_application_credential_create_basic(self): 'expires_at': None, 'description': None, 'unrestricted': False, + 'access_rules': None, } self.app_creds_mock.create.assert_called_with( name, **kwargs ) - collist = ('description', 'expires_at', 'id', 'name', 'project_id', - 'roles', 'secret', 'unrestricted') + collist = ('access_rules', 'description', 'expires_at', 'id', 'name', + 'project_id', 'roles', 'secret', 'unrestricted') self.assertEqual(collist, columns) datalist = ( + None, None, None, identity_fakes.app_cred_id, @@ -135,17 +138,19 @@ def test_application_credential_create_with_options(self): 'roles': [identity_fakes.role_id], 'expires_at': identity_fakes.app_cred_expires, 'description': 'credential for testing', - 'unrestricted': False + 'unrestricted': False, + 'access_rules': None, } self.app_creds_mock.create.assert_called_with( name, **kwargs ) - collist = ('description', 'expires_at', 'id', 'name', 'project_id', - 'roles', 'secret', 'unrestricted') + collist = ('access_rules', 'description', 'expires_at', 'id', 'name', + 'project_id', 'roles', 'secret', 'unrestricted') self.assertEqual(collist, columns) datalist = ( + None, identity_fakes.app_cred_description, identity_fakes.app_cred_expires_str, identity_fakes.app_cred_id, @@ -157,6 +162,111 @@ def test_application_credential_create_with_options(self): ) self.assertEqual(datalist, data) + def test_application_credential_create_with_access_rules_string(self): + name = identity_fakes.app_cred_name + self.app_creds_mock.create.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.APP_CRED_ACCESS_RULES), + loaded=True, + ) + + arglist = [ + name, + '--access-rules', identity_fakes.app_cred_access_rules, + ] + verifylist = [ + ('name', identity_fakes.app_cred_name), + ('access_rules', identity_fakes.app_cred_access_rules), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'secret': None, + 'roles': [], + 'expires_at': None, + 'description': None, + 'unrestricted': False, + 'access_rules': json.loads(identity_fakes.app_cred_access_rules) + } + self.app_creds_mock.create.assert_called_with( + name, + **kwargs + ) + + collist = ('access_rules', 'description', 'expires_at', 'id', 'name', + 'project_id', 'roles', 'secret', 'unrestricted') + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.app_cred_access_rules, + None, + None, + identity_fakes.app_cred_id, + identity_fakes.app_cred_name, + identity_fakes.project_id, + identity_fakes.role_name, + identity_fakes.app_cred_secret, + False, + ) + self.assertEqual(datalist, data) + + @mock.patch('openstackclient.identity.v3.application_credential.json.load') + @mock.patch('openstackclient.identity.v3.application_credential.open') + def test_application_credential_create_with_access_rules_file( + self, _, mock_json_load): + mock_json_load.return_value = identity_fakes.app_cred_access_rules + + name = identity_fakes.app_cred_name + self.app_creds_mock.create.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.APP_CRED_ACCESS_RULES), + loaded=True, + ) + + arglist = [ + name, + '--access-rules', identity_fakes.app_cred_access_rules_path, + ] + verifylist = [ + ('name', identity_fakes.app_cred_name), + ('access_rules', identity_fakes.app_cred_access_rules_path), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'secret': None, + 'roles': [], + 'expires_at': None, + 'description': None, + 'unrestricted': False, + 'access_rules': identity_fakes.app_cred_access_rules + } + self.app_creds_mock.create.assert_called_with( + name, + **kwargs + ) + + collist = ('access_rules', 'description', 'expires_at', 'id', 'name', + 'project_id', 'roles', 'secret', 'unrestricted') + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.app_cred_access_rules, + None, + None, + identity_fakes.app_cred_id, + identity_fakes.app_cred_name, + identity_fakes.project_id, + identity_fakes.role_name, + identity_fakes.app_cred_secret, + False, + ) + self.assertEqual(datalist, data) + class TestApplicationCredentialDelete(TestApplicationCredential): @@ -293,10 +403,11 @@ def test_application_credential_show(self): self.app_creds_mock.get.assert_called_with(identity_fakes.app_cred_id) - collist = ('description', 'expires_at', 'id', 'name', 'project_id', - 'roles', 'secret', 'unrestricted') + collist = ('access_rules', 'description', 'expires_at', 'id', 'name', + 'project_id', 'roles', 'secret', 'unrestricted') self.assertEqual(collist, columns) datalist = ( + None, None, None, identity_fakes.app_cred_id, diff --git a/releasenotes/notes/bp-whitelist-extension-for-app-creds-9afd5009b374190b.yaml b/releasenotes/notes/bp-whitelist-extension-for-app-creds-9afd5009b374190b.yaml new file mode 100644 index 0000000000..afa6e212e0 --- /dev/null +++ b/releasenotes/notes/bp-whitelist-extension-for-app-creds-9afd5009b374190b.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + [`blueprint whitelist-extension-for-app-creds `_] + Added support for creating access rules as an attribute of application + credentials as well as for listing, showing, and deleting access rules. diff --git a/requirements.txt b/requirements.txt index 08777b5fef..f7e2cecadb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,6 +12,6 @@ osc-lib>=2.0.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 python-glanceclient>=2.8.0 # Apache-2.0 -python-keystoneclient>=3.17.0 # Apache-2.0 +python-keystoneclient>=3.22.0 # Apache-2.0 python-novaclient>=15.1.0 # Apache-2.0 python-cinderclient>=3.3.0 # Apache-2.0 diff --git a/setup.cfg b/setup.cfg index 2d6422787f..fcc9700715 100644 --- a/setup.cfg +++ b/setup.cfg @@ -197,6 +197,10 @@ openstack.identity.v2 = openstack.identity.v3 = access_token_create = openstackclient.identity.v3.token:CreateAccessToken + access_rule_delete = openstackclient.identity.v3.access_rule:DeleteAccessRule + access_rule_list = openstackclient.identity.v3.access_rule:ListAccessRule + access_rule_show = openstackclient.identity.v3.access_rule:ShowAccessRule + application_credential_create = openstackclient.identity.v3.application_credential:CreateApplicationCredential application_credential_delete = openstackclient.identity.v3.application_credential:DeleteApplicationCredential application_credential_list = openstackclient.identity.v3.application_credential:ListApplicationCredential From 99b0b073922fb64ca4d0f6a5c44a28bc339b4312 Mon Sep 17 00:00:00 2001 From: Colleen Murphy Date: Tue, 21 Jan 2020 15:08:46 -0800 Subject: [PATCH 2154/3095] Fix copypaste errors in access rule command Access rules are access rules, not application credentials. Change-Id: I74d05f11ec186283e5a86d92dcbfe4eb24130eee --- openstackclient/identity/v3/access_rule.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openstackclient/identity/v3/access_rule.py b/openstackclient/identity/v3/access_rule.py index d96b44daf9..65e78be1d0 100644 --- a/openstackclient/identity/v3/access_rule.py +++ b/openstackclient/identity/v3/access_rule.py @@ -38,7 +38,7 @@ def get_parser(self, prog_name): 'access_rule', metavar='', nargs="+", - help=_('Application credentials(s) to delete (name or ID)'), + help=_('Access rule(s) to delete (name or ID)'), ) return parser @@ -104,7 +104,7 @@ def get_parser(self, prog_name): parser.add_argument( 'access_rule', metavar='', - help=_('Application credential to display (name or ID)'), + help=_('Access rule to display (name or ID)'), ) return parser From cb265774ac33d6fe09f6f9e2bb5052f132662d0d Mon Sep 17 00:00:00 2001 From: Georgina Shippey Date: Tue, 21 Jan 2020 12:30:09 +0000 Subject: [PATCH 2155/3095] Incorrect title for service provider Mistakenly changed to identity provider. Change-Id: I0841a6e5ebd6a27a5375a54c56fc194dff65b370 --- doc/source/cli/command-objects/service-provider.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/source/cli/command-objects/service-provider.rst b/doc/source/cli/command-objects/service-provider.rst index a92c3b208c..b7a282cba5 100644 --- a/doc/source/cli/command-objects/service-provider.rst +++ b/doc/source/cli/command-objects/service-provider.rst @@ -1,6 +1,6 @@ -================= -identity provider -================= +================ +service provider +================ A **service provider** is used by the Identity service's OS-FEDERATION extension. It is used by to register another OpenStack Identity service. From 27b16df7f87c096e8366219ed76ed9467cdcb018 Mon Sep 17 00:00:00 2001 From: Kendall Nelson Date: Mon, 27 Jan 2020 21:51:18 -0800 Subject: [PATCH 2156/3095] Remove mention of meetings from docs Since the OpenStackClient team doesn't meet anymore, I removed the mention of meetings and added mention of the IRC channel in the communication section of the docs.. Change-Id: Iefa95878f95bf84bd9fc22ea4c914effc30dffa7 --- doc/source/contributor/developing.rst | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/doc/source/contributor/developing.rst b/doc/source/contributor/developing.rst index 721a016a03..bd9197d7fc 100644 --- a/doc/source/contributor/developing.rst +++ b/doc/source/contributor/developing.rst @@ -5,12 +5,11 @@ Developing with OpenStackClient Communication ------------- -Meetings -======== -The OpenStackClient team meets regularly on every Thursday. For details -please refer to the `OpenStack IRC meetings`_ page. - -.. _`OpenStack IRC meetings`: http://eavesdrop.openstack.org/#OpenStackClient_Team_Meeting +IRC Channel +=========== +The OpenStackClient team doesn't have regular meetings so if you have +questions or anything you want to discuss, come to our channel: +#openstack-sdks Testing ------- From ea27ebb0f918db9eab2f5751a1b065818faa0e6d Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Fri, 27 Sep 2019 12:19:29 +0100 Subject: [PATCH 2157/3095] Stop silently ignoring invalid 'server create --hint' options The '--hint' option for 'server create' expects a key-value pair like so: openstack server create --hint group=245e1dfe-2d0e-4139-80a9-fce124948896 ... However, the command doesn't complain if this isn't the case, meaning typos like the below aren't indicated to the user: openstack server create --hint 245e1dfe-2d0e-4139-80a9-fce124948896 Due to how we'd implemented this here, this ultimately results in us POSTing the following as part of the body to 'os-servers': { ... "OS-SCH-HNT:scheduler_hints": { "245e1dfe-2d0e-4139-80a9-fce124948896": null } ... } Which is unfortunately allowed and ignored by nova due to the use of 'additionalProperties' in the schema [1] Do what we do for loads of other options and explicitly fail on invalid values. This involves adding a new argparse action since none of those defined in osc-lib work for us. This is included here to ease backporting of the fix but will be moved to osc-lib in a future patch. [1] https://github.com/openstack/nova/blob/19.0.0/nova/api/openstack/compute/schemas/servers.py#L142-L146 Change-Id: I9e96d2978912c8dfeadae4a782c481a17cd7e348 Signed-off-by: Stephen Finucane Story: #2006628 Task: #36840 Related-Bug: #1845322 --- openstackclient/compute/v2/server.py | 48 ++++++++++++++----- .../tests/unit/compute/v2/test_server.py | 25 +++++++++- 2 files changed, 61 insertions(+), 12 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index b5c420fe87..a4cca661b6 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -201,6 +201,36 @@ def _prep_server_detail(compute_client, image_client, server, refresh=True): return info +# TODO(stephenfin): Migrate this to osc-lib +class KeyValueAppendAction(argparse.Action): + """A custom action to parse arguments as key=value pairs + + Ensures that ``dest`` is a dict and values are lists of strings. + """ + + def __call__(self, parser, namespace, values, option_string=None): + # Make sure we have an empty dict rather than None + if getattr(namespace, self.dest, None) is None: + setattr(namespace, self.dest, {}) + + # Add value if an assignment else remove it + if '=' in values: + key, value = values.split('=', 1) + # NOTE(qtang): Prevent null key setting in property + if '' == key: + msg = _("Property key must be specified: %s") + raise argparse.ArgumentTypeError(msg % str(values)) + + dest = getattr(namespace, self.dest) + if key in dest: + dest[key].append(value) + else: + dest[key] = [value] + else: + msg = _("Expected 'key=value' type, but got: %s") + raise argparse.ArgumentTypeError(msg % str(values)) + + class AddFixedIP(command.Command): _description = _("Add fixed IP address to server") @@ -689,8 +719,8 @@ def get_parser(self, prog_name): parser.add_argument( '--hint', metavar='', - action='append', - default=[], + action=KeyValueAppendAction, + default={}, help=_('Hints for the scheduler (optional extension)'), ) parser.add_argument( @@ -986,16 +1016,12 @@ def _match_image(image_api, wanted_properties): security_group_names.append(sg['name']) hints = {} - for hint in parsed_args.hint: - key, _sep, value = hint.partition('=') - # NOTE(vish): multiple copies of the same hint will - # result in a list of values - if key in hints: - if isinstance(hints[key], six.string_types): - hints[key] = [hints[key]] - hints[key] += [value] + for key, values in parsed_args.hint.items(): + # only items with multiple values will result in a list + if len(values) == 1: + hints[key] = values[0] else: - hints[key] = value + hints[key] = values # What does a non-boolean value for config-drive do? # --config-drive argument is either a volume id or diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index b3be514724..27eefd8586 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -860,7 +860,7 @@ def test_server_create_with_options(self): ('key_name', 'keyname'), ('property', {'Beta': 'b'}), ('security_group', ['securitygroup']), - ('hint', ['a=b', 'a=c']), + ('hint', {'a': ['b', 'c']}), ('config_drive', False), ('server_name', self.new_server.name), ] @@ -2060,6 +2060,29 @@ def test_server_create_image_property_missed(self): self.cmd.take_action, parsed_args) + def test_server_create_invalid_hint(self): + # Not a key-value pair + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--hint', 'a0cf03a5-d921-4877-bb5c-86d26cf818e1', + self.new_server.name, + ] + self.assertRaises(argparse.ArgumentTypeError, + self.check_parser, + self.cmd, arglist, []) + + # Empty key + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--hint', '=a0cf03a5-d921-4877-bb5c-86d26cf818e1', + self.new_server.name, + ] + self.assertRaises(argparse.ArgumentTypeError, + self.check_parser, + self.cmd, arglist, []) + def test_server_create_with_description_api_newer(self): # Description is supported for nova api version 2.19 or above From cefa571d4b3162ab1da194573f85cce8bbdacc14 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Fri, 27 Sep 2019 12:59:48 +0100 Subject: [PATCH 2158/3095] Use 'KeyValueAppendAction' from osc-lib Does what it says on the tin. This action was added to osc-lib in change If73cab759fa09bddf1ff519923c5972c3b2052b1. Change-Id: I51efaa096bb26e297d99634c5d9cca34c0919074 Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server.py | 32 +--------------------------- 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index a4cca661b6..5cc73284a2 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -201,36 +201,6 @@ def _prep_server_detail(compute_client, image_client, server, refresh=True): return info -# TODO(stephenfin): Migrate this to osc-lib -class KeyValueAppendAction(argparse.Action): - """A custom action to parse arguments as key=value pairs - - Ensures that ``dest`` is a dict and values are lists of strings. - """ - - def __call__(self, parser, namespace, values, option_string=None): - # Make sure we have an empty dict rather than None - if getattr(namespace, self.dest, None) is None: - setattr(namespace, self.dest, {}) - - # Add value if an assignment else remove it - if '=' in values: - key, value = values.split('=', 1) - # NOTE(qtang): Prevent null key setting in property - if '' == key: - msg = _("Property key must be specified: %s") - raise argparse.ArgumentTypeError(msg % str(values)) - - dest = getattr(namespace, self.dest) - if key in dest: - dest[key].append(value) - else: - dest[key] = [value] - else: - msg = _("Expected 'key=value' type, but got: %s") - raise argparse.ArgumentTypeError(msg % str(values)) - - class AddFixedIP(command.Command): _description = _("Add fixed IP address to server") @@ -719,7 +689,7 @@ def get_parser(self, prog_name): parser.add_argument( '--hint', metavar='', - action=KeyValueAppendAction, + action=parseractions.KeyValueAppendAction, default={}, help=_('Hints for the scheduler (optional extension)'), ) From e6e4b73efa01281002930e0806b765200f38e7c8 Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Fri, 4 Oct 2019 14:42:41 +0200 Subject: [PATCH 2159/3095] Complete "Drop python2 support" goal We stopped testing python2, but there are more things to be cleanup. - Remove python2 entries from setup.cfg - Add releasenotes - remove universal wheel since this is only python 3 now Change-Id: Ie2bbb4d34b8411939ad5cfd750fc76c933779542 --- doc/requirements.txt | 3 +-- releasenotes/notes/drop-py2-421c90fbdf18dbc2.yaml | 7 +++++++ setup.cfg | 5 ----- 3 files changed, 8 insertions(+), 7 deletions(-) create mode 100644 releasenotes/notes/drop-py2-421c90fbdf18dbc2.yaml diff --git a/doc/requirements.txt b/doc/requirements.txt index 8639f9360a..5cfc337b13 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -3,8 +3,7 @@ # process, which may cause wedges in the gate later. openstackdocstheme>=1.23.2 # Apache-2.0 reno>=2.5.0 # Apache-2.0 -sphinx!=1.6.6,!=1.6.7,>=1.6.5,<2.0.0;python_version=='2.7' # BSD -sphinx!=1.6.6,!=1.6.7,>=1.6.5;python_version>='3.4' # BSD +sphinx!=1.6.6,!=1.6.7,>=1.6.5 # BSD sphinxcontrib-apidoc>=0.2.0 # BSD # redirect tests in docs diff --git a/releasenotes/notes/drop-py2-421c90fbdf18dbc2.yaml b/releasenotes/notes/drop-py2-421c90fbdf18dbc2.yaml new file mode 100644 index 0000000000..74c5d8b530 --- /dev/null +++ b/releasenotes/notes/drop-py2-421c90fbdf18dbc2.yaml @@ -0,0 +1,7 @@ +--- +upgrade: + - | + Python 2.7 support has been dropped. The last release of + python-openstackclient to support Python 2.7 is OpenStack Train. The + minimum version of Python now supported by python-openstackclient is + Python 3.6. diff --git a/setup.cfg b/setup.cfg index fcc9700715..60caf5db1e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -13,8 +13,6 @@ classifier = License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux Programming Language :: Python - Programming Language :: Python :: 2 - Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 @@ -720,9 +718,6 @@ openstack.volume.v3 = [upload_sphinx] upload-dir = doc/build/html -[wheel] -universal = 1 - [extract_messages] keywords = _ gettext ngettext l_ lazy_gettext mapping_file = babel.cfg From 2745b178a43f51ba837c485eed08c8ef6011a9ea Mon Sep 17 00:00:00 2001 From: Rodolfo Alonso Hernandez Date: Thu, 9 Jan 2020 17:52:37 +0000 Subject: [PATCH 2160/3095] Add qos_network_policy_id to network port tests Added "qos_network_policy_id" to "port show" command. Because this is just a read-only parameter and is read from the SDK port definition, this patch only modifies the corresponding tests. This patch is adding this new parameter to the test bench. Change-Id: Ice7423e0e0b98a39cc36622b70eae5a8493a037c Closes-Bug: #1851362 --- openstackclient/tests/unit/network/v2/fakes.py | 1 + openstackclient/tests/unit/network/v2/test_port.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index 35f0b1a59c..e2e09cbadf 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -639,6 +639,7 @@ def create_one_port(attrs=None): 'security_group_ids': [], 'status': 'ACTIVE', 'tenant_id': 'project-id-' + uuid.uuid4().hex, + 'qos_network_policy_id': 'qos-policy-id-' + uuid.uuid4().hex, 'qos_policy_id': 'qos-policy-id-' + uuid.uuid4().hex, 'tags': [], 'uplink_status_propagation': False, diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index c30d682f59..ec063b17a4 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -61,6 +61,7 @@ def _get_common_cols_data(fake_port): 'network_id', 'port_security_enabled', 'project_id', + 'qos_network_policy_id', 'qos_policy_id', 'security_group_ids', 'status', @@ -91,6 +92,7 @@ def _get_common_cols_data(fake_port): fake_port.network_id, fake_port.port_security_enabled, fake_port.project_id, + fake_port.qos_network_policy_id, fake_port.qos_policy_id, format_columns.ListColumn(fake_port.security_group_ids), fake_port.status, From d6022f96dfd608b83a4ff41483336f024aeacb16 Mon Sep 17 00:00:00 2001 From: Simon Merrick Date: Tue, 18 Feb 2020 12:48:02 +1300 Subject: [PATCH 2161/3095] Add storage policy option to create container command + Add CLI option to specify swift storage policy + Add CLI flag to specify container uses public read ACLS + Show storage policy in container show data Change-Id: I08ffa0d98bd39d467aa415771675f59bd77768ff --- openstackclient/api/object_store_v1.py | 27 ++++++- openstackclient/object/v1/container.py | 12 ++++ .../tests/unit/api/test_object_store_v1.py | 2 + .../unit/object/v1/test_container_all.py | 72 +++++++++++++++++++ 4 files changed, 111 insertions(+), 2 deletions(-) diff --git a/openstackclient/api/object_store_v1.py b/openstackclient/api/object_store_v1.py index 44ff7a01fb..c8514a5723 100644 --- a/openstackclient/api/object_store_v1.py +++ b/openstackclient/api/object_store_v1.py @@ -24,6 +24,11 @@ from openstackclient.api import api +GLOBAL_READ_ACL = ".r:*" +LIST_CONTENTS_ACL = ".rlistings" +PUBLIC_CONTAINER_ACLS = [GLOBAL_READ_ACL, LIST_CONTENTS_ACL] + + class APIv1(api.BaseAPI): """Object Store v1 API""" @@ -33,15 +38,32 @@ def __init__(self, **kwargs): def container_create( self, container=None, + public=False, + storage_policy=None ): """Create a container :param string container: name of container to create + :param bool public: + Boolean value indicating if the container should be publicly + readable. Defaults to False. + :param string storage_policy: + Name of the a specific storage policy to use. If not specified + swift will use its default storage policy. :returns: dict of returned headers """ - response = self.create(urllib.parse.quote(container), method='PUT') + + headers = {} + if public: + headers['x-container-read'] = ",".join(PUBLIC_CONTAINER_ACLS) + if storage_policy is not None: + headers['x-storage-policy'] = storage_policy + + response = self.create( + urllib.parse.quote(container), method='PUT', headers=headers) + data = { 'account': self._find_account_id(), 'container': container, @@ -173,7 +195,8 @@ def container_show( 'object_count': response.headers.get( 'x-container-object-count' ), - 'bytes_used': response.headers.get('x-container-bytes-used') + 'bytes_used': response.headers.get('x-container-bytes-used'), + 'storage_policy': response.headers.get('x-storage-policy'), } if 'x-container-read' in response.headers: diff --git a/openstackclient/object/v1/container.py b/openstackclient/object/v1/container.py index 47ca5bc1da..917e41c02f 100644 --- a/openstackclient/object/v1/container.py +++ b/openstackclient/object/v1/container.py @@ -33,6 +33,16 @@ class CreateContainer(command.Lister): def get_parser(self, prog_name): parser = super(CreateContainer, self).get_parser(prog_name) + parser.add_argument( + '--public', + action='store_true', + default=False, + help="Make the container publicly accessible" + ) + parser.add_argument( + '--storage-policy', + help="Specify a particular storage policy to use." + ) parser.add_argument( 'containers', metavar='', @@ -51,6 +61,8 @@ def take_action(self, parsed_args): ' is 256'), len(container)) data = self.app.client_manager.object_store.container_create( container=container, + public=parsed_args.public, + storage_policy=parsed_args.storage_policy ) results.append(data) diff --git a/openstackclient/tests/unit/api/test_object_store_v1.py b/openstackclient/tests/unit/api/test_object_store_v1.py index 1c55be7192..96c68d5a1e 100644 --- a/openstackclient/tests/unit/api/test_object_store_v1.py +++ b/openstackclient/tests/unit/api/test_object_store_v1.py @@ -151,12 +151,14 @@ def test_container_show(self): 'X-Container-Meta-Owner': FAKE_ACCOUNT, 'x-container-object-count': '1', 'x-container-bytes-used': '577', + 'x-storage-policy': 'o1--sr-r3' } resp = { 'account': FAKE_ACCOUNT, 'container': 'qaz', 'object_count': '1', 'bytes_used': '577', + 'storage_policy': 'o1--sr-r3', 'properties': {'Owner': FAKE_ACCOUNT}, } self.requests_mock.register_uri( diff --git a/openstackclient/tests/unit/object/v1/test_container_all.py b/openstackclient/tests/unit/object/v1/test_container_all.py index 58c90e36d5..654cfbc740 100644 --- a/openstackclient/tests/unit/object/v1/test_container_all.py +++ b/openstackclient/tests/unit/object/v1/test_container_all.py @@ -70,6 +70,75 @@ def test_object_create_container_single(self): )] self.assertEqual(datalist, list(data)) + def test_object_create_container_storage_policy(self): + self.requests_mock.register_uri( + 'PUT', + object_fakes.ENDPOINT + '/ernie', + headers={ + 'x-trans-id': '314159', + 'x-storage-policy': 'o1--sr-r3' + }, + status_code=200, + ) + + arglist = [ + 'ernie', + '--storage-policy', + 'o1--sr-r3' + ] + verifylist = [ + ('containers', ['ernie']), + ('storage_policy', 'o1--sr-r3') + ] + 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.assertEqual(self.columns, columns) + datalist = [( + object_fakes.ACCOUNT_ID, + 'ernie', + '314159', + )] + self.assertEqual(datalist, list(data)) + + def test_object_create_container_public(self): + self.requests_mock.register_uri( + 'PUT', + object_fakes.ENDPOINT + '/ernie', + headers={ + 'x-trans-id': '314159', + 'x-container-read': '.r:*,.rlistings' + }, + status_code=200, + ) + + arglist = [ + 'ernie', + '--public' + ] + verifylist = [ + ('containers', ['ernie']), + ('public', True) + ] + 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.assertEqual(self.columns, columns) + datalist = [( + object_fakes.ACCOUNT_ID, + 'ernie', + '314159', + )] + self.assertEqual(datalist, list(data)) + def test_object_create_container_more(self): self.requests_mock.register_uri( 'PUT', @@ -300,6 +369,7 @@ def test_object_show_container(self): 'x-container-write': 'wsx', 'x-container-sync-to': 'edc', 'x-container-sync-key': 'rfv', + 'x-storage-policy': 'o1--sr-r3' } self.requests_mock.register_uri( 'HEAD', @@ -327,6 +397,7 @@ def test_object_show_container(self): 'container', 'object_count', 'read_acl', + 'storage_policy', 'sync_key', 'sync_to', 'write_acl', @@ -338,6 +409,7 @@ def test_object_show_container(self): 'ernie', '42', 'qaz', + 'o1--sr-r3', 'rfv', 'edc', 'wsx', From 0699df95c8a6c177940ef2abbc97f21ad4b86ebf Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Tue, 3 Mar 2020 10:55:06 -0600 Subject: [PATCH 2162/3095] Add bindep file We're missing one of these, which means starting from a bare node it's not completely possible to install openstackclient based only on bindep and pip. Add one that allows building and installing. Change-Id: I7b297bb1485773df3d5d1cc3ba78b0b9af4b2d00 --- bindep.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 bindep.txt diff --git a/bindep.txt b/bindep.txt new file mode 100644 index 0000000000..c5ae6c6097 --- /dev/null +++ b/bindep.txt @@ -0,0 +1,10 @@ +# This is a cross-platform list tracking distribution packages needed by tests; +# see https://docs.openstack.org/infra/bindep/ for additional information. + +gcc [compile test] +libffi-devel [platform:rpm] +libffi-dev [compile test platform:dpkg] +libffi6 [platform:dpkg] +libssl-dev [compile test platform:dpkg] +python3-dev [compile test platform:dpkg] +python3-devel [compile test platform:rpm] From e410e61d204d09eeb7bcf0a33e3858ef7b110dd3 Mon Sep 17 00:00:00 2001 From: Sam Morrison Date: Tue, 25 Feb 2020 12:52:01 +1100 Subject: [PATCH 2163/3095] Always display direction for security group rules The --long option is still accepted but is now ignored. Change-Id: I23dd9fa7cff310ee9a62ce32b843b822b93b7548 Story: #2007323 --- .../network/v2/security_group_rule.py | 17 ++++++++++------- .../v2/test_security_group_rule_compute.py | 1 + .../v2/test_security_group_rule_network.py | 16 ++++++++-------- ...-direction-for-sg-rule-130efc39bf67d79a.yaml | 10 ++++++++++ 4 files changed, 29 insertions(+), 15 deletions(-) create mode 100644 releasenotes/notes/always-show-direction-for-sg-rule-130efc39bf67d79a.yaml diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index f48478ea74..150de1667b 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -480,7 +480,7 @@ def update_parser_network(self, parser): action='store_true', default=False, help=self.enhance_help_neutron( - _("List additional fields in output")) + _("**Deprecated** This argument is no longer needed")) ) return parser @@ -510,15 +510,19 @@ def _get_column_headers(self, parsed_args): 'Ethertype', 'IP Range', 'Port Range', + 'Direction', + 'Remote Security Group', ) - if parsed_args.long: - column_headers = column_headers + ('Direction',) - column_headers = column_headers + ('Remote Security Group',) if parsed_args.group is None: column_headers = column_headers + ('Security Group',) return column_headers def take_action_network(self, client, parsed_args): + if parsed_args.long: + self.log.warning(_( + "The --long option has been deprecated and is no longer needed" + )) + column_headers = self._get_column_headers(parsed_args) columns = ( 'id', @@ -526,10 +530,9 @@ def take_action_network(self, client, parsed_args): 'ether_type', 'remote_ip_prefix', 'port_range', + 'direction', + 'remote_group_id', ) - if parsed_args.long: - columns = columns + ('direction',) - columns = columns + ('remote_group_id',) # Get the security group rules using the requested query. query = {} diff --git a/openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py b/openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py index 5720e30584..b7e38afb18 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py @@ -362,6 +362,7 @@ class TestListSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): 'Ethertype', 'IP Range', 'Port Range', + 'Direction', 'Remote Security Group', ) expected_columns_no_group = \ diff --git a/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py b/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py index 0a9522b0bc..0141161134 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py @@ -870,7 +870,7 @@ class TestListSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): _security_group_rules = [_security_group_rule_tcp, _security_group_rule_icmp] - expected_columns_with_group_and_long = ( + expected_columns_with_group = ( 'ID', 'IP Protocol', 'Ethertype', @@ -885,14 +885,15 @@ class TestListSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): 'Ethertype', 'IP Range', 'Port Range', + 'Direction', 'Remote Security Group', 'Security Group', ) - expected_data_with_group_and_long = [] + expected_data_with_group = [] expected_data_no_group = [] for _security_group_rule in _security_group_rules: - expected_data_with_group_and_long.append(( + expected_data_with_group.append(( _security_group_rule.id, _security_group_rule.protocol, _security_group_rule.ether_type, @@ -909,6 +910,7 @@ class TestListSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): _security_group_rule.remote_ip_prefix, security_group_rule._format_network_port_range( _security_group_rule), + _security_group_rule.direction, _security_group_rule.remote_group_id, _security_group_rule.security_group_id, )) @@ -935,14 +937,12 @@ def test_list_default(self): self.assertEqual(self.expected_columns_no_group, columns) self.assertEqual(self.expected_data_no_group, list(data)) - def test_list_with_group_and_long(self): + def test_list_with_group(self): self._security_group_rule_tcp.port_range_min = 80 arglist = [ - '--long', self._security_group.id, ] verifylist = [ - ('long', True), ('group', self._security_group.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -952,8 +952,8 @@ def test_list_with_group_and_long(self): self.network.security_group_rules.assert_called_once_with(**{ 'security_group_id': self._security_group.id, }) - self.assertEqual(self.expected_columns_with_group_and_long, columns) - self.assertEqual(self.expected_data_with_group_and_long, list(data)) + self.assertEqual(self.expected_columns_with_group, columns) + self.assertEqual(self.expected_data_with_group, list(data)) def test_list_with_ignored_options(self): self._security_group_rule_tcp.port_range_min = 80 diff --git a/releasenotes/notes/always-show-direction-for-sg-rule-130efc39bf67d79a.yaml b/releasenotes/notes/always-show-direction-for-sg-rule-130efc39bf67d79a.yaml new file mode 100644 index 0000000000..70dd67504c --- /dev/null +++ b/releasenotes/notes/always-show-direction-for-sg-rule-130efc39bf67d79a.yaml @@ -0,0 +1,10 @@ +--- +features: + - | + By default listing security group rules now shows the direction. + The ``--long`` argument is now redundant and is now ignored as it + was only used to display the direction. +deprecations: + - | + Deprecate the ``--long`` option for the ``security group list`` + command. This is no longer needed to display all columns. From 001796faa26e3edcfb758e448857fd20f5a84158 Mon Sep 17 00:00:00 2001 From: yangxi Date: Wed, 10 Jan 2018 15:32:53 +0800 Subject: [PATCH 2164/3095] Change 'Volume' to 'Block Storage' In volume*.rst files, 'Volume' should be instead of 'Block Storage'. Change-Id: Iafc8bfa19e87edf1ffad2340c75e9d867042cae5 --- doc/source/cli/command-objects/volume-backend.rst | 2 +- doc/source/cli/command-objects/volume-backup.rst | 2 +- doc/source/cli/command-objects/volume-host.rst | 2 +- doc/source/cli/command-objects/volume-service.rst | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/source/cli/command-objects/volume-backend.rst b/doc/source/cli/command-objects/volume-backend.rst index 0285d61b84..4766ecabb2 100644 --- a/doc/source/cli/command-objects/volume-backend.rst +++ b/doc/source/cli/command-objects/volume-backend.rst @@ -2,7 +2,7 @@ volume backend ============== -Volume v2 +Block Storage v2 .. autoprogram-cliff:: openstack.volume.v2 :command: volume backend * diff --git a/doc/source/cli/command-objects/volume-backup.rst b/doc/source/cli/command-objects/volume-backup.rst index 632e215e2b..1c26921197 100644 --- a/doc/source/cli/command-objects/volume-backup.rst +++ b/doc/source/cli/command-objects/volume-backup.rst @@ -2,7 +2,7 @@ volume backup ============= -Volume v1, v2 +Block Storage v1, v2 .. autoprogram-cliff:: openstack.volume.v2 :command: volume backup * diff --git a/doc/source/cli/command-objects/volume-host.rst b/doc/source/cli/command-objects/volume-host.rst index 1e513cb716..350d6dec7c 100644 --- a/doc/source/cli/command-objects/volume-host.rst +++ b/doc/source/cli/command-objects/volume-host.rst @@ -2,7 +2,7 @@ volume host =========== -Volume v2 +Block Storage v2 volume host failover -------------------- diff --git a/doc/source/cli/command-objects/volume-service.rst b/doc/source/cli/command-objects/volume-service.rst index 2ad23240d3..0499fb9062 100644 --- a/doc/source/cli/command-objects/volume-service.rst +++ b/doc/source/cli/command-objects/volume-service.rst @@ -2,7 +2,7 @@ volume service ============== -Volume v1, v2 +Block Storage v1, v2 volume service list ------------------- From 962efd949feb461283a9bb4a668fbd310f80ba40 Mon Sep 17 00:00:00 2001 From: Hongbin Lu Date: Tue, 30 Jan 2018 19:11:40 +0000 Subject: [PATCH 2165/3095] Disallow setting default on internal network The ``--default`` option should be only used for external network. Default internal network is not currently supported so we disallow it for now. Change-Id: Ia9d39b40e1e041d7bda0f6a27d058e382b572e1a Closes-Bug: #1745658 --- openstackclient/network/v2/network.py | 7 +++- .../tests/unit/network/v2/test_network.py | 33 +++++++++++++++++++ ...-on-internal-network-824fdea1a900891c.yaml | 9 +++++ 3 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/disallow-setting-default-on-internal-network-824fdea1a900891c.yaml diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 00cb782b73..3f579b6d52 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -16,6 +16,7 @@ from cliff import columns as cliff_columns from osc_lib.cli import format_columns from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils from osc_lib.utils import tags as _tag @@ -125,6 +126,9 @@ def _get_attrs_network(client_manager, parsed_args): attrs['is_default'] = False if parsed_args.default: attrs['is_default'] = True + if attrs.get('is_default') and not attrs.get('router:external'): + msg = _("Cannot set default for internal network") + raise exceptions.CommandError(msg) # Update Provider network options if parsed_args.provider_network_type: attrs['provider:network_type'] = parsed_args.provider_network_type @@ -702,7 +706,8 @@ def get_parser(self, prog_name): default_router_grp.add_argument( '--default', action='store_true', - help=_("Set the network as the default external network") + help=_("Set the network as the default external network " + "(cannot be used with internal network).") ) default_router_grp.add_argument( '--no-default', diff --git a/openstackclient/tests/unit/network/v2/test_network.py b/openstackclient/tests/unit/network/v2/test_network.py index 5f8eed6702..45d6008b5b 100644 --- a/openstackclient/tests/unit/network/v2/test_network.py +++ b/openstackclient/tests/unit/network/v2/test_network.py @@ -278,6 +278,24 @@ def test_create_with_tags(self): def test_create_with_no_tag(self): self._test_create_with_tag(add_tags=False) + def test_create_default_internal(self): + arglist = [ + self._network.name, + "--default", + ] + verifylist = [ + ('name', self._network.name), + ('enable', True), + ('share', None), + ('project', None), + ('external', False), + ('default', True), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + class TestCreateNetworkIdentityV2(TestNetwork): @@ -1025,6 +1043,21 @@ def test_set_with_tags(self): def test_set_with_no_tag(self): self._test_set_tags(with_tags=False) + def test_set_default_internal(self): + arglist = [ + self._network.name, + '--internal', + '--default', + ] + verifylist = [ + ('internal', True), + ('default', True), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + class TestShowNetwork(TestNetwork): diff --git a/releasenotes/notes/disallow-setting-default-on-internal-network-824fdea1a900891c.yaml b/releasenotes/notes/disallow-setting-default-on-internal-network-824fdea1a900891c.yaml new file mode 100644 index 0000000000..baf4efe91d --- /dev/null +++ b/releasenotes/notes/disallow-setting-default-on-internal-network-824fdea1a900891c.yaml @@ -0,0 +1,9 @@ +--- +fixes: + - | + For ``network create`` the + `--default`` option should be only used for external networks. + After this release, we enforce this scenario. If a users attempts + to create an internal default network or update a network to be + internal default, the command will be denied. + [Bug `1745658 `_] From 519296d762aceed6a9b3b3c206725d03fa3c2a20 Mon Sep 17 00:00:00 2001 From: huangshan Date: Fri, 11 May 2018 10:54:11 +0800 Subject: [PATCH 2166/3095] Update http links in docs This patch is proposed according to the Direction 10 of doc migration(https://etherpad.openstack.org/p/doc-migration-tracking). Change-Id: I0061bf788a8da89da0077db63f6cecf2ead0d0be --- CONTRIBUTING.rst | 4 ++-- doc/source/index.rst | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index dfdfe471a7..69ecd79cad 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -1,13 +1,13 @@ If you would like to contribute to the development of OpenStack, you must follow the steps documented at: - http://docs.openstack.org/infra/manual/developers.html#development-workflow + https://docs.openstack.org/infra/manual/developers.html Once those steps have been completed, changes to OpenStack should be submitted for review via the Gerrit tool, following the workflow documented at: - http://docs.openstack.org/infra/manual/developers.html#development-workflow + https://docs.openstack.org/infra/manual/developers.html#development-workflow Pull requests submitted through GitHub will be ignored. diff --git a/doc/source/index.rst b/doc/source/index.rst index 732e13872a..7675d6c379 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -29,7 +29,7 @@ Release Notes .. toctree:: :maxdepth: 1 - Release Notes + Release Notes Contributor Documentation ------------------------- From 711b9c9405c55ca8bb5649e2b56202845356e049 Mon Sep 17 00:00:00 2001 From: Rodolfo Alonso Hernandez Date: Mon, 2 Mar 2020 16:40:15 +0000 Subject: [PATCH 2167/3095] Add "fields" parameter to ListSecurityGroup query This new query parameter will allow to send a query sending the "fields" parameter. This "fields" parameter contains the needed API fields, translated into OVO fields in Neutron server, that require to be retrieved from the DB. As commented in the related bug, the OSC "list" command only prints five parameters, none of them the security group rules. In systems with a reasonable amount of security groups, skipping the unnecessary rule load can save a lot of time. Depends-On: https://review.opendev.org/#/c/710820/ Change-Id: I16f48e292997d029d68f66365db949b9f4b5a0c8 Closes-Bug: #1865223 --- openstackclient/network/v2/security_group.py | 4 +++- .../network/v2/test_security_group_network.py | 17 ++++++++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py index f8153fa8b2..2033af1432 100644 --- a/openstackclient/network/v2/security_group.py +++ b/openstackclient/network/v2/security_group.py @@ -202,6 +202,7 @@ def take_action_compute(self, client, parsed_args): # the OSC minimum requirements include SDK 1.0. class ListSecurityGroup(common.NetworkAndComputeLister): _description = _("List security groups") + FIELDS_TO_RETRIEVE = ['id', 'name', 'description', 'project_id', 'tags'] def update_parser_network(self, parser): if not self.is_docs_build: @@ -251,7 +252,8 @@ def take_action_network(self, client, parsed_args): filters['project_id'] = project_id _tag.get_tag_filtering_args(parsed_args, filters) - data = client.security_groups(**filters) + data = client.security_groups(fields=self.FIELDS_TO_RETRIEVE, + **filters) columns = ( "ID", diff --git a/openstackclient/tests/unit/network/v2/test_security_group_network.py b/openstackclient/tests/unit/network/v2/test_security_group_network.py index 14d5751436..67908fa8bf 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_network.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_network.py @@ -285,7 +285,8 @@ def test_security_group_list_no_options(self): columns, data = self.cmd.take_action(parsed_args) - self.network.security_groups.assert_called_once_with() + self.network.security_groups.assert_called_once_with( + fields=security_group.ListSecurityGroup.FIELDS_TO_RETRIEVE) self.assertEqual(self.columns, columns) self.assertListItemEqual(self.data, list(data)) @@ -300,7 +301,8 @@ def test_security_group_list_all_projects(self): columns, data = self.cmd.take_action(parsed_args) - self.network.security_groups.assert_called_once_with() + self.network.security_groups.assert_called_once_with( + fields=security_group.ListSecurityGroup.FIELDS_TO_RETRIEVE) self.assertEqual(self.columns, columns) self.assertListItemEqual(self.data, list(data)) @@ -316,7 +318,9 @@ def test_security_group_list_project(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - filters = {'tenant_id': project.id, 'project_id': project.id} + filters = { + 'tenant_id': project.id, 'project_id': project.id, + 'fields': security_group.ListSecurityGroup.FIELDS_TO_RETRIEVE} self.network.security_groups.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) @@ -336,7 +340,9 @@ def test_security_group_list_project_domain(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - filters = {'tenant_id': project.id, 'project_id': project.id} + filters = { + 'tenant_id': project.id, 'project_id': project.id, + 'fields': security_group.ListSecurityGroup.FIELDS_TO_RETRIEVE} self.network.security_groups.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) @@ -362,7 +368,8 @@ def test_list_with_tag_options(self): **{'tags': 'red,blue', 'any_tags': 'red,green', 'not_tags': 'orange,yellow', - 'not_any_tags': 'black,white'} + 'not_any_tags': 'black,white', + 'fields': security_group.ListSecurityGroup.FIELDS_TO_RETRIEVE} ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) From 3e83e7471b57ed1a2c29a5402059e21da6db0666 Mon Sep 17 00:00:00 2001 From: Jose Castro Leon Date: Thu, 12 Mar 2020 14:43:18 +0100 Subject: [PATCH 2168/3095] Allow os quota list query to filter by project In the os quota list command, project parameter is completely ignored ending up in a request to all projects and then all quotas. This patch enables back the parameter and does a single call to quotas if specified. Change-Id: Ie17c256e2bdc307dcd94ad5be7abdbffa776d369 Story: 2007422 Task: 39043 --- openstackclient/common/quota.py | 13 +++- .../tests/unit/common/test_quota.py | 69 +++++++++++++++++++ 2 files changed, 80 insertions(+), 2 deletions(-) diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index 37437344dc..71b8ea61f2 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -274,9 +274,18 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - projects = self.app.client_manager.identity.projects.list() result = [] - project_ids = [getattr(p, 'id', '') for p in projects] + project_ids = [] + if parsed_args.project is None: + for p in self.app.client_manager.identity.projects.list(): + project_ids.append(getattr(p, 'id', '')) + else: + identity_client = self.app.client_manager.identity + project = utils.find_resource( + identity_client.projects, + parsed_args.project, + ) + project_ids.append(getattr(project, 'id', '')) if parsed_args.compute: if parsed_args.detail: diff --git a/openstackclient/tests/unit/common/test_quota.py b/openstackclient/tests/unit/common/test_quota.py index bd59ca77fe..3fff062b4c 100644 --- a/openstackclient/tests/unit/common/test_quota.py +++ b/openstackclient/tests/unit/common/test_quota.py @@ -392,6 +392,29 @@ def test_quota_list_compute_no_project_5xx(self): parsed_args, ) + def test_quota_list_compute_by_project(self): + # Two projects with non-default quotas + self.compute.quotas.get = mock.Mock( + side_effect=self.compute_quotas, + ) + + arglist = [ + '--compute', + '--project', self.projects[0].name, + ] + verifylist = [ + ('compute', True), + ('project', self.projects[0].name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + ret_quotas = list(data) + + self.assertEqual(self.compute_column_header, columns) + self.assertEqual(self.compute_reference_data, ret_quotas[0]) + self.assertEqual(1, len(ret_quotas)) + def test_quota_list_network(self): # Two projects with non-default quotas self.network.get_quota = mock.Mock( @@ -461,6 +484,29 @@ def test_quota_list_network_no_project(self): self.assertEqual(self.network_reference_data, ret_quotas[0]) self.assertEqual(1, len(ret_quotas)) + def test_quota_list_network_by_project(self): + # Two projects with non-default quotas + self.network.get_quota = mock.Mock( + side_effect=self.network_quotas, + ) + + arglist = [ + '--network', + '--project', self.projects[0].name, + ] + verifylist = [ + ('network', True), + ('project', self.projects[0].name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + ret_quotas = list(data) + + self.assertEqual(self.network_column_header, columns) + self.assertEqual(self.network_reference_data, ret_quotas[0]) + self.assertEqual(1, len(ret_quotas)) + def test_quota_list_volume(self): # Two projects with non-default quotas self.volume.quotas.get = mock.Mock( @@ -530,6 +576,29 @@ def test_quota_list_volume_no_project(self): self.assertEqual(self.volume_reference_data, ret_quotas[0]) self.assertEqual(1, len(ret_quotas)) + def test_quota_list_volume_by_project(self): + # Two projects with non-default quotas + self.volume.quotas.get = mock.Mock( + side_effect=self.volume_quotas, + ) + + arglist = [ + '--volume', + '--project', self.projects[0].name, + ] + verifylist = [ + ('volume', True), + ('project', self.projects[0].name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + ret_quotas = list(data) + + self.assertEqual(self.volume_column_header, columns) + self.assertEqual(self.volume_reference_data, ret_quotas[0]) + self.assertEqual(1, len(ret_quotas)) + class TestQuotaSet(TestQuota): From 8c47b67e8337c05a6e14d717452a4438aa17167f Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Wed, 4 Mar 2020 08:56:08 -0600 Subject: [PATCH 2169/3095] Build utility image for using osc python-openstackclient currently has a non-zero number of dependencies, so for admins who would like to run it on laptops or similar it can get tricky. In opendev, for instance, admins have it installed into a venv on a jump host, but it's really wonky to keep up with. Use the opendev/python-builder opendev/python-base pair to make a minimal image that contains an install of python-openstackclient and publish it to the osclient org on dockerhub. There is an overall policy against having binary artifacts such as this appear to be official deliverables of the OpenStack project, which this is not. It's also only publishing images based on master, so no warranties should be implied. But if this makes life easier for a user somewhere, cool. Change-Id: I9a8bfc27c127e92b6856cb6a3e45b32c818db16c --- .zuul.yaml | 58 +++++++++++++++++++++++++++++++++++++++++++ Dockerfile | 26 +++++++++++++++++++ examples/openstack.sh | 33 ++++++++++++++++++++++++ 3 files changed, 117 insertions(+) create mode 100644 Dockerfile create mode 100755 examples/openstack.sh diff --git a/.zuul.yaml b/.zuul.yaml index 15f719c356..26ca146ff6 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -152,6 +152,59 @@ tox_envlist: functional tox_install_siblings: true +- secret: + name: osc-dockerhub + data: + username: osclientzuul + password: !encrypted/pkcs1-oaep + - qQ0O7bXUWBhkygjSKcPHogWvR2ax67EgHZcYd27zgg6KvpdK9GsNTRTIeD5yeBb9Dzr/K + RcAf+0pQT3fRIsKyEx2odHNevGpePjmUZENd5vHTIvTuZWq+X5ehpXgkEYvw3jwYJg78F + ids1igEaHsE86OMHjWauyc1QUzYfwkf+ziK7TIOZ6RpVRHgq5Bf9S+Hz/QnVdxOLaIlO0 + VC/bchKX/36vOQKd20KkNhBQAnUlDBQWMnZocvZKZYtkDs2w2vqlnUPRlzEppBWm5Yae6 + 5acyIHEEAIbECd/wC/OT8YndoeOUiqOZY0uSWtv4JgEKl6AexP+54VxPrsz7LayRMDJ4B + jVCZK6y1sss9mF6mNXvZipPEVgklGcGM76GfGdqTeuQ3i8CqaKmCTBo1IKlEmcslXR/5T + vjibWzvNHPpFcpYEEM6GLGg2K6nja1MCE1s/L76pN3FtxCZHdl8rZXU+mJH37uQk9zvdR + Y6qtWJ+3o5sbgYfjgdp/nPs1xXMUvuG83qykuzYgtOYvlEw51eqwd2SPXd3op/KApAhKR + Zlu8fBUkm/FyXToOpCl0s/eR4w1d+Spv0A+UhrS5pmV18+NlpNs0Krj5wS9KWMUIec0ae + opgPkQrFfj/zD45rrIUJRzT+alZlZeK+WQfeNOXt2i6MLtOPesHMukTc6ksXtA= + +- job: + name: osc-build-image + parent: opendev-build-docker-image + description: Build Docker images. + allowed-projects: openstack/python-openstackclient + requires: + - python-builder-container-image + - python-base-container-image + provides: osc-container-image + vars: &osc_image_vars + docker_images: + - context: . + repository: osclient/python-openstackclient + +- job: + name: osc-upload-image + parent: opendev-upload-docker-image + description: Build Docker images and upload to Docker Hub. + allowed-projects: openstack/python-openstackclient + requires: + - python-builder-container-image + - python-base-container-image + provides: osc-container-image + vars: *osc_image_vars + secrets: &osc_image_secrets + - name: docker_credentials + secret: osc-dockerhub + pass-to-parent: true + +- job: + name: osc-promote-image + parent: opendev-promote-docker-image + allowed-projects: openstack/python-openstackclient + description: Promote previously uploaded Docker images. + vars: *osc_image_vars + secrets: *osc_image_secrets + - project-template: name: osc-tox-unit-tips check: @@ -174,6 +227,7 @@ - lib-forward-testing-python3 check: jobs: + - osc-build-image - osc-functional-devstack # - osc-functional-devstack-n-net: # voting: false @@ -187,4 +241,8 @@ branches: ^(?!stable) gate: jobs: + - osc-upload-image - osc-functional-devstack + promote: + jobs: + - osc-promote-image diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000..9ff8084c30 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,26 @@ +# Copyright (c) 2020 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FROM docker.io/opendevorg/python-builder as builder + +COPY . /tmp/src +RUN assemble + +FROM docker.io/opendevorg/python-base + +COPY --from=builder /output/ /output +RUN /output/install-from-bindep + +CMD ["/usr/local/bin/openstack"] diff --git a/examples/openstack.sh b/examples/openstack.sh new file mode 100755 index 0000000000..aa35acfeb8 --- /dev/null +++ b/examples/openstack.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# Copyright (c) 2020 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is an example script that can be installed somewhere, such as +# /usr/local/bin/openstack, that will allow using python-openstackclient +# via the container image instead of via a direct installation. It +# bind-mounts in clouds.yaml files, so it should behave like a directly +# installed osc. + +if type podman 2>/dev/null ; then + RUNTIME=podman +else + RUNTIME=docker +fi + +exec $RUNTIME run -it --rm \ + -v/etc/openstack:/etc/openstack \ + -v$HOME/.config/openstack:/root/.config/openstack \ + osclient/openstackclient \ + openstack $@ From bf2beb9e868175adb775ec253ef4a929ac557975 Mon Sep 17 00:00:00 2001 From: Daniel Bengtsson Date: Fri, 15 Nov 2019 10:06:35 +0100 Subject: [PATCH 2170/3095] Stop configuring install_command in tox and stop use pip. Currently, we are overriding 'install_command' to use 'pip'. This is considered poor behavior and 'python -m pip' should be used instead: https://snarky.ca/why-you-should-use-python-m-pip/ It turns out that this is the the default value provided by tox: https://tox.readthedocs.io/en/latest/config.html#conf-install_command So we can remove the line and simply use the default value. Use the right way when it's necessary. Change-Id: I410173d5fdcd8c592d98eed2f48b98e06299e8b3 --- lower-constraints.txt | 2 +- requirements.txt | 2 +- tox.ini | 31 +++++++++++++++---------------- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/lower-constraints.txt b/lower-constraints.txt index 0e00a11488..5fdd4dd5fc 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -38,7 +38,7 @@ jmespath==0.9.0 jsonpatch==1.16 jsonpointer==1.13 jsonschema==2.6.0 -keystoneauth1==3.6.2 +keystoneauth1==3.14.0 kombu==4.0.0 linecache2==1.0.0 MarkupSafe==1.0 diff --git a/requirements.txt b/requirements.txt index f7e2cecadb..aaea495e14 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ six>=1.10.0 # MIT Babel!=2.4.0,>=2.3.4 # BSD cliff!=2.9.0,>=2.8.0 # Apache-2.0 -keystoneauth1>=3.6.2 # Apache-2.0 +keystoneauth1>=3.14.0 # Apache-2.0 openstacksdk>=0.17.0 # Apache-2.0 osc-lib>=2.0.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 diff --git a/tox.ini b/tox.ini index 3a7ed09794..3b4b66a0fe 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -minversion = 3.1 +minversion = 3.2.0 envlist = py37,pep8 skipdist = True # Automatic envs (pyXX) will only use the python version appropriate to that @@ -10,9 +10,7 @@ ignore_basepython_conflict = True [testenv] usedevelop = True basepython = python3 -install_command = pip install {opts} {packages} -setenv = VIRTUAL_ENV={envdir} - OS_STDOUT_CAPTURE=1 +setenv = OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 OS_TEST_TIMEOUT=60 deps = @@ -24,6 +22,7 @@ whitelist_externals = stestr [testenv:fast8] # Use same environment directory as pep8 env to save space and install time +setenv = VIRTUAL_ENV={envdir} envdir = {toxworkdir}/pep8 commands = {toxinidir}/tools/fast8.sh @@ -62,12 +61,12 @@ commands = [testenv:unit-tips] commands = - pip install -q -U -e "git+file://{toxinidir}/../cliff#egg=cliff" - pip install -q -U -e "git+file://{toxinidir}/../keystoneauth#egg=keystoneauth" - pip install -q -U -e "git+file://{toxinidir}/../osc-lib#egg=osc_lib" - pip install -q -U -e "git+file://{toxinidir}/../os-client-config#egg=os_client_config" - pip install -q -e "git+file://{toxinidir}/../openstacksdk#egg=openstacksdk" - pip freeze + python -m pip install -q -U -e "git+file://{toxinidir}/../cliff#egg=cliff" + python -m pip install -q -U -e "git+file://{toxinidir}/../keystoneauth#egg=keystoneauth" + python -m pip install -q -U -e "git+file://{toxinidir}/../osc-lib#egg=osc_lib" + python -m pip install -q -U -e "git+file://{toxinidir}/../os-client-config#egg=os_client_config" + pythom -m pip install -q -e "git+file://{toxinidir}/../openstacksdk#egg=openstacksdk" + python -m pip freeze stestr run {posargs} whitelist_externals = stestr @@ -81,12 +80,12 @@ commands = setenv = OS_TEST_PATH=./openstackclient/tests/functional passenv = OS_* commands = - pip install -q -U -e "git+file://{toxinidir}/../cliff#egg=cliff" - pip install -q -U -e "git+file://{toxinidir}/../keystoneauth#egg=keystoneauth" - pip install -q -U -e "git+file://{toxinidir}/../osc-lib#egg=osc_lib" - pip install -q -U -e "git+file://{toxinidir}/../os-client-config#egg=os_client_config" - pip install -q -U -e "git+file://{toxinidir}/../openstacksdk#egg=openstacksdk" - pip freeze + python -m pip install -q -U -e "git+file://{toxinidir}/../cliff#egg=cliff" + python -m pip install -q -U -e "git+file://{toxinidir}/../keystoneauth#egg=keystoneauth" + python -m pip install -q -U -e "git+file://{toxinidir}/../osc-lib#egg=osc_lib" + python -m pip install -q -U -e "git+file://{toxinidir}/../os-client-config#egg=os_client_config" + python -m pip install -q -U -e "git+file://{toxinidir}/../openstacksdk#egg=openstacksdk" + python -m pip freeze stestr run {posargs} [testenv:venv] From d2826e89e99ba63335a468e50e49db2fad6ab337 Mon Sep 17 00:00:00 2001 From: Daniel Strong Date: Wed, 18 Mar 2020 15:51:55 +0000 Subject: [PATCH 2171/3095] Allow setting floating IP description Change-Id: If664bfe3c9fdcb69c7046eb16c5d32602d1b3262 Story: 2007439 Task: 39094 --- openstackclient/network/v2/floating_ip.py | 8 ++++++ .../network/v2/test_floating_ip_network.py | 26 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index 4525913f52..f3e3e5c44c 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -412,6 +412,11 @@ def get_parser(self, prog_name): help=_("Fixed IP of the port " "(required only if port has multiple IPs)") ) + parser.add_argument( + '--description', + metavar='', + help=_('Set floating IP description') + ) qos_policy_group = parser.add_mutually_exclusive_group() qos_policy_group.add_argument( '--qos-policy', @@ -443,6 +448,9 @@ def take_action(self, parsed_args): if parsed_args.fixed_ip_address: attrs['fixed_ip_address'] = parsed_args.fixed_ip_address + if parsed_args.description: + attrs['description'] = parsed_args.description + if parsed_args.qos_policy: attrs['qos_policy_id'] = client.find_qos_policy( parsed_args.qos_policy, ignore_missing=False).id diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip_network.py b/openstackclient/tests/unit/network/v2/test_floating_ip_network.py index a98051e769..dbcd5c9782 100644 --- a/openstackclient/tests/unit/network/v2/test_floating_ip_network.py +++ b/openstackclient/tests/unit/network/v2/test_floating_ip_network.py @@ -776,6 +776,32 @@ def test_fixed_ip_option(self): self.network.update_ip.assert_called_once_with( self.floating_ip, **attrs) + def test_description_option(self): + arglist = [ + self.floating_ip.id, + '--port', self.floating_ip.port_id, + '--description', self.floating_ip.description, + ] + verifylist = [ + ('floating_ip', self.floating_ip.id), + ('port', self.floating_ip.port_id), + ('description', self.floating_ip.description), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + attrs = { + 'port_id': self.floating_ip.port_id, + 'description': self.floating_ip.description, + } + self.network.find_ip.assert_called_once_with( + self.floating_ip.id, + ignore_missing=False, + ) + self.network.update_ip.assert_called_once_with( + self.floating_ip, **attrs) + def test_qos_policy_option(self): qos_policy = network_fakes.FakeNetworkQosPolicy.create_one_qos_policy() self.network.find_qos_policy = mock.Mock(return_value=qos_policy) From 332457bc875401ba990a50c2d7ed14f49ea4e462 Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Thu, 19 Mar 2020 16:17:10 -0500 Subject: [PATCH 2172/3095] Update image building jobs We're failing on promote but not upload. That's weird. Make sure the secret is appropriately encoded, and copy what zuul is doing. Also make promote a zero-node job. Change-Id: Ifcb5b4fe2486087a5ca1ff9609f7bf09ef026974 --- .zuul.yaml | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/.zuul.yaml b/.zuul.yaml index 26ca146ff6..3e8478d62a 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -157,16 +157,16 @@ data: username: osclientzuul password: !encrypted/pkcs1-oaep - - qQ0O7bXUWBhkygjSKcPHogWvR2ax67EgHZcYd27zgg6KvpdK9GsNTRTIeD5yeBb9Dzr/K - RcAf+0pQT3fRIsKyEx2odHNevGpePjmUZENd5vHTIvTuZWq+X5ehpXgkEYvw3jwYJg78F - ids1igEaHsE86OMHjWauyc1QUzYfwkf+ziK7TIOZ6RpVRHgq5Bf9S+Hz/QnVdxOLaIlO0 - VC/bchKX/36vOQKd20KkNhBQAnUlDBQWMnZocvZKZYtkDs2w2vqlnUPRlzEppBWm5Yae6 - 5acyIHEEAIbECd/wC/OT8YndoeOUiqOZY0uSWtv4JgEKl6AexP+54VxPrsz7LayRMDJ4B - jVCZK6y1sss9mF6mNXvZipPEVgklGcGM76GfGdqTeuQ3i8CqaKmCTBo1IKlEmcslXR/5T - vjibWzvNHPpFcpYEEM6GLGg2K6nja1MCE1s/L76pN3FtxCZHdl8rZXU+mJH37uQk9zvdR - Y6qtWJ+3o5sbgYfjgdp/nPs1xXMUvuG83qykuzYgtOYvlEw51eqwd2SPXd3op/KApAhKR - Zlu8fBUkm/FyXToOpCl0s/eR4w1d+Spv0A+UhrS5pmV18+NlpNs0Krj5wS9KWMUIec0ae - opgPkQrFfj/zD45rrIUJRzT+alZlZeK+WQfeNOXt2i6MLtOPesHMukTc6ksXtA= + - n764ECFMGlEna6S5ezNyvW5nmq8IZCBts/7QRzdo2tWLQMp/mNFoaQensd797Ra3NLaS7 + NzCFJwGQvgWF6hJJIUfnf3h2+RecCwHahLN4r95RtjhAltARzHSZVDCeNXhiRJqSDS4Qc + kmXR2NTNfz8kkWUhnWNVjaBhYdMk0LnqZjQCxiCaR+eNdmeecWlSuJXg0Uz6vObLNfGHO + KxV3RiGc4J0AATYpYFEpC/SyPbBk0pJv6JWJb7nNIe0CEVW/7hkCfA6o3hQ5PNAswn5ZP + sp/L8NdoRQe/fEWOm/9K2lZqQehEj6SKsk6jkx3Wiy5stqFcGfafrxWcfQoQWpKHY5TeP + R9U0jJy+ipnhfnm0flBIBt9XHYykrTuFwp5QVdRhRRQDwg5RZBX+VmaBeSQlS2Z0oJmCX + PXFQmUDfnoU5go0BALlXDdy1sYE+SrQH4Eydw+hgf2oDFh+EkdhXMFburxnU8B7t4ey14 + EM1W4BdOBUgeI4fa/92BP6ipgUFvcJu19FYTdg4v7NZ/ApnwZnZ5KC4eYlDaKNQiPQUmW + pFJrnxxYXeDgmiXij8mCCgo8KEGvPCKHAghZ14iBCaWqvniLXuOSkFI1gYU+llg4i2jAp + ts3GfQqBe8jfROGPMexVuonqZxZBxvWmIgDsAaqAeJCtykS1xeIiAMtA8rNl40= - job: name: osc-build-image @@ -191,19 +191,24 @@ - python-builder-container-image - python-base-container-image provides: osc-container-image - vars: *osc_image_vars - secrets: &osc_image_secrets + secrets: - name: docker_credentials secret: osc-dockerhub pass-to-parent: true + vars: *osc_image_vars - job: name: osc-promote-image parent: opendev-promote-docker-image allowed-projects: openstack/python-openstackclient description: Promote previously uploaded Docker images. + secrets: + - name: docker_credentials + secret: osc-dockerhub + pass-to-parent: true + nodeset: + nodes: [] vars: *osc_image_vars - secrets: *osc_image_secrets - project-template: name: osc-tox-unit-tips From aac4f8c4f190d1f56736761e2fa811a76cc4991b Mon Sep 17 00:00:00 2001 From: Bence Romsics Date: Fri, 20 Mar 2020 11:52:29 +0100 Subject: [PATCH 2173/3095] Bump lower constraint of MarkupSafe setuptools 46.0.0's drop of the Features feature broke python-openstackclient's lower-constraints job on master via the MarkupSafe package. Bump the lower constraint of MarkupSafe to fix lower-constraints on master. Change-Id: Ib0a6f94a6611b221efbf76f6f25b55c43782546f --- lower-constraints.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lower-constraints.txt b/lower-constraints.txt index 5fdd4dd5fc..d29de0ab99 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -41,7 +41,7 @@ jsonschema==2.6.0 keystoneauth1==3.14.0 kombu==4.0.0 linecache2==1.0.0 -MarkupSafe==1.0 +MarkupSafe==1.1.0 mccabe==0.2.1 mock==2.0.0 monotonic==0.6 From 42abde330eb830aff7e1b63919df2d53e607ce4d Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Mon, 23 Mar 2020 08:08:52 -0500 Subject: [PATCH 2174/3095] Change dockerhub password Changed it dockerhub side. Change-Id: I1befae9622fc1ef72cd77cfd5792aad3fa231a6a --- .zuul.yaml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.zuul.yaml b/.zuul.yaml index 3e8478d62a..742de7af90 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -157,16 +157,16 @@ data: username: osclientzuul password: !encrypted/pkcs1-oaep - - n764ECFMGlEna6S5ezNyvW5nmq8IZCBts/7QRzdo2tWLQMp/mNFoaQensd797Ra3NLaS7 - NzCFJwGQvgWF6hJJIUfnf3h2+RecCwHahLN4r95RtjhAltARzHSZVDCeNXhiRJqSDS4Qc - kmXR2NTNfz8kkWUhnWNVjaBhYdMk0LnqZjQCxiCaR+eNdmeecWlSuJXg0Uz6vObLNfGHO - KxV3RiGc4J0AATYpYFEpC/SyPbBk0pJv6JWJb7nNIe0CEVW/7hkCfA6o3hQ5PNAswn5ZP - sp/L8NdoRQe/fEWOm/9K2lZqQehEj6SKsk6jkx3Wiy5stqFcGfafrxWcfQoQWpKHY5TeP - R9U0jJy+ipnhfnm0flBIBt9XHYykrTuFwp5QVdRhRRQDwg5RZBX+VmaBeSQlS2Z0oJmCX - PXFQmUDfnoU5go0BALlXDdy1sYE+SrQH4Eydw+hgf2oDFh+EkdhXMFburxnU8B7t4ey14 - EM1W4BdOBUgeI4fa/92BP6ipgUFvcJu19FYTdg4v7NZ/ApnwZnZ5KC4eYlDaKNQiPQUmW - pFJrnxxYXeDgmiXij8mCCgo8KEGvPCKHAghZ14iBCaWqvniLXuOSkFI1gYU+llg4i2jAp - ts3GfQqBe8jfROGPMexVuonqZxZBxvWmIgDsAaqAeJCtykS1xeIiAMtA8rNl40= + - X666jS/g43U4ykLzuQSNhl9XOVpKViT1bMQb/YJgTHj8AzigONu3+4BrxItS7mqwp9AWp + YtNHZ0Dju3piNw07ulnsOAYHpBcz+zFK5UzVMXzkHk5vu1W0WWYLVayU4HJKliAwPmNii + J3itt196sQ6IhIe/Hamcm715rP9cQd50w32lvAV4RNXoc4Gq7ZzOIzyA80UEl6HY7jiR9 + WYFI4PoCrmNxKKV8YAaH1OlJW71WF0O+77+oostKwFG35bxIniUl1ZinxOgjC2va8j5nT + KKgVgQdjwjuMOXEA6iiLewAo39P+nL/81AheDGQNex6x1oJuVRklQmiznbQI3knTOeJIh + ncRJuE9RfS0xuR5J45WOdKWVjIYpMkTXF+5Kn/pEtNelA2ADzaloGidNxEOvOD74vVf68 + mtZ1kffQi6d7yq57m0ZJ8WwTEo5g8Z+tmS40U2vdZ0vN5c/keGJisKbOpwavloP9owize + U9EgIAMIT6LE/fJWPGGugMR57z7gnCNiq970crTjWvsP1yFGvo/FQiUpplv5ni6C+s9jq + 1rba4Ya0uxQa2r5zjFXNJ52zGoRVlpjSsfpEtvCmTzfF5p0fa2aSyDs8ZOjaiDCVSmTPn + SE9rutyH0XO7CP9RFetxirfgSQ8ZDpUmXpfKaTGa0glIfjmmf4yyaaJRKOkubg= - job: name: osc-build-image From 27da238da2f9a8b35082d06624313a844cb6cc6f Mon Sep 17 00:00:00 2001 From: Rodolfo Alonso Hernandez Date: Wed, 26 Feb 2020 12:24:52 +0000 Subject: [PATCH 2175/3095] Fix network segment range "_get_ranges" function This function should return an ordered set of ranges based on an unordered list of numbers (int or str). Change-Id: I918c8befc51236cc33d96a5c88fb6eafdd143e9c Story: 2007341 Task: 38878 --- .../network/v2/network_segment_range.py | 3 +-- .../unit/network/v2/test_network_segment_range.py | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/openstackclient/network/v2/network_segment_range.py b/openstackclient/network/v2/network_segment_range.py index 2cdae642e5..b38c72c248 100644 --- a/openstackclient/network/v2/network_segment_range.py +++ b/openstackclient/network/v2/network_segment_range.py @@ -23,7 +23,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ from openstackclient.identity import common as identity_common @@ -42,7 +41,7 @@ def _get_columns(item): def _get_ranges(item): - item = [int(i) if isinstance(i, six.string_types) else i for i in item] + item = sorted([int(i) for i in item]) for a, b in itertools.groupby(enumerate(item), lambda xy: xy[1] - xy[0]): b = list(b) yield "%s-%s" % (b[0][1], b[-1][1]) if b[0][1] != b[-1][1] else \ diff --git a/openstackclient/tests/unit/network/v2/test_network_segment_range.py b/openstackclient/tests/unit/network/v2/test_network_segment_range.py index 89a0c223ce..b60f1710b7 100644 --- a/openstackclient/tests/unit/network/v2/test_network_segment_range.py +++ b/openstackclient/tests/unit/network/v2/test_network_segment_range.py @@ -24,6 +24,20 @@ from openstackclient.tests.unit import utils as tests_utils +class TestAuxiliaryFunctions(tests_utils.TestCase): + + def test__get_ranges(self): + input_reference = [ + ([1, 2, 3, 4, 5, 6, 7], ['1-7']), + ([1, 2, 5, 4, 3, 6, 7], ['1-7']), + ([1, 2, 4, 3, 7, 6], ['1-4', '6-7']), + ([1, 2, 4, 3, '13', 12, '7', '6'], ['1-4', '6-7', '12-13']) + ] + for input, reference in input_reference: + self.assertEqual(reference, + list(network_segment_range._get_ranges(input))) + + class TestNetworkSegmentRange(network_fakes.TestNetworkV2): def setUp(self): From 9d96a13a97f0da0ae178433140e8aee8d834c6a1 Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Mon, 23 Mar 2020 12:55:11 -0500 Subject: [PATCH 2176/3095] Remove trailing newline from dockerhub secret When doing encrypt_secret, one should use echo -n not just echo. Change-Id: Iefbf0f13cd349b05de910f95b9467877cb53e46b --- .zuul.yaml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.zuul.yaml b/.zuul.yaml index 742de7af90..7a7e264ca7 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -157,16 +157,16 @@ data: username: osclientzuul password: !encrypted/pkcs1-oaep - - X666jS/g43U4ykLzuQSNhl9XOVpKViT1bMQb/YJgTHj8AzigONu3+4BrxItS7mqwp9AWp - YtNHZ0Dju3piNw07ulnsOAYHpBcz+zFK5UzVMXzkHk5vu1W0WWYLVayU4HJKliAwPmNii - J3itt196sQ6IhIe/Hamcm715rP9cQd50w32lvAV4RNXoc4Gq7ZzOIzyA80UEl6HY7jiR9 - WYFI4PoCrmNxKKV8YAaH1OlJW71WF0O+77+oostKwFG35bxIniUl1ZinxOgjC2va8j5nT - KKgVgQdjwjuMOXEA6iiLewAo39P+nL/81AheDGQNex6x1oJuVRklQmiznbQI3knTOeJIh - ncRJuE9RfS0xuR5J45WOdKWVjIYpMkTXF+5Kn/pEtNelA2ADzaloGidNxEOvOD74vVf68 - mtZ1kffQi6d7yq57m0ZJ8WwTEo5g8Z+tmS40U2vdZ0vN5c/keGJisKbOpwavloP9owize - U9EgIAMIT6LE/fJWPGGugMR57z7gnCNiq970crTjWvsP1yFGvo/FQiUpplv5ni6C+s9jq - 1rba4Ya0uxQa2r5zjFXNJ52zGoRVlpjSsfpEtvCmTzfF5p0fa2aSyDs8ZOjaiDCVSmTPn - SE9rutyH0XO7CP9RFetxirfgSQ8ZDpUmXpfKaTGa0glIfjmmf4yyaaJRKOkubg= + - LbIZjJiVstRVXMpoLQ3+/JcNB6lKVUWJXXo5+Outf+PKAaO7mNnv8XLiFMKnJ6ftopLyu + hWbX9rA+NddvplLQkf1xxkh7QBBU8PToLr58quI2SENUclt4tpjxbZfZu451kFSNJvNvR + E58cHHpfJZpyRnS2htXmN/Qy24gbV2w7CQxSZD2YhlcrerD8uQ8rWEnlY1wcJEaEGomtS + ZTGxsdK2TsZC2cd4b7TG7+xbl2i+hjADzwSQAgUzlLlwuG71667+IWk4SOZ7OycJTv9NN + ZTak8+CGfiMKdmsxZ1Z8uD7DC+RIklDjMWyly6zuhWzfhOmsmU0CesR50moodRUvbK79p + NZM8u0hBex5cl2EpUEwJL/FSPJXUhDMPoMoTZT/SAuXf25R9eZ9JGrKsIAlmVhpl8ifoE + 8TpPyvIHGS3YelTQjhqOX0wGb9T4ZauQCcI5Ajzy9NuCTyD9xxme9OX1zz7gMACRnVHvz + q7U7Ue90MnmGH6E2SgKjIZhyzy9Efwb7JUvH1Zb3hlrjCjEhwi9MV5FnABTEeXyYwE10s + 3o/KZg2zvdWkVG6x0dEkjpoQaNuaB7T2Na7Sm421n/z3LCzhiQGuTUjENnL6cMEtuA6Pp + BfI5+Qlg7HMwkBXNB73EPfWHzbCR3VNrzGYTy9FvhGud0/cXsuBXgps4WH63ic= - job: name: osc-build-image From 60e7c51df4cf061ebbb435a959ad63c7d3a296bf Mon Sep 17 00:00:00 2001 From: Artem Goncharov Date: Fri, 13 Sep 2019 18:03:15 +0200 Subject: [PATCH 2177/3095] Switch image to use SDK This is a work to switch OSC from using glanceclient to OpenStackSDK. With this change only v2 is using OpenStackSDK. V1 is still using glanceclient and will be switched in a separate change. Remove the direct depend on keystoneauth- let that flow through openstacksdk. Depends-on: https://review.opendev.org/#/c/698972 Change-Id: I36f292fb70c98f6e558f58be55d533d979c47ca7 --- lower-constraints.txt | 6 +- openstackclient/common/sdk_utils.py | 60 ++ openstackclient/compute/v2/server.py | 31 +- openstackclient/compute/v2/server_backup.py | 7 +- openstackclient/compute/v2/server_image.py | 7 +- openstackclient/image/client.py | 76 +-- openstackclient/image/v2/image.py | 274 +++++---- .../tests/unit/compute/v2/test_server.py | 93 ++- .../unit/compute/v2/test_server_backup.py | 54 +- .../unit/compute/v2/test_server_image.py | 19 +- openstackclient/tests/unit/image/v2/fakes.py | 24 +- .../tests/unit/image/v2/test_image.py | 547 +++++++++--------- .../tests/unit/volume/v2/test_volume.py | 8 +- openstackclient/volume/v2/volume.py | 5 +- .../use_sdk_for_image-f49d2df38e7d9f81.yaml | 4 + requirements.txt | 3 +- 16 files changed, 660 insertions(+), 558 deletions(-) create mode 100644 openstackclient/common/sdk_utils.py create mode 100644 releasenotes/notes/use_sdk_for_image-f49d2df38e7d9f81.yaml diff --git a/lower-constraints.txt b/lower-constraints.txt index 5fdd4dd5fc..1754ed7780 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -38,7 +38,7 @@ jmespath==0.9.0 jsonpatch==1.16 jsonpointer==1.13 jsonschema==2.6.0 -keystoneauth1==3.14.0 +keystoneauth1==3.16.0 kombu==4.0.0 linecache2==1.0.0 MarkupSafe==1.0 @@ -50,9 +50,9 @@ msgpack-python==0.4.0 munch==2.1.0 netaddr==0.7.18 netifaces==0.10.4 -openstacksdk==0.17.0 +openstacksdk==0.36.0 os-client-config==1.28.0 -os-service-types==1.2.0 +os-service-types==1.7.0 os-testr==1.0.0 osc-lib==2.0.0 osc-placement==1.7.0 diff --git a/openstackclient/common/sdk_utils.py b/openstackclient/common/sdk_utils.py new file mode 100644 index 0000000000..9f0856175d --- /dev/null +++ b/openstackclient/common/sdk_utils.py @@ -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. + +import six + + +def get_osc_show_columns_for_sdk_resource( + sdk_resource, + osc_column_map, + invisible_columns=None +): + """Get and filter the display and attribute columns for an SDK resource. + + Common utility function for preparing the output of an OSC show command. + Some of the columns may need to get renamed, others made invisible. + + :param sdk_resource: An SDK resource + :param osc_column_map: A hash of mappings for display column names + :param invisible_columns: A list of invisible column names + + :returns: Two tuples containing the names of the display and attribute + columns + """ + + if getattr(sdk_resource, 'allow_get', None) is not None: + resource_dict = sdk_resource.to_dict( + body=True, headers=False, ignore_none=False) + else: + resource_dict = sdk_resource + + # Build the OSC column names to display for the SDK resource. + attr_map = {} + display_columns = list(resource_dict.keys()) + invisible_columns = [] if invisible_columns is None else invisible_columns + for col_name in invisible_columns: + if col_name in display_columns: + display_columns.remove(col_name) + for sdk_attr, osc_attr in six.iteritems(osc_column_map): + if sdk_attr in display_columns: + attr_map[osc_attr] = sdk_attr + display_columns.remove(sdk_attr) + if osc_attr not in display_columns: + display_columns.append(osc_attr) + sorted_display_columns = sorted(display_columns) + + # Build the SDK attribute names for the OSC column names. + attr_columns = [] + for column in sorted_display_columns: + new_column = attr_map[column] if column in attr_map else column + attr_columns.append(new_column) + return tuple(sorted_display_columns), tuple(attr_columns) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 5cc73284a2..8be78049c8 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -143,7 +143,7 @@ def _prep_server_detail(compute_client, image_client, server, refresh=True): if image_info: image_id = image_info.get('id', '') try: - image = utils.find_resource(image_client.images, image_id) + image = image_client.get_image(image_id) info['image'] = "%s (%s)" % (image.name, image_id) except Exception: info['image'] = image_id @@ -735,10 +735,8 @@ def _show_progress(progress): # Lookup parsed_args.image image = None if parsed_args.image: - image = utils.find_resource( - image_client.images, - parsed_args.image, - ) + image = image_client.find_image( + parsed_args.image, ignore_missing=False) if not image and parsed_args.image_property: def emit_duplicated_warning(img, image_property): @@ -749,7 +747,7 @@ def emit_duplicated_warning(img, image_property): 'chosen_one': img_uuid_list[0]}) def _match_image(image_api, wanted_properties): - image_list = image_api.image_list() + image_list = image_api.images() images_matched = [] for img in image_list: img_dict = {} @@ -768,7 +766,7 @@ def _match_image(image_api, wanted_properties): return [] return images_matched - images = _match_image(image_client.api, parsed_args.image_property) + images = _match_image(image_client, parsed_args.image_property) if len(images) > 1: emit_duplicated_warning(images, parsed_args.image_property) @@ -890,8 +888,8 @@ def _match_image(image_api, wanted_properties): # one specified by --image, then the compute service will # create a volume from the image and attach it to the # server as a non-root volume. - image_id = utils.find_resource( - image_client.images, dev_map[0]).id + image_id = image_client.find_image(dev_map[0], + ignore_missing=False).id mapping['uuid'] = image_id # 3. append size and delete_on_termination if exist if len(dev_map) > 2 and dev_map[2]: @@ -1324,8 +1322,8 @@ def take_action(self, parsed_args): # image name is given, map it to ID. image_id = None if parsed_args.image: - image_id = utils.find_resource(image_client.images, - parsed_args.image).id + image_id = image_client.find_image(parsed_args.image, + ignore_missing=False).id search_opts = { 'reservation_id': parsed_args.reservation_id, @@ -1476,12 +1474,12 @@ def take_action(self, parsed_args): (s.image.get('id') for s in data if s.image))): try: - images[i_id] = image_client.images.get(i_id) + images[i_id] = image_client.get_image(i_id) except Exception: pass else: try: - images_list = image_client.images.list() + images_list = image_client.images() for i in images_list: images[i.id] = i except Exception: @@ -1925,7 +1923,7 @@ def _show_progress(progress): # If parsed_args.image is not set, default to the currently used one. image_id = parsed_args.image or server.to_dict().get( 'image', {}).get('id') - image = utils.find_resource(image_client.images, image_id) + image = image_client.get_image(image_id) kwargs = {} if parsed_args.property: @@ -2195,10 +2193,7 @@ def take_action(self, parsed_args): image = None if parsed_args.image: - image = utils.find_resource( - image_client.images, - parsed_args.image, - ) + image = image_client.find_image(parsed_args.image) utils.find_resource( compute_client.servers, diff --git a/openstackclient/compute/v2/server_backup.py b/openstackclient/compute/v2/server_backup.py index 1d560dc0c7..a5d43fc6f8 100644 --- a/openstackclient/compute/v2/server_backup.py +++ b/openstackclient/compute/v2/server_backup.py @@ -100,14 +100,11 @@ def _show_progress(progress): ) image_client = self.app.client_manager.image - image = utils.find_resource( - image_client.images, - backup_name, - ) + image = image_client.find_image(backup_name, ignore_missing=False) if parsed_args.wait: if utils.wait_for_status( - image_client.images.get, + image_client.get_image, image.id, callback=_show_progress, ): diff --git a/openstackclient/compute/v2/server_image.py b/openstackclient/compute/v2/server_image.py index b93cd4d887..fea87af81f 100644 --- a/openstackclient/compute/v2/server_image.py +++ b/openstackclient/compute/v2/server_image.py @@ -79,14 +79,11 @@ def _show_progress(progress): ) image_client = self.app.client_manager.image - image = utils.find_resource( - image_client.images, - image_id, - ) + image = image_client.find_image(image_id) if parsed_args.wait: if utils.wait_for_status( - image_client.images.get, + image_client.get_image, image_id, callback=_show_progress, ): diff --git a/openstackclient/image/client.py b/openstackclient/image/client.py index b67c291fd0..15bea17eb6 100644 --- a/openstackclient/image/client.py +++ b/openstackclient/image/client.py @@ -27,7 +27,7 @@ API_NAME = "image" API_VERSIONS = { "1": "glanceclient.v1.client.Client", - "2": "glanceclient.v2.client.Client", + "2": "openstack.connection.Connection", } IMAGE_API_TYPE = 'image' @@ -38,44 +38,52 @@ def make_client(instance): - """Returns an image service client""" - image_client = utils.get_client_class( - API_NAME, - instance._api_version[API_NAME], - API_VERSIONS) - LOG.debug('Instantiating image client: %s', image_client) - - endpoint = instance.get_endpoint_for_service_type( - API_NAME, - region_name=instance.region_name, - interface=instance.interface, - ) - - client = image_client( - endpoint, - token=instance.auth.get_token(instance.session), - cacert=instance.cacert, - insecure=not instance.verify, - ) - # Create the low-level API - - image_api = utils.get_client_class( - API_NAME, - instance._api_version[API_NAME], - IMAGE_API_VERSIONS) - LOG.debug('Instantiating image api: %s', image_api) - - client.api = image_api( - session=instance.session, - endpoint=instance.get_endpoint_for_service_type( - IMAGE_API_TYPE, + if instance._api_version[API_NAME] != '1': + LOG.debug( + 'Image client initialized using OpenStack SDK: %s', + instance.sdk_connection.image, + ) + return instance.sdk_connection.image + else: + """Returns an image service client""" + image_client = utils.get_client_class( + API_NAME, + instance._api_version[API_NAME], + API_VERSIONS) + LOG.debug('Instantiating image client: %s', image_client) + + endpoint = instance.get_endpoint_for_service_type( + API_NAME, region_name=instance.region_name, interface=instance.interface, ) - ) - return client + client = image_client( + endpoint, + token=instance.auth.get_token(instance.session), + cacert=instance.cacert, + insecure=not instance.verify, + ) + + # Create the low-level API + + image_api = utils.get_client_class( + API_NAME, + instance._api_version[API_NAME], + IMAGE_API_VERSIONS) + LOG.debug('Instantiating image api: %s', image_api) + + client.api = image_api( + session=instance.session, + endpoint=instance.get_endpoint_for_service_type( + IMAGE_API_TYPE, + region_name=instance.region_name, + interface=instance.interface, + ) + ) + + return client def build_option_parser(parser): diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index feeb256744..412a16cc1c 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -18,8 +18,9 @@ import argparse from base64 import b64encode import logging +import os +import sys -from glanceclient.common import utils as gc_utils from openstack.image import image_signer from osc_lib.api import utils as api_utils from osc_lib.cli import format_columns @@ -27,11 +28,16 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six +from openstackclient.common import sdk_utils from openstackclient.i18n import _ from openstackclient.identity import common +if os.name == "nt": + import msvcrt +else: + msvcrt = None + CONTAINER_CHOICES = ["ami", "ari", "aki", "bare", "docker", "ova", "ovf"] DEFAULT_CONTAINER_FORMAT = 'bare' @@ -44,7 +50,7 @@ LOG = logging.getLogger(__name__) -def _format_image(image): +def _format_image(image, human_readable=False): """Format an image to make it more consistent with OSC operations.""" info = {} @@ -56,15 +62,25 @@ def _format_image(image): 'min_disk', 'protected', 'id', 'file', 'checksum', 'owner', 'virtual_size', 'min_ram', 'schema'] + # TODO(gtema/anybody): actually it should be possible to drop this method, + # since SDK already delivers a proper object + image = image.to_dict(ignore_none=True, original_names=True) + # split out the usual key and the properties which are top-level - for key in six.iterkeys(image): + for key in image: if key in fields_to_show: info[key] = image.get(key) elif key == 'tags': continue # handle this later - else: + elif key == 'properties': + # NOTE(gtema): flatten content of properties + properties.update(image.get(key)) + elif key != 'location': properties[key] = image.get(key) + if human_readable: + info['size'] = utils.format_size(image['size']) + # format the tags if they are there info['tags'] = format_columns.ListColumn(image.get('tags')) @@ -75,6 +91,51 @@ def _format_image(image): return info +_formatters = { + 'tags': format_columns.ListColumn, +} + + +def _get_member_columns(item): + # Trick sdk_utils to return URI attribute + column_map = { + 'image_id': 'image_id' + } + hidden_columns = ['id', 'location', 'name'] + return sdk_utils.get_osc_show_columns_for_sdk_resource( + item.to_dict(), column_map, hidden_columns) + + +def get_data_file(args): + if args.file: + return (open(args.file, 'rb'), args.file) + else: + # distinguish cases where: + # (1) stdin is not valid (as in cron jobs): + # openstack ... <&- + # (2) image data is provided through stdin: + # openstack ... < /tmp/file + # (3) no image data provided + # openstack ... + try: + os.fstat(0) + except OSError: + # (1) stdin is not valid + return (None, None) + if not sys.stdin.isatty(): + # (2) image data is provided through stdin + image = sys.stdin + if hasattr(sys.stdin, 'buffer'): + image = sys.stdin.buffer + if msvcrt: + msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY) + + return (image, None) + else: + # (3) + return (None, None) + + class AddProjectToImage(command.ShowOne): _description = _("Associate project with image") @@ -101,16 +162,18 @@ def take_action(self, parsed_args): parsed_args.project, parsed_args.project_domain).id - image_id = utils.find_resource( - image_client.images, - parsed_args.image).id + image = image_client.find_image(parsed_args.image, + ignore_missing=False) - image_member = image_client.image_members.create( - image_id, - project_id, + obj = image_client.add_member( + image=image.id, + member_id=project_id, ) - return zip(*sorted(image_member.items())) + display_columns, columns = _get_member_columns(obj) + data = utils.get_item_properties(obj, columns, formatters={}) + + return (display_columns, data) class CreateImage(command.ShowOne): @@ -302,9 +365,9 @@ def take_action(self, parsed_args): # to do nothing when no options are present as opposed to always # setting a default. if parsed_args.protected: - kwargs['protected'] = True + kwargs['is_protected'] = True if parsed_args.unprotected: - kwargs['protected'] = False + kwargs['is_protected'] = False if parsed_args.public: kwargs['visibility'] = 'public' if parsed_args.private: @@ -314,24 +377,30 @@ def take_action(self, parsed_args): if parsed_args.shared: kwargs['visibility'] = 'shared' if parsed_args.project: - kwargs['owner'] = common.find_project( + kwargs['owner_id'] = common.find_project( identity_client, parsed_args.project, parsed_args.project_domain, ).id # open the file first to ensure any failures are handled before the - # image is created - fp = gc_utils.get_data_file(parsed_args) + # image is created. Get the file name (if it is file, and not stdin) + # for easier further handling. + (fp, fname) = get_data_file(parsed_args) info = {} + if fp is not None and parsed_args.volume: raise exceptions.CommandError(_("Uploading data and using " "container are not allowed at " "the same time")) - if fp is None and parsed_args.file: LOG.warning(_("Failed to get an image file.")) return {}, {} + elif fname: + kwargs['filename'] = fname + elif fp: + kwargs['validate_checksum'] = False + kwargs['data'] = fp # sign an image using a given local private key file if parsed_args.sign_key_path or parsed_args.sign_cert_id: @@ -361,8 +430,8 @@ def take_action(self, parsed_args): sign_key_path, password=pw) except Exception: - msg = (_("Error during sign operation: private key could " - "not be loaded.")) + msg = (_("Error during sign operation: private key " + "could not be loaded.")) raise exceptions.CommandError(msg) signature = signer.generate_signature(fp) @@ -371,7 +440,8 @@ def take_action(self, parsed_args): kwargs['img_signature_certificate_uuid'] = sign_cert_id kwargs['img_signature_hash_method'] = signer.hash_method if signer.padding_method: - kwargs['img_signature_key_type'] = signer.padding_method + kwargs['img_signature_key_type'] = \ + signer.padding_method # If a volume is specified. if parsed_args.volume: @@ -393,26 +463,7 @@ def take_action(self, parsed_args): except TypeError: info['volume_type'] = None else: - image = image_client.images.create(**kwargs) - - if fp is not None: - with fp: - try: - image_client.images.upload(image.id, fp) - except Exception: - # If the upload fails for some reason attempt to remove the - # dangling queued image made by the create() call above but - # only if the user did not specify an id which indicates - # the Image already exists and should be left alone. - try: - if 'id' not in kwargs: - image_client.images.delete(image.id) - except Exception: - pass # we don't care about this one - raise # now, throw the upload exception again - - # update the image after the data has been uploaded - image = image_client.images.get(image.id) + image = image_client.create_image(**kwargs) if not info: info = _format_image(image) @@ -439,11 +490,9 @@ def take_action(self, parsed_args): image_client = self.app.client_manager.image for image in parsed_args.images: try: - image_obj = utils.find_resource( - image_client.images, - image, - ) - image_client.images.delete(image_obj.id) + image_obj = image_client.find_image(image, + ignore_missing=False) + image_client.delete_image(image_obj.id) except Exception as e: del_result += 1 LOG.error(_("Failed to delete image with name or " @@ -569,18 +618,17 @@ def take_action(self, parsed_args): kwargs = {} if parsed_args.public: - kwargs['public'] = True + kwargs['visibility'] = 'public' if parsed_args.private: - kwargs['private'] = True + kwargs['visibility'] = 'private' if parsed_args.community: - kwargs['community'] = True + kwargs['visibility'] = 'community' if parsed_args.shared: - kwargs['shared'] = True + kwargs['visibility'] = 'shared' if parsed_args.limit: kwargs['limit'] = parsed_args.limit if parsed_args.marker: - kwargs['marker'] = utils.find_resource(image_client.images, - parsed_args.marker).id + kwargs['marker'] = image_client.find_image(parsed_args.marker).id if parsed_args.name: kwargs['name'] = parsed_args.name if parsed_args.status: @@ -599,8 +647,8 @@ def take_action(self, parsed_args): 'Checksum', 'Status', 'visibility', - 'protected', - 'owner', + 'is_protected', + 'owner_id', 'tags', ) column_headers = ( @@ -621,24 +669,10 @@ def take_action(self, parsed_args): column_headers = columns # List of image data received - data = [] - limit = None if 'limit' in kwargs: - limit = kwargs['limit'] - if 'marker' in kwargs: - data = image_client.api.image_list(**kwargs) - else: - # No pages received yet, so start the page marker at None. - marker = None - while True: - page = image_client.api.image_list(marker=marker, **kwargs) - if not page: - break - data.extend(page) - # Set the marker to the id of the last item we received - marker = page[-1]['id'] - if limit: - break + # Disable automatic pagination in SDK + kwargs['paginated'] = False + data = list(image_client.images(**kwargs)) if parsed_args.property: for attr, value in parsed_args.property.items(): @@ -653,12 +687,10 @@ def take_action(self, parsed_args): return ( column_headers, - (utils.get_dict_properties( + (utils.get_item_properties( s, columns, - formatters={ - 'tags': format_columns.ListColumn, - }, + formatters=_formatters, ) for s in data) ) @@ -684,11 +716,9 @@ def take_action(self, parsed_args): "Status" ) - image_id = utils.find_resource( - image_client.images, - parsed_args.image).id + image_id = image_client.find_image(parsed_args.image).id - data = image_client.image_members.list(image_id) + data = image_client.members(image=image_id) return (columns, (utils.get_item_properties( @@ -722,11 +752,12 @@ def take_action(self, parsed_args): parsed_args.project, parsed_args.project_domain).id - image_id = utils.find_resource( - image_client.images, - parsed_args.image).id + image = image_client.find_image(parsed_args.image, + ignore_missing=False) - image_client.image_members.delete(image_id, project_id) + image_client.remove_member( + member=project_id, + image=image.id) class SaveImage(command.Command): @@ -748,19 +779,9 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): image_client = self.app.client_manager.image - image = utils.find_resource( - image_client.images, - parsed_args.image, - ) - data = image_client.images.data(image.id) + image = image_client.find_image(parsed_args.image) - if data.wrapped is None: - msg = _('Image %s has no data.') % image.id - LOG.error(msg) - self.app.stdout.write(msg + '\n') - raise SystemExit - - gc_utils.save_image(data, parsed_args.file) + image_client.download_image(image.id, output=parsed_args.file) class SetImage(command.Command): @@ -979,9 +1000,9 @@ def take_action(self, parsed_args): # to do nothing when no options are present as opposed to always # setting a default. if parsed_args.protected: - kwargs['protected'] = True + kwargs['is_protected'] = True if parsed_args.unprotected: - kwargs['protected'] = False + kwargs['is_protected'] = False if parsed_args.public: kwargs['visibility'] = 'public' if parsed_args.private: @@ -997,17 +1018,20 @@ def take_action(self, parsed_args): parsed_args.project, parsed_args.project_domain, ).id - kwargs['owner'] = project_id + kwargs['owner_id'] = project_id + + image = image_client.find_image(parsed_args.image, + ignore_missing=False) - image = utils.find_resource( - image_client.images, parsed_args.image) + # image = utils.find_resource( + # image_client.images, parsed_args.image) activation_status = None if parsed_args.deactivate: - image_client.images.deactivate(image.id) + image_client.deactivate_image(image.id) activation_status = "deactivated" if parsed_args.activate: - image_client.images.reactivate(image.id) + image_client.reactivate_image(image.id) activation_status = "activated" membership_group_args = ('accept', 'reject', 'pending') @@ -1022,15 +1046,15 @@ def take_action(self, parsed_args): # most one item in the membership_status list. if membership_status[0] != 'pending': membership_status[0] += 'ed' # Glance expects the past form - image_client.image_members.update( - image.id, project_id, membership_status[0]) + image_client.update_member( + image=image.id, member=project_id, status=membership_status[0]) if parsed_args.tags: # Tags should be extended, but duplicates removed kwargs['tags'] = list(set(image.tags).union(set(parsed_args.tags))) try: - image = image_client.images.update(image.id, **kwargs) + image = image_client.update_image(image.id, **kwargs) except Exception: if activation_status is not None: LOG.info(_("Image %(id)s was %(status)s."), @@ -1058,14 +1082,11 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): image_client = self.app.client_manager.image - image = utils.find_resource( - image_client.images, - parsed_args.image, - ) - if parsed_args.human_readable: - image['size'] = utils.format_size(image['size']) - info = _format_image(image) + image = image_client.find_image(parsed_args.image, + ignore_missing=False) + + info = _format_image(image, parsed_args.human_readable) return zip(*sorted(info.items())) @@ -1101,10 +1122,8 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): image_client = self.app.client_manager.image - image = utils.find_resource( - image_client.images, - parsed_args.image, - ) + image = image_client.find_image(parsed_args.image, + ignore_missing=False) kwargs = {} tagret = 0 @@ -1112,7 +1131,7 @@ def take_action(self, parsed_args): if parsed_args.tags: for k in parsed_args.tags: try: - image_client.image_tags.delete(image.id, k) + image_client.remove_tag(image.id, k) except Exception: LOG.error(_("tag unset failed, '%s' is a " "nonexistent tag "), k) @@ -1120,13 +1139,26 @@ def take_action(self, parsed_args): if parsed_args.properties: for k in parsed_args.properties: - if k not in image: + if k in image: + kwargs[k] = None + elif k in image.properties: + # Since image is an "evil" object from SDK POV we need to + # pass modified properties object, so that SDK can figure + # out, what was changed inside + # NOTE: ping gtema to improve that in SDK + new_props = kwargs.get('properties', + image.get('properties').copy()) + new_props.pop(k, None) + kwargs['properties'] = new_props + else: LOG.error(_("property unset failed, '%s' is a " "nonexistent property "), k) propret += 1 - image_client.images.update( - image.id, - parsed_args.properties, + + # We must give to update a current image for the reference on what + # has changed + image_client.update_image( + image, **kwargs) tagtotal = len(parsed_args.tags) diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 27eefd8586..8ec8217dfe 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -55,6 +55,12 @@ def setUp(self): self.images_mock = self.app.client_manager.image.images self.images_mock.reset_mock() + self.find_image_mock = self.app.client_manager.image.find_image + self.find_image_mock.reset_mock() + + self.get_image_mock = self.app.client_manager.image.get_image + self.get_image_mock.reset_mock() + # Get a shortcut to the volume client VolumeManager Mock self.volumes_mock = self.app.client_manager.volume.volumes self.volumes_mock.reset_mock() @@ -770,7 +776,8 @@ def setUp(self): self.servers_mock.create.return_value = self.new_server self.image = image_fakes.FakeImage.create_one_image() - self.images_mock.get.return_value = self.image + self.find_image_mock.return_value = self.image + self.get_image_mock.return_value = self.image self.flavor = compute_fakes.FakeFlavor.create_one_flavor() self.flavors_mock.get.return_value = self.flavor @@ -1916,19 +1923,13 @@ def test_server_create_image_property(self): ('config_drive', False), ('server_name', self.new_server.name), ] - _image = image_fakes.FakeImage.create_one_image() # create a image_info as the side_effect of the fake image_list() image_info = { - 'id': _image.id, - 'name': _image.name, - 'owner': _image.owner, 'hypervisor_type': 'qemu', } - self.api_mock = mock.Mock() - self.api_mock.image_list.side_effect = [ - [image_info], [], - ] - self.app.client_manager.image.api = self.api_mock + + _image = image_fakes.FakeImage.create_one_image(image_info) + self.images_mock.return_value = [_image] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1953,7 +1954,7 @@ def test_server_create_image_property(self): # ServerManager.create(name, image, flavor, **kwargs) self.servers_mock.create.assert_called_with( self.new_server.name, - image_info, + _image, self.flavor, **kwargs ) @@ -1977,20 +1978,13 @@ def test_server_create_image_property_multi(self): ('config_drive', False), ('server_name', self.new_server.name), ] - _image = image_fakes.FakeImage.create_one_image() # create a image_info as the side_effect of the fake image_list() image_info = { - 'id': _image.id, - 'name': _image.name, - 'owner': _image.owner, 'hypervisor_type': 'qemu', 'hw_disk_bus': 'ide', } - self.api_mock = mock.Mock() - self.api_mock.image_list.side_effect = [ - [image_info], [], - ] - self.app.client_manager.image.api = self.api_mock + _image = image_fakes.FakeImage.create_one_image(image_info) + self.images_mock.return_value = [_image] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -2015,7 +2009,7 @@ def test_server_create_image_property_multi(self): # ServerManager.create(name, image, flavor, **kwargs) self.servers_mock.create.assert_called_with( self.new_server.name, - image_info, + _image, self.flavor, **kwargs ) @@ -2039,20 +2033,14 @@ def test_server_create_image_property_missed(self): ('config_drive', False), ('server_name', self.new_server.name), ] - _image = image_fakes.FakeImage.create_one_image() # create a image_info as the side_effect of the fake image_list() image_info = { - 'id': _image.id, - 'name': _image.name, - 'owner': _image.owner, 'hypervisor_type': 'qemu', 'hw_disk_bus': 'ide', } - self.api_mock = mock.Mock() - self.api_mock.image_list.side_effect = [ - [image_info], [], - ] - self.app.client_manager.image.api = self.api_mock + + _image = image_fakes.FakeImage.create_one_image(image_info) + self.images_mock.return_value = [_image] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -2585,7 +2573,10 @@ def setUp(self): self.servers_mock.list.return_value = self.servers self.image = image_fakes.FakeImage.create_one_image() - self.images_mock.get.return_value = self.image + + # self.images_mock.return_value = [self.image] + self.find_image_mock.return_value = self.image + self.get_image_mock.return_value = self.image self.flavor = compute_fakes.FakeFlavor.create_one_flavor() self.flavors_mock.get.return_value = self.flavor @@ -2599,7 +2590,7 @@ def setUp(self): self.data_no_name_lookup = [] Image = collections.namedtuple('Image', 'id name') - self.images_mock.list.return_value = [ + self.images_mock.return_value = [ Image(id=s.image['id'], name=self.image.name) # Image will be an empty string if boot-from-volume for s in self.servers if s.image @@ -2662,11 +2653,11 @@ def test_server_list_no_option(self): columns, data = self.cmd.take_action(parsed_args) self.servers_mock.list.assert_called_with(**self.kwargs) - self.images_mock.list.assert_called() + self.images_mock.assert_called() self.flavors_mock.list.assert_called() # we did not pass image or flavor, so gets on those must be absent self.assertFalse(self.flavors_mock.get.call_count) - self.assertFalse(self.images_mock.get.call_count) + self.assertFalse(self.get_image_mock.call_count) self.assertEqual(self.columns, columns) self.assertEqual(tuple(self.data), tuple(data)) @@ -2753,7 +2744,7 @@ def test_server_list_name_lookup_one_by_one(self): self.servers_mock.list.assert_called_with(**self.kwargs) self.assertFalse(self.images_mock.list.call_count) self.assertFalse(self.flavors_mock.list.call_count) - self.images_mock.get.assert_called() + self.get_image_mock.assert_called() self.flavors_mock.get.assert_called() self.assertEqual(self.columns, columns) @@ -2771,7 +2762,8 @@ def test_server_list_with_image(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.images_mock.get.assert_any_call(self.image.id) + self.find_image_mock.assert_called_with(self.image.id, + ignore_missing=False) self.search_opts['image'] = self.image.id self.servers_mock.list.assert_called_with(**self.kwargs) @@ -3558,7 +3550,7 @@ def setUp(self): # Return value for utils.find_resource for image self.image = image_fakes.FakeImage.create_one_image() - self.images_mock.get.return_value = self.image + self.get_image_mock.return_value = self.image # Fake the rebuilt new server. attrs = { @@ -3598,7 +3590,7 @@ def test_rebuild_with_current_image(self): self.cmd.take_action(parsed_args) self.servers_mock.get.assert_called_with(self.server.id) - self.images_mock.get.assert_called_with(self.image.id) + self.get_image_mock.assert_called_with(self.image.id) self.server.rebuild.assert_called_with(self.image, None) def test_rebuild_with_current_image_and_password(self): @@ -3617,7 +3609,7 @@ def test_rebuild_with_current_image_and_password(self): self.cmd.take_action(parsed_args) self.servers_mock.get.assert_called_with(self.server.id) - self.images_mock.get.assert_called_with(self.image.id) + self.get_image_mock.assert_called_with(self.image.id) self.server.rebuild.assert_called_with(self.image, password) def test_rebuild_with_description_api_older(self): @@ -3665,7 +3657,7 @@ def test_rebuild_with_description_api_newer(self): self.cmd.take_action(parsed_args) self.servers_mock.get.assert_called_with(self.server.id) - self.images_mock.get.assert_called_with(self.image.id) + self.get_image_mock.assert_called_with(self.image.id) self.server.rebuild.assert_called_with(self.image, None, description=description) @@ -3694,7 +3686,7 @@ def test_rebuild_with_wait_ok(self, mock_wait_for_status): ) self.servers_mock.get.assert_called_with(self.server.id) - self.images_mock.get.assert_called_with(self.image.id) + self.get_image_mock.assert_called_with(self.image.id) self.server.rebuild.assert_called_with(self.image, None) @mock.patch.object(common_utils, 'wait_for_status', return_value=False) @@ -3718,7 +3710,7 @@ def test_rebuild_with_wait_fails(self, mock_wait_for_status): ) self.servers_mock.get.assert_called_with(self.server.id) - self.images_mock.get.assert_called_with(self.image.id) + self.get_image_mock.assert_called_with(self.image.id) self.server.rebuild.assert_called_with(self.image, None) def test_rebuild_with_property(self): @@ -3738,7 +3730,7 @@ def test_rebuild_with_property(self): self.cmd.take_action(parsed_args) self.servers_mock.get.assert_called_with(self.server.id) - self.images_mock.get.assert_called_with(self.image.id) + self.get_image_mock.assert_called_with(self.image.id) self.server.rebuild.assert_called_with( self.image, None, meta=expected_property) @@ -3767,7 +3759,7 @@ def test_rebuild_with_keypair_name(self): key_name=self.server.key_name, ) self.servers_mock.get.assert_called_with(self.server.id) - self.images_mock.get.assert_called_with(self.image.id) + self.get_image_mock.assert_called_with(self.image.id) self.server.rebuild.assert_called_with(*args, **kwargs) def test_rebuild_with_keypair_name_older_version(self): @@ -3814,7 +3806,7 @@ def test_rebuild_with_keypair_unset(self): key_name=None, ) self.servers_mock.get.assert_called_with(self.server.id) - self.images_mock.get.assert_called_with(self.image.id) + self.get_image_mock.assert_called_with(self.image.id) self.server.rebuild.assert_called_with(*args, **kwargs) def test_rebuild_with_key_name_and_unset(self): @@ -3872,7 +3864,7 @@ def setUp(self): # Return value for utils.find_resource for image self.image = image_fakes.FakeImage.create_one_image() - self.images_mock.get.return_value = self.image + self.get_image_mock.return_value = self.image new_server = compute_fakes.FakeServer.create_one_server() attrs = { @@ -3913,7 +3905,7 @@ def test_rescue_with_current_image(self): def test_rescue_with_new_image(self): new_image = image_fakes.FakeImage.create_one_image() - self.images_mock.get.return_value = new_image + self.find_image_mock.return_value = new_image arglist = [ '--image', new_image.id, self.server.id, @@ -3928,7 +3920,7 @@ def test_rescue_with_new_image(self): self.cmd.take_action(parsed_args) self.servers_mock.get.assert_called_with(self.server.id) - self.images_mock.get.assert_called_with(new_image.id) + self.find_image_mock.assert_called_with(new_image.id) self.server.rescue.assert_called_with(image=new_image, password=None) def test_rescue_with_current_image_and_password(self): @@ -4679,7 +4671,7 @@ def setUp(self): # This is the return value for utils.find_resource() self.servers_mock.get.return_value = self.server - self.images_mock.get.return_value = self.image + self.get_image_mock.return_value = self.image self.flavors_mock.get.return_value = self.flavor # Get the command object to test @@ -5140,7 +5132,8 @@ def test_prep_server_detail(self, find_resource): 'links': u'http://xxx.yyy.com', } _server = compute_fakes.FakeServer.create_one_server(attrs=server_info) - find_resource.side_effect = [_server, _image, _flavor] + find_resource.side_effect = [_server, _flavor] + self.get_image_mock.return_value = _image # Prepare result data. info = { diff --git a/openstackclient/tests/unit/compute/v2/test_server_backup.py b/openstackclient/tests/unit/compute/v2/test_server_backup.py index 7dd459d823..5cdc20800a 100644 --- a/openstackclient/tests/unit/compute/v2/test_server_backup.py +++ b/openstackclient/tests/unit/compute/v2/test_server_backup.py @@ -32,8 +32,8 @@ def setUp(self): self.servers_mock.reset_mock() # Get a shortcut to the image client ImageManager Mock - self.images_mock = self.app.client_manager.image.images - self.images_mock.reset_mock() + self.images_mock = self.app.client_manager.image + self.images_mock.find_image.reset_mock() # Set object attributes to be tested. Could be overwritten in subclass. self.attrs = {} @@ -60,15 +60,18 @@ class TestServerBackupCreate(TestServerBackup): # Just return whatever Image is testing with these days def image_columns(self, image): - columnlist = tuple(sorted(image.keys())) + # columnlist = tuple(sorted(image.keys())) + columnlist = ( + 'id', 'name', 'owner', 'protected', 'status', 'tags', 'visibility' + ) return columnlist def image_data(self, image): datalist = ( image['id'], image['name'], - image['owner'], - image['protected'], + image['owner_id'], + image['is_protected'], 'active', format_columns.ListColumn(image.get('tags')), image['visibility'], @@ -102,7 +105,8 @@ def setup_images_mock(self, count, servers=None): count=count, ) - self.images_mock.get = mock.Mock(side_effect=images) + # self.images_mock.get = mock.Mock(side_effect=images) + self.images_mock.find_image = mock.Mock(side_effect=images) return images def test_server_backup_defaults(self): @@ -174,16 +178,18 @@ def test_server_backup_create_options(self): @mock.patch.object(common_utils, 'wait_for_status', return_value=False) def test_server_backup_wait_fail(self, mock_wait_for_status): servers = self.setup_servers_mock(count=1) - images = image_fakes.FakeImage.create_images( - attrs={ - 'name': servers[0].name, - 'status': 'active', - }, - count=5, - ) - - self.images_mock.get = mock.Mock( - side_effect=images, + images = self.setup_images_mock(count=1, servers=servers) +# images = image_fakes.FakeImage.create_images( +# attrs={ +# 'name': servers[0].name, +# 'status': 'active', +# }, +# count=1, +# ) +# +# self.images_mock.find_image.return_value = images[0] + self.images_mock.get_image = mock.Mock( + side_effect=images[0], ) arglist = [ @@ -215,7 +221,7 @@ def test_server_backup_wait_fail(self, mock_wait_for_status): ) mock_wait_for_status.assert_called_once_with( - self.images_mock.get, + self.images_mock.get_image, images[0].id, callback=mock.ANY ) @@ -223,16 +229,10 @@ def test_server_backup_wait_fail(self, mock_wait_for_status): @mock.patch.object(common_utils, 'wait_for_status', return_value=True) def test_server_backup_wait_ok(self, mock_wait_for_status): servers = self.setup_servers_mock(count=1) - images = image_fakes.FakeImage.create_images( - attrs={ - 'name': servers[0].name, - 'status': 'active', - }, - count=5, - ) + images = self.setup_images_mock(count=1, servers=servers) - self.images_mock.get = mock.Mock( - side_effect=images, + self.images_mock.get_image = mock.Mock( + side_effect=images[0], ) arglist = [ @@ -263,7 +263,7 @@ def test_server_backup_wait_ok(self, mock_wait_for_status): ) mock_wait_for_status.assert_called_once_with( - self.images_mock.get, + self.images_mock.get_image, images[0].id, callback=mock.ANY ) diff --git a/openstackclient/tests/unit/compute/v2/test_server_image.py b/openstackclient/tests/unit/compute/v2/test_server_image.py index f9d7b10e75..1cec5b685f 100644 --- a/openstackclient/tests/unit/compute/v2/test_server_image.py +++ b/openstackclient/tests/unit/compute/v2/test_server_image.py @@ -31,8 +31,8 @@ def setUp(self): self.servers_mock.reset_mock() # Get a shortcut to the image client ImageManager Mock - self.images_mock = self.app.client_manager.image.images - self.images_mock.reset_mock() + self.images_mock = self.app.client_manager.image + self.images_mock.find_image.reset_mock() # Set object attributes to be tested. Could be overwritten in subclass. self.attrs = {} @@ -58,15 +58,18 @@ def setup_servers_mock(self, count): class TestServerImageCreate(TestServerImage): def image_columns(self, image): - columnlist = tuple(sorted(image.keys())) + # columnlist = tuple(sorted(image.keys())) + columnlist = ( + 'id', 'name', 'owner', 'protected', 'status', 'tags', 'visibility' + ) return columnlist def image_data(self, image): datalist = ( image['id'], image['name'], - image['owner'], - image['protected'], + image['owner_id'], + image['is_protected'], 'active', format_columns.ListColumn(image.get('tags')), image['visibility'], @@ -100,7 +103,7 @@ def setup_images_mock(self, count, servers=None): count=count, ) - self.images_mock.get = mock.Mock(side_effect=images) + self.images_mock.find_image = mock.Mock(side_effect=images) self.servers_mock.create_image = mock.Mock( return_value=images[0].id, ) @@ -188,7 +191,7 @@ def test_server_create_image_wait_fail(self, mock_wait_for_status): ) mock_wait_for_status.assert_called_once_with( - self.images_mock.get, + self.images_mock.get_image, images[0].id, callback=mock.ANY ) @@ -220,7 +223,7 @@ def test_server_create_image_wait_ok(self, mock_wait_for_status): ) mock_wait_for_status.assert_called_once_with( - self.images_mock.get, + self.images_mock.get_image, images[0].id, callback=mock.ANY ) diff --git a/openstackclient/tests/unit/image/v2/fakes.py b/openstackclient/tests/unit/image/v2/fakes.py index 655ae341c6..516d563001 100644 --- a/openstackclient/tests/unit/image/v2/fakes.py +++ b/openstackclient/tests/unit/image/v2/fakes.py @@ -18,9 +18,9 @@ from unittest import mock import uuid -from glanceclient.v2 import schemas +from openstack.image.v2 import image +from openstack.image.v2 import member from osc_lib.cli import format_columns -import warlock from openstackclient.tests.unit import fakes from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes @@ -154,6 +154,12 @@ def __init__(self, **kwargs): self.image_members.resource_class = fakes.FakeResource(None, {}) self.image_tags = mock.Mock() self.image_tags.resource_class = fakes.FakeResource(None, {}) + + self.find_image = mock.Mock() + self.find_image.resource_class = fakes.FakeResource(None, {}) + + self.get_image = mock.Mock() + self.get_image.resource_class = fakes.FakeResource(None, {}) self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] self.version = 2.0 @@ -197,8 +203,8 @@ def create_one_image(attrs=None): image_info = { 'id': str(uuid.uuid4()), 'name': 'image-name' + uuid.uuid4().hex, - 'owner': 'image-owner' + uuid.uuid4().hex, - 'protected': bool(random.choice([0, 1])), + 'owner_id': 'image-owner' + uuid.uuid4().hex, + 'is_protected': bool(random.choice([0, 1])), 'visibility': random.choice(['public', 'private']), 'tags': [uuid.uuid4().hex for r in range(2)], } @@ -206,13 +212,7 @@ def create_one_image(attrs=None): # Overwrite default attributes if there are some attributes set image_info.update(attrs) - # Set up the schema - model = warlock.model_factory( - IMAGE_schema, - schemas.SchemaBasedModel, - ) - - return model(**image_info) + return image.Image(**image_info) @staticmethod def create_images(attrs=None, count=2): @@ -307,6 +307,8 @@ def create_one_image_member(attrs=None): # Overwrite default attributes if there are some attributes set image_member_info.update(attrs) + return member.Member(**image_member_info) + image_member = fakes.FakeModel( copy.deepcopy(image_member_info)) diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py index 78d857e269..a021cfc7fc 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -14,13 +14,14 @@ # import copy +import io +import os +import tempfile from unittest import mock -from glanceclient.common import utils as glanceclient_utils -from glanceclient.v2 import schemas +from openstack import exceptions as sdk_exceptions from osc_lib.cli import format_columns from osc_lib import exceptions -import warlock from openstackclient.image.v2 import image from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes @@ -33,12 +34,16 @@ def setUp(self): super(TestImage, self).setUp() # Get shortcuts to the Mocks in image client - self.images_mock = self.app.client_manager.image.images - self.images_mock.reset_mock() + # SDK proxy mock + self.app.client_manager.image = mock.Mock() + self.client = self.app.client_manager.image + + self.client.remove_member = mock.Mock() + + self.client.create_image = mock.Mock() + self.client.update_image = mock.Mock() self.image_members_mock = self.app.client_manager.image.image_members - self.image_members_mock.reset_mock() self.image_tags_mock = self.app.client_manager.image.image_tags - self.image_tags_mock.reset_mock() # Get shortcut to the Mocks in identity client self.project_mock = self.app.client_manager.identity.projects @@ -49,9 +54,6 @@ def setUp(self): def setup_images_mock(self, count): images = image_fakes.FakeImage.create_images(count=count) - self.images_mock.get = image_fakes.FakeImage.get_images( - images, - 0) return images @@ -64,26 +66,22 @@ def setUp(self): super(TestImageCreate, self).setUp() self.new_image = image_fakes.FakeImage.create_one_image() - self.images_mock.create.return_value = self.new_image + self.client.create_image.return_value = self.new_image self.project_mock.get.return_value = self.project self.domain_mock.get.return_value = self.domain - # This is the return value for utils.find_resource() - self.images_mock.get.return_value = copy.deepcopy( - self.new_image - ) - self.images_mock.update.return_value = self.new_image + self.client.update_image.return_value = self.new_image + + (self.expected_columns, self.expected_data) = zip( + *sorted(image._format_image(self.new_image).items())) # Get the command object to test self.cmd = image.CreateImage(self.app, None) - def test_image_reserve_no_options(self): - mock_exception = { - 'find.side_effect': exceptions.CommandError('x'), - } - self.images_mock.configure_mock(**mock_exception) + @mock.patch("sys.stdin", side_effect=[None]) + def test_image_reserve_no_options(self, raw_input): arglist = [ self.new_image.name ] @@ -100,45 +98,34 @@ def test_image_reserve_no_options(self): columns, data = self.cmd.take_action(parsed_args) # ImageManager.create(name=, **) - self.images_mock.create.assert_called_with( + self.client.create_image.assert_called_with( name=self.new_image.name, container_format=image.DEFAULT_CONTAINER_FORMAT, disk_format=image.DEFAULT_DISK_FORMAT, ) # Verify update() was not called, if it was show the args - self.assertEqual(self.images_mock.update.call_args_list, []) - - self.images_mock.upload.assert_called_with( - mock.ANY, mock.ANY, - ) + self.assertEqual(self.client.update_image.call_args_list, []) self.assertEqual( - image_fakes.FakeImage.get_image_columns(self.new_image), + self.expected_columns, columns) self.assertItemEqual( - image_fakes.FakeImage.get_image_data(self.new_image), + self.expected_data, data) - @mock.patch('glanceclient.common.utils.get_data_file', name='Open') - def test_image_reserve_options(self, mock_open): - mock_file = mock.MagicMock(name='File') - mock_open.return_value = mock_file - mock_open.read.return_value = None - mock_exception = { - 'find.side_effect': exceptions.CommandError('x'), - } - self.images_mock.configure_mock(**mock_exception) + @mock.patch('sys.stdin', side_effect=[None]) + def test_image_reserve_options(self, raw_input): arglist = [ '--container-format', 'ovf', '--disk-format', 'ami', '--min-disk', '10', '--min-ram', '4', ('--protected' - if self.new_image.protected else '--unprotected'), + if self.new_image.is_protected else '--unprotected'), ('--private' if self.new_image.visibility == 'private' else '--public'), - '--project', self.new_image.owner, + '--project', self.new_image.owner_id, '--project-domain', self.domain.id, self.new_image.name, ] @@ -147,11 +134,11 @@ def test_image_reserve_options(self, mock_open): ('disk_format', 'ami'), ('min_disk', 10), ('min_ram', 4), - ('protected', self.new_image.protected), - ('unprotected', not self.new_image.protected), + ('protected', self.new_image.is_protected), + ('unprotected', not self.new_image.is_protected), ('public', self.new_image.visibility == 'public'), ('private', self.new_image.visibility == 'private'), - ('project', self.new_image.owner), + ('project', self.new_image.owner_id), ('project_domain', self.domain.id), ('name', self.new_image.name), ] @@ -163,29 +150,22 @@ def test_image_reserve_options(self, mock_open): columns, data = self.cmd.take_action(parsed_args) # ImageManager.create(name=, **) - self.images_mock.create.assert_called_with( + self.client.create_image.assert_called_with( name=self.new_image.name, container_format='ovf', disk_format='ami', min_disk=10, min_ram=4, - owner=self.project.id, - protected=self.new_image.protected, + owner_id=self.project.id, + is_protected=self.new_image.is_protected, visibility=self.new_image.visibility, ) - # Verify update() was not called, if it was show the args - self.assertEqual(self.images_mock.update.call_args_list, []) - - self.images_mock.upload.assert_called_with( - mock.ANY, mock.ANY, - ) - self.assertEqual( - image_fakes.FakeImage.get_image_columns(self.new_image), + self.expected_columns, columns) self.assertItemEqual( - image_fakes.FakeImage.get_image_data(self.new_image), + self.expected_data, data) def test_image_create_with_unexist_project(self): @@ -222,21 +202,15 @@ def test_image_create_with_unexist_project(self): parsed_args, ) - @mock.patch('glanceclient.common.utils.get_data_file', name='Open') - def test_image_create_file(self, mock_open): - mock_file = mock.MagicMock(name='File') - mock_open.return_value = mock_file - mock_open.read.return_value = ( - image_fakes.FakeImage.get_image_data(self.new_image)) - mock_exception = { - 'find.side_effect': exceptions.CommandError('x'), - } - self.images_mock.configure_mock(**mock_exception) + def test_image_create_file(self): + imagefile = tempfile.NamedTemporaryFile(delete=False) + imagefile.write(b'\0') + imagefile.close() arglist = [ - '--file', 'filer', + '--file', imagefile.name, ('--unprotected' - if not self.new_image.protected else '--protected'), + if not self.new_image.is_protected else '--protected'), ('--public' if self.new_image.visibility == 'public' else '--private'), '--property', 'Alpha=1', @@ -246,9 +220,9 @@ def test_image_create_file(self, mock_open): self.new_image.name, ] verifylist = [ - ('file', 'filer'), - ('protected', self.new_image.protected), - ('unprotected', not self.new_image.protected), + ('file', imagefile.name), + ('protected', self.new_image.is_protected), + ('unprotected', not self.new_image.is_protected), ('public', self.new_image.visibility == 'public'), ('private', self.new_image.visibility == 'private'), ('properties', {'Alpha': '1', 'Beta': '2'}), @@ -263,29 +237,23 @@ def test_image_create_file(self, mock_open): columns, data = self.cmd.take_action(parsed_args) # ImageManager.create(name=, **) - self.images_mock.create.assert_called_with( + self.client.create_image.assert_called_with( name=self.new_image.name, container_format=image.DEFAULT_CONTAINER_FORMAT, disk_format=image.DEFAULT_DISK_FORMAT, - protected=self.new_image.protected, + is_protected=self.new_image.is_protected, visibility=self.new_image.visibility, Alpha='1', Beta='2', tags=self.new_image.tags, - ) - - # Verify update() was not called, if it was show the args - self.assertEqual(self.images_mock.update.call_args_list, []) - - self.images_mock.upload.assert_called_with( - mock.ANY, mock.ANY, + filename=imagefile.name ) self.assertEqual( - image_fakes.FakeImage.get_image_columns(self.new_image), + self.expected_columns, columns) self.assertItemEqual( - image_fakes.FakeImage.get_image_data(self.new_image), + self.expected_data, data) def test_image_create_dead_options(self): @@ -315,25 +283,31 @@ class TestAddProjectToImage(TestImage): ) columns = ( + 'created_at', 'image_id', 'member_id', + 'schema', 'status', + 'updated_at' ) datalist = ( + new_member.created_at, _image.id, new_member.member_id, + new_member.schema, new_member.status, + new_member.updated_at ) def setUp(self): super(TestAddProjectToImage, self).setUp() # This is the return value for utils.find_resource() - self.images_mock.get.return_value = self._image + self.client.find_image.return_value = self._image # Update the image_id in the MEMBER dict - self.image_members_mock.create.return_value = self.new_member + self.client.add_member.return_value = self.new_member self.project_mock.get.return_value = self.project self.domain_mock.get.return_value = self.domain # Get the command object to test @@ -354,9 +328,9 @@ def test_add_project_to_image_no_option(self): # 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.image_members_mock.create.assert_called_with( - self._image.id, - self.project.id + self.client.add_member.assert_called_with( + image=self._image.id, + member_id=self.project.id ) self.assertEqual(self.columns, columns) @@ -379,10 +353,11 @@ def test_add_project_to_image_with_option(self): # 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.image_members_mock.create.assert_called_with( - self._image.id, - self.project.id + self.client.add_member.assert_called_with( + image=self._image.id, + member_id=self.project.id ) + self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, data) @@ -392,7 +367,7 @@ class TestImageDelete(TestImage): def setUp(self): super(TestImageDelete, self).setUp() - self.images_mock.delete.return_value = None + self.client.delete_image.return_value = None # Get the command object to test self.cmd = image.DeleteImage(self.app, None) @@ -408,9 +383,11 @@ def test_image_delete_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.client.find_image.side_effect = images + result = self.cmd.take_action(parsed_args) - self.images_mock.delete.assert_called_with(images[0].id) + self.client.delete_image.assert_called_with(images[0].id) self.assertIsNone(result) def test_image_delete_multi_images(self): @@ -422,10 +399,12 @@ def test_image_delete_multi_images(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.client.find_image.side_effect = images + result = self.cmd.take_action(parsed_args) calls = [mock.call(i.id) for i in images] - self.images_mock.delete.assert_has_calls(calls) + self.client.delete_image.assert_has_calls(calls) self.assertIsNone(result) def test_image_delete_multi_images_exception(self): @@ -449,15 +428,15 @@ def test_image_delete_multi_images_exception(self): ret_find = [ images[0], images[1], - exceptions.NotFound('404'), + sdk_exceptions.ResourceNotFound() ] - self.images_mock.get = Exception() - self.images_mock.find.side_effect = ret_find + self.client.find_image.side_effect = ret_find + self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) calls = [mock.call(i.id) for i in images] - self.images_mock.delete.assert_has_calls(calls) + self.client.delete_image.assert_has_calls(calls) class TestImageList(TestImage): @@ -473,17 +452,17 @@ class TestImageList(TestImage): datalist = ( _image.id, _image.name, - '', + None, ), def setUp(self): super(TestImageList, self).setUp() self.api_mock = mock.Mock() - self.api_mock.image_list.side_effect = [ + self.api_mock.side_effect = [ [self._image], [], ] - self.app.client_manager.image.api = self.api_mock + self.client.images = self.api_mock # Get the command object to test self.cmd = image.ListImage(self.app, None) @@ -503,8 +482,8 @@ def test_image_list_no_options(self): # returns a tuple containing the column names and an iterable # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.api_mock.image_list.assert_called_with( - marker=self._image.id, + self.client.images.assert_called_with( + # marker=self._image.id, ) self.assertEqual(self.columns, columns) @@ -527,9 +506,8 @@ def test_image_list_public_option(self): # returns a tuple containing the column names and an iterable # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.api_mock.image_list.assert_called_with( - public=True, - marker=self._image.id, + self.client.images.assert_called_with( + visibility='public', ) self.assertEqual(self.columns, columns) @@ -552,9 +530,8 @@ def test_image_list_private_option(self): # returns a tuple containing the column names and an iterable # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.api_mock.image_list.assert_called_with( - private=True, - marker=self._image.id, + self.client.images.assert_called_with( + visibility='private', ) self.assertEqual(self.columns, columns) @@ -577,9 +554,8 @@ def test_image_list_community_option(self): # returns a tuple containing the column names and an iterable # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.api_mock.image_list.assert_called_with( - community=True, - marker=self._image.id, + self.client.images.assert_called_with( + visibility='community', ) self.assertEqual(self.columns, columns) @@ -602,9 +578,8 @@ def test_image_list_shared_option(self): # returns a tuple containing the column names and an iterable # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.api_mock.image_list.assert_called_with( - shared=True, - marker=self._image.id, + self.client.images.assert_called_with( + visibility='shared', ) self.assertEqual(self.columns, columns) @@ -629,10 +604,9 @@ def test_image_list_shared_member_status_option(self): # returns a tuple containing the column names and an iterable # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.api_mock.image_list.assert_called_with( - shared=True, + self.client.images.assert_called_with( + visibility='shared', member_status='all', - marker=self._image.id, ) self.assertEqual(self.columns, columns) @@ -666,8 +640,7 @@ def test_image_list_long_option(self): # returns a tuple containing the column names and an iterable # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.api_mock.image_list.assert_called_with( - marker=self._image.id, + self.client.images.assert_called_with( ) collist = ( @@ -688,14 +661,14 @@ def test_image_list_long_option(self): datalist = (( self._image.id, self._image.name, - '', - '', - '', - '', - '', + None, + None, + None, + None, + None, self._image.visibility, - self._image.protected, - self._image.owner, + self._image.is_protected, + self._image.owner_id, format_columns.ListColumn(self._image.tags), ), ) self.assertListItemEqual(datalist, tuple(data)) @@ -716,8 +689,7 @@ def test_image_list_property_option(self, sf_mock): # returns a tuple containing the column names and an iterable # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.api_mock.image_list.assert_called_with( - marker=self._image.id, + self.client.images.assert_called_with( ) sf_mock.assert_called_with( [self._image], @@ -741,8 +713,7 @@ def test_image_list_sort_option(self, si_mock): # returns a tuple containing the column names and an iterable # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.api_mock.image_list.assert_called_with( - marker=self._image.id, + self.client.images.assert_called_with( ) si_mock.assert_called_with( [self._image], @@ -763,8 +734,10 @@ def test_image_list_limit_option(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.api_mock.image_list.assert_called_with( - limit=ret_limit, marker=None + self.client.images.assert_called_with( + limit=ret_limit, + paginated=False + # marker=None ) self.assertEqual(self.columns, columns) @@ -775,8 +748,9 @@ def test_image_list_marker_option(self, fr_mock): # tangchen: Since image_fakes.IMAGE is a dict, it cannot offer a .id # operation. Will fix this by using FakeImage class instead # of IMAGE dict. - fr_mock.return_value = mock.Mock() - fr_mock.return_value.id = image_fakes.image_id + self.client.find_image = mock.Mock(return_value=self._image) +# fr_mock.return_value = mock.Mock() +# fr_mock.return_value.id = image_fakes.image_id arglist = [ '--marker', image_fakes.image_name, @@ -787,10 +761,12 @@ def test_image_list_marker_option(self, fr_mock): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.api_mock.image_list.assert_called_with( - marker=image_fakes.image_id, + self.client.images.assert_called_with( + marker=self._image.id, ) + self.client.find_image.assert_called_with(image_fakes.image_name) + def test_image_list_name_option(self): arglist = [ '--name', 'abc', @@ -801,8 +777,9 @@ def test_image_list_name_option(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.api_mock.image_list.assert_called_with( - name='abc', marker=self._image.id + self.client.images.assert_called_with( + name='abc', + # marker=self._image.id ) def test_image_list_status_option(self): @@ -815,8 +792,8 @@ def test_image_list_status_option(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.api_mock.image_list.assert_called_with( - status='active', marker=self._image.id + self.client.images.assert_called_with( + status='active' ) def test_image_list_tag_option(self): @@ -829,8 +806,8 @@ def test_image_list_tag_option(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.api_mock.image_list.assert_called_with( - tag='abc', marker=self._image.id + self.client.images.assert_called_with( + tag='abc' ) @@ -849,17 +826,17 @@ class TestListImageProjects(TestImage): "Status" ) - datalist = (( + datalist = [( _image.id, member.member_id, member.status, - )) + )] def setUp(self): super(TestListImageProjects, self).setUp() - self.images_mock.get.return_value = self._image - self.image_members_mock.list.return_value = self.datalist + self.client.find_image.return_value = self._image + self.client.members.return_value = [self.member] self.cmd = image.ListImageProjects(self.app, None) @@ -874,10 +851,10 @@ def test_image_member_list(self): columns, data = self.cmd.take_action(parsed_args) - self.image_members_mock.list.assert_called_with(self._image.id) + self.client.members.assert_called_with(image=self._image.id) self.assertEqual(self.columns, columns) - self.assertEqual(len(self.datalist), len(tuple(data))) + self.assertEqual(self.datalist, list(data)) class TestRemoveProjectImage(TestImage): @@ -890,11 +867,11 @@ def setUp(self): self._image = image_fakes.FakeImage.create_one_image() # This is the return value for utils.find_resource() - self.images_mock.get.return_value = self._image + self.client.find_image.return_value = self._image self.project_mock.get.return_value = self.project self.domain_mock.get.return_value = self.domain - self.image_members_mock.delete.return_value = None + self.client.remove_member.return_value = None # Get the command object to test self.cmd = image.RemoveProjectImage(self.app, None) @@ -911,9 +888,13 @@ def test_remove_project_image_no_options(self): result = self.cmd.take_action(parsed_args) - self.image_members_mock.delete.assert_called_with( + self.client.find_image.assert_called_with( self._image.id, - self.project.id, + ignore_missing=False) + + self.client.remove_member.assert_called_with( + member=self.project.id, + image=self._image.id, ) self.assertIsNone(result) @@ -932,9 +913,9 @@ def test_remove_project_image_with_options(self): result = self.cmd.take_action(parsed_args) - self.image_members_mock.delete.assert_called_with( - self._image.id, - self.project.id, + self.client.remove_member.assert_called_with( + member=self.project.id, + image=self._image.id, ) self.assertIsNone(result) @@ -943,21 +924,16 @@ class TestImageSet(TestImage): project = identity_fakes.FakeProject.create_one_project() domain = identity_fakes.FakeDomain.create_one_domain() + _image = image_fakes.FakeImage.create_one_image({'tags': []}) def setUp(self): super(TestImageSet, self).setUp() - # Set up the schema - self.model = warlock.model_factory( - image_fakes.IMAGE_schema, - schemas.SchemaBasedModel, - ) self.project_mock.get.return_value = self.project self.domain_mock.get.return_value = self.domain - self.images_mock.get.return_value = self.model(**image_fakes.IMAGE) - self.images_mock.update.return_value = self.model(**image_fakes.IMAGE) + self.client.find_image.return_value = self._image self.app.client_manager.auth_ref = mock.Mock( project_id=self.project.id, @@ -986,38 +962,38 @@ def test_image_set_membership_option_accept(self): attrs={'image_id': image_fakes.image_id, 'member_id': self.project.id} ) - self.image_members_mock.update.return_value = membership + self.client.update_member.return_value = membership arglist = [ '--accept', - image_fakes.image_id, + self._image.id, ] verifylist = [ ('accept', True), ('reject', False), ('pending', False), - ('image', image_fakes.image_id) + ('image', self._image.id) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) - self.image_members_mock.update.assert_called_once_with( - image_fakes.image_id, - self.app.client_manager.auth_ref.project_id, - 'accepted', + self.client.update_member.assert_called_once_with( + image=self._image.id, + member=self.app.client_manager.auth_ref.project_id, + status='accepted', ) # Assert that the 'update image" route is also called, in addition to # the 'update membership' route. - self.images_mock.update.assert_called_with(image_fakes.image_id) + self.client.update_image.assert_called_with(self._image.id) def test_image_set_membership_option_reject(self): membership = image_fakes.FakeImage.create_one_image_member( attrs={'image_id': image_fakes.image_id, 'member_id': self.project.id} ) - self.image_members_mock.update.return_value = membership + self.client.update_member.return_value = membership arglist = [ '--reject', @@ -1033,22 +1009,22 @@ def test_image_set_membership_option_reject(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) - self.image_members_mock.update.assert_called_once_with( - image_fakes.image_id, - self.app.client_manager.auth_ref.project_id, - 'rejected', + self.client.update_member.assert_called_once_with( + image=self._image.id, + member=self.app.client_manager.auth_ref.project_id, + status='rejected', ) # Assert that the 'update image" route is also called, in addition to # the 'update membership' route. - self.images_mock.update.assert_called_with(image_fakes.image_id) + self.client.update_image.assert_called_with(self._image.id) def test_image_set_membership_option_pending(self): membership = image_fakes.FakeImage.create_one_image_member( attrs={'image_id': image_fakes.image_id, 'member_id': self.project.id} ) - self.image_members_mock.update.return_value = membership + self.client.update_member.return_value = membership arglist = [ '--pending', @@ -1064,15 +1040,15 @@ def test_image_set_membership_option_pending(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) - self.image_members_mock.update.assert_called_once_with( - image_fakes.image_id, - self.app.client_manager.auth_ref.project_id, - 'pending', + self.client.update_member.assert_called_once_with( + image=self._image.id, + member=self.app.client_manager.auth_ref.project_id, + status='pending', ) # Assert that the 'update image" route is also called, in addition to # the 'update membership' route. - self.images_mock.update.assert_called_with(image_fakes.image_id) + self.client.update_image.assert_called_with(self._image.id) def test_image_set_options(self): arglist = [ @@ -1083,7 +1059,7 @@ def test_image_set_options(self): '--disk-format', 'vmdk', '--project', self.project.name, '--project-domain', self.domain.id, - image_fakes.image_id, + self._image.id, ] verifylist = [ ('name', 'new-name'), @@ -1093,7 +1069,7 @@ def test_image_set_options(self): ('disk_format', 'vmdk'), ('project', self.project.name), ('project_domain', self.domain.id), - ('image', image_fakes.image_id), + ('image', self._image.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1101,15 +1077,15 @@ def test_image_set_options(self): kwargs = { 'name': 'new-name', - 'owner': self.project.id, + 'owner_id': self.project.id, 'min_disk': 2, 'min_ram': 4, 'container_format': 'ovf', 'disk_format': 'vmdk', } # ImageManager.update(image, **kwargs) - self.images_mock.update.assert_called_with( - image_fakes.image_id, **kwargs) + self.client.update_image.assert_called_with( + self._image.id, **kwargs) self.assertIsNone(result) def test_image_set_with_unexist_project(self): @@ -1148,12 +1124,12 @@ def test_image_set_bools1(self): result = self.cmd.take_action(parsed_args) kwargs = { - 'protected': True, + 'is_protected': True, 'visibility': 'private', } # ImageManager.update(image, **kwargs) - self.images_mock.update.assert_called_with( - image_fakes.image_id, + self.client.update_image.assert_called_with( + self._image.id, **kwargs ) self.assertIsNone(result) @@ -1176,12 +1152,12 @@ def test_image_set_bools2(self): result = self.cmd.take_action(parsed_args) kwargs = { - 'protected': False, + 'is_protected': False, 'visibility': 'public', } # ImageManager.update(image, **kwargs) - self.images_mock.update.assert_called_with( - image_fakes.image_id, + self.client.update_image.assert_called_with( + self._image.id, **kwargs ) self.assertIsNone(result) @@ -1205,8 +1181,8 @@ def test_image_set_properties(self): 'Beta': '2', } # ImageManager.update(image, **kwargs) - self.images_mock.update.assert_called_with( - image_fakes.image_id, + self.client.update_image.assert_called_with( + self._image.id, **kwargs ) self.assertIsNone(result) @@ -1243,8 +1219,8 @@ def test_image_set_fake_properties(self): 'ramdisk_id': 'xyzpdq', } # ImageManager.update(image, **kwargs) - self.images_mock.update.assert_called_with( - image_fakes.image_id, + self.client.update_image.assert_called_with( + self._image.id, **kwargs ) self.assertIsNone(result) @@ -1266,8 +1242,8 @@ def test_image_set_tag(self): 'tags': ['test-tag'], } # ImageManager.update(image, **kwargs) - self.images_mock.update.assert_called_with( - image_fakes.image_id, + self.client.update_image.assert_called_with( + self._image.id, **kwargs ) self.assertIsNone(result) @@ -1290,12 +1266,12 @@ def test_image_set_activate(self): 'tags': ['test-tag'], } - self.images_mock.reactivate.assert_called_with( - image_fakes.image_id, + self.client.reactivate_image.assert_called_with( + self._image.id, ) # ImageManager.update(image, **kwargs) - self.images_mock.update.assert_called_with( - image_fakes.image_id, + self.client.update_image.assert_called_with( + self._image.id, **kwargs ) self.assertIsNone(result) @@ -1318,20 +1294,20 @@ def test_image_set_deactivate(self): 'tags': ['test-tag'], } - self.images_mock.deactivate.assert_called_with( - image_fakes.image_id, + self.client.deactivate_image.assert_called_with( + self._image.id, ) # ImageManager.update(image, **kwargs) - self.images_mock.update.assert_called_with( - image_fakes.image_id, + self.client.update_image.assert_called_with( + self._image.id, **kwargs ) self.assertIsNone(result) def test_image_set_tag_merge(self): - old_image = copy.copy(image_fakes.IMAGE) + old_image = self._image old_image['tags'] = ['old1', 'new2'] - self.images_mock.get.return_value = self.model(**old_image) + self.client.find_image.return_value = old_image arglist = [ '--tag', 'test-tag', image_fakes.image_name, @@ -1348,16 +1324,16 @@ def test_image_set_tag_merge(self): 'tags': ['old1', 'new2', 'test-tag'], } # ImageManager.update(image, **kwargs) - a, k = self.images_mock.update.call_args - self.assertEqual(image_fakes.image_id, a[0]) + a, k = self.client.update_image.call_args + self.assertEqual(self._image.id, a[0]) self.assertIn('tags', k) self.assertEqual(set(kwargs['tags']), set(k['tags'])) self.assertIsNone(result) def test_image_set_tag_merge_dupe(self): - old_image = copy.copy(image_fakes.IMAGE) + old_image = self._image old_image['tags'] = ['old1', 'new2'] - self.images_mock.get.return_value = self.model(**old_image) + self.client.find_image.return_value = old_image arglist = [ '--tag', 'old1', image_fakes.image_name, @@ -1374,8 +1350,8 @@ def test_image_set_tag_merge_dupe(self): 'tags': ['new2', 'old1'], } # ImageManager.update(image, **kwargs) - a, k = self.images_mock.update.call_args - self.assertEqual(image_fakes.image_id, a[0]) + a, k = self.client.update_image.call_args + self.assertEqual(self._image.id, a[0]) self.assertIn('tags', k) self.assertEqual(set(kwargs['tags']), set(k['tags'])) self.assertIsNone(result) @@ -1416,8 +1392,8 @@ def test_image_set_numeric_options_to_zero(self): 'min_ram': 0, } # ImageManager.update(image, **kwargs) - self.images_mock.update.assert_called_with( - image_fakes.image_id, + self.client.update_image.assert_called_with( + self._image.id, **kwargs ) self.assertIsNone(result) @@ -1428,16 +1404,25 @@ class TestImageShow(TestImage): new_image = image_fakes.FakeImage.create_one_image( attrs={'size': 1000}) + _data = image_fakes.FakeImage.create_one_image() + + columns = ( + 'id', 'name', 'owner', 'protected', 'tags', 'visibility' + ) + + data = ( + _data.id, + _data.name, + _data.owner_id, + _data.is_protected, + format_columns.ListColumn(_data.tags), + _data.visibility + ) + def setUp(self): super(TestImageShow, self).setUp() - # Set up the schema - self.model = warlock.model_factory( - image_fakes.IMAGE_schema, - schemas.SchemaBasedModel, - ) - - self.images_mock.get.return_value = self.model(**image_fakes.IMAGE) + self.client.find_image = mock.Mock(return_value=self._data) # Get the command object to test self.cmd = image.ShowImage(self.app, None) @@ -1455,15 +1440,16 @@ def test_image_show(self): # 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.images_mock.get.assert_called_with( + self.client.find_image.assert_called_with( image_fakes.image_id, + ignore_missing=False ) - self.assertEqual(image_fakes.IMAGE_columns, columns) - self.assertItemEqual(image_fakes.IMAGE_SHOW_data, data) + self.assertEqual(self.columns, columns) + self.assertItemEqual(self.data, data) def test_image_show_human_readable(self): - self.images_mock.get.return_value = self.new_image + self.client.find_image.return_value = self.new_image arglist = [ '--human-readable', self.new_image.id, @@ -1478,8 +1464,9 @@ def test_image_show_human_readable(self): # 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.images_mock.get.assert_called_with( + self.client.find_image.assert_called_with( self.new_image.id, + ignore_missing=False ) size_index = columns.index('size') @@ -1491,19 +1478,15 @@ class TestImageUnset(TestImage): attrs = {} attrs['tags'] = ['test'] attrs['prop'] = 'test' + attrs['prop2'] = 'fake' image = image_fakes.FakeImage.create_one_image(attrs) def setUp(self): super(TestImageUnset, self).setUp() - # Set up the schema - self.model = warlock.model_factory( - image_fakes.IMAGE_schema, - schemas.SchemaBasedModel, - ) - - self.images_mock.get.return_value = self.image - self.image_tags_mock.delete.return_value = self.image + self.client.find_image.return_value = self.image + self.client.remove_tag.return_value = self.image + self.client.update_image.return_value = self.image # Get the command object to test self.cmd = image.UnsetImage(self.app, None) @@ -1535,7 +1518,7 @@ def test_image_unset_tag_option(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.image_tags_mock.delete.assert_called_with( + self.client.remove_tag.assert_called_with( self.image.id, 'test' ) self.assertIsNone(result) @@ -1555,9 +1538,9 @@ def test_image_unset_property_option(self): result = self.cmd.take_action(parsed_args) kwargs = {} - self.images_mock.update.assert_called_with( - self.image.id, - parsed_args.properties, + self.client.update_image.assert_called_with( + self.image, + properties={'prop2': 'fake'}, **kwargs) self.assertIsNone(result) @@ -1579,12 +1562,12 @@ def test_image_unset_mixed_option(self): result = self.cmd.take_action(parsed_args) kwargs = {} - self.images_mock.update.assert_called_with( - self.image.id, - parsed_args.properties, + self.client.update_image.assert_called_with( + self.image, + properties={'prop2': 'fake'}, **kwargs) - self.image_tags_mock.delete.assert_called_with( + self.client.remove_tag.assert_called_with( self.image.id, 'test' ) self.assertIsNone(result) @@ -1597,18 +1580,13 @@ class TestImageSave(TestImage): def setUp(self): super(TestImageSave, self).setUp() - # Generate a request id - self.resp = mock.MagicMock() - self.resp.headers['x-openstack-request-id'] = 'req_id' + self.client.find_image.return_value = self.image + self.client.download_image.return_value = self.image # Get the command object to test self.cmd = image.SaveImage(self.app, None) def test_save_data(self): - req_id_proxy = glanceclient_utils.RequestIdProxy( - ['some_data', self.resp] - ) - self.images_mock.data.return_value = req_id_proxy arglist = ['--file', '/path/to/file', self.image.id] @@ -1618,23 +1596,58 @@ def test_save_data(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - with mock.patch('glanceclient.common.utils.save_image') as mocked_save: - self.cmd.take_action(parsed_args) - mocked_save.assert_called_once_with(req_id_proxy, '/path/to/file') + self.cmd.take_action(parsed_args) - def test_save_no_data(self): - req_id_proxy = glanceclient_utils.RequestIdProxy( - [None, self.resp] - ) - self.images_mock.data.return_value = req_id_proxy + self.client.download_image.assert_called_once_with( + self.image.id, + output='/path/to/file') - arglist = ['--file', '/path/to/file', self.image.id] - verifylist = [ - ('file', '/path/to/file'), - ('image', self.image.id) - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) +class TestImageGetData(TestImage): + + def setUp(self): + super(TestImageGetData, self).setUp() + self.args = mock.Mock() + + def test_get_data_file_file(self): + (fd, fname) = tempfile.mkstemp(prefix='osc_test_image') + self.args.file = fname + + (test_fd, test_name) = image.get_data_file(self.args) + + self.assertEqual(fname, test_name) + test_fd.close() + + os.unlink(fname) + + def test_get_data_file_2(self): + + self.args.file = None + + f = io.BytesIO(b"some initial binary data: \x00\x01") + + with mock.patch('sys.stdin') as stdin: + stdin.return_value = f + stdin.isatty.return_value = False + stdin.buffer = f + + (test_fd, test_name) = image.get_data_file(self.args) + + # Ensure data written to temp file is correct + self.assertEqual(f, test_fd) + self.assertIsNone(test_name) + + def test_get_data_file_3(self): + + self.args.file = None + + f = io.BytesIO(b"some initial binary data: \x00\x01") + + with mock.patch('sys.stdin') as stdin: + # There is stdin, but interactive + stdin.return_value = f + + (test_fd, test_fname) = image.get_data_file(self.args) - # Raise SystemExit if no data was provided. - self.assertRaises(SystemExit, self.cmd.take_action, parsed_args) + self.assertIsNone(test_fd) + self.assertIsNone(test_fname) diff --git a/openstackclient/tests/unit/volume/v2/test_volume.py b/openstackclient/tests/unit/volume/v2/test_volume.py index 5d41b3a180..4e204ad1b2 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume.py +++ b/openstackclient/tests/unit/volume/v2/test_volume.py @@ -41,8 +41,8 @@ def setUp(self): self.users_mock = self.app.client_manager.identity.users self.users_mock.reset_mock() - self.images_mock = self.app.client_manager.image.images - self.images_mock.reset_mock() + self.find_image_mock = self.app.client_manager.image.find_image + self.find_image_mock.reset_mock() self.snapshots_mock = self.app.client_manager.volume.volume_snapshots self.snapshots_mock.reset_mock() @@ -222,7 +222,7 @@ def test_volume_create_properties(self): def test_volume_create_image_id(self): image = image_fakes.FakeImage.create_one_image() - self.images_mock.get.return_value = image + self.find_image_mock.return_value = image arglist = [ '--image', image.id, @@ -260,7 +260,7 @@ def test_volume_create_image_id(self): def test_volume_create_image_name(self): image = image_fakes.FakeImage.create_one_image() - self.images_mock.get.return_value = image + self.find_image_mock.return_value = image arglist = [ '--image', image.name, diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index 4dde1340a7..1e0cb183fc 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -193,9 +193,8 @@ def take_action(self, parsed_args): image = None if parsed_args.image: - image = utils.find_resource( - image_client.images, - parsed_args.image).id + image = image_client.find_image(parsed_args.image, + ignore_missing=False).id size = parsed_args.size diff --git a/releasenotes/notes/use_sdk_for_image-f49d2df38e7d9f81.yaml b/releasenotes/notes/use_sdk_for_image-f49d2df38e7d9f81.yaml new file mode 100644 index 0000000000..ba10f388c7 --- /dev/null +++ b/releasenotes/notes/use_sdk_for_image-f49d2df38e7d9f81.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Image service for v2 is switched from using glanceclient to OpenStackSDK. diff --git a/requirements.txt b/requirements.txt index aaea495e14..bf2a0590f3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,8 +6,7 @@ six>=1.10.0 # MIT Babel!=2.4.0,>=2.3.4 # BSD cliff!=2.9.0,>=2.8.0 # Apache-2.0 -keystoneauth1>=3.14.0 # Apache-2.0 -openstacksdk>=0.17.0 # Apache-2.0 +openstacksdk>=0.36.0 # Apache-2.0 osc-lib>=2.0.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 From cdac869412a48ba0e33542ff7859bcc5ccc0544e Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Tue, 24 Mar 2020 07:23:00 -0500 Subject: [PATCH 2178/3095] Honor endpoint override from config for volume I'm guessing we should do this for everyone, but we have volume on the brain right now. Rackspace is in the weird situation where they do support v2 but only have v1 in the catalog (wut) So we need to override the block-storage enpdoint by config. To do that, we need to actually honor the config setting over here in OSC. NOTE: We need to systemically overhaul how we're injesting config over here - because there's too much variation. But we can leave that for another day. Story: 2007459 Task: 39137 Change-Id: Ifddf1ddd5abaa768ab18049c09d18bc269f3a4f5 --- openstackclient/volume/client.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openstackclient/volume/client.py b/openstackclient/volume/client.py index fdd1794be5..1fbfaaee78 100644 --- a/openstackclient/volume/client.py +++ b/openstackclient/volume/client.py @@ -67,11 +67,15 @@ def make_client(instance): # Remember interface only if it is set kwargs = utils.build_kwargs_dict('endpoint_type', instance.interface) + endpoint_override = instance.sdk_connection.config.get_endpoint( + 'block-storage') + client = volume_client( session=instance.session, extensions=extensions, http_log_debug=http_log_debug, region_name=instance.region_name, + endpoint_override=endpoint_override, **kwargs ) From 768a64aac5bd14f56c142faeec1793aac91947cb Mon Sep 17 00:00:00 2001 From: Artem Goncharov Date: Tue, 17 Dec 2019 14:43:21 +0100 Subject: [PATCH 2179/3095] Complete switch from glanceclient to SDK for image service In https://review.opendev.org/#/c/650374/ a work has been started to switch image service support from glanceclient with all it's dependencies to the SDK version. With this change version 1 (anyway deprecated since ages) is also being switched to SDK. Change-Id: Ic391500af02a73d81d64a9e9113cca85c9e24390 --- openstackclient/image/client.py | 58 +----- openstackclient/image/v1/image.py | 138 +++++++------- openstackclient/tests/unit/image/v1/fakes.py | 10 +- .../tests/unit/image/v1/test_image.py | 173 +++++++----------- ...omplete_image_switch-203e0b3105a54674.yaml | 4 + requirements.txt | 1 - 6 files changed, 155 insertions(+), 229 deletions(-) create mode 100644 releasenotes/notes/complete_image_switch-203e0b3105a54674.yaml diff --git a/openstackclient/image/client.py b/openstackclient/image/client.py index 15bea17eb6..9a0d7bacb1 100644 --- a/openstackclient/image/client.py +++ b/openstackclient/image/client.py @@ -26,64 +26,18 @@ API_VERSION_OPTION = 'os_image_api_version' API_NAME = "image" API_VERSIONS = { - "1": "glanceclient.v1.client.Client", + "1": "openstack.connection.Connection", "2": "openstack.connection.Connection", } -IMAGE_API_TYPE = 'image' -IMAGE_API_VERSIONS = { - '1': 'openstackclient.api.image_v1.APIv1', - '2': 'openstackclient.api.image_v2.APIv2', -} - def make_client(instance): - if instance._api_version[API_NAME] != '1': - LOG.debug( - 'Image client initialized using OpenStack SDK: %s', - instance.sdk_connection.image, - ) - return instance.sdk_connection.image - else: - """Returns an image service client""" - image_client = utils.get_client_class( - API_NAME, - instance._api_version[API_NAME], - API_VERSIONS) - LOG.debug('Instantiating image client: %s', image_client) - - endpoint = instance.get_endpoint_for_service_type( - API_NAME, - region_name=instance.region_name, - interface=instance.interface, - ) - - client = image_client( - endpoint, - token=instance.auth.get_token(instance.session), - cacert=instance.cacert, - insecure=not instance.verify, - ) - - # Create the low-level API - - image_api = utils.get_client_class( - API_NAME, - instance._api_version[API_NAME], - IMAGE_API_VERSIONS) - LOG.debug('Instantiating image api: %s', image_api) - - client.api = image_api( - session=instance.session, - endpoint=instance.get_endpoint_for_service_type( - IMAGE_API_TYPE, - region_name=instance.region_name, - interface=instance.interface, - ) - ) - - return client + LOG.debug( + 'Image client initialized using OpenStack SDK: %s', + instance.sdk_connection.image, + ) + return instance.sdk_connection.image def build_option_parser(parser): diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index a711a1288b..cf1d68171a 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -22,13 +22,13 @@ import sys from cliff import columns as cliff_columns -from glanceclient.common import utils as gc_utils from osc_lib.api import utils as api_utils from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import utils +from openstackclient.common import sdk_utils from openstackclient.i18n import _ if os.name == "nt": @@ -47,6 +47,36 @@ LOG = logging.getLogger(__name__) +def _get_columns(item): + # Trick sdk_utils to return URI attribute + column_map = { + 'is_protected': 'protected', + 'owner_id': 'owner' + } + hidden_columns = ['location', 'checksum', + 'copy_from', 'created_at', 'status', 'updated_at'] + return sdk_utils.get_osc_show_columns_for_sdk_resource( + item.to_dict(), column_map, hidden_columns) + + +_formatters = { +} + + +class HumanReadableSizeColumn(cliff_columns.FormattableColumn): + def human_readable(self): + """Return a formatted visibility string + + :rtype: + A string formatted to public/private + """ + + if self._value: + return utils.format_size(self._value) + else: + return '' + + class VisibilityColumn(cliff_columns.FormattableColumn): def human_readable(self): """Return a formatted visibility string @@ -210,7 +240,7 @@ def take_action(self, parsed_args): # Special case project option back to API attribute name 'owner' val = getattr(parsed_args, 'project', None) if val: - kwargs['owner'] = val + kwargs['owner_id'] = val # Handle exclusive booleans with care # Avoid including attributes in kwargs if an option is not @@ -219,9 +249,9 @@ def take_action(self, parsed_args): # to do nothing when no options are present as opposed to always # setting a default. if parsed_args.protected: - kwargs['protected'] = True + kwargs['is_protected'] = True if parsed_args.unprotected: - kwargs['protected'] = False + kwargs['is_protected'] = False if parsed_args.public: kwargs['is_public'] = True if parsed_args.private: @@ -250,27 +280,35 @@ def take_action(self, parsed_args): kwargs["data"] = io.open(parsed_args.file, "rb") else: # Read file from stdin - if sys.stdin.isatty() is not True: + if not sys.stdin.isatty(): if msvcrt: msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY) - # Send an open file handle to glanceclient so it will - # do a chunked transfer - kwargs["data"] = sys.stdin + if hasattr(sys.stdin, 'buffer'): + kwargs['data'] = sys.stdin.buffer + else: + kwargs["data"] = sys.stdin if not parsed_args.volume: # Wrap the call to catch exceptions in order to close files try: - image = image_client.images.create(**kwargs) + image = image_client.create_image(**kwargs) finally: # Clean up open files - make sure data isn't a string if ('data' in kwargs and hasattr(kwargs['data'], 'close') and kwargs['data'] != sys.stdin): kwargs['data'].close() + if image: + display_columns, columns = _get_columns(image) + _formatters['properties'] = format_columns.DictColumn + data = utils.get_item_properties(image, columns, + formatters=_formatters) + return (display_columns, data) + elif info: info.update(image._info) info['properties'] = format_columns.DictColumn( info.get('properties', {})) - return zip(*sorted(info.items())) + return zip(*sorted(info.items())) class DeleteImage(command.Command): @@ -289,11 +327,8 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): image_client = self.app.client_manager.image for image in parsed_args.images: - image_obj = utils.find_resource( - image_client.images, - image, - ) - image_client.images.delete(image_obj.id) + image_obj = image_client.find_image(image) + image_client.delete_image(image_obj.id) class ListImage(command.Lister): @@ -359,15 +394,9 @@ def take_action(self, parsed_args): kwargs = {} if parsed_args.public: - kwargs['public'] = True + kwargs['is_public'] = True if parsed_args.private: - kwargs['private'] = True - # Note: We specifically need to do that below to get the 'status' - # column. - # - # Always set kwargs['detailed'] to True, and then filter the columns - # according to whether the --long option is specified or not. - kwargs['detailed'] = True + kwargs['is_private'] = True if parsed_args.long: columns = ( @@ -379,8 +408,8 @@ def take_action(self, parsed_args): 'Checksum', 'Status', 'is_public', - 'protected', - 'owner', + 'is_protected', + 'owner_id', 'properties', ) column_headers = ( @@ -401,16 +430,7 @@ def take_action(self, parsed_args): column_headers = columns # List of image data received - data = [] - # No pages received yet, so start the page marker at None. - marker = None - while True: - page = image_client.api.image_list(marker=marker, **kwargs) - if not page: - break - data.extend(page) - # Set the marker to the id of the last item we received - marker = page[-1]['id'] + data = list(image_client.images(**kwargs)) if parsed_args.property: # NOTE(dtroyer): coerce to a list to subscript it in py3 @@ -426,7 +446,7 @@ def take_action(self, parsed_args): return ( column_headers, - (utils.get_dict_properties( + (utils.get_item_properties( s, columns, formatters={ @@ -456,13 +476,9 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): image_client = self.app.client_manager.image - image = utils.find_resource( - image_client.images, - parsed_args.image, - ) - data = image_client.images.data(image) + image = image_client.find_image(parsed_args.image) - gc_utils.save_image(data, parsed_args.file) + image_client.download_image(image.id, output=parsed_args.file) class SetImage(command.Command): @@ -621,22 +637,17 @@ def take_action(self, parsed_args): # to do nothing when no options are present as opposed to always # setting a default. if parsed_args.protected: - kwargs['protected'] = True + kwargs['is_protected'] = True if parsed_args.unprotected: - kwargs['protected'] = False + kwargs['is_protected'] = False if parsed_args.public: kwargs['is_public'] = True if parsed_args.private: kwargs['is_public'] = False - if parsed_args.force: - kwargs['force'] = True # Wrap the call to catch exceptions in order to close files try: - image = utils.find_resource( - image_client.images, - parsed_args.image, - ) + image = image_client.find_image(parsed_args.image) if not parsed_args.location and not parsed_args.copy_from: if parsed_args.volume: @@ -666,9 +677,10 @@ def take_action(self, parsed_args): if parsed_args.stdin: if msvcrt: msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY) - # Send an open file handle to glanceclient so it - # will do a chunked transfer - kwargs["data"] = sys.stdin + if hasattr(sys.stdin, 'buffer'): + kwargs['data'] = sys.stdin.buffer + else: + kwargs["data"] = sys.stdin else: LOG.warning(_('Use --stdin to enable read image ' 'data from standard input')) @@ -677,7 +689,7 @@ def take_action(self, parsed_args): image.properties.update(kwargs['properties']) kwargs['properties'] = image.properties - image = image_client.images.update(image.id, **kwargs) + image = image_client.update_image(image.id, **kwargs) finally: # Clean up open files - make sure data isn't a string if ('data' in kwargs and hasattr(kwargs['data'], 'close') and @@ -705,16 +717,12 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): image_client = self.app.client_manager.image - image = utils.find_resource( - image_client.images, - parsed_args.image, - ) + image = image_client.find_image(parsed_args.image) - info = {} - info.update(image._info) if parsed_args.human_readable: - if 'size' in info: - info['size'] = utils.format_size(info['size']) - info['properties'] = format_columns.DictColumn( - info.get('properties', {})) - return zip(*sorted(info.items())) + _formatters['size'] = HumanReadableSizeColumn + display_columns, columns = _get_columns(image) + _formatters['properties'] = format_columns.DictColumn + data = utils.get_item_properties(image, columns, + formatters=_formatters) + return (display_columns, data) diff --git a/openstackclient/tests/unit/image/v1/fakes.py b/openstackclient/tests/unit/image/v1/fakes.py index de23223572..add3978d1d 100644 --- a/openstackclient/tests/unit/image/v1/fakes.py +++ b/openstackclient/tests/unit/image/v1/fakes.py @@ -13,10 +13,11 @@ # under the License. # -import copy from unittest import mock import uuid +from openstack.image.v1 import image + from openstackclient.tests.unit import fakes from openstackclient.tests.unit import utils from openstackclient.tests.unit.volume.v1 import fakes as volume_fakes @@ -111,13 +112,10 @@ def create_one_image(attrs=None): 'Alpha': 'a', 'Beta': 'b', 'Gamma': 'g'}, + 'status': 'status' + uuid.uuid4().hex } # Overwrite default attributes if there are some attributes set image_info.update(attrs) - image = fakes.FakeResource( - info=copy.deepcopy(image_info), - loaded=True) - - return image + return image.Image(**image_info) diff --git a/openstackclient/tests/unit/image/v1/test_image.py b/openstackclient/tests/unit/image/v1/test_image.py index 970b36c68a..2f190a7a3c 100644 --- a/openstackclient/tests/unit/image/v1/test_image.py +++ b/openstackclient/tests/unit/image/v1/test_image.py @@ -17,7 +17,6 @@ from unittest import mock from osc_lib.cli import format_columns -from osc_lib import exceptions from openstackclient.image.v1 import image from openstackclient.tests.unit import fakes @@ -29,9 +28,8 @@ class TestImage(image_fakes.TestImagev1): def setUp(self): super(TestImage, self).setUp() - # Get a shortcut to the ServerManager Mock - self.images_mock = self.app.client_manager.image.images - self.images_mock.reset_mock() + self.app.client_manager.image = mock.Mock() + self.client = self.app.client_manager.image class TestImageCreate(TestImage): @@ -48,6 +46,7 @@ class TestImageCreate(TestImage): 'owner', 'properties', 'protected', + 'size' ) data = ( new_image.container_format, @@ -57,28 +56,24 @@ class TestImageCreate(TestImage): new_image.min_disk, new_image.min_ram, new_image.name, - new_image.owner, + new_image.owner_id, format_columns.DictColumn(new_image.properties), - new_image.protected, + new_image.is_protected, + new_image.size ) def setUp(self): super(TestImageCreate, self).setUp() - self.images_mock.create.return_value = self.new_image - # This is the return value for utils.find_resource() - self.images_mock.get.return_value = self.new_image - self.images_mock.update.return_value = self.new_image + self.client.create_image = mock.Mock(return_value=self.new_image) + self.client.find_image = mock.Mock(return_value=self.new_image) + self.client.update_image = mock.Mock(return_image=self.new_image) # Get the command object to test self.cmd = image.CreateImage(self.app, None) - def test_image_reserve_no_options(self): - mock_exception = { - 'find.side_effect': exceptions.CommandError('x'), - 'get.side_effect': exceptions.CommandError('x'), - } - self.images_mock.configure_mock(**mock_exception) + @mock.patch('sys.stdin', side_effect=[None]) + def test_image_reserve_no_options(self, raw_input): arglist = [ self.new_image.name, ] @@ -95,25 +90,20 @@ def test_image_reserve_no_options(self): columns, data = self.cmd.take_action(parsed_args) # ImageManager.create(name=, **) - self.images_mock.create.assert_called_with( + self.client.create_image.assert_called_with( name=self.new_image.name, container_format=image.DEFAULT_CONTAINER_FORMAT, - disk_format=image.DEFAULT_DISK_FORMAT, - data=mock.ANY, + disk_format=image.DEFAULT_DISK_FORMAT ) # Verify update() was not called, if it was show the args - self.assertEqual(self.images_mock.update.call_args_list, []) + self.assertEqual(self.client.update_image.call_args_list, []) self.assertEqual(self.columns, columns) self.assertItemEqual(self.data, data) - def test_image_reserve_options(self): - mock_exception = { - 'find.side_effect': exceptions.CommandError('x'), - 'get.side_effect': exceptions.CommandError('x'), - } - self.images_mock.configure_mock(**mock_exception) + @mock.patch('sys.stdin', side_effect=[None]) + def test_image_reserve_options(self, raw_input): arglist = [ '--container-format', 'ovf', '--disk-format', 'ami', @@ -144,20 +134,19 @@ def test_image_reserve_options(self): columns, data = self.cmd.take_action(parsed_args) # ImageManager.create(name=, **) - self.images_mock.create.assert_called_with( + self.client.create_image.assert_called_with( name=self.new_image.name, container_format='ovf', disk_format='ami', min_disk=10, min_ram=4, - protected=True, + is_protected=True, is_public=False, - owner='q', - data=mock.ANY, + owner_id='q', ) # Verify update() was not called, if it was show the args - self.assertEqual(self.images_mock.update.call_args_list, []) + self.assertEqual(self.client.update_image.call_args_list, []) self.assertEqual(self.columns, columns) self.assertItemEqual(self.data, data) @@ -167,11 +156,6 @@ def test_image_create_file(self, mock_open): mock_file = mock.Mock(name='File') mock_open.return_value = mock_file mock_open.read.return_value = self.data - mock_exception = { - 'find.side_effect': exceptions.CommandError('x'), - 'get.side_effect': exceptions.CommandError('x'), - } - self.images_mock.configure_mock(**mock_exception) arglist = [ '--file', 'filer', @@ -203,15 +187,12 @@ def test_image_create_file(self, mock_open): # Ensure the input file is closed mock_file.close.assert_called_with() - # ImageManager.get(name) not to be called since update action exists - self.images_mock.get.assert_not_called() - # ImageManager.create(name=, **) - self.images_mock.create.assert_called_with( + self.client.create_image.assert_called_with( name=self.new_image.name, container_format=image.DEFAULT_CONTAINER_FORMAT, disk_format=image.DEFAULT_DISK_FORMAT, - protected=False, + is_protected=False, is_public=True, properties={ 'Alpha': '1', @@ -221,7 +202,7 @@ def test_image_create_file(self, mock_open): ) # Verify update() was not called, if it was show the args - self.assertEqual(self.images_mock.update.call_args_list, []) + self.assertEqual(self.client.update_image.call_args_list, []) self.assertEqual(self.columns, columns) self.assertItemEqual(self.data, data) @@ -235,8 +216,8 @@ def setUp(self): super(TestImageDelete, self).setUp() # This is the return value for utils.find_resource() - self.images_mock.get.return_value = self._image - self.images_mock.delete.return_value = None + self.client.find_image = mock.Mock(return_value=self._image) + self.client.delete_image = mock.Mock(return_value=None) # Get the command object to test self.cmd = image.DeleteImage(self.app, None) @@ -252,7 +233,7 @@ def test_image_delete_no_options(self): result = self.cmd.take_action(parsed_args) - self.images_mock.delete.assert_called_with(self._image.id) + self.client.delete_image.assert_called_with(self._image.id) self.assertIsNone(result) @@ -269,7 +250,7 @@ class TestImageList(TestImage): ( _image.id, _image.name, - '', + _image.status ), ) @@ -277,13 +258,13 @@ class TestImageList(TestImage): info = { 'id': _image.id, 'name': _image.name, - 'owner': _image.owner, + 'owner': _image.owner_id, 'container_format': _image.container_format, 'disk_format': _image.disk_format, 'min_disk': _image.min_disk, 'min_ram': _image.min_ram, 'is_public': _image.is_public, - 'protected': _image.protected, + 'protected': _image.is_protected, 'properties': _image.properties, } image_info = copy.deepcopy(info) @@ -291,11 +272,10 @@ class TestImageList(TestImage): def setUp(self): super(TestImageList, self).setUp() - self.api_mock = mock.Mock() - self.api_mock.image_list.side_effect = [ - [self.image_info], [], + self.client.images = mock.Mock() + self.client.images.side_effect = [ + [self._image], [], ] - self.app.client_manager.image.api = self.api_mock # Get the command object to test self.cmd = image.ListImage(self.app, None) @@ -313,10 +293,7 @@ def test_image_list_no_options(self): # returns a tuple containing the column names and an iterable # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.api_mock.image_list.assert_called_with( - detailed=True, - marker=self._image.id, - ) + self.client.images.assert_called_with() self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) @@ -336,10 +313,8 @@ def test_image_list_public_option(self): # returns a tuple containing the column names and an iterable # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.api_mock.image_list.assert_called_with( - detailed=True, - public=True, - marker=self._image.id, + self.client.images.assert_called_with( + is_public=True, ) self.assertEqual(self.columns, columns) @@ -360,10 +335,8 @@ def test_image_list_private_option(self): # returns a tuple containing the column names and an iterable # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.api_mock.image_list.assert_called_with( - detailed=True, - private=True, - marker=self._image.id, + self.client.images.assert_called_with( + is_private=True, ) self.assertEqual(self.columns, columns) @@ -382,10 +355,7 @@ def test_image_list_long_option(self): # returns a tuple containing the column names and an iterable # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.api_mock.image_list.assert_called_with( - detailed=True, - marker=self._image.id, - ) + self.client.images.assert_called_with() collist = ( 'ID', @@ -405,14 +375,14 @@ def test_image_list_long_option(self): datalist = (( self._image.id, self._image.name, - '', - '', - '', - '', - '', - image.VisibilityColumn(True), - False, - self._image.owner, + self._image.disk_format, + self._image.container_format, + self._image.size, + self._image.checksum, + self._image.status, + image.VisibilityColumn(self._image.is_public), + self._image.is_protected, + self._image.owner_id, format_columns.DictColumn( {'Alpha': 'a', 'Beta': 'b', 'Gamma': 'g'}), ), ) @@ -436,12 +406,9 @@ def test_image_list_property_option(self, sf_mock): # returns a tuple containing the column names and an iterable # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.api_mock.image_list.assert_called_with( - detailed=True, - marker=self._image.id, - ) + self.client.images.assert_called_with() sf_mock.assert_called_with( - [self.image_info], + [self._image], attr='a', value='1', property_field='properties', @@ -453,7 +420,7 @@ def test_image_list_property_option(self, sf_mock): @mock.patch('osc_lib.utils.sort_items') def test_image_list_sort_option(self, si_mock): si_mock.side_effect = [ - [self.image_info], [], + [self._image], [], ] arglist = ['--sort', 'name:asc'] @@ -464,12 +431,9 @@ def test_image_list_sort_option(self, si_mock): # returns a tuple containing the column names and an iterable # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.api_mock.image_list.assert_called_with( - detailed=True, - marker=self._image.id, - ) + self.client.images.assert_called_with() si_mock.assert_called_with( - [self.image_info], + [self._image], 'name:asc' ) @@ -485,8 +449,8 @@ def setUp(self): super(TestImageSet, self).setUp() # This is the return value for utils.find_resource() - self.images_mock.get.return_value = self._image - self.images_mock.update.return_value = self._image + self.client.find_image = mock.Mock(return_value=self._image) + self.client.update_image = mock.Mock(return_value=self._image) # Get the command object to test self.cmd = image.SetImage(self.app, None) @@ -502,8 +466,7 @@ def test_image_set_no_options(self): result = self.cmd.take_action(parsed_args) - self.images_mock.update.assert_called_with(self._image.id, - **{}) + self.client.update_image.assert_called_with(self._image.id, **{}) self.assertIsNone(result) def test_image_set_options(self): @@ -541,7 +504,7 @@ def test_image_set_options(self): 'size': 35165824 } # ImageManager.update(image, **kwargs) - self.images_mock.update.assert_called_with( + self.client.update_image.assert_called_with( self._image.id, **kwargs ) @@ -565,11 +528,11 @@ def test_image_set_bools1(self): result = self.cmd.take_action(parsed_args) kwargs = { - 'protected': True, + 'is_protected': True, 'is_public': False, } # ImageManager.update(image, **kwargs) - self.images_mock.update.assert_called_with( + self.client.update_image.assert_called_with( self._image.id, **kwargs ) @@ -593,11 +556,11 @@ def test_image_set_bools2(self): result = self.cmd.take_action(parsed_args) kwargs = { - 'protected': False, + 'is_protected': False, 'is_public': True, } # ImageManager.update(image, **kwargs) - self.images_mock.update.assert_called_with( + self.client.update_image.assert_called_with( self._image.id, **kwargs ) @@ -625,7 +588,7 @@ def test_image_set_properties(self): }, } # ImageManager.update(image, **kwargs) - self.images_mock.update.assert_called_with( + self.client.update_image.assert_called_with( self._image.id, **kwargs ) @@ -683,7 +646,7 @@ def test_image_update_volume(self): '', ) # ImageManager.update(image_id, remove_props=, **) - self.images_mock.update.assert_called_with( + self.client.update_image.assert_called_with( self._image.id, name='updated_image', volume='volly', @@ -710,7 +673,7 @@ def test_image_set_numeric_options_to_zero(self): 'min_ram': 0, } # ImageManager.update(image, **kwargs) - self.images_mock.update.assert_called_with( + self.client.update_image.assert_called_with( self._image.id, **kwargs ) @@ -742,16 +705,16 @@ class TestImageShow(TestImage): _image.min_disk, _image.min_ram, _image.name, - _image.owner, + _image.owner_id, format_columns.DictColumn(_image.properties), - _image.protected, + _image.is_protected, _image.size, ) def setUp(self): super(TestImageShow, self).setUp() - self.images_mock.get.return_value = self._image + self.client.find_image = mock.Mock(return_value=self._image) # Get the command object to test self.cmd = image.ShowImage(self.app, None) @@ -769,7 +732,7 @@ def test_image_show(self): # 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.images_mock.get.assert_called_with( + self.client.find_image.assert_called_with( self._image.id, ) @@ -791,9 +754,9 @@ def test_image_show_human_readable(self): # 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.images_mock.get.assert_called_with( + self.client.find_image.assert_called_with( self._image.id, ) size_index = columns.index('size') - self.assertEqual(data[size_index], '2K') + self.assertEqual(data[size_index].human_readable(), '2K') diff --git a/releasenotes/notes/complete_image_switch-203e0b3105a54674.yaml b/releasenotes/notes/complete_image_switch-203e0b3105a54674.yaml new file mode 100644 index 0000000000..6878d1d984 --- /dev/null +++ b/releasenotes/notes/complete_image_switch-203e0b3105a54674.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Complete switch from glanceclient to the SDK for image service. diff --git a/requirements.txt b/requirements.txt index bf2a0590f3..b17b6a5593 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,6 @@ openstacksdk>=0.36.0 # Apache-2.0 osc-lib>=2.0.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 -python-glanceclient>=2.8.0 # Apache-2.0 python-keystoneclient>=3.22.0 # Apache-2.0 python-novaclient>=15.1.0 # Apache-2.0 python-cinderclient>=3.3.0 # Apache-2.0 From 042be7c7fe89f5a1a190af90d5980205d995941b Mon Sep 17 00:00:00 2001 From: Dmitriy Rabotyagov Date: Thu, 12 Mar 2020 20:35:49 +0200 Subject: [PATCH 2180/3095] Don't look up project by id if given id There is a much deeper and systemic issue going on here, but let's start with fixing the immediate issue which is that adding a project to an image fails trying to look up project information even if the user passes the project id by id. _is_uuid_like from sdk isn't perfect, but it'll be good enough for this. Change-Id: I541416d737b961c56aa2f584c172528632fd5537 --- openstackclient/image/v2/image.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 412a16cc1c..53ce560dcd 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -21,6 +21,7 @@ import os import sys +import openstack.cloud._utils from openstack.image import image_signer from osc_lib.api import utils as api_utils from osc_lib.cli import format_columns @@ -158,9 +159,13 @@ def take_action(self, parsed_args): image_client = self.app.client_manager.image identity_client = self.app.client_manager.identity - project_id = common.find_project(identity_client, - parsed_args.project, - parsed_args.project_domain).id + if openstack.cloud._utils._is_uuid_like(parsed_args.project): + project_id = parsed_args.project + else: + project_id = common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain).id image = image_client.find_image(parsed_args.image, ignore_missing=False) From f03cb68ad89dcc856fdad1a46fee36b7d68c5ba2 Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Mon, 17 Feb 2020 17:10:04 +0200 Subject: [PATCH 2181/3095] Add 'address_scope' type support to network rbac commands Change-Id: I6a4b7219934805c1bbd1e88fcc670ae231d9ac37 Partial-Bug: #1862968 Depends-On: https://review.opendev.org/709122 --- lower-constraints.txt | 1 + openstackclient/network/v2/network_rbac.py | 17 +++-- .../unit/network/v2/test_network_rbac.py | 70 ++++++------------- ...ac-add-address-scope-7f6409ab70d36306.yaml | 4 ++ test-requirements.txt | 1 + 5 files changed, 41 insertions(+), 52 deletions(-) create mode 100644 releasenotes/notes/rbac-add-address-scope-7f6409ab70d36306.yaml diff --git a/lower-constraints.txt b/lower-constraints.txt index 1fa30674e1..dac6922bbf 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -11,6 +11,7 @@ cmd2==0.8.0 contextlib2==0.4.0 coverage==4.0 cryptography==2.1 +ddt==1.0.1 debtcollector==1.2.0 decorator==3.4.0 deprecation==1.0 diff --git a/openstackclient/network/v2/network_rbac.py b/openstackclient/network/v2/network_rbac.py index 1781193f61..0d6e07927b 100644 --- a/openstackclient/network/v2/network_rbac.py +++ b/openstackclient/network/v2/network_rbac.py @@ -58,6 +58,10 @@ def _get_attrs(client_manager, parsed_args): object_id = network_client.find_security_group( parsed_args.rbac_object, ignore_missing=False).id + if parsed_args.type == 'address_scope': + object_id = network_client.find_address_scope( + parsed_args.rbac_object, + ignore_missing=False).id attrs['object_id'] = object_id identity_client = client_manager.identity @@ -97,9 +101,11 @@ def get_parser(self, prog_name): '--type', metavar="", required=True, - choices=['security_group', 'qos_policy', 'network'], + choices=['address_scope', 'security_group', + 'qos_policy', 'network'], help=_('Type of the object that RBAC policy ' - 'affects ("security_group", "qos_policy" or "network")') + 'affects ("address_scope", "security_group", ' + '"qos_policy" or "network")') ) parser.add_argument( '--action', @@ -188,10 +194,11 @@ def get_parser(self, prog_name): parser.add_argument( '--type', metavar='', - choices=['security_group', 'qos_policy', 'network'], + choices=['address_scope', 'security_group', + 'qos_policy', 'network'], help=_('List network RBAC policies according to ' - 'given object type ("security_group", "qos_policy" ' - 'or "network")') + 'given object type ("address_scope", "security_group", ' + '"qos_policy" or "network")') ) parser.add_argument( '--action', diff --git a/openstackclient/tests/unit/network/v2/test_network_rbac.py b/openstackclient/tests/unit/network/v2/test_network_rbac.py index 078188ceb5..b3f8de4050 100644 --- a/openstackclient/tests/unit/network/v2/test_network_rbac.py +++ b/openstackclient/tests/unit/network/v2/test_network_rbac.py @@ -14,6 +14,7 @@ from unittest import mock from unittest.mock import call +import ddt from osc_lib import exceptions from openstackclient.network.v2 import network_rbac @@ -33,11 +34,13 @@ def setUp(self): self.projects_mock = self.app.client_manager.identity.projects +@ddt.ddt class TestCreateNetworkRBAC(TestNetworkRBAC): network_object = network_fakes.FakeNetwork.create_one_network() qos_object = network_fakes.FakeNetworkQosPolicy.create_one_qos_policy() sg_object = network_fakes.FakeNetworkSecGroup.create_one_security_group() + as_object = network_fakes.FakeAddressScope.create_one_address_scope() project = identity_fakes_v3.FakeProject.create_one_project() rbac_policy = network_fakes.FakeNetworkRBAC.create_one_network_rbac( attrs={'tenant_id': project.id, @@ -77,6 +80,8 @@ def setUp(self): return_value=self.qos_object) self.network.find_security_group = mock.Mock( return_value=self.sg_object) + self.network.find_address_scope = mock.Mock( + return_value=self.as_object) self.projects_mock.get.return_value = self.project def test_network_rbac_create_no_type(self): @@ -224,57 +229,28 @@ def test_network_rbac_create_all_options(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) - def test_network_rbac_create_qos_object(self): - self.rbac_policy.object_type = 'qos_policy' - self.rbac_policy.object_id = self.qos_object.id - arglist = [ - '--type', 'qos_policy', - '--action', self.rbac_policy.action, - '--target-project', self.rbac_policy.target_tenant, - self.qos_object.name, - ] - verifylist = [ - ('type', 'qos_policy'), - ('action', self.rbac_policy.action), - ('target_project', self.rbac_policy.target_tenant), - ('rbac_object', self.qos_object.name), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # DisplayCommandBase.take_action() returns two tuples - columns, data = self.cmd.take_action(parsed_args) - - self.network.create_rbac_policy.assert_called_with(**{ - 'object_id': self.qos_object.id, - 'object_type': 'qos_policy', - 'action': self.rbac_policy.action, - 'target_tenant': self.rbac_policy.target_tenant, - }) - self.data = [ - self.rbac_policy.action, - self.rbac_policy.id, - self.qos_object.id, - 'qos_policy', - self.rbac_policy.tenant_id, - self.rbac_policy.target_tenant, - ] - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + @ddt.data( + ('qos_policy', "qos_object"), + ('security_group', "sg_object"), + ('address_scope', "as_object") + ) + @ddt.unpack + def test_network_rbac_create_object(self, obj_type, obj_fake_attr): + obj_fake = getattr(self, obj_fake_attr) - def test_network_rbac_create_security_group_object(self): - self.rbac_policy.object_type = 'security_group' - self.rbac_policy.object_id = self.sg_object.id + self.rbac_policy.object_type = obj_type + self.rbac_policy.object_id = obj_fake.id arglist = [ - '--type', 'security_group', + '--type', obj_type, '--action', self.rbac_policy.action, '--target-project', self.rbac_policy.target_tenant, - self.sg_object.name, + obj_fake.name, ] verifylist = [ - ('type', 'security_group'), + ('type', obj_type), ('action', self.rbac_policy.action), ('target_project', self.rbac_policy.target_tenant), - ('rbac_object', self.sg_object.name), + ('rbac_object', obj_fake.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -282,16 +258,16 @@ def test_network_rbac_create_security_group_object(self): columns, data = self.cmd.take_action(parsed_args) self.network.create_rbac_policy.assert_called_with(**{ - 'object_id': self.sg_object.id, - 'object_type': 'security_group', + 'object_id': obj_fake.id, + 'object_type': obj_type, 'action': self.rbac_policy.action, 'target_tenant': self.rbac_policy.target_tenant, }) self.data = [ self.rbac_policy.action, self.rbac_policy.id, - self.sg_object.id, - 'security_group', + obj_fake.id, + obj_type, self.rbac_policy.tenant_id, self.rbac_policy.target_tenant, ] diff --git a/releasenotes/notes/rbac-add-address-scope-7f6409ab70d36306.yaml b/releasenotes/notes/rbac-add-address-scope-7f6409ab70d36306.yaml new file mode 100644 index 0000000000..22b3838e3a --- /dev/null +++ b/releasenotes/notes/rbac-add-address-scope-7f6409ab70d36306.yaml @@ -0,0 +1,4 @@ +features: + - | + Add ``address_scope`` as a valid ``--type`` value for the + ``network rbac create`` and ``network rbac list`` commands. diff --git a/test-requirements.txt b/test-requirements.txt index 55ae1ea459..bdbf716e3a 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -17,3 +17,4 @@ tempest>=17.1.0 # Apache-2.0 osprofiler>=1.4.0 # Apache-2.0 bandit!=1.6.0,>=1.1.0 # Apache-2.0 wrapt>=1.7.0 # BSD License +ddt>=1.0.1 # MIT From 97d027caecdb977779ff130bb9ee2b3204c5646c Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Thu, 26 Mar 2020 08:46:13 -0500 Subject: [PATCH 2182/3095] Add libc6-dev to bindep The python-builder base image was updated to no longer install recommends. This is inline with the other Infra images and keeps image sizes smaller. gcc recommended libc6-dev - but it turns out we need that for limits.h for one of our depends. Add it to fix our image builds. Change-Id: I97950d71bc455c269490812c6597fbe432641733 --- bindep.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/bindep.txt b/bindep.txt index c5ae6c6097..cf94d2a8cb 100644 --- a/bindep.txt +++ b/bindep.txt @@ -2,6 +2,7 @@ # see https://docs.openstack.org/infra/bindep/ for additional information. gcc [compile test] +libc6-dev [compile test platform:dpkg] libffi-devel [platform:rpm] libffi-dev [compile test platform:dpkg] libffi6 [platform:dpkg] From 8efb319819534545a63c57e98ef8446becdb94c2 Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Thu, 26 Mar 2020 08:48:20 -0500 Subject: [PATCH 2183/3095] Be explicit about python version in image python-base has versions available now, defaulting to 3.7. Update our config to 3.7 to be explicit about what we're using. This will let us update the version as we feel like. Change-Id: I40ffde91808a8bb95479697b9127dba16de8a8cd --- .zuul.yaml | 8 ++++---- Dockerfile | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.zuul.yaml b/.zuul.yaml index 7a7e264ca7..4901a1ed8d 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -174,8 +174,8 @@ description: Build Docker images. allowed-projects: openstack/python-openstackclient requires: - - python-builder-container-image - - python-base-container-image + - python-builder-3.7-container-image + - python-base-3.7-container-image provides: osc-container-image vars: &osc_image_vars docker_images: @@ -188,8 +188,8 @@ description: Build Docker images and upload to Docker Hub. allowed-projects: openstack/python-openstackclient requires: - - python-builder-container-image - - python-base-container-image + - python-builder-3.7-container-image + - python-base-3.7-container-image provides: osc-container-image secrets: - name: docker_credentials diff --git a/Dockerfile b/Dockerfile index 9ff8084c30..ca60bb0ecb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,12 +13,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -FROM docker.io/opendevorg/python-builder as builder +FROM docker.io/opendevorg/python-builder:3.7 as builder COPY . /tmp/src RUN assemble -FROM docker.io/opendevorg/python-base +FROM docker.io/opendevorg/python-base:3.7 COPY --from=builder /output/ /output RUN /output/install-from-bindep From 05da145eaee329e299b449ba2d7ea88d1325e432 Mon Sep 17 00:00:00 2001 From: Vishakha Agarwal Date: Thu, 5 Dec 2019 16:48:16 +0530 Subject: [PATCH 2184/3095] Adding options to user cli User options [1] can be set by making POST and PATCH request for /v3/users API calls but cannot by openstack CLI because of no user options defined in create and update user CLI [2]. This patch adds the user options [1] in create user and update user CLI. [1] https://docs.openstack.org/keystone/latest/admin/resource-options.html#multi-factor-auth-rules [2] https://docs.openstack.org/api-ref/identity/v3/#create-user Change-Id: I4e41bae2e8cfbe92d52b14d856991bedcd44164f --- doc/source/cli/command-objects/user.rst | 126 ++ openstackclient/identity/v3/user.py | 120 +- .../tests/unit/identity/v3/fakes.py | 3 + .../tests/unit/identity/v3/test_user.py | 1326 ++++++++++++++--- ..._user_create_and_set-302401520f36d153.yaml | 19 + 5 files changed, 1356 insertions(+), 238 deletions(-) create mode 100644 releasenotes/notes/add_options_to_user_create_and_set-302401520f36d153.yaml diff --git a/doc/source/cli/command-objects/user.rst b/doc/source/cli/command-objects/user.rst index 632d0e2566..d0fc3f8734 100644 --- a/doc/source/cli/command-objects/user.rst +++ b/doc/source/cli/command-objects/user.rst @@ -19,6 +19,12 @@ Create new user [--password-prompt] [--email ] [--description ] + [--multi-factor-auth-rule ] + [--ignore-lockout-failure-attempts| --no-ignore-lockout-failure-attempts] + [--ignore-password-expiry| --no-ignore-password-expiry] + [--ignore-change-password-upon-first-use| --no-ignore-change-password-upon-first-use] + [--enable-lock-password| --disable-lock-password] + [--enable-multi-factor-auth| --disable-multi-factor-auth] [--enable | --disable] [--or-show] @@ -56,6 +62,63 @@ Create new user .. versionadded:: 3 +.. option:: --ignore-lockout-failure-attempts + + Opt into ignoring the number of times a user has authenticated and + locking out the user as a result + +.. option:: --no-ignore-lockout-failure-attempts + + Opt out of ignoring the number of times a user has authenticated + and locking out the user as a result + +.. option:: --ignore-change-password-upon-first-use + + Control if a user should be forced to change their password immediately + after they log into keystone for the first time. Opt into ignoring + the user to change their password during first time login in keystone. + +.. option:: --no-ignore-change-password-upon-first-use + + Control if a user should be forced to change their password immediately + after they log into keystone for the first time. Opt out of ignoring + the user to change their password during first time login in keystone. + +.. option:: --ignore-password-expiry + + Opt into allowing user to continue using passwords that may be + expired + +.. option:: --no-ignore-password-expiry + + Opt out of allowing user to continue using passwords that may be + expired + +.. option:: --enable-lock-password + + Disables the ability for a user to change its password through + self-service APIs + +.. option:: --disable-lock-password + + Enables the ability for a user to change its password through + self-service APIs + +.. option:: --enable-multi-factor-auth + + Enables the MFA (Multi Factor Auth) + +.. option:: --disable-multi-factor-auth + + Disables the MFA (Multi Factor Auth) + +.. option:: --multi-factor-auth-rule + + Set multi-factor auth rules. For example, to set a rule requiring the + "password" and "totp" auth methods to be provided, + use: "--multi-factor-auth-rule password,totp". + May be provided multiple times to set different rule combinations. + .. option:: --enable Enable user (default) @@ -146,6 +209,12 @@ Set user properties [--password-prompt] [--email ] [--description ] + [--multi-factor-auth-rule ] + [--ignore-lockout-failure-attempts| --no-ignore-lockout-failure-attempts] + [--ignore-password-expiry| --no-ignore-password-expiry] + [--ignore-change-password-upon-first-use| --no-ignore-change-password-upon-first-use] + [--enable-lock-password| --disable-lock-password] + [--enable-multi-factor-auth| --disable-multi-factor-auth] [--enable|--disable] @@ -187,6 +256,63 @@ Set user properties .. versionadded:: 3 +.. option:: --ignore-lockout-failure-attempts + + Opt into ignoring the number of times a user has authenticated and + locking out the user as a result + +.. option:: --no-ignore-lockout-failure-attempts + + Opt out of ignoring the number of times a user has authenticated + and locking out the user as a result + +.. option:: --ignore-change-password-upon-first-use + + Control if a user should be forced to change their password immediately + after they log into keystone for the first time. Opt into ignoring + the user to change their password during first time login in keystone. + +.. option:: --no-ignore-change-password-upon-first-use + + Control if a user should be forced to change their password immediately + after they log into keystone for the first time. Opt out of ignoring + the user to change their password during first time login in keystone. + +.. option:: --ignore-password-expiry + + Opt into allowing user to continue using passwords that may be + expired + +.. option:: --no-ignore-password-expiry + + Opt out of allowing user to continue using passwords that may be + expired + +.. option:: --enable-lock-password + + Disables the ability for a user to change its password through + self-service APIs + +.. option:: --disable-lock-password + + Enables the ability for a user to change its password through + self-service APIs + +.. option:: --enable-multi-factor-auth + + Enables the MFA (Multi Factor Auth) + +.. option:: --disable-multi-factor-auth + + Disables the MFA (Multi Factor Auth) + +.. option:: --multi-factor-auth-rule + + Set multi-factor auth rules. For example, to set a rule requiring the + "password" and "totp" auth methods to be provided, + use: "--multi-factor-auth-rule password,totp". + May be provided multiple times to set different rule combinations. + .. option:: --enable Enable user (default) diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index ca85c5d8a8..cbc112a058 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -30,6 +30,114 @@ LOG = logging.getLogger(__name__) +def _get_options_for_user(identity_client, parsed_args): + options = {} + if parsed_args.ignore_lockout_failure_attempts: + options['ignore_lockout_failure_attempts'] = True + if parsed_args.no_ignore_lockout_failure_attempts: + options['ignore_lockout_failure_attempts'] = False + if parsed_args.ignore_password_expiry: + options['ignore_password_expiry'] = True + if parsed_args.no_ignore_password_expiry: + options['ignore_password_expiry'] = False + if parsed_args.ignore_change_password_upon_first_use: + options['ignore_change_password_upon_first_use'] = True + if parsed_args.no_ignore_change_password_upon_first_use: + options['ignore_change_password_upon_first_use'] = False + if parsed_args.enable_lock_password: + options['lock_password'] = True + if parsed_args.disable_lock_password: + options['lock_password'] = False + if parsed_args.enable_multi_factor_auth: + options['multi_factor_auth_enabled'] = True + if parsed_args.disable_multi_factor_auth: + options['multi_factor_auth_enabled'] = False + if parsed_args.multi_factor_auth_rule: + auth_rules = [rule.split(",") for rule in + parsed_args.multi_factor_auth_rule] + if auth_rules: + options['multi_factor_auth_rules'] = auth_rules + return options + + +def _add_user_options(parser): + # Add additional user options + + parser.add_argument( + '--ignore-lockout-failure-attempts', + action="store_true", + help=_('Opt into ignoring the number of times a user has ' + 'authenticated and locking out the user as a result'), + ) + parser.add_argument( + '--no-ignore-lockout-failure-attempts', + action="store_true", + help=_('Opt out of ignoring the number of times a user has ' + 'authenticated and locking out the user as a result'), + ) + parser.add_argument( + '--ignore-password-expiry', + action="store_true", + help=_('Opt into allowing user to continue using passwords that ' + 'may be expired'), + ) + parser.add_argument( + '--no-ignore-password-expiry', + action="store_true", + help=_('Opt out of allowing user to continue using passwords ' + 'that may be expired'), + ) + parser.add_argument( + '--ignore-change-password-upon-first-use', + action="store_true", + help=_('Control if a user should be forced to change their password ' + 'immediately after they log into keystone for the first time. ' + 'Opt into ignoring the user to change their password during ' + 'first time login in keystone'), + ) + parser.add_argument( + '--no-ignore-change-password-upon-first-use', + action="store_true", + help=_('Control if a user should be forced to change their password ' + 'immediately after they log into keystone for the first time. ' + 'Opt out of ignoring the user to change their password during ' + 'first time login in keystone'), + ) + parser.add_argument( + '--enable-lock-password', + action="store_true", + help=_('Disables the ability for a user to change its password ' + 'through self-service APIs'), + ) + parser.add_argument( + '--disable-lock-password', + action="store_true", + help=_('Enables the ability for a user to change its password ' + 'through self-service APIs'), + ) + parser.add_argument( + '--enable-multi-factor-auth', + action="store_true", + help=_('Enables the MFA (Multi Factor Auth)'), + ) + parser.add_argument( + '--disable-multi-factor-auth', + action="store_true", + help=_('Disables the MFA (Multi Factor Auth)'), + ) + parser.add_argument( + '--multi-factor-auth-rule', + metavar='', + action="append", + default=[], + help=_('Set multi-factor auth rules. For example, to set a rule ' + 'requiring the "password" and "totp" auth methods to be ' + 'provided, use: "--multi-factor-auth-rule password,totp". ' + 'May be provided multiple times to set different rule ' + 'combinations.') + ) + + class CreateUser(command.ShowOne): _description = _("Create new user") @@ -72,6 +180,8 @@ def get_parser(self, prog_name): metavar='', help=_('User description'), ) + _add_user_options(parser) + enable_group = parser.add_mutually_exclusive_group() enable_group.add_argument( '--enable', @@ -113,6 +223,7 @@ def take_action(self, parsed_args): if not parsed_args.password: LOG.warning(_("No password was supplied, authentication will fail " "when a user does not have a password.")) + options = _get_options_for_user(identity_client, parsed_args) try: user = identity_client.users.create( @@ -122,7 +233,8 @@ def take_action(self, parsed_args): password=parsed_args.password, email=parsed_args.email, description=parsed_args.description, - enabled=enabled + enabled=enabled, + options=options, ) except ks_exc.Conflict: if parsed_args.or_show: @@ -333,6 +445,8 @@ def get_parser(self, prog_name): metavar='', help=_('Set user description'), ) + _add_user_options(parser) + enable_group = parser.add_mutually_exclusive_group() enable_group.add_argument( '--enable', @@ -390,6 +504,10 @@ def take_action(self, parsed_args): if parsed_args.disable: kwargs['enabled'] = False + options = _get_options_for_user(identity_client, parsed_args) + if options: + kwargs['options'] = options + identity_client.users.update(user.id, **kwargs) diff --git a/openstackclient/tests/unit/identity/v3/fakes.py b/openstackclient/tests/unit/identity/v3/fakes.py index eb3ce2a356..58d5d14d04 100644 --- a/openstackclient/tests/unit/identity/v3/fakes.py +++ b/openstackclient/tests/unit/identity/v3/fakes.py @@ -108,6 +108,9 @@ "rules": MAPPING_RULES_2 } +mfa_opt1 = 'password,totp' +mfa_opt2 = 'password' + project_id = '8-9-64' project_name = 'beatles' project_description = 'Fab Four' diff --git a/openstackclient/tests/unit/identity/v3/test_user.py b/openstackclient/tests/unit/identity/v3/test_user.py index 4b14bca05a..c71435bacf 100644 --- a/openstackclient/tests/unit/identity/v3/test_user.py +++ b/openstackclient/tests/unit/identity/v3/test_user.py @@ -111,6 +111,7 @@ def test_user_create_no_options(self): 'description': None, 'domain': None, 'email': None, + 'options': {}, 'enabled': True, 'password': None, } @@ -150,6 +151,7 @@ def test_user_create_password(self): 'description': None, 'domain': None, 'email': None, + 'options': {}, 'enabled': True, 'password': 'secret', } @@ -190,6 +192,7 @@ def test_user_create_password_prompt(self): 'description': None, 'domain': None, 'email': None, + 'options': {}, 'enabled': True, 'password': 'abc123', } @@ -228,6 +231,7 @@ def test_user_create_email(self): 'domain': None, 'email': 'barney@example.com', 'enabled': True, + 'options': {}, 'password': None, } # UserManager.create(name=, domain=, project=, password=, email=, @@ -265,6 +269,7 @@ def test_user_create_project(self): 'domain': None, 'email': None, 'enabled': True, + 'options': {}, 'password': None, } # UserManager.create(name=, domain=, project=, password=, email=, @@ -311,6 +316,7 @@ def test_user_create_project_domain(self): 'description': None, 'domain': None, 'email': None, + 'options': {}, 'enabled': True, 'password': None, } @@ -356,6 +362,7 @@ def test_user_create_domain(self): 'description': None, 'domain': self.domain.id, 'email': None, + 'options': {}, 'enabled': True, 'password': None, } @@ -392,6 +399,7 @@ def test_user_create_enable(self): 'description': None, 'domain': None, 'email': None, + 'options': {}, 'enabled': True, 'password': None, } @@ -428,6 +436,7 @@ def test_user_create_disable(self): 'description': None, 'domain': None, 'email': None, + 'options': {}, 'enabled': False, 'password': None, } @@ -438,280 +447,1112 @@ def test_user_create_disable(self): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, data) + def test_user_create_ignore_lockout_failure_attempts(self): + arglist = [ + '--ignore-lockout-failure-attempts', + self.user.name, + ] + verifylist = [ + ('ignore_lockout_failure_attempts', True), + ('enable', False), + ('disable', False), + ('name', self.user.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) -class TestUserDelete(TestUser): - - user = identity_fakes.FakeUser.create_one_user() - - def setUp(self): - super(TestUserDelete, self).setUp() + # 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) - # This is the return value for utils.find_resource() - self.users_mock.get.return_value = self.user - self.users_mock.delete.return_value = None + # Set expected values + kwargs = { + 'name': self.user.name, + 'default_project': None, + 'description': None, + 'domain': None, + 'email': None, + 'enabled': True, + 'options': {'ignore_lockout_failure_attempts': True}, + 'password': None, + } + # UserManager.create(name=, domain=, project=, password=, email=, + # description=, enabled=, default_project=) + self.users_mock.create.assert_called_with( + **kwargs + ) - # Get the command object to test - self.cmd = user.DeleteUser(self.app, None) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) - def test_user_delete_no_options(self): + def test_user_create_no_ignore_lockout_failure_attempts(self): arglist = [ - self.user.id, + '--no-ignore-lockout-failure-attempts', + self.user.name, ] verifylist = [ - ('users', [self.user.id]), + ('no_ignore_lockout_failure_attempts', True), + ('enable', False), + ('disable', False), + ('name', self.user.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) + # 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.users_mock.delete.assert_called_with( - self.user.id, + # Set expected values + kwargs = { + 'name': self.user.name, + 'default_project': None, + 'description': None, + 'domain': None, + 'email': None, + 'enabled': True, + 'options': {'ignore_lockout_failure_attempts': False}, + 'password': None, + } + # UserManager.create(name=, domain=, project=, password=, email=, + # description=, enabled=, default_project=) + self.users_mock.create.assert_called_with( + **kwargs ) - self.assertIsNone(result) - @mock.patch.object(utils, 'find_resource') - def test_delete_multi_users_with_exception(self, find_mock): - find_mock.side_effect = [self.user, - exceptions.CommandError] + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) + + def test_user_create_ignore_password_expiry(self): arglist = [ - self.user.id, - 'unexist_user', + '--ignore-password-expiry', + self.user.name, ] verifylist = [ - ('users', arglist), + ('ignore_password_expiry', True), + ('enable', False), + ('disable', False), + ('name', self.user.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - try: - self.cmd.take_action(parsed_args) - self.fail('CommandError should be raised.') - except exceptions.CommandError as e: - self.assertEqual('1 of 2 users failed to delete.', - str(e)) + # 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) - find_mock.assert_any_call(self.users_mock, self.user.id) - find_mock.assert_any_call(self.users_mock, 'unexist_user') + # Set expected values + kwargs = { + 'name': self.user.name, + 'default_project': None, + 'description': None, + 'domain': None, + 'email': None, + 'enabled': True, + 'options': {'ignore_password_expiry': True}, + 'password': None, + } + # UserManager.create(name=, domain=, project=, password=, email=, + # description=, enabled=, default_project=) + self.users_mock.create.assert_called_with( + **kwargs + ) - self.assertEqual(2, find_mock.call_count) - self.users_mock.delete.assert_called_once_with(self.user.id) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) + def test_user_create_no_ignore_password_expiry(self): + arglist = [ + '--no-ignore-password-expiry', + self.user.name, + ] + verifylist = [ + ('no_ignore_password_expiry', True), + ('enable', False), + ('disable', False), + ('name', self.user.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) -class TestUserList(TestUser): + # 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) - domain = identity_fakes.FakeDomain.create_one_domain() - project = identity_fakes.FakeProject.create_one_project() - user = identity_fakes.FakeUser.create_one_user( - attrs={'domain_id': domain.id, - 'default_project_id': project.id} - ) - group = identity_fakes.FakeGroup.create_one_group() - role_assignment = ( - identity_fakes.FakeRoleAssignment.create_one_role_assignment( - attrs={'user': {'id': user.id}})) + # Set expected values + kwargs = { + 'name': self.user.name, + 'default_project': None, + 'description': None, + 'domain': None, + 'email': None, + 'enabled': True, + 'options': {'ignore_password_expiry': False}, + 'password': None, + } + # UserManager.create(name=, domain=, project=, password=, email=, + # description=, enabled=, default_project=) + self.users_mock.create.assert_called_with( + **kwargs + ) - columns = [ - 'ID', - 'Name' - ] - datalist = ( - ( - user.id, - user.name, - ), - ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) - def setUp(self): - super(TestUserList, self).setUp() + def test_user_create_ignore_change_password_upon_first_use(self): + arglist = [ + '--ignore-change-password-upon-first-use', + self.user.name, + ] + verifylist = [ + ('ignore_change_password_upon_first_use', True), + ('enable', False), + ('disable', False), + ('name', self.user.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.users_mock.get.return_value = self.user - self.users_mock.list.return_value = [self.user] - self.domains_mock.get.return_value = self.domain - self.groups_mock.get.return_value = self.group - self.projects_mock.get.return_value = self.project - self.role_assignments_mock.list.return_value = [self.role_assignment] + # 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) - # Get the command object to test - self.cmd = user.ListUser(self.app, None) + # Set expected values + kwargs = { + 'name': self.user.name, + 'default_project': None, + 'description': None, + 'domain': None, + 'email': None, + 'enabled': True, + 'options': {'ignore_change_password_upon_first_use': True}, + 'password': None, + } + # UserManager.create(name=, domain=, project=, password=, email=, + # description=, enabled=, default_project=) + self.users_mock.create.assert_called_with( + **kwargs + ) - def test_user_list_no_options(self): - arglist = [] - verifylist = [] + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) + + def test_user_create_no_ignore_change_password_upon_first_use(self): + arglist = [ + '--no-ignore-change-password-upon-first-use', + self.user.name, + ] + verifylist = [ + ('no_ignore_change_password_upon_first_use', True), + ('enable', False), + ('disable', False), + ('name', self.user.name), + ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # In base command class Lister in cliff, abstract method take_action() - # returns a tuple containing the column names and an iterable - # containing the data to be listed. + # 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) # Set expected values kwargs = { + 'name': self.user.name, + 'default_project': None, + 'description': None, 'domain': None, - 'group': None, + 'email': None, + 'enabled': True, + 'options': {'ignore_change_password_upon_first_use': False}, + 'password': None, } - - self.users_mock.list.assert_called_with( + # UserManager.create(name=, domain=, project=, password=, email=, + # description=, enabled=, default_project=) + self.users_mock.create.assert_called_with( **kwargs ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, tuple(data)) + self.assertEqual(self.datalist, data) + + def test_user_create_enables_lock_password(self): + arglist = [ + '--enable-lock-password', + self.user.name, + ] + verifylist = [ + ('enable_lock_password', True), + ('enable', False), + ('disable', False), + ('name', self.user.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) + + # Set expected values + kwargs = { + 'name': self.user.name, + 'default_project': None, + 'description': None, + 'domain': None, + 'email': None, + 'enabled': True, + 'options': {'lock_password': True}, + 'password': None, + } + # UserManager.create(name=, domain=, project=, password=, email=, + # description=, enabled=, default_project=) + self.users_mock.create.assert_called_with( + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) + + def test_user_create_disables_lock_password(self): + arglist = [ + '--disable-lock-password', + self.user.name, + ] + verifylist = [ + ('disable_lock_password', True), + ('enable', False), + ('disable', False), + ('name', self.user.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) + + # Set expected values + kwargs = { + 'name': self.user.name, + 'default_project': None, + 'description': None, + 'domain': None, + 'email': None, + 'enabled': True, + 'options': {'lock_password': False}, + 'password': None, + } + # UserManager.create(name=, domain=, project=, password=, email=, + # description=, enabled=, default_project=) + self.users_mock.create.assert_called_with( + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) + + def test_user_create_enable_multi_factor_auth(self): + arglist = [ + '--enable-multi-factor-auth', + self.user.name, + ] + verifylist = [ + ('enable_multi_factor_auth', True), + ('enable', False), + ('disable', False), + ('name', self.user.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) + + # Set expected values + kwargs = { + 'name': self.user.name, + 'default_project': None, + 'description': None, + 'domain': None, + 'email': None, + 'enabled': True, + 'options': {'multi_factor_auth_enabled': True}, + 'password': None, + } + # UserManager.create(name=, domain=, project=, password=, email=, + # description=, enabled=, default_project=) + self.users_mock.create.assert_called_with( + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) + + def test_user_create_disable_multi_factor_auth(self): + arglist = [ + '--disable-multi-factor-auth', + self.user.name, + ] + verifylist = [ + ('disable_multi_factor_auth', True), + ('enable', False), + ('disable', False), + ('name', self.user.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) + + # Set expected values + kwargs = { + 'name': self.user.name, + 'default_project': None, + 'description': None, + 'domain': None, + 'email': None, + 'enabled': True, + 'options': {'multi_factor_auth_enabled': False}, + 'password': None, + } + # UserManager.create(name=, domain=, project=, password=, email=, + # description=, enabled=, default_project=) + self.users_mock.create.assert_called_with( + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) + + def test_user_create_option_with_multi_factor_auth_rule(self): + arglist = [ + '--multi-factor-auth-rule', identity_fakes.mfa_opt1, + '--multi-factor-auth-rule', identity_fakes.mfa_opt2, + self.user.name, + ] + verifylist = [ + ('multi_factor_auth_rule', [identity_fakes.mfa_opt1, + identity_fakes.mfa_opt2]), + ('enable', False), + ('disable', False), + ('name', self.user.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) + + # Set expected values + kwargs = { + 'name': self.user.name, + 'default_project': None, + 'description': None, + 'domain': None, + 'email': None, + 'enabled': True, + 'options': {'multi_factor_auth_rules': [["password", "totp"], + ["password"]]}, + 'password': None, + } + # UserManager.create(name=, domain=, project=, password=, email=, + # description=, enabled=, default_project=) + self.users_mock.create.assert_called_with( + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) + + def test_user_create_with_multiple_options(self): + arglist = [ + '--ignore-password-expiry', + '--disable-multi-factor-auth', + '--multi-factor-auth-rule', identity_fakes.mfa_opt1, + self.user.name, + ] + verifylist = [ + ('ignore_password_expiry', True), + ('disable_multi_factor_auth', True), + ('multi_factor_auth_rule', [identity_fakes.mfa_opt1]), + ('enable', False), + ('disable', False), + ('name', self.user.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) + + # Set expected values + kwargs = { + 'name': self.user.name, + 'default_project': None, + 'description': None, + 'domain': None, + 'email': None, + 'enabled': True, + 'options': {'ignore_password_expiry': True, + 'multi_factor_auth_enabled': False, + 'multi_factor_auth_rules': [["password", "totp"]]}, + 'password': None, + } + # UserManager.create(name=, domain=, project=, password=, email=, + # description=, enabled=, default_project=) + self.users_mock.create.assert_called_with( + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) + + +class TestUserDelete(TestUser): + + user = identity_fakes.FakeUser.create_one_user() + + def setUp(self): + super(TestUserDelete, self).setUp() + + # This is the return value for utils.find_resource() + self.users_mock.get.return_value = self.user + self.users_mock.delete.return_value = None + + # Get the command object to test + self.cmd = user.DeleteUser(self.app, None) + + def test_user_delete_no_options(self): + arglist = [ + self.user.id, + ] + verifylist = [ + ('users', [self.user.id]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.users_mock.delete.assert_called_with( + self.user.id, + ) + self.assertIsNone(result) + + @mock.patch.object(utils, 'find_resource') + def test_delete_multi_users_with_exception(self, find_mock): + find_mock.side_effect = [self.user, + exceptions.CommandError] + arglist = [ + self.user.id, + 'unexist_user', + ] + verifylist = [ + ('users', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 users failed to delete.', + str(e)) + + find_mock.assert_any_call(self.users_mock, self.user.id) + find_mock.assert_any_call(self.users_mock, 'unexist_user') + + self.assertEqual(2, find_mock.call_count) + self.users_mock.delete.assert_called_once_with(self.user.id) + + +class TestUserList(TestUser): + + domain = identity_fakes.FakeDomain.create_one_domain() + project = identity_fakes.FakeProject.create_one_project() + user = identity_fakes.FakeUser.create_one_user( + attrs={'domain_id': domain.id, + 'default_project_id': project.id} + ) + group = identity_fakes.FakeGroup.create_one_group() + role_assignment = ( + identity_fakes.FakeRoleAssignment.create_one_role_assignment( + attrs={'user': {'id': user.id}})) + + columns = [ + 'ID', + 'Name' + ] + datalist = ( + ( + user.id, + user.name, + ), + ) + + def setUp(self): + super(TestUserList, self).setUp() + + self.users_mock.get.return_value = self.user + self.users_mock.list.return_value = [self.user] + self.domains_mock.get.return_value = self.domain + self.groups_mock.get.return_value = self.group + self.projects_mock.get.return_value = self.project + self.role_assignments_mock.list.return_value = [self.role_assignment] + + # Get the command object to test + self.cmd = user.ListUser(self.app, None) + + def test_user_list_no_options(self): + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'domain': None, + 'group': None, + } + + self.users_mock.list.assert_called_with( + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) + + def test_user_list_domain(self): + arglist = [ + '--domain', self.domain.id, + ] + verifylist = [ + ('domain', self.domain.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'domain': self.domain.id, + 'group': None, + } + + self.users_mock.list.assert_called_with( + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) + + def test_user_list_group(self): + arglist = [ + '--group', self.group.name, + ] + verifylist = [ + ('group', self.group.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'domain': None, + 'group': self.group.id, + } + + self.users_mock.list.assert_called_with( + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) + + def test_user_list_long(self): + arglist = [ + '--long', + ] + verifylist = [ + ('long', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'domain': None, + 'group': None, + } + + self.users_mock.list.assert_called_with( + **kwargs + ) + + collist = [ + 'ID', + 'Name', + 'Project', + 'Domain', + 'Description', + 'Email', + 'Enabled', + ] + self.assertEqual(collist, columns) + datalist = ( + ( + self.user.id, + self.user.name, + self.project.id, + self.domain.id, + '', + self.user.email, + True, + ), + ) + self.assertEqual(datalist, tuple(data)) + + def test_user_list_project(self): + arglist = [ + '--project', self.project.name, + ] + verifylist = [ + ('project', self.project.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + kwargs = { + 'project': self.project.id, + } + + self.role_assignments_mock.list.assert_called_with(**kwargs) + self.users_mock.get.assert_called_with(self.user.id) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) + + +class TestUserSet(TestUser): + + project = identity_fakes.FakeProject.create_one_project() + domain = identity_fakes.FakeDomain.create_one_domain() + user = identity_fakes.FakeUser.create_one_user( + attrs={'default_project_id': project.id} + ) + user2 = identity_fakes.FakeUser.create_one_user( + attrs={'default_project_id': project.id, + 'domain_id': domain.id} + ) + + def setUp(self): + super(TestUserSet, self).setUp() + + self.projects_mock.get.return_value = self.project + self.users_mock.get.return_value = self.user + self.users_mock.update.return_value = self.user + + # Get the command object to test + self.cmd = user.SetUser(self.app, None) + + def test_user_set_no_options(self): + arglist = [ + self.user.name, + ] + verifylist = [ + ('name', None), + ('password', None), + ('email', None), + ('project', None), + ('enable', False), + ('disable', False), + ('user', self.user.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.assertIsNone(result) + + def test_user_set_name(self): + arglist = [ + '--name', 'qwerty', + self.user.name, + ] + verifylist = [ + ('name', 'qwerty'), + ('password', None), + ('email', None), + ('project', None), + ('enable', False), + ('disable', False), + ('user', self.user.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'enabled': True, + 'name': 'qwerty', + } + # UserManager.update(user, name=, domain=, project=, password=, + # email=, description=, enabled=, default_project=) + self.users_mock.update.assert_called_with( + self.user.id, + **kwargs + ) + self.assertIsNone(result) + + def test_user_set_specify_domain(self): + arglist = [ + '--name', 'qwerty', + '--domain', self.domain.id, + self.user2.name + ] + verifylist = [ + ('name', 'qwerty'), + ('password', None), + ('domain', self.domain.id), + ('email', None), + ('project', None), + ('enable', False), + ('disable', False), + ('user', self.user2.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + kwargs = { + 'enabled': True, + 'name': 'qwerty' + } + + self.users_mock.update.assert_called_with( + self.user.id, + **kwargs + ) + self.assertIsNone(result) + + def test_user_set_password(self): + arglist = [ + '--password', 'secret', + self.user.name, + ] + verifylist = [ + ('name', None), + ('password', 'secret'), + ('password_prompt', False), + ('email', None), + ('project', None), + ('enable', False), + ('disable', False), + ('user', self.user.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'enabled': True, + 'password': 'secret', + } + # UserManager.update(user, name=, domain=, project=, password=, + # email=, description=, enabled=, default_project=) + self.users_mock.update.assert_called_with( + self.user.id, + **kwargs + ) + self.assertIsNone(result) + + def test_user_set_password_prompt(self): + arglist = [ + '--password-prompt', + self.user.name, + ] + verifylist = [ + ('name', None), + ('password', None), + ('password_prompt', True), + ('email', None), + ('project', None), + ('enable', False), + ('disable', False), + ('user', self.user.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + mocker = mock.Mock() + mocker.return_value = 'abc123' + with mock.patch("osc_lib.utils.get_password", mocker): + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'enabled': True, + 'password': 'abc123', + } + # UserManager.update(user, name=, domain=, project=, password=, + # email=, description=, enabled=, default_project=) + self.users_mock.update.assert_called_with( + self.user.id, + **kwargs + ) + self.assertIsNone(result) + + def test_user_set_email(self): + arglist = [ + '--email', 'barney@example.com', + self.user.name, + ] + verifylist = [ + ('name', None), + ('password', None), + ('email', 'barney@example.com'), + ('project', None), + ('enable', False), + ('disable', False), + ('user', self.user.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'enabled': True, + 'email': 'barney@example.com', + } + # UserManager.update(user, name=, domain=, project=, password=, + # email=, description=, enabled=, default_project=) + self.users_mock.update.assert_called_with( + self.user.id, + **kwargs + ) + self.assertIsNone(result) + + def test_user_set_project(self): + arglist = [ + '--project', self.project.id, + self.user.name, + ] + verifylist = [ + ('name', None), + ('password', None), + ('email', None), + ('project', self.project.id), + ('enable', False), + ('disable', False), + ('user', self.user.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'enabled': True, + 'default_project': self.project.id, + } + # UserManager.update(user, name=, domain=, project=, password=, + # email=, description=, enabled=, default_project=) + self.users_mock.update.assert_called_with( + self.user.id, + **kwargs + ) + self.assertIsNone(result) + + def test_user_set_project_domain(self): + arglist = [ + '--project', self.project.id, + '--project-domain', self.project.domain_id, + self.user.name, + ] + verifylist = [ + ('name', None), + ('password', None), + ('email', None), + ('project', self.project.id), + ('project_domain', self.project.domain_id), + ('enable', False), + ('disable', False), + ('user', self.user.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'enabled': True, + 'default_project': self.project.id, + } + # UserManager.update(user, name=, domain=, project=, password=, + # email=, description=, enabled=, default_project=) + self.users_mock.update.assert_called_with( + self.user.id, + **kwargs + ) + self.assertIsNone(result) - def test_user_list_domain(self): + def test_user_set_enable(self): arglist = [ - '--domain', self.domain.id, + '--enable', + self.user.name, ] verifylist = [ - ('domain', self.domain.id), + ('name', None), + ('password', None), + ('email', None), + ('project', None), + ('enable', True), + ('disable', False), + ('user', self.user.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # In base command class Lister in cliff, abstract method take_action() - # returns a tuple containing the column names and an iterable - # containing the data to be listed. - columns, data = self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { - 'domain': self.domain.id, - 'group': None, + 'enabled': True, } - - self.users_mock.list.assert_called_with( + # UserManager.update(user, name=, domain=, project=, password=, + # email=, description=, enabled=, default_project=) + self.users_mock.update.assert_called_with( + self.user.id, **kwargs ) + self.assertIsNone(result) - self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, tuple(data)) - - def test_user_list_group(self): + def test_user_set_disable(self): arglist = [ - '--group', self.group.name, + '--disable', + self.user.name, ] verifylist = [ - ('group', self.group.name), + ('name', None), + ('password', None), + ('email', None), + ('project', None), + ('enable', False), + ('disable', True), + ('user', self.user.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # In base command class Lister in cliff, abstract method take_action() - # returns a tuple containing the column names and an iterable - # containing the data to be listed. - columns, data = self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { - 'domain': None, - 'group': self.group.id, + 'enabled': False, } - - self.users_mock.list.assert_called_with( + # UserManager.update(user, name=, domain=, project=, password=, + # email=, description=, enabled=, default_project=) + self.users_mock.update.assert_called_with( + self.user.id, **kwargs ) + self.assertIsNone(result) - self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, tuple(data)) - - def test_user_list_long(self): + def test_user_set_ignore_lockout_failure_attempts(self): arglist = [ - '--long', + '--ignore-lockout-failure-attempts', + self.user.name, ] verifylist = [ - ('long', True), + ('name', None), + ('password', None), + ('email', None), + ('ignore_lockout_failure_attempts', True), + ('project', None), + ('enable', False), + ('disable', False), + ('user', self.user.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # In base command class Lister in cliff, abstract method take_action() - # returns a tuple containing the column names and an iterable - # containing the data to be listed. - columns, data = self.cmd.take_action(parsed_args) - + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { - 'domain': None, - 'group': None, + 'enabled': True, + 'options': {'ignore_lockout_failure_attempts': True}, } - - self.users_mock.list.assert_called_with( + # UserManager.update(user, name=, domain=, project=, password=, + # email=, description=, enabled=, default_project=) + self.users_mock.update.assert_called_with( + self.user.id, **kwargs ) + self.assertIsNone(result) - collist = [ - 'ID', - 'Name', - 'Project', - 'Domain', - 'Description', - 'Email', - 'Enabled', - ] - self.assertEqual(collist, columns) - datalist = ( - ( - self.user.id, - self.user.name, - self.project.id, - self.domain.id, - '', - self.user.email, - True, - ), - ) - self.assertEqual(datalist, tuple(data)) - - def test_user_list_project(self): + def test_user_set_no_ignore_lockout_failure_attempts(self): arglist = [ - '--project', self.project.name, + '--no-ignore-lockout-failure-attempts', + self.user.name, ] verifylist = [ - ('project', self.project.name), + ('name', None), + ('password', None), + ('email', None), + ('no_ignore_lockout_failure_attempts', True), + ('project', None), + ('enable', False), + ('disable', False), + ('user', self.user.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - # In base command class Lister in cliff, abstract method take_action() - # returns a tuple containing the column names and an iterable - # containing the data to be listed. - columns, data = self.cmd.take_action(parsed_args) - + result = self.cmd.take_action(parsed_args) + # Set expected values kwargs = { - 'project': self.project.id, + 'enabled': True, + 'options': {'ignore_lockout_failure_attempts': False}, } + # UserManager.update(user, name=, domain=, project=, password=, + # email=, description=, enabled=, default_project=) + self.users_mock.update.assert_called_with( + self.user.id, + **kwargs + ) + self.assertIsNone(result) - self.role_assignments_mock.list.assert_called_with(**kwargs) - self.users_mock.get.assert_called_with(self.user.id) - - self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, tuple(data)) - - -class TestUserSet(TestUser): - - project = identity_fakes.FakeProject.create_one_project() - domain = identity_fakes.FakeDomain.create_one_domain() - user = identity_fakes.FakeUser.create_one_user( - attrs={'default_project_id': project.id} - ) - user2 = identity_fakes.FakeUser.create_one_user( - attrs={'default_project_id': project.id, - 'domain_id': domain.id} - ) - - def setUp(self): - super(TestUserSet, self).setUp() - - self.projects_mock.get.return_value = self.project - self.users_mock.get.return_value = self.user - self.users_mock.update.return_value = self.user - - # Get the command object to test - self.cmd = user.SetUser(self.app, None) - - def test_user_set_no_options(self): + def test_user_set_ignore_password_expiry(self): arglist = [ + '--ignore-password-expiry', self.user.name, ] verifylist = [ ('name', None), ('password', None), ('email', None), + ('ignore_password_expiry', True), ('project', None), ('enable', False), ('disable', False), @@ -720,18 +1561,29 @@ def test_user_set_no_options(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - + # Set expected values + kwargs = { + 'enabled': True, + 'options': {'ignore_password_expiry': True}, + } + # UserManager.update(user, name=, domain=, project=, password=, + # email=, description=, enabled=, default_project=) + self.users_mock.update.assert_called_with( + self.user.id, + **kwargs + ) self.assertIsNone(result) - def test_user_set_name(self): + def test_user_set_no_ignore_password_expiry(self): arglist = [ - '--name', 'qwerty', + '--no-ignore-password-expiry', self.user.name, ] verifylist = [ - ('name', 'qwerty'), + ('name', None), ('password', None), ('email', None), + ('no_ignore_password_expiry', True), ('project', None), ('enable', False), ('disable', False), @@ -740,11 +1592,10 @@ def test_user_set_name(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - # Set expected values kwargs = { 'enabled': True, - 'name': 'qwerty', + 'options': {'ignore_password_expiry': False}, } # UserManager.update(user, name=, domain=, project=, password=, # email=, description=, enabled=, default_project=) @@ -754,47 +1605,47 @@ def test_user_set_name(self): ) self.assertIsNone(result) - def test_user_set_specify_domain(self): + def test_user_set_ignore_change_password_upon_first_use(self): arglist = [ - '--name', 'qwerty', - '--domain', self.domain.id, - self.user2.name + '--ignore-change-password-upon-first-use', + self.user.name, ] verifylist = [ - ('name', 'qwerty'), + ('name', None), ('password', None), - ('domain', self.domain.id), ('email', None), + ('ignore_change_password_upon_first_use', True), ('project', None), ('enable', False), ('disable', False), - ('user', self.user2.name), + ('user', self.user.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - + # Set expected values kwargs = { 'enabled': True, - 'name': 'qwerty' + 'options': {'ignore_change_password_upon_first_use': True}, } - + # UserManager.update(user, name=, domain=, project=, password=, + # email=, description=, enabled=, default_project=) self.users_mock.update.assert_called_with( self.user.id, **kwargs ) self.assertIsNone(result) - def test_user_set_password(self): + def test_user_set_no_ignore_change_password_upon_first_use(self): arglist = [ - '--password', 'secret', + '--no-ignore-change-password-upon-first-use', self.user.name, ] verifylist = [ ('name', None), - ('password', 'secret'), - ('password_prompt', False), + ('password', None), ('email', None), + ('no_ignore_change_password_upon_first_use', True), ('project', None), ('enable', False), ('disable', False), @@ -803,11 +1654,10 @@ def test_user_set_password(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - # Set expected values kwargs = { 'enabled': True, - 'password': 'secret', + 'options': {'ignore_change_password_upon_first_use': False}, } # UserManager.update(user, name=, domain=, project=, password=, # email=, description=, enabled=, default_project=) @@ -817,16 +1667,16 @@ def test_user_set_password(self): ) self.assertIsNone(result) - def test_user_set_password_prompt(self): + def test_user_set_enable_lock_password(self): arglist = [ - '--password-prompt', + '--enable-lock-password', self.user.name, ] verifylist = [ ('name', None), ('password', None), - ('password_prompt', True), ('email', None), + ('enable_lock_password', True), ('project', None), ('enable', False), ('disable', False), @@ -834,15 +1684,11 @@ def test_user_set_password_prompt(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - mocker = mock.Mock() - mocker.return_value = 'abc123' - with mock.patch("osc_lib.utils.get_password", mocker): - result = self.cmd.take_action(parsed_args) - + result = self.cmd.take_action(parsed_args) # Set expected values kwargs = { 'enabled': True, - 'password': 'abc123', + 'options': {'lock_password': True}, } # UserManager.update(user, name=, domain=, project=, password=, # email=, description=, enabled=, default_project=) @@ -852,15 +1698,16 @@ def test_user_set_password_prompt(self): ) self.assertIsNone(result) - def test_user_set_email(self): + def test_user_set_disable_lock_password(self): arglist = [ - '--email', 'barney@example.com', + '--disable-lock-password', self.user.name, ] verifylist = [ ('name', None), ('password', None), - ('email', 'barney@example.com'), + ('email', None), + ('disable_lock_password', True), ('project', None), ('enable', False), ('disable', False), @@ -869,11 +1716,10 @@ def test_user_set_email(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - # Set expected values kwargs = { 'enabled': True, - 'email': 'barney@example.com', + 'options': {'lock_password': False}, } # UserManager.update(user, name=, domain=, project=, password=, # email=, description=, enabled=, default_project=) @@ -883,16 +1729,17 @@ def test_user_set_email(self): ) self.assertIsNone(result) - def test_user_set_project(self): + def test_user_set_enable_multi_factor_auth(self): arglist = [ - '--project', self.project.id, + '--enable-multi-factor-auth', self.user.name, ] verifylist = [ ('name', None), ('password', None), ('email', None), - ('project', self.project.id), + ('enable_multi_factor_auth', True), + ('project', None), ('enable', False), ('disable', False), ('user', self.user.name), @@ -900,11 +1747,10 @@ def test_user_set_project(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - # Set expected values kwargs = { 'enabled': True, - 'default_project': self.project.id, + 'options': {'multi_factor_auth_enabled': True}, } # UserManager.update(user, name=, domain=, project=, password=, # email=, description=, enabled=, default_project=) @@ -914,18 +1760,17 @@ def test_user_set_project(self): ) self.assertIsNone(result) - def test_user_set_project_domain(self): + def test_user_set_disable_multi_factor_auth(self): arglist = [ - '--project', self.project.id, - '--project-domain', self.project.domain_id, + '--disable-multi-factor-auth', self.user.name, ] verifylist = [ ('name', None), ('password', None), ('email', None), - ('project', self.project.id), - ('project_domain', self.project.domain_id), + ('disable_multi_factor_auth', True), + ('project', None), ('enable', False), ('disable', False), ('user', self.user.name), @@ -933,11 +1778,10 @@ def test_user_set_project_domain(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - # Set expected values kwargs = { 'enabled': True, - 'default_project': self.project.id, + 'options': {'multi_factor_auth_enabled': False}, } # UserManager.update(user, name=, domain=, project=, password=, # email=, description=, enabled=, default_project=) @@ -947,28 +1791,29 @@ def test_user_set_project_domain(self): ) self.assertIsNone(result) - def test_user_set_enable(self): + def test_user_set_option_multi_factor_auth_rule(self): arglist = [ - '--enable', + '--multi-factor-auth-rule', identity_fakes.mfa_opt1, self.user.name, ] verifylist = [ ('name', None), ('password', None), ('email', None), + ('multi_factor_auth_rule', [identity_fakes.mfa_opt1]), ('project', None), - ('enable', True), + ('enable', False), ('disable', False), ('user', self.user.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - # Set expected values kwargs = { 'enabled': True, - } + 'options': {'multi_factor_auth_rules': [["password", "totp"]]}} + # UserManager.update(user, name=, domain=, project=, password=, # email=, description=, enabled=, default_project=) self.users_mock.update.assert_called_with( @@ -977,28 +1822,35 @@ def test_user_set_enable(self): ) self.assertIsNone(result) - def test_user_set_disable(self): + def test_user_set_with_multiple_options(self): arglist = [ - '--disable', + '--ignore-password-expiry', + '--enable-multi-factor-auth', + '--multi-factor-auth-rule', identity_fakes.mfa_opt1, self.user.name, ] verifylist = [ ('name', None), ('password', None), ('email', None), + ('ignore_password_expiry', True), + ('enable_multi_factor_auth', True), + ('multi_factor_auth_rule', [identity_fakes.mfa_opt1]), ('project', None), ('enable', False), - ('disable', True), + ('disable', False), ('user', self.user.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - # Set expected values kwargs = { - 'enabled': False, - } + 'enabled': True, + 'options': {'ignore_password_expiry': True, + 'multi_factor_auth_enabled': True, + 'multi_factor_auth_rules': [["password", "totp"]]}} + # UserManager.update(user, name=, domain=, project=, password=, # email=, description=, enabled=, default_project=) self.users_mock.update.assert_called_with( diff --git a/releasenotes/notes/add_options_to_user_create_and_set-302401520f36d153.yaml b/releasenotes/notes/add_options_to_user_create_and_set-302401520f36d153.yaml new file mode 100644 index 0000000000..698a6f189d --- /dev/null +++ b/releasenotes/notes/add_options_to_user_create_and_set-302401520f36d153.yaml @@ -0,0 +1,19 @@ +--- +features: + - | + Added the below mentioned parameters to the user create and set commands. + + * --ignore-lockout-failure-attempts + * --no-ignore-lockout-failure-attempts + * --ignore-password-expiry + * --no-ignore-password-expiry + * --ignore-change-password-upon-first-use + * --no-ignore-change-password-upon-first-use + * --enable-lock-password + * --disable-lock-password + * --enable-multi-factor-auth + * --disable-multi-factor-auth + * --multi-factor-auth-rule + + This will now allow users to set user options via CLI. + From dba57c85d5536369d600ae98599c08b921713bd5 Mon Sep 17 00:00:00 2001 From: Bence Romsics Date: Wed, 31 Jul 2019 15:44:27 +0200 Subject: [PATCH 2185/3095] Add command: router add/remove route --route Add commands to osc to call the two new API methods introduced by new Neutron extension: extraroute-atomic. Bump our openstacksdk requirement to >=0.38.0 which contains the corresponding sdk change. The lower-constraints of dogpile.cache and keystoneauth1 are bumped because of requirements bumps in openstacksdk. The lower-constraint of decorator is bumped because of problem already fixed by amotoki here: https://review.opendev.org/701706 Change-Id: Ia9b9c216f1d1161ebedac31594a2c464d77f4ae2 Depends-On: https://review.opendev.org/674324 Partial-Bug: #1826396 (rfe) Related-Change: https://review.opendev.org/655680 (spec) --- lower-constraints.txt | 8 +- openstackclient/network/v2/router.py | 97 +++++++++++- .../functional/network/v2/test_router.py | 43 ++++++ .../tests/unit/network/v2/test_router.py | 140 ++++++++++++++++++ ...er-extraroute-atomic-d6d406ffb15695f2.yaml | 12 ++ requirements.txt | 2 +- setup.cfg | 2 + 7 files changed, 296 insertions(+), 8 deletions(-) create mode 100644 releasenotes/notes/router-extraroute-atomic-d6d406ffb15695f2.yaml diff --git a/lower-constraints.txt b/lower-constraints.txt index 1fa30674e1..83e19fe60f 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -12,11 +12,11 @@ contextlib2==0.4.0 coverage==4.0 cryptography==2.1 debtcollector==1.2.0 -decorator==3.4.0 +decorator==4.4.1 deprecation==1.0 docker-pycreds==0.2.1 docker==2.4.2 -dogpile.cache==0.6.2 +dogpile.cache==0.6.5 eventlet==0.18.2 extras==1.0.0 fasteners==0.7.0 @@ -38,7 +38,7 @@ jmespath==0.9.0 jsonpatch==1.16 jsonpointer==1.13 jsonschema==2.6.0 -keystoneauth1==3.16.0 +keystoneauth1==3.18.0 kombu==4.0.0 linecache2==1.0.0 MarkupSafe==1.1.0 @@ -50,7 +50,7 @@ msgpack-python==0.4.0 munch==2.1.0 netaddr==0.7.18 netifaces==0.10.4 -openstacksdk==0.36.0 +openstacksdk==0.38.0 os-client-config==1.28.0 os-service-types==1.7.0 os-testr==1.0.0 diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 464dbbec78..81b81f98b5 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -168,6 +168,93 @@ def take_action(self, parsed_args): subnet_id=subnet.id) +class AddExtraRoutesToRouter(command.ShowOne): + _description = _("Add extra static routes to a router's routing table.") + + def get_parser(self, prog_name): + parser = super(AddExtraRoutesToRouter, self).get_parser(prog_name) + parser.add_argument( + 'router', + metavar='', + help=_("Router to which extra static routes " + "will be added (name or ID).") + ) + parser.add_argument( + '--route', + metavar='destination=,gateway=', + action=parseractions.MultiKeyValueAction, + dest='routes', + default=[], + required_keys=['destination', 'gateway'], + help=_("Add extra static route to the router. " + "destination: destination subnet (in CIDR notation), " + "gateway: nexthop IP address. " + "Repeat option to add multiple routes. " + "Trying to add a route that's already present " + "(exactly, including destination and nexthop) " + "in the routing table is allowed and is considered " + "a successful operation.") + ) + return parser + + def take_action(self, parsed_args): + if parsed_args.routes is not None: + for route in parsed_args.routes: + route['nexthop'] = route.pop('gateway') + client = self.app.client_manager.network + router_obj = client.add_extra_routes_to_router( + client.find_router(parsed_args.router, ignore_missing=False), + body={'router': {'routes': parsed_args.routes}}) + display_columns, columns = _get_columns(router_obj) + data = utils.get_item_properties( + router_obj, columns, formatters=_formatters) + return (display_columns, data) + + +class RemoveExtraRoutesFromRouter(command.ShowOne): + _description = _( + "Remove extra static routes from a router's routing table.") + + def get_parser(self, prog_name): + parser = super(RemoveExtraRoutesFromRouter, self).get_parser(prog_name) + parser.add_argument( + 'router', + metavar='', + help=_("Router from which extra static routes " + "will be removed (name or ID).") + ) + parser.add_argument( + '--route', + metavar='destination=,gateway=', + action=parseractions.MultiKeyValueAction, + dest='routes', + default=[], + required_keys=['destination', 'gateway'], + help=_("Remove extra static route from the router. " + "destination: destination subnet (in CIDR notation), " + "gateway: nexthop IP address. " + "Repeat option to remove multiple routes. " + "Trying to remove a route that's already missing " + "(fully, including destination and nexthop) " + "from the routing table is allowed and is considered " + "a successful operation.") + ) + return parser + + def take_action(self, parsed_args): + if parsed_args.routes is not None: + for route in parsed_args.routes: + route['nexthop'] = route.pop('gateway') + client = self.app.client_manager.network + router_obj = client.remove_extra_routes_from_router( + client.find_router(parsed_args.router, ignore_missing=False), + body={'router': {'routes': parsed_args.routes}}) + display_columns, columns = _get_columns(router_obj) + data = utils.get_item_properties( + router_obj, columns, formatters=_formatters) + return (display_columns, data) + + # TODO(yanxing'an): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. class CreateRouter(command.ShowOne): @@ -540,17 +627,21 @@ def get_parser(self, prog_name): dest='routes', default=None, required_keys=['destination', 'gateway'], - help=_("Routes associated with the router " + help=_("Add routes to the router " "destination: destination subnet (in CIDR notation) " "gateway: nexthop IP address " - "(repeat option to set multiple routes)") + "(repeat option to add multiple routes). " + "This is deprecated in favor of 'router add/remove route' " + "since it is prone to race conditions between concurrent " + "clients when not used together with --no-route to " + "overwrite the current value of 'routes'.") ) parser.add_argument( '--no-route', action='store_true', help=_("Clear routes associated with the router. " "Specify both --route and --no-route to overwrite " - "current value of route.") + "current value of routes.") ) routes_ha = parser.add_mutually_exclusive_group() routes_ha.add_argument( diff --git a/openstackclient/tests/functional/network/v2/test_router.py b/openstackclient/tests/functional/network/v2/test_router.py index 05aad7a013..0769dca6ff 100644 --- a/openstackclient/tests/functional/network/v2/test_router.py +++ b/openstackclient/tests/functional/network/v2/test_router.py @@ -261,3 +261,46 @@ def test_router_set_show_unset(self): new_name )) self.assertIsNone(cmd_output["external_gateway_info"]) + + def test_router_add_remove_route(self): + network_name = uuid.uuid4().hex + subnet_name = uuid.uuid4().hex + router_name = uuid.uuid4().hex + + self.openstack('network create %s' % network_name) + self.addCleanup(self.openstack, 'network delete %s' % network_name) + + self.openstack( + 'subnet create %s ' + '--network %s --subnet-range 10.0.0.0/24' % ( + subnet_name, network_name)) + + self.openstack('router create %s' % router_name) + self.addCleanup(self.openstack, 'router delete %s' % router_name) + + self.openstack('router add subnet %s %s' % (router_name, subnet_name)) + self.addCleanup(self.openstack, 'router remove subnet %s %s' % ( + router_name, subnet_name)) + + out1 = json.loads(self.openstack( + 'router add route -f json %s ' + '--route destination=10.0.10.0/24,gateway=10.0.0.10' % + router_name)), + self.assertEqual(1, len(out1[0]['routes'])) + + self.addCleanup( + self.openstack, 'router set %s --no-route' % router_name) + + out2 = json.loads(self.openstack( + 'router add route -f json %s ' + '--route destination=10.0.10.0/24,gateway=10.0.0.10 ' + '--route destination=10.0.11.0/24,gateway=10.0.0.11' % + router_name)), + self.assertEqual(2, len(out2[0]['routes'])) + + out3 = json.loads(self.openstack( + 'router remove route -f json %s ' + '--route destination=10.0.11.0/24,gateway=10.0.0.11 ' + '--route destination=10.0.12.0/24,gateway=10.0.0.12' % + router_name)), + self.assertEqual(1, len(out3[0]['routes'])) diff --git a/openstackclient/tests/unit/network/v2/test_router.py b/openstackclient/tests/unit/network/v2/test_router.py index 38861b0ad5..09b4957cce 100644 --- a/openstackclient/tests/unit/network/v2/test_router.py +++ b/openstackclient/tests/unit/network/v2/test_router.py @@ -776,6 +776,146 @@ def test_remove_subnet_required_options(self): self.assertIsNone(result) +class TestAddExtraRoutesToRouter(TestRouter): + + _router = network_fakes.FakeRouter.create_one_router() + + def setUp(self): + super(TestAddExtraRoutesToRouter, self).setUp() + self.network.add_extra_routes_to_router = mock.Mock( + return_value=self._router) + self.cmd = router.AddExtraRoutesToRouter(self.app, self.namespace) + self.network.find_router = mock.Mock(return_value=self._router) + + def test_add_no_extra_route(self): + arglist = [ + self._router.id, + ] + verifylist = [ + ('router', self._router.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.network.add_extra_routes_to_router.assert_called_with( + self._router, body={'router': {'routes': []}}) + self.assertEqual(2, len(result)) + + def test_add_one_extra_route(self): + arglist = [ + self._router.id, + '--route', 'destination=dst1,gateway=gw1', + ] + verifylist = [ + ('router', self._router.id), + ('routes', [{'destination': 'dst1', 'gateway': 'gw1'}]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.network.add_extra_routes_to_router.assert_called_with( + self._router, body={'router': {'routes': [ + {'destination': 'dst1', 'nexthop': 'gw1'}, + ]}}) + self.assertEqual(2, len(result)) + + def test_add_multiple_extra_routes(self): + arglist = [ + self._router.id, + '--route', 'destination=dst1,gateway=gw1', + '--route', 'destination=dst2,gateway=gw2', + ] + verifylist = [ + ('router', self._router.id), + ('routes', [ + {'destination': 'dst1', 'gateway': 'gw1'}, + {'destination': 'dst2', 'gateway': 'gw2'}, + ]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.network.add_extra_routes_to_router.assert_called_with( + self._router, body={'router': {'routes': [ + {'destination': 'dst1', 'nexthop': 'gw1'}, + {'destination': 'dst2', 'nexthop': 'gw2'}, + ]}}) + self.assertEqual(2, len(result)) + + +class TestRemoveExtraRoutesFromRouter(TestRouter): + + _router = network_fakes.FakeRouter.create_one_router() + + def setUp(self): + super(TestRemoveExtraRoutesFromRouter, self).setUp() + self.network.remove_extra_routes_from_router = mock.Mock( + return_value=self._router) + self.cmd = router.RemoveExtraRoutesFromRouter(self.app, self.namespace) + self.network.find_router = mock.Mock(return_value=self._router) + + def test_remove_no_extra_route(self): + arglist = [ + self._router.id, + ] + verifylist = [ + ('router', self._router.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.network.remove_extra_routes_from_router.assert_called_with( + self._router, body={'router': {'routes': []}}) + self.assertEqual(2, len(result)) + + def test_remove_one_extra_route(self): + arglist = [ + self._router.id, + '--route', 'destination=dst1,gateway=gw1', + ] + verifylist = [ + ('router', self._router.id), + ('routes', [{'destination': 'dst1', 'gateway': 'gw1'}]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.network.remove_extra_routes_from_router.assert_called_with( + self._router, body={'router': {'routes': [ + {'destination': 'dst1', 'nexthop': 'gw1'}, + ]}}) + self.assertEqual(2, len(result)) + + def test_remove_multiple_extra_routes(self): + arglist = [ + self._router.id, + '--route', 'destination=dst1,gateway=gw1', + '--route', 'destination=dst2,gateway=gw2', + ] + verifylist = [ + ('router', self._router.id), + ('routes', [ + {'destination': 'dst1', 'gateway': 'gw1'}, + {'destination': 'dst2', 'gateway': 'gw2'}, + ]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.network.remove_extra_routes_from_router.assert_called_with( + self._router, body={'router': {'routes': [ + {'destination': 'dst1', 'nexthop': 'gw1'}, + {'destination': 'dst2', 'nexthop': 'gw2'}, + ]}}) + self.assertEqual(2, len(result)) + + class TestSetRouter(TestRouter): # The router to set. diff --git a/releasenotes/notes/router-extraroute-atomic-d6d406ffb15695f2.yaml b/releasenotes/notes/router-extraroute-atomic-d6d406ffb15695f2.yaml new file mode 100644 index 0000000000..33b5ba7a89 --- /dev/null +++ b/releasenotes/notes/router-extraroute-atomic-d6d406ffb15695f2.yaml @@ -0,0 +1,12 @@ +--- +features: + - | + Add new commands ``router add route`` and ``router remove route`` to + support new Neutron extension: ``extraroute-atomic`` (see `Neutron RFE + `_). +deprecations: + - | + The use of ``router set --route`` to add extra routes next to already + existing extra routes is deprecated in favor of ``router add route + --route``, because ``router set --route`` if used from multiple clients + concurrently may lead to lost updates. diff --git a/requirements.txt b/requirements.txt index b17b6a5593..f7a12dae85 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ six>=1.10.0 # MIT Babel!=2.4.0,>=2.3.4 # BSD cliff!=2.9.0,>=2.8.0 # Apache-2.0 -openstacksdk>=0.36.0 # Apache-2.0 +openstacksdk>=0.38.0 # Apache-2.0 osc-lib>=2.0.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 diff --git a/setup.cfg b/setup.cfg index 60caf5db1e..1e5e36b8c6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -480,11 +480,13 @@ openstack.network.v2 = port_unset = openstackclient.network.v2.port:UnsetPort router_add_port = openstackclient.network.v2.router:AddPortToRouter + router_add_route = openstackclient.network.v2.router:AddExtraRoutesToRouter router_add_subnet = openstackclient.network.v2.router:AddSubnetToRouter router_create = openstackclient.network.v2.router:CreateRouter router_delete = openstackclient.network.v2.router:DeleteRouter router_list = openstackclient.network.v2.router:ListRouter router_remove_port = openstackclient.network.v2.router:RemovePortFromRouter + router_remove_route = openstackclient.network.v2.router:RemoveExtraRoutesFromRouter router_remove_subnet = openstackclient.network.v2.router:RemoveSubnetFromRouter router_set = openstackclient.network.v2.router:SetRouter router_show = openstackclient.network.v2.router:ShowRouter From f01a0f336c497584cc38e4396ee87282bdef1f5c Mon Sep 17 00:00:00 2001 From: Andreas Jaeger Date: Fri, 27 Mar 2020 16:26:50 +0100 Subject: [PATCH 2186/3095] Cleanup Python 2.7 support OpenStack is dropping the py2.7 support in ussuri cycle. Make a few cleanups: - Remove python 2.7 stanza from setup.py - Add python-requires to setup.cfg so that pypi and pip know about support Python version - Remove ancient sections from setup.cfg - Remove version_info setting from conf.py, openstackdocstheme does this automatically nowadays. Change-Id: I5b9c159752c932f874015f20822862c70562c2bd --- doc/source/conf.py | 13 ------------- setup.cfg | 4 +--- setup.py | 9 --------- 3 files changed, 1 insertion(+), 25 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index ff37783fb5..4de95fbb82 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -12,8 +12,6 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import pbr.version - # -- General configuration ---------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. @@ -55,17 +53,6 @@ project = u'OpenStack Command Line Client' copyright = u'2012-2013 OpenStack Foundation' -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -version_info = pbr.version.VersionInfo('python-openstackclient') -# -# The short X.Y version. -version = version_info.version_string() -# The full version, including alpha/beta/rc tags. -release = version_info.release_string() - # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None diff --git a/setup.cfg b/setup.cfg index 60caf5db1e..434da5b5be 100644 --- a/setup.cfg +++ b/setup.cfg @@ -6,6 +6,7 @@ description-file = author = OpenStack author-email = openstack-discuss@lists.openstack.org home-page = https://docs.openstack.org/python-openstackclient/latest/ +python-requires = >=3.6 classifier = Environment :: OpenStack Intended Audience :: Information Technology @@ -715,9 +716,6 @@ openstack.volume.v3 = volume_transfer_request_list = openstackclient.volume.v2.volume_transfer_request:ListTransferRequest volume_transfer_request_show = openstackclient.volume.v2.volume_transfer_request:ShowTransferRequest -[upload_sphinx] -upload-dir = doc/build/html - [extract_messages] keywords = _ gettext ngettext l_ lazy_gettext mapping_file = babel.cfg diff --git a/setup.py b/setup.py index 566d84432e..cd35c3c35b 100644 --- a/setup.py +++ b/setup.py @@ -13,17 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT import setuptools -# In python < 2.7.4, a lazy loading of package `pbr` will break -# setuptools if some other modules registered functions in `atexit`. -# solution from: http://bugs.python.org/issue15881#msg170215 -try: - import multiprocessing # noqa -except ImportError: - pass - setuptools.setup( setup_requires=['pbr>=2.0.0'], pbr=True) From 5e62411e5f6c5b68512d1f72470b4222199eb456 Mon Sep 17 00:00:00 2001 From: Tom Stappaerts Date: Thu, 5 Mar 2020 17:54:55 +0100 Subject: [PATCH 2187/3095] Support for stateless security groups Add support for stateful attribute of security groups, using --stateful and --no-stateful flag on security group. This allows a user to create security groups with stateful false. Change-Id: Ifd20b5fc47fd0ea0bb5aeda84820dcc0fb1e8847 Blueprint: stateless-security-groups Depends-On: https://review.opendev.org/711513/ --- openstackclient/network/v2/security_group.py | 34 +++++++++++++++++++ .../network/v2/test_security_group.py | 4 ++- .../tests/unit/network/v2/fakes.py | 1 + .../network/v2/test_security_group_network.py | 10 ++++++ ...teful-security-group-a21fa8498e866b90.yaml | 6 ++++ 5 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/stateful-security-group-a21fa8498e866b90.yaml diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py index f8153fa8b2..dbfd908bfe 100644 --- a/openstackclient/network/v2/security_group.py +++ b/openstackclient/network/v2/security_group.py @@ -120,6 +120,19 @@ def update_parser_network(self, parser): metavar='', help=self.enhance_help_neutron(_("Owner's project (name or ID)")) ) + stateful_group = parser.add_mutually_exclusive_group() + stateful_group.add_argument( + "--stateful", + action='store_true', + default=None, + help=_("Security group is stateful (Default)") + ) + stateful_group.add_argument( + "--stateless", + action='store_true', + default=None, + help=_("Security group is stateless") + ) identity_common.add_project_domain_option_to_parser( parser, enhance_help=self.enhance_help_neutron) _tag.add_tag_option_to_parser_for_create( @@ -138,6 +151,10 @@ def take_action_network(self, client, parsed_args): attrs = {} attrs['name'] = parsed_args.name attrs['description'] = self._get_description(parsed_args) + if parsed_args.stateful: + attrs['stateful'] = True + if parsed_args.stateless: + attrs['stateful'] = False if parsed_args.project is not None: identity_client = self.app.client_manager.identity project_id = identity_common.find_project( @@ -313,6 +330,19 @@ def update_parser_common(self, parser): metavar="", help=_("New security group description") ) + stateful_group = parser.add_mutually_exclusive_group() + stateful_group.add_argument( + "--stateful", + action='store_true', + default=None, + help=_("Security group is stateful (Default)") + ) + stateful_group.add_argument( + "--stateless", + action='store_true', + default=None, + help=_("Security group is stateless") + ) return parser def update_parser_network(self, parser): @@ -329,6 +359,10 @@ def take_action_network(self, client, parsed_args): attrs['name'] = parsed_args.name if parsed_args.description is not None: attrs['description'] = parsed_args.description + if parsed_args.stateful: + attrs['stateful'] = True + if parsed_args.stateless: + attrs['stateful'] = False # NOTE(rtheis): Previous behavior did not raise a CommandError # if there were no updates. Maintain this behavior and issue # the update. diff --git a/openstackclient/tests/functional/network/v2/test_security_group.py b/openstackclient/tests/functional/network/v2/test_security_group.py index 8ae24b7247..d46f8db787 100644 --- a/openstackclient/tests/functional/network/v2/test_security_group.py +++ b/openstackclient/tests/functional/network/v2/test_security_group.py @@ -42,7 +42,7 @@ def test_security_group_list(self): def test_security_group_set(self): other_name = uuid.uuid4().hex raw_output = self.openstack( - 'security group set --description NSA --name ' + + 'security group set --description NSA --stateless --name ' + other_name + ' ' + self.NAME ) self.assertEqual('', raw_output) @@ -50,8 +50,10 @@ def test_security_group_set(self): cmd_output = json.loads(self.openstack( 'security group show -f json ' + other_name)) self.assertEqual('NSA', cmd_output['description']) + self.assertFalse(cmd_output['stateful']) def test_security_group_show(self): cmd_output = json.loads(self.openstack( 'security group show -f json ' + self.NAME)) self.assertEqual(self.NAME, cmd_output['name']) + self.assertTrue(cmd_output['stateful']) diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index 5809b225e1..f670bd0a0f 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -1226,6 +1226,7 @@ def create_one_security_group(attrs=None): 'id': 'security-group-id-' + uuid.uuid4().hex, 'name': 'security-group-name-' + uuid.uuid4().hex, 'description': 'security-group-description-' + uuid.uuid4().hex, + 'stateful': True, 'project_id': 'project-id-' + uuid.uuid4().hex, 'security_group_rules': [], 'tags': [] diff --git a/openstackclient/tests/unit/network/v2/test_security_group_network.py b/openstackclient/tests/unit/network/v2/test_security_group_network.py index 14d5751436..20983bfea1 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_network.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_network.py @@ -49,6 +49,7 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork): 'name', 'project_id', 'rules', + 'stateful', 'tags', ) @@ -58,6 +59,7 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork): _security_group.name, _security_group.project_id, security_group.NetworkSecurityGroupRulesColumn([]), + _security_group.stateful, _security_group.tags, ) @@ -101,6 +103,7 @@ def test_create_all_options(self): '--description', self._security_group.description, '--project', self.project.name, '--project-domain', self.domain.name, + '--stateful', self._security_group.name, ] verifylist = [ @@ -108,6 +111,7 @@ def test_create_all_options(self): ('name', self._security_group.name), ('project', self.project.name), ('project_domain', self.domain.name), + ('stateful', self._security_group.stateful), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -115,6 +119,7 @@ def test_create_all_options(self): self.network.create_security_group.assert_called_once_with(**{ 'description': self._security_group.description, + 'stateful': self._security_group.stateful, 'name': self._security_group.name, 'tenant_id': self.project.id, }) @@ -414,11 +419,13 @@ def test_set_all_options(self): arglist = [ '--name', new_name, '--description', new_description, + '--stateful', self._security_group.name, ] verifylist = [ ('description', new_description), ('group', self._security_group.name), + ('stateful', self._security_group.stateful), ('name', new_name), ] @@ -428,6 +435,7 @@ def test_set_all_options(self): attrs = { 'description': new_description, 'name': new_name, + 'stateful': True, } self.network.update_security_group.assert_called_once_with( self._security_group, @@ -482,6 +490,7 @@ class TestShowSecurityGroupNetwork(TestSecurityGroupNetwork): 'name', 'project_id', 'rules', + 'stateful', 'tags', ) @@ -492,6 +501,7 @@ class TestShowSecurityGroupNetwork(TestSecurityGroupNetwork): _security_group.project_id, security_group.NetworkSecurityGroupRulesColumn( [_security_group_rule._info]), + _security_group.stateful, _security_group.tags, ) diff --git a/releasenotes/notes/stateful-security-group-a21fa8498e866b90.yaml b/releasenotes/notes/stateful-security-group-a21fa8498e866b90.yaml new file mode 100644 index 0000000000..9b70bce86c --- /dev/null +++ b/releasenotes/notes/stateful-security-group-a21fa8498e866b90.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--stateful`` and ``--stateless`` option to the + ``security group create`` and ``security group set`` commands + to support stateful and stateless security groups. From 74a7c1d9d6efc545676cec1d9efeb2a86c5bc548 Mon Sep 17 00:00:00 2001 From: pedro Date: Mon, 23 Mar 2020 15:15:59 -0300 Subject: [PATCH 2188/3095] Add description field to portforwarding NAT rules Add the `description` field to Floating IP Port Forwardings Depends-On: https://review.opendev.org/#/c/705038/ Change-Id: I6477368e32570c96cacddba4f86455262e533277 Implements: blueprint portforwarding-description Closes-Bug: #1850818 --- lower-constraints.txt | 4 ++-- .../network/v2/floating_ip_port_forwarding.py | 20 +++++++++++++++++++ .../tests/unit/network/v2/fakes.py | 1 + .../v2/test_floating_ip_port_forwarding.py | 15 +++++++++++++- ...d-in-port-forwarding-c536e077b243d517.yaml | 6 ++++++ requirements.txt | 2 +- 6 files changed, 44 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/add-description-field-in-port-forwarding-c536e077b243d517.yaml diff --git a/lower-constraints.txt b/lower-constraints.txt index 83e19fe60f..acf8d78143 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -24,7 +24,7 @@ fixtures==3.0.0 flake8-import-order==0.13 flake8==2.6.2 future==0.16.0 -futurist==1.2.0 +futurist==2.1.0 gitdb==0.6.4 GitPython==1.0.1 gnocchiclient==3.3.1 @@ -50,7 +50,7 @@ msgpack-python==0.4.0 munch==2.1.0 netaddr==0.7.18 netifaces==0.10.4 -openstacksdk==0.38.0 +openstacksdk==0.44.0 os-client-config==1.28.0 os-service-types==1.7.0 os-testr==1.0.0 diff --git a/openstackclient/network/v2/floating_ip_port_forwarding.py b/openstackclient/network/v2/floating_ip_port_forwarding.py index f94bcc065a..06b3df8bcd 100644 --- a/openstackclient/network/v2/floating_ip_port_forwarding.py +++ b/openstackclient/network/v2/floating_ip_port_forwarding.py @@ -75,6 +75,12 @@ def get_parser(self, prog_name): required=True, help=_("The protocol used in the floating IP " "port forwarding, for instance: TCP, UDP") + ), + parser.add_argument( + '--description', + metavar='', + help=_("A text to describe/contextualize the use of the " + "port forwarding configuration") ) parser.add_argument( 'floating_ip', @@ -113,6 +119,9 @@ def take_action(self, parsed_args): attrs['internal_ip_address'] = parsed_args.internal_ip_address attrs['protocol'] = parsed_args.protocol + if parsed_args.description is not None: + attrs['description'] = parsed_args.description + obj = client.create_floating_ip_port_forwarding( floating_ip.id, **attrs @@ -212,6 +221,7 @@ def take_action(self, parsed_args): 'internal_port', 'external_port', 'protocol', + 'description', ) headers = ( 'ID', @@ -220,6 +230,7 @@ def take_action(self, parsed_args): 'Internal Port', 'External Port', 'Protocol', + 'Description', ) query = {} @@ -296,6 +307,12 @@ def get_parser(self, prog_name): metavar='', choices=['tcp', 'udp'], help=_("The IP protocol used in the floating IP port forwarding") + ), + parser.add_argument( + '--description', + metavar='', + help=_("A text to describe/contextualize the use of " + "the port forwarding configuration") ) return parser @@ -332,6 +349,9 @@ def take_action(self, parsed_args): if parsed_args.protocol: attrs['protocol'] = parsed_args.protocol + if parsed_args.description is not None: + attrs['description'] = parsed_args.description + client.update_floating_ip_port_forwarding( floating_ip.id, parsed_args.port_forwarding_id, **attrs) diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index a553f501e2..cc598834dd 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -1843,6 +1843,7 @@ def create_one_port_forwarding(attrs=None): 'internal_port': randint(1, 65535), 'external_port': randint(1, 65535), 'protocol': 'tcp', + 'description': 'some description', } # Overwrite default attributes. diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip_port_forwarding.py b/openstackclient/tests/unit/network/v2/test_floating_ip_port_forwarding.py index ea6cdd266f..1028c18ae9 100644 --- a/openstackclient/tests/unit/network/v2/test_floating_ip_port_forwarding.py +++ b/openstackclient/tests/unit/network/v2/test_floating_ip_port_forwarding.py @@ -62,6 +62,7 @@ def setUp(self): self.app, self.namespace) self.columns = ( + 'description', 'external_port', 'floatingip_id', 'id', @@ -73,6 +74,7 @@ def setUp(self): ) self.data = ( + self.new_port_forwarding.description, self.new_port_forwarding.external_port, self.new_port_forwarding.floatingip_id, self.new_port_forwarding.id, @@ -102,6 +104,8 @@ def test_create_all_options(self): self.new_port_forwarding.floatingip_id, '--internal-ip-address', self.new_port_forwarding.internal_ip_address, + '--description', + self.new_port_forwarding.description, ] verifylist = [ ('port', self.new_port_forwarding.internal_port_id), @@ -111,6 +115,7 @@ def test_create_all_options(self): ('floating_ip', self.new_port_forwarding.floatingip_id), ('internal_ip_address', self.new_port_forwarding. internal_ip_address), + ('description', self.new_port_forwarding.description), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) @@ -126,6 +131,7 @@ def test_create_all_options(self): 'internal_port_id': self.new_port_forwarding. internal_port_id, 'protocol': self.new_port_forwarding.protocol, + 'description': self.new_port_forwarding.description, }) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -251,7 +257,8 @@ class TestListFloatingIPPortForwarding(TestFloatingIPPortForwarding): 'Internal IP Address', 'Internal Port', 'External Port', - 'Protocol' + 'Protocol', + 'Description', ) def setUp(self): @@ -273,6 +280,7 @@ def setUp(self): port_forwarding.internal_port, port_forwarding.external_port, port_forwarding.protocol, + port_forwarding.description, )) self.network.floating_ip_port_forwardings = mock.Mock( return_value=self.port_forwardings @@ -393,6 +401,7 @@ def test_set_all_thing(self): '--internal-protocol-port', '100', '--external-protocol-port', '200', '--protocol', 'tcp', + '--description', 'some description', self._port_forwarding.floatingip_id, self._port_forwarding.id, ] @@ -402,6 +411,7 @@ def test_set_all_thing(self): ('internal_protocol_port', 100), ('external_protocol_port', 200), ('protocol', 'tcp'), + ('description', 'some description'), ('floating_ip', self._port_forwarding.floatingip_id), ('port_forwarding_id', self._port_forwarding.id), ] @@ -415,6 +425,7 @@ def test_set_all_thing(self): 'internal_port': 100, 'external_port': 200, 'protocol': 'tcp', + 'description': 'some description', } self.network.update_floating_ip_port_forwarding.assert_called_with( self._port_forwarding.floatingip_id, @@ -428,6 +439,7 @@ class TestShowFloatingIPPortForwarding(TestFloatingIPPortForwarding): # The port forwarding to show. columns = ( + 'description', 'external_port', 'floatingip_id', 'id', @@ -450,6 +462,7 @@ def setUp(self): ) ) self.data = ( + self._port_forwarding.description, self._port_forwarding.external_port, self._port_forwarding.floatingip_id, self._port_forwarding.id, diff --git a/releasenotes/notes/add-description-field-in-port-forwarding-c536e077b243d517.yaml b/releasenotes/notes/add-description-field-in-port-forwarding-c536e077b243d517.yaml new file mode 100644 index 0000000000..6df5bb3a2b --- /dev/null +++ b/releasenotes/notes/add-description-field-in-port-forwarding-c536e077b243d517.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add a new option `--description` to + ``floating ip port forwarding create`` and + ``floating ip port forwarding set`` commands. diff --git a/requirements.txt b/requirements.txt index f7a12dae85..b6f97b4d35 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ six>=1.10.0 # MIT Babel!=2.4.0,>=2.3.4 # BSD cliff!=2.9.0,>=2.8.0 # Apache-2.0 -openstacksdk>=0.38.0 # Apache-2.0 +openstacksdk>=0.44.0 # Apache-2.0 osc-lib>=2.0.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 From 725e004d32d538f9d163c727308ee20385c0310d Mon Sep 17 00:00:00 2001 From: Sean McGinnis Date: Fri, 3 Apr 2020 17:11:40 -0500 Subject: [PATCH 2189/3095] Use unittest.mock instead of third party mock Now that we no longer support py27, we can use the standard library unittest.mock module instead of the third party mock lib. Change-Id: Ibd39328c27b68190e2edbf1f52fcea52db3ae791 Signed-off-by: Sean McGinnis --- lower-constraints.txt | 1 - openstackclient/tests/unit/identity/v3/test_access_rule.py | 2 +- test-requirements.txt | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/lower-constraints.txt b/lower-constraints.txt index 83e19fe60f..ac41658cda 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -43,7 +43,6 @@ kombu==4.0.0 linecache2==1.0.0 MarkupSafe==1.1.0 mccabe==0.2.1 -mock==2.0.0 monotonic==0.6 mox3==0.20.0 msgpack-python==0.4.0 diff --git a/openstackclient/tests/unit/identity/v3/test_access_rule.py b/openstackclient/tests/unit/identity/v3/test_access_rule.py index f8b6093a6c..904fe323d9 100644 --- a/openstackclient/tests/unit/identity/v3/test_access_rule.py +++ b/openstackclient/tests/unit/identity/v3/test_access_rule.py @@ -14,8 +14,8 @@ # import copy +from unittest import mock -import mock from osc_lib import exceptions from osc_lib import utils diff --git a/test-requirements.txt b/test-requirements.txt index 55ae1ea459..41600a6a22 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -5,7 +5,6 @@ hacking>=2.0.0 # Apache-2.0 coverage!=4.4,>=4.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD flake8-import-order>=0.13 # LGPLv3 -mock>=2.0.0 # BSD oslotest>=3.2.0 # Apache-2.0 requests>=2.14.2 # Apache-2.0 requests-mock>=1.2.0 # Apache-2.0 From bdaebeb508a65f778b1230cc6f112f34745d6351 Mon Sep 17 00:00:00 2001 From: Vasyl Saienko Date: Mon, 6 Apr 2020 11:16:22 +0000 Subject: [PATCH 2190/3095] Revert "Disallow setting default on internal network" The original patch assumes that both --external and --is-default are set in the same request and broke case when --is-default is set as an network update. The validation logic have to be moved on API side to avoid extra API calls from openstackclient. This reverts commit 962efd949feb461283a9bb4a668fbd310f80ba40. Related-Bug: #1745658 Change-Id: Idf08abb0e08a6880f89c3e9df9dd2ac82f36c432 --- openstackclient/network/v2/network.py | 7 +--- .../tests/unit/network/v2/test_network.py | 33 ------------------- ...-on-internal-network-824fdea1a900891c.yaml | 9 ----- 3 files changed, 1 insertion(+), 48 deletions(-) delete mode 100644 releasenotes/notes/disallow-setting-default-on-internal-network-824fdea1a900891c.yaml diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 3f579b6d52..00cb782b73 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -16,7 +16,6 @@ from cliff import columns as cliff_columns from osc_lib.cli import format_columns from osc_lib.command import command -from osc_lib import exceptions from osc_lib import utils from osc_lib.utils import tags as _tag @@ -126,9 +125,6 @@ def _get_attrs_network(client_manager, parsed_args): attrs['is_default'] = False if parsed_args.default: attrs['is_default'] = True - if attrs.get('is_default') and not attrs.get('router:external'): - msg = _("Cannot set default for internal network") - raise exceptions.CommandError(msg) # Update Provider network options if parsed_args.provider_network_type: attrs['provider:network_type'] = parsed_args.provider_network_type @@ -706,8 +702,7 @@ def get_parser(self, prog_name): default_router_grp.add_argument( '--default', action='store_true', - help=_("Set the network as the default external network " - "(cannot be used with internal network).") + help=_("Set the network as the default external network") ) default_router_grp.add_argument( '--no-default', diff --git a/openstackclient/tests/unit/network/v2/test_network.py b/openstackclient/tests/unit/network/v2/test_network.py index 45d6008b5b..5f8eed6702 100644 --- a/openstackclient/tests/unit/network/v2/test_network.py +++ b/openstackclient/tests/unit/network/v2/test_network.py @@ -278,24 +278,6 @@ def test_create_with_tags(self): def test_create_with_no_tag(self): self._test_create_with_tag(add_tags=False) - def test_create_default_internal(self): - arglist = [ - self._network.name, - "--default", - ] - verifylist = [ - ('name', self._network.name), - ('enable', True), - ('share', None), - ('project', None), - ('external', False), - ('default', True), - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.assertRaises(exceptions.CommandError, self.cmd.take_action, - parsed_args) - class TestCreateNetworkIdentityV2(TestNetwork): @@ -1043,21 +1025,6 @@ def test_set_with_tags(self): def test_set_with_no_tag(self): self._test_set_tags(with_tags=False) - def test_set_default_internal(self): - arglist = [ - self._network.name, - '--internal', - '--default', - ] - verifylist = [ - ('internal', True), - ('default', True), - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.assertRaises(exceptions.CommandError, self.cmd.take_action, - parsed_args) - class TestShowNetwork(TestNetwork): diff --git a/releasenotes/notes/disallow-setting-default-on-internal-network-824fdea1a900891c.yaml b/releasenotes/notes/disallow-setting-default-on-internal-network-824fdea1a900891c.yaml deleted file mode 100644 index baf4efe91d..0000000000 --- a/releasenotes/notes/disallow-setting-default-on-internal-network-824fdea1a900891c.yaml +++ /dev/null @@ -1,9 +0,0 @@ ---- -fixes: - - | - For ``network create`` the - `--default`` option should be only used for external networks. - After this release, we enforce this scenario. If a users attempts - to create an internal default network or update a network to be - internal default, the command will be denied. - [Bug `1745658 `_] From 7f66273d3f2fb6449d7b50d88460ace0cb81bf30 Mon Sep 17 00:00:00 2001 From: Vishakha Agarwal Date: Thu, 26 Mar 2020 22:23:57 +0530 Subject: [PATCH 2191/3095] Add resource option immutable This patch adds the --immutable and --no-immutable option to the role, project and domain CLI. Related-Patch: https://review.opendev.org/#/c/712182/ Change-Id: I9c3bdd741f28bf558267fb217818d947597ce13e --- doc/source/cli/command-objects/project.rst | 20 +++ doc/source/cli/command-objects/role.rst | 20 +++ openstackclient/identity/common.py | 24 +++ openstackclient/identity/v3/domain.py | 9 + openstackclient/identity/v3/project.py | 9 + openstackclient/identity/v3/role.py | 10 +- .../tests/unit/identity/v3/test_domain.py | 110 ++++++++++++ .../tests/unit/identity/v3/test_project.py | 161 ++++++++++++++++- .../tests/unit/identity/v3/test_role.py | 162 ++++++++++++++++++ ...rce_option_immutable-efed6e1ebdc69591.yaml | 9 + 10 files changed, 523 insertions(+), 11 deletions(-) create mode 100644 releasenotes/notes/add_resource_option_immutable-efed6e1ebdc69591.yaml diff --git a/doc/source/cli/command-objects/project.rst b/doc/source/cli/command-objects/project.rst index ac7e8cd1ce..afa785cbbe 100644 --- a/doc/source/cli/command-objects/project.rst +++ b/doc/source/cli/command-objects/project.rst @@ -16,6 +16,7 @@ Create new project [--domain ] [--parent ] [--description ] + [--immutable | --no-immutable] [--enable | --disable] [--property ] [--or-show] @@ -46,6 +47,15 @@ Create new project Disable project +.. option:: --immutable + + Make project immutable. An immutable project may not be deleted or + modified except to remove the immutable flag + +.. option:: --no-immutable + + Make project mutable (default) + .. option:: --property Add a property to :ref:`\ ` @@ -180,6 +190,7 @@ Set project properties [--name ] [--domain ] [--description ] + [--immutable | --no-immutable] [--enable | --disable] [--property ] [--tag | --clear-tags | --remove-tags ] @@ -199,6 +210,15 @@ Set project properties Set project description +.. option:: --immutable + + Make project immutable. An immutable project may not be deleted or + modified except to remove the immutable flag + +.. option:: --no-immutable + + Make project mutable (default) + .. option:: --enable Enable project (default) diff --git a/doc/source/cli/command-objects/role.rst b/doc/source/cli/command-objects/role.rst index 752dc4e03c..f9fd28eb75 100644 --- a/doc/source/cli/command-objects/role.rst +++ b/doc/source/cli/command-objects/role.rst @@ -97,6 +97,7 @@ Create new role openstack role create [--or-show] [--domain ] + [--immutable | --no-immutable] .. option:: --domain @@ -119,6 +120,15 @@ Create new role Add description about the role +.. option:: --immutable + + Make role immutable. An immutable role may not be deleted or modified + except to remove the immutable flag + +.. option:: --no-immutable + + Make role mutable (default) + role delete ----------- @@ -253,6 +263,7 @@ Set role properties openstack role set [--name ] [--domain ] + [--immutable | --no-immutable] .. option:: --name @@ -269,6 +280,15 @@ Set role properties Role to modify (name or ID) +.. option:: --immutable + + Make role immutable. An immutable role may not be deleted or modified + except to remove the immutable flag + +.. option:: --no-immutable + + Make role mutable (default) + role show --------- diff --git a/openstackclient/identity/common.py b/openstackclient/identity/common.py index 7be2a17b72..e70d87d21f 100644 --- a/openstackclient/identity/common.py +++ b/openstackclient/identity/common.py @@ -213,6 +213,15 @@ def _find_identity_resource(identity_client_manager, name_or_id, return resource_type(None, {'id': name_or_id, 'name': name_or_id}) +def get_immutable_options(parsed_args): + options = {} + if parsed_args.immutable: + options['immutable'] = True + if parsed_args.no_immutable: + options['immutable'] = False + return options + + def add_user_domain_option_to_parser(parser): parser.add_argument( '--user-domain', @@ -261,3 +270,18 @@ def add_inherited_option_to_parser(parser): help=_('Specifies if the role grant is inheritable to the sub ' 'projects'), ) + + +def add_resource_option_to_parser(parser): + enable_group = parser.add_mutually_exclusive_group() + enable_group.add_argument( + '--immutable', + action='store_true', + help=_('Make resource immutable. An immutable project may not ' + 'be deleted or modified except to remove the immutable flag'), + ) + enable_group.add_argument( + '--no-immutable', + action='store_true', + help=_('Make resource mutable (default)'), + ) diff --git a/openstackclient/identity/v3/domain.py b/openstackclient/identity/v3/domain.py index dbcc97f6d2..e33fce05c5 100644 --- a/openstackclient/identity/v3/domain.py +++ b/openstackclient/identity/v3/domain.py @@ -60,6 +60,7 @@ def get_parser(self, prog_name): action='store_true', help=_('Return existing domain'), ) + common.add_resource_option_to_parser(parser) return parser def take_action(self, parsed_args): @@ -69,10 +70,13 @@ def take_action(self, parsed_args): if parsed_args.disable: enabled = False + options = common.get_immutable_options(parsed_args) + try: domain = identity_client.domains.create( name=parsed_args.name, description=parsed_args.description, + options=options, enabled=enabled, ) except ks_exc.Conflict: @@ -163,6 +167,7 @@ def get_parser(self, prog_name): action='store_true', help=_('Disable domain'), ) + common.add_resource_option_to_parser(parser) return parser def take_action(self, parsed_args): @@ -180,6 +185,10 @@ def take_action(self, parsed_args): if parsed_args.disable: kwargs['enabled'] = False + options = common.get_immutable_options(parsed_args) + if options: + kwargs['options'] = options + identity_client.domains.update(domain.id, **kwargs) diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index 9ecc70ef4e..e32da165b4 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -78,6 +78,7 @@ def get_parser(self, prog_name): action='store_true', help=_('Return existing project'), ) + common.add_resource_option_to_parser(parser) tag.add_tag_option_to_parser_for_create(parser, _('project')) return parser @@ -99,6 +100,9 @@ def take_action(self, parsed_args): enabled = True if parsed_args.disable: enabled = False + + options = common.get_immutable_options(parsed_args) + kwargs = {} if parsed_args.property: kwargs = parsed_args.property.copy() @@ -111,6 +115,7 @@ def take_action(self, parsed_args): parent=parent, description=parsed_args.description, enabled=enabled, + options=options, **kwargs ) except ks_exc.Conflict: @@ -317,6 +322,7 @@ def get_parser(self, prog_name): help=_('Set a property on ' '(repeat option to set multiple properties)'), ) + common.add_resource_option_to_parser(parser) tag.add_tag_option_to_parser_for_set(parser, _('project')) return parser @@ -336,6 +342,9 @@ def take_action(self, parsed_args): kwargs['enabled'] = True if parsed_args.disable: kwargs['enabled'] = False + options = common.get_immutable_options(parsed_args) + if options: + kwargs['options'] = options if parsed_args.property: kwargs.update(parsed_args.property) tag.update_tags_in_args(parsed_args, project, kwargs) diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index 36f3f9384b..980ebf1165 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -191,6 +191,7 @@ def get_parser(self, prog_name): action='store_true', help=_('Return existing role'), ) + common.add_resource_option_to_parser(parser) return parser def take_action(self, parsed_args): @@ -201,10 +202,12 @@ def take_action(self, parsed_args): domain_id = common.find_domain(identity_client, parsed_args.domain).id + options = common.get_immutable_options(parsed_args) + try: role = identity_client.roles.create( name=parsed_args.name, domain=domain_id, - description=parsed_args.description) + description=parsed_args.description, options=options) except ks_exc.Conflict: if parsed_args.or_show: @@ -366,6 +369,7 @@ def get_parser(self, prog_name): metavar='', help=_('Set role name'), ) + common.add_resource_option_to_parser(parser) return parser def take_action(self, parsed_args): @@ -376,12 +380,14 @@ def take_action(self, parsed_args): domain_id = common.find_domain(identity_client, parsed_args.domain).id + options = common.get_immutable_options(parsed_args) role = utils.find_resource(identity_client.roles, parsed_args.role, domain_id=domain_id) identity_client.roles.update(role.id, name=parsed_args.name, - description=parsed_args.description) + description=parsed_args.description, + options=options) class ShowRole(command.ShowOne): diff --git a/openstackclient/tests/unit/identity/v3/test_domain.py b/openstackclient/tests/unit/identity/v3/test_domain.py index 014986e573..46f389e890 100644 --- a/openstackclient/tests/unit/identity/v3/test_domain.py +++ b/openstackclient/tests/unit/identity/v3/test_domain.py @@ -68,6 +68,7 @@ def test_domain_create_no_options(self): kwargs = { 'name': self.domain.name, 'description': None, + 'options': {}, 'enabled': True, } self.domains_mock.create.assert_called_with( @@ -97,6 +98,7 @@ def test_domain_create_description(self): kwargs = { 'name': self.domain.name, 'description': 'new desc', + 'options': {}, 'enabled': True, } self.domains_mock.create.assert_called_with( @@ -126,6 +128,7 @@ def test_domain_create_enable(self): kwargs = { 'name': self.domain.name, 'description': None, + 'options': {}, 'enabled': True, } self.domains_mock.create.assert_called_with( @@ -155,6 +158,7 @@ def test_domain_create_disable(self): kwargs = { 'name': self.domain.name, 'description': None, + 'options': {}, 'enabled': False, } self.domains_mock.create.assert_called_with( @@ -164,6 +168,66 @@ def test_domain_create_disable(self): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, data) + def test_domain_create_with_immutable(self): + arglist = [ + '--immutable', + self.domain.name, + ] + verifylist = [ + ('immutable', True), + ('name', self.domain.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) + + # Set expected values + kwargs = { + 'name': self.domain.name, + 'description': None, + 'options': {'immutable': True}, + 'enabled': True, + } + self.domains_mock.create.assert_called_with( + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) + + def test_domain_create_with_no_immutable(self): + arglist = [ + '--no-immutable', + self.domain.name, + ] + verifylist = [ + ('no_immutable', True), + ('name', self.domain.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) + + # Set expected values + kwargs = { + 'name': self.domain.name, + 'description': None, + 'options': {'immutable': False}, + 'enabled': True, + } + self.domains_mock.create.assert_called_with( + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) + class TestDomainDelete(TestDomain): @@ -354,6 +418,52 @@ def test_domain_set_disable(self): ) self.assertIsNone(result) + def test_domain_set_immutable_option(self): + arglist = [ + '--immutable', + self.domain.id, + ] + verifylist = [ + ('immutable', True), + ('domain', self.domain.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'options': {'immutable': True}, + } + self.domains_mock.update.assert_called_with( + self.domain.id, + **kwargs + ) + self.assertIsNone(result) + + def test_domain_set_no_immutable_option(self): + arglist = [ + '--no-immutable', + self.domain.id, + ] + verifylist = [ + ('no_immutable', True), + ('domain', self.domain.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'options': {'immutable': False}, + } + self.domains_mock.update.assert_called_with( + self.domain.id, + **kwargs + ) + self.assertIsNone(result) + class TestDomainShow(TestDomain): diff --git a/openstackclient/tests/unit/identity/v3/test_project.py b/openstackclient/tests/unit/identity/v3/test_project.py index 466bea1867..8852aa8ecc 100644 --- a/openstackclient/tests/unit/identity/v3/test_project.py +++ b/openstackclient/tests/unit/identity/v3/test_project.py @@ -98,7 +98,8 @@ def test_project_create_no_options(self): 'description': None, 'enabled': True, 'parent': None, - 'tags': [] + 'tags': [], + 'options': {}, } # ProjectManager.create(name=, domain=, description=, # enabled=, **kwargs) @@ -156,7 +157,8 @@ def test_project_create_description(self): 'description': 'new desc', 'enabled': True, 'parent': None, - 'tags': [] + 'tags': [], + 'options': {}, } # ProjectManager.create(name=, domain=, description=, # enabled=, **kwargs) @@ -194,7 +196,8 @@ def test_project_create_domain(self): 'description': None, 'enabled': True, 'parent': None, - 'tags': [] + 'tags': [], + 'options': {}, } # ProjectManager.create(name=, domain=, description=, # enabled=, **kwargs) @@ -232,7 +235,8 @@ def test_project_create_domain_no_perms(self): 'description': None, 'enabled': True, 'parent': None, - 'tags': [] + 'tags': [], + 'options': {}, } self.projects_mock.create.assert_called_with( **kwargs @@ -266,7 +270,8 @@ def test_project_create_enable(self): 'description': None, 'enabled': True, 'parent': None, - 'tags': [] + 'tags': [], + 'options': {}, } # ProjectManager.create(name=, domain=, description=, # enabled=, **kwargs) @@ -302,7 +307,8 @@ def test_project_create_disable(self): 'description': None, 'enabled': False, 'parent': None, - 'tags': [] + 'tags': [], + 'options': {}, } # ProjectManager.create(name=, domain=, # description=, enabled=, **kwargs) @@ -339,7 +345,8 @@ def test_project_create_property(self): 'parent': None, 'fee': 'fi', 'fo': 'fum', - 'tags': [] + 'tags': [], + 'options': {}, } # ProjectManager.create(name=, domain=, description=, # enabled=, **kwargs) @@ -380,7 +387,8 @@ def test_project_create_parent(self): 'parent': self.parent.id, 'description': None, 'enabled': True, - 'tags': [] + 'tags': [], + 'options': {}, } self.projects_mock.create.assert_called_with( @@ -465,8 +473,89 @@ def test_project_create_with_tags(self): 'description': None, 'enabled': True, 'parent': None, - 'tags': ['foo'] + 'tags': ['foo'], + 'options': {}, + } + self.projects_mock.create.assert_called_with( + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) + + def test_project_create_with_immutable_option(self): + arglist = [ + '--immutable', + self.project.name, + ] + verifylist = [ + ('immutable', True), + ('description', None), + ('enable', False), + ('disable', False), + ('name', self.project.name), + ('parent', None), + ('tags', []) + ] + 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) + + # Set expected values + kwargs = { + 'name': self.project.name, + 'domain': None, + 'description': None, + 'enabled': True, + 'parent': None, + 'tags': [], + 'options': {'immutable': True}, } + # ProjectManager.create(name=, domain=, description=, + # enabled=, **kwargs) + self.projects_mock.create.assert_called_with( + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) + + def test_project_create_with_no_immutable_option(self): + arglist = [ + '--no-immutable', + self.project.name, + ] + verifylist = [ + ('no_immutable', True), + ('description', None), + ('enable', False), + ('disable', False), + ('name', self.project.name), + ('parent', None), + ('tags', []) + ] + 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) + + # Set expected values + kwargs = { + 'name': self.project.name, + 'domain': None, + 'description': None, + 'enabled': True, + 'parent': None, + 'tags': [], + 'options': {'immutable': False}, + } + # ProjectManager.create(name=, domain=, description=, + # enabled=, **kwargs) self.projects_mock.create.assert_called_with( **kwargs ) @@ -927,6 +1016,60 @@ def test_project_set_tags(self): ) self.assertIsNone(result) + def test_project_set_with_immutable_option(self): + arglist = [ + '--domain', self.project.domain_id, + '--immutable', + self.project.name, + ] + verifylist = [ + ('domain', self.project.domain_id), + ('immutable', True), + ('enable', False), + ('disable', False), + ('project', self.project.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'options': {'immutable': True}, + } + self.projects_mock.update.assert_called_with( + self.project.id, + **kwargs + ) + self.assertIsNone(result) + + def test_project_set_with_no_immutable_option(self): + arglist = [ + '--domain', self.project.domain_id, + '--no-immutable', + self.project.name, + ] + verifylist = [ + ('domain', self.project.domain_id), + ('no_immutable', True), + ('enable', False), + ('disable', False), + ('project', self.project.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'options': {'immutable': False}, + } + self.projects_mock.update.assert_called_with( + self.project.id, + **kwargs + ) + self.assertIsNone(result) + class TestProjectShow(TestProject): diff --git a/openstackclient/tests/unit/identity/v3/test_role.py b/openstackclient/tests/unit/identity/v3/test_role.py index 4278ab1cc1..544da7c15d 100644 --- a/openstackclient/tests/unit/identity/v3/test_role.py +++ b/openstackclient/tests/unit/identity/v3/test_role.py @@ -333,6 +333,7 @@ def test_role_create_no_options(self): 'domain': None, 'name': identity_fakes.role_name, 'description': None, + 'options': {}, } # RoleManager.create(name=, domain=) @@ -377,6 +378,7 @@ def test_role_create_with_domain(self): 'domain': identity_fakes.domain_id, 'name': identity_fakes.ROLE_2['name'], 'description': None, + 'options': {}, } # RoleManager.create(name=, domain=) @@ -420,6 +422,97 @@ def test_role_create_with_description(self): 'description': identity_fakes.role_description, 'name': identity_fakes.ROLE_2['name'], 'domain': None, + 'options': {}, + } + + # RoleManager.create(name=, domain=) + self.roles_mock.create.assert_called_with( + **kwargs + ) + + collist = ('domain', 'id', 'name') + self.assertEqual(collist, columns) + datalist = ( + 'd1', + identity_fakes.ROLE_2['id'], + identity_fakes.ROLE_2['name'], + ) + self.assertEqual(datalist, data) + + def test_role_create_with_immutable_option(self): + + self.roles_mock.create.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.ROLE_2), + loaded=True, + ) + arglist = [ + '--immutable', + identity_fakes.ROLE_2['name'], + ] + verifylist = [ + ('immutable', True), + ('name', identity_fakes.ROLE_2['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) + + # Set expected values + kwargs = { + + 'options': {'immutable': True}, + 'description': None, + 'name': identity_fakes.ROLE_2['name'], + 'domain': None, + } + + # RoleManager.create(name=, domain=) + self.roles_mock.create.assert_called_with( + **kwargs + ) + + collist = ('domain', 'id', 'name') + self.assertEqual(collist, columns) + datalist = ( + 'd1', + identity_fakes.ROLE_2['id'], + identity_fakes.ROLE_2['name'], + ) + self.assertEqual(datalist, data) + + def test_role_create_with_no_immutable_option(self): + + self.roles_mock.create.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.ROLE_2), + loaded=True, + ) + arglist = [ + '--no-immutable', + identity_fakes.ROLE_2['name'], + ] + verifylist = [ + ('no_immutable', True), + ('name', identity_fakes.ROLE_2['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) + + # Set expected values + kwargs = { + + 'options': {'immutable': False}, + 'description': None, + 'name': identity_fakes.ROLE_2['name'], + 'domain': None, } # RoleManager.create(name=, domain=) @@ -871,6 +964,7 @@ def test_role_set_no_options(self): kwargs = { 'name': 'over', 'description': None, + 'options': {}, } # RoleManager.update(role, name=) self.roles_mock.update.assert_called_with( @@ -903,6 +997,7 @@ def test_role_set_domain_role(self): kwargs = { 'name': 'over', 'description': None, + 'options': {}, } # RoleManager.update(role, name=) self.roles_mock.update.assert_called_with( @@ -935,6 +1030,73 @@ def test_role_set_description(self): kwargs = { 'name': 'over', 'description': identity_fakes.role_description, + 'options': {}, + } + # RoleManager.update(role, name=) + self.roles_mock.update.assert_called_with( + identity_fakes.ROLE_2['id'], + **kwargs + ) + self.assertIsNone(result) + + def test_role_set_with_immutable(self): + self.roles_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.ROLE_2), + loaded=True, + ) + arglist = [ + '--name', 'over', + '--immutable', + identity_fakes.ROLE_2['name'], + ] + verifylist = [ + ('name', 'over'), + ('immutable', True), + ('role', identity_fakes.ROLE_2['name']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'name': 'over', + 'description': None, + 'options': {'immutable': True}, + } + # RoleManager.update(role, name=) + self.roles_mock.update.assert_called_with( + identity_fakes.ROLE_2['id'], + **kwargs + ) + self.assertIsNone(result) + + def test_role_set_with_no_immutable(self): + self.roles_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.ROLE_2), + loaded=True, + ) + arglist = [ + '--name', 'over', + '--no-immutable', + identity_fakes.ROLE_2['name'], + ] + verifylist = [ + ('name', 'over'), + ('no_immutable', True), + ('role', identity_fakes.ROLE_2['name']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'name': 'over', + 'description': None, + 'options': {'immutable': False}, } # RoleManager.update(role, name=) self.roles_mock.update.assert_called_with( diff --git a/releasenotes/notes/add_resource_option_immutable-efed6e1ebdc69591.yaml b/releasenotes/notes/add_resource_option_immutable-efed6e1ebdc69591.yaml new file mode 100644 index 0000000000..814823fe19 --- /dev/null +++ b/releasenotes/notes/add_resource_option_immutable-efed6e1ebdc69591.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + Added the below mentioned parameters to the role, project and domain commands. + + * --immutable + * --no-immutable + + This will allow user to set "immutable" resource option. \ No newline at end of file From 557e65d8ebaf7eaecb2f938d65feffa0e97a86d4 Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Thu, 27 Feb 2020 17:50:48 +0200 Subject: [PATCH 2192/3095] Add 'subnetpool' type support to rbac commands Change-Id: Id6e528ebd1bf21ca142e60052d28371f97f629ac Partial-Bug: #1862032 Depends-On: https://review.opendev.org/710755 --- openstackclient/network/v2/network_rbac.py | 15 ++++++++++----- .../tests/unit/network/v2/test_network_rbac.py | 4 ++++ .../rbac-add-subnetpool-f1fc0e728ff61654.yaml | 4 ++++ 3 files changed, 18 insertions(+), 5 deletions(-) create mode 100644 releasenotes/notes/rbac-add-subnetpool-f1fc0e728ff61654.yaml diff --git a/openstackclient/network/v2/network_rbac.py b/openstackclient/network/v2/network_rbac.py index 0d6e07927b..2c34d7ef9a 100644 --- a/openstackclient/network/v2/network_rbac.py +++ b/openstackclient/network/v2/network_rbac.py @@ -62,6 +62,11 @@ def _get_attrs(client_manager, parsed_args): object_id = network_client.find_address_scope( parsed_args.rbac_object, ignore_missing=False).id + if parsed_args.type == 'subnetpool': + object_id = network_client.find_subnet_pool( + parsed_args.rbac_object, + ignore_missing=False).id + attrs['object_id'] = object_id identity_client = client_manager.identity @@ -101,11 +106,11 @@ def get_parser(self, prog_name): '--type', metavar="", required=True, - choices=['address_scope', 'security_group', + choices=['address_scope', 'security_group', 'subnetpool', 'qos_policy', 'network'], help=_('Type of the object that RBAC policy ' - 'affects ("address_scope", "security_group", ' - '"qos_policy" or "network")') + 'affects ("address_scope", "security_group", "subnetpool",' + ' "qos_policy" or "network")') ) parser.add_argument( '--action', @@ -194,11 +199,11 @@ def get_parser(self, prog_name): parser.add_argument( '--type', metavar='', - choices=['address_scope', 'security_group', + choices=['address_scope', 'security_group', 'subnetpool', 'qos_policy', 'network'], help=_('List network RBAC policies according to ' 'given object type ("address_scope", "security_group", ' - '"qos_policy" or "network")') + '"subnetpool", "qos_policy" or "network")') ) parser.add_argument( '--action', diff --git a/openstackclient/tests/unit/network/v2/test_network_rbac.py b/openstackclient/tests/unit/network/v2/test_network_rbac.py index b3f8de4050..d7c71ea7d0 100644 --- a/openstackclient/tests/unit/network/v2/test_network_rbac.py +++ b/openstackclient/tests/unit/network/v2/test_network_rbac.py @@ -41,6 +41,7 @@ class TestCreateNetworkRBAC(TestNetworkRBAC): qos_object = network_fakes.FakeNetworkQosPolicy.create_one_qos_policy() sg_object = network_fakes.FakeNetworkSecGroup.create_one_security_group() as_object = network_fakes.FakeAddressScope.create_one_address_scope() + snp_object = network_fakes.FakeSubnetPool.create_one_subnet_pool() project = identity_fakes_v3.FakeProject.create_one_project() rbac_policy = network_fakes.FakeNetworkRBAC.create_one_network_rbac( attrs={'tenant_id': project.id, @@ -82,6 +83,8 @@ def setUp(self): return_value=self.sg_object) self.network.find_address_scope = mock.Mock( return_value=self.as_object) + self.network.find_subnet_pool = mock.Mock( + return_value=self.snp_object) self.projects_mock.get.return_value = self.project def test_network_rbac_create_no_type(self): @@ -232,6 +235,7 @@ def test_network_rbac_create_all_options(self): @ddt.data( ('qos_policy', "qos_object"), ('security_group', "sg_object"), + ('subnetpool', "snp_object"), ('address_scope', "as_object") ) @ddt.unpack diff --git a/releasenotes/notes/rbac-add-subnetpool-f1fc0e728ff61654.yaml b/releasenotes/notes/rbac-add-subnetpool-f1fc0e728ff61654.yaml new file mode 100644 index 0000000000..5bb3b59738 --- /dev/null +++ b/releasenotes/notes/rbac-add-subnetpool-f1fc0e728ff61654.yaml @@ -0,0 +1,4 @@ +features: + - | + Add ``subnetpool`` as a valid ``--type`` value for the + ``network rbac create`` and ``network rbac list`` commands. \ No newline at end of file From b328cf74df7f94a20de85b3c0992dcb65e818458 Mon Sep 17 00:00:00 2001 From: hackertron Date: Thu, 19 Mar 2020 14:35:54 +0100 Subject: [PATCH 2193/3095] Add '--force; parameter to 'openstack quota set' The compute service allows us to to force set a quota, setting a quota value that is less than the amount of the resource currently consumed. Expose this feature by way of a '--force' boolean parameter. Change-Id: I1d1ac1ac46f49f64794ffc8631e166935537966c --- openstackclient/common/quota.py | 8 +++ .../tests/functional/common/test_quota.py | 44 ++++++++++++++++ .../tests/unit/common/test_quota.py | 50 +++++++++++++++++++ ...flag-openstackclient-c172de2717e5cfac.yaml | 6 +++ 4 files changed, 108 insertions(+) create mode 100644 releasenotes/notes/force-flag-openstackclient-c172de2717e5cfac.yaml diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index 37437344dc..7a0dda1492 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -516,6 +516,11 @@ def get_parser(self, prog_name): metavar='', help=_('Set quotas for a specific '), ) + parser.add_argument( + '--force', + action='store_true', + help=_('Force quota update (only supported by compute)') + ) return parser def take_action(self, parsed_args): @@ -529,6 +534,9 @@ def take_action(self, parsed_args): if value is not None: compute_kwargs[k] = value + if parsed_args.force: + compute_kwargs['force'] = True + volume_kwargs = {} for k, v in VOLUME_QUOTAS.items(): value = getattr(parsed_args, k, None) diff --git a/openstackclient/tests/functional/common/test_quota.py b/openstackclient/tests/functional/common/test_quota.py index 9c05746077..4c2fc0e34d 100644 --- a/openstackclient/tests/functional/common/test_quota.py +++ b/openstackclient/tests/functional/common/test_quota.py @@ -165,3 +165,47 @@ def test_quota_set_class(self): # returned attributes self.assertTrue(cmd_output["key-pairs"] >= 0) self.assertTrue(cmd_output["snapshots"] >= 0) + + def test_quota_set_force(self): + """Test to set instance value by force """ + json_output = json.loads(self.openstack( + 'quota list -f json --detail --compute' + )) + in_use = limit = None + for j in json_output: + if j["Resource"] == "instances": + in_use = j["In Use"] + limit = j["Limit"] + + # Reduce count of in_use + in_use = in_use - 1 + # cannot have negative instances limit + if in_use < 0: + in_use = 0 + + # set the limit by force now + self.openstack( + 'quota set ' + self.PROJECT_NAME + + '--instances ' + str(in_use) + ' --force' + ) + cmd_output = json.loads(self.openstack( + 'quota show -f json ' + self.PROJECT_NAME + )) + self.assertIsNotNone(cmd_output) + self.assertEqual( + in_use, + cmd_output["instances"] + ) + + # Set instances limit to original limit now + self.openstack( + 'quota set ' + self.PROJECT_NAME + '--instances ' + str(limit) + ) + cmd_output = json.loads(self.openstack( + 'quota show -f json ' + self.PROJECT_NAME + )) + self.assertIsNotNone(cmd_output) + self.assertEqual( + limit, + cmd_output["instances"] + ) diff --git a/openstackclient/tests/unit/common/test_quota.py b/openstackclient/tests/unit/common/test_quota.py index bd59ca77fe..0018e0670d 100644 --- a/openstackclient/tests/unit/common/test_quota.py +++ b/openstackclient/tests/unit/common/test_quota.py @@ -831,6 +831,56 @@ def test_quota_set_with_class(self): self.assertNotCalled(self.network_mock.update_quota) self.assertIsNone(result) + def test_quota_set_with_force(self): + arglist = [ + '--cores', str(compute_fakes.core_num), + '--ram', str(compute_fakes.ram_num), + '--instances', str(compute_fakes.instance_num), + '--volumes', str(volume_fakes.QUOTA['volumes']), + '--subnets', str(network_fakes.QUOTA['subnet']), + '--force', + self.projects[0].name, + ] + verifylist = [ + ('cores', compute_fakes.core_num), + ('ram', compute_fakes.ram_num), + ('instances', compute_fakes.instance_num), + ('volumes', volume_fakes.QUOTA['volumes']), + ('subnet', network_fakes.QUOTA['subnet']), + ('force', True), + ('project', self.projects[0].name), + ] + self.app.client_manager.network_endpoint_enabled = True + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + kwargs_compute = { + 'cores': compute_fakes.core_num, + 'ram': compute_fakes.ram_num, + 'instances': compute_fakes.instance_num, + 'force': True, + } + kwargs_volume = { + 'volumes': volume_fakes.QUOTA['volumes'], + } + kwargs_network = { + 'subnet': network_fakes.QUOTA['subnet'], + } + self.compute_quotas_mock.update.assert_called_once_with( + self.projects[0].id, + **kwargs_compute + ) + self.volume_quotas_mock.update.assert_called_once_with( + self.projects[0].id, + **kwargs_volume + ) + self.network_mock.update_quota.assert_called_once_with( + self.projects[0].id, + **kwargs_network + ) + self.assertIsNone(result) + class TestQuotaShow(TestQuota): diff --git a/releasenotes/notes/force-flag-openstackclient-c172de2717e5cfac.yaml b/releasenotes/notes/force-flag-openstackclient-c172de2717e5cfac.yaml new file mode 100644 index 0000000000..e1980d1186 --- /dev/null +++ b/releasenotes/notes/force-flag-openstackclient-c172de2717e5cfac.yaml @@ -0,0 +1,6 @@ +--- +features: + - Add ``--force`` options to the ``openstack quota set`` + command. The compute service allows us to to force set a quota, setting a + quota value that is less than the amount of the resource currently + consumed. Expose this feature by way of a ``--force`` boolean parameter. From da4e1ca95f276f761844b5cf119eb70055e2c126 Mon Sep 17 00:00:00 2001 From: Dmitry Tantsur Date: Fri, 17 Apr 2020 13:14:47 +0200 Subject: [PATCH 2194/3095] Remove Babel from requirements It's not a runtime dependency (and even oslo.i18n has dropped it). The translation infrastructure installs Babel explicitly. See this mailing list thread for a full reasoning: http://lists.openstack.org/pipermail/openstack-discuss/2020-April/014227.html Keeping Babel in lower-constraints since other projects still pull it. Change-Id: Ib24d2941ac0f780f9092e48b17c343f8eb1d7151 --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index b6f97b4d35..3aac36af2f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,6 @@ pbr!=2.1.0,>=2.0.0 # Apache-2.0 six>=1.10.0 # MIT -Babel!=2.4.0,>=2.3.4 # BSD cliff!=2.9.0,>=2.8.0 # Apache-2.0 openstacksdk>=0.44.0 # Apache-2.0 osc-lib>=2.0.0 # Apache-2.0 From 662024646c585de6fae97390af754916151ad391 Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Tue, 28 Apr 2020 15:18:35 +0000 Subject: [PATCH 2195/3095] Update master for stable/ussuri Add file to the reno documentation build to show release notes for stable/ussuri. Use pbr instruction to increment the minor version number automatically so that master versions are higher than the versions on stable/ussuri. Change-Id: Ia0c7a6f2d84a7ed08514a64a73a5de577fcfb1d0 Sem-Ver: feature --- releasenotes/source/index.rst | 1 + releasenotes/source/ussuri.rst | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 releasenotes/source/ussuri.rst diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index fed41f9ec4..889eeb0c5c 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,6 +6,7 @@ OpenStackClient Release Notes :maxdepth: 1 unreleased + ussuri train stein rocky diff --git a/releasenotes/source/ussuri.rst b/releasenotes/source/ussuri.rst new file mode 100644 index 0000000000..e21e50e0c6 --- /dev/null +++ b/releasenotes/source/ussuri.rst @@ -0,0 +1,6 @@ +=========================== +Ussuri Series Release Notes +=========================== + +.. release-notes:: + :branch: stable/ussuri From cc135e3b04a2804a3ea3af487ab5dda797303d34 Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Tue, 28 Apr 2020 15:18:38 +0000 Subject: [PATCH 2196/3095] Add Python3 victoria unit tests This is an automatically generated patch to ensure unit testing is in place for all the of the tested runtimes for victoria. See also the PTI in governance [1]. [1]: https://governance.openstack.org/tc/reference/project-testing-interface.html Change-Id: I5aa55a9582127ce07efd841b15857c272bebdb9f --- .zuul.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.zuul.yaml b/.zuul.yaml index 4901a1ed8d..1ed2ab9eef 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -225,7 +225,7 @@ - osc-tox-unit-tips - openstack-cover-jobs - openstack-lower-constraints-jobs - - openstack-python3-ussuri-jobs + - openstack-python3-victoria-jobs - publish-openstack-docs-pti - check-requirements - release-notes-jobs-python3 From ae6731710965bd5be6827223abf4b679266d079c Mon Sep 17 00:00:00 2001 From: Adam Harwell Date: Mon, 4 May 2020 16:06:02 -0700 Subject: [PATCH 2197/3095] Correct image lookup during server rebuild The switch to using glance from the SDK accidentally used get_image directly during a server rebuild, when it should have used find_image to match existing functionality. Bug introduced in: I36f292fb70c98f6e558f58be55d533d979c47ca7 Change-Id: I2005bd40a1bd6719670c7f7854316b4f9801b140 Story: 2007620 Task: 39643 --- openstackclient/compute/v2/server.py | 9 +++-- .../tests/unit/compute/v2/test_server.py | 36 +++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 8be78049c8..93e9f966ae 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1921,9 +1921,12 @@ def _show_progress(progress): compute_client.servers, parsed_args.server) # If parsed_args.image is not set, default to the currently used one. - image_id = parsed_args.image or server.to_dict().get( - 'image', {}).get('id') - image = image_client.get_image(image_id) + if parsed_args.image: + image = image_client.find_image( + parsed_args.image, ignore_missing=False) + else: + image_id = server.to_dict().get('image', {}).get('id') + image = image_client.get_image(image_id) kwargs = {} if parsed_args.property: diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 8ec8217dfe..7e4c71c50c 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -3577,6 +3577,41 @@ def setUp(self): self.cmd = server.RebuildServer(self.app, None) + def test_rebuild_with_image_name(self): + image_name = 'my-custom-image' + user_image = image_fakes.FakeImage.create_one_image( + attrs={'name': image_name}) + self.find_image_mock.return_value = user_image + + attrs = { + 'image': { + 'id': user_image.id + }, + 'networks': {}, + 'adminPass': 'passw0rd', + } + new_server = compute_fakes.FakeServer.create_one_server(attrs=attrs) + self.server.rebuild.return_value = new_server + + arglist = [ + self.server.id, + '--image', image_name + ] + verifylist = [ + ('server', self.server.id), + ('image', image_name) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Get the command object to test. + self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.find_image_mock.assert_called_with( + image_name, ignore_missing=False) + self.get_image_mock.assert_called_with(user_image.id) + self.server.rebuild.assert_called_with(user_image, None) + def test_rebuild_with_current_image(self): arglist = [ self.server.id, @@ -3590,6 +3625,7 @@ def test_rebuild_with_current_image(self): self.cmd.take_action(parsed_args) self.servers_mock.get.assert_called_with(self.server.id) + self.find_image_mock.assert_not_called() self.get_image_mock.assert_called_with(self.image.id) self.server.rebuild.assert_called_with(self.image, None) From 52ff421e3d339f81c2625bff429e6829a2d9af67 Mon Sep 17 00:00:00 2001 From: Pete Zaitcev Date: Thu, 14 May 2020 13:53:38 -0500 Subject: [PATCH 2198/3095] Resolve PEP8 No idea how this happened, but reviews started failing the pep8 gate job. The failures are legitimate, see the commit. I guess the pep8 tests became smarter and found these issues. Change-Id: Id9a0dad644134dafd68eed37fe8f41c583d7a619 --- openstackclient/tests/functional/identity/v3/test_project.py | 1 - openstackclient/tests/unit/network/v2/fakes.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/openstackclient/tests/functional/identity/v3/test_project.py b/openstackclient/tests/functional/identity/v3/test_project.py index 96d41c3a14..27cf448130 100644 --- a/openstackclient/tests/functional/identity/v3/test_project.py +++ b/openstackclient/tests/functional/identity/v3/test_project.py @@ -79,7 +79,6 @@ def test_project_set(self): '--disable ' '--property k0=v0 ' '%(name)s' % {'new_name': new_project_name, - 'domain': self.domain_name, 'name': project_name}) self.assertEqual(0, len(raw_output)) # check project details diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index 2b88986a27..cef0a11c91 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -1833,7 +1833,7 @@ def create_one_port_forwarding(attrs=None): """ attrs = attrs or {} floatingip_id = ( - attrs.get('floatingip_id') or'floating-ip-id-' + uuid.uuid4().hex + attrs.get('floatingip_id') or 'floating-ip-id-' + uuid.uuid4().hex ) # Set default attributes. port_forwarding_attrs = { From f6ee42cd32833bf7484cbf18eab9ed110a5e9782 Mon Sep 17 00:00:00 2001 From: Pete Zaitcev Date: Tue, 12 May 2020 23:19:38 -0500 Subject: [PATCH 2199/3095] Cleanup: remove a useless reference to "object" The method "object_list" does not have an argument "object", so we were using a built-in class "object" by mistake. Change-Id: I74687659223d31d3c3c119eee5874edff30634fd --- openstackclient/api/object_store_v1.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/api/object_store_v1.py b/openstackclient/api/object_store_v1.py index c8514a5723..dcb32878c6 100644 --- a/openstackclient/api/object_store_v1.py +++ b/openstackclient/api/object_store_v1.py @@ -328,7 +328,7 @@ def object_list( headers will be a dict and all header names will be lowercase. """ - if container is None or object is None: + if container is None: return None params['format'] = 'json' From 41a2e82939e3fa60922923263e8d96d9c1da2635 Mon Sep 17 00:00:00 2001 From: Pete Zaitcev Date: Fri, 15 May 2020 01:00:32 -0500 Subject: [PATCH 2200/3095] Make container list --all work The caller in openstackclient/object/v1/object.py passed a keyword argument full_listing, but the eventual callee container_list() expected all_data. So, --all did not work at all. The issue passed undetected because --all did not have a test, so we added a unit test. In addition, exisiting tests were using a test set that did not look like the real container listing, so we changed LIST_CONTAINER_RESP to be realistic. Change-Id: Id0604bcab25892e43c26cd6656b2b2eef5daa69b --- openstackclient/api/object_store_v1.py | 12 ++-- .../tests/unit/api/test_object_store_v1.py | 60 +++++++++---------- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/openstackclient/api/object_store_v1.py b/openstackclient/api/object_store_v1.py index c8514a5723..220edb984e 100644 --- a/openstackclient/api/object_store_v1.py +++ b/openstackclient/api/object_store_v1.py @@ -87,7 +87,7 @@ def container_delete( def container_list( self, - all_data=False, + full_listing=False, limit=None, marker=None, end_marker=None, @@ -96,7 +96,7 @@ def container_list( ): """Get containers in an account - :param boolean all_data: + :param boolean full_listing: if True, return a full listing, else returns a max of 10000 listings :param integer limit: @@ -113,7 +113,7 @@ def container_list( params['format'] = 'json' - if all_data: + if full_listing: data = listing = self.container_list( limit=limit, marker=marker, @@ -299,7 +299,7 @@ def object_delete( def object_list( self, container=None, - all_data=False, + full_listing=False, limit=None, marker=None, end_marker=None, @@ -311,7 +311,7 @@ def object_list( :param string container: container name to get a listing for - :param boolean all_data: + :param boolean full_listing: if True, return a full listing, else returns a max of 10000 listings :param integer limit: @@ -332,7 +332,7 @@ def object_list( return None params['format'] = 'json' - if all_data: + if full_listing: data = listing = self.object_list( container=container, limit=limit, diff --git a/openstackclient/tests/unit/api/test_object_store_v1.py b/openstackclient/tests/unit/api/test_object_store_v1.py index 96c68d5a1e..b9e0740c88 100644 --- a/openstackclient/tests/unit/api/test_object_store_v1.py +++ b/openstackclient/tests/unit/api/test_object_store_v1.py @@ -30,8 +30,10 @@ FAKE_OBJECT = 'spigot' LIST_CONTAINER_RESP = [ - 'qaz', - 'fred', + {"name": "qaz", "count": 0, "bytes": 0, + "last_modified": "2020-05-16T05:52:07.377550"}, + {"name": "fred", "count": 0, "bytes": 0, + "last_modified": "2020-05-16T05:55:07.377550"}, ] LIST_OBJECT_RESP = [ @@ -117,34 +119,32 @@ def test_container_list_marker_limit_end(self): ) self.assertEqual(LIST_CONTAINER_RESP, ret) -# def test_container_list_full_listing(self): -# sess = self.app.client_manager.session -# -# def side_effect(*args, **kwargs): -# rv = sess.get().json.return_value -# sess.get().json.return_value = [] -# sess.get().json.side_effect = None -# return rv -# -# resp = [{'name': 'is-name'}] -# sess.get().json.return_value = resp -# sess.get().json.side_effect = side_effect -# -# data = lib_container.list_containers( -# self.app.client_manager.session, -# fake_url, -# full_listing=True, -# ) -# -# # Check expected values -# sess.get.assert_called_with( -# fake_url, -# params={ -# 'format': 'json', -# 'marker': 'is-name', -# } -# ) -# self.assertEqual(resp, data) + def test_container_list_full_listing(self): + self.requests_mock.register_uri( + 'GET', + FAKE_URL + '?limit=1&format=json', + json=[LIST_CONTAINER_RESP[0]], + status_code=200, + ) + self.requests_mock.register_uri( + 'GET', + FAKE_URL + + '?marker=%s&limit=1&format=json' % LIST_CONTAINER_RESP[0]['name'], + json=[LIST_CONTAINER_RESP[1]], + status_code=200, + ) + self.requests_mock.register_uri( + 'GET', + FAKE_URL + + '?marker=%s&limit=1&format=json' % LIST_CONTAINER_RESP[1]['name'], + json=[], + status_code=200, + ) + ret = self.api.container_list( + limit=1, + full_listing=True, + ) + self.assertEqual(LIST_CONTAINER_RESP, ret) def test_container_show(self): headers = { From 709dfd9c214218d69a9278d0a12f09af5b4fdff2 Mon Sep 17 00:00:00 2001 From: Andreas Jaeger Date: Mon, 18 May 2020 22:28:48 +0200 Subject: [PATCH 2201/3095] Switch to newer openstackdocstheme and reno versions Switch to openstackdocstheme 2.2.1 and reno 3.1.0 versions. Using these versions will allow especially: * Linking from HTML to PDF document * Allow parallel building of documents * Fix some rendering problems Update Sphinx version as well. openstackdocstheme renames some variables, so follow the renames before the next release removes them. A couple of variables are also not needed anymore, remove them. Set openstackdocs_auto_name to use 'project' as name. Change pygments_style to 'native' since old theme version always used 'native' and the theme now respects the setting and using 'sphinx' can lead to some strange rendering. Depends-On: https://review.opendev.org/729744 Change-Id: Id3989cabdbf2204821531b00149aa0f1cb8a4955 --- doc/requirements.txt | 6 +++--- doc/source/conf.py | 14 +++++--------- releasenotes/source/conf.py | 12 ++++-------- 3 files changed, 12 insertions(+), 20 deletions(-) diff --git a/doc/requirements.txt b/doc/requirements.txt index 5cfc337b13..ef24b68773 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -1,9 +1,9 @@ # The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -openstackdocstheme>=1.23.2 # Apache-2.0 -reno>=2.5.0 # Apache-2.0 -sphinx!=1.6.6,!=1.6.7,>=1.6.5 # BSD +openstackdocstheme>=2.2.1 # Apache-2.0 +reno>=3.1.0 # Apache-2.0 +sphinx>=2.0.0,!=2.1.0 # BSD sphinxcontrib-apidoc>=0.2.0 # BSD # redirect tests in docs diff --git a/doc/source/conf.py b/doc/source/conf.py index 4de95fbb82..2216ddd7c2 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -29,11 +29,12 @@ ] # openstackdocstheme options -repository_name = 'openstack/python-openstackclient' -use_storyboard = True +openstackdocs_repo_name = 'openstack/python-openstackclient' +openstackdocs_use_storyboard = True +openstackdocs_auto_name = False # Add project 'foo' to this list to enable the :foo-doc: role -openstack_projects = [ +openstackdocs_projects = [ 'neutron', ] @@ -83,7 +84,7 @@ #show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = 'native' # A list of ignored prefixes for module index sorting. modindex_common_prefix = ['openstackclient.'] @@ -126,11 +127,6 @@ # so a file named "default.css" will overwrite the builtin "default.css". #html_static_path = ['_static'] -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' -html_last_updated_fmt = '%Y-%m-%d %H:%M' - # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py index 4060f49c79..206c0ce2ac 100644 --- a/releasenotes/source/conf.py +++ b/releasenotes/source/conf.py @@ -44,8 +44,9 @@ ] # openstackdocstheme options -repository_name = 'openstack/python-openstackclient' -use_storyboard = True +openstackdocs_repo_name = 'openstack/python-openstackclient' +openstackdocs_use_storyboard = True +openstackdocs_auto_name = False # Set aliases for extlinks # * lpbug - generic Launchpad bug :lpbug:`123456` @@ -118,7 +119,7 @@ # show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = 'native' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] @@ -169,11 +170,6 @@ # directly to the root of the documentation. # html_extra_path = [] -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -# html_last_updated_fmt = '%b %d, %Y' -html_last_updated_fmt = '%Y-%m-%d %H:%M' - # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True From 7049fd85fc3a78b2653a4e1c153c904212b8fed8 Mon Sep 17 00:00:00 2001 From: zhangboye Date: Fri, 22 May 2020 15:05:12 +0800 Subject: [PATCH 2202/3095] Add py38 package metadata Change-Id: I75ecaed730a2ef870f41812cfbf55c74fdc36b7b --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index 4129be24b1..1ba0691cc0 100644 --- a/setup.cfg +++ b/setup.cfg @@ -17,6 +17,7 @@ classifier = Programming Language :: Python :: 3 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 [files] packages = From 533af9f1b2de40d98f69e83cdf89ecf254cf3879 Mon Sep 17 00:00:00 2001 From: yanpuqing Date: Mon, 20 May 2019 06:47:44 +0000 Subject: [PATCH 2203/3095] Client should parse string to boolean for value 'is_domain' When we use "--property" parameter, client get lists these the value is string type, but the type of the value 'is_domain' should be boolean, so we should judge it and parse it. The patch parse string to boolean for value 'is_domain'. Co-Authored-By: Lance Bragstad Change-Id: I37c9eb854524bde3a1530bfe2e3a03810fb1a676 Task: 30039 Story: 2005246 --- openstackclient/identity/v3/project.py | 8 ++ .../tests/unit/identity/v3/test_project.py | 120 ++++++++++++++++++ .../notes/bug-2005246-3fb70206bafc5444.yaml | 5 + 3 files changed, 133 insertions(+) create mode 100644 releasenotes/notes/bug-2005246-3fb70206bafc5444.yaml diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index e32da165b4..5e8ce82981 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -106,6 +106,14 @@ def take_action(self, parsed_args): kwargs = {} if parsed_args.property: kwargs = parsed_args.property.copy() + if 'is_domain' in kwargs.keys(): + if kwargs['is_domain'].lower() == "true": + kwargs['is_domain'] = True + elif kwargs['is_domain'].lower() == "false": + kwargs['is_domain'] = False + elif kwargs['is_domain'].lower() == "none": + kwargs['is_domain'] = None + kwargs['tags'] = list(set(parsed_args.tags)) try: diff --git a/openstackclient/tests/unit/identity/v3/test_project.py b/openstackclient/tests/unit/identity/v3/test_project.py index 8852aa8ecc..dfd0805b29 100644 --- a/openstackclient/tests/unit/identity/v3/test_project.py +++ b/openstackclient/tests/unit/identity/v3/test_project.py @@ -357,6 +357,126 @@ def test_project_create_property(self): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, data) + def test_project_create_is_domain_false_property(self): + arglist = [ + '--property', 'is_domain=false', + self.project.name, + ] + verifylist = [ + ('parent', None), + ('enable', False), + ('disable', False), + ('name', self.project.name), + ('tags', []), + ('property', {'is_domain': 'false'}), + ('name', self.project.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) + + # Set expected values + kwargs = { + 'name': self.project.name, + 'domain': None, + 'description': None, + 'enabled': True, + 'parent': None, + 'is_domain': False, + 'tags': [], + 'options': {}, + } + self.projects_mock.create.assert_called_with( + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) + + def test_project_create_is_domain_true_property(self): + arglist = [ + '--property', 'is_domain=true', + self.project.name, + ] + verifylist = [ + ('parent', None), + ('enable', False), + ('disable', False), + ('name', self.project.name), + ('tags', []), + ('property', {'is_domain': 'true'}), + ('name', self.project.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) + + # Set expected values + kwargs = { + 'name': self.project.name, + 'domain': None, + 'description': None, + 'enabled': True, + 'parent': None, + 'is_domain': True, + 'tags': [], + 'options': {}, + } + self.projects_mock.create.assert_called_with( + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) + + def test_project_create_is_domain_none_property(self): + arglist = [ + '--property', 'is_domain=none', + self.project.name, + ] + verifylist = [ + ('parent', None), + ('enable', False), + ('disable', False), + ('name', self.project.name), + ('tags', []), + ('property', {'is_domain': 'none'}), + ('name', self.project.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) + + # Set expected values + kwargs = { + 'name': self.project.name, + 'domain': None, + 'description': None, + 'enabled': True, + 'parent': None, + 'is_domain': None, + 'tags': [], + 'options': {}, + } + self.projects_mock.create.assert_called_with( + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) + def test_project_create_parent(self): self.parent = identity_fakes.FakeProject.create_one_project() self.project = identity_fakes.FakeProject.create_one_project( diff --git a/releasenotes/notes/bug-2005246-3fb70206bafc5444.yaml b/releasenotes/notes/bug-2005246-3fb70206bafc5444.yaml new file mode 100644 index 0000000000..4d7bdd5d42 --- /dev/null +++ b/releasenotes/notes/bug-2005246-3fb70206bafc5444.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + [Story `2005246 `_] + The `is_domain` property safely handles type checking. From a15b1addb41147415716417f385f8ee32aace64b Mon Sep 17 00:00:00 2001 From: Alfredo Moralejo Date: Mon, 1 Jun 2020 16:39:15 +0200 Subject: [PATCH 2204/3095] Replace assertItemsEqual with assertCountEqual assertItemsEqual was removed from Python's unittest.TestCase in Python 3.3 [1][2]. We have been able to use them since then, because testtools required unittest2, which still included it. With testtools removing Python 2.7 support [3][4], we will lose support for assertItemsEqual, so we should switch to use assertCountEqual. [1] - https://bugs.python.org/issue17866 [2] - https://hg.python.org/cpython/rev/d9921cb6e3cd [3] - testing-cabal/testtools#286 [4] - testing-cabal/testtools#277 Change-Id: I1ad0da8deda3a8cbec384b5a9c88860a526eb48c --- openstackclient/tests/unit/common/test_parseractions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openstackclient/tests/unit/common/test_parseractions.py b/openstackclient/tests/unit/common/test_parseractions.py index d015da430e..736cd0b6d2 100644 --- a/openstackclient/tests/unit/common/test_parseractions.py +++ b/openstackclient/tests/unit/common/test_parseractions.py @@ -92,7 +92,7 @@ def test_good_values(self): {'req1': 'aaa', 'req2': 'bbb'}, {'req1': '', 'req2': ''}, ] - self.assertItemsEqual(expect, actual) + self.assertCountEqual(expect, actual) def test_empty_required_optional(self): self.parser.add_argument( @@ -116,7 +116,7 @@ def test_empty_required_optional(self): {'req1': 'aaa', 'req2': 'bbb'}, {'req1': '', 'req2': ''}, ] - self.assertItemsEqual(expect, actual) + self.assertCountEqual(expect, actual) def test_error_values_with_comma(self): self.assertRaises( From b5389dab026658ba5acc40fa8ee0513dcf6c3f5f Mon Sep 17 00:00:00 2001 From: Andreas Jaeger Date: Thu, 4 Jun 2020 18:29:49 +0200 Subject: [PATCH 2205/3095] Remove congress Congress and python-congressclient have been retired and also removed from global requirements, thus requirements-check job fails. Remove congress from this repo. Change-Id: Ibf68fee49e69264a1c46b2f456901d2620befe3c --- doc/requirements.txt | 1 - doc/source/cli/commands.rst | 4 ---- doc/source/cli/plugin-commands/congress.rst | 4 ---- doc/source/cli/plugin-commands/index.rst | 1 - doc/source/contributor/plugins.rst | 1 - lower-constraints.txt | 1 - 6 files changed, 12 deletions(-) delete mode 100644 doc/source/cli/plugin-commands/congress.rst diff --git a/doc/requirements.txt b/doc/requirements.txt index 5cfc337b13..8ceb80a5cf 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -14,7 +14,6 @@ aodhclient>=0.9.0 # Apache-2.0 gnocchiclient>=3.3.1 # Apache-2.0 osc-placement>=1.7.0 # Apache-2.0 python-barbicanclient>=4.5.2 # Apache-2.0 -python-congressclient<2000,>=1.9.0 # Apache-2.0 python-designateclient>=2.7.0 # Apache-2.0 python-heatclient>=1.10.0 # Apache-2.0 python-ironicclient>=2.3.0 # Apache-2.0 diff --git a/doc/source/cli/commands.rst b/doc/source/cli/commands.rst index 7c38aa5b5c..97a829b558 100644 --- a/doc/source/cli/commands.rst +++ b/doc/source/cli/commands.rst @@ -195,10 +195,6 @@ conflicts when creating new plugins. For a complete list check out * ``cluster profile``: (**Clustering (Senlin)**) * ``cluster profile type``: (**Clustering (Senlin)**) * ``cluster receiver``: (**Clustering (Senlin)**) -* ``congress datasource``: (**Policy (Congress)**) -* ``congress driver``: (**Policy (Congress)**) -* ``congress policy``: (**Policy (Congress)**) -* ``congress policy rule``: (**Policy (Congress)**) * ``cron trigger``: (**Workflow Engine (Mistral)**) * ``database flavor``: (**Database (Trove)**) * ``dataprocessing data source``: (**Data Processing (Sahara)**) diff --git a/doc/source/cli/plugin-commands/congress.rst b/doc/source/cli/plugin-commands/congress.rst deleted file mode 100644 index 7a1e63b76a..0000000000 --- a/doc/source/cli/plugin-commands/congress.rst +++ /dev/null @@ -1,4 +0,0 @@ -congress --------- - -.. autoprogram-cliff:: openstack.congressclient.v1 diff --git a/doc/source/cli/plugin-commands/index.rst b/doc/source/cli/plugin-commands/index.rst index 2c3bda3e0f..8217115faa 100644 --- a/doc/source/cli/plugin-commands/index.rst +++ b/doc/source/cli/plugin-commands/index.rst @@ -9,7 +9,6 @@ Plugin Commands aodh barbican - congress designate gnocchi heat diff --git a/doc/source/contributor/plugins.rst b/doc/source/contributor/plugins.rst index d6bcbee197..374b274e6a 100644 --- a/doc/source/contributor/plugins.rst +++ b/doc/source/contributor/plugins.rst @@ -27,7 +27,6 @@ The following is a list of projects that are an OpenStackClient plugin. - gnocchiclient - osc-placement - python-barbicanclient -- python-congressclient - python-designateclient - python-heatclient - python-ironicclient diff --git a/lower-constraints.txt b/lower-constraints.txt index df79ef5dd3..0c754f5c27 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -87,7 +87,6 @@ pyparsing==2.1.0 pyperclip==1.5.27 python-barbicanclient==4.5.2 python-cinderclient==3.3.0 -python-congressclient==1.9.0 python-dateutil==2.5.3 python-designateclient==2.7.0 python-glanceclient==2.8.0 From aa7b84fd08ef3d4d096d76837c636b708d743f46 Mon Sep 17 00:00:00 2001 From: Maari Tamm Date: Thu, 6 Feb 2020 10:36:41 +0000 Subject: [PATCH 2206/3095] Add OpenStack Client for Manila docs This commit includes documentation about OSC implementation for Manila. bp openstack-client-support Change-Id: Ic91a81e16e506103c08ef42ed0f8634a2b70e1dd --- doc/requirements.txt | 1 + doc/source/cli/plugin-commands/index.rst | 1 + doc/source/cli/plugin-commands/manila.rst | 4 ++++ 3 files changed, 6 insertions(+) create mode 100644 doc/source/cli/plugin-commands/manila.rst diff --git a/doc/requirements.txt b/doc/requirements.txt index 5cfc337b13..24d43970cc 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -20,6 +20,7 @@ python-heatclient>=1.10.0 # Apache-2.0 python-ironicclient>=2.3.0 # Apache-2.0 python-ironic-inspector-client>=1.5.0 # Apache-2.0 python-karborclient>=0.6.0 # Apache-2.0 +python-manilaclient>=2.0.0 # Apache-2.0 python-mistralclient!=3.2.0,>=3.1.0 # Apache-2.0 python-muranoclient>=0.8.2 # Apache-2.0 python-neutronclient>=6.7.0 # Apache-2.0 diff --git a/doc/source/cli/plugin-commands/index.rst b/doc/source/cli/plugin-commands/index.rst index 2c3bda3e0f..2eb89dd748 100644 --- a/doc/source/cli/plugin-commands/index.rst +++ b/doc/source/cli/plugin-commands/index.rst @@ -16,6 +16,7 @@ Plugin Commands ironic ironic-inspector karbor + manila mistral neutron octavia diff --git a/doc/source/cli/plugin-commands/manila.rst b/doc/source/cli/plugin-commands/manila.rst new file mode 100644 index 0000000000..c42d6065f4 --- /dev/null +++ b/doc/source/cli/plugin-commands/manila.rst @@ -0,0 +1,4 @@ +manila +------ + +.. autoprogram-cliff:: openstack.share.v2 From 2cb41935790e0d99139340ede22792ec8a842b00 Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Mon, 8 Jun 2020 09:19:24 -0500 Subject: [PATCH 2207/3095] Add cliff to libs-from-git for devstack functional tips We should also use cliff from git in the tips jobs so that we can see whether changes there break things. Change-Id: Ie3375eb7bafafef7fa8209aa6500d1254e29954e --- .zuul.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.zuul.yaml b/.zuul.yaml index 1ed2ab9eef..5e0e73f2ea 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -132,7 +132,7 @@ - openstack/python-openstackclient vars: devstack_localrc: - LIBS_FROM_GIT: python-openstackclient,openstacksdk,osc-lib,os-client-config + LIBS_FROM_GIT: python-openstackclient,openstacksdk,osc-lib,os-client-config,cliff # This is insufficient, but leaving it here as a reminder of what may # someday be all we need to make this work # disable_python3_package swift From 7696593dc18aff34ab0abbc9020616b608219e58 Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Mon, 8 Jun 2020 09:26:00 -0500 Subject: [PATCH 2208/3095] Remove os-client-config references We've depended on openstacksdk for config for ages now, clean up after ourselves and stop installing it in tests. Change-Id: I66b3ec2a36bc462d2f1ac151e95ccbdc946076b8 --- .zuul.yaml | 5 +---- lower-constraints.txt | 1 - .../tests/unit/integ/cli/test_shell.py | 20 ++++--------------- test-requirements.txt | 1 - tox.ini | 2 -- 5 files changed, 5 insertions(+), 24 deletions(-) diff --git a/.zuul.yaml b/.zuul.yaml index 5e0e73f2ea..734b1ededc 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -9,7 +9,6 @@ - openstack/cliff - openstack/keystoneauth - openstack/openstacksdk - - openstack/os-client-config - openstack/osc-lib - openstack/python-openstackclient vars: @@ -30,7 +29,6 @@ - openstack/cliff - openstack/keystoneauth - openstack/openstacksdk - - openstack/os-client-config - openstack/osc-lib - openstack/python-openstackclient vars: @@ -127,12 +125,11 @@ - openstack/cliff - openstack/keystoneauth - openstack/openstacksdk - - openstack/os-client-config - openstack/osc-lib - openstack/python-openstackclient vars: devstack_localrc: - LIBS_FROM_GIT: python-openstackclient,openstacksdk,osc-lib,os-client-config,cliff + LIBS_FROM_GIT: python-openstackclient,openstacksdk,osc-lib,cliff # This is insufficient, but leaving it here as a reminder of what may # someday be all we need to make this work # disable_python3_package swift diff --git a/lower-constraints.txt b/lower-constraints.txt index 0c754f5c27..f16eca4184 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -51,7 +51,6 @@ munch==2.1.0 netaddr==0.7.18 netifaces==0.10.4 openstacksdk==0.44.0 -os-client-config==1.28.0 os-service-types==1.7.0 os-testr==1.0.0 osc-lib==2.0.0 diff --git a/openstackclient/tests/unit/integ/cli/test_shell.py b/openstackclient/tests/unit/integ/cli/test_shell.py index 0c98a12942..5788b47375 100644 --- a/openstackclient/tests/unit/integ/cli/test_shell.py +++ b/openstackclient/tests/unit/integ/cli/test_shell.py @@ -20,16 +20,6 @@ from openstackclient.tests.unit.integ import base as test_base from openstackclient.tests.unit import test_shell -# NOTE(dtroyer): Attempt the import to detect if the SDK installed is new -# enough to contain the os_client_config code. If so, use -# that path for mocks. -CONFIG_MOCK_BASE = "openstack.config.loader" -try: - from openstack.config import defaults # noqa -except ImportError: - # Fall back to os-client-config - CONFIG_MOCK_BASE = "os_client_config.config" - class TestIntegShellCliNoAuth(test_base.TestInteg): @@ -455,8 +445,8 @@ def get_temp_file_path(self, filename): temp_dir = self.useFixture(fixtures.TempDir()) return temp_dir.join(filename) - @mock.patch(CONFIG_MOCK_BASE + ".OpenStackConfig._load_vendor_file") - @mock.patch(CONFIG_MOCK_BASE + ".OpenStackConfig._load_config_file") + @mock.patch("openstack.config.loader.OpenStackConfig._load_vendor_file") + @mock.patch("openstack.config.loader.OpenStackConfig._load_config_file") def test_shell_args_precedence_1(self, config_mock, vendor_mock): """Precedence run 1 @@ -473,7 +463,6 @@ def vendor_mock_return(): return ('file.yaml', copy.deepcopy(test_shell.PUBLIC_1)) vendor_mock.side_effect = vendor_mock_return - print("CONFIG_MOCK_BASE=%s" % CONFIG_MOCK_BASE) _shell = shell.OpenStackShell() _shell.run( "--os-password qaz extension list".split(), @@ -527,8 +516,8 @@ def vendor_mock_return(): # +env, +cli, +occ # see test_shell_args_precedence_2() - @mock.patch(CONFIG_MOCK_BASE + ".OpenStackConfig._load_vendor_file") - @mock.patch(CONFIG_MOCK_BASE + ".OpenStackConfig._load_config_file") + @mock.patch("openstack.config.loader.OpenStackConfig._load_vendor_file") + @mock.patch("openstack.config.loader.OpenStackConfig._load_config_file") def test_shell_args_precedence_2(self, config_mock, vendor_mock): """Precedence run 2 @@ -545,7 +534,6 @@ def vendor_mock_return(): return ('file.yaml', copy.deepcopy(test_shell.PUBLIC_1)) vendor_mock.side_effect = vendor_mock_return - print("CONFIG_MOCK_BASE=%s" % CONFIG_MOCK_BASE) _shell = shell.OpenStackShell() _shell.run( "--os-username zarquon --os-password qaz " diff --git a/test-requirements.txt b/test-requirements.txt index ed3cf0c004..f2b6a13402 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -9,7 +9,6 @@ oslotest>=3.2.0 # Apache-2.0 requests>=2.14.2 # Apache-2.0 requests-mock>=1.2.0 # Apache-2.0 stevedore>=1.20.0 # Apache-2.0 -os-client-config>=1.28.0 # Apache-2.0 stestr>=1.0.0 # Apache-2.0 testtools>=2.2.0 # MIT tempest>=17.1.0 # Apache-2.0 diff --git a/tox.ini b/tox.ini index 3b4b66a0fe..3a2a307de8 100644 --- a/tox.ini +++ b/tox.ini @@ -64,7 +64,6 @@ commands = python -m pip install -q -U -e "git+file://{toxinidir}/../cliff#egg=cliff" python -m pip install -q -U -e "git+file://{toxinidir}/../keystoneauth#egg=keystoneauth" python -m pip install -q -U -e "git+file://{toxinidir}/../osc-lib#egg=osc_lib" - python -m pip install -q -U -e "git+file://{toxinidir}/../os-client-config#egg=os_client_config" pythom -m pip install -q -e "git+file://{toxinidir}/../openstacksdk#egg=openstacksdk" python -m pip freeze stestr run {posargs} @@ -83,7 +82,6 @@ commands = python -m pip install -q -U -e "git+file://{toxinidir}/../cliff#egg=cliff" python -m pip install -q -U -e "git+file://{toxinidir}/../keystoneauth#egg=keystoneauth" python -m pip install -q -U -e "git+file://{toxinidir}/../osc-lib#egg=osc_lib" - python -m pip install -q -U -e "git+file://{toxinidir}/../os-client-config#egg=os_client_config" python -m pip install -q -U -e "git+file://{toxinidir}/../openstacksdk#egg=openstacksdk" python -m pip freeze stestr run {posargs} From 26878e7d53a531adb67c9ddde8fd6d3c3092ce34 Mon Sep 17 00:00:00 2001 From: Eric Fried Date: Mon, 4 Nov 2019 17:16:34 -0600 Subject: [PATCH 2209/3095] identity: autogenerate docs $namespace = openstack.identity.v{2|3} The subcommand documents for $namespace were hardcoded and thus prone to drift over time. This commit removes the hardcoded content and uses the autoprogram-cliff directive to generate them automatically from the subcommand configuration classes. Special things: - Some reorganization happened here. Certain subcommand names, such as `endpoint` and `project`, are shared by identify v2 and v3. Previously the hardcoded documents had them combined and interleaved. Attempting to preserve this with autoprogram-cliff would have required significant additional infrastructure. However, since most readers care completely about one and not at all about the other, we instead split the v2 and v3 versions of these commands into separate pages. In case links to the old pages exist in the wild, they are preserved, but moved (with redirects) to a hidden directory, and populated simply with links to the new version-specific generated documents. - The `federation domain` and `federation project` subcommands were previously absent from the docs. They are added. These are such small commands and they seem related, so they're put into a single document. - Some pages were already being generated but were listing operations individually instead of using wildcards (possibly because they were created before wildcarding was supported by cliff). These are changed to use wildcarding. (We want to do this wherever possible as it is more future-proof in the event that more operations are added to a subcommand later.) - The `service provider` document was incorrectly titled `identity provider`. Fixed. Change-Id: I2030f9fe370038c5908b6eb6bed9692a73fe5067 --- doc/source/_extra/.htaccess | 3 + doc/source/cli/_hidden/ec2-credentials.rst | 13 + doc/source/cli/_hidden/endpoint.rst | 13 + doc/source/cli/_hidden/project.rst | 13 + doc/source/cli/_hidden/role.rst | 13 + doc/source/cli/_hidden/service.rst | 13 + doc/source/cli/_hidden/token.rst | 13 + doc/source/cli/_hidden/user.rst | 13 + .../application-credentials.rst | 107 +----- doc/source/cli/command-objects/catalog.rst | 5 +- doc/source/cli/command-objects/consumer.rst | 14 +- doc/source/cli/command-objects/credential.rst | 124 +------ .../command-objects/ec2-credentials-v2.rst | 6 + .../command-objects/ec2-credentials-v3.rst | 6 + .../cli/command-objects/ec2-credentials.rst | 138 ------- .../cli/command-objects/endpoint-v2.rst | 6 + .../cli/command-objects/endpoint-v3.rst | 24 ++ doc/source/cli/command-objects/endpoint.rst | 275 -------------- .../cli/command-objects/endpoint_group.rst | 20 +- .../federation-domain-project.rst | 11 + .../command-objects/federation-protocol.rst | 14 +- doc/source/cli/command-objects/group.rst | 245 +----------- .../cli/command-objects/identity-provider.rst | 14 +- .../cli/command-objects/implied_role.rst | 53 +-- doc/source/cli/command-objects/limit.rst | 127 +------ doc/source/cli/command-objects/mapping.rst | 14 +- doc/source/cli/command-objects/policy.rst | 14 +- doc/source/cli/command-objects/project-v2.rst | 6 + doc/source/cli/command-objects/project-v3.rst | 6 + doc/source/cli/command-objects/project.rst | 297 --------------- doc/source/cli/command-objects/region.rst | 14 +- .../cli/command-objects/registered-limit.rst | 133 +------ .../cli/command-objects/request-token.rst | 5 +- doc/source/cli/command-objects/role-v2.rst | 6 + doc/source/cli/command-objects/role-v3.rst | 6 + doc/source/cli/command-objects/role.rst | 312 ---------------- .../cli/command-objects/service-provider.rst | 14 +- doc/source/cli/command-objects/service-v2.rst | 6 + doc/source/cli/command-objects/service-v3.rst | 18 + doc/source/cli/command-objects/service.rst | 143 ------- doc/source/cli/command-objects/token-v2.rst | 7 + doc/source/cli/command-objects/token-v3.rst | 7 + doc/source/cli/command-objects/token.rst | 30 -- doc/source/cli/command-objects/trust.rst | 11 +- doc/source/cli/command-objects/user-v2.rst | 7 + doc/source/cli/command-objects/user-v3.rst | 7 + doc/source/cli/command-objects/user.rst | 349 ------------------ doc/source/cli/index.rst | 9 + doc/test/redirect-tests.txt | 7 + setup.cfg | 18 +- 50 files changed, 271 insertions(+), 2458 deletions(-) create mode 100644 doc/source/cli/_hidden/ec2-credentials.rst create mode 100644 doc/source/cli/_hidden/endpoint.rst create mode 100644 doc/source/cli/_hidden/project.rst create mode 100644 doc/source/cli/_hidden/role.rst create mode 100644 doc/source/cli/_hidden/service.rst create mode 100644 doc/source/cli/_hidden/token.rst create mode 100644 doc/source/cli/_hidden/user.rst create mode 100644 doc/source/cli/command-objects/ec2-credentials-v2.rst create mode 100644 doc/source/cli/command-objects/ec2-credentials-v3.rst delete mode 100644 doc/source/cli/command-objects/ec2-credentials.rst create mode 100644 doc/source/cli/command-objects/endpoint-v2.rst create mode 100644 doc/source/cli/command-objects/endpoint-v3.rst delete mode 100644 doc/source/cli/command-objects/endpoint.rst create mode 100644 doc/source/cli/command-objects/federation-domain-project.rst create mode 100644 doc/source/cli/command-objects/project-v2.rst create mode 100644 doc/source/cli/command-objects/project-v3.rst delete mode 100644 doc/source/cli/command-objects/project.rst create mode 100644 doc/source/cli/command-objects/role-v2.rst create mode 100644 doc/source/cli/command-objects/role-v3.rst delete mode 100644 doc/source/cli/command-objects/role.rst create mode 100644 doc/source/cli/command-objects/service-v2.rst create mode 100644 doc/source/cli/command-objects/service-v3.rst delete mode 100644 doc/source/cli/command-objects/service.rst create mode 100644 doc/source/cli/command-objects/token-v2.rst create mode 100644 doc/source/cli/command-objects/token-v3.rst delete mode 100644 doc/source/cli/command-objects/token.rst create mode 100644 doc/source/cli/command-objects/user-v2.rst create mode 100644 doc/source/cli/command-objects/user-v3.rst delete mode 100644 doc/source/cli/command-objects/user.rst diff --git a/doc/source/_extra/.htaccess b/doc/source/_extra/.htaccess index 513deddbdc..1ac063cc35 100644 --- a/doc/source/_extra/.htaccess +++ b/doc/source/_extra/.htaccess @@ -9,3 +9,6 @@ redirectmatch 301 ^/python-openstackclient/([^/]+)/specs/([^/.]+).html$ /python- redirectmatch 301 ^/python-openstackclient/([^/]+)/(command-(beta|errors|logs|options|wrappers)|developing|humaninterfaceguide|plugins).html$ /python-openstackclient/$1/contributor/$2.html redirectmatch 301 ^/python-openstackclient/([^/]+)/cli/plugin-commands.html$ /python-openstackclient/$1/cli/plugin-commands/index.html +# Identity pages were split into -v2 and -v3 for common subcommand names. +# The unversioned page is hidden but contains links to the versioned pages so links in the wild redirect somewhere sane. +redirectmatch 301 ^/python-openstackclient/([^/]+)/cli/command-objects/(ec2-credentials|endpoint|project|role|service|token|user).html$ /python-openstackclient/$1/cli/_hidden/$2.html diff --git a/doc/source/cli/_hidden/ec2-credentials.rst b/doc/source/cli/_hidden/ec2-credentials.rst new file mode 100644 index 0000000000..c54459d877 --- /dev/null +++ b/doc/source/cli/_hidden/ec2-credentials.rst @@ -0,0 +1,13 @@ +=============== +ec2 credentials +=============== + +.. NOTE(efried): This page is hidden from the main TOC; it's here so links in + the wild redirect somewhere sane, because previously identity v2 and v3 were + combined in a single page. + +.. toctree:: + :maxdepth: 2 + + ../command-objects/ec2-credentials-v2 + ../command-objects/ec2-credentials-v3 diff --git a/doc/source/cli/_hidden/endpoint.rst b/doc/source/cli/_hidden/endpoint.rst new file mode 100644 index 0000000000..744e3badcb --- /dev/null +++ b/doc/source/cli/_hidden/endpoint.rst @@ -0,0 +1,13 @@ +======== +endpoint +======== + +.. NOTE(efried): This page is hidden from the main TOC; it's here so links in + the wild redirect somewhere sane, because previously identity v2 and v3 were + combined in a single page. + +.. toctree:: + :maxdepth: 2 + + ../command-objects/endpoint-v2 + ../command-objects/endpoint-v3 diff --git a/doc/source/cli/_hidden/project.rst b/doc/source/cli/_hidden/project.rst new file mode 100644 index 0000000000..209a3129df --- /dev/null +++ b/doc/source/cli/_hidden/project.rst @@ -0,0 +1,13 @@ +======= +project +======= + +.. NOTE(efried): This page is hidden from the main TOC; it's here so links in + the wild redirect somewhere sane, because previously identity v2 and v3 were + combined in a single page. + +.. toctree:: + :maxdepth: 2 + + ../command-objects/project-v2 + ../command-objects/project-v3 diff --git a/doc/source/cli/_hidden/role.rst b/doc/source/cli/_hidden/role.rst new file mode 100644 index 0000000000..c85f48146c --- /dev/null +++ b/doc/source/cli/_hidden/role.rst @@ -0,0 +1,13 @@ +==== +role +==== + +.. NOTE(efried): This page is hidden from the main TOC; it's here so links in + the wild redirect somewhere sane, because previously identity v2 and v3 were + combined in a single page. + +.. toctree:: + :maxdepth: 2 + + ../command-objects/role-v2 + ../command-objects/role-v3 diff --git a/doc/source/cli/_hidden/service.rst b/doc/source/cli/_hidden/service.rst new file mode 100644 index 0000000000..8b33638610 --- /dev/null +++ b/doc/source/cli/_hidden/service.rst @@ -0,0 +1,13 @@ +======= +service +======= + +.. NOTE(efried): This page is hidden from the main TOC; it's here so links in + the wild redirect somewhere sane, because previously identity v2 and v3 were + combined in a single page. + +.. toctree:: + :maxdepth: 2 + + ../command-objects/service-v2 + ../command-objects/service-v3 diff --git a/doc/source/cli/_hidden/token.rst b/doc/source/cli/_hidden/token.rst new file mode 100644 index 0000000000..6ebf801b76 --- /dev/null +++ b/doc/source/cli/_hidden/token.rst @@ -0,0 +1,13 @@ +===== +token +===== + +.. NOTE(efried): This page is hidden from the main TOC; it's here so links in + the wild redirect somewhere sane, because previously identity v2 and v3 were + combined in a single page. + +.. toctree:: + :maxdepth: 2 + + ../command-objects/token-v2 + ../command-objects/token-v3 diff --git a/doc/source/cli/_hidden/user.rst b/doc/source/cli/_hidden/user.rst new file mode 100644 index 0000000000..34eb59954f --- /dev/null +++ b/doc/source/cli/_hidden/user.rst @@ -0,0 +1,13 @@ +==== +user +==== + +.. NOTE(efried): This page is hidden from the main TOC; it's here so links in + the wild redirect somewhere sane, because previously identity v2 and v3 were + combined in a single page. + +.. toctree:: + :maxdepth: 2 + + ../command-objects/user-v2 + ../command-objects/user-v3 diff --git a/doc/source/cli/command-objects/application-credentials.rst b/doc/source/cli/command-objects/application-credentials.rst index 047f5ab661..cef3ef0dfc 100644 --- a/doc/source/cli/command-objects/application-credentials.rst +++ b/doc/source/cli/command-objects/application-credentials.rst @@ -8,109 +8,6 @@ With application credentials, a user can grant their applications limited access to their cloud resources. Once created, users can authenticate with an application credential by using the ``v3applicationcredential`` auth type. -application credential create ------------------------------ -Create new application credential - -.. program:: application credential create -.. code:: bash - - openstack application credential create - [--secret ] - [--role ] - [--expiration ] - [--description ] - [--restricted|--unrestricted] - [--access-rules ] - - -.. option:: --secret - - Secret to use for authentication (if not provided, one will be generated) - -.. option:: --role - - Roles to authorize (name or ID) (repeat option to set multiple values) - -.. option:: --expiration - - Sets an expiration date for the application credential (format of - YYYY-mm-ddTHH:MM:SS) - -.. option:: --description - - Application credential description - -.. option:: --unrestricted - - Enable application credential to create and delete other application - credentials and trusts (this is potentially dangerous behavior and is - disabled by default) - -.. option:: --restricted - - Prohibit application credential from creating and deleting other - application credentials and trusts (this is the default behavior) - -.. option:: --access-rules - - Either a string or file path containing a JSON-formatted list of access - rules, each containing a request method, path, and service, for example - '[{"method": "GET", "path": "/v2.1/servers", "service": "compute"}]' - -.. describe:: - - Name of the application credential - - -application credential delete ------------------------------ - -Delete application credential(s) - -.. program:: application credential delete -.. code:: bash - - openstack application credential delete - [ ...] - -.. describe:: - - Application credential(s) to delete (name or ID) - -application credential list ---------------------------- - -List application credentials - -.. program:: application credential list -.. code:: bash - - openstack application credential list - [--user ] - [--user-domain ] - -.. option:: --user - - User whose application credentials to list (name or ID) - -.. option:: --user-domain - - Domain the user belongs to (name or ID). This can be - used in case collisions between user names exist. - -application credential show ---------------------------- - -Display application credential details - -.. program:: application credential show -.. code:: bash - - openstack application credential show - - -.. describe:: - - Application credential to display (name or ID) +.. autoprogram-cliff:: openstack.identity.v3 + :command: application credential * diff --git a/doc/source/cli/command-objects/catalog.rst b/doc/source/cli/command-objects/catalog.rst index 6db8227ec4..84cd160a0b 100644 --- a/doc/source/cli/command-objects/catalog.rst +++ b/doc/source/cli/command-objects/catalog.rst @@ -6,7 +6,4 @@ A **catalog** lists OpenStack services that are available on the cloud. Applicable to Identity v2 and v3 .. autoprogram-cliff:: openstack.identity.v3 - :command: catalog list - -.. autoprogram-cliff:: openstack.identity.v3 - :command: catalog show + :command: catalog * diff --git a/doc/source/cli/command-objects/consumer.rst b/doc/source/cli/command-objects/consumer.rst index 8f6dda0ad4..17cfc0c996 100644 --- a/doc/source/cli/command-objects/consumer.rst +++ b/doc/source/cli/command-objects/consumer.rst @@ -7,16 +7,4 @@ is used to create a **request token** and **access token**. Applicable to Identity v3. .. autoprogram-cliff:: openstack.identity.v3 - :command: consumer create - -.. autoprogram-cliff:: openstack.identity.v3 - :command: consumer delete - -.. autoprogram-cliff:: openstack.identity.v3 - :command: consumer list - -.. autoprogram-cliff:: openstack.identity.v3 - :command: consumer set - -.. autoprogram-cliff:: openstack.identity.v3 - :command: consumer show + :command: consumer * diff --git a/doc/source/cli/command-objects/credential.rst b/doc/source/cli/command-objects/credential.rst index 7fe57310f3..f490f1cfee 100644 --- a/doc/source/cli/command-objects/credential.rst +++ b/doc/source/cli/command-objects/credential.rst @@ -4,125 +4,5 @@ credential Identity v3 -credential create ------------------ - -Create new credential - -.. program:: credential create -.. code:: bash - - openstack credential create - [--type ] - [--project ] - - -.. option:: --type - - New credential type: cert, ec2, totp and so on - -.. option:: --project - - Project which limits the scope of the credential (name or ID) - -.. _credential_create: -.. describe:: - - User that owns the credential (name or ID) - -.. describe:: - - New credential data - -credential delete ------------------ - -Delete credential(s) - -.. program:: credential delete -.. code:: bash - - openstack credential delete - [ ...] - -.. _credential_delete: -.. describe:: - - ID(s) of credential to delete - -credential list ---------------- - -List credentials - -.. program:: credential list -.. code:: bash - - openstack credential list - [--user [--user-domain ]] - [--type ] - -.. option:: --user - - Filter credentials by (name or ID) - -.. option:: --user-domain - - Domain the user belongs to (name or ID). This can be - used in case collisions between user names exist. - -.. option:: --type - - Filter credentials by type: cert, ec2, totp and so on - -credential set --------------- - -Set credential properties - -.. program:: credential set -.. code:: bash - - openstack credential set - [--user ] - [--type ] - [--data ] - [--project ] - - -.. option:: --user - - User that owns the credential (name or ID) - -.. option:: --type - - New credential type: cert, ec2, totp and so on. - -.. option:: --data - - New credential data - -.. option:: --project - - Project which limits the scope of the credential (name or ID) - -.. _credential_set: -.. describe:: - - ID of credential to change - -credential show ---------------- - -Display credential details - -.. program:: credential show -.. code:: bash - - openstack credential show - - -.. _credential_show: -.. describe:: - - ID of credential to display +.. autoprogram-cliff:: openstack.identity.v3 + :command: credential * diff --git a/doc/source/cli/command-objects/ec2-credentials-v2.rst b/doc/source/cli/command-objects/ec2-credentials-v2.rst new file mode 100644 index 0000000000..ace3d84ec5 --- /dev/null +++ b/doc/source/cli/command-objects/ec2-credentials-v2.rst @@ -0,0 +1,6 @@ +============================= +ec2 credentials (Identity v2) +============================= + +.. autoprogram-cliff:: openstack.identity.v2 + :command: ec2 credentials * diff --git a/doc/source/cli/command-objects/ec2-credentials-v3.rst b/doc/source/cli/command-objects/ec2-credentials-v3.rst new file mode 100644 index 0000000000..761d428050 --- /dev/null +++ b/doc/source/cli/command-objects/ec2-credentials-v3.rst @@ -0,0 +1,6 @@ +============================= +ec2 credentials (Identity v3) +============================= + +.. autoprogram-cliff:: openstack.identity.v3 + :command: ec2 credentials * diff --git a/doc/source/cli/command-objects/ec2-credentials.rst b/doc/source/cli/command-objects/ec2-credentials.rst deleted file mode 100644 index 9174b0413e..0000000000 --- a/doc/source/cli/command-objects/ec2-credentials.rst +++ /dev/null @@ -1,138 +0,0 @@ -=============== -ec2 credentials -=============== - -Identity v2 - -ec2 credentials create ----------------------- - -Create EC2 credentials - -.. program:: ec2 credentials create -.. code-block:: bash - - openstack ec2 credentials create - [--project ] - [--user ] - [--user-domain ] - [--project-domain ] - -.. option:: --project - - Create credentials in project (name or ID; default: current authenticated project) - -.. option:: --user - - Create credentials for user (name or ID; default: current authenticated user) - -.. option:: --user-domain - - Domain the user belongs to (name or ID). This can be - used in case collisions between user names exist. - - .. versionadded:: 3 - -.. option:: --project-domain - - Domain the project belongs to (name or ID). This can be - used in case collisions between user names exist. - - .. versionadded:: 3 - -The :option:`--project` and :option:`--user` options are typically only -useful for admin users, but may be allowed for other users depending on -the policy of the cloud and the roles granted to the user. - -ec2 credentials delete ----------------------- - -Delete EC2 credentials - -.. program:: ec2 credentials delete -.. code-block:: bash - - openstack ec2 credentials delete - [--user ] - [--user-domain ] - [ ...] - -.. option:: --user - - Delete credentials for user (name or ID) - -.. option:: --user-domain - - Select user from a specific domain (name or ID) - This can be used in case collisions between user names exist. - - .. versionadded:: 3 - -.. _ec2_credentials_delete-access-key: -.. describe:: access-key - - Credentials access key(s) - -The :option:`--user` option is typically only useful for admin users, but -may be allowed for other users depending on the policy of the cloud and -the roles granted to the user. - -ec2 credentials list --------------------- - -List EC2 credentials - -.. program:: ec2 credentials list -.. code-block:: bash - - openstack ec2 credentials list - [--user ] - [--user-domain ] - -.. option:: --user - - Filter list by (name or ID) - -.. option:: --user-domain - - Select user from a specific domain (name or ID) - This can be used in case collisions between user names exist. - - .. versionadded:: 3 - -The :option:`--user` option is typically only useful for admin users, but -may be allowed for other users depending on the policy of the cloud and -the roles granted to the user. - -ec2 credentials show --------------------- - -Display EC2 credentials details - -.. program:: ec2 credentials show -.. code-block:: bash - - openstack ec2 credentials show - [--user ] - [--user-domain ] - - -.. option:: --user - - Show credentials for user (name or ID) - -.. option:: --user-domain - - Select user from a specific domain (name or ID) - This can be used in case collisions between user names exist. - - .. versionadded:: 3 - -.. _ec2_credentials_show-access-key: -.. describe:: access-key - - Credentials access key - -The :option:`--user` option is typically only useful for admin users, but -may be allowed for other users depending on the policy of the cloud and -the roles granted to the user. diff --git a/doc/source/cli/command-objects/endpoint-v2.rst b/doc/source/cli/command-objects/endpoint-v2.rst new file mode 100644 index 0000000000..7badfd4080 --- /dev/null +++ b/doc/source/cli/command-objects/endpoint-v2.rst @@ -0,0 +1,6 @@ +====================== +endpoint (Identity v2) +====================== + +.. autoprogram-cliff:: openstack.identity.v2 + :command: endpoint * diff --git a/doc/source/cli/command-objects/endpoint-v3.rst b/doc/source/cli/command-objects/endpoint-v3.rst new file mode 100644 index 0000000000..f12063fb0c --- /dev/null +++ b/doc/source/cli/command-objects/endpoint-v3.rst @@ -0,0 +1,24 @@ +====================== +endpoint (Identity v3) +====================== + +.. autoprogram-cliff:: openstack.identity.v3 + :command: endpoint add project + +.. autoprogram-cliff:: openstack.identity.v3 + :command: endpoint create + +.. autoprogram-cliff:: openstack.identity.v3 + :command: endpoint delete + +.. autoprogram-cliff:: openstack.identity.v3 + :command: endpoint list + +.. autoprogram-cliff:: openstack.identity.v3 + :command: endpoint remove project + +.. autoprogram-cliff:: openstack.identity.v3 + :command: endpoint set + +.. autoprogram-cliff:: openstack.identity.v3 + :command: endpoint show diff --git a/doc/source/cli/command-objects/endpoint.rst b/doc/source/cli/command-objects/endpoint.rst deleted file mode 100644 index 6d0253278b..0000000000 --- a/doc/source/cli/command-objects/endpoint.rst +++ /dev/null @@ -1,275 +0,0 @@ -======== -endpoint -======== - -Identity v2, v3 - -endpoint add project --------------------- - -Associate a project to an endpoint for endpoint filtering - -.. program:: endpoint add project -.. code:: bash - - openstack endpoint add project - [--project-domain ] - - - -.. option:: --project-domain - - Domain the project belongs to (name or ID). - This can be used in case collisions between project names exist. - -.. _endpoint_add_project-endpoint: -.. describe:: - - Endpoint to associate with specified project (name or ID) - -.. _endpoint_add_project-project: -.. describe:: - - Project to associate with specified endpoint (name or ID) - -endpoint create ---------------- - -Create new endpoint - -*Identity version 2 only* - -.. program:: endpoint create (v2) -.. code:: bash - - openstack endpoint create - --publicurl - [--adminurl ] - [--internalurl ] - [--region ] - - -.. option:: --publicurl - - New endpoint public URL (required) - -.. option:: --adminurl - - New endpoint admin URL - -.. option:: --internalurl - - New endpoint internal URL - -.. option:: --region - - New endpoint region ID - -.. _endpoint_create-endpoint: -.. describe:: - - Service to be associated with new endpoint (name or ID) - -*Identity version 3 only* - -.. program:: endpoint create (v3) -.. code:: bash - - openstack endpoint create - [--region ] - [--enable | --disable] - - - - -.. option:: --region - - New endpoint region ID - -.. option:: --enable - - Enable endpoint (default) - -.. option:: --disable - - Disable endpoint - -.. describe:: - - Service to be associated with new endpoint(name or ID) - -.. describe:: - - New endpoint interface type (admin, public or internal) - -.. describe:: - - New endpoint URL - -endpoint delete ---------------- - -Delete endpoint(s) - -.. program:: endpoint delete -.. code:: bash - - openstack endpoint delete - [ ...] - -.. _endpoint_delete-endpoint: -.. describe:: - - Endpoint(s) to delete (ID only) - -endpoint list -------------- - -List endpoints - -.. program:: endpoint list -.. code:: bash - - openstack endpoint list - [--service ] - [--interface ] - [--region ] - [--long] - [--endpoint | - --project [--project-domain ]] - -.. option:: --service - - Filter by service (type, name or ID) - - *Identity version 3 only* - -.. option:: --interface - - Filter by interface type (admin, public or internal) - - *Identity version 3 only* - -.. option:: --region - - Filter by region ID - - *Identity version 3 only* - -.. option:: --long - - List additional fields in output - - *Identity version 2 only* - -.. option:: --endpoint - - List projects that have access to that endpoint using - endpoint filtering - - *Identity version 3 only* - -.. option:: --project - - List endpoints available for the project using - endpoint filtering - - *Identity version 3 only* - -.. option:: --project-domain - - Domain the project belongs to (name or ID). - This can be used in case collisions between project names exist. - - *Identity version 3 only* - -endpoint remove project ------------------------ - -Dissociate a project from an endpoint. - -.. program:: endpoint remove project -.. code:: bash - - openstack endpoint remove project - [--project-domain ] - - - -.. option:: --project-domain - - Domain the project belongs to (name or ID). - This can be used in case collisions between project names exist. - -.. _endpoint_remove_project-endpoint: -.. describe:: - - Endpoint to dissociate with specified project (name or ID) - -.. _endpoint_remove_project-project: -.. describe:: - - Project to dissociate with specified endpoint (name or ID) - -endpoint set ------------- - -Set endpoint properties - -*Identity version 3 only* - -.. program:: endpoint set -.. code:: bash - - openstack endpoint set - [--region ] - [--interface ] - [--url ] - [--service ] - [--enable | --disable] - - -.. option:: --region - - New endpoint region ID - -.. option:: --interface - - New endpoint interface type (admin, public or internal) - -.. option:: --url - - New endpoint URL - -.. option:: --service - - New endpoint service (name or ID) - -.. option:: --enable - - Enable endpoint - -.. option:: --disable - - Disable endpoint - -.. _endpoint_set-endpoint: -.. describe:: - - Endpoint to modify (ID only) - -endpoint show -------------- - -Display endpoint details - -.. program:: endpoint show -.. code:: bash - - openstack endpoint show - - -.. _endpoint_show-endpoint: -.. describe:: - - Endpoint to display (endpoint ID, service ID, service name, service type) diff --git a/doc/source/cli/command-objects/endpoint_group.rst b/doc/source/cli/command-objects/endpoint_group.rst index ccfe5f6615..b0d988e17c 100644 --- a/doc/source/cli/command-objects/endpoint_group.rst +++ b/doc/source/cli/command-objects/endpoint_group.rst @@ -7,22 +7,4 @@ can be used to filter the endpoints that are available to a project. Applicable to Identity v3 .. autoprogram-cliff:: openstack.identity.v3 - :command: endpoint group add project - -.. autoprogram-cliff:: openstack.identity.v3 - :command: endpoint group create - -.. autoprogram-cliff:: openstack.identity.v3 - :command: endpoint group delete - -.. autoprogram-cliff:: openstack.identity.v3 - :command: endpoint group list - -.. autoprogram-cliff:: openstack.identity.v3 - :command: endpoint group remove project - -.. autoprogram-cliff:: openstack.identity.v3 - :command: endpoint group set - -.. autoprogram-cliff:: openstack.identity.v3 - :command: endpoint group show + :command: endpoint group * diff --git a/doc/source/cli/command-objects/federation-domain-project.rst b/doc/source/cli/command-objects/federation-domain-project.rst new file mode 100644 index 0000000000..68db5705b0 --- /dev/null +++ b/doc/source/cli/command-objects/federation-domain-project.rst @@ -0,0 +1,11 @@ +========================= +federation domain/project +========================= + +Identity v3 + +.. autoprogram-cliff:: openstack.identity.v3 + :command: federation domain * + +.. autoprogram-cliff:: openstack.identity.v3 + :command: federation project * diff --git a/doc/source/cli/command-objects/federation-protocol.rst b/doc/source/cli/command-objects/federation-protocol.rst index 3a99fd72b9..81b3f9fcd3 100644 --- a/doc/source/cli/command-objects/federation-protocol.rst +++ b/doc/source/cli/command-objects/federation-protocol.rst @@ -7,16 +7,4 @@ extension. It is used by **identity providers** and **mappings**. Applicable to Identity v3. .. autoprogram-cliff:: openstack.identity.v3 - :command: federation protocol create - -.. autoprogram-cliff:: openstack.identity.v3 - :command: federation protocol delete - -.. autoprogram-cliff:: openstack.identity.v3 - :command: federation protocol list - -.. autoprogram-cliff:: openstack.identity.v3 - :command: federation protocol set - -.. autoprogram-cliff:: openstack.identity.v3 - :command: federation protocol show + :command: federation protocol * diff --git a/doc/source/cli/command-objects/group.rst b/doc/source/cli/command-objects/group.rst index ac938efdc2..a1071069db 100644 --- a/doc/source/cli/command-objects/group.rst +++ b/doc/source/cli/command-objects/group.rst @@ -4,246 +4,5 @@ group Identity v3 -group add user --------------- - -Add user to group - -.. program:: group add user -.. code:: bash - - openstack group add user - [--group-domain ] - [--user-domain ] - - [ ...] - -.. option:: --group-domain - - Domain the group belongs to (name or ID). This can be - used in case collisions between group names exist. - - .. versionadded:: 3 - -.. option:: --user-domain - - Domain the user belongs to (name or ID). This can be - used in case collisions between user names exist. - - .. versionadded:: 3 - -.. describe:: - - Group to contain (name or ID) - -.. describe:: - - User(s) to add to (name or ID) - (repeat option to add multiple users) - -group contains user -------------------- - -Check user membership in group - -.. program:: group contains user -.. code:: bash - - openstack group contains user - [--group-domain ] - [--user-domain ] - - - -.. option:: --group-domain - - Domain the group belongs to (name or ID). This can be - used in case collisions between group names exist. - - .. versionadded:: 3 - -.. option:: --user-domain - - Domain the user belongs to (name or ID). This can be - used in case collisions between user names exist. - - .. versionadded:: 3 - -.. describe:: - - Group to check (name or ID) - -.. describe:: - - User to check (name or ID) - -group create ------------- - -Create new group - -.. program:: group create -.. code:: bash - - openstack group create - [--domain ] - [--description ] - [--or-show] - - -.. option:: --domain - - Domain to contain new group (name or ID) - -.. option:: --description - - New group description - -.. option:: --or-show - - Return existing group - - If the group already exists, return the existing group data and do not fail. - -.. describe:: - - New group name - -group delete ------------- - -Delete group - -.. program:: group delete -.. code:: bash - - openstack group delete - [--domain ] - [ ...] - -.. option:: --domain - - Domain containing group(s) (name or ID) - -.. describe:: - - Group(s) to delete (name or ID) - -group list ----------- - -List groups - -.. program:: group list -.. code:: bash - - openstack group list - [--domain ] - [--user [--user-domain ]] - [--long] - -.. option:: --domain - - Filter group list by (name or ID) - -.. option:: --user - - Filter group list by (name or ID) - -.. option:: --user-domain - - Domain the user belongs to (name or ID). This can be - used in case collisions between user names exist. - - .. versionadded:: 3 - -.. option:: --long - - List additional fields in output - -group remove user ------------------ - -Remove user from group - -.. program:: group remove user -.. code:: bash - - openstack group remove user - [--group-domain ] - [--user-domain ] - - [ ...] - -.. option:: --group-domain - - Domain the group belongs to (name or ID). This can be - used in case collisions between group names exist. - - .. versionadded:: 3 - -.. option:: --user-domain - - Domain the user belongs to (name or ID). This can be - used in case collisions between user names exist. - - .. versionadded:: 3 - -.. describe:: - - Group containing (name or ID) - -.. describe:: - - User(s) to remove from (name or ID) - (repeat option to remove multiple users) - -group set ---------- - -Set group properties - -.. program:: group set -.. code:: bash - - openstack group set - [--domain ] - [--name ] - [--description ] - - -.. option:: --domain - - Domain containing (name or ID) - -.. option:: --name - - New group name - -.. option:: --description - - New group description - -.. describe:: - - Group to modify (name or ID) - -group show ----------- - -Display group details - -.. program:: group show -.. code:: bash - - openstack group show - [--domain ] - - -.. option:: --domain - - Domain containing (name or ID) - -.. describe:: - - Group to display (name or ID) +.. autoprogram-cliff:: openstack.identity.v3 + :command: group * diff --git a/doc/source/cli/command-objects/identity-provider.rst b/doc/source/cli/command-objects/identity-provider.rst index 36c9264a64..ed85cb7ac8 100644 --- a/doc/source/cli/command-objects/identity-provider.rst +++ b/doc/source/cli/command-objects/identity-provider.rst @@ -7,16 +7,4 @@ extension. It is used by **federation protocols** and **mappings**. Applicable to Identity v3. .. autoprogram-cliff:: openstack.identity.v3 - :command: identity provider create - -.. autoprogram-cliff:: openstack.identity.v3 - :command: identity provider delete - -.. autoprogram-cliff:: openstack.identity.v3 - :command: identity provider list - -.. autoprogram-cliff:: openstack.identity.v3 - :command: identity provider set - -.. autoprogram-cliff:: openstack.identity.v3 - :command: identity provider show + :command: identity provider * diff --git a/doc/source/cli/command-objects/implied_role.rst b/doc/source/cli/command-objects/implied_role.rst index e43c9ea39e..09532c0b02 100644 --- a/doc/source/cli/command-objects/implied_role.rst +++ b/doc/source/cli/command-objects/implied_role.rst @@ -4,54 +4,5 @@ implied role Identity v3 - -implied role create -------------------- - -Creates an association between prior and implied roles - -.. program:: implied role create -.. code:: bash - - openstack implied role create - - --implied-role - -.. option:: - - Prior role (name or ID) implies another role - -.. option:: --implied-role - - (name or ID) implied by another role - - -implied role delete -------------------- - -Deletes an association between prior and implied roles - -.. program:: implied role delete -.. code:: bash - - openstack implied role delete - - --implied-role - -.. option:: - - Prior role (name or ID) implies another role - -.. option:: --implied-role - - (name or ID) implied by another role - -implied role list ------------------ - -List implied roles - -.. program:: implied role list -.. code:: bash - - openstack implied role list +.. autoprogram-cliff:: openstack.identity.v3 + :command: implied role * diff --git a/doc/source/cli/command-objects/limit.rst b/doc/source/cli/command-objects/limit.rst index 46da6c1b70..784d0cb423 100644 --- a/doc/source/cli/command-objects/limit.rst +++ b/doc/source/cli/command-objects/limit.rst @@ -6,128 +6,5 @@ Identity v3 Limits are used to specify project-specific limits thresholds of resources. -limit create ------------- - -Create a new limit - -.. program:: limit create -.. code:: bash - - openstack limit create - [--description ] - [--region ] - --project - --service - --resource-limit - - -.. option:: --description - - Useful description of the limit or its purpose - -.. option:: --region - - Region that the limit should be applied to - -.. describe:: --project - - The project that the limit applies to (required) - -.. describe:: --service - - The service that is responsible for the resource being limited (required) - -.. describe:: --resource-limit - - The limit to apply to the project (required) - -.. describe:: - - The name of the resource to limit (e.g. cores or volumes) - -limit delete ------------- - -Delete project-specific limit(s) - -.. program:: limit delete -.. code:: bash - - openstack limit delete - [ ...] - -.. describe:: - - Limit(s) to delete (ID) - -limit list ----------- - -List project-specific limits - -.. program:: limit list -.. code:: bash - - openstack limit list - [--service ] - [--resource-name ] - [--region ] - [--project ] - -.. option:: --service - - The service to filter the response by (name or ID) - -.. option:: --resource-name - - The name of the resource to filter the response by - -.. option:: --region - - The region name to filter the response by - -.. option:: --project - - List resource limits associated with project - -limit show ----------- - -Display details about a limit - -.. program:: limit show -.. code:: bash - - openstack limit show - - -.. describe:: - - Limit to display (ID) - -limit set ---------- - -Update a limit - -.. program:: limit show -.. code:: bash - - openstack limit set - [--description ] - [--resource-limit ] - - - -.. option:: --description - - Useful description of the limit or its purpose - -.. option:: --resource-limit - - The limit to apply to the project - -.. describe:: - - Limit to update (ID) +.. autoprogram-cliff:: openstack.identity.v3 + :command: limit * diff --git a/doc/source/cli/command-objects/mapping.rst b/doc/source/cli/command-objects/mapping.rst index 73dbbc8d8b..5653b52bb9 100644 --- a/doc/source/cli/command-objects/mapping.rst +++ b/doc/source/cli/command-objects/mapping.rst @@ -7,16 +7,4 @@ extension. It is used by **federation protocols** and **identity providers**. Applicable to Identity v3. .. autoprogram-cliff:: openstack.identity.v3 - :command: mapping create - -.. autoprogram-cliff:: openstack.identity.v3 - :command: mapping delete - -.. autoprogram-cliff:: openstack.identity.v3 - :command: mapping list - -.. autoprogram-cliff:: openstack.identity.v3 - :command: mapping set - -.. autoprogram-cliff:: openstack.identity.v3 - :command: mapping show + :command: mapping * \ No newline at end of file diff --git a/doc/source/cli/command-objects/policy.rst b/doc/source/cli/command-objects/policy.rst index 0fe104c56d..66bc2545a6 100644 --- a/doc/source/cli/command-objects/policy.rst +++ b/doc/source/cli/command-objects/policy.rst @@ -6,16 +6,4 @@ A **policy** is an arbitrarily serialized policy engine rule set to be consumed by a remote service. Applies to Identity v3. .. autoprogram-cliff:: openstack.identity.v3 - :command: policy create - -.. autoprogram-cliff:: openstack.identity.v3 - :command: policy delete - -.. autoprogram-cliff:: openstack.identity.v3 - :command: policy list - -.. autoprogram-cliff:: openstack.identity.v3 - :command: policy set - -.. autoprogram-cliff:: openstack.identity.v3 - :command: policy show + :command: policy * diff --git a/doc/source/cli/command-objects/project-v2.rst b/doc/source/cli/command-objects/project-v2.rst new file mode 100644 index 0000000000..502154d37e --- /dev/null +++ b/doc/source/cli/command-objects/project-v2.rst @@ -0,0 +1,6 @@ +===================== +project (Identity v2) +===================== + +.. autoprogram-cliff:: openstack.identity.v2 + :command: project * diff --git a/doc/source/cli/command-objects/project-v3.rst b/doc/source/cli/command-objects/project-v3.rst new file mode 100644 index 0000000000..9bee6ae8cf --- /dev/null +++ b/doc/source/cli/command-objects/project-v3.rst @@ -0,0 +1,6 @@ +===================== +project (Identity v3) +===================== + +.. autoprogram-cliff:: openstack.identity.v3 + :command: project * diff --git a/doc/source/cli/command-objects/project.rst b/doc/source/cli/command-objects/project.rst deleted file mode 100644 index afa785cbbe..0000000000 --- a/doc/source/cli/command-objects/project.rst +++ /dev/null @@ -1,297 +0,0 @@ -======= -project -======= - -Identity v2, v3 - -project create --------------- - -Create new project - -.. program:: project create -.. code:: bash - - openstack project create - [--domain ] - [--parent ] - [--description ] - [--immutable | --no-immutable] - [--enable | --disable] - [--property ] - [--or-show] - [--tag ] - - -.. option:: --domain - - Domain owning the project (name or ID) - - .. versionadded:: 3 - -.. option:: --parent - - Parent of the project (name or ID) - - .. versionadded:: 3 - -.. option:: --description - - Project description - -.. option:: --enable - - Enable project (default) - -.. option:: --disable - - Disable project - -.. option:: --immutable - - Make project immutable. An immutable project may not be deleted or - modified except to remove the immutable flag - -.. option:: --no-immutable - - Make project mutable (default) - -.. option:: --property - - Add a property to :ref:`\ ` - (repeat option to set multiple properties) - -.. option:: --or-show - - Return existing project - - If the project already exists return the existing project data and do not fail. - -.. option:: --tag - - Add a tag to the project - (repeat option to set multiple tags) - - .. versionadded:: 3 - -.. _project_create-name: -.. describe:: - - New project name - -project delete --------------- - -Delete project(s) - -.. program:: project delete -.. code:: bash - - openstack project delete - [--domain ] - [ ...] - -.. option:: --domain - - Domain owning :ref:`\ ` (name or ID) - - .. versionadded:: 3 - -.. _project_delete-project: -.. describe:: - - Project to delete (name or ID) - -project list ------------- - -List projects - -.. program:: project list -.. code:: bash - - openstack project list - [--domain ] - [--parent ] - [--user ] - [--my-projects] - [--long] - [--sort [:,:,..]] - [--tags [,,...]] [--tags-any [,,...]] - [--not-tags [,,...]] [--not-tags-any [,,...]] - -.. option:: --domain - - Filter projects by :option:`\ <--domain>` (name or ID) - - .. versionadded:: 3 - -.. option:: --parent - - Filter projects whose parent is :option:`\ <--parent>` (name or ID) - - .. versionadded:: 3 - -.. option:: --user - - Filter projects by :option:`\ <--user>` (name or ID) - - .. versionadded:: 3 - -.. option:: --my-projects - - List projects for the authenticated user. Supersedes other filters. - - .. versionadded:: 3 - -.. option:: --long - - List additional fields in output - -.. option:: --sort [:,:,..] - - Sort output by selected keys and directions (asc or desc) (default: asc), - multiple keys and directions can be specified --sort - [:,:,..] - -.. option:: --tags [,,...] - - List projects which have all given tag(s) - - .. versionadded:: 3 - -.. option:: --tags-any [,,...] - - List projects which have any given tag(s) - - .. versionadded:: 3 - -.. option:: --not-tags [,,...] - - Exclude projects which have all given tag(s) - - .. versionadded:: 3 - -.. option:: --not-tags-any [,,...] - - Exclude projects which have any given tag(s) - - .. versionadded:: 3 - -project set ------------ - -Set project properties - -.. program:: project set -.. code:: bash - - openstack project set - [--name ] - [--domain ] - [--description ] - [--immutable | --no-immutable] - [--enable | --disable] - [--property ] - [--tag | --clear-tags | --remove-tags ] - - -.. option:: --name - - Set project name - -.. option:: --domain - - Domain owning :ref:`\ ` (name or ID) - - .. versionadded:: 3 - -.. option:: --description - - Set project description - -.. option:: --immutable - - Make project immutable. An immutable project may not be deleted or - modified except to remove the immutable flag - -.. option:: --no-immutable - - Make project mutable (default) - -.. option:: --enable - - Enable project (default) - -.. option:: --disable - - Disable project - -.. option:: --property - - Set a property on :ref:`\ ` - (repeat option to set multiple properties) - - *Identity version 2 only* - -.. _project_set-project: -.. describe:: - - Project to modify (name or ID) - -project show ------------- - -Display project details - -.. program:: project show -.. code:: bash - - openstack project show - [--domain ] - - -.. option:: --domain - - Domain owning :ref:`\ ` (name or ID) - - .. versionadded:: 3 - -.. option:: --parents - - Show the project\'s parents as a list - - .. versionadded:: 3 - -.. option:: --children - - Show project\'s subtree (children) as a list - - .. versionadded:: 3 - -.. _project_show-project: -.. describe:: - - Project to display (name or ID) - -project unset -------------- - -Unset project properties - -*Identity version 2 only* - -.. program:: project unset -.. code:: bash - - openstack project unset - --property [--property ...] - - -.. option:: --property - - Property key to remove from project (repeat option to remove multiple properties) - -.. describe:: - - Project to modify (name or ID) diff --git a/doc/source/cli/command-objects/region.rst b/doc/source/cli/command-objects/region.rst index 58cc341f9a..86e7ca8ae0 100644 --- a/doc/source/cli/command-objects/region.rst +++ b/doc/source/cli/command-objects/region.rst @@ -7,16 +7,4 @@ zero or more sub-regions with a region to create a tree-like structured hierarchy. Applies to Identity v3. .. autoprogram-cliff:: openstack.identity.v3 - :command: region create - -.. autoprogram-cliff:: openstack.identity.v3 - :command: region delete - -.. autoprogram-cliff:: openstack.identity.v3 - :command: region list - -.. autoprogram-cliff:: openstack.identity.v3 - :command: region set - -.. autoprogram-cliff:: openstack.identity.v3 - :command: region show + :command: region * diff --git a/doc/source/cli/command-objects/registered-limit.rst b/doc/source/cli/command-objects/registered-limit.rst index 586fd1ffcc..98a5efba9e 100644 --- a/doc/source/cli/command-objects/registered-limit.rst +++ b/doc/source/cli/command-objects/registered-limit.rst @@ -7,134 +7,5 @@ Identity v3 Registered limits are used to define default limits for resources within a deployment. -registered limit create ------------------------ - -Create a new registered limit - -.. program:: registered limit create -.. code:: bash - - openstack registered limit create - [--description ] - [--region ] - --service - --default-limit - - -.. option:: --description - - Useful description of the registered limit or its purpose - -.. option:: --region - - Region that the limit should be applied to - -.. describe:: --service - - The service that is responsible for the resource being limited (required) - -.. describe:: --default-limit - - The default limit for projects to assume unless explicitly overridden - (required) - -.. describe:: - - The name of the resource to limit (e.g. cores or volumes) - -registered limit delete ------------------------ - -Delete registered limit(s) - -.. program:: registered limit delete -.. code:: bash - - openstack registered limit delete - [ ...] - - -.. describe:: - - Registered limit(s) to delete (ID) - -registered limit list ---------------------- - -List registered limits - -.. program:: registered limit list -.. code:: bash - - openstack registered limit list - [--service ] - [--resource-name ] - [--region ] - -.. option:: --service - - The service to filter the response by (name or ID) - -.. option:: --resource-name - - The name of the resource to filter the response by - -.. option:: --region - - The region name to filter the response by - -registered limit show ---------------------- - -Display details about a registered limit - -.. program:: registered limit show -.. code:: bash - - openstack registered limit show - - -.. describe:: - - Registered limit to display (ID) - -registered limit set --------------------- - -Update a registered limit - -.. program:: registered limit set -.. code:: bash - - openstack registered limit set - [--service ] - [--resource-name ] - [--default-limit ] - [--description ] - [--region ] - - -.. option:: --service - - The service of the registered limit to update (ID or name) - -.. option:: --resource-name - - The name of the resource for the limit - -.. option:: --default-limit - - The default limit for projects to assume for a given resource - -.. option:: --description - - A useful description of the limit or its purpose - -.. option:: --region - - The region the limit should apply to - -.. describe:: - - Registered limit to display (ID) +.. autoprogram-cliff:: openstack.identity.v3 + :command: registered limit * diff --git a/doc/source/cli/command-objects/request-token.rst b/doc/source/cli/command-objects/request-token.rst index e10f6640ef..ea333c4aba 100644 --- a/doc/source/cli/command-objects/request-token.rst +++ b/doc/source/cli/command-objects/request-token.rst @@ -7,7 +7,4 @@ is used by the **consumer** to request **access tokens**. Applicable to Identity v3. .. autoprogram-cliff:: openstack.identity.v3 - :command: request token authorize - -.. autoprogram-cliff:: openstack.identity.v3 - :command: request token create + :command: request token * diff --git a/doc/source/cli/command-objects/role-v2.rst b/doc/source/cli/command-objects/role-v2.rst new file mode 100644 index 0000000000..dc5bac6c65 --- /dev/null +++ b/doc/source/cli/command-objects/role-v2.rst @@ -0,0 +1,6 @@ +================== +role (Identity v2) +================== + +.. autoprogram-cliff:: openstack.identity.v2 + :command: role * diff --git a/doc/source/cli/command-objects/role-v3.rst b/doc/source/cli/command-objects/role-v3.rst new file mode 100644 index 0000000000..e36bef773f --- /dev/null +++ b/doc/source/cli/command-objects/role-v3.rst @@ -0,0 +1,6 @@ +================== +role (Identity v3) +================== + +.. autoprogram-cliff:: openstack.identity.v3 + :command: role * diff --git a/doc/source/cli/command-objects/role.rst b/doc/source/cli/command-objects/role.rst deleted file mode 100644 index f9fd28eb75..0000000000 --- a/doc/source/cli/command-objects/role.rst +++ /dev/null @@ -1,312 +0,0 @@ -==== -role -==== - -Identity v2, v3 - -role add --------- - -Add role assignment to a user or group in a project or domain - -.. program:: role add -.. code:: bash - - openstack role add - --system | --domain | --project [--project-domain ] - --user [--user-domain ] | --group [--group-domain ] - --role-domain - --inherited - - -.. option:: --system - - Include - - System or service to grant authorization to. Currently only ``all`` is - supported which encompasses the entire deployment system. - - .. versionadded:: 3 - -.. option:: --domain - - Include (name or ID) - - .. versionadded:: 3 - -.. option:: --project - - Include (name or ID) - -.. option:: --user - - Include (name or ID) - -.. option:: --group - - Include (name or ID) - - .. versionadded:: 3 - -.. option:: --user-domain - - Domain the user belongs to (name or ID). - This can be used in case collisions between user names exist. - - .. versionadded:: 3 - -.. option:: --group-domain - - Domain the group belongs to (name or ID). - This can be used in case collisions between group names exist. - - .. versionadded:: 3 - -.. option:: --project-domain - - Domain the project belongs to (name or ID). - This can be used in case collisions between project names exist. - - .. versionadded:: 3 - -.. option:: --inherited - - Specifies if the role grant is inheritable to the sub projects. - - .. versionadded:: 3 - -.. option:: --role-domain - - Domain the role belongs to (name or ID). - This must be specified when the name of a domain specific role is used. - - .. versionadded:: 3 - -.. describe:: - - Role to add to : (name or ID) - -role create ------------ - -Create new role - -.. program:: role create -.. code:: bash - - openstack role create - [--or-show] - [--domain ] - [--immutable | --no-immutable] - - -.. option:: --domain - - Domain the role belongs to (name or ID). - - .. versionadded:: 3 - -.. option:: --or-show - - Return existing role - - If the role already exists return the existing role data and do not fail. - -.. describe:: - - New role name - -.. option:: --description - - Add description about the role - -.. option:: --immutable - - Make role immutable. An immutable role may not be deleted or modified - except to remove the immutable flag - -.. option:: --no-immutable - - Make role mutable (default) - -role delete ------------ - -Delete role(s) - -.. program:: role delete -.. code:: bash - - openstack role delete - [ ...] - [--domain ] - -.. describe:: - - Role to delete (name or ID) - -.. option:: --domain - - Domain the role belongs to (name or ID). - - .. versionadded:: 3 - -role list ---------- - -List roles - -.. program:: role list -.. code:: bash - - openstack role list - [--domain ] - -.. option:: --domain - - Filter roles by (name or ID) - - .. versionadded:: 3 - -role remove ------------ - -Remove role assignment from domain/project : user/group - -.. program:: role remove -.. code:: bash - - openstack role remove - --system | --domain | --project [--project-domain ] - --user [--user-domain ] | --group [--group-domain ] - --role-domain - --inherited - - -.. option:: --system - - Include - - System or service to remove authorization from. Currently only ``all`` is - supported which encompasses the entire deployment system. - - .. versionadded:: 3 - -.. option:: --domain - - Include (name or ID) - - .. versionadded:: 3 - -.. option:: --project - - Include (name or ID) - -.. option:: --user - - Include (name or ID) - -.. option:: --group - - Include (name or ID) - - .. versionadded:: 3 - -.. option:: --user-domain - - Domain the user belongs to (name or ID). - This can be used in case collisions between user names exist. - - .. versionadded:: 3 - -.. option:: --group-domain - - Domain the group belongs to (name or ID). - This can be used in case collisions between group names exist. - - .. versionadded:: 3 - -.. option:: --project-domain - - Domain the project belongs to (name or ID). - This can be used in case collisions between project names exist. - - .. versionadded:: 3 - -.. option:: --inherited - - Specifies if the role grant is inheritable to the sub projects. - - .. versionadded:: 3 - -.. option:: --role-domain - - Domain the role belongs to (name or ID). - This must be specified when the name of a domain specific role is used. - - .. versionadded:: 3 - -.. describe:: - - Role to remove (name or ID) - -role set --------- - -Set role properties - -.. versionadded:: 3 - -.. program:: role set -.. code:: bash - - openstack role set - [--name ] - [--domain ] - [--immutable | --no-immutable] - - -.. option:: --name - - Set role name - -.. option:: --domain - - Domain the role belongs to (name or ID). - - .. versionadded:: 3 - -.. describe:: - - Role to modify (name or ID) - -.. option:: --immutable - - Make role immutable. An immutable role may not be deleted or modified - except to remove the immutable flag - -.. option:: --no-immutable - - Make role mutable (default) - -role show ---------- - -Display role details - -.. program:: role show -.. code:: bash - - openstack role show - [--domain ] - - -.. option:: --domain - - Domain the role belongs to (name or ID). - - .. versionadded:: 3 - -.. describe:: - - Role to display (name or ID) diff --git a/doc/source/cli/command-objects/service-provider.rst b/doc/source/cli/command-objects/service-provider.rst index b7a282cba5..47a503278c 100644 --- a/doc/source/cli/command-objects/service-provider.rst +++ b/doc/source/cli/command-objects/service-provider.rst @@ -7,16 +7,4 @@ extension. It is used by to register another OpenStack Identity service. Applicable to Identity v3. .. autoprogram-cliff:: openstack.identity.v3 - :command: service provider create - -.. autoprogram-cliff:: openstack.identity.v3 - :command: service provider delete - -.. autoprogram-cliff:: openstack.identity.v3 - :command: service provider list - -.. autoprogram-cliff:: openstack.identity.v3 - :command: service provider set - -.. autoprogram-cliff:: openstack.identity.v3 - :command: service provider show + :command: service provider * diff --git a/doc/source/cli/command-objects/service-v2.rst b/doc/source/cli/command-objects/service-v2.rst new file mode 100644 index 0000000000..6b22b7eeb6 --- /dev/null +++ b/doc/source/cli/command-objects/service-v2.rst @@ -0,0 +1,6 @@ +===================== +service (Identity v2) +===================== + +.. autoprogram-cliff:: openstack.identity.v2 + :command: service * diff --git a/doc/source/cli/command-objects/service-v3.rst b/doc/source/cli/command-objects/service-v3.rst new file mode 100644 index 0000000000..d4b702067b --- /dev/null +++ b/doc/source/cli/command-objects/service-v3.rst @@ -0,0 +1,18 @@ +===================== +service (Identity v3) +===================== + +.. autoprogram-cliff:: openstack.identity.v3 + :command: service create + +.. autoprogram-cliff:: openstack.identity.v3 + :command: service delete + +.. autoprogram-cliff:: openstack.identity.v3 + :command: service list + +.. autoprogram-cliff:: openstack.identity.v3 + :command: service show + +.. autoprogram-cliff:: openstack.identity.v3 + :command: service set diff --git a/doc/source/cli/command-objects/service.rst b/doc/source/cli/command-objects/service.rst deleted file mode 100644 index a69c69504b..0000000000 --- a/doc/source/cli/command-objects/service.rst +++ /dev/null @@ -1,143 +0,0 @@ -======= -service -======= - -Identity v2, v3 - -service create --------------- - -Create new service - -.. program:: service create -.. code-block:: bash - - openstack service create - [--name ] - [--description ] - [--enable | --disable] - - -.. option:: --name - - New service name - -.. option:: --description - - New service description - -.. option:: --enable - - Enable service (default) - - *Identity version 3 only* - -.. option:: --disable - - Disable service - - *Identity version 3 only* - -.. _service_create-type: -.. describe:: - - New service type (compute, image, identity, volume, etc) - -service delete --------------- - -Delete service(s) - -.. program:: service delete -.. code-block:: bash - - openstack service delete - [ ...] - -.. _service_delete-service: -.. describe:: - - Service(s) to delete (type, name or ID) - -service list ------------- - -List services - -.. program:: service list -.. code-block:: bash - - openstack service list - [--long] - -.. option:: --long - - List additional fields in output - -Returns service fields ID, Name and Type. :option:`--long` adds Description -and Enabled (*Identity version 3 only*) to the output. - -service set ------------ - -Set service properties - -* Identity version 3 only* - -.. program:: service set -.. code-block:: bash - - openstack service set - [--type ] - [--name ] - [--description ] - [--enable | --disable] - - -.. option:: --type - - New service type (compute, image, identity, volume, etc) - -.. option:: --name - - New service name - -.. option:: --description - - New service description - -.. option:: --enable - - Enable service - -.. option:: --disable - - Disable service - -.. _service_set-service: -.. describe:: - - Service to modify (type, name or ID) - -service show ------------- - -Display service details - -.. program:: service show -.. code-block:: bash - - openstack service show - [--catalog] - - -.. option:: --catalog - - Show service catalog information - - *Identity version 2 only* - -.. _service_show-service: -.. describe:: - - Service to display (type, name or ID) diff --git a/doc/source/cli/command-objects/token-v2.rst b/doc/source/cli/command-objects/token-v2.rst new file mode 100644 index 0000000000..c66302bfb2 --- /dev/null +++ b/doc/source/cli/command-objects/token-v2.rst @@ -0,0 +1,7 @@ +=================== +token (Identity v2) +=================== + + +.. autoprogram-cliff:: openstack.identity.v2 + :command: token * diff --git a/doc/source/cli/command-objects/token-v3.rst b/doc/source/cli/command-objects/token-v3.rst new file mode 100644 index 0000000000..6b2d87a61d --- /dev/null +++ b/doc/source/cli/command-objects/token-v3.rst @@ -0,0 +1,7 @@ +=================== +token (Identity v3) +=================== + + +.. autoprogram-cliff:: openstack.identity.v3 + :command: token * diff --git a/doc/source/cli/command-objects/token.rst b/doc/source/cli/command-objects/token.rst deleted file mode 100644 index b4b14cd9fd..0000000000 --- a/doc/source/cli/command-objects/token.rst +++ /dev/null @@ -1,30 +0,0 @@ -===== -token -===== - -Identity v2, v3 - -token issue ------------ - -Issue new token - -.. program:: token issue -.. code:: bash - - openstack token issue - -token revoke ------------- - -Revoke existing token - -.. program:: token revoke -.. code:: bash - - openstack token revoke - - -.. describe:: - - Token to be deleted diff --git a/doc/source/cli/command-objects/trust.rst b/doc/source/cli/command-objects/trust.rst index febef1c5b6..738c640c48 100644 --- a/doc/source/cli/command-objects/trust.rst +++ b/doc/source/cli/command-objects/trust.rst @@ -6,13 +6,4 @@ A **trust** provide project-specific role delegation between users, with optional impersonation. Requires the OS-TRUST extension. Applies to Identity v3. .. autoprogram-cliff:: openstack.identity.v3 - :command: trust create - -.. autoprogram-cliff:: openstack.identity.v3 - :command: trust delete - -.. autoprogram-cliff:: openstack.identity.v3 - :command: trust list - -.. autoprogram-cliff:: openstack.identity.v3 - :command: trust show + :command: trust * diff --git a/doc/source/cli/command-objects/user-v2.rst b/doc/source/cli/command-objects/user-v2.rst new file mode 100644 index 0000000000..966bd37620 --- /dev/null +++ b/doc/source/cli/command-objects/user-v2.rst @@ -0,0 +1,7 @@ +================== +user (Identity v2) +================== + + +.. autoprogram-cliff:: openstack.identity.v2 + :command: user * diff --git a/doc/source/cli/command-objects/user-v3.rst b/doc/source/cli/command-objects/user-v3.rst new file mode 100644 index 0000000000..c11ff9a645 --- /dev/null +++ b/doc/source/cli/command-objects/user-v3.rst @@ -0,0 +1,7 @@ +================== +user (Identity v3) +================== + + +.. autoprogram-cliff:: openstack.identity.v3 + :command: user * diff --git a/doc/source/cli/command-objects/user.rst b/doc/source/cli/command-objects/user.rst deleted file mode 100644 index d0fc3f8734..0000000000 --- a/doc/source/cli/command-objects/user.rst +++ /dev/null @@ -1,349 +0,0 @@ -==== -user -==== - -Identity v2, v3 - -user create ------------ - -Create new user - -.. program:: user create -.. code:: bash - - openstack user create - [--domain ] - [--project [--project-domain ]] - [--password ] - [--password-prompt] - [--email ] - [--description ] - [--multi-factor-auth-rule ] - [--ignore-lockout-failure-attempts| --no-ignore-lockout-failure-attempts] - [--ignore-password-expiry| --no-ignore-password-expiry] - [--ignore-change-password-upon-first-use| --no-ignore-change-password-upon-first-use] - [--enable-lock-password| --disable-lock-password] - [--enable-multi-factor-auth| --disable-multi-factor-auth] - [--enable | --disable] - [--or-show] - - -.. option:: --domain - - Default domain (name or ID) - - .. versionadded:: 3 - -.. option:: --project - - Default project (name or ID) - -.. option:: --project-domain - - Domain the project belongs to (name or ID). - This can be used in case collisions between project names exist. - -.. option:: --password - - Set user password - -.. option:: --password-prompt - - Prompt interactively for password - -.. option:: --email - - Set user email address - -.. option:: --description - - User description - - .. versionadded:: 3 - -.. option:: --ignore-lockout-failure-attempts - - Opt into ignoring the number of times a user has authenticated and - locking out the user as a result - -.. option:: --no-ignore-lockout-failure-attempts - - Opt out of ignoring the number of times a user has authenticated - and locking out the user as a result - -.. option:: --ignore-change-password-upon-first-use - - Control if a user should be forced to change their password immediately - after they log into keystone for the first time. Opt into ignoring - the user to change their password during first time login in keystone. - -.. option:: --no-ignore-change-password-upon-first-use - - Control if a user should be forced to change their password immediately - after they log into keystone for the first time. Opt out of ignoring - the user to change their password during first time login in keystone. - -.. option:: --ignore-password-expiry - - Opt into allowing user to continue using passwords that may be - expired - -.. option:: --no-ignore-password-expiry - - Opt out of allowing user to continue using passwords that may be - expired - -.. option:: --enable-lock-password - - Disables the ability for a user to change its password through - self-service APIs - -.. option:: --disable-lock-password - - Enables the ability for a user to change its password through - self-service APIs - -.. option:: --enable-multi-factor-auth - - Enables the MFA (Multi Factor Auth) - -.. option:: --disable-multi-factor-auth - - Disables the MFA (Multi Factor Auth) - -.. option:: --multi-factor-auth-rule - - Set multi-factor auth rules. For example, to set a rule requiring the - "password" and "totp" auth methods to be provided, - use: "--multi-factor-auth-rule password,totp". - May be provided multiple times to set different rule combinations. - -.. option:: --enable - - Enable user (default) - -.. option:: --disable - - Disable user - -.. option:: --or-show - - Return existing user - - If the username already exist return the existing user data and do not fail. - -.. describe:: - - New user name - -user delete ------------ - -Delete user(s) - -.. program:: user delete -.. code:: bash - - openstack user delete - [--domain ] - [ ...] - -.. option:: --domain - - Domain owning :ref:`\ ` (name or ID) - - .. versionadded:: 3 - -.. _user_delete-user: -.. describe:: - - User(s) to delete (name or ID) - -user list ---------- - -List users - -.. program:: user list -.. code:: bash - - openstack user list - [--project ] - [--domain ] - [--group | --project ] - [--long] - -.. option:: --project - - Filter users by `` (name or ID) - -.. option:: --domain - - Filter users by `` (name or ID) - - *Identity version 3 only* - -.. option:: --group - - Filter users by `` membership (name or ID) - - *Identity version 3 only* - -.. option:: --long - - List additional fields in output - -user set --------- - -Set user properties - -.. program:: user set -.. code:: bash - - openstack user set - [--name ] - [--project [--project-domain ]] - [--password ] - [--password-prompt] - [--email ] - [--description ] - [--multi-factor-auth-rule ] - [--ignore-lockout-failure-attempts| --no-ignore-lockout-failure-attempts] - [--ignore-password-expiry| --no-ignore-password-expiry] - [--ignore-change-password-upon-first-use| --no-ignore-change-password-upon-first-use] - [--enable-lock-password| --disable-lock-password] - [--enable-multi-factor-auth| --disable-multi-factor-auth] - [--enable|--disable] - - -.. option:: --name - - Set user name - -.. option:: --domain - - Domain the user belongs to (name or ID). - This can be used in case collisions between user names exist. - - .. versionadded:: 3 - -.. option:: --project - - Set default project (name or ID) - -.. option:: --project-domain - - Domain the project belongs to (name or ID). - This can be used in case collisions between project names exist. - -.. option:: --password - - Set user password - -.. option:: --password-prompt - - Prompt interactively for password - -.. option:: --email - - Set user email address - -.. option:: --description - - Set user description - - .. versionadded:: 3 - -.. option:: --ignore-lockout-failure-attempts - - Opt into ignoring the number of times a user has authenticated and - locking out the user as a result - -.. option:: --no-ignore-lockout-failure-attempts - - Opt out of ignoring the number of times a user has authenticated - and locking out the user as a result - -.. option:: --ignore-change-password-upon-first-use - - Control if a user should be forced to change their password immediately - after they log into keystone for the first time. Opt into ignoring - the user to change their password during first time login in keystone. - -.. option:: --no-ignore-change-password-upon-first-use - - Control if a user should be forced to change their password immediately - after they log into keystone for the first time. Opt out of ignoring - the user to change their password during first time login in keystone. - -.. option:: --ignore-password-expiry - - Opt into allowing user to continue using passwords that may be - expired - -.. option:: --no-ignore-password-expiry - - Opt out of allowing user to continue using passwords that may be - expired - -.. option:: --enable-lock-password - - Disables the ability for a user to change its password through - self-service APIs - -.. option:: --disable-lock-password - - Enables the ability for a user to change its password through - self-service APIs - -.. option:: --enable-multi-factor-auth - - Enables the MFA (Multi Factor Auth) - -.. option:: --disable-multi-factor-auth - - Disables the MFA (Multi Factor Auth) - -.. option:: --multi-factor-auth-rule - - Set multi-factor auth rules. For example, to set a rule requiring the - "password" and "totp" auth methods to be provided, - use: "--multi-factor-auth-rule password,totp". - May be provided multiple times to set different rule combinations. - -.. option:: --enable - - Enable user (default) - -.. option:: --disable - - Disable user - -.. describe:: - - User to modify (name or ID) - -user show ---------- - -Display user details - -.. program:: user show -.. code:: bash - - openstack user show - [--domain ] - - -.. option:: --domain - - Domain owning :ref:`\ ` (name or ID) - - .. versionadded:: 3 - -.. _user_show-user: -.. describe:: - - User to display (name or ID) diff --git a/doc/source/cli/index.rst b/doc/source/cli/index.rst index 17e50a07d2..9cb3b33a73 100644 --- a/doc/source/cli/index.rst +++ b/doc/source/cli/index.rst @@ -13,3 +13,12 @@ interactive decoder backwards-incompatible + +.. NOTE(efried): Everything must be in a toctree but we don't want these to + show up to the reader. + +.. toctree:: + :glob: + :hidden: + + _hidden/* diff --git a/doc/test/redirect-tests.txt b/doc/test/redirect-tests.txt index ef3a48decb..d18660a0b2 100644 --- a/doc/test/redirect-tests.txt +++ b/doc/test/redirect-tests.txt @@ -16,3 +16,10 @@ /python-openstackclient/latest/humaninterfaceguide.html 301 /python-openstackclient/latest/contributor/humaninterfaceguide.html /python-openstackclient/latest/plugins.html 301 /python-openstackclient/latest/contributor/plugins.html /python-openstackclient/latest/cli/plugin-commands.html 301 /python-openstackclient/latest/cli/plugin-commands/index.html +/python-openstackclient/latest/cli/command-objects/ec2-credentials.html 301 /python-openstackclient/latest/cli/_hidden/ec2-credentials.html +/python-openstackclient/latest/cli/command-objects/endpoint.html 301 /python-openstackclient/latest/cli/_hidden/endpoint.html +/python-openstackclient/latest/cli/command-objects/project.html 301 /python-openstackclient/latest/cli/_hidden/project.html +/python-openstackclient/latest/cli/command-objects/role.html 301 /python-openstackclient/latest/cli/_hidden/role.html +/python-openstackclient/latest/cli/command-objects/service.html 301 /python-openstackclient/latest/cli/_hidden/service.html +/python-openstackclient/latest/cli/command-objects/token.html 301 /python-openstackclient/latest/cli/_hidden/token.html +/python-openstackclient/latest/cli/command-objects/user.html 301 /python-openstackclient/latest/cli/_hidden/user.html diff --git a/setup.cfg b/setup.cfg index 1ba0691cc0..273529b1ec 100644 --- a/setup.cfg +++ b/setup.cfg @@ -248,6 +248,15 @@ openstack.identity.v3 = endpoint_group_set = openstackclient.identity.v3.endpoint_group:SetEndpointGroup endpoint_group_show = openstackclient.identity.v3.endpoint_group:ShowEndpointGroup + federation_domain_list = openstackclient.identity.v3.unscoped_saml:ListAccessibleDomains + federation_project_list = openstackclient.identity.v3.unscoped_saml:ListAccessibleProjects + + federation_protocol_create = openstackclient.identity.v3.federation_protocol:CreateProtocol + federation_protocol_delete = openstackclient.identity.v3.federation_protocol:DeleteProtocol + federation_protocol_list = openstackclient.identity.v3.federation_protocol:ListProtocols + federation_protocol_set = openstackclient.identity.v3.federation_protocol:SetProtocol + federation_protocol_show = openstackclient.identity.v3.federation_protocol:ShowProtocol + group_add_user = openstackclient.identity.v3.group:AddUserToGroup group_contains_user = openstackclient.identity.v3.group:CheckUserInGroup group_create = openstackclient.identity.v3.group:CreateGroup @@ -291,15 +300,6 @@ openstack.identity.v3 = project_set = openstackclient.identity.v3.project:SetProject project_show = openstackclient.identity.v3.project:ShowProject - federation_protocol_create = openstackclient.identity.v3.federation_protocol:CreateProtocol - federation_protocol_delete = openstackclient.identity.v3.federation_protocol:DeleteProtocol - federation_protocol_list = openstackclient.identity.v3.federation_protocol:ListProtocols - federation_protocol_set = openstackclient.identity.v3.federation_protocol:SetProtocol - federation_protocol_show = openstackclient.identity.v3.federation_protocol:ShowProtocol - - federation_domain_list = openstackclient.identity.v3.unscoped_saml:ListAccessibleDomains - federation_project_list = openstackclient.identity.v3.unscoped_saml:ListAccessibleProjects - region_create = openstackclient.identity.v3.region:CreateRegion region_delete = openstackclient.identity.v3.region:DeleteRegion region_list = openstackclient.identity.v3.region:ListRegion From da3c3bde24a0263a9ff3769a5fa67e2aff8f3784 Mon Sep 17 00:00:00 2001 From: Eric Fried Date: Tue, 5 Nov 2019 08:58:58 -0600 Subject: [PATCH 2210/3095] image: autogenerate docs $namespace = openstack.image.v{1|2} The subcommand documents for $namespace were hardcoded and thus prone to drift over time. This commit removes the hardcoded content and uses the autoprogram-cliff directive to generate them automatically from the subcommand configuration classes. Some reorganization happened here. The `image` subcommand name is shared by image v1 and v2. Previously the hardcoded document had them combined and interleaved. Attempting to preserve this with autoprogram-cliff would have required significant additional infrastructure. However, since most readers care completely about one and not at all about the other, we instead split the v1 and v2 versions of these commands into separate pages. In case links to the old pages exist in the wild, they are preserved, but moved (with redirects) to a hidden directory, and populated simply with links to the new version-specific generated documents. Change-Id: I24dc6dc10671c7f1267c27002542f61f8a3c18ae --- doc/source/_extra/.htaccess | 6 +- doc/source/cli/_hidden/image.rst | 13 + doc/source/cli/command-objects/image-v1.rst | 6 + doc/source/cli/command-objects/image-v2.rst | 6 + doc/source/cli/command-objects/image.rst | 650 -------------------- doc/test/redirect-tests.txt | 1 + 6 files changed, 30 insertions(+), 652 deletions(-) create mode 100644 doc/source/cli/_hidden/image.rst create mode 100644 doc/source/cli/command-objects/image-v1.rst create mode 100644 doc/source/cli/command-objects/image-v2.rst delete mode 100644 doc/source/cli/command-objects/image.rst diff --git a/doc/source/_extra/.htaccess b/doc/source/_extra/.htaccess index 1ac063cc35..8c0a005425 100644 --- a/doc/source/_extra/.htaccess +++ b/doc/source/_extra/.htaccess @@ -9,6 +9,8 @@ redirectmatch 301 ^/python-openstackclient/([^/]+)/specs/([^/.]+).html$ /python- redirectmatch 301 ^/python-openstackclient/([^/]+)/(command-(beta|errors|logs|options|wrappers)|developing|humaninterfaceguide|plugins).html$ /python-openstackclient/$1/contributor/$2.html redirectmatch 301 ^/python-openstackclient/([^/]+)/cli/plugin-commands.html$ /python-openstackclient/$1/cli/plugin-commands/index.html -# Identity pages were split into -v2 and -v3 for common subcommand names. +# For common subcommand names: +# - identity pages were split into -v2 and -v3 +# - image pages were split into -v1 and -v2 # The unversioned page is hidden but contains links to the versioned pages so links in the wild redirect somewhere sane. -redirectmatch 301 ^/python-openstackclient/([^/]+)/cli/command-objects/(ec2-credentials|endpoint|project|role|service|token|user).html$ /python-openstackclient/$1/cli/_hidden/$2.html +redirectmatch 301 ^/python-openstackclient/([^/]+)/cli/command-objects/(ec2-credentials|endpoint|image|project|role|service|token|user).html$ /python-openstackclient/$1/cli/_hidden/$2.html diff --git a/doc/source/cli/_hidden/image.rst b/doc/source/cli/_hidden/image.rst new file mode 100644 index 0000000000..85ffde6f39 --- /dev/null +++ b/doc/source/cli/_hidden/image.rst @@ -0,0 +1,13 @@ +===== +image +===== + +.. NOTE(efried): This page is hidden from the main TOC; it's here so links in + the wild redirect somewhere sane, because previously identity v2 and v3 were + combined in a single page. + +.. toctree:: + :maxdepth: 2 + + ../command-objects/image-v1 + ../command-objects/image-v2 diff --git a/doc/source/cli/command-objects/image-v1.rst b/doc/source/cli/command-objects/image-v1.rst new file mode 100644 index 0000000000..4f7edc4322 --- /dev/null +++ b/doc/source/cli/command-objects/image-v1.rst @@ -0,0 +1,6 @@ +======== +image v1 +======== + +.. autoprogram-cliff:: openstack.image.v1 + :command: image * diff --git a/doc/source/cli/command-objects/image-v2.rst b/doc/source/cli/command-objects/image-v2.rst new file mode 100644 index 0000000000..473b26d07b --- /dev/null +++ b/doc/source/cli/command-objects/image-v2.rst @@ -0,0 +1,6 @@ +======== +image v2 +======== + +.. autoprogram-cliff:: openstack.image.v2 + :command: image * diff --git a/doc/source/cli/command-objects/image.rst b/doc/source/cli/command-objects/image.rst deleted file mode 100644 index 459a077029..0000000000 --- a/doc/source/cli/command-objects/image.rst +++ /dev/null @@ -1,650 +0,0 @@ -===== -image -===== - -Image v1, v2 - -image add project ------------------ - -*Only supported for Image v2* - -Associate project with image - -.. program:: image add project -.. code:: bash - - openstack image add project - [--project-domain ] - - - -.. option:: --project-domain - - Domain the project belongs to (name or ID). - This can be used in case collisions between project names exist. - -.. _image_add_project-image: -.. describe:: - - Image to share (name or ID). - -.. _image_add_project-project: -.. describe:: - - Project to associate with image (ID) - -image create ------------- - -*Image v1, v2* - -Create/upload an image - -.. program:: image create -.. code:: bash - - openstack image create - [--id ] - [--store ] - [--container-format ] - [--disk-format ] - [--size ] - [--min-disk ] - [--min-ram ] - [--location ] - [--copy-from ] - [--file | --volume ] - [--force] - [--checksum ] - [--protected | --unprotected] - [--public | --private | --community | --shared] - [--property [...] ] - [--tag [...] ] - [--project ] - [--project-domain ] - - -.. option:: --id - - Image ID to reserve - -.. option:: --store - - Upload image to this store - - *Image version 1 only.* - -.. option:: --container-format - - Image container format. The supported options are: ami, ari, aki, - bare, docker, ova, ovf. The default format is: bare - -.. option:: --disk-format - - Image disk format. The supported options are: ami, ari, aki, vhd, vmdk, - raw, qcow2, vhdx, vdi, iso, and ploop. The default format is: raw - -.. option:: --size - - Image size, in bytes (only used with :option:`--location` and :option:`--copy-from`) - - *Image version 1 only.* - -.. option:: --min-disk - - Minimum disk size needed to boot image, in gigabytes - -.. option:: --min-ram - - Minimum RAM size needed to boot image, in megabytes - -.. option:: --location - - Download image from an existing URL - - *Image version 1 only.* - -.. option:: --copy-from - - Copy image from the data store (similar to :option:`--location`) - - *Image version 1 only.* - -.. option:: --file - - Upload image from local file - -.. option:: --volume - - Create image from a volume - -.. option:: --force - - Force image creation if volume is in use (only meaningful with :option:`--volume`) - -.. option:: --checksum - - Image hash used for verification - - *Image version 1 only.* - -.. option:: --protected - - Prevent image from being deleted - -.. option:: --unprotected - - Allow image to be deleted (default) - -.. option:: --public - - Image is accessible to the public - -.. option:: --private - - Image is inaccessible to the public (default) - -.. option:: --community - - Image is accessible to the community - -.. option:: --shared - - Image can be shared - -.. option:: --property - - Set a property on this image (repeat option to set multiple properties) - -.. option:: --tag - - Set a tag on this image (repeat option to set multiple tags) - - .. versionadded:: 2 - -.. option:: --project - - Set an alternate project on this image (name or ID). - Previously known as `--owner`. - -.. option:: --project-domain - - Domain the project belongs to (name or ID). - This can be used in case collisions between project names exist. - - .. versionadded:: 2 - -.. _image_create-image-name: -.. describe:: - - New image name - -image delete ------------- - -Delete image(s) - -.. program:: image delete -.. code:: bash - - openstack image delete - - -.. _image_delete-image: -.. describe:: - - Image(s) to delete (name or ID) - -image list ----------- - -List available images - -.. program:: image list -.. code:: bash - - openstack image list - [--public | --private | --community | --shared] - [--property ] - [--name ] - [--status ] - [--member-status ] - [--tag ] - [--long] - [--sort [:]] - [--limit ] - [--marker ] - -.. option:: --public - - List only public images - -.. option:: --private - - List only private images - -.. option:: --community - - List only community images - - *Image version 2 only.* - -.. option:: --shared - - List only shared images - - *Image version 2 only.* - -.. option:: --property - - Filter output based on property - -.. option:: --name - - Filter images based on name - - *Image version 2 only.* - -.. option:: --status - - Filter images based on status - - *Image version 2 only* - -.. option:: --member-status - - Filter images based on member status - - *Image version 2 only* - -.. option:: --tag - - Filter images based on tag - - *Image version 2 only* - -.. option:: --long - - List additional fields in output - -.. option:: --sort [:] - - Sort output by selected keys and directions(asc or desc) (default: name:asc), - multiple keys and directions can be specified separated by comma - -.. option:: --limit - - Maximum number of images to display. - - *Image version 2 only* - -.. option:: --marker - - The last image of the previous page. Display list of images - after marker. Display all images if not specified. (name or ID) - - *Image version 2 only* - -image member list ------------------ - -List projects associated with image - -.. program:: image member list -.. code:: bash - - openstack image member list - - -.. _image_member_list-image: -.. describe:: - - Image(s) to view members for (name or ID) - -image remove project --------------------- - -*Only supported for Image v2* - -Disassociate project with image - -.. program:: image remove project -.. code:: bash - - openstack image remove project - [--project-domain ] - - - -.. option:: --project-domain - - Domain the project belongs to (name or ID). - This can be used in case collisions between project names exist. - -.. _image_remove_project: -.. describe:: - - Image to unshare (name or ID). - -.. describe:: - - Project to disassociate with image (name or ID) - -image save ----------- - -Save an image locally - -.. program:: image save -.. code:: bash - - openstack image save - --file - - -.. option:: --file - - Downloaded image save filename (default: stdout) - -.. _image_save-image: -.. describe:: - - Image to save (name or ID) - -image set ---------- - -*Image v1, v2* - -Set image properties - -.. program:: image set -.. code:: bash - - openstack image set - [--name ] - [--min-disk ] - [--min-ram ] - [--container-format ] - [--disk-format ] - [--size ] - [--protected | --unprotected] - [--public | --private | --community | --shared] - [--store ] - [--location ] - [--copy-from ] - [--file ] - [--volume ] - [--force] - [--checksum ] - [--stdin] - [--property [...] ] - [--tag [...] ] - [--architecture ] - [--instance-id ] - [--kernel-id ] - [--os-distro ] - [--os-version ] - [--ramdisk-id ] - [--deactivate | --activate] - [--project ] - [--project-domain ] - [--accept | --reject | --pending] - - -.. option:: --name - - New image name - -.. option:: --min-disk - - Minimum disk size needed to boot image, in gigabytes - -.. option:: --min-ram - - Minimum RAM size needed to boot image, in megabytes - -.. option:: --container-format - - Image container format. The supported options are: ami, ari, aki, - bare, docker, ova, ovf. - -.. option:: --disk-format - - Image disk format. The supported options are: ami, ari, aki, vhd, vmdk, - raw, qcow2, vhdx, vdi, iso, and ploop. - -.. option:: --size - - Size of image data (in bytes) - - *Image version 1 only.* - -.. option:: --protected - - Prevent image from being deleted - -.. option:: --unprotected - - Allow image to be deleted (default) - -.. option:: --public - - Image is accessible to the public - -.. option:: --private - - Image is inaccessible to the public (default) - -.. option:: --community - - Image is accessible to the community - -.. option:: --shared - - Image can be shared - -.. option:: --store - - Upload image to this store - - *Image version 1 only.* - -.. option:: --location - - Download image from an existing URL - - *Image version 1 only.* - -.. option:: --copy-from - - Copy image from the data store (similar to :option:`--location`) - - *Image version 1 only.* - -.. option:: --file - - Upload image from local file - - *Image version 1 only.* - -.. option:: --volume - - Update image with a volume - - *Image version 1 only.* - -.. option:: --force - - Force image update if volume is in use (only meaningful with :option:`--volume`) - - *Image version 1 only.* - -.. option:: --checksum - - Image hash used for verification - - *Image version 1 only.* - -.. option:: --stdin - - Allow to read image data from standard input - - *Image version 1 only.* - -.. option:: --property - - Set a property on this image (repeat option to set multiple properties) - - .. versionadded:: 2 - -.. option:: --tag - - Set a tag on this image (repeat option to set multiple tags) - - .. versionadded:: 2 - -.. option:: --architecture - - Operating system architecture - - .. versionadded:: 2 - -.. option:: --instance-id - - ID of server instance used to create this image - - .. versionadded:: 2 - -.. option:: --kernel-id - - ID of kernel image used to boot this disk image - - .. versionadded:: 2 - -.. option:: --os-distro - - Operating system distribution name - - .. versionadded:: 2 - -.. option:: --os-version - - Operating system distribution version - - .. versionadded:: 2 - -.. option:: --ramdisk-id - - ID of ramdisk image used to boot this disk image - - .. versionadded:: 2 - -.. option:: --deactivate - - Deactivate the image. - - .. versionadded:: 2 - -.. option:: --activate - - Activate the image. - - .. versionadded:: 2 - -.. option:: --project - - Set an alternate project on this image (name or ID). - Previously known as `--owner`. - -.. option:: --project-domain - - Domain the project belongs to (name or ID). - This can be used in case collisions between project names exist. - - .. versionadded:: 2 - -.. option:: --accept - - Accept the image membership. - - If `--project` is passed, this will update the membership status for the - given project, otherwise `--project` will default to the project the user - is authenticated to. - - .. versionadded:: 2 - -.. option:: --reject - - Reject the image membership. - - If `--project` is passed, this will update the membership status for the - given project, otherwise `--project` will default to the project the user - is authenticated to. - - .. versionadded:: 2 - -.. option:: --pending - - Reset the image membership to 'pending'. - - If `--project` is passed, this will update the membership status for the - given project, otherwise `--project` will default to the project the user - is authenticated to. - - .. versionadded:: 2 - -.. _image_set-image: -.. describe:: - - Image to modify (name or ID) - -image show ----------- - -Display image details - -.. program:: image show -.. code:: bash - - openstack image show - [--human-readable] - - -.. option:: --human-readable - - Print image size in a human-friendly format. - -.. _image_show-image: -.. describe:: - - Image to display (name or ID) - -image unset ------------ - -*Only supported for Image v2* - -Unset image tags or properties - -.. program:: image unset -.. code:: bash - - openstack image unset - [--tag ] - [--property ] - - -.. option:: --tag - - Unset a tag on this image (repeat option to unset multiple tags) - -.. option:: --property - - Unset a property on this image (repeat option to unset multiple properties) - -.. _image_unset-image: -.. describe:: - - Image to modify (name or ID) diff --git a/doc/test/redirect-tests.txt b/doc/test/redirect-tests.txt index d18660a0b2..5ed7c19cb5 100644 --- a/doc/test/redirect-tests.txt +++ b/doc/test/redirect-tests.txt @@ -18,6 +18,7 @@ /python-openstackclient/latest/cli/plugin-commands.html 301 /python-openstackclient/latest/cli/plugin-commands/index.html /python-openstackclient/latest/cli/command-objects/ec2-credentials.html 301 /python-openstackclient/latest/cli/_hidden/ec2-credentials.html /python-openstackclient/latest/cli/command-objects/endpoint.html 301 /python-openstackclient/latest/cli/_hidden/endpoint.html +/python-openstackclient/latest/cli/command-objects/image.html 301 /python-openstackclient/latest/cli/_hidden/image.html /python-openstackclient/latest/cli/command-objects/project.html 301 /python-openstackclient/latest/cli/_hidden/project.html /python-openstackclient/latest/cli/command-objects/role.html 301 /python-openstackclient/latest/cli/_hidden/role.html /python-openstackclient/latest/cli/command-objects/service.html 301 /python-openstackclient/latest/cli/_hidden/service.html From 176907f70eb5b84d15efc03571ca772706a164a6 Mon Sep 17 00:00:00 2001 From: Gabriel Ramirez Date: Thu, 4 Jun 2020 12:46:25 -0700 Subject: [PATCH 2211/3095] Allow openstack flavor set to update flavor description using name Modified take_action() method for SetFlavor to use flavor id instead of flavor name when setting description Closes-Bug: #1844708 Story: #2007781 Task: #40019 Change-Id: If6798c89fef4c9feb4ebb460722b891f5655037d --- openstackclient/compute/v2/flavor.py | 2 +- .../tests/unit/compute/v2/test_flavor.py | 36 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 42649db504..805e919ea3 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -402,7 +402,7 @@ def take_action(self, parsed_args): if compute_client.api_version < api_versions.APIVersion("2.55"): msg = _("--os-compute-api-version 2.55 or later is required") raise exceptions.CommandError(msg) - compute_client.flavors.update(flavor=parsed_args.flavor, + compute_client.flavors.update(flavor=flavor.id, description=parsed_args.description) diff --git a/openstackclient/tests/unit/compute/v2/test_flavor.py b/openstackclient/tests/unit/compute/v2/test_flavor.py index fe7ce17481..4732cc822d 100644 --- a/openstackclient/tests/unit/compute/v2/test_flavor.py +++ b/openstackclient/tests/unit/compute/v2/test_flavor.py @@ -749,6 +749,42 @@ def test_flavor_set_description_api_older(self): self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) + def test_flavor_set_description_using_name_api_newer(self): + arglist = [ + '--description', 'description', + self.flavor.name, + ] + verifylist = [ + ('description', 'description'), + ('flavor', self.flavor.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.app.client_manager.compute.api_version = 2.55 + with mock.patch.object(novaclient.api_versions, + 'APIVersion', + return_value=2.55): + result = self.cmd.take_action(parsed_args) + self.flavors_mock.update.assert_called_with( + flavor=self.flavor.id, description='description') + self.assertIsNone(result) + + def test_flavor_set_description_using_name_api_older(self): + arglist = [ + '--description', 'description', + self.flavor.name, + ] + verifylist = [ + ('description', 'description'), + ('flavor', self.flavor.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.app.client_manager.compute.api_version = 2.54 + with mock.patch.object(novaclient.api_versions, + 'APIVersion', + return_value=2.55): + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + class TestFlavorShow(TestFlavor): From a04172969a5e440af50127dfd27a80a6beb7ddb8 Mon Sep 17 00:00:00 2001 From: "Jens Harbott (frickler)" Date: Wed, 17 Jun 2020 10:07:12 +0000 Subject: [PATCH 2212/3095] Revert "Format location columns in network commands" This reverts commit 6ee7b8d138e07bfc37c5cd887f7afa49cdabb02f. Change-Id: I5f59959ba8a01aba49e29f4cb007397467344e58 --- openstackclient/network/v2/address_scope.py | 10 ++-------- openstackclient/network/v2/floating_ip.py | 2 -- openstackclient/network/v2/ip_availability.py | 2 -- openstackclient/network/v2/network.py | 1 - openstackclient/network/v2/network_agent.py | 1 - .../v2/network_auto_allocated_topology.py | 17 +++++------------ openstackclient/network/v2/network_flavor.py | 10 ++-------- .../network/v2/network_flavor_profile.py | 10 ++-------- openstackclient/network/v2/network_meter.py | 10 ++-------- .../network/v2/network_meter_rule.py | 10 ++-------- .../network/v2/network_qos_policy.py | 10 ++-------- openstackclient/network/v2/network_qos_rule.py | 10 ++-------- .../network/v2/network_qos_rule_type.py | 8 +------- openstackclient/network/v2/network_rbac.py | 10 ++-------- openstackclient/network/v2/network_segment.py | 10 ++-------- .../network/v2/network_segment_range.py | 9 ++------- openstackclient/network/v2/port.py | 1 - openstackclient/network/v2/router.py | 1 - openstackclient/network/v2/security_group.py | 3 --- .../network/v2/security_group_rule.py | 10 ++-------- openstackclient/network/v2/subnet.py | 1 - openstackclient/network/v2/subnet_pool.py | 1 - 22 files changed, 28 insertions(+), 119 deletions(-) diff --git a/openstackclient/network/v2/address_scope.py b/openstackclient/network/v2/address_scope.py index 7efbb6311e..71c1a9afb7 100644 --- a/openstackclient/network/v2/address_scope.py +++ b/openstackclient/network/v2/address_scope.py @@ -15,7 +15,6 @@ import logging -from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -28,11 +27,6 @@ LOG = logging.getLogger(__name__) -_formatters = { - 'location': format_columns.DictColumn, -} - - def _get_columns(item): column_map = { 'is_shared': 'shared', @@ -106,7 +100,7 @@ def take_action(self, parsed_args): attrs = _get_attrs(self.app.client_manager, parsed_args) obj = client.create_address_scope(**attrs) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns, formatters=_formatters) + data = utils.get_item_properties(obj, columns, formatters={}) return (display_columns, data) @@ -295,6 +289,6 @@ def take_action(self, parsed_args): parsed_args.address_scope, ignore_missing=False) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns, formatters=_formatters) + data = utils.get_item_properties(obj, columns, formatters={}) return (display_columns, data) diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index f3e3e5c44c..a2765cd1ef 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -13,7 +13,6 @@ """IP Floating action implementations""" -from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import utils from osc_lib.utils import tags as _tag @@ -25,7 +24,6 @@ _formatters = { - 'location': format_columns.DictColumn, 'port_details': utils.format_dict, } diff --git a/openstackclient/network/v2/ip_availability.py b/openstackclient/network/v2/ip_availability.py index c026baa0cc..ddc88e557e 100644 --- a/openstackclient/network/v2/ip_availability.py +++ b/openstackclient/network/v2/ip_availability.py @@ -21,9 +21,7 @@ from openstackclient.identity import common as identity_common from openstackclient.network import sdk_utils - _formatters = { - 'location': format_columns.DictColumn, 'subnet_ip_availability': format_columns.ListDictColumn, } diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 00cb782b73..7a12d523e2 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -40,7 +40,6 @@ def human_readable(self): 'subnet_ids': format_columns.ListColumn, 'admin_state_up': AdminStateColumn, 'is_admin_state_up': AdminStateColumn, - 'location': format_columns.DictColumn, 'router:external': RouterExternalColumn, 'is_router_external': RouterExternalColumn, 'availability_zones': format_columns.ListColumn, diff --git a/openstackclient/network/v2/network_agent.py b/openstackclient/network/v2/network_agent.py index a6ed36299c..1678485434 100644 --- a/openstackclient/network/v2/network_agent.py +++ b/openstackclient/network/v2/network_agent.py @@ -43,7 +43,6 @@ def human_readable(self): 'alive': AliveColumn, 'admin_state_up': AdminStateColumn, 'is_admin_state_up': AdminStateColumn, - 'location': format_columns.DictColumn, 'configurations': format_columns.DictColumn, } diff --git a/openstackclient/network/v2/network_auto_allocated_topology.py b/openstackclient/network/v2/network_auto_allocated_topology.py index f6070a0277..36f392006e 100644 --- a/openstackclient/network/v2/network_auto_allocated_topology.py +++ b/openstackclient/network/v2/network_auto_allocated_topology.py @@ -15,7 +15,6 @@ import logging -from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import utils @@ -26,11 +25,6 @@ LOG = logging.getLogger(__name__) -_formatters = { - 'location': format_columns.DictColumn, -} - - def _get_columns(item): column_map = { 'tenant_id': 'project_id', @@ -99,17 +93,16 @@ def check_resource_topology(self, client, parsed_args): obj = client.validate_auto_allocated_topology(parsed_args.project) columns = _format_check_resource_columns() - data = utils.get_item_properties( - _format_check_resource(obj), - columns, - formatters=_formatters, - ) + data = utils.get_item_properties(_format_check_resource(obj), + columns, + formatters={}) + return (columns, data) def get_topology(self, client, parsed_args): obj = client.get_auto_allocated_topology(parsed_args.project) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns, formatters=_formatters) + data = utils.get_item_properties(obj, columns, formatters={}) return (display_columns, data) def take_action(self, parsed_args): diff --git a/openstackclient/network/v2/network_flavor.py b/openstackclient/network/v2/network_flavor.py index 355d04c1af..c9d368bfc1 100644 --- a/openstackclient/network/v2/network_flavor.py +++ b/openstackclient/network/v2/network_flavor.py @@ -15,7 +15,6 @@ import logging -from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -28,11 +27,6 @@ LOG = logging.getLogger(__name__) -_formatters = { - 'location': format_columns.DictColumn, -} - - def _get_columns(item): column_map = { 'is_enabled': 'enabled', @@ -142,7 +136,7 @@ def take_action(self, parsed_args): attrs = _get_attrs(self.app.client_manager, parsed_args) obj = client.create_flavor(**attrs) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns, formatters=_formatters) + data = utils.get_item_properties(obj, columns, formatters={}) return (display_columns, data) @@ -306,5 +300,5 @@ def take_action(self, parsed_args): client = self.app.client_manager.network obj = client.find_flavor(parsed_args.flavor, ignore_missing=False) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns, formatters=_formatters) + data = utils.get_item_properties(obj, columns) return display_columns, data diff --git a/openstackclient/network/v2/network_flavor_profile.py b/openstackclient/network/v2/network_flavor_profile.py index 492fd432a9..6cf0c4124d 100644 --- a/openstackclient/network/v2/network_flavor_profile.py +++ b/openstackclient/network/v2/network_flavor_profile.py @@ -13,7 +13,6 @@ import logging -from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -26,11 +25,6 @@ LOG = logging.getLogger(__name__) -_formatters = { - 'location': format_columns.DictColumn, -} - - def _get_columns(item): column_map = { 'is_enabled': 'enabled', @@ -116,7 +110,7 @@ def take_action(self, parsed_args): obj = client.create_service_profile(**attrs) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns, formatters=_formatters) + data = utils.get_item_properties(obj, columns, formatters={}) return (display_columns, data) @@ -252,5 +246,5 @@ def take_action(self, parsed_args): obj = client.find_service_profile(parsed_args.flavor_profile, ignore_missing=False) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns, formatters=_formatters) + data = utils.get_item_properties(obj, columns) return (display_columns, data) diff --git a/openstackclient/network/v2/network_meter.py b/openstackclient/network/v2/network_meter.py index cde7a30403..df0e1da119 100644 --- a/openstackclient/network/v2/network_meter.py +++ b/openstackclient/network/v2/network_meter.py @@ -15,7 +15,6 @@ import logging -from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -27,11 +26,6 @@ LOG = logging.getLogger(__name__) -_formatters = { - 'location': format_columns.DictColumn, -} - - def _get_columns(item): column_map = { 'is_shared': 'shared', @@ -108,7 +102,7 @@ def take_action(self, parsed_args): attrs = _get_attrs(self.app.client_manager, parsed_args) obj = client.create_metering_label(**attrs) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns, formatters=_formatters) + data = utils.get_item_properties(obj, columns, formatters={}) return (display_columns, data) @@ -192,5 +186,5 @@ def take_action(self, parsed_args): obj = client.find_metering_label(parsed_args.meter, ignore_missing=False) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns, formatters=_formatters) + data = utils.get_item_properties(obj, columns) return display_columns, data diff --git a/openstackclient/network/v2/network_meter_rule.py b/openstackclient/network/v2/network_meter_rule.py index 5f31255a69..49ff9e1b0e 100644 --- a/openstackclient/network/v2/network_meter_rule.py +++ b/openstackclient/network/v2/network_meter_rule.py @@ -15,7 +15,6 @@ import logging -from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -27,11 +26,6 @@ LOG = logging.getLogger(__name__) -_formatters = { - 'location': format_columns.DictColumn, -} - - def _get_columns(item): column_map = { 'tenant_id': 'project_id', @@ -122,7 +116,7 @@ def take_action(self, parsed_args): attrs = _get_attrs(self.app.client_manager, parsed_args) obj = client.create_metering_label_rule(**attrs) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns, formatters=_formatters) + data = utils.get_item_properties(obj, columns, formatters={}) return (display_columns, data) @@ -205,5 +199,5 @@ def take_action(self, parsed_args): obj = client.find_metering_label_rule(parsed_args.meter_rule_id, ignore_missing=False) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns, formatters=_formatters) + data = utils.get_item_properties(obj, columns) return display_columns, data diff --git a/openstackclient/network/v2/network_qos_policy.py b/openstackclient/network/v2/network_qos_policy.py index 1622de4a7a..fd5ff93771 100644 --- a/openstackclient/network/v2/network_qos_policy.py +++ b/openstackclient/network/v2/network_qos_policy.py @@ -15,7 +15,6 @@ import logging -from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -28,11 +27,6 @@ LOG = logging.getLogger(__name__) -_formatters = { - 'location': format_columns.DictColumn, -} - - def _get_columns(item): column_map = { 'is_shared': 'shared', @@ -125,7 +119,7 @@ def take_action(self, parsed_args): attrs = _get_attrs(self.app.client_manager, parsed_args) obj = client.create_qos_policy(**attrs) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns, formatters=_formatters) + data = utils.get_item_properties(obj, columns, formatters={}) return (display_columns, data) @@ -285,5 +279,5 @@ def take_action(self, parsed_args): obj = client.find_qos_policy(parsed_args.policy, ignore_missing=False) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns, formatters=_formatters) + data = utils.get_item_properties(obj, columns) return (display_columns, data) diff --git a/openstackclient/network/v2/network_qos_rule.py b/openstackclient/network/v2/network_qos_rule.py index d74beda70f..28c5600aa6 100644 --- a/openstackclient/network/v2/network_qos_rule.py +++ b/openstackclient/network/v2/network_qos_rule.py @@ -15,7 +15,6 @@ import itertools -from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -47,11 +46,6 @@ ACTION_SHOW = 'get' -_formatters = { - 'location': format_columns.DictColumn, -} - - def _get_columns(item): column_map = { 'tenant_id': 'project_id', @@ -214,7 +208,7 @@ def take_action(self, parsed_args): msg = (_('Failed to create Network QoS rule: %(e)s') % {'e': e}) raise exceptions.CommandError(msg) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns, formatters=_formatters) + data = utils.get_item_properties(obj, columns) return display_columns, data @@ -364,5 +358,5 @@ def take_action(self, parsed_args): {'rule': rule_id, 'e': e}) raise exceptions.CommandError(msg) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns, formatters=_formatters) + data = utils.get_item_properties(obj, columns) return display_columns, data diff --git a/openstackclient/network/v2/network_qos_rule_type.py b/openstackclient/network/v2/network_qos_rule_type.py index e842944cce..7b92c8ad4b 100644 --- a/openstackclient/network/v2/network_qos_rule_type.py +++ b/openstackclient/network/v2/network_qos_rule_type.py @@ -13,7 +13,6 @@ # License for the specific language governing permissions and limitations # under the License. -from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import utils @@ -21,11 +20,6 @@ from openstackclient.network import sdk_utils -_formatters = { - 'location': format_columns.DictColumn, -} - - def _get_columns(item): column_map = { "type": "rule_type_name", @@ -71,5 +65,5 @@ def take_action(self, parsed_args): client = self.app.client_manager.network obj = client.get_qos_rule_type(parsed_args.rule_type) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns, formatters=_formatters) + data = utils.get_item_properties(obj, columns) return display_columns, data diff --git a/openstackclient/network/v2/network_rbac.py b/openstackclient/network/v2/network_rbac.py index 2c34d7ef9a..b88ef019e0 100644 --- a/openstackclient/network/v2/network_rbac.py +++ b/openstackclient/network/v2/network_rbac.py @@ -15,7 +15,6 @@ import logging -from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -28,11 +27,6 @@ LOG = logging.getLogger(__name__) -_formatters = { - 'location': format_columns.DictColumn, -} - - def _get_columns(item): column_map = { 'target_tenant': 'target_project_id', @@ -153,7 +147,7 @@ def take_action(self, parsed_args): attrs = _get_attrs(self.app.client_manager, parsed_args) obj = client.create_rbac_policy(**attrs) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns, formatters=_formatters) + data = utils.get_item_properties(obj, columns) return display_columns, data @@ -311,5 +305,5 @@ def take_action(self, parsed_args): obj = client.find_rbac_policy(parsed_args.rbac_policy, ignore_missing=False) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns, formatters=_formatters) + data = utils.get_item_properties(obj, columns) return display_columns, data diff --git a/openstackclient/network/v2/network_segment.py b/openstackclient/network/v2/network_segment.py index 5899dc697b..c1a672e2d5 100644 --- a/openstackclient/network/v2/network_segment.py +++ b/openstackclient/network/v2/network_segment.py @@ -15,7 +15,6 @@ import logging -from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -27,11 +26,6 @@ LOG = logging.getLogger(__name__) -_formatters = { - 'location': format_columns.DictColumn, -} - - def _get_columns(item): return sdk_utils.get_osc_show_columns_for_sdk_resource(item, {}) @@ -96,7 +90,7 @@ def take_action(self, parsed_args): attrs['segmentation_id'] = parsed_args.segment obj = client.create_segment(**attrs) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns, formatters=_formatters) + data = utils.get_item_properties(obj, columns) return (display_columns, data) @@ -248,5 +242,5 @@ def take_action(self, parsed_args): ignore_missing=False ) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns, formatters=_formatters) + data = utils.get_item_properties(obj, columns) return (display_columns, data) diff --git a/openstackclient/network/v2/network_segment_range.py b/openstackclient/network/v2/network_segment_range.py index b38c72c248..6229995aab 100644 --- a/openstackclient/network/v2/network_segment_range.py +++ b/openstackclient/network/v2/network_segment_range.py @@ -19,7 +19,6 @@ import itertools import logging -from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -31,10 +30,6 @@ LOG = logging.getLogger(__name__) -_formatters = { - 'location': format_columns.DictColumn, -} - def _get_columns(item): return sdk_utils.get_osc_show_columns_for_sdk_resource(item, {}) @@ -216,7 +211,7 @@ def take_action(self, parsed_args): attrs['physical_network'] = parsed_args.physical_network obj = network_client.create_network_segment_range(**attrs) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns, formatters=_formatters) + data = utils.get_item_properties(obj, columns) data = _update_additional_fields_from_props(columns, props=data) return (display_columns, data) @@ -455,6 +450,6 @@ def take_action(self, parsed_args): ignore_missing=False ) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns, formatters=_formatters) + data = utils.get_item_properties(obj, columns) data = _update_additional_fields_from_props(columns, props=data) return (display_columns, data) diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index a22bcafb56..68154c3d77 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -51,7 +51,6 @@ def human_readable(self): 'dns_assignment': format_columns.ListDictColumn, 'extra_dhcp_opts': format_columns.ListDictColumn, 'fixed_ips': format_columns.ListDictColumn, - 'location': format_columns.DictColumn, 'security_group_ids': format_columns.ListColumn, 'tags': format_columns.ListColumn, } diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 81b81f98b5..e3e8accd21 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -61,7 +61,6 @@ def human_readable(self): 'external_gateway_info': RouterInfoColumn, 'availability_zones': format_columns.ListColumn, 'availability_zone_hints': format_columns.ListColumn, - 'location': format_columns.DictColumn, 'routes': RoutesColumn, 'tags': format_columns.ListColumn, } diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py index aec9c56efe..0732c23edc 100644 --- a/openstackclient/network/v2/security_group.py +++ b/openstackclient/network/v2/security_group.py @@ -16,7 +16,6 @@ import argparse from cliff import columns as cliff_columns -from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import utils from osc_lib.utils import tags as _tag @@ -77,13 +76,11 @@ def human_readable(self): _formatters_network = { - 'location': format_columns.DictColumn, 'security_group_rules': NetworkSecurityGroupRulesColumn, } _formatters_compute = { - 'location': format_columns.DictColumn, 'rules': ComputeSecurityGroupRulesColumn, } diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index f48478ea74..1fbd97abc3 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -16,7 +16,6 @@ import argparse import logging -from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib import exceptions from osc_lib import utils @@ -31,11 +30,6 @@ LOG = logging.getLogger(__name__) -_formatters = { - 'location': format_columns.DictColumn, -} - - def _format_security_group_rule_show(obj): data = network_utils.transform_compute_security_group_rule(obj) return zip(*sorted(data.items())) @@ -353,7 +347,7 @@ def take_action_network(self, client, parsed_args): # Create and show the security group rule. obj = client.create_security_group_rule(**attrs) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns, formatters=_formatters) + data = utils.get_item_properties(obj, columns) return (display_columns, data) def take_action_compute(self, client, parsed_args): @@ -620,7 +614,7 @@ def take_action_network(self, client, parsed_args): if not obj['remote_ip_prefix']: obj['remote_ip_prefix'] = _format_remote_ip_prefix(obj) display_columns, columns = _get_columns(obj) - data = utils.get_item_properties(obj, columns, formatters=_formatters) + data = utils.get_item_properties(obj, columns) return (display_columns, data) def take_action_compute(self, client, parsed_args): diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index f684406558..f87f7abe1b 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -61,7 +61,6 @@ def human_readable(self): 'allocation_pools': AllocationPoolsColumn, 'dns_nameservers': format_columns.ListColumn, 'host_routes': HostRoutesColumn, - 'location': format_columns.DictColumn, 'service_types': format_columns.ListColumn, 'tags': format_columns.ListColumn, } diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index 2750574a97..56cf61526c 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -42,7 +42,6 @@ def _get_columns(item): _formatters = { - 'location': format_columns.DictColumn, 'prefixes': format_columns.ListColumn, 'tags': format_columns.ListColumn, } From 8b7a2c8d5931f15b69d1ef6baad994f4b9904579 Mon Sep 17 00:00:00 2001 From: Jens Harbott Date: Fri, 30 Aug 2019 13:15:22 +0000 Subject: [PATCH 2213/3095] Don't display Munch objects in the output When the sdk gives us a resource that contains Munch columns, drop them from the output as they are for programmatic usage only and have no use in a CLI context. Change-Id: Idd7306cd763b5a017a66e410e70e1adb02663c2a --- openstackclient/network/sdk_utils.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openstackclient/network/sdk_utils.py b/openstackclient/network/sdk_utils.py index af9c74f944..cff3071354 100644 --- a/openstackclient/network/sdk_utils.py +++ b/openstackclient/network/sdk_utils.py @@ -10,6 +10,8 @@ # License for the specific language governing permissions and limitations # under the License. +import munch + def get_osc_show_columns_for_sdk_resource( sdk_resource, @@ -38,6 +40,9 @@ def get_osc_show_columns_for_sdk_resource( # Build the OSC column names to display for the SDK resource. attr_map = {} display_columns = list(resource_dict.keys()) + for col_name in display_columns: + if isinstance(resource_dict[col_name], munch.Munch): + display_columns.remove(col_name) invisible_columns = [] if invisible_columns is None else invisible_columns for col_name in invisible_columns: if col_name in display_columns: From 307d23bb58f0f44c9b9facdeaaa10227748ef50d Mon Sep 17 00:00:00 2001 From: Mohammed Naser Date: Tue, 16 Jun 2020 15:31:26 -0400 Subject: [PATCH 2214/3095] port: add --host to list command This adds an option to allow filtering ports bound to a specific host when listing them. Change-Id: I747ed0f8b070074c51789576a158345f670fe733 --- openstackclient/network/v2/port.py | 6 ++++++ .../tests/unit/network/v2/test_port.py | 16 ++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index a22bcafb56..4011f5f0a2 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -515,6 +515,10 @@ def get_parser(self, prog_name): "This is the entity that uses the port (for example, " "network:dhcp).") ) + parser.add_argument( + '--host', + metavar='', + help=_("List only ports bound to this host ID")) parser.add_argument( '--network', metavar='', @@ -603,6 +607,8 @@ def take_action(self, parsed_args): server = utils.find_resource(compute_client.servers, parsed_args.server) filters['device_id'] = server.id + if parsed_args.host: + filters['binding:host_id'] = parsed_args.host if parsed_args.network: network = network_client.find_network(parsed_args.network, ignore_missing=False) diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index b1a18da6c1..87aea61fac 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -1054,6 +1054,22 @@ def test_list_port_with_long(self): self.assertEqual(self.columns_long, columns) self.assertListItemEqual(self.data_long, list(data)) + def test_port_list_host(self): + arglist = [ + '--host', 'foobar', + ] + verifylist = [ + ('host', 'foobar'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'binding:host_id': 'foobar'} + + self.network.ports.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertListItemEqual(self.data, list(data)) + def test_port_list_project(self): project = identity_fakes.FakeProject.create_one_project() self.projects_mock.get.return_value = project From ff2a70c418c9686b13f64fed760432f30391441a Mon Sep 17 00:00:00 2001 From: melissaml Date: Tue, 23 Jun 2020 12:22:58 +0800 Subject: [PATCH 2215/3095] Remove translation sections from setup.cfg These translation sections are not needed anymore, Babel can generate translation files without them. Change-Id: Ic5d57186766257e9d37b3588e71f973cddad9be4 --- babel.cfg | 1 - lower-constraints.txt | 1 - setup.cfg | 14 -------------- 3 files changed, 16 deletions(-) delete mode 100644 babel.cfg diff --git a/babel.cfg b/babel.cfg deleted file mode 100644 index efceab818b..0000000000 --- a/babel.cfg +++ /dev/null @@ -1 +0,0 @@ -[python: **.py] diff --git a/lower-constraints.txt b/lower-constraints.txt index f16eca4184..840d32a8f6 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -2,7 +2,6 @@ amqp==2.1.1 aodhclient==0.9.0 appdirs==1.3.0 asn1crypto==0.23.0 -Babel==2.3.4 bandit==1.1.0 cachetools==2.0.0 cffi==1.7.0 diff --git a/setup.cfg b/setup.cfg index 273529b1ec..3502396f72 100644 --- a/setup.cfg +++ b/setup.cfg @@ -718,17 +718,3 @@ openstack.volume.v3 = volume_transfer_request_delete = openstackclient.volume.v2.volume_transfer_request:DeleteTransferRequest volume_transfer_request_list = openstackclient.volume.v2.volume_transfer_request:ListTransferRequest volume_transfer_request_show = openstackclient.volume.v2.volume_transfer_request:ShowTransferRequest - -[extract_messages] -keywords = _ gettext ngettext l_ lazy_gettext -mapping_file = babel.cfg -output_file = openstackclient/locale/openstackclient.pot - -[update_catalog] -domain = openstackclient -output_dir = openstackclient/locale -input_file = openstackclient/locale/openstackclient.pot - -[compile_catalog] -directory = openstackclient/locale -domain = openstackclient From c04ec16cf7ad2dbe7bf8edf5f6e7840c54b0efa3 Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Tue, 23 Jun 2020 15:45:47 -0500 Subject: [PATCH 2216/3095] Expose flag for forcing use of import for images openstacksdk added support for using image import as a fallback which is transparently supported here, but also provides an override flag to allow a user to force use of import. Expose that here. Depends-On: https://review.opendev.org/737608 Change-Id: Ied938a8f63f305305a20ace42e9f4c84b0a5c00e --- lower-constraints.txt | 2 +- openstackclient/image/v2/image.py | 11 ++++++++++ .../tests/unit/image/v2/test_image.py | 22 +++++++++++++++++++ ...dd-image-import-flag-899869dc5a92aea7.yaml | 5 +++++ requirements.txt | 2 +- 5 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/add-image-import-flag-899869dc5a92aea7.yaml diff --git a/lower-constraints.txt b/lower-constraints.txt index f16eca4184..c3ac29106e 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -50,7 +50,7 @@ msgpack-python==0.4.0 munch==2.1.0 netaddr==0.7.18 netifaces==0.10.4 -openstacksdk==0.44.0 +openstacksdk==0.48.0 os-service-types==1.7.0 os-testr==1.0.0 osc-lib==2.0.0 diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 53ce560dcd..b068ddafd1 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -324,6 +324,14 @@ def get_parser(self, prog_name): metavar="", help=_("Set an alternate project on this image (name or ID)"), ) + parser.add_argument( + "--import", + dest="use_import", + action="store_true", + help=_( + "Force the use of glance image import instead of" + " direct upload") + ) common.add_project_domain_option_to_parser(parser) for deadopt in self.deadopts: parser.add_argument( @@ -388,6 +396,9 @@ def take_action(self, parsed_args): parsed_args.project_domain, ).id + if parsed_args.use_import: + kwargs['use_import'] = True + # open the file first to ensure any failures are handled before the # image is created. Get the file name (if it is file, and not stdin) # for easier further handling. diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py index a021cfc7fc..310f6b7636 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -271,6 +271,28 @@ def test_image_create_dead_options(self): exceptions.CommandError, self.cmd.take_action, parsed_args) + @mock.patch('sys.stdin', side_effect=[None]) + def test_image_create_import(self, raw_input): + + arglist = [ + '--import', + self.new_image.name, + ] + verifylist = [ + ('name', self.new_image.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + # ImageManager.create(name=, **) + self.client.create_image.assert_called_with( + name=self.new_image.name, + container_format=image.DEFAULT_CONTAINER_FORMAT, + disk_format=image.DEFAULT_DISK_FORMAT, + use_import=True + ) + class TestAddProjectToImage(TestImage): diff --git a/releasenotes/notes/add-image-import-flag-899869dc5a92aea7.yaml b/releasenotes/notes/add-image-import-flag-899869dc5a92aea7.yaml new file mode 100644 index 0000000000..fbd29bcd8f --- /dev/null +++ b/releasenotes/notes/add-image-import-flag-899869dc5a92aea7.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Added ``--import`` flag to ``openstack image create`` to allow + the user to force use of the image import codepath. diff --git a/requirements.txt b/requirements.txt index 3aac36af2f..89e9080602 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ pbr!=2.1.0,>=2.0.0 # Apache-2.0 six>=1.10.0 # MIT cliff!=2.9.0,>=2.8.0 # Apache-2.0 -openstacksdk>=0.44.0 # Apache-2.0 +openstacksdk>=0.48.0 # Apache-2.0 osc-lib>=2.0.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 From 4638dbc7f373177277dbf8aabe6d84f15334492e Mon Sep 17 00:00:00 2001 From: Ghanshyam Mann Date: Fri, 3 Jul 2020 10:02:34 -0500 Subject: [PATCH 2217/3095] Remove enabling of glance v1 API devstack removing the glance v1 api enable option[1] because there is no v1 entry point in glance now[2]. Let's remove ths GLANCE_V1_ENABLED variable setting from zuul job too to avoid any confusion of glance v1 is still available. [1] https://review.opendev.org/#/c/698808/ [2] https://review.opendev.org/#/c/532503/ Change-Id: I6d3a38eee0c75bbc795bad732fe547181d15c677 --- .zuul.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.zuul.yaml b/.zuul.yaml index 734b1ededc..e566ceb845 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -51,8 +51,6 @@ vars: devstack_localrc: LIBS_FROM_GIT: python-openstackclient - # NOTE(dtroyer): OSC needs to support Image v1 for a while yet so re-enable - GLANCE_V1_ENABLED: true # NOTE(dtroyer): Functional tests need a bit more volume headroom VOLUME_BACKING_FILE_SIZE: 20G devstack_local_conf: From b1fc587a6d4eac795a5bc27c2dffd2d876161caa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Piliszek?= Date: Sun, 5 Jul 2020 09:53:14 +0200 Subject: [PATCH 2218/3095] Make volume backup record commands available in v3 They work just fine in Volume API v3 but they were limited in OSC to v2. Change-Id: I510383f8e0cbf05ec24caa1cad330f12f82a913d Story: 2007896 Task: 40279 --- releasenotes/notes/task-40279-eb0d718ac1959c50.yaml | 5 +++++ setup.cfg | 3 +++ 2 files changed, 8 insertions(+) create mode 100644 releasenotes/notes/task-40279-eb0d718ac1959c50.yaml diff --git a/releasenotes/notes/task-40279-eb0d718ac1959c50.yaml b/releasenotes/notes/task-40279-eb0d718ac1959c50.yaml new file mode 100644 index 0000000000..aedb3ea929 --- /dev/null +++ b/releasenotes/notes/task-40279-eb0d718ac1959c50.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Makes ``volume backup record`` commands available in Volume API v3. + `Task 40279 `__ diff --git a/setup.cfg b/setup.cfg index 273529b1ec..473950916f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -685,6 +685,9 @@ openstack.volume.v3 = volume_backup_set = openstackclient.volume.v2.volume_backup:SetVolumeBackup volume_backup_show = openstackclient.volume.v2.volume_backup:ShowVolumeBackup + volume_backup_record_export = openstackclient.volume.v2.backup_record:ExportBackupRecord + volume_backup_record_import = openstackclient.volume.v2.backup_record:ImportBackupRecord + volume_host_set = openstackclient.volume.v2.volume_host:SetVolumeHost volume_snapshot_create = openstackclient.volume.v2.volume_snapshot:CreateVolumeSnapshot From 870cf0114848f145f15a78415e3f4203c6338cd1 Mon Sep 17 00:00:00 2001 From: Doug Hellmann Date: Sat, 4 Jul 2020 11:34:05 -0400 Subject: [PATCH 2219/3095] switch to stevedore for entry points Importing pkg_resources scans every installed distribution to find all of the entry points. Stevedore is adding a new caching layer using importlib.metadata, which will not. Switching to the stevedore should eventually speed up load times, especially for command line apps. This change makes the switch now to ensure API compatibility. We were already using stevedore for tests, so this moves the dependency from test-requirements.txt to requirements.txt and raises the minimum version to something more recent. Change-Id: I3e3632783bc745979b6db73e610df8a77ffaceb0 Signed-off-by: Doug Hellmann --- lower-constraints.txt | 2 +- openstackclient/common/clientmanager.py | 23 ++++++++++++++++------- requirements.txt | 1 + test-requirements.txt | 1 - 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/lower-constraints.txt b/lower-constraints.txt index f16eca4184..c320edbe6e 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -124,7 +124,7 @@ six==1.10.0 smmap==0.9.0 statsd==3.2.1 stestr==1.0.0 -stevedore==1.20.0 +stevedore==2.0.1 sushy==0.1.0 tempest==17.1.0 tenacity==3.2.1 diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index c1118ad3ef..66dc880e07 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -15,12 +15,13 @@ """Manage access to the clients, including authenticating when needed.""" +import importlib import logging import sys from osc_lib import clientmanager from osc_lib import shell -import pkg_resources +import stevedore LOG = logging.getLogger(__name__) @@ -143,17 +144,25 @@ def is_volume_endpoint_enabled(self, volume_client): def get_plugin_modules(group): """Find plugin entry points""" mod_list = [] - for ep in pkg_resources.iter_entry_points(group): + mgr = stevedore.ExtensionManager(group) + for ep in mgr: LOG.debug('Found plugin %s', ep.name) + # Different versions of stevedore use different + # implementations of EntryPoint from other libraries, which + # are not API-compatible. try: - __import__(ep.module_name) - except Exception: + module_name = ep.entry_point.module_name + except AttributeError: + module_name = ep.entry_point.module + + try: + module = importlib.import_module(module_name) + except Exception as err: sys.stderr.write( - "WARNING: Failed to import plugin %s.\n" % ep.name) + "WARNING: Failed to import plugin %s: %s.\n" % (ep.name, err)) continue - module = sys.modules[ep.module_name] mod_list.append(module) init_func = getattr(module, 'Initialize', None) if init_func: @@ -164,7 +173,7 @@ def get_plugin_modules(group): clientmanager.ClientManager, module.API_NAME, clientmanager.ClientCache( - getattr(sys.modules[ep.module_name], 'make_client', None) + getattr(sys.modules[module_name], 'make_client', None) ), ) return mod_list diff --git a/requirements.txt b/requirements.txt index 3aac36af2f..b1421a831f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,3 +12,4 @@ oslo.utils>=3.33.0 # Apache-2.0 python-keystoneclient>=3.22.0 # Apache-2.0 python-novaclient>=15.1.0 # Apache-2.0 python-cinderclient>=3.3.0 # Apache-2.0 +stevedore>=2.0.1 # Apache-2.0 diff --git a/test-requirements.txt b/test-requirements.txt index f2b6a13402..3dce687bc7 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -8,7 +8,6 @@ flake8-import-order>=0.13 # LGPLv3 oslotest>=3.2.0 # Apache-2.0 requests>=2.14.2 # Apache-2.0 requests-mock>=1.2.0 # Apache-2.0 -stevedore>=1.20.0 # Apache-2.0 stestr>=1.0.0 # Apache-2.0 testtools>=2.2.0 # MIT tempest>=17.1.0 # Apache-2.0 From 5c4eb0bf9d7bc154e583f1d864984d56192ccb4b Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Mon, 6 Jul 2020 14:50:59 -0500 Subject: [PATCH 2220/3095] Add a command to trigger entrypoint cache creation stevedore will cache the entrypoint scan when needed. Since we just installed the things here, do an openstack --help to cause the entrypoints to get scanned at build time and for the cache file to be written into the container image. Change-Id: I73502be6d68c4a38561c9524b4def3c6a6f61ac6 --- Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Dockerfile b/Dockerfile index ca60bb0ecb..bf5de3c755 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,4 +23,7 @@ FROM docker.io/opendevorg/python-base:3.7 COPY --from=builder /output/ /output RUN /output/install-from-bindep +# Trigger entrypoint loading to trigger stevedore entrypoint caching +RUN openstack --help >/dev/null 2>&1 + CMD ["/usr/local/bin/openstack"] From c06d82563526c715d3ed508fa3cc5f9dc0963294 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Gr=C3=A4b?= Date: Tue, 7 Jul 2020 11:04:33 +0200 Subject: [PATCH 2221/3095] Fix uploading an signed image does not work if private signing key is encrypted Change-Id: Ia7c84aa7b840bf92aeb7db7246d14119eb727b03 Story: 2007890 Task: 40269 --- openstackclient/image/v2/image.py | 6 ++++++ releasenotes/notes/fix-story-2007890-0974f3e69f26801e.yaml | 7 +++++++ 2 files changed, 13 insertions(+) create mode 100644 releasenotes/notes/fix-story-2007890-0974f3e69f26801e.yaml diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 53ce560dcd..50a64d4cd4 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -429,8 +429,14 @@ def take_action(self, parsed_args): prompt=("Please enter private key password, leave " "empty if none: "), confirm=False) + if not pw or len(pw) < 1: pw = None + else: + # load_private_key() requires the password to be + # passed as bytes + pw = pw.encode() + signer.load_private_key( sign_key_path, password=pw) diff --git a/releasenotes/notes/fix-story-2007890-0974f3e69f26801e.yaml b/releasenotes/notes/fix-story-2007890-0974f3e69f26801e.yaml new file mode 100644 index 0000000000..87d6f18bae --- /dev/null +++ b/releasenotes/notes/fix-story-2007890-0974f3e69f26801e.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + While uploading a signed image, a private key to sign that image must be + specified. The CLI client asks for the password of that private key. Due + to wrong encoding handling while using Python 3, the password is not + accepted, whether it is correct or not. From a8aad9fec80bcb6c9917d2dd076373f06467849f Mon Sep 17 00:00:00 2001 From: Lance Bragstad Date: Thu, 9 Jul 2020 16:50:01 -0500 Subject: [PATCH 2222/3095] Add system role assignment tests for users and groups I was writing some additional functionality and noticed these tests were missing. This commit adds tests for adding and removing system role assignments for users and groups. Change-Id: I30fdc6ec55e1eb1cfa55f4cbf92c3f001d89865f --- .../tests/unit/identity/v3/test_role.py | 137 ++++++++++++++++++ 1 file changed, 137 insertions(+) diff --git a/openstackclient/tests/unit/identity/v3/test_role.py b/openstackclient/tests/unit/identity/v3/test_role.py index 544da7c15d..c1e91f9a0f 100644 --- a/openstackclient/tests/unit/identity/v3/test_role.py +++ b/openstackclient/tests/unit/identity/v3/test_role.py @@ -102,6 +102,40 @@ def setUp(self): # Get the command object to test self.cmd = role.AddRole(self.app, None) + def test_role_add_user_system(self): + arglist = [ + '--user', identity_fakes.user_name, + '--system', 'all', + identity_fakes.role_name, + ] + if self._is_inheritance_testcase(): + arglist.append('--inherited') + verifylist = [ + ('user', identity_fakes.user_name), + ('group', None), + ('system', 'all'), + ('domain', None), + ('project', None), + ('role', identity_fakes.role_name), + ('inherited', self._is_inheritance_testcase()), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'user': identity_fakes.user_id, + 'system': 'all', + 'os_inherit_extension_inherited': self._is_inheritance_testcase(), + } + # RoleManager.grant(role, user=, group=, domain=, project=) + self.roles_mock.grant.assert_called_with( + identity_fakes.role_id, + **kwargs + ) + self.assertIsNone(result) + def test_role_add_user_domain(self): arglist = [ '--user', identity_fakes.user_name, @@ -168,6 +202,40 @@ def test_role_add_user_project(self): ) self.assertIsNone(result) + def test_role_add_group_system(self): + arglist = [ + '--group', identity_fakes.group_name, + '--system', 'all', + identity_fakes.role_name, + ] + if self._is_inheritance_testcase(): + arglist.append('--inherited') + verifylist = [ + ('user', None), + ('group', identity_fakes.group_name), + ('system', 'all'), + ('domain', None), + ('project', None), + ('role', identity_fakes.role_name), + ('inherited', self._is_inheritance_testcase()), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'group': identity_fakes.group_id, + 'system': 'all', + 'os_inherit_extension_inherited': self._is_inheritance_testcase(), + } + # RoleManager.grant(role, user=, group=, domain=, project=) + self.roles_mock.grant.assert_called_with( + identity_fakes.role_id, + **kwargs + ) + self.assertIsNone(result) + def test_role_add_group_domain(self): arglist = [ '--group', identity_fakes.group_name, @@ -744,6 +812,40 @@ def setUp(self): # Get the command object to test self.cmd = role.RemoveRole(self.app, None) + def test_role_remove_user_system(self): + arglist = [ + '--user', identity_fakes.user_name, + '--system', 'all', + identity_fakes.role_name + ] + if self._is_inheritance_testcase(): + arglist.append('--inherited') + verifylist = [ + ('user', identity_fakes.user_name), + ('group', None), + ('system', 'all'), + ('domain', None), + ('project', None), + ('role', identity_fakes.role_name), + ('inherited', self._is_inheritance_testcase()), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'user': identity_fakes.user_id, + 'system': 'all', + 'os_inherit_extension_inherited': self._is_inheritance_testcase(), + } + # RoleManager.revoke(role, user=, group=, domain=, project=) + self.roles_mock.revoke.assert_called_with( + identity_fakes.role_id, + **kwargs + ) + self.assertIsNone(result) + def test_role_remove_user_domain(self): arglist = [ '--user', identity_fakes.user_name, @@ -810,6 +912,41 @@ def test_role_remove_user_project(self): ) self.assertIsNone(result) + def test_role_remove_group_system(self): + arglist = [ + '--group', identity_fakes.group_name, + '--system', 'all', + identity_fakes.role_name, + ] + if self._is_inheritance_testcase(): + arglist.append('--inherited') + verifylist = [ + ('user', None), + ('group', identity_fakes.group_name), + ('system', 'all'), + ('domain', None), + ('project', None), + ('role', identity_fakes.role_name), + ('role', identity_fakes.role_name), + ('inherited', self._is_inheritance_testcase()), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'group': identity_fakes.group_id, + 'system': 'all', + 'os_inherit_extension_inherited': self._is_inheritance_testcase(), + } + # RoleManager.revoke(role, user=, group=, domain=, project=) + self.roles_mock.revoke.assert_called_with( + identity_fakes.role_id, + **kwargs + ) + self.assertIsNone(result) + def test_role_remove_group_domain(self): arglist = [ '--group', identity_fakes.group_name, From 82ebddca006d1dc61855fdd34b0616222039ea58 Mon Sep 17 00:00:00 2001 From: Felix Yan Date: Tue, 14 Jul 2020 01:02:00 +0800 Subject: [PATCH 2223/3095] Fix compatibility issue in 5.3 The offending entry point object looks like: EntryPoint(name='compute', value='openstackclient.compute.client', group='openstack.cli.base') Story: 2007917 Task: 40323 Change-Id: I0f3cc62e23efdc14203ce6645581d5ba5dbf7fa0 --- openstackclient/common/clientmanager.py | 5 ++++- releasenotes/notes/entrypoint-3.8-0597d159889042f7.yaml | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/entrypoint-3.8-0597d159889042f7.yaml diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index 66dc880e07..36c3ce2688 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -154,7 +154,10 @@ def get_plugin_modules(group): try: module_name = ep.entry_point.module_name except AttributeError: - module_name = ep.entry_point.module + try: + module_name = ep.entry_point.module + except AttributeError: + module_name = ep.entry_point.value try: module = importlib.import_module(module_name) diff --git a/releasenotes/notes/entrypoint-3.8-0597d159889042f7.yaml b/releasenotes/notes/entrypoint-3.8-0597d159889042f7.yaml new file mode 100644 index 0000000000..cb25cff14f --- /dev/null +++ b/releasenotes/notes/entrypoint-3.8-0597d159889042f7.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Fixes an issue with python 3.8 and entrypoint loading where the + new builtin importlib entrypoint support had a different + attribute api than expected. From 8628e52de7412e57e13238ad1ba7113deb6a2e1b Mon Sep 17 00:00:00 2001 From: Vishakha Agarwal Date: Tue, 21 Jul 2020 18:24:58 +0530 Subject: [PATCH 2224/3095] Add name and enabled param in ListDomain parser when doing openstack domain list --name xyz_id, and openstack domain list --enabled CLI raising error unrecognized arguments, whereas in api-ref document [1], user can pass name and enabled as optional query param. This addresses the above issue, by adding param --name and --enabled in parser of ListDomain. [1]https://docs.openstack.org/api-ref/identity/v3/?expanded=list-domains-detail#list-domains Change-Id: I3cdb511d3c7059ddfb802ca025188d8976c9302c --- openstackclient/identity/v3/domain.py | 23 +++++++- .../tests/unit/identity/v3/test_domain.py | 55 +++++++++++++++++++ ...abled_to_list_domain-6d23f02994b51c67.yaml | 3 + 3 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/add_name_and_enabled_to_list_domain-6d23f02994b51c67.yaml diff --git a/openstackclient/identity/v3/domain.py b/openstackclient/identity/v3/domain.py index e33fce05c5..e0bd10202a 100644 --- a/openstackclient/identity/v3/domain.py +++ b/openstackclient/identity/v3/domain.py @@ -126,9 +126,30 @@ def take_action(self, parsed_args): class ListDomain(command.Lister): _description = _("List domains") + def get_parser(self, prog_name): + parser = super(ListDomain, self).get_parser(prog_name) + parser.add_argument( + '--name', + metavar='', + help=_('The domain name'), + ) + parser.add_argument( + '--enabled', + dest='enabled', + action='store_true', + help=_('The domains that are enabled will be returned'), + ) + return parser + def take_action(self, parsed_args): + kwargs = {} + if parsed_args.name: + kwargs['name'] = parsed_args.name + if parsed_args.enabled: + kwargs['enabled'] = True + columns = ('ID', 'Name', 'Enabled', 'Description') - data = self.app.client_manager.identity.domains.list() + data = self.app.client_manager.identity.domains.list(**kwargs) return (columns, (utils.get_item_properties( s, columns, diff --git a/openstackclient/tests/unit/identity/v3/test_domain.py b/openstackclient/tests/unit/identity/v3/test_domain.py index 46f389e890..c39f1bd3d7 100644 --- a/openstackclient/tests/unit/identity/v3/test_domain.py +++ b/openstackclient/tests/unit/identity/v3/test_domain.py @@ -293,6 +293,61 @@ def test_domain_list_no_options(self): ), ) self.assertEqual(datalist, tuple(data)) + def test_domain_list_with_option_name(self): + arglist = ['--name', + self.domain.name] + verifylist = [ + ('name', self.domain.name) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + kwargs = { + 'name': self.domain.name + } + self.domains_mock.list.assert_called_with(**kwargs) + + collist = ('ID', 'Name', 'Enabled', 'Description') + self.assertEqual(collist, columns) + datalist = (( + self.domain.id, + self.domain.name, + True, + self.domain.description, + ), ) + self.assertEqual(datalist, tuple(data)) + + def test_domain_list_with_option_enabled(self): + arglist = ['--enabled'] + verifylist = [ + ('enabled', True) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + kwargs = { + 'enabled': True + } + self.domains_mock.list.assert_called_with(**kwargs) + + collist = ('ID', 'Name', 'Enabled', 'Description') + self.assertEqual(collist, columns) + datalist = (( + self.domain.id, + self.domain.name, + True, + self.domain.description, + ), ) + self.assertEqual(datalist, tuple(data)) + class TestDomainSet(TestDomain): diff --git a/releasenotes/notes/add_name_and_enabled_to_list_domain-6d23f02994b51c67.yaml b/releasenotes/notes/add_name_and_enabled_to_list_domain-6d23f02994b51c67.yaml new file mode 100644 index 0000000000..4eb58b7833 --- /dev/null +++ b/releasenotes/notes/add_name_and_enabled_to_list_domain-6d23f02994b51c67.yaml @@ -0,0 +1,3 @@ +--- +features: + - Add ``--name`` and ``--domain`` option to ``domain list`` command. \ No newline at end of file From 12f1e56ebf2ea3999c57246410501c09fced2c4c Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 22 Jul 2020 10:39:47 +0100 Subject: [PATCH 2225/3095] Add 'openstack server create --use-config-drive' Despite what the help text for this options says, the nova API only accepts boolean values for this value and has done so since at least the introduction of the 2.1 microversioned API. While it would be nice to convert '--config-drive' to a boolean flag, we'd need to be able to retain temporary support for people passing arguments. 'nargs=?' [1] looks promising but it has an annoying tendency to swallow a positional argument following it [2]. Since that is not an option, we have to live with a new config option, '--use-config-drive' and a '--no-config-drive' counterpart. [1] https://docs.python.org/3/library/argparse.html#nargs [2] https://bugs.python.org/issue9338 Change-Id: If9cce0ad4094cc9cef1c9136b80c3b0f35a82c7a Signed-off-by: Stephen Finucane Story: #2005468 Task: #30547 --- openstackclient/compute/v2/server.py | 45 ++++++++++++++----- .../tests/unit/compute/v2/test_server.py | 5 ++- ...ver-use-config-drive-9fc68552365cfefa.yaml | 8 ++++ 3 files changed, 44 insertions(+), 14 deletions(-) create mode 100644 releasenotes/notes/story-2005468-server-use-config-drive-9fc68552365cfefa.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 93e9f966ae..54cfe770f2 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -693,12 +693,30 @@ def get_parser(self, prog_name): default={}, help=_('Hints for the scheduler (optional extension)'), ) - parser.add_argument( + config_drive_group = parser.add_mutually_exclusive_group() + config_drive_group.add_argument( + '--use-config-drive', + action='store_true', + dest='config_drive', + help=_("Enable config drive."), + ) + config_drive_group.add_argument( + '--no-config-drive', + action='store_false', + dest='config_drive', + help=_("Disable config drive."), + ) + # TODO(stephenfin): Drop support in the next major version bump after + # Victoria + config_drive_group.add_argument( '--config-drive', metavar='|True', default=False, - help=_('Use specified volume as the config drive, ' - 'or \'True\' to use an ephemeral drive'), + help=_( + "**Deprecated** Use specified volume as the config drive, " + "or 'True' to use an ephemeral drive. Replaced by " + "'--use-config-drive'." + ), ) parser.add_argument( '--min', @@ -991,16 +1009,19 @@ def _match_image(image_api, wanted_properties): else: hints[key] = values - # What does a non-boolean value for config-drive do? - # --config-drive argument is either a volume id or - # 'True' (or '1') to use an ephemeral volume - if str(parsed_args.config_drive).lower() in ("true", "1"): - config_drive = True - elif str(parsed_args.config_drive).lower() in ("false", "0", - "", "none"): - config_drive = None + if isinstance(parsed_args.config_drive, bool): + # NOTE(stephenfin): The API doesn't accept False as a value :'( + config_drive = parsed_args.config_drive or None else: - config_drive = parsed_args.config_drive + # TODO(stephenfin): Remove when we drop support for + # '--config-drive' + if str(parsed_args.config_drive).lower() in ("true", "1"): + config_drive = True + elif str(parsed_args.config_drive).lower() in ("false", "0", + "", "none"): + config_drive = None + else: + config_drive = parsed_args.config_drive boot_kwargs = dict( meta=parsed_args.property, diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 7e4c71c50c..dc699c0f43 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -857,6 +857,7 @@ def test_server_create_with_options(self): '--key-name', 'keyname', '--property', 'Beta=b', '--security-group', 'securitygroup', + '--use-config-drive', '--hint', 'a=b', '--hint', 'a=c', self.new_server.name, @@ -868,7 +869,7 @@ def test_server_create_with_options(self): ('property', {'Beta': 'b'}), ('security_group', ['securitygroup']), ('hint', {'a': ['b', 'c']}), - ('config_drive', False), + ('config_drive', True), ('server_name', self.new_server.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -900,7 +901,7 @@ def test_server_create_with_options(self): block_device_mapping_v2=[], nics=[], scheduler_hints={'a': ['b', 'c']}, - config_drive=None, + config_drive=True, ) # ServerManager.create(name, image, flavor, **kwargs) self.servers_mock.create.assert_called_with( diff --git a/releasenotes/notes/story-2005468-server-use-config-drive-9fc68552365cfefa.yaml b/releasenotes/notes/story-2005468-server-use-config-drive-9fc68552365cfefa.yaml new file mode 100644 index 0000000000..786ede4f78 --- /dev/null +++ b/releasenotes/notes/story-2005468-server-use-config-drive-9fc68552365cfefa.yaml @@ -0,0 +1,8 @@ +--- +deprecations: + - | + The ``--config-drive`` option on the ``openstack server create`` command + has been deprecated in favour of the ``--use-config-drive`` and + ``--no-config-drive`` arguments. The ``--config-drive`` option expected + either a string or bool-like argument, but the nova API has only supported + boolean values since API v2.1 was introduced. From 0a8753dc3eaeda25554ccd769350de1e9792a62b Mon Sep 17 00:00:00 2001 From: Roger Luethi Date: Thu, 23 Jul 2020 13:20:09 +0200 Subject: [PATCH 2226/3095] Fix reverted osc-lib interface change The patch https://review.opendev.org/#/c/673389/ introduced a regression by changing the osc-lib interface. Two conflicting attempts to fix the regression were launched: 1) Reverting the patch. 2) The patch https://review.opendev.org/683119 changes the exception from the generic CommandError back to a specific Forbidden exception. The patch https://review.opendev.org/683118 catches this exception and passes on, i.e. re-implements the same behavior as before. The first idea was implemented, the initial patch reverted. The second idea was partially implemented. The change in python-openstackclient (683118) was merged. The change in osc-lib was approved but failed to merge because the initial change had been reverted. Now we have again a situation where the exception produced in osc-lib does not match the exception expected by the caller. It is unclear if the osc-lib interface will ever get a rebased version of https://review.opendev.org/683119 merged, so the safest way to address the issue is to also catch the exception that used to be thrown before the inital change and is again thrown after the inital change has been reverted. Change-Id: I2ea2def607ec5be112e42d53a1e660fef0cdd69c --- openstackclient/identity/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/identity/common.py b/openstackclient/identity/common.py index e70d87d21f..a75db4f8dd 100644 --- a/openstackclient/identity/common.py +++ b/openstackclient/identity/common.py @@ -207,7 +207,7 @@ def _find_identity_resource(identity_client_manager, name_or_id, name_or_id, **kwargs) if identity_resource is not None: return identity_resource - except exceptions.Forbidden: + except (exceptions.Forbidden, identity_exc.Forbidden): pass return resource_type(None, {'id': name_or_id, 'name': name_or_id}) From 4e2aefb5fa2ce236473187650149cf421c5f8c53 Mon Sep 17 00:00:00 2001 From: mb711d Date: Wed, 15 Jul 2020 17:18:53 -0400 Subject: [PATCH 2227/3095] Delete the testcases that arent needed anymore The file test_examples.py has never worked since its written and and cli in the example directory are covered by other functional tests for container, flavor and object lists and they have better asserts. So, deleting the file Change-Id: Ib9af40da96e66354fe878e79a80048a58f8dd6fe --- openstackclient/tests/functional/base.py | 4 --- .../tests/functional/examples/__init__.py | 0 .../functional/examples/test_examples.py | 28 ------------------- 3 files changed, 32 deletions(-) delete mode 100644 openstackclient/tests/functional/examples/__init__.py delete mode 100644 openstackclient/tests/functional/examples/test_examples.py diff --git a/openstackclient/tests/functional/base.py b/openstackclient/tests/functional/base.py index 08e9390e3e..3542a82756 100644 --- a/openstackclient/tests/functional/base.py +++ b/openstackclient/tests/functional/base.py @@ -19,10 +19,6 @@ import testtools -COMMON_DIR = os.path.dirname(os.path.abspath(__file__)) -FUNCTIONAL_DIR = os.path.normpath(os.path.join(COMMON_DIR, '..')) -ROOT_DIR = os.path.normpath(os.path.join(FUNCTIONAL_DIR, '..')) -EXAMPLE_DIR = os.path.join(ROOT_DIR, 'examples') ADMIN_CLOUD = os.environ.get('OS_ADMIN_CLOUD', 'devstack-admin') diff --git a/openstackclient/tests/functional/examples/__init__.py b/openstackclient/tests/functional/examples/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/openstackclient/tests/functional/examples/test_examples.py b/openstackclient/tests/functional/examples/test_examples.py deleted file mode 100644 index 031f036a98..0000000000 --- a/openstackclient/tests/functional/examples/test_examples.py +++ /dev/null @@ -1,28 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from openstackclient.tests.functional import base - - -class ExampleTests(base.TestCase): - """Functional tests for running examples.""" - - def test_common(self): - # NOTE(stevemar): If an examples has a non-zero return - # code, then execute will raise an error by default. - base.execute('python', base.EXAMPLE_DIR + '/common.py --debug') - - def test_object_api(self): - base.execute('python', base.EXAMPLE_DIR + '/object_api.py --debug') - - def test_osc_lib(self): - base.execute('python', base.EXAMPLE_DIR + '/osc-lib.py --debug') From 1e053babf4d674ac31d51dfba048704f32b558b3 Mon Sep 17 00:00:00 2001 From: Vishakha Agarwal Date: Fri, 24 Jul 2020 19:22:39 +0530 Subject: [PATCH 2228/3095] Add id and enabled param in ListIdentityProvider parser when doing openstack identity provider list --name xyz_id, and openstack identity provider list --enabled CLI raising error unrecognized arguments, whereas in api-ref document [1], user can pass name and enabled as optional query param. This addresses the above issue, by adding param --id and --enabled in parser of ListIdentityProvider. [1] https://docs.openstack.org/api-ref/identity/v3-ext/?expanded=list-identity-providers-detail#list-identity-providers Change-Id: I59ce3a5f54700ba5a735f0b3b4b3b73b3a8658fa --- .../identity/v3/identity_provider.py | 24 +++++++- .../identity/v3/test_identity_provider.py | 55 +++++++++++++++++++ ...st_identity_provider-e0981063a2dc5961.yaml | 3 + 3 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/add_id_and_enabled_to_list_identity_provider-e0981063a2dc5961.yaml diff --git a/openstackclient/identity/v3/identity_provider.py b/openstackclient/identity/v3/identity_provider.py index 2b2d9d11bd..7307cea00c 100644 --- a/openstackclient/identity/v3/identity_provider.py +++ b/openstackclient/identity/v3/identity_provider.py @@ -143,10 +143,32 @@ def take_action(self, parsed_args): class ListIdentityProvider(command.Lister): _description = _("List identity providers") + def get_parser(self, prog_name): + parser = super(ListIdentityProvider, self).get_parser(prog_name) + parser.add_argument( + '--id', + metavar='', + help=_('The Identity Providers’ ID attribute'), + ) + parser.add_argument( + '--enabled', + dest='enabled', + action='store_true', + help=_('The Identity Providers that are enabled will be returned'), + ) + return parser + def take_action(self, parsed_args): columns = ('ID', 'Enabled', 'Domain ID', 'Description') identity_client = self.app.client_manager.identity - data = identity_client.federation.identity_providers.list() + + kwargs = {} + if parsed_args.id: + kwargs['id'] = parsed_args.id + if parsed_args.enabled: + kwargs['enabled'] = True + + data = identity_client.federation.identity_providers.list(**kwargs) return (columns, (utils.get_item_properties( s, columns, diff --git a/openstackclient/tests/unit/identity/v3/test_identity_provider.py b/openstackclient/tests/unit/identity/v3/test_identity_provider.py index a419a9bcb1..39a37db24a 100644 --- a/openstackclient/tests/unit/identity/v3/test_identity_provider.py +++ b/openstackclient/tests/unit/identity/v3/test_identity_provider.py @@ -384,6 +384,61 @@ def test_identity_provider_list_no_options(self): ), ) self.assertListItemEqual(datalist, tuple(data)) + def test_identity_provider_list_ID_option(self): + arglist = ['--id', + identity_fakes.idp_id] + verifylist = [ + ('id', identity_fakes.idp_id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + kwargs = { + 'id': identity_fakes.idp_id + } + self.identity_providers_mock.list.assert_called_with(**kwargs) + + collist = ('ID', 'Enabled', 'Domain ID', 'Description') + self.assertEqual(collist, columns) + datalist = (( + identity_fakes.idp_id, + True, + identity_fakes.domain_id, + identity_fakes.idp_description, + ), ) + self.assertListItemEqual(datalist, tuple(data)) + + def test_identity_provider_list_enabled_option(self): + arglist = ['--enabled'] + verifylist = [ + ('enabled', True) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + kwargs = { + 'enabled': True + } + self.identity_providers_mock.list.assert_called_with(**kwargs) + + collist = ('ID', 'Enabled', 'Domain ID', 'Description') + self.assertEqual(collist, columns) + datalist = (( + identity_fakes.idp_id, + True, + identity_fakes.domain_id, + identity_fakes.idp_description, + ), ) + self.assertListItemEqual(datalist, tuple(data)) + class TestIdentityProviderSet(TestIdentityProvider): diff --git a/releasenotes/notes/add_id_and_enabled_to_list_identity_provider-e0981063a2dc5961.yaml b/releasenotes/notes/add_id_and_enabled_to_list_identity_provider-e0981063a2dc5961.yaml new file mode 100644 index 0000000000..fccd0a6353 --- /dev/null +++ b/releasenotes/notes/add_id_and_enabled_to_list_identity_provider-e0981063a2dc5961.yaml @@ -0,0 +1,3 @@ +--- +features: + - Add ``--id`` and ``--enabled`` option to ``identity provider list`` command. \ No newline at end of file From 454b2195649aec1a9eb1fdaa60b64663cb1f7298 Mon Sep 17 00:00:00 2001 From: Rodolfo Alonso Hernandez Date: Fri, 10 Jul 2020 13:14:26 +0000 Subject: [PATCH 2229/3095] Add NUMA affinity policy parameter to "port" Added port NUMA affinity policy parameter to "port create", "port set" and "port unset" commands. Change-Id: I48cacab275856af2911829f9b7176bb87fd039b3 Related-Bug: #1886798 --- openstackclient/network/v2/port.py | 33 +++++++ .../tests/unit/network/v2/fakes.py | 1 + .../tests/unit/network/v2/test_port.py | 95 +++++++++++++++++++ ...numa-affinity-policy-4706b0f9485a5d4d.yaml | 5 + 4 files changed, 134 insertions(+) create mode 100644 releasenotes/notes/add-port-numa-affinity-policy-4706b0f9485a5d4d.yaml diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index a21324ae27..8ea1077ab3 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -158,6 +158,16 @@ def _get_attrs(client_manager, parsed_args): parsed_args.disable_uplink_status_propagation): attrs['propagate_uplink_status'] = False + if ('numa_policy_required' in parsed_args and + parsed_args.numa_policy_required): + attrs['numa_affinity_policy'] = 'required' + elif ('numa_policy_preferred' in parsed_args and + parsed_args.numa_policy_preferred): + attrs['numa_affinity_policy'] = 'preferred' + elif ('numa_policy_legacy' in parsed_args and + parsed_args.numa_policy_legacy): + attrs['numa_affinity_policy'] = 'legacy' + return attrs @@ -265,6 +275,22 @@ def _add_updatable_args(parser): help=_("Set DNS name for this port " "(requires DNS integration extension)") ) + numa_affinity_policy_group = parser.add_mutually_exclusive_group() + numa_affinity_policy_group.add_argument( + '--numa-policy-required', + action='store_true', + help=_("NUMA affinity policy required to schedule this port") + ) + numa_affinity_policy_group.add_argument( + '--numa-policy-preferred', + action='store_true', + help=_("NUMA affinity policy preferred to schedule this port") + ) + numa_affinity_policy_group.add_argument( + '--numa-policy-legacy', + action='store_true', + help=_("NUMA affinity policy using legacy mode to schedule this port") + ) # TODO(abhiraut): Use the SDK resource mapped attribute names once the @@ -904,6 +930,11 @@ def get_parser(self, prog_name): action='store_true', help=_("Clear existing information of data plane status") ) + parser.add_argument( + '--numa-policy', + action='store_true', + help=_("Clear existing NUMA affinity policy") + ) _tag.add_tag_option_to_parser_for_unset(parser, _('port')) @@ -959,6 +990,8 @@ def take_action(self, parsed_args): attrs['qos_policy_id'] = None if parsed_args.data_plane_status: attrs['data_plane_status'] = None + if parsed_args.numa_policy: + attrs['numa_affinity_policy'] = None if attrs: client.update_port(obj, **attrs) diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index cef0a11c91..3df4042cba 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -634,6 +634,7 @@ def create_one_port(attrs=None): 'mac_address': 'fa:16:3e:a9:4e:72', 'name': 'port-name-' + uuid.uuid4().hex, 'network_id': 'network-id-' + uuid.uuid4().hex, + 'numa_affinity_policy': 'required', 'port_security_enabled': True, 'security_group_ids': [], 'status': 'ACTIVE', diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index 87aea61fac..70fa063dce 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -59,6 +59,7 @@ def _get_common_cols_data(fake_port): 'mac_address', 'name', 'network_id', + 'numa_affinity_policy', 'port_security_enabled', 'project_id', 'qos_network_policy_id', @@ -90,6 +91,7 @@ def _get_common_cols_data(fake_port): fake_port.mac_address, fake_port.name, fake_port.network_id, + fake_port.numa_affinity_policy, fake_port.port_security_enabled, fake_port.project_id, fake_port.qos_network_policy_id, @@ -655,6 +657,50 @@ def test_create_port_with_extra_dhcp_option(self): 'name': 'test-port', }) + def _test_create_with_numa_affinity_policy(self, policy=None): + arglist = [ + '--network', self._port.network_id, + 'test-port', + ] + if policy: + arglist += ['--numa-policy-%s' % policy] + + numa_affinity_policy = None if not policy else policy + verifylist = [ + ('network', self._port.network_id,), + ('name', 'test-port'), + ] + if policy: + verifylist.append(('numa_policy_%s' % policy, True)) + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = (self.cmd.take_action(parsed_args)) + + create_args = { + 'admin_state_up': True, + 'network_id': self._port.network_id, + 'name': 'test-port', + } + if numa_affinity_policy: + create_args['numa_affinity_policy'] = numa_affinity_policy + self.network.create_port.assert_called_once_with(**create_args) + + self.assertEqual(self.columns, columns) + self.assertItemEqual(self.data, data) + + def test_create_with_numa_affinity_policy_required(self): + self._test_create_with_numa_affinity_policy(policy='required') + + def test_create_with_numa_affinity_policy_preferred(self): + self._test_create_with_numa_affinity_policy(policy='preferred') + + def test_create_with_numa_affinity_policy_legacy(self): + self._test_create_with_numa_affinity_policy(policy='legacy') + + def test_create_with_numa_affinity_policy_null(self): + self._test_create_with_numa_affinity_policy() + class TestDeletePort(TestPort): @@ -1668,6 +1714,32 @@ def test_set_with_tags(self): def test_set_with_no_tag(self): self._test_set_tags(with_tags=False) + def _test_create_with_numa_affinity_policy(self, policy): + arglist = [ + '--numa-policy-%s' % policy, + self._port.id, + ] + verifylist = [ + ('numa_policy_%s' % policy, 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, **{'numa_affinity_policy': policy}) + + def test_create_with_numa_affinity_policy_required(self): + self._test_create_with_numa_affinity_policy('required') + + def test_create_with_numa_affinity_policy_preferred(self): + self._test_create_with_numa_affinity_policy('preferred') + + def test_create_with_numa_affinity_policy_legacy(self): + self._test_create_with_numa_affinity_policy('legacy') + class TestShowPort(TestPort): @@ -1923,3 +1995,26 @@ def test_unset_with_tags(self): def test_unset_with_all_tag(self): self._test_unset_tags(with_tags=False) + + def test_unset_numa_affinity_policy(self): + _fake_port = network_fakes.FakePort.create_one_port( + {'numa_affinity_policy': 'required'}) + self.network.find_port = mock.Mock(return_value=_fake_port) + arglist = [ + '--numa-policy', + _fake_port.name, + ] + verifylist = [ + ('numa_policy', True), + ('port', _fake_port.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + attrs = { + 'numa_affinity_policy': None, + } + + self.network.update_port.assert_called_once_with(_fake_port, **attrs) + self.assertIsNone(result) diff --git a/releasenotes/notes/add-port-numa-affinity-policy-4706b0f9485a5d4d.yaml b/releasenotes/notes/add-port-numa-affinity-policy-4706b0f9485a5d4d.yaml new file mode 100644 index 0000000000..0fbc54b187 --- /dev/null +++ b/releasenotes/notes/add-port-numa-affinity-policy-4706b0f9485a5d4d.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add NUMA affinity policy to ``port create``, ``port set`` and + ``port unset`` commands. From e24673267093de85beee753860cda1fb224ce4bc Mon Sep 17 00:00:00 2001 From: Lance Bragstad Date: Thu, 9 Jul 2020 17:07:52 -0500 Subject: [PATCH 2230/3095] Bypass user and group verification in RemoveRole Keystone let's users remove role assignments that reference non-existent users and groups. This is nice when keystone backs to an identity store like LDAP and users or groups are removed. Previously, openstackclient would validate the user and group existed in keystone before sending the request to delete the role assignment. This commit updates the code to bypass that validation so that users can use IDs to forcibly cleanup role assignments. Change-Id: I102b41677736bbe37a82abaa3c5b3e1faf2475d5 Story: 2006635 Task: 36848 --- openstackclient/identity/v3/role.py | 68 ++--- .../tests/unit/identity/v3/test_role.py | 242 ++++++++++++++++++ .../notes/bug-2006635-3110f7a87a186e62.yaml | 7 + 3 files changed, 285 insertions(+), 32 deletions(-) create mode 100644 releasenotes/notes/bug-2006635-3110f7a87a186e62.yaml diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index 980ebf1165..a674564fe0 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -64,59 +64,61 @@ def _add_identity_and_resource_options_to_parser(parser): def _process_identity_and_resource_options(parsed_args, - identity_client_manager): + identity_client_manager, + validate_actor_existence=True): + + def _find_user(): + try: + return common.find_user( + identity_client_manager, + parsed_args.user, + parsed_args.user_domain + ).id + except exceptions.CommandError: + if not validate_actor_existence: + return parsed_args.user + raise + + def _find_group(): + try: + return common.find_group( + identity_client_manager, + parsed_args.group, + parsed_args.group_domain + ).id + except exceptions.CommandError: + if not validate_actor_existence: + return parsed_args.group + raise + kwargs = {} if parsed_args.user and parsed_args.system: - kwargs['user'] = common.find_user( - identity_client_manager, - parsed_args.user, - parsed_args.user_domain, - ).id + kwargs['user'] = _find_user() kwargs['system'] = parsed_args.system elif parsed_args.user and parsed_args.domain: - kwargs['user'] = common.find_user( - identity_client_manager, - parsed_args.user, - parsed_args.user_domain, - ).id + kwargs['user'] = _find_user() kwargs['domain'] = common.find_domain( identity_client_manager, parsed_args.domain, ).id elif parsed_args.user and parsed_args.project: - kwargs['user'] = common.find_user( - identity_client_manager, - parsed_args.user, - parsed_args.user_domain, - ).id + kwargs['user'] = _find_user() kwargs['project'] = common.find_project( identity_client_manager, parsed_args.project, parsed_args.project_domain, ).id elif parsed_args.group and parsed_args.system: - kwargs['group'] = common.find_group( - identity_client_manager, - parsed_args.group, - parsed_args.group_domain, - ).id + kwargs['group'] = _find_group() kwargs['system'] = parsed_args.system elif parsed_args.group and parsed_args.domain: - kwargs['group'] = common.find_group( - identity_client_manager, - parsed_args.group, - parsed_args.group_domain, - ).id + kwargs['group'] = _find_group() kwargs['domain'] = common.find_domain( identity_client_manager, parsed_args.domain, ).id elif parsed_args.group and parsed_args.project: - kwargs['group'] = common.find_group( - identity_client_manager, - parsed_args.group, - parsed_args.group_domain, - ).id + kwargs['group'] = _find_group() kwargs['project'] = common.find_project( identity_client_manager, parsed_args.project, @@ -340,7 +342,9 @@ def take_action(self, parsed_args): ) kwargs = _process_identity_and_resource_options( - parsed_args, self.app.client_manager.identity) + parsed_args, self.app.client_manager.identity, + validate_actor_existence=False + ) identity_client.roles.revoke(role.id, **kwargs) diff --git a/openstackclient/tests/unit/identity/v3/test_role.py b/openstackclient/tests/unit/identity/v3/test_role.py index c1e91f9a0f..774b2c2b5f 100644 --- a/openstackclient/tests/unit/identity/v3/test_role.py +++ b/openstackclient/tests/unit/identity/v3/test_role.py @@ -19,6 +19,7 @@ from osc_lib import exceptions from osc_lib import utils +from openstackclient.identity import common from openstackclient.identity.v3 import role from openstackclient.tests.unit import fakes from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes @@ -846,6 +847,47 @@ def test_role_remove_user_system(self): ) self.assertIsNone(result) + @mock.patch.object(common, 'find_user') + def test_role_remove_non_existent_user_system(self, find_mock): + # Simulate the user not being in keystone, the client should gracefully + # handle this exception and send the request to remove the role since + # keystone supports removing role assignments with non-existent actors + # (e.g., users or groups). + find_mock.side_effect = exceptions.CommandError + + arglist = [ + '--user', identity_fakes.user_id, + '--system', 'all', + identity_fakes.role_name + ] + if self._is_inheritance_testcase(): + arglist.append('--inherited') + verifylist = [ + ('user', identity_fakes.user_id), + ('group', None), + ('system', 'all'), + ('domain', None), + ('project', None), + ('role', identity_fakes.role_name), + ('inherited', self._is_inheritance_testcase()), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'user': identity_fakes.user_id, + 'system': 'all', + 'os_inherit_extension_inherited': self._is_inheritance_testcase(), + } + # RoleManager.revoke(role, user=, group=, domain=, project=) + self.roles_mock.revoke.assert_called_with( + identity_fakes.role_id, + **kwargs + ) + self.assertIsNone(result) + def test_role_remove_user_domain(self): arglist = [ '--user', identity_fakes.user_name, @@ -879,6 +921,46 @@ def test_role_remove_user_domain(self): ) self.assertIsNone(result) + @mock.patch.object(common, 'find_user') + def test_role_remove_non_existent_user_domain(self, find_mock): + # Simulate the user not being in keystone, the client the gracefully + # handle this exception and send the request to remove the role since + # keystone will validate. + find_mock.side_effect = exceptions.CommandError + + arglist = [ + '--user', identity_fakes.user_id, + '--domain', identity_fakes.domain_name, + identity_fakes.role_name + ] + if self._is_inheritance_testcase(): + arglist.append('--inherited') + verifylist = [ + ('user', identity_fakes.user_id), + ('group', None), + ('system', None), + ('domain', identity_fakes.domain_name), + ('project', None), + ('role', identity_fakes.role_name), + ('inherited', self._is_inheritance_testcase()), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'user': identity_fakes.user_id, + 'domain': identity_fakes.domain_id, + 'os_inherit_extension_inherited': self._is_inheritance_testcase(), + } + # RoleManager.revoke(role, user=, group=, domain=, project=) + self.roles_mock.revoke.assert_called_with( + identity_fakes.role_id, + **kwargs + ) + self.assertIsNone(result) + def test_role_remove_user_project(self): arglist = [ '--user', identity_fakes.user_name, @@ -912,6 +994,46 @@ def test_role_remove_user_project(self): ) self.assertIsNone(result) + @mock.patch.object(common, 'find_user') + def test_role_remove_non_existent_user_project(self, find_mock): + # Simulate the user not being in keystone, the client the gracefully + # handle this exception and send the request to remove the role since + # keystone will validate. + find_mock.side_effect = exceptions.CommandError + + arglist = [ + '--user', identity_fakes.user_id, + '--project', identity_fakes.project_name, + identity_fakes.role_name + ] + if self._is_inheritance_testcase(): + arglist.append('--inherited') + verifylist = [ + ('user', identity_fakes.user_id), + ('group', None), + ('system', None), + ('domain', None), + ('project', identity_fakes.project_name), + ('role', identity_fakes.role_name), + ('inherited', self._is_inheritance_testcase()), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'user': identity_fakes.user_id, + 'project': identity_fakes.project_id, + 'os_inherit_extension_inherited': self._is_inheritance_testcase(), + } + # RoleManager.revoke(role, user=, group=, domain=, project=) + self.roles_mock.revoke.assert_called_with( + identity_fakes.role_id, + **kwargs + ) + self.assertIsNone(result) + def test_role_remove_group_system(self): arglist = [ '--group', identity_fakes.group_name, @@ -947,6 +1069,46 @@ def test_role_remove_group_system(self): ) self.assertIsNone(result) + @mock.patch.object(common, 'find_group') + def test_role_remove_non_existent_group_system(self, find_mock): + # Simulate the user not being in keystone, the client the gracefully + # handle this exception and send the request to remove the role since + # keystone will validate. + find_mock.side_effect = exceptions.CommandError + + arglist = [ + '--group', identity_fakes.group_id, + '--system', 'all', + identity_fakes.role_name + ] + if self._is_inheritance_testcase(): + arglist.append('--inherited') + verifylist = [ + ('user', None), + ('group', identity_fakes.group_id), + ('system', 'all'), + ('domain', None), + ('project', None), + ('role', identity_fakes.role_name), + ('inherited', self._is_inheritance_testcase()), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'group': identity_fakes.group_id, + 'system': 'all', + 'os_inherit_extension_inherited': self._is_inheritance_testcase(), + } + # RoleManager.revoke(role, user=, group=, domain=, project=) + self.roles_mock.revoke.assert_called_with( + identity_fakes.role_id, + **kwargs + ) + self.assertIsNone(result) + def test_role_remove_group_domain(self): arglist = [ '--group', identity_fakes.group_name, @@ -981,6 +1143,46 @@ def test_role_remove_group_domain(self): ) self.assertIsNone(result) + @mock.patch.object(common, 'find_group') + def test_role_remove_non_existent_group_domain(self, find_mock): + # Simulate the user not being in keystone, the client the gracefully + # handle this exception and send the request to remove the role since + # keystone will validate. + find_mock.side_effect = exceptions.CommandError + + arglist = [ + '--group', identity_fakes.group_id, + '--domain', identity_fakes.domain_name, + identity_fakes.role_name + ] + if self._is_inheritance_testcase(): + arglist.append('--inherited') + verifylist = [ + ('user', None), + ('group', identity_fakes.group_id), + ('system', None), + ('domain', identity_fakes.domain_name), + ('project', None), + ('role', identity_fakes.role_name), + ('inherited', self._is_inheritance_testcase()), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'group': identity_fakes.group_id, + 'domain': identity_fakes.domain_id, + 'os_inherit_extension_inherited': self._is_inheritance_testcase(), + } + # RoleManager.revoke(role, user=, group=, domain=, project=) + self.roles_mock.revoke.assert_called_with( + identity_fakes.role_id, + **kwargs + ) + self.assertIsNone(result) + def test_role_remove_group_project(self): arglist = [ '--group', identity_fakes.group_name, @@ -1014,6 +1216,46 @@ def test_role_remove_group_project(self): ) self.assertIsNone(result) + @mock.patch.object(common, 'find_group') + def test_role_remove_non_existent_group_project(self, find_mock): + # Simulate the user not being in keystone, the client the gracefully + # handle this exception and send the request to remove the role since + # keystone will validate. + find_mock.side_effect = exceptions.CommandError + + arglist = [ + '--group', identity_fakes.group_id, + '--project', identity_fakes.project_name, + identity_fakes.role_name + ] + if self._is_inheritance_testcase(): + arglist.append('--inherited') + verifylist = [ + ('user', None), + ('group', identity_fakes.group_id), + ('system', None), + ('domain', None), + ('project', identity_fakes.project_name), + ('role', identity_fakes.role_name), + ('inherited', self._is_inheritance_testcase()), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'group': identity_fakes.group_id, + 'project': identity_fakes.project_id, + 'os_inherit_extension_inherited': self._is_inheritance_testcase(), + } + # RoleManager.revoke(role, user=, group=, domain=, project=) + self.roles_mock.revoke.assert_called_with( + identity_fakes.role_id, + **kwargs + ) + self.assertIsNone(result) + def test_role_remove_domain_role_on_group_domain(self): self.roles_mock.get.return_value = fakes.FakeResource( None, diff --git a/releasenotes/notes/bug-2006635-3110f7a87a186e62.yaml b/releasenotes/notes/bug-2006635-3110f7a87a186e62.yaml new file mode 100644 index 0000000000..ecc58c8a3b --- /dev/null +++ b/releasenotes/notes/bug-2006635-3110f7a87a186e62.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + You can now remove role assignments from keystone that reference non-existent + users or groups. + + [Bug `2006635 `_] From ed6d8d941104c60f447de852582eb9388b9d2e73 Mon Sep 17 00:00:00 2001 From: Lewis Denny Date: Thu, 20 Aug 2020 20:18:42 +1000 Subject: [PATCH 2231/3095] Add API check for server_groups.list The policies parameter has been replaced with the policy parameter since Nova API version 2.64[1] This commit adds a check to make sure the correct parameter is used. [1]https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id59 Change-Id: Ia37beb7790884d6d15bec45074f446e64af1a2aa Story: #2008041 Task: #40703 --- openstackclient/compute/v2/server_group.py | 9 +- .../tests/unit/compute/v2/fakes.py | 35 ++++++- .../unit/compute/v2/test_server_group.py | 98 +++++++++++++++++++ 3 files changed, 138 insertions(+), 4 deletions(-) diff --git a/openstackclient/compute/v2/server_group.py b/openstackclient/compute/v2/server_group.py index c49a552f2c..cb6a503f6c 100644 --- a/openstackclient/compute/v2/server_group.py +++ b/openstackclient/compute/v2/server_group.py @@ -17,6 +17,7 @@ import logging +from novaclient import api_versions from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -136,11 +137,15 @@ def take_action(self, parsed_args): compute_client = self.app.client_manager.compute data = compute_client.server_groups.list(parsed_args.all_projects) + policy_key = 'Policies' + if compute_client.api_version >= api_versions.APIVersion("2.64"): + policy_key = 'Policy' + if parsed_args.long: column_headers = columns = ( 'ID', 'Name', - 'Policies', + policy_key, 'Members', 'Project Id', 'User Id', @@ -149,7 +154,7 @@ def take_action(self, parsed_args): column_headers = columns = ( 'ID', 'Name', - 'Policies', + policy_key, ) return (column_headers, diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index 6e12f735d4..3143098474 100644 --- a/openstackclient/tests/unit/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -1244,7 +1244,7 @@ class FakeServerGroup(object): """Fake one server group""" @staticmethod - def create_one_server_group(attrs=None): + def _create_one_server_group(attrs=None): """Create a fake server group :param Dictionary attrs: @@ -1261,7 +1261,6 @@ def create_one_server_group(attrs=None): 'members': [], 'metadata': {}, 'name': 'server-group-name-' + uuid.uuid4().hex, - 'policies': [], 'project_id': 'server-group-project-id-' + uuid.uuid4().hex, 'user_id': 'server-group-user-id-' + uuid.uuid4().hex, } @@ -1274,6 +1273,38 @@ def create_one_server_group(attrs=None): loaded=True) return server_group + @staticmethod + def create_one_server_group(attrs=None): + """Create a fake server group + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object, with id and other attributes + """ + if attrs is None: + attrs = {} + attrs.setdefault('policies', ['policy1', 'policy2']) + return FakeServerGroup._create_one_server_group(attrs) + + +class FakeServerGroupV264(object): + """Fake one server group fo API >= 2.64""" + + @staticmethod + def create_one_server_group(attrs=None): + """Create a fake server group + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object, with id and other attributes + """ + if attrs is None: + attrs = {} + attrs.setdefault('policy', 'policy1') + return FakeServerGroup._create_one_server_group(attrs) + class FakeUsage(object): """Fake one or more usage.""" diff --git a/openstackclient/tests/unit/compute/v2/test_server_group.py b/openstackclient/tests/unit/compute/v2/test_server_group.py index 9cd876ead1..4e20ed4231 100644 --- a/openstackclient/tests/unit/compute/v2/test_server_group.py +++ b/openstackclient/tests/unit/compute/v2/test_server_group.py @@ -15,6 +15,7 @@ from unittest import mock +from novaclient import api_versions from osc_lib import exceptions from osc_lib import utils @@ -53,6 +54,33 @@ def setUp(self): self.server_groups_mock.reset_mock() +class TestServerGroupV264(TestServerGroup): + + fake_server_group = \ + compute_fakes.FakeServerGroupV264.create_one_server_group() + + columns = ( + 'id', + 'members', + 'name', + 'policy', + 'project_id', + 'user_id', + ) + + data = ( + fake_server_group.id, + utils.format_list(fake_server_group.members), + fake_server_group.name, + fake_server_group.policy, + fake_server_group.project_id, + fake_server_group.user_id, + ) + + def setUp(self): + super(TestServerGroupV264, self).setUp() + + class TestServerGroupCreate(TestServerGroup): def setUp(self): @@ -230,6 +258,76 @@ def test_server_group_list_with_all_projects_and_long(self): self.assertEqual(self.list_data_long, tuple(data)) +class TestServerGroupListV264(TestServerGroupV264): + + list_columns = ( + 'ID', + 'Name', + 'Policy', + ) + + list_columns_long = ( + 'ID', + 'Name', + 'Policy', + 'Members', + 'Project Id', + 'User Id', + ) + + list_data = (( + TestServerGroupV264.fake_server_group.id, + TestServerGroupV264.fake_server_group.name, + TestServerGroupV264.fake_server_group.policy, + ),) + + list_data_long = (( + TestServerGroupV264.fake_server_group.id, + TestServerGroupV264.fake_server_group.name, + TestServerGroupV264.fake_server_group.policy, + utils.format_list(TestServerGroupV264.fake_server_group.members), + TestServerGroupV264.fake_server_group.project_id, + TestServerGroupV264.fake_server_group.user_id, + ),) + + def setUp(self): + super(TestServerGroupListV264, self).setUp() + + self.server_groups_mock.list.return_value = [self.fake_server_group] + self.cmd = server_group.ListServerGroup(self.app, None) + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.64') + + def test_server_group_list(self): + arglist = [] + verifylist = [ + ('all_projects', False), + ('long', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.server_groups_mock.list.assert_called_once_with(False) + + self.assertEqual(self.list_columns, columns) + self.assertEqual(self.list_data, tuple(data)) + + def test_server_group_list_with_all_projects_and_long(self): + arglist = [ + '--all-projects', + '--long', + ] + verifylist = [ + ('all_projects', True), + ('long', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.server_groups_mock.list.assert_called_once_with(True) + + self.assertEqual(self.list_columns_long, columns) + self.assertEqual(self.list_data_long, tuple(data)) + + class TestServerGroupShow(TestServerGroup): def setUp(self): From 51a1ea65f4d095b073381200e5268f909bf360de Mon Sep 17 00:00:00 2001 From: Lewis Denny Date: Wed, 17 Jun 2020 15:34:09 +1000 Subject: [PATCH 2232/3095] Add API check for server_groups.create The policies field has been replaced with the policy field since Nova API version 2.64[1] This commit adds a check to make sure the correct field is used. [1]https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id59 Change-Id: I06d3211937d822c26070b7f8ad757c365dcbb1bb Story: #2007822 Task: #40101 --- openstackclient/compute/v2/server_group.py | 9 ++++++-- .../unit/compute/v2/test_server_group.py | 23 +++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/openstackclient/compute/v2/server_group.py b/openstackclient/compute/v2/server_group.py index c49a552f2c..9bc2bb432d 100644 --- a/openstackclient/compute/v2/server_group.py +++ b/openstackclient/compute/v2/server_group.py @@ -17,6 +17,7 @@ import logging +from novaclient import api_versions from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -67,9 +68,13 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): compute_client = self.app.client_manager.compute info = {} + + policy_arg = {'policies': [parsed_args.policy]} + if compute_client.api_version >= api_versions.APIVersion("2.64"): + policy_arg = {'policy': parsed_args.policy} server_group = compute_client.server_groups.create( - name=parsed_args.name, - policies=[parsed_args.policy]) + name=parsed_args.name, **policy_arg) + info.update(server_group._info) columns = _get_columns(info) diff --git a/openstackclient/tests/unit/compute/v2/test_server_group.py b/openstackclient/tests/unit/compute/v2/test_server_group.py index 9cd876ead1..b9bb79f620 100644 --- a/openstackclient/tests/unit/compute/v2/test_server_group.py +++ b/openstackclient/tests/unit/compute/v2/test_server_group.py @@ -15,6 +15,7 @@ from unittest import mock +from novaclient import api_versions from osc_lib import exceptions from osc_lib import utils @@ -80,6 +81,28 @@ def test_server_group_create(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_server_group_create_v264(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.64') + + arglist = [ + '--policy', 'soft-anti-affinity', + 'affinity_group', + ] + verifylist = [ + ('policy', 'soft-anti-affinity'), + ('name', 'affinity_group'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.server_groups_mock.create.assert_called_once_with( + name=parsed_args.name, + policy=parsed_args.policy, + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + class TestServerGroupDelete(TestServerGroup): From 4a3c5207c1b2d628d1ec8b30e28f56e5956bd2e0 Mon Sep 17 00:00:00 2001 From: melanie witt Date: Mon, 27 Jul 2020 22:04:47 +0000 Subject: [PATCH 2233/3095] Show words indicating booted from volume for server image For a server booted from a volume, nova API does not store an image_id and instead returns an empty string. Currently, openstackclient similarly shows an empty string for Image Name and Image ID for servers booted from volumes. To aid CLI users in understanding the meaning of no image_id, we can display the string "N/A (booted from volume)" in the image field if the server was booted from a volume. Change-Id: I9c62cf6fe23b2e934dcbf5ebbf706b2705d2e424 --- openstackclient/compute/v2/server.py | 16 ++++++++++++++-- .../functional/compute/v2/test_server.py | 19 +++++++++++++++++-- .../tests/unit/compute/v2/test_server.py | 8 ++++---- 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 93e9f966ae..ff40565a6b 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -38,6 +38,8 @@ LOG = logging.getLogger(__name__) +IMAGE_STRING_FOR_BFV = 'N/A (booted from volume)' + def _format_servers_list_networks(networks): """Return a formatted string of a server's networks @@ -147,6 +149,12 @@ def _prep_server_detail(compute_client, image_client, server, refresh=True): info['image'] = "%s (%s)" % (image.name, image_id) except Exception: info['image'] = image_id + else: + # NOTE(melwitt): An server booted from a volume will have no image + # associated with it. We fill in the image with "N/A (booted from + # volume)" to help users who want to be able to grep for + # boot-from-volume servers when using the CLI. + info['image'] = IMAGE_STRING_FOR_BFV # Convert the flavor blob to a name flavor_info = info.get('flavor', {}) @@ -1520,8 +1528,12 @@ def take_action(self, parsed_args): s.image_name = image.name s.image_id = s.image['id'] else: - s.image_name = '' - s.image_id = '' + # NOTE(melwitt): An server booted from a volume will have no + # image associated with it. We fill in the Image Name and ID + # with "N/A (booted from volume)" to help users who want to be + # able to grep for boot-from-volume servers when using the CLI. + s.image_name = IMAGE_STRING_FOR_BFV + s.image_id = IMAGE_STRING_FOR_BFV if 'id' in s.flavor: flavor = flavors.get(s.flavor['id']) if flavor: diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py index 6e080e9ba2..3123057c8e 100644 --- a/openstackclient/tests/functional/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -16,6 +16,7 @@ from tempest.lib import exceptions +from openstackclient.compute.v2 import server as v2_server from openstackclient.tests.functional.compute.v2 import common from openstackclient.tests.functional.volume.v2 import common as volume_common @@ -509,6 +510,20 @@ def test_server_boot_from_volume(self): server['name'], ) + # check that image indicates server "booted from volume" + self.assertEqual( + v2_server.IMAGE_STRING_FOR_BFV, + server['image'], + ) + # check server list too + servers = json.loads(self.openstack( + 'server list -f json' + )) + self.assertEqual( + v2_server.IMAGE_STRING_FOR_BFV, + servers[0]['Image'] + ) + # check volumes cmd_output = json.loads(self.openstack( 'volume show -f json ' + @@ -779,8 +794,8 @@ def test_boot_from_volume(self): self.addCleanup(self.openstack, 'volume delete ' + attached_volume_id) # Since the server is volume-backed the GET /servers/{server_id} - # response will have image=''. - self.assertEqual('', cmd_output['image']) + # response will have image='N/A (booted from volume)'. + self.assertEqual(v2_server.IMAGE_STRING_FOR_BFV, cmd_output['image']) # check the volume that attached on server cmd_output = json.loads(self.openstack( diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 7e4c71c50c..405e05d160 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -2609,7 +2609,7 @@ def setUp(self): s.status, server._format_servers_list_networks(s.networks), # Image will be an empty string if boot-from-volume - self.image.name if s.image else s.image, + self.image.name if s.image else server.IMAGE_STRING_FOR_BFV, self.flavor.name, )) self.data_long.append(( @@ -2622,8 +2622,8 @@ def setUp(self): ), server._format_servers_list_networks(s.networks), # Image will be an empty string if boot-from-volume - self.image.name if s.image else s.image, - s.image['id'] if s.image else s.image, + self.image.name if s.image else server.IMAGE_STRING_FOR_BFV, + s.image['id'] if s.image else server.IMAGE_STRING_FOR_BFV, self.flavor.name, s.flavor['id'], getattr(s, 'OS-EXT-AZ:availability_zone'), @@ -2636,7 +2636,7 @@ def setUp(self): s.status, server._format_servers_list_networks(s.networks), # Image will be an empty string if boot-from-volume - s.image['id'] if s.image else s.image, + s.image['id'] if s.image else server.IMAGE_STRING_FOR_BFV, s.flavor['id'] )) From 58f1c90971969ee12089c2e5aeea4993d8c095d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Weing=C3=A4rtner?= Date: Wed, 2 Sep 2020 17:26:00 -0300 Subject: [PATCH 2234/3095] Add source_ip_prefix and destination_ip_prefix to metering label rules As proposed in the RFE and then approved in the spec, we are adding to the neutron metering rules two new parameters. The source IP prefix, and destination IP prefix. Partially-Implements: https://bugs.launchpad.net/neutron/+bug/1889431 RFE: https://bugs.launchpad.net/neutron/+bug/1889431 Depends-On: https://review.opendev.org/#/c/746586/ Change-Id: Ic44d88fabea0fffef2279f2f2c3d2b1da6426d4d --- .../network/v2/network_meter_rule.py | 22 ++++++++++++++++++- .../tests/unit/network/v2/fakes.py | 2 ++ .../network/v2/test_network_meter_rule.py | 12 ++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/openstackclient/network/v2/network_meter_rule.py b/openstackclient/network/v2/network_meter_rule.py index 49ff9e1b0e..1cf0395f44 100644 --- a/openstackclient/network/v2/network_meter_rule.py +++ b/openstackclient/network/v2/network_meter_rule.py @@ -46,6 +46,10 @@ def _get_attrs(client_manager, parsed_args): attrs['direction'] = 'egress' if parsed_args.remote_ip_prefix is not None: attrs['remote_ip_prefix'] = parsed_args.remote_ip_prefix + if parsed_args.source_ip_prefix is not None: + attrs['source_ip_prefix'] = parsed_args.source_ip_prefix + if parsed_args.destination_ip_prefix is not None: + attrs['destination_ip_prefix'] = parsed_args.destination_ip_prefix if parsed_args.meter is not None: attrs['metering_label_id'] = parsed_args.meter if parsed_args.project is not None: @@ -97,9 +101,21 @@ def get_parser(self, prog_name): parser.add_argument( '--remote-ip-prefix', metavar='', - required=True, + required=False, help=_('The remote IP prefix to associate with this rule'), ) + parser.add_argument( + '--source-ip-prefix', + metavar='', + required=False, + help=_('The source IP prefix to associate with this rule'), + ) + parser.add_argument( + '--destination-ip-prefix', + metavar='', + required=False, + help=_('The destination IP prefix to associate with this rule'), + ) parser.add_argument( 'meter', metavar='', @@ -168,12 +184,16 @@ def take_action(self, parsed_args): 'excluded', 'direction', 'remote_ip_prefix', + 'source_ip_prefix', + 'destination_ip_prefix', ) column_headers = ( 'ID', 'Excluded', 'Direction', 'Remote IP Prefix', + 'Source IP Prefix', + 'Destination IP Prefix', ) data = client.metering_label_rules() return (column_headers, diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index cef0a11c91..5095cb9125 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -1590,6 +1590,8 @@ def create_one_rule(attrs=None): 'excluded': False, 'metering_label_id': 'meter-label-id-' + uuid.uuid4().hex, 'remote_ip_prefix': '10.0.0.0/24', + 'source_ip_prefix': '8.8.8.8/32', + 'destination_ip_prefix': '10.0.0.0/24', 'tenant_id': 'project-id-' + uuid.uuid4().hex, } diff --git a/openstackclient/tests/unit/network/v2/test_network_meter_rule.py b/openstackclient/tests/unit/network/v2/test_network_meter_rule.py index 8f8922c020..e9224fa650 100644 --- a/openstackclient/tests/unit/network/v2/test_network_meter_rule.py +++ b/openstackclient/tests/unit/network/v2/test_network_meter_rule.py @@ -42,20 +42,24 @@ class TestCreateMeterRule(TestMeterRule): ) columns = ( + 'destination_ip_prefix', 'direction', 'excluded', 'id', 'metering_label_id', 'project_id', 'remote_ip_prefix', + 'source_ip_prefix', ) data = ( + new_rule.destination_ip_prefix, new_rule.direction, new_rule.excluded, new_rule.id, new_rule.metering_label_id, new_rule.project_id, new_rule.remote_ip_prefix, + new_rule.source_ip_prefix, ) def setUp(self): @@ -228,6 +232,8 @@ class TestListMeterRule(TestMeterRule): 'Excluded', 'Direction', 'Remote IP Prefix', + 'Source IP Prefix', + 'Destination IP Prefix' ) data = [] @@ -238,6 +244,8 @@ class TestListMeterRule(TestMeterRule): rule.excluded, rule.direction, rule.remote_ip_prefix, + rule.source_ip_prefix, + rule.destination_ip_prefix )) def setUp(self): @@ -270,21 +278,25 @@ class TestShowMeterRule(TestMeterRule): ) columns = ( + 'destination_ip_prefix', 'direction', 'excluded', 'id', 'metering_label_id', 'project_id', 'remote_ip_prefix', + 'source_ip_prefix', ) data = ( + new_rule.destination_ip_prefix, new_rule.direction, new_rule.excluded, new_rule.id, new_rule.metering_label_id, new_rule.project_id, new_rule.remote_ip_prefix, + new_rule.source_ip_prefix, ) def setUp(self): From 67700e6dd95b0f58b2f01cc816620819a4a6deae Mon Sep 17 00:00:00 2001 From: Miguel Lavalle Date: Sun, 26 Apr 2020 17:40:17 -0500 Subject: [PATCH 2235/3095] Support tagging Neutron ports on creation This change adds support for tagging ports on creation Co-Authored-By: Slawek Kaplonski Change-Id: I3148a568664588eb2f529138f984859570c0fca1 Related-Bug: #1815933 --- .zuul.yaml | 1 + openstackclient/network/v2/port.py | 15 ++++- .../tests/unit/network/v2/test_port.py | 58 ++++++++++++++----- 3 files changed, 59 insertions(+), 15 deletions(-) diff --git a/.zuul.yaml b/.zuul.yaml index e566ceb845..c04f3446d5 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -91,6 +91,7 @@ neutron-segments: true q-metering: true q-qos: true + neutron-tag-ports-during-bulk-creation: true tox_envlist: functional - job: diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index a21324ae27..be3e1709ff 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -454,12 +454,23 @@ def take_action(self, parsed_args): if parsed_args.qos_policy: attrs['qos_policy_id'] = client.find_qos_policy( parsed_args.qos_policy, ignore_missing=False).id + + set_tags_in_post = bool( + client.find_extension('tag-ports-during-bulk-creation')) + if set_tags_in_post: + if parsed_args.no_tag: + attrs['tags'] = [] + if parsed_args.tags: + attrs['tags'] = list(set(parsed_args.tags)) + with common.check_missing_extension_if_error( self.app.client_manager.network, attrs): obj = client.create_port(**attrs) - # tags cannot be set when created, so tags need to be set later. - _tag.update_tags_for_set(client, obj, parsed_args) + if not set_tags_in_post: + # tags cannot be set when created, so tags need to be set later. + _tag.update_tags_for_set(client, obj, parsed_args) + display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index 87aea61fac..a3d0c517d7 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -119,6 +119,7 @@ def setUp(self): self.network.find_network = mock.Mock(return_value=fake_net) self.fake_subnet = network_fakes.FakeSubnet.create_one_subnet() self.network.find_subnet = mock.Mock(return_value=self.fake_subnet) + self.network.find_extension = mock.Mock(return_value=[]) # Get the command object to test self.cmd = port.CreatePort(self.app, self.namespace) @@ -534,7 +535,7 @@ def test_create_port_security_disabled(self): 'name': 'test-port', }) - def _test_create_with_tag(self, add_tags=True): + def _test_create_with_tag(self, add_tags=True, add_tags_in_post=True): arglist = [ '--network', self._port.network_id, 'test-port', @@ -553,28 +554,59 @@ def _test_create_with_tag(self, add_tags=True): else: verifylist.append(('no_tag', True)) + self.network.find_extension = mock.Mock(return_value=add_tags_in_post) + parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = (self.cmd.take_action(parsed_args)) - self.network.create_port.assert_called_once_with( - admin_state_up=True, - network_id=self._port.network_id, - name='test-port' - ) - if add_tags: - self.network.set_tags.assert_called_once_with( - self._port, - tests_utils.CompareBySet(['red', 'blue'])) + args = { + 'admin_state_up': True, + 'network_id': self._port.network_id, + 'name': 'test-port', + } + if add_tags_in_post: + if add_tags: + args['tags'] = sorted(['red', 'blue']) + else: + args['tags'] = [] + self.network.create_port.assert_called_once() + # Now we need to verify if arguments to call create_port are as + # expected, + # But we can't simply use assert_called_once_with() method because + # duplicates from 'tags' are removed with + # list(set(parsed_args.tags)) and that don't quarantee order of + # tags list which is used to call create_port(). + create_port_call_kwargs = self.network.create_port.call_args[1] + create_port_call_kwargs['tags'] = sorted( + create_port_call_kwargs['tags']) + self.assertDictEqual(args, create_port_call_kwargs) else: - self.assertFalse(self.network.set_tags.called) + self.network.create_port.assert_called_once_with( + admin_state_up=True, + network_id=self._port.network_id, + name='test-port' + ) + if add_tags: + self.network.set_tags.assert_called_once_with( + self._port, + tests_utils.CompareBySet(['red', 'blue'])) + else: + self.assertFalse(self.network.set_tags.called) + self.assertEqual(self.columns, columns) self.assertItemEqual(self.data, data) def test_create_with_tags(self): - self._test_create_with_tag(add_tags=True) + self._test_create_with_tag(add_tags=True, add_tags_in_post=True) def test_create_with_no_tag(self): - self._test_create_with_tag(add_tags=False) + self._test_create_with_tag(add_tags=False, add_tags_in_post=True) + + def test_create_with_tags_using_put(self): + self._test_create_with_tag(add_tags=True, add_tags_in_post=False) + + def test_create_with_no_tag_using_put(self): + self._test_create_with_tag(add_tags=False, add_tags_in_post=False) def _test_create_with_uplink_status_propagation(self, enable=True): arglist = [ From f0642bc05f202395b6e2f7a4f7b0e10155b7c1f8 Mon Sep 17 00:00:00 2001 From: likui Date: Tue, 8 Sep 2020 15:44:51 +0800 Subject: [PATCH 2236/3095] Update developing.rst Use unittest.mock instead of mock Change-Id: Ib573e9d217b4f18ef4e7ba3ab581164be423cb26 --- doc/source/contributor/developing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/contributor/developing.rst b/doc/source/contributor/developing.rst index 5b859199bf..35c7c7b91f 100644 --- a/doc/source/contributor/developing.rst +++ b/doc/source/contributor/developing.rst @@ -199,7 +199,6 @@ Example import copy import fixtures - import mock import os from osc_lib.api import auth @@ -208,4 +207,5 @@ Example from openstackclient import shell from openstackclient.tests import utils + from unittest import mock From fbd2c00b8999202917671bcdb39298eb39c3ad47 Mon Sep 17 00:00:00 2001 From: Myeongchul Chae Date: Sun, 16 Aug 2020 13:44:45 +0000 Subject: [PATCH 2237/3095] Fix --image-property option in 'create server' There was a problem that the '-image-property' option, which can be used to create an instance, did not work as intended. I found that there were two problems with this option. First, I cannot select an image as its metadata. The second is that when there are multiple images available, the desired image may not be selected depending on the situation. This patch solves these two problems. I wrote the test case with these two problems considered together. Change-Id: Ib2745d7e067056ff4ca8bfaf6cff492d0dacb73a story: #2007860 --- openstackclient/compute/v2/server.py | 14 ++++- .../tests/unit/compute/v2/test_server.py | 59 +++++++++++++++++++ ...-property-field.yaml-c51bf37c3106d6ff.yaml | 6 ++ 3 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/properties-with-image-property-field.yaml-c51bf37c3106d6ff.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 93e9f966ae..d7b01a0fb3 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -751,19 +751,27 @@ def _match_image(image_api, wanted_properties): images_matched = [] for img in image_list: img_dict = {} + # exclude any unhashable entries - for key, value in img.items(): + img_dict_items = list(img.items()) + if img.properties: + img_dict_items.extend(list(img.properties.items())) + for key, value in img_dict_items: try: set([key, value]) except TypeError: + if key != 'properties': + LOG.debug('Skipped the \'%s\' attribute. ' + 'That cannot be compared. ' + '(image: %s, value: %s)', + key, img.id, value) pass else: img_dict[key] = value + if all(k in img_dict and img_dict[k] == v for k, v in wanted_properties.items()): images_matched.append(img) - else: - return [] return images_matched images = _match_image(image_client, parsed_args.image_property) diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 7e4c71c50c..6c9497b55c 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -2048,6 +2048,65 @@ def test_server_create_image_property_missed(self): self.cmd.take_action, parsed_args) + def test_server_create_image_property_with_image_list(self): + arglist = [ + '--image-property', + 'owner_specified.openstack.object=image/cirros', + '--flavor', 'flavor1', + '--nic', 'none', + self.new_server.name, + ] + + verifylist = [ + ('image_property', + {'owner_specified.openstack.object': 'image/cirros'}), + ('flavor', 'flavor1'), + ('nic', ['none']), + ('server_name', self.new_server.name), + ] + # create a image_info as the side_effect of the fake image_list() + image_info = { + 'properties': { + 'owner_specified.openstack.object': 'image/cirros' + } + } + + target_image = image_fakes.FakeImage.create_one_image(image_info) + another_image = image_fakes.FakeImage.create_one_image({}) + self.images_mock.return_value = [target_image, another_image] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = dict( + files={}, + reservation_id=None, + min_count=1, + max_count=1, + security_groups=[], + userdata=None, + key_name=None, + availability_zone=None, + block_device_mapping_v2=[], + nics='none', + meta=None, + scheduler_hints={}, + config_drive=None, + ) + + # ServerManager.create(name, image, flavor, **kwargs) + self.servers_mock.create.assert_called_with( + self.new_server.name, + target_image, + self.flavor, + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) + def test_server_create_invalid_hint(self): # Not a key-value pair arglist = [ diff --git a/releasenotes/notes/properties-with-image-property-field.yaml-c51bf37c3106d6ff.yaml b/releasenotes/notes/properties-with-image-property-field.yaml-c51bf37c3106d6ff.yaml new file mode 100644 index 0000000000..cf082f45c8 --- /dev/null +++ b/releasenotes/notes/properties-with-image-property-field.yaml-c51bf37c3106d6ff.yaml @@ -0,0 +1,6 @@ +--- +features: + - Support for image search via properties of image. Currently + "openstack server create --image-property" only takes image property. + Now it can also search image via properties (user defined) too. + Story https://storyboard.openstack.org/#!/story/2007860. From 99b5adf9c65d3cd2e2aa8c40c08a478fae0a49f3 Mon Sep 17 00:00:00 2001 From: Artem Goncharov Date: Thu, 10 Sep 2020 18:39:54 +0200 Subject: [PATCH 2238/3095] Fix gate due to switch to focal In focal we do not have libffi6. cffi and greenlet versions in lower-contraints are too old. Change-Id: Iab3634039845adb649c7fd69d1812b405a61433c --- bindep.txt | 1 - lower-constraints.txt | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/bindep.txt b/bindep.txt index cf94d2a8cb..4c90a026fe 100644 --- a/bindep.txt +++ b/bindep.txt @@ -5,7 +5,6 @@ gcc [compile test] libc6-dev [compile test platform:dpkg] libffi-devel [platform:rpm] libffi-dev [compile test platform:dpkg] -libffi6 [platform:dpkg] libssl-dev [compile test platform:dpkg] python3-dev [compile test platform:dpkg] python3-devel [compile test platform:rpm] diff --git a/lower-constraints.txt b/lower-constraints.txt index 0a453dae25..403ba4e05c 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -4,7 +4,7 @@ appdirs==1.3.0 asn1crypto==0.23.0 bandit==1.1.0 cachetools==2.0.0 -cffi==1.7.0 +cffi==1.14.0 cliff==2.8.0 cmd2==0.8.0 contextlib2==0.4.0 @@ -28,7 +28,7 @@ futurist==2.1.0 gitdb==0.6.4 GitPython==1.0.1 gnocchiclient==3.3.1 -greenlet==0.4.10 +greenlet==0.4.15 hacking==2.0.0 httplib2==0.9.1 idna==2.6 From 5aeec30701bbb0df59359b33fe107904a975e6c0 Mon Sep 17 00:00:00 2001 From: "wu.shiming" Date: Mon, 14 Sep 2020 10:34:02 +0800 Subject: [PATCH 2239/3095] Remove install unnecessary packages The docs and releasenotes requirements migrated to doc/requirements.txt we need not install things from requirements.txt. Change-Id: I4403cee833448beb69afaec503519d5a951f7e34 --- tox.ini | 1 - 1 file changed, 1 deletion(-) diff --git a/tox.ini b/tox.ini index 3a2a307de8..fdff6539e8 100644 --- a/tox.ini +++ b/tox.ini @@ -111,7 +111,6 @@ commands = [testenv:docs] deps = -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} - -r{toxinidir}/requirements.txt -r{toxinidir}/doc/requirements.txt commands = sphinx-build -a -E -W -d doc/build/doctrees -b html doc/source doc/build/html From bae89b30144fdf40d0fea31e1a595b507e32c4f3 Mon Sep 17 00:00:00 2001 From: jay Date: Thu, 4 Jun 2020 14:09:03 +0200 Subject: [PATCH 2240/3095] Output correct json for security groups in 'openstack server show' Fixes incorrect json output for 'openstack server show -f json'. The security group json output groups all the json as one for e.g. "security_groups": "name='group1'\nname='group2'" The correct output should be "security_groups" : [{"name" : "group1"}, {"name" : "group2"}] properties and volumes_attached fields also has similar issue. Story: 2007755 Change-Id: I1b1cac716329e0530400aff782c08000b21d8e1d --- openstackclient/compute/v2/server.py | 17 +++++--- .../functional/compute/v2/test_server.py | 39 ++++++++++++------- .../tests/unit/compute/v2/test_server.py | 3 ++ ...ty-grp-json-fix.yaml-2af1f48a48034d64.yaml | 4 ++ 4 files changed, 42 insertions(+), 21 deletions(-) create mode 100644 releasenotes/notes/security-grp-json-fix.yaml-2af1f48a48034d64.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 93e9f966ae..b3b0b7be15 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -24,6 +24,7 @@ from novaclient import api_versions from novaclient.v2 import servers from openstack import exceptions as sdk_exceptions +from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -166,14 +167,14 @@ def _prep_server_detail(compute_client, image_client, server, refresh=True): if 'os-extended-volumes:volumes_attached' in info: info.update( { - 'volumes_attached': utils.format_list_of_dicts( + 'volumes_attached': format_columns.ListDictColumn( info.pop('os-extended-volumes:volumes_attached')) } ) if 'security_groups' in info: info.update( { - 'security_groups': utils.format_list_of_dicts( + 'security_groups': format_columns.ListDictColumn( info.pop('security_groups')) } ) @@ -182,9 +183,14 @@ def _prep_server_detail(compute_client, image_client, server, refresh=True): info['addresses'] = _format_servers_list_networks(server.networks) # Map 'metadata' field to 'properties' - info.update( - {'properties': utils.format_dict(info.pop('metadata'))} - ) + if not info['metadata']: + info.update( + {'properties': utils.format_dict(info.pop('metadata'))} + ) + else: + info.update( + {'properties': format_columns.DictColumn(info.pop('metadata'))} + ) # Migrate tenant_id to project_id naming if 'tenant_id' in info: @@ -2530,7 +2536,6 @@ def take_action(self, parsed_args): data = _prep_server_detail(compute_client, self.app.client_manager.image, server, refresh=False) - return zip(*sorted(data.items())) diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py index 6e080e9ba2..50c59c17a3 100644 --- a/openstackclient/tests/functional/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -230,7 +230,7 @@ def test_server_set(self): )) # Really, shouldn't this be a list? self.assertEqual( - "a='b', c='d'", + {'a': 'b', 'c': 'd'}, cmd_output['properties'], ) @@ -244,7 +244,7 @@ def test_server_set(self): name )) self.assertEqual( - "c='d'", + {'c': 'd'}, cmd_output['properties'], ) @@ -619,8 +619,8 @@ def test_server_boot_with_bdm_snapshot(self): server_name )) volumes_attached = cmd_output['volumes_attached'] - self.assertTrue(volumes_attached.startswith('id=')) - attached_volume_id = volumes_attached.replace('id=', '') + self.assertIsNotNone(volumes_attached) + attached_volume_id = volumes_attached[0]["id"] # check the volume that attached on server cmd_output = json.loads(self.openstack( @@ -699,8 +699,8 @@ def test_server_boot_with_bdm_image(self): server_name )) volumes_attached = cmd_output['volumes_attached'] - self.assertTrue(volumes_attached.startswith('id=')) - attached_volume_id = volumes_attached.replace('id=', '') + self.assertIsNotNone(volumes_attached) + attached_volume_id = volumes_attached[0]["id"] # check the volume that attached on server cmd_output = json.loads(self.openstack( @@ -773,10 +773,12 @@ def test_boot_from_volume(self): server_name )) volumes_attached = cmd_output['volumes_attached'] - self.assertTrue(volumes_attached.startswith('id=')) - attached_volume_id = volumes_attached.replace('id=', '') - # Don't leak the volume when the test exits. - self.addCleanup(self.openstack, 'volume delete ' + attached_volume_id) + self.assertIsNotNone(volumes_attached) + attached_volume_id = volumes_attached[0]["id"] + for vol in volumes_attached: + self.assertIsNotNone(vol['id']) + # Don't leak the volume when the test exits. + self.addCleanup(self.openstack, 'volume delete ' + vol['id']) # Since the server is volume-backed the GET /servers/{server_id} # response will have image=''. @@ -785,7 +787,7 @@ def test_boot_from_volume(self): # check the volume that attached on server cmd_output = json.loads(self.openstack( 'volume show -f json ' + - attached_volume_id + volumes_attached[0]["id"] )) # The volume size should be what we specified on the command line. self.assertEqual(1, int(cmd_output['size'])) @@ -879,14 +881,21 @@ def test_server_create_with_security_group(self): self.assertIsNotNone(server['id']) self.assertEqual(server_name, server['name']) - self.assertIn(str(security_group1['id']), server['security_groups']) - self.assertIn(str(security_group2['id']), server['security_groups']) + sec_grp = "" + for sec in server['security_groups']: + sec_grp += sec['name'] + self.assertIn(str(security_group1['id']), sec_grp) + self.assertIn(str(security_group2['id']), sec_grp) self.wait_for_status(server_name, 'ACTIVE') server = json.loads(self.openstack( 'server show -f json ' + server_name )) - self.assertIn(sg_name1, server['security_groups']) - self.assertIn(sg_name2, server['security_groups']) + # check if security group exists in list + sec_grp = "" + for sec in server['security_groups']: + sec_grp += sec['name'] + self.assertIn(sg_name1, sec_grp) + self.assertIn(sg_name2, sec_grp) def test_server_create_with_empty_network_option_latest(self): """Test server create with empty network option in nova 2.latest.""" diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 7e4c71c50c..933c4e7ddd 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -5166,6 +5166,8 @@ def test_prep_server_detail(self, find_resource): 'tenant_id': u'tenant-id-xxx', 'networks': {u'public': [u'10.20.30.40', u'2001:db8::f']}, 'links': u'http://xxx.yyy.com', + 'properties': '', + 'volumes_attached': [{"id": "6344fe9d-ef20-45b2-91a6"}], } _server = compute_fakes.FakeServer.create_one_server(attrs=server_info) find_resource.side_effect = [_server, _flavor] @@ -5182,6 +5184,7 @@ def test_prep_server_detail(self, find_resource): 'properties': '', 'OS-EXT-STS:power_state': server._format_servers_list_power_state( getattr(_server, 'OS-EXT-STS:power_state')), + 'volumes_attached': [{"id": "6344fe9d-ef20-45b2-91a6"}], } # Call _prep_server_detail(). diff --git a/releasenotes/notes/security-grp-json-fix.yaml-2af1f48a48034d64.yaml b/releasenotes/notes/security-grp-json-fix.yaml-2af1f48a48034d64.yaml new file mode 100644 index 0000000000..3a0155a1da --- /dev/null +++ b/releasenotes/notes/security-grp-json-fix.yaml-2af1f48a48034d64.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - The ``openstack server show -f json`` command was not outputting + json for security groups, volumes and properties properly. From cbc1fb08ec7fd335eb434606a64e9ae051ea28bf Mon Sep 17 00:00:00 2001 From: wangzihao Date: Fri, 18 Sep 2020 11:15:47 +0800 Subject: [PATCH 2241/3095] bump py37 to py38 in tox.ini in 'victoria' cycle, we should test py38 by default. and remove redundant python env. Change-Id: I6426cc55ee9b6bee96620a8185fbdb39c24a68a8 --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 3a2a307de8..6dd04d5b27 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] minversion = 3.2.0 -envlist = py37,pep8 +envlist = py38,pep8 skipdist = True # Automatic envs (pyXX) will only use the python version appropriate to that # env and ignore basepython inherited from [testenv] if we set From e9bd4ef007153e4f2e2d69f3bcb94eef8e8983c2 Mon Sep 17 00:00:00 2001 From: asarfaty Date: Sun, 16 Aug 2020 09:23:56 +0200 Subject: [PATCH 2242/3095] Remove None valued network quota entries Since the openstack SDK still has the neutron-lbaas entries in the network quota, but those are already deprecated [1], the 'opentack quota show' command shows those as None value. This fix removes those empty deprecated values from the output. [1] https://review.opendev.org/#/c/658494/ Change-Id: I8dbdba2a029ea8e6a268ddf29627e1466a7e3a8a --- openstackclient/common/quota.py | 13 +++++++---- .../tests/unit/common/test_quota.py | 23 +++++++++++++++++++ .../remove-nlbaas-quota-8b38e0c91ab113cb.yaml | 4 ++++ 3 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/remove-nlbaas-quota-8b38e0c91ab113cb.yaml diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index 11de986b8e..643cb4e474 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -161,6 +161,13 @@ def get_volume_quota(self, client, parsed_args): raise return quota._info + def _network_quota_to_dict(self, network_quota): + if type(network_quota) is not dict: + dict_quota = network_quota.to_dict() + else: + dict_quota = network_quota + return {k: v for k, v in dict_quota.items() if v is not None} + def get_network_quota(self, parsed_args): quota_class = ( parsed_args.quota_class if 'quota_class' in parsed_args else False) @@ -174,13 +181,11 @@ def get_network_quota(self, parsed_args): client = self.app.client_manager.network if default: network_quota = client.get_quota_default(project) - if type(network_quota) is not dict: - network_quota = network_quota.to_dict() + network_quota = self._network_quota_to_dict(network_quota) else: network_quota = client.get_quota(project, details=detail) - if type(network_quota) is not dict: - network_quota = network_quota.to_dict() + network_quota = self._network_quota_to_dict(network_quota) if detail: # NOTE(slaweq): Neutron returns values with key "used" but # Nova for example returns same data with key "in_use" diff --git a/openstackclient/tests/unit/common/test_quota.py b/openstackclient/tests/unit/common/test_quota.py index 6504c5b085..8771359cb5 100644 --- a/openstackclient/tests/unit/common/test_quota.py +++ b/openstackclient/tests/unit/common/test_quota.py @@ -1087,3 +1087,26 @@ def test_quota_show_no_project(self): identity_fakes.project_id, details=False ) self.assertNotCalled(self.network.get_quota_default) + + def test_network_quota_show_remove_empty(self): + arglist = [ + self.projects[0].name, + ] + verifylist = [ + ('project', self.projects[0].name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # First check that all regular values are returned + result = self.cmd.get_network_quota(parsed_args) + self.assertEqual(len(network_fakes.QUOTA), len(result)) + + # set 1 of the values to None, and verify it is not returned + orig_get_quota = self.network.get_quota + network_quotas = copy.copy(network_fakes.QUOTA) + network_quotas['healthmonitor'] = None + self.network.get_quota = mock.Mock(return_value=network_quotas) + result = self.cmd.get_network_quota(parsed_args) + self.assertEqual(len(network_fakes.QUOTA) - 1, len(result)) + # Go back to default mock + self.network.get_quota = orig_get_quota diff --git a/releasenotes/notes/remove-nlbaas-quota-8b38e0c91ab113cb.yaml b/releasenotes/notes/remove-nlbaas-quota-8b38e0c91ab113cb.yaml new file mode 100644 index 0000000000..572e215b25 --- /dev/null +++ b/releasenotes/notes/remove-nlbaas-quota-8b38e0c91ab113cb.yaml @@ -0,0 +1,4 @@ +--- +other: + - | + Remove deprecated neutron-lbaas results from ``quota show`` command. From 4b709a2c044ccc7f96836baa6c33a0f800e57be1 Mon Sep 17 00:00:00 2001 From: maaoyu Date: Thu, 24 Sep 2020 17:08:56 +0800 Subject: [PATCH 2243/3095] Remove install unnecessary packages The docs requirements migrated to doc/requirements.txt we need not install things from requirements.txt. Change-Id: I35a367505b2b423c345b05519e4134113cb66648 --- tox.ini | 1 - 1 file changed, 1 deletion(-) diff --git a/tox.ini b/tox.ini index 3a2a307de8..fdff6539e8 100644 --- a/tox.ini +++ b/tox.ini @@ -111,7 +111,6 @@ commands = [testenv:docs] deps = -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} - -r{toxinidir}/requirements.txt -r{toxinidir}/doc/requirements.txt commands = sphinx-build -a -E -W -d doc/build/doctrees -b html doc/source doc/build/html From b77c28d2954a2c5861a3a6a1df29d8de448f7c08 Mon Sep 17 00:00:00 2001 From: zhangbailin Date: Thu, 8 Aug 2019 19:46:30 +0800 Subject: [PATCH 2244/3095] Add server migration list CLI Add ``openstack server migration list`` to fetch server migrations. Part of blueprint add-user-id-field-to-the-migrations-table Change-Id: I15b4a5aca8d0dee59dd293e7b1c7272cdfbeea20 --- .../cli/command-objects/server-migration.rst | 12 + openstackclient/compute/v2/server.py | 161 ++++++ .../tests/unit/compute/v2/fakes.py | 90 ++++ .../tests/unit/compute/v2/test_server.py | 484 ++++++++++++++++++ ...the-migrations-table-299b99ccb1f12a1f.yaml | 5 + setup.cfg | 1 + 6 files changed, 753 insertions(+) create mode 100644 doc/source/cli/command-objects/server-migration.rst create mode 100644 releasenotes/notes/bp-add-user-id-field-to-the-migrations-table-299b99ccb1f12a1f.yaml diff --git a/doc/source/cli/command-objects/server-migration.rst b/doc/source/cli/command-objects/server-migration.rst new file mode 100644 index 0000000000..6e2982cf26 --- /dev/null +++ b/doc/source/cli/command-objects/server-migration.rst @@ -0,0 +1,12 @@ +================ +server migration +================ + +A server migration provides a way to move an instance from one +host to another. There are four types of migration operation +supported: live migration, cold migration, resize and evacuation. + +Compute v2 + +.. autoprogram-cliff:: openstack.compute.v2 + :command: server migration list diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 93e9f966ae..05050d4a7b 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1768,6 +1768,167 @@ def _show_progress(progress): raise SystemExit +class ListMigration(command.Command): + _description = _("""List server migrations.""") + + def get_parser(self, prog_name): + parser = super(ListMigration, self).get_parser(prog_name) + parser.add_argument( + "--server", + metavar="", + dest='server', + default=None, + help=_('Server to show migration details (name or ID).') + ) + parser.add_argument( + "--host", + metavar="", + default=None, + help=_('Fetch migrations for the given host.') + ) + parser.add_argument( + "--status", + metavar="", + default=None, + help=_('Fetch migrations for the given status.') + ) + parser.add_argument( + "--marker", + metavar="", + dest='marker', + default=None, + help=_("The last migration of the previous page; displays list " + "of migrations after 'marker'. Note that the marker is " + "the migration UUID. (Supported with " + "``--os-compute-api-version`` 2.59 or greater.)") + ) + parser.add_argument( + "--limit", + metavar="", + dest='limit', + type=int, + default=None, + help=_("Maximum number of migrations to display. Note that there " + "is a configurable max limit on the server, and the limit " + "that is used will be the minimum of what is requested " + "here and what is configured in the server. " + "(Supported with ``--os-compute-api-version`` 2.59 " + "or greater.)") + ) + parser.add_argument( + '--changes-since', + dest='changes_since', + metavar='', + default=None, + help=_("List only migrations changed later or equal to a certain " + "point of time. The provided time should be an ISO 8061 " + "formatted time, e.g. ``2016-03-04T06:27:59Z``. " + "(Supported with ``--os-compute-api-version`` 2.59 " + "or greater.)") + ) + parser.add_argument( + '--changes-before', + dest='changes_before', + metavar='', + default=None, + help=_("List only migrations changed earlier or equal to a " + "certain point of time. The provided time should be an ISO " + "8061 formatted time, e.g. ``2016-03-04T06:27:59Z``. " + "(Supported with ``--os-compute-api-version`` 2.66 or " + "greater.)") + ) + parser.add_argument( + '--project', + metavar='', + dest='project_id', + default=None, + help=_("Filter the migrations by the given project ID. " + "(Supported with ``--os-compute-api-version`` 2.80 " + "or greater.)"), + ) + parser.add_argument( + '--user', + metavar='', + dest='user_id', + default=None, + help=_("Filter the migrations by the given user ID. " + "(Supported with ``--os-compute-api-version`` 2.80 " + "or greater.)"), + ) + return parser + + def print_migrations(self, parsed_args, compute_client, migrations): + columns = ['Source Node', 'Dest Node', 'Source Compute', + 'Dest Compute', 'Dest Host', 'Status', + 'Server UUID', 'Old Flavor', 'New Flavor', + 'Created At', 'Updated At'] + + # Insert migrations UUID after ID + if compute_client.api_version >= api_versions.APIVersion("2.59"): + columns.insert(0, "UUID") + + # TODO(brinzhang): It also suppports filter migrations by type + # since 2.1. https://review.opendev.org/#/c/675117 supported + # filtering the migrations by 'migration_type' and 'source_compute' + # in novaclient, that will be added in OSC by follow-up. + if compute_client.api_version >= api_versions.APIVersion("2.23"): + columns.insert(0, "Id") + columns.insert(len(columns) - 2, "Type") + + if compute_client.api_version >= api_versions.APIVersion("2.80"): + if parsed_args.project_id: + columns.insert(len(columns) - 2, "Project") + if parsed_args.user_id: + columns.insert(len(columns) - 2, "User") + + columns_header = columns + return (columns_header, (utils.get_item_properties( + mig, columns) for mig in migrations)) + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + + search_opts = { + "host": parsed_args.host, + "server": parsed_args.server, + "status": parsed_args.status, + } + + if (parsed_args.marker or parsed_args.limit or + parsed_args.changes_since): + if compute_client.api_version < api_versions.APIVersion("2.59"): + msg = _("marker, limit and/or changes_since is not supported " + "for --os-compute-api-version less than 2.59") + raise exceptions.CommandError(msg) + if parsed_args.marker: + search_opts['marker'] = parsed_args.marker + if parsed_args.limit: + search_opts['limit'] = parsed_args.limit + if parsed_args.changes_since: + search_opts['changes_since'] = parsed_args.changes_since + + if parsed_args.changes_before: + if compute_client.api_version < api_versions.APIVersion("2.66"): + msg = _("changes_before is not supported for " + "--os-compute-api-version less than 2.66") + raise exceptions.CommandError(msg) + search_opts['changes_before'] = parsed_args.changes_before + + if parsed_args.project_id or parsed_args.user_id: + if compute_client.api_version < api_versions.APIVersion("2.80"): + msg = _("Project and/or user is not supported for " + "--os-compute-api-version less than 2.80") + raise exceptions.CommandError(msg) + if parsed_args.project_id: + search_opts['project_id'] = parsed_args.project_id + if parsed_args.user_id: + search_opts['user_id'] = parsed_args.user_id + + migrations = compute_client.migrations.list(**search_opts) + + return self.print_migrations(parsed_args, compute_client, migrations) + + class PauseServer(command.Command): _description = _("Pause server(s)") diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index 6e12f735d4..2ad489e7cb 100644 --- a/openstackclient/tests/unit/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -14,6 +14,7 @@ # import copy +import random from unittest import mock import uuid @@ -198,6 +199,9 @@ def __init__(self, **kwargs): self.instance_action = mock.Mock() self.instance_action.resource_class = fakes.FakeResource(None, {}) + self.migrations = mock.Mock() + self.migrations.resource_class = fakes.FakeResource(None, {}) + self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] @@ -1539,3 +1543,89 @@ def __init__(self, verb, uri, value, remain, self.remain = remain self.unit = unit self.next_available = next_available + + +class FakeServerMigration(object): + """Fake one or more server migrations.""" + + @staticmethod + def create_one_server_migration(attrs=None, methods=None): + """Create a fake server migration. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :return: + A FakeResource object, with id, type, and so on + """ + attrs = attrs or {} + methods = methods or {} + + # Set default attributes. + migration_info = { + "dest_host": "10.0.2.15", + "status": "migrating", + "type": "migration", + "updated_at": "2017-01-31T08:03:25.000000", + "created_at": "2017-01-31T08:03:21.000000", + "dest_compute": "compute-" + uuid.uuid4().hex, + "id": random.randint(1, 999), + "source_node": "node-" + uuid.uuid4().hex, + "server": uuid.uuid4().hex, + "dest_node": "node-" + uuid.uuid4().hex, + "source_compute": "compute-" + uuid.uuid4().hex, + "uuid": uuid.uuid4().hex, + "old_instance_type_id": uuid.uuid4().hex, + "new_instance_type_id": uuid.uuid4().hex, + "project": uuid.uuid4().hex, + "user": uuid.uuid4().hex + } + + # Overwrite default attributes. + migration_info.update(attrs) + + migration = fakes.FakeResource(info=copy.deepcopy(migration_info), + methods=methods, + loaded=True) + return migration + + @staticmethod + def create_server_migrations(attrs=None, methods=None, count=2): + """Create multiple fake server migrations. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :param int count: + The number of server migrations to fake + :return: + A list of FakeResource objects faking the server migrations + """ + migrations = [] + for i in range(0, count): + migrations.append( + FakeServerMigration.create_one_server_migration( + attrs, methods)) + + return migrations + + @staticmethod + def get_server_migrations(migrations=None, count=2): + """Get an iterable MagicMock object with a list of faked migrations. + + If server migrations list is provided, then initialize the Mock object + with the list. Otherwise create one. + + :param List migrations: + A list of FakeResource objects faking server migrations + :param int count: + The number of server migrations to fake + :return: + An iterable Mock object with side_effect set to a list of faked + server migrations + """ + if migrations is None: + migrations = FakeServerMigration.create_server_migrations(count) + return mock.Mock(side_effect=migrations) diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 7e4c71c50c..deb4345a91 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -3523,6 +3523,490 @@ def test_server_migrate_with_wait_fails(self, mock_wait_for_status): self.assertNotCalled(self.servers_mock.live_migrate) +class TestServerMigration(TestServer): + + def setUp(self): + super(TestServerMigration, self).setUp() + + # Get a shortcut to the compute client ServerManager Mock + self.servers_mock = self.app.client_manager.compute.servers + self.servers_mock.reset_mock() + + self.migrations_mock = ( + self.app.client_manager.compute.migrations) + self.migrations_mock.reset_mock() + + self.server = self.setup_servers_mock(1)[0] + + def setup_servers_mock(self, count): + servers = compute_fakes.FakeServer.create_servers(count=count) + + # This is the return value for utils.find_resource() + self.servers_mock.get = compute_fakes.FakeServer.get_servers(servers) + return servers + + def setup_server_migrations_mock(self, count): + return compute_fakes.FakeServerMigration.create_server_migrations( + count=count) + + +class TestListMigration(TestServerMigration): + """Test fetch all migrations.""" + + MIGRATION_COLUMNS = [ + 'Source Node', 'Dest Node', 'Source Compute', + 'Dest Compute', 'Dest Host', 'Status', 'Server UUID', + 'Old Flavor', 'New Flavor', 'Created At', 'Updated At' + ] + + def setUp(self): + super(TestListMigration, self).setUp() + + self.cmd = server.ListMigration(self.app, None) + self.migrations = self.setup_server_migrations_mock(3) + self.migrations_mock.list.return_value = self.migrations + self.setup_server_migrations_data(self.migrations) + + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.20') + + def setup_server_migrations_data(self, migrations): + self.data = (common_utils.get_item_properties( + s, self.MIGRATION_COLUMNS) for s in migrations) + + def test_server_migraton_list(self): + arglist = [ + '--status', 'migrating' + ] + verifylist = [ + ('status', 'migrating') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'status': 'migrating', + 'host': None, + 'server': None, + } + + self.migrations_mock.list.assert_called_with(**kwargs) + + self.assertEqual(self.MIGRATION_COLUMNS, columns) + self.assertEqual(tuple(self.data), tuple(data)) + + +class TestListMigrationV223(TestListMigration): + """Test fetch all migrations. """ + + MIGRATION_COLUMNS = [ + 'Source Node', 'Dest Node', 'Source Compute', + 'Dest Compute', 'Dest Host', 'Status', 'Server UUID', + 'Old Flavor', 'New Flavor', 'Created At', 'Updated At' + ] + + def setUp(self): + super(TestListMigrationV223, self).setUp() + self.cmd = server.ListMigration(self.app, None) + self.migrations = self.setup_server_migrations_mock(3) + self.migrations_mock.list.return_value = self.migrations + self.setup_server_migrations_data(self.migrations) + + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.23') + + def test_server_migraton_list(self): + arglist = [ + '--status', 'migrating' + ] + verifylist = [ + ('status', 'migrating') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'status': 'migrating', + 'host': None, + 'server': None, + } + + self.migrations_mock.list.assert_called_with(**kwargs) + + self.MIGRATION_COLUMNS.insert(0, "Id") + self.MIGRATION_COLUMNS.insert( + len(self.MIGRATION_COLUMNS) - 2, 'Type') + self.assertEqual(self.MIGRATION_COLUMNS, columns) + self.assertEqual(tuple(self.data), tuple(data)) + + +class TestListMigrationV259(TestListMigration): + """Test fetch all migrations. """ + + MIGRATION_COLUMNS = [ + 'Id', 'UUID', 'Source Node', 'Dest Node', 'Source Compute', + 'Dest Compute', 'Dest Host', 'Status', 'Server UUID', + 'Old Flavor', 'New Flavor', 'Type', 'Created At', 'Updated At' + ] + + def setUp(self): + super(TestListMigrationV259, self).setUp() + self.cmd = server.ListMigration(self.app, None) + self.migrations = self.setup_server_migrations_mock(3) + self.migrations_mock.list.return_value = self.migrations + self.setup_server_migrations_data(self.migrations) + + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.59') + + def test_server_migraton_list(self): + arglist = [ + '--status', 'migrating', + '--limit', '1', + '--marker', 'test_kp', + '--changes-since', '2019-08-09T08:03:25Z' + ] + verifylist = [ + ('status', 'migrating'), + ('limit', 1), + ('marker', 'test_kp'), + ('changes_since', '2019-08-09T08:03:25Z') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'status': 'migrating', + 'limit': 1, + 'marker': 'test_kp', + 'host': None, + 'server': None, + 'changes_since': '2019-08-09T08:03:25Z', + } + + self.migrations_mock.list.assert_called_with(**kwargs) + + self.assertEqual(self.MIGRATION_COLUMNS, columns) + self.assertEqual(tuple(self.data), tuple(data)) + + def test_server_migraton_list_with_limit_pre_v259(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.58') + arglist = [ + '--status', 'migrating', + '--limit', '1' + ] + verifylist = [ + ('status', 'migrating'), + ('limit', 1) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + + def test_server_migraton_list_with_marker_pre_v259(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.58') + arglist = [ + '--status', 'migrating', + '--marker', 'test_kp' + ] + verifylist = [ + ('status', 'migrating'), + ('marker', 'test_kp') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + + def test_server_migraton_list_with_changes_since_pre_v259(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.58') + arglist = [ + '--status', 'migrating', + '--changes-since', '2019-08-09T08:03:25Z' + ] + verifylist = [ + ('status', 'migrating'), + ('changes_since', '2019-08-09T08:03:25Z') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + + +class TestListMigrationV266(TestListMigration): + """Test fetch all migrations by changes-before. """ + + MIGRATION_COLUMNS = [ + 'Id', 'UUID', 'Source Node', 'Dest Node', 'Source Compute', + 'Dest Compute', 'Dest Host', 'Status', 'Server UUID', + 'Old Flavor', 'New Flavor', 'Type', 'Created At', 'Updated At' + ] + + def setUp(self): + super(TestListMigrationV266, self).setUp() + self.cmd = server.ListMigration(self.app, None) + self.migrations = self.setup_server_migrations_mock(3) + self.migrations_mock.list.return_value = self.migrations + self.setup_server_migrations_data(self.migrations) + + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.66') + + def test_server_migraton_list_with_changes_before(self): + arglist = [ + '--status', 'migrating', + '--limit', '1', + '--marker', 'test_kp', + '--changes-since', '2019-08-07T08:03:25Z', + '--changes-before', '2019-08-09T08:03:25Z' + ] + verifylist = [ + ('status', 'migrating'), + ('limit', 1), + ('marker', 'test_kp'), + ('changes_since', '2019-08-07T08:03:25Z'), + ('changes_before', '2019-08-09T08:03:25Z') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'status': 'migrating', + 'limit': 1, + 'marker': 'test_kp', + 'host': None, + 'server': None, + 'changes_since': '2019-08-07T08:03:25Z', + 'changes_before': '2019-08-09T08:03:25Z', + } + + self.migrations_mock.list.assert_called_with(**kwargs) + + self.assertEqual(self.MIGRATION_COLUMNS, columns) + self.assertEqual(tuple(self.data), tuple(data)) + + def test_server_migraton_list_with_changes_before_pre_v266(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.65') + arglist = [ + '--status', 'migrating', + '--changes-before', '2019-08-09T08:03:25Z' + ] + verifylist = [ + ('status', 'migrating'), + ('changes_before', '2019-08-09T08:03:25Z') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + + +class TestListMigrationV280(TestListMigration): + """Test fetch all migrations by user-id and/or project-id. """ + + MIGRATION_COLUMNS = [ + 'Id', 'UUID', 'Source Node', 'Dest Node', 'Source Compute', + 'Dest Compute', 'Dest Host', 'Status', 'Server UUID', + 'Old Flavor', 'New Flavor', 'Type', 'Created At', 'Updated At' + ] + + def setUp(self): + super(TestListMigrationV280, self).setUp() + self.cmd = server.ListMigration(self.app, None) + self.migrations = self.setup_server_migrations_mock(3) + self.migrations_mock.list.return_value = self.migrations + self.setup_server_migrations_data(self.migrations) + + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.80') + + def test_server_migraton_list_with_project(self): + arglist = [ + '--status', 'migrating', + '--limit', '1', + '--marker', 'test_kp', + '--changes-since', '2019-08-07T08:03:25Z', + '--changes-before', '2019-08-09T08:03:25Z', + '--project', '0c2accde-644a-45fa-8c10-e76debc7fbc3' + ] + verifylist = [ + ('status', 'migrating'), + ('limit', 1), + ('marker', 'test_kp'), + ('changes_since', '2019-08-07T08:03:25Z'), + ('changes_before', '2019-08-09T08:03:25Z'), + ('project_id', '0c2accde-644a-45fa-8c10-e76debc7fbc3') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'status': 'migrating', + 'limit': 1, + 'marker': 'test_kp', + 'host': None, + 'server': None, + 'project_id': '0c2accde-644a-45fa-8c10-e76debc7fbc3', + 'changes_since': '2019-08-07T08:03:25Z', + 'changes_before': "2019-08-09T08:03:25Z", + } + + self.migrations_mock.list.assert_called_with(**kwargs) + + self.MIGRATION_COLUMNS.insert( + len(self.MIGRATION_COLUMNS) - 2, "Project") + self.assertEqual(self.MIGRATION_COLUMNS, columns) + self.assertEqual(tuple(self.data), tuple(data)) + # Clean up global variables MIGRATION_COLUMNS + self.MIGRATION_COLUMNS.remove('Project') + + def test_get_migrations_with_project_pre_v280(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.79') + arglist = [ + '--status', 'migrating', + '--changes-before', '2019-08-09T08:03:25Z', + '--project', '0c2accde-644a-45fa-8c10-e76debc7fbc3' + ] + verifylist = [ + ('status', 'migrating'), + ('changes_before', '2019-08-09T08:03:25Z'), + ('project_id', '0c2accde-644a-45fa-8c10-e76debc7fbc3') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + + def test_server_migraton_list_with_user(self): + arglist = [ + '--status', 'migrating', + '--limit', '1', + '--marker', 'test_kp', + '--changes-since', '2019-08-07T08:03:25Z', + '--changes-before', '2019-08-09T08:03:25Z', + '--user', 'dd214878-ca12-40fb-b035-fa7d2c1e86d6' + ] + verifylist = [ + ('status', 'migrating'), + ('limit', 1), + ('marker', 'test_kp'), + ('changes_since', '2019-08-07T08:03:25Z'), + ('changes_before', '2019-08-09T08:03:25Z'), + ('user_id', 'dd214878-ca12-40fb-b035-fa7d2c1e86d6') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'status': 'migrating', + 'limit': 1, + 'marker': 'test_kp', + 'host': None, + 'server': None, + 'user_id': 'dd214878-ca12-40fb-b035-fa7d2c1e86d6', + 'changes_since': '2019-08-07T08:03:25Z', + 'changes_before': "2019-08-09T08:03:25Z", + } + + self.migrations_mock.list.assert_called_with(**kwargs) + + self.MIGRATION_COLUMNS.insert( + len(self.MIGRATION_COLUMNS) - 2, "User") + self.assertEqual(self.MIGRATION_COLUMNS, columns) + self.assertEqual(tuple(self.data), tuple(data)) + # Clean up global variables MIGRATION_COLUMNS + self.MIGRATION_COLUMNS.remove('User') + + def test_get_migrations_with_user_pre_v280(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.79') + arglist = [ + '--status', 'migrating', + '--changes-before', '2019-08-09T08:03:25Z', + '--user', 'dd214878-ca12-40fb-b035-fa7d2c1e86d6' + ] + verifylist = [ + ('status', 'migrating'), + ('changes_before', '2019-08-09T08:03:25Z'), + ('user_id', 'dd214878-ca12-40fb-b035-fa7d2c1e86d6') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + + def test_server_migraton_list_with_project_and_user(self): + arglist = [ + '--status', 'migrating', + '--limit', '1', + '--changes-since', '2019-08-07T08:03:25Z', + '--changes-before', '2019-08-09T08:03:25Z', + '--project', '0c2accde-644a-45fa-8c10-e76debc7fbc3', + '--user', 'dd214878-ca12-40fb-b035-fa7d2c1e86d6' + ] + verifylist = [ + ('status', 'migrating'), + ('limit', 1), + ('changes_since', '2019-08-07T08:03:25Z'), + ('changes_before', '2019-08-09T08:03:25Z'), + ('project_id', '0c2accde-644a-45fa-8c10-e76debc7fbc3'), + ('user_id', 'dd214878-ca12-40fb-b035-fa7d2c1e86d6') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'status': 'migrating', + 'limit': 1, + 'host': None, + 'server': None, + 'project_id': '0c2accde-644a-45fa-8c10-e76debc7fbc3', + 'user_id': 'dd214878-ca12-40fb-b035-fa7d2c1e86d6', + 'changes_since': '2019-08-07T08:03:25Z', + 'changes_before': "2019-08-09T08:03:25Z", + } + + self.migrations_mock.list.assert_called_with(**kwargs) + + self.MIGRATION_COLUMNS.insert( + len(self.MIGRATION_COLUMNS) - 2, "Project") + self.MIGRATION_COLUMNS.insert( + len(self.MIGRATION_COLUMNS) - 2, "User") + self.assertEqual(self.MIGRATION_COLUMNS, columns) + self.assertEqual(tuple(self.data), tuple(data)) + # Clean up global variables MIGRATION_COLUMNS + self.MIGRATION_COLUMNS.remove('Project') + self.MIGRATION_COLUMNS.remove('User') + + def test_get_migrations_with_project_and_user_pre_v280(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.79') + arglist = [ + '--status', 'migrating', + '--changes-before', '2019-08-09T08:03:25Z', + '--project', '0c2accde-644a-45fa-8c10-e76debc7fbc3', + '--user', 'dd214878-ca12-40fb-b035-fa7d2c1e86d6' + ] + verifylist = [ + ('status', 'migrating'), + ('changes_before', '2019-08-09T08:03:25Z'), + ('project_id', '0c2accde-644a-45fa-8c10-e76debc7fbc3'), + ('user_id', 'dd214878-ca12-40fb-b035-fa7d2c1e86d6') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + + class TestServerPause(TestServer): def setUp(self): diff --git a/releasenotes/notes/bp-add-user-id-field-to-the-migrations-table-299b99ccb1f12a1f.yaml b/releasenotes/notes/bp-add-user-id-field-to-the-migrations-table-299b99ccb1f12a1f.yaml new file mode 100644 index 0000000000..766cd0bff2 --- /dev/null +++ b/releasenotes/notes/bp-add-user-id-field-to-the-migrations-table-299b99ccb1f12a1f.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``server migration list`` command. This command allows + users to list the status of ongoing server migrations. diff --git a/setup.cfg b/setup.cfg index 1d9d6df66d..6cf18cc29b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -108,6 +108,7 @@ openstack.compute.v2 = server_migrate = openstackclient.compute.v2.server:MigrateServer server_migrate_confirm = openstackclient.compute.v2.server:MigrateConfirm server_migrate_revert = openstackclient.compute.v2.server:MigrateRevert + server_migration_list = openstackclient.compute.v2.server:ListMigration server_pause = openstackclient.compute.v2.server:PauseServer server_reboot = openstackclient.compute.v2.server:RebootServer server_rebuild = openstackclient.compute.v2.server:RebuildServer From 63b46ac5ec06378a106ed1368f588eef9d8aed7a Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Fri, 2 Oct 2020 20:49:41 +0000 Subject: [PATCH 2245/3095] Update master for stable/victoria Add file to the reno documentation build to show release notes for stable/victoria. Use pbr instruction to increment the minor version number automatically so that master versions are higher than the versions on stable/victoria. Change-Id: I6c1a9cdff90f7073082fc057f0f11b184de5dc32 Sem-Ver: feature --- releasenotes/source/index.rst | 1 + releasenotes/source/victoria.rst | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 releasenotes/source/victoria.rst diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index 889eeb0c5c..8c276de2d8 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,6 +6,7 @@ OpenStackClient Release Notes :maxdepth: 1 unreleased + victoria ussuri train stein diff --git a/releasenotes/source/victoria.rst b/releasenotes/source/victoria.rst new file mode 100644 index 0000000000..4efc7b6f3b --- /dev/null +++ b/releasenotes/source/victoria.rst @@ -0,0 +1,6 @@ +============================= +Victoria Series Release Notes +============================= + +.. release-notes:: + :branch: stable/victoria From 098a3fe2dea70eb16f13b717d62f51a4c890891d Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Fri, 2 Oct 2020 20:49:43 +0000 Subject: [PATCH 2246/3095] Add Python3 wallaby unit tests This is an automatically generated patch to ensure unit testing is in place for all the of the tested runtimes for wallaby. See also the PTI in governance [1]. [1]: https://governance.openstack.org/tc/reference/project-testing-interface.html Change-Id: I27a03e0b51aa7ffe3dcf82ad9bedd9109b016aa1 --- .zuul.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.zuul.yaml b/.zuul.yaml index c04f3446d5..fdc212ae8f 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -221,7 +221,7 @@ - osc-tox-unit-tips - openstack-cover-jobs - openstack-lower-constraints-jobs - - openstack-python3-victoria-jobs + - openstack-python3-wallaby-jobs - publish-openstack-docs-pti - check-requirements - release-notes-jobs-python3 From 17678c9bd6f3f4c5b9e7b5ceb27c2bd12493fba4 Mon Sep 17 00:00:00 2001 From: Sam Morrison Date: Tue, 6 Oct 2020 14:03:19 +1100 Subject: [PATCH 2247/3095] Restore behavior of image create with same name. With 60e7c51df4cf061ebbb435a959ad63c7d3a296bf the behaviour of `openstack image create` changed so that you can't create an image with the same name. This patch restores the previous functionality. Story: 2008229 Task: 41069 Change-Id: Ia0f4920371a918e94d1ccf9fcfcbf90ff885a455 --- openstackclient/image/v2/image.py | 2 +- openstackclient/tests/unit/image/v2/test_image.py | 6 +++++- .../restore-create-image-duplicates-92e06f64038b120c.yaml | 6 ++++++ 3 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/restore-create-image-duplicates-92e06f64038b120c.yaml diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 029f57a3b1..4f3e9d0bcf 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -354,7 +354,7 @@ def take_action(self, parsed_args): # Build an attribute dict from the parsed args, only include # attributes that were actually set on the command line - kwargs = {} + kwargs = {'allow_duplicates': True} copy_attrs = ('name', 'id', 'container_format', 'disk_format', 'min_disk', 'min_ram', 'tags', 'visibility') diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py index 310f6b7636..b094817efe 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -100,6 +100,7 @@ def test_image_reserve_no_options(self, raw_input): # ImageManager.create(name=, **) self.client.create_image.assert_called_with( name=self.new_image.name, + allow_duplicates=True, container_format=image.DEFAULT_CONTAINER_FORMAT, disk_format=image.DEFAULT_DISK_FORMAT, ) @@ -152,6 +153,7 @@ def test_image_reserve_options(self, raw_input): # ImageManager.create(name=, **) self.client.create_image.assert_called_with( name=self.new_image.name, + allow_duplicates=True, container_format='ovf', disk_format='ami', min_disk=10, @@ -239,6 +241,7 @@ def test_image_create_file(self): # ImageManager.create(name=, **) self.client.create_image.assert_called_with( name=self.new_image.name, + allow_duplicates=True, container_format=image.DEFAULT_CONTAINER_FORMAT, disk_format=image.DEFAULT_DISK_FORMAT, is_protected=self.new_image.is_protected, @@ -246,7 +249,7 @@ def test_image_create_file(self): Alpha='1', Beta='2', tags=self.new_image.tags, - filename=imagefile.name + filename=imagefile.name, ) self.assertEqual( @@ -288,6 +291,7 @@ def test_image_create_import(self, raw_input): # ImageManager.create(name=, **) self.client.create_image.assert_called_with( name=self.new_image.name, + allow_duplicates=True, container_format=image.DEFAULT_CONTAINER_FORMAT, disk_format=image.DEFAULT_DISK_FORMAT, use_import=True diff --git a/releasenotes/notes/restore-create-image-duplicates-92e06f64038b120c.yaml b/releasenotes/notes/restore-create-image-duplicates-92e06f64038b120c.yaml new file mode 100644 index 0000000000..7f36e29c41 --- /dev/null +++ b/releasenotes/notes/restore-create-image-duplicates-92e06f64038b120c.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Fixes default behaviour of `openstack image create` in allowing images + with the same name. In version 5.2.0 this changed to not allow + duplicates by default. From f50bd408666df161603f9ce20a5d00428a13b2ef Mon Sep 17 00:00:00 2001 From: pedh Date: Tue, 18 Aug 2020 20:28:27 +0800 Subject: [PATCH 2248/3095] Fix: port attribute name propagate_uplink_status Change the incorrect port attribute name "uplink_status_propagation" to "propagate_uplink_status". Change-Id: Icd7c49af8d988a6e3a52a58c784bd701b2d36faf Closes-Bug: #1891873 --- openstackclient/tests/unit/network/v2/fakes.py | 6 +++--- openstackclient/tests/unit/network/v2/test_port.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index 3df4042cba..74001375a4 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -642,7 +642,7 @@ def create_one_port(attrs=None): 'qos_network_policy_id': 'qos-policy-id-' + uuid.uuid4().hex, 'qos_policy_id': 'qos-policy-id-' + uuid.uuid4().hex, 'tags': [], - 'uplink_status_propagation': False, + 'propagate_uplink_status': False, } # Overwrite default attributes. @@ -662,8 +662,8 @@ def create_one_port(attrs=None): port.project_id = port_attrs['tenant_id'] port.security_group_ids = port_attrs['security_group_ids'] port.qos_policy_id = port_attrs['qos_policy_id'] - port.uplink_status_propagation = port_attrs[ - 'uplink_status_propagation'] + port.propagate_uplink_status = port_attrs[ + 'propagate_uplink_status'] return port diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index f729b552ff..d8889ae558 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -62,12 +62,12 @@ def _get_common_cols_data(fake_port): 'numa_affinity_policy', 'port_security_enabled', 'project_id', + 'propagate_uplink_status', 'qos_network_policy_id', 'qos_policy_id', 'security_group_ids', 'status', 'tags', - 'uplink_status_propagation', ) data = ( @@ -94,12 +94,12 @@ def _get_common_cols_data(fake_port): fake_port.numa_affinity_policy, fake_port.port_security_enabled, fake_port.project_id, + fake_port.propagate_uplink_status, fake_port.qos_network_policy_id, fake_port.qos_policy_id, format_columns.ListColumn(fake_port.security_group_ids), fake_port.status, format_columns.ListColumn(fake_port.tags), - fake_port.uplink_status_propagation, ) return columns, data From 74db8dd65d35b326d3fa1c680b04a668a3f66bdc Mon Sep 17 00:00:00 2001 From: Artem Goncharov Date: Wed, 9 Sep 2020 19:01:27 +0200 Subject: [PATCH 2249/3095] Switch openstack console log show operation to use OpenStackSDK A short switch onto SDK for fetching console logs of the server Change-Id: I3f750ea4f13a4e72272aa67ea4506bd7182b13f9 --- openstackclient/compute/v2/console.py | 19 +++-- .../tests/unit/compute/v2/test_console.py | 79 +++++++++++++++++++ ...h-console-log-to-sdk-6ee92b7833364d3d.yaml | 4 + 3 files changed, 92 insertions(+), 10 deletions(-) create mode 100644 releasenotes/notes/switch-console-log-to-sdk-6ee92b7833364d3d.yaml diff --git a/openstackclient/compute/v2/console.py b/openstackclient/compute/v2/console.py index 110b21b81e..f0abaf4cf4 100644 --- a/openstackclient/compute/v2/console.py +++ b/openstackclient/compute/v2/console.py @@ -44,19 +44,18 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute + compute_client = self.app.client_manager.sdk_connection.compute - server = utils.find_resource( - compute_client.servers, - parsed_args.server, + server = compute_client.find_server( + name_or_id=parsed_args.server, + ignore_missing=False ) - length = parsed_args.lines - if length: - # NOTE(dtroyer): get_console_output() appears to shortchange the - # output by one line - length += 1 - data = server.get_console_output(length=length) + output = compute_client.get_server_console_output( + server.id, length=parsed_args.lines) + data = None + if output: + data = output.get('output', None) if data and data[-1] != '\n': data += '\n' diff --git a/openstackclient/tests/unit/compute/v2/test_console.py b/openstackclient/tests/unit/compute/v2/test_console.py index 99a14f048d..1c6d658bc1 100644 --- a/openstackclient/tests/unit/compute/v2/test_console.py +++ b/openstackclient/tests/unit/compute/v2/test_console.py @@ -17,16 +17,95 @@ from openstackclient.compute.v2 import console from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes +from openstackclient.tests.unit import utils class TestConsole(compute_fakes.TestComputev2): def setUp(self): super(TestConsole, self).setUp() + + # SDK mock + self.app.client_manager.sdk_connection = mock.Mock() + self.app.client_manager.sdk_connection.compute = mock.Mock() + self.sdk_client = self.app.client_manager.sdk_connection.compute + self.sdk_client.find_server = mock.Mock() + self.sdk_client.get_server_console_output = mock.Mock() + self.servers_mock = self.app.client_manager.compute.servers self.servers_mock.reset_mock() +class TestConsoleLog(TestConsole): + _server = compute_fakes.FakeServer.create_one_server() + + def setUp(self): + super(TestConsoleLog, self).setUp() + + self.sdk_client.find_server.return_value = self._server + + self.cmd = console.ShowConsoleLog(self.app, None) + + def test_show_no_args(self): + arglist = [ + ] + verifylist = [ + ] + self.assertRaises(utils.ParserException, + self.check_parser, + self.cmd, + arglist, + verifylist) + + def test_show(self): + arglist = [ + 'fake_server' + ] + verifylist = [ + ('server', 'fake_server') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + output = { + 'output': '1st line\n2nd line\n' + } + self.sdk_client.get_server_console_output.return_value = output + self.cmd.take_action(parsed_args) + + self.sdk_client.find_server.assert_called_with( + name_or_id='fake_server', ignore_missing=False) + self.sdk_client.get_server_console_output.assert_called_with( + self._server.id, + length=None + ) + stdout = self.app.stdout.content + self.assertEqual(stdout[0], output['output']) + + def test_show_lines(self): + arglist = [ + 'fake_server', + '--lines', '15' + ] + verifylist = [ + ('server', 'fake_server'), + ('lines', 15) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + output = { + 'output': '1st line\n2nd line' + } + self.sdk_client.get_server_console_output.return_value = output + self.cmd.take_action(parsed_args) + + self.sdk_client.find_server.assert_called_with( + name_or_id='fake_server', ignore_missing=False) + self.sdk_client.get_server_console_output.assert_called_with( + self._server.id, + length=15 + ) + + class TestConsoleUrlShow(TestConsole): def setUp(self): diff --git a/releasenotes/notes/switch-console-log-to-sdk-6ee92b7833364d3d.yaml b/releasenotes/notes/switch-console-log-to-sdk-6ee92b7833364d3d.yaml new file mode 100644 index 0000000000..2bd0bab49b --- /dev/null +++ b/releasenotes/notes/switch-console-log-to-sdk-6ee92b7833364d3d.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Switch console logs operation to use SDK From c2df9215e19752714e83fcad82c8ae3708f85d7a Mon Sep 17 00:00:00 2001 From: songwenping Date: Tue, 6 Oct 2020 14:26:27 +0800 Subject: [PATCH 2250/3095] Remove usage of six With python3.x, classes can use 'metaclass=' instead of 'six.add_metaclass', 'six.iteritems' and 'six.iterkeys' can be replaced by 'items' and 'keys', 'six.moves.urllib.parse' can be replaced by 'urllib.parse', 'six.StringIO' and 'six.moves.cStringIO' can be replaced by 'io.StringIO', 'six.text_type' and 'six.string_type' are just 'str'. Change-Id: I84848c0bf8ab3c36dd821141191e2725e4e3b58b --- doc/source/contributor/developing.rst | 1 - lower-constraints.txt | 1 - openstackclient/api/object_store_v1.py | 2 +- openstackclient/common/sdk_utils.py | 4 +--- openstackclient/compute/v2/server.py | 5 ++--- openstackclient/identity/v3/access_rule.py | 3 +-- openstackclient/network/common.py | 22 +++++++++---------- openstackclient/shell.py | 8 ------- .../tests/unit/compute/v2/test_server.py | 15 ++++++------- .../tests/unit/compute/v2/test_service.py | 5 ++--- openstackclient/tests/unit/fakes.py | 3 +-- openstackclient/tests/unit/object/v1/fakes.py | 3 +-- .../tests/unit/object/v1/test_object_all.py | 6 ++--- openstackclient/tests/unit/utils.py | 2 +- requirements.txt | 1 - 15 files changed, 30 insertions(+), 51 deletions(-) diff --git a/doc/source/contributor/developing.rst b/doc/source/contributor/developing.rst index 35c7c7b91f..a319849396 100644 --- a/doc/source/contributor/developing.rst +++ b/doc/source/contributor/developing.rst @@ -203,7 +203,6 @@ Example from osc_lib.api import auth from osc_lib import utils - import six from openstackclient import shell from openstackclient.tests import utils diff --git a/lower-constraints.txt b/lower-constraints.txt index 403ba4e05c..2fa6586e08 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -119,7 +119,6 @@ rfc3986==0.3.1 Routes==2.3.1 rsd-lib==0.1.0 simplejson==3.5.1 -six==1.10.0 smmap==0.9.0 statsd==3.2.1 stestr==1.0.0 diff --git a/openstackclient/api/object_store_v1.py b/openstackclient/api/object_store_v1.py index 8092abd06a..67c7923023 100644 --- a/openstackclient/api/object_store_v1.py +++ b/openstackclient/api/object_store_v1.py @@ -17,9 +17,9 @@ import logging import os import sys +import urllib from osc_lib import utils -from six.moves import urllib from openstackclient.api import api diff --git a/openstackclient/common/sdk_utils.py b/openstackclient/common/sdk_utils.py index 9f0856175d..af9c74f944 100644 --- a/openstackclient/common/sdk_utils.py +++ b/openstackclient/common/sdk_utils.py @@ -10,8 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -import six - def get_osc_show_columns_for_sdk_resource( sdk_resource, @@ -44,7 +42,7 @@ def get_osc_show_columns_for_sdk_resource( for col_name in invisible_columns: if col_name in display_columns: display_columns.remove(col_name) - for sdk_attr, osc_attr in six.iteritems(osc_column_map): + for sdk_attr, osc_attr in osc_column_map.items(): if sdk_attr in display_columns: attr_map[osc_attr] = sdk_attr display_columns.remove(sdk_attr) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 1d1fc74165..756903f7a1 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -30,7 +30,6 @@ from osc_lib import exceptions from osc_lib import utils from oslo_utils import timeutils -import six from openstackclient.i18n import _ from openstackclient.identity import common as identity_common @@ -97,7 +96,7 @@ def _get_ip_address(addresses, address_type, ip_address_family): for network in addresses: for addy in addresses[network]: # Case where it is list of strings - if isinstance(addy, six.string_types): + if isinstance(addy, str): if new_address_type == 'fixed': return addresses[network][0] else: @@ -876,7 +875,7 @@ def _match_image(image_api, wanted_properties): boot_args = [parsed_args.server_name, image, flavor] # Handle block device by device name order, like: vdb -> vdc -> vdd - for dev_name in sorted(six.iterkeys(parsed_args.block_device_mapping)): + for dev_name in sorted(parsed_args.block_device_mapping): dev_map = parsed_args.block_device_mapping[dev_name] dev_map = dev_map.split(':') if dev_map[0]: diff --git a/openstackclient/identity/v3/access_rule.py b/openstackclient/identity/v3/access_rule.py index 65e78be1d0..ffda04f9e5 100644 --- a/openstackclient/identity/v3/access_rule.py +++ b/openstackclient/identity/v3/access_rule.py @@ -20,7 +20,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -import six from openstackclient.i18n import _ from openstackclient.identity import common @@ -115,4 +114,4 @@ def take_action(self, parsed_args): access_rule._info.pop('links', None) - return zip(*sorted(six.iteritems(access_rule._info))) + return zip(*sorted(access_rule._info.items())) diff --git a/openstackclient/network/common.py b/openstackclient/network/common.py index e68628b3f1..47ffbe77a6 100644 --- a/openstackclient/network/common.py +++ b/openstackclient/network/common.py @@ -18,7 +18,6 @@ import openstack.exceptions from osc_lib.command import command from osc_lib import exceptions -import six from openstackclient.i18n import _ @@ -54,8 +53,7 @@ def check_missing_extension_if_error(client_manager, attrs): raise -@six.add_metaclass(abc.ABCMeta) -class NetDetectionMixin(object): +class NetDetectionMixin(metaclass=abc.ABCMeta): """Convenience methods for nova-network vs. neutron decisions. A live environment detects which network type it is running and creates its @@ -166,8 +164,8 @@ def take_action_compute(self, client, parsed_args): pass -@six.add_metaclass(abc.ABCMeta) -class NetworkAndComputeCommand(NetDetectionMixin, command.Command): +class NetworkAndComputeCommand(NetDetectionMixin, command.Command, + metaclass=abc.ABCMeta): """Network and Compute Command Command class for commands that support implementation via @@ -178,8 +176,8 @@ class NetworkAndComputeCommand(NetDetectionMixin, command.Command): pass -@six.add_metaclass(abc.ABCMeta) -class NetworkAndComputeDelete(NetworkAndComputeCommand): +class NetworkAndComputeDelete(NetworkAndComputeCommand, + metaclass=abc.ABCMeta): """Network and Compute Delete Delete class for commands that support implementation via @@ -222,8 +220,8 @@ def take_action(self, parsed_args): raise exceptions.CommandError(msg) -@six.add_metaclass(abc.ABCMeta) -class NetworkAndComputeLister(NetDetectionMixin, command.Lister): +class NetworkAndComputeLister(NetDetectionMixin, command.Lister, + metaclass=abc.ABCMeta): """Network and Compute Lister Lister class for commands that support implementation via @@ -234,8 +232,8 @@ class NetworkAndComputeLister(NetDetectionMixin, command.Lister): pass -@six.add_metaclass(abc.ABCMeta) -class NetworkAndComputeShowOne(NetDetectionMixin, command.ShowOne): +class NetworkAndComputeShowOne(NetDetectionMixin, command.ShowOne, + metaclass=abc.ABCMeta): """Network and Compute ShowOne ShowOne class for commands that support implementation via @@ -255,5 +253,5 @@ def take_action(self, parsed_args): except openstack.exceptions.HttpException as exc: msg = _("Error while executing command: %s") % exc.message if exc.details: - msg += ", " + six.text_type(exc.details) + msg += ", " + str(exc.details) raise exceptions.CommandError(msg) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 755af24d2e..bc88e1f1e2 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -16,13 +16,11 @@ """Command-line interface to the OpenStack APIs""" -import locale import sys from osc_lib.api import auth from osc_lib.command import commandmanager from osc_lib import shell -import six import openstackclient from openstackclient.common import clientmanager @@ -143,12 +141,6 @@ def initialize_app(self, argv): def main(argv=None): if argv is None: argv = sys.argv[1:] - if six.PY2: - # Emulate Py3, decode argv into Unicode based on locale so that - # commands always see arguments as text instead of binary data - encoding = locale.getpreferredencoding() - if encoding: - argv = map(lambda arg: arg.decode(encoding), argv) return OpenStackShell().run(argv) diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 02bb406c11..2445e1438b 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -24,7 +24,6 @@ from osc_lib import exceptions from osc_lib import utils as common_utils from oslo_utils import timeutils -import six from openstackclient.compute.v2 import server from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes @@ -1907,7 +1906,7 @@ def test_server_create_volume_boot_from_volume_conflict(self): self.cmd.take_action, parsed_args) # Assert it is the error we expect. self.assertIn('--volume is not allowed with --boot-from-volume', - six.text_type(ex)) + str(ex)) def test_server_create_image_property(self): arglist = [ @@ -3288,7 +3287,7 @@ def test_server_migrate_with_host_pre_2_56(self): # Make sure it's the error we expect. self.assertIn('--os-compute-api-version 2.56 or greater is required ' 'to use --host without --live-migration.', - six.text_type(ex)) + str(ex)) self.servers_mock.get.assert_called_with(self.server.id) self.assertNotCalled(self.servers_mock.live_migrate) @@ -3323,7 +3322,7 @@ def test_server_live_migrate(self): # A warning should have been logged for using --live. mock_warning.assert_called_once() self.assertIn('The --live option has been deprecated.', - six.text_type(mock_warning.call_args[0][0])) + str(mock_warning.call_args[0][0])) def test_server_live_migrate_host_pre_2_30(self): # Tests that the --host option is not supported for --live-migration @@ -3346,7 +3345,7 @@ def test_server_live_migrate_host_pre_2_30(self): # Make sure it's the error we expect. self.assertIn('--os-compute-api-version 2.30 or greater is required ' - 'when using --host', six.text_type(ex)) + 'when using --host', str(ex)) self.servers_mock.get.assert_called_with(self.server.id) self.assertNotCalled(self.servers_mock.live_migrate) @@ -3436,7 +3435,7 @@ def test_server_live_migrate_without_host_override_live(self): # A warning should have been logged for using --live. mock_warning.assert_called_once() self.assertIn('The --live option has been deprecated.', - six.text_type(mock_warning.call_args[0][0])) + str(mock_warning.call_args[0][0])) def test_server_live_migrate_live_and_host_mutex(self): # Tests specifying both the --live and --host options which are in a @@ -4352,7 +4351,7 @@ def test_server_resize_confirm(self): # A warning should have been logged for using --confirm. mock_warning.assert_called_once() self.assertIn('The --confirm option has been deprecated.', - six.text_type(mock_warning.call_args[0][0])) + str(mock_warning.call_args[0][0])) def test_server_resize_revert(self): arglist = [ @@ -4377,7 +4376,7 @@ def test_server_resize_revert(self): # A warning should have been logged for using --revert. mock_warning.assert_called_once() self.assertIn('The --revert option has been deprecated.', - six.text_type(mock_warning.call_args[0][0])) + str(mock_warning.call_args[0][0])) @mock.patch.object(common_utils, 'wait_for_status', return_value=True) def test_server_resize_with_wait_ok(self, mock_wait_for_status): diff --git a/openstackclient/tests/unit/compute/v2/test_service.py b/openstackclient/tests/unit/compute/v2/test_service.py index 7a03683364..87e54747f1 100644 --- a/openstackclient/tests/unit/compute/v2/test_service.py +++ b/openstackclient/tests/unit/compute/v2/test_service.py @@ -18,7 +18,6 @@ from novaclient import api_versions from osc_lib import exceptions -import six from openstackclient.compute.v2 import service from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes @@ -502,7 +501,7 @@ def test_service_set_find_service_by_host_and_binary_no_results(self): self.cmd._find_service_by_host_and_binary, self.service_mock, 'fake-host', 'nova-compute') self.assertIn('Compute service for host "fake-host" and binary ' - '"nova-compute" not found.', six.text_type(ex)) + '"nova-compute" not found.', str(ex)) def test_service_set_find_service_by_host_and_binary_many_results(self): # Tests that more than one compute service is found by host and binary. @@ -512,4 +511,4 @@ def test_service_set_find_service_by_host_and_binary_many_results(self): self.service_mock, 'fake-host', 'nova-compute') self.assertIn('Multiple compute services found for host "fake-host" ' 'and binary "nova-compute". Unable to proceed.', - six.text_type(ex)) + str(ex)) diff --git a/openstackclient/tests/unit/fakes.py b/openstackclient/tests/unit/fakes.py index e5476f06bb..00e0c12925 100644 --- a/openstackclient/tests/unit/fakes.py +++ b/openstackclient/tests/unit/fakes.py @@ -19,7 +19,6 @@ from keystoneauth1 import fixture import requests -import six AUTH_TOKEN = "foobar" @@ -253,7 +252,7 @@ def __init__(self, headers=None, status_code=200, self.headers.update(headers) self._content = json.dumps(data) - if not isinstance(self._content, six.binary_type): + if not isinstance(self._content, bytes): self._content = self._content.encode() diff --git a/openstackclient/tests/unit/object/v1/fakes.py b/openstackclient/tests/unit/object/v1/fakes.py index 0ed791a5bc..1808d5b7d9 100644 --- a/openstackclient/tests/unit/object/v1/fakes.py +++ b/openstackclient/tests/unit/object/v1/fakes.py @@ -14,7 +14,6 @@ # from keystoneauth1 import session -import six from openstackclient.api import object_store_v1 as object_store from openstackclient.tests.unit import utils @@ -68,7 +67,7 @@ 'last_modified': object_modified_1, } -object_1_content = six.b('object 1 content') +object_1_content = b'object 1 content' OBJECT_2 = { 'name': object_name_2, diff --git a/openstackclient/tests/unit/object/v1/test_object_all.py b/openstackclient/tests/unit/object/v1/test_object_all.py index dd587142fa..7e88409f71 100644 --- a/openstackclient/tests/unit/object/v1/test_object_all.py +++ b/openstackclient/tests/unit/object/v1/test_object_all.py @@ -12,11 +12,11 @@ # import copy +import io from unittest import mock from osc_lib import exceptions from requests_mock.contrib import fixture -import six from openstackclient.object.v1 import object as object_cmds from openstackclient.tests.unit.object.v1 import fakes as object_fakes @@ -241,9 +241,9 @@ def test_save_to_stdout(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) - class FakeStdout(six.BytesIO): + class FakeStdout(io.BytesIO): def __init__(self): - six.BytesIO.__init__(self) + io.BytesIO.__init__(self) self.context_manager_calls = [] def __enter__(self): diff --git a/openstackclient/tests/unit/utils.py b/openstackclient/tests/unit/utils.py index 4f1bc46a98..4130f18e2b 100644 --- a/openstackclient/tests/unit/utils.py +++ b/openstackclient/tests/unit/utils.py @@ -14,11 +14,11 @@ # under the License. # +from io import StringIO import os from cliff import columns as cliff_columns import fixtures -from six.moves import StringIO import testtools from openstackclient.tests.unit import fakes diff --git a/requirements.txt b/requirements.txt index 2b7976e59a..64261f97d0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,6 @@ # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. pbr!=2.1.0,>=2.0.0 # Apache-2.0 -six>=1.10.0 # MIT cliff!=2.9.0,>=2.8.0 # Apache-2.0 openstacksdk>=0.48.0 # Apache-2.0 From 1c7fe3b6bd60aa22ae525d0418729590f6429e96 Mon Sep 17 00:00:00 2001 From: tianhui Date: Fri, 6 Jul 2018 08:23:06 +0000 Subject: [PATCH 2251/3095] Compute: Add tag support for server add volume Change-Id: Id9f2e09426f6824e9ca672bf7808b5165c650a69 Story: 2002195 Task: 21675 --- openstackclient/compute/v2/server.py | 64 +++++++++++++------ .../tests/unit/compute/v2/test_server.py | 60 +++++++++++++++-- ...rt-server-add-volume-278e79a22dd482f4.yaml | 5 ++ 3 files changed, 104 insertions(+), 25 deletions(-) create mode 100644 releasenotes/notes/add-tag-support-server-add-volume-278e79a22dd482f4.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 06bbb7ee70..0b1209f496 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -460,20 +460,32 @@ def get_parser(self, prog_name): metavar='', help=_('Server internal device name for volume'), ) + parser.add_argument( + '--tag', + metavar='', + help=_( + "Tag for the attached volume. " + "(Supported by API versions '2.49' - '2.latest')" + ), + ) termination_group = parser.add_mutually_exclusive_group() termination_group.add_argument( '--enable-delete-on-termination', action='store_true', - help=_("Specify if the attached volume should be deleted when " - "the server is destroyed. (Supported with " - "``--os-compute-api-version`` 2.79 or greater.)"), + help=_( + "Specify if the attached volume should be deleted when the " + "server is destroyed. " + "(Supported by API versions '2.79' - '2.latest')" + ), ) termination_group.add_argument( '--disable-delete-on-termination', action='store_true', - help=_("Specify if the attached volume should not be deleted " - "when the server is destroyed. (Supported with " - "``--os-compute-api-version`` 2.79 or greater.)"), + help=_( + "Specify if the attached volume should not be deleted when " + "the server is destroyed. " + "(Supported by API versions '2.79' - '2.latest')" + ), ) return parser @@ -490,28 +502,38 @@ def take_action(self, parsed_args): parsed_args.volume, ) - support_set_delete_on_termination = (compute_client.api_version >= - api_versions.APIVersion('2.79')) - - if not support_set_delete_on_termination: - if parsed_args.enable_delete_on_termination: - msg = _('--os-compute-api-version 2.79 or greater ' - 'is required to support the ' - '--enable-delete-on-termination option.') - raise exceptions.CommandError(msg) - if parsed_args.disable_delete_on_termination: - msg = _('--os-compute-api-version 2.79 or greater ' - 'is required to support the ' - '--disable-delete-on-termination option.') - raise exceptions.CommandError(msg) - kwargs = { "device": parsed_args.device } + if parsed_args.tag: + if compute_client.api_version < api_versions.APIVersion('2.49'): + msg = _( + '--os-compute-api-version 2.49 or greater is required to ' + 'support the --tag option' + ) + raise exceptions.CommandError(msg) + + kwargs['tag'] = parsed_args.tag + if parsed_args.enable_delete_on_termination: + if compute_client.api_version < api_versions.APIVersion('2.79'): + msg = _( + '--os-compute-api-version 2.79 or greater is required to ' + 'support the --enable-delete-on-termination option.' + ) + raise exceptions.CommandError(msg) + kwargs['delete_on_termination'] = True + if parsed_args.disable_delete_on_termination: + if compute_client.api_version < api_versions.APIVersion('2.79'): + msg = _( + '--os-compute-api-version 2.79 or greater is required to ' + 'support the --disable-delete-on-termination option.' + ) + raise exceptions.CommandError(msg) + kwargs['delete_on_termination'] = False compute_client.volumes.create_server_volume( diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index dffedc6dab..d3e159159f 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -503,8 +503,57 @@ def test_server_add_volume(self): servers[0].id, self.volume.id, device='/dev/sdb') self.assertIsNone(result) + def test_server_add_volume_with_tag(self): + # requires API 2.49 or later + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.49') + + servers = self.setup_servers_mock(count=1) + arglist = [ + '--device', '/dev/sdb', + '--tag', 'foo', + servers[0].id, + self.volume.id, + ] + verifylist = [ + ('server', servers[0].id), + ('volume', self.volume.id), + ('device', '/dev/sdb'), + ('tag', 'foo'), + ] -class TestServerVolumeV279(TestServerVolume): + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.servers_volumes_mock.create_server_volume.assert_called_once_with( + servers[0].id, self.volume.id, device='/dev/sdb', tag='foo') + self.assertIsNone(result) + + def test_server_add_volume_with_tag_pre_v249(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.48') + + servers = self.setup_servers_mock(count=1) + arglist = [ + servers[0].id, + self.volume.id, + '--tag', 'foo', + ] + verifylist = [ + ('server', servers[0].id), + ('volume', self.volume.id), + ('tag', 'foo'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.49 or greater is required', + str(ex)) def test_server_add_volume_with_enable_delete_on_termination(self): self.app.client_manager.compute.api_version = api_versions.APIVersion( @@ -561,7 +610,8 @@ def test_server_add_volume_with_disable_delete_on_termination(self): self.assertIsNone(result) def test_server_add_volume_with_enable_delete_on_termination_pre_v279( - self): + self, + ): self.app.client_manager.compute.api_version = api_versions.APIVersion( '2.78') @@ -585,7 +635,8 @@ def test_server_add_volume_with_enable_delete_on_termination_pre_v279( str(ex)) def test_server_add_volume_with_disable_delete_on_termination_pre_v279( - self): + self, + ): self.app.client_manager.compute.api_version = api_versions.APIVersion( '2.78') @@ -609,7 +660,8 @@ def test_server_add_volume_with_disable_delete_on_termination_pre_v279( str(ex)) def test_server_add_volume_with_disable_and_enable_delete_on_termination( - self): + self, + ): self.app.client_manager.compute.api_version = api_versions.APIVersion( '2.79') diff --git a/releasenotes/notes/add-tag-support-server-add-volume-278e79a22dd482f4.yaml b/releasenotes/notes/add-tag-support-server-add-volume-278e79a22dd482f4.yaml new file mode 100644 index 0000000000..510218b2f3 --- /dev/null +++ b/releasenotes/notes/add-tag-support-server-add-volume-278e79a22dd482f4.yaml @@ -0,0 +1,5 @@ +--- +features: + - Add ``--tag`` option to ``server add volume`` command when + add a volume to server. Only available starting with + ``--os-compute-api-version 2.49``. From f3fbb1b648a96e541b53f96904b85d3fdce2af10 Mon Sep 17 00:00:00 2001 From: tianhui Date: Mon, 9 Jul 2018 09:34:07 +0000 Subject: [PATCH 2252/3095] Compute: Add tag support for server add port Change-Id: Ice6bf5fb57afeb10862c870b42732dcf166772d1 Story: 2002195 Task: 21676 --- openstackclient/compute/v2/server.py | 25 ++++++++- .../tests/unit/compute/v2/test_server.py | 53 +++++++++++++++++++ ...port-server-add-port-7e30aa38202d0839.yaml | 5 ++ 3 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/add-tag-support-server-add-port-7e30aa38202d0839.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index e5a7a32833..5370a63f81 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -354,6 +354,14 @@ def get_parser(self, prog_name): metavar="", help=_("Port to add to the server (name or ID)"), ) + parser.add_argument( + '--tag', + metavar='', + help=_( + "Tag for the attached interface. " + "(Supported by API versions '2.49' - '2.latest')" + ) + ) return parser def take_action(self, parsed_args): @@ -369,7 +377,22 @@ def take_action(self, parsed_args): else: port_id = parsed_args.port - server.interface_attach(port_id=port_id, net_id=None, fixed_ip=None) + kwargs = { + 'port_id': port_id, + 'net_id': None, + 'fixed_ip': None, + } + + if parsed_args.tag: + if compute_client.api_version < api_versions.APIVersion("2.49"): + msg = _( + '--os-compute-api-version 2.49 or greater is required to ' + 'support the --tag option' + ) + raise exceptions.CommandError(msg) + kwargs['tag'] = parsed_args.tag + + server.interface_attach(**kwargs) class AddNetwork(command.Command): diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 59282b4a12..c16bb333f7 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -465,6 +465,59 @@ def test_server_add_port_no_neutron(self): self._test_server_add_port('fake-port') self.find_port.assert_not_called() + def test_server_add_port_with_tag(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.49') + + servers = self.setup_servers_mock(count=1) + self.find_port.return_value.id = 'fake-port' + arglist = [ + servers[0].id, + 'fake-port', + '--tag', 'tag1', + ] + verifylist = [ + ('server', servers[0].id), + ('port', 'fake-port'), + ('tag', 'tag1'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.assertIsNone(result) + + servers[0].interface_attach.assert_called_once_with( + port_id='fake-port', + net_id=None, + fixed_ip=None, + tag='tag1') + + def test_server_add_port_with_tag_pre_v249(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.48') + + servers = self.setup_servers_mock(count=1) + self.find_port.return_value.id = 'fake-port' + arglist = [ + servers[0].id, + 'fake-port', + '--tag', 'tag1', + ] + verifylist = [ + ('server', servers[0].id), + ('port', 'fake-port'), + ('tag', 'tag1'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.49 or greater is required', + str(ex)) + class TestServerVolume(TestServer): diff --git a/releasenotes/notes/add-tag-support-server-add-port-7e30aa38202d0839.yaml b/releasenotes/notes/add-tag-support-server-add-port-7e30aa38202d0839.yaml new file mode 100644 index 0000000000..a5a63128c7 --- /dev/null +++ b/releasenotes/notes/add-tag-support-server-add-port-7e30aa38202d0839.yaml @@ -0,0 +1,5 @@ +--- +features: + - Add ``--tag`` option to ``server add port`` command when + add a port to server. Only available starting with + ``--os-compute-api-version 2.49``. From 4855fef8b8f29542ebdaf86aa5da4daa1f97b060 Mon Sep 17 00:00:00 2001 From: tianhui Date: Mon, 25 Jun 2018 09:46:06 +0000 Subject: [PATCH 2253/3095] Compute: Add 'keypair create --type' parameter Change-Id: I2d251e1b97fb9a8069431c867fb7fc5f42d1fd6e Story: 2002606 Task: 22225 --- openstackclient/compute/v2/keypair.py | 42 +++-- .../tests/unit/compute/v2/fakes.py | 1 + .../tests/unit/compute/v2/test_keypair.py | 144 +++++++++++++++--- ...keypair-support-type-6f7c32aab3b61f7b.yaml | 5 + 4 files changed, 161 insertions(+), 31 deletions(-) create mode 100644 releasenotes/notes/keypair-support-type-6f7c32aab3b61f7b.yaml diff --git a/openstackclient/compute/v2/keypair.py b/openstackclient/compute/v2/keypair.py index 2b365cebff..6affaca302 100644 --- a/openstackclient/compute/v2/keypair.py +++ b/openstackclient/compute/v2/keypair.py @@ -20,6 +20,7 @@ import os import sys +from novaclient import api_versions from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -53,6 +54,15 @@ def get_parser(self, prog_name): help=_("Filename for private key to save. If not used, " "print private key in console.") ) + parser.add_argument( + '--type', + metavar='', + choices=['ssh', 'x509'], + help=_( + "Keypair type. Can be ssh or x509. " + "(Supported by API versions '2.2' - '2.latest')" + ), + ) return parser def take_action(self, parsed_args): @@ -70,17 +80,28 @@ def take_action(self, parsed_args): "exception": e} ) - keypair = compute_client.keypairs.create( - parsed_args.name, - public_key=public_key, - ) + kwargs = { + 'name': parsed_args.name, + 'public_key': public_key, + } + if parsed_args.type: + if compute_client.api_version < api_versions.APIVersion('2.2'): + msg = _( + '--os-compute-api-version 2.2 or greater is required to ' + 'support the --type option.' + ) + raise exceptions.CommandError(msg) + + kwargs['key_type'] = parsed_args.type + + keypair = compute_client.keypairs.create(**kwargs) private_key = parsed_args.private_key # Save private key into specified file if private_key: try: with io.open( - os.path.expanduser(parsed_args.private_key), 'w+' + os.path.expanduser(parsed_args.private_key), 'w+' ) as p: p.write(keypair.private_key) except IOError as e: @@ -150,10 +171,13 @@ def take_action(self, parsed_args): ) data = compute_client.keypairs.list() - return (columns, - (utils.get_item_properties( - s, columns, - ) for s in data)) + if compute_client.api_version >= api_versions.APIVersion('2.2'): + columns += ("Type", ) + + return ( + columns, + (utils.get_item_properties(s, columns) for s in data), + ) class ShowKeypair(command.ShowOne): diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index 3143098474..2d66f53c41 100644 --- a/openstackclient/tests/unit/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -877,6 +877,7 @@ def create_one_keypair(attrs=None, no_pri=False): # Set default attributes. keypair_info = { 'name': 'keypair-name-' + uuid.uuid4().hex, + 'type': 'ssh', 'fingerprint': 'dummy', 'public_key': 'dummy', 'user_id': 'user' diff --git a/openstackclient/tests/unit/compute/v2/test_keypair.py b/openstackclient/tests/unit/compute/v2/test_keypair.py index 1f3f56f989..ca3bfe7c8b 100644 --- a/openstackclient/tests/unit/compute/v2/test_keypair.py +++ b/openstackclient/tests/unit/compute/v2/test_keypair.py @@ -17,6 +17,7 @@ from unittest.mock import call import uuid +from novaclient import api_versions from osc_lib import exceptions from osc_lib import utils @@ -45,11 +46,13 @@ def setUp(self): self.columns = ( 'fingerprint', 'name', + 'type', 'user_id' ) self.data = ( self.keypair.fingerprint, self.keypair.name, + self.keypair.type, self.keypair.user_id ) @@ -71,7 +74,7 @@ def test_key_pair_create_no_options(self): columns, data = self.cmd.take_action(parsed_args) self.keypairs_mock.create.assert_called_with( - self.keypair.name, + name=self.keypair.name, public_key=None ) @@ -87,6 +90,7 @@ def test_keypair_create_public_key(self): self.data = ( self.keypair.fingerprint, self.keypair.name, + self.keypair.type, self.keypair.user_id ) @@ -96,7 +100,7 @@ def test_keypair_create_public_key(self): ] verifylist = [ ('public_key', self.keypair.public_key), - ('name', self.keypair.name) + ('name', self.keypair.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -109,8 +113,8 @@ def test_keypair_create_public_key(self): columns, data = self.cmd.take_action(parsed_args) self.keypairs_mock.create.assert_called_with( - self.keypair.name, - public_key=self.keypair.public_key + name=self.keypair.name, + public_key=self.keypair.public_key, ) self.assertEqual(self.columns, columns) @@ -124,7 +128,7 @@ def test_keypair_create_private_key(self): ] verifylist = [ ('private_key', tmp_pk_file), - ('name', self.keypair.name) + ('name', self.keypair.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -136,8 +140,8 @@ def test_keypair_create_private_key(self): columns, data = self.cmd.take_action(parsed_args) self.keypairs_mock.create.assert_called_with( - self.keypair.name, - public_key=None + name=self.keypair.name, + public_key=None, ) mock_open.assert_called_once_with(tmp_pk_file, 'w+') @@ -146,6 +150,79 @@ def test_keypair_create_private_key(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_keypair_create_with_key_type(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.2') + + for key_type in ['x509', 'ssh']: + self.keypair = compute_fakes.FakeKeypair.create_one_keypair( + no_pri=True) + self.keypairs_mock.create.return_value = self.keypair + + self.data = ( + self.keypair.fingerprint, + self.keypair.name, + self.keypair.type, + self.keypair.user_id, + ) + arglist = [ + '--public-key', self.keypair.public_key, + self.keypair.name, + '--type', key_type, + ] + verifylist = [ + ('public_key', self.keypair.public_key), + ('name', self.keypair.name), + ('type', key_type), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + with mock.patch('io.open') as mock_open: + mock_open.return_value = mock.MagicMock() + m_file = mock_open.return_value.__enter__.return_value + m_file.read.return_value = 'dummy' + columns, data = self.cmd.take_action(parsed_args) + + self.keypairs_mock.create.assert_called_with( + name=self.keypair.name, + public_key=self.keypair.public_key, + key_type=key_type, + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_keypair_create_with_key_type_pre_v22(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.1') + + for key_type in ['x509', 'ssh']: + arglist = [ + '--public-key', self.keypair.public_key, + self.keypair.name, + '--type', 'ssh', + ] + verifylist = [ + ('public_key', self.keypair.public_key), + ('name', self.keypair.name), + ('type', 'ssh'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + with mock.patch('io.open') as mock_open: + mock_open.return_value = mock.MagicMock() + m_file = mock_open.return_value.__enter__.return_value + m_file.read.return_value = 'dummy' + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + self.assertIn( + '--os-compute-api-version 2.2 or greater is required', + str(ex)) + class TestKeypairDelete(TestKeypair): @@ -227,16 +304,6 @@ class TestKeypairList(TestKeypair): # Return value of self.keypairs_mock.list(). keypairs = compute_fakes.FakeKeypair.create_keypairs(count=1) - columns = ( - "Name", - "Fingerprint" - ) - - data = (( - keypairs[0].name, - keypairs[0].fingerprint - ), ) - def setUp(self): super(TestKeypairList, self).setUp() @@ -247,8 +314,7 @@ def setUp(self): def test_keypair_list_no_options(self): arglist = [] - verifylist = [ - ] + verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -261,8 +327,39 @@ def test_keypair_list_no_options(self): self.keypairs_mock.list.assert_called_with() - self.assertEqual(self.columns, columns) - self.assertEqual(tuple(self.data), tuple(data)) + self.assertEqual(('Name', 'Fingerprint'), columns) + self.assertEqual( + ((self.keypairs[0].name, self.keypairs[0].fingerprint), ), + tuple(data) + ) + + def test_keypair_list_v22(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.2') + + arglist = [] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + + self.keypairs_mock.list.assert_called_with() + + self.assertEqual(('Name', 'Fingerprint', 'Type'), columns) + self.assertEqual( + (( + self.keypairs[0].name, + self.keypairs[0].fingerprint, + self.keypairs[0].type, + ), ), + tuple(data) + ) class TestKeypairShow(TestKeypair): @@ -279,16 +376,18 @@ def setUp(self): self.columns = ( "fingerprint", "name", + "type", "user_id" ) self.data = ( self.keypair.fingerprint, self.keypair.name, + self.keypair.type, self.keypair.user_id ) - def test_show_no_options(self): + def test_keypair_show_no_options(self): arglist = [] verifylist = [] @@ -306,6 +405,7 @@ def test_keypair_show(self): self.data = ( self.keypair.fingerprint, self.keypair.name, + self.keypair.type, self.keypair.user_id ) diff --git a/releasenotes/notes/keypair-support-type-6f7c32aab3b61f7b.yaml b/releasenotes/notes/keypair-support-type-6f7c32aab3b61f7b.yaml new file mode 100644 index 0000000000..549629d8a4 --- /dev/null +++ b/releasenotes/notes/keypair-support-type-6f7c32aab3b61f7b.yaml @@ -0,0 +1,5 @@ +--- +features: + - Add ``--key-type`` option to ``keypair create`` command to set keypair + type. Can be ssh or x509. Note that ``--os-compute-api-version 2.2`` or + later is required. From 6f1602312b00bcba6e04a34f7f04af9d69cf2d9c Mon Sep 17 00:00:00 2001 From: tianhui Date: Wed, 11 Jul 2018 05:52:32 +0000 Subject: [PATCH 2254/3095] Compute: Add tag support for server add network Change-Id: I31a66b2d4dac44052a71f43a5a67836247ccac64 Story: 2002195 Task: 21678 --- openstackclient/compute/v2/server.py | 26 ++++++++- .../tests/unit/compute/v2/test_server.py | 56 +++++++++++++++++++ ...t-server-add-network-a8590cab5d7babf0.yaml | 5 ++ 3 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/add-tag-support-server-add-network-a8590cab5d7babf0.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index e5a7a32833..adbbb930c7 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -387,6 +387,14 @@ def get_parser(self, prog_name): metavar="", help=_("Network to add to the server (name or ID)"), ) + parser.add_argument( + '--tag', + metavar='', + help=_( + 'Tag for the attached interface. ' + '(supported by --os-compute-api-version 2.49 or above)' + ), + ) return parser def take_action(self, parsed_args): @@ -402,7 +410,23 @@ def take_action(self, parsed_args): else: net_id = parsed_args.network - server.interface_attach(port_id=None, net_id=net_id, fixed_ip=None) + kwargs = { + 'port_id': None, + 'net_id': net_id, + 'fixed_ip': None, + } + + if parsed_args.tag: + if compute_client.api_version < api_versions.APIVersion('2.49'): + msg = _( + '--os-compute-api-version 2.49 or greater is required to ' + 'support the --tag option' + ) + raise exceptions.CommandError(msg) + + kwargs['tag'] = parsed_args.tag + + server.interface_attach(**kwargs) class AddServerSecurityGroup(command.Command): diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 59282b4a12..fd5412e682 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -681,6 +681,62 @@ def test_server_add_network_no_neutron(self): self._test_server_add_network('fake-network') self.find_network.assert_not_called() + def test_server_add_network_with_tag(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.49') + + servers = self.setup_servers_mock(count=1) + self.find_network.return_value.id = 'fake-network' + + arglist = [ + servers[0].id, + 'fake-network', + '--tag', 'tag1', + ] + verifylist = [ + ('server', servers[0].id), + ('network', 'fake-network'), + ('tag', 'tag1'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.assertIsNone(result) + + servers[0].interface_attach.assert_called_once_with( + port_id=None, + net_id='fake-network', + fixed_ip=None, + tag='tag1' + ) + + def test_server_add_network_with_tag_pre_v249(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.48') + + servers = self.setup_servers_mock(count=1) + self.find_network.return_value.id = 'fake-network' + + arglist = [ + servers[0].id, + 'fake-network', + '--tag', 'tag1', + ] + verifylist = [ + ('server', servers[0].id), + ('network', 'fake-network'), + ('tag', 'tag1'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.49 or greater is required', + str(ex)) + @mock.patch( 'openstackclient.api.compute_v2.APIv2.security_group_find' diff --git a/releasenotes/notes/add-tag-support-server-add-network-a8590cab5d7babf0.yaml b/releasenotes/notes/add-tag-support-server-add-network-a8590cab5d7babf0.yaml new file mode 100644 index 0000000000..3442ad6a29 --- /dev/null +++ b/releasenotes/notes/add-tag-support-server-add-network-a8590cab5d7babf0.yaml @@ -0,0 +1,5 @@ +--- +features: + - Add ``--tag`` option to ``server add network`` command + when add network to server. Only available starting + with ``--os-compute-api-version 2.49``. From 742c80a82574e5a6928c305842047b8b6b2b3807 Mon Sep 17 00:00:00 2001 From: tianhui Date: Fri, 27 Jul 2018 06:20:56 +0000 Subject: [PATCH 2255/3095] Compute: Add tag support for server add fixed ip Change-Id: I62ed4729dead9f91630d1f568c834c9642965558 Story: 2002195 Task: 21679 --- openstackclient/compute/v2/server.py | 30 +++++++-- .../tests/unit/compute/v2/test_server.py | 66 +++++++++++++++++++ ...-server-add-fixed-ip-8de2db58f2a80e85.yaml | 5 ++ 3 files changed, 96 insertions(+), 5 deletions(-) create mode 100644 releasenotes/notes/add-tag-support-server-add-fixed-ip-8de2db58f2a80e85.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index e5a7a32833..3e69d93fed 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -236,6 +236,14 @@ def get_parser(self, prog_name): metavar="", help=_("Requested fixed IP address"), ) + parser.add_argument( + '--tag', + metavar='', + help=_( + 'Tag for the attached interface. ' + '(supported by --os-compute-api-version 2.52 or above)' + ) + ) return parser def take_action(self, parsed_args): @@ -246,11 +254,23 @@ def take_action(self, parsed_args): network = compute_client.api.network_find(parsed_args.network) - server.interface_attach( - port_id=None, - net_id=network['id'], - fixed_ip=parsed_args.fixed_ip_address, - ) + kwargs = { + 'port_id': None, + 'net_id': network['id'], + 'fixed_ip': parsed_args.fixed_ip_address, + } + + if parsed_args.tag: + if compute_client.api_version < api_versions.APIVersion('2.49'): + msg = _( + '--os-compute-api-version 2.49 or greater is required to ' + 'support the --tag option' + ) + raise exceptions.CommandError(msg) + + kwargs['tag'] = parsed_args.tag + + server.interface_attach(**kwargs) class AddFloatingIP(network_common.NetworkAndComputeCommand): diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 59282b4a12..c91d6e4899 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -177,6 +177,72 @@ def test_server_add_specific_fixed_ip(self): extralist = ['--fixed-ip-address', '5.6.7.8'] self._test_server_add_fixed_ip(extralist, '5.6.7.8') + def test_server_add_fixed_ip_with_tag(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.49') + + servers = self.setup_servers_mock(count=1) + network = compute_fakes.FakeNetwork.create_one_network() + with mock.patch( + 'openstackclient.api.compute_v2.APIv2.network_find' + ) as net_mock: + net_mock.return_value = network + + arglist = [ + servers[0].id, + network['id'], + '--fixed-ip-address', '5.6.7.8', + '--tag', 'tag1', + ] + verifylist = [ + ('server', servers[0].id), + ('network', network['id']), + ('fixed_ip_address', '5.6.7.8'), + ('tag', 'tag1'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + servers[0].interface_attach.assert_called_once_with( + port_id=None, + net_id=network['id'], + fixed_ip='5.6.7.8', + tag='tag1' + ) + self.assertIsNone(result) + + def test_server_add_fixed_ip_with_tag_pre_v249(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.48') + + servers = self.setup_servers_mock(count=1) + network = compute_fakes.FakeNetwork.create_one_network() + with mock.patch( + 'openstackclient.api.compute_v2.APIv2.network_find' + ) as net_mock: + net_mock.return_value = network + + arglist = [ + servers[0].id, + network['id'], + '--fixed-ip-address', '5.6.7.8', + '--tag', 'tag1', + ] + verifylist = [ + ('server', servers[0].id), + ('network', network['id']), + ('fixed_ip_address', '5.6.7.8'), + ('tag', 'tag1'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.49 or greater is required', + str(ex)) + @mock.patch( 'openstackclient.api.compute_v2.APIv2.floating_ip_add' diff --git a/releasenotes/notes/add-tag-support-server-add-fixed-ip-8de2db58f2a80e85.yaml b/releasenotes/notes/add-tag-support-server-add-fixed-ip-8de2db58f2a80e85.yaml new file mode 100644 index 0000000000..731236eb2b --- /dev/null +++ b/releasenotes/notes/add-tag-support-server-add-fixed-ip-8de2db58f2a80e85.yaml @@ -0,0 +1,5 @@ +--- +features: + - Add ``--tag`` option to ``server add fixed ip`` command + when adding a fixed IP to server. Only available starting + with ``--os-compute-api-version 2.49``. From 415545ab9fd842bdc19b7fbfa63e3332dd63fe6c Mon Sep 17 00:00:00 2001 From: yanpuqing Date: Thu, 20 Jun 2019 11:14:22 +0800 Subject: [PATCH 2256/3095] Add an error message when server bind floating IP If we add a floating IP for the server with no fixed IP, CLI doesn't report an error and nothing happens. The patch adds an error message when the server which don't have fixed IP bind floating IP. Change-Id: I400f2bab08521bb7fa443d87c7f45cc79eb80694 Task: 27941 Story: 2004346 --- openstackclient/compute/v2/server.py | 4 ++ .../tests/unit/compute/v2/test_server.py | 48 +++++++++++++++---- ...ing-ip-with-no-ports-399c5559e1699816.yaml | 7 +++ 3 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 releasenotes/notes/story-2004346-add-floating-ip-with-no-ports-399c5559e1699816.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index e5a7a32833..30057111bb 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -292,6 +292,10 @@ def take_action_network(self, client, parsed_args): parsed_args.server, ) ports = list(client.ports(device_id=server.id)) + if not ports: + msg = _('No attached ports found to associate floating IP with') + raise exceptions.CommandError(msg) + # If the fixed IP address was specified, we need to find the # corresponding port. if parsed_args.fixed_ip_address: diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 59282b4a12..52a6f51711 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -249,7 +249,7 @@ def setUp(self): # Get the command object to test self.cmd = server.AddFloatingIP(self.app, self.namespace) - def test_server_add_floating_ip_default(self): + def test_server_add_floating_ip(self): _server = compute_fakes.FakeServer.create_one_server() self.servers_mock.get.return_value = _server _port = network_fakes.FakePort.create_one_port() @@ -284,8 +284,41 @@ def test_server_add_floating_ip_default(self): **attrs ) - def test_server_add_floating_ip_default_no_external_gateway(self, - success=False): + def test_server_add_floating_ip_no_ports(self): + server = compute_fakes.FakeServer.create_one_server() + floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip() + + self.servers_mock.get.return_value = server + self.network.find_ip = mock.Mock(return_value=floating_ip) + self.network.ports = mock.Mock(return_value=[]) + + arglist = [ + server.id, + floating_ip['floating_ip_address'], + ] + verifylist = [ + ('server', server.id), + ('ip_address', floating_ip['floating_ip_address']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + 'No attached ports found to associate floating IP with', + str(ex)) + + self.network.find_ip.assert_called_once_with( + floating_ip['floating_ip_address'], + ignore_missing=False, + ) + self.network.ports.assert_called_once_with( + device_id=server.id, + ) + + def test_server_add_floating_ip_no_external_gateway(self, success=False): _server = compute_fakes.FakeServer.create_one_server() self.servers_mock.get.return_value = _server _port = network_fakes.FakePort.create_one_port() @@ -338,11 +371,10 @@ def test_server_add_floating_ip_default_no_external_gateway(self, **attrs ) - def test_server_add_floating_ip_default_one_external_gateway(self): - self.test_server_add_floating_ip_default_no_external_gateway( - success=True) + def test_server_add_floating_ip_one_external_gateway(self): + self.test_server_add_floating_ip_no_external_gateway(success=True) - def test_server_add_floating_ip_fixed(self): + def test_server_add_floating_ip_with_fixed_ip(self): _server = compute_fakes.FakeServer.create_one_server() self.servers_mock.get.return_value = _server _port = network_fakes.FakePort.create_one_port() @@ -384,7 +416,7 @@ def test_server_add_floating_ip_fixed(self): **attrs ) - def test_server_add_floating_ip_fixed_no_port_found(self): + def test_server_add_floating_ip_with_fixed_ip_no_port_found(self): _server = compute_fakes.FakeServer.create_one_server() self.servers_mock.get.return_value = _server _port = network_fakes.FakePort.create_one_port() diff --git a/releasenotes/notes/story-2004346-add-floating-ip-with-no-ports-399c5559e1699816.yaml b/releasenotes/notes/story-2004346-add-floating-ip-with-no-ports-399c5559e1699816.yaml new file mode 100644 index 0000000000..ebcb7f6312 --- /dev/null +++ b/releasenotes/notes/story-2004346-add-floating-ip-with-no-ports-399c5559e1699816.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + Associating a floating IP with a server using the ``server add floating + ip`` command requires the server have at least one port associated with it. + Previously, this was not validated, meaning the operation would silently + fail. This has been resolved. From 671f73694a207b4b2b5a7cf377bc46dad7e79324 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Mon, 12 Oct 2020 17:25:03 +0100 Subject: [PATCH 2257/3095] zuul: Stop testing against Tempest Neither Tempest itself nor any of the service projects use OSC. As such, there's no reason to run Tempest jobs here. It's simply a waste of resources. Change-Id: I74b0b196fe59e5e1462e3dadc659cf6680a53f80 Signed-off-by: Stephen Finucane --- .zuul.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.zuul.yaml b/.zuul.yaml index fdc212ae8f..42c29d162d 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -1,3 +1,4 @@ +--- - job: name: osc-tox-unit-tips parent: openstack-tox @@ -225,7 +226,6 @@ - publish-openstack-docs-pti - check-requirements - release-notes-jobs-python3 - - lib-forward-testing-python3 check: jobs: - osc-build-image From 9385113d40f3d9dd77f2d7dfa5ebb71d92635548 Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Mon, 6 Jul 2020 12:10:19 -0500 Subject: [PATCH 2258/3095] Remove oslo.utils Oslo things are really server-side oriented and are heavy-weight for client things. Remove oslo.utils and just use iso8601 and importlib directly. It's not actually a bad library, but pulling it and its other deps in just for a couple of wrapper methods is a bit much here. oslo.i18n, fwiw, is lightweight and helpful. Change-Id: I463993170c03a1d98c47ab6a3c19131b7fca1099 --- openstackclient/compute/v2/server.py | 10 +++++----- openstackclient/compute/v2/server_backup.py | 5 +++-- openstackclient/compute/v2/server_image.py | 4 ++-- openstackclient/tests/unit/compute/v2/test_server.py | 6 +++--- openstackclient/tests/unit/test_shell.py | 9 +++++---- requirements.txt | 2 +- 6 files changed, 19 insertions(+), 17 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index e5a7a32833..2ede3af674 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -21,6 +21,7 @@ import logging import os +import iso8601 from novaclient import api_versions from novaclient.v2 import servers from openstack import exceptions as sdk_exceptions @@ -29,7 +30,6 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -from oslo_utils import timeutils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common @@ -1404,8 +1404,8 @@ def take_action(self, parsed_args): raise exceptions.CommandError(msg) try: - timeutils.parse_isotime(search_opts['changes-before']) - except ValueError: + iso8601.parse_date(search_opts['changes-before']) + except (TypeError, iso8601.ParseError): raise exceptions.CommandError( _('Invalid changes-before value: %s') % search_opts['changes-before'] @@ -1413,8 +1413,8 @@ def take_action(self, parsed_args): if search_opts['changes-since']: try: - timeutils.parse_isotime(search_opts['changes-since']) - except ValueError: + iso8601.parse_date(search_opts['changes-since']) + except (TypeError, iso8601.ParseError): raise exceptions.CommandError( _('Invalid changes-since value: %s') % search_opts['changes-since'] diff --git a/openstackclient/compute/v2/server_backup.py b/openstackclient/compute/v2/server_backup.py index a5d43fc6f8..b1b821b24e 100644 --- a/openstackclient/compute/v2/server_backup.py +++ b/openstackclient/compute/v2/server_backup.py @@ -15,10 +15,11 @@ """Compute v2 Server action implementations""" +import importlib + from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -from oslo_utils import importutils from openstackclient.i18n import _ @@ -119,7 +120,7 @@ def _show_progress(progress): info['properties'] = utils.format_dict(info.get('properties', {})) else: # Get the right image module to format the output - image_module = importutils.import_module( + image_module = importlib.import_module( self.IMAGE_API_VERSIONS[ self.app.client_manager._api_version['image'] ] diff --git a/openstackclient/compute/v2/server_image.py b/openstackclient/compute/v2/server_image.py index fea87af81f..c12bc2b3ae 100644 --- a/openstackclient/compute/v2/server_image.py +++ b/openstackclient/compute/v2/server_image.py @@ -15,12 +15,12 @@ """Compute v2 Server action implementations""" +import importlib import logging from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils -from oslo_utils import importutils from openstackclient.i18n import _ @@ -99,7 +99,7 @@ def _show_progress(progress): info['properties'] = utils.format_dict(info.get('properties', {})) else: # Get the right image module to format the output - image_module = importutils.import_module( + image_module = importlib.import_module( self.IMAGE_API_VERSIONS[ self.app.client_manager._api_version['image'] ] diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 59282b4a12..4bdb322e51 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -19,11 +19,11 @@ from unittest import mock from unittest.mock import call +import iso8601 from novaclient import api_versions from openstack import exceptions as sdk_exceptions from osc_lib import exceptions from osc_lib import utils as common_utils -from oslo_utils import timeutils from openstackclient.compute.v2 import server from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes @@ -2945,7 +2945,7 @@ def test_server_list_with_changes_since(self): self.assertEqual(self.columns, columns) self.assertEqual(tuple(self.data), tuple(data)) - @mock.patch.object(timeutils, 'parse_isotime', side_effect=ValueError) + @mock.patch.object(iso8601, 'parse_date', side_effect=iso8601.ParseError) def test_server_list_with_invalid_changes_since(self, mock_parse_isotime): arglist = [ @@ -2988,7 +2988,7 @@ def test_server_list_v266_with_changes_before(self): self.assertEqual(self.columns, columns) self.assertEqual(tuple(self.data), tuple(data)) - @mock.patch.object(timeutils, 'parse_isotime', side_effect=ValueError) + @mock.patch.object(iso8601, 'parse_date', side_effect=iso8601.ParseError) def test_server_list_v266_with_invalid_changes_before( self, mock_parse_isotime): self.app.client_manager.compute.api_version = ( diff --git a/openstackclient/tests/unit/test_shell.py b/openstackclient/tests/unit/test_shell.py index 94f4f44d24..366c364e06 100644 --- a/openstackclient/tests/unit/test_shell.py +++ b/openstackclient/tests/unit/test_shell.py @@ -13,12 +13,12 @@ # under the License. # +import importlib import os import sys from unittest import mock from osc_lib.tests import utils as osc_lib_test_utils -from oslo_utils import importutils import wrapt from openstackclient import shell @@ -151,12 +151,13 @@ def setUp(self): super(TestShell, self).setUp() # TODO(dtroyer): remove this once the shell_class_patch patch is # released in osc-lib - self.shell_class = importutils.import_class(self.shell_class_name) + mod_str, _sep, class_str = self.shell_class_name.rpartition('.') + self.shell_class = getattr(importlib.import_module(mod_str), class_str) def _assert_admin_token_auth(self, cmd_options, default_args): with mock.patch( - self.shell_class_name + ".initialize_app", - self.app, + self.shell_class_name + ".initialize_app", + self.app, ): _shell = osc_lib_test_utils.make_shell( shell_class=self.shell_class, diff --git a/requirements.txt b/requirements.txt index 64261f97d0..ee6b6241ae 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,10 +4,10 @@ pbr!=2.1.0,>=2.0.0 # Apache-2.0 cliff!=2.9.0,>=2.8.0 # Apache-2.0 +iso8601>=0.1.11 # MIT openstacksdk>=0.48.0 # Apache-2.0 osc-lib>=2.0.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 -oslo.utils>=3.33.0 # Apache-2.0 python-keystoneclient>=3.22.0 # Apache-2.0 python-novaclient>=15.1.0 # Apache-2.0 python-cinderclient>=3.3.0 # Apache-2.0 From fd9a235de3b3592366818ab7799e73f6be029b15 Mon Sep 17 00:00:00 2001 From: Zhaokun Fu Date: Thu, 3 Aug 2017 19:51:27 -0700 Subject: [PATCH 2259/3095] compute: Add --password option for openstack server create Change-Id: Iaf923200efe023655a58ac5acac0b087d2fd5366 Story: #1708570 Task: #13780 --- openstackclient/compute/v2/server.py | 6 +++++ .../tests/unit/compute/v2/test_server.py | 25 +++++++++++++++++++ .../notes/bug-1708570-bb19e1213e887723.yaml | 5 ++++ 3 files changed, 36 insertions(+) create mode 100644 releasenotes/notes/bug-1708570-bb19e1213e887723.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index e5a7a32833..da0cf09732 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -555,6 +555,11 @@ def get_parser(self, prog_name): 'duplicate mapping using --block-device-mapping for this ' 'volume.'), ) + parser.add_argument( + '--password', + metavar='', + help=_("Set the password to this server"), + ) parser.add_argument( '--flavor', metavar='', @@ -1054,6 +1059,7 @@ def _match_image(image_api, wanted_properties): userdata=userdata, key_name=parsed_args.key_name, availability_zone=parsed_args.availability_zone, + admin_pass=parsed_args.password, block_device_mapping_v2=block_device_mapping_v2, nics=nics, scheduler_hints=hints, diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 59282b4a12..9a8fb3ad07 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -831,6 +831,7 @@ def test_server_create_minimal(self): userdata=None, key_name=None, availability_zone=None, + admin_pass=None, block_device_mapping_v2=[], nics=[], scheduler_hints={}, @@ -857,6 +858,7 @@ def test_server_create_with_options(self): '--property', 'Beta=b', '--security-group', 'securitygroup', '--use-config-drive', + '--password', 'passw0rd', '--hint', 'a=b', '--hint', 'a=c', self.new_server.name, @@ -869,6 +871,7 @@ def test_server_create_with_options(self): ('security_group', ['securitygroup']), ('hint', {'a': ['b', 'c']}), ('config_drive', True), + ('password', 'passw0rd'), ('server_name', self.new_server.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -897,6 +900,7 @@ def test_server_create_with_options(self): userdata=None, key_name='keyname', availability_zone=None, + admin_pass='passw0rd', block_device_mapping_v2=[], nics=[], scheduler_hints={'a': ['b', 'c']}, @@ -983,6 +987,7 @@ def test_server_create_with_security_group_in_nova_network(self): userdata=None, key_name='keyname', availability_zone=None, + admin_pass=None, block_device_mapping_v2=[], nics=[], scheduler_hints={}, @@ -1069,6 +1074,7 @@ def test_server_create_with_network(self): userdata=None, key_name=None, availability_zone=None, + admin_pass=None, block_device_mapping_v2=[], nics=[{'net-id': 'net1_uuid', 'v4-fixed-ip': '', @@ -1133,6 +1139,7 @@ def test_server_create_with_auto_network(self): userdata=None, key_name=None, availability_zone=None, + admin_pass=None, block_device_mapping_v2=[], nics='auto', scheduler_hints={}, @@ -1182,6 +1189,7 @@ def test_server_create_with_auto_network_default_v2_37(self): userdata=None, key_name=None, availability_zone=None, + admin_pass=None, block_device_mapping_v2=[], nics='auto', scheduler_hints={}, @@ -1227,6 +1235,7 @@ def test_server_create_with_none_network(self): userdata=None, key_name=None, availability_zone=None, + admin_pass=None, block_device_mapping_v2=[], nics='none', scheduler_hints={}, @@ -1392,6 +1401,7 @@ def test_server_create_with_wait_ok(self, mock_wait_for_status): userdata=None, key_name=None, availability_zone=None, + admin_pass=None, block_device_mapping_v2=[], nics=[], scheduler_hints={}, @@ -1442,6 +1452,7 @@ def test_server_create_with_wait_fails(self, mock_wait_for_status): userdata=None, key_name=None, availability_zone=None, + admin_pass=None, block_device_mapping_v2=[], nics=[], scheduler_hints={}, @@ -1497,6 +1508,7 @@ def test_server_create_userdata(self, mock_open): userdata=mock_file, key_name=None, availability_zone=None, + admin_pass=None, block_device_mapping_v2=[], nics=[], scheduler_hints={}, @@ -1543,6 +1555,7 @@ def test_server_create_with_block_device_mapping(self): userdata=None, key_name=None, availability_zone=None, + admin_pass=None, block_device_mapping_v2=[{ 'device_name': 'vda', 'uuid': self.volume.id, @@ -1595,6 +1608,7 @@ def test_server_create_with_block_device_mapping_min_input(self): userdata=None, key_name=None, availability_zone=None, + admin_pass=None, block_device_mapping_v2=[{ 'device_name': 'vdf', 'uuid': self.volume.id, @@ -1646,6 +1660,7 @@ def test_server_create_with_block_device_mapping_default_input(self): userdata=None, key_name=None, availability_zone=None, + admin_pass=None, block_device_mapping_v2=[{ 'device_name': 'vdf', 'uuid': self.volume.id, @@ -1699,6 +1714,7 @@ def test_server_create_with_block_device_mapping_full_input(self): userdata=None, key_name=None, availability_zone=None, + admin_pass=None, block_device_mapping_v2=[{ 'device_name': 'vde', 'uuid': self.volume.id, @@ -1754,6 +1770,7 @@ def test_server_create_with_block_device_mapping_snapshot(self): userdata=None, key_name=None, availability_zone=None, + admin_pass=None, block_device_mapping_v2=[{ 'device_name': 'vds', 'uuid': self.snapshot.id, @@ -1809,6 +1826,7 @@ def test_server_create_with_block_device_mapping_multiple(self): userdata=None, key_name=None, availability_zone=None, + admin_pass=None, block_device_mapping_v2=[ { 'device_name': 'vdb', @@ -1945,6 +1963,7 @@ def test_server_create_image_property(self): userdata=None, key_name=None, availability_zone=None, + admin_pass=None, block_device_mapping_v2=[], nics='none', meta=None, @@ -2000,6 +2019,7 @@ def test_server_create_image_property_multi(self): userdata=None, key_name=None, availability_zone=None, + admin_pass=None, block_device_mapping_v2=[], nics='none', meta=None, @@ -2089,6 +2109,7 @@ def test_server_create_image_property_with_image_list(self): userdata=None, key_name=None, availability_zone=None, + admin_pass=None, block_device_mapping_v2=[], nics='none', meta=None, @@ -2169,6 +2190,7 @@ def test_server_create_with_description_api_newer(self): userdata=None, key_name=None, availability_zone=None, + admin_pass=None, block_device_mapping_v2=[], nics='auto', scheduler_hints={}, @@ -2253,6 +2275,7 @@ def test_server_create_with_host_v274(self): userdata=None, key_name=None, availability_zone=None, + admin_pass=None, block_device_mapping_v2=[], nics='auto', scheduler_hints={}, @@ -2338,6 +2361,7 @@ def test_server_create_with_hypervisor_hostname_v274(self): userdata=None, key_name=None, availability_zone=None, + admin_pass=None, block_device_mapping_v2=[], nics='auto', scheduler_hints={}, @@ -2425,6 +2449,7 @@ def test_server_create_with_host_and_hypervisor_hostname_v274(self): userdata=None, key_name=None, availability_zone=None, + admin_pass=None, block_device_mapping_v2=[], nics='auto', scheduler_hints={}, diff --git a/releasenotes/notes/bug-1708570-bb19e1213e887723.yaml b/releasenotes/notes/bug-1708570-bb19e1213e887723.yaml new file mode 100644 index 0000000000..1a1cbdbef2 --- /dev/null +++ b/releasenotes/notes/bug-1708570-bb19e1213e887723.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``--password`` option to ``server create`` command, allowing users to + set the admin password when creating a new instance. From 1c3cf11331a5734700e1c333c98928ab933c0e92 Mon Sep 17 00:00:00 2001 From: hackertron Date: Thu, 2 Apr 2020 13:47:02 +0200 Subject: [PATCH 2260/3095] Add 'server migration abort' command This is equivalent to nova client's 'live-migration-abort' command. Change-Id: I0ff520ccfdf2de52c427affad7bef4554c86a06f Story: 2007489 Task: 39210 --- openstackclient/compute/v2/server.py | 38 +++++++++++++ .../tests/unit/compute/v2/fakes.py | 3 + .../tests/unit/compute/v2/test_server.py | 56 +++++++++++++++++++ .../notes/bug-2007489-42e41b14e42128ce.yaml | 4 ++ setup.cfg | 1 + 5 files changed, 102 insertions(+) create mode 100644 releasenotes/notes/bug-2007489-42e41b14e42128ce.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index f8d6aad0e8..0a96eb86de 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -2026,6 +2026,44 @@ def take_action(self, parsed_args): return self.print_migrations(parsed_args, compute_client, migrations) +class AbortMigration(command.Command): + """Cancel an ongoing live migration. + + This command requires ``--os-compute-api-version`` 2.24 or greater. + """ + + def get_parser(self, prog_name): + parser = super(AbortMigration, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + help=_('Server (name or ID)'), + ) + parser.add_argument( + 'migration', + metavar='', + help=_("Migration (ID)"), + ) + return parser + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + + if compute_client.api_version < api_versions.APIVersion('2.24'): + msg = _( + '--os-compute-api-version 2.24 or greater is required to ' + 'support the server migration abort command' + ) + raise exceptions.CommandError(msg) + + server = utils.find_resource( + compute_client.servers, + parsed_args.server, + ) + compute_client.server_migrations.live_migration_abort( + server.id, parsed_args.migration) + + class PauseServer(command.Command): _description = _("Pause server(s)") diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index 6aeb5da72f..ce556fa6cd 100644 --- a/openstackclient/tests/unit/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -196,6 +196,9 @@ def __init__(self, **kwargs): self.server_groups = mock.Mock() self.server_groups.resource_class = fakes.FakeResource(None, {}) + self.server_migrations = mock.Mock() + self.server_migrations.resource_class = fakes.FakeResource(None, {}) + self.instance_action = mock.Mock() self.instance_action.resource_class = fakes.FakeResource(None, {}) diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index a9f72cf6da..9ac2935236 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -42,6 +42,11 @@ def setUp(self): self.servers_mock = self.app.client_manager.compute.servers self.servers_mock.reset_mock() + # Get a shortcut to the compute client ServerMigrationsManager Mock + self.server_migrations_mock = \ + self.app.client_manager.compute.server_migrations + self.server_migrations_mock.reset_mock() + # Get a shortcut to the compute client volumeManager Mock self.servers_volumes_mock = self.app.client_manager.compute.volumes self.servers_volumes_mock.reset_mock() @@ -4207,6 +4212,57 @@ def test_get_migrations_with_project_and_user_pre_v280(self): parsed_args) +class TestServerMigrationAbort(TestServer): + + def setUp(self): + super(TestServerMigrationAbort, self).setUp() + + self.server = compute_fakes.FakeServer.create_one_server() + + # Return value for utils.find_resource for server. + self.servers_mock.get.return_value = self.server + + # Get the command object to test + self.cmd = server.AbortMigration(self.app, None) + + def test_migration_abort(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.24') + + arglist = [ + self.server.id, + '2', # arbitrary migration ID + ] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.server_migrations_mock.live_migration_abort.assert_called_with( + self.server.id, '2',) + self.assertIsNone(result) + + def test_migration_abort_pre_v224(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.23') + + arglist = [ + self.server.id, + '2', # arbitrary migration ID + ] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.24 or greater is required', + str(ex)) + + class TestServerPause(TestServer): def setUp(self): diff --git a/releasenotes/notes/bug-2007489-42e41b14e42128ce.yaml b/releasenotes/notes/bug-2007489-42e41b14e42128ce.yaml new file mode 100644 index 0000000000..99dd5062b0 --- /dev/null +++ b/releasenotes/notes/bug-2007489-42e41b14e42128ce.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Add ``server migration abort`` command to abort ongoing live migrations. diff --git a/setup.cfg b/setup.cfg index 6cf18cc29b..56934a192b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -109,6 +109,7 @@ openstack.compute.v2 = server_migrate_confirm = openstackclient.compute.v2.server:MigrateConfirm server_migrate_revert = openstackclient.compute.v2.server:MigrateRevert server_migration_list = openstackclient.compute.v2.server:ListMigration + server_migration_abort = openstackclient.compute.v2.server:AbortMigration server_pause = openstackclient.compute.v2.server:PauseServer server_reboot = openstackclient.compute.v2.server:RebootServer server_rebuild = openstackclient.compute.v2.server:RebuildServer From 08b0e5855be5dc12a2fd04c5c92027c8a85a0d00 Mon Sep 17 00:00:00 2001 From: jay Date: Tue, 21 Apr 2020 14:23:58 +0200 Subject: [PATCH 2261/3095] Add 'server migration force complete' command This is equivalent to novaclient's 'live-migration-force-complete' command. Change-Id: Ic4dc639afa16cdf8c5a46774895e850d92985292 Story: 2007513 Task: 39293 --- openstackclient/compute/v2/server.py | 38 ++++++++++++++ .../tests/unit/compute/v2/test_server.py | 51 +++++++++++++++++++ .../notes/bug-2007513-ae39456aeb93bb98.yaml | 4 ++ setup.cfg | 1 + 4 files changed, 94 insertions(+) create mode 100644 releasenotes/notes/bug-2007513-ae39456aeb93bb98.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 0a96eb86de..c3aec0ed70 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -2064,6 +2064,44 @@ def take_action(self, parsed_args): server.id, parsed_args.migration) +class ForceCompleteMigration(command.Command): + """Force an ongoing live migration to complete. + + This command requires ``--os-compute-api-version`` 2.22 or greater. + """ + + def get_parser(self, prog_name): + parser = super(ForceCompleteMigration, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + help=_('Server (name or ID)'), + ) + parser.add_argument( + 'migration', + metavar='', + help=_('Migration (ID)') + ) + return parser + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + + if compute_client.api_version < api_versions.APIVersion('2.22'): + msg = _( + '--os-compute-api-version 2.22 or greater is required to ' + 'support the server migration force complete command' + ) + raise exceptions.CommandError(msg) + + server = utils.find_resource( + compute_client.servers, + parsed_args.server, + ) + compute_client.server_migrations.live_migrate_force_complete( + server.id, parsed_args.migration) + + class PauseServer(command.Command): _description = _("Pause server(s)") diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 9ac2935236..3b98c873ec 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -4263,6 +4263,57 @@ def test_migration_abort_pre_v224(self): str(ex)) +class TestServerMigrationForceComplete(TestServer): + + def setUp(self): + super(TestServerMigrationForceComplete, self).setUp() + + self.server = compute_fakes.FakeServer.create_one_server() + + # Return value for utils.find_resource for server. + self.servers_mock.get.return_value = self.server + + # Get the command object to test + self.cmd = server.ForceCompleteMigration(self.app, None) + + def test_migration_force_complete(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.22') + + arglist = [ + self.server.id, + '2', # arbitrary migration ID + ] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.server_migrations_mock.live_migrate_force_complete\ + .assert_called_with(self.server.id, '2',) + self.assertIsNone(result) + + def test_migration_force_complete_pre_v222(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.21') + + arglist = [ + self.server.id, + '2', # arbitrary migration ID + ] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.22 or greater is required', + str(ex)) + + class TestServerPause(TestServer): def setUp(self): diff --git a/releasenotes/notes/bug-2007513-ae39456aeb93bb98.yaml b/releasenotes/notes/bug-2007513-ae39456aeb93bb98.yaml new file mode 100644 index 0000000000..56de14b2c5 --- /dev/null +++ b/releasenotes/notes/bug-2007513-ae39456aeb93bb98.yaml @@ -0,0 +1,4 @@ +--- +features: + - Add ``server migration force complete`` command to force complete + ongoing live migrations. diff --git a/setup.cfg b/setup.cfg index 56934a192b..8363ec6cfd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -110,6 +110,7 @@ openstack.compute.v2 = server_migrate_revert = openstackclient.compute.v2.server:MigrateRevert server_migration_list = openstackclient.compute.v2.server:ListMigration server_migration_abort = openstackclient.compute.v2.server:AbortMigration + server_migration_force_complete = openstackclient.compute.v2.server:ForceCompleteMigration server_pause = openstackclient.compute.v2.server:PauseServer server_reboot = openstackclient.compute.v2.server:RebootServer server_rebuild = openstackclient.compute.v2.server:RebuildServer From 5fd399eabaff7c7994d4ba79c7e9d77131436c5d Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Tue, 13 Oct 2020 14:31:11 +0100 Subject: [PATCH 2262/3095] Cleanup of 'server migration list' command Address some post merge nits. Change-Id: Ie59521d81fab191194f6c1a114b007fa17f5299f Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server.py | 193 ++++++++++-------- .../tests/unit/compute/v2/test_server.py | 185 +++++++++-------- 2 files changed, 211 insertions(+), 167 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index c3aec0ed70..2fa0c52403 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1866,99 +1866,99 @@ def _show_progress(progress): class ListMigration(command.Command): - _description = _("""List server migrations.""") + _description = _("""List server migrations""") def get_parser(self, prog_name): parser = super(ListMigration, self).get_parser(prog_name) parser.add_argument( - "--server", - metavar="", - dest='server', - default=None, - help=_('Server to show migration details (name or ID).') + '--server', + metavar='', + help=_( + 'Filter migrations by server (name or ID)' + ) ) parser.add_argument( - "--host", - metavar="", - default=None, - help=_('Fetch migrations for the given host.') + '--host', + metavar='', + help=_( + 'Filter migrations by source or destination host' + ), ) parser.add_argument( - "--status", - metavar="", - default=None, - help=_('Fetch migrations for the given status.') + '--status', + metavar='', + help=_('Filter migrations by status') ) parser.add_argument( - "--marker", - metavar="", - dest='marker', - default=None, - help=_("The last migration of the previous page; displays list " - "of migrations after 'marker'. Note that the marker is " - "the migration UUID. (Supported with " - "``--os-compute-api-version`` 2.59 or greater.)") + '--marker', + metavar='', + help=_( + "The last migration of the previous page; displays list " + "of migrations after 'marker'. Note that the marker is " + "the migration UUID. " + "(supported with --os-compute-api-version 2.59 or above)" + ), ) parser.add_argument( - "--limit", - metavar="", - dest='limit', + '--limit', + metavar='', type=int, - default=None, - help=_("Maximum number of migrations to display. Note that there " - "is a configurable max limit on the server, and the limit " - "that is used will be the minimum of what is requested " - "here and what is configured in the server. " - "(Supported with ``--os-compute-api-version`` 2.59 " - "or greater.)") + help=_( + "Maximum number of migrations to display. Note that there " + "is a configurable max limit on the server, and the limit " + "that is used will be the minimum of what is requested " + "here and what is configured in the server. " + "(supported with --os-compute-api-version 2.59 or above)" + ), ) parser.add_argument( '--changes-since', dest='changes_since', metavar='', - default=None, - help=_("List only migrations changed later or equal to a certain " - "point of time. The provided time should be an ISO 8061 " - "formatted time, e.g. ``2016-03-04T06:27:59Z``. " - "(Supported with ``--os-compute-api-version`` 2.59 " - "or greater.)") + help=_( + "List only migrations changed later or equal to a certain " + "point of time. The provided time should be an ISO 8061 " + "formatted time, e.g. ``2016-03-04T06:27:59Z``. " + "(supported with --os-compute-api-version 2.59 or above)" + ), ) parser.add_argument( '--changes-before', dest='changes_before', metavar='', - default=None, - help=_("List only migrations changed earlier or equal to a " - "certain point of time. The provided time should be an ISO " - "8061 formatted time, e.g. ``2016-03-04T06:27:59Z``. " - "(Supported with ``--os-compute-api-version`` 2.66 or " - "greater.)") + help=_( + "List only migrations changed earlier or equal to a " + "certain point of time. The provided time should be an ISO " + "8061 formatted time, e.g. ``2016-03-04T06:27:59Z``. " + "(supported with --os-compute-api-version 2.66 or above)" + ), ) parser.add_argument( '--project', metavar='', dest='project_id', - default=None, - help=_("Filter the migrations by the given project ID. " - "(Supported with ``--os-compute-api-version`` 2.80 " - "or greater.)"), + help=_( + "Filter migrations by project (ID) " + "(supported with --os-compute-api-version 2.80 or above)" + ), ) parser.add_argument( '--user', metavar='', dest='user_id', - default=None, - help=_("Filter the migrations by the given user ID. " - "(Supported with ``--os-compute-api-version`` 2.80 " - "or greater.)"), + help=_( + "Filter migrations by user (ID) " + "(supported with --os-compute-api-version 2.80 or above)" + ), ) return parser def print_migrations(self, parsed_args, compute_client, migrations): - columns = ['Source Node', 'Dest Node', 'Source Compute', - 'Dest Compute', 'Dest Host', 'Status', - 'Server UUID', 'Old Flavor', 'New Flavor', - 'Created At', 'Updated At'] + columns = [ + 'Source Node', 'Dest Node', 'Source Compute', 'Dest Compute', + 'Dest Host', 'Status', 'Server UUID', 'Old Flavor', 'New Flavor', + 'Created At', 'Updated At', + ] # Insert migrations UUID after ID if compute_client.api_version >= api_versions.APIVersion("2.59"): @@ -1978,48 +1978,73 @@ def print_migrations(self, parsed_args, compute_client, migrations): if parsed_args.user_id: columns.insert(len(columns) - 2, "User") - columns_header = columns - return (columns_header, (utils.get_item_properties( - mig, columns) for mig in migrations)) + return ( + columns, + (utils.get_item_properties(mig, columns) for mig in migrations), + ) def take_action(self, parsed_args): compute_client = self.app.client_manager.compute search_opts = { - "host": parsed_args.host, - "server": parsed_args.server, - "status": parsed_args.status, + 'host': parsed_args.host, + 'server': parsed_args.server, + 'status': parsed_args.status, } - if (parsed_args.marker or parsed_args.limit or - parsed_args.changes_since): - if compute_client.api_version < api_versions.APIVersion("2.59"): - msg = _("marker, limit and/or changes_since is not supported " - "for --os-compute-api-version less than 2.59") + if parsed_args.marker: + if compute_client.api_version < api_versions.APIVersion('2.59'): + msg = _( + '--os-compute-api-version 2.59 or greater is required to ' + 'support the --marker option' + ) raise exceptions.CommandError(msg) - if parsed_args.marker: - search_opts['marker'] = parsed_args.marker - if parsed_args.limit: - search_opts['limit'] = parsed_args.limit - if parsed_args.changes_since: - search_opts['changes_since'] = parsed_args.changes_since + search_opts['marker'] = parsed_args.marker + + if parsed_args.limit: + if compute_client.api_version < api_versions.APIVersion('2.59'): + msg = _( + '--os-compute-api-version 2.59 or greater is required to ' + 'support the --limit option' + ) + raise exceptions.CommandError(msg) + search_opts['limit'] = parsed_args.limit + + if parsed_args.changes_since: + if compute_client.api_version < api_versions.APIVersion('2.59'): + msg = _( + '--os-compute-api-version 2.59 or greater is required to ' + 'support the --changes-since option' + ) + raise exceptions.CommandError(msg) + search_opts['changes_since'] = parsed_args.changes_since if parsed_args.changes_before: - if compute_client.api_version < api_versions.APIVersion("2.66"): - msg = _("changes_before is not supported for " - "--os-compute-api-version less than 2.66") + if compute_client.api_version < api_versions.APIVersion('2.66'): + msg = _( + '--os-compute-api-version 2.66 or greater is required to ' + 'support the --changes-before option' + ) raise exceptions.CommandError(msg) search_opts['changes_before'] = parsed_args.changes_before - if parsed_args.project_id or parsed_args.user_id: - if compute_client.api_version < api_versions.APIVersion("2.80"): - msg = _("Project and/or user is not supported for " - "--os-compute-api-version less than 2.80") + if parsed_args.project_id: + if compute_client.api_version < api_versions.APIVersion('2.80'): + msg = _( + '--os-compute-api-version 2.80 or greater is required to ' + 'support the --project option' + ) + raise exceptions.CommandError(msg) + search_opts['project_id'] = parsed_args.project_id + + if parsed_args.user_id: + if compute_client.api_version < api_versions.APIVersion('2.80'): + msg = _( + '--os-compute-api-version 2.80 or greater is required to ' + 'support the --user option' + ) raise exceptions.CommandError(msg) - if parsed_args.project_id: - search_opts['project_id'] = parsed_args.project_id - if parsed_args.user_id: - search_opts['user_id'] = parsed_args.user_id + search_opts['user_id'] = parsed_args.user_id migrations = compute_client.migrations.list(**search_opts) diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 3b98c873ec..4b000180ae 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -47,10 +47,14 @@ def setUp(self): self.app.client_manager.compute.server_migrations self.server_migrations_mock.reset_mock() - # Get a shortcut to the compute client volumeManager Mock + # Get a shortcut to the compute client VolumeManager mock self.servers_volumes_mock = self.app.client_manager.compute.volumes self.servers_volumes_mock.reset_mock() + # Get a shortcut to the compute client MigrationManager mock + self.migrations_mock = self.app.client_manager.compute.migrations + self.migrations_mock.reset_mock() + # Get a shortcut to the compute client FlavorManager Mock self.flavors_mock = self.app.client_manager.compute.flavors self.flavors_mock.reset_mock() @@ -3728,34 +3732,7 @@ def test_server_migrate_with_wait_fails(self, mock_wait_for_status): self.assertNotCalled(self.servers_mock.live_migrate) -class TestServerMigration(TestServer): - - def setUp(self): - super(TestServerMigration, self).setUp() - - # Get a shortcut to the compute client ServerManager Mock - self.servers_mock = self.app.client_manager.compute.servers - self.servers_mock.reset_mock() - - self.migrations_mock = ( - self.app.client_manager.compute.migrations) - self.migrations_mock.reset_mock() - - self.server = self.setup_servers_mock(1)[0] - - def setup_servers_mock(self, count): - servers = compute_fakes.FakeServer.create_servers(count=count) - - # This is the return value for utils.find_resource() - self.servers_mock.get = compute_fakes.FakeServer.get_servers(servers) - return servers - - def setup_server_migrations_mock(self, count): - return compute_fakes.FakeServerMigration.create_server_migrations( - count=count) - - -class TestListMigration(TestServerMigration): +class TestListMigration(TestServer): """Test fetch all migrations.""" MIGRATION_COLUMNS = [ @@ -3767,24 +3744,48 @@ class TestListMigration(TestServerMigration): def setUp(self): super(TestListMigration, self).setUp() - self.cmd = server.ListMigration(self.app, None) - self.migrations = self.setup_server_migrations_mock(3) - self.migrations_mock.list.return_value = self.migrations - self.setup_server_migrations_data(self.migrations) + self.server = compute_fakes.FakeServer.create_one_server() + self.servers_mock.get.return_value = self.server - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.20') + self.migrations = compute_fakes.FakeServerMigration\ + .create_server_migrations(count=3) + self.migrations_mock.list.return_value = self.migrations - def setup_server_migrations_data(self, migrations): self.data = (common_utils.get_item_properties( - s, self.MIGRATION_COLUMNS) for s in migrations) + s, self.MIGRATION_COLUMNS) for s in self.migrations) + + # Get the command object to test + self.cmd = server.ListMigration(self.app, None) + + def test_server_migration_list_no_options(self): + arglist = [] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'status': None, + 'host': None, + 'server': None, + } + + self.migrations_mock.list.assert_called_with(**kwargs) + + self.assertEqual(self.MIGRATION_COLUMNS, columns) + self.assertEqual(tuple(self.data), tuple(data)) - def test_server_migraton_list(self): + def test_server_migration_list(self): arglist = [ - '--status', 'migrating' + '--server', 'server1', + '--host', 'host1', + '--status', 'migrating', ] verifylist = [ - ('status', 'migrating') + ('server', 'server1'), + ('host', 'host1'), + ('status', 'migrating'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) @@ -3792,8 +3793,8 @@ def test_server_migraton_list(self): # Set expected values kwargs = { 'status': 'migrating', - 'host': None, - 'server': None, + 'host': 'host1', + 'server': 'server1', } self.migrations_mock.list.assert_called_with(**kwargs) @@ -3813,15 +3814,11 @@ class TestListMigrationV223(TestListMigration): def setUp(self): super(TestListMigrationV223, self).setUp() - self.cmd = server.ListMigration(self.app, None) - self.migrations = self.setup_server_migrations_mock(3) - self.migrations_mock.list.return_value = self.migrations - self.setup_server_migrations_data(self.migrations) self.app.client_manager.compute.api_version = api_versions.APIVersion( '2.23') - def test_server_migraton_list(self): + def test_server_migration_list(self): arglist = [ '--status', 'migrating' ] @@ -3858,15 +3855,11 @@ class TestListMigrationV259(TestListMigration): def setUp(self): super(TestListMigrationV259, self).setUp() - self.cmd = server.ListMigration(self.app, None) - self.migrations = self.setup_server_migrations_mock(3) - self.migrations_mock.list.return_value = self.migrations - self.setup_server_migrations_data(self.migrations) self.app.client_manager.compute.api_version = api_versions.APIVersion( '2.59') - def test_server_migraton_list(self): + def test_server_migration_list(self): arglist = [ '--status', 'migrating', '--limit', '1', @@ -3897,7 +3890,7 @@ def test_server_migraton_list(self): self.assertEqual(self.MIGRATION_COLUMNS, columns) self.assertEqual(tuple(self.data), tuple(data)) - def test_server_migraton_list_with_limit_pre_v259(self): + def test_server_migration_list_with_limit_pre_v259(self): self.app.client_manager.compute.api_version = api_versions.APIVersion( '2.58') arglist = [ @@ -3909,10 +3902,15 @@ def test_server_migraton_list_with_limit_pre_v259(self): ('limit', 1) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.assertRaises(exceptions.CommandError, self.cmd.take_action, - parsed_args) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.59 or greater is required', + str(ex)) - def test_server_migraton_list_with_marker_pre_v259(self): + def test_server_migration_list_with_marker_pre_v259(self): self.app.client_manager.compute.api_version = api_versions.APIVersion( '2.58') arglist = [ @@ -3924,10 +3922,15 @@ def test_server_migraton_list_with_marker_pre_v259(self): ('marker', 'test_kp') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.assertRaises(exceptions.CommandError, self.cmd.take_action, - parsed_args) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.59 or greater is required', + str(ex)) - def test_server_migraton_list_with_changes_since_pre_v259(self): + def test_server_migration_list_with_changes_since_pre_v259(self): self.app.client_manager.compute.api_version = api_versions.APIVersion( '2.58') arglist = [ @@ -3939,8 +3942,13 @@ def test_server_migraton_list_with_changes_since_pre_v259(self): ('changes_since', '2019-08-09T08:03:25Z') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.assertRaises(exceptions.CommandError, self.cmd.take_action, - parsed_args) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.59 or greater is required', + str(ex)) class TestListMigrationV266(TestListMigration): @@ -3954,15 +3962,11 @@ class TestListMigrationV266(TestListMigration): def setUp(self): super(TestListMigrationV266, self).setUp() - self.cmd = server.ListMigration(self.app, None) - self.migrations = self.setup_server_migrations_mock(3) - self.migrations_mock.list.return_value = self.migrations - self.setup_server_migrations_data(self.migrations) self.app.client_manager.compute.api_version = api_versions.APIVersion( '2.66') - def test_server_migraton_list_with_changes_before(self): + def test_server_migration_list_with_changes_before(self): arglist = [ '--status', 'migrating', '--limit', '1', @@ -3996,7 +4000,7 @@ def test_server_migraton_list_with_changes_before(self): self.assertEqual(self.MIGRATION_COLUMNS, columns) self.assertEqual(tuple(self.data), tuple(data)) - def test_server_migraton_list_with_changes_before_pre_v266(self): + def test_server_migration_list_with_changes_before_pre_v266(self): self.app.client_manager.compute.api_version = api_versions.APIVersion( '2.65') arglist = [ @@ -4008,8 +4012,13 @@ def test_server_migraton_list_with_changes_before_pre_v266(self): ('changes_before', '2019-08-09T08:03:25Z') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.assertRaises(exceptions.CommandError, self.cmd.take_action, - parsed_args) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.66 or greater is required', + str(ex)) class TestListMigrationV280(TestListMigration): @@ -4023,15 +4032,11 @@ class TestListMigrationV280(TestListMigration): def setUp(self): super(TestListMigrationV280, self).setUp() - self.cmd = server.ListMigration(self.app, None) - self.migrations = self.setup_server_migrations_mock(3) - self.migrations_mock.list.return_value = self.migrations - self.setup_server_migrations_data(self.migrations) self.app.client_manager.compute.api_version = api_versions.APIVersion( '2.80') - def test_server_migraton_list_with_project(self): + def test_server_migration_list_with_project(self): arglist = [ '--status', 'migrating', '--limit', '1', @@ -4086,10 +4091,15 @@ def test_get_migrations_with_project_pre_v280(self): ('project_id', '0c2accde-644a-45fa-8c10-e76debc7fbc3') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.assertRaises(exceptions.CommandError, self.cmd.take_action, - parsed_args) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.80 or greater is required', + str(ex)) - def test_server_migraton_list_with_user(self): + def test_server_migration_list_with_user(self): arglist = [ '--status', 'migrating', '--limit', '1', @@ -4144,11 +4154,15 @@ def test_get_migrations_with_user_pre_v280(self): ('user_id', 'dd214878-ca12-40fb-b035-fa7d2c1e86d6') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.80 or greater is required', + str(ex)) - self.assertRaises(exceptions.CommandError, self.cmd.take_action, - parsed_args) - - def test_server_migraton_list_with_project_and_user(self): + def test_server_migration_list_with_project_and_user(self): arglist = [ '--status', 'migrating', '--limit', '1', @@ -4208,8 +4222,13 @@ def test_get_migrations_with_project_and_user_pre_v280(self): ('user_id', 'dd214878-ca12-40fb-b035-fa7d2c1e86d6') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.assertRaises(exceptions.CommandError, self.cmd.take_action, - parsed_args) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.80 or greater is required', + str(ex)) class TestServerMigrationAbort(TestServer): From bf35f04682fa2bdd00a7cff7b9a963b4ffb80eff Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Tue, 13 Oct 2020 14:47:25 +0100 Subject: [PATCH 2263/3095] Add 'openstack server migration list --type' option Another gap with novaclient closed. Change-Id: Id3ca95ceda6f438fa72496ab9ab15ac09bb64fa5 Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server.py | 19 +++++++++++++++---- .../tests/unit/compute/v2/test_server.py | 2 ++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 2fa0c52403..dec6eb62e6 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1889,6 +1889,14 @@ def get_parser(self, prog_name): metavar='', help=_('Filter migrations by status') ) + parser.add_argument( + '--type', + metavar='', + choices=[ + 'evacuation', 'live-migration', 'cold-migration', 'resize', + ], + help=_('Filter migrations by type'), + ) parser.add_argument( '--marker', metavar='', @@ -1964,10 +1972,6 @@ def print_migrations(self, parsed_args, compute_client, migrations): if compute_client.api_version >= api_versions.APIVersion("2.59"): columns.insert(0, "UUID") - # TODO(brinzhang): It also suppports filter migrations by type - # since 2.1. https://review.opendev.org/#/c/675117 supported - # filtering the migrations by 'migration_type' and 'source_compute' - # in novaclient, that will be added in OSC by follow-up. if compute_client.api_version >= api_versions.APIVersion("2.23"): columns.insert(0, "Id") columns.insert(len(columns) - 2, "Type") @@ -1992,6 +1996,13 @@ def take_action(self, parsed_args): 'status': parsed_args.status, } + if parsed_args.type: + migration_type = parsed_args.type + # we're using an alias because the default value is confusing + if migration_type == 'cold-migration': + migration_type = 'migration' + search_opts['type'] = migration_type + if parsed_args.marker: if compute_client.api_version < api_versions.APIVersion('2.59'): msg = _( diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 4b000180ae..51a3beb557 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -3781,6 +3781,7 @@ def test_server_migration_list(self): '--server', 'server1', '--host', 'host1', '--status', 'migrating', + '--type', 'cold-migration', ] verifylist = [ ('server', 'server1'), @@ -3795,6 +3796,7 @@ def test_server_migration_list(self): 'status': 'migrating', 'host': 'host1', 'server': 'server1', + 'type': 'migration', } self.migrations_mock.list.assert_called_with(**kwargs) From ab0b1fe885ee0a210a58008b631521025be7f3eb Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Tue, 13 Oct 2020 15:35:21 +0100 Subject: [PATCH 2264/3095] Validate 'server group create --policy' option We were documenting that some of these policies were only supported with specific microversions, however, we weren't actually enforcing that, leading to a poor user experience. Correct this. Change-Id: Ic3c555226a220efd9b0f27edffccf6c4c95c2747 Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server_group.py | 25 ++++++++--- .../unit/compute/v2/test_server_group.py | 43 +++++++++++++++++++ 2 files changed, 63 insertions(+), 5 deletions(-) diff --git a/openstackclient/compute/v2/server_group.py b/openstackclient/compute/v2/server_group.py index 1af6e28dc7..a33632446c 100644 --- a/openstackclient/compute/v2/server_group.py +++ b/openstackclient/compute/v2/server_group.py @@ -56,12 +56,18 @@ def get_parser(self, prog_name): parser.add_argument( '--policy', metavar='', + choices=[ + 'affinity', + 'anti-affinity', + 'soft-affinity', + 'soft-anti-affinity', + ], default='affinity', - help=_("Add a policy to " - "('affinity' or 'anti-affinity', " - "defaults to 'affinity'). Specify --os-compute-api-version " - "2.15 or higher for the 'soft-affinity' or " - "'soft-anti-affinity' policy.") + help=_( + "Add a policy to " + "Specify --os-compute-api-version 2.15 or higher for the " + "'soft-affinity' or 'soft-anti-affinity' policy." + ) ) return parser @@ -69,9 +75,18 @@ def take_action(self, parsed_args): compute_client = self.app.client_manager.compute info = {} + if parsed_args.policy in ('soft-affinity', 'soft-anti-affinity'): + if compute_client.api_version < api_versions.APIVersion('2.15'): + msg = _( + '--os-compute-api-version 2.15 or greater is required to ' + 'support the %s policy' + ) + raise exceptions.CommandError(msg % parsed_args.policy) + policy_arg = {'policies': [parsed_args.policy]} if compute_client.api_version >= api_versions.APIVersion("2.64"): policy_arg = {'policy': parsed_args.policy} + server_group = compute_client.server_groups.create( name=parsed_args.name, **policy_arg) diff --git a/openstackclient/tests/unit/compute/v2/test_server_group.py b/openstackclient/tests/unit/compute/v2/test_server_group.py index 359cd2bd02..bf0ea0ba45 100644 --- a/openstackclient/tests/unit/compute/v2/test_server_group.py +++ b/openstackclient/tests/unit/compute/v2/test_server_group.py @@ -90,6 +90,28 @@ def setUp(self): self.cmd = server_group.CreateServerGroup(self.app, None) def test_server_group_create(self): + arglist = [ + '--policy', 'anti-affinity', + 'affinity_group', + ] + verifylist = [ + ('policy', 'anti-affinity'), + ('name', 'affinity_group'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.server_groups_mock.create.assert_called_once_with( + name=parsed_args.name, + policies=[parsed_args.policy], + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_server_group_create_with_soft_policies(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.15') + arglist = [ '--policy', 'soft-anti-affinity', 'affinity_group', @@ -108,6 +130,27 @@ def test_server_group_create(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_server_group_create_with_soft_policies_pre_v215(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.14') + + arglist = [ + '--policy', 'soft-anti-affinity', + 'affinity_group', + ] + verifylist = [ + ('policy', 'soft-anti-affinity'), + ('name', 'affinity_group'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.15 or greater is required', + str(ex)) + def test_server_group_create_v264(self): self.app.client_manager.compute.api_version = api_versions.APIVersion( '2.64') From 2f76bfa3a69b6867491a6c5f0bf3c7e9f62743ca Mon Sep 17 00:00:00 2001 From: tianhui Date: Fri, 18 May 2018 18:59:37 +0800 Subject: [PATCH 2265/3095] Compute: Add tags support for server Change-Id: If065602792958ff0145ae9f2e05f5b7a3177905c Story: 2002006 Task: 19641 --- openstackclient/compute/v2/server.py | 115 +++++++- .../tests/unit/compute/v2/test_server.py | 260 ++++++++++++++++++ .../server-add-tag-63f9cd01dbd82d1b.yaml | 14 + 3 files changed, 388 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/server-add-tag-63f9cd01dbd82d1b.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 89ea006709..09842f8874 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -828,6 +828,18 @@ def get_parser(self, prog_name): action='store_true', help=_('Wait for build to complete'), ) + parser.add_argument( + '--tag', + metavar='', + action='append', + default=[], + dest='tags', + help=_( + 'Tags for the server. ' + 'Specify multiple times to add multiple tags. ' + '(supported by --os-compute-api-version 2.52 or above)' + ), + ) return parser def take_action(self, parsed_args): @@ -1141,6 +1153,16 @@ def _match_image(image_api, wanted_properties): if parsed_args.description: boot_kwargs['description'] = parsed_args.description + if parsed_args.tags: + if compute_client.api_version < api_versions.APIVersion('2.52'): + msg = _( + '--os-compute-api-version 2.52 or greater is required to ' + 'support the --tag option' + ) + raise exceptions.CommandError(msg) + + boot_kwargs['tags'] = parsed_args.tags + if parsed_args.host: if compute_client.api_version < api_versions.APIVersion("2.74"): msg = _("Specifying --host is not supported for " @@ -1408,6 +1430,30 @@ def get_parser(self, prog_name): help=_('Only display unlocked servers. ' 'Requires ``--os-compute-api-version`` 2.73 or greater.'), ) + parser.add_argument( + '--tags', + metavar='', + action='append', + default=[], + dest='tags', + help=_( + 'Only list servers with the specified tag. ' + 'Specify multiple times to filter on multiple tags. ' + '(supported by --os-compute-api-version 2.26 or above)' + ), + ) + parser.add_argument( + '--not-tags', + metavar='', + action='append', + default=[], + dest='not_tags', + help=_( + 'Only list servers without the specified tag. ' + 'Specify multiple times to filter on multiple tags. ' + '(supported by --os-compute-api-version 2.26 or above)' + ), + ) return parser def take_action(self, parsed_args): @@ -1463,6 +1509,27 @@ def take_action(self, parsed_args): 'changes-before': parsed_args.changes_before, 'changes-since': parsed_args.changes_since, } + + if parsed_args.tags: + if compute_client.api_version < api_versions.APIVersion('2.26'): + msg = _( + '--os-compute-api-version 2.26 or greater is required to ' + 'support the --tag option' + ) + raise exceptions.CommandError(msg) + + search_opts['tags'] = parsed_args.tags + + if parsed_args.not_tags: + if compute_client.api_version < api_versions.APIVersion('2.26'): + msg = _( + '--os-compute-api-version 2.26 or greater is required to ' + 'support the --not-tag option' + ) + raise exceptions.CommandError(msg) + + search_opts['not-tags'] = parsed_args.not_tags + support_locked = (compute_client.api_version >= api_versions.APIVersion('2.73')) if not support_locked and (parsed_args.locked or parsed_args.unlocked): @@ -2795,6 +2862,18 @@ def get_parser(self, prog_name): help=_('New server description (supported by ' '--os-compute-api-version 2.19 or above)'), ) + parser.add_argument( + '--tag', + metavar='', + action='append', + default=[], + dest='tags', + help=_( + 'Tag for the server. ' + 'Specify multiple times to add multiple tags. ' + '(supported by --os-compute-api-version 2.26 or above)' + ), + ) return parser def take_action(self, parsed_args): @@ -2833,6 +2912,17 @@ def take_action(self, parsed_args): raise exceptions.CommandError(msg) server.update(description=parsed_args.description) + if parsed_args.tags: + if server.api_version < api_versions.APIVersion('2.26'): + msg = _( + '--os-compute-api-version 2.26 or greater is required to ' + 'support the --tag option' + ) + raise exceptions.CommandError(msg) + + for tag in parsed_args.tags: + server.add_tag(tag=tag) + class ShelveServer(command.Command): _description = _("Shelve server(s)") @@ -3174,7 +3264,7 @@ def take_action(self, parsed_args): class UnsetServer(command.Command): - _description = _("Unset server properties") + _description = _("Unset server properties and tags") def get_parser(self, prog_name): parser = super(UnsetServer, self).get_parser(prog_name) @@ -3198,6 +3288,18 @@ def get_parser(self, prog_name): help=_('Unset server description (supported by ' '--os-compute-api-version 2.19 or above)'), ) + parser.add_argument( + '--tag', + metavar='', + action='append', + default=[], + dest='tags', + help=_( + 'Tag to remove from the server. ' + 'Specify multiple times to remove multiple tags. ' + '(supported by --os-compute-api-version 2.26 or later' + ), + ) return parser def take_action(self, parsed_args): @@ -3223,6 +3325,17 @@ def take_action(self, parsed_args): description="", ) + if parsed_args.tags: + if compute_client.api_version < api_versions.APIVersion('2.26'): + msg = _( + '--os-compute-api-version 2.26 or greater is required to ' + 'support the --tag option' + ) + raise exceptions.CommandError(msg) + + for tag in parsed_args.tags: + compute_client.servers.delete_tag(server, tag=tag) + class UnshelveServer(command.Command): _description = _("Unshelve server(s)") diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 5f1d5d0620..544e013740 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -2434,6 +2434,87 @@ def test_server_create_with_description_api_older(self): self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) + def test_server_create_with_tag(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.52') + + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--tag', 'tag1', + '--tag', 'tag2', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('tags', ['tag1', 'tag2']), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'meta': None, + 'files': {}, + 'reservation_id': None, + 'min_count': 1, + 'max_count': 1, + 'security_groups': [], + 'userdata': None, + 'key_name': None, + 'availability_zone': None, + 'block_device_mapping_v2': [], + 'admin_pass': None, + 'nics': 'auto', + 'scheduler_hints': {}, + 'config_drive': None, + 'tags': ['tag1', 'tag2'], + } + # ServerManager.create(name, image, flavor, **kwargs) + self.servers_mock.create.assert_called_with( + self.new_server.name, + self.image, + self.flavor, + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) + self.assertFalse(self.images_mock.called) + self.assertFalse(self.flavors_mock.called) + + def test_server_create_with_tag_pre_v252(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.51') + + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--tag', 'tag1', + '--tag', 'tag2', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('tags', ['tag1', 'tag2']), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.52 or greater is required', + str(ex)) + def test_server_create_with_host_v274(self): # Explicit host is supported for nova api version 2.74 or above @@ -3206,6 +3287,7 @@ def test_server_list_v266_with_changes_before(self): self.search_opts['changes-before'] = '2016-03-05T06:27:59Z' self.search_opts['deleted'] = True + self.servers_mock.list.assert_called_with(**self.kwargs) self.assertEqual(self.columns, columns) @@ -3298,6 +3380,92 @@ def test_server_list_v269_with_partial_constructs(self): 'UNKNOWN', '', '', '') self.assertEqual(expected_row, partial_server) + def test_server_list_with_tag(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.26') + + arglist = [ + '--tag', 'tag1', + '--tag', 'tag2', + ] + verifylist = [ + ('tags', ['tag1', 'tag2']), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.search_opts['tags'] = ['tag1', 'tag2'] + + self.servers_mock.list.assert_called_with(**self.kwargs) + + self.assertEqual(self.columns, columns) + self.assertEqual(tuple(self.data), tuple(data)) + + def test_server_list_with_tag_pre_v225(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.25') + + arglist = [ + '--tag', 'tag1', + '--tag', 'tag2', + ] + verifylist = [ + ('tags', ['tag1', 'tag2']), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.26 or greater is required', + str(ex)) + + def test_server_list_with_not_tag(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.26') + + arglist = [ + '--not-tag', 'tag1', + '--not-tag', 'tag2', + ] + verifylist = [ + ('not_tags', ['tag1', 'tag2']), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.search_opts['not-tags'] = ['tag1', 'tag2'] + + self.servers_mock.list.assert_called_with(**self.kwargs) + + self.assertEqual(self.columns, columns) + self.assertEqual(tuple(self.data), tuple(data)) + + def test_server_list_with_not_tag_pre_v226(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.25') + + arglist = [ + '--not-tag', 'tag1', + '--not-tag', 'tag2', + ] + verifylist = [ + ('not_tags', ['tag1', 'tag2']), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.26 or greater is required', + str(ex)) + class TestServerLock(TestServer): @@ -5388,6 +5556,8 @@ def setUp(self): 'update': None, 'reset_state': None, 'change_password': None, + 'add_tag': None, + 'set_tags': None, } self.fake_servers = self.setup_servers_mock(2) @@ -5528,6 +5698,50 @@ def test_server_set_with_description_api_older(self): self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) + def test_server_set_with_tag(self): + self.fake_servers[0].api_version = api_versions.APIVersion('2.26') + + arglist = [ + '--tag', 'tag1', + '--tag', 'tag2', + 'foo_vm', + ] + verifylist = [ + ('tags', ['tag1', 'tag2']), + ('server', 'foo_vm'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.fake_servers[0].add_tag.assert_has_calls([ + mock.call(tag='tag1'), + mock.call(tag='tag2'), + ]) + self.assertIsNone(result) + + def test_server_set_with_tag_pre_v226(self): + self.fake_servers[0].api_version = api_versions.APIVersion('2.25') + + arglist = [ + '--tag', 'tag1', + '--tag', 'tag2', + 'foo_vm', + ] + verifylist = [ + ('tags', ['tag1', 'tag2']), + ('server', 'foo_vm'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.26 or greater is required', + str(ex)) + class TestServerShelve(TestServer): @@ -5853,6 +6067,52 @@ def test_server_unset_with_description_api_older(self): self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) + def test_server_unset_with_tag(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.26') + + arglist = [ + '--tag', 'tag1', + '--tag', 'tag2', + 'foo_vm', + ] + verifylist = [ + ('tags', ['tag1', 'tag2']), + ('server', 'foo_vm'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.assertIsNone(result) + + self.servers_mock.delete_tag.assert_has_calls([ + mock.call(self.fake_server, tag='tag1'), + mock.call(self.fake_server, tag='tag2'), + ]) + + def test_server_unset_with_tag_pre_v226(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.25') + + arglist = [ + '--tag', 'tag1', + '--tag', 'tag2', + 'foo_vm', + ] + verifylist = [ + ('tags', ['tag1', 'tag2']), + ('server', 'foo_vm'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.26 or greater is required', + str(ex)) + class TestServerUnshelve(TestServer): diff --git a/releasenotes/notes/server-add-tag-63f9cd01dbd82d1b.yaml b/releasenotes/notes/server-add-tag-63f9cd01dbd82d1b.yaml new file mode 100644 index 0000000000..78e7482c48 --- /dev/null +++ b/releasenotes/notes/server-add-tag-63f9cd01dbd82d1b.yaml @@ -0,0 +1,14 @@ +--- +features: + - Add ``--tag`` option to ``server create`` command to add tags when creating + a server. + Only available starting with ``--os-compute-api-version 2.52``. + - Add ``--tag`` option to ``server set`` command to add a tag to an + existing server. + Only available starting with ``--os-compute-api-version 2.26``. + - Add ``--tag`` options to ``server unset`` command to remove a tag from an + existing server. + Only available starting with ``--os-compute-api-version 2.26``. + - Add ``--tags`` and ``--not-tags`` options to ``server list`` command to + list instances with and without the specified tag(s), respectively. + Only available starting with ``--os-compute-api-version 2.26``. From 98a0016cfa1d39bcc37144f0c7700e9a9d6b2c0b Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Tue, 29 Sep 2020 16:30:34 +0100 Subject: [PATCH 2266/3095] Add support for 'keypairs list --user' parameter This has been supported by nova and novaclient since the veritable dark ages. Add it to OSC. Change-Id: Ifc95e7dd6c00807c80e87e10046ab154d0989014 Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/keypair.py | 35 +++++++++- .../tests/unit/compute/v2/test_keypair.py | 66 ++++++++++++++++++- ...keypairs-user-filter-e1ce57a4c09c278b.yaml | 6 ++ 3 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/add-keypairs-user-filter-e1ce57a4c09c278b.yaml diff --git a/openstackclient/compute/v2/keypair.py b/openstackclient/compute/v2/keypair.py index 6affaca302..ae653e76ee 100644 --- a/openstackclient/compute/v2/keypair.py +++ b/openstackclient/compute/v2/keypair.py @@ -26,6 +26,7 @@ from osc_lib import utils from openstackclient.i18n import _ +from openstackclient.identity import common as identity_common LOG = logging.getLogger(__name__) @@ -163,13 +164,45 @@ def take_action(self, parsed_args): class ListKeypair(command.Lister): _description = _("List key fingerprints") + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + '--user', + metavar='', + help=_( + 'Show keypairs for another user (admin only) (name or ID). ' + 'Requires ``--os-compute-api-version`` 2.10 or greater.' + ), + ) + identity_common.add_user_domain_option_to_parser(parser) + return parser + def take_action(self, parsed_args): compute_client = self.app.client_manager.compute + identity_client = self.app.client_manager.identity + + kwargs = {} + + if parsed_args.user: + if compute_client.api_version < api_versions.APIVersion('2.10'): + msg = _( + '--os-compute-api-version 2.10 or greater is required to ' + 'support the --user option' + ) + raise exceptions.CommandError(msg) + + kwargs['user_id'] = identity_common.find_user( + identity_client, + parsed_args.user, + parsed_args.user_domain, + ).id + + data = compute_client.keypairs.list(**kwargs) + columns = ( "Name", "Fingerprint" ) - data = compute_client.keypairs.list() if compute_client.api_version >= api_versions.APIVersion('2.2'): columns += ("Type", ) diff --git a/openstackclient/tests/unit/compute/v2/test_keypair.py b/openstackclient/tests/unit/compute/v2/test_keypair.py index ca3bfe7c8b..5e6a47414f 100644 --- a/openstackclient/tests/unit/compute/v2/test_keypair.py +++ b/openstackclient/tests/unit/compute/v2/test_keypair.py @@ -13,6 +13,7 @@ # under the License. # +import copy from unittest import mock from unittest.mock import call import uuid @@ -23,6 +24,8 @@ from openstackclient.compute.v2 import keypair from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes +from openstackclient.tests.unit import fakes +from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes from openstackclient.tests.unit import utils as tests_utils @@ -307,6 +310,14 @@ class TestKeypairList(TestKeypair): def setUp(self): super(TestKeypairList, self).setUp() + self.users_mock = self.app.client_manager.identity.users + self.users_mock.reset_mock() + self.users_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.USER), + loaded=True, + ) + self.keypairs_mock.list.return_value = self.keypairs # Get the command object to test @@ -334,8 +345,8 @@ def test_keypair_list_no_options(self): ) def test_keypair_list_v22(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.2') + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.2') arglist = [] verifylist = [] @@ -361,6 +372,57 @@ def test_keypair_list_v22(self): tuple(data) ) + def test_keypair_list_with_user(self): + + # Filtering by user is support for nova api 2.10 or above + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.10') + + arglist = [ + '--user', identity_fakes.user_name, + ] + verifylist = [ + ('user', identity_fakes.user_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.users_mock.get.assert_called_with(identity_fakes.user_name) + self.keypairs_mock.list.assert_called_with( + user_id=identity_fakes.user_id, + ) + + self.assertEqual(('Name', 'Fingerprint', 'Type'), columns) + self.assertEqual( + (( + self.keypairs[0].name, + self.keypairs[0].fingerprint, + self.keypairs[0].type, + ), ), + tuple(data) + ) + + def test_keypair_list_with_user_pre_v210(self): + + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.9') + + arglist = [ + '--user', identity_fakes.user_name, + ] + verifylist = [ + ('user', identity_fakes.user_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.10 or greater is required', str(ex)) + class TestKeypairShow(TestKeypair): diff --git a/releasenotes/notes/add-keypairs-user-filter-e1ce57a4c09c278b.yaml b/releasenotes/notes/add-keypairs-user-filter-e1ce57a4c09c278b.yaml new file mode 100644 index 0000000000..6e82091a0f --- /dev/null +++ b/releasenotes/notes/add-keypairs-user-filter-e1ce57a4c09c278b.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + It is now possible to list the keypairs for a specific user using the + ``--user`` parameter. This is an admin-only action by default and requires + Compute API microversion 2.10 or later. From 5645fad7622a18e4f3c550ee7d409bf1b685a1a5 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Tue, 29 Sep 2020 16:51:12 +0100 Subject: [PATCH 2267/3095] Add support for 'keypairs list --project' parameter It would be lovely to do this server side but doing so requires a new microversion, a blueprint and a spec. This is less performant but should do the trick for the odd time users want to do this. Change-Id: I26e7d38966304dd67be5da8ed0bb24f87191b82f Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/keypair.py | 44 +++++++-- .../tests/unit/compute/v2/test_keypair.py | 95 +++++++++++++++++-- ...pairs-project-filter-99cb6938f247927f.yaml | 6 ++ 3 files changed, 130 insertions(+), 15 deletions(-) create mode 100644 releasenotes/notes/add-keypairs-project-filter-99cb6938f247927f.yaml diff --git a/openstackclient/compute/v2/keypair.py b/openstackclient/compute/v2/keypair.py index ae653e76ee..8c365cf0fe 100644 --- a/openstackclient/compute/v2/keypair.py +++ b/openstackclient/compute/v2/keypair.py @@ -166,7 +166,8 @@ class ListKeypair(command.Lister): def get_parser(self, prog_name): parser = super().get_parser(prog_name) - parser.add_argument( + user_group = parser.add_mutually_exclusive_group() + user_group.add_argument( '--user', metavar='', help=_( @@ -175,15 +176,44 @@ def get_parser(self, prog_name): ), ) identity_common.add_user_domain_option_to_parser(parser) + user_group.add_argument( + '--project', + metavar='', + help=_( + 'Show keypairs for all users associated with project ' + '(admin only) (name or ID). ' + 'Requires ``--os-compute-api-version`` 2.10 or greater.' + ), + ) + identity_common.add_project_domain_option_to_parser(parser) return parser def take_action(self, parsed_args): compute_client = self.app.client_manager.compute identity_client = self.app.client_manager.identity - kwargs = {} + if parsed_args.project: + if compute_client.api_version < api_versions.APIVersion('2.10'): + msg = _( + '--os-compute-api-version 2.10 or greater is required to ' + 'support the --project option' + ) + raise exceptions.CommandError(msg) - if parsed_args.user: + # NOTE(stephenfin): This is done client side because nova doesn't + # currently support doing so server-side. If this is slow, we can + # think about spinning up a threadpool or similar. + project = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + users = identity_client.users.list(tenant_id=project) + + data = [] + for user in users: + data.extend(compute_client.keypairs.list(user_id=user.id)) + elif parsed_args.user: if compute_client.api_version < api_versions.APIVersion('2.10'): msg = _( '--os-compute-api-version 2.10 or greater is required to ' @@ -191,13 +221,15 @@ def take_action(self, parsed_args): ) raise exceptions.CommandError(msg) - kwargs['user_id'] = identity_common.find_user( + user = identity_common.find_user( identity_client, parsed_args.user, parsed_args.user_domain, - ).id + ) - data = compute_client.keypairs.list(**kwargs) + data = compute_client.keypairs.list(user_id=user.id) + else: + data = compute_client.keypairs.list() columns = ( "Name", diff --git a/openstackclient/tests/unit/compute/v2/test_keypair.py b/openstackclient/tests/unit/compute/v2/test_keypair.py index 5e6a47414f..286cbb0929 100644 --- a/openstackclient/tests/unit/compute/v2/test_keypair.py +++ b/openstackclient/tests/unit/compute/v2/test_keypair.py @@ -310,14 +310,6 @@ class TestKeypairList(TestKeypair): def setUp(self): super(TestKeypairList, self).setUp() - self.users_mock = self.app.client_manager.identity.users - self.users_mock.reset_mock() - self.users_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.USER), - loaded=True, - ) - self.keypairs_mock.list.return_value = self.keypairs # Get the command object to test @@ -378,6 +370,14 @@ def test_keypair_list_with_user(self): self.app.client_manager.compute.api_version = \ api_versions.APIVersion('2.10') + users_mock = self.app.client_manager.identity.users + users_mock.reset_mock() + users_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.USER), + loaded=True, + ) + arglist = [ '--user', identity_fakes.user_name, ] @@ -388,7 +388,7 @@ def test_keypair_list_with_user(self): columns, data = self.cmd.take_action(parsed_args) - self.users_mock.get.assert_called_with(identity_fakes.user_name) + users_mock.get.assert_called_with(identity_fakes.user_name) self.keypairs_mock.list.assert_called_with( user_id=identity_fakes.user_id, ) @@ -423,6 +423,83 @@ def test_keypair_list_with_user_pre_v210(self): self.assertIn( '--os-compute-api-version 2.10 or greater is required', str(ex)) + def test_keypair_list_with_project(self): + + # Filtering by user is support for nova api 2.10 or above + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.10') + + projects_mock = self.app.client_manager.identity.tenants + projects_mock.reset_mock() + projects_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.PROJECT), + loaded=True, + ) + + users_mock = self.app.client_manager.identity.users + users_mock.reset_mock() + users_mock.list.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.USER), + loaded=True, + ), + ] + + arglist = ['--project', identity_fakes.project_name] + verifylist = [('project', identity_fakes.project_name)] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + projects_mock.get.assert_called_with(identity_fakes.project_name) + users_mock.list.assert_called_with(tenant_id=identity_fakes.project_id) + self.keypairs_mock.list.assert_called_with( + user_id=identity_fakes.user_id, + ) + + self.assertEqual(('Name', 'Fingerprint', 'Type'), columns) + self.assertEqual( + (( + self.keypairs[0].name, + self.keypairs[0].fingerprint, + self.keypairs[0].type, + ), ), + tuple(data) + ) + + def test_keypair_list_with_project_pre_v210(self): + + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.9') + + arglist = ['--project', identity_fakes.project_name] + verifylist = [('project', identity_fakes.project_name)] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.10 or greater is required', str(ex)) + + def test_keypair_list_conflicting_user_options(self): + + # Filtering by user is support for nova api 2.10 or above + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.10') + + arglist = [ + '--user', identity_fakes.user_name, + '--project', identity_fakes.project_name, + ] + + self.assertRaises( + tests_utils.ParserException, + self.check_parser, self.cmd, arglist, None) + class TestKeypairShow(TestKeypair): diff --git a/releasenotes/notes/add-keypairs-project-filter-99cb6938f247927f.yaml b/releasenotes/notes/add-keypairs-project-filter-99cb6938f247927f.yaml new file mode 100644 index 0000000000..77e7afe6ba --- /dev/null +++ b/releasenotes/notes/add-keypairs-project-filter-99cb6938f247927f.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + It is now possible to list the keypairs for all users in a project using + the ``--project`` parameter. This is an admin-only action by default and + requires Compute API microversion 2.10 or later. From f464bba792b45691e8667dcf966ac703119ff3c0 Mon Sep 17 00:00:00 2001 From: "wu.chunyang" Date: Wed, 21 Oct 2020 23:40:27 +0800 Subject: [PATCH 2268/3095] Remove the unused coding style modules Python modules related to coding style checks (listed in blacklist.txt in openstack/requirements repo) are dropped from lower-constraints.txt they are not needed during installation. Change-Id: Id735af397e8d41ad3ff583386844d4131fc9e12c --- lower-constraints.txt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lower-constraints.txt b/lower-constraints.txt index 2fa6586e08..74eac6403b 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -21,15 +21,12 @@ eventlet==0.18.2 extras==1.0.0 fasteners==0.7.0 fixtures==3.0.0 -flake8-import-order==0.13 -flake8==2.6.2 future==0.16.0 futurist==2.1.0 gitdb==0.6.4 GitPython==1.0.1 gnocchiclient==3.3.1 greenlet==0.4.15 -hacking==2.0.0 httplib2==0.9.1 idna==2.6 iso8601==0.1.11 @@ -42,7 +39,6 @@ keystoneauth1==3.18.0 kombu==4.0.0 linecache2==1.0.0 MarkupSafe==1.1.0 -mccabe==0.2.1 monotonic==0.6 mox3==0.20.0 msgpack-python==0.4.0 @@ -78,7 +74,6 @@ prettytable==0.7.2 pyasn1==0.1.8 pycodestyle==2.0.0 pycparser==2.18 -pyflakes==0.8.1 pyinotify==0.9.6 pyOpenSSL==17.1.0 pyparsing==2.1.0 From 5f650853f7908bc04555ea3ee393798502b79fed Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 22 Oct 2020 09:52:57 +0100 Subject: [PATCH 2269/3095] Remove references to setuptools Newer versions of cliff and stevedore use importlib rather than setuptools to work with entry points. Replace any references to "setuptools' entry points mechanism" with "Python's entry points mechanism". Change-Id: Iae36155685ee37ab5e38a0c173110a5ece33d05d Signed-off-by: Stephen Finucane --- doc/source/cli/commands.rst | 2 +- doc/source/contributor/command-wrappers.rst | 2 +- doc/source/contributor/plugins.rst | 5 ++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/doc/source/cli/commands.rst b/doc/source/cli/commands.rst index 97a829b558..497c79f0b9 100644 --- a/doc/source/cli/commands.rst +++ b/doc/source/cli/commands.rst @@ -306,7 +306,7 @@ Implementation -------------- The command structure is designed to support seamless addition of plugin -command modules via ``setuptools`` entry points. The plugin commands must +command modules via Python's *entry points* mechanism. The plugin commands must be subclasses of Cliff's ``command.Command`` object. See :ref:`plugins` for more information. diff --git a/doc/source/contributor/command-wrappers.rst b/doc/source/contributor/command-wrappers.rst index 2a5d92239d..cf4eece23a 100644 --- a/doc/source/contributor/command-wrappers.rst +++ b/doc/source/contributor/command-wrappers.rst @@ -7,7 +7,7 @@ We do this with a message logged at WARNING level before any command output is emitted. OpenStackClient command classes are derived from the ``cliff`` classes. -Cliff uses ``setuptools`` entry points for dispatching the parsed command +Cliff uses Python's *entry points* mechanism for dispatching the parsed command to the respective handler classes. This lends itself to modifying the command execution at run-time. diff --git a/doc/source/contributor/plugins.rst b/doc/source/contributor/plugins.rst index 374b274e6a..7ea48edd38 100644 --- a/doc/source/contributor/plugins.rst +++ b/doc/source/contributor/plugins.rst @@ -5,9 +5,8 @@ Plugins ======= The OpenStackClient plugin system is designed so that the plugin need only be -properly installed for OSC to find and use it. It utilizes the -``setuptools`` entry points mechanism to advertise to OSC the -plugin module and supported commands. +properly installed for OSC to find and use it. It utilizes Python's *entry +points* mechanism to advertise to OSC the plugin module and supported commands. Adoption ======== From e05d39abb56b2bf048533b84f44406d415210ffd Mon Sep 17 00:00:00 2001 From: Artem Goncharov Date: Fri, 11 Sep 2020 14:31:18 +0200 Subject: [PATCH 2270/3095] Switch console url show operations to SDK Switch from using novaclient to SDK for openstack console url show operation. Depends-On: https://review.opendev.org/756286 Change-Id: Ibe247825148788c549c2c1e991aae92338cdf557 --- lower-constraints.txt | 4 +- openstackclient/compute/v2/console.py | 35 ++++---- .../tests/unit/compute/v2/test_console.py | 86 +++++++------------ requirements.txt | 4 +- 4 files changed, 54 insertions(+), 75 deletions(-) diff --git a/lower-constraints.txt b/lower-constraints.txt index 74eac6403b..52766b2290 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -5,7 +5,7 @@ asn1crypto==0.23.0 bandit==1.1.0 cachetools==2.0.0 cffi==1.14.0 -cliff==2.8.0 +cliff==3.4.0 cmd2==0.8.0 contextlib2==0.4.0 coverage==4.0 @@ -48,7 +48,7 @@ netifaces==0.10.4 openstacksdk==0.48.0 os-service-types==1.7.0 os-testr==1.0.0 -osc-lib==2.0.0 +osc-lib==2.2.0 osc-placement==1.7.0 oslo.concurrency==3.26.0 oslo.config==5.2.0 diff --git a/openstackclient/compute/v2/console.py b/openstackclient/compute/v2/console.py index f0abaf4cf4..0ab5c8a2a3 100644 --- a/openstackclient/compute/v2/console.py +++ b/openstackclient/compute/v2/console.py @@ -22,6 +22,15 @@ from openstackclient.i18n import _ +def _get_console_columns(item): + # To maintain backwards compatibility we need to rename sdk props to + # whatever OSC was using before + column_map = {} + hidden_columns = ['id', 'links', 'location', 'name'] + return utils.get_osc_show_columns_for_sdk_resource( + item, column_map, hidden_columns) + + class ShowConsoleLog(command.Command): _description = _("Show server's console output") @@ -119,21 +128,15 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - server = utils.find_resource( - compute_client.servers, + compute_client = self.app.client_manager.sdk_connection.compute + server = compute_client.find_server( parsed_args.server, - ) + ignore_missing=False) + + data = compute_client.create_console(server.id, + console_type=parsed_args.url_type) + + display_columns, columns = _get_console_columns(data) + data = utils.get_dict_properties(data, columns) - data = server.get_console_url(parsed_args.url_type) - if not data: - return ({}, {}) - - info = {} - # NOTE(Rui Chen): Return 'remote_console' in compute microversion API - # 2.6 and later, return 'console' in compute - # microversion API from 2.0 to 2.5, do compatibility - # handle for different microversion API. - console_data = data.get('remote_console', data.get('console')) - info.update(console_data) - return zip(*sorted(info.items())) + return (display_columns, data) diff --git a/openstackclient/tests/unit/compute/v2/test_console.py b/openstackclient/tests/unit/compute/v2/test_console.py index 1c6d658bc1..db9603c9b9 100644 --- a/openstackclient/tests/unit/compute/v2/test_console.py +++ b/openstackclient/tests/unit/compute/v2/test_console.py @@ -32,9 +32,6 @@ def setUp(self): self.sdk_client.find_server = mock.Mock() self.sdk_client.get_server_console_output = mock.Mock() - self.servers_mock = self.app.client_manager.compute.servers - self.servers_mock.reset_mock() - class TestConsoleLog(TestConsole): _server = compute_fakes.FakeServer.create_one_server() @@ -107,18 +104,16 @@ def test_show_lines(self): class TestConsoleUrlShow(TestConsole): + _server = compute_fakes.FakeServer.create_one_server() def setUp(self): super(TestConsoleUrlShow, self).setUp() - fake_console_data = {'remote_console': {'url': 'http://localhost', - 'protocol': 'fake_protocol', - 'type': 'fake_type'}} - methods = { - 'get_console_url': fake_console_data - } - self.fake_server = compute_fakes.FakeServer.create_one_server( - methods=methods) - self.servers_mock.get.return_value = self.fake_server + self.sdk_client.find_server.return_value = self._server + fake_console_data = {'url': 'http://localhost', + 'protocol': 'fake_protocol', + 'type': 'fake_type'} + self.sdk_client.create_console = mock.Mock( + return_value=fake_console_data) self.columns = ( 'protocol', @@ -126,9 +121,9 @@ def setUp(self): 'url', ) self.data = ( - fake_console_data['remote_console']['protocol'], - fake_console_data['remote_console']['type'], - fake_console_data['remote_console']['url'] + fake_console_data['protocol'], + fake_console_data['type'], + fake_console_data['url'] ) self.cmd = console.ShowConsoleURL(self.app, None) @@ -143,7 +138,9 @@ def test_console_url_show_by_default(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.fake_server.get_console_url.assert_called_once_with('novnc') + self.sdk_client.create_console.assert_called_once_with( + self._server.id, + console_type='novnc') self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -158,7 +155,9 @@ def test_console_url_show_with_novnc(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.fake_server.get_console_url.assert_called_once_with('novnc') + self.sdk_client.create_console.assert_called_once_with( + self._server.id, + console_type='novnc') self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -173,7 +172,9 @@ def test_console_url_show_with_xvpvnc(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.fake_server.get_console_url.assert_called_once_with('xvpvnc') + self.sdk_client.create_console.assert_called_once_with( + self._server.id, + console_type='xvpvnc') self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -188,41 +189,12 @@ def test_console_url_show_with_spice(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.fake_server.get_console_url.assert_called_once_with( - 'spice-html5') + self.sdk_client.create_console.assert_called_once_with( + self._server.id, + console_type='spice-html5') self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) - def test_console_url_show_compatible(self): - methods = { - 'get_console_url': {'console': {'url': 'http://localhost', - 'type': 'fake_type'}}, - } - old_fake_server = compute_fakes.FakeServer.create_one_server( - methods=methods) - old_columns = ( - 'type', - 'url', - ) - old_data = ( - methods['get_console_url']['console']['type'], - methods['get_console_url']['console']['url'] - ) - arglist = [ - 'foo_vm', - ] - verifylist = [ - ('url_type', 'novnc'), - ('server', 'foo_vm'), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - with mock.patch.object(self.servers_mock, 'get', - return_value=old_fake_server): - columns, data = self.cmd.take_action(parsed_args) - old_fake_server.get_console_url.assert_called_once_with('novnc') - self.assertEqual(old_columns, columns) - self.assertEqual(old_data, data) - def test_console_url_show_with_rdp(self): arglist = [ '--rdp', @@ -234,8 +206,9 @@ def test_console_url_show_with_rdp(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.fake_server.get_console_url.assert_called_once_with( - 'rdp-html5') + self.sdk_client.create_console.assert_called_once_with( + self._server.id, + console_type='rdp-html5') self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -250,8 +223,9 @@ def test_console_url_show_with_serial(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.fake_server.get_console_url.assert_called_once_with( - 'serial') + self.sdk_client.create_console.assert_called_once_with( + self._server.id, + console_type='serial') self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -266,6 +240,8 @@ def test_console_url_show_with_mks(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.fake_server.get_console_url.assert_called_once_with('webmks') + self.sdk_client.create_console.assert_called_once_with( + self._server.id, + console_type='webmks') self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) diff --git a/requirements.txt b/requirements.txt index ee6b6241ae..99d409bf51 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,10 +3,10 @@ # process, which may cause wedges in the gate later. pbr!=2.1.0,>=2.0.0 # Apache-2.0 -cliff!=2.9.0,>=2.8.0 # Apache-2.0 +cliff>=3.4.0 # Apache-2.0 iso8601>=0.1.11 # MIT openstacksdk>=0.48.0 # Apache-2.0 -osc-lib>=2.0.0 # Apache-2.0 +osc-lib>=2.2.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 python-keystoneclient>=3.22.0 # Apache-2.0 python-novaclient>=15.1.0 # Apache-2.0 From 5b04a86d1f59b13014839a826ff3c530fe330235 Mon Sep 17 00:00:00 2001 From: Elod Illes Date: Wed, 28 Oct 2020 12:37:58 +0100 Subject: [PATCH 2271/3095] Replace deprecated UPPER_CONSTRAINTS_FILE variable UPPER_CONSTRAINTS_FILE is deprecated and TOX_CONSTRAINTS_FILE is the new environment variable name that replaces it [1]. [1] https://zuul-ci.org/docs/zuul-jobs/python-roles.html#rolevar-tox.tox_constraints_file Change-Id: Ic0d2dcf20f7929733deb10a275e5d67f9627428f --- tox.ini | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tox.ini b/tox.ini index c0f6dd5db1..663463e2f2 100644 --- a/tox.ini +++ b/tox.ini @@ -14,7 +14,7 @@ setenv = OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 OS_TEST_TIMEOUT=60 deps = - -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} + -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/test-requirements.txt -r{toxinidir}/requirements.txt commands = stestr run {posargs} @@ -88,7 +88,7 @@ commands = [testenv:venv] deps = - -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} + -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt -r{toxinidir}/doc/requirements.txt commands = {posargs} @@ -110,7 +110,7 @@ commands = [testenv:docs] deps = - -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} + -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/doc/requirements.txt commands = sphinx-build -a -E -W -d doc/build/doctrees -b html doc/source doc/build/html @@ -120,7 +120,7 @@ commands = [testenv:releasenotes] deps = - -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} + -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/doc/requirements.txt commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html From 9b02f37e8f06bafb16a139aa1c978b4dd939f35e Mon Sep 17 00:00:00 2001 From: likui Date: Thu, 29 Oct 2020 09:53:16 +0800 Subject: [PATCH 2272/3095] update lower-constraints.txt We also need to change the lower-constraint requirements to make them py3.8 compatible. See https://bugs.launchpad.net/nova/+bug/1886298 MarkupSafe==1.1.1 paramiko==2.7.1 Change-Id: I04a0fcd98327b9f41e24e19bcab97c813760f414 --- lower-constraints.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lower-constraints.txt b/lower-constraints.txt index 74eac6403b..32f6ee9e9b 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -38,7 +38,7 @@ jsonschema==2.6.0 keystoneauth1==3.18.0 kombu==4.0.0 linecache2==1.0.0 -MarkupSafe==1.1.0 +MarkupSafe==1.1.1 monotonic==0.6 mox3==0.20.0 msgpack-python==0.4.0 @@ -62,7 +62,7 @@ oslo.service==1.24.0 oslo.utils==3.33.0 oslotest==3.2.0 osprofiler==1.4.0 -paramiko==2.0.0 +paramiko==2.7.1 Paste==2.0.2 PasteDeploy==1.5.0 pbr==2.0.0 From 7c239403e98f7355c39aee696c2fd6f0c6328af5 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Mon, 12 Oct 2020 15:12:08 +0100 Subject: [PATCH 2273/3095] trivial: Rework 'CreateServer' function This rather complex function has had stuff tacked on over the years. Help make working with it a bit easier through liberal application of whitespace and some nicer indentation. Some option help text is improved based on changes to modern nova. Change-Id: I8154dd395dd904c3bcd180a7d0f9037b7e0be64f Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server.py | 295 ++++++++++++++++----------- 1 file changed, 178 insertions(+), 117 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 09842f8874..01132819d5 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -620,13 +620,15 @@ def get_parser(self, prog_name): disk_group.add_argument( '--volume', metavar='', - help=_('Create server using this volume as the boot disk (name ' - 'or ID).\n' - 'This option automatically creates a block device mapping ' - 'with a boot index of 0. On many hypervisors (libvirt/kvm ' - 'for example) this will be device vda. Do not create a ' - 'duplicate mapping using --block-device-mapping for this ' - 'volume.'), + help=_( + 'Create server using this volume as the boot disk (name or ID)' + '\n' + 'This option automatically creates a block device mapping ' + 'with a boot index of 0. On many hypervisors (libvirt/kvm ' + 'for example) this will be device vda. Do not create a ' + 'duplicate mapping using --block-device-mapping for this ' + 'volume.' + ), ) parser.add_argument( '--password', @@ -644,28 +646,34 @@ def get_parser(self, prog_name): metavar='', action='append', default=[], - help=_('Security group to assign to this server (name or ID) ' - '(repeat option to set multiple groups)'), + help=_( + 'Security group to assign to this server (name or ID) ' + '(repeat option to set multiple groups)' + ), ) parser.add_argument( '--key-name', metavar='', - help=_('Keypair to inject into this server (optional extension)'), + help=_('Keypair to inject into this server'), ) parser.add_argument( '--property', metavar='', action=parseractions.KeyValueAction, - help=_('Set a property on this server ' - '(repeat option to set multiple values)'), + help=_( + 'Set a property on this server ' + '(repeat option to set multiple values)' + ), ) parser.add_argument( '--file', metavar='', action='append', default=[], - help=_('File to inject into image before boot ' - '(repeat option to set multiple files)'), + help=_( + 'File to inject into image before boot ' + '(repeat option to set multiple files)' + ), ) parser.add_argument( '--user-data', @@ -675,8 +683,10 @@ def get_parser(self, prog_name): parser.add_argument( '--description', metavar='', - help=_('Set description for the server (supported by ' - '--os-compute-api-version 2.19 or above)'), + help=_( + 'Set description for the server ' + '(supported by --os-compute-api-version 2.19 or above)' + ), ) parser.add_argument( '--availability-zone', @@ -686,29 +696,35 @@ def get_parser(self, prog_name): parser.add_argument( '--host', metavar='', - help=_('Requested host to create servers. Admin only ' - 'by default. (supported by --os-compute-api-version 2.74 ' - 'or above)'), + help=_( + 'Requested host to create servers. ' + '(admin only) ' + '(supported by --os-compute-api-version 2.74 or above)' + ), ) parser.add_argument( '--hypervisor-hostname', metavar='', - help=_('Requested hypervisor hostname to create servers. Admin ' - 'only by default. (supported by --os-compute-api-version ' - '2.74 or above)'), + help=_( + 'Requested hypervisor hostname to create servers. ' + '(admin only) ' + '(supported by --os-compute-api-version 2.74 or above)' + ), ) parser.add_argument( '--boot-from-volume', metavar='', type=int, - help=_('When used in conjunction with the ``--image`` or ' - '``--image-property`` option, this option automatically ' - 'creates a block device mapping with a boot index of 0 ' - 'and tells the compute service to create a volume of the ' - 'given size (in GB) from the specified image and use it ' - 'as the root disk of the server. The root volume will not ' - 'be deleted when the server is deleted. This option is ' - 'mutually exclusive with the ``--volume`` option.') + help=_( + 'When used in conjunction with the ``--image`` or ' + '``--image-property`` option, this option automatically ' + 'creates a block device mapping with a boot index of 0 ' + 'and tells the compute service to create a volume of the ' + 'given size (in GB) from the specified image and use it ' + 'as the root disk of the server. The root volume will not ' + 'be deleted when the server is deleted. This option is ' + 'mutually exclusive with the ``--volume`` option.' + ) ) parser.add_argument( '--block-device-mapping', @@ -718,37 +734,40 @@ def get_parser(self, prog_name): # NOTE(RuiChen): Add '\n' at the end of line to put each item in # the separated line, avoid the help message looks # messy, see _SmartHelpFormatter in cliff. - help=_('Create a block device on the server.\n' - 'Block device mapping in the format\n' - '=:::\n' - ': block device name, like: vdb, xvdc ' - '(required)\n' - ': Name or ID of the volume, volume snapshot or image ' - '(required)\n' - ': volume, snapshot or image; default: volume ' - '(optional)\n' - ': volume size if create from image or snapshot ' - '(optional)\n' - ': true or false; default: false ' - '(optional)\n' - '(optional extension)'), + help=_( + 'Create a block device on the server.\n' + 'Block device mapping in the format\n' + '=:::\n' + ': block device name, like: vdb, xvdc ' + '(required)\n' + ': Name or ID of the volume, volume snapshot or image ' + '(required)\n' + ': volume, snapshot or image; default: volume ' + '(optional)\n' + ': volume size if create from image or snapshot ' + '(optional)\n' + ': true or false; default: false ' + '(optional)\n' + ), ) parser.add_argument( '--nic', metavar="", action='append', - help=_("Create a NIC on the server. " - "Specify option multiple times to create multiple NICs. " - "Either net-id or port-id must be provided, but not both. " - "net-id: attach NIC to network with this UUID, " - "port-id: attach NIC to port with this UUID, " - "v4-fixed-ip: IPv4 fixed address for NIC (optional), " - "v6-fixed-ip: IPv6 fixed address for NIC (optional), " - "none: (v2.37+) no network is attached, " - "auto: (v2.37+) the compute service will automatically " - "allocate a network. Specifying a --nic of auto or none " - "cannot be used with any other --nic value."), + help=_( + "Create a NIC on the server. " + "Specify option multiple times to create multiple NICs. " + "Either net-id or port-id must be provided, but not both. " + "net-id: attach NIC to network with this UUID, " + "port-id: attach NIC to port with this UUID, " + "v4-fixed-ip: IPv4 fixed address for NIC (optional), " + "v6-fixed-ip: IPv6 fixed address for NIC (optional), " + "none: (v2.37+) no network is attached, " + "auto: (v2.37+) the compute service will automatically " + "allocate a network. Specifying a --nic of auto or none " + "cannot be used with any other --nic value." + ), ) parser.add_argument( '--network', @@ -756,13 +775,15 @@ def get_parser(self, prog_name): action='append', dest='nic', type=_prefix_checked_value('net-id='), - help=_("Create a NIC on the server and connect it to network. " - "Specify option multiple times to create multiple NICs. " - "This is a wrapper for the '--nic net-id=' " - "parameter that provides simple syntax for the standard " - "use case of connecting a new server to a given network. " - "For more advanced use cases, refer to the '--nic' " - "parameter."), + help=_( + "Create a NIC on the server and connect it to network. " + "Specify option multiple times to create multiple NICs. " + "This is a wrapper for the '--nic net-id=' " + "parameter that provides simple syntax for the standard " + "use case of connecting a new server to a given network. " + "For more advanced use cases, refer to the '--nic' " + "parameter." + ), ) parser.add_argument( '--port', @@ -770,12 +791,14 @@ def get_parser(self, prog_name): action='append', dest='nic', type=_prefix_checked_value('port-id='), - help=_("Create a NIC on the server and connect it to port. " - "Specify option multiple times to create multiple NICs. " - "This is a wrapper for the '--nic port-id=' " - "parameter that provides simple syntax for the standard " - "use case of connecting a new server to a given port. For " - "more advanced use cases, refer to the '--nic' parameter."), + help=_( + "Create a NIC on the server and connect it to port. " + "Specify option multiple times to create multiple NICs. " + "This is a wrapper for the '--nic port-id=' " + "parameter that provides simple syntax for the standard " + "use case of connecting a new server to a given port. For " + "more advanced use cases, refer to the '--nic' parameter." + ), ) parser.add_argument( '--hint', @@ -862,10 +885,13 @@ def _show_progress(progress): if not image and parsed_args.image_property: def emit_duplicated_warning(img, image_property): img_uuid_list = [str(image.id) for image in img] - LOG.warning(_('Multiple matching images: %(img_uuid_list)s\n' - 'Using image: %(chosen_one)s') % - {'img_uuid_list': img_uuid_list, - 'chosen_one': img_uuid_list[0]}) + LOG.warning( + 'Multiple matching images: %(img_uuid_list)s\n' + 'Using image: %(chosen_one)s', + { + 'img_uuid_list': img_uuid_list, + 'chosen_one': img_uuid_list[0], + }) def _match_image(image_api, wanted_properties): image_list = image_api.images() @@ -882,45 +908,52 @@ def _match_image(image_api, wanted_properties): set([key, value]) except TypeError: if key != 'properties': - LOG.debug('Skipped the \'%s\' attribute. ' - 'That cannot be compared. ' - '(image: %s, value: %s)', - key, img.id, value) + LOG.debug( + 'Skipped the \'%s\' attribute. ' + 'That cannot be compared. ' + '(image: %s, value: %s)', + key, img.id, value, + ) pass else: img_dict[key] = value - if all(k in img_dict and img_dict[k] == v - for k, v in wanted_properties.items()): + if all( + k in img_dict and img_dict[k] == v + for k, v in wanted_properties.items() + ): images_matched.append(img) + return images_matched images = _match_image(image_client, parsed_args.image_property) if len(images) > 1: - emit_duplicated_warning(images, - parsed_args.image_property) + emit_duplicated_warning(images, parsed_args.image_property) if images: image = images[0] else: - raise exceptions.CommandError(_("No images match the " - "property expected by " - "--image-property")) + msg = _( + 'No images match the property expected by ' + '--image-property' + ) + raise exceptions.CommandError(msg) # Lookup parsed_args.volume volume = None if parsed_args.volume: # --volume and --boot-from-volume are mutually exclusive. if parsed_args.boot_from_volume: - raise exceptions.CommandError( - _('--volume is not allowed with --boot-from-volume')) + msg = _('--volume is not allowed with --boot-from-volume') + raise exceptions.CommandError(msg) + volume = utils.find_resource( volume_client.volumes, parsed_args.volume, ).id # Lookup parsed_args.flavor - flavor = utils.find_resource(compute_client.flavors, - parsed_args.flavor) + flavor = utils.find_resource( + compute_client.flavors, parsed_args.flavor) files = {} for f in parsed_args.file: @@ -930,16 +963,17 @@ def _match_image(image_api, wanted_properties): except IOError as e: msg = _("Can't open '%(source)s': %(exception)s") raise exceptions.CommandError( - msg % {"source": src, - "exception": e} + msg % {'source': src, 'exception': e} ) if parsed_args.min > parsed_args.max: msg = _("min instances should be <= max instances") raise exceptions.CommandError(msg) + if parsed_args.min < 1: msg = _("min instances should be > 0") raise exceptions.CommandError(msg) + if parsed_args.max < 1: msg = _("max instances should be > 0") raise exceptions.CommandError(msg) @@ -951,8 +985,7 @@ def _match_image(image_api, wanted_properties): except IOError as e: msg = _("Can't open '%(data)s': %(exception)s") raise exceptions.CommandError( - msg % {"data": parsed_args.user_data, - "exception": e} + msg % {'data': parsed_args.user_data, 'exception': e} ) if parsed_args.description: @@ -963,11 +996,12 @@ def _match_image(image_api, wanted_properties): block_device_mapping_v2 = [] if volume: - block_device_mapping_v2 = [{'uuid': volume, - 'boot_index': '0', - 'source_type': 'volume', - 'destination_type': 'volume' - }] + block_device_mapping_v2 = [{ + 'uuid': volume, + 'boot_index': '0', + 'source_type': 'volume', + 'destination_type': 'volume' + }] elif parsed_args.boot_from_volume: # Tell nova to create a root volume from the image provided. block_device_mapping_v2 = [{ @@ -988,13 +1022,16 @@ def _match_image(image_api, wanted_properties): dev_map = dev_map.split(':') if dev_map[0]: mapping = {'device_name': dev_name} + # 1. decide source and destination type if (len(dev_map) > 1 and dev_map[1] in ('volume', 'snapshot', 'image')): mapping['source_type'] = dev_map[1] else: mapping['source_type'] = 'volume' + mapping['destination_type'] = 'volume' + # 2. check target exist, update target uuid according by # source type if mapping['source_type'] == 'volume': @@ -1020,14 +1057,18 @@ def _match_image(image_api, wanted_properties): image_id = image_client.find_image(dev_map[0], ignore_missing=False).id mapping['uuid'] = image_id + # 3. append size and delete_on_termination if exist if len(dev_map) > 2 and dev_map[2]: mapping['volume_size'] = dev_map[2] + if len(dev_map) > 3 and dev_map[3]: mapping['delete_on_termination'] = dev_map[3] else: - msg = _("Volume, volume snapshot or image (name or ID) must " - "be specified if --block-device-mapping is specified") + msg = _( + 'Volume, volume snapshot or image (name or ID) must ' + 'be specified if --block-device-mapping is specified' + ) raise exceptions.CommandError(msg) block_device_mapping_v2.append(mapping) @@ -1041,22 +1082,32 @@ def _match_image(image_api, wanted_properties): auto_or_none = True nics.append(nic_str) else: - nic_info = {"net-id": "", "v4-fixed-ip": "", - "v6-fixed-ip": "", "port-id": ""} + nic_info = { + 'net-id': '', + 'v4-fixed-ip': '', + 'v6-fixed-ip': '', + 'port-id': '', + } for kv_str in nic_str.split(","): k, sep, v = kv_str.partition("=") if k in nic_info and v: nic_info[k] = v else: - msg = (_("Invalid nic argument '%s'. Nic arguments " - "must be of the form --nic .")) + msg = _( + "Invalid nic argument '%s'. Nic arguments " + "must be of the form --nic ." + ) raise exceptions.CommandError(msg % k) + if bool(nic_info["net-id"]) == bool(nic_info["port-id"]): - msg = _("either network or port should be specified " - "but not both") + msg = _( + 'Either network or port should be specified ' + 'but not both' + ) raise exceptions.CommandError(msg) + if self.app.client_manager.is_network_endpoint_enabled(): network_client = self.app.client_manager.network if nic_info["net-id"]: @@ -1073,17 +1124,22 @@ def _match_image(image_api, wanted_properties): nic_info["net-id"] )['id'] if nic_info["port-id"]: - msg = _("can't create server with port specified " - "since network endpoint not enabled") + msg = _( + "Can't create server with port specified " + "since network endpoint not enabled" + ) raise exceptions.CommandError(msg) + nics.append(nic_info) if nics: if auto_or_none: if len(nics) > 1: - msg = _('Specifying a --nic of auto or none cannot ' - 'be used with any other --nic, --network ' - 'or --port value.') + msg = _( + 'Specifying a --nic of auto or none cannot ' + 'be used with any other --nic, --network ' + 'or --port value.' + ) raise exceptions.CommandError(msg) nics = nics[0] else: @@ -1165,16 +1221,22 @@ def _match_image(image_api, wanted_properties): if parsed_args.host: if compute_client.api_version < api_versions.APIVersion("2.74"): - msg = _("Specifying --host is not supported for " - "--os-compute-api-version less than 2.74") + msg = _( + '--os-compute-api-version 2.74 or greater is required to ' + 'support the --host option' + ) raise exceptions.CommandError(msg) + boot_kwargs['host'] = parsed_args.host if parsed_args.hypervisor_hostname: if compute_client.api_version < api_versions.APIVersion("2.74"): - msg = _("Specifying --hypervisor-hostname is not supported " - "for --os-compute-api-version less than 2.74") + msg = _( + '--os-compute-api-version 2.74 or greater is required to ' + 'support the --hypervisor-hostname option' + ) raise exceptions.CommandError(msg) + boot_kwargs['hypervisor_hostname'] = ( parsed_args.hypervisor_hostname) @@ -1200,8 +1262,7 @@ def _match_image(image_api, wanted_properties): ): self.app.stdout.write('\n') else: - LOG.error(_('Error creating server: %s'), - parsed_args.server_name) + LOG.error('Error creating server: %s', parsed_args.server_name) self.app.stdout.write(_('Error creating server\n')) raise SystemExit From 17f641e1c3ac58acdc39d064369395198d2654d2 Mon Sep 17 00:00:00 2001 From: He Jie Xu Date: Tue, 19 Jun 2018 12:19:49 +0000 Subject: [PATCH 2274/3095] Compute: Add user id support for keypair This patch adds functionality of specific the user id when create, delete, show and list keypairs. Change-Id: Ib826f1f4f5a73d1875ba0f02e124b3222c4d05ed Co-Authored-By: tianhui --- openstackclient/compute/v2/keypair.py | 86 ++++++++- .../tests/unit/compute/v2/test_keypair.py | 171 +++++++++++++++++- .../keypair-user-id-db694210695a0ee0.yaml | 6 + 3 files changed, 250 insertions(+), 13 deletions(-) create mode 100644 releasenotes/notes/keypair-user-id-db694210695a0ee0.yaml diff --git a/openstackclient/compute/v2/keypair.py b/openstackclient/compute/v2/keypair.py index ae653e76ee..157dd8cd90 100644 --- a/openstackclient/compute/v2/keypair.py +++ b/openstackclient/compute/v2/keypair.py @@ -64,10 +64,20 @@ def get_parser(self, prog_name): "(Supported by API versions '2.2' - '2.latest')" ), ) + parser.add_argument( + '--user', + metavar='', + help=_( + 'The owner of the keypair. (admin only) (name or ID). ' + 'Requires ``--os-compute-api-version`` 2.10 or greater.' + ), + ) + identity_common.add_user_domain_option_to_parser(parser) return parser def take_action(self, parsed_args): compute_client = self.app.client_manager.compute + identity_client = self.app.client_manager.identity public_key = parsed_args.public_key if public_key: @@ -89,12 +99,26 @@ def take_action(self, parsed_args): if compute_client.api_version < api_versions.APIVersion('2.2'): msg = _( '--os-compute-api-version 2.2 or greater is required to ' - 'support the --type option.' + 'support the --type option' ) raise exceptions.CommandError(msg) kwargs['key_type'] = parsed_args.type + if parsed_args.user: + if compute_client.api_version < api_versions.APIVersion('2.10'): + msg = _( + '--os-compute-api-version 2.10 or greater is required to ' + 'support the --user option' + ) + raise exceptions.CommandError(msg) + + kwargs['user_id'] = identity_common.find_user( + identity_client, + parsed_args.user, + parsed_args.user_domain, + ).id + keypair = compute_client.keypairs.create(**kwargs) private_key = parsed_args.private_key @@ -139,16 +163,43 @@ def get_parser(self, prog_name): nargs='+', help=_("Name of key(s) to delete (name only)") ) + parser.add_argument( + '--user', + metavar='', + help=_( + 'The owner of the keypair. (admin only) (name or ID). ' + 'Requires ``--os-compute-api-version`` 2.10 or greater.' + ), + ) + identity_common.add_user_domain_option_to_parser(parser) return parser def take_action(self, parsed_args): compute_client = self.app.client_manager.compute + identity_client = self.app.client_manager.identity + + kwargs = {} result = 0 + + if parsed_args.user: + if compute_client.api_version < api_versions.APIVersion('2.10'): + msg = _( + '--os-compute-api-version 2.10 or greater is required to ' + 'support the --user option' + ) + raise exceptions.CommandError(msg) + + kwargs['user_id'] = identity_common.find_user( + identity_client, + parsed_args.user, + parsed_args.user_domain, + ).id + for n in parsed_args.name: try: data = utils.find_resource( compute_client.keypairs, n) - compute_client.keypairs.delete(data.name) + compute_client.keypairs.delete(data.name, **kwargs) except Exception as e: result += 1 LOG.error(_("Failed to delete key with name " @@ -229,12 +280,39 @@ def get_parser(self, prog_name): default=False, help=_("Show only bare public key paired with the generated key") ) + parser.add_argument( + '--user', + metavar='', + help=_( + 'The owner of the keypair. (admin only) (name or ID). ' + 'Requires ``--os-compute-api-version`` 2.10 or greater.' + ), + ) + identity_common.add_user_domain_option_to_parser(parser) return parser def take_action(self, parsed_args): compute_client = self.app.client_manager.compute - keypair = utils.find_resource(compute_client.keypairs, - parsed_args.name) + identity_client = self.app.client_manager.identity + + kwargs = {} + + if parsed_args.user: + if compute_client.api_version < api_versions.APIVersion('2.10'): + msg = _( + '--os-compute-api-version 2.10 or greater is required to ' + 'support the --user option' + ) + raise exceptions.CommandError(msg) + + kwargs['user_id'] = identity_common.find_user( + identity_client, + parsed_args.user, + parsed_args.user_domain, + ).id + + keypair = utils.find_resource( + compute_client.keypairs, parsed_args.name, **kwargs) info = {} info.update(keypair._info) diff --git a/openstackclient/tests/unit/compute/v2/test_keypair.py b/openstackclient/tests/unit/compute/v2/test_keypair.py index 5e6a47414f..56cbe3f100 100644 --- a/openstackclient/tests/unit/compute/v2/test_keypair.py +++ b/openstackclient/tests/unit/compute/v2/test_keypair.py @@ -38,6 +38,15 @@ def setUp(self): self.keypairs_mock = self.app.client_manager.compute.keypairs self.keypairs_mock.reset_mock() + # Initialize the user mock + self.users_mock = self.app.client_manager.identity.users + self.users_mock.reset_mock() + self.users_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.USER), + loaded=True, + ) + class TestKeypairCreate(TestKeypair): @@ -226,6 +235,54 @@ def test_keypair_create_with_key_type_pre_v22(self): '--os-compute-api-version 2.2 or greater is required', str(ex)) + def test_key_pair_create_with_user(self): + + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.10') + + arglist = [ + '--user', identity_fakes.user_name, + self.keypair.name, + ] + verifylist = [ + ('user', identity_fakes.user_name), + ('name', self.keypair.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.keypairs_mock.create.assert_called_with( + name=self.keypair.name, + public_key=None, + user_id=identity_fakes.user_id, + ) + + self.assertEqual({}, columns) + self.assertEqual({}, data) + + def test_key_pair_create_with_user_pre_v210(self): + + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.9') + + arglist = [ + '--user', identity_fakes.user_name, + self.keypair.name, + ] + verifylist = [ + ('user', identity_fakes.user_name), + ('name', self.keypair.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.10 or greater is required', str(ex)) + class TestKeypairDelete(TestKeypair): @@ -301,6 +358,51 @@ def test_delete_multiple_keypairs_with_exception(self): self.keypairs[0].name ) + def test_keypair_delete_with_user(self): + + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.10') + + arglist = [ + '--user', identity_fakes.user_name, + self.keypairs[0].name + ] + verifylist = [ + ('user', identity_fakes.user_name), + ('name', [self.keypairs[0].name]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ret = self.cmd.take_action(parsed_args) + + self.assertIsNone(ret) + self.keypairs_mock.delete.assert_called_with( + self.keypairs[0].name, + user_id=identity_fakes.user_id, + ) + + def test_keypair_delete_with_user_pre_v210(self): + + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.9') + + arglist = [ + '--user', identity_fakes.user_name, + self.keypairs[0].name + ] + verifylist = [ + ('user', identity_fakes.user_name), + ('name', [self.keypairs[0].name]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.10 or greater is required', str(ex)) + class TestKeypairList(TestKeypair): @@ -310,14 +412,6 @@ class TestKeypairList(TestKeypair): def setUp(self): super(TestKeypairList, self).setUp() - self.users_mock = self.app.client_manager.identity.users - self.users_mock.reset_mock() - self.users_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.USER), - loaded=True, - ) - self.keypairs_mock.list.return_value = self.keypairs # Get the command object to test @@ -477,11 +571,14 @@ def test_keypair_show(self): verifylist = [ ('name', self.keypair.name) ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) + self.keypairs_mock.get.assert_called_with( + self.keypair.name, + ) + self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -502,3 +599,59 @@ def test_keypair_show_public(self): self.assertEqual({}, columns) self.assertEqual({}, data) + + def test_keypair_show_with_user(self): + + # overwrite the setup one because we want to omit private_key + self.keypair = compute_fakes.FakeKeypair.create_one_keypair( + no_pri=True) + self.keypairs_mock.get.return_value = self.keypair + + self.data = ( + self.keypair.fingerprint, + self.keypair.name, + self.keypair.type, + self.keypair.user_id + ) + + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.10') + + arglist = [ + '--user', identity_fakes.user_name, + self.keypair.name, + ] + verifylist = [ + ('user', identity_fakes.user_name), + ('name', self.keypair.name) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.users_mock.get.assert_called_with(identity_fakes.user_name) + self.keypairs_mock.get.assert_called_with( + self.keypair.name, + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_keypair_show_with_user_pre_v210(self): + + arglist = [ + '--user', identity_fakes.user_name, + self.keypair.name, + ] + verifylist = [ + ('user', identity_fakes.user_name), + ('name', self.keypair.name) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.10 or greater is required', str(ex)) diff --git a/releasenotes/notes/keypair-user-id-db694210695a0ee0.yaml b/releasenotes/notes/keypair-user-id-db694210695a0ee0.yaml new file mode 100644 index 0000000000..073973e2cf --- /dev/null +++ b/releasenotes/notes/keypair-user-id-db694210695a0ee0.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--user`` option to the ``keypair create``, ``keypair delete``, and + ``keypair show`` commands. Only available starting with + ``--os-compute-api-version 2.10``. From ad3369ed1fdad73b5d457a40df7df8ad55eb69cd Mon Sep 17 00:00:00 2001 From: Artem Goncharov Date: Mon, 2 Nov 2020 13:25:59 +0100 Subject: [PATCH 2275/3095] Fix formatting of the flavor properties Do not stringify flavor properties to allow proper output formatting to json/yaml/etc Change-Id: I9f4c42acb85b726af87123134dd19de98fe95074 --- lower-constraints.txt | 4 +- openstackclient/compute/v2/flavor.py | 59 +++++++++++++++---- .../functional/compute/v2/test_flavor.py | 43 ++++++++------ .../tests/unit/compute/v2/test_flavor.py | 26 ++++---- ...vor-props-formatting-d21e97745543caa7.yaml | 4 ++ requirements.txt | 4 +- 6 files changed, 94 insertions(+), 46 deletions(-) create mode 100644 releasenotes/notes/fix-flavor-props-formatting-d21e97745543caa7.yaml diff --git a/lower-constraints.txt b/lower-constraints.txt index 74eac6403b..52766b2290 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -5,7 +5,7 @@ asn1crypto==0.23.0 bandit==1.1.0 cachetools==2.0.0 cffi==1.14.0 -cliff==2.8.0 +cliff==3.4.0 cmd2==0.8.0 contextlib2==0.4.0 coverage==4.0 @@ -48,7 +48,7 @@ netifaces==0.10.4 openstacksdk==0.48.0 os-service-types==1.7.0 os-testr==1.0.0 -osc-lib==2.0.0 +osc-lib==2.2.0 osc-placement==1.7.0 oslo.concurrency==3.26.0 oslo.config==5.2.0 diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 805e919ea3..00431b7b73 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -18,6 +18,7 @@ import logging from novaclient import api_versions +from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -30,6 +31,31 @@ LOG = logging.getLogger(__name__) +_formatters = { + 'extra_specs': format_columns.DictColumn, + # Unless we finish switch to use SDK resources this need to be doubled this + # way + 'properties': format_columns.DictColumn, + 'Properties': format_columns.DictColumn +} + + +def _get_flavor_columns(item): + # To maintain backwards compatibility we need to rename sdk props to + # whatever OSC was using before + column_map = { + 'extra_specs': 'properties', + 'ephemeral': 'OS-FLV-EXT-DATA:ephemeral', + 'is_disabled': 'OS-FLV-DISABLED:disabled', + 'is_public': 'os-flavor-access:is_public' + + } + hidden_columns = ['links', 'location'] + + return utils.get_osc_show_columns_for_sdk_resource( + item, column_map, hidden_columns) + + def _find_flavor(compute_client, flavor): try: return compute_client.flavors.get(flavor) @@ -191,10 +217,16 @@ def take_action(self, parsed_args): LOG.error(_("Failed to set flavor property: %s"), e) flavor_info = flavor._info.copy() - flavor_info.pop("links") - flavor_info['properties'] = utils.format_dict(flavor.get_keys()) + flavor_info['properties'] = flavor.get_keys() - return zip(*sorted(flavor_info.items())) + display_columns, columns = _get_flavor_columns(flavor_info) + data = utils.get_dict_properties( + flavor_info, columns, + formatters=_formatters, + mixed_case_fields=['OS-FLV-DISABLED:disabled', + 'OS-FLV-EXT-DATA:ephemeral']) + + return (display_columns, data) class DeleteFlavor(command.Command): @@ -309,7 +341,7 @@ def take_action(self, parsed_args): return (column_headers, (utils.get_item_properties( - s, columns, formatters={'Properties': utils.format_dict}, + s, columns, formatters=_formatters, ) for s in data)) @@ -428,11 +460,8 @@ def take_action(self, parsed_args): try: flavor_access = compute_client.flavor_access.list( flavor=resource_flavor.id) - projects = [utils.get_field(access, 'tenant_id') - for access in flavor_access] - # TODO(Huanxuan Ao): This format case can be removed after - # patch https://review.opendev.org/#/c/330223/ merged. - access_projects = utils.format_list(projects) + access_projects = [utils.get_field(access, 'tenant_id') + for access in flavor_access] except Exception as e: msg = _("Failed to get access projects list " "for flavor '%(flavor)s': %(e)s") @@ -442,11 +471,17 @@ def take_action(self, parsed_args): flavor.update({ 'access_project_ids': access_projects }) - flavor.pop("links", None) - flavor['properties'] = utils.format_dict(resource_flavor.get_keys()) + flavor['properties'] = resource_flavor.get_keys() + + display_columns, columns = _get_flavor_columns(flavor) + data = utils.get_dict_properties( + flavor, columns, + formatters=_formatters, + mixed_case_fields=['OS-FLV-DISABLED:disabled', + 'OS-FLV-EXT-DATA:ephemeral']) - return zip(*sorted(flavor.items())) + return (display_columns, data) class UnsetFlavor(command.Command): diff --git a/openstackclient/tests/functional/compute/v2/test_flavor.py b/openstackclient/tests/functional/compute/v2/test_flavor.py index c274adf2e7..162d428789 100644 --- a/openstackclient/tests/functional/compute/v2/test_flavor.py +++ b/openstackclient/tests/functional/compute/v2/test_flavor.py @@ -115,8 +115,8 @@ def test_flavor_list(self): self.assertFalse( cmd_output["os-flavor-access:is_public"], ) - self.assertEqual( - "a='b2', b='d2'", + self.assertDictEqual( + {"a": "b2", "b": "d2"}, cmd_output["properties"], ) @@ -133,12 +133,18 @@ def test_flavor_list(self): "flavor list -f json " + "--long" )) - col_name = [x["Name"] for x in cmd_output] - col_properties = [x['Properties'] for x in cmd_output] - self.assertIn(name1, col_name) - self.assertIn("a='b', c='d'", col_properties) - self.assertNotIn(name2, col_name) - self.assertNotIn("b2', b='d2'", col_properties) + # We have list of complex json objects + # Iterate through the list setting flags + found_expected = False + for rec in cmd_output: + if rec['Name'] == name1: + found_expected = True + self.assertEqual('b', rec['Properties']['a']) + self.assertEqual('d', rec['Properties']['c']) + elif rec['Name'] == name2: + # We should have not seen private flavor + self.assertFalse(True) + self.assertTrue(found_expected) # Test list --public cmd_output = json.loads(self.openstack( @@ -201,8 +207,8 @@ def test_flavor_properties(self): self.assertFalse( cmd_output["os-flavor-access:is_public"], ) - self.assertEqual( - "a='first', b='second'", + self.assertDictEqual( + {"a": "first", "b": "second"}, cmd_output["properties"], ) @@ -223,9 +229,14 @@ def test_flavor_properties(self): cmd_output["id"], ) self.assertEqual( - "a='third and 10', b='second', g='fourth'", - cmd_output['properties'], - ) + 'third and 10', + cmd_output['properties']['a']) + self.assertEqual( + 'second', + cmd_output['properties']['b']) + self.assertEqual( + 'fourth', + cmd_output['properties']['g']) raw_output = self.openstack( "flavor unset " + @@ -238,7 +249,5 @@ def test_flavor_properties(self): "flavor show -f json " + name1 )) - self.assertEqual( - "a='third and 10', g='fourth'", - cmd_output["properties"], - ) + + self.assertNotIn('b', cmd_output['properties']) diff --git a/openstackclient/tests/unit/compute/v2/test_flavor.py b/openstackclient/tests/unit/compute/v2/test_flavor.py index 4732cc822d..2828d74e36 100644 --- a/openstackclient/tests/unit/compute/v2/test_flavor.py +++ b/openstackclient/tests/unit/compute/v2/test_flavor.py @@ -17,8 +17,8 @@ from unittest.mock import call import novaclient +from osc_lib.cli import format_columns from osc_lib import exceptions -from osc_lib import utils from openstackclient.compute.v2 import flavor from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes @@ -70,7 +70,7 @@ class TestFlavorCreate(TestFlavor): flavor.id, flavor.name, flavor.is_public, - utils.format_dict(flavor.properties), + format_columns.DictColumn(flavor.properties), flavor.ram, flavor.rxtx_factor, flavor.swap, @@ -111,7 +111,7 @@ def test_flavor_create_default_options(self): self.flavors_mock.create.assert_called_once_with(*default_args) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_flavor_create_all_options(self): @@ -165,7 +165,7 @@ def test_flavor_create_all_options(self): self.flavor.get_keys.assert_called_once_with() self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_flavor_create_other_options(self): @@ -226,7 +226,7 @@ def test_flavor_create_other_options(self): {'key1': 'value1', 'key2': 'value2'}) self.flavor.get_keys.assert_called_with() self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_public_flavor_create_with_project(self): arglist = [ @@ -300,7 +300,7 @@ def test_flavor_create_with_description_api_newer(self): self.flavors_mock.create.assert_called_once_with(*args) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_flavor_create_with_description_api_older(self): arglist = [ @@ -429,7 +429,7 @@ class TestFlavorList(TestFlavor): data_long = (data[0] + ( flavors[0].swap, flavors[0].rxtx_factor, - u'property=\'value\'' + format_columns.DictColumn(flavors[0].properties) ), ) def setUp(self): @@ -583,7 +583,7 @@ def test_flavor_list_long(self): ) self.assertEqual(self.columns_long, columns) - self.assertEqual(tuple(self.data_long), tuple(data)) + self.assertListItemEqual(self.data_long, tuple(data)) class TestFlavorSet(TestFlavor): @@ -817,7 +817,7 @@ class TestFlavorShow(TestFlavor): flavor.id, flavor.name, flavor.is_public, - utils.format_dict(flavor.get_keys()), + format_columns.DictColumn(flavor.get_keys()), flavor.ram, flavor.rxtx_factor, flavor.swap, @@ -854,7 +854,7 @@ def test_public_flavor_show(self): columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_private_flavor_show(self): private_flavor = compute_fakes.FakeFlavor.create_one_flavor( @@ -874,13 +874,13 @@ def test_private_flavor_show(self): data_with_project = ( private_flavor.disabled, private_flavor.ephemeral, - self.flavor_access.tenant_id, + [self.flavor_access.tenant_id], private_flavor.description, private_flavor.disk, private_flavor.id, private_flavor.name, private_flavor.is_public, - utils.format_dict(private_flavor.get_keys()), + format_columns.DictColumn(private_flavor.get_keys()), private_flavor.ram, private_flavor.rxtx_factor, private_flavor.swap, @@ -894,7 +894,7 @@ def test_private_flavor_show(self): self.flavor_access_mock.list.assert_called_with( flavor=private_flavor.id) self.assertEqual(self.columns, columns) - self.assertEqual(data_with_project, data) + self.assertItemEqual(data_with_project, data) class TestFlavorUnset(TestFlavor): diff --git a/releasenotes/notes/fix-flavor-props-formatting-d21e97745543caa7.yaml b/releasenotes/notes/fix-flavor-props-formatting-d21e97745543caa7.yaml new file mode 100644 index 0000000000..2a026b3f76 --- /dev/null +++ b/releasenotes/notes/fix-flavor-props-formatting-d21e97745543caa7.yaml @@ -0,0 +1,4 @@ +--- +fixes: + Fix '-f json' output of the flavor properties to return valid json object + instead of stringying it. diff --git a/requirements.txt b/requirements.txt index ee6b6241ae..99d409bf51 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,10 +3,10 @@ # process, which may cause wedges in the gate later. pbr!=2.1.0,>=2.0.0 # Apache-2.0 -cliff!=2.9.0,>=2.8.0 # Apache-2.0 +cliff>=3.4.0 # Apache-2.0 iso8601>=0.1.11 # MIT openstacksdk>=0.48.0 # Apache-2.0 -osc-lib>=2.0.0 # Apache-2.0 +osc-lib>=2.2.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 python-keystoneclient>=3.22.0 # Apache-2.0 python-novaclient>=15.1.0 # Apache-2.0 From 01eb4e839394fe433a92a06daf1499fb00f2fe69 Mon Sep 17 00:00:00 2001 From: Sean Mooney Date: Fri, 15 Mar 2019 13:03:10 +0000 Subject: [PATCH 2276/3095] Add 'openstack server evacuate' command This change adds a new 'openstack server evacuate' command to provide parity with the 'nova evacuate' command. The term "evacuate" is notoriously poor, in that it implies the instance is moved rather than recreated, but it is retained since people are familiar with it now. Change-Id: I1e32ca51036c501862d8e89b3144a9695d98a06f --- doc/source/cli/command-objects/server.rst | 3 + openstackclient/compute/v2/server.py | 112 ++++++++++++ .../tests/unit/compute/v2/test_server.py | 161 ++++++++++++++++++ .../add-server-evacuate-8359246692cb642f.yaml | 6 + setup.cfg | 1 + 5 files changed, 283 insertions(+) create mode 100644 releasenotes/notes/add-server-evacuate-8359246692cb642f.yaml diff --git a/doc/source/cli/command-objects/server.rst b/doc/source/cli/command-objects/server.rst index 89eb4e3710..cf7df1dae3 100644 --- a/doc/source/cli/command-objects/server.rst +++ b/doc/source/cli/command-objects/server.rst @@ -10,6 +10,9 @@ Compute v2 .. autoprogram-cliff:: openstack.compute.v2 :command: server create +.. autoprogram-cliff:: openstack.compute.v2 + :command: server evacuate + .. autoprogram-cliff:: openstack.compute.v2 :command: server delete diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 294b468331..7f1bc08852 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -2502,6 +2502,118 @@ def _show_progress(progress): return zip(*sorted(details.items())) +class EvacuateServer(command.ShowOne): + _description = _("""Evacuate a server to a different host. + +This command is used to recreate a server after the host it was on has failed. +It can only be used if the compute service that manages the server is down. +This command should only be used by an admin after they have confirmed that the +instance is not running on the failed host. + +If the server instance was created with an ephemeral root disk on non-shared +storage the server will be rebuilt using the original glance image preserving +the ports and any attached data volumes. + +If the server uses boot for volume or has its root disk on shared storage the +root disk will be preserved and reused for the evacuated instance on the new +host.""") + + def get_parser(self, prog_name): + parser = super(EvacuateServer, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + help=_('Server (name or ID)'), + ) + + parser.add_argument( + '--wait', action='store_true', + help=_('Wait for evacuation to complete'), + ) + parser.add_argument( + '--host', metavar='', default=None, + help=_( + 'Set the preferred host on which to rebuild the evacuated ' + 'server. The host will be validated by the scheduler. ' + '(supported by --os-compute-api-version 2.29 or above)' + ), + ) + shared_storage_group = parser.add_mutually_exclusive_group() + shared_storage_group.add_argument( + '--password', metavar='', default=None, + help=_( + 'Set the password on the evacuated instance. This option is ' + 'mutually exclusive with the --shared-storage option' + ), + ) + shared_storage_group.add_argument( + '--shared-storage', action='store_true', dest='shared_storage', + help=_( + 'Indicate that the instance is on shared storage. ' + 'This will be auto-calculated with ' + '--os-compute-api-version 2.14 and greater and should not ' + 'be used with later microversions. This option is mutually ' + 'exclusive with the --password option' + ), + ) + return parser + + def take_action(self, parsed_args): + + def _show_progress(progress): + if progress: + self.app.stdout.write('\rProgress: %s' % progress) + self.app.stdout.flush() + + compute_client = self.app.client_manager.compute + image_client = self.app.client_manager.image + + if parsed_args.host: + if compute_client.api_version < api_versions.APIVersion('2.29'): + msg = _( + '--os-compute-api-version 2.29 or later is required ' + 'to specify a preferred host.' + ) + raise exceptions.CommandError(msg) + + if parsed_args.shared_storage: + if compute_client.api_version > api_versions.APIVersion('2.13'): + msg = _( + '--os-compute-api-version 2.13 or earlier is required ' + 'to specify shared-storage.' + ) + raise exceptions.CommandError(msg) + + kwargs = { + 'host': parsed_args.host, + 'password': parsed_args.password, + } + + if compute_client.api_version <= api_versions.APIVersion('2.13'): + kwargs['on_shared_storage'] = parsed_args.shared_storage + + server = utils.find_resource( + compute_client.servers, parsed_args.server) + + server = server.evacuate(**kwargs) + + if parsed_args.wait: + if utils.wait_for_status( + compute_client.servers.get, + server.id, + callback=_show_progress, + ): + self.app.stdout.write(_('Complete\n')) + else: + LOG.error(_('Error evacuating server: %s'), server.id) + self.app.stdout.write(_('Error evacuating server\n')) + raise SystemExit + + details = _prep_server_detail( + compute_client, image_client, server, refresh=False) + return zip(*sorted(details.items())) + + class RemoveFixedIP(command.Command): _description = _("Remove fixed IP address from server") diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 380ef66b4e..20e3f70e95 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -4982,6 +4982,167 @@ def test_rebuild_with_key_name_and_unset(self): self.cmd, arglist, verifylist) +class TestEvacuateServer(TestServer): + + def setUp(self): + super(TestEvacuateServer, self).setUp() + # Return value for utils.find_resource for image + self.image = image_fakes.FakeImage.create_one_image() + self.images_mock.get.return_value = self.image + + # Fake the rebuilt new server. + attrs = { + 'image': { + 'id': self.image.id + }, + 'networks': {}, + 'adminPass': 'passw0rd', + } + new_server = compute_fakes.FakeServer.create_one_server(attrs=attrs) + + # Fake the server to be rebuilt. The IDs of them should be the same. + attrs['id'] = new_server.id + methods = { + 'evacuate': new_server, + } + self.server = compute_fakes.FakeServer.create_one_server( + attrs=attrs, + methods=methods + ) + + # Return value for utils.find_resource for server. + self.servers_mock.get.return_value = self.server + + self.cmd = server.EvacuateServer(self.app, None) + + def _test_evacuate(self, args, verify_args, evac_args): + parsed_args = self.check_parser(self.cmd, args, verify_args) + + # Get the command object to test + self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.server.evacuate.assert_called_with(**evac_args) + + def test_evacuate(self): + args = [ + self.server.id, + ] + verify_args = [ + ('server', self.server.id), + ] + evac_args = { + 'host': None, 'on_shared_storage': False, 'password': None, + } + self._test_evacuate(args, verify_args, evac_args) + + def test_evacuate_with_password(self): + args = [ + self.server.id, + '--password', 'password', + ] + verify_args = [ + ('server', self.server.id), + ('password', 'password'), + ] + evac_args = { + 'host': None, 'on_shared_storage': False, 'password': 'password', + } + self._test_evacuate(args, verify_args, evac_args) + + def test_evacuate_with_host(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.29') + + host = 'target-host' + args = [ + self.server.id, + '--host', 'target-host', + ] + verify_args = [ + ('server', self.server.id), + ('host', 'target-host'), + ] + evac_args = {'host': host, 'password': None} + + self._test_evacuate(args, verify_args, evac_args) + + def test_evacuate_with_host_pre_v229(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.28') + + args = [ + self.server.id, + '--host', 'target-host', + ] + verify_args = [ + ('server', self.server.id), + ('host', 'target-host'), + ] + parsed_args = self.check_parser(self.cmd, args, verify_args) + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + def test_evacuate_without_share_storage(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.13') + + args = [ + self.server.id, + '--shared-storage' + ] + verify_args = [ + ('server', self.server.id), + ('shared_storage', True), + ] + evac_args = { + 'host': None, 'on_shared_storage': True, 'password': None, + } + self._test_evacuate(args, verify_args, evac_args) + + def test_evacuate_without_share_storage_post_v213(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.14') + + args = [ + self.server.id, + '--shared-storage' + ] + verify_args = [ + ('server', self.server.id), + ('shared_storage', True), + ] + parsed_args = self.check_parser(self.cmd, args, verify_args) + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + @mock.patch.object(common_utils, 'wait_for_status', return_value=True) + def test_evacuate_with_wait_ok(self, mock_wait_for_status): + args = [ + self.server.id, + '--wait', + ] + verify_args = [ + ('server', self.server.id), + ('wait', True), + ] + evac_args = { + 'host': None, 'on_shared_storage': False, 'password': None, + } + self._test_evacuate(args, verify_args, evac_args) + mock_wait_for_status.assert_called_once_with( + self.servers_mock.get, + self.server.id, + callback=mock.ANY, + ) + + class TestServerRemoveFixedIP(TestServer): def setUp(self): diff --git a/releasenotes/notes/add-server-evacuate-8359246692cb642f.yaml b/releasenotes/notes/add-server-evacuate-8359246692cb642f.yaml new file mode 100644 index 0000000000..41bbed736a --- /dev/null +++ b/releasenotes/notes/add-server-evacuate-8359246692cb642f.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``server evacuate`` command. This command will recreate an instance + from scratch on a new host and is intended to be used when the original + host fails. diff --git a/setup.cfg b/setup.cfg index 8363ec6cfd..a29852e353 100644 --- a/setup.cfg +++ b/setup.cfg @@ -103,6 +103,7 @@ openstack.compute.v2 = server_create = openstackclient.compute.v2.server:CreateServer server_delete = openstackclient.compute.v2.server:DeleteServer server_dump_create = openstackclient.compute.v2.server:CreateServerDump + server_evacuate = openstackclient.compute.v2.server:EvacuateServer server_list = openstackclient.compute.v2.server:ListServer server_lock = openstackclient.compute.v2.server:LockServer server_migrate = openstackclient.compute.v2.server:MigrateServer From 311f4130d2c59638074531fa59e67783c2571e91 Mon Sep 17 00:00:00 2001 From: jay Date: Wed, 15 Jul 2020 15:10:19 +0200 Subject: [PATCH 2277/3095] Add a few selectable fields to the "openstack server list" output Added ``-c project_id | user_id | created_at`` to ``openstack server list`` command to get these columns as an output. Change-Id: I18991adf899c7b72c98bb89871bf0715d35943f0 Story: 2007925 --- openstackclient/compute/v2/server.py | 21 +++++++++++++++++++ .../tests/unit/compute/v2/test_server.py | 19 +++++++++++++++++ ...lectable-fields.yaml-1d5fb4784fa6f232.yaml | 4 ++++ 3 files changed, 44 insertions(+) create mode 100644 releasenotes/notes/server-list-selectable-fields.yaml-1d5fb4784fa6f232.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 93e9f966ae..db3914b358 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1446,6 +1446,27 @@ def take_action(self, parsed_args): marker_id = None + # support for additional columns + if parsed_args.columns: + # convert tuple to list to edit them + column_headers = list(column_headers) + columns = list(columns) + + for c in parsed_args.columns: + if c in ('Project ID', 'project_id'): + columns.append('tenant_id') + column_headers.append('Project ID') + if c in ('User ID', 'user_id'): + columns.append('user_id') + column_headers.append('User ID') + if c in ('Created At', 'created_at'): + columns.append('created_at') + column_headers.append('Created At') + + # convert back to tuple + column_headers = tuple(column_headers) + columns = tuple(columns) + if parsed_args.marker: # Check if both "--marker" and "--deleted" are used. # In that scenario a lookup is not needed as the marker diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 7e4c71c50c..f6506bf7ad 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -2696,6 +2696,25 @@ def test_server_list_long_option(self): self.assertEqual(self.columns_long, columns) self.assertEqual(tuple(self.data_long), tuple(data)) + def test_server_list_column_option(self): + arglist = [ + '-c', 'Project ID', + '-c', 'User ID', + '-c', 'Created At', + '--long' + ] + verifylist = [ + ('long', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.servers_mock.list.assert_called_with(**self.kwargs) + self.assertIn('Project ID', columns) + self.assertIn('User ID', columns) + self.assertIn('Created At', columns) + def test_server_list_no_name_lookup_option(self): arglist = [ '--no-name-lookup', diff --git a/releasenotes/notes/server-list-selectable-fields.yaml-1d5fb4784fa6f232.yaml b/releasenotes/notes/server-list-selectable-fields.yaml-1d5fb4784fa6f232.yaml new file mode 100644 index 0000000000..c40a050261 --- /dev/null +++ b/releasenotes/notes/server-list-selectable-fields.yaml-1d5fb4784fa6f232.yaml @@ -0,0 +1,4 @@ +--- +features: + - Add ``--c project_id | user_id | created_at`` to ``openstack server list`` + command to get these columns as an output. From 0a7f2692c62846bb0d49d366df65f1cfc2565f03 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 1 Oct 2020 21:50:40 +0100 Subject: [PATCH 2278/3095] Remove references to Python 2.7 We don't support it anymore. This is just noise now. Change-Id: I3640e7d8e520db69f83f95e9c7759279f7c15008 Signed-off-by: Stephen Finucane --- HACKING.rst | 21 +-------------------- doc/source/contributor/developing.rst | 15 +++++++-------- 2 files changed, 8 insertions(+), 28 deletions(-) diff --git a/HACKING.rst b/HACKING.rst index 52e96caa93..b5fbad3ca1 100644 --- a/HACKING.rst +++ b/HACKING.rst @@ -7,6 +7,7 @@ OpenStack Style Commandments General ------- + - thou shalt not violate causality in our time cone, or else Docstrings @@ -85,23 +86,3 @@ commandline arguments, etc.) should be presumed to be encoded as utf-8. returntext = do_some_magic_with(mytext) returnstring = returntext.encode('utf-8') outfile.write(returnstring) - -Python 3.x Compatibility ------------------------- - -OpenStackClient strives to be Python 3.3 compatible. Common guidelines: - -* Convert print statements to functions: print statements should be converted - to an appropriate log or other output mechanism. -* Prefer to x.items() over six.iteritems(x). - -Running Tests -------------- - -Note: Oh boy, are we behind on writing tests. But they are coming! - -The testing system is based on a combination of tox and testr. If you just -want to run the whole suite, run `tox` and all will be fine. However, if -you'd like to dig in a bit more, you might want to learn some things about -testr itself. A basic walkthrough for OpenStack can be found at -http://wiki.openstack.org/testr diff --git a/doc/source/contributor/developing.rst b/doc/source/contributor/developing.rst index a319849396..9142edb86c 100644 --- a/doc/source/contributor/developing.rst +++ b/doc/source/contributor/developing.rst @@ -59,13 +59,13 @@ To run the full suite of tests maintained within OpenStackClient. virtualenvs. You can later use the ``-r`` option with ``tox`` to rebuild your virtualenv in a similar manner. - -To run tests for one or more specific test environments(for example, the most common configuration of -Python 2.7 and PEP-8), list the environments with the ``-e`` option, separated by spaces: +To run tests for one or more specific test environments(for example, the most +common configuration of the latest Python version and PEP-8), list the +environments with the ``-e`` option, separated by spaces: .. code-block:: bash - $ tox -e py27,pep8 + $ tox -e py38,pep8 See ``tox.ini`` for the full list of available test environments. @@ -96,9 +96,9 @@ Using PDB breakpoints with ``tox`` and ``testr`` normally does not work since the tests fail with a `BdbQuit` exception rather than stopping at the breakpoint. -To run with PDB breakpoints during testing, use the `debug` ``tox`` environment -rather than ``py27``. For example, passing a test name since you will normally -only want to run the test that hits your breakpoint: +To run with PDB breakpoints during testing, use the ``debug`` ``tox`` +environment. For example, passing a test name since you will normally only want +to run the test that hits your breakpoint: .. code-block:: bash @@ -207,4 +207,3 @@ Example from openstackclient import shell from openstackclient.tests import utils from unittest import mock - From 4d3a3bb28f61a75c14a06e98dc0dd265308d658f Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 1 Oct 2020 21:52:45 +0100 Subject: [PATCH 2279/3095] Remove unnecessary test As noted, we're simply testing the default behavior of Python 3 in this test. Remove it, now that this is the only version(s) of Python 3 we have to worry about. Change-Id: I5f07343df8334457d907086033d5685f59c0bf0e Signed-off-by: Stephen Finucane --- openstackclient/tests/unit/test_shell.py | 30 ------------------------ 1 file changed, 30 deletions(-) diff --git a/openstackclient/tests/unit/test_shell.py b/openstackclient/tests/unit/test_shell.py index 366c364e06..bee2b40149 100644 --- a/openstackclient/tests/unit/test_shell.py +++ b/openstackclient/tests/unit/test_shell.py @@ -15,7 +15,6 @@ import importlib import os -import sys from unittest import mock from osc_lib.tests import utils as osc_lib_test_utils @@ -412,32 +411,3 @@ def test_empty_env(self): "network_api_version": LIB_NETWORK_API_VERSION } self._assert_cli(flag, kwargs) - - -class TestShellArgV(TestShell): - """Test the deferred help flag""" - - def test_shell_argv(self): - """Test argv decoding - - Python 2 does nothing with argv while Python 3 decodes it into - Unicode before we ever see it. We manually decode when running - under Python 2 so verify that we get the right argv types. - - Use the argv supplied by the test runner so we get actual Python - runtime behaviour; we only need to check the type of argv[0] - which will always be present. - """ - - with mock.patch( - self.shell_class_name + ".run", - self.app, - ): - # Ensure type gets through unmolested through shell.main() - argv = sys.argv - shell.main(sys.argv) - self.assertEqual(type(argv[0]), type(self.app.call_args[0][0][0])) - - # When shell.main() gets sys.argv itself it should be decoded - shell.main() - self.assertEqual(type(u'x'), type(self.app.call_args[0][0][0])) From ffd7e939615975b68bb7ec011cdaea077d9201c9 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Fri, 6 Nov 2020 17:31:14 +0000 Subject: [PATCH 2280/3095] functional: Remove test for 'quota set --force' Change I1d1ac1ac46f49f64794ffc8631e166935537966c introduced the 'quota set --force' parameter to force set nova quotas. As part of that fix, we introduced a functional test, 'QuotaTests.test_quota_set_force' that works by attempting to set the 'limit' of the quota for instances to the current usage ('is_use') minus one. This test is flawed. It doesn't create any instances so when it fires by itself, it will always set the 'limit' to 0. When it fires at the same time as other tests (remember, we run tests in parallel), notably tests that rely on booting instances, it can cause other tests to fail with the following error: Quota exceeded for instances: Requested 1, but already used 0 of 0 instances (HTTP 403) We could attempt to work around this by creating a new project and using that project to fiddle with quotas. That's a lot of work though, and the returns are questionable: the 'quota set' command is an admin-only command by default and the '--force' parameter should almost never be used. Simply remove this test. Change-Id: Ic07ff6f4a7c1c27852c892eb906bb144aae91788 Signed-off-by: Stephen Finucane Story: #2008327 Task: #41225 --- .../tests/functional/common/test_quota.py | 44 ------------------- 1 file changed, 44 deletions(-) diff --git a/openstackclient/tests/functional/common/test_quota.py b/openstackclient/tests/functional/common/test_quota.py index 4c2fc0e34d..9c05746077 100644 --- a/openstackclient/tests/functional/common/test_quota.py +++ b/openstackclient/tests/functional/common/test_quota.py @@ -165,47 +165,3 @@ def test_quota_set_class(self): # returned attributes self.assertTrue(cmd_output["key-pairs"] >= 0) self.assertTrue(cmd_output["snapshots"] >= 0) - - def test_quota_set_force(self): - """Test to set instance value by force """ - json_output = json.loads(self.openstack( - 'quota list -f json --detail --compute' - )) - in_use = limit = None - for j in json_output: - if j["Resource"] == "instances": - in_use = j["In Use"] - limit = j["Limit"] - - # Reduce count of in_use - in_use = in_use - 1 - # cannot have negative instances limit - if in_use < 0: - in_use = 0 - - # set the limit by force now - self.openstack( - 'quota set ' + self.PROJECT_NAME + - '--instances ' + str(in_use) + ' --force' - ) - cmd_output = json.loads(self.openstack( - 'quota show -f json ' + self.PROJECT_NAME - )) - self.assertIsNotNone(cmd_output) - self.assertEqual( - in_use, - cmd_output["instances"] - ) - - # Set instances limit to original limit now - self.openstack( - 'quota set ' + self.PROJECT_NAME + '--instances ' + str(limit) - ) - cmd_output = json.loads(self.openstack( - 'quota show -f json ' + self.PROJECT_NAME - )) - self.assertIsNotNone(cmd_output) - self.assertEqual( - limit, - cmd_output["instances"] - ) From 512ba114a18d9ae49eb3e6b4123e92dd896d3f4f Mon Sep 17 00:00:00 2001 From: Artem Goncharov Date: Mon, 9 Nov 2020 13:14:53 +0100 Subject: [PATCH 2281/3095] Switch 'openstack keypair' ops to use SDK Let's continue our journey and start using SDK for the keypair operations Depends-On: https://review.opendev.org/#/c/761883/ Change-Id: Id411e70b8e1a79c0e88a0e22be7ff37e5c30fcda --- openstackclient/compute/v2/keypair.py | 86 ++++---- .../tests/unit/compute/v2/test_keypair.py | 185 ++++++++---------- ...witch-keypair-to-sdk-81e28380e66a7f9c.yaml | 3 + 3 files changed, 128 insertions(+), 146 deletions(-) create mode 100644 releasenotes/notes/switch-keypair-to-sdk-81e28380e66a7f9c.yaml diff --git a/openstackclient/compute/v2/keypair.py b/openstackclient/compute/v2/keypair.py index 726dbfb9aa..19e30bff10 100644 --- a/openstackclient/compute/v2/keypair.py +++ b/openstackclient/compute/v2/keypair.py @@ -20,7 +20,7 @@ import os import sys -from novaclient import api_versions +from openstack import utils as sdk_utils from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -32,6 +32,19 @@ LOG = logging.getLogger(__name__) +def _get_keypair_columns(item, hide_pub_key=False, hide_priv_key=False): + # To maintain backwards compatibility we need to rename sdk props to + # whatever OSC was using before + column_map = {} + hidden_columns = ['links', 'location'] + if hide_pub_key: + hidden_columns.append('public_key') + if hide_priv_key: + hidden_columns.append('private_key') + return utils.get_osc_show_columns_for_sdk_resource( + item, column_map, hidden_columns) + + class CreateKeypair(command.ShowOne): _description = _("Create new public or private key for server ssh access") @@ -76,9 +89,13 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute + compute_client = self.app.client_manager.sdk_connection.compute identity_client = self.app.client_manager.identity + kwargs = { + 'name': parsed_args.name + } + public_key = parsed_args.public_key if public_key: try: @@ -91,12 +108,10 @@ def take_action(self, parsed_args): "exception": e} ) - kwargs = { - 'name': parsed_args.name, - 'public_key': public_key, - } + kwargs['public_key'] = public_key + if parsed_args.type: - if compute_client.api_version < api_versions.APIVersion('2.2'): + if not sdk_utils.supports_microversion(compute_client, '2.2'): msg = _( '--os-compute-api-version 2.2 or greater is required to ' 'support the --type option' @@ -106,7 +121,7 @@ def take_action(self, parsed_args): kwargs['key_type'] = parsed_args.type if parsed_args.user: - if compute_client.api_version < api_versions.APIVersion('2.10'): + if not sdk_utils.supports_microversion(compute_client, '2.10'): msg = _( '--os-compute-api-version 2.10 or greater is required to ' 'support the --user option' @@ -119,7 +134,7 @@ def take_action(self, parsed_args): parsed_args.user_domain, ).id - keypair = compute_client.keypairs.create(**kwargs) + keypair = compute_client.create_keypair(**kwargs) private_key = parsed_args.private_key # Save private key into specified file @@ -139,14 +154,12 @@ def take_action(self, parsed_args): # NOTE(dtroyer): how do we want to handle the display of the private # key when it needs to be communicated back to the user # For now, duplicate nova keypair-add command output - info = {} if public_key or private_key: - info.update(keypair._info) - if 'public_key' in info: - del info['public_key'] - if 'private_key' in info: - del info['private_key'] - return zip(*sorted(info.items())) + display_columns, columns = _get_keypair_columns( + keypair, hide_pub_key=True, hide_priv_key=True) + data = utils.get_item_properties(keypair, columns) + + return (display_columns, data) else: sys.stdout.write(keypair.private_key) return ({}, {}) @@ -175,14 +188,14 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute + compute_client = self.app.client_manager.sdk_connection.compute identity_client = self.app.client_manager.identity kwargs = {} result = 0 if parsed_args.user: - if compute_client.api_version < api_versions.APIVersion('2.10'): + if not sdk_utils.supports_microversion(compute_client, '2.10'): msg = _( '--os-compute-api-version 2.10 or greater is required to ' 'support the --user option' @@ -197,9 +210,8 @@ def take_action(self, parsed_args): for n in parsed_args.name: try: - data = utils.find_resource( - compute_client.keypairs, n) - compute_client.keypairs.delete(data.name, **kwargs) + compute_client.delete_keypair( + n, **kwargs, ignore_missing=False) except Exception as e: result += 1 LOG.error(_("Failed to delete key with name " @@ -240,11 +252,11 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute + compute_client = self.app.client_manager.sdk_connection.compute identity_client = self.app.client_manager.identity if parsed_args.project: - if compute_client.api_version < api_versions.APIVersion('2.10'): + if not sdk_utils.supports_microversion(compute_client, '2.10'): msg = _( '--os-compute-api-version 2.10 or greater is required to ' 'support the --project option' @@ -263,9 +275,9 @@ def take_action(self, parsed_args): data = [] for user in users: - data.extend(compute_client.keypairs.list(user_id=user.id)) + data.extend(compute_client.keypairs(user_id=user.id)) elif parsed_args.user: - if compute_client.api_version < api_versions.APIVersion('2.10'): + if not sdk_utils.supports_microversion(compute_client, '2.10'): msg = _( '--os-compute-api-version 2.10 or greater is required to ' 'support the --user option' @@ -278,16 +290,16 @@ def take_action(self, parsed_args): parsed_args.user_domain, ) - data = compute_client.keypairs.list(user_id=user.id) + data = compute_client.keypairs(user_id=user.id) else: - data = compute_client.keypairs.list() + data = compute_client.keypairs() columns = ( "Name", "Fingerprint" ) - if compute_client.api_version >= api_versions.APIVersion('2.2'): + if sdk_utils.supports_microversion(compute_client, '2.2'): columns += ("Type", ) return ( @@ -324,13 +336,13 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute + compute_client = self.app.client_manager.sdk_connection.compute identity_client = self.app.client_manager.identity kwargs = {} if parsed_args.user: - if compute_client.api_version < api_versions.APIVersion('2.10'): + if not sdk_utils.supports_microversion(compute_client, '2.10'): msg = _( '--os-compute-api-version 2.10 or greater is required to ' 'support the --user option' @@ -343,16 +355,14 @@ def take_action(self, parsed_args): parsed_args.user_domain, ).id - keypair = utils.find_resource( - compute_client.keypairs, parsed_args.name, **kwargs) + keypair = compute_client.find_keypair( + parsed_args.name, **kwargs, ignore_missing=False) - info = {} - info.update(keypair._info) if not parsed_args.public_key: - del info['public_key'] - return zip(*sorted(info.items())) + display_columns, columns = _get_keypair_columns( + keypair, hide_pub_key=True) + data = utils.get_item_properties(keypair, columns) + return (display_columns, data) else: - # NOTE(dtroyer): a way to get the public key in a similar form - # as the private key in the create command sys.stdout.write(keypair.public_key) return ({}, {}) diff --git a/openstackclient/tests/unit/compute/v2/test_keypair.py b/openstackclient/tests/unit/compute/v2/test_keypair.py index 9632a6671a..5a17808fa1 100644 --- a/openstackclient/tests/unit/compute/v2/test_keypair.py +++ b/openstackclient/tests/unit/compute/v2/test_keypair.py @@ -19,8 +19,8 @@ import uuid from novaclient import api_versions +from openstack import utils as sdk_utils from osc_lib import exceptions -from osc_lib import utils from openstackclient.compute.v2 import keypair from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes @@ -34,10 +34,6 @@ class TestKeypair(compute_fakes.TestComputev2): def setUp(self): super(TestKeypair, self).setUp() - # Get a shortcut to the KeypairManager Mock - self.keypairs_mock = self.app.client_manager.compute.keypairs - self.keypairs_mock.reset_mock() - # Initialize the user mock self.users_mock = self.app.client_manager.identity.users self.users_mock.reset_mock() @@ -47,6 +43,14 @@ def setUp(self): loaded=True, ) + self.app.client_manager.sdk_connection = mock.Mock() + self.app.client_manager.sdk_connection.compute = mock.Mock() + self.sdk_client = self.app.client_manager.sdk_connection.compute + self.sdk_client.keypairs = mock.Mock() + self.sdk_client.create_keypair = mock.Mock() + self.sdk_client.delete_keypair = mock.Mock() + self.sdk_client.find_keypair = mock.Mock() + class TestKeypairCreate(TestKeypair): @@ -71,7 +75,7 @@ def setUp(self): # Get the command object to test self.cmd = keypair.CreateKeypair(self.app, None) - self.keypairs_mock.create.return_value = self.keypair + self.sdk_client.create_keypair.return_value = self.keypair def test_key_pair_create_no_options(self): @@ -85,9 +89,8 @@ def test_key_pair_create_no_options(self): columns, data = self.cmd.take_action(parsed_args) - self.keypairs_mock.create.assert_called_with( - name=self.keypair.name, - public_key=None + self.sdk_client.create_keypair.assert_called_with( + name=self.keypair.name ) self.assertEqual({}, columns) @@ -97,7 +100,7 @@ def test_keypair_create_public_key(self): # overwrite the setup one because we want to omit private_key self.keypair = compute_fakes.FakeKeypair.create_one_keypair( no_pri=True) - self.keypairs_mock.create.return_value = self.keypair + self.sdk_client.create_keypair.return_value = self.keypair self.data = ( self.keypair.fingerprint, @@ -124,7 +127,7 @@ def test_keypair_create_public_key(self): columns, data = self.cmd.take_action(parsed_args) - self.keypairs_mock.create.assert_called_with( + self.sdk_client.create_keypair.assert_called_with( name=self.keypair.name, public_key=self.keypair.public_key, ) @@ -151,9 +154,8 @@ def test_keypair_create_private_key(self): columns, data = self.cmd.take_action(parsed_args) - self.keypairs_mock.create.assert_called_with( + self.sdk_client.create_keypair.assert_called_with( name=self.keypair.name, - public_key=None, ) mock_open.assert_called_once_with(tmp_pk_file, 'w+') @@ -162,14 +164,12 @@ def test_keypair_create_private_key(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) - def test_keypair_create_with_key_type(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.2') - + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=True) + def test_keypair_create_with_key_type(self, sm_mock): for key_type in ['x509', 'ssh']: self.keypair = compute_fakes.FakeKeypair.create_one_keypair( no_pri=True) - self.keypairs_mock.create.return_value = self.keypair + self.sdk_client.create_keypair.return_value = self.keypair self.data = ( self.keypair.fingerprint, @@ -195,7 +195,7 @@ def test_keypair_create_with_key_type(self): m_file.read.return_value = 'dummy' columns, data = self.cmd.take_action(parsed_args) - self.keypairs_mock.create.assert_called_with( + self.sdk_client.create_keypair.assert_called_with( name=self.keypair.name, public_key=self.keypair.public_key, key_type=key_type, @@ -204,10 +204,8 @@ def test_keypair_create_with_key_type(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) - def test_keypair_create_with_key_type_pre_v22(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.1') - + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=False) + def test_keypair_create_with_key_type_pre_v22(self, sm_mock): for key_type in ['x509', 'ssh']: arglist = [ '--public-key', self.keypair.public_key, @@ -235,11 +233,8 @@ def test_keypair_create_with_key_type_pre_v22(self): '--os-compute-api-version 2.2 or greater is required', str(ex)) - def test_key_pair_create_with_user(self): - - self.app.client_manager.compute.api_version = \ - api_versions.APIVersion('2.10') - + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=True) + def test_key_pair_create_with_user(self, sm_mock): arglist = [ '--user', identity_fakes.user_name, self.keypair.name, @@ -252,20 +247,16 @@ def test_key_pair_create_with_user(self): columns, data = self.cmd.take_action(parsed_args) - self.keypairs_mock.create.assert_called_with( + self.sdk_client.create_keypair.assert_called_with( name=self.keypair.name, - public_key=None, user_id=identity_fakes.user_id, ) self.assertEqual({}, columns) self.assertEqual({}, data) - def test_key_pair_create_with_user_pre_v210(self): - - self.app.client_manager.compute.api_version = \ - api_versions.APIVersion('2.9') - + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=False) + def test_key_pair_create_with_user_pre_v210(self, sm_mock): arglist = [ '--user', identity_fakes.user_name, self.keypair.name, @@ -291,10 +282,6 @@ class TestKeypairDelete(TestKeypair): def setUp(self): super(TestKeypairDelete, self).setUp() - self.keypairs_mock.get = compute_fakes.FakeKeypair.get_keypairs( - self.keypairs) - self.keypairs_mock.delete.return_value = None - self.cmd = keypair.DeleteKeypair(self.app, None) def test_keypair_delete(self): @@ -310,7 +297,8 @@ def test_keypair_delete(self): ret = self.cmd.take_action(parsed_args) self.assertIsNone(ret) - self.keypairs_mock.delete.assert_called_with(self.keypairs[0].name) + self.sdk_client.delete_keypair.assert_called_with( + self.keypairs[0].name, ignore_missing=False) def test_delete_multiple_keypairs(self): arglist = [] @@ -325,8 +313,8 @@ def test_delete_multiple_keypairs(self): calls = [] for k in self.keypairs: - calls.append(call(k.name)) - self.keypairs_mock.delete.assert_has_calls(calls) + calls.append(call(k.name, ignore_missing=False)) + self.sdk_client.delete_keypair.assert_has_calls(calls) self.assertIsNone(result) def test_delete_multiple_keypairs_with_exception(self): @@ -340,29 +328,21 @@ def test_delete_multiple_keypairs_with_exception(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) - find_mock_result = [self.keypairs[0], exceptions.CommandError] - with mock.patch.object(utils, 'find_resource', - side_effect=find_mock_result) as find_mock: - try: - self.cmd.take_action(parsed_args) - self.fail('CommandError should be raised.') - except exceptions.CommandError as e: - self.assertEqual('1 of 2 keys failed to delete.', str(e)) - - find_mock.assert_any_call( - self.keypairs_mock, self.keypairs[0].name) - find_mock.assert_any_call(self.keypairs_mock, 'unexist_keypair') - - self.assertEqual(2, find_mock.call_count) - self.keypairs_mock.delete.assert_called_once_with( - self.keypairs[0].name - ) - - def test_keypair_delete_with_user(self): + self.sdk_client.delete_keypair.side_effect = [ + None, exceptions.CommandError] + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 keys failed to delete.', str(e)) - self.app.client_manager.compute.api_version = \ - api_versions.APIVersion('2.10') + calls = [] + for k in arglist: + calls.append(call(k, ignore_missing=False)) + self.sdk_client.delete_keypair.assert_has_calls(calls) + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=True) + def test_keypair_delete_with_user(self, sm_mock): arglist = [ '--user', identity_fakes.user_name, self.keypairs[0].name @@ -376,12 +356,14 @@ def test_keypair_delete_with_user(self): ret = self.cmd.take_action(parsed_args) self.assertIsNone(ret) - self.keypairs_mock.delete.assert_called_with( + self.sdk_client.delete_keypair.assert_called_with( self.keypairs[0].name, user_id=identity_fakes.user_id, + ignore_missing=False ) - def test_keypair_delete_with_user_pre_v210(self): + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=False) + def test_keypair_delete_with_user_pre_v210(self, sm_mock): self.app.client_manager.compute.api_version = \ api_versions.APIVersion('2.9') @@ -406,18 +388,19 @@ def test_keypair_delete_with_user_pre_v210(self): class TestKeypairList(TestKeypair): - # Return value of self.keypairs_mock.list(). + # Return value of self.sdk_client.keypairs(). keypairs = compute_fakes.FakeKeypair.create_keypairs(count=1) def setUp(self): super(TestKeypairList, self).setUp() - self.keypairs_mock.list.return_value = self.keypairs + self.sdk_client.keypairs.return_value = self.keypairs # Get the command object to test self.cmd = keypair.ListKeypair(self.app, None) - def test_keypair_list_no_options(self): + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=False) + def test_keypair_list_no_options(self, sm_mock): arglist = [] verifylist = [] @@ -430,7 +413,7 @@ def test_keypair_list_no_options(self): # Set expected values - self.keypairs_mock.list.assert_called_with() + self.sdk_client.keypairs.assert_called_with() self.assertEqual(('Name', 'Fingerprint'), columns) self.assertEqual( @@ -438,10 +421,8 @@ def test_keypair_list_no_options(self): tuple(data) ) - def test_keypair_list_v22(self): - self.app.client_manager.compute.api_version = \ - api_versions.APIVersion('2.2') - + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=True) + def test_keypair_list_v22(self, sm_mock): arglist = [] verifylist = [] @@ -454,7 +435,7 @@ def test_keypair_list_v22(self): # Set expected values - self.keypairs_mock.list.assert_called_with() + self.sdk_client.keypairs.assert_called_with() self.assertEqual(('Name', 'Fingerprint', 'Type'), columns) self.assertEqual( @@ -466,11 +447,8 @@ def test_keypair_list_v22(self): tuple(data) ) - def test_keypair_list_with_user(self): - - # Filtering by user is support for nova api 2.10 or above - self.app.client_manager.compute.api_version = \ - api_versions.APIVersion('2.10') + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=True) + def test_keypair_list_with_user(self, sm_mock): users_mock = self.app.client_manager.identity.users users_mock.reset_mock() @@ -491,7 +469,7 @@ def test_keypair_list_with_user(self): columns, data = self.cmd.take_action(parsed_args) users_mock.get.assert_called_with(identity_fakes.user_name) - self.keypairs_mock.list.assert_called_with( + self.sdk_client.keypairs.assert_called_with( user_id=identity_fakes.user_id, ) @@ -505,10 +483,8 @@ def test_keypair_list_with_user(self): tuple(data) ) - def test_keypair_list_with_user_pre_v210(self): - - self.app.client_manager.compute.api_version = \ - api_versions.APIVersion('2.9') + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=False) + def test_keypair_list_with_user_pre_v210(self, sm_mock): arglist = [ '--user', identity_fakes.user_name, @@ -525,11 +501,8 @@ def test_keypair_list_with_user_pre_v210(self): self.assertIn( '--os-compute-api-version 2.10 or greater is required', str(ex)) - def test_keypair_list_with_project(self): - - # Filtering by user is support for nova api 2.10 or above - self.app.client_manager.compute.api_version = \ - api_versions.APIVersion('2.10') + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=True) + def test_keypair_list_with_project(self, sm_mock): projects_mock = self.app.client_manager.identity.tenants projects_mock.reset_mock() @@ -557,7 +530,7 @@ def test_keypair_list_with_project(self): projects_mock.get.assert_called_with(identity_fakes.project_name) users_mock.list.assert_called_with(tenant_id=identity_fakes.project_id) - self.keypairs_mock.list.assert_called_with( + self.sdk_client.keypairs.assert_called_with( user_id=identity_fakes.user_id, ) @@ -571,10 +544,8 @@ def test_keypair_list_with_project(self): tuple(data) ) - def test_keypair_list_with_project_pre_v210(self): - - self.app.client_manager.compute.api_version = \ - api_versions.APIVersion('2.9') + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=False) + def test_keypair_list_with_project_pre_v210(self, sm_mock): arglist = ['--project', identity_fakes.project_name] verifylist = [('project', identity_fakes.project_name)] @@ -589,10 +560,6 @@ def test_keypair_list_with_project_pre_v210(self): def test_keypair_list_conflicting_user_options(self): - # Filtering by user is support for nova api 2.10 or above - self.app.client_manager.compute.api_version = \ - api_versions.APIVersion('2.10') - arglist = [ '--user', identity_fakes.user_name, '--project', identity_fakes.project_name, @@ -610,7 +577,7 @@ class TestKeypairShow(TestKeypair): def setUp(self): super(TestKeypairShow, self).setUp() - self.keypairs_mock.get.return_value = self.keypair + self.sdk_client.find_keypair.return_value = self.keypair self.cmd = keypair.ShowKeypair(self.app, None) @@ -641,7 +608,7 @@ def test_keypair_show(self): # overwrite the setup one because we want to omit private_key self.keypair = compute_fakes.FakeKeypair.create_one_keypair( no_pri=True) - self.keypairs_mock.get.return_value = self.keypair + self.sdk_client.find_keypair.return_value = self.keypair self.data = ( self.keypair.fingerprint, @@ -660,8 +627,9 @@ def test_keypair_show(self): columns, data = self.cmd.take_action(parsed_args) - self.keypairs_mock.get.assert_called_with( + self.sdk_client.find_keypair.assert_called_with( self.keypair.name, + ignore_missing=False ) self.assertEqual(self.columns, columns) @@ -685,12 +653,13 @@ def test_keypair_show_public(self): self.assertEqual({}, columns) self.assertEqual({}, data) - def test_keypair_show_with_user(self): + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=True) + def test_keypair_show_with_user(self, sm_mock): # overwrite the setup one because we want to omit private_key self.keypair = compute_fakes.FakeKeypair.create_one_keypair( no_pri=True) - self.keypairs_mock.get.return_value = self.keypair + self.sdk_client.find_keypair.return_value = self.keypair self.data = ( self.keypair.fingerprint, @@ -699,9 +668,6 @@ def test_keypair_show_with_user(self): self.keypair.user_id ) - self.app.client_manager.compute.api_version = \ - api_versions.APIVersion('2.10') - arglist = [ '--user', identity_fakes.user_name, self.keypair.name, @@ -715,14 +681,17 @@ def test_keypair_show_with_user(self): columns, data = self.cmd.take_action(parsed_args) self.users_mock.get.assert_called_with(identity_fakes.user_name) - self.keypairs_mock.get.assert_called_with( + self.sdk_client.find_keypair.assert_called_with( self.keypair.name, + ignore_missing=False, + user_id=identity_fakes.user_id ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) - def test_keypair_show_with_user_pre_v210(self): + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=False) + def test_keypair_show_with_user_pre_v210(self, sm_mock): arglist = [ '--user', identity_fakes.user_name, diff --git a/releasenotes/notes/switch-keypair-to-sdk-81e28380e66a7f9c.yaml b/releasenotes/notes/switch-keypair-to-sdk-81e28380e66a7f9c.yaml new file mode 100644 index 0000000000..17e53c4cd6 --- /dev/null +++ b/releasenotes/notes/switch-keypair-to-sdk-81e28380e66a7f9c.yaml @@ -0,0 +1,3 @@ +--- +Features: + - Switch 'openstack keypair' commands to use OpenStackSDK From 1a5dd4af5bf7fdc1b903b300b917f302ba75a1bc Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Mon, 9 Nov 2020 14:53:57 +0000 Subject: [PATCH 2282/3095] Resolve issues with 'server migration list' The 'os-migrations' API accepts 'instance_uuid' and 'migration_type' query string parameters, not 'server' and 'type'. For the former, as the name would suggest, the value should be a server UUID, not a name. In addition, this is a list command and therefore should subclass the 'Lister' base class. Change-Id: I736f5575156fc04d7ada7783a1865ab3b438396f Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server.py | 11 ++++++++--- openstackclient/tests/unit/compute/v2/test_server.py | 12 +++--------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index fddafaee69..e897ab1ae7 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -2041,7 +2041,7 @@ def _show_progress(progress): raise SystemExit -class ListMigration(command.Command): +class ListMigration(command.Lister): _description = _("""List server migrations""") def get_parser(self, prog_name): @@ -2168,16 +2168,21 @@ def take_action(self, parsed_args): search_opts = { 'host': parsed_args.host, - 'server': parsed_args.server, 'status': parsed_args.status, } + if parsed_args.server: + search_opts['instance_uuid'] = utils.find_resource( + compute_client.servers, + parsed_args.server, + ).id + if parsed_args.type: migration_type = parsed_args.type # we're using an alias because the default value is confusing if migration_type == 'cold-migration': migration_type = 'migration' - search_opts['type'] = migration_type + search_opts['migration_type'] = migration_type if parsed_args.marker: if compute_client.api_version < api_versions.APIVersion('2.59'): diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 15cdbf1637..d095f0cb84 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -4079,7 +4079,6 @@ def test_server_migration_list_no_options(self): kwargs = { 'status': None, 'host': None, - 'server': None, } self.migrations_mock.list.assert_called_with(**kwargs) @@ -4106,10 +4105,11 @@ def test_server_migration_list(self): kwargs = { 'status': 'migrating', 'host': 'host1', - 'server': 'server1', - 'type': 'migration', + 'instance_uuid': self.server.id, + 'migration_type': 'migration', } + self.servers_mock.get.assert_called_with('server1') self.migrations_mock.list.assert_called_with(**kwargs) self.assertEqual(self.MIGRATION_COLUMNS, columns) @@ -4145,7 +4145,6 @@ def test_server_migration_list(self): kwargs = { 'status': 'migrating', 'host': None, - 'server': None, } self.migrations_mock.list.assert_called_with(**kwargs) @@ -4194,7 +4193,6 @@ def test_server_migration_list(self): 'limit': 1, 'marker': 'test_kp', 'host': None, - 'server': None, 'changes_since': '2019-08-09T08:03:25Z', } @@ -4303,7 +4301,6 @@ def test_server_migration_list_with_changes_before(self): 'limit': 1, 'marker': 'test_kp', 'host': None, - 'server': None, 'changes_since': '2019-08-07T08:03:25Z', 'changes_before': '2019-08-09T08:03:25Z', } @@ -4375,7 +4372,6 @@ def test_server_migration_list_with_project(self): 'limit': 1, 'marker': 'test_kp', 'host': None, - 'server': None, 'project_id': '0c2accde-644a-45fa-8c10-e76debc7fbc3', 'changes_since': '2019-08-07T08:03:25Z', 'changes_before': "2019-08-09T08:03:25Z", @@ -4438,7 +4434,6 @@ def test_server_migration_list_with_user(self): 'limit': 1, 'marker': 'test_kp', 'host': None, - 'server': None, 'user_id': 'dd214878-ca12-40fb-b035-fa7d2c1e86d6', 'changes_since': '2019-08-07T08:03:25Z', 'changes_before': "2019-08-09T08:03:25Z", @@ -4500,7 +4495,6 @@ def test_server_migration_list_with_project_and_user(self): 'status': 'migrating', 'limit': 1, 'host': None, - 'server': None, 'project_id': '0c2accde-644a-45fa-8c10-e76debc7fbc3', 'user_id': 'dd214878-ca12-40fb-b035-fa7d2c1e86d6', 'changes_since': '2019-08-07T08:03:25Z', From 4c0bfb03fc688575fa93667bb59d399fd1c6b41d Mon Sep 17 00:00:00 2001 From: Dmitriy Rabotyagov Date: Thu, 5 Nov 2020 17:51:27 +0200 Subject: [PATCH 2283/3095] Allow to resize in-use volumes Since Pike (microversion 3.42) [1] Cinder API allows to resize in-use volumes. So no reason not to implement it in CLI. [1] https://opendev.org/openstack/cinder/src/branch/master/cinder/api/openstack/rest_api_version_history.rst#user-content-section-39 Change-Id: I22462a56d261e0a100aac3f27af7be47223edec0 --- openstackclient/volume/client.py | 11 +++++++++-- openstackclient/volume/v2/volume.py | 10 ++++++---- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/openstackclient/volume/client.py b/openstackclient/volume/client.py index 1fbfaaee78..64e8b9f34d 100644 --- a/openstackclient/volume/client.py +++ b/openstackclient/volume/client.py @@ -29,6 +29,7 @@ "1": "cinderclient.v1.client.Client", "2": "cinderclient.v2.client.Client", "3": "cinderclient.v3.client.Client", + "3.42": "cinderclient.v3.client.Client", } @@ -47,14 +48,19 @@ def make_client(instance): except Exception: del API_VERSIONS['1'] - if instance._api_version[API_NAME] == '1': + version = instance._api_version[API_NAME] + from cinderclient import api_versions + # convert to APIVersion object + version = api_versions.get_api_version(version) + + if version.ver_major == '1': # Monkey patch for v1 cinderclient volumes.Volume.NAME_ATTR = 'display_name' volume_snapshots.Snapshot.NAME_ATTR = 'display_name' volume_client = utils.get_client_class( API_NAME, - instance._api_version[API_NAME], + version.ver_major, API_VERSIONS ) LOG.debug('Instantiating volume client: %s', volume_client) @@ -76,6 +82,7 @@ def make_client(instance): http_log_debug=http_log_debug, region_name=instance.region_name, endpoint_override=endpoint_override, + api_version=version, **kwargs ) diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index 1e0cb183fc..cab0b2f4c7 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -605,14 +605,16 @@ def take_action(self, parsed_args): result = 0 if parsed_args.size: try: - if volume.status != 'available': - msg = (_("Volume is in %s state, it must be available " - "before size can be extended") % volume.status) - raise exceptions.CommandError(msg) if parsed_args.size <= volume.size: msg = (_("New size must be greater than %s GB") % volume.size) raise exceptions.CommandError(msg) + if volume.status != 'available' and \ + not volume_client.api_version.matches('3.42'): + + msg = (_("Volume is in %s state, it must be available " + "before size can be extended") % volume.status) + raise exceptions.CommandError(msg) volume_client.volumes.extend(volume.id, parsed_args.size) except Exception as e: LOG.error(_("Failed to set volume size: %s"), e) From ebaf0eae2c60bf000a0af53a8d5f5c54d32fc311 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Mon, 16 Nov 2020 11:11:21 +0000 Subject: [PATCH 2284/3095] tests: Remove 'agent' functional tests The 'os-agents' API was recently removed from nova [1]. Remove the functional tests, since they will always fail going forward but will continue to run on older stable branches. Also Squeeze https://review.opendev.org/#/c/762559/ inside, since those 2 are simultaneously blocking gate [1] https://review.opendev.org/#/c/749309/ Change-Id: I0bf7d4c0ba2a9d4637db0d209d8d52163d772f12 Signed-off-by: Stephen Finucane --- .../tests/functional/compute/v2/test_agent.py | 196 ------------------ .../unit/volume/v2/test_volume_backend.py | 2 +- 2 files changed, 1 insertion(+), 197 deletions(-) delete mode 100644 openstackclient/tests/functional/compute/v2/test_agent.py diff --git a/openstackclient/tests/functional/compute/v2/test_agent.py b/openstackclient/tests/functional/compute/v2/test_agent.py deleted file mode 100644 index 25d8c868dc..0000000000 --- a/openstackclient/tests/functional/compute/v2/test_agent.py +++ /dev/null @@ -1,196 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import hashlib -import json - -from openstackclient.tests.functional import base - - -class ComputeAgentTests(base.TestCase): - """Functional tests for compute agent.""" - - # Generate two different md5hash - MD5HASH1 = hashlib.md5() - MD5HASH1.update('agent_1'.encode('utf-8')) - MD5HASH1 = MD5HASH1.hexdigest() - MD5HASH2 = hashlib.md5() - MD5HASH2.update('agent_2'.encode('utf-8')) - MD5HASH2 = MD5HASH2.hexdigest() - - def test_compute_agent_delete(self): - """Test compute agent create, delete multiple""" - os1 = "os_1" - arch1 = "x86_64" - ver1 = "v1" - url1 = "http://localhost" - md5hash1 = self.MD5HASH1 - hyper1 = "kvm" - cmd1 = ' '.join((os1, arch1, ver1, url1, md5hash1, hyper1)) - - cmd_output = json.loads(self.openstack( - 'compute agent create -f json ' + - cmd1 - )) - agent_id1 = str(cmd_output["agent_id"]) - - os2 = "os_2" - arch2 = "x86" - ver2 = "v2" - url2 = "http://openstack" - md5hash2 = self.MD5HASH2 - hyper2 = "xen" - cmd2 = ' '.join((os2, arch2, ver2, url2, md5hash2, hyper2)) - - cmd_output = json.loads(self.openstack( - 'compute agent create -f json ' + - cmd2 - )) - agent_id2 = str(cmd_output["agent_id"]) - - # Test compute agent delete - del_output = self.openstack( - 'compute agent delete ' + - agent_id1 + ' ' + agent_id2 - ) - self.assertOutput('', del_output) - - def test_compute_agent_list(self): - """Test compute agent create and list""" - os1 = "os_1" - arch1 = "x86_64" - ver1 = "v1" - url1 = "http://localhost" - md5hash1 = self.MD5HASH1 - hyper1 = "kvm" - cmd1 = ' '.join((os1, arch1, ver1, url1, md5hash1, hyper1)) - - cmd_output = json.loads(self.openstack( - 'compute agent create -f json ' + - cmd1 - )) - agent_id1 = str(cmd_output["agent_id"]) - self.addCleanup(self.openstack, 'compute agent delete ' + agent_id1) - - os2 = "os_2" - arch2 = "x86" - ver2 = "v2" - url2 = "http://openstack" - md5hash2 = self.MD5HASH2 - hyper2 = "xen" - cmd2 = ' '.join((os2, arch2, ver2, url2, md5hash2, hyper2)) - - cmd_output = json.loads(self.openstack( - 'compute agent create -f json ' + - cmd2 - )) - agent_id2 = str(cmd_output["agent_id"]) - self.addCleanup(self.openstack, 'compute agent delete ' + agent_id2) - - # Test compute agent list - cmd_output = json.loads(self.openstack( - 'compute agent list -f json' - )) - - hypervisors = [x["Hypervisor"] for x in cmd_output] - self.assertIn(hyper1, hypervisors) - self.assertIn(hyper2, hypervisors) - - os = [x['OS'] for x in cmd_output] - self.assertIn(os1, os) - self.assertIn(os2, os) - - archs = [x['Architecture'] for x in cmd_output] - self.assertIn(arch1, archs) - self.assertIn(arch2, archs) - - versions = [x['Version'] for x in cmd_output] - self.assertIn(ver1, versions) - self.assertIn(ver2, versions) - - md5hashes = [x['Md5Hash'] for x in cmd_output] - self.assertIn(md5hash1, md5hashes) - self.assertIn(md5hash2, md5hashes) - - urls = [x['URL'] for x in cmd_output] - self.assertIn(url1, urls) - self.assertIn(url2, urls) - - # Test compute agent list --hypervisor - cmd_output = json.loads(self.openstack( - 'compute agent list -f json ' + - '--hypervisor kvm' - )) - - hypervisors = [x["Hypervisor"] for x in cmd_output] - self.assertIn(hyper1, hypervisors) - self.assertNotIn(hyper2, hypervisors) - - os = [x['OS'] for x in cmd_output] - self.assertIn(os1, os) - self.assertNotIn(os2, os) - - archs = [x['Architecture'] for x in cmd_output] - self.assertIn(arch1, archs) - self.assertNotIn(arch2, archs) - - versions = [x['Version'] for x in cmd_output] - self.assertIn(ver1, versions) - self.assertNotIn(ver2, versions) - - md5hashes = [x['Md5Hash'] for x in cmd_output] - self.assertIn(md5hash1, md5hashes) - self.assertNotIn(md5hash2, md5hashes) - - urls = [x['URL'] for x in cmd_output] - self.assertIn(url1, urls) - self.assertNotIn(url2, urls) - - def test_compute_agent_set(self): - """Test compute agent set""" - os1 = "os_1" - arch1 = "x86_64" - ver1 = "v1" - ver2 = "v2" - url1 = "http://localhost" - url2 = "http://openstack" - md5hash1 = self.MD5HASH1 - md5hash2 = self.MD5HASH2 - hyper1 = "kvm" - cmd = ' '.join((os1, arch1, ver1, url1, md5hash1, hyper1)) - - cmd_output = json.loads(self.openstack( - 'compute agent create -f json ' + - cmd - )) - agent_id = str(cmd_output["agent_id"]) - self.assertEqual(ver1, cmd_output["version"]) - self.assertEqual(url1, cmd_output["url"]) - self.assertEqual(md5hash1, cmd_output["md5hash"]) - - self.addCleanup(self.openstack, 'compute agent delete ' + agent_id) - - raw_output = self.openstack( - 'compute agent set ' + - agent_id + ' ' + - '--agent-version ' + ver2 + ' ' + - '--url ' + url2 + ' ' + - '--md5hash ' + md5hash2 - ) - self.assertOutput('', raw_output) - - cmd_output = json.loads(self.openstack( - 'compute agent list -f json' - )) - self.assertEqual(ver2, cmd_output[0]["Version"]) - self.assertEqual(url2, cmd_output[0]["URL"]) - self.assertEqual(md5hash2, cmd_output[0]["Md5Hash"]) diff --git a/openstackclient/tests/unit/volume/v2/test_volume_backend.py b/openstackclient/tests/unit/volume/v2/test_volume_backend.py index db18866087..d9ac2c96c7 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume_backend.py +++ b/openstackclient/tests/unit/volume/v2/test_volume_backend.py @@ -65,7 +65,7 @@ def test_capability_show(self): # confirming if all expected values are present in the result. for cap in data: - self.assertTrue(cap[0] in capabilities) + self.assertIn(cap[0], capabilities) # checking if proper call was made to get capabilities self.capability_mock.get.assert_called_with( From a5101a413983c868c8dd865c6db42b5d87456d0e Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Mon, 16 Nov 2020 11:15:32 +0000 Subject: [PATCH 2285/3095] trivial: Document removal of support for agents We can't remove these commands for a long time, given OSC's intention to support multiple releases of OpenStack, but we can at least indicate to users that this thing might not work anymore. Change-Id: I9093cc1197a0287984d83e2020fba100d0c958b3 Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/agent.py | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/openstackclient/compute/v2/agent.py b/openstackclient/compute/v2/agent.py index 3feb99ec83..15fb0f9c36 100644 --- a/openstackclient/compute/v2/agent.py +++ b/openstackclient/compute/v2/agent.py @@ -28,7 +28,12 @@ class CreateAgent(command.ShowOne): - _description = _("Create compute agent") + """Create compute agent. + + The compute agent functionality is hypervisor specific and is only + supported by the XenAPI hypervisor driver. It was removed from nova in the + 23.0.0 (Wallaby) release. + """ def get_parser(self, prog_name): parser = super(CreateAgent, self).get_parser(prog_name) @@ -80,7 +85,12 @@ def take_action(self, parsed_args): class DeleteAgent(command.Command): - _description = _("Delete compute agent(s)") + """Delete compute agent(s). + + The compute agent functionality is hypervisor specific and is only + supported by the XenAPI hypervisor driver. It was removed from nova in the + 23.0.0 (Wallaby) release. + """ def get_parser(self, prog_name): parser = super(DeleteAgent, self).get_parser(prog_name) @@ -111,7 +121,12 @@ def take_action(self, parsed_args): class ListAgent(command.Lister): - _description = _("List compute agents") + """List compute agents. + + The compute agent functionality is hypervisor specific and is only + supported by the XenAPI hypervisor driver. It was removed from nova in the + 23.0.0 (Wallaby) release. + """ def get_parser(self, prog_name): parser = super(ListAgent, self).get_parser(prog_name) @@ -141,7 +156,12 @@ def take_action(self, parsed_args): class SetAgent(command.Command): - _description = _("Set compute agent properties") + """Set compute agent properties. + + The compute agent functionality is hypervisor specific and is only + supported by the XenAPI hypervisor driver. It was removed from nova in the + 23.0.0 (Wallaby) release. + """ def get_parser(self, prog_name): parser = super(SetAgent, self).get_parser(prog_name) From 8387b114e38f21922967ba982bb1a25289fdb3ab Mon Sep 17 00:00:00 2001 From: Rodolfo Alonso Hernandez Date: Thu, 24 Sep 2020 14:49:55 +0000 Subject: [PATCH 2286/3095] Add "fields" parameter to ListPort query This new query parameter will allow to send a query to the Neutron server filtering only by those parameters needed by the list command: ID, name, MAC address, fixed IPs and status. When using input parameter "long", security groups IDs, device owner and tags will be added to the fields filter. With 4500 ports, those are the execution times for the command "openstack port list" (average values in a development environment): Neutron API (seconds) CLI (seconds) Without filter: 3.05 10.15 With filter: 2.76 8.19 Depends-On: https://review.opendev.org/#/c/754113/ Change-Id: I1cccf0bc3533f8085e8dd61bf2fbe78c49b74b31 Closes-Bug: #1897100 --- lower-constraints.txt | 2 +- openstackclient/network/v2/port.py | 2 +- .../tests/unit/network/v2/test_port.py | 74 ++++++++++++++----- requirements.txt | 2 +- 4 files changed, 58 insertions(+), 22 deletions(-) diff --git a/lower-constraints.txt b/lower-constraints.txt index 2dae561cc9..b489501b87 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -45,7 +45,7 @@ msgpack-python==0.4.0 munch==2.1.0 netaddr==0.7.18 netifaces==0.10.4 -openstacksdk==0.48.0 +openstacksdk==0.51.0 os-service-types==1.7.0 os-testr==1.0.0 osc-lib==2.2.0 diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 02ab06c1c8..cb77759efd 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -665,7 +665,7 @@ def take_action(self, parsed_args): _tag.get_tag_filtering_args(parsed_args, filters) - data = network_client.ports(**filters) + data = network_client.ports(fields=columns, **filters) headers, attrs = utils.calculate_header_and_attrs( column_headers, columns, parsed_args) diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index d8889ae558..f7685f46e5 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -26,6 +26,10 @@ from openstackclient.tests.unit import utils as tests_utils +LIST_FIELDS_TO_RETRIEVE = ('id', 'name', 'mac_address', 'fixed_ips', 'status') +LIST_FIELDS_TO_RETRIEVE_LONG = ('security_group_ids', 'device_owner', 'tags') + + class TestPort(network_fakes.TestNetworkV2): def setUp(self): @@ -883,7 +887,8 @@ def test_port_list_no_options(self): columns, data = self.cmd.take_action(parsed_args) - self.network.ports.assert_called_once_with() + self.network.ports.assert_called_once_with( + fields=LIST_FIELDS_TO_RETRIEVE) self.assertEqual(self.columns, columns) self.assertListItemEqual(self.data, list(data)) @@ -901,7 +906,8 @@ def test_port_list_router_opt(self): columns, data = self.cmd.take_action(parsed_args) self.network.ports.assert_called_once_with(**{ - 'device_id': 'fake-router-id' + 'device_id': 'fake-router-id', + 'fields': LIST_FIELDS_TO_RETRIEVE, }) self.assertEqual(self.columns, columns) self.assertListItemEqual(self.data, list(data)) @@ -921,7 +927,8 @@ def test_port_list_with_server_option(self, mock_find): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.network.ports.assert_called_once_with( - device_id=fake_server.id) + device_id=fake_server.id, + fields=LIST_FIELDS_TO_RETRIEVE) mock_find.assert_called_once_with(mock.ANY, 'fake-server-name') self.assertEqual(self.columns, columns) self.assertListItemEqual(self.data, list(data)) @@ -940,7 +947,8 @@ def test_port_list_device_id_opt(self): columns, data = self.cmd.take_action(parsed_args) self.network.ports.assert_called_once_with(**{ - 'device_id': self._ports[0].device_id + 'device_id': self._ports[0].device_id, + 'fields': LIST_FIELDS_TO_RETRIEVE, }) self.assertEqual(self.columns, columns) self.assertListItemEqual(self.data, list(data)) @@ -959,7 +967,8 @@ def test_port_list_device_owner_opt(self): columns, data = self.cmd.take_action(parsed_args) self.network.ports.assert_called_once_with(**{ - 'device_owner': self._ports[0].device_owner + 'device_owner': self._ports[0].device_owner, + 'fields': LIST_FIELDS_TO_RETRIEVE, }) self.assertEqual(self.columns, columns) self.assertListItemEqual(self.data, list(data)) @@ -987,7 +996,8 @@ def test_port_list_all_opt(self): 'device_owner': self._ports[0].device_owner, 'device_id': 'fake-router-id', 'network_id': 'fake-network-id', - 'mac_address': self._ports[0].mac_address + 'mac_address': self._ports[0].mac_address, + 'fields': LIST_FIELDS_TO_RETRIEVE, }) self.assertEqual(self.columns, columns) self.assertListItemEqual(self.data, list(data)) @@ -1006,7 +1016,8 @@ def test_port_list_mac_address_opt(self): columns, data = self.cmd.take_action(parsed_args) self.network.ports.assert_called_once_with(**{ - 'mac_address': self._ports[0].mac_address + 'mac_address': self._ports[0].mac_address, + 'fields': LIST_FIELDS_TO_RETRIEVE, }) self.assertEqual(self.columns, columns) self.assertListItemEqual(self.data, list(data)) @@ -1025,7 +1036,9 @@ def test_port_list_fixed_ip_opt_ip_address(self): columns, data = self.cmd.take_action(parsed_args) self.network.ports.assert_called_once_with(**{ - 'fixed_ips': ['ip_address=%s' % ip_address]}) + 'fixed_ips': ['ip_address=%s' % ip_address], + 'fields': LIST_FIELDS_TO_RETRIEVE, + }) self.assertEqual(self.columns, columns) self.assertListItemEqual(self.data, list(data)) @@ -1043,7 +1056,9 @@ def test_port_list_fixed_ip_opt_ip_address_substr(self): columns, data = self.cmd.take_action(parsed_args) self.network.ports.assert_called_once_with(**{ - 'fixed_ips': ['ip_address_substr=%s' % ip_address_ss]}) + 'fixed_ips': ['ip_address_substr=%s' % ip_address_ss], + 'fields': LIST_FIELDS_TO_RETRIEVE, + }) self.assertEqual(self.columns, columns) self.assertListItemEqual(self.data, list(data)) @@ -1063,7 +1078,9 @@ def test_port_list_fixed_ip_opt_subnet_id(self): columns, data = self.cmd.take_action(parsed_args) self.network.ports.assert_called_once_with(**{ - 'fixed_ips': ['subnet_id=%s' % subnet_id]}) + 'fixed_ips': ['subnet_id=%s' % subnet_id], + 'fields': LIST_FIELDS_TO_RETRIEVE, + }) self.assertEqual(self.columns, columns) self.assertListItemEqual(self.data, list(data)) @@ -1087,7 +1104,9 @@ def test_port_list_fixed_ip_opts(self): self.network.ports.assert_called_once_with(**{ 'fixed_ips': ['subnet_id=%s' % subnet_id, - 'ip_address=%s' % ip_address]}) + 'ip_address=%s' % ip_address], + 'fields': LIST_FIELDS_TO_RETRIEVE, + }) self.assertEqual(self.columns, columns) self.assertListItemEqual(self.data, list(data)) @@ -1103,15 +1122,19 @@ def test_port_list_fixed_ips(self): {'ip-address': ip_address}]) ] - self.fake_subnet = network_fakes.FakeSubnet.create_one_subnet( - {'id': subnet_id}) + self.fake_subnet = network_fakes.FakeSubnet.create_one_subnet({ + 'id': subnet_id, + 'fields': LIST_FIELDS_TO_RETRIEVE, + }) self.network.find_subnet = mock.Mock(return_value=self.fake_subnet) parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.network.ports.assert_called_once_with(**{ 'fixed_ips': ['subnet_id=%s' % subnet_id, - 'ip_address=%s' % ip_address]}) + 'ip_address=%s' % ip_address], + 'fields': LIST_FIELDS_TO_RETRIEVE, + }) self.assertEqual(self.columns, columns) self.assertListItemEqual(self.data, list(data)) @@ -1128,7 +1151,8 @@ def test_list_port_with_long(self): columns, data = self.cmd.take_action(parsed_args) - self.network.ports.assert_called_once_with() + self.network.ports.assert_called_once_with( + fields=LIST_FIELDS_TO_RETRIEVE + LIST_FIELDS_TO_RETRIEVE_LONG) self.assertEqual(self.columns_long, columns) self.assertListItemEqual(self.data_long, list(data)) @@ -1142,7 +1166,10 @@ def test_port_list_host(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - filters = {'binding:host_id': 'foobar'} + filters = { + 'binding:host_id': 'foobar', + 'fields': LIST_FIELDS_TO_RETRIEVE, + } self.network.ports.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) @@ -1160,7 +1187,11 @@ def test_port_list_project(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - filters = {'tenant_id': project.id, 'project_id': project.id} + filters = { + 'tenant_id': project.id, + 'project_id': project.id, + 'fields': LIST_FIELDS_TO_RETRIEVE, + } self.network.ports.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) @@ -1180,7 +1211,11 @@ def test_port_list_project_domain(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - filters = {'tenant_id': project.id, 'project_id': project.id} + filters = { + 'tenant_id': project.id, + 'project_id': project.id, + 'fields': LIST_FIELDS_TO_RETRIEVE, + } self.network.ports.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) @@ -1206,7 +1241,8 @@ def test_list_with_tag_options(self): **{'tags': 'red,blue', 'any_tags': 'red,green', 'not_tags': 'orange,yellow', - 'not_any_tags': 'black,white'} + 'not_any_tags': 'black,white', + 'fields': LIST_FIELDS_TO_RETRIEVE} ) self.assertEqual(self.columns, columns) self.assertListItemEqual(self.data, list(data)) diff --git a/requirements.txt b/requirements.txt index 99d409bf51..9430a7fde0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ pbr!=2.1.0,>=2.0.0 # Apache-2.0 cliff>=3.4.0 # Apache-2.0 iso8601>=0.1.11 # MIT -openstacksdk>=0.48.0 # Apache-2.0 +openstacksdk>=0.51.0 # Apache-2.0 osc-lib>=2.2.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 python-keystoneclient>=3.22.0 # Apache-2.0 From 5bdcd590ecacbc0aa8db2cbafa0ab1a9f3c28ce0 Mon Sep 17 00:00:00 2001 From: Simon Merrick Date: Thu, 19 Nov 2020 20:05:54 +1300 Subject: [PATCH 2287/3095] stop image downloads to memory + Fixes issue with large images hogging memory + stream image downloads + output to stdout if file not specified Change-Id: Ia01ff9b21a2dac5d0ccf2bd58a8640e88c5cbb36 Story: 2007672 Task: 39776 --- openstackclient/image/v1/image.py | 6 +++++- openstackclient/image/v2/image.py | 6 +++++- openstackclient/tests/unit/image/v2/test_image.py | 3 ++- .../fix-openstak-image-save-sdk-port-eb160e8ffc92e514.yaml | 7 +++++++ 4 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/fix-openstak-image-save-sdk-port-eb160e8ffc92e514.yaml diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index cf1d68171a..64aa3fcdab 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -478,7 +478,11 @@ def take_action(self, parsed_args): image_client = self.app.client_manager.image image = image_client.find_image(parsed_args.image) - image_client.download_image(image.id, output=parsed_args.file) + output_file = parsed_args.file + if output_file is None: + output_file = getattr(sys.stdout, "buffer", sys.stdout) + + image_client.download_image(image.id, stream=True, output=output_file) class SetImage(command.Command): diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 4f3e9d0bcf..58d92f5165 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -803,7 +803,11 @@ def take_action(self, parsed_args): image_client = self.app.client_manager.image image = image_client.find_image(parsed_args.image) - image_client.download_image(image.id, output=parsed_args.file) + output_file = parsed_args.file + if output_file is None: + output_file = getattr(sys.stdout, "buffer", sys.stdout) + + image_client.download_image(image.id, stream=True, output=output_file) class SetImage(command.Command): diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py index b094817efe..80e60ee818 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -1618,7 +1618,7 @@ def test_save_data(self): verifylist = [ ('file', '/path/to/file'), - ('image', self.image.id) + ('image', self.image.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1626,6 +1626,7 @@ def test_save_data(self): self.client.download_image.assert_called_once_with( self.image.id, + stream=True, output='/path/to/file') diff --git a/releasenotes/notes/fix-openstak-image-save-sdk-port-eb160e8ffc92e514.yaml b/releasenotes/notes/fix-openstak-image-save-sdk-port-eb160e8ffc92e514.yaml new file mode 100644 index 0000000000..857f3c507e --- /dev/null +++ b/releasenotes/notes/fix-openstak-image-save-sdk-port-eb160e8ffc92e514.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - Stream image download to avoid buffering data in memory which rapidly + exhausts memory resulting in OOM kill or system crash for all but the + smallest of images. Fixes https://storyboard.openstack.org/#!/story/2007672 + - Restore default behavior of 'openstack image save' to send data to stdout + Relates to https://storyboard.openstack.org/#!/story/2007672. \ No newline at end of file From 0f02029d917366c6c757ebd2644d83e4fef1f33e Mon Sep 17 00:00:00 2001 From: Dmitriy Rabotyagov Date: Tue, 1 Dec 2020 12:44:30 +0200 Subject: [PATCH 2288/3095] Add option to filter instances by AZ Since nova API microversion 2.83 it is possible for users to filter instances by AZ. However even before that this functionality was available for admin role. Change-Id: Ife4c8e81aad2ff1dde50d9f23913d9dd9397b00c --- openstackclient/compute/v2/server.py | 8 ++++++++ openstackclient/tests/unit/compute/v2/test_server.py | 1 + 2 files changed, 9 insertions(+) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 1e39010af4..c6da0fbaca 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1365,6 +1365,13 @@ class ListServer(command.Lister): def get_parser(self, prog_name): parser = super(ListServer, self).get_parser(prog_name) + parser.add_argument( + '--availability-zone', + metavar='', + help=_('Only return instances that match the availability zone. ' + 'Note that this option will be ignored for non-admin users ' + 'when using ``--os-compute-api-version`` prior to 2.83.'), + ) parser.add_argument( '--reservation-id', metavar='', @@ -1574,6 +1581,7 @@ def take_action(self, parsed_args): ignore_missing=False).id search_opts = { + 'availability_zone': parsed_args.availability_zone, 'reservation_id': parsed_args.reservation_id, 'ip': parsed_args.ip, 'ip6': parsed_args.ip6, diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 5fd15e6ab6..dfb8df30d0 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -2962,6 +2962,7 @@ def setUp(self): super(TestServerList, self).setUp() self.search_opts = { + 'availability_zone': None, 'reservation_id': None, 'ip': None, 'ip6': None, From 3e8968af3dc82715dc93e7e76209e1bdcbdb2c14 Mon Sep 17 00:00:00 2001 From: yanpuqing Date: Wed, 15 Aug 2018 07:36:10 +0000 Subject: [PATCH 2289/3095] Add NODE and HOST parameters in "server create" help text Add optional parameters "NODE" and "HOST" in the help text of the server create comand for --availability-zone. Co-Authored-By: tianhui Change-Id: I4faea8a3d3aecb21ec535e55c238c71745fc68cb Task: 24274 Story: 2003313 --- openstackclient/compute/v2/server.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 1e39010af4..996c924b2e 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -711,7 +711,12 @@ def get_parser(self, prog_name): parser.add_argument( '--availability-zone', metavar='', - help=_('Select an availability zone for the server'), + help=_('Select an availability zone for the server. ' + 'Host and node are optional parameters. ' + 'Availability zone in the format ' + '::, ' + '::, : ' + 'or '), ) parser.add_argument( '--host', From 284c38bcf2624c8b82d77f2eb5a2015822f21aa0 Mon Sep 17 00:00:00 2001 From: Eric Fried Date: Thu, 31 Oct 2019 18:38:21 -0500 Subject: [PATCH 2290/3095] Let autoprogram-cliff know who's running The autoprogram-cliff directive has a habit of producing text like This command is provided by the $me plugin. which doesn't make any sense. Cliff recently added a config option whereby consumers can let it know who $me is so it can suppress that message where appropriate (while still producing it for $plugin, as intended). Depends-On: https://review.opendev.org/692464 Change-Id: I0d580fb1d34dd56740eb6d976caa795e0e951047 --- doc/source/conf.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/source/conf.py b/doc/source/conf.py index 2216ddd7c2..4b60ce4a5d 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -263,6 +263,9 @@ '--help', '--format', '--column', '--max-width', '--fit-width', '--print-empty', '--prefix', '--noindent', '--quote'] +# Prevent cliff from generating "This command is provided by the +# python-openstackclient plugin." +autoprogram_cliff_app_dist_name = 'python-openstackclient' # -- Options for sphinxcontrib.apidoc ---------------------------------------- From 0f4f42b65281b9b8b4f8fc3e58da8c9d8b68ee08 Mon Sep 17 00:00:00 2001 From: Artem Goncharov Date: Sat, 5 Sep 2020 22:20:07 +0200 Subject: [PATCH 2291/3095] Switch compute flavors from novaclient/direct to SDK Let's switch flavors from novaclient or direct API requests onto using SDK. Microversion agreement comes out of the box. SDK normalizes property names, while OSC uses server side names. In order not to break OSC users continue using server-side names. Depends-On: https://review.opendev.org/#/c/762989/ Change-Id: I62b2ed8488ee4ac9c42051311bcfb455506ddd90 --- lower-constraints.txt | 2 +- openstackclient/compute/v2/flavor.py | 262 ++++----- .../tests/unit/compute/v2/fakes.py | 20 +- .../tests/unit/compute/v2/test_flavor.py | 502 +++++++++++------- ...switch-flavor-to-sdk-b874a3c39559815e.yaml | 4 + requirements.txt | 2 +- 6 files changed, 453 insertions(+), 339 deletions(-) create mode 100644 releasenotes/notes/switch-flavor-to-sdk-b874a3c39559815e.yaml diff --git a/lower-constraints.txt b/lower-constraints.txt index b489501b87..e08b05d22c 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -45,7 +45,7 @@ msgpack-python==0.4.0 munch==2.1.0 netaddr==0.7.18 netifaces==0.10.4 -openstacksdk==0.51.0 +openstacksdk==0.52.0 os-service-types==1.7.0 os-testr==1.0.0 osc-lib==2.2.0 diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 00431b7b73..8477e8efb1 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -17,7 +17,8 @@ import logging -from novaclient import api_versions +from openstack import exceptions as sdk_exceptions +from openstack import utils as sdk_utils from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command @@ -33,10 +34,7 @@ _formatters = { 'extra_specs': format_columns.DictColumn, - # Unless we finish switch to use SDK resources this need to be doubled this - # way - 'properties': format_columns.DictColumn, - 'Properties': format_columns.DictColumn + 'properties': format_columns.DictColumn } @@ -51,29 +49,10 @@ def _get_flavor_columns(item): } hidden_columns = ['links', 'location'] - return utils.get_osc_show_columns_for_sdk_resource( item, column_map, hidden_columns) -def _find_flavor(compute_client, flavor): - try: - return compute_client.flavors.get(flavor) - except Exception as ex: - if type(ex).__name__ == 'NotFound': - pass - else: - raise - try: - return compute_client.flavors.find(name=flavor, is_public=None) - except Exception as ex: - if type(ex).__name__ == 'NotFound': - msg = _("No flavor with a name or ID of '%s' exists.") % flavor - raise exceptions.CommandError(msg) - else: - raise - - class CreateFlavor(command.ShowOne): _description = _("Create new flavor") @@ -87,9 +66,7 @@ def get_parser(self, prog_name): parser.add_argument( "--id", metavar="", - default='auto', - help=_("Unique flavor ID; 'auto' creates a UUID " - "(default: auto)") + help=_("Unique flavor ID") ) parser.add_argument( "--ram", @@ -170,32 +147,36 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute + compute_client = self.app.client_manager.sdk_connection.compute identity_client = self.app.client_manager.identity if parsed_args.project and parsed_args.public: msg = _("--project is only allowed with --private") raise exceptions.CommandError(msg) + args = { + 'name': parsed_args.name, + 'ram': parsed_args.ram, + 'vcpus': parsed_args.vcpus, + 'disk': parsed_args.disk, + 'id': parsed_args.id, + 'ephemeral': parsed_args.ephemeral, + 'swap': parsed_args.swap, + 'rxtx_factor': parsed_args.rxtx_factor, + 'is_public': parsed_args.public, + } + if parsed_args.description: - if compute_client.api_version < api_versions.APIVersion("2.55"): - msg = _("--os-compute-api-version 2.55 or later is required") + if not sdk_utils.supports_microversion(compute_client, '2.55'): + msg = _( + 'The --description parameter requires server support for ' + 'API microversion 2.55' + ) raise exceptions.CommandError(msg) - args = ( - parsed_args.name, - parsed_args.ram, - parsed_args.vcpus, - parsed_args.disk, - parsed_args.id, - parsed_args.ephemeral, - parsed_args.swap, - parsed_args.rxtx_factor, - parsed_args.public, - parsed_args.description - ) + args['description'] = parsed_args.description - flavor = compute_client.flavors.create(*args) + flavor = compute_client.create_flavor(**args) if parsed_args.project: try: @@ -204,7 +185,7 @@ def take_action(self, parsed_args): parsed_args.project, parsed_args.project_domain, ).id - compute_client.flavor_access.add_tenant_access( + compute_client.flavor_add_tenant_access( flavor.id, project_id) except Exception as e: msg = _("Failed to add project %(project)s access to " @@ -212,19 +193,14 @@ def take_action(self, parsed_args): LOG.error(msg, {'project': parsed_args.project, 'e': e}) if parsed_args.property: try: - flavor.set_keys(parsed_args.property) + flavor = compute_client.create_flavor_extra_specs( + flavor, parsed_args.property) except Exception as e: LOG.error(_("Failed to set flavor property: %s"), e) - flavor_info = flavor._info.copy() - flavor_info['properties'] = flavor.get_keys() - - display_columns, columns = _get_flavor_columns(flavor_info) - data = utils.get_dict_properties( - flavor_info, columns, - formatters=_formatters, - mixed_case_fields=['OS-FLV-DISABLED:disabled', - 'OS-FLV-EXT-DATA:ephemeral']) + display_columns, columns = _get_flavor_columns(flavor) + data = utils.get_dict_properties(flavor, columns, + formatters=_formatters) return (display_columns, data) @@ -243,12 +219,12 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute + compute_client = self.app.client_manager.sdk_connection.compute result = 0 for f in parsed_args.flavor: try: - flavor = _find_flavor(compute_client, f) - compute_client.flavors.delete(flavor.id) + flavor = compute_client.find_flavor(f, ignore_missing=False) + compute_client.delete_flavor(flavor.id) except Exception as e: result += 1 LOG.error(_("Failed to delete flavor with name or " @@ -307,37 +283,63 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute + compute_client = self.app.client_manager.sdk_connection.compute + # is_public is ternary - None means give all flavors, + # True is public only and False is private only + # By default Nova assumes True and gives admins public flavors + # and flavors from their own projects only. + is_public = None if parsed_args.all else parsed_args.public + + query_attrs = { + 'is_public': is_public + } + if parsed_args.marker: + query_attrs['marker'] = parsed_args.marker + if parsed_args.limit: + query_attrs['limit'] = parsed_args.limit + if parsed_args.limit or parsed_args.marker: + # User passed explicit pagination request, switch off SDK + # pagination + query_attrs['paginated'] = False + + data = list(compute_client.flavors(**query_attrs)) + # Even if server supports 2.61 some policy might stop it sending us + # extra_specs. So try to fetch them if they are absent + for f in data: + if not f.extra_specs: + compute_client.fetch_flavor_extra_specs(f) + columns = ( + "id", + "name", + "ram", + "disk", + "ephemeral", + "vcpus", + "is_public" + ) + if parsed_args.long: + columns += ( + "swap", + "rxtx_factor", + "extra_specs", + ) + + column_headers = ( "ID", "Name", "RAM", "Disk", "Ephemeral", "VCPUs", - "Is Public", + "Is Public" ) - - # is_public is ternary - None means give all flavors, - # True is public only and False is private only - # By default Nova assumes True and gives admins public flavors - # and flavors from their own projects only. - is_public = None if parsed_args.all else parsed_args.public - - data = compute_client.flavors.list(is_public=is_public, - marker=parsed_args.marker, - limit=parsed_args.limit) - if parsed_args.long: - columns = columns + ( + column_headers += ( "Swap", "RXTX Factor", "Properties", ) - for f in data: - f.properties = f.get_keys() - - column_headers = columns return (column_headers, (utils.get_item_properties( @@ -387,24 +389,42 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute + compute_client = self.app.client_manager.sdk_connection.compute identity_client = self.app.client_manager.identity - flavor = _find_flavor(compute_client, parsed_args.flavor) + try: + flavor = compute_client.find_flavor( + parsed_args.flavor, + get_extra_specs=True, + ignore_missing=False) + except sdk_exceptions.ResourceNotFound as e: + raise exceptions.CommandError(e.message) + + if parsed_args.description: + if not sdk_utils.supports_microversion(compute_client, '2.55'): + msg = _( + 'The --description parameter requires server support for ' + 'API microversion 2.55' + ) + raise exceptions.CommandError(msg) + + compute_client.update_flavor( + flavor=flavor.id, description=parsed_args.description) result = 0 - key_list = [] if parsed_args.no_property: try: - for key in flavor.get_keys().keys(): - key_list.append(key) - flavor.unset_keys(key_list) + for key in flavor.extra_specs.keys(): + compute_client.delete_flavor_extra_specs_property( + flavor.id, key) except Exception as e: LOG.error(_("Failed to clear flavor property: %s"), e) result += 1 + if parsed_args.property: try: - flavor.set_keys(parsed_args.property) + compute_client.create_flavor_extra_specs( + flavor.id, parsed_args.property) except Exception as e: LOG.error(_("Failed to set flavor property: %s"), e) result += 1 @@ -420,7 +440,7 @@ def take_action(self, parsed_args): parsed_args.project, parsed_args.project_domain, ).id - compute_client.flavor_access.add_tenant_access( + compute_client.flavor_add_tenant_access( flavor.id, project_id) except Exception as e: LOG.error(_("Failed to set flavor access to project: %s"), e) @@ -430,13 +450,6 @@ def take_action(self, parsed_args): raise exceptions.CommandError(_("Command Failed: One or more of" " the operations failed")) - if parsed_args.description: - if compute_client.api_version < api_versions.APIVersion("2.55"): - msg = _("--os-compute-api-version 2.55 or later is required") - raise exceptions.CommandError(msg) - compute_client.flavors.update(flavor=flavor.id, - description=parsed_args.description) - class ShowFlavor(command.ShowOne): _description = _("Display flavor details") @@ -451,35 +464,32 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - resource_flavor = _find_flavor(compute_client, parsed_args.flavor) + compute_client = self.app.client_manager.sdk_connection.compute + flavor = compute_client.find_flavor( + parsed_args.flavor, get_extra_specs=True, ignore_missing=False) access_projects = None # get access projects list of this flavor - if not resource_flavor.is_public: + if not flavor.is_public: try: - flavor_access = compute_client.flavor_access.list( - flavor=resource_flavor.id) - access_projects = [utils.get_field(access, 'tenant_id') - for access in flavor_access] + flavor_access = compute_client.get_flavor_access( + flavor=flavor.id) + access_projects = [ + utils.get_field(access, 'tenant_id') + for access in flavor_access] except Exception as e: msg = _("Failed to get access projects list " "for flavor '%(flavor)s': %(e)s") LOG.error(msg, {'flavor': parsed_args.flavor, 'e': e}) - flavor = resource_flavor._info.copy() - flavor.update({ - 'access_project_ids': access_projects - }) - - flavor['properties'] = resource_flavor.get_keys() + # Since we need to inject "access_project_id" into resource - convert + # it to dict and treat it respectively + flavor = flavor.to_dict() + flavor['access_project_ids'] = access_projects display_columns, columns = _get_flavor_columns(flavor) data = utils.get_dict_properties( - flavor, columns, - formatters=_formatters, - mixed_case_fields=['OS-FLV-DISABLED:disabled', - 'OS-FLV-EXT-DATA:ephemeral']) + flavor, columns, formatters=_formatters) return (display_columns, data) @@ -512,32 +522,40 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute + compute_client = self.app.client_manager.sdk_connection.compute identity_client = self.app.client_manager.identity - flavor = _find_flavor(compute_client, parsed_args.flavor) + try: + flavor = compute_client.find_flavor( + parsed_args.flavor, + get_extra_specs=True, + ignore_missing=False) + except sdk_exceptions.ResourceNotFound as e: + raise exceptions.CommandError(_(e.message)) result = 0 if parsed_args.property: - try: - flavor.unset_keys(parsed_args.property) - except Exception as e: - LOG.error(_("Failed to unset flavor property: %s"), e) - result += 1 + for key in parsed_args.property: + try: + compute_client.delete_flavor_extra_specs_property( + flavor.id, key) + except sdk_exceptions.SDKException as e: + LOG.error(_("Failed to unset flavor property: %s"), e) + result += 1 if parsed_args.project: try: if flavor.is_public: msg = _("Cannot remove access for a public flavor") raise exceptions.CommandError(msg) - else: - project_id = identity_common.find_project( - identity_client, - parsed_args.project, - parsed_args.project_domain, - ).id - compute_client.flavor_access.remove_tenant_access( - flavor.id, project_id) + + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + compute_client.flavor_remove_tenant_access( + flavor.id, project_id) except Exception as e: LOG.error(_("Failed to remove flavor access from project: %s"), e) diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index 3a06d27137..d3d037a9a8 100644 --- a/openstackclient/tests/unit/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -19,6 +19,7 @@ import uuid from novaclient import api_versions +from openstack.compute.v2 import flavor as _flavor from openstackclient.api import compute_v2 from openstackclient.tests.unit import fakes @@ -164,7 +165,6 @@ def __init__(self, **kwargs): self.extensions.resource_class = fakes.FakeResource(None, {}) self.flavors = mock.Mock() - self.flavors.resource_class = fakes.FakeResource(None, {}) self.flavor_access = mock.Mock() self.flavor_access.resource_class = fakes.FakeResource(None, {}) @@ -777,27 +777,13 @@ def create_one_flavor(attrs=None): 'os-flavor-access:is_public': True, 'description': 'description', 'OS-FLV-EXT-DATA:ephemeral': 0, - 'properties': {'property': 'value'}, + 'extra_specs': {'property': 'value'}, } # Overwrite default attributes. flavor_info.update(attrs) - # Set default methods. - flavor_methods = { - 'set_keys': None, - 'unset_keys': None, - 'get_keys': {'property': 'value'}, - } - - flavor = fakes.FakeResource(info=copy.deepcopy(flavor_info), - methods=flavor_methods, - loaded=True) - - # Set attributes with special mappings in nova client. - flavor.disabled = flavor_info['OS-FLV-DISABLED:disabled'] - flavor.is_public = flavor_info['os-flavor-access:is_public'] - flavor.ephemeral = flavor_info['OS-FLV-EXT-DATA:ephemeral'] + flavor = _flavor.Flavor(**flavor_info) return flavor diff --git a/openstackclient/tests/unit/compute/v2/test_flavor.py b/openstackclient/tests/unit/compute/v2/test_flavor.py index 2828d74e36..8625b71202 100644 --- a/openstackclient/tests/unit/compute/v2/test_flavor.py +++ b/openstackclient/tests/unit/compute/v2/test_flavor.py @@ -12,11 +12,11 @@ # License for the specific language governing permissions and limitations # under the License. # - from unittest import mock -from unittest.mock import call -import novaclient +from openstack.compute.v2 import flavor as _flavor +from openstack import exceptions as sdk_exceptions +from openstack import utils as sdk_utils from osc_lib.cli import format_columns from osc_lib import exceptions @@ -31,13 +31,19 @@ class TestFlavor(compute_fakes.TestComputev2): def setUp(self): super(TestFlavor, self).setUp() - # Get a shortcut to the FlavorManager Mock - self.flavors_mock = self.app.client_manager.compute.flavors - self.flavors_mock.reset_mock() - - # Get a shortcut to the FlavorAccessManager Mock - self.flavor_access_mock = self.app.client_manager.compute.flavor_access - self.flavor_access_mock.reset_mock() + # SDK mock + self.app.client_manager.sdk_connection = mock.Mock() + self.app.client_manager.sdk_connection.compute = mock.Mock() + self.sdk_client = self.app.client_manager.sdk_connection.compute + self.sdk_client.flavors = mock.Mock() + self.sdk_client.find_flavor = mock.Mock() + self.sdk_client.delete_flavor = mock.Mock() + self.sdk_client.update_flavor = mock.Mock() + self.sdk_client.flavor_add_tenant_access = mock.Mock() + self.sdk_client.flavor_remove_tenant_access = mock.Mock() + self.sdk_client.create_flavor_extra_specs = mock.Mock() + self.sdk_client.update_flavor_extra_specs_property = mock.Mock() + self.sdk_client.delete_flavor_extra_specs_property = mock.Mock() self.projects_mock = self.app.client_manager.identity.projects self.projects_mock.reset_mock() @@ -48,6 +54,7 @@ class TestFlavorCreate(TestFlavor): flavor = compute_fakes.FakeFlavor.create_one_flavor( attrs={'links': 'flavor-links'}) project = identity_fakes.FakeProject.create_one_project() + columns = ( 'OS-FLV-DISABLED:disabled', 'OS-FLV-EXT-DATA:ephemeral', @@ -60,17 +67,32 @@ class TestFlavorCreate(TestFlavor): 'ram', 'rxtx_factor', 'swap', - 'vcpus', + 'vcpus' ) + data = ( - flavor.disabled, + flavor.is_disabled, flavor.ephemeral, flavor.description, flavor.disk, flavor.id, flavor.name, flavor.is_public, - format_columns.DictColumn(flavor.properties), + format_columns.DictColumn(flavor.extra_specs), + flavor.ram, + flavor.rxtx_factor, + flavor.swap, + flavor.vcpus, + ) + data_private = ( + flavor.is_disabled, + flavor.ephemeral, + flavor.description, + flavor.disk, + flavor.id, + flavor.name, + False, + format_columns.DictColumn(flavor.extra_specs), flavor.ram, flavor.rxtx_factor, flavor.swap, @@ -82,7 +104,7 @@ def setUp(self): # Return a project self.projects_mock.get.return_value = self.project - self.flavors_mock.create.return_value = self.flavor + self.sdk_client.create_flavor.return_value = self.flavor self.cmd = flavor.CreateFlavor(self.app, None) def test_flavor_create_default_options(self): @@ -95,20 +117,20 @@ def test_flavor_create_default_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - default_args = ( - self.flavor.name, - 256, - 1, - 0, - 'auto', - 0, - 0, - 1.0, - True, - None, - ) + default_args = { + 'name': self.flavor.name, + 'ram': 256, + 'vcpus': 1, + 'disk': 0, + 'id': None, + 'ephemeral': 0, + 'swap': 0, + 'rxtx_factor': 1.0, + 'is_public': True, + } + columns, data = self.cmd.take_action(parsed_args) - self.flavors_mock.create.assert_called_once_with(*default_args) + self.sdk_client.create_flavor.assert_called_once_with(**default_args) self.assertEqual(self.columns, columns) self.assertItemEqual(self.data, data) @@ -143,29 +165,44 @@ def test_flavor_create_all_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - args = ( - self.flavor.name, - self.flavor.ram, - self.flavor.vcpus, - self.flavor.disk, - self.flavor.id, - self.flavor.ephemeral, - self.flavor.swap, - self.flavor.rxtx_factor, - self.flavor.is_public, - self.flavor.description, - ) - self.app.client_manager.compute.api_version = 2.55 - with mock.patch.object(novaclient.api_versions, - 'APIVersion', - return_value=2.55): + args = { + 'name': self.flavor.name, + 'ram': self.flavor.ram, + 'vcpus': self.flavor.vcpus, + 'disk': self.flavor.disk, + 'id': self.flavor.id, + 'ephemeral': self.flavor.ephemeral, + 'swap': self.flavor.swap, + 'rxtx_factor': self.flavor.rxtx_factor, + 'is_public': self.flavor.is_public, + 'description': self.flavor.description + } + + props = {'property': 'value'} + + # SDK updates the flavor object instance. In order to make the + # verification clear and preciese let's create new flavor and change + # expected props this way + create_flavor = _flavor.Flavor(**self.flavor) + expected_flavor = _flavor.Flavor(**self.flavor) + expected_flavor.extra_specs = props + # convert expected data tuple to list to be able to modify it + cmp_data = list(self.data) + cmp_data[7] = format_columns.DictColumn(props) + self.sdk_client.create_flavor.return_value = create_flavor + self.sdk_client.create_flavor_extra_specs.return_value = \ + expected_flavor + + with mock.patch.object(sdk_utils, 'supports_microversion', + return_value=True): columns, data = self.cmd.take_action(parsed_args) - self.flavors_mock.create.assert_called_once_with(*args) - self.flavor.set_keys.assert_called_once_with({'property': 'value'}) - self.flavor.get_keys.assert_called_once_with() + self.sdk_client.create_flavor.assert_called_once_with(**args) + self.sdk_client.create_flavor_extra_specs.assert_called_once_with( + create_flavor, props) + self.sdk_client.get_flavor_access.assert_not_called() - self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertEqual(self.columns, columns) + self.assertItemEqual(tuple(cmp_data), data) def test_flavor_create_other_options(self): @@ -200,33 +237,47 @@ def test_flavor_create_other_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - args = ( - self.flavor.name, - self.flavor.ram, - self.flavor.vcpus, - self.flavor.disk, - 'auto', - self.flavor.ephemeral, - self.flavor.swap, - self.flavor.rxtx_factor, - self.flavor.is_public, - self.flavor.description, - ) - self.app.client_manager.compute.api_version = 2.55 - with mock.patch.object(novaclient.api_versions, - 'APIVersion', - return_value=2.55): + args = { + 'name': self.flavor.name, + 'ram': self.flavor.ram, + 'vcpus': self.flavor.vcpus, + 'disk': self.flavor.disk, + 'id': 'auto', + 'ephemeral': self.flavor.ephemeral, + 'swap': self.flavor.swap, + 'rxtx_factor': self.flavor.rxtx_factor, + 'is_public': False, + 'description': self.flavor.description + } + + props = {'key1': 'value1', 'key2': 'value2'} + + # SDK updates the flavor object instance. In order to make the + # verification clear and preciese let's create new flavor and change + # expected props this way + create_flavor = _flavor.Flavor(**self.flavor) + expected_flavor = _flavor.Flavor(**self.flavor) + expected_flavor.extra_specs = props + expected_flavor.is_public = False + # convert expected data tuple to list to be able to modify it + cmp_data = list(self.data_private) + cmp_data[7] = format_columns.DictColumn(props) + self.sdk_client.create_flavor.return_value = create_flavor + self.sdk_client.create_flavor_extra_specs.return_value = \ + expected_flavor + + with mock.patch.object(sdk_utils, 'supports_microversion', + return_value=True): columns, data = self.cmd.take_action(parsed_args) - self.flavors_mock.create.assert_called_once_with(*args) - self.flavor_access_mock.add_tenant_access.assert_called_with( + self.sdk_client.create_flavor.assert_called_once_with(**args) + self.sdk_client.flavor_add_tenant_access.assert_called_with( self.flavor.id, self.project.id, ) - self.flavor.set_keys.assert_called_with( - {'key1': 'value1', 'key2': 'value2'}) - self.flavor.get_keys.assert_called_with() + self.sdk_client.create_flavor_extra_specs.assert_called_with( + create_flavor, props) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemEqual(cmp_data, data) def test_public_flavor_create_with_project(self): arglist = [ @@ -278,29 +329,28 @@ def test_flavor_create_with_description_api_newer(self): ('name', self.flavor.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.app.client_manager.compute.api_version = 2.55 - with mock.patch.object(novaclient.api_versions, - 'APIVersion', - return_value=2.55): + with mock.patch.object(sdk_utils, 'supports_microversion', + return_value=True): + columns, data = self.cmd.take_action(parsed_args) - args = ( - self.flavor.name, - self.flavor.ram, - self.flavor.vcpus, - self.flavor.disk, - self.flavor.id, - self.flavor.ephemeral, - self.flavor.swap, - self.flavor.rxtx_factor, - False, - 'fake description', - ) + args = { + 'name': self.flavor.name, + 'ram': self.flavor.ram, + 'vcpus': self.flavor.vcpus, + 'disk': self.flavor.disk, + 'id': self.flavor.id, + 'ephemeral': self.flavor.ephemeral, + 'swap': self.flavor.swap, + 'rxtx_factor': self.flavor.rxtx_factor, + 'is_public': self.flavor.is_public, + 'description': 'fake description' + } - self.flavors_mock.create.assert_called_once_with(*args) + self.sdk_client.create_flavor.assert_called_once_with(**args) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemEqual(self.data_private, data) def test_flavor_create_with_description_api_older(self): arglist = [ @@ -318,10 +368,8 @@ def test_flavor_create_with_description_api_older(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.app.client_manager.compute.api_version = 2.54 - with mock.patch.object(novaclient.api_versions, - 'APIVersion', - return_value=2.55): + with mock.patch.object(sdk_utils, 'supports_microversion', + return_value=False): self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) @@ -333,9 +381,7 @@ class TestFlavorDelete(TestFlavor): def setUp(self): super(TestFlavorDelete, self).setUp() - self.flavors_mock.get = ( - compute_fakes.FakeFlavor.get_flavors(self.flavors)) - self.flavors_mock.delete.return_value = None + self.sdk_client.delete_flavor.return_value = None self.cmd = flavor.DeleteFlavor(self.app, None) @@ -348,9 +394,13 @@ def test_flavor_delete(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.sdk_client.find_flavor.return_value = self.flavors[0] + result = self.cmd.take_action(parsed_args) - self.flavors_mock.delete.assert_called_with(self.flavors[0].id) + self.sdk_client.find_flavor.assert_called_with(self.flavors[0].id, + ignore_missing=False) + self.sdk_client.delete_flavor.assert_called_with(self.flavors[0].id) self.assertIsNone(result) def test_delete_multiple_flavors(self): @@ -362,12 +412,17 @@ def test_delete_multiple_flavors(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.sdk_client.find_flavor.side_effect = self.flavors + result = self.cmd.take_action(parsed_args) - calls = [] - for f in self.flavors: - calls.append(call(f.id)) - self.flavors_mock.delete.assert_has_calls(calls) + find_calls = [ + mock.call(i.id, ignore_missing=False) for i in self.flavors + ] + delete_calls = [mock.call(i.id) for i in self.flavors] + self.sdk_client.find_flavor.assert_has_calls(find_calls) + self.sdk_client.delete_flavor.assert_has_calls(delete_calls) self.assertIsNone(result) def test_multi_flavors_delete_with_exception(self): @@ -380,11 +435,10 @@ def test_multi_flavors_delete_with_exception(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - find_mock_result = [self.flavors[0], exceptions.CommandError] - self.flavors_mock.get = ( - mock.Mock(side_effect=find_mock_result) - ) - self.flavors_mock.find.side_effect = exceptions.NotFound(None) + self.sdk_client.find_flavor.side_effect = [ + self.flavors[0], + sdk_exceptions.ResourceNotFound + ] try: self.cmd.take_action(parsed_args) @@ -392,15 +446,18 @@ def test_multi_flavors_delete_with_exception(self): except exceptions.CommandError as e: self.assertEqual('1 of 2 flavors failed to delete.', str(e)) - self.flavors_mock.get.assert_any_call(self.flavors[0].id) - self.flavors_mock.get.assert_any_call('unexist_flavor') - self.flavors_mock.delete.assert_called_once_with(self.flavors[0].id) + find_calls = [ + mock.call(self.flavors[0].id, ignore_missing=False), + mock.call('unexist_flavor', ignore_missing=False), + ] + delete_calls = [mock.call(self.flavors[0].id)] + self.sdk_client.find_flavor.assert_has_calls(find_calls) + self.sdk_client.delete_flavor.assert_has_calls(delete_calls) class TestFlavorList(TestFlavor): - # Return value of self.flavors_mock.list(). - flavors = compute_fakes.FakeFlavor.create_flavors(count=1) + _flavor = compute_fakes.FakeFlavor.create_one_flavor() columns = ( 'ID', @@ -418,24 +475,27 @@ class TestFlavorList(TestFlavor): ) data = (( - flavors[0].id, - flavors[0].name, - flavors[0].ram, - flavors[0].disk, - flavors[0].ephemeral, - flavors[0].vcpus, - flavors[0].is_public, - ), ) + _flavor.id, + _flavor.name, + _flavor.ram, + _flavor.disk, + _flavor.ephemeral, + _flavor.vcpus, + _flavor.is_public, + ),) data_long = (data[0] + ( - flavors[0].swap, - flavors[0].rxtx_factor, - format_columns.DictColumn(flavors[0].properties) + _flavor.swap, + _flavor.rxtx_factor, + format_columns.DictColumn(_flavor.extra_specs) ), ) def setUp(self): super(TestFlavorList, self).setUp() - self.flavors_mock.list.return_value = self.flavors + self.api_mock = mock.Mock() + self.api_mock.side_effect = [[self._flavor], [], ] + + self.sdk_client.flavors = self.api_mock # Get the command object to test self.cmd = flavor.ListFlavor(self.app, None) @@ -458,16 +518,14 @@ def test_flavor_list_no_options(self): # Set expected values kwargs = { 'is_public': True, - 'limit': None, - 'marker': None } - self.flavors_mock.list.assert_called_with( + self.sdk_client.flavors.assert_called_with( **kwargs ) self.assertEqual(self.columns, columns) - self.assertEqual(tuple(self.data), tuple(data)) + self.assertEqual(self.data, tuple(data)) def test_flavor_list_all_flavors(self): arglist = [ @@ -487,16 +545,14 @@ def test_flavor_list_all_flavors(self): # Set expected values kwargs = { 'is_public': None, - 'limit': None, - 'marker': None } - self.flavors_mock.list.assert_called_with( + self.sdk_client.flavors.assert_called_with( **kwargs ) self.assertEqual(self.columns, columns) - self.assertEqual(tuple(self.data), tuple(data)) + self.assertEqual(self.data, tuple(data)) def test_flavor_list_private_flavors(self): arglist = [ @@ -516,16 +572,14 @@ def test_flavor_list_private_flavors(self): # Set expected values kwargs = { 'is_public': False, - 'limit': None, - 'marker': None } - self.flavors_mock.list.assert_called_with( + self.sdk_client.flavors.assert_called_with( **kwargs ) self.assertEqual(self.columns, columns) - self.assertEqual(tuple(self.data), tuple(data)) + self.assertEqual(self.data, tuple(data)) def test_flavor_list_public_flavors(self): arglist = [ @@ -545,16 +599,14 @@ def test_flavor_list_public_flavors(self): # Set expected values kwargs = { 'is_public': True, - 'limit': None, - 'marker': None } - self.flavors_mock.list.assert_called_with( + self.sdk_client.flavors.assert_called_with( **kwargs ) self.assertEqual(self.columns, columns) - self.assertEqual(tuple(self.data), tuple(data)) + self.assertEqual(self.data, tuple(data)) def test_flavor_list_long(self): arglist = [ @@ -574,11 +626,9 @@ def test_flavor_list_long(self): # Set expected values kwargs = { 'is_public': True, - 'limit': None, - 'marker': None } - self.flavors_mock.list.assert_called_with( + self.sdk_client.flavors.assert_called_with( **kwargs ) @@ -588,7 +638,7 @@ def test_flavor_list_long(self): class TestFlavorSet(TestFlavor): - # Return value of self.flavors_mock.find(). + # Return value of self.sdk_client.find_flavor(). flavor = compute_fakes.FakeFlavor.create_one_flavor( attrs={'os-flavor-access:is_public': False}) project = identity_fakes.FakeProject.create_one_project() @@ -596,8 +646,7 @@ class TestFlavorSet(TestFlavor): def setUp(self): super(TestFlavorSet, self).setUp() - self.flavors_mock.find.return_value = self.flavor - self.flavors_mock.get.side_effect = exceptions.NotFound(None) + self.sdk_client.find_flavor.return_value = self.flavor # Return a project self.projects_mock.get.return_value = self.project self.cmd = flavor.SetFlavor(self.app, None) @@ -614,9 +663,14 @@ def test_flavor_set_property(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.flavors_mock.find.assert_called_with(name=parsed_args.flavor, - is_public=None) - self.flavor.set_keys.assert_called_with({'FOO': '"B A R"'}) + self.sdk_client.find_flavor.assert_called_with( + parsed_args.flavor, + get_extra_specs=True, + ignore_missing=False + ) + self.sdk_client.create_flavor_extra_specs.assert_called_with( + self.flavor.id, + {'FOO': '"B A R"'}) self.assertIsNone(result) def test_flavor_set_no_property(self): @@ -631,9 +685,13 @@ def test_flavor_set_no_property(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.flavors_mock.find.assert_called_with(name=parsed_args.flavor, - is_public=None) - self.flavor.unset_keys.assert_called_with(['property']) + self.sdk_client.find_flavor.assert_called_with( + parsed_args.flavor, + get_extra_specs=True, + ignore_missing=False + ) + self.sdk_client.delete_flavor_extra_specs_property.assert_called_with( + self.flavor.id, 'property') self.assertIsNone(result) def test_flavor_set_project(self): @@ -649,13 +707,16 @@ def test_flavor_set_project(self): result = self.cmd.take_action(parsed_args) - self.flavors_mock.find.assert_called_with(name=parsed_args.flavor, - is_public=None) - self.flavor_access_mock.add_tenant_access.assert_called_with( + self.sdk_client.find_flavor.assert_called_with( + parsed_args.flavor, + get_extra_specs=True, + ignore_missing=False + ) + self.sdk_client.flavor_add_tenant_access.assert_called_with( self.flavor.id, self.project.id, ) - self.flavor.set_keys.assert_not_called() + self.sdk_client.create_flavor_extra_specs.assert_not_called() self.assertIsNone(result) def test_flavor_set_no_project(self): @@ -681,8 +742,9 @@ def test_flavor_set_no_flavor(self): self.cmd, arglist, verifylist) def test_flavor_set_with_unexist_flavor(self): - self.flavors_mock.get.side_effect = exceptions.NotFound(None) - self.flavors_mock.find.side_effect = exceptions.NotFound(None) + self.sdk_client.find_flavor.side_effect = [ + sdk_exceptions.ResourceNotFound() + ] arglist = [ '--project', self.project.id, @@ -708,9 +770,12 @@ def test_flavor_set_nothing(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.flavors_mock.find.assert_called_with(name=parsed_args.flavor, - is_public=None) - self.flavor_access_mock.add_tenant_access.assert_not_called() + self.sdk_client.find_flavor.assert_called_with( + parsed_args.flavor, + get_extra_specs=True, + ignore_missing=False + ) + self.sdk_client.flavor_add_tenant_access.assert_not_called() self.assertIsNone(result) def test_flavor_set_description_api_newer(self): @@ -724,11 +789,11 @@ def test_flavor_set_description_api_newer(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.app.client_manager.compute.api_version = 2.55 - with mock.patch.object(novaclient.api_versions, - 'APIVersion', - return_value=2.55): + with mock.patch.object(sdk_utils, + 'supports_microversion', + return_value=True): result = self.cmd.take_action(parsed_args) - self.flavors_mock.update.assert_called_with( + self.sdk_client.update_flavor.assert_called_with( flavor=self.flavor.id, description='description') self.assertIsNone(result) @@ -743,9 +808,9 @@ def test_flavor_set_description_api_older(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.app.client_manager.compute.api_version = 2.54 - with mock.patch.object(novaclient.api_versions, - 'APIVersion', - return_value=2.55): + with mock.patch.object(sdk_utils, + 'supports_microversion', + return_value=False): self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) @@ -760,11 +825,12 @@ def test_flavor_set_description_using_name_api_newer(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.app.client_manager.compute.api_version = 2.55 - with mock.patch.object(novaclient.api_versions, - 'APIVersion', - return_value=2.55): + + with mock.patch.object(sdk_utils, + 'supports_microversion', + return_value=True): result = self.cmd.take_action(parsed_args) - self.flavors_mock.update.assert_called_with( + self.sdk_client.update_flavor.assert_called_with( flavor=self.flavor.id, description='description') self.assertIsNone(result) @@ -779,16 +845,17 @@ def test_flavor_set_description_using_name_api_older(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.app.client_manager.compute.api_version = 2.54 - with mock.patch.object(novaclient.api_versions, - 'APIVersion', - return_value=2.55): + + with mock.patch.object(sdk_utils, + 'supports_microversion', + return_value=False): self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) class TestFlavorShow(TestFlavor): - # Return value of self.flavors_mock.find(). + # Return value of self.sdk_client.find_flavor(). flavor_access = compute_fakes.FakeFlavorAccess.create_one_flavor_access() flavor = compute_fakes.FakeFlavor.create_one_flavor() @@ -805,11 +872,11 @@ class TestFlavorShow(TestFlavor): 'ram', 'rxtx_factor', 'swap', - 'vcpus', + 'vcpus' ) data = ( - flavor.disabled, + flavor.is_disabled, flavor.ephemeral, None, flavor.description, @@ -817,7 +884,7 @@ class TestFlavorShow(TestFlavor): flavor.id, flavor.name, flavor.is_public, - format_columns.DictColumn(flavor.get_keys()), + format_columns.DictColumn(flavor.extra_specs), flavor.ram, flavor.rxtx_factor, flavor.swap, @@ -828,9 +895,8 @@ def setUp(self): super(TestFlavorShow, self).setUp() # Return value of _find_resource() - self.flavors_mock.find.return_value = self.flavor - self.flavors_mock.get.side_effect = exceptions.NotFound(None) - self.flavor_access_mock.list.return_value = [self.flavor_access] + self.sdk_client.find_flavor.return_value = self.flavor + self.sdk_client.get_flavor_access.return_value = [self.flavor_access] self.cmd = flavor.ShowFlavor(self.app, None) def test_show_no_options(self): @@ -862,7 +928,7 @@ def test_private_flavor_show(self): 'os-flavor-access:is_public': False, } ) - self.flavors_mock.find.return_value = private_flavor + self.sdk_client.find_flavor.return_value = private_flavor arglist = [ private_flavor.name, @@ -872,7 +938,7 @@ def test_private_flavor_show(self): ] data_with_project = ( - private_flavor.disabled, + private_flavor.is_disabled, private_flavor.ephemeral, [self.flavor_access.tenant_id], private_flavor.description, @@ -880,7 +946,7 @@ def test_private_flavor_show(self): private_flavor.id, private_flavor.name, private_flavor.is_public, - format_columns.DictColumn(private_flavor.get_keys()), + format_columns.DictColumn(private_flavor.extra_specs), private_flavor.ram, private_flavor.rxtx_factor, private_flavor.swap, @@ -891,7 +957,7 @@ def test_private_flavor_show(self): columns, data = self.cmd.take_action(parsed_args) - self.flavor_access_mock.list.assert_called_with( + self.sdk_client.get_flavor_access.assert_called_with( flavor=private_flavor.id) self.assertEqual(self.columns, columns) self.assertItemEqual(data_with_project, data) @@ -899,7 +965,7 @@ def test_private_flavor_show(self): class TestFlavorUnset(TestFlavor): - # Return value of self.flavors_mock.find(). + # Return value of self.sdk_client.find_flavor(). flavor = compute_fakes.FakeFlavor.create_one_flavor( attrs={'os-flavor-access:is_public': False}) project = identity_fakes.FakeProject.create_one_project() @@ -907,12 +973,13 @@ class TestFlavorUnset(TestFlavor): def setUp(self): super(TestFlavorUnset, self).setUp() - self.flavors_mock.find.return_value = self.flavor - self.flavors_mock.get.side_effect = exceptions.NotFound(None) + self.sdk_client.find_flavor.return_value = self.flavor # Return a project self.projects_mock.get.return_value = self.project self.cmd = flavor.UnsetFlavor(self.app, None) + self.mock_shortcut = self.sdk_client.delete_flavor_extra_specs_property + def test_flavor_unset_property(self): arglist = [ '--property', 'property', @@ -925,12 +992,49 @@ def test_flavor_unset_property(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.flavors_mock.find.assert_called_with(name=parsed_args.flavor, - is_public=None) - self.flavor.unset_keys.assert_called_with(['property']) - self.flavor_access_mock.remove_tenant_access.assert_not_called() + self.sdk_client.find_flavor.assert_called_with( + parsed_args.flavor, + get_extra_specs=True, + ignore_missing=False) + self.mock_shortcut.assert_called_with( + self.flavor.id, 'property') + self.sdk_client.flavor_remove_tenant_access.assert_not_called() self.assertIsNone(result) + def test_flavor_unset_properties(self): + arglist = [ + '--property', 'property1', + '--property', 'property2', + 'baremetal' + ] + verifylist = [ + ('property', ['property1', 'property2']), + ('flavor', 'baremetal'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + self.sdk_client.find_flavor.assert_called_with( + parsed_args.flavor, + get_extra_specs=True, + ignore_missing=False) + calls = [ + mock.call(self.flavor.id, 'property1'), + mock.call(self.flavor.id, 'property2') + ] + self.mock_shortcut.assert_has_calls( + calls) + + # A bit tricky way to ensure we do not unset other properties + calls.append(mock.call(self.flavor.id, 'property')) + self.assertRaises( + AssertionError, + self.mock_shortcut.assert_has_calls, + calls + ) + + self.sdk_client.flavor_remove_tenant_access.assert_not_called() + def test_flavor_unset_project(self): arglist = [ '--project', self.project.id, @@ -945,13 +1049,14 @@ def test_flavor_unset_project(self): result = self.cmd.take_action(parsed_args) self.assertIsNone(result) - self.flavors_mock.find.assert_called_with(name=parsed_args.flavor, - is_public=None) - self.flavor_access_mock.remove_tenant_access.assert_called_with( + self.sdk_client.find_flavor.assert_called_with( + parsed_args.flavor, get_extra_specs=True, + ignore_missing=False) + self.sdk_client.flavor_remove_tenant_access.assert_called_with( self.flavor.id, self.project.id, ) - self.flavor.unset_keys.assert_not_called() + self.sdk_client.delete_flavor_extra_specs_proerty.assert_not_called() self.assertIsNone(result) def test_flavor_unset_no_project(self): @@ -977,8 +1082,9 @@ def test_flavor_unset_no_flavor(self): self.cmd, arglist, verifylist) def test_flavor_unset_with_unexist_flavor(self): - self.flavors_mock.get.side_effect = exceptions.NotFound(None) - self.flavors_mock.find.side_effect = exceptions.NotFound(None) + self.sdk_client.find_flavor.side_effect = [ + sdk_exceptions.ResourceNotFound + ] arglist = [ '--project', self.project.id, @@ -1004,4 +1110,4 @@ def test_flavor_unset_nothing(self): result = self.cmd.take_action(parsed_args) self.assertIsNone(result) - self.flavor_access_mock.remove_tenant_access.assert_not_called() + self.sdk_client.flavor_remove_tenant_access.assert_not_called() diff --git a/releasenotes/notes/switch-flavor-to-sdk-b874a3c39559815e.yaml b/releasenotes/notes/switch-flavor-to-sdk-b874a3c39559815e.yaml new file mode 100644 index 0000000000..7863c32385 --- /dev/null +++ b/releasenotes/notes/switch-flavor-to-sdk-b874a3c39559815e.yaml @@ -0,0 +1,4 @@ +--- +features: + - Switch compute.flavor operations from direct API calls (novaclient) to + OpenStackSDK. diff --git a/requirements.txt b/requirements.txt index 9430a7fde0..b167628b51 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ pbr!=2.1.0,>=2.0.0 # Apache-2.0 cliff>=3.4.0 # Apache-2.0 iso8601>=0.1.11 # MIT -openstacksdk>=0.51.0 # Apache-2.0 +openstacksdk>=0.52.0 # Apache-2.0 osc-lib>=2.2.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 python-keystoneclient>=3.22.0 # Apache-2.0 From f36a34b675e69a811d5cd48f6bcfd6ce7bda6a5a Mon Sep 17 00:00:00 2001 From: Artem Goncharov Date: Tue, 10 Nov 2020 13:56:11 +0100 Subject: [PATCH 2292/3095] Switch compute aggregate functions to SDK Continue journey towards having OSC consuming SDK for nova part. Depends-On: https://review.opendev.org/#/c/762131/ Change-Id: Id16e6c47aa93f02f15f49e1f59f73fecaa3e3b80 --- openstackclient/compute/v2/aggregate.py | 263 +++++++++--------- .../tests/unit/compute/v2/test_aggregate.py | 257 +++++++++++------ ...tch-aggregate-to-sdk-ced451a0f28bf6ea.yaml | 4 + setup.cfg | 1 + 4 files changed, 313 insertions(+), 212 deletions(-) create mode 100644 releasenotes/notes/switch-aggregate-to-sdk-ced451a0f28bf6ea.yaml diff --git a/openstackclient/compute/v2/aggregate.py b/openstackclient/compute/v2/aggregate.py index 599659a3e2..8b70f42640 100644 --- a/openstackclient/compute/v2/aggregate.py +++ b/openstackclient/compute/v2/aggregate.py @@ -18,6 +18,7 @@ import logging +from openstack import utils as sdk_utils from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command @@ -30,6 +31,25 @@ LOG = logging.getLogger(__name__) +_aggregate_formatters = { + 'Hosts': format_columns.ListColumn, + 'Metadata': format_columns.DictColumn, + 'hosts': format_columns.ListColumn, + 'metadata': format_columns.DictColumn, +} + + +def _get_aggregate_columns(item): + # To maintain backwards compatibility we need to rename sdk props to + # whatever OSC was using before + column_map = { + 'metadata': 'properties', + } + hidden_columns = ['links', 'location'] + return utils.get_osc_show_columns_for_sdk_resource( + item, column_map, hidden_columns) + + class AddAggregateHost(command.ShowOne): _description = _("Add host to aggregate") @@ -48,26 +68,18 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute + compute_client = self.app.client_manager.sdk_connection.compute - aggregate = utils.find_resource( - compute_client.aggregates, - parsed_args.aggregate, - ) - data = compute_client.aggregates.add_host(aggregate, parsed_args.host) - - info = {} - info.update(data._info) - - # Special mapping for columns to make the output easier to read: - # 'metadata' --> 'properties' - info.update( - { - 'hosts': format_columns.ListColumn(info.pop('hosts')), - 'properties': format_columns.DictColumn(info.pop('metadata')), - }, - ) - return zip(*sorted(info.items())) + aggregate = compute_client.find_aggregate( + parsed_args.aggregate, ignore_missing=False) + + aggregate = compute_client.add_host_to_aggregate( + aggregate.id, parsed_args.host) + + display_columns, columns = _get_aggregate_columns(aggregate) + data = utils.get_item_properties( + aggregate, columns, formatters=_aggregate_formatters) + return (display_columns, data) class CreateAggregate(command.ShowOne): @@ -95,36 +107,25 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute + compute_client = self.app.client_manager.sdk_connection.compute - info = {} - data = compute_client.aggregates.create( - parsed_args.name, - parsed_args.zone, - ) - info.update(data._info) + attrs = {'name': parsed_args.name} + + if parsed_args.zone: + attrs['availability_zone'] = parsed_args.zone + + aggregate = compute_client.create_aggregate(**attrs) if parsed_args.property: - info.update(compute_client.aggregates.set_metadata( - data, + aggregate = compute_client.set_aggregate_metadata( + aggregate.id, parsed_args.property, - )._info) - - # Special mapping for columns to make the output easier to read: - # 'metadata' --> 'properties' - hosts = None - properties = None - if 'hosts' in info.keys(): - hosts = format_columns.ListColumn(info.pop('hosts')) - if 'metadata' in info.keys(): - properties = format_columns.DictColumn(info.pop('metadata')) - info.update( - { - 'hosts': hosts, - 'properties': properties, - }, - ) - return zip(*sorted(info.items())) + ) + + display_columns, columns = _get_aggregate_columns(aggregate) + data = utils.get_item_properties( + aggregate, columns, formatters=_aggregate_formatters) + return (display_columns, data) class DeleteAggregate(command.Command): @@ -141,13 +142,14 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute + compute_client = self.app.client_manager.sdk_connection.compute result = 0 for a in parsed_args.aggregate: try: - data = utils.find_resource( - compute_client.aggregates, a) - compute_client.aggregates.delete(data.id) + aggregate = compute_client.find_aggregate( + a, ignore_missing=False) + compute_client.delete_aggregate( + aggregate.id, ignore_missing=False) except Exception as e: result += 1 LOG.error(_("Failed to delete aggregate with name or " @@ -175,15 +177,15 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute + compute_client = self.app.client_manager.sdk_connection.compute - data = compute_client.aggregates.list() + aggregates = list(compute_client.aggregates()) if parsed_args.long: # Remove availability_zone from metadata because Nova doesn't - for d in data: - if 'availability_zone' in d.metadata: - d.metadata.pop('availability_zone') + for aggregate in aggregates: + if 'availability_zone' in aggregate.metadata: + aggregate.metadata.pop('availability_zone') # This is the easiest way to change column headers column_headers = ( "ID", @@ -204,14 +206,11 @@ def take_action(self, parsed_args): "Availability Zone", ) - return (column_headers, - (utils.get_item_properties( - s, columns, - formatters={ - 'Hosts': format_columns.ListColumn, - 'Metadata': format_columns.DictColumn, - }, - ) for s in data)) + data = ( + utils.get_item_properties( + s, columns, formatters=_aggregate_formatters + ) for s in aggregates) + return (column_headers, data) class RemoveAggregateHost(command.ShowOne): @@ -232,29 +231,18 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute + compute_client = self.app.client_manager.sdk_connection.compute - aggregate = utils.find_resource( - compute_client.aggregates, - parsed_args.aggregate, - ) - data = compute_client.aggregates.remove_host( - aggregate, - parsed_args.host, - ) + aggregate = compute_client.find_aggregate( + parsed_args.aggregate, ignore_missing=False) - info = {} - info.update(data._info) + aggregate = compute_client.remove_host_from_aggregate( + aggregate.id, parsed_args.host) - # Special mapping for columns to make the output easier to read: - # 'metadata' --> 'properties' - info.update( - { - 'hosts': format_columns.ListColumn(info.pop('hosts')), - 'properties': format_columns.DictColumn(info.pop('metadata')), - }, - ) - return zip(*sorted(info.items())) + display_columns, columns = _get_aggregate_columns(aggregate) + data = utils.get_item_properties( + aggregate, columns, formatters=_aggregate_formatters) + return (display_columns, data) class SetAggregate(command.Command): @@ -296,11 +284,9 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - aggregate = utils.find_resource( - compute_client.aggregates, - parsed_args.aggregate, - ) + compute_client = self.app.client_manager.sdk_connection.compute + aggregate = compute_client.find_aggregate( + parsed_args.aggregate, ignore_missing=False) kwargs = {} if parsed_args.name: @@ -308,18 +294,12 @@ def take_action(self, parsed_args): if parsed_args.zone: kwargs['availability_zone'] = parsed_args.zone if kwargs: - compute_client.aggregates.update( - aggregate, - kwargs - ) + compute_client.update_aggregate(aggregate.id, **kwargs) set_property = {} if parsed_args.no_property: - # NOTE(RuiChen): "availability_zone" is removed from response of - # aggregate show and create commands, don't see it - # anywhere, so pop it, avoid the unexpected server - # exception(can't unset the availability zone from - # aggregate metadata in nova). + # NOTE(RuiChen): "availability_zone" can not be unset from + # properties. It is already excluded from show and create output. set_property.update({key: None for key in aggregate.metadata.keys() if key != 'availability_zone'}) @@ -327,8 +307,8 @@ def take_action(self, parsed_args): set_property.update(parsed_args.property) if set_property: - compute_client.aggregates.set_metadata( - aggregate, + compute_client.set_aggregate_metadata( + aggregate.id, set_property ) @@ -347,31 +327,18 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - data = utils.find_resource( - compute_client.aggregates, - parsed_args.aggregate, - ) + compute_client = self.app.client_manager.sdk_connection.compute + aggregate = compute_client.find_aggregate( + parsed_args.aggregate, ignore_missing=False) + # Remove availability_zone from metadata because Nova doesn't - if 'availability_zone' in data.metadata: - data.metadata.pop('availability_zone') - - # Special mapping for columns to make the output easier to read: - # 'metadata' --> 'properties' - data._info.update( - { - 'hosts': format_columns.ListColumn( - data._info.pop('hosts') - ), - 'properties': format_columns.DictColumn( - data._info.pop('metadata') - ), - }, - ) + if 'availability_zone' in aggregate.metadata: + aggregate.metadata.pop('availability_zone') - info = {} - info.update(data._info) - return zip(*sorted(info.items())) + display_columns, columns = _get_aggregate_columns(aggregate) + data = utils.get_item_properties( + aggregate, columns, formatters=_aggregate_formatters) + return (display_columns, data) class UnsetAggregate(command.Command): @@ -394,14 +361,56 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - aggregate = utils.find_resource( - compute_client.aggregates, - parsed_args.aggregate) + compute_client = self.app.client_manager.sdk_connection.compute + aggregate = compute_client.find_aggregate( + parsed_args.aggregate, ignore_missing=False) unset_property = {} if parsed_args.property: unset_property.update({key: None for key in parsed_args.property}) if unset_property: - compute_client.aggregates.set_metadata(aggregate, - unset_property) + compute_client.set_aggregate_metadata( + aggregate, unset_property) + + +class CacheImageForAggregate(command.Command): + _description = _("Request image caching for aggregate") + # NOTE(gtema): According to stephenfin and dansmith there is no and will + # not be anything to return. + + def get_parser(self, prog_name): + parser = super(CacheImageForAggregate, self).get_parser(prog_name) + parser.add_argument( + 'aggregate', + metavar='', + help=_("Aggregate (name or ID)") + ) + parser.add_argument( + 'image', + metavar='', + nargs='+', + help=_("Image ID to request caching for aggregate (name or ID). " + "May be specified multiple times.") + ) + return parser + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.sdk_connection.compute + + if not sdk_utils.supports_microversion(compute_client, '2.81'): + msg = _( + 'This operation requires server support for ' + 'API microversion 2.81' + ) + raise exceptions.CommandError(msg) + + aggregate = compute_client.find_aggregate( + parsed_args.aggregate, ignore_missing=False) + + images = [] + for img in parsed_args.image: + image = self.app.client_manager.sdk_connection.image.find_image( + img, ignore_missing=False) + images.append(image.id) + + compute_client.aggregate_precache_images(aggregate.id, images) diff --git a/openstackclient/tests/unit/compute/v2/test_aggregate.py b/openstackclient/tests/unit/compute/v2/test_aggregate.py index cd0c1525ee..e4c13ea55c 100644 --- a/openstackclient/tests/unit/compute/v2/test_aggregate.py +++ b/openstackclient/tests/unit/compute/v2/test_aggregate.py @@ -16,12 +16,14 @@ from unittest import mock from unittest.mock import call +from openstack import exceptions as sdk_exceptions +from openstack import utils as sdk_utils from osc_lib.cli import format_columns from osc_lib import exceptions -from osc_lib import utils from openstackclient.compute.v2 import aggregate from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes +from openstackclient.tests.unit.image.v2 import fakes as image_fakes class TestAggregate(compute_fakes.TestComputev2): @@ -48,8 +50,17 @@ def setUp(self): super(TestAggregate, self).setUp() # Get a shortcut to the AggregateManager Mock - self.aggregate_mock = self.app.client_manager.compute.aggregates - self.aggregate_mock.reset_mock() + self.app.client_manager.sdk_connection = mock.Mock() + self.app.client_manager.sdk_connection.compute = mock.Mock() + self.sdk_client = self.app.client_manager.sdk_connection.compute + self.sdk_client.aggregates = mock.Mock() + self.sdk_client.find_aggregate = mock.Mock() + self.sdk_client.create_aggregate = mock.Mock() + self.sdk_client.update_aggregate = mock.Mock() + self.sdk_client.update_aggregate = mock.Mock() + self.sdk_client.set_aggregate_metadata = mock.Mock() + self.sdk_client.add_host_to_aggregate = mock.Mock() + self.sdk_client.remove_host_from_aggregate = mock.Mock() class TestAggregateAddHost(TestAggregate): @@ -57,8 +68,8 @@ class TestAggregateAddHost(TestAggregate): def setUp(self): super(TestAggregateAddHost, self).setUp() - self.aggregate_mock.get.return_value = self.fake_ag - self.aggregate_mock.add_host.return_value = self.fake_ag + self.sdk_client.find_aggregate.return_value = self.fake_ag + self.sdk_client.add_host_to_aggregate.return_value = self.fake_ag self.cmd = aggregate.AddAggregateHost(self.app, None) def test_aggregate_add_host(self): @@ -72,9 +83,10 @@ def test_aggregate_add_host(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate) - self.aggregate_mock.add_host.assert_called_once_with(self.fake_ag, - parsed_args.host) + self.sdk_client.find_aggregate.assert_called_once_with( + parsed_args.aggregate, ignore_missing=False) + self.sdk_client.add_host_to_aggregate.assert_called_once_with( + self.fake_ag.id, parsed_args.host) self.assertEqual(self.columns, columns) self.assertItemEqual(self.data, data) @@ -84,8 +96,8 @@ class TestAggregateCreate(TestAggregate): def setUp(self): super(TestAggregateCreate, self).setUp() - self.aggregate_mock.create.return_value = self.fake_ag - self.aggregate_mock.set_metadata.return_value = self.fake_ag + self.sdk_client.create_aggregate.return_value = self.fake_ag + self.sdk_client.set_aggregate_metadata.return_value = self.fake_ag self.cmd = aggregate.CreateAggregate(self.app, None) def test_aggregate_create(self): @@ -97,8 +109,8 @@ def test_aggregate_create(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.aggregate_mock.create.assert_called_once_with(parsed_args.name, - None) + self.sdk_client.create_aggregate.assert_called_once_with( + name=parsed_args.name) self.assertEqual(self.columns, columns) self.assertItemEqual(self.data, data) @@ -114,8 +126,8 @@ def test_aggregate_create_with_zone(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.aggregate_mock.create.assert_called_once_with(parsed_args.name, - parsed_args.zone) + self.sdk_client.create_aggregate.assert_called_once_with( + name=parsed_args.name, availability_zone=parsed_args.zone) self.assertEqual(self.columns, columns) self.assertItemEqual(self.data, data) @@ -131,10 +143,10 @@ def test_aggregate_create_with_property(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.aggregate_mock.create.assert_called_once_with(parsed_args.name, - None) - self.aggregate_mock.set_metadata.assert_called_once_with( - self.fake_ag, parsed_args.property) + self.sdk_client.create_aggregate.assert_called_once_with( + name=parsed_args.name) + self.sdk_client.set_aggregate_metadata.assert_called_once_with( + self.fake_ag.id, parsed_args.property) self.assertEqual(self.columns, columns) self.assertItemEqual(self.data, data) @@ -146,7 +158,7 @@ class TestAggregateDelete(TestAggregate): def setUp(self): super(TestAggregateDelete, self).setUp() - self.aggregate_mock.get = ( + self.sdk_client.find_aggregate = ( compute_fakes.FakeAggregate.get_aggregates(self.fake_ags)) self.cmd = aggregate.DeleteAggregate(self.app, None) @@ -158,10 +170,11 @@ def test_aggregate_delete(self): ('aggregate', [self.fake_ags[0].id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) - self.aggregate_mock.get.assert_called_once_with(self.fake_ags[0].id) - self.aggregate_mock.delete.assert_called_once_with(self.fake_ags[0].id) - self.assertIsNone(result) + self.cmd.take_action(parsed_args) + self.sdk_client.find_aggregate.assert_called_once_with( + self.fake_ags[0].id, ignore_missing=False) + self.sdk_client.delete_aggregate.assert_called_once_with( + self.fake_ags[0].id, ignore_missing=False) def test_delete_multiple_aggregates(self): arglist = [] @@ -172,13 +185,13 @@ def test_delete_multiple_aggregates(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) + self.cmd.take_action(parsed_args) calls = [] for a in self.fake_ags: - calls.append(call(a.id)) - self.aggregate_mock.delete.assert_has_calls(calls) - self.assertIsNone(result) + calls.append(call(a.id, ignore_missing=False)) + self.sdk_client.find_aggregate.assert_has_calls(calls) + self.sdk_client.delete_aggregate.assert_has_calls(calls) def test_delete_multiple_agggregates_with_exception(self): arglist = [ @@ -191,23 +204,21 @@ def test_delete_multiple_agggregates_with_exception(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) - find_mock_result = [self.fake_ags[0], exceptions.CommandError] - with mock.patch.object(utils, 'find_resource', - side_effect=find_mock_result) as find_mock: - try: - self.cmd.take_action(parsed_args) - self.fail('CommandError should be raised.') - except exceptions.CommandError as e: - self.assertEqual('1 of 2 aggregates failed to delete.', - str(e)) - - find_mock.assert_any_call(self.aggregate_mock, self.fake_ags[0].id) - find_mock.assert_any_call(self.aggregate_mock, 'unexist_aggregate') + self.sdk_client.find_aggregate.side_effect = [ + self.fake_ags[0], sdk_exceptions.NotFoundException] + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 aggregates failed to delete.', + str(e)) - self.assertEqual(2, find_mock.call_count) - self.aggregate_mock.delete.assert_called_once_with( - self.fake_ags[0].id - ) + calls = [] + for a in arglist: + calls.append(call(a, ignore_missing=False)) + self.sdk_client.find_aggregate.assert_has_calls(calls) + self.sdk_client.delete_aggregate.assert_called_with( + self.fake_ags[0].id, ignore_missing=False) class TestAggregateList(TestAggregate): @@ -245,7 +256,7 @@ class TestAggregateList(TestAggregate): def setUp(self): super(TestAggregateList, self).setUp() - self.aggregate_mock.list.return_value = [self.fake_ag] + self.sdk_client.aggregates.return_value = [self.fake_ag] self.cmd = aggregate.ListAggregate(self.app, None) def test_aggregate_list(self): @@ -275,11 +286,11 @@ class TestAggregateRemoveHost(TestAggregate): def setUp(self): super(TestAggregateRemoveHost, self).setUp() - self.aggregate_mock.get.return_value = self.fake_ag - self.aggregate_mock.remove_host.return_value = self.fake_ag + self.sdk_client.find_aggregate.return_value = self.fake_ag + self.sdk_client.remove_host_from_aggregate.return_value = self.fake_ag self.cmd = aggregate.RemoveAggregateHost(self.app, None) - def test_aggregate_add_host(self): + def test_aggregate_remove_host(self): arglist = [ 'ag1', 'host1', @@ -290,9 +301,10 @@ def test_aggregate_add_host(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate) - self.aggregate_mock.remove_host.assert_called_once_with( - self.fake_ag, parsed_args.host) + self.sdk_client.find_aggregate.assert_called_once_with( + parsed_args.aggregate, ignore_missing=False) + self.sdk_client.remove_host_from_aggregate.assert_called_once_with( + self.fake_ag.id, parsed_args.host) self.assertEqual(self.columns, columns) self.assertItemEqual(self.data, data) @@ -302,7 +314,7 @@ class TestAggregateSet(TestAggregate): def setUp(self): super(TestAggregateSet, self).setUp() - self.aggregate_mock.get.return_value = self.fake_ag + self.sdk_client.find_aggregate.return_value = self.fake_ag self.cmd = aggregate.SetAggregate(self.app, None) def test_aggregate_set_no_option(self): @@ -315,9 +327,10 @@ def test_aggregate_set_no_option(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate) - self.assertNotCalled(self.aggregate_mock.update) - self.assertNotCalled(self.aggregate_mock.set_metadata) + self.sdk_client.find_aggregate.assert_called_once_with( + parsed_args.aggregate, ignore_missing=False) + self.assertNotCalled(self.sdk_client.update_aggregate) + self.assertNotCalled(self.sdk_client.set_aggregate_metadata) self.assertIsNone(result) def test_aggregate_set_with_name(self): @@ -332,10 +345,11 @@ def test_aggregate_set_with_name(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate) - self.aggregate_mock.update.assert_called_once_with( - self.fake_ag, {'name': parsed_args.name}) - self.assertNotCalled(self.aggregate_mock.set_metadata) + self.sdk_client.find_aggregate.assert_called_once_with( + parsed_args.aggregate, ignore_missing=False) + self.sdk_client.update_aggregate.assert_called_once_with( + self.fake_ag.id, name=parsed_args.name) + self.assertNotCalled(self.sdk_client.set_aggregate_metadata) self.assertIsNone(result) def test_aggregate_set_with_zone(self): @@ -350,10 +364,11 @@ def test_aggregate_set_with_zone(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate) - self.aggregate_mock.update.assert_called_once_with( - self.fake_ag, {'availability_zone': parsed_args.zone}) - self.assertNotCalled(self.aggregate_mock.set_metadata) + self.sdk_client.find_aggregate.assert_called_once_with( + parsed_args.aggregate, ignore_missing=False) + self.sdk_client.update_aggregate.assert_called_once_with( + self.fake_ag.id, availability_zone=parsed_args.zone) + self.assertNotCalled(self.sdk_client.set_aggregate_metadata) self.assertIsNone(result) def test_aggregate_set_with_property(self): @@ -369,10 +384,11 @@ def test_aggregate_set_with_property(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate) - self.assertNotCalled(self.aggregate_mock.update) - self.aggregate_mock.set_metadata.assert_called_once_with( - self.fake_ag, parsed_args.property) + self.sdk_client.find_aggregate.assert_called_once_with( + parsed_args.aggregate, ignore_missing=False) + self.assertNotCalled(self.sdk_client.update_aggregate) + self.sdk_client.set_aggregate_metadata.assert_called_once_with( + self.fake_ag.id, parsed_args.property) self.assertIsNone(result) def test_aggregate_set_with_no_property_and_property(self): @@ -388,10 +404,11 @@ def test_aggregate_set_with_no_property_and_property(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate) - self.assertNotCalled(self.aggregate_mock.update) - self.aggregate_mock.set_metadata.assert_called_once_with( - self.fake_ag, {'key1': None, 'key2': 'value2'}) + self.sdk_client.find_aggregate.assert_called_once_with( + parsed_args.aggregate, ignore_missing=False) + self.assertNotCalled(self.sdk_client.update_aggregate) + self.sdk_client.set_aggregate_metadata.assert_called_once_with( + self.fake_ag.id, {'key1': None, 'key2': 'value2'}) self.assertIsNone(result) def test_aggregate_set_with_no_property(self): @@ -405,10 +422,11 @@ def test_aggregate_set_with_no_property(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate) - self.assertNotCalled(self.aggregate_mock.update) - self.aggregate_mock.set_metadata.assert_called_once_with( - self.fake_ag, {'key1': None}) + self.sdk_client.find_aggregate.assert_called_once_with( + parsed_args.aggregate, ignore_missing=False) + self.assertNotCalled(self.sdk_client.update_aggregate) + self.sdk_client.set_aggregate_metadata.assert_called_once_with( + self.fake_ag.id, {'key1': None}) self.assertIsNone(result) def test_aggregate_set_with_zone_and_no_property(self): @@ -424,11 +442,12 @@ def test_aggregate_set_with_zone_and_no_property(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate) - self.aggregate_mock.update.assert_called_once_with( - self.fake_ag, {'availability_zone': parsed_args.zone}) - self.aggregate_mock.set_metadata.assert_called_once_with( - self.fake_ag, {'key1': None}) + self.sdk_client.find_aggregate.assert_called_once_with( + parsed_args.aggregate, ignore_missing=False) + self.sdk_client.update_aggregate.assert_called_once_with( + self.fake_ag.id, availability_zone=parsed_args.zone) + self.sdk_client.set_aggregate_metadata.assert_called_once_with( + self.fake_ag.id, {'key1': None}) self.assertIsNone(result) @@ -457,7 +476,7 @@ class TestAggregateShow(TestAggregate): def setUp(self): super(TestAggregateShow, self).setUp() - self.aggregate_mock.get.return_value = self.fake_ag + self.sdk_client.find_aggregate.return_value = self.fake_ag self.cmd = aggregate.ShowAggregate(self.app, None) def test_aggregate_show(self): @@ -469,7 +488,8 @@ def test_aggregate_show(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate) + self.sdk_client.find_aggregate.assert_called_once_with( + parsed_args.aggregate, ignore_missing=False) self.assertEqual(self.columns, columns) self.assertItemEqual(self.data, tuple(data)) @@ -480,7 +500,7 @@ class TestAggregateUnset(TestAggregate): def setUp(self): super(TestAggregateUnset, self).setUp() - self.aggregate_mock.get.return_value = self.fake_ag + self.sdk_client.find_aggregate.return_value = self.fake_ag self.cmd = aggregate.UnsetAggregate(self.app, None) def test_aggregate_unset(self): @@ -495,7 +515,7 @@ def test_aggregate_unset(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.aggregate_mock.set_metadata.assert_called_once_with( + self.sdk_client.set_aggregate_metadata.assert_called_once_with( self.fake_ag, {'unset_key': None}) self.assertIsNone(result) @@ -512,7 +532,7 @@ def test_aggregate_unset_multiple_properties(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.aggregate_mock.set_metadata.assert_called_once_with( + self.sdk_client.set_aggregate_metadata.assert_called_once_with( self.fake_ag, {'unset_key1': None, 'unset_key2': None}) self.assertIsNone(result) @@ -526,5 +546,72 @@ def test_aggregate_unset_no_option(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.assertNotCalled(self.aggregate_mock.set_metadata) + self.assertNotCalled(self.sdk_client.set_aggregate_metadata) self.assertIsNone(result) + + +class TestAggregateCacheImage(TestAggregate): + + images = image_fakes.FakeImage.create_images(count=2) + + def setUp(self): + super(TestAggregateCacheImage, self).setUp() + + self.sdk_client.find_aggregate.return_value = self.fake_ag + self.find_image_mock = mock.Mock(side_effect=self.images) + self.app.client_manager.sdk_connection.image.find_image = \ + self.find_image_mock + + self.cmd = aggregate.CacheImageForAggregate(self.app, None) + + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=False) + def test_aggregate_not_supported(self, sm_mock): + arglist = [ + 'ag1', + 'im1' + ] + verifylist = [ + ('aggregate', 'ag1'), + ('image', ['im1']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args + ) + + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=True) + def test_aggregate_add_single_image(self, sm_mock): + arglist = [ + 'ag1', + 'im1' + ] + verifylist = [ + ('aggregate', 'ag1'), + ('image', ['im1']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.sdk_client.find_aggregate.assert_called_once_with( + parsed_args.aggregate, ignore_missing=False) + self.sdk_client.aggregate_precache_images.assert_called_once_with( + self.fake_ag.id, [self.images[0].id]) + + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=True) + def test_aggregate_add_multiple_images(self, sm_mock): + arglist = [ + 'ag1', + 'im1', + 'im2', + ] + verifylist = [ + ('aggregate', 'ag1'), + ('image', ['im1', 'im2']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.sdk_client.find_aggregate.assert_called_once_with( + parsed_args.aggregate, ignore_missing=False) + self.sdk_client.aggregate_precache_images.assert_called_once_with( + self.fake_ag.id, [self.images[0].id, self.images[1].id]) diff --git a/releasenotes/notes/switch-aggregate-to-sdk-ced451a0f28bf6ea.yaml b/releasenotes/notes/switch-aggregate-to-sdk-ced451a0f28bf6ea.yaml new file mode 100644 index 0000000000..0df2d63557 --- /dev/null +++ b/releasenotes/notes/switch-aggregate-to-sdk-ced451a0f28bf6ea.yaml @@ -0,0 +1,4 @@ +--- +features: + - Switch aggregate operations to use SDK + - Adds 'aggregate cache image' operation diff --git a/setup.cfg b/setup.cfg index a29852e353..9299fb918a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -65,6 +65,7 @@ openstack.compute.v2 = aggregate_set = openstackclient.compute.v2.aggregate:SetAggregate aggregate_show = openstackclient.compute.v2.aggregate:ShowAggregate aggregate_unset = openstackclient.compute.v2.aggregate:UnsetAggregate + aggregate_cache_image = openstackclient.compute.v2.aggregate:CacheImageForAggregate compute_service_delete = openstackclient.compute.v2.service:DeleteService compute_service_list = openstackclient.compute.v2.service:ListService From d688cb58a3a21ce5fbb5edf4e4feaae9998cb21c Mon Sep 17 00:00:00 2001 From: pedro Date: Mon, 4 Nov 2019 19:14:23 -0300 Subject: [PATCH 2293/3095] Add documentation about login with federation The documentation presents the parameters necessary to authenticate via federation (using password) and do a brief description of each parameter used in the process. Change-Id: Iae3b6d0b56ebd2bbbb94f9f3637b5086e75559a7 --- README.rst | 76 +++++++++++++++----- doc/source/cli/authentication.rst | 14 ++++ doc/source/cli/man/openstack.rst | 112 ++++++++++++++++++++++++++++++ 3 files changed, 183 insertions(+), 19 deletions(-) diff --git a/README.rst b/README.rst index 41d0112428..7dfabd8456 100644 --- a/README.rst +++ b/README.rst @@ -76,25 +76,63 @@ Configuration The CLI is configured via environment variables and command-line options as listed in https://docs.openstack.org/python-openstackclient/latest/cli/authentication.html. -Authentication using username/password is most commonly used:: - - export OS_AUTH_URL= - export OS_IDENTITY_API_VERSION=3 - export OS_PROJECT_NAME= - export OS_PROJECT_DOMAIN_NAME= - export OS_USERNAME= - export OS_USER_DOMAIN_NAME= - export OS_PASSWORD= # (optional) - -The corresponding command-line options look very similar:: - - --os-auth-url - --os-identity-api-version 3 - --os-project-name - --os-project-domain-name - --os-username - --os-user-domain-name - [--os-password ] +Authentication using username/password is most commonly used: + +- For a local user, your configuration will look like the one below:: + + export OS_AUTH_URL= + export OS_IDENTITY_API_VERSION=3 + export OS_PROJECT_NAME= + export OS_PROJECT_DOMAIN_NAME= + export OS_USERNAME= + export OS_USER_DOMAIN_NAME= + export OS_PASSWORD= # (optional) + + The corresponding command-line options look very similar:: + + --os-auth-url + --os-identity-api-version 3 + --os-project-name + --os-project-domain-name + --os-username + --os-user-domain-name + [--os-password ] + +- For a federated user, your configuration will look the so:: + + export OS_PROJECT_NAME= + export OS_PROJECT_DOMAIN_NAME= + export OS_AUTH_URL= + export OS_IDENTITY_API_VERSION=3 + export OS_AUTH_PLUGIN=openid + export OS_AUTH_TYPE=v3oidcpassword + export OS_USERNAME= + export OS_PASSWORD= + export OS_IDENTITY_PROVIDER= + export OS_CLIENT_ID= + export OS_CLIENT_SECRET= + export OS_OPENID_SCOPE= + export OS_PROTOCOL= + export OS_ACCESS_TOKEN_TYPE= + export OS_DISCOVERY_ENDPOINT= + + The corresponding command-line options look very similar:: + + --os-project-name + --os-project-domain-name + --os-auth-url + --os-identity-api-version 3 + --os-auth-plugin openid + --os-auth-type v3oidcpassword + --os-username + --os-password + --os-identity-provider + --os-client-id + --os-client-secret + --os-openid-scope + --os-protocol + --os-access-token-type + --os-discovery-endpoint If a password is not provided above (in plaintext), you will be interactively prompted to provide one securely. diff --git a/doc/source/cli/authentication.rst b/doc/source/cli/authentication.rst index 3b404bce5b..2e9148c356 100644 --- a/doc/source/cli/authentication.rst +++ b/doc/source/cli/authentication.rst @@ -133,3 +133,17 @@ Thus, a minimal set of environment variables would be: $ export OS_USERNAME=admin $ export OS_PASSWORD=secret $ export OS_PROJECT_NAME=admin + +Federated users support +----------------------- + +The OpenStackClient also allows the use of Federated users to log in. +It enables one to use the identity providers credentials such as Google or +Facebook to log in the OpenStackClient instead of using the Keystone +credentials. + +This is useful in a Federated environment where one credential give access +to many applications/services that the Federation supports. To check how to +configure the OpenStackClient to allow Federated users to log in, please check +the +:ref:`Authentication using federation. ` diff --git a/doc/source/cli/man/openstack.rst b/doc/source/cli/man/openstack.rst index 687e39eb9f..dc327a66e8 100644 --- a/doc/source/cli/man/openstack.rst +++ b/doc/source/cli/man/openstack.rst @@ -44,6 +44,7 @@ command line. The primary difference is the use of 'project' in the name of the * ``token``: Authentication with a token * ``password``: Authentication with a username and a password +* ``openid`` : Authentication using the protocol OpenID Connect Refer to the keystoneclient library documentation for more details about these plugins and their options, and for a complete list of available plugins. Please bear in mind that some plugins might not support all of the functionalities of :program:`openstack`; for example the v3unscopedsaml plugin can deliver only unscoped tokens, some commands might not be available through this authentication method. @@ -53,6 +54,31 @@ Additionally, it is possible to use Keystone's service token to authenticate, by .. NOTE:: To use the ``v3unscopedsaml`` method, the lxml package will need to be installed. +AUTHENTICATION USING FEDERATION +------------------------------- + +To use federated authentication, your configuration file needs the following: + +:: + + export OS_PROJECT_NAME= + export OS_PROJECT_DOMAIN_NAME= + export OS_AUTH_URL= + export OS_IDENTITY_API_VERSION=3 + export OS_AUTH_PLUGIN=openid + export OS_AUTH_TYPE=v3oidcpassword + export OS_USERNAME= + export OS_PASSWORD= + export OS_IDENTITY_PROVIDER= + export OS_CLIENT_ID= + export OS_CLIENT_SECRET= + export OS_OPENID_SCOPE= + export OS_PROTOCOL= + export OS_ACCESS_TOKEN_TYPE= + export OS_DISCOVERY_ENDPOINT= + export OS_ACCESS_TOKEN_ENDPOINT= + + OPTIONS ======= @@ -356,6 +382,24 @@ Show the detailed information for server ``appweb01``:: --os-auth-url http://localhost:5000:/v2.0 \ server show appweb01 +The same but using openid to authenticate in keystone:: + + openstack \ + --os-project-name ExampleCo \ + --os-auth-url http://localhost:5000:/v2.0 \ + --os-auth-plugin openid \ + --os-auth-type v3oidcpassword \ + --os-username demo-idp \ + --os-password secret-idp \ + --os-identity-provider google \ + --os-client-id the-id-assigned-to-keystone-in-google \ + --os-client-secret 3315162f-2b28-4809-9369-cb54730ac837 \ + --os-openid-scope 'openid email profile'\ + --os-protocol openid \ + --os-access-token-type access_token \ + --os-discovery-endpoint https://accounts.google.com/.well-known/openid-configuration \ + server show appweb01 + The same command if the auth environment variables (:envvar:`OS_AUTH_URL`, :envvar:`OS_PROJECT_NAME`, :envvar:`OS_USERNAME`, :envvar:`OS_PASSWORD`) are set:: @@ -404,6 +448,24 @@ The following environment variables can be set to alter the behaviour of :progra Authentication URL +.. envvar:: OS_AUTH_TYPE + + Define the authentication plugin that will be used to handle the + authentication process. One of the following: + + - ``v2password`` + - ``v2token`` + - ``v3password`` + - ``v3token`` + - ``v3oidcclientcredentials`` + - ``v3oidcpassword`` + - ``v3oidcauthorizationcode`` + - ``v3oidcaccesstoken`` + - ``v3totp`` + - ``v3tokenlessauth`` + - ``v3applicationcredential`` + - ``v3multifactor`` + .. envvar:: OS_URL Service URL (when using the service token) @@ -473,6 +535,56 @@ The following environment variables can be set to alter the behaviour of :progra Interface type. Valid options are `public`, `admin` and `internal`. +.. envvar:: OS_PROTOCOL + + Define the protocol that is used to execute the federated authentication + process. It is used in the Keystone authentication URL generation process. + +.. envvar:: OS_IDENTITY_PROVIDER + + Define the identity provider of your federation that will be used. It is + used by the Keystone authentication URL generation process. The available + Identity Providers can be listed using the + :program:`openstack identity provider list` command + +.. envvar:: OS_CLIENT_ID + + Configure the ``CLIENT_ID`` that the CLI will use to authenticate the + application (OpenStack) in the Identity Provider. This value is defined on + the identity provider side. Do not confuse with the user ID. + +.. envvar:: OS_CLIENT_SECRET + + Configure the OS_CLIENT_SECRET that the CLI will use to authenticate the + CLI (OpenStack secret in the identity provider). + +.. envvar:: OS_OPENID_SCOPE + + Configure the attribute scopes that will be claimed by the Service Provider + (SP), in this case OpenStack, from the identity provider. These scopes and + which attributes each scope contains are defined in the identity provider + side. This parameter can receive multiple values separated by space. + +.. envvar:: OS_ACCESS_TOKEN_TYPE + + Define the type of access token that is used in the token introspection + process. + This variable can assume only one of the states ("access_token" or + "id_token"). + +.. envvar:: OS_DISCOVERY_ENDPOINT + + Configure the identity provider's discovery URL. This URL will provide a + discover document that contains metadata describing the identity provider + endpoints. This variable is optional if the variable + ``OS_ACCESS_TOKEN_ENDPOINT`` is defined. + +.. envvar:: OS_ACCESS_TOKEN_ENDPOINT + + Overrides the value presented in the discovery document retrieved from + ``OS_DISCOVERY_ENDPOINT`` URL request. This variable is optional if the + ``OS_DISCOVERY_ENDPOINT`` is configured. + .. NOTE:: If you switch to openstackclient from project specified clients, like: novaclient, neutronclient and so on, please use `OS_INTERFACE` instead of From ceaba4c5729b3defe51466b6c5c4bb28696edecd Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 18 Nov 2020 13:57:44 +0000 Subject: [PATCH 2294/3095] trivial: Cleanup docs for 'server rebuild' Use consistent help strings and error messages. Change-Id: I42647a6b7e67ce4b8dd5f826e20802ade691c266 Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server.py | 81 ++++++++++++++++++---------- 1 file changed, 52 insertions(+), 29 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 996c924b2e..4c177f7c3b 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -2440,47 +2440,57 @@ def get_parser(self, prog_name): parser.add_argument( '--image', metavar='', - help=_('Recreate server from the specified image (name or ID).' - ' Defaults to the currently used one.'), + help=_( + 'Recreate server from the specified image (name or ID).' + 'Defaults to the currently used one.' + ), ) parser.add_argument( '--password', metavar='', - help=_("Set the password on the rebuilt instance"), + help=_('Set a password on the rebuilt server'), ) parser.add_argument( '--property', metavar='', action=parseractions.KeyValueAction, - help=_('Set a property on the rebuilt instance ' - '(repeat option to set multiple values)'), + help=_( + 'Set a new property on the rebuilt server ' + '(repeat option to set multiple values)' + ), ) parser.add_argument( '--description', metavar='', - help=_('New description for the server (supported by ' - '--os-compute-api-version 2.19 or above'), - ) - parser.add_argument( - '--wait', - action='store_true', - help=_('Wait for rebuild to complete'), + help=_( + 'Set a new description on the rebuilt server ' + '(supported by --os-compute-api-version 2.19 or above)' + ), ) key_group = parser.add_mutually_exclusive_group() key_group.add_argument( '--key-name', metavar='', - help=_("Set the key name of key pair on the rebuilt instance." - " Cannot be specified with the '--key-unset' option." - " (Supported by API versions '2.54' - '2.latest')"), + help=_( + 'Set the key name of key pair on the rebuilt server. ' + 'Cannot be specified with the --key-unset option. ' + '(supported by --os-compute-api-version 2.54 or above)' + ), ) key_group.add_argument( '--key-unset', action='store_true', default=False, - help=_("Unset the key name of key pair on the rebuilt instance." - " Cannot be specified with the '--key-name' option." - " (Supported by API versions '2.54' - '2.latest')"), + help=_( + 'Unset the key name of key pair on the rebuilt server. ' + 'Cannot be specified with the --key-name option. ' + '(supported by --os-compute-api-version 2.54 or above)' + ), + ) + parser.add_argument( + '--wait', + action='store_true', + help=_('Wait for rebuild to complete'), ) return parser @@ -2506,24 +2516,38 @@ def _show_progress(progress): image = image_client.get_image(image_id) kwargs = {} + if parsed_args.property: kwargs['meta'] = parsed_args.property + if parsed_args.description: if server.api_version < api_versions.APIVersion("2.19"): - msg = _("Description is not supported for " - "--os-compute-api-version less than 2.19") + msg = _( + '--os-compute-api-version 2.19 or greater is required to ' + 'support the --description option' + ) raise exceptions.CommandError(msg) + kwargs['description'] = parsed_args.description - if parsed_args.key_name or parsed_args.key_unset: + if parsed_args.key_name: + if compute_client.api_version < api_versions.APIVersion('2.54'): + msg = _( + '--os-compute-api-version 2.54 or greater is required to ' + 'support the --key-name option' + ) + raise exceptions.CommandError(msg) + + kwargs['key_name'] = parsed_args.key_name + elif parsed_args.key_unset: if compute_client.api_version < api_versions.APIVersion('2.54'): - msg = _('--os-compute-api-version 2.54 or later is required') + msg = _( + '--os-compute-api-version 2.54 or greater is required to ' + 'support the --no-key-name option' + ) raise exceptions.CommandError(msg) - if parsed_args.key_unset: kwargs['key_name'] = None - if parsed_args.key_name: - kwargs['key_name'] = parsed_args.key_name server = server.rebuild(image, parsed_args.password, **kwargs) if parsed_args.wait: @@ -2534,13 +2558,12 @@ def _show_progress(progress): ): self.app.stdout.write(_('Complete\n')) else: - LOG.error(_('Error rebuilding server: %s'), - server.id) + LOG.error(_('Error rebuilding server: %s'), server.id) self.app.stdout.write(_('Error rebuilding server\n')) raise SystemExit - details = _prep_server_detail(compute_client, image_client, server, - refresh=False) + details = _prep_server_detail( + compute_client, image_client, server, refresh=False) return zip(*sorted(details.items())) From 30d5f14a700dd53d80e0fbedefd9b1ad337390f9 Mon Sep 17 00:00:00 2001 From: Artem Goncharov Date: Sat, 5 Dec 2020 15:40:24 +0100 Subject: [PATCH 2295/3095] Add support for token caching SDK starts caching token in keyring (when available and configured). A small change is required in OSC not to reject this state. Overall this helps avoiding reauthentication upon next openstack call. If token is not valid anymore automatically reauthentication is done. Depends-On: https://review.opendev.org/c/openstack/openstacksdk/+/735352 Depends-On: https://review.opendev.org/c/openstack/osc-lib/+/765650 Change-Id: I47261a32bd3b106a589974d3de5bf2a6ebd57263 --- openstackclient/common/clientmanager.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index 36c3ce2688..1ed6aa2400 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -83,10 +83,12 @@ def setup_auth(self): self._cli_options._openstack_config._pw_callback = \ shell.prompt_for_password try: - self._cli_options._auth = \ - self._cli_options._openstack_config.load_auth_plugin( - self._cli_options.config, - ) + # We might already get auth from SDK caching + if not self._cli_options._auth: + self._cli_options._auth = \ + self._cli_options._openstack_config.load_auth_plugin( + self._cli_options.config, + ) except TypeError as e: self._fallback_load_auth_plugin(e) From 20769cd7b27d51da84a324a17922427eba5c6eac Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Tue, 8 Dec 2020 10:20:19 +0000 Subject: [PATCH 2296/3095] Fix lower-constraints job pip 20.3 finally includes a proper dependency resolver. Its use is causing the following error messages on the lower-constraints job: ERROR: Cannot install ... because these package versions have conflicting dependencies. The conflict is caused by: bandit 1.1.0 depends on PyYAML>=3.1.0 cliff 3.4.0 depends on PyYAML>=3.12 openstacksdk 0.52.0 depends on PyYAML>=3.13 Bump our lower constraint for PyYAML to resolve this issue. With that resolved, we see a new issue: ERROR: Could not find a version that satisfies the requirement cryptography>=2.7 (from openstacksdk) ERROR: No matching distribution found for cryptography>=2.7 This is less self-explanatory but looking at the lower-constraints for openstacksdk 0.52.0 shows a dependency on cryptography 2.7 [1], meaning we need to bump this also. Next up, flake8-import-order seems to cause the dependency resolver to go nuts, eventually ending with the following error message in a Python 3.6 environment: Using cached enum34-1.1.2.zip (49 kB) ERROR: Command errored out with exit status 1: command: ... cwd: ... Complete output (9 lines): Traceback (most recent call last): File "", line 1, in File ".../lib/python3.6/site-packages/setuptools/__init__.py", line 7, in import setuptools.distutils_patch # noqa: F401 File ".../lib/python3.6/site-packages/setuptools/distutils_patch.py", line 9, in import re File "/usr/lib64/python3.6/re.py", line 142, in class RegexFlag(enum.IntFlag): AttributeError: module 'enum' has no attribute 'IntFlag' ---------------------------------------- A quick Google suggests this is because the enum34 package is not complete [2]. We shouldn't even be using it since our base virtualenv should at least use Python 3.6, but I guess some dependency doesn't properly restrict the dependency to <= Python 3.4. This is moved from 'test-requirements.txt' to 'tox.ini' since we don't need to use our constraints machinery for linters. Finally, the versions of bandit and hacking that pip is bringing in both requires in a newer version of babel, which in turn requires a new version of pytz. Collecting hacking>=2.0.0 ... ERROR: Cannot install oslo.i18n because these package versions have conflicting dependencies. The conflict is caused by: babel 2.9.0 depends on pytz>=2015.7 babel 2.8.1 depends on pytz>=2015.7 babel 2.8.0 depends on pytz>=2015.7 babel 2.7.0 depends on pytz>=2015.7 Seeing as we shouldn't be tracking bandit in lower-constraints, I'm not sure why we're want to bump these dependencies for just that. As above, we move these dependencies out of 'test-requirements' and into 'tox.ini' since we can do that for linters. [1] https://opendev.org/openstack/openstacksdk/src/tag/0.52.0/requirements.txt#L19 [2] https://github.com/iterative/dvc/issues/1995#issuecomment-491889669 Change-Id: I8ec738fbcabc8d8553db79a876e5592576cd18fa Signed-off-by: Stephen Finucane --- lower-constraints.txt | 4 ++-- test-requirements.txt | 3 --- tox.ini | 8 ++++++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/lower-constraints.txt b/lower-constraints.txt index e08b05d22c..cfb89d0765 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -9,7 +9,7 @@ cliff==3.4.0 cmd2==0.8.0 contextlib2==0.4.0 coverage==4.0 -cryptography==2.1 +cryptography==2.7 ddt==1.0.1 debtcollector==1.2.0 decorator==4.4.1 @@ -105,7 +105,7 @@ python-watcherclient==2.5.0 python-zaqarclient==1.0.0 python-zunclient==3.6.0 pytz==2013.6 -PyYAML==3.12 +PyYAML==3.13 repoze.lru==0.7 requests-mock==1.2.0 requests==2.14.2 diff --git a/test-requirements.txt b/test-requirements.txt index 3dce687bc7..8b61a5c077 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,10 +1,8 @@ # The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -hacking>=2.0.0 # Apache-2.0 coverage!=4.4,>=4.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD -flake8-import-order>=0.13 # LGPLv3 oslotest>=3.2.0 # Apache-2.0 requests>=2.14.2 # Apache-2.0 requests-mock>=1.2.0 # Apache-2.0 @@ -12,6 +10,5 @@ stestr>=1.0.0 # Apache-2.0 testtools>=2.2.0 # MIT tempest>=17.1.0 # Apache-2.0 osprofiler>=1.4.0 # Apache-2.0 -bandit!=1.6.0,>=1.1.0 # Apache-2.0 wrapt>=1.7.0 # BSD License ddt>=1.0.1 # MIT diff --git a/tox.ini b/tox.ini index 663463e2f2..ebcdc2d5d9 100644 --- a/tox.ini +++ b/tox.ini @@ -28,9 +28,13 @@ commands = {toxinidir}/tools/fast8.sh [testenv:pep8] +deps = + hacking>=2.0.0 + bandit!=1.6.0,>=1.1.0 + flake8-import-order>=0.13 # LGPLv3 commands = - flake8 - bandit -r openstackclient -x tests -s B105,B106,B107,B401,B404,B603,B606,B607,B110,B605,B101 + flake8 + bandit -r openstackclient -x tests -s B105,B106,B107,B401,B404,B603,B606,B607,B110,B605,B101 [testenv:bandit] # This command runs the bandit security linter against the openstackclient From ecfda7654e47b8be419929cd742e2de6b29ed1cb Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Tue, 8 Dec 2020 11:19:22 +0000 Subject: [PATCH 2297/3095] Update lower-constraints This had gotten pretty out-of-date and included a whole load of OSC plugins which I don't think we need to track from here. This updated version is simply generated via 'pip freeze' using pip >= 20.3, which includes the new dependency resolver. Change-Id: I4fb0b69dbd538f313c6fef97126c22078904c69f Signed-off-by: Stephen Finucane --- lower-constraints.txt | 57 ++++++++----------------------------------- 1 file changed, 10 insertions(+), 47 deletions(-) diff --git a/lower-constraints.txt b/lower-constraints.txt index cfb89d0765..040b976705 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -1,21 +1,17 @@ amqp==2.1.1 -aodhclient==0.9.0 appdirs==1.3.0 asn1crypto==0.23.0 -bandit==1.1.0 +Babel==2.6.0 +bcrypt==3.2.0 cachetools==2.0.0 cffi==1.14.0 cliff==3.4.0 cmd2==0.8.0 -contextlib2==0.4.0 coverage==4.0 cryptography==2.7 ddt==1.0.1 debtcollector==1.2.0 decorator==4.4.1 -deprecation==1.0 -docker-pycreds==0.2.1 -docker==2.4.2 dogpile.cache==0.6.5 eventlet==0.18.2 extras==1.0.0 @@ -23,12 +19,8 @@ fasteners==0.7.0 fixtures==3.0.0 future==0.16.0 futurist==2.1.0 -gitdb==0.6.4 -GitPython==1.0.1 -gnocchiclient==3.3.1 greenlet==0.4.15 -httplib2==0.9.1 -idna==2.6 +importlib-metadata==3.1.1 iso8601==0.1.11 Jinja2==2.10 jmespath==0.9.0 @@ -39,6 +31,7 @@ keystoneauth1==3.18.0 kombu==4.0.0 linecache2==1.0.0 MarkupSafe==1.1.1 +mock==4.0.2 monotonic==0.6 mox3==0.20.0 msgpack-python==0.4.0 @@ -46,10 +39,10 @@ munch==2.1.0 netaddr==0.7.18 netifaces==0.10.4 openstacksdk==0.52.0 +os-client-config==2.1.0 os-service-types==1.7.0 os-testr==1.0.0 osc-lib==2.2.0 -osc-placement==1.7.0 oslo.concurrency==3.26.0 oslo.config==5.2.0 oslo.context==2.19.2 @@ -66,71 +59,41 @@ paramiko==2.7.1 Paste==2.0.2 PasteDeploy==1.5.0 pbr==2.0.0 -pika-pool==0.1.3 pika==0.10.0 -ply==3.10 -positional==1.2.1 +pika-pool==0.1.3 prettytable==0.7.2 -pyasn1==0.1.8 -pycodestyle==2.0.0 pycparser==2.18 pyinotify==0.9.6 -pyOpenSSL==17.1.0 +PyNaCl==1.4.0 pyparsing==2.1.0 pyperclip==1.5.27 -python-barbicanclient==4.5.2 python-cinderclient==3.3.0 python-dateutil==2.5.3 -python-designateclient==2.7.0 -python-glanceclient==2.8.0 -python-heatclient==1.10.0 -python-ironic-inspector-client==1.5.0 -python-ironicclient==2.3.0 -python-karborclient==0.6.0 python-keystoneclient==3.22.0 python-mimeparse==1.6.0 -python-mistralclient==3.1.0 -python-muranoclient==0.8.2 -python-neutronclient==6.7.0 python-novaclient==15.1.0 -python-octaviaclient==1.11.0 -python-rsdclient==1.0.1 -python-saharaclient==1.4.0 -python-searchlightclient==1.0.0 -python-senlinclient==1.1.0 python-subunit==1.0.0 -python-swiftclient==3.2.0 -python-troveclient==3.1.0 -python-watcherclient==2.5.0 -python-zaqarclient==1.0.0 -python-zunclient==3.6.0 pytz==2013.6 PyYAML==3.13 repoze.lru==0.7 -requests-mock==1.2.0 requests==2.14.2 +requests-mock==1.2.0 requestsexceptions==1.2.0 rfc3986==0.3.1 Routes==2.3.1 -rsd-lib==0.1.0 simplejson==3.5.1 -smmap==0.9.0 +six==1.15.0 statsd==3.2.1 stestr==1.0.0 stevedore==2.0.1 -sushy==0.1.0 tempest==17.1.0 tenacity==3.2.1 testrepository==0.0.18 testtools==2.2.0 traceback2==1.4.0 -ujson==1.35 unittest2==1.1.0 urllib3==1.21.1 -validictory==1.1.1 vine==1.1.4 -warlock==1.2.0 WebOb==1.7.1 -websocket-client==0.44.0 wrapt==1.7.0 -yaql==1.1.3 +zipp==3.4.0 From a79e7db4aeb6990912497eb0fd313ae3e60adafb Mon Sep 17 00:00:00 2001 From: Ghanshyam Mann Date: Sat, 28 Nov 2020 16:15:28 -0600 Subject: [PATCH 2298/3095] Remove retired Searchlight support Searchlight project is retiring in Wallaby cycle[1]. This commit removes the support/usage of Searchlight project before its code is removed. Needed-By: https://review.opendev.org/c/openstack/searchlight/+/764526 [1] http://lists.openstack.org/pipermail/openstack-discuss/2020-November/018637.html Change-Id: Idad97343b9ce66186d50ee0560a2fded66655f9b --- doc/requirements.txt | 1 - doc/source/cli/plugin-commands/index.rst | 1 - doc/source/cli/plugin-commands/searchlight.rst | 4 ---- doc/source/contributor/plugins.rst | 1 - 4 files changed, 7 deletions(-) delete mode 100644 doc/source/cli/plugin-commands/searchlight.rst diff --git a/doc/requirements.txt b/doc/requirements.txt index 962b0e2607..28030ca422 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -26,7 +26,6 @@ python-neutronclient>=6.7.0 # Apache-2.0 python-octaviaclient>=1.11.0 # Apache-2.0 python-rsdclient>=1.0.1 # Apache-2.0 python-saharaclient>=1.4.0 # Apache-2.0 -python-searchlightclient>=1.0.0 #Apache-2.0 python-senlinclient>=1.1.0 # Apache-2.0 python-troveclient>=3.1.0 # Apache-2.0 python-watcherclient>=2.5.0 # Apache-2.0 diff --git a/doc/source/cli/plugin-commands/index.rst b/doc/source/cli/plugin-commands/index.rst index 33a8fe064f..8c9c5c13e3 100644 --- a/doc/source/cli/plugin-commands/index.rst +++ b/doc/source/cli/plugin-commands/index.rst @@ -22,7 +22,6 @@ Plugin Commands placement rsd sahara - searchlight senlin trove watcher diff --git a/doc/source/cli/plugin-commands/searchlight.rst b/doc/source/cli/plugin-commands/searchlight.rst deleted file mode 100644 index be934aeb36..0000000000 --- a/doc/source/cli/plugin-commands/searchlight.rst +++ /dev/null @@ -1,4 +0,0 @@ -searchlight ------------ - -.. autoprogram-cliff:: openstack.search.v1 diff --git a/doc/source/contributor/plugins.rst b/doc/source/contributor/plugins.rst index 7ea48edd38..fe2cc14ad0 100644 --- a/doc/source/contributor/plugins.rst +++ b/doc/source/contributor/plugins.rst @@ -37,7 +37,6 @@ The following is a list of projects that are an OpenStackClient plugin. - python-octaviaclient - python-rsdclient - python-saharaclient -- python-searchlightclient - python-senlinclient - python-tripleoclient\*\* - python-troveclient From f5b185c35728025ebfd4145c800648b34476b775 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 4 Nov 2020 16:16:58 +0000 Subject: [PATCH 2299/3095] Make use of comparable 'FormattableColumn' subclasses This requires fixes found in cliff 3.5.0 [1] and osc-lib 2.3.0 [2]. With these fixes in place, we can remove the icky, still broken 'assertItemEqual' and 'assertListItemEqual' helpers. [1] https://review.opendev.org/761421 [2] https://review.opendev.org/761394 Change-Id: Id6c26b37c3c7d5ec6761361abca57f9219b76838 Signed-off-by: Stephen Finucane --- lower-constraints.txt | 4 +- openstackclient/identity/v2_0/catalog.py | 2 +- .../tests/unit/compute/v2/test_aggregate.py | 16 ++--- .../tests/unit/compute/v2/test_flavor.py | 16 ++--- .../unit/compute/v2/test_server_backup.py | 6 +- .../unit/compute/v2/test_server_image.py | 6 +- .../tests/unit/identity/v2_0/test_catalog.py | 17 +++-- .../tests/unit/identity/v2_0/test_project.py | 2 +- .../tests/unit/identity/v2_0/test_user.py | 8 +-- .../tests/unit/identity/v3/test_catalog.py | 10 +-- .../identity/v3/test_identity_provider.py | 24 +++---- .../tests/unit/image/v1/test_image.py | 10 +-- .../tests/unit/image/v2/test_image.py | 22 +++---- .../unit/network/v2/test_ip_availability.py | 8 +-- .../tests/unit/network/v2/test_network.py | 46 ++++++------- .../unit/network/v2/test_network_agent.py | 14 ++-- .../tests/unit/network/v2/test_port.py | 64 +++++++++---------- .../tests/unit/network/v2/test_router.py | 32 +++++----- .../network/v2/test_security_group_compute.py | 10 +-- .../network/v2/test_security_group_network.py | 16 ++--- .../tests/unit/network/v2/test_subnet.py | 42 ++++++------ .../tests/unit/network/v2/test_subnet_pool.py | 40 ++++++------ openstackclient/tests/unit/utils.py | 16 ----- .../tests/unit/volume/v1/test_qos_specs.py | 12 ++-- .../tests/unit/volume/v1/test_type.py | 14 ++-- .../tests/unit/volume/v1/test_volume.py | 36 +++++------ .../unit/volume/v1/test_volume_backup.py | 10 +-- .../unit/volume/v2/test_consistency_group.py | 14 ++-- .../tests/unit/volume/v2/test_qos_specs.py | 12 ++-- .../tests/unit/volume/v2/test_type.py | 24 +++---- .../tests/unit/volume/v2/test_volume.py | 40 ++++++------ .../unit/volume/v2/test_volume_backup.py | 4 +- requirements.txt | 4 +- 33 files changed, 295 insertions(+), 306 deletions(-) diff --git a/lower-constraints.txt b/lower-constraints.txt index 040b976705..287f7d4599 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -5,7 +5,7 @@ Babel==2.6.0 bcrypt==3.2.0 cachetools==2.0.0 cffi==1.14.0 -cliff==3.4.0 +cliff==3.5.0 cmd2==0.8.0 coverage==4.0 cryptography==2.7 @@ -42,7 +42,7 @@ openstacksdk==0.52.0 os-client-config==2.1.0 os-service-types==1.7.0 os-testr==1.0.0 -osc-lib==2.2.0 +osc-lib==2.3.0 oslo.concurrency==3.26.0 oslo.config==5.2.0 oslo.context==2.19.2 diff --git a/openstackclient/identity/v2_0/catalog.py b/openstackclient/identity/v2_0/catalog.py index ccedbf3312..05d0e9aebc 100644 --- a/openstackclient/identity/v2_0/catalog.py +++ b/openstackclient/identity/v2_0/catalog.py @@ -91,7 +91,7 @@ def take_action(self, parsed_args): for service in auth_ref.service_catalog.catalog: if (service.get('name') == parsed_args.service or service.get('type') == parsed_args.service): - data = service + data = service.copy() data['endpoints'] = EndpointsColumn(data['endpoints']) if 'endpoints_links' in data: data.pop('endpoints_links') diff --git a/openstackclient/tests/unit/compute/v2/test_aggregate.py b/openstackclient/tests/unit/compute/v2/test_aggregate.py index e4c13ea55c..e12edd0fce 100644 --- a/openstackclient/tests/unit/compute/v2/test_aggregate.py +++ b/openstackclient/tests/unit/compute/v2/test_aggregate.py @@ -88,7 +88,7 @@ def test_aggregate_add_host(self): self.sdk_client.add_host_to_aggregate.assert_called_once_with( self.fake_ag.id, parsed_args.host) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) class TestAggregateCreate(TestAggregate): @@ -112,7 +112,7 @@ def test_aggregate_create(self): self.sdk_client.create_aggregate.assert_called_once_with( name=parsed_args.name) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_aggregate_create_with_zone(self): arglist = [ @@ -129,7 +129,7 @@ def test_aggregate_create_with_zone(self): self.sdk_client.create_aggregate.assert_called_once_with( name=parsed_args.name, availability_zone=parsed_args.zone) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_aggregate_create_with_property(self): arglist = [ @@ -148,7 +148,7 @@ def test_aggregate_create_with_property(self): self.sdk_client.set_aggregate_metadata.assert_called_once_with( self.fake_ag.id, parsed_args.property) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) class TestAggregateDelete(TestAggregate): @@ -265,7 +265,7 @@ def test_aggregate_list(self): columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.list_columns, columns) - self.assertItemEqual(self.list_data, tuple(data)) + self.assertItemsEqual(self.list_data, tuple(data)) def test_aggregate_list_with_long(self): arglist = [ @@ -278,7 +278,7 @@ def test_aggregate_list_with_long(self): columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.list_columns_long, columns) - self.assertListItemEqual(self.list_data_long, tuple(data)) + self.assertItemsEqual(self.list_data_long, tuple(data)) class TestAggregateRemoveHost(TestAggregate): @@ -306,7 +306,7 @@ def test_aggregate_remove_host(self): self.sdk_client.remove_host_from_aggregate.assert_called_once_with( self.fake_ag.id, parsed_args.host) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) class TestAggregateSet(TestAggregate): @@ -492,7 +492,7 @@ def test_aggregate_show(self): parsed_args.aggregate, ignore_missing=False) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, tuple(data)) + self.assertItemsEqual(self.data, tuple(data)) class TestAggregateUnset(TestAggregate): diff --git a/openstackclient/tests/unit/compute/v2/test_flavor.py b/openstackclient/tests/unit/compute/v2/test_flavor.py index 8625b71202..4c4882ecbd 100644 --- a/openstackclient/tests/unit/compute/v2/test_flavor.py +++ b/openstackclient/tests/unit/compute/v2/test_flavor.py @@ -133,7 +133,7 @@ def test_flavor_create_default_options(self): self.sdk_client.create_flavor.assert_called_once_with(**default_args) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_flavor_create_all_options(self): @@ -201,8 +201,8 @@ def test_flavor_create_all_options(self): create_flavor, props) self.sdk_client.get_flavor_access.assert_not_called() - self.assertEqual(self.columns, columns) - self.assertItemEqual(tuple(cmp_data), data) + self.assertEqual(self.columns, columns) + self.assertItemsEqual(tuple(cmp_data), data) def test_flavor_create_other_options(self): @@ -277,7 +277,7 @@ def test_flavor_create_other_options(self): self.sdk_client.create_flavor_extra_specs.assert_called_with( create_flavor, props) self.assertEqual(self.columns, columns) - self.assertItemEqual(cmp_data, data) + self.assertItemsEqual(cmp_data, data) def test_public_flavor_create_with_project(self): arglist = [ @@ -350,7 +350,7 @@ def test_flavor_create_with_description_api_newer(self): self.sdk_client.create_flavor.assert_called_once_with(**args) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data_private, data) + self.assertItemsEqual(self.data_private, data) def test_flavor_create_with_description_api_older(self): arglist = [ @@ -633,7 +633,7 @@ def test_flavor_list_long(self): ) self.assertEqual(self.columns_long, columns) - self.assertListItemEqual(self.data_long, tuple(data)) + self.assertItemsEqual(self.data_long, tuple(data)) class TestFlavorSet(TestFlavor): @@ -920,7 +920,7 @@ def test_public_flavor_show(self): columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_private_flavor_show(self): private_flavor = compute_fakes.FakeFlavor.create_one_flavor( @@ -960,7 +960,7 @@ def test_private_flavor_show(self): self.sdk_client.get_flavor_access.assert_called_with( flavor=private_flavor.id) self.assertEqual(self.columns, columns) - self.assertItemEqual(data_with_project, data) + self.assertItemsEqual(data_with_project, data) class TestFlavorUnset(TestFlavor): diff --git a/openstackclient/tests/unit/compute/v2/test_server_backup.py b/openstackclient/tests/unit/compute/v2/test_server_backup.py index 5cdc20800a..753db9cd70 100644 --- a/openstackclient/tests/unit/compute/v2/test_server_backup.py +++ b/openstackclient/tests/unit/compute/v2/test_server_backup.py @@ -139,7 +139,7 @@ def test_server_backup_defaults(self): ) self.assertEqual(self.image_columns(images[0]), columns) - self.assertItemEqual(self.image_data(images[0]), data) + self.assertItemsEqual(self.image_data(images[0]), data) def test_server_backup_create_options(self): servers = self.setup_servers_mock(count=1) @@ -173,7 +173,7 @@ def test_server_backup_create_options(self): ) self.assertEqual(self.image_columns(images[0]), columns) - self.assertItemEqual(self.image_data(images[0]), data) + self.assertItemsEqual(self.image_data(images[0]), data) @mock.patch.object(common_utils, 'wait_for_status', return_value=False) def test_server_backup_wait_fail(self, mock_wait_for_status): @@ -269,4 +269,4 @@ def test_server_backup_wait_ok(self, mock_wait_for_status): ) self.assertEqual(self.image_columns(images[0]), columns) - self.assertItemEqual(self.image_data(images[0]), data) + self.assertItemsEqual(self.image_data(images[0]), data) diff --git a/openstackclient/tests/unit/compute/v2/test_server_image.py b/openstackclient/tests/unit/compute/v2/test_server_image.py index 1cec5b685f..06f6017c11 100644 --- a/openstackclient/tests/unit/compute/v2/test_server_image.py +++ b/openstackclient/tests/unit/compute/v2/test_server_image.py @@ -133,7 +133,7 @@ def test_server_image_create_defaults(self): ) self.assertEqual(self.image_columns(images[0]), columns) - self.assertItemEqual(self.image_data(images[0]), data) + self.assertItemsEqual(self.image_data(images[0]), data) def test_server_image_create_options(self): servers = self.setup_servers_mock(count=1) @@ -161,7 +161,7 @@ def test_server_image_create_options(self): ) self.assertEqual(self.image_columns(images[0]), columns) - self.assertItemEqual(self.image_data(images[0]), data) + self.assertItemsEqual(self.image_data(images[0]), data) @mock.patch.object(common_utils, 'wait_for_status', return_value=False) def test_server_create_image_wait_fail(self, mock_wait_for_status): @@ -229,4 +229,4 @@ def test_server_create_image_wait_ok(self, mock_wait_for_status): ) self.assertEqual(self.image_columns(images[0]), columns) - self.assertItemEqual(self.image_data(images[0]), data) + self.assertItemsEqual(self.image_data(images[0]), data) diff --git a/openstackclient/tests/unit/identity/v2_0/test_catalog.py b/openstackclient/tests/unit/identity/v2_0/test_catalog.py index 1735507469..e2c56ba1ec 100644 --- a/openstackclient/tests/unit/identity/v2_0/test_catalog.py +++ b/openstackclient/tests/unit/identity/v2_0/test_catalog.py @@ -71,9 +71,10 @@ def test_catalog_list(self): datalist = (( 'supernova', 'compute', - catalog.EndpointsColumn(self.service_catalog['endpoints']), + catalog.EndpointsColumn( + auth_ref.service_catalog.catalog[0]['endpoints']), ), ) - self.assertListItemEqual(datalist, tuple(data)) + self.assertItemsEqual(datalist, tuple(data)) def test_catalog_list_with_endpoint_url(self): attr = { @@ -113,9 +114,10 @@ def test_catalog_list_with_endpoint_url(self): datalist = (( 'supernova', 'compute', - catalog.EndpointsColumn(service_catalog['endpoints']), + catalog.EndpointsColumn( + auth_ref.service_catalog.catalog[0]['endpoints']), ), ) - self.assertListItemEqual(datalist, tuple(data)) + self.assertItemsEqual(datalist, tuple(data)) class TestCatalogShow(TestCatalog): @@ -150,16 +152,17 @@ def test_catalog_show(self): collist = ('endpoints', 'id', 'name', 'type') self.assertEqual(collist, columns) datalist = ( - catalog.EndpointsColumn(self.service_catalog['endpoints']), + catalog.EndpointsColumn( + auth_ref.service_catalog.catalog[0]['endpoints']), self.service_catalog.id, 'supernova', 'compute', ) - self.assertItemEqual(datalist, data) + self.assertItemsEqual(datalist, data) class TestFormatColumns(TestCatalog): - def test_endpoints_column_human_readabale(self): + def test_endpoints_column_human_readable(self): col = catalog.EndpointsColumn(self.service_catalog['endpoints']) self.assertEqual( 'one\n publicURL: https://public.one.example.com\n ' diff --git a/openstackclient/tests/unit/identity/v2_0/test_project.py b/openstackclient/tests/unit/identity/v2_0/test_project.py index cd8c825d15..766d5dab55 100644 --- a/openstackclient/tests/unit/identity/v2_0/test_project.py +++ b/openstackclient/tests/unit/identity/v2_0/test_project.py @@ -643,7 +643,7 @@ def test_project_show(self): self.fake_proj_show.name, format_columns.DictColumn({}), ) - self.assertItemEqual(datalist, data) + self.assertItemsEqual(datalist, data) class TestProjectUnset(TestProject): diff --git a/openstackclient/tests/unit/identity/v2_0/test_user.py b/openstackclient/tests/unit/identity/v2_0/test_user.py index 4308b05d05..dd30047814 100644 --- a/openstackclient/tests/unit/identity/v2_0/test_user.py +++ b/openstackclient/tests/unit/identity/v2_0/test_user.py @@ -482,7 +482,7 @@ def test_user_list_no_options(self): self.users_mock.list.assert_called_with(tenant_id=None) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.datalist, tuple(data)) + self.assertItemsEqual(self.datalist, tuple(data)) def test_user_list_project(self): arglist = [ @@ -502,7 +502,7 @@ def test_user_list_project(self): self.users_mock.list.assert_called_with(tenant_id=project_id) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.datalist, tuple(data)) + self.assertItemsEqual(self.datalist, tuple(data)) def test_user_list_long(self): arglist = [ @@ -531,7 +531,7 @@ def test_user_list_long(self): self.fake_user_l.email, True, ), ) - self.assertListItemEqual(datalist, tuple(data)) + self.assertItemsEqual(datalist, tuple(data)) class TestUserSet(TestUser): @@ -819,4 +819,4 @@ def test_user_show(self): self.fake_user.name, self.fake_project.id, ) - self.assertItemEqual(datalist, data) + self.assertItemsEqual(datalist, data) diff --git a/openstackclient/tests/unit/identity/v3/test_catalog.py b/openstackclient/tests/unit/identity/v3/test_catalog.py index 3630ccb6a3..97ce48f6a5 100644 --- a/openstackclient/tests/unit/identity/v3/test_catalog.py +++ b/openstackclient/tests/unit/identity/v3/test_catalog.py @@ -91,9 +91,10 @@ def test_catalog_list(self): datalist = (( 'supernova', 'compute', - catalog.EndpointsColumn(self.fake_service['endpoints']), + catalog.EndpointsColumn( + auth_ref.service_catalog.catalog[0]['endpoints']), ), ) - self.assertListItemEqual(datalist, tuple(data)) + self.assertItemsEqual(datalist, tuple(data)) class TestCatalogShow(TestCatalog): @@ -128,12 +129,13 @@ def test_catalog_show(self): collist = ('endpoints', 'id', 'name', 'type') self.assertEqual(collist, columns) datalist = ( - catalog.EndpointsColumn(self.fake_service['endpoints']), + catalog.EndpointsColumn( + auth_ref.service_catalog.catalog[0]['endpoints']), 'qwertyuiop', 'supernova', 'compute', ) - self.assertItemEqual(datalist, data) + self.assertItemsEqual(datalist, data) class TestFormatColumns(TestCatalog): diff --git a/openstackclient/tests/unit/identity/v3/test_identity_provider.py b/openstackclient/tests/unit/identity/v3/test_identity_provider.py index 39a37db24a..5aff2b1be2 100644 --- a/openstackclient/tests/unit/identity/v3/test_identity_provider.py +++ b/openstackclient/tests/unit/identity/v3/test_identity_provider.py @@ -89,7 +89,7 @@ def test_create_identity_provider_no_options(self): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) def test_create_identity_provider_description(self): arglist = [ @@ -117,7 +117,7 @@ def test_create_identity_provider_description(self): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) def test_create_identity_provider_remote_id(self): arglist = [ @@ -145,7 +145,7 @@ def test_create_identity_provider_remote_id(self): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) def test_create_identity_provider_remote_ids_multiple(self): arglist = [ @@ -174,7 +174,7 @@ def test_create_identity_provider_remote_ids_multiple(self): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) def test_create_identity_provider_remote_ids_file(self): arglist = [ @@ -207,7 +207,7 @@ def test_create_identity_provider_remote_ids_file(self): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) def test_create_identity_provider_disabled(self): @@ -250,7 +250,7 @@ def test_create_identity_provider_disabled(self): identity_fakes.idp_id, identity_fakes.formatted_idp_remote_ids ) - self.assertItemEqual(datalist, data) + self.assertItemsEqual(datalist, data) def test_create_identity_provider_domain_name(self): arglist = [ @@ -278,7 +278,7 @@ def test_create_identity_provider_domain_name(self): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) def test_create_identity_provider_domain_id(self): arglist = [ @@ -306,7 +306,7 @@ def test_create_identity_provider_domain_id(self): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) class TestIdentityProviderDelete(TestIdentityProvider): @@ -382,7 +382,7 @@ def test_identity_provider_list_no_options(self): identity_fakes.domain_id, identity_fakes.idp_description, ), ) - self.assertListItemEqual(datalist, tuple(data)) + self.assertItemsEqual(datalist, tuple(data)) def test_identity_provider_list_ID_option(self): arglist = ['--id', @@ -410,7 +410,7 @@ def test_identity_provider_list_ID_option(self): identity_fakes.domain_id, identity_fakes.idp_description, ), ) - self.assertListItemEqual(datalist, tuple(data)) + self.assertItemsEqual(datalist, tuple(data)) def test_identity_provider_list_enabled_option(self): arglist = ['--enabled'] @@ -437,7 +437,7 @@ def test_identity_provider_list_enabled_option(self): identity_fakes.domain_id, identity_fakes.idp_description, ), ) - self.assertListItemEqual(datalist, tuple(data)) + self.assertItemsEqual(datalist, tuple(data)) class TestIdentityProviderSet(TestIdentityProvider): @@ -722,4 +722,4 @@ def test_identity_provider_show(self): identity_fakes.idp_id, identity_fakes.formatted_idp_remote_ids ) - self.assertItemEqual(datalist, data) + self.assertItemsEqual(datalist, data) diff --git a/openstackclient/tests/unit/image/v1/test_image.py b/openstackclient/tests/unit/image/v1/test_image.py index 2f190a7a3c..db64983c97 100644 --- a/openstackclient/tests/unit/image/v1/test_image.py +++ b/openstackclient/tests/unit/image/v1/test_image.py @@ -100,7 +100,7 @@ def test_image_reserve_no_options(self, raw_input): self.assertEqual(self.client.update_image.call_args_list, []) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) @mock.patch('sys.stdin', side_effect=[None]) def test_image_reserve_options(self, raw_input): @@ -149,7 +149,7 @@ def test_image_reserve_options(self, raw_input): self.assertEqual(self.client.update_image.call_args_list, []) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) @mock.patch('openstackclient.image.v1.image.io.open', name='Open') def test_image_create_file(self, mock_open): @@ -205,7 +205,7 @@ def test_image_create_file(self, mock_open): self.assertEqual(self.client.update_image.call_args_list, []) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) class TestImageDelete(TestImage): @@ -386,7 +386,7 @@ def test_image_list_long_option(self): format_columns.DictColumn( {'Alpha': 'a', 'Beta': 'b', 'Gamma': 'g'}), ), ) - self.assertListItemEqual(datalist, tuple(data)) + self.assertItemsEqual(datalist, tuple(data)) @mock.patch('osc_lib.api.utils.simple_filter') def test_image_list_property_option(self, sf_mock): @@ -737,7 +737,7 @@ def test_image_show(self): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_image_show_human_readable(self): arglist = [ diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py index 80e60ee818..b72e983599 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -111,7 +111,7 @@ def test_image_reserve_no_options(self, raw_input): self.assertEqual( self.expected_columns, columns) - self.assertItemEqual( + self.assertItemsEqual( self.expected_data, data) @@ -166,7 +166,7 @@ def test_image_reserve_options(self, raw_input): self.assertEqual( self.expected_columns, columns) - self.assertItemEqual( + self.assertItemsEqual( self.expected_data, data) @@ -255,7 +255,7 @@ def test_image_create_file(self): self.assertEqual( self.expected_columns, columns) - self.assertItemEqual( + self.assertItemsEqual( self.expected_data, data) @@ -513,7 +513,7 @@ def test_image_list_no_options(self): ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.datalist, tuple(data)) + self.assertItemsEqual(self.datalist, tuple(data)) def test_image_list_public_option(self): arglist = [ @@ -537,7 +537,7 @@ def test_image_list_public_option(self): ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.datalist, tuple(data)) + self.assertItemsEqual(self.datalist, tuple(data)) def test_image_list_private_option(self): arglist = [ @@ -561,7 +561,7 @@ def test_image_list_private_option(self): ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.datalist, tuple(data)) + self.assertItemsEqual(self.datalist, tuple(data)) def test_image_list_community_option(self): arglist = [ @@ -609,7 +609,7 @@ def test_image_list_shared_option(self): ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.datalist, tuple(data)) + self.assertItemsEqual(self.datalist, tuple(data)) def test_image_list_shared_member_status_option(self): arglist = [ @@ -697,7 +697,7 @@ def test_image_list_long_option(self): self._image.owner_id, format_columns.ListColumn(self._image.tags), ), ) - self.assertListItemEqual(datalist, tuple(data)) + self.assertItemsEqual(datalist, tuple(data)) @mock.patch('osc_lib.api.utils.simple_filter') def test_image_list_property_option(self, sf_mock): @@ -725,7 +725,7 @@ def test_image_list_property_option(self, sf_mock): ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.datalist, tuple(data)) + self.assertItemsEqual(self.datalist, tuple(data)) @mock.patch('osc_lib.utils.sort_items') def test_image_list_sort_option(self, si_mock): @@ -747,7 +747,7 @@ def test_image_list_sort_option(self, si_mock): str, ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.datalist, tuple(data)) + self.assertItemsEqual(self.datalist, tuple(data)) def test_image_list_limit_option(self): ret_limit = 1 @@ -1472,7 +1472,7 @@ def test_image_show(self): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_image_show_human_readable(self): self.client.find_image.return_value = self.new_image diff --git a/openstackclient/tests/unit/network/v2/test_ip_availability.py b/openstackclient/tests/unit/network/v2/test_ip_availability.py index 9a712704c6..ade5783700 100644 --- a/openstackclient/tests/unit/network/v2/test_ip_availability.py +++ b/openstackclient/tests/unit/network/v2/test_ip_availability.py @@ -75,7 +75,7 @@ def test_list_no_options(self): self.network.network_ip_availabilities.assert_called_once_with( **filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_list_ip_version(self): arglist = [ @@ -93,7 +93,7 @@ def test_list_ip_version(self): self.network.network_ip_availabilities.assert_called_once_with( **filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_list_project(self): arglist = [ @@ -113,7 +113,7 @@ def test_list_project(self): self.network.network_ip_availabilities.assert_called_once_with( **filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) class TestShowIPAvailability(TestIPAvailability): @@ -176,4 +176,4 @@ def test_show_all_options(self): self._ip_availability.network_name, ignore_missing=False) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) diff --git a/openstackclient/tests/unit/network/v2/test_network.py b/openstackclient/tests/unit/network/v2/test_network.py index 5f8eed6702..e29b72c769 100644 --- a/openstackclient/tests/unit/network/v2/test_network.py +++ b/openstackclient/tests/unit/network/v2/test_network.py @@ -146,7 +146,7 @@ def test_create_default_options(self): }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_all_options(self): arglist = [ @@ -211,7 +211,7 @@ def test_create_all_options(self): 'dns_domain': 'example.org.', }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_other_options(self): arglist = [ @@ -238,7 +238,7 @@ def test_create_other_options(self): 'port_security_enabled': False, }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def _test_create_with_tag(self, add_tags=True): arglist = [self._network.name] @@ -270,7 +270,7 @@ def _test_create_with_tag(self, add_tags=True): else: self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_with_tags(self): self._test_create_with_tag(add_tags=True) @@ -385,7 +385,7 @@ def test_create_with_project_identityv2(self): }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_with_domain_identityv2(self): arglist = [ @@ -577,7 +577,7 @@ def test_network_list_no_options(self): self.network.networks.assert_called_once_with() self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_list_external(self): arglist = [ @@ -598,7 +598,7 @@ def test_list_external(self): **{'router:external': True, 'is_router_external': True} ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_list_internal(self): arglist = [ @@ -615,7 +615,7 @@ def test_list_internal(self): **{'router:external': False, 'is_router_external': False} ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_network_list_long(self): arglist = [ @@ -634,7 +634,7 @@ def test_network_list_long(self): self.network.networks.assert_called_once_with() self.assertEqual(self.columns_long, columns) - self.assertListItemEqual(self.data_long, list(data)) + self.assertItemsEqual(self.data_long, list(data)) def test_list_name(self): test_name = "fakename" @@ -653,7 +653,7 @@ def test_list_name(self): **{'name': test_name} ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_network_list_enable(self): arglist = [ @@ -671,7 +671,7 @@ def test_network_list_enable(self): **{'admin_state_up': True, 'is_admin_state_up': True} ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_network_list_disable(self): arglist = [ @@ -689,7 +689,7 @@ def test_network_list_disable(self): **{'admin_state_up': False, 'is_admin_state_up': False} ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_network_list_project(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -708,7 +708,7 @@ def test_network_list_project(self): ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_network_list_project_domain(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -727,7 +727,7 @@ def test_network_list_project_domain(self): self.network.networks.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_network_list_share(self): arglist = [ @@ -744,7 +744,7 @@ def test_network_list_share(self): **{'shared': True, 'is_shared': True} ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_network_list_no_share(self): arglist = [ @@ -761,7 +761,7 @@ def test_network_list_no_share(self): **{'shared': False, 'is_shared': False} ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_network_list_status(self): choices = ['ACTIVE', 'BUILD', 'DOWN', 'ERROR'] @@ -780,7 +780,7 @@ def test_network_list_status(self): **{'status': test_status} ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_network_list_provider_network_type(self): network_type = self._network[0].provider_network_type @@ -798,7 +798,7 @@ def test_network_list_provider_network_type(self): 'provider_network_type': network_type} ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_network_list_provider_physical_network(self): physical_network = self._network[0].provider_physical_network @@ -816,7 +816,7 @@ def test_network_list_provider_physical_network(self): 'provider_physical_network': physical_network} ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_network_list_provider_segment(self): segmentation_id = self._network[0].provider_segmentation_id @@ -834,7 +834,7 @@ def test_network_list_provider_segment(self): 'provider_segmentation_id': segmentation_id} ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_network_list_dhcp_agent(self): arglist = [ @@ -853,7 +853,7 @@ def test_network_list_dhcp_agent(self): *attrs) self.assertEqual(self.columns, columns) - self.assertListItemEqual(list(data), list(self.data)) + self.assertItemsEqual(list(data), list(self.data)) def test_list_with_tag_options(self): arglist = [ @@ -878,7 +878,7 @@ def test_list_with_tag_options(self): 'not_any_tags': 'black,white'} ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) class TestSetNetwork(TestNetwork): @@ -1111,7 +1111,7 @@ def test_show_all_options(self): self._network.name, ignore_missing=False) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) class TestUnsetNetwork(TestNetwork): diff --git a/openstackclient/tests/unit/network/v2/test_network_agent.py b/openstackclient/tests/unit/network/v2/test_network_agent.py index 3181ee78a1..fceac68ea0 100644 --- a/openstackclient/tests/unit/network/v2/test_network_agent.py +++ b/openstackclient/tests/unit/network/v2/test_network_agent.py @@ -246,7 +246,7 @@ def test_network_agents_list(self): self.network.agents.assert_called_once_with(**{}) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_network_agents_list_agent_type(self): arglist = [ @@ -263,7 +263,7 @@ def test_network_agents_list_agent_type(self): 'agent_type': 'DHCP agent', }) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_network_agents_list_host(self): arglist = [ @@ -280,7 +280,7 @@ def test_network_agents_list_host(self): 'host': self.network_agents[0].host, }) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_network_agents_list_networks(self): arglist = [ @@ -298,7 +298,7 @@ def test_network_agents_list_networks(self): self.network.network_hosting_dhcp_agents.assert_called_once_with( *attrs) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_network_agents_list_routers(self): arglist = [ @@ -318,7 +318,7 @@ def test_network_agents_list_routers(self): *attrs) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_network_agents_list_routers_with_long_option(self): arglist = [ @@ -343,7 +343,7 @@ def test_network_agents_list_routers_with_long_option(self): router_agent_data = [d + ('',) for d in self.data] self.assertEqual(router_agent_columns, columns) - self.assertListItemEqual(router_agent_data, list(data)) + self.assertItemsEqual(router_agent_data, list(data)) class TestRemoveNetworkFromAgent(TestNetworkAgent): @@ -571,4 +571,4 @@ def test_show_all_options(self): self.network.get_agent.assert_called_once_with( self._network_agent.id) self.assertEqual(self.columns, columns) - self.assertItemEqual(list(self.data), list(data)) + self.assertItemsEqual(list(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 f7685f46e5..e21f9d01f1 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -151,7 +151,7 @@ def test_create_default_options(self): self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_full_options(self): arglist = [ @@ -209,7 +209,7 @@ def test_create_full_options(self): }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_invalid_json_binding_profile(self): arglist = [ @@ -260,7 +260,7 @@ def test_create_json_binding_profile(self): }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_with_security_group(self): secgroup = network_fakes.FakeSecurityGroup.create_one_security_group() @@ -289,7 +289,7 @@ def test_create_with_security_group(self): }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_port_with_dns_name(self): arglist = [ @@ -315,7 +315,7 @@ def test_create_port_with_dns_name(self): }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_with_security_groups(self): sg_1 = network_fakes.FakeSecurityGroup.create_one_security_group() @@ -345,7 +345,7 @@ def test_create_with_security_groups(self): }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_with_no_security_groups(self): arglist = [ @@ -371,7 +371,7 @@ def test_create_with_no_security_groups(self): }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_with_no_fixed_ips(self): arglist = [ @@ -397,7 +397,7 @@ def test_create_with_no_fixed_ips(self): }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_port_with_allowed_address_pair_ipaddr(self): pairs = [{'ip_address': '192.168.1.123'}, @@ -427,7 +427,7 @@ def test_create_port_with_allowed_address_pair_ipaddr(self): }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_port_with_allowed_address_pair(self): pairs = [{'ip_address': '192.168.1.123', @@ -463,7 +463,7 @@ def test_create_port_with_allowed_address_pair(self): }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_port_with_qos(self): qos_policy = network_fakes.FakeNetworkQosPolicy.create_one_qos_policy() @@ -491,7 +491,7 @@ def test_create_port_with_qos(self): }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_port_security_enabled(self): arglist = [ @@ -600,7 +600,7 @@ def _test_create_with_tag(self, add_tags=True, add_tags_in_post=True): self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_with_tags(self): self._test_create_with_tag(add_tags=True, add_tags_in_post=True) @@ -643,7 +643,7 @@ def _test_create_with_uplink_status_propagation(self, enable=True): }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_with_uplink_status_propagation_enabled(self): self._test_create_with_uplink_status_propagation(enable=True) @@ -723,7 +723,7 @@ def _test_create_with_numa_affinity_policy(self, policy=None): self.network.create_port.assert_called_once_with(**create_args) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_with_numa_affinity_policy_required(self): self._test_create_with_numa_affinity_policy(policy='required') @@ -890,7 +890,7 @@ def test_port_list_no_options(self): self.network.ports.assert_called_once_with( fields=LIST_FIELDS_TO_RETRIEVE) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_port_list_router_opt(self): arglist = [ @@ -910,7 +910,7 @@ def test_port_list_router_opt(self): 'fields': LIST_FIELDS_TO_RETRIEVE, }) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) @mock.patch.object(utils, 'find_resource') def test_port_list_with_server_option(self, mock_find): @@ -931,7 +931,7 @@ def test_port_list_with_server_option(self, mock_find): fields=LIST_FIELDS_TO_RETRIEVE) mock_find.assert_called_once_with(mock.ANY, 'fake-server-name') self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_port_list_device_id_opt(self): arglist = [ @@ -951,7 +951,7 @@ def test_port_list_device_id_opt(self): 'fields': LIST_FIELDS_TO_RETRIEVE, }) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_port_list_device_owner_opt(self): arglist = [ @@ -971,7 +971,7 @@ def test_port_list_device_owner_opt(self): 'fields': LIST_FIELDS_TO_RETRIEVE, }) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_port_list_all_opt(self): arglist = [ @@ -1000,7 +1000,7 @@ def test_port_list_all_opt(self): 'fields': LIST_FIELDS_TO_RETRIEVE, }) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_port_list_mac_address_opt(self): arglist = [ @@ -1020,7 +1020,7 @@ def test_port_list_mac_address_opt(self): 'fields': LIST_FIELDS_TO_RETRIEVE, }) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_port_list_fixed_ip_opt_ip_address(self): ip_address = self._ports[0].fixed_ips[0]['ip_address'] @@ -1040,7 +1040,7 @@ def test_port_list_fixed_ip_opt_ip_address(self): 'fields': LIST_FIELDS_TO_RETRIEVE, }) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_port_list_fixed_ip_opt_ip_address_substr(self): ip_address_ss = self._ports[0].fixed_ips[0]['ip_address'][:-1] @@ -1060,7 +1060,7 @@ def test_port_list_fixed_ip_opt_ip_address_substr(self): 'fields': LIST_FIELDS_TO_RETRIEVE, }) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_port_list_fixed_ip_opt_subnet_id(self): subnet_id = self._ports[0].fixed_ips[0]['subnet_id'] @@ -1082,7 +1082,7 @@ def test_port_list_fixed_ip_opt_subnet_id(self): 'fields': LIST_FIELDS_TO_RETRIEVE, }) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_port_list_fixed_ip_opts(self): subnet_id = self._ports[0].fixed_ips[0]['subnet_id'] @@ -1108,7 +1108,7 @@ def test_port_list_fixed_ip_opts(self): 'fields': LIST_FIELDS_TO_RETRIEVE, }) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_port_list_fixed_ips(self): subnet_id = self._ports[0].fixed_ips[0]['subnet_id'] @@ -1136,7 +1136,7 @@ def test_port_list_fixed_ips(self): 'fields': LIST_FIELDS_TO_RETRIEVE, }) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_list_port_with_long(self): arglist = [ @@ -1154,7 +1154,7 @@ def test_list_port_with_long(self): self.network.ports.assert_called_once_with( fields=LIST_FIELDS_TO_RETRIEVE + LIST_FIELDS_TO_RETRIEVE_LONG) self.assertEqual(self.columns_long, columns) - self.assertListItemEqual(self.data_long, list(data)) + self.assertItemsEqual(self.data_long, list(data)) def test_port_list_host(self): arglist = [ @@ -1173,7 +1173,7 @@ def test_port_list_host(self): self.network.ports.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_port_list_project(self): project = identity_fakes.FakeProject.create_one_project() @@ -1195,7 +1195,7 @@ def test_port_list_project(self): self.network.ports.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_port_list_project_domain(self): project = identity_fakes.FakeProject.create_one_project() @@ -1219,7 +1219,7 @@ def test_port_list_project_domain(self): self.network.ports.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_list_with_tag_options(self): arglist = [ @@ -1245,7 +1245,7 @@ def test_list_with_tag_options(self): 'fields': LIST_FIELDS_TO_RETRIEVE} ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) class TestSetPort(TestPort): @@ -1845,7 +1845,7 @@ def test_show_all_options(self): self._port.name, ignore_missing=False) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) class TestUnsetPort(TestPort): diff --git a/openstackclient/tests/unit/network/v2/test_router.py b/openstackclient/tests/unit/network/v2/test_router.py index 09b4957cce..323c919828 100644 --- a/openstackclient/tests/unit/network/v2/test_router.py +++ b/openstackclient/tests/unit/network/v2/test_router.py @@ -184,7 +184,7 @@ def test_create_default_options(self): }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def _test_create_with_ha_options(self, option, ha): arglist = [ @@ -208,7 +208,7 @@ def _test_create_with_ha_options(self, option, ha): 'ha': ha, }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_with_ha_option(self): self._test_create_with_ha_options('--ha', True) @@ -237,7 +237,7 @@ def _test_create_with_distributed_options(self, option, distributed): 'distributed': distributed, }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_with_distributed_option(self): self._test_create_with_distributed_options('--distributed', True) @@ -268,7 +268,7 @@ def test_create_with_AZ_hints(self): }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def _test_create_with_tag(self, add_tags=True): arglist = [self.new_router.name] @@ -301,7 +301,7 @@ def _test_create_with_tag(self, add_tags=True): else: self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_with_tags(self): self._test_create_with_tag(add_tags=True) @@ -494,7 +494,7 @@ def test_router_list_no_options(self): self.network.routers.assert_called_once_with() self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_router_list_no_ha_no_distributed(self): _routers = network_fakes.FakeRouter.create_routers({ @@ -531,7 +531,7 @@ def test_router_list_long(self): self.network.routers.assert_called_once_with() self.assertEqual(self.columns_long, columns) - self.assertListItemEqual(self.data_long, list(data)) + self.assertItemsEqual(self.data_long, list(data)) def test_router_list_long_no_az(self): arglist = [ @@ -552,7 +552,7 @@ def test_router_list_long_no_az(self): self.network.routers.assert_called_once_with() self.assertEqual(self.columns_long_no_az, columns) - self.assertListItemEqual(self.data_long_no_az, list(data)) + self.assertItemsEqual(self.data_long_no_az, list(data)) def test_list_name(self): test_name = "fakename" @@ -570,7 +570,7 @@ def test_list_name(self): **{'name': test_name} ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_router_list_enable(self): arglist = [ @@ -587,7 +587,7 @@ def test_router_list_enable(self): **{'admin_state_up': True, 'is_admin_state_up': True} ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_router_list_disable(self): arglist = [ @@ -605,7 +605,7 @@ def test_router_list_disable(self): ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_router_list_project(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -623,7 +623,7 @@ def test_router_list_project(self): self.network.routers.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_router_list_project_domain(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -643,7 +643,7 @@ def test_router_list_project_domain(self): self.network.routers.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_router_list_agents_no_args(self): arglist = [ @@ -671,7 +671,7 @@ def test_router_list_agents(self): self.network.agent_hosted_routers( *attrs) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_list_with_tag_options(self): arglist = [ @@ -696,7 +696,7 @@ def test_list_with_tag_options(self): 'not_any_tags': 'black,white'} ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) class TestRemovePortFromRouter(TestRouter): @@ -1403,7 +1403,7 @@ def test_show_all_options(self): 'device_id': self._router.id }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_show_no_ha_no_distributed(self): _router = network_fakes.FakeRouter.create_one_router({ diff --git a/openstackclient/tests/unit/network/v2/test_security_group_compute.py b/openstackclient/tests/unit/network/v2/test_security_group_compute.py index b4ddcf803d..837c9b21af 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_compute.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_compute.py @@ -88,7 +88,7 @@ def test_security_group_create_min_options(self, sg_mock): self._security_group['name'], ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_security_group_create_all_options(self, sg_mock): sg_mock.return_value = self._security_group @@ -109,7 +109,7 @@ def test_security_group_create_all_options(self, sg_mock): self._security_group['description'], ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) @mock.patch( @@ -255,7 +255,7 @@ def test_security_group_list_no_options(self, sg_mock): kwargs = {'search_opts': {'all_tenants': False}} sg_mock.assert_called_once_with(**kwargs) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_security_group_list_all_projects(self, sg_mock): sg_mock.return_value = self._security_groups @@ -272,7 +272,7 @@ def test_security_group_list_all_projects(self, sg_mock): kwargs = {'search_opts': {'all_tenants': True}} sg_mock.assert_called_once_with(**kwargs) self.assertEqual(self.columns_all_projects, columns) - self.assertListItemEqual(self.data_all_projects, list(data)) + self.assertItemsEqual(self.data_all_projects, list(data)) @mock.patch( @@ -401,4 +401,4 @@ def test_security_group_show_all_options(self, sg_mock): sg_mock.assert_called_once_with(self._security_group['id']) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) diff --git a/openstackclient/tests/unit/network/v2/test_security_group_network.py b/openstackclient/tests/unit/network/v2/test_security_group_network.py index 7c1d7fb6b0..fe37778598 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_network.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_network.py @@ -96,7 +96,7 @@ def test_create_min_options(self): 'name': self._security_group.name, }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_all_options(self): arglist = [ @@ -124,7 +124,7 @@ def test_create_all_options(self): 'tenant_id': self.project.id, }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def _test_create_with_tag(self, add_tags=True): arglist = [self._security_group.name] @@ -155,7 +155,7 @@ def _test_create_with_tag(self, add_tags=True): else: self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_with_tags(self): self._test_create_with_tag(add_tags=True) @@ -293,7 +293,7 @@ def test_security_group_list_no_options(self): self.network.security_groups.assert_called_once_with( fields=security_group.ListSecurityGroup.FIELDS_TO_RETRIEVE) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_security_group_list_all_projects(self): arglist = [ @@ -309,7 +309,7 @@ def test_security_group_list_all_projects(self): self.network.security_groups.assert_called_once_with( fields=security_group.ListSecurityGroup.FIELDS_TO_RETRIEVE) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_security_group_list_project(self): project = identity_fakes.FakeProject.create_one_project() @@ -329,7 +329,7 @@ def test_security_group_list_project(self): self.network.security_groups.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_security_group_list_project_domain(self): project = identity_fakes.FakeProject.create_one_project() @@ -351,7 +351,7 @@ def test_security_group_list_project_domain(self): self.network.security_groups.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_list_with_tag_options(self): arglist = [ @@ -539,7 +539,7 @@ def test_show_all_options(self): self.network.find_security_group.assert_called_once_with( self._security_group.id, ignore_missing=False) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) class TestUnsetSecurityGroupNetwork(TestSecurityGroupNetwork): diff --git a/openstackclient/tests/unit/network/v2/test_subnet.py b/openstackclient/tests/unit/network/v2/test_subnet.py index 47d0c6b47f..1b4bfdad2f 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet.py +++ b/openstackclient/tests/unit/network/v2/test_subnet.py @@ -255,7 +255,7 @@ def test_create_default_options(self): }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_from_subnet_pool_options(self): # Mock SDK calls for this test. @@ -317,7 +317,7 @@ def test_create_from_subnet_pool_options(self): 'service_types': self._subnet_from_pool.service_types, }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data_subnet_pool, data) + self.assertItemsEqual(self.data_subnet_pool, data) def test_create_options_subnet_range_ipv6(self): # Mock SDK calls for this test. @@ -390,7 +390,7 @@ def test_create_options_subnet_range_ipv6(self): }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data_ipv6, data) + self.assertItemsEqual(self.data_ipv6, data) def test_create_with_network_segment(self): # Mock SDK calls for this test. @@ -424,7 +424,7 @@ def test_create_with_network_segment(self): }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_with_description(self): # Mock SDK calls for this test. @@ -458,7 +458,7 @@ def test_create_with_description(self): }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def _test_create_with_dns(self, publish_dns=True): arglist = [ @@ -490,7 +490,7 @@ def _test_create_with_dns(self, publish_dns=True): dns_publish_fixed_ip=publish_dns, ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_with_dns(self): self._test_create_with_dns(publish_dns=True) @@ -535,7 +535,7 @@ def _test_create_with_tag(self, add_tags=True): else: self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_with_tags(self): self._test_create_with_tag(add_tags=True) @@ -691,7 +691,7 @@ def test_subnet_list_no_options(self): self.network.subnets.assert_called_once_with() self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_subnet_list_long(self): arglist = [ @@ -706,7 +706,7 @@ def test_subnet_list_long(self): self.network.subnets.assert_called_once_with() self.assertEqual(self.columns_long, columns) - self.assertListItemEqual(self.data_long, list(data)) + self.assertItemsEqual(self.data_long, list(data)) def test_subnet_list_ip_version(self): arglist = [ @@ -722,7 +722,7 @@ def test_subnet_list_ip_version(self): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_subnet_list_dhcp(self): arglist = [ @@ -738,7 +738,7 @@ def test_subnet_list_dhcp(self): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_subnet_list_no_dhcp(self): arglist = [ @@ -754,7 +754,7 @@ def test_subnet_list_no_dhcp(self): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_subnet_list_service_type(self): arglist = [ @@ -769,7 +769,7 @@ def test_subnet_list_service_type(self): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_subnet_list_project(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -787,7 +787,7 @@ def test_subnet_list_project(self): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_subnet_list_service_type_multiple(self): arglist = [ @@ -805,7 +805,7 @@ def test_subnet_list_service_type_multiple(self): 'network:floatingip_agent_gateway']} self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_subnet_list_project_domain(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -825,7 +825,7 @@ def test_subnet_list_project_domain(self): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_subnet_list_network(self): network = network_fakes.FakeNetwork.create_one_network() @@ -843,7 +843,7 @@ def test_subnet_list_network(self): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_subnet_list_gateway(self): subnet = network_fakes.FakeSubnet.create_one_subnet() @@ -861,7 +861,7 @@ def test_subnet_list_gateway(self): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_subnet_list_name(self): subnet = network_fakes.FakeSubnet.create_one_subnet() @@ -879,7 +879,7 @@ def test_subnet_list_name(self): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_subnet_list_subnet_range(self): subnet = network_fakes.FakeSubnet.create_one_subnet() @@ -897,7 +897,7 @@ def test_subnet_list_subnet_range(self): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_list_with_tag_options(self): arglist = [ @@ -1244,7 +1244,7 @@ def test_show_all_options(self): self._subnet.name, ignore_missing=False) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) class TestUnsetSubnet(TestSubnet): diff --git a/openstackclient/tests/unit/network/v2/test_subnet_pool.py b/openstackclient/tests/unit/network/v2/test_subnet_pool.py index eb454646de..243fc76df4 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/unit/network/v2/test_subnet_pool.py @@ -133,7 +133,7 @@ def test_create_default_options(self): }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_prefixlen_options(self): arglist = [ @@ -163,7 +163,7 @@ def test_create_prefixlen_options(self): 'name': self._subnet_pool.name, }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_len_negative(self): arglist = [ @@ -201,7 +201,7 @@ def test_create_project_domain(self): 'name': self._subnet_pool.name, }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_address_scope_option(self): arglist = [ @@ -224,7 +224,7 @@ def test_create_address_scope_option(self): 'name': self._subnet_pool.name, }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_default_and_shared_options(self): arglist = [ @@ -250,7 +250,7 @@ def test_create_default_and_shared_options(self): 'shared': True, }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_with_description(self): arglist = [ @@ -273,7 +273,7 @@ def test_create_with_description(self): 'description': self._subnet_pool.description, }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_with_default_quota(self): arglist = [ @@ -294,7 +294,7 @@ def test_create_with_default_quota(self): 'default_quota': 10, }) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def _test_create_with_tag(self, add_tags=True): arglist = [ @@ -328,7 +328,7 @@ def _test_create_with_tag(self, add_tags=True): else: self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_create_with_tags(self): self._test_create_with_tag(add_tags=True) @@ -476,7 +476,7 @@ def test_subnet_pool_list_no_option(self): self.network.subnet_pools.assert_called_once_with() self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_subnet_pool_list_long(self): arglist = [ @@ -491,7 +491,7 @@ def test_subnet_pool_list_long(self): self.network.subnet_pools.assert_called_once_with() self.assertEqual(self.columns_long, columns) - self.assertListItemEqual(self.data_long, list(data)) + self.assertItemsEqual(self.data_long, list(data)) def test_subnet_pool_list_no_share(self): arglist = [ @@ -507,7 +507,7 @@ def test_subnet_pool_list_no_share(self): self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_subnet_pool_list_share(self): arglist = [ @@ -523,7 +523,7 @@ def test_subnet_pool_list_share(self): self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_subnet_pool_list_no_default(self): arglist = [ @@ -539,7 +539,7 @@ def test_subnet_pool_list_no_default(self): self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_subnet_pool_list_default(self): arglist = [ @@ -555,7 +555,7 @@ def test_subnet_pool_list_default(self): self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_subnet_pool_list_project(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -573,7 +573,7 @@ def test_subnet_pool_list_project(self): self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_subnet_pool_list_project_domain(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -593,7 +593,7 @@ def test_subnet_pool_list_project_domain(self): self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_subnet_pool_list_name(self): subnet_pool = network_fakes.FakeSubnetPool.create_one_subnet_pool() @@ -611,7 +611,7 @@ def test_subnet_pool_list_name(self): self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_subnet_pool_list_address_scope(self): addr_scope = network_fakes.FakeAddressScope.create_one_address_scope() @@ -629,7 +629,7 @@ def test_subnet_pool_list_address_scope(self): self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_list_with_tag_options(self): arglist = [ @@ -654,7 +654,7 @@ def test_list_with_tag_options(self): 'not_any_tags': 'black,white'} ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) class TestSetSubnetPool(TestSubnetPool): @@ -1008,7 +1008,7 @@ def test_show_all_options(self): ignore_missing=False ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) class TestUnsetSubnetPool(TestSubnetPool): diff --git a/openstackclient/tests/unit/utils.py b/openstackclient/tests/unit/utils.py index 4130f18e2b..39cb561474 100644 --- a/openstackclient/tests/unit/utils.py +++ b/openstackclient/tests/unit/utils.py @@ -17,7 +17,6 @@ from io import StringIO import os -from cliff import columns as cliff_columns import fixtures import testtools @@ -85,18 +84,3 @@ def check_parser(self, cmd, args, verify_args): self.assertIn(attr, parsed_args) self.assertEqual(value, getattr(parsed_args, attr)) return parsed_args - - def assertListItemEqual(self, expected, actual): - self.assertEqual(len(expected), len(actual)) - for item_expected, item_actual in zip(expected, actual): - self.assertItemEqual(item_expected, item_actual) - - def assertItemEqual(self, expected, actual): - self.assertEqual(len(expected), len(actual)) - for col_expected, col_actual in zip(expected, actual): - if isinstance(col_expected, cliff_columns.FormattableColumn): - self.assertIsInstance(col_actual, col_expected.__class__) - self.assertEqual(col_expected.human_readable(), - col_actual.human_readable()) - else: - self.assertEqual(col_expected, col_actual) diff --git a/openstackclient/tests/unit/volume/v1/test_qos_specs.py b/openstackclient/tests/unit/volume/v1/test_qos_specs.py index 83c533b6f0..5500438bd6 100644 --- a/openstackclient/tests/unit/volume/v1/test_qos_specs.py +++ b/openstackclient/tests/unit/volume/v1/test_qos_specs.py @@ -109,7 +109,7 @@ def test_qos_create_without_properties(self): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) def test_qos_create_with_consumer(self): arglist = [ @@ -129,7 +129,7 @@ def test_qos_create_with_consumer(self): {'consumer': self.new_qos_spec.consumer} ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) def test_qos_create_with_properties(self): arglist = [ @@ -155,7 +155,7 @@ def test_qos_create_with_properties(self): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) class TestQosDelete(TestQos): @@ -350,7 +350,7 @@ def test_qos_list(self): self.qos_mock.list.assert_called_with() self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_qos_list_no_association(self): self.qos_mock.reset_mock() @@ -377,7 +377,7 @@ def test_qos_list_no_association(self): format_columns.ListColumn(None), format_columns.DictColumn(self.qos_specs[1].specs), ) - self.assertListItemEqual(ex_data, list(data)) + self.assertItemsEqual(ex_data, list(data)) class TestQosSet(TestQos): @@ -454,7 +454,7 @@ def test_qos_show(self): self.qos_spec.name, format_columns.DictColumn(self.qos_spec.specs), ) - self.assertItemEqual(datalist, tuple(data)) + self.assertItemsEqual(datalist, tuple(data)) class TestQosUnset(TestQos): diff --git a/openstackclient/tests/unit/volume/v1/test_type.py b/openstackclient/tests/unit/volume/v1/test_type.py index 8bee574795..f1d469140b 100644 --- a/openstackclient/tests/unit/volume/v1/test_type.py +++ b/openstackclient/tests/unit/volume/v1/test_type.py @@ -78,7 +78,7 @@ def test_type_create(self): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_type_create_with_encryption(self): encryption_info = { @@ -139,7 +139,7 @@ def test_type_create_with_encryption(self): body, ) self.assertEqual(encryption_columns, columns) - self.assertItemEqual(encryption_data, data) + self.assertItemsEqual(encryption_data, data) class TestTypeDelete(TestType): @@ -270,7 +270,7 @@ def test_type_list_without_options(self): columns, data = self.cmd.take_action(parsed_args) self.types_mock.list.assert_called_once_with() self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_type_list_with_options(self): arglist = [ @@ -284,7 +284,7 @@ def test_type_list_with_options(self): columns, data = self.cmd.take_action(parsed_args) self.types_mock.list.assert_called_once_with() self.assertEqual(self.columns_long, columns) - self.assertListItemEqual(self.data_long, list(data)) + self.assertItemsEqual(self.data_long, list(data)) def test_type_list_with_encryption(self): encryption_type = volume_fakes.FakeType.create_one_encryption_type( @@ -328,7 +328,7 @@ def test_type_list_with_encryption(self): self.encryption_types_mock.list.assert_called_once_with() self.types_mock.list.assert_called_once_with() self.assertEqual(encryption_columns, columns) - self.assertListItemEqual(encryption_data, list(data)) + self.assertItemsEqual(encryption_data, list(data)) class TestTypeSet(TestType): @@ -469,7 +469,7 @@ def test_type_show(self): self.types_mock.get.assert_called_with(self.volume_type.id) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_type_show_with_encryption(self): encryption_type = volume_fakes.FakeType.create_one_encryption_type() @@ -513,7 +513,7 @@ def test_type_show_with_encryption(self): self.types_mock.get.assert_called_with(self.volume_type.id) self.encryption_types_mock.get.assert_called_with(self.volume_type.id) self.assertEqual(encryption_columns, columns) - self.assertItemEqual(encryption_data, data) + self.assertItemsEqual(encryption_data, data) class TestTypeUnset(TestType): diff --git a/openstackclient/tests/unit/volume/v1/test_volume.py b/openstackclient/tests/unit/volume/v1/test_volume.py index 25cdf92a43..704a66da71 100644 --- a/openstackclient/tests/unit/volume/v1/test_volume.py +++ b/openstackclient/tests/unit/volume/v1/test_volume.py @@ -135,7 +135,7 @@ def test_volume_create_min_options(self): None, ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) def test_volume_create_options(self): arglist = [ @@ -179,7 +179,7 @@ def test_volume_create_options(self): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) def test_volume_create_user_project_id(self): # Return a project @@ -226,7 +226,7 @@ def test_volume_create_user_project_id(self): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) def test_volume_create_user_project_name(self): # Return a project @@ -273,7 +273,7 @@ def test_volume_create_user_project_name(self): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) def test_volume_create_properties(self): arglist = [ @@ -314,7 +314,7 @@ def test_volume_create_properties(self): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) def test_volume_create_image_id(self): image = image_fakes.FakeImage.create_one_image() @@ -357,7 +357,7 @@ def test_volume_create_image_id(self): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) def test_volume_create_image_name(self): image = image_fakes.FakeImage.create_one_image() @@ -400,7 +400,7 @@ def test_volume_create_image_name(self): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) def test_volume_create_with_source(self): self.volumes_mock.get.return_value = self.new_volume @@ -430,7 +430,7 @@ def test_volume_create_with_source(self): None, ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) def test_volume_create_with_bootable_and_readonly(self): arglist = [ @@ -468,7 +468,7 @@ def test_volume_create_with_bootable_and_readonly(self): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(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( @@ -510,7 +510,7 @@ def test_volume_create_with_nonbootable_and_readwrite(self): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(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( @@ -562,7 +562,7 @@ def test_volume_create_with_bootable_and_readonly_fail( self.assertEqual(2, mock_error.call_count) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(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( @@ -765,7 +765,7 @@ def test_volume_list_no_options(self): columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.datalist, tuple(data)) + self.assertItemsEqual(self.datalist, tuple(data)) def test_volume_list_name(self): arglist = [ @@ -782,7 +782,7 @@ def test_volume_list_name(self): columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, tuple(columns)) - self.assertListItemEqual(self.datalist, tuple(data)) + self.assertItemsEqual(self.datalist, tuple(data)) def test_volume_list_status(self): arglist = [ @@ -799,7 +799,7 @@ def test_volume_list_status(self): columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, tuple(columns)) - self.assertListItemEqual(self.datalist, tuple(data)) + self.assertItemsEqual(self.datalist, tuple(data)) def test_volume_list_all_projects(self): arglist = [ @@ -816,7 +816,7 @@ def test_volume_list_all_projects(self): columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, tuple(columns)) - self.assertListItemEqual(self.datalist, tuple(data)) + self.assertItemsEqual(self.datalist, tuple(data)) def test_volume_list_long(self): arglist = [ @@ -856,7 +856,7 @@ def test_volume_list_long(self): volume.AttachmentsColumn(self._volume.attachments), format_columns.DictColumn(self._volume.metadata), ), ) - self.assertListItemEqual(datalist, tuple(data)) + self.assertItemsEqual(datalist, tuple(data)) def test_volume_list_with_limit(self): arglist = [ @@ -881,7 +881,7 @@ def test_volume_list_with_limit(self): 'all_tenants': False, } ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.datalist, tuple(data)) + self.assertItemsEqual(self.datalist, tuple(data)) def test_volume_list_negative_limit(self): arglist = [ @@ -1272,7 +1272,7 @@ def test_volume_show(self): self.volumes_mock.get.assert_called_with(self._volume.id) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) def test_volume_show_backward_compatibility(self): arglist = [ diff --git a/openstackclient/tests/unit/volume/v1/test_volume_backup.py b/openstackclient/tests/unit/volume/v1/test_volume_backup.py index 20aadcd344..a713155092 100644 --- a/openstackclient/tests/unit/volume/v1/test_volume_backup.py +++ b/openstackclient/tests/unit/volume/v1/test_volume_backup.py @@ -100,7 +100,7 @@ def test_backup_create(self): self.new_backup.description, ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_backup_create_without_name(self): arglist = [ @@ -124,7 +124,7 @@ def test_backup_create_without_name(self): self.new_backup.description, ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) class TestBackupDelete(TestBackup): @@ -277,7 +277,7 @@ def test_backup_list_without_options(self): search_opts=search_opts, ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_backup_list_with_options(self): arglist = [ @@ -309,7 +309,7 @@ def test_backup_list_with_options(self): search_opts=search_opts, ) self.assertEqual(self.columns_long, columns) - self.assertListItemEqual(self.data_long, list(data)) + self.assertItemsEqual(self.data_long, list(data)) class TestBackupRestore(TestBackup): @@ -391,4 +391,4 @@ def test_backup_show(self): self.backups_mock.get.assert_called_with(self.backup.id) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) diff --git a/openstackclient/tests/unit/volume/v2/test_consistency_group.py b/openstackclient/tests/unit/volume/v2/test_consistency_group.py index c3bd71e325..6bb6c02978 100644 --- a/openstackclient/tests/unit/volume/v2/test_consistency_group.py +++ b/openstackclient/tests/unit/volume/v2/test_consistency_group.py @@ -251,7 +251,7 @@ def test_consistency_group_create_without_name(self): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_consistency_group_create_from_source(self): arglist = [ @@ -279,7 +279,7 @@ def test_consistency_group_create_from_source(self): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_consistency_group_create_from_snapshot(self): arglist = [ @@ -307,7 +307,7 @@ def test_consistency_group_create_from_snapshot(self): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) class TestConsistencyGroupDelete(TestConsistencyGroup): @@ -463,7 +463,7 @@ def test_consistency_group_list_without_options(self): self.consistencygroups_mock.list.assert_called_once_with( detailed=True, search_opts={'all_tenants': False}) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_consistency_group_list_with_all_project(self): arglist = [ @@ -480,7 +480,7 @@ def test_consistency_group_list_with_all_project(self): self.consistencygroups_mock.list.assert_called_once_with( detailed=True, search_opts={'all_tenants': True}) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_consistency_group_list_with_long(self): arglist = [ @@ -497,7 +497,7 @@ def test_consistency_group_list_with_long(self): self.consistencygroups_mock.list.assert_called_once_with( detailed=True, search_opts={'all_tenants': False}) self.assertEqual(self.columns_long, columns) - self.assertListItemEqual(self.data_long, list(data)) + self.assertItemsEqual(self.data_long, list(data)) class TestConsistencyGroupRemoveVolume(TestConsistencyGroup): @@ -705,4 +705,4 @@ def test_consistency_group_show(self): self.consistencygroups_mock.get.assert_called_once_with( self.consistency_group.id) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) diff --git a/openstackclient/tests/unit/volume/v2/test_qos_specs.py b/openstackclient/tests/unit/volume/v2/test_qos_specs.py index 073ec5709b..bc4cee8b40 100644 --- a/openstackclient/tests/unit/volume/v2/test_qos_specs.py +++ b/openstackclient/tests/unit/volume/v2/test_qos_specs.py @@ -112,7 +112,7 @@ def test_qos_create_without_properties(self): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_qos_create_with_consumer(self): arglist = [ @@ -133,7 +133,7 @@ def test_qos_create_with_consumer(self): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_qos_create_with_properties(self): arglist = [ @@ -159,7 +159,7 @@ def test_qos_create_with_properties(self): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) class TestQosDelete(TestQos): @@ -342,7 +342,7 @@ def test_qos_list(self): self.qos_mock.list.assert_called_with() self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_qos_list_no_association(self): self.qos_mock.reset_mock() @@ -369,7 +369,7 @@ def test_qos_list_no_association(self): format_columns.ListColumn(None), format_columns.DictColumn(self.qos_specs[1].specs), ) - self.assertListItemEqual(ex_data, list(data)) + self.assertItemsEqual(ex_data, list(data)) class TestQosSet(TestQos): @@ -449,7 +449,7 @@ def test_qos_show(self): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, tuple(data)) + self.assertItemsEqual(self.data, tuple(data)) class TestQosUnset(TestQos): diff --git a/openstackclient/tests/unit/volume/v2/test_type.py b/openstackclient/tests/unit/volume/v2/test_type.py index f13d08517a..000464c5d8 100644 --- a/openstackclient/tests/unit/volume/v2/test_type.py +++ b/openstackclient/tests/unit/volume/v2/test_type.py @@ -93,7 +93,7 @@ def test_type_create_public(self): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_type_create_private(self): arglist = [ @@ -119,7 +119,7 @@ def test_type_create_private(self): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_public_type_create_with_project(self): arglist = [ @@ -196,7 +196,7 @@ def test_type_create_with_encryption(self): body, ) self.assertEqual(encryption_columns, columns) - self.assertItemEqual(encryption_data, data) + self.assertItemsEqual(encryption_data, data) class TestTypeDelete(TestType): @@ -330,7 +330,7 @@ def test_type_list_without_options(self): columns, data = self.cmd.take_action(parsed_args) self.types_mock.list.assert_called_once_with(is_public=None) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_type_list_with_options(self): arglist = [ @@ -348,7 +348,7 @@ def test_type_list_with_options(self): columns, data = self.cmd.take_action(parsed_args) self.types_mock.list.assert_called_once_with(is_public=True) self.assertEqual(self.columns_long, columns) - self.assertListItemEqual(self.data_long, list(data)) + self.assertItemsEqual(self.data_long, list(data)) def test_type_list_with_private_option(self): arglist = [ @@ -365,7 +365,7 @@ def test_type_list_with_private_option(self): columns, data = self.cmd.take_action(parsed_args) self.types_mock.list.assert_called_once_with(is_public=False) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_type_list_with_default_option(self): arglist = [ @@ -383,7 +383,7 @@ def test_type_list_with_default_option(self): columns, data = self.cmd.take_action(parsed_args) self.types_mock.default.assert_called_once_with() self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data_with_default_type, list(data)) + self.assertItemsEqual(self.data_with_default_type, list(data)) def test_type_list_with_encryption(self): encryption_type = volume_fakes.FakeType.create_one_encryption_type( @@ -427,7 +427,7 @@ def test_type_list_with_encryption(self): self.encryption_types_mock.list.assert_called_once_with() self.types_mock.list.assert_called_once_with(is_public=None) self.assertEqual(encryption_columns, columns) - self.assertListItemEqual(encryption_data, list(data)) + self.assertItemsEqual(encryption_data, list(data)) class TestTypeSet(TestType): @@ -713,7 +713,7 @@ def test_type_show(self): self.types_mock.get.assert_called_with(self.volume_type.id) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.data, data) + self.assertItemsEqual(self.data, data) def test_type_show_with_access(self): arglist = [ @@ -746,7 +746,7 @@ def test_type_show_with_access(self): private_type.name, format_columns.DictColumn(private_type.extra_specs) ) - self.assertItemEqual(private_type_data, data) + self.assertItemsEqual(private_type_data, data) def test_type_show_with_list_access_exec(self): arglist = [ @@ -778,7 +778,7 @@ def test_type_show_with_list_access_exec(self): private_type.name, format_columns.DictColumn(private_type.extra_specs) ) - self.assertItemEqual(private_type_data, data) + self.assertItemsEqual(private_type_data, data) def test_type_show_with_encryption(self): encryption_type = volume_fakes.FakeType.create_one_encryption_type() @@ -824,7 +824,7 @@ def test_type_show_with_encryption(self): self.types_mock.get.assert_called_with(self.volume_type.id) self.encryption_types_mock.get.assert_called_with(self.volume_type.id) self.assertEqual(encryption_columns, columns) - self.assertItemEqual(encryption_data, data) + self.assertItemsEqual(encryption_data, data) class TestTypeUnset(TestType): diff --git a/openstackclient/tests/unit/volume/v2/test_volume.py b/openstackclient/tests/unit/volume/v2/test_volume.py index 4e204ad1b2..b9fe4e834c 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume.py +++ b/openstackclient/tests/unit/volume/v2/test_volume.py @@ -136,7 +136,7 @@ def test_volume_create_min_options(self): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) def test_volume_create_options(self): consistency_group = ( @@ -182,7 +182,7 @@ def test_volume_create_options(self): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) def test_volume_create_properties(self): arglist = [ @@ -218,7 +218,7 @@ def test_volume_create_properties(self): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) def test_volume_create_image_id(self): image = image_fakes.FakeImage.create_one_image() @@ -256,7 +256,7 @@ def test_volume_create_image_id(self): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) def test_volume_create_image_name(self): image = image_fakes.FakeImage.create_one_image() @@ -294,7 +294,7 @@ def test_volume_create_image_name(self): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) def test_volume_create_with_snapshot(self): snapshot = volume_fakes.FakeSnapshot.create_one_snapshot() @@ -331,7 +331,7 @@ def test_volume_create_with_snapshot(self): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(self.datalist, data) def test_volume_create_with_bootable_and_readonly(self): arglist = [ @@ -369,7 +369,7 @@ def test_volume_create_with_bootable_and_readonly(self): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(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( @@ -411,7 +411,7 @@ def test_volume_create_with_nonbootable_and_readwrite(self): ) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(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( @@ -463,7 +463,7 @@ def test_volume_create_with_bootable_and_readonly_fail( self.assertEqual(2, mock_error.call_count) self.assertEqual(self.columns, columns) - self.assertItemEqual(self.datalist, data) + self.assertItemsEqual(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( @@ -680,7 +680,7 @@ def test_volume_list_no_options(self): self.mock_volume.size, volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertListItemEqual(datalist, tuple(data)) + self.assertItemsEqual(datalist, tuple(data)) def test_volume_list_project(self): arglist = [ @@ -720,7 +720,7 @@ def test_volume_list_project(self): self.mock_volume.size, volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertListItemEqual(datalist, tuple(data)) + self.assertItemsEqual(datalist, tuple(data)) def test_volume_list_project_domain(self): arglist = [ @@ -762,7 +762,7 @@ def test_volume_list_project_domain(self): self.mock_volume.size, volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertListItemEqual(datalist, tuple(data)) + self.assertItemsEqual(datalist, tuple(data)) def test_volume_list_user(self): arglist = [ @@ -801,7 +801,7 @@ def test_volume_list_user(self): self.mock_volume.size, volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertListItemEqual(datalist, tuple(data)) + self.assertItemsEqual(datalist, tuple(data)) def test_volume_list_user_domain(self): arglist = [ @@ -843,7 +843,7 @@ def test_volume_list_user_domain(self): self.mock_volume.size, volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertListItemEqual(datalist, tuple(data)) + self.assertItemsEqual(datalist, tuple(data)) def test_volume_list_name(self): arglist = [ @@ -883,7 +883,7 @@ def test_volume_list_name(self): self.mock_volume.size, volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertListItemEqual(datalist, tuple(data)) + self.assertItemsEqual(datalist, tuple(data)) def test_volume_list_status(self): arglist = [ @@ -923,7 +923,7 @@ def test_volume_list_status(self): self.mock_volume.size, volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertListItemEqual(datalist, tuple(data)) + self.assertItemsEqual(datalist, tuple(data)) def test_volume_list_all_projects(self): arglist = [ @@ -963,7 +963,7 @@ def test_volume_list_all_projects(self): self.mock_volume.size, volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertListItemEqual(datalist, tuple(data)) + self.assertItemsEqual(datalist, tuple(data)) def test_volume_list_long(self): arglist = [ @@ -1017,7 +1017,7 @@ def test_volume_list_long(self): volume.AttachmentsColumn(self.mock_volume.attachments), format_columns.DictColumn(self.mock_volume.metadata), ), ) - self.assertListItemEqual(datalist, tuple(data)) + self.assertItemsEqual(datalist, tuple(data)) def test_volume_list_with_marker_and_limit(self): arglist = [ @@ -1056,7 +1056,7 @@ def test_volume_list_with_marker_and_limit(self): 'name': None, 'all_tenants': False, } ) - self.assertListItemEqual(datalist, tuple(data)) + self.assertItemsEqual(datalist, tuple(data)) def test_volume_list_negative_limit(self): arglist = [ @@ -1450,7 +1450,7 @@ def test_volume_show(self): volume_fakes.FakeVolume.get_volume_columns(self._volume), columns) - self.assertItemEqual( + self.assertItemsEqual( volume_fakes.FakeVolume.get_volume_data(self._volume), data) diff --git a/openstackclient/tests/unit/volume/v2/test_volume_backup.py b/openstackclient/tests/unit/volume/v2/test_volume_backup.py index 4e1f7ee135..13513ed8ad 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume_backup.py +++ b/openstackclient/tests/unit/volume/v2/test_volume_backup.py @@ -314,7 +314,7 @@ def test_backup_list_without_options(self): limit=None, ) self.assertEqual(self.columns, columns) - self.assertListItemEqual(self.data, list(data)) + self.assertItemsEqual(self.data, list(data)) def test_backup_list_with_options(self): arglist = [ @@ -353,7 +353,7 @@ def test_backup_list_with_options(self): limit=3, ) self.assertEqual(self.columns_long, columns) - self.assertListItemEqual(self.data_long, list(data)) + self.assertItemsEqual(self.data_long, list(data)) class TestBackupRestore(TestBackup): diff --git a/requirements.txt b/requirements.txt index b167628b51..ed55ce3794 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,10 +3,10 @@ # process, which may cause wedges in the gate later. pbr!=2.1.0,>=2.0.0 # Apache-2.0 -cliff>=3.4.0 # Apache-2.0 +cliff>=3.5.0 # Apache-2.0 iso8601>=0.1.11 # MIT openstacksdk>=0.52.0 # Apache-2.0 -osc-lib>=2.2.0 # Apache-2.0 +osc-lib>=2.3.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 python-keystoneclient>=3.22.0 # Apache-2.0 python-novaclient>=15.1.0 # Apache-2.0 From da03bd80e3b83faf465f1446c4553c5d97b5bad5 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 14 Oct 2020 10:48:44 +0100 Subject: [PATCH 2300/3095] Add 'flavor list --min-disk', '--min-ram' options Allow us to filter on minimum disk and RAM, and close another gap with novaclient. Change-Id: Ib3f0bdf419675e1c35c3406fbac8a4c18ac56a33 Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/flavor.py | 43 ++++++++++++++++--- .../tests/unit/compute/v2/test_flavor.py | 31 +++++++++++++ ...ist-min-disk-min-ram-65ba35e7acc24134.yaml | 6 +++ 3 files changed, 73 insertions(+), 7 deletions(-) create mode 100644 releasenotes/notes/flavor-list-min-disk-min-ram-65ba35e7acc24134.yaml diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 8477e8efb1..fa98e131b1 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -263,6 +263,18 @@ def get_parser(self, prog_name): default=False, help=_("List all flavors, whether public or private") ) + parser.add_argument( + '--min-disk', + type=int, + metavar='', + help=_('Filters the flavors by a minimum disk space, in GiB.'), + ) + parser.add_argument( + '--min-ram', + type=int, + metavar='', + help=_('Filters the flavors by a minimum RAM, in MiB.'), + ) parser.add_argument( '--long', action='store_true', @@ -277,8 +289,13 @@ def get_parser(self, prog_name): parser.add_argument( '--limit', type=int, - metavar="", - help=_("Maximum number of flavors to display") + metavar='', + help=_( + 'Maximum number of flavors to display. This is also ' + 'configurable on the server. The actual limit used will be ' + 'the lower of the user-supplied value and the server ' + 'configuration-derived value' + ), ) return parser @@ -293,15 +310,24 @@ def take_action(self, parsed_args): query_attrs = { 'is_public': is_public } + if parsed_args.marker: query_attrs['marker'] = parsed_args.marker + if parsed_args.limit: query_attrs['limit'] = parsed_args.limit + if parsed_args.limit or parsed_args.marker: # User passed explicit pagination request, switch off SDK # pagination query_attrs['paginated'] = False + if parsed_args.min_disk: + query_attrs['min_disk'] = parsed_args.min_disk + + if parsed_args.min_ram: + query_attrs['min_ram'] = parsed_args.min_ram + data = list(compute_client.flavors(**query_attrs)) # Even if server supports 2.61 some policy might stop it sending us # extra_specs. So try to fetch them if they are absent @@ -341,10 +367,13 @@ def take_action(self, parsed_args): "Properties", ) - return (column_headers, - (utils.get_item_properties( - s, columns, formatters=_formatters, - ) for s in data)) + return ( + column_headers, + ( + utils.get_item_properties(s, columns, formatters=_formatters) + for s in data + ), + ) class SetFlavor(command.Command): @@ -378,13 +407,13 @@ def get_parser(self, prog_name): help=_('Set flavor access to project (name or ID) ' '(admin only)'), ) + identity_common.add_project_domain_option_to_parser(parser) parser.add_argument( '--description', metavar='', help=_("Set description for the flavor.(Supported by API " "versions '2.55' - '2.latest'") ) - identity_common.add_project_domain_option_to_parser(parser) return parser diff --git a/openstackclient/tests/unit/compute/v2/test_flavor.py b/openstackclient/tests/unit/compute/v2/test_flavor.py index 8625b71202..af9e4a2c33 100644 --- a/openstackclient/tests/unit/compute/v2/test_flavor.py +++ b/openstackclient/tests/unit/compute/v2/test_flavor.py @@ -635,6 +635,37 @@ def test_flavor_list_long(self): self.assertEqual(self.columns_long, columns) self.assertListItemEqual(self.data_long, tuple(data)) + def test_flavor_list_min_disk_min_ram(self): + arglist = [ + '--min-disk', '10', + '--min-ram', '2048', + ] + verifylist = [ + ('min_disk', 10), + ('min_ram', 2048), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'is_public': True, + 'min_disk': 10, + 'min_ram': 2048, + } + + self.sdk_client.flavors.assert_called_with( + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(tuple(self.data), tuple(data)) + class TestFlavorSet(TestFlavor): diff --git a/releasenotes/notes/flavor-list-min-disk-min-ram-65ba35e7acc24134.yaml b/releasenotes/notes/flavor-list-min-disk-min-ram-65ba35e7acc24134.yaml new file mode 100644 index 0000000000..3418276c89 --- /dev/null +++ b/releasenotes/notes/flavor-list-min-disk-min-ram-65ba35e7acc24134.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + The ``openstack flavor list`` command now accepts two additional + options, ``--min-disk`` and ``--min-ram``, to filter flavor by + minimum disk and minimum RAM, respectively. From f9fd3642f8e8c59cd5a819e260d0575f884ae174 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 18 Nov 2020 14:29:24 +0000 Subject: [PATCH 2301/3095] compute: Add missing options for 'server rebuild' This accepts a large number of options that we weren't exposing. Add the following options: '--name', '--preserve-ephemeral', '--user-data', '--no-user-data', '--trusted-image-cert' and '--no-trusted-image-certs'. In addition, rename the '--key-unset' parameter to '--no-key-name', to mimic e.g. '--no-property' on other commands. Change-Id: I61c46e18bef1f086b62a015ebdc56be91071b826 Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server.py | 149 +++++++- .../tests/unit/compute/v2/test_server.py | 358 ++++++++++++++---- ...-server-rebuild-opts-5c75e838d8f0487d.yaml | 13 + 3 files changed, 446 insertions(+), 74 deletions(-) create mode 100644 releasenotes/notes/add-missing-server-rebuild-opts-5c75e838d8f0487d.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 4c177f7c3b..c9fbc42256 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -2445,10 +2445,15 @@ def get_parser(self, prog_name): 'Defaults to the currently used one.' ), ) + parser.add_argument( + '--name', + metavar='', + help=_('Set the new name of the rebuilt server'), + ) parser.add_argument( '--password', metavar='', - help=_('Set a password on the rebuilt server'), + help=_('Set the password on the rebuilt server'), ) parser.add_argument( '--property', @@ -2467,6 +2472,24 @@ def get_parser(self, prog_name): '(supported by --os-compute-api-version 2.19 or above)' ), ) + preserve_ephemeral_group = parser.add_mutually_exclusive_group() + preserve_ephemeral_group.add_argument( + '--preserve-ephemeral', + action='store_true', + default=None, + help=_( + 'Preserve the default ephemeral storage partition on rebuild.' + ), + ) + preserve_ephemeral_group.add_argument( + '--no-preserve-ephemeral', + action='store_false', + dest='preserve_ephemeral', + help=_( + 'Do not preserve the default ephemeral storage partition on ' + 'rebuild.' + ), + ) key_group = parser.add_mutually_exclusive_group() key_group.add_argument( '--key-name', @@ -2478,15 +2501,69 @@ def get_parser(self, prog_name): ), ) key_group.add_argument( - '--key-unset', + '--no-key-name', action='store_true', - default=False, + dest='no_key_name', help=_( 'Unset the key name of key pair on the rebuilt server. ' 'Cannot be specified with the --key-name option. ' '(supported by --os-compute-api-version 2.54 or above)' ), ) + # TODO(stephenfin): Remove this in a future major version bump + key_group.add_argument( + '--key-unset', + action='store_true', + dest='no_key_name', + help=argparse.SUPPRESS, + ) + user_data_group = parser.add_mutually_exclusive_group() + user_data_group.add_argument( + '--user-data', + metavar='', + help=_( + 'Add a new user data file to the rebuilt server. ' + 'Cannot be specified with the --no-user-data option. ' + '(supported by --os-compute-api-version 2.57 or above)' + ), + ) + user_data_group.add_argument( + '--no-user-data', + action='store_true', + default=False, + help=_( + 'Remove existing user data when rebuilding server. ' + 'Cannot be specified with the --user-data option. ' + '(supported by --os-compute-api-version 2.57 or above)' + ), + ) + trusted_certs_group = parser.add_mutually_exclusive_group() + trusted_certs_group.add_argument( + '--trusted-image-cert', + metavar='', + action='append', + dest='trusted_image_certs', + help=_( + 'Trusted image certificate IDs used to validate certificates ' + 'during the image signature verification process. ' + 'Defaults to env[OS_TRUSTED_IMAGE_CERTIFICATE_IDS]. ' + 'May be specified multiple times to pass multiple trusted ' + 'image certificate IDs. ' + 'Cannot be specified with the --no-trusted-certs option. ' + '(supported by --os-compute-api-version 2.63 or above)' + ), + ) + trusted_certs_group.add_argument( + '--no-trusted-image-certs', + action='store_true', + default=False, + help=_( + 'Remove any existing trusted image certificates from the ' + 'server. ' + 'Cannot be specified with the --trusted-certs option. ' + '(supported by --os-compute-api-version 2.63 or above)' + ), + ) parser.add_argument( '--wait', action='store_true', @@ -2517,11 +2594,17 @@ def _show_progress(progress): kwargs = {} + if parsed_args.name is not None: + kwargs['name'] = parsed_args.name + + if parsed_args.preserve_ephemeral is not None: + kwargs['preserve_ephemeral'] = parsed_args.preserve_ephemeral + if parsed_args.property: kwargs['meta'] = parsed_args.property if parsed_args.description: - if server.api_version < api_versions.APIVersion("2.19"): + if compute_client.api_version < api_versions.APIVersion('2.19'): msg = _( '--os-compute-api-version 2.19 or greater is required to ' 'support the --description option' @@ -2539,7 +2622,7 @@ def _show_progress(progress): raise exceptions.CommandError(msg) kwargs['key_name'] = parsed_args.key_name - elif parsed_args.key_unset: + elif parsed_args.no_key_name: if compute_client.api_version < api_versions.APIVersion('2.54'): msg = _( '--os-compute-api-version 2.54 or greater is required to ' @@ -2549,7 +2632,61 @@ def _show_progress(progress): kwargs['key_name'] = None - server = server.rebuild(image, parsed_args.password, **kwargs) + userdata = None + if parsed_args.user_data: + if compute_client.api_version < api_versions.APIVersion('2.54'): + msg = _( + '--os-compute-api-version 2.54 or greater is required to ' + 'support the --user-data option' + ) + raise exceptions.CommandError(msg) + + try: + userdata = io.open(parsed_args.user_data) + except IOError as e: + msg = _("Can't open '%(data)s': %(exception)s") + raise exceptions.CommandError( + msg % {'data': parsed_args.user_data, 'exception': e} + ) + + kwargs['userdata'] = userdata + elif parsed_args.no_user_data: + if compute_client.api_version < api_versions.APIVersion('2.54'): + msg = _( + '--os-compute-api-version 2.54 or greater is required to ' + 'support the --no-user-data option' + ) + raise exceptions.CommandError(msg) + + kwargs['userdata'] = None + + # TODO(stephenfin): Handle OS_TRUSTED_IMAGE_CERTIFICATE_IDS + if parsed_args.trusted_image_certs: + if compute_client.api_version < api_versions.APIVersion('2.63'): + msg = _( + '--os-compute-api-version 2.63 or greater is required to ' + 'support the --trusted-certs option' + ) + raise exceptions.CommandError(msg) + + certs = parsed_args.trusted_image_certs + kwargs['trusted_image_certificates'] = certs + elif parsed_args.no_trusted_image_certs: + if compute_client.api_version < api_versions.APIVersion('2.63'): + msg = _( + '--os-compute-api-version 2.63 or greater is required to ' + 'support the --no-trusted-certs option' + ) + raise exceptions.CommandError(msg) + + kwargs['trusted_image_certificates'] = None + + try: + server = server.rebuild(image, parsed_args.password, **kwargs) + finally: + if userdata and hasattr(userdata, 'close'): + userdata.close() + if parsed_args.wait: if utils.wait_for_status( compute_client.servers.get, diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 5fd15e6ab6..a4b1fad103 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -4765,7 +4765,64 @@ def test_rebuild_with_current_image(self): self.get_image_mock.assert_called_with(self.image.id) self.server.rebuild.assert_called_with(self.image, None) - def test_rebuild_with_current_image_and_password(self): + def test_rebuild_with_name(self): + name = 'test-server-xxx' + arglist = [ + self.server.id, + '--name', name, + ] + verifylist = [ + ('server', self.server.id), + ('name', name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Get the command object to test + self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.get_image_mock.assert_called_with(self.image.id) + self.server.rebuild.assert_called_with(self.image, None, name=name) + + def test_rebuild_with_preserve_ephemeral(self): + arglist = [ + self.server.id, + '--preserve-ephemeral', + ] + verifylist = [ + ('server', self.server.id), + ('preserve_ephemeral', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Get the command object to test + self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.get_image_mock.assert_called_with(self.image.id) + self.server.rebuild.assert_called_with( + self.image, None, preserve_ephemeral=True) + + def test_rebuild_with_no_preserve_ephemeral(self): + arglist = [ + self.server.id, + '--no-preserve-ephemeral', + ] + verifylist = [ + ('server', self.server.id), + ('preserve_ephemeral', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Get the command object to test + self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.get_image_mock.assert_called_with(self.image.id) + self.server.rebuild.assert_called_with( + self.image, None, preserve_ephemeral=False) + + def test_rebuild_with_password(self): password = 'password-xxx' arglist = [ self.server.id, @@ -4784,10 +4841,9 @@ def test_rebuild_with_current_image_and_password(self): self.get_image_mock.assert_called_with(self.image.id) self.server.rebuild.assert_called_with(self.image, password) - def test_rebuild_with_description_api_older(self): - - # Description is not supported for nova api version below 2.19 - self.server.api_version = 2.18 + def test_rebuild_with_description(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.19') description = 'description1' arglist = [ @@ -4800,16 +4856,16 @@ def test_rebuild_with_description_api_older(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - with mock.patch.object(api_versions, - 'APIVersion', - return_value=2.19): - self.assertRaises(exceptions.CommandError, self.cmd.take_action, - parsed_args) + self.cmd.take_action(parsed_args) - def test_rebuild_with_description_api_newer(self): + self.servers_mock.get.assert_called_with(self.server.id) + self.get_image_mock.assert_called_with(self.image.id) + self.server.rebuild.assert_called_with(self.image, None, + description=description) - # Description is supported for nova api version 2.19 or above - self.server.api_version = 2.19 + def test_rebuild_with_description_pre_v219(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.18') description = 'description1' arglist = [ @@ -4822,16 +4878,10 @@ def test_rebuild_with_description_api_newer(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - with mock.patch.object(api_versions, - 'APIVersion', - return_value=2.19): - # Get the command object to test - self.cmd.take_action(parsed_args) - - self.servers_mock.get.assert_called_with(self.server.id) - self.get_image_mock.assert_called_with(self.image.id) - self.server.rebuild.assert_called_with(self.image, None, - description=description) + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) @mock.patch.object(common_utils, 'wait_for_status', return_value=True) def test_rebuild_with_wait_ok(self, mock_wait_for_status): @@ -4907,6 +4957,9 @@ def test_rebuild_with_property(self): self.image, None, meta=expected_property) def test_rebuild_with_keypair_name(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.54') + self.server.key_name = 'mykey' arglist = [ self.server.id, @@ -4918,23 +4971,17 @@ def test_rebuild_with_keypair_name(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.app.client_manager.compute.api_version = 2.54 - with mock.patch.object(api_versions, - 'APIVersion', - return_value=2.54): - self.cmd.take_action(parsed_args) - args = ( - self.image, - None, - ) - kwargs = dict( - key_name=self.server.key_name, - ) - self.servers_mock.get.assert_called_with(self.server.id) - self.get_image_mock.assert_called_with(self.image.id) - self.server.rebuild.assert_called_with(*args, **kwargs) + self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.get_image_mock.assert_called_with(self.image.id) + self.server.rebuild.assert_called_with( + self.image, None, key_name=self.server.key_name) + + def test_rebuild_with_keypair_name_pre_v254(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.53') - def test_rebuild_with_keypair_name_older_version(self): self.server.key_name = 'mykey' arglist = [ self.server.id, @@ -4946,55 +4993,230 @@ def test_rebuild_with_keypair_name_older_version(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.app.client_manager.compute.api_version = 2.53 - with mock.patch.object(api_versions, - 'APIVersion', - return_value=2.54): - self.assertRaises(exceptions.CommandError, - self.cmd.take_action, - parsed_args) + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + def test_rebuild_with_no_keypair_name(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.54') - def test_rebuild_with_keypair_unset(self): self.server.key_name = 'mykey' arglist = [ self.server.id, - '--key-unset', + '--no-key-name', ] verifylist = [ ('server', self.server.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.app.client_manager.compute.api_version = 2.54 - with mock.patch.object(api_versions, - 'APIVersion', - return_value=2.54): - self.cmd.take_action(parsed_args) - args = ( - self.image, - None, - ) - kwargs = dict( - key_name=None, - ) - self.servers_mock.get.assert_called_with(self.server.id) - self.get_image_mock.assert_called_with(self.image.id) - self.server.rebuild.assert_called_with(*args, **kwargs) + self.cmd.take_action(parsed_args) + self.servers_mock.get.assert_called_with(self.server.id) + self.get_image_mock.assert_called_with(self.image.id) + self.server.rebuild.assert_called_with( + self.image, None, key_name=None) - def test_rebuild_with_key_name_and_unset(self): + def test_rebuild_with_keypair_name_and_unset(self): self.server.key_name = 'mykey' arglist = [ self.server.id, '--key-name', self.server.key_name, - '--key-unset', + '--no-key-name', ] verifylist = [ ('server', self.server.id), ('key_name', self.server.key_name) ] - self.assertRaises(utils.ParserException, - self.check_parser, - self.cmd, arglist, verifylist) + self.assertRaises( + utils.ParserException, + self.check_parser, + self.cmd, arglist, verifylist) + + @mock.patch('openstackclient.compute.v2.server.io.open') + def test_rebuild_with_user_data(self, mock_open): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.57') + + mock_file = mock.Mock(name='File') + mock_open.return_value = mock_file + mock_open.read.return_value = '#!/bin/sh' + + arglist = [ + self.server.id, + '--user-data', 'userdata.sh', + ] + verifylist = [ + ('server', self.server.id), + ('user_data', 'userdata.sh'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + # Ensure the userdata file is opened + mock_open.assert_called_with('userdata.sh') + + # Ensure the userdata file is closed + mock_file.close.assert_called_with() + + self.servers_mock.get.assert_called_with(self.server.id) + self.get_image_mock.assert_called_with(self.image.id) + self.server.rebuild.assert_called_with( + self.image, None, + userdata=mock_file,) + + def test_rebuild_with_user_data_pre_257(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.56') + + arglist = [ + self.server.id, + '--user-data', 'userdata.sh', + ] + verifylist = [ + ('server', self.server.id), + ('user_data', 'userdata.sh'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + def test_rebuild_with_no_user_data(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.54') + + self.server.key_name = 'mykey' + arglist = [ + self.server.id, + '--no-user-data', + ] + verifylist = [ + ('server', self.server.id), + ('no_user_data', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + self.servers_mock.get.assert_called_with(self.server.id) + self.get_image_mock.assert_called_with(self.image.id) + self.server.rebuild.assert_called_with( + self.image, None, userdata=None) + + def test_rebuild_with_no_user_data_pre_254(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.53') + + arglist = [ + self.server.id, + '--no-user-data', + ] + verifylist = [ + ('server', self.server.id), + ('no_user_data', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + def test_rebuild_with_user_data_and_unset(self): + arglist = [ + self.server.id, + '--user-data', 'userdata.sh', + '--no-user-data', + ] + self.assertRaises( + utils.ParserException, + self.check_parser, + self.cmd, arglist, None) + + def test_rebuild_with_trusted_image_cert(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.63') + + arglist = [ + self.server.id, + '--trusted-image-cert', 'foo', + '--trusted-image-cert', 'bar', + ] + verifylist = [ + ('server', self.server.id), + ('trusted_image_certs', ['foo', 'bar']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.get_image_mock.assert_called_with(self.image.id) + self.server.rebuild.assert_called_with( + self.image, None, trusted_image_certificates=['foo', 'bar']) + + def test_rebuild_with_trusted_image_cert_pre_v263(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.62') + + arglist = [ + self.server.id, + '--trusted-image-cert', 'foo', + '--trusted-image-cert', 'bar', + ] + verifylist = [ + ('server', self.server.id), + ('trusted_image_certs', ['foo', 'bar']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + def test_rebuild_with_no_trusted_image_cert(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.63') + + arglist = [ + self.server.id, + '--no-trusted-image-certs', + ] + verifylist = [ + ('server', self.server.id), + ('no_trusted_image_certs', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + self.servers_mock.get.assert_called_with(self.server.id) + self.get_image_mock.assert_called_with(self.image.id) + self.server.rebuild.assert_called_with( + self.image, None, trusted_image_certificates=None) + + def test_rebuild_with_no_trusted_image_cert_pre_257(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.62') + + arglist = [ + self.server.id, + '--no-trusted-image-certs', + ] + verifylist = [ + ('server', self.server.id), + ('no_trusted_image_certs', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) class TestEvacuateServer(TestServer): diff --git a/releasenotes/notes/add-missing-server-rebuild-opts-5c75e838d8f0487d.yaml b/releasenotes/notes/add-missing-server-rebuild-opts-5c75e838d8f0487d.yaml new file mode 100644 index 0000000000..81e0eabb7d --- /dev/null +++ b/releasenotes/notes/add-missing-server-rebuild-opts-5c75e838d8f0487d.yaml @@ -0,0 +1,13 @@ +--- +features: + - | + Add a number of additional options to the ``server rebuild`` command: + + - ``--name`` + - ``--preserve-ephemeral``, ``--no-preserve-ephemeral`` + - ``--user-data``, ``--no-user-data`` + - ``--trusted-image-cert``, ``--no-trusted-image-certs`` +upgrade: + - | + The ``--key-unset`` option of the ``server rebuild`` command has been + replaced by ``--no-key-name``. An alias is provided. From 054562238d31757b023da9a6a566f7970527ca4c Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 18 Nov 2020 14:58:38 +0000 Subject: [PATCH 2302/3095] trivial: Cleanup docs for 'server list' Change-Id: I2f2033a8d49ee42eb21696a9cd28e63ad9712fad Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server.py | 142 ++++++++++++++++----------- 1 file changed, 85 insertions(+), 57 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index c9fbc42256..78e118ea43 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1383,9 +1383,11 @@ def get_parser(self, prog_name): parser.add_argument( '--ip6', metavar='', - help=_('Regular expression to match IPv6 addresses. Note ' - 'that this option only applies for non-admin users ' - 'when using ``--os-compute-api-version`` 2.5 or greater.'), + help=_( + 'Regular expression to match IPv6 addresses. Note ' + 'that this option only applies for non-admin users ' + 'when using ``--os-compute-api-version`` 2.5 or greater.' + ), ) parser.add_argument( '--name', @@ -1436,6 +1438,12 @@ def get_parser(self, prog_name): help=_('Search by user (admin only) (name or ID)'), ) identity_common.add_user_domain_option_to_parser(parser) + parser.add_argument( + '--deleted', + action='store_true', + default=False, + help=_('Only display deleted servers (admin only)'), + ) parser.add_argument( '--long', action='store_true', @@ -1447,74 +1455,83 @@ def get_parser(self, prog_name): '-n', '--no-name-lookup', action='store_true', default=False, - help=_('Skip flavor and image name lookup.' - 'Mutually exclusive with "--name-lookup-one-by-one"' - ' option.'), + help=_( + 'Skip flavor and image name lookup. ' + 'Mutually exclusive with "--name-lookup-one-by-one" option.' + ), ) name_lookup_group.add_argument( '--name-lookup-one-by-one', action='store_true', default=False, - help=_('When looking up flavor and image names, look them up' - 'one by one as needed instead of all together (default). ' - 'Mutually exclusive with "--no-name-lookup|-n" option.'), + help=_( + 'When looking up flavor and image names, look them up' + 'one by one as needed instead of all together (default). ' + 'Mutually exclusive with "--no-name-lookup|-n" option.' + ), ) parser.add_argument( '--marker', metavar='', default=None, - help=_('The last server of the previous page. Display ' - 'list of servers after marker. Display all servers if not ' - 'specified. When used with ``--deleted``, the marker must ' - 'be an ID, otherwise a name or ID can be used.'), + help=_( + 'The last server of the previous page. Display ' + 'list of servers after marker. Display all servers if not ' + 'specified. When used with ``--deleted``, the marker must ' + 'be an ID, otherwise a name or ID can be used.' + ), ) parser.add_argument( '--limit', metavar='', type=int, default=None, - help=_("Maximum number of servers to display. If limit equals -1, " - "all servers will be displayed. If limit is greater than " - "'osapi_max_limit' option of Nova API, " - "'osapi_max_limit' will be used instead."), - ) - parser.add_argument( - '--deleted', - action="store_true", - default=False, - help=_('Only display deleted servers (Admin only).') + help=_( + "Maximum number of servers to display. If limit equals -1, " + "all servers will be displayed. If limit is greater than " + "'osapi_max_limit' option of Nova API, " + "'osapi_max_limit' will be used instead." + ), ) parser.add_argument( '--changes-before', metavar='', default=None, - help=_("List only servers changed before a certain point of time. " - "The provided time should be an ISO 8061 formatted time " - "(e.g., 2016-03-05T06:27:59Z). " - "(Supported by API versions '2.66' - '2.latest')") + help=_( + "List only servers changed before a certain point of time. " + "The provided time should be an ISO 8061 formatted time " + "(e.g., 2016-03-05T06:27:59Z). " + '(supported by --os-compute-api-version 2.66 or above)' + ), ) parser.add_argument( '--changes-since', metavar='', default=None, - help=_("List only servers changed after a certain point of time." - " The provided time should be an ISO 8061 formatted time" - " (e.g., 2016-03-04T06:27:59Z).") + help=_( + "List only servers changed after a certain point of time. " + "The provided time should be an ISO 8061 formatted time " + "(e.g., 2016-03-04T06:27:59Z)." + ), ) lock_group = parser.add_mutually_exclusive_group() lock_group.add_argument( '--locked', action='store_true', default=False, - help=_('Only display locked servers. ' - 'Requires ``--os-compute-api-version`` 2.73 or greater.'), + help=_( + 'Only display locked servers ' + '(supported by --os-compute-api-version 2.73 or above)' + ), ) lock_group.add_argument( '--unlocked', action='store_true', default=False, - help=_('Only display unlocked servers. ' - 'Requires ``--os-compute-api-version`` 2.73 or greater.'), + help=_( + 'Only display unlocked servers ' + '(supported by --os-compute-api-version 2.73 or above)' + ), ) parser.add_argument( '--tags', @@ -1616,18 +1633,25 @@ def take_action(self, parsed_args): search_opts['not-tags'] = parsed_args.not_tags - support_locked = (compute_client.api_version >= - api_versions.APIVersion('2.73')) - if not support_locked and (parsed_args.locked or parsed_args.unlocked): - msg = _('--os-compute-api-version 2.73 or greater is required to ' - 'use the (un)locked filter option.') - raise exceptions.CommandError(msg) - elif support_locked: - # Only from 2.73. - if parsed_args.locked: - search_opts['locked'] = True - if parsed_args.unlocked: - search_opts['locked'] = False + if parsed_args.locked: + if compute_client.api_version < api_versions.APIVersion('2.73'): + msg = _( + '--os-compute-api-version 2.73 or greater is required to ' + 'support the --locked option' + ) + raise exceptions.CommandError(msg) + + search_opts['locked'] = True + elif parsed_args.unlocked: + if compute_client.api_version < api_versions.APIVersion('2.73'): + msg = _( + '--os-compute-api-version 2.73 or greater is required to ' + 'support the --unlocked option' + ) + raise exceptions.CommandError(msg) + + search_opts['locked'] = False + LOG.debug('search options: %s', search_opts) if search_opts['changes-before']: @@ -1834,17 +1858,21 @@ def take_action(self, parsed_args): s.flavor_name = '' s.flavor_id = '' - table = (column_headers, - (utils.get_item_properties( - s, columns, - mixed_case_fields=mixed_case_fields, - formatters={ - 'OS-EXT-STS:power_state': - _format_servers_list_power_state, - 'Networks': _format_servers_list_networks, - 'Metadata': utils.format_dict, - }, - ) for s in data)) + table = ( + column_headers, + ( + utils.get_item_properties( + s, columns, + mixed_case_fields=mixed_case_fields, + formatters={ + 'OS-EXT-STS:power_state': + _format_servers_list_power_state, + 'Networks': _format_servers_list_networks, + 'Metadata': utils.format_dict, + }, + ) for s in data + ), + ) return table From 3c80b1b3b29307381b869f6a767e14e9d47e212f Mon Sep 17 00:00:00 2001 From: okozachenko Date: Thu, 10 Dec 2020 20:22:55 +0200 Subject: [PATCH 2303/3095] Add project field in image list subcommand The motivation is to filter the image by owner Change-Id: I1f08da175a06e62a844f76b0ec18cb3332efef86 --- openstackclient/image/v2/image.py | 15 +++++++++++++++ openstackclient/tests/unit/image/v2/test_image.py | 15 +++++++++++++++ ...e-project-filter-support-ed6204391e503adf.yaml | 5 +++++ 3 files changed, 35 insertions(+) create mode 100644 releasenotes/notes/add-image-project-filter-support-ed6204391e503adf.yaml diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 58d92f5165..fa7f4be562 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -591,6 +591,12 @@ def get_parser(self, prog_name): "The supported options are: %s. ") % ', '.join(MEMBER_STATUS_CHOICES)) ) + parser.add_argument( + '--project', + metavar='', + help=_("Search by project (admin only) (name or ID)") + ) + common.add_project_domain_option_to_parser(parser) parser.add_argument( '--tag', metavar='', @@ -636,6 +642,7 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity image_client = self.app.client_manager.image kwargs = {} @@ -659,6 +666,14 @@ def take_action(self, parsed_args): kwargs['member_status'] = parsed_args.member_status if parsed_args.tag: kwargs['tag'] = parsed_args.tag + project_id = None + if parsed_args.project: + project_id = common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + kwargs['owner'] = project_id if parsed_args.long: columns = ( 'ID', diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py index b72e983599..ebeb8353a5 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -769,6 +769,21 @@ def test_image_list_limit_option(self): self.assertEqual(self.columns, columns) self.assertEqual(ret_limit, len(tuple(data))) + def test_image_list_project_option(self): + self.client.find_image = mock.Mock(return_value=self._image) + arglist = [ + '--project', 'nova', + ] + verifylist = [ + ('project', 'nova'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual(self.columns, columns) + self.assertItemsEqual(self.datalist, tuple(data)) + @mock.patch('osc_lib.utils.find_resource') def test_image_list_marker_option(self, fr_mock): # tangchen: Since image_fakes.IMAGE is a dict, it cannot offer a .id diff --git a/releasenotes/notes/add-image-project-filter-support-ed6204391e503adf.yaml b/releasenotes/notes/add-image-project-filter-support-ed6204391e503adf.yaml new file mode 100644 index 0000000000..e909803c93 --- /dev/null +++ b/releasenotes/notes/add-image-project-filter-support-ed6204391e503adf.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``--project`` and ``--project-domain``options to ``image list`` + command to filter by owner. From 29a7c9afce32e7acbd40b9e4a73140ef9e05e7f3 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Tue, 22 Dec 2020 16:46:33 +0000 Subject: [PATCH 2304/3095] image: Unset properties rather than setting to None Currently, we attempt to unset an image property by setting it to None. This doesn't work for known properties and is rightly rejected by the Glance API with the following error: BadRequestException: 400: Client Error for url: http://172.20.4.87/image/v2/images/368c5751-2b0b-4a38-a255-fd146fe52d31, Bad Request The solution is to actually unset the field by deleting it. Change-Id: Ie156bedbe0f9244f82c81401679706f484caf9aa Signed-off-by: Stephen Finucane Story: #2008463 Task: #41493 --- openstackclient/image/v2/image.py | 2 +- .../tests/functional/image/v2/test_image.py | 2 ++ .../tests/unit/image/v2/test_image.py | 29 +++++++++---------- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 58d92f5165..6081152217 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -1166,7 +1166,7 @@ def take_action(self, parsed_args): if parsed_args.properties: for k in parsed_args.properties: if k in image: - kwargs[k] = None + delattr(image, k) elif k in image.properties: # Since image is an "evil" object from SDK POV we need to # pass modified properties object, so that SDK can figure diff --git a/openstackclient/tests/functional/image/v2/test_image.py b/openstackclient/tests/functional/image/v2/test_image.py index 264ba5199c..0a3a73602c 100644 --- a/openstackclient/tests/functional/image/v2/test_image.py +++ b/openstackclient/tests/functional/image/v2/test_image.py @@ -119,6 +119,7 @@ def test_image_attributes(self): 'image set ' + '--property a=b ' + '--property c=d ' + + '--property hw_rng_model=virtio ' + '--public ' + self.name ) @@ -133,6 +134,7 @@ def test_image_attributes(self): 'image unset ' + '--property a ' + '--property c ' + + '--property hw_rng_model ' + self.name ) json_output = json.loads(self.openstack( diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py index b72e983599..8088e15e41 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -1501,15 +1501,16 @@ def test_image_show_human_readable(self): class TestImageUnset(TestImage): - attrs = {} - attrs['tags'] = ['test'] - attrs['prop'] = 'test' - attrs['prop2'] = 'fake' - image = image_fakes.FakeImage.create_one_image(attrs) - def setUp(self): super(TestImageUnset, self).setUp() + attrs = {} + attrs['tags'] = ['test'] + attrs['hw_rng_model'] = 'virtio' + attrs['prop'] = 'test' + attrs['prop2'] = 'fake' + self.image = image_fakes.FakeImage.create_one_image(attrs) + self.client.find_image.return_value = self.image self.client.remove_tag.return_value = self.image self.client.update_image.return_value = self.image @@ -1552,22 +1553,20 @@ def test_image_unset_tag_option(self): def test_image_unset_property_option(self): arglist = [ + '--property', 'hw_rng_model', '--property', 'prop', self.image.id, ] verifylist = [ - ('properties', ['prop']), + ('properties', ['hw_rng_model', 'prop']), ('image', self.image.id) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - kwargs = {} self.client.update_image.assert_called_with( - self.image, - properties={'prop2': 'fake'}, - **kwargs) + self.image, properties={'prop2': 'fake'}) self.assertIsNone(result) @@ -1575,23 +1574,21 @@ def test_image_unset_mixed_option(self): arglist = [ '--tag', 'test', + '--property', 'hw_rng_model', '--property', 'prop', self.image.id, ] verifylist = [ ('tags', ['test']), - ('properties', ['prop']), + ('properties', ['hw_rng_model', 'prop']), ('image', self.image.id) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - kwargs = {} self.client.update_image.assert_called_with( - self.image, - properties={'prop2': 'fake'}, - **kwargs) + self.image, properties={'prop2': 'fake'}) self.client.remove_tag.assert_called_with( self.image.id, 'test' From f57e10b903fa71d02a6e104717824d004178bbf5 Mon Sep 17 00:00:00 2001 From: Hang Yang Date: Tue, 18 Aug 2020 17:08:40 -0500 Subject: [PATCH 2305/3095] Support Neutron Address Group CRUD Add support for Neutron address group CRUD operations. Subsequent patches will be added to use address groups in security group rules. Change-Id: I3c313fc9329837dde67815901528a34dca98ebcc Implements: blueprint address-groups-in-sg-rules Depends-On: https://review.opendev.org/738274 Depends-On: https://review.opendev.org/745594 --- .../cli/command-objects/address-group.rst | 12 + openstackclient/network/v2/address_group.py | 296 +++++++++++ .../network/v2/test_address_group.py | 177 ++++++ .../tests/unit/network/v2/fakes.py | 73 +++ .../unit/network/v2/test_address_group.py | 503 ++++++++++++++++++ ...s-groups-in-sg-rules-e0dc7e889e107799.yaml | 7 + setup.cfg | 7 + 7 files changed, 1075 insertions(+) create mode 100644 doc/source/cli/command-objects/address-group.rst create mode 100644 openstackclient/network/v2/address_group.py create mode 100644 openstackclient/tests/functional/network/v2/test_address_group.py create mode 100644 openstackclient/tests/unit/network/v2/test_address_group.py create mode 100644 releasenotes/notes/bp-address-groups-in-sg-rules-e0dc7e889e107799.yaml diff --git a/doc/source/cli/command-objects/address-group.rst b/doc/source/cli/command-objects/address-group.rst new file mode 100644 index 0000000000..c1ff6f8858 --- /dev/null +++ b/doc/source/cli/command-objects/address-group.rst @@ -0,0 +1,12 @@ +============= +address group +============= + +An **address group** is a group of IPv4 or IPv6 address blocks which could be +referenced as a remote source or destination when creating a security group +rule. + +Network v2 + +.. autoprogram-cliff:: openstack.network.v2 + :command: address group * diff --git a/openstackclient/network/v2/address_group.py b/openstackclient/network/v2/address_group.py new file mode 100644 index 0000000000..fe1e14a316 --- /dev/null +++ b/openstackclient/network/v2/address_group.py @@ -0,0 +1,296 @@ +# 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. +# + +"""Address group action implementations""" + +import logging + +import netaddr +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils + +from openstackclient.i18n import _ +from openstackclient.identity import common as identity_common +from openstackclient.network import sdk_utils + + +LOG = logging.getLogger(__name__) + + +def _get_columns(item): + column_map = { + 'tenant_id': 'project_id', + } + return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + + +def _format_addresses(addresses): + ret = [] + for addr in addresses: + ret.append(str(netaddr.IPNetwork(addr))) + return ret + + +def _get_attrs(client_manager, parsed_args): + attrs = {} + attrs['name'] = parsed_args.name + if parsed_args.description: + attrs['description'] = parsed_args.description + attrs['addresses'] = _format_addresses(parsed_args.address) + if 'project' in parsed_args and parsed_args.project is not None: + identity_client = client_manager.identity + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + attrs['tenant_id'] = project_id + + return attrs + + +class CreateAddressGroup(command.ShowOne): + _description = _("Create a new Address Group") + + def get_parser(self, prog_name): + parser = super(CreateAddressGroup, self).get_parser(prog_name) + parser.add_argument( + 'name', + metavar="", + help=_("New address group name") + ) + parser.add_argument( + '--description', + metavar="", + help=_("New address group description") + ) + parser.add_argument( + "--address", + metavar="", + action='append', + default=[], + help=_("IP address or CIDR " + "(repeat option to set multiple addresses)"), + ) + parser.add_argument( + '--project', + metavar="", + help=_("Owner's project (name or ID)") + ) + identity_common.add_project_domain_option_to_parser(parser) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + attrs = _get_attrs(self.app.client_manager, parsed_args) + + obj = client.create_address_group(**attrs) + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns, formatters={}) + + return (display_columns, data) + + +class DeleteAddressGroup(command.Command): + _description = _("Delete address group(s)") + + def get_parser(self, prog_name): + parser = super(DeleteAddressGroup, self).get_parser(prog_name) + parser.add_argument( + 'address_group', + metavar="", + nargs='+', + help=_("Address group(s) to delete (name or ID)") + ) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + result = 0 + + for group in parsed_args.address_group: + try: + obj = client.find_address_group(group, ignore_missing=False) + client.delete_address_group(obj) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete address group with " + "name or ID '%(group)s': %(e)s"), + {'group': group, 'e': e}) + + if result > 0: + total = len(parsed_args.address_group) + msg = (_("%(result)s of %(total)s address groups failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) + + +class ListAddressGroup(command.Lister): + _description = _("List address groups") + + def get_parser(self, prog_name): + parser = super(ListAddressGroup, self).get_parser(prog_name) + + parser.add_argument( + '--name', + metavar='', + help=_("List only address groups of given name in output") + ) + parser.add_argument( + '--project', + metavar="", + help=_("List address groups according to their project " + "(name or ID)") + ) + identity_common.add_project_domain_option_to_parser(parser) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + columns = ( + 'id', + 'name', + 'description', + 'project_id', + 'addresses', + ) + column_headers = ( + 'ID', + 'Name', + 'Description', + 'Project', + 'Addresses', + ) + attrs = {} + if parsed_args.name: + attrs['name'] = parsed_args.name + if 'project' in parsed_args and parsed_args.project is not None: + identity_client = self.app.client_manager.identity + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + attrs['tenant_id'] = project_id + attrs['project_id'] = project_id + data = client.address_groups(**attrs) + + return (column_headers, + (utils.get_item_properties( + s, columns, formatters={}, + ) for s in data)) + + +class SetAddressGroup(command.Command): + _description = _("Set address group properties") + + def get_parser(self, prog_name): + parser = super(SetAddressGroup, self).get_parser(prog_name) + parser.add_argument( + 'address_group', + metavar="", + help=_("Address group to modify (name or ID)") + ) + parser.add_argument( + '--name', + metavar="", + help=_('Set address group name') + ) + parser.add_argument( + '--description', + metavar="", + help=_('Set address group description') + ) + parser.add_argument( + "--address", + metavar="", + action='append', + default=[], + help=_("IP address or CIDR " + "(repeat option to set multiple addresses)"), + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.find_address_group( + parsed_args.address_group, + ignore_missing=False) + attrs = {} + if parsed_args.name is not None: + attrs['name'] = parsed_args.name + if parsed_args.description is not None: + attrs['description'] = parsed_args.description + if attrs: + client.update_address_group(obj, **attrs) + if parsed_args.address: + client.add_addresses_to_address_group( + obj, _format_addresses(parsed_args.address)) + + +class ShowAddressGroup(command.ShowOne): + _description = _("Display address group details") + + def get_parser(self, prog_name): + parser = super(ShowAddressGroup, self).get_parser(prog_name) + parser.add_argument( + 'address_group', + metavar="", + help=_("Address group to display (name or ID)") + ) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.find_address_group( + parsed_args.address_group, + ignore_missing=False) + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns, formatters={}) + + return (display_columns, data) + + +class UnsetAddressGroup(command.Command): + _description = _("Unset address group properties") + + def get_parser(self, prog_name): + parser = super(UnsetAddressGroup, self).get_parser(prog_name) + parser.add_argument( + 'address_group', + metavar="", + help=_("Address group to modify (name or ID)") + ) + parser.add_argument( + "--address", + metavar="", + action='append', + default=[], + help=_("IP address or CIDR " + "(repeat option to unset multiple addresses)"), + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.find_address_group( + parsed_args.address_group, + ignore_missing=False) + if parsed_args.address: + client.remove_addresses_from_address_group( + obj, _format_addresses(parsed_args.address)) diff --git a/openstackclient/tests/functional/network/v2/test_address_group.py b/openstackclient/tests/functional/network/v2/test_address_group.py new file mode 100644 index 0000000000..52c628a36c --- /dev/null +++ b/openstackclient/tests/functional/network/v2/test_address_group.py @@ -0,0 +1,177 @@ +# 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 json +import uuid + +from openstackclient.tests.functional.network.v2 import common + + +class AddressGroupTests(common.NetworkTests): + """Functional tests for address group""" + + def setUp(self): + super(AddressGroupTests, self).setUp() + # Nothing in this class works with Nova Network + if not self.haz_network: + self.skipTest("No Network service present") + if not self.is_extension_enabled('address-group'): + self.skipTest("No address-group extension present") + + def test_address_group_create_and_delete(self): + """Test create, delete multiple""" + name1 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'address group create -f json ' + + name1 + )) + self.assertEqual( + name1, + cmd_output['name'], + ) + + name2 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'address group create -f json ' + + name2 + )) + self.assertEqual( + name2, + cmd_output['name'], + ) + + raw_output = self.openstack( + 'address group delete ' + name1 + ' ' + name2, + ) + self.assertOutput('', raw_output) + + def test_address_group_list(self): + """Test create, list filters, delete""" + # Get project IDs + cmd_output = json.loads(self.openstack('token issue -f json ')) + auth_project_id = cmd_output['project_id'] + + cmd_output = json.loads(self.openstack('project list -f json ')) + admin_project_id = None + demo_project_id = None + for p in cmd_output: + if p['Name'] == 'admin': + admin_project_id = p['ID'] + if p['Name'] == 'demo': + demo_project_id = p['ID'] + + # Verify assumptions: + # * admin and demo projects are present + # * demo and admin are distinct projects + # * tests run as admin + self.assertIsNotNone(admin_project_id) + self.assertIsNotNone(demo_project_id) + self.assertNotEqual(admin_project_id, demo_project_id) + self.assertEqual(admin_project_id, auth_project_id) + + name1 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'address group create -f json ' + + name1 + )) + self.addCleanup(self.openstack, 'address group delete ' + name1) + self.assertEqual( + admin_project_id, + cmd_output["project_id"], + ) + + name2 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'address group create -f json ' + + '--project ' + demo_project_id + + ' ' + name2 + )) + self.addCleanup(self.openstack, 'address group delete ' + name2) + self.assertEqual( + demo_project_id, + cmd_output["project_id"], + ) + + # Test list + cmd_output = json.loads(self.openstack( + 'address group list -f json ', + )) + names = [x["Name"] for x in cmd_output] + self.assertIn(name1, names) + self.assertIn(name2, names) + + # Test list --project + cmd_output = json.loads(self.openstack( + 'address group list -f json ' + + '--project ' + demo_project_id + )) + names = [x["Name"] for x in cmd_output] + self.assertNotIn(name1, names) + self.assertIn(name2, names) + + # Test list --name + cmd_output = json.loads(self.openstack( + 'address group list -f json ' + + '--name ' + name1 + )) + names = [x["Name"] for x in cmd_output] + self.assertIn(name1, names) + self.assertNotIn(name2, names) + + def test_address_group_set_unset_and_show(self): + """Tests create options, set, unset, and show""" + name = uuid.uuid4().hex + newname = name + "_" + cmd_output = json.loads(self.openstack( + 'address group create -f json ' + + '--description aaaa ' + + '--address 10.0.0.1 --address 2001::/16 ' + + name + )) + self.addCleanup(self.openstack, 'address group delete ' + newname) + self.assertEqual(name, cmd_output['name']) + self.assertEqual('aaaa', cmd_output['description']) + self.assertEqual(2, len(cmd_output['addresses'])) + + # Test set name, description and address + raw_output = self.openstack( + 'address group set ' + + '--name ' + newname + ' ' + + '--description bbbb ' + + '--address 10.0.0.2 --address 192.0.0.0/8 ' + + name, + ) + self.assertOutput('', raw_output) + + # Show the updated address group + cmd_output = json.loads(self.openstack( + 'address group show -f json ' + + newname, + )) + self.assertEqual(newname, cmd_output['name']) + self.assertEqual('bbbb', cmd_output['description']) + self.assertEqual(4, len(cmd_output['addresses'])) + + # Test unset address + raw_output = self.openstack( + 'address group unset ' + + '--address 10.0.0.1 --address 2001::/16 ' + + '--address 10.0.0.2 --address 192.0.0.0/8 ' + + newname, + ) + self.assertEqual('', raw_output) + + cmd_output = json.loads(self.openstack( + 'address group show -f json ' + + newname, + )) + self.assertEqual(0, len(cmd_output['addresses'])) diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index 2db83d3b98..798cfd967b 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -83,6 +83,79 @@ def setUp(self): ) +class FakeAddressGroup(object): + """Fake one or more address groups.""" + + @staticmethod + def create_one_address_group(attrs=None): + """Create a fake address group. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object with name, id, etc. + """ + attrs = attrs or {} + + # Set default attributes. + address_group_attrs = { + 'name': 'address-group-name-' + uuid.uuid4().hex, + 'description': 'address-group-description-' + uuid.uuid4().hex, + 'id': 'address-group-id-' + uuid.uuid4().hex, + 'tenant_id': 'project-id-' + uuid.uuid4().hex, + 'addresses': ['10.0.0.1/32'], + } + + # Overwrite default attributes. + address_group_attrs.update(attrs) + + address_group = fakes.FakeResource( + info=copy.deepcopy(address_group_attrs), + loaded=True) + + # Set attributes with special mapping in OpenStack SDK. + address_group.project_id = address_group_attrs['tenant_id'] + + return address_group + + @staticmethod + def create_address_groups(attrs=None, count=2): + """Create multiple fake address groups. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of address groups to fake + :return: + A list of FakeResource objects faking the address groups + """ + address_groups = [] + for i in range(0, count): + address_groups.append( + FakeAddressGroup.create_one_address_group(attrs)) + + return address_groups + + @staticmethod + def get_address_groups(address_groups=None, count=2): + """Get an iterable Mock object with a list of faked address groups. + + If address groups list is provided, then initialize the Mock object + with the list. Otherwise create one. + + :param List address_groups: + A list of FakeResource objects faking address groups + :param int count: + The number of address groups to fake + :return: + An iterable Mock object with side_effect set to a list of faked + address groups + """ + if address_groups is None: + address_groups = FakeAddressGroup.create_address_groups(count) + return mock.Mock(side_effect=address_groups) + + class FakeAddressScope(object): """Fake one or more address scopes.""" diff --git a/openstackclient/tests/unit/network/v2/test_address_group.py b/openstackclient/tests/unit/network/v2/test_address_group.py new file mode 100644 index 0000000000..3b2b1ab6b9 --- /dev/null +++ b/openstackclient/tests/unit/network/v2/test_address_group.py @@ -0,0 +1,503 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +from unittest import mock +from unittest.mock import call + +from osc_lib import exceptions + +from openstackclient.network.v2 import address_group +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3 +from openstackclient.tests.unit.network.v2 import fakes as network_fakes +from openstackclient.tests.unit import utils as tests_utils + + +class TestAddressGroup(network_fakes.TestNetworkV2): + + def setUp(self): + super(TestAddressGroup, self).setUp() + + # Get a shortcut to the network client + self.network = self.app.client_manager.network + # Get a shortcut to the ProjectManager Mock + self.projects_mock = self.app.client_manager.identity.projects + # Get a shortcut to the DomainManager Mock + self.domains_mock = self.app.client_manager.identity.domains + + +class TestCreateAddressGroup(TestAddressGroup): + + project = identity_fakes_v3.FakeProject.create_one_project() + domain = identity_fakes_v3.FakeDomain.create_one_domain() + # The new address group created. + new_address_group = ( + network_fakes.FakeAddressGroup.create_one_address_group( + attrs={ + 'tenant_id': project.id, + } + )) + columns = ( + 'addresses', + 'description', + 'id', + 'name', + 'project_id', + ) + data = ( + new_address_group.addresses, + new_address_group.description, + new_address_group.id, + new_address_group.name, + new_address_group.project_id, + ) + + def setUp(self): + super(TestCreateAddressGroup, self).setUp() + self.network.create_address_group = mock.Mock( + return_value=self.new_address_group) + + # Get the command object to test + self.cmd = address_group.CreateAddressGroup(self.app, self.namespace) + + self.projects_mock.get.return_value = self.project + self.domains_mock.get.return_value = self.domain + + def test_create_no_options(self): + arglist = [] + verifylist = [] + + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_create_default_options(self): + arglist = [ + self.new_address_group.name, + ] + verifylist = [ + ('project', None), + ('name', self.new_address_group.name), + ('description', None), + ('address', []), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_address_group.assert_called_once_with(**{ + 'name': self.new_address_group.name, + 'addresses': [], + }) + self.assertEqual(self.columns, columns) + self.assertItemsEqual(self.data, data) + + def test_create_all_options(self): + arglist = [ + '--project', self.project.name, + '--project-domain', self.domain.name, + '--address', '10.0.0.1', + '--description', self.new_address_group.description, + self.new_address_group.name, + ] + verifylist = [ + ('project', self.project.name), + ('project_domain', self.domain.name), + ('address', ['10.0.0.1']), + ('description', self.new_address_group.description), + ('name', self.new_address_group.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_address_group.assert_called_once_with(**{ + 'addresses': ['10.0.0.1/32'], + 'tenant_id': self.project.id, + 'name': self.new_address_group.name, + 'description': self.new_address_group.description, + }) + self.assertEqual(self.columns, columns) + self.assertItemsEqual(self.data, data) + + +class TestDeleteAddressGroup(TestAddressGroup): + + # The address group to delete. + _address_groups = ( + network_fakes.FakeAddressGroup.create_address_groups(count=2)) + + def setUp(self): + super(TestDeleteAddressGroup, self).setUp() + self.network.delete_address_group = mock.Mock(return_value=None) + self.network.find_address_group = ( + network_fakes.FakeAddressGroup.get_address_groups( + address_groups=self._address_groups) + ) + + # Get the command object to test + self.cmd = address_group.DeleteAddressGroup(self.app, self.namespace) + + def test_address_group_delete(self): + arglist = [ + self._address_groups[0].name, + ] + verifylist = [ + ('address_group', [self._address_groups[0].name]), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.network.find_address_group.assert_called_once_with( + self._address_groups[0].name, ignore_missing=False) + self.network.delete_address_group.assert_called_once_with( + self._address_groups[0]) + self.assertIsNone(result) + + def test_multi_address_groups_delete(self): + arglist = [] + + for a in self._address_groups: + arglist.append(a.name) + verifylist = [ + ('address_group', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [] + for a in self._address_groups: + calls.append(call(a)) + self.network.delete_address_group.assert_has_calls(calls) + self.assertIsNone(result) + + def test_multi_address_groups_delete_with_exception(self): + arglist = [ + self._address_groups[0].name, + 'unexist_address_group', + ] + verifylist = [ + ('address_group', + [self._address_groups[0].name, 'unexist_address_group']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self._address_groups[0], exceptions.CommandError] + self.network.find_address_group = ( + mock.Mock(side_effect=find_mock_result) + ) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 address groups failed to delete.', str(e)) + + self.network.find_address_group.assert_any_call( + self._address_groups[0].name, ignore_missing=False) + self.network.find_address_group.assert_any_call( + 'unexist_address_group', ignore_missing=False) + self.network.delete_address_group.assert_called_once_with( + self._address_groups[0] + ) + + +class TestListAddressGroup(TestAddressGroup): + + # The address groups to list up. + address_groups = ( + network_fakes.FakeAddressGroup.create_address_groups(count=3)) + columns = ( + 'ID', + 'Name', + 'Description', + 'Project', + 'Addresses', + ) + data = [] + for group in address_groups: + data.append(( + group.id, + group.name, + group.description, + group.project_id, + group.addresses, + )) + + def setUp(self): + super(TestListAddressGroup, self).setUp() + self.network.address_groups = mock.Mock( + return_value=self.address_groups) + + # Get the command object to test + self.cmd = address_group.ListAddressGroup(self.app, self.namespace) + + def test_address_group_list(self): + arglist = [] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.address_groups.assert_called_once_with(**{}) + self.assertEqual(self.columns, columns) + self.assertItemsEqual(self.data, list(data)) + + def test_address_group_list_name(self): + arglist = [ + '--name', self.address_groups[0].name, + ] + verifylist = [ + ('name', self.address_groups[0].name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.address_groups.assert_called_once_with( + **{'name': self.address_groups[0].name}) + self.assertEqual(self.columns, columns) + self.assertItemsEqual(self.data, list(data)) + + def test_address_group_list_project(self): + project = identity_fakes_v3.FakeProject.create_one_project() + self.projects_mock.get.return_value = project + arglist = [ + '--project', project.id, + ] + verifylist = [ + ('project', project.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.address_groups.assert_called_once_with( + **{'tenant_id': project.id, 'project_id': project.id}) + self.assertEqual(self.columns, columns) + self.assertItemsEqual(self.data, list(data)) + + def test_address_group_project_domain(self): + project = identity_fakes_v3.FakeProject.create_one_project() + self.projects_mock.get.return_value = project + arglist = [ + '--project', project.id, + '--project-domain', project.domain_id, + ] + verifylist = [ + ('project', project.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'tenant_id': project.id, 'project_id': project.id} + + self.network.address_groups.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertItemsEqual(self.data, list(data)) + + +class TestSetAddressGroup(TestAddressGroup): + + # The address group to set. + _address_group = network_fakes.FakeAddressGroup.create_one_address_group() + + def setUp(self): + super(TestSetAddressGroup, self).setUp() + self.network.update_address_group = mock.Mock(return_value=None) + self.network.find_address_group = mock.Mock( + return_value=self._address_group) + self.network.add_addresses_to_address_group = mock.Mock( + return_value=self._address_group) + # Get the command object to test + self.cmd = address_group.SetAddressGroup(self.app, self.namespace) + + def test_set_nothing(self): + arglist = [self._address_group.name, ] + verifylist = [ + ('address_group', self._address_group.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.network.update_address_group.assert_not_called() + self.network.add_addresses_to_address_group.assert_not_called() + self.assertIsNone(result) + + def test_set_name_and_description(self): + arglist = [ + '--name', 'new_address_group_name', + '--description', 'new_address_group_description', + self._address_group.name, + ] + verifylist = [ + ('name', 'new_address_group_name'), + ('description', 'new_address_group_description'), + ('address_group', self._address_group.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + attrs = { + 'name': "new_address_group_name", + 'description': 'new_address_group_description', + } + self.network.update_address_group.assert_called_with( + self._address_group, **attrs) + self.assertIsNone(result) + + def test_set_one_address(self): + arglist = [ + self._address_group.name, + '--address', '10.0.0.2', + ] + verifylist = [ + ('address_group', self._address_group.name), + ('address', ['10.0.0.2']), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.network.add_addresses_to_address_group.assert_called_once_with( + self._address_group, ['10.0.0.2/32']) + self.assertIsNone(result) + + def test_set_multiple_addresses(self): + arglist = [ + self._address_group.name, + '--address', '10.0.0.2', + '--address', '2001::/16', + ] + verifylist = [ + ('address_group', self._address_group.name), + ('address', ['10.0.0.2', '2001::/16']), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.network.add_addresses_to_address_group.assert_called_once_with( + self._address_group, ['10.0.0.2/32', '2001::/16']) + self.assertIsNone(result) + + +class TestShowAddressGroup(TestAddressGroup): + + # The address group to show. + _address_group = network_fakes.FakeAddressGroup.create_one_address_group() + columns = ( + 'addresses', + 'description', + 'id', + 'name', + 'project_id', + ) + data = ( + _address_group.addresses, + _address_group.description, + _address_group.id, + _address_group.name, + _address_group.project_id, + ) + + def setUp(self): + super(TestShowAddressGroup, self).setUp() + self.network.find_address_group = mock.Mock( + return_value=self._address_group) + + # Get the command object to test + self.cmd = address_group.ShowAddressGroup(self.app, self.namespace) + + def test_show_no_options(self): + arglist = [] + verifylist = [] + + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_show_all_options(self): + arglist = [ + self._address_group.name, + ] + verifylist = [ + ('address_group', self._address_group.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.find_address_group.assert_called_once_with( + self._address_group.name, ignore_missing=False) + self.assertEqual(self.columns, columns) + self.assertItemsEqual(self.data, list(data)) + + +class TestUnsetAddressGroup(TestAddressGroup): + + # The address group to unset. + _address_group = network_fakes.FakeAddressGroup.create_one_address_group() + + def setUp(self): + super(TestUnsetAddressGroup, self).setUp() + self.network.find_address_group = mock.Mock( + return_value=self._address_group) + self.network.remove_addresses_from_address_group = mock.Mock( + return_value=self._address_group) + # Get the command object to test + self.cmd = address_group.UnsetAddressGroup(self.app, self.namespace) + + def test_unset_nothing(self): + arglist = [self._address_group.name, ] + verifylist = [ + ('address_group', self._address_group.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.network.remove_addresses_from_address_group.assert_not_called() + self.assertIsNone(result) + + def test_unset_one_address(self): + arglist = [ + self._address_group.name, + '--address', '10.0.0.2', + ] + verifylist = [ + ('address_group', self._address_group.name), + ('address', ['10.0.0.2']), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.network.remove_addresses_from_address_group.\ + assert_called_once_with(self._address_group, ['10.0.0.2/32']) + self.assertIsNone(result) + + def test_unset_multiple_addresses(self): + arglist = [ + self._address_group.name, + '--address', '10.0.0.2', + '--address', '2001::/16', + ] + verifylist = [ + ('address_group', self._address_group.name), + ('address', ['10.0.0.2', '2001::/16']), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.network.remove_addresses_from_address_group.\ + assert_called_once_with(self._address_group, + ['10.0.0.2/32', '2001::/16']) + self.assertIsNone(result) diff --git a/releasenotes/notes/bp-address-groups-in-sg-rules-e0dc7e889e107799.yaml b/releasenotes/notes/bp-address-groups-in-sg-rules-e0dc7e889e107799.yaml new file mode 100644 index 0000000000..74725ad6e4 --- /dev/null +++ b/releasenotes/notes/bp-address-groups-in-sg-rules-e0dc7e889e107799.yaml @@ -0,0 +1,7 @@ +--- +features: + - Add ``address group create``, ``address group delete``, + ``address group list``, ``address group set``, ``address group show`` + and ``address group unset`` commands to support Neutron address group + CRUD operations. + [Blueprint `address-groups-in-sg-rules `_] diff --git a/setup.cfg b/setup.cfg index 9299fb918a..34ab4329ea 100644 --- a/setup.cfg +++ b/setup.cfg @@ -377,6 +377,13 @@ openstack.image.v2 = image_unset = openstackclient.image.v2.image:UnsetImage openstack.network.v2 = + address_group_create = openstackclient.network.v2.address_group:CreateAddressGroup + address_group_delete = openstackclient.network.v2.address_group:DeleteAddressGroup + address_group_list = openstackclient.network.v2.address_group:ListAddressGroup + address_group_set = openstackclient.network.v2.address_group:SetAddressGroup + address_group_show = openstackclient.network.v2.address_group:ShowAddressGroup + address_group_unset = openstackclient.network.v2.address_group:UnsetAddressGroup + address_scope_create = openstackclient.network.v2.address_scope:CreateAddressScope address_scope_delete = openstackclient.network.v2.address_scope:DeleteAddressScope address_scope_list = openstackclient.network.v2.address_scope:ListAddressScope From 6f616a29b300238c004b676edd98a5337be38193 Mon Sep 17 00:00:00 2001 From: youngho choi <0505zxc@gmail.com> Date: Mon, 7 Sep 2020 06:35:04 +0900 Subject: [PATCH 2306/3095] Add support '--progress' option for 'image create' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit openstack-client doesn’t support the upload progress bar. This patch shows progressbar when create image if you added '--progress' option like a python-glanceclient. like this. [=============================>] 100% +------------------+---------------------------+ | Field | Value | +------------------+---------------------------+ | container_format | bare | | created_at | 2020-09-06T20:44:40Z | ... How to use Add the'--progress' option on the 'openstack image create' command. Code was written by referring to 'python-glanceclient' project on stable/ussuri branch Change-Id: Ic3035b49da10b6555066eee607a14a5b73797c00 task: 40003 story: 2007777 --- openstackclient/common/progressbar.py | 67 ++++++++++++++++ openstackclient/image/v2/image.py | 12 +++ .../tests/unit/common/test_progressbar.py | 77 +++++++++++++++++++ ...ess-option-to-create-1ed1881d58ebad4b.yaml | 5 ++ 4 files changed, 161 insertions(+) create mode 100644 openstackclient/common/progressbar.py create mode 100644 openstackclient/tests/unit/common/test_progressbar.py create mode 100644 releasenotes/notes/add-image-progress-option-to-create-1ed1881d58ebad4b.yaml diff --git a/openstackclient/common/progressbar.py b/openstackclient/common/progressbar.py new file mode 100644 index 0000000000..ef767a9cf8 --- /dev/null +++ b/openstackclient/common/progressbar.py @@ -0,0 +1,67 @@ +# Copyright 2013 OpenStack Foundation +# 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 sys + + +class _ProgressBarBase(object): + """A progress bar provider for a wrapped obect. + + Base abstract class used by specific class wrapper to show + a progress bar when the wrapped object are consumed. + + :param wrapped: Object to wrap that hold data to be consumed. + :param totalsize: The total size of the data in the wrapped object. + + :note: The progress will be displayed only if sys.stdout is a tty. + """ + + def __init__(self, wrapped, totalsize): + self._wrapped = wrapped + self._totalsize = float(totalsize) + self._show_progress = sys.stdout.isatty() and self._totalsize != 0 + self._percent = 0 + + def _display_progress_bar(self, size_read): + if self._show_progress: + self._percent += size_read / self._totalsize + # Output something like this: [==========> ] 49% + sys.stdout.write('\r[{0:<30}] {1:.0%}'.format( + '=' * int(round(self._percent * 29)) + '>', self._percent + )) + sys.stdout.flush() + + def __getattr__(self, attr): + # Forward other attribute access to the wrapped object. + return getattr(self._wrapped, attr) + + +class VerboseFileWrapper(_ProgressBarBase): + """A file wrapper with a progress bar. + + The file wrapper shows and advances a progress bar whenever the + wrapped file's read method is called. + """ + + def read(self, *args, **kwargs): + data = self._wrapped.read(*args, **kwargs) + if data: + self._display_progress_bar(len(data)) + else: + if self._show_progress: + # Break to a new line from the progress bar for incoming + # output. + sys.stdout.write('\n') + return data diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index fa7f4be562..7177929339 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -30,6 +30,7 @@ from osc_lib import exceptions from osc_lib import utils +from openstackclient.common import progressbar from openstackclient.common import sdk_utils from openstackclient.i18n import _ from openstackclient.identity import common @@ -255,6 +256,12 @@ def get_parser(self, prog_name): help=_("Force image creation if volume is in use " "(only meaningful with --volume)"), ) + parser.add_argument( + "--progress", + action="store_true", + default=False, + help=_("Show upload progress bar."), + ) parser.add_argument( '--sign-key-path', metavar="", @@ -412,6 +419,11 @@ def take_action(self, parsed_args): if fp is None and parsed_args.file: LOG.warning(_("Failed to get an image file.")) return {}, {} + if fp is not None and parsed_args.progress: + filesize = os.path.getsize(fname) + if filesize is not None: + kwargs['validate_checksum'] = False + kwargs['data'] = progressbar.VerboseFileWrapper(fp, filesize) elif fname: kwargs['filename'] = fname elif fp: diff --git a/openstackclient/tests/unit/common/test_progressbar.py b/openstackclient/tests/unit/common/test_progressbar.py new file mode 100644 index 0000000000..7bc0b6baf4 --- /dev/null +++ b/openstackclient/tests/unit/common/test_progressbar.py @@ -0,0 +1,77 @@ +# 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 sys + +import six + +from openstackclient.common import progressbar +from openstackclient.tests.unit import utils + + +class TestProgressBarWrapper(utils.TestCase): + + def test_iter_file_display_progress_bar(self): + size = 98304 + file_obj = six.StringIO('X' * size) + saved_stdout = sys.stdout + try: + sys.stdout = output = FakeTTYStdout() + file_obj = progressbar.VerboseFileWrapper(file_obj, size) + chunksize = 1024 + chunk = file_obj.read(chunksize) + while chunk: + chunk = file_obj.read(chunksize) + self.assertEqual( + '[%s>] 100%%\n' % ('=' * 29), + output.getvalue() + ) + finally: + sys.stdout = saved_stdout + + def test_iter_file_no_tty(self): + size = 98304 + file_obj = six.StringIO('X' * size) + saved_stdout = sys.stdout + try: + sys.stdout = output = FakeNoTTYStdout() + file_obj = progressbar.VerboseFileWrapper(file_obj, size) + chunksize = 1024 + chunk = file_obj.read(chunksize) + while chunk: + chunk = file_obj.read(chunksize) + # If stdout is not a tty progress bar should do nothing. + self.assertEqual('', output.getvalue()) + finally: + sys.stdout = saved_stdout + + +class FakeTTYStdout(six.StringIO): + """A Fake stdout that try to emulate a TTY device as much as possible.""" + + def isatty(self): + return True + + def write(self, data): + # When a CR (carriage return) is found reset file. + if data.startswith('\r'): + self.seek(0) + data = data[1:] + return six.StringIO.write(self, data) + + +class FakeNoTTYStdout(FakeTTYStdout): + """A Fake stdout that is not a TTY device.""" + + def isatty(self): + return False diff --git a/releasenotes/notes/add-image-progress-option-to-create-1ed1881d58ebad4b.yaml b/releasenotes/notes/add-image-progress-option-to-create-1ed1881d58ebad4b.yaml new file mode 100644 index 0000000000..593c5bd3b8 --- /dev/null +++ b/releasenotes/notes/add-image-progress-option-to-create-1ed1881d58ebad4b.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``--progress`` option to ``image create`` command to enable a progress + bar when creating and uploading an image. From 03776d82e58622b30b90260ed9c374b0cfc70f2b Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 4 Nov 2020 10:14:00 +0000 Subject: [PATCH 2307/3095] compute: Fix 'server * -f yaml' output Make use of 'FormattableColumn'-derived formatters, which provide better output than what we were using before, particularly for the YAML output format. For example, compare before for the 'server show' command: $ openstack --os-compute-api-version 2.79 server show test-server -f yaml ... addresses: private=fdff:77e3:9bb4:0:f816:3eff:fe6d:a944, 10.0.0.44 flavor: disk='1', ephemeral='0', extra_specs.hw_rng:allowed='True', original_name='m1.tiny', ram='512', swap='0', vcpus='1' ... To after: $ openstack --os-compute-api-version 2.79 server show test-server -f yaml ... addresses: private: - fdff:77e3:9bb4:0:f816:3eff:fe6d:a944 - 10.0.0.44 flavor: disk: 1 ephemeral: 0 extra_specs: hw_rng:allowed: 'True' original_name: m1.tiny ram: 512 swap: 0 vcpus: 1 ... Similarly, compare before for 'server list': $ openstack --os-compute-api-version 2.79 server list -f yaml - ... Networks: private=fdff:77e3:9bb4:0:f816:3eff:fe6d:a944, 10.0.0.44 Power State: Running Properties: '' ... To after: $ openstack --os-compute-api-version 2.79 server list -f yaml - ... Networks: private: - fdff:77e3:9bb4:0:f816:3eff:fe6d:a944 - 10.0.0.44 Power State: 1 Properties: {} ... We also fix the human-readable output for the 'tags' field. Before: $ openstack --os-compute-api-version 2.79 server list ... | tags | ['bar', 'foo'] | After: $ openstack --os-compute-api-version 2.79 server list ... | tags | bar, foo | Change-Id: I7a8349106e211c57c4577b75326b39b88bd9ac1e Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server.py | 58 +++----- .../functional/compute/v2/test_server.py | 20 ++- .../tests/unit/compute/v2/test_server.py | 126 ++++++++---------- ...proved-server-output-6965b664f6abda8d.yaml | 10 ++ 4 files changed, 99 insertions(+), 115 deletions(-) create mode 100644 releasenotes/notes/improved-server-output-6965b664f6abda8d.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 2512a8778c..ba0243ef3e 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -21,6 +21,7 @@ import logging import os +from cliff import columns as cliff_columns import iso8601 from novaclient import api_versions from novaclient.v2 import servers @@ -41,28 +42,9 @@ IMAGE_STRING_FOR_BFV = 'N/A (booted from volume)' -def _format_servers_list_networks(networks): - """Return a formatted string of a server's networks +class PowerStateColumn(cliff_columns.FormattableColumn): + """Generate a formatted string of a server's power state.""" - :param networks: a Server.networks field - :rtype: a string of formatted network addresses - """ - output = [] - for (network, addresses) in networks.items(): - if not addresses: - continue - addresses_csv = ', '.join(addresses) - group = "%s=%s" % (network, addresses_csv) - output.append(group) - return '; '.join(output) - - -def _format_servers_list_power_state(state): - """Return a formatted string of a server's power state - - :param state: the power state number of a server - :rtype: a string mapped to the power state number - """ power_states = [ 'NOSTATE', # 0x00 'Running', # 0x01 @@ -74,10 +56,11 @@ def _format_servers_list_power_state(state): 'Suspended' # 0x07 ] - try: - return power_states[state] - except Exception: - return 'N/A' + def human_readable(self): + try: + return self.power_states[self._value] + except Exception: + return 'N/A' def _get_ip_address(addresses, address_type, ip_address_family): @@ -169,7 +152,7 @@ def _prep_server_detail(compute_client, image_client, server, refresh=True): except Exception: info['flavor'] = flavor_id else: - info['flavor'] = utils.format_dict(flavor_info) + info['flavor'] = format_columns.DictColumn(flavor_info) if 'os-extended-volumes:volumes_attached' in info: info.update( @@ -185,19 +168,15 @@ def _prep_server_detail(compute_client, image_client, server, refresh=True): info.pop('security_groups')) } ) + if 'tags' in info: + info.update({'tags': format_columns.ListColumn(info.pop('tags'))}) + # NOTE(dtroyer): novaclient splits these into separate entries... # Format addresses in a useful way - info['addresses'] = _format_servers_list_networks(server.networks) + info['addresses'] = format_columns.DictListColumn(server.networks) # Map 'metadata' field to 'properties' - if not info['metadata']: - info.update( - {'properties': utils.format_dict(info.pop('metadata'))} - ) - else: - info.update( - {'properties': format_columns.DictColumn(info.pop('metadata'))} - ) + info['properties'] = format_columns.DictColumn(info.pop('metadata')) # Migrate tenant_id to project_id naming if 'tenant_id' in info: @@ -205,7 +184,7 @@ def _prep_server_detail(compute_client, image_client, server, refresh=True): # Map power state num to meaningful string if 'OS-EXT-STS:power_state' in info: - info['OS-EXT-STS:power_state'] = _format_servers_list_power_state( + info['OS-EXT-STS:power_state'] = PowerStateColumn( info['OS-EXT-STS:power_state']) # Remove values that are long and not too useful @@ -1873,10 +1852,9 @@ def take_action(self, parsed_args): s, columns, mixed_case_fields=mixed_case_fields, formatters={ - 'OS-EXT-STS:power_state': - _format_servers_list_power_state, - 'Networks': _format_servers_list_networks, - 'Metadata': utils.format_dict, + 'OS-EXT-STS:power_state': PowerStateColumn, + 'Networks': format_columns.DictListColumn, + 'Metadata': format_columns.DictColumn, }, ) for s in data ), diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py index 44d9c61ffb..bad3f93d47 100644 --- a/openstackclient/tests/functional/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import itertools import json import time import uuid @@ -346,6 +347,14 @@ def test_server_attach_detach_floating_ip(self): # DevStack without cells. self.skipTest("No Network service present") + def _chain_addresses(addresses): + # Flatten a dict of lists mapping network names to IP addresses, + # returning only the IP addresses + # + # >>> _chain_addresses({'private': ['10.1.0.32', '172.24.5.41']}) + # ['10.1.0.32', '172.24.5.41'] + return itertools.chain(*[*addresses.values()]) + cmd_output = self.server_create() name = cmd_output['name'] self.wait_for_status(name, "ACTIVE") @@ -387,7 +396,7 @@ def test_server_attach_detach_floating_ip(self): 'server show -f json ' + name )) - if floating_ip not in cmd_output['addresses']: + if floating_ip not in _chain_addresses(cmd_output['addresses']): # Hang out for a bit and try again print('retrying floating IP check') wait_time += 10 @@ -397,7 +406,7 @@ def test_server_attach_detach_floating_ip(self): self.assertIn( floating_ip, - cmd_output['addresses'], + _chain_addresses(cmd_output['addresses']), ) # detach ip @@ -417,7 +426,7 @@ def test_server_attach_detach_floating_ip(self): 'server show -f json ' + name )) - if floating_ip in cmd_output['addresses']: + if floating_ip in _chain_addresses(cmd_output['addresses']): # Hang out for a bit and try again print('retrying floating IP check') wait_time += 10 @@ -431,7 +440,7 @@ def test_server_attach_detach_floating_ip(self): )) self.assertNotIn( floating_ip, - cmd_output['addresses'], + _chain_addresses(cmd_output['addresses']), ) def test_server_reboot(self): @@ -856,8 +865,7 @@ def test_server_create_with_none_network(self): server = json.loads(self.openstack( 'server show -f json ' + server_name )) - self.assertIsNotNone(server['addresses']) - self.assertEqual('', server['addresses']) + self.assertEqual({}, server['addresses']) def test_server_create_with_security_group(self): """Test server create with security group ID and name""" diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 1c8541936b..efc105e565 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -22,6 +22,7 @@ import iso8601 from novaclient import api_versions from openstack import exceptions as sdk_exceptions +from osc_lib.cli import format_columns from osc_lib import exceptions from osc_lib import utils as common_utils @@ -33,6 +34,29 @@ from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes +class TestPowerStateColumn(utils.TestCase): + + def test_human_readable(self): + self.assertEqual( + 'NOSTATE', server.PowerStateColumn(0x00).human_readable()) + self.assertEqual( + 'Running', server.PowerStateColumn(0x01).human_readable()) + self.assertEqual( + '', server.PowerStateColumn(0x02).human_readable()) + self.assertEqual( + 'Paused', server.PowerStateColumn(0x03).human_readable()) + self.assertEqual( + 'Shutdown', server.PowerStateColumn(0x04).human_readable()) + self.assertEqual( + '', server.PowerStateColumn(0x05).human_readable()) + self.assertEqual( + 'Crashed', server.PowerStateColumn(0x06).human_readable()) + self.assertEqual( + 'Suspended', server.PowerStateColumn(0x07).human_readable()) + self.assertEqual( + 'N/A', server.PowerStateColumn(0x08).human_readable()) + + class TestServer(compute_fakes.TestComputev2): def setUp(self): @@ -1015,15 +1039,15 @@ class TestServerCreate(TestServer): def datalist(self): datalist = ( - server._format_servers_list_power_state( + server.PowerStateColumn( getattr(self.new_server, 'OS-EXT-STS:power_state')), - '', + format_columns.DictListColumn({}), self.flavor.name + ' (' + self.new_server.flavor.get('id') + ')', self.new_server.id, self.image.name + ' (' + self.new_server.image.get('id') + ')', self.new_server.name, self.new_server.networks, - '', + format_columns.DictColumn(self.new_server.metadata), ) return datalist @@ -3041,7 +3065,7 @@ def setUp(self): s.id, s.name, s.status, - server._format_servers_list_networks(s.networks), + format_columns.DictListColumn(s.networks), # Image will be an empty string if boot-from-volume self.image.name if s.image else server.IMAGE_STRING_FOR_BFV, self.flavor.name, @@ -3051,10 +3075,10 @@ def setUp(self): s.name, s.status, getattr(s, 'OS-EXT-STS:task_state'), - server._format_servers_list_power_state( + server.PowerStateColumn( getattr(s, 'OS-EXT-STS:power_state') ), - server._format_servers_list_networks(s.networks), + format_columns.DictListColumn(s.networks), # Image will be an empty string if boot-from-volume self.image.name if s.image else server.IMAGE_STRING_FOR_BFV, s.image['id'] if s.image else server.IMAGE_STRING_FOR_BFV, @@ -3062,13 +3086,13 @@ def setUp(self): s.flavor['id'], getattr(s, 'OS-EXT-AZ:availability_zone'), getattr(s, 'OS-EXT-SRV-ATTR:host'), - s.Metadata, + format_columns.DictColumn({}), )) self.data_no_name_lookup.append(( s.id, s.name, s.status, - server._format_servers_list_networks(s.networks), + format_columns.DictListColumn(s.networks), # Image will be an empty string if boot-from-volume s.image['id'] if s.image else server.IMAGE_STRING_FOR_BFV, s.flavor['id'] @@ -3128,7 +3152,7 @@ def test_server_list_long_option(self): self.servers_mock.list.assert_called_with(**self.kwargs) self.assertEqual(self.columns_long, columns) - self.assertEqual(tuple(self.data_long), tuple(data)) + self.assertCountEqual(tuple(self.data_long), tuple(data)) def test_server_list_column_option(self): arglist = [ @@ -3429,9 +3453,11 @@ def test_server_with_changes_before_older_version(self): def test_server_list_v269_with_partial_constructs(self): self.app.client_manager.compute.api_version = \ api_versions.APIVersion('2.69') + arglist = [] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + # include "partial results" from non-responsive part of # infrastructure. server_dict = { @@ -3454,10 +3480,10 @@ def test_server_list_v269_with_partial_constructs(self): # it will fail at formatting the networks info later on. "networks": {} } - server = compute_fakes.fakes.FakeResource( + _server = compute_fakes.fakes.FakeResource( info=server_dict, ) - self.servers.append(server) + self.servers.append(_server) columns, data = self.cmd.take_action(parsed_args) # get the first three servers out since our interest is in the partial # server. @@ -3466,8 +3492,13 @@ def test_server_list_v269_with_partial_constructs(self): next(data) partial_server = next(data) expected_row = ( - 'server-id-95a56bfc4xxxxxx28d7e418bfd97813a', '', - 'UNKNOWN', '', '', '') + 'server-id-95a56bfc4xxxxxx28d7e418bfd97813a', + '', + 'UNKNOWN', + format_columns.DictListColumn({}), + '', + '', + ) self.assertEqual(expected_row, partial_server) def test_server_list_with_tag(self): @@ -6292,15 +6323,16 @@ def setUp(self): ) self.data = ( - 'Running', - 'public=10.20.30.40, 2001:db8::f', + server.PowerStateColumn( + getattr(self.server, 'OS-EXT-STS:power_state')), + format_columns.DictListColumn(self.server.networks), self.flavor.name + " (" + self.flavor.id + ")", self.server.id, self.image.name + " (" + self.image.id + ")", self.server.name, {'public': ['10.20.30.40', '2001:db8::f']}, 'tenant-id-xxx', - '', + format_columns.DictColumn({}), ) def test_show_no_options(self): @@ -6323,7 +6355,7 @@ def test_show(self): columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_show_embedded_flavor(self): # Tests using --os-compute-api-version >= 2.47 where the flavor @@ -6350,7 +6382,7 @@ def test_show_embedded_flavor(self): self.assertEqual(self.columns, columns) # Since the flavor details are in a dict we can't be sure of the # ordering so just assert that one of the keys is in the output. - self.assertIn('original_name', data[2]) + self.assertIn('original_name', data[2]._value) def test_show_diagnostics(self): arglist = [ @@ -6719,50 +6751,6 @@ def test_get_ip_address(self): self.assertRaises(exceptions.CommandError, server._get_ip_address, self.OLD, 'private', [6]) - def test_format_servers_list_power_state(self): - self.assertEqual("NOSTATE", - server._format_servers_list_power_state(0x00)) - self.assertEqual("Running", - server._format_servers_list_power_state(0x01)) - self.assertEqual("", - server._format_servers_list_power_state(0x02)) - self.assertEqual("Paused", - server._format_servers_list_power_state(0x03)) - self.assertEqual("Shutdown", - server._format_servers_list_power_state(0x04)) - self.assertEqual("", - server._format_servers_list_power_state(0x05)) - self.assertEqual("Crashed", - server._format_servers_list_power_state(0x06)) - self.assertEqual("Suspended", - server._format_servers_list_power_state(0x07)) - self.assertEqual("N/A", - server._format_servers_list_power_state(0x08)) - - def test_format_servers_list_networks(self): - # Setup network info to test. - networks = { - u'public': [u'10.20.30.40', u'2001:db8::f'], - u'private': [u'2001:db8::f', u'10.20.30.40'], - } - - # Prepare expected data. - # Since networks is a dict, whose items are in random order, there - # could be two results after formatted. - data_1 = (u'private=2001:db8::f, 10.20.30.40; ' - u'public=10.20.30.40, 2001:db8::f') - data_2 = (u'public=10.20.30.40, 2001:db8::f; ' - u'private=2001:db8::f, 10.20.30.40') - - # Call _format_servers_list_networks(). - networks_format = server._format_servers_list_networks(networks) - - msg = ('Network string is not formatted correctly.\n' - 'reference = %s or %s\n' - 'actual = %s\n' % - (data_1, data_2, networks_format)) - self.assertIn(networks_format, (data_1, data_2), msg) - @mock.patch('osc_lib.utils.find_resource') def test_prep_server_detail(self, find_resource): # Setup mock method return value. utils.find_resource() will be called @@ -6789,14 +6777,14 @@ def test_prep_server_detail(self, find_resource): info = { 'id': _server.id, 'name': _server.name, - 'addresses': u'public=10.20.30.40, 2001:db8::f', - 'flavor': u'%s (%s)' % (_flavor.name, _flavor.id), - 'image': u'%s (%s)' % (_image.name, _image.id), - 'project_id': u'tenant-id-xxx', - 'properties': '', - 'OS-EXT-STS:power_state': server._format_servers_list_power_state( + 'image': '%s (%s)' % (_image.name, _image.id), + 'flavor': '%s (%s)' % (_flavor.name, _flavor.id), + 'OS-EXT-STS:power_state': server.PowerStateColumn( getattr(_server, 'OS-EXT-STS:power_state')), + 'properties': '', 'volumes_attached': [{"id": "6344fe9d-ef20-45b2-91a6"}], + 'addresses': format_columns.DictListColumn(_server.networks), + 'project_id': 'tenant-id-xxx', } # Call _prep_server_detail(). @@ -6809,4 +6797,4 @@ def test_prep_server_detail(self, find_resource): server_detail.pop('networks') # Check the results. - self.assertEqual(info, server_detail) + self.assertCountEqual(info, server_detail) diff --git a/releasenotes/notes/improved-server-output-6965b664f6abda8d.yaml b/releasenotes/notes/improved-server-output-6965b664f6abda8d.yaml new file mode 100644 index 0000000000..cbe950eace --- /dev/null +++ b/releasenotes/notes/improved-server-output-6965b664f6abda8d.yaml @@ -0,0 +1,10 @@ +--- +fixes: + - | + The ``addresses`` and ``flavor`` fields of the ``server show`` command will + now be correctly rendered as a list of objects and an object, respectively. + - | + The ``networks`` and ``properties`` fields of the ``server list`` command + will now be rendered as objects. In addition, the ``power_state`` field + will now be humanized and rendered as a string value when using the table + formatter. From af5e9d16e8a00c0d382b7090c66df211fefc0b3c Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 5 Nov 2020 11:22:05 +0000 Subject: [PATCH 2308/3095] compute: Fix 'usage * -f yaml' output Make use of 'FormattableColumn'-derived formatters, which provide better output than what we were using before, particularly for the YAML output format. Change-Id: Ic770f27cb1f74222636f05350f97400808adffbf Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/usage.py | 103 ++++++++++++++---- .../tests/unit/compute/v2/test_usage.py | 42 +++---- ...proved-server-output-6965b664f6abda8d.yaml | 6 + 3 files changed, 107 insertions(+), 44 deletions(-) diff --git a/openstackclient/compute/v2/usage.py b/openstackclient/compute/v2/usage.py index 307c238afe..69fa04e8ba 100644 --- a/openstackclient/compute/v2/usage.py +++ b/openstackclient/compute/v2/usage.py @@ -17,7 +17,9 @@ import collections import datetime +import functools +from cliff import columns as cliff_columns from novaclient import api_versions from osc_lib.command import command from osc_lib import utils @@ -25,6 +27,57 @@ from openstackclient.i18n import _ +# TODO(stephenfin): This exists in a couple of places and should be moved to a +# common module +class ProjectColumn(cliff_columns.FormattableColumn): + """Formattable column for project column. + + Unlike the parent FormattableColumn class, the initializer of the class + takes project_cache as the second argument. + ``osc_lib.utils.get_item_properties`` instantiates ``FormattableColumn`` + objects with a single parameter, the column value, so you need to pass a + partially initialized class like ``functools.partial(ProjectColumn, + project_cache)`` to use this. + """ + + def __init__(self, value, project_cache=None): + super().__init__(value) + self.project_cache = project_cache or {} + + def human_readable(self): + project = self._value + if not project: + return '' + + if project in self.project_cache.keys(): + return self.project_cache[project].name + + return project + + +class CountColumn(cliff_columns.FormattableColumn): + + def human_readable(self): + return len(self._value) + + +class FloatColumn(cliff_columns.FormattableColumn): + + def human_readable(self): + return float("%.2f" % self._value) + + +def _formatters(project_cache): + return { + 'tenant_id': functools.partial( + ProjectColumn, project_cache=project_cache), + 'server_usages': CountColumn, + 'total_memory_mb_usage': FloatColumn, + 'total_vcpus_usage': FloatColumn, + 'total_local_gb_usage': FloatColumn, + } + + def _get_usage_marker(usage): marker = None if hasattr(usage, 'server_usages') and usage.server_usages: @@ -147,17 +200,15 @@ def _format_project(project): "end": end.strftime(dateformat), }) - return (column_headers, - (utils.get_item_properties( + return ( + column_headers, + ( + utils.get_item_properties( s, columns, - formatters={ - 'tenant_id': _format_project, - 'server_usages': lambda x: len(x), - 'total_memory_mb_usage': lambda x: float("%.2f" % x), - 'total_vcpus_usage': lambda x: float("%.2f" % x), - 'total_local_gb_usage': lambda x: float("%.2f" % x), - }, - ) for s in usage_list)) + formatters=_formatters(project_cache), + ) for s in usage_list + ), + ) class ShowUsage(command.ShowOne): @@ -222,17 +273,21 @@ def take_action(self, parsed_args): "project": project, }) - info = {} - info['Servers'] = ( - len(usage.server_usages) - if hasattr(usage, "server_usages") else None) - info['RAM MB-Hours'] = ( - float("%.2f" % usage.total_memory_mb_usage) - if hasattr(usage, "total_memory_mb_usage") else None) - info['CPU Hours'] = ( - float("%.2f" % usage.total_vcpus_usage) - if hasattr(usage, "total_vcpus_usage") else None) - info['Disk GB-Hours'] = ( - float("%.2f" % usage.total_local_gb_usage) - if hasattr(usage, "total_local_gb_usage") else None) - return zip(*sorted(info.items())) + columns = ( + "tenant_id", + "server_usages", + "total_memory_mb_usage", + "total_vcpus_usage", + "total_local_gb_usage" + ) + column_headers = ( + "Project", + "Servers", + "RAM MB-Hours", + "CPU Hours", + "Disk GB-Hours" + ) + + data = utils.get_item_properties( + usage, columns, formatters=_formatters(None)) + return column_headers, data diff --git a/openstackclient/tests/unit/compute/v2/test_usage.py b/openstackclient/tests/unit/compute/v2/test_usage.py index c08710256b..bbccb9bdc7 100644 --- a/openstackclient/tests/unit/compute/v2/test_usage.py +++ b/openstackclient/tests/unit/compute/v2/test_usage.py @@ -16,7 +16,7 @@ from novaclient import api_versions -from openstackclient.compute.v2 import usage +from openstackclient.compute.v2 import usage as usage_cmds from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes @@ -49,11 +49,11 @@ class TestUsageList(TestUsage): ) data = [( - usages[0].tenant_id, - len(usages[0].server_usages), - float("%.2f" % usages[0].total_memory_mb_usage), - float("%.2f" % usages[0].total_vcpus_usage), - float("%.2f" % usages[0].total_local_gb_usage), + usage_cmds.ProjectColumn(usages[0].tenant_id), + usage_cmds.CountColumn(usages[0].server_usages), + usage_cmds.FloatColumn(usages[0].total_memory_mb_usage), + usage_cmds.FloatColumn(usages[0].total_vcpus_usage), + usage_cmds.FloatColumn(usages[0].total_local_gb_usage), )] def setUp(self): @@ -63,7 +63,7 @@ def setUp(self): self.projects_mock.list.return_value = [self.project] # Get the command object to test - self.cmd = usage.ListUsage(self.app, None) + self.cmd = usage_cmds.ListUsage(self.app, None) def test_usage_list_no_options(self): @@ -79,8 +79,8 @@ def test_usage_list_no_options(self): self.projects_mock.list.assert_called_with() - self.assertEqual(self.columns, columns) - self.assertEqual(tuple(self.data), tuple(data)) + self.assertCountEqual(self.columns, columns) + self.assertCountEqual(tuple(self.data), tuple(data)) def test_usage_list_with_options(self): arglist = [ @@ -102,8 +102,8 @@ def test_usage_list_with_options(self): datetime.datetime(2016, 12, 20, 0, 0), detailed=True) - self.assertEqual(self.columns, columns) - self.assertEqual(tuple(self.data), tuple(data)) + self.assertCountEqual(self.columns, columns) + self.assertCountEqual(tuple(self.data), tuple(data)) def test_usage_list_with_pagination(self): arglist = [] @@ -127,8 +127,8 @@ def test_usage_list_with_pagination(self): mock.call(mock.ANY, mock.ANY, detailed=True, marker=self.usages[0]['server_usages'][0]['instance_id']) ]) - self.assertEqual(self.columns, columns) - self.assertEqual(tuple(self.data), tuple(data)) + self.assertCountEqual(self.columns, columns) + self.assertCountEqual(tuple(self.data), tuple(data)) class TestUsageShow(TestUsage): @@ -139,17 +139,19 @@ class TestUsageShow(TestUsage): attrs={'tenant_id': project.name}) columns = ( + 'Project', + 'Servers', + 'RAM MB-Hours', 'CPU Hours', 'Disk GB-Hours', - 'RAM MB-Hours', - 'Servers', ) data = ( - float("%.2f" % usage.total_vcpus_usage), - float("%.2f" % usage.total_local_gb_usage), - float("%.2f" % usage.total_memory_mb_usage), - len(usage.server_usages), + usage_cmds.ProjectColumn(usage.tenant_id), + usage_cmds.CountColumn(usage.server_usages), + usage_cmds.FloatColumn(usage.total_memory_mb_usage), + usage_cmds.FloatColumn(usage.total_vcpus_usage), + usage_cmds.FloatColumn(usage.total_local_gb_usage), ) def setUp(self): @@ -159,7 +161,7 @@ def setUp(self): self.projects_mock.get.return_value = self.project # Get the command object to test - self.cmd = usage.ShowUsage(self.app, None) + self.cmd = usage_cmds.ShowUsage(self.app, None) def test_usage_show_no_options(self): diff --git a/releasenotes/notes/improved-server-output-6965b664f6abda8d.yaml b/releasenotes/notes/improved-server-output-6965b664f6abda8d.yaml index cbe950eace..7a42fa899d 100644 --- a/releasenotes/notes/improved-server-output-6965b664f6abda8d.yaml +++ b/releasenotes/notes/improved-server-output-6965b664f6abda8d.yaml @@ -8,3 +8,9 @@ fixes: will now be rendered as objects. In addition, the ``power_state`` field will now be humanized and rendered as a string value when using the table formatter. + - | + The ``usage list`` and ``usage show`` commands will now display the name + of the project being queried rather than the ID when using the table + formatter. In addition, the ``server_usages``, ``total_memory_mb_usage``, + ``total_vcpus_usage`` and ``total_local_gb_usage`` values will only be + humanized when using the table formatter. From e2a9a9607cc84c0afc2fc5524681a3adebdc68ec Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 5 Nov 2020 11:29:54 +0000 Subject: [PATCH 2309/3095] compute: Fix 'server group * -f yaml' output Make use of 'FormattableColumn'-derived formatters, which provide better output than what we were using before, particularly for the YAML output format. Change-Id: Id6d25a0a348596d5a0430ff7afbf87b049a76bc8 Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server_group.py | 27 ++++++----- .../compute/v2/test_server_group.py | 12 ++--- .../unit/compute/v2/test_server_group.py | 48 ++++++++++--------- ...proved-server-output-6965b664f6abda8d.yaml | 4 ++ 4 files changed, 51 insertions(+), 40 deletions(-) diff --git a/openstackclient/compute/v2/server_group.py b/openstackclient/compute/v2/server_group.py index a33632446c..d245c0927f 100644 --- a/openstackclient/compute/v2/server_group.py +++ b/openstackclient/compute/v2/server_group.py @@ -18,6 +18,7 @@ import logging from novaclient import api_versions +from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -29,8 +30,8 @@ _formatters = { - 'policies': utils.format_list, - 'members': utils.format_list, + 'policies': format_columns.ListColumn, + 'members': format_columns.ListColumn, } @@ -93,8 +94,8 @@ def take_action(self, parsed_args): info.update(server_group._info) columns = _get_columns(info) - data = utils.get_dict_properties(info, columns, - formatters=_formatters) + data = utils.get_dict_properties( + info, columns, formatters=_formatters) return columns, data @@ -176,14 +177,18 @@ def take_action(self, parsed_args): policy_key, ) - return (column_headers, - (utils.get_item_properties( + return ( + column_headers, + ( + utils.get_item_properties( s, columns, formatters={ - 'Policies': utils.format_list, - 'Members': utils.format_list, + 'Policies': format_columns.ListColumn, + 'Members': format_columns.ListColumn, } - ) for s in data)) + ) for s in data + ), + ) class ShowServerGroup(command.ShowOne): @@ -205,6 +210,6 @@ def take_action(self, parsed_args): info = {} info.update(group._info) columns = _get_columns(info) - data = utils.get_dict_properties(info, columns, - formatters=_formatters) + data = utils.get_dict_properties( + info, columns, formatters=_formatters) return columns, data diff --git a/openstackclient/tests/functional/compute/v2/test_server_group.py b/openstackclient/tests/functional/compute/v2/test_server_group.py index 44ecda1de7..3dff3dcdaf 100644 --- a/openstackclient/tests/functional/compute/v2/test_server_group.py +++ b/openstackclient/tests/functional/compute/v2/test_server_group.py @@ -33,7 +33,7 @@ def test_server_group_delete(self): cmd_output['name'] ) self.assertEqual( - 'affinity', + ['affinity'], cmd_output['policies'] ) @@ -47,7 +47,7 @@ def test_server_group_delete(self): cmd_output['name'] ) self.assertEqual( - 'anti-affinity', + ['anti-affinity'], cmd_output['policies'] ) @@ -74,7 +74,7 @@ def test_server_group_show_and_list(self): cmd_output['name'] ) self.assertEqual( - 'affinity', + ['affinity'], cmd_output['policies'] ) @@ -91,7 +91,7 @@ def test_server_group_show_and_list(self): cmd_output['name'] ) self.assertEqual( - 'anti-affinity', + ['anti-affinity'], cmd_output['policies'] ) @@ -102,5 +102,5 @@ def test_server_group_show_and_list(self): self.assertIn(name1, names) self.assertIn(name2, names) policies = [x["Policies"] for x in cmd_output] - self.assertIn('affinity', policies) - self.assertIn('anti-affinity', policies) + self.assertIn(['affinity'], policies) + self.assertIn(['anti-affinity'], policies) diff --git a/openstackclient/tests/unit/compute/v2/test_server_group.py b/openstackclient/tests/unit/compute/v2/test_server_group.py index bf0ea0ba45..8de7492c79 100644 --- a/openstackclient/tests/unit/compute/v2/test_server_group.py +++ b/openstackclient/tests/unit/compute/v2/test_server_group.py @@ -16,6 +16,7 @@ from unittest import mock from novaclient import api_versions +from osc_lib.cli import format_columns from osc_lib import exceptions from osc_lib import utils @@ -39,9 +40,9 @@ class TestServerGroup(compute_fakes.TestComputev2): data = ( fake_server_group.id, - utils.format_list(fake_server_group.members), + format_columns.ListColumn(fake_server_group.members), fake_server_group.name, - utils.format_list(fake_server_group.policies), + format_columns.ListColumn(fake_server_group.policies), fake_server_group.project_id, fake_server_group.user_id, ) @@ -70,7 +71,7 @@ class TestServerGroupV264(TestServerGroup): data = ( fake_server_group.id, - utils.format_list(fake_server_group.members), + format_columns.ListColumn(fake_server_group.members), fake_server_group.name, fake_server_group.policy, fake_server_group.project_id, @@ -105,8 +106,8 @@ def test_server_group_create(self): policies=[parsed_args.policy], ) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertCountEqual(self.columns, columns) + self.assertCountEqual(self.data, data) def test_server_group_create_with_soft_policies(self): self.app.client_manager.compute.api_version = api_versions.APIVersion( @@ -127,8 +128,8 @@ def test_server_group_create_with_soft_policies(self): policies=[parsed_args.policy], ) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertCountEqual(self.columns, columns) + self.assertCountEqual(self.data, data) def test_server_group_create_with_soft_policies_pre_v215(self): self.app.client_manager.compute.api_version = api_versions.APIVersion( @@ -170,8 +171,8 @@ def test_server_group_create_v264(self): policy=parsed_args.policy, ) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertCountEqual(self.columns, columns) + self.assertCountEqual(self.data, data) class TestServerGroupDelete(TestServerGroup): @@ -275,14 +276,14 @@ class TestServerGroupList(TestServerGroup): list_data = (( TestServerGroup.fake_server_group.id, TestServerGroup.fake_server_group.name, - utils.format_list(TestServerGroup.fake_server_group.policies), + format_columns.ListColumn(TestServerGroup.fake_server_group.policies), ),) list_data_long = (( TestServerGroup.fake_server_group.id, TestServerGroup.fake_server_group.name, - utils.format_list(TestServerGroup.fake_server_group.policies), - utils.format_list(TestServerGroup.fake_server_group.members), + format_columns.ListColumn(TestServerGroup.fake_server_group.policies), + format_columns.ListColumn(TestServerGroup.fake_server_group.members), TestServerGroup.fake_server_group.project_id, TestServerGroup.fake_server_group.user_id, ),) @@ -303,8 +304,8 @@ def test_server_group_list(self): columns, data = self.cmd.take_action(parsed_args) self.server_groups_mock.list.assert_called_once_with(False) - self.assertEqual(self.list_columns, columns) - self.assertEqual(self.list_data, tuple(data)) + self.assertCountEqual(self.list_columns, columns) + self.assertCountEqual(self.list_data, tuple(data)) def test_server_group_list_with_all_projects_and_long(self): arglist = [ @@ -319,8 +320,8 @@ def test_server_group_list_with_all_projects_and_long(self): columns, data = self.cmd.take_action(parsed_args) self.server_groups_mock.list.assert_called_once_with(True) - self.assertEqual(self.list_columns_long, columns) - self.assertEqual(self.list_data_long, tuple(data)) + self.assertCountEqual(self.list_columns_long, columns) + self.assertCountEqual(self.list_data_long, tuple(data)) class TestServerGroupListV264(TestServerGroupV264): @@ -350,7 +351,8 @@ class TestServerGroupListV264(TestServerGroupV264): TestServerGroupV264.fake_server_group.id, TestServerGroupV264.fake_server_group.name, TestServerGroupV264.fake_server_group.policy, - utils.format_list(TestServerGroupV264.fake_server_group.members), + format_columns.ListColumn( + TestServerGroupV264.fake_server_group.members), TestServerGroupV264.fake_server_group.project_id, TestServerGroupV264.fake_server_group.user_id, ),) @@ -373,8 +375,8 @@ def test_server_group_list(self): columns, data = self.cmd.take_action(parsed_args) self.server_groups_mock.list.assert_called_once_with(False) - self.assertEqual(self.list_columns, columns) - self.assertEqual(self.list_data, tuple(data)) + self.assertCountEqual(self.list_columns, columns) + self.assertCountEqual(self.list_data, tuple(data)) def test_server_group_list_with_all_projects_and_long(self): arglist = [ @@ -389,8 +391,8 @@ def test_server_group_list_with_all_projects_and_long(self): columns, data = self.cmd.take_action(parsed_args) self.server_groups_mock.list.assert_called_once_with(True) - self.assertEqual(self.list_columns_long, columns) - self.assertEqual(self.list_data_long, tuple(data)) + self.assertCountEqual(self.list_columns_long, columns) + self.assertCountEqual(self.list_data_long, tuple(data)) class TestServerGroupShow(TestServerGroup): @@ -412,5 +414,5 @@ def test_server_group_show(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertCountEqual(self.columns, columns) + self.assertCountEqual(self.data, data) diff --git a/releasenotes/notes/improved-server-output-6965b664f6abda8d.yaml b/releasenotes/notes/improved-server-output-6965b664f6abda8d.yaml index 7a42fa899d..5dc2980c77 100644 --- a/releasenotes/notes/improved-server-output-6965b664f6abda8d.yaml +++ b/releasenotes/notes/improved-server-output-6965b664f6abda8d.yaml @@ -14,3 +14,7 @@ fixes: formatter. In addition, the ``server_usages``, ``total_memory_mb_usage``, ``total_vcpus_usage`` and ``total_local_gb_usage`` values will only be humanized when using the table formatter. + - | + The ``policies`` (or ``policy``, on newer microversions) and ``members`` + fields of the ``server group list`` and ``server group show`` commands + will now be rendered correctly as lists. From bf834f6d7582009672fc65aa983f314684dc4380 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Tue, 17 Nov 2020 12:33:40 +0000 Subject: [PATCH 2310/3095] compute: Fix 'hypervisor show -f yaml' output The 'cpu_info' field returned by the 'os-hypervisors' API is an object and should be formatted as such. However, this is complicated by the fact that the object in this field is stringified until microversion 2.28 and is only returned as an actual object on later microversions. Handle the conversion from the string for older microversions and display things correctly for all releases. Change-Id: Ide31466cbb9e89c96d6bd542fe039ab5ed1fac1f Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/hypervisor.py | 24 ++++++++-- .../tests/unit/compute/v2/test_hypervisor.py | 45 ++++++++++++++++--- ...proved-server-output-6965b664f6abda8d.yaml | 3 ++ 3 files changed, 64 insertions(+), 8 deletions(-) diff --git a/openstackclient/compute/v2/hypervisor.py b/openstackclient/compute/v2/hypervisor.py index 7f110028b6..8fdb66983c 100644 --- a/openstackclient/compute/v2/hypervisor.py +++ b/openstackclient/compute/v2/hypervisor.py @@ -15,9 +15,12 @@ """Hypervisor action implementations""" +import json import re +from novaclient import api_versions from novaclient import exceptions as nova_exceptions +from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import utils @@ -86,8 +89,8 @@ def take_action(self, parsed_args): if aggregates: # Hypervisors in nova cells are prefixed by "@" if "@" in hypervisor['service']['host']: - cell, service_host = hypervisor['service']['host'].split('@', - 1) + cell, service_host = hypervisor['service']['host'].split( + '@', 1) else: cell = None service_host = hypervisor['service']['host'] @@ -125,4 +128,19 @@ def take_action(self, parsed_args): hypervisor["service_host"] = hypervisor["service"]["host"] del hypervisor["service"] - return zip(*sorted(hypervisor.items())) + if compute_client.api_version < api_versions.APIVersion('2.28'): + # microversion 2.28 transformed this to a JSON blob rather than a + # string; on earlier fields, do this manually + if hypervisor['cpu_info']: + hypervisor['cpu_info'] = json.loads(hypervisor['cpu_info']) + else: + hypervisor['cpu_info'] = {} + + columns = tuple(sorted(hypervisor)) + data = utils.get_dict_properties( + hypervisor, columns, + formatters={ + 'cpu_info': format_columns.DictColumn, + }) + + return (columns, data) diff --git a/openstackclient/tests/unit/compute/v2/test_hypervisor.py b/openstackclient/tests/unit/compute/v2/test_hypervisor.py index 7200d04ed8..518109c6b0 100644 --- a/openstackclient/tests/unit/compute/v2/test_hypervisor.py +++ b/openstackclient/tests/unit/compute/v2/test_hypervisor.py @@ -14,8 +14,11 @@ # import copy +import json +from novaclient import api_versions from novaclient import exceptions as nova_exceptions +from osc_lib.cli import format_columns from osc_lib import exceptions from openstackclient.compute.v2 import hypervisor @@ -247,7 +250,7 @@ def setUp(self): ) self.data = ( [], - {'aaa': 'aaa'}, + format_columns.DictColumn({'aaa': 'aaa'}), 0, 50, 50, @@ -278,6 +281,35 @@ def setUp(self): self.cmd = hypervisor.ShowHypervisor(self.app, None) def test_hypervisor_show(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.28') + + arglist = [ + self.hypervisor.hypervisor_hostname, + ] + verifylist = [ + ('hypervisor', self.hypervisor.hypervisor_hostname), + ] + 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.assertEqual(self.columns, columns) + self.assertItemsEqual(self.data, data) + + def test_hypervisor_show_pre_v228(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.27') + + # before microversion 2.28, nova returned a stringified version of this + # field + self.hypervisor._info['cpu_info'] = json.dumps( + self.hypervisor._info['cpu_info']) + self.hypervisors_mock.get.return_value = self.hypervisor + arglist = [ self.hypervisor.hypervisor_hostname, ] @@ -292,9 +324,12 @@ def test_hypervisor_show(self): columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemsEqual(self.data, data) + + def test_hypervisor_show_uptime_not_implemented(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.28') - def test_hyprvisor_show_uptime_not_implemented(self): arglist = [ self.hypervisor.hypervisor_hostname, ] @@ -337,7 +372,7 @@ def test_hyprvisor_show_uptime_not_implemented(self): ) expected_data = ( [], - {'aaa': 'aaa'}, + format_columns.DictColumn({'aaa': 'aaa'}), 0, 50, 50, @@ -361,4 +396,4 @@ def test_hyprvisor_show_uptime_not_implemented(self): ) self.assertEqual(expected_columns, columns) - self.assertEqual(expected_data, data) + self.assertItemsEqual(expected_data, data) diff --git a/releasenotes/notes/improved-server-output-6965b664f6abda8d.yaml b/releasenotes/notes/improved-server-output-6965b664f6abda8d.yaml index 5dc2980c77..305e915e61 100644 --- a/releasenotes/notes/improved-server-output-6965b664f6abda8d.yaml +++ b/releasenotes/notes/improved-server-output-6965b664f6abda8d.yaml @@ -18,3 +18,6 @@ fixes: The ``policies`` (or ``policy``, on newer microversions) and ``members`` fields of the ``server group list`` and ``server group show`` commands will now be rendered correctly as lists. + - | + The ``cpu_info`` field of the ``hypervisor show`` output is now + correctly decoded and output as an object. From a5c6470f2d76afb9ac87d05c4326bebf925a55da Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 5 Nov 2020 11:49:11 +0000 Subject: [PATCH 2311/3095] compute: Add 'server group create --rule' option This closes the remaining gap with the 2.64 compute API microversion. Change-Id: Ia42b23d813b7af6ddb1a41f4e9bdc8a6160b908c Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server_group.py | 70 +++++++++++++------ .../unit/compute/v2/test_server_group.py | 31 +++++++- ...p-create_rule_option-9f84e52f35e7c3ba.yaml | 4 ++ 3 files changed, 82 insertions(+), 23 deletions(-) create mode 100644 releasenotes/notes/server-group-create_rule_option-9f84e52f35e7c3ba.yaml diff --git a/openstackclient/compute/v2/server_group.py b/openstackclient/compute/v2/server_group.py index d245c0927f..783fdbfea3 100644 --- a/openstackclient/compute/v2/server_group.py +++ b/openstackclient/compute/v2/server_group.py @@ -19,6 +19,7 @@ from novaclient import api_versions from osc_lib.cli import format_columns +from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -30,8 +31,9 @@ _formatters = { - 'policies': format_columns.ListColumn, 'members': format_columns.ListColumn, + 'policies': format_columns.ListColumn, + 'rules': format_columns.DictColumn, } @@ -68,7 +70,19 @@ def get_parser(self, prog_name): "Add a policy to " "Specify --os-compute-api-version 2.15 or higher for the " "'soft-affinity' or 'soft-anti-affinity' policy." - ) + ), + ) + parser.add_argument( + '--rule', + metavar='', + action=parseractions.KeyValueAction, + default={}, + dest='rules', + help=_( + "A rule for the policy. Currently, only the " + "'max_server_per_host' rule is supported for the " + "'anti-affinity' policy." + ), ) return parser @@ -84,12 +98,24 @@ def take_action(self, parsed_args): ) raise exceptions.CommandError(msg % parsed_args.policy) - policy_arg = {'policies': [parsed_args.policy]} - if compute_client.api_version >= api_versions.APIVersion("2.64"): - policy_arg = {'policy': parsed_args.policy} + if parsed_args.rules: + if compute_client.api_version < api_versions.APIVersion('2.64'): + msg = _( + '--os-compute-api-version 2.64 or greater is required to ' + 'support the --rule option' + ) + raise exceptions.CommandError(msg) + + if compute_client.api_version < api_versions.APIVersion('2.64'): + kwargs = {'policies': [parsed_args.policy]} + else: + kwargs = { + 'policy': parsed_args.policy, + 'rules': parsed_args.rules or None, + } server_group = compute_client.server_groups.create( - name=parsed_args.name, **policy_arg) + name=parsed_args.name, **kwargs) info.update(server_group._info) @@ -161,31 +187,33 @@ def take_action(self, parsed_args): if compute_client.api_version >= api_versions.APIVersion("2.64"): policy_key = 'Policy' + columns = ( + 'id', + 'name', + policy_key.lower(), + ) + column_headers = ( + 'ID', + 'Name', + policy_key, + ) if parsed_args.long: - column_headers = columns = ( - 'ID', - 'Name', - policy_key, + columns += ( + 'members', + 'project_id', + 'user_id', + ) + column_headers += ( 'Members', 'Project Id', 'User Id', ) - else: - column_headers = columns = ( - 'ID', - 'Name', - policy_key, - ) return ( column_headers, ( utils.get_item_properties( - s, columns, - formatters={ - 'Policies': format_columns.ListColumn, - 'Members': format_columns.ListColumn, - } + s, columns, formatters=_formatters, ) for s in data ), ) diff --git a/openstackclient/tests/unit/compute/v2/test_server_group.py b/openstackclient/tests/unit/compute/v2/test_server_group.py index 8de7492c79..732c18810b 100644 --- a/openstackclient/tests/unit/compute/v2/test_server_group.py +++ b/openstackclient/tests/unit/compute/v2/test_server_group.py @@ -152,28 +152,55 @@ def test_server_group_create_with_soft_policies_pre_v215(self): '--os-compute-api-version 2.15 or greater is required', str(ex)) - def test_server_group_create_v264(self): + def test_server_group_create_with_rules(self): self.app.client_manager.compute.api_version = api_versions.APIVersion( '2.64') arglist = [ '--policy', 'soft-anti-affinity', + '--rule', 'max_server_per_host=2', 'affinity_group', ] verifylist = [ ('policy', 'soft-anti-affinity'), + ('rules', {'max_server_per_host': '2'}), ('name', 'affinity_group'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) self.server_groups_mock.create.assert_called_once_with( name=parsed_args.name, - policy=parsed_args.policy, + policy=parsed_args.policy, # should be 'policy', not 'policies' + rules=parsed_args.rules, ) self.assertCountEqual(self.columns, columns) self.assertCountEqual(self.data, data) + def test_server_group_create_with_rules_pre_v264(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.63') + + arglist = [ + '--policy', 'soft-anti-affinity', + '--rule', 'max_server_per_host=2', + 'affinity_group', + ] + verifylist = [ + ('policy', 'soft-anti-affinity'), + ('rules', {'max_server_per_host': '2'}), + ('name', 'affinity_group'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.64 or greater is required', + str(ex)) + class TestServerGroupDelete(TestServerGroup): diff --git a/releasenotes/notes/server-group-create_rule_option-9f84e52f35e7c3ba.yaml b/releasenotes/notes/server-group-create_rule_option-9f84e52f35e7c3ba.yaml new file mode 100644 index 0000000000..29ed3e941d --- /dev/null +++ b/releasenotes/notes/server-group-create_rule_option-9f84e52f35e7c3ba.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Add support for ``--rule`` option for ``server group create``. From f200799848831a00f350c324bb77c00efa50da1c Mon Sep 17 00:00:00 2001 From: Yongli He Date: Mon, 9 Sep 2019 13:56:30 +0800 Subject: [PATCH 2312/3095] compute: Add 'server show --topology' option Add support for compute microversion 2.78 by adding a '--topology' option to 'openstack server show' command that retrieves server NUMA information. Change-Id: Ie22979df2ea9082ca64a4d43b571bd4025684825 --- doc/source/cli/data/nova.csv | 1 + openstackclient/compute/v2/server.py | 46 +++++++++++++---- .../tests/unit/compute/v2/test_server.py | 49 +++++++++++++++++++ ...y-microversion-v2_78-3891fc67f767177e.yaml | 5 ++ 4 files changed, 92 insertions(+), 9 deletions(-) create mode 100644 releasenotes/notes/show-server-topology-microversion-v2_78-3891fc67f767177e.yaml diff --git a/doc/source/cli/data/nova.csv b/doc/source/cli/data/nova.csv index 872d6e09b2..2004007f8d 100644 --- a/doc/source/cli/data/nova.csv +++ b/doc/source/cli/data/nova.csv @@ -121,6 +121,7 @@ start,server start,Start the server(s). stop,server stop,Stop the server(s). suspend,server suspend,Suspend a server. trigger-crash-dump,server dump create,Trigger crash dump in an instance. +topology,openstack server show --topology,Retrieve server NUMA topology. unlock,server unlock,Unlock a server. unpause,server unpause,Unpause a server. unrescue,server unrescue,Restart the server from normal boot disk again. diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index ba0243ef3e..c1716e5be1 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -3383,7 +3383,8 @@ def take_action(self, parsed_args): class ShowServer(command.ShowOne): _description = _( "Show server details. Specify ``--os-compute-api-version 2.47`` " - "or higher to see the embedded flavor information for the server.") + "or higher to see the embedded flavor information for the server." + ) def get_parser(self, prog_name): parser = super(ShowServer, self).get_parser(prog_name) @@ -3392,18 +3393,29 @@ def get_parser(self, prog_name): metavar='', help=_('Server (name or ID)'), ) - parser.add_argument( + # TODO(stephenfin): This should be a separate command, not a flag + diagnostics_group = parser.add_mutually_exclusive_group() + diagnostics_group.add_argument( '--diagnostics', action='store_true', default=False, help=_('Display server diagnostics information'), ) + diagnostics_group.add_argument( + '--topology', + action='store_true', + default=False, + help=_( + 'Include topology information in the output ' + '(supported by --os-compute-api-version 2.78 or above)' + ), + ) return parser def take_action(self, parsed_args): compute_client = self.app.client_manager.compute - server = utils.find_resource(compute_client.servers, - parsed_args.server) + server = utils.find_resource( + compute_client.servers, parsed_args.server) if parsed_args.diagnostics: (resp, data) = server.diagnostics() @@ -3412,10 +3424,26 @@ def take_action(self, parsed_args): "Error retrieving diagnostics data\n" )) return ({}, {}) - else: - data = _prep_server_detail(compute_client, - self.app.client_manager.image, server, - refresh=False) + return zip(*sorted(data.items())) + + topology = None + if parsed_args.topology: + if compute_client.api_version < api_versions.APIVersion('2.78'): + msg = _( + '--os-compute-api-version 2.78 or greater is required to ' + 'support the --topology option' + ) + raise exceptions.CommandError(msg) + + topology = server.topology() + + data = _prep_server_detail( + compute_client, self.app.client_manager.image, server, + refresh=False) + + if topology: + data['topology'] = format_columns.DictColumn(topology) + return zip(*sorted(data.items())) @@ -3731,7 +3759,7 @@ def get_parser(self, prog_name): help=_( 'Tag to remove from the server. ' 'Specify multiple times to remove multiple tags. ' - '(supported by --os-compute-api-version 2.26 or later' + '(supported by --os-compute-api-version 2.26 or above)' ), ) return parser diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index efc105e565..8c3bf317e0 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -6285,6 +6285,10 @@ def setUp(self): self.image = image_fakes.FakeImage.create_one_image() self.flavor = compute_fakes.FakeFlavor.create_one_flavor() + self.topology = { + 'nodes': [{'vcpu_set': [0, 1]}, {'vcpu_set': [2, 3]}], + 'pagesize_kb': None, + } server_info = { 'image': {'id': self.image.id}, 'flavor': {'id': self.flavor.id}, @@ -6298,6 +6302,7 @@ def setUp(self): resp.status_code = 200 server_method = { 'diagnostics': (resp, {'test': 'test'}), + 'topology': self.topology, } self.server = compute_fakes.FakeServer.create_one_server( attrs=server_info, methods=server_method) @@ -6348,6 +6353,7 @@ def test_show(self): ] verifylist = [ ('diagnostics', False), + ('topology', False), ('server', self.server.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -6365,6 +6371,7 @@ def test_show_embedded_flavor(self): ] verifylist = [ ('diagnostics', False), + ('topology', False), ('server', self.server.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -6391,6 +6398,7 @@ def test_show_diagnostics(self): ] verifylist = [ ('diagnostics', True), + ('topology', False), ('server', self.server.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -6400,6 +6408,47 @@ def test_show_diagnostics(self): self.assertEqual(('test',), columns) self.assertEqual(('test',), data) + def test_show_topology(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.78') + + arglist = [ + '--topology', + self.server.name, + ] + verifylist = [ + ('diagnostics', False), + ('topology', True), + ('server', self.server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.columns += ('topology',) + self.data += (format_columns.DictColumn(self.topology),) + + columns, data = self.cmd.take_action(parsed_args) + + self.assertCountEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + + def test_show_topology_pre_v278(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.77') + + arglist = [ + '--topology', + self.server.name, + ] + verifylist = [ + ('diagnostics', False), + ('topology', True), + ('server', self.server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, self.cmd.take_action, parsed_args) + class TestServerStart(TestServer): diff --git a/releasenotes/notes/show-server-topology-microversion-v2_78-3891fc67f767177e.yaml b/releasenotes/notes/show-server-topology-microversion-v2_78-3891fc67f767177e.yaml new file mode 100644 index 0000000000..0b48ed009c --- /dev/null +++ b/releasenotes/notes/show-server-topology-microversion-v2_78-3891fc67f767177e.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add support for ``openstack server show --topology`` flag, which will + include NUMA topology information in the output. From bbf7de83ff83b7c124ff30e421bd3eea6f1a8765 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 5 Nov 2020 12:27:17 +0000 Subject: [PATCH 2313/3095] trivial: Use plural for appended parameters Multiple compute commands take a '--property' parameter or variant thereof. These should be stored in a 'properties' (plural) dest for sanity's sake. Correct this. Change-Id: If393836925fa736404527d9abd212b8ac9931027 Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/aggregate.py | 44 +++++++++---------- openstackclient/compute/v2/flavor.py | 21 +++++---- openstackclient/compute/v2/server.py | 37 ++++++++-------- .../tests/unit/compute/v2/test_aggregate.py | 20 ++++----- .../tests/unit/compute/v2/test_flavor.py | 10 ++--- .../tests/unit/compute/v2/test_server.py | 22 +++++----- 6 files changed, 78 insertions(+), 76 deletions(-) diff --git a/openstackclient/compute/v2/aggregate.py b/openstackclient/compute/v2/aggregate.py index 8b70f42640..e39eb2d272 100644 --- a/openstackclient/compute/v2/aggregate.py +++ b/openstackclient/compute/v2/aggregate.py @@ -101,6 +101,7 @@ def get_parser(self, prog_name): "--property", metavar="", action=parseractions.KeyValueAction, + dest="properties", help=_("Property to add to this aggregate " "(repeat option to set multiple properties)") ) @@ -116,10 +117,10 @@ def take_action(self, parsed_args): aggregate = compute_client.create_aggregate(**attrs) - if parsed_args.property: + if parsed_args.properties: aggregate = compute_client.set_aggregate_metadata( aggregate.id, - parsed_args.property, + parsed_args.properties, ) display_columns, columns = _get_aggregate_columns(aggregate) @@ -269,12 +270,12 @@ def get_parser(self, prog_name): "--property", metavar="", action=parseractions.KeyValueAction, + dest="properties", help=_("Property to set on " "(repeat option to set multiple properties)") ) parser.add_argument( "--no-property", - dest="no_property", action="store_true", help=_("Remove all properties from " "(specify both --property and --no-property to " @@ -296,21 +297,20 @@ def take_action(self, parsed_args): if kwargs: compute_client.update_aggregate(aggregate.id, **kwargs) - set_property = {} + properties = {} if parsed_args.no_property: # NOTE(RuiChen): "availability_zone" can not be unset from # properties. It is already excluded from show and create output. - set_property.update({key: None - for key in aggregate.metadata.keys() - if key != 'availability_zone'}) - if parsed_args.property: - set_property.update(parsed_args.property) - - if set_property: - compute_client.set_aggregate_metadata( - aggregate.id, - set_property - ) + properties.update({ + key: None for key in aggregate.metadata.keys() + if key != 'availability_zone' + }) + + if parsed_args.properties: + properties.update(parsed_args.properties) + + if properties: + compute_client.set_aggregate_metadata(aggregate.id, properties) class ShowAggregate(command.ShowOne): @@ -354,7 +354,9 @@ def get_parser(self, prog_name): parser.add_argument( "--property", metavar="", - action='append', + action="append", + default=[], + dest="properties", help=_("Property to remove from aggregate " "(repeat option to remove multiple properties)") ) @@ -365,12 +367,10 @@ def take_action(self, parsed_args): aggregate = compute_client.find_aggregate( parsed_args.aggregate, ignore_missing=False) - unset_property = {} - if parsed_args.property: - unset_property.update({key: None for key in parsed_args.property}) - if unset_property: - compute_client.set_aggregate_metadata( - aggregate, unset_property) + properties = {key: None for key in parsed_args.properties} + + if properties: + compute_client.set_aggregate_metadata(aggregate.id, properties) class CacheImageForAggregate(command.Command): diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index fa98e131b1..a55aba2a41 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -128,6 +128,7 @@ def get_parser(self, prog_name): "--property", metavar="", action=parseractions.KeyValueAction, + dest="properties", help=_("Property to add for this flavor " "(repeat option to set multiple properties)") ) @@ -191,12 +192,12 @@ def take_action(self, parsed_args): msg = _("Failed to add project %(project)s access to " "flavor: %(e)s") LOG.error(msg, {'project': parsed_args.project, 'e': e}) - if parsed_args.property: + if parsed_args.properties: try: flavor = compute_client.create_flavor_extra_specs( - flavor, parsed_args.property) + flavor, parsed_args.properties) except Exception as e: - LOG.error(_("Failed to set flavor property: %s"), e) + LOG.error(_("Failed to set flavor properties: %s"), e) display_columns, columns = _get_flavor_columns(flavor) data = utils.get_dict_properties(flavor, columns, @@ -398,6 +399,7 @@ def get_parser(self, prog_name): "--property", metavar="", action=parseractions.KeyValueAction, + dest="properties", help=_("Property to add or modify for this flavor " "(repeat option to set multiple properties)") ) @@ -447,15 +449,15 @@ def take_action(self, parsed_args): compute_client.delete_flavor_extra_specs_property( flavor.id, key) except Exception as e: - LOG.error(_("Failed to clear flavor property: %s"), e) + LOG.error(_("Failed to clear flavor properties: %s"), e) result += 1 - if parsed_args.property: + if parsed_args.properties: try: compute_client.create_flavor_extra_specs( - flavor.id, parsed_args.property) + flavor.id, parsed_args.properties) except Exception as e: - LOG.error(_("Failed to set flavor property: %s"), e) + LOG.error(_("Failed to set flavor properties: %s"), e) result += 1 if parsed_args.project: @@ -537,6 +539,7 @@ def get_parser(self, prog_name): "--property", metavar="", action='append', + dest="properties", help=_("Property to remove from flavor " "(repeat option to unset multiple properties)") ) @@ -563,8 +566,8 @@ def take_action(self, parsed_args): raise exceptions.CommandError(_(e.message)) result = 0 - if parsed_args.property: - for key in parsed_args.property: + if parsed_args.properties: + for key in parsed_args.properties: try: compute_client.delete_flavor_extra_specs_property( flavor.id, key) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index c1716e5be1..6cb364fefe 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -614,6 +614,7 @@ def get_parser(self, prog_name): '--image-property', metavar='', action=parseractions.KeyValueAction, + dest='image_properties', help=_("Image property to be matched"), ) disk_group.add_argument( @@ -659,6 +660,7 @@ def get_parser(self, prog_name): '--property', metavar='', action=parseractions.KeyValueAction, + dest='properties', help=_( 'Set a property on this server ' '(repeat option to set multiple values)' @@ -886,8 +888,8 @@ def _show_progress(progress): image = image_client.find_image( parsed_args.image, ignore_missing=False) - if not image and parsed_args.image_property: - def emit_duplicated_warning(img, image_property): + if not image and parsed_args.image_properties: + def emit_duplicated_warning(img): img_uuid_list = [str(image.id) for image in img] LOG.warning( 'Multiple matching images: %(img_uuid_list)s\n' @@ -930,9 +932,9 @@ def _match_image(image_api, wanted_properties): return images_matched - images = _match_image(image_client, parsed_args.image_property) + images = _match_image(image_client, parsed_args.image_properties) if len(images) > 1: - emit_duplicated_warning(images, parsed_args.image_property) + emit_duplicated_warning(images, parsed_args.image_properties) if images: image = images[0] else: @@ -1195,7 +1197,7 @@ def _match_image(image_api, wanted_properties): config_drive = parsed_args.config_drive boot_kwargs = dict( - meta=parsed_args.property, + meta=parsed_args.properties, files=files, reservation_id=None, min_count=parsed_args.min, @@ -2473,6 +2475,7 @@ def get_parser(self, prog_name): '--property', metavar='', action=parseractions.KeyValueAction, + dest='properties', help=_( 'Set a new property on the rebuilt server ' '(repeat option to set multiple values)' @@ -2614,8 +2617,8 @@ def _show_progress(progress): if parsed_args.preserve_ephemeral is not None: kwargs['preserve_ephemeral'] = parsed_args.preserve_ephemeral - if parsed_args.property: - kwargs['meta'] = parsed_args.property + if parsed_args.properties: + kwargs['meta'] = parsed_args.properties if parsed_args.description: if compute_client.api_version < api_versions.APIVersion('2.19'): @@ -3278,9 +3281,10 @@ def get_parser(self, prog_name): help=_('Set new root password (interactive only)'), ) parser.add_argument( - "--property", - metavar="", + '--property', + metavar='', action=parseractions.KeyValueAction, + dest='properties', help=_('Property to add/change for this server ' '(repeat option to set multiple properties)'), ) @@ -3321,11 +3325,8 @@ def take_action(self, parsed_args): if parsed_args.name: server.update(name=parsed_args.name) - if parsed_args.property: - compute_client.servers.set_meta( - server, - parsed_args.property, - ) + if parsed_args.properties: + compute_client.servers.set_meta(server, parsed_args.properties) if parsed_args.state: server.reset_state(state=parsed_args.state) @@ -3740,6 +3741,7 @@ def get_parser(self, prog_name): metavar='', action='append', default=[], + dest='properties', help=_('Property key to remove from server ' '(repeat option to remove multiple values)'), ) @@ -3771,11 +3773,8 @@ def take_action(self, parsed_args): parsed_args.server, ) - if parsed_args.property: - compute_client.servers.delete_meta( - server, - parsed_args.property, - ) + if parsed_args.properties: + compute_client.servers.delete_meta(server, parsed_args.properties) if parsed_args.description: if compute_client.api_version < api_versions.APIVersion("2.19"): diff --git a/openstackclient/tests/unit/compute/v2/test_aggregate.py b/openstackclient/tests/unit/compute/v2/test_aggregate.py index e12edd0fce..8563f98811 100644 --- a/openstackclient/tests/unit/compute/v2/test_aggregate.py +++ b/openstackclient/tests/unit/compute/v2/test_aggregate.py @@ -138,7 +138,7 @@ def test_aggregate_create_with_property(self): 'ag1', ] verifylist = [ - ('property', {'key1': 'value1', 'key2': 'value2'}), + ('properties', {'key1': 'value1', 'key2': 'value2'}), ('name', 'ag1'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -146,7 +146,7 @@ def test_aggregate_create_with_property(self): self.sdk_client.create_aggregate.assert_called_once_with( name=parsed_args.name) self.sdk_client.set_aggregate_metadata.assert_called_once_with( - self.fake_ag.id, parsed_args.property) + self.fake_ag.id, parsed_args.properties) self.assertEqual(self.columns, columns) self.assertItemsEqual(self.data, data) @@ -378,7 +378,7 @@ def test_aggregate_set_with_property(self): 'ag1', ] verifylist = [ - ('property', {'key1': 'value1', 'key2': 'value2'}), + ('properties', {'key1': 'value1', 'key2': 'value2'}), ('aggregate', 'ag1'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -388,7 +388,7 @@ def test_aggregate_set_with_property(self): parsed_args.aggregate, ignore_missing=False) self.assertNotCalled(self.sdk_client.update_aggregate) self.sdk_client.set_aggregate_metadata.assert_called_once_with( - self.fake_ag.id, parsed_args.property) + self.fake_ag.id, parsed_args.properties) self.assertIsNone(result) def test_aggregate_set_with_no_property_and_property(self): @@ -399,7 +399,7 @@ def test_aggregate_set_with_no_property_and_property(self): ] verifylist = [ ('no_property', True), - ('property', {'key2': 'value2'}), + ('properties', {'key2': 'value2'}), ('aggregate', 'ag1'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -509,14 +509,14 @@ def test_aggregate_unset(self): 'ag1', ] verifylist = [ - ('property', ['unset_key']), + ('properties', ['unset_key']), ('aggregate', 'ag1'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.sdk_client.set_aggregate_metadata.assert_called_once_with( - self.fake_ag, {'unset_key': None}) + self.fake_ag.id, {'unset_key': None}) self.assertIsNone(result) def test_aggregate_unset_multiple_properties(self): @@ -526,14 +526,14 @@ def test_aggregate_unset_multiple_properties(self): 'ag1', ] verifylist = [ - ('property', ['unset_key1', 'unset_key2']), + ('properties', ['unset_key1', 'unset_key2']), ('aggregate', 'ag1'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.sdk_client.set_aggregate_metadata.assert_called_once_with( - self.fake_ag, {'unset_key1': None, 'unset_key2': None}) + self.fake_ag.id, {'unset_key1': None, 'unset_key2': None}) self.assertIsNone(result) def test_aggregate_unset_no_option(self): @@ -541,7 +541,7 @@ def test_aggregate_unset_no_option(self): 'ag1', ] verifylist = [ - ('property', None), + ('properties', []), ('aggregate', 'ag1'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/unit/compute/v2/test_flavor.py b/openstackclient/tests/unit/compute/v2/test_flavor.py index 8c1147fd2a..ee4479b009 100644 --- a/openstackclient/tests/unit/compute/v2/test_flavor.py +++ b/openstackclient/tests/unit/compute/v2/test_flavor.py @@ -160,7 +160,7 @@ def test_flavor_create_all_options(self): ('rxtx_factor', self.flavor.rxtx_factor), ('public', True), ('description', self.flavor.description), - ('property', {'property': 'value'}), + ('properties', {'property': 'value'}), ('name', self.flavor.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -232,7 +232,7 @@ def test_flavor_create_other_options(self): ('public', False), ('description', 'description'), ('project', self.project.id), - ('property', {'key1': 'value1', 'key2': 'value2'}), + ('properties', {'key1': 'value1', 'key2': 'value2'}), ('name', self.flavor.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -688,7 +688,7 @@ def test_flavor_set_property(self): 'baremetal' ] verifylist = [ - ('property', {'FOO': '"B A R"'}), + ('properties', {'FOO': '"B A R"'}), ('flavor', 'baremetal') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1017,7 +1017,7 @@ def test_flavor_unset_property(self): 'baremetal' ] verifylist = [ - ('property', ['property']), + ('properties', ['property']), ('flavor', 'baremetal'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1039,7 +1039,7 @@ def test_flavor_unset_properties(self): 'baremetal' ] verifylist = [ - ('property', ['property1', 'property2']), + ('properties', ['property1', 'property2']), ('flavor', 'baremetal'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 8c3bf317e0..99f7eede4d 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -1159,7 +1159,7 @@ def test_server_create_with_options(self): ('image', 'image1'), ('flavor', 'flavor1'), ('key_name', 'keyname'), - ('property', {'Beta': 'b'}), + ('properties', {'Beta': 'b'}), ('security_group', ['securitygroup']), ('hint', {'a': ['b', 'c']}), ('config_drive', True), @@ -2227,7 +2227,7 @@ def test_server_create_image_property(self): self.new_server.name, ] verifylist = [ - ('image_property', {'hypervisor_type': 'qemu'}), + ('image_properties', {'hypervisor_type': 'qemu'}), ('flavor', 'flavor1'), ('nic', ['none']), ('config_drive', False), @@ -2282,7 +2282,7 @@ def test_server_create_image_property_multi(self): self.new_server.name, ] verifylist = [ - ('image_property', {'hypervisor_type': 'qemu', + ('image_properties', {'hypervisor_type': 'qemu', 'hw_disk_bus': 'ide'}), ('flavor', 'flavor1'), ('nic', ['none']), @@ -2338,7 +2338,7 @@ def test_server_create_image_property_missed(self): self.new_server.name, ] verifylist = [ - ('image_property', {'hypervisor_type': 'qemu', + ('image_properties', {'hypervisor_type': 'qemu', 'hw_disk_bus': 'virtio'}), ('flavor', 'flavor1'), ('nic', ['none']), @@ -2370,7 +2370,7 @@ def test_server_create_image_property_with_image_list(self): ] verifylist = [ - ('image_property', + ('image_properties', {'owner_specified.openstack.object': 'image/cirros'}), ('flavor', 'flavor1'), ('nic', ['none']), @@ -4973,10 +4973,10 @@ def test_rebuild_with_property(self): '--property', 'key1=value1', '--property', 'key2=value2' ] - expected_property = {'key1': 'value1', 'key2': 'value2'} + expected_properties = {'key1': 'value1', 'key2': 'value2'} verifylist = [ ('server', self.server.id), - ('property', expected_property) + ('properties', expected_properties) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -4986,7 +4986,7 @@ def test_rebuild_with_property(self): self.servers_mock.get.assert_called_with(self.server.id) self.get_image_mock.assert_called_with(self.image.id) self.server.rebuild.assert_called_with( - self.image, None, meta=expected_property) + self.image, None, meta=expected_properties) def test_rebuild_with_keypair_name(self): self.app.client_manager.compute.api_version = \ @@ -6145,13 +6145,13 @@ def test_server_set_with_property(self): 'foo_vm', ] verifylist = [ - ('property', {'key1': 'value1', 'key2': 'value2'}), + ('properties', {'key1': 'value1', 'key2': 'value2'}), ('server', 'foo_vm'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.servers_mock.set_meta.assert_called_once_with( - self.fake_servers[0], parsed_args.property) + self.fake_servers[0], parsed_args.properties) self.assertIsNone(result) @mock.patch.object(getpass, 'getpass', @@ -6579,7 +6579,7 @@ def test_server_unset_with_property(self): 'foo_vm', ] verifylist = [ - ('property', ['key1', 'key2']), + ('properties', ['key1', 'key2']), ('server', 'foo_vm'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) From d0112a801a20c810d92dfcdf1f8fb71df3bd1d26 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 18 Nov 2020 15:11:59 +0000 Subject: [PATCH 2314/3095] compute: Add missing options for 'server list' This accepts a large number of options that we weren't exposing. Add the following options: '--availability-zone', '--key-name', '--config-drive' and '--no-config-drive', '--progress', '--vm-state', '--task-state' and '--power-state'. In addition, refine the 'openstack server list --status' parameter to restrict users to the actual choices supported by the server. Change-Id: Ieeb1f22df7092e66a411b6a36eafb3e16efc2fc2 Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server.py | 217 +++++++++++++++++- .../tests/unit/compute/v2/test_server.py | 140 ++++++++++- ...ing-server-list-opts-c41e97e86ff1e1ca.yaml | 16 ++ 3 files changed, 361 insertions(+), 12 deletions(-) create mode 100644 releasenotes/notes/add-missing-server-list-opts-c41e97e86ff1e1ca.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index ba0243ef3e..2d6a4b1849 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1344,18 +1344,18 @@ def _show_progress(progress): raise SystemExit +def percent_type(x): + x = int(x) + if not 0 < x <= 100: + raise argparse.ArgumentTypeError("Must be between 0 and 100") + return x + + class ListServer(command.Lister): _description = _("List servers") def get_parser(self, prog_name): parser = super(ListServer, self).get_parser(prog_name) - parser.add_argument( - '--availability-zone', - metavar='', - help=_('Only return instances that match the availability zone. ' - 'Note that this option will be ignored for non-admin users ' - 'when using ``--os-compute-api-version`` prior to 2.83.'), - ) parser.add_argument( '--reservation-id', metavar='', @@ -1385,10 +1385,35 @@ def get_parser(self, prog_name): metavar='', help=_('Regular expression to match instance name (admin only)'), ) + # taken from 'task_and_vm_state_from_status' function in nova + # the API sadly reports these in upper case and while it would be + # wonderful to plaster over this ugliness client-side, there are + # already users in the wild doing this in upper case that we need to + # support parser.add_argument( '--status', metavar='', - # FIXME(dhellmann): Add choices? + choices=( + 'ACTIVE', + 'BUILD', + 'DELETED', + 'ERROR', + 'HARD_REBOOT', + 'MIGRATING', + 'PASSWORD', + 'PAUSED', + 'REBOOT', + 'REBUILD', + 'RESCUE', + 'RESIZE', + 'REVERT_RESIZE', + 'SHELVED', + 'SHELVED_OFFLOADED', + 'SHUTOFF', + 'SOFT_DELETED', + 'SUSPENDED', + 'VERIFY_RESIZE' + ), help=_('Search by server status'), ) parser.add_argument( @@ -1421,7 +1446,10 @@ def get_parser(self, prog_name): parser.add_argument( '--user', metavar='', - help=_('Search by user (admin only) (name or ID)'), + help=_( + 'Search by user (name or ID) ' + '(admin only before microversion 2.83)' + ), ) identity_common.add_user_domain_option_to_parser(parser) parser.add_argument( @@ -1430,6 +1458,146 @@ def get_parser(self, prog_name): default=False, help=_('Only display deleted servers (admin only)'), ) + parser.add_argument( + '--availability-zone', + default=None, + help=_( + 'Search by availability zone ' + '(admin only before microversion 2.83)' + ), + ) + parser.add_argument( + '--key-name', + help=_( + 'Search by keypair name ' + '(admin only before microversion 2.83)' + ), + ) + config_drive_group = parser.add_mutually_exclusive_group() + config_drive_group.add_argument( + '--config-drive', + action='store_true', + dest='has_config_drive', + default=None, + help=_( + 'Only display servers with a config drive attached ' + '(admin only before microversion 2.83)' + ), + ) + # NOTE(gibi): this won't actually do anything until bug 1871409 is + # fixed and the REST API is cleaned up regarding the values of + # config_drive + config_drive_group.add_argument( + '--no-config-drive', + action='store_false', + dest='has_config_drive', + help=_( + 'Only display servers without a config drive attached ' + '(admin only before microversion 2.83)' + ), + ) + parser.add_argument( + '--progress', + type=percent_type, + default=None, + help=_( + 'Search by progress value (%%) ' + '(admin only before microversion 2.83)' + ), + ) + parser.add_argument( + '--vm-state', + metavar='', + # taken from 'InstanceState' object field in nova + choices=( + 'active', + 'building', + 'deleted', + 'error', + 'paused', + 'stopped', + 'suspended', + 'rescued', + 'resized', + 'shelved', + 'shelved_offloaded', + 'soft-delete', + ), + help=_( + 'Search by vm_state value ' + '(admin only before microversion 2.83)' + ), + ) + parser.add_argument( + '--task-state', + metavar='', + # taken from 'InstanceTaskState' object field in nova + choices=( + 'block_device_mapping', + 'deleting', + 'image_backup', + 'image_pending_upload', + 'image_snapshot', + 'image_snapshot_pending', + 'image_uploading', + 'migrating', + 'networking', + 'pausing', + 'powering-off', + 'powering-on', + 'rebooting', + 'reboot_pending', + 'reboot_started', + 'reboot_pending_hard', + 'reboot_started_hard', + 'rebooting_hard', + 'rebuilding', + 'rebuild_block_device_mapping', + 'rebuild_spawning', + 'rescuing', + 'resize_confirming', + 'resize_finish', + 'resize_migrated', + 'resize_migrating', + 'resize_prep', + 'resize_reverting', + 'restoring', + 'resuming', + 'scheduling', + 'shelving', + 'shelving_image_pending_upload', + 'shelving_image_uploading', + 'shelving_offloading', + 'soft-deleting', + 'spawning', + 'suspending', + 'updating_password', + 'unpausing', + 'unrescuing', + 'unshelving', + ), + help=_( + 'Search by task_state value ' + '(admin only before microversion 2.83)' + ), + ) + parser.add_argument( + '--power-state', + metavar='', + # taken from 'InstancePowerState' object field in nova + choices=( + 'pending', + 'running', + 'paused', + 'shutdown', + 'crashed', + 'suspended', + ), + help=_( + 'Search by power_state value ' + '(admin only before microversion 2.83)' + ), + ) parser.add_argument( '--long', action='store_true', @@ -1582,7 +1750,6 @@ def take_action(self, parsed_args): ignore_missing=False).id search_opts = { - 'availability_zone': parsed_args.availability_zone, 'reservation_id': parsed_args.reservation_id, 'ip': parsed_args.ip, 'ip6': parsed_args.ip6, @@ -1600,6 +1767,36 @@ def take_action(self, parsed_args): 'changes-since': parsed_args.changes_since, } + if parsed_args.availability_zone: + search_opts['availability_zone'] = parsed_args.availability_zone + + if parsed_args.key_name: + search_opts['key_name'] = parsed_args.key_name + + if parsed_args.has_config_drive is not None: + search_opts['config_drive'] = parsed_args.has_config_drive + + if parsed_args.progress is not None: + search_opts['progress'] = str(parsed_args.progress) + + if parsed_args.vm_state: + search_opts['vm_state'] = parsed_args.vm_state + + if parsed_args.task_state: + search_opts['task_state'] = parsed_args.task_state + + if parsed_args.power_state: + # taken from 'InstancePowerState' object field in nova + power_state = { + 'pending': 0, + 'running': 1, + 'paused': 3, + 'shutdown': 4, + 'crashed': 6, + 'suspended': 7, + }[parsed_args.power_state] + search_opts['power_state'] = power_state + if parsed_args.tags: if compute_client.api_version < api_versions.APIVersion('2.26'): msg = _( diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index efc105e565..0eeb2cec23 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -2986,7 +2986,6 @@ def setUp(self): super(TestServerList, self).setUp() self.search_opts = { - 'availability_zone': None, 'reservation_id': None, 'ip': None, 'ip6': None, @@ -3431,7 +3430,7 @@ def test_server_list_v266_with_invalid_changes_before( 'Invalid time value' ) - def test_server_with_changes_before_older_version(self): + def test_server_with_changes_before_pre_v266(self): self.app.client_manager.compute.api_version = ( api_versions.APIVersion('2.65')) @@ -3587,6 +3586,143 @@ def test_server_list_with_not_tag_pre_v226(self): '--os-compute-api-version 2.26 or greater is required', str(ex)) + def test_server_list_with_availability_zone(self): + arglist = [ + '--availability-zone', 'test-az', + ] + verifylist = [ + ('availability_zone', 'test-az'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.search_opts['availability_zone'] = 'test-az' + self.servers_mock.list.assert_called_with(**self.kwargs) + self.assertEqual(self.columns, columns) + self.assertEqual(tuple(self.data), tuple(data)) + + def test_server_list_with_key_name(self): + arglist = [ + '--key-name', 'test-key', + ] + verifylist = [ + ('key_name', 'test-key'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.search_opts['key_name'] = 'test-key' + self.servers_mock.list.assert_called_with(**self.kwargs) + self.assertEqual(self.columns, columns) + self.assertEqual(tuple(self.data), tuple(data)) + + def test_server_list_with_config_drive(self): + arglist = [ + '--config-drive', + ] + verifylist = [ + ('has_config_drive', True), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.search_opts['config_drive'] = True + self.servers_mock.list.assert_called_with(**self.kwargs) + self.assertEqual(self.columns, columns) + self.assertEqual(tuple(self.data), tuple(data)) + + def test_server_list_with_no_config_drive(self): + arglist = [ + '--no-config-drive', + ] + verifylist = [ + ('has_config_drive', False), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.search_opts['config_drive'] = False + self.servers_mock.list.assert_called_with(**self.kwargs) + self.assertEqual(self.columns, columns) + self.assertEqual(tuple(self.data), tuple(data)) + + def test_server_list_with_progress(self): + arglist = [ + '--progress', '100', + ] + verifylist = [ + ('progress', 100), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.search_opts['progress'] = '100' + self.servers_mock.list.assert_called_with(**self.kwargs) + self.assertEqual(self.columns, columns) + self.assertEqual(tuple(self.data), tuple(data)) + + def test_server_list_with_progress_invalid(self): + arglist = [ + '--progress', '101', + ] + + self.assertRaises( + utils.ParserException, + self.check_parser, self.cmd, arglist, verify_args=[]) + + def test_server_list_with_vm_state(self): + arglist = [ + '--vm-state', 'active', + ] + verifylist = [ + ('vm_state', 'active'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.search_opts['vm_state'] = 'active' + self.servers_mock.list.assert_called_with(**self.kwargs) + self.assertEqual(self.columns, columns) + self.assertEqual(tuple(self.data), tuple(data)) + + def test_server_list_with_task_state(self): + arglist = [ + '--task-state', 'deleting', + ] + verifylist = [ + ('task_state', 'deleting'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.search_opts['task_state'] = 'deleting' + self.servers_mock.list.assert_called_with(**self.kwargs) + self.assertEqual(self.columns, columns) + self.assertEqual(tuple(self.data), tuple(data)) + + def test_server_list_with_power_state(self): + arglist = [ + '--power-state', 'running', + ] + verifylist = [ + ('power_state', 'running'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.search_opts['power_state'] = 1 + self.servers_mock.list.assert_called_with(**self.kwargs) + self.assertEqual(self.columns, columns) + self.assertEqual(tuple(self.data), tuple(data)) + class TestServerLock(TestServer): diff --git a/releasenotes/notes/add-missing-server-list-opts-c41e97e86ff1e1ca.yaml b/releasenotes/notes/add-missing-server-list-opts-c41e97e86ff1e1ca.yaml new file mode 100644 index 0000000000..f536eb9ea1 --- /dev/null +++ b/releasenotes/notes/add-missing-server-list-opts-c41e97e86ff1e1ca.yaml @@ -0,0 +1,16 @@ +--- +features: + - | + Add a number of additional options to the ``server list`` command: + + - ``--availability-zone`` + - ``--key-name`` + - ``--config-drive``, ``--no-config-drive`` + - ``--progress`` + - ``--vm-state`` + - ``--task-state`` + - ``--power-state`` +upgrade: + - | + The ``openstack server list --status`` parameter will now validate the + requested status. From 8a0f3fc6a8b30328c575bb5c3fc21ddc4f500d9d Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 18 Nov 2020 15:38:18 +0000 Subject: [PATCH 2315/3095] compute: Add missing options for 'server set' Add a new '--no-password' option to unset the password on an existing server. In addition, add a new '--password' option that replaces the interactive '--root-password' option. This makes sense given no other commands uses interactive password options. Checks that rely on specific API microversions now run before we execute any action, to avoid situations where an update is only partially applied. Change-Id: Ibf8717efdd418a2d95215b4d9ab2acf0d57c4a70 Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server.py | 62 ++++++++++++++----- .../tests/unit/compute/v2/test_server.py | 32 ++++++++++ ...sing-server-set-opts-e1b4300f5f42e863.yaml | 10 +++ 3 files changed, 87 insertions(+), 17 deletions(-) create mode 100644 releasenotes/notes/add-missing-server-set-opts-e1b4300f5f42e863.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index c49c181559..50299d65d0 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -3472,18 +3472,35 @@ def get_parser(self, prog_name): metavar='', help=_('New server name'), ) - parser.add_argument( + password_group = parser.add_mutually_exclusive_group() + password_group.add_argument( + '--password', + help=_('Set the server password'), + ) + password_group.add_argument( + '--no-password', + action='store_true', + help=_( + 'Clear the admin password for the server from the metadata ' + 'service; note that this action does not actually change the ' + 'server password' + ), + ) + # TODO(stephenfin): Remove this in a future major version + password_group.add_argument( '--root-password', action="store_true", - help=_('Set new root password (interactive only)'), + help=argparse.SUPPRESS, ) parser.add_argument( '--property', metavar='', action=parseractions.KeyValueAction, dest='properties', - help=_('Property to add/change for this server ' - '(repeat option to set multiple properties)'), + help=_( + 'Property to add/change for this server ' + '(repeat option to set multiple properties)' + ), ) parser.add_argument( '--state', @@ -3494,8 +3511,10 @@ def get_parser(self, prog_name): parser.add_argument( '--description', metavar='', - help=_('New server description (supported by ' - '--os-compute-api-version 2.19 or above)'), + help=_( + 'New server description ' + '(supported by --os-compute-api-version 2.19 or above)' + ), ) parser.add_argument( '--tag', @@ -3519,6 +3538,22 @@ def take_action(self, parsed_args): parsed_args.server, ) + if parsed_args.description: + if server.api_version < api_versions.APIVersion("2.19"): + msg = _( + '--os-compute-api-version 2.19 or greater is required to ' + 'support the --description option' + ) + raise exceptions.CommandError(msg) + + if parsed_args.tags: + if server.api_version < api_versions.APIVersion('2.26'): + msg = _( + '--os-compute-api-version 2.26 or greater is required to ' + 'support the --tag option' + ) + raise exceptions.CommandError(msg) + if parsed_args.name: server.update(name=parsed_args.name) @@ -3536,22 +3571,15 @@ def take_action(self, parsed_args): else: msg = _("Passwords do not match, password unchanged") raise exceptions.CommandError(msg) + elif parsed_args.password: + server.change_password(parsed_args.password) + elif parsed_args.no_password: + server.clear_password() if parsed_args.description: - if server.api_version < api_versions.APIVersion("2.19"): - msg = _("Description is not supported for " - "--os-compute-api-version less than 2.19") - raise exceptions.CommandError(msg) server.update(description=parsed_args.description) if parsed_args.tags: - if server.api_version < api_versions.APIVersion('2.26'): - msg = _( - '--os-compute-api-version 2.26 or greater is required to ' - 'support the --tag option' - ) - raise exceptions.CommandError(msg) - for tag in parsed_args.tags: server.add_tag(tag=tag) diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index f046925af1..0f33dd7047 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -6207,6 +6207,7 @@ def setUp(self): 'update': None, 'reset_state': None, 'change_password': None, + 'clear_password': None, 'add_tag': None, 'set_tags': None, } @@ -6290,6 +6291,37 @@ def test_server_set_with_property(self): self.fake_servers[0], parsed_args.properties) self.assertIsNone(result) + def test_server_set_with_password(self): + arglist = [ + '--password', 'foo', + 'foo_vm', + ] + verifylist = [ + ('password', 'foo'), + ('server', 'foo_vm'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.fake_servers[0].change_password.assert_called_once_with('foo') + + def test_server_set_with_no_password(self): + arglist = [ + '--no-password', + 'foo_vm', + ] + verifylist = [ + ('no_password', True), + ('server', 'foo_vm'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.fake_servers[0].clear_password.assert_called_once_with() + + # TODO(stephenfin): Remove this in a future major version @mock.patch.object(getpass, 'getpass', return_value=mock.sentinel.fake_pass) def test_server_set_with_root_password(self, mock_getpass): diff --git a/releasenotes/notes/add-missing-server-set-opts-e1b4300f5f42e863.yaml b/releasenotes/notes/add-missing-server-set-opts-e1b4300f5f42e863.yaml new file mode 100644 index 0000000000..ccffaa1fe7 --- /dev/null +++ b/releasenotes/notes/add-missing-server-set-opts-e1b4300f5f42e863.yaml @@ -0,0 +1,10 @@ +--- +features: + - | + Add ``--no-password`` option to ``server set`` command, allowing users + to clear the admin password from the metadata service. Note that this does + not actually change the server password. +upgrade: + - | + The ``server set --root-password`` option has been deprecated in favour of + a non-interactive ``--password`` option. From fc24142ed41e15622687558c68d670bdc37223f0 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 18 Nov 2020 15:55:44 +0000 Subject: [PATCH 2316/3095] compute: Add missing options for 'keypair list' Add pagination parameters, '--limit' and '--marker'. This isn't compatible with our client-side '--project' parameter so we error out for that. Change-Id: I403cf0fb7aabad4a3dfda5adae62d47ecf7faf5c Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/keypair.py | 47 ++++++++++++- .../tests/unit/compute/v2/test_keypair.py | 68 +++++++++++++++++++ ...ng-keypair-list-opts-243a33d8276f91b8.yaml | 5 ++ 3 files changed, 117 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/add-missing-keypair-list-opts-243a33d8276f91b8.yaml diff --git a/openstackclient/compute/v2/keypair.py b/openstackclient/compute/v2/keypair.py index 19e30bff10..7dabf78d9c 100644 --- a/openstackclient/compute/v2/keypair.py +++ b/openstackclient/compute/v2/keypair.py @@ -249,12 +249,43 @@ def get_parser(self, prog_name): ), ) identity_common.add_project_domain_option_to_parser(parser) + parser.add_argument( + '--marker', + help=_('The last keypair ID of the previous page'), + ) + parser.add_argument( + '--limit', + type=int, + help=_('Maximum number of keypairs to display'), + ) return parser def take_action(self, parsed_args): compute_client = self.app.client_manager.sdk_connection.compute identity_client = self.app.client_manager.identity + kwargs = {} + + if parsed_args.marker: + if not sdk_utils.supports_microversion(compute_client, '2.35'): + msg = _( + '--os-compute-api-version 2.35 or greater is required ' + 'to support the --marker option' + ) + raise exceptions.CommandError(msg) + + kwargs['marker'] = parsed_args.marker + + if parsed_args.limit: + if not sdk_utils.supports_microversion(compute_client, '2.35'): + msg = _( + '--os-compute-api-version 2.35 or greater is required ' + 'to support the --limit option' + ) + raise exceptions.CommandError(msg) + + kwargs['limit'] = parsed_args.limit + if parsed_args.project: if not sdk_utils.supports_microversion(compute_client, '2.10'): msg = _( @@ -263,6 +294,14 @@ def take_action(self, parsed_args): ) raise exceptions.CommandError(msg) + if parsed_args.marker: + # NOTE(stephenfin): Because we're doing this client-side, we + # can't really rely on the marker, because we don't know what + # user the marker is associated with + msg = _( + '--project is not compatible with --marker' + ) + # NOTE(stephenfin): This is done client side because nova doesn't # currently support doing so server-side. If this is slow, we can # think about spinning up a threadpool or similar. @@ -275,7 +314,8 @@ def take_action(self, parsed_args): data = [] for user in users: - data.extend(compute_client.keypairs(user_id=user.id)) + kwargs['user_id'] = user.id + data.extend(compute_client.keypairs(**kwargs)) elif parsed_args.user: if not sdk_utils.supports_microversion(compute_client, '2.10'): msg = _( @@ -289,10 +329,11 @@ def take_action(self, parsed_args): parsed_args.user, parsed_args.user_domain, ) + kwargs['user_id'] = user.id - data = compute_client.keypairs(user_id=user.id) + data = compute_client.keypairs(**kwargs) else: - data = compute_client.keypairs() + data = compute_client.keypairs(**kwargs) columns = ( "Name", diff --git a/openstackclient/tests/unit/compute/v2/test_keypair.py b/openstackclient/tests/unit/compute/v2/test_keypair.py index 5a17808fa1..65d9396aee 100644 --- a/openstackclient/tests/unit/compute/v2/test_keypair.py +++ b/openstackclient/tests/unit/compute/v2/test_keypair.py @@ -569,6 +569,74 @@ def test_keypair_list_conflicting_user_options(self): tests_utils.ParserException, self.check_parser, self.cmd, arglist, None) + @mock.patch.object( + sdk_utils, 'supports_microversion', new=mock.Mock(return_value=True)) + def test_keypair_list_with_limit(self): + arglist = [ + '--limit', '1', + ] + verifylist = [ + ('limit', 1), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.sdk_client.keypairs.assert_called_with(limit=1) + + @mock.patch.object( + sdk_utils, 'supports_microversion', new=mock.Mock(return_value=False)) + def test_keypair_list_with_limit_pre_v235(self): + arglist = [ + '--limit', '1', + ] + verifylist = [ + ('limit', 1), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + self.assertIn( + '--os-compute-api-version 2.35 or greater is required', str(ex)) + + @mock.patch.object( + sdk_utils, 'supports_microversion', new=mock.Mock(return_value=True)) + def test_keypair_list_with_marker(self): + arglist = [ + '--marker', 'test_kp', + ] + verifylist = [ + ('marker', 'test_kp'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.sdk_client.keypairs.assert_called_with(marker='test_kp') + + @mock.patch.object( + sdk_utils, 'supports_microversion', new=mock.Mock(return_value=False)) + def test_keypair_list_with_marker_pre_v235(self): + arglist = [ + '--marker', 'test_kp', + ] + verifylist = [ + ('marker', 'test_kp'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + self.assertIn( + '--os-compute-api-version 2.35 or greater is required', str(ex)) + class TestKeypairShow(TestKeypair): diff --git a/releasenotes/notes/add-missing-keypair-list-opts-243a33d8276f91b8.yaml b/releasenotes/notes/add-missing-keypair-list-opts-243a33d8276f91b8.yaml new file mode 100644 index 0000000000..789eaa029e --- /dev/null +++ b/releasenotes/notes/add-missing-keypair-list-opts-243a33d8276f91b8.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``--limit`` and ``--marker`` options to ``keypair list`` command, to + configure pagination of results. From b34905722015646538c8557f9fa91fc2b5edffdb Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Tue, 8 Dec 2020 15:37:14 +0000 Subject: [PATCH 2317/3095] tests: Remove unused fake method The FakeServerMigration.get_server_migrations method was added in change I15b4a5aca8d0dee59dd293e7b1c7272cdfbeea20 but has never been used. Remove it. Change-Id: I6089c5200737b9319a8e96f2a2fc18b7cdd6b2c6 Signed-off-by: Stephen Finucane --- .../tests/unit/compute/v2/fakes.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index d3d037a9a8..b667c691d3 100644 --- a/openstackclient/tests/unit/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -1631,22 +1631,3 @@ def create_server_migrations(attrs=None, methods=None, count=2): attrs, methods)) return migrations - - @staticmethod - def get_server_migrations(migrations=None, count=2): - """Get an iterable MagicMock object with a list of faked migrations. - - If server migrations list is provided, then initialize the Mock object - with the list. Otherwise create one. - - :param List migrations: - A list of FakeResource objects faking server migrations - :param int count: - The number of server migrations to fake - :return: - An iterable Mock object with side_effect set to a list of faked - server migrations - """ - if migrations is None: - migrations = FakeServerMigration.create_server_migrations(count) - return mock.Mock(side_effect=migrations) From 958344733aa6d8aea6cb8d06cc4d879fe1ee44a6 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 3 Dec 2020 12:20:40 +0000 Subject: [PATCH 2318/3095] compute: Add missing options for 'server image create' Add a '--property' option to record arbitrary key/value metadata to 'meta_data.json' on the metadata server. Change-Id: I267f3290fce3692cbd1ff6a9af146c2736ee31fe Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server_image.py | 17 +++++++++++++++-- .../tests/unit/compute/v2/test_server_image.py | 6 ++++++ ...rver-image-create-opts-3c19a7492dc50fd7.yaml | 6 ++++++ 3 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/add-missing-server-image-create-opts-3c19a7492dc50fd7.yaml diff --git a/openstackclient/compute/v2/server_image.py b/openstackclient/compute/v2/server_image.py index c12bc2b3ae..6c0e3b22cf 100644 --- a/openstackclient/compute/v2/server_image.py +++ b/openstackclient/compute/v2/server_image.py @@ -18,6 +18,7 @@ import importlib import logging +from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -48,6 +49,16 @@ def get_parser(self, prog_name): metavar='', help=_('Name of new disk image (default: server name)'), ) + parser.add_argument( + '--property', + metavar='', + dest='properties', + action=parseractions.KeyValueAction, + help=_( + 'Set a new property to meta_data.json on the metadata server ' + '(repeat option to set multiple values)' + ), + ) parser.add_argument( '--wait', action='store_true', @@ -76,6 +87,7 @@ def _show_progress(progress): image_id = compute_client.servers.create_image( server.id, image_name, + parsed_args.properties, ) image_client = self.app.client_manager.image @@ -89,8 +101,8 @@ def _show_progress(progress): ): self.app.stdout.write('\n') else: - LOG.error(_('Error creating server image: %s'), - parsed_args.server) + LOG.error( + _('Error creating server image: %s'), parsed_args.server) raise exceptions.CommandError if self.app.client_manager._api_version['image'] == '1': @@ -105,4 +117,5 @@ def _show_progress(progress): ] ) info = image_module._format_image(image) + return zip(*sorted(info.items())) diff --git a/openstackclient/tests/unit/compute/v2/test_server_image.py b/openstackclient/tests/unit/compute/v2/test_server_image.py index 06f6017c11..66452a8bb8 100644 --- a/openstackclient/tests/unit/compute/v2/test_server_image.py +++ b/openstackclient/tests/unit/compute/v2/test_server_image.py @@ -130,6 +130,7 @@ def test_server_image_create_defaults(self): self.servers_mock.create_image.assert_called_with( servers[0].id, servers[0].name, + None, ) self.assertEqual(self.image_columns(images[0]), columns) @@ -141,11 +142,13 @@ def test_server_image_create_options(self): arglist = [ '--name', 'img-nam', + '--property', 'key=value', servers[0].id, ] verifylist = [ ('name', 'img-nam'), ('server', servers[0].id), + ('properties', {'key': 'value'}), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -158,6 +161,7 @@ def test_server_image_create_options(self): self.servers_mock.create_image.assert_called_with( servers[0].id, 'img-nam', + {'key': 'value'}, ) self.assertEqual(self.image_columns(images[0]), columns) @@ -188,6 +192,7 @@ def test_server_create_image_wait_fail(self, mock_wait_for_status): self.servers_mock.create_image.assert_called_with( servers[0].id, servers[0].name, + None, ) mock_wait_for_status.assert_called_once_with( @@ -220,6 +225,7 @@ def test_server_create_image_wait_ok(self, mock_wait_for_status): self.servers_mock.create_image.assert_called_with( servers[0].id, servers[0].name, + None, ) mock_wait_for_status.assert_called_once_with( diff --git a/releasenotes/notes/add-missing-server-image-create-opts-3c19a7492dc50fd7.yaml b/releasenotes/notes/add-missing-server-image-create-opts-3c19a7492dc50fd7.yaml new file mode 100644 index 0000000000..b5e3c0627a --- /dev/null +++ b/releasenotes/notes/add-missing-server-image-create-opts-3c19a7492dc50fd7.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--property`` option to ``server image create`` command, allowing + users to record arbitrary key/value metadata to ``meta_data.json`` on + the metadata server. From d5026278ede4dbe8126839ec59ec4ca371e806a8 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 3 Dec 2020 12:47:10 +0000 Subject: [PATCH 2319/3095] compute: Add 'server volume list' command This replaces the old 'nova volume-attachments' command. Change-Id: Icb98766f98bd1f2469bdb6df62b4624711f98422 Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server_volume.py | 73 ++++++++ .../tests/unit/compute/v2/fakes.py | 60 +++++++ .../unit/compute/v2/test_server_volume.py | 167 ++++++++++++++++++ ...server-volume-update-89740dca61596dd1.yaml | 5 + setup.cfg | 2 + 5 files changed, 307 insertions(+) create mode 100644 openstackclient/compute/v2/server_volume.py create mode 100644 openstackclient/tests/unit/compute/v2/test_server_volume.py create mode 100644 releasenotes/notes/add-server-volume-update-89740dca61596dd1.yaml diff --git a/openstackclient/compute/v2/server_volume.py b/openstackclient/compute/v2/server_volume.py new file mode 100644 index 0000000000..8a931ae5ab --- /dev/null +++ b/openstackclient/compute/v2/server_volume.py @@ -0,0 +1,73 @@ +# Copyright 2020, Red Hat Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Compute v2 Server action implementations""" + +from novaclient import api_versions +from osc_lib.command import command +from osc_lib import utils + +from openstackclient.i18n import _ + + +class ListServerVolume(command.Lister): + """List all the volumes attached to a server.""" + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'server', + help=_('Server to list volume attachments for (name or ID)'), + ) + return parser + + def take_action(self, parsed_args): + + compute_client = self.app.client_manager.compute + + server = utils.find_resource( + compute_client.servers, + parsed_args.server, + ) + + volumes = compute_client.volumes.get_server_volumes(server.id) + + columns = ( + 'id', + 'device', + 'serverId', + 'volumeId', + ) + column_headers = ( + 'ID', + 'Device', + 'Server ID', + 'Volume ID', + ) + if compute_client.api_version >= api_versions.APIVersion('2.70'): + columns += ('tag',) + column_headers += ('Tag',) + + if compute_client.api_version >= api_versions.APIVersion('2.79'): + columns += ('delete_on_termination',) + column_headers += ('Delete On Termination?',) + + return ( + column_headers, + ( + utils.get_item_properties( + s, columns, mixed_case_fields=('serverId', 'volumeId') + ) for s in volumes + ), + ) diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index b667c691d3..e4cf10454e 100644 --- a/openstackclient/tests/unit/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -1631,3 +1631,63 @@ def create_server_migrations(attrs=None, methods=None, count=2): attrs, methods)) return migrations + + +class FakeVolumeAttachment(object): + """Fake one or more volume attachments (BDMs).""" + + @staticmethod + def create_one_volume_attachment(attrs=None, methods=None): + """Create a fake volume attachment. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :return: + A FakeResource object, with id, device, and so on + """ + attrs = attrs or {} + methods = methods or {} + + # Set default attributes. + volume_attachment_info = { + "id": uuid.uuid4().hex, + "device": "/dev/sdb", + "serverId": uuid.uuid4().hex, + "volumeId": uuid.uuid4().hex, + # introduced in API microversion 2.70 + "tag": "foo", + # introduced in API microversion 2.79 + "delete_on_termination": True, + } + + # Overwrite default attributes. + volume_attachment_info.update(attrs) + + volume_attachment = fakes.FakeResource( + info=copy.deepcopy(volume_attachment_info), + methods=methods, + loaded=True) + return volume_attachment + + @staticmethod + def create_volume_attachments(attrs=None, methods=None, count=2): + """Create multiple fake volume attachments (BDMs). + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :param int count: + The number of server migrations to fake + :return: + A list of FakeResource objects faking the volume attachments. + """ + volume_attachments = [] + for i in range(0, count): + volume_attachments.append( + FakeVolumeAttachment.create_one_volume_attachment( + attrs, methods)) + + return volume_attachments diff --git a/openstackclient/tests/unit/compute/v2/test_server_volume.py b/openstackclient/tests/unit/compute/v2/test_server_volume.py new file mode 100644 index 0000000000..d09c287450 --- /dev/null +++ b/openstackclient/tests/unit/compute/v2/test_server_volume.py @@ -0,0 +1,167 @@ +# 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 novaclient import api_versions + +from openstackclient.compute.v2 import server_volume +from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes + + +class TestServerVolume(compute_fakes.TestComputev2): + + def setUp(self): + super().setUp() + + # Get a shortcut to the compute client ServerManager Mock + self.servers_mock = self.app.client_manager.compute.servers + self.servers_mock.reset_mock() + + # Get a shortcut to the compute client VolumeManager mock + self.servers_volumes_mock = self.app.client_manager.compute.volumes + self.servers_volumes_mock.reset_mock() + + +class TestServerVolumeList(TestServerVolume): + + def setUp(self): + super().setUp() + + self.server = compute_fakes.FakeServer.create_one_server() + self.volume_attachments = ( + compute_fakes.FakeVolumeAttachment.create_volume_attachments()) + + self.servers_mock.get.return_value = self.server + self.servers_volumes_mock.get_server_volumes.return_value = ( + self.volume_attachments) + + # Get the command object to test + self.cmd = server_volume.ListServerVolume(self.app, None) + + def test_server_volume_list(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.1') + + arglist = [ + self.server.id, + ] + verifylist = [ + ('server', self.server.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual(('ID', 'Device', 'Server ID', 'Volume ID'), columns) + self.assertEqual( + ( + ( + self.volume_attachments[0].id, + self.volume_attachments[0].device, + self.volume_attachments[0].serverId, + self.volume_attachments[0].volumeId, + ), + ( + self.volume_attachments[1].id, + self.volume_attachments[1].device, + self.volume_attachments[1].serverId, + self.volume_attachments[1].volumeId, + ), + ), + tuple(data), + ) + self.servers_volumes_mock.get_server_volumes.assert_called_once_with( + self.server.id) + + def test_server_volume_list_with_tags(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.70') + + arglist = [ + self.server.id, + ] + verifylist = [ + ('server', self.server.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual( + ('ID', 'Device', 'Server ID', 'Volume ID', 'Tag',), columns, + ) + self.assertEqual( + ( + ( + self.volume_attachments[0].id, + self.volume_attachments[0].device, + self.volume_attachments[0].serverId, + self.volume_attachments[0].volumeId, + self.volume_attachments[0].tag, + ), + ( + self.volume_attachments[1].id, + self.volume_attachments[1].device, + self.volume_attachments[1].serverId, + self.volume_attachments[1].volumeId, + self.volume_attachments[1].tag, + ), + ), + tuple(data), + ) + self.servers_volumes_mock.get_server_volumes.assert_called_once_with( + self.server.id) + + def test_server_volume_list_with_delete_on_attachment(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.79') + + arglist = [ + self.server.id, + ] + verifylist = [ + ('server', self.server.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual( + ( + 'ID', 'Device', 'Server ID', 'Volume ID', 'Tag', + 'Delete On Termination?', + ), + columns, + ) + self.assertEqual( + ( + ( + self.volume_attachments[0].id, + self.volume_attachments[0].device, + self.volume_attachments[0].serverId, + self.volume_attachments[0].volumeId, + self.volume_attachments[0].tag, + self.volume_attachments[0].delete_on_termination, + ), + ( + self.volume_attachments[1].id, + self.volume_attachments[1].device, + self.volume_attachments[1].serverId, + self.volume_attachments[1].volumeId, + self.volume_attachments[1].tag, + self.volume_attachments[1].delete_on_termination, + ), + ), + tuple(data), + ) + self.servers_volumes_mock.get_server_volumes.assert_called_once_with( + self.server.id) diff --git a/releasenotes/notes/add-server-volume-update-89740dca61596dd1.yaml b/releasenotes/notes/add-server-volume-update-89740dca61596dd1.yaml new file mode 100644 index 0000000000..d1c0501ff8 --- /dev/null +++ b/releasenotes/notes/add-server-volume-update-89740dca61596dd1.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``server volume list`` command, to list the volumes attached to an + instance. diff --git a/setup.cfg b/setup.cfg index 9299fb918a..3fe7fa38f9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -153,6 +153,8 @@ openstack.compute.v2 = server_image_create = openstackclient.compute.v2.server_image:CreateServerImage + server_volume_list = openstackclient.compute.v2.server_volume:ListServerVolume + usage_list = openstackclient.compute.v2.usage:ListUsage usage_show = openstackclient.compute.v2.usage:ShowUsage From 64c2a1a453fce8f4e2e7e8441692af007c176459 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 11 Nov 2020 18:39:22 +0000 Subject: [PATCH 2320/3095] Add 'server shelve --offload', 'server shelve --wait' options The '--offload' option allows us to explicitly request that the server be offloaded once shelved or if already shelved. The '--wait' option allows us to wait for the shelve and/or offload operations to complete before returning. It is implied when attempting to offload a server than is not yet shelved. Change-Id: Id226831e3c09bc95c34b222151b27391a844b073 Signed-off-by: Stephen Finucane --- doc/source/cli/data/nova.csv | 2 +- openstackclient/compute/v2/server.py | 100 +++++++++++++- .../tests/functional/common/test_help.py | 2 +- .../tests/unit/compute/v2/test_server.py | 122 +++++++++++++++++- ...-shelve-offload-wait-d0a5c8ba92586f72.yaml | 8 ++ 5 files changed, 221 insertions(+), 13 deletions(-) create mode 100644 releasenotes/notes/add-shelve-offload-wait-d0a5c8ba92586f72.yaml diff --git a/doc/source/cli/data/nova.csv b/doc/source/cli/data/nova.csv index 2004007f8d..c319a4a69d 100644 --- a/doc/source/cli/data/nova.csv +++ b/doc/source/cli/data/nova.csv @@ -114,7 +114,7 @@ service-force-down,compute service set --force,Force service to down. service-list,compute service list,Show a list of all running services. set-password,server set --root-password,Change the admin password for a server. shelve,server shelve,Shelve a server. -shelve-offload,,Remove a shelved server from the compute node. +shelve-offload,shelve --offload,Remove a shelved server from the compute node. show,server show,Show details about the given server. ssh,server ssh,SSH into a server. start,server start,Start the server(s). diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 50299d65d0..33545a74e8 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -3585,25 +3585,115 @@ def take_action(self, parsed_args): class ShelveServer(command.Command): - _description = _("Shelve server(s)") + """Shelve and optionally offload server(s). + + Shelving a server creates a snapshot of the server and stores this + snapshot before shutting down the server. This shelved server can then be + offloaded or deleted from the host, freeing up remaining resources on the + host, such as network interfaces. Shelved servers can be unshelved, + restoring the server from the snapshot. Shelving is therefore useful where + users wish to retain the UUID and IP of a server, without utilizing other + resources or disks. + + Most clouds are configured to automatically offload shelved servers + immediately or after a small delay. For clouds where this is not + configured, or where the delay is larger, offloading can be manually + specified. This is an admin-only operation by default. + """ def get_parser(self, prog_name): parser = super(ShelveServer, self).get_parser(prog_name) parser.add_argument( - 'server', + 'servers', metavar='', nargs='+', help=_('Server(s) to shelve (name or ID)'), ) + parser.add_argument( + '--offload', + action='store_true', + default=False, + help=_( + 'Remove the shelved server(s) from the host (admin only). ' + 'Invoking this option on an unshelved server(s) will result ' + 'in the server being shelved first' + ), + ) + parser.add_argument( + '--wait', + action='store_true', + default=False, + help=_('Wait for shelve and/or offload operation to complete'), + ) return parser def take_action(self, parsed_args): + + def _show_progress(progress): + if progress: + self.app.stdout.write('\rProgress: %s' % progress) + self.app.stdout.flush() + compute_client = self.app.client_manager.compute - for server in parsed_args.server: - utils.find_resource( + + for server in parsed_args.servers: + server_obj = utils.find_resource( + compute_client.servers, + server, + ) + if server_obj.status.lower() in ('shelved', 'shelved_offloaded'): + continue + + server_obj.shelve() + + # if we don't hav to wait, either because it was requested explicitly + # or is required implicitly, then our job is done + if not parsed_args.wait and not parsed_args.offload: + return + + for server in parsed_args.servers: + # TODO(stephenfin): We should wait for these in parallel using e.g. + # https://review.opendev.org/c/openstack/osc-lib/+/762503/ + if not utils.wait_for_status( + compute_client.servers.get, server_obj.id, + success_status=('shelved', 'shelved_offloaded'), + callback=_show_progress, + ): + LOG.error(_('Error shelving server: %s'), server_obj.id) + self.app.stdout.write( + _('Error shelving server: %s\n') % server_obj.id) + raise SystemExit + + if not parsed_args.offload: + return + + for server in parsed_args.servers: + server_obj = utils.find_resource( compute_client.servers, server, - ).shelve() + ) + if server_obj.status.lower() == 'shelved_offloaded': + continue + + server_obj.shelve_offload() + + if not parsed_args.wait: + return + + for server in parsed_args.servers: + # TODO(stephenfin): We should wait for these in parallel using e.g. + # https://review.opendev.org/c/openstack/osc-lib/+/762503/ + if not utils.wait_for_status( + compute_client.servers.get, server_obj.id, + success_status=('shelved_offloaded',), + callback=_show_progress, + ): + LOG.error( + _('Error offloading shelved server %s'), server_obj.id) + self.app.stdout.write( + _('Error offloading shelved server: %s\n') % ( + server_obj.id)) + raise SystemExit class ShowServer(command.ShowOne): diff --git a/openstackclient/tests/functional/common/test_help.py b/openstackclient/tests/functional/common/test_help.py index 3a9aef9ef3..c55741f19c 100644 --- a/openstackclient/tests/functional/common/test_help.py +++ b/openstackclient/tests/functional/common/test_help.py @@ -43,7 +43,7 @@ class HelpTests(base.TestCase): ('server resize', 'Scale server to a new flavor'), ('server resume', 'Resume server(s)'), ('server set', 'Set server properties'), - ('server shelve', 'Shelve server(s)'), + ('server shelve', 'Shelve and optionally offload server(s)'), ('server show', 'Show server details'), ('server ssh', 'SSH to server'), ('server start', 'Start server(s).'), diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 0f33dd7047..9ad6d15508 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -6434,16 +6434,126 @@ def setUp(self): # Get the command object to test self.cmd = server.ShelveServer(self.app, None) - # Set shelve method to be tested. - self.methods = { + def test_shelve(self): + server_info = {'status': 'ACTIVE'} + server_methods = { 'shelve': None, + 'shelve_offload': None, } - def test_shelve_one_server(self): - self.run_method_with_servers('shelve', 1) + server = compute_fakes.FakeServer.create_one_server( + attrs=server_info, methods=server_methods) + self.servers_mock.get.return_value = server - def test_shelve_multi_servers(self): - self.run_method_with_servers('shelve', 3) + arglist = [server.name] + verifylist = [ + ('servers', [server.name]), + ('wait', False), + ('offload', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.assertIsNone(result) + + self.servers_mock.get.assert_called_once_with(server.name) + server.shelve.assert_called_once_with() + server.shelve_offload.assert_not_called() + + def test_shelve_already_shelved(self): + server_info = {'status': 'SHELVED'} + server_methods = { + 'shelve': None, + 'shelve_offload': None, + } + + server = compute_fakes.FakeServer.create_one_server( + attrs=server_info, methods=server_methods) + self.servers_mock.get.return_value = server + + arglist = [server.name] + verifylist = [ + ('servers', [server.name]), + ('wait', False), + ('offload', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.assertIsNone(result) + + self.servers_mock.get.assert_called_once_with(server.name) + server.shelve.assert_not_called() + server.shelve_offload.assert_not_called() + + @mock.patch.object(common_utils, 'wait_for_status', return_value=True) + def test_shelve_with_wait(self, mock_wait_for_status): + server_info = {'status': 'ACTIVE'} + server_methods = { + 'shelve': None, + 'shelve_offload': None, + } + + server = compute_fakes.FakeServer.create_one_server( + attrs=server_info, methods=server_methods) + self.servers_mock.get.return_value = server + + arglist = ['--wait', server.name] + verifylist = [ + ('servers', [server.name]), + ('wait', True), + ('offload', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.assertIsNone(result) + + self.servers_mock.get.assert_called_once_with(server.name) + server.shelve.assert_called_once_with() + server.shelve_offload.assert_not_called() + mock_wait_for_status.assert_called_once_with( + self.servers_mock.get, + server.id, + callback=mock.ANY, + success_status=('shelved', 'shelved_offloaded'), + ) + + @mock.patch.object(common_utils, 'wait_for_status', return_value=True) + def test_shelve_offload(self, mock_wait_for_status): + server_info = {'status': 'ACTIVE'} + server_methods = { + 'shelve': None, + 'shelve_offload': None, + } + + server = compute_fakes.FakeServer.create_one_server( + attrs=server_info, methods=server_methods) + self.servers_mock.get.return_value = server + + arglist = ['--offload', server.name] + verifylist = [ + ('servers', [server.name]), + ('wait', False), + ('offload', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.assertIsNone(result) + + self.servers_mock.get.assert_has_calls([ + mock.call(server.name), + mock.call(server.name), + ]) + server.shelve.assert_called_once_with() + server.shelve_offload.assert_called_once_with() + mock_wait_for_status.assert_called_once_with( + self.servers_mock.get, + server.id, + callback=mock.ANY, + success_status=('shelved', 'shelved_offloaded'), + ) class TestServerShow(TestServer): diff --git a/releasenotes/notes/add-shelve-offload-wait-d0a5c8ba92586f72.yaml b/releasenotes/notes/add-shelve-offload-wait-d0a5c8ba92586f72.yaml new file mode 100644 index 0000000000..ddd3293191 --- /dev/null +++ b/releasenotes/notes/add-shelve-offload-wait-d0a5c8ba92586f72.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Add support for ``--offload`` and ``--wait`` options for ``server shelve``. + ``--offload`` allows users to explicitly request offloading of a shelved + server in environments where automatic offloading is not configured, while + ``--wait`` allows users to wait for the shelve and/or shelve offload + operations to complete. From 2b073c2034acdabb8d4097b7f2c0408e53fe2d63 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 18 Nov 2020 11:27:30 +0000 Subject: [PATCH 2321/3095] Add 'server unshelve --wait' option This was recently added to the 'server shelve' command. Add it now for the 'unshelve' command. Change-Id: I633dd85b60cf70b4f8610f414d82669dd6a53111 Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server.py | 61 ++++++++++---- .../tests/unit/compute/v2/test_server.py | 80 ++++++++++++------- ...-shelve-offload-wait-d0a5c8ba92586f72.yaml | 2 + 3 files changed, 98 insertions(+), 45 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 33545a74e8..59fc4b7d44 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -4131,25 +4131,54 @@ def get_parser(self, prog_name): 'SHELVED_OFFLOADED server (supported by ' '--os-compute-api-version 2.77 or above)'), ) + parser.add_argument( + '--wait', + action='store_true', + default=False, + help=_('Wait for unshelve operation to complete'), + ) return parser def take_action(self, parsed_args): + + def _show_progress(progress): + if progress: + self.app.stdout.write('\rProgress: %s' % progress) + self.app.stdout.flush() + compute_client = self.app.client_manager.compute - support_az = compute_client.api_version >= api_versions.APIVersion( - '2.77') - if not support_az and parsed_args.availability_zone: - msg = _("--os-compute-api-version 2.77 or greater is required " - "to support the '--availability-zone' option.") - raise exceptions.CommandError(msg) + kwargs = {} + + if parsed_args.availability_zone: + if compute_client.api_version < api_versions.APIVersion('2.77'): + msg = _( + '--os-compute-api-version 2.77 or greater is required ' + 'to support the --availability-zone option' + ) + raise exceptions.CommandError(msg) + + kwargs['availability_zone'] = parsed_args.availability_zone for server in parsed_args.server: - if support_az: - utils.find_resource( - compute_client.servers, - server - ).unshelve(availability_zone=parsed_args.availability_zone) - else: - utils.find_resource( - compute_client.servers, - server, - ).unshelve() + server_obj = utils.find_resource( + compute_client.servers, + server, + ) + + if server_obj.status.lower() not in ( + 'shelved', 'shelved_offloaded', + ): + continue + + server_obj.unshelve(**kwargs) + + if parsed_args.wait: + if not utils.wait_for_status( + compute_client.servers.get, server_obj.id, + success_status=('active', 'shutoff'), + callback=_show_progress, + ): + LOG.error(_('Error unshelving server %s'), server_obj.id) + self.app.stdout.write( + _('Error unshelving server: %s\n') % server_obj.id) + raise SystemExit diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 9ad6d15508..2c0cadfc88 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -6969,6 +6969,9 @@ def setUp(self): self.methods = { 'unshelve': None, } + self.attrs = { + 'status': 'SHELVED', + } def test_unshelve_one_server(self): self.run_method_with_servers('unshelve', 1) @@ -6976,55 +6979,74 @@ def test_unshelve_one_server(self): def test_unshelve_multi_servers(self): self.run_method_with_servers('unshelve', 3) - def test_unshelve_server_with_specified_az(self): - server = compute_fakes.FakeServer.create_one_server() + def test_unshelve_with_specified_az(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.77') + + server = compute_fakes.FakeServer.create_one_server( + attrs=self.attrs, methods=self.methods) + self.servers_mock.get.return_value = server arglist = [ - server.id, '--availability-zone', "foo-az", + server.id, ] verifylist = [ ('availability_zone', "foo-az"), ('server', [server.id]) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - ex = self.assertRaises(exceptions.CommandError, - self.cmd.take_action, - parsed_args) - self.assertIn( - '--os-compute-api-version 2.77 or greater is required', str(ex)) - -class TestServerUnshelveV277(TestServerUnshelve): - - def setUp(self): - super(TestServerUnshelveV277, self).setUp() - - self.server = compute_fakes.FakeServer.create_one_server( - methods=self.methods) - - # This is the return value for utils.find_resource() - self.servers_mock.get.return_value = self.server + self.cmd.take_action(parsed_args) - # Get the command object to test - self.cmd = server.UnshelveServer(self.app, None) + self.servers_mock.get.assert_called_with(server.id) + server.unshelve.assert_called_with(availability_zone="foo-az") - def test_specified_az_to_unshelve_with_v277(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.77') + def test_unshelve_with_specified_az_pre_v277(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.76') + server = compute_fakes.FakeServer.create_one_server( + attrs=self.attrs, methods=self.methods) arglist = [ + server.id, '--availability-zone', "foo-az", - self.server.id, ] verifylist = [ ('availability_zone', "foo-az"), - ('server', [self.server.id]) + ('server', [server.id]) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.77 or greater is required', str(ex)) - self.cmd.take_action(parsed_args) - self.servers_mock.get.assert_called_with(self.server.id) - self.server.unshelve.assert_called_with(availability_zone="foo-az") + @mock.patch.object(common_utils, 'wait_for_status', return_value=True) + def test_unshelve_with_wait(self, mock_wait_for_status): + server = compute_fakes.FakeServer.create_one_server( + attrs=self.attrs, methods=self.methods) + self.servers_mock.get.return_value = server + + arglist = ['--wait', server.name] + verifylist = [ + ('server', [server.name]), + ('wait', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.assertIsNone(result) + + self.servers_mock.get.assert_called_once_with(server.name) + server.unshelve.assert_called_once_with() + mock_wait_for_status.assert_called_once_with( + self.servers_mock.get, + server.id, + callback=mock.ANY, + success_status=('active', 'shutoff'), + ) class TestServerGeneral(TestServer): diff --git a/releasenotes/notes/add-shelve-offload-wait-d0a5c8ba92586f72.yaml b/releasenotes/notes/add-shelve-offload-wait-d0a5c8ba92586f72.yaml index ddd3293191..750abd6af1 100644 --- a/releasenotes/notes/add-shelve-offload-wait-d0a5c8ba92586f72.yaml +++ b/releasenotes/notes/add-shelve-offload-wait-d0a5c8ba92586f72.yaml @@ -6,3 +6,5 @@ features: server in environments where automatic offloading is not configured, while ``--wait`` allows users to wait for the shelve and/or shelve offload operations to complete. + - | + Add support ``--wait`` option for ``server shelve``. From d33eb3e1da5730f13a539b70bd6eea04725b12e1 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Fri, 8 Jan 2021 18:42:25 +0000 Subject: [PATCH 2322/3095] Remove retired Karbor support The Karbor project is being retired in Wallaby [1]. Remove the docs for its client. [1] http://lists.openstack.org/pipermail/openstack-discuss/2020-November/018643.html Change-Id: I52d0f6a76cc9bcfc8b33f0e2cd3751859770ac8a Signed-off-by: Stephen Finucane --- doc/requirements.txt | 1 - doc/source/cli/commands.rst | 9 --------- doc/source/cli/plugin-commands/index.rst | 1 - doc/source/cli/plugin-commands/karbor.rst | 4 ---- doc/source/contributor/plugins.rst | 1 - 5 files changed, 16 deletions(-) delete mode 100644 doc/source/cli/plugin-commands/karbor.rst diff --git a/doc/requirements.txt b/doc/requirements.txt index 28030ca422..60a877970c 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -18,7 +18,6 @@ python-designateclient>=2.7.0 # Apache-2.0 python-heatclient>=1.10.0 # Apache-2.0 python-ironicclient>=2.3.0 # Apache-2.0 python-ironic-inspector-client>=1.5.0 # Apache-2.0 -python-karborclient>=0.6.0 # Apache-2.0 python-manilaclient>=2.0.0 # Apache-2.0 python-mistralclient!=3.2.0,>=3.1.0 # Apache-2.0 python-muranoclient>=0.8.2 # Apache-2.0 diff --git a/doc/source/cli/commands.rst b/doc/source/cli/commands.rst index 497c79f0b9..0dfac00bdc 100644 --- a/doc/source/cli/commands.rst +++ b/doc/source/cli/commands.rst @@ -201,15 +201,6 @@ conflicts when creating new plugins. For a complete list check out * ``dataprocessing image``: (**Data Processing (Sahara)**) * ``dataprocessing image tags``: (**Data Processing (Sahara)**) * ``dataprocessing plugin``: (**Data Processing (Sahara)**) -* ``data protection plan``: (**Data Protection (Karbor)**) -* ``data protection restore``: (**Data Protection (Karbor)**) -* ``data protection provider``: (**Data Protection (Karbor)**) -* ``data protection protectable``: (**Data Protection (Karbor)**) -* ``data protection protectable instance``: (**Data Protection (Karbor)**) -* ``data protection trigger``: (**Data Protection (Karbor)**) -* ``data protection checkpoint``: (**Data Protection (Karbor)**) -* ``data protection scheduledoperation``: (**Data Protection (Karbor)**) -* ``data protection operationlog``: (**Data Protection (Karbor)**) * ``loadbalancer``: (**Load Balancer (Octavia)**) * ``loadbalancer healthmonitor``: (**Load Balancer (Octavia)**) * ``loadbalancer l7policy``: (**Load Balancer (Octavia)**) diff --git a/doc/source/cli/plugin-commands/index.rst b/doc/source/cli/plugin-commands/index.rst index 8c9c5c13e3..4e1ce54b13 100644 --- a/doc/source/cli/plugin-commands/index.rst +++ b/doc/source/cli/plugin-commands/index.rst @@ -14,7 +14,6 @@ Plugin Commands heat ironic ironic-inspector - karbor manila mistral neutron diff --git a/doc/source/cli/plugin-commands/karbor.rst b/doc/source/cli/plugin-commands/karbor.rst deleted file mode 100644 index 0e28ba5733..0000000000 --- a/doc/source/cli/plugin-commands/karbor.rst +++ /dev/null @@ -1,4 +0,0 @@ -karbor ------- - -.. autoprogram-cliff:: openstack.data_protection.v1 diff --git a/doc/source/contributor/plugins.rst b/doc/source/contributor/plugins.rst index fe2cc14ad0..067c1b9912 100644 --- a/doc/source/contributor/plugins.rst +++ b/doc/source/contributor/plugins.rst @@ -30,7 +30,6 @@ The following is a list of projects that are an OpenStackClient plugin. - python-heatclient - python-ironicclient - python-ironic-inspector-client -- python-karborclient - python-mistralclient - python-muranoclient - python-neutronclient\*\*\* From bb15b29190dfe77ea0218a01edc1f4886993b177 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Mon, 11 Jan 2021 14:24:48 +0000 Subject: [PATCH 2323/3095] Add reno for change Ic3c555226a220efd9b0f27edffccf6c4c95c2747 Change Ic3c555226a220efd9b0f27edffccf6c4c95c2747 introduced some validation for the 'openstack server group create --policy' command. Call this out in the release notes. Change-Id: I7e00851a03470364db00f0f114fc724b0f686b72 Signed-off-by: Stephen Finucane --- releasenotes/notes/story-2007727-69b705c561309742.yaml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 releasenotes/notes/story-2007727-69b705c561309742.yaml diff --git a/releasenotes/notes/story-2007727-69b705c561309742.yaml b/releasenotes/notes/story-2007727-69b705c561309742.yaml new file mode 100644 index 0000000000..4a9eeae39a --- /dev/null +++ b/releasenotes/notes/story-2007727-69b705c561309742.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + The ``openstack server group create`` command will now validate the policy + value requested with ``--policy``, restricting it to the valid values + allowed by given microversions. From dd89efd5acbabcd4de0f071a2f7fbb93d067a950 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Mon, 11 Jan 2021 14:28:54 +0000 Subject: [PATCH 2324/3095] network: Address nits for I3c313fc9329837dde67815901528a34dca98ebcc Address comments left in the review for $subject. Change-Id: I69449112027736152c9fb62f5fe427efd6a25107 Signed-off-by: Stephen Finucane --- openstackclient/network/v2/address_group.py | 6 +----- .../tests/unit/network/v2/test_address_group.py | 7 +++---- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/openstackclient/network/v2/address_group.py b/openstackclient/network/v2/address_group.py index fe1e14a316..c5b2f12606 100644 --- a/openstackclient/network/v2/address_group.py +++ b/openstackclient/network/v2/address_group.py @@ -36,10 +36,7 @@ def _get_columns(item): def _format_addresses(addresses): - ret = [] - for addr in addresses: - ret.append(str(netaddr.IPNetwork(addr))) - return ret + return [str(netaddr.IPNetwork(addr)) for addr in addresses] def _get_attrs(client_manager, parsed_args): @@ -185,7 +182,6 @@ def take_action(self, parsed_args): parsed_args.project, parsed_args.project_domain, ).id - attrs['tenant_id'] = project_id attrs['project_id'] = project_id data = client.address_groups(**attrs) diff --git a/openstackclient/tests/unit/network/v2/test_address_group.py b/openstackclient/tests/unit/network/v2/test_address_group.py index 3b2b1ab6b9..e4fa8ab3dc 100644 --- a/openstackclient/tests/unit/network/v2/test_address_group.py +++ b/openstackclient/tests/unit/network/v2/test_address_group.py @@ -282,7 +282,7 @@ def test_address_group_list_project(self): columns, data = self.cmd.take_action(parsed_args) self.network.address_groups.assert_called_once_with( - **{'tenant_id': project.id, 'project_id': project.id}) + project_id=project.id) self.assertEqual(self.columns, columns) self.assertItemsEqual(self.data, list(data)) @@ -297,11 +297,10 @@ def test_address_group_project_domain(self): ('project', project.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) - filters = {'tenant_id': project.id, 'project_id': project.id} - self.network.address_groups.assert_called_once_with(**filters) + self.network.address_groups.assert_called_once_with( + project_id=project.id) self.assertEqual(self.columns, columns) self.assertItemsEqual(self.data, list(data)) From ca7f23d0d1876dc53ef4d5ecbf2c5f367aafe13e Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 3 Dec 2020 12:41:25 +0000 Subject: [PATCH 2325/3095] compute: Add 'server volume update' command We're not going to expose the ability to swap volumes since that's a things humans should not generally use. From the API docs [1]: When updating volumeId, this API is typically meant to only be used as part of a larger orchestrated volume migration operation initiated in the block storage service via the os-retype or os-migrate_volume volume actions. Direct usage of this API to update volumeId is not recommended and may result in needing to hard reboot the server to update details within the guest such as block storage serial IDs. Furthermore, updating volumeId via this API is only implemented by certain compute drivers. We *do* want users to have the ability to change the delete on termination behavior though, so that's what we expose. [1] https://docs.openstack.org/api-ref/compute/?expanded=update-a-volume-attachment-detail#update-a-volume-attachment Change-Id: I50938e1237b4d298521b26a5f9cb90c018dfebaf Signed-off-by: Stephen Finucane --- lower-constraints.txt | 2 +- openstackclient/compute/v2/server.py | 16 +-- openstackclient/compute/v2/server_volume.py | 67 ++++++++++ .../unit/compute/v2/test_server_volume.py | 120 ++++++++++++++++++ ...server-volume-update-89740dca61596dd1.yaml | 3 + requirements.txt | 2 +- setup.cfg | 1 + 7 files changed, 201 insertions(+), 10 deletions(-) diff --git a/lower-constraints.txt b/lower-constraints.txt index 287f7d4599..25f71e45a2 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -71,7 +71,7 @@ python-cinderclient==3.3.0 python-dateutil==2.5.3 python-keystoneclient==3.22.0 python-mimeparse==1.6.0 -python-novaclient==15.1.0 +python-novaclient==17.0.0 python-subunit==1.0.0 pytz==2013.6 PyYAML==3.13 diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 50299d65d0..fa27f68c88 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -513,27 +513,27 @@ def get_parser(self, prog_name): '--tag', metavar='', help=_( - "Tag for the attached volume. " - "(Supported by API versions '2.49' - '2.latest')" + 'Tag for the attached volume ' + '(supported by --os-compute-api-version 2.49 or above)' ), ) + # TODO(stephenfin): These should be called 'delete-on-termination' and + # 'preserve-on-termination' termination_group = parser.add_mutually_exclusive_group() termination_group.add_argument( '--enable-delete-on-termination', action='store_true', help=_( - "Specify if the attached volume should be deleted when the " - "server is destroyed. " - "(Supported by API versions '2.79' - '2.latest')" + 'Delete the volume when the server is destroyed ' + '(supported by --os-compute-api-version 2.79 or above)' ), ) termination_group.add_argument( '--disable-delete-on-termination', action='store_true', help=_( - "Specify if the attached volume should not be deleted when " - "the server is destroyed. " - "(Supported by API versions '2.79' - '2.latest')" + 'Do not delete the volume when the server is destroyed ' + '(supported by --os-compute-api-version 2.79 or above)' ), ) return parser diff --git a/openstackclient/compute/v2/server_volume.py b/openstackclient/compute/v2/server_volume.py index 8a931ae5ab..b53c92fe5e 100644 --- a/openstackclient/compute/v2/server_volume.py +++ b/openstackclient/compute/v2/server_volume.py @@ -16,6 +16,7 @@ from novaclient import api_versions from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils from openstackclient.i18n import _ @@ -71,3 +72,69 @@ def take_action(self, parsed_args): ) for s in volumes ), ) + + +class UpdateServerVolume(command.Command): + """Update a volume attachment on the server.""" + + def get_parser(self, prog_name): + parser = super(UpdateServerVolume, self).get_parser(prog_name) + parser.add_argument( + 'server', + help=_('Server to update volume for (name or ID)'), + ) + parser.add_argument( + 'volume', + help=_('Volume (ID)'), + ) + termination_group = parser.add_mutually_exclusive_group() + termination_group.add_argument( + '--delete-on-termination', + action='store_true', + dest='delete_on_termination', + default=None, + help=_( + 'Delete the volume when the server is destroyed ' + '(supported by --os-compute-api-version 2.85 or above)' + ), + ) + termination_group.add_argument( + '--preserve-on-termination', + action='store_false', + dest='delete_on_termination', + help=_( + 'Preserve the volume when the server is destroyed ' + '(supported by --os-compute-api-version 2.85 or above)' + ), + ) + return parser + + def take_action(self, parsed_args): + + compute_client = self.app.client_manager.compute + + if parsed_args.delete_on_termination is not None: + if compute_client.api_version < api_versions.APIVersion('2.85'): + msg = _( + '--os-compute-api-version 2.85 or greater is required to ' + 'support the --(no-)delete-on-termination option' + ) + raise exceptions.CommandError(msg) + + server = utils.find_resource( + compute_client.servers, + parsed_args.server, + ) + + # NOTE(stephenfin): This may look silly, and that's because it is. + # This API was originally used only for the swapping volumes, which + # is an internal operation that should only be done by + # orchestration software rather than a human. We're not going to + # expose that, but we are going to expose the ability to change the + # delete on termination behavior. + compute_client.volumes.update_server_volume( + server.id, + parsed_args.volume, + parsed_args.volume, + delete_on_termination=parsed_args.delete_on_termination, + ) diff --git a/openstackclient/tests/unit/compute/v2/test_server_volume.py b/openstackclient/tests/unit/compute/v2/test_server_volume.py index d09c287450..4d4916b7ec 100644 --- a/openstackclient/tests/unit/compute/v2/test_server_volume.py +++ b/openstackclient/tests/unit/compute/v2/test_server_volume.py @@ -12,6 +12,7 @@ # from novaclient import api_versions +from osc_lib import exceptions from openstackclient.compute.v2 import server_volume from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes @@ -165,3 +166,122 @@ def test_server_volume_list_with_delete_on_attachment(self): ) self.servers_volumes_mock.get_server_volumes.assert_called_once_with( self.server.id) + + +class TestServerVolumeUpdate(TestServerVolume): + + def setUp(self): + super().setUp() + + self.server = compute_fakes.FakeServer.create_one_server() + self.servers_mock.get.return_value = self.server + + # Get the command object to test + self.cmd = server_volume.UpdateServerVolume(self.app, None) + + def test_server_volume_update(self): + + arglist = [ + self.server.id, + 'foo', + ] + verifylist = [ + ('server', self.server.id), + ('volume', 'foo'), + ('delete_on_termination', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # This is a no-op + self.servers_volumes_mock.update_server_volume.assert_not_called() + self.assertIsNone(result) + + def test_server_volume_update_with_delete_on_termination(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.85') + + arglist = [ + self.server.id, + 'foo', + '--delete-on-termination', + ] + verifylist = [ + ('server', self.server.id), + ('volume', 'foo'), + ('delete_on_termination', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.servers_volumes_mock.update_server_volume.assert_called_once_with( + self.server.id, 'foo', 'foo', + delete_on_termination=True) + self.assertIsNone(result) + + def test_server_volume_update_with_preserve_on_termination(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.85') + + arglist = [ + self.server.id, + 'foo', + '--preserve-on-termination', + ] + verifylist = [ + ('server', self.server.id), + ('volume', 'foo'), + ('delete_on_termination', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.servers_volumes_mock.update_server_volume.assert_called_once_with( + self.server.id, 'foo', 'foo', + delete_on_termination=False) + self.assertIsNone(result) + + def test_server_volume_update_with_delete_on_termination_pre_v285(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.84') + + arglist = [ + self.server.id, + 'foo', + '--delete-on-termination', + ] + verifylist = [ + ('server', self.server.id), + ('volume', 'foo'), + ('delete_on_termination', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + def test_server_volume_update_with_preserve_on_termination_pre_v285(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.84') + + arglist = [ + self.server.id, + 'foo', + '--preserve-on-termination', + ] + verifylist = [ + ('server', self.server.id), + ('volume', 'foo'), + ('delete_on_termination', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) diff --git a/releasenotes/notes/add-server-volume-update-89740dca61596dd1.yaml b/releasenotes/notes/add-server-volume-update-89740dca61596dd1.yaml index d1c0501ff8..b8a3de1a4b 100644 --- a/releasenotes/notes/add-server-volume-update-89740dca61596dd1.yaml +++ b/releasenotes/notes/add-server-volume-update-89740dca61596dd1.yaml @@ -3,3 +3,6 @@ features: - | Add ``server volume list`` command, to list the volumes attached to an instance. + - | + Add ``server volume update`` command, to update the volumes attached to + an instance. diff --git a/requirements.txt b/requirements.txt index ed55ce3794..21e291013b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,6 +9,6 @@ openstacksdk>=0.52.0 # Apache-2.0 osc-lib>=2.3.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 python-keystoneclient>=3.22.0 # Apache-2.0 -python-novaclient>=15.1.0 # Apache-2.0 +python-novaclient>=17.0.0 # Apache-2.0 python-cinderclient>=3.3.0 # Apache-2.0 stevedore>=2.0.1 # Apache-2.0 diff --git a/setup.cfg b/setup.cfg index 3fe7fa38f9..ef6db57879 100644 --- a/setup.cfg +++ b/setup.cfg @@ -154,6 +154,7 @@ openstack.compute.v2 = server_image_create = openstackclient.compute.v2.server_image:CreateServerImage server_volume_list = openstackclient.compute.v2.server_volume:ListServerVolume + server_volume_update = openstackclient.compute.v2.server_volume:UpdateServerVolume usage_list = openstackclient.compute.v2.usage:ListUsage usage_show = openstackclient.compute.v2.usage:ShowUsage From e01e59caeb56cb7bf4f38403b82d0a265b163098 Mon Sep 17 00:00:00 2001 From: Hang Yang Date: Wed, 14 Oct 2020 11:35:22 -0500 Subject: [PATCH 2326/3095] Support remote-address-group in SG rules Add support for using remote-address-group in security group rules. Change-Id: Ib1972244d484839943bc3cda07519a6c6d4b945a Implements: blueprint address-groups-in-sg-rules Depends-On: https://review.opendev.org/755644 --- .../network/v2/security_group_rule.py | 14 ++++++ .../tests/unit/network/v2/fakes.py | 1 + .../v2/test_security_group_rule_network.py | 43 +++++++++++++++++++ ...s-groups-in-sg-rules-b3b7742e4e6f5745.yaml | 9 ++++ 4 files changed, 67 insertions(+) create mode 100644 releasenotes/notes/bp-address-groups-in-sg-rules-b3b7742e4e6f5745.yaml diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index a770393313..17241ed256 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -126,6 +126,12 @@ def update_parser_common(self, parser): metavar="", help=_("Remote security group (name or ID)"), ) + if self.is_neutron: + remote_group.add_argument( + "--remote-address-group", + metavar="", + help=_("Remote address group (name or ID)"), + ) # NOTE(efried): The --dst-port, --protocol, and --proto options exist # for both nova-network and neutron, but differ slightly. For the sake @@ -328,6 +334,11 @@ def take_action_network(self, client, parsed_args): parsed_args.remote_group, ignore_missing=False ).id + elif parsed_args.remote_address_group is not None: + attrs['remote_address_group_id'] = client.find_address_group( + parsed_args.remote_address_group, + ignore_missing=False + ).id elif parsed_args.remote_ip is not None: attrs['remote_ip_prefix'] = parsed_args.remote_ip elif attrs['ethertype'] == 'IPv4': @@ -507,6 +518,8 @@ def _get_column_headers(self, parsed_args): 'Direction', 'Remote Security Group', ) + if self.is_neutron: + column_headers = column_headers + ('Remote Address Group',) if parsed_args.group is None: column_headers = column_headers + ('Security Group',) return column_headers @@ -526,6 +539,7 @@ def take_action_network(self, client, parsed_args): 'port_range', 'direction', 'remote_group_id', + 'remote_address_group_id', ) # Get the security group rules using the requested query. diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index 798cfd967b..d690669038 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -1382,6 +1382,7 @@ def create_one_security_group_rule(attrs=None): 'port_range_min': None, 'protocol': None, 'remote_group_id': None, + 'remote_address_group_id': None, 'remote_ip_prefix': '0.0.0.0/0', 'security_group_id': 'security-group-id-' + uuid.uuid4().hex, 'tenant_id': 'project-id-' + uuid.uuid4().hex, diff --git a/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py b/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py index 0141161134..bcdb0c2618 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py @@ -46,6 +46,9 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): _security_group = \ network_fakes.FakeSecurityGroup.create_one_security_group() + # The address group to be used in security group rules + _address_group = network_fakes.FakeAddressGroup.create_one_address_group() + expected_columns = ( 'description', 'direction', @@ -55,6 +58,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): 'port_range_min', 'project_id', 'protocol', + 'remote_address_group_id', 'remote_group_id', 'remote_ip_prefix', 'security_group_id', @@ -77,6 +81,7 @@ def _setup_security_group_rule(self, attrs=None): self._security_group_rule.port_range_min, self._security_group_rule.project_id, self._security_group_rule.protocol, + self._security_group_rule.remote_address_group_id, self._security_group_rule.remote_group_id, self._security_group_rule.remote_ip_prefix, self._security_group_rule.security_group_id, @@ -88,6 +93,9 @@ def setUp(self): self.network.find_security_group = mock.Mock( return_value=self._security_group) + self.network.find_address_group = mock.Mock( + return_value=self._address_group) + self.projects_mock.get.return_value = self.project self.domains_mock.get.return_value = self.domain @@ -103,6 +111,7 @@ def test_create_all_remote_options(self): arglist = [ '--remote-ip', '10.10.0.0/24', '--remote-group', self._security_group.id, + '--remote-address-group', self._address_group.id, self._security_group.id, ] self.assertRaises(tests_utils.ParserException, @@ -258,6 +267,34 @@ def test_create_protocol_any(self): self.assertEqual(self.expected_columns, columns) self.assertEqual(self.expected_data, data) + def test_create_remote_address_group(self): + self._setup_security_group_rule({ + 'protocol': 'icmp', + 'remote_address_group_id': self._address_group.id, + }) + arglist = [ + '--protocol', 'icmp', + '--remote-address-group', self._address_group.name, + self._security_group.id, + ] + verifylist = [ + ('remote_address_group', self._address_group.name), + ('group', self._security_group.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_security_group_rule.assert_called_once_with(**{ + 'direction': self._security_group_rule.direction, + 'ethertype': self._security_group_rule.ether_type, + 'protocol': self._security_group_rule.protocol, + 'remote_address_group_id': self._address_group.id, + 'security_group_id': self._security_group.id, + }) + self.assertEqual(self.expected_columns, columns) + self.assertEqual(self.expected_data, data) + def test_create_remote_group(self): self._setup_security_group_rule({ 'protocol': 'tcp', @@ -878,6 +915,7 @@ class TestListSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): 'Port Range', 'Direction', 'Remote Security Group', + 'Remote Address Group', ) expected_columns_no_group = ( 'ID', @@ -887,6 +925,7 @@ class TestListSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): 'Port Range', 'Direction', 'Remote Security Group', + 'Remote Address Group', 'Security Group', ) @@ -902,6 +941,7 @@ class TestListSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): _security_group_rule), _security_group_rule.direction, _security_group_rule.remote_group_id, + _security_group_rule.remote_address_group_id, )) expected_data_no_group.append(( _security_group_rule.id, @@ -912,6 +952,7 @@ class TestListSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): _security_group_rule), _security_group_rule.direction, _security_group_rule.remote_group_id, + _security_group_rule.remote_address_group_id, _security_group_rule.security_group_id, )) @@ -1041,6 +1082,7 @@ class TestShowSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): 'port_range_min', 'project_id', 'protocol', + 'remote_address_group_id', 'remote_group_id', 'remote_ip_prefix', 'security_group_id', @@ -1055,6 +1097,7 @@ class TestShowSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): _security_group_rule.port_range_min, _security_group_rule.project_id, _security_group_rule.protocol, + _security_group_rule.remote_address_group_id, _security_group_rule.remote_group_id, _security_group_rule.remote_ip_prefix, _security_group_rule.security_group_id, diff --git a/releasenotes/notes/bp-address-groups-in-sg-rules-b3b7742e4e6f5745.yaml b/releasenotes/notes/bp-address-groups-in-sg-rules-b3b7742e4e6f5745.yaml new file mode 100644 index 0000000000..a962eadf6f --- /dev/null +++ b/releasenotes/notes/bp-address-groups-in-sg-rules-b3b7742e4e6f5745.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + Add ``--remote-address-group`` option to ``security group rule create`` + command for using an address group as the source/destination in security + group rules. Also add field ``remote_address_group_id`` to the output of + ``security group rule show`` and add column ``Remote Address Group`` to + the output of ``security group rule list``. + [Blueprint `address-groups-in-sg-rules `_] From 262e525aada8bfaedb4545be5d2bbd27edcc55fd Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 3 Dec 2020 13:07:03 +0000 Subject: [PATCH 2327/3095] compute: Add missing options for 'hypervisor list' Yet more pagination parameters. Change-Id: I9f0145c89ddc49c1d907e6e6e294319cf80fc6ff Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/hypervisor.py | 63 ++++++++++-- .../tests/unit/compute/v2/test_hypervisor.py | 96 +++++++++++++++++++ ...hypervisor-list-opts-71da2cc36eac4edd.yaml | 5 + 3 files changed, 157 insertions(+), 7 deletions(-) create mode 100644 releasenotes/notes/add-missing-hypervisor-list-opts-71da2cc36eac4edd.yaml diff --git a/openstackclient/compute/v2/hypervisor.py b/openstackclient/compute/v2/hypervisor.py index 8fdb66983c..5f7497b5b1 100644 --- a/openstackclient/compute/v2/hypervisor.py +++ b/openstackclient/compute/v2/hypervisor.py @@ -22,6 +22,7 @@ from novaclient import exceptions as nova_exceptions from osc_lib.cli import format_columns from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils from openstackclient.i18n import _ @@ -33,10 +34,31 @@ class ListHypervisor(command.Lister): def get_parser(self, prog_name): parser = super(ListHypervisor, self).get_parser(prog_name) parser.add_argument( - "--matching", - metavar="", + '--matching', + metavar='', help=_("Filter hypervisors using substring") ) + parser.add_argument( + '--marker', + metavar='', + help=_( + "The UUID of the last hypervisor of the previous page; " + "displays list of hypervisors after 'marker'. " + "(supported with --os-compute-api-version 2.33 or above)" + ), + ) + parser.add_argument( + '--limit', + metavar='', + type=int, + help=_( + "Maximum number of hypervisors to display. Note that there " + "is a configurable max limit on the server, and the limit " + "that is used will be the minimum of what is requested " + "here and what is configured in the server. " + "(supported with --os-compute-api-version 2.33 or above)" + ), + ) parser.add_argument( '--long', action='store_true', @@ -46,6 +68,33 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): compute_client = self.app.client_manager.compute + + list_opts = {} + + if parsed_args.matching and (parsed_args.marker or parsed_args.limit): + msg = _( + '--matching is not compatible with --marker or --limit' + ) + raise exceptions.CommandError(msg) + + if parsed_args.marker: + if compute_client.api_version < api_versions.APIVersion('2.33'): + msg = _( + '--os-compute-api-version 2.33 or greater is required to ' + 'support the --marker option' + ) + raise exceptions.CommandError(msg) + list_opts['marker'] = parsed_args.marker + + if parsed_args.limit: + if compute_client.api_version < api_versions.APIVersion('2.33'): + msg = _( + '--os-compute-api-version 2.33 or greater is required to ' + 'support the --limit option' + ) + raise exceptions.CommandError(msg) + list_opts['limit'] = parsed_args.limit + columns = ( "ID", "Hypervisor Hostname", @@ -59,12 +108,12 @@ def take_action(self, parsed_args): if parsed_args.matching: data = compute_client.hypervisors.search(parsed_args.matching) else: - data = compute_client.hypervisors.list() + data = compute_client.hypervisors.list(**list_opts) - return (columns, - (utils.get_item_properties( - s, columns, - ) for s in data)) + return ( + columns, + (utils.get_item_properties(s, columns) for s in data), + ) class ShowHypervisor(command.ShowOne): diff --git a/openstackclient/tests/unit/compute/v2/test_hypervisor.py b/openstackclient/tests/unit/compute/v2/test_hypervisor.py index 518109c6b0..3220a76450 100644 --- a/openstackclient/tests/unit/compute/v2/test_hypervisor.py +++ b/openstackclient/tests/unit/compute/v2/test_hypervisor.py @@ -173,6 +173,30 @@ def test_hypervisor_list_matching_option_not_found(self): self.cmd.take_action, parsed_args) + def test_hypervisor_list_with_matching_and_pagination_options(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.32') + + arglist = [ + '--matching', self.hypervisors[0].hypervisor_hostname, + '--limit', '1', + '--marker', self.hypervisors[0].hypervisor_hostname, + ] + verifylist = [ + ('matching', self.hypervisors[0].hypervisor_hostname), + ('limit', 1), + ('marker', self.hypervisors[0].hypervisor_hostname), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + self.assertIn( + '--matching is not compatible with --marker or --limit', str(ex)) + def test_hypervisor_list_long_option(self): arglist = [ '--long', @@ -191,6 +215,78 @@ def test_hypervisor_list_long_option(self): self.assertEqual(self.columns_long, columns) self.assertEqual(self.data_long, tuple(data)) + def test_hypervisor_list_with_limit(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.33') + + arglist = [ + '--limit', '1', + ] + verifylist = [ + ('limit', 1), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.hypervisors_mock.list.assert_called_with(limit=1) + + def test_hypervisor_list_with_limit_pre_v233(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.32') + + arglist = [ + '--limit', '1', + ] + verifylist = [ + ('limit', 1), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + self.assertIn( + '--os-compute-api-version 2.33 or greater is required', str(ex)) + + def test_hypervisor_list_with_marker(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.33') + + arglist = [ + '--marker', 'test_hyp', + ] + verifylist = [ + ('marker', 'test_hyp'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.hypervisors_mock.list.assert_called_with(marker='test_hyp') + + def test_hypervisor_list_with_marker_pre_v233(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.32') + + arglist = [ + '--marker', 'test_hyp', + ] + verifylist = [ + ('marker', 'test_hyp'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + self.assertIn( + '--os-compute-api-version 2.33 or greater is required', str(ex)) + class TestHypervisorShow(TestHypervisor): diff --git a/releasenotes/notes/add-missing-hypervisor-list-opts-71da2cc36eac4edd.yaml b/releasenotes/notes/add-missing-hypervisor-list-opts-71da2cc36eac4edd.yaml new file mode 100644 index 0000000000..c5401210a4 --- /dev/null +++ b/releasenotes/notes/add-missing-hypervisor-list-opts-71da2cc36eac4edd.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``--limit`` and ``--marker`` options to ``hypervisor list`` command, to + configure pagination of results. From 8a164bb09c0801c3ffd2431d41c3e232388ab407 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 3 Dec 2020 13:13:16 +0000 Subject: [PATCH 2328/3095] compute: Add '--force' option to 'server delete' This is an admin-only operation by default but can be useful. Change-Id: I25a4da697e27c0fba4d28b504377667eb18f15fe Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server.py | 12 ++++++++++- .../tests/unit/compute/v2/test_server.py | 21 +++++++++++++++++++ ...g-server-delete-opts-071c3e054e3ce674.yaml | 5 +++++ 3 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/add-missing-server-delete-opts-071c3e054e3ce674.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index fa27f68c88..aa4f237208 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1317,6 +1317,11 @@ def get_parser(self, prog_name): nargs="+", help=_('Server(s) to delete (name or ID)'), ) + parser.add_argument( + '--force', + action='store_true', + help=_('Force delete server(s)'), + ) parser.add_argument( '--wait', action='store_true', @@ -1335,7 +1340,12 @@ def _show_progress(progress): for server in parsed_args.server: server_obj = utils.find_resource( compute_client.servers, server) - compute_client.servers.delete(server_obj.id) + + if parsed_args.force: + compute_client.servers.force_delete(server_obj.id) + else: + compute_client.servers.delete(server_obj.id) + if parsed_args.wait: if not utils.wait_for_delete(compute_client.servers, server_obj.id, diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 0f33dd7047..bde0699b06 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -2850,6 +2850,7 @@ def setUp(self): super(TestServerDelete, self).setUp() self.servers_mock.delete.return_value = None + self.servers_mock.force_delete.return_value = None # Get the command object to test self.cmd = server.DeleteServer(self.app, None) @@ -2868,6 +2869,26 @@ def test_server_delete_no_options(self): result = self.cmd.take_action(parsed_args) self.servers_mock.delete.assert_called_with(servers[0].id) + self.servers_mock.force_delete.assert_not_called() + self.assertIsNone(result) + + def test_server_delete_with_force(self): + servers = self.setup_servers_mock(count=1) + + arglist = [ + servers[0].id, + '--force', + ] + verifylist = [ + ('server', [servers[0].id]), + ('force', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.servers_mock.force_delete.assert_called_with(servers[0].id) + self.servers_mock.delete.assert_not_called() self.assertIsNone(result) def test_server_delete_multi_servers(self): diff --git a/releasenotes/notes/add-missing-server-delete-opts-071c3e054e3ce674.yaml b/releasenotes/notes/add-missing-server-delete-opts-071c3e054e3ce674.yaml new file mode 100644 index 0000000000..2c1fc46ed2 --- /dev/null +++ b/releasenotes/notes/add-missing-server-delete-opts-071c3e054e3ce674.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``--force`` option to ``server delete`` command, allowing users to + force delete a server. This is admin-only by default. From dfa869ed1dbd54c76a9d7cd7d520f0d21064918d Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 3 Dec 2020 17:36:53 +0000 Subject: [PATCH 2329/3095] compute: Improve 'server migration list' options Improve both the '--user' and '--project' options to allow names as well as UUIDs. There's no release note included since this entire command was added in change I15b4a5aca8d0dee59dd293e7b1c7272cdfbeea20, which hasn't been included in a release yet. Change-Id: I7654f3ffc54d38d5cfb03d8d1b2f4dc4fb06fb3d Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server.py | 31 +++++++---- .../tests/unit/compute/v2/test_server.py | 51 ++++++++++++------- 2 files changed, 53 insertions(+), 29 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index aa4f237208..1e38869e76 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -2369,21 +2369,21 @@ def get_parser(self, prog_name): parser.add_argument( '--project', metavar='', - dest='project_id', help=_( - "Filter migrations by project (ID) " + "Filter migrations by project (name or ID) " "(supported with --os-compute-api-version 2.80 or above)" ), ) + identity_common.add_project_domain_option_to_parser(parser) parser.add_argument( '--user', metavar='', - dest='user_id', help=_( - "Filter migrations by user (ID) " + "Filter migrations by user (name or ID) " "(supported with --os-compute-api-version 2.80 or above)" ), ) + identity_common.add_user_domain_option_to_parser(parser) return parser def print_migrations(self, parsed_args, compute_client, migrations): @@ -2402,9 +2402,9 @@ def print_migrations(self, parsed_args, compute_client, migrations): columns.insert(len(columns) - 2, "Type") if compute_client.api_version >= api_versions.APIVersion("2.80"): - if parsed_args.project_id: + if parsed_args.project: columns.insert(len(columns) - 2, "Project") - if parsed_args.user_id: + if parsed_args.user: columns.insert(len(columns) - 2, "User") return ( @@ -2414,6 +2414,7 @@ def print_migrations(self, parsed_args, compute_client, migrations): def take_action(self, parsed_args): compute_client = self.app.client_manager.compute + identity_client = self.app.client_manager.identity search_opts = { 'host': parsed_args.host, @@ -2469,23 +2470,33 @@ def take_action(self, parsed_args): raise exceptions.CommandError(msg) search_opts['changes_before'] = parsed_args.changes_before - if parsed_args.project_id: + if parsed_args.project: if compute_client.api_version < api_versions.APIVersion('2.80'): msg = _( '--os-compute-api-version 2.80 or greater is required to ' 'support the --project option' ) raise exceptions.CommandError(msg) - search_opts['project_id'] = parsed_args.project_id - if parsed_args.user_id: + search_opts['project_id'] = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + + if parsed_args.user: if compute_client.api_version < api_versions.APIVersion('2.80'): msg = _( '--os-compute-api-version 2.80 or greater is required to ' 'support the --user option' ) raise exceptions.CommandError(msg) - search_opts['user_id'] = parsed_args.user_id + + search_opts['user_id'] = identity_common.find_user( + identity_client, + parsed_args.user, + parsed_args.user_domain, + ).id migrations = compute_client.migrations.list(**search_opts) diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index bde0699b06..ea33463ce8 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -28,6 +28,7 @@ from openstackclient.compute.v2 import server from openstackclient.tests.unit.compute.v2 import fakes as compute_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.network.v2 import fakes as network_fakes from openstackclient.tests.unit import utils @@ -4548,9 +4549,21 @@ class TestListMigrationV280(TestListMigration): 'Old Flavor', 'New Flavor', 'Type', 'Created At', 'Updated At' ] + project = identity_fakes.FakeProject.create_one_project() + user = identity_fakes.FakeUser.create_one_user() + def setUp(self): super(TestListMigrationV280, self).setUp() + self.projects_mock = self.app.client_manager.identity.projects + self.projects_mock.reset_mock() + + self.users_mock = self.app.client_manager.identity.users + self.users_mock.reset_mock() + + self.projects_mock.get.return_value = self.project + self.users_mock.get.return_value = self.user + self.app.client_manager.compute.api_version = api_versions.APIVersion( '2.80') @@ -4561,7 +4574,7 @@ def test_server_migration_list_with_project(self): '--marker', 'test_kp', '--changes-since', '2019-08-07T08:03:25Z', '--changes-before', '2019-08-09T08:03:25Z', - '--project', '0c2accde-644a-45fa-8c10-e76debc7fbc3' + '--project', self.project.id ] verifylist = [ ('status', 'migrating'), @@ -4569,7 +4582,7 @@ def test_server_migration_list_with_project(self): ('marker', 'test_kp'), ('changes_since', '2019-08-07T08:03:25Z'), ('changes_before', '2019-08-09T08:03:25Z'), - ('project_id', '0c2accde-644a-45fa-8c10-e76debc7fbc3') + ('project', self.project.id) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) @@ -4580,7 +4593,7 @@ def test_server_migration_list_with_project(self): 'limit': 1, 'marker': 'test_kp', 'host': None, - 'project_id': '0c2accde-644a-45fa-8c10-e76debc7fbc3', + 'project_id': self.project.id, 'changes_since': '2019-08-07T08:03:25Z', 'changes_before': "2019-08-09T08:03:25Z", } @@ -4605,7 +4618,7 @@ def test_get_migrations_with_project_pre_v280(self): verifylist = [ ('status', 'migrating'), ('changes_before', '2019-08-09T08:03:25Z'), - ('project_id', '0c2accde-644a-45fa-8c10-e76debc7fbc3') + ('project', '0c2accde-644a-45fa-8c10-e76debc7fbc3') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) ex = self.assertRaises( @@ -4623,7 +4636,7 @@ def test_server_migration_list_with_user(self): '--marker', 'test_kp', '--changes-since', '2019-08-07T08:03:25Z', '--changes-before', '2019-08-09T08:03:25Z', - '--user', 'dd214878-ca12-40fb-b035-fa7d2c1e86d6' + '--user', self.user.id, ] verifylist = [ ('status', 'migrating'), @@ -4631,7 +4644,7 @@ def test_server_migration_list_with_user(self): ('marker', 'test_kp'), ('changes_since', '2019-08-07T08:03:25Z'), ('changes_before', '2019-08-09T08:03:25Z'), - ('user_id', 'dd214878-ca12-40fb-b035-fa7d2c1e86d6') + ('user', self.user.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) @@ -4642,7 +4655,7 @@ def test_server_migration_list_with_user(self): 'limit': 1, 'marker': 'test_kp', 'host': None, - 'user_id': 'dd214878-ca12-40fb-b035-fa7d2c1e86d6', + 'user_id': self.user.id, 'changes_since': '2019-08-07T08:03:25Z', 'changes_before': "2019-08-09T08:03:25Z", } @@ -4662,12 +4675,12 @@ def test_get_migrations_with_user_pre_v280(self): arglist = [ '--status', 'migrating', '--changes-before', '2019-08-09T08:03:25Z', - '--user', 'dd214878-ca12-40fb-b035-fa7d2c1e86d6' + '--user', self.user.id, ] verifylist = [ ('status', 'migrating'), ('changes_before', '2019-08-09T08:03:25Z'), - ('user_id', 'dd214878-ca12-40fb-b035-fa7d2c1e86d6') + ('user', self.user.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) ex = self.assertRaises( @@ -4684,16 +4697,16 @@ def test_server_migration_list_with_project_and_user(self): '--limit', '1', '--changes-since', '2019-08-07T08:03:25Z', '--changes-before', '2019-08-09T08:03:25Z', - '--project', '0c2accde-644a-45fa-8c10-e76debc7fbc3', - '--user', 'dd214878-ca12-40fb-b035-fa7d2c1e86d6' + '--project', self.project.id, + '--user', self.user.id, ] verifylist = [ ('status', 'migrating'), ('limit', 1), ('changes_since', '2019-08-07T08:03:25Z'), ('changes_before', '2019-08-09T08:03:25Z'), - ('project_id', '0c2accde-644a-45fa-8c10-e76debc7fbc3'), - ('user_id', 'dd214878-ca12-40fb-b035-fa7d2c1e86d6') + ('project', self.project.id), + ('user', self.user.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) @@ -4703,8 +4716,8 @@ def test_server_migration_list_with_project_and_user(self): 'status': 'migrating', 'limit': 1, 'host': None, - 'project_id': '0c2accde-644a-45fa-8c10-e76debc7fbc3', - 'user_id': 'dd214878-ca12-40fb-b035-fa7d2c1e86d6', + 'project_id': self.project.id, + 'user_id': self.user.id, 'changes_since': '2019-08-07T08:03:25Z', 'changes_before': "2019-08-09T08:03:25Z", } @@ -4727,14 +4740,14 @@ def test_get_migrations_with_project_and_user_pre_v280(self): arglist = [ '--status', 'migrating', '--changes-before', '2019-08-09T08:03:25Z', - '--project', '0c2accde-644a-45fa-8c10-e76debc7fbc3', - '--user', 'dd214878-ca12-40fb-b035-fa7d2c1e86d6' + '--project', self.project.id, + '--user', self.user.id, ] verifylist = [ ('status', 'migrating'), ('changes_before', '2019-08-09T08:03:25Z'), - ('project_id', '0c2accde-644a-45fa-8c10-e76debc7fbc3'), - ('user_id', 'dd214878-ca12-40fb-b035-fa7d2c1e86d6') + ('project', self.project.id), + ('user', self.user.id) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) ex = self.assertRaises( From 0cc878e5b053765a0d3c13f5588bc160b05a388b Mon Sep 17 00:00:00 2001 From: Rodolfo Alonso Hernandez Date: Thu, 17 Dec 2020 17:08:22 +0000 Subject: [PATCH 2330/3095] Add device profile to ``port`` Added device profile parameter to ``port create`` command. Related-Bug: #1906602 Change-Id: I4c222ac334d3a0a0ee568ed1e0bc8518baa375e1 --- lower-constraints.txt | 2 +- openstackclient/network/v2/port.py | 8 +++++ .../tests/unit/network/v2/fakes.py | 1 + .../tests/unit/network/v2/test_port.py | 29 +++++++++++++++++++ .../port-device-profile-4a3bf800da21c778.yaml | 4 +++ requirements.txt | 2 +- 6 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/port-device-profile-4a3bf800da21c778.yaml diff --git a/lower-constraints.txt b/lower-constraints.txt index 25f71e45a2..f93db8aa8c 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -38,7 +38,7 @@ msgpack-python==0.4.0 munch==2.1.0 netaddr==0.7.18 netifaces==0.10.4 -openstacksdk==0.52.0 +openstacksdk==0.53.0 os-client-config==2.1.0 os-service-types==1.7.0 os-testr==1.0.0 diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index cb77759efd..dfdb604d55 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -168,6 +168,9 @@ def _get_attrs(client_manager, parsed_args): parsed_args.numa_policy_legacy): attrs['numa_affinity_policy'] = 'legacy' + if 'device_profile' in parsed_args and parsed_args.device_profile: + attrs['device_profile'] = parsed_args.device_profile + return attrs @@ -443,6 +446,11 @@ def get_parser(self, prog_name): "ip-address=[,mac-address=] " "(repeat option to set multiple allowed-address pairs)") ) + parser.add_argument( + '--device-profile', + metavar='', + help=_('Cyborg port device profile') + ) _tag.add_tag_option_to_parser_for_create(parser, _('port')) return parser diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index 798cfd967b..32e09ba845 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -697,6 +697,7 @@ def create_one_port(attrs=None): 'description': 'description-' + uuid.uuid4().hex, 'device_id': 'device-id-' + uuid.uuid4().hex, 'device_owner': 'compute:nova', + 'device_profile': 'cyborg_device_profile_1', 'dns_assignment': [{}], 'dns_domain': 'dns-domain-' + uuid.uuid4().hex, 'dns_name': 'dns-name-' + uuid.uuid4().hex, diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index e21f9d01f1..c8bced71c3 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -54,6 +54,7 @@ def _get_common_cols_data(fake_port): 'description', 'device_id', 'device_owner', + 'device_profile', 'dns_assignment', 'dns_domain', 'dns_name', @@ -86,6 +87,7 @@ def _get_common_cols_data(fake_port): fake_port.description, fake_port.device_id, fake_port.device_owner, + fake_port.device_profile, format_columns.ListDictColumn(fake_port.dns_assignment), fake_port.dns_domain, fake_port.dns_name, @@ -737,6 +739,33 @@ def test_create_with_numa_affinity_policy_legacy(self): def test_create_with_numa_affinity_policy_null(self): self._test_create_with_numa_affinity_policy() + def test_create_with_device_profile(self): + arglist = [ + '--network', self._port.network_id, + '--device-profile', 'cyborg_device_profile_1', + 'test-port', + ] + + verifylist = [ + ('network', self._port.network_id,), + ('device_profile', self._port.device_profile,), + ('name', 'test-port'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = (self.cmd.take_action(parsed_args)) + + create_args = { + 'admin_state_up': True, + 'network_id': self._port.network_id, + 'name': 'test-port', + 'device_profile': 'cyborg_device_profile_1', + } + self.network.create_port.assert_called_once_with(**create_args) + self.assertEqual(self.columns, columns) + self.assertItemsEqual(self.data, data) + class TestDeletePort(TestPort): diff --git a/releasenotes/notes/port-device-profile-4a3bf800da21c778.yaml b/releasenotes/notes/port-device-profile-4a3bf800da21c778.yaml new file mode 100644 index 0000000000..db0d495fd7 --- /dev/null +++ b/releasenotes/notes/port-device-profile-4a3bf800da21c778.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Add device profile to ``port create`` command. diff --git a/requirements.txt b/requirements.txt index 21e291013b..5077ad779b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ pbr!=2.1.0,>=2.0.0 # Apache-2.0 cliff>=3.5.0 # Apache-2.0 iso8601>=0.1.11 # MIT -openstacksdk>=0.52.0 # Apache-2.0 +openstacksdk>=0.53.0 # Apache-2.0 osc-lib>=2.3.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 python-keystoneclient>=3.22.0 # Apache-2.0 From 1a6df700be2507bcec760994e64042d03b09ae16 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Fri, 15 Jan 2021 17:06:20 +0000 Subject: [PATCH 2331/3095] compute: Add 'server * --all-projects' option Add an '--all-projects' option to a number of commands: - server delete - server start - server stop This is in addition to 'server list', which already supports this option. This option allows users to request the corresponding action on one or more servers using the server names when that server exists in another project. This is admin-only by default. As part of this work, we also introduce a 'boolenv' helper function that allows us to parse the environment variable as a boolean using 'bool_from_string' helper provided by oslo.utils. This could probably be clever and it has the unfortunate side effect of modifying the help text in environments where this is configured, but it's good enough for now. It also appears to add a new dependency, in the form of oslo.utils, but that dependency was already required by osc-lib and probably more. Change-Id: I4811f8f66dcb14ed99cc1cfb80b00e2d77afe45f Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server.py | 70 ++++++++++++++++--- .../tests/unit/compute/v2/test_server.py | 66 +++++++++++++++++ ...ver-ops-all-projects-2ce2202cdf617184.yaml | 7 ++ requirements.txt | 1 + 4 files changed, 136 insertions(+), 8 deletions(-) create mode 100644 releasenotes/notes/server-ops-all-projects-2ce2202cdf617184.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 02fc5816b8..15a5084d0d 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -31,6 +31,7 @@ from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from oslo_utils import strutils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common @@ -193,6 +194,24 @@ def _prep_server_detail(compute_client, image_client, server, refresh=True): return info +def boolenv(*vars, default=False): + """Search for the first defined of possibly many bool-like env vars. + + Returns the first environment variable defined in vars, or returns the + default. + + :param vars: Arbitrary strings to search for. Case sensitive. + :param default: The default to return if no value found. + :returns: A boolean corresponding to the value found, else the default if + no value found. + """ + for v in vars: + value = os.environ.get(v, None) + if value: + return strutils.bool_from_string(value) + return default + + class AddFixedIP(command.Command): _description = _("Add fixed IP address to server") @@ -1322,6 +1341,15 @@ def get_parser(self, prog_name): action='store_true', help=_('Force delete server(s)'), ) + parser.add_argument( + '--all-projects', + action='store_true', + default=boolenv('ALL_PROJECTS'), + help=_( + 'Delete server(s) in another project by name (admin only)' + '(can be specified using the ALL_PROJECTS envvar)' + ), + ) parser.add_argument( '--wait', action='store_true', @@ -1339,7 +1367,8 @@ def _show_progress(progress): compute_client = self.app.client_manager.compute for server in parsed_args.server: server_obj = utils.find_resource( - compute_client.servers, server) + compute_client.servers, server, + all_tenants=parsed_args.all_projects) if parsed_args.force: compute_client.servers.force_delete(server_obj.id) @@ -1347,11 +1376,13 @@ def _show_progress(progress): compute_client.servers.delete(server_obj.id) if parsed_args.wait: - if not utils.wait_for_delete(compute_client.servers, - server_obj.id, - callback=_show_progress): - LOG.error(_('Error deleting server: %s'), - server_obj.id) + if not utils.wait_for_delete( + compute_client.servers, + server_obj.id, + callback=_show_progress, + ): + msg = _('Error deleting server: %s') + LOG.error(msg, server_obj.id) self.app.stdout.write(_('Error deleting server\n')) raise SystemExit @@ -1446,8 +1477,11 @@ def get_parser(self, prog_name): parser.add_argument( '--all-projects', action='store_true', - default=bool(int(os.environ.get("ALL_PROJECTS", 0))), - help=_('Include all projects (admin only)'), + default=boolenv('ALL_PROJECTS'), + help=_( + 'Include all projects (admin only) ' + '(can be specified using the ALL_PROJECTS envvar)' + ), ) parser.add_argument( '--project', @@ -3939,6 +3973,15 @@ def get_parser(self, prog_name): nargs="+", help=_('Server(s) to start (name or ID)'), ) + parser.add_argument( + '--all-projects', + action='store_true', + default=boolenv('ALL_PROJECTS'), + help=_( + 'Start server(s) in another project by name (admin only)' + '(can be specified using the ALL_PROJECTS envvar)' + ), + ) return parser def take_action(self, parsed_args): @@ -3947,6 +3990,7 @@ def take_action(self, parsed_args): utils.find_resource( compute_client.servers, server, + all_tenants=parsed_args.all_projects, ).start() @@ -3961,6 +4005,15 @@ def get_parser(self, prog_name): nargs="+", help=_('Server(s) to stop (name or ID)'), ) + parser.add_argument( + '--all-projects', + action='store_true', + default=boolenv('ALL_PROJECTS'), + help=_( + 'Stop server(s) in another project by name (admin only)' + '(can be specified using the ALL_PROJECTS envvar)' + ), + ) return parser def take_action(self, parsed_args): @@ -3969,6 +4022,7 @@ def take_action(self, parsed_args): utils.find_resource( compute_client.servers, server, + all_tenants=parsed_args.all_projects, ).stop() diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 16885eb86a..9a01758c8c 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -2913,6 +2913,28 @@ def test_server_delete_multi_servers(self): self.servers_mock.delete.assert_has_calls(calls) self.assertIsNone(result) + @mock.patch.object(common_utils, 'find_resource') + def test_server_delete_with_all_projects(self, mock_find_resource): + servers = self.setup_servers_mock(count=1) + mock_find_resource.side_effect = compute_fakes.FakeServer.get_servers( + servers, 0, + ) + + arglist = [ + servers[0].id, + '--all-projects', + ] + verifylist = [ + ('server', [servers[0].id]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + mock_find_resource.assert_called_once_with( + mock.ANY, servers[0].id, all_tenants=True, + ) + @mock.patch.object(common_utils, 'wait_for_delete', return_value=True) def test_server_delete_wait_ok(self, mock_wait_for_delete): servers = self.setup_servers_mock(count=1) @@ -6781,6 +6803,28 @@ def test_server_start_one_server(self): def test_server_start_multi_servers(self): self.run_method_with_servers('start', 3) + @mock.patch.object(common_utils, 'find_resource') + def test_server_start_with_all_projects(self, mock_find_resource): + servers = self.setup_servers_mock(count=1) + mock_find_resource.side_effect = compute_fakes.FakeServer.get_servers( + servers, 0, + ) + + arglist = [ + servers[0].id, + '--all-projects', + ] + verifylist = [ + ('server', [servers[0].id]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + mock_find_resource.assert_called_once_with( + mock.ANY, servers[0].id, all_tenants=True, + ) + class TestServerStop(TestServer): @@ -6801,6 +6845,28 @@ def test_server_stop_one_server(self): def test_server_stop_multi_servers(self): self.run_method_with_servers('stop', 3) + @mock.patch.object(common_utils, 'find_resource') + def test_server_start_with_all_projects(self, mock_find_resource): + servers = self.setup_servers_mock(count=1) + mock_find_resource.side_effect = compute_fakes.FakeServer.get_servers( + servers, 0, + ) + + arglist = [ + servers[0].id, + '--all-projects', + ] + verifylist = [ + ('server', [servers[0].id]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + mock_find_resource.assert_called_once_with( + mock.ANY, servers[0].id, all_tenants=True, + ) + class TestServerSuspend(TestServer): diff --git a/releasenotes/notes/server-ops-all-projects-2ce2202cdf617184.yaml b/releasenotes/notes/server-ops-all-projects-2ce2202cdf617184.yaml new file mode 100644 index 0000000000..b14eb50414 --- /dev/null +++ b/releasenotes/notes/server-ops-all-projects-2ce2202cdf617184.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + The ``server delete``, ``server start`` and ``server stop`` commands now + support the ``--all-projects`` option. This allows you to perform the + specified action on a server in another project using the server name. + This is an admin-only action by default. diff --git a/requirements.txt b/requirements.txt index 21e291013b..ad0c50e50d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,6 +8,7 @@ iso8601>=0.1.11 # MIT openstacksdk>=0.52.0 # Apache-2.0 osc-lib>=2.3.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 +oslo.utils>=3.33.0 # Apache-2.0 python-keystoneclient>=3.22.0 # Apache-2.0 python-novaclient>=17.0.0 # Apache-2.0 python-cinderclient>=3.3.0 # Apache-2.0 From 5ec4d4c7188f4766d270be32e12b64b709d2b835 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 10 Dec 2020 11:29:01 +0000 Subject: [PATCH 2332/3095] compute: Add missing options for 'server group list' Add pagination parameters, '--limit' and '--offset'. It's unfortunate that we can't use '--marker' like elsewhere but that requires server-side support to be truly effective. Change-Id: I186adc8cdf28e9c540ad22bca6684d9dd892976a Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server_group.py | 38 ++++++++++++++- .../unit/compute/v2/test_server_group.py | 47 +++++++++++++++++-- ...rver-group-list-opts-d3c3d98b7f7a56a6.yaml | 5 ++ 3 files changed, 85 insertions(+), 5 deletions(-) create mode 100644 releasenotes/notes/add-missing-server-group-list-opts-d3c3d98b7f7a56a6.yaml diff --git a/openstackclient/compute/v2/server_group.py b/openstackclient/compute/v2/server_group.py index 783fdbfea3..32dd19370f 100644 --- a/openstackclient/compute/v2/server_group.py +++ b/openstackclient/compute/v2/server_group.py @@ -177,11 +177,47 @@ def get_parser(self, prog_name): default=False, help=_("List additional fields in output") ) + # TODO(stephenfin): This should really be a --marker option, but alas + # the API doesn't support that for some reason + parser.add_argument( + '--offset', + metavar='', + type=int, + default=None, + help=_( + 'Index from which to start listing servers. This should ' + 'typically be a factor of --limit. Display all servers groups ' + 'if not specified.' + ), + ) + parser.add_argument( + '--limit', + metavar='', + type=int, + default=None, + help=_( + "Maximum number of server groups to display. " + "If limit is greater than 'osapi_max_limit' option of Nova " + "API, 'osapi_max_limit' will be used instead." + ), + ) return parser def take_action(self, parsed_args): compute_client = self.app.client_manager.compute - data = compute_client.server_groups.list(parsed_args.all_projects) + + kwargs = {} + + if parsed_args.all_projects: + kwargs['all_projects'] = parsed_args.all_projects + + if parsed_args.offset: + kwargs['offset'] = parsed_args.offset + + if parsed_args.limit: + kwargs['limit'] = parsed_args.limit + + data = compute_client.server_groups.list(**kwargs) policy_key = 'Policies' if compute_client.api_version >= api_versions.APIVersion("2.64"): diff --git a/openstackclient/tests/unit/compute/v2/test_server_group.py b/openstackclient/tests/unit/compute/v2/test_server_group.py index 732c18810b..3ed19e27d5 100644 --- a/openstackclient/tests/unit/compute/v2/test_server_group.py +++ b/openstackclient/tests/unit/compute/v2/test_server_group.py @@ -326,10 +326,13 @@ def test_server_group_list(self): verifylist = [ ('all_projects', False), ('long', False), + ('limit', None), + ('offset', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.server_groups_mock.list.assert_called_once_with(False) + + self.server_groups_mock.list.assert_called_once_with() self.assertCountEqual(self.list_columns, columns) self.assertCountEqual(self.list_data, tuple(data)) @@ -342,14 +345,49 @@ def test_server_group_list_with_all_projects_and_long(self): verifylist = [ ('all_projects', True), ('long', True), + ('limit', None), + ('offset', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.server_groups_mock.list.assert_called_once_with(True) + self.server_groups_mock.list.assert_called_once_with( + all_projects=True) self.assertCountEqual(self.list_columns_long, columns) self.assertCountEqual(self.list_data_long, tuple(data)) + def test_server_group_list_with_limit(self): + arglist = [ + '--limit', '1', + ] + verifylist = [ + ('all_projects', False), + ('long', False), + ('limit', 1), + ('offset', None), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.server_groups_mock.list.assert_called_once_with(limit=1) + + def test_server_group_list_with_offset(self): + arglist = [ + '--offset', '5', + ] + verifylist = [ + ('all_projects', False), + ('long', False), + ('limit', None), + ('offset', 5), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.server_groups_mock.list.assert_called_once_with(offset=5) + class TestServerGroupListV264(TestServerGroupV264): @@ -400,7 +438,7 @@ def test_server_group_list(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.server_groups_mock.list.assert_called_once_with(False) + self.server_groups_mock.list.assert_called_once_with() self.assertCountEqual(self.list_columns, columns) self.assertCountEqual(self.list_data, tuple(data)) @@ -416,7 +454,8 @@ def test_server_group_list_with_all_projects_and_long(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.server_groups_mock.list.assert_called_once_with(True) + self.server_groups_mock.list.assert_called_once_with( + all_projects=True) self.assertCountEqual(self.list_columns_long, columns) self.assertCountEqual(self.list_data_long, tuple(data)) diff --git a/releasenotes/notes/add-missing-server-group-list-opts-d3c3d98b7f7a56a6.yaml b/releasenotes/notes/add-missing-server-group-list-opts-d3c3d98b7f7a56a6.yaml new file mode 100644 index 0000000000..b359e77ca2 --- /dev/null +++ b/releasenotes/notes/add-missing-server-group-list-opts-d3c3d98b7f7a56a6.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``--limit`` and ``--offset`` options to ``server group list`` command, + to configure pagination of results. From 7ed4f68c68cb17b02deced203f9a461605a68dfe Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 10 Dec 2020 11:57:20 +0000 Subject: [PATCH 2333/3095] compute: Add missing options for 'server event list' Add pagination parameters, '--limit' and '--offset', and filtering parameters, '--changes-since' and '--changes-before'. Change-Id: Ieca8267c3b204ae2db580502cc8fe72c95eddf09 Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server_event.py | 178 +++++++++---- .../unit/compute/v2/test_server_event.py | 235 +++++++++++++++++- ...rver-group-list-opts-ebcf84bfcea07a4b.yaml | 6 + 3 files changed, 375 insertions(+), 44 deletions(-) create mode 100644 releasenotes/notes/add-missing-server-group-list-opts-ebcf84bfcea07a4b.yaml diff --git a/openstackclient/compute/v2/server_event.py b/openstackclient/compute/v2/server_event.py index 4fcc913614..1b971e51df 100644 --- a/openstackclient/compute/v2/server_event.py +++ b/openstackclient/compute/v2/server_event.py @@ -17,7 +17,10 @@ import logging +import iso8601 +from novaclient import api_versions from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils from openstackclient.i18n import _ @@ -27,10 +30,11 @@ class ListServerEvent(command.Lister): - _description = _( - "List recent events of a server. " - "Specify ``--os-compute-api-version 2.21`` " - "or higher to show events for a deleted server.") + """List recent events of a server. + + Specify ``--os-compute-api-version 2.21`` or higher to show events for a + deleted server. + """ def get_parser(self, prog_name): parser = super(ListServerEvent, self).get_parser(prog_name) @@ -45,60 +49,144 @@ def get_parser(self, prog_name): default=False, help=_("List additional fields in output") ) + parser.add_argument( + '--changes-since', + dest='changes_since', + metavar='', + help=_( + "List only server events changed later or equal to a certain " + "point of time. The provided time should be an ISO 8061 " + "formatted time, e.g. ``2016-03-04T06:27:59Z``. " + "(supported with --os-compute-api-version 2.58 or above)" + ), + ) + parser.add_argument( + '--changes-before', + dest='changes_before', + metavar='', + help=_( + "List only server events changed earlier or equal to a " + "certain point of time. The provided time should be an ISO " + "8061 formatted time, e.g. ``2016-03-04T06:27:59Z``. " + "(supported with --os-compute-api-version 2.66 or above)" + ), + ) + parser.add_argument( + '--marker', + help=_( + 'The last server event ID of the previous page ' + '(supported by --os-compute-api-version 2.58 or above)' + ), + ) + parser.add_argument( + '--limit', + type=int, + help=_( + 'Maximum number of server events to display ' + '(supported by --os-compute-api-version 2.58 or above)' + ), + ) return parser def take_action(self, parsed_args): compute_client = self.app.client_manager.compute - server_id = utils.find_resource(compute_client.servers, - parsed_args.server).id - data = compute_client.instance_action.list(server_id) + + kwargs = {} + + if parsed_args.marker: + if compute_client.api_version < api_versions.APIVersion('2.58'): + msg = _( + '--os-compute-api-version 2.58 or greater is required to ' + 'support the --marker option' + ) + raise exceptions.CommandError(msg) + kwargs['marker'] = parsed_args.marker + + if parsed_args.limit: + if compute_client.api_version < api_versions.APIVersion('2.58'): + msg = _( + '--os-compute-api-version 2.58 or greater is required to ' + 'support the --limit option' + ) + raise exceptions.CommandError(msg) + kwargs['limit'] = parsed_args.limit + + if parsed_args.changes_since: + if compute_client.api_version < api_versions.APIVersion('2.58'): + msg = _( + '--os-compute-api-version 2.58 or greater is required to ' + 'support the --changes-since option' + ) + raise exceptions.CommandError(msg) + + try: + iso8601.parse_date(parsed_args.changes_since) + except (TypeError, iso8601.ParseError): + msg = _('Invalid changes-since value: %s') + raise exceptions.CommandError(msg % parsed_args.changes_since) + + kwargs['changes_since'] = parsed_args.changes_since + + if parsed_args.changes_before: + if compute_client.api_version < api_versions.APIVersion('2.66'): + msg = _( + '--os-compute-api-version 2.66 or greater is required to ' + 'support the --changes-before option' + ) + raise exceptions.CommandError(msg) + + try: + iso8601.parse_date(parsed_args.changes_before) + except (TypeError, iso8601.ParseError): + msg = _('Invalid changes-before value: %s') + raise exceptions.CommandError(msg % parsed_args.changes_before) + + kwargs['changes_before'] = parsed_args.changes_before + + server_id = utils.find_resource( + compute_client.servers, parsed_args.server, + ).id + + data = compute_client.instance_action.list(server_id, **kwargs) + + columns = ( + 'request_id', + 'instance_uuid', + 'action', + 'start_time', + ) + column_headers = ( + 'Request ID', + 'Server ID', + 'Action', + 'Start Time', + ) if parsed_args.long: - columns = ( - 'request_id', - 'instance_uuid', - 'action', - 'start_time', + columns += ( 'message', 'project_id', 'user_id', ) - column_headers = ( - 'Request ID', - 'Server ID', - 'Action', - 'Start Time', + column_headers += ( 'Message', 'Project ID', 'User ID', ) - else: - columns = ( - 'request_id', - 'instance_uuid', - 'action', - 'start_time', - ) - column_headers = ( - 'Request ID', - 'Server ID', - 'Action', - 'Start Time', - ) - return (column_headers, - (utils.get_item_properties( - s, columns, - ) for s in data)) + return ( + column_headers, + (utils.get_item_properties(s, columns) for s in data), + ) class ShowServerEvent(command.ShowOne): - _description = _( - "Show server event details. " - "Specify ``--os-compute-api-version 2.21`` " - "or higher to show event details for a deleted server. " - "Specify ``--os-compute-api-version 2.51`` " - "or higher to show event details for non-admin users.") + """Show server event details. + + Specify ``--os-compute-api-version 2.21`` or higher to show event details + for a deleted server. Specify ``--os-compute-api-version 2.51`` or higher + to show event details for non-admin users. + """ def get_parser(self, prog_name): parser = super(ShowServerEvent, self).get_parser(prog_name) @@ -116,9 +204,13 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): compute_client = self.app.client_manager.compute - server_id = utils.find_resource(compute_client.servers, - parsed_args.server).id + + server_id = utils.find_resource( + compute_client.servers, parsed_args.server, + ).id + action_detail = compute_client.instance_action.get( - server_id, parsed_args.request_id) + server_id, parsed_args.request_id + ) return zip(*sorted(action_detail.to_dict().items())) diff --git a/openstackclient/tests/unit/compute/v2/test_server_event.py b/openstackclient/tests/unit/compute/v2/test_server_event.py index 5c94891a14..058a44d8b6 100644 --- a/openstackclient/tests/unit/compute/v2/test_server_event.py +++ b/openstackclient/tests/unit/compute/v2/test_server_event.py @@ -11,7 +11,12 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -# + +from unittest import mock + +import iso8601 +from novaclient import api_versions +from osc_lib import exceptions from openstackclient.compute.v2 import server_event from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes @@ -113,6 +118,234 @@ def test_server_event_list_long(self): self.assertEqual(self.long_columns, columns) self.assertEqual(self.long_data, tuple(data)) + def test_server_event_list_with_changes_since(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.58') + + arglist = [ + '--changes-since', '2016-03-04T06:27:59Z', + self.fake_server.name, + ] + verifylist = [ + ('server', self.fake_server.name), + ('changes_since', '2016-03-04T06:27:59Z'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_once_with(self.fake_server.name) + self.events_mock.list.assert_called_once_with( + self.fake_server.id, changes_since='2016-03-04T06:27:59Z') + + self.assertEqual(self.columns, columns) + self.assertEqual(tuple(self.data), tuple(data)) + + @mock.patch.object(iso8601, 'parse_date', side_effect=iso8601.ParseError) + def test_server_event_list_with_changes_since_invalid( + self, mock_parse_isotime, + ): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.58') + + arglist = [ + '--changes-since', 'Invalid time value', + self.fake_server.name, + ] + verifylist = [ + ('server', self.fake_server.name), + ('changes_since', 'Invalid time value'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + self.assertIn( + 'Invalid changes-since value:', str(ex)) + mock_parse_isotime.assert_called_once_with( + 'Invalid time value' + ) + + def test_server_event_list_with_changes_since_pre_v258(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.57') + + arglist = [ + '--changes-since', '2016-03-04T06:27:59Z', + self.fake_server.name, + ] + verifylist = [ + ('server', self.fake_server.name), + ('changes_since', '2016-03-04T06:27:59Z'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + self.assertIn( + '--os-compute-api-version 2.58 or greater is required', str(ex)) + + def test_server_event_list_with_changes_before(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.66') + + arglist = [ + '--changes-before', '2016-03-04T06:27:59Z', + self.fake_server.name, + ] + verifylist = [ + ('server', self.fake_server.name), + ('changes_before', '2016-03-04T06:27:59Z'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_once_with(self.fake_server.name) + self.events_mock.list.assert_called_once_with( + self.fake_server.id, changes_before='2016-03-04T06:27:59Z') + + self.assertEqual(self.columns, columns) + self.assertEqual(tuple(self.data), tuple(data)) + + @mock.patch.object(iso8601, 'parse_date', side_effect=iso8601.ParseError) + def test_server_event_list_with_changes_before_invalid( + self, mock_parse_isotime, + ): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.66') + + arglist = [ + '--changes-before', 'Invalid time value', + self.fake_server.name, + ] + verifylist = [ + ('server', self.fake_server.name), + ('changes_before', 'Invalid time value'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + self.assertIn( + 'Invalid changes-before value:', str(ex)) + mock_parse_isotime.assert_called_once_with( + 'Invalid time value' + ) + + def test_server_event_list_with_changes_before_pre_v266(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.65') + + arglist = [ + '--changes-before', '2016-03-04T06:27:59Z', + self.fake_server.name, + ] + verifylist = [ + ('server', self.fake_server.name), + ('changes_before', '2016-03-04T06:27:59Z'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + self.assertIn( + '--os-compute-api-version 2.66 or greater is required', str(ex)) + + def test_server_event_list_with_limit(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.58') + + arglist = [ + '--limit', '1', + self.fake_server.name, + ] + verifylist = [ + ('limit', 1), + ('server', self.fake_server.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.events_mock.list.assert_called_once_with( + self.fake_server.id, limit=1) + + def test_server_event_list_with_limit_pre_v258(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.57') + + arglist = [ + '--limit', '1', + self.fake_server.name, + ] + verifylist = [ + ('limit', 1), + ('server', self.fake_server.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + self.assertIn( + '--os-compute-api-version 2.58 or greater is required', str(ex)) + + def test_server_event_list_with_marker(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.58') + + arglist = [ + '--marker', 'test_event', + self.fake_server.name, + ] + verifylist = [ + ('marker', 'test_event'), + ('server', self.fake_server.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.events_mock.list.assert_called_once_with( + self.fake_server.id, marker='test_event') + + def test_server_event_list_with_marker_pre_v258(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.57') + + arglist = [ + '--marker', 'test_event', + self.fake_server.name, + ] + verifylist = [ + ('marker', 'test_event'), + ('server', self.fake_server.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + self.assertIn( + '--os-compute-api-version 2.58 or greater is required', str(ex)) + class TestShowServerEvent(TestServerEvent): diff --git a/releasenotes/notes/add-missing-server-group-list-opts-ebcf84bfcea07a4b.yaml b/releasenotes/notes/add-missing-server-group-list-opts-ebcf84bfcea07a4b.yaml new file mode 100644 index 0000000000..0533b6185e --- /dev/null +++ b/releasenotes/notes/add-missing-server-group-list-opts-ebcf84bfcea07a4b.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``--limit``, ``--marker``, ``--change-since`` and ``--changes-before`` + options to ``server group list`` command, to configure pagination of + results and filter results by last modification, respectively. From d6c9b7f198b94ef05c96dc72fc71d34f019e9350 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 10 Dec 2020 14:13:47 +0000 Subject: [PATCH 2334/3095] compute: Shuffle options for 'server create' argparse doesn't sort options by name, meaning we can use the opportunity to group closely related options together. Do that. Change-Id: I6714c8db1a549bd4206d2282d2876a406af65aa2 Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server.py | 198 ++++++++++++++------------- 1 file changed, 102 insertions(+), 96 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 15a5084d0d..7e17d0d043 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -621,6 +621,12 @@ def get_parser(self, prog_name): metavar='', help=_('New server name'), ) + parser.add_argument( + '--flavor', + metavar='', + required=True, + help=_('Create server with this flavor (name or ID)'), + ) disk_group = parser.add_mutually_exclusive_group( required=True, ) @@ -629,12 +635,17 @@ def get_parser(self, prog_name): metavar='', help=_('Create server boot disk from this image (name or ID)'), ) + # TODO(stephenfin): Is this actually useful? Looks like a straight port + # from 'nova boot --image-with'. Perhaps we should deprecate this. disk_group.add_argument( '--image-property', metavar='', action=parseractions.KeyValueAction, dest='image_properties', - help=_("Image property to be matched"), + help=_( + "Create server using the image that matches the specified " + "property. Property must match exactly one property." + ), ) disk_group.add_argument( '--volume', @@ -649,17 +660,101 @@ def get_parser(self, prog_name): 'volume.' ), ) + parser.add_argument( + '--boot-from-volume', + metavar='', + type=int, + help=_( + 'When used in conjunction with the ``--image`` or ' + '``--image-property`` option, this option automatically ' + 'creates a block device mapping with a boot index of 0 ' + 'and tells the compute service to create a volume of the ' + 'given size (in GB) from the specified image and use it ' + 'as the root disk of the server. The root volume will not ' + 'be deleted when the server is deleted. This option is ' + 'mutually exclusive with the ``--volume`` option.' + ) + ) + parser.add_argument( + '--block-device-mapping', + metavar='', + action=parseractions.KeyValueAction, + default={}, + # NOTE(RuiChen): Add '\n' to the end of line to improve formatting; + # see cliff's _SmartHelpFormatter for more details. + help=_( + 'Create a block device on the server.\n' + 'Block device mapping in the format\n' + '=:::\n' + ': block device name, like: vdb, xvdc ' + '(required)\n' + ': Name or ID of the volume, volume snapshot or image ' + '(required)\n' + ': volume, snapshot or image; default: volume ' + '(optional)\n' + ': volume size if create from image or snapshot ' + '(optional)\n' + ': true or false; default: false ' + '(optional)\n' + ), + ) + parser.add_argument( + '--network', + metavar="", + action='append', + dest='nic', + type=_prefix_checked_value('net-id='), + # NOTE(RuiChen): Add '\n' to the end of line to improve formatting; + # see cliff's _SmartHelpFormatter for more details. + help=_( + "Create a NIC on the server and connect it to network. " + "Specify option multiple times to create multiple NICs. " + "This is a wrapper for the '--nic net-id=' " + "parameter that provides simple syntax for the standard " + "use case of connecting a new server to a given network. " + "For more advanced use cases, refer to the '--nic' " + "parameter." + ), + ) + parser.add_argument( + '--port', + metavar="", + action='append', + dest='nic', + type=_prefix_checked_value('port-id='), + help=_( + "Create a NIC on the server and connect it to port. " + "Specify option multiple times to create multiple NICs. " + "This is a wrapper for the '--nic port-id=' " + "parameter that provides simple syntax for the standard " + "use case of connecting a new server to a given port. For " + "more advanced use cases, refer to the '--nic' parameter." + ), + ) + parser.add_argument( + '--nic', + metavar="", + action='append', + help=_( + "Create a NIC on the server. " + "Specify option multiple times to create multiple NICs. " + "Either net-id or port-id must be provided, but not both. " + "net-id: attach NIC to network with this UUID, " + "port-id: attach NIC to port with this UUID, " + "v4-fixed-ip: IPv4 fixed address for NIC (optional), " + "v6-fixed-ip: IPv6 fixed address for NIC (optional), " + "none: (v2.37+) no network is attached, " + "auto: (v2.37+) the compute service will automatically " + "allocate a network. Specifying a --nic of auto or none " + "cannot be used with any other --nic value." + ), + ) parser.add_argument( '--password', metavar='', help=_("Set the password to this server"), ) - parser.add_argument( - '--flavor', - metavar='', - required=True, - help=_('Create server with this flavor (name or ID)'), - ) parser.add_argument( '--security-group', metavar='', @@ -736,95 +831,6 @@ def get_parser(self, prog_name): '(supported by --os-compute-api-version 2.74 or above)' ), ) - parser.add_argument( - '--boot-from-volume', - metavar='', - type=int, - help=_( - 'When used in conjunction with the ``--image`` or ' - '``--image-property`` option, this option automatically ' - 'creates a block device mapping with a boot index of 0 ' - 'and tells the compute service to create a volume of the ' - 'given size (in GB) from the specified image and use it ' - 'as the root disk of the server. The root volume will not ' - 'be deleted when the server is deleted. This option is ' - 'mutually exclusive with the ``--volume`` option.' - ) - ) - parser.add_argument( - '--block-device-mapping', - metavar='', - action=parseractions.KeyValueAction, - default={}, - # NOTE(RuiChen): Add '\n' at the end of line to put each item in - # the separated line, avoid the help message looks - # messy, see _SmartHelpFormatter in cliff. - help=_( - 'Create a block device on the server.\n' - 'Block device mapping in the format\n' - '=:::\n' - ': block device name, like: vdb, xvdc ' - '(required)\n' - ': Name or ID of the volume, volume snapshot or image ' - '(required)\n' - ': volume, snapshot or image; default: volume ' - '(optional)\n' - ': volume size if create from image or snapshot ' - '(optional)\n' - ': true or false; default: false ' - '(optional)\n' - ), - ) - parser.add_argument( - '--nic', - metavar="", - action='append', - help=_( - "Create a NIC on the server. " - "Specify option multiple times to create multiple NICs. " - "Either net-id or port-id must be provided, but not both. " - "net-id: attach NIC to network with this UUID, " - "port-id: attach NIC to port with this UUID, " - "v4-fixed-ip: IPv4 fixed address for NIC (optional), " - "v6-fixed-ip: IPv6 fixed address for NIC (optional), " - "none: (v2.37+) no network is attached, " - "auto: (v2.37+) the compute service will automatically " - "allocate a network. Specifying a --nic of auto or none " - "cannot be used with any other --nic value." - ), - ) - parser.add_argument( - '--network', - metavar="", - action='append', - dest='nic', - type=_prefix_checked_value('net-id='), - help=_( - "Create a NIC on the server and connect it to network. " - "Specify option multiple times to create multiple NICs. " - "This is a wrapper for the '--nic net-id=' " - "parameter that provides simple syntax for the standard " - "use case of connecting a new server to a given network. " - "For more advanced use cases, refer to the '--nic' " - "parameter." - ), - ) - parser.add_argument( - '--port', - metavar="", - action='append', - dest='nic', - type=_prefix_checked_value('port-id='), - help=_( - "Create a NIC on the server and connect it to port. " - "Specify option multiple times to create multiple NICs. " - "This is a wrapper for the '--nic port-id=' " - "parameter that provides simple syntax for the standard " - "use case of connecting a new server to a given port. For " - "more advanced use cases, refer to the '--nic' parameter." - ), - ) parser.add_argument( '--hint', metavar='', From c7d582379ad6b22c6dd8b7334b34a51ec59b69d4 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 10 Dec 2020 15:06:03 +0000 Subject: [PATCH 2335/3095] compute: Improve 'server create --nic' option parsing Simplify the parsing of this option by making use of a custom action. Change-Id: I670ff5109522d533ef4e62a79116e49a35c4e8fa Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server.py | 221 +++++++++++------- .../tests/unit/compute/v2/test_server.py | 104 ++++----- 2 files changed, 185 insertions(+), 140 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 7e17d0d043..1277c34130 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -98,16 +98,6 @@ def _get_ip_address(addresses, address_type, ip_address_family): ) -def _prefix_checked_value(prefix): - def func(value): - if ',' in value or '=' in value: - msg = _("Invalid argument %s, " - "characters ',' and '=' are not allowed") % value - raise argparse.ArgumentTypeError(msg) - return prefix + value - return func - - def _prep_server_detail(compute_client, image_client, server, refresh=True): """Prepare the detailed server dict for printing @@ -611,6 +601,81 @@ def take_action(self, parsed_args): ) +# TODO(stephenfin): Replace with 'MultiKeyValueAction' when we no longer +# support '--nic=auto' and '--nic=none' +class NICAction(argparse.Action): + + def __init__( + self, + option_strings, + dest, + nargs=None, + const=None, + default=None, + type=None, + choices=None, + required=False, + help=None, + metavar=None, + key=None, + ): + self.key = key + super().__init__( + option_strings=option_strings, dest=dest, nargs=nargs, const=const, + default=default, type=type, choices=choices, required=required, + help=help, metavar=metavar, + ) + + def __call__(self, parser, namespace, values, option_string=None): + # Make sure we have an empty dict rather than None + if getattr(namespace, self.dest, None) is None: + setattr(namespace, self.dest, []) + + # Handle the special auto/none cases + if values in ('auto', 'none'): + getattr(namespace, self.dest).append(values) + return + + if self.key: + if ',' in values or '=' in values: + msg = _( + "Invalid argument %s; characters ',' and '=' are not " + "allowed" + ) + raise argparse.ArgumentTypeError(msg % values) + + values = '='.join([self.key, values]) + + info = { + 'net-id': '', + 'port-id': '', + 'v4-fixed-ip': '', + 'v6-fixed-ip': '', + } + + for kv_str in values.split(','): + k, sep, v = kv_str.partition("=") + + if k not in info or not v: + msg = _( + "Invalid argument %s; argument must be of form " + "'net-id=net-uuid,v4-fixed-ip=ip-addr,v6-fixed-ip=ip-addr," + "port-id=port-uuid'" + ) + raise argparse.ArgumentTypeError(msg % values) + + info[k] = v + + if info['net-id'] and info['port-id']: + msg = _( + 'Invalid argument %s; either network or port should be ' + 'specified but not both' + ) + raise argparse.ArgumentTypeError(msg % values) + + getattr(namespace, self.dest).append(info) + + class CreateServer(command.ShowOne): _description = _("Create a new server") @@ -701,9 +766,10 @@ def get_parser(self, prog_name): parser.add_argument( '--network', metavar="", - action='append', - dest='nic', - type=_prefix_checked_value('net-id='), + dest='nics', + default=[], + action=NICAction, + key='net-id', # NOTE(RuiChen): Add '\n' to the end of line to improve formatting; # see cliff's _SmartHelpFormatter for more details. help=_( @@ -719,9 +785,10 @@ def get_parser(self, prog_name): parser.add_argument( '--port', metavar="", - action='append', - dest='nic', - type=_prefix_checked_value('port-id='), + dest='nics', + default=[], + action=NICAction, + key='port-id', help=_( "Create a NIC on the server and connect it to port. " "Specify option multiple times to create multiple NICs. " @@ -733,21 +800,28 @@ def get_parser(self, prog_name): ) parser.add_argument( '--nic', - metavar="", - action='append', + metavar="", + action=NICAction, + dest='nics', + default=[], help=_( - "Create a NIC on the server. " - "Specify option multiple times to create multiple NICs. " - "Either net-id or port-id must be provided, but not both. " - "net-id: attach NIC to network with this UUID, " - "port-id: attach NIC to port with this UUID, " - "v4-fixed-ip: IPv4 fixed address for NIC (optional), " - "v6-fixed-ip: IPv6 fixed address for NIC (optional), " - "none: (v2.37+) no network is attached, " + "Create a NIC on the server.\n" + "NIC in the format:\n" + "net-id=: attach NIC to network with this UUID,\n" + "port-id=: attach NIC to port with this UUID,\n" + "v4-fixed-ip=: IPv4 fixed address for NIC (optional)," + "\n" + "v6-fixed-ip=: IPv6 fixed address for NIC (optional)," + "\n" + "none: (v2.37+) no network is attached,\n" "auto: (v2.37+) the compute service will automatically " - "allocate a network. Specifying a --nic of auto or none " - "cannot be used with any other --nic value." + "allocate a network.\n" + "\n" + "Specify option multiple times to create multiple NICs.\n" + "Specifying a --nic of auto or none cannot be used with any " + "other --nic value.\n" + "Either net-id or port-id must be provided, but not both." ), ) parser.add_argument( @@ -1103,84 +1177,55 @@ def _match_image(image_api, wanted_properties): raise exceptions.CommandError(msg) block_device_mapping_v2.append(mapping) - nics = [] - auto_or_none = False - if parsed_args.nic is None: - parsed_args.nic = [] - for nic_str in parsed_args.nic: - # Handle the special auto/none cases - if nic_str in ('auto', 'none'): - auto_or_none = True - nics.append(nic_str) - else: - nic_info = { - 'net-id': '', - 'v4-fixed-ip': '', - 'v6-fixed-ip': '', - 'port-id': '', - } - for kv_str in nic_str.split(","): - k, sep, v = kv_str.partition("=") - if k in nic_info and v: - nic_info[k] = v - else: - msg = _( - "Invalid nic argument '%s'. Nic arguments " - "must be of the form --nic ." - ) - raise exceptions.CommandError(msg % k) + nics = parsed_args.nics - if bool(nic_info["net-id"]) == bool(nic_info["port-id"]): - msg = _( - 'Either network or port should be specified ' - 'but not both' - ) - raise exceptions.CommandError(msg) + if 'auto' in nics or 'none' in nics: + if len(nics) > 1: + msg = _( + 'Specifying a --nic of auto or none cannot ' + 'be used with any other --nic, --network ' + 'or --port value.' + ) + raise exceptions.CommandError(msg) + nics = nics[0] + else: + for nic in nics: if self.app.client_manager.is_network_endpoint_enabled(): network_client = self.app.client_manager.network - if nic_info["net-id"]: + + if nic['net-id']: net = network_client.find_network( - nic_info["net-id"], ignore_missing=False) - nic_info["net-id"] = net.id - if nic_info["port-id"]: + nic['net-id'], ignore_missing=False, + ) + nic['net-id'] = net.id + + if nic['port-id']: port = network_client.find_port( - nic_info["port-id"], ignore_missing=False) - nic_info["port-id"] = port.id + nic['port-id'], ignore_missing=False, + ) + nic['port-id'] = port.id else: - if nic_info["net-id"]: - nic_info["net-id"] = compute_client.api.network_find( - nic_info["net-id"] + if nic['net-id']: + nic['net-id'] = compute_client.api.network_find( + nic['net-id'], )['id'] - if nic_info["port-id"]: + + if nic['port-id']: msg = _( "Can't create server with port specified " "since network endpoint not enabled" ) raise exceptions.CommandError(msg) - nics.append(nic_info) - - if nics: - if auto_or_none: - if len(nics) > 1: - msg = _( - 'Specifying a --nic of auto or none cannot ' - 'be used with any other --nic, --network ' - 'or --port value.' - ) - raise exceptions.CommandError(msg) - nics = nics[0] - else: + if not nics: # Compute API version >= 2.37 requires a value, so default to # 'auto' to maintain legacy behavior if a nic wasn't specified. if compute_client.api_version >= api_versions.APIVersion('2.37'): nics = 'auto' else: - # Default to empty list if nothing was specified, let nova - # side to decide the default behavior. + # Default to empty list if nothing was specified and let nova + # decide the default behavior. nics = [] # Check security group exist and convert ID to name diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 9a01758c8c..e8db7f596f 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -1311,8 +1311,28 @@ def test_server_create_with_network(self): verifylist = [ ('image', 'image1'), ('flavor', 'flavor1'), - ('nic', ['net-id=net1', 'net-id=net1,v4-fixed-ip=10.0.0.2', - 'port-id=port1', 'net-id=net1', 'port-id=port2']), + ('nics', [ + { + 'net-id': 'net1', 'port-id': '', + 'v4-fixed-ip': '', 'v6-fixed-ip': '', + }, + { + 'net-id': 'net1', 'port-id': '', + 'v4-fixed-ip': '10.0.0.2', 'v6-fixed-ip': '', + }, + { + 'net-id': '', 'port-id': 'port1', + 'v4-fixed-ip': '', 'v6-fixed-ip': '', + }, + { + 'net-id': 'net1', 'port-id': '', + 'v4-fixed-ip': '', 'v6-fixed-ip': '', + }, + { + 'net-id': '', 'port-id': 'port2', + 'v4-fixed-ip': '', 'v6-fixed-ip': '', + }, + ]), ('config_drive', False), ('server_name', self.new_server.name), ] @@ -1413,7 +1433,7 @@ def test_server_create_with_auto_network(self): verifylist = [ ('image', 'image1'), ('flavor', 'flavor1'), - ('nic', ['auto']), + ('nics', ['auto']), ('config_drive', False), ('server_name', self.new_server.name), ] @@ -1509,7 +1529,7 @@ def test_server_create_with_none_network(self): verifylist = [ ('image', 'image1'), ('flavor', 'flavor1'), - ('nic', ['none']), + ('nics', ['none']), ('config_drive', False), ('server_name', self.new_server.name), ] @@ -1557,7 +1577,14 @@ def test_server_create_with_conflict_network_options(self): verifylist = [ ('image', 'image1'), ('flavor', 'flavor1'), - ('nic', ['none', 'auto', 'port-id=port1']), + ('nics', [ + 'none', + 'auto', + { + 'net-id': '', 'port-id': 'port1', + 'v4-fixed-ip': '', 'v6-fixed-ip': '', + }, + ]), ('config_drive', False), ('server_name', self.new_server.name), ] @@ -1587,17 +1614,10 @@ def test_server_create_with_invalid_network_options(self): '--nic', 'abcdefgh', self.new_server.name, ] - verifylist = [ - ('image', 'image1'), - ('flavor', 'flavor1'), - ('nic', ['abcdefgh']), - ('config_drive', False), - ('server_name', self.new_server.name), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - self.assertRaises(exceptions.CommandError, - self.cmd.take_action, parsed_args) + self.assertRaises( + argparse.ArgumentTypeError, + self.check_parser, + self.cmd, arglist, []) self.assertNotCalled(self.servers_mock.create) def test_server_create_with_invalid_network_key(self): @@ -1607,17 +1627,10 @@ def test_server_create_with_invalid_network_key(self): '--nic', 'abcdefgh=12324', self.new_server.name, ] - verifylist = [ - ('image', 'image1'), - ('flavor', 'flavor1'), - ('nic', ['abcdefgh=12324']), - ('config_drive', False), - ('server_name', self.new_server.name), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - self.assertRaises(exceptions.CommandError, - self.cmd.take_action, parsed_args) + self.assertRaises( + argparse.ArgumentTypeError, + self.check_parser, + self.cmd, arglist, []) self.assertNotCalled(self.servers_mock.create) def test_server_create_with_empty_network_key_value(self): @@ -1627,17 +1640,10 @@ def test_server_create_with_empty_network_key_value(self): '--nic', 'net-id=', self.new_server.name, ] - verifylist = [ - ('image', 'image1'), - ('flavor', 'flavor1'), - ('nic', ['net-id=']), - ('config_drive', False), - ('server_name', self.new_server.name), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - self.assertRaises(exceptions.CommandError, - self.cmd.take_action, parsed_args) + self.assertRaises( + argparse.ArgumentTypeError, + self.check_parser, + self.cmd, arglist, []) self.assertNotCalled(self.servers_mock.create) def test_server_create_with_only_network_key(self): @@ -1647,17 +1653,11 @@ def test_server_create_with_only_network_key(self): '--nic', 'net-id', self.new_server.name, ] - verifylist = [ - ('image', 'image1'), - ('flavor', 'flavor1'), - ('nic', ['net-id']), - ('config_drive', False), - ('server_name', self.new_server.name), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises( + argparse.ArgumentTypeError, + self.check_parser, + self.cmd, arglist, []) - self.assertRaises(exceptions.CommandError, - self.cmd.take_action, parsed_args) self.assertNotCalled(self.servers_mock.create) @mock.patch.object(common_utils, 'wait_for_status', return_value=True) @@ -2230,7 +2230,7 @@ def test_server_create_image_property(self): verifylist = [ ('image_properties', {'hypervisor_type': 'qemu'}), ('flavor', 'flavor1'), - ('nic', ['none']), + ('nics', ['none']), ('config_drive', False), ('server_name', self.new_server.name), ] @@ -2286,7 +2286,7 @@ def test_server_create_image_property_multi(self): ('image_properties', {'hypervisor_type': 'qemu', 'hw_disk_bus': 'ide'}), ('flavor', 'flavor1'), - ('nic', ['none']), + ('nics', ['none']), ('config_drive', False), ('server_name', self.new_server.name), ] @@ -2342,7 +2342,7 @@ def test_server_create_image_property_missed(self): ('image_properties', {'hypervisor_type': 'qemu', 'hw_disk_bus': 'virtio'}), ('flavor', 'flavor1'), - ('nic', ['none']), + ('nics', ['none']), ('config_drive', False), ('server_name', self.new_server.name), ] @@ -2374,7 +2374,7 @@ def test_server_create_image_property_with_image_list(self): ('image_properties', {'owner_specified.openstack.object': 'image/cirros'}), ('flavor', 'flavor1'), - ('nic', ['none']), + ('nics', ['none']), ('server_name', self.new_server.name), ] # create a image_info as the side_effect of the fake image_list() From 9ed34aac0a172ece4cd856319486208fcdba095d Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Tue, 19 Jan 2021 16:55:14 +0000 Subject: [PATCH 2336/3095] compute: Add support for 'server boot --nic ...,tag=' This has been around for a long time but was not exposed via OSC. Close this gap. Change-Id: I71aabf10f791f68ee7405ffb5e8317cc96cb3b38 Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server.py | 23 +++- .../tests/unit/compute/v2/test_server.py | 107 ++++++++++++++++++ ...-nic-tagging-support-f77b4247e87771d5.yaml | 7 ++ 3 files changed, 133 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/add-server-nic-tagging-support-f77b4247e87771d5.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 1277c34130..9294288305 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -646,6 +646,8 @@ def __call__(self, parser, namespace, values, option_string=None): values = '='.join([self.key, values]) + # We don't include 'tag' here by default since that requires a + # particular microversion info = { 'net-id': '', 'port-id': '', @@ -656,11 +658,11 @@ def __call__(self, parser, namespace, values, option_string=None): for kv_str in values.split(','): k, sep, v = kv_str.partition("=") - if k not in info or not v: + if k not in list(info) + ['tag'] or not v: msg = _( "Invalid argument %s; argument must be of form " - "'net-id=net-uuid,v4-fixed-ip=ip-addr,v6-fixed-ip=ip-addr," - "port-id=port-uuid'" + "'net-id=net-uuid,port-id=port-uuid,v4-fixed-ip=ip-addr," + "v6-fixed-ip=ip-addr,tag=tag'" ) raise argparse.ArgumentTypeError(msg % values) @@ -801,7 +803,7 @@ def get_parser(self, prog_name): parser.add_argument( '--nic', metavar="", + "v6-fixed-ip=ip-addr,tag=tag,auto,none>", action=NICAction, dest='nics', default=[], @@ -814,6 +816,8 @@ def get_parser(self, prog_name): "\n" "v6-fixed-ip=: IPv6 fixed address for NIC (optional)," "\n" + "tag: interface metadata tag (optional) " + "(supported by --os-compute-api-version 2.43 or above),\n" "none: (v2.37+) no network is attached,\n" "auto: (v2.37+) the compute service will automatically " "allocate a network.\n" @@ -1191,6 +1195,17 @@ def _match_image(image_api, wanted_properties): nics = nics[0] else: for nic in nics: + if 'tag' in nic: + if ( + compute_client.api_version < + api_versions.APIVersion('2.43') + ): + msg = _( + '--os-compute-api-version 2.43 or greater is ' + 'required to support the --nic tag field' + ) + raise exceptions.CommandError(msg) + if self.app.client_manager.is_network_endpoint_enabled(): network_client = self.app.client_manager.network diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index e8db7f596f..5cf76c887e 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -1423,6 +1423,113 @@ def test_server_create_with_network(self): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist(), data) + def test_server_create_with_network_tag(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.43') + + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--nic', 'net-id=net1,tag=foo', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('nics', [ + { + 'net-id': 'net1', 'port-id': '', + 'v4-fixed-ip': '', 'v6-fixed-ip': '', + 'tag': 'foo', + }, + ]), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_network = mock.Mock() + network_client = self.app.client_manager.network + network_client.find_network = find_network + network_resource = mock.Mock(id='net1_uuid') + find_network.return_value = network_resource + + # Mock sdk APIs. + _network = mock.Mock(id='net1_uuid') + find_network = mock.Mock() + find_network.return_value = _network + self.app.client_manager.network.find_network = find_network + + # 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) + + # Set expected values + kwargs = dict( + meta=None, + files={}, + reservation_id=None, + min_count=1, + max_count=1, + security_groups=[], + userdata=None, + key_name=None, + availability_zone=None, + admin_pass=None, + block_device_mapping_v2=[], + nics=[ + { + 'net-id': 'net1_uuid', + 'v4-fixed-ip': '', + 'v6-fixed-ip': '', + 'port-id': '', + 'tag': 'foo', + }, + ], + scheduler_hints={}, + config_drive=None, + ) + # ServerManager.create(name, image, flavor, **kwargs) + self.servers_mock.create.assert_called_with( + self.new_server.name, + self.image, + self.flavor, + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) + + network_client.find_network.assert_called_once() + self.app.client_manager.network.find_network.assert_called_once() + + def test_server_create_with_network_tag_pre_v243(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.42') + + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--nic', 'net-id=net1,tag=foo', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('nics', [ + { + 'net-id': 'net1', 'port-id': '', + 'v4-fixed-ip': '', 'v6-fixed-ip': '', + 'tag': 'foo', + }, + ]), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, self.cmd.take_action, parsed_args) + def test_server_create_with_auto_network(self): arglist = [ '--image', 'image1', diff --git a/releasenotes/notes/add-server-nic-tagging-support-f77b4247e87771d5.yaml b/releasenotes/notes/add-server-nic-tagging-support-f77b4247e87771d5.yaml new file mode 100644 index 0000000000..e5f4fd5255 --- /dev/null +++ b/releasenotes/notes/add-server-nic-tagging-support-f77b4247e87771d5.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + The ``--nic`` option of the ``server create`` command now supports an + optional ``tag=`` key-value pair. This can be used to set a tag for + the interface in server metadata, which can be useful to maintain + persistent references to interfaces during various operations. From 32ae1857d12fc2d5b4292e2d98caed0238959081 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Tue, 19 Jan 2021 17:11:48 +0000 Subject: [PATCH 2337/3095] Rename FakeServerMigration to FakeMigration Server migrations are (confusingly) a different thing returned by a different API. Change-Id: Ib6b7c8f9cc3d1521a993616f832d41651dc46f73 Signed-off-by: Stephen Finucane --- .../tests/unit/compute/v2/fakes.py | 20 +++++++++---------- .../tests/unit/compute/v2/test_server.py | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index e4cf10454e..7f3dcae4b1 100644 --- a/openstackclient/tests/unit/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -1566,12 +1566,12 @@ def __init__(self, verb, uri, value, remain, self.next_available = next_available -class FakeServerMigration(object): - """Fake one or more server migrations.""" +class FakeMigration(object): + """Fake one or more migrations.""" @staticmethod - def create_one_server_migration(attrs=None, methods=None): - """Create a fake server migration. + def create_one_migration(attrs=None, methods=None): + """Create a fake migration. :param Dictionary attrs: A dictionary with all attributes @@ -1612,22 +1612,22 @@ def create_one_server_migration(attrs=None, methods=None): return migration @staticmethod - def create_server_migrations(attrs=None, methods=None, count=2): - """Create multiple fake server migrations. + def create_migrations(attrs=None, methods=None, count=2): + """Create multiple fake migrations. :param Dictionary attrs: A dictionary with all attributes :param Dictionary methods: A dictionary with all methods :param int count: - The number of server migrations to fake + The number of migrations to fake :return: - A list of FakeResource objects faking the server migrations + A list of FakeResource objects faking the migrations """ migrations = [] for i in range(0, count): migrations.append( - FakeServerMigration.create_one_server_migration( + FakeMigration.create_one_migration( attrs, methods)) return migrations @@ -1680,7 +1680,7 @@ def create_volume_attachments(attrs=None, methods=None, count=2): :param Dictionary methods: A dictionary with all methods :param int count: - The number of server migrations to fake + The number of volume attachments to fake :return: A list of FakeResource objects faking the volume attachments. """ diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 5cf76c887e..5025f020c2 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -4396,8 +4396,8 @@ def setUp(self): self.server = compute_fakes.FakeServer.create_one_server() self.servers_mock.get.return_value = self.server - self.migrations = compute_fakes.FakeServerMigration\ - .create_server_migrations(count=3) + self.migrations = compute_fakes.FakeMigration.create_migrations( + count=3) self.migrations_mock.list.return_value = self.migrations self.data = (common_utils.get_item_properties( From f80fe2d8cf50f85601e2895a5f878764fa77c154 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 20 Jan 2021 10:11:37 +0000 Subject: [PATCH 2338/3095] compute: Add 'server migration show' command This replaces the 'server-migration-show' command provided by novaclient. Change-Id: I413310b481cc13b70853eb579417f6e6fad10d98 Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server.py | 64 ++++++++++ .../tests/unit/compute/v2/fakes.py | 53 ++++++++ .../tests/unit/compute/v2/test_server.py | 118 ++++++++++++++++++ ...gration-show-command-2e3a25e383dc5d70.yaml | 5 + setup.cfg | 3 +- 5 files changed, 242 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/add-server-migration-show-command-2e3a25e383dc5d70.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 9294288305..3c598981f8 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -2603,6 +2603,70 @@ def take_action(self, parsed_args): return self.print_migrations(parsed_args, compute_client, migrations) +class ShowMigration(command.Command): + """Show a migration for a given server.""" + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'server', + metavar='', + help=_('Server (name or ID)'), + ) + parser.add_argument( + 'migration', + metavar='', + help=_("Migration (ID)"), + ) + return parser + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + + if compute_client.api_version < api_versions.APIVersion('2.24'): + msg = _( + '--os-compute-api-version 2.24 or greater is required to ' + 'support the server migration show command' + ) + raise exceptions.CommandError(msg) + + server = utils.find_resource( + compute_client.servers, + parsed_args.server, + ) + server_migration = compute_client.server_migrations.get( + server.id, parsed_args.migration, + ) + + columns = ( + 'ID', + 'Server UUID', + 'Status', + 'Source Compute', + 'Source Node', + 'Dest Compute', + 'Dest Host', + 'Dest Node', + 'Memory Total Bytes', + 'Memory Processed Bytes', + 'Memory Remaining Bytes', + 'Disk Total Bytes', + 'Disk Processed Bytes', + 'Disk Remaining Bytes', + 'Created At', + 'Updated At', + ) + + if compute_client.api_version >= api_versions.APIVersion('2.59'): + columns += ('UUID',) + + if compute_client.api_version >= api_versions.APIVersion('2.80'): + columns += ('User ID', 'Project ID') + + data = utils.get_item_properties(server_migration, columns) + return columns, data + + class AbortMigration(command.Command): """Cancel an ongoing live migration. diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index 7f3dcae4b1..4a2a44de12 100644 --- a/openstackclient/tests/unit/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -1633,6 +1633,59 @@ def create_migrations(attrs=None, methods=None, count=2): return migrations +class FakeServerMigration(object): + """Fake one or more server migrations.""" + + @staticmethod + def create_one_server_migration(attrs=None, methods=None): + """Create a fake server migration. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :return: + A FakeResource object, with id, type, and so on + """ + attrs = attrs or {} + methods = methods or {} + + # Set default attributes. + + migration_info = { + "created_at": "2016-01-29T13:42:02.000000", + "dest_compute": "compute2", + "dest_host": "1.2.3.4", + "dest_node": "node2", + "id": random.randint(1, 999), + "server_uuid": uuid.uuid4().hex, + "source_compute": "compute1", + "source_node": "node1", + "status": "running", + "memory_total_bytes": random.randint(1, 99999), + "memory_processed_bytes": random.randint(1, 99999), + "memory_remaining_bytes": random.randint(1, 99999), + "disk_total_bytes": random.randint(1, 99999), + "disk_processed_bytes": random.randint(1, 99999), + "disk_remaining_bytes": random.randint(1, 99999), + "updated_at": "2016-01-29T13:42:02.000000", + # added in 2.59 + "uuid": uuid.uuid4().hex, + # added in 2.80 + "user_id": uuid.uuid4().hex, + "project_id": uuid.uuid4().hex, + } + + # Overwrite default attributes. + migration_info.update(attrs) + + migration = fakes.FakeResource( + info=copy.deepcopy(migration_info), + methods=methods, + loaded=True) + return migration + + class FakeVolumeAttachment(object): """Fake one or more volume attachments (BDMs).""" diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 5025f020c2..8d040472f0 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -4888,6 +4888,124 @@ def test_get_migrations_with_project_and_user_pre_v280(self): str(ex)) +class TestServerMigrationShow(TestServer): + + def setUp(self): + super().setUp() + + self.server = compute_fakes.FakeServer.create_one_server() + self.servers_mock.get.return_value = self.server + + self.server_migration = compute_fakes.FakeServerMigration\ + .create_one_server_migration() + self.server_migrations_mock.get.return_value = self.server_migration + + self.columns = ( + 'ID', + 'Server UUID', + 'Status', + 'Source Compute', + 'Source Node', + 'Dest Compute', + 'Dest Host', + 'Dest Node', + 'Memory Total Bytes', + 'Memory Processed Bytes', + 'Memory Remaining Bytes', + 'Disk Total Bytes', + 'Disk Processed Bytes', + 'Disk Remaining Bytes', + 'Created At', + 'Updated At', + ) + + self.data = ( + self.server_migration.id, + self.server_migration.server_uuid, + self.server_migration.status, + self.server_migration.source_compute, + self.server_migration.source_node, + self.server_migration.dest_compute, + self.server_migration.dest_host, + self.server_migration.dest_node, + self.server_migration.memory_total_bytes, + self.server_migration.memory_processed_bytes, + self.server_migration.memory_remaining_bytes, + self.server_migration.disk_total_bytes, + self.server_migration.disk_processed_bytes, + self.server_migration.disk_remaining_bytes, + self.server_migration.created_at, + self.server_migration.updated_at, + ) + + # Get the command object to test + self.cmd = server.ShowMigration(self.app, None) + + def _test_server_migration_show(self): + arglist = [ + self.server.id, + '2', # arbitrary migration ID + ] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + self.servers_mock.get.assert_called_with(self.server.id) + self.server_migrations_mock.get.assert_called_with( + self.server.id, '2',) + + def test_server_migration_show(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.24') + + self._test_server_migration_show() + + def test_server_migration_show_v259(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.59') + + self.columns += ('UUID',) + self.data += (self.server_migration.uuid,) + + self._test_server_migration_show() + + def test_server_migration_show_v280(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.80') + + self.columns += ('UUID', 'User ID', 'Project ID') + self.data += ( + self.server_migration.uuid, + self.server_migration.user_id, + self.server_migration.project_id, + ) + + self._test_server_migration_show() + + def test_server_migration_show_pre_v224(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.23') + + arglist = [ + self.server.id, + '2', # arbitrary migration ID + ] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.24 or greater is required', + str(ex)) + + class TestServerMigrationAbort(TestServer): def setUp(self): diff --git a/releasenotes/notes/add-server-migration-show-command-2e3a25e383dc5d70.yaml b/releasenotes/notes/add-server-migration-show-command-2e3a25e383dc5d70.yaml new file mode 100644 index 0000000000..28ab2759a6 --- /dev/null +++ b/releasenotes/notes/add-server-migration-show-command-2e3a25e383dc5d70.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``server migration show`` commands. This can be used to show detailed + information about an ongoing server migration. diff --git a/setup.cfg b/setup.cfg index 48384897a0..ee412c050a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -110,9 +110,10 @@ openstack.compute.v2 = server_migrate = openstackclient.compute.v2.server:MigrateServer server_migrate_confirm = openstackclient.compute.v2.server:MigrateConfirm server_migrate_revert = openstackclient.compute.v2.server:MigrateRevert - server_migration_list = openstackclient.compute.v2.server:ListMigration server_migration_abort = openstackclient.compute.v2.server:AbortMigration server_migration_force_complete = openstackclient.compute.v2.server:ForceCompleteMigration + server_migration_list = openstackclient.compute.v2.server:ListMigration + server_migration_show = openstackclient.compute.v2.server:ShowMigration server_pause = openstackclient.compute.v2.server:PauseServer server_reboot = openstackclient.compute.v2.server:RebootServer server_rebuild = openstackclient.compute.v2.server:RebuildServer From a52beacaa6fcc11d48f5b742c73aa2c0f87634ce Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 20 Jan 2021 10:19:19 +0000 Subject: [PATCH 2339/3095] compute: Rename 'server migrate (confirm|revert)' We're confirming or reverting a server migration, not a server migrate. We've a number of 'server migration *' commands now so it makes sense to move them under here. Change-Id: Ib95bb36511dad1aafe75f0c88d10ded382e4fa5c Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server.py | 41 ++++- .../tests/unit/compute/v2/test_server.py | 152 ++++++++++++++++++ ...firm-revert-commands-84fcb937721f5c4a.yaml | 7 + setup.cfg | 2 + 4 files changed, 198 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/rename-server-migrate-confirm-revert-commands-84fcb937721f5c4a.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 3c598981f8..419ae4a5dd 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -3559,10 +3559,27 @@ def take_action(self, parsed_args): server.confirm_resize() +# TODO(stephenfin): Remove in OSC 7.0 class MigrateConfirm(ResizeConfirm): - _description = _("""Confirm server migrate. + _description = _("""DEPRECATED: Confirm server migration. -Confirm (verify) success of migrate operation and release the old server.""") +Use 'server migration confirm' instead.""") + + def take_action(self, parsed_args): + msg = _( + "The 'server migrate confirm' command has been deprecated in " + "favour of the 'server migration confirm' command." + ) + self.log.warning(msg) + + super().take_action(parsed_args) + + +class ConfirmMigration(ResizeConfirm): + _description = _("""Confirm server migration. + +Confirm (verify) success of the migration operation and release the old +server.""") class ResizeRevert(command.Command): @@ -3590,10 +3607,26 @@ def take_action(self, parsed_args): server.revert_resize() +# TODO(stephenfin): Remove in OSC 7.0 class MigrateRevert(ResizeRevert): - _description = _("""Revert server migrate. + _description = _("""Revert server migration. + +Use 'server migration revert' instead.""") + + def take_action(self, parsed_args): + msg = _( + "The 'server migrate revert' command has been deprecated in " + "favour of the 'server migration revert' command." + ) + self.log.warning(msg) + + super().take_action(parsed_args) + + +class RevertMigration(ResizeRevert): + _description = _("""Revert server migration. -Revert the migrate operation. Release the new server and restart the old +Revert the migration operation. Release the new server and restart the old one.""") diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 8d040472f0..ce04ea4ccd 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -6401,6 +6401,82 @@ def test_resize_confirm(self): self.server.confirm_resize.assert_called_with() +# TODO(stephenfin): Remove in OSC 7.0 +class TestServerMigrateConfirm(TestServer): + + def setUp(self): + super().setUp() + + methods = { + 'confirm_resize': None, + } + self.server = compute_fakes.FakeServer.create_one_server( + methods=methods) + + # This is the return value for utils.find_resource() + self.servers_mock.get.return_value = self.server + + self.servers_mock.confirm_resize.return_value = None + + # Get the command object to test + self.cmd = server.MigrateConfirm(self.app, None) + + def test_migrate_confirm(self): + arglist = [ + self.server.id, + ] + verifylist = [ + ('server', self.server.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + with mock.patch.object(self.cmd.log, 'warning') as mock_warning: + self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.server.confirm_resize.assert_called_with() + + mock_warning.assert_called_once() + self.assertIn( + "The 'server migrate confirm' command has been deprecated", + str(mock_warning.call_args[0][0]) + ) + + +class TestServerConfirmMigration(TestServerResizeConfirm): + + def setUp(self): + super().setUp() + + methods = { + 'confirm_resize': None, + } + self.server = compute_fakes.FakeServer.create_one_server( + methods=methods) + + # This is the return value for utils.find_resource() + self.servers_mock.get.return_value = self.server + + self.servers_mock.confirm_resize.return_value = None + + # Get the command object to test + self.cmd = server.ConfirmMigration(self.app, None) + + def test_migration_confirm(self): + arglist = [ + self.server.id, + ] + verifylist = [ + ('server', self.server.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.server.confirm_resize.assert_called_with() + + class TestServerResizeRevert(TestServer): def setUp(self): @@ -6435,6 +6511,82 @@ def test_resize_revert(self): self.server.revert_resize.assert_called_with() +# TODO(stephenfin): Remove in OSC 7.0 +class TestServerMigrateRevert(TestServer): + + def setUp(self): + super().setUp() + + methods = { + 'revert_resize': None, + } + self.server = compute_fakes.FakeServer.create_one_server( + methods=methods) + + # This is the return value for utils.find_resource() + self.servers_mock.get.return_value = self.server + + self.servers_mock.revert_resize.return_value = None + + # Get the command object to test + self.cmd = server.MigrateRevert(self.app, None) + + def test_migrate_revert(self): + arglist = [ + self.server.id, + ] + verifylist = [ + ('server', self.server.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + with mock.patch.object(self.cmd.log, 'warning') as mock_warning: + self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.server.revert_resize.assert_called_with() + + mock_warning.assert_called_once() + self.assertIn( + "The 'server migrate revert' command has been deprecated", + str(mock_warning.call_args[0][0]) + ) + + +class TestServerRevertMigration(TestServer): + + def setUp(self): + super().setUp() + + methods = { + 'revert_resize': None, + } + self.server = compute_fakes.FakeServer.create_one_server( + methods=methods) + + # This is the return value for utils.find_resource() + self.servers_mock.get.return_value = self.server + + self.servers_mock.revert_resize.return_value = None + + # Get the command object to test + self.cmd = server.RevertMigration(self.app, None) + + def test_migration_revert(self): + arglist = [ + self.server.id, + ] + verifylist = [ + ('server', self.server.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.server.revert_resize.assert_called_with() + + class TestServerRestore(TestServer): def setUp(self): diff --git a/releasenotes/notes/rename-server-migrate-confirm-revert-commands-84fcb937721f5c4a.yaml b/releasenotes/notes/rename-server-migrate-confirm-revert-commands-84fcb937721f5c4a.yaml new file mode 100644 index 0000000000..4c24d5f229 --- /dev/null +++ b/releasenotes/notes/rename-server-migrate-confirm-revert-commands-84fcb937721f5c4a.yaml @@ -0,0 +1,7 @@ +--- +upgrade: + - | + The ``server migrate confirm`` and ``server migrate revert`` commands, + introduced in OSC 5.0, have been deprecated in favour of + ``server migration confirm`` and ``server migration revert`` respectively. + The deprecated commands will be removed in a future major version. diff --git a/setup.cfg b/setup.cfg index ee412c050a..7c3eef8570 100644 --- a/setup.cfg +++ b/setup.cfg @@ -111,8 +111,10 @@ openstack.compute.v2 = server_migrate_confirm = openstackclient.compute.v2.server:MigrateConfirm server_migrate_revert = openstackclient.compute.v2.server:MigrateRevert server_migration_abort = openstackclient.compute.v2.server:AbortMigration + server_migration_confirm = openstackclient.compute.v2.server:ConfirmMigration server_migration_force_complete = openstackclient.compute.v2.server:ForceCompleteMigration server_migration_list = openstackclient.compute.v2.server:ListMigration + server_migration_revert = openstackclient.compute.v2.server:RevertMigration server_migration_show = openstackclient.compute.v2.server:ShowMigration server_pause = openstackclient.compute.v2.server:PauseServer server_reboot = openstackclient.compute.v2.server:RebootServer From 074e045c6938189c77fd13ce4dd9fccf69d3a12a Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 20 Jan 2021 15:34:25 +0000 Subject: [PATCH 2340/3095] compute: Improve 'server create --block-device-mapping' option parsing Once again, custom actions to the rescue. Change-Id: I6b4f80882dbbeb6a2a7e877f63becae7211b7f9a Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server.py | 134 ++++++++++-------- .../tests/unit/compute/v2/test_server.py | 105 ++++++++++---- 2 files changed, 153 insertions(+), 86 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 419ae4a5dd..7edbb18e83 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -678,6 +678,51 @@ def __call__(self, parser, namespace, values, option_string=None): getattr(namespace, self.dest).append(info) +class BDMLegacyAction(argparse.Action): + + def __call__(self, parser, namespace, values, option_string=None): + # Make sure we have an empty dict rather than None + if getattr(namespace, self.dest, None) is None: + setattr(namespace, self.dest, []) + + dev_name, sep, dev_map = values.partition('=') + dev_map = dev_map.split(':') if dev_map else dev_map + if not dev_name or not dev_map or len(dev_map) > 4: + msg = _( + "Invalid argument %s; argument must be of form " + "'dev-name=id[:type[:size[:delete-on-terminate]]]'" + ) + raise argparse.ArgumentTypeError(msg % values) + + mapping = { + 'device_name': dev_name, + # store target; this may be a name and will need verification later + 'uuid': dev_map[0], + 'source_type': 'volume', + 'destination_type': 'volume', + } + + # decide source and destination type + if len(dev_map) > 1 and dev_map[1]: + if dev_map[1] not in ('volume', 'snapshot', 'image'): + msg = _( + "Invalid argument %s; 'type' must be one of: volume, " + "snapshot, image" + ) + raise argparse.ArgumentTypeError(msg % values) + + mapping['source_type'] = dev_map[1] + + # 3. append size and delete_on_termination, if present + if len(dev_map) > 2 and dev_map[2]: + mapping['volume_size'] = dev_map[2] + + if len(dev_map) > 3 and dev_map[3]: + mapping['delete_on_termination'] = dev_map[3] + + getattr(namespace, self.dest).append(mapping) + + class CreateServer(command.ShowOne): _description = _("Create a new server") @@ -745,8 +790,8 @@ def get_parser(self, prog_name): parser.add_argument( '--block-device-mapping', metavar='', - action=parseractions.KeyValueAction, - default={}, + action=BDMLegacyAction, + default=[], # NOTE(RuiChen): Add '\n' to the end of line to improve formatting; # see cliff's _SmartHelpFormatter for more details. help=_( @@ -1123,62 +1168,35 @@ def _match_image(image_api, wanted_properties): # If booting from volume we do not pass an image to compute. image = None - boot_args = [parsed_args.server_name, image, flavor] - # Handle block device by device name order, like: vdb -> vdc -> vdd - for dev_name in sorted(parsed_args.block_device_mapping): - dev_map = parsed_args.block_device_mapping[dev_name] - dev_map = dev_map.split(':') - if dev_map[0]: - mapping = {'device_name': dev_name} - - # 1. decide source and destination type - if (len(dev_map) > 1 and - dev_map[1] in ('volume', 'snapshot', 'image')): - mapping['source_type'] = dev_map[1] - else: - mapping['source_type'] = 'volume' - - mapping['destination_type'] = 'volume' - - # 2. check target exist, update target uuid according by - # source type - if mapping['source_type'] == 'volume': - volume_id = utils.find_resource( - volume_client.volumes, dev_map[0]).id - mapping['uuid'] = volume_id - elif mapping['source_type'] == 'snapshot': - snapshot_id = utils.find_resource( - volume_client.volume_snapshots, dev_map[0]).id - mapping['uuid'] = snapshot_id - elif mapping['source_type'] == 'image': - # NOTE(mriedem): In case --image is specified with the same - # image, that becomes the root disk for the server. If the - # block device is specified with a root device name, e.g. - # vda, then the compute API will likely fail complaining - # that there is a conflict. So if using the same image ID, - # which doesn't really make sense but it's allowed, the - # device name would need to be a non-root device, e.g. vdb. - # Otherwise if the block device image is different from the - # one specified by --image, then the compute service will - # create a volume from the image and attach it to the - # server as a non-root volume. - image_id = image_client.find_image(dev_map[0], - ignore_missing=False).id - mapping['uuid'] = image_id - - # 3. append size and delete_on_termination if exist - if len(dev_map) > 2 and dev_map[2]: - mapping['volume_size'] = dev_map[2] - - if len(dev_map) > 3 and dev_map[3]: - mapping['delete_on_termination'] = dev_map[3] - else: - msg = _( - 'Volume, volume snapshot or image (name or ID) must ' - 'be specified if --block-device-mapping is specified' - ) - raise exceptions.CommandError(msg) + for mapping in parsed_args.block_device_mapping: + if mapping['source_type'] == 'volume': + volume_id = utils.find_resource( + volume_client.volumes, mapping['uuid'], + ).id + mapping['uuid'] = volume_id + elif mapping['source_type'] == 'snapshot': + snapshot_id = utils.find_resource( + volume_client.volume_snapshots, mapping['uuid'], + ).id + mapping['uuid'] = snapshot_id + elif mapping['source_type'] == 'image': + # NOTE(mriedem): In case --image is specified with the same + # image, that becomes the root disk for the server. If the + # block device is specified with a root device name, e.g. + # vda, then the compute API will likely fail complaining + # that there is a conflict. So if using the same image ID, + # which doesn't really make sense but it's allowed, the + # device name would need to be a non-root device, e.g. vdb. + # Otherwise if the block device image is different from the + # one specified by --image, then the compute service will + # create a volume from the image and attach it to the + # server as a non-root volume. + image_id = image_client.find_image( + mapping['uuid'], ignore_missing=False, + ).id + mapping['uuid'] = image_id + block_device_mapping_v2.append(mapping) nics = parsed_args.nics @@ -1281,6 +1299,8 @@ def _match_image(image_api, wanted_properties): else: config_drive = parsed_args.config_drive + boot_args = [parsed_args.server_name, image, flavor] + boot_kwargs = dict( meta=parsed_args.properties, files=files, diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index ce04ea4ccd..58daf531ec 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -1935,7 +1935,15 @@ def test_server_create_with_block_device_mapping(self): verifylist = [ ('image', 'image1'), ('flavor', self.flavor.id), - ('block_device_mapping', {'vda': self.volume.name + ':::false'}), + ('block_device_mapping', [ + { + 'device_name': 'vda', + 'uuid': self.volume.name, + 'source_type': 'volume', + 'destination_type': 'volume', + 'delete_on_termination': 'false', + } + ]), ('config_drive', False), ('server_name', self.new_server.name), ] @@ -1988,7 +1996,14 @@ def test_server_create_with_block_device_mapping_min_input(self): verifylist = [ ('image', 'image1'), ('flavor', self.flavor.id), - ('block_device_mapping', {'vdf': self.volume.name}), + ('block_device_mapping', [ + { + 'device_name': 'vdf', + 'uuid': self.volume.name, + 'source_type': 'volume', + 'destination_type': 'volume', + } + ]), ('config_drive', False), ('server_name', self.new_server.name), ] @@ -2040,7 +2055,14 @@ def test_server_create_with_block_device_mapping_default_input(self): verifylist = [ ('image', 'image1'), ('flavor', self.flavor.id), - ('block_device_mapping', {'vdf': self.volume.name + ':::'}), + ('block_device_mapping', [ + { + 'device_name': 'vdf', + 'uuid': self.volume.name, + 'source_type': 'volume', + 'destination_type': 'volume', + } + ]), ('config_drive', False), ('server_name', self.new_server.name), ] @@ -2093,8 +2115,16 @@ def test_server_create_with_block_device_mapping_full_input(self): verifylist = [ ('image', 'image1'), ('flavor', self.flavor.id), - ('block_device_mapping', - {'vde': self.volume.name + ':volume:3:true'}), + ('block_device_mapping', [ + { + 'device_name': 'vde', + 'uuid': self.volume.name, + 'source_type': 'volume', + 'destination_type': 'volume', + 'volume_size': '3', + 'delete_on_termination': 'true', + } + ]), ('config_drive', False), ('server_name', self.new_server.name), ] @@ -2149,8 +2179,16 @@ def test_server_create_with_block_device_mapping_snapshot(self): verifylist = [ ('image', 'image1'), ('flavor', self.flavor.id), - ('block_device_mapping', - {'vds': self.volume.name + ':snapshot:5:true'}), + ('block_device_mapping', [ + { + 'device_name': 'vds', + 'uuid': self.volume.name, + 'source_type': 'snapshot', + 'volume_size': '5', + 'destination_type': 'volume', + 'delete_on_termination': 'true', + } + ]), ('config_drive', False), ('server_name', self.new_server.name), ] @@ -2205,8 +2243,22 @@ def test_server_create_with_block_device_mapping_multiple(self): verifylist = [ ('image', 'image1'), ('flavor', self.flavor.id), - ('block_device_mapping', {'vdb': self.volume.name + ':::false', - 'vdc': self.volume.name + ':::true'}), + ('block_device_mapping', [ + { + 'device_name': 'vdb', + 'uuid': self.volume.name, + 'source_type': 'volume', + 'destination_type': 'volume', + 'delete_on_termination': 'false', + }, + { + 'device_name': 'vdc', + 'uuid': self.volume.name, + 'source_type': 'volume', + 'destination_type': 'volume', + 'delete_on_termination': 'true', + }, + ]), ('config_drive', False), ('server_name', self.new_server.name), ] @@ -2259,26 +2311,29 @@ def test_server_create_with_block_device_mapping_multiple(self): self.assertEqual(self.datalist(), data) def test_server_create_with_block_device_mapping_invalid_format(self): - # 1. block device mapping don't contain equal sign "=" + # block device mapping don't contain equal sign "=" arglist = [ '--image', 'image1', '--flavor', self.flavor.id, '--block-device-mapping', 'not_contain_equal_sign', self.new_server.name, ] - self.assertRaises(argparse.ArgumentTypeError, - self.check_parser, - self.cmd, arglist, []) - # 2. block device mapping don't contain device name "=uuid:::true" + self.assertRaises( + argparse.ArgumentTypeError, + self.check_parser, + self.cmd, arglist, []) + + # block device mapping don't contain device name "=uuid:::true" arglist = [ '--image', 'image1', '--flavor', self.flavor.id, '--block-device-mapping', '=uuid:::true', self.new_server.name, ] - self.assertRaises(argparse.ArgumentTypeError, - self.check_parser, - self.cmd, arglist, []) + self.assertRaises( + argparse.ArgumentTypeError, + self.check_parser, + self.cmd, arglist, []) def test_server_create_with_block_device_mapping_no_uuid(self): arglist = [ @@ -2287,18 +2342,10 @@ def test_server_create_with_block_device_mapping_no_uuid(self): '--block-device-mapping', 'vdb=', self.new_server.name, ] - verifylist = [ - ('image', 'image1'), - ('flavor', self.flavor.id), - ('block_device_mapping', {'vdb': ''}), - ('config_drive', False), - ('server_name', self.new_server.name), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - self.assertRaises(exceptions.CommandError, - self.cmd.take_action, - parsed_args) + self.assertRaises( + argparse.ArgumentTypeError, + self.check_parser, + self.cmd, arglist, []) def test_server_create_volume_boot_from_volume_conflict(self): # Tests that specifying --volume and --boot-from-volume results in From 4da4b96296c6b6d4351ebd47e32d5049a88211f1 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 10 Dec 2020 12:40:59 +0000 Subject: [PATCH 2341/3095] compute: Add missing 'server create' options Add some volume-related options, namely '--snapshot', '--swap', and '--ephemeral'. All are shortcuts to avoid having to use '--block-device-mapping'. Change-Id: I450e429ade46a7103740150c90e3ba9f2894e1a5 Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server.py | 108 ++++++-- .../tests/unit/compute/v2/test_server.py | 233 ++++++++++++++++++ ...g-server-create-opts-d5e32bd743e9e132.yaml | 8 + 3 files changed, 335 insertions(+), 14 deletions(-) create mode 100644 releasenotes/notes/add-missing-server-create-opts-d5e32bd743e9e132.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 7edbb18e83..1e4f19361f 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -772,6 +772,19 @@ def get_parser(self, prog_name): 'volume.' ), ) + disk_group.add_argument( + '--snapshot', + metavar='', + help=_( + 'Create server using this snapshot as the boot disk (name or ' + 'ID)\n' + 'This option automatically creates a block device mapping ' + 'with a boot index of 0. On many hypervisors (libvirt/kvm ' + 'for example) this will be device vda. Do not create a ' + 'duplicate mapping using --block-device-mapping for this ' + 'volume.' + ), + ) parser.add_argument( '--boot-from-volume', metavar='', @@ -784,7 +797,8 @@ def get_parser(self, prog_name): 'given size (in GB) from the specified image and use it ' 'as the root disk of the server. The root volume will not ' 'be deleted when the server is deleted. This option is ' - 'mutually exclusive with the ``--volume`` option.' + 'mutually exclusive with the ``--volume`` and ``--snapshot`` ' + 'options.' ) ) parser.add_argument( @@ -810,6 +824,28 @@ def get_parser(self, prog_name): '(optional)\n' ), ) + parser.add_argument( + '--swap', + metavar='', + type=int, + help=( + "Create and attach a local swap block device of " + "MiB." + ), + ) + parser.add_argument( + '--ephemeral', + metavar='', + action=parseractions.MultiKeyValueAction, + dest='ephemerals', + default=[], + required_keys=['size'], + optional_keys=['format'], + help=( + "Create and attach a local ephemeral block device of " + "GiB and format it to ." + ), + ) parser.add_argument( '--network', metavar="", @@ -929,12 +965,14 @@ def get_parser(self, prog_name): parser.add_argument( '--availability-zone', metavar='', - help=_('Select an availability zone for the server. ' - 'Host and node are optional parameters. ' - 'Availability zone in the format ' - '::, ' - '::, : ' - 'or '), + help=_( + 'Select an availability zone for the server. ' + 'Host and node are optional parameters. ' + 'Availability zone in the format ' + '::, ' + '::, : ' + 'or ' + ), ) parser.add_argument( '--host', @@ -1000,11 +1038,6 @@ def get_parser(self, prog_name): default=1, help=_('Maximum number of servers to launch (default=1)'), ) - parser.add_argument( - '--wait', - action='store_true', - help=_('Wait for build to complete'), - ) parser.add_argument( '--tag', metavar='', @@ -1017,6 +1050,11 @@ def get_parser(self, prog_name): '(supported by --os-compute-api-version 2.52 or above)' ), ) + parser.add_argument( + '--wait', + action='store_true', + help=_('Wait for build to complete'), + ) return parser def take_action(self, parsed_args): @@ -1092,7 +1130,6 @@ def _match_image(image_api, wanted_properties): ) raise exceptions.CommandError(msg) - # Lookup parsed_args.volume volume = None if parsed_args.volume: # --volume and --boot-from-volume are mutually exclusive. @@ -1105,7 +1142,18 @@ def _match_image(image_api, wanted_properties): parsed_args.volume, ).id - # Lookup parsed_args.flavor + snapshot = None + if parsed_args.snapshot: + # --snapshot and --boot-from-volume are mutually exclusive. + if parsed_args.boot_from_volume: + msg = _('--snapshot is not allowed with --boot-from-volume') + raise exceptions.CommandError(msg) + + snapshot = utils.find_resource( + volume_client.volume_snapshots, + parsed_args.snapshot, + ).id + flavor = utils.find_resource( compute_client.flavors, parsed_args.flavor) @@ -1156,6 +1204,14 @@ def _match_image(image_api, wanted_properties): 'source_type': 'volume', 'destination_type': 'volume' }] + elif snapshot: + block_device_mapping_v2 = [{ + 'uuid': snapshot, + 'boot_index': '0', + 'source_type': 'snapshot', + 'destination_type': 'volume', + 'delete_on_termination': False + }] elif parsed_args.boot_from_volume: # Tell nova to create a root volume from the image provided. block_device_mapping_v2 = [{ @@ -1168,6 +1224,30 @@ def _match_image(image_api, wanted_properties): # If booting from volume we do not pass an image to compute. image = None + if parsed_args.swap: + block_device_mapping_v2.append({ + 'boot_index': -1, + 'source_type': 'blank', + 'destination_type': 'local', + 'guest_format': 'swap', + 'volume_size': parsed_args.swap, + 'delete_on_termination': True, + }) + + for mapping in parsed_args.ephemerals: + block_device_mapping_dict = { + 'boot_index': -1, + 'source_type': 'blank', + 'destination_type': 'local', + 'delete_on_termination': True, + 'volume_size': mapping['size'], + } + + if 'format' in mapping: + block_device_mapping_dict['guest_format'] = mapping['format'] + + block_device_mapping_v2.append(block_device_mapping_dict) + # Handle block device by device name order, like: vdb -> vdc -> vdd for mapping in parsed_args.block_device_mapping: if mapping['source_type'] == 'volume': diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 58daf531ec..ce93f21ea3 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -1925,6 +1925,109 @@ def test_server_create_userdata(self, mock_open): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist(), data) + def test_server_create_with_volume(self): + arglist = [ + '--flavor', self.flavor.id, + '--volume', self.volume.name, + self.new_server.name, + ] + verifylist = [ + ('flavor', self.flavor.id), + ('volume', self.volume.name), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # CreateServer.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'meta': None, + 'files': {}, + 'reservation_id': None, + 'min_count': 1, + 'max_count': 1, + 'security_groups': [], + 'userdata': None, + 'key_name': None, + 'availability_zone': None, + 'admin_pass': None, + 'block_device_mapping_v2': [{ + 'uuid': self.volume.id, + 'boot_index': '0', + 'source_type': 'volume', + 'destination_type': 'volume', + }], + 'nics': [], + 'scheduler_hints': {}, + 'config_drive': None, + } + # ServerManager.create(name, image, flavor, **kwargs) + self.servers_mock.create.assert_called_with( + self.new_server.name, + None, + self.flavor, + **kwargs + ) + self.volumes_mock.get.assert_called_once_with( + self.volume.name) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) + + def test_server_create_with_snapshot(self): + arglist = [ + '--flavor', self.flavor.id, + '--snapshot', self.snapshot.name, + self.new_server.name, + ] + verifylist = [ + ('flavor', self.flavor.id), + ('snapshot', self.snapshot.name), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # CreateServer.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'meta': None, + 'files': {}, + 'reservation_id': None, + 'min_count': 1, + 'max_count': 1, + 'security_groups': [], + 'userdata': None, + 'key_name': None, + 'availability_zone': None, + 'admin_pass': None, + 'block_device_mapping_v2': [{ + 'uuid': self.snapshot.id, + 'boot_index': '0', + 'source_type': 'snapshot', + 'destination_type': 'volume', + 'delete_on_termination': False, + }], + 'nics': [], + 'scheduler_hints': {}, + 'config_drive': None, + } + # ServerManager.create(name, image, flavor, **kwargs) + self.servers_mock.create.assert_called_with( + self.new_server.name, + None, + self.flavor, + **kwargs + ) + self.snapshots_mock.get.assert_called_once_with( + self.snapshot.name) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) + def test_server_create_with_block_device_mapping(self): arglist = [ '--image', 'image1', @@ -2575,6 +2678,136 @@ def test_server_create_image_property_with_image_list(self): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist(), data) + def test_server_create_with_swap(self): + arglist = [ + '--image', 'image1', + '--flavor', self.flavor.id, + '--swap', '1024', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', self.flavor.id), + ('swap', 1024), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # CreateServer.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'meta': None, + 'files': {}, + 'reservation_id': None, + 'min_count': 1, + 'max_count': 1, + 'security_groups': [], + 'userdata': None, + 'key_name': None, + 'availability_zone': None, + 'admin_pass': None, + 'block_device_mapping_v2': [{ + 'boot_index': -1, + 'source_type': 'blank', + 'destination_type': 'local', + 'guest_format': 'swap', + 'volume_size': 1024, + 'delete_on_termination': True, + }], + 'nics': [], + 'scheduler_hints': {}, + 'config_drive': None, + } + # ServerManager.create(name, image, flavor, **kwargs) + self.servers_mock.create.assert_called_with( + self.new_server.name, + self.image, + self.flavor, + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) + + def test_server_create_with_ephemeral(self): + arglist = [ + '--image', 'image1', + '--flavor', self.flavor.id, + '--ephemeral', 'size=1024,format=ext4', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', self.flavor.id), + ('ephemerals', [{'size': '1024', 'format': 'ext4'}]), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # CreateServer.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'meta': None, + 'files': {}, + 'reservation_id': None, + 'min_count': 1, + 'max_count': 1, + 'security_groups': [], + 'userdata': None, + 'key_name': None, + 'availability_zone': None, + 'admin_pass': None, + 'block_device_mapping_v2': [{ + 'boot_index': -1, + 'source_type': 'blank', + 'destination_type': 'local', + 'guest_format': 'ext4', + 'volume_size': '1024', + 'delete_on_termination': True, + }], + 'nics': [], + 'scheduler_hints': {}, + 'config_drive': None, + } + # ServerManager.create(name, image, flavor, **kwargs) + self.servers_mock.create.assert_called_with( + self.new_server.name, + self.image, + self.flavor, + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) + + def test_server_create_with_ephemeral_missing_key(self): + arglist = [ + '--image', 'image1', + '--flavor', self.flavor.id, + '--ephemeral', 'format=ext3', + self.new_server.name, + ] + self.assertRaises( + argparse.ArgumentTypeError, + self.check_parser, + self.cmd, arglist, []) + + def test_server_create_with_ephemeral_invalid_key(self): + arglist = [ + '--image', 'image1', + '--flavor', self.flavor.id, + '--ephemeral', 'size=1024,foo=bar', + self.new_server.name, + ] + self.assertRaises( + argparse.ArgumentTypeError, + self.check_parser, + self.cmd, arglist, []) + def test_server_create_invalid_hint(self): # Not a key-value pair arglist = [ diff --git a/releasenotes/notes/add-missing-server-create-opts-d5e32bd743e9e132.yaml b/releasenotes/notes/add-missing-server-create-opts-d5e32bd743e9e132.yaml new file mode 100644 index 0000000000..b82a9d30d1 --- /dev/null +++ b/releasenotes/notes/add-missing-server-create-opts-d5e32bd743e9e132.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Add a number of additional options to the ``server create`` command: + + - ``--snapshot`` + - ``--ephemeral`` + - ``--swap`` From f2deabb136efdfca02a51b503f97cbb436ddfb45 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 20 Jan 2021 16:38:52 +0000 Subject: [PATCH 2342/3095] compute: Remove references to optional extensions This is no longer a thing in nova. Change-Id: I2413b826385792a4f33ff70e75621b48de65c799 Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 1e4f19361f..3db36f7262 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -997,7 +997,7 @@ def get_parser(self, prog_name): metavar='', action=parseractions.KeyValueAppendAction, default={}, - help=_('Hints for the scheduler (optional extension)'), + help=_('Hints for the scheduler'), ) config_drive_group = parser.add_mutually_exclusive_group() config_drive_group.add_argument( From ace4bfb6404b7b39c597c4884c56e26a47a94fc4 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 20 Jan 2021 17:42:42 +0000 Subject: [PATCH 2343/3095] compute: Add 'server create --block-device' option One of the last big gaps with novaclient. As noted in the release note, the current '--block-device-mapping' format is based on the old BDM v1 format, even though it actually results in BDM v2-style requests to the server. It's time to replace that. Change-Id: If4eba38ccfb208ee186b90a0eec95e5fe6cf8415 Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server.py | 119 +++++++- .../tests/unit/compute/v2/test_server.py | 256 ++++++++++++++++++ ...g-server-create-opts-d5e32bd743e9e132.yaml | 9 + 3 files changed, 383 insertions(+), 1 deletion(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 3db36f7262..b1a86a23df 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -801,6 +801,7 @@ def get_parser(self, prog_name): 'options.' ) ) + # TODO(stephenfin): Remove this in the v7.0 parser.add_argument( '--block-device-mapping', metavar='', @@ -809,7 +810,7 @@ def get_parser(self, prog_name): # NOTE(RuiChen): Add '\n' to the end of line to improve formatting; # see cliff's _SmartHelpFormatter for more details. help=_( - 'Create a block device on the server.\n' + '**Deprecated** Create a block device on the server.\n' 'Block device mapping in the format\n' '=:::\n' ': block device name, like: vdb, xvdc ' @@ -822,6 +823,49 @@ def get_parser(self, prog_name): '(optional)\n' ': true or false; default: false ' '(optional)\n' + 'Replaced by --block-device' + ), + ) + parser.add_argument( + '--block-device', + metavar='', + action=parseractions.MultiKeyValueAction, + dest='block_devices', + default=[], + required_keys=[ + 'boot_index', + ], + optional_keys=[ + 'uuid', 'source_type', 'destination_type', + 'disk_bus', 'device_type', 'device_name', 'guest_format', + 'volume_size', 'volume_type', 'delete_on_termination', 'tag', + ], + help=_( + 'Create a block device on the server.\n' + 'Block device in the format:\n' + 'uuid=: UUID of the volume, snapshot or ID ' + '(required if using source image, snapshot or volume),\n' + 'source_type=: source type ' + '(one of: image, snapshot, volume, blank),\n' + 'destination_typ=: destination type ' + '(one of: volume, local) (optional),\n' + 'disk_bus=: device bus ' + '(one of: uml, lxc, virtio, ...) (optional),\n' + 'device_type=: device type ' + '(one of: disk, cdrom, etc. (optional),\n' + 'device_name=: name of the device (optional),\n' + 'volume_size=: size of the block device in MiB ' + '(for swap) or GiB (for everything else) (optional),\n' + 'guest_format=: format of device (optional),\n' + 'boot_index=: index of disk used to order boot ' + 'disk ' + '(required for volume-backed instances),\n' + 'delete_on_termination=: whether to delete the ' + 'volume upon deletion of server (optional),\n' + 'tag=: device metadata tag (optional),\n' + 'volume_type=: type of volume to create (name or ' + 'ID) when source if blank, image or snapshot and dest is ' + 'volume (optional)' ), ) parser.add_argument( @@ -1250,6 +1294,8 @@ def _match_image(image_api, wanted_properties): # Handle block device by device name order, like: vdb -> vdc -> vdd for mapping in parsed_args.block_device_mapping: + # The 'uuid' field isn't necessarily a UUID yet; let's validate it + # just in case if mapping['source_type'] == 'volume': volume_id = utils.find_resource( volume_client.volumes, mapping['uuid'], @@ -1279,6 +1325,77 @@ def _match_image(image_api, wanted_properties): block_device_mapping_v2.append(mapping) + for mapping in parsed_args.block_devices: + try: + mapping['boot_index'] = int(mapping['boot_index']) + except ValueError: + msg = _( + 'The boot_index key of --block-device should be an ' + 'integer' + ) + raise exceptions.CommandError(msg) + + if 'tag' in mapping and ( + compute_client.api_version < api_versions.APIVersion('2.42') + ): + msg = _( + '--os-compute-api-version 2.42 or greater is ' + 'required to support the tag key of --block-device' + ) + raise exceptions.CommandError(msg) + + if 'volume_type' in mapping and ( + compute_client.api_version < api_versions.APIVersion('2.67') + ): + msg = _( + '--os-compute-api-version 2.67 or greater is ' + 'required to support the volume_type key of --block-device' + ) + raise exceptions.CommandError(msg) + + if 'source_type' in mapping: + if mapping['source_type'] not in ( + 'volume', 'image', 'snapshot', 'blank', + ): + msg = _( + 'The source_type key of --block-device should be one ' + 'of: volume, image, snapshot, blank' + ) + raise exceptions.CommandError(msg) + else: + mapping['source_type'] = 'blank' + + if 'destination_type' in mapping: + if mapping['destination_type'] not in ('local', 'volume'): + msg = _( + 'The destination_type key of --block-device should be ' + 'one of: local, volume' + ) + raise exceptions.CommandError(msg) + else: + if mapping['source_type'] in ('image', 'blank'): + mapping['destination_type'] = 'local' + else: # volume, snapshot + mapping['destination_type'] = 'volume' + + if 'delete_on_termination' in mapping: + try: + value = strutils.bool_from_string( + mapping['delete_on_termination'], strict=True) + except ValueError: + msg = _( + 'The delete_on_termination key of --block-device ' + 'should be a boolean-like value' + ) + raise exceptions.CommandError(msg) + + mapping['delete_on_termination'] = value + else: + if mapping['destination_type'] == 'local': + mapping['delete_on_termination'] = True + + block_device_mapping_v2.append(mapping) + nics = parsed_args.nics if 'auto' in nics or 'none' in nics: diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index ce93f21ea3..0548924d27 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -2028,6 +2028,262 @@ def test_server_create_with_snapshot(self): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist(), data) + def test_server_create_with_block_device(self): + block_device = f'uuid={self.volume.id},source_type=volume,boot_index=1' + arglist = [ + '--image', 'image1', + '--flavor', self.flavor.id, + '--block-device', block_device, + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', self.flavor.id), + ('block_devices', [ + { + 'uuid': self.volume.id, + 'source_type': 'volume', + 'boot_index': '1', + }, + ]), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # CreateServer.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'meta': None, + 'files': {}, + 'reservation_id': None, + 'min_count': 1, + 'max_count': 1, + 'security_groups': [], + 'userdata': None, + 'key_name': None, + 'availability_zone': None, + 'admin_pass': None, + 'block_device_mapping_v2': [{ + 'uuid': self.volume.id, + 'source_type': 'volume', + 'destination_type': 'volume', + 'boot_index': 1, + }], + 'nics': [], + 'scheduler_hints': {}, + 'config_drive': None, + } + # ServerManager.create(name, image, flavor, **kwargs) + self.servers_mock.create.assert_called_with( + self.new_server.name, + self.image, + self.flavor, + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) + + def test_server_create_with_block_device_full(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.67') + + block_device = ( + f'uuid={self.volume.id},source_type=volume,' + f'destination_type=volume,disk_bus=ide,device_type=disk,' + f'device_name=sdb,guest_format=ext4,volume_size=64,' + f'volume_type=foo,boot_index=1,delete_on_termination=true,' + f'tag=foo' + ) + + arglist = [ + '--image', 'image1', + '--flavor', self.flavor.id, + '--block-device', block_device, + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', self.flavor.id), + ('block_devices', [ + { + 'uuid': self.volume.id, + 'source_type': 'volume', + 'destination_type': 'volume', + 'disk_bus': 'ide', + 'device_type': 'disk', + 'device_name': 'sdb', + 'guest_format': 'ext4', + 'volume_size': '64', + 'volume_type': 'foo', + 'boot_index': '1', + 'delete_on_termination': 'true', + 'tag': 'foo', + }, + ]), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # CreateServer.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'meta': None, + 'files': {}, + 'reservation_id': None, + 'min_count': 1, + 'max_count': 1, + 'security_groups': [], + 'userdata': None, + 'key_name': None, + 'availability_zone': None, + 'admin_pass': None, + 'block_device_mapping_v2': [{ + 'uuid': self.volume.id, + 'source_type': 'volume', + 'destination_type': 'volume', + 'disk_bus': 'ide', + 'device_name': 'sdb', + 'volume_size': '64', + 'guest_format': 'ext4', + 'boot_index': 1, + 'device_type': 'disk', + 'delete_on_termination': True, + 'tag': 'foo', + 'volume_type': 'foo', + }], + 'nics': 'auto', + 'scheduler_hints': {}, + 'config_drive': None, + } + # ServerManager.create(name, image, flavor, **kwargs) + self.servers_mock.create.assert_called_with( + self.new_server.name, + self.image, + self.flavor, + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) + + def test_server_create_with_block_device_no_boot_index(self): + block_device = \ + f'uuid={self.volume.name},source_type=volume' + arglist = [ + '--image', 'image1', + '--flavor', self.flavor.id, + '--block-device', block_device, + self.new_server.name, + ] + self.assertRaises( + argparse.ArgumentTypeError, + self.check_parser, + self.cmd, arglist, []) + + def test_server_create_with_block_device_invalid_boot_index(self): + block_device = \ + f'uuid={self.volume.name},source_type=volume,boot_index=foo' + arglist = [ + '--image', 'image1', + '--flavor', self.flavor.id, + '--block-device', block_device, + self.new_server.name, + ] + parsed_args = self.check_parser(self.cmd, arglist, []) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, parsed_args) + self.assertIn('The boot_index key of --block-device ', str(ex)) + + def test_server_create_with_block_device_invalid_source_type(self): + block_device = f'uuid={self.volume.name},source_type=foo,boot_index=1' + arglist = [ + '--image', 'image1', + '--flavor', self.flavor.id, + '--block-device', block_device, + self.new_server.name, + ] + parsed_args = self.check_parser(self.cmd, arglist, []) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, parsed_args) + self.assertIn('The source_type key of --block-device ', str(ex)) + + def test_server_create_with_block_device_invalid_destination_type(self): + block_device = \ + f'uuid={self.volume.name},destination_type=foo,boot_index=1' + arglist = [ + '--image', 'image1', + '--flavor', self.flavor.id, + '--block-device', block_device, + self.new_server.name, + ] + parsed_args = self.check_parser(self.cmd, arglist, []) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, parsed_args) + self.assertIn('The destination_type key of --block-device ', str(ex)) + + def test_server_create_with_block_device_invalid_shutdown(self): + block_device = \ + f'uuid={self.volume.name},delete_on_termination=foo,boot_index=1' + arglist = [ + '--image', 'image1', + '--flavor', self.flavor.id, + '--block-device', block_device, + self.new_server.name, + ] + parsed_args = self.check_parser(self.cmd, arglist, []) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, parsed_args) + self.assertIn( + 'The delete_on_termination key of --block-device ', str(ex)) + + def test_server_create_with_block_device_tag_pre_v242(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.41') + + block_device = \ + f'uuid={self.volume.name},tag=foo,boot_index=1' + arglist = [ + '--image', 'image1', + '--flavor', self.flavor.id, + '--block-device', block_device, + self.new_server.name, + ] + parsed_args = self.check_parser(self.cmd, arglist, []) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, parsed_args) + self.assertIn( + '--os-compute-api-version 2.42 or greater is required', + str(ex)) + + def test_server_create_with_block_device_volume_type_pre_v267(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.66') + + block_device = f'uuid={self.volume.name},volume_type=foo,boot_index=1' + arglist = [ + '--image', 'image1', + '--flavor', self.flavor.id, + '--block-device', block_device, + self.new_server.name, + ] + parsed_args = self.check_parser(self.cmd, arglist, []) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, parsed_args) + self.assertIn( + '--os-compute-api-version 2.67 or greater is required', + str(ex)) + def test_server_create_with_block_device_mapping(self): arglist = [ '--image', 'image1', diff --git a/releasenotes/notes/add-missing-server-create-opts-d5e32bd743e9e132.yaml b/releasenotes/notes/add-missing-server-create-opts-d5e32bd743e9e132.yaml index b82a9d30d1..951d944dd4 100644 --- a/releasenotes/notes/add-missing-server-create-opts-d5e32bd743e9e132.yaml +++ b/releasenotes/notes/add-missing-server-create-opts-d5e32bd743e9e132.yaml @@ -6,3 +6,12 @@ features: - ``--snapshot`` - ``--ephemeral`` - ``--swap`` + - ``--block-device`` +deprecations: + - | + The ``--block-device-mapping`` option of the ``server create`` command + has been deprecated in favour of ``--block-device``. The format of the + ``--block-device-mapping`` option is based on the limited "BDM v1" + format for block device maps introduced way back in the v1 nova API. The + ``--block-device`` option instead exposes the richer key-value based + "BDM v2" format. From 2bdf34dcc3f8957ff78709467197b5fcb44e07c3 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 21 Jan 2021 11:25:08 +0000 Subject: [PATCH 2344/3095] compute: Auto-configure shared/block live migration API microversion 2.25 introduced the 'block_migration=auto' value for the os-migrateLive server action. This is a sensible default that we should use, allowing users to avoid stating one of the '--block-migration' or '--shared-migration' parameters explicitly. While we're here, we take the opportunity to fix up some formatting in the function, which is really rather messy. Change-Id: Ieedc77d6dc3d4a3cd93b29672faa97dd4e8c1185 Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server.py | 87 +++++++++++++------ .../tests/unit/compute/v2/test_server.py | 45 +++++----- ...block-live-migration-437d461c914f8f2f.yaml | 6 ++ 3 files changed, 90 insertions(+), 48 deletions(-) create mode 100644 releasenotes/notes/auto-configure-block-live-migration-437d461c914f8f2f.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index b1a86a23df..c42486d9c7 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -2457,9 +2457,11 @@ def get_parser(self, prog_name): '--live-migration', dest='live_migration', action='store_true', - help=_('Live migrate the server. Use the ``--host`` option to ' - 'specify a target host for the migration which will be ' - 'validated by the scheduler.'), + help=_( + 'Live migrate the server; use the ``--host`` option to ' + 'specify a target host for the migration which will be ' + 'validated by the scheduler' + ), ) # The --live and --host options are mutually exclusive ways of asking # for a target host during a live migration. @@ -2469,37 +2471,48 @@ def get_parser(self, prog_name): host_group.add_argument( '--live', metavar='', - help=_('**Deprecated** This option is problematic in that it ' - 'requires a host and prior to compute API version 2.30, ' - 'specifying a host during live migration will bypass ' - 'validation by the scheduler which could result in ' - 'failures to actually migrate the server to the specified ' - 'host or over-subscribe the host. Use the ' - '``--live-migration`` option instead. If both this option ' - 'and ``--live-migration`` are used, ``--live-migration`` ' - 'takes priority.'), + help=_( + '**Deprecated** This option is problematic in that it ' + 'requires a host and prior to compute API version 2.30, ' + 'specifying a host during live migration will bypass ' + 'validation by the scheduler which could result in ' + 'failures to actually migrate the server to the specified ' + 'host or over-subscribe the host. Use the ' + '``--live-migration`` option instead. If both this option ' + 'and ``--live-migration`` are used, ``--live-migration`` ' + 'takes priority.' + ), ) host_group.add_argument( '--host', metavar='', - help=_('Migrate the server to the specified host. Requires ' - '``--os-compute-api-version`` 2.30 or greater when used ' - 'with the ``--live-migration`` option, otherwise requires ' - '``--os-compute-api-version`` 2.56 or greater.'), + help=_( + 'Migrate the server to the specified host. ' + '(supported with --os-compute-api-version 2.30 or above ' + 'when used with the --live-migration option) ' + '(supported with --os-compute-api-version 2.56 or above ' + 'when used without the --live-migration option)' + ), ) migration_group = parser.add_mutually_exclusive_group() migration_group.add_argument( '--shared-migration', dest='block_migration', action='store_false', - default=False, - help=_('Perform a shared live migration (default)'), + default=None, + help=_( + 'Perform a shared live migration ' + '(default before --os-compute-api-version 2.25, auto after)' + ), ) migration_group.add_argument( '--block-migration', dest='block_migration', action='store_true', - help=_('Perform a block live migration'), + help=_( + 'Perform a block live migration ' + '(auto-configured from --os-compute-api-version 2.25)' + ), ) disk_group = parser.add_mutually_exclusive_group() disk_group.add_argument( @@ -2513,8 +2526,9 @@ def get_parser(self, prog_name): dest='disk_overcommit', action='store_false', default=False, - help=_('Do not over-commit disk on the' - ' destination host (default)'), + help=_( + 'Do not over-commit disk on the destination host (default)' + ), ) parser.add_argument( '--wait', @@ -2549,9 +2563,21 @@ def _show_progress(progress): if parsed_args.live or parsed_args.live_migration: # Always log a warning if --live is used. self._log_warning_for_live(parsed_args) - kwargs = { - 'block_migration': parsed_args.block_migration - } + + kwargs = {} + + block_migration = parsed_args.block_migration + if block_migration is None: + if ( + compute_client.api_version < + api_versions.APIVersion('2.25') + ): + block_migration = False + else: + block_migration = 'auto' + + kwargs['block_migration'] = block_migration + # Prefer --live-migration over --live if both are specified. if parsed_args.live_migration: # Technically we could pass a non-None host with @@ -2560,12 +2586,16 @@ def _show_progress(progress): # want to support, so if the user is using --live-migration # and --host, we want to enforce that they are using version # 2.30 or greater. - if (parsed_args.host and - compute_client.api_version < - api_versions.APIVersion('2.30')): + if ( + parsed_args.host and + compute_client.api_version < + api_versions.APIVersion('2.30') + ): raise exceptions.CommandError( '--os-compute-api-version 2.30 or greater is required ' - 'when using --host') + 'when using --host' + ) + # The host parameter is required in the API even if None. kwargs['host'] = parsed_args.host else: @@ -2573,6 +2603,7 @@ def _show_progress(progress): if compute_client.api_version < api_versions.APIVersion('2.25'): kwargs['disk_over_commit'] = parsed_args.disk_overcommit + server.live_migrate(**kwargs) else: if parsed_args.block_migration or parsed_args.disk_overcommit: diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 0548924d27..5de6d006ca 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -4524,7 +4524,7 @@ def test_server_migrate_no_options(self): ] verifylist = [ ('live', None), - ('block_migration', False), + ('block_migration', None), ('disk_overcommit', False), ('wait', False), ] @@ -4547,7 +4547,7 @@ def test_server_migrate_with_host_2_56(self): ('live', None), ('live_migration', False), ('host', 'fakehost'), - ('block_migration', False), + ('block_migration', None), ('disk_overcommit', False), ('wait', False), ] @@ -4588,7 +4588,7 @@ def test_server_migrate_with_disk_overcommit(self): ] verifylist = [ ('live', None), - ('block_migration', False), + ('block_migration', None), ('disk_overcommit', True), ('wait', False), ] @@ -4611,7 +4611,7 @@ def test_server_migrate_with_host_pre_2_56(self): ('live', None), ('live_migration', False), ('host', 'fakehost'), - ('block_migration', False), + ('block_migration', None), ('disk_overcommit', False), ('wait', False), ] @@ -4637,7 +4637,7 @@ def test_server_live_migrate(self): ('live', 'fakehost'), ('live_migration', False), ('host', None), - ('block_migration', False), + ('block_migration', None), ('disk_overcommit', False), ('wait', False), ] @@ -4670,7 +4670,7 @@ def test_server_live_migrate_host_pre_2_30(self): ('live', None), ('live_migration', True), ('host', 'fakehost'), - ('block_migration', False), + ('block_migration', None), ('disk_overcommit', False), ('wait', False), ] @@ -4696,7 +4696,7 @@ def test_server_live_migrate_no_host(self): ('live', None), ('live_migration', True), ('host', None), - ('block_migration', False), + ('block_migration', None), ('disk_overcommit', False), ('wait', False), ] @@ -4724,7 +4724,7 @@ def test_server_live_migrate_with_host(self): ('live', None), ('live_migration', True), ('host', 'fakehost'), - ('block_migration', False), + ('block_migration', None), ('disk_overcommit', False), ('wait', False), ] @@ -4736,9 +4736,10 @@ def test_server_live_migrate_with_host(self): result = self.cmd.take_action(parsed_args) self.servers_mock.get.assert_called_with(self.server.id) - # No disk_overcommit with microversion >= 2.25. - self.server.live_migrate.assert_called_with(block_migration=False, - host='fakehost') + # No disk_overcommit and block_migration defaults to auto with + # microversion >= 2.25 + self.server.live_migrate.assert_called_with( + block_migration='auto', host='fakehost') self.assertNotCalled(self.servers_mock.migrate) self.assertIsNone(result) @@ -4753,7 +4754,7 @@ def test_server_live_migrate_without_host_override_live(self): ('live', 'fakehost'), ('live_migration', True), ('host', None), - ('block_migration', False), + ('block_migration', None), ('disk_overcommit', False), ('wait', False), ] @@ -4779,8 +4780,9 @@ def test_server_live_migrate_live_and_host_mutex(self): arglist = [ '--live', 'fakehost', '--host', 'fakehost', self.server.id, ] - self.assertRaises(utils.ParserException, - self.check_parser, self.cmd, arglist, verify_args=[]) + self.assertRaises( + utils.ParserException, + self.check_parser, self.cmd, arglist, verify_args=[]) def test_server_block_live_migrate(self): arglist = [ @@ -4812,7 +4814,7 @@ def test_server_live_migrate_with_disk_overcommit(self): ] verifylist = [ ('live', 'fakehost'), - ('block_migration', False), + ('block_migration', None), ('disk_overcommit', True), ('wait', False), ] @@ -4861,7 +4863,7 @@ def test_server_live_migrate_225(self): ] verifylist = [ ('live', 'fakehost'), - ('block_migration', False), + ('block_migration', None), ('wait', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -4872,8 +4874,11 @@ def test_server_live_migrate_225(self): result = self.cmd.take_action(parsed_args) self.servers_mock.get.assert_called_with(self.server.id) - self.server.live_migrate.assert_called_with(block_migration=False, - host='fakehost') + # No disk_overcommit and block_migration defaults to auto with + # microversion >= 2.25 + self.server.live_migrate.assert_called_with( + block_migration='auto', + host='fakehost') self.assertNotCalled(self.servers_mock.migrate) self.assertIsNone(result) @@ -4884,7 +4889,7 @@ def test_server_migrate_with_wait(self, mock_wait_for_status): ] verifylist = [ ('live', None), - ('block_migration', False), + ('block_migration', None), ('disk_overcommit', False), ('wait', True), ] @@ -4904,7 +4909,7 @@ def test_server_migrate_with_wait_fails(self, mock_wait_for_status): ] verifylist = [ ('live', None), - ('block_migration', False), + ('block_migration', None), ('disk_overcommit', False), ('wait', True), ] diff --git a/releasenotes/notes/auto-configure-block-live-migration-437d461c914f8f2f.yaml b/releasenotes/notes/auto-configure-block-live-migration-437d461c914f8f2f.yaml new file mode 100644 index 0000000000..e1ff187e62 --- /dev/null +++ b/releasenotes/notes/auto-configure-block-live-migration-437d461c914f8f2f.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + The ``server migrate`` command will now automatically determine whether + to use block or shared migration during a live migration operation. This + requires Compute API microversion 2.25 or greater. From 8868c77a201703edaded5d06aa1734265431f786 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 21 Jan 2021 11:45:38 +0000 Subject: [PATCH 2345/3095] compute: Stop silently ignore --(no-)disk-overcommit These options are not supported from Nova API microversion 2.25 and above. This can be a source of confusion. Start warning, with an eye on erroring out in the future. Change-Id: I53f27eb3e3c1a84d0d77a1672c008d0e8bb8536f Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server.py | 15 +++++++- .../tests/unit/compute/v2/test_server.py | 34 +++++++++++++++++++ ...n-on-disk-overcommit-087ae46f12d74693.yaml | 9 +++++ 3 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/warn-on-disk-overcommit-087ae46f12d74693.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index c42486d9c7..111c4a6b04 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -2519,7 +2519,10 @@ def get_parser(self, prog_name): '--disk-overcommit', action='store_true', default=False, - help=_('Allow disk over-commit on the destination host'), + help=_( + 'Allow disk over-commit on the destination host' + '(supported with --os-compute-api-version 2.24 or below)' + ), ) disk_group.add_argument( '--no-disk-overcommit', @@ -2528,6 +2531,7 @@ def get_parser(self, prog_name): default=False, help=_( 'Do not over-commit disk on the destination host (default)' + '(supported with --os-compute-api-version 2.24 or below)' ), ) parser.add_argument( @@ -2603,6 +2607,15 @@ def _show_progress(progress): if compute_client.api_version < api_versions.APIVersion('2.25'): kwargs['disk_over_commit'] = parsed_args.disk_overcommit + elif parsed_args.disk_overcommit is not None: + # TODO(stephenfin): Raise an error here in OSC 7.0 + msg = _( + 'The --disk-overcommit and --no-disk-overcommit ' + 'options are only supported by ' + '--os-compute-api-version 2.24 or below; this will ' + 'be an error in a future release' + ) + self.log.warning(msg) server.live_migrate(**kwargs) else: diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 5de6d006ca..9afc4cebe8 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -4832,6 +4832,40 @@ def test_server_live_migrate_with_disk_overcommit(self): self.assertNotCalled(self.servers_mock.migrate) self.assertIsNone(result) + def test_server_live_migrate_with_disk_overcommit_post_v224(self): + arglist = [ + '--live-migration', + '--disk-overcommit', + self.server.id, + ] + verifylist = [ + ('live', None), + ('live_migration', True), + ('block_migration', None), + ('disk_overcommit', True), + ('wait', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.25') + + with mock.patch.object(self.cmd.log, 'warning') as mock_warning: + result = self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + # There should be no 'disk_over_commit' value present + self.server.live_migrate.assert_called_with( + block_migration='auto', + host=None) + self.assertNotCalled(self.servers_mock.migrate) + self.assertIsNone(result) + # A warning should have been logged for using --disk-overcommit. + mock_warning.assert_called_once() + self.assertIn( + 'The --disk-overcommit and --no-disk-overcommit options ', + str(mock_warning.call_args[0][0])) + def test_server_live_migrate_with_false_value_options(self): arglist = [ '--live', 'fakehost', '--no-disk-overcommit', diff --git a/releasenotes/notes/warn-on-disk-overcommit-087ae46f12d74693.yaml b/releasenotes/notes/warn-on-disk-overcommit-087ae46f12d74693.yaml new file mode 100644 index 0000000000..766dbab320 --- /dev/null +++ b/releasenotes/notes/warn-on-disk-overcommit-087ae46f12d74693.yaml @@ -0,0 +1,9 @@ +--- +upgrade: + - | + A warning will now be emitted when using the ``--disk-overcommit`` + or ``--no-disk-overcommit`` flags of the ``server migrate`` command on + Compute API microversion 2.25 or greater. This feature is only supported + before this microversion and previously the flag was silently ignored on + newer microversions. This warning will become an error in a future + release. From 6f3969a0c8a608236a6f7258aa213c12af060a9d Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 21 Jan 2021 12:06:57 +0000 Subject: [PATCH 2346/3095] compute: Deprecate 'server create --file' The parameter isn't actually deprecated, since we need to support older API microversion, however, we now emit an error if someone attempts to boot a server with the wrong microversion. This would happen server-side anyway since this parameter was removed entirely in API microversion 2.57. Change-Id: I73864ccbf5bf181fecf505ca168c1a35a8b0af3a Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server.py | 12 +++++++++++- ...d-server-create-file-option-80246b13bd3c1b43.yaml | 7 +++++++ 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/deprecated-server-create-file-option-80246b13bd3c1b43.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 111c4a6b04..48f5b7cf16 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -989,8 +989,9 @@ def get_parser(self, prog_name): action='append', default=[], help=_( - 'File to inject into image before boot ' + 'File(s) to inject into image before boot ' '(repeat option to set multiple files)' + '(supported by --os-compute-api-version 2.57 or below)' ), ) parser.add_argument( @@ -1201,6 +1202,15 @@ def _match_image(image_api, wanted_properties): flavor = utils.find_resource( compute_client.flavors, parsed_args.flavor) + if parsed_args.file: + if compute_client.api_version >= api_versions.APIVersion('2.57'): + msg = _( + 'Personality files are deprecated and are not supported ' + 'for --os-compute-api-version greater than 2.56; use ' + 'user data instead' + ) + raise exceptions.CommandError(msg) + files = {} for f in parsed_args.file: dst, src = f.split('=', 1) diff --git a/releasenotes/notes/deprecated-server-create-file-option-80246b13bd3c1b43.yaml b/releasenotes/notes/deprecated-server-create-file-option-80246b13bd3c1b43.yaml new file mode 100644 index 0000000000..93de724461 --- /dev/null +++ b/releasenotes/notes/deprecated-server-create-file-option-80246b13bd3c1b43.yaml @@ -0,0 +1,7 @@ +--- +upgrade: + - | + The ``server create`` command will now error out if the ``--file`` option + is specified alongside ``--os-compute-api-version`` of ``2.57`` or greater. + This reflects the removal of this feature from the compute service in this + microversion. From 70480fa86236f7de583c7b098cc53f0acedfd91d Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 21 Jan 2021 12:26:13 +0000 Subject: [PATCH 2347/3095] compute: Remove deprecated 'server migrate --live' option It's been long enough. Time to remove this. Change-Id: I37ef09eca0db9286544a4b0bb33f845311baa9b2 Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server.py | 75 ++----- .../tests/unit/compute/v2/test_server.py | 203 ++++-------------- ...-migrate-live-option-28ec43ee210124dc.yaml | 8 + 3 files changed, 67 insertions(+), 219 deletions(-) create mode 100644 releasenotes/notes/remove-deprecated-server-migrate-live-option-28ec43ee210124dc.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 48f5b7cf16..9838ed5480 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -2473,27 +2473,7 @@ def get_parser(self, prog_name): 'validated by the scheduler' ), ) - # The --live and --host options are mutually exclusive ways of asking - # for a target host during a live migration. - host_group = parser.add_mutually_exclusive_group() - # TODO(mriedem): Remove --live in the next major version bump after - # the Train release. - host_group.add_argument( - '--live', - metavar='', - help=_( - '**Deprecated** This option is problematic in that it ' - 'requires a host and prior to compute API version 2.30, ' - 'specifying a host during live migration will bypass ' - 'validation by the scheduler which could result in ' - 'failures to actually migrate the server to the specified ' - 'host or over-subscribe the host. Use the ' - '``--live-migration`` option instead. If both this option ' - 'and ``--live-migration`` are used, ``--live-migration`` ' - 'takes priority.' - ), - ) - host_group.add_argument( + parser.add_argument( '--host', metavar='', help=_( @@ -2551,15 +2531,6 @@ def get_parser(self, prog_name): ) return parser - def _log_warning_for_live(self, parsed_args): - if parsed_args.live: - # NOTE(mriedem): The --live option requires a host and if - # --os-compute-api-version is less than 2.30 it will forcefully - # bypass the scheduler which is dangerous. - self.log.warning(_( - 'The --live option has been deprecated. Please use the ' - '--live-migration option instead.')) - def take_action(self, parsed_args): def _show_progress(progress): @@ -2573,11 +2544,8 @@ def _show_progress(progress): compute_client.servers, parsed_args.server, ) - # Check for live migration. - if parsed_args.live or parsed_args.live_migration: - # Always log a warning if --live is used. - self._log_warning_for_live(parsed_args) + if parsed_args.live_migration: kwargs = {} block_migration = parsed_args.block_migration @@ -2592,28 +2560,23 @@ def _show_progress(progress): kwargs['block_migration'] = block_migration - # Prefer --live-migration over --live if both are specified. - if parsed_args.live_migration: - # Technically we could pass a non-None host with - # --os-compute-api-version < 2.30 but that is the same thing - # as the --live option bypassing the scheduler which we don't - # want to support, so if the user is using --live-migration - # and --host, we want to enforce that they are using version - # 2.30 or greater. - if ( - parsed_args.host and - compute_client.api_version < - api_versions.APIVersion('2.30') - ): - raise exceptions.CommandError( - '--os-compute-api-version 2.30 or greater is required ' - 'when using --host' - ) + # Technically we could pass a non-None host with + # --os-compute-api-version < 2.30 but that is the same thing + # as the --live option bypassing the scheduler which we don't + # want to support, so if the user is using --live-migration + # and --host, we want to enforce that they are using version + # 2.30 or greater. + if ( + parsed_args.host and + compute_client.api_version < api_versions.APIVersion('2.30') + ): + raise exceptions.CommandError( + '--os-compute-api-version 2.30 or greater is required ' + 'when using --host' + ) - # The host parameter is required in the API even if None. - kwargs['host'] = parsed_args.host - else: - kwargs['host'] = parsed_args.live + # The host parameter is required in the API even if None. + kwargs['host'] = parsed_args.host if compute_client.api_version < api_versions.APIVersion('2.25'): kwargs['disk_over_commit'] = parsed_args.disk_overcommit @@ -2628,7 +2591,7 @@ def _show_progress(progress): self.log.warning(msg) server.live_migrate(**kwargs) - else: + else: # cold migration if parsed_args.block_migration or parsed_args.disk_overcommit: raise exceptions.CommandError( "--live-migration must be specified if " diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 9afc4cebe8..ced5d458c2 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -4523,7 +4523,7 @@ def test_server_migrate_no_options(self): self.server.id, ] verifylist = [ - ('live', None), + ('live_migration', False), ('block_migration', None), ('disk_overcommit', False), ('wait', False), @@ -4544,7 +4544,6 @@ def test_server_migrate_with_host_2_56(self): '--host', 'fakehost', self.server.id, ] verifylist = [ - ('live', None), ('live_migration', False), ('host', 'fakehost'), ('block_migration', None), @@ -4568,7 +4567,7 @@ def test_server_migrate_with_block_migration(self): '--block-migration', self.server.id, ] verifylist = [ - ('live', None), + ('live_migration', False), ('block_migration', True), ('disk_overcommit', False), ('wait', False), @@ -4587,7 +4586,7 @@ def test_server_migrate_with_disk_overcommit(self): '--disk-overcommit', self.server.id, ] verifylist = [ - ('live', None), + ('live_migration', False), ('block_migration', None), ('disk_overcommit', True), ('wait', False), @@ -4608,7 +4607,6 @@ def test_server_migrate_with_host_pre_2_56(self): '--host', 'fakehost', self.server.id, ] verifylist = [ - ('live', None), ('live_migration', False), ('host', 'fakehost'), ('block_migration', None), @@ -4630,70 +4628,11 @@ def test_server_migrate_with_host_pre_2_56(self): self.assertNotCalled(self.servers_mock.migrate) def test_server_live_migrate(self): - arglist = [ - '--live', 'fakehost', self.server.id, - ] - verifylist = [ - ('live', 'fakehost'), - ('live_migration', False), - ('host', None), - ('block_migration', None), - ('disk_overcommit', False), - ('wait', False), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - self.app.client_manager.compute.api_version = \ - api_versions.APIVersion('2.24') - - with mock.patch.object(self.cmd.log, 'warning') as mock_warning: - result = self.cmd.take_action(parsed_args) - - self.servers_mock.get.assert_called_with(self.server.id) - self.server.live_migrate.assert_called_with(block_migration=False, - disk_over_commit=False, - host='fakehost') - self.assertNotCalled(self.servers_mock.migrate) - self.assertIsNone(result) - # A warning should have been logged for using --live. - mock_warning.assert_called_once() - self.assertIn('The --live option has been deprecated.', - str(mock_warning.call_args[0][0])) - - def test_server_live_migrate_host_pre_2_30(self): - # Tests that the --host option is not supported for --live-migration - # before microversion 2.30 (the test defaults to 2.1). - arglist = [ - '--live-migration', '--host', 'fakehost', self.server.id, - ] - verifylist = [ - ('live', None), - ('live_migration', True), - ('host', 'fakehost'), - ('block_migration', None), - ('disk_overcommit', False), - ('wait', False), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - ex = self.assertRaises(exceptions.CommandError, self.cmd.take_action, - parsed_args) - - # Make sure it's the error we expect. - self.assertIn('--os-compute-api-version 2.30 or greater is required ' - 'when using --host', str(ex)) - - self.servers_mock.get.assert_called_with(self.server.id) - self.assertNotCalled(self.servers_mock.live_migrate) - self.assertNotCalled(self.servers_mock.migrate) - - def test_server_live_migrate_no_host(self): # Tests the --live-migration option without --host or --live. arglist = [ '--live-migration', self.server.id, ] verifylist = [ - ('live', None), ('live_migration', True), ('host', None), ('block_migration', None), @@ -4702,26 +4641,22 @@ def test_server_live_migrate_no_host(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - with mock.patch.object(self.cmd.log, 'warning') as mock_warning: - result = self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) self.servers_mock.get.assert_called_with(self.server.id) - self.server.live_migrate.assert_called_with(block_migration=False, - disk_over_commit=False, - host=None) + self.server.live_migrate.assert_called_with( + block_migration=False, + disk_over_commit=False, + host=None) self.assertNotCalled(self.servers_mock.migrate) self.assertIsNone(result) - # Since --live wasn't used a warning shouldn't have been logged. - mock_warning.assert_not_called() def test_server_live_migrate_with_host(self): - # Tests the --live-migration option with --host but no --live. # This requires --os-compute-api-version >= 2.30 so the test uses 2.30. arglist = [ '--live-migration', '--host', 'fakehost', self.server.id, ] verifylist = [ - ('live', None), ('live_migration', True), ('host', 'fakehost'), ('block_migration', None), @@ -4743,53 +4678,42 @@ def test_server_live_migrate_with_host(self): self.assertNotCalled(self.servers_mock.migrate) self.assertIsNone(result) - def test_server_live_migrate_without_host_override_live(self): - # Tests the --live-migration option without --host and with --live. - # The --live-migration option will take precedence and a warning is - # logged for using --live. + def test_server_live_migrate_with_host_pre_v230(self): + # Tests that the --host option is not supported for --live-migration + # before microversion 2.30 (the test defaults to 2.1). arglist = [ - '--live', 'fakehost', '--live-migration', self.server.id, + '--live-migration', '--host', 'fakehost', self.server.id, ] verifylist = [ - ('live', 'fakehost'), ('live_migration', True), - ('host', None), + ('host', 'fakehost'), ('block_migration', None), ('disk_overcommit', False), ('wait', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - with mock.patch.object(self.cmd.log, 'warning') as mock_warning: - result = self.cmd.take_action(parsed_args) + ex = self.assertRaises( + exceptions.CommandError, self.cmd.take_action, + parsed_args) + + # Make sure it's the error we expect. + self.assertIn( + '--os-compute-api-version 2.30 or greater is required ' + 'when using --host', str(ex)) self.servers_mock.get.assert_called_with(self.server.id) - self.server.live_migrate.assert_called_with(block_migration=False, - disk_over_commit=False, - host=None) + self.assertNotCalled(self.servers_mock.live_migrate) self.assertNotCalled(self.servers_mock.migrate) - self.assertIsNone(result) - # A warning should have been logged for using --live. - mock_warning.assert_called_once() - self.assertIn('The --live option has been deprecated.', - str(mock_warning.call_args[0][0])) - - def test_server_live_migrate_live_and_host_mutex(self): - # Tests specifying both the --live and --host options which are in a - # mutex group so argparse should fail. - arglist = [ - '--live', 'fakehost', '--host', 'fakehost', self.server.id, - ] - self.assertRaises( - utils.ParserException, - self.check_parser, self.cmd, arglist, verify_args=[]) def test_server_block_live_migrate(self): arglist = [ - '--live', 'fakehost', '--block-migration', self.server.id, + '--live-migration', + '--block-migration', + self.server.id, ] verifylist = [ - ('live', 'fakehost'), + ('live_migration', True), ('block_migration', True), ('disk_overcommit', False), ('wait', False), @@ -4802,18 +4726,21 @@ def test_server_block_live_migrate(self): result = self.cmd.take_action(parsed_args) self.servers_mock.get.assert_called_with(self.server.id) - self.server.live_migrate.assert_called_with(block_migration=True, - disk_over_commit=False, - host='fakehost') + self.server.live_migrate.assert_called_with( + block_migration=True, + disk_over_commit=False, + host=None) self.assertNotCalled(self.servers_mock.migrate) self.assertIsNone(result) def test_server_live_migrate_with_disk_overcommit(self): arglist = [ - '--live', 'fakehost', '--disk-overcommit', self.server.id, + '--live-migration', + '--disk-overcommit', + self.server.id, ] verifylist = [ - ('live', 'fakehost'), + ('live_migration', True), ('block_migration', None), ('disk_overcommit', True), ('wait', False), @@ -4826,9 +4753,10 @@ def test_server_live_migrate_with_disk_overcommit(self): result = self.cmd.take_action(parsed_args) self.servers_mock.get.assert_called_with(self.server.id) - self.server.live_migrate.assert_called_with(block_migration=False, - disk_over_commit=True, - host='fakehost') + self.server.live_migrate.assert_called_with( + block_migration=False, + disk_over_commit=True, + host=None) self.assertNotCalled(self.servers_mock.migrate) self.assertIsNone(result) @@ -4839,7 +4767,6 @@ def test_server_live_migrate_with_disk_overcommit_post_v224(self): self.server.id, ] verifylist = [ - ('live', None), ('live_migration', True), ('block_migration', None), ('disk_overcommit', True), @@ -4866,63 +4793,13 @@ def test_server_live_migrate_with_disk_overcommit_post_v224(self): 'The --disk-overcommit and --no-disk-overcommit options ', str(mock_warning.call_args[0][0])) - def test_server_live_migrate_with_false_value_options(self): - arglist = [ - '--live', 'fakehost', '--no-disk-overcommit', - '--shared-migration', self.server.id, - ] - verifylist = [ - ('live', 'fakehost'), - ('block_migration', False), - ('disk_overcommit', False), - ('wait', False), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - self.app.client_manager.compute.api_version = \ - api_versions.APIVersion('2.24') - - result = self.cmd.take_action(parsed_args) - - self.servers_mock.get.assert_called_with(self.server.id) - self.server.live_migrate.assert_called_with(block_migration=False, - disk_over_commit=False, - host='fakehost') - self.assertNotCalled(self.servers_mock.migrate) - self.assertIsNone(result) - - def test_server_live_migrate_225(self): - arglist = [ - '--live', 'fakehost', self.server.id, - ] - verifylist = [ - ('live', 'fakehost'), - ('block_migration', None), - ('wait', False), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - self.app.client_manager.compute.api_version = \ - api_versions.APIVersion('2.25') - - result = self.cmd.take_action(parsed_args) - - self.servers_mock.get.assert_called_with(self.server.id) - # No disk_overcommit and block_migration defaults to auto with - # microversion >= 2.25 - self.server.live_migrate.assert_called_with( - block_migration='auto', - host='fakehost') - self.assertNotCalled(self.servers_mock.migrate) - self.assertIsNone(result) - @mock.patch.object(common_utils, 'wait_for_status', return_value=True) def test_server_migrate_with_wait(self, mock_wait_for_status): arglist = [ '--wait', self.server.id, ] verifylist = [ - ('live', None), + ('live_migration', False), ('block_migration', None), ('disk_overcommit', False), ('wait', True), @@ -4942,7 +4819,7 @@ def test_server_migrate_with_wait_fails(self, mock_wait_for_status): '--wait', self.server.id, ] verifylist = [ - ('live', None), + ('live_migration', False), ('block_migration', None), ('disk_overcommit', False), ('wait', True), diff --git a/releasenotes/notes/remove-deprecated-server-migrate-live-option-28ec43ee210124dc.yaml b/releasenotes/notes/remove-deprecated-server-migrate-live-option-28ec43ee210124dc.yaml new file mode 100644 index 0000000000..bc31aa2699 --- /dev/null +++ b/releasenotes/notes/remove-deprecated-server-migrate-live-option-28ec43ee210124dc.yaml @@ -0,0 +1,8 @@ +--- +upgrade: + - | + The deprecated ``--live`` option of the ``server migrate`` command has + been removed. This was problematic as it required a host argument and + would result in a forced live migration to a host, bypassing the + scheduler. It has been replaced by a ``--live-migration`` option and + optional ``--host`` option. From 119d2fae2567285b9149b2c737d7d4452b59288c Mon Sep 17 00:00:00 2001 From: Artem Goncharov Date: Tue, 9 Jun 2020 11:35:46 +0200 Subject: [PATCH 2348/3095] project cleanup New implementation of the project cleanup based on the sdk.project_cleanup. It is implemented as an additional OSC operation and will ideally obsolete the `openstack project purge` giving flexibility to extend services support, parallelization, filters, etc. Change-Id: Ie08877f182379f73e5ec5ad4daaf84b3092c829c --- .../cli/command-objects/project-cleanup.rst | 12 ++ doc/source/cli/commands.rst | 1 + openstackclient/common/project_cleanup.py | 140 ++++++++++++++ .../tests/unit/common/test_project_cleanup.py | 183 ++++++++++++++++++ .../add-project-cleanup-beb08c9df3c95b24.yaml | 6 + setup.cfg | 1 + 6 files changed, 343 insertions(+) create mode 100644 doc/source/cli/command-objects/project-cleanup.rst create mode 100644 openstackclient/common/project_cleanup.py create mode 100644 openstackclient/tests/unit/common/test_project_cleanup.py create mode 100644 releasenotes/notes/add-project-cleanup-beb08c9df3c95b24.yaml diff --git a/doc/source/cli/command-objects/project-cleanup.rst b/doc/source/cli/command-objects/project-cleanup.rst new file mode 100644 index 0000000000..e76e538948 --- /dev/null +++ b/doc/source/cli/command-objects/project-cleanup.rst @@ -0,0 +1,12 @@ +=============== +project cleanup +=============== + +Clean resources associated with a specific project based on OpenStackSDK +implementation + +Block Storage v2, v3; Compute v2; Network v2; DNS v2; Orchestrate v1 + + +.. autoprogram-cliff:: openstack.common + :command: project cleanup diff --git a/doc/source/cli/commands.rst b/doc/source/cli/commands.rst index 0dfac00bdc..94a0b5a638 100644 --- a/doc/source/cli/commands.rst +++ b/doc/source/cli/commands.rst @@ -270,6 +270,7 @@ Those actions with an opposite action are noted in parens if applicable. * ``pause`` (``unpause``) - stop one or more servers and leave them in memory * ``query`` - Query resources by Elasticsearch query string or json format DSL. * ``purge`` - clean resources associated with a specific project +* ``cleanup`` - flexible clean resources associated with a specific project * ``reboot`` - forcibly reboot a server * ``rebuild`` - rebuild a server using (most of) the same arguments as in the original create * ``remove`` (``add``) - remove an object from a group of objects diff --git a/openstackclient/common/project_cleanup.py b/openstackclient/common/project_cleanup.py new file mode 100644 index 0000000000..f253635495 --- /dev/null +++ b/openstackclient/common/project_cleanup.py @@ -0,0 +1,140 @@ +# Copyright 2020 OpenStack Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +import getpass +import logging +import os +import queue + +from cliff.formatters import table +from osc_lib.command import command + +from openstackclient.i18n import _ +from openstackclient.identity import common as identity_common + + +LOG = logging.getLogger(__name__) + + +def ask_user_yesno(msg, default=True): + """Ask user Y/N question + + :param str msg: question text + :param bool default: default value + :return bool: User choice + """ + while True: + answer = getpass._raw_input( + '{} [{}]: '.format(msg, 'y/N' if not default else 'Y/n')) + if answer in ('y', 'Y', 'yes'): + return True + elif answer in ('n', 'N', 'no'): + return False + + +class ProjectCleanup(command.Command): + _description = _("Clean resources associated with a project") + + def get_parser(self, prog_name): + parser = super(ProjectCleanup, self).get_parser(prog_name) + parser.add_argument( + '--dry-run', + action='store_true', + help=_("List a project's resources") + ) + project_group = parser.add_mutually_exclusive_group(required=True) + project_group.add_argument( + '--auth-project', + action='store_true', + help=_('Delete resources of the project used to authenticate') + ) + project_group.add_argument( + '--project', + metavar='', + help=_('Project to clean (name or ID)') + ) + parser.add_argument( + '--created-before', + metavar='', + help=_('Drop resources created before the given time') + ) + parser.add_argument( + '--updated-before', + metavar='', + help=_('Drop resources updated before the given time') + ) + identity_common.add_project_domain_option_to_parser(parser) + return parser + + def take_action(self, parsed_args): + sdk = self.app.client_manager.sdk_connection + + if parsed_args.auth_project: + project_connect = sdk + elif parsed_args.project: + project = sdk.identity.find_project( + name_or_id=parsed_args.project, + ignore_missing=False) + project_connect = sdk.connect_as_project(project) + + if project_connect: + status_queue = queue.Queue() + parsed_args.max_width = int(os.environ.get('CLIFF_MAX_TERM_WIDTH', + 0)) + parsed_args.fit_width = bool(int(os.environ.get('CLIFF_FIT_WIDTH', + 0))) + parsed_args.print_empty = False + table_fmt = table.TableFormatter() + + self.log.info('Searching resources...') + + filters = {} + if parsed_args.created_before: + filters['created_at'] = parsed_args.created_before + + if parsed_args.updated_before: + filters['updated_at'] = parsed_args.updated_before + + project_connect.project_cleanup(dry_run=True, + status_queue=status_queue, + filters=filters) + + data = [] + while not status_queue.empty(): + resource = status_queue.get_nowait() + data.append( + (type(resource).__name__, resource.id, resource.name)) + status_queue.task_done() + status_queue.join() + table_fmt.emit_list( + ('Type', 'ID', 'Name'), + data, + self.app.stdout, + parsed_args + ) + + if parsed_args.dry_run: + return + + confirm = ask_user_yesno( + _("These resources will be deleted. Are you sure"), + default=False) + + if confirm: + self.log.warning(_('Deleting resources')) + + project_connect.project_cleanup(dry_run=False, + status_queue=status_queue, + filters=filters) diff --git a/openstackclient/tests/unit/common/test_project_cleanup.py b/openstackclient/tests/unit/common/test_project_cleanup.py new file mode 100644 index 0000000000..d235aeb063 --- /dev/null +++ b/openstackclient/tests/unit/common/test_project_cleanup.py @@ -0,0 +1,183 @@ +# 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 io import StringIO +from unittest import mock + +from openstackclient.common import project_cleanup +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes +from openstackclient.tests.unit import utils as tests_utils + + +class TestProjectCleanupBase(tests_utils.TestCommand): + + def setUp(self): + super(TestProjectCleanupBase, self).setUp() + + self.app.client_manager.sdk_connection = mock.Mock() + + +class TestProjectCleanup(TestProjectCleanupBase): + + project = identity_fakes.FakeProject.create_one_project() + + def setUp(self): + super(TestProjectCleanup, self).setUp() + self.cmd = project_cleanup.ProjectCleanup(self.app, None) + + self.project_cleanup_mock = mock.Mock() + self.sdk_connect_as_project_mock = \ + mock.Mock(return_value=self.app.client_manager.sdk_connection) + self.app.client_manager.sdk_connection.project_cleanup = \ + self.project_cleanup_mock + self.app.client_manager.sdk_connection.identity.find_project = \ + mock.Mock(return_value=self.project) + self.app.client_manager.sdk_connection.connect_as_project = \ + self.sdk_connect_as_project_mock + + def test_project_no_options(self): + arglist = [] + verifylist = [] + + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_project_cleanup_with_filters(self): + arglist = [ + '--project', self.project.id, + '--created-before', '2200-01-01', + '--updated-before', '2200-01-02' + ] + verifylist = [ + ('dry_run', False), + ('auth_project', False), + ('project', self.project.id), + ('created_before', '2200-01-01'), + ('updated_before', '2200-01-02') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = None + + with mock.patch('sys.stdin', StringIO('y')): + result = self.cmd.take_action(parsed_args) + + self.sdk_connect_as_project_mock.assert_called_with( + self.project) + filters = { + 'created_at': '2200-01-01', + 'updated_at': '2200-01-02' + } + + calls = [ + mock.call(dry_run=True, status_queue=mock.ANY, filters=filters), + mock.call(dry_run=False, status_queue=mock.ANY, filters=filters) + ] + self.project_cleanup_mock.assert_has_calls(calls) + + self.assertIsNone(result) + + def test_project_cleanup_with_project(self): + arglist = [ + '--project', self.project.id, + ] + verifylist = [ + ('dry_run', False), + ('auth_project', False), + ('project', self.project.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = None + + with mock.patch('sys.stdin', StringIO('y')): + result = self.cmd.take_action(parsed_args) + + self.sdk_connect_as_project_mock.assert_called_with( + self.project) + calls = [ + mock.call(dry_run=True, status_queue=mock.ANY, filters={}), + mock.call(dry_run=False, status_queue=mock.ANY, filters={}) + ] + self.project_cleanup_mock.assert_has_calls(calls) + + self.assertIsNone(result) + + def test_project_cleanup_with_project_abort(self): + arglist = [ + '--project', self.project.id, + ] + verifylist = [ + ('dry_run', False), + ('auth_project', False), + ('project', self.project.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = None + + with mock.patch('sys.stdin', StringIO('n')): + result = self.cmd.take_action(parsed_args) + + self.sdk_connect_as_project_mock.assert_called_with( + self.project) + calls = [ + mock.call(dry_run=True, status_queue=mock.ANY, filters={}), + ] + self.project_cleanup_mock.assert_has_calls(calls) + + self.assertIsNone(result) + + def test_project_cleanup_with_dry_run(self): + arglist = [ + '--dry-run', + '--project', self.project.id, + ] + verifylist = [ + ('dry_run', True), + ('auth_project', False), + ('project', self.project.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = None + + result = self.cmd.take_action(parsed_args) + + self.sdk_connect_as_project_mock.assert_called_with( + self.project) + self.project_cleanup_mock.assert_called_once_with( + dry_run=True, status_queue=mock.ANY, filters={}) + + self.assertIsNone(result) + + def test_project_cleanup_with_auth_project(self): + self.app.client_manager.auth_ref = mock.Mock() + self.app.client_manager.auth_ref.project_id = self.project.id + arglist = [ + '--auth-project', + ] + verifylist = [ + ('dry_run', False), + ('auth_project', True), + ('project', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = None + + with mock.patch('sys.stdin', StringIO('y')): + result = self.cmd.take_action(parsed_args) + + self.sdk_connect_as_project_mock.assert_not_called() + calls = [ + mock.call(dry_run=True, status_queue=mock.ANY, filters={}), + mock.call(dry_run=False, status_queue=mock.ANY, filters={}) + ] + self.project_cleanup_mock.assert_has_calls(calls) + + self.assertIsNone(result) diff --git a/releasenotes/notes/add-project-cleanup-beb08c9df3c95b24.yaml b/releasenotes/notes/add-project-cleanup-beb08c9df3c95b24.yaml new file mode 100644 index 0000000000..58d4223d2b --- /dev/null +++ b/releasenotes/notes/add-project-cleanup-beb08c9df3c95b24.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add support for project cleanup based on the OpenStackSDK with + create/update time filters. In the long run this will replace + `openstack project purge` command. diff --git a/setup.cfg b/setup.cfg index 48384897a0..9deb0f5313 100644 --- a/setup.cfg +++ b/setup.cfg @@ -45,6 +45,7 @@ openstack.common = extension_list = openstackclient.common.extension:ListExtension extension_show = openstackclient.common.extension:ShowExtension limits_show = openstackclient.common.limits:ShowLimits + project_cleanup = openstackclient.common.project_cleanup:ProjectCleanup project_purge = openstackclient.common.project_purge:ProjectPurge quota_list = openstackclient.common.quota:ListQuota quota_set = openstackclient.common.quota:SetQuota From e8509d81ee0b541033411e744f633e769073530b Mon Sep 17 00:00:00 2001 From: Miguel Lavalle Date: Wed, 10 Feb 2021 17:40:39 -0600 Subject: [PATCH 2349/3095] Add 'address_group' type support to rbac commands Depends-On: https://review.opendev.org/c/openstack/neutron/+/772460 Change-Id: Icd5e96d180364b979d1e93fcb39f9133a41a06e5 --- openstackclient/network/v2/network_rbac.py | 22 ++++++++++++------- .../unit/network/v2/test_network_rbac.py | 6 ++++- ...ac-add-address-group-f9bb83238b5a7c1f.yaml | 4 ++++ 3 files changed, 23 insertions(+), 9 deletions(-) create mode 100644 releasenotes/notes/rbac-add-address-group-f9bb83238b5a7c1f.yaml diff --git a/openstackclient/network/v2/network_rbac.py b/openstackclient/network/v2/network_rbac.py index b88ef019e0..4984e89d56 100644 --- a/openstackclient/network/v2/network_rbac.py +++ b/openstackclient/network/v2/network_rbac.py @@ -60,6 +60,10 @@ def _get_attrs(client_manager, parsed_args): object_id = network_client.find_subnet_pool( parsed_args.rbac_object, ignore_missing=False).id + if parsed_args.type == 'address_group': + object_id = network_client.find_address_group( + parsed_args.rbac_object, + ignore_missing=False).id attrs['object_id'] = object_id @@ -100,11 +104,12 @@ def get_parser(self, prog_name): '--type', metavar="", required=True, - choices=['address_scope', 'security_group', 'subnetpool', - 'qos_policy', 'network'], + choices=['address_group', 'address_scope', 'security_group', + 'subnetpool', 'qos_policy', 'network'], help=_('Type of the object that RBAC policy ' - 'affects ("address_scope", "security_group", "subnetpool",' - ' "qos_policy" or "network")') + 'affects ("address_group", "address_scope", ' + '"security_group", "subnetpool", "qos_policy" or ' + '"network")') ) parser.add_argument( '--action', @@ -193,11 +198,12 @@ def get_parser(self, prog_name): parser.add_argument( '--type', metavar='', - choices=['address_scope', 'security_group', 'subnetpool', - 'qos_policy', 'network'], + choices=['address_group', 'address_scope', 'security_group', + 'subnetpool', 'qos_policy', 'network'], help=_('List network RBAC policies according to ' - 'given object type ("address_scope", "security_group", ' - '"subnetpool", "qos_policy" or "network")') + 'given object type ("address_group", "address_scope", ' + '"security_group", "subnetpool", "qos_policy" or ' + '"network")') ) parser.add_argument( '--action', diff --git a/openstackclient/tests/unit/network/v2/test_network_rbac.py b/openstackclient/tests/unit/network/v2/test_network_rbac.py index d7c71ea7d0..08be64c516 100644 --- a/openstackclient/tests/unit/network/v2/test_network_rbac.py +++ b/openstackclient/tests/unit/network/v2/test_network_rbac.py @@ -42,6 +42,7 @@ class TestCreateNetworkRBAC(TestNetworkRBAC): sg_object = network_fakes.FakeNetworkSecGroup.create_one_security_group() as_object = network_fakes.FakeAddressScope.create_one_address_scope() snp_object = network_fakes.FakeSubnetPool.create_one_subnet_pool() + ag_object = network_fakes.FakeAddressGroup.create_one_address_group() project = identity_fakes_v3.FakeProject.create_one_project() rbac_policy = network_fakes.FakeNetworkRBAC.create_one_network_rbac( attrs={'tenant_id': project.id, @@ -85,6 +86,8 @@ def setUp(self): return_value=self.as_object) self.network.find_subnet_pool = mock.Mock( return_value=self.snp_object) + self.network.find_address_group = mock.Mock( + return_value=self.ag_object) self.projects_mock.get.return_value = self.project def test_network_rbac_create_no_type(self): @@ -236,7 +239,8 @@ def test_network_rbac_create_all_options(self): ('qos_policy', "qos_object"), ('security_group', "sg_object"), ('subnetpool', "snp_object"), - ('address_scope', "as_object") + ('address_scope', "as_object"), + ('address_group', "ag_object") ) @ddt.unpack def test_network_rbac_create_object(self, obj_type, obj_fake_attr): diff --git a/releasenotes/notes/rbac-add-address-group-f9bb83238b5a7c1f.yaml b/releasenotes/notes/rbac-add-address-group-f9bb83238b5a7c1f.yaml new file mode 100644 index 0000000000..d25da2f005 --- /dev/null +++ b/releasenotes/notes/rbac-add-address-group-f9bb83238b5a7c1f.yaml @@ -0,0 +1,4 @@ +features: + - | + Add ``address_group`` as a valid ``--type`` value for the + ``network rbac create`` and ``network rbac list`` commands. From 16c72f8642c24e4e2d8af93698a84aced54be97a Mon Sep 17 00:00:00 2001 From: Brian Haley Date: Tue, 23 Feb 2021 18:58:24 -0500 Subject: [PATCH 2350/3095] Add --name to port list The neutron API supports filtering ports by name, but the CLI was missing support for it like it does for other networking resources. Change-Id: I4ff339e18656013218a26f045b205cb7a02dd2fb Story: #2008654 --- openstackclient/network/v2/port.py | 7 +++++++ .../tests/unit/network/v2/test_port.py | 20 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index dfdb604d55..6885e147e0 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -600,6 +600,11 @@ def get_parser(self, prog_name): metavar='', help=_("List ports according to their project (name or ID)") ) + parser.add_argument( + '--name', + metavar='', + help=_("List ports according to their name") + ) identity_common.add_project_domain_option_to_parser(parser) parser.add_argument( '--fixed-ip', @@ -667,6 +672,8 @@ def take_action(self, parsed_args): ).id filters['tenant_id'] = project_id filters['project_id'] = project_id + if parsed_args.name: + filters['name'] = parsed_args.name if parsed_args.fixed_ip: filters['fixed_ips'] = _prepare_filter_fixed_ips( self.app.client_manager, parsed_args) diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index c8bced71c3..8c5158d7c8 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -1250,6 +1250,26 @@ def test_port_list_project_domain(self): self.assertEqual(self.columns, columns) self.assertItemsEqual(self.data, list(data)) + def test_port_list_name(self): + test_name = "fakename" + arglist = [ + '--name', test_name, + ] + verifylist = [ + ('name', test_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = { + 'name': test_name, + 'fields': LIST_FIELDS_TO_RETRIEVE, + } + + self.network.ports.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertItemsEqual(self.data, list(data)) + def test_list_with_tag_options(self): arglist = [ '--tags', 'red,blue', From 7c1d6f769c4f0d2afe61410fefd8bc8f26a22980 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 4 Mar 2021 18:34:00 +0000 Subject: [PATCH 2351/3095] compute: Add functional tests for --block-device This mostly reuses the existing tests for '--block-device-mapping', which can hopefully be removed at some point in the future. This highlights two issues with the implementation of this option. Firstly, the 'boot_index' parameter is not required so don't mandate it. Secondly, and more significantly, we were defaulting the destination type for the 'image' source type to 'local'. Nova only allows you to attach a single image to local mapping [1], which means this default would only make sense if you were expecting users to use the '--block-device' option exclusively and omit the '--image' option. This is the *less common* case so this is a bad default. Default instead to a destination type of 'volume' like everything else, and require users specifying '--block-device' alone to pass 'destination_type=local' explicitly. [1] https://github.com/openstack/nova/blob/c8a6f8d2e/nova/block_device.py#L193-L206 Change-Id: I1718be965f57c3bbdb8a14f3cfac967dd4c55b4d Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server.py | 30 ++-- .../functional/compute/v2/test_server.py | 164 ++++++++++++++++-- .../tests/unit/compute/v2/test_server.py | 28 +-- 3 files changed, 169 insertions(+), 53 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 9838ed5480..b2ae93c807 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -832,13 +832,12 @@ def get_parser(self, prog_name): action=parseractions.MultiKeyValueAction, dest='block_devices', default=[], - required_keys=[ - 'boot_index', - ], + required_keys=[], optional_keys=[ 'uuid', 'source_type', 'destination_type', - 'disk_bus', 'device_type', 'device_name', 'guest_format', - 'volume_size', 'volume_type', 'delete_on_termination', 'tag', + 'disk_bus', 'device_type', 'device_name', 'volume_size', + 'guest_format', 'boot_index', 'delete_on_termination', 'tag', + 'volume_type', ], help=_( 'Create a block device on the server.\n' @@ -1336,14 +1335,15 @@ def _match_image(image_api, wanted_properties): block_device_mapping_v2.append(mapping) for mapping in parsed_args.block_devices: - try: - mapping['boot_index'] = int(mapping['boot_index']) - except ValueError: - msg = _( - 'The boot_index key of --block-device should be an ' - 'integer' - ) - raise exceptions.CommandError(msg) + if 'boot_index' in mapping: + try: + mapping['boot_index'] = int(mapping['boot_index']) + except ValueError: + msg = _( + 'The boot_index key of --block-device should be an ' + 'integer' + ) + raise exceptions.CommandError(msg) if 'tag' in mapping and ( compute_client.api_version < api_versions.APIVersion('2.42') @@ -1383,9 +1383,9 @@ def _match_image(image_api, wanted_properties): ) raise exceptions.CommandError(msg) else: - if mapping['source_type'] in ('image', 'blank'): + if mapping['source_type'] in ('blank',): mapping['destination_type'] = 'local' - else: # volume, snapshot + else: # volume, image, snapshot mapping['destination_type'] = 'volume' if 'delete_on_termination' in mapping: diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py index bad3f93d47..9cf2fc7f0f 100644 --- a/openstackclient/tests/functional/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -574,7 +574,90 @@ def test_server_boot_from_volume(self): cmd_output['status'], ) - def test_server_boot_with_bdm_snapshot(self): + def _test_server_boot_with_bdm_volume(self, use_legacy): + """Test server create from volume, server delete""" + # get volume status wait function + volume_wait_for = volume_common.BaseVolumeTests.wait_for_status + + # create source empty volume + volume_name = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume create -f json ' + + '--size 1 ' + + volume_name + )) + volume_id = cmd_output["id"] + self.assertIsNotNone(volume_id) + self.addCleanup(self.openstack, 'volume delete ' + volume_name) + self.assertEqual(volume_name, cmd_output['name']) + volume_wait_for("volume", volume_name, "available") + + if use_legacy: + bdm_arg = f'--block-device-mapping vdb={volume_name}' + else: + bdm_arg = ( + f'--block-device ' + f'device_name=vdb,source_type=volume,boot_index=1,' + f'uuid={volume_id}' + ) + + # create server + server_name = uuid.uuid4().hex + server = json.loads(self.openstack( + 'server create -f json ' + + '--flavor ' + self.flavor_name + ' ' + + '--image ' + self.image_name + ' ' + + bdm_arg + ' ' + + self.network_arg + ' ' + + '--wait ' + + server_name + )) + self.assertIsNotNone(server["id"]) + self.addCleanup(self.openstack, 'server delete --wait ' + server_name) + self.assertEqual( + server_name, + server['name'], + ) + + # check server volumes_attached, format is + # {"volumes_attached": "id='2518bc76-bf0b-476e-ad6b-571973745bb5'",} + cmd_output = json.loads(self.openstack( + 'server show -f json ' + + server_name + )) + volumes_attached = cmd_output['volumes_attached'] + self.assertIsNotNone(volumes_attached) + + # check volumes + cmd_output = json.loads(self.openstack( + 'volume show -f json ' + + volume_name + )) + attachments = cmd_output['attachments'] + self.assertEqual( + 1, + len(attachments), + ) + self.assertEqual( + server['id'], + attachments[0]['server_id'], + ) + self.assertEqual( + "in-use", + cmd_output['status'], + ) + + def test_server_boot_with_bdm_volume(self): + """Test server create from image with bdm volume, server delete""" + self._test_server_boot_with_bdm_volume(use_legacy=False) + + # TODO(stephenfin): Remove when we drop support for the + # '--block-device-mapping' option + def test_server_boot_with_bdm_volume_legacy(self): + """Test server create from image with bdm volume, server delete""" + self._test_server_boot_with_bdm_volume(use_legacy=True) + + def _test_server_boot_with_bdm_snapshot(self, use_legacy): """Test server create from image with bdm snapshot, server delete""" # get volume status wait function volume_wait_for = volume_common.BaseVolumeTests.wait_for_status @@ -588,12 +671,8 @@ def test_server_boot_with_bdm_snapshot(self): empty_volume_name )) self.assertIsNotNone(cmd_output["id"]) - self.addCleanup(self.openstack, - 'volume delete ' + empty_volume_name) - self.assertEqual( - empty_volume_name, - cmd_output['name'], - ) + self.addCleanup(self.openstack, 'volume delete ' + empty_volume_name) + self.assertEqual(empty_volume_name, cmd_output['name']) volume_wait_for("volume", empty_volume_name, "available") # create snapshot of source empty volume @@ -603,7 +682,8 @@ def test_server_boot_with_bdm_snapshot(self): '--volume ' + empty_volume_name + ' ' + empty_snapshot_name )) - self.assertIsNotNone(cmd_output["id"]) + empty_snapshot_id = cmd_output["id"] + self.assertIsNotNone(empty_snapshot_id) # Deleting volume snapshot take time, so we need to wait until the # snapshot goes. Entries registered by self.addCleanup will be called # in the reverse order, so we need to register wait_for_delete first. @@ -617,14 +697,26 @@ def test_server_boot_with_bdm_snapshot(self): ) volume_wait_for("volume snapshot", empty_snapshot_name, "available") + if use_legacy: + bdm_arg = ( + f'--block-device-mapping ' + f'vdb={empty_snapshot_name}:snapshot:1:true' + ) + else: + bdm_arg = ( + f'--block-device ' + f'device_name=vdb,uuid={empty_snapshot_id},' + f'source_type=snapshot,volume_size=1,' + f'delete_on_termination=true,boot_index=1' + ) + # create server with bdm snapshot server_name = uuid.uuid4().hex server = json.loads(self.openstack( 'server create -f json ' + '--flavor ' + self.flavor_name + ' ' + '--image ' + self.image_name + ' ' + - '--block-device-mapping ' - 'vdb=' + empty_snapshot_name + ':snapshot:1:true ' + + bdm_arg + ' ' + self.network_arg + ' ' + '--wait ' + server_name @@ -681,7 +773,17 @@ def test_server_boot_with_bdm_snapshot(self): # the attached volume had been deleted pass - def test_server_boot_with_bdm_image(self): + def test_server_boot_with_bdm_snapshot(self): + """Test server create from image with bdm snapshot, server delete""" + self._test_server_boot_with_bdm_snapshot(use_legacy=False) + + # TODO(stephenfin): Remove when we drop support for the + # '--block-device-mapping' option + def test_server_boot_with_bdm_snapshot_legacy(self): + """Test server create from image with bdm snapshot, server delete""" + self._test_server_boot_with_bdm_snapshot(use_legacy=True) + + def _test_server_boot_with_bdm_image(self, use_legacy): # Tests creating a server where the root disk is backed by the given # --image but a --block-device-mapping with type=image is provided so # that the compute service creates a volume from that image and @@ -689,6 +791,32 @@ def test_server_boot_with_bdm_image(self): # marked as delete_on_termination=True so it will be automatically # deleted when the server is deleted. + if use_legacy: + # This means create a 1GB volume from the specified image, attach + # it to the server at /dev/vdb and delete the volume when the + # server is deleted. + bdm_arg = ( + f'--block-device-mapping ' + f'vdb={self.image_name}:image:1:true ' + ) + else: + # get image ID + cmd_output = json.loads(self.openstack( + 'image show -f json ' + + self.image_name + )) + image_id = cmd_output['id'] + + # This means create a 1GB volume from the specified image, attach + # it to the server at /dev/vdb and delete the volume when the + # server is deleted. + bdm_arg = ( + f'--block-device ' + f'device_name=vdb,uuid={image_id},' + f'source_type=image,volume_size=1,' + f'delete_on_termination=true,boot_index=1' + ) + # create server with bdm type=image # NOTE(mriedem): This test is a bit unrealistic in that specifying the # same image in the block device as the --image option does not really @@ -700,11 +828,7 @@ def test_server_boot_with_bdm_image(self): 'server create -f json ' + '--flavor ' + self.flavor_name + ' ' + '--image ' + self.image_name + ' ' + - '--block-device-mapping ' - # This means create a 1GB volume from the specified image, attach - # it to the server at /dev/vdb and delete the volume when the - # server is deleted. - 'vdb=' + self.image_name + ':image:1:true ' + + bdm_arg + ' ' + self.network_arg + ' ' + '--wait ' + server_name @@ -768,6 +892,14 @@ def test_server_boot_with_bdm_image(self): # the attached volume had been deleted pass + def test_server_boot_with_bdm_image(self): + self._test_server_boot_with_bdm_image(use_legacy=False) + + # TODO(stephenfin): Remove when we drop support for the + # '--block-device-mapping' option + def test_server_boot_with_bdm_image_legacy(self): + self._test_server_boot_with_bdm_image(use_legacy=True) + def test_boot_from_volume(self): # Tests creating a server using --image and --boot-from-volume where # the compute service will create a root volume of the specified size diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index ced5d458c2..9666c5fdba 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -2029,7 +2029,7 @@ def test_server_create_with_snapshot(self): self.assertEqual(self.datalist(), data) def test_server_create_with_block_device(self): - block_device = f'uuid={self.volume.id},source_type=volume,boot_index=1' + block_device = f'uuid={self.volume.id},source_type=volume' arglist = [ '--image', 'image1', '--flavor', self.flavor.id, @@ -2043,7 +2043,6 @@ def test_server_create_with_block_device(self): { 'uuid': self.volume.id, 'source_type': 'volume', - 'boot_index': '1', }, ]), ('server_name', self.new_server.name), @@ -2069,7 +2068,6 @@ def test_server_create_with_block_device(self): 'uuid': self.volume.id, 'source_type': 'volume', 'destination_type': 'volume', - 'boot_index': 1, }], 'nics': [], 'scheduler_hints': {}, @@ -2171,20 +2169,6 @@ def test_server_create_with_block_device_full(self): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist(), data) - def test_server_create_with_block_device_no_boot_index(self): - block_device = \ - f'uuid={self.volume.name},source_type=volume' - arglist = [ - '--image', 'image1', - '--flavor', self.flavor.id, - '--block-device', block_device, - self.new_server.name, - ] - self.assertRaises( - argparse.ArgumentTypeError, - self.check_parser, - self.cmd, arglist, []) - def test_server_create_with_block_device_invalid_boot_index(self): block_device = \ f'uuid={self.volume.name},source_type=volume,boot_index=foo' @@ -2201,7 +2185,7 @@ def test_server_create_with_block_device_invalid_boot_index(self): self.assertIn('The boot_index key of --block-device ', str(ex)) def test_server_create_with_block_device_invalid_source_type(self): - block_device = f'uuid={self.volume.name},source_type=foo,boot_index=1' + block_device = f'uuid={self.volume.name},source_type=foo' arglist = [ '--image', 'image1', '--flavor', self.flavor.id, @@ -2216,7 +2200,7 @@ def test_server_create_with_block_device_invalid_source_type(self): def test_server_create_with_block_device_invalid_destination_type(self): block_device = \ - f'uuid={self.volume.name},destination_type=foo,boot_index=1' + f'uuid={self.volume.name},destination_type=foo' arglist = [ '--image', 'image1', '--flavor', self.flavor.id, @@ -2231,7 +2215,7 @@ def test_server_create_with_block_device_invalid_destination_type(self): def test_server_create_with_block_device_invalid_shutdown(self): block_device = \ - f'uuid={self.volume.name},delete_on_termination=foo,boot_index=1' + f'uuid={self.volume.name},delete_on_termination=foo' arglist = [ '--image', 'image1', '--flavor', self.flavor.id, @@ -2250,7 +2234,7 @@ def test_server_create_with_block_device_tag_pre_v242(self): '2.41') block_device = \ - f'uuid={self.volume.name},tag=foo,boot_index=1' + f'uuid={self.volume.name},tag=foo' arglist = [ '--image', 'image1', '--flavor', self.flavor.id, @@ -2269,7 +2253,7 @@ def test_server_create_with_block_device_volume_type_pre_v267(self): self.app.client_manager.compute.api_version = api_versions.APIVersion( '2.66') - block_device = f'uuid={self.volume.name},volume_type=foo,boot_index=1' + block_device = f'uuid={self.volume.name},volume_type=foo' arglist = [ '--image', 'image1', '--flavor', self.flavor.id, From d3bd0146ae64ebc3a7c5d7a3a2fc90efe4071495 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Fri, 5 Mar 2021 12:39:37 +0000 Subject: [PATCH 2352/3095] compute: Add support for loading BDMs from files The syntax of the '--block-device' parameter is complex and easily screwed up. Allow users to load a block device config from a file. For example: $ openstack server create ... --block-device file:///tmp/bdm.json ... This should alleviate the pain that is BDMv2 somewhat. No functional tests are provided since we already have tests for the CSV style of passing parameters and the unit tests show that the net result is the same. Change-Id: I3e3299bbdbbb343863b4c14fb4d9196ff3e1698d Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server.py | 80 +++++++++++++++--- .../tests/unit/compute/v2/test_server.py | 83 +++++++++++++++++++ 2 files changed, 153 insertions(+), 10 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index b2ae93c807..cd6e2c9165 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -18,8 +18,10 @@ import argparse import getpass import io +import json import logging import os +import urllib.parse from cliff import columns as cliff_columns import iso8601 @@ -681,7 +683,7 @@ def __call__(self, parser, namespace, values, option_string=None): class BDMLegacyAction(argparse.Action): def __call__(self, parser, namespace, values, option_string=None): - # Make sure we have an empty dict rather than None + # Make sure we have an empty list rather than None if getattr(namespace, self.dest, None) is None: setattr(namespace, self.dest, []) @@ -723,6 +725,68 @@ def __call__(self, parser, namespace, values, option_string=None): getattr(namespace, self.dest).append(mapping) +class BDMAction(parseractions.MultiKeyValueAction): + + def __init__(self, option_strings, dest, **kwargs): + required_keys = [] + optional_keys = [ + 'uuid', 'source_type', 'destination_type', + 'disk_bus', 'device_type', 'device_name', 'volume_size', + 'guest_format', 'boot_index', 'delete_on_termination', 'tag', + 'volume_type', + ] + super().__init__( + option_strings, dest, required_keys=required_keys, + optional_keys=optional_keys, **kwargs, + ) + + # TODO(stephenfin): Remove once I549d0897ef3704b7f47000f867d6731ad15d3f2b + # or similar lands in a release + def validate_keys(self, keys): + """Validate the provided keys. + + :param keys: A list of keys to validate. + """ + valid_keys = self.required_keys | self.optional_keys + invalid_keys = [k for k in keys if k not in valid_keys] + if invalid_keys: + msg = _( + "Invalid keys %(invalid_keys)s specified.\n" + "Valid keys are: %(valid_keys)s" + ) + raise argparse.ArgumentTypeError(msg % { + 'invalid_keys': ', '.join(invalid_keys), + 'valid_keys': ', '.join(valid_keys), + }) + + missing_keys = [k for k in self.required_keys if k not in keys] + if missing_keys: + msg = _( + "Missing required keys %(missing_keys)s.\n" + "Required keys are: %(required_keys)s" + ) + raise argparse.ArgumentTypeError(msg % { + 'missing_keys': ', '.join(missing_keys), + 'required_keys': ', '.join(self.required_keys), + }) + + def __call__(self, parser, namespace, values, option_string=None): + if getattr(namespace, self.dest, None) is None: + setattr(namespace, self.dest, []) + + if values.startswith('file://'): + path = urllib.parse.urlparse(values).path + with open(path) as fh: + data = json.load(fh) + + # Validate the keys - other validation is left to later + self.validate_keys(list(data)) + + getattr(namespace, self.dest, []).append(data) + else: + super().__call__(parser, namespace, values, option_string) + + class CreateServer(command.ShowOne): _description = _("Create a new server") @@ -829,19 +893,15 @@ def get_parser(self, prog_name): parser.add_argument( '--block-device', metavar='', - action=parseractions.MultiKeyValueAction, + action=BDMAction, dest='block_devices', default=[], - required_keys=[], - optional_keys=[ - 'uuid', 'source_type', 'destination_type', - 'disk_bus', 'device_type', 'device_name', 'volume_size', - 'guest_format', 'boot_index', 'delete_on_termination', 'tag', - 'volume_type', - ], help=_( 'Create a block device on the server.\n' - 'Block device in the format:\n' + 'Either a URI-style path (\'file:\\\\{path}\') to a JSON file ' + 'or a CSV-serialized string describing the block device ' + 'mapping.\n' + 'The following keys are accepted:\n' 'uuid=: UUID of the volume, snapshot or ID ' '(required if using source image, snapshot or volume),\n' 'source_type=: source type ' diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 9666c5fdba..775ad0d911 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -16,6 +16,8 @@ import collections import copy import getpass +import json +import tempfile from unittest import mock from unittest.mock import call @@ -2169,6 +2171,87 @@ def test_server_create_with_block_device_full(self): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist(), data) + def test_server_create_with_block_device_from_file(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.67') + + block_device = { + 'uuid': self.volume.id, + 'source_type': 'volume', + 'destination_type': 'volume', + 'disk_bus': 'ide', + 'device_type': 'disk', + 'device_name': 'sdb', + 'guest_format': 'ext4', + 'volume_size': 64, + 'volume_type': 'foo', + 'boot_index': 1, + 'delete_on_termination': True, + 'tag': 'foo', + } + + with tempfile.NamedTemporaryFile(mode='w+') as fp: + json.dump(block_device, fp=fp) + fp.flush() + + arglist = [ + '--image', 'image1', + '--flavor', self.flavor.id, + '--block-device', f'file://{fp.name}', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', self.flavor.id), + ('block_devices', [block_device]), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # CreateServer.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'meta': None, + 'files': {}, + 'reservation_id': None, + 'min_count': 1, + 'max_count': 1, + 'security_groups': [], + 'userdata': None, + 'key_name': None, + 'availability_zone': None, + 'admin_pass': None, + 'block_device_mapping_v2': [{ + 'uuid': self.volume.id, + 'source_type': 'volume', + 'destination_type': 'volume', + 'disk_bus': 'ide', + 'device_name': 'sdb', + 'volume_size': 64, + 'guest_format': 'ext4', + 'boot_index': 1, + 'device_type': 'disk', + 'delete_on_termination': True, + 'tag': 'foo', + 'volume_type': 'foo', + }], + 'nics': 'auto', + 'scheduler_hints': {}, + 'config_drive': None, + } + # ServerManager.create(name, image, flavor, **kwargs) + self.servers_mock.create.assert_called_with( + self.new_server.name, + self.image, + self.flavor, + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) + def test_server_create_with_block_device_invalid_boot_index(self): block_device = \ f'uuid={self.volume.name},source_type=volume,boot_index=foo' From ed731d6cd9254eae047d14ccd55132229e13e7d1 Mon Sep 17 00:00:00 2001 From: Bharat Kunwar Date: Fri, 5 Mar 2021 12:52:26 +0000 Subject: [PATCH 2353/3095] network: Add missing subnet unset --gateway Story: 2008695 Task: 42003 Change-Id: I9486a09531b11f27a9ff0d68fd4ad8c68a65cccf --- openstackclient/network/v2/subnet.py | 7 +++++++ openstackclient/tests/unit/network/v2/test_subnet.py | 4 ++++ .../notes/subnet-unset-gateway-20239d5910e10778.yaml | 4 ++++ 3 files changed, 15 insertions(+) create mode 100644 releasenotes/notes/subnet-unset-gateway-20239d5910e10778.yaml diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index f87f7abe1b..b98f86412c 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -672,6 +672,11 @@ def get_parser(self, prog_name): 'subnet e.g.: start=192.168.199.2,end=192.168.199.254 ' '(repeat option to unset multiple allocation pools)') ) + parser.add_argument( + '--gateway', + action='store_true', + help=_("Remove gateway IP from this subnet") + ) parser.add_argument( '--dns-nameserver', metavar='', @@ -715,6 +720,8 @@ def take_action(self, parsed_args): obj = client.find_subnet(parsed_args.subnet, ignore_missing=False) attrs = {} + if parsed_args.gateway: + attrs['gateway_ip'] = None if parsed_args.dns_nameservers: attrs['dns_nameservers'] = copy.deepcopy(obj.dns_nameservers) _update_arguments(attrs['dns_nameservers'], diff --git a/openstackclient/tests/unit/network/v2/test_subnet.py b/openstackclient/tests/unit/network/v2/test_subnet.py index 1b4bfdad2f..6085cda8a1 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet.py +++ b/openstackclient/tests/unit/network/v2/test_subnet.py @@ -1264,6 +1264,7 @@ def setUp(self): 'end': '8.8.8.170'}], 'service_types': ['network:router_gateway', 'network:floatingip_agent_gateway'], + 'gateway_ip': 'fe80::a00a:0:c0de:0:1', 'tags': ['green', 'red'], }) self.network.find_subnet = mock.Mock(return_value=self._testsubnet) self.network.update_subnet = mock.Mock(return_value=None) @@ -1277,6 +1278,7 @@ def test_unset_subnet_params(self): '--host-route', 'destination=10.30.30.30/24,gateway=10.30.30.1', '--allocation-pool', 'start=8.8.8.100,end=8.8.8.150', '--service-type', 'network:router_gateway', + '--gateway', self._testsubnet.name, ] verifylist = [ @@ -1286,6 +1288,7 @@ def test_unset_subnet_params(self): ('allocation_pools', [{ 'start': '8.8.8.100', 'end': '8.8.8.150'}]), ('service_types', ['network:router_gateway']), + ('gateway', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1297,6 +1300,7 @@ def test_unset_subnet_params(self): "destination": "10.20.20.0/24", "nexthop": "10.20.20.1"}], 'allocation_pools': [{'start': '8.8.8.160', 'end': '8.8.8.170'}], 'service_types': ['network:floatingip_agent_gateway'], + 'gateway_ip': None, } self.network.update_subnet.assert_called_once_with( self._testsubnet, **attrs) diff --git a/releasenotes/notes/subnet-unset-gateway-20239d5910e10778.yaml b/releasenotes/notes/subnet-unset-gateway-20239d5910e10778.yaml new file mode 100644 index 0000000000..55be3e4a34 --- /dev/null +++ b/releasenotes/notes/subnet-unset-gateway-20239d5910e10778.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + Add missing ``openstack subnet unset --gateway ``. From 46bd6ef91f297aa84fb761b0e7fd983668da56de Mon Sep 17 00:00:00 2001 From: Pierre Riteau Date: Tue, 9 Mar 2021 14:49:38 +0100 Subject: [PATCH 2354/3095] Update volume create documentation Change I94aa7a9824e44f9585ffb45e5e7637b9588539b4 removed these options. Change-Id: I43d84b5532ae6570e1486867c03b8ebec81e38e4 --- doc/source/cli/command-objects/volume.rst | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/doc/source/cli/command-objects/volume.rst b/doc/source/cli/command-objects/volume.rst index fc6188c0a8..ac414110a0 100644 --- a/doc/source/cli/command-objects/volume.rst +++ b/doc/source/cli/command-objects/volume.rst @@ -17,13 +17,10 @@ Create new volume [--type ] [--image | --snapshot | --source ] [--description ] - [--user ] - [--project ] [--availability-zone ] [--consistency-group ] [--property [...] ] [--hint [...] ] - [--multi-attach] [--bootable | --non-bootable] [--read-only | --read-write] @@ -58,14 +55,6 @@ Create new volume Volume description -.. option:: --user - - Specify an alternate user (name or ID) - -.. option:: --project - - Specify an alternate project (name or ID) - .. option:: --availability-zone Create volume in ```` @@ -83,10 +72,6 @@ Create new volume Arbitrary scheduler hint key-value pairs to help boot an instance (repeat option to set multiple hints) -.. option:: --multi-attach - - Allow volume to be attached more than once (default to False) - .. option:: --bootable Mark volume as bootable @@ -108,10 +93,6 @@ Create new volume Volume name -The :option:`--project` and :option:`--user` options are typically only -useful for admin users, but may be allowed for other users depending on -the policy of the cloud and the roles granted to the user. - volume delete ------------- From 2ccf7727a6c06214956b7067e7932015bcdfb5a5 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 11 Mar 2021 15:53:39 +0000 Subject: [PATCH 2355/3095] compute: Remove 'file://' prefix from '--block-device' There are a couple of other (networking-related) options which accept paths, none of which insist on a URI-style path. Let's just drop this bit of complexity before we release the feature. Change-Id: Ia7f781d82f3f4695b49b55a39abbb6e582cd879c Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server.py | 13 +++++-------- .../tests/unit/compute/v2/test_server.py | 2 +- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index cd6e2c9165..b81d2a181c 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -21,7 +21,6 @@ import json import logging import os -import urllib.parse from cliff import columns as cliff_columns import iso8601 @@ -774,9 +773,8 @@ def __call__(self, parser, namespace, values, option_string=None): if getattr(namespace, self.dest, None) is None: setattr(namespace, self.dest, []) - if values.startswith('file://'): - path = urllib.parse.urlparse(values).path - with open(path) as fh: + if os.path.exists(values): + with open(values) as fh: data = json.load(fh) # Validate the keys - other validation is left to later @@ -898,10 +896,9 @@ def get_parser(self, prog_name): default=[], help=_( 'Create a block device on the server.\n' - 'Either a URI-style path (\'file:\\\\{path}\') to a JSON file ' - 'or a CSV-serialized string describing the block device ' - 'mapping.\n' - 'The following keys are accepted:\n' + 'Either a path to a JSON file or a CSV-serialized string ' + 'describing the block device mapping.\n' + 'The following keys are accepted for both:\n' 'uuid=: UUID of the volume, snapshot or ID ' '(required if using source image, snapshot or volume),\n' 'source_type=: source type ' diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 775ad0d911..c6dff5a8a2 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -2197,7 +2197,7 @@ def test_server_create_with_block_device_from_file(self): arglist = [ '--image', 'image1', '--flavor', self.flavor.id, - '--block-device', f'file://{fp.name}', + '--block-device', fp.name, self.new_server.name, ] verifylist = [ From 87e682867886bddd10f1dc9c4a174603414e0480 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 11 Mar 2021 16:20:15 +0000 Subject: [PATCH 2356/3095] Add pre-commit This is helpful to automate code style checks at runtime. We include documentation on how to run this as well as a general overview of style guidelines in OSC. Change-Id: I2dc5a0f760ce53269ae25677560b2611cc6bfd91 Signed-off-by: Stephen Finucane --- .pre-commit-config.yaml | 29 +++++++++++++++ doc/source/contributor/developing.rst | 53 ++++++++++++++++++++------- 2 files changed, 69 insertions(+), 13 deletions(-) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000000..f91d10b7be --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,29 @@ +--- +default_language_version: + # force all unspecified python hooks to run python3 + python: python3 +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.4.0 + hooks: + - id: trailing-whitespace + - id: mixed-line-ending + args: ['--fix', 'lf'] + exclude: '.*\.(svg)$' + - id: check-byte-order-marker + - id: check-executables-have-shebangs + - id: check-merge-conflict + - id: debug-statements + - id: check-yaml + files: .*\.(yaml|yml)$ + - repo: local + hooks: + - id: flake8 + name: flake8 + additional_dependencies: + - hacking>=2.0.0 + - flake8-import-order>=0.13 + language: python + entry: flake8 + files: '^.*\.py$' + exclude: '^(doc|releasenotes|tools)/.*$' diff --git a/doc/source/contributor/developing.rst b/doc/source/contributor/developing.rst index 9142edb86c..a70b33268a 100644 --- a/doc/source/contributor/developing.rst +++ b/doc/source/contributor/developing.rst @@ -6,16 +6,18 @@ Communication ------------- IRC Channel -=========== +~~~~~~~~~~~ + The OpenStackClient team doesn't have regular meetings so if you have questions or anything you want to discuss, come to our channel: #openstack-sdks + Testing ------- Tox prerequisites and installation -================================== +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Install the prerequisites for Tox: @@ -23,23 +25,22 @@ Install the prerequisites for Tox: .. code-block:: bash - $ apt-get install gcc gettext python-dev libxml2-dev libxslt1-dev \ + $ apt-get install gcc gettext python3-dev libxml2-dev libxslt1-dev \ zlib1g-dev You may need to use pip install for some packages. - * On RHEL or CentOS including Fedora: .. code-block:: bash - $ yum install gcc python-devel libxml2-devel libxslt-devel + $ yum install gcc python3-devel libxml2-devel libxslt-devel * On openSUSE or SUSE linux Enterprise: .. code-block:: bash - $ zypper install gcc python-devel libxml2-devel libxslt-devel + $ zypper install gcc python3-devel libxml2-devel libxslt-devel Install python-tox: @@ -59,7 +60,7 @@ To run the full suite of tests maintained within OpenStackClient. virtualenvs. You can later use the ``-r`` option with ``tox`` to rebuild your virtualenv in a similar manner. -To run tests for one or more specific test environments(for example, the most +To run tests for one or more specific test environments (for example, the most common configuration of the latest Python version and PEP-8), list the environments with the ``-e`` option, separated by spaces: @@ -70,7 +71,7 @@ environments with the ``-e`` option, separated by spaces: See ``tox.ini`` for the full list of available test environments. Running functional tests -======================== +~~~~~~~~~~~~~~~~~~~~~~~~ OpenStackClient also maintains a set of functional tests that are optimally designed to be run against OpenStack's gate. Optionally, a developer may @@ -90,7 +91,7 @@ To run a specific functional test: $ tox -e functional -- --regex functional.tests.compute.v2.test_server Running with PDB -================ +~~~~~~~~~~~~~~~~ Using PDB breakpoints with ``tox`` and ``testr`` normally does not work since the tests fail with a `BdbQuit` exception rather than stopping at the @@ -109,8 +110,32 @@ For reference, the `debug`_ ``tox`` environment implements the instructions .. _`debug`: https://wiki.openstack.org/wiki/Testr#Debugging_.28pdb.29_Tests -Building the Documentation --------------------------- +Coding Style +------------ + +OpenStackClient uses `flake8`__ along with `hacking`__, an OpenStack-specific +superset of ``flake8`` rules, to enforce coding style. This can be run manually +using ``tox``: + +.. code-block:: bash + + $ tox -e pep8 + +Alternatively, you can use the `pre-commit framework`__ to allow running of +some linters on each commit. This must be enabled locally to function: + +.. code-block:: bash + + $ pip install --user pre-commit + $ pre-commit install --allow-missing-config + +.. __: https://flake8.pycqa.org/en/latest/ +.. __: https://docs.openstack.org/hacking/latest/user/hacking.html +.. __: https://pre-commit.com/ + + +Documentation +------------- The documentation is generated with Sphinx using the ``tox`` command. To create HTML docs, run the commands: @@ -121,6 +146,7 @@ create HTML docs, run the commands: The resultant HTML will be in the ``doc/build/html`` directory. + Release Notes ------------- @@ -156,6 +182,7 @@ To run the commands and see results: At last, look at the generated release notes files in ``releasenotes/build/html`` in your browser. + Testing new code ---------------- @@ -174,7 +201,7 @@ or $ pip install -e . Standardize Import Format -========================= +~~~~~~~~~~~~~~~~~~~~~~~~~ More information about Import Format, see `Import Order Guide `__. @@ -193,7 +220,7 @@ The import order shows below: {{begin your code}} Example -~~~~~~~ +^^^^^^^ .. code-block:: python From 791bed6dd2272138b619e7c7ab26ca0692defff8 Mon Sep 17 00:00:00 2001 From: Takashi Kajinami Date: Mon, 15 Mar 2021 23:29:53 +0900 Subject: [PATCH 2357/3095] Update the file paths mentioned in README.rst This change fixes the outdated file paths, which were renamed by commit 9599ffe65d9dcd4b3aa780d346eccd1e760890bf . Change-Id: I9ec4c49711a2fde24f5527086e495c86af9ef1ce --- doc/source/contributor/plugins.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/source/contributor/plugins.rst b/doc/source/contributor/plugins.rst index 067c1b9912..c2a08c5d63 100644 --- a/doc/source/contributor/plugins.rst +++ b/doc/source/contributor/plugins.rst @@ -226,15 +226,15 @@ Add the command checker to your CI Changes to python-openstackclient --------------------------------- -#. In ``doc/source/plugins.rst``, update the `Adoption` section to reflect the - status of the project. +#. In ``doc/source/contributor/plugins.rst``, update the `Adoption` section to + reflect the status of the project. -#. Update ``doc/source/commands.rst`` to include objects that are defined by - fooclient's new plugin. +#. Update ``doc/source/contributor/commands.rst`` to include objects that are + defined by fooclient's new plugin. -#. Update ``doc/source/plugin-commands.rst`` to include the entry point defined - in fooclient. We use `sphinxext`_ to automatically document commands that - are used. +#. Update ``doc/source/contributor/plugin-commands.rst`` to include the entry + point defined in fooclient. We use `sphinxext`_ to automatically document + commands that are used. #. Update ``test-requirements.txt`` to include fooclient. This is necessary to auto-document the commands in the previous step. From e4e9fb594d003ea6c3ec29aab0bccf72ffab6781 Mon Sep 17 00:00:00 2001 From: Brian Haley Date: Wed, 3 Mar 2021 12:46:02 -0500 Subject: [PATCH 2358/3095] Add --subnet-pool to subnet list The neutron API supports filtering subnets by subnet pool id, but the CLI was missing support for it. Change-Id: Ic230c2c5cda8255d8f2c422880aeac81670b2df3 --- openstackclient/network/v2/subnet.py | 10 +++++ .../tests/unit/network/v2/test_subnet.py | 42 +++++++++++++++++++ ...st-subnet-by-pool-id-a642efc13d04fa08.yaml | 5 +++ 3 files changed, 57 insertions(+) create mode 100644 releasenotes/notes/list-subnet-by-pool-id-a642efc13d04fa08.yaml diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index f87f7abe1b..92a9e750c5 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -488,6 +488,12 @@ def get_parser(self, prog_name): "(in CIDR notation) in output " "e.g.: --subnet-range 10.10.0.0/16") ) + parser.add_argument( + '--subnet-pool', + metavar='', + help=_("List only subnets which belong to a given subnet pool " + "in output (Name or ID)") + ) _tag.add_tag_filtering_option_to_parser(parser, _('subnets')) return parser @@ -523,6 +529,10 @@ def take_action(self, parsed_args): filters['name'] = parsed_args.name if parsed_args.subnet_range: filters['cidr'] = parsed_args.subnet_range + if parsed_args.subnet_pool: + subnetpool_id = network_client.find_subnet_pool( + parsed_args.subnet_pool, ignore_missing=False).id + filters['subnetpool_id'] = subnetpool_id _tag.get_tag_filtering_args(parsed_args, filters) data = network_client.subnets(**filters) diff --git a/openstackclient/tests/unit/network/v2/test_subnet.py b/openstackclient/tests/unit/network/v2/test_subnet.py index 1b4bfdad2f..06096f4b63 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet.py +++ b/openstackclient/tests/unit/network/v2/test_subnet.py @@ -899,6 +899,48 @@ def test_subnet_list_subnet_range(self): self.assertEqual(self.columns, columns) self.assertItemsEqual(self.data, list(data)) + def test_subnet_list_subnetpool_by_name(self): + subnet_pool = network_fakes.FakeSubnetPool.create_one_subnet_pool() + subnet = network_fakes.FakeSubnet.create_one_subnet( + {'subnetpool_id': subnet_pool.id}) + self.network.find_network = mock.Mock(return_value=subnet) + self.network.find_subnet_pool = mock.Mock(return_value=subnet_pool) + arglist = [ + '--subnet-pool', subnet_pool.name, + ] + verifylist = [ + ('subnet_pool', subnet_pool.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'subnetpool_id': subnet_pool.id} + + self.network.subnets.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertItemsEqual(self.data, list(data)) + + def test_subnet_list_subnetpool_by_id(self): + subnet_pool = network_fakes.FakeSubnetPool.create_one_subnet_pool() + subnet = network_fakes.FakeSubnet.create_one_subnet( + {'subnetpool_id': subnet_pool.id}) + self.network.find_network = mock.Mock(return_value=subnet) + self.network.find_subnet_pool = mock.Mock(return_value=subnet_pool) + arglist = [ + '--subnet-pool', subnet_pool.id, + ] + verifylist = [ + ('subnet_pool', subnet_pool.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'subnetpool_id': subnet_pool.id} + + self.network.subnets.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertItemsEqual(self.data, list(data)) + def test_list_with_tag_options(self): arglist = [ '--tags', 'red,blue', diff --git a/releasenotes/notes/list-subnet-by-pool-id-a642efc13d04fa08.yaml b/releasenotes/notes/list-subnet-by-pool-id-a642efc13d04fa08.yaml new file mode 100644 index 0000000000..d784a9aadb --- /dev/null +++ b/releasenotes/notes/list-subnet-by-pool-id-a642efc13d04fa08.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``--subnet-pool`` option to ``subnet list`` to filter + by subnets by subnet pool. From 6f821659795fdc82c694bb9fdddd5d3c61702e21 Mon Sep 17 00:00:00 2001 From: Sean Mooney Date: Wed, 3 Mar 2021 20:20:00 +0000 Subject: [PATCH 2359/3095] network: Add support for vnic-type vdpa Extend 'port create' to support vinc-type vdpa as introduced by neutron in [1]. [1] https://review.opendev.org/c/openstack/neutron/+/760047 Change-Id: I635c5269f4e8fc55f234c98e85fced87b39fce81 --- openstackclient/network/v2/port.py | 14 +++++++++----- ...ort-create-vnic-type-vdpa-fc02516cfb919941.yaml | 4 ++++ 2 files changed, 13 insertions(+), 5 deletions(-) create mode 100644 releasenotes/notes/network-port-create-vnic-type-vdpa-fc02516cfb919941.yaml diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 6885e147e0..4feffc1df4 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -255,11 +255,15 @@ def _add_updatable_args(parser): parser.add_argument( '--vnic-type', metavar='', - choices=['direct', 'direct-physical', 'macvtap', - 'normal', 'baremetal', 'virtio-forwarder'], - help=_("VNIC type for this port (direct | direct-physical | " - "macvtap | normal | baremetal | virtio-forwarder, " - "default: normal)") + choices=( + 'direct', 'direct-physical', 'macvtap', + 'normal', 'baremetal', 'virtio-forwarder', 'vdpa' + ), + help=_( + "VNIC type for this port (direct | direct-physical | " + "macvtap | normal | baremetal | virtio-forwarder | vdpa, " + "default: normal)" + ), ) parser.add_argument( '--host', diff --git a/releasenotes/notes/network-port-create-vnic-type-vdpa-fc02516cfb919941.yaml b/releasenotes/notes/network-port-create-vnic-type-vdpa-fc02516cfb919941.yaml new file mode 100644 index 0000000000..4821d2a647 --- /dev/null +++ b/releasenotes/notes/network-port-create-vnic-type-vdpa-fc02516cfb919941.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + The ``port create --vnic-type`` option now accepts a ``vdpa`` value. From bf97b5f2878884416613d104f7168edf26f64ffa Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Fri, 19 Mar 2021 15:55:30 +0000 Subject: [PATCH 2360/3095] volume: Re-add accidentally deleted test This is essentially a partial revert of change I94aa7a9824e44f9585ffb45e5e7637b9588539b4, which removed some deprecated commands like 'openstack snapshot *' in favour of 'openstack volume snapshot *'. Unfortunately the latter appeared to have no test coverage and were relying on tests for the former to validate behavior. Re-add the tests removed back then. Change-Id: Ib2cd975221034c8997d272d43cfb18acefc319fe Signed-off-by: Stephen Finucane --- .../unit/volume/v2/test_volume_snapshot.py | 742 ++++++++++++++++++ 1 file changed, 742 insertions(+) create mode 100644 openstackclient/tests/unit/volume/v2/test_volume_snapshot.py diff --git a/openstackclient/tests/unit/volume/v2/test_volume_snapshot.py b/openstackclient/tests/unit/volume/v2/test_volume_snapshot.py new file mode 100644 index 0000000000..3830f458d6 --- /dev/null +++ b/openstackclient/tests/unit/volume/v2/test_volume_snapshot.py @@ -0,0 +1,742 @@ +# +# 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 argparse +from unittest import mock + +from osc_lib.cli import format_columns +from osc_lib import exceptions +from osc_lib import utils + +from openstackclient.tests.unit.identity.v3 import fakes as project_fakes +from openstackclient.tests.unit import utils as tests_utils +from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes +from openstackclient.volume.v2 import volume_snapshot + + +class TestVolumeSnapshot(volume_fakes.TestVolume): + + def setUp(self): + super().setUp() + + self.snapshots_mock = self.app.client_manager.volume.volume_snapshots + self.snapshots_mock.reset_mock() + self.volumes_mock = self.app.client_manager.volume.volumes + self.volumes_mock.reset_mock() + self.project_mock = self.app.client_manager.identity.projects + self.project_mock.reset_mock() + + +class TestVolumeSnapshotCreate(TestVolumeSnapshot): + + columns = ( + 'created_at', + 'description', + 'id', + 'name', + 'properties', + 'size', + 'status', + 'volume_id', + ) + + def setUp(self): + super().setUp() + + self.volume = volume_fakes.FakeVolume.create_one_volume() + self.new_snapshot = volume_fakes.FakeSnapshot.create_one_snapshot( + attrs={'volume_id': self.volume.id}) + + self.data = ( + self.new_snapshot.created_at, + self.new_snapshot.description, + self.new_snapshot.id, + self.new_snapshot.name, + format_columns.DictColumn(self.new_snapshot.metadata), + self.new_snapshot.size, + self.new_snapshot.status, + self.new_snapshot.volume_id, + ) + + self.volumes_mock.get.return_value = self.volume + self.snapshots_mock.create.return_value = self.new_snapshot + self.snapshots_mock.manage.return_value = self.new_snapshot + # Get the command object to test + self.cmd = volume_snapshot.CreateVolumeSnapshot(self.app, None) + + def test_snapshot_create(self): + arglist = [ + "--volume", self.new_snapshot.volume_id, + "--description", self.new_snapshot.description, + "--force", + '--property', 'Alpha=a', + '--property', 'Beta=b', + self.new_snapshot.name, + ] + verifylist = [ + ("volume", self.new_snapshot.volume_id), + ("description", self.new_snapshot.description), + ("force", True), + ('property', {'Alpha': 'a', 'Beta': 'b'}), + ("snapshot_name", self.new_snapshot.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.snapshots_mock.create.assert_called_with( + self.new_snapshot.volume_id, + force=True, + name=self.new_snapshot.name, + description=self.new_snapshot.description, + metadata={'Alpha': 'a', 'Beta': 'b'}, + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_snapshot_create_without_name(self): + arglist = [ + "--volume", self.new_snapshot.volume_id, + ] + verifylist = [ + ("volume", self.new_snapshot.volume_id), + ] + self.assertRaises( + tests_utils.ParserException, + self.check_parser, + self.cmd, + arglist, + verifylist, + ) + + def test_snapshot_create_without_volume(self): + arglist = [ + "--description", self.new_snapshot.description, + "--force", + self.new_snapshot.name + ] + verifylist = [ + ("description", self.new_snapshot.description), + ("force", True), + ("snapshot_name", self.new_snapshot.name) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volumes_mock.get.assert_called_once_with( + self.new_snapshot.name) + self.snapshots_mock.create.assert_called_once_with( + self.new_snapshot.volume_id, + force=True, + name=self.new_snapshot.name, + description=self.new_snapshot.description, + metadata=None, + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_snapshot_create_with_remote_source(self): + arglist = [ + '--remote-source', 'source-name=test_source_name', + '--remote-source', 'source-id=test_source_id', + '--volume', self.new_snapshot.volume_id, + self.new_snapshot.name, + ] + ref_dict = {'source-name': 'test_source_name', + 'source-id': 'test_source_id'} + verifylist = [ + ('remote_source', ref_dict), + ('volume', self.new_snapshot.volume_id), + ("snapshot_name", self.new_snapshot.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.snapshots_mock.manage.assert_called_with( + volume_id=self.new_snapshot.volume_id, + ref=ref_dict, + name=self.new_snapshot.name, + description=None, + metadata=None, + ) + self.snapshots_mock.create.assert_not_called() + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestVolumeSnapshotDelete(TestVolumeSnapshot): + + snapshots = volume_fakes.FakeSnapshot.create_snapshots(count=2) + + def setUp(self): + super().setUp() + + self.snapshots_mock.get = ( + volume_fakes.FakeSnapshot.get_snapshots(self.snapshots)) + self.snapshots_mock.delete.return_value = None + + # Get the command object to mock + self.cmd = volume_snapshot.DeleteVolumeSnapshot(self.app, None) + + def test_snapshot_delete(self): + arglist = [ + self.snapshots[0].id + ] + verifylist = [ + ("snapshots", [self.snapshots[0].id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.snapshots_mock.delete.assert_called_with( + self.snapshots[0].id, False) + self.assertIsNone(result) + + def test_snapshot_delete_with_force(self): + arglist = [ + '--force', + self.snapshots[0].id + ] + verifylist = [ + ('force', True), + ("snapshots", [self.snapshots[0].id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.snapshots_mock.delete.assert_called_with( + self.snapshots[0].id, True) + self.assertIsNone(result) + + def test_delete_multiple_snapshots(self): + arglist = [] + for s in self.snapshots: + arglist.append(s.id) + verifylist = [ + ('snapshots', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + calls = [] + for s in self.snapshots: + calls.append(mock.call(s.id, False)) + self.snapshots_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_delete_multiple_snapshots_with_exception(self): + arglist = [ + self.snapshots[0].id, + 'unexist_snapshot', + ] + verifylist = [ + ('snapshots', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self.snapshots[0], exceptions.CommandError] + with mock.patch.object(utils, 'find_resource', + side_effect=find_mock_result) as find_mock: + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 snapshots failed to delete.', + str(e)) + + find_mock.assert_any_call( + self.snapshots_mock, self.snapshots[0].id) + find_mock.assert_any_call(self.snapshots_mock, 'unexist_snapshot') + + self.assertEqual(2, find_mock.call_count) + self.snapshots_mock.delete.assert_called_once_with( + self.snapshots[0].id, False + ) + + +class TestVolumeSnapshotList(TestVolumeSnapshot): + + volume = volume_fakes.FakeVolume.create_one_volume() + project = project_fakes.FakeProject.create_one_project() + snapshots = volume_fakes.FakeSnapshot.create_snapshots( + attrs={'volume_id': volume.name}, count=3) + + columns = [ + "ID", + "Name", + "Description", + "Status", + "Size" + ] + columns_long = columns + [ + "Created At", + "Volume", + "Properties" + ] + + data = [] + for s in snapshots: + data.append(( + s.id, + s.name, + s.description, + s.status, + s.size, + )) + data_long = [] + for s in snapshots: + data_long.append(( + s.id, + s.name, + s.description, + s.status, + s.size, + s.created_at, + volume_snapshot.VolumeIdColumn( + s.volume_id, volume_cache={volume.id: volume}), + format_columns.DictColumn(s.metadata), + )) + + def setUp(self): + super().setUp() + + self.volumes_mock.list.return_value = [self.volume] + self.volumes_mock.get.return_value = self.volume + self.project_mock.get.return_value = self.project + self.snapshots_mock.list.return_value = self.snapshots + # Get the command to test + self.cmd = volume_snapshot.ListVolumeSnapshot(self.app, None) + + def test_snapshot_list_without_options(self): + arglist = [] + verifylist = [ + ('all_projects', False), + ('long', False) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.snapshots_mock.list.assert_called_once_with( + limit=None, marker=None, + search_opts={ + 'all_tenants': False, + 'name': None, + 'status': None, + 'project_id': None, + 'volume_id': None + } + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_snapshot_list_with_options(self): + arglist = [ + "--long", + "--limit", "2", + "--project", self.project.id, + "--marker", self.snapshots[0].id, + ] + verifylist = [ + ("long", True), + ("limit", 2), + ("project", self.project.id), + ("marker", self.snapshots[0].id), + ('all_projects', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.snapshots_mock.list.assert_called_once_with( + limit=2, + marker=self.snapshots[0].id, + search_opts={ + 'all_tenants': True, + 'project_id': self.project.id, + 'name': None, + 'status': None, + 'volume_id': None + } + ) + self.assertEqual(self.columns_long, columns) + self.assertEqual(self.data_long, list(data)) + + def test_snapshot_list_all_projects(self): + arglist = [ + '--all-projects', + ] + verifylist = [ + ('long', False), + ('all_projects', True) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.snapshots_mock.list.assert_called_once_with( + limit=None, marker=None, + search_opts={ + 'all_tenants': True, + 'name': None, + 'status': None, + 'project_id': None, + 'volume_id': None + } + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_snapshot_list_name_option(self): + arglist = [ + '--name', self.snapshots[0].name, + ] + verifylist = [ + ('all_projects', False), + ('long', False), + ('name', self.snapshots[0].name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.snapshots_mock.list.assert_called_once_with( + limit=None, marker=None, + search_opts={ + 'all_tenants': False, + 'name': self.snapshots[0].name, + 'status': None, + 'project_id': None, + 'volume_id': None + } + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_snapshot_list_status_option(self): + arglist = [ + '--status', self.snapshots[0].status, + ] + verifylist = [ + ('all_projects', False), + ('long', False), + ('status', self.snapshots[0].status), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.snapshots_mock.list.assert_called_once_with( + limit=None, marker=None, + search_opts={ + 'all_tenants': False, + 'name': None, + 'status': self.snapshots[0].status, + 'project_id': None, + 'volume_id': None + } + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_snapshot_list_volumeid_option(self): + arglist = [ + '--volume', self.volume.id, + ] + verifylist = [ + ('all_projects', False), + ('long', False), + ('volume', self.volume.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.snapshots_mock.list.assert_called_once_with( + limit=None, marker=None, + search_opts={ + 'all_tenants': False, + 'name': None, + 'status': None, + 'project_id': None, + 'volume_id': self.volume.id + } + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_snapshot_list_negative_limit(self): + arglist = [ + "--limit", "-2", + ] + verifylist = [ + ("limit", -2), + ] + self.assertRaises(argparse.ArgumentTypeError, self.check_parser, + self.cmd, arglist, verifylist) + + +class TestVolumeSnapshotSet(TestVolumeSnapshot): + + snapshot = volume_fakes.FakeSnapshot.create_one_snapshot() + + def setUp(self): + super().setUp() + + self.snapshots_mock.get.return_value = self.snapshot + self.snapshots_mock.set_metadata.return_value = None + self.snapshots_mock.update.return_value = None + # Get the command object to mock + self.cmd = volume_snapshot.SetVolumeSnapshot(self.app, None) + + def test_snapshot_set_no_option(self): + arglist = [ + self.snapshot.id, + ] + verifylist = [ + ("snapshot", self.snapshot.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.snapshots_mock.get.assert_called_once_with(parsed_args.snapshot) + self.assertNotCalled(self.snapshots_mock.reset_state) + self.assertNotCalled(self.snapshots_mock.update) + self.assertNotCalled(self.snapshots_mock.set_metadata) + self.assertIsNone(result) + + def test_snapshot_set_name_and_property(self): + arglist = [ + "--name", "new_snapshot", + "--property", "x=y", + "--property", "foo=foo", + self.snapshot.id, + ] + new_property = {"x": "y", "foo": "foo"} + verifylist = [ + ("name", "new_snapshot"), + ("property", new_property), + ("snapshot", self.snapshot.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + kwargs = { + "name": "new_snapshot", + } + self.snapshots_mock.update.assert_called_with( + self.snapshot.id, **kwargs) + self.snapshots_mock.set_metadata.assert_called_with( + self.snapshot.id, new_property + ) + self.assertIsNone(result) + + def test_snapshot_set_with_no_property(self): + arglist = [ + "--no-property", + self.snapshot.id, + ] + verifylist = [ + ("no_property", True), + ("snapshot", self.snapshot.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.snapshots_mock.get.assert_called_once_with(parsed_args.snapshot) + self.assertNotCalled(self.snapshots_mock.reset_state) + self.assertNotCalled(self.snapshots_mock.update) + self.assertNotCalled(self.snapshots_mock.set_metadata) + self.snapshots_mock.delete_metadata.assert_called_with( + self.snapshot.id, ["foo"] + ) + self.assertIsNone(result) + + def test_snapshot_set_with_no_property_and_property(self): + arglist = [ + "--no-property", + "--property", "foo_1=bar_1", + self.snapshot.id, + ] + verifylist = [ + ("no_property", True), + ("property", {"foo_1": "bar_1"}), + ("snapshot", self.snapshot.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.snapshots_mock.get.assert_called_once_with(parsed_args.snapshot) + self.assertNotCalled(self.snapshots_mock.reset_state) + self.assertNotCalled(self.snapshots_mock.update) + self.snapshots_mock.delete_metadata.assert_called_with( + self.snapshot.id, ["foo"] + ) + self.snapshots_mock.set_metadata.assert_called_once_with( + self.snapshot.id, {"foo_1": "bar_1"}) + self.assertIsNone(result) + + def test_snapshot_set_state_to_error(self): + arglist = [ + "--state", "error", + self.snapshot.id + ] + verifylist = [ + ("state", "error"), + ("snapshot", self.snapshot.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.snapshots_mock.reset_state.assert_called_with( + self.snapshot.id, "error") + self.assertIsNone(result) + + def test_volume_set_state_failed(self): + self.snapshots_mock.reset_state.side_effect = exceptions.CommandError() + arglist = [ + '--state', 'error', + self.snapshot.id + ] + verifylist = [ + ('state', 'error'), + ('snapshot', self.snapshot.id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('One or more of the set operations failed', + str(e)) + self.snapshots_mock.reset_state.assert_called_once_with( + self.snapshot.id, 'error') + + def test_volume_set_name_and_state_failed(self): + self.snapshots_mock.reset_state.side_effect = exceptions.CommandError() + arglist = [ + '--state', 'error', + "--name", "new_snapshot", + self.snapshot.id + ] + verifylist = [ + ('state', 'error'), + ("name", "new_snapshot"), + ('snapshot', self.snapshot.id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('One or more of the set operations failed', + str(e)) + kwargs = { + "name": "new_snapshot", + } + self.snapshots_mock.update.assert_called_once_with( + self.snapshot.id, **kwargs) + self.snapshots_mock.reset_state.assert_called_once_with( + self.snapshot.id, 'error') + + +class TestVolumeSnapshotShow(TestVolumeSnapshot): + + columns = ( + 'created_at', + 'description', + 'id', + 'name', + 'properties', + 'size', + 'status', + 'volume_id', + ) + + def setUp(self): + super().setUp() + + self.snapshot = volume_fakes.FakeSnapshot.create_one_snapshot() + + self.data = ( + self.snapshot.created_at, + self.snapshot.description, + self.snapshot.id, + self.snapshot.name, + format_columns.DictColumn(self.snapshot.metadata), + self.snapshot.size, + self.snapshot.status, + self.snapshot.volume_id, + ) + + self.snapshots_mock.get.return_value = self.snapshot + # Get the command object to test + self.cmd = volume_snapshot.ShowVolumeSnapshot(self.app, None) + + def test_snapshot_show(self): + arglist = [ + self.snapshot.id + ] + verifylist = [ + ("snapshot", self.snapshot.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.snapshots_mock.get.assert_called_with(self.snapshot.id) + + self.assertEqual(self.columns, columns) + self.assertItemsEqual(self.data, data) + + +class TestVolumeSnapshotUnset(TestVolumeSnapshot): + + snapshot = volume_fakes.FakeSnapshot.create_one_snapshot() + + def setUp(self): + super().setUp() + + self.snapshots_mock.get.return_value = self.snapshot + self.snapshots_mock.delete_metadata.return_value = None + # Get the command object to mock + self.cmd = volume_snapshot.UnsetVolumeSnapshot(self.app, None) + + def test_snapshot_unset(self): + arglist = [ + "--property", "foo", + self.snapshot.id, + ] + verifylist = [ + ("property", ["foo"]), + ("snapshot", self.snapshot.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.snapshots_mock.delete_metadata.assert_called_with( + self.snapshot.id, ["foo"] + ) + self.assertIsNone(result) From c58f0277a74a0f94638689de7db297f48243048e Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Fri, 19 Mar 2021 18:36:44 +0000 Subject: [PATCH 2361/3095] network: Make 'network qos rule create --type' option required When we create a network qos rule we need specify the type so that we can call the corresponding API. It's not possible to use the command without the type so mark it as required. This was already being done but inline. Change-Id: I559f884bac198d2c69e800620aef66b200473418 Signed-off-by: Stephen Finucane --- openstackclient/network/v2/network_qos_rule.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/openstackclient/network/v2/network_qos_rule.py b/openstackclient/network/v2/network_qos_rule.py index 28c5600aa6..2e4b385d2e 100644 --- a/openstackclient/network/v2/network_qos_rule.py +++ b/openstackclient/network/v2/network_qos_rule.py @@ -84,9 +84,6 @@ def _get_attrs(network_client, parsed_args, is_create=False): {'rule_id': parsed_args.id}) raise exceptions.CommandError(msg) else: - if not parsed_args.type: - msg = _('"Create" rule command requires argument "type"') - raise exceptions.CommandError(msg) rule_type = parsed_args.type if parsed_args.max_kbps is not None: attrs['max_kbps'] = parsed_args.max_kbps @@ -188,6 +185,7 @@ def get_parser(self, prog_name): parser.add_argument( '--type', metavar='', + required=True, choices=[RULE_TYPE_MINIMUM_BANDWIDTH, RULE_TYPE_DSCP_MARKING, RULE_TYPE_BANDWIDTH_LIMIT], From d769ff4393661ebfde9d683c04cc271ddb550140 Mon Sep 17 00:00:00 2001 From: James Denton Date: Mon, 14 Jan 2019 15:22:45 +0000 Subject: [PATCH 2362/3095] Hides prefix_length column in subnet show output When the openstacksdk is patched to properly support defining prefix lengths when creating subnets, the resulting subnet show output reveals a prefix_length column with a value of 'none'. This patch hides the prefix_length column. Change-Id: I59dfb0b1585ed624f9d82b3557df2ff5ff9d1b3e Partial-Bug: 1754062 Depends-On: https://review.openstack.org/#/c/550558/ --- openstackclient/network/v2/subnet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index b98f86412c..eccdd4e460 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -141,7 +141,7 @@ def _get_columns(item): 'tenant_id': 'project_id', } # Do not show this column when displaying a subnet - invisible_columns = ['use_default_subnet_pool'] + invisible_columns = ['use_default_subnet_pool', 'prefix_length'] return sdk_utils.get_osc_show_columns_for_sdk_resource( item, column_map, From 32151b099c9b237adadbb8123a885eb46bcc2a30 Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Sat, 20 Mar 2021 09:16:50 +0000 Subject: [PATCH 2363/3095] Update master for stable/wallaby Add file to the reno documentation build to show release notes for stable/wallaby. Use pbr instruction to increment the minor version number automatically so that master versions are higher than the versions on stable/wallaby. Sem-Ver: feature Change-Id: I848e21bf2d2ea1a1a132525070e5f20d7ff8478d --- releasenotes/source/index.rst | 1 + releasenotes/source/wallaby.rst | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 releasenotes/source/wallaby.rst diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index 8c276de2d8..cdbb595ce8 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,6 +6,7 @@ OpenStackClient Release Notes :maxdepth: 1 unreleased + wallaby victoria ussuri train diff --git a/releasenotes/source/wallaby.rst b/releasenotes/source/wallaby.rst new file mode 100644 index 0000000000..d77b565995 --- /dev/null +++ b/releasenotes/source/wallaby.rst @@ -0,0 +1,6 @@ +============================ +Wallaby Series Release Notes +============================ + +.. release-notes:: + :branch: stable/wallaby From 449b30981a5151fb19174e61f51f5f6dee142af1 Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Sat, 20 Mar 2021 09:16:55 +0000 Subject: [PATCH 2364/3095] Add Python3 xena unit tests This is an automatically generated patch to ensure unit testing is in place for all the of the tested runtimes for xena. See also the PTI in governance [1]. [1]: https://governance.openstack.org/tc/reference/project-testing-interface.html Change-Id: I2415ec61c3580fcd43ee7d8f2a90b698ac156593 --- .zuul.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.zuul.yaml b/.zuul.yaml index 42c29d162d..404c005a41 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -222,7 +222,7 @@ - osc-tox-unit-tips - openstack-cover-jobs - openstack-lower-constraints-jobs - - openstack-python3-wallaby-jobs + - openstack-python3-xena-jobs - publish-openstack-docs-pti - check-requirements - release-notes-jobs-python3 From 383289edd8ea222ce1a6e77ed0298ecdb21608a1 Mon Sep 17 00:00:00 2001 From: Valery Tschopp Date: Tue, 2 Feb 2021 12:56:55 +0100 Subject: [PATCH 2365/3095] Implements hide image openstack image set [--hidden|--unhidden] IMAGE openstack image list --hidden Task: 41734 Story: 2008581 Change-Id: Ie84f10c0f7aa2e7b7f78bfadc70132a10673866e --- openstackclient/image/v2/image.py | 27 ++++++++ .../tests/unit/image/v2/test_image.py | 68 +++++++++++++++++++ ...mplements-hide-image-4c726a61c336ebaa.yaml | 8 +++ 3 files changed, 103 insertions(+) create mode 100644 releasenotes/notes/implements-hide-image-4c726a61c336ebaa.yaml diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index ad38c01ec2..644fbbb4f7 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -615,6 +615,12 @@ def get_parser(self, prog_name): default=None, help=_('Filter images based on tag.'), ) + parser.add_argument( + '--hidden', + action='store_true', + default=False, + help=_('List hidden images'), + ) parser.add_argument( '--long', action='store_true', @@ -686,6 +692,8 @@ def take_action(self, parsed_args): parsed_args.project_domain, ).id kwargs['owner'] = project_id + if parsed_args.hidden: + kwargs['is_hidden'] = True if parsed_args.long: columns = ( 'ID', @@ -1016,6 +1024,22 @@ def get_parser(self, prog_name): action="store_true", help=_("Reset the image membership to 'pending'"), ) + + hidden_group = parser.add_mutually_exclusive_group() + hidden_group.add_argument( + "--hidden", + dest='hidden', + default=None, + action="store_true", + help=_("Hide the image"), + ) + hidden_group.add_argument( + "--unhidden", + dest='hidden', + default=None, + action="store_false", + help=_("Unhide the image"), + ) return parser def take_action(self, parsed_args): @@ -1106,6 +1130,9 @@ def take_action(self, parsed_args): # Tags should be extended, but duplicates removed kwargs['tags'] = list(set(image.tags).union(set(parsed_args.tags))) + if parsed_args.hidden is not None: + kwargs['is_hidden'] = parsed_args.hidden + try: image = image_client.update_image(image.id, **kwargs) except Exception: diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py index 87dfdbeab9..c44c767b70 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -837,6 +837,20 @@ def test_image_list_status_option(self): status='active' ) + def test_image_list_hidden_option(self): + arglist = [ + '--hidden', + ] + verifylist = [ + ('hidden', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.client.images.assert_called_with( + is_hidden=True + ) + def test_image_list_tag_option(self): arglist = [ '--tag', 'abc', @@ -1439,6 +1453,60 @@ def test_image_set_numeric_options_to_zero(self): ) self.assertIsNone(result) + def test_image_set_hidden(self): + arglist = [ + '--hidden', + '--public', + image_fakes.image_name, + ] + verifylist = [ + ('hidden', True), + ('public', True), + ('private', False), + ('image', image_fakes.image_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + kwargs = { + 'is_hidden': True, + 'visibility': 'public', + } + # ImageManager.update(image, **kwargs) + self.client.update_image.assert_called_with( + self._image.id, + **kwargs + ) + self.assertIsNone(result) + + def test_image_set_unhidden(self): + arglist = [ + '--unhidden', + '--public', + image_fakes.image_name, + ] + verifylist = [ + ('hidden', False), + ('public', True), + ('private', False), + ('image', image_fakes.image_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + kwargs = { + 'is_hidden': False, + 'visibility': 'public', + } + # ImageManager.update(image, **kwargs) + self.client.update_image.assert_called_with( + self._image.id, + **kwargs + ) + self.assertIsNone(result) + class TestImageShow(TestImage): diff --git a/releasenotes/notes/implements-hide-image-4c726a61c336ebaa.yaml b/releasenotes/notes/implements-hide-image-4c726a61c336ebaa.yaml new file mode 100644 index 0000000000..718ac2920d --- /dev/null +++ b/releasenotes/notes/implements-hide-image-4c726a61c336ebaa.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Add mutually exclusive options ``--hidden`` and ``--unhidden`` to + ``image set`` command to hide or unhide an image (``is_hidden`` + attribute). + - | + Add option ``--hidden`` to ``image list`` command to list hidden images. From f00e14f400193e35c8040bc93ac6c509fa2bb4dc Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 31 Mar 2021 18:18:55 +0100 Subject: [PATCH 2366/3095] hacking: Remove references to encoding This is no longer an issue in our new Python 3-only world. Change-Id: I25c31a0b7f76a253499d9713ba48fd7ba7168450 Signed-off-by: Stephen Finucane --- HACKING.rst | 48 +----------------------------------------------- 1 file changed, 1 insertion(+), 47 deletions(-) diff --git a/HACKING.rst b/HACKING.rst index b5fbad3ca1..432d19f4e4 100644 --- a/HACKING.rst +++ b/HACKING.rst @@ -32,7 +32,7 @@ use the alternate 4 space indent. With the first argument on the succeeding line all arguments will then be vertically aligned. Use the same convention used with other data structure literals and terminate the method call with the last argument line ending with a comma and the closing paren on its own -line indented to the starting line level. +line indented to the starting line level. :: unnecessarily_long_function_name( 'string one', @@ -40,49 +40,3 @@ line indented to the starting line level. kwarg1=constants.ACTIVE, kwarg2=['a', 'b', 'c'], ) - -Text encoding -------------- - -Note: this section clearly has not been implemented in this project yet, it is -the intention to do so. - -All text within python code should be of type 'unicode'. - - WRONG: - - >>> s = 'foo' - >>> s - 'foo' - >>> type(s) - - - RIGHT: - - >>> u = u'foo' - >>> u - u'foo' - >>> type(u) - - -Transitions between internal unicode and external strings should always -be immediately and explicitly encoded or decoded. - -All external text that is not explicitly encoded (database storage, -commandline arguments, etc.) should be presumed to be encoded as utf-8. - - WRONG: - - infile = open('testfile', 'r') - mystring = infile.readline() - myreturnstring = do_some_magic_with(mystring) - outfile.write(myreturnstring) - - RIGHT: - - infile = open('testfile', 'r') - mystring = infile.readline() - mytext = mystring.decode('utf-8') - returntext = do_some_magic_with(mytext) - returnstring = returntext.encode('utf-8') - outfile.write(returnstring) From 168a4e73904f15c2fb34e98f8884ff76291ee287 Mon Sep 17 00:00:00 2001 From: zhangbailin Date: Thu, 8 Apr 2021 14:22:49 +0800 Subject: [PATCH 2367/3095] requirements: Drop os-testr os-testr has been decrepated [1], it's not necessary in a world with stestr. [1]https://opendev.org/openstack/os-testr/src/branch/master/README.rst Change-Id: Id2382f2c559ea7f4d4a629d137f07f0ce8841abc --- lower-constraints.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/lower-constraints.txt b/lower-constraints.txt index f93db8aa8c..09aabede1f 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -41,7 +41,6 @@ netifaces==0.10.4 openstacksdk==0.53.0 os-client-config==2.1.0 os-service-types==1.7.0 -os-testr==1.0.0 osc-lib==2.3.0 oslo.concurrency==3.26.0 oslo.config==5.2.0 From e82a05864f482acc485d1bd35a4db23452f8b2ac Mon Sep 17 00:00:00 2001 From: Dirk Mueller Date: Mon, 3 May 2021 22:07:39 +0200 Subject: [PATCH 2368/3095] Replace assertItemsEqual with assertCountEqual assertItemsEqual was removed from Python's unittest.TestCase in Python 3.3 [1][2]. We have been able to use them since then, because testtools required unittest2, which still included it. With testtools removing Python 2.7 support [3][4], we will lose support for assertItemsEqual, so we should switch to use assertCountEqual. [1] - https://bugs.python.org/issue17866 [2] - https://hg.python.org/cpython/rev/d9921cb6e3cd [3] - testing-cabal/testtools#286 [4] - testing-cabal/testtools#277 Change-Id: I0bbffbec8889b8b3067cfe17d258f5cb16624f38 --- .../tests/unit/compute/v2/test_aggregate.py | 16 ++--- .../tests/unit/compute/v2/test_flavor.py | 14 ++-- .../tests/unit/compute/v2/test_hypervisor.py | 6 +- .../unit/compute/v2/test_server_backup.py | 6 +- .../unit/compute/v2/test_server_image.py | 6 +- .../tests/unit/identity/v2_0/test_catalog.py | 6 +- .../tests/unit/identity/v2_0/test_project.py | 2 +- .../tests/unit/identity/v2_0/test_user.py | 8 +-- .../tests/unit/identity/v3/test_catalog.py | 4 +- .../identity/v3/test_identity_provider.py | 24 +++---- .../tests/unit/image/v1/test_image.py | 10 +-- .../tests/unit/image/v2/test_image.py | 24 +++---- .../unit/network/v2/test_address_group.py | 14 ++-- .../unit/network/v2/test_ip_availability.py | 8 +-- .../tests/unit/network/v2/test_network.py | 46 ++++++------- .../unit/network/v2/test_network_agent.py | 14 ++-- .../tests/unit/network/v2/test_port.py | 68 +++++++++---------- .../tests/unit/network/v2/test_router.py | 32 ++++----- .../network/v2/test_security_group_compute.py | 10 +-- .../network/v2/test_security_group_network.py | 16 ++--- .../tests/unit/network/v2/test_subnet.py | 42 ++++++------ .../tests/unit/network/v2/test_subnet_pool.py | 40 +++++------ .../tests/unit/volume/v1/test_qos_specs.py | 12 ++-- .../tests/unit/volume/v1/test_type.py | 14 ++-- .../tests/unit/volume/v1/test_volume.py | 36 +++++----- .../unit/volume/v1/test_volume_backup.py | 10 +-- .../unit/volume/v2/test_consistency_group.py | 14 ++-- .../tests/unit/volume/v2/test_qos_specs.py | 12 ++-- .../tests/unit/volume/v2/test_type.py | 24 +++---- .../tests/unit/volume/v2/test_volume.py | 40 +++++------ .../unit/volume/v2/test_volume_backup.py | 4 +- .../unit/volume/v2/test_volume_snapshot.py | 2 +- 32 files changed, 292 insertions(+), 292 deletions(-) diff --git a/openstackclient/tests/unit/compute/v2/test_aggregate.py b/openstackclient/tests/unit/compute/v2/test_aggregate.py index 8563f98811..7c4fe5cbd3 100644 --- a/openstackclient/tests/unit/compute/v2/test_aggregate.py +++ b/openstackclient/tests/unit/compute/v2/test_aggregate.py @@ -88,7 +88,7 @@ def test_aggregate_add_host(self): self.sdk_client.add_host_to_aggregate.assert_called_once_with( self.fake_ag.id, parsed_args.host) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) class TestAggregateCreate(TestAggregate): @@ -112,7 +112,7 @@ def test_aggregate_create(self): self.sdk_client.create_aggregate.assert_called_once_with( name=parsed_args.name) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_aggregate_create_with_zone(self): arglist = [ @@ -129,7 +129,7 @@ def test_aggregate_create_with_zone(self): self.sdk_client.create_aggregate.assert_called_once_with( name=parsed_args.name, availability_zone=parsed_args.zone) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_aggregate_create_with_property(self): arglist = [ @@ -148,7 +148,7 @@ def test_aggregate_create_with_property(self): self.sdk_client.set_aggregate_metadata.assert_called_once_with( self.fake_ag.id, parsed_args.properties) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) class TestAggregateDelete(TestAggregate): @@ -265,7 +265,7 @@ def test_aggregate_list(self): columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.list_columns, columns) - self.assertItemsEqual(self.list_data, tuple(data)) + self.assertCountEqual(self.list_data, tuple(data)) def test_aggregate_list_with_long(self): arglist = [ @@ -278,7 +278,7 @@ def test_aggregate_list_with_long(self): columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.list_columns_long, columns) - self.assertItemsEqual(self.list_data_long, tuple(data)) + self.assertCountEqual(self.list_data_long, tuple(data)) class TestAggregateRemoveHost(TestAggregate): @@ -306,7 +306,7 @@ def test_aggregate_remove_host(self): self.sdk_client.remove_host_from_aggregate.assert_called_once_with( self.fake_ag.id, parsed_args.host) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) class TestAggregateSet(TestAggregate): @@ -492,7 +492,7 @@ def test_aggregate_show(self): parsed_args.aggregate, ignore_missing=False) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, tuple(data)) + self.assertCountEqual(self.data, tuple(data)) class TestAggregateUnset(TestAggregate): diff --git a/openstackclient/tests/unit/compute/v2/test_flavor.py b/openstackclient/tests/unit/compute/v2/test_flavor.py index ee4479b009..14dd3df20a 100644 --- a/openstackclient/tests/unit/compute/v2/test_flavor.py +++ b/openstackclient/tests/unit/compute/v2/test_flavor.py @@ -133,7 +133,7 @@ def test_flavor_create_default_options(self): self.sdk_client.create_flavor.assert_called_once_with(**default_args) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_flavor_create_all_options(self): @@ -202,7 +202,7 @@ def test_flavor_create_all_options(self): self.sdk_client.get_flavor_access.assert_not_called() self.assertEqual(self.columns, columns) - self.assertItemsEqual(tuple(cmp_data), data) + self.assertCountEqual(tuple(cmp_data), data) def test_flavor_create_other_options(self): @@ -277,7 +277,7 @@ def test_flavor_create_other_options(self): self.sdk_client.create_flavor_extra_specs.assert_called_with( create_flavor, props) self.assertEqual(self.columns, columns) - self.assertItemsEqual(cmp_data, data) + self.assertCountEqual(cmp_data, data) def test_public_flavor_create_with_project(self): arglist = [ @@ -350,7 +350,7 @@ def test_flavor_create_with_description_api_newer(self): self.sdk_client.create_flavor.assert_called_once_with(**args) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data_private, data) + self.assertCountEqual(self.data_private, data) def test_flavor_create_with_description_api_older(self): arglist = [ @@ -633,7 +633,7 @@ def test_flavor_list_long(self): ) self.assertEqual(self.columns_long, columns) - self.assertItemsEqual(self.data_long, tuple(data)) + self.assertCountEqual(self.data_long, tuple(data)) def test_flavor_list_min_disk_min_ram(self): arglist = [ @@ -951,7 +951,7 @@ def test_public_flavor_show(self): columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_private_flavor_show(self): private_flavor = compute_fakes.FakeFlavor.create_one_flavor( @@ -991,7 +991,7 @@ def test_private_flavor_show(self): self.sdk_client.get_flavor_access.assert_called_with( flavor=private_flavor.id) self.assertEqual(self.columns, columns) - self.assertItemsEqual(data_with_project, data) + self.assertCountEqual(data_with_project, data) class TestFlavorUnset(TestFlavor): diff --git a/openstackclient/tests/unit/compute/v2/test_hypervisor.py b/openstackclient/tests/unit/compute/v2/test_hypervisor.py index 3220a76450..7dbd6e1983 100644 --- a/openstackclient/tests/unit/compute/v2/test_hypervisor.py +++ b/openstackclient/tests/unit/compute/v2/test_hypervisor.py @@ -394,7 +394,7 @@ def test_hypervisor_show(self): columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_hypervisor_show_pre_v228(self): self.app.client_manager.compute.api_version = \ @@ -420,7 +420,7 @@ def test_hypervisor_show_pre_v228(self): columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_hypervisor_show_uptime_not_implemented(self): self.app.client_manager.compute.api_version = \ @@ -492,4 +492,4 @@ def test_hypervisor_show_uptime_not_implemented(self): ) self.assertEqual(expected_columns, columns) - self.assertItemsEqual(expected_data, data) + self.assertCountEqual(expected_data, data) diff --git a/openstackclient/tests/unit/compute/v2/test_server_backup.py b/openstackclient/tests/unit/compute/v2/test_server_backup.py index 753db9cd70..0012d70062 100644 --- a/openstackclient/tests/unit/compute/v2/test_server_backup.py +++ b/openstackclient/tests/unit/compute/v2/test_server_backup.py @@ -139,7 +139,7 @@ def test_server_backup_defaults(self): ) self.assertEqual(self.image_columns(images[0]), columns) - self.assertItemsEqual(self.image_data(images[0]), data) + self.assertCountEqual(self.image_data(images[0]), data) def test_server_backup_create_options(self): servers = self.setup_servers_mock(count=1) @@ -173,7 +173,7 @@ def test_server_backup_create_options(self): ) self.assertEqual(self.image_columns(images[0]), columns) - self.assertItemsEqual(self.image_data(images[0]), data) + self.assertCountEqual(self.image_data(images[0]), data) @mock.patch.object(common_utils, 'wait_for_status', return_value=False) def test_server_backup_wait_fail(self, mock_wait_for_status): @@ -269,4 +269,4 @@ def test_server_backup_wait_ok(self, mock_wait_for_status): ) self.assertEqual(self.image_columns(images[0]), columns) - self.assertItemsEqual(self.image_data(images[0]), data) + self.assertCountEqual(self.image_data(images[0]), data) diff --git a/openstackclient/tests/unit/compute/v2/test_server_image.py b/openstackclient/tests/unit/compute/v2/test_server_image.py index 66452a8bb8..9b14428a27 100644 --- a/openstackclient/tests/unit/compute/v2/test_server_image.py +++ b/openstackclient/tests/unit/compute/v2/test_server_image.py @@ -134,7 +134,7 @@ def test_server_image_create_defaults(self): ) self.assertEqual(self.image_columns(images[0]), columns) - self.assertItemsEqual(self.image_data(images[0]), data) + self.assertCountEqual(self.image_data(images[0]), data) def test_server_image_create_options(self): servers = self.setup_servers_mock(count=1) @@ -165,7 +165,7 @@ def test_server_image_create_options(self): ) self.assertEqual(self.image_columns(images[0]), columns) - self.assertItemsEqual(self.image_data(images[0]), data) + self.assertCountEqual(self.image_data(images[0]), data) @mock.patch.object(common_utils, 'wait_for_status', return_value=False) def test_server_create_image_wait_fail(self, mock_wait_for_status): @@ -235,4 +235,4 @@ def test_server_create_image_wait_ok(self, mock_wait_for_status): ) self.assertEqual(self.image_columns(images[0]), columns) - self.assertItemsEqual(self.image_data(images[0]), data) + self.assertCountEqual(self.image_data(images[0]), data) diff --git a/openstackclient/tests/unit/identity/v2_0/test_catalog.py b/openstackclient/tests/unit/identity/v2_0/test_catalog.py index e2c56ba1ec..bfb28f6962 100644 --- a/openstackclient/tests/unit/identity/v2_0/test_catalog.py +++ b/openstackclient/tests/unit/identity/v2_0/test_catalog.py @@ -74,7 +74,7 @@ def test_catalog_list(self): catalog.EndpointsColumn( auth_ref.service_catalog.catalog[0]['endpoints']), ), ) - self.assertItemsEqual(datalist, tuple(data)) + self.assertCountEqual(datalist, tuple(data)) def test_catalog_list_with_endpoint_url(self): attr = { @@ -117,7 +117,7 @@ def test_catalog_list_with_endpoint_url(self): catalog.EndpointsColumn( auth_ref.service_catalog.catalog[0]['endpoints']), ), ) - self.assertItemsEqual(datalist, tuple(data)) + self.assertCountEqual(datalist, tuple(data)) class TestCatalogShow(TestCatalog): @@ -158,7 +158,7 @@ def test_catalog_show(self): 'supernova', 'compute', ) - self.assertItemsEqual(datalist, data) + self.assertCountEqual(datalist, data) class TestFormatColumns(TestCatalog): diff --git a/openstackclient/tests/unit/identity/v2_0/test_project.py b/openstackclient/tests/unit/identity/v2_0/test_project.py index 766d5dab55..496214aaee 100644 --- a/openstackclient/tests/unit/identity/v2_0/test_project.py +++ b/openstackclient/tests/unit/identity/v2_0/test_project.py @@ -643,7 +643,7 @@ def test_project_show(self): self.fake_proj_show.name, format_columns.DictColumn({}), ) - self.assertItemsEqual(datalist, data) + self.assertCountEqual(datalist, data) class TestProjectUnset(TestProject): diff --git a/openstackclient/tests/unit/identity/v2_0/test_user.py b/openstackclient/tests/unit/identity/v2_0/test_user.py index dd30047814..c3f5f1d7a1 100644 --- a/openstackclient/tests/unit/identity/v2_0/test_user.py +++ b/openstackclient/tests/unit/identity/v2_0/test_user.py @@ -482,7 +482,7 @@ def test_user_list_no_options(self): self.users_mock.list.assert_called_with(tenant_id=None) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, tuple(data)) + self.assertCountEqual(self.datalist, tuple(data)) def test_user_list_project(self): arglist = [ @@ -502,7 +502,7 @@ def test_user_list_project(self): self.users_mock.list.assert_called_with(tenant_id=project_id) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, tuple(data)) + self.assertCountEqual(self.datalist, tuple(data)) def test_user_list_long(self): arglist = [ @@ -531,7 +531,7 @@ def test_user_list_long(self): self.fake_user_l.email, True, ), ) - self.assertItemsEqual(datalist, tuple(data)) + self.assertCountEqual(datalist, tuple(data)) class TestUserSet(TestUser): @@ -819,4 +819,4 @@ def test_user_show(self): self.fake_user.name, self.fake_project.id, ) - self.assertItemsEqual(datalist, data) + self.assertCountEqual(datalist, data) diff --git a/openstackclient/tests/unit/identity/v3/test_catalog.py b/openstackclient/tests/unit/identity/v3/test_catalog.py index 97ce48f6a5..802a9017e2 100644 --- a/openstackclient/tests/unit/identity/v3/test_catalog.py +++ b/openstackclient/tests/unit/identity/v3/test_catalog.py @@ -94,7 +94,7 @@ def test_catalog_list(self): catalog.EndpointsColumn( auth_ref.service_catalog.catalog[0]['endpoints']), ), ) - self.assertItemsEqual(datalist, tuple(data)) + self.assertCountEqual(datalist, tuple(data)) class TestCatalogShow(TestCatalog): @@ -135,7 +135,7 @@ def test_catalog_show(self): 'supernova', 'compute', ) - self.assertItemsEqual(datalist, data) + self.assertCountEqual(datalist, data) class TestFormatColumns(TestCatalog): diff --git a/openstackclient/tests/unit/identity/v3/test_identity_provider.py b/openstackclient/tests/unit/identity/v3/test_identity_provider.py index 5aff2b1be2..1a9a799123 100644 --- a/openstackclient/tests/unit/identity/v3/test_identity_provider.py +++ b/openstackclient/tests/unit/identity/v3/test_identity_provider.py @@ -89,7 +89,7 @@ def test_create_identity_provider_no_options(self): ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) def test_create_identity_provider_description(self): arglist = [ @@ -117,7 +117,7 @@ def test_create_identity_provider_description(self): ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) def test_create_identity_provider_remote_id(self): arglist = [ @@ -145,7 +145,7 @@ def test_create_identity_provider_remote_id(self): ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) def test_create_identity_provider_remote_ids_multiple(self): arglist = [ @@ -174,7 +174,7 @@ def test_create_identity_provider_remote_ids_multiple(self): ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) def test_create_identity_provider_remote_ids_file(self): arglist = [ @@ -207,7 +207,7 @@ def test_create_identity_provider_remote_ids_file(self): ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) def test_create_identity_provider_disabled(self): @@ -250,7 +250,7 @@ def test_create_identity_provider_disabled(self): identity_fakes.idp_id, identity_fakes.formatted_idp_remote_ids ) - self.assertItemsEqual(datalist, data) + self.assertCountEqual(datalist, data) def test_create_identity_provider_domain_name(self): arglist = [ @@ -278,7 +278,7 @@ def test_create_identity_provider_domain_name(self): ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) def test_create_identity_provider_domain_id(self): arglist = [ @@ -306,7 +306,7 @@ def test_create_identity_provider_domain_id(self): ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) class TestIdentityProviderDelete(TestIdentityProvider): @@ -382,7 +382,7 @@ def test_identity_provider_list_no_options(self): identity_fakes.domain_id, identity_fakes.idp_description, ), ) - self.assertItemsEqual(datalist, tuple(data)) + self.assertCountEqual(datalist, tuple(data)) def test_identity_provider_list_ID_option(self): arglist = ['--id', @@ -410,7 +410,7 @@ def test_identity_provider_list_ID_option(self): identity_fakes.domain_id, identity_fakes.idp_description, ), ) - self.assertItemsEqual(datalist, tuple(data)) + self.assertCountEqual(datalist, tuple(data)) def test_identity_provider_list_enabled_option(self): arglist = ['--enabled'] @@ -437,7 +437,7 @@ def test_identity_provider_list_enabled_option(self): identity_fakes.domain_id, identity_fakes.idp_description, ), ) - self.assertItemsEqual(datalist, tuple(data)) + self.assertCountEqual(datalist, tuple(data)) class TestIdentityProviderSet(TestIdentityProvider): @@ -722,4 +722,4 @@ def test_identity_provider_show(self): identity_fakes.idp_id, identity_fakes.formatted_idp_remote_ids ) - self.assertItemsEqual(datalist, data) + self.assertCountEqual(datalist, data) diff --git a/openstackclient/tests/unit/image/v1/test_image.py b/openstackclient/tests/unit/image/v1/test_image.py index db64983c97..5c69bf0f87 100644 --- a/openstackclient/tests/unit/image/v1/test_image.py +++ b/openstackclient/tests/unit/image/v1/test_image.py @@ -100,7 +100,7 @@ def test_image_reserve_no_options(self, raw_input): self.assertEqual(self.client.update_image.call_args_list, []) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) @mock.patch('sys.stdin', side_effect=[None]) def test_image_reserve_options(self, raw_input): @@ -149,7 +149,7 @@ def test_image_reserve_options(self, raw_input): self.assertEqual(self.client.update_image.call_args_list, []) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) @mock.patch('openstackclient.image.v1.image.io.open', name='Open') def test_image_create_file(self, mock_open): @@ -205,7 +205,7 @@ def test_image_create_file(self, mock_open): self.assertEqual(self.client.update_image.call_args_list, []) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) class TestImageDelete(TestImage): @@ -386,7 +386,7 @@ def test_image_list_long_option(self): format_columns.DictColumn( {'Alpha': 'a', 'Beta': 'b', 'Gamma': 'g'}), ), ) - self.assertItemsEqual(datalist, tuple(data)) + self.assertCountEqual(datalist, tuple(data)) @mock.patch('osc_lib.api.utils.simple_filter') def test_image_list_property_option(self, sf_mock): @@ -737,7 +737,7 @@ def test_image_show(self): ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_image_show_human_readable(self): arglist = [ diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py index c44c767b70..35af6799f6 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -111,7 +111,7 @@ def test_image_reserve_no_options(self, raw_input): self.assertEqual( self.expected_columns, columns) - self.assertItemsEqual( + self.assertCountEqual( self.expected_data, data) @@ -166,7 +166,7 @@ def test_image_reserve_options(self, raw_input): self.assertEqual( self.expected_columns, columns) - self.assertItemsEqual( + self.assertCountEqual( self.expected_data, data) @@ -255,7 +255,7 @@ def test_image_create_file(self): self.assertEqual( self.expected_columns, columns) - self.assertItemsEqual( + self.assertCountEqual( self.expected_data, data) @@ -513,7 +513,7 @@ def test_image_list_no_options(self): ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, tuple(data)) + self.assertCountEqual(self.datalist, tuple(data)) def test_image_list_public_option(self): arglist = [ @@ -537,7 +537,7 @@ def test_image_list_public_option(self): ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, tuple(data)) + self.assertCountEqual(self.datalist, tuple(data)) def test_image_list_private_option(self): arglist = [ @@ -561,7 +561,7 @@ def test_image_list_private_option(self): ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, tuple(data)) + self.assertCountEqual(self.datalist, tuple(data)) def test_image_list_community_option(self): arglist = [ @@ -609,7 +609,7 @@ def test_image_list_shared_option(self): ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, tuple(data)) + self.assertCountEqual(self.datalist, tuple(data)) def test_image_list_shared_member_status_option(self): arglist = [ @@ -697,7 +697,7 @@ def test_image_list_long_option(self): self._image.owner_id, format_columns.ListColumn(self._image.tags), ), ) - self.assertItemsEqual(datalist, tuple(data)) + self.assertCountEqual(datalist, tuple(data)) @mock.patch('osc_lib.api.utils.simple_filter') def test_image_list_property_option(self, sf_mock): @@ -725,7 +725,7 @@ def test_image_list_property_option(self, sf_mock): ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, tuple(data)) + self.assertCountEqual(self.datalist, tuple(data)) @mock.patch('osc_lib.utils.sort_items') def test_image_list_sort_option(self, si_mock): @@ -747,7 +747,7 @@ def test_image_list_sort_option(self, si_mock): str, ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, tuple(data)) + self.assertCountEqual(self.datalist, tuple(data)) def test_image_list_limit_option(self): ret_limit = 1 @@ -782,7 +782,7 @@ def test_image_list_project_option(self): columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, tuple(data)) + self.assertCountEqual(self.datalist, tuple(data)) @mock.patch('osc_lib.utils.find_resource') def test_image_list_marker_option(self, fr_mock): @@ -1555,7 +1555,7 @@ def test_image_show(self): ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_image_show_human_readable(self): self.client.find_image.return_value = self.new_image diff --git a/openstackclient/tests/unit/network/v2/test_address_group.py b/openstackclient/tests/unit/network/v2/test_address_group.py index e4fa8ab3dc..a5ee83cb56 100644 --- a/openstackclient/tests/unit/network/v2/test_address_group.py +++ b/openstackclient/tests/unit/network/v2/test_address_group.py @@ -99,7 +99,7 @@ def test_create_default_options(self): 'addresses': [], }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_all_options(self): arglist = [ @@ -127,7 +127,7 @@ def test_create_all_options(self): 'description': self.new_address_group.description, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) class TestDeleteAddressGroup(TestAddressGroup): @@ -252,7 +252,7 @@ def test_address_group_list(self): self.network.address_groups.assert_called_once_with(**{}) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_address_group_list_name(self): arglist = [ @@ -267,7 +267,7 @@ def test_address_group_list_name(self): self.network.address_groups.assert_called_once_with( **{'name': self.address_groups[0].name}) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_address_group_list_project(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -284,7 +284,7 @@ def test_address_group_list_project(self): self.network.address_groups.assert_called_once_with( project_id=project.id) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_address_group_project_domain(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -302,7 +302,7 @@ def test_address_group_project_domain(self): self.network.address_groups.assert_called_once_with( project_id=project.id) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) class TestSetAddressGroup(TestAddressGroup): @@ -438,7 +438,7 @@ def test_show_all_options(self): self.network.find_address_group.assert_called_once_with( self._address_group.name, ignore_missing=False) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) class TestUnsetAddressGroup(TestAddressGroup): diff --git a/openstackclient/tests/unit/network/v2/test_ip_availability.py b/openstackclient/tests/unit/network/v2/test_ip_availability.py index ade5783700..a722a02391 100644 --- a/openstackclient/tests/unit/network/v2/test_ip_availability.py +++ b/openstackclient/tests/unit/network/v2/test_ip_availability.py @@ -75,7 +75,7 @@ def test_list_no_options(self): self.network.network_ip_availabilities.assert_called_once_with( **filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_list_ip_version(self): arglist = [ @@ -93,7 +93,7 @@ def test_list_ip_version(self): self.network.network_ip_availabilities.assert_called_once_with( **filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_list_project(self): arglist = [ @@ -113,7 +113,7 @@ def test_list_project(self): self.network.network_ip_availabilities.assert_called_once_with( **filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) class TestShowIPAvailability(TestIPAvailability): @@ -176,4 +176,4 @@ def test_show_all_options(self): self._ip_availability.network_name, ignore_missing=False) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) diff --git a/openstackclient/tests/unit/network/v2/test_network.py b/openstackclient/tests/unit/network/v2/test_network.py index e29b72c769..127d82b015 100644 --- a/openstackclient/tests/unit/network/v2/test_network.py +++ b/openstackclient/tests/unit/network/v2/test_network.py @@ -146,7 +146,7 @@ def test_create_default_options(self): }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_all_options(self): arglist = [ @@ -211,7 +211,7 @@ def test_create_all_options(self): 'dns_domain': 'example.org.', }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_other_options(self): arglist = [ @@ -238,7 +238,7 @@ def test_create_other_options(self): 'port_security_enabled': False, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def _test_create_with_tag(self, add_tags=True): arglist = [self._network.name] @@ -270,7 +270,7 @@ def _test_create_with_tag(self, add_tags=True): else: self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_with_tags(self): self._test_create_with_tag(add_tags=True) @@ -385,7 +385,7 @@ def test_create_with_project_identityv2(self): }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_with_domain_identityv2(self): arglist = [ @@ -577,7 +577,7 @@ def test_network_list_no_options(self): self.network.networks.assert_called_once_with() self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_list_external(self): arglist = [ @@ -598,7 +598,7 @@ def test_list_external(self): **{'router:external': True, 'is_router_external': True} ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_list_internal(self): arglist = [ @@ -615,7 +615,7 @@ def test_list_internal(self): **{'router:external': False, 'is_router_external': False} ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_network_list_long(self): arglist = [ @@ -634,7 +634,7 @@ def test_network_list_long(self): self.network.networks.assert_called_once_with() self.assertEqual(self.columns_long, columns) - self.assertItemsEqual(self.data_long, list(data)) + self.assertCountEqual(self.data_long, list(data)) def test_list_name(self): test_name = "fakename" @@ -653,7 +653,7 @@ def test_list_name(self): **{'name': test_name} ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_network_list_enable(self): arglist = [ @@ -671,7 +671,7 @@ def test_network_list_enable(self): **{'admin_state_up': True, 'is_admin_state_up': True} ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_network_list_disable(self): arglist = [ @@ -689,7 +689,7 @@ def test_network_list_disable(self): **{'admin_state_up': False, 'is_admin_state_up': False} ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_network_list_project(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -708,7 +708,7 @@ def test_network_list_project(self): ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_network_list_project_domain(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -727,7 +727,7 @@ def test_network_list_project_domain(self): self.network.networks.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_network_list_share(self): arglist = [ @@ -744,7 +744,7 @@ def test_network_list_share(self): **{'shared': True, 'is_shared': True} ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_network_list_no_share(self): arglist = [ @@ -761,7 +761,7 @@ def test_network_list_no_share(self): **{'shared': False, 'is_shared': False} ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_network_list_status(self): choices = ['ACTIVE', 'BUILD', 'DOWN', 'ERROR'] @@ -780,7 +780,7 @@ def test_network_list_status(self): **{'status': test_status} ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_network_list_provider_network_type(self): network_type = self._network[0].provider_network_type @@ -798,7 +798,7 @@ def test_network_list_provider_network_type(self): 'provider_network_type': network_type} ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_network_list_provider_physical_network(self): physical_network = self._network[0].provider_physical_network @@ -816,7 +816,7 @@ def test_network_list_provider_physical_network(self): 'provider_physical_network': physical_network} ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_network_list_provider_segment(self): segmentation_id = self._network[0].provider_segmentation_id @@ -834,7 +834,7 @@ def test_network_list_provider_segment(self): 'provider_segmentation_id': segmentation_id} ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_network_list_dhcp_agent(self): arglist = [ @@ -853,7 +853,7 @@ def test_network_list_dhcp_agent(self): *attrs) self.assertEqual(self.columns, columns) - self.assertItemsEqual(list(data), list(self.data)) + self.assertCountEqual(list(data), list(self.data)) def test_list_with_tag_options(self): arglist = [ @@ -878,7 +878,7 @@ def test_list_with_tag_options(self): 'not_any_tags': 'black,white'} ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) class TestSetNetwork(TestNetwork): @@ -1111,7 +1111,7 @@ def test_show_all_options(self): self._network.name, ignore_missing=False) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) class TestUnsetNetwork(TestNetwork): diff --git a/openstackclient/tests/unit/network/v2/test_network_agent.py b/openstackclient/tests/unit/network/v2/test_network_agent.py index fceac68ea0..734a36ee94 100644 --- a/openstackclient/tests/unit/network/v2/test_network_agent.py +++ b/openstackclient/tests/unit/network/v2/test_network_agent.py @@ -246,7 +246,7 @@ def test_network_agents_list(self): self.network.agents.assert_called_once_with(**{}) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_network_agents_list_agent_type(self): arglist = [ @@ -263,7 +263,7 @@ def test_network_agents_list_agent_type(self): 'agent_type': 'DHCP agent', }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_network_agents_list_host(self): arglist = [ @@ -280,7 +280,7 @@ def test_network_agents_list_host(self): 'host': self.network_agents[0].host, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_network_agents_list_networks(self): arglist = [ @@ -298,7 +298,7 @@ def test_network_agents_list_networks(self): self.network.network_hosting_dhcp_agents.assert_called_once_with( *attrs) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_network_agents_list_routers(self): arglist = [ @@ -318,7 +318,7 @@ def test_network_agents_list_routers(self): *attrs) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_network_agents_list_routers_with_long_option(self): arglist = [ @@ -343,7 +343,7 @@ def test_network_agents_list_routers_with_long_option(self): router_agent_data = [d + ('',) for d in self.data] self.assertEqual(router_agent_columns, columns) - self.assertItemsEqual(router_agent_data, list(data)) + self.assertCountEqual(router_agent_data, list(data)) class TestRemoveNetworkFromAgent(TestNetworkAgent): @@ -571,4 +571,4 @@ def test_show_all_options(self): self.network.get_agent.assert_called_once_with( self._network_agent.id) self.assertEqual(self.columns, columns) - self.assertItemsEqual(list(self.data), list(data)) + self.assertCountEqual(list(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 8c5158d7c8..5f2a12836a 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -153,7 +153,7 @@ def test_create_default_options(self): self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_full_options(self): arglist = [ @@ -211,7 +211,7 @@ def test_create_full_options(self): }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_invalid_json_binding_profile(self): arglist = [ @@ -262,7 +262,7 @@ def test_create_json_binding_profile(self): }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_with_security_group(self): secgroup = network_fakes.FakeSecurityGroup.create_one_security_group() @@ -291,7 +291,7 @@ def test_create_with_security_group(self): }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_port_with_dns_name(self): arglist = [ @@ -317,7 +317,7 @@ def test_create_port_with_dns_name(self): }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_with_security_groups(self): sg_1 = network_fakes.FakeSecurityGroup.create_one_security_group() @@ -347,7 +347,7 @@ def test_create_with_security_groups(self): }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_with_no_security_groups(self): arglist = [ @@ -373,7 +373,7 @@ def test_create_with_no_security_groups(self): }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_with_no_fixed_ips(self): arglist = [ @@ -399,7 +399,7 @@ def test_create_with_no_fixed_ips(self): }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_port_with_allowed_address_pair_ipaddr(self): pairs = [{'ip_address': '192.168.1.123'}, @@ -429,7 +429,7 @@ def test_create_port_with_allowed_address_pair_ipaddr(self): }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_port_with_allowed_address_pair(self): pairs = [{'ip_address': '192.168.1.123', @@ -465,7 +465,7 @@ def test_create_port_with_allowed_address_pair(self): }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_port_with_qos(self): qos_policy = network_fakes.FakeNetworkQosPolicy.create_one_qos_policy() @@ -493,7 +493,7 @@ def test_create_port_with_qos(self): }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_port_security_enabled(self): arglist = [ @@ -602,7 +602,7 @@ def _test_create_with_tag(self, add_tags=True, add_tags_in_post=True): self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_with_tags(self): self._test_create_with_tag(add_tags=True, add_tags_in_post=True) @@ -645,7 +645,7 @@ def _test_create_with_uplink_status_propagation(self, enable=True): }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_with_uplink_status_propagation_enabled(self): self._test_create_with_uplink_status_propagation(enable=True) @@ -725,7 +725,7 @@ def _test_create_with_numa_affinity_policy(self, policy=None): self.network.create_port.assert_called_once_with(**create_args) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_with_numa_affinity_policy_required(self): self._test_create_with_numa_affinity_policy(policy='required') @@ -764,7 +764,7 @@ def test_create_with_device_profile(self): } self.network.create_port.assert_called_once_with(**create_args) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) class TestDeletePort(TestPort): @@ -919,7 +919,7 @@ def test_port_list_no_options(self): self.network.ports.assert_called_once_with( fields=LIST_FIELDS_TO_RETRIEVE) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_port_list_router_opt(self): arglist = [ @@ -939,7 +939,7 @@ def test_port_list_router_opt(self): 'fields': LIST_FIELDS_TO_RETRIEVE, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) @mock.patch.object(utils, 'find_resource') def test_port_list_with_server_option(self, mock_find): @@ -960,7 +960,7 @@ def test_port_list_with_server_option(self, mock_find): fields=LIST_FIELDS_TO_RETRIEVE) mock_find.assert_called_once_with(mock.ANY, 'fake-server-name') self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_port_list_device_id_opt(self): arglist = [ @@ -980,7 +980,7 @@ def test_port_list_device_id_opt(self): 'fields': LIST_FIELDS_TO_RETRIEVE, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_port_list_device_owner_opt(self): arglist = [ @@ -1000,7 +1000,7 @@ def test_port_list_device_owner_opt(self): 'fields': LIST_FIELDS_TO_RETRIEVE, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_port_list_all_opt(self): arglist = [ @@ -1029,7 +1029,7 @@ def test_port_list_all_opt(self): 'fields': LIST_FIELDS_TO_RETRIEVE, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_port_list_mac_address_opt(self): arglist = [ @@ -1049,7 +1049,7 @@ def test_port_list_mac_address_opt(self): 'fields': LIST_FIELDS_TO_RETRIEVE, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_port_list_fixed_ip_opt_ip_address(self): ip_address = self._ports[0].fixed_ips[0]['ip_address'] @@ -1069,7 +1069,7 @@ def test_port_list_fixed_ip_opt_ip_address(self): 'fields': LIST_FIELDS_TO_RETRIEVE, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_port_list_fixed_ip_opt_ip_address_substr(self): ip_address_ss = self._ports[0].fixed_ips[0]['ip_address'][:-1] @@ -1089,7 +1089,7 @@ def test_port_list_fixed_ip_opt_ip_address_substr(self): 'fields': LIST_FIELDS_TO_RETRIEVE, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_port_list_fixed_ip_opt_subnet_id(self): subnet_id = self._ports[0].fixed_ips[0]['subnet_id'] @@ -1111,7 +1111,7 @@ def test_port_list_fixed_ip_opt_subnet_id(self): 'fields': LIST_FIELDS_TO_RETRIEVE, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_port_list_fixed_ip_opts(self): subnet_id = self._ports[0].fixed_ips[0]['subnet_id'] @@ -1137,7 +1137,7 @@ def test_port_list_fixed_ip_opts(self): 'fields': LIST_FIELDS_TO_RETRIEVE, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_port_list_fixed_ips(self): subnet_id = self._ports[0].fixed_ips[0]['subnet_id'] @@ -1165,7 +1165,7 @@ def test_port_list_fixed_ips(self): 'fields': LIST_FIELDS_TO_RETRIEVE, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_list_port_with_long(self): arglist = [ @@ -1183,7 +1183,7 @@ def test_list_port_with_long(self): self.network.ports.assert_called_once_with( fields=LIST_FIELDS_TO_RETRIEVE + LIST_FIELDS_TO_RETRIEVE_LONG) self.assertEqual(self.columns_long, columns) - self.assertItemsEqual(self.data_long, list(data)) + self.assertCountEqual(self.data_long, list(data)) def test_port_list_host(self): arglist = [ @@ -1202,7 +1202,7 @@ def test_port_list_host(self): self.network.ports.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_port_list_project(self): project = identity_fakes.FakeProject.create_one_project() @@ -1224,7 +1224,7 @@ def test_port_list_project(self): self.network.ports.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_port_list_project_domain(self): project = identity_fakes.FakeProject.create_one_project() @@ -1248,7 +1248,7 @@ def test_port_list_project_domain(self): self.network.ports.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_port_list_name(self): test_name = "fakename" @@ -1268,7 +1268,7 @@ def test_port_list_name(self): self.network.ports.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_list_with_tag_options(self): arglist = [ @@ -1294,7 +1294,7 @@ def test_list_with_tag_options(self): 'fields': LIST_FIELDS_TO_RETRIEVE} ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) class TestSetPort(TestPort): @@ -1894,7 +1894,7 @@ def test_show_all_options(self): self._port.name, ignore_missing=False) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) class TestUnsetPort(TestPort): diff --git a/openstackclient/tests/unit/network/v2/test_router.py b/openstackclient/tests/unit/network/v2/test_router.py index 323c919828..0324674817 100644 --- a/openstackclient/tests/unit/network/v2/test_router.py +++ b/openstackclient/tests/unit/network/v2/test_router.py @@ -184,7 +184,7 @@ def test_create_default_options(self): }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def _test_create_with_ha_options(self, option, ha): arglist = [ @@ -208,7 +208,7 @@ def _test_create_with_ha_options(self, option, ha): 'ha': ha, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_with_ha_option(self): self._test_create_with_ha_options('--ha', True) @@ -237,7 +237,7 @@ def _test_create_with_distributed_options(self, option, distributed): 'distributed': distributed, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_with_distributed_option(self): self._test_create_with_distributed_options('--distributed', True) @@ -268,7 +268,7 @@ def test_create_with_AZ_hints(self): }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def _test_create_with_tag(self, add_tags=True): arglist = [self.new_router.name] @@ -301,7 +301,7 @@ def _test_create_with_tag(self, add_tags=True): else: self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_with_tags(self): self._test_create_with_tag(add_tags=True) @@ -494,7 +494,7 @@ def test_router_list_no_options(self): self.network.routers.assert_called_once_with() self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_router_list_no_ha_no_distributed(self): _routers = network_fakes.FakeRouter.create_routers({ @@ -531,7 +531,7 @@ def test_router_list_long(self): self.network.routers.assert_called_once_with() self.assertEqual(self.columns_long, columns) - self.assertItemsEqual(self.data_long, list(data)) + self.assertCountEqual(self.data_long, list(data)) def test_router_list_long_no_az(self): arglist = [ @@ -552,7 +552,7 @@ def test_router_list_long_no_az(self): self.network.routers.assert_called_once_with() self.assertEqual(self.columns_long_no_az, columns) - self.assertItemsEqual(self.data_long_no_az, list(data)) + self.assertCountEqual(self.data_long_no_az, list(data)) def test_list_name(self): test_name = "fakename" @@ -570,7 +570,7 @@ def test_list_name(self): **{'name': test_name} ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_router_list_enable(self): arglist = [ @@ -587,7 +587,7 @@ def test_router_list_enable(self): **{'admin_state_up': True, 'is_admin_state_up': True} ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_router_list_disable(self): arglist = [ @@ -605,7 +605,7 @@ def test_router_list_disable(self): ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_router_list_project(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -623,7 +623,7 @@ def test_router_list_project(self): self.network.routers.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_router_list_project_domain(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -643,7 +643,7 @@ def test_router_list_project_domain(self): self.network.routers.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_router_list_agents_no_args(self): arglist = [ @@ -671,7 +671,7 @@ def test_router_list_agents(self): self.network.agent_hosted_routers( *attrs) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_list_with_tag_options(self): arglist = [ @@ -696,7 +696,7 @@ def test_list_with_tag_options(self): 'not_any_tags': 'black,white'} ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) class TestRemovePortFromRouter(TestRouter): @@ -1403,7 +1403,7 @@ def test_show_all_options(self): 'device_id': self._router.id }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_show_no_ha_no_distributed(self): _router = network_fakes.FakeRouter.create_one_router({ diff --git a/openstackclient/tests/unit/network/v2/test_security_group_compute.py b/openstackclient/tests/unit/network/v2/test_security_group_compute.py index 837c9b21af..4f1ddce590 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_compute.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_compute.py @@ -88,7 +88,7 @@ def test_security_group_create_min_options(self, sg_mock): self._security_group['name'], ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_security_group_create_all_options(self, sg_mock): sg_mock.return_value = self._security_group @@ -109,7 +109,7 @@ def test_security_group_create_all_options(self, sg_mock): self._security_group['description'], ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) @mock.patch( @@ -255,7 +255,7 @@ def test_security_group_list_no_options(self, sg_mock): kwargs = {'search_opts': {'all_tenants': False}} sg_mock.assert_called_once_with(**kwargs) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_security_group_list_all_projects(self, sg_mock): sg_mock.return_value = self._security_groups @@ -272,7 +272,7 @@ def test_security_group_list_all_projects(self, sg_mock): kwargs = {'search_opts': {'all_tenants': True}} sg_mock.assert_called_once_with(**kwargs) self.assertEqual(self.columns_all_projects, columns) - self.assertItemsEqual(self.data_all_projects, list(data)) + self.assertCountEqual(self.data_all_projects, list(data)) @mock.patch( @@ -401,4 +401,4 @@ def test_security_group_show_all_options(self, sg_mock): sg_mock.assert_called_once_with(self._security_group['id']) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) diff --git a/openstackclient/tests/unit/network/v2/test_security_group_network.py b/openstackclient/tests/unit/network/v2/test_security_group_network.py index fe37778598..569c0cd5f0 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_network.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_network.py @@ -96,7 +96,7 @@ def test_create_min_options(self): 'name': self._security_group.name, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_all_options(self): arglist = [ @@ -124,7 +124,7 @@ def test_create_all_options(self): 'tenant_id': self.project.id, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def _test_create_with_tag(self, add_tags=True): arglist = [self._security_group.name] @@ -155,7 +155,7 @@ def _test_create_with_tag(self, add_tags=True): else: self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_with_tags(self): self._test_create_with_tag(add_tags=True) @@ -293,7 +293,7 @@ def test_security_group_list_no_options(self): self.network.security_groups.assert_called_once_with( fields=security_group.ListSecurityGroup.FIELDS_TO_RETRIEVE) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_security_group_list_all_projects(self): arglist = [ @@ -309,7 +309,7 @@ def test_security_group_list_all_projects(self): self.network.security_groups.assert_called_once_with( fields=security_group.ListSecurityGroup.FIELDS_TO_RETRIEVE) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_security_group_list_project(self): project = identity_fakes.FakeProject.create_one_project() @@ -329,7 +329,7 @@ def test_security_group_list_project(self): self.network.security_groups.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_security_group_list_project_domain(self): project = identity_fakes.FakeProject.create_one_project() @@ -351,7 +351,7 @@ def test_security_group_list_project_domain(self): self.network.security_groups.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_list_with_tag_options(self): arglist = [ @@ -539,7 +539,7 @@ def test_show_all_options(self): self.network.find_security_group.assert_called_once_with( self._security_group.id, ignore_missing=False) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) class TestUnsetSecurityGroupNetwork(TestSecurityGroupNetwork): diff --git a/openstackclient/tests/unit/network/v2/test_subnet.py b/openstackclient/tests/unit/network/v2/test_subnet.py index 6085cda8a1..5147b64d2a 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet.py +++ b/openstackclient/tests/unit/network/v2/test_subnet.py @@ -255,7 +255,7 @@ def test_create_default_options(self): }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_from_subnet_pool_options(self): # Mock SDK calls for this test. @@ -317,7 +317,7 @@ def test_create_from_subnet_pool_options(self): 'service_types': self._subnet_from_pool.service_types, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data_subnet_pool, data) + self.assertCountEqual(self.data_subnet_pool, data) def test_create_options_subnet_range_ipv6(self): # Mock SDK calls for this test. @@ -390,7 +390,7 @@ def test_create_options_subnet_range_ipv6(self): }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data_ipv6, data) + self.assertCountEqual(self.data_ipv6, data) def test_create_with_network_segment(self): # Mock SDK calls for this test. @@ -424,7 +424,7 @@ def test_create_with_network_segment(self): }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_with_description(self): # Mock SDK calls for this test. @@ -458,7 +458,7 @@ def test_create_with_description(self): }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def _test_create_with_dns(self, publish_dns=True): arglist = [ @@ -490,7 +490,7 @@ def _test_create_with_dns(self, publish_dns=True): dns_publish_fixed_ip=publish_dns, ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_with_dns(self): self._test_create_with_dns(publish_dns=True) @@ -535,7 +535,7 @@ def _test_create_with_tag(self, add_tags=True): else: self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_with_tags(self): self._test_create_with_tag(add_tags=True) @@ -691,7 +691,7 @@ def test_subnet_list_no_options(self): self.network.subnets.assert_called_once_with() self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_subnet_list_long(self): arglist = [ @@ -706,7 +706,7 @@ def test_subnet_list_long(self): self.network.subnets.assert_called_once_with() self.assertEqual(self.columns_long, columns) - self.assertItemsEqual(self.data_long, list(data)) + self.assertCountEqual(self.data_long, list(data)) def test_subnet_list_ip_version(self): arglist = [ @@ -722,7 +722,7 @@ def test_subnet_list_ip_version(self): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_subnet_list_dhcp(self): arglist = [ @@ -738,7 +738,7 @@ def test_subnet_list_dhcp(self): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_subnet_list_no_dhcp(self): arglist = [ @@ -754,7 +754,7 @@ def test_subnet_list_no_dhcp(self): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_subnet_list_service_type(self): arglist = [ @@ -769,7 +769,7 @@ def test_subnet_list_service_type(self): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_subnet_list_project(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -787,7 +787,7 @@ def test_subnet_list_project(self): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_subnet_list_service_type_multiple(self): arglist = [ @@ -805,7 +805,7 @@ def test_subnet_list_service_type_multiple(self): 'network:floatingip_agent_gateway']} self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_subnet_list_project_domain(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -825,7 +825,7 @@ def test_subnet_list_project_domain(self): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_subnet_list_network(self): network = network_fakes.FakeNetwork.create_one_network() @@ -843,7 +843,7 @@ def test_subnet_list_network(self): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_subnet_list_gateway(self): subnet = network_fakes.FakeSubnet.create_one_subnet() @@ -861,7 +861,7 @@ def test_subnet_list_gateway(self): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_subnet_list_name(self): subnet = network_fakes.FakeSubnet.create_one_subnet() @@ -879,7 +879,7 @@ def test_subnet_list_name(self): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_subnet_list_subnet_range(self): subnet = network_fakes.FakeSubnet.create_one_subnet() @@ -897,7 +897,7 @@ def test_subnet_list_subnet_range(self): self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_list_with_tag_options(self): arglist = [ @@ -1244,7 +1244,7 @@ def test_show_all_options(self): self._subnet.name, ignore_missing=False) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) class TestUnsetSubnet(TestSubnet): diff --git a/openstackclient/tests/unit/network/v2/test_subnet_pool.py b/openstackclient/tests/unit/network/v2/test_subnet_pool.py index 243fc76df4..4d18dc99b4 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/unit/network/v2/test_subnet_pool.py @@ -133,7 +133,7 @@ def test_create_default_options(self): }) self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_prefixlen_options(self): arglist = [ @@ -163,7 +163,7 @@ def test_create_prefixlen_options(self): 'name': self._subnet_pool.name, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_len_negative(self): arglist = [ @@ -201,7 +201,7 @@ def test_create_project_domain(self): 'name': self._subnet_pool.name, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_address_scope_option(self): arglist = [ @@ -224,7 +224,7 @@ def test_create_address_scope_option(self): 'name': self._subnet_pool.name, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_default_and_shared_options(self): arglist = [ @@ -250,7 +250,7 @@ def test_create_default_and_shared_options(self): 'shared': True, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_with_description(self): arglist = [ @@ -273,7 +273,7 @@ def test_create_with_description(self): 'description': self._subnet_pool.description, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_with_default_quota(self): arglist = [ @@ -294,7 +294,7 @@ def test_create_with_default_quota(self): 'default_quota': 10, }) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def _test_create_with_tag(self, add_tags=True): arglist = [ @@ -328,7 +328,7 @@ def _test_create_with_tag(self, add_tags=True): else: self.assertFalse(self.network.set_tags.called) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_create_with_tags(self): self._test_create_with_tag(add_tags=True) @@ -476,7 +476,7 @@ def test_subnet_pool_list_no_option(self): self.network.subnet_pools.assert_called_once_with() self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_subnet_pool_list_long(self): arglist = [ @@ -491,7 +491,7 @@ def test_subnet_pool_list_long(self): self.network.subnet_pools.assert_called_once_with() self.assertEqual(self.columns_long, columns) - self.assertItemsEqual(self.data_long, list(data)) + self.assertCountEqual(self.data_long, list(data)) def test_subnet_pool_list_no_share(self): arglist = [ @@ -507,7 +507,7 @@ def test_subnet_pool_list_no_share(self): self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_subnet_pool_list_share(self): arglist = [ @@ -523,7 +523,7 @@ def test_subnet_pool_list_share(self): self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_subnet_pool_list_no_default(self): arglist = [ @@ -539,7 +539,7 @@ def test_subnet_pool_list_no_default(self): self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_subnet_pool_list_default(self): arglist = [ @@ -555,7 +555,7 @@ def test_subnet_pool_list_default(self): self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_subnet_pool_list_project(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -573,7 +573,7 @@ def test_subnet_pool_list_project(self): self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_subnet_pool_list_project_domain(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -593,7 +593,7 @@ def test_subnet_pool_list_project_domain(self): self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_subnet_pool_list_name(self): subnet_pool = network_fakes.FakeSubnetPool.create_one_subnet_pool() @@ -611,7 +611,7 @@ def test_subnet_pool_list_name(self): self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_subnet_pool_list_address_scope(self): addr_scope = network_fakes.FakeAddressScope.create_one_address_scope() @@ -629,7 +629,7 @@ def test_subnet_pool_list_address_scope(self): self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_list_with_tag_options(self): arglist = [ @@ -654,7 +654,7 @@ def test_list_with_tag_options(self): 'not_any_tags': 'black,white'} ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) class TestSetSubnetPool(TestSubnetPool): @@ -1008,7 +1008,7 @@ def test_show_all_options(self): ignore_missing=False ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) class TestUnsetSubnetPool(TestSubnetPool): diff --git a/openstackclient/tests/unit/volume/v1/test_qos_specs.py b/openstackclient/tests/unit/volume/v1/test_qos_specs.py index 5500438bd6..15c20561e2 100644 --- a/openstackclient/tests/unit/volume/v1/test_qos_specs.py +++ b/openstackclient/tests/unit/volume/v1/test_qos_specs.py @@ -109,7 +109,7 @@ def test_qos_create_without_properties(self): ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) def test_qos_create_with_consumer(self): arglist = [ @@ -129,7 +129,7 @@ def test_qos_create_with_consumer(self): {'consumer': self.new_qos_spec.consumer} ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) def test_qos_create_with_properties(self): arglist = [ @@ -155,7 +155,7 @@ def test_qos_create_with_properties(self): ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) class TestQosDelete(TestQos): @@ -350,7 +350,7 @@ def test_qos_list(self): self.qos_mock.list.assert_called_with() self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_qos_list_no_association(self): self.qos_mock.reset_mock() @@ -377,7 +377,7 @@ def test_qos_list_no_association(self): format_columns.ListColumn(None), format_columns.DictColumn(self.qos_specs[1].specs), ) - self.assertItemsEqual(ex_data, list(data)) + self.assertCountEqual(ex_data, list(data)) class TestQosSet(TestQos): @@ -454,7 +454,7 @@ def test_qos_show(self): self.qos_spec.name, format_columns.DictColumn(self.qos_spec.specs), ) - self.assertItemsEqual(datalist, tuple(data)) + self.assertCountEqual(datalist, tuple(data)) class TestQosUnset(TestQos): diff --git a/openstackclient/tests/unit/volume/v1/test_type.py b/openstackclient/tests/unit/volume/v1/test_type.py index f1d469140b..be47f5db59 100644 --- a/openstackclient/tests/unit/volume/v1/test_type.py +++ b/openstackclient/tests/unit/volume/v1/test_type.py @@ -78,7 +78,7 @@ def test_type_create(self): ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_type_create_with_encryption(self): encryption_info = { @@ -139,7 +139,7 @@ def test_type_create_with_encryption(self): body, ) self.assertEqual(encryption_columns, columns) - self.assertItemsEqual(encryption_data, data) + self.assertCountEqual(encryption_data, data) class TestTypeDelete(TestType): @@ -270,7 +270,7 @@ def test_type_list_without_options(self): columns, data = self.cmd.take_action(parsed_args) self.types_mock.list.assert_called_once_with() self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_type_list_with_options(self): arglist = [ @@ -284,7 +284,7 @@ def test_type_list_with_options(self): columns, data = self.cmd.take_action(parsed_args) self.types_mock.list.assert_called_once_with() self.assertEqual(self.columns_long, columns) - self.assertItemsEqual(self.data_long, list(data)) + self.assertCountEqual(self.data_long, list(data)) def test_type_list_with_encryption(self): encryption_type = volume_fakes.FakeType.create_one_encryption_type( @@ -328,7 +328,7 @@ def test_type_list_with_encryption(self): self.encryption_types_mock.list.assert_called_once_with() self.types_mock.list.assert_called_once_with() self.assertEqual(encryption_columns, columns) - self.assertItemsEqual(encryption_data, list(data)) + self.assertCountEqual(encryption_data, list(data)) class TestTypeSet(TestType): @@ -469,7 +469,7 @@ def test_type_show(self): self.types_mock.get.assert_called_with(self.volume_type.id) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_type_show_with_encryption(self): encryption_type = volume_fakes.FakeType.create_one_encryption_type() @@ -513,7 +513,7 @@ def test_type_show_with_encryption(self): self.types_mock.get.assert_called_with(self.volume_type.id) self.encryption_types_mock.get.assert_called_with(self.volume_type.id) self.assertEqual(encryption_columns, columns) - self.assertItemsEqual(encryption_data, data) + self.assertCountEqual(encryption_data, data) class TestTypeUnset(TestType): diff --git a/openstackclient/tests/unit/volume/v1/test_volume.py b/openstackclient/tests/unit/volume/v1/test_volume.py index 704a66da71..de0c99c291 100644 --- a/openstackclient/tests/unit/volume/v1/test_volume.py +++ b/openstackclient/tests/unit/volume/v1/test_volume.py @@ -135,7 +135,7 @@ def test_volume_create_min_options(self): None, ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) def test_volume_create_options(self): arglist = [ @@ -179,7 +179,7 @@ def test_volume_create_options(self): ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) def test_volume_create_user_project_id(self): # Return a project @@ -226,7 +226,7 @@ def test_volume_create_user_project_id(self): ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) def test_volume_create_user_project_name(self): # Return a project @@ -273,7 +273,7 @@ def test_volume_create_user_project_name(self): ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) def test_volume_create_properties(self): arglist = [ @@ -314,7 +314,7 @@ def test_volume_create_properties(self): ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) def test_volume_create_image_id(self): image = image_fakes.FakeImage.create_one_image() @@ -357,7 +357,7 @@ def test_volume_create_image_id(self): ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) def test_volume_create_image_name(self): image = image_fakes.FakeImage.create_one_image() @@ -400,7 +400,7 @@ def test_volume_create_image_name(self): ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) def test_volume_create_with_source(self): self.volumes_mock.get.return_value = self.new_volume @@ -430,7 +430,7 @@ def test_volume_create_with_source(self): None, ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) def test_volume_create_with_bootable_and_readonly(self): arglist = [ @@ -468,7 +468,7 @@ def test_volume_create_with_bootable_and_readonly(self): ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) self.volumes_mock.set_bootable.assert_called_with( self.new_volume.id, True) self.volumes_mock.update_readonly_flag.assert_called_with( @@ -510,7 +510,7 @@ def test_volume_create_with_nonbootable_and_readwrite(self): ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) self.volumes_mock.set_bootable.assert_called_with( self.new_volume.id, False) self.volumes_mock.update_readonly_flag.assert_called_with( @@ -562,7 +562,7 @@ def test_volume_create_with_bootable_and_readonly_fail( self.assertEqual(2, mock_error.call_count) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) self.volumes_mock.set_bootable.assert_called_with( self.new_volume.id, True) self.volumes_mock.update_readonly_flag.assert_called_with( @@ -765,7 +765,7 @@ def test_volume_list_no_options(self): columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, tuple(data)) + self.assertCountEqual(self.datalist, tuple(data)) def test_volume_list_name(self): arglist = [ @@ -782,7 +782,7 @@ def test_volume_list_name(self): columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, tuple(columns)) - self.assertItemsEqual(self.datalist, tuple(data)) + self.assertCountEqual(self.datalist, tuple(data)) def test_volume_list_status(self): arglist = [ @@ -799,7 +799,7 @@ def test_volume_list_status(self): columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, tuple(columns)) - self.assertItemsEqual(self.datalist, tuple(data)) + self.assertCountEqual(self.datalist, tuple(data)) def test_volume_list_all_projects(self): arglist = [ @@ -816,7 +816,7 @@ def test_volume_list_all_projects(self): columns, data = self.cmd.take_action(parsed_args) self.assertEqual(self.columns, tuple(columns)) - self.assertItemsEqual(self.datalist, tuple(data)) + self.assertCountEqual(self.datalist, tuple(data)) def test_volume_list_long(self): arglist = [ @@ -856,7 +856,7 @@ def test_volume_list_long(self): volume.AttachmentsColumn(self._volume.attachments), format_columns.DictColumn(self._volume.metadata), ), ) - self.assertItemsEqual(datalist, tuple(data)) + self.assertCountEqual(datalist, tuple(data)) def test_volume_list_with_limit(self): arglist = [ @@ -881,7 +881,7 @@ def test_volume_list_with_limit(self): 'all_tenants': False, } ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, tuple(data)) + self.assertCountEqual(self.datalist, tuple(data)) def test_volume_list_negative_limit(self): arglist = [ @@ -1272,7 +1272,7 @@ def test_volume_show(self): self.volumes_mock.get.assert_called_with(self._volume.id) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) def test_volume_show_backward_compatibility(self): arglist = [ diff --git a/openstackclient/tests/unit/volume/v1/test_volume_backup.py b/openstackclient/tests/unit/volume/v1/test_volume_backup.py index a713155092..f25a5ffa6d 100644 --- a/openstackclient/tests/unit/volume/v1/test_volume_backup.py +++ b/openstackclient/tests/unit/volume/v1/test_volume_backup.py @@ -100,7 +100,7 @@ def test_backup_create(self): self.new_backup.description, ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_backup_create_without_name(self): arglist = [ @@ -124,7 +124,7 @@ def test_backup_create_without_name(self): self.new_backup.description, ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) class TestBackupDelete(TestBackup): @@ -277,7 +277,7 @@ def test_backup_list_without_options(self): search_opts=search_opts, ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_backup_list_with_options(self): arglist = [ @@ -309,7 +309,7 @@ def test_backup_list_with_options(self): search_opts=search_opts, ) self.assertEqual(self.columns_long, columns) - self.assertItemsEqual(self.data_long, list(data)) + self.assertCountEqual(self.data_long, list(data)) class TestBackupRestore(TestBackup): @@ -391,4 +391,4 @@ def test_backup_show(self): self.backups_mock.get.assert_called_with(self.backup.id) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) diff --git a/openstackclient/tests/unit/volume/v2/test_consistency_group.py b/openstackclient/tests/unit/volume/v2/test_consistency_group.py index 6bb6c02978..7fd5187170 100644 --- a/openstackclient/tests/unit/volume/v2/test_consistency_group.py +++ b/openstackclient/tests/unit/volume/v2/test_consistency_group.py @@ -251,7 +251,7 @@ def test_consistency_group_create_without_name(self): ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_consistency_group_create_from_source(self): arglist = [ @@ -279,7 +279,7 @@ def test_consistency_group_create_from_source(self): ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_consistency_group_create_from_snapshot(self): arglist = [ @@ -307,7 +307,7 @@ def test_consistency_group_create_from_snapshot(self): ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) class TestConsistencyGroupDelete(TestConsistencyGroup): @@ -463,7 +463,7 @@ def test_consistency_group_list_without_options(self): self.consistencygroups_mock.list.assert_called_once_with( detailed=True, search_opts={'all_tenants': False}) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_consistency_group_list_with_all_project(self): arglist = [ @@ -480,7 +480,7 @@ def test_consistency_group_list_with_all_project(self): self.consistencygroups_mock.list.assert_called_once_with( detailed=True, search_opts={'all_tenants': True}) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_consistency_group_list_with_long(self): arglist = [ @@ -497,7 +497,7 @@ def test_consistency_group_list_with_long(self): self.consistencygroups_mock.list.assert_called_once_with( detailed=True, search_opts={'all_tenants': False}) self.assertEqual(self.columns_long, columns) - self.assertItemsEqual(self.data_long, list(data)) + self.assertCountEqual(self.data_long, list(data)) class TestConsistencyGroupRemoveVolume(TestConsistencyGroup): @@ -705,4 +705,4 @@ def test_consistency_group_show(self): self.consistencygroups_mock.get.assert_called_once_with( self.consistency_group.id) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) diff --git a/openstackclient/tests/unit/volume/v2/test_qos_specs.py b/openstackclient/tests/unit/volume/v2/test_qos_specs.py index bc4cee8b40..8f8d26c864 100644 --- a/openstackclient/tests/unit/volume/v2/test_qos_specs.py +++ b/openstackclient/tests/unit/volume/v2/test_qos_specs.py @@ -112,7 +112,7 @@ def test_qos_create_without_properties(self): ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_qos_create_with_consumer(self): arglist = [ @@ -133,7 +133,7 @@ def test_qos_create_with_consumer(self): ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_qos_create_with_properties(self): arglist = [ @@ -159,7 +159,7 @@ def test_qos_create_with_properties(self): ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) class TestQosDelete(TestQos): @@ -342,7 +342,7 @@ def test_qos_list(self): self.qos_mock.list.assert_called_with() self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_qos_list_no_association(self): self.qos_mock.reset_mock() @@ -369,7 +369,7 @@ def test_qos_list_no_association(self): format_columns.ListColumn(None), format_columns.DictColumn(self.qos_specs[1].specs), ) - self.assertItemsEqual(ex_data, list(data)) + self.assertCountEqual(ex_data, list(data)) class TestQosSet(TestQos): @@ -449,7 +449,7 @@ def test_qos_show(self): ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, tuple(data)) + self.assertCountEqual(self.data, tuple(data)) class TestQosUnset(TestQos): diff --git a/openstackclient/tests/unit/volume/v2/test_type.py b/openstackclient/tests/unit/volume/v2/test_type.py index 000464c5d8..f718f4c4b8 100644 --- a/openstackclient/tests/unit/volume/v2/test_type.py +++ b/openstackclient/tests/unit/volume/v2/test_type.py @@ -93,7 +93,7 @@ def test_type_create_public(self): ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_type_create_private(self): arglist = [ @@ -119,7 +119,7 @@ def test_type_create_private(self): ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_public_type_create_with_project(self): arglist = [ @@ -196,7 +196,7 @@ def test_type_create_with_encryption(self): body, ) self.assertEqual(encryption_columns, columns) - self.assertItemsEqual(encryption_data, data) + self.assertCountEqual(encryption_data, data) class TestTypeDelete(TestType): @@ -330,7 +330,7 @@ def test_type_list_without_options(self): columns, data = self.cmd.take_action(parsed_args) self.types_mock.list.assert_called_once_with(is_public=None) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_type_list_with_options(self): arglist = [ @@ -348,7 +348,7 @@ def test_type_list_with_options(self): columns, data = self.cmd.take_action(parsed_args) self.types_mock.list.assert_called_once_with(is_public=True) self.assertEqual(self.columns_long, columns) - self.assertItemsEqual(self.data_long, list(data)) + self.assertCountEqual(self.data_long, list(data)) def test_type_list_with_private_option(self): arglist = [ @@ -365,7 +365,7 @@ def test_type_list_with_private_option(self): columns, data = self.cmd.take_action(parsed_args) self.types_mock.list.assert_called_once_with(is_public=False) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_type_list_with_default_option(self): arglist = [ @@ -383,7 +383,7 @@ def test_type_list_with_default_option(self): columns, data = self.cmd.take_action(parsed_args) self.types_mock.default.assert_called_once_with() self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data_with_default_type, list(data)) + self.assertCountEqual(self.data_with_default_type, list(data)) def test_type_list_with_encryption(self): encryption_type = volume_fakes.FakeType.create_one_encryption_type( @@ -427,7 +427,7 @@ def test_type_list_with_encryption(self): self.encryption_types_mock.list.assert_called_once_with() self.types_mock.list.assert_called_once_with(is_public=None) self.assertEqual(encryption_columns, columns) - self.assertItemsEqual(encryption_data, list(data)) + self.assertCountEqual(encryption_data, list(data)) class TestTypeSet(TestType): @@ -713,7 +713,7 @@ def test_type_show(self): self.types_mock.get.assert_called_with(self.volume_type.id) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) def test_type_show_with_access(self): arglist = [ @@ -746,7 +746,7 @@ def test_type_show_with_access(self): private_type.name, format_columns.DictColumn(private_type.extra_specs) ) - self.assertItemsEqual(private_type_data, data) + self.assertCountEqual(private_type_data, data) def test_type_show_with_list_access_exec(self): arglist = [ @@ -778,7 +778,7 @@ def test_type_show_with_list_access_exec(self): private_type.name, format_columns.DictColumn(private_type.extra_specs) ) - self.assertItemsEqual(private_type_data, data) + self.assertCountEqual(private_type_data, data) def test_type_show_with_encryption(self): encryption_type = volume_fakes.FakeType.create_one_encryption_type() @@ -824,7 +824,7 @@ def test_type_show_with_encryption(self): self.types_mock.get.assert_called_with(self.volume_type.id) self.encryption_types_mock.get.assert_called_with(self.volume_type.id) self.assertEqual(encryption_columns, columns) - self.assertItemsEqual(encryption_data, data) + self.assertCountEqual(encryption_data, data) class TestTypeUnset(TestType): diff --git a/openstackclient/tests/unit/volume/v2/test_volume.py b/openstackclient/tests/unit/volume/v2/test_volume.py index b9fe4e834c..31ba6036c4 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume.py +++ b/openstackclient/tests/unit/volume/v2/test_volume.py @@ -136,7 +136,7 @@ def test_volume_create_min_options(self): ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) def test_volume_create_options(self): consistency_group = ( @@ -182,7 +182,7 @@ def test_volume_create_options(self): ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) def test_volume_create_properties(self): arglist = [ @@ -218,7 +218,7 @@ def test_volume_create_properties(self): ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) def test_volume_create_image_id(self): image = image_fakes.FakeImage.create_one_image() @@ -256,7 +256,7 @@ def test_volume_create_image_id(self): ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) def test_volume_create_image_name(self): image = image_fakes.FakeImage.create_one_image() @@ -294,7 +294,7 @@ def test_volume_create_image_name(self): ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) def test_volume_create_with_snapshot(self): snapshot = volume_fakes.FakeSnapshot.create_one_snapshot() @@ -331,7 +331,7 @@ def test_volume_create_with_snapshot(self): ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) def test_volume_create_with_bootable_and_readonly(self): arglist = [ @@ -369,7 +369,7 @@ def test_volume_create_with_bootable_and_readonly(self): ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) self.volumes_mock.set_bootable.assert_called_with( self.new_volume.id, True) self.volumes_mock.update_readonly_flag.assert_called_with( @@ -411,7 +411,7 @@ def test_volume_create_with_nonbootable_and_readwrite(self): ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) self.volumes_mock.set_bootable.assert_called_with( self.new_volume.id, False) self.volumes_mock.update_readonly_flag.assert_called_with( @@ -463,7 +463,7 @@ def test_volume_create_with_bootable_and_readonly_fail( self.assertEqual(2, mock_error.call_count) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.datalist, data) + self.assertCountEqual(self.datalist, data) self.volumes_mock.set_bootable.assert_called_with( self.new_volume.id, True) self.volumes_mock.update_readonly_flag.assert_called_with( @@ -680,7 +680,7 @@ def test_volume_list_no_options(self): self.mock_volume.size, volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertItemsEqual(datalist, tuple(data)) + self.assertCountEqual(datalist, tuple(data)) def test_volume_list_project(self): arglist = [ @@ -720,7 +720,7 @@ def test_volume_list_project(self): self.mock_volume.size, volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertItemsEqual(datalist, tuple(data)) + self.assertCountEqual(datalist, tuple(data)) def test_volume_list_project_domain(self): arglist = [ @@ -762,7 +762,7 @@ def test_volume_list_project_domain(self): self.mock_volume.size, volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertItemsEqual(datalist, tuple(data)) + self.assertCountEqual(datalist, tuple(data)) def test_volume_list_user(self): arglist = [ @@ -801,7 +801,7 @@ def test_volume_list_user(self): self.mock_volume.size, volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertItemsEqual(datalist, tuple(data)) + self.assertCountEqual(datalist, tuple(data)) def test_volume_list_user_domain(self): arglist = [ @@ -843,7 +843,7 @@ def test_volume_list_user_domain(self): self.mock_volume.size, volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertItemsEqual(datalist, tuple(data)) + self.assertCountEqual(datalist, tuple(data)) def test_volume_list_name(self): arglist = [ @@ -883,7 +883,7 @@ def test_volume_list_name(self): self.mock_volume.size, volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertItemsEqual(datalist, tuple(data)) + self.assertCountEqual(datalist, tuple(data)) def test_volume_list_status(self): arglist = [ @@ -923,7 +923,7 @@ def test_volume_list_status(self): self.mock_volume.size, volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertItemsEqual(datalist, tuple(data)) + self.assertCountEqual(datalist, tuple(data)) def test_volume_list_all_projects(self): arglist = [ @@ -963,7 +963,7 @@ def test_volume_list_all_projects(self): self.mock_volume.size, volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertItemsEqual(datalist, tuple(data)) + self.assertCountEqual(datalist, tuple(data)) def test_volume_list_long(self): arglist = [ @@ -1017,7 +1017,7 @@ def test_volume_list_long(self): volume.AttachmentsColumn(self.mock_volume.attachments), format_columns.DictColumn(self.mock_volume.metadata), ), ) - self.assertItemsEqual(datalist, tuple(data)) + self.assertCountEqual(datalist, tuple(data)) def test_volume_list_with_marker_and_limit(self): arglist = [ @@ -1056,7 +1056,7 @@ def test_volume_list_with_marker_and_limit(self): 'name': None, 'all_tenants': False, } ) - self.assertItemsEqual(datalist, tuple(data)) + self.assertCountEqual(datalist, tuple(data)) def test_volume_list_negative_limit(self): arglist = [ @@ -1450,7 +1450,7 @@ def test_volume_show(self): volume_fakes.FakeVolume.get_volume_columns(self._volume), columns) - self.assertItemsEqual( + self.assertCountEqual( volume_fakes.FakeVolume.get_volume_data(self._volume), data) diff --git a/openstackclient/tests/unit/volume/v2/test_volume_backup.py b/openstackclient/tests/unit/volume/v2/test_volume_backup.py index 13513ed8ad..97f64ce7ad 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume_backup.py +++ b/openstackclient/tests/unit/volume/v2/test_volume_backup.py @@ -314,7 +314,7 @@ def test_backup_list_without_options(self): limit=None, ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_backup_list_with_options(self): arglist = [ @@ -353,7 +353,7 @@ def test_backup_list_with_options(self): limit=3, ) self.assertEqual(self.columns_long, columns) - self.assertItemsEqual(self.data_long, list(data)) + self.assertCountEqual(self.data_long, list(data)) class TestBackupRestore(TestBackup): diff --git a/openstackclient/tests/unit/volume/v2/test_volume_snapshot.py b/openstackclient/tests/unit/volume/v2/test_volume_snapshot.py index 3830f458d6..33a5a98a35 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume_snapshot.py +++ b/openstackclient/tests/unit/volume/v2/test_volume_snapshot.py @@ -707,7 +707,7 @@ def test_snapshot_show(self): self.snapshots_mock.get.assert_called_with(self.snapshot.id) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, data) + self.assertCountEqual(self.data, data) class TestVolumeSnapshotUnset(TestVolumeSnapshot): From 3918622968073738e8fa17eec8bf5512ed609af9 Mon Sep 17 00:00:00 2001 From: Cyril Roelandt Date: Wed, 5 May 2021 01:26:51 +0200 Subject: [PATCH 2369/3095] openstack image create: honor protection/visibility flags The --protected, --unprotected, --public, --shared, --community, --private flags were ignored when using --volume. Change-Id: Id5c05ef7d7bb0a04b9d7a9d821e544e1ff7b3d28 Story: 2008882 --- openstackclient/image/v2/image.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 644fbbb4f7..c1f46d2d99 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -490,6 +490,8 @@ def take_action(self, parsed_args): parsed_args.name, parsed_args.container_format, parsed_args.disk_format, + visibility=kwargs.get('visibility', 'private'), + protected=True if parsed_args.protected else False ) info = body['os-volume_upload_image'] try: From 1f0fcbcd1d480be8c097a5a663dd97b0bdcac2bc Mon Sep 17 00:00:00 2001 From: Jens Harbott Date: Tue, 11 May 2021 11:22:59 +0000 Subject: [PATCH 2370/3095] Fix the functional-tips tox environment The egg for the keystoneauth project is actually called keystonauth1. Seems newer pip actually complains about the difference and fails. Change-Id: I1602832d33cd467745a03b36c9b1545cd069ba1d --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index ebcdc2d5d9..5f9bf73b20 100644 --- a/tox.ini +++ b/tox.ini @@ -84,7 +84,7 @@ setenv = OS_TEST_PATH=./openstackclient/tests/functional passenv = OS_* commands = python -m pip install -q -U -e "git+file://{toxinidir}/../cliff#egg=cliff" - python -m pip install -q -U -e "git+file://{toxinidir}/../keystoneauth#egg=keystoneauth" + python -m pip install -q -U -e "git+file://{toxinidir}/../keystoneauth#egg=keystoneauth1" python -m pip install -q -U -e "git+file://{toxinidir}/../osc-lib#egg=osc_lib" python -m pip install -q -U -e "git+file://{toxinidir}/../openstacksdk#egg=openstacksdk" python -m pip freeze From 05807ee0dba1eb4d87943817a0efd234278589eb Mon Sep 17 00:00:00 2001 From: Slawek Kaplonski Date: Thu, 13 May 2021 22:37:38 +0200 Subject: [PATCH 2371/3095] Set ML2/OVS backend explicitly in the devstack jobs Neutron team recently switched default backend used in Neutron by Devstack to OVN. With that backend some tests, like e.g. related to DHCP or L3 agents aren't working fine. So to have still the same test coverage as we had before, let's explicitly set ML2/OVS as a Neutron's backend in those CI jobs. Change-Id: Idf6466a59c6cf96be2f1d53e696f0564584fa233 --- .zuul.yaml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.zuul.yaml b/.zuul.yaml index 404c005a41..e7b01da409 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -88,11 +88,28 @@ # NOTE(amotoki): Some neutron features are enabled by devstack plugin neutron: https://opendev.org/openstack/neutron devstack_services: + # Disable OVN services + br-ex-tcpdump: false + br-int-flows: false + ovn-controller: false + ovn-northd: false + ovs-vswitchd: false + ovsdb-server: false + q-ovn-metadata-agent: false + # Neutron services + q-agt: true + q-dhcp: true + q-l3: true + q-meta: true neutron-network-segment-range: true neutron-segments: true q-metering: true q-qos: true neutron-tag-ports-during-bulk-creation: true + devstack_localrc: + Q_AGENT: openvswitch + Q_ML2_TENANT_NETWORK_TYPE: vxlan + Q_ML2_PLUGIN_MECHANISM_DRIVERS: openvswitch tox_envlist: functional - job: From b019a561876b51523021654f3b499c962ce54fb6 Mon Sep 17 00:00:00 2001 From: Brian Rosmaita Date: Thu, 20 May 2021 09:18:26 -0400 Subject: [PATCH 2372/3095] Add check for cinderclient.v2 support Block Storage API v2 support is being removed from the cinderclient during the Xena development cycle [0], so add a check to determine whether the available cinderclient has v2 support. [0] https://wiki.openstack.org/wiki/CinderXenaPTGSummary#Removing_the_Block_Storage_API_v2 Change-Id: Id54da1704d94526071f500c36a6e38d6d84aa7b8 --- openstackclient/volume/client.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openstackclient/volume/client.py b/openstackclient/volume/client.py index 64e8b9f34d..37cb41685c 100644 --- a/openstackclient/volume/client.py +++ b/openstackclient/volume/client.py @@ -42,11 +42,15 @@ def make_client(instance): from cinderclient.v3 import volume_snapshots from cinderclient.v3 import volumes - # Try a small import to check if cinderclient v1 is supported + # Check whether the available cinderclient supports v1 or v2 try: from cinderclient.v1 import services # noqa except Exception: del API_VERSIONS['1'] + try: + from cinderclient.v2 import services # noqa + except Exception: + del API_VERSIONS['2'] version = instance._api_version[API_NAME] from cinderclient import api_versions From b1a41904c367872727d3daf4caf58756ae07c417 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 20 May 2021 16:58:57 +0100 Subject: [PATCH 2373/3095] compute: Update 'server resize --revert', '--confirm' help Update the help strings for these two arguments to indicate their deprecated nature. This was previously flagged via a deprecation warning but users would only see that if they were to run the command. Change-Id: I31a5e27ac8bd2625a6073b54a51bf3e8d6126c8c Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index b81d2a181c..468c6b1b3e 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -3769,12 +3769,20 @@ def get_parser(self, prog_name): phase_group.add_argument( '--confirm', action="store_true", - help=_('Confirm server resize is complete'), + help=_( + "**Deprecated** Confirm server resize is complete. " + "Replaced by the 'openstack server resize confirm' and " + "'openstack server migration confirm' commands" + ), ) phase_group.add_argument( '--revert', action="store_true", - help=_('Restore server state before resize'), + help=_( + '**Deprecated** Restore server state before resize' + "Replaced by the 'openstack server resize revert' and " + "'openstack server migration revert' commands" + ), ) parser.add_argument( '--wait', From b26b7f3440d4f756c0b7906b93751d7e83a733f7 Mon Sep 17 00:00:00 2001 From: Slawek Kaplonski Date: Tue, 22 Dec 2020 15:31:44 +0100 Subject: [PATCH 2374/3095] Allow to send extra attributes in Neutron related commands To deprecate and drop support for neutronclient CLI and use only OSC we need feature parity between OSC and neutronclient. Last missing piece here is possibility to send in POST/PUT requests unknown parameters to the Neutron server. This patch adds such possibility to the OSC. Change-Id: Iba09297c2be9fb9fa0be1b3dc65755277b79230e --- lower-constraints.txt | 2 +- openstackclient/network/common.py | 74 +++++++++- openstackclient/network/utils.py | 42 ++++++ openstackclient/network/v2/address_group.py | 11 +- openstackclient/network/v2/address_scope.py | 9 +- openstackclient/network/v2/floating_ip.py | 15 +- .../network/v2/floating_ip_port_forwarding.py | 12 +- openstackclient/network/v2/network.py | 17 ++- openstackclient/network/v2/network_flavor.py | 9 +- .../network/v2/network_flavor_profile.py | 10 +- openstackclient/network/v2/network_meter.py | 5 +- .../network/v2/network_meter_rule.py | 5 +- .../network/v2/network_qos_policy.py | 10 +- .../network/v2/network_qos_rule.py | 10 +- openstackclient/network/v2/network_rbac.py | 9 +- openstackclient/network/v2/network_segment.py | 10 +- .../network/v2/network_segment_range.py | 12 +- openstackclient/network/v2/port.py | 15 +- openstackclient/network/v2/router.py | 18 ++- openstackclient/network/v2/security_group.py | 10 +- .../network/v2/security_group_rule.py | 6 +- openstackclient/network/v2/subnet.py | 14 +- openstackclient/network/v2/subnet_pool.py | 10 +- .../tests/unit/network/test_common.py | 139 ++++++++++++++++++ .../tests/unit/network/test_utils.py | 59 ++++++++ requirements.txt | 2 +- 26 files changed, 486 insertions(+), 49 deletions(-) create mode 100644 openstackclient/tests/unit/network/test_utils.py diff --git a/lower-constraints.txt b/lower-constraints.txt index 09aabede1f..861749b738 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -38,7 +38,7 @@ msgpack-python==0.4.0 munch==2.1.0 netaddr==0.7.18 netifaces==0.10.4 -openstacksdk==0.53.0 +openstacksdk==0.56.0 os-client-config==2.1.0 os-service-types==1.7.0 osc-lib==2.3.0 diff --git a/openstackclient/network/common.py b/openstackclient/network/common.py index 47ffbe77a6..b1902a6c9c 100644 --- a/openstackclient/network/common.py +++ b/openstackclient/network/common.py @@ -16,10 +16,12 @@ import logging import openstack.exceptions +from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions from openstackclient.i18n import _ +from openstackclient.network import utils LOG = logging.getLogger(__name__) @@ -75,7 +77,6 @@ def _network_type(self): """ # Have we set it up yet for this command? if not hasattr(self, '_net_type'): - # import pdb; pdb.set_trace() try: if self.app.client_manager.is_network_endpoint_enabled(): net_type = _NET_TYPE_NEUTRON @@ -255,3 +256,74 @@ def take_action(self, parsed_args): if exc.details: msg += ", " + str(exc.details) raise exceptions.CommandError(msg) + + +class NeutronCommandWithExtraArgs(command.Command): + """Create and Update commands with additional extra properties. + + Extra properties can be passed to the command and are then send to the + Neutron as given to the command. + """ + + # dict of allowed types + _allowed_types_dict = { + 'bool': utils.str2bool, + 'dict': utils.str2dict, + 'list': utils.str2list, + 'int': int, + 'str': str, + } + + def _get_property_converter(self, _property): + if 'type' not in _property: + converter = str + else: + converter = self._allowed_types_dict.get(_property['type']) + + if not converter: + raise exceptions.CommandError( + _("Type {property_type} of property {name} " + "is not supported").format( + property_type=_property['type'], + name=_property['name'])) + return converter + + def _parse_extra_properties(self, extra_properties): + result = {} + if extra_properties: + for _property in extra_properties: + converter = self._get_property_converter(_property) + result[_property['name']] = converter(_property['value']) + return result + + def get_parser(self, prog_name): + parser = super(NeutronCommandWithExtraArgs, self).get_parser(prog_name) + parser.add_argument( + '--extra-property', + metavar='type=,name=,' + 'value=', + dest='extra_properties', + action=parseractions.MultiKeyValueAction, + required_keys=['name', 'value'], + optional_keys=['type'], + help=_("Additional parameters can be passed using this property. " + "Default type of the extra property is string ('str'), but " + "other types can be used as well. Available types are: " + "'dict', 'list', 'str', 'bool', 'int'. " + "In case of 'list' type, 'value' can be " + "semicolon-separated list of values. " + "For 'dict' value is semicolon-separated list of the " + "key:value pairs.") + ) + return parser + + +class NeutronUnsetCommandWithExtraArgs(NeutronCommandWithExtraArgs): + + def _parse_extra_properties(self, extra_properties): + result = {} + if extra_properties: + for _property in extra_properties: + result[_property['name']] = None + + return result diff --git a/openstackclient/network/utils.py b/openstackclient/network/utils.py index 287f027163..4d4d18e470 100644 --- a/openstackclient/network/utils.py +++ b/openstackclient/network/utils.py @@ -11,6 +11,10 @@ # under the License. # +from osc_lib import exceptions + +from openstackclient.i18n import _ + # Transform compute security group rule for display. def transform_compute_security_group_rule(sg_rule): @@ -39,3 +43,41 @@ def transform_compute_security_group_rule(sg_rule): else: info['remote_security_group'] = '' return info + + +def str2bool(strbool): + if strbool is None: + return None + return strbool.lower() == 'true' + + +def str2list(strlist): + result = [] + if strlist: + result = strlist.split(';') + return result + + +def str2dict(strdict): + """Convert key1:value1;key2:value2;... string into dictionary. + + :param strdict: string in the form of key1:value1;key2:value2 + """ + result = {} + if not strdict: + return result + i = 0 + kvlist = [] + for kv in strdict.split(';'): + if ':' in kv: + kvlist.append(kv) + i += 1 + elif i == 0: + msg = _("missing value for key '%s'") + raise exceptions.CommandError(msg % kv) + else: + kvlist[i - 1] = "%s;%s" % (kvlist[i - 1], kv) + for kv in kvlist: + key, sep, value = kv.partition(':') + result[key] = value + return result diff --git a/openstackclient/network/v2/address_group.py b/openstackclient/network/v2/address_group.py index c5b2f12606..fc83470053 100644 --- a/openstackclient/network/v2/address_group.py +++ b/openstackclient/network/v2/address_group.py @@ -22,6 +22,7 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import common from openstackclient.network import sdk_utils @@ -57,7 +58,7 @@ def _get_attrs(client_manager, parsed_args): return attrs -class CreateAddressGroup(command.ShowOne): +class CreateAddressGroup(command.ShowOne, common.NeutronCommandWithExtraArgs): _description = _("Create a new Address Group") def get_parser(self, prog_name): @@ -93,6 +94,9 @@ def take_action(self, parsed_args): client = self.app.client_manager.network attrs = _get_attrs(self.app.client_manager, parsed_args) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + obj = client.create_address_group(**attrs) display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters={}) @@ -191,7 +195,7 @@ def take_action(self, parsed_args): ) for s in data)) -class SetAddressGroup(command.Command): +class SetAddressGroup(common.NeutronCommandWithExtraArgs): _description = _("Set address group properties") def get_parser(self, prog_name): @@ -231,6 +235,9 @@ def take_action(self, parsed_args): attrs['name'] = parsed_args.name if parsed_args.description is not None: attrs['description'] = parsed_args.description + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + if attrs: client.update_address_group(obj, **attrs) if parsed_args.address: diff --git a/openstackclient/network/v2/address_scope.py b/openstackclient/network/v2/address_scope.py index 71c1a9afb7..cd27678ee9 100644 --- a/openstackclient/network/v2/address_scope.py +++ b/openstackclient/network/v2/address_scope.py @@ -21,6 +21,7 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import common from openstackclient.network import sdk_utils @@ -57,7 +58,7 @@ def _get_attrs(client_manager, parsed_args): # TODO(rtheis): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class CreateAddressScope(command.ShowOne): +class CreateAddressScope(command.ShowOne, common.NeutronCommandWithExtraArgs): _description = _("Create a new Address Scope") def get_parser(self, prog_name): @@ -98,6 +99,8 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): client = self.app.client_manager.network attrs = _get_attrs(self.app.client_manager, parsed_args) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) obj = client.create_address_scope(**attrs) display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters={}) @@ -226,7 +229,7 @@ def take_action(self, parsed_args): # TODO(rtheis): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class SetAddressScope(command.Command): +class SetAddressScope(common.NeutronCommandWithExtraArgs): _description = _("Set address scope properties") def get_parser(self, prog_name): @@ -267,6 +270,8 @@ def take_action(self, parsed_args): attrs['shared'] = True if parsed_args.no_share: attrs['shared'] = False + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) client.update_address_scope(obj, **attrs) diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index a2765cd1ef..25b2a1baf6 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -13,7 +13,6 @@ """IP Floating action implementations""" -from osc_lib.command import command from osc_lib import utils from osc_lib.utils import tags as _tag @@ -94,7 +93,8 @@ def _get_attrs(client_manager, parsed_args): return attrs -class CreateFloatingIP(common.NetworkAndComputeShowOne): +class CreateFloatingIP(common.NetworkAndComputeShowOne, + common.NeutronCommandWithExtraArgs): _description = _("Create floating IP") def update_parser_common(self, parser): @@ -175,6 +175,8 @@ def update_parser_network(self, parser): def take_action_network(self, client, parsed_args): attrs = _get_attrs(self.app.client_manager, parsed_args) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) with common.check_missing_extension_if_error( self.app.client_manager.network, attrs): obj = client.create_ip(**attrs) @@ -390,7 +392,7 @@ def take_action_compute(self, client, parsed_args): ) for s in data)) -class SetFloatingIP(command.Command): +class SetFloatingIP(common.NeutronCommandWithExtraArgs): _description = _("Set floating IP Properties") def get_parser(self, prog_name): @@ -456,6 +458,9 @@ def take_action(self, parsed_args): if 'no_qos_policy' in parsed_args and parsed_args.no_qos_policy: attrs['qos_policy_id'] = None + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + if attrs: client.update_ip(obj, **attrs) @@ -490,7 +495,7 @@ def take_action_compute(self, client, parsed_args): return (columns, data) -class UnsetFloatingIP(command.Command): +class UnsetFloatingIP(common.NeutronCommandWithExtraArgs): _description = _("Unset floating IP Properties") def get_parser(self, prog_name): @@ -526,6 +531,8 @@ def take_action(self, parsed_args): attrs['port_id'] = None if parsed_args.qos_policy: attrs['qos_policy_id'] = None + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) if attrs: client.update_ip(obj, **attrs) diff --git a/openstackclient/network/v2/floating_ip_port_forwarding.py b/openstackclient/network/v2/floating_ip_port_forwarding.py index 06b3df8bcd..71b0b7da63 100644 --- a/openstackclient/network/v2/floating_ip_port_forwarding.py +++ b/openstackclient/network/v2/floating_ip_port_forwarding.py @@ -19,6 +19,7 @@ from osc_lib import utils from openstackclient.i18n import _ +from openstackclient.network import common from openstackclient.network import sdk_utils @@ -32,7 +33,8 @@ def _get_columns(item): return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) -class CreateFloatingIPPortForwarding(command.ShowOne): +class CreateFloatingIPPortForwarding(command.ShowOne, + common.NeutronCommandWithExtraArgs): _description = _("Create floating IP port forwarding") def get_parser(self, prog_name): @@ -122,6 +124,9 @@ def take_action(self, parsed_args): if parsed_args.description is not None: attrs['description'] = parsed_args.description + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + obj = client.create_floating_ip_port_forwarding( floating_ip.id, **attrs @@ -258,7 +263,7 @@ def take_action(self, parsed_args): ) for s in data)) -class SetFloatingIPPortForwarding(command.Command): +class SetFloatingIPPortForwarding(common.NeutronCommandWithExtraArgs): _description = _("Set floating IP Port Forwarding Properties") def get_parser(self, prog_name): @@ -352,6 +357,9 @@ def take_action(self, parsed_args): if parsed_args.description is not None: attrs['description'] = parsed_args.description + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + client.update_floating_ip_port_forwarding( floating_ip.id, parsed_args.port_forwarding_id, **attrs) diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 7a12d523e2..b8eb9f014b 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -15,7 +15,6 @@ from cliff import columns as cliff_columns from osc_lib.cli import format_columns -from osc_lib.command import command from osc_lib import utils from osc_lib.utils import tags as _tag @@ -189,7 +188,8 @@ def _add_additional_network_options(parser): # TODO(sindhu): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class CreateNetwork(common.NetworkAndComputeShowOne): +class CreateNetwork(common.NetworkAndComputeShowOne, + common.NeutronCommandWithExtraArgs): _description = _("Create new network") def update_parser_common(self, parser): @@ -334,6 +334,8 @@ def take_action_network(self, client, parsed_args): attrs['vlan_transparent'] = True if parsed_args.no_transparent_vlan: attrs['vlan_transparent'] = False + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) with common.check_missing_extension_if_error( self.app.client_manager.network, attrs): obj = client.create_network(**attrs) @@ -623,7 +625,7 @@ def take_action_compute(self, client, parsed_args): # TODO(sindhu): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class SetNetwork(command.Command): +class SetNetwork(common.NeutronCommandWithExtraArgs): _description = _("Set network properties") def get_parser(self, prog_name): @@ -728,6 +730,8 @@ def take_action(self, parsed_args): obj = client.find_network(parsed_args.network, ignore_missing=False) attrs = _get_attrs_network(self.app.client_manager, parsed_args) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) if attrs: with common.check_missing_extension_if_error( self.app.client_manager.network, attrs): @@ -761,7 +765,7 @@ def take_action_compute(self, client, parsed_args): return (display_columns, data) -class UnsetNetwork(command.Command): +class UnsetNetwork(common.NeutronUnsetCommandWithExtraArgs): _description = _("Unset network properties") def get_parser(self, prog_name): @@ -778,8 +782,9 @@ def take_action(self, parsed_args): client = self.app.client_manager.network obj = client.find_network(parsed_args.network, ignore_missing=False) - # NOTE: As of now, UnsetNetwork has no attributes which need - # to be updated by update_network(). + attrs = self._parse_extra_properties(parsed_args.extra_properties) + if attrs: + client.update_network(obj, **attrs) # tags is a subresource and it needs to be updated separately. _tag.update_tags_for_unset(client, obj, parsed_args) diff --git a/openstackclient/network/v2/network_flavor.py b/openstackclient/network/v2/network_flavor.py index c9d368bfc1..9e758ae29c 100644 --- a/openstackclient/network/v2/network_flavor.py +++ b/openstackclient/network/v2/network_flavor.py @@ -21,6 +21,7 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import common from openstackclient.network import sdk_utils @@ -88,7 +89,7 @@ def take_action(self, parsed_args): # TODO(dasanind): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class CreateNetworkFlavor(command.ShowOne): +class CreateNetworkFlavor(command.ShowOne, common.NeutronCommandWithExtraArgs): _description = _("Create new network flavor") def get_parser(self, prog_name): @@ -134,6 +135,8 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): client = self.app.client_manager.network attrs = _get_attrs(self.app.client_manager, parsed_args) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) obj = client.create_flavor(**attrs) display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters={}) @@ -234,7 +237,7 @@ def take_action(self, parsed_args): # TODO(dasanind): Use only the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class SetNetworkFlavor(command.Command): +class SetNetworkFlavor(common.NeutronCommandWithExtraArgs): _description = _("Set network flavor properties") def get_parser(self, prog_name): @@ -281,6 +284,8 @@ def take_action(self, parsed_args): attrs['enabled'] = True if parsed_args.disable: attrs['enabled'] = False + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) client.update_flavor(obj, **attrs) diff --git a/openstackclient/network/v2/network_flavor_profile.py b/openstackclient/network/v2/network_flavor_profile.py index 6cf0c4124d..0212e0d9ba 100644 --- a/openstackclient/network/v2/network_flavor_profile.py +++ b/openstackclient/network/v2/network_flavor_profile.py @@ -19,6 +19,7 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import common from openstackclient.network import sdk_utils @@ -60,7 +61,8 @@ def _get_attrs(client_manager, parsed_args): # TODO(ndahiwade): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class CreateNetworkFlavorProfile(command.ShowOne): +class CreateNetworkFlavorProfile(command.ShowOne, + common.NeutronCommandWithExtraArgs): _description = _("Create new network flavor profile") def get_parser(self, prog_name): @@ -103,6 +105,8 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): client = self.app.client_manager.network attrs = _get_attrs(self.app.client_manager, parsed_args) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) if parsed_args.driver is None and parsed_args.metainfo is None: msg = _("Either --driver or --metainfo or both are required") @@ -180,7 +184,7 @@ def take_action(self, parsed_args): # TODO(ndahiwade): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class SetNetworkFlavorProfile(command.Command): +class SetNetworkFlavorProfile(common.NeutronCommandWithExtraArgs): _description = _("Set network flavor profile properties") def get_parser(self, prog_name): @@ -225,6 +229,8 @@ def take_action(self, parsed_args): obj = client.find_service_profile(parsed_args.flavor_profile, ignore_missing=False) attrs = _get_attrs(self.app.client_manager, parsed_args) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) client.update_service_profile(obj, **attrs) diff --git a/openstackclient/network/v2/network_meter.py b/openstackclient/network/v2/network_meter.py index df0e1da119..f8f188a806 100644 --- a/openstackclient/network/v2/network_meter.py +++ b/openstackclient/network/v2/network_meter.py @@ -21,6 +21,7 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import common from openstackclient.network import sdk_utils LOG = logging.getLogger(__name__) @@ -59,7 +60,7 @@ def _get_attrs(client_manager, parsed_args): # TODO(ankur-gupta-f): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class CreateMeter(command.ShowOne): +class CreateMeter(command.ShowOne, common.NeutronCommandWithExtraArgs): _description = _("Create network meter") def get_parser(self, prog_name): @@ -100,6 +101,8 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): client = self.app.client_manager.network attrs = _get_attrs(self.app.client_manager, parsed_args) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) obj = client.create_metering_label(**attrs) display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters={}) diff --git a/openstackclient/network/v2/network_meter_rule.py b/openstackclient/network/v2/network_meter_rule.py index 1cf0395f44..06362fa14c 100644 --- a/openstackclient/network/v2/network_meter_rule.py +++ b/openstackclient/network/v2/network_meter_rule.py @@ -21,6 +21,7 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import common from openstackclient.network import sdk_utils LOG = logging.getLogger(__name__) @@ -64,7 +65,7 @@ def _get_attrs(client_manager, parsed_args): return attrs -class CreateMeterRule(command.ShowOne): +class CreateMeterRule(command.ShowOne, common.NeutronCommandWithExtraArgs): _description = _("Create a new meter rule") def get_parser(self, prog_name): @@ -130,6 +131,8 @@ def take_action(self, parsed_args): ignore_missing=False) parsed_args.meter = _meter.id attrs = _get_attrs(self.app.client_manager, parsed_args) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) obj = client.create_metering_label_rule(**attrs) display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters={}) diff --git a/openstackclient/network/v2/network_qos_policy.py b/openstackclient/network/v2/network_qos_policy.py index fd5ff93771..7300a5c0ac 100644 --- a/openstackclient/network/v2/network_qos_policy.py +++ b/openstackclient/network/v2/network_qos_policy.py @@ -21,6 +21,7 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import common from openstackclient.network import sdk_utils @@ -67,7 +68,8 @@ def _get_attrs(client_manager, parsed_args): # TODO(abhiraut): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class CreateNetworkQosPolicy(command.ShowOne): +class CreateNetworkQosPolicy(command.ShowOne, + common.NeutronCommandWithExtraArgs): _description = _("Create a QoS policy") def get_parser(self, prog_name): @@ -117,6 +119,8 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): client = self.app.client_manager.network attrs = _get_attrs(self.app.client_manager, parsed_args) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) obj = client.create_qos_policy(**attrs) display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters={}) @@ -209,7 +213,7 @@ def take_action(self, parsed_args): # TODO(abhiraut): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class SetNetworkQosPolicy(command.Command): +class SetNetworkQosPolicy(common.NeutronCommandWithExtraArgs): _description = _("Set QoS policy properties") def get_parser(self, prog_name): @@ -259,6 +263,8 @@ def take_action(self, parsed_args): parsed_args.policy, ignore_missing=False) attrs = _get_attrs(self.app.client_manager, parsed_args) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) client.update_qos_policy(obj, **attrs) diff --git a/openstackclient/network/v2/network_qos_rule.py b/openstackclient/network/v2/network_qos_rule.py index 2e4b385d2e..f30a5aeb1f 100644 --- a/openstackclient/network/v2/network_qos_rule.py +++ b/openstackclient/network/v2/network_qos_rule.py @@ -20,6 +20,7 @@ from osc_lib import utils from openstackclient.i18n import _ +from openstackclient.network import common from openstackclient.network import sdk_utils @@ -171,7 +172,8 @@ def _add_rule_arguments(parser): ) -class CreateNetworkQosRule(command.ShowOne): +class CreateNetworkQosRule(command.ShowOne, + common.NeutronCommandWithExtraArgs): _description = _("Create new Network QoS rule") def get_parser(self, prog_name): @@ -198,6 +200,8 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): network_client = self.app.client_manager.network attrs = _get_attrs(network_client, parsed_args, is_create=True) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) try: obj = _rule_action_call( network_client, ACTION_CREATE, parsed_args.type)( @@ -285,7 +289,7 @@ def take_action(self, parsed_args): (_get_item_properties(s, columns) for s in data)) -class SetNetworkQosRule(command.Command): +class SetNetworkQosRule(common.NeutronCommandWithExtraArgs): _description = _("Set Network QoS rule properties") def get_parser(self, prog_name): @@ -312,6 +316,8 @@ def take_action(self, parsed_args): if not rule_type: raise Exception('Rule not found') attrs = _get_attrs(network_client, parsed_args) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) qos_id = attrs.pop('qos_policy_id') qos_rule = _rule_action_call(network_client, ACTION_FIND, rule_type)(attrs.pop('id'), qos_id) diff --git a/openstackclient/network/v2/network_rbac.py b/openstackclient/network/v2/network_rbac.py index 4984e89d56..692a43857d 100644 --- a/openstackclient/network/v2/network_rbac.py +++ b/openstackclient/network/v2/network_rbac.py @@ -21,6 +21,7 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import common from openstackclient.network import sdk_utils @@ -90,7 +91,7 @@ def _get_attrs(client_manager, parsed_args): # TODO(abhiraut): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class CreateNetworkRBAC(command.ShowOne): +class CreateNetworkRBAC(command.ShowOne, common.NeutronCommandWithExtraArgs): _description = _("Create network RBAC policy") def get_parser(self, prog_name): @@ -150,6 +151,8 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): client = self.app.client_manager.network attrs = _get_attrs(self.app.client_manager, parsed_args) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) obj = client.create_rbac_policy(**attrs) display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns) @@ -253,7 +256,7 @@ def take_action(self, parsed_args): # TODO(abhiraut): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class SetNetworkRBAC(command.Command): +class SetNetworkRBAC(common.NeutronCommandWithExtraArgs): _description = _("Set network RBAC policy properties") def get_parser(self, prog_name): @@ -291,6 +294,8 @@ def take_action(self, parsed_args): parsed_args.target_project_domain, ).id attrs['target_tenant'] = project_id + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) client.update_rbac_policy(obj, **attrs) diff --git a/openstackclient/network/v2/network_segment.py b/openstackclient/network/v2/network_segment.py index c1a672e2d5..14a8edabe5 100644 --- a/openstackclient/network/v2/network_segment.py +++ b/openstackclient/network/v2/network_segment.py @@ -20,6 +20,7 @@ from osc_lib import utils from openstackclient.i18n import _ +from openstackclient.network import common from openstackclient.network import sdk_utils @@ -30,7 +31,8 @@ def _get_columns(item): return sdk_utils.get_osc_show_columns_for_sdk_resource(item, {}) -class CreateNetworkSegment(command.ShowOne): +class CreateNetworkSegment(command.ShowOne, + common.NeutronCommandWithExtraArgs): _description = _("Create new network segment") def get_parser(self, prog_name): @@ -88,6 +90,8 @@ def take_action(self, parsed_args): attrs['physical_network'] = parsed_args.physical_network if parsed_args.segment is not None: attrs['segmentation_id'] = parsed_args.segment + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) obj = client.create_segment(**attrs) display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns) @@ -189,7 +193,7 @@ def take_action(self, parsed_args): ) for s in data)) -class SetNetworkSegment(command.Command): +class SetNetworkSegment(common.NeutronCommandWithExtraArgs): _description = _("Set network segment properties") def get_parser(self, prog_name): @@ -220,6 +224,8 @@ def take_action(self, parsed_args): attrs['description'] = parsed_args.description if parsed_args.name is not None: attrs['name'] = parsed_args.name + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) client.update_segment(obj, **attrs) diff --git a/openstackclient/network/v2/network_segment_range.py b/openstackclient/network/v2/network_segment_range.py index 6229995aab..ee414407ee 100644 --- a/openstackclient/network/v2/network_segment_range.py +++ b/openstackclient/network/v2/network_segment_range.py @@ -25,6 +25,7 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import common from openstackclient.network import sdk_utils @@ -87,7 +88,8 @@ def _update_additional_fields_from_props(columns, props): return props -class CreateNetworkSegmentRange(command.ShowOne): +class CreateNetworkSegmentRange(command.ShowOne, + common.NeutronCommandWithExtraArgs): _description = _("Create new network segment range") def get_parser(self, prog_name): @@ -209,6 +211,10 @@ def take_action(self, parsed_args): if parsed_args.physical_network: attrs['physical_network'] = parsed_args.physical_network + + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + obj = network_client.create_network_segment_range(**attrs) display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns) @@ -365,7 +371,7 @@ def take_action(self, parsed_args): return headers, display_props -class SetNetworkSegmentRange(command.Command): +class SetNetworkSegmentRange(common.NeutronCommandWithExtraArgs): _description = _("Set network segment range properties") def get_parser(self, prog_name): @@ -419,6 +425,8 @@ def take_action(self, parsed_args): attrs['minimum'] = parsed_args.minimum if parsed_args.maximum: attrs['maximum'] = parsed_args.maximum + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) network_client.update_network_segment_range(obj, **attrs) diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 4feffc1df4..ecb2382a85 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -326,7 +326,7 @@ def _convert_extra_dhcp_options(parsed_args): return dhcp_options -class CreatePort(command.ShowOne): +class CreatePort(command.ShowOne, common.NeutronCommandWithExtraArgs): _description = _("Create a new port") def get_parser(self, prog_name): @@ -501,6 +501,9 @@ def take_action(self, parsed_args): if parsed_args.tags: attrs['tags'] = list(set(parsed_args.tags)) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + with common.check_missing_extension_if_error( self.app.client_manager.network, attrs): obj = client.create_port(**attrs) @@ -697,7 +700,7 @@ def take_action(self, parsed_args): # TODO(abhiraut): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class SetPort(command.Command): +class SetPort(common.NeutronCommandWithExtraArgs): _description = _("Set port properties") def get_parser(self, prog_name): @@ -871,6 +874,9 @@ def take_action(self, parsed_args): if parsed_args.data_plane_status: attrs['data_plane_status'] = parsed_args.data_plane_status + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + if attrs: with common.check_missing_extension_if_error( self.app.client_manager.network, attrs): @@ -902,7 +908,7 @@ def take_action(self, parsed_args): # TODO(abhiraut): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class UnsetPort(command.Command): +class UnsetPort(common.NeutronUnsetCommandWithExtraArgs): _description = _("Unset port properties") def get_parser(self, prog_name): @@ -1023,6 +1029,9 @@ def take_action(self, parsed_args): if parsed_args.numa_policy: attrs['numa_affinity_policy'] = None + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + if attrs: client.update_port(obj, **attrs) diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index e3e8accd21..d15300a0b7 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -27,6 +27,7 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import common from openstackclient.network import sdk_utils @@ -256,7 +257,7 @@ def take_action(self, parsed_args): # TODO(yanxing'an): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class CreateRouter(command.ShowOne): +class CreateRouter(command.ShowOne, common.NeutronCommandWithExtraArgs): _description = _("Create a new router") def get_parser(self, prog_name): @@ -332,6 +333,9 @@ def take_action(self, parsed_args): attrs['ha'] = True if parsed_args.no_ha: attrs['ha'] = False + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + obj = client.create_router(**attrs) # tags cannot be set when created, so tags need to be set later. _tag.update_tags_for_set(client, obj, parsed_args) @@ -576,7 +580,7 @@ def take_action(self, parsed_args): # TODO(yanxing'an): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class SetRouter(command.Command): +class SetRouter(common.NeutronCommandWithExtraArgs): _description = _("Set router properties") def get_parser(self, prog_name): @@ -767,6 +771,10 @@ def take_action(self, parsed_args): if 'no_qos_policy' in parsed_args and parsed_args.no_qos_policy: attrs['external_gateway_info']['qos_policy_id'] = None + + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + if attrs: client.update_router(obj, **attrs) # tags is a subresource and it needs to be updated separately. @@ -809,7 +817,7 @@ def take_action(self, parsed_args): return (display_columns, data) -class UnsetRouter(command.Command): +class UnsetRouter(common.NeutronUnsetCommandWithExtraArgs): _description = _("Unset router properties") def get_parser(self, prog_name): @@ -875,6 +883,10 @@ def take_action(self, parsed_args): if parsed_args.external_gateway: attrs['external_gateway_info'] = {} + + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + if attrs: client.update_router(obj, **attrs) # tags is a subresource and it needs to be updated separately. diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py index 0732c23edc..49dc14e403 100644 --- a/openstackclient/network/v2/security_group.py +++ b/openstackclient/network/v2/security_group.py @@ -95,7 +95,8 @@ def _get_columns(item): # TODO(abhiraut): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class CreateSecurityGroup(common.NetworkAndComputeShowOne): +class CreateSecurityGroup(common.NetworkAndComputeShowOne, + common.NeutronCommandWithExtraArgs): _description = _("Create a new security group") def update_parser_common(self, parser): @@ -160,6 +161,8 @@ def take_action_network(self, client, parsed_args): parsed_args.project_domain, ).id attrs['tenant_id'] = project_id + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) # Create the security group and display the results. obj = client.create_security_group(**attrs) @@ -310,7 +313,8 @@ def take_action_compute(self, client, parsed_args): ) for s in data)) -class SetSecurityGroup(common.NetworkAndComputeCommand): +class SetSecurityGroup(common.NetworkAndComputeCommand, + common.NeutronCommandWithExtraArgs): _description = _("Set security group properties") def update_parser_common(self, parser): @@ -362,6 +366,8 @@ def take_action_network(self, client, parsed_args): attrs['stateful'] = True if parsed_args.stateless: attrs['stateful'] = False + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) # NOTE(rtheis): Previous behavior did not raise a CommandError # if there were no updates. Maintain this behavior and issue # the update. diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index 17241ed256..e273ded3d6 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -104,7 +104,8 @@ def _is_icmp_protocol(protocol): # TODO(abhiraut): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class CreateSecurityGroupRule(common.NetworkAndComputeShowOne): +class CreateSecurityGroupRule(common.NetworkAndComputeShowOne, + common.NeutronCommandWithExtraArgs): _description = _("Create a new security group rule") def update_parser_common(self, parser): @@ -355,6 +356,9 @@ def take_action_network(self, client, parsed_args): ).id attrs['tenant_id'] = project_id + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + # Create and show the security group rule. obj = client.create_security_group_rule(**attrs) display_columns, columns = _get_columns(obj) diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index eccdd4e460..09fd7c7c39 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -26,6 +26,7 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import common from openstackclient.network import sdk_utils @@ -250,7 +251,7 @@ def _get_attrs(client_manager, parsed_args, is_create=True): # TODO(abhiraut): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class CreateSubnet(command.ShowOne): +class CreateSubnet(command.ShowOne, common.NeutronCommandWithExtraArgs): _description = _("Create a subnet") def get_parser(self, prog_name): @@ -373,6 +374,8 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): client = self.app.client_manager.network attrs = _get_attrs(self.app.client_manager, parsed_args) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) obj = client.create_subnet(**attrs) # tags cannot be set when created, so tags need to be set later. _tag.update_tags_for_set(client, obj, parsed_args) @@ -545,7 +548,7 @@ def take_action(self, parsed_args): # TODO(abhiraut): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class SetSubnet(command.Command): +class SetSubnet(common.NeutronCommandWithExtraArgs): _description = _("Set subnet properties") def get_parser(self, prog_name): @@ -630,6 +633,8 @@ def take_action(self, parsed_args): attrs['allocation_pools'] = [] if 'service_types' in attrs: attrs['service_types'] += obj.service_types + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) if attrs: client.update_subnet(obj, **attrs) # tags is a subresource and it needs to be updated separately. @@ -657,7 +662,7 @@ def take_action(self, parsed_args): return (display_columns, data) -class UnsetSubnet(command.Command): +class UnsetSubnet(common.NeutronUnsetCommandWithExtraArgs): _description = _("Unset subnet properties") def get_parser(self, prog_name): @@ -744,6 +749,9 @@ def take_action(self, parsed_args): _update_arguments(attrs['service_types'], parsed_args.service_types, 'service-type') + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + if attrs: client.update_subnet(obj, **attrs) diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index 56cf61526c..bdf7aba805 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -24,6 +24,7 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import common from openstackclient.network import sdk_utils @@ -146,7 +147,7 @@ def _add_default_options(parser): # TODO(rtheis): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class CreateSubnetPool(command.ShowOne): +class CreateSubnetPool(command.ShowOne, common.NeutronCommandWithExtraArgs): _description = _("Create subnet pool") def get_parser(self, prog_name): @@ -203,6 +204,8 @@ def take_action(self, parsed_args): # NeutronServer expects prefixes to be a List if "prefixes" not in attrs: attrs['prefixes'] = [] + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) obj = client.create_subnet_pool(**attrs) # tags cannot be set when created, so tags need to be set later. _tag.update_tags_for_set(client, obj, parsed_args) @@ -351,7 +354,7 @@ def take_action(self, parsed_args): # TODO(rtheis): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class SetSubnetPool(command.Command): +class SetSubnetPool(common.NeutronCommandWithExtraArgs): _description = _("Set subnet pool properties") def get_parser(self, prog_name): @@ -408,6 +411,9 @@ def take_action(self, parsed_args): if 'prefixes' in attrs: attrs['prefixes'].extend(obj.prefixes) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + if attrs: client.update_subnet_pool(obj, **attrs) # tags is a subresource and it needs to be updated separately. diff --git a/openstackclient/tests/unit/network/test_common.py b/openstackclient/tests/unit/network/test_common.py index cde321aa23..4dde1b2bea 100644 --- a/openstackclient/tests/unit/network/test_common.py +++ b/openstackclient/tests/unit/network/test_common.py @@ -102,6 +102,27 @@ def take_action_compute(self, client, parsed_args): return client.compute_action(parsed_args) +class FakeCreateNeutronCommandWithExtraArgs( + common.NeutronCommandWithExtraArgs): + + def get_parser(self, prog_name): + parser = super(FakeCreateNeutronCommandWithExtraArgs, + self).get_parser(prog_name) + parser.add_argument( + '--known-attribute', + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + attrs = {} + if 'known_attribute' in parsed_args: + attrs['known_attribute'] = parsed_args.known_attribute + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + client.test_create_action(**attrs) + + class TestNetworkAndCompute(utils.TestCommand): def setUp(self): @@ -187,3 +208,121 @@ def test_take_action_with_http_exception(self): m_action.side_effect = openstack.exceptions.HttpException("bar") self.assertRaisesRegex(exceptions.CommandError, "bar", self.cmd.take_action, mock.Mock()) + + +class TestNeutronCommandWithExtraArgs(utils.TestCommand): + + def setUp(self): + super(TestNeutronCommandWithExtraArgs, self).setUp() + + self.namespace = argparse.Namespace() + + self.app.client_manager.network = mock.Mock() + self.network = self.app.client_manager.network + self.network.test_create_action = mock.Mock() + + # Subclasses can override the command object to test. + self.cmd = FakeCreateNeutronCommandWithExtraArgs( + self.app, self.namespace) + + def test_create_extra_attributes_default_type(self): + arglist = [ + '--known-attribute', 'known-value', + '--extra-property', 'name=extra_name,value=extra_value' + ] + verifylist = [ + ('known_attribute', 'known-value'), + ('extra_properties', [{'name': 'extra_name', + 'value': 'extra_value'}]) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.network.test_create_action.assert_called_with( + known_attribute='known-value', extra_name='extra_value') + + def test_create_extra_attributes_string(self): + arglist = [ + '--known-attribute', 'known-value', + '--extra-property', 'type=str,name=extra_name,value=extra_value' + ] + verifylist = [ + ('known_attribute', 'known-value'), + ('extra_properties', [{'name': 'extra_name', + 'type': 'str', + 'value': 'extra_value'}]) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.network.test_create_action.assert_called_with( + known_attribute='known-value', extra_name='extra_value') + + def test_create_extra_attributes_bool(self): + arglist = [ + '--known-attribute', 'known-value', + '--extra-property', 'type=bool,name=extra_name,value=TrUe' + ] + verifylist = [ + ('known_attribute', 'known-value'), + ('extra_properties', [{'name': 'extra_name', + 'type': 'bool', + 'value': 'TrUe'}]) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.network.test_create_action.assert_called_with( + known_attribute='known-value', extra_name=True) + + def test_create_extra_attributes_int(self): + arglist = [ + '--known-attribute', 'known-value', + '--extra-property', 'type=int,name=extra_name,value=8' + ] + verifylist = [ + ('known_attribute', 'known-value'), + ('extra_properties', [{'name': 'extra_name', + 'type': 'int', + 'value': '8'}]) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.network.test_create_action.assert_called_with( + known_attribute='known-value', extra_name=8) + + def test_create_extra_attributes_list(self): + arglist = [ + '--known-attribute', 'known-value', + '--extra-property', 'type=list,name=extra_name,value=v_1;v_2' + ] + verifylist = [ + ('known_attribute', 'known-value'), + ('extra_properties', [{'name': 'extra_name', + 'type': 'list', + 'value': 'v_1;v_2'}]) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.network.test_create_action.assert_called_with( + known_attribute='known-value', extra_name=['v_1', 'v_2']) + + def test_create_extra_attributes_dict(self): + arglist = [ + '--known-attribute', 'known-value', + '--extra-property', 'type=dict,name=extra_name,value=n1:v1;n2:v2' + ] + verifylist = [ + ('known_attribute', 'known-value'), + ('extra_properties', [{'name': 'extra_name', + 'type': 'dict', + 'value': 'n1:v1;n2:v2'}]) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.network.test_create_action.assert_called_with( + known_attribute='known-value', + extra_name={'n1': 'v1', 'n2': 'v2'}) diff --git a/openstackclient/tests/unit/network/test_utils.py b/openstackclient/tests/unit/network/test_utils.py new file mode 100644 index 0000000000..6252d7f766 --- /dev/null +++ b/openstackclient/tests/unit/network/test_utils.py @@ -0,0 +1,59 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +from osc_lib import exceptions + +from openstackclient.network import utils +from openstackclient.tests.unit import utils as tests_utils + + +class TestUtils(tests_utils.TestCase): + + def test_str2bool(self): + self.assertTrue(utils.str2bool("true")) + self.assertTrue(utils.str2bool("True")) + self.assertTrue(utils.str2bool("TRUE")) + self.assertTrue(utils.str2bool("TrUe")) + + self.assertFalse(utils.str2bool("false")) + self.assertFalse(utils.str2bool("False")) + self.assertFalse(utils.str2bool("FALSE")) + self.assertFalse(utils.str2bool("FaLsE")) + self.assertFalse(utils.str2bool("Something else")) + self.assertFalse(utils.str2bool("")) + + self.assertIsNone(utils.str2bool(None)) + + def test_str2list(self): + self.assertEqual( + ['a', 'b', 'c'], utils.str2list("a;b;c")) + self.assertEqual( + ['abc'], utils.str2list("abc")) + + self.assertEqual([], utils.str2list("")) + self.assertEqual([], utils.str2list(None)) + + def test_str2dict(self): + self.assertEqual( + {'a': 'aaa', 'b': '2'}, + utils.str2dict('a:aaa;b:2')) + self.assertEqual( + {'a': 'aaa;b;c', 'd': 'ddd'}, + utils.str2dict('a:aaa;b;c;d:ddd')) + + self.assertEqual({}, utils.str2dict("")) + self.assertEqual({}, utils.str2dict(None)) + + self.assertRaises( + exceptions.CommandError, + utils.str2dict, "aaa;b:2") diff --git a/requirements.txt b/requirements.txt index d3a17e9a48..0ac991da20 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ pbr!=2.1.0,>=2.0.0 # Apache-2.0 cliff>=3.5.0 # Apache-2.0 iso8601>=0.1.11 # MIT -openstacksdk>=0.53.0 # Apache-2.0 +openstacksdk>=0.56.0 # Apache-2.0 osc-lib>=2.3.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 From a2375b878785d739b4fdc49f563c00208a6d18ae Mon Sep 17 00:00:00 2001 From: Slawek Kaplonski Date: Wed, 26 May 2021 12:26:40 +0200 Subject: [PATCH 2375/3095] Make functional Neutron tests running fine on ML2/OVN environments Devstack recently switched default Neutron's backend from ML2/OVS to ML2/OVN. As OVN backend has some parity gaps and differences in some APIs, functional tests job was failing with ML2/OVN as some tests weren't properly skipped in case of missing some Neutron API extensions. This patch fixes that by doing some small changes in the functional tests: - skip DHCP/L3 agent tests when dhcp/l3 agent scheduler extensions aren't available, - skip updating neutron agent as OVN agents don't allows that, - skip service providers tests when there is no Neutron L3 agent available, - skip setting router as distributed as OVN backend don't supports that router's attribute at all. Depends-On: https://review.opendev.org/c/openstack/neutron/+/793141 Change-Id: I29a8db202086b0b49fed865409fa8ca244b98439 --- .../functional/network/v2/test_network.py | 2 ++ .../network/v2/test_network_agent.py | 12 +++++++ .../v2/test_network_service_provider.py | 8 +++++ .../functional/network/v2/test_router.py | 36 ++++++++++++------- 4 files changed, 45 insertions(+), 13 deletions(-) diff --git a/openstackclient/tests/functional/network/v2/test_network.py b/openstackclient/tests/functional/network/v2/test_network.py index 71b533ed22..f68b314382 100644 --- a/openstackclient/tests/functional/network/v2/test_network.py +++ b/openstackclient/tests/functional/network/v2/test_network.py @@ -335,6 +335,8 @@ def test_network_list(self): def test_network_dhcp_agent(self): if not self.haz_network: self.skipTest("No Network service present") + if not self.is_extension_enabled("dhcp_agent_scheduler"): + self.skipTest("No dhcp_agent_scheduler extension present") name1 = uuid.uuid4().hex cmd_output1 = json.loads(self.openstack( diff --git a/openstackclient/tests/functional/network/v2/test_network_agent.py b/openstackclient/tests/functional/network/v2/test_network_agent.py index 963227de48..e558094598 100644 --- a/openstackclient/tests/functional/network/v2/test_network_agent.py +++ b/openstackclient/tests/functional/network/v2/test_network_agent.py @@ -49,6 +49,11 @@ def test_network_agent_list_show_set(self): cmd_output['id'], ) + if 'ovn' in agent_list[0]['Agent Type'].lower(): + # NOTE(slaweq): OVN Neutron agents can't be updated so test can be + # finished here + return + # agent set raw_output = self.openstack( 'network agent set --disable %s' % agent_ids[0] @@ -89,6 +94,9 @@ def setUp(self): def test_network_dhcp_agent_list(self): """Test network agent list""" + if not self.is_extension_enabled("dhcp_agent_scheduler"): + self.skipTest("No dhcp_agent_scheduler extension present") + name1 = uuid.uuid4().hex cmd_output1 = json.loads(self.openstack( 'network create -f json --description aaaa %s' % name1 @@ -131,6 +139,10 @@ def test_network_dhcp_agent_list(self): def test_network_agent_list_routers(self): """Add agent to router, list agents on router, delete.""" + + if not self.is_extension_enabled("l3_agent_scheduler"): + self.skipTest("No l3_agent_scheduler extension present") + name = uuid.uuid4().hex cmd_output = json.loads(self.openstack( 'router create -f json %s' % name)) diff --git a/openstackclient/tests/functional/network/v2/test_network_service_provider.py b/openstackclient/tests/functional/network/v2/test_network_service_provider.py index 999b7eb713..c571a75640 100644 --- a/openstackclient/tests/functional/network/v2/test_network_service_provider.py +++ b/openstackclient/tests/functional/network/v2/test_network_service_provider.py @@ -26,6 +26,14 @@ def setUp(self): # Nothing in this class works with Nova Network if not self.haz_network: self.skipTest("No Network service present") + # NOTE(slaweq): + # that tests should works only when "standard" Neutron L3 agent is + # used, as e.g. OVN L3 plugin don't supports that. + l3_agent_list = json.loads(self.openstack( + 'network agent list -f json --agent-type l3 -c ID' + )) + if not l3_agent_list: + self.skipTest("No Neutron L3 Agents present") def test_network_service_provider_list(self): cmd_output = json.loads(self.openstack( diff --git a/openstackclient/tests/functional/network/v2/test_router.py b/openstackclient/tests/functional/network/v2/test_router.py index 0769dca6ff..2464b681f6 100644 --- a/openstackclient/tests/functional/network/v2/test_router.py +++ b/openstackclient/tests/functional/network/v2/test_router.py @@ -155,6 +155,10 @@ def test_router_list(self): def test_router_list_l3_agent(self): """Tests create router, add l3 agent, list, delete""" + + if not self.is_extension_enabled("l3_agent_scheduler"): + self.skipTest("No l3_agent_scheduler extension present") + name = uuid.uuid4().hex cmd_output = json.loads(self.openstack( 'router create -f json ' + name)) @@ -235,32 +239,38 @@ def test_router_set_show_unset(self): ) # Test set --ha --distributed + self._test_set_router_distributed(new_name) + + # Test unset cmd_output = self.openstack( - 'router set ' + - '--distributed ' + - '--external-gateway public ' + + 'router unset ' + + '--external-gateway ' + new_name ) - self.assertOutput('', cmd_output) - cmd_output = json.loads(self.openstack( 'router show -f json ' + new_name )) - self.assertTrue(cmd_output["distributed"]) - self.assertIsNotNone(cmd_output["external_gateway_info"]) + self.assertIsNone(cmd_output["external_gateway_info"]) + + def _test_set_router_distributed(self, router_name): + if not self.is_extension_enabled("dvr"): + return - # Test unset cmd_output = self.openstack( - 'router unset ' + - '--external-gateway ' + - new_name + 'router set ' + + '--distributed ' + + '--external-gateway public ' + + router_name ) + self.assertOutput('', cmd_output) + cmd_output = json.loads(self.openstack( 'router show -f json ' + - new_name + router_name )) - self.assertIsNone(cmd_output["external_gateway_info"]) + self.assertTrue(cmd_output["distributed"]) + self.assertIsNotNone(cmd_output["external_gateway_info"]) def test_router_add_remove_route(self): network_name = uuid.uuid4().hex From fe6a4fa8fc82a05785671c546345719bae39bbb3 Mon Sep 17 00:00:00 2001 From: YuehuiLei Date: Wed, 5 May 2021 14:56:50 +0800 Subject: [PATCH 2376/3095] setup.cfg: Replace dashes with underscores Setuptools v54.1.0 introduces a warning that the use of dash-separated options in 'setup.cfg' will not be supported in a future version [1]. Get ahead of the issue by replacing the dashes with underscores. Without this, we see 'UserWarning' messages like the following on new enough versions of setuptools: UserWarning: Usage of dash-separated 'description-file' will not be supported in future versions. Please use the underscore name 'description_file' instead [1] https://github.com/pypa/setuptools/commit/a2e9ae4cb Change-Id: I7e43e43bc5a24f49aa7b225502e5d0176fef3783 --- setup.cfg | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/setup.cfg b/setup.cfg index c61b3bac35..d6d7a3d2b0 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,12 +1,12 @@ [metadata] name = python-openstackclient summary = OpenStack Command-line Client -description-file = +description_file = README.rst author = OpenStack -author-email = openstack-discuss@lists.openstack.org -home-page = https://docs.openstack.org/python-openstackclient/latest/ -python-requires = >=3.6 +author_email = openstack-discuss@lists.openstack.org +home_page = https://docs.openstack.org/python-openstackclient/latest/ +python_requires = >=3.6 classifier = Environment :: OpenStack Intended Audience :: Information Technology From d08795271783cc6fc912ce99aef932d93dc60c47 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 2 Jun 2021 12:17:35 +0100 Subject: [PATCH 2377/3095] compute: Fix typo Change-Id: I3795142318b63b7c8f836d78a415a2161f61164d Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index b81d2a181c..9670a6205d 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -903,7 +903,7 @@ def get_parser(self, prog_name): '(required if using source image, snapshot or volume),\n' 'source_type=: source type ' '(one of: image, snapshot, volume, blank),\n' - 'destination_typ=: destination type ' + 'destination_type=: destination type ' '(one of: volume, local) (optional),\n' 'disk_bus=: device bus ' '(one of: uml, lxc, virtio, ...) (optional),\n' From 1169a114e7388e4ee622dca98b9fb8e2a06c8ec3 Mon Sep 17 00:00:00 2001 From: "wu.shiming" Date: Thu, 3 Jun 2021 14:49:23 +0800 Subject: [PATCH 2378/3095] Changed minversion in tox to 3.18.0 The patch bumps min version of tox to 3.18.0 in order to replace tox's whitelist_externals by allowlist_externals option: https://github.com/tox-dev/tox/blob/master/docs/changelog.rst#v3180-2020-07-23 Change-Id: Ibb77fa2afad3f09e95f0dba243d3a096daedd787 --- tox.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tox.ini b/tox.ini index ebcdc2d5d9..5e0be38422 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -minversion = 3.2.0 +minversion = 3.18.0 envlist = py38,pep8 skipdist = True # Automatic envs (pyXX) will only use the python version appropriate to that @@ -18,7 +18,7 @@ deps = -r{toxinidir}/test-requirements.txt -r{toxinidir}/requirements.txt commands = stestr run {posargs} -whitelist_externals = stestr +allowlist_externals = stestr [testenv:fast8] # Use same environment directory as pep8 env to save space and install time @@ -71,7 +71,7 @@ commands = pythom -m pip install -q -e "git+file://{toxinidir}/../openstacksdk#egg=openstacksdk" python -m pip freeze stestr run {posargs} -whitelist_externals = stestr +allowlist_externals = stestr [testenv:functional] setenv = OS_TEST_PATH=./openstackclient/tests/functional From eca1fcd65f22ead444ec39fe20f48c83f8d7c1cf Mon Sep 17 00:00:00 2001 From: David Caro Date: Wed, 2 Jun 2021 16:33:53 +0200 Subject: [PATCH 2379/3095] Include hosts in aggregate list --long This makes it easier to get the total list of aggregates and the hosts belonging to each of them (specially for scripting purposes). Change-Id: I94833c15075ae655bc11e7c0fc47c0abad5846fc Signed-off-by: David Caro --- openstackclient/compute/v2/aggregate.py | 2 ++ openstackclient/tests/unit/compute/v2/test_aggregate.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/openstackclient/compute/v2/aggregate.py b/openstackclient/compute/v2/aggregate.py index e39eb2d272..37522a788a 100644 --- a/openstackclient/compute/v2/aggregate.py +++ b/openstackclient/compute/v2/aggregate.py @@ -193,12 +193,14 @@ def take_action(self, parsed_args): "Name", "Availability Zone", "Properties", + "Hosts", ) columns = ( "ID", "Name", "Availability Zone", "Metadata", + "Hosts", ) else: column_headers = columns = ( diff --git a/openstackclient/tests/unit/compute/v2/test_aggregate.py b/openstackclient/tests/unit/compute/v2/test_aggregate.py index 8563f98811..92ff607f94 100644 --- a/openstackclient/tests/unit/compute/v2/test_aggregate.py +++ b/openstackclient/tests/unit/compute/v2/test_aggregate.py @@ -234,6 +234,7 @@ class TestAggregateList(TestAggregate): "Name", "Availability Zone", "Properties", + "Hosts", ) list_data = (( @@ -251,6 +252,7 @@ class TestAggregateList(TestAggregate): for key, value in TestAggregate.fake_ag.metadata.items() if key != 'availability_zone' }), + format_columns.ListColumn(TestAggregate.fake_ag.hosts), ), ) def setUp(self): From 8dc2a7e9f79ef22c53a6f71187d47c40b95631f6 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 1 Apr 2021 15:46:36 +0100 Subject: [PATCH 2380/3095] docs: Update cinderclient comparison doc Done manually by looking at the help text for the 'cinder' client (version 7.0.0) and identifying gaps. Change-Id: Ib16c7e9dfa47a93d8b077f0e3e5bbd5bf8984ec3 Signed-off-by: Stephen Finucane --- doc/source/cli/data/cinder.csv | 250 +++++++++++++++++++-------------- 1 file changed, 146 insertions(+), 104 deletions(-) diff --git a/doc/source/cli/data/cinder.csv b/doc/source/cli/data/cinder.csv index 22fb84cffa..27141494a2 100644 --- a/doc/source/cli/data/cinder.csv +++ b/doc/source/cli/data/cinder.csv @@ -1,104 +1,146 @@ -absolute-limits,limits show --absolute,Lists absolute limits for a user. -availability-zone-list,availability zone list --volume,Lists all availability zones. -backup-create,volume backup create,Creates a volume backup. -backup-delete,volume backup delete,Removes a backup. -backup-export,volume backup record export,Export backup metadata record. -backup-import,volume backup record import,Import backup metadata record. -backup-list,volume backup list,Lists all backups. -backup-reset-state,volume backup set --state,Explicitly updates the backup state. -backup-restore,volume backup restore,Restores a backup. -backup-show,volume backup show,Show backup details. -cgsnapshot-create,consistency group snapshot create,Creates a cgsnapshot. -cgsnapshot-delete,consistency group snapshot delete,Removes one or more cgsnapshots. -cgsnapshot-list,consistency group snapshot list,Lists all cgsnapshots. -cgsnapshot-show,consistency group snapshot show,Shows cgsnapshot details. -consisgroup-create,consistency group create,Creates a consistency group. -consisgroup-create-from-src,consistency group create --consistency-group-snapshot,Creates a consistency group from a cgsnapshot or a source CG -consisgroup-delete,consistency group delete,Removes one or more consistency groups. -consisgroup-list,consistency group list,Lists all consistencygroups. -consisgroup-show,consistency group show,Shows details of a consistency group. -consisgroup-update,consistency group set,Updates a consistencygroup. -create,volume create,Creates a volume. -credentials,WONTFIX,Shows user credentials returned from auth. -delete,volume delete,Removes one or more volumes. -encryption-type-create,volume type create --encryption-provider --enc..,Creates encryption type for a volume type. Admin only. -encryption-type-delete,volume type delete,Deletes encryption type for a volume type. Admin only. -encryption-type-list,volume type list --encryption-type,Shows encryption type details for volume types. Admin only. -encryption-type-show,volume type list --encryption-show,Shows encryption type details for volume type. Admin only. -encryption-type-update,volume type set --encryption-provider --enc..,Update encryption type information for a volume type (Admin Only). -endpoints,catalog list,Discovers endpoints registered by authentication service. -extend,volume set --size,Attempts to extend size of an existing volume. -extra-specs-list,volume type list --long,Lists current volume types and extra specs. -failover-host,volume host failover,Failover a replicating cinder-volume host. -force-delete,volume delete --force,"Attempts force-delete of volume, regardless of state." -freeze-host,volume host set --disable,Freeze and disable the specified cinder-volume host. -get-capabilities,volume backend capability show,Show capabilities of a volume backend. Admin only. -get-pools,volume backend pool list,Show pool information for backends. Admin only. -image-metadata,volume set --image-property,Sets or deletes volume image metadata. -image-metadata-show,volume show,Shows volume image metadata. -list,volume list,Lists all volumes. -manage,volume create --remote-source k=v,Manage an existing volume. -metadata,volume set --property k=v / volume unset --property k,Sets or deletes volume metadata. -metadata-show,volume show,Shows volume metadata. -metadata-update-all,volume set --property k=v,Updates volume metadata. -migrate,volume migrate --host --force-copy --lock-volume ,Migrates volume to a new host. -qos-associate,volume qos associate,Associates qos specs with specified volume type. -qos-create,volume qos create,Creates a qos specs. -qos-delete,volume qos delete,Deletes a specified qos specs. -qos-disassociate,volume qos disassociate,Disassociates qos specs from specified volume type. -qos-disassociate-all,volume qos disassociate --all,Disassociates qos specs from all associations. -qos-get-association,volume qos show,Gets all associations for specified qos specs. -qos-key,volume qos set --property k=v / volume qos unset --property k,Sets or unsets specifications for a qos spec -qos-list,volume qos list,Lists qos specs. -qos-show,volume qos show,Shows a specified qos specs. -quota-class-show,quota show --class,Lists quotas for a quota class. -quota-class-update,quota set --class,Updates quotas for a quota class. -quota-defaults,quota show --default,Lists default quotas for a tenant. -quota-delete,,Delete the quotas for a tenant. -quota-show,quota show,Lists quotas for a tenant. -quota-update,quota set,Updates quotas for a tenant. -quota-usage,,Lists quota usage for a tenant. -rate-limits,limits show --rate,Lists rate limits for a user. -readonly-mode-update,volume set --read-only-mode | --read-write-mode,Updates volume read-only access-mode flag. -rename,volume set --name,Renames a volume. -replication-promote,WONTFIX,Promote a secondary volume to primary for a relationship -replication-reenable,WONTFIX,Sync the secondary volume with primary for a relationship -reset-state,volume set --state,Explicitly updates the volume state. -retype,volume type set --type,Changes the volume type for a volume. -service-disable,volume service set --disable,Disables the service. -service-enable,volume service set --enable,Enables the service. -service-list,volume service list,Lists all services. Filter by host and service binary. -set-bootable,volume set --bootable / --not-bootable,Update bootable status of a volume. -show,volume show,Shows volume details. -snapshot-create,snapshot create,Creates a snapshot. -snapshot-delete,snapshot delete,Remove one or more snapshots. -snapshot-list,snapshot list,Lists all snapshots. -snapshot-manage,volume snapshot create --remote-source ,Manage an existing snapshot. -snapshot-metadata,snapshot set --property k=v / snapshot unset --property k,Sets or deletes snapshot metadata. -snapshot-metadata-show,snapshot show,Shows snapshot metadata. -snapshot-metadata-update-all,snapshot set --property k=v,Updates snapshot metadata. -snapshot-rename,snapshot set --name,Renames a snapshot. -snapshot-reset-state,snapshot set --state,Explicitly updates the snapshot state. -snapshot-show,snapshot show,Shows snapshot details. -snapshot-unmanage,volume snapshot delete --remote,Stop managing a snapshot. -thaw-host,volume host set --enable,Thaw and enable the specified cinder-volume host. -transfer-accept,volume transfer accept,Accepts a volume transfer. -transfer-create,volume transfer create,Creates a volume transfer. -transfer-delete,volume transfer delete,Undoes a transfer. -transfer-list,volume transfer list,Lists all transfers. -transfer-show,volume transfer show,Show transfer details. -type-access-add,volume type set --project,Adds volume type access for the given project. -type-access-list,volume type show,Print access information about the given volume type. -type-access-remove,volume type unset --project,Removes volume type access for the given project. -type-create,volume type create,Creates a volume type. -type-default,volume type list --default,List the default volume type. -type-delete,volume type delete,Deletes a specified volume type. -type-key,volume type set --property k=v / volume type unset --property k,Sets or unsets extra_spec for a volume type. -type-list,volume type list,Lists available 'volume types'. -type-show,volume type show,Show volume type details. -type-update,volume type set,"Updates volume type name, description, and/or is_public." -unmanage,volume delete --remote,Stop managing a volume. -upload-to-image,image create --volume,Uploads volume to Image Service as an image. -bash-completion,complete,Prints arguments for bash_completion. -help,help,Shows help about this program or one of its subcommands. -list-extensions,extension list --volume,Lists all available os-api extensions. +absolute-limits,limits show --absolute,Lists absolute limits for a user. +api-version,WONTFIX,Display the server API version information. +availability-zone-list,availability zone list --volume,Lists all availability zones. +attachment-complete,,Complete an attachment for a cinder volume. (Supported by API versions 3.44 - 3.latest) +attachment-create,,Create an attachment for a cinder volume. (Supported by API versions 3.27 - 3.latest) +attachment-delete,,Delete an attachment for a cinder volume. (Supported by API versions 3.27 - 3.latest) +attachment-list,,Lists all attachments. (Supported by API versions 3.27 - 3.latest) +attachment-show,,Show detailed information for attachment. (Supported by API versions 3.27 - 3.latest) +attachment-update,,Update an attachment for a cinder volume. (Supported by API versions 3.27 - 3.latest) +backup-create,volume backup create,Creates a volume backup. +backup-delete,volume backup delete,Removes a backup. +backup-export,volume backup record export,Export backup metadata record. +backup-import,volume backup record import,Import backup metadata record. +backup-list,volume backup list,Lists all backups. +backup-reset-state,volume backup set --state,Explicitly updates the backup state. +backup-restore,volume backup restore,Restores a backup. +backup-show,volume backup show,Show backup details. +backup-update,,Updates a backup. (Supported by API versions 3.9 - 3.latest) +cgsnapshot-create,consistency group snapshot create,Creates a cgsnapshot. +cgsnapshot-delete,consistency group snapshot delete,Removes one or more cgsnapshots. +cgsnapshot-list,consistency group snapshot list,Lists all cgsnapshots. +cgsnapshot-show,consistency group snapshot show,Shows cgsnapshot details. +cluster-disable,,Disables clustered services. (Supported by API versions 3.7 - 3.latest) +cluster-enable,,Enables clustered services. (Supported by API versions 3.7 - 3.latest) +cluster-list,,Lists clustered services with optional filtering. (Supported by API versions 3.7 - 3.latest) +cluster-show,,Show detailed information on a clustered service. (Supported by API versions 3.7 - 3.latest) +consisgroup-create,consistency group create,Creates a consistency group. +consisgroup-create-from-src,consistency group create --consistency-group-snapshot,Creates a consistency group from a cgsnapshot or a source CG +consisgroup-delete,consistency group delete,Removes one or more consistency groups. +consisgroup-list,consistency group list,Lists all consistencygroups. +consisgroup-show,consistency group show,Shows details of a consistency group. +consisgroup-update,consistency group set,Updates a consistencygroup. +create,volume create,Creates a volume. +delete,volume delete,Removes one or more volumes. +encryption-type-create,volume type create --encryption-provider --enc..,Creates encryption type for a volume type. Admin only. +encryption-type-delete,volume type delete,Deletes encryption type for a volume type. Admin only. +encryption-type-list,volume type list --encryption-type,Shows encryption type details for volume types. Admin only. +encryption-type-show,volume type list --encryption-show,Shows encryption type details for volume type. Admin only. +encryption-type-update,volume type set --encryption-provider --enc..,Update encryption type information for a volume type (Admin Only). +extend,volume set --size,Attempts to extend size of an existing volume. +extra-specs-list,volume type list --long,Lists current volume types and extra specs. +failover-host,volume host failover,Failover a replicating cinder-volume host. +force-delete,volume delete --force,"Attempts force-delete of volume regardless of state." +freeze-host,volume host set --disable,Freeze and disable the specified cinder-volume host. +get-capabilities,volume backend capability show,Show capabilities of a volume backend. Admin only. +get-pools,volume backend pool list,Show pool information for backends. Admin only. +group-create,,Creates a group. (Supported by API versions 3.13 - 3.latest) +group-create-from-src,,Creates a group from a group snapshot or a source group. (Supported by API versions 3.14 - 3.latest) +group-delete,,Removes one or more groups. (Supported by API versions 3.13 - 3.latest) +group-disable-replication,,Disables replication for group. (Supported by API versions 3.38 - 3.latest) +group-enable-replication,,Enables replication for group. (Supported by API versions 3.38 - 3.latest) +group-failover-replication,,Fails over replication for group. (Supported by API versions 3.38 - 3.latest) +group-list,,Lists all groups. (Supported by API versions 3.13 - 3.latest) +group-list-replication-targets,,Lists replication targets for group. (Supported by API versions 3.38 - 3.latest) +group-show,,Shows details of a group. (Supported by API versions 3.13 - 3.latest) +group-snapshot-create,,Creates a group snapshot. (Supported by API versions 3.14 - 3.latest) +group-snapshot-delete,,Removes one or more group snapshots. (Supported by API versions 3.14 - 3.latest) +group-snapshot-list,,Lists all group snapshots. (Supported by API versions 3.14 - 3.latest) +group-snapshot-show,,Shows group snapshot details. (Supported by API versions 3.14 - 3.latest) +group-specs-list,,Lists current group types and specs. (Supported by API versions 3.11 - 3.latest) +group-type-create,,Creates a group type. (Supported by API versions 3.11 - 3.latest) +group-type-default,,List the default group type. (Supported by API versions 3.11 - 3.latest) +group-type-delete,,Deletes group type or types. (Supported by API versions 3.11 - 3.latest) +group-type-key,,Sets or unsets group_spec for a group type. (Supported by API versions 3.11 - 3.latest) +group-type-list,,Lists available 'group types'. (Admin only will see private types) (Supported by API versions 3.11 - 3.latest) +group-type-show,,Show group type details. (Supported by API versions 3.11 - 3.latest) +group-type-update,,Updates group type name description and/or is_public. (Supported by API versions 3.11 - 3.latest) +group-update,,Updates a group. (Supported by API versions 3.13 - 3.latest) +image-metadata,volume set --image-property,Sets or deletes volume image metadata. +image-metadata-show,volume show,Shows volume image metadata. +list,volume list,Lists all volumes. +list-filters,,List enabled filters. (Supported by API versions 3.33 - 3.latest) +manage,volume create --remote-source k=v,Manage an existing volume. +manageable-list,,Lists all manageable volumes. (Supported by API versions 3.8 - 3.latest) +message-delete,,Removes one or more messages. (Supported by API versions 3.3 - 3.latest) +message-list,,Lists all messages. (Supported by API versions 3.3 - 3.latest) +message-show,,Shows message details. (Supported by API versions 3.3 - 3.latest) +metadata,volume set --property k=v / volume unset --property k,Sets or deletes volume metadata. +metadata-show,volume show,Shows volume metadata. +metadata-update-all,volume set --property k=v,Updates volume metadata. +migrate,volume migrate --host --force-copy --lock-volume ,Migrates volume to a new host. +qos-associate,volume qos associate,Associates qos specs with specified volume type. +qos-create,volume qos create,Creates a qos specs. +qos-delete,volume qos delete,Deletes a specified qos specs. +qos-disassociate,volume qos disassociate,Disassociates qos specs from specified volume type. +qos-disassociate-all,volume qos disassociate --all,Disassociates qos specs from all associations. +qos-get-association,volume qos show,Gets all associations for specified qos specs. +qos-key,volume qos set --property k=v / volume qos unset --property k,Sets or unsets specifications for a qos spec +qos-list,volume qos list,Lists qos specs. +qos-show,volume qos show,Shows a specified qos specs. +quota-class-show,quota show --class,Lists quotas for a quota class. +quota-class-update,quota set --class,Updates quotas for a quota class. +quota-defaults,quota show --default,Lists default quotas for a tenant. +quota-delete,,Delete the quotas for a tenant. +quota-show,quota show,Lists quotas for a tenant. +quota-update,quota set,Updates quotas for a tenant. +quota-usage,,Lists quota usage for a tenant. +rate-limits,limits show --rate,Lists rate limits for a user. +readonly-mode-update,volume set --read-only-mode | --read-write-mode,Updates volume read-only access-mode flag. +rename,volume set --name,Renames a volume. +reset-state,volume set --state,Explicitly updates the volume state. +retype,volume type set --type,Changes the volume type for a volume. +revert-to-snapshot,,Revert a volume to the specified snapshot. (Supported by API versions 3.40 - 3.latest) +service-disable,volume service set --disable,Disables the service. +service-enable,volume service set --enable,Enables the service. +service-get-log,,(Supported by API versions 3.32 - 3.latest) +service-list,volume service list,Lists all services. Filter by host and service binary. +service-set-log,,(Supported by API versions 3.32 - 3.latest) +set-bootable,volume set --bootable / --not-bootable,Update bootable status of a volume. +show,volume show,Shows volume details. +snapshot-create,snapshot create,Creates a snapshot. +snapshot-delete,snapshot delete,Remove one or more snapshots. +snapshot-list,snapshot list,Lists all snapshots. +snapshot-manage,volume snapshot create --remote-source ,Manage an existing snapshot. +snapshot-manageable-list,,Lists all manageable snapshots. (Supported by API versions 3.8 - 3.latest) +snapshot-metadata,snapshot set --property k=v / snapshot unset --property k,Sets or deletes snapshot metadata. +snapshot-metadata-show,snapshot show,Shows snapshot metadata. +snapshot-metadata-update-all,snapshot set --property k=v,Updates snapshot metadata. +snapshot-rename,snapshot set --name,Renames a snapshot. +snapshot-reset-state,snapshot set --state,Explicitly updates the snapshot state. +snapshot-show,snapshot show,Shows snapshot details. +snapshot-unmanage,volume snapshot delete --remote,Stop managing a snapshot. +summary,,Get volumes summary. (Supported by API versions 3.12 - 3.latest) +thaw-host,volume host set --enable,Thaw and enable the specified cinder-volume host. +transfer-accept,volume transfer accept,Accepts a volume transfer. +transfer-create,volume transfer create,Creates a volume transfer. +transfer-delete,volume transfer delete,Undoes a transfer. +transfer-list,volume transfer list,Lists all transfers. +transfer-show,volume transfer show,Show transfer details. +type-access-add,volume type set --project,Adds volume type access for the given project. +type-access-list,volume type show,Print access information about the given volume type. +type-access-remove,volume type unset --project,Removes volume type access for the given project. +type-create,volume type create,Creates a volume type. +type-default,volume type list --default,List the default volume type. +type-delete,volume type delete,Deletes a specified volume type. +type-key,volume type set --property k=v / volume type unset --property k,Sets or unsets extra_spec for a volume type. +type-list,volume type list,Lists available 'volume types'. +type-show,volume type show,Show volume type details. +type-update,volume type set,"Updates volume type name description and/or is_public." +unmanage,volume delete --remote,Stop managing a volume. +upload-to-image,image create --volume,Uploads volume to Image Service as an image. +version-list,,List all API versions. (Supported by API versions 3.0 - 3.latest) +work-cleanup,,Request cleanup of services with optional filtering. (Supported by API versions 3.24 - 3.latest) +bash-completion,complete,Prints arguments for bash_completion. +help,help,Shows help about this program or one of its subcommands. +list-extensions,extension list --volume,Lists all available os-api extensions. From 3751f1fdb60081d0ef72bf41b027c1c05f0a89b9 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 1 Apr 2021 17:26:27 +0100 Subject: [PATCH 2381/3095] docs: Update novaclient comparison doc Done manually by looking at the help text for the 'nova' client (version 17.0.0) and identifying gaps. Change-Id: I23a4947a13d5e576c5aa66902686df60379ffda0 Signed-off-by: Stephen Finucane --- doc/source/cli/data/nova.csv | 37 +++++++++++------------------------- 1 file changed, 11 insertions(+), 26 deletions(-) diff --git a/doc/source/cli/data/nova.csv b/doc/source/cli/data/nova.csv index c319a4a69d..83911f1237 100644 --- a/doc/source/cli/data/nova.csv +++ b/doc/source/cli/data/nova.csv @@ -1,10 +1,10 @@ -add-fixed-ip,server add fixed ip,Add new IP address on a network to server. add-secgroup,server add security group,Add a Security Group to a server. agent-create,compute agent create,Create new agent build. agent-delete,compute agent delete,Delete existing agent build. agent-list,compute agent list,List all builds. agent-modify,compute agent set,Modify existing agent build. aggregate-add-host,aggregate add host,Add the host to the specified aggregate. +aggregate-cache-images,,Request images be cached. (Supported by API versions '2.81' - '2.latest') [hint: use '-- os-compute-api-version' flag to show help message for proper version] aggregate-create,aggregate create,Create a new aggregate with the specified details. aggregate-delete,aggregate delete,Delete the aggregate. aggregate-list,aggregate list,Print a list of all aggregates. @@ -15,12 +15,7 @@ aggregate-update,aggregate set / unset,Update the aggregate's name and optionall availability-zone-list,availability zone list,List all the availability zones. backup,server backup create,Backup a server by creating a 'backup' type snapshot. boot,server create,Boot a new server. -cell-capacities,,Get cell capacities for all cells or a given cell. -cell-show,,Show details of a given cell. -clear-password,server set --root-password,Clear the admin password for a server from the metadata server. -cloudpipe-configure,WONTFIX,Update the VPN IP/port of a cloudpipe instance. -cloudpipe-create,WONTFIX,Create a cloudpipe instance for the given project. -cloudpipe-list,WONTFIX,Print a list of all cloudpipe instances. +clear-password,server set --root-password,Clear the admin password for a server from the metadata server. This action does not actually change the instance server password. console-log,console log show,Get console log output of a server. delete,server delete,Immediately shut down and delete specified server(s). diagnostics,openstack server show --diagnostics,Retrieve server diagnostics. @@ -33,24 +28,19 @@ flavor-delete,flavor delete,Delete a specific flavor flavor-key,flavor set / unset,Set or unset extra_spec for a flavor. flavor-list,flavor list,Print a list of available 'flavors' flavor-show,flavor show,Show details about the given flavor. -floating-ip-associate,server add floating ip,Associate a floating IP address to a server. -floating-ip-disassociate,server remove floating ip,Disassociate a floating IP address from a server. +flavor-update,,Update the description of an existing flavor. (Supported by API versions '2.55' - '2.latest') [hint: use '--os-compute-api- version' flag to show help message for proper version] force-delete,server delete,Force delete a server. -get-mks-console,console url show --mks,Get an MKS console to a server. -get-password,WONTFIX,Get the admin password for a server. +get-mks-console,console url show --mks,Get an MKS console to a server. (Supported by API versions '2.8' - '2.latest') [hint: use ' --os-compute-api-version' flag to show help message for proper version] +get-password,WONTFIX,Get the admin password for a server. This operation calls the metadata service to query metadata information and does not read password information from the server itself. get-rdp-console,console url show --rdp,Get a rdp console to a server. get-serial-console,console url show --serial,Get a serial console to a server. get-spice-console,console url show --spice,Get a spice console to a server. -get-vnc-console,console url show --novnc | --xvpvnc,Get a vnc console to a server. -host-action,,Perform a power action on a host. -host-describe,host show,Describe a specific host. +get-vnc-console,console url show --novnc,Get a vnc console to a server. host-evacuate,,Evacuate all instances from failed host. host-evacuate-live,,Live migrate all instances off the specified host to other available hosts. -host-list,host list,List all hosts by service. host-meta,,Set or Delete metadata on all instances of a host. host-servers-migrate,,Cold migrate all instances off the specified host to other available hosts. -host-update,host set,Update host settings. -hypervisor-list,hypervisor list,List hypervisors. +hypervisor-list,hypervisor list,List hypervisors. (Supported by API versions '2.0' - '2.latest') hypervisor-servers,,List servers belonging to specific hypervisors. hypervisor-show,hypervisor show,Display the details of the specified hypervisor. hypervisor-stats,hypervisor stats show,Get hypervisor statistics over all compute nodes. @@ -58,6 +48,7 @@ hypervisor-uptime,,Display the uptime of the specified hypervisor. image-create,server image create,Create a new image by taking a snapshot of a running server. instance-action,,Show an action. instance-action-list,,List actions on a server. +instance-usage-audit-log,,List/Get server usage audits. interface-attach,,Attach a network interface to a server. interface-detach,,Detach a network interface from a server. interface-list,port list --server,List interfaces attached to a server. @@ -67,7 +58,6 @@ keypair-list,keypair list,Print a list of keypairs for a user keypair-show,keypair show,Show details about the given keypair. limits,limits show,Print rate and absolute limits. list,server list,List active servers. -list-extensions,extension list,List all the os-api extensions that are available. list-secgroup,security group list,List Security Group(s) of a server. live-migration,,Migrate running server to a new machine. live-migration-abort,,Abort an on-going live migration. @@ -86,7 +76,6 @@ quota-update,quota set,Update the quotas for a tenant/user. reboot,server reboot,Reboot a server. rebuild,server rebuild,"Shutdown, re-image, and re-boot a server." refresh-network,WONTFIX,Refresh server network information. -remove-fixed-ip,server remove fixed ip,Remove an IP address from a server. remove-secgroup,server remove security group,Remove a Security Group from a server. rescue,server rescue,Reboots a server into rescue mode. reset-network,WONTFIX,Reset network of a server. @@ -107,6 +96,7 @@ server-tag-delete,,Delete one or more tags from a server. server-tag-delete-all,,Delete all tags from a server. server-tag-list,,Get list of tags from a server. server-tag-set,,Set list of tags to a server. +server-topology,openstack server show --topology,Retrieve server topology. (Supported by API versions '2.78' - '2.latest') [hint: use '-- os-compute-api-version' flag to show help message for proper version] service-delete,compute service delete,Delete the service. service-disable,compute service set --disable,Disable the service. service-enable,compute service set --enable,Enable the service. @@ -121,22 +111,17 @@ start,server start,Start the server(s). stop,server stop,Stop the server(s). suspend,server suspend,Suspend a server. trigger-crash-dump,server dump create,Trigger crash dump in an instance. -topology,openstack server show --topology,Retrieve server NUMA topology. unlock,server unlock,Unlock a server. unpause,server unpause,Unpause a server. unrescue,server unrescue,Restart the server from normal boot disk again. unshelve,server unshelve,Unshelve a server. -update,server set / unset --description,Update or unset the description for a server. -update,server set --name,Update the name for a server. +update,server set / unset,Update the name or the description for a server. usage,usage show,Show usage data for a single tenant. usage-list,usage list,List usage data for all tenants. version-list,,List all API versions. -virtual-interface-list,,Show virtual interface info about the given server. volume-attach,server add volume,Attach a volume to a server. volume-attachments,server show,List all the volumes attached to a server. volume-detach,server remove volume,Detach a volume from a server. volume-update,,Update volume attachment. -x509-create-cert,WONTFIX,Create x509 cert for a user in tenant. -x509-get-root-cert,WONTFIX,Fetch the x509 root cert. -bash-completion,complete,Prints all of the commands and options to +bash-completion,complete,Prints all of the commands and options to stdout so that the nova.bash_completion script doesn't have to hard code them. help,help,Display help about this program or one of its subcommands. From 83e7e4ab2e84169241651e504be164939a11b417 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 1 Apr 2021 17:33:42 +0100 Subject: [PATCH 2382/3095] docs: Update glanceclient comparison doc Done manually by looking at the help text for the 'glance' client (version 3.1.1) and identifying gaps. Change-Id: Ic46bbdef7182e5f707cd5083868886ce60c7eb47 Signed-off-by: Stephen Finucane --- doc/source/cli/data/glance.csv | 36 ++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/doc/source/cli/data/glance.csv b/doc/source/cli/data/glance.csv index 994a8fdaae..27585b9510 100644 --- a/doc/source/cli/data/glance.csv +++ b/doc/source/cli/data/glance.csv @@ -1,22 +1,58 @@ explain,WONTFIX,Describe a specific model. image-create,image create,Create a new image. +image-create-via-import,,EXPERIMENTAL: Create a new image via image import. image-deactivate,image set --deactivate,Deactivate specified image. image-delete,image delete,Delete specified image. image-download,image save,Download a specific image. +image-import,,Initiate the image import taskflow. image-list,image list,List images you can access. image-reactivate,image set --activate,Reactivate specified image. image-show,image show,Describe a specific image. +image-stage,,Upload data for a specific image to staging. image-tag-delete,image unset --tag ,Delete the tag associated with the given image. image-tag-update,image set --tag ,Update an image with the given tag. image-update,image set,Update an existing image. image-upload,,Upload data for a specific image. +import-info,,Print import methods available from Glance. location-add,,Add a location (and related metadata) to an image. location-delete,,Remove locations (and related metadata) from an image. location-update,,Update metadata of an image's location. +md-namespace-create,,Create a new metadata definitions namespace. +md-namespace-delete,,Delete specified metadata definitions namespace with its contents. +md-namespace-import,,Import a metadata definitions namespace from file or standard input. +md-namespace-list,,List metadata definitions namespaces. +md-namespace-objects-delete,,Delete all metadata definitions objects inside a specific namespace. +md-namespace-properties-delete,,Delete all metadata definitions property inside a specific namespace. +md-namespace-resource-type-list,,List resource types associated to specific namespace. +md-namespace-show,,Describe a specific metadata definitions namespace. +md-namespace-tags-delete,,Delete all metadata definitions tags inside a specific namespace. +md-namespace-update,,Update an existing metadata definitions namespace. +md-object-create,,Create a new metadata definitions object inside a namespace. +md-object-delete,,Delete a specific metadata definitions object inside a namespace. +md-object-list,,List metadata definitions objects inside a specific namespace. +md-object-property-show,,Describe a specific metadata definitions property inside an object. +md-object-show,,Describe a specific metadata definitions object inside a namespace. +md-object-update,,Update metadata definitions object inside a namespace. +md-property-create,,Create a new metadata definitions property inside a namespace. +md-property-delete,,Delete a specific metadata definitions property inside a namespace. +md-property-list,,List metadata definitions properties inside a specific namespace. +md-property-show,,Describe a specific metadata definitions property inside a namespace. +md-property-update,,Update metadata definitions property inside a namespace. +md-resource-type-associate,,Associate resource type with a metadata definitions namespace. +md-resource-type-deassociate,,Deassociate resource type with a metadata definitions namespace. +md-resource-type-list,,List available resource type names. +md-tag-create,,Add a new metadata definitions tag inside a namespace. +md-tag-create-multiple,,Create new metadata definitions tags inside a namespace. +md-tag-delete,,Delete a specific metadata definitions tag inside a namespace. +md-tag-list,,List metadata definitions tags inside a specific namespace. +md-tag-show,,Describe a specific metadata definitions tag inside a namespace. +md-tag-update,,Rename a metadata definitions tag inside a namespace. member-create,image add project,Create member for a given image. member-delete,image remove project,Delete image member. member-list,,Describe sharing permissions by image. member-update,image set --accept --reject --status,Update the status of a member for a given image. +stores-delete,,Delete image from specific store. +stores-info,,Print available backends from Glance. task-create,,Create a new task. task-list,,List tasks you can access. task-show,,Describe a specific task. From 95f914769a1ba8724ff16bbd06477783bceea157 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 1 Apr 2021 17:45:18 +0100 Subject: [PATCH 2383/3095] docs: Update neutronclient comparison doc Done manually by looking at the help text for the 'neutron' client (version 7.1.1) and identifying gaps. Change-Id: Ib029b2c236f79a0ca6f64834f069db2be4332ea8 Signed-off-by: Stephen Finucane --- doc/source/cli/data/neutron.csv | 34 +++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/doc/source/cli/data/neutron.csv b/doc/source/cli/data/neutron.csv index 080114e241..402f4064ee 100644 --- a/doc/source/cli/data/neutron.csv +++ b/doc/source/cli/data/neutron.csv @@ -35,6 +35,23 @@ dhcp-agent-network-add,network agent add network,Add a network to a DHCP agent. dhcp-agent-network-remove,network agent remove network,Remove a network from a DHCP agent. ext-list,extension list,List all extensions. ext-show,extension show,Show information of a given resource. +firewall-create,,Create a firewall. +firewall-delete,,Delete a given firewall. +firewall-list,,List firewalls that belong to a given tenant. +firewall-policy-create,,Create a firewall policy. +firewall-policy-delete,,Delete a given firewall policy. +firewall-policy-insert-rule,,Insert a rule into a given firewall policy. +firewall-policy-list,,List firewall policies that belong to a given tenant. +firewall-policy-remove-rule,,Remove a rule from a given firewall policy. +firewall-policy-show,,Show information of a given firewall policy. +firewall-policy-update,,Update a given firewall policy. +firewall-rule-create,,Create a firewall rule. +firewall-rule-delete,,Delete a given firewall rule. +firewall-rule-list,,List firewall rules that belong to a given tenant. +firewall-rule-show,,Show information of a given firewall rule. +firewall-rule-update,,Update a given firewall rule. +firewall-show,,Show information of a given firewall. +firewall-update,,Update a given firewall. flavor-associate,network flavor add profile,Add a Neutron service flavor with a flavor profile. flavor-create,network flavor create,Create a Neutron service flavor. flavor-delete,network flavor delete,Delete a given Neutron service flavor. @@ -214,14 +231,6 @@ subnetpool-update,subnet pool set / subnet pool unset,Update subnetpool's inform tag-add,network set --tag,Add a tag into the resource. tag-remove,network unset --tag,Remove a tag on the resource. tag-replace,,Replace all tags on the resource. -tap-flow-create,tapflow create,Create a tap flow -tap-flow-delete,tapflow delete,Delete a tap flow -tap-flow-list,tapflow list,List all tap flows -tap-flow-show,tapflow show,Show details of the tap flow -tap-service-create,tapservice create,Create a tap service -tap-service-delete,tapservice delete,Delete a tap service -tap-service-list,tapservice list,List all tap services -tap-service-show,tapservice show,Show details of the tap service vpn-endpoint-group-create,,Create a VPN endpoint group. vpn-endpoint-group-delete,,Delete a given VPN endpoint group. vpn-endpoint-group-list,,List VPN endpoint groups that belong to a given tenant. @@ -242,3 +251,12 @@ vpn-service-delete,,Delete a given VPN service. vpn-service-list,,List VPN service configurations that belong to a given tenant. vpn-service-show,,Show information of a given VPN service. vpn-service-update,,Update a given VPN service. + +tap-flow-create,tapflow create,Create a tap flow +tap-flow-delete,tapflow delete,Delete a tap flow +tap-flow-list,tapflow list,List all tap flows +tap-flow-show,tapflow show,Show details of the tap flow +tap-service-create,tapservice create,Create a tap service +tap-service-delete,tapservice delete,Delete a tap service +tap-service-list,tapservice list,List all tap services +tap-service-show,tapservice show,Show details of the tap service From 0f28588e48c1e296f834e8684f293c2cdf4afc33 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Mon, 24 May 2021 20:37:33 +0100 Subject: [PATCH 2384/3095] volume: Allow more versions Copy the API version checks from the 'openstackclient.compute.client' module. These will only be necessary until we migrate everything to SDK but it's very helpful until then. Change-Id: I2d9c68db5bf891ffa25fd5a7fc9e8953e44b73ab Signed-off-by: Stephen Finucane --- openstackclient/volume/client.py | 55 +++++++++++++++++++++++++++++--- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/openstackclient/volume/client.py b/openstackclient/volume/client.py index 37cb41685c..0712fa7b00 100644 --- a/openstackclient/volume/client.py +++ b/openstackclient/volume/client.py @@ -15,6 +15,7 @@ import logging +from osc_lib import exceptions from osc_lib import utils from openstackclient.i18n import _ @@ -29,9 +30,11 @@ "1": "cinderclient.v1.client.Client", "2": "cinderclient.v2.client.Client", "3": "cinderclient.v3.client.Client", - "3.42": "cinderclient.v3.client.Client", } +# Save the microversion if in use +_volume_api_version = None + def make_client(instance): """Returns a volume service client.""" @@ -52,10 +55,13 @@ def make_client(instance): except Exception: del API_VERSIONS['2'] - version = instance._api_version[API_NAME] - from cinderclient import api_versions - # convert to APIVersion object - version = api_versions.get_api_version(version) + if _volume_api_version is not None: + version = _volume_api_version + else: + version = instance._api_version[API_NAME] + from cinderclient import api_versions + # convert to APIVersion object + version = api_versions.get_api_version(version) if version.ver_major == '1': # Monkey patch for v1 cinderclient @@ -103,3 +109,42 @@ def build_option_parser(parser): '(Env: OS_VOLUME_API_VERSION)') % DEFAULT_API_VERSION ) return parser + + +def check_api_version(check_version): + """Validate version supplied by user + + Returns: + + * True if version is OK + * False if the version has not been checked and the previous plugin + check should be performed + * throws an exception if the version is no good + """ + + # Defer client imports until we actually need them + from cinderclient import api_versions + + global _volume_api_version + + # Copy some logic from novaclient 3.3.0 for basic version detection + # NOTE(dtroyer): This is only enough to resume operations using API + # version 3.0 or any valid version supplied by the user. + _volume_api_version = api_versions.get_api_version(check_version) + + # Bypass X.latest format microversion + if not _volume_api_version.is_latest(): + if _volume_api_version > api_versions.APIVersion("3.0"): + if not _volume_api_version.matches( + api_versions.MIN_VERSION, + api_versions.MAX_VERSION, + ): + msg = _("versions supported by client: %(min)s - %(max)s") % { + "min": api_versions.MIN_VERSION, + "max": api_versions.MAX_VERSION, + } + raise exceptions.CommandError(msg) + + return True + + return False From 6dc94e1fb85595653dcdd24185c914b9df1741df Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Mon, 24 May 2021 15:56:27 +0100 Subject: [PATCH 2385/3095] volume: Add 'volume attachment *' commands These mirror the 'cinder attachment-*' commands, with arguments copied across essentially verbatim. The only significant departure is the replacement of "tenant" terminology with "project". volume attachment create volume attachment delete volume attachment list volume attachment complete volume attachment set volume attachment show Full support for filtering is deferred for now since that's a more complicated change that requires additional commands be added first. TODOs are included to this effect. Change-Id: If47c2b56fe65ee2cee07c000d6ae3688d5ef3b42 Signed-off-by: Stephen Finucane --- .../cli/command-objects/volume-attachment.rst | 8 + doc/source/cli/commands.rst | 7 +- doc/source/cli/data/cinder.csv | 12 +- openstackclient/tests/unit/volume/v3/fakes.py | 155 +++++ .../unit/volume/v3/test_volume_attachment.py | 560 ++++++++++++++++++ .../volume/v3/volume_attachment.py | 511 ++++++++++++++++ ...-attachment-commands-db2974c6460fa3bc.yaml | 8 + setup.cfg | 7 + 8 files changed, 1259 insertions(+), 9 deletions(-) create mode 100644 doc/source/cli/command-objects/volume-attachment.rst create mode 100644 openstackclient/tests/unit/volume/v3/fakes.py create mode 100644 openstackclient/tests/unit/volume/v3/test_volume_attachment.py create mode 100644 openstackclient/volume/v3/volume_attachment.py create mode 100644 releasenotes/notes/add-volume-attachment-commands-db2974c6460fa3bc.yaml diff --git a/doc/source/cli/command-objects/volume-attachment.rst b/doc/source/cli/command-objects/volume-attachment.rst new file mode 100644 index 0000000000..5622444638 --- /dev/null +++ b/doc/source/cli/command-objects/volume-attachment.rst @@ -0,0 +1,8 @@ +================= +volume attachment +================= + +Block Storage v3 + +.. autoprogram-cliff:: openstack.volume.v3 + :command: volume attachment * diff --git a/doc/source/cli/commands.rst b/doc/source/cli/commands.rst index 94a0b5a638..00f6b23a77 100644 --- a/doc/source/cli/commands.rst +++ b/doc/source/cli/commands.rst @@ -153,11 +153,12 @@ referring to both Compute and Volume quotas. * ``user``: (**Identity**) individual cloud resources users * ``user role``: (**Identity**) roles assigned to a user * ``volume``: (**Volume**) block volumes +* ``volume attachment``: (**Volume**) an attachment of a volumes to a server * ``volume backup``: (**Volume**) backup for volumes -* ``volume backend capability``: (**volume**) volume backend storage capabilities -* ``volume backend pool``: (**volume**) volume backend storage pools +* ``volume backend capability``: (**Volume**) volume backend storage capabilities +* ``volume backend pool``: (**Volume**) volume backend storage pools * ``volume backup record``: (**Volume**) volume record that can be imported or exported -* ``volume backend``: (**volume**) volume backend storage +* ``volume backend``: (**Volume**) volume backend storage * ``volume host``: (**Volume**) the physical computer for volumes * ``volume qos``: (**Volume**) quality-of-service (QoS) specification for volumes * ``volume snapshot``: (**Volume**) a point-in-time copy of a volume diff --git a/doc/source/cli/data/cinder.csv b/doc/source/cli/data/cinder.csv index 27141494a2..dc43ab5b1a 100644 --- a/doc/source/cli/data/cinder.csv +++ b/doc/source/cli/data/cinder.csv @@ -1,12 +1,12 @@ absolute-limits,limits show --absolute,Lists absolute limits for a user. api-version,WONTFIX,Display the server API version information. availability-zone-list,availability zone list --volume,Lists all availability zones. -attachment-complete,,Complete an attachment for a cinder volume. (Supported by API versions 3.44 - 3.latest) -attachment-create,,Create an attachment for a cinder volume. (Supported by API versions 3.27 - 3.latest) -attachment-delete,,Delete an attachment for a cinder volume. (Supported by API versions 3.27 - 3.latest) -attachment-list,,Lists all attachments. (Supported by API versions 3.27 - 3.latest) -attachment-show,,Show detailed information for attachment. (Supported by API versions 3.27 - 3.latest) -attachment-update,,Update an attachment for a cinder volume. (Supported by API versions 3.27 - 3.latest) +attachment-complete,volume attachment complete,Complete an attachment for a cinder volume. (Supported by API versions 3.44 - 3.latest) +attachment-create,volume attachment create,Create an attachment for a cinder volume. (Supported by API versions 3.27 - 3.latest) +attachment-delete,volume attachment delete,Delete an attachment for a cinder volume. (Supported by API versions 3.27 - 3.latest) +attachment-list,volume attachment list,Lists all attachments. (Supported by API versions 3.27 - 3.latest) +attachment-show,volume attachment show,Show detailed information for attachment. (Supported by API versions 3.27 - 3.latest) +attachment-update,volume attachment update,Update an attachment for a cinder volume. (Supported by API versions 3.27 - 3.latest) backup-create,volume backup create,Creates a volume backup. backup-delete,volume backup delete,Removes a backup. backup-export,volume backup record export,Export backup metadata record. diff --git a/openstackclient/tests/unit/volume/v3/fakes.py b/openstackclient/tests/unit/volume/v3/fakes.py new file mode 100644 index 0000000000..fb3b1b74e1 --- /dev/null +++ b/openstackclient/tests/unit/volume/v3/fakes.py @@ -0,0 +1,155 @@ +# 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 random +from unittest import mock +import uuid + +from cinderclient import api_versions + +from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes +from openstackclient.tests.unit import fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes +from openstackclient.tests.unit import utils +from openstackclient.tests.unit.volume.v2 import fakes as volume_v2_fakes + + +class FakeVolumeClient(object): + + def __init__(self, **kwargs): + self.auth_token = kwargs['token'] + self.management_url = kwargs['endpoint'] + self.api_version = api_versions.APIVersion('3.0') + + self.attachments = mock.Mock() + self.attachments.resource_class = fakes.FakeResource(None, {}) + self.volumes = mock.Mock() + self.volumes.resource_class = fakes.FakeResource(None, {}) + + +class TestVolume(utils.TestCommand): + + def setUp(self): + super().setUp() + + self.app.client_manager.volume = FakeVolumeClient( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN + ) + self.app.client_manager.identity = identity_fakes.FakeIdentityv3Client( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN + ) + self.app.client_manager.compute = compute_fakes.FakeComputev2Client( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN, + ) + + +# TODO(stephenfin): Check if the responses are actually the same +FakeVolume = volume_v2_fakes.FakeVolume + + +class FakeVolumeAttachment: + """Fake one or more volume attachments.""" + + @staticmethod + def create_one_volume_attachment(attrs=None): + """Create a fake volume attachment. + + :param attrs: A dictionary with all attributes of volume attachment + :return: A FakeResource object with id, status, etc. + """ + attrs = attrs or {} + + attachment_id = uuid.uuid4().hex + volume_id = attrs.pop('volume_id', None) or uuid.uuid4().hex + server_id = attrs.pop('instance', None) or uuid.uuid4().hex + + # Set default attribute + attachment_info = { + 'id': attachment_id, + 'volume_id': volume_id, + 'instance': server_id, + 'status': random.choice([ + 'attached', + 'attaching', + 'detached', + 'reserved', + 'error_attaching', + 'error_detaching', + 'deleted', + ]), + 'attach_mode': random.choice(['ro', 'rw']), + 'attached_at': '2015-09-16T09:28:52.000000', + 'detached_at': None, + 'connection_info': { + 'access_mode': 'rw', + 'attachment_id': attachment_id, + 'auth_method': 'CHAP', + 'auth_password': 'AcUZ8PpxLHwzypMC', + 'auth_username': '7j3EZQWT3rbE6pcSGKvK', + 'cacheable': False, + 'driver_volume_type': 'iscsi', + 'encrypted': False, + 'qos_specs': None, + 'target_discovered': False, + 'target_iqn': + f'iqn.2010-10.org.openstack:volume-{attachment_id}', + 'target_lun': '1', + 'target_portal': '192.168.122.170:3260', + 'volume_id': volume_id, + }, + } + + # Overwrite default attributes if there are some attributes set + attachment_info.update(attrs) + + attachment = fakes.FakeResource( + None, + attachment_info, + loaded=True) + return attachment + + @staticmethod + def create_volume_attachments(attrs=None, count=2): + """Create multiple fake volume attachments. + + :param attrs: A dictionary with all attributes of volume attachment + :param count: The number of volume attachments to be faked + :return: A list of FakeResource objects + """ + attachments = [] + + for n in range(0, count): + attachments.append( + FakeVolumeAttachment.create_one_volume_attachment(attrs)) + + return attachments + + @staticmethod + def get_volume_attachments(attachments=None, count=2): + """Get an iterable MagicMock object with a list of faked volumes. + + If attachments list is provided, then initialize the Mock object with + the list. Otherwise create one. + + :param attachments: A list of FakeResource objects faking volume + attachments + :param count: The number of volume attachments to be faked + :return An iterable Mock object with side_effect set to a list of faked + volume attachments + """ + if attachments is None: + attachments = FakeVolumeAttachment.create_volume_attachments(count) + + return mock.Mock(side_effect=attachments) diff --git a/openstackclient/tests/unit/volume/v3/test_volume_attachment.py b/openstackclient/tests/unit/volume/v3/test_volume_attachment.py new file mode 100644 index 0000000000..09f698e7fd --- /dev/null +++ b/openstackclient/tests/unit/volume/v3/test_volume_attachment.py @@ -0,0 +1,560 @@ +# 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 cinderclient import api_versions +from osc_lib.cli import format_columns +from osc_lib import exceptions + +from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes +from openstackclient.tests.unit.volume.v3 import fakes as volume_fakes +from openstackclient.volume.v3 import volume_attachment + + +class TestVolumeAttachment(volume_fakes.TestVolume): + + def setUp(self): + super().setUp() + + self.volumes_mock = self.app.client_manager.volume.volumes + self.volumes_mock.reset_mock() + + self.volume_attachments_mock = \ + self.app.client_manager.volume.attachments + self.volume_attachments_mock.reset_mock() + + self.projects_mock = self.app.client_manager.identity.projects + self.projects_mock.reset_mock() + + self.servers_mock = self.app.client_manager.compute.servers + self.servers_mock.reset_mock() + + +class TestVolumeAttachmentCreate(TestVolumeAttachment): + + volume = volume_fakes.FakeVolume.create_one_volume() + server = compute_fakes.FakeServer.create_one_server() + volume_attachment = \ + volume_fakes.FakeVolumeAttachment.create_one_volume_attachment( + attrs={'instance': server.id, 'volume_id': volume.id}) + + columns = ( + 'ID', + 'Volume ID', + 'Instance ID', + 'Status', + 'Attach Mode', + 'Attached At', + 'Detached At', + 'Properties', + ) + data = ( + volume_attachment.id, + volume_attachment.volume_id, + volume_attachment.instance, + volume_attachment.status, + volume_attachment.attach_mode, + volume_attachment.attached_at, + volume_attachment.detached_at, + format_columns.DictColumn(volume_attachment.connection_info), + ) + + def setUp(self): + super().setUp() + + self.volumes_mock.get.return_value = self.volume + self.servers_mock.get.return_value = self.server + self.volume_attachments_mock.create.return_value = \ + self.volume_attachment + + self.cmd = volume_attachment.CreateVolumeAttachment(self.app, None) + + def test_volume_attachment_create(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.27') + + arglist = [ + self.volume.id, + self.server.id, + ] + verifylist = [ + ('volume', self.volume.id), + ('server', self.server.id), + ('connect', False), + ('initiator', None), + ('ip', None), + ('host', None), + ('platform', None), + ('os_type', None), + ('multipath', False), + ('mountpoint', None), + ('mode', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volumes_mock.get.assert_called_once_with(self.volume.id) + self.servers_mock.get.assert_called_once_with(self.server.id) + self.volume_attachments_mock.create.assert_called_once_with( + self.volume.id, {}, self.server.id, None, + ) + self.assertEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + + def test_volume_attachment_create_with_connect(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.54') + + arglist = [ + self.volume.id, + self.server.id, + '--connect', + '--initiator', 'iqn.1993-08.org.debian:01:cad181614cec', + '--ip', '192.168.1.20', + '--host', 'my-host', + '--platform', 'x86_64', + '--os-type', 'linux2', + '--multipath', + '--mountpoint', '/dev/vdb', + '--mode', 'null', + ] + verifylist = [ + ('volume', self.volume.id), + ('server', self.server.id), + ('connect', True), + ('initiator', 'iqn.1993-08.org.debian:01:cad181614cec'), + ('ip', '192.168.1.20'), + ('host', 'my-host'), + ('platform', 'x86_64'), + ('os_type', 'linux2'), + ('multipath', True), + ('mountpoint', '/dev/vdb'), + ('mode', 'null'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + connect_info = dict([ + ('initiator', 'iqn.1993-08.org.debian:01:cad181614cec'), + ('ip', '192.168.1.20'), + ('host', 'my-host'), + ('platform', 'x86_64'), + ('os_type', 'linux2'), + ('multipath', True), + ('mountpoint', '/dev/vdb'), + ]) + + self.volumes_mock.get.assert_called_once_with(self.volume.id) + self.servers_mock.get.assert_called_once_with(self.server.id) + self.volume_attachments_mock.create.assert_called_once_with( + self.volume.id, connect_info, self.server.id, 'null', + ) + self.assertEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + + def test_volume_attachment_create_pre_v327(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.26') + + arglist = [ + self.volume.id, + self.server.id, + ] + verifylist = [ + ('volume', self.volume.id), + ('server', self.server.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.27 or greater is required', + str(exc)) + + def test_volume_attachment_create_with_mode_pre_v354(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.53') + + arglist = [ + self.volume.id, + self.server.id, + '--mode', 'rw', + ] + verifylist = [ + ('volume', self.volume.id), + ('server', self.server.id), + ('mode', 'rw'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.54 or greater is required', + str(exc)) + + def test_volume_attachment_create_with_connect_missing_arg(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.54') + + arglist = [ + self.volume.id, + self.server.id, + '--initiator', 'iqn.1993-08.org.debian:01:cad181614cec', + ] + verifylist = [ + ('volume', self.volume.id), + ('server', self.server.id), + ('connect', False), + ('initiator', 'iqn.1993-08.org.debian:01:cad181614cec'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + 'You must specify the --connect option for any', + str(exc)) + + +class TestVolumeAttachmentDelete(TestVolumeAttachment): + + volume_attachment = \ + volume_fakes.FakeVolumeAttachment.create_one_volume_attachment() + + def setUp(self): + super().setUp() + + self.volume_attachments_mock.delete.return_value = None + + self.cmd = volume_attachment.DeleteVolumeAttachment(self.app, None) + + def test_volume_attachment_delete(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.27') + + arglist = [ + self.volume_attachment.id, + ] + verifylist = [ + ('attachment', self.volume_attachment.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.volume_attachments_mock.delete.assert_called_once_with( + self.volume_attachment.id, + ) + self.assertIsNone(result) + + def test_volume_attachment_delete_pre_v327(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.26') + + arglist = [ + self.volume_attachment.id, + ] + verifylist = [ + ('attachment', self.volume_attachment.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.27 or greater is required', + str(exc)) + + +class TestVolumeAttachmentSet(TestVolumeAttachment): + + volume_attachment = \ + volume_fakes.FakeVolumeAttachment.create_one_volume_attachment() + + columns = ( + 'ID', + 'Volume ID', + 'Instance ID', + 'Status', + 'Attach Mode', + 'Attached At', + 'Detached At', + 'Properties', + ) + data = ( + volume_attachment.id, + volume_attachment.volume_id, + volume_attachment.instance, + volume_attachment.status, + volume_attachment.attach_mode, + volume_attachment.attached_at, + volume_attachment.detached_at, + format_columns.DictColumn(volume_attachment.connection_info), + ) + + def setUp(self): + super().setUp() + + self.volume_attachments_mock.update.return_value = \ + self.volume_attachment + + self.cmd = volume_attachment.SetVolumeAttachment(self.app, None) + + def test_volume_attachment_set(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.27') + + arglist = [ + self.volume_attachment.id, + '--initiator', 'iqn.1993-08.org.debian:01:cad181614cec', + '--ip', '192.168.1.20', + '--host', 'my-host', + '--platform', 'x86_64', + '--os-type', 'linux2', + '--multipath', + '--mountpoint', '/dev/vdb', + ] + verifylist = [ + ('attachment', self.volume_attachment.id), + ('initiator', 'iqn.1993-08.org.debian:01:cad181614cec'), + ('ip', '192.168.1.20'), + ('host', 'my-host'), + ('platform', 'x86_64'), + ('os_type', 'linux2'), + ('multipath', True), + ('mountpoint', '/dev/vdb'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + connect_info = dict([ + ('initiator', 'iqn.1993-08.org.debian:01:cad181614cec'), + ('ip', '192.168.1.20'), + ('host', 'my-host'), + ('platform', 'x86_64'), + ('os_type', 'linux2'), + ('multipath', True), + ('mountpoint', '/dev/vdb'), + ]) + + self.volume_attachments_mock.update.assert_called_once_with( + self.volume_attachment.id, connect_info, + ) + self.assertEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + + def test_volume_attachment_set_pre_v327(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.26') + + arglist = [ + self.volume_attachment.id, + '--initiator', 'iqn.1993-08.org.debian:01:cad181614cec', + ] + verifylist = [ + ('attachment', self.volume_attachment.id), + ('initiator', 'iqn.1993-08.org.debian:01:cad181614cec'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.27 or greater is required', + str(exc)) + + +class TestVolumeAttachmentComplete(TestVolumeAttachment): + + volume_attachment = \ + volume_fakes.FakeVolumeAttachment.create_one_volume_attachment() + + def setUp(self): + super().setUp() + + self.volume_attachments_mock.complete.return_value = None + + self.cmd = volume_attachment.CompleteVolumeAttachment(self.app, None) + + def test_volume_attachment_complete(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.44') + + arglist = [ + self.volume_attachment.id, + ] + verifylist = [ + ('attachment', self.volume_attachment.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.volume_attachments_mock.complete.assert_called_once_with( + self.volume_attachment.id, + ) + self.assertIsNone(result) + + def test_volume_attachment_complete_pre_v344(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.43') + + arglist = [ + self.volume_attachment.id, + ] + verifylist = [ + ('attachment', self.volume_attachment.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.44 or greater is required', + str(exc)) + + +class TestVolumeAttachmentList(TestVolumeAttachment): + + project = identity_fakes.FakeProject.create_one_project() + volume_attachments = \ + volume_fakes.FakeVolumeAttachment.create_volume_attachments() + + columns = ( + 'ID', + 'Volume ID', + 'Server ID', + 'Status', + ) + data = [ + ( + volume_attachment.id, + volume_attachment.volume_id, + volume_attachment.instance, + volume_attachment.status, + ) for volume_attachment in volume_attachments + ] + + def setUp(self): + super().setUp() + + self.projects_mock.get.return_value = self.project + self.volume_attachments_mock.list.return_value = \ + self.volume_attachments + + self.cmd = volume_attachment.ListVolumeAttachment(self.app, None) + + def test_volume_attachment_list(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.27') + + arglist = [] + verifylist = [ + ('project', None), + ('all_projects', False), + ('volume_id', None), + ('status', None), + ('marker', None), + ('limit', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volume_attachments_mock.list.assert_called_once_with( + search_opts={ + 'all_tenants': False, + 'project_id': None, + 'status': None, + 'volume_id': None, + }, + marker=None, + limit=None, + ) + self.assertEqual(self.columns, columns) + self.assertCountEqual(tuple(self.data), data) + + def test_volume_attachment_list_with_options(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.27') + + arglist = [ + '--project', self.project.name, + '--volume-id', 'volume-id', + '--status', 'attached', + '--marker', 'volume-attachment-id', + '--limit', '2', + ] + verifylist = [ + ('project', self.project.name), + ('all_projects', False), + ('volume_id', 'volume-id'), + ('status', 'attached'), + ('marker', 'volume-attachment-id'), + ('limit', 2), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volume_attachments_mock.list.assert_called_once_with( + search_opts={ + 'all_tenants': True, + 'project_id': self.project.id, + 'status': 'attached', + 'volume_id': 'volume-id', + }, + marker='volume-attachment-id', + limit=2, + ) + self.assertEqual(self.columns, columns) + self.assertCountEqual(tuple(self.data), data) + + def test_volume_attachment_list_pre_v327(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.26') + + arglist = [] + verifylist = [ + ('project', None), + ('all_projects', False), + ('volume_id', None), + ('status', None), + ('marker', None), + ('limit', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.27 or greater is required', + str(exc)) diff --git a/openstackclient/volume/v3/volume_attachment.py b/openstackclient/volume/v3/volume_attachment.py new file mode 100644 index 0000000000..39a9c37fdb --- /dev/null +++ b/openstackclient/volume/v3/volume_attachment.py @@ -0,0 +1,511 @@ +# 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 logging + +from cinderclient import api_versions +from osc_lib.cli import format_columns +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils + +from openstackclient.i18n import _ +from openstackclient.identity import common as identity_common + +LOG = logging.getLogger(__name__) + +_FILTER_DEPRECATED = _( + "This option is deprecated. Consider using the '--filters' option which " + "was introduced in microversion 3.33 instead." +) + + +def _format_attachment(attachment): + columns = ( + 'id', + 'volume_id', + 'instance', + 'status', + 'attach_mode', + 'attached_at', + 'detached_at', + 'connection_info', + ) + column_headers = ( + 'ID', + 'Volume ID', + 'Instance ID', + 'Status', + 'Attach Mode', + 'Attached At', + 'Detached At', + 'Properties', + ) + + # TODO(stephenfin): Improve output with the nested connection_info + # field - cinderclient printed two things but that's equally ugly + return ( + column_headers, + utils.get_item_properties( + attachment, + columns, + formatters={ + 'connection_info': format_columns.DictColumn, + }, + ), + ) + + +class CreateVolumeAttachment(command.ShowOne): + """Create an attachment for a volume. + + This command will only create a volume attachment in the Volume service. It + will not invoke the necessary Compute service actions to actually attach + the volume to the server at the hypervisor level. As a result, it should + typically only be used for troubleshooting issues with an existing server + in combination with other tooling. For all other use cases, the 'server + volume add' command should be preferred. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'volume', + metavar='', + help=_('Name or ID of volume to attach to server.'), + ) + parser.add_argument( + 'server', + metavar='', + help=_('Name or ID of server to attach volume to.'), + ) + parser.add_argument( + '--connect', + action='store_true', + dest='connect', + default=False, + help=_('Make an active connection using provided connector info'), + ) + parser.add_argument( + '--no-connect', + action='store_false', + dest='connect', + help=_( + 'Do not make an active connection using provided connector ' + 'info' + ), + ) + parser.add_argument( + '--initiator', + metavar='', + help=_('IQN of the initiator attaching to'), + ) + parser.add_argument( + '--ip', + metavar='', + help=_('IP of the system attaching to'), + ) + parser.add_argument( + '--host', + metavar='', + help=_('Name of the host attaching to'), + ) + parser.add_argument( + '--platform', + metavar='', + help=_('Platform type'), + ) + parser.add_argument( + '--os-type', + metavar='', + help=_('OS type'), + ) + parser.add_argument( + '--multipath', + action='store_true', + dest='multipath', + default=False, + help=_('Use multipath'), + ) + parser.add_argument( + '--no-multipath', + action='store_false', + dest='multipath', + help=_('Use multipath'), + ) + parser.add_argument( + '--mountpoint', + metavar='', + help=_('Mountpoint volume will be attached at'), + ) + parser.add_argument( + '--mode', + metavar='', + help=_( + 'Mode of volume attachment, rw, ro and null, where null ' + 'indicates we want to honor any existing admin-metadata ' + 'settings ' + '(supported by --os-volume-api-version 3.54 or later)' + ), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + compute_client = self.app.client_manager.compute + + if volume_client.api_version < api_versions.APIVersion('3.27'): + msg = _( + "--os-volume-api-version 3.27 or greater is required to " + "support the 'volume attachment create' command" + ) + raise exceptions.CommandError(msg) + + if parsed_args.mode: + if volume_client.api_version < api_versions.APIVersion('3.54'): + msg = _( + "--os-volume-api-version 3.54 or greater is required to " + "support the '--mode' option" + ) + raise exceptions.CommandError(msg) + + connector = {} + if parsed_args.connect: + connector = { + 'initiator': parsed_args.initiator, + 'ip': parsed_args.ip, + 'platform': parsed_args.platform, + 'host': parsed_args.host, + 'os_type': parsed_args.os_type, + 'multipath': parsed_args.multipath, + 'mountpoint': parsed_args.mountpoint, + } + else: + if any({ + parsed_args.initiator, + parsed_args.ip, + parsed_args.platform, + parsed_args.host, + parsed_args.host, + parsed_args.multipath, + parsed_args.mountpoint, + }): + msg = _( + 'You must specify the --connect option for any of the ' + 'connection-specific options such as --initiator to be ' + 'valid' + ) + raise exceptions.CommandError(msg) + + volume = utils.find_resource( + volume_client.volumes, + parsed_args.volume, + ) + server = utils.find_resource( + compute_client.servers, + parsed_args.server, + ) + + attachment = volume_client.attachments.create( + volume.id, connector, server.id, parsed_args.mode) + + return _format_attachment(attachment) + + +class DeleteVolumeAttachment(command.Command): + """Delete an attachment for a volume. + + Similarly to the 'volume attachment create' command, this command will only + delete the volume attachment record in the Volume service. It will not + invoke the necessary Compute service actions to actually attach the volume + to the server at the hypervisor level. As a result, it should typically + only be used for troubleshooting issues with an existing server in + combination with other tooling. For all other use cases, the 'server volume + remove' command should be preferred. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'attachment', + metavar='', + help=_('ID of volume attachment to delete'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.27'): + msg = _( + "--os-volume-api-version 3.27 or greater is required to " + "support the 'volume attachment delete' command" + ) + raise exceptions.CommandError(msg) + + volume_client.attachments.delete(parsed_args.attachment) + + +class SetVolumeAttachment(command.ShowOne): + """Update an attachment for a volume. + + This call is designed to be more of an volume attachment completion than + anything else. It expects the value of a connector object to notify the + driver that the volume is going to be connected and where it's being + connected to. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'attachment', + metavar='', + help=_('ID of volume attachment.'), + ) + parser.add_argument( + '--initiator', + metavar='', + help=_('IQN of the initiator attaching to'), + ) + parser.add_argument( + '--ip', + metavar='', + help=_('IP of the system attaching to'), + ) + parser.add_argument( + '--host', + metavar='', + help=_('Name of the host attaching to'), + ) + parser.add_argument( + '--platform', + metavar='', + help=_('Platform type'), + ) + parser.add_argument( + '--os-type', + metavar='', + help=_('OS type'), + ) + parser.add_argument( + '--multipath', + action='store_true', + dest='multipath', + default=False, + help=_('Use multipath'), + ) + parser.add_argument( + '--no-multipath', + action='store_false', + dest='multipath', + help=_('Use multipath'), + ) + parser.add_argument( + '--mountpoint', + metavar='', + help=_('Mountpoint volume will be attached at'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.27'): + msg = _( + "--os-volume-api-version 3.27 or greater is required to " + "support the 'volume attachment set' command" + ) + raise exceptions.CommandError(msg) + + connector = { + 'initiator': parsed_args.initiator, + 'ip': parsed_args.ip, + 'platform': parsed_args.platform, + 'host': parsed_args.host, + 'os_type': parsed_args.os_type, + 'multipath': parsed_args.multipath, + 'mountpoint': parsed_args.mountpoint, + } + + attachment = volume_client.attachments.update( + parsed_args.attachment, connector) + + return _format_attachment(attachment) + + +class CompleteVolumeAttachment(command.Command): + """Complete an attachment for a volume.""" + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'attachment', + metavar='', + help=_('ID of volume attachment to mark as completed'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.44'): + msg = _( + "--os-volume-api-version 3.44 or greater is required to " + "support the 'volume attachment complete' command" + ) + raise exceptions.CommandError(msg) + + volume_client.attachments.complete(parsed_args.attachment) + + +class ListVolumeAttachment(command.Lister): + """Lists all volume attachments.""" + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + '--project', + dest='project', + metavar='', + help=_('Filter results by project (name or ID) (admin only)'), + ) + identity_common.add_project_domain_option_to_parser(parser) + parser.add_argument( + '--all-projects', + dest='all_projects', + action='store_true', + default=utils.env('ALL_PROJECTS', default=False), + help=_('Shows details for all projects (admin only).'), + ) + parser.add_argument( + '--volume-id', + metavar='', + default=None, + help=_('Filters results by a volume ID. ') + _FILTER_DEPRECATED, + ) + parser.add_argument( + '--status', + metavar='', + help=_('Filters results by a status. ') + _FILTER_DEPRECATED, + ) + parser.add_argument( + '--marker', + metavar='', + help=_( + 'Begin returning volume attachments that appear later in ' + 'volume attachment list than that represented by this ID.' + ), + ) + parser.add_argument( + '--limit', + type=int, + metavar='', + help=_('Maximum number of volume attachments to return.'), + ) + # TODO(stephenfin): Add once we have an equivalent command for + # 'cinder list-filters' + # parser.add_argument( + # '--filter', + # metavar='', + # action=parseractions.KeyValueAction, + # dest='filters', + # help=_( + # "Filter key and value pairs. Use 'foo' to " + # "check enabled filters from server. Use 'key~=value' for " + # "inexact filtering if the key supports " + # "(supported by --os-volume-api-version 3.33 or above)" + # ), + # ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + identity_client = self.app.client_manager.identity + + if volume_client.api_version < api_versions.APIVersion('3.27'): + msg = _( + "--os-volume-api-version 3.27 or greater is required to " + "support the 'volume attachment list' command" + ) + raise exceptions.CommandError(msg) + + project_id = None + if parsed_args.project: + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + + search_opts = { + 'all_tenants': True if project_id else parsed_args.all_projects, + 'project_id': project_id, + 'status': parsed_args.status, + 'volume_id': parsed_args.volume_id, + } + # Update search option with `filters` + # if AppendFilters.filters: + # search_opts.update(shell_utils.extract_filters(AppendFilters.filters)) + + # TODO(stephenfin): Implement sorting + attachments = volume_client.attachments.list( + search_opts=search_opts, + marker=parsed_args.marker, + limit=parsed_args.limit) + + column_headers = ( + 'ID', + 'Volume ID', + 'Server ID', + 'Status', + ) + columns = ( + 'id', + 'volume_id', + 'instance', + 'status', + ) + + return ( + column_headers, + ( + utils.get_item_properties(a, columns) + for a in attachments + ), + ) + + +class ShowVolumeAttachment(command.ShowOne): + """Show detailed information for a volume attachment.""" + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'attachment', + metavar='', + help=_('ID of volume attachment.'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.27'): + msg = _( + "--os-volume-api-version 3.27 or greater is required to " + "support the 'volume attachment show' command" + ) + raise exceptions.CommandError(msg) + + attachment = volume_client.attachments.show(parsed_args.attachment) + + return _format_attachment(attachment) diff --git a/releasenotes/notes/add-volume-attachment-commands-db2974c6460fa3bc.yaml b/releasenotes/notes/add-volume-attachment-commands-db2974c6460fa3bc.yaml new file mode 100644 index 0000000000..8355cea55a --- /dev/null +++ b/releasenotes/notes/add-volume-attachment-commands-db2974c6460fa3bc.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Add ``volume attachment create``, ``volume attachment delete``, + ``volume attachment list``, ``volume attachment complete``, + ``volume attachment set`` and ``volume attachment show`` commands to + create, delete, list, complete, update and show volume attachments, + respectively. diff --git a/setup.cfg b/setup.cfg index d6d7a3d2b0..792e5a8584 100644 --- a/setup.cfg +++ b/setup.cfg @@ -697,6 +697,13 @@ openstack.volume.v3 = volume_show = openstackclient.volume.v2.volume:ShowVolume volume_unset = openstackclient.volume.v2.volume:UnsetVolume + volume_attachment_create = openstackclient.volume.v3.volume_attachment:CreateVolumeAttachment + volume_attachment_delete = openstackclient.volume.v3.volume_attachment:DeleteVolumeAttachment + volume_attachment_list = openstackclient.volume.v3.volume_attachment:ListVolumeAttachment + volume_attachment_complete = openstackclient.volume.v3.volume_attachment:CompleteVolumeAttachment + volume_attachment_set = openstackclient.volume.v3.volume_attachment:SetVolumeAttachment + volume_attachment_show = openstackclient.volume.v3.volume_attachment:ShowVolumeAttachment + volume_backup_create = openstackclient.volume.v2.volume_backup:CreateVolumeBackup volume_backup_delete = openstackclient.volume.v2.volume_backup:DeleteVolumeBackup volume_backup_list = openstackclient.volume.v2.volume_backup:ListVolumeBackup From 0eddab36e52e813e2329ac10044fa4f67830efec Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Fri, 19 Mar 2021 11:34:31 +0000 Subject: [PATCH 2386/3095] volume: Add 'volume message *' commands This patch implements the necessary commands to utilize the Messages API introduced in Cinder API version 3.3. Version 3.5 built upon this by implementing pagination support for these commands which is present in this patch as well. volume message get volume message list volume message delete Change-Id: I64aa0b4a8d4468baa8c63e5e30ee31de68df999d --- .../cli/command-objects/volume-message.rst | 8 + doc/source/cli/commands.rst | 1 + doc/source/cli/data/cinder.csv | 6 +- openstackclient/tests/unit/volume/v3/fakes.py | 68 ++++ .../unit/volume/v3/test_volume_message.py | 324 ++++++++++++++++++ openstackclient/volume/v3/volume_message.py | 165 +++++++++ ...ume-message-commands-89a590a1549c333e.yaml | 6 + setup.cfg | 4 + 8 files changed, 579 insertions(+), 3 deletions(-) create mode 100644 doc/source/cli/command-objects/volume-message.rst create mode 100644 openstackclient/tests/unit/volume/v3/test_volume_message.py create mode 100644 openstackclient/volume/v3/volume_message.py create mode 100644 releasenotes/notes/add-volume-message-commands-89a590a1549c333e.yaml diff --git a/doc/source/cli/command-objects/volume-message.rst b/doc/source/cli/command-objects/volume-message.rst new file mode 100644 index 0000000000..5b1a8acef2 --- /dev/null +++ b/doc/source/cli/command-objects/volume-message.rst @@ -0,0 +1,8 @@ +============== +volume message +============== + +Block Storage v3 + +.. autoprogram-cliff:: openstack.volume.v3 + :command: volume message * diff --git a/doc/source/cli/commands.rst b/doc/source/cli/commands.rst index 00f6b23a77..cc95b3d121 100644 --- a/doc/source/cli/commands.rst +++ b/doc/source/cli/commands.rst @@ -160,6 +160,7 @@ referring to both Compute and Volume quotas. * ``volume backup record``: (**Volume**) volume record that can be imported or exported * ``volume backend``: (**Volume**) volume backend storage * ``volume host``: (**Volume**) the physical computer for volumes +* ``volume message``: (**Volume**) volume API internal messages detailing volume failure messages * ``volume qos``: (**Volume**) quality-of-service (QoS) specification for volumes * ``volume snapshot``: (**Volume**) a point-in-time copy of a volume * ``volume type``: (**Volume**) deployment-specific types of volumes available diff --git a/doc/source/cli/data/cinder.csv b/doc/source/cli/data/cinder.csv index dc43ab5b1a..f94552ea93 100644 --- a/doc/source/cli/data/cinder.csv +++ b/doc/source/cli/data/cinder.csv @@ -72,9 +72,9 @@ list,volume list,Lists all volumes. list-filters,,List enabled filters. (Supported by API versions 3.33 - 3.latest) manage,volume create --remote-source k=v,Manage an existing volume. manageable-list,,Lists all manageable volumes. (Supported by API versions 3.8 - 3.latest) -message-delete,,Removes one or more messages. (Supported by API versions 3.3 - 3.latest) -message-list,,Lists all messages. (Supported by API versions 3.3 - 3.latest) -message-show,,Shows message details. (Supported by API versions 3.3 - 3.latest) +message-delete,volume message delete,Removes one or more messages. (Supported by API versions 3.3 - 3.latest) +message-list,volume message list,Lists all messages. (Supported by API versions 3.3 - 3.latest) +message-show,volume message show,Shows message details. (Supported by API versions 3.3 - 3.latest) metadata,volume set --property k=v / volume unset --property k,Sets or deletes volume metadata. metadata-show,volume show,Shows volume metadata. metadata-update-all,volume set --property k=v,Updates volume metadata. diff --git a/openstackclient/tests/unit/volume/v3/fakes.py b/openstackclient/tests/unit/volume/v3/fakes.py index fb3b1b74e1..45cad8c14c 100644 --- a/openstackclient/tests/unit/volume/v3/fakes.py +++ b/openstackclient/tests/unit/volume/v3/fakes.py @@ -32,6 +32,8 @@ def __init__(self, **kwargs): self.attachments = mock.Mock() self.attachments.resource_class = fakes.FakeResource(None, {}) + self.messages = mock.Mock() + self.messages.resource_class = fakes.FakeResource(None, {}) self.volumes = mock.Mock() self.volumes.resource_class = fakes.FakeResource(None, {}) @@ -59,6 +61,72 @@ def setUp(self): FakeVolume = volume_v2_fakes.FakeVolume +class FakeVolumeMessage: + """Fake one or more volume messages.""" + + @staticmethod + def create_one_volume_message(attrs=None): + """Create a fake message. + + :param attrs: A dictionary with all attributes of message + :return: A FakeResource object with id, name, status, etc. + """ + attrs = attrs or {} + + # Set default attribute + message_info = { + 'created_at': '2016-02-11T11:17:37.000000', + 'event_id': f'VOLUME_{random.randint(1, 999999):06d}', + 'guaranteed_until': '2016-02-11T11:17:37.000000', + 'id': uuid.uuid4().hex, + 'message_level': 'ERROR', + 'request_id': f'req-{uuid.uuid4().hex}', + 'resource_type': 'VOLUME', + 'resource_uuid': uuid.uuid4().hex, + 'user_message': f'message-{uuid.uuid4().hex}', + } + + # Overwrite default attributes if there are some attributes set + message_info.update(attrs) + + message = fakes.FakeResource( + None, + message_info, + loaded=True) + return message + + @staticmethod + def create_volume_messages(attrs=None, count=2): + """Create multiple fake messages. + + :param attrs: A dictionary with all attributes of message + :param count: The number of messages to be faked + :return: A list of FakeResource objects + """ + messages = [] + for n in range(0, count): + messages.append(FakeVolumeMessage.create_one_volume_message(attrs)) + + return messages + + @staticmethod + def get_volume_messages(messages=None, count=2): + """Get an iterable MagicMock object with a list of faked messages. + + If messages list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param messages: A list of FakeResource objects faking messages + :param count: The number of messages to be faked + :return An iterable Mock object with side_effect set to a list of faked + messages + """ + if messages is None: + messages = FakeVolumeMessage.create_messages(count) + + return mock.Mock(side_effect=messages) + + class FakeVolumeAttachment: """Fake one or more volume attachments.""" diff --git a/openstackclient/tests/unit/volume/v3/test_volume_message.py b/openstackclient/tests/unit/volume/v3/test_volume_message.py new file mode 100644 index 0000000000..68becf4418 --- /dev/null +++ b/openstackclient/tests/unit/volume/v3/test_volume_message.py @@ -0,0 +1,324 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from unittest.mock import call + +from cinderclient import api_versions +from osc_lib import exceptions + +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes +from openstackclient.tests.unit.volume.v3 import fakes as volume_fakes +from openstackclient.volume.v3 import volume_message + + +class TestVolumeMessage(volume_fakes.TestVolume): + + def setUp(self): + super().setUp() + + self.projects_mock = self.app.client_manager.identity.projects + self.projects_mock.reset_mock() + + self.volume_messages_mock = self.app.client_manager.volume.messages + self.volume_messages_mock.reset_mock() + + +class TestVolumeMessageDelete(TestVolumeMessage): + + fake_messages = volume_fakes.FakeVolumeMessage.create_volume_messages( + count=2) + + def setUp(self): + super().setUp() + + self.volume_messages_mock.get = \ + volume_fakes.FakeVolumeMessage.get_volume_messages( + self.fake_messages) + self.volume_messages_mock.delete.return_value = None + + # Get the command object to mock + self.cmd = volume_message.DeleteMessage(self.app, None) + + def test_message_delete(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.3') + + arglist = [ + self.fake_messages[0].id, + ] + verifylist = [ + ('message_ids', [self.fake_messages[0].id]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.volume_messages_mock.delete.assert_called_with( + self.fake_messages[0].id) + self.assertIsNone(result) + + def test_message_delete_multiple_messages(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.3') + + arglist = [ + self.fake_messages[0].id, + self.fake_messages[1].id, + ] + verifylist = [ + ('message_ids', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + calls = [] + for m in self.fake_messages: + calls.append(call(m.id)) + self.volume_messages_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_message_delete_multiple_messages_with_exception(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.3') + + arglist = [ + self.fake_messages[0].id, + 'invalid_message', + ] + verifylist = [ + ('message_ids', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.volume_messages_mock.delete.side_effect = [ + self.fake_messages[0], exceptions.CommandError] + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, parsed_args) + self.assertEqual('Failed to delete 1 of 2 messages.', str(exc)) + + self.volume_messages_mock.delete.assert_any_call( + self.fake_messages[0].id) + self.volume_messages_mock.delete.assert_any_call('invalid_message') + + self.assertEqual(2, self.volume_messages_mock.delete.call_count) + + def test_message_delete_pre_v33(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.2') + + arglist = [ + self.fake_messages[0].id, + ] + verifylist = [ + ('message_ids', [self.fake_messages[0].id]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.3 or greater is required', + str(exc)) + + +class TestVolumeMessageList(TestVolumeMessage): + + fake_project = identity_fakes.FakeProject.create_one_project() + fake_messages = volume_fakes.FakeVolumeMessage.create_volume_messages( + count=3) + + columns = ( + 'ID', + 'Event ID', + 'Resource Type', + 'Resource UUID', + 'Message Level', + 'User Message', + 'Request ID', + 'Created At', + 'Guaranteed Until', + ) + data = [] + for fake_message in fake_messages: + data.append(( + fake_message.id, + fake_message.event_id, + fake_message.resource_type, + fake_message.resource_uuid, + fake_message.message_level, + fake_message.user_message, + fake_message.request_id, + fake_message.created_at, + fake_message.guaranteed_until, + )) + + def setUp(self): + super().setUp() + + self.projects_mock.get.return_value = self.fake_project + self.volume_messages_mock.list.return_value = self.fake_messages + # Get the command to test + self.cmd = volume_message.ListMessages(self.app, None) + + def test_message_list(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.3') + + arglist = [] + verifylist = [ + ('project', None), + ('marker', None), + ('limit', None), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + search_opts = { + 'project_id': None, + } + self.volume_messages_mock.list.assert_called_with( + search_opts=search_opts, + marker=None, + limit=None, + ) + self.assertEqual(self.columns, columns) + self.assertItemsEqual(self.data, list(data)) + + def test_message_list_with_options(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.3') + + arglist = [ + '--project', self.fake_project.name, + '--marker', self.fake_messages[0].id, + '--limit', '3', + ] + verifylist = [ + ('project', self.fake_project.name), + ('marker', self.fake_messages[0].id), + ('limit', 3), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + search_opts = { + 'project_id': self.fake_project.id, + } + self.volume_messages_mock.list.assert_called_with( + search_opts=search_opts, + marker=self.fake_messages[0].id, + limit=3, + ) + self.assertEqual(self.columns, columns) + self.assertItemsEqual(self.data, list(data)) + + def test_message_list_pre_v33(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.2') + + arglist = [] + verifylist = [ + ('project', None), + ('marker', None), + ('limit', None), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.3 or greater is required', + str(exc)) + + +class TestVolumeMessageShow(TestVolumeMessage): + + fake_message = volume_fakes.FakeVolumeMessage.create_one_volume_message() + + columns = ( + 'created_at', + 'event_id', + 'guaranteed_until', + 'id', + 'message_level', + 'request_id', + 'resource_type', + 'resource_uuid', + 'user_message', + ) + data = ( + fake_message.created_at, + fake_message.event_id, + fake_message.guaranteed_until, + fake_message.id, + fake_message.message_level, + fake_message.request_id, + fake_message.resource_type, + fake_message.resource_uuid, + fake_message.user_message, + ) + + def setUp(self): + super().setUp() + + self.volume_messages_mock.get.return_value = self.fake_message + # Get the command object to test + self.cmd = volume_message.ShowMessage(self.app, None) + + def test_message_show(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.3') + + arglist = [ + self.fake_message.id + ] + verifylist = [ + ('message_id', self.fake_message.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.volume_messages_mock.get.assert_called_with(self.fake_message.id) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_message_show_pre_v33(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.2') + + arglist = [ + self.fake_message.id + ] + verifylist = [ + ('message_id', self.fake_message.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.3 or greater is required', + str(exc)) diff --git a/openstackclient/volume/v3/volume_message.py b/openstackclient/volume/v3/volume_message.py new file mode 100644 index 0000000000..4fe5ae92f6 --- /dev/null +++ b/openstackclient/volume/v3/volume_message.py @@ -0,0 +1,165 @@ +# +# 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 Messages implementations""" + +import logging as LOG + +from cinderclient import api_versions +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils + +from openstackclient.i18n import _ +from openstackclient.identity import common as identity_common + + +class DeleteMessage(command.Command): + _description = _('Delete a volume failure message') + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'message_ids', + metavar='', + nargs='+', + help=_('Message(s) to delete (ID)') + ) + + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.3'): + msg = _( + "--os-volume-api-version 3.3 or greater is required to " + "support the 'volume message delete' command" + ) + raise exceptions.CommandError(msg) + + errors = 0 + for message_id in parsed_args.message_ids: + try: + volume_client.messages.delete(message_id) + except Exception: + LOG.error(_('Failed to delete message: %s'), message_id) + errors += 1 + + if errors > 0: + total = len(parsed_args.message_ids) + msg = _('Failed to delete %(errors)s of %(total)s messages.') % { + 'errors': errors, 'total': total, + } + raise exceptions.CommandError(msg) + + +class ListMessages(command.Lister): + _description = _('List volume failure messages') + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + + parser.add_argument( + '--project', + metavar='', + help=_('Filter results by project (name or ID) (admin only)'), + ) + identity_common.add_project_domain_option_to_parser(parser) + parser.add_argument( + '--marker', + metavar='', + help=_('The last message ID of the previous page'), + default=None, + ) + parser.add_argument( + '--limit', + type=int, + metavar='', + help=_('Maximum number of messages to display'), + default=None, + ) + + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + identity_client = self.app.client_manager.identity + + if volume_client.api_version < api_versions.APIVersion('3.3'): + msg = _( + "--os-volume-api-version 3.3 or greater is required to " + "support the 'volume message list' command" + ) + raise exceptions.CommandError(msg) + + column_headers = ( + 'ID', + 'Event ID', + 'Resource Type', + 'Resource UUID', + 'Message Level', + 'User Message', + 'Request ID', + 'Created At', + 'Guaranteed Until', + ) + + project_id = None + if parsed_args.project: + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain).id + + search_opts = { + 'project_id': project_id, + } + data = volume_client.messages.list( + search_opts=search_opts, + marker=parsed_args.marker, + limit=parsed_args.limit) + + return ( + column_headers, + (utils.get_item_properties(s, column_headers) for s in data) + ) + + +class ShowMessage(command.ShowOne): + _description = _('Show a volume failure message') + + def get_parser(self, prog_name): + parser = super(ShowMessage, self).get_parser(prog_name) + parser.add_argument( + 'message_id', + metavar='', + help=_('Message to show (ID).') + ) + + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.3'): + msg = _( + "--os-volume-api-version 3.3 or greater is required to " + "support the 'volume message show' command" + ) + raise exceptions.CommandError(msg) + + message = volume_client.messages.get(parsed_args.message_id) + + return zip(*sorted(message._info.items())) diff --git a/releasenotes/notes/add-volume-message-commands-89a590a1549c333e.yaml b/releasenotes/notes/add-volume-message-commands-89a590a1549c333e.yaml new file mode 100644 index 0000000000..eba53524e7 --- /dev/null +++ b/releasenotes/notes/add-volume-message-commands-89a590a1549c333e.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``volume message list``, ``volume message get`` and + ``volume message delete`` commands, to list, get and delete volume + failure messages, respectively. diff --git a/setup.cfg b/setup.cfg index 792e5a8584..d593762ac9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -716,6 +716,10 @@ openstack.volume.v3 = volume_host_set = openstackclient.volume.v2.volume_host:SetVolumeHost + volume_message_delete = openstackclient.volume.v3.volume_message:DeleteMessage + volume_message_list = openstackclient.volume.v3.volume_message:ListMessages + volume_message_show = openstackclient.volume.v3.volume_message:ShowMessage + volume_snapshot_create = openstackclient.volume.v2.volume_snapshot:CreateVolumeSnapshot volume_snapshot_delete = openstackclient.volume.v2.volume_snapshot:DeleteVolumeSnapshot volume_snapshot_list = openstackclient.volume.v2.volume_snapshot:ListVolumeSnapshot From 524af4a23efde62989ad55ecebabff0b50395308 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 26 May 2021 14:42:52 +0100 Subject: [PATCH 2387/3095] volume: Add missing 'volume backup *' options Add a couple of missing options to each command: volume backup create --no-incremental --property --availability-zone volume backup set --property Most of these are version dependent so we add the relevant version checks as part of this work. While we're here, we also make the formatting a little easier on the eye in places. Change-Id: I328d5c981cb32b2ee9a4b1bd43aa36b22347ff63 Signed-off-by: Stephen Finucane --- doc/source/cli/data/cinder.csv | 2 +- .../unit/volume/v2/test_volume_backup.py | 155 ++++++++++++- openstackclient/volume/v2/volume_backup.py | 219 ++++++++++++++---- ...g-volume-backup-opts-b9246aded87427ce.yaml | 15 ++ 4 files changed, 337 insertions(+), 54 deletions(-) create mode 100644 releasenotes/notes/add-missing-volume-backup-opts-b9246aded87427ce.yaml diff --git a/doc/source/cli/data/cinder.csv b/doc/source/cli/data/cinder.csv index f94552ea93..649cd8f59a 100644 --- a/doc/source/cli/data/cinder.csv +++ b/doc/source/cli/data/cinder.csv @@ -15,7 +15,7 @@ backup-list,volume backup list,Lists all backups. backup-reset-state,volume backup set --state,Explicitly updates the backup state. backup-restore,volume backup restore,Restores a backup. backup-show,volume backup show,Show backup details. -backup-update,,Updates a backup. (Supported by API versions 3.9 - 3.latest) +backup-update,volume backup set,Updates a backup. (Supported by API versions 3.9 - 3.latest) cgsnapshot-create,consistency group snapshot create,Creates a cgsnapshot. cgsnapshot-delete,consistency group snapshot delete,Removes one or more cgsnapshots. cgsnapshot-list,consistency group snapshot list,Lists all cgsnapshots. diff --git a/openstackclient/tests/unit/volume/v2/test_volume_backup.py b/openstackclient/tests/unit/volume/v2/test_volume_backup.py index 13513ed8ad..7b5a965e09 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume_backup.py +++ b/openstackclient/tests/unit/volume/v2/test_volume_backup.py @@ -15,6 +15,7 @@ from unittest import mock from unittest.mock import call +from cinderclient import api_versions from osc_lib import exceptions from osc_lib import utils @@ -114,6 +115,104 @@ def test_backup_create(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_backup_create_with_properties(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.43') + + arglist = [ + "--property", "foo=bar", + "--property", "wow=much-cool", + self.new_backup.volume_id, + ] + verifylist = [ + ("properties", {"foo": "bar", "wow": "much-cool"}), + ("volume", self.new_backup.volume_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.backups_mock.create.assert_called_with( + self.new_backup.volume_id, + container=None, + name=None, + description=None, + force=False, + incremental=False, + metadata={"foo": "bar", "wow": "much-cool"}, + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_backup_create_with_properties_pre_v343(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.42') + + arglist = [ + "--property", "foo=bar", + "--property", "wow=much-cool", + self.new_backup.volume_id, + ] + verifylist = [ + ("properties", {"foo": "bar", "wow": "much-cool"}), + ("volume", self.new_backup.volume_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn("--os-volume-api-version 3.43 or greater", str(exc)) + + def test_backup_create_with_availability_zone(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.51') + + arglist = [ + "--availability-zone", "my-az", + self.new_backup.volume_id, + ] + verifylist = [ + ("availability_zone", "my-az"), + ("volume", self.new_backup.volume_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.backups_mock.create.assert_called_with( + self.new_backup.volume_id, + container=None, + name=None, + description=None, + force=False, + incremental=False, + availability_zone="my-az", + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_backup_create_with_availability_zone_pre_v351(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.50') + + arglist = [ + "--availability-zone", "my-az", + self.new_backup.volume_id, + ] + verifylist = [ + ("availability_zone", "my-az"), + ("volume", self.new_backup.volume_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn("--os-volume-api-version 3.51 or greater", str(exc)) + def test_backup_create_without_name(self): arglist = [ "--description", self.new_backup.description, @@ -136,7 +235,6 @@ def test_backup_create_without_name(self): description=self.new_backup.description, force=False, incremental=False, - snapshot_id=None, ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -240,18 +338,18 @@ class TestBackupList(TestBackup): backups = volume_fakes.FakeBackup.create_backups( attrs={'volume_id': volume.name}, count=3) - columns = [ + columns = ( 'ID', 'Name', 'Description', 'Status', 'Size', - ] - columns_long = columns + [ + ) + columns_long = columns + ( 'Availability Zone', 'Volume', 'Container', - ] + ) data = [] for b in backups: @@ -403,6 +501,9 @@ def setUp(self): self.cmd = volume_backup.SetVolumeBackup(self.app, None) def test_backup_set_name(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.9') + arglist = [ '--name', 'new_name', self.backup.id, @@ -420,7 +521,30 @@ def test_backup_set_name(self): self.backup.id, **{'name': 'new_name'}) self.assertIsNone(result) + def test_backup_set_name_pre_v39(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.8') + + arglist = [ + '--name', 'new_name', + self.backup.id, + ] + verifylist = [ + ('name', 'new_name'), + ('backup', self.backup.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn("--os-volume-api-version 3.9 or greater", str(exc)) + def test_backup_set_description(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.9') + arglist = [ '--description', 'new_description', self.backup.id, @@ -444,6 +568,27 @@ def test_backup_set_description(self): ) self.assertIsNone(result) + def test_backup_set_description_pre_v39(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.8') + + arglist = [ + '--description', 'new_description', + self.backup.id, + ] + verifylist = [ + ('name', None), + ('description', 'new_description'), + ('backup', self.backup.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn("--os-volume-api-version 3.9 or greater", str(exc)) + def test_backup_set_state(self): arglist = [ '--state', 'error', diff --git a/openstackclient/volume/v2/volume_backup.py b/openstackclient/volume/v2/volume_backup.py index c336f6c9ac..a171164e3a 100644 --- a/openstackclient/volume/v2/volume_backup.py +++ b/openstackclient/volume/v2/volume_backup.py @@ -14,10 +14,10 @@ """Volume v2 Backup action implementations""" -import copy import functools import logging +from cinderclient import api_versions from cliff import columns as cliff_columns from osc_lib.cli import parseractions from osc_lib.command import command @@ -61,7 +61,7 @@ class CreateVolumeBackup(command.ShowOne): _description = _("Create new volume backup") def get_parser(self, prog_name): - parser = super(CreateVolumeBackup, self).get_parser(prog_name) + parser = super().get_parser(prog_name) parser.add_argument( "volume", metavar="", @@ -99,16 +99,67 @@ def get_parser(self, prog_name): default=False, help=_("Perform an incremental backup") ) + parser.add_argument( + '--no-incremental', + action='store_false', + help=_("Do not perform an incremental backup") + ) + parser.add_argument( + '--property', + metavar='', + action=parseractions.KeyValueAction, + dest='properties', + help=_( + 'Set a property on this backup ' + '(repeat option to remove multiple values) ' + '(supported by --os-volume-api-version 3.43 or above)' + ), + ) + parser.add_argument( + '--availability-zone', + metavar='', + help=_( + 'AZ where the backup should be stored; by default it will be ' + 'the same as the source ' + '(supported by --os-volume-api-version 3.51 or above)' + ), + ) return parser def take_action(self, parsed_args): volume_client = self.app.client_manager.volume + volume_id = utils.find_resource( - volume_client.volumes, parsed_args.volume).id - snapshot_id = None + volume_client.volumes, parsed_args.volume, + ).id + + kwargs = {} + if parsed_args.snapshot: - snapshot_id = utils.find_resource( - volume_client.volume_snapshots, parsed_args.snapshot).id + kwargs['snapshot_id'] = utils.find_resource( + volume_client.volume_snapshots, parsed_args.snapshot, + ).id + + if parsed_args.properties: + if volume_client.api_version < api_versions.APIVersion('3.43'): + msg = _( + '--os-volume-api-version 3.43 or greater is required to ' + 'support the --property option' + ) + raise exceptions.CommandError(msg) + + kwargs['metadata'] = parsed_args.properties + + if parsed_args.availability_zone: + if volume_client.api_version < api_versions.APIVersion('3.51'): + msg = _( + '--os-volume-api-version 3.51 or greater is required to ' + 'support the --availability-zone option' + ) + raise exceptions.CommandError(msg) + + kwargs['availability_zone'] = parsed_args.availability_zone + backup = volume_client.backups.create( volume_id, container=parsed_args.container, @@ -116,7 +167,7 @@ def take_action(self, parsed_args): description=parsed_args.description, force=parsed_args.force, incremental=parsed_args.incremental, - snapshot_id=snapshot_id, + **kwargs, ) backup._info.pop("links", None) return zip(*sorted(backup._info.items())) @@ -148,7 +199,8 @@ def take_action(self, parsed_args): for i in parsed_args.backups: try: backup_id = utils.find_resource( - volume_client.backups, i).id + volume_client.backups, i, + ).id volume_client.backups.delete(backup_id, parsed_args.force) except Exception as e: result += 1 @@ -158,8 +210,9 @@ def take_action(self, parsed_args): if result > 0: total = len(parsed_args.backups) - msg = (_("%(result)s of %(total)s backups failed " - "to delete.") % {'result': result, 'total': total}) + msg = _("%(result)s of %(total)s backups failed to delete.") % { + 'result': result, 'total': total, + } raise exceptions.CommandError(msg) @@ -182,17 +235,22 @@ def get_parser(self, prog_name): parser.add_argument( "--status", metavar="", - choices=['creating', 'available', 'deleting', - 'error', 'restoring', 'error_restoring'], - help=_("Filters results by the backup status " - "('creating', 'available', 'deleting', " - "'error', 'restoring' or 'error_restoring')") + choices=[ + 'creating', 'available', 'deleting', + 'error', 'restoring', 'error_restoring', + ], + help=_( + "Filters results by the backup status, one of: " + "creating, available, deleting, error, restoring or " + "error_restoring" + ), ) parser.add_argument( "--volume", metavar="", - help=_("Filters results by the volume which they " - "backup (name or ID)") + help=_( + "Filters results by the volume which they backup (name or ID)" + ), ) parser.add_argument( '--marker', @@ -212,19 +270,30 @@ def get_parser(self, prog_name): default=False, help=_('Include all projects (admin only)'), ) + # TODO(stephenfin): Add once we have an equivalent command for + # 'cinder list-filters' + # parser.add_argument( + # '--filter', + # metavar='', + # action=parseractions.KeyValueAction, + # dest='filters', + # help=_( + # "Filter key and value pairs. Use 'foo' to " + # "check enabled filters from server. Use 'key~=value' for " + # "inexact filtering if the key supports " + # "(supported by --os-volume-api-version 3.33 or above)" + # ), + # ) return parser def take_action(self, parsed_args): volume_client = self.app.client_manager.volume + columns = ('id', 'name', 'description', 'status', 'size') + column_headers = ('ID', 'Name', 'Description', 'Status', 'Size') if parsed_args.long: - columns = ['ID', 'Name', 'Description', 'Status', 'Size', - 'Availability Zone', 'Volume ID', 'Container'] - column_headers = copy.deepcopy(columns) - column_headers[6] = 'Volume' - else: - columns = ['ID', 'Name', 'Description', 'Status', 'Size'] - column_headers = columns + columns += ('availability_zone', 'volume_id', 'container') + column_headers += ('Availability Zone', 'Volume', 'Container') # Cache the volume list volume_cache = {} @@ -234,17 +303,22 @@ def take_action(self, parsed_args): except Exception: # Just forget it if there's any trouble pass - _VolumeIdColumn = functools.partial(VolumeIdColumn, - volume_cache=volume_cache) + + _VolumeIdColumn = functools.partial( + VolumeIdColumn, volume_cache=volume_cache) filter_volume_id = None if parsed_args.volume: - filter_volume_id = utils.find_resource(volume_client.volumes, - parsed_args.volume).id + filter_volume_id = utils.find_resource( + volume_client.volumes, parsed_args.volume, + ).id + marker_backup_id = None if parsed_args.marker: - marker_backup_id = utils.find_resource(volume_client.backups, - parsed_args.marker).id + marker_backup_id = utils.find_resource( + volume_client.backups, parsed_args.marker, + ).id + search_opts = { 'name': parsed_args.name, 'status': parsed_args.status, @@ -257,11 +331,14 @@ def take_action(self, parsed_args): limit=parsed_args.limit, ) - return (column_headers, - (utils.get_item_properties( - s, columns, - formatters={'Volume ID': _VolumeIdColumn}, - ) for s in data)) + return ( + column_headers, + ( + utils.get_item_properties( + s, columns, formatters={'volume_id': _VolumeIdColumn}, + ) for s in data + ), + ) class RestoreVolumeBackup(command.ShowOne): @@ -295,7 +372,7 @@ class SetVolumeBackup(command.Command): _description = _("Set volume backup properties") def get_parser(self, prog_name): - parser = super(SetVolumeBackup, self).get_parser(prog_name) + parser = super().get_parser(prog_name) parser.add_argument( "backup", metavar="", @@ -304,28 +381,48 @@ def get_parser(self, prog_name): parser.add_argument( '--name', metavar='', - help=_('New backup name') + help=_( + 'New backup name' + '(supported by --os-volume-api-version 3.9 or above)' + ), ) parser.add_argument( '--description', metavar='', - help=_('New backup description') + help=_( + 'New backup description ' + '(supported by --os-volume-api-version 3.9 or above)' + ), ) parser.add_argument( '--state', metavar='', choices=['available', 'error'], - help=_('New backup state ("available" or "error") (admin only) ' - '(This option simply changes the state of the backup ' - 'in the database with no regard to actual status, ' - 'exercise caution when using)'), + help=_( + 'New backup state ("available" or "error") (admin only) ' + '(This option simply changes the state of the backup ' + 'in the database with no regard to actual status; ' + 'exercise caution when using)' + ), + ) + parser.add_argument( + '--property', + metavar='', + action=parseractions.KeyValueAction, + dest='properties', + help=_( + 'Set a property on this backup ' + '(repeat option to set multiple values) ' + '(supported by --os-volume-api-version 3.43 or above)' + ), ) return parser def take_action(self, parsed_args): volume_client = self.app.client_manager.volume - backup = utils.find_resource(volume_client.backups, - parsed_args.backup) + backup = utils.find_resource( + volume_client.backups, parsed_args.backup) + result = 0 if parsed_args.state: try: @@ -336,21 +433,47 @@ def take_action(self, parsed_args): result += 1 kwargs = {} + if parsed_args.name: + if volume_client.api_version < api_versions.APIVersion('3.9'): + msg = _( + '--os-volume-api-version 3.9 or greater is required to ' + 'support the --name option' + ) + raise exceptions.CommandError(msg) + kwargs['name'] = parsed_args.name + if parsed_args.description: + if volume_client.api_version < api_versions.APIVersion('3.9'): + msg = _( + '--os-volume-api-version 3.9 or greater is required to ' + 'support the --description option' + ) + raise exceptions.CommandError(msg) + kwargs['description'] = parsed_args.description + + if parsed_args.properties: + if volume_client.api_version < api_versions.APIVersion('3.43'): + msg = _( + '--os-volume-api-version 3.43 or greater is required to ' + 'support the --property option' + ) + raise exceptions.CommandError(msg) + + kwargs['metadata'] = parsed_args.properties + if kwargs: try: volume_client.backups.update(backup.id, **kwargs) except Exception as e: - LOG.error(_("Failed to update backup name " - "or description: %s"), e) + LOG.error("Failed to update backup name or description: %s", e) result += 1 if result > 0: - raise exceptions.CommandError(_("One or more of the " - "set operations failed")) + msg = _("One or more of the set operations failed") + raise exceptions.CommandError(msg) class ShowVolumeBackup(command.ShowOne): diff --git a/releasenotes/notes/add-missing-volume-backup-opts-b9246aded87427ce.yaml b/releasenotes/notes/add-missing-volume-backup-opts-b9246aded87427ce.yaml new file mode 100644 index 0000000000..883cb0d564 --- /dev/null +++ b/releasenotes/notes/add-missing-volume-backup-opts-b9246aded87427ce.yaml @@ -0,0 +1,15 @@ +--- +features: + - | + Add ``--no-incremental``, ``--property`` and ``--availability-zone`` + options to ``volume backup create`` command, allowing users to request a + non-incremental backup, set a metadata property on the created backup, and + set an availability zone on the created backup, respectively. + - | + Add ``--property`` option the ``volume backup set`` command to set a + metadata property on an existing backup. +fixes: + - | + The ``--name`` and ``--description`` options of the ``volume backup set`` + command will now verify the version requested on the client side. + Previously this would fail on the server side. From 5faa9ef8058a161a0637dceb91f213ac5ac39070 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 3 Jun 2021 11:09:47 +0100 Subject: [PATCH 2388/3095] tests: Rename 'FakeType' -> 'FakeVolumeType' There are more types than just volume types. Change-Id: I6af66f966a221437ff79fabcb0b81fd38586fe67 Signed-off-by: Stephen Finucane --- openstackclient/tests/unit/volume/v1/fakes.py | 30 ++++++------ .../tests/unit/volume/v1/test_qos_specs.py | 4 +- .../tests/unit/volume/v1/test_type.py | 43 +++++++++-------- openstackclient/tests/unit/volume/v2/fakes.py | 46 +++++++++---------- .../unit/volume/v2/test_consistency_group.py | 2 +- .../tests/unit/volume/v2/test_qos_specs.py | 4 +- .../tests/unit/volume/v2/test_type.py | 44 ++++++++++-------- .../tests/unit/volume/v2/test_volume.py | 2 +- 8 files changed, 91 insertions(+), 84 deletions(-) diff --git a/openstackclient/tests/unit/volume/v1/fakes.py b/openstackclient/tests/unit/volume/v1/fakes.py index adb775edf2..438a60ade5 100644 --- a/openstackclient/tests/unit/volume/v1/fakes.py +++ b/openstackclient/tests/unit/volume/v1/fakes.py @@ -400,12 +400,12 @@ def setUp(self): ) -class FakeType(object): +class FakeVolumeType(object): """Fake one or more type.""" @staticmethod - def create_one_type(attrs=None, methods=None): - """Create a fake type. + def create_one_volume_type(attrs=None, methods=None): + """Create a fake volume type. :param Dictionary attrs: A dictionary with all attributes @@ -418,7 +418,7 @@ def create_one_type(attrs=None, methods=None): methods = methods or {} # Set default attributes. - type_info = { + volume_type_info = { "id": 'type-id-' + uuid.uuid4().hex, "name": 'type-name-' + uuid.uuid4().hex, "description": 'type-description-' + uuid.uuid4().hex, @@ -427,16 +427,16 @@ def create_one_type(attrs=None, methods=None): } # Overwrite default attributes. - type_info.update(attrs) + volume_type_info.update(attrs) volume_type = fakes.FakeResource( - info=copy.deepcopy(type_info), + info=copy.deepcopy(volume_type_info), methods=methods, loaded=True) return volume_type @staticmethod - def create_types(attrs=None, count=2): + def create_volume_types(attrs=None, count=2): """Create multiple fake types. :param Dictionary attrs: @@ -448,19 +448,19 @@ def create_types(attrs=None, count=2): """ volume_types = [] for i in range(0, count): - volume_type = FakeType.create_one_type(attrs) + volume_type = FakeVolumeType.create_one_volume_type(attrs) volume_types.append(volume_type) return volume_types @staticmethod - def get_types(types=None, count=2): + def get_volume_types(volume_types=None, count=2): """Get an iterable MagicMock object with a list of faked types. If types list is provided, then initialize the Mock object with the list. Otherwise create one. - :param List types: + :param List volume_types: A list of FakeResource objects faking types :param Integer count: The number of types to be faked @@ -468,14 +468,14 @@ def get_types(types=None, count=2): An iterable Mock object with side_effect set to a list of faked types """ - if types is None: - types = FakeType.create_types(count) + if volume_types is None: + volume_types = FakeVolumeType.create_volume_types(count) - return mock.Mock(side_effect=types) + return mock.Mock(side_effect=volume_types) @staticmethod - def create_one_encryption_type(attrs=None): - """Create a fake encryption type. + def create_one_encryption_volume_type(attrs=None): + """Create a fake encryption volume type. :param Dictionary attrs: A dictionary with all attributes diff --git a/openstackclient/tests/unit/volume/v1/test_qos_specs.py b/openstackclient/tests/unit/volume/v1/test_qos_specs.py index 5500438bd6..5b8e065686 100644 --- a/openstackclient/tests/unit/volume/v1/test_qos_specs.py +++ b/openstackclient/tests/unit/volume/v1/test_qos_specs.py @@ -39,7 +39,7 @@ def setUp(self): class TestQosAssociate(TestQos): - volume_type = volume_fakes.FakeType.create_one_type() + volume_type = volume_fakes.FakeVolumeType.create_one_volume_type() qos_spec = volume_fakes.FakeQos.create_one_qos() def setUp(self): @@ -263,7 +263,7 @@ def test_delete_multiple_qoses_with_exception(self): class TestQosDisassociate(TestQos): - volume_type = volume_fakes.FakeType.create_one_type() + volume_type = volume_fakes.FakeVolumeType.create_one_volume_type() qos_spec = volume_fakes.FakeQos.create_one_qos() def setUp(self): diff --git a/openstackclient/tests/unit/volume/v1/test_type.py b/openstackclient/tests/unit/volume/v1/test_type.py index f1d469140b..f3707849ea 100644 --- a/openstackclient/tests/unit/volume/v1/test_type.py +++ b/openstackclient/tests/unit/volume/v1/test_type.py @@ -49,9 +49,9 @@ class TestTypeCreate(TestType): def setUp(self): super(TestTypeCreate, self).setUp() - self.new_volume_type = volume_fakes.FakeType.create_one_type( - methods={'set_keys': {'myprop': 'myvalue'}} - ) + self.new_volume_type = \ + volume_fakes.FakeVolumeType.create_one_volume_type( + methods={'set_keys': {'myprop': 'myvalue'}}) self.data = ( self.new_volume_type.description, self.new_volume_type.id, @@ -87,11 +87,12 @@ def test_type_create_with_encryption(self): 'key_size': '128', 'control_location': 'front-end', } - encryption_type = volume_fakes.FakeType.create_one_encryption_type( - attrs=encryption_info - ) - self.new_volume_type = volume_fakes.FakeType.create_one_type( - attrs={'encryption': encryption_info}) + encryption_type = \ + volume_fakes.FakeVolumeType.create_one_encryption_volume_type( + attrs=encryption_info) + self.new_volume_type = \ + volume_fakes.FakeVolumeType.create_one_volume_type( + attrs={'encryption': encryption_info}) self.types_mock.create.return_value = self.new_volume_type self.encryption_types_mock.create.return_value = encryption_type encryption_columns = ( @@ -144,12 +145,12 @@ def test_type_create_with_encryption(self): class TestTypeDelete(TestType): - volume_types = volume_fakes.FakeType.create_types(count=2) + volume_types = volume_fakes.FakeVolumeType.create_volume_types(count=2) def setUp(self): super(TestTypeDelete, self).setUp() - self.types_mock.get = volume_fakes.FakeType.get_types( + self.types_mock.get = volume_fakes.FakeVolumeType.get_volume_types( self.volume_types) self.types_mock.delete.return_value = None @@ -220,7 +221,7 @@ def test_delete_multiple_types_with_exception(self): class TestTypeList(TestType): - volume_types = volume_fakes.FakeType.create_types() + volume_types = volume_fakes.FakeVolumeType.create_volume_types() columns = [ "ID", @@ -287,8 +288,9 @@ def test_type_list_with_options(self): self.assertItemsEqual(self.data_long, list(data)) def test_type_list_with_encryption(self): - encryption_type = volume_fakes.FakeType.create_one_encryption_type( - attrs={'volume_type_id': self.volume_types[0].id}) + encryption_type = \ + volume_fakes.FakeVolumeType.create_one_encryption_volume_type( + attrs={'volume_type_id': self.volume_types[0].id}) encryption_info = { 'provider': 'LuksEncryptor', 'cipher': None, @@ -333,7 +335,7 @@ def test_type_list_with_encryption(self): class TestTypeSet(TestType): - volume_type = volume_fakes.FakeType.create_one_type( + volume_type = volume_fakes.FakeVolumeType.create_one_volume_type( methods={'set_keys': None}) def setUp(self): @@ -441,7 +443,7 @@ class TestTypeShow(TestType): def setUp(self): super(TestTypeShow, self).setUp() - self.volume_type = volume_fakes.FakeType.create_one_type() + self.volume_type = volume_fakes.FakeVolumeType.create_one_volume_type() self.data = ( self.volume_type.description, self.volume_type.id, @@ -472,14 +474,15 @@ def test_type_show(self): self.assertItemsEqual(self.data, data) def test_type_show_with_encryption(self): - encryption_type = volume_fakes.FakeType.create_one_encryption_type() + encryption_type = \ + volume_fakes.FakeVolumeType.create_one_encryption_volume_type() encryption_info = { 'provider': 'LuksEncryptor', 'cipher': None, 'key_size': None, 'control_location': 'front-end', } - self.volume_type = volume_fakes.FakeType.create_one_type( + self.volume_type = volume_fakes.FakeVolumeType.create_one_volume_type( attrs={'encryption': encryption_info}) self.types_mock.get.return_value = self.volume_type self.encryption_types_mock.get.return_value = encryption_type @@ -518,7 +521,7 @@ def test_type_show_with_encryption(self): class TestTypeUnset(TestType): - volume_type = volume_fakes.FakeType.create_one_type( + volume_type = volume_fakes.FakeVolumeType.create_one_volume_type( methods={'unset_keys': None}) def setUp(self): @@ -596,7 +599,7 @@ def test_type_unset_encryption_type(self): class TestColumns(TestType): def test_encryption_info_column_with_info(self): - fake_volume_type = volume_fakes.FakeType.create_one_type() + fake_volume_type = volume_fakes.FakeVolumeType.create_one_volume_type() type_id = fake_volume_type.id encryption_info = { @@ -612,7 +615,7 @@ def test_encryption_info_column_with_info(self): self.assertEqual(encryption_info, col.machine_readable()) def test_encryption_info_column_without_info(self): - fake_volume_type = volume_fakes.FakeType.create_one_type() + fake_volume_type = volume_fakes.FakeVolumeType.create_one_volume_type() type_id = fake_volume_type.id col = volume_type.EncryptionInfoColumn(type_id, {}) diff --git a/openstackclient/tests/unit/volume/v2/fakes.py b/openstackclient/tests/unit/volume/v2/fakes.py index 5f18990e96..86778698da 100644 --- a/openstackclient/tests/unit/volume/v2/fakes.py +++ b/openstackclient/tests/unit/volume/v2/fakes.py @@ -983,12 +983,12 @@ def get_snapshots(snapshots=None, count=2): return mock.Mock(side_effect=snapshots) -class FakeType(object): - """Fake one or more type.""" +class FakeVolumeType(object): + """Fake one or more volume type.""" @staticmethod - def create_one_type(attrs=None, methods=None): - """Create a fake type. + def create_one_volume_type(attrs=None, methods=None): + """Create a fake volume type. :param Dictionary attrs: A dictionary with all attributes @@ -1001,7 +1001,7 @@ def create_one_type(attrs=None, methods=None): methods = methods or {} # Set default attributes. - type_info = { + volume_type_info = { "id": 'type-id-' + uuid.uuid4().hex, "name": 'type-name-' + uuid.uuid4().hex, "description": 'type-description-' + uuid.uuid4().hex, @@ -1010,17 +1010,17 @@ def create_one_type(attrs=None, methods=None): } # Overwrite default attributes. - type_info.update(attrs) + volume_type_info.update(attrs) volume_type = fakes.FakeResource( - info=copy.deepcopy(type_info), + info=copy.deepcopy(volume_type_info), methods=methods, loaded=True) return volume_type @staticmethod - def create_types(attrs=None, count=2): - """Create multiple fake types. + def create_volume_types(attrs=None, count=2): + """Create multiple fake volume_types. :param Dictionary attrs: A dictionary with all attributes @@ -1031,34 +1031,34 @@ def create_types(attrs=None, count=2): """ volume_types = [] for i in range(0, count): - volume_type = FakeType.create_one_type(attrs) + volume_type = FakeVolumeType.create_one_volume_type(attrs) volume_types.append(volume_type) return volume_types @staticmethod - def get_types(types=None, count=2): - """Get an iterable MagicMock object with a list of faked types. + def get_volume_types(volume_types=None, count=2): + """Get an iterable MagicMock object with a list of faked volume types. - If types list is provided, then initialize the Mock object with the - list. Otherwise create one. + If volume_types list is provided, then initialize the Mock object with + the list. Otherwise create one. - :param List types: - A list of FakeResource objects faking types + :param List volume_types: + A list of FakeResource objects faking volume types :param Integer count: - The number of types to be faked + The number of volume types to be faked :return An iterable Mock object with side_effect set to a list of faked - types + volume types """ - if types is None: - types = FakeType.create_types(count) + if volume_types is None: + volume_types = FakeVolumeType.create_volume_types(count) - return mock.Mock(side_effect=types) + return mock.Mock(side_effect=volume_types) @staticmethod - def create_one_encryption_type(attrs=None): - """Create a fake encryption type. + def create_one_encryption_volume_type(attrs=None): + """Create a fake encryption volume type. :param Dictionary attrs: A dictionary with all attributes diff --git a/openstackclient/tests/unit/volume/v2/test_consistency_group.py b/openstackclient/tests/unit/volume/v2/test_consistency_group.py index 6bb6c02978..dcdd9bc808 100644 --- a/openstackclient/tests/unit/volume/v2/test_consistency_group.py +++ b/openstackclient/tests/unit/volume/v2/test_consistency_group.py @@ -148,7 +148,7 @@ def test_add_multiple_volumes_to_consistency_group_with_exception( class TestConsistencyGroupCreate(TestConsistencyGroup): - volume_type = volume_fakes.FakeType.create_one_type() + volume_type = volume_fakes.FakeVolumeType.create_one_volume_type() new_consistency_group = ( volume_fakes.FakeConsistencyGroup.create_one_consistency_group()) consistency_group_snapshot = ( diff --git a/openstackclient/tests/unit/volume/v2/test_qos_specs.py b/openstackclient/tests/unit/volume/v2/test_qos_specs.py index bc4cee8b40..a29080d10b 100644 --- a/openstackclient/tests/unit/volume/v2/test_qos_specs.py +++ b/openstackclient/tests/unit/volume/v2/test_qos_specs.py @@ -39,7 +39,7 @@ def setUp(self): class TestQosAssociate(TestQos): - volume_type = volume_fakes.FakeType.create_one_type() + volume_type = volume_fakes.FakeVolumeType.create_one_volume_type() qos_spec = volume_fakes.FakeQos.create_one_qos() def setUp(self): @@ -255,7 +255,7 @@ def test_delete_multiple_qoses_with_exception(self): class TestQosDisassociate(TestQos): - volume_type = volume_fakes.FakeType.create_one_type() + volume_type = volume_fakes.FakeVolumeType.create_one_volume_type() qos_spec = volume_fakes.FakeQos.create_one_qos() def setUp(self): diff --git a/openstackclient/tests/unit/volume/v2/test_type.py b/openstackclient/tests/unit/volume/v2/test_type.py index 000464c5d8..d1cbda2f3d 100644 --- a/openstackclient/tests/unit/volume/v2/test_type.py +++ b/openstackclient/tests/unit/volume/v2/test_type.py @@ -58,7 +58,8 @@ class TestTypeCreate(TestType): def setUp(self): super(TestTypeCreate, self).setUp() - self.new_volume_type = volume_fakes.FakeType.create_one_type() + self.new_volume_type = \ + volume_fakes.FakeVolumeType.create_one_volume_type() self.data = ( self.new_volume_type.description, self.new_volume_type.id, @@ -143,11 +144,12 @@ def test_type_create_with_encryption(self): 'key_size': '128', 'control_location': 'front-end', } - encryption_type = volume_fakes.FakeType.create_one_encryption_type( - attrs=encryption_info - ) - self.new_volume_type = volume_fakes.FakeType.create_one_type( - attrs={'encryption': encryption_info}) + encryption_type = \ + volume_fakes.FakeVolumeType.create_one_encryption_volume_type( + attrs=encryption_info) + self.new_volume_type = \ + volume_fakes.FakeVolumeType.create_one_volume_type( + attrs={'encryption': encryption_info}) self.types_mock.create.return_value = self.new_volume_type self.encryption_types_mock.create.return_value = encryption_type encryption_columns = ( @@ -201,12 +203,12 @@ def test_type_create_with_encryption(self): class TestTypeDelete(TestType): - volume_types = volume_fakes.FakeType.create_types(count=2) + volume_types = volume_fakes.FakeVolumeType.create_volume_types(count=2) def setUp(self): super(TestTypeDelete, self).setUp() - self.types_mock.get = volume_fakes.FakeType.get_types( + self.types_mock.get = volume_fakes.FakeVolumeType.get_volume_types( self.volume_types) self.types_mock.delete.return_value = None @@ -276,7 +278,7 @@ def test_delete_multiple_types_with_exception(self): class TestTypeList(TestType): - volume_types = volume_fakes.FakeType.create_types() + volume_types = volume_fakes.FakeVolumeType.create_volume_types() columns = [ "ID", @@ -386,8 +388,9 @@ def test_type_list_with_default_option(self): self.assertItemsEqual(self.data_with_default_type, list(data)) def test_type_list_with_encryption(self): - encryption_type = volume_fakes.FakeType.create_one_encryption_type( - attrs={'volume_type_id': self.volume_types[0].id}) + encryption_type = \ + volume_fakes.FakeVolumeType.create_one_encryption_volume_type( + attrs={'volume_type_id': self.volume_types[0].id}) encryption_info = { 'provider': 'LuksEncryptor', 'cipher': None, @@ -433,7 +436,7 @@ def test_type_list_with_encryption(self): class TestTypeSet(TestType): project = identity_fakes.FakeProject.create_one_project() - volume_type = volume_fakes.FakeType.create_one_type( + volume_type = volume_fakes.FakeVolumeType.create_one_volume_type( methods={'set_keys': None}) def setUp(self): @@ -684,7 +687,7 @@ class TestTypeShow(TestType): def setUp(self): super(TestTypeShow, self).setUp() - self.volume_type = volume_fakes.FakeType.create_one_type() + self.volume_type = volume_fakes.FakeVolumeType.create_one_volume_type() self.data = ( None, self.volume_type.description, @@ -724,7 +727,7 @@ def test_type_show_with_access(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - private_type = volume_fakes.FakeType.create_one_type( + private_type = volume_fakes.FakeVolumeType.create_one_volume_type( attrs={'is_public': False}) type_access_list = volume_fakes.FakeTypeAccess.create_one_type_access() with mock.patch.object(self.types_mock, 'get', @@ -757,7 +760,7 @@ def test_type_show_with_list_access_exec(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - private_type = volume_fakes.FakeType.create_one_type( + private_type = volume_fakes.FakeVolumeType.create_one_volume_type( attrs={'is_public': False}) with mock.patch.object(self.types_mock, 'get', return_value=private_type): @@ -781,14 +784,15 @@ def test_type_show_with_list_access_exec(self): self.assertItemsEqual(private_type_data, data) def test_type_show_with_encryption(self): - encryption_type = volume_fakes.FakeType.create_one_encryption_type() + encryption_type = \ + volume_fakes.FakeVolumeType.create_one_encryption_volume_type() encryption_info = { 'provider': 'LuksEncryptor', 'cipher': None, 'key_size': None, 'control_location': 'front-end', } - self.volume_type = volume_fakes.FakeType.create_one_type( + self.volume_type = volume_fakes.FakeVolumeType.create_one_volume_type( attrs={'encryption': encryption_info}) self.types_mock.get.return_value = self.volume_type self.encryption_types_mock.get.return_value = encryption_type @@ -830,7 +834,7 @@ def test_type_show_with_encryption(self): class TestTypeUnset(TestType): project = identity_fakes.FakeProject.create_one_project() - volume_type = volume_fakes.FakeType.create_one_type( + volume_type = volume_fakes.FakeVolumeType.create_one_volume_type( methods={'unset_keys': None}) def setUp(self): @@ -932,7 +936,7 @@ def test_type_unset_encryption_type(self): class TestColumns(TestType): def test_encryption_info_column_with_info(self): - fake_volume_type = volume_fakes.FakeType.create_one_type() + fake_volume_type = volume_fakes.FakeVolumeType.create_one_volume_type() type_id = fake_volume_type.id encryption_info = { @@ -948,7 +952,7 @@ def test_encryption_info_column_with_info(self): self.assertEqual(encryption_info, col.machine_readable()) def test_encryption_info_column_without_info(self): - fake_volume_type = volume_fakes.FakeType.create_one_type() + fake_volume_type = volume_fakes.FakeVolumeType.create_one_volume_type() type_id = fake_volume_type.id col = volume_type.EncryptionInfoColumn(type_id, {}) diff --git a/openstackclient/tests/unit/volume/v2/test_volume.py b/openstackclient/tests/unit/volume/v2/test_volume.py index b9fe4e834c..377f7ec49f 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume.py +++ b/openstackclient/tests/unit/volume/v2/test_volume.py @@ -1173,7 +1173,7 @@ def test_volume_migrate_without_host(self): class TestVolumeSet(TestVolume): - volume_type = volume_fakes.FakeType.create_one_type() + volume_type = volume_fakes.FakeVolumeType.create_one_volume_type() def setUp(self): super(TestVolumeSet, self).setUp() From 4c2e8523a98a0dd33e0203c47e94384420c14f9c Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 3 Jun 2021 10:21:20 +0100 Subject: [PATCH 2389/3095] volume: Add 'volume group *' commands These mirror the 'cinder group-*' commands, with arguments copied across essentially verbatim. The only significant departures are the replacement of "tenant" terminology with "project" and the merging of the various volume group replication action commands into the parent volume group (e.g. 'openstack volume group set --enable-replication' instead of 'cinder group enable-replication') volume group create volume group delete volume group list volume group show volume group set volume group failover Change-Id: I3b2c0cb92b8a53cc1c0cefa3313b80f59c9e5835 Signed-off-by: Stephen Finucane --- .../cli/command-objects/volume-group.rst | 23 + doc/source/cli/commands.rst | 1 + doc/source/cli/data/cinder.csv | 18 +- openstackclient/tests/unit/volume/v3/fakes.py | 111 ++++ .../tests/unit/volume/v3/test_volume_group.py | 497 +++++++++++++++++ openstackclient/volume/v3/volume_group.py | 506 ++++++++++++++++++ ...olume-group-commands-b121d6ec7da9779a.yaml | 8 + setup.cfg | 7 + 8 files changed, 1162 insertions(+), 9 deletions(-) create mode 100644 doc/source/cli/command-objects/volume-group.rst create mode 100644 openstackclient/tests/unit/volume/v3/test_volume_group.py create mode 100644 openstackclient/volume/v3/volume_group.py create mode 100644 releasenotes/notes/add-volume-group-commands-b121d6ec7da9779a.yaml diff --git a/doc/source/cli/command-objects/volume-group.rst b/doc/source/cli/command-objects/volume-group.rst new file mode 100644 index 0000000000..50bc830f90 --- /dev/null +++ b/doc/source/cli/command-objects/volume-group.rst @@ -0,0 +1,23 @@ +============ +volume group +============ + +Block Storage v3 + +.. autoprogram-cliff:: openstack.volume.v3 + :command: volume group create + +.. autoprogram-cliff:: openstack.volume.v3 + :command: volume group delete + +.. autoprogram-cliff:: openstack.volume.v3 + :command: volume group list + +.. autoprogram-cliff:: openstack.volume.v3 + :command: volume group failover + +.. autoprogram-cliff:: openstack.volume.v3 + :command: volume group set + +.. autoprogram-cliff:: openstack.volume.v3 + :command: volume group show diff --git a/doc/source/cli/commands.rst b/doc/source/cli/commands.rst index cc95b3d121..b91a896f41 100644 --- a/doc/source/cli/commands.rst +++ b/doc/source/cli/commands.rst @@ -159,6 +159,7 @@ referring to both Compute and Volume quotas. * ``volume backend pool``: (**Volume**) volume backend storage pools * ``volume backup record``: (**Volume**) volume record that can be imported or exported * ``volume backend``: (**Volume**) volume backend storage +* ``volume group``: (**Volume**) group of volumes * ``volume host``: (**Volume**) the physical computer for volumes * ``volume message``: (**Volume**) volume API internal messages detailing volume failure messages * ``volume qos``: (**Volume**) quality-of-service (QoS) specification for volumes diff --git a/doc/source/cli/data/cinder.csv b/doc/source/cli/data/cinder.csv index 649cd8f59a..031aa43900 100644 --- a/doc/source/cli/data/cinder.csv +++ b/doc/source/cli/data/cinder.csv @@ -44,15 +44,15 @@ force-delete,volume delete --force,"Attempts force-delete of volume regardless o freeze-host,volume host set --disable,Freeze and disable the specified cinder-volume host. get-capabilities,volume backend capability show,Show capabilities of a volume backend. Admin only. get-pools,volume backend pool list,Show pool information for backends. Admin only. -group-create,,Creates a group. (Supported by API versions 3.13 - 3.latest) +group-create,volume group create,Creates a group. (Supported by API versions 3.13 - 3.latest) group-create-from-src,,Creates a group from a group snapshot or a source group. (Supported by API versions 3.14 - 3.latest) -group-delete,,Removes one or more groups. (Supported by API versions 3.13 - 3.latest) -group-disable-replication,,Disables replication for group. (Supported by API versions 3.38 - 3.latest) -group-enable-replication,,Enables replication for group. (Supported by API versions 3.38 - 3.latest) -group-failover-replication,,Fails over replication for group. (Supported by API versions 3.38 - 3.latest) -group-list,,Lists all groups. (Supported by API versions 3.13 - 3.latest) -group-list-replication-targets,,Lists replication targets for group. (Supported by API versions 3.38 - 3.latest) -group-show,,Shows details of a group. (Supported by API versions 3.13 - 3.latest) +group-delete,volume group delete,Removes one or more groups. (Supported by API versions 3.13 - 3.latest) +group-disable-replication,volume group set --disable-replication,Disables replication for group. (Supported by API versions 3.38 - 3.latest) +group-enable-replication,volume group set --enable-replication,Enables replication for group. (Supported by API versions 3.38 - 3.latest) +group-failover-replication,volume group failover,Fails over replication for group. (Supported by API versions 3.38 - 3.latest) +group-list,volume group list,Lists all groups. (Supported by API versions 3.13 - 3.latest) +group-list-replication-targets,volume group list --replication-targets,Lists replication targets for group. (Supported by API versions 3.38 - 3.latest) +group-show,volume group show,Shows details of a group. (Supported by API versions 3.13 - 3.latest) group-snapshot-create,,Creates a group snapshot. (Supported by API versions 3.14 - 3.latest) group-snapshot-delete,,Removes one or more group snapshots. (Supported by API versions 3.14 - 3.latest) group-snapshot-list,,Lists all group snapshots. (Supported by API versions 3.14 - 3.latest) @@ -65,7 +65,7 @@ group-type-key,,Sets or unsets group_spec for a group type. (Supported by API ve group-type-list,,Lists available 'group types'. (Admin only will see private types) (Supported by API versions 3.11 - 3.latest) group-type-show,,Show group type details. (Supported by API versions 3.11 - 3.latest) group-type-update,,Updates group type name description and/or is_public. (Supported by API versions 3.11 - 3.latest) -group-update,,Updates a group. (Supported by API versions 3.13 - 3.latest) +group-update,volume group set,Updates a group. (Supported by API versions 3.13 - 3.latest) image-metadata,volume set --image-property,Sets or deletes volume image metadata. image-metadata-show,volume show,Shows volume image metadata. list,volume list,Lists all volumes. diff --git a/openstackclient/tests/unit/volume/v3/fakes.py b/openstackclient/tests/unit/volume/v3/fakes.py index 45cad8c14c..b0c96290f8 100644 --- a/openstackclient/tests/unit/volume/v3/fakes.py +++ b/openstackclient/tests/unit/volume/v3/fakes.py @@ -32,10 +32,16 @@ def __init__(self, **kwargs): self.attachments = mock.Mock() self.attachments.resource_class = fakes.FakeResource(None, {}) + self.groups = mock.Mock() + self.groups.resource_class = fakes.FakeResource(None, {}) + self.group_types = mock.Mock() + self.group_types.resource_class = fakes.FakeResource(None, {}) self.messages = mock.Mock() self.messages.resource_class = fakes.FakeResource(None, {}) self.volumes = mock.Mock() self.volumes.resource_class = fakes.FakeResource(None, {}) + self.volume_types = mock.Mock() + self.volume_types.resource_class = fakes.FakeResource(None, {}) class TestVolume(utils.TestCommand): @@ -59,6 +65,111 @@ def setUp(self): # TODO(stephenfin): Check if the responses are actually the same FakeVolume = volume_v2_fakes.FakeVolume +FakeVolumeType = volume_v2_fakes.FakeVolumeType + + +class FakeVolumeGroup: + """Fake one or more volume groups.""" + + @staticmethod + def create_one_volume_group(attrs=None): + """Create a fake group. + + :param attrs: A dictionary with all attributes of group + :return: A FakeResource object with id, name, status, etc. + """ + attrs = attrs or {} + + group_type = attrs.pop('group_type', None) or uuid.uuid4().hex + volume_types = attrs.pop('volume_types', None) or [uuid.uuid4().hex] + + # Set default attribute + group_info = { + 'id': uuid.uuid4().hex, + 'status': random.choice([ + 'available', + ]), + 'availability_zone': f'az-{uuid.uuid4().hex}', + 'created_at': '2015-09-16T09:28:52.000000', + 'name': 'first_group', + 'description': f'description-{uuid.uuid4().hex}', + 'group_type': group_type, + 'volume_types': volume_types, + 'volumes': [f'volume-{uuid.uuid4().hex}'], + 'group_snapshot_id': None, + 'source_group_id': None, + 'project_id': f'project-{uuid.uuid4().hex}', + } + + # Overwrite default attributes if there are some attributes set + group_info.update(attrs) + + group = fakes.FakeResource( + None, + group_info, + loaded=True) + return group + + @staticmethod + def create_volume_groups(attrs=None, count=2): + """Create multiple fake groups. + + :param attrs: A dictionary with all attributes of group + :param count: The number of groups to be faked + :return: A list of FakeResource objects + """ + groups = [] + for n in range(0, count): + groups.append(FakeVolumeGroup.create_one_volume_group(attrs)) + + return groups + + +class FakeVolumeGroupType: + """Fake one or more volume group types.""" + + @staticmethod + def create_one_volume_group_type(attrs=None): + """Create a fake group type. + + :param attrs: A dictionary with all attributes of group type + :return: A FakeResource object with id, name, description, etc. + """ + attrs = attrs or {} + + # Set default attribute + group_type_info = { + 'id': uuid.uuid4().hex, + 'name': f'group-type-{uuid.uuid4().hex}', + 'description': f'description-{uuid.uuid4().hex}', + 'is_public': random.choice([True, False]), + 'group_specs': {}, + } + + # Overwrite default attributes if there are some attributes set + group_type_info.update(attrs) + + group_type = fakes.FakeResource( + None, + group_type_info, + loaded=True) + return group_type + + @staticmethod + def create_volume_group_types(attrs=None, count=2): + """Create multiple fake group types. + + :param attrs: A dictionary with all attributes of group type + :param count: The number of group types to be faked + :return: A list of FakeResource objects + """ + group_types = [] + for n in range(0, count): + group_types.append( + FakeVolumeGroupType.create_one_volume_group_type(attrs) + ) + + return group_types class FakeVolumeMessage: diff --git a/openstackclient/tests/unit/volume/v3/test_volume_group.py b/openstackclient/tests/unit/volume/v3/test_volume_group.py new file mode 100644 index 0000000000..13ef38d208 --- /dev/null +++ b/openstackclient/tests/unit/volume/v3/test_volume_group.py @@ -0,0 +1,497 @@ +# 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 cinderclient import api_versions +from osc_lib import exceptions + +from openstackclient.tests.unit.volume.v3 import fakes as volume_fakes +from openstackclient.volume.v3 import volume_group + + +class TestVolumeGroup(volume_fakes.TestVolume): + + def setUp(self): + super().setUp() + + self.volume_groups_mock = self.app.client_manager.volume.groups + self.volume_groups_mock.reset_mock() + + self.volume_group_types_mock = \ + self.app.client_manager.volume.group_types + self.volume_group_types_mock.reset_mock() + + self.volume_types_mock = self.app.client_manager.volume.volume_types + self.volume_types_mock.reset_mock() + + +class TestVolumeGroupCreate(TestVolumeGroup): + + fake_volume_type = volume_fakes.FakeVolumeType.create_one_volume_type() + fake_volume_group_type = \ + volume_fakes.FakeVolumeGroupType.create_one_volume_group_type() + fake_volume_group = volume_fakes.FakeVolumeGroup.create_one_volume_group( + attrs={ + 'group_type': fake_volume_group_type.id, + 'volume_types': [fake_volume_type.id], + }, + ) + + columns = ( + 'ID', + 'Status', + 'Name', + 'Description', + 'Group Type', + 'Volume Types', + 'Availability Zone', + 'Created At', + 'Volumes', + 'Group Snapshot ID', + 'Source Group ID', + ) + data = ( + fake_volume_group.id, + fake_volume_group.status, + fake_volume_group.name, + fake_volume_group.description, + fake_volume_group.group_type, + fake_volume_group.volume_types, + fake_volume_group.availability_zone, + fake_volume_group.created_at, + fake_volume_group.volumes, + fake_volume_group.group_snapshot_id, + fake_volume_group.source_group_id, + ) + + def setUp(self): + super().setUp() + + self.volume_types_mock.get.return_value = self.fake_volume_type + self.volume_group_types_mock.get.return_value = \ + self.fake_volume_group_type + self.volume_groups_mock.create.return_value = self.fake_volume_group + self.volume_groups_mock.get.return_value = self.fake_volume_group + + self.cmd = volume_group.CreateVolumeGroup(self.app, None) + + def test_volume_group_create(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.13') + + arglist = [ + self.fake_volume_group_type.id, + self.fake_volume_type.id, + ] + verifylist = [ + ('volume_group_type', self.fake_volume_group_type.id), + ('volume_types', [self.fake_volume_type.id]), + ('name', None), + ('description', None), + ('availability_zone', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volume_group_types_mock.get.assert_called_once_with( + self.fake_volume_group_type.id) + self.volume_types_mock.get.assert_called_once_with( + self.fake_volume_type.id) + self.volume_groups_mock.create.assert_called_once_with( + self.fake_volume_group_type.id, + self.fake_volume_type.id, + None, + None, + availability_zone=None, + ) + self.assertEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + + def test_volume_group_create_with_options(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.13') + + arglist = [ + self.fake_volume_group_type.id, + self.fake_volume_type.id, + '--name', 'foo', + '--description', 'hello, world', + '--availability-zone', 'bar', + ] + verifylist = [ + ('volume_group_type', self.fake_volume_group_type.id), + ('volume_types', [self.fake_volume_type.id]), + ('name', 'foo'), + ('description', 'hello, world'), + ('availability_zone', 'bar'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volume_group_types_mock.get.assert_called_once_with( + self.fake_volume_group_type.id) + self.volume_types_mock.get.assert_called_once_with( + self.fake_volume_type.id) + self.volume_groups_mock.create.assert_called_once_with( + self.fake_volume_group_type.id, + self.fake_volume_type.id, + 'foo', + 'hello, world', + availability_zone='bar', + ) + self.assertEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + + def test_volume_group_create_pre_v313(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.12') + + arglist = [ + self.fake_volume_group_type.id, + self.fake_volume_type.id, + ] + verifylist = [ + ('volume_group_type', self.fake_volume_group_type.id), + ('volume_types', [self.fake_volume_type.id]), + ('name', None), + ('description', None), + ('availability_zone', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.13 or greater is required', + str(exc)) + + +class TestVolumeGroupDelete(TestVolumeGroup): + + fake_volume_group = \ + volume_fakes.FakeVolumeGroup.create_one_volume_group() + + def setUp(self): + super().setUp() + + self.volume_groups_mock.get.return_value = self.fake_volume_group + self.volume_groups_mock.delete.return_value = None + + self.cmd = volume_group.DeleteVolumeGroup(self.app, None) + + def test_volume_group_delete(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.13') + + arglist = [ + self.fake_volume_group.id, + '--force', + ] + verifylist = [ + ('group', self.fake_volume_group.id), + ('force', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.volume_groups_mock.delete.assert_called_once_with( + self.fake_volume_group.id, delete_volumes=True, + ) + self.assertIsNone(result) + + def test_volume_group_delete_pre_v313(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.12') + + arglist = [ + self.fake_volume_group.id, + ] + verifylist = [ + ('group', self.fake_volume_group.id), + ('force', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.13 or greater is required', + str(exc)) + + +class TestVolumeGroupSet(TestVolumeGroup): + + fake_volume_group = \ + volume_fakes.FakeVolumeGroup.create_one_volume_group() + + columns = ( + 'ID', + 'Status', + 'Name', + 'Description', + 'Group Type', + 'Volume Types', + 'Availability Zone', + 'Created At', + 'Volumes', + 'Group Snapshot ID', + 'Source Group ID', + ) + data = ( + fake_volume_group.id, + fake_volume_group.status, + fake_volume_group.name, + fake_volume_group.description, + fake_volume_group.group_type, + fake_volume_group.volume_types, + fake_volume_group.availability_zone, + fake_volume_group.created_at, + fake_volume_group.volumes, + fake_volume_group.group_snapshot_id, + fake_volume_group.source_group_id, + ) + + def setUp(self): + super().setUp() + + self.volume_groups_mock.get.return_value = self.fake_volume_group + self.volume_groups_mock.update.return_value = self.fake_volume_group + + self.cmd = volume_group.SetVolumeGroup(self.app, None) + + def test_volume_group_set(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.13') + + arglist = [ + self.fake_volume_group.id, + '--name', 'foo', + '--description', 'hello, world', + ] + verifylist = [ + ('group', self.fake_volume_group.id), + ('name', 'foo'), + ('description', 'hello, world'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volume_groups_mock.update.assert_called_once_with( + self.fake_volume_group.id, name='foo', description='hello, world', + ) + self.assertEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + + def test_volume_group_with_enable_replication_option(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.38') + + arglist = [ + self.fake_volume_group.id, + '--enable-replication', + ] + verifylist = [ + ('group', self.fake_volume_group.id), + ('enable_replication', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volume_groups_mock.enable_replication.assert_called_once_with( + self.fake_volume_group.id) + self.assertEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + + def test_volume_group_set_pre_v313(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.12') + + arglist = [ + self.fake_volume_group.id, + '--name', 'foo', + '--description', 'hello, world', + ] + verifylist = [ + ('group', self.fake_volume_group.id), + ('name', 'foo'), + ('description', 'hello, world'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.13 or greater is required', + str(exc)) + + def test_volume_group_with_enable_replication_option_pre_v338(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.37') + + arglist = [ + self.fake_volume_group.id, + '--enable-replication', + ] + verifylist = [ + ('group', self.fake_volume_group.id), + ('enable_replication', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.38 or greater is required', + str(exc)) + + +class TestVolumeGroupList(TestVolumeGroup): + + fake_volume_groups = \ + volume_fakes.FakeVolumeGroup.create_volume_groups() + + columns = ( + 'ID', + 'Status', + 'Name', + ) + data = [ + ( + fake_volume_group.id, + fake_volume_group.status, + fake_volume_group.name, + ) for fake_volume_group in fake_volume_groups + ] + + def setUp(self): + super().setUp() + + self.volume_groups_mock.list.return_value = self.fake_volume_groups + + self.cmd = volume_group.ListVolumeGroup(self.app, None) + + def test_volume_group_list(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.13') + + arglist = [ + '--all-projects', + ] + verifylist = [ + ('all_projects', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volume_groups_mock.list.assert_called_once_with( + search_opts={ + 'all_tenants': True, + }, + ) + self.assertEqual(self.columns, columns) + self.assertCountEqual(tuple(self.data), data) + + def test_volume_group_list_pre_v313(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.12') + + arglist = [ + '--all-projects', + ] + verifylist = [ + ('all_projects', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.13 or greater is required', + str(exc)) + + +class TestVolumeGroupFailover(TestVolumeGroup): + + fake_volume_group = \ + volume_fakes.FakeVolumeGroup.create_one_volume_group() + + def setUp(self): + super().setUp() + + self.volume_groups_mock.get.return_value = self.fake_volume_group + self.volume_groups_mock.failover_replication.return_value = None + + self.cmd = volume_group.FailoverVolumeGroup(self.app, None) + + def test_volume_group_failover(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.38') + + arglist = [ + self.fake_volume_group.id, + '--allow-attached-volume', + '--secondary-backend-id', 'foo', + ] + verifylist = [ + ('group', self.fake_volume_group.id), + ('allow_attached_volume', True), + ('secondary_backend_id', 'foo'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.volume_groups_mock.failover_replication.assert_called_once_with( + self.fake_volume_group.id, + allow_attached_volume=True, + secondary_backend_id='foo', + ) + self.assertIsNone(result) + + def test_volume_group_failover_pre_v338(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.37') + + arglist = [ + self.fake_volume_group.id, + '--allow-attached-volume', + '--secondary-backend-id', 'foo', + ] + verifylist = [ + ('group', self.fake_volume_group.id), + ('allow_attached_volume', True), + ('secondary_backend_id', 'foo'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.38 or greater is required', + str(exc)) diff --git a/openstackclient/volume/v3/volume_group.py b/openstackclient/volume/v3/volume_group.py new file mode 100644 index 0000000000..db4e9a94fa --- /dev/null +++ b/openstackclient/volume/v3/volume_group.py @@ -0,0 +1,506 @@ +# 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 logging + +from cinderclient import api_versions +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils + +from openstackclient.i18n import _ + +LOG = logging.getLogger(__name__) + + +def _format_group(group): + columns = ( + 'id', + 'status', + 'name', + 'description', + 'group_type', + 'volume_types', + 'availability_zone', + 'created_at', + 'volumes', + 'group_snapshot_id', + 'source_group_id', + ) + column_headers = ( + 'ID', + 'Status', + 'Name', + 'Description', + 'Group Type', + 'Volume Types', + 'Availability Zone', + 'Created At', + 'Volumes', + 'Group Snapshot ID', + 'Source Group ID', + ) + + # TODO(stephenfin): Consider using a formatter for volume_types since it's + # a list + return ( + column_headers, + utils.get_item_properties( + group, + columns, + ), + ) + + +class CreateVolumeGroup(command.ShowOne): + """Create a volume group. + + Generic volume groups enable you to create a group of volumes and manage + them together. + + Generic volume groups are more flexible than consistency groups. Currently + volume consistency groups only support consistent group snapshot. It + cannot be extended easily to serve other purposes. A project may want to + put volumes used in the same application together in a group so that it is + easier to manage them together, and this group of volumes may or may not + support consistent group snapshot. Generic volume group solve this problem. + By decoupling the tight relationship between the group construct and the + consistency concept, generic volume groups can be extended to support other + features in the future. + + This command requires ``--os-volume-api-version`` 3.13 or greater. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'volume_group_type', + metavar='', + help=_('Name or ID of volume group type to use.'), + ) + parser.add_argument( + 'volume_types', + metavar='', + nargs='+', + default=[], + help=_('Name or ID of volume type(s) to use.'), + ) + parser.add_argument( + '--name', + metavar='', + help=_('Name of the volume group.'), + ) + parser.add_argument( + '--description', + metavar='', + help=_('Description of a volume group.') + ) + parser.add_argument( + '--availability-zone', + metavar='', + help=_('Availability zone for volume group.'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.13'): + msg = _( + "--os-volume-api-version 3.13 or greater is required to " + "support the 'volume group create' command" + ) + raise exceptions.CommandError(msg) + + volume_group_type = utils.find_resource( + volume_client.group_types, + parsed_args.volume_group_type, + ) + + volume_types = [] + for volume_type in parsed_args.volume_types: + volume_types.append( + utils.find_resource( + volume_client.volume_types, + volume_type, + ) + ) + + group = volume_client.groups.create( + volume_group_type.id, + ','.join(x.id for x in volume_types), + parsed_args.name, + parsed_args.description, + availability_zone=parsed_args.availability_zone) + + group = volume_client.groups.get(group.id) + + return _format_group(group) + + +class DeleteVolumeGroup(command.Command): + """Delete a volume group. + + This command requires ``--os-volume-api-version`` 3.13 or greater. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'group', + metavar='', + help=_('Name or ID of volume group to delete'), + ) + parser.add_argument( + '--force', + action='store_true', + default=False, + help=_( + 'Delete the volume group even if it contains volumes. ' + 'This will delete any remaining volumes in the group.', + ) + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.13'): + msg = _( + "--os-volume-api-version 3.13 or greater is required to " + "support the 'volume group delete' command" + ) + raise exceptions.CommandError(msg) + + group = utils.find_resource( + volume_client.groups, + parsed_args.group, + ) + + volume_client.groups.delete( + group.id, delete_volumes=parsed_args.force) + + +class SetVolumeGroup(command.ShowOne): + """Update a volume group. + + This command requires ``--os-volume-api-version`` 3.13 or greater. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'group', + metavar='', + help=_('Name or ID of volume group.'), + ) + parser.add_argument( + '--name', + metavar='', + help=_('New name for group.'), + ) + parser.add_argument( + '--description', + metavar='', + help=_('New description for group.'), + ) + parser.add_argument( + '--enable-replication', + action='store_true', + dest='enable_replication', + default=None, + help=_( + 'Enable replication for group. ' + '(supported by --os-volume-api-version 3.38 or above)' + ), + ) + parser.add_argument( + '--disable-replication', + action='store_false', + dest='enable_replication', + help=_( + 'Disable replication for group. ' + '(supported by --os-volume-api-version 3.38 or above)' + ), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.13'): + msg = _( + "--os-volume-api-version 3.13 or greater is required to " + "support the 'volume group set' command" + ) + raise exceptions.CommandError(msg) + + group = utils.find_resource( + volume_client.groups, + parsed_args.group, + ) + + if parsed_args.enable_replication is not None: + if volume_client.api_version < api_versions.APIVersion('3.38'): + msg = _( + "--os-volume-api-version 3.38 or greater is required to " + "support the '--enable-replication' or " + "'--disable-replication' options" + ) + raise exceptions.CommandError(msg) + + if parsed_args.enable_replication: + volume_client.groups.enable_replication(group.id) + else: + volume_client.groups.disable_replication(group.id) + + kwargs = {} + + if parsed_args.name is not None: + kwargs['name'] = parsed_args.name + + if parsed_args.description is not None: + kwargs['description'] = parsed_args.description + + if kwargs: + group = volume_client.groups.update(group.id, **kwargs) + + return _format_group(group) + + +class ListVolumeGroup(command.Lister): + """Lists all volume groups. + + This command requires ``--os-volume-api-version`` 3.13 or greater. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + '--all-projects', + dest='all_projects', + action='store_true', + default=utils.env('ALL_PROJECTS', default=False), + help=_('Shows details for all projects (admin only).'), + ) + # TODO(stephenfin): Add once we have an equivalent command for + # 'cinder list-filters' + # parser.add_argument( + # '--filter', + # metavar='', + # action=parseractions.KeyValueAction, + # dest='filters', + # help=_( + # "Filter key and value pairs. Use 'foo' to " + # "check enabled filters from server. Use 'key~=value' for " + # "inexact filtering if the key supports " + # "(supported by --os-volume-api-version 3.33 or above)" + # ), + # ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.13'): + msg = _( + "--os-volume-api-version 3.13 or greater is required to " + "support the 'volume group list' command" + ) + raise exceptions.CommandError(msg) + + search_opts = { + 'all_tenants': parsed_args.all_projects, + } + + groups = volume_client.groups.list( + search_opts=search_opts) + + column_headers = ( + 'ID', + 'Status', + 'Name', + ) + columns = ( + 'id', + 'status', + 'name', + ) + + return ( + column_headers, + ( + utils.get_item_properties(a, columns) + for a in groups + ), + ) + + +class ShowVolumeGroup(command.ShowOne): + """Show detailed information for a volume group. + + This command requires ``--os-volume-api-version`` 3.13 or greater. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'group', + metavar='', + help=_('Name or ID of volume group.'), + ) + parser.add_argument( + '--volumes', + action='store_true', + dest='show_volumes', + default=None, + help=_( + 'Show volumes included in the group. ' + '(supported by --os-volume-api-version 3.25 or above)' + ), + ) + parser.add_argument( + '--no-volumes', + action='store_false', + dest='show_volumes', + help=_( + 'Do not show volumes included in the group. ' + '(supported by --os-volume-api-version 3.25 or above)' + ), + ) + parser.add_argument( + '--replication-targets', + action='store_true', + dest='show_replication_targets', + default=None, + help=_( + 'Show replication targets for the group. ' + '(supported by --os-volume-api-version 3.38 or above)' + ), + ) + parser.add_argument( + '--no-replication-targets', + action='store_false', + dest='show_replication_targets', + help=_( + 'Do not show replication targets for the group. ' + '(supported by --os-volume-api-version 3.38 or above)' + ), + ) + + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.13'): + msg = _( + "--os-volume-api-version 3.13 or greater is required to " + "support the 'volume group show' command" + ) + raise exceptions.CommandError(msg) + + kwargs = {} + + if parsed_args.show_volumes is not None: + if volume_client.api_version < api_versions.APIVersion('3.25'): + msg = _( + "--os-volume-api-version 3.25 or greater is required to " + "support the '--(no-)volumes' option" + ) + raise exceptions.CommandError(msg) + + kwargs['list_volume'] = parsed_args.show_volumes + + if parsed_args.show_replication_targets is not None: + if volume_client.api_version < api_versions.APIVersion('3.38'): + msg = _( + "--os-volume-api-version 3.38 or greater is required to " + "support the '--(no-)replication-targets' option" + ) + raise exceptions.CommandError(msg) + + group = utils.find_resource( + volume_client.groups, + parsed_args.group, + ) + + group = volume_client.groups.show(group.id, **kwargs) + + if parsed_args.show_replication_targets: + replication_targets = \ + volume_client.groups.list_replication_targets(group.id) + + group.replication_targets = replication_targets + + # TODO(stephenfin): Show replication targets + return _format_group(group) + + +class FailoverVolumeGroup(command.Command): + """Failover replication for a volume group. + + This command requires ``--os-volume-api-version`` 3.38 or greater. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'group', + metavar='', + help=_('Name or ID of volume group to failover replication for.'), + ) + parser.add_argument( + '--allow-attached-volume', + action='store_true', + dest='allow_attached_volume', + default=False, + help=_( + 'Allow group with attached volumes to be failed over.', + ) + ) + parser.add_argument( + '--disallow-attached-volume', + action='store_false', + dest='allow_attached_volume', + default=False, + help=_( + 'Disallow group with attached volumes to be failed over.', + ) + ) + parser.add_argument( + '--secondary-backend-id', + metavar='', + help=_('Secondary backend ID.'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.38'): + msg = _( + "--os-volume-api-version 3.38 or greater is required to " + "support the 'volume group failover' command" + ) + raise exceptions.CommandError(msg) + + group = utils.find_resource( + volume_client.groups, + parsed_args.group, + ) + + volume_client.groups.failover_replication( + group.id, + allow_attached_volume=parsed_args.allow_attached_volume, + secondary_backend_id=parsed_args.secondary_backend_id, + ) diff --git a/releasenotes/notes/add-volume-group-commands-b121d6ec7da9779a.yaml b/releasenotes/notes/add-volume-group-commands-b121d6ec7da9779a.yaml new file mode 100644 index 0000000000..8b3fe7ecc4 --- /dev/null +++ b/releasenotes/notes/add-volume-group-commands-b121d6ec7da9779a.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Add ``volume group create``, ``volume group delete``, + ``volume group list``, ``volume group failover``, + ``volume group set/unset`` and ``volume attachment show`` + commands to create, delete, list, failover, update and show volume groups, + respectively. diff --git a/setup.cfg b/setup.cfg index d593762ac9..1d031a8fc4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -714,6 +714,13 @@ openstack.volume.v3 = volume_backup_record_export = openstackclient.volume.v2.backup_record:ExportBackupRecord volume_backup_record_import = openstackclient.volume.v2.backup_record:ImportBackupRecord + volume_group_create = openstackclient.volume.v3.volume_group:CreateVolumeGroup + volume_group_delete = openstackclient.volume.v3.volume_group:DeleteVolumeGroup + volume_group_list = openstackclient.volume.v3.volume_group:ListVolumeGroup + volume_group_failover = openstackclient.volume.v3.volume_group:FailoverVolumeGroup + volume_group_set = openstackclient.volume.v3.volume_group:SetVolumeGroup + volume_group_show = openstackclient.volume.v3.volume_group:ShowVolumeGroup + volume_host_set = openstackclient.volume.v2.volume_host:SetVolumeHost volume_message_delete = openstackclient.volume.v3.volume_message:DeleteMessage From 83551d2a0c7604179c0988c13d58455a6b289cc8 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 3 Jun 2021 14:38:55 +0100 Subject: [PATCH 2390/3095] volume: Add 'volume group type *' commands These mirror the 'cinder group-type-*' commands, with arguments copied across essentially verbatim. The only significant departure is the merging of some commands, such as 'group-type-default' and 'group-type-list' into 'group type list', and 'group-type-update' and 'group-type-key' into 'group type set/unset'. volume group type create volume group type delete volume group type list volume group type show volume group type set volume group type unset Change-Id: Iee6ee2f1f276e6ef6f75a74f8f2980f14c0d5e2f Signed-off-by: Stephen Finucane --- .../cli/command-objects/volume-group-type.rst | 8 + doc/source/cli/commands.rst | 1 + doc/source/cli/data/cinder.csv | 16 +- openstackclient/tests/unit/volume/v3/fakes.py | 4 +- .../unit/volume/v3/test_volume_group_type.py | 475 ++++++++++++++++++ .../volume/v3/volume_group_type.py | 410 +++++++++++++++ ...-group-type-commands-13eabc7664a5c2bc.yaml | 7 + setup.cfg | 7 + 8 files changed, 919 insertions(+), 9 deletions(-) create mode 100644 doc/source/cli/command-objects/volume-group-type.rst create mode 100644 openstackclient/tests/unit/volume/v3/test_volume_group_type.py create mode 100644 openstackclient/volume/v3/volume_group_type.py create mode 100644 releasenotes/notes/add-volume-group-type-commands-13eabc7664a5c2bc.yaml diff --git a/doc/source/cli/command-objects/volume-group-type.rst b/doc/source/cli/command-objects/volume-group-type.rst new file mode 100644 index 0000000000..edb88dc7b0 --- /dev/null +++ b/doc/source/cli/command-objects/volume-group-type.rst @@ -0,0 +1,8 @@ +================= +volume group type +================= + +Block Storage v3 + +.. autoprogram-cliff:: openstack.volume.v3 + :command: volume group type * diff --git a/doc/source/cli/commands.rst b/doc/source/cli/commands.rst index b91a896f41..ada38e3e57 100644 --- a/doc/source/cli/commands.rst +++ b/doc/source/cli/commands.rst @@ -160,6 +160,7 @@ referring to both Compute and Volume quotas. * ``volume backup record``: (**Volume**) volume record that can be imported or exported * ``volume backend``: (**Volume**) volume backend storage * ``volume group``: (**Volume**) group of volumes +* ``volume group type``: (**Volume**) deployment-specific types of volumes groups available * ``volume host``: (**Volume**) the physical computer for volumes * ``volume message``: (**Volume**) volume API internal messages detailing volume failure messages * ``volume qos``: (**Volume**) quality-of-service (QoS) specification for volumes diff --git a/doc/source/cli/data/cinder.csv b/doc/source/cli/data/cinder.csv index 031aa43900..5de8ea5cdd 100644 --- a/doc/source/cli/data/cinder.csv +++ b/doc/source/cli/data/cinder.csv @@ -57,14 +57,14 @@ group-snapshot-create,,Creates a group snapshot. (Supported by API versions 3.14 group-snapshot-delete,,Removes one or more group snapshots. (Supported by API versions 3.14 - 3.latest) group-snapshot-list,,Lists all group snapshots. (Supported by API versions 3.14 - 3.latest) group-snapshot-show,,Shows group snapshot details. (Supported by API versions 3.14 - 3.latest) -group-specs-list,,Lists current group types and specs. (Supported by API versions 3.11 - 3.latest) -group-type-create,,Creates a group type. (Supported by API versions 3.11 - 3.latest) -group-type-default,,List the default group type. (Supported by API versions 3.11 - 3.latest) -group-type-delete,,Deletes group type or types. (Supported by API versions 3.11 - 3.latest) -group-type-key,,Sets or unsets group_spec for a group type. (Supported by API versions 3.11 - 3.latest) -group-type-list,,Lists available 'group types'. (Admin only will see private types) (Supported by API versions 3.11 - 3.latest) -group-type-show,,Show group type details. (Supported by API versions 3.11 - 3.latest) -group-type-update,,Updates group type name description and/or is_public. (Supported by API versions 3.11 - 3.latest) +group-specs-list,volume group type list,Lists current group types and specs. (Supported by API versions 3.11 - 3.latest) +group-type-create,volume group type create,Creates a group type. (Supported by API versions 3.11 - 3.latest) +group-type-default,volume group type list --default,List the default group type. (Supported by API versions 3.11 - 3.latest) +group-type-delete,volume group type delete,Deletes group type or types. (Supported by API versions 3.11 - 3.latest) +group-type-key,volume group type set,Sets or unsets group_spec for a group type. (Supported by API versions 3.11 - 3.latest) +group-type-list,volume group type set,Lists available 'group types'. (Admin only will see private types) (Supported by API versions 3.11 - 3.latest) +group-type-show,volume group type show,Show group type details. (Supported by API versions 3.11 - 3.latest) +group-type-update,volume group type set,Updates group type name description and/or is_public. (Supported by API versions 3.11 - 3.latest) group-update,volume group set,Updates a group. (Supported by API versions 3.13 - 3.latest) image-metadata,volume set --image-property,Sets or deletes volume image metadata. image-metadata-show,volume show,Shows volume image metadata. diff --git a/openstackclient/tests/unit/volume/v3/fakes.py b/openstackclient/tests/unit/volume/v3/fakes.py index b0c96290f8..c300ca3855 100644 --- a/openstackclient/tests/unit/volume/v3/fakes.py +++ b/openstackclient/tests/unit/volume/v3/fakes.py @@ -129,10 +129,11 @@ class FakeVolumeGroupType: """Fake one or more volume group types.""" @staticmethod - def create_one_volume_group_type(attrs=None): + def create_one_volume_group_type(attrs=None, methods=None): """Create a fake group type. :param attrs: A dictionary with all attributes of group type + :param methods: A dictionary with all methods :return: A FakeResource object with id, name, description, etc. """ attrs = attrs or {} @@ -152,6 +153,7 @@ def create_one_volume_group_type(attrs=None): group_type = fakes.FakeResource( None, group_type_info, + methods=methods, loaded=True) return group_type diff --git a/openstackclient/tests/unit/volume/v3/test_volume_group_type.py b/openstackclient/tests/unit/volume/v3/test_volume_group_type.py new file mode 100644 index 0000000000..7e758a2c1f --- /dev/null +++ b/openstackclient/tests/unit/volume/v3/test_volume_group_type.py @@ -0,0 +1,475 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from unittest import mock + +from cinderclient import api_versions +from osc_lib.cli import format_columns +from osc_lib import exceptions + +from openstackclient.tests.unit.volume.v3 import fakes as volume_fakes +from openstackclient.volume.v3 import volume_group_type + + +class TestVolumeGroupType(volume_fakes.TestVolume): + + def setUp(self): + super().setUp() + + self.volume_group_types_mock = \ + self.app.client_manager.volume.group_types + self.volume_group_types_mock.reset_mock() + + +class TestVolumeGroupTypeCreate(TestVolumeGroupType): + + maxDiff = 2000 + + fake_volume_group_type = \ + volume_fakes.FakeVolumeGroupType.create_one_volume_group_type() + + columns = ( + 'ID', + 'Name', + 'Description', + 'Is Public', + 'Properties', + ) + data = ( + fake_volume_group_type.id, + fake_volume_group_type.name, + fake_volume_group_type.description, + fake_volume_group_type.is_public, + format_columns.DictColumn(fake_volume_group_type.group_specs), + ) + + def setUp(self): + super().setUp() + + self.volume_group_types_mock.create.return_value = \ + self.fake_volume_group_type + + self.cmd = volume_group_type.CreateVolumeGroupType(self.app, None) + + def test_volume_group_type_create(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.11') + + arglist = [ + self.fake_volume_group_type.name, + ] + verifylist = [ + ('name', self.fake_volume_group_type.name), + ('description', None), + ('is_public', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volume_group_types_mock.create.assert_called_once_with( + self.fake_volume_group_type.name, + None, + True) + self.assertEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + + def test_volume_group_type_create_with_options(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.11') + + arglist = [ + self.fake_volume_group_type.name, + '--description', 'foo', + '--private', + ] + verifylist = [ + ('name', self.fake_volume_group_type.name), + ('description', 'foo'), + ('is_public', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volume_group_types_mock.create.assert_called_once_with( + self.fake_volume_group_type.name, + 'foo', + False) + self.assertEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + + def test_volume_group_type_create_pre_v311(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.10') + + arglist = [ + self.fake_volume_group_type.name, + ] + verifylist = [ + ('name', self.fake_volume_group_type.name), + ('description', None), + ('is_public', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.11 or greater is required', + str(exc)) + + +class TestVolumeGroupTypeDelete(TestVolumeGroupType): + + fake_volume_group_type = \ + volume_fakes.FakeVolumeGroupType.create_one_volume_group_type() + + def setUp(self): + super().setUp() + + self.volume_group_types_mock.get.return_value = \ + self.fake_volume_group_type + self.volume_group_types_mock.delete.return_value = None + + self.cmd = volume_group_type.DeleteVolumeGroupType(self.app, None) + + def test_volume_group_type_delete(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.11') + + arglist = [ + self.fake_volume_group_type.id, + ] + verifylist = [ + ('group_type', self.fake_volume_group_type.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.volume_group_types_mock.delete.assert_called_once_with( + self.fake_volume_group_type.id, + ) + self.assertIsNone(result) + + def test_volume_group_type_delete_pre_v311(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.10') + + arglist = [ + self.fake_volume_group_type.id, + ] + verifylist = [ + ('group_type', self.fake_volume_group_type.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.11 or greater is required', + str(exc)) + + +class TestVolumeGroupTypeSet(TestVolumeGroupType): + + fake_volume_group_type = \ + volume_fakes.FakeVolumeGroupType.create_one_volume_group_type( + methods={ + 'get_keys': {'foo': 'bar'}, + 'set_keys': None, + 'unset_keys': None, + }) + + columns = ( + 'ID', + 'Name', + 'Description', + 'Is Public', + 'Properties', + ) + data = ( + fake_volume_group_type.id, + fake_volume_group_type.name, + fake_volume_group_type.description, + fake_volume_group_type.is_public, + format_columns.DictColumn(fake_volume_group_type.group_specs), + ) + + def setUp(self): + super().setUp() + + self.volume_group_types_mock.get.return_value = \ + self.fake_volume_group_type + self.volume_group_types_mock.update.return_value = \ + self.fake_volume_group_type + + self.cmd = volume_group_type.SetVolumeGroupType(self.app, None) + + def test_volume_group_type_set(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.11') + + self.fake_volume_group_type.set_keys.return_value = None + + arglist = [ + self.fake_volume_group_type.id, + '--name', 'foo', + '--description', 'hello, world', + '--public', + '--property', 'fizz=buzz', + ] + verifylist = [ + ('group_type', self.fake_volume_group_type.id), + ('name', 'foo'), + ('description', 'hello, world'), + ('is_public', True), + ('no_property', False), + ('properties', {'fizz': 'buzz'}), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volume_group_types_mock.update.assert_called_once_with( + self.fake_volume_group_type.id, + name='foo', + description='hello, world', + is_public=True, + ) + self.fake_volume_group_type.set_keys.assert_called_once_with( + {'fizz': 'buzz'}, + ) + self.assertEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + + def test_volume_group_type_with_no_property_option(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.11') + + arglist = [ + self.fake_volume_group_type.id, + '--no-property', + '--property', 'fizz=buzz', + ] + verifylist = [ + ('group_type', self.fake_volume_group_type.id), + ('name', None), + ('description', None), + ('is_public', None), + ('no_property', True), + ('properties', {'fizz': 'buzz'}), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volume_group_types_mock.get.assert_called_once_with( + self.fake_volume_group_type.id) + self.fake_volume_group_type.get_keys.assert_called_once_with() + self.fake_volume_group_type.unset_keys.assert_called_once_with( + {'foo': 'bar'}.keys()) + self.assertEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + + def test_volume_group_type_set_pre_v311(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.10') + + arglist = [ + self.fake_volume_group_type.id, + '--name', 'foo', + '--description', 'hello, world', + ] + verifylist = [ + ('group_type', self.fake_volume_group_type.id), + ('name', 'foo'), + ('description', 'hello, world'), + ('is_public', None), + ('no_property', False), + ('properties', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.11 or greater is required', + str(exc)) + + +class TestVolumeGroupTypeUnset(TestVolumeGroupType): + + fake_volume_group_type = \ + volume_fakes.FakeVolumeGroupType.create_one_volume_group_type( + methods={'unset_keys': None}) + + columns = ( + 'ID', + 'Name', + 'Description', + 'Is Public', + 'Properties', + ) + data = ( + fake_volume_group_type.id, + fake_volume_group_type.name, + fake_volume_group_type.description, + fake_volume_group_type.is_public, + format_columns.DictColumn(fake_volume_group_type.group_specs), + ) + + def setUp(self): + super().setUp() + + self.volume_group_types_mock.get.return_value = \ + self.fake_volume_group_type + + self.cmd = volume_group_type.UnsetVolumeGroupType(self.app, None) + + def test_volume_group_type_unset(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.11') + + arglist = [ + self.fake_volume_group_type.id, + '--property', 'fizz', + ] + verifylist = [ + ('group_type', self.fake_volume_group_type.id), + ('properties', ['fizz']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volume_group_types_mock.get.assert_has_calls([ + mock.call(self.fake_volume_group_type.id), + mock.call(self.fake_volume_group_type.id), + ]) + self.fake_volume_group_type.unset_keys.assert_called_once_with( + ['fizz']) + self.assertEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + + def test_volume_group_type_unset_pre_v311(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.10') + + arglist = [ + self.fake_volume_group_type.id, + '--property', 'fizz', + ] + verifylist = [ + ('group_type', self.fake_volume_group_type.id), + ('properties', ['fizz']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.11 or greater is required', + str(exc)) + + +class TestVolumeGroupTypeList(TestVolumeGroupType): + + fake_volume_group_types = \ + volume_fakes.FakeVolumeGroupType.create_volume_group_types() + + columns = ( + 'ID', + 'Name', + 'Is Public', + 'Properties', + ) + data = [ + ( + fake_volume_group_type.id, + fake_volume_group_type.name, + fake_volume_group_type.is_public, + fake_volume_group_type.group_specs, + ) for fake_volume_group_type in fake_volume_group_types + ] + + def setUp(self): + super().setUp() + + self.volume_group_types_mock.list.return_value = \ + self.fake_volume_group_types + self.volume_group_types_mock.default.return_value = \ + self.fake_volume_group_types[0] + + self.cmd = volume_group_type.ListVolumeGroupType(self.app, None) + + def test_volume_group_type_list(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.11') + + arglist = [ + ] + verifylist = [ + ('show_default', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volume_group_types_mock.list.assert_called_once_with() + self.assertEqual(self.columns, columns) + self.assertCountEqual(tuple(self.data), data) + + def test_volume_group_type_list_with_default_option(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.11') + + arglist = [ + '--default', + ] + verifylist = [ + ('show_default', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volume_group_types_mock.default.assert_called_once_with() + self.assertEqual(self.columns, columns) + self.assertCountEqual(tuple([self.data[0]]), data) + + def test_volume_group_type_list_pre_v311(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.10') + + arglist = [ + ] + verifylist = [ + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.11 or greater is required', + str(exc)) diff --git a/openstackclient/volume/v3/volume_group_type.py b/openstackclient/volume/v3/volume_group_type.py new file mode 100644 index 0000000000..860fa544a5 --- /dev/null +++ b/openstackclient/volume/v3/volume_group_type.py @@ -0,0 +1,410 @@ +# 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 logging + +from cinderclient import api_versions +from osc_lib.cli import format_columns +from osc_lib.cli import parseractions +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils + +from openstackclient.i18n import _ + +LOG = logging.getLogger(__name__) + + +def _format_group_type(group): + columns = ( + 'id', + 'name', + 'description', + 'is_public', + 'group_specs', + ) + column_headers = ( + 'ID', + 'Name', + 'Description', + 'Is Public', + 'Properties', + ) + + # TODO(stephenfin): Consider using a formatter for volume_types since it's + # a list + return ( + column_headers, + utils.get_item_properties( + group, + columns, + formatters={ + 'group_specs': format_columns.DictColumn, + }, + ), + ) + + +class CreateVolumeGroupType(command.ShowOne): + """Create a volume group type. + + This command requires ``--os-volume-api-version`` 3.11 or greater. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'name', + metavar='', + help=_('Name of new volume group type.'), + ) + parser.add_argument( + '--description', + metavar='', + help=_('Description of the volume group type.') + ) + type_group = parser.add_mutually_exclusive_group() + type_group.add_argument( + '--public', + dest='is_public', + action='store_true', + default=True, + help=_( + 'Volume group type is available to other projects (default)' + ), + ) + type_group.add_argument( + '--private', + dest='is_public', + action='store_false', + help=_('Volume group type is not available to other projects') + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.11'): + msg = _( + "--os-volume-api-version 3.11 or greater is required to " + "support the 'volume group type create' command" + ) + raise exceptions.CommandError(msg) + + group_type = volume_client.group_types.create( + parsed_args.name, + parsed_args.description, + parsed_args.is_public) + + return _format_group_type(group_type) + + +class DeleteVolumeGroupType(command.Command): + """Delete a volume group type. + + This command requires ``--os-volume-api-version`` 3.11 or greater. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'group_type', + metavar='', + help=_('Name or ID of volume group type to delete'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.11'): + msg = _( + "--os-volume-api-version 3.11 or greater is required to " + "support the 'volume group type delete' command" + ) + raise exceptions.CommandError(msg) + + group_type = utils.find_resource( + volume_client.group_types, + parsed_args.group_type, + ) + + volume_client.group_types.delete(group_type.id) + + +class SetVolumeGroupType(command.ShowOne): + """Update a volume group type. + + This command requires ``--os-volume-api-version`` 3.11 or greater. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'group_type', + metavar='', + help=_('Name or ID of volume group type.'), + ) + parser.add_argument( + '--name', + metavar='', + help=_('New name for volume group type.'), + ) + parser.add_argument( + '--description', + metavar='', + help=_('New description for volume group type.'), + ) + type_group = parser.add_mutually_exclusive_group() + type_group.add_argument( + '--public', + dest='is_public', + action='store_true', + default=None, + help=_('Make volume group type available to other projects.'), + ) + type_group.add_argument( + '--private', + dest='is_public', + action='store_false', + help=_('Make volume group type unavailable to other projects.') + ) + parser.add_argument( + '--no-property', + action='store_true', + help=_( + 'Remove all properties from this volume group type ' + '(specify both --no-property and --property ' + 'to remove the current properties before setting ' + 'new properties)' + ), + ) + parser.add_argument( + '--property', + metavar='', + action=parseractions.KeyValueAction, + dest='properties', + help=_( + 'Property to add or modify for this volume group type ' + '(repeat option to set multiple properties)' + ), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.11'): + msg = _( + "--os-volume-api-version 3.11 or greater is required to " + "support the 'volume group type set' command" + ) + raise exceptions.CommandError(msg) + + group_type = utils.find_resource( + volume_client.group_types, + parsed_args.group_type, + ) + + kwargs = {} + errors = 0 + + if parsed_args.name is not None: + kwargs['name'] = parsed_args.name + + if parsed_args.description is not None: + kwargs['description'] = parsed_args.description + + if parsed_args.is_public is not None: + kwargs['is_public'] = parsed_args.is_public + + if kwargs: + try: + group_type = volume_client.group_types.update( + group_type.id, **kwargs) + except Exception as e: + LOG.error(_("Failed to update group type: %s"), e) + errors += 1 + + if parsed_args.no_property: + try: + keys = group_type.get_keys().keys() + group_type.unset_keys(keys) + except Exception as e: + LOG.error(_("Failed to clear group type properties: %s"), e) + errors += 1 + + if parsed_args.properties: + try: + group_type.set_keys(parsed_args.properties) + except Exception as e: + LOG.error(_("Failed to set group type properties: %s"), e) + errors += 1 + + if errors > 0: + msg = _( + "Command Failed: One or more of the operations failed" + ) + raise exceptions.CommandError() + + return _format_group_type(group_type) + + +class UnsetVolumeGroupType(command.ShowOne): + """Unset properties of a volume group type. + + This command requires ``--os-volume-api-version`` 3.11 or greater. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'group_type', + metavar='', + help=_('Name or ID of volume group type.'), + ) + parser.add_argument( + '--property', + metavar='', + action='append', + dest='properties', + help=_( + 'Property to remove from this volume group type ' + '(repeat option to unset multiple properties)' + ), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.11'): + msg = _( + "--os-volume-api-version 3.11 or greater is required to " + "support the 'volume group type unset' command" + ) + raise exceptions.CommandError(msg) + + group_type = utils.find_resource( + volume_client.group_types, + parsed_args.group_type, + ) + + group_type.unset_keys(parsed_args.properties) + + group_type = utils.find_resource( + volume_client.group_types, + parsed_args.group_type, + ) + + return _format_group_type(group_type) + + +class ListVolumeGroupType(command.Lister): + """Lists all volume group types. + + This command requires ``--os-volume-api-version`` 3.11 or greater. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + '--default', + action='store_true', + dest='show_default', + default=False, + help=_('List the default volume group type.'), + ) + # TODO(stephenfin): Add once we have an equivalent command for + # 'cinder list-filters' + # parser.add_argument( + # '--filter', + # metavar='', + # action=parseractions.KeyValueAction, + # dest='filters', + # help=_( + # "Filter key and value pairs. Use 'foo' to " + # "check enabled filters from server. Use 'key~=value' for " + # "inexact filtering if the key supports " + # "(supported by --os-volume-api-version 3.33 or above)" + # ), + # ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.11'): + msg = _( + "--os-volume-api-version 3.11 or greater is required to " + "support the 'volume group type list' command" + ) + raise exceptions.CommandError(msg) + + if parsed_args.show_default: + group_types = [volume_client.group_types.default()] + else: + group_types = volume_client.group_types.list() + + column_headers = ( + 'ID', + 'Name', + 'Is Public', + 'Properties', + ) + columns = ( + 'id', + 'name', + 'is_public', + 'group_specs', + ) + + return ( + column_headers, + ( + utils.get_item_properties(a, columns) + for a in group_types + ), + ) + + +class ShowVolumeGroupType(command.ShowOne): + """Show detailed information for a volume group type. + + This command requires ``--os-volume-api-version`` 3.11 or greater. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'group_type', + metavar='', + help=_('Name or ID of volume group type.'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.11'): + msg = _( + "--os-volume-api-version 3.11 or greater is required to " + "support the 'volume group type show' command" + ) + raise exceptions.CommandError(msg) + + group_type = utils.find_resource( + volume_client.group_types, + parsed_args.group, + ) + + return _format_group_type(group_type) diff --git a/releasenotes/notes/add-volume-group-type-commands-13eabc7664a5c2bc.yaml b/releasenotes/notes/add-volume-group-type-commands-13eabc7664a5c2bc.yaml new file mode 100644 index 0000000000..02ffd38dda --- /dev/null +++ b/releasenotes/notes/add-volume-group-type-commands-13eabc7664a5c2bc.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Add ``volume group type create``, ``volume group type delete``, + ``volume group type list``, ``volume group type set/unset`` and + ``volume group type show`` commands to create, delete, list, update, + and show volume group types, respectively. diff --git a/setup.cfg b/setup.cfg index 1d031a8fc4..2a01ae7b0d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -719,8 +719,15 @@ openstack.volume.v3 = volume_group_list = openstackclient.volume.v3.volume_group:ListVolumeGroup volume_group_failover = openstackclient.volume.v3.volume_group:FailoverVolumeGroup volume_group_set = openstackclient.volume.v3.volume_group:SetVolumeGroup + volume_group_unset = openstackclient.volume.v3.volume_group:UnsetVolumeGroup volume_group_show = openstackclient.volume.v3.volume_group:ShowVolumeGroup + volume_group_type_create = openstackclient.volume.v3.volume_group_type:CreateVolumeGroupType + volume_group_type_delete = openstackclient.volume.v3.volume_group_type:DeleteVolumeGroupType + volume_group_type_list = openstackclient.volume.v3.volume_group_type:ListVolumeGroupType + volume_group_type_set = openstackclient.volume.v3.volume_group_type:SetVolumeGroupType + volume_group_type_show = openstackclient.volume.v3.volume_group_type:ShowVolumeGroupType + volume_host_set = openstackclient.volume.v2.volume_host:SetVolumeHost volume_message_delete = openstackclient.volume.v3.volume_message:DeleteMessage From fa8c8d26a7696d169b0b9d5aaf6b723d8feee08a Mon Sep 17 00:00:00 2001 From: Slawek Kaplonski Date: Wed, 7 Apr 2021 23:40:16 +0200 Subject: [PATCH 2391/3095] Add support for Neutron's L3 conntrack helper resource Neutron has got CRUD API for L3 conntrack helper since some time. This patch adds support for it in the OSC. OpenStack SDK supports that since [1] This patch also bumps minimum OpenStack SDK version to the 0.56.0 as that version introduced support for the Neutron's L3 conntrack helper. [1] https://review.opendev.org/c/openstack/openstacksdk/+/782870 Change-Id: I55604182ae50b6ad70c8bc1f7efad8859f191269 --- .zuul.yaml | 1 + .../network-l3-conntrack-helper.rst | 8 + .../network/v2/l3_conntrack_helper.py | 255 ++++++++++++++ .../network/v2/test_l3_conntrack_helper.py | 145 ++++++++ .../tests/unit/network/v2/fakes.py | 75 +++++ .../network/v2/test_l3_conntrack_helper.py | 316 ++++++++++++++++++ .../L3-conntrack-helper-bd0d9da041747e84.yaml | 8 + setup.cfg | 6 + 8 files changed, 814 insertions(+) create mode 100644 doc/source/cli/command-objects/network-l3-conntrack-helper.rst create mode 100644 openstackclient/network/v2/l3_conntrack_helper.py create mode 100644 openstackclient/tests/functional/network/v2/test_l3_conntrack_helper.py create mode 100644 openstackclient/tests/unit/network/v2/test_l3_conntrack_helper.py create mode 100644 releasenotes/notes/L3-conntrack-helper-bd0d9da041747e84.yaml diff --git a/.zuul.yaml b/.zuul.yaml index e7b01da409..e1c1f970af 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -106,6 +106,7 @@ q-metering: true q-qos: true neutron-tag-ports-during-bulk-creation: true + neutron-conntrack-helper: true devstack_localrc: Q_AGENT: openvswitch Q_ML2_TENANT_NETWORK_TYPE: vxlan diff --git a/doc/source/cli/command-objects/network-l3-conntrack-helper.rst b/doc/source/cli/command-objects/network-l3-conntrack-helper.rst new file mode 100644 index 0000000000..badbab6337 --- /dev/null +++ b/doc/source/cli/command-objects/network-l3-conntrack-helper.rst @@ -0,0 +1,8 @@ +=========================== +network l3 conntrack helper +=========================== + +Network v2 + +.. autoprogram-cliff:: openstack.network.v2 + :command: network l3 conntrack helper * diff --git a/openstackclient/network/v2/l3_conntrack_helper.py b/openstackclient/network/v2/l3_conntrack_helper.py new file mode 100644 index 0000000000..dae259273f --- /dev/null +++ b/openstackclient/network/v2/l3_conntrack_helper.py @@ -0,0 +1,255 @@ +# 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. +# + +"""L3 Conntrack Helper action implementations""" + +import logging + +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils + +from openstackclient.i18n import _ +from openstackclient.network import sdk_utils + + +LOG = logging.getLogger(__name__) + + +def _get_columns(item): + column_map = {} + return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + + +def _get_attrs(client, parsed_args): + router = client.find_router(parsed_args.router, ignore_missing=False) + attrs = {'router_id': router.id} + if parsed_args.helper: + attrs['helper'] = parsed_args.helper + if parsed_args.protocol: + attrs['protocol'] = parsed_args.protocol + if parsed_args.port: + attrs['port'] = parsed_args.port + + return attrs + + +class CreateConntrackHelper(command.ShowOne): + _description = _("Create a new L3 conntrack helper") + + def get_parser(self, prog_name): + parser = super(CreateConntrackHelper, self).get_parser(prog_name) + parser.add_argument( + 'router', + metavar='', + help=_('Router for which conntrack helper will be created') + ) + parser.add_argument( + '--helper', + required=True, + metavar='', + help=_('The netfilter conntrack helper module') + ) + parser.add_argument( + '--protocol', + required=True, + metavar='', + help=_('The network protocol for the netfilter conntrack target ' + 'rule') + ) + parser.add_argument( + '--port', + required=True, + metavar='', + type=int, + help=_('The network port for the netfilter conntrack target rule') + ) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + + attrs = _get_attrs(client, parsed_args) + obj = client.create_conntrack_helper(attrs.pop('router_id'), **attrs) + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns, formatters={}) + + return (display_columns, data) + + +class DeleteConntrackHelper(command.Command): + _description = _("Delete L3 conntrack helper") + + def get_parser(self, prog_name): + parser = super(DeleteConntrackHelper, self).get_parser(prog_name) + parser.add_argument( + 'router', + metavar='', + help=_('Router that the conntrack helper belong to') + ) + parser.add_argument( + 'conntrack_helper_ids', + metavar='', + nargs='+', + help=_('The ID of the conntrack helper(s) to delete') + ) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + result = 0 + + router = client.find_router(parsed_args.router, ignore_missing=False) + for ct_helper in parsed_args.conntrack_helper_ids: + try: + client.delete_conntrack_helper( + ct_helper, router.id, ignore_missing=False) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete L3 conntrack helper with " + "ID '%(ct_helper)s': %(e)s"), + {'ct_helper': ct_helper, 'e': e}) + + if result > 0: + total = len(parsed_args.conntrack_helper_ids) + msg = (_("%(result)s of %(total)s L3 conntrack helpers failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) + + +class ListConntrackHelper(command.Lister): + _description = _("List L3 conntrack helpers") + + def get_parser(self, prog_name): + parser = super(ListConntrackHelper, self).get_parser(prog_name) + parser.add_argument( + 'router', + metavar='', + help=_('Router that the conntrack helper belong to') + ) + parser.add_argument( + '--helper', + metavar='', + help=_('The netfilter conntrack helper module') + ) + parser.add_argument( + '--protocol', + metavar='', + help=_('The network protocol for the netfilter conntrack target ' + 'rule') + ) + parser.add_argument( + '--port', + metavar='', + help=_('The network port for the netfilter conntrack target rule') + ) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + columns = ( + 'id', + 'router_id', + 'helper', + 'protocol', + 'port', + ) + column_headers = ( + 'ID', + 'Router ID', + 'Helper', + 'Protocol', + 'Port', + ) + attrs = _get_attrs(client, parsed_args) + data = client.conntrack_helpers(attrs.pop('router_id'), **attrs) + + return (column_headers, + (utils.get_item_properties( + s, columns, formatters={}, + ) for s in data)) + + +class SetConntrackHelper(command.Command): + _description = _("Set L3 conntrack helper properties") + + def get_parser(self, prog_name): + parser = super(SetConntrackHelper, self).get_parser(prog_name) + parser.add_argument( + 'router', + metavar='', + help=_('Router that the conntrack helper belong to') + ) + parser.add_argument( + 'conntrack_helper_id', + metavar='', + help=_('The ID of the conntrack helper(s)') + ) + parser.add_argument( + '--helper', + metavar='', + help=_('The netfilter conntrack helper module') + ) + parser.add_argument( + '--protocol', + metavar='', + help=_('The network protocol for the netfilter conntrack target ' + 'rule') + ) + parser.add_argument( + '--port', + metavar='', + type=int, + help=_('The network port for the netfilter conntrack target rule') + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + attrs = _get_attrs(client, parsed_args) + if attrs: + client.update_conntrack_helper( + parsed_args.conntrack_helper_id, attrs.pop('router_id'), + **attrs) + + +class ShowConntrackHelper(command.ShowOne): + _description = _("Display L3 conntrack helper details") + + def get_parser(self, prog_name): + parser = super(ShowConntrackHelper, self).get_parser(prog_name) + parser.add_argument( + 'router', + metavar='', + help=_('Router that the conntrack helper belong to') + ) + parser.add_argument( + 'conntrack_helper_id', + metavar='', + help=_('The ID of the conntrack helper') + ) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + router = client.find_router(parsed_args.router, ignore_missing=False) + obj = client.get_conntrack_helper( + parsed_args.conntrack_helper_id, router.id) + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns, formatters={}) + + return (display_columns, data) diff --git a/openstackclient/tests/functional/network/v2/test_l3_conntrack_helper.py b/openstackclient/tests/functional/network/v2/test_l3_conntrack_helper.py new file mode 100644 index 0000000000..bbb9a7cd97 --- /dev/null +++ b/openstackclient/tests/functional/network/v2/test_l3_conntrack_helper.py @@ -0,0 +1,145 @@ +# 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 json +import uuid + +from openstackclient.tests.functional.network.v2 import common + + +class L3ConntrackHelperTests(common.NetworkTests): + + def setUp(self): + super(L3ConntrackHelperTests, self).setUp() + # Nothing in this class works with Nova Network + if not self.haz_network: + self.skipTest("No Network service present") + if not self.is_extension_enabled('l3-conntrack-helper'): + self.skipTest("No l3-conntrack-helper extension present") + if not self.is_extension_enabled('expose-l3-conntrack-helper'): + self.skipTest("No expose-l3-conntrack-helper extension present") + + def _create_router(self): + router_name = uuid.uuid4().hex + json_output = json.loads(self.openstack( + 'router create -f json ' + router_name + )) + self.assertIsNotNone(json_output['id']) + router_id = json_output['id'] + self.addCleanup(self.openstack, 'router delete ' + router_id) + return router_id + + def _create_helpers(self, router_id, helpers): + created_helpers = [] + for helper in helpers: + output = json.loads(self.openstack( + 'network l3 conntrack helper create %(router)s ' + '--helper %(helper)s --protocol %(protocol)s --port %(port)s ' + '-f json' % {'router': router_id, + 'helper': helper['helper'], + 'protocol': helper['protocol'], + 'port': helper['port']})) + self.assertEqual(helper['helper'], output['helper']) + self.assertEqual(helper['protocol'], output['protocol']) + self.assertEqual(helper['port'], output['port']) + created_helpers.append(output) + return created_helpers + + def test_l3_conntrack_helper_create_and_delete(self): + """Test create, delete multiple""" + + helpers = [ + { + 'helper': 'tftp', + 'protocol': 'udp', + 'port': 69 + }, { + 'helper': 'ftp', + 'protocol': 'tcp', + 'port': 21 + } + ] + router_id = self._create_router() + created_helpers = self._create_helpers(router_id, helpers) + ct_ids = " ".join([ct['id'] for ct in created_helpers]) + + raw_output = self.openstack( + '--debug network l3 conntrack helper delete %(router)s ' + '%(ct_ids)s' % { + 'router': router_id, 'ct_ids': ct_ids}) + self.assertOutput('', raw_output) + + def test_l3_conntrack_helper_list(self): + helpers = [ + { + 'helper': 'tftp', + 'protocol': 'udp', + 'port': 69 + }, { + 'helper': 'ftp', + 'protocol': 'tcp', + 'port': 21 + } + ] + expected_helpers = [ + { + 'Helper': 'tftp', + 'Protocol': 'udp', + 'Port': 69 + }, { + 'Helper': 'ftp', + 'Protocol': 'tcp', + 'Port': 21 + } + ] + router_id = self._create_router() + self._create_helpers(router_id, helpers) + output = json.loads(self.openstack( + 'network l3 conntrack helper list %s -f json ' % router_id + )) + for ct in output: + self.assertEqual(router_id, ct.pop('Router ID')) + ct.pop("ID") + self.assertIn(ct, expected_helpers) + + def test_l3_conntrack_helper_set_and_show(self): + helper = { + 'helper': 'tftp', + 'protocol': 'udp', + 'port': 69} + router_id = self._create_router() + created_helper = self._create_helpers(router_id, [helper])[0] + output = json.loads(self.openstack( + 'network l3 conntrack helper show %(router_id)s %(ct_id)s ' + '-f json' % { + 'router_id': router_id, 'ct_id': created_helper['id']})) + self.assertEqual(helper['helper'], output['helper']) + self.assertEqual(helper['protocol'], output['protocol']) + self.assertEqual(helper['port'], output['port']) + + raw_output = self.openstack( + 'network l3 conntrack helper set %(router_id)s %(ct_id)s ' + '--port %(port)s ' % { + 'router_id': router_id, + 'ct_id': created_helper['id'], + 'port': helper['port'] + 1}) + self.assertOutput('', raw_output) + + output = json.loads(self.openstack( + 'network l3 conntrack helper show %(router_id)s %(ct_id)s ' + '-f json' % { + 'router_id': router_id, 'ct_id': created_helper['id']})) + self.assertEqual(helper['port'] + 1, output['port']) + self.assertEqual(helper['helper'], output['helper']) + self.assertEqual(helper['protocol'], output['protocol']) diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index e5023d43c5..ab77d719c0 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -1973,3 +1973,78 @@ def get_port_forwardings(port_forwardings=None, count=2): ) return mock.Mock(side_effect=port_forwardings) + + +class FakeL3ConntrackHelper(object): + """"Fake one or more L3 conntrack helper""" + + @staticmethod + def create_one_l3_conntrack_helper(attrs=None): + """Create a fake L3 conntrack helper. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object with protocol, port, etc. + """ + attrs = attrs or {} + router_id = ( + attrs.get('router_id') or 'router-id-' + uuid.uuid4().hex + ) + # Set default attributes. + ct_attrs = { + 'id': uuid.uuid4().hex, + 'router_id': router_id, + 'helper': 'tftp', + 'protocol': 'tcp', + 'port': randint(1, 65535), + } + + # Overwrite default attributes. + ct_attrs.update(attrs) + + ct = fakes.FakeResource( + info=copy.deepcopy(ct_attrs), + loaded=True + ) + return ct + + @staticmethod + def create_l3_conntrack_helpers(attrs=None, count=2): + """Create multiple fake L3 Conntrack helpers. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of L3 Conntrack helper rule to fake + :return: + A list of FakeResource objects faking the Conntrack helpers + """ + ct_helpers = [] + for i in range(0, count): + ct_helpers.append( + FakeL3ConntrackHelper.create_one_l3_conntrack_helper(attrs) + ) + return ct_helpers + + @staticmethod + def get_l3_conntrack_helpers(ct_helpers=None, count=2): + """Get a list of faked L3 Conntrack helpers. + + If ct_helpers list is provided, then initialize the Mock object + with the list. Otherwise create one. + + :param List ct_helpers: + A list of FakeResource objects faking conntrack helpers + :param int count: + The number of L3 conntrack helpers to fake + :return: + An iterable Mock object with side_effect set to a list of faked + L3 conntrack helpers + """ + if ct_helpers is None: + ct_helpers = ( + FakeL3ConntrackHelper.create_l3_conntrack_helpers(count) + ) + + return mock.Mock(side_effect=ct_helpers) diff --git a/openstackclient/tests/unit/network/v2/test_l3_conntrack_helper.py b/openstackclient/tests/unit/network/v2/test_l3_conntrack_helper.py new file mode 100644 index 0000000000..1676c9ffad --- /dev/null +++ b/openstackclient/tests/unit/network/v2/test_l3_conntrack_helper.py @@ -0,0 +1,316 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +from unittest import mock + +from osc_lib import exceptions + +from openstackclient.network.v2 import l3_conntrack_helper +from openstackclient.tests.unit.network.v2 import fakes as network_fakes +from openstackclient.tests.unit import utils as tests_utils + + +class TestConntrackHelper(network_fakes.TestNetworkV2): + + def setUp(self): + super(TestConntrackHelper, self).setUp() + # Get a shortcut to the network client + self.network = self.app.client_manager.network + self.router = network_fakes.FakeRouter.create_one_router() + self.network.find_router = mock.Mock(return_value=self.router) + + +class TestCreateL3ConntrackHelper(TestConntrackHelper): + + def setUp(self): + super(TestCreateL3ConntrackHelper, self).setUp() + attrs = {'router_id': self.router.id} + self.ct_helper = ( + network_fakes.FakeL3ConntrackHelper.create_one_l3_conntrack_helper( + attrs)) + self.columns = ( + 'helper', + 'id', + 'port', + 'protocol', + 'router_id' + ) + + self.data = ( + self.ct_helper.helper, + self.ct_helper.id, + self.ct_helper.port, + self.ct_helper.protocol, + self.ct_helper.router_id + ) + self.network.create_conntrack_helper = mock.Mock( + return_value=self.ct_helper) + + # Get the command object to test + self.cmd = l3_conntrack_helper.CreateConntrackHelper(self.app, + self.namespace) + + def test_create_no_options(self): + arglist = [] + verifylist = [] + + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_create_default_options(self): + arglist = [ + '--helper', 'tftp', + '--protocol', 'udp', + '--port', '69', + self.router.id, + ] + + verifylist = [ + ('helper', 'tftp'), + ('protocol', 'udp'), + ('port', 69), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_conntrack_helper.assert_called_once_with( + self.router.id, + **{'helper': 'tftp', 'protocol': 'udp', + 'port': 69} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_wrong_options(self): + arglist = [ + '--protocol', 'udp', + '--port', '69', + self.router.id, + ] + + self.assertRaises( + tests_utils.ParserException, + self.check_parser, + self.cmd, arglist, None) + + +class TestDeleteL3ConntrackHelper(TestConntrackHelper): + + def setUp(self): + super(TestDeleteL3ConntrackHelper, self).setUp() + attrs = {'router_id': self.router.id} + self.ct_helper = ( + network_fakes.FakeL3ConntrackHelper.create_one_l3_conntrack_helper( + attrs)) + self.network.delete_conntrack_helper = mock.Mock( + return_value=None) + + # Get the command object to test + self.cmd = l3_conntrack_helper.DeleteConntrackHelper(self.app, + self.namespace) + + def test_delete(self): + arglist = [ + self.ct_helper.router_id, + self.ct_helper.id + ] + verifylist = [ + ('conntrack_helper_ids', [self.ct_helper.id]), + ('router', self.ct_helper.router_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.network.delete_conntrack_helper.assert_called_once_with( + self.ct_helper.id, self.router.id, + ignore_missing=False) + self.assertIsNone(result) + + def test_delete_error(self): + arglist = [ + self.router.id, + self.ct_helper.id + ] + verifylist = [ + ('conntrack_helper_ids', [self.ct_helper.id]), + ('router', self.router.id), + ] + self.network.delete_conntrack_helper.side_effect = Exception( + 'Error message') + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, parsed_args) + + +class TestListL3ConntrackHelper(TestConntrackHelper): + + def setUp(self): + super(TestListL3ConntrackHelper, self).setUp() + attrs = {'router_id': self.router.id} + ct_helpers = ( + network_fakes.FakeL3ConntrackHelper.create_l3_conntrack_helpers( + attrs, count=3)) + self.columns = ( + 'ID', + 'Router ID', + 'Helper', + 'Protocol', + 'Port', + ) + self.data = [] + for ct_helper in ct_helpers: + self.data.append(( + ct_helper.id, + ct_helper.router_id, + ct_helper.helper, + ct_helper.protocol, + ct_helper.port, + )) + self.network.conntrack_helpers = mock.Mock( + return_value=ct_helpers) + + # Get the command object to test + self.cmd = l3_conntrack_helper.ListConntrackHelper(self.app, + self.namespace) + + def test_conntrack_helpers_list(self): + arglist = [ + self.router.id + ] + verifylist = [ + ('router', self.router.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.conntrack_helpers.assert_called_once_with( + self.router.id) + self.assertEqual(self.columns, columns) + list_data = list(data) + self.assertEqual(len(self.data), len(list_data)) + for index in range(len(list_data)): + self.assertEqual(self.data[index], list_data[index]) + + +class TestSetL3ConntrackHelper(TestConntrackHelper): + + def setUp(self): + super(TestSetL3ConntrackHelper, self).setUp() + attrs = {'router_id': self.router.id} + self.ct_helper = ( + network_fakes.FakeL3ConntrackHelper.create_one_l3_conntrack_helper( + attrs)) + self.network.update_conntrack_helper = mock.Mock(return_value=None) + + # Get the command object to test + self.cmd = l3_conntrack_helper.SetConntrackHelper(self.app, + self.namespace) + + def test_set_nothing(self): + arglist = [ + self.router.id, + self.ct_helper.id, + ] + verifylist = [ + ('router', self.router.id), + ('conntrack_helper_id', self.ct_helper.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = (self.cmd.take_action(parsed_args)) + + self.network.update_conntrack_helper.assert_called_once_with( + self.ct_helper.id, self.router.id + ) + self.assertIsNone(result) + + def test_set_port(self): + arglist = [ + self.router.id, + self.ct_helper.id, + '--port', '124', + ] + verifylist = [ + ('router', self.router.id), + ('conntrack_helper_id', self.ct_helper.id), + ('port', 124), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = (self.cmd.take_action(parsed_args)) + + self.network.update_conntrack_helper.assert_called_once_with( + self.ct_helper.id, self.router.id, port=124 + ) + self.assertIsNone(result) + + +class TestShowL3ConntrackHelper(TestConntrackHelper): + + def setUp(self): + super(TestShowL3ConntrackHelper, self).setUp() + attrs = {'router_id': self.router.id} + self.ct_helper = ( + network_fakes.FakeL3ConntrackHelper.create_one_l3_conntrack_helper( + attrs)) + self.columns = ( + 'helper', + 'id', + 'port', + 'protocol', + 'router_id' + ) + + self.data = ( + self.ct_helper.helper, + self.ct_helper.id, + self.ct_helper.port, + self.ct_helper.protocol, + self.ct_helper.router_id + ) + self.network.get_conntrack_helper = mock.Mock( + return_value=self.ct_helper) + + # Get the command object to test + self.cmd = l3_conntrack_helper.ShowConntrackHelper(self.app, + self.namespace) + + def test_show_no_options(self): + arglist = [] + verifylist = [] + + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_show_default_options(self): + arglist = [ + self.router.id, + self.ct_helper.id, + ] + verifylist = [ + ('router', self.router.id), + ('conntrack_helper_id', self.ct_helper.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.get_conntrack_helper.assert_called_once_with( + self.ct_helper.id, self.router.id + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) diff --git a/releasenotes/notes/L3-conntrack-helper-bd0d9da041747e84.yaml b/releasenotes/notes/L3-conntrack-helper-bd0d9da041747e84.yaml new file mode 100644 index 0000000000..de5348dc16 --- /dev/null +++ b/releasenotes/notes/L3-conntrack-helper-bd0d9da041747e84.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Add new commands ``network l3 conntrack helper create``, + ``network l3 conntrack helper set``, ``network l3 conntrack helper show``, + ``network l3 conntrack helper set`` and + ``network l3 conntrack helper delete`` to support Neutron L3 conntrack + helper CRUD operations. diff --git a/setup.cfg b/setup.cfg index d6d7a3d2b0..7084c70bf3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -448,6 +448,12 @@ openstack.network.v2 = network_show = openstackclient.network.v2.network:ShowNetwork network_unset = openstackclient.network.v2.network:UnsetNetwork + network_l3_conntrack_helper_create = openstackclient.network.v2.l3_conntrack_helper:CreateConntrackHelper + network_l3_conntrack_helper_delete = openstackclient.network.v2.l3_conntrack_helper:DeleteConntrackHelper + network_l3_conntrack_helper_list = openstackclient.network.v2.l3_conntrack_helper:ListConntrackHelper + network_l3_conntrack_helper_set = openstackclient.network.v2.l3_conntrack_helper:SetConntrackHelper + network_l3_conntrack_helper_show = openstackclient.network.v2.l3_conntrack_helper:ShowConntrackHelper + network_meter_create = openstackclient.network.v2.network_meter:CreateMeter network_meter_delete = openstackclient.network.v2.network_meter:DeleteMeter network_meter_list = openstackclient.network.v2.network_meter:ListMeter From 02d6fe9be629c28da2568d1ad51a5334064709bd Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Tue, 8 Jun 2021 15:28:06 +0900 Subject: [PATCH 2392/3095] L3 conntrack helper: Use singular name consistently We use singular form for delete command argument in all places. This commit replaces conntrack-helper-ids with a singular form. The only visible change is a fix for the help message below. openstack network l3 conntrack helper delete [ ...] Change-Id: I50bbd9f6199071bb86cbb2f37c45ebda1de58433 --- openstackclient/network/v2/l3_conntrack_helper.py | 8 ++++---- .../tests/unit/network/v2/test_l3_conntrack_helper.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/openstackclient/network/v2/l3_conntrack_helper.py b/openstackclient/network/v2/l3_conntrack_helper.py index dae259273f..94788823ab 100644 --- a/openstackclient/network/v2/l3_conntrack_helper.py +++ b/openstackclient/network/v2/l3_conntrack_helper.py @@ -99,8 +99,8 @@ def get_parser(self, prog_name): help=_('Router that the conntrack helper belong to') ) parser.add_argument( - 'conntrack_helper_ids', - metavar='', + 'conntrack_helper_id', + metavar='', nargs='+', help=_('The ID of the conntrack helper(s) to delete') ) @@ -112,7 +112,7 @@ def take_action(self, parsed_args): result = 0 router = client.find_router(parsed_args.router, ignore_missing=False) - for ct_helper in parsed_args.conntrack_helper_ids: + for ct_helper in parsed_args.conntrack_helper_id: try: client.delete_conntrack_helper( ct_helper, router.id, ignore_missing=False) @@ -123,7 +123,7 @@ def take_action(self, parsed_args): {'ct_helper': ct_helper, 'e': e}) if result > 0: - total = len(parsed_args.conntrack_helper_ids) + total = len(parsed_args.conntrack_helper_id) msg = (_("%(result)s of %(total)s L3 conntrack helpers failed " "to delete.") % {'result': result, 'total': total}) raise exceptions.CommandError(msg) diff --git a/openstackclient/tests/unit/network/v2/test_l3_conntrack_helper.py b/openstackclient/tests/unit/network/v2/test_l3_conntrack_helper.py index 1676c9ffad..b3d026a7ef 100644 --- a/openstackclient/tests/unit/network/v2/test_l3_conntrack_helper.py +++ b/openstackclient/tests/unit/network/v2/test_l3_conntrack_helper.py @@ -127,7 +127,7 @@ def test_delete(self): self.ct_helper.id ] verifylist = [ - ('conntrack_helper_ids', [self.ct_helper.id]), + ('conntrack_helper_id', [self.ct_helper.id]), ('router', self.ct_helper.router_id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -143,7 +143,7 @@ def test_delete_error(self): self.ct_helper.id ] verifylist = [ - ('conntrack_helper_ids', [self.ct_helper.id]), + ('conntrack_helper_id', [self.ct_helper.id]), ('router', self.router.id), ] self.network.delete_conntrack_helper.side_effect = Exception( From 34de2d3352aaef5c1bb86a5441cc8781e03b5587 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 3 Jun 2021 16:58:07 +0100 Subject: [PATCH 2393/3095] volume: Add 'volume group snapshot *' commands These mirror the 'cinder group-snapshot-*' commands, with arguments copied across essentially verbatim. The only significant departure is the replacement of "tenant" terminology with "project". volume group snapshot create volume group snapshot delete volume group snapshot list volume group snapshot show Change-Id: Ia5084749b7c1a5a936fd6d6e8d89b9b80969f68c Signed-off-by: Stephen Finucane --- .../command-objects/volume-group-snapshot.rst | 8 + doc/source/cli/commands.rst | 1 + doc/source/cli/data/cinder.csv | 8 +- openstackclient/tests/unit/volume/v3/fakes.py | 53 ++++ .../volume/v3/test_volume_group_snapshot.py | 262 ++++++++++++++++++ .../volume/v3/volume_group_snapshot.py | 234 ++++++++++++++++ ...up-snapshot-commands-27fa8920d55f6bdb.yaml | 6 + setup.cfg | 5 + 8 files changed, 573 insertions(+), 4 deletions(-) create mode 100644 doc/source/cli/command-objects/volume-group-snapshot.rst create mode 100644 openstackclient/tests/unit/volume/v3/test_volume_group_snapshot.py create mode 100644 openstackclient/volume/v3/volume_group_snapshot.py create mode 100644 releasenotes/notes/add-volume-group-snapshot-commands-27fa8920d55f6bdb.yaml diff --git a/doc/source/cli/command-objects/volume-group-snapshot.rst b/doc/source/cli/command-objects/volume-group-snapshot.rst new file mode 100644 index 0000000000..02a33c1e3c --- /dev/null +++ b/doc/source/cli/command-objects/volume-group-snapshot.rst @@ -0,0 +1,8 @@ +===================== +volume group snapshot +===================== + +Block Storage v3 + +.. autoprogram-cliff:: openstack.volume.v3 + :command: volume group snapshot * diff --git a/doc/source/cli/commands.rst b/doc/source/cli/commands.rst index ada38e3e57..7e59215271 100644 --- a/doc/source/cli/commands.rst +++ b/doc/source/cli/commands.rst @@ -160,6 +160,7 @@ referring to both Compute and Volume quotas. * ``volume backup record``: (**Volume**) volume record that can be imported or exported * ``volume backend``: (**Volume**) volume backend storage * ``volume group``: (**Volume**) group of volumes +* ``volume group snapshot``: (**Volume**) a point-in-time copy of a volume group * ``volume group type``: (**Volume**) deployment-specific types of volumes groups available * ``volume host``: (**Volume**) the physical computer for volumes * ``volume message``: (**Volume**) volume API internal messages detailing volume failure messages diff --git a/doc/source/cli/data/cinder.csv b/doc/source/cli/data/cinder.csv index 5de8ea5cdd..9d79d1ba97 100644 --- a/doc/source/cli/data/cinder.csv +++ b/doc/source/cli/data/cinder.csv @@ -53,10 +53,10 @@ group-failover-replication,volume group failover,Fails over replication for grou group-list,volume group list,Lists all groups. (Supported by API versions 3.13 - 3.latest) group-list-replication-targets,volume group list --replication-targets,Lists replication targets for group. (Supported by API versions 3.38 - 3.latest) group-show,volume group show,Shows details of a group. (Supported by API versions 3.13 - 3.latest) -group-snapshot-create,,Creates a group snapshot. (Supported by API versions 3.14 - 3.latest) -group-snapshot-delete,,Removes one or more group snapshots. (Supported by API versions 3.14 - 3.latest) -group-snapshot-list,,Lists all group snapshots. (Supported by API versions 3.14 - 3.latest) -group-snapshot-show,,Shows group snapshot details. (Supported by API versions 3.14 - 3.latest) +group-snapshot-create,volume group snapshot create,Creates a group snapshot. (Supported by API versions 3.14 - 3.latest) +group-snapshot-delete,volume group snapshot delete,Removes one or more group snapshots. (Supported by API versions 3.14 - 3.latest) +group-snapshot-list,volume group snapshot list,Lists all group snapshots. (Supported by API versions 3.14 - 3.latest) +group-snapshot-show,volume group snapshot show,Shows group snapshot details. (Supported by API versions 3.14 - 3.latest) group-specs-list,volume group type list,Lists current group types and specs. (Supported by API versions 3.11 - 3.latest) group-type-create,volume group type create,Creates a group type. (Supported by API versions 3.11 - 3.latest) group-type-default,volume group type list --default,List the default group type. (Supported by API versions 3.11 - 3.latest) diff --git a/openstackclient/tests/unit/volume/v3/fakes.py b/openstackclient/tests/unit/volume/v3/fakes.py index c300ca3855..9040b2be08 100644 --- a/openstackclient/tests/unit/volume/v3/fakes.py +++ b/openstackclient/tests/unit/volume/v3/fakes.py @@ -34,6 +34,8 @@ def __init__(self, **kwargs): self.attachments.resource_class = fakes.FakeResource(None, {}) self.groups = mock.Mock() self.groups.resource_class = fakes.FakeResource(None, {}) + self.group_snapshots = mock.Mock() + self.group_snapshots.resource_class = fakes.FakeResource(None, {}) self.group_types = mock.Mock() self.group_types.resource_class = fakes.FakeResource(None, {}) self.messages = mock.Mock() @@ -125,6 +127,57 @@ def create_volume_groups(attrs=None, count=2): return groups +class FakeVolumeGroupSnapshot: + """Fake one or more volume group snapshots.""" + + @staticmethod + def create_one_volume_group_snapshot(attrs=None, methods=None): + """Create a fake group snapshot. + + :param attrs: A dictionary with all attributes + :param methods: A dictionary with all methods + :return: A FakeResource object with id, name, description, etc. + """ + attrs = attrs or {} + + # Set default attribute + group_snapshot_info = { + 'id': uuid.uuid4().hex, + 'name': f'group-snapshot-{uuid.uuid4().hex}', + 'description': f'description-{uuid.uuid4().hex}', + 'status': random.choice(['available']), + 'group_id': uuid.uuid4().hex, + 'group_type_id': uuid.uuid4().hex, + 'project_id': uuid.uuid4().hex, + } + + # Overwrite default attributes if there are some attributes set + group_snapshot_info.update(attrs) + + group_snapshot = fakes.FakeResource( + None, + group_snapshot_info, + methods=methods, + loaded=True) + return group_snapshot + + @staticmethod + def create_volume_group_snapshots(attrs=None, count=2): + """Create multiple fake group snapshots. + + :param attrs: A dictionary with all attributes of group snapshot + :param count: The number of group snapshots to be faked + :return: A list of FakeResource objects + """ + group_snapshots = [] + for n in range(0, count): + group_snapshots.append( + FakeVolumeGroupSnapshot.create_one_volume_group_snapshot(attrs) + ) + + return group_snapshots + + class FakeVolumeGroupType: """Fake one or more volume group types.""" diff --git a/openstackclient/tests/unit/volume/v3/test_volume_group_snapshot.py b/openstackclient/tests/unit/volume/v3/test_volume_group_snapshot.py new file mode 100644 index 0000000000..509d9f08f2 --- /dev/null +++ b/openstackclient/tests/unit/volume/v3/test_volume_group_snapshot.py @@ -0,0 +1,262 @@ +# 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 cinderclient import api_versions +from osc_lib import exceptions + +from openstackclient.tests.unit.volume.v3 import fakes as volume_fakes +from openstackclient.volume.v3 import volume_group_snapshot + + +class TestVolumeGroupSnapshot(volume_fakes.TestVolume): + + def setUp(self): + super().setUp() + + self.volume_groups_mock = self.app.client_manager.volume.groups + self.volume_groups_mock.reset_mock() + + self.volume_group_snapshots_mock = \ + self.app.client_manager.volume.group_snapshots + self.volume_group_snapshots_mock.reset_mock() + + +class TestVolumeGroupSnapshotCreate(TestVolumeGroupSnapshot): + + fake_volume_group = volume_fakes.FakeVolumeGroup.create_one_volume_group() + fake_volume_group_snapshot = \ + volume_fakes.FakeVolumeGroupSnapshot.create_one_volume_group_snapshot() + + columns = ( + 'ID', + 'Status', + 'Name', + 'Description', + 'Group', + 'Group Type', + ) + data = ( + fake_volume_group_snapshot.id, + fake_volume_group_snapshot.status, + fake_volume_group_snapshot.name, + fake_volume_group_snapshot.description, + fake_volume_group_snapshot.group_id, + fake_volume_group_snapshot.group_type_id, + ) + + def setUp(self): + super().setUp() + + self.volume_groups_mock.get.return_value = self.fake_volume_group + self.volume_group_snapshots_mock.create.return_value = \ + self.fake_volume_group_snapshot + self.volume_group_snapshots_mock.get.return_value = \ + self.fake_volume_group_snapshot + + self.cmd = volume_group_snapshot.CreateVolumeGroupSnapshot( + self.app, None) + + def test_volume_group_snapshot_create(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.14') + + arglist = [ + self.fake_volume_group.id, + ] + verifylist = [ + ('volume_group', self.fake_volume_group.id), + ('name', None), + ('description', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volume_groups_mock.get.assert_called_once_with( + self.fake_volume_group.id) + self.volume_group_snapshots_mock.create.assert_called_once_with( + self.fake_volume_group.id, None, None, + ) + self.assertEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + + def test_volume_group_snapshot_create_with_options(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.14') + + arglist = [ + self.fake_volume_group.id, + '--name', 'foo', + '--description', 'hello, world', + ] + verifylist = [ + ('volume_group', self.fake_volume_group.id), + ('name', 'foo'), + ('description', 'hello, world'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volume_groups_mock.get.assert_called_once_with( + self.fake_volume_group.id) + self.volume_group_snapshots_mock.create.assert_called_once_with( + self.fake_volume_group.id, 'foo', 'hello, world', + ) + self.assertEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + + def test_volume_group_snapshot_create_pre_v314(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.13') + + arglist = [ + self.fake_volume_group.id, + ] + verifylist = [ + ('volume_group', self.fake_volume_group.id), + ('name', None), + ('description', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.14 or greater is required', + str(exc)) + + +class TestVolumeGroupSnapshotDelete(TestVolumeGroupSnapshot): + + fake_volume_group_snapshot = \ + volume_fakes.FakeVolumeGroupSnapshot.create_one_volume_group_snapshot() + + def setUp(self): + super().setUp() + + self.volume_group_snapshots_mock.get.return_value = \ + self.fake_volume_group_snapshot + self.volume_group_snapshots_mock.delete.return_value = None + + self.cmd = volume_group_snapshot.DeleteVolumeGroupSnapshot( + self.app, None) + + def test_volume_group_snapshot_delete(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.14') + + arglist = [ + self.fake_volume_group_snapshot.id, + ] + verifylist = [ + ('snapshot', self.fake_volume_group_snapshot.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.volume_group_snapshots_mock.delete.assert_called_once_with( + self.fake_volume_group_snapshot.id, + ) + self.assertIsNone(result) + + def test_volume_group_snapshot_delete_pre_v314(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.13') + + arglist = [ + self.fake_volume_group_snapshot.id, + ] + verifylist = [ + ('snapshot', self.fake_volume_group_snapshot.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.14 or greater is required', + str(exc)) + + +class TestVolumeGroupSnapshotList(TestVolumeGroupSnapshot): + + fake_volume_group_snapshots = \ + volume_fakes.FakeVolumeGroupSnapshot.create_volume_group_snapshots() + + columns = ( + 'ID', + 'Status', + 'Name', + ) + data = [ + ( + fake_volume_group_snapshot.id, + fake_volume_group_snapshot.status, + fake_volume_group_snapshot.name, + ) for fake_volume_group_snapshot in fake_volume_group_snapshots + ] + + def setUp(self): + super().setUp() + + self.volume_group_snapshots_mock.list.return_value = \ + self.fake_volume_group_snapshots + + self.cmd = volume_group_snapshot.ListVolumeGroupSnapshot( + self.app, None) + + def test_volume_group_snapshot_list(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.14') + + arglist = [ + '--all-projects', + ] + verifylist = [ + ('all_projects', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volume_group_snapshots_mock.list.assert_called_once_with( + search_opts={ + 'all_tenants': True, + }, + ) + self.assertEqual(self.columns, columns) + self.assertCountEqual(tuple(self.data), data) + + def test_volume_group_snapshot_list_pre_v314(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.13') + + arglist = [ + ] + verifylist = [ + ('all_projects', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.14 or greater is required', + str(exc)) diff --git a/openstackclient/volume/v3/volume_group_snapshot.py b/openstackclient/volume/v3/volume_group_snapshot.py new file mode 100644 index 0000000000..229cbd713c --- /dev/null +++ b/openstackclient/volume/v3/volume_group_snapshot.py @@ -0,0 +1,234 @@ +# 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 logging + +from cinderclient import api_versions +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils + +from openstackclient.i18n import _ + +LOG = logging.getLogger(__name__) + + +def _format_group_snapshot(snapshot): + columns = ( + 'id', + 'status', + 'name', + 'description', + 'group_id', + 'group_type_id', + ) + column_headers = ( + 'ID', + 'Status', + 'Name', + 'Description', + 'Group', + 'Group Type', + ) + + return ( + column_headers, + utils.get_item_properties( + snapshot, + columns, + ), + ) + + +class CreateVolumeGroupSnapshot(command.ShowOne): + """Create a volume group snapshot. + + This command requires ``--os-volume-api-version`` 3.13 or greater. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'volume_group', + metavar='', + help=_('Name or ID of volume group to create a snapshot of.'), + ) + parser.add_argument( + '--name', + metavar='', + help=_('Name of the volume group snapshot.'), + ) + parser.add_argument( + '--description', + metavar='', + help=_('Description of a volume group snapshot.') + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.14'): + msg = _( + "--os-volume-api-version 3.14 or greater is required to " + "support the 'volume group snapshot create' command" + ) + raise exceptions.CommandError(msg) + + volume_group = utils.find_resource( + volume_client.groups, + parsed_args.volume_group, + ) + + snapshot = volume_client.group_snapshots.create( + volume_group.id, + parsed_args.name, + parsed_args.description) + + return _format_group_snapshot(snapshot) + + +class DeleteVolumeGroupSnapshot(command.Command): + """Delete a volume group snapshot. + + This command requires ``--os-volume-api-version`` 3.14 or greater. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'snapshot', + metavar='', + help=_('Name or ID of volume group snapshot to delete'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.14'): + msg = _( + "--os-volume-api-version 3.14 or greater is required to " + "support the 'volume group snapshot delete' command" + ) + raise exceptions.CommandError(msg) + + snapshot = utils.find_resource( + volume_client.group_snapshots, + parsed_args.snapshot, + ) + + volume_client.group_snapshots.delete(snapshot.id) + + +class ListVolumeGroupSnapshot(command.Lister): + """Lists all volume group snapshot. + + This command requires ``--os-volume-api-version`` 3.14 or greater. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + '--all-projects', + dest='all_projects', + action='store_true', + default=utils.env('ALL_PROJECTS', default=False), + help=_('Shows details for all projects (admin only).'), + ) + # TODO(stephenfin): Add once we have an equivalent command for + # 'cinder list-filters' + # parser.add_argument( + # '--filter', + # metavar='', + # action=parseractions.KeyValueAction, + # dest='filters', + # help=_( + # "Filter key and value pairs. Use 'foo' to " + # "check enabled filters from server. Use 'key~=value' for " + # "inexact filtering if the key supports " + # "(supported by --os-volume-api-version 3.33 or above)" + # ), + # ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.14'): + msg = _( + "--os-volume-api-version 3.14 or greater is required to " + "support the 'volume group snapshot list' command" + ) + raise exceptions.CommandError(msg) + + search_opts = { + 'all_tenants': parsed_args.all_projects, + } + + groups = volume_client.group_snapshots.list( + search_opts=search_opts) + + column_headers = ( + 'ID', + 'Status', + 'Name', + ) + columns = ( + 'id', + 'status', + 'name', + ) + + return ( + column_headers, + ( + utils.get_item_properties(a, columns) + for a in groups + ), + ) + + +class ShowVolumeGroupSnapshot(command.ShowOne): + """Show detailed information for a volume group snapshot. + + This command requires ``--os-volume-api-version`` 3.14 or greater. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'snapshot', + metavar='', + help=_('Name or ID of volume group snapshot.'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.14'): + msg = _( + "--os-volume-api-version 3.14 or greater is required to " + "support the 'volume group snapshot show' command" + ) + raise exceptions.CommandError(msg) + + snapshot = utils.find_resource( + volume_client.group_snapshots, + parsed_args.snapshot, + ) + + # TODO(stephenfin): Do we need this? + snapshot = volume_client.groups.show(snapshot.id) + + return _format_group_snapshot(snapshot) diff --git a/releasenotes/notes/add-volume-group-snapshot-commands-27fa8920d55f6bdb.yaml b/releasenotes/notes/add-volume-group-snapshot-commands-27fa8920d55f6bdb.yaml new file mode 100644 index 0000000000..820faf7361 --- /dev/null +++ b/releasenotes/notes/add-volume-group-snapshot-commands-27fa8920d55f6bdb.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``volume group snapshot create``, ``volume group snapshot delete``, + ``volume group snapshot list`` and ``volume group snapshot show`` commands + to create, delete, list, and show volume group snapshots, respectively. diff --git a/setup.cfg b/setup.cfg index 2a01ae7b0d..761736998a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -722,6 +722,11 @@ openstack.volume.v3 = volume_group_unset = openstackclient.volume.v3.volume_group:UnsetVolumeGroup volume_group_show = openstackclient.volume.v3.volume_group:ShowVolumeGroup + volume_group_snapshot_create = openstackclient.volume.v3.volume_group_snapshot:CreateVolumeGroupSnapshot + volume_group_snapshot_delete = openstackclient.volume.v3.volume_group_snapshot:DeleteVolumeGroupSnapshot + volume_group_snapshot_list = openstackclient.volume.v3.volume_group_snapshot:ListVolumeGroupSnapshot + volume_group_snapshot_show = openstackclient.volume.v3.volume_group_snapshot:ShowVolumeGroupSnapshot + volume_group_type_create = openstackclient.volume.v3.volume_group_type:CreateVolumeGroupType volume_group_type_delete = openstackclient.volume.v3.volume_group_type:DeleteVolumeGroupType volume_group_type_list = openstackclient.volume.v3.volume_group_type:ListVolumeGroupType From 7f66dfe0e3e8720e847211494c185d7d4983ba5b Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 9 Jun 2021 11:57:32 +0100 Subject: [PATCH 2394/3095] volume: Add more missing 'volume backup *' options Add an additional '--no-property' option to the 'volume backup set' command, along with a brand spanking new 'volume backup unset' command. Change-Id: Id7ca925e0ada03e259f0ecaf3e02af11c900641e Signed-off-by: Stephen Finucane --- openstackclient/tests/unit/volume/v2/fakes.py | 3 + .../unit/volume/v2/test_volume_backup.py | 157 +++++++++++++++++- openstackclient/volume/v2/volume_backup.py | 87 +++++++++- ...g-volume-backup-opts-b9246aded87427ce.yaml | 8 +- setup.cfg | 1 + 5 files changed, 251 insertions(+), 5 deletions(-) diff --git a/openstackclient/tests/unit/volume/v2/fakes.py b/openstackclient/tests/unit/volume/v2/fakes.py index 86778698da..b5f66d4b1d 100644 --- a/openstackclient/tests/unit/volume/v2/fakes.py +++ b/openstackclient/tests/unit/volume/v2/fakes.py @@ -17,6 +17,7 @@ from unittest import mock import uuid +from cinderclient import api_versions from osc_lib.cli import format_columns from openstackclient.tests.unit import fakes @@ -292,6 +293,8 @@ class FakeVolumeClient(object): def __init__(self, **kwargs): self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] + self.api_version = api_versions.APIVersion('2.0') + self.availability_zones = mock.Mock() self.availability_zones.resource_class = fakes.FakeResource(None, {}) self.backups = mock.Mock() diff --git a/openstackclient/tests/unit/volume/v2/test_volume_backup.py b/openstackclient/tests/unit/volume/v2/test_volume_backup.py index 7b5a965e09..5d57bf609b 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume_backup.py +++ b/openstackclient/tests/unit/volume/v2/test_volume_backup.py @@ -490,7 +490,9 @@ def test_backup_restore(self): class TestBackupSet(TestBackup): - backup = volume_fakes.FakeBackup.create_one_backup() + backup = volume_fakes.FakeBackup.create_one_backup( + attrs={'metadata': {'wow': 'cool'}}, + ) def setUp(self): super(TestBackupSet, self).setUp() @@ -627,6 +629,159 @@ def test_backup_set_state_failed(self): self.backups_mock.reset_state.assert_called_with( self.backup.id, 'error') + def test_backup_set_no_property(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.43') + + arglist = [ + '--no-property', + self.backup.id, + ] + verifylist = [ + ('no_property', True), + ('backup', self.backup.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'metadata': {}, + } + self.backups_mock.update.assert_called_once_with( + self.backup.id, + **kwargs + ) + self.assertIsNone(result) + + def test_backup_set_no_property_pre_v343(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.42') + + arglist = [ + '--no-property', + self.backup.id, + ] + verifylist = [ + ('no_property', True), + ('backup', self.backup.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn("--os-volume-api-version 3.43 or greater", str(exc)) + + def test_backup_set_property(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.43') + + arglist = [ + '--property', 'foo=bar', + self.backup.id, + ] + verifylist = [ + ('properties', {'foo': 'bar'}), + ('backup', self.backup.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'metadata': {'wow': 'cool', 'foo': 'bar'}, + } + self.backups_mock.update.assert_called_once_with( + self.backup.id, + **kwargs + ) + self.assertIsNone(result) + + def test_backup_set_property_pre_v343(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.42') + + arglist = [ + '--property', 'foo=bar', + self.backup.id, + ] + verifylist = [ + ('properties', {'foo': 'bar'}), + ('backup', self.backup.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn("--os-volume-api-version 3.43 or greater", str(exc)) + + +class TestBackupUnset(TestBackup): + + backup = volume_fakes.FakeBackup.create_one_backup( + attrs={'metadata': {'foo': 'bar'}}, + ) + + def setUp(self): + super().setUp() + + self.backups_mock.get.return_value = self.backup + + # Get the command object to test + self.cmd = volume_backup.UnsetVolumeBackup(self.app, None) + + def test_backup_unset_property(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.43') + + arglist = [ + '--property', 'foo', + self.backup.id, + ] + verifylist = [ + ('properties', ['foo']), + ('backup', self.backup.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'metadata': {}, + } + self.backups_mock.update.assert_called_once_with( + self.backup.id, + **kwargs + ) + self.assertIsNone(result) + + def test_backup_unset_property_pre_v343(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.42') + + arglist = [ + '--property', 'foo', + self.backup.id, + ] + verifylist = [ + ('properties', ['foo']), + ('backup', self.backup.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn("--os-volume-api-version 3.43 or greater", str(exc)) + class TestBackupShow(TestBackup): diff --git a/openstackclient/volume/v2/volume_backup.py b/openstackclient/volume/v2/volume_backup.py index a171164e3a..96b22a681c 100644 --- a/openstackclient/volume/v2/volume_backup.py +++ b/openstackclient/volume/v2/volume_backup.py @@ -14,6 +14,7 @@ """Volume v2 Backup action implementations""" +import copy import functools import logging @@ -405,11 +406,21 @@ def get_parser(self, prog_name): 'exercise caution when using)' ), ) + parser.add_argument( + '--no-property', + action='store_true', + help=_( + 'Remove all properties from this backup ' + '(specify both --no-property and --property to remove the ' + 'current properties before setting new properties)' + ), + ) parser.add_argument( '--property', metavar='', action=parseractions.KeyValueAction, dest='properties', + default={}, help=_( 'Set a property on this backup ' '(repeat option to set multiple values) ' @@ -454,6 +465,14 @@ def take_action(self, parsed_args): kwargs['description'] = parsed_args.description + if parsed_args.no_property: + if volume_client.api_version < api_versions.APIVersion('3.43'): + msg = _( + '--os-volume-api-version 3.43 or greater is required to ' + 'support the --no-property option' + ) + raise exceptions.CommandError(msg) + if parsed_args.properties: if volume_client.api_version < api_versions.APIVersion('3.43'): msg = _( @@ -462,13 +481,20 @@ def take_action(self, parsed_args): ) raise exceptions.CommandError(msg) - kwargs['metadata'] = parsed_args.properties + if volume_client.api_version >= api_versions.APIVersion('3.43'): + metadata = copy.deepcopy(backup.metadata) + + if parsed_args.no_property: + metadata = {} + + metadata.update(parsed_args.properties) + kwargs['metadata'] = metadata if kwargs: try: volume_client.backups.update(backup.id, **kwargs) except Exception as e: - LOG.error("Failed to update backup name or description: %s", e) + LOG.error("Failed to update backup: %s", e) result += 1 if result > 0: @@ -476,6 +502,63 @@ def take_action(self, parsed_args): raise exceptions.CommandError(msg) +class UnsetVolumeBackup(command.Command): + """Unset volume backup properties. + + This command requires ``--os-volume-api-version`` 3.43 or greater. + """ + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'backup', + metavar='', + help=_('Backup to modify (name or ID)') + ) + parser.add_argument( + '--property', + metavar='', + action='append', + dest='properties', + help=_( + 'Property to remove from this backup ' + '(repeat option to unset multiple values) ' + ), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + + if volume_client.api_version < api_versions.APIVersion('3.43'): + msg = _( + '--os-volume-api-version 3.43 or greater is required to ' + 'support the --property option' + ) + raise exceptions.CommandError(msg) + + backup = utils.find_resource( + volume_client.backups, parsed_args.backup) + metadata = copy.deepcopy(backup.metadata) + + for key in parsed_args.properties: + if key not in metadata: + # ignore invalid properties but continue + LOG.warning( + "'%s' is not a valid property for backup '%s'", + key, parsed_args.backup, + ) + continue + + del metadata[key] + + kwargs = { + 'metadata': metadata, + } + + volume_client.backups.update(backup.id, **kwargs) + + class ShowVolumeBackup(command.ShowOne): _description = _("Display volume backup details") diff --git a/releasenotes/notes/add-missing-volume-backup-opts-b9246aded87427ce.yaml b/releasenotes/notes/add-missing-volume-backup-opts-b9246aded87427ce.yaml index 883cb0d564..f3b8bbc389 100644 --- a/releasenotes/notes/add-missing-volume-backup-opts-b9246aded87427ce.yaml +++ b/releasenotes/notes/add-missing-volume-backup-opts-b9246aded87427ce.yaml @@ -6,8 +6,12 @@ features: non-incremental backup, set a metadata property on the created backup, and set an availability zone on the created backup, respectively. - | - Add ``--property`` option the ``volume backup set`` command to set a - metadata property on an existing backup. + Add ``--property`` and ``--no-property`` options to the + ``volume backup set`` command to set a metadata property or remove all + metadata properties from an existing backup. + - | + Add new ``volume backup unset`` command to allow unsetting of properties + from an existing volume backup. fixes: - | The ``--name`` and ``--description`` options of the ``volume backup set`` diff --git a/setup.cfg b/setup.cfg index 761736998a..a46b7edb1c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -709,6 +709,7 @@ openstack.volume.v3 = volume_backup_list = openstackclient.volume.v2.volume_backup:ListVolumeBackup volume_backup_restore = openstackclient.volume.v2.volume_backup:RestoreVolumeBackup volume_backup_set = openstackclient.volume.v2.volume_backup:SetVolumeBackup + volume_backup_unset = openstackclient.volume.v2.volume_backup:UnsetVolumeBackup volume_backup_show = openstackclient.volume.v2.volume_backup:ShowVolumeBackup volume_backup_record_export = openstackclient.volume.v2.backup_record:ExportBackupRecord From 280b14abcddf3a308a819771117dd6b72d802642 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 16 Jun 2021 16:19:08 +0100 Subject: [PATCH 2395/3095] compute: Note that '--password' is deployment-specific Password injection requires either hypervisor-support or an agent running in the guest that will talk to the metadata service. It can be disabled for a deployment using the '[api] enable_instance_password' nova config option. Indicate this, albeit briefly. Change-Id: Ief94ea07fc7ab6a487af972e8759ca6704d8f085 Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 468c6b1b3e..272233917a 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1012,7 +1012,10 @@ def get_parser(self, prog_name): parser.add_argument( '--password', metavar='', - help=_("Set the password to this server"), + help=_( + 'Set the password to this server. ' + 'This option requires cloud support.' + ), ) parser.add_argument( '--security-group', @@ -3142,7 +3145,10 @@ def get_parser(self, prog_name): parser.add_argument( '--password', metavar='', - help=_('Set the password on the rebuilt server'), + help=_( + 'Set the password on the rebuilt server. ' + 'This option requires cloud support.' + ), ) parser.add_argument( '--property', @@ -3435,7 +3441,8 @@ def get_parser(self, prog_name): '--password', metavar='', default=None, help=_( 'Set the password on the evacuated instance. This option is ' - 'mutually exclusive with the --shared-storage option' + 'mutually exclusive with the --shared-storage option. ' + 'This option requires cloud support.' ), ) shared_storage_group.add_argument( @@ -3725,7 +3732,10 @@ def get_parser(self, prog_name): parser.add_argument( '--password', metavar='', - help=_("Set the password on the rescued instance"), + help=_( + 'Set the password on the rescued instance. ' + 'This option requires cloud support.' + ), ) return parser @@ -3992,7 +4002,10 @@ def get_parser(self, prog_name): password_group = parser.add_mutually_exclusive_group() password_group.add_argument( '--password', - help=_('Set the server password'), + help=_( + 'Set the server password. ' + 'This option requires cloud support.' + ), ) password_group.add_argument( '--no-password', From 13de3494115c1c460613b9792d1a4186234a25be Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 16 Jun 2021 18:09:32 +0100 Subject: [PATCH 2396/3095] compute: Better help text for 'openstack server set --state' Manually changing the server state is a potentially dangerous operation that should only be done under limited circumstances. It's also an admin-only operation by default. Highlight both points. Change-Id: Ifd8aec94937764202131ba8caf6b507caa76d7e9 Signed-off-by: Stephen Finucane Story: 2008549 Task: 41672 --- openstackclient/compute/v2/server.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 468c6b1b3e..ef0f714915 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -4023,7 +4023,12 @@ def get_parser(self, prog_name): '--state', metavar='', choices=['active', 'error'], - help=_('New server state (valid value: active, error)'), + help=_( + 'New server state ' + '**WARNING** This can result in instances that are no longer ' + 'usable and should be used with caution ' + '(admin only)' + ), ) parser.add_argument( '--description', From 98979cfc7f27e323e576af58b5b5d9c0594a8a70 Mon Sep 17 00:00:00 2001 From: Jeremy Stanley Date: Thu, 17 Jun 2021 17:07:39 +0000 Subject: [PATCH 2397/3095] Correct the tox option for skipping sdist generation The tox option to skip source distribution building is skipsdist, but this seems to be often misspelled skipdist instead, which gets silently ignored and so does not take effect. Correct it everywhere, in hopes that new projects will finally stop copying this mistake around. See https://tox.readthedocs.io/en/latest/config.html#conf-skipsdist and https://github.com/tox-dev/tox/issues/1388 for details. Change-Id: I05c1cc0c2fbf77021cc1e05bc96bee03528c69f0 --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index ebcdc2d5d9..c43e9d75ad 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,7 @@ [tox] minversion = 3.2.0 envlist = py38,pep8 -skipdist = True +skipsdist = True # Automatic envs (pyXX) will only use the python version appropriate to that # env and ignore basepython inherited from [testenv] if we set # ignore_basepython_conflict. From af406f33e38a8e9a5fb3c58e34f0d55c1d4d1d74 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Tue, 22 Jun 2021 18:25:41 +0100 Subject: [PATCH 2398/3095] cinder: Remove redundant command There is no 'volume group unset' command nor any need for one right now. This was mistakenly added in I3b2c0cb92b8a53cc1c0cefa3313b80f59c9e5835. Change-Id: I9386d1350099b10659c6b0e632e4d83cae5b2bfd Signed-off-by: Stephen Finucane --- .../notes/add-volume-group-commands-b121d6ec7da9779a.yaml | 2 +- setup.cfg | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/releasenotes/notes/add-volume-group-commands-b121d6ec7da9779a.yaml b/releasenotes/notes/add-volume-group-commands-b121d6ec7da9779a.yaml index 8b3fe7ecc4..bd99818a0f 100644 --- a/releasenotes/notes/add-volume-group-commands-b121d6ec7da9779a.yaml +++ b/releasenotes/notes/add-volume-group-commands-b121d6ec7da9779a.yaml @@ -3,6 +3,6 @@ features: - | Add ``volume group create``, ``volume group delete``, ``volume group list``, ``volume group failover``, - ``volume group set/unset`` and ``volume attachment show`` + ``volume group set`` and ``volume attachment show`` commands to create, delete, list, failover, update and show volume groups, respectively. diff --git a/setup.cfg b/setup.cfg index b39061622b..cb15203803 100644 --- a/setup.cfg +++ b/setup.cfg @@ -726,7 +726,6 @@ openstack.volume.v3 = volume_group_list = openstackclient.volume.v3.volume_group:ListVolumeGroup volume_group_failover = openstackclient.volume.v3.volume_group:FailoverVolumeGroup volume_group_set = openstackclient.volume.v3.volume_group:SetVolumeGroup - volume_group_unset = openstackclient.volume.v3.volume_group:UnsetVolumeGroup volume_group_show = openstackclient.volume.v3.volume_group:ShowVolumeGroup volume_group_snapshot_create = openstackclient.volume.v3.volume_group_snapshot:CreateVolumeGroupSnapshot From 4891bb38208fdcd1a2ae60e47b056841e14fbdf7 Mon Sep 17 00:00:00 2001 From: Ghanshyam Mann Date: Wed, 7 Jul 2021 19:43:00 -0500 Subject: [PATCH 2399/3095] Moving IRC network reference to OFTC Change-Id: I11e00f18fa8dca02bc0f136c0c5e9a2f040eef8f --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 7dfabd8456..7f31bcdbea 100644 --- a/README.rst +++ b/README.rst @@ -30,7 +30,7 @@ language to describe operations in OpenStack. * `Developer`_ - getting started as a developer * `Contributing`_ - contributing code * `Testing`_ - testing code -* IRC: #openstack-sdks on Freenode (irc.freenode.net) +* IRC: #openstack-sdks on OFTC (irc.oftc.net) * License: Apache 2.0 .. _PyPi: https://pypi.org/project/python-openstackclient From a821d6b7c57c7684a990ee39b6b93d5085f25a70 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Tue, 13 Jul 2021 20:29:43 +0100 Subject: [PATCH 2400/3095] volume: Add 'volume transfer request create --(no-)snapshots' option This closes a gap with cinderclient's 'transfer-create' command. Change-Id: I7386a7be15c0e3ee87abbcfc2275ba8524c10ff8 Signed-off-by: Stephen Finucane Story: 2009054 Task: 42831 --- ...est.py => test_volume_transfer_request.py} | 46 +++++++++++++++++++ .../volume/v2/volume_transfer_request.py | 36 +++++++++++++++ ...reate-snapshots-opts-1361416d37021e89.yaml | 6 +++ 3 files changed, 88 insertions(+) rename openstackclient/tests/unit/volume/v2/{test_transfer_request.py => test_volume_transfer_request.py} (89%) create mode 100644 releasenotes/notes/add-volume-transfer-request-create-snapshots-opts-1361416d37021e89.yaml diff --git a/openstackclient/tests/unit/volume/v2/test_transfer_request.py b/openstackclient/tests/unit/volume/v2/test_volume_transfer_request.py similarity index 89% rename from openstackclient/tests/unit/volume/v2/test_transfer_request.py rename to openstackclient/tests/unit/volume/v2/test_volume_transfer_request.py index c9dce3cab0..1a1f220ff6 100644 --- a/openstackclient/tests/unit/volume/v2/test_transfer_request.py +++ b/openstackclient/tests/unit/volume/v2/test_volume_transfer_request.py @@ -15,6 +15,7 @@ from unittest import mock from unittest.mock import call +from cinderclient import api_versions from osc_lib import exceptions from osc_lib import utils @@ -172,6 +173,51 @@ def test_transfer_create_with_name(self): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_transfer_create_with_no_snapshots(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.55') + + arglist = [ + '--no-snapshots', + self.volume.id, + ] + verifylist = [ + ('name', None), + ('snapshots', False), + ('volume', self.volume.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.transfer_mock.create.assert_called_once_with( + self.volume.id, None, no_snapshots=True) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_transfer_create_pre_v355(self): + self.app.client_manager.volume.api_version = \ + api_versions.APIVersion('3.54') + + arglist = [ + '--no-snapshots', + self.volume.id, + ] + verifylist = [ + ('name', None), + ('snapshots', False), + ('volume', self.volume.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-volume-api-version 3.55 or greater is required', + str(exc)) + class TestTransferDelete(TestTransfer): diff --git a/openstackclient/volume/v2/volume_transfer_request.py b/openstackclient/volume/v2/volume_transfer_request.py index 2a1ace1f42..8919933609 100644 --- a/openstackclient/volume/v2/volume_transfer_request.py +++ b/openstackclient/volume/v2/volume_transfer_request.py @@ -16,6 +16,7 @@ import logging +from cinderclient import api_versions from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -76,6 +77,25 @@ def get_parser(self, prog_name): metavar="", help=_('New transfer request name (default to None)'), ) + parser.add_argument( + '--snapshots', + action='store_true', + dest='snapshots', + help=_( + 'Allow transfer volumes without snapshots (default) ' + '(supported by --os-volume-api-version 3.55 or later)' + ), + default=None, + ) + parser.add_argument( + '--no-snapshots', + action='store_false', + dest='snapshots', + help=_( + 'Disallow transfer volumes without snapshots ' + '(supported by --os-volume-api-version 3.55 or later)' + ), + ) parser.add_argument( 'volume', metavar="", @@ -85,6 +105,21 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): volume_client = self.app.client_manager.volume + + kwargs = {} + + if parsed_args.snapshots is not None: + if volume_client.api_version < api_versions.APIVersion('3.55'): + msg = _( + "--os-volume-api-version 3.55 or greater is required to " + "support the '--(no-)snapshots' option" + ) + raise exceptions.CommandError(msg) + + # unfortunately this option is negative so we have to reverse + # things + kwargs['no_snapshots'] = not parsed_args.snapshots + volume_id = utils.find_resource( volume_client.volumes, parsed_args.volume, @@ -92,6 +127,7 @@ def take_action(self, parsed_args): volume_transfer_request = volume_client.transfers.create( volume_id, parsed_args.name, + **kwargs, ) volume_transfer_request._info.pop("links", None) diff --git a/releasenotes/notes/add-volume-transfer-request-create-snapshots-opts-1361416d37021e89.yaml b/releasenotes/notes/add-volume-transfer-request-create-snapshots-opts-1361416d37021e89.yaml new file mode 100644 index 0000000000..f915f87c16 --- /dev/null +++ b/releasenotes/notes/add-volume-transfer-request-create-snapshots-opts-1361416d37021e89.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + The ``volume transfer request create`` command now accepts the + ``--snapshots`` / ``--no-snapshots`` option to configure whether to + create a transfer request for a volume without snapshots or not. From c1209601b4f4b81690a186e51aa819c783367fae Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Fri, 23 Jul 2021 12:48:23 +0100 Subject: [PATCH 2401/3095] tests: Handle removal of block-storage v2 API Cinder recently removed their v2 API [1] which is causing the functional tests to fail. Improve our 'is_service_enabled' test helper to use the 'versions show' command, which queries the service catalog and can give us information about the service version as well as answer the more general "is this service available" question. We also resolve a long-standing TODO in the process. [1] https://review.opendev.org/c/openstack/cinder/+/792299 Change-Id: I381069357aa008344e15327adf3a863c0c2e1f04 Signed-off-by: Stephen Finucane --- openstackclient/tests/functional/base.py | 29 ++++++++++++------- .../tests/functional/volume/v1/common.py | 23 ++++----------- .../tests/functional/volume/v2/common.py | 11 ++++++- .../tests/functional/volume/v3/common.py | 11 ++++++- 4 files changed, 44 insertions(+), 30 deletions(-) diff --git a/openstackclient/tests/functional/base.py b/openstackclient/tests/functional/base.py index 3542a82756..0ed7dff8c4 100644 --- a/openstackclient/tests/functional/base.py +++ b/openstackclient/tests/functional/base.py @@ -68,17 +68,24 @@ def openstack(cls, cmd, cloud=ADMIN_CLOUD, fail_ok=False): ) @classmethod - def is_service_enabled(cls, service): - """Ask client cloud if service is available""" - cmd = ('service show -f value -c enabled {service}' - .format(service=service)) - try: - return "True" in cls.openstack(cmd) - except exceptions.CommandFailed as e: - if "No service with a type, name or ID of" in str(e): - return False - else: - raise # Unable to determine if service is enabled + def is_service_enabled(cls, service, version=None): + """Ask client cloud if service is available + + :param service: The service name or type. This should be either an + exact match to what is in the catalog or a known official value or + alias from service-types-authority + :param version: Optional version. This should be a major version, e.g. + '2.0' + :returns: True if the service is enabled and optionally provides the + specified API version, else False + """ + ret = cls.openstack( + f'versions show --service {service} -f value -c Version' + ).splitlines() + if version: + return version in ret + + return bool(ret) @classmethod def is_extension_enabled(cls, alias): diff --git a/openstackclient/tests/functional/volume/v1/common.py b/openstackclient/tests/functional/volume/v1/common.py index 04eb1f48ae..755874785d 100644 --- a/openstackclient/tests/functional/volume/v1/common.py +++ b/openstackclient/tests/functional/volume/v1/common.py @@ -20,25 +20,14 @@ class BaseVolumeTests(volume_base.BaseVolumeTests): @classmethod def setUpClass(cls): - super(BaseVolumeTests, cls).setUpClass() - # TODO(dtroyer): This needs to be updated to specifically check for - # Volume v1 rather than just 'volume', but for now - # that is enough until we get proper version negotiation - cls.haz_volume_v1 = cls.is_service_enabled('volume') + super().setUpClass() + cls.haz_volume_v1 = cls.is_service_enabled('block-storage', '1.0') def setUp(self): - super(BaseVolumeTests, self).setUp() - - # This class requires Volume v1 - # if not self.haz_volume_v1: - # self.skipTest("No Volume v1 service present") - - # TODO(dtroyer): We really want the above to work but right now - # (12Sep2017) DevStack still creates a 'volume' - # service type even though there is no service behind - # it. Until that is fixed we need to just skip the - # volume v1 functional tests in master. - self.skipTest("No Volume v1 service present") + super().setUp() + + if not self.haz_volume_v1: + self.skipTest("No Volume v1 service present") ver_fixture = fixtures.EnvironmentVariable( 'OS_VOLUME_API_VERSION', '1' diff --git a/openstackclient/tests/functional/volume/v2/common.py b/openstackclient/tests/functional/volume/v2/common.py index 3817671425..7e3a80845a 100644 --- a/openstackclient/tests/functional/volume/v2/common.py +++ b/openstackclient/tests/functional/volume/v2/common.py @@ -18,8 +18,17 @@ class BaseVolumeTests(base.BaseVolumeTests): """Base class for Volume functional tests. """ + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.haz_volume_v2 = cls.is_service_enabled('block-storage', '2.0') + def setUp(self): - super(BaseVolumeTests, self).setUp() + super().setUp() + + if not self.haz_volume_v2: + self.skipTest("No Volume v2 service present") + ver_fixture = fixtures.EnvironmentVariable( 'OS_VOLUME_API_VERSION', '2' ) diff --git a/openstackclient/tests/functional/volume/v3/common.py b/openstackclient/tests/functional/volume/v3/common.py index a710a6835c..29f769b640 100644 --- a/openstackclient/tests/functional/volume/v3/common.py +++ b/openstackclient/tests/functional/volume/v3/common.py @@ -18,8 +18,17 @@ class BaseVolumeTests(base.BaseVolumeTests): """Base class for Volume functional tests. """ + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.haz_volume_v3 = cls.is_service_enabled('block-storage', '3.0') + def setUp(self): - super(BaseVolumeTests, self).setUp() + super().setUp() + + if not self.haz_volume_v3: + self.skipTest("No Volume v3 service present") + ver_fixture = fixtures.EnvironmentVariable( 'OS_VOLUME_API_VERSION', '3' ) From 4f6fe1c0fd0ee5be3cc78961fd334aac0bacd57b Mon Sep 17 00:00:00 2001 From: melanie witt Date: Tue, 27 Jul 2021 02:10:20 +0000 Subject: [PATCH 2402/3095] Fix TestListMigrationV223 test class MIGRATION_COLUMNS Currently only the test_server_migration_list adds the 'Id' and 'Type' columns to the expected output, so if the test_server_migration_list_no_options test is run by itself, it fails as the actual response contains 'Id' and 'Type' but the reference does not. This example run fails: tox -epy38 test_server_migration_list_no_options The reason the tests pass in the gate is because test_server_migration_list (which adds the 'Id' and 'Type' columns to self.MIGRATION_COLUMNS) appears to always run before test_server_migration_list_no_options, so the latter test gets the benefit of the former test's column additions. This changes the test class to just include the 'Id' and 'Type' columns all the time as they are always returned in microversion 2.23 anyway. Story: 2009079 Task: 42891 Change-Id: I2c97e9f64790b5e978e4d04230d45b8e343b53d4 --- openstackclient/tests/unit/compute/v2/test_server.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index c6dff5a8a2..42c8816bcf 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -4977,9 +4977,9 @@ class TestListMigrationV223(TestListMigration): """Test fetch all migrations. """ MIGRATION_COLUMNS = [ - 'Source Node', 'Dest Node', 'Source Compute', - 'Dest Compute', 'Dest Host', 'Status', 'Server UUID', - 'Old Flavor', 'New Flavor', 'Created At', 'Updated At' + 'Id', 'Source Node', 'Dest Node', 'Source Compute', 'Dest Compute', + 'Dest Host', 'Status', 'Server UUID', 'Old Flavor', 'New Flavor', + 'Type', 'Created At', 'Updated At' ] def setUp(self): @@ -5006,9 +5006,6 @@ def test_server_migration_list(self): self.migrations_mock.list.assert_called_with(**kwargs) - self.MIGRATION_COLUMNS.insert(0, "Id") - self.MIGRATION_COLUMNS.insert( - len(self.MIGRATION_COLUMNS) - 2, 'Type') self.assertEqual(self.MIGRATION_COLUMNS, columns) self.assertEqual(tuple(self.data), tuple(data)) From e0dc31f32eb6720059439e791713e2c61f81bf70 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Tue, 27 Jul 2021 11:08:35 +0100 Subject: [PATCH 2403/3095] volume: Add missing 'volume list --offset' parameter Looking at the code for the ancient v1 cinder API, we see that this supported offset-style pagination [1][2][3]. Add this parameter, simplifying a future patch to standardize pagination across OSC. [1] https://github.com/openstack/cinder/blob/juno-eol/cinder/api/v1/volumes.py#L259 [2] https://github.com/openstack/cinder/blob/juno-eol/cinder/api/v1/volumes.py#L292 [3] https://github.com/openstack/cinder/blob/juno-eol/cinder/api/common.py#L120 Change-Id: Ifec208ea9ed7afb4bebced6132abb96a3af034b5 Signed-off-by: Stephen Finucane --- openstackclient/tests/unit/volume/v1/test_volume.py | 8 ++++++-- openstackclient/volume/v1/volume.py | 10 ++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/openstackclient/tests/unit/volume/v1/test_volume.py b/openstackclient/tests/unit/volume/v1/test_volume.py index 704a66da71..b8002d6341 100644 --- a/openstackclient/tests/unit/volume/v1/test_volume.py +++ b/openstackclient/tests/unit/volume/v1/test_volume.py @@ -858,9 +858,10 @@ def test_volume_list_long(self): ), ) self.assertItemsEqual(datalist, tuple(data)) - def test_volume_list_with_limit(self): + def test_volume_list_with_limit_and_offset(self): arglist = [ '--limit', '2', + '--offset', '5', ] verifylist = [ ('long', False), @@ -868,6 +869,7 @@ def test_volume_list_with_limit(self): ('name', None), ('status', None), ('limit', 2), + ('offset', 5), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -876,9 +878,11 @@ def test_volume_list_with_limit(self): self.volumes_mock.list.assert_called_once_with( limit=2, search_opts={ + 'offset': 5, 'status': None, 'display_name': None, - 'all_tenants': False, } + 'all_tenants': False, + }, ) self.assertEqual(self.columns, columns) self.assertItemsEqual(self.datalist, tuple(data)) diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index 460bd85a8b..dfbb0c545f 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -327,6 +327,13 @@ def get_parser(self, prog_name): default=False, help=_('List additional fields in output'), ) + parser.add_argument( + '--offset', + type=int, + action=parseractions.NonNegativeAction, + metavar='', + help=_('Index from which to start listing volumes'), + ) parser.add_argument( '--limit', type=int, @@ -395,6 +402,9 @@ def take_action(self, parsed_args): 'status': parsed_args.status, } + if parsed_args.offset: + search_opts['offset'] = parsed_args.offset + data = volume_client.volumes.list( search_opts=search_opts, limit=parsed_args.limit, From ed87f7949ef1ef580ed71b9820e16823c0466472 Mon Sep 17 00:00:00 2001 From: melanie witt Date: Mon, 26 Jul 2021 22:13:55 +0000 Subject: [PATCH 2404/3095] Correct REST API response fields for /os-migrations API The compute APIs are unfortunately inconsistent with regard to the response parameters for migrations. * GET /servers/{server_id}/migrations returns server_uuid * GET /os-migrations returns instance_uuid Because the 'Server UUID' column is being specified for parsing the response from GET /os-migrations, it is always showing as an empty string to users. There are a few other mismatches between the column names and the REST API response fields [1]: * 'Old Flavor' vs 'old_instance_type_id' * 'New Flavor' vs 'new_instance_type_id' * 'Type' vs 'migration_type' This adds a new list containing the REST API response field names to pass to utils.get_item_properties so that the responses are correctly parsed and the client output contains the response data instead of empty strings. Story: 2009078 Task: 42890 [1] https://docs.openstack.org/api-ref/compute/?expanded=list-migrations-detail#list-migrations Change-Id: I8aab60619e0225047f6a1c31e44917ca8fcc799e --- openstackclient/compute/v2/server.py | 27 +++++++--- .../tests/unit/compute/v2/fakes.py | 8 +-- .../tests/unit/compute/v2/test_server.py | 54 ++++++++++++++++++- 3 files changed, 77 insertions(+), 12 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 24df46d8ce..a09fd44d0a 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -2781,28 +2781,41 @@ def get_parser(self, prog_name): return parser def print_migrations(self, parsed_args, compute_client, migrations): - columns = [ + column_headers = [ 'Source Node', 'Dest Node', 'Source Compute', 'Dest Compute', 'Dest Host', 'Status', 'Server UUID', 'Old Flavor', 'New Flavor', 'Created At', 'Updated At', ] + # Response fields coming back from the REST API are not always exactly + # the same as the column header names. + columns = [ + 'source_node', 'dest_node', 'source_compute', 'dest_compute', + 'dest_host', 'status', 'instance_uuid', 'old_instance_type_id', + 'new_instance_type_id', 'created_at', 'updated_at', + ] + # Insert migrations UUID after ID if compute_client.api_version >= api_versions.APIVersion("2.59"): - columns.insert(0, "UUID") + column_headers.insert(0, "UUID") + columns.insert(0, "uuid") if compute_client.api_version >= api_versions.APIVersion("2.23"): - columns.insert(0, "Id") - columns.insert(len(columns) - 2, "Type") + column_headers.insert(0, "Id") + columns.insert(0, "id") + column_headers.insert(len(column_headers) - 2, "Type") + columns.insert(len(columns) - 2, "migration_type") if compute_client.api_version >= api_versions.APIVersion("2.80"): if parsed_args.project: - columns.insert(len(columns) - 2, "Project") + column_headers.insert(len(column_headers) - 2, "Project") + columns.insert(len(columns) - 2, "project_id") if parsed_args.user: - columns.insert(len(columns) - 2, "User") + column_headers.insert(len(column_headers) - 2, "User") + columns.insert(len(columns) - 2, "user_id") return ( - columns, + column_headers, (utils.get_item_properties(mig, columns) for mig in migrations), ) diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index 4a2a44de12..47457acbc6 100644 --- a/openstackclient/tests/unit/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -1587,20 +1587,20 @@ def create_one_migration(attrs=None, methods=None): migration_info = { "dest_host": "10.0.2.15", "status": "migrating", - "type": "migration", + "migration_type": "migration", "updated_at": "2017-01-31T08:03:25.000000", "created_at": "2017-01-31T08:03:21.000000", "dest_compute": "compute-" + uuid.uuid4().hex, "id": random.randint(1, 999), "source_node": "node-" + uuid.uuid4().hex, - "server": uuid.uuid4().hex, + "instance_uuid": uuid.uuid4().hex, "dest_node": "node-" + uuid.uuid4().hex, "source_compute": "compute-" + uuid.uuid4().hex, "uuid": uuid.uuid4().hex, "old_instance_type_id": uuid.uuid4().hex, "new_instance_type_id": uuid.uuid4().hex, - "project": uuid.uuid4().hex, - "user": uuid.uuid4().hex + "project_id": uuid.uuid4().hex, + "user_id": uuid.uuid4().hex } # Overwrite default attributes. diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 42c8816bcf..57840cb076 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -4909,6 +4909,13 @@ class TestListMigration(TestServer): 'Old Flavor', 'New Flavor', 'Created At', 'Updated At' ] + # These are the fields that come back in the response from the REST API. + MIGRATION_FIELDS = [ + 'source_node', 'dest_node', 'source_compute', 'dest_compute', + 'dest_host', 'status', 'instance_uuid', 'old_instance_type_id', + 'new_instance_type_id', 'created_at', 'updated_at' + ] + def setUp(self): super(TestListMigration, self).setUp() @@ -4920,7 +4927,7 @@ def setUp(self): self.migrations_mock.list.return_value = self.migrations self.data = (common_utils.get_item_properties( - s, self.MIGRATION_COLUMNS) for s in self.migrations) + s, self.MIGRATION_FIELDS) for s in self.migrations) # Get the command object to test self.cmd = server.ListMigration(self.app, None) @@ -4982,6 +4989,13 @@ class TestListMigrationV223(TestListMigration): 'Type', 'Created At', 'Updated At' ] + # These are the fields that come back in the response from the REST API. + MIGRATION_FIELDS = [ + 'id', 'source_node', 'dest_node', 'source_compute', 'dest_compute', + 'dest_host', 'status', 'instance_uuid', 'old_instance_type_id', + 'new_instance_type_id', 'migration_type', 'created_at', 'updated_at' + ] + def setUp(self): super(TestListMigrationV223, self).setUp() @@ -5019,6 +5033,14 @@ class TestListMigrationV259(TestListMigration): 'Old Flavor', 'New Flavor', 'Type', 'Created At', 'Updated At' ] + # These are the fields that come back in the response from the REST API. + MIGRATION_FIELDS = [ + 'id', 'uuid', 'source_node', 'dest_node', 'source_compute', + 'dest_compute', 'dest_host', 'status', 'instance_uuid', + 'old_instance_type_id', 'new_instance_type_id', 'migration_type', + 'created_at', 'updated_at' + ] + def setUp(self): super(TestListMigrationV259, self).setUp() @@ -5125,6 +5147,14 @@ class TestListMigrationV266(TestListMigration): 'Old Flavor', 'New Flavor', 'Type', 'Created At', 'Updated At' ] + # These are the fields that come back in the response from the REST API. + MIGRATION_FIELDS = [ + 'id', 'uuid', 'source_node', 'dest_node', 'source_compute', + 'dest_compute', 'dest_host', 'status', 'instance_uuid', + 'old_instance_type_id', 'new_instance_type_id', 'migration_type', + 'created_at', 'updated_at' + ] + def setUp(self): super(TestListMigrationV266, self).setUp() @@ -5194,6 +5224,14 @@ class TestListMigrationV280(TestListMigration): 'Old Flavor', 'New Flavor', 'Type', 'Created At', 'Updated At' ] + # These are the fields that come back in the response from the REST API. + MIGRATION_FIELDS = [ + 'id', 'uuid', 'source_node', 'dest_node', 'source_compute', + 'dest_compute', 'dest_host', 'status', 'instance_uuid', + 'old_instance_type_id', 'new_instance_type_id', 'migration_type', + 'created_at', 'updated_at' + ] + project = identity_fakes.FakeProject.create_one_project() user = identity_fakes.FakeUser.create_one_user() @@ -5247,10 +5285,14 @@ def test_server_migration_list_with_project(self): self.MIGRATION_COLUMNS.insert( len(self.MIGRATION_COLUMNS) - 2, "Project") + self.MIGRATION_FIELDS.insert( + len(self.MIGRATION_FIELDS) - 2, "project_id") self.assertEqual(self.MIGRATION_COLUMNS, columns) self.assertEqual(tuple(self.data), tuple(data)) # Clean up global variables MIGRATION_COLUMNS self.MIGRATION_COLUMNS.remove('Project') + # Clean up global variables MIGRATION_FIELDS + self.MIGRATION_FIELDS.remove('project_id') def test_get_migrations_with_project_pre_v280(self): self.app.client_manager.compute.api_version = api_versions.APIVersion( @@ -5309,10 +5351,14 @@ def test_server_migration_list_with_user(self): self.MIGRATION_COLUMNS.insert( len(self.MIGRATION_COLUMNS) - 2, "User") + self.MIGRATION_FIELDS.insert( + len(self.MIGRATION_FIELDS) - 2, "user_id") self.assertEqual(self.MIGRATION_COLUMNS, columns) self.assertEqual(tuple(self.data), tuple(data)) # Clean up global variables MIGRATION_COLUMNS self.MIGRATION_COLUMNS.remove('User') + # Clean up global variables MIGRATION_FIELDS + self.MIGRATION_FIELDS.remove('user_id') def test_get_migrations_with_user_pre_v280(self): self.app.client_manager.compute.api_version = api_versions.APIVersion( @@ -5371,13 +5417,19 @@ def test_server_migration_list_with_project_and_user(self): self.MIGRATION_COLUMNS.insert( len(self.MIGRATION_COLUMNS) - 2, "Project") + self.MIGRATION_FIELDS.insert( + len(self.MIGRATION_FIELDS) - 2, "project_id") self.MIGRATION_COLUMNS.insert( len(self.MIGRATION_COLUMNS) - 2, "User") + self.MIGRATION_FIELDS.insert( + len(self.MIGRATION_FIELDS) - 2, "user_id") self.assertEqual(self.MIGRATION_COLUMNS, columns) self.assertEqual(tuple(self.data), tuple(data)) # Clean up global variables MIGRATION_COLUMNS self.MIGRATION_COLUMNS.remove('Project') + self.MIGRATION_FIELDS.remove('project_id') self.MIGRATION_COLUMNS.remove('User') + self.MIGRATION_FIELDS.remove('user_id') def test_get_migrations_with_project_and_user_pre_v280(self): self.app.client_manager.compute.api_version = api_versions.APIVersion( From 12c93c6d5ff420f6a4a8833d33bad6ee7222e2f7 Mon Sep 17 00:00:00 2001 From: melanie witt Date: Thu, 12 Aug 2021 22:06:36 +0000 Subject: [PATCH 2405/3095] Show "Forced Down" compute service status with --long Currently, the unified client does not have the ability to show the "Forced Down" field of a GET /os-services response in microversion 2.11 even though the legacy client can. This adds a "Forced Down" column to the 'openstack compute service list --long' command output when microversion 2.11 is used. Story: 2009115 Task: 43011 Change-Id: I10bc2fedbf0e867a990227962b2b6e60f5681f69 --- openstackclient/compute/v2/service.py | 4 +++ .../tests/unit/compute/v2/fakes.py | 2 ++ .../tests/unit/compute/v2/test_service.py | 32 +++++++++++++++++++ ...ice-list-forced-down-2b16d1cb44f71a08.yaml | 5 +++ 4 files changed, 43 insertions(+) create mode 100644 releasenotes/notes/compute-service-list-forced-down-2b16d1cb44f71a08.yaml diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index 625c0fad3a..6427e548be 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.py @@ -103,6 +103,10 @@ def take_action(self, parsed_args): "Updated At", "Disabled Reason" ) + has_forced_down = ( + compute_client.api_version >= api_versions.APIVersion('2.11')) + if has_forced_down: + columns += ("Forced Down",) else: columns = ( "ID", diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index 47457acbc6..05a14e16a7 100644 --- a/openstackclient/tests/unit/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -722,6 +722,8 @@ def create_one_service(attrs=None): 'state': 'state-' + uuid.uuid4().hex, 'updated_at': 'time-' + uuid.uuid4().hex, 'disabled_reason': 'earthquake', + # Introduced in API microversion 2.11 + 'forced_down': False, } # Overwrite default attributes. diff --git a/openstackclient/tests/unit/compute/v2/test_service.py b/openstackclient/tests/unit/compute/v2/test_service.py index 87e54747f1..c547c3a687 100644 --- a/openstackclient/tests/unit/compute/v2/test_service.py +++ b/openstackclient/tests/unit/compute/v2/test_service.py @@ -190,6 +190,38 @@ def test_service_list_with_long_option(self): self.assertEqual(self.columns_long, columns) self.assertEqual(self.data_long, list(data)) + def test_service_list_with_long_option_2_11(self): + arglist = [ + '--host', self.service.host, + '--service', self.service.binary, + '--long' + ] + verifylist = [ + ('host', self.service.host), + ('service', self.service.binary), + ('long', True) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.11') + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + self.service_mock.list.assert_called_with( + self.service.host, + self.service.binary, + ) + + # In 2.11 there is also a forced_down column. + columns_long = self.columns_long + ('Forced Down',) + data_long = [self.data_long[0] + (self.service.forced_down,)] + + self.assertEqual(columns_long, columns) + self.assertEqual(data_long, list(data)) + class TestServiceSet(TestService): diff --git a/releasenotes/notes/compute-service-list-forced-down-2b16d1cb44f71a08.yaml b/releasenotes/notes/compute-service-list-forced-down-2b16d1cb44f71a08.yaml new file mode 100644 index 0000000000..ebdc41556f --- /dev/null +++ b/releasenotes/notes/compute-service-list-forced-down-2b16d1cb44f71a08.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add column ``Forced Down`` to the output of ``compute service list + --long``. Only available starting with ``--os-compute-api-version 2.11``. From 4aad7dd77953e09b4973df0b37d1cb23d8b0afbf Mon Sep 17 00:00:00 2001 From: LEE JAE YONG Date: Sat, 28 Aug 2021 07:17:04 +0000 Subject: [PATCH 2406/3095] Fix typo error in listing server's column name openstack server list -c "Created At" command doesn't work because the wrong variable was used here. When we receive resp data, Created At data is saved with the name "created". But in "server.py", we append columns as created_at. So it seems to print an empty table. Story: 2009149 Task: 43112 Change-Id: I06de6903d5cc427a8b0fdcd168fec47192f4365b --- openstackclient/compute/v2/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index a09fd44d0a..6996405faa 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -2341,7 +2341,7 @@ def take_action(self, parsed_args): columns.append('user_id') column_headers.append('User ID') if c in ('Created At', 'created_at'): - columns.append('created_at') + columns.append('created') column_headers.append('Created At') # convert back to tuple From 6ce7da8aeb7e5d1347940433e087036e8e43eaa6 Mon Sep 17 00:00:00 2001 From: Ghanshyam Mann Date: Mon, 30 Aug 2021 12:06:10 -0500 Subject: [PATCH 2407/3095] [community goal] Update contributor documentation This patch updates/adds the contributor documentation to follow the guidelines of the Ussuri cycle community goal[1]. [1] https://governance.openstack.org/tc/goals/selected/ussuri/project-ptl-and-contrib-docs.html Story: #2007236 Task: #38547 Change-Id: I0afa1796d488a96160f4a7fd615920d05fe1771c --- CONTRIBUTING.rst | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 69ecd79cad..f8732b7211 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -1,16 +1,27 @@ -If you would like to contribute to the development of OpenStack, -you must follow the steps documented at: +The source repository for this project can be found at: - https://docs.openstack.org/infra/manual/developers.html + https://opendev.org/openstack/python-openstackclient -Once those steps have been completed, changes to OpenStack -should be submitted for review via the Gerrit tool, following -the workflow documented at: +Pull requests submitted through GitHub are not monitored. - https://docs.openstack.org/infra/manual/developers.html#development-workflow +To start contributing to OpenStack, follow the steps in the contribution guide +to set up and use Gerrit: -Pull requests submitted through GitHub will be ignored. + https://docs.openstack.org/contributors/code-and-documentation/quick-start.html -Bugs should be filed on Storyboard, not GitHub or Launchpad: +Bugs should be filed on StoryBoard: https://storyboard.openstack.org/#!/project/openstack/python-openstackclient + +Developers should also join the discussion on the mailing list, at: + + http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-discuss + +or join the IRC channel on + + #openstack-sdks on OFTC (irc.oftc.net) + +For more specific information about contributing to this repository, see the +openstacksdk contributor guide: + + https://docs.openstack.org/openstacksdk/latest/contributor/index.html From 8e833a3ed26467a1190ba69d8ba716a7cd1cccb3 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 1 Sep 2021 13:04:51 +0100 Subject: [PATCH 2408/3095] compute: Add support for microversion 2.90 Allow configuring hostname when creating a new server or updating or rebuilding an existing server. Change-Id: Ibe603eab78bbbec43605f56de62a20493b6aa93d Signed-off-by: Stephen Finucane Depends-On: https://review.opendev.org/c/openstack/python-novaclient/+/806917 --- openstackclient/compute/v2/server.py | 76 +++++++- .../tests/unit/compute/v2/test_server.py | 171 ++++++++++++++++-- ...server-hostname-opts-3cb4fd90b5bf47ca.yaml | 8 + 3 files changed, 235 insertions(+), 20 deletions(-) create mode 100644 releasenotes/notes/add-server-hostname-opts-3cb4fd90b5bf47ca.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index a09fd44d0a..4750583812 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1154,6 +1154,18 @@ def get_parser(self, prog_name): '(supported by --os-compute-api-version 2.52 or above)' ), ) + parser.add_argument( + '--hostname', + metavar='', + help=_( + 'Hostname configured for the server in the metadata service. ' + 'If unset, a hostname will be automatically generated from ' + 'the server name. ' + 'A utility such as cloud-init is required to propagate the ' + 'hostname in the metadata service to the guest OS itself. ' + '(supported by --os-compute-api-version 2.90 or above)' + ), + ) parser.add_argument( '--wait', action='store_true', @@ -1618,6 +1630,16 @@ def _match_image(image_api, wanted_properties): boot_kwargs['hypervisor_hostname'] = ( parsed_args.hypervisor_hostname) + if parsed_args.hostname: + if compute_client.api_version < api_versions.APIVersion("2.90"): + msg = _( + '--os-compute-api-version 2.90 or greater is required to ' + 'support the --hostname option' + ) + raise exceptions.CommandError(msg) + + boot_kwargs['hostname'] = parsed_args.hostname + LOG.debug('boot_args: %s', boot_args) LOG.debug('boot_kwargs: %s', boot_kwargs) @@ -3273,6 +3295,16 @@ def get_parser(self, prog_name): '(supported by --os-compute-api-version 2.63 or above)' ), ) + parser.add_argument( + '--hostname', + metavar='', + help=_( + 'Hostname configured for the server in the metadata service. ' + 'A separate utility running in the guest is required to ' + 'propagate changes to this value to the guest OS itself. ' + '(supported by --os-compute-api-version 2.90 or above)' + ), + ) parser.add_argument( '--wait', action='store_true', @@ -3390,6 +3422,16 @@ def _show_progress(progress): kwargs['trusted_image_certificates'] = None + if parsed_args.hostname: + if compute_client.api_version < api_versions.APIVersion('2.90'): + msg = _( + '--os-compute-api-version 2.90 or greater is required to ' + 'support the --hostname option' + ) + raise exceptions.CommandError(msg) + + kwargs['hostname'] = parsed_args.hostname + try: server = server.rebuild(image, parsed_args.password, **kwargs) finally: @@ -4076,6 +4118,16 @@ def get_parser(self, prog_name): '(supported by --os-compute-api-version 2.26 or above)' ), ) + parser.add_argument( + '--hostname', + metavar='', + help=_( + 'Hostname configured for the server in the metadata service. ' + 'A separate utility running in the guest is required to ' + 'propagate changes to this value to the guest OS itself. ' + '(supported by --os-compute-api-version 2.90 or above)' + ), + ) return parser def take_action(self, parsed_args): @@ -4102,8 +4154,27 @@ def take_action(self, parsed_args): ) raise exceptions.CommandError(msg) + if parsed_args.hostname: + if server.api_version < api_versions.APIVersion('2.90'): + msg = _( + '--os-compute-api-version 2.90 or greater is required to ' + 'support the --hostname option' + ) + raise exceptions.CommandError(msg) + + update_kwargs = {} + if parsed_args.name: - server.update(name=parsed_args.name) + update_kwargs['name'] = parsed_args.name + + if parsed_args.description: + update_kwargs['description'] = parsed_args.description + + if parsed_args.hostname: + update_kwargs['hostname'] = parsed_args.hostname + + if update_kwargs: + server.update(**update_kwargs) if parsed_args.properties: compute_client.servers.set_meta(server, parsed_args.properties) @@ -4124,9 +4195,6 @@ def take_action(self, parsed_args): elif parsed_args.no_password: server.clear_password() - if parsed_args.description: - server.update(description=parsed_args.description) - if parsed_args.tags: for tag in parsed_args.tags: server.add_tag(tag=tag) diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 57840cb076..cab9efd0c0 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -3554,6 +3554,76 @@ def test_server_create_with_host_and_hypervisor_hostname_v274(self): self.assertFalse(self.images_mock.called) self.assertFalse(self.flavors_mock.called) + def test_server_create_with_hostname_v290(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.90') + + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--hostname', 'hostname', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('hostname', 'hostname'), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + # ServerManager.create(name, image, flavor, **kwargs) + self.servers_mock.create.assert_called_with( + self.new_server.name, + self.image, + self.flavor, + meta=None, + files={}, + reservation_id=None, + min_count=1, + max_count=1, + security_groups=[], + userdata=None, + key_name=None, + availability_zone=None, + admin_pass=None, + block_device_mapping_v2=[], + nics='auto', + scheduler_hints={}, + config_drive=None, + hostname='hostname', + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) + self.assertFalse(self.images_mock.called) + self.assertFalse(self.flavors_mock.called) + + def test_server_create_with_hostname_pre_v290(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.89') + + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--hostname', 'hostname', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('hostname', 'hostname'), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, self.cmd.take_action, + parsed_args) + class TestServerDelete(TestServer): @@ -6235,6 +6305,46 @@ def test_rebuild_with_no_trusted_image_cert_pre_257(self): self.cmd.take_action, parsed_args) + def test_rebuild_with_hostname(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.90') + + arglist = [ + self.server.id, + '--hostname', 'new-hostname' + ] + verifylist = [ + ('server', self.server.id), + ('hostname', 'new-hostname') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with(self.server.id) + self.get_image_mock.assert_called_with(self.image.id) + self.server.rebuild.assert_called_with( + self.image, None, hostname='new-hostname') + + def test_rebuild_with_hostname_pre_v290(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.89') + + arglist = [ + self.server.id, + '--hostname', 'new-hostname', + ] + verifylist = [ + ('server', self.server.id), + ('hostname', 'new-hostname') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + class TestEvacuateServer(TestServer): @@ -7340,10 +7450,10 @@ def test_server_set_with_root_password(self, mock_getpass): mock.sentinel.fake_pass) self.assertIsNone(result) - def test_server_set_with_description_api_newer(self): + def test_server_set_with_description(self): # Description is supported for nova api version 2.19 or above - self.fake_servers[0].api_version = 2.19 + self.fake_servers[0].api_version = api_versions.APIVersion('2.19') arglist = [ '--description', 'foo_description', @@ -7354,18 +7464,15 @@ def test_server_set_with_description_api_newer(self): ('server', 'foo_vm'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - with mock.patch.object(api_versions, - 'APIVersion', - return_value=2.19): - result = self.cmd.take_action(parsed_args) - self.fake_servers[0].update.assert_called_once_with( - description='foo_description') - self.assertIsNone(result) + result = self.cmd.take_action(parsed_args) + self.fake_servers[0].update.assert_called_once_with( + description='foo_description') + self.assertIsNone(result) - def test_server_set_with_description_api_older(self): + def test_server_set_with_description_pre_v219(self): # Description is not supported for nova api version below 2.19 - self.fake_servers[0].api_version = 2.18 + self.fake_servers[0].api_version = api_versions.APIVersion('2.18') arglist = [ '--description', 'foo_description', @@ -7376,11 +7483,8 @@ def test_server_set_with_description_api_older(self): ('server', 'foo_vm'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - with mock.patch.object(api_versions, - 'APIVersion', - return_value=2.19): - self.assertRaises(exceptions.CommandError, self.cmd.take_action, - parsed_args) + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) def test_server_set_with_tag(self): self.fake_servers[0].api_version = api_versions.APIVersion('2.26') @@ -7426,6 +7530,41 @@ def test_server_set_with_tag_pre_v226(self): '--os-compute-api-version 2.26 or greater is required', str(ex)) + def test_server_set_with_hostname(self): + + self.fake_servers[0].api_version = api_versions.APIVersion('2.90') + + arglist = [ + '--hostname', 'foo-hostname', + 'foo_vm', + ] + verifylist = [ + ('hostname', 'foo-hostname'), + ('server', 'foo_vm'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.fake_servers[0].update.assert_called_once_with( + hostname='foo-hostname') + self.assertIsNone(result) + + def test_server_set_with_hostname_pre_v290(self): + + self.fake_servers[0].api_version = api_versions.APIVersion('2.89') + + arglist = [ + '--hostname', 'foo-hostname', + 'foo_vm', + ] + verifylist = [ + ('hostname', 'foo-hostname'), + ('server', 'foo_vm'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises( + exceptions.CommandError, self.cmd.take_action, + parsed_args) + class TestServerShelve(TestServer): diff --git a/releasenotes/notes/add-server-hostname-opts-3cb4fd90b5bf47ca.yaml b/releasenotes/notes/add-server-hostname-opts-3cb4fd90b5bf47ca.yaml new file mode 100644 index 0000000000..458d1529e8 --- /dev/null +++ b/releasenotes/notes/add-server-hostname-opts-3cb4fd90b5bf47ca.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + The ``server create``, ``server set`` and ``server rebuild`` commands now + accept an optional ``--hostname HOSTNAME`` option. This can be used to + configure the hostname stored in the metadata service and/or config drive. + Utilities such as ``cloud-init`` can then consume this information to set + the hostname within the guest OS. From 51ee17a94dccd297101725593b223f01c4f9b906 Mon Sep 17 00:00:00 2001 From: Lee Yarwood Date: Thu, 12 Aug 2021 11:27:17 +0100 Subject: [PATCH 2409/3095] compute: Add support for microversion 2.89 This microversion drops the duplicate ``id`` field while adding ``attachment_id`` and ``bdm_uuid`` to the output of the os-volume_attachments API reflected within osc by the ``openstack server volume list $server``command. Depends-On: https://review.opendev.org/c/openstack/nova/+/804275 Change-Id: I8a7002d8d65d7795e106b768df868198ab8b8143 --- openstackclient/compute/v2/server_volume.py | 18 +++++-- .../tests/unit/compute/v2/fakes.py | 3 ++ .../unit/compute/v2/test_server_volume.py | 49 +++++++++++++++++++ ...to_volume_attachment-cea605585db29e14.yaml | 11 +++++ 4 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/add_attachment_id_to_volume_attachment-cea605585db29e14.yaml diff --git a/openstackclient/compute/v2/server_volume.py b/openstackclient/compute/v2/server_volume.py index b53c92fe5e..d53cec931d 100644 --- a/openstackclient/compute/v2/server_volume.py +++ b/openstackclient/compute/v2/server_volume.py @@ -44,18 +44,24 @@ def take_action(self, parsed_args): volumes = compute_client.volumes.get_server_volumes(server.id) - columns = ( - 'id', + columns = () + column_headers = () + + if compute_client.api_version < api_versions.APIVersion('2.89'): + columns += ('id',) + column_headers += ('ID',) + + columns += ( 'device', 'serverId', 'volumeId', ) - column_headers = ( - 'ID', + column_headers += ( 'Device', 'Server ID', 'Volume ID', ) + if compute_client.api_version >= api_versions.APIVersion('2.70'): columns += ('tag',) column_headers += ('Tag',) @@ -64,6 +70,10 @@ def take_action(self, parsed_args): columns += ('delete_on_termination',) column_headers += ('Delete On Termination?',) + if compute_client.api_version >= api_versions.APIVersion('2.89'): + columns += ('attachment_id', 'bdm_uuid') + column_headers += ('Attachment ID', 'BlockDeviceMapping UUID') + return ( column_headers, ( diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index 05a14e16a7..3142a24489 100644 --- a/openstackclient/tests/unit/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -1715,6 +1715,9 @@ def create_one_volume_attachment(attrs=None, methods=None): "tag": "foo", # introduced in API microversion 2.79 "delete_on_termination": True, + # introduced in API microversion 2.89 + "attachment_id": uuid.uuid4().hex, + "bdm_uuid": uuid.uuid4().hex } # Overwrite default attributes. diff --git a/openstackclient/tests/unit/compute/v2/test_server_volume.py b/openstackclient/tests/unit/compute/v2/test_server_volume.py index 4d4916b7ec..02d378f82a 100644 --- a/openstackclient/tests/unit/compute/v2/test_server_volume.py +++ b/openstackclient/tests/unit/compute/v2/test_server_volume.py @@ -167,6 +167,55 @@ def test_server_volume_list_with_delete_on_attachment(self): self.servers_volumes_mock.get_server_volumes.assert_called_once_with( self.server.id) + def test_server_volume_list_with_attachment_ids(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.89') + + arglist = [ + self.server.id, + ] + verifylist = [ + ('server', self.server.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual( + ( + 'Device', 'Server ID', 'Volume ID', 'Tag', + 'Delete On Termination?', 'Attachment ID', + 'BlockDeviceMapping UUID', + ), + columns, + ) + self.assertEqual( + ( + ( + self.volume_attachments[0].device, + self.volume_attachments[0].serverId, + self.volume_attachments[0].volumeId, + self.volume_attachments[0].tag, + self.volume_attachments[0].delete_on_termination, + self.volume_attachments[0].attachment_id, + self.volume_attachments[0].bdm_uuid + + ), + ( + self.volume_attachments[1].device, + self.volume_attachments[1].serverId, + self.volume_attachments[1].volumeId, + self.volume_attachments[1].tag, + self.volume_attachments[1].delete_on_termination, + self.volume_attachments[1].attachment_id, + self.volume_attachments[1].bdm_uuid + ), + ), + tuple(data), + ) + self.servers_volumes_mock.get_server_volumes.assert_called_once_with( + self.server.id) + class TestServerVolumeUpdate(TestServerVolume): diff --git a/releasenotes/notes/add_attachment_id_to_volume_attachment-cea605585db29e14.yaml b/releasenotes/notes/add_attachment_id_to_volume_attachment-cea605585db29e14.yaml new file mode 100644 index 0000000000..9a43989683 --- /dev/null +++ b/releasenotes/notes/add_attachment_id_to_volume_attachment-cea605585db29e14.yaml @@ -0,0 +1,11 @@ +--- +features: + - | + Added support for `microversion 2.89`_. This microversion removes the + ``id`` field while adding the ``attachment_id`` and ``bdm_uuid`` fields to + the responses of ``GET /servers/{server_id}/os-volume_attachments`` and + ``GET /servers/{server_id}/os-volume_attachments/{volume_id}`` with these + changes reflected in novaclient under the ``openstack server volume list`` + command. + + .. _microversion 2.89: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#microversion-2-89 From ed5d2a37c51dfc621b0d8ac79510e21c9ee3dd0f Mon Sep 17 00:00:00 2001 From: Alfredo Moralejo Date: Thu, 9 Sep 2021 15:49:04 +0200 Subject: [PATCH 2410/3095] Replace assertItemsEqual with assertCountEqual Follow-up of [1]. After this patch was sent, two more assertItemsEqual were added in [2]. This patch is fixing it. [1] https://review.opendev.org/c/openstack/python-openstackclient/+/789410 [2] https://review.opendev.org/c/openstack/python-openstackclient/+/781637 Change-Id: Ic2276bd0ff0f5df76505f37d8994b3384d40e9a7 --- openstackclient/tests/unit/volume/v3/test_volume_message.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openstackclient/tests/unit/volume/v3/test_volume_message.py b/openstackclient/tests/unit/volume/v3/test_volume_message.py index 68becf4418..8cabc0c3ad 100644 --- a/openstackclient/tests/unit/volume/v3/test_volume_message.py +++ b/openstackclient/tests/unit/volume/v3/test_volume_message.py @@ -198,7 +198,7 @@ def test_message_list(self): limit=None, ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_message_list_with_options(self): self.app.client_manager.volume.api_version = \ @@ -227,7 +227,7 @@ def test_message_list_with_options(self): limit=3, ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_message_list_pre_v33(self): self.app.client_manager.volume.api_version = \ From 8ef9280af93b46e44b489592d3ae640a01c98190 Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Wed, 22 Sep 2021 10:42:16 +0000 Subject: [PATCH 2411/3095] Update master for stable/xena Add file to the reno documentation build to show release notes for stable/xena. Use pbr instruction to increment the minor version number automatically so that master versions are higher than the versions on stable/xena. Sem-Ver: feature Change-Id: Iedf2c908bf5a9d87effa02717eb604ee8d15ef3b --- releasenotes/source/index.rst | 1 + releasenotes/source/xena.rst | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 releasenotes/source/xena.rst diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index cdbb595ce8..0ad18a2b9f 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,6 +6,7 @@ OpenStackClient Release Notes :maxdepth: 1 unreleased + xena wallaby victoria ussuri diff --git a/releasenotes/source/xena.rst b/releasenotes/source/xena.rst new file mode 100644 index 0000000000..1be85be3eb --- /dev/null +++ b/releasenotes/source/xena.rst @@ -0,0 +1,6 @@ +========================= +Xena Series Release Notes +========================= + +.. release-notes:: + :branch: stable/xena From ff372ffdfbfe036993f84be20cd18262599b37de Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Wed, 22 Sep 2021 10:42:18 +0000 Subject: [PATCH 2412/3095] Add Python3 yoga unit tests This is an automatically generated patch to ensure unit testing is in place for all the of the tested runtimes for yoga. See also the PTI in governance [1]. [1]: https://governance.openstack.org/tc/reference/project-testing-interface.html Change-Id: I89cff43c0eb97c63deaba320e0fc63bd8ba31a2a --- .zuul.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.zuul.yaml b/.zuul.yaml index e1c1f970af..9ed506c676 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -240,7 +240,7 @@ - osc-tox-unit-tips - openstack-cover-jobs - openstack-lower-constraints-jobs - - openstack-python3-xena-jobs + - openstack-python3-yoga-jobs - publish-openstack-docs-pti - check-requirements - release-notes-jobs-python3 From c0a0f0f3d86cacbff386a5ea7b8e846dd595e197 Mon Sep 17 00:00:00 2001 From: ryanKor Date: Sat, 25 Sep 2021 18:21:03 +0900 Subject: [PATCH 2413/3095] Fix that the path of functional test before change: $ tox -e functional -- --regex functional.tests.compute.v2.test_server after change: $ tox -e functional -- --regex tests.functional.compute.v2.test_server the test unit path document should be change the above line. (fixed wrong letter) Change-Id: I49674fb0d56ee65c1f6328b9d960b16876173e2d --- doc/source/contributor/developing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/contributor/developing.rst b/doc/source/contributor/developing.rst index a70b33268a..c6573b9ba6 100644 --- a/doc/source/contributor/developing.rst +++ b/doc/source/contributor/developing.rst @@ -88,7 +88,7 @@ To run a specific functional test: .. code-block:: bash - $ tox -e functional -- --regex functional.tests.compute.v2.test_server + $ tox -e functional -- --regex tests.functional.compute.v2.test_server Running with PDB ~~~~~~~~~~~~~~~~ From 28a376bfb0a330470b028b6d5244ee4c8e1fe864 Mon Sep 17 00:00:00 2001 From: Pavlo Shchelokovskyy Date: Thu, 30 Sep 2021 17:14:19 +0300 Subject: [PATCH 2414/3095] Add --trusted-image-cert option for server create this already exists for server rebuild, but was missing for server create. This option is supported from Compute API version >= 2.63, and is only available for servers booted directly from images (not from volumes, not from snapshots, and not from images first converted to volumes). Additionally, this patch removes mentions of OS_TRUSTED_IMAGE_CERTIFICATE_IDS env var from similar option help string in server rebuild command as it is not actually implemented yet. Change-Id: I4e9faea05c499bd91034d1d284c44fdcc8e18db5 --- openstackclient/compute/v2/server.py | 32 +++- .../tests/unit/compute/v2/test_server.py | 150 ++++++++++++++++++ ...option-server-create-a660488407300f22.yaml | 7 + 3 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/add-trusted-certs-option-server-create-a660488407300f22.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 4750583812..291397769f 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1171,6 +1171,19 @@ def get_parser(self, prog_name): action='store_true', help=_('Wait for build to complete'), ) + parser.add_argument( + '--trusted-image-cert', + metavar='', + action='append', + dest='trusted_image_certs', + help=_( + 'Trusted image certificate IDs used to validate certificates ' + 'during the image signature verification process. ' + 'May be specified multiple times to pass multiple trusted ' + 'image certificate IDs. ' + '(supported by --os-compute-api-version 2.63 or above)' + ), + ) return parser def take_action(self, parsed_args): @@ -1640,6 +1653,24 @@ def _match_image(image_api, wanted_properties): boot_kwargs['hostname'] = parsed_args.hostname + # TODO(stephenfin): Handle OS_TRUSTED_IMAGE_CERTIFICATE_IDS + if parsed_args.trusted_image_certs: + if not (image and not parsed_args.boot_from_volume): + msg = _( + '--trusted-image-cert option is only supported for ' + 'servers booted directly from images' + ) + raise exceptions.CommandError(msg) + if compute_client.api_version < api_versions.APIVersion('2.63'): + msg = _( + '--os-compute-api-version 2.63 or greater is required to ' + 'support the --trusted-image-cert option' + ) + raise exceptions.CommandError(msg) + + certs = parsed_args.trusted_image_certs + boot_kwargs['trusted_image_certificates'] = certs + LOG.debug('boot_args: %s', boot_args) LOG.debug('boot_kwargs: %s', boot_kwargs) @@ -3277,7 +3308,6 @@ def get_parser(self, prog_name): help=_( 'Trusted image certificate IDs used to validate certificates ' 'during the image signature verification process. ' - 'Defaults to env[OS_TRUSTED_IMAGE_CERTIFICATE_IDS]. ' 'May be specified multiple times to pass multiple trusted ' 'image certificate IDs. ' 'Cannot be specified with the --no-trusted-certs option. ' diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index cab9efd0c0..13431e005d 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -3624,6 +3624,156 @@ def test_server_create_with_hostname_pre_v290(self): exceptions.CommandError, self.cmd.take_action, parsed_args) + def test_server_create_with_trusted_image_cert(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.63') + + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--trusted-image-cert', 'foo', + '--trusted-image-cert', 'bar', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('config_drive', False), + ('trusted_image_certs', ['foo', 'bar']), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = dict( + meta=None, + files={}, + reservation_id=None, + min_count=1, + max_count=1, + security_groups=[], + userdata=None, + key_name=None, + availability_zone=None, + admin_pass=None, + block_device_mapping_v2=[], + nics='auto', + scheduler_hints={}, + config_drive=None, + trusted_image_certificates=['foo', 'bar'], + ) + # ServerManager.create(name, image, flavor, **kwargs) + self.servers_mock.create.assert_called_with( + self.new_server.name, + self.image, + self.flavor, + **kwargs + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) + self.assertFalse(self.images_mock.called) + self.assertFalse(self.flavors_mock.called) + + def test_server_create_with_trusted_image_cert_prev263(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.62') + + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--trusted-image-cert', 'foo', + '--trusted-image-cert', 'bar', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('config_drive', False), + ('trusted_image_certs', ['foo', 'bar']), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + def test_server_create_with_trusted_image_cert_from_volume(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.63') + arglist = [ + '--volume', 'volume1', + '--flavor', 'flavor1', + '--trusted-image-cert', 'foo', + '--trusted-image-cert', 'bar', + self.new_server.name, + ] + verifylist = [ + ('volume', 'volume1'), + ('flavor', 'flavor1'), + ('config_drive', False), + ('trusted_image_certs', ['foo', 'bar']), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + def test_server_create_with_trusted_image_cert_from_snapshot(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.63') + arglist = [ + '--snapshot', 'snapshot1', + '--flavor', 'flavor1', + '--trusted-image-cert', 'foo', + '--trusted-image-cert', 'bar', + self.new_server.name, + ] + verifylist = [ + ('snapshot', 'snapshot1'), + ('flavor', 'flavor1'), + ('config_drive', False), + ('trusted_image_certs', ['foo', 'bar']), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + def test_server_create_with_trusted_image_cert_boot_from_volume(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.63') + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--boot-from-volume', '1', + '--trusted-image-cert', 'foo', + '--trusted-image-cert', 'bar', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('boot_from_volume', 1), + ('config_drive', False), + ('trusted_image_certs', ['foo', 'bar']), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + class TestServerDelete(TestServer): diff --git a/releasenotes/notes/add-trusted-certs-option-server-create-a660488407300f22.yaml b/releasenotes/notes/add-trusted-certs-option-server-create-a660488407300f22.yaml new file mode 100644 index 0000000000..8814a63ae6 --- /dev/null +++ b/releasenotes/notes/add-trusted-certs-option-server-create-a660488407300f22.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Added ``--trusted-image-cert`` option for server create. It is available + only when directly booting server from image (not from volume, not from + snapshot and not via image converted to volume first). + This option is supported for Compute API version >=2.63 From abed9f20f5d9c1af3345456ec82d726e49db9f68 Mon Sep 17 00:00:00 2001 From: lsmman Date: Wed, 6 Oct 2021 19:21:51 +0900 Subject: [PATCH 2415/3095] Remove non-working code after method return. Delete duplicate return code. While adding return of a new Member type, the existing return code part is not deleted. Note the code in fakes.py in the below commit where these codes were added. - Project: python-openstackclient - The commit: 60e7c51df4cf061ebbb435a959ad63c7d3a296bf Change-Id: Iae44770a784732991962cd38472095f76ab2543f --- openstackclient/tests/unit/image/v2/fakes.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/openstackclient/tests/unit/image/v2/fakes.py b/openstackclient/tests/unit/image/v2/fakes.py index 516d563001..0d83f98b95 100644 --- a/openstackclient/tests/unit/image/v2/fakes.py +++ b/openstackclient/tests/unit/image/v2/fakes.py @@ -308,8 +308,3 @@ def create_one_image_member(attrs=None): image_member_info.update(attrs) return member.Member(**image_member_info) - - image_member = fakes.FakeModel( - copy.deepcopy(image_member_info)) - - return image_member From 70fed75c852d5a0518adbde14dad41b7579b04a5 Mon Sep 17 00:00:00 2001 From: choidoa-git Date: Wed, 6 Oct 2021 15:32:29 +0000 Subject: [PATCH 2416/3095] Update the Nova CLI decoder document In this patch, Update missing command in Mapping Guide. List of updated commands (Nova CLI / OSC) - server-migration-list / server migration list - server-migration-show / server migration show - live-migration-abort / server migration abort - live-migration-force-complete / server migration force complete - migration-list / server migration list - evacuate / server evacuate - flavor-access-add / flavor set --project - flavor-access-list / flavor show - flavor-access-remove / flavor unset - server-tag-add / server set --tag - server-tag-delete / server unset --tag - server-tag-delete-all / server unset --tag - server-tag-list / server list --tag - server-tag-set / server set --tag - quota-class-show / quota show --class Change-Id: Id1b4980fbc0f6e8e58bfae6f393f9336c6a7e3b1 --- doc/source/cli/data/nova.csv | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/doc/source/cli/data/nova.csv b/doc/source/cli/data/nova.csv index 83911f1237..cc42fe5475 100644 --- a/doc/source/cli/data/nova.csv +++ b/doc/source/cli/data/nova.csv @@ -19,10 +19,10 @@ clear-password,server set --root-password,Clear the admin password for a server console-log,console log show,Get console log output of a server. delete,server delete,Immediately shut down and delete specified server(s). diagnostics,openstack server show --diagnostics,Retrieve server diagnostics. -evacuate,,Evacuate server from failed host. -flavor-access-add,,Add flavor access for the given tenant. -flavor-access-list,,Print access information about the given flavor. -flavor-access-remove,,Remove flavor access for the given tenant. +evacuate,server evacuate,Evacuate server from failed host. +flavor-access-add,flavor set --project,Add flavor access for the given tenant. +flavor-access-list,flavor show,Print access information about the given flavor. +flavor-access-remove,flavor unset,Remove flavor access for the given tenant. flavor-create,flavor create,Create a new flavor. flavor-delete,flavor delete,Delete a specific flavor flavor-key,flavor set / unset,Set or unset extra_spec for a flavor. @@ -59,15 +59,15 @@ keypair-show,keypair show,Show details about the given keypair. limits,limits show,Print rate and absolute limits. list,server list,List active servers. list-secgroup,security group list,List Security Group(s) of a server. -live-migration,,Migrate running server to a new machine. -live-migration-abort,,Abort an on-going live migration. -live-migration-force-comp,,Force on-going live migration to complete. +live-migration,server migration list,Migrate running server to a new machine. +live-migration-abort,server migration abort,Abort an on-going live migration. +live-migration-force-comp,server migration force complete,Force on-going live migration to complete. lock,server lock,Lock a server. meta,server set --property / unset,Set or delete metadata on a server. migrate,server migrate,Migrate a server. The new host will be selected by the scheduler. migration-list,,Print a list of migrations. pause,server pause,Pause a server. -quota-class-show,,List the quotas for a quota class. +quota-class-show,quota show --class,List the quotas for a quota class. quota-class-update,quota set --class,Update the quotas for a quota class. quota-defaults,quota list,List the default quotas for a tenant. quota-delete,quota set,Delete quota for a tenant/user so their quota will Revert back to default. @@ -89,13 +89,13 @@ server-group-create,server group create,Create a new server group with the speci server-group-delete,server group delete,Delete specific server group(s). server-group-get,server group show,Get a specific server group. server-group-list,server group list,Print a list of all server groups. -server-migration-list,,Get the migrations list of specified server. -server-migration-show,,Get the migration of specified server. -server-tag-add,,Add one or more tags to a server. -server-tag-delete,,Delete one or more tags from a server. -server-tag-delete-all,,Delete all tags from a server. -server-tag-list,,Get list of tags from a server. -server-tag-set,,Set list of tags to a server. +server-migration-list,server migration list,Get the migrations list of specified server. +server-migration-show,server migration show,Get the migration of specified server. +server-tag-add,server set --tag,Add one or more tags to a server. +server-tag-delete,server unset --tag,Delete one or more tags from a server. +server-tag-delete-all,server unset --tag,Delete all tags from a server. +server-tag-list,server list --tag,Get list of tags from a server. +server-tag-set,server set --tag,Set list of tags to a server. server-topology,openstack server show --topology,Retrieve server topology. (Supported by API versions '2.78' - '2.latest') [hint: use '-- os-compute-api-version' flag to show help message for proper version] service-delete,compute service delete,Delete the service. service-disable,compute service set --disable,Disable the service. From e06a4f1c20496c3acc4cdd0027d31d1cfe3485ea Mon Sep 17 00:00:00 2001 From: JIHOJU Date: Tue, 5 Oct 2021 09:13:57 +0900 Subject: [PATCH 2417/3095] Update the Nova CLI docoder document There are several update in CLI decoder document. - Change flavor set/unset to flavor set/unset --property - Update the mapping with flavor-update, interface-attach, and interface-detach Change-Id: I1db50188b3643d3fe28689dc73b3f63806defd29 --- doc/source/cli/data/nova.csv | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/source/cli/data/nova.csv b/doc/source/cli/data/nova.csv index 83911f1237..5141e86ea6 100644 --- a/doc/source/cli/data/nova.csv +++ b/doc/source/cli/data/nova.csv @@ -25,10 +25,10 @@ flavor-access-list,,Print access information about the given flavor. flavor-access-remove,,Remove flavor access for the given tenant. flavor-create,flavor create,Create a new flavor. flavor-delete,flavor delete,Delete a specific flavor -flavor-key,flavor set / unset,Set or unset extra_spec for a flavor. +flavor-key,flavor set / unset --property,Set or unset extra_spec for a flavor. flavor-list,flavor list,Print a list of available 'flavors' flavor-show,flavor show,Show details about the given flavor. -flavor-update,,Update the description of an existing flavor. (Supported by API versions '2.55' - '2.latest') [hint: use '--os-compute-api- version' flag to show help message for proper version] +flavor-update,flavor set --description,Update the description of an existing flavor. (Supported by API versions '2.55' - '2.latest') [hint: use '--os-compute-api-version' flag to show help message for proper version] force-delete,server delete,Force delete a server. get-mks-console,console url show --mks,Get an MKS console to a server. (Supported by API versions '2.8' - '2.latest') [hint: use ' --os-compute-api-version' flag to show help message for proper version] get-password,WONTFIX,Get the admin password for a server. This operation calls the metadata service to query metadata information and does not read password information from the server itself. @@ -49,8 +49,8 @@ image-create,server image create,Create a new image by taking a snapshot of a ru instance-action,,Show an action. instance-action-list,,List actions on a server. instance-usage-audit-log,,List/Get server usage audits. -interface-attach,,Attach a network interface to a server. -interface-detach,,Detach a network interface from a server. +interface-attach,server add port / server add floating ip / server add fixed ip,Attach a network interface to a server. +interface-detach,server remove port,Detach a network interface from a server. interface-list,port list --server,List interfaces attached to a server. keypair-add,keypair create,Create a new key pair for use with servers. keypair-delete,keypair delete,Delete keypair given by its name. From 53debe7fe1978f661768a27430f646a288948ecc Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 13 Oct 2021 10:18:53 +0100 Subject: [PATCH 2418/3095] compute: Fix filtering servers by tags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The nova API expects the 'tags' and 'not-tags' filters of the 'GET /servers' (list servers) API to be a CSV string [1]: tags (Optional) A list of tags to filter the server list by. Servers that match all tags in this list will be returned. Boolean expression in this case is 't1 AND t2'. Tags in query must be separated by comma. New in version 2.26 not-tags (Optional) A list of tags to filter the server list by. Servers that don’t match all tags in this list will be returned. Boolean expression in this case is 'NOT (t1 AND t2)'. Tags in query must be separated by comma. New in version 2.26 We were instead providing a Python list, which was simply being URL encoded. Correct this. [1] https://docs.openstack.org/api-ref/compute/?expanded=list-servers-detail#list-servers Change-Id: Ie0251a0dccdf3385089e5bbaedf646a5e928cc48 Signed-off-by: Stephen Finucane Closes-Bug: #1946816 --- openstackclient/compute/v2/server.py | 4 ++-- openstackclient/tests/unit/compute/v2/test_server.py | 4 ++-- releasenotes/notes/bug-1946816-7665858605453578.yaml | 6 ++++++ 3 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/bug-1946816-7665858605453578.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 08345243e9..c11f4b5781 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -2257,7 +2257,7 @@ def take_action(self, parsed_args): ) raise exceptions.CommandError(msg) - search_opts['tags'] = parsed_args.tags + search_opts['tags'] = ','.join(parsed_args.tags) if parsed_args.not_tags: if compute_client.api_version < api_versions.APIVersion('2.26'): @@ -2267,7 +2267,7 @@ def take_action(self, parsed_args): ) raise exceptions.CommandError(msg) - search_opts['not-tags'] = parsed_args.not_tags + search_opts['not-tags'] = ','.join(parsed_args.not_tags) if parsed_args.locked: if compute_client.api_version < api_versions.APIVersion('2.73'): diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 13431e005d..3d8c17fdeb 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -4489,7 +4489,7 @@ def test_server_list_with_tag(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.search_opts['tags'] = ['tag1', 'tag2'] + self.search_opts['tags'] = 'tag1,tag2' self.servers_mock.list.assert_called_with(**self.kwargs) @@ -4532,7 +4532,7 @@ def test_server_list_with_not_tag(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.search_opts['not-tags'] = ['tag1', 'tag2'] + self.search_opts['not-tags'] = 'tag1,tag2' self.servers_mock.list.assert_called_with(**self.kwargs) diff --git a/releasenotes/notes/bug-1946816-7665858605453578.yaml b/releasenotes/notes/bug-1946816-7665858605453578.yaml new file mode 100644 index 0000000000..f9e8406a9e --- /dev/null +++ b/releasenotes/notes/bug-1946816-7665858605453578.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Filtering servers by tags (``server list --tag``, + ``server list --not-tag``) now works correctly. + [Bug `1946816 `_] From a797c9d2a351fd87d2f8075bbf7180fc6b202d92 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 13 Oct 2021 12:14:30 +0100 Subject: [PATCH 2419/3095] tox: Ignore virtualenvs for pep8 environment Change-Id: I473d1b6c1287325566a5f5f5aadaea802c6af6f4 Signed-off-by: Stephen Finucane --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 8a386267da..6eefc26ece 100644 --- a/tox.ini +++ b/tox.ini @@ -133,7 +133,7 @@ commands = show-source = True # H203: Use assertIs(Not)None to check for None enable-extensions = H203 -exclude = .git,.tox,dist,doc,*lib/python*,*egg,build,tools +exclude = .venv,.git,.tox,dist,doc,*lib/python*,*egg,build,tools,releasenotes # W504 is disabled since you must choose between this or W503 ignore = W504 import-order-style = pep8 From 30612bf62295720a21d39c4f589cfcefb7a86a2f Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Fri, 8 Oct 2021 18:06:11 +0100 Subject: [PATCH 2420/3095] Remove 'get_osc_show_columns_for_sdk_resource' duplicates There were a number of 'get_osc_show_columns_for_sdk_resource' defined in-tree. However, osc-lib has provided this method for some time (since 2.2.0, June 2020 [1] - our minimum version is currently 2.3.0) so there's no need to provide our own copies. Remove them. [1] https://github.com/openstack/osc-lib/commit/29a0c5a5 Change-Id: I25695f4f9a379dd691b7eaa1e3247164668ae77e Signed-off-by: Stephen Finucane --- openstackclient/common/sdk_utils.py | 58 ----------------- openstackclient/image/v1/image.py | 13 ++-- openstackclient/image/v2/image.py | 7 +-- openstackclient/network/sdk_utils.py | 63 ------------------- openstackclient/network/v2/address_group.py | 4 +- openstackclient/network/v2/address_scope.py | 4 +- openstackclient/network/v2/floating_ip.py | 3 +- .../network/v2/floating_ip_port_forwarding.py | 5 +- openstackclient/network/v2/ip_availability.py | 3 +- .../network/v2/l3_conntrack_helper.py | 4 +- openstackclient/network/v2/network.py | 5 +- openstackclient/network/v2/network_agent.py | 4 +- .../v2/network_auto_allocated_topology.py | 3 +- openstackclient/network/v2/network_flavor.py | 4 +- .../network/v2/network_flavor_profile.py | 4 +- openstackclient/network/v2/network_meter.py | 3 +- .../network/v2/network_meter_rule.py | 3 +- .../network/v2/network_qos_policy.py | 4 +- .../network/v2/network_qos_rule.py | 4 +- .../network/v2/network_qos_rule_type.py | 3 +- openstackclient/network/v2/network_rbac.py | 4 +- openstackclient/network/v2/network_segment.py | 4 +- .../network/v2/network_segment_range.py | 3 +- openstackclient/network/v2/port.py | 4 +- openstackclient/network/v2/router.py | 4 +- openstackclient/network/v2/security_group.py | 3 +- .../network/v2/security_group_rule.py | 4 +- openstackclient/network/v2/subnet.py | 4 +- openstackclient/network/v2/subnet_pool.py | 3 +- .../tests/unit/network/test_sdk_utils.py | 59 ----------------- 30 files changed, 37 insertions(+), 256 deletions(-) delete mode 100644 openstackclient/common/sdk_utils.py delete mode 100644 openstackclient/network/sdk_utils.py delete mode 100644 openstackclient/tests/unit/network/test_sdk_utils.py diff --git a/openstackclient/common/sdk_utils.py b/openstackclient/common/sdk_utils.py deleted file mode 100644 index af9c74f944..0000000000 --- a/openstackclient/common/sdk_utils.py +++ /dev/null @@ -1,58 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - - -def get_osc_show_columns_for_sdk_resource( - sdk_resource, - osc_column_map, - invisible_columns=None -): - """Get and filter the display and attribute columns for an SDK resource. - - Common utility function for preparing the output of an OSC show command. - Some of the columns may need to get renamed, others made invisible. - - :param sdk_resource: An SDK resource - :param osc_column_map: A hash of mappings for display column names - :param invisible_columns: A list of invisible column names - - :returns: Two tuples containing the names of the display and attribute - columns - """ - - if getattr(sdk_resource, 'allow_get', None) is not None: - resource_dict = sdk_resource.to_dict( - body=True, headers=False, ignore_none=False) - else: - resource_dict = sdk_resource - - # Build the OSC column names to display for the SDK resource. - attr_map = {} - display_columns = list(resource_dict.keys()) - invisible_columns = [] if invisible_columns is None else invisible_columns - for col_name in invisible_columns: - if col_name in display_columns: - display_columns.remove(col_name) - for sdk_attr, osc_attr in osc_column_map.items(): - if sdk_attr in display_columns: - attr_map[osc_attr] = sdk_attr - display_columns.remove(sdk_attr) - if osc_attr not in display_columns: - display_columns.append(osc_attr) - sorted_display_columns = sorted(display_columns) - - # Build the SDK attribute names for the OSC column names. - attr_columns = [] - for column in sorted_display_columns: - new_column = attr_map[column] if column in attr_map else column - attr_columns.append(new_column) - return tuple(sorted_display_columns), tuple(attr_columns) diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index 64aa3fcdab..43ccf5d212 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -28,7 +28,6 @@ from osc_lib.command import command from osc_lib import utils -from openstackclient.common import sdk_utils from openstackclient.i18n import _ if os.name == "nt": @@ -48,15 +47,17 @@ def _get_columns(item): - # Trick sdk_utils to return URI attribute column_map = { 'is_protected': 'protected', 'owner_id': 'owner' } - hidden_columns = ['location', 'checksum', - 'copy_from', 'created_at', 'status', 'updated_at'] - return sdk_utils.get_osc_show_columns_for_sdk_resource( - item.to_dict(), column_map, hidden_columns) + hidden_columns = [ + 'location', 'checksum', 'copy_from', 'created_at', 'status', + 'updated_at', + ] + return utils.get_osc_show_columns_for_sdk_resource( + item.to_dict(), column_map, hidden_columns, + ) _formatters = { diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index c1f46d2d99..becb54f456 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -31,7 +31,6 @@ from osc_lib import utils from openstackclient.common import progressbar -from openstackclient.common import sdk_utils from openstackclient.i18n import _ from openstackclient.identity import common @@ -99,13 +98,13 @@ def _format_image(image, human_readable=False): def _get_member_columns(item): - # Trick sdk_utils to return URI attribute column_map = { 'image_id': 'image_id' } hidden_columns = ['id', 'location', 'name'] - return sdk_utils.get_osc_show_columns_for_sdk_resource( - item.to_dict(), column_map, hidden_columns) + return utils.get_osc_show_columns_for_sdk_resource( + item.to_dict(), column_map, hidden_columns, + ) def get_data_file(args): diff --git a/openstackclient/network/sdk_utils.py b/openstackclient/network/sdk_utils.py deleted file mode 100644 index cff3071354..0000000000 --- a/openstackclient/network/sdk_utils.py +++ /dev/null @@ -1,63 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import munch - - -def get_osc_show_columns_for_sdk_resource( - sdk_resource, - osc_column_map, - invisible_columns=None -): - """Get and filter the display and attribute columns for an SDK resource. - - Common utility function for preparing the output of an OSC show command. - Some of the columns may need to get renamed, others made invisible. - - :param sdk_resource: An SDK resource - :param osc_column_map: A hash of mappings for display column names - :param invisible_columns: A list of invisible column names - - :returns: Two tuples containing the names of the display and attribute - columns - """ - - if getattr(sdk_resource, 'allow_get', None) is not None: - resource_dict = sdk_resource.to_dict( - body=True, headers=False, ignore_none=False) - else: - resource_dict = sdk_resource - - # Build the OSC column names to display for the SDK resource. - attr_map = {} - display_columns = list(resource_dict.keys()) - for col_name in display_columns: - if isinstance(resource_dict[col_name], munch.Munch): - display_columns.remove(col_name) - invisible_columns = [] if invisible_columns is None else invisible_columns - for col_name in invisible_columns: - if col_name in display_columns: - display_columns.remove(col_name) - for sdk_attr, osc_attr in osc_column_map.items(): - if sdk_attr in display_columns: - attr_map[osc_attr] = sdk_attr - display_columns.remove(sdk_attr) - if osc_attr not in display_columns: - display_columns.append(osc_attr) - sorted_display_columns = sorted(display_columns) - - # Build the SDK attribute names for the OSC column names. - attr_columns = [] - for column in sorted_display_columns: - new_column = attr_map[column] if column in attr_map else column - attr_columns.append(new_column) - return tuple(sorted_display_columns), tuple(attr_columns) diff --git a/openstackclient/network/v2/address_group.py b/openstackclient/network/v2/address_group.py index fc83470053..9017047fac 100644 --- a/openstackclient/network/v2/address_group.py +++ b/openstackclient/network/v2/address_group.py @@ -23,8 +23,6 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common -from openstackclient.network import sdk_utils - LOG = logging.getLogger(__name__) @@ -33,7 +31,7 @@ def _get_columns(item): column_map = { 'tenant_id': 'project_id', } - return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + return utils.get_osc_show_columns_for_sdk_resource(item, column_map) def _format_addresses(addresses): diff --git a/openstackclient/network/v2/address_scope.py b/openstackclient/network/v2/address_scope.py index cd27678ee9..5748793a57 100644 --- a/openstackclient/network/v2/address_scope.py +++ b/openstackclient/network/v2/address_scope.py @@ -22,8 +22,6 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common -from openstackclient.network import sdk_utils - LOG = logging.getLogger(__name__) @@ -33,7 +31,7 @@ def _get_columns(item): 'is_shared': 'shared', 'tenant_id': 'project_id', } - return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + return utils.get_osc_show_columns_for_sdk_resource(item, column_map) def _get_attrs(client_manager, parsed_args): diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index 25b2a1baf6..0951565cb6 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -19,7 +19,6 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common -from openstackclient.network import sdk_utils _formatters = { @@ -31,7 +30,7 @@ def _get_network_columns(item): column_map = { 'tenant_id': 'project_id', } - return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + return utils.get_osc_show_columns_for_sdk_resource(item, column_map) def _get_columns(item): diff --git a/openstackclient/network/v2/floating_ip_port_forwarding.py b/openstackclient/network/v2/floating_ip_port_forwarding.py index 71b0b7da63..f137174cf1 100644 --- a/openstackclient/network/v2/floating_ip_port_forwarding.py +++ b/openstackclient/network/v2/floating_ip_port_forwarding.py @@ -12,6 +12,7 @@ # """Floating IP Port Forwarding action implementations""" + import logging from osc_lib.command import command @@ -20,8 +21,6 @@ from openstackclient.i18n import _ from openstackclient.network import common -from openstackclient.network import sdk_utils - LOG = logging.getLogger(__name__) @@ -30,7 +29,7 @@ def _get_columns(item): column_map = { 'tenant_id': 'project_id', } - return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + return utils.get_osc_show_columns_for_sdk_resource(item, column_map) class CreateFloatingIPPortForwarding(command.ShowOne, diff --git a/openstackclient/network/v2/ip_availability.py b/openstackclient/network/v2/ip_availability.py index ddc88e557e..6a3c67e21b 100644 --- a/openstackclient/network/v2/ip_availability.py +++ b/openstackclient/network/v2/ip_availability.py @@ -19,7 +19,6 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common -from openstackclient.network import sdk_utils _formatters = { 'subnet_ip_availability': format_columns.ListDictColumn, @@ -30,7 +29,7 @@ def _get_columns(item): column_map = { 'tenant_id': 'project_id', } - return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + return utils.get_osc_show_columns_for_sdk_resource(item, column_map) # TODO(ankur-gupta-f): Use the SDK resource mapped attribute names once diff --git a/openstackclient/network/v2/l3_conntrack_helper.py b/openstackclient/network/v2/l3_conntrack_helper.py index 94788823ab..9fc33d8f15 100644 --- a/openstackclient/network/v2/l3_conntrack_helper.py +++ b/openstackclient/network/v2/l3_conntrack_helper.py @@ -20,15 +20,13 @@ from osc_lib import utils from openstackclient.i18n import _ -from openstackclient.network import sdk_utils - LOG = logging.getLogger(__name__) def _get_columns(item): column_map = {} - return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + return utils.get_osc_show_columns_for_sdk_resource(item, column_map) def _get_attrs(client, parsed_args): diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index b8eb9f014b..191e4aa8e2 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -21,7 +21,6 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common -from openstackclient.network import sdk_utils class AdminStateColumn(cliff_columns.FormattableColumn): @@ -62,14 +61,14 @@ def _get_columns_network(item): 'tenant_id': 'project_id', 'tags': 'tags', } - return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + return utils.get_osc_show_columns_for_sdk_resource(item, column_map) def _get_columns_compute(item): column_map = { 'tenant_id': 'project_id', } - return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + return utils.get_osc_show_columns_for_sdk_resource(item, column_map) def _get_attrs_network(client_manager, parsed_args): diff --git a/openstackclient/network/v2/network_agent.py b/openstackclient/network/v2/network_agent.py index 1678485434..c995e36cb8 100644 --- a/openstackclient/network/v2/network_agent.py +++ b/openstackclient/network/v2/network_agent.py @@ -22,8 +22,6 @@ from osc_lib import utils from openstackclient.i18n import _ -from openstackclient.network import sdk_utils - LOG = logging.getLogger(__name__) @@ -52,7 +50,7 @@ def _get_network_columns(item): 'is_admin_state_up': 'admin_state_up', 'is_alive': 'alive', } - return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + return utils.get_osc_show_columns_for_sdk_resource(item, column_map) class AddNetworkToAgent(command.Command): diff --git a/openstackclient/network/v2/network_auto_allocated_topology.py b/openstackclient/network/v2/network_auto_allocated_topology.py index 36f392006e..7b7df4d75c 100644 --- a/openstackclient/network/v2/network_auto_allocated_topology.py +++ b/openstackclient/network/v2/network_auto_allocated_topology.py @@ -20,7 +20,6 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common -from openstackclient.network import sdk_utils LOG = logging.getLogger(__name__) @@ -29,7 +28,7 @@ def _get_columns(item): column_map = { 'tenant_id': 'project_id', } - return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + return utils.get_osc_show_columns_for_sdk_resource(item, column_map) def _format_check_resource_columns(): diff --git a/openstackclient/network/v2/network_flavor.py b/openstackclient/network/v2/network_flavor.py index 9e758ae29c..6e3a5a0432 100644 --- a/openstackclient/network/v2/network_flavor.py +++ b/openstackclient/network/v2/network_flavor.py @@ -22,8 +22,6 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common -from openstackclient.network import sdk_utils - LOG = logging.getLogger(__name__) @@ -34,7 +32,7 @@ def _get_columns(item): 'tenant_id': 'project_id', } - return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + return utils.get_osc_show_columns_for_sdk_resource(item, column_map) def _get_attrs(client_manager, parsed_args): diff --git a/openstackclient/network/v2/network_flavor_profile.py b/openstackclient/network/v2/network_flavor_profile.py index 0212e0d9ba..df7cfb7430 100644 --- a/openstackclient/network/v2/network_flavor_profile.py +++ b/openstackclient/network/v2/network_flavor_profile.py @@ -20,8 +20,6 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common -from openstackclient.network import sdk_utils - LOG = logging.getLogger(__name__) @@ -32,7 +30,7 @@ def _get_columns(item): 'tenant_id': 'project_id', } - return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + return utils.get_osc_show_columns_for_sdk_resource(item, column_map) def _get_attrs(client_manager, parsed_args): diff --git a/openstackclient/network/v2/network_meter.py b/openstackclient/network/v2/network_meter.py index f8f188a806..8b63de2c7a 100644 --- a/openstackclient/network/v2/network_meter.py +++ b/openstackclient/network/v2/network_meter.py @@ -22,7 +22,6 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common -from openstackclient.network import sdk_utils LOG = logging.getLogger(__name__) @@ -32,7 +31,7 @@ def _get_columns(item): 'is_shared': 'shared', 'tenant_id': 'project_id', } - return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + return utils.get_osc_show_columns_for_sdk_resource(item, column_map) def _get_attrs(client_manager, parsed_args): diff --git a/openstackclient/network/v2/network_meter_rule.py b/openstackclient/network/v2/network_meter_rule.py index 06362fa14c..4117d04342 100644 --- a/openstackclient/network/v2/network_meter_rule.py +++ b/openstackclient/network/v2/network_meter_rule.py @@ -22,7 +22,6 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common -from openstackclient.network import sdk_utils LOG = logging.getLogger(__name__) @@ -31,7 +30,7 @@ def _get_columns(item): column_map = { 'tenant_id': 'project_id', } - return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + return utils.get_osc_show_columns_for_sdk_resource(item, column_map) def _get_attrs(client_manager, parsed_args): diff --git a/openstackclient/network/v2/network_qos_policy.py b/openstackclient/network/v2/network_qos_policy.py index 7300a5c0ac..8d4312484b 100644 --- a/openstackclient/network/v2/network_qos_policy.py +++ b/openstackclient/network/v2/network_qos_policy.py @@ -22,8 +22,6 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common -from openstackclient.network import sdk_utils - LOG = logging.getLogger(__name__) @@ -33,7 +31,7 @@ def _get_columns(item): 'is_shared': 'shared', 'tenant_id': 'project_id', } - return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + return utils.get_osc_show_columns_for_sdk_resource(item, column_map) def _get_attrs(client_manager, parsed_args): diff --git a/openstackclient/network/v2/network_qos_rule.py b/openstackclient/network/v2/network_qos_rule.py index f30a5aeb1f..4bf72d269a 100644 --- a/openstackclient/network/v2/network_qos_rule.py +++ b/openstackclient/network/v2/network_qos_rule.py @@ -21,8 +21,6 @@ from openstackclient.i18n import _ from openstackclient.network import common -from openstackclient.network import sdk_utils - RULE_TYPE_BANDWIDTH_LIMIT = 'bandwidth-limit' RULE_TYPE_DSCP_MARKING = 'dscp-marking' @@ -51,7 +49,7 @@ def _get_columns(item): column_map = { 'tenant_id': 'project_id', } - return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + return utils.get_osc_show_columns_for_sdk_resource(item, column_map) def _check_type_parameters(attrs, type, is_create): diff --git a/openstackclient/network/v2/network_qos_rule_type.py b/openstackclient/network/v2/network_qos_rule_type.py index 7b92c8ad4b..036b682fae 100644 --- a/openstackclient/network/v2/network_qos_rule_type.py +++ b/openstackclient/network/v2/network_qos_rule_type.py @@ -17,7 +17,6 @@ from osc_lib import utils from openstackclient.i18n import _ -from openstackclient.network import sdk_utils def _get_columns(item): @@ -26,7 +25,7 @@ def _get_columns(item): "drivers": "drivers", } invisible_columns = ["id", "name"] - return sdk_utils.get_osc_show_columns_for_sdk_resource( + return utils.get_osc_show_columns_for_sdk_resource( item, column_map, invisible_columns) diff --git a/openstackclient/network/v2/network_rbac.py b/openstackclient/network/v2/network_rbac.py index 692a43857d..edca872cf2 100644 --- a/openstackclient/network/v2/network_rbac.py +++ b/openstackclient/network/v2/network_rbac.py @@ -22,8 +22,6 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common -from openstackclient.network import sdk_utils - LOG = logging.getLogger(__name__) @@ -33,7 +31,7 @@ def _get_columns(item): 'target_tenant': 'target_project_id', 'tenant_id': 'project_id', } - return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + return utils.get_osc_show_columns_for_sdk_resource(item, column_map) def _get_attrs(client_manager, parsed_args): diff --git a/openstackclient/network/v2/network_segment.py b/openstackclient/network/v2/network_segment.py index 14a8edabe5..e18ac47529 100644 --- a/openstackclient/network/v2/network_segment.py +++ b/openstackclient/network/v2/network_segment.py @@ -21,14 +21,12 @@ from openstackclient.i18n import _ from openstackclient.network import common -from openstackclient.network import sdk_utils - LOG = logging.getLogger(__name__) def _get_columns(item): - return sdk_utils.get_osc_show_columns_for_sdk_resource(item, {}) + return utils.get_osc_show_columns_for_sdk_resource(item, {}) class CreateNetworkSegment(command.ShowOne, diff --git a/openstackclient/network/v2/network_segment_range.py b/openstackclient/network/v2/network_segment_range.py index ee414407ee..e105111dd0 100644 --- a/openstackclient/network/v2/network_segment_range.py +++ b/openstackclient/network/v2/network_segment_range.py @@ -26,14 +26,13 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common -from openstackclient.network import sdk_utils LOG = logging.getLogger(__name__) def _get_columns(item): - return sdk_utils.get_osc_show_columns_for_sdk_resource(item, {}) + return utils.get_osc_show_columns_for_sdk_resource(item, {}) def _get_ranges(item): diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index ecb2382a85..132c384a0e 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -29,8 +29,6 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common -from openstackclient.network import sdk_utils - LOG = logging.getLogger(__name__) @@ -67,7 +65,7 @@ def _get_columns(item): 'is_port_security_enabled': 'port_security_enabled', 'tenant_id': 'project_id', } - return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + return utils.get_osc_show_columns_for_sdk_resource(item, column_map) class JSONKeyValueAction(argparse.Action): diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index d15300a0b7..dde4eda99f 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -28,8 +28,6 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common -from openstackclient.network import sdk_utils - LOG = logging.getLogger(__name__) @@ -83,7 +81,7 @@ def _get_columns(item): if item.is_distributed is None: invisible_columns.append('is_distributed') column_map.pop('is_distributed') - return sdk_utils.get_osc_show_columns_for_sdk_resource( + return utils.get_osc_show_columns_for_sdk_resource( item, column_map, invisible_columns) diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py index 49dc14e403..37d2dc5be0 100644 --- a/openstackclient/network/v2/security_group.py +++ b/openstackclient/network/v2/security_group.py @@ -23,7 +23,6 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common -from openstackclient.network import sdk_utils from openstackclient.network import utils as network_utils @@ -90,7 +89,7 @@ def _get_columns(item): 'security_group_rules': 'rules', 'tenant_id': 'project_id', } - return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + return utils.get_osc_show_columns_for_sdk_resource(item, column_map) # TODO(abhiraut): Use the SDK resource mapped attribute names once the diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index e273ded3d6..252dcb05a8 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -23,10 +23,8 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common -from openstackclient.network import sdk_utils from openstackclient.network import utils as network_utils - LOG = logging.getLogger(__name__) @@ -76,7 +74,7 @@ def _get_columns(item): column_map = { 'tenant_id': 'project_id', } - return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + return utils.get_osc_show_columns_for_sdk_resource(item, column_map) def _convert_to_lowercase(string): diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 09fd7c7c39..c07fab4170 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -27,8 +27,6 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common -from openstackclient.network import sdk_utils - LOG = logging.getLogger(__name__) @@ -143,7 +141,7 @@ def _get_columns(item): } # Do not show this column when displaying a subnet invisible_columns = ['use_default_subnet_pool', 'prefix_length'] - return sdk_utils.get_osc_show_columns_for_sdk_resource( + return utils.get_osc_show_columns_for_sdk_resource( item, column_map, invisible_columns=invisible_columns diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index bdf7aba805..6b88888c0e 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -25,7 +25,6 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common -from openstackclient.network import sdk_utils LOG = logging.getLogger(__name__) @@ -39,7 +38,7 @@ def _get_columns(item): 'minimum_prefix_length': 'min_prefixlen', 'tenant_id': 'project_id', } - return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + return utils.get_osc_show_columns_for_sdk_resource(item, column_map) _formatters = { diff --git a/openstackclient/tests/unit/network/test_sdk_utils.py b/openstackclient/tests/unit/network/test_sdk_utils.py deleted file mode 100644 index d1efa7e40a..0000000000 --- a/openstackclient/tests/unit/network/test_sdk_utils.py +++ /dev/null @@ -1,59 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from openstackclient.network import sdk_utils -from openstackclient.tests.unit import utils as tests_utils - - -class TestSDKUtils(tests_utils.TestCase): - - def setUp(self): - super(TestSDKUtils, self).setUp() - - def _test_get_osc_show_columns_for_sdk_resource( - self, sdk_resource, column_map, - expected_display_columns, expected_attr_columns): - display_columns, attr_columns = \ - sdk_utils.get_osc_show_columns_for_sdk_resource( - sdk_resource, column_map) - self.assertEqual(expected_display_columns, display_columns) - self.assertEqual(expected_attr_columns, attr_columns) - - def test_get_osc_show_columns_for_sdk_resource_empty(self): - self._test_get_osc_show_columns_for_sdk_resource( - {}, {}, tuple(), tuple()) - - def test_get_osc_show_columns_for_sdk_resource_empty_map(self): - self._test_get_osc_show_columns_for_sdk_resource( - {'foo': 'foo1'}, {}, - ('foo',), ('foo',)) - - def test_get_osc_show_columns_for_sdk_resource_empty_data(self): - self._test_get_osc_show_columns_for_sdk_resource( - {}, {'foo': 'foo_map'}, - ('foo_map',), ('foo_map',)) - - def test_get_osc_show_columns_for_sdk_resource_map(self): - self._test_get_osc_show_columns_for_sdk_resource( - {'foo': 'foo1'}, {'foo': 'foo_map'}, - ('foo_map',), ('foo',)) - - def test_get_osc_show_columns_for_sdk_resource_map_dup(self): - self._test_get_osc_show_columns_for_sdk_resource( - {'foo': 'foo1', 'foo_map': 'foo1'}, {'foo': 'foo_map'}, - ('foo_map',), ('foo',)) - - def test_get_osc_show_columns_for_sdk_resource_map_full(self): - self._test_get_osc_show_columns_for_sdk_resource( - {'foo': 'foo1', 'bar': 'bar1'}, - {'foo': 'foo_map', 'new': 'bar'}, - ('bar', 'foo_map'), ('bar', 'foo')) From 728401bbd76afc4d31b4f22e44bf98d1de40ef46 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Fri, 8 Oct 2021 18:20:31 +0100 Subject: [PATCH 2421/3095] Remove remnants of 'six' Just one entry left. Remove it. Change-Id: Ia12173ecb7f3fed4a1195a46ebf9b096d917b3b6 Signed-off-by: Stephen Finucane --- lower-constraints.txt | 1 - openstackclient/tests/unit/common/test_progressbar.py | 11 +++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/lower-constraints.txt b/lower-constraints.txt index 861749b738..b98b8432a9 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -81,7 +81,6 @@ requestsexceptions==1.2.0 rfc3986==0.3.1 Routes==2.3.1 simplejson==3.5.1 -six==1.15.0 statsd==3.2.1 stestr==1.0.0 stevedore==2.0.1 diff --git a/openstackclient/tests/unit/common/test_progressbar.py b/openstackclient/tests/unit/common/test_progressbar.py index 7bc0b6baf4..a624fc438a 100644 --- a/openstackclient/tests/unit/common/test_progressbar.py +++ b/openstackclient/tests/unit/common/test_progressbar.py @@ -11,10 +11,9 @@ # under the License. # +import io import sys -import six - from openstackclient.common import progressbar from openstackclient.tests.unit import utils @@ -23,7 +22,7 @@ class TestProgressBarWrapper(utils.TestCase): def test_iter_file_display_progress_bar(self): size = 98304 - file_obj = six.StringIO('X' * size) + file_obj = io.StringIO('X' * size) saved_stdout = sys.stdout try: sys.stdout = output = FakeTTYStdout() @@ -41,7 +40,7 @@ def test_iter_file_display_progress_bar(self): def test_iter_file_no_tty(self): size = 98304 - file_obj = six.StringIO('X' * size) + file_obj = io.StringIO('X' * size) saved_stdout = sys.stdout try: sys.stdout = output = FakeNoTTYStdout() @@ -56,7 +55,7 @@ def test_iter_file_no_tty(self): sys.stdout = saved_stdout -class FakeTTYStdout(six.StringIO): +class FakeTTYStdout(io.StringIO): """A Fake stdout that try to emulate a TTY device as much as possible.""" def isatty(self): @@ -67,7 +66,7 @@ def write(self, data): if data.startswith('\r'): self.seek(0) data = data[1:] - return six.StringIO.write(self, data) + return io.StringIO.write(self, data) class FakeNoTTYStdout(FakeTTYStdout): From 43639e1118a757fd1a00d9ea999db43133c40763 Mon Sep 17 00:00:00 2001 From: Cyril Roelandt Date: Tue, 26 Oct 2021 15:53:35 +0200 Subject: [PATCH 2422/3095] Fix typos Change-Id: Idd502c8df21da79ff3b9339870f38378f5337879 --- openstackclient/common/progressbar.py | 2 +- openstackclient/common/quota.py | 4 ++-- openstackclient/compute/v2/server.py | 4 ++-- openstackclient/identity/v3/endpoint_group.py | 2 +- openstackclient/tests/functional/base.py | 4 ++-- .../tests/functional/volume/v1/test_volume_type.py | 4 ++-- .../tests/functional/volume/v2/test_volume_type.py | 4 ++-- .../tests/functional/volume/v3/test_volume_type.py | 4 ++-- openstackclient/tests/unit/compute/v2/fakes.py | 4 ++-- openstackclient/volume/v1/volume_type.py | 2 +- openstackclient/volume/v2/volume_snapshot.py | 2 +- 11 files changed, 18 insertions(+), 18 deletions(-) diff --git a/openstackclient/common/progressbar.py b/openstackclient/common/progressbar.py index ef767a9cf8..7678aceba0 100644 --- a/openstackclient/common/progressbar.py +++ b/openstackclient/common/progressbar.py @@ -17,7 +17,7 @@ class _ProgressBarBase(object): - """A progress bar provider for a wrapped obect. + """A progress bar provider for a wrapped object. Base abstract class used by specific class wrapper to show a progress bar when the wrapped object are consumed. diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index 643cb4e474..00bbc51459 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -205,7 +205,7 @@ def get_network_quota(self, parsed_args): class ListQuota(command.Lister, BaseQuota): _description = _( "List quotas for all projects with non-default quota values or " - "list detailed quota informations for requested project") + "list detailed quota information for requested project") def _get_detailed_quotas(self, parsed_args): columns = ( @@ -230,7 +230,7 @@ def _get_detailed_quotas(self, parsed_args): result = [] for resource, values in quotas.items(): # NOTE(slaweq): there is no detailed quotas info for some resources - # and it should't be displayed here + # and it shouldn't be displayed here if type(values) is dict: result.append({ 'resource': resource, diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index c11f4b5781..b0de65a7c3 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -2466,7 +2466,7 @@ def take_action(self, parsed_args): # and 'image' missing in the server response during # infrastructure failure situations. # For those servers with partial constructs we just skip the - # processing of the image and flavor informations. + # processing of the image and flavor information. if not hasattr(s, 'image') or not hasattr(s, 'flavor'): continue if 'id' in s.image: @@ -4292,7 +4292,7 @@ def _show_progress(progress): server_obj.shelve() - # if we don't hav to wait, either because it was requested explicitly + # if we don't have to wait, either because it was requested explicitly # or is required implicitly, then our job is done if not parsed_args.wait and not parsed_args.offload: return diff --git a/openstackclient/identity/v3/endpoint_group.py b/openstackclient/identity/v3/endpoint_group.py index cbe27edb4b..9bb026a9b8 100644 --- a/openstackclient/identity/v3/endpoint_group.py +++ b/openstackclient/identity/v3/endpoint_group.py @@ -268,7 +268,7 @@ def get_parser(self, prog_name): parser.add_argument( '--name', metavar='', - help=_('New enpoint group name'), + help=_('New endpoint group name'), ) parser.add_argument( '--filters', diff --git a/openstackclient/tests/functional/base.py b/openstackclient/tests/functional/base.py index 0ed7dff8c4..3a4c13944e 100644 --- a/openstackclient/tests/functional/base.py +++ b/openstackclient/tests/functional/base.py @@ -42,7 +42,7 @@ class TestCase(testtools.TestCase): def openstack(cls, cmd, cloud=ADMIN_CLOUD, fail_ok=False): """Executes openstackclient command for the given action - NOTE(dtroyer): There is a subtle distinction between pasing + NOTE(dtroyer): There is a subtle distinction between passing cloud=None and cloud='': for compatibility reasons passing cloud=None continues to include the option '--os-auth-type none' in the command while passing cloud='' omits the '--os-auth-type' @@ -61,7 +61,7 @@ def openstack(cls, cmd, cloud=ADMIN_CLOUD, fail_ok=False): fail_ok=fail_ok ) else: - # Execure command with an explicit cloud specified + # Execute command with an explicit cloud specified return execute( 'openstack --os-cloud=' + cloud + ' ' + cmd, fail_ok=fail_ok diff --git a/openstackclient/tests/functional/volume/v1/test_volume_type.py b/openstackclient/tests/functional/volume/v1/test_volume_type.py index fb8dabdb04..7434b5b36b 100644 --- a/openstackclient/tests/functional/volume/v1/test_volume_type.py +++ b/openstackclient/tests/functional/volume/v1/test_volume_type.py @@ -118,10 +118,10 @@ def test_multi_delete(self): raw_output = self.openstack(cmd) self.assertOutput('', raw_output) - # NOTE: Add some basic funtional tests with the old format to + # NOTE: Add some basic functional tests with the old format to # make sure the command works properly, need to change # these to new test format when beef up all tests for - # volume tye commands. + # volume type commands. def test_encryption_type(self): encryption_type = uuid.uuid4().hex # test create new encryption type diff --git a/openstackclient/tests/functional/volume/v2/test_volume_type.py b/openstackclient/tests/functional/volume/v2/test_volume_type.py index 3f1a6ea8a5..861c393d80 100644 --- a/openstackclient/tests/functional/volume/v2/test_volume_type.py +++ b/openstackclient/tests/functional/volume/v2/test_volume_type.py @@ -139,10 +139,10 @@ def test_multi_delete(self): raw_output = self.openstack(cmd) self.assertOutput('', raw_output) - # NOTE: Add some basic funtional tests with the old format to + # NOTE: Add some basic functional tests with the old format to # make sure the command works properly, need to change # these to new test format when beef up all tests for - # volume tye commands. + # volume type commands. def test_encryption_type(self): name = uuid.uuid4().hex encryption_type = uuid.uuid4().hex diff --git a/openstackclient/tests/functional/volume/v3/test_volume_type.py b/openstackclient/tests/functional/volume/v3/test_volume_type.py index 79d4096998..165c625c20 100644 --- a/openstackclient/tests/functional/volume/v3/test_volume_type.py +++ b/openstackclient/tests/functional/volume/v3/test_volume_type.py @@ -139,10 +139,10 @@ def test_multi_delete(self): raw_output = self.openstack(cmd) self.assertOutput('', raw_output) - # NOTE: Add some basic funtional tests with the old format to + # NOTE: Add some basic functional tests with the old format to # make sure the command works properly, need to change # these to new test format when beef up all tests for - # volume tye commands. + # volume type commands. def test_encryption_type(self): name = uuid.uuid4().hex encryption_type = uuid.uuid4().hex diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index 3142a24489..fd0a570969 100644 --- a/openstackclient/tests/unit/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -1285,7 +1285,7 @@ def create_one_server_group(attrs=None): class FakeServerGroupV264(object): - """Fake one server group fo API >= 2.64""" + """Fake one server group for API >= 2.64""" @staticmethod def create_one_server_group(attrs=None): @@ -1400,7 +1400,7 @@ def create_one_comp_quota(attrs=None): @staticmethod def create_one_default_comp_quota(attrs=None): - """Crate one quota""" + """Create one quota""" attrs = attrs or {} diff --git a/openstackclient/volume/v1/volume_type.py b/openstackclient/volume/v1/volume_type.py index 4f015d13f6..c584943e10 100644 --- a/openstackclient/volume/v1/volume_type.py +++ b/openstackclient/volume/v1/volume_type.py @@ -411,7 +411,7 @@ def get_parser(self, prog_name): "--encryption-type", action="store_true", help=_("Remove the encryption type for this volume type " - "(admin oly)"), + "(admin only)"), ) return parser diff --git a/openstackclient/volume/v2/volume_snapshot.py b/openstackclient/volume/v2/volume_snapshot.py index 656f59d4ac..53d8d27fed 100644 --- a/openstackclient/volume/v2/volume_snapshot.py +++ b/openstackclient/volume/v2/volume_snapshot.py @@ -98,7 +98,7 @@ def get_parser(self, prog_name): "--remote-source", metavar="", action=parseractions.KeyValueAction, - help=_("The attribute(s) of the exsiting remote volume snapshot " + help=_("The attribute(s) of the existing remote volume snapshot " "(admin required) (repeat option to specify multiple " "attributes) e.g.: '--remote-source source-name=test_name " "--remote-source source-id=test_id'"), From 57aad01886fe9d98210496a92d517aa067c049a1 Mon Sep 17 00:00:00 2001 From: Diwei Zhu Date: Tue, 26 Oct 2021 14:47:46 +0000 Subject: [PATCH 2423/3095] Switch server backup to sdk. Switch this command from novaclient to SDK. As this is the first command related to server that we are migrating, we need to extend our test fakes to support fake Server resources. The extended fakes will replace the old ones once all commands related to server are switched. Change-Id: If476fb1614a64320ed071bbda35e941bf3290a2e --- openstackclient/compute/v2/server_backup.py | 9 +- .../tests/unit/compute/v2/fakes.py | 150 ++++++++++++------ .../unit/compute/v2/test_server_backup.py | 23 ++- ...server-backup-to-sdk-0f170baf38e98b40.yaml | 4 + 4 files changed, 119 insertions(+), 67 deletions(-) create mode 100644 releasenotes/notes/migrate-server-backup-to-sdk-0f170baf38e98b40.yaml diff --git a/openstackclient/compute/v2/server_backup.py b/openstackclient/compute/v2/server_backup.py index b1b821b24e..53891991b4 100644 --- a/openstackclient/compute/v2/server_backup.py +++ b/openstackclient/compute/v2/server_backup.py @@ -72,12 +72,9 @@ def _show_progress(progress): self.app.stderr.write('\rProgress: %s' % progress) self.app.stderr.flush() - compute_client = self.app.client_manager.compute + compute_client = self.app.client_manager.sdk_connection.compute - server = utils.find_resource( - compute_client.servers, - parsed_args.server, - ) + server = compute_client.find_server(parsed_args.server) # Set sane defaults as this API wants all mouths to be fed if parsed_args.name is None: @@ -93,7 +90,7 @@ def _show_progress(progress): else: backup_rotation = parsed_args.rotate - compute_client.servers.backup( + compute_client.backup_server( server.id, backup_name, backup_type, diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index 3142a24489..23468ebc4d 100644 --- a/openstackclient/tests/unit/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -20,6 +20,7 @@ from novaclient import api_versions from openstack.compute.v2 import flavor as _flavor +from openstack.compute.v2 import server from openstackclient.api import compute_v2 from openstackclient.tests.unit import fakes @@ -73,7 +74,7 @@ class FakeAggregate(object): def create_one_aggregate(attrs=None): """Create a fake aggregate. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :return: A FakeResource object, with id and other attributes @@ -104,7 +105,7 @@ def create_one_aggregate(attrs=None): def create_aggregates(attrs=None, count=2): """Create multiple fake aggregates. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :param int count: The number of aggregates to fake @@ -255,7 +256,7 @@ class FakeAgent(object): def create_one_agent(attrs=None): """Create a fake agent. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :return: A FakeResource object, with agent_id, os, and so on @@ -285,7 +286,7 @@ def create_one_agent(attrs=None): def create_agents(attrs=None, count=2): """Create multiple fake agents. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :param int count: The number of agents to fake @@ -306,7 +307,7 @@ class FakeExtension(object): def create_one_extension(attrs=None): """Create a fake extension. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :return: A FakeResource object with name, namespace, etc. @@ -342,7 +343,7 @@ class FakeHypervisor(object): def create_one_hypervisor(attrs=None): """Create a fake hypervisor. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :return: A FakeResource object, with id, hypervisor_hostname, and so on @@ -390,7 +391,7 @@ def create_one_hypervisor(attrs=None): def create_hypervisors(attrs=None, count=2): """Create multiple fake hypervisors. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :param int count: The number of hypervisors to fake @@ -411,7 +412,7 @@ class FakeHypervisorStats(object): def create_one_hypervisor_stats(attrs=None): """Create a fake hypervisor stats. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :return: A FakeResource object, with count, current_workload, and so on @@ -450,7 +451,7 @@ def create_one_hypervisor_stats(attrs=None): def create_hypervisors_stats(attrs=None, count=2): """Create multiple fake hypervisors stats. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :param int count: The number of hypervisors to fake @@ -472,7 +473,7 @@ class FakeSecurityGroup(object): def create_one_security_group(attrs=None): """Create a fake security group. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :return: A FakeResource object, with id, name, etc. @@ -496,7 +497,7 @@ def create_one_security_group(attrs=None): def create_security_groups(attrs=None, count=2): """Create multiple fake security groups. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :param int count: The number of security groups to fake @@ -537,7 +538,7 @@ class FakeSecurityGroupRule(object): def create_one_security_group_rule(attrs=None): """Create a fake security group rule. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :return: A FakeResource object, with id, etc. @@ -564,7 +565,7 @@ def create_one_security_group_rule(attrs=None): def create_security_group_rules(attrs=None, count=2): """Create multiple fake security group rules. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :param int count: The number of security group rules to fake @@ -586,9 +587,9 @@ class FakeServer(object): def create_one_server(attrs=None, methods=None): """Create a fake server. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes - :param Dictionary methods: + :param dict methods: A dictionary with all methods :return: A FakeResource object, with id, name, metadata, and so on @@ -622,9 +623,9 @@ def create_one_server(attrs=None, methods=None): def create_servers(attrs=None, methods=None, count=2): """Create multiple fake servers. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes - :param Dictionary methods: + :param dict methods: A dictionary with all methods :param int count: The number of servers to fake @@ -637,6 +638,59 @@ def create_servers(attrs=None, methods=None, count=2): return servers + @staticmethod + def create_one_sdk_server(attrs=None, methods=None): + """Create a fake server for testing migration to sdk + + :param dict attrs: + A dictionary with all attributes + :param dict methods: + A dictionary with all methods + :return: + A openstack.compute.v2.server.Server object, + with id, name, metadata, and so on + """ + attrs = attrs or {} + methods = methods or {} + + # Set default attributes. + server_info = { + 'id': 'server-id-' + uuid.uuid4().hex, + 'name': 'server-name-' + uuid.uuid4().hex, + 'metadata': {}, + 'image': { + 'id': 'image-id-' + uuid.uuid4().hex, + }, + 'flavor': { + 'id': 'flavor-id-' + uuid.uuid4().hex, + }, + 'OS-EXT-STS:power_state': 1, + } + + # Overwrite default attributes. + server_info.update(attrs) + return server.Server(**server_info) + + @staticmethod + def create_sdk_servers(attrs=None, methods=None, count=2): + """Create multiple fake servers for testing migration to sdk + + :param dict attrs: + A dictionary with all attributes + :param dict methods: + A dictionary with all methods + :param int count: + The number of servers to fake + :return: + A list of openstack.compute.v2.server.Server objects + faking the servers + """ + servers = [] + for i in range(0, count): + servers.append(FakeServer.create_one_sdk_server(attrs, methods)) + + return servers + @staticmethod def get_servers(servers=None, count=2): """Get an iterable MagicMock object with a list of faked servers. @@ -705,7 +759,7 @@ class FakeService(object): def create_one_service(attrs=None): """Create a fake service. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :return: A FakeResource object, with id, host, binary, and so on @@ -738,7 +792,7 @@ def create_one_service(attrs=None): def create_services(attrs=None, count=2): """Create multiple fake services. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :param int count: The number of services to fake @@ -759,7 +813,7 @@ class FakeFlavor(object): def create_one_flavor(attrs=None): """Create a fake flavor. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :return: A FakeResource object, with id, name, ram, vcpus, and so on @@ -793,7 +847,7 @@ def create_one_flavor(attrs=None): def create_flavors(attrs=None, count=2): """Create multiple fake flavors. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :param int count: The number of flavors to fake @@ -833,7 +887,7 @@ class FakeFlavorAccess(object): def create_one_flavor_access(attrs=None): """Create a fake flavor access. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :return: A FakeResource object, with flavor_id, tenat_id @@ -862,7 +916,7 @@ class FakeKeypair(object): def create_one_keypair(attrs=None, no_pri=False): """Create a fake keypair - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :return: A FakeResource object, name, fingerprint, and so on @@ -892,7 +946,7 @@ def create_one_keypair(attrs=None, no_pri=False): def create_keypairs(attrs=None, count=2): """Create multiple fake keypairs. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :param int count: The number of keypairs to fake @@ -933,7 +987,7 @@ class FakeAvailabilityZone(object): def create_one_availability_zone(attrs=None): """Create a fake AZ. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :return: A FakeResource object with zoneName, zoneState, etc. @@ -966,7 +1020,7 @@ def create_one_availability_zone(attrs=None): def create_availability_zones(attrs=None, count=2): """Create multiple fake AZs. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :param int count: The number of AZs to fake @@ -989,7 +1043,7 @@ class FakeFloatingIP(object): def create_one_floating_ip(attrs=None): """Create a fake floating ip. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :return: A FakeResource object, with id, ip, and so on @@ -1014,7 +1068,7 @@ def create_one_floating_ip(attrs=None): def create_floating_ips(attrs=None, count=2): """Create multiple fake floating ips. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :param int count: The number of floating ips to fake @@ -1053,7 +1107,7 @@ class FakeFloatingIPPool(object): def create_one_floating_ip_pool(attrs=None): """Create a fake floating ip pool. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :return: A FakeResource object, with name, etc @@ -1075,7 +1129,7 @@ def create_one_floating_ip_pool(attrs=None): def create_floating_ip_pools(attrs=None, count=2): """Create multiple fake floating ip pools. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :param int count: The number of floating ip pools to fake @@ -1097,7 +1151,7 @@ class FakeNetwork(object): def create_one_network(attrs=None): """Create a fake network. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :return: A FakeResource object, with id, label, cidr and so on @@ -1149,7 +1203,7 @@ def create_one_network(attrs=None): def create_networks(attrs=None, count=2): """Create multiple fake networks. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :param int count: The number of networks to fake @@ -1189,7 +1243,7 @@ class FakeHost(object): def create_one_host(attrs=None): """Create a fake host. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :return: A FakeResource object, with uuid and other attributes @@ -1243,7 +1297,7 @@ class FakeServerGroup(object): def _create_one_server_group(attrs=None): """Create a fake server group - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :return: A FakeResource object, with id and other attributes @@ -1273,7 +1327,7 @@ def _create_one_server_group(attrs=None): def create_one_server_group(attrs=None): """Create a fake server group - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :return: A FakeResource object, with id and other attributes @@ -1291,7 +1345,7 @@ class FakeServerGroupV264(object): def create_one_server_group(attrs=None): """Create a fake server group - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :return: A FakeResource object, with id and other attributes @@ -1309,7 +1363,7 @@ class FakeUsage(object): def create_one_usage(attrs=None): """Create a fake usage. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :return: A FakeResource object, with tenant_id and other attributes @@ -1351,7 +1405,7 @@ def create_one_usage(attrs=None): def create_usages(attrs=None, count=2): """Create multiple fake services. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :param int count: The number of services to fake @@ -1575,9 +1629,9 @@ class FakeMigration(object): def create_one_migration(attrs=None, methods=None): """Create a fake migration. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes - :param Dictionary methods: + :param dict methods: A dictionary with all methods :return: A FakeResource object, with id, type, and so on @@ -1617,9 +1671,9 @@ def create_one_migration(attrs=None, methods=None): def create_migrations(attrs=None, methods=None, count=2): """Create multiple fake migrations. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes - :param Dictionary methods: + :param dict methods: A dictionary with all methods :param int count: The number of migrations to fake @@ -1642,9 +1696,9 @@ class FakeServerMigration(object): def create_one_server_migration(attrs=None, methods=None): """Create a fake server migration. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes - :param Dictionary methods: + :param dict methods: A dictionary with all methods :return: A FakeResource object, with id, type, and so on @@ -1695,9 +1749,9 @@ class FakeVolumeAttachment(object): def create_one_volume_attachment(attrs=None, methods=None): """Create a fake volume attachment. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes - :param Dictionary methods: + :param dict methods: A dictionary with all methods :return: A FakeResource object, with id, device, and so on @@ -1733,9 +1787,9 @@ def create_one_volume_attachment(attrs=None, methods=None): def create_volume_attachments(attrs=None, methods=None, count=2): """Create multiple fake volume attachments (BDMs). - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes - :param Dictionary methods: + :param dict methods: A dictionary with all methods :param int count: The number of volume attachments to fake diff --git a/openstackclient/tests/unit/compute/v2/test_server_backup.py b/openstackclient/tests/unit/compute/v2/test_server_backup.py index 0012d70062..1644baae00 100644 --- a/openstackclient/tests/unit/compute/v2/test_server_backup.py +++ b/openstackclient/tests/unit/compute/v2/test_server_backup.py @@ -28,8 +28,9 @@ def setUp(self): super(TestServerBackup, self).setUp() # Get a shortcut to the compute client ServerManager Mock - self.servers_mock = self.app.client_manager.compute.servers - self.servers_mock.reset_mock() + self.app.client_manager.sdk_connection = mock.Mock() + self.app.client_manager.sdk_connection.compute = mock.Mock() + self.sdk_client = self.app.client_manager.sdk_connection.compute # Get a shortcut to the image client ImageManager Mock self.images_mock = self.app.client_manager.image @@ -42,14 +43,14 @@ def setUp(self): self.methods = {} def setup_servers_mock(self, count): - servers = compute_fakes.FakeServer.create_servers( + servers = compute_fakes.FakeServer.create_sdk_servers( attrs=self.attrs, methods=self.methods, count=count, ) - # This is the return value for utils.find_resource() - self.servers_mock.get = compute_fakes.FakeServer.get_servers( + # This is the return value for compute_client.find_server() + self.sdk_client.find_server = compute_fakes.FakeServer.get_servers( servers, 0, ) @@ -130,8 +131,7 @@ def test_server_backup_defaults(self): # data to be shown. columns, data = self.cmd.take_action(parsed_args) - # ServerManager.backup(server, backup_name, backup_type, rotation) - self.servers_mock.backup.assert_called_with( + self.sdk_client.backup_server.assert_called_with( servers[0].id, servers[0].name, '', @@ -164,8 +164,7 @@ def test_server_backup_create_options(self): # data to be shown. columns, data = self.cmd.take_action(parsed_args) - # ServerManager.backup(server, backup_name, backup_type, rotation) - self.servers_mock.backup.assert_called_with( + self.sdk_client.backup_server.assert_called_with( servers[0].id, 'image', 'daily', @@ -212,8 +211,7 @@ def test_server_backup_wait_fail(self, mock_wait_for_status): parsed_args, ) - # ServerManager.backup(server, backup_name, backup_type, rotation) - self.servers_mock.backup.assert_called_with( + self.sdk_client.backup_server.assert_called_with( servers[0].id, 'image', 'daily', @@ -254,8 +252,7 @@ def test_server_backup_wait_ok(self, mock_wait_for_status): # data to be shown. columns, data = self.cmd.take_action(parsed_args) - # ServerManager.backup(server, backup_name, backup_type, rotation) - self.servers_mock.backup.assert_called_with( + self.sdk_client.backup_server.assert_called_with( servers[0].id, 'image', 'daily', diff --git a/releasenotes/notes/migrate-server-backup-to-sdk-0f170baf38e98b40.yaml b/releasenotes/notes/migrate-server-backup-to-sdk-0f170baf38e98b40.yaml new file mode 100644 index 0000000000..4f5cef27f1 --- /dev/null +++ b/releasenotes/notes/migrate-server-backup-to-sdk-0f170baf38e98b40.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Migrate openstack server backup from novaclient to sdk. From 8cb0a28607e7f8b9eb4bb71a95b39230d43a969c Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Tue, 2 Nov 2021 09:59:27 +0000 Subject: [PATCH 2424/3095] compute: Don't warn if disk overcommit params unset Due to a small logic error, we were emitting a warning about a deprecated option when the user tried to live migrate an instance using microversion 2.25 even though the user hadn't actually set that option. Correct this. Change-Id: Ib61e817bd4ced9b5533e7c7f9d8f0b45fe81c211 Signed-off-by: Stephen Finucane Story: 2009657 Task: 43836 --- openstackclient/compute/v2/server.py | 8 ++++++-- .../tests/unit/compute/v2/test_server.py | 20 +++++++++---------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index c11f4b5781..d0edaf0769 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -2621,7 +2621,7 @@ def get_parser(self, prog_name): disk_group.add_argument( '--disk-overcommit', action='store_true', - default=False, + default=None, help=_( 'Allow disk over-commit on the destination host' '(supported with --os-compute-api-version 2.24 or below)' @@ -2631,7 +2631,6 @@ def get_parser(self, prog_name): '--no-disk-overcommit', dest='disk_overcommit', action='store_false', - default=False, help=_( 'Do not over-commit disk on the destination host (default)' '(supported with --os-compute-api-version 2.24 or below)' @@ -2693,6 +2692,11 @@ def _show_progress(progress): if compute_client.api_version < api_versions.APIVersion('2.25'): kwargs['disk_over_commit'] = parsed_args.disk_overcommit + # We can't use an argparse default value because then we can't + # distinguish between explicit 'False' and unset for the below + # case (microversion >= 2.25) + if kwargs['disk_over_commit'] is None: + kwargs['disk_over_commit'] = False elif parsed_args.disk_overcommit is not None: # TODO(stephenfin): Raise an error here in OSC 7.0 msg = _( diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 3d8c17fdeb..285b2b411e 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -4812,7 +4812,7 @@ def test_server_migrate_no_options(self): verifylist = [ ('live_migration', False), ('block_migration', None), - ('disk_overcommit', False), + ('disk_overcommit', None), ('wait', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -4834,7 +4834,7 @@ def test_server_migrate_with_host_2_56(self): ('live_migration', False), ('host', 'fakehost'), ('block_migration', None), - ('disk_overcommit', False), + ('disk_overcommit', None), ('wait', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -4856,7 +4856,7 @@ def test_server_migrate_with_block_migration(self): verifylist = [ ('live_migration', False), ('block_migration', True), - ('disk_overcommit', False), + ('disk_overcommit', None), ('wait', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -4897,7 +4897,7 @@ def test_server_migrate_with_host_pre_2_56(self): ('live_migration', False), ('host', 'fakehost'), ('block_migration', None), - ('disk_overcommit', False), + ('disk_overcommit', None), ('wait', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -4923,7 +4923,7 @@ def test_server_live_migrate(self): ('live_migration', True), ('host', None), ('block_migration', None), - ('disk_overcommit', False), + ('disk_overcommit', None), ('wait', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -4947,7 +4947,7 @@ def test_server_live_migrate_with_host(self): ('live_migration', True), ('host', 'fakehost'), ('block_migration', None), - ('disk_overcommit', False), + ('disk_overcommit', None), ('wait', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -4975,7 +4975,7 @@ def test_server_live_migrate_with_host_pre_v230(self): ('live_migration', True), ('host', 'fakehost'), ('block_migration', None), - ('disk_overcommit', False), + ('disk_overcommit', None), ('wait', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -5002,7 +5002,7 @@ def test_server_block_live_migrate(self): verifylist = [ ('live_migration', True), ('block_migration', True), - ('disk_overcommit', False), + ('disk_overcommit', None), ('wait', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -5088,7 +5088,7 @@ def test_server_migrate_with_wait(self, mock_wait_for_status): verifylist = [ ('live_migration', False), ('block_migration', None), - ('disk_overcommit', False), + ('disk_overcommit', None), ('wait', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -5108,7 +5108,7 @@ def test_server_migrate_with_wait_fails(self, mock_wait_for_status): verifylist = [ ('live_migration', False), ('block_migration', None), - ('disk_overcommit', False), + ('disk_overcommit', None), ('wait', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) From 442838ed158cd8fb4168877744a60de5cfca29cc Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Tue, 2 Nov 2021 09:34:32 +0000 Subject: [PATCH 2425/3095] compute: Use correct command class for 'show migration' We should be inheriting from 'ShowOne'. Failure to do so results in a tuple being dumped to the screen. Not what we intended. While we're here, we update the docstring of this command to clarify the command's intent. Nova does not provide an API to retrieve an individual migration record for a cold migration or completed live migration. As such, the 'server migration show' command only works for in-progress live-migrations. Change-Id: I2e2fe3da7d642b9e8e3d930603dcde178cd68cde Signed-off-by: Stephen Finucane Story: 2009658 Task: 43837 --- openstackclient/compute/v2/server.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index d0edaf0769..1dc56fc7e6 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -2967,8 +2967,13 @@ def take_action(self, parsed_args): return self.print_migrations(parsed_args, compute_client, migrations) -class ShowMigration(command.Command): - """Show a migration for a given server.""" +class ShowMigration(command.ShowOne): + """Show an in-progress live migration for a given server. + + Note that it is not possible to show cold migrations or completed + live-migrations. Use 'openstack server migration list' to get details for + these. + """ def get_parser(self, prog_name): parser = super().get_parser(prog_name) From 163cb01e46fc3f906154a7045fdbe9342cd446c7 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 3 Nov 2021 11:31:04 +0000 Subject: [PATCH 2426/3095] compute: Return details of attached volumes The API behind the 'server add volume' command returns details of the created volume attachment, however, we were dropping these results rather than displaying them to the user. Correct this. Change-Id: I3f7e121220d29422ccf4e6940de2f28bb8496c83 Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server.py | 22 ++++- .../tests/unit/compute/v2/test_server.py | 91 +++++++++++++++++-- ...or-server-add-volume-f75277ad58e31024.yaml | 5 + 3 files changed, 108 insertions(+), 10 deletions(-) create mode 100644 releasenotes/notes/show-result-for-server-add-volume-f75277ad58e31024.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index c11f4b5781..80bdc61224 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -496,7 +496,7 @@ def take_action(self, parsed_args): server.add_security_group(security_group['id']) -class AddServerVolume(command.Command): +class AddServerVolume(command.ShowOne): _description = _( "Add volume to server. " "Specify ``--os-compute-api-version 2.20`` or higher to add a volume " @@ -595,12 +595,30 @@ def take_action(self, parsed_args): kwargs['delete_on_termination'] = False - compute_client.volumes.create_server_volume( + volume_attachment = compute_client.volumes.create_server_volume( server.id, volume.id, **kwargs ) + columns = ('id', 'serverId', 'volumeId', 'device') + column_headers = ('ID', 'Server ID', 'Volume ID', 'Device') + if compute_client.api_version >= api_versions.APIVersion('2.49'): + columns += ('tag',) + column_headers += ('Tag',) + if compute_client.api_version >= api_versions.APIVersion('2.79'): + columns += ('delete_on_termination',) + column_headers += ('Delete On Termination',) + + return ( + column_headers, + utils.get_item_properties( + volume_attachment, + columns, + mixed_case_fields=('serverId', 'volumeId'), + ) + ) + # TODO(stephenfin): Replace with 'MultiKeyValueAction' when we no longer # support '--nic=auto' and '--nic=none' diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 3d8c17fdeb..585f49de84 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -670,6 +670,11 @@ def setUp(self): def test_server_add_volume(self): servers = self.setup_servers_mock(count=1) + volume_attachment = \ + compute_fakes.FakeVolumeAttachment.create_one_volume_attachment() + self.servers_volumes_mock.create_server_volume.return_value = \ + volume_attachment + arglist = [ '--device', '/dev/sdb', servers[0].id, @@ -683,11 +688,20 @@ def test_server_add_volume(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) + expected_columns = ('ID', 'Server ID', 'Volume ID', 'Device') + expected_data = ( + volume_attachment.id, + volume_attachment.serverId, + volume_attachment.volumeId, + volume_attachment.device, + ) + + columns, data = self.cmd.take_action(parsed_args) self.servers_volumes_mock.create_server_volume.assert_called_once_with( servers[0].id, self.volume.id, device='/dev/sdb') - self.assertIsNone(result) + self.assertEqual(expected_columns, columns) + self.assertEqual(expected_data, data) def test_server_add_volume_with_tag(self): # requires API 2.49 or later @@ -695,6 +709,11 @@ def test_server_add_volume_with_tag(self): '2.49') servers = self.setup_servers_mock(count=1) + volume_attachment = \ + compute_fakes.FakeVolumeAttachment.create_one_volume_attachment() + self.servers_volumes_mock.create_server_volume.return_value = \ + volume_attachment + arglist = [ '--device', '/dev/sdb', '--tag', 'foo', @@ -710,11 +729,21 @@ def test_server_add_volume_with_tag(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) + expected_columns = ('ID', 'Server ID', 'Volume ID', 'Device', 'Tag') + expected_data = ( + volume_attachment.id, + volume_attachment.serverId, + volume_attachment.volumeId, + volume_attachment.device, + volume_attachment.tag, + ) + + columns, data = self.cmd.take_action(parsed_args) self.servers_volumes_mock.create_server_volume.assert_called_once_with( servers[0].id, self.volume.id, device='/dev/sdb', tag='foo') - self.assertIsNone(result) + self.assertEqual(expected_columns, columns) + self.assertEqual(expected_data, data) def test_server_add_volume_with_tag_pre_v249(self): self.app.client_manager.compute.api_version = api_versions.APIVersion( @@ -746,6 +775,11 @@ def test_server_add_volume_with_enable_delete_on_termination(self): '2.79') servers = self.setup_servers_mock(count=1) + volume_attachment = \ + compute_fakes.FakeVolumeAttachment.create_one_volume_attachment() + self.servers_volumes_mock.create_server_volume.return_value = \ + volume_attachment + arglist = [ '--enable-delete-on-termination', '--device', '/dev/sdb', @@ -761,18 +795,41 @@ def test_server_add_volume_with_enable_delete_on_termination(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) + expected_columns = ( + 'ID', + 'Server ID', + 'Volume ID', + 'Device', + 'Tag', + 'Delete On Termination', + ) + expected_data = ( + volume_attachment.id, + volume_attachment.serverId, + volume_attachment.volumeId, + volume_attachment.device, + volume_attachment.tag, + volume_attachment.delete_on_termination, + ) + + columns, data = self.cmd.take_action(parsed_args) self.servers_volumes_mock.create_server_volume.assert_called_once_with( servers[0].id, self.volume.id, device='/dev/sdb', delete_on_termination=True) - self.assertIsNone(result) + self.assertEqual(expected_columns, columns) + self.assertEqual(expected_data, data) def test_server_add_volume_with_disable_delete_on_termination(self): self.app.client_manager.compute.api_version = api_versions.APIVersion( '2.79') servers = self.setup_servers_mock(count=1) + volume_attachment = \ + compute_fakes.FakeVolumeAttachment.create_one_volume_attachment() + self.servers_volumes_mock.create_server_volume.return_value = \ + volume_attachment + arglist = [ '--disable-delete-on-termination', '--device', '/dev/sdb', @@ -788,12 +845,30 @@ def test_server_add_volume_with_disable_delete_on_termination(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) + expected_columns = ( + 'ID', + 'Server ID', + 'Volume ID', + 'Device', + 'Tag', + 'Delete On Termination', + ) + expected_data = ( + volume_attachment.id, + volume_attachment.serverId, + volume_attachment.volumeId, + volume_attachment.device, + volume_attachment.tag, + volume_attachment.delete_on_termination, + ) + + columns, data = self.cmd.take_action(parsed_args) self.servers_volumes_mock.create_server_volume.assert_called_once_with( servers[0].id, self.volume.id, device='/dev/sdb', delete_on_termination=False) - self.assertIsNone(result) + self.assertEqual(expected_columns, columns) + self.assertEqual(expected_data, data) def test_server_add_volume_with_enable_delete_on_termination_pre_v279( self, diff --git a/releasenotes/notes/show-result-for-server-add-volume-f75277ad58e31024.yaml b/releasenotes/notes/show-result-for-server-add-volume-f75277ad58e31024.yaml new file mode 100644 index 0000000000..7e062d6d5f --- /dev/null +++ b/releasenotes/notes/show-result-for-server-add-volume-f75277ad58e31024.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + The ``server add volume`` command will now return details of the created + volume attachment upon successful attachment. From 2183a611475090347863917f6c90f0f38cd80893 Mon Sep 17 00:00:00 2001 From: Diwei Zhu Date: Thu, 28 Oct 2021 02:16:23 +0000 Subject: [PATCH 2427/3095] Switch openstack server add port/network to using sdk. The old novaclient.v2.server.Server.interface_attach() method is replaced with proxy.create_server_interface(). In swargs, 'net_id' and 'port_id' are mutual-exclusive, if one of them is given with value, the other one cannot be None, as the API would responde with 400 (None is not string). In unit test, temporary method 'setup_sdk_servers_mock' is added, because other tests are still using the old 'setup_servers_mock'. Functional tests are added. Releasenote is generated. Change-Id: I9899f0509febc5143560a1859ae6344d0a6d1427 --- openstackclient/compute/v2/server.py | 23 ++++---- .../functional/compute/v2/test_server.py | 48 +++++++++++++++ .../tests/unit/compute/v2/test_server.py | 58 +++++++++++++------ ...work-add-port-to-sdk-7d81b25f59cfbec9.yaml | 4 ++ 4 files changed, 104 insertions(+), 29 deletions(-) create mode 100644 releasenotes/notes/migrate-server-add-network-add-port-to-sdk-7d81b25f59cfbec9.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index c11f4b5781..fcd7d69cdc 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -27,6 +27,7 @@ from novaclient import api_versions from novaclient.v2 import servers from openstack import exceptions as sdk_exceptions +from openstack import utils as sdk_utils from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command @@ -378,10 +379,10 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute + compute_client = self.app.client_manager.sdk_connection.compute - server = utils.find_resource( - compute_client.servers, parsed_args.server) + server = compute_client.find_server( + parsed_args.server, ignore_missing=False) if self.app.client_manager.is_network_endpoint_enabled(): network_client = self.app.client_manager.network @@ -392,12 +393,11 @@ def take_action(self, parsed_args): kwargs = { 'port_id': port_id, - 'net_id': None, 'fixed_ip': None, } if parsed_args.tag: - if compute_client.api_version < api_versions.APIVersion("2.49"): + if not sdk_utils.supports_microversion(compute_client, '2.49'): msg = _( '--os-compute-api-version 2.49 or greater is required to ' 'support the --tag option' @@ -405,7 +405,7 @@ def take_action(self, parsed_args): raise exceptions.CommandError(msg) kwargs['tag'] = parsed_args.tag - server.interface_attach(**kwargs) + compute_client.create_server_interface(server, **kwargs) class AddNetwork(command.Command): @@ -434,10 +434,10 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute + compute_client = self.app.client_manager.sdk_connection.compute - server = utils.find_resource( - compute_client.servers, parsed_args.server) + server = compute_client.find_server( + parsed_args.server, ignore_missing=False) if self.app.client_manager.is_network_endpoint_enabled(): network_client = self.app.client_manager.network @@ -447,13 +447,12 @@ def take_action(self, parsed_args): net_id = parsed_args.network kwargs = { - 'port_id': None, 'net_id': net_id, 'fixed_ip': None, } if parsed_args.tag: - if compute_client.api_version < api_versions.APIVersion('2.49'): + if not sdk_utils.supports_microversion(compute_client, '2.49'): msg = _( '--os-compute-api-version 2.49 or greater is required to ' 'support the --tag option' @@ -462,7 +461,7 @@ def take_action(self, parsed_args): kwargs['tag'] = parsed_args.tag - server.interface_attach(**kwargs) + compute_client.create_server_interface(server, **kwargs) class AddServerSecurityGroup(command.Command): diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py index 9cf2fc7f0f..59b1fad5f9 100644 --- a/openstackclient/tests/functional/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -1071,3 +1071,51 @@ def test_server_create_with_empty_network_option_latest(self): # networks and the test didn't specify a specific network. self.assertNotIn('nics are required after microversion 2.36', e.stderr) + + def test_server_add_remove_network_port(self): + name = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'server create -f json ' + + '--network private ' + + '--flavor ' + self.flavor_name + ' ' + + '--image ' + self.image_name + ' ' + + '--wait ' + + name + )) + + self.assertIsNotNone(cmd_output['id']) + self.assertEqual(name, cmd_output['name']) + + self.openstack( + 'server add network ' + name + ' public') + + cmd_output = json.loads(self.openstack( + 'server show -f json ' + name + )) + + addresses = cmd_output['addresses'] + self.assertIn('public', addresses) + + port_name = 'test-port' + + cmd_output = json.loads(self.openstack( + 'port list -f json' + )) + self.assertNotIn(port_name, cmd_output) + + cmd_output = json.loads(self.openstack( + 'port create -f json ' + + '--network private ' + port_name + )) + self.assertIsNotNone(cmd_output['id']) + + self.openstack('server add port ' + name + ' ' + port_name) + + cmd_output = json.loads(self.openstack( + 'server show -f json ' + name + )) + + # TODO(diwei): test remove network/port after the commands are switched + + self.openstack('server delete ' + name) + self.openstack('port delete ' + port_name) diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 3d8c17fdeb..705a96b79c 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -24,6 +24,7 @@ import iso8601 from novaclient import api_versions from openstack import exceptions as sdk_exceptions +from openstack import utils as sdk_utils from osc_lib.cli import format_columns from osc_lib import exceptions from osc_lib import utils as common_utils @@ -69,6 +70,10 @@ def setUp(self): self.servers_mock = self.app.client_manager.compute.servers self.servers_mock.reset_mock() + self.app.client_manager.sdk_connection = mock.Mock() + self.app.client_manager.sdk_connection.compute = mock.Mock() + self.sdk_client = self.app.client_manager.sdk_connection.compute + # Get a shortcut to the compute client ServerMigrationsManager Mock self.server_migrations_mock = \ self.app.client_manager.compute.server_migrations @@ -133,6 +138,21 @@ def setup_servers_mock(self, count): 0) return servers + def setup_sdk_servers_mock(self, count): + servers = compute_fakes.FakeServer.create_sdk_servers( + attrs=self.attrs, + methods=self.methods, + count=count, + ) + + # This is the return value for compute_client.find_server() + self.sdk_client.find_server = compute_fakes.FakeServer.get_servers( + servers, + 0, + ) + + return servers + def run_method_with_servers(self, method_name, server_count): servers = self.setup_servers_mock(server_count) @@ -570,7 +590,7 @@ def setUp(self): self.app.client_manager.network.find_port = self.find_port def _test_server_add_port(self, port_id): - servers = self.setup_servers_mock(count=1) + servers = self.setup_sdk_servers_mock(count=1) port = 'fake-port' arglist = [ @@ -585,8 +605,8 @@ def _test_server_add_port(self, port_id): result = self.cmd.take_action(parsed_args) - servers[0].interface_attach.assert_called_once_with( - port_id=port_id, net_id=None, fixed_ip=None) + self.sdk_client.create_server_interface.assert_called_once_with( + servers[0], port_id=port_id, fixed_ip=None) self.assertIsNone(result) def test_server_add_port(self): @@ -599,11 +619,12 @@ def test_server_add_port_no_neutron(self): self._test_server_add_port('fake-port') self.find_port.assert_not_called() - def test_server_add_port_with_tag(self): + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=True) + def test_server_add_port_with_tag(self, sm_mock): self.app.client_manager.compute.api_version = api_versions.APIVersion( '2.49') - servers = self.setup_servers_mock(count=1) + servers = self.setup_sdk_servers_mock(count=1) self.find_port.return_value.id = 'fake-port' arglist = [ servers[0].id, @@ -620,13 +641,14 @@ def test_server_add_port_with_tag(self): result = self.cmd.take_action(parsed_args) self.assertIsNone(result) - servers[0].interface_attach.assert_called_once_with( + self.sdk_client.create_server_interface.assert_called_once_with( + servers[0], port_id='fake-port', - net_id=None, fixed_ip=None, tag='tag1') - def test_server_add_port_with_tag_pre_v249(self): + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=False) + def test_server_add_port_with_tag_pre_v249(self, sm_mock): self.app.client_manager.compute.api_version = api_versions.APIVersion( '2.48') @@ -891,7 +913,7 @@ def setUp(self): self.app.client_manager.network.find_network = self.find_network def _test_server_add_network(self, net_id): - servers = self.setup_servers_mock(count=1) + servers = self.setup_sdk_servers_mock(count=1) network = 'fake-network' arglist = [ @@ -906,8 +928,8 @@ def _test_server_add_network(self, net_id): result = self.cmd.take_action(parsed_args) - servers[0].interface_attach.assert_called_once_with( - port_id=None, net_id=net_id, fixed_ip=None) + self.sdk_client.create_server_interface.assert_called_once_with( + servers[0], net_id=net_id, fixed_ip=None) self.assertIsNone(result) def test_server_add_network(self): @@ -920,11 +942,12 @@ def test_server_add_network_no_neutron(self): self._test_server_add_network('fake-network') self.find_network.assert_not_called() - def test_server_add_network_with_tag(self): + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=True) + def test_server_add_network_with_tag(self, sm_mock): self.app.client_manager.compute.api_version = api_versions.APIVersion( '2.49') - servers = self.setup_servers_mock(count=1) + servers = self.setup_sdk_servers_mock(count=1) self.find_network.return_value.id = 'fake-network' arglist = [ @@ -942,18 +965,19 @@ def test_server_add_network_with_tag(self): result = self.cmd.take_action(parsed_args) self.assertIsNone(result) - servers[0].interface_attach.assert_called_once_with( - port_id=None, + self.sdk_client.create_server_interface.assert_called_once_with( + servers[0], net_id='fake-network', fixed_ip=None, tag='tag1' ) - def test_server_add_network_with_tag_pre_v249(self): + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=False) + def test_server_add_network_with_tag_pre_v249(self, sm_mock): self.app.client_manager.compute.api_version = api_versions.APIVersion( '2.48') - servers = self.setup_servers_mock(count=1) + servers = self.setup_sdk_servers_mock(count=1) self.find_network.return_value.id = 'fake-network' arglist = [ diff --git a/releasenotes/notes/migrate-server-add-network-add-port-to-sdk-7d81b25f59cfbec9.yaml b/releasenotes/notes/migrate-server-add-network-add-port-to-sdk-7d81b25f59cfbec9.yaml new file mode 100644 index 0000000000..930d6bc597 --- /dev/null +++ b/releasenotes/notes/migrate-server-add-network-add-port-to-sdk-7d81b25f59cfbec9.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Migrate server add network/port from novaclient to openstacksdk. From 9acbd3e1052d533c1395eb59de4274170baed67b Mon Sep 17 00:00:00 2001 From: Thrivikram Mudunuri Date: Thu, 28 Oct 2021 18:05:03 -0400 Subject: [PATCH 2428/3095] Switch server image create to SDK Switch the server image create command from novaclient to SDK. Use the SDK versions of test fakes to support fake Server resources. Also, fetch updated image *after* waiting. If a user requests that we wait (--wait) for a server image to become active before returning, then we should probably return the final image. If we don't then the image can appear to be in a non-active state when it fact it's active. Correct this by fetching the image after the wait call. Change-Id: I83a403c035add9ab041ed6d59b5b29e42267f143 --- openstackclient/compute/v2/server_image.py | 18 ++++++------- .../unit/compute/v2/test_server_image.py | 27 +++++++++---------- ...-server-image-to-sdk-e3d8077ffe05bb3d.yaml | 4 +++ 3 files changed, 25 insertions(+), 24 deletions(-) create mode 100644 releasenotes/notes/migrate-create-server-image-to-sdk-e3d8077ffe05bb3d.yaml diff --git a/openstackclient/compute/v2/server_image.py b/openstackclient/compute/v2/server_image.py index 6c0e3b22cf..2021fae7c0 100644 --- a/openstackclient/compute/v2/server_image.py +++ b/openstackclient/compute/v2/server_image.py @@ -73,25 +73,23 @@ def _show_progress(progress): self.app.stdout.write('\rProgress: %s' % progress) self.app.stdout.flush() - compute_client = self.app.client_manager.compute + compute_client = self.app.client_manager.sdk_connection.compute + image_client = self.app.client_manager.image - server = utils.find_resource( - compute_client.servers, - parsed_args.server, + server = compute_client.find_server( + parsed_args.server, ignore_missing=False, ) + if parsed_args.name: image_name = parsed_args.name else: image_name = server.name - image_id = compute_client.servers.create_image( + image_id = compute_client.create_server_image( server.id, image_name, parsed_args.properties, - ) - - image_client = self.app.client_manager.image - image = image_client.find_image(image_id) + ).id if parsed_args.wait: if utils.wait_for_status( @@ -105,6 +103,8 @@ def _show_progress(progress): _('Error creating server image: %s'), parsed_args.server) raise exceptions.CommandError + image = image_client.find_image(image_id, ignore_missing=False) + if self.app.client_manager._api_version['image'] == '1': info = {} info.update(image._info) diff --git a/openstackclient/tests/unit/compute/v2/test_server_image.py b/openstackclient/tests/unit/compute/v2/test_server_image.py index 9b14428a27..b73cc763cb 100644 --- a/openstackclient/tests/unit/compute/v2/test_server_image.py +++ b/openstackclient/tests/unit/compute/v2/test_server_image.py @@ -27,8 +27,9 @@ def setUp(self): super(TestServerImage, self).setUp() # Get a shortcut to the compute client ServerManager Mock - self.servers_mock = self.app.client_manager.compute.servers - self.servers_mock.reset_mock() + self.app.client_manager.sdk_connection = mock.Mock() + self.app.client_manager.sdk_connection.compute = mock.Mock() + self.sdk_client = self.app.client_manager.sdk_connection.compute # Get a shortcut to the image client ImageManager Mock self.images_mock = self.app.client_manager.image @@ -41,14 +42,14 @@ def setUp(self): self.methods = {} def setup_servers_mock(self, count): - servers = compute_fakes.FakeServer.create_servers( + servers = compute_fakes.FakeServer.create_sdk_servers( attrs=self.attrs, methods=self.methods, count=count, ) - # This is the return value for utils.find_resource() - self.servers_mock.get = compute_fakes.FakeServer.get_servers( + # This is the return value for compute_client.find_server() + self.sdk_client.find_server = compute_fakes.FakeServer.get_servers( servers, 0, ) @@ -104,8 +105,8 @@ def setup_images_mock(self, count, servers=None): ) self.images_mock.find_image = mock.Mock(side_effect=images) - self.servers_mock.create_image = mock.Mock( - return_value=images[0].id, + self.sdk_client.create_server_image = mock.Mock( + return_value=images[0], ) return images @@ -126,8 +127,7 @@ def test_server_image_create_defaults(self): # data to be shown. columns, data = self.cmd.take_action(parsed_args) - # ServerManager.create_image(server, image_name, metadata=) - self.servers_mock.create_image.assert_called_with( + self.sdk_client.create_server_image.assert_called_with( servers[0].id, servers[0].name, None, @@ -157,8 +157,7 @@ def test_server_image_create_options(self): # data to be shown. columns, data = self.cmd.take_action(parsed_args) - # ServerManager.create_image(server, image_name, metadata=) - self.servers_mock.create_image.assert_called_with( + self.sdk_client.create_server_image.assert_called_with( servers[0].id, 'img-nam', {'key': 'value'}, @@ -188,8 +187,7 @@ def test_server_create_image_wait_fail(self, mock_wait_for_status): parsed_args, ) - # ServerManager.create_image(server, image_name, metadata=) - self.servers_mock.create_image.assert_called_with( + self.sdk_client.create_server_image.assert_called_with( servers[0].id, servers[0].name, None, @@ -221,8 +219,7 @@ def test_server_create_image_wait_ok(self, mock_wait_for_status): # data to be shown. columns, data = self.cmd.take_action(parsed_args) - # ServerManager.create_image(server, image_name, metadata=) - self.servers_mock.create_image.assert_called_with( + self.sdk_client.create_server_image.assert_called_with( servers[0].id, servers[0].name, None, diff --git a/releasenotes/notes/migrate-create-server-image-to-sdk-e3d8077ffe05bb3d.yaml b/releasenotes/notes/migrate-create-server-image-to-sdk-e3d8077ffe05bb3d.yaml new file mode 100644 index 0000000000..20f8d55018 --- /dev/null +++ b/releasenotes/notes/migrate-create-server-image-to-sdk-e3d8077ffe05bb3d.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Migrate openstack server image create from novaclient to sdk. From 690e9a13a232f522162adc109d32c8eee864814e Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 17 Nov 2021 10:28:40 +0000 Subject: [PATCH 2429/3095] image: Remove dead test helper methods These haven't been used since we switched the image commands from glanceclient to openstacksdk. There's more cleanup to be done here but that can be done later. Change-Id: I3de1f24323886b122b3a30660fb3de18eb7014e9 Signed-off-by: Stephen Finucane --- openstackclient/tests/unit/image/v1/fakes.py | 31 ---- openstackclient/tests/unit/image/v2/fakes.py | 173 ------------------- 2 files changed, 204 deletions(-) diff --git a/openstackclient/tests/unit/image/v1/fakes.py b/openstackclient/tests/unit/image/v1/fakes.py index add3978d1d..59ae5f7a2d 100644 --- a/openstackclient/tests/unit/image/v1/fakes.py +++ b/openstackclient/tests/unit/image/v1/fakes.py @@ -11,7 +11,6 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -# from unittest import mock import uuid @@ -25,36 +24,6 @@ image_id = 'im1' image_name = 'graven' -image_owner = 'baal' -image_protected = False -image_public = True -image_properties = { - 'Alpha': 'a', - 'Beta': 'b', - 'Gamma': 'g', -} -image_properties_str = "Alpha='a', Beta='b', Gamma='g'" -image_data = 'line 1\nline 2\n' -image_size = 0 - -IMAGE = { - 'id': image_id, - 'name': image_name, - 'container_format': '', - 'disk_format': '', - 'owner': image_owner, - 'min_disk': 0, - 'min_ram': 0, - 'is_public': image_public, - 'protected': image_protected, - 'properties': image_properties, - 'size': image_size, -} - -IMAGE_columns = tuple(sorted(IMAGE)) -IMAGE_output = dict(IMAGE) -IMAGE_output['properties'] = image_properties_str -IMAGE_data = tuple((IMAGE_output[x] for x in sorted(IMAGE_output))) class FakeImagev1Client(object): diff --git a/openstackclient/tests/unit/image/v2/fakes.py b/openstackclient/tests/unit/image/v2/fakes.py index 0d83f98b95..49ce400d92 100644 --- a/openstackclient/tests/unit/image/v2/fakes.py +++ b/openstackclient/tests/unit/image/v2/fakes.py @@ -11,16 +11,13 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -# -import copy import random from unittest import mock import uuid from openstack.image.v2 import image from openstack.image.v2 import member -from osc_lib.cli import format_columns from openstackclient.tests.unit import fakes from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes @@ -28,121 +25,6 @@ image_id = '0f41529e-7c12-4de8-be2d-181abb825b3c' image_name = 'graven' -image_owner = 'baal' -image_protected = False -image_visibility = 'public' -image_tags = [] -image_size = 0 - -IMAGE = { - 'id': image_id, - 'name': image_name, - 'owner': image_owner, - 'protected': image_protected, - 'visibility': image_visibility, - 'tags': image_tags, - 'size': image_size -} - -IMAGE_columns = tuple(sorted(IMAGE)) -IMAGE_data = tuple((IMAGE[x] for x in sorted(IMAGE))) - -IMAGE_SHOW = copy.copy(IMAGE) -IMAGE_SHOW['tags'] = format_columns.ListColumn(IMAGE_SHOW['tags']) -IMAGE_SHOW_data = tuple((IMAGE_SHOW[x] for x in sorted(IMAGE_SHOW))) - -# Just enough v2 schema to do some testing -IMAGE_schema = { - "additionalProperties": { - "type": "string" - }, - "name": "image", - "links": [ - { - "href": "{self}", - "rel": "self" - }, - { - "href": "{file}", - "rel": "enclosure" - }, - { - "href": "{schema}", - "rel": "describedby" - } - ], - "properties": { - "id": { - "pattern": "^([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}$", # noqa - "type": "string", - "description": "An identifier for the image" - }, - "name": { - "type": [ - "null", - "string" - ], - "description": "Descriptive name for the image", - "maxLength": 255 - }, - "owner": { - "type": [ - "null", - "string" - ], - "description": "Owner of the image", - "maxLength": 255 - }, - "protected": { - "type": "boolean", - "description": "If true, image will not be deletable." - }, - "self": { - "type": "string", - "description": "(READ-ONLY)" - }, - "schema": { - "type": "string", - "description": "(READ-ONLY)" - }, - "size": { - "type": [ - "null", - "integer", - "string" - ], - "description": "Size of image file in bytes (READ-ONLY)" - }, - "status": { - "enum": [ - "queued", - "saving", - "active", - "killed", - "deleted", - "pending_delete" - ], - "type": "string", - "description": "Status of the image (READ-ONLY)" - }, - "tags": { - "items": { - "type": "string", - "maxLength": 255 - }, - "type": "array", - "description": "List of strings related to the image" - }, - "visibility": { - "enum": [ - "public", - "private" - ], - "type": "string", - "description": "Scope of image accessibility" - }, - } -} class FakeImagev2Client(object): @@ -231,61 +113,6 @@ def create_images(attrs=None, count=2): return images - @staticmethod - def get_images(images=None, count=2): - """Get an iterable MagicMock object with a list of faked images. - - If images list is provided, then initialize the Mock object with the - list. Otherwise create one. - - :param List images: - A list of FakeResource objects faking images - :param Integer count: - The number of images to be faked - :return: - An iterable Mock object with side_effect set to a list of faked - images - """ - if images is None: - images = FakeImage.create_images(count) - - return mock.Mock(side_effect=images) - - @staticmethod - def get_image_columns(image=None): - """Get the image columns from a faked image object. - - :param image: - A FakeResource objects faking image - :return: - A tuple which may include the following keys: - ('id', 'name', 'owner', 'protected', 'visibility', 'tags') - """ - if image is not None: - return tuple(sorted(image)) - return IMAGE_columns - - @staticmethod - def get_image_data(image=None): - """Get the image data from a faked image object. - - :param image: - A FakeResource objects faking image - :return: - A tuple which may include the following values: - ('image-123', 'image-foo', 'admin', False, 'public', 'bar, baz') - """ - data_list = [] - if image is not None: - for x in sorted(image.keys()): - if x == 'tags': - # The 'tags' should be format_list - data_list.append( - format_columns.ListColumn(getattr(image, x))) - else: - data_list.append(getattr(image, x)) - return tuple(data_list) - @staticmethod def create_one_image_member(attrs=None): """Create a fake image member. From 2135a9ea05c79a11185ca87f6bb5ade3b71501bb Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 17 Nov 2021 10:35:51 +0000 Subject: [PATCH 2430/3095] image: Remove FakeImage test helper We're no longer creating fake versions of glanceclient's 'Resource' object but rather openstacksdk objects. As such, there's no point nesting things under a fake resource class. Change-Id: I39cd5302622f4542db9eebcccfad0cb90d077441 Signed-off-by: Stephen Finucane --- .../tests/unit/common/test_project_purge.py | 2 +- .../tests/unit/compute/v2/test_aggregate.py | 2 +- .../tests/unit/compute/v2/test_server.py | 28 ++-- .../unit/compute/v2/test_server_backup.py | 13 +- .../unit/compute/v2/test_server_image.py | 4 +- openstackclient/tests/unit/image/v1/fakes.py | 74 +++++----- .../tests/unit/image/v1/test_image.py | 11 +- openstackclient/tests/unit/image/v2/fakes.py | 133 ++++++++--------- .../tests/unit/image/v2/test_image.py | 134 +++++++++--------- .../tests/unit/volume/v1/test_volume.py | 4 +- .../tests/unit/volume/v2/test_volume.py | 4 +- 11 files changed, 187 insertions(+), 222 deletions(-) diff --git a/openstackclient/tests/unit/common/test_project_purge.py b/openstackclient/tests/unit/common/test_project_purge.py index adc48ce26f..5199093ce3 100644 --- a/openstackclient/tests/unit/common/test_project_purge.py +++ b/openstackclient/tests/unit/common/test_project_purge.py @@ -70,7 +70,7 @@ class TestProjectPurge(TestProjectPurgeInit): project = identity_fakes.FakeProject.create_one_project() server = compute_fakes.FakeServer.create_one_server() - image = image_fakes.FakeImage.create_one_image() + image = image_fakes.create_one_image() volume = volume_fakes.FakeVolume.create_one_volume() backup = volume_fakes.FakeBackup.create_one_backup() snapshot = volume_fakes.FakeSnapshot.create_one_snapshot() diff --git a/openstackclient/tests/unit/compute/v2/test_aggregate.py b/openstackclient/tests/unit/compute/v2/test_aggregate.py index 7c4fe5cbd3..071f2a3027 100644 --- a/openstackclient/tests/unit/compute/v2/test_aggregate.py +++ b/openstackclient/tests/unit/compute/v2/test_aggregate.py @@ -552,7 +552,7 @@ def test_aggregate_unset_no_option(self): class TestAggregateCacheImage(TestAggregate): - images = image_fakes.FakeImage.create_images(count=2) + images = image_fakes.create_images(count=2) def setUp(self): super(TestAggregateCacheImage, self).setUp() diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 9623cb0abf..f7ed4b16a4 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -1168,7 +1168,7 @@ def setUp(self): self.servers_mock.create.return_value = self.new_server - self.image = image_fakes.FakeImage.create_one_image() + self.image = image_fakes.create_one_image() self.find_image_mock.return_value = self.image self.get_image_mock.return_value = self.image @@ -2918,7 +2918,7 @@ def test_server_create_image_property(self): 'hypervisor_type': 'qemu', } - _image = image_fakes.FakeImage.create_one_image(image_info) + _image = image_fakes.create_one_image(image_info) self.images_mock.return_value = [_image] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -2974,7 +2974,7 @@ def test_server_create_image_property_multi(self): 'hypervisor_type': 'qemu', 'hw_disk_bus': 'ide', } - _image = image_fakes.FakeImage.create_one_image(image_info) + _image = image_fakes.create_one_image(image_info) self.images_mock.return_value = [_image] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -3031,7 +3031,7 @@ def test_server_create_image_property_missed(self): 'hw_disk_bus': 'ide', } - _image = image_fakes.FakeImage.create_one_image(image_info) + _image = image_fakes.create_one_image(image_info) self.images_mock.return_value = [_image] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -3063,8 +3063,8 @@ def test_server_create_image_property_with_image_list(self): } } - target_image = image_fakes.FakeImage.create_one_image(image_info) - another_image = image_fakes.FakeImage.create_one_image({}) + target_image = image_fakes.create_one_image(image_info) + another_image = image_fakes.create_one_image({}) self.images_mock.return_value = [target_image, another_image] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -4102,7 +4102,7 @@ def setUp(self): self.servers = self.setup_servers_mock(3) self.servers_mock.list.return_value = self.servers - self.image = image_fakes.FakeImage.create_one_image() + self.image = image_fakes.create_one_image() # self.images_mock.return_value = [self.image] self.find_image_mock.return_value = self.image @@ -6021,7 +6021,7 @@ def setUp(self): super(TestServerRebuild, self).setUp() # Return value for utils.find_resource for image - self.image = image_fakes.FakeImage.create_one_image() + self.image = image_fakes.create_one_image() self.get_image_mock.return_value = self.image # Fake the rebuilt new server. @@ -6051,7 +6051,7 @@ def setUp(self): def test_rebuild_with_image_name(self): image_name = 'my-custom-image' - user_image = image_fakes.FakeImage.create_one_image( + user_image = image_fakes.create_one_image( attrs={'name': image_name}) self.find_image_mock.return_value = user_image @@ -6600,7 +6600,7 @@ class TestEvacuateServer(TestServer): def setUp(self): super(TestEvacuateServer, self).setUp() # Return value for utils.find_resource for image - self.image = image_fakes.FakeImage.create_one_image() + self.image = image_fakes.create_one_image() self.images_mock.get.return_value = self.image # Fake the rebuilt new server. @@ -6794,7 +6794,7 @@ def setUp(self): super(TestServerRescue, self).setUp() # Return value for utils.find_resource for image - self.image = image_fakes.FakeImage.create_one_image() + self.image = image_fakes.create_one_image() self.get_image_mock.return_value = self.image new_server = compute_fakes.FakeServer.create_one_server() @@ -6835,7 +6835,7 @@ def test_rescue_with_current_image(self): self.server.rescue.assert_called_with(image=None, password=None) def test_rescue_with_new_image(self): - new_image = image_fakes.FakeImage.create_one_image() + new_image = image_fakes.create_one_image() self.find_image_mock.return_value = new_image arglist = [ '--image', new_image.id, @@ -7950,7 +7950,7 @@ class TestServerShow(TestServer): def setUp(self): super(TestServerShow, self).setUp() - self.image = image_fakes.FakeImage.create_one_image() + self.image = image_fakes.create_one_image() self.flavor = compute_fakes.FakeFlavor.create_one_flavor() self.topology = { 'nodes': [{'vcpu_set': [0, 1]}, {'vcpu_set': [2, 3]}], @@ -8540,7 +8540,7 @@ def test_prep_server_detail(self, find_resource): # - The first time, return server info. # - The second time, return image info. # - The third time, return flavor info. - _image = image_fakes.FakeImage.create_one_image() + _image = image_fakes.create_one_image() _flavor = compute_fakes.FakeFlavor.create_one_flavor() server_info = { 'image': {u'id': _image.id}, diff --git a/openstackclient/tests/unit/compute/v2/test_server_backup.py b/openstackclient/tests/unit/compute/v2/test_server_backup.py index 1644baae00..1a5e0a1225 100644 --- a/openstackclient/tests/unit/compute/v2/test_server_backup.py +++ b/openstackclient/tests/unit/compute/v2/test_server_backup.py @@ -91,7 +91,7 @@ def setUp(self): def setup_images_mock(self, count, servers=None): if servers: - images = image_fakes.FakeImage.create_images( + images = image_fakes.create_images( attrs={ 'name': servers[0].name, 'status': 'active', @@ -99,7 +99,7 @@ def setup_images_mock(self, count, servers=None): count=count, ) else: - images = image_fakes.FakeImage.create_images( + images = image_fakes.create_images( attrs={ 'status': 'active', }, @@ -178,15 +178,6 @@ def test_server_backup_create_options(self): def test_server_backup_wait_fail(self, mock_wait_for_status): servers = self.setup_servers_mock(count=1) images = self.setup_images_mock(count=1, servers=servers) -# images = image_fakes.FakeImage.create_images( -# attrs={ -# 'name': servers[0].name, -# 'status': 'active', -# }, -# count=1, -# ) -# -# self.images_mock.find_image.return_value = images[0] self.images_mock.get_image = mock.Mock( side_effect=images[0], ) diff --git a/openstackclient/tests/unit/compute/v2/test_server_image.py b/openstackclient/tests/unit/compute/v2/test_server_image.py index 9b14428a27..e17401691a 100644 --- a/openstackclient/tests/unit/compute/v2/test_server_image.py +++ b/openstackclient/tests/unit/compute/v2/test_server_image.py @@ -88,7 +88,7 @@ def setUp(self): def setup_images_mock(self, count, servers=None): if servers: - images = image_fakes.FakeImage.create_images( + images = image_fakes.create_images( attrs={ 'name': servers[0].name, 'status': 'active', @@ -96,7 +96,7 @@ def setup_images_mock(self, count, servers=None): count=count, ) else: - images = image_fakes.FakeImage.create_images( + images = image_fakes.create_images( attrs={ 'status': 'active', }, diff --git a/openstackclient/tests/unit/image/v1/fakes.py b/openstackclient/tests/unit/image/v1/fakes.py index 59ae5f7a2d..3097a42f59 100644 --- a/openstackclient/tests/unit/image/v1/fakes.py +++ b/openstackclient/tests/unit/image/v1/fakes.py @@ -22,10 +22,6 @@ from openstackclient.tests.unit.volume.v1 import fakes as volume_fakes -image_id = 'im1' -image_name = 'graven' - - class FakeImagev1Client(object): def __init__(self, **kwargs): @@ -51,40 +47,36 @@ def setUp(self): ) -class FakeImage(object): - """Fake one or more images.""" - - @staticmethod - def create_one_image(attrs=None): - """Create a fake image. - - :param Dictionary attrs: - A dictionary with all attrbutes of image - :return: - A FakeResource object with id, name, owner, protected, - visibility and tags attrs - """ - attrs = attrs or {} - - # Set default attribute - image_info = { - 'id': str(uuid.uuid4()), - 'name': 'image-name' + uuid.uuid4().hex, - 'owner': 'image-owner' + uuid.uuid4().hex, - 'container_format': '', - 'disk_format': '', - 'min_disk': 0, - 'min_ram': 0, - 'is_public': True, - 'protected': False, - 'properties': { - 'Alpha': 'a', - 'Beta': 'b', - 'Gamma': 'g'}, - 'status': 'status' + uuid.uuid4().hex - } - - # Overwrite default attributes if there are some attributes set - image_info.update(attrs) - - return image.Image(**image_info) +def create_one_image(attrs=None): + """Create a fake image. + + :param Dictionary attrs: + A dictionary with all attrbutes of image + :return: + A FakeResource object with id, name, owner, protected, + visibility and tags attrs + """ + attrs = attrs or {} + + # Set default attribute + image_info = { + 'id': str(uuid.uuid4()), + 'name': 'image-name' + uuid.uuid4().hex, + 'owner': 'image-owner' + uuid.uuid4().hex, + 'container_format': '', + 'disk_format': '', + 'min_disk': 0, + 'min_ram': 0, + 'is_public': True, + 'protected': False, + 'properties': { + 'Alpha': 'a', + 'Beta': 'b', + 'Gamma': 'g'}, + 'status': 'status' + uuid.uuid4().hex + } + + # Overwrite default attributes if there are some attributes set + image_info.update(attrs) + + return image.Image(**image_info) diff --git a/openstackclient/tests/unit/image/v1/test_image.py b/openstackclient/tests/unit/image/v1/test_image.py index 5c69bf0f87..06519800ce 100644 --- a/openstackclient/tests/unit/image/v1/test_image.py +++ b/openstackclient/tests/unit/image/v1/test_image.py @@ -34,7 +34,7 @@ def setUp(self): class TestImageCreate(TestImage): - new_image = image_fakes.FakeImage.create_one_image() + new_image = image_fakes.create_one_image() columns = ( 'container_format', 'disk_format', @@ -210,7 +210,7 @@ def test_image_create_file(self, mock_open): class TestImageDelete(TestImage): - _image = image_fakes.FakeImage.create_one_image() + _image = image_fakes.create_one_image() def setUp(self): super(TestImageDelete, self).setUp() @@ -239,7 +239,7 @@ def test_image_delete_no_options(self): class TestImageList(TestImage): - _image = image_fakes.FakeImage.create_one_image() + _image = image_fakes.create_one_image() columns = ( 'ID', @@ -443,7 +443,7 @@ def test_image_list_sort_option(self, si_mock): class TestImageSet(TestImage): - _image = image_fakes.FakeImage.create_one_image() + _image = image_fakes.create_one_image() def setUp(self): super(TestImageSet, self).setUp() @@ -682,8 +682,7 @@ def test_image_set_numeric_options_to_zero(self): class TestImageShow(TestImage): - _image = image_fakes.FakeImage.create_one_image( - attrs={'size': 2000}) + _image = image_fakes.create_one_image(attrs={'size': 2000}) columns = ( 'container_format', 'disk_format', diff --git a/openstackclient/tests/unit/image/v2/fakes.py b/openstackclient/tests/unit/image/v2/fakes.py index 49ce400d92..910bd726ff 100644 --- a/openstackclient/tests/unit/image/v2/fakes.py +++ b/openstackclient/tests/unit/image/v2/fakes.py @@ -23,9 +23,6 @@ from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes from openstackclient.tests.unit import utils -image_id = '0f41529e-7c12-4de8-be2d-181abb825b3c' -image_name = 'graven' - class FakeImagev2Client(object): @@ -63,75 +60,67 @@ def setUp(self): ) -class FakeImage(object): - """Fake one or more images. +def create_one_image(attrs=None): + """Create a fake image. + + :param attrs: A dictionary with all attributes of image + :type attrs: dict + :return: A fake Image object. + :rtype: `openstack.image.v2.image.Image` + """ + attrs = attrs or {} + + # Set default attribute + image_info = { + 'id': str(uuid.uuid4()), + 'name': 'image-name' + uuid.uuid4().hex, + 'owner_id': 'image-owner' + uuid.uuid4().hex, + 'is_protected': bool(random.choice([0, 1])), + 'visibility': random.choice(['public', 'private']), + 'tags': [uuid.uuid4().hex for r in range(2)], + } + + # Overwrite default attributes if there are some attributes set + image_info.update(attrs) + + return image.Image(**image_info) + + +def create_images(attrs=None, count=2): + """Create multiple fake images. - TODO(xiexs): Currently, only image API v2 is supported by this class. + :param attrs: A dictionary with all attributes of image + :type attrs: dict + :param count: The number of images to be faked + :type count: int + :return: A list of fake Image objects + :rtype: list """ + images = [] + for n in range(0, count): + images.append(create_one_image(attrs)) + + return images + + +def create_one_image_member(attrs=None): + """Create a fake image member. + + :param attrs: A dictionary with all attributes of image member + :type attrs: dict + :return: A fake Member object. + :rtype: `openstack.image.v2.member.Member` + """ + attrs = attrs or {} + + # Set default attribute + image_member_info = { + 'member_id': 'member-id-' + uuid.uuid4().hex, + 'image_id': 'image-id-' + uuid.uuid4().hex, + 'status': 'pending', + } + + # Overwrite default attributes if there are some attributes set + image_member_info.update(attrs) - @staticmethod - def create_one_image(attrs=None): - """Create a fake image. - - :param Dictionary attrs: - A dictionary with all attrbutes of image - :return: - A FakeResource object with id, name, owner, protected, - visibility, tags and size attrs - """ - attrs = attrs or {} - - # Set default attribute - image_info = { - 'id': str(uuid.uuid4()), - 'name': 'image-name' + uuid.uuid4().hex, - 'owner_id': 'image-owner' + uuid.uuid4().hex, - 'is_protected': bool(random.choice([0, 1])), - 'visibility': random.choice(['public', 'private']), - 'tags': [uuid.uuid4().hex for r in range(2)], - } - - # Overwrite default attributes if there are some attributes set - image_info.update(attrs) - - return image.Image(**image_info) - - @staticmethod - def create_images(attrs=None, count=2): - """Create multiple fake images. - - :param Dictionary attrs: - A dictionary with all attributes of image - :param Integer count: - The number of images to be faked - :return: - A list of FakeResource objects - """ - images = [] - for n in range(0, count): - images.append(FakeImage.create_one_image(attrs)) - - return images - - @staticmethod - def create_one_image_member(attrs=None): - """Create a fake image member. - - :param Dictionary attrs: - A dictionary with all attributes of image member - :return: - A FakeResource object with member_id, image_id and so on - """ - attrs = attrs or {} - - # Set default attribute - image_member_info = { - 'member_id': 'member-id-' + uuid.uuid4().hex, - 'image_id': 'image-id-' + uuid.uuid4().hex, - 'status': 'pending', - } - - # Overwrite default attributes if there are some attributes set - image_member_info.update(attrs) - - return member.Member(**image_member_info) + return member.Member(**image_member_info) diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py index 35af6799f6..f15463738a 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -11,7 +11,6 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -# import copy import io @@ -52,7 +51,7 @@ def setUp(self): self.domain_mock.reset_mock() def setup_images_mock(self, count): - images = image_fakes.FakeImage.create_images(count=count) + images = image_fakes.create_images(count=count) return images @@ -65,7 +64,7 @@ class TestImageCreate(TestImage): def setUp(self): super(TestImageCreate, self).setUp() - self.new_image = image_fakes.FakeImage.create_one_image() + self.new_image = image_fakes.create_one_image() self.client.create_image.return_value = self.new_image self.project_mock.get.return_value = self.project @@ -182,7 +181,7 @@ def test_image_create_with_unexist_project(self): '--protected', '--private', '--project', 'unexist_owner', - image_fakes.image_name, + 'graven', ] verifylist = [ ('container_format', 'ovf'), @@ -194,7 +193,7 @@ def test_image_create_with_unexist_project(self): ('public', False), ('private', True), ('project', 'unexist_owner'), - ('name', image_fakes.image_name), + ('name', 'graven'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -302,8 +301,8 @@ class TestAddProjectToImage(TestImage): project = identity_fakes.FakeProject.create_one_project() domain = identity_fakes.FakeDomain.create_one_domain() - _image = image_fakes.FakeImage.create_one_image() - new_member = image_fakes.FakeImage.create_one_image_member( + _image = image_fakes.create_one_image() + new_member = image_fakes.create_one_image_member( attrs={'image_id': _image.id, 'member_id': project.id} ) @@ -435,7 +434,7 @@ def test_image_delete_multi_images(self): def test_image_delete_multi_images_exception(self): - images = image_fakes.FakeImage.create_images(count=2) + images = image_fakes.create_images(count=2) arglist = [ images[0].id, images[1].id, @@ -467,7 +466,7 @@ def test_image_delete_multi_images_exception(self): class TestImageList(TestImage): - _image = image_fakes.FakeImage.create_one_image() + _image = image_fakes.create_one_image() columns = ( 'ID', @@ -786,18 +785,13 @@ def test_image_list_project_option(self): @mock.patch('osc_lib.utils.find_resource') def test_image_list_marker_option(self, fr_mock): - # tangchen: Since image_fakes.IMAGE is a dict, it cannot offer a .id - # operation. Will fix this by using FakeImage class instead - # of IMAGE dict. self.client.find_image = mock.Mock(return_value=self._image) -# fr_mock.return_value = mock.Mock() -# fr_mock.return_value.id = image_fakes.image_id arglist = [ - '--marker', image_fakes.image_name, + '--marker', 'graven', ] verifylist = [ - ('marker', image_fakes.image_name), + ('marker', 'graven'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -806,7 +800,7 @@ def test_image_list_marker_option(self, fr_mock): marker=self._image.id, ) - self.client.find_image.assert_called_with(image_fakes.image_name) + self.client.find_image.assert_called_with('graven') def test_image_list_name_option(self): arglist = [ @@ -869,8 +863,8 @@ def test_image_list_tag_option(self): class TestListImageProjects(TestImage): project = identity_fakes.FakeProject.create_one_project() - _image = image_fakes.FakeImage.create_one_image() - member = image_fakes.FakeImage.create_one_image_member( + _image = image_fakes.create_one_image() + member = image_fakes.create_one_image_member( attrs={'image_id': _image.id, 'member_id': project.id} ) @@ -920,7 +914,7 @@ class TestRemoveProjectImage(TestImage): def setUp(self): super(TestRemoveProjectImage, self).setUp() - self._image = image_fakes.FakeImage.create_one_image() + self._image = image_fakes.create_one_image() # This is the return value for utils.find_resource() self.client.find_image.return_value = self._image @@ -979,7 +973,7 @@ class TestImageSet(TestImage): project = identity_fakes.FakeProject.create_one_project() domain = identity_fakes.FakeDomain.create_one_domain() - _image = image_fakes.FakeImage.create_one_image({'tags': []}) + _image = image_fakes.create_one_image({'tags': []}) def setUp(self): super(TestImageSet, self).setUp() @@ -999,10 +993,10 @@ def setUp(self): def test_image_set_no_options(self): arglist = [ - image_fakes.image_id, + '0f41529e-7c12-4de8-be2d-181abb825b3c', ] verifylist = [ - ('image', image_fakes.image_id) + ('image', '0f41529e-7c12-4de8-be2d-181abb825b3c') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1013,8 +1007,8 @@ def test_image_set_no_options(self): self.image_members_mock.update.assert_not_called() def test_image_set_membership_option_accept(self): - membership = image_fakes.FakeImage.create_one_image_member( - attrs={'image_id': image_fakes.image_id, + membership = image_fakes.create_one_image_member( + attrs={'image_id': '0f41529e-7c12-4de8-be2d-181abb825b3c', 'member_id': self.project.id} ) self.client.update_member.return_value = membership @@ -1044,21 +1038,21 @@ def test_image_set_membership_option_accept(self): self.client.update_image.assert_called_with(self._image.id) def test_image_set_membership_option_reject(self): - membership = image_fakes.FakeImage.create_one_image_member( - attrs={'image_id': image_fakes.image_id, + membership = image_fakes.create_one_image_member( + attrs={'image_id': '0f41529e-7c12-4de8-be2d-181abb825b3c', 'member_id': self.project.id} ) self.client.update_member.return_value = membership arglist = [ '--reject', - image_fakes.image_id, + '0f41529e-7c12-4de8-be2d-181abb825b3c', ] verifylist = [ ('accept', False), ('reject', True), ('pending', False), - ('image', image_fakes.image_id) + ('image', '0f41529e-7c12-4de8-be2d-181abb825b3c') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1075,21 +1069,21 @@ def test_image_set_membership_option_reject(self): self.client.update_image.assert_called_with(self._image.id) def test_image_set_membership_option_pending(self): - membership = image_fakes.FakeImage.create_one_image_member( - attrs={'image_id': image_fakes.image_id, + membership = image_fakes.create_one_image_member( + attrs={'image_id': '0f41529e-7c12-4de8-be2d-181abb825b3c', 'member_id': self.project.id} ) self.client.update_member.return_value = membership arglist = [ '--pending', - image_fakes.image_id, + '0f41529e-7c12-4de8-be2d-181abb825b3c', ] verifylist = [ ('accept', False), ('reject', False), ('pending', True), - ('image', image_fakes.image_id) + ('image', '0f41529e-7c12-4de8-be2d-181abb825b3c') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1149,11 +1143,11 @@ def test_image_set_with_unexist_project(self): arglist = [ '--project', 'unexist_owner', - image_fakes.image_id, + '0f41529e-7c12-4de8-be2d-181abb825b3c', ] verifylist = [ ('project', 'unexist_owner'), - ('image', image_fakes.image_id), + ('image', '0f41529e-7c12-4de8-be2d-181abb825b3c'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1165,14 +1159,14 @@ def test_image_set_bools1(self): arglist = [ '--protected', '--private', - image_fakes.image_name, + 'graven', ] verifylist = [ ('protected', True), ('unprotected', False), ('public', False), ('private', True), - ('image', image_fakes.image_name), + ('image', 'graven'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1193,14 +1187,14 @@ def test_image_set_bools2(self): arglist = [ '--unprotected', '--public', - image_fakes.image_name, + 'graven', ] verifylist = [ ('protected', False), ('unprotected', True), ('public', True), ('private', False), - ('image', image_fakes.image_name), + ('image', 'graven'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1221,11 +1215,11 @@ def test_image_set_properties(self): arglist = [ '--property', 'Alpha=1', '--property', 'Beta=2', - image_fakes.image_name, + 'graven', ] verifylist = [ ('properties', {'Alpha': '1', 'Beta': '2'}), - ('image', image_fakes.image_name), + ('image', 'graven'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1250,7 +1244,7 @@ def test_image_set_fake_properties(self): '--os-distro', 'cpm', '--os-version', '2.2H', '--ramdisk-id', 'xyzpdq', - image_fakes.image_name, + 'graven', ] verifylist = [ ('architecture', 'z80'), @@ -1259,7 +1253,7 @@ def test_image_set_fake_properties(self): ('os_distro', 'cpm'), ('os_version', '2.2H'), ('ramdisk_id', 'xyzpdq'), - ('image', image_fakes.image_name), + ('image', 'graven'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1283,11 +1277,11 @@ def test_image_set_fake_properties(self): def test_image_set_tag(self): arglist = [ '--tag', 'test-tag', - image_fakes.image_name, + 'graven', ] verifylist = [ ('tags', ['test-tag']), - ('image', image_fakes.image_name), + ('image', 'graven'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1307,11 +1301,11 @@ def test_image_set_activate(self): arglist = [ '--tag', 'test-tag', '--activate', - image_fakes.image_name, + 'graven', ] verifylist = [ ('tags', ['test-tag']), - ('image', image_fakes.image_name), + ('image', 'graven'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1335,11 +1329,11 @@ def test_image_set_deactivate(self): arglist = [ '--tag', 'test-tag', '--deactivate', - image_fakes.image_name, + 'graven', ] verifylist = [ ('tags', ['test-tag']), - ('image', image_fakes.image_name), + ('image', 'graven'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1365,11 +1359,11 @@ def test_image_set_tag_merge(self): self.client.find_image.return_value = old_image arglist = [ '--tag', 'test-tag', - image_fakes.image_name, + 'graven', ] verifylist = [ ('tags', ['test-tag']), - ('image', image_fakes.image_name), + ('image', 'graven'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1391,11 +1385,11 @@ def test_image_set_tag_merge_dupe(self): self.client.find_image.return_value = old_image arglist = [ '--tag', 'old1', - image_fakes.image_name, + 'graven', ] verifylist = [ ('tags', ['old1']), - ('image', image_fakes.image_name), + ('image', 'graven'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1415,11 +1409,11 @@ def test_image_set_dead_options(self): arglist = [ '--visibility', '1-mile', - image_fakes.image_name, + 'graven', ] verifylist = [ ('visibility', '1-mile'), - ('image', image_fakes.image_name), + ('image', 'graven'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1431,12 +1425,12 @@ def test_image_set_numeric_options_to_zero(self): arglist = [ '--min-disk', '0', '--min-ram', '0', - image_fakes.image_name, + 'graven', ] verifylist = [ ('min_disk', 0), ('min_ram', 0), - ('image', image_fakes.image_name), + ('image', 'graven'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1457,13 +1451,13 @@ def test_image_set_hidden(self): arglist = [ '--hidden', '--public', - image_fakes.image_name, + 'graven', ] verifylist = [ ('hidden', True), ('public', True), ('private', False), - ('image', image_fakes.image_name), + ('image', 'graven'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1484,13 +1478,13 @@ def test_image_set_unhidden(self): arglist = [ '--unhidden', '--public', - image_fakes.image_name, + 'graven', ] verifylist = [ ('hidden', False), ('public', True), ('private', False), - ('image', image_fakes.image_name), + ('image', 'graven'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1510,10 +1504,10 @@ def test_image_set_unhidden(self): class TestImageShow(TestImage): - new_image = image_fakes.FakeImage.create_one_image( + new_image = image_fakes.create_one_image( attrs={'size': 1000}) - _data = image_fakes.FakeImage.create_one_image() + _data = image_fakes.create_one_image() columns = ( 'id', 'name', 'owner', 'protected', 'tags', 'visibility' @@ -1538,10 +1532,10 @@ def setUp(self): def test_image_show(self): arglist = [ - image_fakes.image_id, + '0f41529e-7c12-4de8-be2d-181abb825b3c', ] verifylist = [ - ('image', image_fakes.image_id), + ('image', '0f41529e-7c12-4de8-be2d-181abb825b3c'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1550,7 +1544,7 @@ def test_image_show(self): # data to be shown. columns, data = self.cmd.take_action(parsed_args) self.client.find_image.assert_called_with( - image_fakes.image_id, + '0f41529e-7c12-4de8-be2d-181abb825b3c', ignore_missing=False ) @@ -1592,7 +1586,7 @@ def setUp(self): attrs['hw_rng_model'] = 'virtio' attrs['prop'] = 'test' attrs['prop2'] = 'fake' - self.image = image_fakes.FakeImage.create_one_image(attrs) + self.image = image_fakes.create_one_image(attrs) self.client.find_image.return_value = self.image self.client.remove_tag.return_value = self.image @@ -1603,10 +1597,10 @@ def setUp(self): def test_image_unset_no_options(self): arglist = [ - image_fakes.image_id, + '0f41529e-7c12-4de8-be2d-181abb825b3c', ] verifylist = [ - ('image', image_fakes.image_id) + ('image', '0f41529e-7c12-4de8-be2d-181abb825b3c') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1681,7 +1675,7 @@ def test_image_unset_mixed_option(self): class TestImageSave(TestImage): - image = image_fakes.FakeImage.create_one_image({}) + image = image_fakes.create_one_image({}) def setUp(self): super(TestImageSave, self).setUp() diff --git a/openstackclient/tests/unit/volume/v1/test_volume.py b/openstackclient/tests/unit/volume/v1/test_volume.py index 702f79ed83..584eca2a7b 100644 --- a/openstackclient/tests/unit/volume/v1/test_volume.py +++ b/openstackclient/tests/unit/volume/v1/test_volume.py @@ -317,7 +317,7 @@ def test_volume_create_properties(self): self.assertCountEqual(self.datalist, data) def test_volume_create_image_id(self): - image = image_fakes.FakeImage.create_one_image() + image = image_fakes.create_one_image() self.images_mock.get.return_value = image arglist = [ @@ -360,7 +360,7 @@ def test_volume_create_image_id(self): self.assertCountEqual(self.datalist, data) def test_volume_create_image_name(self): - image = image_fakes.FakeImage.create_one_image() + image = image_fakes.create_one_image() self.images_mock.get.return_value = image arglist = [ diff --git a/openstackclient/tests/unit/volume/v2/test_volume.py b/openstackclient/tests/unit/volume/v2/test_volume.py index 4aa6f906cc..ec82e6746b 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume.py +++ b/openstackclient/tests/unit/volume/v2/test_volume.py @@ -221,7 +221,7 @@ def test_volume_create_properties(self): self.assertCountEqual(self.datalist, data) def test_volume_create_image_id(self): - image = image_fakes.FakeImage.create_one_image() + image = image_fakes.create_one_image() self.find_image_mock.return_value = image arglist = [ @@ -259,7 +259,7 @@ def test_volume_create_image_id(self): self.assertCountEqual(self.datalist, data) def test_volume_create_image_name(self): - image = image_fakes.FakeImage.create_one_image() + image = image_fakes.create_one_image() self.find_image_mock.return_value = image arglist = [ From 1feb676469f7ccd6a022027bf2e1ecee9cf6d548 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 17 Nov 2021 10:55:33 +0000 Subject: [PATCH 2431/3095] tests: Update fake image client in tests These clients are intended to fake out the old glanceclient client which we no longer use. They were only "working" because we weren't actually using any of the glancelclient-based stuff and were instead overriding everything within the tests. Move these overrides back to the main fake client and remove the crud. Change-Id: I92ee74a1df72a6dd23f9d2dc04342aab0cbd3210 Signed-off-by: Stephen Finucane --- openstackclient/tests/unit/image/v1/fakes.py | 8 +++--- .../tests/unit/image/v1/test_image.py | 6 +---- openstackclient/tests/unit/image/v2/fakes.py | 27 +++++++++++-------- .../tests/unit/image/v2/test_image.py | 23 +++++----------- 4 files changed, 28 insertions(+), 36 deletions(-) diff --git a/openstackclient/tests/unit/image/v1/fakes.py b/openstackclient/tests/unit/image/v1/fakes.py index 3097a42f59..164050c00e 100644 --- a/openstackclient/tests/unit/image/v1/fakes.py +++ b/openstackclient/tests/unit/image/v1/fakes.py @@ -22,11 +22,11 @@ from openstackclient.tests.unit.volume.v1 import fakes as volume_fakes -class FakeImagev1Client(object): +class FakeImagev1Client: def __init__(self, **kwargs): self.images = mock.Mock() - self.images.resource_class = fakes.FakeResource(None, {}) + self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] self.version = 1.0 @@ -35,7 +35,7 @@ def __init__(self, **kwargs): class TestImagev1(utils.TestCommand): def setUp(self): - super(TestImagev1, self).setUp() + super().setUp() self.app.client_manager.image = FakeImagev1Client( endpoint=fakes.AUTH_URL, @@ -46,6 +46,8 @@ def setUp(self): token=fakes.AUTH_TOKEN, ) + self.client = self.app.client_manager.image + def create_one_image(attrs=None): """Create a fake image. diff --git a/openstackclient/tests/unit/image/v1/test_image.py b/openstackclient/tests/unit/image/v1/test_image.py index 06519800ce..6c65f9a395 100644 --- a/openstackclient/tests/unit/image/v1/test_image.py +++ b/openstackclient/tests/unit/image/v1/test_image.py @@ -25,11 +25,7 @@ class TestImage(image_fakes.TestImagev1): - def setUp(self): - super(TestImage, self).setUp() - - self.app.client_manager.image = mock.Mock() - self.client = self.app.client_manager.image + pass class TestImageCreate(TestImage): diff --git a/openstackclient/tests/unit/image/v2/fakes.py b/openstackclient/tests/unit/image/v2/fakes.py index 910bd726ff..a0eda6d23a 100644 --- a/openstackclient/tests/unit/image/v2/fakes.py +++ b/openstackclient/tests/unit/image/v2/fakes.py @@ -24,21 +24,26 @@ from openstackclient.tests.unit import utils -class FakeImagev2Client(object): +class FakeImagev2Client: def __init__(self, **kwargs): self.images = mock.Mock() - self.images.resource_class = fakes.FakeResource(None, {}) - self.image_members = mock.Mock() - self.image_members.resource_class = fakes.FakeResource(None, {}) - self.image_tags = mock.Mock() - self.image_tags.resource_class = fakes.FakeResource(None, {}) - + self.create_image = mock.Mock() + self.delete_image = mock.Mock() + self.update_image = mock.Mock() self.find_image = mock.Mock() - self.find_image.resource_class = fakes.FakeResource(None, {}) - self.get_image = mock.Mock() - self.get_image.resource_class = fakes.FakeResource(None, {}) + self.download_image = mock.Mock() + self.reactivate_image = mock.Mock() + self.deactivate_image = mock.Mock() + + self.members = mock.Mock() + self.add_member = mock.Mock() + self.remove_member = mock.Mock() + self.update_member = mock.Mock() + + self.remove_tag = mock.Mock() + self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] self.version = 2.0 @@ -47,7 +52,7 @@ def __init__(self, **kwargs): class TestImagev2(utils.TestCommand): def setUp(self): - super(TestImagev2, self).setUp() + super().setUp() self.app.client_manager.image = FakeImagev2Client( endpoint=fakes.AUTH_URL, diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py index f15463738a..510976f7e8 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -32,18 +32,9 @@ class TestImage(image_fakes.TestImagev2): def setUp(self): super(TestImage, self).setUp() - # Get shortcuts to the Mocks in image client - # SDK proxy mock - self.app.client_manager.image = mock.Mock() + # Get shortcuts to mocked image client self.client = self.app.client_manager.image - self.client.remove_member = mock.Mock() - - self.client.create_image = mock.Mock() - self.client.update_image = mock.Mock() - self.image_members_mock = self.app.client_manager.image.image_members - self.image_tags_mock = self.app.client_manager.image.image_tags - # Get shortcut to the Mocks in identity client self.project_mock = self.app.client_manager.identity.projects self.project_mock.reset_mock() @@ -483,11 +474,7 @@ class TestImageList(TestImage): def setUp(self): super(TestImageList, self).setUp() - self.api_mock = mock.Mock() - self.api_mock.side_effect = [ - [self._image], [], - ] - self.client.images = self.api_mock + self.client.images.side_effect = [[self._image], []] # Get the command object to test self.cmd = image.ListImage(self.app, None) @@ -1003,8 +990,10 @@ def test_image_set_no_options(self): result = self.cmd.take_action(parsed_args) self.assertIsNone(result) - - self.image_members_mock.update.assert_not_called() + # we'll have called this but not set anything + self.app.client_manager.image.update_image.called_once_with( + self._image.id, + ) def test_image_set_membership_option_accept(self): membership = image_fakes.create_one_image_member( From 61fac5b79e2e4a4120046b2f830e4745bb383fb3 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 17 Nov 2021 11:31:55 +0000 Subject: [PATCH 2432/3095] image: Sanity check the 'SetImage' command This was a very difficult command to grok, due to the layering on of additional features over the years. Make this a little easier to follow by grouping related logic and making use of argparse features. Change-Id: I4e1a0aed09ea5d6a8c26ec3e888c9c7b6cefc25a Signed-off-by: Stephen Finucane --- openstackclient/image/v2/image.py | 90 ++++++++++--------- .../tests/unit/image/v2/test_image.py | 12 +-- 2 files changed, 53 insertions(+), 49 deletions(-) diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index becb54f456..407c129294 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -1012,17 +1012,26 @@ def get_parser(self, prog_name): membership_group = parser.add_mutually_exclusive_group() membership_group.add_argument( "--accept", - action="store_true", + action="store_const", + const="accepted", + dest="membership", + default=None, help=_("Accept the image membership"), ) membership_group.add_argument( "--reject", - action="store_true", + action="store_const", + const="rejected", + dest="membership", + default=None, help=_("Reject the image membership"), ) membership_group.add_argument( "--pending", - action="store_true", + action="store_const", + const="pending", + dest="membership", + default=None, help=_("Reset the image membership to 'pending'"), ) @@ -1053,6 +1062,43 @@ def take_action(self, parsed_args): _("ERROR: --%s was given, which is an Image v1 option" " that is no longer supported in Image v2") % deadopt) + image = image_client.find_image( + parsed_args.image, ignore_missing=False, + ) + project_id = None + if parsed_args.project: + project_id = common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + + # handle activation status changes + + activation_status = None + if parsed_args.deactivate or parsed_args.activate: + if parsed_args.deactivate: + image_client.deactivate_image(image.id) + activation_status = "deactivated" + if parsed_args.activate: + image_client.reactivate_image(image.id) + activation_status = "activated" + + # handle membership changes + + if parsed_args.membership: + # If a specific project is not passed, assume we want to update + # our own membership + if not project_id: + project_id = self.app.client_manager.auth_ref.project_id + image_client.update_member( + image=image.id, + member=project_id, + status=parsed_args.membership, + ) + + # handle everything else + kwargs = {} copy_attrs = ('architecture', 'container_format', 'disk_format', 'file', 'instance_id', 'kernel_id', 'locations', @@ -1089,48 +1135,12 @@ def take_action(self, parsed_args): kwargs['visibility'] = 'community' if parsed_args.shared: kwargs['visibility'] = 'shared' - project_id = None if parsed_args.project: - project_id = common.find_project( - identity_client, - parsed_args.project, - parsed_args.project_domain, - ).id + # We already did the project lookup above kwargs['owner_id'] = project_id - - image = image_client.find_image(parsed_args.image, - ignore_missing=False) - - # image = utils.find_resource( - # image_client.images, parsed_args.image) - - activation_status = None - if parsed_args.deactivate: - image_client.deactivate_image(image.id) - activation_status = "deactivated" - if parsed_args.activate: - image_client.reactivate_image(image.id) - activation_status = "activated" - - membership_group_args = ('accept', 'reject', 'pending') - membership_status = [status for status in membership_group_args - if getattr(parsed_args, status)] - if membership_status: - # If a specific project is not passed, assume we want to update - # our own membership - if not project_id: - project_id = self.app.client_manager.auth_ref.project_id - # The mutually exclusive group of the arg parser ensure we have at - # most one item in the membership_status list. - if membership_status[0] != 'pending': - membership_status[0] += 'ed' # Glance expects the past form - image_client.update_member( - image=image.id, member=project_id, status=membership_status[0]) - if parsed_args.tags: # Tags should be extended, but duplicates removed kwargs['tags'] = list(set(image.tags).union(set(parsed_args.tags))) - if parsed_args.hidden is not None: kwargs['is_hidden'] = parsed_args.hidden diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py index 510976f7e8..7ccc9f0fb4 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -1007,9 +1007,7 @@ def test_image_set_membership_option_accept(self): self._image.id, ] verifylist = [ - ('accept', True), - ('reject', False), - ('pending', False), + ('membership', 'accepted'), ('image', self._image.id) ] @@ -1038,9 +1036,7 @@ def test_image_set_membership_option_reject(self): '0f41529e-7c12-4de8-be2d-181abb825b3c', ] verifylist = [ - ('accept', False), - ('reject', True), - ('pending', False), + ('membership', 'rejected'), ('image', '0f41529e-7c12-4de8-be2d-181abb825b3c') ] @@ -1069,9 +1065,7 @@ def test_image_set_membership_option_pending(self): '0f41529e-7c12-4de8-be2d-181abb825b3c', ] verifylist = [ - ('accept', False), - ('reject', False), - ('pending', True), + ('membership', 'pending'), ('image', '0f41529e-7c12-4de8-be2d-181abb825b3c') ] From b3d09ffc37f6b577ce479a5203c6c882062fee74 Mon Sep 17 00:00:00 2001 From: JieonLee Date: Sun, 21 Nov 2021 05:05:25 +0000 Subject: [PATCH 2433/3095] Add missing command mapping in nova nova command: instance-action openstack command: server event show Change-Id: I8e5dad90cfd28b1f0d65be688651918869f679e4 --- doc/source/cli/data/nova.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/cli/data/nova.csv b/doc/source/cli/data/nova.csv index dd8992f68c..0ab2b2fee4 100644 --- a/doc/source/cli/data/nova.csv +++ b/doc/source/cli/data/nova.csv @@ -46,7 +46,7 @@ hypervisor-show,hypervisor show,Display the details of the specified hypervisor. hypervisor-stats,hypervisor stats show,Get hypervisor statistics over all compute nodes. hypervisor-uptime,,Display the uptime of the specified hypervisor. image-create,server image create,Create a new image by taking a snapshot of a running server. -instance-action,,Show an action. +instance-action,server event show,Show an action. instance-action-list,,List actions on a server. instance-usage-audit-log,,List/Get server usage audits. interface-attach,server add port / server add floating ip / server add fixed ip,Attach a network interface to a server. From 3078a0a121743c387d83d7f27ce3d8fd8fbb4ccf Mon Sep 17 00:00:00 2001 From: Diwei Zhu Date: Thu, 28 Oct 2021 23:25:52 +0000 Subject: [PATCH 2434/3095] Switch command server add volume to sdk. File tests.unit.volume.v2.fakes is modified to provide sdk volume fakes. File tests.unit.compute.v2.fakes is modified to provide sdk volume attachment fakes. For test, setup_sdk_volumes_mock() method is created so that volumes are created in similar way as servers are created. Change-Id: I290ba83b6ba27a1377ab73fd0ae06ecced25efd1 --- openstackclient/compute/v2/server.py | 38 ++- .../tests/unit/compute/v2/fakes.py | 56 ++++ .../tests/unit/compute/v2/test_server.py | 258 +++++++++--------- openstackclient/tests/unit/volume/v2/fakes.py | 110 ++++++-- ...er-add-volume-to-sdk-685e036a88839651.yaml | 4 + 5 files changed, 296 insertions(+), 170 deletions(-) create mode 100644 releasenotes/notes/migrate-server-add-volume-to-sdk-685e036a88839651.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 08627f9bc5..18c1197cc9 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -548,24 +548,25 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - volume_client = self.app.client_manager.volume + compute_client = self.app.client_manager.sdk_connection.compute + volume_client = self.app.client_manager.sdk_connection.volume - server = utils.find_resource( - compute_client.servers, + server = compute_client.find_server( parsed_args.server, + ignore_missing=False, ) - volume = utils.find_resource( - volume_client.volumes, + volume = volume_client.find_volume( parsed_args.volume, + ignore_missing=False, ) kwargs = { + "volumeId": volume.id, "device": parsed_args.device } if parsed_args.tag: - if compute_client.api_version < api_versions.APIVersion('2.49'): + if not sdk_utils.supports_microversion(compute_client, '2.49'): msg = _( '--os-compute-api-version 2.49 or greater is required to ' 'support the --tag option' @@ -575,7 +576,7 @@ def take_action(self, parsed_args): kwargs['tag'] = parsed_args.tag if parsed_args.enable_delete_on_termination: - if compute_client.api_version < api_versions.APIVersion('2.79'): + if not sdk_utils.supports_microversion(compute_client, '2.79'): msg = _( '--os-compute-api-version 2.79 or greater is required to ' 'support the --enable-delete-on-termination option.' @@ -585,7 +586,7 @@ def take_action(self, parsed_args): kwargs['delete_on_termination'] = True if parsed_args.disable_delete_on_termination: - if compute_client.api_version < api_versions.APIVersion('2.79'): + if not sdk_utils.supports_microversion(compute_client, '2.79'): msg = _( '--os-compute-api-version 2.79 or greater is required to ' 'support the --disable-delete-on-termination option.' @@ -594,28 +595,23 @@ def take_action(self, parsed_args): kwargs['delete_on_termination'] = False - volume_attachment = compute_client.volumes.create_server_volume( - server.id, - volume.id, - **kwargs + volume_attachment = compute_client.create_volume_attachment( + server, + **kwargs, ) - columns = ('id', 'serverId', 'volumeId', 'device') + columns = ('id', 'server id', 'volume id', 'device') column_headers = ('ID', 'Server ID', 'Volume ID', 'Device') - if compute_client.api_version >= api_versions.APIVersion('2.49'): + if sdk_utils.supports_microversion(compute_client, '2.49'): columns += ('tag',) column_headers += ('Tag',) - if compute_client.api_version >= api_versions.APIVersion('2.79'): + if sdk_utils.supports_microversion(compute_client, '2.79'): columns += ('delete_on_termination',) column_headers += ('Delete On Termination',) return ( column_headers, - utils.get_item_properties( - volume_attachment, - columns, - mixed_case_fields=('serverId', 'volumeId'), - ) + utils.get_item_properties(volume_attachment, columns,) ) diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index 23468ebc4d..7618c229c5 100644 --- a/openstackclient/tests/unit/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -21,6 +21,7 @@ from novaclient import api_versions from openstack.compute.v2 import flavor as _flavor from openstack.compute.v2 import server +from openstack.compute.v2 import volume_attachment from openstackclient.api import compute_v2 from openstackclient.tests.unit import fakes @@ -1803,3 +1804,58 @@ def create_volume_attachments(attrs=None, methods=None, count=2): attrs, methods)) return volume_attachments + + @staticmethod + def create_one_sdk_volume_attachment(attrs=None, methods=None): + """Create a fake sdk VolumeAttachment. + + :param dict attrs: + A dictionary with all attributes + :param dict methods: + A dictionary with all methods + :return: + A fake VolumeAttachment object, with id, device, and so on + """ + attrs = attrs or {} + methods = methods or {} + + # Set default attributes. + volume_attachment_info = { + "id": uuid.uuid4().hex, + "device": "/dev/sdb", + "server_id": uuid.uuid4().hex, + "volume_id": uuid.uuid4().hex, + # introduced in API microversion 2.70 + "tag": "foo", + # introduced in API microversion 2.79 + "delete_on_termination": True, + # introduced in API microversion 2.89 + "attachment_id": uuid.uuid4().hex, + "bdm_uuid": uuid.uuid4().hex + } + + # Overwrite default attributes. + volume_attachment_info.update(attrs) + + return volume_attachment.VolumeAttachment(**volume_attachment_info) + + @staticmethod + def create_sdk_volume_attachments(attrs=None, methods=None, count=2): + """Create multiple fake VolumeAttachment objects (BDMs). + + :param dict attrs: + A dictionary with all attributes + :param dict methods: + A dictionary with all methods + :param int count: + The number of volume attachments to fake + :return: + A list of VolumeAttachment objects faking the volume attachments. + """ + volume_attachments = [] + for i in range(0, count): + volume_attachments.append( + FakeVolumeAttachment.create_one_sdk_volume_attachment( + attrs, methods)) + + return volume_attachments diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 9623cb0abf..203e47ebb3 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -105,6 +105,9 @@ def setUp(self): self.volumes_mock = self.app.client_manager.volume.volumes self.volumes_mock.reset_mock() + self.app.client_manager.sdk_connection.volume = mock.Mock() + self.sdk_volume_client = self.app.client_manager.sdk_connection.volume + # Get a shortcut to the volume client VolumeManager Mock self.snapshots_mock = self.app.client_manager.volume.volume_snapshots self.snapshots_mock.reset_mock() @@ -146,13 +149,18 @@ def setup_sdk_servers_mock(self, count): ) # This is the return value for compute_client.find_server() - self.sdk_client.find_server = compute_fakes.FakeServer.get_servers( - servers, - 0, - ) + self.sdk_client.find_server.side_effect = servers return servers + def setup_sdk_volumes_mock(self, count): + volumes = volume_fakes.FakeVolume.create_sdk_volumes(count=count) + + # This is the return value for volume_client.find_volume() + self.sdk_volume_client.find_volume.side_effect = volumes + + return volumes + def run_method_with_servers(self, method_name, server_count): servers = self.setup_servers_mock(server_count) @@ -680,31 +688,38 @@ class TestServerVolume(TestServer): def setUp(self): super(TestServerVolume, self).setUp() - self.volume = volume_fakes.FakeVolume.create_one_volume() - self.volumes_mock.get.return_value = self.volume - self.methods = { - 'create_server_volume': None, + 'create_volume_attachment': None, } # Get the command object to test self.cmd = server.AddServerVolume(self.app, None) - def test_server_add_volume(self): - servers = self.setup_servers_mock(count=1) - volume_attachment = \ - compute_fakes.FakeVolumeAttachment.create_one_volume_attachment() - self.servers_volumes_mock.create_server_volume.return_value = \ - volume_attachment + self.servers = self.setup_sdk_servers_mock(count=1) + self.volumes = self.setup_sdk_volumes_mock(count=1) + + attrs = { + 'server_id': self.servers[0].id, + 'volume_id': self.volumes[0].id, + } + self.volume_attachment = \ + compute_fakes.FakeVolumeAttachment.\ + create_one_sdk_volume_attachment(attrs=attrs) + + self.sdk_client.create_volume_attachment.return_value = \ + self.volume_attachment + + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=False) + def test_server_add_volume(self, sm_mock): arglist = [ '--device', '/dev/sdb', - servers[0].id, - self.volume.id, + self.servers[0].id, + self.volumes[0].id, ] verifylist = [ - ('server', servers[0].id), - ('volume', self.volume.id), + ('server', self.servers[0].id), + ('volume', self.volumes[0].id), ('device', '/dev/sdb'), ] @@ -712,39 +727,36 @@ def test_server_add_volume(self): expected_columns = ('ID', 'Server ID', 'Volume ID', 'Device') expected_data = ( - volume_attachment.id, - volume_attachment.serverId, - volume_attachment.volumeId, - volume_attachment.device, + self.volume_attachment.id, + self.volume_attachment.server_id, + self.volume_attachment.volume_id, + '/dev/sdb', ) columns, data = self.cmd.take_action(parsed_args) - self.servers_volumes_mock.create_server_volume.assert_called_once_with( - servers[0].id, self.volume.id, device='/dev/sdb') self.assertEqual(expected_columns, columns) self.assertEqual(expected_data, data) + self.sdk_client.create_volume_attachment.assert_called_once_with( + self.servers[0], volumeId=self.volumes[0].id, device='/dev/sdb') - def test_server_add_volume_with_tag(self): - # requires API 2.49 or later - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.49') - - servers = self.setup_servers_mock(count=1) - volume_attachment = \ - compute_fakes.FakeVolumeAttachment.create_one_volume_attachment() - self.servers_volumes_mock.create_server_volume.return_value = \ - volume_attachment + @mock.patch.object(sdk_utils, 'supports_microversion') + def test_server_add_volume_with_tag(self, sm_mock): + def side_effect(compute_client, version): + if version == '2.49': + return True + return False + sm_mock.side_effect = side_effect arglist = [ '--device', '/dev/sdb', '--tag', 'foo', - servers[0].id, - self.volume.id, + self.servers[0].id, + self.volumes[0].id, ] verifylist = [ - ('server', servers[0].id), - ('volume', self.volume.id), + ('server', self.servers[0].id), + ('volume', self.volumes[0].id), ('device', '/dev/sdb'), ('tag', 'foo'), ] @@ -753,33 +765,33 @@ def test_server_add_volume_with_tag(self): expected_columns = ('ID', 'Server ID', 'Volume ID', 'Device', 'Tag') expected_data = ( - volume_attachment.id, - volume_attachment.serverId, - volume_attachment.volumeId, - volume_attachment.device, - volume_attachment.tag, + self.volume_attachment.id, + self.volume_attachment.server_id, + self.volume_attachment.volume_id, + self.volume_attachment.device, + self.volume_attachment.tag, ) columns, data = self.cmd.take_action(parsed_args) - self.servers_volumes_mock.create_server_volume.assert_called_once_with( - servers[0].id, self.volume.id, device='/dev/sdb', tag='foo') self.assertEqual(expected_columns, columns) self.assertEqual(expected_data, data) + self.sdk_client.create_volume_attachment.assert_called_once_with( + self.servers[0], + volumeId=self.volumes[0].id, + device='/dev/sdb', + tag='foo') - def test_server_add_volume_with_tag_pre_v249(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.48') - - servers = self.setup_servers_mock(count=1) + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=False) + def test_server_add_volume_with_tag_pre_v249(self, sm_mock): arglist = [ - servers[0].id, - self.volume.id, + self.servers[0].id, + self.volumes[0].id, '--tag', 'foo', ] verifylist = [ - ('server', servers[0].id), - ('volume', self.volume.id), + ('server', self.servers[0].id), + ('volume', self.volumes[0].id), ('tag', 'foo'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -792,26 +804,22 @@ def test_server_add_volume_with_tag_pre_v249(self): '--os-compute-api-version 2.49 or greater is required', str(ex)) - def test_server_add_volume_with_enable_delete_on_termination(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.79') - - servers = self.setup_servers_mock(count=1) - volume_attachment = \ - compute_fakes.FakeVolumeAttachment.create_one_volume_attachment() - self.servers_volumes_mock.create_server_volume.return_value = \ - volume_attachment - + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=True) + def test_server_add_volume_with_enable_delete_on_termination( + self, + sm_mock, + ): + self.volume_attachment.delete_on_termination = True arglist = [ '--enable-delete-on-termination', '--device', '/dev/sdb', - servers[0].id, - self.volume.id, + self.servers[0].id, + self.volumes[0].id, ] verifylist = [ - ('server', servers[0].id), - ('volume', self.volume.id), + ('server', self.servers[0].id), + ('volume', self.volumes[0].id), ('device', '/dev/sdb'), ('enable_delete_on_termination', True), ] @@ -826,42 +834,40 @@ def test_server_add_volume_with_enable_delete_on_termination(self): 'Delete On Termination', ) expected_data = ( - volume_attachment.id, - volume_attachment.serverId, - volume_attachment.volumeId, - volume_attachment.device, - volume_attachment.tag, - volume_attachment.delete_on_termination, + self.volume_attachment.id, + self.volume_attachment.server_id, + self.volume_attachment.volume_id, + self.volume_attachment.device, + self.volume_attachment.tag, + self.volume_attachment.delete_on_termination, ) columns, data = self.cmd.take_action(parsed_args) - - self.servers_volumes_mock.create_server_volume.assert_called_once_with( - servers[0].id, self.volume.id, - device='/dev/sdb', delete_on_termination=True) self.assertEqual(expected_columns, columns) self.assertEqual(expected_data, data) + self.sdk_client.create_volume_attachment.assert_called_once_with( + self.servers[0], + volumeId=self.volumes[0].id, + device='/dev/sdb', + delete_on_termination=True) - def test_server_add_volume_with_disable_delete_on_termination(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.79') - - servers = self.setup_servers_mock(count=1) - volume_attachment = \ - compute_fakes.FakeVolumeAttachment.create_one_volume_attachment() - self.servers_volumes_mock.create_server_volume.return_value = \ - volume_attachment + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=True) + def test_server_add_volume_with_disable_delete_on_termination( + self, + sm_mock, + ): + self.volume_attachment.delete_on_termination = False arglist = [ '--disable-delete-on-termination', '--device', '/dev/sdb', - servers[0].id, - self.volume.id, + self.servers[0].id, + self.volumes[0].id, ] verifylist = [ - ('server', servers[0].id), - ('volume', self.volume.id), + ('server', self.servers[0].id), + ('volume', self.volumes[0].id), ('device', '/dev/sdb'), ('disable_delete_on_termination', True), ] @@ -876,37 +882,43 @@ def test_server_add_volume_with_disable_delete_on_termination(self): 'Delete On Termination', ) expected_data = ( - volume_attachment.id, - volume_attachment.serverId, - volume_attachment.volumeId, - volume_attachment.device, - volume_attachment.tag, - volume_attachment.delete_on_termination, + self.volume_attachment.id, + self.volume_attachment.server_id, + self.volume_attachment.volume_id, + self.volume_attachment.device, + self.volume_attachment.tag, + self.volume_attachment.delete_on_termination, ) columns, data = self.cmd.take_action(parsed_args) - self.servers_volumes_mock.create_server_volume.assert_called_once_with( - servers[0].id, self.volume.id, - device='/dev/sdb', delete_on_termination=False) self.assertEqual(expected_columns, columns) self.assertEqual(expected_data, data) + self.sdk_client.create_volume_attachment.assert_called_once_with( + self.servers[0], + volumeId=self.volumes[0].id, + device='/dev/sdb', + delete_on_termination=False) + @mock.patch.object(sdk_utils, 'supports_microversion') def test_server_add_volume_with_enable_delete_on_termination_pre_v279( self, + sm_mock, ): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.78') + def side_effect(compute_client, version): + if version == '2.79': + return False + return True + sm_mock.side_effect = side_effect - servers = self.setup_servers_mock(count=1) arglist = [ - servers[0].id, - self.volume.id, + self.servers[0].id, + self.volumes[0].id, '--enable-delete-on-termination', ] verifylist = [ - ('server', servers[0].id), - ('volume', self.volume.id), + ('server', self.servers[0].id), + ('volume', self.volumes[0].id), ('enable_delete_on_termination', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -917,21 +929,25 @@ def test_server_add_volume_with_enable_delete_on_termination_pre_v279( self.assertIn('--os-compute-api-version 2.79 or greater is required', str(ex)) + @mock.patch.object(sdk_utils, 'supports_microversion') def test_server_add_volume_with_disable_delete_on_termination_pre_v279( self, + sm_mock, ): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.78') + def side_effect(compute_client, version): + if version == '2.79': + return False + return True + sm_mock.side_effect = side_effect - servers = self.setup_servers_mock(count=1) arglist = [ - servers[0].id, - self.volume.id, + self.servers[0].id, + self.volumes[0].id, '--disable-delete-on-termination', ] verifylist = [ - ('server', servers[0].id), - ('volume', self.volume.id), + ('server', self.servers[0].id), + ('volume', self.volumes[0].id), ('disable_delete_on_termination', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -942,24 +958,22 @@ def test_server_add_volume_with_disable_delete_on_termination_pre_v279( self.assertIn('--os-compute-api-version 2.79 or greater is required', str(ex)) + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=True) def test_server_add_volume_with_disable_and_enable_delete_on_termination( self, + sm_mock, ): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.79') - - servers = self.setup_servers_mock(count=1) arglist = [ '--enable-delete-on-termination', '--disable-delete-on-termination', '--device', '/dev/sdb', - servers[0].id, - self.volume.id, + self.servers[0].id, + self.volumes[0].id, ] verifylist = [ - ('server', servers[0].id), - ('volume', self.volume.id), + ('server', self.servers[0].id), + ('volume', self.volumes[0].id), ('device', '/dev/sdb'), ('enable_delete_on_termination', True), ('disable_delete_on_termination', True), diff --git a/openstackclient/tests/unit/volume/v2/fakes.py b/openstackclient/tests/unit/volume/v2/fakes.py index b5f66d4b1d..96e381d3cc 100644 --- a/openstackclient/tests/unit/volume/v2/fakes.py +++ b/openstackclient/tests/unit/volume/v2/fakes.py @@ -18,6 +18,7 @@ import uuid from cinderclient import api_versions +from openstack.block_storage.v3 import volume from osc_lib.cli import format_columns from openstackclient.tests.unit import fakes @@ -46,7 +47,7 @@ class FakeTransfer(object): def create_one_transfer(attrs=None): """Create a fake transfer. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes of Transfer Request :return: A FakeResource object with volume_id, name, id. @@ -75,7 +76,7 @@ def create_one_transfer(attrs=None): def create_transfers(attrs=None, count=2): """Create multiple fake transfers. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes of transfer :param Integer count: The number of transfers to be faked @@ -116,7 +117,7 @@ class FakeTypeAccess(object): def create_one_type_access(attrs=None): """Create a fake volume type access for project. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :return: A FakeResource object, with Volume_type_ID and Project_ID. @@ -148,7 +149,7 @@ class FakeService(object): def create_one_service(attrs=None): """Create a fake service. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes of service :return: A FakeResource object with host, status, etc. @@ -180,7 +181,7 @@ def create_one_service(attrs=None): def create_services(attrs=None, count=2): """Create multiple fake services. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes of service :param Integer count: The number of services to be faked @@ -201,7 +202,7 @@ class FakeCapability(object): def create_one_capability(attrs=None): """Create a fake volume backend capability. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes of the Capabilities. :return: A FakeResource object with capability name and attrs. @@ -260,7 +261,7 @@ class FakePool(object): def create_one_pool(attrs=None): """Create a fake pool. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes of the pool :return: A FakeResource object with pool name and attrs. @@ -362,7 +363,7 @@ class FakeVolume(object): def create_one_volume(attrs=None): """Create a fake volume. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes of volume :return: A FakeResource object with id, name, status, etc. @@ -405,7 +406,7 @@ def create_one_volume(attrs=None): def create_volumes(attrs=None, count=2): """Create multiple fake volumes. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes of volume :param Integer count: The number of volumes to be faked @@ -418,6 +419,61 @@ def create_volumes(attrs=None, count=2): return volumes + @staticmethod + def create_one_sdk_volume(attrs=None): + """Create a fake volume. + + :param dict attrs: + A dictionary with all attributes of volume + :return: + A FakeResource object with id, name, status, etc. + """ + attrs = attrs or {} + + # Set default attribute + volume_info = { + 'id': 'volume-id' + uuid.uuid4().hex, + 'name': 'volume-name' + uuid.uuid4().hex, + 'description': 'description' + uuid.uuid4().hex, + 'status': random.choice(['available', 'in_use']), + 'size': random.randint(1, 20), + 'volume_type': + random.choice(['fake_lvmdriver-1', 'fake_lvmdriver-2']), + 'bootable': + random.choice(['true', 'false']), + 'metadata': { + 'key' + uuid.uuid4().hex: 'val' + uuid.uuid4().hex, + 'key' + uuid.uuid4().hex: 'val' + uuid.uuid4().hex, + 'key' + uuid.uuid4().hex: 'val' + uuid.uuid4().hex}, + 'snapshot_id': random.randint(1, 5), + 'availability_zone': 'zone' + uuid.uuid4().hex, + 'attachments': [{ + 'device': '/dev/' + uuid.uuid4().hex, + 'server_id': uuid.uuid4().hex, + }, ], + } + + # Overwrite default attributes if there are some attributes set + volume_info.update(attrs) + return volume.Volume(**volume_info) + + @staticmethod + def create_sdk_volumes(attrs=None, count=2): + """Create multiple fake volumes. + + :param dict attrs: + A dictionary with all attributes of volume + :param Integer count: + The number of volumes to be faked + :return: + A list of FakeResource objects + """ + volumes = [] + for n in range(0, count): + volumes.append(FakeVolume.create_one_sdk_volume(attrs)) + + return volumes + @staticmethod def get_volumes(volumes=None, count=2): """Get an iterable MagicMock object with a list of faked volumes. @@ -484,7 +540,7 @@ class FakeAvailabilityZone(object): def create_one_availability_zone(attrs=None): """Create a fake AZ. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :return: A FakeResource object with zoneName, zoneState, etc. @@ -509,7 +565,7 @@ def create_one_availability_zone(attrs=None): def create_availability_zones(attrs=None, count=2): """Create multiple fake AZs. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :param int count: The number of AZs to fake @@ -532,7 +588,7 @@ class FakeBackup(object): def create_one_backup(attrs=None): """Create a fake backup. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :return: A FakeResource object with id, name, volume_id, etc. @@ -565,7 +621,7 @@ def create_one_backup(attrs=None): def create_backups(attrs=None, count=2): """Create multiple fake backups. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :param int count: The number of backups to fake @@ -636,7 +692,7 @@ class FakeConsistencyGroup(object): def create_one_consistency_group(attrs=None): """Create a fake consistency group. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :return: A FakeResource object with id, name, description, etc. @@ -666,7 +722,7 @@ def create_one_consistency_group(attrs=None): def create_consistency_groups(attrs=None, count=2): """Create multiple fake consistency groups. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :param int count: The number of consistency groups to fake @@ -713,7 +769,7 @@ class FakeConsistencyGroupSnapshot(object): def create_one_consistency_group_snapshot(attrs=None): """Create a fake consistency group snapshot. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :return: A FakeResource object with id, name, description, etc. @@ -742,7 +798,7 @@ def create_one_consistency_group_snapshot(attrs=None): def create_consistency_group_snapshots(attrs=None, count=2): """Create multiple fake consistency group snapshots. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :param int count: The number of consistency group snapshots to fake @@ -789,7 +845,7 @@ class FakeExtension(object): def create_one_extension(attrs=None): """Create a fake extension. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :return: A FakeResource object with name, namespace, etc. @@ -825,7 +881,7 @@ class FakeQos(object): def create_one_qos(attrs=None): """Create a fake Qos specification. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :return: A FakeResource object with id, name, consumer, etc. @@ -852,7 +908,7 @@ def create_one_qos(attrs=None): def create_one_qos_association(attrs=None): """Create a fake Qos specification association. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :return: A FakeResource object with id, name, association_type, etc. @@ -878,7 +934,7 @@ def create_one_qos_association(attrs=None): def create_qoses(attrs=None, count=2): """Create multiple fake Qos specifications. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :param int count: The number of Qos specifications to fake @@ -920,7 +976,7 @@ class FakeSnapshot(object): def create_one_snapshot(attrs=None): """Create a fake snapshot. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :return: A FakeResource object with id, name, description, etc. @@ -951,7 +1007,7 @@ def create_one_snapshot(attrs=None): def create_snapshots(attrs=None, count=2): """Create multiple fake snapshots. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :param int count: The number of snapshots to fake @@ -993,9 +1049,9 @@ class FakeVolumeType(object): def create_one_volume_type(attrs=None, methods=None): """Create a fake volume type. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes - :param Dictionary methods: + :param dict methods: A dictionary with all methods :return: A FakeResource object with id, name, description, etc. @@ -1025,7 +1081,7 @@ def create_one_volume_type(attrs=None, methods=None): def create_volume_types(attrs=None, count=2): """Create multiple fake volume_types. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :param int count: The number of types to fake @@ -1063,7 +1119,7 @@ def get_volume_types(volume_types=None, count=2): def create_one_encryption_volume_type(attrs=None): """Create a fake encryption volume type. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :return: A FakeResource object with volume_type_id etc. diff --git a/releasenotes/notes/migrate-server-add-volume-to-sdk-685e036a88839651.yaml b/releasenotes/notes/migrate-server-add-volume-to-sdk-685e036a88839651.yaml new file mode 100644 index 0000000000..54abdacbbc --- /dev/null +++ b/releasenotes/notes/migrate-server-add-volume-to-sdk-685e036a88839651.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Migrate openstack server add volume to using sdk. From 860d6360474b2f215097d1aa4018a57070e44924 Mon Sep 17 00:00:00 2001 From: "Dr. Jens Harbott" Date: Wed, 24 Nov 2021 06:46:22 +0100 Subject: [PATCH 2435/3095] Temporarily drop aodhclient from doc build Building plugin documentation is failing for aodhclient when running with latest pyparsing. Drop the plugin from docs for now until the issue can be fixed. Needed-By: https://review.opendev.org/c/openstack/requirements/+/818614/ Signed-off-by: Dr. Jens Harbott Change-Id: I1cc1efd9ff2004dd711ed9da0b1d9e8be31175f4 --- doc/source/cli/plugin-commands/aodh.rst | 4 ---- doc/source/cli/plugin-commands/index.rst | 5 ++++- 2 files changed, 4 insertions(+), 5 deletions(-) delete mode 100644 doc/source/cli/plugin-commands/aodh.rst diff --git a/doc/source/cli/plugin-commands/aodh.rst b/doc/source/cli/plugin-commands/aodh.rst deleted file mode 100644 index 5d8b4332cf..0000000000 --- a/doc/source/cli/plugin-commands/aodh.rst +++ /dev/null @@ -1,4 +0,0 @@ -aodh ----- - -.. autoprogram-cliff:: openstack.alarming.v2 diff --git a/doc/source/cli/plugin-commands/index.rst b/doc/source/cli/plugin-commands/index.rst index 4e1ce54b13..638dcbe560 100644 --- a/doc/source/cli/plugin-commands/index.rst +++ b/doc/source/cli/plugin-commands/index.rst @@ -7,7 +7,6 @@ Plugin Commands .. toctree:: :maxdepth: 1 - aodh barbican designate gnocchi @@ -29,6 +28,10 @@ Plugin Commands .. TODO(efried): Make pages for the following once they're fixed. +.. aodh +.. # aodhclient docs build is failing with recent pyparsing +.. # autoprogram-cliff:: openstack.alarming.v2 + .. cue .. # cueclient is not in global-requirements .. # list-plugins:: openstack.mb.v1 From 28cd5763de8706fb997bdd53deaf94aca0de5c52 Mon Sep 17 00:00:00 2001 From: Diwei Zhu Date: Fri, 26 Nov 2021 14:11:02 +0000 Subject: [PATCH 2436/3095] Add functional test for server add/remove volume. Change-Id: I86a76f32790cafcff1d94364fb72f8890a8cb025 --- .../functional/compute/v2/test_server.py | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py index 59b1fad5f9..0d07950e97 100644 --- a/openstackclient/tests/functional/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -1119,3 +1119,62 @@ def test_server_add_remove_network_port(self): self.openstack('server delete ' + name) self.openstack('port delete ' + port_name) + + def test_server_add_remove_volume(self): + volume_wait_for = volume_common.BaseVolumeTests.wait_for_status + + name = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'server create -f json ' + + '--network private ' + + '--flavor ' + self.flavor_name + ' ' + + '--image ' + self.image_name + ' ' + + '--wait ' + + name + )) + + self.assertIsNotNone(cmd_output['id']) + self.assertEqual(name, cmd_output['name']) + self.addCleanup(self.openstack, 'server delete --wait ' + name) + server_id = cmd_output['id'] + + volume_name = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume create -f json ' + + '--size 1 ' + + volume_name + )) + + self.assertIsNotNone(cmd_output['id']) + self.assertEqual(volume_name, cmd_output['name']) + volume_wait_for('volume', volume_name, 'available') + self.addCleanup(self.openstack, 'volume delete ' + volume_name) + volume_id = cmd_output['id'] + + cmd_output = json.loads(self.openstack( + 'server add volume -f json ' + + name + ' ' + + volume_name + ' ' + + '--tag bar' + )) + + self.assertIsNotNone(cmd_output['ID']) + self.assertEqual(server_id, cmd_output['Server ID']) + self.assertEqual(volume_id, cmd_output['Volume ID']) + volume_attachment_id = cmd_output['ID'] + + cmd_output = json.loads(self.openstack( + 'server volume list -f json ' + + name + )) + + self.assertEqual(volume_attachment_id, cmd_output[0]['ID']) + self.assertEqual(server_id, cmd_output[0]['Server ID']) + self.assertEqual(volume_id, cmd_output[0]['Volume ID']) + + volume_wait_for('volume', volume_name, 'in-use') + self.openstack('server remove volume ' + name + ' ' + volume_name) + volume_wait_for('volume', volume_name, 'available') + + raw_output = self.openstack('server volume list ' + name) + self.assertEqual('\n', raw_output) From fae293dd5218cf4ea03d0a4c44d17b97987dea12 Mon Sep 17 00:00:00 2001 From: Diwei Zhu Date: Tue, 16 Nov 2021 19:08:58 +0000 Subject: [PATCH 2437/3095] Switch command server remove volume to sdk Change-Id: If6f6cf93b55a67e767c54de8ce21f25252cf99ca --- openstackclient/compute/v2/server.py | 27 ++++++----- .../tests/unit/compute/v2/test_server.py | 45 +++++++++++++++++-- ...remove-volume-to-sdk-47e9befd2672dcdf.yaml | 4 ++ 3 files changed, 63 insertions(+), 13 deletions(-) create mode 100644 releasenotes/notes/switch-server-remove-volume-to-sdk-47e9befd2672dcdf.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 18c1197cc9..121a7b82e8 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -3793,22 +3793,29 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - volume_client = self.app.client_manager.volume + compute_client = self.app.client_manager.sdk_connection.compute + volume_client = self.app.client_manager.sdk_connection.volume - server = utils.find_resource( - compute_client.servers, + server = compute_client.find_server( parsed_args.server, + ignore_missing=False, ) - volume = utils.find_resource( - volume_client.volumes, + volume = volume_client.find_volume( parsed_args.volume, + ignore_missing=False, ) - compute_client.volumes.delete_server_volume( - server.id, - volume.id, - ) + volume_attachments = compute_client.volume_attachments(server) + for volume_attachment in volume_attachments: + if volume_attachment.volume_id == volume.id: + compute_client.delete_volume_attachment( + volume_attachment, + server, + ) + break + else: + msg = _('Target volume attachment not found.') + raise exceptions.CommandError(msg) class RescueServer(command.Command): diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 203e47ebb3..10ea07adb3 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -692,9 +692,6 @@ def setUp(self): 'create_volume_attachment': None, } - # Get the command object to test - self.cmd = server.AddServerVolume(self.app, None) - self.servers = self.setup_sdk_servers_mock(count=1) self.volumes = self.setup_sdk_volumes_mock(count=1) @@ -709,6 +706,15 @@ def setUp(self): self.sdk_client.create_volume_attachment.return_value = \ self.volume_attachment + +class TestServerAddVolume(TestServerVolume): + + def setUp(self): + super(TestServerAddVolume, self).setUp() + + # Get the command object to test + self.cmd = server.AddServerVolume(self.app, None) + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=False) def test_server_add_volume(self, sm_mock): @@ -985,6 +991,39 @@ def test_server_add_volume_with_disable_and_enable_delete_on_termination( 'with argument --enable-delete-on-termination', str(ex)) +class TestServerRemoveVolume(TestServerVolume): + + def setUp(self): + super(TestServerRemoveVolume, self).setUp() + + # Get the command object to test + self.cmd = server.RemoveServerVolume(self.app, None) + + def test_server_remove_volume(self): + self.sdk_client.volume_attachments.return_value = [ + self.volume_attachment + ] + + arglist = [ + self.servers[0].id, + self.volumes[0].id, + ] + + verifylist = [ + ('server', self.servers[0].id), + ('volume', self.volumes[0].id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.assertIsNone(result) + self.sdk_client.delete_volume_attachment.assert_called_once_with( + self.volume_attachment, + self.servers[0]) + + class TestServerAddNetwork(TestServer): def setUp(self): diff --git a/releasenotes/notes/switch-server-remove-volume-to-sdk-47e9befd2672dcdf.yaml b/releasenotes/notes/switch-server-remove-volume-to-sdk-47e9befd2672dcdf.yaml new file mode 100644 index 0000000000..3e0397d789 --- /dev/null +++ b/releasenotes/notes/switch-server-remove-volume-to-sdk-47e9befd2672dcdf.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Switch command server remove volume to using sdk. From f4629331134c40599f9baf908a391460b74d2767 Mon Sep 17 00:00:00 2001 From: Slawek Kaplonski Date: Tue, 23 Nov 2021 21:56:56 +0100 Subject: [PATCH 2438/3095] Allow unset port's host_id It is supported by Neutron and needs to be done like that when e.g. admin wants to unbound port from the host. Task: #44043 Story: #2009705 Change-Id: I08f1bb40f4dc72cfa7c62feeb5f513455de0ca45 --- openstackclient/network/v2/port.py | 8 ++++++++ openstackclient/tests/unit/network/v2/test_port.py | 5 ++++- .../add-option-to-unset-port-host-c76de9b1d2addf9a.yaml | 5 +++++ 3 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/add-option-to-unset-port-host-c76de9b1d2addf9a.yaml diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 132c384a0e..8f79b80b0b 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -969,6 +969,12 @@ def get_parser(self, prog_name): action='store_true', help=_("Clear existing NUMA affinity policy") ) + parser.add_argument( + '--host', + action='store_true', + default=False, + help=_("Clear host binding for the port.") + ) _tag.add_tag_option_to_parser_for_unset(parser, _('port')) @@ -1026,6 +1032,8 @@ def take_action(self, parsed_args): attrs['data_plane_status'] = None if parsed_args.numa_policy: attrs['numa_affinity_policy'] = None + if parsed_args.host: + attrs['binding:host_id'] = None attrs.update( self._parse_extra_properties(parsed_args.extra_properties)) diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index 5f2a12836a..c8540ba057 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -1923,6 +1923,7 @@ def test_unset_port_parameters(self): 'subnet=042eb10a-3a18-4658-ab-cf47c8d03152,ip-address=1.0.0.0', '--binding-profile', 'Superman', '--qos-policy', + '--host', self._testport.name, ] verifylist = [ @@ -1931,6 +1932,7 @@ def test_unset_port_parameters(self): 'ip-address': '1.0.0.0'}]), ('binding_profile', ['Superman']), ('qos_policy', True), + ('host', True) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1941,7 +1943,8 @@ def test_unset_port_parameters(self): 'subnet_id': '042eb10a-3a18-4658-ab-cf47c8d03152', 'ip_address': '0.0.0.1'}], 'binding:profile': {'batman': 'Joker'}, - 'qos_policy_id': None + 'qos_policy_id': None, + 'binding:host_id': None } self.network.update_port.assert_called_once_with( self._testport, **attrs) diff --git a/releasenotes/notes/add-option-to-unset-port-host-c76de9b1d2addf9a.yaml b/releasenotes/notes/add-option-to-unset-port-host-c76de9b1d2addf9a.yaml new file mode 100644 index 0000000000..0aa0476050 --- /dev/null +++ b/releasenotes/notes/add-option-to-unset-port-host-c76de9b1d2addf9a.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add possibility to unbind Neutron's port from the host by unsetting its + host_id. From f82afc7f379daebd1994d9133eff801f790c0d32 Mon Sep 17 00:00:00 2001 From: Diwei Zhu Date: Sun, 14 Nov 2021 22:52:14 +0000 Subject: [PATCH 2439/3095] Switch openstack server remove port/network to using sdk Change-Id: I1540c1f52e9a107dba20eeea9dc323c5510fe2b1 --- openstackclient/compute/v2/server.py | 25 +++-- .../functional/compute/v2/test_server.py | 94 ++++++++++++++++--- .../tests/unit/compute/v2/test_server.py | 19 ++-- ...-network-port-to-sdk-829ba711e0e198d5.yaml | 4 + 4 files changed, 114 insertions(+), 28 deletions(-) create mode 100644 releasenotes/notes/switch-server-remove-network-port-to-sdk-829ba711e0e198d5.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 18c1197cc9..dfb4dba8fe 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -3690,10 +3690,10 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute + compute_client = self.app.client_manager.sdk_connection.compute - server = utils.find_resource( - compute_client.servers, parsed_args.server) + server = compute_client.find_server( + parsed_args.server, ignore_missing=False) if self.app.client_manager.is_network_endpoint_enabled(): network_client = self.app.client_manager.network @@ -3702,7 +3702,11 @@ def take_action(self, parsed_args): else: port_id = parsed_args.port - server.interface_detach(port_id) + compute_client.delete_server_interface( + port_id, + server=server, + ignore_missing=False, + ) class RemoveNetwork(command.Command): @@ -3723,10 +3727,10 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute + compute_client = self.app.client_manager.sdk_connection.compute - server = utils.find_resource( - compute_client.servers, parsed_args.server) + server = compute_client.find_server( + parsed_args.server, ignore_missing=False) if self.app.client_manager.is_network_endpoint_enabled(): network_client = self.app.client_manager.network @@ -3735,9 +3739,12 @@ def take_action(self, parsed_args): else: net_id = parsed_args.network - for inf in server.interface_list(): + for inf in compute_client.server_interfaces(server): if inf.net_id == net_id: - server.interface_detach(inf.port_id) + compute_client.delete_server_interface( + inf.port_id, + server=server, + ) class RemoveServerSecurityGroup(command.Command): diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py index 0d07950e97..cf4bcbc2a9 100644 --- a/openstackclient/tests/functional/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -1072,7 +1072,7 @@ def test_server_create_with_empty_network_option_latest(self): self.assertNotIn('nics are required after microversion 2.36', e.stderr) - def test_server_add_remove_network_port(self): + def test_server_add_remove_network(self): name = uuid.uuid4().hex cmd_output = json.loads(self.openstack( 'server create -f json ' + @@ -1085,18 +1085,63 @@ def test_server_add_remove_network_port(self): self.assertIsNotNone(cmd_output['id']) self.assertEqual(name, cmd_output['name']) + self.addCleanup(self.openstack, 'server delete --wait ' + name) + # add network and check 'public' is in server show self.openstack( 'server add network ' + name + ' public') + wait_time = 0 + while wait_time < 60: + cmd_output = json.loads(self.openstack( + 'server show -f json ' + name + )) + if 'public' not in cmd_output['addresses']: + # Hang out for a bit and try again + print('retrying add network check') + wait_time += 10 + time.sleep(10) + else: + break + addresses = cmd_output['addresses'] + self.assertIn('public', addresses) + + # remove network and check 'public' is not in server show + self.openstack('server remove network ' + name + ' public') + + wait_time = 0 + while wait_time < 60: + cmd_output = json.loads(self.openstack( + 'server show -f json ' + name + )) + if 'public' in cmd_output['addresses']: + # Hang out for a bit and try again + print('retrying remove network check') + wait_time += 10 + time.sleep(10) + else: + break + + addresses = cmd_output['addresses'] + self.assertNotIn('public', addresses) + + def test_server_add_remove_port(self): + name = uuid.uuid4().hex cmd_output = json.loads(self.openstack( - 'server show -f json ' + name + 'server create -f json ' + + '--network private ' + + '--flavor ' + self.flavor_name + ' ' + + '--image ' + self.image_name + ' ' + + '--wait ' + + name )) - addresses = cmd_output['addresses'] - self.assertIn('public', addresses) + self.assertIsNotNone(cmd_output['id']) + self.assertEqual(name, cmd_output['name']) + self.addCleanup(self.openstack, 'server delete --wait ' + name) - port_name = 'test-port' + # create port, record one of its ip address + port_name = uuid.uuid4().hex cmd_output = json.loads(self.openstack( 'port list -f json' @@ -1108,17 +1153,44 @@ def test_server_add_remove_network_port(self): '--network private ' + port_name )) self.assertIsNotNone(cmd_output['id']) + ip_address = cmd_output['fixed_ips'][0]['ip_address'] + self.addCleanup(self.openstack, 'port delete ' + port_name) + # add port to server, assert the ip address of the port appears self.openstack('server add port ' + name + ' ' + port_name) - cmd_output = json.loads(self.openstack( - 'server show -f json ' + name - )) + wait_time = 0 + while wait_time < 60: + cmd_output = json.loads(self.openstack( + 'server show -f json ' + name + )) + if ip_address not in cmd_output['addresses']['private']: + # Hang out for a bit and try again + print('retrying add port check') + wait_time += 10 + time.sleep(10) + else: + break + addresses = cmd_output['addresses']['private'] + self.assertIn(ip_address, addresses) - # TODO(diwei): test remove network/port after the commands are switched + # remove port, assert the ip address of the port doesn't appear + self.openstack('server remove port ' + name + ' ' + port_name) - self.openstack('server delete ' + name) - self.openstack('port delete ' + port_name) + wait_time = 0 + while wait_time < 60: + cmd_output = json.loads(self.openstack( + 'server show -f json ' + name + )) + if ip_address in cmd_output['addresses']['private']: + # Hang out for a bit and try again + print('retrying add port check') + wait_time += 10 + time.sleep(10) + else: + break + addresses = cmd_output['addresses']['private'] + self.assertNotIn(ip_address, addresses) def test_server_add_remove_volume(self): volume_wait_for = volume_common.BaseVolumeTests.wait_for_status diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 203e47ebb3..bb0a2c6768 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -6973,14 +6973,14 @@ def setUp(self): # Set method to be tested. self.methods = { - 'interface_detach': None, + 'delete_server_interface': None, } self.find_port = mock.Mock() self.app.client_manager.network.find_port = self.find_port def _test_server_remove_port(self, port_id): - servers = self.setup_servers_mock(count=1) + servers = self.setup_sdk_servers_mock(count=1) port = 'fake-port' arglist = [ @@ -6995,7 +6995,8 @@ def _test_server_remove_port(self, port_id): result = self.cmd.take_action(parsed_args) - servers[0].interface_detach.assert_called_once_with(port_id) + self.sdk_client.delete_server_interface.assert_called_with( + port_id, server=servers[0], ignore_missing=False) self.assertIsNone(result) def test_server_remove_port(self): @@ -7020,17 +7021,18 @@ def setUp(self): # Set method to be tested. self.fake_inf = mock.Mock() self.methods = { - 'interface_list': [self.fake_inf], - 'interface_detach': None, + 'server_interfaces': [self.fake_inf], + 'delete_server_interface': None, } self.find_network = mock.Mock() self.app.client_manager.network.find_network = self.find_network + self.sdk_client.server_interfaces.return_value = [self.fake_inf] def _test_server_remove_network(self, network_id): self.fake_inf.net_id = network_id self.fake_inf.port_id = 'fake-port' - servers = self.setup_servers_mock(count=1) + servers = self.setup_sdk_servers_mock(count=1) network = 'fake-network' arglist = [ @@ -7045,8 +7047,9 @@ def _test_server_remove_network(self, network_id): result = self.cmd.take_action(parsed_args) - servers[0].interface_list.assert_called_once_with() - servers[0].interface_detach.assert_called_once_with('fake-port') + self.sdk_client.server_interfaces.assert_called_once_with(servers[0]) + self.sdk_client.delete_server_interface.assert_called_once_with( + 'fake-port', server=servers[0]) self.assertIsNone(result) def test_server_remove_network(self): diff --git a/releasenotes/notes/switch-server-remove-network-port-to-sdk-829ba711e0e198d5.yaml b/releasenotes/notes/switch-server-remove-network-port-to-sdk-829ba711e0e198d5.yaml new file mode 100644 index 0000000000..6b47b1b35c --- /dev/null +++ b/releasenotes/notes/switch-server-remove-network-port-to-sdk-829ba711e0e198d5.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Switch server remove volume/port to using sdk. From b515fe61b27408e78639da8abb3acaa485ebca4e Mon Sep 17 00:00:00 2001 From: Thrivikram Mudunuri Date: Sat, 13 Nov 2021 02:37:44 -0500 Subject: [PATCH 2440/3095] Switch server pause and server unpause to SDK Switch the server pause and server unpause commands from novaclient to SDK. Use the SDK versions of test fakes to support fake Server resources. Change-Id: Id626f06f3d7edd44b306b7fc7b9b00d04af09621 --- openstackclient/compute/v2/server.py | 22 ++++++++--------- .../tests/unit/compute/v2/test_server.py | 24 +++++++++++++++---- ...pause-unpause-to-sdk-d74ec8536b764af6.yaml | 5 ++++ 3 files changed, 36 insertions(+), 15 deletions(-) create mode 100644 releasenotes/notes/migrate-server-pause-unpause-to-sdk-d74ec8536b764af6.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 121a7b82e8..09954c49d2 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -3130,12 +3130,13 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute + compute_client = self.app.client_manager.sdk_connection.compute for server in parsed_args.server: - utils.find_resource( - compute_client.servers, - server - ).pause() + server_id = compute_client.find_server( + server, + ignore_missing=False, + ).id + compute_client.pause_server(server_id) class RebootServer(command.Command): @@ -4674,7 +4675,6 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute for server in parsed_args.server: utils.find_resource( @@ -4697,13 +4697,13 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - - compute_client = self.app.client_manager.compute + compute_client = self.app.client_manager.sdk_connection.compute for server in parsed_args.server: - utils.find_resource( - compute_client.servers, + server_id = compute_client.find_server( server, - ).unpause() + ignore_missing=False, + ).id + compute_client.unpause_server(server_id) class UnrescueServer(command.Command): diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 10ea07adb3..435ddb4778 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -192,6 +192,22 @@ def run_method_with_servers(self, method_name, server_count): method.assert_called_with() self.assertIsNone(result) + def run_method_with_sdk_servers(self, method_name, server_count): + servers = self.setup_sdk_servers_mock(count=server_count) + + arglist = [s.id for s in servers] + verifylist = [ + ('server', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [call(s.id) for s in servers] + method = getattr(self.sdk_client, method_name) + method.assert_has_calls(calls) + self.assertIsNone(result) + class TestServerAddFixedIP(TestServer): @@ -6062,10 +6078,10 @@ def setUp(self): } def test_server_pause_one_server(self): - self.run_method_with_servers('pause', 1) + self.run_method_with_sdk_servers('pause_server', 1) def test_server_pause_multi_servers(self): - self.run_method_with_servers('pause', 3) + self.run_method_with_sdk_servers('pause_server', 3) class TestServerRebuild(TestServer): @@ -8308,10 +8324,10 @@ def setUp(self): } def test_server_unpause_one_server(self): - self.run_method_with_servers('unpause', 1) + self.run_method_with_sdk_servers('unpause_server', 1) def test_server_unpause_multi_servers(self): - self.run_method_with_servers('unpause', 3) + self.run_method_with_sdk_servers('unpause_server', 3) class TestServerUnset(TestServer): diff --git a/releasenotes/notes/migrate-server-pause-unpause-to-sdk-d74ec8536b764af6.yaml b/releasenotes/notes/migrate-server-pause-unpause-to-sdk-d74ec8536b764af6.yaml new file mode 100644 index 0000000000..e2d4003489 --- /dev/null +++ b/releasenotes/notes/migrate-server-pause-unpause-to-sdk-d74ec8536b764af6.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Migrate ``server pause`` and ``server unpause`` commands from novaclient + to sdk. From ff96fea0120ab43968a10230ce7899a3c6504e75 Mon Sep 17 00:00:00 2001 From: Thrivikram Mudunuri Date: Sat, 13 Nov 2021 17:39:41 -0500 Subject: [PATCH 2441/3095] Switch server suspend and server resume to SDK Switch the server suspend and server resume commands from novaclient to SDK. Use the SDK versions of test fakes to support fake Server resources. Change-Id: Idd0b4f13fab0f238e42844a7d759538bbda24f68 --- openstackclient/compute/v2/server.py | 20 +++++++++---------- .../tests/unit/compute/v2/test_server.py | 8 ++++---- ...uspend-resume-to-sdk-fd1709336607b496.yaml | 5 +++++ 3 files changed, 19 insertions(+), 14 deletions(-) create mode 100644 releasenotes/notes/migrate-server-suspend-resume-to-sdk-fd1709336607b496.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 09954c49d2..a7479bb41a 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -4081,13 +4081,13 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - - compute_client = self.app.client_manager.compute + compute_client = self.app.client_manager.sdk_connection.compute for server in parsed_args.server: - utils.find_resource( - compute_client.servers, + server_id = compute_client.find_server( server, - ).resume() + ignore_missing=False, + ).id + compute_client.resume_server(server_id) class SetServer(command.Command): @@ -4652,13 +4652,13 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - - compute_client = self.app.client_manager.compute + compute_client = self.app.client_manager.sdk_connection.compute for server in parsed_args.server: - utils.find_resource( - compute_client.servers, + server_id = compute_client.find_server( server, - ).suspend() + ignore_missing=False, + ).id + compute_client.suspend_server(server_id) class UnlockServer(command.Command): diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 435ddb4778..27ea12637c 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -7617,10 +7617,10 @@ def setUp(self): } def test_server_resume_one_server(self): - self.run_method_with_servers('resume', 1) + self.run_method_with_sdk_servers('resume_server', 1) def test_server_resume_multi_servers(self): - self.run_method_with_servers('resume', 3) + self.run_method_with_sdk_servers('resume_server', 3) class TestServerSet(TestServer): @@ -8284,10 +8284,10 @@ def setUp(self): } def test_server_suspend_one_server(self): - self.run_method_with_servers('suspend', 1) + self.run_method_with_sdk_servers('suspend_server', 1) def test_server_suspend_multi_servers(self): - self.run_method_with_servers('suspend', 3) + self.run_method_with_sdk_servers('suspend_server', 3) class TestServerUnlock(TestServer): diff --git a/releasenotes/notes/migrate-server-suspend-resume-to-sdk-fd1709336607b496.yaml b/releasenotes/notes/migrate-server-suspend-resume-to-sdk-fd1709336607b496.yaml new file mode 100644 index 0000000000..7d3781bbf4 --- /dev/null +++ b/releasenotes/notes/migrate-server-suspend-resume-to-sdk-fd1709336607b496.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Migrate ``server suspend`` and ``server resume`` commands from novaclient + to sdk. From 4c3de28e83babb0672950320a20492dc61803b4a Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Tue, 26 Jan 2021 16:55:05 +0000 Subject: [PATCH 2442/3095] compute: Reorder building of columns for 'server list' This has no impact on the end result, but it should make fixing issues introduced by API microversion 2.69 a little easier. Change-Id: I7d70eac8aa1a6197ed05a49f071e6899ec219c03 Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server.py | 192 +++++++++++++++------------ 1 file changed, 105 insertions(+), 87 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 121a7b82e8..be14074b7d 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -2204,15 +2204,19 @@ def take_action(self, parsed_args): # flavor name is given, map it to ID. flavor_id = None if parsed_args.flavor: - flavor_id = utils.find_resource(compute_client.flavors, - parsed_args.flavor).id + flavor_id = utils.find_resource( + compute_client.flavors, + parsed_args.flavor, + ).id # Nova only supports list servers searching by image ID. So if a # image name is given, map it to ID. image_id = None if parsed_args.image: - image_id = image_client.find_image(parsed_args.image, - ignore_missing=False).id + image_id = image_client.find_image( + parsed_args.image, + ignore_missing=False, + ).id search_opts = { 'reservation_id': parsed_args.reservation_id, @@ -2320,95 +2324,93 @@ def take_action(self, parsed_args): try: iso8601.parse_date(search_opts['changes-since']) except (TypeError, iso8601.ParseError): + msg = _('Invalid changes-since value: %s') raise exceptions.CommandError( - _('Invalid changes-since value: %s') % - search_opts['changes-since'] + msg % search_opts['changes-since'] ) + columns = ( + 'id', + 'name', + 'status', + ) + column_headers = ( + 'ID', + 'Name', + 'Status', + ) + if parsed_args.long: - columns = ( - 'ID', - 'Name', - 'Status', + columns += ( 'OS-EXT-STS:task_state', 'OS-EXT-STS:power_state', - 'Networks', - 'Image Name', - 'Image ID', - 'Flavor Name', - 'Flavor ID', - 'OS-EXT-AZ:availability_zone', - 'OS-EXT-SRV-ATTR:host', - 'Metadata', ) - column_headers = ( - 'ID', - 'Name', - 'Status', + column_headers += ( 'Task State', 'Power State', - 'Networks', + ) + + columns += ('networks',) + column_headers += ('Networks',) + + if parsed_args.long: + columns += ( + 'image_name', + 'image_id', + ) + column_headers += ( 'Image Name', 'Image ID', + ) + else: + if parsed_args.no_name_lookup: + columns += ('image_id',) + else: + columns += ('image_name',) + column_headers += ('Image',) + + if parsed_args.long: + columns += ( + 'flavor_name', + 'flavor_id', + ) + column_headers += ( 'Flavor Name', 'Flavor ID', - 'Availability Zone', - 'Host', - 'Properties', ) - mixed_case_fields = [ - 'OS-EXT-STS:task_state', - 'OS-EXT-STS:power_state', - 'OS-EXT-AZ:availability_zone', - 'OS-EXT-SRV-ATTR:host', - ] else: if parsed_args.no_name_lookup: - columns = ( - 'ID', - 'Name', - 'Status', - 'Networks', - 'Image ID', - 'Flavor ID', - ) + columns += ('flavor_id',) else: - columns = ( - 'ID', - 'Name', - 'Status', - 'Networks', - 'Image Name', - 'Flavor Name', - ) - column_headers = ( - 'ID', - 'Name', - 'Status', - 'Networks', - 'Image', - 'Flavor', + columns += ('flavor_name',) + column_headers += ('Flavor',) + + if parsed_args.long: + columns += ( + 'OS-EXT-AZ:availability_zone', + 'OS-EXT-SRV-ATTR:host', + 'metadata', + ) + column_headers += ( + 'Availability Zone', + 'Host', + 'Properties', ) - mixed_case_fields = [] marker_id = None # support for additional columns if parsed_args.columns: - # convert tuple to list to edit them - column_headers = list(column_headers) - columns = list(columns) - for c in parsed_args.columns: if c in ('Project ID', 'project_id'): - columns.append('tenant_id') - column_headers.append('Project ID') + columns += ('tenant_id',) + column_headers += ('Project ID',) if c in ('User ID', 'user_id'): - columns.append('user_id') - column_headers.append('User ID') + columns += ('user_id',) + column_headers += ('User ID',) if c in ('Created At', 'created_at'): - columns.append('created') - column_headers.append('Created At') + columns += ('created',) + column_headers += ('Created At',) # convert back to tuple column_headers = tuple(column_headers) @@ -2422,25 +2424,29 @@ def take_action(self, parsed_args): if parsed_args.deleted: marker_id = parsed_args.marker else: - marker_id = utils.find_resource(compute_client.servers, - parsed_args.marker).id + marker_id = utils.find_resource( + compute_client.servers, + parsed_args.marker, + ).id - data = compute_client.servers.list(search_opts=search_opts, - marker=marker_id, - limit=parsed_args.limit) + data = compute_client.servers.list( + search_opts=search_opts, + marker=marker_id, + limit=parsed_args.limit) images = {} flavors = {} if data and not parsed_args.no_name_lookup: - # Create a dict that maps image_id to image object. - # Needed so that we can display the "Image Name" column. - # "Image Name" is not crucial, so we swallow any exceptions. - # The 'image' attribute can be an empty string if the server was - # booted from a volume. + # create a dict that maps image_id to image object, which is used + # to display the "Image Name" column. Note that 'image.id' can be + # empty for BFV instances and 'image' can be missing entirely if + # there are infra failures if parsed_args.name_lookup_one_by_one or image_id: - for i_id in set(filter(lambda x: x is not None, - (s.image.get('id') for s in data - if s.image))): + for i_id in set( + s.image['id'] for s in data + if s.image and s.image.get('id') + ): + # "Image Name" is not crucial, so we swallow any exceptions try: images[i_id] = image_client.get_image(i_id) except Exception: @@ -2453,12 +2459,17 @@ def take_action(self, parsed_args): except Exception: pass - # Create a dict that maps flavor_id to flavor object. - # Needed so that we can display the "Flavor Name" column. - # "Flavor Name" is not crucial, so we swallow any exceptions. + # create a dict that maps flavor_id to flavor object, which is used + # to display the "Flavor Name" column. Note that 'flavor.id' is not + # present on microversion 2.47 or later and 'flavor' won't be + # present if there are infra failures if parsed_args.name_lookup_one_by_one or flavor_id: - for f_id in set(filter(lambda x: x is not None, - (s.flavor.get('id') for s in data))): + for f_id in set( + s.flavor['id'] for s in data + if s.flavor and s.flavor.get('id') + ): + # "Flavor Name" is not crucial, so we swallow any + # exceptions try: flavors[f_id] = compute_client.flavors.get(f_id) except Exception: @@ -2482,6 +2493,7 @@ def take_action(self, parsed_args): # processing of the image and flavor informations. if not hasattr(s, 'image') or not hasattr(s, 'flavor'): continue + if 'id' in s.image: image = images.get(s.image['id']) if image: @@ -2494,6 +2506,7 @@ def take_action(self, parsed_args): # able to grep for boot-from-volume servers when using the CLI. s.image_name = IMAGE_STRING_FOR_BFV s.image_id = IMAGE_STRING_FOR_BFV + if 'id' in s.flavor: flavor = flavors.get(s.flavor['id']) if flavor: @@ -2512,11 +2525,16 @@ def take_action(self, parsed_args): ( utils.get_item_properties( s, columns, - mixed_case_fields=mixed_case_fields, + mixed_case_fields=( + 'OS-EXT-STS:task_state', + 'OS-EXT-STS:power_state', + 'OS-EXT-AZ:availability_zone', + 'OS-EXT-SRV-ATTR:host', + ), formatters={ 'OS-EXT-STS:power_state': PowerStateColumn, - 'Networks': format_columns.DictListColumn, - 'Metadata': format_columns.DictColumn, + 'networks': format_columns.DictListColumn, + 'metadata': format_columns.DictColumn, }, ) for s in data ), From 8e362402dee07744668bcf7f6774af4fbe9a07e3 Mon Sep 17 00:00:00 2001 From: Khomesh Thakre Date: Fri, 6 Nov 2020 22:45:03 +0530 Subject: [PATCH 2443/3095] compute: Show flavor in 'server list' with API >= 2.47 Fix the issue where the flavor name was empty in server list output. This requires somewhat invasive unit test changes to reflect the changed API response from the server, but this has the upside of meaning we don't need new tests since what we have validates things. Also drop the flavor ID column as it is removed from the compute API. Change-Id: Ica3320242a38901c1180b2b29109c9474366fde0 Signed-off-by: Khomesh Thakre Story: 2008257 Task: 41113 --- openstackclient/compute/v2/server.py | 42 +- .../tests/unit/compute/v2/test_server.py | 558 ++++++++++-------- ...st-microversion-2.47-af200e9bb4747e2d.yaml | 8 + 3 files changed, 348 insertions(+), 260 deletions(-) create mode 100644 releasenotes/notes/fix-flavor-in-server-list-microversion-2.47-af200e9bb4747e2d.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index be14074b7d..a0b27242d4 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -2369,21 +2369,28 @@ def take_action(self, parsed_args): columns += ('image_name',) column_headers += ('Image',) - if parsed_args.long: - columns += ( - 'flavor_name', - 'flavor_id', - ) - column_headers += ( - 'Flavor Name', - 'Flavor ID', - ) + # microversion 2.47 puts the embedded flavor into the server response + # body but omits the id, so if not present we just expose the original + # flavor name in the output + if compute_client.api_version >= api_versions.APIVersion('2.47'): + columns += ('flavor_name',) + column_headers += ('Flavor',) else: - if parsed_args.no_name_lookup: - columns += ('flavor_id',) + if parsed_args.long: + columns += ( + 'flavor_name', + 'flavor_id', + ) + column_headers += ( + 'Flavor Name', + 'Flavor ID', + ) else: - columns += ('flavor_name',) - column_headers += ('Flavor',) + if parsed_args.no_name_lookup: + columns += ('flavor_id',) + else: + columns += ('flavor_name',) + column_headers += ('Flavor',) if parsed_args.long: columns += ( @@ -2507,18 +2514,13 @@ def take_action(self, parsed_args): s.image_name = IMAGE_STRING_FOR_BFV s.image_id = IMAGE_STRING_FOR_BFV - if 'id' in s.flavor: + if compute_client.api_version < api_versions.APIVersion('2.47'): flavor = flavors.get(s.flavor['id']) if flavor: s.flavor_name = flavor.name s.flavor_id = s.flavor['id'] else: - # TODO(mriedem): Fix this for microversion >= 2.47 where the - # flavor is embedded in the server response without the id. - # We likely need to drop the Flavor ID column in that case if - # --long is specified. - s.flavor_name = '' - s.flavor_id = '' + s.flavor_name = s.flavor['original_name'] table = ( column_headers, diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 10ea07adb3..17762d72bc 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -4081,7 +4081,7 @@ def test_server_dump_multi_servers(self): self.run_method_with_servers('trigger_crash_dump', 3) -class TestServerList(TestServer): +class _TestServerList(TestServer): # Columns to be listed up. columns = ( @@ -4109,7 +4109,7 @@ class TestServerList(TestServer): ) def setUp(self): - super(TestServerList, self).setUp() + super(_TestServerList, self).setUp() self.search_opts = { 'reservation_id': None, @@ -4148,7 +4148,7 @@ def setUp(self): }, 'OS-EXT-AZ:availability_zone': 'availability-zone-xxx', 'OS-EXT-SRV-ATTR:host': 'host-name-xxx', - 'Metadata': '', + 'Metadata': format_columns.DictColumn({}), } # The servers to be listed. @@ -4167,10 +4167,11 @@ def setUp(self): # Get the command object to test self.cmd = server.ListServer(self.app, None) - # Prepare data returned by fake Nova API. - self.data = [] - self.data_long = [] - self.data_no_name_lookup = [] + +class TestServerList(_TestServerList): + + def setUp(self): + super(TestServerList, self).setUp() Image = collections.namedtuple('Image', 'id name') self.images_mock.return_value = [ @@ -4185,8 +4186,8 @@ def setUp(self): for s in self.servers ] - for s in self.servers: - self.data.append(( + self.data = tuple( + ( s.id, s.name, s.status, @@ -4194,34 +4195,8 @@ def setUp(self): # Image will be an empty string if boot-from-volume self.image.name if s.image else server.IMAGE_STRING_FOR_BFV, self.flavor.name, - )) - self.data_long.append(( - s.id, - s.name, - s.status, - getattr(s, 'OS-EXT-STS:task_state'), - server.PowerStateColumn( - getattr(s, 'OS-EXT-STS:power_state') - ), - format_columns.DictListColumn(s.networks), - # Image will be an empty string if boot-from-volume - self.image.name if s.image else server.IMAGE_STRING_FOR_BFV, - s.image['id'] if s.image else server.IMAGE_STRING_FOR_BFV, - self.flavor.name, - s.flavor['id'], - getattr(s, 'OS-EXT-AZ:availability_zone'), - getattr(s, 'OS-EXT-SRV-ATTR:host'), - format_columns.DictColumn({}), - )) - self.data_no_name_lookup.append(( - s.id, - s.name, - s.status, - format_columns.DictListColumn(s.networks), - # Image will be an empty string if boot-from-volume - s.image['id'] if s.image else server.IMAGE_STRING_FOR_BFV, - s.flavor['id'] - )) + ) for s in self.servers + ) def test_server_list_no_option(self): arglist = [] @@ -4242,7 +4217,7 @@ def test_server_list_no_option(self): self.assertFalse(self.flavors_mock.get.call_count) self.assertFalse(self.get_image_mock.call_count) self.assertEqual(self.columns, columns) - self.assertEqual(tuple(self.data), tuple(data)) + self.assertEqual(self.data, tuple(data)) def test_server_list_no_servers(self): arglist = [] @@ -4261,9 +4236,28 @@ def test_server_list_no_servers(self): self.assertEqual(0, self.images_mock.list.call_count) self.assertEqual(0, self.flavors_mock.list.call_count) self.assertEqual(self.columns, columns) - self.assertEqual(tuple(self.data), tuple(data)) + self.assertEqual(self.data, tuple(data)) def test_server_list_long_option(self): + self.data = tuple( + ( + s.id, + s.name, + s.status, + getattr(s, 'OS-EXT-STS:task_state'), + server.PowerStateColumn( + getattr(s, 'OS-EXT-STS:power_state') + ), + format_columns.DictListColumn(s.networks), + # Image will be an empty string if boot-from-volume + self.image.name if s.image else server.IMAGE_STRING_FOR_BFV, + s.image['id'] if s.image else server.IMAGE_STRING_FOR_BFV, + self.flavor.name, + s.flavor['id'], + getattr(s, 'OS-EXT-AZ:availability_zone'), + getattr(s, 'OS-EXT-SRV-ATTR:host'), + s.Metadata, + ) for s in self.servers) arglist = [ '--long', ] @@ -4274,10 +4268,9 @@ def test_server_list_long_option(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.servers_mock.list.assert_called_with(**self.kwargs) self.assertEqual(self.columns_long, columns) - self.assertCountEqual(tuple(self.data_long), tuple(data)) + self.assertEqual(self.data, tuple(data)) def test_server_list_column_option(self): arglist = [ @@ -4299,6 +4292,18 @@ def test_server_list_column_option(self): self.assertIn('Created At', columns) def test_server_list_no_name_lookup_option(self): + self.data = tuple( + ( + s.id, + s.name, + s.status, + format_columns.DictListColumn(s.networks), + # Image will be an empty string if boot-from-volume + s.image['id'] if s.image else server.IMAGE_STRING_FOR_BFV, + s.flavor['id'] + ) for s in self.servers + ) + arglist = [ '--no-name-lookup', ] @@ -4312,9 +4317,21 @@ def test_server_list_no_name_lookup_option(self): self.servers_mock.list.assert_called_with(**self.kwargs) self.assertEqual(self.columns, columns) - self.assertEqual(tuple(self.data_no_name_lookup), tuple(data)) + self.assertEqual(self.data, tuple(data)) def test_server_list_n_option(self): + self.data = tuple( + ( + s.id, + s.name, + s.status, + format_columns.DictListColumn(s.networks), + # Image will be an empty string if boot-from-volume + s.image['id'] if s.image else server.IMAGE_STRING_FOR_BFV, + s.flavor['id'] + ) for s in self.servers + ) + arglist = [ '-n', ] @@ -4328,7 +4345,7 @@ def test_server_list_n_option(self): self.servers_mock.list.assert_called_with(**self.kwargs) self.assertEqual(self.columns, columns) - self.assertEqual(tuple(self.data_no_name_lookup), tuple(data)) + self.assertEqual(self.data, tuple(data)) def test_server_list_name_lookup_one_by_one(self): arglist = [ @@ -4350,7 +4367,7 @@ def test_server_list_name_lookup_one_by_one(self): self.flavors_mock.get.assert_called() self.assertEqual(self.columns, columns) - self.assertEqual(tuple(self.data), tuple(data)) + self.assertEqual(self.data, tuple(data)) def test_server_list_with_image(self): @@ -4371,81 +4388,7 @@ def test_server_list_with_image(self): self.servers_mock.list.assert_called_with(**self.kwargs) self.assertEqual(self.columns, columns) - self.assertEqual(tuple(self.data), tuple(data)) - - def test_server_list_with_locked_pre_v273(self): - - arglist = [ - '--locked' - ] - verifylist = [ - ('locked', True) - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - ex = self.assertRaises(exceptions.CommandError, - self.cmd.take_action, - parsed_args) - self.assertIn( - '--os-compute-api-version 2.73 or greater is required', str(ex)) - - def test_server_list_with_locked_v273(self): - - self.app.client_manager.compute.api_version = \ - api_versions.APIVersion('2.73') - arglist = [ - '--locked' - ] - verifylist = [ - ('locked', True) - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) - - self.search_opts['locked'] = True - self.servers_mock.list.assert_called_with(**self.kwargs) - - self.assertEqual(self.columns, columns) - self.assertEqual(tuple(self.data), tuple(data)) - - def test_server_list_with_unlocked_v273(self): - - self.app.client_manager.compute.api_version = \ - api_versions.APIVersion('2.73') - arglist = [ - '--unlocked' - ] - verifylist = [ - ('unlocked', True) - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) - - self.search_opts['locked'] = False - self.servers_mock.list.assert_called_with(**self.kwargs) - - self.assertEqual(self.columns, columns) - self.assertEqual(tuple(self.data), tuple(data)) - - def test_server_list_with_locked_and_unlocked_v273(self): - - self.app.client_manager.compute.api_version = \ - api_versions.APIVersion('2.73') - arglist = [ - '--locked', - '--unlocked' - ] - verifylist = [ - ('locked', True), - ('unlocked', True) - ] - - ex = self.assertRaises( - utils.ParserException, - self.check_parser, self.cmd, arglist, verifylist) - self.assertIn('Argument parse failed', str(ex)) + self.assertEqual(self.data, tuple(data)) def test_server_list_with_flavor(self): @@ -4465,7 +4408,7 @@ def test_server_list_with_flavor(self): self.servers_mock.list.assert_called_with(**self.kwargs) self.assertEqual(self.columns, columns) - self.assertEqual(tuple(self.data), tuple(data)) + self.assertEqual(self.data, tuple(data)) def test_server_list_with_changes_since(self): @@ -4486,7 +4429,7 @@ def test_server_list_with_changes_since(self): self.servers_mock.list.assert_called_with(**self.kwargs) self.assertEqual(self.columns, columns) - self.assertEqual(tuple(self.data), tuple(data)) + self.assertEqual(self.data, tuple(data)) @mock.patch.object(iso8601, 'parse_date', side_effect=iso8601.ParseError) def test_server_list_with_invalid_changes_since(self, mock_parse_isotime): @@ -4509,123 +4452,6 @@ def test_server_list_with_invalid_changes_since(self, mock_parse_isotime): 'Invalid time value' ) - def test_server_list_v266_with_changes_before(self): - self.app.client_manager.compute.api_version = ( - api_versions.APIVersion('2.66')) - arglist = [ - '--changes-before', '2016-03-05T06:27:59Z', - '--deleted' - ] - verifylist = [ - ('changes_before', '2016-03-05T06:27:59Z'), - ('deleted', True), - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) - - self.search_opts['changes-before'] = '2016-03-05T06:27:59Z' - self.search_opts['deleted'] = True - - self.servers_mock.list.assert_called_with(**self.kwargs) - - self.assertEqual(self.columns, columns) - self.assertEqual(tuple(self.data), tuple(data)) - - @mock.patch.object(iso8601, 'parse_date', side_effect=iso8601.ParseError) - def test_server_list_v266_with_invalid_changes_before( - self, mock_parse_isotime): - self.app.client_manager.compute.api_version = ( - api_versions.APIVersion('2.66')) - - arglist = [ - '--changes-before', 'Invalid time value', - ] - verifylist = [ - ('changes_before', 'Invalid time value'), - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - try: - self.cmd.take_action(parsed_args) - self.fail('CommandError should be raised.') - except exceptions.CommandError as e: - self.assertEqual('Invalid changes-before value: Invalid time ' - 'value', str(e)) - mock_parse_isotime.assert_called_once_with( - 'Invalid time value' - ) - - def test_server_with_changes_before_pre_v266(self): - self.app.client_manager.compute.api_version = ( - api_versions.APIVersion('2.65')) - - arglist = [ - '--changes-before', '2016-03-05T06:27:59Z', - '--deleted' - ] - verifylist = [ - ('changes_before', '2016-03-05T06:27:59Z'), - ('deleted', True), - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - self.assertRaises(exceptions.CommandError, - self.cmd.take_action, - parsed_args) - - def test_server_list_v269_with_partial_constructs(self): - self.app.client_manager.compute.api_version = \ - api_versions.APIVersion('2.69') - - arglist = [] - verifylist = [] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # include "partial results" from non-responsive part of - # infrastructure. - server_dict = { - "id": "server-id-95a56bfc4xxxxxx28d7e418bfd97813a", - "status": "UNKNOWN", - "tenant_id": "6f70656e737461636b20342065766572", - "created": "2018-12-03T21:06:18Z", - "links": [ - { - "href": "http://fake/v2.1/", - "rel": "self" - }, - { - "href": "http://fake", - "rel": "bookmark" - } - ], - # We need to pass networks as {} because its defined as a property - # of the novaclient Server class which gives {} by default. If not - # it will fail at formatting the networks info later on. - "networks": {} - } - _server = compute_fakes.fakes.FakeResource( - info=server_dict, - ) - self.servers.append(_server) - columns, data = self.cmd.take_action(parsed_args) - # get the first three servers out since our interest is in the partial - # server. - next(data) - next(data) - next(data) - partial_server = next(data) - expected_row = ( - 'server-id-95a56bfc4xxxxxx28d7e418bfd97813a', - '', - 'UNKNOWN', - format_columns.DictListColumn({}), - '', - '', - ) - self.assertEqual(expected_row, partial_server) - def test_server_list_with_tag(self): self.app.client_manager.compute.api_version = api_versions.APIVersion( '2.26') @@ -4646,7 +4472,7 @@ def test_server_list_with_tag(self): self.servers_mock.list.assert_called_with(**self.kwargs) self.assertEqual(self.columns, columns) - self.assertEqual(tuple(self.data), tuple(data)) + self.assertEqual(self.data, tuple(data)) def test_server_list_with_tag_pre_v225(self): self.app.client_manager.compute.api_version = api_versions.APIVersion( @@ -4689,7 +4515,7 @@ def test_server_list_with_not_tag(self): self.servers_mock.list.assert_called_with(**self.kwargs) self.assertEqual(self.columns, columns) - self.assertEqual(tuple(self.data), tuple(data)) + self.assertEqual(self.data, tuple(data)) def test_server_list_with_not_tag_pre_v226(self): self.app.client_manager.compute.api_version = api_versions.APIVersion( @@ -4850,6 +4676,258 @@ def test_server_list_with_power_state(self): self.assertEqual(tuple(self.data), tuple(data)) +class TestServerListV273(_TestServerList): + + # Columns to be listed up. + columns = ( + 'ID', + 'Name', + 'Status', + 'Networks', + 'Image', + 'Flavor', + ) + columns_long = ( + 'ID', + 'Name', + 'Status', + 'Task State', + 'Power State', + 'Networks', + 'Image Name', + 'Image ID', + 'Flavor', + 'Availability Zone', + 'Host', + 'Properties', + ) + + def setUp(self): + super(TestServerListV273, self).setUp() + + # The fake servers' attributes. Use the original attributes names in + # nova, not the ones printed by "server list" command. + self.attrs['flavor'] = { + 'vcpus': self.flavor.vcpus, + 'ram': self.flavor.ram, + 'disk': self.flavor.disk, + 'ephemeral': self.flavor.ephemeral, + 'swap': self.flavor.swap, + 'original_name': self.flavor.name, + 'extra_specs': self.flavor.extra_specs, + } + + # The servers to be listed. + self.servers = self.setup_servers_mock(3) + self.servers_mock.list.return_value = self.servers + + Image = collections.namedtuple('Image', 'id name') + self.images_mock.return_value = [ + Image(id=s.image['id'], name=self.image.name) + # Image will be an empty string if boot-from-volume + for s in self.servers if s.image + ] + + # The flavor information is embedded, so now reason for this to be + # called + self.flavors_mock.list = mock.NonCallableMock() + + self.data = tuple( + ( + s.id, + s.name, + s.status, + format_columns.DictListColumn(s.networks), + # Image will be an empty string if boot-from-volume + self.image.name if s.image else server.IMAGE_STRING_FOR_BFV, + self.flavor.name, + ) for s in self.servers) + + def test_server_list_with_locked_pre_v273(self): + + arglist = [ + '--locked' + ] + verifylist = [ + ('locked', True) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises(exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.73 or greater is required', str(ex)) + + def test_server_list_with_locked(self): + + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.73') + arglist = [ + '--locked' + ] + verifylist = [ + ('locked', True) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.search_opts['locked'] = True + self.servers_mock.list.assert_called_with(**self.kwargs) + + self.assertItemsEqual(self.columns, columns) + self.assertItemsEqual(self.data, tuple(data)) + + def test_server_list_with_unlocked_v273(self): + + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.73') + arglist = [ + '--unlocked' + ] + verifylist = [ + ('unlocked', True) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.search_opts['locked'] = False + self.servers_mock.list.assert_called_with(**self.kwargs) + + self.assertItemsEqual(self.columns, columns) + self.assertItemsEqual(self.data, tuple(data)) + + def test_server_list_with_locked_and_unlocked(self): + + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.73') + arglist = [ + '--locked', + '--unlocked' + ] + verifylist = [ + ('locked', True), + ('unlocked', True) + ] + + ex = self.assertRaises( + utils.ParserException, + self.check_parser, self.cmd, arglist, verifylist) + self.assertIn('Argument parse failed', str(ex)) + + def test_server_list_with_changes_before(self): + self.app.client_manager.compute.api_version = ( + api_versions.APIVersion('2.66')) + arglist = [ + '--changes-before', '2016-03-05T06:27:59Z', + '--deleted' + ] + verifylist = [ + ('changes_before', '2016-03-05T06:27:59Z'), + ('deleted', True), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.search_opts['changes-before'] = '2016-03-05T06:27:59Z' + self.search_opts['deleted'] = True + + self.servers_mock.list.assert_called_with(**self.kwargs) + + self.assertItemsEqual(self.columns, columns) + self.assertItemsEqual(self.data, tuple(data)) + + @mock.patch.object(iso8601, 'parse_date', side_effect=iso8601.ParseError) + def test_server_list_with_invalid_changes_before( + self, mock_parse_isotime): + self.app.client_manager.compute.api_version = ( + api_versions.APIVersion('2.66')) + + arglist = [ + '--changes-before', 'Invalid time value', + ] + verifylist = [ + ('changes_before', 'Invalid time value'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('Invalid changes-before value: Invalid time ' + 'value', str(e)) + mock_parse_isotime.assert_called_once_with( + 'Invalid time value' + ) + + def test_server_with_changes_before_pre_v266(self): + self.app.client_manager.compute.api_version = ( + api_versions.APIVersion('2.65')) + + arglist = [ + '--changes-before', '2016-03-05T06:27:59Z', + '--deleted' + ] + verifylist = [ + ('changes_before', '2016-03-05T06:27:59Z'), + ('deleted', True), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + def test_server_list_v269_with_partial_constructs(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.69') + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + # include "partial results" from non-responsive part of + # infrastructure. + server_dict = { + "id": "server-id-95a56bfc4xxxxxx28d7e418bfd97813a", + "status": "UNKNOWN", + "tenant_id": "6f70656e737461636b20342065766572", + "created": "2018-12-03T21:06:18Z", + "links": [ + { + "href": "http://fake/v2.1/", + "rel": "self" + }, + { + "href": "http://fake", + "rel": "bookmark" + } + ], + # We need to pass networks as {} because its defined as a property + # of the novaclient Server class which gives {} by default. If not + # it will fail at formatting the networks info later on. + "networks": {} + } + server = compute_fakes.fakes.FakeResource( + info=server_dict, + ) + self.servers.append(server) + columns, data = self.cmd.take_action(parsed_args) + # get the first three servers out since our interest is in the partial + # server. + next(data) + next(data) + next(data) + partial_server = next(data) + expected_row = ( + 'server-id-95a56bfc4xxxxxx28d7e418bfd97813a', '', + 'UNKNOWN', format_columns.DictListColumn({}), '', '') + self.assertEqual(expected_row, partial_server) + + class TestServerLock(TestServer): def setUp(self): diff --git a/releasenotes/notes/fix-flavor-in-server-list-microversion-2.47-af200e9bb4747e2d.yaml b/releasenotes/notes/fix-flavor-in-server-list-microversion-2.47-af200e9bb4747e2d.yaml new file mode 100644 index 0000000000..fdb37bbbc9 --- /dev/null +++ b/releasenotes/notes/fix-flavor-in-server-list-microversion-2.47-af200e9bb4747e2d.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Add support for compute API microversion 2.47, which changes how flavor + details are included in server detail responses. In 2.46 and below, + only the flavor ID was shown in the server detail response. Starting in + 2.47, flavor information is embedded in the server response. The newer + behavior is now supported. From c8c4f76498de3380c7cbf80c5dc800a588bed649 Mon Sep 17 00:00:00 2001 From: Rodolfo Alonso Hernandez Date: Tue, 26 Oct 2021 13:03:22 +0000 Subject: [PATCH 2444/3095] Add --security-group to port list The neutron API supports filtering ports by security group. Closes-Bug: #1405057 Depends-On: https://review.opendev.org/c/openstack/openstacksdk/+/804979 Change-Id: I0f626882716c21ac200c1b929ea04664d21874d8 --- openstackclient/network/v2/port.py | 9 +++++++++ .../tests/unit/network/v2/test_port.py | 20 +++++++++++++++++++ ...-list-security-group-4af5d2e789174ff9.yaml | 5 +++++ 3 files changed, 34 insertions(+) create mode 100644 releasenotes/notes/port-list-security-group-4af5d2e789174ff9.yaml diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 132c384a0e..887c531808 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -610,6 +610,13 @@ def get_parser(self, prog_name): metavar='', help=_("List ports according to their name") ) + parser.add_argument( + '--security-group', + action='append', + dest='security_groups', + metavar='', + help=_("List only ports associated with this security group") + ) identity_common.add_project_domain_option_to_parser(parser) parser.add_argument( '--fixed-ip', @@ -682,6 +689,8 @@ def take_action(self, parsed_args): if parsed_args.fixed_ip: filters['fixed_ips'] = _prepare_filter_fixed_ips( self.app.client_manager, parsed_args) + if parsed_args.security_groups: + filters['security_groups'] = parsed_args.security_groups _tag.get_tag_filtering_args(parsed_args, filters) diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index 5f2a12836a..96edb74df8 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -1296,6 +1296,26 @@ def test_list_with_tag_options(self): self.assertEqual(self.columns, columns) self.assertCountEqual(self.data, list(data)) + def test_port_list_security_group(self): + arglist = [ + '--security-group', 'sg-id1', + '--security-group', 'sg-id2', + ] + verifylist = [ + ('security_groups', ['sg-id1', 'sg-id2']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = { + 'security_groups': ['sg-id1', 'sg-id2'], + 'fields': LIST_FIELDS_TO_RETRIEVE, + } + + self.network.ports.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertCountEqual(self.data, list(data)) + class TestSetPort(TestPort): diff --git a/releasenotes/notes/port-list-security-group-4af5d2e789174ff9.yaml b/releasenotes/notes/port-list-security-group-4af5d2e789174ff9.yaml new file mode 100644 index 0000000000..c68eeafbce --- /dev/null +++ b/releasenotes/notes/port-list-security-group-4af5d2e789174ff9.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Added ``--security-group`` option to the ``os port list`` command. This + option is appendable and multiple security group IDs can be provided. From bef70397a3e1240cc593b3fb34049f2ff6601e68 Mon Sep 17 00:00:00 2001 From: Rodolfo Alonso Hernandez Date: Tue, 27 Jul 2021 16:45:24 +0000 Subject: [PATCH 2445/3095] Add network update quota "limit_check" parameter This new parameter commands the Neutron server to first check the resource usage before setting the new quota limit. If the resource usage is below the new limit, the Neutron server will raise an exception. Depends-On: https://review.opendev.org/c/openstack/openstacksdk/+/806254 Depends-On: https://review.opendev.org/c/openstack/neutron/+/801470 Partial-Bug: #1936408 Change-Id: Idc1b99492d609eb699d0a6bef6cd760458a774f6 --- openstackclient/common/quota.py | 9 ++++ .../tests/functional/common/test_quota.py | 25 +++++++++++ .../tests/unit/common/test_quota.py | 43 +++++++++++++++++++ .../check-limit-quota-cc7f291dd1b537c1.yaml | 5 +++ 4 files changed, 82 insertions(+) create mode 100644 releasenotes/notes/check-limit-quota-cc7f291dd1b537c1.yaml diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index 643cb4e474..677cba038e 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -535,6 +535,12 @@ def get_parser(self, prog_name): action='store_true', help=_('Force quota update (only supported by compute)') ) + parser.add_argument( + '--check-limit', + action='store_true', + help=_('Check quota limit when updating (only supported by ' + 'network)') + ) return parser def take_action(self, parsed_args): @@ -561,6 +567,9 @@ def take_action(self, parsed_args): volume_kwargs[k] = value network_kwargs = {} + if parsed_args.check_limit: + network_kwargs['check_limit'] = True + if self.app.client_manager.is_network_endpoint_enabled(): for k, v in NETWORK_QUOTAS.items(): value = getattr(parsed_args, k, None) diff --git a/openstackclient/tests/functional/common/test_quota.py b/openstackclient/tests/functional/common/test_quota.py index 9c05746077..bf67101a32 100644 --- a/openstackclient/tests/functional/common/test_quota.py +++ b/openstackclient/tests/functional/common/test_quota.py @@ -11,6 +11,9 @@ # under the License. import json +import uuid + +from tempest.lib import exceptions from openstackclient.tests.functional import base @@ -165,3 +168,25 @@ def test_quota_set_class(self): # returned attributes self.assertTrue(cmd_output["key-pairs"] >= 0) self.assertTrue(cmd_output["snapshots"] >= 0) + + def test_quota_network_set_with_check_limit(self): + if not self.haz_network: + self.skipTest('No Network service present') + if not self.is_extension_enabled('quota-check-limit'): + self.skipTest('No "quota-check-limit" extension present') + + self.openstack('quota set --networks 40 ' + self.PROJECT_NAME) + cmd_output = json.loads(self.openstack( + 'quota list -f json --network' + )) + self.assertIsNotNone(cmd_output) + self.assertEqual(40, cmd_output[0]['Networks']) + + # That will ensure we have at least two networks in the system. + for _ in range(2): + self.openstack('network create --project %s %s' % + (self.PROJECT_NAME, uuid.uuid4().hex)) + + self.assertRaises(exceptions.CommandFailed, self.openstack, + 'quota set --networks 1 --check-limit ' + + self.PROJECT_NAME) diff --git a/openstackclient/tests/unit/common/test_quota.py b/openstackclient/tests/unit/common/test_quota.py index 8771359cb5..896a63a7bc 100644 --- a/openstackclient/tests/unit/common/test_quota.py +++ b/openstackclient/tests/unit/common/test_quota.py @@ -950,6 +950,49 @@ def test_quota_set_with_force(self): ) self.assertIsNone(result) + def test_quota_set_with_check_limit(self): + arglist = [ + '--subnets', str(network_fakes.QUOTA['subnet']), + '--volumes', str(volume_fakes.QUOTA['volumes']), + '--cores', str(compute_fakes.core_num), + '--check-limit', + self.projects[0].name, + ] + verifylist = [ + ('subnet', network_fakes.QUOTA['subnet']), + ('volumes', volume_fakes.QUOTA['volumes']), + ('cores', compute_fakes.core_num), + ('check_limit', True), + ('project', self.projects[0].name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + kwargs_compute = { + 'cores': compute_fakes.core_num, + } + kwargs_volume = { + 'volumes': volume_fakes.QUOTA['volumes'], + } + kwargs_network = { + 'subnet': network_fakes.QUOTA['subnet'], + 'check_limit': True, + } + self.compute_quotas_mock.update.assert_called_once_with( + self.projects[0].id, + **kwargs_compute + ) + self.volume_quotas_mock.update.assert_called_once_with( + self.projects[0].id, + **kwargs_volume + ) + self.network_mock.update_quota.assert_called_once_with( + self.projects[0].id, + **kwargs_network + ) + self.assertIsNone(result) + class TestQuotaShow(TestQuota): diff --git a/releasenotes/notes/check-limit-quota-cc7f291dd1b537c1.yaml b/releasenotes/notes/check-limit-quota-cc7f291dd1b537c1.yaml new file mode 100644 index 0000000000..171b4a5ae0 --- /dev/null +++ b/releasenotes/notes/check-limit-quota-cc7f291dd1b537c1.yaml @@ -0,0 +1,5 @@ +--- +features: + - Add ``--check-limit`` option to the ``openstack quota set`` command (only + for network commands). The network quota engine will check the resource + usage before setting the new quota limit. From 32e18253faa742aae5a4c9708a8a505c85ebb317 Mon Sep 17 00:00:00 2001 From: "Dr. Jens Harbott" Date: Tue, 7 Dec 2021 18:33:54 +0100 Subject: [PATCH 2446/3095] Fix RemoveServerVolume The nova API we're using to delete a server volume attachment needs to be handed a volume, not a volume attachment. Also make sure that we create an error if the volume isn't actually attached to the server. Signed-off-by: Dr. Jens Harbott Co-authored-by: Stephen Finucane Change-Id: I12abd3787ea47acb4da282d00fdc1989405a0564 --- openstackclient/compute/v2/server.py | 16 +++++----------- .../tests/unit/compute/v2/test_server.py | 10 ++++------ 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index fb13d30572..a18ce81012 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -3833,17 +3833,11 @@ def take_action(self, parsed_args): ignore_missing=False, ) - volume_attachments = compute_client.volume_attachments(server) - for volume_attachment in volume_attachments: - if volume_attachment.volume_id == volume.id: - compute_client.delete_volume_attachment( - volume_attachment, - server, - ) - break - else: - msg = _('Target volume attachment not found.') - raise exceptions.CommandError(msg) + compute_client.delete_volume_attachment( + volume, + server, + ignore_missing=False, + ) class RescueServer(command.Command): diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 00733e4a76..15be07fc6c 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -1016,10 +1016,6 @@ def setUp(self): self.cmd = server.RemoveServerVolume(self.app, None) def test_server_remove_volume(self): - self.sdk_client.volume_attachments.return_value = [ - self.volume_attachment - ] - arglist = [ self.servers[0].id, self.volumes[0].id, @@ -1036,8 +1032,10 @@ def test_server_remove_volume(self): self.assertIsNone(result) self.sdk_client.delete_volume_attachment.assert_called_once_with( - self.volume_attachment, - self.servers[0]) + self.volumes[0], + self.servers[0], + ignore_missing=False, + ) class TestServerAddNetwork(TestServer): From 4e9b9298429f5db505987853f98d2388b6745b13 Mon Sep 17 00:00:00 2001 From: "Dr. Jens Harbott" Date: Tue, 30 Nov 2021 09:21:56 +0100 Subject: [PATCH 2447/3095] Allow setting gateway when creating a router These options are not only valid when modifying a router, but also when one is created initially. Signed-off-by: Dr. Jens Harbott Change-Id: I3e12901f37cbd1639ac9dc9cc49b04114b80474c --- openstackclient/network/v2/router.py | 79 +++++++++++++------ .../tests/unit/network/v2/test_router.py | 37 +++++++++ ...ptions-create-router-97910a882b604652.yaml | 8 ++ 3 files changed, 101 insertions(+), 23 deletions(-) create mode 100644 releasenotes/notes/options-create-router-97910a882b604652.yaml diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index dde4eda99f..aeeec93175 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -111,6 +111,30 @@ def _get_attrs(client_manager, parsed_args): parsed_args.project_domain, ).id attrs['tenant_id'] = project_id + if parsed_args.external_gateway: + gateway_info = {} + n_client = client_manager.network + network = n_client.find_network( + parsed_args.external_gateway, ignore_missing=False) + gateway_info['network_id'] = network.id + if parsed_args.disable_snat: + gateway_info['enable_snat'] = False + if parsed_args.enable_snat: + gateway_info['enable_snat'] = True + if parsed_args.fixed_ip: + ips = [] + for ip_spec in parsed_args.fixed_ip: + if ip_spec.get('subnet', False): + subnet_name_id = ip_spec.pop('subnet') + if subnet_name_id: + subnet = n_client.find_subnet(subnet_name_id, + ignore_missing=False) + ip_spec['subnet_id'] = subnet.id + if ip_spec.get('ip-address', False): + ip_spec['ip_address'] = ip_spec.pop('ip-address') + ips.append(ip_spec) + gateway_info['external_fixed_ips'] = ips + attrs['external_gateway_info'] = gateway_info return attrs @@ -320,6 +344,32 @@ def get_parser(self, prog_name): "repeat option to set multiple availability zones)") ) _tag.add_tag_option_to_parser_for_create(parser, _('router')) + parser.add_argument( + '--external-gateway', + metavar="", + help=_("External Network used as router's gateway (name or ID)") + ) + parser.add_argument( + '--fixed-ip', + metavar='subnet=,ip-address=', + action=parseractions.MultiKeyValueAction, + optional_keys=['subnet', 'ip-address'], + help=_("Desired IP and/or subnet (name or ID) " + "on external gateway: " + "subnet=,ip-address= " + "(repeat option to set multiple fixed IP addresses)") + ) + snat_group = parser.add_mutually_exclusive_group() + snat_group.add_argument( + '--enable-snat', + action='store_true', + help=_("Enable Source NAT on external gateway") + ) + snat_group.add_argument( + '--disable-snat', + action='store_true', + help=_("Disable Source NAT on external gateway") + ) return parser @@ -338,6 +388,12 @@ def take_action(self, parsed_args): # tags cannot be set when created, so tags need to be set later. _tag.update_tags_for_set(client, obj, parsed_args) + if (parsed_args.disable_snat or parsed_args.enable_snat or + parsed_args.fixed_ip) and not parsed_args.external_gateway: + msg = (_("You must specify '--external-gateway' in order " + "to specify SNAT or fixed-ip values")) + raise exceptions.CommandError(msg) + display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) @@ -725,29 +781,6 @@ def take_action(self, parsed_args): msg = (_("You must specify '--external-gateway' in order " "to update the SNAT or fixed-ip values")) raise exceptions.CommandError(msg) - if parsed_args.external_gateway: - gateway_info = {} - network = client.find_network( - parsed_args.external_gateway, ignore_missing=False) - gateway_info['network_id'] = network.id - if parsed_args.disable_snat: - gateway_info['enable_snat'] = False - if parsed_args.enable_snat: - gateway_info['enable_snat'] = True - if parsed_args.fixed_ip: - ips = [] - for ip_spec in parsed_args.fixed_ip: - if ip_spec.get('subnet', False): - subnet_name_id = ip_spec.pop('subnet') - if subnet_name_id: - subnet = client.find_subnet(subnet_name_id, - ignore_missing=False) - ip_spec['subnet_id'] = subnet.id - if ip_spec.get('ip-address', False): - ip_spec['ip_address'] = ip_spec.pop('ip-address') - ips.append(ip_spec) - gateway_info['external_fixed_ips'] = ips - attrs['external_gateway_info'] = gateway_info if ((parsed_args.qos_policy or parsed_args.no_qos_policy) and not parsed_args.external_gateway): diff --git a/openstackclient/tests/unit/network/v2/test_router.py b/openstackclient/tests/unit/network/v2/test_router.py index 0324674817..04d9fe4836 100644 --- a/openstackclient/tests/unit/network/v2/test_router.py +++ b/openstackclient/tests/unit/network/v2/test_router.py @@ -186,6 +186,43 @@ def test_create_default_options(self): self.assertEqual(self.columns, columns) self.assertCountEqual(self.data, data) + def test_create_with_gateway(self): + _network = network_fakes.FakeNetwork.create_one_network() + _subnet = network_fakes.FakeSubnet.create_one_subnet() + self.network.find_network = mock.Mock(return_value=_network) + self.network.find_subnet = mock.Mock(return_value=_subnet) + arglist = [ + self.new_router.name, + '--external-gateway', _network.name, + '--enable-snat', + '--fixed-ip', 'ip-address=2001:db8::1' + ] + verifylist = [ + ('name', self.new_router.name), + ('enable', True), + ('distributed', False), + ('ha', False), + ('external_gateway', _network.name), + ('enable_snat', True), + ('fixed_ip', [{'ip-address': '2001:db8::1'}]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_router.assert_called_once_with(**{ + 'admin_state_up': True, + 'name': self.new_router.name, + 'external_gateway_info': { + 'network_id': _network.id, + 'enable_snat': True, + 'external_fixed_ips': [{'ip_address': '2001:db8::1'}], + }, + }) + self.assertFalse(self.network.set_tags.called) + self.assertEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + def _test_create_with_ha_options(self, option, ha): arglist = [ option, diff --git a/releasenotes/notes/options-create-router-97910a882b604652.yaml b/releasenotes/notes/options-create-router-97910a882b604652.yaml new file mode 100644 index 0000000000..f7d90b75c2 --- /dev/null +++ b/releasenotes/notes/options-create-router-97910a882b604652.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + It is now possible to add an external gateway to a router + immediately on creation. Previously it could only be done by + modifying the router after it had been created. This includes + the options to en- or disable SNAT and to specify a fixed-ip + on the external network. From b3cb85f1123b15c1ec4fafac9dcedc9381072a8b Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Mon, 6 Dec 2021 10:24:15 +0000 Subject: [PATCH 2448/3095] tests: Improve logging for executed commands We're seeing failures in a recently added tests, 'ServerTests.test_server_add_remove_volume' from 'openstackclient/tests/functional/compute/v2/test_server.py'. These failures are likely the result of slow CI nodes, but we don't have enough information in the CI logs to debug them. Starting logging the various commands executed in tests so that we can see these logs if and when tests fail. Change-Id: I4584dc5e6343fe8c8544431a527d8c3c7e7b3c5b Signed-off-by: Stephen Finucane --- openstackclient/tests/functional/base.py | 21 ++++++++---- .../functional/compute/v2/test_server.py | 33 ++++++++++++++----- 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/openstackclient/tests/functional/base.py b/openstackclient/tests/functional/base.py index 0ed7dff8c4..e89c5b9737 100644 --- a/openstackclient/tests/functional/base.py +++ b/openstackclient/tests/functional/base.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import logging import os import shlex import subprocess @@ -18,22 +19,30 @@ from tempest.lib import exceptions import testtools - ADMIN_CLOUD = os.environ.get('OS_ADMIN_CLOUD', 'devstack-admin') +LOG = logging.getLogger(__name__) def execute(cmd, fail_ok=False, merge_stderr=False): """Executes specified command for the given action.""" + LOG.debug('Executing: %s', cmd) cmdlist = shlex.split(cmd) stdout = subprocess.PIPE stderr = subprocess.STDOUT if merge_stderr else subprocess.PIPE + proc = subprocess.Popen(cmdlist, stdout=stdout, stderr=stderr) - result, result_err = proc.communicate() - result = result.decode('utf-8') + + result_out, result_err = proc.communicate() + result_out = result_out.decode('utf-8') + LOG.debug('stdout: %s', result_out) + LOG.debug('stderr: %s', result_err) + if not fail_ok and proc.returncode != 0: - raise exceptions.CommandFailed(proc.returncode, cmd, result, - result_err) - return result + raise exceptions.CommandFailed( + proc.returncode, cmd, result_out, result_err, + ) + + return result_out class TestCase(testtools.TestCase): diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py index cf4bcbc2a9..0558ef6213 100644 --- a/openstackclient/tests/functional/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -1195,19 +1195,19 @@ def test_server_add_remove_port(self): def test_server_add_remove_volume(self): volume_wait_for = volume_common.BaseVolumeTests.wait_for_status - name = uuid.uuid4().hex + server_name = uuid.uuid4().hex cmd_output = json.loads(self.openstack( 'server create -f json ' + '--network private ' + '--flavor ' + self.flavor_name + ' ' + '--image ' + self.image_name + ' ' + '--wait ' + - name + server_name )) self.assertIsNotNone(cmd_output['id']) - self.assertEqual(name, cmd_output['name']) - self.addCleanup(self.openstack, 'server delete --wait ' + name) + self.assertEqual(server_name, cmd_output['name']) + self.addCleanup(self.openstack, 'server delete --wait ' + server_name) server_id = cmd_output['id'] volume_name = uuid.uuid4().hex @@ -1225,7 +1225,7 @@ def test_server_add_remove_volume(self): cmd_output = json.loads(self.openstack( 'server add volume -f json ' + - name + ' ' + + server_name + ' ' + volume_name + ' ' + '--tag bar' )) @@ -1237,7 +1237,7 @@ def test_server_add_remove_volume(self): cmd_output = json.loads(self.openstack( 'server volume list -f json ' + - name + server_name )) self.assertEqual(volume_attachment_id, cmd_output[0]['ID']) @@ -1245,8 +1245,25 @@ def test_server_add_remove_volume(self): self.assertEqual(volume_id, cmd_output[0]['Volume ID']) volume_wait_for('volume', volume_name, 'in-use') - self.openstack('server remove volume ' + name + ' ' + volume_name) + + cmd_output = json.loads(self.openstack( + 'server event list -f json ' + + server_name + )) + self.assertEqual(2, len(cmd_output)) + self.assertIn('attach_volume', {x['Action'] for x in cmd_output}) + + self.openstack( + 'server remove volume ' + server_name + ' ' + volume_name + ) volume_wait_for('volume', volume_name, 'available') - raw_output = self.openstack('server volume list ' + name) + cmd_output = json.loads(self.openstack( + 'server event list -f json ' + + server_name + )) + self.assertEqual(3, len(cmd_output)) + self.assertIn('detach_volume', {x['Action'] for x in cmd_output}) + + raw_output = self.openstack('server volume list ' + server_name) self.assertEqual('\n', raw_output) From 9971d7253e9c3abd2e3940bf549ef8532ef929f9 Mon Sep 17 00:00:00 2001 From: Ritvik Vinodkumar Date: Wed, 1 Dec 2021 16:54:53 +0000 Subject: [PATCH 2449/3095] Switch add fixed IP to SDK Switch the add fixed IP command from novaclient to SDK. Change-Id: I4752ea7b4bfc17e04b8f46dbe9a68d938501a89e --- openstackclient/compute/v2/server.py | 44 ++-- .../tests/unit/compute/v2/test_server.py | 224 ++++++++++++++---- ...-add-fixed-ip-to-sdk-3d932d77633bc765.yaml | 3 + 3 files changed, 211 insertions(+), 60 deletions(-) create mode 100644 releasenotes/notes/migrate-add-fixed-ip-to-sdk-3d932d77633bc765.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index a18ce81012..0931a6ca01 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -237,30 +237,44 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - - server = utils.find_resource( - compute_client.servers, parsed_args.server) - - network = compute_client.api.network_find(parsed_args.network) - - kwargs = { - 'port_id': None, - 'net_id': network['id'], - 'fixed_ip': parsed_args.fixed_ip_address, - } + compute_client = self.app.client_manager.sdk_connection.compute + server = compute_client.find_server( + parsed_args.server, + ignore_missing=False + ) if parsed_args.tag: - if compute_client.api_version < api_versions.APIVersion('2.49'): + if not sdk_utils.supports_microversion(compute_client, '2.49'): msg = _( '--os-compute-api-version 2.49 or greater is required to ' 'support the --tag option' ) raise exceptions.CommandError(msg) - kwargs['tag'] = parsed_args.tag + if self.app.client_manager.is_network_endpoint_enabled(): + network_client = self.app.client_manager.network + net_id = network_client.find_network( + parsed_args.network, + ignore_missing=False + ).id + else: + net_id = parsed_args.network + + if not sdk_utils.supports_microversion(compute_client, '2.44'): + compute_client.add_fixed_ip_to_server( + server.id, + net_id + ) + return + + kwargs = { + 'net_id': net_id, + 'fixed_ip': parsed_args.fixed_ip_address, + } - server.interface_attach(**kwargs) + if parsed_args.tag: + kwargs['tag'] = parsed_args.tag + compute_client.create_server_interface(server.id, **kwargs) class AddFloatingIP(network_common.NetworkAndComputeCommand): diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 15be07fc6c..5d47b68784 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -217,104 +217,104 @@ def setUp(self): # Get the command object to test self.cmd = server.AddFixedIP(self.app, None) + # Mock network methods + self.find_network = mock.Mock() + self.app.client_manager.network.find_network = self.find_network + # Set add_fixed_ip method to be tested. self.methods = { 'interface_attach': None, } - def _test_server_add_fixed_ip(self, extralist, fixed_ip_address): - servers = self.setup_servers_mock(count=1) + @mock.patch.object(sdk_utils, 'supports_microversion') + def test_server_add_fixed_ip_pre_2_44(self, sm_mock): + sm_mock.return_value = False + + servers = self.setup_sdk_servers_mock(count=1) network = compute_fakes.FakeNetwork.create_one_network() - with mock.patch( - 'openstackclient.api.compute_v2.APIv2.network_find' - ) as net_mock: - net_mock.return_value = network + with mock.patch.object( + self.app.client_manager, + 'is_network_endpoint_enabled', + return_value=False + ): arglist = [ servers[0].id, network['id'], - ] + extralist + ] verifylist = [ ('server', servers[0].id), ('network', network['id']), - ('fixed_ip_address', fixed_ip_address), + ('fixed_ip_address', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - servers[0].interface_attach.assert_called_once_with( - port_id=None, - net_id=network['id'], - fixed_ip=fixed_ip_address, + self.sdk_client.add_fixed_ip_to_server.assert_called_once_with( + servers[0].id, + network['id'] ) self.assertIsNone(result) - def test_server_add_fixed_ip(self): - self._test_server_add_fixed_ip([], None) - - def test_server_add_specific_fixed_ip(self): - extralist = ['--fixed-ip-address', '5.6.7.8'] - self._test_server_add_fixed_ip(extralist, '5.6.7.8') - - def test_server_add_fixed_ip_with_tag(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.49') + @mock.patch.object(sdk_utils, 'supports_microversion') + def test_server_add_fixed_ip_pre_2_44_with_fixed_ip(self, sm_mock): + sm_mock.return_value = False - servers = self.setup_servers_mock(count=1) + servers = self.setup_sdk_servers_mock(count=1) network = compute_fakes.FakeNetwork.create_one_network() - with mock.patch( - 'openstackclient.api.compute_v2.APIv2.network_find' - ) as net_mock: - net_mock.return_value = network + with mock.patch.object( + self.app.client_manager, + 'is_network_endpoint_enabled', + return_value=False + ): arglist = [ servers[0].id, network['id'], - '--fixed-ip-address', '5.6.7.8', - '--tag', 'tag1', + '--fixed-ip-address', '5.6.7.8' ] verifylist = [ ('server', servers[0].id), ('network', network['id']), ('fixed_ip_address', '5.6.7.8'), - ('tag', 'tag1'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) - servers[0].interface_attach.assert_called_once_with( - port_id=None, - net_id=network['id'], - fixed_ip='5.6.7.8', - tag='tag1' + self.sdk_client.add_fixed_ip_to_server.assert_called_once_with( + servers[0].id, + network['id'] ) self.assertIsNone(result) - def test_server_add_fixed_ip_with_tag_pre_v249(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.48') + @mock.patch.object(sdk_utils, 'supports_microversion') + def test_server_add_fixed_ip_pre_2_44_with_tag(self, sm_mock): + sm_mock.return_value = False - servers = self.setup_servers_mock(count=1) + servers = self.setup_sdk_servers_mock(count=1) network = compute_fakes.FakeNetwork.create_one_network() - with mock.patch( - 'openstackclient.api.compute_v2.APIv2.network_find' - ) as net_mock: - net_mock.return_value = network + with mock.patch.object( + self.app.client_manager, + 'is_network_endpoint_enabled', + return_value=False + ): arglist = [ servers[0].id, network['id'], '--fixed-ip-address', '5.6.7.8', - '--tag', 'tag1', + '--tag', 'tag1' ] verifylist = [ ('server', servers[0].id), ('network', network['id']), ('fixed_ip_address', '5.6.7.8'), - ('tag', 'tag1'), + ('tag', 'tag1') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( exceptions.CommandError, self.cmd.take_action, @@ -323,6 +323,140 @@ def test_server_add_fixed_ip_with_tag_pre_v249(self): '--os-compute-api-version 2.49 or greater is required', str(ex)) + @mock.patch.object(sdk_utils, 'supports_microversion') + def test_server_add_fixed_ip_pre_2_49_with_tag(self, sm_mock): + sm_mock.side_effect = [False, True] + + servers = self.setup_sdk_servers_mock(count=1) + network = compute_fakes.FakeNetwork.create_one_network() + + with mock.patch.object( + self.app.client_manager, + 'is_network_endpoint_enabled', + return_value=False + ): + arglist = [ + servers[0].id, + network['id'], + '--fixed-ip-address', '5.6.7.8', + '--tag', 'tag1' + ] + verifylist = [ + ('server', servers[0].id), + ('network', network['id']), + ('fixed_ip_address', '5.6.7.8'), + ('tag', 'tag1') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.49 or greater is required', + str(ex)) + + @mock.patch.object(sdk_utils, 'supports_microversion') + def test_server_add_fixed_ip_post_2_49(self, sm_mock): + sm_mock.side_effect = [True, True] + + servers = self.setup_sdk_servers_mock(count=1) + network = compute_fakes.FakeNetwork.create_one_network() + + with mock.patch.object( + self.app.client_manager, + 'is_network_endpoint_enabled', + return_value=False + ): + arglist = [ + servers[0].id, + network['id'] + ] + verifylist = [ + ('server', servers[0].id), + ('network', network['id']) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.sdk_client.create_server_interface.assert_called_once_with( + servers[0].id, + net_id=network['id'], + fixed_ip=None + ) + self.assertIsNone(result) + + @mock.patch.object(sdk_utils, 'supports_microversion') + def test_server_add_fixed_ip_post_2_49_with_fixedip(self, sm_mock): + sm_mock.side_effect = [True, True] + + servers = self.setup_sdk_servers_mock(count=1) + network = compute_fakes.FakeNetwork.create_one_network() + + with mock.patch.object( + self.app.client_manager, + 'is_network_endpoint_enabled', + return_value=False + ): + arglist = [ + servers[0].id, + network['id'], + '--fixed-ip-address', '5.6.7.8' + ] + verifylist = [ + ('server', servers[0].id), + ('network', network['id']), + ('fixed_ip_address', '5.6.7.8') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.sdk_client.create_server_interface.assert_called_once_with( + servers[0].id, + net_id=network['id'], + fixed_ip='5.6.7.8' + ) + self.assertIsNone(result) + + @mock.patch.object(sdk_utils, 'supports_microversion') + def test_server_add_fixed_ip_post_2_49_with_tag(self, sm_mock): + sm_mock.side_effect = [True, True] + + servers = self.setup_sdk_servers_mock(count=1) + network = compute_fakes.FakeNetwork.create_one_network() + + with mock.patch.object( + self.app.client_manager, + 'is_network_endpoint_enabled', + return_value=False + ): + arglist = [ + servers[0].id, + network['id'], + '--fixed-ip-address', '5.6.7.8', + '--tag', 'tag1' + ] + verifylist = [ + ('server', servers[0].id), + ('network', network['id']), + ('fixed_ip_address', '5.6.7.8'), + ('tag', 'tag1') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.sdk_client.create_server_interface.assert_called_once_with( + servers[0].id, + net_id=network['id'], + fixed_ip='5.6.7.8', + tag='tag1' + ) + self.assertIsNone(result) + @mock.patch( 'openstackclient.api.compute_v2.APIv2.floating_ip_add' diff --git a/releasenotes/notes/migrate-add-fixed-ip-to-sdk-3d932d77633bc765.yaml b/releasenotes/notes/migrate-add-fixed-ip-to-sdk-3d932d77633bc765.yaml new file mode 100644 index 0000000000..79899b3e92 --- /dev/null +++ b/releasenotes/notes/migrate-add-fixed-ip-to-sdk-3d932d77633bc765.yaml @@ -0,0 +1,3 @@ +features: + - | + Switch the add fixed IP command from novaclient to SDK. From 0cde82dcd86205f9e190b1a4f02136e1d83797b9 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Tue, 14 Dec 2021 15:14:49 +0000 Subject: [PATCH 2450/3095] compute: Return information about fixed IP The compute API provides this information to us. We might as well use it. Change-Id: I5608fa80745975ce49712718452cfe296c0f64d2 Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server.py | 33 ++++- .../tests/unit/compute/v2/fakes.py | 28 +++++ .../tests/unit/compute/v2/test_server.py | 117 +++++++++++++----- 3 files changed, 145 insertions(+), 33 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 0931a6ca01..2c1e42cc79 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -204,7 +204,7 @@ def boolenv(*vars, default=False): return default -class AddFixedIP(command.Command): +class AddFixedIP(command.ShowOne): _description = _("Add fixed IP address to server") def get_parser(self, prog_name): @@ -231,7 +231,7 @@ def get_parser(self, prog_name): metavar='', help=_( 'Tag for the attached interface. ' - '(supported by --os-compute-api-version 2.52 or above)' + '(supported by --os-compute-api-version 2.49 or above)' ) ) return parser @@ -265,16 +265,39 @@ def take_action(self, parsed_args): server.id, net_id ) - return + return ((), ()) kwargs = { 'net_id': net_id, 'fixed_ip': parsed_args.fixed_ip_address, } - if parsed_args.tag: kwargs['tag'] = parsed_args.tag - compute_client.create_server_interface(server.id, **kwargs) + + interface = compute_client.create_server_interface(server.id, **kwargs) + + columns = ( + 'port_id', 'server_id', 'net_id', 'mac_addr', 'port_state', + 'fixed_ips', + ) + column_headers = ( + 'Port ID', 'Server ID', 'Network ID', 'MAC Address', 'Port State', + 'Fixed IPs', + ) + if sdk_utils.supports_microversion(compute_client, '2.49'): + columns += ('tag',) + column_headers += ('Tag',) + + return ( + column_headers, + utils.get_item_properties( + interface, + columns, + formatters={ + 'fixed_ips': format_columns.ListDictColumn, + }, + ), + ) class AddFloatingIP(network_common.NetworkAndComputeCommand): diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index 7618c229c5..2a53164735 100644 --- a/openstackclient/tests/unit/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -21,6 +21,7 @@ from novaclient import api_versions from openstack.compute.v2 import flavor as _flavor from openstack.compute.v2 import server +from openstack.compute.v2 import server_interface as _server_interface from openstack.compute.v2 import volume_attachment from openstackclient.api import compute_v2 @@ -1859,3 +1860,30 @@ def create_sdk_volume_attachments(attrs=None, methods=None, count=2): attrs, methods)) return volume_attachments + + +def create_one_server_interface(attrs=None): + """Create a fake SDK ServerInterface. + + :param dict attrs: A dictionary with all attributes + :param dict methods: A dictionary with all methods + :return: A fake ServerInterface object with various attributes set + """ + attrs = attrs or {} + + # Set default attributes. + server_interface_info = { + "fixed_ips": uuid.uuid4().hex, + "mac_addr": "aa:aa:aa:aa:aa:aa", + "net_id": uuid.uuid4().hex, + "port_id": uuid.uuid4().hex, + "port_state": "ACTIVE", + "server_id": uuid.uuid4().hex, + # introduced in API microversion 2.70 + "tag": "foo", + } + + # Overwrite default attributes. + server_interface_info.update(attrs) + + return _server_interface.ServerInterface(**server_interface_info) diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 5d47b68784..6553f90465 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -212,7 +212,7 @@ def run_method_with_sdk_servers(self, method_name, server_count): class TestServerAddFixedIP(TestServer): def setUp(self): - super(TestServerAddFixedIP, self).setUp() + super().setUp() # Get the command object to test self.cmd = server.AddFixedIP(self.app, None) @@ -221,13 +221,8 @@ def setUp(self): self.find_network = mock.Mock() self.app.client_manager.network.find_network = self.find_network - # Set add_fixed_ip method to be tested. - self.methods = { - 'interface_attach': None, - } - @mock.patch.object(sdk_utils, 'supports_microversion') - def test_server_add_fixed_ip_pre_2_44(self, sm_mock): + def test_server_add_fixed_ip_pre_v244(self, sm_mock): sm_mock.return_value = False servers = self.setup_sdk_servers_mock(count=1) @@ -255,10 +250,11 @@ def test_server_add_fixed_ip_pre_2_44(self, sm_mock): servers[0].id, network['id'] ) - self.assertIsNone(result) + # the legacy API operates asynchronously + self.assertEqual(((), ()), result) @mock.patch.object(sdk_utils, 'supports_microversion') - def test_server_add_fixed_ip_pre_2_44_with_fixed_ip(self, sm_mock): + def test_server_add_fixed_ip_pre_v244_with_fixed_ip(self, sm_mock): sm_mock.return_value = False servers = self.setup_sdk_servers_mock(count=1) @@ -287,10 +283,11 @@ def test_server_add_fixed_ip_pre_2_44_with_fixed_ip(self, sm_mock): servers[0].id, network['id'] ) - self.assertIsNone(result) + # the legacy API operates asynchronously + self.assertEqual(((), ()), result) @mock.patch.object(sdk_utils, 'supports_microversion') - def test_server_add_fixed_ip_pre_2_44_with_tag(self, sm_mock): + def test_server_add_fixed_ip_pre_v244_with_tag(self, sm_mock): sm_mock.return_value = False servers = self.setup_sdk_servers_mock(count=1) @@ -324,7 +321,7 @@ def test_server_add_fixed_ip_pre_2_44_with_tag(self, sm_mock): str(ex)) @mock.patch.object(sdk_utils, 'supports_microversion') - def test_server_add_fixed_ip_pre_2_49_with_tag(self, sm_mock): + def test_server_add_fixed_ip_pre_v249_with_tag(self, sm_mock): sm_mock.side_effect = [False, True] servers = self.setup_sdk_servers_mock(count=1) @@ -358,11 +355,13 @@ def test_server_add_fixed_ip_pre_2_49_with_tag(self, sm_mock): str(ex)) @mock.patch.object(sdk_utils, 'supports_microversion') - def test_server_add_fixed_ip_post_2_49(self, sm_mock): - sm_mock.side_effect = [True, True] + def test_server_add_fixed_ip(self, sm_mock): + sm_mock.side_effect = [True, False] servers = self.setup_sdk_servers_mock(count=1) network = compute_fakes.FakeNetwork.create_one_network() + interface = compute_fakes.create_one_server_interface() + self.sdk_client.create_server_interface.return_value = interface with mock.patch.object( self.app.client_manager, @@ -379,21 +378,41 @@ def test_server_add_fixed_ip_post_2_49(self, sm_mock): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) + expected_columns = ( + 'Port ID', + 'Server ID', + 'Network ID', + 'MAC Address', + 'Port State', + 'Fixed IPs', + ) + expected_data = ( + interface.port_id, + interface.server_id, + interface.net_id, + interface.mac_addr, + interface.port_state, + format_columns.ListDictColumn(interface.fixed_ips), + ) + + columns, data = self.cmd.take_action(parsed_args) + self.assertEqual(expected_columns, columns) + self.assertEqual(expected_data, tuple(data)) self.sdk_client.create_server_interface.assert_called_once_with( servers[0].id, net_id=network['id'], fixed_ip=None ) - self.assertIsNone(result) @mock.patch.object(sdk_utils, 'supports_microversion') - def test_server_add_fixed_ip_post_2_49_with_fixedip(self, sm_mock): + def test_server_add_fixed_ip_with_fixed_ip(self, sm_mock): sm_mock.side_effect = [True, True] servers = self.setup_sdk_servers_mock(count=1) network = compute_fakes.FakeNetwork.create_one_network() + interface = compute_fakes.create_one_server_interface() + self.sdk_client.create_server_interface.return_value = interface with mock.patch.object( self.app.client_manager, @@ -412,21 +431,43 @@ def test_server_add_fixed_ip_post_2_49_with_fixedip(self, sm_mock): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) + expected_columns = ( + 'Port ID', + 'Server ID', + 'Network ID', + 'MAC Address', + 'Port State', + 'Fixed IPs', + 'Tag', + ) + expected_data = ( + interface.port_id, + interface.server_id, + interface.net_id, + interface.mac_addr, + interface.port_state, + format_columns.ListDictColumn(interface.fixed_ips), + interface.tag, + ) + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual(expected_columns, columns) + self.assertEqual(expected_data, tuple(data)) self.sdk_client.create_server_interface.assert_called_once_with( servers[0].id, net_id=network['id'], fixed_ip='5.6.7.8' ) - self.assertIsNone(result) @mock.patch.object(sdk_utils, 'supports_microversion') - def test_server_add_fixed_ip_post_2_49_with_tag(self, sm_mock): - sm_mock.side_effect = [True, True] + def test_server_add_fixed_ip_with_tag(self, sm_mock): + sm_mock.side_effect = [True, True, True] servers = self.setup_sdk_servers_mock(count=1) network = compute_fakes.FakeNetwork.create_one_network() + interface = compute_fakes.create_one_server_interface() + self.sdk_client.create_server_interface.return_value = interface with mock.patch.object( self.app.client_manager, @@ -447,15 +488,35 @@ def test_server_add_fixed_ip_post_2_49_with_tag(self, sm_mock): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) + expected_columns = ( + 'Port ID', + 'Server ID', + 'Network ID', + 'MAC Address', + 'Port State', + 'Fixed IPs', + 'Tag', + ) + expected_data = ( + interface.port_id, + interface.server_id, + interface.net_id, + interface.mac_addr, + interface.port_state, + format_columns.ListDictColumn(interface.fixed_ips), + interface.tag, + ) + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual(expected_columns, columns) + self.assertEqual(expected_data, tuple(data)) self.sdk_client.create_server_interface.assert_called_once_with( servers[0].id, net_id=network['id'], fixed_ip='5.6.7.8', - tag='tag1' + tag='tag1', ) - self.assertIsNone(result) @mock.patch( @@ -5265,7 +5326,7 @@ def test_server_migrate_with_disk_overcommit(self): self.assertNotCalled(self.servers_mock.live_migrate) self.assertNotCalled(self.servers_mock.migrate) - def test_server_migrate_with_host_pre_2_56(self): + def test_server_migrate_with_host_pre_v256(self): # Tests that --host is not allowed for a cold migration # before microversion 2.56 (the test defaults to 2.1). arglist = [ @@ -6682,7 +6743,7 @@ def test_rebuild_with_user_data(self, mock_open): self.image, None, userdata=mock_file,) - def test_rebuild_with_user_data_pre_257(self): + def test_rebuild_with_user_data_pre_v257(self): self.app.client_manager.compute.api_version = \ api_versions.APIVersion('2.56') @@ -6722,7 +6783,7 @@ def test_rebuild_with_no_user_data(self): self.server.rebuild.assert_called_with( self.image, None, userdata=None) - def test_rebuild_with_no_user_data_pre_254(self): + def test_rebuild_with_no_user_data_pre_v254(self): self.app.client_manager.compute.api_version = \ api_versions.APIVersion('2.53') @@ -6814,7 +6875,7 @@ def test_rebuild_with_no_trusted_image_cert(self): self.server.rebuild.assert_called_with( self.image, None, trusted_image_certificates=None) - def test_rebuild_with_no_trusted_image_cert_pre_257(self): + def test_rebuild_with_no_trusted_image_cert_pre_v263(self): self.app.client_manager.compute.api_version = \ api_versions.APIVersion('2.62') From ba69870d86b5840dec06c6c30c8ddf50398bdb44 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 15 Dec 2021 17:32:10 +0000 Subject: [PATCH 2451/3095] compute: Fix weird option definition for 'server ssh' argparse allows you to specify multiple options for a given argument when declaring the argument. For some reason, we weren't doing this for the 'server ssh' command. There's no apparent reason for doing things this way and it's been that way since the beginning (2013) so let's not do that. We also add unit tests since they were missing and should exist. Change-Id: I67a9e6516d7057266210cd4083e9ddeb1cfaa5de Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server.py | 33 +------- .../tests/unit/compute/v2/test_server.py | 77 +++++++++++++++++++ 2 files changed, 81 insertions(+), 29 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index a18ce81012..5c603d04da 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -4462,51 +4462,26 @@ def get_parser(self, prog_name): help=_('Server (name or ID)'), ) parser.add_argument( - '--login', + '--login', '-l', metavar='', help=_('Login name (ssh -l option)'), ) parser.add_argument( - '-l', - dest='login', - metavar='', - help=argparse.SUPPRESS, - ) - parser.add_argument( - '--port', + '--port', '-p', metavar='', type=int, help=_('Destination port (ssh -p option)'), ) parser.add_argument( - '-p', - metavar='', - dest='port', - type=int, - help=argparse.SUPPRESS, - ) - parser.add_argument( - '--identity', + '--identity', '-i', metavar='', help=_('Private key file (ssh -i option)'), ) parser.add_argument( - '-i', - metavar='', - dest='identity', - help=argparse.SUPPRESS, - ) - parser.add_argument( - '--option', + '--option', '-o', metavar='', help=_('Options in ssh_config(5) format (ssh -o option)'), ) - parser.add_argument( - '-o', - metavar='